mirror of
https://github.com/glittercowboy/get-shit-done
synced 2026-04-25 17:25:23 +02:00
Compare commits
96 Commits
fix/2544-c
...
fix/2643-i
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a72bebb379 | ||
|
|
31569c8cc8 | ||
|
|
eba0c99698 | ||
|
|
5a8a6fb511 | ||
|
|
bdba40cc3d | ||
|
|
df0ab0c0c9 | ||
|
|
807db75d55 | ||
|
|
74da61fb4a | ||
|
|
0a049149e1 | ||
|
|
a56707a07b | ||
|
|
f30da8326a | ||
|
|
1a3d953767 | ||
|
|
cc17886c51 | ||
|
|
41dc475c46 | ||
|
|
220da8e487 | ||
|
|
c90081176d | ||
|
|
1a694fcac3 | ||
|
|
9c0a153a5f | ||
|
|
86c5863afb | ||
|
|
1f2850c1a8 | ||
|
|
b35fdd51f3 | ||
|
|
7212cfd4de | ||
|
|
2b5c35cdb1 | ||
|
|
73c1af5168 | ||
|
|
533973700c | ||
|
|
349daf7e6a | ||
|
|
6b7b5c15a5 | ||
|
|
67a9550720 | ||
|
|
fba040c72c | ||
|
|
7032f44633 | ||
|
|
2404b40a15 | ||
|
|
0d6349a6c1 | ||
|
|
c47a6a2164 | ||
|
|
af2dba2328 | ||
|
|
9b5397a30f | ||
|
|
7397f580a5 | ||
|
|
9a67e350b3 | ||
|
|
98d92d7570 | ||
|
|
8eeaa20791 | ||
|
|
f32ffc9fb8 | ||
|
|
5676e2e4ef | ||
|
|
7bb6b6452a | ||
|
|
43ea92578b | ||
|
|
a42d5db742 | ||
|
|
c86ca1b3eb | ||
|
|
337e052aa9 | ||
|
|
969ee38ee5 | ||
|
|
2980f0ec48 | ||
|
|
8789211038 | ||
|
|
57bbfe652b | ||
|
|
a4764c5611 | ||
|
|
b2534e8a05 | ||
|
|
d1b56febcb | ||
|
|
1657321eb0 | ||
|
|
2b494407e5 | ||
|
|
d0f4340807 | ||
|
|
280eed93bc | ||
|
|
b432d4a726 | ||
|
|
cfe4dc76fd | ||
|
|
f19d0327b2 | ||
|
|
bd27d4fabe | ||
|
|
e8ec42082d | ||
|
|
86fb9c85c3 | ||
|
|
c5b1445529 | ||
|
|
c8807e38d7 | ||
|
|
2b4446e2f9 | ||
|
|
ef4ce7d6f9 | ||
|
|
12d38b2da0 | ||
|
|
e7a6d9ef2e | ||
|
|
beb3ac247b | ||
|
|
a95cabaedb | ||
|
|
9d55d531a4 | ||
|
|
5f419c0238 | ||
|
|
dfa1ecce99 | ||
|
|
4cd890b252 | ||
|
|
d117c1045a | ||
|
|
0ea443cbcf | ||
|
|
53b9fba324 | ||
|
|
5afcd5577e | ||
|
|
9f79cdc40a | ||
|
|
59cfbbba6a | ||
|
|
990c3e648d | ||
|
|
30433368a0 | ||
|
|
04fab926b5 | ||
|
|
f98ef1e460 | ||
|
|
d0565e95c1 | ||
|
|
4ef6275e86 | ||
|
|
6c50490766 | ||
|
|
4cbebfe78c | ||
|
|
9e87d43831 | ||
|
|
29ea90bc83 | ||
|
|
0c6172bfad | ||
|
|
e3bd06c9fd | ||
|
|
c69ecd975a | ||
|
|
06c4ded4ec | ||
|
|
341bb941c6 |
172
.github/workflows/install-smoke.yml
vendored
172
.github/workflows/install-smoke.yml
vendored
@@ -1,10 +1,13 @@
|
||||
name: Install Smoke
|
||||
|
||||
# Exercises the real install path: `npm pack` → `npm install -g <tarball>`
|
||||
# → run `bin/install.js` → assert `gsd-sdk` is on PATH.
|
||||
# Exercises the real install paths:
|
||||
# tarball: `npm pack` → `npm install -g <tarball>` → assert gsd-sdk on PATH
|
||||
# unpacked: `npm install -g <dir>` (no pack) → assert gsd-sdk on PATH + executable
|
||||
#
|
||||
# Closes the CI gap that let #2439 ship: the rest of the suite only reads
|
||||
# `bin/install.js` as a string and never executes it.
|
||||
# The tarball path is the canonical ship path. The unpacked path reproduces the
|
||||
# mode-644 failure class (issue #2453): npm does NOT chmod bin targets when
|
||||
# installing from an unpacked local directory, so any stale tsc output lacking
|
||||
# execute bits will be caught by the unpacked job before release.
|
||||
#
|
||||
# - PRs: path-filtered, minimal runner (ubuntu + Node LTS) for fast signal.
|
||||
# - Push to release branches / main: full matrix.
|
||||
@@ -16,6 +19,7 @@ on:
|
||||
- main
|
||||
paths:
|
||||
- 'bin/install.js'
|
||||
- 'bin/gsd-sdk.js'
|
||||
- 'sdk/**'
|
||||
- 'package.json'
|
||||
- 'package-lock.json'
|
||||
@@ -40,6 +44,9 @@ concurrency:
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
# ---------------------------------------------------------------------------
|
||||
# Job 1: tarball install (existing canonical path)
|
||||
# ---------------------------------------------------------------------------
|
||||
smoke:
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 12
|
||||
@@ -78,6 +85,31 @@ jobs:
|
||||
if: steps.skip.outputs.skip != 'true'
|
||||
with:
|
||||
ref: ${{ inputs.ref || github.ref }}
|
||||
# Need enough history to merge origin/main for stale-base detection.
|
||||
fetch-depth: 0
|
||||
|
||||
# The default `refs/pull/N/merge` ref GitHub produces for PRs is cached
|
||||
# against the recorded merge-base, not current main. When main advances
|
||||
# after the PR was opened, the merge ref stays stale and CI can fail on
|
||||
# issues that were already fixed upstream. Explicitly merge current
|
||||
# origin/main into the PR head so smoke always tests the PR against the
|
||||
# latest trunk. If the merge conflicts, emit a clear "rebase onto main"
|
||||
# diagnostic instead of a downstream build error that looks unrelated.
|
||||
- name: Rebase check — merge origin/main into PR head
|
||||
if: steps.skip.outputs.skip != 'true' && github.event_name == 'pull_request'
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
git config user.email "ci@gsd-build"
|
||||
git config user.name "CI Rebase Check"
|
||||
git fetch origin main
|
||||
if ! git merge --no-edit --no-ff origin/main; then
|
||||
echo "::error::This PR cannot cleanly merge origin/main. Rebase your branch onto current main and push again."
|
||||
echo "::error::Conflicting files:"
|
||||
git diff --name-only --diff-filter=U
|
||||
git merge --abort
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Set up Node.js ${{ matrix.node-version }}
|
||||
if: steps.skip.outputs.skip != 'true'
|
||||
@@ -90,6 +122,23 @@ jobs:
|
||||
if: steps.skip.outputs.skip != 'true'
|
||||
run: npm ci
|
||||
|
||||
# Isolated SDK typecheck — if the build fails, emit a clear "stale base
|
||||
# or real type error" diagnostic instead of letting the failure cascade
|
||||
# into the tarball install step, where the downstream PATH assertion
|
||||
# misreports it as "gsd-sdk not on PATH — installSdkIfNeeded regression".
|
||||
- name: SDK typecheck (fails fast on type regressions)
|
||||
if: steps.skip.outputs.skip != 'true'
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if ! npm run build:sdk; then
|
||||
echo "::error::SDK build (npm run build:sdk) failed."
|
||||
echo "::error::Common cause: your PR base is behind main and picks up intermediate type errors that are already fixed on trunk."
|
||||
echo "::error::Fix: git fetch origin main && git rebase origin/main && git push --force-with-lease"
|
||||
echo "::error::If the error persists on a fresh rebase, the type error is real — fix it in sdk/src/ and push."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Pack root tarball
|
||||
if: steps.skip.outputs.skip != 'true'
|
||||
id: pack
|
||||
@@ -109,7 +158,7 @@ jobs:
|
||||
echo "$NPM_BIN" >> "$GITHUB_PATH"
|
||||
echo "npm global bin: $NPM_BIN"
|
||||
|
||||
- name: Install tarball globally (runs bin/install.js → installSdkIfNeeded)
|
||||
- name: Install tarball globally
|
||||
if: steps.skip.outputs.skip != 'true'
|
||||
shell: bash
|
||||
env:
|
||||
@@ -121,13 +170,14 @@ jobs:
|
||||
cd "$TMPDIR_ROOT"
|
||||
npm install -g "$WORKSPACE/$TARBALL"
|
||||
command -v get-shit-done-cc
|
||||
# `--claude --local` is the non-interactive code path (see
|
||||
# install.js main block: when both a runtime and location are set,
|
||||
# installAllRuntimes runs with isInteractive=false, no prompts).
|
||||
# We tolerate non-zero here because the authoritative assertion is
|
||||
# the next step: gsd-sdk must land on PATH. Some runtime targets
|
||||
# may exit before the SDK step for unrelated reasons on CI.
|
||||
get-shit-done-cc --claude --local || true
|
||||
# `--claude --local` is the non-interactive code path. Don't swallow
|
||||
# non-zero exit — if the installer fails, that IS the CI failure, and
|
||||
# its own error message is more useful than the downstream "shim
|
||||
# regression" assertion masking the real cause.
|
||||
if ! get-shit-done-cc --claude --local; then
|
||||
echo "::error::get-shit-done-cc --claude --local failed. See the install.js output above for the real error (SDK build, PATH resolution, chmod, etc.)."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Assert gsd-sdk resolves on PATH
|
||||
if: steps.skip.outputs.skip != 'true'
|
||||
@@ -135,7 +185,7 @@ jobs:
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if ! command -v gsd-sdk >/dev/null 2>&1; then
|
||||
echo "::error::gsd-sdk is not on PATH after install — installSdkIfNeeded() regression"
|
||||
echo "::error::gsd-sdk is not on PATH after tarball install — shim regression"
|
||||
NPM_BIN="$(npm config get prefix)/bin"
|
||||
echo "npm global bin: $NPM_BIN"
|
||||
ls -la "$NPM_BIN" | grep -i gsd || true
|
||||
@@ -150,3 +200,99 @@ jobs:
|
||||
set -euo pipefail
|
||||
gsd-sdk --version || gsd-sdk --help
|
||||
echo "✓ gsd-sdk is executable"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Job 2: unpacked-dir install — reproduces the mode-644 failure class (#2453)
|
||||
#
|
||||
# `npm install -g <directory>` does NOT chmod bin targets when the source
|
||||
# file was produced by a build script (tsc emits 0o644). This job catches
|
||||
# regressions where sdk/dist/cli.js loses its execute bit before publish.
|
||||
# ---------------------------------------------------------------------------
|
||||
smoke-unpacked:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
ref: ${{ inputs.ref || github.ref }}
|
||||
fetch-depth: 0
|
||||
|
||||
# See the `smoke` job above for rationale — refs/pull/N/merge is cached
|
||||
# against the recorded merge-base, not current main. Explicitly merge
|
||||
# origin/main so smoke-unpacked also runs against the latest trunk.
|
||||
- name: Rebase check — merge origin/main into PR head
|
||||
if: github.event_name == 'pull_request'
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
git config user.email "ci@gsd-build"
|
||||
git config user.name "CI Rebase Check"
|
||||
git fetch origin main
|
||||
if ! git merge --no-edit --no-ff origin/main; then
|
||||
echo "::error::This PR cannot cleanly merge origin/main. Rebase your branch onto current main and push again."
|
||||
echo "::error::Conflicting files:"
|
||||
git diff --name-only --diff-filter=U
|
||||
git merge --abort
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Set up Node.js 22
|
||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
||||
with:
|
||||
node-version: 22
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install root deps
|
||||
run: npm ci
|
||||
|
||||
- name: Build SDK dist (sdk/dist is gitignored — must build for unpacked install)
|
||||
run: npm run build:sdk
|
||||
|
||||
- name: Ensure npm global bin is on PATH
|
||||
shell: bash
|
||||
run: |
|
||||
NPM_BIN="$(npm config get prefix)/bin"
|
||||
echo "$NPM_BIN" >> "$GITHUB_PATH"
|
||||
echo "npm global bin: $NPM_BIN"
|
||||
|
||||
- name: Strip execute bit from sdk/dist/cli.js to simulate tsc-fresh output
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
# Simulate the exact state tsc produces: cli.js at mode 644.
|
||||
chmod 644 sdk/dist/cli.js
|
||||
echo "Stripped execute bit: $(stat -c '%a' sdk/dist/cli.js 2>/dev/null || stat -f '%p' sdk/dist/cli.js)"
|
||||
|
||||
- name: Install from unpacked directory (no npm pack)
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
TMPDIR_ROOT=$(mktemp -d)
|
||||
cd "$TMPDIR_ROOT"
|
||||
npm install -g "$GITHUB_WORKSPACE"
|
||||
command -v get-shit-done-cc
|
||||
get-shit-done-cc --claude --local || true
|
||||
|
||||
- name: Assert gsd-sdk resolves on PATH after unpacked install
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if ! command -v gsd-sdk >/dev/null 2>&1; then
|
||||
echo "::error::gsd-sdk is not on PATH after unpacked install — #2453 regression"
|
||||
NPM_BIN="$(npm config get prefix)/bin"
|
||||
ls -la "$NPM_BIN" | grep -i gsd || true
|
||||
exit 1
|
||||
fi
|
||||
echo "✓ gsd-sdk resolves at: $(command -v gsd-sdk)"
|
||||
|
||||
- name: Assert gsd-sdk is executable after unpacked install (#2453)
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
# This is the exact check that would have caught #2453 before release.
|
||||
# The shim (bin/gsd-sdk.js) invokes sdk/dist/cli.js via `node`, so
|
||||
# the execute bit on cli.js is not needed for the shim path. However
|
||||
# installSdkIfNeeded() also chmods cli.js in-place as a safety net.
|
||||
gsd-sdk --version || gsd-sdk --help
|
||||
echo "✓ gsd-sdk is executable after unpacked install"
|
||||
|
||||
23
.github/workflows/release.yml
vendored
23
.github/workflows/release.yml
vendored
@@ -189,8 +189,8 @@ jobs:
|
||||
git add package.json package-lock.json sdk/package.json
|
||||
git commit -m "chore: bump to ${PRE_VERSION}"
|
||||
|
||||
- name: Build SDK
|
||||
run: cd sdk && npm ci && npm run build
|
||||
- name: Build SDK dist for tarball
|
||||
run: npm run build:sdk
|
||||
|
||||
- name: Dry-run publish validation
|
||||
run: |
|
||||
@@ -330,8 +330,8 @@ jobs:
|
||||
npm ci
|
||||
npm run test:coverage
|
||||
|
||||
- name: Build SDK
|
||||
run: cd sdk && npm ci && npm run build
|
||||
- name: Build SDK dist for tarball
|
||||
run: npm run build:sdk
|
||||
|
||||
- name: Dry-run publish validation
|
||||
run: |
|
||||
@@ -342,23 +342,32 @@ jobs:
|
||||
|
||||
- name: Create PR to merge release back to main
|
||||
if: ${{ !inputs.dry_run }}
|
||||
continue-on-error: true
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
BRANCH: ${{ needs.validate-version.outputs.branch }}
|
||||
VERSION: ${{ inputs.version }}
|
||||
run: |
|
||||
EXISTING_PR=$(gh pr list --base main --head "$BRANCH" --state open --json number --jq '.[0].number')
|
||||
# Non-fatal: repos that disable "Allow GitHub Actions to create and
|
||||
# approve pull requests" cause this step to fail with GraphQL 403.
|
||||
# The release itself (tag + npm publish + GitHub Release) must still
|
||||
# proceed. Open the merge-back PR manually afterwards with:
|
||||
# gh pr create --base main --head release/${VERSION} \
|
||||
# --title "chore: merge release v${VERSION} to main"
|
||||
EXISTING_PR=$(gh pr list --base main --head "$BRANCH" --state open --json number --jq '.[0].number' 2>/dev/null || echo "")
|
||||
if [ -n "$EXISTING_PR" ]; then
|
||||
echo "PR #$EXISTING_PR already exists; updating"
|
||||
gh pr edit "$EXISTING_PR" \
|
||||
--title "chore: merge release v${VERSION} to main" \
|
||||
--body "Merge release branch back to main after v${VERSION} stable release."
|
||||
--body "Merge release branch back to main after v${VERSION} stable release." \
|
||||
|| echo "::warning::Could not update merge-back PR (likely PR-creation policy disabled). Open it manually after release."
|
||||
else
|
||||
gh pr create \
|
||||
--base main \
|
||||
--head "$BRANCH" \
|
||||
--title "chore: merge release v${VERSION} to main" \
|
||||
--body "Merge release branch back to main after v${VERSION} stable release."
|
||||
--body "Merge release branch back to main after v${VERSION} stable release." \
|
||||
|| echo "::warning::Could not create merge-back PR (likely PR-creation policy disabled). Open it manually after release."
|
||||
fi
|
||||
|
||||
- name: Tag and push
|
||||
|
||||
28
.github/workflows/test.yml
vendored
28
.github/workflows/test.yml
vendored
@@ -35,6 +35,31 @@ jobs:
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
# Fetch full history so we can merge origin/main for stale-base detection.
|
||||
fetch-depth: 0
|
||||
|
||||
# GitHub's `refs/pull/N/merge` is cached against the recorded merge-base.
|
||||
# When main advances after a PR is opened, the cache stays stale and CI
|
||||
# runs against the pre-advance state — hiding bugs that are already fixed
|
||||
# on trunk and surfacing type errors that were introduced and then patched
|
||||
# on main in between. Explicitly merge current origin/main here so tests
|
||||
# always run against the latest trunk.
|
||||
- name: Rebase check — merge origin/main into PR head
|
||||
if: github.event_name == 'pull_request'
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
git config user.email "ci@gsd-build"
|
||||
git config user.name "CI Rebase Check"
|
||||
git fetch origin main
|
||||
if ! git merge --no-edit --no-ff origin/main; then
|
||||
echo "::error::This PR cannot cleanly merge origin/main. Rebase your branch onto current main and push again."
|
||||
echo "::error::Conflicting files:"
|
||||
git diff --name-only --diff-filter=U
|
||||
git merge --abort
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Set up Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
||||
@@ -45,6 +70,9 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Build SDK dist (required by installer)
|
||||
run: npm run build:sdk
|
||||
|
||||
- name: Run tests with coverage
|
||||
shell: bash
|
||||
run: npm run test:coverage
|
||||
|
||||
841
CHANGELOG.md
841
CHANGELOG.md
File diff suppressed because it is too large
Load Diff
@@ -314,6 +314,15 @@ bin/install.js — Installer (multi-runtime)
|
||||
get-shit-done/
|
||||
bin/lib/ — Core library modules (.cjs)
|
||||
workflows/ — Workflow definitions (.md)
|
||||
Large workflows split per progressive-disclosure
|
||||
pattern: workflows/<name>/modes/*.md +
|
||||
workflows/<name>/templates/*. Parent dispatches
|
||||
to mode files. See workflows/discuss-phase/ as
|
||||
the canonical example (#2551). New modes for
|
||||
discuss-phase land in
|
||||
workflows/discuss-phase/modes/<mode>.md.
|
||||
Per-file budgets enforced by
|
||||
tests/workflow-size-budget.test.cjs.
|
||||
references/ — Reference documentation (.md)
|
||||
templates/ — File templates
|
||||
agents/ — Agent definitions (.md) — CANONICAL SOURCE
|
||||
|
||||
@@ -8,7 +8,7 @@ color: "#F59E0B"
|
||||
---
|
||||
|
||||
<role>
|
||||
You are a GSD code reviewer. You analyze source files for bugs, security vulnerabilities, and code quality issues.
|
||||
Source files from a completed implementation have been submitted for adversarial review. Find every bug, security vulnerability, and quality defect — do not validate that work was done.
|
||||
|
||||
Spawned by `/gsd-code-review` workflow. You produce REVIEW.md artifact in the phase directory.
|
||||
|
||||
@@ -16,6 +16,22 @@ Spawned by `/gsd-code-review` workflow. You produce REVIEW.md artifact in the ph
|
||||
If the prompt contains a `<required_reading>` block, you MUST use the `Read` tool to load every file listed there before performing any other actions. This is your primary context.
|
||||
</role>
|
||||
|
||||
<adversarial_stance>
|
||||
**FORCE stance:** Assume every submitted implementation contains defects. Your starting hypothesis: this code has bugs, security gaps, or quality failures. Surface what you can prove.
|
||||
|
||||
**Common failure modes — how code reviewers go soft:**
|
||||
- Stopping at obvious surface issues (console.log, empty catch) and assuming the rest is sound
|
||||
- Accepting plausible-looking logic without tracing through edge cases (nulls, empty collections, boundary values)
|
||||
- Treating "code compiles" or "tests pass" as evidence of correctness
|
||||
- Reading only the file under review without checking called functions for bugs they introduce
|
||||
- Downgrading findings from BLOCKER to WARNING to avoid seeming harsh
|
||||
|
||||
**Required finding classification:** Every finding in REVIEW.md must carry:
|
||||
- **BLOCKER** — incorrect behavior, security vulnerability, or data loss risk; must be fixed before this code ships
|
||||
- **WARNING** — degrades quality, maintainability, or robustness; should be fixed
|
||||
Findings without a classification are not valid output.
|
||||
</adversarial_stance>
|
||||
|
||||
<project_context>
|
||||
Before reviewing, discover project context:
|
||||
|
||||
|
||||
@@ -94,6 +94,19 @@ Based on focus, determine which documents you'll write:
|
||||
- `arch` → ARCHITECTURE.md, STRUCTURE.md
|
||||
- `quality` → CONVENTIONS.md, TESTING.md
|
||||
- `concerns` → CONCERNS.md
|
||||
|
||||
**Optional `--paths` scope hint (#2003):**
|
||||
The prompt may include a line of the form:
|
||||
|
||||
```text
|
||||
--paths <p1>,<p2>,...
|
||||
```
|
||||
|
||||
When present, restrict your exploration (Glob/Grep/Bash globs) to files under the listed repo-relative path prefixes. This is the incremental-remap path used by the post-execute codebase-drift gate in `/gsd:execute-phase`. You still produce the same documents, but their "where to add new code" / "directory layout" sections focus on the provided subtrees rather than re-scanning the whole repository.
|
||||
|
||||
**Path validation:** Reject any `--paths` value containing `..`, starting with `/`, or containing shell metacharacters (`;`, `` ` ``, `$`, `&`, `|`, `<`, `>`). If all provided paths are invalid, log a warning in your confirmation and fall back to the default whole-repo scan.
|
||||
|
||||
If no `--paths` hint is provided, behave exactly as before.
|
||||
</step>
|
||||
|
||||
<step name="explore_codebase">
|
||||
|
||||
@@ -110,7 +110,7 @@ Regardless of type, extract:
|
||||
</step>
|
||||
|
||||
<step name="write_output">
|
||||
Write to `{OUTPUT_DIR}/{slug}.json` where `slug` is the filename without extension (replace non-alphanumerics with `-`).
|
||||
Write to `{OUTPUT_DIR}/{slug}-{source_hash}.json` where `slug` is the filename without extension (replace non-alphanumerics with `-`), and `source_hash` is the first 8 hex chars of SHA-256 of the **full source file path** (POSIX-style) so parallel classifiers never collide on sibling `README.md` files.
|
||||
|
||||
JSON schema:
|
||||
|
||||
|
||||
@@ -12,18 +12,34 @@ color: orange
|
||||
---
|
||||
|
||||
<role>
|
||||
You are a GSD doc verifier. You check factual claims in project documentation against the live codebase.
|
||||
A documentation file has been submitted for factual verification against the live codebase. Every checkable claim must be verified — do not assume claims are correct because the doc was recently written.
|
||||
|
||||
You are spawned by the `/gsd-docs-update` workflow. Each spawn receives a `<verify_assignment>` XML block containing:
|
||||
Spawned by the `/gsd-docs-update` workflow. Each spawn receives a `<verify_assignment>` XML block containing:
|
||||
- `doc_path`: path to the doc file to verify (relative to project_root)
|
||||
- `project_root`: absolute path to project root
|
||||
|
||||
Your job: Extract checkable claims from the doc, verify each against the codebase using filesystem tools only, then write a structured JSON result file. Returns a one-line confirmation to the orchestrator only — do not return doc content or claim details inline.
|
||||
Extract checkable claims from the doc, verify each against the codebase using filesystem tools only, then write a structured JSON result file. Returns a one-line confirmation to the orchestrator only — do not return doc content or claim details inline.
|
||||
|
||||
**CRITICAL: Mandatory Initial Read**
|
||||
If the prompt contains a `<required_reading>` block, you MUST use the `Read` tool to load every file listed there before performing any other actions. This is your primary context.
|
||||
</role>
|
||||
|
||||
<adversarial_stance>
|
||||
**FORCE stance:** Assume every factual claim in the doc is wrong until filesystem evidence proves it correct. Your starting hypothesis: the documentation has drifted from the code. Surface every false claim.
|
||||
|
||||
**Common failure modes — how doc verifiers go soft:**
|
||||
- Checking only explicit backtick file paths and skipping implicit file references in prose
|
||||
- Accepting "the file exists" without verifying the specific content the claim describes (e.g., a function name, a config key)
|
||||
- Missing command claims inside nested code blocks or multi-line bash examples
|
||||
- Stopping verification after finding the first PASS evidence for a claim rather than exhausting all checkable sub-claims
|
||||
- Marking claims UNCERTAIN when the filesystem can answer the question with a grep
|
||||
|
||||
**Required finding classification:**
|
||||
- **BLOCKER** — a claim is demonstrably false (file missing, function doesn't exist, command not in package.json); doc will mislead readers
|
||||
- **WARNING** — a claim cannot be verified from the filesystem alone (behavior claim, runtime claim) or is partially correct
|
||||
Every extracted claim must resolve to PASS, FAIL (BLOCKER), or UNVERIFIABLE (WARNING with reason).
|
||||
</adversarial_stance>
|
||||
|
||||
<project_context>
|
||||
Before verifying, discover project context:
|
||||
|
||||
|
||||
@@ -12,10 +12,26 @@ color: "#EF4444"
|
||||
---
|
||||
|
||||
<role>
|
||||
You are a GSD eval auditor. Answer: "Did the implemented AI system actually deliver its planned evaluation strategy?"
|
||||
An implemented AI phase has been submitted for evaluation coverage audit. Answer: "Did the implemented system actually deliver its planned evaluation strategy?" — not whether it looks like it might.
|
||||
Scan the codebase, score each dimension COVERED/PARTIAL/MISSING, write EVAL-REVIEW.md.
|
||||
</role>
|
||||
|
||||
<adversarial_stance>
|
||||
**FORCE stance:** Assume the eval strategy was not implemented until codebase evidence proves otherwise. Your starting hypothesis: AI-SPEC.md documents intent; the code does something different or less. Surface every gap.
|
||||
|
||||
**Common failure modes — how eval auditors go soft:**
|
||||
- Marking PARTIAL instead of MISSING because "some tests exist" — partial coverage of a critical eval dimension is MISSING until the gap is quantified
|
||||
- Accepting metric logging as evidence of evaluation without checking that logged metrics drive actual decisions
|
||||
- Crediting AI-SPEC.md documentation as implementation evidence
|
||||
- Not verifying that eval dimensions are scored against the rubric, only that test files exist
|
||||
- Downgrading MISSING to PARTIAL to soften the report
|
||||
|
||||
**Required finding classification:**
|
||||
- **BLOCKER** — an eval dimension is MISSING or a guardrail is unimplemented; AI system must not ship to production
|
||||
- **WARNING** — an eval dimension is PARTIAL; coverage is insufficient for confidence but not absent
|
||||
Every planned eval dimension must resolve to COVERED, PARTIAL (WARNING), or MISSING (BLOCKER).
|
||||
</adversarial_stance>
|
||||
|
||||
<required_reading>
|
||||
Read `~/.claude/get-shit-done/references/ai-evals.md` before auditing. This is your scoring framework.
|
||||
</required_reading>
|
||||
|
||||
@@ -72,10 +72,11 @@ if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
|
||||
|
||||
Extract from init JSON: `executor_model`, `commit_docs`, `sub_repos`, `phase_dir`, `plans`, `incomplete_plans`.
|
||||
|
||||
Also read STATE.md for position, decisions, blockers:
|
||||
Also load planning state (position, decisions, blockers) via the SDK — **use `node` to invoke the CLI** (not `npx`):
|
||||
```bash
|
||||
cat .planning/STATE.md 2>/dev/null
|
||||
node ./node_modules/@gsd-build/sdk/dist/cli.js query state.load 2>/dev/null
|
||||
```
|
||||
If the SDK is not installed under `node_modules`, use the same `query state.load` argv with your local `gsd-sdk` CLI on `PATH`.
|
||||
|
||||
If STATE.md missing but .planning/ exists: offer to reconstruct or continue without.
|
||||
If .planning/ missing: Error — project not initialized.
|
||||
|
||||
@@ -6,9 +6,9 @@ color: blue
|
||||
---
|
||||
|
||||
<role>
|
||||
You are an integration checker. You verify that phases work together as a system, not just individually.
|
||||
A set of completed phases has been submitted for cross-phase integration audit. Verify that phases actually wire together — not that each phase individually looks complete.
|
||||
|
||||
Your job: Check cross-phase wiring (exports used, APIs called, data flows) and verify E2E user flows complete without breaks.
|
||||
Check cross-phase wiring (exports used, APIs called, data flows) and verify E2E user flows complete without breaks.
|
||||
|
||||
**CRITICAL: Mandatory Initial Read**
|
||||
If the prompt contains a `<required_reading>` block, you MUST use the `Read` tool to load every file listed there before performing any other actions. This is your primary context.
|
||||
@@ -16,6 +16,22 @@ If the prompt contains a `<required_reading>` block, you MUST use the `Read` too
|
||||
**Critical mindset:** Individual phases can pass while the system fails. A component can exist without being imported. An API can exist without being called. Focus on connections, not existence.
|
||||
</role>
|
||||
|
||||
<adversarial_stance>
|
||||
**FORCE stance:** Assume every cross-phase connection is broken until a grep or trace proves the link exists end-to-end. Your starting hypothesis: phases are silos. Surface every missing connection.
|
||||
|
||||
**Common failure modes — how integration checkers go soft:**
|
||||
- Verifying that a function is exported and imported but not that it is actually called at the right point
|
||||
- Accepting API route existence as "API is wired" without checking that any consumer fetches from it
|
||||
- Tracing only the first link in a data chain (form → handler) and not the full chain (form → handler → DB → display)
|
||||
- Marking a flow as passing when only the happy path is traced and error/empty states are broken
|
||||
- Stopping at Phase 1↔2 wiring and not checking Phase 2↔3, Phase 3↔4, etc.
|
||||
|
||||
**Required finding classification:**
|
||||
- **BLOCKER** — a cross-phase connection is absent or broken; an E2E user flow cannot complete
|
||||
- **WARNING** — a connection exists but is fragile, incomplete for edge cases, or inconsistently applied
|
||||
Every expected cross-phase connection must resolve to WIRED (verified end-to-end) or BROKEN (BLOCKER).
|
||||
</adversarial_stance>
|
||||
|
||||
**Context budget:** Load project skills first (lightweight). Read implementation files incrementally — load only what each check requires, not the full codebase upfront.
|
||||
|
||||
**Project skills:** Check `.claude/skills/` or `.agents/skills/` directory if either exists:
|
||||
|
||||
@@ -12,7 +12,7 @@ color: "#8B5CF6"
|
||||
---
|
||||
|
||||
<role>
|
||||
GSD Nyquist auditor. Spawned by /gsd-validate-phase to fill validation gaps in completed phases.
|
||||
A completed phase has validation gaps submitted for adversarial test coverage. For each gap: generate a real behavioral test that can fail, run it, and report what actually happens — not what the implementation claims.
|
||||
|
||||
For each gap in `<gaps>`: generate minimal behavioral test, run it, debug if failing (max 3 iterations), report results.
|
||||
|
||||
@@ -21,6 +21,22 @@ For each gap in `<gaps>`: generate minimal behavioral test, run it, debug if fai
|
||||
**Implementation files are READ-ONLY.** Only create/modify: test files, fixtures, VALIDATION.md. Implementation bugs → ESCALATE. Never fix implementation.
|
||||
</role>
|
||||
|
||||
<adversarial_stance>
|
||||
**FORCE stance:** Assume every gap is genuinely uncovered until a passing test proves the requirement is satisfied. Your starting hypothesis: the implementation does not meet the requirement. Write tests that can fail.
|
||||
|
||||
**Common failure modes — how Nyquist auditors go soft:**
|
||||
- Writing tests that pass trivially because they test a simpler behavior than the requirement demands
|
||||
- Generating tests only for easy-to-test cases while skipping the gap's hard behavioral edge
|
||||
- Treating "test file created" as "gap filled" before the test actually runs and passes
|
||||
- Marking gaps as SKIP without escalating — a skipped gap is an unverified requirement, not a resolved one
|
||||
- Debugging a failing test by weakening the assertion rather than fixing the implementation via ESCALATE
|
||||
|
||||
**Required finding classification:**
|
||||
- **BLOCKER** — gap test fails after 3 iterations; requirement unmet; ESCALATE to developer
|
||||
- **WARNING** — gap test passes but with caveats (partial coverage, environment-specific, not deterministic)
|
||||
Every gap must resolve to FILLED (test passes), ESCALATED (BLOCKER), or explicitly justified SKIP.
|
||||
</adversarial_stance>
|
||||
|
||||
<execution_flow>
|
||||
|
||||
<step name="load_context">
|
||||
|
||||
@@ -145,7 +145,7 @@ When researching "best library for X": find what the ecosystem actually uses, do
|
||||
1. `mcp__context7__resolve-library-id` with libraryName
|
||||
2. `mcp__context7__query-docs` with resolved ID + specific query
|
||||
|
||||
**WebSearch tips:** Always include current year. Use multiple query variations. Cross-verify with authoritative sources.
|
||||
**WebSearch tips:** Use multiple query variations. Cross-verify with authoritative sources. Do not inject a year into queries — it biases results toward stale dated content; check publication dates on the results you read instead.
|
||||
|
||||
## Enhanced Web Search (Brave API)
|
||||
|
||||
@@ -836,6 +836,6 @@ Quality indicators:
|
||||
- **Verified, not assumed:** Findings cite Context7 or official docs
|
||||
- **Honest about gaps:** LOW confidence items flagged, unknowns admitted
|
||||
- **Actionable:** Planner could create tasks based on this research
|
||||
- **Current:** Year included in searches, publication dates checked
|
||||
- **Current:** Publication dates checked on sources (do not inject year into queries)
|
||||
|
||||
</success_criteria>
|
||||
@@ -6,7 +6,7 @@ color: green
|
||||
---
|
||||
|
||||
<role>
|
||||
You are a GSD plan checker. Verify that plans WILL achieve the phase goal, not just that they look complete.
|
||||
A set of phase plans has been submitted for pre-execution review. Verify they WILL achieve the phase goal — do not credit effort or intent, only verifiable coverage.
|
||||
|
||||
Spawned by `/gsd-plan-phase` orchestrator (after planner creates PLAN.md) or re-verification (after planner revises).
|
||||
|
||||
@@ -26,6 +26,22 @@ If the prompt contains a `<required_reading>` block, you MUST use the `Read` too
|
||||
You are NOT the executor or verifier — you verify plans WILL work before execution burns context.
|
||||
</role>
|
||||
|
||||
<adversarial_stance>
|
||||
**FORCE stance:** Assume every plan set is flawed until evidence proves otherwise. Your starting hypothesis: these plans will not deliver the phase goal. Surface what disqualifies them.
|
||||
|
||||
**Common failure modes — how plan checkers go soft:**
|
||||
- Accepting a plausible-sounding task list without tracing each task back to a phase requirement
|
||||
- Crediting a decision reference (e.g., "D-26") without verifying the task actually delivers the full decision scope
|
||||
- Treating scope reduction ("v1", "static for now", "future enhancement") as acceptable when the user's decision demands full delivery
|
||||
- Letting dimensions that pass anchor judgment — a plan can pass 6 of 7 dimensions and still fail the phase goal on the 7th
|
||||
- Issuing warnings for what are actually blockers to avoid conflict with the planner
|
||||
|
||||
**Required finding classification:** Every issue must carry an explicit severity:
|
||||
- **BLOCKER** — the phase goal will not be achieved if this is not fixed before execution
|
||||
- **WARNING** — quality or maintainability is degraded; fix recommended but execution can proceed
|
||||
Issues without a severity classification are not valid output.
|
||||
</adversarial_stance>
|
||||
|
||||
<required_reading>
|
||||
@~/.claude/get-shit-done/references/gates.md
|
||||
</required_reading>
|
||||
@@ -639,11 +655,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
|
||||
ls "$phase_dir"/*-PLAN.md 2>/dev/null
|
||||
# Read research for Nyquist validation data
|
||||
cat "$phase_dir"/*-RESEARCH.md 2>/dev/null
|
||||
gsd-sdk query roadmap.get-phase "$phase_number"
|
||||
ls "$phase_dir"/*-BRIEF.md 2>/dev/null
|
||||
node ./node_modules/@gsd-build/sdk/dist/cli.js query phase.list-plans "$phase_number"
|
||||
# Research / brief artifacts (deterministic listing)
|
||||
node ./node_modules/@gsd-build/sdk/dist/cli.js query phase.list-artifacts "$phase_number" --type research
|
||||
node ./node_modules/@gsd-build/sdk/dist/cli.js query roadmap.get-phase "$phase_number"
|
||||
node ./node_modules/@gsd-build/sdk/dist/cli.js query phase.list-artifacts "$phase_number" --type summary
|
||||
```
|
||||
|
||||
**Extract:** Phase goal, requirements (decompose goal), locked decisions, deferred ideas.
|
||||
@@ -729,10 +745,11 @@ The `tasks` array in the result shows each task's completeness:
|
||||
|
||||
**Check:** valid task type (auto, checkpoint:*, tdd), auto tasks have files/action/verify/done, action is specific, verify is runnable, done is measurable.
|
||||
|
||||
**For manual validation of specificity** (`verify.plan-structure` checks structure, not content quality):
|
||||
**For manual validation of specificity** (`verify.plan-structure` checks structure, not content quality), use structured extraction instead of grepping raw XML:
|
||||
```bash
|
||||
grep -B5 "</task>" "$PHASE_DIR"/*-PLAN.md | grep -v "<verify>"
|
||||
node ./node_modules/@gsd-build/sdk/dist/cli.js query plan.task-structure "$PLAN_PATH"
|
||||
```
|
||||
Inspect `tasks` in the JSON; open the PLAN in the editor for prose-level review.
|
||||
|
||||
## Step 6: Verify Dependency Graph
|
||||
|
||||
@@ -757,8 +774,8 @@ Missing: No mention of fetch/API call → Issue: Key link not planned
|
||||
## Step 8: Assess Scope
|
||||
|
||||
```bash
|
||||
grep -c "<task" "$PHASE_DIR"/$PHASE-01-PLAN.md
|
||||
grep "files_modified:" "$PHASE_DIR"/$PHASE-01-PLAN.md
|
||||
node ./node_modules/@gsd-build/sdk/dist/cli.js query plan.task-structure "$PHASE_DIR/$PHASE-01-PLAN.md"
|
||||
node ./node_modules/@gsd-build/sdk/dist/cli.js query frontmatter.get "$PHASE_DIR/$PHASE-01-PLAN.md" files_modified
|
||||
```
|
||||
|
||||
Thresholds: 2-3 tasks/plan good, 4 warning, 5+ blocker (split required).
|
||||
|
||||
@@ -215,6 +215,8 @@ Every task has four required fields:
|
||||
|
||||
**Nyquist Rule:** Every `<verify>` must include an `<automated>` command. If no test exists yet, set `<automated>MISSING — Wave 0 must create {test_file} first</automated>` and create a Wave 0 task that generates the test scaffold.
|
||||
|
||||
**Grep gate hygiene:** `grep -c` counts comments — header prose triggers its own invariant ("self-invalidating grep gate"). Use `grep -v '^#' | grep -c token`. Bare `== 0` gates on unfiltered files are forbidden.
|
||||
|
||||
**<done>:** Acceptance criteria - measurable state of completion.
|
||||
- Good: "Valid credentials return 200 + JWT cookie, invalid credentials return 401"
|
||||
- Bad: "Authentication is complete"
|
||||
@@ -810,10 +812,11 @@ if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
|
||||
|
||||
Extract from init JSON: `planner_model`, `researcher_model`, `checker_model`, `commit_docs`, `research_enabled`, `phase_dir`, `phase_number`, `has_research`, `has_context`.
|
||||
|
||||
Also read STATE.md for position, decisions, blockers:
|
||||
Also load planning state (position, decisions, blockers) via the SDK — **use `node` to invoke the CLI** (not `npx`):
|
||||
```bash
|
||||
cat .planning/STATE.md 2>/dev/null
|
||||
node ./node_modules/@gsd-build/sdk/dist/cli.js query state.load 2>/dev/null
|
||||
```
|
||||
If the SDK is not installed under `node_modules`, use the same `query state.load` argv with your local `gsd-sdk` CLI on `PATH`.
|
||||
|
||||
If STATE.md missing but .planning/ exists, offer to reconstruct or continue without.
|
||||
</step>
|
||||
@@ -1198,6 +1201,10 @@ Execute: `/gsd-execute-phase {phase} --gaps-only`
|
||||
|
||||
Follow templates in checkpoints and revision_mode sections respectively.
|
||||
|
||||
## Chunked Mode Returns
|
||||
|
||||
See @~/.claude/get-shit-done/references/planner-chunked.md for `## OUTLINE COMPLETE` and `## PLAN COMPLETE` return formats used in chunked mode.
|
||||
|
||||
</structured_returns>
|
||||
|
||||
<critical_rules>
|
||||
|
||||
@@ -116,12 +116,12 @@ For finding what exists, community patterns, real-world usage.
|
||||
|
||||
**Query templates:**
|
||||
```
|
||||
Ecosystem: "[tech] best practices [current year]", "[tech] recommended libraries [current year]"
|
||||
Ecosystem: "[tech] best practices", "[tech] recommended libraries"
|
||||
Patterns: "how to build [type] with [tech]", "[tech] architecture patterns"
|
||||
Problems: "[tech] common mistakes", "[tech] gotchas"
|
||||
```
|
||||
|
||||
Always include current year. Use multiple query variations. Mark WebSearch-only findings as LOW confidence.
|
||||
Use multiple query variations. Mark WebSearch-only findings as LOW confidence. Do not inject a year into queries — it biases results toward stale dated content; check publication dates on the results you read instead.
|
||||
|
||||
### Enhanced Web Search (Brave API)
|
||||
|
||||
@@ -672,6 +672,6 @@ Research is complete when:
|
||||
- [ ] Files written (DO NOT commit — orchestrator handles this)
|
||||
- [ ] Structured return provided to orchestrator
|
||||
|
||||
**Quality:** Comprehensive not shallow. Opinionated not wishy-washy. Verified not assumed. Honest about gaps. Actionable for roadmap. Current (year in searches).
|
||||
**Quality:** Comprehensive not shallow. Opinionated not wishy-washy. Verified not assumed. Honest about gaps. Actionable for roadmap. Current (check publication dates, do not inject year into queries).
|
||||
|
||||
</success_criteria>
|
||||
|
||||
@@ -560,9 +560,7 @@ When files are written and returning to orchestrator:
|
||||
|
||||
### Files Ready for Review
|
||||
|
||||
User can review actual files:
|
||||
- `cat .planning/ROADMAP.md`
|
||||
- `cat .planning/STATE.md`
|
||||
User can review actual files in the editor or via SDK queries (e.g. `node ./node_modules/@gsd-build/sdk/dist/cli.js query roadmap.analyze` and `query state.load`) instead of ad-hoc shell `cat`.
|
||||
|
||||
{If gaps found during creation:}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ color: "#EF4444"
|
||||
---
|
||||
|
||||
<role>
|
||||
GSD security auditor. Spawned by /gsd-secure-phase to verify that threat mitigations declared in PLAN.md are present in implemented code.
|
||||
An implemented phase has been submitted for security audit. Verify that every declared threat mitigation is present in the code — do not accept documentation or intent as evidence.
|
||||
|
||||
Does NOT scan blindly for new vulnerabilities. Verifies each threat in `<threat_model>` by its declared disposition (mitigate / accept / transfer). Reports gaps. Writes SECURITY.md.
|
||||
|
||||
@@ -21,6 +21,22 @@ Does NOT scan blindly for new vulnerabilities. Verifies each threat in `<threat_
|
||||
**Implementation files are READ-ONLY.** Only create/modify: SECURITY.md. Implementation security gaps → OPEN_THREATS or ESCALATE. Never patch implementation.
|
||||
</role>
|
||||
|
||||
<adversarial_stance>
|
||||
**FORCE stance:** Assume every mitigation is absent until a grep match proves it exists in the right location. Your starting hypothesis: threats are open. Surface every unverified mitigation.
|
||||
|
||||
**Common failure modes — how security auditors go soft:**
|
||||
- Accepting a single grep match as full mitigation without checking it applies to ALL entry points
|
||||
- Treating `transfer` disposition as "not our problem" without verifying transfer documentation exists
|
||||
- Assuming SUMMARY.md `## Threat Flags` is a complete list of new attack surface
|
||||
- Skipping threats with complex dispositions because verification is hard
|
||||
- Marking CLOSED based on code structure ("looks like it validates input") without finding the actual validation call
|
||||
|
||||
**Required finding classification:**
|
||||
- **BLOCKER** — `OPEN_THREATS`: a declared mitigation is absent in implemented code; phase must not ship
|
||||
- **WARNING** — `unregistered_flag`: new attack surface appeared during implementation with no threat mapping
|
||||
Every threat must resolve to CLOSED, OPEN (BLOCKER), or documented accepted risk.
|
||||
</adversarial_stance>
|
||||
|
||||
<execution_flow>
|
||||
|
||||
<step name="load_context">
|
||||
|
||||
@@ -12,7 +12,7 @@ color: "#F472B6"
|
||||
---
|
||||
|
||||
<role>
|
||||
You are a GSD UI auditor. You conduct retroactive visual and interaction audits of implemented frontend code and produce a scored UI-REVIEW.md.
|
||||
An implemented frontend has been submitted for adversarial visual and interaction audit. Score what was actually built against the design contract or 6-pillar standards — do not average scores upward to soften findings.
|
||||
|
||||
Spawned by `/gsd-ui-review` orchestrator.
|
||||
|
||||
@@ -27,6 +27,22 @@ If the prompt contains a `<required_reading>` block, you MUST use the `Read` too
|
||||
- Write UI-REVIEW.md with actionable findings
|
||||
</role>
|
||||
|
||||
<adversarial_stance>
|
||||
**FORCE stance:** Assume every pillar has failures until screenshots or code analysis proves otherwise. Your starting hypothesis: the UI diverges from the design contract. Surface every deviation.
|
||||
|
||||
**Common failure modes — how UI auditors go soft:**
|
||||
- Averaging pillar scores upward so no single score looks too damning
|
||||
- Accepting "the component exists" as evidence the UI is correct without checking spacing, color, or interaction
|
||||
- Not testing against UI-SPEC.md breakpoints and spacing scale — just eyeballing layout
|
||||
- Treating brand-compliant primary colors as a full pass on the color pillar without checking 60/30/10 distribution
|
||||
- Identifying 3 priority fixes and stopping, when 6+ issues exist
|
||||
|
||||
**Required finding classification:**
|
||||
- **BLOCKER** — pillar score 1 or a specific defect that breaks user task completion; must fix before shipping
|
||||
- **WARNING** — pillar score 2-3 or a defect that degrades quality but doesn't break flows; fix recommended
|
||||
Every scored pillar must have at least one specific finding justifying the score.
|
||||
</adversarial_stance>
|
||||
|
||||
<project_context>
|
||||
Before auditing, discover project context:
|
||||
|
||||
|
||||
@@ -12,9 +12,9 @@ color: green
|
||||
---
|
||||
|
||||
<role>
|
||||
You are a GSD phase verifier. You verify that a phase achieved its GOAL, not just completed its TASKS.
|
||||
A completed phase has been submitted for goal-backward verification. Verify that the phase goal is actually achieved in the codebase — SUMMARY.md claims are not evidence.
|
||||
|
||||
Your job: Goal-backward verification. Start from what the phase SHOULD deliver, verify it actually exists and works in the codebase.
|
||||
Goal-backward verification. Start from what the phase SHOULD deliver, verify it actually exists and works in the codebase.
|
||||
|
||||
@~/.claude/get-shit-done/references/mandatory-initial-read.md
|
||||
|
||||
@@ -22,6 +22,22 @@ Your job: Goal-backward verification. Start from what the phase SHOULD deliver,
|
||||
|
||||
</role>
|
||||
|
||||
<adversarial_stance>
|
||||
**FORCE stance:** Assume the phase goal was not achieved until codebase evidence proves it. Your starting hypothesis: tasks completed, goal missed. Falsify the SUMMARY.md narrative.
|
||||
|
||||
**Common failure modes — how verifiers go soft:**
|
||||
- Trusting SUMMARY.md bullet points without reading the actual code files they describe
|
||||
- Accepting "file exists" as "truth verified" — a stub file satisfies existence but not behavior
|
||||
- Choosing UNCERTAIN instead of FAILED when absence of implementation is observable
|
||||
- Letting high task-completion percentage bias judgment toward PASS before truths are checked
|
||||
- Anchoring on truths that passed early and giving less scrutiny to later ones
|
||||
|
||||
**Required finding classification:**
|
||||
- **BLOCKER** — a must-have truth is FAILED; phase goal not achieved; must not proceed to next phase
|
||||
- **WARNING** — a must-have is UNCERTAIN or an artifact exists but wiring is incomplete
|
||||
Every truth must resolve to VERIFIED, FAILED (BLOCKER), or UNCERTAIN (WARNING with human decision requested.
|
||||
</adversarial_stance>
|
||||
|
||||
<required_reading>
|
||||
@~/.claude/get-shit-done/references/verification-overrides.md
|
||||
@~/.claude/get-shit-done/references/gates.md
|
||||
|
||||
32
bin/gsd-sdk.js
Executable file
32
bin/gsd-sdk.js
Executable file
@@ -0,0 +1,32 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* bin/gsd-sdk.js — back-compat shim for external callers of `gsd-sdk`.
|
||||
*
|
||||
* When the parent package is installed globally (`npm install -g get-shit-done-cc`
|
||||
* or `npx get-shit-done-cc`), npm creates a `gsd-sdk` symlink in the global bin
|
||||
* directory pointing at this file. npm correctly chmods bin entries from a tarball,
|
||||
* so the execute-bit problem that afflicted the sub-install approach (issue #2453)
|
||||
* cannot occur here.
|
||||
*
|
||||
* This shim resolves sdk/dist/cli.js relative to its own location and delegates
|
||||
* to it via `node`, so `gsd-sdk <args>` behaves identically to
|
||||
* `node <packageDir>/sdk/dist/cli.js <args>`.
|
||||
*
|
||||
* Call sites (slash commands, agent prompts, hook scripts) continue to work without
|
||||
* changes because `gsd-sdk` still resolves on PATH — it just comes from this shim
|
||||
* in the parent package rather than from a separately installed @gsd-build/sdk.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const path = require('path');
|
||||
const { spawnSync } = require('child_process');
|
||||
|
||||
const cliPath = path.resolve(__dirname, '..', 'sdk', 'dist', 'cli.js');
|
||||
|
||||
const result = spawnSync(process.execPath, [cliPath, ...process.argv.slice(2)], {
|
||||
stdio: 'inherit',
|
||||
env: process.env,
|
||||
});
|
||||
|
||||
process.exit(result.status ?? 1);
|
||||
637
bin/install.js
637
bin/install.js
@@ -57,6 +57,20 @@ const claudeToCopilotTools = {
|
||||
// Get version from package.json
|
||||
const pkg = require('../package.json');
|
||||
|
||||
// #2517 — runtime-aware tier resolution shared with core.cjs.
|
||||
// Hoisted to top with absolute __dirname-based paths so `gsd install codex` works
|
||||
// when invoked via npm global install (cwd is the user's project, not the gsd repo
|
||||
// root). Inline `require('../get-shit-done/...')` from inside install functions
|
||||
// works only because Node resolves it relative to the install.js file regardless
|
||||
// of cwd, but keeping the require at the top makes the dependency explicit and
|
||||
// surfaces resolution failures at process start instead of at first install call.
|
||||
const _gsdLibDir = path.join(__dirname, '..', 'get-shit-done', 'bin', 'lib');
|
||||
const { MODEL_PROFILES: GSD_MODEL_PROFILES } = require(path.join(_gsdLibDir, 'model-profiles.cjs'));
|
||||
const {
|
||||
RUNTIME_PROFILE_MAP: GSD_RUNTIME_PROFILE_MAP,
|
||||
resolveTierEntry: gsdResolveTierEntry,
|
||||
} = require(path.join(_gsdLibDir, 'core.cjs'));
|
||||
|
||||
// Parse args
|
||||
const args = process.argv.slice(2);
|
||||
const hasGlobal = args.includes('--global') || args.includes('-g');
|
||||
@@ -78,6 +92,7 @@ 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';
|
||||
const hasSdk = args.includes('--sdk');
|
||||
const hasNoSdk = args.includes('--no-sdk');
|
||||
@@ -438,7 +453,7 @@ const explicitConfigDir = parseConfigDirArg();
|
||||
const hasHelp = args.includes('--help') || args.includes('-h');
|
||||
const forceStatusline = args.includes('--force-statusline');
|
||||
|
||||
console.log(banner);
|
||||
if (!hasSkillsRoot) console.log(banner);
|
||||
|
||||
if (hasUninstall) {
|
||||
console.log(' Mode: Uninstall\n');
|
||||
@@ -619,6 +634,172 @@ function readGsdGlobalModelOverrides() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Effective per-agent model_overrides for the Codex / OpenCode install paths.
|
||||
*
|
||||
* Merges `~/.gsd/defaults.json` (global) with per-project
|
||||
* `<project>/.planning/config.json`. Per-project keys win on conflict so a
|
||||
* user can tune a single agent's model in one repo without re-setting the
|
||||
* global defaults for every other repo. Non-conflicting keys from both
|
||||
* sources are preserved.
|
||||
*
|
||||
* This is the fix for #2256: both adapters previously read only the global
|
||||
* file, so a per-project `model_overrides` (the common case the reporter
|
||||
* described — a per-project override for `gsd-codebase-mapper` in
|
||||
* `.planning/config.json`) was silently dropped and child agents inherited
|
||||
* the session default.
|
||||
*
|
||||
* `targetDir` is the consuming runtime's install root (e.g. `~/.codex` for
|
||||
* a global install, or `<project>/.codex` for a local install). We walk up
|
||||
* from there looking for `.planning/` so both cases resolve the correct
|
||||
* project root. When `targetDir` is null/undefined only the global file is
|
||||
* consulted (matches prior behavior for code paths that have no project
|
||||
* context).
|
||||
*
|
||||
* Returns a plain `{ agentName: modelId }` object, or `null` when neither
|
||||
* source defines `model_overrides`.
|
||||
*/
|
||||
function readGsdEffectiveModelOverrides(targetDir = null) {
|
||||
const global = readGsdGlobalModelOverrides();
|
||||
|
||||
let projectOverrides = null;
|
||||
if (targetDir) {
|
||||
let probeDir = path.resolve(targetDir);
|
||||
for (let depth = 0; depth < 8; depth += 1) {
|
||||
const candidate = path.join(probeDir, '.planning', 'config.json');
|
||||
if (fs.existsSync(candidate)) {
|
||||
try {
|
||||
const parsed = JSON.parse(fs.readFileSync(candidate, 'utf-8'));
|
||||
if (parsed && typeof parsed === 'object' && parsed.model_overrides
|
||||
&& typeof parsed.model_overrides === 'object') {
|
||||
projectOverrides = parsed.model_overrides;
|
||||
}
|
||||
} catch {
|
||||
// Malformed config.json — fall back to global; readGsdRuntimeProfileResolver
|
||||
// surfaces a parse warning via _readGsdConfigFile already.
|
||||
}
|
||||
break;
|
||||
}
|
||||
const parent = path.dirname(probeDir);
|
||||
if (parent === probeDir) break;
|
||||
probeDir = parent;
|
||||
}
|
||||
}
|
||||
|
||||
if (!global && !projectOverrides) return null;
|
||||
// Per-project wins on conflict; preserve non-conflicting global keys.
|
||||
return { ...(global || {}), ...(projectOverrides || {}) };
|
||||
}
|
||||
|
||||
/**
|
||||
* #2517 — Read a single GSD config file (defaults.json or per-project
|
||||
* config.json) into a plain object, returning null on missing/empty files
|
||||
* and warning to stderr on JSON parse failures so silent corruption can't
|
||||
* mask broken configs (review finding #5).
|
||||
*/
|
||||
function _readGsdConfigFile(absPath, label) {
|
||||
if (!fs.existsSync(absPath)) return null;
|
||||
let raw;
|
||||
try {
|
||||
raw = fs.readFileSync(absPath, 'utf-8');
|
||||
} catch (err) {
|
||||
process.stderr.write(`gsd: warning — could not read ${label} (${absPath}): ${err.message}\n`);
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return JSON.parse(raw);
|
||||
} catch (err) {
|
||||
process.stderr.write(`gsd: warning — invalid JSON in ${label} (${absPath}): ${err.message}\n`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* #2517 — Build a runtime-aware tier resolver for the install path.
|
||||
*
|
||||
* Probes BOTH per-project `<targetDir>/.planning/config.json` AND
|
||||
* `~/.gsd/defaults.json`, with per-project keys winning over global. This
|
||||
* matches `loadConfig`'s precedence and is the only way the PR's headline claim
|
||||
* — "set runtime in .planning/config.json and the Codex TOML emit picks it up"
|
||||
* — actually holds end-to-end (review finding #1).
|
||||
*
|
||||
* `targetDir` should be the consuming runtime's install root — install code
|
||||
* passes `path.dirname(<runtime root>)` so `.planning/config.json` resolves
|
||||
* relative to the user's project. When `targetDir` is null/undefined, only the
|
||||
* global defaults are consulted.
|
||||
*
|
||||
* Returns null if no `runtime` is configured (preserves prior behavior — only
|
||||
* model_overrides is embedded, no tier/reasoning-effort inference). Returns
|
||||
* null when `model_profile` is `inherit` so the literal alias passes through
|
||||
* unchanged.
|
||||
*
|
||||
* Returns { runtime, resolve(agentName) -> { model, reasoning_effort? } | null }
|
||||
*/
|
||||
function readGsdRuntimeProfileResolver(targetDir = null) {
|
||||
const homeDefaults = _readGsdConfigFile(
|
||||
path.join(os.homedir(), '.gsd', 'defaults.json'),
|
||||
'~/.gsd/defaults.json'
|
||||
);
|
||||
|
||||
// Per-project config probe. Resolve the project root by walking up from
|
||||
// targetDir until we hit a `.planning/` directory; this covers both the
|
||||
// common case (caller passes the project root) and the case where caller
|
||||
// passes a nested install dir like `<root>/.codex/`.
|
||||
let projectConfig = null;
|
||||
if (targetDir) {
|
||||
let probeDir = path.resolve(targetDir);
|
||||
for (let depth = 0; depth < 8; depth += 1) {
|
||||
const candidate = path.join(probeDir, '.planning', 'config.json');
|
||||
if (fs.existsSync(candidate)) {
|
||||
projectConfig = _readGsdConfigFile(candidate, '.planning/config.json');
|
||||
break;
|
||||
}
|
||||
const parent = path.dirname(probeDir);
|
||||
if (parent === probeDir) break;
|
||||
probeDir = parent;
|
||||
}
|
||||
}
|
||||
|
||||
// Per-project wins. Only fall back to ~/.gsd/defaults.json when the project
|
||||
// didn't set the field. Field-level merge (not whole-object replace) so a
|
||||
// user can keep `runtime` global while overriding only `model_profile` per
|
||||
// project, and vice versa.
|
||||
const merged = {
|
||||
runtime:
|
||||
(projectConfig && projectConfig.runtime) ||
|
||||
(homeDefaults && homeDefaults.runtime) ||
|
||||
null,
|
||||
model_profile:
|
||||
(projectConfig && projectConfig.model_profile) ||
|
||||
(homeDefaults && homeDefaults.model_profile) ||
|
||||
'balanced',
|
||||
model_profile_overrides:
|
||||
(projectConfig && projectConfig.model_profile_overrides) ||
|
||||
(homeDefaults && homeDefaults.model_profile_overrides) ||
|
||||
null,
|
||||
};
|
||||
|
||||
if (!merged.runtime) return null;
|
||||
|
||||
const profile = String(merged.model_profile).toLowerCase();
|
||||
if (profile === 'inherit') return null;
|
||||
|
||||
return {
|
||||
runtime: merged.runtime,
|
||||
resolve(agentName) {
|
||||
const agentModels = GSD_MODEL_PROFILES[agentName];
|
||||
if (!agentModels) return null;
|
||||
const tier = agentModels[profile] || agentModels.balanced;
|
||||
if (!tier) return null;
|
||||
return gsdResolveTierEntry({
|
||||
runtime: merged.runtime,
|
||||
tier,
|
||||
overrides: merged.model_profile_overrides,
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// Cache for attribution settings (populated once per runtime during install)
|
||||
const attributionCache = new Map();
|
||||
|
||||
@@ -876,14 +1057,18 @@ function convertCopilotToolName(claudeTool) {
|
||||
*/
|
||||
function convertClaudeToCopilotContent(content, isGlobal = false) {
|
||||
let c = content;
|
||||
// CONV-06: Path replacement — most specific first to avoid substring matches
|
||||
// CONV-06: Path replacement — most specific first to avoid substring matches.
|
||||
// Handle both `~/.claude/foo` (trailing slash) and bare `~/.claude` forms in
|
||||
// one pass via a capture group, matching the approach used by Antigravity,
|
||||
// OpenCode, Kilo, and Codex converters (issue #2545).
|
||||
if (isGlobal) {
|
||||
c = c.replace(/\$HOME\/\.claude\//g, '$HOME/.copilot/');
|
||||
c = c.replace(/~\/\.claude\//g, '~/.copilot/');
|
||||
c = c.replace(/\$HOME\/\.claude(\/|\b)/g, '$HOME/.copilot$1');
|
||||
c = c.replace(/~\/\.claude(\/|\b)/g, '~/.copilot$1');
|
||||
} else {
|
||||
c = c.replace(/\$HOME\/\.claude\//g, '.github/');
|
||||
c = c.replace(/~\/\.claude\//g, '.github/');
|
||||
c = c.replace(/~\/\.claude\n/g, '.github/');
|
||||
c = c.replace(/\$HOME\/\.claude\b/g, '.github');
|
||||
c = c.replace(/~\/\.claude\b/g, '.github');
|
||||
}
|
||||
c = c.replace(/\.\/\.claude\//g, './.github/');
|
||||
c = c.replace(/\.claude\//g, '.github/');
|
||||
@@ -1006,9 +1191,15 @@ function convertClaudeToAntigravityContent(content, isGlobal = false) {
|
||||
if (isGlobal) {
|
||||
c = c.replace(/\$HOME\/\.claude\//g, '$HOME/.gemini/antigravity/');
|
||||
c = c.replace(/~\/\.claude\//g, '~/.gemini/antigravity/');
|
||||
// Bare form (no trailing slash) — must come after slash form to avoid double-replace
|
||||
c = c.replace(/\$HOME\/\.claude\b/g, '$HOME/.gemini/antigravity');
|
||||
c = c.replace(/~\/\.claude\b/g, '~/.gemini/antigravity');
|
||||
} else {
|
||||
c = c.replace(/\$HOME\/\.claude\//g, '.agent/');
|
||||
c = c.replace(/~\/\.claude\//g, '.agent/');
|
||||
// Bare form (no trailing slash) — must come after slash form to avoid double-replace
|
||||
c = c.replace(/\$HOME\/\.claude\b/g, '.agent');
|
||||
c = c.replace(/~\/\.claude\b/g, '.agent');
|
||||
}
|
||||
c = c.replace(/\.\/\.claude\//g, './.agent/');
|
||||
c = c.replace(/\.claude\//g, '.agent/');
|
||||
@@ -1718,9 +1909,17 @@ GSD workflows use \`Task(...)\` (Claude Code syntax). Translate to Codex collabo
|
||||
|
||||
Direct mapping:
|
||||
- \`Task(subagent_type="X", prompt="Y")\` → \`spawn_agent(agent_type="X", message="Y")\`
|
||||
- \`Task(model="...")\` → omit (Codex uses per-role config, not inline model selection)
|
||||
- \`Task(model="...")\` → omit. \`spawn_agent\` has no inline \`model\` parameter;
|
||||
GSD embeds the resolved per-agent model directly into each agent's \`.toml\`
|
||||
at install time so \`model_overrides\` from \`.planning/config.json\` and
|
||||
\`~/.gsd/defaults.json\` are honored automatically by Codex's agent router.
|
||||
- \`fork_context: false\` by default — GSD agents load their own context via \`<files_to_read>\` blocks
|
||||
|
||||
Spawn restriction:
|
||||
- Codex restricts \`spawn_agent\` to cases where the user has explicitly
|
||||
requested sub-agents. When automatic spawning is not permitted, do the
|
||||
work inline in the current agent rather than attempting to force a spawn.
|
||||
|
||||
Parallel fan-out:
|
||||
- Spawn multiple agents → collect agent IDs → \`wait(ids)\` for all to complete
|
||||
|
||||
@@ -1778,7 +1977,7 @@ purpose: ${toSingleLine(description)}
|
||||
* Sets required agent metadata, sandbox_mode, and developer_instructions
|
||||
* from the agent markdown content.
|
||||
*/
|
||||
function generateCodexAgentToml(agentName, agentContent, modelOverrides = null) {
|
||||
function generateCodexAgentToml(agentName, agentContent, modelOverrides = null, runtimeResolver = null) {
|
||||
const sandboxMode = CODEX_AGENT_SANDBOX[agentName] || 'read-only';
|
||||
const { frontmatter, body } = extractFrontmatterAndBody(agentContent);
|
||||
const frontmatterText = frontmatter || '';
|
||||
@@ -1797,9 +1996,20 @@ function generateCodexAgentToml(agentName, agentContent, modelOverrides = null)
|
||||
// Embed model override when configured in ~/.gsd/defaults.json so that
|
||||
// model_overrides is respected on Codex (which uses static TOML, not inline
|
||||
// Task() model parameters). See #2256.
|
||||
// Precedence: per-agent model_overrides > runtime-aware tier resolution (#2517).
|
||||
const modelOverride = modelOverrides?.[resolvedName] || modelOverrides?.[agentName];
|
||||
if (modelOverride) {
|
||||
lines.push(`model = ${JSON.stringify(modelOverride)}`);
|
||||
} else if (runtimeResolver) {
|
||||
// #2517 — runtime-aware tier resolution. Embeds Codex-native model + reasoning_effort
|
||||
// from RUNTIME_PROFILE_MAP / model_profile_overrides for the configured tier.
|
||||
const entry = runtimeResolver.resolve(resolvedName) || runtimeResolver.resolve(agentName);
|
||||
if (entry?.model) {
|
||||
lines.push(`model = ${JSON.stringify(entry.model)}`);
|
||||
if (entry.reasoning_effort) {
|
||||
lines.push(`model_reasoning_effort = ${JSON.stringify(entry.reasoning_effort)}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Agent prompts contain raw backslashes in regexes and shell snippets.
|
||||
@@ -3037,10 +3247,21 @@ function installCodexConfig(targetDir, agentsSrc) {
|
||||
|
||||
agents.push({ name, description: toSingleLine(description) });
|
||||
|
||||
// Pass model overrides from ~/.gsd/defaults.json so Codex TOML files
|
||||
// Pass model overrides from both per-project `.planning/config.json` and
|
||||
// `~/.gsd/defaults.json` (project wins on conflict) so Codex TOML files
|
||||
// embed the configured model — Codex cannot receive model inline (#2256).
|
||||
const modelOverrides = readGsdGlobalModelOverrides();
|
||||
const tomlContent = generateCodexAgentToml(name, content, modelOverrides);
|
||||
// Previously only the global file was read, which silently dropped the
|
||||
// per-project override the reporter had set for gsd-codebase-mapper.
|
||||
// #2517 — also pass the runtime-aware tier resolver so profile tiers can
|
||||
// resolve to Codex-native model IDs + reasoning_effort when `runtime: "codex"`
|
||||
// is set in defaults.json.
|
||||
const modelOverrides = readGsdEffectiveModelOverrides(targetDir);
|
||||
// Pass `targetDir` so per-project .planning/config.json wins over global
|
||||
// ~/.gsd/defaults.json — without this, the PR's headline claim that
|
||||
// setting runtime in the project config reaches the Codex emit path is
|
||||
// false (review finding #1).
|
||||
const runtimeResolver = readGsdRuntimeProfileResolver(targetDir);
|
||||
const tomlContent = generateCodexAgentToml(name, content, modelOverrides, runtimeResolver);
|
||||
fs.writeFileSync(path.join(agentsTomlDir, `${name}.toml`), tomlContent);
|
||||
}
|
||||
|
||||
@@ -5459,9 +5680,12 @@ function install(isGlobal, runtime = 'claude') {
|
||||
// For global installs: use $HOME/ so paths expand correctly inside double-quoted
|
||||
// shell commands (~ does NOT expand inside double quotes, causing MODULE_NOT_FOUND).
|
||||
// For local installs: use resolved absolute path (may be outside $HOME).
|
||||
// Exception: OpenCode on Windows does not expand $HOME in @file references —
|
||||
// use the absolute path instead so @$HOME/... references resolve correctly (#2376).
|
||||
const resolvedTarget = path.resolve(targetDir).replace(/\\/g, '/');
|
||||
const homeDir = os.homedir().replace(/\\/g, '/');
|
||||
const pathPrefix = isGlobal && resolvedTarget.startsWith(homeDir)
|
||||
const isWindowsHost = process.platform === 'win32';
|
||||
const pathPrefix = isGlobal && resolvedTarget.startsWith(homeDir) && !(isOpencode && isWindowsHost)
|
||||
? '$HOME' + resolvedTarget.slice(homeDir.length) + '/'
|
||||
: `${resolvedTarget}/`;
|
||||
|
||||
@@ -5737,9 +5961,13 @@ function install(isGlobal, runtime = 'claude') {
|
||||
content = processAttribution(content, getCommitAttribution(runtime));
|
||||
// Convert frontmatter for runtime compatibility (agents need different handling)
|
||||
if (isOpencode) {
|
||||
// Resolve per-agent model override from ~/.gsd/defaults.json (#2256)
|
||||
// Resolve per-agent model override from BOTH per-project
|
||||
// `.planning/config.json` and `~/.gsd/defaults.json`, with
|
||||
// per-project winning on conflict (#2256). Without the per-project
|
||||
// probe, an override set in `.planning/config.json` was silently
|
||||
// ignored and the child inherited OpenCode's default model.
|
||||
const _ocAgentName = entry.name.replace(/\.md$/, '');
|
||||
const _ocModelOverrides = readGsdGlobalModelOverrides();
|
||||
const _ocModelOverrides = readGsdEffectiveModelOverrides(targetDir);
|
||||
const _ocModelOverride = _ocModelOverrides?.[_ocAgentName] || null;
|
||||
content = convertClaudeToOpencodeFrontmatter(content, { isAgent: true, modelOverride: _ocModelOverride });
|
||||
} else if (isKilo) {
|
||||
@@ -6065,9 +6293,13 @@ function install(isGlobal, runtime = 'claude') {
|
||||
return;
|
||||
}
|
||||
const settings = validateHookFields(cleanupOrphanedHooks(rawSettings));
|
||||
// Local installs anchor paths to $CLAUDE_PROJECT_DIR so hooks resolve
|
||||
// correctly regardless of the shell's current working directory (#1906).
|
||||
const localPrefix = '"$CLAUDE_PROJECT_DIR"/' + dirName;
|
||||
// Local installs anchor hook paths so they resolve regardless of cwd (#1906).
|
||||
// Claude Code sets $CLAUDE_PROJECT_DIR; Gemini/Antigravity do not — and on
|
||||
// Windows their own substitution logic doubles the path (#2557). Those runtimes
|
||||
// run project hooks with the project dir as cwd, so bare relative paths work.
|
||||
const localPrefix = (runtime === 'gemini' || runtime === 'antigravity')
|
||||
? dirName
|
||||
: '"$CLAUDE_PROJECT_DIR"/' + dirName;
|
||||
const hookOpts = { portableHooks: hasPortableHooks };
|
||||
const statuslineCommand = isGlobal
|
||||
? buildHookCommand(targetDir, 'gsd-statusline.js', hookOpts)
|
||||
@@ -6638,176 +6870,216 @@ function promptLocation(runtimes) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Build `@gsd-build/sdk` from the in-repo `sdk/` source tree and install the
|
||||
* resulting `gsd-sdk` binary globally so workflow commands that shell out to
|
||||
* `gsd-sdk query …` succeed.
|
||||
* Check whether any common shell rc file already contains a `PATH=` line
|
||||
* whose HOME-expanded value places `globalBin` on PATH (#2620).
|
||||
*
|
||||
* We build from source rather than `npm install -g @gsd-build/sdk` because the
|
||||
* npm-published package lags the source tree and shipping a stale SDK breaks
|
||||
* every /gsd-* command that depends on newer query handlers.
|
||||
* Parses `~/.zshrc`, `~/.bashrc`, `~/.bash_profile`, `~/.profile` (or the
|
||||
* override list in `rcFileNames`), matches `export PATH=` / bare `PATH=`
|
||||
* lines, and substitutes the common HOME forms (`$HOME`, `${HOME}`, `~`)
|
||||
* with `homeDir` before comparing each PATH segment against `globalBin`.
|
||||
*
|
||||
* Skip if --no-sdk. Skip if already on PATH (unless --sdk was explicit).
|
||||
* Failures are FATAL — we exit non-zero so install does not complete with a
|
||||
* silently broken SDK (issue #2439). Set GSD_ALLOW_OFF_PATH=1 to downgrade the
|
||||
* post-install PATH verification to a warning (exit code 2) for users with an
|
||||
* intentionally restricted PATH who will wire things up manually.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Resolve `gsd-sdk` on PATH. Uses `command -v` via `sh -c` on POSIX (portable
|
||||
* across sh/bash/zsh) and `where` on Windows. Returns trimmed path or null.
|
||||
*/
|
||||
function resolveGsdSdk() {
|
||||
const { spawnSync } = require('child_process');
|
||||
if (process.platform === 'win32') {
|
||||
const r = spawnSync('where', ['gsd-sdk'], { encoding: 'utf-8' });
|
||||
if (r.status === 0 && r.stdout && r.stdout.trim()) {
|
||||
return r.stdout.trim().split('\n')[0].trim();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
const r = spawnSync('sh', ['-c', 'command -v gsd-sdk'], { encoding: 'utf-8' });
|
||||
if (r.status === 0 && r.stdout && r.stdout.trim()) {
|
||||
return r.stdout.trim();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Best-effort detection of the user's shell rc file for PATH remediation hints.
|
||||
*/
|
||||
function detectShellRc() {
|
||||
const path = require('path');
|
||||
const shell = process.env.SHELL || '';
|
||||
const home = process.env.HOME || '~';
|
||||
if (/\/zsh$/.test(shell)) return { shell: 'zsh', rc: path.join(home, '.zshrc') };
|
||||
if (/\/bash$/.test(shell)) return { shell: 'bash', rc: path.join(home, '.bashrc') };
|
||||
if (/\/fish$/.test(shell)) return { shell: 'fish', rc: path.join(home, '.config', 'fish', 'config.fish') };
|
||||
return { shell: 'sh', rc: path.join(home, '.profile') };
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit a red fatal banner and exit. Prints actionable PATH remediation when
|
||||
* the global install succeeded but the bin dir is not on PATH.
|
||||
* Best-effort: any unreadable / malformed / non-existent rc file is ignored
|
||||
* and the fallback is the caller's existing absolute-path suggestion. Only
|
||||
* the `$HOME/…`, `${HOME}/…`, and `~/…` forms are handled — we do not try
|
||||
* to fully parse bash syntax.
|
||||
*
|
||||
* If exitCode is 2, this is the "off-PATH" case and GSD_ALLOW_OFF_PATH respect
|
||||
* is applied by the caller; we only print.
|
||||
* @param {string} globalBin Absolute path to npm's global bin directory.
|
||||
* @param {string} homeDir Absolute path used to substitute HOME / ~.
|
||||
* @param {string[]} [rcFileNames] Override the default rc file list.
|
||||
* @returns {boolean} true iff any rc file adds globalBin to PATH.
|
||||
*/
|
||||
function emitSdkFatal(reason, { globalBin, exitCode }) {
|
||||
const { shell, rc } = detectShellRc();
|
||||
const bar = '━'.repeat(72);
|
||||
const redBold = `${red}${bold}`;
|
||||
|
||||
console.error('');
|
||||
console.error(`${redBold}${bar}${reset}`);
|
||||
console.error(`${redBold} ✗ GSD SDK install failed — /gsd-* commands will not work${reset}`);
|
||||
console.error(`${redBold}${bar}${reset}`);
|
||||
console.error(` ${red}Reason:${reset} ${reason}`);
|
||||
|
||||
if (globalBin) {
|
||||
console.error('');
|
||||
console.error(` ${yellow}gsd-sdk was installed to:${reset}`);
|
||||
console.error(` ${cyan}${globalBin}${reset}`);
|
||||
console.error('');
|
||||
console.error(` ${yellow}Your shell's PATH does not include this directory.${reset}`);
|
||||
console.error(` Add it by running:`);
|
||||
if (shell === 'fish') {
|
||||
console.error(` ${cyan}fish_add_path "${globalBin}"${reset}`);
|
||||
console.error(` (or append to ${rc})`);
|
||||
} else {
|
||||
console.error(` ${cyan}echo 'export PATH="${globalBin}:$PATH"' >> ${rc}${reset}`);
|
||||
console.error(` ${cyan}source ${rc}${reset}`);
|
||||
}
|
||||
console.error('');
|
||||
console.error(` Then verify: ${cyan}command -v gsd-sdk${reset}`);
|
||||
if (exitCode === 2) {
|
||||
console.error('');
|
||||
console.error(` ${dim}(GSD_ALLOW_OFF_PATH=1 set → exit ${exitCode} instead of hard failure)${reset}`);
|
||||
}
|
||||
} else {
|
||||
console.error('');
|
||||
console.error(` Build manually to retry:`);
|
||||
console.error(` ${cyan}cd <install-dir>/sdk && npm install && npm run build && npm install -g .${reset}`);
|
||||
}
|
||||
|
||||
console.error(`${redBold}${bar}${reset}`);
|
||||
console.error('');
|
||||
process.exit(exitCode);
|
||||
}
|
||||
|
||||
function installSdkIfNeeded() {
|
||||
if (hasNoSdk) {
|
||||
console.log(`\n ${dim}Skipping GSD SDK install (--no-sdk)${reset}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const { spawnSync } = require('child_process');
|
||||
function homePathCoveredByRc(globalBin, homeDir, rcFileNames) {
|
||||
if (!globalBin || !homeDir) return false;
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
if (!hasSdk) {
|
||||
const resolved = resolveGsdSdk();
|
||||
if (resolved) {
|
||||
console.log(` ${green}✓${reset} GSD SDK already installed (gsd-sdk on PATH at ${resolved})`);
|
||||
return;
|
||||
const normalise = (p) => {
|
||||
if (!p) return '';
|
||||
let n = p.replace(/[\\/]+$/g, '');
|
||||
if (n === '') n = p.startsWith('/') ? '/' : p;
|
||||
return n;
|
||||
};
|
||||
|
||||
const targetAbs = normalise(path.resolve(globalBin));
|
||||
const homeAbs = path.resolve(homeDir);
|
||||
const files = rcFileNames || ['.zshrc', '.bashrc', '.bash_profile', '.profile'];
|
||||
|
||||
const expandHome = (segment) => {
|
||||
let s = segment;
|
||||
s = s.replace(/\$\{HOME\}/g, homeAbs);
|
||||
s = s.replace(/\$HOME/g, homeAbs);
|
||||
if (s.startsWith('~/') || s === '~') {
|
||||
s = s === '~' ? homeAbs : path.join(homeAbs, s.slice(2));
|
||||
}
|
||||
return s;
|
||||
};
|
||||
|
||||
// Match `PATH=…` (optionally prefixed with `export `). The RHS captures
|
||||
// through end-of-line; surrounding quotes are stripped before splitting.
|
||||
const assignRe = /^\s*(?:export\s+)?PATH\s*=\s*(.+?)\s*$/;
|
||||
|
||||
for (const name of files) {
|
||||
const rcPath = path.join(homeAbs, name);
|
||||
let content;
|
||||
try {
|
||||
content = fs.readFileSync(rcPath, 'utf8');
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const rawLine of content.split(/\r?\n/)) {
|
||||
const line = rawLine.replace(/^\s+/, '');
|
||||
if (line.startsWith('#')) continue;
|
||||
|
||||
const m = assignRe.exec(rawLine);
|
||||
if (!m) continue;
|
||||
|
||||
let rhs = m[1];
|
||||
if ((rhs.startsWith('"') && rhs.endsWith('"')) ||
|
||||
(rhs.startsWith("'") && rhs.endsWith("'"))) {
|
||||
rhs = rhs.slice(1, -1);
|
||||
}
|
||||
|
||||
for (const segment of rhs.split(':')) {
|
||||
if (!segment) continue;
|
||||
const trimmed = segment.trim();
|
||||
const expanded = expandHome(trimmed);
|
||||
if (expanded.includes('$')) continue;
|
||||
// Skip segments that are still relative after HOME expansion. A bare
|
||||
// `bin` entry (or `./bin`, `node_modules/.bin`, etc.) depends on the
|
||||
// shell's cwd at lookup time — it is NOT equivalent to `$HOME/bin`,
|
||||
// so resolving against homeAbs would produce false positives.
|
||||
if (!path.isAbsolute(expanded)) continue;
|
||||
try {
|
||||
const abs = normalise(path.resolve(expanded));
|
||||
if (abs === targetAbs) return true;
|
||||
} catch {
|
||||
// ignore unresolvable segments
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Locate the in-repo sdk/ directory relative to this installer file.
|
||||
// For global npm installs this resolves inside the published package dir;
|
||||
// for git-based installs (npx github:..., local clone) it resolves to the
|
||||
// repo's sdk/ tree. Both contain the source tree because root package.json
|
||||
// includes "sdk" in its `files` array.
|
||||
const sdkDir = path.resolve(__dirname, '..', 'sdk');
|
||||
const sdkPackageJson = path.join(sdkDir, 'package.json');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!fs.existsSync(sdkPackageJson)) {
|
||||
emitSdkFatal(`SDK source tree not found at ${sdkDir}.`, { globalBin: null, exitCode: 1 });
|
||||
}
|
||||
/**
|
||||
* Emit a PATH-export suggestion if globalBin is not already on PATH AND
|
||||
* the user's shell rc files do not already cover it via a HOME-relative
|
||||
* entry (#2620).
|
||||
*
|
||||
* Prints one of:
|
||||
* - nothing, if `globalBin` is already present on `process.env.PATH`
|
||||
* - a diagnostic "already covered via rc file" note, if an rc file has
|
||||
* `export PATH="$HOME/…/bin:$PATH"` (or equivalent) and the user just
|
||||
* needs to reopen their shell
|
||||
* - the absolute `echo 'export PATH="…:$PATH"' >> ~/.zshrc` suggestion,
|
||||
* if neither PATH nor any rc file covers globalBin
|
||||
*
|
||||
* Exported for tests; the installer calls this from finishInstall.
|
||||
*
|
||||
* @param {string} globalBin Absolute path to npm's global bin directory.
|
||||
* @param {string} homeDir Absolute HOME path.
|
||||
*/
|
||||
function maybeSuggestPathExport(globalBin, homeDir) {
|
||||
if (!globalBin || !homeDir) return;
|
||||
const path = require('path');
|
||||
|
||||
console.log(`\n ${cyan}Building GSD SDK from source (${sdkDir})…${reset}`);
|
||||
const npmCmd = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
||||
const pathEnv = process.env.PATH || '';
|
||||
const targetAbs = path.resolve(globalBin).replace(/[\\/]+$/g, '') || globalBin;
|
||||
const onPath = pathEnv.split(path.delimiter).some((seg) => {
|
||||
if (!seg) return false;
|
||||
const abs = path.resolve(seg).replace(/[\\/]+$/g, '') || seg;
|
||||
return abs === targetAbs;
|
||||
});
|
||||
if (onPath) return;
|
||||
|
||||
// 1. Install sdk build-time dependencies (tsc, etc.)
|
||||
const installResult = spawnSync(npmCmd, ['install'], { cwd: sdkDir, stdio: 'inherit' });
|
||||
if (installResult.status !== 0) {
|
||||
emitSdkFatal('Failed to `npm install` in sdk/.', { globalBin: null, exitCode: 1 });
|
||||
}
|
||||
|
||||
// 2. Compile TypeScript → sdk/dist/
|
||||
const buildResult = spawnSync(npmCmd, ['run', 'build'], { cwd: sdkDir, stdio: 'inherit' });
|
||||
if (buildResult.status !== 0) {
|
||||
emitSdkFatal('Failed to `npm run build` in sdk/.', { globalBin: null, exitCode: 1 });
|
||||
}
|
||||
|
||||
// 3. Install the built package globally so `gsd-sdk` lands on PATH.
|
||||
const globalResult = spawnSync(npmCmd, ['install', '-g', '.'], { cwd: sdkDir, stdio: 'inherit' });
|
||||
if (globalResult.status !== 0) {
|
||||
emitSdkFatal('Failed to `npm install -g .` from sdk/.', { globalBin: null, exitCode: 1 });
|
||||
}
|
||||
|
||||
// 4. Verify gsd-sdk is actually resolvable on PATH. npm's global bin dir is
|
||||
// not always on the current shell's PATH (Homebrew prefixes, nvm setups,
|
||||
// unconfigured npm prefix), so a zero exit status from `npm install -g`
|
||||
// alone is not proof of a working binary (issue #2439 root cause).
|
||||
const resolved = resolveGsdSdk();
|
||||
if (resolved) {
|
||||
console.log(` ${green}✓${reset} Built and installed GSD SDK from source (gsd-sdk resolved at ${resolved})`);
|
||||
if (homePathCoveredByRc(globalBin, homeDir)) {
|
||||
console.log(` ${yellow}⚠${reset} ${bold}gsd-sdk${reset}'s directory is already on your PATH via an rc file entry — try reopening your shell (or ${cyan}source ~/.zshrc${reset}).`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Off-PATH: resolve npm global bin dir for actionable remediation.
|
||||
const prefixResult = spawnSync(npmCmd, ['config', 'get', 'prefix'], { encoding: 'utf-8' });
|
||||
const prefix = prefixResult.status === 0 ? (prefixResult.stdout || '').trim() : null;
|
||||
const globalBin = prefix
|
||||
? (process.platform === 'win32' ? prefix : path.join(prefix, 'bin'))
|
||||
: null;
|
||||
console.log('');
|
||||
console.log(` ${yellow}⚠${reset} ${bold}${globalBin}${reset} is not on your PATH.`);
|
||||
console.log(` Add it with one of:`);
|
||||
console.log(` ${cyan}echo 'export PATH="${globalBin}:$PATH"' >> ~/.zshrc${reset}`);
|
||||
console.log(` ${cyan}echo 'export PATH="${globalBin}:$PATH"' >> ~/.bashrc${reset}`);
|
||||
console.log('');
|
||||
}
|
||||
|
||||
const allowOffPath = process.env.GSD_ALLOW_OFF_PATH === '1';
|
||||
emitSdkFatal(
|
||||
'Built and installed GSD SDK, but `gsd-sdk` is not on your PATH.',
|
||||
{ globalBin, exitCode: allowOffPath ? 2 : 1 },
|
||||
);
|
||||
/**
|
||||
* Verify the prebuilt SDK dist is present and the gsd-sdk shim is wired up.
|
||||
*
|
||||
* As of fix/2441-sdk-decouple, sdk/dist/ is shipped prebuilt inside the
|
||||
* get-shit-done-cc npm tarball. The parent package declares a bin entry
|
||||
* "gsd-sdk": "bin/gsd-sdk.js" so npm chmods the shim correctly when
|
||||
* installing from a packed tarball — eliminating the mode-644 failure
|
||||
* (issue #2453) and the build-from-source failure modes (#2439, #2441).
|
||||
*
|
||||
* This function verifies the invariant: sdk/dist/cli.js exists and is
|
||||
* executable. If the execute bit is missing (possible in dev/clone setups
|
||||
* where sdk/dist was committed without +x), we fix it in-place.
|
||||
*
|
||||
* --no-sdk skips the check entirely (back-compat).
|
||||
* --sdk forces the check even if it would otherwise be skipped.
|
||||
*/
|
||||
function installSdkIfNeeded() {
|
||||
if (hasNoSdk) {
|
||||
console.log(`\n ${dim}Skipping GSD SDK check (--no-sdk)${reset}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
const sdkCliPath = path.resolve(__dirname, '..', 'sdk', 'dist', 'cli.js');
|
||||
|
||||
if (!fs.existsSync(sdkCliPath)) {
|
||||
const bar = '━'.repeat(72);
|
||||
const redBold = `${red}${bold}`;
|
||||
console.error('');
|
||||
console.error(`${redBold}${bar}${reset}`);
|
||||
console.error(`${redBold} ✗ GSD SDK dist not found — /gsd-* commands will not work${reset}`);
|
||||
console.error(`${redBold}${bar}${reset}`);
|
||||
console.error(` ${red}Reason:${reset} sdk/dist/cli.js not found at ${sdkCliPath}`);
|
||||
console.error('');
|
||||
console.error(` This should not happen with a published tarball install.`);
|
||||
console.error(` If you are running from a git clone, build the SDK first:`);
|
||||
console.error(` ${cyan}cd sdk && npm install && npm run build${reset}`);
|
||||
console.error(`${redBold}${bar}${reset}`);
|
||||
console.error('');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Ensure execute bit is set. tsc emits files at 0o644; git clone preserves
|
||||
// whatever mode was committed. Fix in-place so node-invoked paths work too.
|
||||
try {
|
||||
const stat = fs.statSync(sdkCliPath);
|
||||
const isExecutable = !!(stat.mode & 0o111);
|
||||
if (!isExecutable) {
|
||||
fs.chmodSync(sdkCliPath, stat.mode | 0o111);
|
||||
}
|
||||
} catch {
|
||||
// Non-fatal: if chmod fails (e.g. read-only fs) the shim still works via
|
||||
// `node sdkCliPath` invocation in bin/gsd-sdk.js.
|
||||
}
|
||||
|
||||
console.log(` ${green}✓${reset} GSD SDK ready (sdk/dist/cli.js)`);
|
||||
|
||||
// #2620: warn if npm's global bin is not on PATH, suppressing the
|
||||
// absolute-path suggestion when the user's rc already covers it via
|
||||
// a HOME-relative entry (e.g. `export PATH="$HOME/.npm-global/bin:$PATH"`).
|
||||
try {
|
||||
const { execSync } = require('child_process');
|
||||
const npmPrefix = execSync('npm prefix -g', { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
|
||||
if (npmPrefix) {
|
||||
// On Windows npm prefix IS the bin dir; on POSIX it's `${prefix}/bin`.
|
||||
const globalBin = process.platform === 'win32' ? npmPrefix : path.join(npmPrefix, 'bin');
|
||||
maybeSuggestPathExport(globalBin, os.homedir());
|
||||
}
|
||||
} catch {
|
||||
// npm not available / exec failed — silently skip the PATH advice.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -6825,13 +7097,10 @@ function installAllRuntimes(runtimes, isGlobal, isInteractive) {
|
||||
const primaryStatuslineResult = results.find(r => statuslineRuntimes.includes(r.runtime));
|
||||
|
||||
const finalize = (shouldInstallStatusline) => {
|
||||
// Build @gsd-build/sdk from the in-repo sdk/ source and install it globally
|
||||
// so `gsd-sdk` lands on PATH. Every /gsd-* command shells out to
|
||||
// `gsd-sdk query …`; without this, commands fail with "command not found:
|
||||
// gsd-sdk". The npm-published @gsd-build/sdk is kept intentionally frozen
|
||||
// at an older version; we always build from source so users get the SDK
|
||||
// that matches the installed GSD version.
|
||||
// Runs by default; skip with --no-sdk. Idempotent when already present.
|
||||
// Verify sdk/dist/cli.js is present and executable. The dist is shipped
|
||||
// prebuilt in the tarball (fix/2441-sdk-decouple); gsd-sdk reaches users via
|
||||
// the parent package's bin/gsd-sdk.js shim, so no sub-install is needed.
|
||||
// Skip with --no-sdk.
|
||||
installSdkIfNeeded();
|
||||
|
||||
const printSummaries = () => {
|
||||
@@ -6873,6 +7142,8 @@ if (process.env.GSD_TEST_MODE) {
|
||||
stripGsdFromCodexConfig,
|
||||
mergeCodexConfig,
|
||||
installCodexConfig,
|
||||
readGsdRuntimeProfileResolver,
|
||||
readGsdEffectiveModelOverrides,
|
||||
install,
|
||||
uninstall,
|
||||
convertClaudeCommandToCodexSkill,
|
||||
@@ -6927,11 +7198,23 @@ if (process.env.GSD_TEST_MODE) {
|
||||
preserveUserArtifacts,
|
||||
restoreUserArtifacts,
|
||||
finishInstall,
|
||||
homePathCoveredByRc,
|
||||
maybeSuggestPathExport,
|
||||
};
|
||||
} else {
|
||||
|
||||
// Main logic
|
||||
if (hasGlobal && hasLocal) {
|
||||
if (hasSkillsRoot) {
|
||||
// Print the skills root directory for a given runtime (used by /gsd-sync-skills).
|
||||
// Usage: node install.js --skills-root <runtime>
|
||||
const runtimeArg = args[args.indexOf('--skills-root') + 1];
|
||||
if (!runtimeArg || runtimeArg.startsWith('--')) {
|
||||
console.error('Usage: node install.js --skills-root <runtime>');
|
||||
process.exit(1);
|
||||
}
|
||||
const globalDir = getGlobalDir(runtimeArg, null);
|
||||
console.log(path.join(globalDir, 'skills'));
|
||||
} else if (hasGlobal && hasLocal) {
|
||||
console.error(` ${yellow}Cannot specify both --global and --local${reset}`);
|
||||
process.exit(1);
|
||||
} else if (explicitConfigDir && hasLocal) {
|
||||
|
||||
@@ -42,7 +42,7 @@ the normal phase sequence and accumulate context over time.
|
||||
**Plans:** 0 plans
|
||||
|
||||
Plans:
|
||||
- [ ] TBD (promote with /gsd-review-backlog when ready)
|
||||
- [ ] TBD (promote with /gsd:review-backlog when ready)
|
||||
```
|
||||
|
||||
4. **Create the phase directory:**
|
||||
@@ -65,15 +65,15 @@ the normal phase sequence and accumulate context over time.
|
||||
Directory: .planning/phases/{NEXT}-{slug}/
|
||||
|
||||
This item lives in the backlog parking lot.
|
||||
Use /gsd-discuss-phase {NEXT} to explore it further.
|
||||
Use /gsd-review-backlog to promote items to active milestone.
|
||||
Use /gsd:discuss-phase {NEXT} to explore it further.
|
||||
Use /gsd:review-backlog to promote items to active milestone.
|
||||
```
|
||||
|
||||
</process>
|
||||
|
||||
<notes>
|
||||
- 999.x numbering keeps backlog items out of the active phase sequence
|
||||
- Phase directories are created immediately, so /gsd-discuss-phase and /gsd-plan-phase work on them
|
||||
- Phase directories are created immediately, so /gsd:discuss-phase and /gsd:plan-phase work on them
|
||||
- No `Depends on:` field — backlog items are unsequenced by definition
|
||||
- Sparse numbering is fine (999.1, 999.3) — always uses next-decimal
|
||||
</notes>
|
||||
|
||||
@@ -13,8 +13,8 @@ allowed-tools:
|
||||
- AskUserQuestion
|
||||
argument-instructions: |
|
||||
Parse the argument as a phase number (integer, decimal, or letter-suffix), plus optional free-text instructions.
|
||||
Example: /gsd-add-tests 12
|
||||
Example: /gsd-add-tests 12 focus on edge cases in the pricing module
|
||||
Example: /gsd:add-tests 12
|
||||
Example: /gsd:add-tests 12 focus on edge cases in the pricing module
|
||||
---
|
||||
<objective>
|
||||
Generate unit and E2E tests for a completed phase, using its SUMMARY.md, CONTEXT.md, and VERIFICATION.md as specifications.
|
||||
|
||||
@@ -25,7 +25,7 @@ Then suggest `Depends on` updates to ROADMAP.md.
|
||||
<context>
|
||||
No arguments required. Requires an active milestone with ROADMAP.md.
|
||||
|
||||
Run this command BEFORE `/gsd-manager` to fill in missing `Depends on` fields and prevent merge conflicts from unordered parallel execution.
|
||||
Run this command BEFORE `/gsd:manager` to fill in missing `Depends on` fields and prevent merge conflicts from unordered parallel execution.
|
||||
</context>
|
||||
|
||||
<process>
|
||||
|
||||
@@ -42,19 +42,19 @@ Output: Milestone archived (roadmap + requirements), PROJECT.md evolved, git tag
|
||||
0. **Check for audit:**
|
||||
|
||||
- Look for `.planning/v{{version}}-MILESTONE-AUDIT.md`
|
||||
- If missing or stale: recommend `/gsd-audit-milestone` first
|
||||
- If audit status is `gaps_found`: recommend `/gsd-plan-milestone-gaps` first
|
||||
- If missing or stale: recommend `/gsd:audit-milestone` first
|
||||
- If audit status is `gaps_found`: recommend `/gsd:plan-milestone-gaps` first
|
||||
- If audit status is `passed`: proceed to step 1
|
||||
|
||||
```markdown
|
||||
## Pre-flight Check
|
||||
|
||||
{If no v{{version}}-MILESTONE-AUDIT.md:}
|
||||
⚠ No milestone audit found. Run `/gsd-audit-milestone` first to verify
|
||||
⚠ No milestone audit found. Run `/gsd:audit-milestone` first to verify
|
||||
requirements coverage, cross-phase integration, and E2E flows.
|
||||
|
||||
{If audit has gaps:}
|
||||
⚠ Milestone audit found gaps. Run `/gsd-plan-milestone-gaps` to create
|
||||
⚠ Milestone audit found gaps. Run `/gsd:plan-milestone-gaps` to create
|
||||
phases that close the gaps, or proceed anyway to accept as tech debt.
|
||||
|
||||
{If audit passed:}
|
||||
@@ -108,7 +108,7 @@ Output: Milestone archived (roadmap + requirements), PROJECT.md evolved, git tag
|
||||
- Ask about pushing tag
|
||||
|
||||
8. **Offer next steps:**
|
||||
- `/gsd-new-milestone` — start next milestone (questioning → research → requirements → roadmap)
|
||||
- `/gsd:new-milestone` — start next milestone (questioning → research → requirements → roadmap)
|
||||
|
||||
</process>
|
||||
|
||||
@@ -132,5 +132,5 @@ Output: Milestone archived (roadmap + requirements), PROJECT.md evolved, git tag
|
||||
- **Archive before deleting:** Always create archive files before updating/deleting originals
|
||||
- **One-line summary:** Collapsed milestone in ROADMAP.md should be single line with link
|
||||
- **Context efficiency:** Archive keeps ROADMAP.md and REQUIREMENTS.md constant size per milestone
|
||||
- **Fresh requirements:** Next milestone starts with `/gsd-new-milestone` which includes requirements definition
|
||||
- **Fresh requirements:** Next milestone starts with `/gsd:new-milestone` which includes requirements definition
|
||||
</critical_rules>
|
||||
|
||||
@@ -88,11 +88,11 @@ Active Debug Sessions
|
||||
hypothesis: Missing null check on req.body.user
|
||||
next: Verify fix passes regression test
|
||||
─────────────────────────────────────────────
|
||||
Run `/gsd-debug continue <slug>` to resume a session.
|
||||
No sessions? `/gsd-debug <description>` to start.
|
||||
Run `/gsd:debug continue <slug>` to resume a session.
|
||||
No sessions? `/gsd:debug <description>` to start.
|
||||
```
|
||||
|
||||
If no files exist or the glob returns nothing: print "No active debug sessions. Run `/gsd-debug <issue description>` to start one."
|
||||
If no files exist or the glob returns nothing: print "No active debug sessions. Run `/gsd:debug <issue description>` to start one."
|
||||
|
||||
STOP after displaying list. Do NOT proceed to further steps.
|
||||
|
||||
@@ -117,7 +117,7 @@ No agent spawn. Just information display. STOP after printing.
|
||||
|
||||
When SUBCMD=continue and SLUG is set:
|
||||
|
||||
Check `.planning/debug/{SLUG}.md` exists. If not, print "No active debug session found with slug: {SLUG}. Check `/gsd-debug list` for active sessions." and stop.
|
||||
Check `.planning/debug/{SLUG}.md` exists. If not, print "No active debug session found with slug: {SLUG}. Check `/gsd:debug list` for active sessions." and stop.
|
||||
|
||||
Read file and print Current Focus block to console:
|
||||
|
||||
@@ -247,7 +247,7 @@ specialist_dispatch_enabled: true
|
||||
Display the compact summary returned by the session manager.
|
||||
|
||||
If summary shows `DEBUG SESSION COMPLETE`: done.
|
||||
If summary shows `ABANDONED`: note session saved at `.planning/debug/{slug}.md` for later `/gsd-debug continue {slug}`.
|
||||
If summary shows `ABANDONED`: note session saved at `.planning/debug/{slug}.md` for later `/gsd:debug continue {slug}`.
|
||||
|
||||
</process>
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ Open-ended Socratic ideation session. Guides the developer through exploring an
|
||||
probing questions, optionally spawns research, then routes outputs to the appropriate GSD
|
||||
artifacts (notes, todos, seeds, research questions, requirements, or new phases).
|
||||
|
||||
Accepts an optional topic argument: `/gsd-explore authentication strategy`
|
||||
Accepts an optional topic argument: `/gsd:explore authentication strategy`
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
|
||||
@@ -16,8 +16,8 @@ Execute a trivial task directly in the current context without spawning subagent
|
||||
or generating PLAN.md files. For tasks too small to justify planning overhead:
|
||||
typo fixes, config changes, small refactors, forgotten commits, simple additions.
|
||||
|
||||
This is NOT a replacement for /gsd-quick — use /gsd-quick for anything that
|
||||
needs research, multi-step planning, or verification. /gsd-fast is for tasks
|
||||
This is NOT a replacement for /gsd:quick — use /gsd:quick for anything that
|
||||
needs research, multi-step planning, or verification. /gsd:fast is for tasks
|
||||
you could describe in one sentence and execute in under 2 minutes.
|
||||
</objective>
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ Knowledge graph is disabled. To activate:
|
||||
|
||||
node $HOME/.claude/get-shit-done/bin/gsd-tools.cjs config-set graphify.enabled true
|
||||
|
||||
Then run /gsd-graphify build to create the initial graph.
|
||||
Then run /gsd:graphify build to create the initial graph.
|
||||
```
|
||||
|
||||
---
|
||||
@@ -65,7 +65,7 @@ Parse `$ARGUMENTS` to determine the operation mode:
|
||||
```
|
||||
GSD > GRAPHIFY
|
||||
|
||||
Usage: /gsd-graphify <mode>
|
||||
Usage: /gsd:graphify <mode>
|
||||
|
||||
Modes:
|
||||
build Build or rebuild the knowledge graph
|
||||
@@ -85,7 +85,7 @@ node $HOME/.claude/get-shit-done/bin/gsd-tools.cjs graphify query <term>
|
||||
Parse the JSON output and display results:
|
||||
- If the output contains `"disabled": true`, display the disabled message from Step 1 and **STOP**
|
||||
- If the output contains `"error"` field, display the error message and **STOP**
|
||||
- If no nodes found, display: `No graph matches for '<term>'. Try /gsd-graphify build to create or rebuild the graph.`
|
||||
- If no nodes found, display: `No graph matches for '<term>'. Try /gsd:graphify build to create or rebuild the graph.`
|
||||
- Otherwise, display matched nodes grouped by type, with edge relationships and confidence tiers (EXTRACTED/INFERRED/AMBIGUOUS)
|
||||
|
||||
**STOP** after displaying results. Do not spawn an agent.
|
||||
|
||||
@@ -41,7 +41,7 @@ Intel system is disabled. To activate:
|
||||
|
||||
gsd-sdk query config-set intel.enabled true
|
||||
|
||||
Then run /gsd-intel refresh to build the initial index.
|
||||
Then run /gsd:intel refresh to build the initial index.
|
||||
```
|
||||
|
||||
---
|
||||
@@ -63,7 +63,7 @@ Parse `$ARGUMENTS` to determine the operation mode:
|
||||
```
|
||||
GSD > INTEL
|
||||
|
||||
Usage: /gsd-intel <mode>
|
||||
Usage: /gsd:intel <mode>
|
||||
|
||||
Modes:
|
||||
query <term> Search intel files for a term
|
||||
@@ -82,7 +82,7 @@ gsd-sdk query intel.query <term>
|
||||
|
||||
Parse the JSON output and display results:
|
||||
- If the output contains `"disabled": true`, display the disabled message from Step 1 and **STOP**
|
||||
- If no matches found, display: `No intel matches for '<term>'. Try /gsd-intel refresh to build the index.`
|
||||
- If no matches found, display: `No intel matches for '<term>'. Try /gsd:intel refresh to build the index.`
|
||||
- Otherwise, display matching entries grouped by intel file
|
||||
|
||||
**STOP** after displaying results. Do not spawn an agent.
|
||||
|
||||
@@ -30,8 +30,8 @@ Focus area: $ARGUMENTS (optional - if provided, tells agents to focus on specifi
|
||||
Check for .planning/STATE.md - loads context if project already initialized
|
||||
|
||||
**This command can run:**
|
||||
- Before /gsd-new-project (brownfield codebases) - creates codebase map first
|
||||
- After /gsd-new-project (greenfield codebases) - updates codebase map as code evolves
|
||||
- Before /gsd:new-project (brownfield codebases) - creates codebase map first
|
||||
- After /gsd:new-project (greenfield codebases) - updates codebase map as code evolves
|
||||
- Anytime to refresh codebase understanding
|
||||
</context>
|
||||
|
||||
@@ -59,7 +59,7 @@ Check for .planning/STATE.md - loads context if project already initialized
|
||||
4. Wait for agents to complete, collect confirmations (NOT document contents)
|
||||
5. Verify all 7 documents exist with line counts
|
||||
6. Commit codebase map
|
||||
7. Offer next steps (typically: /gsd-new-project or /gsd-plan-phase)
|
||||
7. Offer next steps (typically: /gsd:new-project or /gsd:plan-phase)
|
||||
</process>
|
||||
|
||||
<success_criteria>
|
||||
|
||||
@@ -21,7 +21,7 @@ Brownfield equivalent of new-project. Project exists, PROJECT.md has history. Ga
|
||||
- `.planning/ROADMAP.md` — phase structure (continues numbering)
|
||||
- `.planning/STATE.md` — reset for new milestone
|
||||
|
||||
**After:** `/gsd-plan-phase [N]` to start execution.
|
||||
**After:** `/gsd:plan-phase [N]` to start execution.
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
|
||||
@@ -29,7 +29,7 @@ Initialize a new project through unified flow: questioning → research (optiona
|
||||
- `.planning/ROADMAP.md` — phase structure
|
||||
- `.planning/STATE.md` — project memory
|
||||
|
||||
**After this command:** Run `/gsd-plan-phase 1` to start execution.
|
||||
**After this command:** Run `/gsd:plan-phase 1` to start execution.
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
|
||||
@@ -30,7 +30,7 @@ Create a physical workspace directory containing copies of specified git repos (
|
||||
- `<path>/.planning/` — independent planning directory
|
||||
- `<path>/<repo>/` — git worktree or clone for each specified repo
|
||||
|
||||
**After this command:** `cd` into the workspace and run `/gsd-new-project` to initialize GSD.
|
||||
**After this command:** `cd` into the workspace and run `/gsd:new-project` to initialize GSD.
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
|
||||
@@ -10,11 +10,11 @@ allowed-tools:
|
||||
- AskUserQuestion
|
||||
---
|
||||
<objective>
|
||||
Create all phases necessary to close gaps identified by `/gsd-audit-milestone`.
|
||||
Create all phases necessary to close gaps identified by `/gsd:audit-milestone`.
|
||||
|
||||
Reads MILESTONE-AUDIT.md, groups gaps into logical phases, creates phase entries in ROADMAP.md, and offers to plan each phase.
|
||||
|
||||
One command creates all fix phases — no manual `/gsd-add-phase` per gap.
|
||||
One command creates all fix phases — no manual `/gsd:add-phase` per gap.
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
|
||||
@@ -40,7 +40,7 @@ Phase number: $ARGUMENTS (optional — auto-detects next unplanned phase if omit
|
||||
- `--gaps` — Gap closure mode (reads VERIFICATION.md, skips research)
|
||||
- `--skip-verify` — Skip verification loop
|
||||
- `--prd <file>` — Use a PRD/acceptance criteria file instead of discuss-phase. Parses requirements into CONTEXT.md automatically. Skips discuss-phase entirely.
|
||||
- `--reviews` — Replan incorporating cross-AI review feedback from REVIEWS.md (produced by `/gsd-review`)
|
||||
- `--reviews` — Replan incorporating cross-AI review feedback from REVIEWS.md (produced by `/gsd:review`)
|
||||
- `--text` — Use plain-text numbered lists instead of TUI menus (required for `/rc` remote sessions)
|
||||
|
||||
Normalize phase input in step 2 before any directory lookups.
|
||||
|
||||
@@ -16,7 +16,7 @@ milestone arrives. Seeds solve context rot: instead of a one-liner in Deferred t
|
||||
reads, a seed preserves the full WHY, WHEN to surface, and breadcrumbs to details.
|
||||
|
||||
Creates: .planning/seeds/SEED-NNN-slug.md
|
||||
Consumed by: /gsd-new-milestone (scans seeds and presents matches)
|
||||
Consumed by: /gsd:new-milestone (scans seeds and presents matches)
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
|
||||
@@ -118,7 +118,7 @@ Status: {status from SUMMARY.md frontmatter, or "no summary yet"}
|
||||
Description: {first non-empty line from PLAN.md after frontmatter}
|
||||
Last action: {last meaningful line of SUMMARY.md, or "none"}
|
||||
─────────────────────────────────────
|
||||
Resume with: /gsd-quick resume {slug}
|
||||
Resume with: /gsd:quick resume {slug}
|
||||
```
|
||||
|
||||
No agent spawn. STOP after printing.
|
||||
|
||||
@@ -115,7 +115,7 @@ Read `backup-meta.json` from the patches directory.
|
||||
```
|
||||
No local patches found. Nothing to reapply.
|
||||
|
||||
Local patches are automatically saved when you run /gsd-update
|
||||
Local patches are automatically saved when you run /gsd:update
|
||||
after modifying any GSD workflow, command, or agent files.
|
||||
```
|
||||
Exit.
|
||||
@@ -129,7 +129,7 @@ The quality of the merge depends on having a **pristine baseline** — the origi
|
||||
|
||||
Check for baseline sources in priority order:
|
||||
|
||||
### Option A: Git history (most reliable)
|
||||
### Option A: Pristine hash from backup-meta.json + git history (most reliable)
|
||||
If the config directory is a git repository:
|
||||
```bash
|
||||
CONFIG_DIR=$(dirname "$PATCHES_DIR")
|
||||
@@ -137,15 +137,35 @@ if git -C "$CONFIG_DIR" rev-parse --git-dir >/dev/null 2>&1; then
|
||||
HAS_GIT=true
|
||||
fi
|
||||
```
|
||||
When `HAS_GIT=true`, use `git log` to find the commit where GSD was originally installed (before user edits). For each file, the pristine baseline can be extracted with:
|
||||
When `HAS_GIT=true`, use the `pristine_hashes` recorded in `backup-meta.json` to locate the correct baseline commit. For each file, iterate commits that touched it and find the one whose blob SHA-256 matches the recorded pristine hash:
|
||||
```bash
|
||||
git -C "$CONFIG_DIR" log --diff-filter=A --format="%H" -- "{file_path}"
|
||||
# Get the expected pristine SHA-256 from backup-meta.json
|
||||
PRISTINE_HASH=$(jq -r ".pristine_hashes[\"${file_path}\"] // empty" "$PATCHES_DIR/backup-meta.json")
|
||||
|
||||
BASELINE_COMMIT=""
|
||||
if [ -n "$PRISTINE_HASH" ]; then
|
||||
# Walk commits that touched this file, pick the one matching the pristine hash
|
||||
while IFS= read -r commit_hash; do
|
||||
blob_hash=$(git -C "$CONFIG_DIR" show "${commit_hash}:${file_path}" 2>/dev/null | sha256sum | cut -d' ' -f1)
|
||||
if [ "$blob_hash" = "$PRISTINE_HASH" ]; then
|
||||
BASELINE_COMMIT="$commit_hash"
|
||||
break
|
||||
fi
|
||||
done < <(git -C "$CONFIG_DIR" log --format="%H" -- "${file_path}")
|
||||
fi
|
||||
|
||||
# Fallback: if no pristine hash in backup-meta (older installer), use first-add commit
|
||||
if [ -z "$BASELINE_COMMIT" ]; then
|
||||
BASELINE_COMMIT=$(git -C "$CONFIG_DIR" log --diff-filter=A --format="%H" -- "${file_path}" | tail -1)
|
||||
fi
|
||||
```
|
||||
This gives the commit that first added the file (the install commit). Extract the pristine version:
|
||||
Extract the pristine version from the matched commit:
|
||||
```bash
|
||||
git -C "$CONFIG_DIR" show {install_commit}:{file_path}
|
||||
git -C "$CONFIG_DIR" show "${BASELINE_COMMIT}:${file_path}"
|
||||
```
|
||||
|
||||
**Why this matters:** `git log --diff-filter=A` returns the commit that *first added* the file, which is the wrong baseline on repos that have been through multiple GSD update cycles. The `pristine_hashes` field in `backup-meta.json` records the SHA-256 of the file as it existed in the pre-update GSD release — matching against it finds the correct baseline regardless of how many updates have occurred.
|
||||
|
||||
### Option B: Pristine snapshot directory
|
||||
Check if a `gsd-pristine/` directory exists alongside `gsd-local-patches/`:
|
||||
```bash
|
||||
@@ -258,7 +278,7 @@ Before proceeding to cleanup, evaluate the Hunk Verification Table produced in S
|
||||
**If the Hunk Verification Table is absent** (Step 4 did not produce it), STOP immediately and report to the user:
|
||||
```
|
||||
ERROR: Hunk Verification Table is missing. Post-merge verification was not completed.
|
||||
Rerun /gsd-reapply-patches to retry with full verification.
|
||||
Rerun /gsd:reapply-patches to retry with full verification.
|
||||
```
|
||||
|
||||
**If any row in the Hunk Verification Table shows `verified: no`**, STOP and report to the user:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: gsd:research-phase
|
||||
description: Research how to implement a phase (standalone - usually use /gsd-plan-phase instead)
|
||||
description: Research how to implement a phase (standalone - usually use /gsd:plan-phase instead)
|
||||
argument-hint: "[phase]"
|
||||
allowed-tools:
|
||||
- Read
|
||||
@@ -11,7 +11,7 @@ allowed-tools:
|
||||
<objective>
|
||||
Research how to implement a phase. Spawns gsd-phase-researcher agent with phase context.
|
||||
|
||||
**Note:** This is a standalone research command. For most workflows, use `/gsd-plan-phase` which integrates research automatically.
|
||||
**Note:** This is a standalone research command. For most workflows, use `/gsd:plan-phase` which integrates research automatically.
|
||||
|
||||
**Use this command when:**
|
||||
- You want to research without planning yet
|
||||
@@ -115,7 +115,7 @@ Mode: ecosystem
|
||||
</additional_context>
|
||||
|
||||
<downstream_consumer>
|
||||
Your RESEARCH.md will be loaded by `/gsd-plan-phase` which uses specific sections:
|
||||
Your RESEARCH.md will be loaded by `/gsd:plan-phase` which uses specific sections:
|
||||
- `## Standard Stack` → Plans use these libraries
|
||||
- `## Architecture Patterns` → Task structure follows these
|
||||
- `## Don't Hand-Roll` → Tasks NEVER build custom solutions for listed problems
|
||||
|
||||
@@ -13,7 +13,7 @@ allowed-tools:
|
||||
<objective>
|
||||
Invoke external AI CLIs (Gemini, Claude, Codex, OpenCode, Qwen Code, Cursor) to independently review phase plans.
|
||||
Produces a structured REVIEWS.md with per-reviewer feedback that can be fed back into
|
||||
planning via /gsd-plan-phase --reviews.
|
||||
planning via /gsd:plan-phase --reviews.
|
||||
|
||||
**Flow:** Detect CLIs → Build review prompt → Invoke each CLI → Collect responses → Write REVIEWS.md
|
||||
</objective>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: gsd:scan
|
||||
description: Rapid codebase assessment — lightweight alternative to /gsd-map-codebase
|
||||
description: Rapid codebase assessment — lightweight alternative to /gsd:map-codebase
|
||||
allowed-tools:
|
||||
- Read
|
||||
- Write
|
||||
@@ -14,7 +14,7 @@ allowed-tools:
|
||||
Run a focused codebase scan for a single area, producing targeted documents in `.planning/codebase/`.
|
||||
Accepts an optional `--focus` flag: `tech`, `arch`, `quality`, `concerns`, or `tech+arch` (default).
|
||||
|
||||
Lightweight alternative to `/gsd-map-codebase` — spawns one mapper agent instead of four parallel ones.
|
||||
Lightweight alternative to `/gsd:map-codebase` — spawns one mapper agent instead of four parallel ones.
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
|
||||
@@ -9,4 +9,4 @@ allowed-tools:
|
||||
|
||||
Show the following output to the user verbatim, with no extra commentary:
|
||||
|
||||
!`if ! command -v gsd-sdk >/dev/null 2>&1; then printf '⚠ gsd-sdk not found in PATH — /gsd-set-profile requires it.\n\nInstall the GSD SDK:\n npm install -g @gsd-build/sdk\n\nOr update GSD to get the latest packages:\n /gsd-update\n'; exit 1; fi; gsd-sdk query config-set-model-profile $ARGUMENTS --raw`
|
||||
!`if ! command -v gsd-sdk >/dev/null 2>&1; then printf '⚠ gsd-sdk not found in PATH — /gsd:set-profile requires it.\n\nInstall the GSD SDK:\n npm install -g @gsd-build/sdk\n\nOr update GSD to get the latest packages:\n /gsd:update\n'; exit 1; fi; gsd-sdk query config-set-model-profile $ARGUMENTS --raw`
|
||||
|
||||
39
commands/gsd/settings-advanced.md
Normal file
39
commands/gsd/settings-advanced.md
Normal file
@@ -0,0 +1,39 @@
|
||||
---
|
||||
name: gsd:settings-advanced
|
||||
description: Power-user configuration — plan bounce, timeouts, branch templates, cross-AI execution, runtime knobs
|
||||
allowed-tools:
|
||||
- Read
|
||||
- Write
|
||||
- Bash
|
||||
- AskUserQuestion
|
||||
---
|
||||
|
||||
<objective>
|
||||
Interactive configuration of GSD power-user knobs that don't belong in the common-case `/gsd:settings` prompt.
|
||||
|
||||
Routes to the settings-advanced workflow which handles:
|
||||
- Config existence ensuring (workstream-aware path resolution)
|
||||
- Current settings reading and parsing
|
||||
- Sectioned prompts: Planning Tuning, Execution Tuning, Discussion Tuning, Cross-AI Execution, Git Customization, Runtime / Output
|
||||
- Config merging that preserves every unrelated key
|
||||
- Confirmation table display
|
||||
|
||||
Use `/gsd:settings` for the common-case toggles (model profile, research/plan_check/verifier, branching strategy, context warnings). Use `/gsd:settings-advanced` once those are set and you want to tune the internals.
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@~/.claude/get-shit-done/workflows/settings-advanced.md
|
||||
</execution_context>
|
||||
|
||||
<process>
|
||||
**Follow the settings-advanced workflow** from `@~/.claude/get-shit-done/workflows/settings-advanced.md`.
|
||||
|
||||
The workflow handles all logic including:
|
||||
1. Config file creation with defaults if missing (via `gsd-sdk query config-ensure-section`)
|
||||
2. Current config reading
|
||||
3. Six sectioned AskUserQuestion batches with current values pre-selected
|
||||
4. Numeric-input validation (non-numeric rejected, empty input keeps current)
|
||||
5. Answer parsing and config merging (preserves unrelated keys)
|
||||
6. File writing (atomic)
|
||||
7. Confirmation table display
|
||||
</process>
|
||||
44
commands/gsd/settings-integrations.md
Normal file
44
commands/gsd/settings-integrations.md
Normal file
@@ -0,0 +1,44 @@
|
||||
---
|
||||
name: gsd:settings-integrations
|
||||
description: Configure third-party API keys, code-review CLI routing, and agent-skill injection
|
||||
allowed-tools:
|
||||
- Read
|
||||
- Write
|
||||
- Bash
|
||||
- AskUserQuestion
|
||||
---
|
||||
|
||||
<objective>
|
||||
Interactive configuration of GSD's third-party integration surface:
|
||||
- Search API keys: `brave_search`, `firecrawl`, `exa_search`, and
|
||||
the `search_gitignored` toggle
|
||||
- Code-review CLI routing: `review.models.{claude,codex,gemini,opencode}`
|
||||
- Agent-skill injection: `agent_skills.<agent-type>`
|
||||
|
||||
API keys are stored plaintext in `.planning/config.json` but are masked
|
||||
(`****<last-4>`) in every piece of interactive output. The workflow never
|
||||
echoes plaintext to stdout, stderr, or any log.
|
||||
|
||||
This command is deliberately distinct from `/gsd:settings` (workflow toggles)
|
||||
and any `/gsd:settings-advanced` tuning surface. It handles *connectivity*,
|
||||
not pipeline shape.
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@~/.claude/get-shit-done/workflows/settings-integrations.md
|
||||
</execution_context>
|
||||
|
||||
<process>
|
||||
**Follow the settings-integrations workflow** from
|
||||
`@~/.claude/get-shit-done/workflows/settings-integrations.md`.
|
||||
|
||||
The workflow handles:
|
||||
1. Resolving `$GSD_CONFIG_PATH` (flat vs workstream)
|
||||
2. Reading current integration values (masked for display)
|
||||
3. Section 1 — Search Integrations: Brave / Firecrawl / Exa / search_gitignored
|
||||
4. Section 2 — Review CLI Routing: review.models.{claude,codex,gemini,opencode}
|
||||
5. Section 3 — Agent Skills Injection: agent_skills.<agent-type>
|
||||
6. Writing values via `gsd-sdk query config-set` (which merges, preserving
|
||||
unrelated keys)
|
||||
7. Masked confirmation display
|
||||
</process>
|
||||
@@ -11,7 +11,7 @@ allowed-tools:
|
||||
- AskUserQuestion
|
||||
---
|
||||
<objective>
|
||||
Bridge local completion → merged PR. After /gsd-verify-work passes, ship the work: push branch, create PR with auto-generated body, optionally trigger review, and track the merge.
|
||||
Bridge local completion → merged PR. After /gsd:verify-work passes, ship the work: push branch, create PR with auto-generated body, optionally trigger review, and track the merge.
|
||||
|
||||
Closes the plan → execute → verify → ship loop.
|
||||
</objective>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: gsd:sketch
|
||||
description: Rapidly sketch UI/design ideas using throwaway HTML mockups with multi-variant exploration
|
||||
argument-hint: "<design idea to explore> [--quick]"
|
||||
description: Sketch UI/design ideas with throwaway HTML mockups, or propose what to sketch next (frontier mode)
|
||||
argument-hint: "[design idea to explore] [--quick] [--text] or [frontier]"
|
||||
allowed-tools:
|
||||
- Read
|
||||
- Write
|
||||
@@ -10,13 +10,22 @@ allowed-tools:
|
||||
- Grep
|
||||
- Glob
|
||||
- AskUserQuestion
|
||||
- WebSearch
|
||||
- WebFetch
|
||||
- mcp__context7__resolve-library-id
|
||||
- mcp__context7__query-docs
|
||||
---
|
||||
<objective>
|
||||
Explore design directions through throwaway HTML mockups before committing to implementation.
|
||||
Each sketch produces 2-3 variants for comparison. Sketches live in `.planning/sketches/` and
|
||||
integrate with GSD commit patterns, state tracking, and handoff workflows.
|
||||
integrate with GSD commit patterns, state tracking, and handoff workflows. Loads spike
|
||||
findings to ground mockups in real data shapes and validated interaction patterns.
|
||||
|
||||
Does not require `/gsd-new-project` — auto-creates `.planning/sketches/` if needed.
|
||||
Two modes:
|
||||
- **Idea mode** (default) — describe a design idea to sketch
|
||||
- **Frontier mode** (no argument or "frontier") — analyzes existing sketch landscape and proposes consistency and frontier sketches
|
||||
|
||||
Does not require `/gsd:new-project` — auto-creates `.planning/sketches/` if needed.
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@@ -41,5 +50,5 @@ Design idea: $ARGUMENTS
|
||||
|
||||
<process>
|
||||
Execute the sketch workflow from @~/.claude/get-shit-done/workflows/sketch.md end-to-end.
|
||||
Preserve all workflow gates (intake, decomposition, variant evaluation, MANIFEST updates, commit patterns).
|
||||
Preserve all workflow gates (intake, decomposition, target stack research, variant evaluation, MANIFEST updates, commit patterns).
|
||||
</process>
|
||||
|
||||
@@ -58,5 +58,5 @@ Execute the spec-phase workflow from @~/.claude/get-shit-done/workflows/spec-pha
|
||||
- Gate passed: ambiguity ≤ 0.20 AND all dimension minimums met
|
||||
- SPEC.md written with falsifiable requirements, explicit boundaries, and acceptance criteria
|
||||
- SPEC.md committed atomically
|
||||
- User knows they can now run /gsd-discuss-phase which will load SPEC.md automatically
|
||||
- User knows they can now run /gsd:discuss-phase which will load SPEC.md automatically
|
||||
</success_criteria>
|
||||
|
||||
@@ -27,5 +27,5 @@ project history. Output skill goes to `./.claude/skills/spike-findings-[project]
|
||||
|
||||
<process>
|
||||
Execute the spike-wrap-up workflow from @~/.claude/get-shit-done/workflows/spike-wrap-up.md end-to-end.
|
||||
Preserve all curation gates (per-spike review, grouping approval, CLAUDE.md routing line).
|
||||
Preserve all workflow gates (auto-include, feature-area grouping, skill synthesis, CLAUDE.md routing line, intelligent next-step routing).
|
||||
</process>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: gsd:spike
|
||||
description: Rapidly spike an idea with throwaway experiments to validate feasibility before planning
|
||||
argument-hint: "<idea to validate> [--quick]"
|
||||
description: Spike an idea through experiential exploration, or propose what to spike next (frontier mode)
|
||||
argument-hint: "[idea to validate] [--quick] [--text] or [frontier]"
|
||||
allowed-tools:
|
||||
- Read
|
||||
- Write
|
||||
@@ -10,13 +10,22 @@ allowed-tools:
|
||||
- Grep
|
||||
- Glob
|
||||
- AskUserQuestion
|
||||
- WebSearch
|
||||
- WebFetch
|
||||
- mcp__context7__resolve-library-id
|
||||
- mcp__context7__query-docs
|
||||
---
|
||||
<objective>
|
||||
Rapid feasibility validation through focused, throwaway experiments. Each spike answers one
|
||||
specific question with observable evidence. Spikes live in `.planning/spikes/` and integrate
|
||||
with GSD commit patterns, state tracking, and handoff workflows.
|
||||
Spike an idea through experiential exploration — build focused experiments to feel the pieces
|
||||
of a future app, validate feasibility, and produce verified knowledge for the real build.
|
||||
Spikes live in `.planning/spikes/` and integrate with GSD commit patterns, state tracking,
|
||||
and handoff workflows.
|
||||
|
||||
Does not require `/gsd-new-project` — auto-creates `.planning/spikes/` if needed.
|
||||
Two modes:
|
||||
- **Idea mode** (default) — describe an idea to spike
|
||||
- **Frontier mode** (no argument or "frontier") — analyzes existing spike landscape and proposes integration and frontier spikes
|
||||
|
||||
Does not require `/gsd:new-project` — auto-creates `.planning/spikes/` if needed.
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@@ -33,9 +42,10 @@ Idea: $ARGUMENTS
|
||||
|
||||
**Available flags:**
|
||||
- `--quick` — Skip decomposition/alignment, jump straight to building. Use when you already know what to spike.
|
||||
- `--text` — Use plain-text numbered lists instead of AskUserQuestion (for non-Claude runtimes).
|
||||
</context>
|
||||
|
||||
<process>
|
||||
Execute the spike workflow from @~/.claude/get-shit-done/workflows/spike.md end-to-end.
|
||||
Preserve all workflow gates (decomposition, risk ordering, verification, MANIFEST updates, commit patterns).
|
||||
Preserve all workflow gates (prior spike check, decomposition, research, risk ordering, observability assessment, verification, MANIFEST updates, commit patterns).
|
||||
</process>
|
||||
|
||||
19
commands/gsd/sync-skills.md
Normal file
19
commands/gsd/sync-skills.md
Normal file
@@ -0,0 +1,19 @@
|
||||
---
|
||||
name: gsd:sync-skills
|
||||
description: Sync managed GSD skills across runtime roots so multi-runtime users stay aligned after an update
|
||||
allowed-tools:
|
||||
- Bash
|
||||
- AskUserQuestion
|
||||
---
|
||||
|
||||
<objective>
|
||||
Sync managed `gsd-*` skill directories from one canonical runtime's skills root to one or more destination runtime skills roots.
|
||||
|
||||
Routes to the sync-skills workflow which handles:
|
||||
- Argument parsing (--from, --to, --dry-run, --apply)
|
||||
- Runtime skills root resolution via install.js --skills-root
|
||||
- Diff computation (CREATE / UPDATE / REMOVE per destination)
|
||||
- Dry-run reporting (default — no writes)
|
||||
- Apply execution (copy and remove with idempotency)
|
||||
- Non-GSD skill preservation (only gsd-* dirs are touched)
|
||||
</objective>
|
||||
@@ -62,7 +62,7 @@ frontend-build-tools resolved 2026-04-01 Vite vs webpack
|
||||
|
||||
If no threads exist (or none match the filter):
|
||||
```
|
||||
No threads found. Create one with: /gsd-thread <description>
|
||||
No threads found. Create one with: /gsd:thread <description>
|
||||
```
|
||||
|
||||
STOP after displaying. Do NOT proceed to further steps.
|
||||
@@ -117,8 +117,8 @@ When SUBCMD=status and SLUG is set (already sanitized):
|
||||
Next Steps:
|
||||
{content of ## Next Steps section}
|
||||
─────────────────────────────────────
|
||||
Resume with: /gsd-thread {SLUG}
|
||||
Close with: /gsd-thread close {SLUG}
|
||||
Resume with: /gsd:thread {SLUG}
|
||||
Close with: /gsd:thread close {SLUG}
|
||||
```
|
||||
|
||||
No agent spawn. STOP after printing.
|
||||
@@ -201,8 +201,8 @@ updated: {today ISO date}
|
||||
Thread: {slug}
|
||||
File: .planning/threads/{slug}.md
|
||||
|
||||
Resume anytime with: /gsd-thread {slug}
|
||||
Close when done with: /gsd-thread close {slug}
|
||||
Resume anytime with: /gsd:thread {slug}
|
||||
Close when done with: /gsd:thread close {slug}
|
||||
```
|
||||
</mode_create>
|
||||
|
||||
@@ -210,10 +210,10 @@ updated: {today ISO date}
|
||||
|
||||
<notes>
|
||||
- Threads are NOT phase-scoped — they exist independently of the roadmap
|
||||
- Lighter weight than /gsd-pause-work — no phase state, no plan context
|
||||
- Lighter weight than /gsd:pause-work — no phase state, no plan context
|
||||
- The value is in Context and Next Steps — a cold-start session can pick up immediately
|
||||
- Threads can be promoted to phases or backlog items when they mature:
|
||||
/gsd-add-phase or /gsd-add-backlog with context from the thread
|
||||
/gsd:add-phase or /gsd:add-backlog with context from the thread
|
||||
- Thread files live in .planning/threads/ — no collision with phases or other GSD structures
|
||||
- Thread status values: `open`, `in_progress`, `resolved`
|
||||
</notes>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: gsd:ultraplan-phase
|
||||
description: "[BETA] Offload plan phase to Claude Code's ultraplan cloud — drafts remotely while terminal stays free, review in browser with inline comments, import back via /gsd-import. Claude Code only."
|
||||
description: "[BETA] Offload plan phase to Claude Code's ultraplan cloud — drafts remotely while terminal stays free, review in browser with inline comments, import back via /gsd:import. Claude Code only."
|
||||
argument-hint: "[phase-number]"
|
||||
allowed-tools:
|
||||
- Read
|
||||
@@ -13,9 +13,9 @@ allowed-tools:
|
||||
Offload GSD's plan phase to Claude Code's ultraplan cloud infrastructure.
|
||||
|
||||
Ultraplan drafts the plan in a remote cloud session while your terminal stays free.
|
||||
Review and comment on the plan in your browser, then import it back via /gsd-import --from.
|
||||
Review and comment on the plan in your browser, then import it back via /gsd:import --from.
|
||||
|
||||
⚠ BETA: ultraplan is in research preview. Use /gsd-plan-phase for stable local planning.
|
||||
⚠ BETA: ultraplan is in research preview. Use /gsd:plan-phase for stable local planning.
|
||||
Requirements: Claude Code v2.1.91+, claude.ai account, GitHub repository.
|
||||
</objective>
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ Validate built features through conversational testing with persistent state.
|
||||
|
||||
Purpose: Confirm what Claude built actually works from user's perspective. One test at a time, plain text responses, no interrogation. When issues are found, automatically diagnose, plan fixes, and prepare for execution.
|
||||
|
||||
Output: {phase_num}-UAT.md tracking all test results. If issues found: diagnosed gaps, verified fix plans ready for /gsd-execute-phase
|
||||
Output: {phase_num}-UAT.md tracking all test results. If issues found: diagnosed gaps, verified fix plans ready for /gsd:execute-phase
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
|
||||
@@ -6,13 +6,13 @@ allowed-tools:
|
||||
- Bash
|
||||
---
|
||||
|
||||
# /gsd-workstreams
|
||||
# /gsd:workstreams
|
||||
|
||||
Manage parallel workstreams for concurrent milestone work.
|
||||
|
||||
## Usage
|
||||
|
||||
`/gsd-workstreams [subcommand] [args]`
|
||||
`/gsd:workstreams [subcommand] [args]`
|
||||
|
||||
### Subcommands
|
||||
|
||||
@@ -40,7 +40,7 @@ Display the workstreams in a table format showing name, status, current phase, a
|
||||
### create
|
||||
Run: `gsd-sdk query workstream.create <name> --raw --cwd "$CWD"`
|
||||
After creation, display the new workstream path and suggest next steps:
|
||||
- `/gsd-new-milestone --ws <name>` to set up the milestone
|
||||
- `/gsd:new-milestone --ws <name>` to set up the milestone
|
||||
|
||||
### status
|
||||
Run: `gsd-sdk query workstream.status <name> --raw --cwd "$CWD"`
|
||||
@@ -61,7 +61,7 @@ Run: `gsd-sdk query workstream.complete <name> --raw --cwd "$CWD"`
|
||||
Archive the workstream to milestones/.
|
||||
|
||||
### resume
|
||||
Set the workstream as active and suggest `/gsd-resume-work --ws <name>`.
|
||||
Set the workstream as active and suggest `/gsd:resume-work --ws <name>`.
|
||||
|
||||
## Step 3: Display Results
|
||||
|
||||
|
||||
@@ -343,18 +343,26 @@ GSD uses a multi-agent architecture where thin orchestrators (workflow files) sp
|
||||
|
||||
| Property | Value |
|
||||
|----------|-------|
|
||||
| **Spawned by** | `/gsd-map-codebase` |
|
||||
| **Spawned by** | `/gsd-map-codebase`, post-execute drift gate in `/gsd:execute-phase` |
|
||||
| **Parallelism** | 4 instances (tech, architecture, quality, concerns) |
|
||||
| **Tools** | Read, Bash, Grep, Glob, Write |
|
||||
| **Model (balanced)** | Haiku |
|
||||
| **Color** | Cyan |
|
||||
| **Produces** | `.planning/codebase/*.md` (7 documents) |
|
||||
| **Produces** | `.planning/codebase/*.md` (7 documents, with `last_mapped_commit` frontmatter) |
|
||||
|
||||
**Key behaviors:**
|
||||
- Read-only exploration + structured output
|
||||
- Writes documents directly to disk
|
||||
- No reasoning required — pattern extraction from file contents
|
||||
|
||||
**`--paths <p1,p2,...>` scope hint (#2003):**
|
||||
Accepts an optional `--paths` directive in its prompt. When present, the
|
||||
mapper restricts Glob/Grep/Bash exploration to the listed repo-relative path
|
||||
prefixes — this is the incremental-remap path used by the post-execute
|
||||
codebase-drift gate. Path values that contain `..`, start with `/`, or
|
||||
include shell metacharacters are rejected. Without the hint, the mapper
|
||||
runs its default whole-repo scan.
|
||||
|
||||
---
|
||||
|
||||
### gsd-debugger
|
||||
|
||||
@@ -76,6 +76,7 @@ Every agent spawned by an orchestrator gets a clean context window (up to 200K t
|
||||
### 2. Thin Orchestrators
|
||||
|
||||
Workflow files (`get-shit-done/workflows/*.md`) never do heavy lifting. They:
|
||||
|
||||
- Load context via `gsd-sdk query init.<workflow>` (or legacy `gsd-tools.cjs init <workflow>`)
|
||||
- Spawn specialized agents with focused prompts
|
||||
- Collect results and route to the next step
|
||||
@@ -84,6 +85,7 @@ Workflow files (`get-shit-done/workflows/*.md`) never do heavy lifting. They:
|
||||
### 3. File-Based State
|
||||
|
||||
All state lives in `.planning/` as human-readable Markdown and JSON. No database, no server, no external dependencies. This means:
|
||||
|
||||
- State survives context resets (`/clear`)
|
||||
- State is inspectable by both humans and agents
|
||||
- State can be committed to git for team visibility
|
||||
@@ -95,6 +97,7 @@ Workflow feature flags follow the **absent = enabled** pattern. If a key is miss
|
||||
### 5. Defense in Depth
|
||||
|
||||
Multiple layers prevent common failure modes:
|
||||
|
||||
- Plans are verified before execution (plan-checker agent)
|
||||
- Execution produces atomic commits per task
|
||||
- Post-execution verification checks against phase goals
|
||||
@@ -107,6 +110,7 @@ Multiple layers prevent common failure modes:
|
||||
### Commands (`commands/gsd/*.md`)
|
||||
|
||||
User-facing entry points. Each file contains YAML frontmatter (name, description, allowed-tools) and a prompt body that bootstraps the workflow. Commands are installed as:
|
||||
|
||||
- **Claude Code:** Custom slash commands (`/gsd-command-name`)
|
||||
- **OpenCode / Kilo:** Slash commands (`/gsd-command-name`)
|
||||
- **Codex:** Skills (`$gsd-command-name`)
|
||||
@@ -118,6 +122,7 @@ User-facing entry points. Each file contains YAML frontmatter (name, description
|
||||
### Workflows (`get-shit-done/workflows/*.md`)
|
||||
|
||||
Orchestration logic that commands reference. Contains the step-by-step process including:
|
||||
|
||||
- Context loading via `gsd-sdk query` init handlers (or legacy `gsd-tools.cjs init`)
|
||||
- Agent spawn instructions with model resolution
|
||||
- Gate/checkpoint definitions
|
||||
@@ -126,9 +131,37 @@ Orchestration logic that commands reference. Contains the step-by-step process i
|
||||
|
||||
**Total workflows:** see [`docs/INVENTORY.md`](INVENTORY.md#workflows) for the authoritative count and full roster.
|
||||
|
||||
#### Progressive disclosure for workflows
|
||||
|
||||
Workflow files are loaded verbatim into Claude's context every time the
|
||||
corresponding `/gsd:*` command is invoked. To keep that cost bounded, the
|
||||
workflow size budget enforced by `tests/workflow-size-budget.test.cjs`
|
||||
mirrors the agent budget from #2361:
|
||||
|
||||
| Tier | Per-file line limit |
|
||||
|-----------|--------------------|
|
||||
| `XL` | 1700 — top-level orchestrators (`execute-phase`, `plan-phase`, `new-project`) |
|
||||
| `LARGE` | 1500 — multi-step planners and large feature workflows |
|
||||
| `DEFAULT` | 1000 — focused single-purpose workflows (the target tier) |
|
||||
|
||||
`workflows/discuss-phase.md` is held to a stricter <500-line ceiling per
|
||||
issue #2551. When a workflow grows beyond its tier, extract per-mode bodies
|
||||
into `workflows/<workflow>/modes/<mode>.md`, templates into
|
||||
`workflows/<workflow>/templates/`, and shared knowledge into
|
||||
`get-shit-done/references/`. The parent file becomes a thin dispatcher that
|
||||
Reads only the mode and template files needed for the current invocation.
|
||||
|
||||
`workflows/discuss-phase/` is the canonical example of this pattern —
|
||||
parent dispatches, modes/ holds per-flag behavior (`power.md`, `all.md`,
|
||||
`auto.md`, `chain.md`, `text.md`, `batch.md`, `analyze.md`, `default.md`,
|
||||
`advisor.md`), and templates/ holds CONTEXT.md, DISCUSSION-LOG.md, and
|
||||
checkpoint.json schemas that are read only when the corresponding output
|
||||
file is being written.
|
||||
|
||||
### Agents (`agents/*.md`)
|
||||
|
||||
Specialized agent definitions with frontmatter specifying:
|
||||
|
||||
- `name` — Agent identifier
|
||||
- `description` — Role and purpose
|
||||
- `tools` — Allowed tool access (Read, Write, Edit, Bash, Grep, Glob, WebSearch, etc.)
|
||||
@@ -141,6 +174,7 @@ Specialized agent definitions with frontmatter specifying:
|
||||
Shared knowledge documents that workflows and agents `@-reference` (see [`docs/INVENTORY.md`](INVENTORY.md#references-41-shipped) for the authoritative count and full roster):
|
||||
|
||||
**Core references:**
|
||||
|
||||
- `checkpoints.md` — Checkpoint type definitions and interaction patterns
|
||||
- `gates.md` — 4 canonical gate types (Confirm, Quality, Safety, Transition) wired into plan-checker and verifier
|
||||
- `model-profiles.md` — Per-agent model tier assignments
|
||||
@@ -156,6 +190,7 @@ Shared knowledge documents that workflows and agents `@-reference` (see [`docs/I
|
||||
- `common-bug-patterns.md` — Common bug patterns for code review and verification
|
||||
|
||||
**Workflow references:**
|
||||
|
||||
- `agent-contracts.md` — Formal interface between orchestrators and agents
|
||||
- `context-budget.md` — Context window budget allocation rules
|
||||
- `continuation-format.md` — Session continuation/resume format
|
||||
@@ -190,7 +225,7 @@ The planner agent (`agents/gsd-planner.md`) was decomposed from a single monolit
|
||||
|
||||
### Templates (`get-shit-done/templates/`)
|
||||
|
||||
Markdown templates for all planning artifacts. Used by `gsd-tools.cjs template fill` and `scaffold` commands to create pre-structured files:
|
||||
Markdown templates for all planning artifacts. Used by `gsd-sdk query template.fill` / `phase.scaffold` (and legacy `gsd-tools.cjs template fill` / top-level `scaffold`) to create pre-structured files:
|
||||
- `project.md`, `requirements.md`, `roadmap.md`, `state.md` — Core project files
|
||||
- `phase-prompt.md` — Phase execution prompt template
|
||||
- `summary.md` (+ `summary-minimal.md`, `summary-standard.md`, `summary-complex.md`) — Granularity-aware summary templates
|
||||
@@ -224,27 +259,29 @@ See [`docs/INVENTORY.md`](INVENTORY.md#hooks-11-shipped) for the authoritative 1
|
||||
|
||||
Node.js CLI utility (`gsd-tools.cjs`) with domain modules split across `get-shit-done/bin/lib/` (see [`docs/INVENTORY.md`](INVENTORY.md#cli-modules-24-shipped) for the authoritative roster):
|
||||
|
||||
| Module | Responsibility |
|
||||
|--------|---------------|
|
||||
| `core.cjs` | Error handling, output formatting, shared utilities |
|
||||
| `state.cjs` | STATE.md parsing, updating, progression, metrics |
|
||||
| `phase.cjs` | Phase directory operations, decimal numbering, plan indexing |
|
||||
| `roadmap.cjs` | ROADMAP.md parsing, phase extraction, plan progress |
|
||||
| `config.cjs` | config.json read/write, section initialization |
|
||||
| `verify.cjs` | Plan structure, phase completeness, reference, commit validation |
|
||||
| `template.cjs` | Template selection and filling with variable substitution |
|
||||
| `frontmatter.cjs` | YAML frontmatter CRUD operations |
|
||||
| `init.cjs` | Compound context loading for each workflow type |
|
||||
| `milestone.cjs` | Milestone archival, requirements marking |
|
||||
| `commands.cjs` | Misc commands (slug, timestamp, todos, scaffolding, stats) |
|
||||
| `model-profiles.cjs` | Model profile resolution table |
|
||||
| `security.cjs` | Path traversal prevention, prompt injection detection, safe JSON parsing, shell argument validation |
|
||||
| `uat.cjs` | UAT file parsing, verification debt tracking, audit-uat support |
|
||||
| `docs.cjs` | Docs-update workflow init, Markdown scanning, monorepo detection |
|
||||
| `workstream.cjs` | Workstream CRUD, migration, session-scoped active pointer |
|
||||
| `schema-detect.cjs` | Schema-drift detection for ORM patterns (Prisma, Drizzle, etc.) |
|
||||
| `profile-pipeline.cjs` | User behavioral profiling data pipeline, session file scanning |
|
||||
| `profile-output.cjs` | Profile rendering, USER-PROFILE.md and dev-preferences.md generation |
|
||||
|
||||
| Module | Responsibility |
|
||||
| ---------------------- | --------------------------------------------------------------------------------------------------- |
|
||||
| `core.cjs` | Error handling, output formatting, shared utilities |
|
||||
| `state.cjs` | STATE.md parsing, updating, progression, metrics |
|
||||
| `phase.cjs` | Phase directory operations, decimal numbering, plan indexing |
|
||||
| `roadmap.cjs` | ROADMAP.md parsing, phase extraction, plan progress |
|
||||
| `config.cjs` | config.json read/write, section initialization |
|
||||
| `verify.cjs` | Plan structure, phase completeness, reference, commit validation |
|
||||
| `template.cjs` | Template selection and filling with variable substitution |
|
||||
| `frontmatter.cjs` | YAML frontmatter CRUD operations |
|
||||
| `init.cjs` | Compound context loading for each workflow type |
|
||||
| `milestone.cjs` | Milestone archival, requirements marking |
|
||||
| `commands.cjs` | Misc commands (slug, timestamp, todos, scaffolding, stats) |
|
||||
| `model-profiles.cjs` | Model profile resolution table |
|
||||
| `security.cjs` | Path traversal prevention, prompt injection detection, safe JSON parsing, shell argument validation |
|
||||
| `uat.cjs` | UAT file parsing, verification debt tracking, audit-uat support |
|
||||
| `docs.cjs` | Docs-update workflow init, Markdown scanning, monorepo detection |
|
||||
| `workstream.cjs` | Workstream CRUD, migration, session-scoped active pointer |
|
||||
| `schema-detect.cjs` | Schema-drift detection for ORM patterns (Prisma, Drizzle, etc.) |
|
||||
| `profile-pipeline.cjs` | User behavioral profiling data pipeline, session file scanning |
|
||||
| `profile-output.cjs` | Profile rendering, USER-PROFILE.md and dev-preferences.md generation |
|
||||
|
||||
|
||||
---
|
||||
|
||||
@@ -255,10 +292,10 @@ Node.js CLI utility (`gsd-tools.cjs`) with domain modules split across `get-shit
|
||||
```
|
||||
Orchestrator (workflow .md)
|
||||
│
|
||||
├── Load context: gsd-tools.cjs init <workflow> <phase>
|
||||
├── Load context: gsd-sdk query init.<workflow> <phase> (or legacy gsd-tools.cjs init)
|
||||
│ Returns JSON with: project info, config, state, phase details
|
||||
│
|
||||
├── Resolve model: gsd-tools.cjs resolve-model <agent-name>
|
||||
├── Resolve model: gsd-sdk query resolve-model <agent-name>
|
||||
│ Returns: opus | sonnet | haiku | inherit
|
||||
│
|
||||
├── Spawn Agent (Task/SubAgent call)
|
||||
@@ -269,27 +306,29 @@ Orchestrator (workflow .md)
|
||||
│
|
||||
├── Collect result
|
||||
│
|
||||
└── Update state: gsd-tools.cjs state update/patch/advance-plan
|
||||
└── Update state: gsd-sdk query state.update / state.patch / state.advance-plan (or legacy gsd-tools.cjs)
|
||||
```
|
||||
|
||||
### Primary Agent Spawn Categories
|
||||
|
||||
Conceptual spawn-pattern taxonomy for the 21 primary agents. For the authoritative 31-agent roster (including the 10 advanced/specialized agents such as `gsd-pattern-mapper`, `gsd-code-reviewer`, `gsd-code-fixer`, `gsd-ai-researcher`, `gsd-domain-researcher`, `gsd-eval-planner`, `gsd-eval-auditor`, `gsd-framework-selector`, `gsd-debug-session-manager`, `gsd-intel-updater`), see [`docs/INVENTORY.md`](INVENTORY.md#agents-31-shipped).
|
||||
|
||||
| Category | Agents | Parallelism |
|
||||
|----------|--------|-------------|
|
||||
| **Researchers** | gsd-project-researcher, gsd-phase-researcher, gsd-ui-researcher, gsd-advisor-researcher | 4 parallel (stack, features, architecture, pitfalls); advisor spawns during discuss-phase |
|
||||
| **Synthesizers** | gsd-research-synthesizer | Sequential (after researchers complete) |
|
||||
| **Planners** | gsd-planner, gsd-roadmapper | Sequential |
|
||||
| **Checkers** | gsd-plan-checker, gsd-integration-checker, gsd-ui-checker, gsd-nyquist-auditor | Sequential (verification loop, max 3 iterations) |
|
||||
| **Executors** | gsd-executor | Parallel within waves, sequential across waves |
|
||||
| **Verifiers** | gsd-verifier | Sequential (after all executors complete) |
|
||||
| **Mappers** | gsd-codebase-mapper | 4 parallel (tech, arch, quality, concerns) |
|
||||
| **Debuggers** | gsd-debugger | Sequential (interactive) |
|
||||
| **Auditors** | gsd-ui-auditor, gsd-security-auditor | Sequential |
|
||||
| **Doc Writers** | gsd-doc-writer, gsd-doc-verifier | Sequential (writer then verifier) |
|
||||
| **Profilers** | gsd-user-profiler | Sequential |
|
||||
| **Analyzers** | gsd-assumptions-analyzer | Sequential (during discuss-phase) |
|
||||
|
||||
| Category | Agents | Parallelism |
|
||||
| ---------------- | --------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- |
|
||||
| **Researchers** | gsd-project-researcher, gsd-phase-researcher, gsd-ui-researcher, gsd-advisor-researcher | 4 parallel (stack, features, architecture, pitfalls); advisor spawns during discuss-phase |
|
||||
| **Synthesizers** | gsd-research-synthesizer | Sequential (after researchers complete) |
|
||||
| **Planners** | gsd-planner, gsd-roadmapper | Sequential |
|
||||
| **Checkers** | gsd-plan-checker, gsd-integration-checker, gsd-ui-checker, gsd-nyquist-auditor | Sequential (verification loop, max 3 iterations) |
|
||||
| **Executors** | gsd-executor | Parallel within waves, sequential across waves |
|
||||
| **Verifiers** | gsd-verifier | Sequential (after all executors complete) |
|
||||
| **Mappers** | gsd-codebase-mapper | 4 parallel (tech, arch, quality, concerns) |
|
||||
| **Debuggers** | gsd-debugger | Sequential (interactive) |
|
||||
| **Auditors** | gsd-ui-auditor, gsd-security-auditor | Sequential |
|
||||
| **Doc Writers** | gsd-doc-writer, gsd-doc-verifier | Sequential (writer then verifier) |
|
||||
| **Profilers** | gsd-user-profiler | Sequential |
|
||||
| **Analyzers** | gsd-assumptions-analyzer | Sequential (during discuss-phase) |
|
||||
|
||||
|
||||
### Wave Execution Model
|
||||
|
||||
@@ -305,6 +344,7 @@ Wave Analysis:
|
||||
```
|
||||
|
||||
Each executor gets:
|
||||
|
||||
- Fresh 200K context window (or up to 1M for models that support it)
|
||||
- The specific PLAN.md to execute
|
||||
- Project context (PROJECT.md, STATE.md)
|
||||
@@ -317,14 +357,13 @@ When the context window is 500K+ tokens (1M-class models like Opus 4.6, Sonnet 4
|
||||
- **Executor agents** receive prior wave SUMMARY.md files and the phase CONTEXT.md/RESEARCH.md, enabling cross-plan awareness within a phase
|
||||
- **Verifier agents** receive all PLAN.md, SUMMARY.md, CONTEXT.md files plus REQUIREMENTS.md, enabling history-aware verification
|
||||
|
||||
The orchestrator reads `context_window` from config (`gsd-tools.cjs config-get context_window`) and conditionally includes richer context when the value is >= 500,000. For standard 200K windows, prompts use truncated versions with cache-friendly ordering to maximize context efficiency.
|
||||
The orchestrator reads `context_window` from config (`gsd-sdk query config-get context_window`, or legacy `gsd-tools.cjs config-get`) and conditionally includes richer context when the value is >= 500,000. For standard 200K windows, prompts use truncated versions with cache-friendly ordering to maximize context efficiency.
|
||||
|
||||
#### Parallel Commit Safety
|
||||
|
||||
When multiple executors run within the same wave, two mechanisms prevent conflicts:
|
||||
|
||||
1. **`--no-verify` commits** — Parallel agents skip pre-commit hooks (which can cause build lock contention, e.g., cargo lock fights in Rust projects). The orchestrator runs `git hook run pre-commit` once after each wave completes.
|
||||
|
||||
1. `--no-verify` commits — Parallel agents skip pre-commit hooks (which can cause build lock contention, e.g., cargo lock fights in Rust projects). The orchestrator runs `git hook run pre-commit` once after each wave completes.
|
||||
2. **STATE.md file locking** — All `writeStateMd()` calls use lockfile-based mutual exclusion (`STATE.md.lock` with `O_EXCL` atomic creation). This prevents the read-modify-write race condition where two agents read STATE.md, modify different fields, and the last writer overwrites the other's changes. Includes stale lock detection (10s timeout) and spin-wait with jitter.
|
||||
|
||||
---
|
||||
@@ -372,7 +411,9 @@ plan-phase
|
||||
├── Research gate (blocks if RESEARCH.md has unresolved open questions)
|
||||
├── Phase Researcher → RESEARCH.md
|
||||
├── Planner (with reachability check) → PLAN.md files
|
||||
└── Plan Checker → Verify loop (max 3x)
|
||||
├── Plan Checker → Verify loop (max 3x)
|
||||
├── Requirements coverage gate (REQ-IDs → plans)
|
||||
└── Decision coverage gate (CONTEXT.md `<decisions>` → plans, BLOCKING — #2492)
|
||||
│
|
||||
▼
|
||||
state planned-phase → STATE.md (Planned/Ready to execute)
|
||||
@@ -383,6 +424,7 @@ execute-phase (context reduction: truncated prompts, cache-friendly ordering)
|
||||
├── Executor per plan → code + atomic commits
|
||||
├── SUMMARY.md per plan
|
||||
└── Verifier → VERIFICATION.md
|
||||
└── Decision coverage gate (CONTEXT.md decisions → shipped artifacts, NON-BLOCKING — #2492)
|
||||
│
|
||||
▼
|
||||
verify-work → UAT.md (user acceptance testing)
|
||||
@@ -430,6 +472,7 @@ UI-SPEC.md (per phase) ───────────────────
|
||||
```
|
||||
|
||||
Equivalent paths for other runtimes:
|
||||
|
||||
- **OpenCode:** `~/.config/opencode/` or `~/.opencode/`
|
||||
- **Kilo:** `~/.config/kilo/` or `~/.kilo/`
|
||||
- **Gemini CLI:** `~/.gemini/`
|
||||
@@ -454,8 +497,8 @@ Equivalent paths for other runtimes:
|
||||
│ ├── ARCHITECTURE.md
|
||||
│ └── PITFALLS.md
|
||||
├── codebase/ # Brownfield mapping (from /gsd-map-codebase)
|
||||
│ ├── STACK.md
|
||||
│ ├── ARCHITECTURE.md
|
||||
│ ├── STACK.md # YAML frontmatter carries `last_mapped_commit`
|
||||
│ ├── ARCHITECTURE.md # for the post-execute drift gate (#2003)
|
||||
│ ├── CONVENTIONS.md
|
||||
│ ├── CONCERNS.md
|
||||
│ ├── STRUCTURE.md
|
||||
@@ -489,6 +532,30 @@ Equivalent paths for other runtimes:
|
||||
└── continue-here.md # Context handoff (from pause-work)
|
||||
```
|
||||
|
||||
### Post-Execute Codebase Drift Gate (#2003)
|
||||
|
||||
After the last wave of `/gsd:execute-phase` commits, the workflow runs a
|
||||
non-blocking `codebase_drift_gate` step (between `schema_drift_gate` and
|
||||
`verify_phase_goal`). It compares the diff `last_mapped_commit..HEAD`
|
||||
against `.planning/codebase/STRUCTURE.md` and counts four kinds of
|
||||
structural elements:
|
||||
|
||||
1. New directories outside mapped paths
|
||||
2. New barrel exports at `(packages|apps)/<name>/src/index.*`
|
||||
3. New migration files
|
||||
4. New route modules under `routes/` or `api/`
|
||||
|
||||
If the count meets `workflow.drift_threshold` (default 3), the gate either
|
||||
**warns** (default) with the suggested `/gsd:map-codebase --paths …` command,
|
||||
or **auto-remaps** (`workflow.drift_action = auto-remap`) by spawning
|
||||
`gsd-codebase-mapper` scoped to the affected paths. Any error in detection
|
||||
or remap is logged and the phase continues — drift detection cannot fail
|
||||
verification.
|
||||
|
||||
`last_mapped_commit` lives in YAML frontmatter at the top of each
|
||||
`.planning/codebase/*.md` file; `bin/lib/drift.cjs` provides
|
||||
`readMappedCommit` and `writeMappedCommit` round-trip helpers.
|
||||
|
||||
---
|
||||
|
||||
## Installer Architecture
|
||||
@@ -499,16 +566,16 @@ The installer (`bin/install.js`, ~3,000 lines) handles:
|
||||
2. **Location selection** — Global (`--global`) or local (`--local`)
|
||||
3. **File deployment** — Copies commands, workflows, references, templates, agents, hooks
|
||||
4. **Runtime adaptation** — Transforms file content per runtime:
|
||||
- Claude Code: Uses as-is
|
||||
- OpenCode: Converts commands/agents to OpenCode-compatible flat command + subagent format
|
||||
- Kilo: Reuses the OpenCode conversion pipeline with Kilo config paths
|
||||
- Codex: Generates TOML config + skills from commands
|
||||
- Copilot: Maps tool names (Read→read, Bash→execute, etc.)
|
||||
- Gemini: Adjusts hook event names (`AfterTool` instead of `PostToolUse`)
|
||||
- Antigravity: Skills-first with Google model equivalents
|
||||
- Trae: Skills-first install to `~/.trae` / `./.trae` with no `settings.json` or hook integration
|
||||
- Cline: Writes `.clinerules` for rule-based integration
|
||||
- Augment Code: Skills-first with full skill conversion and config management
|
||||
- Claude Code: Uses as-is
|
||||
- OpenCode: Converts commands/agents to OpenCode-compatible flat command + subagent format
|
||||
- Kilo: Reuses the OpenCode conversion pipeline with Kilo config paths
|
||||
- Codex: Generates TOML config + skills from commands
|
||||
- Copilot: Maps tool names (Read→read, Bash→execute, etc.)
|
||||
- Gemini: Adjusts hook event names (`AfterTool` instead of `PostToolUse`)
|
||||
- Antigravity: Skills-first with Google model equivalents
|
||||
- Trae: Skills-first install to `~/.trae` / `./.trae` with no `settings.json` or hook integration
|
||||
- Cline: Writes `.clinerules` for rule-based integration
|
||||
- Augment Code: Skills-first with full skill conversion and config management
|
||||
5. **Path normalization** — Replaces `~/.claude/` paths with runtime-specific paths
|
||||
6. **Settings integration** — Registers hooks in runtime's `settings.json`
|
||||
7. **Patch backup** — Since v1.17, backs up locally modified files to `gsd-local-patches/` for `/gsd-reapply-patches`
|
||||
@@ -545,11 +612,13 @@ Runtime Engine (Claude Code / Gemini CLI)
|
||||
|
||||
### Context Monitor Thresholds
|
||||
|
||||
| Remaining Context | Level | Agent Behavior |
|
||||
|-------------------|-------|----------------|
|
||||
| > 35% | Normal | No warning injected |
|
||||
| ≤ 35% | WARNING | "Avoid starting new complex work" |
|
||||
| ≤ 25% | CRITICAL | "Context nearly exhausted, inform user" |
|
||||
|
||||
| Remaining Context | Level | Agent Behavior |
|
||||
| ----------------- | -------- | --------------------------------------- |
|
||||
| > 35% | Normal | No warning injected |
|
||||
| ≤ 35% | WARNING | "Avoid starting new complex work" |
|
||||
| ≤ 25% | CRITICAL | "Context nearly exhausted, inform user" |
|
||||
|
||||
|
||||
Debounce: 5 tool uses between repeated warnings. Severity escalation (WARNING→CRITICAL) bypasses debounce.
|
||||
|
||||
@@ -564,12 +633,14 @@ Debounce: 5 tool uses between repeated warnings. Severity escalation (WARNING→
|
||||
### Security Hooks (v1.27)
|
||||
|
||||
**Prompt Guard** (`gsd-prompt-guard.js`):
|
||||
|
||||
- Triggers on Write/Edit to `.planning/` files
|
||||
- Scans content for prompt injection patterns (role override, instruction bypass, system tag injection)
|
||||
- Advisory-only — logs detection, does not block
|
||||
- Patterns are inlined (subset of `security.cjs`) for hook independence
|
||||
|
||||
**Workflow Guard** (`gsd-workflow-guard.js`):
|
||||
|
||||
- Triggers on Write/Edit to non-`.planning/` files
|
||||
- Detects edits outside GSD workflow context (no active `/gsd-` command or Task subagent)
|
||||
- Advises using `/gsd-quick` or `/gsd-fast` for state-tracked changes
|
||||
@@ -581,18 +652,20 @@ Debounce: 5 tool uses between repeated warnings. Severity escalation (WARNING→
|
||||
|
||||
GSD supports multiple AI coding runtimes through a unified command/workflow architecture:
|
||||
|
||||
| Runtime | Command Format | Agent System | Config Location |
|
||||
|---------|---------------|--------------|-----------------|
|
||||
| Claude Code | `/gsd-command` | Task spawning | `~/.claude/` |
|
||||
| OpenCode | `/gsd-command` | Subagent mode | `~/.config/opencode/` |
|
||||
| Kilo | `/gsd-command` | Subagent mode | `~/.config/kilo/` |
|
||||
| Gemini CLI | `/gsd-command` | Task spawning | `~/.gemini/` |
|
||||
| Codex | `$gsd-command` | Skills | `~/.codex/` |
|
||||
| Copilot | `/gsd-command` | Agent delegation | `~/.github/` |
|
||||
| Antigravity | Skills | Skills | `~/.gemini/antigravity/` |
|
||||
| Trae | Skills | Skills | `~/.trae/` |
|
||||
| Cline | Rules | Rules | `.clinerules` |
|
||||
| Augment Code | Skills | Skills | Augment config |
|
||||
|
||||
| Runtime | Command Format | Agent System | Config Location |
|
||||
| ------------ | -------------- | ---------------- | ------------------------ |
|
||||
| Claude Code | `/gsd-command` | Task spawning | `~/.claude/` |
|
||||
| OpenCode | `/gsd-command` | Subagent mode | `~/.config/opencode/` |
|
||||
| Kilo | `/gsd-command` | Subagent mode | `~/.config/kilo/` |
|
||||
| Gemini CLI | `/gsd-command` | Task spawning | `~/.gemini/` |
|
||||
| Codex | `$gsd-command` | Skills | `~/.codex/` |
|
||||
| Copilot | `/gsd-command` | Agent delegation | `~/.github/` |
|
||||
| Antigravity | Skills | Skills | `~/.gemini/antigravity/` |
|
||||
| Trae | Skills | Skills | `~/.trae/` |
|
||||
| Cline | Rules | Rules | `.clinerules` |
|
||||
| Augment Code | Skills | Skills | Augment config |
|
||||
|
||||
|
||||
### Abstraction Points
|
||||
|
||||
@@ -602,4 +675,4 @@ GSD supports multiple AI coding runtimes through a unified command/workflow arch
|
||||
4. **Path conventions** — Each runtime stores config in different directories
|
||||
5. **Model references** — `inherit` profile lets GSD defer to runtime's model selection
|
||||
|
||||
The installer handles all translation at install time. Workflows and agents are written in Claude Code's native format and transformed during deployment.
|
||||
The installer handles all translation at install time. Workflows and agents are written in Claude Code's native format and transformed during deployment.
|
||||
@@ -1,29 +1,71 @@
|
||||
# GSD CLI Tools Reference
|
||||
|
||||
> Programmatic API reference for `gsd-tools.cjs`. Used by workflows and agents internally. For user-facing commands, see [Command Reference](COMMANDS.md).
|
||||
> Surface-area reference for `get-shit-done/bin/gsd-tools.cjs` (legacy Node CLI). Workflows and agents should prefer `gsd-sdk query` or `@gsd-build/sdk` where a handler exists — see [SDK and programmatic access](#sdk-and-programmatic-access). For slash commands and user flows, see [Command Reference](COMMANDS.md).
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
`gsd-tools.cjs` is a Node.js CLI utility that replaces repetitive inline bash patterns across GSD's ~50 command, workflow, and agent files. It centralizes: config parsing, model resolution, phase lookup, git commits, summary verification, state management, and template operations.
|
||||
`gsd-tools.cjs` centralizes config parsing, model resolution, phase lookup, git commits, summary verification, state management, and template operations across GSD commands, workflows, and agents.
|
||||
|
||||
**Preferred for new orchestration:** Many of the same operations are available as `gsd-sdk query <command>` (see `sdk/src/query/index.ts` and `docs/QUERY-HANDLERS.md`). Use that in workflows and examples where the handler exists; keep `node … gsd-tools.cjs` for commands not yet in the registry (for example graphify) or when you need CJS-only flags.
|
||||
|
||||
**Location:** `get-shit-done/bin/gsd-tools.cjs`
|
||||
**Modules:** see the [Module Architecture](#module-architecture) table; the `get-shit-done/bin/lib/` directory is authoritative.
|
||||
| | |
|
||||
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| **Shipped path** | `get-shit-done/bin/gsd-tools.cjs` |
|
||||
| **Implementation** | 20 domain modules under `get-shit-done/bin/lib/` (the directory is authoritative) |
|
||||
| **Status** | Maintained for parity tests and CJS-only entrypoints; `gsd-sdk query` / SDK registry are the supported path for new orchestration (see [QUERY-HANDLERS.md](../sdk/src/query/QUERY-HANDLERS.md)). |
|
||||
|
||||
|
||||
**Usage (CJS):**
|
||||
|
||||
**Usage:**
|
||||
```bash
|
||||
node gsd-tools.cjs <command> [args] [--raw] [--cwd <path>]
|
||||
```
|
||||
|
||||
**Global Flags:**
|
||||
| Flag | Description |
|
||||
|------|-------------|
|
||||
| `--raw` | Machine-readable output (JSON or plain text, no formatting) |
|
||||
| `--cwd <path>` | Override working directory (for sandboxed subagents) |
|
||||
| `--ws <name>` | Target a specific workstream context (SDK only) |
|
||||
**Global flags (CJS):**
|
||||
|
||||
|
||||
| Flag | Description |
|
||||
| -------------- | ---------------------------------------------------------------------------- |
|
||||
| `--raw` | Machine-readable output (JSON or plain text, no formatting) |
|
||||
| `--cwd <path>` | Override working directory (for sandboxed subagents) |
|
||||
| `--ws <name>` | Workstream context (also honored when the SDK spawns this binary; see below) |
|
||||
|
||||
|
||||
---
|
||||
|
||||
## SDK and programmatic access
|
||||
|
||||
Use this when authoring workflows, not when you only need the command list below.
|
||||
|
||||
**1. CLI — `gsd-sdk query <argv…>`**
|
||||
|
||||
- Resolves argv with the same **longest-prefix** rules as the typed registry (`resolveQueryArgv` in `sdk/src/query/registry.ts`). Unregistered commands **fail fast** — use `node …/gsd-tools.cjs` only for handlers not in the registry.
|
||||
- Full matrix (CJS command → registry key, CLI-only tools, aliases, golden tiers): [sdk/src/query/QUERY-HANDLERS.md](../sdk/src/query/QUERY-HANDLERS.md).
|
||||
|
||||
**2. TypeScript — `@gsd-build/sdk` (`GSDTools`, `createRegistry`)**
|
||||
|
||||
- `GSDTools` (used by `PhaseRunner`, `InitRunner`, and `GSD.createTools()`) always shells out to `gsd-tools.cjs` via `execFile` — there is no in-process registry path on this class. For typed, in-process dispatch use `createRegistry()` from `sdk/src/query/index.ts`, or invoke `gsd-sdk query` (see [QUERY-HANDLERS.md](../sdk/src/query/QUERY-HANDLERS.md)).
|
||||
- Conventions: mutation event wiring, `GSDError` vs `{ data: { error } }`, locks, and stubs — [QUERY-HANDLERS.md](../sdk/src/query/QUERY-HANDLERS.md).
|
||||
|
||||
**CJS → SDK examples (same project directory):**
|
||||
|
||||
|
||||
| Legacy CJS | Preferred `gsd-sdk query` (examples) |
|
||||
| ---------------------------------------- | ------------------------------------ |
|
||||
| `node gsd-tools.cjs init phase-op 12` | `gsd-sdk query init phase-op 12` |
|
||||
| `node gsd-tools.cjs phase-plan-index 12` | `gsd-sdk query phase-plan-index 12` |
|
||||
| `node gsd-tools.cjs state json` | `gsd-sdk query state json` |
|
||||
| `node gsd-tools.cjs roadmap analyze` | `gsd-sdk query roadmap analyze` |
|
||||
|
||||
|
||||
**SDK state reads:** `gsd-sdk query state json` / `state.json` and `gsd-sdk query state load` / `state.load` currently share one native handler (rebuilt STATE.md frontmatter — CJS `cmdStateJson`). The legacy CJS `state load` payload (`config`, `state_raw`, existence flags) is still **CLI-only** via `node …/gsd-tools.cjs state load` until a separate registry handler exists. Full routing and golden rules: [QUERY-HANDLERS.md](../sdk/src/query/QUERY-HANDLERS.md).
|
||||
|
||||
**CLI-only (not in registry):** e.g. **graphify**, **from-gsd2** / **gsd2-import** — call `gsd-tools.cjs` until registered.
|
||||
|
||||
**Mutation events (SDK):** `QUERY_MUTATION_COMMANDS` in `sdk/src/query/index.ts` lists commands that may emit structured events after a successful dispatch. Exceptions called out in QUERY-HANDLERS: `state validate` (read-only), `skill-manifest` (writes only with `--write`), `intel update` (stub).
|
||||
|
||||
**Golden parity:** Policy and CJS↔SDK test categories are documented under **Golden parity** in [QUERY-HANDLERS.md](../sdk/src/query/QUERY-HANDLERS.md).
|
||||
|
||||
---
|
||||
|
||||
@@ -373,7 +415,7 @@ node gsd-tools.cjs from-gsd2 [--path <dir>] [--force] [--dry-run]
|
||||
node gsd-tools.cjs commit <message> [--files f1 f2] [--amend] [--no-verify]
|
||||
```
|
||||
|
||||
> **`--no-verify`**: Skips pre-commit hooks. Used by parallel executor agents during wave-based execution to avoid build lock contention (e.g., cargo lock fights in Rust projects). The orchestrator runs hooks once after each wave completes. Do not use `--no-verify` during sequential execution — let hooks run normally.
|
||||
> `--no-verify`: Skips pre-commit hooks. Used by parallel executor agents during wave-based execution to avoid build lock contention (e.g., cargo lock fights in Rust projects). The orchestrator runs hooks once after each wave completes. Do not use `--no-verify` during sequential execution — let hooks run normally.
|
||||
|
||||
# Web search (requires Brave API key)
|
||||
node gsd-tools.cjs websearch <query> [--limit N] [--freshness day|week|month]
|
||||
@@ -430,3 +472,30 @@ User-facing entry point: `/gsd-graphify` (see [Command Reference](COMMANDS.md#gs
|
||||
| Audit | `lib/audit.cjs` | Phase/milestone audit queue handlers; `audit-open` helper |
|
||||
| GSD2 Import | `lib/gsd2-import.cjs` | Reverse-migration importer from GSD-2 projects (backs `/gsd-from-gsd2`) |
|
||||
| Intel | `lib/intel.cjs` | Queryable codebase intelligence index (backs `/gsd-intel`) |
|
||||
|
||||
---
|
||||
|
||||
## Reviewer CLI Routing
|
||||
|
||||
`review.models.<cli>` maps a reviewer flavor to a shell command invoked by the code-review workflow. Set via [`/gsd-settings-integrations`](COMMANDS.md#gsd-settings-integrations) or directly:
|
||||
|
||||
```bash
|
||||
gsd-sdk query config-set review.models.codex "codex exec --model gpt-5"
|
||||
gsd-sdk query config-set review.models.gemini "gemini -m gemini-2.5-pro"
|
||||
gsd-sdk query config-set review.models.opencode "opencode run --model claude-sonnet-4"
|
||||
gsd-sdk query config-set review.models.claude "" # clear — fall back to session model
|
||||
```
|
||||
|
||||
Slugs are validated against `[a-zA-Z0-9_-]+`; empty or path-containing slugs are rejected. See [`docs/CONFIGURATION.md`](CONFIGURATION.md#code-review-cli-routing) for the full field reference.
|
||||
|
||||
## Secret Handling
|
||||
|
||||
API keys configured via `/gsd-settings-integrations` (`brave_search`, `firecrawl`, `exa_search`) are written plaintext to `.planning/config.json` but are masked (`****<last-4>`) in every `config-set` / `config-get` output, confirmation table, and interactive prompt. See `get-shit-done/bin/lib/secrets.cjs` for the masking implementation. The `config.json` file itself is the security boundary — protect it with filesystem permissions and keep it out of git (`.planning/` is gitignored by default).
|
||||
|
||||
---
|
||||
|
||||
## See also
|
||||
|
||||
- [sdk/src/query/QUERY-HANDLERS.md](../sdk/src/query/QUERY-HANDLERS.md) — registry matrix, routing, golden parity, intentional CJS differences
|
||||
- [Architecture](ARCHITECTURE.md) — where `gsd-sdk query` fits in orchestration
|
||||
- [Command Reference](COMMANDS.md) — user-facing `/gsd:` commands
|
||||
|
||||
@@ -562,6 +562,24 @@ Interactive command center for managing multiple phases from one terminal.
|
||||
/gsd-manager # Open command center dashboard
|
||||
```
|
||||
|
||||
**Checkpoint Heartbeats (#2410):**
|
||||
|
||||
Background `execute-phase` runs emit `[checkpoint]` markers at every wave and plan
|
||||
boundary so the Claude API SSE stream never idles long enough to trigger
|
||||
`Stream idle timeout - partial response received` on multi-plan phases. The
|
||||
format is:
|
||||
|
||||
```
|
||||
[checkpoint] phase {N} wave {W}/{M} starting, {count} plan(s), {P}/{Q} plans done
|
||||
[checkpoint] phase {N} wave {W}/{M} plan {plan_id} starting ({P}/{Q} plans done)
|
||||
[checkpoint] phase {N} wave {W}/{M} plan {plan_id} complete ({P}/{Q} plans done)
|
||||
[checkpoint] phase {N} wave {W}/{M} complete, {P}/{Q} plans done ({ok}/{count} ok)
|
||||
```
|
||||
|
||||
If a background phase fails partway through, grep the transcript for `[checkpoint]`
|
||||
to see the last confirmed boundary. The manager's background-completion handler
|
||||
uses these markers to report partial progress when an agent errors out.
|
||||
|
||||
**Manager Passthrough Flags:**
|
||||
|
||||
Configure per-step flags in `.planning/config.json` under `manager.flags`. These flags are appended to each dispatched command:
|
||||
@@ -1037,12 +1055,73 @@ Manage parallel workstreams for concurrent work on different milestone areas.
|
||||
|
||||
### `/gsd-settings`
|
||||
|
||||
Interactive configuration of workflow toggles and model profile.
|
||||
Interactive configuration of workflow toggles and model profile. Questions are grouped into six visual sections:
|
||||
|
||||
- **Planning** — Research, Plan Checker, Pattern Mapper, Nyquist, UI Phase, UI Gate, AI Phase
|
||||
- **Execution** — Verifier, TDD Mode, Code Review, Code Review Depth _(conditional — only when Code Review is on)_, UI Review
|
||||
- **Docs & Output** — Commit Docs, Skip Discuss, Worktrees
|
||||
- **Features** — Intel, Graphify
|
||||
- **Model & Pipeline** — Model Profile, Auto-Advance, Branching
|
||||
- **Misc** — Context Warnings, Research Qs
|
||||
|
||||
All answers are merged via `gsd-sdk query config-set` into the resolved project config path (`.planning/config.json` for a standard install, or `.planning/workstreams/<active>/config.json` when a workstream is active), preserving unrelated keys. After confirmation, the user may save the full settings object to `~/.gsd/defaults.json` so future `/gsd-new-project` runs start from the same baseline.
|
||||
|
||||
```bash
|
||||
/gsd-settings # Interactive config
|
||||
```
|
||||
|
||||
### `/gsd-settings-advanced`
|
||||
|
||||
Interactive configuration of power-user knobs — plan bounce, subagent timeouts, branch templates, cross-AI delegation, context window, and runtime output. Use after `/gsd-settings` once the common-case toggles are dialed in.
|
||||
|
||||
Six sections, each a focused prompt batch:
|
||||
|
||||
| Section | Keys |
|
||||
|---------|------|
|
||||
| Planning Tuning | `workflow.plan_bounce`, `workflow.plan_bounce_passes`, `workflow.plan_bounce_script`, `workflow.subagent_timeout`, `workflow.inline_plan_threshold` |
|
||||
| Execution Tuning | `workflow.node_repair`, `workflow.node_repair_budget`, `workflow.auto_prune_state` |
|
||||
| Discussion Tuning | `workflow.max_discuss_passes` |
|
||||
| Cross-AI Execution | `workflow.cross_ai_execution`, `workflow.cross_ai_command`, `workflow.cross_ai_timeout` |
|
||||
| Git Customization | `git.base_branch`, `git.phase_branch_template`, `git.milestone_branch_template` |
|
||||
| Runtime / Output | `response_language`, `context_window`, `search_gitignored`, `graphify.build_timeout` |
|
||||
|
||||
Current values are pre-selected; an empty input keeps the existing value. Numeric fields reject non-numeric input and re-prompt. Null-allowed fields (`plan_bounce_script`, `cross_ai_command`, `response_language`) accept an empty input as a clear. Writes route through `gsd-sdk query config-set`, which preserves every unrelated key.
|
||||
|
||||
```bash
|
||||
/gsd-settings-advanced # Six-section interactive config
|
||||
```
|
||||
|
||||
See [CONFIGURATION.md](CONFIGURATION.md) for the full schema and defaults.
|
||||
|
||||
### `/gsd-settings-integrations`
|
||||
|
||||
Interactive configuration of third-party integrations and cross-tool routing.
|
||||
Distinct from `/gsd-settings` (workflow toggles) — this command handles
|
||||
connectivity: API keys, reviewer CLI routing, and agent-skill injection.
|
||||
|
||||
Covers:
|
||||
|
||||
- **Search integrations:** `brave_search`, `firecrawl`, `exa_search` API keys,
|
||||
and the `search_gitignored` toggle.
|
||||
- **Code-review CLI routing:** `review.models.{claude,codex,gemini,opencode}`
|
||||
— a shell command per reviewer flavor.
|
||||
- **Agent-skill injection:** `agent_skills.<agent-type>` — skill names
|
||||
injected into an agent's spawn frontmatter. Agent-type slugs are validated
|
||||
against `[a-zA-Z0-9_-]+` so path separators and shell metacharacters are
|
||||
rejected.
|
||||
|
||||
API keys are stored plaintext in `.planning/config.json` but displayed masked
|
||||
(`****<last-4>`) in every interactive output, confirmation table, and
|
||||
`config-set` stdout/stderr line. Plaintext is never echoed, never logged,
|
||||
and never written to any file outside `config.json` by this workflow.
|
||||
|
||||
```bash
|
||||
/gsd-settings-integrations # Interactive config (three sections)
|
||||
```
|
||||
|
||||
See [`docs/CONFIGURATION.md`](CONFIGURATION.md) for the per-field reference and
|
||||
[`docs/CLI-TOOLS.md`](CLI-TOOLS.md) for the reviewer-CLI routing contract.
|
||||
|
||||
### `/gsd-set-profile`
|
||||
|
||||
Quick profile switch.
|
||||
|
||||
@@ -21,7 +21,7 @@ GSD stores project settings in `.planning/config.json`. Created during `/gsd-new
|
||||
"search_gitignored": false,
|
||||
"sub_repos": []
|
||||
},
|
||||
"context_profile": null,
|
||||
"context": null,
|
||||
"workflow": {
|
||||
"research": true,
|
||||
"plan_check": true,
|
||||
@@ -30,10 +30,12 @@ GSD stores project settings in `.planning/config.json`. Created during `/gsd-new
|
||||
"nyquist_validation": true,
|
||||
"ui_phase": true,
|
||||
"ui_safety_gate": true,
|
||||
"ui_review": true,
|
||||
"node_repair": true,
|
||||
"node_repair_budget": 2,
|
||||
"research_before_questions": false,
|
||||
"discuss_mode": "discuss",
|
||||
"max_discuss_passes": 3,
|
||||
"skip_discuss": false,
|
||||
"tdd_mode": false,
|
||||
"text_mode": false,
|
||||
@@ -43,13 +45,15 @@ GSD stores project settings in `.planning/config.json`. Created during `/gsd-new
|
||||
"plan_bounce": false,
|
||||
"plan_bounce_script": null,
|
||||
"plan_bounce_passes": 2,
|
||||
"plan_chunked": false,
|
||||
"code_review_command": null,
|
||||
"cross_ai_execution": false,
|
||||
"cross_ai_command": null,
|
||||
"cross_ai_timeout": 300,
|
||||
"security_enforcement": true,
|
||||
"security_asvs_level": 1,
|
||||
"security_block_on": "high"
|
||||
"security_block_on": "high",
|
||||
"post_planning_gaps": true
|
||||
},
|
||||
"hooks": {
|
||||
"context_warnings": true,
|
||||
@@ -108,11 +112,15 @@ GSD stores project settings in `.planning/config.json`. Created during `/gsd-new
|
||||
|---------|------|---------|---------|-------------|
|
||||
| `mode` | enum | `interactive`, `yolo` | `interactive` | `yolo` auto-approves decisions; `interactive` confirms at each step |
|
||||
| `granularity` | enum | `coarse`, `standard`, `fine` | `standard` | Controls phase count: `coarse` (3-5), `standard` (5-8), `fine` (8-12) |
|
||||
| `model_profile` | enum | `quality`, `balanced`, `budget`, `inherit` | `balanced` | Model tier for each agent (see [Model Profiles](#model-profiles)) |
|
||||
| `model_profile` | enum | `quality`, `balanced`, `budget`, `adaptive`, `inherit` | `balanced` | Model tier for each agent (see [Model Profiles](#model-profiles)). `adaptive` was added per [#1713](https://github.com/gsd-build/get-shit-done/issues/1713) / [#1806](https://github.com/gsd-build/get-shit-done/issues/1806) and resolves the same way as the other tiers under runtime-aware profiles. |
|
||||
| `runtime` | string | `claude`, `codex`, or any string | (none) | Active runtime for [runtime-aware profile resolution](#runtime-aware-profiles-2517). When set, profile tiers (opus/sonnet/haiku) resolve to runtime-native model IDs. Today only the Codex install path emits per-agent model IDs from this resolver; other runtimes (`opencode`, `gemini`, `qwen`, `copilot`, …) consume the resolver at spawn time and gain dedicated install-path support in [#2612](https://github.com/gsd-build/get-shit-done/issues/2612). When unset (default), behavior is unchanged from prior versions. Added in v1.39 |
|
||||
| `model_profile_overrides.<runtime>.<tier>` | string \| object | per-runtime tier override | (none) | Override the runtime-aware tier mapping for a specific `(runtime, tier)`. Tier is one of `opus`, `sonnet`, `haiku`. Value is either a model ID string (e.g. `"gpt-5-pro"`) or `{ model, reasoning_effort }`. See [Runtime-Aware Profiles](#runtime-aware-profiles-2517). Added in v1.39 |
|
||||
| `project_code` | string | any short string | (none) | Prefix for phase directory names (e.g., `"ABC"` produces `ABC-01-setup/`). Added in v1.31 |
|
||||
| `response_language` | string | language code | (none) | Language for agent responses (e.g., `"pt"`, `"ko"`, `"ja"`). Propagates to all spawned agents for cross-phase language consistency. Added in v1.32 |
|
||||
| `context_window` | number | any integer | `200000` | Context window size in tokens. Set `1000000` for 1M-context models (e.g., `claude-opus-4-7[1m]`). Values `>= 500000` enable adaptive context enrichment (full-body reads of prior SUMMARY.md, deeper anti-pattern reads). Configured via `/gsd-settings-advanced`. |
|
||||
| `context_profile` | string | `dev`, `research`, `review` | (none) | Execution context preset that applies a pre-configured bundle of mode, model, and workflow settings for the current type of work. Added in v1.34 |
|
||||
| `claude_md_path` | string | any file path | `./CLAUDE.md` | Custom output path for the generated CLAUDE.md file. Useful for monorepos or projects that need CLAUDE.md in a non-root location. Defaults to `./CLAUDE.md` at the project root. Added in v1.36 |
|
||||
| `claude_md_assembly.mode` | enum | `embed`, `link` | `embed` | Controls how managed sections are written into CLAUDE.md. `embed` (default) inlines content between GSD markers. `link` writes `@.planning/<source-path>` instead — Claude Code expands the reference at runtime, reducing CLAUDE.md size by ~65% on typical projects. `link` only applies to sections that have a real source file; `workflow` and fallback sections always embed. Per-block overrides: `claude_md_assembly.blocks.<section>` (e.g. `claude_md_assembly.blocks.architecture: link`). Added in v1.38 |
|
||||
| `context` | string | any text | (none) | Custom context string injected into every agent prompt for the project. Use to provide persistent project-specific guidance (e.g., coding conventions, team practices) that every agent should be aware of |
|
||||
| `phase_naming` | string | any string | (none) | Custom prefix for phase directory names. When set, overrides the auto-generated phase slug (e.g., `"feature"` produces `feature-01-setup/` instead of the roadmap-derived slug) |
|
||||
| `brave_search` | boolean | `true`/`false` | auto-detected | Override auto-detection of Brave Search API availability. When unset, GSD checks for `BRAVE_API_KEY` env var or `~/.gsd/brave_api_key` file |
|
||||
@@ -124,6 +132,41 @@ GSD stores project settings in `.planning/config.json`. Created during `/gsd-new
|
||||
|
||||
---
|
||||
|
||||
## Integration Settings
|
||||
|
||||
Configured interactively via [`/gsd-settings-integrations`](COMMANDS.md#gsd-settings-integrations). These are *connectivity* settings — API keys and cross-tool routing — and are intentionally kept separate from `/gsd-settings` (workflow toggles).
|
||||
|
||||
### Search API keys
|
||||
|
||||
API key fields accept a string value (the key itself). They can also be set to the sentinels `true`/`false`/`null` to override auto-detection from env vars / `~/.gsd/*_api_key` files (legacy behavior, see rows above).
|
||||
|
||||
| Setting | Type | Default | Description |
|
||||
|---------|------|---------|-------------|
|
||||
| `brave_search` | string \| boolean \| null | `null` | Brave Search API key used for web research. Displayed as `****<last-4>` in all UI / `config-set` output; never echoed plaintext |
|
||||
| `firecrawl` | string \| boolean \| null | `null` | Firecrawl API key for deep-crawl scraping. Masked in display |
|
||||
| `exa_search` | string \| boolean \| null | `null` | Exa Search API key for semantic search. Masked in display |
|
||||
|
||||
**Masking convention (`get-shit-done/bin/lib/secrets.cjs`):** keys 8+ characters render as `****<last-4>`; shorter keys render as `****`; `null`/empty renders as `(unset)`. Plaintext is written as-is to `.planning/config.json` — that file is the security boundary — but the CLI, confirmation tables, logs, and `AskUserQuestion` descriptions never display the plaintext. This applies to the `config-set` command output itself: `config-set brave_search <key>` returns a JSON payload with the value masked.
|
||||
|
||||
### Code-review CLI routing
|
||||
|
||||
`review.models.<cli>` maps a reviewer flavor to a shell command. The code-review workflow shells out using this command when a matching flavor is requested.
|
||||
|
||||
| Setting | Type | Default | Description |
|
||||
|---------|------|---------|-------------|
|
||||
| `review.models.claude` | string | (session model) | Command for Claude-flavored review. Defaults to the session model when unset |
|
||||
| `review.models.codex` | string | `null` | Command for Codex review, e.g. `"codex exec --model gpt-5"` |
|
||||
| `review.models.gemini` | string | `null` | Command for Gemini review, e.g. `"gemini -m gemini-2.5-pro"` |
|
||||
| `review.models.opencode` | string | `null` | Command for OpenCode review, e.g. `"opencode run --model claude-sonnet-4"` |
|
||||
|
||||
The `<cli>` slug is validated against `[a-zA-Z0-9_-]+`. Empty or path-containing slugs are rejected by `config-set`.
|
||||
|
||||
### Agent-skill injection (dynamic)
|
||||
|
||||
`agent_skills.<agent-type>` extends the `agent_skills` map documented below. Slug is validated against `[a-zA-Z0-9_-]+` — no path separators, no whitespace, no shell metacharacters. Configured interactively via `/gsd-settings-integrations`.
|
||||
|
||||
---
|
||||
|
||||
## Workflow Toggles
|
||||
|
||||
All workflow toggles follow the **absent = enabled** pattern. If a key is missing from config, it defaults to `true`.
|
||||
@@ -137,10 +180,12 @@ All workflow toggles follow the **absent = enabled** pattern. If a key is missin
|
||||
| `workflow.nyquist_validation` | boolean | `true` | Test coverage mapping during plan-phase research |
|
||||
| `workflow.ui_phase` | boolean | `true` | Generate UI design contracts for frontend phases |
|
||||
| `workflow.ui_safety_gate` | boolean | `true` | Prompt to run /gsd-ui-phase for frontend phases during plan-phase |
|
||||
| `workflow.ui_review` | boolean | `true` | Run visual quality audit (`/gsd-ui-review`) after phase execution in autonomous mode. When `false`, the UI audit step is skipped. |
|
||||
| `workflow.node_repair` | boolean | `true` | Autonomous task repair on verification failure |
|
||||
| `workflow.node_repair_budget` | number | `2` | Max repair attempts per failed task |
|
||||
| `workflow.research_before_questions` | boolean | `false` | Run research before discussion questions instead of after |
|
||||
| `workflow.discuss_mode` | string | `'discuss'` | Controls how `/gsd-discuss-phase` gathers context. `'discuss'` (default) asks questions one-by-one. `'assumptions'` reads the codebase first, generates structured assumptions with confidence levels, and only asks you to correct what's wrong. Added in v1.28 |
|
||||
| `workflow.max_discuss_passes` | number | `3` | Maximum number of question rounds in discuss-phase before the workflow stops asking. Useful in headless/auto mode to prevent infinite discussion loops. |
|
||||
| `workflow.skip_discuss` | boolean | `false` | When `true`, `/gsd-autonomous` bypasses the discuss-phase entirely, writing minimal CONTEXT.md from the ROADMAP phase goal. Useful for projects where developer preferences are fully captured in PROJECT.md/REQUIREMENTS.md. Added in v1.28 |
|
||||
| `workflow.text_mode` | boolean | `false` | Replaces AskUserQuestion TUI menus with plain-text numbered lists. Required for Claude Code remote sessions (`/rc` mode) where TUI menus don't render. Can also be set per-session with `--text` flag on discuss-phase. Added in v1.28 |
|
||||
| `workflow.use_worktrees` | boolean | `true` | When `false`, disables git worktree isolation for parallel execution. Users who prefer sequential execution or whose environment does not support worktrees can disable this. Added in v1.31 |
|
||||
@@ -149,6 +194,8 @@ All workflow toggles follow the **absent = enabled** pattern. If a key is missin
|
||||
| `workflow.plan_bounce` | boolean | `false` | Run external validation script against generated plans. When enabled, the plan-phase orchestrator pipes each PLAN.md through the script specified by `plan_bounce_script` and blocks on non-zero exit. Added in v1.36 |
|
||||
| `workflow.plan_bounce_script` | string | (none) | Path to the external script invoked for plan bounce validation. Receives the PLAN.md path as its first argument. Required when `plan_bounce` is `true`. Added in v1.36 |
|
||||
| `workflow.plan_bounce_passes` | number | `2` | Number of sequential bounce passes to run. Each pass feeds the previous pass's output back into the validator. Higher values increase rigor at the cost of latency. Added in v1.36 |
|
||||
| `workflow.post_planning_gaps` | boolean | `true` | Unified post-planning gap report (#2493). After all plans are generated and committed, scans REQUIREMENTS.md and CONTEXT.md `<decisions>` against every PLAN.md in the phase directory, then prints one `Source \| Item \| Status` table. Word-boundary matching (REQ-1 vs REQ-10) and natural sort (REQ-02 before REQ-10). Non-blocking — informational report only. Set to `false` to skip Step 13e of plan-phase. |
|
||||
| `workflow.plan_chunked` | boolean | `false` | Enable chunked planning mode. When `true` (or when `--chunked` flag is passed to `/gsd-plan-phase`), the orchestrator splits the single long-lived planner Task into a short outline Task followed by N short per-plan Tasks (~3-5 min each). Each plan is committed individually for crash resilience. If a Task hangs and the terminal is force-killed, rerunning with `--chunked` resumes from the last completed plan. Particularly useful on Windows where long-lived Tasks may hang on stdio. Added in v1.38 |
|
||||
| `workflow.code_review_command` | string | (none) | Shell command for external code review integration in `/gsd-ship`. Receives changed file paths via stdin. Non-zero exit blocks the ship workflow. Added in v1.36 |
|
||||
| `workflow.tdd_mode` | boolean | `false` | Enable TDD pipeline as a first-class execution mode. When `true`, the planner aggressively applies `type: tdd` to eligible tasks (business logic, APIs, validations, algorithms) and the executor enforces RED/GREEN/REFACTOR gate sequence. An end-of-phase collaborative review checkpoint verifies gate compliance. Added in v1.36 |
|
||||
| `workflow.cross_ai_execution` | boolean | `false` | Delegate phase execution to an external AI CLI instead of spawning local executor agents. Useful for leveraging a different model's strengths for specific phases. Added in v1.36 |
|
||||
@@ -159,6 +206,8 @@ All workflow toggles follow the **absent = enabled** pattern. If a key is missin
|
||||
| `workflow.pattern_mapper` | boolean | `true` | Run the `gsd-pattern-mapper` agent between research and planning to map new files to existing codebase analogs |
|
||||
| `workflow.subagent_timeout` | number | `600` | Timeout in seconds for individual subagent invocations. Increase for long-running research or execution phases |
|
||||
| `workflow.inline_plan_threshold` | number | `3` | Maximum number of tasks in a phase before the planner generates a separate PLAN.md file instead of inlining tasks in the prompt |
|
||||
| `workflow.drift_threshold` | number | `3` | Minimum number of new structural elements (new directories, barrel exports, migrations, route modules) introduced during a phase before the post-execute codebase-drift gate takes action. See [#2003](https://github.com/gsd-build/get-shit-done/issues/2003). Added in v1.39 |
|
||||
| `workflow.drift_action` | string | `warn` | What to do when `workflow.drift_threshold` is exceeded after `/gsd-execute-phase`. `warn` prints a message suggesting `/gsd-map-codebase --paths …`; `auto-remap` spawns `gsd-codebase-mapper` scoped to the affected paths. Added in v1.39 |
|
||||
|
||||
### Recommended Presets
|
||||
|
||||
@@ -178,6 +227,17 @@ All workflow toggles follow the **absent = enabled** pattern. If a key is missin
|
||||
| `planning.search_gitignored` | boolean | `false` | Add `--no-ignore` to broad searches to include `.planning/` |
|
||||
| `planning.sub_repos` | array of strings | `[]` | Paths of nested sub-repos relative to the project root. When set, GSD-aware tooling scopes phase-lookup, path-resolution, and commit operations per sub-repo instead of treating the outer repo as a monorepo |
|
||||
|
||||
### Project-Root Resolution in Multi-Repo Workspaces
|
||||
|
||||
When `sub_repos` is set and `gsd-tools.cjs` or `gsd-sdk query` is invoked from inside a listed child repo, both CLIs walk up to the parent workspace that owns `.planning/` before dispatching handlers. Resolution order (checked at each ancestor up to 10 levels, never above `$HOME`):
|
||||
|
||||
1. If the starting directory already has its own `.planning/`, it is the project root (no walk-up).
|
||||
2. Parent has `.planning/config.json` listing the starting directory's top-level segment in `sub_repos` (or the legacy `planning.sub_repos` shape).
|
||||
3. Parent has `.planning/config.json` with legacy `multiRepo: true` and the starting directory is inside a git repo.
|
||||
4. Parent has `.planning/` and an ancestor up to the candidate parent contains `.git` (heuristic fallback).
|
||||
|
||||
If none match, the starting directory is returned unchanged. Explicit `--project-dir /path/to/workspace` is idempotent under this resolution.
|
||||
|
||||
### Auto-Detection
|
||||
|
||||
If `.planning/` is in `.gitignore`, `commit_docs` is automatically `false` regardless of config.json. This prevents git errors.
|
||||
@@ -190,6 +250,7 @@ If `.planning/` is in `.gitignore`, `commit_docs` is automatically `false` regar
|
||||
|---------|------|---------|-------------|
|
||||
| `hooks.context_warnings` | boolean | `true` | Show context window usage warnings via context monitor hook |
|
||||
| `hooks.workflow_guard` | boolean | `false` | Warn when file edits happen outside GSD workflow context (advises using `/gsd-quick` or `/gsd-fast`) |
|
||||
| `statusline.show_last_command` | boolean | `false` | Append `last: /<cmd>` suffix to the statusline showing the most recently invoked slash command. Opt-in; reads the active session transcript to extract the latest `<command-name>` tag (closes #2538) |
|
||||
|
||||
The prompt injection guard hook (`gsd-prompt-guard.js`) is always active and cannot be disabled — it's a security feature, not a workflow toggle.
|
||||
|
||||
@@ -247,7 +308,7 @@ Any GSD agent type can receive skills. Common types:
|
||||
|
||||
### How It Works
|
||||
|
||||
At spawn time, workflows call `node gsd-tools.cjs agent-skills <type>` to load configured skills. If skills exist for the agent type, they are injected as an `<agent_skills>` block in the Task() prompt:
|
||||
At spawn time, workflows call `gsd-sdk query agent-skills <type>` (or legacy `node gsd-tools.cjs agent-skills <type>`) to load configured skills. If skills exist for the agent type, they are injected as an `<agent_skills>` block in the Task() prompt:
|
||||
|
||||
```xml
|
||||
<agent_skills>
|
||||
@@ -264,7 +325,7 @@ If no skills are configured, the block is omitted (zero overhead).
|
||||
Set skills via the CLI:
|
||||
|
||||
```bash
|
||||
node gsd-tools.cjs config-set agent_skills.gsd-executor '["skills/my-skill"]'
|
||||
gsd-sdk query config-set agent_skills.gsd-executor '["skills/my-skill"]'
|
||||
```
|
||||
|
||||
---
|
||||
@@ -292,10 +353,10 @@ Toggle optional capabilities via the `features.*` config namespace. Feature flag
|
||||
|
||||
```bash
|
||||
# Enable a feature
|
||||
node gsd-tools.cjs config-set features.global_learnings true
|
||||
gsd-sdk query config-set features.global_learnings true
|
||||
|
||||
# Disable a feature
|
||||
node gsd-tools.cjs config-set features.thinking_partner false
|
||||
gsd-sdk query config-set features.thinking_partner false
|
||||
```
|
||||
|
||||
The `features.*` namespace is a dynamic key pattern — new feature flags can be added without modifying `VALID_CONFIG_KEYS`. Any key matching `features.<name>` is accepted by the config system.
|
||||
@@ -394,6 +455,8 @@ Control confirmation prompts during workflows.
|
||||
|
||||
Settings for the security enforcement feature (v1.31). All follow the **absent = enabled** pattern. These keys live under `workflow.*` in `.planning/config.json` — matching the shipped template and the runtime reads in `workflows/plan-phase.md`, `workflows/execute-phase.md`, `workflows/secure-phase.md`, and `workflows/verify-work.md`.
|
||||
|
||||
These keys live under `workflow.*` — that is where the workflows and installer write and read them. Setting them at the top level of `config.json` is silently ignored.
|
||||
|
||||
| Setting | Type | Default | Description |
|
||||
|---------|------|---------|-------------|
|
||||
| `workflow.security_enforcement` | boolean | `true` | Enable threat-model-anchored security verification via `/gsd-secure-phase`. When `false`, security checks are skipped entirely |
|
||||
@@ -402,6 +465,60 @@ Settings for the security enforcement feature (v1.31). All follow the **absent =
|
||||
|
||||
---
|
||||
|
||||
## Decision Coverage Gates (`workflow.context_coverage_gate`)
|
||||
|
||||
When `discuss-phase` writes implementation decisions into CONTEXT.md
|
||||
`<decisions>`, two gates ensure those decisions survive the trip into
|
||||
plans and shipped code (issue #2492).
|
||||
|
||||
| Setting | Type | Default | Description |
|
||||
|---------|------|---------|-------------|
|
||||
| `workflow.context_coverage_gate` | boolean | `true` | Toggle for both decision-coverage gates. When `false`, both the plan-phase translation gate and the verify-phase validation gate skip silently. |
|
||||
|
||||
### What the gates do
|
||||
|
||||
**Plan-phase translation gate (BLOCKING).** Runs immediately after the
|
||||
existing requirements coverage gate, before plans are committed. For each
|
||||
trackable decision in `<decisions>`, it checks that the decision id
|
||||
(`D-NN`) or its text appears in at least one plan's `must_haves`,
|
||||
`truths`, or body. A miss surfaces the missing decision by id and refuses
|
||||
to mark the phase planned.
|
||||
|
||||
**Verify-phase validation gate (NON-BLOCKING).** Runs alongside the other
|
||||
verify steps. Searches every shipped artifact (PLAN.md, SUMMARY.md, files
|
||||
modified, recent commit subjects) for each trackable decision. Misses are
|
||||
written to VERIFICATION.md as a warning section but do **not** flip the
|
||||
overall verification status. The asymmetry is deliberate — by verify time
|
||||
the work is done, and a fuzzy substring miss should not fail an otherwise
|
||||
green phase.
|
||||
|
||||
### How to write decisions the gates accept
|
||||
|
||||
The discuss-phase template already produces `D-NN`-numbered decisions.
|
||||
The gate is happiest when:
|
||||
|
||||
1. Every plan that implements a decision **cites the id** somewhere —
|
||||
`must_haves.truths: ["D-12: bit offsets exposed"]` or a `D-12:` mention
|
||||
in the plan body. Strict id match is the cheapest, deterministic path.
|
||||
2. Soft phrase matching is a fallback for paraphrases — if a 6+-word slice
|
||||
of the decision text appears verbatim in a plan/summary, it counts.
|
||||
|
||||
### Opt-outs
|
||||
|
||||
A decision is **not** subject to the gates when any of the following
|
||||
apply:
|
||||
|
||||
- It lives under the `### Claude's Discretion` heading inside `<decisions>`.
|
||||
- It is tagged `[informational]`, `[folded]`, or `[deferred]` in its
|
||||
bullet (e.g., `- **D-08 [informational]:** Naming style for internal
|
||||
helpers`).
|
||||
|
||||
Use these escape hatches when a decision genuinely doesn't need plan
|
||||
coverage — implementation discretion, future ideas captured for the
|
||||
record, or items already deferred to a later phase.
|
||||
|
||||
---
|
||||
|
||||
## Review Settings
|
||||
|
||||
Configure per-CLI model selection for `/gsd-review`. When set, overrides the CLI's default model for that reviewer.
|
||||
@@ -503,6 +620,17 @@ Override specific agents without changing the entire profile:
|
||||
|
||||
Valid override values: `opus`, `sonnet`, `haiku`, `inherit`, or any fully-qualified model ID (e.g., `"openai/o3"`, `"google/gemini-2.5-pro"`).
|
||||
|
||||
`model_overrides` can be set in either `.planning/config.json` (per-project)
|
||||
or `~/.gsd/defaults.json` (global). Per-project entries win on conflict and
|
||||
non-conflicting global entries are preserved, so you can tune a single
|
||||
agent's model in one repo without re-setting global defaults. This applies
|
||||
uniformly across Claude Code, Codex, OpenCode, Kilo, and the other
|
||||
supported runtimes. On Codex and OpenCode, the resolved model is embedded
|
||||
into each agent's static config at install time — `spawn_agent` and
|
||||
OpenCode's `task` interface do not accept an inline `model` parameter, so
|
||||
running `gsd install <runtime>` after editing `model_overrides` is required
|
||||
for the change to take effect. See issue #2256.
|
||||
|
||||
### Non-Claude Runtimes (Codex, OpenCode, Gemini CLI, Kilo)
|
||||
|
||||
When GSD is installed for a non-Claude runtime, the installer automatically sets `resolve_model_ids: "omit"` in `~/.gsd/defaults.json`. This causes GSD to return an empty model parameter for all agents, so each agent uses whatever model the runtime is configured with. No additional setup is needed for the default case.
|
||||
@@ -540,6 +668,64 @@ The intent is the same as the Claude profile tiers -- use a stronger model for p
|
||||
| `true` | Maps aliases to full Claude model IDs (`claude-opus-4-6`) | Claude Code with API that requires full IDs |
|
||||
| `"omit"` | Returns empty string (runtime picks its default) | Non-Claude runtimes (Codex, OpenCode, Gemini CLI, Kilo) |
|
||||
|
||||
### Runtime-Aware Profiles (#2517)
|
||||
|
||||
When `runtime` is set, profile tiers (`opus`/`sonnet`/`haiku`) resolve to runtime-native model IDs instead of Claude aliases. This lets a single shared `.planning/config.json` work cleanly across Claude and Codex.
|
||||
|
||||
**Built-in tier maps:**
|
||||
|
||||
| Runtime | `opus` | `sonnet` | `haiku` | reasoning_effort |
|
||||
|---------|--------|----------|---------|------------------|
|
||||
| `claude` | `claude-opus-4-6` | `claude-sonnet-4-6` | `claude-haiku-4-5` | (not used) |
|
||||
| `codex` | `gpt-5.4` | `gpt-5.3-codex` | `gpt-5.4-mini` | `xhigh` / `medium` / `medium` |
|
||||
|
||||
**Codex example** — one config, tiered models, no large `model_overrides` block:
|
||||
|
||||
```json
|
||||
{
|
||||
"runtime": "codex",
|
||||
"model_profile": "balanced"
|
||||
}
|
||||
```
|
||||
|
||||
This resolves `gsd-planner` → `gpt-5.4` (xhigh), `gsd-executor` → `gpt-5.3-codex` (medium), `gsd-codebase-mapper` → `gpt-5.4-mini` (medium). The Codex installer embeds `model = "..."` and `model_reasoning_effort = "..."` in each generated agent TOML.
|
||||
|
||||
**Claude example** — explicit opt-in resolves to full Claude IDs (no `resolve_model_ids: true` needed):
|
||||
|
||||
```json
|
||||
{
|
||||
"runtime": "claude",
|
||||
"model_profile": "quality"
|
||||
}
|
||||
```
|
||||
|
||||
**Per-runtime overrides** — replace one or more tier defaults:
|
||||
|
||||
```json
|
||||
{
|
||||
"runtime": "codex",
|
||||
"model_profile": "quality",
|
||||
"model_profile_overrides": {
|
||||
"codex": {
|
||||
"opus": "gpt-5-pro",
|
||||
"haiku": { "model": "gpt-5-nano", "reasoning_effort": "low" }
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Precedence (highest to lowest):**
|
||||
|
||||
1. `model_overrides[<agent>]` — explicit per-agent ID always wins.
|
||||
2. **Runtime-aware tier resolution** (this section) — when `runtime` is set and profile is not `inherit`.
|
||||
3. `resolve_model_ids: "omit"` — returns empty string when no `runtime` is set.
|
||||
4. Claude-native default — `model_profile` tier as alias (current default).
|
||||
5. `inherit` — propagates literal `inherit` for `Task(model="inherit")` semantics.
|
||||
|
||||
**Backwards compatibility.** Setups without `runtime` set see zero behavior change — every existing config continues to work identically. Codex installs that auto-set `resolve_model_ids: "omit"` continue to omit the model field unless the user opts in by setting `runtime: "codex"`.
|
||||
|
||||
**Unknown runtimes.** If `runtime` is set to a value with no built-in tier map and no `model_profile_overrides[<runtime>]`, GSD falls back to the Claude-alias safe default rather than emit a model ID the runtime cannot accept. To support a new runtime, populate `model_profile_overrides.<runtime>.{opus,sonnet,haiku}` with valid IDs.
|
||||
|
||||
### Profile Philosophy
|
||||
|
||||
| Profile | Philosophy | When to Use |
|
||||
|
||||
@@ -802,6 +802,45 @@
|
||||
| `TESTING.md` | Test infrastructure, coverage, patterns |
|
||||
| `INTEGRATIONS.md` | External services, APIs, third-party dependencies |
|
||||
|
||||
**Incremental remap — `--paths` (#2003):** The mapper accepts an optional
|
||||
`--paths <p1,p2,...>` scope hint. When provided, it restricts exploration
|
||||
to the listed repo-relative prefixes instead of scanning the whole tree.
|
||||
This is the pathway used by the post-execute codebase-drift gate to refresh
|
||||
only the subtrees the phase actually changed. Each produced document carries
|
||||
`last_mapped_commit` in its YAML frontmatter so drift can be measured
|
||||
against the mapping point, not HEAD.
|
||||
|
||||
### 27a. Post-Execute Codebase Drift Detection
|
||||
|
||||
**Introduced by:** #2003
|
||||
**Trigger:** Runs automatically at the end of every `/gsd:execute-phase`
|
||||
**Configuration:**
|
||||
- `workflow.drift_threshold` (integer, default `3`) — minimum new
|
||||
structural elements before the gate acts.
|
||||
- `workflow.drift_action` (`warn` | `auto-remap`, default `warn`) —
|
||||
warn-only or spawn `gsd-codebase-mapper` with `--paths` scoped to
|
||||
affected subtrees.
|
||||
|
||||
**What counts as drift:**
|
||||
- New directory outside mapped paths
|
||||
- New barrel export at `(packages|apps)/*/src/index.*`
|
||||
- New migration file (supabase/prisma/drizzle/src/migrations/…)
|
||||
- New route module under `routes/` or `api/`
|
||||
|
||||
**Non-blocking guarantee:** any internal failure (missing STRUCTURE.md,
|
||||
git errors, mapper spawn failure) logs a single line and the phase
|
||||
continues. Drift detection cannot fail verification.
|
||||
|
||||
**Requirements:**
|
||||
- REQ-DRIFT-01: System MUST detect the four drift categories from `git diff
|
||||
--name-status last_mapped_commit..HEAD`
|
||||
- REQ-DRIFT-02: Action fires only when element count ≥ `workflow.drift_threshold`
|
||||
- REQ-DRIFT-03: `warn` action MUST NOT spawn any agent
|
||||
- REQ-DRIFT-04: `auto-remap` action MUST pass sanitized `--paths` to the mapper
|
||||
- REQ-DRIFT-05: Detection/remap failure MUST be non-blocking for `/gsd:execute-phase`
|
||||
- REQ-DRIFT-06: `last_mapped_commit` round-trip through YAML frontmatter
|
||||
on each `.planning/codebase/*.md` file
|
||||
|
||||
---
|
||||
|
||||
## Utility Features
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"generated": "2026-04-20",
|
||||
"generated": "2026-04-23",
|
||||
"families": {
|
||||
"agents": [
|
||||
"gsd-advisor-researcher",
|
||||
@@ -103,6 +103,8 @@
|
||||
"/gsd-session-report",
|
||||
"/gsd-set-profile",
|
||||
"/gsd-settings",
|
||||
"/gsd-settings-advanced",
|
||||
"/gsd-settings-integrations",
|
||||
"/gsd-ship",
|
||||
"/gsd-sketch",
|
||||
"/gsd-sketch-wrap-up",
|
||||
@@ -110,6 +112,7 @@
|
||||
"/gsd-spike",
|
||||
"/gsd-spike-wrap-up",
|
||||
"/gsd-stats",
|
||||
"/gsd-sync-skills",
|
||||
"/gsd-thread",
|
||||
"/gsd-ui-phase",
|
||||
"/gsd-ui-review",
|
||||
@@ -149,6 +152,7 @@
|
||||
"extract_learnings.md",
|
||||
"fast.md",
|
||||
"forensics.md",
|
||||
"graduation.md",
|
||||
"health.md",
|
||||
"help.md",
|
||||
"import.md",
|
||||
@@ -183,6 +187,8 @@
|
||||
"scan.md",
|
||||
"secure-phase.md",
|
||||
"session-report.md",
|
||||
"settings-advanced.md",
|
||||
"settings-integrations.md",
|
||||
"settings.md",
|
||||
"ship.md",
|
||||
"sketch-wrap-up.md",
|
||||
@@ -191,6 +197,7 @@
|
||||
"spike-wrap-up.md",
|
||||
"spike.md",
|
||||
"stats.md",
|
||||
"sync-skills.md",
|
||||
"transition.md",
|
||||
"ui-phase.md",
|
||||
"ui-review.md",
|
||||
@@ -226,6 +233,7 @@
|
||||
"model-profiles.md",
|
||||
"phase-argument-parsing.md",
|
||||
"planner-antipatterns.md",
|
||||
"planner-chunked.md",
|
||||
"planner-gap-closure.md",
|
||||
"planner-reviews.md",
|
||||
"planner-revision.md",
|
||||
@@ -234,6 +242,7 @@
|
||||
"project-skills-discovery.md",
|
||||
"questioning.md",
|
||||
"revision-loop.md",
|
||||
"scout-codebase.md",
|
||||
"sketch-interactivity.md",
|
||||
"sketch-theme-system.md",
|
||||
"sketch-tooling.md",
|
||||
@@ -253,13 +262,17 @@
|
||||
"workstream-flag.md"
|
||||
],
|
||||
"cli_modules": [
|
||||
"artifacts.cjs",
|
||||
"audit.cjs",
|
||||
"commands.cjs",
|
||||
"config-schema.cjs",
|
||||
"config.cjs",
|
||||
"core.cjs",
|
||||
"decisions.cjs",
|
||||
"docs.cjs",
|
||||
"drift.cjs",
|
||||
"frontmatter.cjs",
|
||||
"gap-checker.cjs",
|
||||
"graphify.cjs",
|
||||
"gsd2-import.cjs",
|
||||
"init.cjs",
|
||||
@@ -272,6 +285,7 @@
|
||||
"profile-pipeline.cjs",
|
||||
"roadmap.cjs",
|
||||
"schema-detect.cjs",
|
||||
"secrets.cjs",
|
||||
"security.cjs",
|
||||
"state.cjs",
|
||||
"template.cjs",
|
||||
|
||||
@@ -54,7 +54,7 @@ Full roster at `agents/gsd-*.md`. The "Primary doc" column flags whether [`docs/
|
||||
|
||||
---
|
||||
|
||||
## Commands (82 shipped)
|
||||
## Commands (85 shipped)
|
||||
|
||||
Full roster at `commands/gsd/*.md`. The groupings below mirror `docs/COMMANDS.md` section order; each row carries the command name, a one-line role derived from the command's frontmatter `description:`, and a link to the source file. `tests/command-count-sync.test.cjs` locks the count against the filesystem.
|
||||
|
||||
@@ -163,8 +163,11 @@ Full roster at `commands/gsd/*.md`. The groupings below mirror `docs/COMMANDS.md
|
||||
| `/gsd-sketch-wrap-up` | Package sketch design findings into a persistent project skill for future build conversations. | [commands/gsd/sketch-wrap-up.md](../commands/gsd/sketch-wrap-up.md) |
|
||||
| `/gsd-profile-user` | Generate developer behavioral profile and Claude-discoverable artifacts. | [commands/gsd/profile-user.md](../commands/gsd/profile-user.md) |
|
||||
| `/gsd-settings` | Configure GSD workflow toggles and model profile. | [commands/gsd/settings.md](../commands/gsd/settings.md) |
|
||||
| `/gsd-settings-advanced` | Power-user configuration — plan bounce, timeouts, branch templates, cross-AI execution, runtime knobs. | [commands/gsd/settings-advanced.md](../commands/gsd/settings-advanced.md) |
|
||||
| `/gsd-settings-integrations` | Configure third-party API keys, code-review CLI routing, and agent-skill injection. | [commands/gsd/settings-integrations.md](../commands/gsd/settings-integrations.md) |
|
||||
| `/gsd-set-profile` | Switch model profile for GSD agents (quality/balanced/budget/inherit). | [commands/gsd/set-profile.md](../commands/gsd/set-profile.md) |
|
||||
| `/gsd-pr-branch` | Create a clean PR branch by filtering out `.planning/` commits. | [commands/gsd/pr-branch.md](../commands/gsd/pr-branch.md) |
|
||||
| `/gsd-sync-skills` | Sync managed GSD skill directories across runtime roots for multi-runtime users. | [commands/gsd/sync-skills.md](../commands/gsd/sync-skills.md) |
|
||||
| `/gsd-update` | Update GSD to latest version with changelog display. | [commands/gsd/update.md](../commands/gsd/update.md) |
|
||||
| `/gsd-reapply-patches` | Reapply local modifications after a GSD update. | [commands/gsd/reapply-patches.md](../commands/gsd/reapply-patches.md) |
|
||||
| `/gsd-help` | Show available GSD commands and usage guide. | [commands/gsd/help.md](../commands/gsd/help.md) |
|
||||
@@ -172,7 +175,7 @@ Full roster at `commands/gsd/*.md`. The groupings below mirror `docs/COMMANDS.md
|
||||
|
||||
---
|
||||
|
||||
## Workflows (79 shipped)
|
||||
## Workflows (83 shipped)
|
||||
|
||||
Full roster at `get-shit-done/workflows/*.md`. Workflows are thin orchestrators that commands reference internally; most are not read directly by end users. Rows below map each workflow file to its role (derived from the `<purpose>` block) and, where applicable, to the command that invokes it.
|
||||
|
||||
@@ -206,6 +209,7 @@ Full roster at `get-shit-done/workflows/*.md`. Workflows are thin orchestrators
|
||||
| `extract_learnings.md` | Extract decisions, lessons, patterns, and surprises from completed phase artifacts. | `/gsd-extract-learnings` |
|
||||
| `fast.md` | Execute a trivial task inline without subagent overhead. | `/gsd-fast` |
|
||||
| `forensics.md` | Forensics investigation of failed workflows — git, artifacts, and state analysis. | `/gsd-forensics` |
|
||||
| `graduation.md` | Cluster recurring LEARNINGS.md items across phases and surface HITL promotion candidates. | `transition.md` (graduation_scan step) |
|
||||
| `health.md` | Validate `.planning/` directory integrity and report actionable issues. | `/gsd-health` |
|
||||
| `help.md` | Display the complete GSD command reference. | `/gsd-help` |
|
||||
| `import.md` | Ingest external plans with conflict detection against existing project decisions. | `/gsd-import` |
|
||||
@@ -241,6 +245,8 @@ Full roster at `get-shit-done/workflows/*.md`. Workflows are thin orchestrators
|
||||
| `secure-phase.md` | Retroactive threat-mitigation audit for a completed phase. | `/gsd-secure-phase` |
|
||||
| `session-report.md` | Session report — token usage, work summary, outcomes. | `/gsd-session-report` |
|
||||
| `settings.md` | Configure GSD workflow toggles and model profile. | `/gsd-settings`, `/gsd-set-profile` |
|
||||
| `settings-advanced.md` | Configure GSD power-user knobs — plan bounce, timeouts, branch templates, cross-AI execution, runtime knobs. | `/gsd-settings-advanced` |
|
||||
| `settings-integrations.md` | Configure third-party API keys (Brave/Firecrawl/Exa), `review.models.<cli>` CLI routing, and `agent_skills.<agent-type>` injection with masked (`****<last-4>`) display. | `/gsd-settings-integrations` |
|
||||
| `ship.md` | Create PR, run review, and prepare for merge after verification. | `/gsd-ship` |
|
||||
| `sketch.md` | Explore design directions through throwaway HTML mockups with 2-3 variants per sketch. | `/gsd-sketch` |
|
||||
| `sketch-wrap-up.md` | Curate sketch findings and package them as a persistent `sketch-findings-[project]` skill. | `/gsd-sketch-wrap-up` |
|
||||
@@ -248,6 +254,7 @@ Full roster at `get-shit-done/workflows/*.md`. Workflows are thin orchestrators
|
||||
| `spike.md` | Rapid feasibility validation through focused, throwaway experiments. | `/gsd-spike` |
|
||||
| `spike-wrap-up.md` | Curate spike findings and package them as a persistent `spike-findings-[project]` skill. | `/gsd-spike-wrap-up` |
|
||||
| `stats.md` | Project statistics rendering — phases, plans, requirements, git metrics. | `/gsd-stats` |
|
||||
| `sync-skills.md` | Cross-runtime GSD skill sync — diff and apply `gsd-*` skill directories across runtime roots. | `/gsd-sync-skills` |
|
||||
| `transition.md` | Phase-boundary transition workflow — workstream checks, state advancement. | `execute-phase.md`, `/gsd-next` |
|
||||
| `ui-phase.md` | Generate UI-SPEC.md design contract via gsd-ui-researcher. | `/gsd-ui-phase` |
|
||||
| `ui-review.md` | Retroactive 6-pillar visual audit via gsd-ui-auditor. | `/gsd-ui-review` |
|
||||
@@ -262,7 +269,7 @@ Full roster at `get-shit-done/workflows/*.md`. Workflows are thin orchestrators
|
||||
|
||||
---
|
||||
|
||||
## References (49 shipped)
|
||||
## References (51 shipped)
|
||||
|
||||
Full roster at `get-shit-done/references/*.md`. References are shared knowledge documents that workflows and agents `@-reference`. The groupings below match [`docs/ARCHITECTURE.md`](ARCHITECTURE.md#references-get-shit-donereferencesmd) — core, workflow, thinking-model clusters, and the modular planner decomposition.
|
||||
|
||||
@@ -296,6 +303,7 @@ Full roster at `get-shit-done/references/*.md`. References are shared knowledge
|
||||
| `continuation-format.md` | Session continuation/resume format. |
|
||||
| `domain-probes.md` | Domain-specific probing questions for discuss-phase. |
|
||||
| `gate-prompts.md` | Gate/checkpoint prompt templates. |
|
||||
| `scout-codebase.md` | Phase-type→codebase-map selection table for discuss-phase scout step (extracted via #2551). |
|
||||
| `revision-loop.md` | Plan revision iteration patterns. |
|
||||
| `universal-anti-patterns.md` | Universal anti-patterns to detect and avoid. |
|
||||
| `artifact-types.md` | Planning artifact type definitions. |
|
||||
@@ -341,28 +349,33 @@ The `gsd-planner` agent is decomposed into a core agent plus reference modules t
|
||||
| Reference | Role |
|
||||
|-----------|------|
|
||||
| `planner-antipatterns.md` | Planner anti-patterns and specificity examples. |
|
||||
| `planner-chunked.md` | Chunked mode return formats (`## OUTLINE COMPLETE`, `## PLAN COMPLETE`) for Windows stdio hang mitigation. |
|
||||
| `planner-gap-closure.md` | Gap-closure mode behavior (reads VERIFICATION.md, targeted replanning). |
|
||||
| `planner-reviews.md` | Cross-AI review integration (reads REVIEWS.md from `/gsd-review`). |
|
||||
| `planner-revision.md` | Plan revision patterns for iterative refinement. |
|
||||
| `planner-source-audit.md` | Planner source-audit and authority-limit rules. |
|
||||
|
||||
> **Subdirectory:** `get-shit-done/references/few-shot-examples/` contains additional few-shot examples (`plan-checker.md`, `verifier.md`) that are referenced from specific agents. These are not counted in the 49 top-level references.
|
||||
> **Subdirectory:** `get-shit-done/references/few-shot-examples/` contains additional few-shot examples (`plan-checker.md`, `verifier.md`) that are referenced from specific agents. These are not counted in the 51 top-level references.
|
||||
|
||||
---
|
||||
|
||||
## CLI Modules (25 shipped)
|
||||
## CLI Modules (30 shipped)
|
||||
|
||||
Full listing: `get-shit-done/bin/lib/*.cjs`.
|
||||
|
||||
| Module | Responsibility |
|
||||
|--------|----------------|
|
||||
| `artifacts.cjs` | Canonical artifact registry — known `.planning/` root file names; used by `gsd-health` W019 lint |
|
||||
| `audit.cjs` | Audit dispatch, audit open sessions, audit storage helpers |
|
||||
| `commands.cjs` | Misc CLI commands (slug, timestamp, todos, scaffolding, stats) |
|
||||
| `config-schema.cjs` | Single source of truth for `VALID_CONFIG_KEYS` and dynamic key patterns; imported by both the validator and the config-schema-docs parity test |
|
||||
| `config.cjs` | `config.json` read/write, section initialization; imports validator from `config-schema.cjs` |
|
||||
| `core.cjs` | Error handling, output formatting, shared utilities, runtime fallbacks |
|
||||
| `decisions.cjs` | Shared parser for CONTEXT.md `<decisions>` blocks (D-NN entries); used by `gap-checker.cjs` and intended for #2492 plan/verify decision gates |
|
||||
| `docs.cjs` | Docs-update workflow init, Markdown scanning, monorepo detection |
|
||||
| `drift.cjs` | Post-execute codebase structural drift detector (#2003): classifies file changes into new-dir/barrel/migration/route categories and round-trips `last_mapped_commit` frontmatter |
|
||||
| `frontmatter.cjs` | YAML frontmatter CRUD operations |
|
||||
| `gap-checker.cjs` | Post-planning gap analysis (#2493): unified REQUIREMENTS.md + CONTEXT.md decisions vs PLAN.md coverage report (`gsd-tools gap-analysis`) |
|
||||
| `graphify.cjs` | Knowledge-graph build/query/status/diff for `/gsd-graphify` |
|
||||
| `gsd2-import.cjs` | External-plan ingest for `/gsd-from-gsd2` |
|
||||
| `init.cjs` | Compound context loading for each workflow type |
|
||||
@@ -375,6 +388,7 @@ Full listing: `get-shit-done/bin/lib/*.cjs`.
|
||||
| `profile-pipeline.cjs` | User behavioral profiling data pipeline, session file scanning |
|
||||
| `roadmap.cjs` | ROADMAP.md parsing, phase extraction, plan progress |
|
||||
| `schema-detect.cjs` | Schema-drift detection for ORM patterns (Prisma, Drizzle, etc.) |
|
||||
| `secrets.cjs` | Secret-config masking convention (`****<last-4>`) for integration keys managed by `/gsd-settings-integrations` — keeps plaintext out of `config-set` output |
|
||||
| `security.cjs` | Path traversal prevention, prompt injection detection, safe JSON/shell helpers |
|
||||
| `state.cjs` | STATE.md parsing, updating, progression, metrics |
|
||||
| `template.cjs` | Template selection and filling with variable substitution |
|
||||
|
||||
@@ -26,4 +26,4 @@ Language versions: [English](README.md) · [Português (pt-BR)](pt-BR/README.md)
|
||||
- **All commands at a glance:** [Command Reference](COMMANDS.md)
|
||||
- **Configuring GSD:** [Configuration Reference](CONFIGURATION.md)
|
||||
- **How the system works internally:** [Architecture](ARCHITECTURE.md)
|
||||
- **Contributing or extending:** [CLI Tools Reference](CLI-TOOLS.md) + [Agent Reference](AGENTS.md)
|
||||
- **Contributing or extending:** [CLI Tools Reference](CLI-TOOLS.md) + [Agent Reference](AGENTS.md)
|
||||
@@ -165,18 +165,61 @@ By default, `/gsd-discuss-phase` asks open-ended questions about your implementa
|
||||
**Enable:** Set `workflow.discuss_mode` to `'assumptions'` via `/gsd-settings`.
|
||||
|
||||
**How it works:**
|
||||
|
||||
1. Reads PROJECT.md, codebase mapping, and existing conventions
|
||||
2. Generates a structured list of assumptions (tech choices, patterns, file locations)
|
||||
3. Presents assumptions for you to confirm, correct, or expand
|
||||
4. Writes CONTEXT.md from confirmed assumptions
|
||||
|
||||
**When to use:**
|
||||
|
||||
- Experienced developers who already know their codebase well
|
||||
- Rapid iteration where open-ended questions slow you down
|
||||
- Projects where patterns are well-established and predictable
|
||||
|
||||
See [docs/workflow-discuss-mode.md](workflow-discuss-mode.md) for the full discuss-mode reference.
|
||||
|
||||
### Decision Coverage Gates
|
||||
|
||||
The discuss-phase captures implementation decisions in CONTEXT.md under a
|
||||
`<decisions>` block as numbered bullets (`- **D-01:** …`). Two gates — added
|
||||
for issue #2492 — ensure those decisions survive into plans and shipped
|
||||
code.
|
||||
|
||||
**Plan-phase translation gate (blocking).** After planning, GSD refuses to
|
||||
mark the phase planned until every trackable decision appears in at least
|
||||
one plan's `must_haves`, `truths`, or body. The gate names each missed
|
||||
decision by id (`D-07: …`) so you know exactly what to add, move, or
|
||||
reclassify.
|
||||
|
||||
**Verify-phase validation gate (non-blocking).** During verification, GSD
|
||||
searches plans, SUMMARY.md, modified files, and recent commit messages for
|
||||
each trackable decision. Misses are logged to VERIFICATION.md as a warning
|
||||
section; verification status is unchanged. The asymmetry is deliberate —
|
||||
the blocking gate is cheap at plan time but hostile at verify time.
|
||||
|
||||
**Writing decisions the gate can match.** Two match modes:
|
||||
|
||||
1. **Strict id match (recommended).** Cite the decision id anywhere in a
|
||||
plan that implements it — `must_haves.truths: ["D-12: bit offsets
|
||||
exposed"]`, a bullet in the plan body, a frontmatter comment. This is
|
||||
deterministic and unambiguous.
|
||||
2. **Soft phrase match (fallback).** If a 6+-word slice of the decision
|
||||
text appears verbatim in any plan or shipped artifact, it counts. This
|
||||
forgives paraphrasing but is less reliable.
|
||||
|
||||
**Opting a decision out.** If a decision genuinely should not be tracked —
|
||||
an implementation-discretion note, an informational capture, a decision
|
||||
already deferred — mark it one of these ways:
|
||||
|
||||
- Move it under the `### Claude's Discretion` heading inside `<decisions>`.
|
||||
- Tag it in its bullet: `- **D-08 [informational]:** …`,
|
||||
`- **D-09 [folded]:** …`, `- **D-10 [deferred]:** …`.
|
||||
|
||||
**Disabling the gates.** Set
|
||||
`workflow.context_coverage_gate: false` in `.planning/config.json` (or via
|
||||
`/gsd-settings`) to skip both gates silently. Default is `true`.
|
||||
|
||||
---
|
||||
|
||||
## UI Design Contract
|
||||
@@ -189,16 +232,19 @@ AI-generated frontends are visually inconsistent not because Claude Code is bad
|
||||
|
||||
### Commands
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| `/gsd-ui-phase [N]` | Generate UI-SPEC.md design contract for a frontend phase |
|
||||
| `/gsd-ui-review [N]` | Retroactive 6-pillar visual audit of implemented UI |
|
||||
|
||||
| Command | Description |
|
||||
| -------------------- | -------------------------------------------------------- |
|
||||
| `/gsd-ui-phase [N]` | Generate UI-SPEC.md design contract for a frontend phase |
|
||||
| `/gsd-ui-review [N]` | Retroactive 6-pillar visual audit of implemented UI |
|
||||
|
||||
|
||||
### Workflow: `/gsd-ui-phase`
|
||||
|
||||
**When to run:** After `/gsd-discuss-phase`, before `/gsd-plan-phase` — for phases with frontend/UI work.
|
||||
|
||||
**Flow:**
|
||||
|
||||
1. Reads CONTEXT.md, RESEARCH.md, REQUIREMENTS.md for existing decisions
|
||||
2. Detects design system state (shadcn components.json, Tailwind config, existing tokens)
|
||||
3. shadcn initialization gate — offers to initialize if React/Next.js/Vite project has none
|
||||
@@ -216,6 +262,7 @@ AI-generated frontends are visually inconsistent not because Claude Code is bad
|
||||
**Standalone:** Works on any project, not just GSD-managed ones. If no UI-SPEC.md exists, audits against abstract 6-pillar standards.
|
||||
|
||||
**6 Pillars (scored 1-4 each):**
|
||||
|
||||
1. Copywriting — CTA labels, empty states, error states
|
||||
2. Visuals — focal points, visual hierarchy, icon accessibility
|
||||
3. Color — accent usage discipline, 60/30/10 compliance
|
||||
@@ -227,10 +274,12 @@ AI-generated frontends are visually inconsistent not because Claude Code is bad
|
||||
|
||||
### Configuration
|
||||
|
||||
| Setting | Default | Description |
|
||||
|---------|---------|-------------|
|
||||
| `workflow.ui_phase` | `true` | Generate UI design contracts for frontend phases |
|
||||
| `workflow.ui_safety_gate` | `true` | plan-phase prompts to run /gsd-ui-phase for frontend phases |
|
||||
|
||||
| Setting | Default | Description |
|
||||
| ------------------------- | ------- | ----------------------------------------------------------- |
|
||||
| `workflow.ui_phase` | `true` | Generate UI design contracts for frontend phases |
|
||||
| `workflow.ui_safety_gate` | `true` | plan-phase prompts to run /gsd-ui-phase for frontend phases |
|
||||
|
||||
|
||||
Both follow the absent=enabled pattern. Disable via `/gsd-settings`.
|
||||
|
||||
@@ -248,6 +297,7 @@ The preset string becomes a first-class GSD planning artifact, reproducible acro
|
||||
### Registry Safety Gate
|
||||
|
||||
Third-party shadcn registries can inject arbitrary code. The safety gate requires:
|
||||
|
||||
- `npx shadcn view {component}` — inspect before installing
|
||||
- `npx shadcn diff {component}` — compare against official
|
||||
|
||||
@@ -365,12 +415,14 @@ Workstreams let you work on multiple milestone areas concurrently without state
|
||||
|
||||
### Commands
|
||||
|
||||
| Command | Purpose |
|
||||
|---------|---------|
|
||||
| `/gsd-workstreams create <name>` | Create a new workstream with isolated planning state |
|
||||
| `/gsd-workstreams switch <name>` | Switch active context to a different workstream |
|
||||
| `/gsd-workstreams list` | Show all workstreams and which is active |
|
||||
| `/gsd-workstreams complete <name>` | Mark a workstream as done and archive its state |
|
||||
|
||||
| Command | Purpose |
|
||||
| ---------------------------------- | ---------------------------------------------------- |
|
||||
| `/gsd-workstreams create <name>` | Create a new workstream with isolated planning state |
|
||||
| `/gsd-workstreams switch <name>` | Switch active context to a different workstream |
|
||||
| `/gsd-workstreams list` | Show all workstreams and which is active |
|
||||
| `/gsd-workstreams complete <name>` | Mark a workstream as done and archive its state |
|
||||
|
||||
|
||||
### How It Works
|
||||
|
||||
@@ -393,6 +445,7 @@ All user-supplied file paths (`--text-file`, `--prd`) are validated to resolve w
|
||||
The `security.cjs` module scans for known injection patterns (role overrides, instruction bypasses, system tag injections) in user-supplied text before it enters planning artifacts.
|
||||
|
||||
**Runtime Hooks:**
|
||||
|
||||
- `gsd-prompt-guard.js` — Scans Write/Edit calls to `.planning/` for injection patterns (always active, advisory-only)
|
||||
- `gsd-workflow-guard.js` — Warns on file edits outside GSD workflow context (opt-in via `hooks.workflow_guard`)
|
||||
|
||||
@@ -573,6 +626,20 @@ claude --dangerously-skip-permissions
|
||||
# (normal phase workflow from here)
|
||||
```
|
||||
|
||||
**Post-execute drift detection (#2003).** After every `/gsd:execute-phase`,
|
||||
GSD checks whether the phase introduced enough structural change
|
||||
(new directories, barrel exports, migrations, or route modules) to make
|
||||
`.planning/codebase/STRUCTURE.md` stale. If it did, the default behavior is
|
||||
to print a one-shot warning suggesting the exact `/gsd:map-codebase --paths …`
|
||||
invocation to refresh just the affected subtrees. Flip the behavior with:
|
||||
|
||||
```bash
|
||||
/gsd:settings workflow.drift_action auto-remap # remap automatically
|
||||
/gsd:settings workflow.drift_threshold 5 # tune sensitivity
|
||||
```
|
||||
|
||||
The gate is non-blocking: any internal failure logs and the phase continues.
|
||||
|
||||
### Quick Bug Fix
|
||||
|
||||
```bash
|
||||
@@ -598,11 +665,13 @@ claude --dangerously-skip-permissions
|
||||
|
||||
### Speed vs Quality Presets
|
||||
|
||||
| Scenario | Mode | Granularity | Profile | Research | Plan Check | Verifier |
|
||||
|----------|------|-------|---------|----------|------------|----------|
|
||||
| Prototyping | `yolo` | `coarse` | `budget` | off | off | off |
|
||||
| Normal dev | `interactive` | `standard` | `balanced` | on | on | on |
|
||||
| Production | `interactive` | `fine` | `quality` | on | on | on |
|
||||
|
||||
| Scenario | Mode | Granularity | Profile | Research | Plan Check | Verifier |
|
||||
| ----------- | ------------- | ----------- | ---------- | -------- | ---------- | -------- |
|
||||
| Prototyping | `yolo` | `coarse` | `budget` | off | off | off |
|
||||
| Normal dev | `interactive` | `standard` | `balanced` | on | on | on |
|
||||
| Production | `interactive` | `fine` | `quality` | on | on | on |
|
||||
|
||||
|
||||
**Skipping discuss-phase in autonomous mode:** When running in `yolo` mode with well-established preferences already captured in PROJECT.md, set `workflow.skip_discuss: true` via `/gsd-settings`. This bypasses the discuss-phase entirely and writes a minimal CONTEXT.md derived from the ROADMAP phase goal. Useful when your PROJECT.md and conventions are comprehensive enough that discussion adds no new information.
|
||||
|
||||
@@ -637,6 +706,7 @@ cd ~/gsd-workspaces/feature-b
|
||||
```
|
||||
|
||||
Each workspace gets:
|
||||
|
||||
- Its own `.planning/` directory (fully independent from source repos)
|
||||
- Git worktrees (default) or clones of specified repos
|
||||
- A `WORKSPACE.md` manifest tracking member repos
|
||||
@@ -647,9 +717,9 @@ Each workspace gets:
|
||||
|
||||
### Programmatic CLI (`gsd-sdk query` vs `gsd-tools.cjs`)
|
||||
|
||||
For automation and copy-paste from docs, prefer **`gsd-sdk query`** with a registered subcommand (see [CLI-TOOLS.md](CLI-TOOLS.md) and [QUERY-HANDLERS.md](../sdk/src/query/QUERY-HANDLERS.md)). The legacy **`node $HOME/.claude/get-shit-done/bin/gsd-tools.cjs`** CLI remains supported for dual-mode operation.
|
||||
For automation and copy-paste from docs, prefer **`gsd-sdk query`** with a registered subcommand (see [CLI-TOOLS.md — SDK and programmatic access](CLI-TOOLS.md#sdk-and-programmatic-access) and [QUERY-HANDLERS.md](../sdk/src/query/QUERY-HANDLERS.md)). The legacy `node $HOME/.claude/get-shit-done/bin/gsd-tools.cjs` CLI remains supported for dual-mode operation.
|
||||
|
||||
**Not yet on `gsd-sdk query` (use CJS):** `state validate`, `state sync`, `audit-open`, `graphify`, `from-gsd2`, and any subcommand not listed in the registry.
|
||||
**CLI-only (not in the query registry):** **graphify**, **from-gsd2** / **gsd2-import** — call `gsd-tools.cjs` (see [QUERY-HANDLERS.md](../sdk/src/query/QUERY-HANDLERS.md)). **Two different `state` JSON shapes in the legacy CLI:** `state json` (frontmatter rebuild) vs `state load` (`config` + `state_raw` + flags). **`gsd-sdk query` today:** both `state.json` and `state.load` resolve to the frontmatter-rebuild handler — use `node …/gsd-tools.cjs state load` when you need the CJS `state load` shape. See [CLI-TOOLS.md](CLI-TOOLS.md#sdk-and-programmatic-access) and QUERY-HANDLERS.
|
||||
|
||||
### STATE.md Out of Sync
|
||||
|
||||
@@ -725,6 +795,19 @@ To assign different models to different agents on a non-Claude runtime, add `mod
|
||||
|
||||
The installer auto-configures `resolve_model_ids: "omit"` for Gemini CLI, OpenCode, Kilo, and Codex. If you're manually setting up a non-Claude runtime, add it to `.planning/config.json` yourself.
|
||||
|
||||
#### Switching from Claude to Codex with one config change (#2517)
|
||||
|
||||
If you want tiered models on Codex without writing a large `model_overrides` block, set `runtime: "codex"` and pick a profile:
|
||||
|
||||
```json
|
||||
{
|
||||
"runtime": "codex",
|
||||
"model_profile": "balanced"
|
||||
}
|
||||
```
|
||||
|
||||
GSD will resolve each agent's tier (`opus`/`sonnet`/`haiku`) to the Codex-native model and reasoning effort defined in the runtime tier map (`gpt-5.4` xhigh / `gpt-5.3-codex` medium / `gpt-5.4-mini` medium). The Codex installer embeds both `model` and `model_reasoning_effort` into each agent's TOML automatically. To override a single tier, add `model_profile_overrides.codex.<tier>`. See [Runtime-Aware Profiles](CONFIGURATION.md#runtime-aware-profiles-2517).
|
||||
|
||||
See the [Configuration Reference](CONFIGURATION.md#non-claude-runtimes-codex-opencode-gemini-cli-kilo) for the full explanation.
|
||||
|
||||
### Installing for Cline
|
||||
@@ -782,6 +865,7 @@ If `npx get-shit-done-cc` fails due to npm outages or network restrictions, see
|
||||
When a workflow fails in a way that isn't obvious -- plans reference nonexistent files, execution produces unexpected results, or state seems corrupted -- run `/gsd-forensics` to generate a diagnostic report.
|
||||
|
||||
**What it checks:**
|
||||
|
||||
- Git history anomalies (orphaned commits, unexpected branch state, rebase artifacts)
|
||||
- Artifact integrity (missing or malformed planning files, broken cross-references)
|
||||
- State inconsistencies (ROADMAP status vs. actual file presence, config drift)
|
||||
@@ -916,22 +1000,24 @@ If the installer crashes with `EPERM: operation not permitted, scandir` on Windo
|
||||
|
||||
## Recovery Quick Reference
|
||||
|
||||
| Problem | Solution |
|
||||
|---------|----------|
|
||||
| Lost context / new session | `/gsd-resume-work` or `/gsd-progress` |
|
||||
| Phase went wrong | `git revert` the phase commits, then re-plan |
|
||||
| Need to change scope | `/gsd-add-phase`, `/gsd-insert-phase`, or `/gsd-remove-phase` |
|
||||
| Milestone audit found gaps | `/gsd-plan-milestone-gaps` |
|
||||
| Something broke | `/gsd-debug "description"` (add `--diagnose` for analysis without fixes) |
|
||||
| STATE.md out of sync | `state validate` then `state sync` |
|
||||
| Workflow state seems corrupted | `/gsd-forensics` |
|
||||
| Quick targeted fix | `/gsd-quick` |
|
||||
| Plan doesn't match your vision | `/gsd-discuss-phase [N]` then re-plan |
|
||||
| Costs running high | `/gsd-set-profile budget` and `/gsd-settings` to toggle agents off |
|
||||
| Update broke local changes | `/gsd-reapply-patches` |
|
||||
| Want session summary for stakeholder | `/gsd-session-report` |
|
||||
| Don't know what step is next | `/gsd-next` |
|
||||
| Parallel execution build errors | Update GSD or set `parallelization.enabled: false` |
|
||||
|
||||
| Problem | Solution |
|
||||
| ------------------------------------ | ------------------------------------------------------------------------ |
|
||||
| Lost context / new session | `/gsd-resume-work` or `/gsd-progress` |
|
||||
| Phase went wrong | `git revert` the phase commits, then re-plan |
|
||||
| Need to change scope | `/gsd-add-phase`, `/gsd-insert-phase`, or `/gsd-remove-phase` |
|
||||
| Milestone audit found gaps | `/gsd-plan-milestone-gaps` |
|
||||
| Something broke | `/gsd-debug "description"` (add `--diagnose` for analysis without fixes) |
|
||||
| STATE.md out of sync | `state validate` then `state sync` |
|
||||
| Workflow state seems corrupted | `/gsd-forensics` |
|
||||
| Quick targeted fix | `/gsd-quick` |
|
||||
| Plan doesn't match your vision | `/gsd-discuss-phase [N]` then re-plan |
|
||||
| Costs running high | `/gsd-set-profile budget` and `/gsd-settings` to toggle agents off |
|
||||
| Update broke local changes | `/gsd-reapply-patches` |
|
||||
| Want session summary for stakeholder | `/gsd-session-report` |
|
||||
| Don't know what step is next | `/gsd-next` |
|
||||
| Parallel execution build errors | Update GSD or set `parallelization.enabled: false` |
|
||||
|
||||
|
||||
---
|
||||
|
||||
@@ -975,3 +1061,4 @@ For reference, here is what GSD creates in your project:
|
||||
XX-UI-REVIEW.md # Visual audit scores (from /gsd-ui-review)
|
||||
ui-reviews/ # Screenshots from /gsd-ui-review (gitignored)
|
||||
```
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ Copy-paste friendly for Discord and GitHub comments.
|
||||
|
||||
---
|
||||
|
||||
**@gsd-build/sdk** replaces the untyped, monolithic `gsd-tools.cjs` subprocess with a typed, tested, registry-based query system and **`gsd-sdk query`**, giving GSD structured results, classified errors (`GSDQueryError`), and golden-verified parity with the old CLI. That gives the framework one stable contract instead of a fragile, very large CLI that every workflow had to spawn and parse by hand.
|
||||
**@gsd-build/sdk** replaces the untyped, monolithic `gsd-tools.cjs` subprocess with a typed, tested, registry-based query system and **`gsd-sdk query`**, giving GSD structured results, classified errors (`GSDError` with `ErrorClassification`), and golden-verified parity with the old CLI. That gives the framework one stable contract instead of a fragile, very large CLI that every workflow had to spawn and parse by hand.
|
||||
|
||||
**What users can expect**
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ Get Shit Done(GSD)フレームワークの包括的なドキュメントで
|
||||
| [機能リファレンス](FEATURES.md) | 全ユーザー | 全機能の詳細ドキュメントと要件 |
|
||||
| [コマンドリファレンス](COMMANDS.md) | 全ユーザー | 全コマンドの構文、フラグ、オプション、使用例 |
|
||||
| [設定リファレンス](CONFIGURATION.md) | 全ユーザー | 設定スキーマ、ワークフロートグル、モデルプロファイル、Git ブランチ |
|
||||
| [CLI ツールリファレンス](CLI-TOOLS.md) | コントリビューター、エージェント作成者 | `gsd-tools.cjs` のプログラマティック API(ワークフローおよびエージェント向け) |
|
||||
| [CLI ツールリファレンス](CLI-TOOLS.md) | コントリビューター、エージェント作成者 | CJS `gsd-tools.cjs` と **`gsd-sdk query` / SDK** のガイド |
|
||||
| [エージェントリファレンス](AGENTS.md) | コントリビューター、上級ユーザー | 全18種の専門エージェント — 役割、ツール、スポーンパターン |
|
||||
| [ユーザーガイド](USER-GUIDE.md) | 全ユーザー | ワークフローのウォークスルー、トラブルシューティング、リカバリー |
|
||||
| [コンテキストモニター](context-monitor.md) | 全ユーザー | コンテキストウィンドウ監視フックのアーキテクチャ |
|
||||
|
||||
@@ -12,7 +12,7 @@ Get Shit Done (GSD) 프레임워크의 종합 문서입니다. GSD는 AI 코딩
|
||||
| [Feature Reference](FEATURES.md) | 전체 사용자 | 요구사항이 포함된 전체 기능 및 함수 문서 |
|
||||
| [Command Reference](COMMANDS.md) | 전체 사용자 | 모든 명령어의 구문, 플래그, 옵션 및 예제 |
|
||||
| [Configuration Reference](CONFIGURATION.md) | 전체 사용자 | 전체 설정 스키마, 워크플로우 토글, 모델 프로필, git 브랜칭 |
|
||||
| [CLI Tools Reference](CLI-TOOLS.md) | 기여자, 에이전트 작성자 | 워크플로우 및 에이전트를 위한 `gsd-tools.cjs` 프로그래매틱 API |
|
||||
| [CLI Tools Reference](CLI-TOOLS.md) | 기여자, 에이전트 작성자 | CJS `gsd-tools.cjs` + **`gsd-sdk query`/SDK** 안내 |
|
||||
| [Agent Reference](AGENTS.md) | 기여자, 고급 사용자 | 18개 전문 에이전트의 역할, 도구, 스폰 패턴 |
|
||||
| [User Guide](USER-GUIDE.md) | 전체 사용자 | 워크플로우 안내, 문제 해결, 복구 방법 |
|
||||
| [Context Monitor](context-monitor.md) | 전체 사용자 | 컨텍스트 윈도우 모니터링 훅 아키텍처 |
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Referência de Ferramentas CLI
|
||||
|
||||
Resumo em Português das ferramentas CLI do GSD.
|
||||
Para API completa (assinaturas, argumentos e comportamento detalhado), consulte [CLI-TOOLS.md em inglês](../CLI-TOOLS.md).
|
||||
Para API completa (assinaturas, argumentos e comportamento detalhado), consulte [CLI-TOOLS.md em inglês](../CLI-TOOLS.md) — inclui a secção **SDK and programmatic access** (`gsd-sdk query`, `@gsd-build/sdk`).
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ Documentação abrangente do framework Get Shit Done (GSD) — um sistema de met
|
||||
| [Referência de configuração](CONFIGURATION.md) | Todos os usuários | Schema completo de configuração, toggles e perfis |
|
||||
| [Referência de recursos](FEATURES.md) | Todos os usuários | Recursos e requisitos detalhados |
|
||||
| [Referência de agentes](AGENTS.md) | Contribuidores, usuários avançados | Agentes especializados, papéis e padrões de orquestração |
|
||||
| [Ferramentas CLI](CLI-TOOLS.md) | Contribuidores, autores de agentes | API programática `gsd-tools.cjs` |
|
||||
| [Ferramentas CLI](CLI-TOOLS.md) | Contribuidores, autores de agentes | Superfície CJS `gsd-tools.cjs` + guia **`gsd-sdk query`/SDK** |
|
||||
| [Monitor de contexto](context-monitor.md) | Todos os usuários | Arquitetura de monitoramento da janela de contexto |
|
||||
| [Discuss Mode](workflow-discuss-mode.md) | Todos os usuários | Modo suposições vs entrevista no `discuss-phase` |
|
||||
| [Referências](references/) | Todos os usuários | Guias complementares de decisão, verificação e padrões |
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
|
||||
为紧急插入计算下一个小数阶段编号。
|
||||
|
||||
## 使用 gsd-tools
|
||||
## 使用 gsd-sdk query
|
||||
|
||||
```bash
|
||||
# 获取阶段 6 之后的下一个小数阶段
|
||||
node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" phase next-decimal 6
|
||||
gsd-sdk query phase.next-decimal 6
|
||||
```
|
||||
|
||||
输出:
|
||||
@@ -32,14 +32,13 @@ node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" phase next-decimal 6
|
||||
## 提取值
|
||||
|
||||
```bash
|
||||
DECIMAL_INFO=$(node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" phase next-decimal "${AFTER_PHASE}")
|
||||
DECIMAL_PHASE=$(printf '%s\n' "$DECIMAL_INFO" | jq -r '.next')
|
||||
BASE_PHASE=$(printf '%s\n' "$DECIMAL_INFO" | jq -r '.base_phase')
|
||||
DECIMAL_PHASE=$(gsd-sdk query phase.next-decimal "${AFTER_PHASE}" --pick next)
|
||||
BASE_PHASE=$(gsd-sdk query phase.next-decimal "${AFTER_PHASE}" --pick base_phase)
|
||||
```
|
||||
|
||||
或使用 --raw 标志:
|
||||
```bash
|
||||
DECIMAL_PHASE=$(node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" phase next-decimal "${AFTER_PHASE}" --raw)
|
||||
DECIMAL_PHASE=$(gsd-sdk query phase.next-decimal "${AFTER_PHASE}" --raw)
|
||||
# 返回: 06.1
|
||||
```
|
||||
|
||||
@@ -57,9 +56,9 @@ DECIMAL_PHASE=$(node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" phase next-
|
||||
小数阶段目录使用完整的小数编号:
|
||||
|
||||
```bash
|
||||
SLUG=$(node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" generate-slug "$DESCRIPTION" --raw)
|
||||
SLUG=$(gsd-sdk query generate-slug "$DESCRIPTION" --raw)
|
||||
PHASE_DIR=".planning/phases/${DECIMAL_PHASE}-${SLUG}"
|
||||
mkdir -p "$PHASE_DIR"
|
||||
```
|
||||
|
||||
示例:`.planning/phases/06.1-fix-critical-auth-bug/`
|
||||
示例:`.planning/phases/06.1-fix-critical-auth-bug/`
|
||||
|
||||
@@ -51,7 +51,7 @@ Phases:
|
||||
提交内容:
|
||||
|
||||
```bash
|
||||
node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" commit "docs: initialize [project-name] ([N] phases)" --files .planning/
|
||||
gsd-sdk query commit "docs: initialize [project-name] ([N] phases)" .planning/
|
||||
```
|
||||
|
||||
</format>
|
||||
@@ -129,7 +129,7 @@ SUMMARY: .planning/phases/XX-name/{phase}-{plan}-SUMMARY.md
|
||||
提交内容:
|
||||
|
||||
```bash
|
||||
node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" commit "docs({phase}-{plan}): complete [plan-name] plan" --files .planning/phases/XX-name/{phase}-{plan}-PLAN.md .planning/phases/XX-name/{phase}-{plan}-SUMMARY.md .planning/STATE.md .planning/ROADMAP.md
|
||||
gsd-sdk query commit "docs({phase}-{plan}): complete [plan-name] plan" .planning/phases/XX-name/{phase}-{plan}-PLAN.md .planning/phases/XX-name/{phase}-{plan}-SUMMARY.md .planning/STATE.md .planning/ROADMAP.md
|
||||
```
|
||||
|
||||
**注意:** 代码文件不包含 - 已按任务提交。
|
||||
@@ -149,7 +149,7 @@ Current: [task name]
|
||||
提交内容:
|
||||
|
||||
```bash
|
||||
node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" commit "wip: [phase-name] paused at task [X]/[Y]" --files .planning/
|
||||
gsd-sdk query commit "wip: [phase-name] paused at task [X]/[Y]" .planning/
|
||||
```
|
||||
|
||||
</format>
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
# Git 规划提交
|
||||
|
||||
使用 gsd-tools CLI 提交规划工件,它会自动检查 `commit_docs` 配置和 gitignore 状态。
|
||||
通过 `gsd-sdk query commit` 提交规划工件,它会自动检查 `commit_docs` 配置和 gitignore 状态(与旧版 `gsd-tools.cjs commit` 行为相同)。
|
||||
|
||||
## 通过 CLI 提交
|
||||
|
||||
始终使用 `gsd-tools.cjs commit` 处理 `.planning/` 文件 — 它会自动处理 `commit_docs` 和 gitignore 检查:
|
||||
先传提交说明,再传文件路径(位置参数)。`commit` 不要使用 `--files`(该标志仅用于 `commit-to-subrepo`)。
|
||||
|
||||
对 `.planning/` 文件始终使用此方式 —— 它会自动处理 `commit_docs` 与 gitignore 检查:
|
||||
|
||||
```bash
|
||||
node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" commit "docs({scope}): {description}" --files .planning/STATE.md .planning/ROADMAP.md
|
||||
gsd-sdk query commit "docs({scope}): {description}" .planning/STATE.md .planning/ROADMAP.md
|
||||
```
|
||||
|
||||
如果 `commit_docs` 为 `false` 或 `.planning/` 被 gitignore,CLI 会返回 `skipped`(带原因)。无需手动条件检查。
|
||||
@@ -17,7 +19,7 @@ node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" commit "docs({scope}): {des
|
||||
将 `.planning/` 文件变更合并到上次提交:
|
||||
|
||||
```bash
|
||||
node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" commit "" --files .planning/codebase/*.md --amend
|
||||
gsd-sdk query commit "" .planning/codebase/*.md --amend
|
||||
```
|
||||
|
||||
## 提交消息模式
|
||||
@@ -35,4 +37,4 @@ node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" commit "" --files .planning
|
||||
|
||||
- config 中 `commit_docs: false`
|
||||
- `.planning/` 被 gitignore
|
||||
- 无变更可提交(用 `git status --porcelain .planning/` 检查)
|
||||
- 无变更可提交(用 `git status --porcelain .planning/` 检查)
|
||||
|
||||
@@ -36,19 +36,19 @@
|
||||
- 用户必须将 `.planning/` 添加到 `.gitignore`
|
||||
- 适用于:OSS 贡献、客户项目、保持规划私有
|
||||
|
||||
**使用 gsd-tools.cjs(推荐):**
|
||||
**使用 `gsd-sdk query`(推荐):**
|
||||
|
||||
```bash
|
||||
# 提交时自动检查 commit_docs + gitignore:
|
||||
node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" commit "docs: update state" --files .planning/STATE.md
|
||||
gsd-sdk query commit "docs: update state" .planning/STATE.md
|
||||
|
||||
# 通过 state load 加载配置(返回 JSON):
|
||||
INIT=$(node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" state load)
|
||||
INIT=$(gsd-sdk query state.load)
|
||||
if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
|
||||
# commit_docs 在 JSON 输出中可用
|
||||
|
||||
# 或使用包含 commit_docs 的 init 命令:
|
||||
INIT=$(node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" init execute-phase "1")
|
||||
INIT=$(gsd-sdk query init.execute-phase "1")
|
||||
if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
|
||||
# commit_docs 包含在所有 init 命令输出中
|
||||
```
|
||||
@@ -58,7 +58,7 @@ if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
|
||||
**通过 CLI 提交(自动处理检查):**
|
||||
|
||||
```bash
|
||||
node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" commit "docs: update state" --files .planning/STATE.md
|
||||
gsd-sdk query commit "docs: update state" .planning/STATE.md
|
||||
```
|
||||
|
||||
CLI 在内部检查 `commit_docs` 配置和 gitignore 状态 —— 无需手动条件判断。
|
||||
@@ -146,14 +146,14 @@ CLI 在内部检查 `commit_docs` 配置和 gitignore 状态 —— 无需手动
|
||||
|
||||
使用 `init execute-phase` 返回所有配置为 JSON:
|
||||
```bash
|
||||
INIT=$(node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" init execute-phase "1")
|
||||
INIT=$(gsd-sdk query init.execute-phase "1")
|
||||
if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
|
||||
# JSON 输出包含:branching_strategy, phase_branch_template, milestone_branch_template
|
||||
```
|
||||
|
||||
或使用 `state load` 获取配置值:
|
||||
```bash
|
||||
INIT=$(node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" state load)
|
||||
INIT=$(gsd-sdk query state.load)
|
||||
if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
|
||||
# 从 JSON 解析 branching_strategy, phase_branch_template, milestone_branch_template
|
||||
```
|
||||
|
||||
@@ -49,6 +49,7 @@
|
||||
* roadmap get-phase <phase> Extract phase section from ROADMAP.md
|
||||
* roadmap analyze Full roadmap parse with disk status
|
||||
* roadmap update-plan-progress <N> Update progress table row from disk (PLAN vs SUMMARY counts)
|
||||
* roadmap annotate-dependencies <N> Add wave dependency notes + cross-cutting constraints to ROADMAP.md
|
||||
*
|
||||
* Requirements Operations:
|
||||
* requirements mark-complete <ids> Mark requirement IDs as complete in REQUIREMENTS.md
|
||||
@@ -111,6 +112,7 @@
|
||||
* verify artifacts <plan-file> Check must_haves.artifacts
|
||||
* verify key-links <plan-file> Check must_haves.key_links
|
||||
* verify schema-drift <phase> [--skip] Detect schema file changes without push
|
||||
* verify codebase-drift Detect structural drift since last codebase map (#2003)
|
||||
*
|
||||
* Template Fill:
|
||||
* template fill summary --phase N Create pre-filled SUMMARY.md
|
||||
@@ -186,6 +188,7 @@ const profileOutput = require('./lib/profile-output.cjs');
|
||||
const workstream = require('./lib/workstream.cjs');
|
||||
const docs = require('./lib/docs.cjs');
|
||||
const learnings = require('./lib/learnings.cjs');
|
||||
const gapChecker = require('./lib/gap-checker.cjs');
|
||||
|
||||
// ─── Arg parsing helpers ──────────────────────────────────────────────────────
|
||||
|
||||
@@ -592,8 +595,10 @@ async function runCommand(command, args, cwd, raw, defaultValue) {
|
||||
} else if (subcommand === 'schema-drift') {
|
||||
const skipFlag = args.includes('--skip');
|
||||
verify.cmdVerifySchemaDrift(cwd, args[2], skipFlag, raw);
|
||||
} else if (subcommand === 'codebase-drift') {
|
||||
verify.cmdVerifyCodebaseDrift(cwd, raw);
|
||||
} else {
|
||||
error('Unknown verify subcommand. Available: plan-structure, phase-completeness, references, commits, artifacts, key-links, schema-drift');
|
||||
error('Unknown verify subcommand. Available: plan-structure, phase-completeness, references, commits, artifacts, key-links, schema-drift, codebase-drift');
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -690,8 +695,10 @@ async function runCommand(command, args, cwd, raw, defaultValue) {
|
||||
roadmap.cmdRoadmapAnalyze(cwd, raw);
|
||||
} else if (subcommand === 'update-plan-progress') {
|
||||
roadmap.cmdRoadmapUpdatePlanProgress(cwd, args[2], raw);
|
||||
} else if (subcommand === 'annotate-dependencies') {
|
||||
roadmap.cmdRoadmapAnnotateDependencies(cwd, args[2], raw);
|
||||
} else {
|
||||
error('Unknown roadmap subcommand. Available: get-phase, analyze, update-plan-progress');
|
||||
error('Unknown roadmap subcommand. Available: get-phase, analyze, update-plan-progress, annotate-dependencies');
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -706,6 +713,13 @@ async function runCommand(command, args, cwd, raw, defaultValue) {
|
||||
break;
|
||||
}
|
||||
|
||||
case 'gap-analysis': {
|
||||
// Post-planning gap checker (#2493) — unified REQUIREMENTS.md +
|
||||
// CONTEXT.md <decisions> coverage report against PLAN.md files.
|
||||
gapChecker.cmdGapAnalysis(cwd, args.slice(1), raw);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'phase': {
|
||||
const subcommand = args[1];
|
||||
if (subcommand === 'next-decimal') {
|
||||
@@ -764,7 +778,8 @@ async function runCommand(command, args, cwd, raw, defaultValue) {
|
||||
verify.cmdValidateConsistency(cwd, raw);
|
||||
} else if (subcommand === 'health') {
|
||||
const repairFlag = args.includes('--repair');
|
||||
verify.cmdValidateHealth(cwd, { repair: repairFlag }, raw);
|
||||
const backfillFlag = args.includes('--backfill');
|
||||
verify.cmdValidateHealth(cwd, { repair: repairFlag, backfill: backfillFlag }, raw);
|
||||
} else if (subcommand === 'agents') {
|
||||
verify.cmdValidateAgents(cwd, raw);
|
||||
} else {
|
||||
@@ -1200,10 +1215,6 @@ async function runCommand(command, args, cwd, raw, defaultValue) {
|
||||
'agents',
|
||||
path.join('commands', 'gsd'),
|
||||
'hooks',
|
||||
// OpenCode/Kilo flat command dir
|
||||
'command',
|
||||
// Codex/Copilot skills dir
|
||||
'skills',
|
||||
];
|
||||
|
||||
function walkDir(dir, baseDir) {
|
||||
|
||||
52
get-shit-done/bin/lib/artifacts.cjs
Normal file
52
get-shit-done/bin/lib/artifacts.cjs
Normal file
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* Canonical GSD artifact registry.
|
||||
*
|
||||
* Enumerates the file names that gsd workflows officially produce at the
|
||||
* .planning/ root level. Used by gsd-health (W019) to flag unrecognized files
|
||||
* so stale or misnamed artifacts don't silently mislead agents or reviewers.
|
||||
*
|
||||
* Add entries here whenever a new workflow produces a .planning/ root file.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
// Exact-match canonical file names at .planning/ root
|
||||
const CANONICAL_EXACT = new Set([
|
||||
'PROJECT.md',
|
||||
'ROADMAP.md',
|
||||
'STATE.md',
|
||||
'REQUIREMENTS.md',
|
||||
'MILESTONES.md',
|
||||
'BACKLOG.md',
|
||||
'LEARNINGS.md',
|
||||
'THREADS.md',
|
||||
'config.json',
|
||||
'CLAUDE.md',
|
||||
]);
|
||||
|
||||
// Pattern-match canonical file names (regex tests on the basename)
|
||||
// Each pattern includes the name of the workflow that produces it as a comment.
|
||||
const CANONICAL_PATTERNS = [
|
||||
/^v\d+\.\d+(?:\.\d+)?-MILESTONE-AUDIT\.md$/i, // gsd-complete-milestone (pre-archive)
|
||||
/^v\d+\.\d+(?:\.\d+)?-.*\.md$/i, // other version-stamped planning docs
|
||||
];
|
||||
|
||||
/**
|
||||
* Return true if `filename` (basename only, no path) matches a canonical
|
||||
* .planning/ root artifact — either an exact name or a known pattern.
|
||||
*
|
||||
* @param {string} filename - Basename of the file (e.g. "STATE.md")
|
||||
*/
|
||||
function isCanonicalPlanningFile(filename) {
|
||||
if (CANONICAL_EXACT.has(filename)) return true;
|
||||
for (const pattern of CANONICAL_PATTERNS) {
|
||||
if (pattern.test(filename)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
CANONICAL_EXACT,
|
||||
CANONICAL_PATTERNS,
|
||||
isCanonicalPlanningFile,
|
||||
};
|
||||
@@ -4,7 +4,7 @@
|
||||
* Scans all .planning/ artifact categories for items with open/unresolved state.
|
||||
* Returns structured JSON for workflow consumption.
|
||||
* Called by: gsd-tools.cjs audit-open
|
||||
* Used by: /gsd-complete-milestone pre-close gate
|
||||
* Used by: /gsd:complete-milestone pre-close gate
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
@@ -773,7 +773,7 @@ function cmdScaffold(cwd, type, options, raw) {
|
||||
switch (type) {
|
||||
case 'context': {
|
||||
filePath = path.join(phaseDir, `${padded}-CONTEXT.md`);
|
||||
content = `---\nphase: "${padded}"\nname: "${name || phaseInfo?.phase_name || 'Unnamed'}"\ncreated: ${today}\n---\n\n# Phase ${phase}: ${name || phaseInfo?.phase_name || 'Unnamed'} — Context\n\n## Decisions\n\n_Decisions will be captured during /gsd-discuss-phase ${phase}_\n\n## Discretion Areas\n\n_Areas where the executor can use judgment_\n\n## Deferred Ideas\n\n_Ideas to consider later_\n`;
|
||||
content = `---\nphase: "${padded}"\nname: "${name || phaseInfo?.phase_name || 'Unnamed'}"\ncreated: ${today}\n---\n\n# Phase ${phase}: ${name || phaseInfo?.phase_name || 'Unnamed'} — Context\n\n## Decisions\n\n_Decisions will be captured during /gsd:discuss-phase ${phase}_\n\n## Discretion Areas\n\n_Areas where the executor can use judgment_\n\n## Deferred Ideas\n\n_Ideas to consider later_\n`;
|
||||
break;
|
||||
}
|
||||
case 'uat': {
|
||||
|
||||
@@ -25,7 +25,6 @@ const VALID_CONFIG_KEYS = new Set([
|
||||
'workflow.discuss_mode',
|
||||
'workflow.skip_discuss',
|
||||
'workflow.auto_prune_state',
|
||||
'workflow._auto_chain_active',
|
||||
'workflow.use_worktrees',
|
||||
'workflow.code_review',
|
||||
'workflow.code_review_depth',
|
||||
@@ -34,15 +33,23 @@ const VALID_CONFIG_KEYS = new Set([
|
||||
'workflow.plan_bounce',
|
||||
'workflow.plan_bounce_script',
|
||||
'workflow.plan_bounce_passes',
|
||||
'workflow.plan_chunked',
|
||||
'workflow.post_planning_gaps',
|
||||
'workflow.security_enforcement',
|
||||
'workflow.security_asvs_level',
|
||||
'workflow.security_block_on',
|
||||
'workflow.drift_threshold',
|
||||
'workflow.drift_action',
|
||||
'git.branching_strategy', 'git.base_branch', 'git.phase_branch_template', 'git.milestone_branch_template', 'git.quick_branch_template',
|
||||
'planning.commit_docs', 'planning.search_gitignored', 'planning.sub_repos',
|
||||
'workflow.cross_ai_execution', 'workflow.cross_ai_command', 'workflow.cross_ai_timeout',
|
||||
'workflow.subagent_timeout',
|
||||
'workflow.inline_plan_threshold',
|
||||
'hooks.context_warnings',
|
||||
'hooks.workflow_guard',
|
||||
'statusline.show_last_command',
|
||||
'workflow.ui_review',
|
||||
'workflow.max_discuss_passes',
|
||||
'features.thinking_partner',
|
||||
'context',
|
||||
'features.global_learnings',
|
||||
@@ -50,10 +57,14 @@ const VALID_CONFIG_KEYS = new Set([
|
||||
'project_code', 'phase_naming',
|
||||
'manager.flags.discuss', 'manager.flags.plan', 'manager.flags.execute',
|
||||
'response_language',
|
||||
'context_window',
|
||||
'intel.enabled',
|
||||
'graphify.enabled',
|
||||
'graphify.build_timeout',
|
||||
'claude_md_path',
|
||||
'claude_md_assembly.mode',
|
||||
// #2517 — runtime-aware model profiles
|
||||
'runtime',
|
||||
]);
|
||||
|
||||
/**
|
||||
@@ -61,9 +72,14 @@ const VALID_CONFIG_KEYS = new Set([
|
||||
* Each entry has a `test` function and a human-readable `description`.
|
||||
*/
|
||||
const DYNAMIC_KEY_PATTERNS = [
|
||||
{ test: (k) => /^agent_skills\.[a-zA-Z0-9_-]+$/.test(k), description: 'agent_skills.<agent-type>' },
|
||||
{ test: (k) => /^review\.models\.[a-zA-Z0-9_-]+$/.test(k), description: 'review.models.<cli-name>' },
|
||||
{ test: (k) => /^features\.[a-zA-Z0-9_]+$/.test(k), description: 'features.<feature_name>' },
|
||||
{ test: (k) => /^agent_skills\.[a-zA-Z0-9_-]+$/.test(k), description: 'agent_skills.<agent-type>' },
|
||||
{ test: (k) => /^review\.models\.[a-zA-Z0-9_-]+$/.test(k), description: 'review.models.<cli-name>' },
|
||||
{ test: (k) => /^features\.[a-zA-Z0-9_]+$/.test(k), description: 'features.<feature_name>' },
|
||||
{ test: (k) => /^claude_md_assembly\.blocks\.[a-zA-Z0-9_]+$/.test(k), description: 'claude_md_assembly.blocks.<section>' },
|
||||
// #2517 — runtime-aware model profile overrides: model_profile_overrides.<runtime>.<tier>
|
||||
// <runtime> is a free string (so users can map non-built-in runtimes); <tier> is enum-restricted.
|
||||
{ test: (k) => /^model_profile_overrides\.[a-zA-Z0-9_-]+\.(opus|sonnet|haiku)$/.test(k),
|
||||
description: 'model_profile_overrides.<runtime>.<opus|sonnet|haiku>' },
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||
@@ -11,6 +11,7 @@ const {
|
||||
formatAgentToModelMapAsTable,
|
||||
} = require('./model-profiles.cjs');
|
||||
const { VALID_CONFIG_KEYS, isValidConfigKey } = require('./config-schema.cjs');
|
||||
const { isSecretKey, maskSecret } = require('./secrets.cjs');
|
||||
|
||||
const CONFIG_KEY_SUGGESTIONS = {
|
||||
'workflow.nyquist_validation_enabled': 'workflow.nyquist_validation',
|
||||
@@ -24,6 +25,8 @@ const CONFIG_KEY_SUGGESTIONS = {
|
||||
'workflow.code_review_level': 'workflow.code_review_depth',
|
||||
'workflow.review_depth': 'workflow.code_review_depth',
|
||||
'review.model': 'review.models.<cli-name>',
|
||||
'sub_repos': 'planning.sub_repos',
|
||||
'plan_checker': 'workflow.plan_check',
|
||||
};
|
||||
|
||||
function validateKnownConfigKeyPath(keyPath) {
|
||||
@@ -39,7 +42,7 @@ function validateKnownConfigKeyPath(keyPath) {
|
||||
* Merges (increasing priority):
|
||||
* 1. Hardcoded defaults — every key that loadConfig() resolves, plus mode/granularity
|
||||
* 2. User-level defaults from ~/.gsd/defaults.json (if present)
|
||||
* 3. userChoices — the settings the user explicitly selected during /gsd-new-project
|
||||
* 3. userChoices — the settings the user explicitly selected during /gsd:new-project
|
||||
*
|
||||
* Uses the canonical `git` namespace for branching keys (consistent with VALID_CONFIG_KEYS
|
||||
* and the settings workflow). loadConfig() handles both flat and nested formats, so this
|
||||
@@ -117,6 +120,7 @@ function buildNewProjectConfig(userChoices) {
|
||||
plan_bounce_script: null,
|
||||
plan_bounce_passes: 2,
|
||||
auto_prune_state: false,
|
||||
post_planning_gaps: CONFIG_DEFAULTS.post_planning_gaps,
|
||||
security_enforcement: CONFIG_DEFAULTS.security_enforcement,
|
||||
security_asvs_level: CONFIG_DEFAULTS.security_asvs_level,
|
||||
security_block_on: CONFIG_DEFAULTS.security_block_on,
|
||||
@@ -162,7 +166,7 @@ function buildNewProjectConfig(userChoices) {
|
||||
* Command: create a fully-materialized .planning/config.json for a new project.
|
||||
*
|
||||
* Accepts user-chosen settings as a JSON string (the keys the user explicitly
|
||||
* configured during /gsd-new-project). All remaining keys are filled from
|
||||
* configured during /gsd:new-project). All remaining keys are filled from
|
||||
* hardcoded defaults and optional ~/.gsd/defaults.json.
|
||||
*
|
||||
* Idempotent: if config.json already exists, returns { created: false }.
|
||||
@@ -331,7 +335,44 @@ function cmdConfigSet(cwd, keyPath, value, raw) {
|
||||
error(`Invalid context value '${value}'. Valid values: ${VALID_CONTEXT_VALUES.join(', ')}`);
|
||||
}
|
||||
|
||||
// Codebase drift detector (#2003)
|
||||
const VALID_DRIFT_ACTIONS = ['warn', 'auto-remap'];
|
||||
if (keyPath === 'workflow.drift_action' && !VALID_DRIFT_ACTIONS.includes(String(parsedValue))) {
|
||||
error(`Invalid workflow.drift_action '${value}'. Valid values: ${VALID_DRIFT_ACTIONS.join(', ')}`);
|
||||
}
|
||||
if (keyPath === 'workflow.drift_threshold') {
|
||||
if (typeof parsedValue !== 'number' || !Number.isInteger(parsedValue) || parsedValue < 1) {
|
||||
error(`Invalid workflow.drift_threshold '${value}'. Must be a positive integer.`);
|
||||
}
|
||||
}
|
||||
|
||||
// Post-planning gap checker (#2493)
|
||||
if (keyPath === 'workflow.post_planning_gaps') {
|
||||
if (typeof parsedValue !== 'boolean') {
|
||||
error(`Invalid workflow.post_planning_gaps '${value}'. Must be a boolean (true or false).`);
|
||||
}
|
||||
}
|
||||
|
||||
const setConfigValueResult = setConfigValue(cwd, keyPath, parsedValue);
|
||||
|
||||
// Mask secrets in both JSON and text output. The plaintext is written
|
||||
// to config.json (that's where secrets live on disk); the CLI output
|
||||
// must never echo it. See lib/secrets.cjs.
|
||||
if (isSecretKey(keyPath)) {
|
||||
const masked = maskSecret(parsedValue);
|
||||
const maskedPrev = setConfigValueResult.previousValue === undefined
|
||||
? undefined
|
||||
: maskSecret(setConfigValueResult.previousValue);
|
||||
const maskedResult = {
|
||||
...setConfigValueResult,
|
||||
value: masked,
|
||||
previousValue: maskedPrev,
|
||||
masked: true,
|
||||
};
|
||||
output(maskedResult, raw, `${keyPath}=${masked}`);
|
||||
return;
|
||||
}
|
||||
|
||||
output(setConfigValueResult, raw, `${keyPath}=${parsedValue}`);
|
||||
}
|
||||
|
||||
@@ -374,6 +415,14 @@ function cmdConfigGet(cwd, keyPath, raw, defaultValue) {
|
||||
error(`Key not found: ${keyPath}`);
|
||||
}
|
||||
|
||||
// Never echo plaintext for sensitive keys via config-get. Plaintext lives
|
||||
// in config.json on disk; the CLI surface always shows the masked form.
|
||||
if (isSecretKey(keyPath)) {
|
||||
const masked = maskSecret(current);
|
||||
output(masked, raw, masked);
|
||||
return;
|
||||
}
|
||||
|
||||
output(current, raw, String(current));
|
||||
}
|
||||
|
||||
|
||||
@@ -263,9 +263,10 @@ const CONFIG_DEFAULTS = {
|
||||
phase_naming: 'sequential', // 'sequential' (default, auto-increment) or 'custom' (arbitrary string IDs)
|
||||
project_code: null, // optional short prefix for phase dirs (e.g., 'CK' → 'CK-01-foundation')
|
||||
subagent_timeout: 300000, // 5 min default; increase for large codebases or slower models (ms)
|
||||
security_enforcement: true, // workflow.security_enforcement — threat-model-anchored security verification via /gsd-secure-phase
|
||||
security_enforcement: true, // workflow.security_enforcement — threat-model-anchored security verification via /gsd:secure-phase
|
||||
security_asvs_level: 1, // workflow.security_asvs_level — OWASP ASVS verification level (1=opportunistic, 2=standard, 3=comprehensive)
|
||||
security_block_on: 'high', // workflow.security_block_on — minimum severity that blocks phase advancement ('high' | 'medium' | 'low')
|
||||
post_planning_gaps: true, // workflow.post_planning_gaps — unified post-planning gap report (#2493): scan REQUIREMENTS.md + CONTEXT.md decisions vs all PLAN.md files
|
||||
};
|
||||
|
||||
function loadConfig(cwd) {
|
||||
@@ -339,6 +340,13 @@ function loadConfig(cwd) {
|
||||
);
|
||||
}
|
||||
|
||||
// #2517 — Validate runtime/tier values for keys that loadConfig handles but
|
||||
// can be edited directly into config.json (bypassing config-set's enum check).
|
||||
// This catches typos like `runtime: "codx"` and `model_profile_overrides.codex.banana`
|
||||
// at read time without rejecting back-compat values from new runtimes
|
||||
// (review findings #10, #13).
|
||||
_warnUnknownProfileOverrides(parsed, '.planning/config.json');
|
||||
|
||||
const get = (key, nested) => {
|
||||
if (parsed[key] !== undefined) return parsed[key];
|
||||
if (nested && parsed[nested.section] && parsed[nested.section][nested.field] !== undefined) {
|
||||
@@ -374,6 +382,7 @@ function loadConfig(cwd) {
|
||||
plan_checker: get('plan_checker', { section: 'workflow', field: 'plan_check' }) ?? defaults.plan_checker,
|
||||
verifier: get('verifier', { section: 'workflow', field: 'verifier' }) ?? defaults.verifier,
|
||||
nyquist_validation: get('nyquist_validation', { section: 'workflow', field: 'nyquist_validation' }) ?? defaults.nyquist_validation,
|
||||
post_planning_gaps: get('post_planning_gaps', { section: 'workflow', field: 'post_planning_gaps' }) ?? defaults.post_planning_gaps,
|
||||
parallelization,
|
||||
brave_search: get('brave_search') ?? defaults.brave_search,
|
||||
firecrawl: get('firecrawl') ?? defaults.firecrawl,
|
||||
@@ -390,10 +399,23 @@ function loadConfig(cwd) {
|
||||
project_code: get('project_code') ?? defaults.project_code,
|
||||
subagent_timeout: get('subagent_timeout', { section: 'workflow', field: 'subagent_timeout' }) ?? defaults.subagent_timeout,
|
||||
model_overrides: parsed.model_overrides || null,
|
||||
// #2517 — runtime-aware profiles. `runtime` defaults to null (back-compat).
|
||||
// When null, resolveModelInternal preserves today's Claude-native behavior.
|
||||
// NOTE: `runtime` and `model_profile_overrides` are intentionally read
|
||||
// flat-only (not via `get()` with a workflow.X fallback) — they are
|
||||
// top-level keys per docs/CONFIGURATION.md. The lighter-touch decision
|
||||
// here was to document the constraint rather than introduce nested
|
||||
// resolution edge cases for two new keys (review finding #9). The
|
||||
// schema validation in `_warnUnknownProfileOverrides` runs against the
|
||||
// raw `parsed` blob, so direct `.planning/config.json` edits surface
|
||||
// unknown runtime/tier names at load time, not silently (review finding #10).
|
||||
runtime: parsed.runtime || null,
|
||||
model_profile_overrides: parsed.model_profile_overrides || null,
|
||||
agent_skills: parsed.agent_skills || {},
|
||||
manager: parsed.manager || {},
|
||||
response_language: get('response_language') || null,
|
||||
claude_md_path: get('claude_md_path') || null,
|
||||
claude_md_assembly: parsed.claude_md_assembly || null,
|
||||
};
|
||||
} catch {
|
||||
// Fall back to ~/.gsd/defaults.json only for truly pre-project contexts (#1683)
|
||||
@@ -414,6 +436,9 @@ function loadConfig(cwd) {
|
||||
plan_checker: globalDefaults.plan_checker ?? defaults.plan_checker,
|
||||
verifier: globalDefaults.verifier ?? defaults.verifier,
|
||||
nyquist_validation: globalDefaults.nyquist_validation ?? defaults.nyquist_validation,
|
||||
post_planning_gaps: globalDefaults.post_planning_gaps
|
||||
?? globalDefaults.workflow?.post_planning_gaps
|
||||
?? defaults.post_planning_gaps,
|
||||
parallelization: globalDefaults.parallelization ?? defaults.parallelization,
|
||||
text_mode: globalDefaults.text_mode ?? defaults.text_mode,
|
||||
resolve_model_ids: globalDefaults.resolve_model_ids ?? defaults.resolve_model_ids,
|
||||
@@ -1284,8 +1309,11 @@ function extractCurrentMilestone(content, cwd) {
|
||||
// Milestone headings look like: ## v2.0, ## Roadmap v2.0, ## ✅ v1.0, etc.
|
||||
const headingLevel = sectionMatch[1].match(/^(#{1,3})\s/)[1].length;
|
||||
const restContent = content.slice(sectionStart + sectionMatch[0].length);
|
||||
// Exclude phase headings (e.g. "### Phase 12: v1.0 Tech-Debt Closure") from
|
||||
// being treated as milestone boundaries just because they mention vX.Y in
|
||||
// the title. Phase headings always start with the literal `Phase `. See #2619.
|
||||
const nextMilestonePattern = new RegExp(
|
||||
`^#{1,${headingLevel}}\\s+(?:.*v\\d+\\.\\d+|✅|📋|🚧)`,
|
||||
`^#{1,${headingLevel}}\\s+(?!Phase\\s+\\S)(?:.*v\\d+\\.\\d+|✅|📋|🚧)`,
|
||||
'mi'
|
||||
);
|
||||
const nextMatch = restContent.match(nextMilestonePattern);
|
||||
@@ -1334,9 +1362,19 @@ function getRoadmapPhaseInternal(cwd, phaseNum) {
|
||||
|
||||
try {
|
||||
const content = extractCurrentMilestone(fs.readFileSync(roadmapPath, 'utf-8'), cwd);
|
||||
const escapedPhase = escapeRegex(phaseNum.toString());
|
||||
// Match both numeric (Phase 1:) and custom (Phase PROJ-42:) headers
|
||||
const phasePattern = new RegExp(`#{2,4}\\s*Phase\\s+${escapedPhase}:\\s*([^\\n]+)`, 'i');
|
||||
// Strip leading zeros from purely numeric phase numbers so "03" matches "Phase 3:"
|
||||
// in canonical ROADMAP headings. Non-numeric IDs (e.g. "PROJ-42") are kept as-is.
|
||||
const normalized = /^\d+$/.test(String(phaseNum))
|
||||
? String(phaseNum).replace(/^0+(?=\d)/, '')
|
||||
: String(phaseNum);
|
||||
const escapedPhase = escapeRegex(normalized);
|
||||
// Match both numeric and custom (Phase PROJ-42:) headers.
|
||||
// For purely numeric phases allow optional leading zeros so both "Phase 1:" and
|
||||
// "Phase 01:" are matched regardless of whether the ROADMAP uses padded numbers.
|
||||
const isNumeric = /^\d+$/.test(String(phaseNum));
|
||||
const phasePattern = isNumeric
|
||||
? new RegExp(`#{2,4}\\s*Phase\\s+0*${escapedPhase}:\\s*([^\\n]+)`, 'i')
|
||||
: new RegExp(`#{2,4}\\s*Phase\\s+${escapedPhase}:\\s*([^\\n]+)`, 'i');
|
||||
const headerMatch = content.match(phasePattern);
|
||||
if (!headerMatch) return null;
|
||||
|
||||
@@ -1438,32 +1476,220 @@ const MODEL_ALIAS_MAP = {
|
||||
'haiku': 'claude-haiku-4-5',
|
||||
};
|
||||
|
||||
/**
|
||||
* #2517 — runtime-aware tier resolution.
|
||||
* Maps `model_profile` tiers (opus/sonnet/haiku) to runtime-native model IDs and
|
||||
* (where supported) reasoning_effort settings.
|
||||
*
|
||||
* Each entry: { model: <id>, reasoning_effort?: <level> }
|
||||
*
|
||||
* `claude` mirrors MODEL_ALIAS_MAP — present for symmetry so `runtime: "claude"`
|
||||
* resolves through the same code path. `codex` defaults are taken from the spec
|
||||
* in #2517. Unknown runtimes fall back to the Claude alias to avoid emitting
|
||||
* provider-specific IDs the runtime cannot accept.
|
||||
*/
|
||||
const RUNTIME_PROFILE_MAP = {
|
||||
claude: {
|
||||
opus: { model: 'claude-opus-4-6' },
|
||||
sonnet: { model: 'claude-sonnet-4-6' },
|
||||
haiku: { model: 'claude-haiku-4-5' },
|
||||
},
|
||||
codex: {
|
||||
opus: { model: 'gpt-5.4', reasoning_effort: 'xhigh' },
|
||||
sonnet: { model: 'gpt-5.3-codex', reasoning_effort: 'medium' },
|
||||
haiku: { model: 'gpt-5.4-mini', reasoning_effort: 'medium' },
|
||||
},
|
||||
};
|
||||
|
||||
const RUNTIMES_WITH_REASONING_EFFORT = new Set(['codex']);
|
||||
|
||||
/**
|
||||
* Tier enum allowed under `model_profile_overrides[runtime][tier]`. Mirrors the
|
||||
* regex in `config-schema.cjs` (DYNAMIC_KEY_PATTERNS) so loadConfig surfaces the
|
||||
* same constraint at read time, not only at config-set time (review finding #10).
|
||||
*/
|
||||
const RUNTIME_OVERRIDE_TIERS = new Set(['opus', 'sonnet', 'haiku']);
|
||||
|
||||
/**
|
||||
* Allowlist of runtime names the install pipeline currently knows how to emit
|
||||
* native model IDs for. Synced with `getDirName` in `bin/install.js` and the
|
||||
* runtime list in `docs/CONFIGURATION.md`. Free-string runtimes outside this
|
||||
* set are still accepted (#2517 deliberately leaves the runtime field open) —
|
||||
* a warning fires once at loadConfig so a typo like `runtime: "codx"` does not
|
||||
* silently fall back to Claude defaults (review findings #10, #13).
|
||||
*/
|
||||
const KNOWN_RUNTIMES = new Set([
|
||||
'claude', 'codex', 'opencode', 'kilo', 'gemini', 'qwen',
|
||||
'copilot', 'cursor', 'windsurf', 'augment', 'trae', 'codebuddy',
|
||||
'antigravity', 'cline',
|
||||
]);
|
||||
|
||||
const _warnedConfigKeys = new Set();
|
||||
/**
|
||||
* Emit a one-time stderr warning for unknown runtime/tier keys in a parsed
|
||||
* config blob. Idempotent across calls — the same (file, key) pair only warns
|
||||
* once per process so loadConfig can be called repeatedly without spamming.
|
||||
*
|
||||
* Does NOT reject — preserves back-compat for users on a runtime not yet in the
|
||||
* allowlist (the new-runtime case must always be possible without code changes).
|
||||
*/
|
||||
function _warnUnknownProfileOverrides(parsed, configLabel) {
|
||||
if (!parsed || typeof parsed !== 'object') return;
|
||||
|
||||
const runtime = parsed.runtime;
|
||||
if (runtime && typeof runtime === 'string' && !KNOWN_RUNTIMES.has(runtime)) {
|
||||
const key = `${configLabel}::runtime::${runtime}`;
|
||||
if (!_warnedConfigKeys.has(key)) {
|
||||
_warnedConfigKeys.add(key);
|
||||
try {
|
||||
process.stderr.write(
|
||||
`gsd: warning — config key "runtime" has unknown value "${runtime}". ` +
|
||||
`Known runtimes: ${[...KNOWN_RUNTIMES].sort().join(', ')}. ` +
|
||||
`Resolution will fall back to safe defaults. (#2517)\n`
|
||||
);
|
||||
} catch { /* stderr might be closed in some test harnesses */ }
|
||||
}
|
||||
}
|
||||
|
||||
const overrides = parsed.model_profile_overrides;
|
||||
if (!overrides || typeof overrides !== 'object') return;
|
||||
for (const [overrideRuntime, tierMap] of Object.entries(overrides)) {
|
||||
if (!KNOWN_RUNTIMES.has(overrideRuntime)) {
|
||||
const key = `${configLabel}::override-runtime::${overrideRuntime}`;
|
||||
if (!_warnedConfigKeys.has(key)) {
|
||||
_warnedConfigKeys.add(key);
|
||||
try {
|
||||
process.stderr.write(
|
||||
`gsd: warning — model_profile_overrides.${overrideRuntime}.* uses ` +
|
||||
`unknown runtime "${overrideRuntime}". Known runtimes: ` +
|
||||
`${[...KNOWN_RUNTIMES].sort().join(', ')}. (#2517)\n`
|
||||
);
|
||||
} catch { /* ok */ }
|
||||
}
|
||||
}
|
||||
if (!tierMap || typeof tierMap !== 'object') continue;
|
||||
for (const tierName of Object.keys(tierMap)) {
|
||||
if (!RUNTIME_OVERRIDE_TIERS.has(tierName)) {
|
||||
const key = `${configLabel}::override-tier::${overrideRuntime}.${tierName}`;
|
||||
if (!_warnedConfigKeys.has(key)) {
|
||||
_warnedConfigKeys.add(key);
|
||||
try {
|
||||
process.stderr.write(
|
||||
`gsd: warning — model_profile_overrides.${overrideRuntime}.${tierName} ` +
|
||||
`uses unknown tier "${tierName}". Allowed tiers: opus, sonnet, haiku. (#2517)\n`
|
||||
);
|
||||
} catch { /* ok */ }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Internal helper exposed for tests so per-process warning state can be reset
|
||||
// between cases that intentionally exercise the warning path repeatedly.
|
||||
function _resetRuntimeWarningCacheForTests() {
|
||||
_warnedConfigKeys.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* #2517 — Resolve the runtime-aware tier entry for (runtime, tier).
|
||||
*
|
||||
* Single source of truth shared by core.cjs (resolveModelInternal /
|
||||
* resolveReasoningEffortInternal) and bin/install.js (Codex/OpenCode TOML emit
|
||||
* paths). Always merges built-in defaults with user overrides at the field
|
||||
* level so partial overrides keep the unspecified fields:
|
||||
*
|
||||
* `{ codex: { opus: "gpt-5-pro" } }` keeps reasoning_effort: 'xhigh'
|
||||
* `{ codex: { opus: { reasoning_effort: 'low' } } }` keeps model: 'gpt-5.4'
|
||||
*
|
||||
* Without this field-merge, the documented string-shorthand example silently
|
||||
* dropped reasoning_effort and a partial-object override silently dropped the
|
||||
* model — both reported as critical findings in the #2609 review.
|
||||
*
|
||||
* Inputs:
|
||||
* - runtime: string (e.g. 'codex', 'claude', 'opencode')
|
||||
* - tier: 'opus' | 'sonnet' | 'haiku'
|
||||
* - overrides: optional `model_profile_overrides` blob (may be null/undefined)
|
||||
*
|
||||
* Returns `{ model: string, reasoning_effort?: string } | null`.
|
||||
*/
|
||||
function resolveTierEntry({ runtime, tier, overrides }) {
|
||||
if (!runtime || !tier) return null;
|
||||
|
||||
const builtin = RUNTIME_PROFILE_MAP[runtime]?.[tier] || null;
|
||||
const userRaw = overrides?.[runtime]?.[tier];
|
||||
|
||||
// String shorthand from CONFIGURATION.md examples — `{ codex: { opus: "gpt-5-pro" } }`.
|
||||
// Treat as `{ model: "gpt-5-pro" }` so the field-merge below still preserves
|
||||
// reasoning_effort from the built-in defaults.
|
||||
let userEntry = null;
|
||||
if (userRaw) {
|
||||
userEntry = typeof userRaw === 'string' ? { model: userRaw } : userRaw;
|
||||
}
|
||||
|
||||
if (!builtin && !userEntry) return null;
|
||||
// Field-merge: user fields win, built-in fills the gaps.
|
||||
return { ...(builtin || {}), ...(userEntry || {}) };
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience wrapper used by resolveModelInternal / resolveReasoningEffortInternal.
|
||||
* Pulls runtime + overrides out of a loaded config and delegates to resolveTierEntry.
|
||||
*/
|
||||
function _resolveRuntimeTier(config, tier) {
|
||||
return resolveTierEntry({
|
||||
runtime: config.runtime,
|
||||
tier,
|
||||
overrides: config.model_profile_overrides,
|
||||
});
|
||||
}
|
||||
|
||||
function resolveModelInternal(cwd, agentType) {
|
||||
const config = loadConfig(cwd);
|
||||
|
||||
// Check per-agent override first — always respected regardless of resolve_model_ids.
|
||||
// 1. Per-agent override — always respected; highest precedence.
|
||||
// Users who set fully-qualified model IDs (e.g., "openai/gpt-5.4") get exactly that.
|
||||
const override = config.model_overrides?.[agentType];
|
||||
if (override) {
|
||||
return override;
|
||||
}
|
||||
|
||||
// resolve_model_ids: "omit" — return empty string so the runtime uses its configured
|
||||
// default model. For non-Claude runtimes (OpenCode, Codex, etc.) that don't recognize
|
||||
// Claude aliases (opus/sonnet/haiku/inherit). Set automatically during install. See #1156.
|
||||
// 2. Compute the tier (opus/sonnet/haiku) for this agent under the active profile.
|
||||
const profile = String(config.model_profile || 'balanced').toLowerCase();
|
||||
const agentModels = MODEL_PROFILES[agentType];
|
||||
const tier = agentModels ? (agentModels[profile] || agentModels['balanced']) : null;
|
||||
|
||||
// 3. Runtime-aware resolution (#2517) — only when `runtime` is explicitly set
|
||||
// to a non-Claude runtime. `runtime: "claude"` is the implicit default and is
|
||||
// treated as a no-op here so it does not silently override `resolve_model_ids:
|
||||
// "omit"` (review finding #4). Deliberate ordering for non-Claude runtimes:
|
||||
// explicit opt-in beats `resolve_model_ids: "omit"` so users on Codex installs
|
||||
// that auto-set "omit" can still flip on tiered behavior by setting runtime
|
||||
// alone. inherit profile is preserved verbatim.
|
||||
if (config.runtime && config.runtime !== 'claude' && profile !== 'inherit' && tier) {
|
||||
const entry = _resolveRuntimeTier(config, tier);
|
||||
if (entry?.model) return entry.model;
|
||||
// Unknown runtime with no user-supplied overrides — fall through to Claude-safe
|
||||
// default rather than emit an ID the runtime can't accept.
|
||||
}
|
||||
|
||||
// 4. resolve_model_ids: "omit" — return empty string so the runtime uses its
|
||||
// configured default model. For non-Claude runtimes (OpenCode, Codex, etc.) that
|
||||
// don't recognize Claude aliases. Set automatically during install. See #1156.
|
||||
if (config.resolve_model_ids === 'omit') {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Fall back to profile lookup
|
||||
const profile = String(config.model_profile || 'balanced').toLowerCase();
|
||||
const agentModels = MODEL_PROFILES[agentType];
|
||||
// 5. Profile lookup (Claude-native default).
|
||||
if (!agentModels) return 'sonnet';
|
||||
if (profile === 'inherit') return 'inherit';
|
||||
const alias = agentModels[profile] || agentModels['balanced'] || 'sonnet';
|
||||
// `tier` is guaranteed truthy here: agentModels exists, and MODEL_PROFILES
|
||||
// entries always define `balanced`, so `agentModels[profile] || agentModels.balanced`
|
||||
// resolves to a string. Keep the local for readability — no defensive fallback.
|
||||
const alias = tier;
|
||||
|
||||
// resolve_model_ids: true — map alias to full Claude model ID
|
||||
// Prevents 404s when the Task tool passes aliases directly to the API
|
||||
// resolve_model_ids: true — map alias to full Claude model ID.
|
||||
// Prevents 404s when the Task tool passes aliases directly to the API.
|
||||
if (config.resolve_model_ids) {
|
||||
return MODEL_ALIAS_MAP[alias] || alias;
|
||||
}
|
||||
@@ -1471,6 +1697,41 @@ function resolveModelInternal(cwd, agentType) {
|
||||
return alias;
|
||||
}
|
||||
|
||||
/**
|
||||
* #2517 — Resolve runtime-specific reasoning_effort for an agent.
|
||||
* Returns null unless:
|
||||
* - `runtime` is explicitly set in config,
|
||||
* - the runtime supports reasoning_effort (currently: codex),
|
||||
* - profile is not 'inherit',
|
||||
* - the resolved tier entry has a `reasoning_effort` value.
|
||||
*
|
||||
* Never returns a value for Claude — keeps reasoning_effort out of Claude spawn paths.
|
||||
*/
|
||||
function resolveReasoningEffortInternal(cwd, agentType) {
|
||||
const config = loadConfig(cwd);
|
||||
if (!config.runtime) return null;
|
||||
// Strict allowlist: reasoning_effort only propagates for runtimes whose
|
||||
// install path actually accepts it. Adding a new runtime here is the only
|
||||
// way to enable effort propagation — overrides cannot bypass the gate.
|
||||
// Without this, a typo in `runtime` (e.g. `"codx"`) plus a user override
|
||||
// for that typo would leak `xhigh` into a Claude or unknown install
|
||||
// (review finding #3).
|
||||
if (!RUNTIMES_WITH_REASONING_EFFORT.has(config.runtime)) return null;
|
||||
// Per-agent override means user supplied a fully-qualified ID; reasoning_effort
|
||||
// for that case must be set via per-agent mechanism, not tier inference.
|
||||
if (config.model_overrides?.[agentType]) return null;
|
||||
|
||||
const profile = String(config.model_profile || 'balanced').toLowerCase();
|
||||
if (profile === 'inherit') return null;
|
||||
const agentModels = MODEL_PROFILES[agentType];
|
||||
if (!agentModels) return null;
|
||||
const tier = agentModels[profile] || agentModels['balanced'];
|
||||
if (!tier) return null;
|
||||
|
||||
const entry = _resolveRuntimeTier(config, tier);
|
||||
return entry?.reasoning_effort || null;
|
||||
}
|
||||
|
||||
// ─── Summary body helpers ─────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
@@ -1509,6 +1770,50 @@ function getMilestoneInfo(cwd) {
|
||||
try {
|
||||
const roadmap = fs.readFileSync(path.join(planningDir(cwd), 'ROADMAP.md'), 'utf-8');
|
||||
|
||||
// 0. Prefer STATE.md milestone: frontmatter as the authoritative source.
|
||||
// This prevents falling through to a regex that may match an old heading
|
||||
// when the active milestone's 🚧 marker is inside a <summary> tag without
|
||||
// **bold** formatting (bug #2409).
|
||||
let stateVersion = null;
|
||||
if (cwd) {
|
||||
try {
|
||||
const statePath = path.join(planningDir(cwd), 'STATE.md');
|
||||
if (fs.existsSync(statePath)) {
|
||||
const stateRaw = fs.readFileSync(statePath, 'utf-8');
|
||||
const m = stateRaw.match(/^milestone:\s*(.+)/m);
|
||||
if (m) stateVersion = m[1].trim();
|
||||
}
|
||||
} catch { /* intentionally empty */ }
|
||||
}
|
||||
|
||||
if (stateVersion) {
|
||||
// Look up the name for this version in ROADMAP.md
|
||||
const escapedVer = escapeRegex(stateVersion);
|
||||
// Match heading-format: ## Roadmap v2.9: Name or ## v2.9 Name
|
||||
const headingMatch = roadmap.match(
|
||||
new RegExp(`##[^\\n]*${escapedVer}[:\\s]+([^\\n(]+)`, 'i')
|
||||
);
|
||||
if (headingMatch) {
|
||||
// If the heading line contains ✅ the milestone is already shipped.
|
||||
// Fall through to normal detection so the NEW active milestone is returned
|
||||
// instead of the stale shipped one still recorded in STATE.md.
|
||||
if (!headingMatch[0].includes('✅')) {
|
||||
return { version: stateVersion, name: headingMatch[1].trim() };
|
||||
}
|
||||
// Shipped milestone — do not early-return; fall through to normal detection below.
|
||||
} else {
|
||||
// Match list-format: 🚧 **v2.9 Name** or 🚧 v2.9 Name
|
||||
const listMatch = roadmap.match(
|
||||
new RegExp(`🚧\\s*\\*?\\*?${escapedVer}\\s+([^*\\n]+)`, 'i')
|
||||
);
|
||||
if (listMatch) {
|
||||
return { version: stateVersion, name: listMatch[1].trim() };
|
||||
}
|
||||
// Version found in STATE.md but no name match in ROADMAP — return bare version
|
||||
return { version: stateVersion, name: 'milestone' };
|
||||
}
|
||||
}
|
||||
|
||||
// First: check for list-format roadmaps using 🚧 (in-progress) marker
|
||||
// e.g. "- 🚧 **v2.1 Belgium** — Phases 24-28 (in progress)"
|
||||
// e.g. "- 🚧 **v1.2.1 Tech Debt** — Phases 1-8 (in progress)"
|
||||
@@ -1520,11 +1825,14 @@ function getMilestoneInfo(cwd) {
|
||||
};
|
||||
}
|
||||
|
||||
// Second: heading-format roadmaps — strip shipped milestones in <details> blocks
|
||||
// Second: heading-format roadmaps — strip shipped milestones.
|
||||
// <details> blocks are stripped by stripShippedMilestones; heading-format ✅ markers
|
||||
// are excluded by the negative lookahead below so a stale STATE.md version (or any
|
||||
// shipped ✅ heading) never wins over the first non-shipped milestone heading.
|
||||
const cleaned = stripShippedMilestones(roadmap);
|
||||
// Extract version and name from the same ## heading for consistency
|
||||
// Negative lookahead skips headings that contain ✅ (shipped milestone marker).
|
||||
// Supports 2+ segment versions: v1.2, v1.2.1, v2.0.1, etc.
|
||||
const headingMatch = cleaned.match(/## .*v(\d+(?:\.\d+)+)[:\s]+([^\n(]+)/);
|
||||
const headingMatch = cleaned.match(/## (?!.*✅).*v(\d+(?:\.\d+)+)[:\s]+([^\n(]+)/);
|
||||
if (headingMatch) {
|
||||
return {
|
||||
version: 'v' + headingMatch[1],
|
||||
@@ -1566,7 +1874,7 @@ function getMilestonePhaseFilter(cwd) {
|
||||
}
|
||||
|
||||
const normalized = new Set(
|
||||
[...milestonePhaseNums].map(n => (n.replace(/^0+/, '') || '0').toLowerCase())
|
||||
[...milestonePhaseNums].map(n => (n.replace(/^0+(?=\d)/, '') || '0').toLowerCase())
|
||||
);
|
||||
|
||||
function isDirInMilestone(dirName) {
|
||||
@@ -1702,6 +2010,13 @@ module.exports = {
|
||||
getArchivedPhaseDirs,
|
||||
getRoadmapPhaseInternal,
|
||||
resolveModelInternal,
|
||||
resolveReasoningEffortInternal,
|
||||
RUNTIME_PROFILE_MAP,
|
||||
RUNTIMES_WITH_REASONING_EFFORT,
|
||||
KNOWN_RUNTIMES,
|
||||
RUNTIME_OVERRIDE_TIERS,
|
||||
resolveTierEntry,
|
||||
_resetRuntimeWarningCacheForTests,
|
||||
pathExistsInternal,
|
||||
generateSlugInternal,
|
||||
getMilestoneInfo,
|
||||
|
||||
48
get-shit-done/bin/lib/decisions.cjs
Normal file
48
get-shit-done/bin/lib/decisions.cjs
Normal file
@@ -0,0 +1,48 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Shared parser for CONTEXT.md `<decisions>` blocks.
|
||||
*
|
||||
* Used by:
|
||||
* - gap-checker.cjs (#2493 post-planning gap analysis)
|
||||
* - intended for #2492 (plan-phase decision gate, verify-phase decision validator)
|
||||
*
|
||||
* Format produced by discuss-phase.md:
|
||||
*
|
||||
* <decisions>
|
||||
* ## Implementation Decisions
|
||||
*
|
||||
* ### Category
|
||||
* - **D-01:** Decision text
|
||||
* - **D-02:** Another decision
|
||||
* </decisions>
|
||||
*
|
||||
* D-IDs outside the <decisions> block are ignored. Missing block returns [].
|
||||
*/
|
||||
|
||||
/**
|
||||
* Parse the <decisions> section of a CONTEXT.md string.
|
||||
*
|
||||
* @param {string|null|undefined} contextMd - File contents, may be empty/missing.
|
||||
* @returns {Array<{id: string, text: string}>}
|
||||
*/
|
||||
function parseDecisions(contextMd) {
|
||||
if (!contextMd || typeof contextMd !== 'string') return [];
|
||||
const blockMatch = contextMd.match(/<decisions>([\s\S]*?)<\/decisions>/);
|
||||
if (!blockMatch) return [];
|
||||
const block = blockMatch[1];
|
||||
|
||||
const decisionRe = /^\s*-\s*\*\*(D-[A-Za-z0-9_-]+):\*\*\s*(.+?)\s*$/gm;
|
||||
const out = [];
|
||||
const seen = new Set();
|
||||
let m;
|
||||
while ((m = decisionRe.exec(block)) !== null) {
|
||||
const id = m[1];
|
||||
if (seen.has(id)) continue;
|
||||
seen.add(id);
|
||||
out.push({ id, text: m[2] });
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
module.exports = { parseDecisions };
|
||||
378
get-shit-done/bin/lib/drift.cjs
Normal file
378
get-shit-done/bin/lib/drift.cjs
Normal file
@@ -0,0 +1,378 @@
|
||||
/**
|
||||
* Codebase Drift Detection (#2003)
|
||||
*
|
||||
* Detects structural drift between a committed codebase and the
|
||||
* `.planning/codebase/STRUCTURE.md` map produced by `gsd-codebase-mapper`.
|
||||
*
|
||||
* Four categories of drift element:
|
||||
* - new_dir → a newly-added file whose directory prefix does not appear
|
||||
* in STRUCTURE.md
|
||||
* - barrel → a newly-added barrel export at
|
||||
* (packages|apps)/<name>/src/index.(ts|tsx|js|mjs|cjs)
|
||||
* - migration → a newly-added migration file under one of the recognized
|
||||
* migration directories (supabase, prisma, drizzle, src/migrations, …)
|
||||
* - route → a newly-added route module under a `routes/` or `api/` dir
|
||||
*
|
||||
* Each file is counted at most once; when a file matches multiple categories
|
||||
* the most specific category wins (migration > route > barrel > new_dir).
|
||||
*
|
||||
* Design decisions (see PR for full rubber-duck):
|
||||
* - The library is pure. It takes parsed git diff output and returns a
|
||||
* structured result. The CLI/workflow layer is responsible for running
|
||||
* git and for spawning mappers.
|
||||
* - `last_mapped_commit` is stored as YAML-style frontmatter at the top of
|
||||
* each `.planning/codebase/*.md` file. This keeps the baseline attached
|
||||
* to the file, survives git moves, and avoids a sidecar JSON.
|
||||
* - The detector NEVER throws on malformed input — it returns a
|
||||
* `{ skipped: true }` result. The phase workflow depends on this
|
||||
* non-blocking guarantee.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const fs = require('node:fs');
|
||||
|
||||
// ─── Constants ───────────────────────────────────────────────────────────────
|
||||
|
||||
const DRIFT_CATEGORIES = Object.freeze(['new_dir', 'barrel', 'migration', 'route']);
|
||||
|
||||
// Category priority when a single file matches multiple rules.
|
||||
// Higher index = more specific = wins.
|
||||
const CATEGORY_PRIORITY = { new_dir: 0, barrel: 1, route: 2, migration: 3 };
|
||||
|
||||
const BARREL_RE = /^(packages|apps)\/[^/]+\/src\/index\.(ts|tsx|js|mjs|cjs)$/;
|
||||
|
||||
const MIGRATION_RES = [
|
||||
/^supabase\/migrations\/.+\.sql$/,
|
||||
/^prisma\/migrations\/.+/,
|
||||
/^drizzle\/meta\/.+/,
|
||||
/^drizzle\/migrations\/.+/,
|
||||
/^src\/migrations\/.+\.(ts|js|sql)$/,
|
||||
/^db\/migrations\/.+\.(sql|ts|js)$/,
|
||||
/^migrations\/.+\.(sql|ts|js)$/,
|
||||
];
|
||||
|
||||
const ROUTE_RES = [
|
||||
/^(apps|packages)\/[^/]+\/src\/routes\/.+\.(ts|tsx|js|jsx|mjs|cjs)$/,
|
||||
/^src\/routes\/.+\.(ts|tsx|js|jsx|mjs|cjs)$/,
|
||||
/^src\/api\/.+\.(ts|tsx|js|jsx|mjs|cjs)$/,
|
||||
/^(apps|packages)\/[^/]+\/src\/api\/.+\.(ts|tsx|js|jsx|mjs|cjs)$/,
|
||||
];
|
||||
|
||||
// A conservative allowlist for `--paths` arguments passed to the mapper:
|
||||
// repo-relative path components separated by /, containing only
|
||||
// alphanumerics, dash, underscore, and dot (no `..`, no `/..`).
|
||||
const SAFE_PATH_RE = /^(?!.*\.\.)(?:[A-Za-z0-9_.][A-Za-z0-9_.\-]*)(?:\/[A-Za-z0-9_.][A-Za-z0-9_.\-]*)*$/;
|
||||
|
||||
// ─── Classification ──────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Classify a single file path into a drift category or null.
|
||||
*
|
||||
* @param {string} file - repo-relative path, forward slashes.
|
||||
* @returns {'barrel'|'migration'|'route'|null}
|
||||
*/
|
||||
function classifyFile(file) {
|
||||
if (typeof file !== 'string' || !file) return null;
|
||||
const norm = file.replace(/\\/g, '/');
|
||||
if (MIGRATION_RES.some((r) => r.test(norm))) return 'migration';
|
||||
if (ROUTE_RES.some((r) => r.test(norm))) return 'route';
|
||||
if (BARREL_RE.test(norm)) return 'barrel';
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* True iff any prefix of `file` (dir1, dir1/dir2, …) appears as a substring
|
||||
* of `structureMd`. Used to decide whether a file is in "mapped territory".
|
||||
*
|
||||
* Matching is deliberately substring-based — STRUCTURE.md is free-form
|
||||
* markdown, not a structured manifest. If the map mentions `src/lib/` the
|
||||
* check `structureMd.includes('src/lib')` holds.
|
||||
*/
|
||||
function isPathMapped(file, structureMd) {
|
||||
const norm = file.replace(/\\/g, '/');
|
||||
const parts = norm.split('/');
|
||||
// Check prefixes from longest to shortest; any hit means "mapped".
|
||||
for (let i = parts.length - 1; i >= 1; i--) {
|
||||
const prefix = parts.slice(0, i).join('/');
|
||||
if (structureMd.includes(prefix)) return true;
|
||||
}
|
||||
// Finally, if even the top-level dir is mentioned, count as mapped.
|
||||
if (parts.length > 0 && structureMd.includes(parts[0] + '/')) return true;
|
||||
if (parts.length > 0 && structureMd.includes('`' + parts[0] + '`')) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
// ─── Main detection ──────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Detect codebase drift.
|
||||
*
|
||||
* @param {object} input
|
||||
* @param {string[]} input.addedFiles - files with git status A (new)
|
||||
* @param {string[]} input.modifiedFiles - files with git status M
|
||||
* @param {string[]} input.deletedFiles - files with git status D
|
||||
* @param {string|null|undefined} input.structureMd - contents of STRUCTURE.md
|
||||
* @param {number} [input.threshold=3] - min number of drift elements that triggers action
|
||||
* @param {'warn'|'auto-remap'} [input.action='warn']
|
||||
* @returns {object} result
|
||||
*/
|
||||
function detectDrift(input) {
|
||||
try {
|
||||
if (!input || typeof input !== 'object') {
|
||||
return skipped('invalid-input');
|
||||
}
|
||||
const {
|
||||
addedFiles,
|
||||
modifiedFiles,
|
||||
deletedFiles,
|
||||
structureMd,
|
||||
} = input;
|
||||
const threshold = Number.isInteger(input.threshold) && input.threshold >= 1
|
||||
? input.threshold
|
||||
: 3;
|
||||
const action = input.action === 'auto-remap' ? 'auto-remap' : 'warn';
|
||||
|
||||
if (structureMd === null || structureMd === undefined) {
|
||||
return skipped('missing-structure-md');
|
||||
}
|
||||
if (typeof structureMd !== 'string') {
|
||||
return skipped('invalid-structure-md');
|
||||
}
|
||||
|
||||
const added = Array.isArray(addedFiles) ? addedFiles.filter((x) => typeof x === 'string') : [];
|
||||
const modified = Array.isArray(modifiedFiles) ? modifiedFiles : [];
|
||||
const deleted = Array.isArray(deletedFiles) ? deletedFiles : [];
|
||||
|
||||
// Build elements. One element per file, highest-priority category wins.
|
||||
/** @type {{category: string, path: string}[]} */
|
||||
const elements = [];
|
||||
const seen = new Map();
|
||||
|
||||
for (const rawFile of added) {
|
||||
const file = rawFile.replace(/\\/g, '/');
|
||||
const specific = classifyFile(file);
|
||||
let category = specific;
|
||||
if (!category) {
|
||||
if (!isPathMapped(file, structureMd)) {
|
||||
category = 'new_dir';
|
||||
} else {
|
||||
continue; // mapped, known, ordinary file — not drift
|
||||
}
|
||||
}
|
||||
// Dedup: if we've already counted this path at higher-or-equal priority, skip
|
||||
const prior = seen.get(file);
|
||||
if (prior && CATEGORY_PRIORITY[prior] >= CATEGORY_PRIORITY[category]) continue;
|
||||
seen.set(file, category);
|
||||
}
|
||||
|
||||
for (const [file, category] of seen.entries()) {
|
||||
elements.push({ category, path: file });
|
||||
}
|
||||
|
||||
// Sort for stable output.
|
||||
elements.sort((a, b) =>
|
||||
a.category === b.category
|
||||
? a.path.localeCompare(b.path)
|
||||
: a.category.localeCompare(b.category),
|
||||
);
|
||||
|
||||
const actionRequired = elements.length >= threshold;
|
||||
let directive = 'none';
|
||||
let spawnMapper = false;
|
||||
let affectedPaths = [];
|
||||
let message = '';
|
||||
|
||||
if (actionRequired) {
|
||||
directive = action;
|
||||
affectedPaths = chooseAffectedPaths(elements.map((e) => e.path));
|
||||
if (action === 'auto-remap') {
|
||||
spawnMapper = true;
|
||||
}
|
||||
message = buildMessage(elements, affectedPaths, action);
|
||||
}
|
||||
|
||||
return {
|
||||
skipped: false,
|
||||
elements,
|
||||
actionRequired,
|
||||
directive,
|
||||
spawnMapper,
|
||||
affectedPaths,
|
||||
threshold,
|
||||
action,
|
||||
message,
|
||||
counts: {
|
||||
added: added.length,
|
||||
modified: modified.length,
|
||||
deleted: deleted.length,
|
||||
},
|
||||
};
|
||||
} catch (err) {
|
||||
// Non-blocking: never throw from this function.
|
||||
return skipped('exception:' + (err && err.message ? err.message : String(err)));
|
||||
}
|
||||
}
|
||||
|
||||
function skipped(reason) {
|
||||
return {
|
||||
skipped: true,
|
||||
reason,
|
||||
elements: [],
|
||||
actionRequired: false,
|
||||
directive: 'none',
|
||||
spawnMapper: false,
|
||||
affectedPaths: [],
|
||||
message: '',
|
||||
};
|
||||
}
|
||||
|
||||
function buildMessage(elements, affectedPaths, action) {
|
||||
const byCat = {};
|
||||
for (const e of elements) {
|
||||
(byCat[e.category] ||= []).push(e.path);
|
||||
}
|
||||
const lines = [
|
||||
`Codebase drift detected: ${elements.length} structural element(s) since last mapping.`,
|
||||
'',
|
||||
];
|
||||
const labels = {
|
||||
new_dir: 'New directories',
|
||||
barrel: 'New barrel exports',
|
||||
migration: 'New migrations',
|
||||
route: 'New route modules',
|
||||
};
|
||||
for (const cat of ['new_dir', 'barrel', 'migration', 'route']) {
|
||||
if (byCat[cat]) {
|
||||
lines.push(`${labels[cat]}:`);
|
||||
for (const p of byCat[cat]) lines.push(` - ${p}`);
|
||||
}
|
||||
}
|
||||
lines.push('');
|
||||
if (action === 'auto-remap') {
|
||||
lines.push(`Auto-remap scheduled for paths: ${affectedPaths.join(', ')}`);
|
||||
} else {
|
||||
lines.push(
|
||||
`Run /gsd:map-codebase --paths ${affectedPaths.join(',')} to refresh planning context.`,
|
||||
);
|
||||
}
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
// ─── Affected paths ──────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Collapse a list of drifted file paths into a sorted, deduplicated list of
|
||||
* the top-level directory prefixes (depth 2 when the repo uses an
|
||||
* `<apps|packages>/<name>/…` layout; depth 1 otherwise).
|
||||
*/
|
||||
function chooseAffectedPaths(paths) {
|
||||
const out = new Set();
|
||||
for (const raw of paths || []) {
|
||||
if (typeof raw !== 'string' || !raw) continue;
|
||||
const file = raw.replace(/\\/g, '/');
|
||||
const parts = file.split('/');
|
||||
if (parts.length === 0) continue;
|
||||
const top = parts[0];
|
||||
if ((top === 'apps' || top === 'packages') && parts.length >= 2) {
|
||||
out.add(`${top}/${parts[1]}`);
|
||||
} else {
|
||||
out.add(top);
|
||||
}
|
||||
}
|
||||
return [...out].sort();
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter `paths` to only those that are safe to splice into a mapper prompt.
|
||||
* Any path that is absolute, contains traversal, or includes shell
|
||||
* metacharacters is dropped.
|
||||
*/
|
||||
function sanitizePaths(paths) {
|
||||
if (!Array.isArray(paths)) return [];
|
||||
const out = [];
|
||||
for (const p of paths) {
|
||||
if (typeof p !== 'string') continue;
|
||||
if (p.startsWith('/')) continue;
|
||||
if (!SAFE_PATH_RE.test(p)) continue;
|
||||
out.push(p);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// ─── Frontmatter helpers ─────────────────────────────────────────────────────
|
||||
|
||||
const FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/;
|
||||
|
||||
function parseFrontmatter(content) {
|
||||
if (typeof content !== 'string') return { data: {}, body: '' };
|
||||
const m = content.match(FRONTMATTER_RE);
|
||||
if (!m) return { data: {}, body: content };
|
||||
const data = {};
|
||||
for (const line of m[1].split(/\r?\n/)) {
|
||||
const kv = line.match(/^([A-Za-z0-9_][A-Za-z0-9_-]*):\s*(.*)$/);
|
||||
if (!kv) continue;
|
||||
data[kv[1]] = kv[2];
|
||||
}
|
||||
return { data, body: content.slice(m[0].length) };
|
||||
}
|
||||
|
||||
function serializeFrontmatter(data, body) {
|
||||
const keys = Object.keys(data);
|
||||
if (keys.length === 0) return body;
|
||||
const lines = ['---'];
|
||||
for (const k of keys) lines.push(`${k}: ${data[k]}`);
|
||||
lines.push('---');
|
||||
return lines.join('\n') + '\n' + body;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read `last_mapped_commit` from the frontmatter of a `.planning/codebase/*.md`
|
||||
* file. Returns null if the file does not exist or has no frontmatter.
|
||||
*/
|
||||
function readMappedCommit(filePath) {
|
||||
let content;
|
||||
try {
|
||||
content = fs.readFileSync(filePath, 'utf8');
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
const { data } = parseFrontmatter(content);
|
||||
const sha = data.last_mapped_commit;
|
||||
return typeof sha === 'string' && sha.length > 0 ? sha : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Upsert `last_mapped_commit` and `last_mapped_at` into the frontmatter of
|
||||
* the given file, preserving any other frontmatter keys and the body.
|
||||
*/
|
||||
function writeMappedCommit(filePath, commitSha, isoDate) {
|
||||
// Symmetric with readMappedCommit (which returns null on missing files):
|
||||
// tolerate a missing target by creating a minimal frontmatter-only file
|
||||
// rather than throwing ENOENT. This matters when a mapper produces a new
|
||||
// doc and the caller stamps it before any prior content existed.
|
||||
let content = '';
|
||||
try {
|
||||
content = fs.readFileSync(filePath, 'utf8');
|
||||
} catch (err) {
|
||||
if (err.code !== 'ENOENT') throw err;
|
||||
}
|
||||
const { data, body } = parseFrontmatter(content);
|
||||
data.last_mapped_commit = commitSha;
|
||||
if (isoDate) data.last_mapped_at = isoDate;
|
||||
fs.writeFileSync(filePath, serializeFrontmatter(data, body));
|
||||
}
|
||||
|
||||
// ─── Exports ─────────────────────────────────────────────────────────────────
|
||||
|
||||
module.exports = {
|
||||
DRIFT_CATEGORIES,
|
||||
classifyFile,
|
||||
detectDrift,
|
||||
chooseAffectedPaths,
|
||||
sanitizePaths,
|
||||
readMappedCommit,
|
||||
writeMappedCommit,
|
||||
// Exposed for the CLI layer to reuse the same parser.
|
||||
parseFrontmatter,
|
||||
};
|
||||
183
get-shit-done/bin/lib/gap-checker.cjs
Normal file
183
get-shit-done/bin/lib/gap-checker.cjs
Normal file
@@ -0,0 +1,183 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Post-planning gap analysis (#2493).
|
||||
*
|
||||
* Reads REQUIREMENTS.md (planning-root) and CONTEXT.md (per-phase) and compares
|
||||
* each REQ-ID and D-ID against the concatenated text of all PLAN.md files in
|
||||
* the phase directory. Emits a unified `Source | Item | Status` report.
|
||||
*
|
||||
* Gated on workflow.post_planning_gaps (default true). When false, returns
|
||||
* { enabled: false } and does not scan.
|
||||
*
|
||||
* Coverage detection uses word-boundary regex matching to avoid false positives
|
||||
* (REQ-1 must not match REQ-10).
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { planningPaths, planningDir, escapeRegex, output, error } = require('./core.cjs');
|
||||
const { parseDecisions } = require('./decisions.cjs');
|
||||
|
||||
/**
|
||||
* Parse REQ-IDs from REQUIREMENTS.md content.
|
||||
*
|
||||
* Supports both checkbox (`- [ ] **REQ-NN** ...`) and traceability table
|
||||
* (`| REQ-NN | ... |`) formats.
|
||||
*/
|
||||
function parseRequirements(reqMd) {
|
||||
if (!reqMd || typeof reqMd !== 'string') return [];
|
||||
const out = [];
|
||||
const seen = new Set();
|
||||
|
||||
const checkboxRe = /^\s*-\s*\[[x ]\]\s*\*\*(REQ-[A-Za-z0-9_-]+)\*\*\s*(.*)$/gm;
|
||||
let cm = checkboxRe.exec(reqMd);
|
||||
while (cm !== null) {
|
||||
const id = cm[1];
|
||||
if (!seen.has(id)) {
|
||||
seen.add(id);
|
||||
out.push({ id, text: (cm[2] || '').trim() });
|
||||
}
|
||||
cm = checkboxRe.exec(reqMd);
|
||||
}
|
||||
|
||||
const tableRe = /\|\s*(REQ-[A-Za-z0-9_-]+)\s*\|/g;
|
||||
let tm = tableRe.exec(reqMd);
|
||||
while (tm !== null) {
|
||||
const id = tm[1];
|
||||
if (!seen.has(id)) {
|
||||
seen.add(id);
|
||||
out.push({ id, text: '' });
|
||||
}
|
||||
tm = tableRe.exec(reqMd);
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
function detectCoverage(items, planText) {
|
||||
return items.map(it => {
|
||||
const re = new RegExp('\\b' + escapeRegex(it.id) + '\\b');
|
||||
return {
|
||||
source: it.source,
|
||||
item: it.id,
|
||||
status: re.test(planText) ? 'Covered' : 'Not covered',
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function naturalKey(s) {
|
||||
return String(s).replace(/(\d+)/g, (_, n) => n.padStart(8, '0'));
|
||||
}
|
||||
|
||||
function sortRows(rows) {
|
||||
const sourceOrder = { 'REQUIREMENTS.md': 0, 'CONTEXT.md': 1 };
|
||||
return rows.slice().sort((a, b) => {
|
||||
const so = (sourceOrder[a.source] ?? 99) - (sourceOrder[b.source] ?? 99);
|
||||
if (so !== 0) return so;
|
||||
return naturalKey(a.item).localeCompare(naturalKey(b.item));
|
||||
});
|
||||
}
|
||||
|
||||
function formatGapTable(rows) {
|
||||
if (rows.length === 0) {
|
||||
return '## Post-Planning Gap Analysis\n\nNo requirements or decisions to check.\n';
|
||||
}
|
||||
const header = '| Source | Item | Status |\n|--------|------|--------|';
|
||||
const body = rows.map(r => {
|
||||
const tick = r.status === 'Covered' ? '\u2713 Covered' : '\u2717 Not covered';
|
||||
return `| ${r.source} | ${r.item} | ${tick} |`;
|
||||
}).join('\n');
|
||||
return `## Post-Planning Gap Analysis\n\n${header}\n${body}\n`;
|
||||
}
|
||||
|
||||
function readGate(cwd) {
|
||||
const cfgPath = path.join(planningDir(cwd), 'config.json');
|
||||
try {
|
||||
const raw = JSON.parse(fs.readFileSync(cfgPath, 'utf-8'));
|
||||
if (raw && raw.workflow && typeof raw.workflow.post_planning_gaps === 'boolean') {
|
||||
return raw.workflow.post_planning_gaps;
|
||||
}
|
||||
} catch { /* fall through */ }
|
||||
return true;
|
||||
}
|
||||
|
||||
function runGapAnalysis(cwd, phaseDir) {
|
||||
if (!readGate(cwd)) {
|
||||
return {
|
||||
enabled: false,
|
||||
rows: [],
|
||||
table: '',
|
||||
summary: 'workflow.post_planning_gaps disabled — skipping post-planning gap analysis',
|
||||
counts: { total: 0, covered: 0, uncovered: 0 },
|
||||
};
|
||||
}
|
||||
|
||||
const absPhaseDir = path.isAbsolute(phaseDir) ? phaseDir : path.join(cwd, phaseDir);
|
||||
|
||||
const reqPath = planningPaths(cwd).requirements;
|
||||
const reqMd = fs.existsSync(reqPath) ? fs.readFileSync(reqPath, 'utf-8') : '';
|
||||
const reqItems = parseRequirements(reqMd).map(r => ({ ...r, source: 'REQUIREMENTS.md' }));
|
||||
|
||||
const ctxPath = path.join(absPhaseDir, 'CONTEXT.md');
|
||||
const ctxMd = fs.existsSync(ctxPath) ? fs.readFileSync(ctxPath, 'utf-8') : '';
|
||||
const dItems = parseDecisions(ctxMd).map(d => ({ ...d, source: 'CONTEXT.md' }));
|
||||
|
||||
const items = [...reqItems, ...dItems];
|
||||
|
||||
let planText = '';
|
||||
try {
|
||||
if (fs.existsSync(absPhaseDir)) {
|
||||
const files = fs.readdirSync(absPhaseDir).filter(f => /-PLAN\.md$/.test(f));
|
||||
planText = files.map(f => {
|
||||
try { return fs.readFileSync(path.join(absPhaseDir, f), 'utf-8'); }
|
||||
catch { return ''; }
|
||||
}).join('\n');
|
||||
}
|
||||
} catch { /* unreadable */ }
|
||||
|
||||
if (items.length === 0) {
|
||||
return {
|
||||
enabled: true,
|
||||
rows: [],
|
||||
table: '## Post-Planning Gap Analysis\n\nNo requirements or decisions to check.\n',
|
||||
summary: 'no requirements or decisions to check',
|
||||
counts: { total: 0, covered: 0, uncovered: 0 },
|
||||
};
|
||||
}
|
||||
|
||||
const rows = sortRows(detectCoverage(items, planText));
|
||||
const uncovered = rows.filter(r => r.status === 'Not covered').length;
|
||||
const covered = rows.length - uncovered;
|
||||
|
||||
const summary = uncovered === 0
|
||||
? `\u2713 All ${rows.length} items covered by plans`
|
||||
: `\u26A0 ${uncovered} of ${rows.length} items not covered by any plan`;
|
||||
|
||||
return {
|
||||
enabled: true,
|
||||
rows,
|
||||
table: formatGapTable(rows) + '\n' + summary + '\n',
|
||||
summary,
|
||||
counts: { total: rows.length, covered, uncovered },
|
||||
};
|
||||
}
|
||||
|
||||
function cmdGapAnalysis(cwd, args, raw) {
|
||||
const idx = args.indexOf('--phase-dir');
|
||||
if (idx === -1 || !args[idx + 1]) {
|
||||
error('Usage: gap-analysis --phase-dir <path-to-phase-directory>');
|
||||
}
|
||||
const phaseDir = args[idx + 1];
|
||||
const result = runGapAnalysis(cwd, phaseDir);
|
||||
output(result, raw, result.table || result.summary);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
parseRequirements,
|
||||
detectCoverage,
|
||||
formatGapTable,
|
||||
sortRows,
|
||||
runGapAnalysis,
|
||||
cmdGapAnalysis,
|
||||
};
|
||||
@@ -420,7 +420,7 @@ function buildPreview(gsd2Data, artifacts) {
|
||||
lines.push('');
|
||||
lines.push('Cannot migrate automatically:');
|
||||
lines.push(' - GSD-2 cost/token ledger (no v1 equivalent)');
|
||||
lines.push(' - GSD-2 database state (rebuilt from files on first /gsd-health)');
|
||||
lines.push(' - GSD-2 database state (rebuilt from files on first /gsd:health)');
|
||||
lines.push(' - VS Code extension state');
|
||||
|
||||
return lines.join('\n');
|
||||
|
||||
@@ -458,8 +458,11 @@ function cmdInitNewMilestone(cwd, raw) {
|
||||
|
||||
try {
|
||||
if (fs.existsSync(phasesDir)) {
|
||||
// Bug #2445: filter phase dirs to current milestone only so stale dirs
|
||||
// from a prior milestone that were not archived don't inflate the count.
|
||||
const isDirInMilestone = getMilestonePhaseFilter(cwd);
|
||||
phaseDirCount = fs.readdirSync(phasesDir, { withFileTypes: true })
|
||||
.filter(entry => entry.isDirectory())
|
||||
.filter(entry => entry.isDirectory() && isDirInMilestone(entry.name))
|
||||
.length;
|
||||
}
|
||||
} catch {}
|
||||
@@ -926,10 +929,10 @@ function cmdInitManager(cwd, raw) {
|
||||
|
||||
// Validate prerequisites
|
||||
if (!fs.existsSync(paths.roadmap)) {
|
||||
error('No ROADMAP.md found. Run /gsd-new-milestone first.');
|
||||
error('No ROADMAP.md found. Run /gsd:new-milestone first.');
|
||||
}
|
||||
if (!fs.existsSync(paths.state)) {
|
||||
error('No STATE.md found. Run /gsd-new-milestone first.');
|
||||
error('No STATE.md found. Run /gsd:new-milestone first.');
|
||||
}
|
||||
const rawContent = fs.readFileSync(paths.roadmap, 'utf-8');
|
||||
const content = extractCurrentMilestone(rawContent, cwd);
|
||||
@@ -1108,7 +1111,7 @@ function cmdInitManager(cwd, raw) {
|
||||
phase_name: phase.name,
|
||||
action: 'execute',
|
||||
reason: `${phase.plan_count} plans ready, dependencies met`,
|
||||
command: `/gsd-execute-phase ${phase.number}`,
|
||||
command: `/gsd:execute-phase ${phase.number}`,
|
||||
});
|
||||
} else if (phase.disk_status === 'discussed' || phase.disk_status === 'researched') {
|
||||
recommendedActions.push({
|
||||
@@ -1116,7 +1119,7 @@ function cmdInitManager(cwd, raw) {
|
||||
phase_name: phase.name,
|
||||
action: 'plan',
|
||||
reason: 'Context gathered, ready for planning',
|
||||
command: `/gsd-plan-phase ${phase.number}`,
|
||||
command: `/gsd:plan-phase ${phase.number}`,
|
||||
});
|
||||
} else if ((phase.disk_status === 'empty' || phase.disk_status === 'no_directory') && phase.is_next_to_discuss) {
|
||||
recommendedActions.push({
|
||||
@@ -1124,7 +1127,7 @@ function cmdInitManager(cwd, raw) {
|
||||
phase_name: phase.name,
|
||||
action: 'discuss',
|
||||
reason: 'Unblocked, ready to gather context',
|
||||
command: `/gsd-discuss-phase ${phase.number}`,
|
||||
command: `/gsd:discuss-phase ${phase.number}`,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -381,7 +381,7 @@ function cmdPhaseAdd(cwd, description, raw, customId) {
|
||||
|
||||
// Build phase entry
|
||||
const dependsOn = config.phase_naming === 'custom' ? '' : `\n**Depends on:** Phase ${typeof _newPhaseId === 'number' ? _newPhaseId - 1 : 'TBD'}`;
|
||||
const phaseEntry = `\n### Phase ${_newPhaseId}: ${description}\n\n**Goal:** [To be planned]\n**Requirements**: TBD${dependsOn}\n**Plans:** 0 plans\n\nPlans:\n- [ ] TBD (run /gsd-plan-phase ${_newPhaseId} to break down)\n`;
|
||||
const phaseEntry = `\n### Phase ${_newPhaseId}: ${description}\n\n**Goal:** [To be planned]\n**Requirements**: TBD${dependsOn}\n**Plans:** 0 plans\n\nPlans:\n- [ ] TBD (run /gsd:plan-phase ${_newPhaseId} to break down)\n`;
|
||||
|
||||
// Find insertion point: before last "---" or at end
|
||||
let updatedContent;
|
||||
@@ -458,7 +458,7 @@ function cmdPhaseAddBatch(cwd, descriptions, raw) {
|
||||
fs.mkdirSync(dirPath, { recursive: true });
|
||||
fs.writeFileSync(path.join(dirPath, '.gitkeep'), '');
|
||||
const dependsOn = config.phase_naming === 'custom' ? '' : `\n**Depends on:** Phase ${typeof newPhaseId === 'number' ? newPhaseId - 1 : 'TBD'}`;
|
||||
const phaseEntry = `\n### Phase ${newPhaseId}: ${description}\n\n**Goal:** [To be planned]\n**Requirements**: TBD${dependsOn}\n**Plans:** 0 plans\n\nPlans:\n- [ ] TBD (run /gsd-plan-phase ${newPhaseId} to break down)\n`;
|
||||
const phaseEntry = `\n### Phase ${newPhaseId}: ${description}\n\n**Goal:** [To be planned]\n**Requirements**: TBD${dependsOn}\n**Plans:** 0 plans\n\nPlans:\n- [ ] TBD (run /gsd:plan-phase ${newPhaseId} to break down)\n`;
|
||||
const lastSeparator = rawContent.lastIndexOf('\n---');
|
||||
rawContent = lastSeparator > 0
|
||||
? rawContent.slice(0, lastSeparator) + phaseEntry + rawContent.slice(lastSeparator)
|
||||
@@ -542,7 +542,7 @@ function cmdPhaseInsert(cwd, afterPhase, description, raw) {
|
||||
fs.writeFileSync(path.join(dirPath, '.gitkeep'), '');
|
||||
|
||||
// Build phase entry
|
||||
const phaseEntry = `\n### Phase ${_decimalPhase}: ${description} (INSERTED)\n\n**Goal:** [Urgent work - to be planned]\n**Requirements**: TBD\n**Depends on:** Phase ${afterPhase}\n**Plans:** 0 plans\n\nPlans:\n- [ ] TBD (run /gsd-plan-phase ${_decimalPhase} to break down)\n`;
|
||||
const phaseEntry = `\n### Phase ${_decimalPhase}: ${description} (INSERTED)\n\n**Goal:** [Urgent work - to be planned]\n**Requirements**: TBD\n**Depends on:** Phase ${afterPhase}\n**Plans:** 0 plans\n\nPlans:\n- [ ] TBD (run /gsd:plan-phase ${_decimalPhase} to break down)\n`;
|
||||
|
||||
// Insert after the target phase section
|
||||
const headerPattern = new RegExp(`(#{2,4}\\s*Phase\\s+0*${afterPhaseEscaped}:[^\\n]*\\n)`, 'i');
|
||||
@@ -625,7 +625,7 @@ function renameIntegerPhases(phasesDir, removedInt) {
|
||||
const m = dir.match(/^(\d+)([A-Z])?(?:\.(\d+))?-(.+)$/i);
|
||||
if (!m) return null;
|
||||
const dirInt = parseInt(m[1], 10);
|
||||
return dirInt > removedInt ? { dir, oldInt: dirInt, letter: m[2] ? m[2].toUpperCase() : '', decimal: m[3] ? parseInt(m[3], 10) : null, slug: m[4] } : null;
|
||||
return (dirInt > removedInt && dirInt < 999) ? { dir, oldInt: dirInt, letter: m[2] ? m[2].toUpperCase() : '', decimal: m[3] ? parseInt(m[3], 10) : null, slug: m[4] } : null;
|
||||
})
|
||||
.filter(Boolean)
|
||||
.sort((a, b) => a.oldInt !== b.oldInt ? b.oldInt - a.oldInt : (b.decimal || 0) - (a.decimal || 0));
|
||||
@@ -673,7 +673,7 @@ function updateRoadmapAfterPhaseRemoval(roadmapPath, targetPhase, isDecimal, rem
|
||||
const oldPad = oldStr.padStart(2, '0'), newPad = newStr.padStart(2, '0');
|
||||
content = content.replace(new RegExp(`(#{2,4}\\s*Phase\\s+)${oldStr}(\\s*:)`, 'gi'), `$1${newStr}$2`);
|
||||
content = content.replace(new RegExp(`(Phase\\s+)${oldStr}([:\\s])`, 'g'), `$1${newStr}$2`);
|
||||
content = content.replace(new RegExp(`${oldPad}-(\\d{2})`, 'g'), `${newPad}-$1`);
|
||||
content = content.replace(new RegExp(`(?<![0-9-])${oldPad}-(\\d{2})(?![0-9-])`, 'g'), `${newPad}-$1`);
|
||||
content = content.replace(new RegExp(`(\\|\\s*)${oldStr}\\.\\s`, 'g'), `$1${newStr}. `);
|
||||
content = content.replace(new RegExp(`(Depends on:\\*\\*\\s*Phase\\s+)${oldStr}\\b`, 'gi'), `$1${newStr}`);
|
||||
}
|
||||
@@ -828,7 +828,7 @@ function cmdPhaseComplete(cwd, phaseNum, raw) {
|
||||
// Update plan count in phase section.
|
||||
// Use direct .replace() rather than replaceInCurrentMilestone() so this
|
||||
// works when the current milestone section is itself inside a <details>
|
||||
// block (the standard /gsd-new-project layout). replaceInCurrentMilestone
|
||||
// block (the standard /gsd:new-project layout). replaceInCurrentMilestone
|
||||
// scopes to content after the last </details>, which misses content inside
|
||||
// the current milestone's own <details> wrapper (#2005).
|
||||
// The phase-scoped heading pattern is specific enough to avoid matching
|
||||
@@ -870,9 +870,10 @@ function cmdPhaseComplete(cwd, phaseNum, raw) {
|
||||
const sectionText = phaseSectionMatch ? phaseSectionMatch[1] : '';
|
||||
const reqMatch = sectionText.match(/\*\*Requirements:\*\*\s*([^\n]+)/i);
|
||||
|
||||
let reqContent = fs.readFileSync(reqPath, 'utf-8');
|
||||
|
||||
if (reqMatch) {
|
||||
const reqIds = reqMatch[1].replace(/[\[\]]/g, '').split(/[,\s]+/).map(r => r.trim()).filter(Boolean);
|
||||
let reqContent = fs.readFileSync(reqPath, 'utf-8');
|
||||
|
||||
for (const reqId of reqIds) {
|
||||
const reqEscaped = escapeRegex(reqId);
|
||||
@@ -887,10 +888,40 @@ function cmdPhaseComplete(cwd, phaseNum, raw) {
|
||||
'$1 Complete $2'
|
||||
);
|
||||
}
|
||||
|
||||
atomicWriteFileSync(reqPath, reqContent);
|
||||
requirementsUpdated = true;
|
||||
}
|
||||
|
||||
// Scan body for all **REQ-ID** patterns, warn about any missing from the Traceability table.
|
||||
// Always runs regardless of whether the roadmap has a Requirements: line.
|
||||
const bodyReqIds = [];
|
||||
const bodyReqPattern = /\*\*([A-Z][A-Z0-9]*-\d+)\*\*/g;
|
||||
let bodyMatch;
|
||||
while ((bodyMatch = bodyReqPattern.exec(reqContent)) !== null) {
|
||||
const id = bodyMatch[1];
|
||||
if (!bodyReqIds.includes(id)) bodyReqIds.push(id);
|
||||
}
|
||||
|
||||
// Collect REQ-IDs present in the Traceability section only, to avoid
|
||||
// picking up IDs from other tables in the document.
|
||||
const traceabilityHeadingMatch = reqContent.match(/^#{1,6}\s+Traceability\b/im);
|
||||
const traceabilitySection = traceabilityHeadingMatch
|
||||
? reqContent.slice(traceabilityHeadingMatch.index)
|
||||
: '';
|
||||
const tableReqIds = new Set();
|
||||
const tableRowPattern = /^\|\s*([A-Z][A-Z0-9]*-\d+)\s*\|/gm;
|
||||
let tableMatch;
|
||||
while ((tableMatch = tableRowPattern.exec(traceabilitySection)) !== null) {
|
||||
tableReqIds.add(tableMatch[1]);
|
||||
}
|
||||
|
||||
const unregistered = bodyReqIds.filter(id => !tableReqIds.has(id));
|
||||
if (unregistered.length > 0) {
|
||||
warnings.push(
|
||||
`REQUIREMENTS.md: ${unregistered.length} REQ-ID(s) found in body but missing from Traceability table: ${unregistered.join(', ')} — add them manually to keep traceability in sync`
|
||||
);
|
||||
}
|
||||
|
||||
atomicWriteFileSync(reqPath, reqContent);
|
||||
requirementsUpdated = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -173,7 +173,7 @@ const CLAUDE_INSTRUCTIONS = {
|
||||
};
|
||||
|
||||
const CLAUDE_MD_FALLBACKS = {
|
||||
project: 'Project not yet initialized. Run /gsd-new-project to set up.',
|
||||
project: 'Project not yet initialized. Run /gsd:new-project to set up.',
|
||||
stack: 'Technology stack not yet documented. Will populate after codebase mapping or first phase.',
|
||||
conventions: 'Conventions not yet established. Will populate as patterns emerge during development.',
|
||||
architecture: 'Architecture not yet mapped. Follow existing patterns found in the codebase.',
|
||||
@@ -187,9 +187,9 @@ const CLAUDE_MD_WORKFLOW_ENFORCEMENT = [
|
||||
'Before using Edit, Write, or other file-changing tools, start work through a GSD command so planning artifacts and execution context stay in sync.',
|
||||
'',
|
||||
'Use these entry points:',
|
||||
'- `/gsd-quick` for small fixes, doc updates, and ad-hoc tasks',
|
||||
'- `/gsd-debug` for investigation and bug fixing',
|
||||
'- `/gsd-execute-phase` for planned phase work',
|
||||
'- `/gsd:quick` for small fixes, doc updates, and ad-hoc tasks',
|
||||
'- `/gsd:debug` for investigation and bug fixing',
|
||||
'- `/gsd:execute-phase` for planned phase work',
|
||||
'',
|
||||
'Do not make direct repo edits outside a GSD workflow unless the user explicitly asks to bypass it.',
|
||||
].join('\n');
|
||||
@@ -198,7 +198,7 @@ const CLAUDE_MD_PROFILE_PLACEHOLDER = [
|
||||
'<!-- GSD:profile-start -->',
|
||||
'## Developer Profile',
|
||||
'',
|
||||
'> Profile not yet configured. Run `/gsd-profile-user` to generate your developer profile.',
|
||||
'> Profile not yet configured. Run `/gsd:profile-user` to generate your developer profile.',
|
||||
'> This section is managed by `generate-claude-profile` -- do not edit manually.',
|
||||
'<!-- GSD:profile-end -->',
|
||||
].join('\n');
|
||||
@@ -285,7 +285,7 @@ function generateProjectSection(cwd) {
|
||||
const projectPath = path.join(cwd, '.planning', 'PROJECT.md');
|
||||
const content = safeReadFile(projectPath);
|
||||
if (!content) {
|
||||
return { content: CLAUDE_MD_FALLBACKS.project, source: 'PROJECT.md', hasFallback: true };
|
||||
return { content: CLAUDE_MD_FALLBACKS.project, source: 'PROJECT.md', linkPath: null, hasFallback: true };
|
||||
}
|
||||
const parts = [];
|
||||
const h1Match = content.match(/^# (.+)$/m);
|
||||
@@ -306,9 +306,9 @@ function generateProjectSection(cwd) {
|
||||
if (body) parts.push(`### Constraints\n\n${body}`);
|
||||
}
|
||||
if (parts.length === 0) {
|
||||
return { content: CLAUDE_MD_FALLBACKS.project, source: 'PROJECT.md', hasFallback: true };
|
||||
return { content: CLAUDE_MD_FALLBACKS.project, source: 'PROJECT.md', linkPath: null, hasFallback: true };
|
||||
}
|
||||
return { content: parts.join('\n\n'), source: 'PROJECT.md', hasFallback: false };
|
||||
return { content: parts.join('\n\n'), source: 'PROJECT.md', linkPath: '.planning/PROJECT.md', hasFallback: false };
|
||||
}
|
||||
|
||||
function generateStackSection(cwd) {
|
||||
@@ -316,12 +316,14 @@ function generateStackSection(cwd) {
|
||||
const researchPath = path.join(cwd, '.planning', 'research', 'STACK.md');
|
||||
let content = safeReadFile(codebasePath);
|
||||
let source = 'codebase/STACK.md';
|
||||
let linkPath = '.planning/codebase/STACK.md';
|
||||
if (!content) {
|
||||
content = safeReadFile(researchPath);
|
||||
source = 'research/STACK.md';
|
||||
linkPath = '.planning/research/STACK.md';
|
||||
}
|
||||
if (!content) {
|
||||
return { content: CLAUDE_MD_FALLBACKS.stack, source: 'STACK.md', hasFallback: true };
|
||||
return { content: CLAUDE_MD_FALLBACKS.stack, source: 'STACK.md', linkPath: null, hasFallback: true };
|
||||
}
|
||||
const lines = content.split('\n');
|
||||
const summaryLines = [];
|
||||
@@ -336,14 +338,14 @@ function generateStackSection(cwd) {
|
||||
if (line.startsWith('- ') || line.startsWith('* ')) summaryLines.push(line);
|
||||
}
|
||||
const summary = summaryLines.length > 0 ? summaryLines.join('\n') : content.trim();
|
||||
return { content: summary, source, hasFallback: false };
|
||||
return { content: summary, source, linkPath, hasFallback: false };
|
||||
}
|
||||
|
||||
function generateConventionsSection(cwd) {
|
||||
const conventionsPath = path.join(cwd, '.planning', 'codebase', 'CONVENTIONS.md');
|
||||
const content = safeReadFile(conventionsPath);
|
||||
if (!content) {
|
||||
return { content: CLAUDE_MD_FALLBACKS.conventions, source: 'CONVENTIONS.md', hasFallback: true };
|
||||
return { content: CLAUDE_MD_FALLBACKS.conventions, source: 'CONVENTIONS.md', linkPath: null, hasFallback: true };
|
||||
}
|
||||
const lines = content.split('\n');
|
||||
const summaryLines = [];
|
||||
@@ -352,14 +354,14 @@ function generateConventionsSection(cwd) {
|
||||
if (line.startsWith('- ') || line.startsWith('* ') || line.startsWith('|')) summaryLines.push(line);
|
||||
}
|
||||
const summary = summaryLines.length > 0 ? summaryLines.join('\n') : content.trim();
|
||||
return { content: summary, source: 'CONVENTIONS.md', hasFallback: false };
|
||||
return { content: summary, source: 'CONVENTIONS.md', linkPath: '.planning/codebase/CONVENTIONS.md', hasFallback: false };
|
||||
}
|
||||
|
||||
function generateArchitectureSection(cwd) {
|
||||
const architecturePath = path.join(cwd, '.planning', 'codebase', 'ARCHITECTURE.md');
|
||||
const content = safeReadFile(architecturePath);
|
||||
if (!content) {
|
||||
return { content: CLAUDE_MD_FALLBACKS.architecture, source: 'ARCHITECTURE.md', hasFallback: true };
|
||||
return { content: CLAUDE_MD_FALLBACKS.architecture, source: 'ARCHITECTURE.md', linkPath: null, hasFallback: true };
|
||||
}
|
||||
const lines = content.split('\n');
|
||||
const summaryLines = [];
|
||||
@@ -368,13 +370,14 @@ function generateArchitectureSection(cwd) {
|
||||
if (line.startsWith('- ') || line.startsWith('* ') || line.startsWith('|') || line.startsWith('```')) summaryLines.push(line);
|
||||
}
|
||||
const summary = summaryLines.length > 0 ? summaryLines.join('\n') : content.trim();
|
||||
return { content: summary, source: 'ARCHITECTURE.md', hasFallback: false };
|
||||
return { content: summary, source: 'ARCHITECTURE.md', linkPath: '.planning/codebase/ARCHITECTURE.md', hasFallback: false };
|
||||
}
|
||||
|
||||
function generateWorkflowSection() {
|
||||
return {
|
||||
content: CLAUDE_MD_WORKFLOW_ENFORCEMENT,
|
||||
source: 'GSD defaults',
|
||||
linkPath: null,
|
||||
hasFallback: false,
|
||||
};
|
||||
}
|
||||
@@ -765,7 +768,7 @@ function cmdGenerateDevPreferences(cwd, options, raw) {
|
||||
|
||||
let stackBlock;
|
||||
if (analysis.data_source === 'questionnaire') {
|
||||
stackBlock = 'Stack preferences not available (questionnaire-only profile). Run `/gsd-profile-user --refresh` with session data to populate.';
|
||||
stackBlock = 'Stack preferences not available (questionnaire-only profile). Run `/gsd:profile-user --refresh` with session data to populate.';
|
||||
} else if (options.stack) {
|
||||
stackBlock = options.stack;
|
||||
} else {
|
||||
@@ -851,7 +854,7 @@ function cmdGenerateClaudeProfile(cwd, options, raw) {
|
||||
'<!-- GSD:profile-start -->',
|
||||
'## Developer Profile',
|
||||
'',
|
||||
`> Generated by GSD from ${dataSource}. Run \`/gsd-profile-user --refresh\` to update.`,
|
||||
`> Generated by GSD from ${dataSource}. Run \`/gsd:profile-user --refresh\` to update.`,
|
||||
'',
|
||||
'| Dimension | Rating | Confidence |',
|
||||
'|-----------|--------|------------|',
|
||||
@@ -948,19 +951,35 @@ function cmdGenerateClaudeMd(cwd, options, raw) {
|
||||
}
|
||||
}
|
||||
|
||||
let assemblyConfig = {};
|
||||
let configClaudeMdPath = './CLAUDE.md';
|
||||
try {
|
||||
const config = loadConfig(cwd);
|
||||
if (config.claude_md_path) configClaudeMdPath = config.claude_md_path;
|
||||
if (config.claude_md_assembly) assemblyConfig = config.claude_md_assembly;
|
||||
} catch { /* use default */ }
|
||||
|
||||
let outputPath = options.output;
|
||||
if (!outputPath) {
|
||||
// Read claude_md_path from config, default to ./CLAUDE.md
|
||||
let configClaudeMdPath = './CLAUDE.md';
|
||||
try {
|
||||
const config = loadConfig(cwd);
|
||||
if (config.claude_md_path) configClaudeMdPath = config.claude_md_path;
|
||||
} catch { /* use default */ }
|
||||
outputPath = path.isAbsolute(configClaudeMdPath) ? configClaudeMdPath : path.join(cwd, configClaudeMdPath);
|
||||
} else if (!path.isAbsolute(outputPath)) {
|
||||
outputPath = path.join(cwd, outputPath);
|
||||
}
|
||||
|
||||
const globalAssemblyMode = assemblyConfig.mode || 'embed';
|
||||
const blockModes = assemblyConfig.blocks || {};
|
||||
|
||||
// Return the assembled content for a section, respecting link vs embed mode.
|
||||
// "link" mode writes `@<linkPath>` when the generator has a real source file.
|
||||
// Falls back to "embed" for sections without a linkable source (workflow, fallbacks).
|
||||
function buildSectionContent(name, gen, heading) {
|
||||
const effectiveMode = blockModes[name] || globalAssemblyMode;
|
||||
if (effectiveMode === 'link' && gen.linkPath && !gen.hasFallback) {
|
||||
return buildSection(name, gen.source, `${heading}\n\n@${gen.linkPath}`);
|
||||
}
|
||||
return buildSection(name, gen.source, `${heading}\n\n${gen.content}`);
|
||||
}
|
||||
|
||||
let existingContent = safeReadFile(outputPath);
|
||||
let action;
|
||||
|
||||
@@ -969,8 +988,7 @@ function cmdGenerateClaudeMd(cwd, options, raw) {
|
||||
for (const name of MANAGED_SECTIONS) {
|
||||
const gen = generated[name];
|
||||
const heading = sectionHeadings[name];
|
||||
const body = `${heading}\n\n${gen.content}`;
|
||||
sections.push(buildSection(name, gen.source, body));
|
||||
sections.push(buildSectionContent(name, gen, heading));
|
||||
}
|
||||
sections.push('');
|
||||
sections.push(CLAUDE_MD_PROFILE_PLACEHOLDER);
|
||||
@@ -985,13 +1003,15 @@ function cmdGenerateClaudeMd(cwd, options, raw) {
|
||||
for (const name of MANAGED_SECTIONS) {
|
||||
const gen = generated[name];
|
||||
const heading = sectionHeadings[name];
|
||||
const body = `${heading}\n\n${gen.content}`;
|
||||
const fullSection = buildSection(name, gen.source, body);
|
||||
const fullSection = buildSectionContent(name, gen, heading);
|
||||
const hasMarkers = fileContent.indexOf(`<!-- GSD:${name}-start`) !== -1;
|
||||
|
||||
if (hasMarkers) {
|
||||
if (options.auto) {
|
||||
const expectedBody = `${heading}\n\n${gen.content}`;
|
||||
const effectiveMode = blockModes[name] || globalAssemblyMode;
|
||||
const expectedBody = (effectiveMode === 'link' && gen.linkPath && !gen.hasFallback)
|
||||
? `${heading}\n\n@${gen.linkPath}`
|
||||
: `${heading}\n\n${gen.content}`;
|
||||
if (detectManualEdit(fileContent, name, expectedBody)) {
|
||||
sectionsSkipped.push(name);
|
||||
const genIdx = sectionsGenerated.indexOf(name);
|
||||
@@ -1033,7 +1053,7 @@ function cmdGenerateClaudeMd(cwd, options, raw) {
|
||||
let message = `Generated ${genCount}/${totalManaged} sections.`;
|
||||
if (sectionsFallback.length > 0) message += ` Fallback: ${sectionsFallback.join(', ')}.`;
|
||||
if (sectionsSkipped.length > 0) message += ` Skipped (manually edited): ${sectionsSkipped.join(', ')}.`;
|
||||
if (profileStatus === 'placeholder_added') message += ' Run /gsd-profile-user to unlock Developer Profile.';
|
||||
if (profileStatus === 'placeholder_added') message += ' Run /gsd:profile-user to unlock Developer Profile.';
|
||||
|
||||
const result = {
|
||||
claude_md_path: outputPath,
|
||||
|
||||
@@ -353,8 +353,171 @@ function cmdRoadmapUpdatePlanProgress(cwd, phaseNum, raw) {
|
||||
}, raw, `${summaryCount}/${planCount} ${status}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Annotate the ROADMAP.md plan list for a phase with wave dependency notes
|
||||
* and a cross-cutting constraints subsection derived from PLAN frontmatter.
|
||||
*
|
||||
* Wave dependency notes: "Wave 2 — blocked on Wave 1 completion" inserted as
|
||||
* bold headers before each wave group in the plan checklist.
|
||||
*
|
||||
* Cross-cutting constraints: must_haves.truths strings that appear in 2+ plans
|
||||
* are surfaced in a "Cross-cutting constraints" subsection below the plan list.
|
||||
*
|
||||
* The operation is idempotent: if wave headers already exist in the section
|
||||
* the function returns without modifying the file.
|
||||
*/
|
||||
function cmdRoadmapAnnotateDependencies(cwd, phaseNum, raw) {
|
||||
if (!phaseNum) {
|
||||
error('phase number required for roadmap annotate-dependencies');
|
||||
}
|
||||
|
||||
const roadmapPath = planningPaths(cwd).roadmap;
|
||||
if (!fs.existsSync(roadmapPath)) {
|
||||
output({ updated: false, reason: 'ROADMAP.md not found' }, raw, 'no roadmap');
|
||||
return;
|
||||
}
|
||||
|
||||
const phaseInfo = findPhaseInternal(cwd, phaseNum);
|
||||
if (!phaseInfo || phaseInfo.plans.length === 0) {
|
||||
output({ updated: false, reason: 'no plans found for phase', phase: phaseNum }, raw, 'no plans');
|
||||
return;
|
||||
}
|
||||
|
||||
const { extractFrontmatter, parseMustHavesBlock } = require('./frontmatter.cjs');
|
||||
|
||||
// Read each PLAN.md and extract wave + must_haves.truths
|
||||
const planData = [];
|
||||
for (const planFile of phaseInfo.plans) {
|
||||
const planPath = path.join(path.resolve(cwd, phaseInfo.directory), planFile);
|
||||
try {
|
||||
const content = fs.readFileSync(planPath, 'utf-8');
|
||||
const fm = extractFrontmatter(content);
|
||||
const wave = parseInt(fm.wave, 10) || 1;
|
||||
const planId = planFile.replace(/-PLAN\.md$/i, '').replace(/PLAN\.md$/i, '');
|
||||
const truths = parseMustHavesBlock(content, 'truths') || [];
|
||||
planData.push({ planFile, planId, wave, truths });
|
||||
} catch { /* skip unreadable plans */ }
|
||||
}
|
||||
|
||||
if (planData.length === 0) {
|
||||
output({ updated: false, reason: 'could not read plan frontmatter' }, raw, 'no frontmatter');
|
||||
return;
|
||||
}
|
||||
|
||||
// Group plans by wave (sorted)
|
||||
const waveGroups = new Map();
|
||||
for (const p of planData) {
|
||||
if (!waveGroups.has(p.wave)) waveGroups.set(p.wave, []);
|
||||
waveGroups.get(p.wave).push(p);
|
||||
}
|
||||
const waves = [...waveGroups.keys()].sort((a, b) => a - b);
|
||||
|
||||
// Find cross-cutting truths: appear in 2+ plans (de-duplicated, case-insensitive)
|
||||
const truthCounts = new Map();
|
||||
for (const { truths } of planData) {
|
||||
const seen = new Set();
|
||||
for (const t of truths) {
|
||||
const key = t.trim().toLowerCase();
|
||||
if (!key || seen.has(key)) continue;
|
||||
seen.add(key);
|
||||
truthCounts.set(key, (truthCounts.get(key) || { count: 0, text: t.trim() }));
|
||||
truthCounts.get(key).count++;
|
||||
}
|
||||
}
|
||||
const crossCuttingTruths = [...truthCounts.values()]
|
||||
.filter(v => v.count >= 2)
|
||||
.map(v => v.text);
|
||||
|
||||
// Patch ROADMAP.md
|
||||
let updated = false;
|
||||
withPlanningLock(cwd, () => {
|
||||
let content = fs.readFileSync(roadmapPath, 'utf-8');
|
||||
|
||||
// Find the phase section
|
||||
const phaseEscaped = escapeRegex(phaseNum);
|
||||
const phaseHeaderPattern = new RegExp(`(#{2,4}\\s*Phase\\s+${phaseEscaped}:[^\\n]*)`, 'i');
|
||||
const phaseMatch = content.match(phaseHeaderPattern);
|
||||
if (!phaseMatch) return;
|
||||
|
||||
const phaseStart = phaseMatch.index;
|
||||
const restAfterHeader = content.slice(phaseStart);
|
||||
const nextPhaseOffset = restAfterHeader.slice(1).search(/\n#{2,4}\s+Phase\s+\d/i);
|
||||
const phaseEnd = nextPhaseOffset >= 0 ? phaseStart + 1 + nextPhaseOffset : content.length;
|
||||
const phaseSection = content.slice(phaseStart, phaseEnd);
|
||||
|
||||
// Idempotency: skip if annotation markers already present
|
||||
if (
|
||||
/\*\*Wave\s+\d+/i.test(phaseSection) ||
|
||||
/\*\*Cross-cutting constraints:\*\*/i.test(phaseSection)
|
||||
) return;
|
||||
|
||||
// Find the Plans: section within the phase section
|
||||
const plansBlockMatch = phaseSection.match(/(Plans:\s*\n)((?:\s*-\s*\[[ x]\][^\n]*\n?)*)/i);
|
||||
if (!plansBlockMatch) return;
|
||||
|
||||
const plansHeader = plansBlockMatch[1];
|
||||
const existingList = plansBlockMatch[2];
|
||||
const listLines = existingList.split('\n').filter(l => /^\s*-\s*\[/.test(l));
|
||||
|
||||
if (listLines.length === 0) return;
|
||||
|
||||
// Build wave-annotated plan list
|
||||
const linesByWave = new Map();
|
||||
for (const line of listLines) {
|
||||
// Match plan ID from line: "- [ ] 01-01-PLAN.md — ..." or "- [ ] 01-01: ..."
|
||||
const idMatch = line.match(/\[\s*[x ]\s*\]\s*([\w-]+?)(?:-PLAN\.md|\.md|:|\s—)/i);
|
||||
const planId = idMatch ? idMatch[1] : null;
|
||||
const planEntry = planId ? planData.find(p => p.planId === planId) : null;
|
||||
const wave = planEntry ? planEntry.wave : 1;
|
||||
if (!linesByWave.has(wave)) linesByWave.set(wave, []);
|
||||
linesByWave.get(wave).push(line);
|
||||
}
|
||||
|
||||
const annotatedLines = [];
|
||||
const sortedWaves = [...linesByWave.keys()].sort((a, b) => a - b);
|
||||
for (let i = 0; i < sortedWaves.length; i++) {
|
||||
const w = sortedWaves[i];
|
||||
const waveLines = linesByWave.get(w);
|
||||
if (sortedWaves.length > 1) {
|
||||
const dep = i > 0 ? ` *(blocked on Wave ${sortedWaves[i - 1]} completion)*` : '';
|
||||
annotatedLines.push(`**Wave ${w}**${dep}`);
|
||||
}
|
||||
annotatedLines.push(...waveLines);
|
||||
if (i < sortedWaves.length - 1) annotatedLines.push('');
|
||||
}
|
||||
|
||||
// Append cross-cutting constraints subsection if any found
|
||||
if (crossCuttingTruths.length > 0) {
|
||||
annotatedLines.push('');
|
||||
annotatedLines.push('**Cross-cutting constraints:**');
|
||||
for (const t of crossCuttingTruths) {
|
||||
annotatedLines.push(`- ${t}`);
|
||||
}
|
||||
}
|
||||
|
||||
const newListBlock = annotatedLines.join('\n') + '\n';
|
||||
const newPhaseSection = phaseSection.replace(
|
||||
plansBlockMatch[0],
|
||||
plansHeader + newListBlock
|
||||
);
|
||||
|
||||
const nextContent = content.slice(0, phaseStart) + newPhaseSection + content.slice(phaseEnd);
|
||||
if (nextContent === content) return;
|
||||
atomicWriteFileSync(roadmapPath, nextContent);
|
||||
updated = true;
|
||||
});
|
||||
|
||||
output({
|
||||
updated,
|
||||
phase: phaseNum,
|
||||
waves: waves.length,
|
||||
cross_cutting_constraints: crossCuttingTruths.length,
|
||||
}, raw, updated ? `annotated ${waves.length} wave(s), ${crossCuttingTruths.length} constraint(s)` : 'skipped (already annotated or no plan list)');
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
cmdRoadmapGetPhase,
|
||||
cmdRoadmapAnalyze,
|
||||
cmdRoadmapUpdatePlanProgress,
|
||||
cmdRoadmapAnnotateDependencies,
|
||||
};
|
||||
|
||||
33
get-shit-done/bin/lib/secrets.cjs
Normal file
33
get-shit-done/bin/lib/secrets.cjs
Normal file
@@ -0,0 +1,33 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Secrets handling — masking convention for API keys and other
|
||||
* credentials managed via /gsd:settings-integrations.
|
||||
*
|
||||
* Convention: strings 8+ chars long render as `****<last-4>`; shorter
|
||||
* strings render as `****` with no tail (to avoid leaking a meaningful
|
||||
* fraction of a short secret). null/empty renders as `(unset)`.
|
||||
*
|
||||
* Keys considered sensitive are listed in SECRET_CONFIG_KEYS and matched
|
||||
* at the exact key-path level. The list is intentionally narrow — these
|
||||
* are the fields documented as secrets in docs/CONFIGURATION.md.
|
||||
*/
|
||||
|
||||
const SECRET_CONFIG_KEYS = new Set([
|
||||
'brave_search',
|
||||
'firecrawl',
|
||||
'exa_search',
|
||||
]);
|
||||
|
||||
function isSecretKey(keyPath) {
|
||||
return SECRET_CONFIG_KEYS.has(keyPath);
|
||||
}
|
||||
|
||||
function maskSecret(value) {
|
||||
if (value === null || value === undefined || value === '') return '(unset)';
|
||||
const s = String(value);
|
||||
if (s.length < 8) return '****';
|
||||
return '****' + s.slice(-4);
|
||||
}
|
||||
|
||||
module.exports = { SECRET_CONFIG_KEYS, isSecretKey, maskSecret };
|
||||
@@ -245,14 +245,15 @@ function sanitizeForPrompt(text) {
|
||||
// Neutralize XML/HTML tags that mimic system boundaries
|
||||
// Replace < > with full-width equivalents to prevent tag interpretation
|
||||
// Note: <instructions> is excluded — GSD uses it as legitimate prompt structure
|
||||
sanitized = sanitized.replace(/<(\/?)(?:system|assistant|human)>/gi,
|
||||
// Matches system|assistant|human|user with optional whitespace before the closing >
|
||||
sanitized = sanitized.replace(/<(\/?)\s*(?:system|assistant|human|user)\s*>/gi,
|
||||
(_, slash) => `<${slash || ''}system-text>`);
|
||||
|
||||
// Neutralize [SYSTEM] / [INST] / [/INST] markers
|
||||
// Neutralize [SYSTEM] / [INST] / [/INST] markers — both opening and closing variants
|
||||
sanitized = sanitized.replace(/\[(\/?)(SYSTEM|INST)\]/gi, (_, slash, tag) => `[${slash}${tag.toUpperCase()}-TEXT]`);
|
||||
|
||||
// Neutralize <<SYS>> markers
|
||||
sanitized = sanitized.replace(/<<\s*SYS\s*>>/gi, '«SYS-TEXT»');
|
||||
// Neutralize <<SYS>> and <</SYS>> markers (Llama-style delimiters)
|
||||
sanitized = sanitized.replace(/<<\/?\s*SYS\s*>>/gi, '«SYS-TEXT»');
|
||||
|
||||
return sanitized;
|
||||
}
|
||||
|
||||
@@ -29,12 +29,13 @@ process.on('exit', () => {
|
||||
|
||||
// Shared helper: extract a field value from STATE.md content.
|
||||
// Supports both **Field:** bold and plain Field: format.
|
||||
// Horizontal whitespace only after ':' so YAML keys like `progress:` do not match as `Progress:` (parity with sdk/helpers stateExtractField).
|
||||
function stateExtractField(content, fieldName) {
|
||||
const escaped = escapeRegex(fieldName);
|
||||
const boldPattern = new RegExp(`\\*\\*${escaped}:\\*\\*\\s*(.+)`, 'i');
|
||||
const boldPattern = new RegExp(`\\*\\*${escaped}:\\*\\*[ \\t]*(.+)`, 'i');
|
||||
const boldMatch = content.match(boldPattern);
|
||||
if (boldMatch) return boldMatch[1].trim();
|
||||
const plainPattern = new RegExp(`^${escaped}:\\s*(.+)`, 'im');
|
||||
const plainPattern = new RegExp(`^${escaped}:[ \\t]*(.+)`, 'im');
|
||||
const plainMatch = content.match(plainPattern);
|
||||
return plainMatch ? plainMatch[1].trim() : null;
|
||||
}
|
||||
@@ -720,7 +721,13 @@ function buildStateFrontmatter(bodyContent, cwd) {
|
||||
const status = stateExtractField(bodyContent, 'Status');
|
||||
const progressRaw = stateExtractField(bodyContent, 'Progress');
|
||||
const lastActivity = stateExtractField(bodyContent, 'Last Activity');
|
||||
const stoppedAt = stateExtractField(bodyContent, 'Stopped At') || stateExtractField(bodyContent, 'Stopped at');
|
||||
// Bug #2444: scope Stopped At extraction to the ## Session section so that
|
||||
// historical "Stopped at:" prose elsewhere in the body (e.g. in a
|
||||
// Session Continuity Archive section) never overwrites the current value.
|
||||
// Fall back to full-body search only when no ## Session section exists.
|
||||
const sessionSectionMatch = bodyContent.match(/##\s*Session\s*\n([\s\S]*?)(?=\n##|$)/i);
|
||||
const sessionBodyScope = sessionSectionMatch ? sessionSectionMatch[1] : bodyContent;
|
||||
const stoppedAt = stateExtractField(sessionBodyScope, 'Stopped At') || stateExtractField(sessionBodyScope, 'Stopped at');
|
||||
const pausedAt = stateExtractField(bodyContent, 'Paused At');
|
||||
|
||||
let milestone = null;
|
||||
@@ -747,9 +754,33 @@ function buildStateFrontmatter(bodyContent, cwd) {
|
||||
let cached = _diskScanCache.get(cwd);
|
||||
if (!cached) {
|
||||
const isDirInMilestone = getMilestonePhaseFilter(cwd);
|
||||
const phaseDirs = fs.readdirSync(phasesDir, { withFileTypes: true })
|
||||
const allMatchingDirs = fs.readdirSync(phasesDir, { withFileTypes: true })
|
||||
.filter(e => e.isDirectory()).map(e => e.name)
|
||||
.filter(isDirInMilestone);
|
||||
|
||||
// Bug #2445: when stale phase dirs from a prior milestone remain in
|
||||
// .planning/phases/ alongside new dirs with the same phase number,
|
||||
// de-duplicate by normalized phase number keeping the most recently
|
||||
// modified dir. This prevents double-counting (e.g. two "Phase 1" dirs).
|
||||
const seenPhaseNums = new Map(); // normalizedNum -> dirName
|
||||
for (const dir of allMatchingDirs) {
|
||||
const m = dir.match(/^0*(\d+[A-Za-z]?(?:\.\d+)*)/);
|
||||
const key = m ? m[1].toLowerCase() : dir;
|
||||
if (!seenPhaseNums.has(key)) {
|
||||
seenPhaseNums.set(key, dir);
|
||||
} else {
|
||||
// Keep the dir that is newer on disk (more likely current milestone)
|
||||
try {
|
||||
const existing = path.join(phasesDir, seenPhaseNums.get(key));
|
||||
const candidate = path.join(phasesDir, dir);
|
||||
if (fs.statSync(candidate).mtimeMs > fs.statSync(existing).mtimeMs) {
|
||||
seenPhaseNums.set(key, dir);
|
||||
}
|
||||
} catch { /* keep existing on stat error */ }
|
||||
}
|
||||
}
|
||||
const phaseDirs = [...seenPhaseNums.values()];
|
||||
|
||||
let diskTotalPlans = 0;
|
||||
let diskTotalSummaries = 0;
|
||||
let diskCompletedPhases = 0;
|
||||
|
||||
@@ -225,6 +225,11 @@ function parseVerificationItems(content, status) {
|
||||
const numberedMatch = line.match(/^(\d+)\.\s+(.+)/);
|
||||
|
||||
if (tableMatch) {
|
||||
// Skip rows that already have a passing result (PASS, pass, resolved, etc.)
|
||||
const rowRemainder = line.slice(tableMatch.index + tableMatch[0].length);
|
||||
const cellValues = rowRemainder.split('|').map(c => c.trim());
|
||||
const hasPassResult = cellValues.some(c => /^pass$/i.test(c) || /^resolved$/i.test(c));
|
||||
if (hasPassResult) continue;
|
||||
items.push({
|
||||
test: parseInt(tableMatch[1], 10),
|
||||
name: tableMatch[2].trim(),
|
||||
|
||||
@@ -555,7 +555,7 @@ function cmdValidateHealth(cwd, options, raw) {
|
||||
|
||||
// ─── Check 1: .planning/ exists ───────────────────────────────────────────
|
||||
if (!fs.existsSync(planBase)) {
|
||||
addIssue('error', 'E001', '.planning/ directory not found', 'Run /gsd-new-project to initialize');
|
||||
addIssue('error', 'E001', '.planning/ directory not found', 'Run /gsd:new-project to initialize');
|
||||
output({
|
||||
status: 'broken',
|
||||
errors,
|
||||
@@ -568,7 +568,7 @@ function cmdValidateHealth(cwd, options, raw) {
|
||||
|
||||
// ─── Check 2: PROJECT.md exists and has required sections ─────────────────
|
||||
if (!fs.existsSync(projectPath)) {
|
||||
addIssue('error', 'E002', 'PROJECT.md not found', 'Run /gsd-new-project to create');
|
||||
addIssue('error', 'E002', 'PROJECT.md not found', 'Run /gsd:new-project to create');
|
||||
} else {
|
||||
const content = fs.readFileSync(projectPath, 'utf-8');
|
||||
const requiredSections = ['## What This Is', '## Core Value', '## Requirements'];
|
||||
@@ -581,12 +581,12 @@ function cmdValidateHealth(cwd, options, raw) {
|
||||
|
||||
// ─── Check 3: ROADMAP.md exists ───────────────────────────────────────────
|
||||
if (!fs.existsSync(roadmapPath)) {
|
||||
addIssue('error', 'E003', 'ROADMAP.md not found', 'Run /gsd-new-milestone to create roadmap');
|
||||
addIssue('error', 'E003', 'ROADMAP.md not found', 'Run /gsd:new-milestone to create roadmap');
|
||||
}
|
||||
|
||||
// ─── Check 4: STATE.md exists and references valid phases ─────────────────
|
||||
if (!fs.existsSync(statePath)) {
|
||||
addIssue('error', 'E004', 'STATE.md not found', 'Run /gsd-health --repair to regenerate', true);
|
||||
addIssue('error', 'E004', 'STATE.md not found', 'Run /gsd:health --repair to regenerate', true);
|
||||
repairs.push('regenerateState');
|
||||
} else {
|
||||
const stateContent = fs.readFileSync(statePath, 'utf-8');
|
||||
@@ -613,7 +613,7 @@ function cmdValidateHealth(cwd, options, raw) {
|
||||
'warning',
|
||||
'W002',
|
||||
`STATE.md references phase ${ref}, but only phases ${[...diskPhases].sort().join(', ')} exist`,
|
||||
'Review STATE.md manually before changing it; /gsd-health --repair will not overwrite an existing STATE.md for phase mismatches'
|
||||
'Review STATE.md manually before changing it; /gsd:health --repair will not overwrite an existing STATE.md for phase mismatches'
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -622,7 +622,7 @@ function cmdValidateHealth(cwd, options, raw) {
|
||||
|
||||
// ─── Check 5: config.json valid JSON + valid schema ───────────────────────
|
||||
if (!fs.existsSync(configPath)) {
|
||||
addIssue('warning', 'W003', 'config.json not found', 'Run /gsd-health --repair to create with defaults', true);
|
||||
addIssue('warning', 'W003', 'config.json not found', 'Run /gsd:health --repair to create with defaults', true);
|
||||
repairs.push('createConfig');
|
||||
} else {
|
||||
try {
|
||||
@@ -634,7 +634,7 @@ function cmdValidateHealth(cwd, options, raw) {
|
||||
addIssue('warning', 'W004', `config.json: invalid model_profile "${parsed.model_profile}"`, `Valid values: ${validProfiles.join(', ')}`);
|
||||
}
|
||||
} catch (err) {
|
||||
addIssue('error', 'E005', `config.json: JSON parse error - ${err.message}`, 'Run /gsd-health --repair to reset to defaults', true);
|
||||
addIssue('error', 'E005', `config.json: JSON parse error - ${err.message}`, 'Run /gsd:health --repair to reset to defaults', true);
|
||||
repairs.push('resetConfig');
|
||||
}
|
||||
}
|
||||
@@ -645,11 +645,11 @@ function cmdValidateHealth(cwd, options, raw) {
|
||||
const configRaw = fs.readFileSync(configPath, 'utf-8');
|
||||
const configParsed = JSON.parse(configRaw);
|
||||
if (configParsed.workflow && configParsed.workflow.nyquist_validation === undefined) {
|
||||
addIssue('warning', 'W008', 'config.json: workflow.nyquist_validation absent (defaults to enabled but agents may skip)', 'Run /gsd-health --repair to add key', true);
|
||||
addIssue('warning', 'W008', 'config.json: workflow.nyquist_validation absent (defaults to enabled but agents may skip)', 'Run /gsd:health --repair to add key', true);
|
||||
if (!repairs.includes('addNyquistKey')) repairs.push('addNyquistKey');
|
||||
}
|
||||
if (configParsed.workflow && configParsed.workflow.ai_integration_phase === undefined) {
|
||||
addIssue('warning', 'W016', 'config.json: workflow.ai_integration_phase absent (defaults to enabled — run /gsd-ai-integration-phase before planning AI system phases)', 'Run /gsd-health --repair to add key', true);
|
||||
addIssue('warning', 'W016', 'config.json: workflow.ai_integration_phase absent (defaults to enabled — run /gsd:ai-integration-phase before planning AI system phases)', 'Run /gsd:health --repair to add key', true);
|
||||
if (!repairs.includes('addAiIntegrationPhaseKey')) repairs.push('addAiIntegrationPhaseKey');
|
||||
}
|
||||
} catch { /* intentionally empty */ }
|
||||
@@ -699,7 +699,7 @@ function cmdValidateHealth(cwd, options, raw) {
|
||||
try {
|
||||
const researchContent = fs.readFileSync(path.join(phasesDir, e.name, researchFile), 'utf-8');
|
||||
if (researchContent.includes('## Validation Architecture')) {
|
||||
addIssue('warning', 'W009', `Phase ${e.name}: has Validation Architecture in RESEARCH.md but no VALIDATION.md`, 'Re-run /gsd-plan-phase with --research to regenerate');
|
||||
addIssue('warning', 'W009', `Phase ${e.name}: has Validation Architecture in RESEARCH.md but no VALIDATION.md`, 'Re-run /gsd:plan-phase with --research to regenerate');
|
||||
}
|
||||
} catch { /* intentionally empty */ }
|
||||
}
|
||||
@@ -792,7 +792,7 @@ function cmdValidateHealth(cwd, options, raw) {
|
||||
if (statusVal !== 'complete' && statusVal !== 'done') {
|
||||
addIssue('warning', 'W011',
|
||||
`STATE.md says current phase is ${statePhase} (status: ${statusVal || 'unknown'}) but ROADMAP.md shows it as [x] complete — state files may be out of sync`,
|
||||
'Run /gsd-progress to re-derive current position, or manually update STATE.md');
|
||||
'Run /gsd:progress to re-derive current position, or manually update STATE.md');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -871,6 +871,54 @@ function cmdValidateHealth(cwd, options, raw) {
|
||||
}
|
||||
} catch { /* git worktree not available or not a git repo — skip silently */ }
|
||||
|
||||
// ─── Check 12: MILESTONES.md / archive snapshot drift (#2446) ─────────────
|
||||
const milestonesPath = path.join(planBase, 'MILESTONES.md');
|
||||
const milestonesArchiveDir = path.join(planBase, 'milestones');
|
||||
const missingFromRegistry = [];
|
||||
try {
|
||||
if (fs.existsSync(milestonesArchiveDir)) {
|
||||
const archiveFiles = fs.readdirSync(milestonesArchiveDir);
|
||||
const archivedVersions = archiveFiles
|
||||
.map(f => f.match(/^(v\d+\.\d+(?:\.\d+)?)-ROADMAP\.md$/))
|
||||
.filter(Boolean)
|
||||
.map(m => m[1]);
|
||||
|
||||
if (archivedVersions.length > 0) {
|
||||
const registryContent = fs.existsSync(milestonesPath)
|
||||
? fs.readFileSync(milestonesPath, 'utf-8')
|
||||
: '';
|
||||
for (const ver of archivedVersions) {
|
||||
if (!registryContent.includes(`## ${ver}`)) {
|
||||
missingFromRegistry.push(ver);
|
||||
}
|
||||
}
|
||||
if (missingFromRegistry.length > 0) {
|
||||
addIssue('warning', 'W018',
|
||||
`MILESTONES.md missing ${missingFromRegistry.length} archived milestone(s): ${missingFromRegistry.join(', ')}`,
|
||||
'Run /gsd:health --backfill to synthesize missing entries from archive snapshots',
|
||||
true);
|
||||
repairs.push('backfillMilestones');
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch { /* intentionally empty — milestone sync check is advisory */ }
|
||||
|
||||
// ─── Check 13: Unrecognized .planning/ root files (W019) ──────────────────
|
||||
try {
|
||||
const { isCanonicalPlanningFile } = require('./artifacts.cjs');
|
||||
const entries = fs.readdirSync(planBase, { withFileTypes: true });
|
||||
for (const entry of entries) {
|
||||
if (!entry.isFile()) continue;
|
||||
if (!entry.name.endsWith('.md')) continue;
|
||||
if (!isCanonicalPlanningFile(entry.name)) {
|
||||
addIssue('warning', 'W019',
|
||||
`Unrecognized .planning/ file: ${entry.name} — not a canonical GSD artifact`,
|
||||
'Move to .planning/milestones/ archive subdir or delete if stale. See templates/README.md for the canonical artifact list.',
|
||||
false);
|
||||
}
|
||||
}
|
||||
} catch { /* artifact check is advisory — skip on error */ }
|
||||
|
||||
// ─── Perform repairs if requested ─────────────────────────────────────────
|
||||
const repairActions = [];
|
||||
if (options.repair && repairs.length > 0) {
|
||||
@@ -921,7 +969,7 @@ function cmdValidateHealth(cwd, options, raw) {
|
||||
stateContent += `**Current phase:** (determining...)\n`;
|
||||
stateContent += `**Status:** Resuming\n\n`;
|
||||
stateContent += `## Session Log\n\n`;
|
||||
stateContent += `- ${new Date().toISOString().split('T')[0]}: STATE.md regenerated by /gsd-health --repair\n`;
|
||||
stateContent += `- ${new Date().toISOString().split('T')[0]}: STATE.md regenerated by /gsd:health --repair\n`;
|
||||
writeStateMd(statePath, stateContent, cwd);
|
||||
repairActions.push({ action: repair, success: true, path: 'STATE.md' });
|
||||
break;
|
||||
@@ -960,6 +1008,39 @@ function cmdValidateHealth(cwd, options, raw) {
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'backfillMilestones': {
|
||||
if (!options.backfill && !options.repair) break;
|
||||
const today = new Date().toISOString().split('T')[0];
|
||||
let backfilled = 0;
|
||||
for (const ver of missingFromRegistry) {
|
||||
try {
|
||||
const snapshotPath = path.join(milestonesArchiveDir, `${ver}-ROADMAP.md`);
|
||||
const snapshot = fs.existsSync(snapshotPath) ? fs.readFileSync(snapshotPath, 'utf-8') : null;
|
||||
// Build minimal entry from snapshot title or version
|
||||
const titleMatch = snapshot && snapshot.match(/^#\s+(.+)$/m);
|
||||
const milestoneName = titleMatch ? titleMatch[1].replace(/^Milestone\s+/i, '').replace(/^v[\d.]+\s*/, '').trim() : ver;
|
||||
const entry = `## ${ver}${milestoneName && milestoneName !== ver ? ` ${milestoneName}` : ''} (Backfilled: ${today})\n\n**Note:** Synthesized from archive snapshot by \`/gsd:health --backfill\`. Original completion date unknown.\n\n---\n\n`;
|
||||
const milestonesContent = fs.existsSync(milestonesPath)
|
||||
? fs.readFileSync(milestonesPath, 'utf-8')
|
||||
: '';
|
||||
if (!milestonesContent.trim()) {
|
||||
fs.writeFileSync(milestonesPath, `# Milestones\n\n${entry}`, 'utf-8');
|
||||
} else {
|
||||
const headerMatch = milestonesContent.match(/^(#{1,3}\s+[^\n]*\n\n?)/);
|
||||
if (headerMatch) {
|
||||
const header = headerMatch[1];
|
||||
const rest = milestonesContent.slice(header.length);
|
||||
fs.writeFileSync(milestonesPath, header + entry + rest, 'utf-8');
|
||||
} else {
|
||||
fs.writeFileSync(milestonesPath, entry + milestonesContent, 'utf-8');
|
||||
}
|
||||
}
|
||||
backfilled++;
|
||||
} catch { /* intentionally empty — partial backfill is acceptable */ }
|
||||
}
|
||||
repairActions.push({ action: repair, success: true, detail: `Backfilled ${backfilled} milestone(s) into MILESTONES.md` });
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
repairActions.push({ action: repair, success: false, error: err.message });
|
||||
@@ -980,14 +1061,16 @@ function cmdValidateHealth(cwd, options, raw) {
|
||||
const repairableCount = errors.filter(e => e.repairable).length +
|
||||
warnings.filter(w => w.repairable).length;
|
||||
|
||||
output({
|
||||
const result = {
|
||||
status,
|
||||
errors,
|
||||
warnings,
|
||||
info,
|
||||
repairable_count: repairableCount,
|
||||
repairs_performed: repairActions.length > 0 ? repairActions : undefined,
|
||||
}, raw);
|
||||
};
|
||||
output(result, raw);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1086,6 +1169,141 @@ function cmdVerifySchemaDrift(cwd, phaseArg, skipFlag, raw) {
|
||||
}, raw);
|
||||
}
|
||||
|
||||
// ─── Codebase Drift Detection (#2003) ────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Detect structural drift between the committed tree and
|
||||
* `.planning/codebase/STRUCTURE.md`. Non-blocking: any failure returns a
|
||||
* `{ skipped: true }` JSON result with a reason; the command never exits
|
||||
* non-zero so `execute-phase`'s drift gate cannot fail the phase.
|
||||
*/
|
||||
function cmdVerifyCodebaseDrift(cwd, raw) {
|
||||
const drift = require('./drift.cjs');
|
||||
|
||||
const emit = (payload) => output(payload, raw);
|
||||
|
||||
try {
|
||||
const codebaseDir = path.join(planningDir(cwd), 'codebase');
|
||||
const structurePath = path.join(codebaseDir, 'STRUCTURE.md');
|
||||
if (!fs.existsSync(structurePath)) {
|
||||
emit({
|
||||
skipped: true,
|
||||
reason: 'no-structure-md',
|
||||
action_required: false,
|
||||
directive: 'none',
|
||||
elements: [],
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
let structureMd;
|
||||
try {
|
||||
structureMd = fs.readFileSync(structurePath, 'utf-8');
|
||||
} catch (err) {
|
||||
emit({
|
||||
skipped: true,
|
||||
reason: 'cannot-read-structure-md: ' + err.message,
|
||||
action_required: false,
|
||||
directive: 'none',
|
||||
elements: [],
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const lastMapped = drift.readMappedCommit(structurePath);
|
||||
|
||||
// Verify we're inside a git repo and resolve the diff range.
|
||||
const revProbe = execGit(cwd, ['rev-parse', 'HEAD']);
|
||||
if (revProbe.exitCode !== 0) {
|
||||
emit({
|
||||
skipped: true,
|
||||
reason: 'not-a-git-repo',
|
||||
action_required: false,
|
||||
directive: 'none',
|
||||
elements: [],
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Empty-tree SHA is a stable fallback when no mapping commit is recorded.
|
||||
const EMPTY_TREE = '4b825dc642cb6eb9a060e54bf8d69288fbee4904';
|
||||
let base = lastMapped;
|
||||
if (!base) {
|
||||
base = EMPTY_TREE;
|
||||
} else {
|
||||
// Verify the commit is reachable; if not, fall back to EMPTY_TREE.
|
||||
const verify = execGit(cwd, ['cat-file', '-t', base]);
|
||||
if (verify.exitCode !== 0) base = EMPTY_TREE;
|
||||
}
|
||||
|
||||
const diff = execGit(cwd, ['diff', '--name-status', base, 'HEAD']);
|
||||
if (diff.exitCode !== 0) {
|
||||
emit({
|
||||
skipped: true,
|
||||
reason: 'git-diff-failed',
|
||||
action_required: false,
|
||||
directive: 'none',
|
||||
elements: [],
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const added = [];
|
||||
const modified = [];
|
||||
const deleted = [];
|
||||
for (const line of diff.stdout.split(/\r?\n/)) {
|
||||
if (!line.trim()) continue;
|
||||
const m = line.match(/^([A-Z])\d*\t(.+?)(?:\t(.+))?$/);
|
||||
if (!m) continue;
|
||||
const status = m[1];
|
||||
// For renames (R), use the new path (m[3] if present, else m[2]).
|
||||
const file = m[3] || m[2];
|
||||
if (status === 'A' || status === 'R' || status === 'C') added.push(file);
|
||||
else if (status === 'M') modified.push(file);
|
||||
else if (status === 'D') deleted.push(file);
|
||||
}
|
||||
|
||||
// Threshold and action read from config, with defaults.
|
||||
const config = loadConfig(cwd);
|
||||
const threshold = Number.isInteger(config?.workflow?.drift_threshold) && config.workflow.drift_threshold >= 1
|
||||
? config.workflow.drift_threshold
|
||||
: 3;
|
||||
const action = config?.workflow?.drift_action === 'auto-remap' ? 'auto-remap' : 'warn';
|
||||
|
||||
const result = drift.detectDrift({
|
||||
addedFiles: added,
|
||||
modifiedFiles: modified,
|
||||
deletedFiles: deleted,
|
||||
structureMd,
|
||||
threshold,
|
||||
action,
|
||||
});
|
||||
|
||||
emit({
|
||||
skipped: !!result.skipped,
|
||||
reason: result.reason || null,
|
||||
action_required: !!result.actionRequired,
|
||||
directive: result.directive,
|
||||
spawn_mapper: !!result.spawnMapper,
|
||||
affected_paths: result.affectedPaths || [],
|
||||
elements: result.elements || [],
|
||||
threshold,
|
||||
action,
|
||||
last_mapped_commit: lastMapped,
|
||||
message: result.message || '',
|
||||
});
|
||||
} catch (err) {
|
||||
// Non-blocking: never bubble up an exception.
|
||||
emit({
|
||||
skipped: true,
|
||||
reason: 'exception: ' + (err && err.message ? err.message : String(err)),
|
||||
action_required: false,
|
||||
directive: 'none',
|
||||
elements: [],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
cmdVerifySummary,
|
||||
cmdVerifyPlanStructure,
|
||||
@@ -1098,4 +1316,5 @@ module.exports = {
|
||||
cmdValidateHealth,
|
||||
cmdValidateAgents,
|
||||
cmdVerifySchemaDrift,
|
||||
cmdVerifyCodebaseDrift,
|
||||
};
|
||||
|
||||
@@ -78,7 +78,7 @@ function cmdWorkstreamCreate(cwd, name, options, raw) {
|
||||
|
||||
const baseDir = planningRoot(cwd);
|
||||
if (!fs.existsSync(baseDir)) {
|
||||
error('.planning/ directory not found — run /gsd-new-project first');
|
||||
error('.planning/ directory not found — run /gsd:new-project first');
|
||||
}
|
||||
|
||||
const wsRoot = path.join(baseDir, 'workstreams');
|
||||
|
||||
@@ -72,21 +72,21 @@ reads is inert — the consumption mechanism is what gives an artifact meaning.
|
||||
- **Location**: `.planning/spikes/SPIKE-NNN/`
|
||||
- **Consumed by**: Planner when spike is referenced; `pause-work` for spike context handoff
|
||||
|
||||
### Spike README.md / MANIFEST.md (per-spike, via /gsd-spike)
|
||||
### Spike README.md / MANIFEST.md (per-spike, via /gsd:spike)
|
||||
- **Shape**: YAML frontmatter (spike, name, validates, verdict, related, tags) + run instructions + results
|
||||
- **Lifecycle**: Created by `/gsd-spike` → Verified → Wrapped up by `/gsd-spike-wrap-up`
|
||||
- **Lifecycle**: Created by `/gsd:spike` → Verified → Wrapped up by `/gsd:spike-wrap-up`
|
||||
- **Location**: `.planning/spikes/NNN-name/README.md`, `.planning/spikes/MANIFEST.md`
|
||||
- **Consumed by**: `/gsd-spike-wrap-up` for curation; `pause-work` for spike context handoff
|
||||
- **Consumed by**: `/gsd:spike-wrap-up` for curation; `pause-work` for spike context handoff
|
||||
|
||||
### Sketch README.md / MANIFEST.md / index.html (per-sketch)
|
||||
- **Shape**: YAML frontmatter (sketch, name, question, winner, tags) + variants as tabbed HTML
|
||||
- **Lifecycle**: Created by `/gsd-sketch` → Evaluated → Wrapped up by `/gsd-sketch-wrap-up`
|
||||
- **Lifecycle**: Created by `/gsd:sketch` → Evaluated → Wrapped up by `/gsd:sketch-wrap-up`
|
||||
- **Location**: `.planning/sketches/NNN-name/README.md`, `.planning/sketches/NNN-name/index.html`, `.planning/sketches/MANIFEST.md`
|
||||
- **Consumed by**: `/gsd-sketch-wrap-up` for curation; `pause-work` for sketch context handoff
|
||||
- **Consumed by**: `/gsd:sketch-wrap-up` for curation; `pause-work` for sketch context handoff
|
||||
|
||||
### WRAP-UP-SUMMARY.md (per wrap-up session)
|
||||
- **Shape**: Curation results, included/excluded items, feature/design area groupings
|
||||
- **Lifecycle**: Created by `/gsd-spike-wrap-up` or `/gsd-sketch-wrap-up`
|
||||
- **Lifecycle**: Created by `/gsd:spike-wrap-up` or `/gsd:sketch-wrap-up`
|
||||
- **Location**: `.planning/spikes/WRAP-UP-SUMMARY.md` or `.planning/sketches/WRAP-UP-SUMMARY.md`
|
||||
- **Consumed by**: Project history; not read by automated workflows
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ Every workflow that spawns agents or reads significant content must follow these
|
||||
|
||||
1. **Never** read agent definition files (`agents/*.md`) -- `subagent_type` auto-loads them
|
||||
2. **Never** inline large files into subagent prompts -- tell agents to read files from disk instead
|
||||
3. **Read depth scales with context window** -- check `context_window_tokens` in `.planning/config.json`:
|
||||
3. **Read depth scales with context window** -- check `context_window` in `.planning/config.json`:
|
||||
- At < 500000 tokens (default 200k): read only frontmatter, status fields, or summaries. Never read full SUMMARY.md, VERIFICATION.md, or RESEARCH.md bodies.
|
||||
- At >= 500000 tokens (1M model): MAY read full subagent output bodies when the content is needed for inline presentation or decision-making. Still avoid unnecessary reads.
|
||||
4. **Delegate** heavy work to subagents -- the orchestrator routes, it doesn't execute
|
||||
@@ -25,7 +25,7 @@ Every workflow that spawns agents or reads significant content must follow these
|
||||
| < 500k (200k model) | Frontmatter only | Frontmatter only | Frontmatter only | Current phase only |
|
||||
| >= 500k (1M model) | Full body permitted | Full body permitted | Full body permitted | Current phase only |
|
||||
|
||||
**How to check:** Read `.planning/config.json` and inspect `context_window_tokens`. If the field is absent, treat as 200k (conservative default).
|
||||
**How to check:** Read `.planning/config.json` and inspect `context_window`. If the field is absent, treat as 200k (conservative default).
|
||||
|
||||
## Context Degradation Tiers
|
||||
|
||||
|
||||
@@ -50,13 +50,13 @@ Standard format for presenting next steps after completing a command or workflow
|
||||
|
||||
`/clear` then:
|
||||
|
||||
`/gsd-execute-phase 2`
|
||||
`/gsd:execute-phase 2`
|
||||
|
||||
---
|
||||
|
||||
**Also available:**
|
||||
- Review plan before executing
|
||||
- `/gsd-list-phase-assumptions 2` — check assumptions
|
||||
- `/gsd:list-phase-assumptions 2` — check assumptions
|
||||
|
||||
---
|
||||
```
|
||||
@@ -75,7 +75,7 @@ Add note that this is the last plan and what comes after:
|
||||
|
||||
`/clear` then:
|
||||
|
||||
`/gsd-execute-phase 2`
|
||||
`/gsd:execute-phase 2`
|
||||
|
||||
---
|
||||
|
||||
@@ -97,13 +97,13 @@ Add note that this is the last plan and what comes after:
|
||||
|
||||
`/clear` then:
|
||||
|
||||
`/gsd-plan-phase 2`
|
||||
`/gsd:plan-phase 2`
|
||||
|
||||
---
|
||||
|
||||
**Also available:**
|
||||
- `/gsd-discuss-phase 2` — gather context first
|
||||
- `/gsd-research-phase 2` — investigate unknowns
|
||||
- `/gsd:discuss-phase 2` — gather context first
|
||||
- `/gsd:research-phase 2` — investigate unknowns
|
||||
- Review roadmap
|
||||
|
||||
---
|
||||
@@ -126,13 +126,13 @@ Show completion status before next action:
|
||||
|
||||
`/clear` then:
|
||||
|
||||
`/gsd-plan-phase 3`
|
||||
`/gsd:plan-phase 3`
|
||||
|
||||
---
|
||||
|
||||
**Also available:**
|
||||
- `/gsd-discuss-phase 3` — gather context first
|
||||
- `/gsd-research-phase 3` — investigate unknowns
|
||||
- `/gsd:discuss-phase 3` — gather context first
|
||||
- `/gsd:research-phase 3` — investigate unknowns
|
||||
- Review what Phase 2 built
|
||||
|
||||
---
|
||||
@@ -151,11 +151,11 @@ When there's no clear primary action:
|
||||
|
||||
`/clear` then one of:
|
||||
|
||||
**To plan directly:** `/gsd-plan-phase 3`
|
||||
**To plan directly:** `/gsd:plan-phase 3`
|
||||
|
||||
**To discuss context first:** `/gsd-discuss-phase 3`
|
||||
**To discuss context first:** `/gsd:discuss-phase 3`
|
||||
|
||||
**To research unknowns:** `/gsd-research-phase 3`
|
||||
**To research unknowns:** `/gsd:research-phase 3`
|
||||
|
||||
---
|
||||
```
|
||||
@@ -175,7 +175,7 @@ All 4 phases shipped
|
||||
|
||||
`/clear` then:
|
||||
|
||||
`/gsd-new-milestone`
|
||||
`/gsd:new-milestone`
|
||||
|
||||
---
|
||||
```
|
||||
@@ -218,7 +218,7 @@ Extract: `**02-03: Refresh Token Rotation** — Add /api/auth/refresh with slidi
|
||||
## To Continue
|
||||
|
||||
Run `/clear`, then paste:
|
||||
/gsd-execute-phase 2
|
||||
/gsd:execute-phase 2
|
||||
```
|
||||
|
||||
User has no idea what 02-03 is about.
|
||||
@@ -226,7 +226,7 @@ User has no idea what 02-03 is about.
|
||||
### Don't: Missing /clear explanation
|
||||
|
||||
```
|
||||
`/gsd-plan-phase 3`
|
||||
`/gsd:plan-phase 3`
|
||||
|
||||
Run /clear first.
|
||||
```
|
||||
@@ -246,7 +246,7 @@ Sounds like an afterthought. Use "Also available:" instead.
|
||||
|
||||
```
|
||||
```
|
||||
/gsd-plan-phase 3
|
||||
/gsd:plan-phase 3
|
||||
```
|
||||
```
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user