diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index c74270839..2f11f4682 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "gsd-core", "displayName": "GSD Core", - "version": "1.4.5", + "version": "1.5.0", "description": "GSD Core is a meta-prompting, context engineering, and spec-driven development system for AI coding agents.", "author": { "name": "open-gsd", diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..a6e3fc6a1 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,10 @@ +root = true + +[*] +end_of_line = lf +charset = utf-8 +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +trim_trailing_whitespace = false diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..56654af1f --- /dev/null +++ b/.gitattributes @@ -0,0 +1,12 @@ +# Normalize line endings to LF on checkin; auto-detect binary (skipped). +* text=auto eol=lf +# Common binary types — never normalize. +*.png binary +*.jpg binary +*.jpeg binary +*.gif binary +*.ico binary +*.woff binary +*.woff2 binary +*.ttf binary +*.pdf binary diff --git a/.github/workflows/auto-backmerge.yml b/.github/workflows/auto-backmerge.yml index 2c7bbe588..a13361562 100644 --- a/.github/workflows/auto-backmerge.yml +++ b/.github/workflows/auto-backmerge.yml @@ -1,9 +1,17 @@ name: Auto Back-Merge main → next # Keep `next` at-or-ahead of `main` automatically. Every push to `main` -# (release finalize, hotfix finalize, occasional emergency fix) opens — or -# updates — a PR titled "chore: back-merge main → next". CI gates the merge; -# auto-merge is enabled so it lands as soon as it's green. +# (release finalize, hotfix finalize, occasional emergency fix) opens a PR +# titled "chore: back-merge main → next" and admin-merges it when it is safe. +# +# Resolution model (see #1336 / #1339): `next` is the integration branch and +# already contains every CODE change on `main` (fixes flow next -> release -> +# main). So the back-merge keeps `next`'s tree wholesale (`-s ours`, which never +# conflicts) and overlays only `main`'s release-engineering: the rendered +# CHANGELOG.md and the `.changeset` fragments `main` consumed at release. A +# safety net detects the rare case of a change that exists ONLY on `main` (an +# emergency fix pushed straight to `main`) and routes that PR to human review +# instead of auto-merging. # # Phase 1 of the next-branch migration: this workflow runs but is a no-op # (`if: false` on the job) until `next` exists in the repo. Once Phase 2 @@ -24,6 +32,9 @@ permissions: contents: write pull-requests: write +env: + NODE_VERSION: 24 + jobs: backmerge: # Phase-1 gate: leave false until `next` exists. Flip to `true` in Phase 2. @@ -36,6 +47,10 @@ jobs: fetch-depth: 0 token: ${{ secrets.GITHUB_TOKEN }} + - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + with: + node-version: ${{ env.NODE_VERSION }} + - name: Verify next branch exists id: check run: | @@ -52,51 +67,87 @@ jobs: git config user.name "github-actions[bot]" git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - - name: Create or update back-merge branch + - name: Create back-merge branch (reconcile main → next) if: steps.check.outputs.next_exists == 'true' id: branch - env: - GH_TOKEN: ${{ secrets.GSD_BOT_PR_TOKEN || secrets.GITHUB_TOKEN }} run: | set -euo pipefail - SHORT_SHA=$(git rev-parse --short HEAD) + git fetch --no-tags origin main next + SHORT_SHA=$(git rev-parse --short origin/main) BR="chore/backmerge-main-to-next-${SHORT_SHA}" echo "branch=$BR" >> "$GITHUB_OUTPUT" - # If there's already an open back-merge PR, reuse its branch by - # updating it with the new main HEAD via a merge. Otherwise create - # a fresh branch. - EXISTING_PR=$(gh pr list --base next --label automation --label backmerge --state open \ - --json number,headRefName --jq '.[0]' 2>/dev/null || echo "") - EXISTING_BR=$(echo "$EXISTING_PR" | jq -r '.headRefName // empty') + git checkout -B "$BR" origin/next + BASE=$(git merge-base HEAD origin/main) - if [ -n "$EXISTING_BR" ]; then - case "$EXISTING_BR" in - chore/backmerge-main-to-next-*) ;; - *) echo "::error::refusing to operate on non-backmerge branch: $EXISTING_BR"; exit 1 ;; + # Keep next's tree wholesale and record main as a second parent so main + # becomes an ancestor of next (stops the back-merge backlog recurring). + # -s ours never conflicts; next already has main's code. + git merge -s ours --no-commit --no-ff origin/main + + # Overlay main's rendered CHANGELOG.md (the release promotions). + if git cat-file -e "origin/main:CHANGELOG.md" 2>/dev/null; then + git checkout origin/main -- CHANGELOG.md + git add CHANGELOG.md + fi + + # Replay main's .changeset changes vs the merge-base: prune fragments + # main consumed at release (D); bring any main-authored fragment (A/M). + while IFS="$(printf '\t')" read -r status file; do + [ -n "${file:-}" ] || continue + case "$status" in + D) git rm --quiet --ignore-unmatch -- "$file" ;; + A|M) git checkout origin/main -- "$file" && git add -- "$file" ;; esac - echo "Updating existing back-merge branch: $EXISTING_BR" - git fetch origin "$EXISTING_BR":"$EXISTING_BR" || true - git checkout "$EXISTING_BR" - # Fast-forward to current main if possible; otherwise merge. - git merge --no-edit origin/main || { - echo "::warning::Merge conflict back-merging main into existing back-merge branch. Human resolution required." - exit 1 - } - git push --force origin "$EXISTING_BR" - echo "branch=$EXISTING_BR" >> "$GITHUB_OUTPUT" - echo "reused=true" >> "$GITHUB_OUTPUT" + done < <(git diff --name-status "$BASE" origin/main -- .changeset) + + # Safety net: -s ours silently drops anything existing ONLY on main. + # List code files (excluding CHANGELOG/.changeset) that main changed + # since the merge-base but next did not — a rare straight-to-main fix. + MAIN_CODE=$(git diff --name-only "$BASE" origin/main -- . ':(exclude)CHANGELOG.md' ':(exclude).changeset' | sort) + NEXT_CODE=$(git diff --name-only "$BASE" origin/next -- . ':(exclude)CHANGELOG.md' ':(exclude).changeset' | sort) + DROPPED=$(comm -23 <(printf '%s\n' "$MAIN_CODE") <(printf '%s\n' "$NEXT_CODE") | grep -v '^$' || true) + + git commit -m "chore: back-merge main into next (${SHORT_SHA})" + git push --force origin "$BR" + + if [ -n "$DROPPED" ]; then + echo "needs_review=true" >> "$GITHUB_OUTPUT" + echo "dropped_oneline=$(printf '%s' "$DROPPED" | tr '\n' ' ')" >> "$GITHUB_OUTPUT" + echo "::warning::Back-merge auto-resolved in favor of next and dropped main-only change(s) in: $(printf '%s' "$DROPPED" | tr '\n' ' '). PR left open for manual review." else - git fetch origin next:next - git checkout -b "$BR" next - # Bring main's commits onto next via a merge commit (preserves tag history). - if ! git merge --no-edit -m "chore: back-merge main into next" origin/main; then - echo "::error::Cannot auto-back-merge main into next: merge conflict. A maintainer must back-merge manually (git checkout next; git merge origin/main; resolve; push)." - git merge --abort || true - exit 1 - fi - git push --force origin "$BR" - echo "reused=false" >> "$GITHUB_OUTPUT" + echo "needs_review=false" >> "$GITHUB_OUTPUT" + echo "dropped_oneline=" >> "$GITHUB_OUTPUT" + fi + + - name: Sync next's version to main's released version + if: steps.check.outputs.next_exists == 'true' + run: | + set -euo pipefail + VERSION=$(git show origin/main:package.json | node -pe "JSON.parse(require('fs').readFileSync(0,'utf8')).version") + case "$VERSION" in + [0-9]*.[0-9]*.[0-9]*) : ;; + *) echo "refusing to sync next: unexpected version '$VERSION' from origin/main" >&2; exit 1 ;; + esac + NEXT_VERSION=$(node -pe "require('./package.json').version") + # Never downgrade an ahead next (e.g. next on 1.5.0-rc.N while main is + # 1.4.5). Only sync when main's version is strictly higher; a release + # (no prerelease) outranks a prerelease at the same X.Y.Z base. + if node -e ' + const split = v => { const [b, pre] = String(v).split("-"); return [b.split(".").map(Number), pre]; }; + const [a, ap] = split(process.argv[1]); + const [b, bp] = split(process.argv[2]); + let c = 0; + for (let i = 0; i < 3; i++) { if (a[i] !== b[i]) { c = a[i] - b[i]; break; } } + if (c === 0 && !!ap !== !!bp) c = ap ? -1 : 1; // release > prerelease + process.exit(c > 0 ? 0 : 1); + ' "$VERSION" "$NEXT_VERSION"; then + node scripts/sync-next-version.cjs "$VERSION" --in-place + git add -u + git diff --cached --quiet || git commit -m "chore: sync next package version to ${VERSION}" + git push origin HEAD + else + echo "next ($NEXT_VERSION) is at or ahead of main ($VERSION); leaving next version unchanged." fi - name: Open or update PR @@ -105,16 +156,24 @@ jobs: env: GH_TOKEN: ${{ secrets.GSD_BOT_PR_TOKEN || secrets.GITHUB_TOKEN }} BR: ${{ steps.branch.outputs.branch }} + NEEDS_REVIEW: ${{ steps.branch.outputs.needs_review }} + DROPPED_ONELINE: ${{ steps.branch.outputs.dropped_oneline }} run: | set -euo pipefail - SHORT_SHA=$(git rev-parse --short HEAD) + SHORT_SHA=$(git rev-parse --short origin/main) + REVIEW_NOTE="" + if [ "${NEEDS_REVIEW:-false}" = "true" ]; then + REVIEW_NOTE=" + REVIEW REQUIRED: this back-merge auto-resolved in favor of next and dropped main-only change(s) in: ${DROPPED_ONELINE}. A maintainer must verify these are not emergency fixes that need re-applying to next. Do not merge until verified." + fi BODY=$(cat <> "$GITHUB_OUTPUT" - name: Admin-merge the back-merge PR - if: steps.check.outputs.next_exists == 'true' + # Skip auto-merge when the back-merge dropped main-only changes — a human + # must review those before this lands. + if: steps.check.outputs.next_exists == 'true' && steps.branch.outputs.needs_review != 'true' env: GH_TOKEN: ${{ secrets.GSD_BOT_PR_TOKEN || secrets.GITHUB_TOKEN }} PR: ${{ steps.openpr.outputs.pr }} run: | - # Squash would lose the merge-commit context; use merge commit. + # Merge commit (not squash) preserves main as an ancestor of next. gh pr merge --admin --merge "$PR" diff --git a/.github/workflows/mutation.yml b/.github/workflows/mutation.yml index d33bdfd20..dcaee19dc 100644 --- a/.github/workflows/mutation.yml +++ b/.github/workflows/mutation.yml @@ -20,6 +20,7 @@ on: - 'tests/**/*.unit.test.cjs' - 'tests/adr-parser.test.cjs' - 'tests/active-workstream-store.test.cjs' + - 'tests/core-utils.test.cjs' - 'stryker.config.mjs' - 'scripts/mutation-matrix.cjs' workflow_dispatch: @@ -106,14 +107,19 @@ jobs: # MUTATION_TEST_CMD scopes the command runner to only this module's tests. # --mutate scopes mutation to only the changed module's built artifact. # --incremental reuses cached results for unchanged mutants. - # The break threshold (50) is read from stryker.config.mjs. + # MUTATION_BREAK passes the per-module minScore ratchet floor to + # stryker.config.mjs (which reads process.env.MUTATION_BREAK for the + # break threshold). See ADR-456 / issue #1187. + # Note: Stryker 9.x has no --break CLI flag; the threshold is config-only. env: NODE_OPTIONS: '--max-old-space-size=4096' MUTATION_TEST_CMD: node --test ${{ matrix.tests }} + MUTATION_BREAK: ${{ matrix.minScore }} run: | - echo "Module: ${{ matrix.name }}" - echo "Mutate: ${{ matrix.mutate }}" - echo "Tests: ${{ matrix.tests }}" + echo "Module: ${{ matrix.name }}" + echo "Mutate: ${{ matrix.mutate }}" + echo "Tests: ${{ matrix.tests }}" + echo "MinScore: ${{ matrix.minScore }}" npx stryker run --incremental --mutate "${{ matrix.mutate }}" - name: Upload mutation report — ${{ matrix.name }} diff --git a/.github/workflows/pr-target-validator.yml b/.github/workflows/pr-target-validator.yml index ade2c8fea..a1bdddd2d 100644 --- a/.github/workflows/pr-target-validator.yml +++ b/.github/workflows/pr-target-validator.yml @@ -18,6 +18,7 @@ concurrency: cancel-in-progress: true permissions: + contents: read pull-requests: write jobs: @@ -31,96 +32,91 @@ jobs: # Phase-1: warn only. Phase-2: set to 'false' to enforce. WARN_ONLY: 'false' steps: + - name: Checkout base branch (trusted policy source) + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + ref: ${{ github.event.pull_request.base.ref }} + persist-credentials: false + - name: Validate PR target branch uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: WARN_ONLY: ${{ env.WARN_ONLY }} with: script: | + const { classifyPrTarget } = require(`${process.env.GITHUB_WORKSPACE}/scripts/pr-target-policy.cjs`); + const pr = context.payload.pull_request; const base = pr.base.ref; const head = pr.head.ref; const warnOnly = process.env.WARN_ONLY === 'true'; - // PRs targeting `next` are always fine. - if (base === 'next') { - core.info(`PR targets next — OK.`); + const { decision } = classifyPrTarget(base, head); + + if (decision === 'allowed') { + if (base === 'next') { + core.info(`PR targets next — OK.`); + } else if (base === 'main') { + core.info(`PR from ${head} → main matches an allowed pattern.`); + } else { + core.info(`PR targets a release/hotfix branch — OK.`); + } return; } - // PRs targeting `main`: only specific branch types allowed. - if (base === 'main') { - const mainAllowed = [ - /^release\/\d+\.\d+\.0$/, // release branches - /^hotfix\/\d+\.\d+\.\d+$/, // hotfix branches - /^fix\/critical-/, // production-down emergencies - /^chore\/backmerge-/, // auto-backmerge from this workflow - /^revert\/critical-/, // emergency reverts - ]; - const allowed = mainAllowed.some(re => re.test(head)); - if (allowed) { - core.info(`PR from ${head} → main matches an allowed pattern.`); - return; - } + if (decision === 'unusual') { + core.warning(`Unusual PR target: ${base}`); + return; + } - const msg = [ - `### Wrong target branch`, - ``, - `This PR targets \`main\` but the source branch \`${head}\` is not a release, hotfix, critical-fix, or back-merge branch.`, - ``, - `**Most PRs should target \`next\`, not \`main\`.** See [docs/branching.md](../blob/main/docs/branching.md).`, - ``, - `**How to fix:** click "Edit" next to the PR title above and change the base branch from \`main\` to \`next\`. No need to recreate the PR.`, - ``, - `
When IS it OK to target main?`, - ``, - `- \`release/X.Y.0\` branches (cut by \`release.yml\`)`, - `- \`hotfix/X.Y.Z\` branches (cut by \`hotfix.yml\`)`, - `- \`fix/critical-*\` branches (production-down emergencies)`, - `- \`chore/backmerge-*\` branches (automated back-merge from this workflow)`, - `- \`revert/critical-*\` branches (emergency reverts of a bad merge on main)`, - ``, - `
`, - ].join('\n'); + // decision === 'blocked': base is main and head is not an allowed pattern. + const msg = [ + `### Wrong target branch`, + ``, + `This PR targets \`main\` but the source branch \`${head}\` is not a release, hotfix, critical-fix, or back-merge branch.`, + ``, + `**Most PRs should target \`next\`, not \`main\`.** See [docs/branching.md](../blob/main/docs/branching.md).`, + ``, + `**How to fix:** click "Edit" next to the PR title above and change the base branch from \`main\` to \`next\`. No need to recreate the PR.`, + ``, + `
When IS it OK to target main?`, + ``, + `- \`release/X.Y.0\` branches (cut by \`release.yml\`)`, + `- \`hotfix/X.Y.Z\` branches (cut by \`hotfix.yml\`)`, + `- \`fix/critical-*\` branches (production-down emergencies)`, + `- \`chore/backmerge-*\` branches (automated back-merge from this workflow)`, + `- \`revert/critical-*\` branches (emergency reverts of a bad merge on main)`, + ``, + `
`, + ].join('\n'); - // Post or update a sticky comment. - const { data: comments } = await github.rest.issues.listComments({ + // Post or update a sticky comment. + const { data: comments } = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: pr.number, + }); + const marker = ''; + const existing = comments.find(c => c.body && c.body.includes(marker)); + const body = `${marker}\n${msg}`; + if (existing) { + await github.rest.issues.updateComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: existing.id, + body, + }); + } else { + await github.rest.issues.createComment({ owner: context.repo.owner, repo: context.repo.repo, issue_number: pr.number, + body, }); - const marker = ''; - const existing = comments.find(c => c.body && c.body.includes(marker)); - const body = `${marker}\n${msg}`; - if (existing) { - await github.rest.issues.updateComment({ - owner: context.repo.owner, - repo: context.repo.repo, - comment_id: existing.id, - body, - }); - } else { - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: pr.number, - body, - }); - } - - if (warnOnly) { - core.warning(`PR target mismatch (warning-only mode): ${head} → ${base}`); - } else { - core.setFailed(`PR target mismatch: ${head} should target next, not main.`); - } - return; } - // PRs targeting release/X.Y.0 or hotfix/X.Y.Z are fine (stabilization PRs). - if (/^release\/\d+\.\d+\.0$/.test(base) || /^hotfix\/\d+\.\d+\.\d+$/.test(base)) { - core.info(`PR targets a release/hotfix branch — OK.`); - return; + if (warnOnly) { + core.warning(`PR target mismatch (warning-only mode): ${head} → ${base}`); + } else { + core.setFailed(`PR target mismatch: ${head} should target next, not main.`); } - - // Any other target is unusual but not forbidden. - core.warning(`Unusual PR target: ${base}`); diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 19867304a..bf3af0b70 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -65,7 +65,7 @@ jobs: if echo "$VERSION" | grep -qE '^(0|[1-9][0-9]*)\.0\.0$'; then IS_MAJOR="true" fi - elif echo "$VERSION" | grep -qE '^[0-9]+\.[0-9]+\.[1-9][0-9]*$'; then + elif echo "$VERSION" | grep -qE '^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.[1-9][0-9]*$'; then # Patch / hotfix release (X.Y.Z, Z>0) IS_HOTFIX="true" if [ "$ACTION" = "rc" ]; then @@ -463,6 +463,14 @@ jobs: PRE_VERSION: ${{ steps.prerelease.outputs.pre_version }} run: node scripts/verify-npm-publish.cjs --package @opengsd/gsd-core --version "$PRE_VERSION" --dist-tag next + - name: Sync next branch to the published pre-release + if: ${{ !inputs.dry_run }} + continue-on-error: true + env: + GH_TOKEN: ${{ secrets.GSD_BOT_PR_TOKEN || secrets.GITHUB_TOKEN }} + PRE_VERSION: ${{ steps.prerelease.outputs.pre_version }} + run: node scripts/sync-next-version.cjs "$PRE_VERSION" + - name: Summary env: PRE_VERSION: ${{ steps.prerelease.outputs.pre_version }} @@ -642,16 +650,6 @@ jobs: --post \ --allow-missing-webhook - - name: Clean up next dist-tag - if: ${{ !inputs.dry_run }} - env: - VERSION: ${{ inputs.version }} - run: | - # Point next to the stable release so @next never returns something - # older than @latest. This prevents stale pre-release installs. - npm dist-tag add "@opengsd/gsd-core@${VERSION}" next 2>/dev/null || true - echo "✓ next dist-tag updated to v${VERSION}" - - name: Verify publish if: ${{ !inputs.dry_run }} env: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d186c6d65..24d44cbde 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -103,18 +103,13 @@ jobs: # lint scripts) require() the built modules — so build them explicitly. - name: Build runtime lib (required by lint scripts) run: npm run build:lib - - name: Lint — ESLint (source-grep + timing + no-only-tests + quality) - run: npx eslint . --cache --cache-location node_modules/.cache/eslint/ - - name: Lint — skill dependency graph - run: npm run lint:skill-deps - - name: Lint — test file count per module - run: node scripts/lint-test-file-count.cjs - - name: Lint — command contract (ADR-0002) - run: node scripts/lint-command-contract.cjs - - name: Lint — PR checks use projectDir - run: node scripts/lint-pr-check-project-dir.cjs - - name: Lint — legacy directory name guard (#604) - run: npm run lint:legacy-name + # Single orchestrated lint entry point (npm run lint:ci) so local and CI + # always run the identical set. It composes `npm run lint`, keeping one + # eslint invocation home; the --cache flag inside it is a no-op in CI + # (node_modules/.cache is never restored) but still speeds local runs. + # Each sub-lint prints its own banner, so a failure identifies itself. + - name: Lint — all (ESLint, skill deps, test-file count, command contract, PR checks, legacy name, regression-test names) + run: npm run lint:ci test: name: test (${{ matrix.os }}, ${{ matrix.node-version }}) @@ -196,9 +191,37 @@ jobs: if: matrix.scope != 'full' run: node scripts/run-tests.cjs --files-from .ci-selected-tests.txt - - name: Run unit tests + # The unit suite runs ONCE here, under c8 with the coverage gate — the + # former standalone `coverage` job duplicated this lane's entire unit + # run (~4 min of runner time per PR) just to collect the same numbers. + - name: Run unit tests (coverage gate ≥70% on gsd-core/bin/lib) if: matrix.scope == 'full' - run: npm run test:unit + env: + NODE_OPTIONS: --max-old-space-size=6144 + run: npm run test:coverage:unit + + # Second-tier floor over the CI/release/lint tooling itself. Re-slices + # the SAME V8 coverage data left in coverage/tmp by the run above — no + # extra suite execution. Audit 2026-06: scripts/ measured 65.95%; the + # 55% floor prevents a collapse to zero-coverage tooling while leaving + # headroom for variance. The threshold lives in package.json next to + # the 70% lib gate — raise deliberately, never lower. + - name: Coverage floor — scripts/ tooling (≥55%) + if: matrix.scope == 'full' + run: npm run test:coverage:scripts-floor + + - name: Upload coverage artifact + if: always() && matrix.scope == 'full' + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: coverage-unit + # coverage/tmp holds raw per-process V8 dumps (>1 GB for the full + # unit suite) — exclude it; the rendered reports are the artifact. + path: | + coverage/ + !coverage/tmp + .nyc_output/ + if-no-files-found: ignore - name: Run integration tests if: matrix.scope == 'full' @@ -258,14 +281,29 @@ jobs: run: node scripts/run-tests.cjs --files-from .ci-selected-tests.txt test-full: - name: full test (${{ matrix.os }}, ${{ matrix.node-version }}) + name: full test (${{ matrix.os }}, ${{ matrix.node-version }}, shard ${{ matrix.shard }}/3) needs: changes if: needs.changes.outputs.code_changed == 'true' && needs.changes.outputs.full_matrix == 'true' runs-on: ${{ matrix.os }} defaults: run: shell: ${{ matrix.shell }} - timeout-minutes: 15 + # The unit suite is sharded across 3 parallel runners per OS/node leg + # (#1212). Each shard runs a deterministic round-robin third of the sorted + # unit-file list via `run-tests.cjs --suite unit --shard i/3`, so per-job + # wall-clock scales as O(total/3) and stays well under the cap as the suite + # grows — replacing the #869 timeout bump (15→20m) which only deferred the + # cliff. The cap stays at 20m as a generous backstop; a healthy shard now + # finishes in roughly a third of the old single-lane wall-clock. + # + # The matrix is the cross-product of 3 OS/node legs × 3 shards = 9 jobs, + # enumerated explicitly as `include:` rows. (A base `shard: [1,2,3]` + # dimension would NOT cross-product against `include` legs — include rows + # sharing no key with the base matrix are appended as standalone combos — + # and a NESTED `leg.os` key is not resolvable by the H1 shell-policy linter + # in scripts/workflow-policy.cjs, which reads `matrix.os`/`matrix.shell` + # directly. Explicit rows keep both the cross-product and the linter happy.) + timeout-minutes: 20 env: GSD_PLUGIN_ROOT: .ci-gsd-plugin-root-disabled strategy: @@ -275,12 +313,39 @@ jobs: - os: windows-latest node-version: 22 shell: pwsh + shard: 1 + - os: windows-latest + node-version: 22 + shell: pwsh + shard: 2 + - os: windows-latest + node-version: 22 + shell: pwsh + shard: 3 - os: macos-latest node-version: 22 shell: 'zsh {0}' + shard: 1 + - os: macos-latest + node-version: 22 + shell: 'zsh {0}' + shard: 2 + - os: macos-latest + node-version: 22 + shell: 'zsh {0}' + shard: 3 - os: macos-latest node-version: 24 shell: 'zsh {0}' + shard: 1 + - os: macos-latest + node-version: 24 + shell: 'zsh {0}' + shard: 2 + - os: macos-latest + node-version: 24 + shell: 'zsh {0}' + shard: 3 steps: - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 (Windows) @@ -321,58 +386,26 @@ jobs: - name: Dependency integrity gate run: node scripts/check-npm-integrity.cjs - - name: Run unit tests - run: npm run test:unit + # The heavy unit suite is split across the 3 shards — each runs a + # deterministic round-robin third of the sorted unit-file list. The + # union of shards 1/3 + 2/3 + 3/3 is the full unit suite, so coverage is + # unchanged; only wall-clock per job drops to ~total/3. + - name: Run unit tests (shard ${{ matrix.shard }}/3) + run: node scripts/run-tests.cjs --suite unit --shard ${{ matrix.shard }}/3 + # Integration and security suites are small; run them once per OS/node + # leg (on shard 1 only) instead of redundantly on all three shards. They + # still run on every leg (3 times total, once per platform), so each + # platform's integration/security coverage is unchanged — only the + # 3x-per-leg duplication is removed. - name: Run integration tests + if: matrix.shard == 1 run: npm run test:integration - name: Run security tests + if: matrix.shard == 1 run: npm run test:security - coverage: - needs: changes - if: needs.changes.outputs.product_changed == 'true' - runs-on: ubuntu-latest - timeout-minutes: 15 - env: - GSD_PLUGIN_ROOT: .ci-gsd-plugin-root-disabled - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - fetch-depth: 0 - persist-credentials: true - token: ${{ github.token }} - - name: Guard — require GitHub-hosted runner - run: node scripts/ci-guard-runner.cjs - - name: Rebase check — merge PR base branch into PR head - if: github.event_name == 'pull_request' - env: - GITHUB_TOKEN: ${{ github.token }} - run: node scripts/ci-rebase-check.cjs - - name: Set up Node.js 24 - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 - with: - node-version: 24 - cache: 'npm' - - name: Install dependencies - run: npm ci - - name: Dependency integrity gate - run: node scripts/check-npm-integrity.cjs - - name: Unit coverage - env: - NODE_OPTIONS: --max-old-space-size=6144 - run: npm run test:coverage:unit - - name: Upload coverage artifact - if: always() - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 - with: - name: coverage-unit - path: | - coverage/ - .nyc_output/ - if-no-files-found: ignore - required-tests: name: Required tests needs: @@ -381,7 +414,6 @@ jobs: - test - test-inert - test-full - - coverage if: always() runs-on: ubuntu-latest timeout-minutes: 1 @@ -395,7 +427,6 @@ jobs: TEST_RESULT: ${{ needs.test.result }} INERT_RESULT: ${{ needs.test-inert.result }} FULL_TEST_RESULT: ${{ needs.test-full.result }} - COVERAGE_RESULT: ${{ needs.coverage.result }} run: | set -euo pipefail echo "code_changed=$CODE_CHANGED" @@ -405,7 +436,6 @@ jobs: echo "test=$TEST_RESULT" echo "test-inert=$INERT_RESULT" echo "test-full=$FULL_TEST_RESULT" - echo "coverage=$COVERAGE_RESULT" if [ "$CHANGES_RESULT" != "success" ]; then echo "::error::test scope detection did not pass" @@ -427,14 +457,12 @@ jobs: echo "::error::test matrix did not pass" exit 1 fi + # The coverage gates (lib 70% + scripts/ 55% floor) run inside the + # ubuntu/24 full lane of the `test` matrix, so TEST_RESULT covers them. if [ "$FULL_TEST_RESULT" != "success" ] && [ "$FULL_TEST_RESULT" != "skipped" ]; then echo "::error::full parity matrix did not pass" exit 1 fi - if [ "$COVERAGE_RESULT" != "success" ]; then - echo "::error::coverage did not pass" - exit 1 - fi else if [ "$INERT_RESULT" != "success" ]; then echo "::error::inert CI lane did not pass" diff --git a/.github/workflows/version-gate.yml b/.github/workflows/version-gate.yml new file mode 100644 index 000000000..44cf4ebf4 --- /dev/null +++ b/.github/workflows/version-gate.yml @@ -0,0 +1,47 @@ +name: Issue Version Gate + +on: + issues: + types: [opened] + +concurrency: + group: ${{ github.workflow }}-${{ github.event.issue.number }} + cancel-in-progress: false + +permissions: + issues: write + contents: read + +jobs: + gate: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const gate = require(`${process.env.GITHUB_WORKSPACE}/scripts/issue-version-gate.cjs`); + const issue = context.payload.issue; + if (!issue) return; + if (issue.pull_request) return; + const labels = (issue.labels || []).map((l) => (typeof l === 'string' ? l : l.name)); + const result = gate.evaluateVersionGate({ labels, body: issue.body || '' }); + core.info(`version-gate: #${issue.number} → ${result.action} (${result.reason})`); + if (result.action !== 'close') return; + const { owner, repo } = context.repo; + try { + await github.rest.issues.addLabels({ + owner, repo, issue_number: issue.number, + labels: [gate.NEEDS_VERSION_LABEL], + }); + } catch (err) { + core.warning(`could not add ${gate.NEEDS_VERSION_LABEL} label: ${err.message}`); + } + await github.rest.issues.createComment({ + owner, repo, issue_number: issue.number, + body: gate.renderCloseComment(), + }); + await github.rest.issues.update({ + owner, repo, issue_number: issue.number, + state: 'closed', state_reason: 'not_planned', + }); diff --git a/.gitignore b/.gitignore index ddde43901..479c85bad 100644 --- a/.gitignore +++ b/.gitignore @@ -66,10 +66,15 @@ build/ # ADR-457 build-at-publish: TS-generated runtime artifacts (compiled from src/*.cts # by `npm run build:lib`). Source of truth is src/; these are emitted, never edited. # Published via prepublishOnly; built before test via pretest. Grows as modules migrate. +/tsconfig.build.tsbuildinfo /gsd-core/bin/lib/research-store.cjs /gsd-core/bin/lib/research-provider.cjs /gsd-core/bin/lib/package-legitimacy.cjs /gsd-core/bin/lib/semver-compare.cjs +/gsd-core/bin/lib/plan-drift-guard.cjs +/gsd-core/bin/lib/edge-probe.cjs +/gsd-core/bin/lib/probe-core.cjs +/gsd-core/bin/lib/prohibition-enforcement.cjs /gsd-core/bin/lib/config-types.cjs /gsd-core/bin/lib/cli-exit.cjs /gsd-core/bin/lib/code-review-flags.cjs @@ -88,6 +93,7 @@ build/ /gsd-core/bin/lib/phase-lifecycle.cjs /gsd-core/bin/lib/workstream-name-policy.cjs /gsd-core/bin/lib/decisions.cjs +/gsd-core/bin/lib/teams-status.cjs /gsd-core/bin/lib/validate.cjs /gsd-core/bin/lib/schema-detect.cjs /gsd-core/bin/lib/runtime-name-policy.cjs @@ -118,16 +124,33 @@ build/ /gsd-core/bin/lib/active-workstream-store.cjs /gsd-core/bin/lib/adr-parser.cjs /gsd-core/bin/lib/graphify.cjs +/gsd-core/bin/lib/graphify-command-router.cjs +/gsd-core/bin/lib/audit-command-router.cjs +/gsd-core/bin/lib/intel-command-router.cjs /gsd-core/bin/lib/install-profiles.cjs /gsd-core/bin/lib/intel.cjs /gsd-core/bin/lib/installer-migrations.cjs /gsd-core/bin/lib/worktree-base-ref.cjs /gsd-core/bin/lib/worktree-safety.cjs /gsd-core/bin/lib/planning-workspace.cjs +/gsd-core/bin/lib/command-roster.cjs +/gsd-core/bin/lib/runtime-artifact-conversion.cjs /gsd-core/bin/lib/runtime-artifact-layout.cjs /gsd-core/bin/lib/runtime-config-adapter-registry.cjs +/gsd-core/bin/lib/runtime-hooks-surface.cjs /gsd-core/bin/lib/command-routing-hub.cjs -/gsd-core/bin/lib/core.cjs +/gsd-core/bin/lib/core-utils.cjs +/gsd-core/bin/lib/io.cjs +/gsd-core/bin/lib/phase-id.cjs +/gsd-core/bin/lib/config-loader.cjs +/gsd-core/bin/lib/model-resolver.cjs +/gsd-core/bin/lib/loop-resolver.cjs +/gsd-core/bin/lib/capability-state.cjs +/gsd-core/bin/lib/capability-writer.cjs +/gsd-core/bin/lib/capability-activation.cjs +/gsd-core/bin/lib/federated-config.cjs +/gsd-core/bin/lib/phase-locator.cjs +/gsd-core/bin/lib/roadmap-parser.cjs /gsd-core/bin/lib/drift.cjs /gsd-core/bin/lib/cjs-command-router-adapter.cjs /gsd-core/bin/lib/phase-command-router.cjs @@ -144,6 +167,7 @@ build/ /gsd-core/bin/lib/verify-command-router.cjs /gsd-core/bin/lib/init-command-router.cjs /gsd-core/bin/lib/agent-command-router.cjs +/gsd-core/bin/lib/agent-install-check.cjs /gsd-core/bin/lib/task-command-router.cjs /gsd-core/bin/lib/validate-command-router.cjs /gsd-core/bin/lib/workstream-inventory.cjs @@ -159,9 +183,11 @@ build/ /gsd-core/bin/lib/verify.cjs /gsd-core/bin/lib/init.cjs /gsd-core/bin/lib/uat.cjs +/gsd-core/bin/lib/uat-predicate.cjs /gsd-core/bin/lib/workstream.cjs /gsd-core/bin/lib/roadmap.cjs /gsd-core/bin/lib/audit.cjs +/gsd-core/bin/lib/git-base-branch.cjs __pycache__/ *.pyc .venv/ diff --git a/AGENTS.md b/AGENTS.md deleted file mode 100644 index 56ea2a9b2..000000000 --- a/AGENTS.md +++ /dev/null @@ -1,55 +0,0 @@ -# Repository Guidelines - -## Active Discussions - -For current work on **Grok Build compatibility** and multi-runtime synchronization across Grok Build, Claude Code, Gemini CLI, and Codex, see: - -- `docs/discussions/grok-build-support-2026-05.md` - -## Project Structure & Module Organization - -This repository ships GSD as a Node.js CLI and SDK. Root package entry points live in `bin/`, scripts in `scripts/`, runtime hooks in `hooks/`, command definitions in `commands/gsd/`, and workflow/template content in `gsd-core/`. Agent role files are in `agents/`; docs are in `docs/`; logos and terminal images are in `assets/`. Root tests are in `tests/*.test.cjs`. The TypeScript SDK is isolated under `sdk/`, with source and Vitest tests in `sdk/src/`. - -## Build, Test, and Development Commands - -Use Node.js `>=22`. - -- `npm install`: install root dependencies. -- `npm test`: builds the SDK first, then runs root `node:test` suites via `scripts/run-tests.cjs`. -- `npm run test:coverage`: runs root tests with `c8` and enforces 70% line coverage for included CommonJS library files. -- `npm run build:hooks`: rebuilds generated hook artifacts. -- `npm run build:sdk`: installs SDK dependencies and builds TypeScript. -- `cd sdk && npm test`: runs SDK Vitest unit and integration projects. -- `cd sdk && npm run build`: type-checks and emits `sdk/dist/`. - -## Coding Style & Naming Conventions - -Match the existing style in the edited area. Root JavaScript is CommonJS, generally strict-mode, two-space indentation, semicolons, `const`/`let`, and `node:` imports for built-ins. SDK code is strict TypeScript using ESM/`NodeNext`. Keep command, workflow, and test filenames kebab-case, for example `commands/gsd/plan-phase.md` and `tests/bug-2396-makefile-test-priority.test.cjs`. Agent files use `gsd-*.md`. Avoid unrelated formatting and unnecessary dependencies. - -## Testing Guidelines - -Root tests use Node’s built-in `node:test` and `node:assert/strict`; do not add Jest, Mocha, or Chai. Prefer helpers from `tests/helpers.cjs` for temporary projects, cleanup, and CLI execution. Name root tests `*.test.cjs`; run one with `node --test tests/name.test.cjs`. SDK tests use Vitest with `*.test.ts` for unit tests and `*.integration.test.ts` for integration tests. - -## Commit & Pull Request Guidelines - -Recent history follows Conventional Commit prefixes such as `fix:`, `feat:`, and `ci:`, often with issue references: `fix(#2623): resolve parent .planning root...`. Keep commits scoped and descriptive. - -Every PR must link an approved or confirmed issue with `Closes #123`, `Fixes #123`, or `Resolves #123`. Use the matching template in `.github/PULL_REQUEST_TEMPLATE/`. Include behavior changes, root cause when relevant, test evidence, affected platforms/runtimes, and update `CHANGELOG.md` or docs for user-facing changes. - -## Security & Configuration Tips - -Do not commit secrets, local config, or generated worktree artifacts. Before release-facing changes, run the relevant scan scripts in `scripts/`, especially `secret-scan.sh`, `base64-scan.sh`, and `prompt-injection-scan.sh`. - -## Agent skills - -### Issue tracker - -Issues live in GitHub Issues at `open-gsd/gsd-core` (via the `gh` CLI, always with `--repo open-gsd/gsd-core`). See `docs/agents/issue-tracker.md`. - -### Triage labels - -Five canonical triage roles mapped to this repo's labels — `needs-info`→`needs-reproduction`, `ready-for-agent`→`confirmed`, `ready-for-human`→`approved-enhancement`/`approved-feature`, others default. See `docs/agents/triage-labels.md`. - -### Domain docs - -Single-context — `CONTEXT.md` (domain glossary + recurring PR rules) and `docs/adr/` at the repo root. See `docs/agents/domain.md`. diff --git a/CHANGELOG.md b/CHANGELOG.md index 72ed80a19..a60c1508c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,186 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). ## [Unreleased] +## [1.5.0] - 2026-06-17 + +### Added + +- **`gen-capability-registry` now rejects duplicate artifact producers at the same Loop Extension Point** — if two capability `steps` declare `produces: []` at the same point, the generator throws at gen time naming the artifact, the point, and the producing capability ids, instead of letting the topological sort pick a winner silently (which left ADR-857 Decision #6's data-flow contract undefined). The check counts distinct `(capId, stepIdx)` producer steps, so a single step listing an artifact twice does not false-positive. ADR-894 §4's enumerated cross-capability invariant list gains the artifact-production-uniqueness rule. (#1123) (#1131) +- **`gsd-tools drift-guard` — deterministic plan-drift severity/authority decisions (ADR-22).** The plan-review source-grounding pass now classifies cited-symbol drift through a tested seam (5-rung authority ladder, `grep`→`intel` auto-upgrade, severity mapping, rung≥3 hard-block) instead of re-deriving the rules from workflow prose on each run. (#1190) (#1242) +- **`/gsd-progress --next --auto --converge` now routes planning through plan-review convergence.** ADR-15's designated *primary* convergence surface is wired into the progress/next workflow (previously only `/gsd-autonomous --converge` honored it; on `/gsd-progress` the flag was silently dropped). Accepts `--cross-ai` as an alias plus reviewer flags and `--max-cycles N`, and is gated on `workflow.plan_review_convergence`. (#1190) (#1237) +- +**`gsd-tools query teams-status` + a plan-phase warning detect claude-code agent-teams** — GSD's multi-agent orchestration can stall under claude-code's experimental agent-teams (a subagent's completion can fail to route back to the orchestrator). A new read-only `query teams-status` command reports `{ active, runtime, env_present, source }` (and `--active` for a clean shell guard), and `/gsd:plan-phase` now emits a single non-fatal warning when agent-teams is detected, recommending you disable it for GSD workflows. The detector only activates on the `claude` runtime with `CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS` strictly truthy — every other runtime and the teams-off path are completely unaffected. (#1355) (#1371) +- spec-phase: prohibition probe — a prose-orchestrated Step 5.6 that surfaces the unwritten *must-NOT* constraints (values/safety/ethics) a feature could silently become but the spec never forbids. Two stages per requirement: an adversarial recall question ("what could this silently become that the author would NOT want?") then a one-pass precision classifier that drops routine engineering and keeps genuine prohibitions. Confirmed prohibitions become NEGATIVE SPEC acceptance criteria carrying a `test`/`judgment` verification tier, which plan-phase lifts into the `must_haves.prohibitions` sibling block (`truths` untouched). Judgment-tier items soft-gate at verify time (never silent, never hard-halt); unwired test-tier items fail closed. The recall stage is model-driven (no compiled engine); canon-bound concerns (OWASP/GDPR/fairness) are referred to `/gsd:secure-phase`. Additive and optional: existing SPECs without a Prohibitions section remain valid. Second adapter of the `probe-core` resolution model (ADR-550 Decision 7). (#1149) +- **Optional `## Business Context` section in the PROJECT.md template** — a four-field block (Customer, Revenue model, Success metric, Strategy notes) for monetized or customer-facing projects, positioned between Core Value and Requirements. Optional by default (an HTML comment tells non-business projects to delete it), capped at four one-line fields to stay a constraint reference rather than a business plan, and reviewed at each milestone by `/gsd-complete-milestone` when present. (#72) (#756) +- **Async external jobs can now defer an Execute step legally (`external_job_waiting`).** An Execute step that dispatches a long-running external job and commits a `.planning/async-jobs/.json` manifest — deferring `SUMMARY.md` — is now recognized as a *legal deferred state*, not an illegal partial-plan state. `execute-phase` safe-resume, `resume-project`, and `pause-work` reconcile against the manifest instead of re-dispatching (which would duplicate the external compute). This defines the versioned, scheduler-agnostic manifest **stability contract** consumed by the core loop; the scheduler adapter that *produces* manifests is the capability half (#1164). (#1165) (#1221) +- **MemPalace memory capability (opt-in)** — adds cross-session/cross-project recall and verbatim+temporal-KG capture at GSD loop boundaries via the MemPalace MCP server and CLI; disabled by default, skip-on-error. (#1201) (#1201) +- spec-phase: spec-completeness edge-probe — a taxonomy-driven Step 5.5 that walks each SPEC requirement against a closed 8-category edge taxonomy (boundary, adjacency, empty/degenerate, encoding, ordering, precision, idempotency, concurrency), proposes concrete candidate edges, and resolves each to covered/dismissed/backstop/unresolved. covered edges add acceptance criteria the planner lifts into must_haves.truths; a soft gate flags unresolved edges. Additive and optional: existing SPECs without an Edge Coverage section remain valid. (#584) +- **`phase uat-passed` predicate** — new runtime-neutral command evaluates HUMAN-UAT results with markdown-aware parsing (ignores frontmatter, fenced code, blockquotes, and HTML comments) and reports pass only when every required check passes. (#1063) (#1063) +- Bug-report issues that lack a valid GSD Version are now auto-closed on open by a new `version-gate.yml` GitHub Actions workflow. GitHub Issue Forms only enforce `required: true` in the web UI, so issues filed via the REST API, `gh issue create`, or AI reporters can arrive without a version; values like `idk`, `_No response_`, or an empty field are treated as missing. Affected issues receive a closing comment with instructions to add the version (e.g. `1.18.0`) and reopen; maintainers can add the `version-exempt` label to opt an issue out. (#1181) +- **Kimi CLI runtime support is now documented and installable** — users can install global GSD Agent Skills with `--kimi --global`, invoke them as `/skill:gsd-*`, and launch the generated custom agent explicitly with `kimi --agent-file`. The custom-agent (`--agent-file`) surface targets the legacy/Python `kimi-cli` contract; newer Kimi Code (`@moonshot-ai/kimi-code`) consumes the same `/skill:gsd-*` skills via `--skills-dir` instead. (#743) +- **`agent_skills` can now reference Claude-Code plugin-provided skills** via the namespaced `global::` form (e.g. `global:coderabbit:code-review`). On the Claude runtime the agent's skills block emits a by-name Skill-tool load directive that resolves the plugin skill (no plugin-cache path is read); path-resolvable skills keep the existing `@`-include unchanged; on non-Claude runtimes a namespaced entry is skipped with a warning. The 22 agent_skills-consumer agents now carry the `Skill` tool so they can load plugin-provided skills. (#1261) +- **`gsd-tools capability set` — turn capabilities on/off and gate hooks from one command.** Adds the write side of the capability system (ADR-857/ADR-1213): `capability set --on|--off` toggles a capability through the runtime surface (the canonical on/off switch) and `--gate =` toggles a hook within an enabled capability, then re-resolves and reports — so disabling a capability is consistent across surface and config ("off means off") as a write-time invariant. `/gsd:settings` capability hook-gates now route through it. (#1213) (#1225) + +### Changed + +- Added an opt-in `anthropic-fable` model policy provider preset for Claude Fable 5 high-budget routing while preserving the existing Anthropic Opus 4.8 defaults and `anthropic` provider preset. (#1014) (#1015) +- **Windsurf/Devin workspace skills now install to the canonical `.devin/skills/` directory** — fresh workspace installs write skills under `.devin/skills/` (Devin Desktop's documented preferred location) instead of `.windsurf/skills/`; the legacy `.windsurf/skills/` layout is still recognized. The global `~/.codeium/windsurf/skills/` path is unchanged. (#1093) (#1093) +- +**`loadCentralConfigKeys` now fails loud on a malformed central config-schema instead of silently returning an empty Set** — `ENOENT` (the schema legitimately absent) still returns an empty Set silently, but a JSON parse error or any other read failure now writes a prominent `stderr` warning naming the schema file and throws `ExitError(1)`. Previously a single `catch (_)` swallowed parse errors too, so a merge-conflict marker or truncated write in `config-schema.manifest.json` made every capability config key look non-central — the config-key collision / `pending-migration` gate fired zero warnings and `--check` passed clean, defeating the gate invisibly. (#1124) (#1131) +- **Capability hook rendering now consumes resolved Capability State** — `gsd-tools loop render-hooks` uses the same installed/surfaced/configured state reported by `gsd-tools capability state`, so disabling a migrated capability at the runtime surface removes its workflow hooks even when config defaults are enabled. Migrated capability config keys remain accepted through the generated capability registry/federated config path instead of duplicated central `VALID_CONFIG_KEYS` entries. (#1136) (#1153) +- +Added ADR-857 Phase 6 capstone conformance coverage so migrated Capability activation keys cannot be read directly from host loop workflows, Capability-owned config keys stay out of the central schema, and the host loop workflow size budgets remain documented. The verify-work UI automation preflight now resolves UI activation through the Capability hook registry instead of reading `workflow.ui_phase` directly. (#1158) +- **ADR-857 phase 6 complete: optional features are now Capabilities, not inline loop branches.** `tdd`, `schema-gate`, `drift`, `gap-analysis`, and `profile-pipeline` are migrated out of the five-step host loop into declarative Capabilities (loop hooks + a command family); their config keys are federated to capability ownership; and the `plan-phase`/`execute-phase` workflow bodies shrink accordingly. Two previously-declared-but-dead capability gates now actually fire — the security ship-time gate (`ship:pre`) and the UI safety gate (`execute:wave:post`) — and the phase-6 conformance gate is hardened to be un-gameable (rejects empty stubs, requires loop-body shrink, verifies hook dispatch and gate-result contracts). Behavior is preserved, verified across five adversarial review passes. (#1139, #1167, #1168, #1169) (#1183) +- +**Test-tier prohibitions are now a real, provable gate instead of a permanent, unsatisfiable `gaps_found`** — the deferred ENFORCEMENT half of ADR-550 Decision 5d (the "heavy half" that #644 / PR #1149 deferred) has landed. A new deterministic `check prohibition-enforcement` sub-command (authored as `src/prohibition-enforcement.cts`, compiled by `build:lib` to the gitignored `gsd-core/bin/lib/prohibition-enforcement.cjs`) is the missing PRODUCER: it locates the wired mechanical check, runs it for a genuine **non-vacuous** pass, builds `enforcementEvidence`, and emits the `dispositionForProhibition()` verdict. The previously-unreachable green branch in `dispositionForProhibition()` is now reachable from the live pipeline — a test-tier prohibition with a genuinely-passing wired check disposes `green` and can reach `passed`, while a missing, non-attested, or non-passing check hard-gates (flagged, never green → `gaps_found`) in BOTH interactive and autonomous modes (ADR-550 D4 / D3). `verify-phase.md` wires the consumer; the green/fail-closed policy in `src/probe-core.cts` is untouched. Both wired-check kinds are accepted (ADR-550 D2): a `node --test` negative test (requiring a real reported test — an empty file, which `node --test` counts as one passing "test", does NOT green) AND a lint/AST rule run as `eslint --format json` filtered by `ruleId` (so plugin rules like `local/*` load — bare `--rule` cannot), anchored on the in-tree `local/no-source-grep` rule (dogfooding, ADR-550 D4). This enforcement seam is the concrete instance of ADR-857 open-question §147 and lands on the core verify rail, never in `capabilities/` (D6). (#1259) + +**Honest scope — `failFirst` is caller-attested, not yet machine-proven.** This lands the *execution + non-vacuous-pass* half: the producer requires the caller to attest `failFirst: true` and the check to genuinely run and pass. It does NOT yet independently prove the check *fails-on-violation* (the literal `regression-must-fail-first` property) — cheap proof of that at verify time needs running the check against a known violation fixture, which is a **tracked follow-up (#1279)**. The red-first property currently rests on caller attestation, surfaced transparently in the evidence record. + +**Correction to the issue body (#1259):** the issue's "96 invalid/error negative-proof cases" figure is wrong. For the `no-source-grep` anchor specifically, the genuine `regression-must-fail-first` proofs are its **two `invalid` cases** (the `.includes()` and `.match()` blocks) in `tests/eslint-rules.test.cjs` — not 96. The anchor argument is unaffected (those two cases ARE real fail-first proofs); only the count was off. (#1273) +- **The test-tier prohibition gate now has a deterministic SOURCE for its wired check** — a resolved `test`-tier `must_haves.prohibitions` item MAY carry an optional `check` descriptor authored at spec-phase: the flat-scalar keys `check_kind` (`node-test` | `lint-rule`), `check_target`, and `check_rule` (lint-rule only). `projectProhibitions` projects these scalars deterministically and verify-phase reads them back (via `descriptorFromProjection`) to locate the check handed to `check prohibition-enforcement` — so a wired, passing test closes the gap with **zero manual descriptor authoring** (previously the verify-phase LLM had to invent `{kind, target, rule}` each run, #1259). This extends the ADR-550 Decision 3 prohibition-item shape (ratified in a dated 2026-06-15 ADR-550 addendum). The descriptor is **optional and fully backward-compatible** — a prohibition with no descriptor parses and disposes byte-identically to today — and **fail-closed**: a partial, invalid, or absent descriptor falls through to the producer's existing fail-closed locate, never a silent green. The descriptor is represented as flat scalars (not a nested `check:{}` object) to keep the shared `parseMustHavesBlock` round-trip regression-free. Out of scope: machine-proven fail-first (#1279) and the `dispositionForProhibition` policy stay unchanged. (#1278) (#1301) +- +**Test-tier prohibition fail-first is now MACHINE-PROVEN, not caller-attested** — the deferred literal `regression-must-fail-first` property of ADR-550 Decision 4 (the gap #1259 / PR #1273 left as a tracked follow-up) has landed. The `check prohibition-enforcement` producer (`src/prohibition-enforcement.cts`, compiled by `build:lib` to the gitignored `gsd-core/bin/lib/prohibition-enforcement.cjs`) no longer trusts the caller's `failFirst` attestation: before a clean, non-vacuous pass can dispose a `test`-tier prohibition green, the new `defaultProveFailFirst` prover independently RUNS the wired check against a KNOWN VIOLATION and confirms it goes RED. Attestation is gone from the green AND (`passed = proof.provenFailFirst === true && run.passed === true`); any other outcome — passes-on-violation, can't-prove, throws, times out, or no violation source — **hard-gates in BOTH interactive and autonomous modes** (ADR-550 D4 / D3). The evidence record gains a `failFirstProof` field recording HOW fail-first was proven (FF-07). A caller can no longer green a toothless check. + +The violation is sourced from a new descriptor field, **`CheckDescriptor.violationFixture`** — an author-supplied path to a known-bad subject. For a `lint-rule` the prover lints that fixture and requires the rule id to appear in the JSON report (the rule must have teeth); for a `node-test` the prover spawns the negative test with the subject injected through the **`GSD_PROHIB_SUBJECT`** env convention and requires a NON-VACUOUS red — `# fail >= 1` AND a failing test named distinctly from the file (`isNonVacuousNodeTestRed`), so a violation fixture that merely CRASHES the test at load is not mistaken for the negative assertion firing red (symmetric with the clean-pass non-vacuity guard). The node-test prover also requires the `violationFixture` to EXIST (resolved against `cwd`) before spawning — a missing/typo'd path fail-CLOSES rather than letting an honest test's ENOENT crash forge a green (symmetric with the lint path's file-result guard). The deterministic spec→verify path composes end-to-end: a fourth flat scalar **`check_violation_fixture`** is projected by `projectProhibitions` and read back by `descriptorFromProjection` (rides both kinds), so a prohibition authored with all four `check_*` scalars machine-proves fail-first and greens through the projection alone — zero hand-authoring at verify time (#1278 + #1279 + #1346; round-trip pinned by a fast-check property + CHK-03(D) + an end-to-end COMPOSE capstone). One documented residual remains under **#1346**: the node-test proof confirms the fixture exists and the check reds, but cannot generically prove the red was *caused by* the subject's content rather than by the env merely being set. The `lint-rule` path is fully shippable now and is dogfooded against the in-tree `local/no-source-grep` rule; the `node-test` path ships its mechanism (a fixture-bearing descriptor IS machine-proven) and is exercised by SYNTHETIC temp fixtures — there is no live in-tree `node --test` prohibition to dogfood. `CheckDescriptor.failFirst` is **DEMOTED, not removed** (FF-08): it is kept as a non-authoritative hint so the #1259 route-JSON shape and the `CheckDescriptor` type stay backward-compatible mid-migration, but no path greens on it alone. The green/fail-closed policy in `src/probe-core.cts` (`dispositionForProhibition`, reads only `evidence.length > 0`) is untouched; the evidence array shape is additive. This closes ADR-550's D5d follow-up — see the dated 2026-06-15 ADR-550 addendum. (#1279) + +**PR-review flag — `GSD_PROHIB_SUBJECT` + `violationFixture` are PROPOSED, renamable conventions.** Both are net-new surface with ZERO live in-tree consumers (no in-tree node-test prohibition yet; node-test proof runs only on synthetic test fixtures, the real dogfood stays the lint-rule). They are forward-looking scaffolding, so a later rename — or replacing the env var with an argv — is a mechanical, zero-migration find/replace. Surfacing them here so the maintainer can **ratify, rename, or replace them at PR review** with no migration cost, exactly as #1278's ADR addendum was reviewed at PR time. The `failFirst` demotion is likewise open to weighing outright removal; the keep-as-hint rationale is recorded in the ADR addendum. (#1314) +- **Read-only verifier/auditor agents now ship a Claude-Code `disallowedTools` deny-list** — the installer injects a framework-level write-tool deny-list into the Claude copies of the read-only verifier/auditor agents (gsd-verifier, gsd-plan-checker, gsd-integration-checker, gsd-doc-verifier, gsd-eval-auditor, gsd-ui-auditor, gsd-ui-checker) so write actions are blocked even if a tool grant is inherited. Injected for Claude only; other runtimes are unaffected. (#1081) (#1081) +- +**Antigravity workspace skills now install to the canonical `.agents/` directory** — fresh installs write workspace artifacts under `.agents/` (the Google-Codelabs-documented base) instead of `.agent/`; the legacy `.agent/` layout is still recognized so existing installs keep working. The global `~/.gemini/antigravity/` path is unchanged. (#1090) (#1090) +- +**`devin-desktop` runtime alias for the Windsurf→Devin Desktop rebrand** — the `windsurf` runtime now also answers to `devin-desktop` (CLI `--devin-desktop`), aiding discoverability after Cognition rebranded Windsurf as Devin Desktop. All paths are unchanged — global skills still install to `~/.codeium/windsurf/skills/`. (#1086) (#1086) +- Remove dead `loadConfig` export from `configuration.cts` — superseded by `config-loader.cts` (ADR-857 phase 2e, #885). All live callers already import `loadConfig` from `config-loader.cjs` or the `core.cjs` back-compat re-export; exhaustive grep confirms zero callers importing it from `configuration.cjs`. `configuration.cjs` now provides only the pure normalization and defaults primitives (`normalizeLegacyKeys`, `mergeDefaults`, `migrateOnDisk`, `CONFIG_DEFAULTS`) that `config-loader.cjs` depends on. (#893) (#893) +- audit(#779): correct stale model-catalog IDs verified against live provider sources. The gemini opus default `gemini-3-pro` → `gemini-3.1-pro-preview` (the bare `gemini-3-pro` ID is undefined in gemini-cli source — only `gemini-3-pro-preview`/`gemini-3.1-pro-preview` exist) and the codex sonnet default `gpt-5.3-codex` → `gpt-5.4` (deprecated per OpenAI's Codex models page); the same two IDs are also updated in the `google`/`openai` provider-preset entries. `qwen3-coder-next` was verified valid (callable on Alibaba Model Studio) and left unchanged. Adds a regression guard against the retired IDs and a sourcing/verification note in CONFIGURATION.md. Catalog IDs are internal defaults; users who pinned the old IDs must update their config. (#1047) +- **INVENTORY.md no longer carries `(N shipped)` count scalars** — the hand-maintained absolute counts collided silently on merge (two branches each bumping the same integer to N+1 while the merged tree held N+2), red-flagging CI on the merge commit across all platforms. The manifest's name-set is now the sole registry, anchors are count-free and stable, and a guard test blocks re-adding a count. (#1179) (#1179) +- Edge-probe `precision` probe text now names tie-breaking / rounding-mode (half-up vs half-to-even, ceil/floor/truncate), so a surfaced precision edge cues the most common rounding failure mode. Prose-only; firing rule and the 8-category core unchanged. (#1108) +- Capability manifests now declare runtime compatibility through a validated runtimeCompat contract, and runtime descriptor interpreters now read artifact layout, skills-home, and hook-surface facts directly from runtime Capability descriptors instead of parallel runtime-name allowlists or fallbacks. This preserves existing supported runtime behavior while making future descriptor-backed runtimes additive. (#1157) +- Planning-time research, AI integration, and pattern mapping now participate through Capability declarations and rendered plan:pre hooks, with developer documentation for building GSD capabilities. (#1141) +- **The planner now blocks plans that would self-trip their own verify gate** — when an acceptance criterion negative-greps for a literal (`grep -c 'LIT' file == 0`) and that same literal appears verbatim in an `` body, plan creation now fails at write time instead of letting the executor waste cycles on a comment-text echo at commit time. Unquoted/ambiguous grep targets warn instead of failing; add `` to allowlist a legitimate occurrence. (#1062) (#1062) +- **Namespace router skills now nest their concrete sub-skills at install time (#69).** On runtimes with non-recursive skill loaders (Claude global, Cline, Qwen, Hermes, Augment, Trae, Antigravity) the installer emits the 6 `gsd-ns-*` routers as the only top-level skill bundles and nests the ~61 concrete skills under `/skills//SKILL.md`, cutting the eager skill-listing overhead to ≈6 entries. Concrete skills stay reachable via the router's `Read skills//SKILL.md` routing table. **Breaking:** on those runtimes the concrete skills are no longer invocable by bare name through the Skill tool / top-level listing — route via the namespace router (or the unchanged `/gsd-*` slash command where a commands surface exists). Legacy top-level `gsd-/` skill dirs are removed on upgrade. Recursive/unconfirmed loaders (Cursor, Codex, Copilot, Windsurf, CodeBuddy, OpenCode, Kilo) keep the flat layout. (#883) +- **Graphify now respects surface/profile state, not just `graphify.enabled`** — `gsd-tools graphify` is off unless graphify is installed AND surfaced AND `graphify.enabled` is true (previously only the config key was checked). The gate is now runtime-aware: Codex/Cursor/etc. read their own runtime's surface instead of `~/.claude`. (#1313) (#1313) +- **Isolated-executor recovery now fails safe** — when an isolated (worktree) executor run is rejected (you decline to merge it) or over-reached the requested scope, `/gsd:execute-phase` and `/gsd:quick` no longer default or propose recovery by editing the primary checkout (`main`). The orchestrator halts safely and offers a fresh, narrowly-scoped worktree or inspect/discard; editing the primary checkout requires explicit, clearly-labeled confirmation. (#1292) (#1303) +- Migrate code review, security, and Nyquist verification workflows to ADR-857 capability hooks. (#1147) +- **Intel and loop-hook rendering now honor the single capability `active` state** — `gsd-tools intel` gates through the shared resolver (consistency; intel stays governed by `intel.enabled`), and loop-hook rendering now suppresses a config-disabled capability's hooks via the capability-level `active` gate (fail-closed), not just per-hook `when`. (#1315) (#1315) +- Added no-drift guard tests (`tests/issue-57-runtime-install-no-drift.test.cjs`) that protect the Runtime Install Policy Module boundary (ADR-58) and the explicit Runtime Config Adapter Registry (#60). They fail loudly when supported-runtime metadata is added to an installer call site (`allRuntimes`, the interactive `runtimeMap` menu) without a matching registry adapter entry, or when config-mutation dispatch escapes the registry's declared install surfaces — catching reintroduction of the scattered per-runtime branching those seams removed. (#867) +- Edge-probe now surfaces a zero-classification requirement (non-empty prose, no shape cue matched, no `shapes` override) as a single soft `unclassified — review manually` candidate instead of silently dropping it. Dismissible like any edge; `shapes: []` opt-out stays silent; TAXONOMY unchanged. (#1117) +- **Capability state now reports a tri-state `active`** — `gsd-tools capability state` adds an `active` field per capability (installed && surfaced && config-enabled), alongside the existing `enabled` (installed && surfaced). Internal `isCapabilityActive(capId, cwd)` lets consumers honor the single resolved on/off answer. (#1311) (#1311) +- **`gsd-verifier` no longer marks behavior-dependent must-haves `VERIFIED` on symbol presence alone** — a truth that asserts a state transition or a cancellation/cleanup/ordering invariant is marked `PRESENT_BEHAVIOR_UNVERIFIED` when no test exercises it: excluded from the `verified_truths` score, reported as a `behavior_unverified` count, and routed to human verification, so a clean N/N now certifies behavioral evidence rather than mere symbol presence. (#966) (#1271) +- **`verify plan-structure` warns on cross-task region-scope conflicts (#968)** — when a plan task's file-wide negative grep (`! grep -Eq 'PAT' file` / `grep -c 'PAT' file == 0`) bans a construct a sibling task legitimately requires elsewhere in the same file, plan validation now surfaces a warning pointing to the new region/function-scoped negative-gate idiom (documented in the gsd-planner guidance and the planner-antipatterns reference, with a worked banned-in-X / required-in-Y example). Warn-only: it never errors and never changes `valid`. (#1320) (#1320) + +### Fixed + +- **`gsd-intel-updater` now writes the canonical intel filenames the `gsd-tools intel` CLI actually reads** — the agent was instructed to emit short names (`files.json`, `apis.json`, `deps.json`) and a markdown `arch.md`, but the intel library reads only `file-roles.json`, `api-map.json`, `dependency-graph.json`, and `arch-decisions.json` (JSON). After `/gsd:map-codebase --query refresh` the output was orphaned, so `intel status`/`validate` reported the files missing and `intel query` returned nothing. The agent now emits the canonical long names and structured `arch-decisions.json`. (#1000) (#1037) +- **Installer no longer appends a duplicate managed hook when it is registered via an HTTP route** — a hook re-registered as a `type:"http"` entry (local hook-server routing) carries its identity only in `url`, which the installer's presence check ignored, so a stock command duplicate was appended on every install/update and the hook ran twice per event. The presence check now also inspects `h.url`. (#1004) (#1032) +- **`/gsd-code-review`'s fallow structural pre-pass now actually runs and delivers findings** — it invoked fallow with flags no published fallow version accepts (`--json`, `--profile`, `--stdin-files`), so the pre-pass failed on every run and silently degraded (the structural-findings feature never delivered on any fallow version). It now uses fallow's real CLI (`audit --format json --quiet`, `--changed-since` for phase scope, and `--max-crap` mapped from the `code_quality.fallow.profile` preset: minimal→50, standard→30, strict→15), treats fallow's exit code 1 ("issues found") as a successful run instead of a crash (gating on a valid JSON report, not the exit code), and normalizes fallow's real `audit --format json` schema (`dead_code.*`, `duplication.clone_groups`) into the reviewer's `` contract. The report normalizer — previously dead code parsing a schema fallow never shipped — is wired to the real schema and exercised against real fallow output. (#1012) (#1044) +- **`worktree base-check` now honors a user/global `worktree.baseRef:"head"` (and `CLAUDE_CONFIG_DIR`)** — base-check resolved `baseRef` from the project checkout's `.claude/` only, so a machine-wide `head` set via `/config` (the layer the harness itself honors) was invisible. On any phase/feature lane it returned `shouldDegrade:true` and `execute-phase` silently forced sequential execution, losing the parallel worktree execution the user configured. Resolution now falls back to the user/global `settings.json` (via `getGlobalConfigDir('claude')`, honoring `CLAUDE_CONFIG_DIR`) below the existing project-local and project-shared layers. (#1013) (#1038) +- **Agent SDK/state/commit steps now resolve `gsd-tools` on shim-only installs for every runtime** — source `agents/*.md` (`gsd-planner`, `gsd-executor`, `gsd-verifier`, `gsd-plan-checker`, …) invoked bare `gsd-tools …`, which fails with `command not found` on shim-only installs where the binary is only reachable as `/gsd-core/bin/gsd-tools.cjs` and is not on `PATH`. The agent then silently skipped init/state/validate/commit ceremony. #725 fixed only Codex's conversion layer; the source agents were never migrated, so the bug persisted on Claude Code and every other runtime that consumes the source agents directly. All 12 `gsd-tools`-calling agents now carry the canonical multi-runtime `gsd_run` resolver (the same preamble the workflow launchers use — covering claude/codex/cursor/gemini/copilot/windsurf/augment/trae/qwen/cline/opencode/kilo/hermes/antigravity homes), `gsd-phase-researcher`'s stale claude-only resolver is upgraded to the canonical one, and the launcher-parity + bare-call regression guards are extended to `agents/` so no runtime can silently regress. (#1041) (#1045) +- **`gsd-tools generate-claude-md` no longer clobbers a hand-crafted `CLAUDE.md`, and defaults the Claude-runtime output to `./.claude/CLAUDE.md`** — `/gsd-new-project` wrote a repo-root `CLAUDE.md` full of broad project documentation, overwriting/diluting an existing hand-authored instruction file. Now: (1) an existing instruction file that contains no GSD section markers (a hand-crafted file) is left untouched and the command reports `action: "skipped"` — pass `--force` to overwrite intentionally (the flag was already parsed but ignored); (2) the default output for Claude-family runtimes is `./.claude/CLAUDE.md` (a valid project-scoped memory location) instead of repo-root `./CLAUDE.md`, so generated content does not pollute a repo-root file. The config default (`claude_md_path`), the project config template, and the new-project workflow are aligned to the new location. Codex projects still write `AGENTS.md`. (#1098) (#1118) +- **`state record-session` updates an existing `## Session Continuity` section in place instead of appending a duplicate `## Session` block** — on a freshly bootstrapped project (workstream / gsd2-import / new-project templates all emit `## Session Continuity`), the auto-create path recognised only the normalized `## Session` heading, so it appended a second session block. It now inserts only the missing canonical fields after the `## Session Continuity` heading, preserving the heading and any existing prose, and the snapshot / frontmatter readers recognise that heading. (The originally reported `recorded:false`-yet-mutated symptom was already resolved by #944/#948.) (#1101) (#1113) +- **`roadmap annotate-dependencies` no longer fuses the preceding summary line onto the `Plans:` header** — when the match regex's `(?:^|\n)` anchor consumed a leading newline (mid-string match), the replacement dropped it, producing corrupted output like `**Plans:** 3 plansPlans:`. The replacement now re-emits the leading newline when present. (#1103) (#1111) +- **`/gsd-progress` no longer reports a phase as complete (and routes to the next phase) when its verification ended `human_needed` or `gaps_found`** — routing derived completeness from plan/summary counts only and never consulted the `verification.status` query (the seam built in #651). A new Step 1.7 consults it for the current phase, and the routing table sends `gaps_found` to `/gsd:plan-phase {phase} --gaps` (Route V.gaps) and `human_needed` to `/gsd:verify-work {phase}` (Route V.human) before the generic complete row. `passed`, `missing` (unverified), and `unknown` still route as complete, so unverified phases are not falsely blocked. (#1107) (#1116) +- **`write-profile` now writes `USER-PROFILE.md` to the active runtime's config home instead of always `~/.claude`** — under Codex, `gsd-tools query write-profile` wrote `~/.claude/gsd-core/USER-PROFILE.md` while Codex `discuss-phase` advisor-mode (installed under `~/.codex`) checked the Codex home and never found it, so advisor-mode silently stayed disabled. The default output path is now resolved via the runtime-aware `getGlobalConfigDir` (`GSD_RUNTIME` / `config.runtime` → e.g. `~/.codex` for Codex), matching how the runtime's own workflows resolve it — mirroring `generate-dev-preferences`. Claude is unchanged (`~/.claude`); an explicit `--output` still wins. (#1114) (#1119) +- **`/gsd:review` no longer produces a silent empty Codex review on codex-cli < 0.137** — the `codex exec` invocation passed `--dangerously-bypass-hook-trust` (added in codex 0.137.0) unconditionally and discarded stderr, so on older CLIs codex exited with `unexpected argument` before reading the prompt and the empty output was treated as a completed review. The flag is now capability-probed (`codex exec --help | grep`) and applied via `$CODEX_BYPASS_FLAG` only when supported, codex stderr is captured to a `.err` file instead of `/dev/null`, and an empty Codex output is replaced with a diagnostic so a broken reviewer is surfaced rather than silently skipped. (#1115) (#1122) +- **`sandbox_mode` emission in Codex TOML is now gated on the runtime descriptor's `sandboxTier` axis** — previously `installCodexConfig` emitted `sandbox_mode` unconditionally from a hardcoded policy map regardless of whether the runtime descriptor declared a sandbox tier, making the descriptor field cosmetic. `resolveInstallPlan` now projects `sandboxTier` from the capability registry, and `generateCodexAgentToml` / `installCodexConfig` gate emission on `sandboxTier !== 'none'`. The per-agent mode table `CODEX_AGENT_SANDBOX` remains GSD agent policy (not a runtime-descriptor property). For codex (`sandboxTier === 'codex-agent-sandbox'`) output is byte-identical to before; for all other runtimes (`sandboxTier === 'none'`) `sandbox_mode` is correctly omitted. `resolveInstallPlan` now fails loud (throws `TypeError`) on a missing or invalid `sandboxTier` descriptor axis rather than silently coercing garbage to `'none'`, preventing a corrupt/stale registry from silently dropping sandbox enforcement. Full removal of the per-agent registration-tax map remains tracked under #1138. (#1151) (#1152) +- **Installed runtimes no longer silently disable `verify:post` gates** — in a global skills-runtime install (e.g. Codex at `~/.codex`), the `commands/gsd` source tree is absent, so capability-state resolved an empty skill manifest. The full-profile `*` sentinel then materialized to an empty surfaced set, marking every capability `surfaced=false` → `enabled=false`. The result: `gsd-tools loop render-hooks verify:post` returned `activeHooks: []` even with `security_enforcement` and `nyquist_validation` enabled, so the security and Nyquist gates never fired. Capability-state now falls back to the installed `/skills/gsd-*/SKILL.md` layout when the source tree is unreachable, so `verify:post` again includes `security -> secure-phase` and `nyquist -> validate-phase`. (#1206) (#1206) +- **`gsd install` no longer warns that `settings.local.json` "may be malformed" when the file contains a valid JSON `null`.** `readSettings` now treats a successfully-parsed `null` as empty settings (`{}`) instead of collapsing it into the parse-failure path, so a literal-`null` settings file is preserved silently; genuinely unparseable files still emit the warning. (#1191) (#1233) +- **gsd-tools no longer crashes at load on a fresh install** — the installer omitted `scripts/fix-slash-commands.cjs`, which `command-roster` requires at module load, so every `gsd-tools` command failed with MODULE_NOT_FOUND. The installer now ships it (with a smoke assertion), and `readCmdNames()` tolerates a missing commands directory. (#1240) (#1240) +- **`state begin-phase` / `complete-phase` now advance the frontmatter `status` for pipe-table `STATE.md`, not only inline `Status:` files.** The status update matched the YAML frontmatter `status:` line first and never updated a body `| Status | … |` cell, so the frontmatter `status` froze (e.g. stuck at `planning`); it now transitions correctly (`planning → executing → completed`) regardless of whether the body `Status` is inline or pipe-table. (#1255) (#1256) +- **`state planned-phase` now advances the pipe-table `Status` cell (and frontmatter `status`), and `state begin-phase` now updates the Current Position `| Phase |` / `| Plan |` cells instead of prepending stray inline lines.** Systemic follow-up to #1255: `planned-phase` ran its body-field replacements on the full file content, so the YAML frontmatter `status:` line was matched before the body `| Status | … |` cell and the status never reached `Ready to execute`; and `begin-phase` had pipe-table branches only for `Status`/`Last activity`, so for pipe-table `STATE.md` the `Phase`/`Plan` rows were left stale while a spurious inline `Phase: N — EXECUTING` line was prepended. Both handlers now strip frontmatter before body-field replacement and update pipe-table cells in place, matching the inline-format behaviour. (#1257) (#1260) +- +**Parallel worktree execution now has executor-authored cleanup metadata** — executor agents capture their worktree path, branch, and expected base before task commits and return a parseable metadata block for execute-phase to prefer over runtime harness metadata. (#1297) (#1349) +- +**UAT resume now accepts paused checkpoints** — `uat render-checkpoint` treats a non-structured paused `Current Test` placeholder as a resume signal and derives the checkpoint from the first pending UAT test instead of failing as malformed. (#1300) (#1350) +- +**`phase complete` now preserves prose-block STATE phase names** — template-shaped `Current Position` prose now advances with the next phase name, avoids missing-field warnings, and keeps `Last activity:` on the template em-dash delimiter. (#1316) (#1351) +- +**Claude skill installs now avoid rejected `xhigh` effort frontmatter** — heavyweight GSD skills now ship with portable `effort: max`, and the Claude skill converter normalizes any remaining `xhigh` source effort before writing `SKILL.md`. (#1319) (#1352) +- +**Glued letter-prefix phase directories now resolve correctly** -- phase lookup now recognizes tokens like `P0.3` and `M1-2` from directory names, so phase commands can find their plans instead of reporting none found. (#1324) (#1353) +- +**Update backups now ignore preserved shared skills and hooks** -- `/gsd-update` custom-file detection now mirrors installer cleanup scope for shared runtime roots, so non-`gsd-*` skills and hooks are not copied into backup folders unnecessarily. (#1325) (#1354) +- +**Codex skills no longer show up twice in autocomplete** — GSD's Codex install wrote an `agents/openai.yaml` sidecar under every managed `gsd-*` skill directory, and recent Codex builds index both `SKILL.md` and the sidecar, so each skill appeared twice (once as `gsd-foo`, once as a humanized `foo` display name). The installer now stops emitting these sidecars and removes stale ones left by prior installs (pruning the empty `agents/` directory), while preserving user-owned skill directories. Codex discovers GSD skills via `SKILL.md` alone. (#1326) (#1360) +- +**The worktree path guard no longer blocks ordinary writes in non-GSD git worktrees** — the `gsd-worktree-path-guard` PreToolUse hook fired for every `Write`/`Edit` in any linked git worktree, so Claude Code plan-mode writing its plan to `~/.claude/plans/.md` from a manually-created worktree was hard-blocked. The hook now only enforces inside a GSD isolated-executor worktree (branch `worktree-agent-*`) and fails open when a target resolves to no git repository, while still blocking writes that escape to a different git root (the #260 protection) or into a repository's `.git` internals. (#1342) (#1361) +- +**`check.decision-coverage-plan` no longer reports a false pass when a `D-NN` decision header has text before the colon** — `parseDecisions` previously dropped any `- **D-NN …:**` bullet whose header contained a `(parenthetical)`, em-dash, or other prose before the `:**`, silently narrowing the trackable set so the blocking coverage gate green-lit a phase whose dropped decisions were never checked. The parser now tolerates a freeform run before the colon (preserving `[bracket]` tags) and warns on any `D-NN` bullet it still cannot parse instead of dropping it. (#1343) (#1358) +- +**Codex `hooks.json` is now always written in the nested `{ "hooks": { … } }` shape Codex expects** — the writer previously echoed back whatever shape it read, so an empty, absent, or legacy top-level `hooks.json` (`{ "SessionStart": [...] }`) stayed in the legacy shape that current Codex can reject or warn on. Every write now canonicalizes to the nested form, lifting any legacy top-level event entries (including mixed nested+top-level files) under `hooks` without dropping user-owned entries. Managed-hook dedup/removal is unchanged. (#1348) (#1363) +- +**`gsd install --cursor` no longer leaves bare `~/.claude` paths in installed artifacts** — the Cursor install branch only rewrote the trailing-slash `.claude` forms, so bare `~/.claude` / `$HOME/.claude` references survived into installed skills and workflows (e.g. `gsd-surface`, `gsd-graphify`, `plan-phase`, `autonomous`) and tripped the post-install "unreplaced .claude path reference(s)" warning, pointing at a directory that doesn't exist on a Cursor-only install. The Cursor branch now rewrites bare forms too (mirroring the Trae/Augment/Copilot branches), using a `(?![\w-])` lookahead so `.claude-plugin` / `.claudeignore` are not corrupted. (#1356) (#1368) +- **`/gsd-new-project` and `/gsd-new-milestone` now self-heal when the research synthesizer returns `SUMMARY.md` inline instead of writing it** — under some context loads the `gsd-research-synthesizer` agent hits an LLM false-refusal (fabricating a non-existent write restriction) and returns the SUMMARY.md content in its reply rather than writing `.planning/research/SUMMARY.md`. Prompt hardening (#240) reduced but did not eliminate this. Both workflows now verify the file exists after the synthesizer returns and, if it is missing but content came back inline, the orchestrator persists it before spawning `gsd-roadmapper` — so the roadmapper never fails with "SUMMARY.md not found". (#222) (#1042) +- Codex agent TOML generation no longer pins `model_reasoning_effort` when the agent is intentionally inheriting the active Codex chat model. GSD still emits both `model` and `model_reasoning_effort` when a per-agent model override or `runtime: "codex"` resolver pins the model, avoiding the confusing partial state where the model followed Codex UI selection while effort followed GSD catalog defaults. (#838) (#842) +- **profile-pipeline temp output now lands under the reaped GSD temp root.** `cmdExtractMessages` and `cmdProfileSample` previously created their output directories directly in `os.tmpdir()` root (`gsd-pipeline-*` / `gsd-profile-*`), which `reapStaleTempFiles` never scans (it only scans `GSD_TEMP_DIR = os.tmpdir()/gsd`). The directories accumulated forever. Both sites now call `ensureGsdTempDir()` and create under `GSD_TEMP_DIR`. Also adds missing `after`/`afterEach` teardown to four test fixtures that leaked `gsd-*` temp dirs on every `npm test` run. (#866) (#879) +- `getMilestonePhaseFilter` now excludes phase headings inside fenced code blocks (``` ``` ``` or `~~~`) — consistent with the fence-aware behavior of `extractCurrentMilestone`. Previously, a `### Phase N:` line inside a fenced block was wrongly counted as a real phase. (#875) (#880) +- **`gsd_run` launcher shim now probes all non-Claude runtime homes before failing.** The shim's last-resort detection previously stopped at `$HOME/.claude`, causing a false-positive fatal error on every non-Claude runtime (Hermes, Cursor, Codex, Copilot, Windsurf, Augment, Trae, Qwen, CodeBuddy, Cline, Grok, Antigravity, OpenCode, Kilo) when `RUNTIME_DIR` was unset and `gsd-tools` was not on `PATH`. The snippet now probes each runtime's config directory (respecting `HERMES_HOME`, `CURSOR_CONFIG_DIR`, `CODEX_HOME`, etc. with sensible `$HOME`-relative defaults) before emitting the install error. (#903) +- **`validate health` and `validate consistency` no longer emit false-positive W007 warnings for projects using checklist-style ROADMAP.md phases.** `buildRoadmapPhaseVariants()` in `src/validate.cts` previously used only a heading-style regex (`## Phase N: name`), silently ignoring the supported checklist format (`- [x] **Phase N: name**`). This caused every on-disk phase directory to trigger W007 ("exists on disk but not in ROADMAP.md") when the project's ROADMAP used checklist-only notation. The fix adds a second regex pass mirroring the existing `buildNotStartedPhaseVariants()` approach. Additionally, `cmdValidateConsistency()` in `src/verify.cts` had a duplicate inline heading-only regex with the same gap — refactored to delegate to `buildRoadmapPhaseVariants()` (DRY). (#892) (#893) +- **`init execute-phase` and `cmdCommit` now produce correct `branch_name` when `project_code` is set** — the `{phase}` substitution in `phase_branch_template` now calls `normalizePhaseName()`, stripping the project-code prefix and zero-padding the number, so the generated branch is e.g. `gsd/phase-01-foundation` instead of `gsd/phase-CK-01-foundation`. Both the execute-phase output path (`src/init.cts`) and the pre-execution commit path (`src/commands.cts`) are fixed. (#904) (#904) +- **`syncStateFrontmatter` no longer strips `current_phase`, `current_phase_name`, `current_plan`, and `progress` from `STATE.md`** — when body annotations are absent (e.g. after an agent rewrites the body), the existing frontmatter values for those scalars are now preserved, mirroring the fallback already applied in `cmdStateJson`. (#905) (#905) +- **Top-level Claude Code `/gsd-plan-phase` now always spawns the researcher/planner/plan-checker agents instead of collapsing them inline** — a `` block after `` makes the Agent-availability requirement explicit and documents that the workflow fails-closed (stops with a clear log message) in genuinely Agent-less contexts; seven "ORCHESTRATOR RULE — CODEX RUNTIME" labels are renamed to "ALL RUNTIMES" so the guard applies universally; `execute-phase.md` scopes its existing "Other runtimes" inline-fallback prose to non-Claude contexts, preserving the #853 backgrounded-agent behaviour. (#913) (#913) +- **`/gsd-plan-phase`, `/gsd-execute-phase`, `/gsd-autonomous` no longer carry `context: fork`** — these are spawning orchestrators; a forked subagent context has no `Agent` tool, preventing them from spawning the subagents they require. `effort: xhigh` is preserved. Fixes `/gsd:autonomous` halting with "running as a forked subagent" on 1.4.1 (#921). Also replaces the introspection-based Agent-availability check in `plan-phase`'s `` block with an attempt-based gate: the workflow now always attempts the `Agent()` call and only stops if a real tool-unavailable error is returned, eliminating false-negative aborts in top-level sessions (#922). (#921) +- **Claude global install reverted to flat skill layout so concrete skills are discoverable.** PR #883 introduced nested skill layout for Claude (`~/.claude/skills/gsd-ns-/skills//SKILL.md`), but Claude Code's skill discovery scans only one level under `~/.claude/skills/` — nested concrete skills were never listed in the Skill-tool available-skills list and direct `Skill(skill="gsd-plan-phase")` calls stopped working. This fix reverts Claude to the flat layout (`~/.claude/skills/gsd-/SKILL.md`) so all ~61 concrete skills are top-level and immediately discoverable. The 6 other runtimes that confirmed non-recursive scanning (cline, qwen, hermes, augment, trae, antigravity) retain their nested layout. (#924) (#924) +- **`gsd-context-monitor.js` now echoes the actual invoking hook event name** — instead of hardcoding `hookEventName: "PostToolUse"` (or `"AfterTool"` for Gemini), the hook reads `data.hook_event_name` from the stdin payload and falls back to the runtime heuristic only when the field is absent or blank; this fixes Claude Code rejecting hook output with `"expected Stop but got PostToolUse"` when the monitor is invoked by the Stop, SubagentStop, or PreCompact hooks registered in PR #821. (#925) (#926) +- Fix `--reapply` verifier false-positives on post-#604-rename installs caused by two gaps in pristine-baseline handling: + +**Gap 1** (`verify-reapply-patches.cjs`): when `backup-meta.json` records a `pristine_hash` for a file but `gsd-pristine/` has no corresponding snapshot on disk, the verifier fell to over-broad mode (every upstream-changed line treated as a user-added requirement) and produced `FAIL_USER_LINES_MISSING` false positives. Fix: return advisory `OK_NO_BASELINE` reason (non-blocking, exit 0) when a recorded hash is present but the pristine file is absent — the verifier cannot reason correctly without a baseline and must not block. + +**Gap 2** (new migration `004-prune-stale-pristine-get-shit-done`): migration 003 removed legacy `get-shit-done/` runtime files but left `gsd-pristine/get-shit-done/` orphan snapshots in place. Those stale snapshots referenced `get-shit-done/...` key paths that no longer match the active `gsd-core/...` layout, contributing to `FAIL_INSTALLED_MISSING` false reports. Fix: add a new migration (not editing 003, to preserve its checksum) that removes all files under `gsd-pristine/get-shit-done/`. (#934) (#935) +- **`/gsd-update` changelog preview no longer silently fails** — the installer now copies `scripts/changeset/` and `scripts/lib/` into the runtime config dir so `$GSD_DIR/scripts/changeset/cli.cjs` resolves at runtime; `update.md` was updated to use the correct installed path and to surface an explicit error if the CLI is missing rather than swallowing it. (#935) +- **`plan-review-convergence` now runs `gsd-plan-phase` inline instead of inside `Agent()`** — both sites that previously wrapped `gsd-plan-phase` in `Agent()` (initial planning + replan loop) have been changed to bare `Skill()` calls at depth 0. On Claude Code, a depth-1 Agent has no Agent tool, so a wrapped `plan-phase` could never spawn `gsd-planner` or `gsd-plan-checker` — the replan loop silently failed to produce a revised plan whenever HIGH concerns were found. Running plan-phase inline from the depth-0 orchestrator (which retains the Agent tool) restores the full planner→checker sub-agent chain. A new structural guard test (`bug-936-no-nested-spawner-wrap.test.cjs`) statically scans all workflow files and fails if any workflow wraps a spawner orchestrator in `Agent()` without a `RUNTIME != claude` carve-out, preventing regression. (#936) (#939) +- **`--json-errors` now emits a structured error even when a handler throws unexpectedly** — an unexpected (non-`ExitError`) throw fell through to a raw stack trace on stderr, breaking SDK structured-error parsing. (#965) (#987) +- **`verify key-links` docs now correctly state `from:`/`to:` are relative file paths** — the reference implied component/endpoint values the verifier never supported, so locator-style links failed with a misleading 'Source file not found' and the author's `pattern:` was never evaluated. (#967) (#990) +- **Fixed a test-infrastructure regression (#996) where bug-969 hardening tests deleted the shared `gsd-core/bin/lib/core.cjs` during concurrent runs and the build tsbuildinfo lived inside the copied install tree, intermittently failing CI with MODULE_NOT_FOUND/ENOENT.** The destructive tests now run hermetically against a temp project, and the tsbuildinfo moved out of `gsd-core/bin/`. (#969) (#1002) +- **`gsd-planner` now ships the `Edit` tool, so it can no longer destroy `ROADMAP.md` via a whole-file `Write`** — the planner had `Write` but not `Edit` (the #571/#581 writer-agent gap), so an in-place ROADMAP edit fell back to a full overwrite that truncated committed milestone history. The `update_roadmap` step now directs scoped `Edit` calls and explicitly forbids passing the full file to `Write`. (#973) (#989) +- **`graphify query --budget` with no value now errors instead of silently ignoring the budget** — a trailing `--budget` parsed as `NaN` and was treated as 'no budget', so the query ran unbounded with no warning. (#974) (#986) +- **The installer now resolves a stable fnm node path instead of the ephemeral multishell shim on Windows** — managed `.js` hooks were pinned to `fnm_multishells//node.exe`, a per-shell-session path fnm later deletes, breaking every managed hook until reinstall. (#977) (#992) +- **`gsd-tools milestone complete --force` now actually overrides the unstarted-phase guard** — the dispatcher never parsed `--force`, so the guard's own documented escape hatch was inert. (#978) (#982) +- +**Trae and Windsurf installs no longer leak unreplaced `~/.claude` / `$HOME/.claude` paths** — both converters only rewrote trailing-slash `.claude/` forms, so bare home-path references survived conversion and pointed users at the wrong config dir; bare forms are now rewritten (Codex/Cline #570/#782 parity) and `CLAUDE_CONFIG_DIR` maps to the runtime's own var, with `.claude-plugin` preserved. (#983) (#995) +- **Claude Code plugin installs no longer fail with empty `@~/.claude/gsd-core/...` includes** — agents, commands, and templates `@`-include the canonical `~/.claude/gsd-core/` path, but a marketplace plugin install (`claude plugin install`) never creates that directory, so every include resolved to nothing and agents (e.g. the executor) failed. A new `SessionStart` hook (`gsd-ensure-canonical-path.js`) symlinks the canonical path's immutable subdirs (`bin`, `contexts`, `references`, `templates`, `workflows`) to the plugin's bundled tree. It is a no-op in classic `bin/install.js` installs, preserves user-generated files (e.g. `USER-PROFILE.md`), prunes stale links so it self-heals after `claude plugin update`, and uses Windows junctions. (#1207) (#1207) +- **`/gsd-code-review`, `/gsd-code-review --fix`, and `/gsd-eval-review` now inject configured `agent_skills` into their subagents** — these review-family workflows previously spawned their reviewer/fixer/auditor agents (including the `--auto` re-review/re-fix loops) without the project-configured skill and rule context, so any `agent_skills` set for `gsd-code-reviewer`, `gsd-code-fixer`, or `gsd-eval-auditor` were silently ignored. They now query and inject those skills like the ~20 sibling workflows. (#1005) +- **`phase complete` no longer rewrites an existing roadmap completion date** — repeat runs on an already-`Complete` phase preserve the recorded `YYYY-MM-DD` date (4- and 5-column layouts); empty/`-`/non-date cells are still stamped with the current date. (#1177) +- **Legacy ROADMAP projects no longer get deprecation-warning spam** — the free-form ROADMAP warning fired on every command regardless of phase_id_convention; it now only warns when the milestone-prefixed convention is explicitly set and unmet. (#1218) (#1218) +- **Forking workflows target wrong base branch on `master` repos when `origin/HEAD` is unset** — `execute-phase`, `quick`, `ship`, `complete-milestone`, and `pr-branch` detection bash fell through to a hardcoded `main` fallback whenever `origin/HEAD` was absent (common in `git init` + `remote add` + `fetch` without `set-head`, CI checkouts, and worktrees), causing GSD to fork phase branches off a non-existent `main` on `master` repos. Replaced with a single `gsd_run query git.base-branch` resolver that walks the full precedence ladder: config override → `origin/HEAD` symref → `git remote show origin` → local branch presence → `"main"`. (#1198) (#1198) +- **`query user-story.validate` now works** — `mvp-phase` and `verify-work` workflows both invoked this command to validate "As a / I want to / so that" user stories, but no CJS handler existed; every call errored with "Unknown command: user-story". (#1193) (#1193) +- **Context meter no longer sticks at 100%** — the statusline reserved-buffer math was inverted, pinning usage at 100% whenever CLAUDE_CODE_AUTO_COMPACT_WINDOW equalled the total window. (#1194) (#1211) +- **Roadmapper honors phase_id_convention** — new-project roadmaps now use milestone-prefixed phase IDs when phase_id_convention is set, instead of ignoring the default. (#1205) (#1215) +- +**`phase complete` no longer emits false warnings from historical verification metadata or deferred requirement IDs** — two distinct false-positive warning bugs: (A) the verification-status check used a full-text regex that matched `previous_status: gaps_found` in the file body, triggering an "unresolved gaps" warning even when the current frontmatter `status: passed`; the check now reads only the frontmatter `status` key via `extractFrontmatter`. (B) requirement IDs under explicitly deferred/backlog/future/v2 section headings in `REQUIREMENTS.md` were flagged as missing from the Traceability table; the check now skips any section whose heading matches those terms. (#1197) (#1197) +- **verify key-links no longer fails on planned future files** — a from: link whose file is declared in a current/upcoming wave plan’s files_modified is now reported pending instead of a hard missing-file failure. (#1202) (#1219) +- **`state patch` and `state record-session` no longer corrupt STATE.md** — a no-match patch no longer rewrites the file (was resetting `milestone_name` and resurrecting a stale `stopped_at`), and `record-session` now persists `--stopped-at`/`--resume-file` even when the body lacks the exact labels. (#952) +- **`/gsd-update` no longer flags `managed-hooks-registry.cjs` as a custom file** — the shipped hook is now recorded in the file manifest, eliminating a perpetual false-positive custom-file warning. (#953) +- **`gsd-tools` no longer throws `EAGAIN` or truncates output under heavy load** — the CLI's stdout/stderr writes now retry the transient `EAGAIN`/`EINTR` errnos and handle short writes when the output stream is a full non-blocking pipe (e.g. the parallel test runner), instead of throwing or silently dropping bytes. (#1009) +- **Quick worktree execution now accepts parent-or-plan bases for pre-dispatch plan commits** — quick mode records the parent and plan commit around the pre-dispatch PLAN.md commit, lets the worktree guard accept either approved base, materializes the plan from git objects when a runtime forks from the parent, and teaches cleanup to validate the same allowed-base set. (#1265) (#1347) +- **`phase add` no longer reuses an existing phase number when that phase exists only as a roadmap bullet** — the next-number scan now counts phases listed only as `- [ ] **Phase N: ...**` bullets (all checkbox variants, with or without a title), in addition to `### Phase N:` section headers and on-disk phase directories, so a bullet-only phase is no longer shadowed and `phase add` appends after the highest used number. (#1249) +- Preserve curated STATE.md progress frontmatter when `state patch` updates non-progress fields, while still allowing progress-related fields to resync from disk-derived project state. (#1345) +- **The installer no longer re-adds a duplicate managed hook when the user registered it in `command`+`args` (wrapped) form** — the presence checks only inspected `h.command`, so an args-form wrapper (a common Windows windowless-launcher mitigation) was invisible and a stock entry was appended on every install/update, running the hook twice. (#976) (#994) +- `cmdSkillManifest` now discovers concrete skills nested under `gsd-ns-*` routers (`/gsd-ns-/skills//SKILL.md`), so `gsd-health` and `gsd-settings` report the correct count on nested-layout runtimes (cline, qwen, hermes, augment, trae, antigravity). The scan is scoped to `gsd-ns-*` router dirs only — unrelated user dirs that happen to have a `skills/` subdirectory are not traversed. Dual-routed concretes (same skill installed under two routers) are deduped by name within each root. (#929) (#929) +- **state record-session no longer pins a CPU core forever** — acquireStateLock busy-spun at 100% CPU when a recoverable errno (e.g. ENOENT from a removed worktree) persisted, because that retry path skipped the backoff sleep and the 30s time budget. Every retry path is now bounded and backed off. (#1236) (#1236) +- **`/gsd-manager` and `/gsd-autonomous --interactive` no longer silently skip worktree isolation and independent verification on Claude Code.** They dispatched plan/execute as background agents, but a backgrounded Claude Code agent has no Agent/Task tool and cannot spawn the nested executors, plan-checker, or verifier — so isolation and verification silently never ran. Both workflows now resolve the runtime and run plan/execute inline on Claude Code (background dispatch is kept on runtimes that support nested subagents). (#863) +- **Researcher agents can now invoke Perplexity** — `gsd-phase-researcher` and `gsd-project-researcher` referenced `mcp__perplexity__*` in their provider dispatch tables but never granted it in their `tools:` allowlist, so Perplexity web research silently fell through to the next provider. The grant is now generated from the researcher profiles, with a parity guard that fails if a future dispatch-table provider is added without its tool grant. (#1284) (#1288) +- Init phase lookups now resolve active phases whose canonical details live in a flat Phase Details block outside the current milestone summary, restoring requirement coverage for plan/execute/phase-op flows. (#1344) +- **Installer no longer leaks `gsd-cmd-rewrites-*` temp directories.** Each install that emitted slash commands left one `fs.mkdtempSync` directory under the system temp root; on `tmpfs` `/tmp` hosts these accumulated and consumed RAM-backed storage. `installRuntimeArtifacts()` now removes the temp copy in a `finally` once command files are copied. (#862) +- `validate agents` (and `validate health`) now cross-reference the install manifest to detect manifest-backed Codex agent pair drift: when a generated `agents/gsd-*.md` / `agents/gsd-*.toml` pair has one side missing on disk, the agent is reported as incomplete and `agents_found` is `false` (previously a false-healthy `agents_found: true, missing: []`). `validate health` names the incomplete agents and recommends re-running the installer. The check no-ops when no manifest is present. (#1058) (#1079) +- **The `map-codebase` and `docs-update` workflows no longer collect background sub-agent results with the deprecated Claude Code `TaskOutput` tool** — they keep `run_in_background=true` on the spawn and `Read` each agent's `outputFile` (from the `async_launched` result) once it reports completion, removing the `TaskOutput(block=true)` main-session hang surface (anthropics/claude-code#20236). Completion-marker contracts and on-disk verification are unchanged, and the non-Claude runtime fallbacks are preserved. (#1362) +- **`model_policy` is now honored on the default `claude` runtime** — including the `anthropic-fable` Claude Fable 5 preset. Policy-resolved model IDs map to Claude Code agent aliases (e.g. `claude-fable-5` → `fable`), and IDs without a Claude alias warn and fall back to the configured tier. Forward-port of #1133 (originally shipped on the 1.4.5 hotfix line). (#1133) (#1133) +- **`/gsd-plan-review-convergence` now blocks on actionable review findings outside PLAN.md (#724).** The convergence summary contract includes current_actionable alongside current_high, and reviews-mode planning/checking requires actionable MEDIUM/LOW feedback to be incorporated or explicitly deferred in executable PLAN.md content. (#728) +- **Config docs/prompts now match the consumers** — `workflow.subagent_timeout` is documented in milliseconds (default 300000), not "seconds (default 600)" (a user who entered 600 got a 600 ms timeout); `review.models.` is documented as a bare model id injected into `--model`/`-m`, not a shell command; and `workflow.test_command` / `workflow.build_command` (consumed by verify-phase, execute-phase, audit-fix, and the post-merge gate) are now accepted by `config set` and documented. (#1296) (#1299) +- **`changeset new --pr 0` now accepted at creation** — the required-field guard treated the integer 0 as a missing `--pr` flag, so the documented `pr: 0` placeholder could not be authored via the CLI. (#1231) (#1231) +- **`$gsd-quick` Codex adapter no longer assumes typed `spawn_agent(agent_type=...)`** — documents that typed planner/executor spawning needs the agent_type-capable Codex schema and provides a clearly-labeled generic-subagent fallback when only `multi_agent_v1` is exposed. (#958) +- **`state update` and `roadmap update-plan-progress` now handle current Markdown artifact shapes** — state field read/replace works on table-format `STATE.md` (`| Status | … |`), and `roadmap update-plan-progress` inserts missing per-plan checklist rows (filling partial gaps), tolerates `Plans:`/`**Plans:**`/`**Plans**:`, and scopes changes to the active milestone. (#1172) +- `state planned-phase` now advances the Status field when the prior phase left a `Complete ✓` (checkmark) or bare `Complete` terminal status. Previously such a status matched no known template default, so the transition was silently skipped and the state machine stayed stuck on the prior phase. Caveat-bearing statuses (e.g. `Complete but needs manual QA`) remain preserved. (#1070) (#1078) +- **`state.*` writes no longer silently revert the STATE.md frontmatter `status`/`stopped_at`** — an incidental write (e.g. `state record-session`) that doesn't change the body's `Status:`/`Stopped at:` source field now preserves the existing frontmatter value instead of re-deriving it from possibly-stale body text. Legitimate transitions (e.g. `begin-phase`/`complete-phase`, which do update the body Status) still re-derive normally, so a verified-complete phase can no longer be flipped back to `verifying` by an unrelated write. (#1252) +- **`audit-open` no longer false-flags completed quick tasks** — quick-task SUMMARYs now carry `status: complete` in frontmatter by construction, so the milestone-close auditor stops reporting finished quick tasks as `[unknown]`. (#951) +- **Workspace (local) Antigravity and Copilot skill installs no longer point at the global config home** — a local install rewrote `~/.claude/` references in `SKILL.md` bodies to the global `~/.gemini/antigravity/` / `~/.copilot/` paths instead of the workspace-relative `.agent/` / `.github/`, because the skills layout wrapper passed the runtime name into the converter's `isGlobal` parameter slot. (#1092) (#1092) +- Fix the workflow gsd_run launcher being unreachable in later bash blocks on runtimes that run each fenced block in a fresh shell (e.g. Claude Code): ship a standalone gsd-core/bin/gsd_run executable and have the per-file preamble persist the launcher's bin dir onto PATH via CLAUDE_ENV_FILE, with the inline function definition kept as the fallback for all other runtimes. (#1084) +- **`/gsd:phase insert` and `/gsd:phase --edit` no longer dead-end recording Roadmap Evolution** — `query state.add-roadmap-evolution` was rejected as "SDK-only" with an error that pointed back at the very command that just failed, and no CJS handler existed after the SDK retirement. The handler is now implemented in CJS, so the insert/edit phase workflows append the `### Roadmap Evolution` entry under `## Accumulated Context` (creating the subsection if missing, deduping identical entries) as documented. (#1148) (#1148) +- Corrected the installer `--help` profile skill counts: `core` now shows 8 (was 7) and `standard` shows 14 (was 13), both derived from `PROFILES` so they can't drift again; the `full` line drops the stale hardcoded `66` for `all skills`. (#834) (#847) +- **Codex-installed GSD skills and agents no longer rely on a bare `gsd-tools` executable** — generated Codex surfaces now call the bundled shim, and workflow launchers can resolve the Codex shim-only install path. (#731) +- +**Wire the discuss loop step for capability hooks** — capabilities can now register `discuss:pre`/`discuss:post` hooks (e.g. discuss-time context recall and CONTEXT capture); previously `discuss` was contract-declared but structurally unwireable. Also collapses the host-loop file set to a single source of truth and adds an authoring-time guard rejecting hooks at unwired extension points. (#1199) (#1199) +- **`/gsd-autonomous --converge` now routes phase planning through plan-review convergence** instead of silently ignoring the flag. (#711) (#729) +- Hermes skills now install at skills/gsd/gsd-/SKILL.md with name gsd-, restoring canonical /gsd- dispatch that was broken by the bare-stem prefix introduced in #3664. (#955) + ## [1.4.5] - 2026-06-12 ### Fixed diff --git a/CONTEXT.md b/CONTEXT.md index 871b64625..3507c87c4 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -14,9 +14,15 @@ Module owning `milestone complete` (archive roadmap/requirements/phases, build M ### Dispatch Pipeline Module Module that composes Dispatch Policy Module, Query Execution Policy Module, and per-stage handlers (input-validation, plan, execution, result-builder, formatting, error-mapping, observability) into the end-to-end pipeline that produces a `QueryDispatchResult`. The SDK-era pipeline collapsed onto the Command Routing Hub per ADR-0174; current dispatch seam: `gsd-core/bin/lib/command-routing-hub.cjs` (see Command Routing Hub below). +### Phase Id Module +Module owning the pure phase-id parsing and matching helpers: phase-name normalization, phase-token extraction/matching, milestone- and phase-dir id parsing, and phase-markdown regex builders (`escapeRegex`, `normalizePhaseName`, `comparePhaseNum`, `extractPhaseToken`, `phaseTokenMatches`, `phaseMarkdownRegexSource`/`phaseMarkdownRegexSourceExact`, `getMilestoneFromPhaseId`, `getPhaseDirFromPhaseId`). Pure string/regex — no I/O, no config, no other core dependency. Extracted from the Core module per ADR-857 rollout phase 2a (#865) as the cycle-free leaf that unblocks the roadmap-parser and phase-locator extractions; the `core.cjs` re-export spine was retired in epic #1267, so callers import this leaf directly. Source of truth: `gsd-core/bin/lib/phase-id.cjs` (generated from `src/phase-id.cts`). + ### Phase Lifecycle Module Module owning phase create, rename, complete, remove, list, and plan-index operations, plus phase-dir prefix validation, STATE.md staleness detection, and auto-prune behaviour. Entry point: `gsd-core/bin/lib/phase.cjs` (CJS surface). Typed phase events: `GSDPhaseStartEvent`, `GSDPhaseStepStartEvent`, `GSDPhaseStepCompleteEvent`, `GSDPhaseCompleteEvent`. (The SDK native-query surface, the `types.ts` event definitions, `phase-runner.ts`, and `phase-prompt.ts` were retired with the SDK package per ADR-0174.) +### Phase Locator Module +Module owning phase-directory search and location: active-phase discovery against the `.planning/phases/` tree (`searchPhaseInDir`, `findPhaseInternal`) and archived-phase-dir enumeration (`getArchivedPhaseDirs`), matching phase ids/tokens against the filesystem. Depends only on leaf modules (`phase-id` for token/name matching, `core-utils` for fs-scan/path helpers, `planning-workspace` for `planningDir`) — no `loadConfig`, no other core dependency. Extracted from the Core module per ADR-857 rollout phase 2d (#881); the `core.cjs` re-export spine was retired in epic #1267, so callers import this leaf directly. Source of truth: `gsd-core/bin/lib/phase-locator.cjs` (generated from `src/phase-locator.cts`). + ### Dispatch Policy Module Module owning dispatch error mapping, fallback policy, timeout classification, and CLI exit mapping contract. @@ -65,7 +71,7 @@ Single dispatch seam (`gsd-core/bin/lib/command-routing-hub.cjs`) that centraliz Single-runtime seam layout for this repository after SDK retirement. Runtime execution paths live under `gsd-core/bin/lib/` and are grouped by seam concern (dispatch, manifest, handlers, runtime, observability, installer). ADR-0174 preserves the seam vocabulary and defines the canonical long-term shape as a seam-aligned TypeScript `src/` tree (`src/dispatch/`, `src/handlers/`, `src/errors/`, `src/manifest/`, `src/config/`, `src/state/`, `src/workstream/`, `src/runtime/`, `src/cli/`, `src/observability/`) compiled to CJS. ### Runtime Launcher Module -Canonical space-safe shell preamble (`gsd_run`) used by every workflow bash block to invoke the GSD runtime CLI. Resolves `gsd-core/bin/gsd-tools.cjs` via `node` when present, falls back to a `gsd-tools` binary on PATH, else errors. Single source of truth: `gsd-core/workflows/_runtime-launcher.snippet.sh`; propagated by `scripts/sync-runtime-launcher.cjs`; enforced by `tests/runtime-launcher-parity.test.cjs`. Replaced the retired unquoted `$GSD_SDK` variable (#373). +Canonical space-safe shell preamble (`gsd_run`) used by every workflow bash block to invoke the GSD runtime CLI. Resolves `gsd-core/bin/gsd-tools.cjs` via `node` when present, falls back to a `gsd-tools` binary on PATH, else errors. Single source of truth: `gsd-core/workflows/_runtime-launcher.snippet.sh`; propagated by `scripts/sync-runtime-launcher.cjs`; enforced by `tests/runtime-launcher-parity.test.cjs`. Replaced the retired unquoted `$GSD_SDK` variable (#373). `gsd_run` is also shipped as a standalone executable (`gsd-core/bin/gsd_run`) via the npm `bin` field; on runtimes that run each fenced bash block in a fresh shell (e.g. Claude Code), the per-file preamble appends the bin directory to `CLAUDE_ENV_FILE` so `gsd_run` resolves from PATH in later blocks — the inline function definition remains the fallback for all other runtimes. ### Dispatch Observability Module Module owning dispatch-event creation, redaction, and logger behavior for the Command Routing Hub. Core files: `gsd-core/bin/lib/observability/event.cjs`, `gsd-core/bin/lib/observability/logger.cjs`, `gsd-core/bin/lib/observability/redaction.cjs`. Contract: silent on success by default, structured JSON to stderr on error, and opt-in audit trail at `.planning/.gsd-trace.jsonl` via `GSD_AUDIT=1` or config (`audit.enabled`). Each dispatch carries a `traceId`; composed dispatches set `parentTraceId` for correlation. @@ -74,7 +80,7 @@ Module owning dispatch-event creation, redaction, and logger behavior for the Co Module policy that defines query-time behavior when `.planning/config.json` is absent: use built-in defaults for parity-sensitive query Interfaces, and emit parity-aligned empty model ids for pre-project model resolution surfaces. ### Configuration Module -Module owning config load, legacy-key normalization, defaults merge, and explicit on-disk migration for `.planning/config.json`. Interface: `loadConfig(cwd) → MergedConfig` (pure read, never writes disk), `normalizeLegacyKeys(parsed) → { parsed, normalizations[] }` (idempotent, pure, returns the list of normalizations applied), `mergeDefaults(parsed) → MergedConfig` (deep-merge of parsed config over canonical defaults), `migrateOnDisk(cwd) → MigrationReport` (explicit, opt-in, called by the installer and by `gsd-tools migrate-config`). Invariants: never mutates disk inside `loadConfig`; legacy top-level keys (`branching_strategy`, `sub_repos`, `multiRepo`, `depth`) are normalized into their canonical nested locations in the returned value; defaults come from the shared `gsd-core/bin/shared/config-defaults.manifest.json`; schema (`VALID_CONFIG_KEYS`, `RUNTIME_STATE_KEYS`, `DYNAMIC_KEY_PATTERNS`) comes from `gsd-core/bin/shared/config-schema.manifest.json`. Source of truth: `gsd-core/bin/lib/configuration.cjs`, consumed via the thin Adapters at `bin/lib/core.cjs:loadConfig` and `bin/lib/config-schema.cjs`. Eliminates the recurring #3523-class drift bug structurally. +Module owning legacy-key normalization, defaults merge, and explicit on-disk migration for `.planning/config.json`. Interface: `normalizeLegacyKeys(parsed) → { parsed, normalizations[] }` (idempotent, pure, returns the list of normalizations applied), `mergeDefaults(parsed) → MergedConfig` (deep-merge of parsed config over canonical defaults), `migrateOnDisk(cwd) → MigrationReport` (explicit, opt-in, called by the installer and by `gsd-tools migrate-config`). Invariants: legacy top-level keys (`branching_strategy`, `sub_repos`, `multiRepo`, `depth`) are normalized into their canonical nested locations in the returned value; defaults come from the shared `gsd-core/bin/shared/config-defaults.manifest.json`; schema (`VALID_CONFIG_KEYS`, `RUNTIME_STATE_KEYS`, `DYNAMIC_KEY_PATTERNS`) comes from `gsd-core/bin/shared/config-schema.manifest.json`. Note: `loadConfig` (project config read + merge) was extracted to the Config Loader Module (`config-loader.cjs`) per ADR-857 phase 2e (#885); `configuration.cjs` now provides only the pure normalization and defaults primitives that `config-loader.cjs` depends on. Source of truth: `gsd-core/bin/lib/configuration.cjs`, consumed via `bin/lib/config-loader.cjs` and `bin/lib/config-schema.cjs`. Eliminates the recurring #3523-class drift bug structurally. ### Planning Workspace Module Module owning `.planning` path resolution, active workstream pointer policy (`session-scoped > shared`), pointer self-heal behavior, and planning lock semantics for workstream-aware execution. @@ -83,13 +89,13 @@ Module owning `.planning` path resolution, active workstream pointer policy (`se Module owning workstream directory discovery, per-workstream state projection, phase/plan/summary counting, roadmap-declared phase count, active marker projection, and active-workstream collision inputs. Command handlers render list/status/progress outputs from this inventory instead of rescanning `.planning/workstreams/*` directly. Source of truth for the pure projection is `gsd-core/bin/lib/workstream-inventory-builder.cjs` (a Builder Module); the Reader Adapter `gsd-core/bin/lib/workstream-inventory.cjs` collects filesystem inputs and delegates projection to the Builder. ### Project-Root Resolution Module -Module owning project-root resolution from any starting directory. Walks the ancestor chain (bounded by `FIND_PROJECT_ROOT_MAX_DEPTH = 10`) applying four heuristics in order: (0) own `.planning/` guard (#1362), (1) parent `.planning/config.json` `sub_repos` traversal, (2) legacy `multiRepo: true` boolean + ancestor `.git`, (3) `.git` heuristic with parent `.planning/`. Returns `startDir` when no ancestor qualifies. Sync `node:fs` I/O. Source of truth: `gsd-core/bin/lib/project-root.cjs`; consumed via a thin re-export at `gsd-core/bin/lib/core.cjs`. +Module owning project-root resolution from any starting directory. Walks the ancestor chain (bounded by `FIND_PROJECT_ROOT_MAX_DEPTH = 10`) applying four heuristics in order: (0) own `.planning/` guard (#1362), (1) parent `.planning/config.json` `sub_repos` traversal, (2) legacy `multiRepo: true` boolean + ancestor `.git`, (3) `.git` heuristic with parent `.planning/`. Returns `startDir` when no ancestor qualifies. Sync `node:fs` I/O. Source of truth: `gsd-core/bin/lib/project-root.cjs`; the `core.cjs` re-export spine was retired in epic #1267, so callers import this leaf directly. ### Planning Path Projection Module -SDK query Module owning projection from project/workstream context to concrete `.planning` paths. Policy precedence is `explicit workstream > env workstream > env project > root`. Invalid workspace context is a validation error at this seam rather than a silent fallback. +Module owning projection from project/workstream context to concrete `.planning` paths. Policy precedence is `explicit workstream > env workstream > env project > root`. Invalid workspace context is a validation error at this seam rather than a silent fallback. ### Worktree Safety Policy Module -CJS Module owning worktree lifecycle safety policy for the GSD orchestration layer. Interface: `resolveWorktreeContext(cwd, deps) → WorktreeContext` (linked-worktree root mapping), `parseWorktreePorcelain(output) → WorktreeEntry[]` (porcelain parser, skips detached HEAD), `planWorktreePrune(repoRoot, opts, deps) → PrunePlan` (metadata-prune plan, never destructive by default), `executeWorktreePrunePlan(plan, deps) → PruneResult` (executes prune; degrades gracefully on git timeout), `listLinkedWorktreePaths(repoRoot, deps) → LinkedPathsResult`, `inspectWorktreeHealth(repoRoot, opts, deps) → HealthResult` (orphan + stale detection), `snapshotWorktreeInventory(repoRoot, opts, deps) → InventoryResult`, `planWorktreeWaveCleanup(repoRoot, manifest) → CleanupPlan` (manifest-scoped, fail-closed), `executeWorktreeWaveCleanupPlan(plan, deps) → CleanupResult`. Source of truth: `gsd-core/bin/lib/worktree-safety.cjs`. Timeout path: all git subprocess calls are bounded; callers receive `ok:false, reason:'git_timed_out'` rather than a thrown exception. Test anchor: `tests/worktree-safety.test.cjs`. +CJS Module owning worktree lifecycle safety policy for the GSD orchestration layer. Interface: `resolveWorktreeContext(cwd, deps) → WorktreeContext` (linked-worktree root mapping), `parseWorktreePorcelain(output) → WorktreeEntry[]` (porcelain parser, skips detached HEAD), `planWorktreePrune(repoRoot, opts, deps) → PrunePlan` (metadata-prune plan, never destructive by default), `executeWorktreePrunePlan(plan, deps) → PruneResult` (executes prune; degrades gracefully on git timeout), `listLinkedWorktreePaths(repoRoot, deps) → LinkedPathsResult`, `inspectWorktreeHealth(repoRoot, opts, deps) → HealthResult` (orphan + stale detection), `snapshotWorktreeInventory(repoRoot, opts, deps) → InventoryResult`, `planWorktreeWaveCleanup(repoRoot, manifest) → CleanupPlan` (manifest-scoped, fail-closed), `executeWorktreeWaveCleanupPlan(plan, deps) → CleanupResult`. Source of truth: `gsd-core/bin/lib/worktree-safety.cjs`. Timeout path: all git subprocess calls are bounded; callers receive `ok:false, reason:'git_timed_out'` rather than a thrown exception. Test anchor: `tests/worktree-safety.test.cjs`. The `core.cjs` re-export spine was retired in epic #1267: this module absorbed the two thin compositional wrappers that squatted in Core — `resolveWorktreeRoot(cwd, deps)` (a projection over `resolveWorktreeContext`) and `pruneOrphanedWorktrees(...)` (sequences `planWorktreePrune` + `executeWorktreePrunePlan` with a timeout warning) — so callers reach this single worktree-lifecycle seam directly. `gitWorktreeInfoInternal` did NOT move here — worktree-info detection belongs to the Git Query Module. ### Worktree Lifecycle Module Workflow contract seam covering agent worktree lifecycle orchestration rules. The `worktree_branch_check` block lives in one canonical fragment (`gsd-core/references/worktree-branch-check.md`) that `execute-phase.md`, `quick.md`, `diagnose-issues.md`, and `execute-plan.md` embed at dispatch. Key invariants: `worktree_branch_check` is **verify-only and fail-closed** — the orchestrator owns worktree lifecycle and base recovery, so the sub-agent holds no state-correction primitives; HEAD attachment verified via `git symbolic-ref`; positive allow-list `^worktree-agent-*` enforced; `git update-ref` on protected refs is prohibited; on base mismatch the sub-agent halts with `exit 42` and surfaces to the orchestrator (#48); the orchestrator runs a cwd-drift guard at `execute_waves` entry that resolves the worktree root and refuses drift into an agent worktree (#48); cleanup is manifest-scoped (`WAVE_WORKTREE_MANIFEST`) not global-discovery-based; worktree spawning is sequential (one `run_in_background` at a time to avoid `config.lock` contention). Test anchor: `tests/worktree.test.cjs`. @@ -97,6 +103,9 @@ Workflow contract seam covering agent worktree lifecycle orchestration rules. Th ### Worktree Root Resolution Adapter Module Adapter Module owning linked-worktree root mapping and metadata-prune policy (`git worktree prune` non-destructive default) for planning/workstream callers. +### Git Query Module +Module owning bounded, never-throw git repository introspection — the single seam for read-only git queries that degrade gracefully rather than throwing. **Adapter 1 — base-branch detection** (`gsd_run query git.base-branch`): Implements a full precedence ladder: (1) `git.base_branch` config override from `.planning/config.json`; (2) `git symbolic-ref --short refs/remotes/origin/HEAD`; (3) `git remote show origin` HEAD branch (authoritative when origin/HEAD is unset — the common case for `git init + remote add + fetch` without `set-head`); (4) local branch existence (`master` present and `main` absent → `master`; `main` present → `main`); (5) `"main"` last-resort default. All git subprocesses are bounded with timeouts (5–15 s) and degrade gracefully to the next tier; the function never throws. Replaces duplicated per-workflow bash detection that silently fell through to `:-main` on master repos (#1146). **Adapter 2 — worktree-info detection**: `gitWorktreeInfoInternal` (`git rev-parse --is-inside-work-tree` + `--show-toplevel`), absorbed from the Core module when the `core.cjs` re-export spine was retired and aligned to this module's bounded-timeout / degrade-don't-throw convention (worktree-info detection is a query concern, distinct from the Worktree Safety Policy Module's lifecycle policy). Source: `src/git-base-branch.cts` → `gsd-core/bin/lib/git-base-branch.cjs`. Wired into `execute-phase.md`, `quick.md`, `ship.md`, `complete-milestone.md`, and `pr-branch.md`. + ### Runtime Name Policy Module Module owning runtime identity normalization at runtime-selection seams. Canonicalizes alias signals from env/config (`GSD_RUNTIME`, `.planning/config.json:runtime`) to supported runtime IDs so output emitters and query runtime gates stay consistent across naming variants (for example `codex-app`/`codex-cli` -> `codex`). Sources: `gsd-core/bin/lib/runtime-name-policy.cjs`, alias manifest `gsd-core/bin/shared/runtime-aliases.manifest.json`. @@ -104,7 +113,25 @@ Module owning runtime identity normalization at runtime-selection seams. Canonic Module owning validation for Installer Migration Module records and planned actions. It enforces migration metadata, explicit install scopes, ownership evidence for destructive/config actions, and runtime contract citations for runtime config rewrites before a migration can enter planning or apply. ### Installer Module -Primary installer for all runtimes. Single production file: `bin/install.js` (generated). Exports: `install(isGlobal, runtime[, configDir])` → typed result `{ runtime, configDir, settingsPath, settings, statuslineCommand, updateBannerCommand }`; `uninstall(isGlobal, runtime[, configDir])`; `installRuntimeArtifacts(runtime, configDir, scope, resolvedProfile)`; `uninstallRuntimeArtifacts(runtime, configDir, scope)`; `writeManifest(configDir, runtime)`. Runtime enum: `allRuntimes` (15 values: claude, antigravity, augment, cline, codebuddy, codex, copilot, cursor, gemini, hermes, kilo, opencode, qwen, trae, windsurf). Directory helpers: `getDirName(runtime)` → local dir name; `getConfigDirFromHome(runtime, isGlobal)` → shell-quoted path fragment. Per-runtime global config-dir resolution is delegated to `gsd-core/bin/lib/runtime-homes.cjs:getGlobalConfigDir(runtime[, explicitDir])` — the canonical, env-var–aware projection (`explicitDir` override + opencode/kilo `*_CONFIG` file-path precedence); the legacy in-installer `getGlobalDir`/`getOpencodeGlobalDir`/`getKiloGlobalDir` were retired into it (#56). Runtime-specific helpers: `resolveKiloConfigPath(configDir)`, `configureKiloPermissions(isGlobal[, explicitDir])`. Claude-specific permission helpers: `mergeClaudePermissions(settings)` — non-destructively appends GSD-owned allow/deny entries (see `GSD_CLAUDE_ALLOW_PERMISSIONS`, `GSD_CLAUDE_DENY_PERMISSIONS` constants) to a Claude Code settings object; called from `finishInstall` for `runtime === 'claude'` only; uninstall removes exactly these entries (#768). Layout-driven artifact copy/removal delegates to `gsd-core/bin/lib/runtime-artifact-layout.cjs:resolveRuntimeArtifactLayout` (throws `TypeError` for unknown runtimes). Hermes uses nested `skills/gsd//` layout (prefix: ''); other skill-runtimes use flat `skills/gsd-/` layout. See Skill Surface Budget Module and Runtime Artifact Layout Module. +Primary installer for all runtimes. Single production file: `bin/install.js` (generated). Exports: `install(isGlobal, runtime[, configDir])` → typed result `{ runtime, configDir, settingsPath, settings, statuslineCommand, updateBannerCommand }`; `uninstall(isGlobal, runtime[, configDir])`; `installRuntimeArtifacts(runtime, configDir, scope, resolvedProfile)`; `uninstallRuntimeArtifacts(runtime, configDir, scope)`; `writeManifest(configDir, runtime)`. Runtime enum: `allRuntimes` (15 values: claude, antigravity, augment, cline, codebuddy, codex, copilot, cursor, gemini, hermes, kilo, opencode, qwen, trae, windsurf). Directory helpers: `getDirName(runtime)` → local dir name; `getConfigDirFromHome(runtime, isGlobal)` → shell-quoted path fragment. Per-runtime global config-dir resolution is delegated to `gsd-core/bin/lib/runtime-homes.cjs:getGlobalConfigDir(runtime[, explicitDir])` — the canonical, env-var–aware projection (`explicitDir` override + opencode/kilo `*_CONFIG` file-path precedence); the legacy in-installer `getGlobalDir`/`getOpencodeGlobalDir`/`getKiloGlobalDir` were retired into it (#56). Runtime-specific helpers: `resolveKiloConfigPath(configDir)`, `configureKiloPermissions(isGlobal[, explicitDir])`. Claude-specific permission helpers: `mergeClaudePermissions(settings)` — non-destructively appends GSD-owned allow/deny entries (see `GSD_CLAUDE_ALLOW_PERMISSIONS`, `GSD_CLAUDE_DENY_PERMISSIONS` constants) to a Claude Code settings object; called from `finishInstall` for `runtime === 'claude'` only; uninstall removes exactly these entries (#768). Layout-driven artifact copy/removal delegates to `gsd-core/bin/lib/runtime-artifact-layout.cjs:resolveRuntimeArtifactLayout` (throws `TypeError` for unknown runtimes). Seven runtimes with non-recursive skill loaders (claude global, cline, qwen, hermes, augment, trae, antigravity) use a nested router layout: 6 `gsd-ns-*` router bundles emitted as top-level skills, with concrete skills nested at `/skills//SKILL.md` (hermes prefix='': `skills/gsd/ns-*/…`). The remaining skills-runtimes (cursor, codex, copilot, windsurf, codebuddy, opencode, kilo) use the flat `skills/gsd-/` layout unchanged. See Skill Surface Budget Module and Runtime Artifact Layout Module. + +### I/O Module +Module owning the tool's CLI I/O primitives: `output()` result emission (with large-payload temp-file spillover via `GSD_TEMP_DIR`/`ensureGsdTempDir`/`reapStaleTempFiles`), `error()` stderr emission with exit-code mapping, and the JSON-error-mode toggle (`setJsonErrorMode`/`getJsonErrorMode`, `ERROR_REASON`). Extracted from the Core module per ADR-857 rollout phase 1 (#859) so feature modules (`graphify`, `intel`, `audit`, `profile-pipeline`) depend on a small I/O seam instead of the core god-module; the `core.cjs` re-export spine was retired in epic #1267, so callers import this leaf directly. Source of truth: `gsd-core/bin/lib/io.cjs` (generated from `src/io.cts`). + +### Roadmap Parser Module +Module owning ROADMAP.md parsing: shipped-milestone slicing, current-milestone extraction, milestone/phase lookups, and milestone-phase filtering (`stripShippedMilestones`, `extractCurrentMilestone`, `replaceInCurrentMilestone`, `getRoadmapPhaseInternal`, `getMilestoneInfo`, `getMilestonePhaseFilter`). Depends only on leaf modules (`phase-id`, `planning-workspace`, `shell-command-projection`) — no `loadConfig`, no other core dependency. Extracted from the Core module per ADR-857 rollout phase 2b (#870), resolving the ROADMAP.md parse/write straddle so the Roadmap module (`roadmap.cjs`, which owns ROADMAP.md mutation) imports parsing directly instead of through Core; the `core.cjs` re-export spine was retired in epic #1267, so callers import this leaf directly. Source of truth: `gsd-core/bin/lib/roadmap-parser.cjs` (generated from `src/roadmap-parser.cts`). + +### Core Utilities Module +Module owning the shared low-level utility primitives extracted from Core: POSIX path normalization (`toPosixPath`), filesystem scanning (`detectSubRepos`, `readSubdirectories`, `getPhaseFileStats`, `pathExistsInternal`), and small pure helpers (`generateSlugInternal`, `extractOneLinerFromBody`, `filterPlanFiles`, `filterSummaryFiles`, `extractCanonicalPlanId`, `timeAgo`). Depends only on Node built-ins and already-leafed modules (`phase-id` for `comparePhaseNum`, `planning-workspace` for `findContextMdIn`) — no `loadConfig`, no other core dependency. Extracted from the Core module per ADR-857 rollout phase 2c (#877) as the shared leaf that unblocks the phase-locator fs-search extraction (2d); the `core.cjs` re-export spine was retired in epic #1267, so callers import this leaf directly. Source of truth: `gsd-core/bin/lib/core-utils.cjs` (generated from `src/core-utils.cts`). + +### Agent Install Check Module +Module owning agent-presence resolution and verification, extracted from the Core module as the cleanup step that retired the `core.cjs` re-export spine (the final ADR-857 decomposition, epic #1267). Interface: `getAgentsDir(runtime?, env?)` — env-var-aware, runtime-aware agents-directory resolution (the `claude` runtime resolves `__dirname`-relative); `checkAgentsInstalled(...)` — multi-runtime agent-presence check that validates `gsd-file-manifest.json` completeness and confirms the declared agents exist on disk. Pure read/verify — no install-write side effects (writes remain the Installer Module's). Consumed by the Init Command Module, the verify workflow, and the docs workflow. Source of truth: `gsd-core/bin/lib/agent-install-check.cjs` (generated from `src/agent-install-check.cts`); replaced the two functions that squatted in `core.cts`. See Installer Module and ADR-857. + +### Config Loader Module +Module owning project configuration loading: reads `.planning/config.json`, merges built-in defaults (`CONFIG_DEFAULTS`/`CANONICAL_CONFIG_DEFAULTS`), normalizes legacy keys, applies the active-workstream overlay, validates against the config schema, and warns on unknown keys/profile overrides (`loadConfig` plus its `_deepMergeConfig`/`isGitIgnored`/`_warnUnknownProfileOverrides` helpers). Depends only on leaf modules (`configuration`, `config-schema`, `planning-workspace`, `shell-command-projection`, `core-utils`, `model-catalog`) — no other core dependency. Extracted from the Core module per ADR-857 rollout phase 2e (#885) as the prerequisite for the model-resolver extraction (the resolvers call `loadConfig`); the `core.cjs` re-export spine was retired in epic #1267, so callers import this leaf directly. Source of truth: `gsd-core/bin/lib/config-loader.cjs` (generated from `src/config-loader.cts`). + +### Model Resolver Module +Module owning model and effort resolution policy: resolves the model, runtime tier, planning granularity, reasoning effort, and fast-mode for a given agent by reading project config and resolving against the model profiles and catalog (`resolveModelInternal`, `resolveModelPolicy`, `resolveTierEntry`, `resolveModelForTier`, `resolveGranularityInternal`, `resolveEffortInternal`, `resolveFastModeInternal`, `resolveEffortForTier`, `nextEffort`, `assertValidGranularityOverride`). Depends only on leaf modules (`config-loader` for `loadConfig`, `configuration` for defaults, `model-profiles` and `model-catalog` for the static tables) — no other core dependency. Extracted from the Core module per ADR-857 rollout phase 2f (#888) — the final core.cts decomposition step; the `core.cjs` re-export spine was retired in epic #1267, so callers import this leaf directly. Source of truth: `gsd-core/bin/lib/model-resolver.cjs` (generated from `src/model-resolver.cts`). ### Package Identity Module [Planned] Single seam owning GSD's published-package coordinates so a repoint/rename is a one-line change instead of a tree-wide sweep. Source of truth is `package.json`; values are *derived*, not re-typed: `packageName` (`.name` → `@opengsd/get-shit-done-redux`), `binName` (`Object.keys(.bin)[0]` → `get-shit-done-redux`), `repoSlug` (parsed from `.repository.url` → `open-gsd/get-shit-done-redux`), plus derived `changelogRawUrl` and `manualInstallCommand({ scope, runtime })`. Generated `.cjs` per ADR-457 (generated-single-source); shipped under `gsd-core/bin/lib/`. Three consumer worlds: **Node** consumers `require()` it at runtime (worker, `check-latest-version.cjs`, `bin/install.js`); the **bash launcher** snippet receives the literal injected by `scripts/sync-runtime-launcher.cjs` at sync time; **prose/help** literals (`update.md`, installer help) carry a committed copy. A drift-guard lint (`scripts/lint-package-identity-drift.cjs`, sibling to `check:alias-drift`) fails CI on any raw package/repo literal outside `package.json`, the generated module, and the value-checked materialization sites — this is what keeps the seam real (`two adapters`, not one). Replaces the contradictory pair it consolidates: the runtime-broken `require('../package.json').name` in `hooks/gsd-check-update-worker.js` (#378, resolves to `undefined` post-install) and the hardcoded constant in `check-latest-version.cjs` (#2992). _Avoid_: "package name string", "the npm name" (when you mean the seam). See ADR-457 and Installer Module. @@ -116,13 +143,81 @@ Module owning install detection for `/gsd:update`. `resolveUpdateContext({ home, Module owning which skills and agents are written to runtime config directories at install time (Phase 1) and at runtime via cluster-level toggles (Phase 2). Phase 1: `gsd-core/bin/lib/install-profiles.cjs` defines named profiles (`core`, `standard`, `full`), computes transitive closure over `requires:` frontmatter, stages skills/agents to runtime config dirs, and persists the chosen profile in a `.gsd-profile` marker. Profile resolution precedence: explicit `--profile=` flag > `.gsd-profile` marker > `full`. `--minimal`/`--core-only` are back-compat aliases for `--profile=core`. Phase 2: `gsd-core/bin/lib/surface.cjs` implements the `/gsd:surface` slash command for cluster-level enable/disable without reinstall; cluster definitions live in `gsd-core/bin/lib/clusters.cjs`; per-runtime state persists in `/.gsd-surface.json` independent from the `.gsd-profile` marker. See ADR-0011. ### Runtime Artifact Layout Module -Module owning the per-runtime mapping from artifact kind to filesystem placement. ADR-3660 defines the typed `kinds` per runtime (`commands`, `agents`, `skills`) with destination subpath, prefix, and stage adapter (with per-runtime converters in `bin/install.js`: `convertClaudeCommandToClaudeSkill`, `…CodexSkill`, `…CopilotSkill`, `…AntigravitySkill`). Phase 1 applies this seam to the Runtime Surface Module (`surface.cjs:applySurface`); as of #813, `applySurface` applies the same per-runtime skill-body path rewrites as `installRuntimeArtifacts` for `skills` kinds — re-surfacing no longer overwrites installed SKILL.md bodies with converter-default `~/.claude` paths. The shared accessor `getInstallExports` (exported from `runtime-artifact-layout.cjs`) is the single-source seam through which `surface.cjs` reaches `computePathPrefix` and `applyRuntimeContentRewritesInPlace`; the resolved `scope` (`'local'`|`'global'`) is now carried on the `Layout` object returned by `resolveRuntimeArtifactLayout` so `applySurface` derives the same `pathPrefix` (global `$HOME` form vs. absolute) as a fresh install. Phase 2 is planned to migrate install/uninstall in `bin/install.js` so all lifecycle sites iterate one shared layout table instead of re-encoding runtime layout logic. This design is intended to remove the #3659 class of omissions. Migrations remain under the Installer Migration Module (ADR-0008). See ADR-3660. +Module owning the per-runtime mapping from artifact kind to filesystem placement. ADR-3660 defines the typed `kinds` per runtime (`commands`, `agents`, `skills`) with destination subpath, prefix, and stage adapter (with per-runtime converters in `bin/install.js`: `convertClaudeCommandToClaudeSkill`, `…CodexSkill`, `…CopilotSkill`, `…AntigravitySkill`). Owns the per-runtime `nested` skill-bundle decision (#69): a `skillsKind` flag in `src/runtime-artifact-layout.cts` drives whether a runtime receives the nested router layout (6 `gsd-ns-*` routers + concrete skills under `/skills//`) or the flat `skills/gsd-/` layout; the evidence/doc-link matrix is recorded in a comment above `resolveRuntimeArtifactLayout`. Phase 1 applies this seam to the Runtime Surface Module (`surface.cjs:applySurface`); as of #813, `applySurface` applies the same per-runtime skill-body path rewrites as `installRuntimeArtifacts` for `skills` kinds — re-surfacing no longer overwrites installed SKILL.md bodies with converter-default `~/.claude` paths. The shared accessor `getInstallExports` (exported from `runtime-artifact-layout.cjs`) is the single-source seam through which `surface.cjs` reaches `computePathPrefix` and `applyRuntimeContentRewritesInPlace`; the resolved `scope` (`'local'`|`'global'`) is now carried on the `Layout` object returned by `resolveRuntimeArtifactLayout` so `applySurface` derives the same `pathPrefix` (global `$HOME` form vs. absolute) as a fresh install. Phase 2 is planned to migrate install/uninstall in `bin/install.js` so all lifecycle sites iterate one shared layout table instead of re-encoding runtime layout logic. This design is intended to remove the #3659 class of omissions. Migrations remain under the Installer Migration Module (ADR-0008). See ADR-3660. + +### Runtime Artifact Conversion Module +Sibling Module to Runtime Artifact Layout Module. Owns projection from canonical Claude-authored command/agent/skill markdown into runtime-specific artifact bodies, including converter selection, frontmatter/body normalization, runtime path rewrites, and staged artifact generation. Runtime Artifact Layout remains responsible for filesystem placement (`kind`, destination subpath, prefix, nesting); Runtime Artifact Conversion owns the content Implementation behind that placement seam so install, uninstall/surface parity, and future plugin/package projections stop reaching back through `bin/install.js` for converter functions or `GSD_TEST_MODE`-guarded installer exports. Chosen direction: sibling Module, not an expanded Layout Module, to preserve ADR-3660's narrow placement responsibility while deepening artifact content locality. First slice: relocate only the layout-reached conversion family (`convertClaudeCommandTo*Skill`, converted command-file emitters, `buildKimiAgentArtifacts`) plus the minimal helper closure they need; do not leave helper dependencies in `bin/install.js` because that would preserve the same shallow seam under a new filename. Installer integration decision: `bin/install.js` imports the conversion Module at top level and re-exports the moved names for compatibility; the conversion Module must not import `bin/install.js` or Runtime Artifact Layout, so the dependency direction becomes installer/layout Adapters -> conversion Module, never conversion -> installer. First-slice Interface decision: export the existing compatibility names only; do not introduce a grouped `convertRuntimeArtifact` Interface until after relocation proves byte-for-byte behavior. + +### Command Roster Module +Tiny read-only helper Module owning discovery of canonical `commands/gsd/*.md` command stems for artifact conversion and runtime projection. It is a sibling dependency of Runtime Artifact Conversion Module, not part of conversion itself: conversion consumes a roster to safely rewrite `gsd:` / `/gsd-` references, while roster discovery owns filesystem/catalog knowledge. First slice: extract existing `readGsdCommandNames` behavior behind this Module instead of moving it into Runtime Artifact Conversion Module or keeping it as installer-owned state. ### Runtime Install Policy Module Projects a pure, typed install plan for a given runtime by composing artifact placements (Runtime Artifact Layout Module), command text (Shell Command Projection Module), and per-runtime config intentions — with no filesystem IO or format-specific serialization. Runtime-specific adapters consume the plan and execute concrete file mutations and config rendering. See ADR-58. +### Capability [Planned] +A bundle delivering one optional GSD feature, toggled as a unit at install or after install. Owns its skills, agents, hooks, federated config-key schema (keys + defaults + validation), and loop extension-point registrations, plus a `requires` list of other Capabilities, plus an optional `activationKey` (a dotted config key, e.g. `graphify.enabled`) naming the config toggle that gates the whole capability — consumed by the Capability State Resolver's per-capability `active` (absent → no config gate; see Capability State Resolver tri-state deepening). Declared co-located in the Capability's own folder and compiled into a generated central Capability Registry at build time. The five-step loop (Discuss → Plan → Execute → Verify → Ship) and shared-infrastructure skills (phase, config, help, update, surface, progress) are the privileged host, not Capabilities, in v1 — but host extension points are data so a loop step can become a Capability under a future uniform kernel. Supersedes the implicit feature-scattering across clusters, install-profiles, and config-schema. Generalizes the Skill Surface Budget Module and Runtime Install Policy Module. + +### Loop Host Contract +Generated description of what the five-step loop (Discuss → Plan → Execute → Verify → Ship) exposes as extension points: per-step loop points, agent roles, and core artifacts. Sourced from structured `` HTML-comment markers embedded near the top of each of the five step workflow files (`discuss-phase.md`, `plan-phase.md`, `execute-phase.md`, `verify-work.md`, `ship.md`). Generated by `scripts/gen-loop-host-contract.cjs` → `gsd-core/bin/lib/loop-host-contract.cjs` (ADR-894 §3 phase 3a-impl-2). Covers exactly the 12 canonical points (discuss:pre/post, plan:pre/post, execute:pre/wave:pre/wave:post/post, verify:pre/post, ship:pre/post). The generator enforces a drift guard: every declared non-orchestrator agent role must correspond to an actual agent reference in the workflow file. Consumed by `gen-capability-registry.cjs` (replaces the former inline `LOOP_HOST_CONTRACT` constant). Run `node scripts/gen-loop-host-contract.cjs --write` after editing a workflow step marker. + +### Capability Registry +Generated central manifest projecting all co-located Capability declarations into one validated artifact for runtime resolution and for the install, surface, config, and loop-extension adapters. Mirrors the research-profiles / package-identity generation pattern (co-located source → generated central file). Generated by `scripts/gen-capability-registry.cjs` → `gsd-core/bin/lib/capability-registry.cjs` (ADR-894 §5 phase 3a-impl). Role-partitioned indexes: `bySkill`, `byAgent`, `byLoopPoint` (hook ordering materialized), `configKeys` (ownership map: key→capId), `configSchema` (full per-key schema: key→{ owner, type, default, description }), `runtimes`, `requiresClosure(id)`. Each feature capability's entry in `capabilities` now includes the optional `activationKey` field (the dotted config key that gates the whole capability, e.g. `"graphify.enabled"`; absent means no config gate). ADR-857 phase 3b adds `configSchema` with validated type/default/description per key, sourced from each capability's `.config` slice. ADR-857 phase 4a adds two derived views: `capabilityClusters` (`{ : [] }` — each cap's skills array, sorted, derived from the capability's `skills` declaration; consistency-gated against the hand-authored `CLUSTERS`) and `profileMembership` (`{ : { tier, profiles: [...] } }` — the tier-derived index: suffix of `PROFILE_RANK` starting at the capability's tier). Both views cover the same capability set: only capabilities that own skills (non-empty `skills` array). The generator enforces a HARD gate (throws) if a capId matching a `CLUSTERS` key has a mismatched skill set, and emits SOFT `⚠ pending-reconciliation` warnings to stderr (never to the file) for skills not yet in the hand-authored profile at the capability's tier. `install` and `surface` are UNTOUCHED (still read hand-authored constants; derived views are emitted and tested but unconsumed until cutover). Validated against the Loop Host Contract (12 points; generated by `gen-loop-host-contract.cjs` from workflow markers, phase 3a-impl-2). Run `node scripts/gen-capability-registry.cjs --write` after editing any `capabilities//capability.json`. + +### Federated Config +ADR-857 phase 3b seam that merges capability-declared config slices into the `loadConfig` return value. Implemented in `src/federated-config.cts` → `gsd-core/bin/lib/federated-config.cjs`. Exports `mergeFederatedConfig({ configSchema, isCentralKey, userConfig }) → { values, validKeys, warnings }`. Rules: central-schema keys are skipped with a `pending-migration` warning; malformed slices are skipped with a warning (never throws); valid federated keys (absent from the central schema) resolve to the user-supplied value (if type-matches) or the slice default. Object writes are guarded against prototype pollution with inline literal `__proto__`/`constructor`/`prototype` key checks. ADR-857 phase 6 made the channel live for migrated Capability keys: `config-schema.cjs` exposes `isCentralConfigKey()` for central ownership and `isValidConfigKey()` accepts central + runtime + dynamic + Capability-owned registry keys. `loadConfig` exposes `_setFederatedRegistryForTests`/`_resetFederatedRegistryForTests` seams for injecting a synthetic registry in tests. + +### Loop Extension Point +A named, stable site on a host loop step (per-step `pre`/`post` plus per-wave in Execute; 12 total) where Capabilities register hooks. Three hook kinds: `step` (runs as its own sequenced unit), `contribution` (injects into the core step's prompt/context), and `gate` (checks and optionally blocks via a declared `blocking` flag). Each hook declares the artifacts it produces and consumes; hook order is derived by topological sort of that produces/consumes graph (capability-id tiebreak), which also defines data flow — file-artifact based, surviving `/clear` and fresh executor contexts. Hooks are surfaced by runtime resolution with concrete projection: the workflow calls a query that resolves the active hooks and returns fully-rendered, ordered markdown for the executor. Failure is default-resilient — a non-gate hook that errors is skipped with a warning; a hook may opt into `onError: halt`. Part of the Capability system. ADR-857 phase 3c ships the registry-consuming query layer: `gsd-core/bin/lib/loop-resolver.cjs` exposes `resolveLoopHooks({ point, registry, config })` (pure, no I/O), `renderLoopHooks(resolved)` (pure markdown renderer), and `cmdLoopRenderHooks(cwd, point, raw, opts)` (I/O entry point); activated via `gsd-tools loop render-hooks ` which emits `{ point, activeHooks[], rendered }`. Activation is driven by `when` (dotted config key resolved against `loadConfig`), with inline literal `__proto__`/`constructor`/`prototype` prototype-pollution guard. The first phase-6 cutovers wiring workflows to this query have landed — ui-phase at `plan:pre` and ui-review at `verify:post` (in `plan-phase.md`/`autonomous.md`); further per-feature cutovers are ongoing. + +### Capability State Resolver +ADR-857 phase 4b/6 unified resolver that composes the three toggle systems (install profile, runtime surface, config activation) into one per-capability view consumed by workflow hook rendering. Source of truth: `gsd-core/bin/lib/capability-state.cjs` (generated from `src/capability-state.cts`). Interface: `resolveCapabilityState({ registry, installedSkills, surfacedSkills, config, cwd? }) → { capabilities: CapabilityStateEntry[] }` (pure, no I/O); `resolveCapabilityRuntimeState(cwd, runtimeConfigDir)` (I/O resolver shared by workflow dispatch and diagnostics — returns `{ runtimeConfigDir, warnings, capabilities }` only; `registry` and `config` are NOT returned — callers that need them import `capability-registry.cjs` and call `loadConfig(cwd)` directly); `cmdCapabilityState(cwd, runtimeConfigDir, raw, opts)` (I/O output entry point); `isCapabilityActive(capId, cwd): boolean` (convenience predicate — calls `resolveCapabilityRuntimeState`, finds the entry, returns `entry.active`; `false` when capability not found). CLI surface: `gsd-tools capability state [--config-dir ]` — emits `{ runtimeConfigDir, capabilities[] }`. Per-capability output: `{ id, tier, skills[], installed, surfaced, enabled, active, hooks[] }` where `installed` = every owned skill ∈ installedSkills (or `installedSkills==='*'`; vacuously true for empty-skills caps), `surfaced` = every owned skill ∈ surfacedSkills (vacuously true for empty-skills caps), `enabled = installed && surfaced` (unchanged — install+surface toggle only), `active = enabled && configActivation` (tri-state deepening: configActivation resolves the capability's `activationKey` via `_resolveActivationValue`; absent `activationKey` → configActivation=true, so `active===enabled` for ungated capabilities), and `hooks` = `[{ point, kind: 'step'|'gate'|'contribution', when, configured, active }]` derived from the cap's `steps`, `gates`, `contributions` arrays (`configured` resolves `when`; `active = enabled && configured`). Capabilities sorted by `id` for determinism. Defensive: malformed registry → `{ capabilities: [] }`, never throws; inline literal `__proto__`/`constructor`/`prototype` prototype-pollution guard on capability id keys. + +### Capability State Writer +The write-side mirror of the Capability State Resolver. Takes a desired capability state — per-capability `enabled` plus per-hook `gates` — and projects it onto the substrates: `enabled` drives the runtime surface (`.gsd-surface.json`) as the capability on/off switch; `gates` drive the federated config keys (`config.json` `workflow.*`) for hook-level granularity; the install profile (`.gsd-profile`) is a read-only floor it never writes. Writes the surface once and config once (atomic per substrate), then re-runs the resolver and reports divergence (assert-and-report) — so 'off means off' holds as a write-time invariant rather than by caller discipline. Source of truth: `src/capability-writer.cts`; the surface and config writers become its internal adapters. + +### Capability Command Family [Planned — mechanism built, unconsumed] +ADR-959 (phase 4d) — a CLI command family (a top-level `gsd-tools` command and its subcommands) owned by a Capability via a new optional `commands: [{ family, module, router }]` field on the `feature` role. The Capability declares the `family` name, a first-party in-tree `module` (under `gsd-core/bin/lib/`), and the exported `router` — a standard `route*Command({ args, cwd, raw, error })` function identical in shape to the 12 existing host routers (so it routes through the stateless CommandRoutingHub via `routeCjsCommandFamily`, owning its own subcommand list and arg parsing). The registry materializes a `commandFamilies` index (`family → { capId, module, router }`); the formerly-dead `_dispatchNonFamily` shim is replaced by a real `dispatchCapabilityCommand` (exported from `gsd-core/bin/gsd-tools.cjs`) consulted in `runCommand`'s **`default` case** — an unmigrated command hits its hardcoded `case`; a migrated command's `case` is removed so it reaches `default` → registry → router, making collision structurally impossible. The registry *discovers* a router (it does not rebuild a handler table). First-party only; third-party command loading deferred. **Mechanism built (4d-impl-1):** `commands` schema + validator + single-family-ownership cross-check in `gen-capability-registry.cjs`; `commandFamilies` index emitted in the generated `capability-registry.cjs` (currently `{}` — no capability declares commands yet); `dispatchCapabilityCommand` wired into `runCommand`'s `default` case (behavior-preserving today). **Pilot complete (4d-impl-2):** `graphify` cut over as the first real capability command family — `capabilities/graphify/capability.json` bundles the command (`family: graphify`, `module: graphify-command-router.cjs`, `router: routeGraphifyCommand`), skill (`graphify`), config gate (`graphify.enabled`), and `tier: full`; the `case 'graphify':` arm removed from `gsd-tools.cjs`; dispatch flows `default → dispatchCapabilityCommand → commandFamilies.graphify → graphify-command-router.cjs → routeGraphifyCommand`; behavior proven equivalent (all subcommands: build, query, status, diff, build snapshot, unknown subcommand error, usage error, disabled gate). Template for phase-6 per-feature cutovers. **Audit cutover (4d-impl-3):** `audit-uat` and `audit-open` cut over as the second capability command family pair — `capabilities/audit/capability.json` declares two commands (`family: audit-uat`, `module: audit-command-router.cjs`, `router: routeAuditUat`) and (`family: audit-open`, `module: audit-command-router.cjs`, `router: routeAuditOpen`); the `case 'audit-uat':` and `case 'audit-open':` arms removed from `gsd-tools.cjs`; `commandFamilies` now holds `audit-uat`, `audit-open`, and `graphify`; dispatch flows `default → dispatchCapabilityCommand → commandFamilies["audit-uat"|"audit-open"] → audit-command-router.cjs → routeAuditUat|routeAuditOpen`; behavior equivalence proven by existing regression tests (bug-2659, bug-2911, uat.test.cjs) plus new cutover tests. Confirms hyphenated family names pass registry validator (no format restriction beyond non-empty + non-reserved). **Intel cutover (4d-impl-4, last first-party cutover):** `intel` cut over — `capabilities/intel/capability.json` declares the command (`family: intel`, `module: intel-command-router.cjs`, `router: routeIntelCommand`) and the existing config gate (`intel.enabled`, default false); the `case 'intel':` arm removed from `gsd-tools.cjs`; `commandFamilies` now holds `intel`, `audit-uat`, `audit-open`, and `graphify`; dispatch flows `default → dispatchCapabilityCommand → commandFamilies.intel → intel-command-router.cjs → routeIntelCommand`; all 9 subcommands (query, status, update, diff, snapshot, patch-meta, validate, extract-exports, api-surface) and both usage-error paths preserved; non-raw `timeAgo` transform on `status.files[*].updated_at` preserved exactly. `intel.enabled` is Capability-owned config after ADR-857 phase 6. Completes the initial 4d capability command cutover batch. + +### Runtime Capability [Planned] +A `role: runtime` variant of a Capability (a Capability carries `role: feature | runtime`) that projects GSD's produced artifacts (skills/agents/hooks/commands) onto one host CLI's conventions — config-surface format, artifact-layout kinds, command template, hooks manifest, sandbox tier. It is a declarative descriptor over a fixed first-party primitive vocabulary (not a code adapter); install composes active Feature Capabilities × the chosen Runtime Capability at the InstallPlan seam (ADR-0058). First-party runtimes are authored through the same descriptor a third party would write (dogfooding the interface); tier-1 (Claude Code, Codex, Antigravity) is fully tested, the other existing runtimes ship lower-tier, none dropped. Third-party runtime loading is deferred to a purely additive external loader + trust gate. Note: "third-party" here is the authorship/distribution axis (who wrote/ships it), distinct from the integration-shape axis (in-host vs Connected Capability). + +### Connected Capability [Planned — deferred design] +A Capability whose integration shape brings its own external process, service, or persistent state — for example an MCP server plus a backing database — rather than running entirely within the host's process and trust boundary as declarative artifacts and in-tree first-party code referenced by closed name. Orthogonal to authorship: a Connected Capability may be first-party (e.g. MemPalace, issue #956) or third-party. Contrast with a plain Capability (declarative artifacts + in-host-trust code) and a Runtime Capability (closed-vocabulary projection descriptor), both of which run within host trust. The Connected Capability contract — external-process/MCP-server/backend-provider contributions plus a trust and load gate — is deferred design (ADR-857 §7); vehicle issue #956. It is NOT expressed by the current capability schema. + +`RULESET.CAPABILITY.off-means-off=the host derives shared outputs from the ACTIVE hook set (via loop.render-hooks); a hook may ADD a labeled block or be COUNTED into a host-computed aggregate (e.g. a score denominator), but NEVER mutates host source — so a disabled capability yields the base output by construction, not by authoring discipline. Ratify in ADR-894; proven by spike #1018.` + +`RULESET.CAPABILITY.cutover-self-gating=a phase-6 per-feature cutover moves the host's phase-context detection + mode/flag logic INTO the skill (self-gating, per ADR-894); the loop hook is intentionally COARSE — "invoke skill X at point Y when config Z" — and carries no detection/mode. WORKED EXAMPLE: plan-phase.md §5.6 UI gate (frontend-detection via ui-safety-gate.cjs + --auto/manual branch + --skip-ui bypass) must move into gsd-ui-phase before its plan:pre hook can replace the inline call without behavior loss. Spike #1018 finding.` + +`RULESET.CAPABILITY.step-additive-gate-blocks=a `step` hook is purely additive (invoke skill + produce artifacts, NEVER halts the host); host-blocking preconditions are `gate`s (blocking:true, onError:halt); runtime/mode context (auto/chain vs manual) self-gates IN THE SKILL, not via `when` (config-only). §5.6 = plan:pre step (ui-phase; skill self-gates on frontend+pipeline, auto-fires only in pipelines) + a NEW plan:pre gate (frontend-and-no-UI-SPEC → halt, when:workflow.ui_safety_gate); the loop.render-hooks dispatch template handles steps AND gates. Resolves #1022.` + +`RULESET.CAPABILITY.precedence-engine-single-owner=the config-key four-level precedence walk (loadConfig result → workstream config.json → root config.json → registry.configSchema default → absent) is owned solely by src/capability-activation.cts: raw-value primitive resolveConfigKey(dotKey, {config,cwd,registry}) and boolean wrapper _resolveActivationValue(dotKey,config,cwd,registry); loop-resolver.cts imports the engine (no duplicate); resolveConfigValues in loop-resolver.cts delegates to resolveConfigKey; resolveCapabilityRuntimeState does NOT return registry/config — callers import capability-registry.cjs and call loadConfig(cwd) directly.` + +### Teams Status Module +Pure read-only detector for claude-code's experimental agent-teams feature (issue #1355). Stops gsd-core hanging silently when run under `CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS`. Source of truth: `src/teams-status.cts` → `gsd-core/bin/lib/teams-status.cjs`. Exports: `resolveTeamsStatus({ runtime, env }) → TeamsStatus` (pure, env injected, no process.env/disk inside — hermetic for tests) and `cmdTeamsStatus(cwd, { active? })` (I/O entry point; reuses `resolveRuntime` from `runtime-slash.cjs` for canonical `GSD_RUNTIME` → `config.runtime` → `'claude'` precedence). `TeamsStatus` shape: `{ active: boolean, runtime: string, env_present: boolean, source: 'on: env' | 'off: flag absent' | 'off: non-claude' }`. `active` is true only when the flag is strictly truthy (`"1"` or `"true"`, case-insensitive) AND the runtime is `"claude"`. CLI surface: `gsd-tools query teams-status [--active]` (default: JSON; `--active`: exit 0/1 boolean). Used only by a non-fatal `--active` check warning in `plan-phase.md` init block — does NOT block execution, does NOT activate capabilities, does NOT change behavior on any non-claude runtime. + +### Wing +A MemPalace organizational unit corresponding to one project or repository. GSD derives the wing name from `project_code` or the project directory when `mempalace.wing` is unset. A wing contains Rooms. Cross-project Tunnels connect rooms across wings. MemPalace vocabulary — see Connected Capability, MemPalace memory capability (issue #956). + +### Room +A named bucket inside a Wing that groups drawers by semantic kind. GSD maps its phase artifacts to five fixed rooms: `decisions` (CONTEXT.md), `planning` (PLAN.md), `milestones` (SUMMARY.md/UAT.md excerpts), `problems` (confirmed bug→fix pairs), and `learnings` (extract-learnings output). MemPalace vocabulary — see Wing. + +### Drawer +A verbatim-content unit stored inside a Room. GSD files phase artifacts as drawers using `mempalace_add_drawer`; duplicate-check via `mempalace_check_duplicate` makes capture idempotent. GSD stores verbatim text (not AAAK summaries) to preserve recall fidelity. MemPalace vocabulary — see Room. + +### Tunnel +A cross-Wing knowledge connection created by `mempalace_create_tunnel`. GSD proposes tunnels at `ship:post` when `mempalace.cross_project_tunnels: true`, linking rooms in the current wing to semantically related rooms in other project wings. MemPalace vocabulary — see Wing. + +### Diary +A per-agent narrative entry written by `mempalace_diary_write`. GSD's `gsd-mempalace-curator` writes a diary entry at `ship:post` when `mempalace.diary_journal: true`, recording a session summary scoped to the project and agent role. MemPalace vocabulary — see Connected Capability. + +### memory_mode +The `mempalace.memory_mode` config key controlling how tightly MemPalace couples to GSD's native memory. Three declared values: `augment` (default — **implemented**; palace is an additional write-mostly recall layer; lowest coupling), `kg_backend` (**declared; routing seam not yet implemented** — intended to route graphify KG queries through MemPalace's temporal graph; selecting today behaves as `augment`), `replace` (**declared; not yet functional** — intended to make the palace the durable store; selecting today behaves as `augment`). Only `augment` has effect in the current release; `kg_backend` and `replace` are forward-declared for a future release. Read at hook-render time; switching is a config change, not a reinstall. See MemPalace Settings in `docs/CONFIGURATION.md`. + +### Runtime Hooks Surface Module +Standalone hook-surface writer module extracted from `bin/install.js` as ADR-857 phase 5f-1 (behavior-preserving relocation, no logic change). Owns: Cline rules-body/agents-md/pre-tool-use hook generation (`buildClineRulesBody`, `buildClineAgentsMdBody`, `buildClinePreToolUseHook`, `mergeGsdAgentsMd`, `writeClineArtifacts`); Cursor `hooks.json` lifecycle (`buildCursorHookEntry`, `isManagedCursorHookEntry`, `reconcileCursorHooksJson`, `writeCursorHooksJson`, `removeCursorHooksJson`); Copilot session-hook config (`buildCopilotHookConfig`, `writeCopilotHookConfig`); Codex hook-block and event management (`buildCodexHookBlock`, `rewriteLegacyCodexHookBlock`, `reconcileCodexHooksJsonEvent`, `reconcileCodexHooksJsonSessionStart`, `ensureCodexHooksJsonSessionStart`, `ensureCodexHooksJsonEvent`, `removeCodexHooksJsonEvent`, `removeCodexHooksJsonSessionStart`, `buildCodexHookWindowsShimIR`); and shared hook command helpers (`buildHookCommand`, `rewriteLegacyManagedNodeHookCommands`, `normalizeNodePath`, `resolveNodeRunner`). `bin/install.js` delegates to this module via thin wrappers and re-exports its functions unchanged so existing tests require no modification. Source: `src/runtime-hooks-surface.cts`. Built output: `gsd-core/bin/lib/runtime-hooks-surface.cjs`. + ### Runtime Config Adapter Registry -Module owning the explicit per-runtime config-mutation dispatch table for the installer. `resolveRuntimeConfigIntent(runtime)` projects a typed config intent — `installSurface` (`settings-json` | `codex-toml` | `copilot-instructions` | `cline-rules` | `cursor-hooks-json` | `profile-marker-only`), `writesSharedSettings` (the `finishInstall` shared-settings write gate), and `finishPermissionWriter` (`opencode` | `kilo` | none) — that `bin/install.js` dispatches on instead of inline `runtime === '...'` branching. Owns adapter selection only: it performs no filesystem IO and does not execute config mutations (the install/finishInstall handlers and the per-runtime writers do that). Unknown runtimes fail loudly with a `TypeError`, guarded by an `Object.hasOwn` own-property check so prototype-chain keys (`__proto__`, `constructor`) also throw. Realizes the adapter-selection half of the Runtime Install Policy Module boundary. Source: `gsd-core/bin/lib/runtime-config-adapter-registry.cjs`. See ADR-58, #60. +Module owning the explicit per-runtime config-mutation dispatch table for the installer. `resolveRuntimeConfigIntent(runtime)` projects a typed config intent — `installSurface` (`settings-json` | `codex-toml` | `copilot-instructions` | `cline-rules` | `cursor-hooks-json` | `profile-marker-only`), `writesSharedSettings` (the `finishInstall` shared-settings write gate), and `finishPermissionWriter` (`opencode` | `kilo` | none) — that `bin/install.js` dispatches on instead of inline `runtime === '...'` branching. Owns adapter selection only: it performs no filesystem IO and does not execute config mutations (the install/finishInstall handlers and the per-runtime writers do that). Unknown runtimes fail loudly with a `TypeError`, guarded by an `Object.hasOwn` own-property check so prototype-chain keys (`__proto__`, `constructor`) also throw. Also exports `resolveInstallPlan(runtime)` — the ADR-58 `InstallPlan` capstone — which collects the install-level descriptor axes (`installSurface`, `writesSharedSettings`, `finishPermissionWriter`, `hookEvents`, `extendedHookEvents`, `hooksSurface`, `sandboxTier`) into one typed `InstallPlan` value consumed by `install()` and `finishInstall()` in `bin/install.js`. `sandboxTier` (`none` | `codex-agent-sandbox`) gates per-agent `sandbox_mode` emission in the codex TOML path and fails loud on a missing/invalid value (#1151). The spatial axes (`configHome`, `artifactLayout`, `commandStyle`) remain behind their self-resolving adapter modules and are not part of the plan; they are the execution adapters. Realizes both the adapter-selection and plan-collection halves of the Runtime Install Policy Module boundary. Source: `gsd-core/bin/lib/runtime-config-adapter-registry.cjs`. See ADR-58, #60. ### Claude Code Plugin Manifest Module Module owning the projection of gsd-core's artifact surfaces (`commands`, `agents`, hooks) onto the Claude Code plugin contract (`.claude-plugin/plugin.json` + `hooks/hooks.json`) — the plugin-contract sibling of the Runtime Artifact Layout Module (which projects the same surfaces onto filesystem placements). Defined mapping: `name`=`binName` (drives the `/gsd-core:` command namespace), `repository`/`homepage`=`repoUrl` (Package Identity Module), `version`/`description`/`license` from `package.json` (`version` is required for `claude plugin validate --strict`), `commands`=`./commands/gsd/`, agents via Claude Code's default `agents/` discovery (the explicit string form is schema-rejected), `hooks`=`./hooks/hooks.json`. The hook projection carries ONLY the always-on subset of the Installer Module's Claude `settings.json` wiring (check-update, context-monitor, prompt-guard, read-guard, worktree-path-guard, read-injection-scanner) via `${CLAUDE_PLUGIN_ROOT}`; config-gated opt-in hooks are excluded because a static manifest cannot honor per-project config gates, and plugin-shipped agents cannot carry hook frontmatter (so all plugin-path hook wiring lives in hooks.json). `hooks.json` covers all seven Claude Code lifecycle events: SessionStart, PreToolUse, PostToolUse, SubagentStop, Stop, PreCompact (all wired to context-monitor for context-headroom awareness), and FileChanged (matcher: `config.json` → config-reload, injects `additionalContext` when `.planning/config.json` changes mid-session). Additive — the file-copy path (Runtime Artifact Layout / Install Policy / Installer Modules) is unchanged. Conformance is validated by `claude plugin validate --strict` plus the in-repo drift-guard `tests/issue-766-plugin-manifest.test.cjs`. _Avoid_: "the plugin API", "the plugin file" (when you mean the seam). See ADR-766 and Runtime Artifact Layout Module. @@ -131,7 +226,10 @@ Module owning the projection of gsd-core's artifact surfaces (`commands`, `agent The repo-root `gemini-extension.json` + `GEMINI.md` pair that projects gsd-core onto the Gemini CLI extension contract, enabling one-step lifecycle management via `gemini extensions install ` / `update` / `remove` (and `gemini extensions link ` for dev). The Gemini-CLI sibling of the Claude Code Plugin Manifest Module — same additive idea, different runtime package format. Defined mapping: `name`=`binName` (`gsd-core`; lowercase-dashes per Gemini's extension naming rule), `version` tracks `package.json` (Gemini's `gemini extensions update` keys off the manifest `version` field), `description` (required by the manifest schema), `contextFileName`=`GEMINI.md` (the extension's context payload, loaded into every Gemini session). Intentionally minimal: no `mcpServers` (gsd-core ships no MCP server). Slash-command / agent / hook projection into the extension (which would require committing the Gemini-format TOML/agent conversions the Installer Module produces at `--gemini` install time) is deferred — the manual `npx gsd-core --gemini` path remains the way to install the `/gsd:*` commands, and is unchanged (additive, no breaking change). Conformance is guarded by the in-repo drift test `tests/issue-775-gemini-extension.test.cjs` (manifest validity, `version`↔`package.json` parity, `contextFileName` existence, `files[]` publication). _Avoid_: "the Gemini plugin" (Gemini calls them extensions, not plugins). See #775, ADR-766, Claude Code Plugin Manifest Module, and Runtime Artifact Layout Module. ### Knowledge Graph Module -Module owning the graphify integration: config gate (`isGraphifyEnabled`), disabled response (`disabledResponse`), subprocess helper (`execGraphify`, typed `GRAPHIFY_REASON` enum), presence detection (`checkGraphifyInstalled`), version checking (`checkGraphifyVersion`), query surface (`graphifyQuery` — BFS seed-expand + budget trim), status surface (`graphifyStatus` — node/edge counts, mtime staleness, commit-staleness tri-state via `built_at_commit`/`commits_behind`/`commit_stale`), diff surface (`graphifyDiff` — added/removed/changed nodes+edges), build pre-flight (`graphifyBuild`), snapshot management (`writeSnapshot`). Reads `.planning/config.json:graphify.enabled` as config gate; writes to `.planning/graphs/`. Auto-update hook (`hooks/gsd-graphify-update.sh`) triggers a detached background rebuild after HEAD-advancing git operations on the default branch when `graphify.auto_update=true`. Status file `.planning/graphs/.last-build-status.json` carries `{ ts, status, exit_code, duration_ms, head_at_build, graphify_version }`. Graph IR uses `nodes[]`, `edges[]` (or `links[]` for graphify ≥0.7 compat), `hyperedges[]`, `built_at_commit`. `commit_stale` is tri-state: `false` (known fresh), `true` (stale), `null` (unknown — no git or pre-v0.7 graph). Source: `gsd-core/bin/lib/graphify.cjs`. Skill: `commands/gsd/graphify.md`. +Module owning the graphify integration: tri-state capability gate (`isCapabilityActive('graphify', cwd)` from capability-state.cjs — requires installed AND surfaced AND config-enabled; replaces the former config-only `isGraphifyEnabled` gate, cutover in #1306), disabled response (`disabledResponse`), subprocess helper (`execGraphify`, typed `GRAPHIFY_REASON` enum), presence detection (`checkGraphifyInstalled`), version checking (`checkGraphifyVersion`), query surface (`graphifyQuery` — BFS seed-expand + budget trim), status surface (`graphifyStatus` — node/edge counts, mtime staleness, commit-staleness tri-state via `built_at_commit`/`commits_behind`/`commit_stale`), diff surface (`graphifyDiff` — added/removed/changed nodes+edges), build pre-flight (`graphifyBuild`), snapshot management (`writeSnapshot`). Config leg reads `.planning/config.json:graphify.enabled`; all three legs (install, surface, config) must be active; writes to `.planning/graphs/`. Auto-update hook (`hooks/gsd-graphify-update.sh`) triggers a detached background rebuild after HEAD-advancing git operations on the default branch when `graphify.auto_update=true`. Status file `.planning/graphs/.last-build-status.json` carries `{ ts, status, exit_code, duration_ms, head_at_build, graphify_version }`. Graph IR uses `nodes[]`, `edges[]` (or `links[]` for graphify ≥0.7 compat), `hyperedges[]`, `built_at_commit`. `commit_stale` is tri-state: `false` (known fresh), `true` (stale), `null` (unknown — no git or pre-v0.7 graph). Source: `gsd-core/bin/lib/graphify.cjs`. Skill: `commands/gsd/graphify.md`. + +### Intel Module +Module owning the code-intelligence store: tri-state capability gate (`isCapabilityActive('intel', cwd)` from capability-state.cjs — honours installed+surfaced+config-enabled; replaces the former config-only `isIntelEnabled` gate, cutover in #1307; intel has `skills:[]` so installed/surfaced are vacuously true and the effective gate is `intel.enabled` in config), disabled response, query surface (`intelQuery` — full-text search across all intel JSON files), status surface (`intelStatus` — per-file freshness, 24-hour staleness threshold), diff surface (`intelDiff` — added/changed/removed files vs last-refresh snapshot), snapshot management (`saveRefreshSnapshot`/`intelSnapshot`), validation (`intelValidate` — existence, JSON validity, _meta.updated_at recency), api-surface render (`intelApiSurface` — generates `.planning/intel/API-SURFACE.md` from `api-map.json`), plus ungated utilities (`intelPatchMeta` — patches `_meta.updated_at` in any JSON file; `intelExtractExports` — extracts CJS/ESM exports from any JS file). Loop hook rendering gates on `state.active` (not `state.enabled`) so the `activationKey` config gate is honoured even without a per-hook `when` guard (Phase 4 tri-state alignment, #1307). Source: `gsd-core/bin/lib/intel.cjs` (generated from `src/intel.cts`). Router: `gsd-core/bin/lib/intel-command-router.cjs`. See Capability Command Family Module (ADR-959 4d-impl-4) and Loop Extension Point. ### Research Module The GSD-RESEARCH capability behind an L2-hybrid seam: code owns cache + provider policy + package legitimacy; MCP owns the actual fetch. Reachable via `gsd-tools query research-plan|research-store|package-legitimacy`. Source: `src/research-{store,provider}.cts` + `src/package-legitimacy.cts` (generated to `gsd-core/bin/lib/*.cjs` per ADR-457). Replaces the prose provider-waterfall duplicated across the researcher agents and the pip-install `slopcheck` bolt-on. @@ -144,6 +242,30 @@ The GSD-RESEARCH capability behind an L2-hybrid seam: code owns cache + provider - `GSD-RESEARCH.CONTEXT-DISCIPLINE=less-context levers: subagent isolation + compact provider output + fetches-to-disk + cache-returns-digest; API clear_tool_uses/memory tool are the conceptual model, not a Claude Code harness knob` - `DEFECT.RESEARCH-PROVIDER-PROSE-DRIFT=provider waterfall duplicated across N researcher agent .md files drifts independently (META.RULE.brief-no-paraphrase); fix-forward=research-provider.cjs single source of truth + generated agents (#657)` +### UAT-Passed Predicate +Runtime-neutral predicate evaluating `*-UAT.md` / `*-VERIFICATION.md` result fields with markdown-aware parsing that ignores false-positive contexts (frontmatter body, fenced code, HTML comments, blockquotes). Returns `passed: true` only when all required checks pass; supports `--require-verification` to demand at least one VERIFICATION.md file alongside UAT results. Output envelope: `{ passed, uat_files[], verification_files[], checks[], blockers[], policy }`. Source: `gsd-core/bin/lib/uat-predicate.cjs` (generated from `src/uat-predicate.cts`). Wired via `phase uat-passed` alias → `phase-command-router` → `cmdPhaseUatPassed`. + +### Probe Core Module +Generic spec-phase probe resolution model — the shared seam underlying spec-completeness probes (ADR-550 Decision 7). Owns the `status × verification` model (`status: resolved | dismissed | unresolved` × a per-probe `verification` tier), structural validation (`validateResolution`, `validateRequirement` — fail-closed: `verification` must be null unless status is `resolved`, and an out-of-enum status, a `dismissed`-without-`reason`, or an `unresolved` carrying a `resolution`/`reason`/tier payload all throw rather than silently miscount), the `analyzeCoverage(items, resolutions?, validators)` merge/rollup/orphan-reject pipeline, the `byVerification` per-tier rollup, and the `runProbeCli` I/O scaffold (parse → validate → analyze → emit, structurally guarding the report shape before write — a malformed report fails closed with stderr + exit 2 instead of stringifying as green). Adapter-agnostic: consumed by the Edge Probe Module today and the Prohibition Probe Module (#644) next. Exports (generic surface): `VALID_STATUS`, `validateResolution`, `validateRequirement`, `analyzeCoverage`, `runProbeCli` — the prohibition adapter exports that also ship from this module (`projectProhibitions`, `PROHIBITION_VALIDATORS`, `validateProhibitionResolution`, `dispositionForProhibition`) are documented under the Prohibition Probe Module's own locked-surface line. Source of truth: `gsd-core/bin/lib/probe-core.cjs` (generated from `src/probe-core.cts`, gitignored per ADR-457). Tests: `tests/probe-core.test.cjs`. See ADR-550 and Edge Probe Module. Under ADR-857 (phase-6 boundary, settled 2026-06-12) this seam is classified **core verification substrate** on the *contract* side: its deterministic validators are the verifier↔predicate contract's CI-testable surface (ADR-550 Decision 5) — core and non-toggleable, never an off-by-default Feature Capability. (The recall-gapped *generator* is the probe adapters that propose predicates, not this resolution engine — see Edge Probe Module and Verification substrate (predicate boundary).) + +### Edge Probe Module +First adapter of the Probe Core Module (ADR-550 Decision 7): the spec-phase edge-completeness probe wired into spec-phase Step 5.5. Owns shape classification (`classifyShape`), the applicable-category relevance filter (`applicableCategories` over the 8-category edge `TAXONOMY`), edge proposal (`proposeEdges`), and the `{explicit, backstop}` verification validators; delegates merge/rollup/CLI to `probe-core`. Fail-closed input contract: an edge requirement with missing/empty `text` and no `shapes` override is rejected (a `{ id }`-only requirement no longer classifies to zero edges and silently drops), while the legitimate `shapes: []` opt-out is preserved. Downstream, the `plan-phase` planner lifts every `covered`/`backstop` edge from the SPEC `## Edge Coverage` section into `must_haves.truths`. Exports (locked surface): `classifyShape`, `applicableCategories`, `proposeEdges`, `analyzeCoverage`, `validateResolution`, `validateRequirement`, plus the constants `TAXONOMY` (the **closed** 8 edge categories), `UNCLASSIFIED_CATEGORY` (the `unclassified` review-manually sentinel for zero-cue prose, #1110 — deliberately **not** a 9th taxonomy entry: it stays out of `TAXONOMY` but is present in `EDGE_VALIDATORS.categories`), `VALID_SHAPES`, `SHAPE_CUES`, and `EDGE_VALIDATORS` (the `{explicit, backstop}` validators bundle injected into `probe-core`'s generic engine; its `categories` = the `TAXONOMY` ids plus `UNCLASSIFIED_CATEGORY`). Source of truth: `gsd-core/bin/lib/edge-probe.cjs` (generated from `src/edge-probe.cts`, gitignored per ADR-457). Tests: `tests/edge-probe.test.cjs`, `tests/edge-probe-spec-phase-contract.test.cjs`, `tests/edge-probe-planner-contract.test.cjs`. See ADR-550 and Probe Core Module. Per ADR-857's phase-6 boundary (2026-06-12) the predicates this module generates are **core verification substrate** (they set the verifier's reach), so it is wired onto the core predicate rail as a core-default module rather than migrated to an off-by-default `capabilities/edge-probe/` Feature Capability. + +### Verification substrate (predicate boundary) +The ADR-857 classification (settled 2026-06-12, prompted by @davesienkowski's boundary analysis on #857) that predicate-generation — the must-NOT-have / edge predicates that set the verifier's reach — is **core**, not an off-by-default Feature Capability. Load-bearing premise: *verifier reach = spec reach* (the verifier can only catch what the spec concretely names). Decomposes into: the **verifier↔predicate contract** (the verifier always expects predicates and grades **exogenously** against them — core, non-toggleable, a stability contract alongside the Loop Extension Point names), and the **generator** (the probe *adapters* that propose predicates — edge-probe's `classifyShape`/`proposeEdges`, the prohibition probe's adversarial LLM-propose — core-default but independently versionable, kept their own module because of a measured recall gap; `probe-core`'s deterministic validators sit on the contract side, not the generator side). Altitude rule distinguishing it from `gate` hooks: a gate runs *against* the spec (hook); predicate-generation defines the spec's *reach* (core). The decision-#6 `produces`/`consumes` artifact flow is the internal rail from generation to the core verifier. See ADR-857 *Verification substrate vs. plug-in tier (the predicate boundary)* and the ADR-550 cross-reference. + +### Probe Family +The set of spec-phase completeness probes that share the Probe Core Module seam (ADR-550 Decision 7): the Edge Probe (Step 5.5, data-shape edges) and the Prohibition Probe (Step 5.6, unwritten *must-NOT* constraints) today, with room for a third nearly-free adapter. A "probe" walks each SPEC requirement, surfaces candidate omissions, and resolves each through the shared `status × verification` model — but each family member owns its own *recall* mechanism: a deterministic closed-taxonomy classifier for edges (shape→category), open-vocabulary adversarial LLM prose for prohibitions (recall is model-driven, not a compute adapter — ADR-550 D7b). What is shared is the resolution/validation/rollup engine and the soft-gate lifecycle; what diverges is how candidates are recalled. See Probe Core Module, Edge Probe Module, Prohibition Probe Module. + +### Verification Tier +The orthogonal `verification` dimension a resolved probe item carries alongside its `status` (ADR-550 D7a — `status: resolved | dismissed | unresolved` is the shared resolution lifecycle; `verification` is the probe-defined enforcement axis). Each probe defines its own tier vocabulary: the edge probe uses `explicit | backstop`; the prohibition probe uses `test | judgment`. For prohibitions the tier names *how* a must-NOT can be enforced — `test` (a negative test can fail-close on it) vs `judgment` (an irreducible values/safety rule only human/LLM judgment can assess). Verify-phase routes on the tier: `test`-tier items must be provably wired and fail closed when unwired (never a silent green); `judgment`-tier items take the mode-dependent soft-gate (ADR-550 D4 — interactive demands human resolution, autonomous records a non-authoritative LLM-judge verdict + an `unverified-prohibition` flag, never a silent pass and never a hard halt). The rollup exposes `coverage.byVerification: { : count }` so verify-phase reads the per-tier denominator without re-scanning. See Probe Core Module, Prohibition Probe Module. + +### Bespoke vs Canon Prohibition +The ownership seam between the prohibition probe and security/compliance tooling (ADR-550 D6). The probe owns **bespoke** product/values prohibitions — the unwritten must-NOTs specific to this feature's intent (e.g. "the streak reminder must not manipulate the user into returning"). When precision classifies an item as a **canon** security/compliance concern (OWASP / GDPR / fairness / prototype-pollution / path-traversal — the codified, cross-project rule sets), the probe does **not** mint a SPEC prohibition: it emits a one-line breadcrumb (*"possible canon-security concern X — owned by `/gsd:secure-phase` / eslint"*) and stops. Canon checks are **referred, not duplicated** — keeping the surfaced list short (#644's ~2–3-item precision goal) and the secure-phase boundary explicit. See Prohibition Probe Module. + +### Prohibition Probe Module +Second adapter of the Probe Core Module (ADR-550 Decision 7): the spec-phase prohibition-completeness probe wired into spec-phase Step 5.6, surfacing the unwritten *must-NOT* constraints (values/safety/ethics) the spec never forbids. Unlike the Edge Probe, recall is **prose-orchestrated, not a compiled engine** (ADR-550 D7b) — a two-stage pass per requirement: Stage 1 an adversarial recall question, Stage 2 a one-pass precision classifier (drop routine engineering, keep genuine prohibitions). The code surface is schema/projection only: `projectProhibitions()` (deterministic SPEC↔`must_haves.prohibitions` projection backing the `DEFECT.GENERATIVE-FIX` parity assertion), the `{test, judgment}` `PROHIBITION_VALIDATORS`, `validateProhibitionResolution`, and `dispositionForProhibition()` (the fail-closed default — an unwired `test`-tier item resolves to `unverified`/flagged, never green). **Deterministic test-tier locate (#1278, ADR-550 D3 addendum):** a resolved `test`-tier prohibition MAY carry an optional flat-scalar `check` descriptor — `check_kind` (`node-test` | `lint-rule`), `check_target`, and `check_rule` (lint-rule only) — that `projectProhibitions` emits into `must_haves.prohibitions` when well-formed, and `descriptorFromProjection()` (the read-back seam in the #1259 enforcement producer, `src/prohibition-enforcement.cts`) reconstructs into a `{kind, target, rule?}` `CheckDescriptor`, so verify-phase locates the wired check with zero LLM/author authoring. **Flat scalars, never a nested `check:{}` object** — so the round-trip rides the *unchanged* shared `parseMustHavesBlock` (the #644 no-parser-rewrite precedent); an absent/partial descriptor falls through to the producer's existing fail-closed locate, and `failFirst` stays caller-attested (machine-proof is #1279). No `proposeProhibitions()` — recall is LLM prose. `plan-phase` lifts every resolved prohibition from the SPEC `## Prohibitions (must-NOT)` section into the `must_haves.prohibitions` sibling block (never `truths`). Exports (locked surface): `projectProhibitions`, `PROHIBITION_VALIDATORS` (the `{test, judgment}` validators bundle injected into `probe-core`'s generic engine), `validateProhibitionResolution`, and `dispositionForProhibition` (the fail-closed disposition) — the prohibition adapter surface, shipped from `probe-core` alongside the generic engine. Source of truth: `gsd-core/bin/lib/probe-core.cjs` (the prohibition exports live in `src/probe-core.cts`, gitignored per ADR-457) + `gsd-core/references/prohibition-probe.md`. Tests: `tests/prohibition-probe.*.test.cjs`. See ADR-550, Probe Core Module, Edge Probe Module, Verification Tier, Bespoke vs Canon Prohibition. + ### MVP Mode Phase-level planning mode that frames work as a vertical slice (UI → API → DB) of one user-visible capability instead of horizontal layers. Resolved at workflow init via the precedence chain: `--mvp` CLI flag → ROADMAP.md `**Mode:** mvp` field → `workflow.mvp_mode` config → false. All-or-nothing per phase (PRD #2826 Q1). Surfaced as `MVP_MODE=true|false` to the planner, executor, verifier, and discovery surfaces (progress, stats, graphify). Canonical parser: `roadmap.cjs` `**Mode:**` field; canonical resolution chain documented in `workflows/plan-phase.md`. Concept index: `references/mvp-concepts.md`. @@ -180,6 +302,9 @@ Stryker injects small code mutations (e.g., flipping a `>` to `>=`, deleting a ` ### ESLint harness The canonical lint infrastructure adopted in ADR 452 (`docs/adr/452-eslint-lint-harness.md`): ESLint flat config (`eslint.config.mjs`) with `typescript-eslint`, `eslint-plugin-n`, `eslint-plugin-no-only-tests`, and a local AST-rule plugin at `scripts/eslint-rules/`. Replaces the homegrown `scripts/lint-*.cjs` regex scanners. The three custom test-rigor rules (`local/no-source-grep`, `local/no-magic-sleep-in-tests`, `local/no-elapsed-assertion`) initially ship at `warn`; they become `error` after the cleanup sweep tracked at issue #453 merges. +### External-job-waiting half-state +A legal deferred state of an Execute step (`external_job_waiting`): the executor has dispatched a long-running async external job and committed an async-job manifest at `.planning/async-jobs/.json` instead of a SUMMARY.md. Distinct from the synchronous "mid-production-commits" half-state and from an illegal partial-plan state. The core loop's step-completion + safe-resume/pause contract treats a non-terminal manifest as legal and reconciles against it (never re-dispatching the plan, which would duplicate the external job); SUMMARY.md is deferred until the job reaches a terminal state and its `expected_artifacts` are verified. The manifest is a versioned stability contract (`docs/reference/planning-artifacts.md`); core *consumes* it while a default-off scheduler-adapter Capability (#1164) *produces* it at `execute:wave:post` — the contract-is-core / producer-is-capability seam mirrors ADR-857's verification-substrate decision. Status enum is closed and scheduler-agnostic: `submitted`, `running`, `completed-unverified`, `failed`, `cancelled`, `timeout`. + --- ## Test rules and lint @@ -189,7 +314,7 @@ The canonical lint infrastructure adopted in ADR 452 (`docs/adr/452-eslint-lint- `RULESET.TESTS.no-source-grep.exemption=// allow-test-rule: with one-line justification; reserved for tests where the file content IS the product surface (STATE.md, config.toml, hooks.json, agent .md). Migration to typed-IR parser tracked in #2974.` `RULESET.TESTS.no-source-grep.tmp-file-traps=reading tmp files written by the SUT in tests still trips lint; round-trip through CLI (e.g. frontmatter get) instead of readFileSync+.includes()` -`RULESET.TESTS.escape-regex=new RegExp("prefix${var}") must escapeRegex(var); core.cjs already exports escapeRegex; phase IDs like 5.1 contain . which is metacharacter` +`RULESET.TESTS.escape-regex=new RegExp("prefix${var}") must escapeRegex(var); phase-id.cjs exports escapeRegex (core.cjs re-export spine retired in epic #1267); phase IDs like 5.1 contain . which is metacharacter` `RULESET.TESTS.no-dead-regex-in-includes=src.includes("foo.*bar") is always false — .* is regex metacharacter not wildcard; use new RegExp(...).test(src) or delete` `RULESET.TESTS.guard-toplevel-readFileSync=module-level const src = readFileSync(...) throws before any test() registers — wrap in try/catch in test() or use lazy load` `RULESET.TESTS.coderabbit-fix-prefer=behavioral tests (call exported fn, capture JSON, assert typed fields) over source-grep` @@ -206,8 +331,11 @@ The canonical lint infrastructure adopted in ADR 452 (`docs/adr/452-eslint-lint- `RULESET.TESTS.delete-bad-tests=pass-always / vacuous-truth / source-grep / elapsed-time / real-race / permanent-allow-test-rule tests are DELETED and replaced with compliant tests in the same PR; not skipped, not commented out, not permanently exempted; replacement must cover the same logical path via typed-surface assertion or clock-seam pattern` `RULESET.TESTS.eslint-harness=ADR 452 (2026-05-28): ESLint flat config + typescript-eslint + eslint-plugin-n + eslint-plugin-no-only-tests + local plugin at scripts/eslint-rules/; replaces scripts/lint-*.cjs regex scanners; three test-rigor rules (local/no-source-grep, local/no-magic-sleep-in-tests, local/no-elapsed-assertion) ship at warn, promoted to error after #453 cleanup sweep merges` +`RULESET.AUDIT.search-source-not-generated=verify an invariant/validation EXISTS by searching the AUTHORED source (src/*.cts OR the scripts/gen-*.cjs generator), never the generated bin/lib/*.cjs (gitignored, ADR-457); gen-time checks live in gen-*.cjs not the .cts it consumes → search BOTH before declaring absent; read generated .cjs only for output drift. Repro: grep src/*.cts for VALID_CONVERTER_NAMES → false "5e ConverterName unenforced"; actually enforced in gen-capability-registry.cjs. cf RULESET.TESTS.no-source-grep` + `RULESET.WORKFLOW_MARKDOWN.FENCES=preserve opening language fence when editing shell snippets in workflow markdown; malformed fence creates fresh CR threads (MD040)` -`RULESET.WORKFLOW_SIZE_BUDGET=workflow-size-budget (#717) measures BYTES not lines; tiers XL<=90000 / LARGE<=54000 / DEFAULT<=38000 bytes, discuss-phase<30000; can fail otherwise-valid review fixes — trim prose or extract LAZILY-loaded content (eager @-imports don't reduce loaded context) before final checks` +`RULESET.WORKFLOW_SIZE_BUDGET=workflow size enforcement (#1074; BYTES not lines per #717; LF-normalized per #683) = per-file baseline (PRIMARY anti-creep: tests/workflow-size-baseline.json pins each file's exact size) + loose tier hard caps (outer red lines, NEVER raised on approach: XL<=98304 / LARGE<=61440 / DEFAULT<=40960) + new-file cap (un-baselined files <32768, the Codex anchor) + discuss-phase<32000; a file that grew fails the baseline guard — fix with `npm run size:baseline`, commit the one-line diff, and justify the growth in the PR (or extract LAZILY-loaded content; eager @-imports don't reduce loaded context); crossing a hard cap means EXTRACT, not bump` +`RULESET.AGENT_SIZE_BUDGET=agent-size-budget (#1074; sibling of WORKFLOW_SIZE_BUDGET; BYTES not lines per #717/#683, rebased from lines in PR 3/3) = per-file baseline (PRIMARY anti-creep: tests/agent-size-baseline.json pins each agents/gsd-*.md exact byte size) + loose tier hard caps (red lines, never raised on approach: XL<=57344 / LARGE<=49152 / DEFAULT<=24576); net-new agents are DEFAULT-tier (no separate new-file cap). One 'npm run size:baseline' regenerates BOTH workflow and agent baselines via the shared scripts/workflow-size.cjs measureMdFiles(dir,predicate) counter. A grown agent fails the baseline guard — regenerate + justify, or extract LAZILY to gsd-core/references/. DISTINCT from DEFECT.AGENT-FILE-SIZE-CAP-BREACH (a separate 45K-CHAR extraction-evidence threshold on gsd-planner via planner-decomposition/reachability tests): that guard proves mode-sections were extracted; this one bounds total agent bytes. Two guards, two units (chars vs bytes), two purposes` `RULESET.WORKFLOW_FILE_NAMES=workflow files use hyphens; XML attributes must match (extract-learnings not extract_learnings); tests should pin exact hyphenated name` `RULESET.WORKFLOW_EXECUTION_CONTEXT=@-ref in commands/gsd/*.md must resolve to an existing file on disk; regression test in tests/bug-3135-capture-backlog-workflow.test.cjs; INVENTORY.md row + INVENTORY-MANIFEST.json families.workflows must stay in sync; "Invoked by" attribution must move when a flag absorbs a micro-skill` `RULESET.WORKFLOW_EXECUTE_END_TO_END=ADR-0002 standard for single-workflow commands is "Execute end-to-end." (no bolded **Follow the X workflow** fragments); flag-dispatch routing uses "execute the X workflow end-to-end." in routing bullets` @@ -220,7 +348,7 @@ The canonical lint infrastructure adopted in ADR 452 (`docs/adr/452-eslint-lint- `RULESET.GEMINI.TEST_SENTINEL=convertClaudeToGeminiAgent regression should assert tools excludes ask_user, body excludes AskUserQuestion/ask_user, and Read still maps to read_file` `RULESET.ADR-HEADER=every docs/adr/NNNN-*.md must open with - **Status:** Accepted|Proposed|Deprecated + - **Date:** YYYY-MM-DD immediately after title` -`RULESET.MANIFEST-CANONICAL-KEY=docs/INVENTORY-MANIFEST.json — only families.workflows is canonical (read by tooling); top-level workflows key is stale, delete if present` +`RULESET.MANIFEST-CANONICAL-KEY=docs/INVENTORY-MANIFEST.json has a single top-level key: families; ALL SIX families.* arrays (agents/commands/workflows/references/cli_modules/hooks) are canonical, consumed by test suites — tests/inventory-manifest-sync.test.cjs reads all six, edit-phase/enh-2380/enh-2430 tests read commands+workflows; the old generated date field and the stale top-level workflows key are both gone; regen via node scripts/gen-inventory-manifest.cjs --write` `RULESET.PR-SCOPE.one-concern-per-pr=split unrelated changes into separate PRs; cherry-pick doc changes to dedicated docs/ branch immediately, then force-push original to remove the commit` @@ -228,8 +356,6 @@ The canonical lint infrastructure adopted in ADR 452 (`docs/adr/452-eslint-lint- `RULESET.CR-THREAD-RESOLVE=after adding // allow-test-rule: to silence lint, resolve existing inline CR threads via graphql resolveReviewThread mutation before merge — open threads mislead future reviewers; pattern: gh api graphql -f query='mutation { resolveReviewThread(input:{threadId:"PRRT_..."}) { thread { isResolved } } }'` -`RULESET.DOC-CONSISTENCY=when heading says (N shipped) and footnote says N-1 top-level references, update both; CR catches every time` - --- ## CodeRabbit + repo-process guards (machine-oriented predicates) @@ -261,7 +387,7 @@ The canonical lint infrastructure adopted in ADR 452 (`docs/adr/452-eslint-lint- `PR.3267.POSTMORTEM.recovery=[issue#3270 created, label approved-enhancement applied, PR reopened, body includes "Closes #3270", label no-changelog applied]` `WORKTREE.SEAM.current=Worktree Safety Policy Module` -`WORKTREE.SEAM.files=[gsd-core/bin/lib/worktree-safety.cjs, gsd-core/bin/lib/core.cjs]` +`WORKTREE.SEAM.files=[gsd-core/bin/lib/worktree-safety.cjs]` `WORKTREE.SEAM.interface=[resolveWorktreeContext, parseWorktreePorcelain, planWorktreePrune, executeWorktreePrunePlan]` `WORKTREE.SEAM.default-prune-policy=metadata_prune_only (non-destructive)` `WORKTREE.SEAM.decision-1=retain non-destructive default; destructive path only as explicit future opt-in scaffold` @@ -280,8 +406,8 @@ The canonical lint infrastructure adopted in ADR 452 (`docs/adr/452-eslint-lint- `WORKTREE.SEAM.caller-rule=verify.cjs must consume inspectWorktreeHealth for W017 classification; no ad-hoc porcelain parsing in callers` `WORKTREE.SEAM.test-anchor-w017=tests/orphan-worktree-detection.test.cjs + tests/worktree-safety-policy.test.cjs` `WORKTREE.SEAM.inventory-snapshot=snapshotWorktreeInventory(repoRoot,{staleAfterMs,nowMs}) is canonical linked-worktree health snapshot for callers` -`PLANNING.PATH.PARITY.sdk-project-scope=.planning/ (never .planning/projects/); mirror planning-workspace.cjs planningDir()` -`PLANNING.PATH.SEAM.sdk=helpers.planningPaths delegates to workspacePlanningPaths + resolveWorkspaceContext; precedence explicit-ws > env-ws > env-project > root` +`PLANNING.PATH.PARITY.project-scope=.planning/ (never .planning/projects/); mirror planning-workspace.cjs planningDir()` +`PLANNING.PATH.SEAM.helpers=helpers.planningPaths delegates to workspacePlanningPaths + resolveWorkspaceContext; precedence explicit-ws > env-ws > env-project > root` `PLANNING.PATH.SEAM.init-handlers=[initExecutePhase, initPlanPhase, initPhaseOp, initMilestoneOp] consume helpers.planningPaths().planning (no direct relPlanningPath join)` `WORKSTREAM.NAME.POLICY.cjs-module=gsd-core/bin/lib/workstream-name-policy.cjs owns toWorkstreamSlug + active-name/path-segment validation` `WORKSTREAM.POINTER.SEAM.cjs-module=gsd-core/bin/lib/active-workstream-store.cjs owns read/write self-heal for .planning/active-workstream` @@ -476,14 +602,14 @@ The canonical lint infrastructure adopted in ADR 452 (`docs/adr/452-eslint-lint- `DEFECT.SUPERSEDED-CONCURRENT-PRS.fix-forward=close superseded PRs via gh api PATCH state=closed; do not comment on self-authored PRs (k101); the link to the merged PR makes supersession discoverable in PR history` `DEFECT.PROMPT-INJECTION-SCAN-COLLISION.symptom=custom XML element name in agent .md file matches scripts/scan-prompt-injection regex; legitimate agent vocabulary trips the security gate` -`DEFECT.PROMPT-INJECTION-SCAN-COLLISION.examples=#3309 added a bare 'human' element (angle-bracket-wrapped) for verify-block harvesting; tests/prompt-injection-scan.test.cjs flags angle-bracket-wrapped names matching system|assistant|human (open or close form)` +`DEFECT.PROMPT-INJECTION-SCAN-COLLISION.examples=#3309 added a bare 'human' element (angle-bracket-wrapped) for verify-block harvesting; tests/prompt-injection-scan.security.test.cjs flags angle-bracket-wrapped names matching system|assistant|human (open or close form)` `DEFECT.PROMPT-INJECTION-SCAN-COLLISION.detect=any new bare tag in agents/*.md` `DEFECT.PROMPT-INJECTION-SCAN-COLLISION.fix-forward=hyphenate the tag (, ) — scanner regex matches bare names only` -`DEFECT.INVENTORY-DRIFT.symptom=new file added under gsd-core/references/ or gsd-core/workflows/ without updating docs/INVENTORY.md count + row AND docs/INVENTORY-MANIFEST.json` -`DEFECT.INVENTORY-DRIFT.examples=#3309 planner-human-verify-mode.md (caught by tests/inventory-counts.test.cjs + tests/inventory-manifest-sync.test.cjs)` -`DEFECT.INVENTORY-DRIFT.detect=tests/inventory-* fails with "References (N shipped) disagrees with filesystem" or "New surfaces not in manifest"` -`DEFECT.INVENTORY-DRIFT.fix-forward=update INVENTORY.md headline count + row entry + footnote count; run node scripts/gen-inventory-manifest.cjs --write to regen INVENTORY-MANIFEST.json; only families.workflows is canonical (top-level workflows key is stale)` +`DEFECT.INVENTORY-DRIFT.symptom=new file added under gsd-core/references/ or gsd-core/workflows/ without updating docs/INVENTORY.md row AND docs/INVENTORY-MANIFEST.json` +`DEFECT.INVENTORY-DRIFT.examples=#3309 planner-human-verify-mode.md (caught by tests/inventory-manifest-sync.test.cjs)` +`DEFECT.INVENTORY-DRIFT.detect=tests/inventory-manifest-sync.test.cjs fails with "New surfaces not in manifest"; tests/inventory-headings-countfree.test.cjs fails if a (N shipped) count is re-added to a heading` +`DEFECT.INVENTORY-DRIFT.fix-forward=update INVENTORY.md row entry; run node scripts/gen-inventory-manifest.cjs --write to regen INVENTORY-MANIFEST.json (all six families.* arrays are canonical — see RULESET.MANIFEST-CANONICAL-KEY)` `DEFECT.AGENT-FILE-SIZE-CAP-BREACH.symptom=adding to agents/gsd-planner.md (or other large agent files) exceeds the 45K char extraction-evidence threshold` `DEFECT.AGENT-FILE-SIZE-CAP-BREACH.state=gsd-planner.md is already 49,121 chars on main (over 45K); test fails on main; net-new content makes it strictly worse` @@ -547,6 +673,12 @@ The canonical lint infrastructure adopted in ADR 452 (`docs/adr/452-eslint-lint- `DEFECT.TEST-SHELL-PIPELINE-NONPORTABLE.detect=test does readFileSync(md).match for a bash fence with literal \n, OR execFileSync('bash',...) gated only on a bash-presence probe; also verifying a new test with a file-scoped run instead of the full suite hides repo-wide static guards` `DEFECT.TEST-SHELL-PIPELINE-NONPORTABLE.fix-forward=match the fence with \r?\n and normalize the captured block to LF; gate pipeline execution on process.platform !== 'win32' && hasBash since the extraction LOGIC is platform-independent and POSIX coverage suffices; run the full suite (or the parity/lint guards) before push when adding a test file` +`DEFECT.WINDOWS-TEST-PORTABILITY.symptom=local gsd-test runs Mac+Linux only (no Windows host); Windows-only test failures (chmod exec-bit not honored for PATH-executing extension-less scripts in Git Bash msys2; / vs \ path-separator in assertions; Git Bash msys2 shell semantics) surface ONLY in CI test (windows-latest,*) / full test (windows-latest,*) lanes, never locally` +`DEFECT.WINDOWS-TEST-PORTABILITY.examples=PR #1084 (chmod 0o755 + bare-command execution failed on windows lane); test files that assert path.join result without normalizing to forward slashes` +`DEFECT.WINDOWS-TEST-PORTABILITY.detect=npm run lint:windows-test-portability (tripwire: flags tests combining chmod exec-bit with sh/bash -c and no platform guard); watch CI windows matrix green before declaring a PR done` +`DEFECT.WINDOWS-TEST-PORTABILITY.fix-forward=gate platform-specific execution with if (process.platform !== 'win32'); normalize path expectations to forward slashes with .replace(/\\/g, '/'); invoke scripts via explicit interpreter (sh ) rather than relying on exec-bit; annotate // windows-portability-ok: when a bypass is intentional` +`DEFECT.WINDOWS-TEST-PORTABILITY.prevention=run lint:ci before opening a PR; treat the CI windows lane as the only true Windows signal — gsd-test (Mac/Linux only) cannot substitute for it` + --- @@ -559,7 +691,7 @@ Invariants: - Platform policy owned at the seam: `shell: process.platform === 'win32'` lives only in run-npm; probeTty returns `null` on Windows. - Normalization policy: platformWriteSync owns full `normalizeMd` for `.md`; CRLF-to-LF + trailing newline for all others; callers must NOT pre-call `normalizeMd`. - `_normalizeMd` is re-implemented inline (not imported from `core.cjs`) to avoid circular dep. -- `atomicWriteFileSync`, `safeReadFile`, `normalizeMd` remain in `core.cjs` exports until Phase 4 (#3468). +- `atomicWriteFileSync`, `safeReadFile`, `normalizeMd` were in `core.cjs` exports (retired in epic #1267); callers now import these from their respective leaf modules directly. Migration plan: Phase 1 (#3465) seam additions complete; Phase 2 (#3466) targets 6 subprocess files; Phase 3 (#3467) targets 15 fs files (215 call sites); Phase 4 (#3468) removes compat exports. @@ -595,6 +727,12 @@ Migration plan: Phase 1 (#3465) seam additions complete; Phase 2 (#3466) targets `DEFECT.WINDOWS-ARGV-OVERFLOW.fix-forward=chunk argv into batches whose total length stays under 28,000 chars (headroom under the 32,767 ceiling); run each chunk sequentially; aggregate exit codes (first non-zero wins). Expose RUN_TESTS_MAX_CMDLINE_CHARS env override so cross-platform regression tests can force chunking with short tmp paths` `DEFECT.WINDOWS-ARGV-OVERFLOW.test-anchor=tests/run-tests-harness.test.cjs "Windows argv-overflow chunking (issue #3597)" — 30 long-named fixture files + RUN_TESTS_MAX_CMDLINE_CHARS=2000 → asserts run-tests: chunk N/M marker in stderr; pattern works on every platform` +`DEFECT.SHARED-ARTIFACT-MUTATION-IN-CONCURRENT-TEST.symptom=a test deletes/rewrites a SHARED REAL build artifact or fixture (e.g. gsd-core/bin/lib/*.cjs, the build tsbuildinfo) that other test files require; node --test runs files concurrently, so innocent concurrent tests intermittently fail with "Cannot find module" / ENOENT while the racy test itself passes (victim-not-culprit, leg-asymmetric red); placing mutable build state inside a copied/shipped tree (gsd-core/bin/) additionally races install-test fs.cpSync copies → copyfile ENOENT` +`DEFECT.SHARED-ARTIFACT-MUTATION-IN-CONCURRENT-TEST.examples=#996/88e30d53 — bug-969 hardening tests fs.unlinkSync'd + restored the real gsd-core/bin/lib/core.cjs and set tsBuildInfoFile inside gsd-core/bin/ → next red across the full-test matrix (macOS/Windows) + ubuntu-24 coverage leg, ~40-50 MODULE_NOT_FOUND/ENOENT per leg; reproduced locally on iteration 1; fixed #1001/#1002` +`DEFECT.SHARED-ARTIFACT-MUTATION-IN-CONCURRENT-TEST.detect=grep tests for fs.unlinkSync|rmSync|writeFileSync|renameSync|cpSync targeting paths resolved from the repo root (join(__dirname,'..',...)) under gsd-core/bin/lib or a shared committed fixture, instead of a mkdtempSync temp dir; any build helper (e.g. ensureBuiltArtifacts) invoked with real-tree paths during the concurrent test phase; any tsBuildInfoFile / build-cache path that lands inside a copied/shipped dir (gsd-core/bin/)` +`DEFECT.SHARED-ARTIFACT-MUTATION-IN-CONCURRENT-TEST.fix-forward=tests mutate ONLY isolated mkdtempSync copies — never delete/rewrite shared real build outputs while node --test runs files concurrently; parameterize build helpers to accept {root,srcDir,outDir,tsBuildInfoPath,tsconfigPath} overrides and point the test at a throwaway temp project (precedent: #1002 ensureBuiltArtifacts(overrides)); keep mutable build state (tsbuildinfo) OUTSIDE copied/shipped trees (repo root, gitignored) + best-effort self-heal of stale bin-local copies; this is the concrete instance of the RULESET.TESTS.delete-bad-tests real-race class` +`DEFECT.SHARED-ARTIFACT-MUTATION-IN-CONCURRENT-TEST.test-anchor=tests/bug-969-test-infra-flake-hardening.test.cjs (hermetic temp-project rewrite); regression gate = 10x concurrent run of that suite + tests/state.test.cjs + tests/install.test.cjs must be clean (reproduces on iter 1 when racy)` + `DEFECT.STACKED-PR-CANNOT-STAND-ALONE.symptom=patch PR was authored against scaffolding (handler files, lint scripts, generated modules) that exists only on an unmerged upstream feature branch; the PR's "base" on GitHub is the feature branch, not main; merging requires the upstream PR to land first` `DEFECT.STACKED-PR-CANNOT-STAND-ALONE.examples=#3639 + #3637 both targeted base=feat/3575-enforcement-hardening (the Phase 6 PR #3577); #3639 modifies SDK-bridge calls in 6 family-router files that on main do NOT have any SDK-bridge call yet; #3637 patches scripts/lint-shared-module-handsync.cjs which does not exist on main at all` `DEFECT.STACKED-PR-CANNOT-STAND-ALONE.detect=gh pr view --json baseRefName shows non-main base; OR git rebase --onto origin/main produces real (not whitespace) conflicts at files the patch claims to modify; OR git cat-file -e origin/main: errors with "does not exist in origin/main"` @@ -669,9 +807,9 @@ Full detail in `~/.claude/skills/gsd-pr-fix-discipline/SKILL.md`. AI agents MUST ### INVENTORY / manifest drift -- **Symptom:** `inventory-counts.test.cjs` fails — `" (N shipped)" disagrees with filesystem (N+1)` +- **Symptom:** `tests/inventory-manifest-sync.test.cjs` fails — `"New surfaces not in manifest"`; or `tests/inventory-headings-countfree.test.cjs` fails if a `(N shipped)` count was re-added to a heading - **Affected this session:** #154, #156, #143, #155, #169 -- **Fix:** Add row to `docs/INVENTORY.md` CLI Modules table + increment headline count + `node scripts/gen-inventory-manifest.cjs --write` +- **Fix:** Add row to `docs/INVENTORY.md` + `node scripts/gen-inventory-manifest.cjs --write` ### Slash command two-tier confusion diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2b0caebf8..2594adee7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -203,7 +203,7 @@ This writes `.changeset/--.md`. Three random words → co Fragments are consolidated into `CHANGELOG.md` at release time by the release workflow. See [`.changeset/README.md`](.changeset/README.md) for the format spec and [#2975](https://github.com/open-gsd/gsd-core/issues/2975) for the rationale. -**CI enforcement:** the `Changeset Required` workflow (`scripts/changeset/lint.cjs`) fails any PR that touches `bin/`, `gsd-core/`, `agents/`, `commands/`, `hooks/`, or `sdk/src/` without a `.changeset/*.md` fragment. +**CI enforcement:** the `Changeset Required` workflow (`scripts/changeset/lint.cjs`) fails any PR that touches `bin/`, `gsd-core/`, `agents/`, `commands/`, `hooks/`, or `sdk/src/` without a `.changeset/*.md` fragment. The gate also **validates the content** of every changed fragment: a fragment whose frontmatter does not parse (e.g. a `pr: 0` placeholder that was never backfilled to the real PR number) fails the gate with `fail_invalid_fragment`, naming the offending file. This stops a malformed fragment from merging to `next` and only detonating later in the release job's CHANGELOG render. **Opt-out:** PRs with no user-facing impact (test refactors, lint config changes, CI tweaks, formatting-only changes) can add the `no-changelog` label. The lint honors it. When unsure whether a change is user-facing, **add the fragment**. @@ -850,8 +850,16 @@ gsd-core/ the canonical example (#2551). New modes for discuss-phase land in workflows/discuss-phase/modes/.md. - Per-file budgets enforced by - tests/workflow-size-budget.test.cjs. + Per-file sizes are pinned by a committed baseline + (tests/workflow-size-baseline.json) plus loose tier + hard caps, both in tests/workflow-size-budget.test.cjs. + If you legitimately grow or shrink a workflow file, + run `npm run size:baseline` to update the snapshot and + justify any growth in your PR (or extract content + lazily). The same guard covers agent files + (agents/gsd-*.md). Full how-to + reference in + docs/TESTING-SUITES.md (Workflow & agent size + budget); see issue #1074. references/ — Reference documentation (.md) templates/ — File templates agents/ — Agent definitions (.md) — CANONICAL SOURCE diff --git a/LICENSE b/LICENSE index 332687536..d47f8ea21 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Lex Christopherson +Copyright (c) 2026 Open GSD Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index c111e412e..db7187704 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ **English** · [Português](README.pt-BR.md) · [简体中文](README.zh-CN.md) · [日本語](README.ja-JP.md) · [한국어](README.ko-KR.md) -**A light-weight meta-prompting, context engineering, and spec-driven development system for Claude Code, OpenCode, Gemini CLI, Kilo, Codex, Copilot, Cursor, Windsurf, and more.** +**A light-weight meta-prompting, context engineering, and spec-driven development system for Claude Code, OpenCode, Gemini CLI, Kimi CLI, Kilo, Codex, Copilot, Cursor, Windsurf, and more.** [![npm version](https://img.shields.io/npm/v/%40opengsd%2Fgsd-core?style=for-the-badge&logo=npm&logoColor=white&color=CB3837)](https://www.npmjs.com/package/@opengsd/gsd-core) [![npm downloads](https://img.shields.io/npm/dm/%40opengsd%2Fgsd-core?style=for-the-badge&logo=npm&logoColor=white&color=CB3837)](https://www.npmjs.com/package/@opengsd/gsd-core) @@ -21,7 +21,7 @@ ## What is GSD Core -GSD Core is a context-engineering and spec-driven development framework that drives AI coding agents (Claude Code, Codex, Gemini CLI, Copilot, Cursor, and more) through a disciplined phase loop. It solves [context rot](docs/explanation/context-engineering.md) — the quality degradation that accumulates as an AI fills its context window — by running all heavy research, planning, and execution work in fresh-context subagents while keeping your main session lean. +GSD Core is a context-engineering and spec-driven development framework that drives AI coding agents (Claude Code, Codex, Gemini CLI, Kimi CLI, Copilot, Cursor, and more) through a disciplined phase loop. It solves [context rot](docs/explanation/context-engineering.md) — the quality degradation that accumulates as an AI fills its context window — by running all heavy research, planning, and execution work in fresh-context subagents while keeping your main session lean. --- @@ -43,7 +43,7 @@ Each milestone repeats the same five-step loop, one phase at a time: npx @opengsd/gsd-core@latest ``` -The installer prompts for your runtime (Claude Code, OpenCode, Gemini CLI, Kilo, Codex, Copilot, Cursor, Windsurf, and more) and whether to install globally or locally. The installer is required for cross-runtime compatibility — do not copy files from `agents/` or `commands/` directly. +The installer prompts for your runtime (Claude Code, OpenCode, Gemini CLI, Kimi CLI, Kilo, Codex, Copilot, Cursor, Windsurf, and more) and whether to install globally or locally. The installer is required for cross-runtime compatibility — do not copy files from `agents/` or `commands/` directly. On another runtime or without Node.js? See [Install on your runtime](docs/how-to/install-on-your-runtime.md). diff --git a/agents/gsd-advisor-researcher.md b/agents/gsd-advisor-researcher.md index 9218b97b8..069e98e38 100644 --- a/agents/gsd-advisor-researcher.md +++ b/agents/gsd-advisor-researcher.md @@ -1,7 +1,7 @@ --- name: gsd-advisor-researcher description: Researches a single gray area decision and returns a structured comparison table with rationale. Spawned by discuss-phase advisor mode. -tools: Read, Bash, Grep, Glob, WebSearch, WebFetch, mcp__context7__* +tools: Read, Bash, Grep, Glob, Skill, WebSearch, WebFetch, mcp__context7__* color: cyan --- diff --git a/agents/gsd-assumptions-analyzer.md b/agents/gsd-assumptions-analyzer.md index 5531fc4a9..91d7cde1f 100644 --- a/agents/gsd-assumptions-analyzer.md +++ b/agents/gsd-assumptions-analyzer.md @@ -1,7 +1,7 @@ --- name: gsd-assumptions-analyzer description: Deeply analyzes codebase for a phase and returns structured assumptions with evidence. Spawned by discuss-phase assumptions mode. -tools: Read, Bash, Grep, Glob +tools: Read, Bash, Grep, Glob, Skill color: cyan --- diff --git a/agents/gsd-code-fixer.md b/agents/gsd-code-fixer.md index 841e8b435..1b89dd79c 100644 --- a/agents/gsd-code-fixer.md +++ b/agents/gsd-code-fixer.md @@ -1,7 +1,7 @@ --- name: gsd-code-fixer description: Applies fixes to code review findings from REVIEW.md. Reads source files, applies intelligent fixes, and commits each fix atomically. Spawned by /gsd:code-review --fix. -tools: Read, Edit, Write, Bash, Grep, Glob +tools: Read, Edit, Write, Bash, Grep, Glob, Skill color: green # hooks: # - before_write @@ -456,7 +456,8 @@ For each finding in sorted order: Use `gsd-tools query commit` with conventional format (message first, then every staged file path): ```bash -gsd-tools query commit \ +_GSD_SHIM_NAME="gsd-tools.cjs"; _GSD_RUNTIME_ROOT="${RUNTIME_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"; GSD_TOOLS="${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}"; if [ -f "$GSD_TOOLS" ]; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif command -v gsd-tools >/dev/null 2>&1; then GSD_TOOLS="$(command -v gsd-tools)"; gsd_run() { "$GSD_TOOLS" "$@"; }; elif [ -f "$HOME/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="$HOME/.claude/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; else echo "ERROR: gsd-tools.cjs not found at $GSD_TOOLS and gsd-tools is not on PATH. Run: npx -y @opengsd/gsd-core@latest --claude --local" >&2; exit 1; fi; if [ -n "${CLAUDE_ENV_FILE:-}" ] && [ -n "${GSD_TOOLS:-}" ]; then printf "export PATH='%s':\"\$PATH\"\n" "${GSD_TOOLS%/*}" >> "$CLAUDE_ENV_FILE" 2>/dev/null || true; fi +gsd_run query commit \ "fix({padded_phase}): {finding_id} {short_description}" \ --files \ {all_modified_files} @@ -468,7 +469,7 @@ Examples: **Multiple files:** List ALL modified files after the message (space-separated): ```bash -gsd-tools query commit "fix(02): CR-01 ..." --files \ +gsd_run query commit "fix(02): CR-01 ..." --files \ src/api/auth.ts src/types/user.ts tests/auth.test.ts ``` diff --git a/agents/gsd-code-reviewer.md b/agents/gsd-code-reviewer.md index 772c22aeb..0ecc81549 100644 --- a/agents/gsd-code-reviewer.md +++ b/agents/gsd-code-reviewer.md @@ -1,7 +1,7 @@ --- name: gsd-code-reviewer description: Reviews source files for bugs, security issues, and code quality problems. Produces structured REVIEW.md with severity-classified findings. Spawned by /gsd:code-review. -tools: Read, Write, Bash, Grep, Glob +tools: Read, Write, Bash, Grep, Glob, Skill color: orange # hooks: # - before_write diff --git a/agents/gsd-codebase-mapper.md b/agents/gsd-codebase-mapper.md index cc1eb1c82..4897c3040 100644 --- a/agents/gsd-codebase-mapper.md +++ b/agents/gsd-codebase-mapper.md @@ -1,7 +1,7 @@ --- name: gsd-codebase-mapper description: Explores codebase and writes structured analysis documents. Spawned by map-codebase with a focus area (tech, arch, quality, concerns). Writes documents directly to reduce orchestrator context load. -tools: Read, Bash, Grep, Glob, Write +tools: Read, Bash, Grep, Glob, Write, Skill color: cyan # hooks: # PostToolUse: diff --git a/agents/gsd-debug-session-manager.md b/agents/gsd-debug-session-manager.md index 6da552d7f..86170ce03 100644 --- a/agents/gsd-debug-session-manager.md +++ b/agents/gsd-debug-session-manager.md @@ -93,7 +93,8 @@ Agent( Resolve the debugger model before spawning: ```bash -debugger_model=$(gsd-tools query resolve-model gsd-debugger 2>/dev/null | jq -r '.model' 2>/dev/null || true) +_GSD_SHIM_NAME="gsd-tools.cjs"; _GSD_RUNTIME_ROOT="${RUNTIME_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"; GSD_TOOLS="${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}"; if [ -f "$GSD_TOOLS" ]; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif command -v gsd-tools >/dev/null 2>&1; then GSD_TOOLS="$(command -v gsd-tools)"; gsd_run() { "$GSD_TOOLS" "$@"; }; elif [ -f "$HOME/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="$HOME/.claude/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; else echo "ERROR: gsd-tools.cjs not found at $GSD_TOOLS and gsd-tools is not on PATH. Run: npx -y @opengsd/gsd-core@latest --claude --local" >&2; exit 1; fi; if [ -n "${CLAUDE_ENV_FILE:-}" ] && [ -n "${GSD_TOOLS:-}" ]; then printf "export PATH='%s':\"\$PATH\"\n" "${GSD_TOOLS%/*}" >> "$CLAUDE_ENV_FILE" 2>/dev/null || true; fi +debugger_model=$(gsd_run query resolve-model gsd-debugger 2>/dev/null | jq -r '.model' 2>/dev/null || true) ``` ## Step 3: Handle Agent Return diff --git a/agents/gsd-debugger.md b/agents/gsd-debugger.md index 9db62ea44..565ea6508 100644 --- a/agents/gsd-debugger.md +++ b/agents/gsd-debugger.md @@ -1,7 +1,7 @@ --- name: gsd-debugger description: Investigates bugs using scientific method, manages debug sessions, handles checkpoints. Spawned by /gsd:debug orchestrator. -tools: Read, Write, Edit, Bash, Grep, Glob, WebSearch +tools: Read, Write, Edit, Bash, Grep, Glob, Skill, WebSearch color: orange # hooks: # PostToolUse: @@ -1150,7 +1150,8 @@ mv .planning/debug/{slug}.md .planning/debug/resolved/ **Check planning config using state load (commit_docs is available from the output):** ```bash -INIT=$(gsd-tools query state.load) +_GSD_SHIM_NAME="gsd-tools.cjs"; _GSD_RUNTIME_ROOT="${RUNTIME_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"; GSD_TOOLS="${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}"; if [ -f "$GSD_TOOLS" ]; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif command -v gsd-tools >/dev/null 2>&1; then GSD_TOOLS="$(command -v gsd-tools)"; gsd_run() { "$GSD_TOOLS" "$@"; }; elif [ -f "$HOME/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="$HOME/.claude/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; else echo "ERROR: gsd-tools.cjs not found at $GSD_TOOLS and gsd-tools is not on PATH. Run: npx -y @opengsd/gsd-core@latest --claude --local" >&2; exit 1; fi; if [ -n "${CLAUDE_ENV_FILE:-}" ] && [ -n "${GSD_TOOLS:-}" ]; then printf "export PATH='%s':\"\$PATH\"\n" "${GSD_TOOLS%/*}" >> "$CLAUDE_ENV_FILE" 2>/dev/null || true; fi +INIT=$(gsd_run query state.load) if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi # commit_docs is in the JSON output ``` @@ -1168,7 +1169,7 @@ Root cause: {root_cause}" Then commit planning docs via CLI (respects `commit_docs` config automatically): ```bash -gsd-tools query commit "docs: resolve debug {slug}" --files .planning/debug/resolved/{slug}.md +gsd_run query commit "docs: resolve debug {slug}" --files .planning/debug/resolved/{slug}.md ``` **Append to knowledge base:** @@ -1199,7 +1200,7 @@ Then append the entry: Commit the knowledge base update alongside the resolved session: ```bash -gsd-tools query commit "docs: update debug knowledge base with {slug}" --files .planning/debug/knowledge-base.md +gsd_run query commit "docs: update debug knowledge base with {slug}" --files .planning/debug/knowledge-base.md ``` Report completion and offer next steps. diff --git a/agents/gsd-doc-writer.md b/agents/gsd-doc-writer.md index 620fe96b1..d594b7bb3 100644 --- a/agents/gsd-doc-writer.md +++ b/agents/gsd-doc-writer.md @@ -1,7 +1,7 @@ --- name: gsd-doc-writer description: Writes and updates project documentation. Spawned with a doc_assignment block specifying doc type, mode (create/update/supplement), and project context. -tools: Read, Bash, Grep, Glob, Write, Edit +tools: Read, Bash, Grep, Glob, Write, Edit, Skill color: purple # hooks: # PostToolUse: diff --git a/agents/gsd-eval-auditor.md b/agents/gsd-eval-auditor.md index d55503831..b0608810c 100644 --- a/agents/gsd-eval-auditor.md +++ b/agents/gsd-eval-auditor.md @@ -1,7 +1,7 @@ --- name: gsd-eval-auditor description: Retroactive audit of an implemented AI phase's evaluation coverage. Checks implementation against the AI-SPEC.md evaluation plan. Scores each eval dimension as COVERED/PARTIAL/MISSING. Produces a scored EVAL-REVIEW.md with findings, gaps, and remediation guidance. Spawned by /gsd:eval-review orchestrator. -tools: Read, Write, Bash, Grep, Glob +tools: Read, Write, Bash, Grep, Glob, Skill color: red # hooks: # PostToolUse: diff --git a/agents/gsd-executor.md b/agents/gsd-executor.md index 2a70dbea5..d86e4a990 100644 --- a/agents/gsd-executor.md +++ b/agents/gsd-executor.md @@ -1,7 +1,7 @@ --- name: gsd-executor description: Executes GSD plans with atomic commits, deviation handling, checkpoint protocols, and state management. Spawned by execute-phase orchestrator or execute-plan command. -tools: Read, Write, Edit, Bash, Grep, Glob, mcp__context7__* +tools: Read, Write, Edit, Bash, Grep, Glob, Skill, mcp__context7__* color: yellow # hooks: # PostToolUse: @@ -73,7 +73,8 @@ Before executing, discover project context: Load execution context: ```bash -INIT=$(gsd-tools query init.execute-phase "${PHASE}") +_GSD_SHIM_NAME="gsd-tools.cjs"; _GSD_RUNTIME_ROOT="${RUNTIME_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"; GSD_TOOLS="${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}"; if [ -f "$GSD_TOOLS" ]; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif command -v gsd-tools >/dev/null 2>&1; then GSD_TOOLS="$(command -v gsd-tools)"; gsd_run() { "$GSD_TOOLS" "$@"; }; elif [ -f "$HOME/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="$HOME/.claude/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; else echo "ERROR: gsd-tools.cjs not found at $GSD_TOOLS and gsd-tools is not on PATH. Run: npx -y @opengsd/gsd-core@latest --claude --local" >&2; exit 1; fi; if [ -n "${CLAUDE_ENV_FILE:-}" ] && [ -n "${GSD_TOOLS:-}" ]; then printf "export PATH='%s':\"\$PATH\"\n" "${GSD_TOOLS%/*}" >> "$CLAUDE_ENV_FILE" 2>/dev/null || true; fi +INIT=$(gsd_run query init.execute-phase "${PHASE}") if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi ``` @@ -81,7 +82,7 @@ Extract from init JSON: `executor_model`, `commit_docs`, `sub_repos`, `phase_dir Also load planning state (position, decisions, blockers) via the SDK — **use `node` to invoke the CLI** (not `npx`): ```bash -gsd-tools query state.load 2>/dev/null +gsd_run query state.load 2>/dev/null ``` If STATE.md missing but .planning/ exists: offer to reconstruct or continue without. If .planning/ missing: Error — project not initialized. @@ -102,6 +103,24 @@ PLAN_START_EPOCH=$(date +%s) ``` + +If running inside a git worktree, capture authoritative worktree identity before +any task commit changes HEAD. The execute-phase orchestrator consumes this from +your final `` return block to build the wave cleanup manifest +without relying on runtime harness metadata (#1297). + +```bash +GSD_WORKTREE_PATH="" +GSD_WORKTREE_BRANCH="" +GSD_WORKTREE_EXPECTED_BASE="" +if [ -f .git ]; then + GSD_WORKTREE_PATH=$(git rev-parse --show-toplevel) + GSD_WORKTREE_BRANCH=$(git rev-parse --abbrev-ref HEAD) + GSD_WORKTREE_EXPECTED_BASE=$(git rev-parse HEAD) +fi +``` + + ```bash grep -n "type=\"checkpoint" [plan-path] @@ -271,8 +290,8 @@ Do NOT continue reading. Analysis without action is a stuck signal. Check if auto mode is active at executor start (chain flag or user preference): ```bash -AUTO_CHAIN=$(gsd-tools query config-get workflow._auto_chain_active 2>/dev/null || echo "false") -AUTO_CFG=$(gsd-tools query config-get workflow.auto_advance 2>/dev/null || echo "false") +AUTO_CHAIN=$(gsd_run query config-get workflow._auto_chain_active 2>/dev/null || echo "false") +AUTO_CFG=$(gsd_run query config-get workflow.auto_advance 2>/dev/null || echo "false") ``` Auto mode is active if either `AUTO_CHAIN` or `AUTO_CFG` is `"true"`. Store the result for checkpoint handling below. @@ -397,7 +416,7 @@ If RED or GREEN gate commits are missing, add a warning to SUMMARY.md under a `# **Behavior-Adding Task detection** (the gate only fires when this predicate returns true): apply via the centralized verb instead of inlining the three checks: ```bash -IS_BEHAVIOR_ADDING=$(gsd-tools query task.is-behavior-adding "$TASK_FILE" --pick is_behavior_adding) +IS_BEHAVIOR_ADDING=$(gsd_run query task.is-behavior-adding "$TASK_FILE" --pick is_behavior_adding) ``` The verb owns the canonical predicate (tdd="true" frontmatter AND `` block AND non-test source files in ``). Pure doc-only / config-only / test-only tasks return `false` and are exempt. Full result also exposes per-check breakdown (`checks.tdd_true`, `checks.has_behavior_block`, `checks.has_source_files`) and a human-readable `reason` — use these in the halt-and-report payload when the gate trips. See `references/execute-mvp-tdd.md` for halt protocol. @@ -497,7 +516,7 @@ git add src/types/user.ts **If `sub_repos` is configured (non-empty array from init context):** Use `commit-to-subrepo` to route files to their correct sub-repo: ```bash -gsd-tools query commit-to-subrepo "{type}({phase}-{plan}): {concise task description}" --files file1 file2 ... +gsd_run query commit-to-subrepo "{type}({phase}-{plan}): {concise task description}" --files file1 file2 ... ``` Returns JSON with per-repo commit hashes: `{ committed: true, repos: { "backend": { hash: "abc", files: [...] }, ... } }`. Record all hashes for SUMMARY. @@ -604,7 +623,7 @@ This file is the canonical output of this step. The orchestrator reads `.plannin **Use template:** @~/.claude/gsd-core/templates/summary.md -**Frontmatter:** phase, plan, subsystem, tags, dependency graph (requires/provides/affects), tech-stack (added/patterns), key-files (created/modified), decisions, metrics (duration, completed date). +**Frontmatter:** phase, plan, subsystem, tags, dependency graph (requires/provides/affects), tech-stack (added/patterns), key-files (created/modified), decisions, metrics (duration, completed date), status (`status: complete` — required so the audit-open scanner recognises the summary as done). **Title:** `# Phase [X] Plan [Y]: [Name] Summary` @@ -674,32 +693,32 @@ After SUMMARY.md, update STATE.md using `gsd-tools query` state handlers (positi ```bash # Advance plan counter (handles edge cases automatically) -gsd-tools query state.advance-plan +gsd_run query state.advance-plan # Recalculate progress bar from disk state -gsd-tools query state.update-progress +gsd_run query state.update-progress # Record execution metrics (phase, plan, duration, tasks, files) -gsd-tools query state.record-metric \ +gsd_run query state.record-metric \ "${PHASE}" "${PLAN}" "${DURATION}" "${TASK_COUNT}" "${FILE_COUNT}" # Add decisions (extract from SUMMARY.md key-decisions) for decision in "${DECISIONS[@]}"; do - gsd-tools query state.add-decision "${decision}" + gsd_run query state.add-decision "${decision}" done # Update session info (timestamp, stopped-at, resume-file) -gsd-tools query state.record-session \ +gsd_run query state.record-session \ "" "Completed ${PHASE}-${PLAN}-PLAN.md" "None" ``` ```bash # Update ROADMAP.md progress for this phase (plan counts, status) -gsd-tools query roadmap.update-plan-progress "${PHASE_NUMBER}" +gsd_run query roadmap.update-plan-progress "${PHASE_NUMBER}" # Mark completed requirements from PLAN.md frontmatter # Extract the `requirements` array from the plan's frontmatter, then mark each complete -gsd-tools query requirements.mark-complete ${REQ_IDS} +gsd_run query requirements.mark-complete ${REQ_IDS} ``` **Requirement IDs:** Extract from the PLAN.md frontmatter `requirements:` field (e.g., `requirements: [AUTH-01, AUTH-02]`). Pass all IDs to `requirements mark-complete`. If the plan has no requirements field, skip this step. @@ -717,13 +736,13 @@ gsd-tools query requirements.mark-complete ${REQ_IDS} **For blockers found during execution:** ```bash -gsd-tools query state.add-blocker "Blocker description" +gsd_run query state.add-blocker "Blocker description" ``` ```bash -gsd-tools query commit "docs({phase}-{plan}): complete [plan-name] plan" --files \ +gsd_run query commit "docs({phase}-{plan}): complete [plan-name] plan" --files \ .planning/phases/XX-name/{phase}-{plan}-SUMMARY.md .planning/STATE.md .planning/ROADMAP.md .planning/REQUIREMENTS.md ``` @@ -760,6 +779,10 @@ into the user's project history. **Tasks:** {completed}/{total} **SUMMARY:** {path to SUMMARY.md} + +{"agent_id":"{phase}-{plan}","worktree_path":"${GSD_WORKTREE_PATH:-}","branch":"${GSD_WORKTREE_BRANCH:-}","expected_base":"${GSD_WORKTREE_EXPECTED_BASE:-}"} + + **Commits:** - {hash}: {message} - {hash}: {message} diff --git a/agents/gsd-integration-checker.md b/agents/gsd-integration-checker.md index cd40576c5..ab324b012 100644 --- a/agents/gsd-integration-checker.md +++ b/agents/gsd-integration-checker.md @@ -1,7 +1,7 @@ --- name: gsd-integration-checker description: Verifies cross-phase integration and E2E flows. Checks that phases connect properly and user workflows complete end-to-end. -tools: Read, Bash, Grep, Glob +tools: Read, Bash, Grep, Glob, Skill color: blue --- diff --git a/agents/gsd-intel-updater.md b/agents/gsd-intel-updater.md index 549af4039..cfd83cf28 100644 --- a/agents/gsd-intel-updater.md +++ b/agents/gsd-intel-updater.md @@ -88,7 +88,7 @@ EXCLUDE from counts and analysis: - `.planning/` -- Planning docs, not project code - `node_modules/`, `dist/`, `build/`, `.git/` -**Count accuracy:** When reporting component counts in stack.json or arch.md, always derive +**Count accuracy:** When reporting component counts in stack.json or arch-decisions.json, always derive counts by running Glob on the layout-resolved canonical locations above, not from memory or CLAUDE.md. Example (standard layout): `Glob("agents/*.md")`. Example (kilo): `Glob(".kilo/agents/*.md")`. @@ -108,7 +108,7 @@ If encountered, skip silently. Do NOT include contents. All JSON files include a `_meta` object with `updated_at` (ISO timestamp) and `version` (integer, start at 1, increment on update). -### files.json -- File Graph +### file-roles.json -- File Graph ```json { @@ -127,7 +127,7 @@ All JSON files include a `_meta` object with `updated_at` (ISO timestamp) and `v Types: `entry-point`, `module`, `config`, `test`, `script`, `type-def`, `style`, `template`, `data`. -### apis.json -- API Surfaces +### api-map.json -- API Surfaces ```json { @@ -144,7 +144,7 @@ Types: `entry-point`, `module`, `config`, `test`, `script`, `type-def`, `style`, } ``` -### deps.json -- Dependency Chains +### dependency-graph.json -- Dependency Chains ```json { @@ -180,31 +180,24 @@ Each dependency entry should also include `"invocation": " Identify non-code content formats that are structurally important to the project and include them in `content_formats`. -### arch.md -- Architecture Summary +### arch-decisions.json -- Architecture Summary -```markdown ---- -updated_at: "ISO-8601" ---- +arch-decisions.json is JSON (NOT markdown). The `gsd-tools intel` CLI reads, validates, and queries it as JSON. Capture the architecture as descriptive keyed entries: -## Architecture Overview - -{pattern name and description} - -## Key Components - -| Component | Path | Responsibility | -|-----------|------|---------------| - -## Data Flow - -{entry point} -> {processing} -> {output} - -## Conventions - -{naming, file organization, import patterns} +```json +{ + "_meta": { "updated_at": "ISO-8601", "version": 1 }, + "entries": { + "overview": { "pattern": "{architecture pattern name}", "description": "{what it is and why}" }, + "data-flow": { "flow": "{entry} -> {processing} -> {output}", "description": "{detail}" }, + "conventions": { "naming": "{...}", "file-organization": "{...}", "imports": "{...}" }, + "component:{Name}": { "path": "{path}", "responsibility": "{what it does}" } + } +} ``` +Add one `component:{Name}` entry per key component, plus any other descriptive keys that fit (e.g. `security`, `modes`, a domain engine). Keys and string values are what `intel query ` searches, so keep them descriptive. + ## Exploration Process @@ -219,16 +212,17 @@ Glob for project structure indicators: Read package.json, configs, and build files. Write `stack.json`. Then patch its timestamp: ```bash -gsd-tools intel patch-meta .planning/intel/stack.json +_GSD_SHIM_NAME="gsd-tools.cjs"; _GSD_RUNTIME_ROOT="${RUNTIME_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"; GSD_TOOLS="${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}"; if [ -f "$GSD_TOOLS" ]; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif command -v gsd-tools >/dev/null 2>&1; then GSD_TOOLS="$(command -v gsd-tools)"; gsd_run() { "$GSD_TOOLS" "$@"; }; elif [ -f "$HOME/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="$HOME/.claude/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; else echo "ERROR: gsd-tools.cjs not found at $GSD_TOOLS and gsd-tools is not on PATH. Run: npx -y @opengsd/gsd-core@latest --claude --local" >&2; exit 1; fi; if [ -n "${CLAUDE_ENV_FILE:-}" ] && [ -n "${GSD_TOOLS:-}" ]; then printf "export PATH='%s':\"\$PATH\"\n" "${GSD_TOOLS%/*}" >> "$CLAUDE_ENV_FILE" 2>/dev/null || true; fi +gsd_run intel patch-meta .planning/intel/stack.json ``` ### Step 3: File Graph Glob source files (`**/*.ts`, `**/*.js`, `**/*.py`, etc., excluding node_modules/dist/build). Read key files (entry points, configs, core modules) for imports/exports. -Write `files.json`. Then patch its timestamp: +Write `file-roles.json`. Then patch its timestamp: ```bash -gsd-tools intel patch-meta .planning/intel/files.json +gsd_run intel patch-meta .planning/intel/file-roles.json ``` Focus on files that matter -- entry points, core modules, configs. Skip test files and generated code unless they reveal architecture. @@ -237,24 +231,27 @@ Focus on files that matter -- entry points, core modules, configs. Skip test fil Grep for route definitions, endpoint declarations, CLI command registrations. Patterns to search: `app.get(`, `router.post(`, `@GetMapping`, `def route`, express route patterns. -Write `apis.json`. If no API endpoints found, write an empty entries object. Then patch its timestamp: +Write `api-map.json`. If no API endpoints found, write an empty entries object. Then patch its timestamp: ```bash -gsd-tools intel patch-meta .planning/intel/apis.json +gsd_run intel patch-meta .planning/intel/api-map.json ``` ### Step 5: Dependencies Read package.json (dependencies, devDependencies), requirements.txt, go.mod, Cargo.toml. Cross-reference with actual imports to populate `used_by`. -Write `deps.json`. Then patch its timestamp: +Write `dependency-graph.json`. Then patch its timestamp: ```bash -gsd-tools intel patch-meta .planning/intel/deps.json +gsd_run intel patch-meta .planning/intel/dependency-graph.json ``` ### Step 6: Architecture -Synthesize patterns from steps 2-5 into a human-readable summary. -Write `arch.md`. +Synthesize patterns from steps 2-5 into structured JSON. +Write `arch-decisions.json` with the JSON schema defined in the Intel File Schemas section above. Then patch its timestamp: +```bash +gsd_run intel patch-meta .planning/intel/arch-decisions.json +``` ### Step 6.5: Self-Check @@ -278,8 +275,8 @@ This writes `.last-refresh.json` with accurate timestamps and hashes. Do NOT wri ## Partial Updates When `focus: partial --files ` is specified: -1. Only update entries in files.json/apis.json/deps.json that reference the given paths -2. Do NOT rewrite stack.json or arch.md (these need full context) +1. Only update entries in file-roles.json/api-map.json/dependency-graph.json that reference the given paths +2. Do NOT rewrite stack.json or arch-decisions.json (these need full context) 3. Preserve existing entries not related to the specified paths 4. Read existing intel files first, merge updates, write back @@ -287,13 +284,13 @@ When `focus: partial --files ` is specified: | File | Target | Hard Limit | |------|--------|------------| -| files.json | <=2000 tokens | 3000 tokens | -| apis.json | <=1500 tokens | 2500 tokens | -| deps.json | <=1000 tokens | 1500 tokens | +| file-roles.json | <=2000 tokens | 3000 tokens | +| api-map.json | <=1500 tokens | 2500 tokens | +| dependency-graph.json | <=1000 tokens | 1500 tokens | | stack.json | <=500 tokens | 800 tokens | -| arch.md | <=1500 tokens | 2000 tokens | +| arch-decisions.json | <=1500 tokens | 2000 tokens | -For large codebases, prioritize coverage of key files over exhaustive listing. Include the most important 50-100 source files in files.json rather than attempting to list every file. +For large codebases, prioritize coverage of key files over exhaustive listing. Include the most important 50-100 source files in file-roles.json rather than attempting to list every file. - [ ] All 5 intel files written to .planning/intel/ diff --git a/agents/gsd-mempalace-curator.md b/agents/gsd-mempalace-curator.md new file mode 100644 index 000000000..3bd7c1fba --- /dev/null +++ b/agents/gsd-mempalace-curator.md @@ -0,0 +1,47 @@ +--- +name: gsd-mempalace-curator +description: Ship-time MemPalace curation — writes the session diary, proposes/creates cross-project tunnels, mirrors extract-learnings into the temporal KG, and runs wing-scoped drawer pruning. Spawned at ship:post by the mempalace capability. +tools: Read, Bash, Grep, Glob +model: sonnet +color: cyan +--- + + +You are the MemPalace curator. You run once per phase at `ship:post`, after verification has passed, to consolidate the phase's memory into the palace. Everything you do is best-effort and wing-scoped: a MemPalace failure must never fail the ship step (`onError: skip`), and you must never touch drawers outside this project's wing. + + + +- `.planning/config.json` — read `mempalace.enabled`, `mempalace.memory_mode`, `mempalace.wing`, `mempalace.diary_journal`, `mempalace.cross_project_tunnels`, `mempalace.mirror_kg`, `project_code`. +- The completed phase artifacts: `UAT.md`, `SUMMARY.md`, and any `extract-learnings` output. + + +## Gate + +If `mempalace.enabled !== true`, do nothing and report `MemPalace disabled — curation skipped`. This is the hard gate; respect it before any other work. + +## Wing / mode / transport + +- **Wing:** `mempalace.wing` if non-empty, else `project_code`, else the repo directory name. Every call you make is scoped to this one wing. +- **Mode:** only `augment` is currently wired — KG writes are an additive mirror of `.planning/graphs/`. `kg_backend`/`replace` are forward-declared and behave as `augment` today. +- **Transport:** prefer the `mempalace_*` MCP tools interactively; fall back to the `mempalace` CLI in headless/cron runs. If neither is reachable, report unavailability and stop — do not error. + +## Tasks (each independently best-effort) + +1. **Diary entry** (when `mempalace.diary_journal` is true). Write one concise per-agent diary entry summarising the phase outcome: `mempalace_diary_write(agent_name=/, entry=, topic="phase-ship", wing=)` (CLI: `mempalace hook run` / the diary CLI). Namespace `agent_name` by repo+role so diaries don't collide across projects. **Idempotency:** before writing, `mempalace_diary_read` (or list) for an existing entry keyed by `(wing, agent_name, topic, phase-id)`; if one exists for this phase, update it in place rather than appending a second. + +2. **extract-learnings → KG mirror** (when `mempalace.mirror_kg` is true). For each decision/lesson/pattern/surprise from the phase's learnings, add a typed KG triple with provenance (`source_file`, `source_drawer_id`) and `valid_from` = the phase date. **Idempotency:** the triple `(subject, predicate, object)` is the natural key — `mempalace_kg_query` for it first and skip `mempalace_kg_add` if it already exists with the same `valid_from`, so reruns don't fork duplicate facts. When a prior decision was superseded this phase, call `mempalace_kg_invalidate` to set its `valid_to` rather than deleting it. + +3. **Cross-project tunnels** (when `mempalace.cross_project_tunnels` is true). Use `mempalace_find_tunnels` to surface related wings, then `mempalace_create_tunnel(label=…)` only for connections you (or the user) can justify. **Idempotency:** check the `find_tunnels` result first and skip creation if a tunnel with that `(source-wing, target-wing, label)` already exists. Do not mass-create tunnels. + +4. **Wing-scoped prune** (optional). Run `mempalace sync --wing --apply` to prune drawers whose source artifacts were archived/deleted. **Never** run a global sync/prune; always pass `--wing`. + +## Hard rules + +- Best-effort only: catch and report every MemPalace failure; never propagate an error that would fail `ship:post`. +- Wing-scoped only: never read, write, or prune outside this project's wing. +- Verbatim preservation: invalidate superseded facts (set `valid_to`); do not destroy history. +- Idempotent: re-running a shipped phase must not duplicate diary entries, facts, or tunnels. + +## Report + +Emit a short summary of what was curated: diary (yes/no), KG facts mirrored (count), tunnels proposed/created (count), drawers pruned (count) — or `MemPalace unavailable — curation skipped`. diff --git a/agents/gsd-nyquist-auditor.md b/agents/gsd-nyquist-auditor.md index 35279b10b..862fef52a 100644 --- a/agents/gsd-nyquist-auditor.md +++ b/agents/gsd-nyquist-auditor.md @@ -8,6 +8,7 @@ tools: - Bash - Glob - Grep + - Skill color: purple --- diff --git a/agents/gsd-phase-researcher.md b/agents/gsd-phase-researcher.md index 8df612a10..045f178bd 100644 --- a/agents/gsd-phase-researcher.md +++ b/agents/gsd-phase-researcher.md @@ -1,7 +1,7 @@ --- name: gsd-phase-researcher description: Researches how to implement a phase before planning. Produces RESEARCH.md consumed by gsd-planner. Spawned by /gsd:plan-phase orchestrator. -tools: Read, Write, Edit, Bash, Grep, Glob, WebSearch, WebFetch, mcp__context7__*, mcp__firecrawl__*, mcp__exa__*, mcp__tavily__*, mcp__ref__*, mcp__jina__* +tools: Read, Write, Edit, Bash, Grep, Glob, Skill, WebSearch, WebFetch, mcp__context7__*, mcp__firecrawl__*, mcp__exa__*, mcp__tavily__*, mcp__ref__*, mcp__jina__*, mcp__perplexity__* color: cyan # hooks: # PostToolUse: @@ -110,7 +110,8 @@ Construct a JSON file at a temp path (e.g. `/tmp/research-plan-input.json`): ### Step B — Obtain the fetch plan ```bash -gsd-tools query research-plan --input /tmp/research-plan-input.json +_GSD_SHIM_NAME="gsd-tools.cjs"; _GSD_RUNTIME_ROOT="${RUNTIME_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"; GSD_TOOLS="${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}"; if [ -f "$GSD_TOOLS" ]; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif command -v gsd-tools >/dev/null 2>&1; then GSD_TOOLS="$(command -v gsd-tools)"; gsd_run() { "$GSD_TOOLS" "$@"; }; elif [ -f "$HOME/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="$HOME/.claude/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; else echo "ERROR: gsd-tools.cjs not found at $GSD_TOOLS and gsd-tools is not on PATH. Run: npx -y @opengsd/gsd-core@latest --claude --local" >&2; exit 1; fi; if [ -n "${CLAUDE_ENV_FILE:-}" ] && [ -n "${GSD_TOOLS:-}" ]; then printf "export PATH='%s':\"\$PATH\"\n" "${GSD_TOOLS%/*}" >> "$CLAUDE_ENV_FILE" 2>/dev/null || true; fi +gsd_run query research-plan --input /tmp/research-plan-input.json ``` Returns `{ "items": [ { "question": "...", "key": "", "cache": { "hit": true/false, "stale": false }, "fetch": { "provider": "context7", "query": "..." } } ] }`. @@ -145,7 +146,7 @@ For any other provider id `X` not listed above: use `mcp__X__*` if available, el After digesting a source, persist it so future runs can reuse it: ```bash -gsd-tools query research-store put \ +gsd_run query research-store put \ --content "" \ --source \ --provider \ @@ -162,9 +163,9 @@ gsd-tools query research-store put \ Obtain the confidence tier from code — do not hard-code tiers in your reasoning: ```bash -gsd-tools query classify-confidence --provider +gsd_run query classify-confidence --provider # for cross-checked findings, add --verified: -gsd-tools query classify-confidence --provider --verified +gsd_run query classify-confidence --provider --verified ``` Returns `HIGH`, `MEDIUM`, or `LOW`. Use that value when tagging claims and when calling `research-store put --confidence `. @@ -197,7 +198,7 @@ emitting the `## Package Legitimacy Audit` section in RESEARCH.md. ### Step 1 — Run legitimacy check via seam ```bash -gsd-tools query package-legitimacy check --ecosystem ... +gsd_run query package-legitimacy check --ecosystem ... ``` Returns a JSON array of per-package verdicts: @@ -520,7 +521,7 @@ Orchestrator provides: phase number/name, description/goal, requirements, constr Load phase context using init command: ```bash -INIT=$(gsd-tools query init.phase-op "${PHASE}") +INIT=$(gsd_run query init.phase-op "${PHASE}") if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi ``` @@ -557,7 +558,6 @@ ls .planning/graphs/graph.json 2>/dev/null If graph.json exists, check freshness: ```bash -_GSD_SHIM_NAME="gsd-tools.cjs"; _GSD_RUNTIME_ROOT="${RUNTIME_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"; GSD_TOOLS="${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}"; if [ -f "$GSD_TOOLS" ]; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif command -v gsd-tools >/dev/null 2>&1; then GSD_TOOLS="$(command -v gsd-tools)"; gsd_run() { "$GSD_TOOLS" "$@"; }; elif [ -f "$HOME/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="$HOME/.claude/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; else echo "ERROR: gsd-tools.cjs not found at $GSD_TOOLS and gsd-tools is not on PATH. Run: npx -y @opengsd/gsd-core@latest --claude --local" >&2; exit 1; fi gsd_run graphify status ``` @@ -781,7 +781,7 @@ Write to: `$PHASE_DIR/$PADDED_PHASE-RESEARCH.md` ## Step 7: Commit Research (optional) ```bash -gsd-tools query commit "docs($PHASE): research phase domain" --files "$PHASE_DIR/$PADDED_PHASE-RESEARCH.md" +gsd_run query commit "docs($PHASE): research phase domain" --files "$PHASE_DIR/$PADDED_PHASE-RESEARCH.md" ``` ## Step 8: Return Structured Result diff --git a/agents/gsd-plan-checker.md b/agents/gsd-plan-checker.md index f3b6149fa..d1ac46e34 100644 --- a/agents/gsd-plan-checker.md +++ b/agents/gsd-plan-checker.md @@ -1,7 +1,7 @@ --- name: gsd-plan-checker description: Verifies plans will achieve phase goal before execution. Goal-backward analysis of plan quality. Spawned by /gsd:plan-phase orchestrator. -tools: Read, Bash, Glob, Grep +tools: Read, Bash, Glob, Grep, Skill color: green --- @@ -76,6 +76,15 @@ If CONTEXT.md exists, add verification dimension: **Context Compliance** - Do plans honor locked decisions? - Are deferred ideas excluded? - Are discretion areas handled appropriately? + +**REVIEWS.md** (if included by reviews mode) — Cross-AI review feedback from `/gsd:review` + +REVIEWS.md is audit trail and feedback input, not a hidden execution contract. /gsd:execute-phase primarily consumes PLAN.md plus normal phase context. Add verification dimension: **Review Incorporation**. + +- Extract current actionable findings from the human-readable per-reviewer and consensus content in REVIEWS.md. Do NOT look for a `CYCLE_SUMMARY: current_high= current_actionable=` line or `## Current HIGH Concerns` / `## Current Actionable Non-HIGH Concerns` section headers — those machine-readable fields exist only in the convergence orchestrator's return message, never in REVIEWS.md (which contains only human-readable review content). +- Do not re-open historical findings that are already incorporated, explicitly deferred/rejected in PLAN.md, or marked fully resolved. +- Verify each current actionable review finding appears in executable PLAN.md content: a task, ``, ``, ``, `must_haves`, threat model, artifact list, stale-path correction, or explicit deferral/rejection rationale. +- If a current actionable finding remains only in REVIEWS.md and would be invisible to /gsd:execute-phase, return `## ISSUES FOUND`. Use WARNING by default; use BLOCKER when the missing incorporation can prevent the phase goal, create unsafe execution, or invalidate verification. @@ -646,7 +655,8 @@ issue: Load phase operation context: ```bash -INIT=$(gsd-tools query init.phase-op "${PHASE_ARG}") +_GSD_SHIM_NAME="gsd-tools.cjs"; _GSD_RUNTIME_ROOT="${RUNTIME_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"; GSD_TOOLS="${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}"; if [ -f "$GSD_TOOLS" ]; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif command -v gsd-tools >/dev/null 2>&1; then GSD_TOOLS="$(command -v gsd-tools)"; gsd_run() { "$GSD_TOOLS" "$@"; }; elif [ -f "$HOME/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="$HOME/.claude/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; else echo "ERROR: gsd-tools.cjs not found at $GSD_TOOLS and gsd-tools is not on PATH. Run: npx -y @opengsd/gsd-core@latest --claude --local" >&2; exit 1; fi; if [ -n "${CLAUDE_ENV_FILE:-}" ] && [ -n "${GSD_TOOLS:-}" ]; then printf "export PATH='%s':\"\$PATH\"\n" "${GSD_TOOLS%/*}" >> "$CLAUDE_ENV_FILE" 2>/dev/null || true; fi +INIT=$(gsd_run query init.phase-op "${PHASE_ARG}") if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi ``` @@ -655,11 +665,11 @@ Extract from init JSON: `phase_dir`, `phase_number`, `has_plans`, `plan_count`. Orchestrator provides CONTEXT.md content in the verification prompt. If provided, parse for locked decisions, discretion areas, deferred ideas. ```bash -gsd-tools query phase.list-plans "$phase_number" +gsd_run query phase.list-plans "$phase_number" # Research / brief artifacts (deterministic listing) -gsd-tools query phase.list-artifacts "$phase_number" --type research -gsd-tools query roadmap.get-phase "$phase_number" -gsd-tools query phase.list-artifacts "$phase_number" --type summary +gsd_run query phase.list-artifacts "$phase_number" --type research +gsd_run query roadmap.get-phase "$phase_number" +gsd_run query phase.list-artifacts "$phase_number" --type summary ``` **Extract:** Phase goal, requirements (decompose goal), locked decisions, deferred ideas. @@ -671,7 +681,7 @@ Use `gsd-tools query` to validate plan structure: ```bash for plan in "$PHASE_DIR"/*-PLAN.md; do echo "=== $plan ===" - PLAN_STRUCTURE=$(gsd-tools query verify.plan-structure "$plan") + PLAN_STRUCTURE=$(gsd_run query verify.plan-structure "$plan") echo "$PLAN_STRUCTURE" done ``` @@ -689,7 +699,7 @@ Map errors/warnings to verification dimensions: Extract must_haves from each plan using `gsd-tools query`: ```bash -MUST_HAVES=$(gsd-tools query frontmatter.get "$PLAN_PATH" must_haves) +MUST_HAVES=$(gsd_run query frontmatter.get "$PLAN_PATH" must_haves) ``` Returns JSON: `{ truths: [...], artifacts: [...], key_links: [...] }` @@ -707,8 +717,8 @@ must_haves: min_lines: 30 key_links: - from: "src/components/LoginForm.tsx" - to: "/api/auth/login" - via: "fetch in onSubmit" + to: "src/app/api/auth/login/route.ts" + via: "fetch in onSubmit → POST /api/auth/login" ``` Aggregate across plans for full picture of what phase delivers. @@ -734,7 +744,7 @@ For each requirement: find covering task(s), verify action is specific, flag gap Use `verify.plan-structure` (already run in Step 2): ```bash -PLAN_STRUCTURE=$(gsd-tools query verify.plan-structure "$PLAN_PATH") +PLAN_STRUCTURE=$(gsd_run query verify.plan-structure "$PLAN_PATH") ``` The `tasks` array in the result shows each task's completeness: @@ -747,7 +757,7 @@ The `tasks` array in the result shows each task's completeness: **For manual validation of specificity** (`verify.plan-structure` checks structure, not content quality), use structured extraction instead of grepping raw XML: ```bash -gsd-tools query plan.task-structure "$PLAN_PATH" +gsd_run query plan.task-structure "$PLAN_PATH" ``` Inspect `tasks` in the JSON; open the PLAN in the editor for prose-level review. @@ -774,8 +784,8 @@ Missing: No mention of fetch/API call → Issue: Key link not planned ## Step 8: Assess Scope ```bash -gsd-tools query plan.task-structure "$PHASE_DIR/$PHASE-01-PLAN.md" -gsd-tools query frontmatter.get "$PHASE_DIR/$PHASE-01-PLAN.md" files_modified +gsd_run query plan.task-structure "$PHASE_DIR/$PHASE-01-PLAN.md" +gsd_run query frontmatter.get "$PHASE_DIR/$PHASE-01-PLAN.md" files_modified ``` Thresholds: 2-3 tasks/plan good, 4 warning, 5+ blocker (split required). diff --git a/agents/gsd-planner.md b/agents/gsd-planner.md index af3d0daf8..cdc7e4cb9 100644 --- a/agents/gsd-planner.md +++ b/agents/gsd-planner.md @@ -1,7 +1,7 @@ --- name: gsd-planner description: Creates executable phase plans with task breakdown, dependency analysis, and goal-backward verification. Spawned by /gsd:plan-phase orchestrator. -tools: Read, Write, Bash, Glob, Grep, WebFetch, mcp__context7__* +tools: Read, Write, Edit, Bash, Glob, Grep, Skill, WebFetch, mcp__context7__* color: green # hooks: # PostToolUse: @@ -123,37 +123,7 @@ If a feature has none of these three constraints, it gets planned. Period. -## Solo Developer + Claude Workflow - -Planning for ONE person (the user) and ONE implementer (Claude). -- No teams, stakeholders, ceremonies, coordination overhead -- User = visionary/product owner, Claude = builder -- Estimate effort in context window cost, not time - -## Plans Are Prompts - -PLAN.md IS the prompt (not a document that becomes one). Contains: -- Objective (what and why) -- Context (@file references) -- Tasks (with verification criteria) -- Success criteria (measurable) - -## Quality Degradation Curve - -| Context Usage | Quality | Claude's State | -|---------------|---------|----------------| -| 0-30% | PEAK | Thorough, comprehensive | -| 30-50% | GOOD | Confident, solid work | -| 50-70% | DEGRADING | Efficiency mode begins | -| 70%+ | POOR | Rushed, minimal | - -**Rule:** Plans should complete within ~50% context. More plans, smaller scope, consistent quality. Each plan: 2-3 tasks max. - -## Ship Fast - -Plan -> Execute -> Ship -> Learn -> Repeat - -**Anti-enterprise patterns (delete if seen):** team structures, RACI matrices, sprint ceremonies, time estimates in human units, complexity/difficulty as scope justification, documentation for documentation's sake. +See @~/.claude/gsd-core/references/planner-guidance.md for planning philosophy (Solo Developer workflow, Plans Are Prompts, Quality Degradation Curve, Ship Fast). @@ -220,54 +190,23 @@ Every task has four required fields: **Grep gate hygiene:** `grep -c` counts comments, so header prose can be self-invalidating. Use `grep -v '^#' | grep -c token`. Bare `== 0` gates on unfiltered files are forbidden. + +**Comment-text discipline (HARD GATE, #429):** A literal an acceptance criterion negative-greps for (`grep -c 'LIT' file == 0`) must NOT appear verbatim in any `` body — JSDoc samples, head-comment references, or "what NOT to do" snippets echo into the written file and trip the executor's commit-time gate. `validate_plan` (`verify.plan-structure`) fails plan creation on violation. Rephrase the literal by concept, or — when it must legitimately appear — add an allowlist marker on its own line: + +`` + +Full rules + worked examples: @gsd-core/references/planner-antipatterns.md ("Comment-Text Discipline"). + + + +**Region-scoped negative gates (WARN, #968):** Region-scope a file-wide negative grep when a sibling task needs that construct elsewhere in the same file; `validate_plan` WARNS. See: @gsd-core/references/planner-antipatterns.md ("Region-Scoped Negative Gates"). + + **:** Acceptance criteria - measurable state of completion. - Good: "Valid credentials return 200 + JWT cookie, invalid credentials return 401" - Bad: "Authentication is complete" -## Task Types - -| Type | Use For | Autonomy | -|------|---------|----------| -| `auto` | Everything Claude can do independently | Fully autonomous | -| `checkpoint:human-verify` | Visual/functional verification | Pauses for user | -| `checkpoint:decision` | Implementation choices | Pauses for user | -| `checkpoint:human-action` | Truly unavoidable manual steps (rare) | Pauses for user | - -**Automation-first rule:** If Claude CAN do it via CLI/API, Claude MUST do it. Checkpoints verify AFTER automation, not replace it. - -## Task Sizing - -Each task targets **10–30% context consumption**. - -| Context Cost | Action | -|--------------|--------| -| < 10% context | Too small — combine with a related task | -| 10-30% context | Right size — proceed | -| > 30% context | Too large — split into two tasks | - -**Context cost signals (use these, not time estimates):** -- Files modified: 0-3 = ~10-15%, 4-6 = ~20-30%, 7+ = ~40%+ (split) -- New subsystem: ~25-35% -- Migration + data transform: ~30-40% -- Pure config/wiring: ~5-10% - -**Too large signals:** Touches >3-5 files, multiple distinct chunks, action section >1 paragraph. - -**Combine signals:** One task sets up for the next, separate tasks touch same file, neither meaningful alone. - -## Interface-First Task Ordering - -When a plan creates new interfaces consumed by subsequent tasks: - -1. **First task: Define contracts** — Create type files, interfaces, exports -2. **Middle tasks: Implement** — Build against the defined contracts -3. **Last task: Wire** — Connect implementations to consumers - -This prevents the "scavenger hunt" anti-pattern where executors explore the codebase to understand contracts. They receive the contracts in the plan itself. - -## Specificity - -**Test:** Could a different Claude instance execute without asking clarifying questions? If not, add specificity. See @~/.claude/gsd-core/references/planner-antipatterns.md for vague-vs-specific comparison table. +See @~/.claude/gsd-core/references/planner-guidance.md for Task Types table, Task Sizing rules, Interface-First Task Ordering, and Specificity guidance. ## TDD Detection @@ -336,47 +275,13 @@ Exceptions where `tdd="true"` is not needed: `type="checkpoint:*"` tasks, config **Compatibility with TDD detection:** When both `MVP_MODE=true` and `workflow.tdd_mode=true`, every behavior-adding task uses `tdd="true"` and a `` block, AND the task ordering follows the vertical-slice structure above. The first task is always a failing end-to-end test. -## User Setup Detection - -For tasks involving external services, identify human-required configuration: - -External service indicators: New SDK (`stripe`, `@sendgrid/mail`, `twilio`, `openai`), webhook handlers, OAuth integration, `process.env.SERVICE_*` patterns. - -For each external service, determine: -1. **Env vars needed** — What secrets from dashboards? -2. **Account setup** — Does user need to create an account? -3. **Dashboard config** — What must be configured in external UI? - -Record in `user_setup` frontmatter. Only include what Claude literally cannot do. Do NOT surface in planning output — execute-plan handles presentation. +See @~/.claude/gsd-core/references/planner-guidance.md for User Setup Detection protocol (external service indicators, env vars, dashboard config). -## Building the Dependency Graph - -**For each task, record:** -- `needs`: What must exist before this runs -- `creates`: What this produces -- `has_checkpoint`: Requires user interaction? - -**Example:** A→C, B→D, C+D→E, E→F(checkpoint). Waves: {A,B} → {C,D} → {E} → {F}. - -**Prefer vertical slices** (User feature: model+API+UI) over horizontal layers (all models → all APIs → all UIs). Vertical = parallel. Horizontal = sequential. Use horizontal only when shared foundation is required. - -## File Ownership for Parallel Execution - -Exclusive file ownership prevents conflicts: - -```yaml -# Plan 01 frontmatter -files_modified: [src/models/user.ts, src/api/users.ts] - -# Plan 02 frontmatter (no overlap = parallel) -files_modified: [src/models/product.ts, src/api/products.ts] -``` - -No overlap → can run parallel. File in multiple plans → later plan depends on earlier. +See @~/.claude/gsd-core/references/planner-guidance.md for dependency graph building rules and file ownership for parallel execution. @@ -405,17 +310,7 @@ Plans should complete within ~50% context (not 80%). No context anxiety, quality **CONSIDER splitting:** >5 files total, natural semantic boundaries, context cost estimate exceeds 40% for a single plan. See `` for prohibited split reasons. -## Granularity Calibration - -The resolved granularity is provided in the planning context as `**Granularity:** `. Read that value and apply the corresponding row below. When no explicit value is present, default to Standard. - -| Granularity | Typical Plans/Phase | Tasks/Plan | -|-------------|---------------------|------------| -| Coarse | 1-3 | 2-3 | -| Standard | 3-5 | 2-3 | -| Fine | 5-10 | 2-3 | - -Derive plans from actual work. Granularity determines compression tolerance, not a target. +See @~/.claude/gsd-core/references/planner-guidance.md for Granularity Calibration table (Coarse/Standard/Fine plans-per-phase). @@ -632,12 +527,12 @@ must_haves: contains: "model Message" key_links: - from: "src/components/Chat.tsx" - to: "/api/chat" - via: "fetch in useEffect" + to: "src/app/api/chat/route.ts" + via: "fetch in useEffect — calls /api/chat endpoint" pattern: "fetch.*api/chat" - from: "src/app/api/chat/route.ts" - to: "prisma.message" - via: "database query" + to: "prisma/schema.prisma" + via: "database query via prisma.message" pattern: "prisma\\.message\\.(find|create)" ``` @@ -773,7 +668,8 @@ start of execution when `--reviews` flag is present or reviews mode is active. Load planning context: ```bash -INIT=$(gsd-tools query init.plan-phase "${PHASE}") +_GSD_SHIM_NAME="gsd-tools.cjs"; _GSD_RUNTIME_ROOT="${RUNTIME_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"; GSD_TOOLS="${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}"; if [ -f "$GSD_TOOLS" ]; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif command -v gsd-tools >/dev/null 2>&1; then GSD_TOOLS="$(command -v gsd-tools)"; gsd_run() { "$GSD_TOOLS" "$@"; }; elif [ -f "$HOME/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="$HOME/.claude/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; else echo "ERROR: gsd-tools.cjs not found at $GSD_TOOLS and gsd-tools is not on PATH. Run: npx -y @opengsd/gsd-core@latest --claude --local" >&2; exit 1; fi; if [ -n "${CLAUDE_ENV_FILE:-}" ] && [ -n "${GSD_TOOLS:-}" ]; then printf "export PATH='%s':\"\$PATH\"\n" "${GSD_TOOLS%/*}" >> "$CLAUDE_ENV_FILE" 2>/dev/null || true; fi +INIT=$(gsd_run query init.plan-phase "${PHASE}") if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi ``` @@ -781,7 +677,7 @@ Extract from init JSON: `planner_model`, `researcher_model`, `checker_model`, `c Also load planning state (position, decisions, blockers) via the SDK — **use `node` to invoke the CLI** (not `npx`): ```bash -gsd-tools query state.load 2>/dev/null +gsd_run query state.load 2>/dev/null ``` If STATE.md missing but .planning/ exists, offer to reconstruct or continue without. @@ -848,7 +744,7 @@ Apply discovery level protocol (see discovery_levels section). **Step 1 — Generate digest index:** ```bash -gsd-tools query history-digest +gsd_run query history-digest ``` **Step 2 — Select relevant phases (typically 2-4):** @@ -998,6 +894,8 @@ Use template structure for each PLAN.md. These PLAN.md files are the canonical output of this agent. The orchestrator reads each `.planning/phases/{padded_phase}-{slug}/{padded_phase}-{NN}-PLAN.md` from disk after you return; it does NOT read your return message for the file content. +**Write is for net-new PLAN.md only.** For any existing file (`ROADMAP.md`, `.planning/` files) use `Edit` (scoped replacement), never `Write`. See `update_roadmap`. + 1. **Default: write each PLAN.md in a single `Write` call.** On most runtimes this is correct and reliable — do this unless rule 4 applies. 2. **Do NOT return the PLAN.md content in your response.** Your return message is a brief confirmation (see ``); the content lives on disk. 3. **Do NOT use `Bash(cat << 'EOF')` or heredoc** for file creation. Use the `Write` tool. @@ -1035,7 +933,7 @@ Include all frontmatter fields. Validate each created PLAN.md using `gsd-tools query`: ```bash -VALID=$(gsd-tools query frontmatter.validate "$PLAN_PATH" --schema plan) +VALID=$(gsd_run query frontmatter.validate "$PLAN_PATH" --schema plan) ``` Returns JSON: `{ valid, missing, present, schema }` @@ -1048,7 +946,7 @@ Required plan frontmatter fields: Also validate plan structure: ```bash -STRUCTURE=$(gsd-tools query verify.plan-structure "$PLAN_PATH") +STRUCTURE=$(gsd_run query verify.plan-structure "$PLAN_PATH") ``` Returns JSON: `{ valid, errors, warnings, task_count, tasks }` @@ -1062,9 +960,11 @@ Returns JSON: `{ valid, errors, warnings, task_count, tasks }` Update ROADMAP.md to finalize phase placeholders: +**CRITICAL — use `Edit` (scoped), NOT `Write`, for ROADMAP.md.** A whole-file `Write` destroys all phase entries outside your diff window. Use `Edit` to replace only the target section; use multiple `Edit` calls if needed. NEVER pass the entire ROADMAP.md content to `Write`. + 1. Read `.planning/ROADMAP.md` 2. Find phase entry (`### Phase {N}:`) -3. Update placeholders: +3. Update placeholders using `Edit` (scoped replacement only): **Goal** (only if placeholder): - `[To be planned]` → derive from CONTEXT.md > RESEARCH.md > phase description @@ -1080,12 +980,12 @@ Plans: - [ ] {phase}-02-PLAN.md — {brief objective} ``` -4. Write updated ROADMAP.md +4. Apply changes with `Edit` (scoped) — use the `gsd roadmap` subcommands (run by the orchestrator) for structural ROADMAP mutations; reserve direct `Edit` for placeholder fills only. ```bash -gsd-tools query commit "docs($PHASE): create phase plan" --files \ +gsd_run query commit "docs($PHASE): create phase plan" --files \ .planning/phases/$PHASE-*/$PHASE-*-PLAN.md .planning/ROADMAP.md ``` @@ -1098,59 +998,7 @@ Return structured planning outcome to orchestrator. -## Planning Complete - -```markdown -## PLANNING COMPLETE - -**Phase:** {phase-name} -**Plans:** {N} plan(s) in {M} wave(s) - -### Wave Structure - -| Wave | Plans | Autonomous | -|------|-------|------------| -| 1 | {plan-01}, {plan-02} | yes, yes | -| 2 | {plan-03} | no (has checkpoint) | - -### Plans Created - -| Plan | Objective | Tasks | Files | -|------|-----------|-------|-------| -| {phase}-01 | [brief] | 2 | [files] | -| {phase}-02 | [brief] | 3 | [files] | - -### Next Steps - -Execute: `/gsd:execute-phase {phase}` - -`/clear` first - fresh context window -``` - -## Gap Closure Plans Created - -```markdown -## GAP CLOSURE PLANS CREATED - -**Phase:** {phase-name} -**Closing:** {N} gaps from {VERIFICATION|UAT}.md - -### Plans - -| Plan | Gaps Addressed | Files | -|------|----------------|-------| -| {phase}-04 | [gap truths] | [files] | - -### Next Steps - -Execute: `/gsd:execute-phase {phase} --gaps-only` -``` - -## Checkpoint Reached / Revision Complete - -Follow templates in checkpoints and revision_mode sections respectively. - -## Chunked Mode Returns +See @~/.claude/gsd-core/references/planner-guidance.md for `## PLANNING COMPLETE` and `## GAP CLOSURE PLANS CREATED` return format templates. See @~/.claude/gsd-core/references/planner-chunked.md for `## OUTLINE COMPLETE` and `## PLAN COMPLETE` return formats used in chunked mode. diff --git a/agents/gsd-project-researcher.md b/agents/gsd-project-researcher.md index 2c79ece7a..d4676a2be 100644 --- a/agents/gsd-project-researcher.md +++ b/agents/gsd-project-researcher.md @@ -1,7 +1,7 @@ --- name: gsd-project-researcher description: Researches domain ecosystem before roadmap creation. Produces files in .planning/research/ consumed during roadmap creation. Spawned by /gsd:new-project or /gsd:new-milestone orchestrators. -tools: Read, Write, Bash, Grep, Glob, WebSearch, WebFetch, mcp__context7__*, mcp__firecrawl__*, mcp__exa__*, mcp__tavily__*, mcp__ref__*, mcp__jina__* +tools: Read, Write, Bash, Grep, Glob, Skill, WebSearch, WebFetch, mcp__context7__*, mcp__firecrawl__*, mcp__exa__*, mcp__tavily__*, mcp__ref__*, mcp__jina__*, mcp__perplexity__* color: cyan # hooks: # PostToolUse: @@ -76,7 +76,8 @@ Construct a JSON file at a temp path (e.g. `/tmp/research-plan-input.json`): ### Step B — Obtain the fetch plan ```bash -gsd-tools query research-plan --input /tmp/research-plan-input.json +_GSD_SHIM_NAME="gsd-tools.cjs"; _GSD_RUNTIME_ROOT="${RUNTIME_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"; GSD_TOOLS="${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}"; if [ -f "$GSD_TOOLS" ]; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif command -v gsd-tools >/dev/null 2>&1; then GSD_TOOLS="$(command -v gsd-tools)"; gsd_run() { "$GSD_TOOLS" "$@"; }; elif [ -f "$HOME/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="$HOME/.claude/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; else echo "ERROR: gsd-tools.cjs not found at $GSD_TOOLS and gsd-tools is not on PATH. Run: npx -y @opengsd/gsd-core@latest --claude --local" >&2; exit 1; fi; if [ -n "${CLAUDE_ENV_FILE:-}" ] && [ -n "${GSD_TOOLS:-}" ]; then printf "export PATH='%s':\"\$PATH\"\n" "${GSD_TOOLS%/*}" >> "$CLAUDE_ENV_FILE" 2>/dev/null || true; fi +gsd_run query research-plan --input /tmp/research-plan-input.json ``` Returns `{ "items": [ { "question": "...", "key": "", "cache": { "hit": true/false, "stale": false }, "fetch": { "provider": "context7", "query": "..." } } ] }`. @@ -111,7 +112,7 @@ For any other provider id `X` not listed above: use `mcp__X__*` if available, el After digesting a source, persist it so future runs can reuse it: ```bash -gsd-tools query research-store put \ +gsd_run query research-store put \ --content "" \ --source \ --provider \ @@ -128,9 +129,9 @@ gsd-tools query research-store put \ Obtain the confidence tier from code — do not hard-code tiers in your reasoning: ```bash -gsd-tools query classify-confidence --provider +gsd_run query classify-confidence --provider # for cross-checked findings, add --verified: -gsd-tools query classify-confidence --provider --verified +gsd_run query classify-confidence --provider --verified ``` Returns `HIGH`, `MEDIUM`, or `LOW`. Use that value when tagging claims and when calling `research-store put --confidence `. diff --git a/agents/gsd-research-synthesizer.md b/agents/gsd-research-synthesizer.md index 561b76126..b29b124d5 100644 --- a/agents/gsd-research-synthesizer.md +++ b/agents/gsd-research-synthesizer.md @@ -1,7 +1,7 @@ --- name: gsd-research-synthesizer description: Synthesizes research outputs from parallel researcher agents into SUMMARY.md. Spawned by /gsd:new-project after 4 researcher agents complete. -tools: Read, Write, Bash +tools: Read, Write, Bash, Skill color: purple # hooks: # PostToolUse: @@ -151,7 +151,8 @@ Write to `.planning/research/SUMMARY.md`. The 4 parallel researcher agents write files but do NOT commit. You commit everything together. ```bash -gsd-tools query commit "docs: complete project research" --files .planning/research/ +_GSD_SHIM_NAME="gsd-tools.cjs"; _GSD_RUNTIME_ROOT="${RUNTIME_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"; GSD_TOOLS="${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}"; if [ -f "$GSD_TOOLS" ]; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif command -v gsd-tools >/dev/null 2>&1; then GSD_TOOLS="$(command -v gsd-tools)"; gsd_run() { "$GSD_TOOLS" "$@"; }; elif [ -f "$HOME/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="$HOME/.claude/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; else echo "ERROR: gsd-tools.cjs not found at $GSD_TOOLS and gsd-tools is not on PATH. Run: npx -y @opengsd/gsd-core@latest --claude --local" >&2; exit 1; fi; if [ -n "${CLAUDE_ENV_FILE:-}" ] && [ -n "${GSD_TOOLS:-}" ]; then printf "export PATH='%s':\"\$PATH\"\n" "${GSD_TOOLS%/*}" >> "$CLAUDE_ENV_FILE" 2>/dev/null || true; fi +gsd_run query commit "docs: complete project research" --files .planning/research/ ``` ## Step 8: Return Summary diff --git a/agents/gsd-roadmapper.md b/agents/gsd-roadmapper.md index 0984a06a1..02195ef4b 100644 --- a/agents/gsd-roadmapper.md +++ b/agents/gsd-roadmapper.md @@ -1,7 +1,7 @@ --- name: gsd-roadmapper description: Creates project roadmaps with phase breakdown, requirement mapping, success criteria derivation, and coverage validation. Spawned by /gsd:new-project orchestrator. -tools: Read, Write, Bash, Glob, Grep +tools: Read, Write, Bash, Glob, Grep, Skill color: purple # hooks: # PostToolUse: @@ -209,6 +209,23 @@ Track coverage as you go. - New milestone: Start at 1 - Continuing milestone: Check existing phases, start at last + 1 +## Phase ID Convention + +Read `phase_id_convention` from config.json. This setting controls how phase headers and +checklist entries are formatted throughout the generated ROADMAP.md. + +| Convention | Summary checklist form | Detail header form | +|---|---|---| +| `sequential` (default) | `- [ ] **Phase 1: Name**` | `### Phase 1: Name` | +| `milestone-prefixed` | `- [ ] **Phase 1-01: Name**` | `### Phase 1-01: Name` | + +When `phase_id_convention` is absent or set to `"sequential"`, use plain sequential phase IDs +(e.g. `Phase 1`, `Phase 2`). When set to `"milestone-prefixed"`, prefix each phase ID with the +current milestone number and a two-digit phase index within that milestone +(e.g. `Phase 1-01`, `Phase 1-02`, `Phase 2-01`). The milestone number comes from the project's +active milestone context (default: `1` for new projects). This ensures downstream tools that +parse `### Phase N-NN:` headers for milestone-scoped workflows receive correctly prefixed IDs. + ## Granularity Calibration Read granularity from config.json. Granularity controls compression tolerance. @@ -310,14 +327,30 @@ After roadmap creation, REQUIREMENTS.md gets updated with phase mappings: ### 1. Summary Checklist (under `## Phases`) +Use the form matching `phase_id_convention` from config. + +**Sequential (default — when absent or `"sequential"`):** + ```markdown - [ ] **Phase 1: Name** - One-line description - [ ] **Phase 2: Name** - One-line description - [ ] **Phase 3: Name** - One-line description ``` +**Milestone-prefixed (when `phase_id_convention: "milestone-prefixed"`):** + +```markdown +- [ ] **Phase 1-01: Name** - One-line description +- [ ] **Phase 1-02: Name** - One-line description +- [ ] **Phase 1-03: Name** - One-line description +``` + ### 2. Detail Sections (under `## Phase Details`) +Use the header form matching `phase_id_convention` from config. + +**Sequential (default):** + ```markdown ### Phase 1: Name **Goal**: What this phase delivers @@ -334,7 +367,25 @@ After roadmap creation, REQUIREMENTS.md gets updated with phase mappings: ... ``` -**The `### Phase X:` headers are parsed by downstream tools.** If you only write the summary checklist, phase lookups will fail. +**Milestone-prefixed (when `phase_id_convention: "milestone-prefixed"`):** + +```markdown +### Phase 1-01: Name +**Goal**: What this phase delivers +**Depends on**: Nothing (first phase) +**Requirements**: REQ-01, REQ-02 +**Success Criteria** (what must be TRUE): + 1. Observable behavior from user perspective + 2. Observable behavior from user perspective +**Plans**: TBD + +### Phase 1-02: Name +**Goal**: What this phase delivers +**Depends on**: Phase 1-01 +... +``` + +**The `### Phase X:` headers are parsed by downstream tools.** If you only write the summary checklist, phase lookups will fail. Use the correct form for the configured convention so downstream parsing succeeds. ### UI Phase Detection @@ -476,6 +527,8 @@ Apply phase identification methodology: 2. Identify dependencies between groups 3. Create phases that complete coherent capabilities 4. Check granularity setting for compression guidance +5. Read `phase_id_convention` from config (`sequential` or `milestone-prefixed`); apply the + matching header and checklist form throughout all output sections ## Step 5: Derive Success Criteria diff --git a/agents/gsd-security-auditor.md b/agents/gsd-security-auditor.md index 209ce4683..e668a64b8 100644 --- a/agents/gsd-security-auditor.md +++ b/agents/gsd-security-auditor.md @@ -8,6 +8,7 @@ tools: - Bash - Glob - Grep + - Skill color: red --- diff --git a/agents/gsd-ui-auditor.md b/agents/gsd-ui-auditor.md index 3ed6cc9e6..f30d28a35 100644 --- a/agents/gsd-ui-auditor.md +++ b/agents/gsd-ui-auditor.md @@ -1,7 +1,7 @@ --- name: gsd-ui-auditor description: Retroactive 6-pillar visual audit of implemented frontend code. Produces scored UI-REVIEW.md. Spawned by /gsd:ui-review orchestrator. -tools: Read, Write, Bash, Grep, Glob +tools: Read, Write, Bash, Grep, Glob, Skill color: pink # hooks: # PostToolUse: diff --git a/agents/gsd-ui-checker.md b/agents/gsd-ui-checker.md index adb0e67a3..b59e4f85e 100644 --- a/agents/gsd-ui-checker.md +++ b/agents/gsd-ui-checker.md @@ -1,7 +1,7 @@ --- name: gsd-ui-checker description: Validates UI-SPEC.md design contracts against 6 quality dimensions. Produces BLOCK/FLAG/PASS verdicts. Spawned by /gsd:ui-phase orchestrator. -tools: Read, Bash, Glob, Grep +tools: Read, Bash, Glob, Grep, Skill color: cyan --- diff --git a/agents/gsd-ui-researcher.md b/agents/gsd-ui-researcher.md index 63a7d6f84..8a4e9263d 100644 --- a/agents/gsd-ui-researcher.md +++ b/agents/gsd-ui-researcher.md @@ -1,7 +1,7 @@ --- name: gsd-ui-researcher description: Produces UI-SPEC.md design contract for frontend phases. Reads upstream artifacts, detects design system state, asks only unanswered questions. Spawned by /gsd:ui-phase orchestrator. -tools: Read, Write, Edit, Bash, Grep, Glob, WebSearch, WebFetch, mcp__context7__*, mcp__firecrawl__*, mcp__exa__*, mcp__tavily__*, mcp__ref__*, mcp__jina__* +tools: Read, Write, Edit, Bash, Grep, Glob, Skill, WebSearch, WebFetch, mcp__context7__*, mcp__firecrawl__*, mcp__exa__*, mcp__tavily__*, mcp__ref__*, mcp__jina__* color: purple # hooks: # PostToolUse: @@ -286,7 +286,8 @@ This file is the canonical output of this agent. The orchestrator reads `$PHASE_ ## Step 6: Commit (optional) ```bash -gsd-tools query commit "docs($PHASE): UI design contract" --files "$PHASE_DIR/$PADDED_PHASE-UI-SPEC.md" +_GSD_SHIM_NAME="gsd-tools.cjs"; _GSD_RUNTIME_ROOT="${RUNTIME_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"; GSD_TOOLS="${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}"; if [ -f "$GSD_TOOLS" ]; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif command -v gsd-tools >/dev/null 2>&1; then GSD_TOOLS="$(command -v gsd-tools)"; gsd_run() { "$GSD_TOOLS" "$@"; }; elif [ -f "$HOME/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="$HOME/.claude/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; else echo "ERROR: gsd-tools.cjs not found at $GSD_TOOLS and gsd-tools is not on PATH. Run: npx -y @opengsd/gsd-core@latest --claude --local" >&2; exit 1; fi; if [ -n "${CLAUDE_ENV_FILE:-}" ] && [ -n "${GSD_TOOLS:-}" ]; then printf "export PATH='%s':\"\$PATH\"\n" "${GSD_TOOLS%/*}" >> "$CLAUDE_ENV_FILE" 2>/dev/null || true; fi +gsd_run query commit "docs($PHASE): UI design contract" --files "$PHASE_DIR/$PADDED_PHASE-UI-SPEC.md" ``` ## Step 7: Return Structured Result diff --git a/agents/gsd-verifier.md b/agents/gsd-verifier.md index 2b2641b4e..55a4c427c 100644 --- a/agents/gsd-verifier.md +++ b/agents/gsd-verifier.md @@ -1,7 +1,7 @@ --- name: gsd-verifier description: Verifies phase goal achievement through goal-backward analysis. Checks codebase delivers what phase promised, not just that tasks completed. Creates VERIFICATION.md report. -tools: Read, Write, Bash, Grep, Glob +tools: Read, Write, Bash, Grep, Glob, Skill color: green # hooks: # PostToolUse: @@ -85,7 +85,7 @@ cat "$PHASE_DIR"/*-VERIFICATION.md 2>/dev/null **If previous verification exists with `gaps:` section → RE-VERIFICATION MODE:** 1. Parse previous VERIFICATION.md frontmatter -2. Extract `must_haves` (truths, artifacts, key_links) +2. Extract `must_haves` (truths, artifacts, key_links, prohibitions) 3. Extract `gaps` (items that failed) 4. Set `is_re_verification = true` 5. **Skip to Step 3** with optimization: @@ -99,9 +99,10 @@ Set `is_re_verification = false`, proceed with Step 1. ## Step 1: Load Context (Initial Mode Only) ```bash +_GSD_SHIM_NAME="gsd-tools.cjs"; _GSD_RUNTIME_ROOT="${RUNTIME_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"; GSD_TOOLS="${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}"; if [ -f "$GSD_TOOLS" ]; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif command -v gsd-tools >/dev/null 2>&1; then GSD_TOOLS="$(command -v gsd-tools)"; gsd_run() { "$GSD_TOOLS" "$@"; }; elif [ -f "$HOME/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="$HOME/.claude/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; else echo "ERROR: gsd-tools.cjs not found at $GSD_TOOLS and gsd-tools is not on PATH. Run: npx -y @opengsd/gsd-core@latest --claude --local" >&2; exit 1; fi; if [ -n "${CLAUDE_ENV_FILE:-}" ] && [ -n "${GSD_TOOLS:-}" ]; then printf "export PATH='%s':\"\$PATH\"\n" "${GSD_TOOLS%/*}" >> "$CLAUDE_ENV_FILE" 2>/dev/null || true; fi ls "$PHASE_DIR"/*-PLAN.md 2>/dev/null ls "$PHASE_DIR"/*-SUMMARY.md 2>/dev/null -gsd-tools query roadmap.get-phase "$PHASE_NUM" +gsd_run query roadmap.get-phase "$PHASE_NUM" grep -E "^| $PHASE_NUM" .planning/REQUIREMENTS.md 2>/dev/null ``` @@ -114,7 +115,7 @@ In re-verification mode, must-haves come from Step 0. **Step 2a: Always load ROADMAP Success Criteria** ```bash -PHASE_DATA=$(gsd-tools query roadmap.get-phase "$PHASE_NUM" --raw) +PHASE_DATA=$(gsd_run query roadmap.get-phase "$PHASE_NUM" --raw) ``` Parse the `success_criteria` array from the JSON output. These are the **roadmap contract** — they must always be verified regardless of what PLAN frontmatter says. Store them as `roadmap_truths`. @@ -136,11 +137,22 @@ must_haves: - path: "src/components/Chat.tsx" provides: "Message list rendering" key_links: - - from: "Chat.tsx" - to: "api/chat" - via: "fetch in useEffect" + - from: "src/components/Chat.tsx" + to: "src/app/api/chat/route.ts" + via: "fetch in useEffect — calls /api/chat endpoint" + prohibitions: + - statement: "MUST NOT store raw SSN in plaintext" + status: "resolved" + verification: "judgment" ``` +**Also extract `must_haves.prohibitions`** when present (ADR-550 D3 — the must-NOT sibling block, distinct from `truths`). Each item is `{ statement, status, verification }` where `verification` is `test | judgment`. These are NEGATIVE checks: a verified prohibition means the must-NOT did NOT happen. Route them by verification tier in the verdict assembly (ADR-550 D4, the "B-with-guard" 2026-06-12 maintainer decision): + +- **judgment-tier prohibitions → mode-dependent soft-gate.** Interactive verify requires explicit human resolution per item (belongs in the end-of-phase human checkpoint, not a mid-run gate). Autonomous verify records a NON-AUTHORITATIVE LLM-judge verdict plus a prominent `unverified-prohibition — human review recommended` flag in the verdict/SUMMARY — autonomous completion reads "complete with N flagged prohibitions". NEVER a silent pass; NEVER a hard halt of an AFK run. +- **test-tier prohibitions → FAIL CLOSED (accept-and-flag, not reject-at-parse).** Accept the `verification: test` value (the SPEC↔must_haves.prohibitions projection contract must hold, so no schema change is forced later). But a well-formed test-tier item that reaches verify with NO wired enforcement is treated as UNVERIFIED — flagged exactly like an unresolved judgment item, NEVER green. The deterministic fail-closed default is `dispositionForProhibition()` in probe-core (status `unverified`, `flagged: true` when `enforcementEvidence` is empty). Do NOT wire a real fail-first negative-test hard gate here — that enforcement MECHANISM defers to a follow-up PR (it needs a real test-tier consumer to `regression-must-fail-first` against; #644's corpus is entirely judgment-tier). + +A flagged prohibition counts as a human-verification item (status `human_needed`) or a gap (status `gaps_found`) per the existing decision tree — it must never be silently absorbed into a `passed` verdict. + **Step 2c: Merge must-haves** Combine all sources into a single must-haves list: @@ -168,21 +180,28 @@ For each truth, determine if codebase enables it. **Verification status:** -- ✓ VERIFIED: All supporting artifacts pass all checks +- ✓ VERIFIED: All supporting artifacts pass all checks — and, for a behavior-dependent truth, a behavioral test exercises the asserted behavior (see below) +- ⚠️ PRESENT_BEHAVIOR_UNVERIFIED: Supporting artifacts are present and wired, but the truth asserts runtime behavior that no test exercises — present, not behaviorally proven. Routes to human verification (Step 8) and does NOT count toward the verified score (Step 9). - ✗ FAILED: One or more artifacts missing, stub, or unwired - ? UNCERTAIN: Can't verify programmatically (needs human) +**Behavior-dependent truths.** A truth is *behavior-dependent* when its correctness hinges on runtime behavior grep/presence checks cannot see — a **state transition** or a **cancellation / cleanup / ordering invariant** (e.g. "cancels the in-flight task and bumps the generation counter", "resets the busy flag on abort", "rolls back on failure"). For these, symbol presence + wiring is *necessary but not sufficient*: the code can be present and wired yet still leak state on the very path the invariant covers. + For each truth: 1. Identify supporting artifacts 2. Check artifact status (Step 4) 3. Check wiring status (Step 5) -4. **Before marking FAIL:** Check for override (Step 3b) -5. Determine truth status +4. **Before marking FAIL or PRESENT_BEHAVIOR_UNVERIFIED:** Check for override (Step 3b) +5. **Classify behavior-dependence.** If the truth asserts a state transition or a cancellation/cleanup/ordering invariant, its status cannot be VERIFIED on presence alone: + - A pre-existing test exercises the transition/invariant and passes (confirm via Step 7b's single-named-test path) → ✓ VERIFIED. + - No such test exists, or it can't run without a server/state mutation → ⚠️ PRESENT_BEHAVIOR_UNVERIFIED. Emit a human-verification item (Step 8) and do not count it toward the verified score (Step 9). + - An accepted override (Step 3b) carries the truth as PASSED (override), exactly as it does for a FAILED truth. +6. Determine truth status ## Step 3b: Check Verification Overrides -Before marking any must-have as FAILED, check the VERIFICATION.md frontmatter for an `overrides:` entry that matches this must-have. +Before marking any must-have as FAILED or ⚠️ PRESENT_BEHAVIOR_UNVERIFIED, check the VERIFICATION.md frontmatter for an `overrides:` entry that matches this must-have. **Override check procedure:** @@ -192,12 +211,12 @@ Before marking any must-have as FAILED, check the VERIFICATION.md frontmatter fo 4. Key technical terms (file paths, component names, API endpoints) have higher weight **If override found:** -- Mark as `PASSED (override)` instead of FAIL +- Mark as `PASSED (override)` instead of FAIL/PRESENT_BEHAVIOR_UNVERIFIED - Evidence: `Override: {reason} — accepted by {accepted_by} on {accepted_at}` -- Count toward passing score, not failing score +- Count toward passing score (`verified_truths`), not failing score **If no override found:** -- Mark as FAILED as normal +- Mark as FAILED (or ⚠️ PRESENT_BEHAVIOR_UNVERIFIED, per Step 3 step 5) as normal - Consider suggesting an override if the failure looks intentional (alternative implementation exists) **Suggesting overrides:** When a must-have FAILs but evidence shows an alternative implementation that achieves the same intent, include an override suggestion in the report: @@ -219,7 +238,7 @@ overrides: Use `gsd-tools query` for artifact verification against must_haves in PLAN frontmatter: ```bash -ARTIFACT_RESULT=$(gsd-tools query verify.artifacts "$PLAN_PATH") +ARTIFACT_RESULT=$(gsd_run query verify.artifacts "$PLAN_PATH") ``` Parse JSON result: `{ all_passed, passed, total, artifacts: [{path, exists, issues, passed}] }` @@ -325,7 +344,7 @@ Key links are critical connections. If broken, the goal fails even with all arti Use `gsd-tools query` for key link verification against must_haves in PLAN frontmatter: ```bash -LINKS_RESULT=$(gsd-tools query verify.key-links "$PLAN_PATH") +LINKS_RESULT=$(gsd_run query verify.key-links "$PLAN_PATH") ``` Parse JSON result: `{ all_verified, verified, total, links: [{from, to, via, verified, detail}] }` @@ -407,12 +426,12 @@ Identify files modified in this phase from SUMMARY.md key-files section, or extr ```bash # Option 1: Extract from SUMMARY frontmatter -SUMMARY_FILES=$(gsd-tools query summary-extract "$PHASE_DIR"/*-SUMMARY.md --fields key-files) +SUMMARY_FILES=$(gsd_run query summary-extract "$PHASE_DIR"/*-SUMMARY.md --fields key-files) # Option 2: Verify commits exist (if commit hashes documented) COMMIT_HASHES=$(grep -oE "[a-f0-9]{7,40}" "$PHASE_DIR"/*-SUMMARY.md | head -10) if [ -n "$COMMIT_HASHES" ]; then - COMMITS_VALID=$(gsd-tools query verify.commits $COMMIT_HASHES) + COMMITS_VALID=$(gsd_run query verify.commits $COMMIT_HASHES) fi # Fallback: grep for files @@ -449,6 +468,8 @@ Anti-pattern scanning (Step 7) checks for code smells. Behavioral spot-checks go **When to run:** For phases that produce runnable code (APIs, CLI tools, build scripts, data pipelines). Skip for documentation-only or config-only phases. +**Behavioral evidence for behavior-dependent truths (Step 3).** When a truth asserts a state transition or a cancellation/cleanup/ordering invariant, the single named test below is what upgrades it from ⚠️ PRESENT_BEHAVIOR_UNVERIFIED to ✓ VERIFIED. Run only the one named test that exercises the transition/invariant — never the full suite (per #25/#753). If no such test exists, leave the truth ⚠️ PRESENT_BEHAVIOR_UNVERIFIED and route it to human verification (Step 8); do not mark it VERIFIED on presence. + **How:** 1. **Identify checkable behaviors** from must-haves truths. Select 2-4 that can be tested with a single command: @@ -536,6 +557,8 @@ done **Needs human if uncertain:** Complex wiring grep can't trace, dynamic state behavior, edge cases. +**Behavior-unverified truths (Step 3):** Every truth left ⚠️ PRESENT_BEHAVIOR_UNVERIFIED is recorded in the `behavior_unverified_items` frontmatter list (emitted whenever the count > 0, regardless of overall status, so it survives a gaps_found phase) and surfaces for human verification; when the overall status is human_needed it also appears in the human_verification section. Phrase each item around the invariant: what to trigger, what state must hold afterward, and why presence checks can't see it. + **Harvest deferred items from PLAN.md (#3309 / `workflow.human_verify_mode = end-of-phase`):** Scan every PLAN file in the phase for `` blocks on `auto` tasks. These are verification items the planner deliberately deferred from `checkpoint:human-verify` to end-of-phase to avoid the executor cold-start cost. Each block has the same shape used by the planner: ```xml @@ -567,18 +590,30 @@ Classify status using this decision tree IN ORDER (most restrictive first): 1. IF any truth FAILED, artifact MISSING/STUB, key link NOT_WIRED, or blocker anti-pattern found: → **status: gaps_found** -2. IF Step 8 produced ANY human verification items (section is non-empty): +2. IF Step 8 produced ANY human verification items (section is non-empty) — this includes every ⚠️ PRESENT_BEHAVIOR_UNVERIFIED truth from Step 3: → **status: human_needed** - (Even if all truths are VERIFIED and score is N/N — human items take priority) + (Even if all other truths are VERIFIED — human items take priority) 3. IF all truths VERIFIED, all artifacts pass, all links WIRED, no blockers, AND no human verification items: → **status: passed** -**passed is ONLY valid when the human verification section is empty.** If you identified items requiring human testing in Step 8, status MUST be human_needed. +**passed is ONLY valid when the human verification section is empty.** If Step 8 produced any items — including any truth left ⚠️ PRESENT_BEHAVIOR_UNVERIFIED — the status is not `passed`: it is `human_needed`, or `gaps_found` when rule 1 also fires (the ordered tree keeps gaps_found's precedence). + +**A ⚠️ PRESENT_BEHAVIOR_UNVERIFIED truth is never FAILED and never VERIFIED.** It does not trigger gaps_found (the code is present and wired) and is not counted as verified (behavior unexercised). On its own it routes to human_needed; when a higher-precedence gaps_found also applies, the status stays gaps_found and the item is preserved in the always-on `behavior_unverified_items` list so it is never lost. Either way it stays a *per-truth* state — the overall-status vocabulary is unchanged, with no new status value. > **Shared status seam**: the status vocabulary (`passed`, `gaps_found`, `human_needed`) and the per-status routing (next action and next command for each value) are owned by `src/verification.cts` via `gsd_run query verification.status`. This agent is the single emitter of the frontmatter status field; consumers (ship.md, execute-phase.md) read routing from that query instead of re-deriving it. -**Score:** `verified_truths / total_truths` +**Score (presence- vs behavior-verified split):** + +- `verified_truths` counts ✓ VERIFIED truths plus PASSED (override) truths (Step 3b). For a behavior-dependent truth, VERIFIED means a behavioral test passed, not just that symbols are present. +- ⚠️ PRESENT_BEHAVIOR_UNVERIFIED truths are the *only* ones excluded from `verified_truths`; they are reported separately as `behavior_unverified`. + +```text +score: verified_truths / total_truths # e.g. 6/7 +behavior_unverified: P # truths present + wired but behavior not exercised +``` + +A headline N/N therefore certifies that every behavior-dependent truth had behavioral evidence — a clean score can no longer be reached on symbol presence alone. ## Step 9b: Filter Deferred Items @@ -587,7 +622,7 @@ Before reporting gaps, check if any identified gaps are explicitly addressed in **Load the full milestone roadmap:** ```bash -ROADMAP_DATA=$(gsd-tools query roadmap.analyze --raw) +ROADMAP_DATA=$(gsd_run query roadmap.analyze --raw) ``` Parse the JSON to extract all phases. Identify phases with `number > current_phase_number` (later phases in the milestone). For each later phase, extract its `goal` and `success_criteria`. @@ -662,7 +697,7 @@ Deferred items are informational only — they do not require closure plans. **User Story format guard:** Apply via the centralized verb instead of inlining the regex: ```bash -USER_STORY_VALID=$(gsd-tools query user-story.validate --story "$PHASE_GOAL" --pick valid) +USER_STORY_VALID=$(gsd_run query user-story.validate --story "$PHASE_GOAL" --pick valid) ``` If `valid != true`, refuse to verify. Surface the discrepancy and ask the user to run `/gsd mvp-phase ${PHASE}` to set a proper User Story goal. The verb owns the canonical regex `/^As a .+, I want to .+, so that .+\.$/` and surfaces per-error guidance in `errors[]` plus slot extractions in `slots`. Do NOT attempt to verify against a non-User Story goal under MVP mode — the User Flow Coverage section would be low-quality. @@ -687,6 +722,7 @@ phase: XX-name verified: YYYY-MM-DDTHH:MM:SSZ status: passed | gaps_found | human_needed score: N/M must-haves verified +behavior_unverified: 0 # Count of ⚠️ PRESENT_BEHAVIOR_UNVERIFIED truths (present + wired, behavior not exercised); each is detailed in behavior_unverified_items below (and in human_verification when status is human_needed) overrides_applied: 0 # Count of PASSED (override) items included in score overrides: # Only if overrides exist — carried forward or newly added - must_have: "Must-have text that was overridden" @@ -713,6 +749,11 @@ deferred: # Only if deferred items exist (Step 9b) - truth: "Observable truth addressed in a later phase" addressed_in: "Phase N" evidence: "Matching goal or success criteria text" +behavior_unverified_items: # Only if behavior_unverified > 0 — emitted regardless of overall status, so these survive a gaps_found phase + - truth: "Observable truth whose state transition or cancellation/cleanup/ordering invariant no test exercises" + test: "What to trigger" + expected: "What state must hold afterward" + why_human: "Why presence checks can't see it" human_verification: # Only if status: human_needed - test: "What to do" expected: "What should happen" @@ -734,8 +775,9 @@ human_verification: # Only if status: human_needed | --- | ------- | ---------- | -------------- | | 1 | {truth} | ✓ VERIFIED | {evidence} | | 2 | {truth} | ✗ FAILED | {what's wrong} | +| 3 | {truth} | ⚠️ PRESENT_BEHAVIOR_UNVERIFIED | {present + wired; no test exercises the transition/invariant — see Human Verification} | -**Score:** {N}/{M} truths verified +**Score:** {N}/{M} truths verified ({P} present, behavior-unverified) ### Deferred Items @@ -822,7 +864,7 @@ Structured gaps in VERIFICATION.md frontmatter for `/gsd:plan-phase --gaps`. {If human_needed:} ### Human Verification Required -{N} items need human testing: +{N} items need human testing (including {P} present-but-behavior-unverified truths — code wired, transition/invariant not exercised by a test): 1. **{Test name}** — {what to do} - Expected: {what should happen} @@ -845,6 +887,8 @@ Automated checks passed. Awaiting human verification. **Keep verification fast.** Use grep/file checks, not running the app. +**Presence is not behavior.** Grep/file checks prove a symbol is present and wired — they do not prove a state transition or a cancellation/cleanup/ordering invariant holds at runtime. For a behavior-dependent truth, require a passing behavioral test (Step 7b's single named test) or mark it ⚠️ PRESENT_BEHAVIOR_UNVERIFIED and route to human verification. Never let symbol presence alone produce a VERIFIED on a behavior-dependent truth. + **DO NOT commit.** Leave committing to the orchestrator. diff --git a/bin/install.js b/bin/install.js index 751b4d950..8b982f426 100755 --- a/bin/install.js +++ b/bin/install.js @@ -26,17 +26,30 @@ const { // fs.readdirSync + RegExp work for every skill. const { transformContentToHyphen, - readCmdNames: readGsdCommandNames, -} = require(path.join(__dirname, '..', 'scripts', 'fix-slash-commands.cjs')); + readGsdCommandNames, +} = require('../gsd-core/bin/lib/command-roster.cjs'); const { resolveAntigravityGlobalDir, getGlobalConfigDir, + getGlobalSkillsBase, } = require('../gsd-core/bin/lib/runtime-homes.cjs'); const { applyWorktreeBaseRef, readBaseRefFromSettings, } = require('../gsd-core/bin/lib/worktree-base-ref.cjs'); -const { resolveRuntimeConfigIntent } = require('../gsd-core/bin/lib/runtime-config-adapter-registry.cjs'); +const { resolveInstallPlan } = require('../gsd-core/bin/lib/runtime-config-adapter-registry.cjs'); +const runtimeArtifactConversion = require('../gsd-core/bin/lib/runtime-artifact-conversion.cjs'); +// Canonical set of hook files shipped to users. Imported here so writeManifest() +// records exactly the same set that build-hooks.js copies to hooks/dist/, making +// the manifest and the installed hooks/ dir structurally identical. Avoids the +// prefix/extension-regex approach that missed managed-hooks-registry.cjs (#941). +const { HOOKS_TO_COPY: _HOOKS_TO_COPY } = require('../scripts/build-hooks.js'); +const INSTALLED_HOOK_FILES = new Set(_HOOKS_TO_COPY); + +// ADR-857 phase 5f-1: hook-surface writer functions extracted to a dedicated module. +// bin/install.js re-exports everything from hooksSurface so existing callers +// (require('../bin/install.js').writeCursorHooksJson etc.) continue to work. +const hooksSurface = require('../gsd-core/bin/lib/runtime-hooks-surface.cjs'); /** * Runtimes that register hyphen-form `name:` per #2808 AND copy agent bodies @@ -252,9 +265,11 @@ const _gsdLibDir = path.join(__dirname, '..', 'gsd-core', 'bin', 'lib'); const { MODEL_PROFILES: GSD_MODEL_PROFILES } = require(path.join(_gsdLibDir, 'model-profiles.cjs')); const { RUNTIME_PROFILE_MAP: GSD_RUNTIME_PROFILE_MAP, +} = require(path.join(_gsdLibDir, 'model-catalog.cjs')); +const { resolveTierEntry: gsdResolveTierEntry, EFFORT_SET: GSD_EFFORT_SET, -} = require(path.join(_gsdLibDir, 'core.cjs')); +} = require(path.join(_gsdLibDir, 'model-resolver.cjs')); // #443 — model-catalog and config-defaults.manifest.json exports needed only // by effort-resolution code paths (resolveInstallTimeEffort / @@ -323,6 +338,13 @@ const { stageAgentsForProfile, stageSkillsForRuntimeAsSkills, } = require(path.join(_gsdLibDir, 'install-profiles.cjs')); +// ADR-857 phase 4c: load capability registry (optional; missing → falls back to undefined) +let _capabilityRegistry; +try { + _capabilityRegistry = require(path.join(_gsdLibDir, 'capability-registry.cjs')); +} catch (_) { + _capabilityRegistry = undefined; +} const { applyInstallerMigrationPlan, discoverInstallerMigrations, @@ -348,23 +370,6 @@ const { const args = process.argv.slice(2); const hasGlobal = args.includes('--global') || args.includes('-g'); const hasLocal = args.includes('--local') || args.includes('-l'); -const hasOpencode = args.includes('--opencode'); -const hasClaude = args.includes('--claude'); -const hasGemini = args.includes('--gemini'); -const hasKilo = args.includes('--kilo'); -const hasCodex = args.includes('--codex'); -const hasCopilot = args.includes('--copilot'); -const hasAntigravity = args.includes('--antigravity'); -const hasCursor = args.includes('--cursor'); -const hasWindsurf = args.includes('--windsurf'); -const hasAugment = args.includes('--augment'); -const hasTrae = args.includes('--trae'); -const hasQwen = args.includes('--qwen'); -const hasHermes = args.includes('--hermes'); -const hasCodebuddy = args.includes('--codebuddy'); -const hasCline = args.includes('--cline'); -const hasBoth = args.includes('--both'); // Legacy flag, keeps working -const hasAll = args.includes('--all'); const hasUninstall = args.includes('--uninstall') || args.includes('-u'); const hasSkillsRoot = args.includes('--skills-root'); const hasPortableHooks = args.includes('--portable-hooks') || process.env.GSD_PORTABLE_HOOKS === '1'; @@ -391,30 +396,37 @@ if (hasMinimal && _profileArgRaw) { process.exit(1); } -// Runtime selection - can be set by flags or interactive prompt -let selectedRuntimes = []; -if (hasAll) { - selectedRuntimes = ['claude', 'kilo', 'opencode', 'gemini', 'codex', 'copilot', 'antigravity', 'cursor', 'windsurf', 'augment', 'trae', 'qwen', 'hermes', 'codebuddy', 'cline']; -} else if (hasBoth) { - selectedRuntimes = ['claude', 'opencode']; -} else { - if (hasClaude) selectedRuntimes.push('claude'); - if (hasOpencode) selectedRuntimes.push('opencode'); - if (hasGemini) selectedRuntimes.push('gemini'); - if (hasKilo) selectedRuntimes.push('kilo'); - if (hasCodex) selectedRuntimes.push('codex'); - if (hasCopilot) selectedRuntimes.push('copilot'); - if (hasAntigravity) selectedRuntimes.push('antigravity'); - if (hasCursor) selectedRuntimes.push('cursor'); - if (hasWindsurf) selectedRuntimes.push('windsurf'); - if (hasAugment) selectedRuntimes.push('augment'); - if (hasTrae) selectedRuntimes.push('trae'); - if (hasQwen) selectedRuntimes.push('qwen'); - if (hasHermes) selectedRuntimes.push('hermes'); - if (hasCodebuddy) selectedRuntimes.push('codebuddy'); - if (hasCline) selectedRuntimes.push('cline'); +function selectRuntimesFromArgs(runtimeArgs) { + if (runtimeArgs.includes('--all')) { + return ['claude', 'kimi', 'kilo', 'opencode', 'gemini', 'codex', 'copilot', 'antigravity', 'cursor', 'windsurf', 'augment', 'trae', 'qwen', 'hermes', 'codebuddy', 'cline']; + } + if (runtimeArgs.includes('--both')) { + return ['claude', 'opencode']; + } + + const selected = []; + if (runtimeArgs.includes('--claude')) selected.push('claude'); + if (runtimeArgs.includes('--opencode')) selected.push('opencode'); + if (runtimeArgs.includes('--gemini')) selected.push('gemini'); + if (runtimeArgs.includes('--kilo')) selected.push('kilo'); + if (runtimeArgs.includes('--codex')) selected.push('codex'); + if (runtimeArgs.includes('--copilot')) selected.push('copilot'); + if (runtimeArgs.includes('--antigravity')) selected.push('antigravity'); + if (runtimeArgs.includes('--cursor')) selected.push('cursor'); + if (runtimeArgs.includes('--windsurf') || runtimeArgs.includes('--devin-desktop')) selected.push('windsurf'); + if (runtimeArgs.includes('--augment')) selected.push('augment'); + if (runtimeArgs.includes('--trae')) selected.push('trae'); + if (runtimeArgs.includes('--qwen')) selected.push('qwen'); + if (runtimeArgs.includes('--hermes')) selected.push('hermes'); + if (runtimeArgs.includes('--kimi')) selected.push('kimi'); + if (runtimeArgs.includes('--codebuddy')) selected.push('codebuddy'); + if (runtimeArgs.includes('--cline')) selected.push('cline'); + return selected; } +// Runtime selection - can be set by flags or interactive prompt +let selectedRuntimes = selectRuntimesFromArgs(args); + // WSL + Windows Node.js detection // When Windows-native Node runs on WSL, os.homedir() and path.join() produce // backslash paths that don't resolve correctly on the Linux filesystem. @@ -456,13 +468,14 @@ function getDirName(runtime) { if (runtime === 'gemini') return '.gemini'; if (runtime === 'kilo') return '.kilo'; if (runtime === 'codex') return '.codex'; - if (runtime === 'antigravity') return '.agent'; + if (runtime === 'antigravity') return '.agents'; if (runtime === 'cursor') return '.cursor'; - if (runtime === 'windsurf') return '.windsurf'; + if (runtime === 'windsurf') return '.devin'; if (runtime === 'augment') return '.augment'; if (runtime === 'trae') return '.trae'; if (runtime === 'qwen') return '.qwen'; if (runtime === 'hermes') return '.hermes'; + if (runtime === 'kimi') return '.kimi-code'; if (runtime === 'codebuddy') return '.codebuddy'; if (runtime === 'cline') return '.cline'; return '.claude'; @@ -490,7 +503,7 @@ function getConfigDirFromHome(runtime, isGlobal) { if (runtime === 'kilo') return "'.config', 'kilo'"; if (runtime === 'codex') return "'.codex'"; if (runtime === 'antigravity') { - if (!isGlobal) return "'.agent'"; + if (!isGlobal) return "'.agents'"; const antigravityDir = resolveAntigravityGlobalDir(); const rel = path.relative(os.homedir(), antigravityDir); const segments = rel.split(path.sep).filter(Boolean); @@ -509,9 +522,17 @@ function getConfigDirFromHome(runtime, isGlobal) { if (runtime === 'hermes') return "'.hermes'"; if (runtime === 'codebuddy') return "'.codebuddy'"; if (runtime === 'cline') return "'.cline'"; + if (runtime === 'kimi') return "'.config', 'agents'"; return "'.claude'"; } +/** + * Compatibility seam for tests and older installer consumers. + * Runtime home resolution now lives in runtime-homes.cjs. + */ +function getGlobalDir(runtime, explicitDir = null) { + return getGlobalConfigDir(runtime, explicitDir); +} const banner = '\n' + cyan + ' ██████╗ ███████╗██████╗\n' + ' ██╔════╝ ██╔════╝██╔══██╗\n' + @@ -523,7 +544,7 @@ const banner = '\n' + ' GSD Core ' + dim + 'v' + pkg.version + reset + '\n' + ' Git. Ship. Done.\n' + ' A meta-prompting, context engineering and spec-driven\n' + - ' development workflows for Claude Code, OpenCode, Gemini, Kilo, Codex, Copilot, Antigravity, Cursor, Windsurf, Augment, Trae, Qwen Code, Hermes Agent, Cline and CodeBuddy.\n'; + ' development workflows for Claude Code, OpenCode, Gemini, Kimi CLI, Kilo, Codex, Copilot, Antigravity, Cursor, Windsurf, Augment, Trae, Qwen Code, Hermes Agent, Cline and CodeBuddy.\n'; // Pure seam: parse --config-dir / -c from an arbitrary args array. // Returns the path string, '' for an empty equals-form value, or null when the @@ -580,7 +601,7 @@ if (hasUninstall) { // Show help if requested if (hasHelp) { - console.log(` ${yellow}Usage:${reset} npx ${pkg.name} [options]\n\n ${yellow}Options:${reset}\n ${cyan}-g, --global${reset} Install globally (to config directory)\n ${cyan}-l, --local${reset} Install locally (to current directory)\n ${cyan}--claude${reset} Install for Claude Code only\n ${cyan}--opencode${reset} Install for OpenCode only\n ${cyan}--gemini${reset} Install for Gemini only\n ${cyan}--kilo${reset} Install for Kilo only\n ${cyan}--codex${reset} Install for Codex only\n ${cyan}--copilot${reset} Install for Copilot only\n ${cyan}--antigravity${reset} Install for Antigravity only\n ${cyan}--cursor${reset} Install for Cursor only\n ${cyan}--windsurf${reset} Install for Windsurf only\n ${cyan}--augment${reset} Install for Augment only\n ${cyan}--trae${reset} Install for Trae only\n ${cyan}--qwen${reset} Install for Qwen Code only\n ${cyan}--hermes${reset} Install for Hermes Agent only\n ${cyan}--cline${reset} Install for Cline only\n ${cyan}--codebuddy${reset} Install for CodeBuddy only\n ${cyan}--all${reset} Install for all runtimes\n ${cyan}-u, --uninstall${reset} Uninstall GSD (remove all GSD files)\n ${cyan}-c, --config-dir ${reset} Specify custom config directory\n ${cyan}-h, --help${reset} Show this help message\n ${cyan}--force-statusline${reset} Replace existing statusline config\n ${cyan}--portable-hooks${reset} Emit \$HOME-relative hook paths in settings.json\n (for WSL/Docker bind-mount setups; also GSD_PORTABLE_HOOKS=1)\n ${cyan}--profile=${reset} Install a named skill profile. Profiles:\n core — ${PROFILES.core.length} main-loop skills incl. phase (~130 desc tokens)\n standard — ${PROFILES.standard.length} skills incl. phase, review, config (~700)\n full — all skills (default)\n Composable: --profile=core,audit installs union of closures.\n Profile is persisted and respected by \`gsd update\`.\n ${cyan}--minimal${reset} Alias for --profile=core (back-compat).\n Cuts cold-start overhead from ~12k tokens to ~700.\n Alias: --core-only.\n\n ${yellow}Examples:${reset}\n ${dim}# Interactive install (prompts for runtime and location)${reset}\n npx ${pkg.name}\n\n ${dim}# Install for Claude Code globally${reset}\n npx ${pkg.name} --claude --global\n\n ${dim}# Install for Gemini globally${reset}\n npx ${pkg.name} --gemini --global\n\n ${dim}# Install for Kilo globally${reset}\n npx ${pkg.name} --kilo --global\n\n ${dim}# Install for Codex globally${reset}\n npx ${pkg.name} --codex --global\n\n ${dim}# Install for Copilot globally${reset}\n npx ${pkg.name} --copilot --global\n\n ${dim}# Install for Copilot locally${reset}\n npx ${pkg.name} --copilot --local\n\n ${dim}# Install for Antigravity globally${reset}\n npx ${pkg.name} --antigravity --global\n\n ${dim}# Install for Antigravity locally${reset}\n npx ${pkg.name} --antigravity --local\n\n ${dim}# Install for Cursor globally${reset}\n npx ${pkg.name} --cursor --global\n\n ${dim}# Install for Cursor locally${reset}\n npx ${pkg.name} --cursor --local\n\n ${dim}# Install for Windsurf globally${reset}\n npx ${pkg.name} --windsurf --global\n\n ${dim}# Install for Windsurf locally${reset}\n npx ${pkg.name} --windsurf --local\n\n ${dim}# Install for Augment globally${reset}\n npx ${pkg.name} --augment --global\n\n ${dim}# Install for Augment locally${reset}\n npx ${pkg.name} --augment --local\n\n ${dim}# Install for Trae globally${reset}\n npx ${pkg.name} --trae --global\n\n ${dim}# Install for Trae locally${reset}\n npx ${pkg.name} --trae --local\n\n ${dim}# Install for Hermes Agent globally${reset}\n npx ${pkg.name} --hermes --global\n\n ${dim}# Install for Hermes Agent locally${reset}\n npx ${pkg.name} --hermes --local\n\n ${dim}# Install for Cline globally${reset}\n npx ${pkg.name} --cline --global\n\n ${dim}# Install for Cline locally${reset}\n npx ${pkg.name} --cline --local\n\n ${dim}# Install for CodeBuddy globally${reset}\n npx ${pkg.name} --codebuddy --global\n\n ${dim}# Install for CodeBuddy locally${reset}\n npx ${pkg.name} --codebuddy --local\n\n ${dim}# Install for all runtimes globally${reset}\n npx ${pkg.name} --all --global\n\n ${dim}# Install to custom config directory${reset}\n npx ${pkg.name} --kilo --global --config-dir ~/.kilo-work\n\n ${dim}# Install to current project only${reset}\n npx ${pkg.name} --claude --local\n\n ${dim}# Uninstall GSD from Cursor globally${reset}\n npx ${pkg.name} --cursor --global --uninstall\n\n ${yellow}Notes:${reset}\n The --config-dir option is useful when you have multiple configurations.\n It takes priority over CLAUDE_CONFIG_DIR / OPENCODE_CONFIG_DIR / GEMINI_CONFIG_DIR / KILO_CONFIG_DIR / CODEX_HOME / COPILOT_CONFIG_DIR / COPILOT_HOME / ANTIGRAVITY_CONFIG_DIR / CURSOR_CONFIG_DIR / WINDSURF_CONFIG_DIR / AUGMENT_CONFIG_DIR / TRAE_CONFIG_DIR / QWEN_CONFIG_DIR / HERMES_HOME / CLINE_CONFIG_DIR / CODEBUDDY_CONFIG_DIR environment variables.\n`); + console.log(` ${yellow}Usage:${reset} npx ${pkg.name} [options]\n\n ${yellow}Options:${reset}\n ${cyan}-g, --global${reset} Install globally (to config directory)\n ${cyan}-l, --local${reset} Install locally (to current directory)\n ${cyan}--claude${reset} Install for Claude Code only\n ${cyan}--opencode${reset} Install for OpenCode only\n ${cyan}--gemini${reset} Install for Gemini only\n ${cyan}--kilo${reset} Install for Kilo only\n ${cyan}--codex${reset} Install for Codex only\n ${cyan}--kimi${reset} Install for Kimi CLI only\n ${cyan}--copilot${reset} Install for Copilot only\n ${cyan}--antigravity${reset} Install for Antigravity only\n ${cyan}--cursor${reset} Install for Cursor only\n ${cyan}--windsurf${reset} Install for Windsurf only\n ${cyan}--augment${reset} Install for Augment only\n ${cyan}--trae${reset} Install for Trae only\n ${cyan}--qwen${reset} Install for Qwen Code only\n ${cyan}--hermes${reset} Install for Hermes Agent only\n ${cyan}--cline${reset} Install for Cline only\n ${cyan}--codebuddy${reset} Install for CodeBuddy only\n ${cyan}--all${reset} Install for all runtimes\n ${cyan}-u, --uninstall${reset} Uninstall GSD (remove all GSD files)\n ${cyan}-c, --config-dir ${reset} Specify custom config directory\n ${cyan}-h, --help${reset} Show this help message\n ${cyan}--force-statusline${reset} Replace existing statusline config\n ${cyan}--portable-hooks${reset} Emit \$HOME-relative hook paths in settings.json\n (for WSL/Docker bind-mount setups; also GSD_PORTABLE_HOOKS=1)\n ${cyan}--profile=${reset} Install a named skill profile. Profiles:\n core — ${PROFILES.core.length} main-loop skills incl. phase (~130 desc tokens)\n standard — ${PROFILES.standard.length} skills incl. phase, review, config (~700)\n full — all skills (default)\n Composable: --profile=core,audit installs union of closures.\n Profile is persisted and respected by \`gsd update\`.\n ${cyan}--minimal${reset} Alias for --profile=core (back-compat).\n Cuts cold-start overhead from ~12k tokens to ~700.\n Alias: --core-only.\n\n ${yellow}Examples:${reset}\n ${dim}# Interactive install (prompts for runtime and location)${reset}\n npx ${pkg.name}\n\n ${dim}# Install for Claude Code globally${reset}\n npx ${pkg.name} --claude --global\n\n ${dim}# Install for Gemini globally${reset}\n npx ${pkg.name} --gemini --global\n\n ${dim}# Install for Kilo globally${reset}\n npx ${pkg.name} --kilo --global\n\n ${dim}# Install for Codex globally${reset}\n npx ${pkg.name} --codex --global\n\n ${dim}# Install for Kimi CLI globally${reset}\n npx ${pkg.name} --kimi --global\n\n ${dim}# Install for Kimi CLI under ~/.kimi-code${reset}\n npx ${pkg.name} --kimi --global --config-dir ~/.kimi-code\n\n ${dim}# Install for Copilot globally${reset}\n npx ${pkg.name} --copilot --global\n\n ${dim}# Install for Copilot locally${reset}\n npx ${pkg.name} --copilot --local\n\n ${dim}# Install for Antigravity globally${reset}\n npx ${pkg.name} --antigravity --global\n\n ${dim}# Install for Antigravity locally${reset}\n npx ${pkg.name} --antigravity --local\n\n ${dim}# Install for Cursor globally${reset}\n npx ${pkg.name} --cursor --global\n\n ${dim}# Install for Cursor locally${reset}\n npx ${pkg.name} --cursor --local\n\n ${dim}# Install for Windsurf globally${reset}\n npx ${pkg.name} --windsurf --global\n\n ${dim}# Install for Windsurf locally${reset}\n npx ${pkg.name} --windsurf --local\n\n ${dim}# Install for Augment globally${reset}\n npx ${pkg.name} --augment --global\n\n ${dim}# Install for Augment locally${reset}\n npx ${pkg.name} --augment --local\n\n ${dim}# Install for Trae globally${reset}\n npx ${pkg.name} --trae --global\n\n ${dim}# Install for Trae locally${reset}\n npx ${pkg.name} --trae --local\n\n ${dim}# Install for Hermes Agent globally${reset}\n npx ${pkg.name} --hermes --global\n\n ${dim}# Install for Hermes Agent locally${reset}\n npx ${pkg.name} --hermes --local\n\n ${dim}# Install for Cline globally${reset}\n npx ${pkg.name} --cline --global\n\n ${dim}# Install for Cline locally${reset}\n npx ${pkg.name} --cline --local\n\n ${dim}# Install for CodeBuddy globally${reset}\n npx ${pkg.name} --codebuddy --global\n\n ${dim}# Install for CodeBuddy locally${reset}\n npx ${pkg.name} --codebuddy --local\n\n ${dim}# Install for all runtimes globally${reset}\n npx ${pkg.name} --all --global\n\n ${dim}# Install to custom config directory${reset}\n npx ${pkg.name} --kilo --global --config-dir ~/.kilo-work\n\n ${dim}# Install to current project only${reset}\n npx ${pkg.name} --claude --local\n\n ${dim}# Uninstall GSD from Cursor globally${reset}\n npx ${pkg.name} --cursor --global --uninstall\n\n ${yellow}Notes:${reset}\n The --config-dir option is useful when you have multiple configurations.\n It takes priority over CLAUDE_CONFIG_DIR / OPENCODE_CONFIG_DIR / GEMINI_CONFIG_DIR / KILO_CONFIG_DIR / CODEX_HOME / KIMI_CONFIG_DIR / COPILOT_CONFIG_DIR / COPILOT_HOME / ANTIGRAVITY_CONFIG_DIR / CURSOR_CONFIG_DIR / WINDSURF_CONFIG_DIR / AUGMENT_CONFIG_DIR / TRAE_CONFIG_DIR / QWEN_CONFIG_DIR / HERMES_HOME / CLINE_CONFIG_DIR / CODEBUDDY_CONFIG_DIR environment variables.\n Kimi CLI defaults to the first existing generic skills root: ${cyan}~/.config/agents/skills${reset}, then ${cyan}~/.agents/skills${reset}; if neither exists, GSD creates ${cyan}~/.config/agents${reset}.\n Use ${cyan}--config-dir ~/.kimi-code${reset} or ${cyan}KIMI_CONFIG_DIR=~/.kimi-code${reset} for brand-specific Kimi installs.\n`); process.exit(0); } @@ -609,177 +630,22 @@ function computePathPrefix({ isGlobal, isOpencode, isWindowsHost: _isWindowsHost return `${resolvedTarget}/`; } -/** - * Normalize a raw `process.execPath` to a stable, upgrade-safe node binary - * path. On Homebrew installs, `process.execPath` resolves symlinks and returns - * the versioned Cellar path (e.g. - * `/usr/local/Cellar/node/25.8.1/bin/node`). Baking that path into hook - * commands causes `dyld: Library not loaded` errors after `brew upgrade node` - * because the shared libraries referenced by the Cellar binary have changed - * SOVERSION. (#3181) - * - * The stable Homebrew symlinks (`/usr/local/bin/node` for Intel, - * `/opt/homebrew/bin/node` for Apple Silicon) survive upgrades — Homebrew - * re-points them atomically. We prefer those when a Cellar path is detected. - * - * Non-Homebrew installs (NVM, system node, Windows, etc.) are returned as-is. - */ -function normalizeNodePath(execPath) { - if (!execPath) return execPath; - // Intel Homebrew: /usr/local/Cellar/node//bin/node - // or /usr/local/Cellar/node@20//bin/node - if (/^\/usr\/local\/Cellar\/node(@\d+)?\/[^/]+\/bin\/node(\.exe)?$/.test(execPath)) { - return '/usr/local/bin/node'; - } - // Apple Silicon Homebrew: /opt/homebrew/Cellar/node//bin/node - // or /opt/homebrew/Cellar/node@18//bin/node - if (/^\/opt\/homebrew\/Cellar\/node(@\d+)?\/[^/]+\/bin\/node(\.exe)?$/.test(execPath)) { - return '/opt/homebrew/bin/node'; - } - return execPath; -} - -/** - * Resolve the absolute path to the node binary running the installer. - * Used as the runner for .js hooks so they execute in GUI/minimal-PATH - * runtimes (Gemini, Antigravity, Codex CLIs launched from a Finder - * shortcut etc.) where bare `node` is not on `/usr/bin:/bin:/usr/sbin:/sbin` - * and the hook would fail with `node: command not found` (#2979). - * - * Returns a forward-slash-normalized, double-quoted path so the emitted - * command is shell-safe across POSIX and Windows. `process.execPath` - * gives the absolute path of the node binary actively running the - * installer — that is the version the user just installed under, and - * the right default runtime for hooks invoked under the same install. - * - * When `process.execPath` is a versioned Homebrew Cellar path, the stable - * Homebrew symlink is returned instead to survive `brew upgrade node` (#3181). - */ -function resolveNodeRunner() { - const execPath = typeof process.execPath === 'string' ? process.execPath : ''; - if (!execPath) return null; - const stablePath = normalizeNodePath(execPath); - // JSON.stringify produces a properly escaped double-quoted shell token, - // safe for paths containing spaces or unusual characters. - return JSON.stringify(stablePath.replace(/\\/g, '/')); -} - -/** - * Rewrite legacy `node .../gsd-*.js` command strings in settings.hooks to use - * the absolute Node binary path (#2979 follow-up: CR feedback on #3002). - * - * The original #2979 fix only emitted absolute paths for *newly registered* - * hooks. Pre-existing entries kept their bare `node ` prefix on reinstall, - * which left them broken under minimal-PATH GUI runtimes — exactly the - * failure mode the original fix was meant to close. This walker normalizes - * any managed-hook entry whose command starts with bare `node ` to - * `