Skip to content

ctx fixes#74

Merged
josealekhine merged 11 commits into
mainfrom
feat/ctx-fixes
May 4, 2026
Merged

ctx fixes#74
josealekhine merged 11 commits into
mainfrom
feat/ctx-fixes

Conversation

@josealekhine
Copy link
Copy Markdown
Member

@josealekhine josealekhine commented May 3, 2026

The CLI structure was asymmetrical; and other changes.

See the commit messages for the entire 🐴 💩 that had accumulated.

The main breaking change is the ctx tasks are now noun-anchored

Instead of ctx add task it is ctx task add

It has been always like that;
but apparently somewhere down the line some agent "decided" that ctx add task would be more natural.

No. And it creates a bucket load of inconsistencies.

Reverted!

cc: @parlakisik @bilersan @hamzaerbay @CoderMungan
tagging y'all; since you both maintain and actively use ctx and I don't want you to be surprised when you rebuild it.

@v0lkan told me "those agents are like highly-gifted high-potential exceptional kids; but still kids; sometimes they find fusion, and sometime they eat glue at the corner of the classroom."

He damn is right; and I'm the Kindergarten teacher here, making them behave.

…d by 423016c

Commit 423016c (single-source-context-anchor) ran `ctx init` against
populated context and answered the silent "Overwrite existing
context?" prompt with `y`, replacing 5878 curated lines with the
embedded scaffolding templates from internal/assets/context/.

Restored from git (pre-423016c3 blobs) with post-butchering
additions layered back:

- TASKS.md: 2138-line base + Phase CP block (Ceremony Profiles,
  added 2026-04-26 in commit 3815e94)
- DECISIONS.md: 1801-line base + 2 entries from 4ba3d86
  (t.Setenv for subprocess env, state.Dir/(string,error) tightening)
- LEARNINGS.md: 1667-line base + 3 entries from 4ba3d86
  (confident-comments trap, filepath.Join('',rel) trap, parallel
  go test ~/.claude/settings.json race)
- CONVENTIONS.md: clean restore, no post-butchering edits to layer

The init-time UX defect that enabled this incident is captured in
specs/ctx-init-overwrite-safety.md along with the implementation
plan to make it impossible to recur.

Spec: specs/ctx-init-overwrite-safety.md
Signed-off-by: Jose Alekhinne <jose@ctx.ist>
Captures the design for preventing the 5878-line context loss
incident from 2026-04-25 (commit 423016c) recurring. Two
compounding defects identified:

1. ctx init silently overwrites populated context on "y" or
   --force, with no blast-radius shown and no backup taken.
2. A hook (suspected: the session-anchor jsonl-path-* writer)
   materializes .context/ in fresh empty projects when it should
   bail. This couples to (1) — naive "refuse if .context/ exists"
   would punish first-time users.

Fix order in the embedded task list: Phase B (stop phantom
.context/) before Phase A (refuse-by-default), then Phase C
(docs/recipes/CLAUDE.md sweep) and Phase D (verification). Tasks
live at the end of the spec rather than in TASKS.md (spec+task
combo per user direction).

Replaces --force with --reset; --reset enumerates files,
backs up to .context/.backup-init-<ISO>/, and refuses in
non-interactive mode.

Spec: specs/ctx-init-overwrite-safety.md
Signed-off-by: Jose Alekhinne <jose@ctx.ist>
The CLI mixes two organizing principles for context-artifact
operations: noun-first (ctx task complete, ctx task archive,
ctx decision reindex) and verb-first (ctx add task). Add is the
lone outlier — every other verb in the artifact namespace is
noun-first. This spec flips add to noun-first too, removing the
asymmetry.

Surfaced by an in-session incident: assistant referenced the
slash-skill names (ctx-decision-add, ctx-learning-add); user
checked ctx decision add / ctx task add on the CLI; user found
nothing and reasonably concluded the schema-enforcing commands
had been deleted. They hadn't — they live at ctx add <noun> —
but the CLI/skill/docstring naming dissonance is real and
recurring. Three places already imply the noun-first form is
canonical (internal/write/add/doc.go, specs/explicit-context-dir.md,
the 2026-04-19 journal entry).

Decisions baked in:

- Hard cut: ctx add <noun> removed in the same commit as
  ctx <noun> add (matches prior namespace-cleanup pattern).
- ctx convention add ships; ctx convention reindex deferred
  (CONVENTIONS.md has no INDEX block today).

Phase D expanded to cover the full source-tree sweep:
godocs, cobra Example fields, embed text, specs, recipes,
home docs, runbooks, blog editor's notes (history preserved),
CLAUDE.md/AGENT_PLAYBOOK templates, README, generated CLI
reference, plus a CI drift gate. Includes the migration sed
for the breaking-change release-notes entry.

Spec: specs/cli-add-symmetry.md
Signed-off-by: Jose Alekhinne <jose@ctx.ist>
Removes the verb-first ctx add parent and registers a noun-first
add subcommand on each artifact noun (task, decision, learning,
plus a new convention parent). The CLI now follows a single
organizing principle: every operation on a context artifact lives
under that artifact's noun parent.

Phase A — new noun commands:
  - ctx task add        (internal/cli/task/cmd/add/)
  - ctx decision add    (internal/cli/decision/cmd/add/)
  - ctx learning add    (internal/cli/learning/cmd/add/)
  - ctx convention add  (internal/cli/convention/{,cmd/add/})
    plus the new ctx convention parent itself

Each is a thin adapter (~3 lines) that calls the shared add
core. Flag set, validation, error messages, and exit codes
match the prior ctx add <noun> exactly. The new convention
parent currently hosts only add; reindex deferred until
CONVENTIONS.md grows an INDEX block (per spec).

Phase B — hard-cut removal:
  - internal/cli/add/cmd/ deleted entirely (cmd/root/, cmd/doc.go)
  - internal/cli/add/{add,add_test,doc}.go deleted; replaced by a
    new top-level doc.go that documents the package as the home
    for shared add machinery
  - cmd.UseAdd and cmd.DescKeyAdd retired
  - dead internal/inspect.StartsWithCtxMarker removed (its only
    consumer was the deleted add cmd coverage test)

Shared core:
  - internal/cli/add/core/run/ holds the Run function (moved
    from internal/cli/add/cmd/root/run.go)
  - internal/cli/add/core/build/ exposes Cmd(noun, descKey,
    useStr) so each noun adapter is a one-liner

Tests:
  - per-noun add tests under each cmd/add/ (task, decision,
    learning, convention) — happy-path + provenance-required
  - bootstrap_test, cli_test (binary integration), task_test,
    compact_test updated to invoke noun-first form
  - bootstrap expected-commands list: drop "add", add "convention"

Doc.go updates:
  - decision/doc.go, learning/doc.go, task/doc.go now document the
    cmd/add subcommand (TestDocGoSubcommandDrift gate)

YAML: commands.yaml and examples.yaml grew task.add, decision.add,
learning.add, convention, convention.add entries; the verb-first
add: parent entries were removed. Parent docs for task/decision/
learning updated to list add in their subcommands section.

Spec: specs/cli-add-symmetry.md
Signed-off-by: Jose Alekhinne <jose@ctx.ist>
Companion to f43264ed (CLI flip). The slash skills already had
noun-first names (ctx-task-add, ctx-decision-add, etc) but their
SKILL.md bodies still showed `ctx add <noun>` invocations and
"prefer this skill over raw `ctx add <noun>`" comparison lines.

Updated 10 skill files to use the new `ctx <noun> add` form:
  - ctx-task-add, ctx-decision-add, ctx-learning-add,
    ctx-convention-add (the four direct add wrappers)
  - ctx-reflect, ctx-brainstorm, ctx-wrap-up, ctx-remind,
    ctx-pad, ctx-commit (cross-references)

Spec: specs/cli-add-symmetry.md
Signed-off-by: Jose Alekhinne <jose@ctx.ist>
Final phase of the cli-add-symmetry flip (companion to f43264ed
and 8bde9ed5). Rewrites every active reference to the retired
ctx add <noun> form to use the new ctx <noun> add canonical form.

Scope (38 files):
  - README.md, SECURITY.md
  - docs/cli/{connect,connection,context}.md
  - docs/home/{common-workflows,context-files,first-session,
    is-ctx-right,joining-a-project,repeated-mistakes}.md
  - docs/operations/{autonomous-loop,
    runbooks/hub-deployment,runbooks/sanitize-permissions}.md
  - docs/recipes/{external-context,guide-your-agent,
    hub-getting-started,hub-overview,hub-personal,hub-team,
    index,knowledge-capture,session-lifecycle,steering,
    task-management}.md
  - docs/reference/skills.md
  - .context/AGENT_PLAYBOOK.md
  - .claude/skills/_ctx-qa/SKILL.md
  - internal/assets/context/AGENT_PLAYBOOK.md (embedded ctx init
    template)
  - internal/assets/integrations/copilot-cli/skills/* (9 SKILL.md
    files mirroring the Claude skills for the copilot-cli
    integration)
  - internal/assets/tpl/doc.go
  - internal/cli/add/core/normalize/doc.go
  - internal/index/index_test.go (test invocation)

Deliberately untouched:
  - .context/{DECISIONS,LEARNINGS,TASKS}.md — historical content,
    represent state at write-time
  - .context/journal/, .context/journal-site/, .context/archive/ —
    immutable history
  - docs/blog/ — published posts; rewriting silently rewrites
    history
  - specs/ — each spec is a snapshot of intent at spec-time;
    cli-add-symmetry.md itself necessarily references both
    forms when describing the flip

Spec: specs/cli-add-symmetry.md
Signed-off-by: Jose Alekhinne <jose@ctx.ist>
…ontext/

Provenance.Emit is intentionally unconditional — it prints the
session/branch/commit line on every hook fire regardless of init
state. But Emit -> ContextFreePct -> ReadTokenInfo -> FindJSONLPath
-> state.Dir() ends up calling SafeMkdirAll on .context/state/,
materializing a .context/ in projects that have never run ctx init.

Symptom: a fresh project with no real context shows up with a
.context/ containing only state/jsonl-path-<sessionID>. The
session-anchor cache writeback was the visible artifact, but the
underlying bug is FindJSONLPath calling state.Dir() (which
unconditionally MkdirAll's its target) before checking whether the
project has been initialized at all.

Fix: gate FindJSONLPath on state.Initialized() and bail with
("", nil) when false. The contract holds for callers — they treat
empty path as "no token data, no suffix to render" — and the
phantom .context/ no longer appears.

Test: TestFindJSONLPathDoesNotMaterializeContext exercises the
gate against a temp project where CTX_DIR is declared but
.context/ has never been initialized; asserts the directory does
not appear after the call.

Phase B of specs/ctx-init-overwrite-safety.md. Phase A (refuse-by-
default in ctx init, --reset, backup) is the next commit.

Spec: specs/ctx-init-overwrite-safety.md
Signed-off-by: Jose Alekhinne <jose@ctx.ist>
The previous "Overwrite existing context? [y/N]" prompt destroyed
5878 lines of curated TASKS/DECISIONS/LEARNINGS/CONVENTIONS in
the 2026-04-25 incident (commit 423016c). The prompt did not
enumerate which files would be overwritten, did not warn that
templates would replace curated content, and did not back anything
up. The --force flag silently bypassed it entirely.

This commit hardens ctx init against that whole class of footgun.

New behavior (specs/ctx-init-overwrite-safety.md, Phase A):
  - When .context/ exists with any populated essential file
    (TASKS.md / DECISIONS.md / LEARNINGS.md / CONVENTIONS.md /
    CONSTITUTION.md), init refuses with errInit.ErrContextPopulated,
    enumerating the populated files and pointing at --reset.
  - --force is retired. --reset replaces it as the destructive
    escape hatch with stricter semantics: enumerates files,
    backs them up to .context/.backup-init-<UTC-ISO>/ before
    overwriting, prompts y/N for confirmation, and refuses
    when --caller is set (editor / scripted entry points
    cannot reset).
  - When .context/ exists with no essential files (only
    state/, hook scratch, etc.), init still scaffolds the
    missing templates without refusal — no behavior change for
    the partial-init recovery path.

Implementation:
  - internal/cli/initialize/core/backup/: WriteSnapshot copies
    populated files into a timestamped backup directory.
  - internal/cli/initialize/core/validate/PopulatedFiles:
    returns the basenames of populated essential files (replaces
    the bool-returning EssentialFilesPresent, which is removed).
  - internal/err/initialize/{ErrContextPopulated, ErrResetRequiresInteractive,
    Populated, ResetRequiresInteractive, BackupMkdir, BackupRead,
    BackupWrite}: new sentinels and wrappers, all routed through
    desc.Text/cfgInit constants.
  - internal/config/initialize/: new package for compile-time
    constants (sentinel error messages, backup naming, reset
    flag literal). Lives separately from desc.Text because the
    sentinel vars initialize before YAML lookup is ready.
  - internal/write/initialize.{InfoResetPrompt, InfoBackupWritten}:
    user-facing prompt and post-backup confirmation, both routed
    through desc.Text.
  - internal/cli/initialize/cmd/root: --force renamed to --reset
    in cmd.go and run.go; existing-context branch rewritten
    around the refuse/reset/scaffold-missing trichotomy. Reads
    confirmation from cmd.InOrStdin so tests can pipe "y\n".
  - internal/flagbind: BindBoolFlagsP retired (no callers after
    init's flag refactor).

Tests:
  - TestRunInit_RefuseWhenPopulated: second init must refuse
    and leave the curated TASKS.md untouched.
  - TestRunInit_ResetRequiresInteractive: --reset --caller
    refuses with ErrResetRequiresInteractive.
  - TestRunInit_ResetWithConfirmationBacksUp: --reset with "y"
    creates .backup-init-<ISO>/ holding the curated content,
    then scaffolds fresh templates.
  - TestRunInit_ResetDeclinedNoChanges: --reset with "n" does
    not modify files and does not create a backup directory.
  - TestRunInit_FlagsExist: --reset present, --force absent.
  - TestInitSkipsExistingSteeringHooksSkillsDirs: drops the
    --force flag — partial-init scaffold still works without it.

Spec: specs/ctx-init-overwrite-safety.md
Signed-off-by: Jose Alekhinne <jose@ctx.ist>
Companion to a893a136 (refuse-by-default + --reset). Updates
every active reference to the retired ctx init --force form.

Scope:
  - docs/cli/init-status.md
  - docs/operations/{migration,upgrading}.md
  - docs/operations/runbooks/breaking-migration.md
  - internal/assets/claude/skills/ctx-drift/SKILL.md
  - internal/assets/integrations/copilot-cli/skills/ctx-drift/SKILL.md
  - internal/config/embed/text/err_backup.go
  - internal/err/backup/{backup,doc}.go
    (the legacy ctx backup err domain referenced --force in its
    user-facing recovery hint)

Deliberately untouched:
  - .context/{DECISIONS,LEARNINGS,TASKS}.md, journal/, archive/
  - docs/blog/ (published posts)
  - specs/ (snapshots of intent at spec-time)

Phase C of specs/ctx-init-overwrite-safety.md.

Spec: specs/ctx-init-overwrite-safety.md
Signed-off-by: Jose Alekhinne <jose@ctx.ist>
Audit after the hard cuts surfaced four classes of leftover drift:

  - internal/assets/commands/commands.yaml (init's long-help
    Examples block) still showed "ctx init --force"
  - internal/assets/commands/text/ui.yaml (drift.stale-header
    error message) suggested "run ctx init --force to sync"
  - docs/cli/index.md still listed "ctx add" in the Context
    table; missing a row for the new "ctx convention" parent
  - docs/cli/context.md "### ctx add" section described the
    retired verb-first surface

This commit fixes all four.

Audit also confirmed (no changes needed):
  - MCP server: handler.Add calls entry.ValidateAndWrite
    directly (Go function, not a subprocess), so the CLI flip
    does not affect the ctx_add MCP tool. Doc comments
    referencing "ctx_add" describe the MCP tool name (verb-
    first by design at the MCP layer) and stay correct.
  - copilot/copilot-instructions.md: no verb-first add refs.
  - .kiro: no integration assets exist (only copilot,
    copilot-cli, and agents.md live under
    internal/assets/integrations/).
  - site/search.json contains a stale hit (1 occurrence) but
    that's a built artifact regenerated by the docs-site
    pipeline; should not be hand-edited.

Spec: specs/cli-add-symmetry.md
Signed-off-by: Jose Alekhinne <jose@ctx.ist>
@josealekhine josealekhine self-assigned this May 3, 2026
@josealekhine josealekhine requested a review from bilersan as a code owner May 3, 2026 23:08
@josealekhine josealekhine merged commit 19678be into main May 4, 2026
11 of 12 checks passed
@josealekhine josealekhine deleted the feat/ctx-fixes branch May 4, 2026 01:16
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