From 85c67b4b21555218ef0d74f4a98785f4753a8ca1 Mon Sep 17 00:00:00 2001 From: Tom Boucher Date: Sat, 23 May 2026 18:42:47 -0400 Subject: [PATCH] chore: invalidate bug-2543 outdated namespace invariant + document /gsd- migration in CONTEXT.md (#164) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * test(meta): invalidate bug-2543 outdated /gsd: namespace invariant The "no /gsd- hyphen form in source files" scan (test 2 of the describe block) is skipped via test.skip. Motivation: - Bug-3584 (2026-05-15) introduced runtime-slash.cjs, which intentionally emits `/gsd-${token}` for skills-based runtimes. This is the correct canonical form for runtime-persisted strings (ROADMAP.md, STATE.md, recommended_actions, fix hints). - Bug-2543 was last updated 2026-05-12, three days BEFORE bug-3584 landed. The "no hyphen form" invariant was never updated to reflect the new two-tier model. - PR #154 first-pass: an agent misread bug-2543 and reverted the correct `/gsd-plan-phase` to `/gsd:plan-phase` in phase-lifecycle-policy.ts:156, breaking tests/bug-3584-runtime-slash-emitters.test.cjs. A 2nd-pass agent reverted. The canonical active invariant for runtime-emitter context is: tests/bug-3584-runtime-slash-emitters.test.cjs The remaining 4 tests in this file (commands/gsd/ existence, command filename slug format, transformer behavior, non-command identifier safety) are still valid and remain active — only test 2 is skipped. This avoids vacuous-truth (a full describe-block skip would let all assertions pass trivially). See CONTEXT.md § "Slash-command form: /gsd- vs /gsd:" (added in the companion commit) for the full two-tier model documentation. User directive: 2026-05-23 session authorizing this invalidation. Co-Authored-By: Claude Sonnet 4.6 * docs(context): document /gsd- slash-command migration for AI agents Adds CONTEXT.md § "Slash-command form: /gsd- (current) vs /gsd: (legacy)" to give future agents an unambiguous reference for which form to use in which context. Covers: - The two-tier model (source text → colon; runtime-emitted strings → hyphen) - The runtime-emitter authority: get-shit-done/bin/lib/runtime-slash.cjs - The canonical invariant test: bug-3584-runtime-slash-emitters.test.cjs - The PR #154 incident: how an agent misread bug-2543 and applied the wrong form - An explicit "Context for AI agents" block: stop and re-read if bug-2543 is influencing a patch - A note that DEFECT.AGENT-RETIRED-SLASH-SYNTAX-DRIFT.* predicates (written pre-two-tier) are stale for runtime-emitter contexts Also updates the stale DEFECT predicates at the end of CONTEXT.md with a clarifying note (the predicates remain for historical reference; the new § supersedes their fix-forward guidance for runtime-emitter contexts). Motivation: PR #154 first-pass incident (2026-05-23 session). Canonical runtime contract: tests/bug-3584-runtime-slash-emitters.test.cjs. Companion: test(meta) commit invalidating bug-2543's scan. Co-Authored-By: Claude Sonnet 4.6 * test(meta): re-activate bug-2543 scan as scoped invariant, exclude runtime-emitter contexts Codex adversarial review of PR #164 [high finding]: the single content-scanning test in bug-2543 was test.skip, leaving a vacuous test surface. The transformer/ filename unit tests remained active but no live source surface was guarded. Fix: replace test.skip with an active scoped invariant. Scope change: - REMOVED get-shit-done/bin/lib/ from SEARCH_DIRS entirely. That directory is runtime-emitter territory (runtime-slash.cjs, *.generated.cjs, phase-lifecycle-policy.ts) and intentionally uses /gsd- (hyphen) per bug-3584's contract. Scanning it causes false positives that led to PR #154 first-pass incident. - Added RUNTIME_EMITTER_EXCLUDES set documenting exactly why each file is exempt. - Remaining SEARCH_DIRS (workflows/, references/, templates/, commands/gsd/, agents/, hooks/) are Claude-facing source — colon form is correct there. Verified: 9/9 tests pass, 0 skipped. Canonical runtime-emitter contract: tests/bug-3584-runtime-slash-emitters.test.cjs bug-2543's scoped scope: excludes bin/lib/ (runtime-emitter contexts). * docs(context): rewrite slash-command section as directory-level matrix Codex adversarial review of PR #164 [high finding]: the previous CONTEXT.md section was internally contradictory — line 616 claimed /gsd- was globally canonical while lines 634/640 correctly stated Claude-facing source text uses /gsd:. Same document; opposite claims. Root cause: the first push of this section (2026-05-23) overstated the hyphen form as universal, when the project has always had a two-tier model. Fix: replace the section with an unambiguous directory-level matrix. New structure: - Single table mapping each directory/surface to its correct form and enforcement. - "How to choose" decision tree (4 steps, replaces ambiguous prose). - "What was WRONG previously" retains historical motivation and adds the PR #164 contradiction incident to the record. - "Context for AI agents" updated: ban mass-rewrites based on single test failure, cite the two-tier model explicitly. Two-tier model (unchanged from reality, now clearly documented): - Claude-facing source (commands/, agents/, workflows/, etc.): /gsd: colon. - Runtime-emitter contexts (runtime-slash.cjs, *.generated.cjs, ROADMAP.md persistence): /gsd- hyphen per bug-3584 invariant. Canonical authorities: bug-2543 (colon contract), bug-3584 (hyphen contract). * fix(workflows): replace dead /gsd-* tokens with live registry forms Codex adversarial review of PR #164 [medium finding]: stale slash-command references in user-facing workflow content. Registry-backed sweep confirmed the following tokens are not in commands/gsd/ registry. Tokens removed/updated (dead token → live registry form): 1. /gsd-remove-workspace → /gsd:workspace --remove File: get-shit-done/workflows/list-workspaces.md:58 Reason: no commands/gsd/remove-workspace.md; remove-workspace is a subcommand of /gsd:workspace (commands/gsd/workspace.md, --remove flag). 2. /gsd-list-workspaces → /gsd:workspace --list File: get-shit-done/workflows/remove-workspace.md:35 Reason: no commands/gsd/list-workspaces.md; list-workspaces is a subcommand of /gsd:workspace (commands/gsd/workspace.md, --list flag). 3. /gsd-list-phase-assumptions → /gsd:discuss-phase --assumptions File: get-shit-done/workflows/list-phase-assumptions.md:17-18 (usage block) Reason: no commands/gsd/list-phase-assumptions.md; the workflow is invoked via commands/gsd/discuss-phase.md with --assumptions flag. 4. /gsd-list-phase-assumptions 2 → /gsd:discuss-phase 2 --assumptions File: get-shit-done/references/continuation-format.md:59 Reason: same as #3. Sweep scope: user-facing markdown only (workflows/, references/). Runtime-emitter hyphen-form references in bin/lib/ are guarded by bug-3584 and were not touched. * chore(164): add changeset fragment for adversarial-review fixes The 3 commits addressing Codex review touched user-facing surfaces (get-shit-done/workflows/, get-shit-done/references/, CONTEXT.md, bug-2543 test). Add fragment to satisfy changeset-lint. Refs #164 --------- Co-authored-by: Claude Sonnet 4.6 --- .changeset/nimble-seals-munch.md | 5 ++ CONTEXT.md | 41 +++++++++++++ .../references/continuation-format.md | 2 +- .../workflows/list-phase-assumptions.md | 4 +- get-shit-done/workflows/list-workspaces.md | 2 +- get-shit-done/workflows/remove-workspace.md | 2 +- tests/bug-2543-gsd-slash-namespace.test.cjs | 58 ++++++++++++++++--- 7 files changed, 101 insertions(+), 13 deletions(-) create mode 100644 .changeset/nimble-seals-munch.md diff --git a/.changeset/nimble-seals-munch.md b/.changeset/nimble-seals-munch.md new file mode 100644 index 000000000..7216c52fe --- /dev/null +++ b/.changeset/nimble-seals-munch.md @@ -0,0 +1,5 @@ +--- +type: Fixed +pr: 164 +--- +Re-activate bug-2543 scan as scoped invariant (excludes runtime-emitter contexts), rewrite CONTEXT.md slash-command guidance as directory-level matrix, replace dead /gsd-* tokens in workflow and reference docs with live registry forms. diff --git a/CONTEXT.md b/CONTEXT.md index 99eab6a46..89b555dee 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -617,4 +617,45 @@ Migration plan: Phase 1 (#3465) seam additions complete; Phase 2 (#3466) targets `PROC.TRIAGE.routing-incoming=stale-bug-already-fixed to close as duplicate of originating issue + cite fix PR + first stable tag; release-publish-or-backport to ready-for-human; reporter-can-self-test to awaiting-retest` `PROC.TRIAGE.comment-shape=lead with "duplicate of #NNNN, fixed by PR #MMMM, in v1.X.Y"; show current code snippet proving bug-surface gone; give @latest and @next upgrade commands; close` + +--- + +## Slash-command form: directory-level matrix + +There is NO single global canonical form. The form depends on the directory and runtime context. Use this table: + +| Surface | Form | Source of truth | Enforced by | +|---|---|---|---| +| `agents/*.md` (Claude Code agent definitions) | `/gsd:` (colon) | Claude Code agent file convention | (Claude Code runtime) | +| `commands/gsd/*.md` (Claude Code slash commands) | `/gsd:` (colon) | Claude Code slash-command convention | (Claude Code runtime) | +| `get-shit-done/workflows/**/*.md` (workflow docs read by Claude Code) | `/gsd:` (colon, when invoking commands) | Claude Code slash-command convention | Live command registry (`tests/helpers/live-command-registry.cjs`) | +| `get-shit-done/bin/lib/runtime-slash.cjs` and runtime-emitter contexts | `/gsd-` (hyphen) | bug-3584 invariant | `tests/bug-3584-runtime-slash-emitters.test.cjs` | +| ROADMAP.md / STATE.md persistence strings | `/gsd-` (hyphen) | bug-3584 invariant | `tests/bug-3584-runtime-slash-emitters.test.cjs` | +| `*.generated.cjs` files | matches the TS source's emitted form | Generator | Freshness checks per ADR-3524 | +| CHANGELOG.md / .changeset / ADRs | whichever form was used at the time | Historical record | (no enforcement) | + +### How to choose + +1. Are you writing a slash-command reference that a Claude Code session will execute? → `/gsd:` (colon). +2. Are you writing a string that will be persisted to ROADMAP.md or returned by a runtime emitter (recommended_actions, fix hints)? → `/gsd-` (hyphen). +3. Is it a generated CJS file? Don't hand-edit; fix the generator's source. +4. Unsure? Check both `tests/bug-2543-gsd-slash-namespace.test.cjs` (Claude-facing colon contract) and `tests/bug-3584-runtime-slash-emitters.test.cjs` (runtime-emitter hyphen contract). + +### What was WRONG previously + +- `tests/bug-2543-gsd-slash-namespace.test.cjs` originally enforced the OPPOSITE invariant ("no `/gsd-` hyphen form anywhere in source files"), which was OUTDATED — it codified the pre-migration state (commit `73c1af51`, 2026-05-12). +- Bug-3584 introduced `runtime-slash.cjs` on 2026-05-15 (three days AFTER bug-2543 was last updated), creating a two-tier model. Bug-2543 was never updated to reflect the new model. +- An agent fix on PR #154 misread bug-2543 and changed `/gsd-plan-phase` → `/gsd:plan-phase` in `sdk/src/query/phase-lifecycle-policy.ts:156`, breaking bug-3584's runtime contract. A 2nd-pass agent reverted. +- The first version of this CONTEXT.md section (2026-05-23 first push) claimed `/gsd-` was globally canonical. That was incorrect — the project has a two-tier model. Codex adversarial review of PR #164 surfaced the contradiction; this rewrite corrects it. +- Bug-2543's content scan was `test.skip` from 2026-05-23 to avoid the PR #154 incident. The Codex adversarial review of PR #164 flagged that skip as leaving a vacuous test surface. The scan was re-activated as a scoped invariant (excluding `get-shit-done/bin/lib/` entirely) in the PR #164 follow-up commits. + +### Context for AI agents + +If you see a slash-command reference and aren't sure which form is right: +- Default to the table above. +- If the context isn't covered, ask before changing it. +- DO NOT mass-rewrite slash forms based on a single test failure — both bug-2543 and bug-3584 are active and they enforce opposite rules in opposite contexts. +- PR #154 first-pass incident illustrates the failure mode of getting this wrong. + +The DEFECT predicates below (`DEFECT.AGENT-RETIRED-SLASH-SYNTAX-DRIFT.*`) were written before the two-tier model and refer to bug-2543 as the authoritative invariant test. That predicate is now STALE for runtime-emitter contexts. The "fix-forward" in those predicates applies only to Claude-facing source text drift, not to `runtime-slash.cjs` or other emitter modules. `PROC.TRIAGE.no-duplicate-label=this repo has no duplicate label; framing lives in comment text + closing the issue` diff --git a/get-shit-done/references/continuation-format.md b/get-shit-done/references/continuation-format.md index 01921b26a..c3152381d 100644 --- a/get-shit-done/references/continuation-format.md +++ b/get-shit-done/references/continuation-format.md @@ -56,7 +56,7 @@ Standard format for presenting next steps after completing a command or workflow **Also available:** - Review plan before executing -- `/gsd-list-phase-assumptions 2` — check assumptions +- `/gsd:discuss-phase 2 --assumptions` — check assumptions --- ``` diff --git a/get-shit-done/workflows/list-phase-assumptions.md b/get-shit-done/workflows/list-phase-assumptions.md index 4970faa7c..255f133c2 100644 --- a/get-shit-done/workflows/list-phase-assumptions.md +++ b/get-shit-done/workflows/list-phase-assumptions.md @@ -14,8 +14,8 @@ Phase number: $ARGUMENTS (required) ``` Error: Phase number required. -Usage: /gsd-list-phase-assumptions [phase-number] -Example: /gsd-list-phase-assumptions 3 +Usage: /gsd:discuss-phase --assumptions +Example: /gsd:discuss-phase 3 --assumptions ``` Exit workflow. diff --git a/get-shit-done/workflows/list-workspaces.md b/get-shit-done/workflows/list-workspaces.md index a1e7fd7e8..12df54b99 100644 --- a/get-shit-done/workflows/list-workspaces.md +++ b/get-shit-done/workflows/list-workspaces.md @@ -55,7 +55,7 @@ GSD Workspaces (~/gsd-workspaces/) Manage: cd ~/gsd-workspaces/ # Enter a workspace - /gsd-remove-workspace # Remove a workspace + /gsd:workspace --remove # Remove a workspace ``` For each workspace, show: diff --git a/get-shit-done/workflows/remove-workspace.md b/get-shit-done/workflows/remove-workspace.md index cef6c1351..d8ea27737 100644 --- a/get-shit-done/workflows/remove-workspace.md +++ b/get-shit-done/workflows/remove-workspace.md @@ -32,7 +32,7 @@ Parse JSON for: `workspace_name`, `workspace_path`, `has_manifest`, `strategy`, **If no workspace name provided:** -First run `/gsd-list-workspaces` to show available workspaces, then ask: +First run `/gsd:workspace --list` to show available workspaces, then ask: **Text mode (`workflow.text_mode: true` in config or `--text` flag):** Set `TEXT_MODE=true` if `--text` is present in `$ARGUMENTS` OR `text_mode` from init JSON is `true`. When TEXT_MODE is active, replace every `AskUserQuestion` call with a plain-text numbered list and ask the user to type their choice number. This is required for non-Claude runtimes (OpenAI Codex, Gemini CLI, etc.) where `AskUserQuestion` is not available. diff --git a/tests/bug-2543-gsd-slash-namespace.test.cjs b/tests/bug-2543-gsd-slash-namespace.test.cjs index 138f13d9b..053d41fa2 100644 --- a/tests/bug-2543-gsd-slash-namespace.test.cjs +++ b/tests/bug-2543-gsd-slash-namespace.test.cjs @@ -3,7 +3,7 @@ // allow-test-rule: structural-regression-guard /** - * Slash-command namespace invariant (#3443). + * Slash-command namespace invariant (#3443) — SCOPED ACTIVE VARIANT. * * History: * #3443 re-establishes `/gsd:` as canonical in Claude-facing source text. @@ -11,12 +11,27 @@ * `.claude/commands/gsd/` (namespaced slash commands), while non-Claude runtimes * perform install-time conversion (for example `/gsd:` -> `/gsd-`). * - * Invariant enforced here: - * No `/gsd-` pattern in Claude-facing source text. + * Two-tier model (current — see CONTEXT.md § "Slash-command form: directory-level matrix"): + * • Claude-facing SOURCE TEXT (commands/, agents/, workflows/, references/, + * templates/, hooks/, .clinerules): uses `/gsd:` (colon). + * THIS test enforces the colon invariant over those directories. + * • Runtime-emitter contexts (runtime-slash.cjs, phase-lifecycle-policy.ts, + * *.generated.cjs, bug-3584 test file): use `/gsd-` (hyphen) per + * bug-3584's contract. Those files are EXCLUDED from this scan. * - * Exceptions: - * - CHANGELOG.md: historical entries document commands under their original names. - * - gsd-sdk / gsd-tools identifiers: never rewritten (not slash commands). + * Scoped invariant enforced here: + * No `/gsd-` pattern in Claude-facing source files, EXCLUDING the + * runtime-emitter contexts listed in RUNTIME_EMITTER_EXCLUDES below. + * + * Canonical reference for the runtime-emitter (hyphen-form) contract: + * tests/bug-3584-runtime-slash-emitters.test.cjs + * + * DO NOT expand RUNTIME_EMITTER_EXCLUDES without also updating the bug-3584 + * test and CONTEXT.md § "Slash-command form: directory-level matrix". + * + * See also: PR #154 first-pass incident (agent applied outdated invariant, + * broke bug-3584 contract); PR #164 Codex adversarial review (surfaced the + * need to re-activate this test with explicit exclusions). */ const { test, describe } = require('node:test'); @@ -27,8 +42,27 @@ const path = require('node:path'); const ROOT = path.join(__dirname, '..'); const COMMANDS_DIR = path.join(ROOT, 'commands', 'gsd'); +// Runtime-emitter contexts: these files intentionally emit `/gsd-` (hyphen) +// as part of the bug-3584 runtime contract. They must NOT be scanned by this +// invariant — doing so caused PR #154 first-pass to revert correct hyphen form +// to colon form, breaking bug-3584-runtime-slash-emitters.test.cjs. +// +// Expand this list only if a new runtime-emitter module is introduced AND the +// bug-3584 test is updated to cover it. +const RUNTIME_EMITTER_EXCLUDES = new Set([ + // Primary runtime-slash emitter (bug-3584 canonical contract): + path.join(ROOT, 'get-shit-done', 'bin', 'lib', 'runtime-slash.cjs'), + // phase-lifecycle-policy.ts emits runtime-persisted slash references (bug-3584): + path.join(ROOT, 'get-shit-done', 'bin', 'lib', 'phase-lifecycle-policy.ts'), + // Generated CJS files match the TS source's emitted form — never hand-edited: + // (matched below by .generated.cjs extension — see collectFiles exclusion) +]); + const SEARCH_DIRS = [ - path.join(ROOT, 'get-shit-done', 'bin', 'lib'), + // NOTE: get-shit-done/bin/lib is intentionally EXCLUDED from SEARCH_DIRS. + // runtime-slash.cjs and *.generated.cjs live there and use the hyphen form + // per bug-3584's runtime-emitter contract. The full bin/lib tree is + // runtime-emitter territory — scanning it would cause false positives. path.join(ROOT, 'get-shit-done', 'workflows'), path.join(ROOT, 'get-shit-done', 'references'), path.join(ROOT, 'get-shit-done', 'templates'), @@ -81,7 +115,15 @@ describe('slash-command namespace invariant (#3443)', () => { assert.ok(cmdNames.includes('execute-phase'), 'execute-phase must be a known command'); }); - test('no /gsd- retired syntax in Claude-facing source files', () => { + // SCOPED ACTIVE INVARIANT (2026-05-23 re-activation after Codex adversarial review of PR #164). + // + // Scan is scoped to Claude-facing source directories only (SEARCH_DIRS above). + // get-shit-done/bin/lib/ is excluded entirely — runtime-slash.cjs and + // *.generated.cjs there use hyphen form per bug-3584's runtime-emitter contract. + // + // If this test fails: check CONTEXT.md § "Slash-command form: directory-level matrix" + // before deciding whether to update the file or add to RUNTIME_EMITTER_EXCLUDES. + test('no /gsd- retired syntax in Claude-facing source files (scoped — excludes runtime-emitter contexts)', () => { const violations = []; for (const file of allUserFacingFiles) { const src = fs.readFileSync(file, 'utf-8');