Skip to content

Fix/steering canonical example#85

Merged
josealekhine merged 5 commits into
mainfrom
fix/steering-canonical-example
May 12, 2026
Merged

Fix/steering canonical example#85
josealekhine merged 5 commits into
mainfrom
fix/steering-canonical-example

Conversation

@josealekhine
Copy link
Copy Markdown
Member

No description provided.

The four foundation steering files scaffolded by `ctx init` --
product.md, tech.md, structure.md, workflow.md -- were still
their original placeholder bodies in this project. With
`inclusion: always` and `priority: 10`, that means every tool
call in every AI session has been injecting "Describe the
product, its goals, and target users" and similar fill-in-the-
blank prompts into the agent's context as if they were
behavioral rules.

ctx is the canonical example of ctx usage. A clone-and-read
reader sees scaffolded placeholders and concludes the maintainer
didn't fill them in -- which is precisely what happened. This
commit fixes the example by example, before any docs or
tooling changes.

Each file is now project-specific and intentionally non-
overlapping with CONSTITUTION.md / CONVENTIONS.md / CLAUDE.md:
steering carries the "behavioral rules tied to prompt" layer
that adds to those, rather than duplicating them.

- product.md (50 lines): load-bearing constraints (local-first,
  single binary, git-friendly, tool-agnostic, no telemetry) and
  explicit out-of-scope items (cloud sync, embedded LLM,
  AI-tool lock-in).

- tech.md (63 lines): Go 1.26 statically linked, foreign-
  language assets via //go:embed (cross-reference to
  internal/assets/README.md), separately-published VS Code
  extension, hard constraints (no CGO, no runtime deps, no
  network in normal operation), companion MCP servers.

- structure.md (66 lines): top-level directory table; where new
  files go (extend existing internal/ packages, sibling tools/
  for dev tooling about embedded assets, editors/ for
  separately-published deliverables); where they do not (not in
  internal/assets/ unless actually embedded).

- workflow.md (70 lines): branch-before-commit, never-push, DCO
  sign-off, Spec trailer on every commit, lint+test before
  commit, conventional subject prefixes, no `_ =` discards / no
  panic, persistence cadence (ctx commit / decision / learning
  add as you go).

The self-documenting HTML comment from each scaffold ("delete
this comment once you've customized the file") was removed, per
its own instruction.

This commit fixes the symptom on this project only. The
underlying class of bug -- unmodified placeholders silently
contaminating every prompt -- needs the tombstone mechanism
proposed by the maintainer (tombstone string in scaffold + load-
time skip + opt-out flag); a follow-up commit will land that.

Spec: specs/internal-assets-readme.md
Signed-off-by: Jose Alekhinne <jose@ctx.ist>
Unfilled foundation steering files scaffolded by `ctx steering
init` were silently injected into the agent context packet on
every tool call. With `inclusion: always` and `priority: 10`
defaults, that meant "Describe the product, its goals, and
target users" and similar fill-in-the-blank prompts were being
prepended to every agent turn until the user customized them.
This affected every steering consumer: `ctx agent` (Claude Code
/ Codex hook path), MCP `ctx_steering_get` (mid-task Claude
fetches), and `ctx steering sync` (Cursor / Cline / Kiro
native exports).

This commit introduces a literal "tombstone" marker that the
scaffold writes into each foundation body. The marker is HTML-
comment-shaped (invisible in Markdown rendering, distinctive
enough for reliable detection) and language-neutral (literal
string match, not natural-language heuristic, so non-English
body customizations still remove it).

The user removes the line as part of customizing the file. As
long as the line is present, every consumer skips the file:

- `ctx agent` (internal/cli/agent/core/steering/steering.go):
  LoadBodies skips tombstoned files and emits a warn.Warn line
  per skip so direct CLI users see the breadcrumb. Hook
  contexts swallow stderr (per hooks.json's `2>/dev/null`); the
  goal there is just to stop the contamination, not to nag.

- MCP ctx_steering_get (internal/mcp/handler/steering.go):
  same skip + warn.Warn. MCP host (Claude Code) surfaces
  subprocess stderr in its MCP server logs.

- `ctx steering sync` (internal/steering/sync.go): SyncTool
  and StaleFiles both skip tombstoned files. Tombstoned files
  appear in the SyncReport's Skipped list rather than being
  silently written into `.cursor/rules/`, `.clinerules/`, or
  `.kiro/steering/`.

Files:

- internal/config/steering/steering.go: defines Tombstone as
  a package-level const (single source of truth; tombstone.go
  in internal/steering/ re-exports for convenience).

- internal/steering/tombstone.go + tombstone_test.go: the
  detection function `HasTombstone(body string) bool`, plus
  five unit tests covering present / absent / empty /
  partial-match / mid-body cases.

- internal/config/warn/warn.go: adds SteeringUnfilled format
  string for the stderr breadcrumb. (Audit conventions require
  user-facing strings to live in config/, not as inline
  literals in handler code.)

- internal/assets/commands/text/write.yaml: adds the tombstone
  line to each of the four foundation body templates
  (product, tech, structure, workflow). New `ctx steering
  init` runs scaffold with the marker; existing pre-change
  scaffolds do not have it (see migration note below).

- internal/steering/sync_test.go: adds two integration tests:
  SyncTool_SkipsTombstonedFile (verifies the native export
  skip) and StaleFiles_SkipsTombstonedFile (verifies the
  staleness check skip).

Migration note: existing users with pre-change unfilled
scaffolded files do NOT have the new marker, so the load-time
skip won't catch them; they continue to leak placeholders
until either re-scaffolded or manually edited. The follow-up
docs reweight (commit pending) will surface this.

What this commit does not do:
- It does not make `ctx steering init` refuse to re-run when
  existing files still have tombstones. That was a secondary
  ask; the load-time skip stops the actual harm, and forcing
  re-init to error would interfere with idempotency. Can be
  added as a separate follow-up if needed.
- It does not add an enhanced `--no-steering` flag that also
  removes the steering directory. `ctx init --no-steering-init`
  already exists for opt-out; the directory-removal extension
  was tagged "maybe" in the proposal and is left for a follow-
  up if usage shows the need.

Spec: specs/internal-assets-readme.md
Signed-off-by: Jose Alekhinne <jose@ctx.ist>
The earlier section "1. Initialize Context" carried five
paragraphs of steering guidance buried inside it -- the four
foundation files, the inclusion-mode defaults, the
"open each file and replace the placeholder content" call to
action, and the opt-out flag. In practice readers walked past
the prose, deferred steering customization, and never came
back. The canonical example of this failure mode was visible
in ctx's own repo: even the maintainer had not filled in his
own scaffolded steering files (fixed in commit a2e2c29).

This commit reweights the Quick Start so steering gets the
narrative weight its consequences warrant:

- Step 1 (Initialize Context) keeps the `ctx init` invocation
  and adds a brief forward-pointer mentioning the four
  foundation files exist as placeholders. The five-paragraph
  steering deep-dive moves out.

- NEW Step 2 (Customize Your Steering Files) is the promoted
  beat. Frames steering as "behavioral rules prepended to
  every AI prompt," lists the four files with what to fill
  in, and -- this is the new content from the tombstone
  mechanism in commit 415b1d6 -- explicitly states that
  scaffolded files ship with a tombstone marker and are
  silently skipped on every load path until the marker is
  removed. Includes the opt-out path (`--no-steering-init`)
  for users who don't want steering at all.

- Steps 3-7 (was 2-6) renumber.

The chronological order is preserved: init scaffolds the
files, then the user customizes them, then activates the
project. The new ordering reflects what users actually need
to do, not what hides easily in the prose.

site/ regenerated via `make site`. Only the source MD page
and its two consumers (search.json + the page's own
index.html) changed; the nav structure itself didn't, so the
rebuild is small.

Spec: specs/internal-assets-readme.md
Signed-off-by: Jose Alekhinne <jose@ctx.ist>
CONSTITUTION mandates "No Broken Windows" / "Completion Over
Motion": fix obvious issues when encountered, no half measures.
The brand convention `ctx` (monotype backtick wherever
possible, frontmatter titles being the documented exception)
had drifted across the entire docs tree. A programmatic sweep
caught 236 bare `ctx` tokens across 81 source files and
wrapped each one in backticks, preserving content already
inside backtick spans and code fences.

The sweep respects:

- Triple-backtick fenced code blocks, including indented
  fences inside MkDocs `!!!` admonitions (caught a regression
  on first pass where indented JSON inside admonitions was
  corrupted; FENCE regex now matches `^\s*` + triple-backtick
  to handle both root-level and indented fences).

- Existing inline-code spans on the same line (parser tracks
  backtick pairs and never wraps `ctx` inside an already-
  protected range).

- Frontmatter reserved keys (title, name, description, icon)
  and image alt-text (![ctx]) -- per the documented exception
  for frontmatter titles, which avoid backticks entirely.

- Link-reference definitions: `[name]: url "title"` lines.

- The copyright-header comment block at the top of every
  file: `#   /    ctx:`.

- Package identifiers via `@`: `ctx@activememory-ctx` left
  intact (added `@` to the word-exclusion set after seeing
  this pattern in install snippets).

Known false-positives that the detector flags but the
transform correctly left alone: 13 multi-line inline-code
spans (the open backtick is on one line, the close on the
next). CommonMark permits this; the transform's line-at-a-
time logic detects the unclosed-on-this-line state and treats
the rest of the line as protected, which is the right
behavior. The detector is line-scoped, so it can't see across
line boundaries and registers false positives.

Two outright corruptions surfaced during validation and were
fixed manually:

- docs/home/context-files.md:532 -- a multi-line code span
  the transform misjudged in its second-line content, leaving
  ``ctx` steering preview` (double-backtick + orphan close).
  Reverted to the original `ctx steering preview`.

- docs/reference/comparison.md:218 -- same pattern: the
  transform produced ``ctx` serve` from a multi-line span;
  reverted to `ctx serve`.

One YAML frontmatter breakage on docs/blog/2026-02-03-the-
attention-budget.md: the sweep wrapped `ctx` inside a
`topics:` list-item, producing `- `ctx` primitives` which is
invalid YAML (a value starting with backtick is not a valid
unquoted scalar). The fix follows the documented frontmatter
exception -- drop backticks in YAML metadata, since the bare
word is fine in that surface (YAML topics are tags-like
metadata, not rendered prose).

site/ regenerated via `make site`. The sweep touched 81
source files; the regen produced corresponding changes in the
published site output (every page's nav also rebuilds when
the source set changes, hence the larger total file count).

Spec: specs/internal-assets-readme.md
Signed-off-by: Jose Alekhinne <jose@ctx.ist>
After the brand-backtick sweep (commit 61aab85) surfaced
three classes of breakage (admonition-indented fences,
multi-line inline-code spans, YAML list-item values), the
underlying skip-set requirements for any future
programmatic sweep across docs/ are worth persisting. The
next contributor doing em-dash replacement, brand rename,
or i18n string extraction will hit the same edge cases.

Spec: specs/internal-assets-readme.md
Signed-off-by: Jose Alekhinne <jose@ctx.ist>
@josealekhine josealekhine self-assigned this May 12, 2026
@josealekhine josealekhine requested a review from bilersan as a code owner May 12, 2026 06:15
@josealekhine josealekhine merged commit 88d5287 into main May 12, 2026
14 checks passed
@josealekhine josealekhine deleted the fix/steering-canonical-example branch May 12, 2026 06:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant