Skip to content

feat(cwd-anchored): drop CTX_DIR + ctx activate/deactivate; jumbo#90

Merged
josealekhine merged 5 commits into
mainfrom
feat/cwd-anchored-context
May 23, 2026
Merged

feat(cwd-anchored): drop CTX_DIR + ctx activate/deactivate; jumbo#90
josealekhine merged 5 commits into
mainfrom
feat/cwd-anchored-context

Conversation

@josealekhine
Copy link
Copy Markdown
Member

Implements specs/cwd-anchored-context.md end-to-end across two sessions. ctx is now anchored to its working directory: every resolver reads $PWD/.context/, and there is no env-var or walk-up. Mental model matches zensical (zensical.toml), terraform (.tf), and Claude Code (CLAUDE_PROJECT_DIR): the tool anchors to a config location, not a parallel declaration channel.

Net: 283 files changed, +6004 / -11546 (~5500 net deletion).

== Steps 1+2 (prior session, included here): ==

  • rc.ContextDir collapses to filepath.Join(cwd, dir.Context) after a single os.Stat; basename guard, env channel, ScanCandidates, ErrDirNotDeclared/ErrRelativeNotAllowed/ ErrNonCanonicalBasename all removed.
  • gitmeta.RequireGitTree collapses to os.Stat($PWD/.git); upward walk gone.
  • internal/cli/initialize/core/envmatch/ deleted (the env-vs-CWD mismatch guard becomes vestigial).
  • ctx init drops the env-vs-CWD branch; always targets $PWD/.context.

== Step 3 - Hook cd migration: ==

  • internal/assets/claude/hooks/hooks.json migrated from CTX_DIR="${CLAUDE_PROJECT_DIR:?...}/.context" ctx system <verb> to cd "${CLAUDE_PROJECT_DIR:?...}" && ctx system <verb> across all 22 hook commands.
  • check-anchor-drift hook entry removed (the drift it detected cannot occur under cwd-anchored).
  • Trace hooks (post-commit.sh, prepare-commit-msg.sh) and copilot-cli scripts already cwd-correct; no migration needed.

== Step 4 - activate/deactivate/anchor-drift deletion: ==

Packages deleted:

  • internal/cli/activate/ (whole subtree)
  • internal/cli/deactivate/ (whole subtree)
  • internal/write/activate/
  • internal/err/activate/
  • internal/cli/system/cmd/checkanchordrift/
  • internal/cli/system/core/anchor/
  • internal/config/shell/

Files deleted:

  • internal/config/embed/text/err_activate.go
  • internal/config/embed/text/check_anchor.go
  • internal/config/embed/flag/activate.go
  • internal/rc/candidates.go

Constants removed:

  • env.CtxDir, env.CtxDirInherited, env.Shell
  • hook.CheckAnchorDrift
  • flag.Shell
  • cmd.UseActivate / UseDeactivate / DescKeyActivate / DescKeyDeactivate
  • cmd.UseSystemCheckAnchorDrift / DescKeySystemCheckAnchorDrift
  • text.DescKeyErrActivateNoLocalContext, text.DescKeyCheckAnchorDrift*, text.DescKeyWriteInitActivateHint

Registrations trimmed:

  • bootstrap/group.go: activate/deactivate imports + getting-started entries removed.
  • cli/system/system.go: checkanchordrift import + Cmd() entry removed.

YAML pruned:

  • commands.yaml: activate, deactivate, system.checkanchordrift
  • examples.yaml: activate, deactivate, system.checkanchordrift
  • flags.yaml: activate.shell
  • errors.yaml: err.activate.no-local-context
  • hooks.yaml: check-anchor-drift.* (4 keys)
  • write.yaml: write.init-activate-hint + reflow of write.init-anatomy-preamble

== Step 5 - Docs sweep: ==

Recipes deleted (no longer applicable under cwd-anchored):

  • docs/recipes/activating-context.md
  • docs/recipes/external-context.md

Recipes updated (warning-block removal + eval-line removal, some prose rewrites): 22 recipes across hub, session, knowledge, permission, journal, scratchpad families. Bulk pattern was the !!! warning "Activate the Project First" boilerplate plus inline eval "$(ctx activate)" snippets.

Home docs updated: getting-started.md (steps renumbered), first-session.md, joining-a-project.md, is-ctx-right.md, configuration.md (External .context/ example removed, CTX_DIR env-var table row removed), opencode.md, vscode.md.

CLI docs updated: cli/index.md (activate/deactivate command rows removed, env-var table row removed, declaration-model preamble rewritten), cli/init-status.md (activate/deactivate sections deleted), cli/bootstrap.md, cli/mcp.md.

Operations docs updated: integrations.md, autonomous-loop.md, migration.md, upgrading.md, and 5 runbooks
(architecture-exploration.md, breaking-migration.md, hub-deployment.md, new-contributor.md, release-checklist.md).

AI-facing templates updated:

  • assets/claude/CLAUDE.md, assets/integrations/copilot-cli/INSTRUCTIONS.md, CLAUDE.md: ctx.ist/recipes/activating-context/ URL repointed to ctx.ist/home/getting-started/.
  • assets/integrations/opencode/plugin/index.ts: shell.env hook now sets output.cwd instead of injecting CTX_DIR; ctx.$ uses .cwd(ctx.directory).
  • 4 journal-enrich SKILL.md files: local var renamed CTX_DIR -> CTX_PATH (cosmetic; the var was already local-only).

Spec status updates:

  • specs/activate-strict-cwd.md: marked superseded by this spec.
  • specs/single-source-context-anchor.md: marked superseded (large sections; basename-guard rationale and no-walk-up principle survive in stronger form).

== Tests: ==

Cleaned dead t.Setenv("CTX_DIR", ...) calls from:
init_test.go (16 calls), pad_test.go (3), remind_test.go, checkreminder/run_test.go, notify_test.go, cli_test.go, change/core/cmd_test.go (replaced with t.Chdir + mkdir .context), drift/check_ext_test.go (mkdir .context), mcp/server/server_test.go.

mcp/cmd/root/cmd_test.go rewritten:
TestMcpServe_FailsClosedOnUnsetCTXDIR ->
TestMcpServe_FailsClosedWhenNoDotContext: now uses t.Chdir(t.TempDir()) and asserts the fail-closed behaviour under cwd-anchored.

User-facing warning updated: config/warn/warn.go's RCNoContextDir message changed from
"rc.RC: no CTX_DIR declared" to "rc.RC: no .context/ at $PWD".

== Also in this jumbo (prior + this session): ==

  • AI backend spec scaffolding (block A contract + block B+C sketched-not-contracted):

    • .context/briefs/20260522T001011Z-ctx-ai-backend.md
    • specs/ctx-ai-backend.md (with paste-ready task breakdown appended)
    • specs/ctx-ai-extraction-and-recall.md
  • KB editorial pipeline scaffolding (Phase KB landed in prior session):

    • .context/kb/ (topic pages, index, source-coverage, evidence-index)
    • .context/ingest/ (KB-RULES.md, OPERATOR.md, PROMPT.md, SESSION_LOG.md, schemas)
    • .context/archive/closeouts/ (folded closeouts)
    • first kb topic: .context/kb/topics/vllm/index.md (contrastive study of vLLM's example landing page vs ctx's recipe surface)
  • Substrate-vs-artifact placement convention codified: CONVENTIONS.md entry under File Organization plus a DECISIONS.md entry capturing the three coupling tests (consumed via ctx-mediated paths; tightly coupled to ctx pipeline machinery; authored under ctx skill discipline) and the three rejected alternatives.

  • Handover artifact migration (prior sessions):

    • .context/HANDOVER-2026-04-22.md removed (legacy flat shape)
    • .context/handovers/ now uses timestamped -.md (20260521T045353Z, 20260521T211326Z)
  • CI/lint helpers:

    • hack/lint-shellcheck.sh, hack/lint-powershell.sh
    • .github/workflows/ci.yml gains shellcheck + powershell jobs
  • Typography guide:

    • .context/typography.md (contributor/agent surface for Title Case, monotype ctx, banner shape, recipe Problem->TL;DR arc)
  • Many small assets edits (test surface fallout, skill polish, YAML key renames captured in prior commits, leftover here).

  • docs/ site/ regenerated to match prose edits.

== Verification: ==

  • golangci-lint run: 0 issues.
  • go test ./...: all packages green, no failures.

== Provenance: ==

Two-session work on feat/cwd-anchored-context; the user's jumbo strategy explicitly accumulates across sessions until a single comprehensive commit lands. Not pushed.

Spec: specs/cwd-anchored-context.md
Spec: specs/ctx-ai-backend.md
Spec: specs/ctx-ai-extraction-and-recall.md
Spec: specs/activate-strict-cwd.md (superseded)
Spec: specs/single-source-context-anchor.md (superseded)

Implements specs/cwd-anchored-context.md end-to-end across two
sessions. `ctx` is now anchored to its working directory: every
resolver reads $PWD/.context/, and there is no env-var or
walk-up. Mental model matches zensical (zensical.toml),
terraform (.tf), and Claude Code (CLAUDE_PROJECT_DIR): the tool
anchors to a config location, not a parallel declaration
channel.

Net: 283 files changed, +6004 / -11546 (~5500 net deletion).

== Steps 1+2 (prior session, included here): ==

* rc.ContextDir collapses to filepath.Join(cwd, dir.Context)
  after a single os.Stat; basename guard, env channel,
  ScanCandidates, ErrDirNotDeclared/ErrRelativeNotAllowed/
  ErrNonCanonicalBasename all removed.
* gitmeta.RequireGitTree collapses to os.Stat($PWD/.git);
  upward walk gone.
* internal/cli/initialize/core/envmatch/ deleted (the env-vs-CWD
  mismatch guard becomes vestigial).
* ctx init drops the env-vs-CWD branch; always targets
  $PWD/.context.

== Step 3 - Hook cd migration: ==

* internal/assets/claude/hooks/hooks.json migrated from
  `CTX_DIR="${CLAUDE_PROJECT_DIR:?...}/.context" ctx system <verb>`
  to `cd "${CLAUDE_PROJECT_DIR:?...}" && ctx system <verb>`
  across all 22 hook commands.
* check-anchor-drift hook entry removed (the drift it detected
  cannot occur under cwd-anchored).
* Trace hooks (post-commit.sh, prepare-commit-msg.sh) and
  copilot-cli scripts already cwd-correct; no migration needed.

== Step 4 - activate/deactivate/anchor-drift deletion: ==

Packages deleted:
* internal/cli/activate/ (whole subtree)
* internal/cli/deactivate/ (whole subtree)
* internal/write/activate/
* internal/err/activate/
* internal/cli/system/cmd/checkanchordrift/
* internal/cli/system/core/anchor/
* internal/config/shell/

Files deleted:
* internal/config/embed/text/err_activate.go
* internal/config/embed/text/check_anchor.go
* internal/config/embed/flag/activate.go
* internal/rc/candidates.go

Constants removed:
* env.CtxDir, env.CtxDirInherited, env.Shell
* hook.CheckAnchorDrift
* flag.Shell
* cmd.UseActivate / UseDeactivate / DescKeyActivate /
  DescKeyDeactivate
* cmd.UseSystemCheckAnchorDrift / DescKeySystemCheckAnchorDrift
* text.DescKeyErrActivateNoLocalContext,
  text.DescKeyCheckAnchorDrift*,
  text.DescKeyWriteInitActivateHint

Registrations trimmed:
* bootstrap/group.go: activate/deactivate imports + getting-started
  entries removed.
* cli/system/system.go: checkanchordrift import + Cmd() entry
  removed.

YAML pruned:
* commands.yaml: activate, deactivate, system.checkanchordrift
* examples.yaml: activate, deactivate, system.checkanchordrift
* flags.yaml: activate.shell
* errors.yaml: err.activate.no-local-context
* hooks.yaml: check-anchor-drift.* (4 keys)
* write.yaml: write.init-activate-hint + reflow of
  write.init-anatomy-preamble

== Step 5 - Docs sweep: ==

Recipes deleted (no longer applicable under cwd-anchored):
* docs/recipes/activating-context.md
* docs/recipes/external-context.md

Recipes updated (warning-block removal + eval-line removal,
some prose rewrites): 22 recipes across hub, session, knowledge,
permission, journal, scratchpad families. Bulk pattern was the
`!!! warning "Activate the Project First"` boilerplate plus
inline `eval "$(ctx activate)"` snippets.

Home docs updated: getting-started.md (steps renumbered),
first-session.md, joining-a-project.md, is-ctx-right.md,
configuration.md (External `.context/` example removed,
CTX_DIR env-var table row removed), opencode.md, vscode.md.

CLI docs updated: cli/index.md (activate/deactivate command
rows removed, env-var table row removed, declaration-model
preamble rewritten), cli/init-status.md (activate/deactivate
sections deleted), cli/bootstrap.md, cli/mcp.md.

Operations docs updated: integrations.md, autonomous-loop.md,
migration.md, upgrading.md, and 5 runbooks
(architecture-exploration.md, breaking-migration.md,
hub-deployment.md, new-contributor.md, release-checklist.md).

AI-facing templates updated:
* assets/claude/CLAUDE.md, assets/integrations/copilot-cli/INSTRUCTIONS.md,
  CLAUDE.md: ctx.ist/recipes/activating-context/ URL repointed
  to ctx.ist/home/getting-started/.
* assets/integrations/opencode/plugin/index.ts: shell.env hook
  now sets output.cwd instead of injecting CTX_DIR; ctx.$ uses
  .cwd(ctx.directory).
* 4 journal-enrich SKILL.md files: local var renamed
  CTX_DIR -> CTX_PATH (cosmetic; the var was already local-only).

Spec status updates:
* specs/activate-strict-cwd.md: marked superseded by this spec.
* specs/single-source-context-anchor.md: marked superseded
  (large sections; basename-guard rationale and no-walk-up
  principle survive in stronger form).

== Tests: ==

Cleaned dead t.Setenv("CTX_DIR", ...) calls from:
init_test.go (16 calls), pad_test.go (3), remind_test.go,
checkreminder/run_test.go, notify_test.go, cli_test.go,
change/core/cmd_test.go (replaced with t.Chdir + mkdir
.context), drift/check_ext_test.go (mkdir .context),
mcp/server/server_test.go.

mcp/cmd/root/cmd_test.go rewritten:
TestMcpServe_FailsClosedOnUnsetCTXDIR ->
TestMcpServe_FailsClosedWhenNoDotContext: now uses
t.Chdir(t.TempDir()) and asserts the fail-closed behaviour
under cwd-anchored.

User-facing warning updated: config/warn/warn.go's
RCNoContextDir message changed from
"rc.RC: no CTX_DIR declared" to "rc.RC: no .context/ at $PWD".

== Also in this jumbo (prior + this session): ==

* AI backend spec scaffolding (block A contract + block B+C
  sketched-not-contracted):
  - .context/briefs/20260522T001011Z-ctx-ai-backend.md
  - specs/ctx-ai-backend.md (with paste-ready task breakdown
    appended)
  - specs/ctx-ai-extraction-and-recall.md

* KB editorial pipeline scaffolding (Phase KB landed in
  prior session):
  - .context/kb/ (topic pages, index, source-coverage,
    evidence-index)
  - .context/ingest/ (KB-RULES.md, OPERATOR.md, PROMPT.md,
    SESSION_LOG.md, schemas)
  - .context/archive/closeouts/ (folded closeouts)
  - first kb topic: .context/kb/topics/vllm/index.md
    (contrastive study of vLLM's example landing page vs ctx's
    recipe surface)

* Substrate-vs-artifact placement convention codified:
  CONVENTIONS.md entry under File Organization plus a
  DECISIONS.md entry capturing the three coupling tests
  (consumed via ctx-mediated paths; tightly coupled to ctx
  pipeline machinery; authored under ctx skill discipline)
  and the three rejected alternatives.

* Handover artifact migration (prior sessions):
  - .context/HANDOVER-2026-04-22.md removed (legacy flat shape)
  - .context/handovers/ now uses timestamped <TS>-<slug>.md
    (20260521T045353Z, 20260521T211326Z)

* CI/lint helpers:
  - hack/lint-shellcheck.sh, hack/lint-powershell.sh
  - .github/workflows/ci.yml gains shellcheck + powershell jobs

* Typography guide:
  - .context/typography.md (contributor/agent surface for Title
    Case, monotype ctx, banner shape, recipe Problem->TL;DR arc)

* Many small assets edits (test surface fallout, skill polish,
  YAML key renames captured in prior commits, leftover here).

* docs/ site/ regenerated to match prose edits.

== Verification: ==

* golangci-lint run: 0 issues.
* go test ./...: all packages green, no failures.

== Provenance: ==

Two-session work on feat/cwd-anchored-context; the user's
jumbo strategy explicitly accumulates across sessions until a
single comprehensive commit lands. Not pushed.

Spec: specs/cwd-anchored-context.md
Spec: specs/ctx-ai-backend.md
Spec: specs/ctx-ai-extraction-and-recall.md
Spec: specs/activate-strict-cwd.md (superseded)
Spec: specs/single-source-context-anchor.md (superseded)
Signed-off-by: Jose Alekhinne <jose@ctx.ist>
@josealekhine josealekhine requested a review from bilersan as a code owner May 22, 2026 22:47
@josealekhine josealekhine self-assigned this May 22, 2026
Signed-off-by: Jose Alekhinne <jose@ctx.ist>
@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages Bot commented May 22, 2026

Deploying ctx with  Cloudflare Pages  Cloudflare Pages

Latest commit: a1f23fc
Status: ✅  Deploy successful!
Preview URL: https://8a113814.ctx-bhl.pages.dev
Branch Preview URL: https://feat-cwd-anchored-context.ctx-bhl.pages.dev

View logs

Earlier cwd-anchored refactor mutated `output.cwd = input.cwd` in
the shell.env hook, but the OpenCode SDK type for that hook's
output exposes only `env`, not `cwd` — `npx tsc --noEmit` failed
with TS2339.

Removed the shell.env handler entirely:

- Under cwd-anchored there are no env vars to inject (ctx no
  longer reads CTX_DIR or any sibling).
- The SDK does not expose a way to force the agent's shell cwd
  from the plugin, so the hook cannot serve its original purpose
  (anchoring the agent's shell at the project root).
- Plugin-internal subprocess calls remain anchored via
  `ctx.$.cwd(ctx.directory)`; only the agent-side shell tool is
  affected.

User-visible consequence (documented in the leading comment):
users must launch OpenCode from the project root for agent-issued
`ctx` commands to resolve. The recipe (docs/home/opencode.md)
already reflects this in its setup section.

Verified locally with `cd tools/typecheck/opencode && npx tsc
--noEmit` (clean). golangci-lint and go test ./... unchanged
(green).

Spec: specs/cwd-anchored-context.md
Signed-off-by: Jose Alekhinne <jose@ctx.ist>
Signed-off-by: Jose Alekhinne <jose@ctx.ist>
Signed-off-by: Jose Alekhinne <jose@ctx.ist>
@josealekhine josealekhine merged commit 573425c into main May 23, 2026
16 checks passed
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