mirror of
https://github.com/glittercowboy/get-shit-done
synced 2026-04-25 17:25:23 +02:00
Compare commits
86 Commits
fix/2544-c
...
feat/2606-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 |
15
.github/workflows/release.yml
vendored
15
.github/workflows/release.yml
vendored
@@ -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
|
||||
|
||||
831
CHANGELOG.md
831
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
|
||||
|
||||
261
bin/install.js
261
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,115 @@ function readGsdGlobalModelOverrides() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* #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 +1000,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 +1134,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/');
|
||||
@@ -1778,7 +1912,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 +1931,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.
|
||||
@@ -3039,8 +3184,16 @@ function installCodexConfig(targetDir, agentsSrc) {
|
||||
|
||||
// Pass model overrides from ~/.gsd/defaults.json so Codex TOML files
|
||||
// embed the configured model — Codex cannot receive model inline (#2256).
|
||||
// #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 = readGsdGlobalModelOverrides();
|
||||
const tomlContent = generateCodexAgentToml(name, content, modelOverrides);
|
||||
// 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 +5612,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}/`;
|
||||
|
||||
@@ -6065,9 +6221,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)
|
||||
@@ -6768,24 +6928,76 @@ function installSdkIfNeeded() {
|
||||
console.log(`\n ${cyan}Building GSD SDK from source (${sdkDir})…${reset}`);
|
||||
const npmCmd = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
||||
|
||||
// Windows: Node.js refuses to spawn .cmd/.bat files without `shell: true`
|
||||
// after CVE-2024-27980 (fixed in Node ≥ 18.20.2 / ≥ 20.12.2 / ≥ 21.7.3).
|
||||
// Without shell, spawnSync returns { status: null, error: EINVAL } and
|
||||
// every `status !== 0` check trips — producing a silent build failure
|
||||
// with no underlying diagnostic because stdio: 'inherit' never gets a
|
||||
// child to stream (#2598).
|
||||
const needsShell = process.platform === 'win32';
|
||||
const spawnNpm = (args, opts = {}) =>
|
||||
spawnSync(npmCmd, args, { ...opts, shell: opts.shell ?? needsShell });
|
||||
|
||||
// Format the underlying spawnSync failure so EINVAL / ENOENT / signal exits
|
||||
// surface in the fatal banner instead of being swallowed. The #2598 silent
|
||||
// failure happened precisely because `{ status: null, error: EINVAL }` was
|
||||
// reduced to a generic "Failed to npm install" with no diagnostic — the real
|
||||
// cause (CVE-2024-27980 on Windows) was invisible in the output.
|
||||
const formatSpawnFailure = (result) => {
|
||||
if (!result) return '';
|
||||
if (result.error) return ` (${result.error.code || result.error.name || 'spawn error'}: ${result.error.message})`;
|
||||
if (result.signal) return ` (signal: ${result.signal})`;
|
||||
if (typeof result.status === 'number') return ` (exit status: ${result.status})`;
|
||||
return '';
|
||||
};
|
||||
|
||||
// 1. Install sdk build-time dependencies (tsc, etc.)
|
||||
const installResult = spawnSync(npmCmd, ['install'], { cwd: sdkDir, stdio: 'inherit' });
|
||||
const installResult = spawnNpm(['install'], { cwd: sdkDir, stdio: 'inherit' });
|
||||
if (installResult.status !== 0) {
|
||||
emitSdkFatal('Failed to `npm install` in sdk/.', { globalBin: null, exitCode: 1 });
|
||||
emitSdkFatal(
|
||||
`Failed to \`npm install\` in sdk/.${formatSpawnFailure(installResult)}`,
|
||||
{ globalBin: null, exitCode: 1 },
|
||||
);
|
||||
}
|
||||
|
||||
// 2. Compile TypeScript → sdk/dist/
|
||||
const buildResult = spawnSync(npmCmd, ['run', 'build'], { cwd: sdkDir, stdio: 'inherit' });
|
||||
const buildResult = spawnNpm(['run', 'build'], { cwd: sdkDir, stdio: 'inherit' });
|
||||
if (buildResult.status !== 0) {
|
||||
emitSdkFatal('Failed to `npm run build` in sdk/.', { globalBin: null, exitCode: 1 });
|
||||
emitSdkFatal(
|
||||
`Failed to \`npm run build\` in sdk/.${formatSpawnFailure(buildResult)}`,
|
||||
{ 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' });
|
||||
const globalResult = spawnNpm(['install', '-g', '.'], { cwd: sdkDir, stdio: 'inherit' });
|
||||
if (globalResult.status !== 0) {
|
||||
emitSdkFatal('Failed to `npm install -g .` from sdk/.', { globalBin: null, exitCode: 1 });
|
||||
emitSdkFatal(
|
||||
`Failed to \`npm install -g .\` from sdk/.${formatSpawnFailure(globalResult)}`,
|
||||
{ globalBin: null, exitCode: 1 },
|
||||
);
|
||||
}
|
||||
|
||||
// 3a. Explicitly chmod dist/cli.js to 0o755 in the global install location.
|
||||
// `tsc` emits files at process umask (typically 0o644 — non-executable), and
|
||||
// `npm install -g` from a local directory does NOT chmod bin-script targets the
|
||||
// way tarball extraction does. Without this, the `gsd-sdk` bin symlink points at
|
||||
// a non-executable file and `command -v gsd-sdk` fails on every first install
|
||||
// (root cause of #2453). Mirrors the pattern used for hook files in this installer.
|
||||
try {
|
||||
const prefixRes = spawnNpm(['config', 'get', 'prefix'], { encoding: 'utf-8' });
|
||||
if (prefixRes.status === 0) {
|
||||
const npmPrefix = (prefixRes.stdout || '').trim();
|
||||
const sdkPkg = JSON.parse(fs.readFileSync(path.join(sdkDir, 'package.json'), 'utf-8'));
|
||||
const sdkName = sdkPkg.name; // '@gsd-build/sdk'
|
||||
const globalModulesDir = process.platform === 'win32'
|
||||
? path.join(npmPrefix, 'node_modules')
|
||||
: path.join(npmPrefix, 'lib', 'node_modules');
|
||||
const cliPath = path.join(globalModulesDir, sdkName, 'dist', 'cli.js');
|
||||
try { fs.chmodSync(cliPath, 0o755); } catch (e) { if (process.platform !== 'win32') throw e; }
|
||||
}
|
||||
} catch (e) { /* Non-fatal: PATH verification in step 4 will catch any real failure */ }
|
||||
|
||||
// 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`
|
||||
@@ -6797,7 +7009,7 @@ function installSdkIfNeeded() {
|
||||
}
|
||||
|
||||
// Off-PATH: resolve npm global bin dir for actionable remediation.
|
||||
const prefixResult = spawnSync(npmCmd, ['config', 'get', 'prefix'], { encoding: 'utf-8' });
|
||||
const prefixResult = spawnNpm(['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'))
|
||||
@@ -6873,6 +7085,7 @@ if (process.env.GSD_TEST_MODE) {
|
||||
stripGsdFromCodexConfig,
|
||||
mergeCodexConfig,
|
||||
installCodexConfig,
|
||||
readGsdRuntimeProfileResolver,
|
||||
install,
|
||||
uninstall,
|
||||
convertClaudeCommandToCodexSkill,
|
||||
@@ -6931,7 +7144,17 @@ if (process.env.GSD_TEST_MODE) {
|
||||
} 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
|
||||
|
||||
@@ -1037,12 +1037,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
|
||||
|
||||
@@ -190,6 +239,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 +297,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 +314,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 +342,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 +444,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 +454,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.
|
||||
@@ -540,6 +646,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,
|
||||
@@ -1334,9 +1359,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 +1473,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 +1694,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 +1767,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 +1822,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 +1871,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 +2007,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
|
||||
```
|
||||
```
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Doc Conflict Engine
|
||||
|
||||
Shared conflict-detection contract for workflows that ingest external content into `.planning/` (e.g., `/gsd-import`, `/gsd-ingest-docs`). Defines the report format, severity semantics, and safety-gate behavior. The specific checks that populate each severity bucket are workflow-specific and defined by the calling workflow.
|
||||
Shared conflict-detection contract for workflows that ingest external content into `.planning/` (e.g., `/gsd:import`, `/gsd:ingest-docs`). Defines the report format, severity semantics, and safety-gate behavior. The specific checks that populate each severity bucket are workflow-specific and defined by the calling workflow.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Domain-Aware Probing Patterns
|
||||
|
||||
Shared reference for `/gsd-begin`, `/gsd-discuss-phase`, and domain exploration workflows.
|
||||
Shared reference for `/gsd-begin`, `/gsd:discuss-phase`, and domain exploration workflows.
|
||||
|
||||
When the user mentions a technology area, use these probes to ask insightful follow-up questions. Don't run through them as a checklist -- pick the 2-3 most relevant based on context. The goal is to surface hidden assumptions and trade-offs the user may not have considered yet.
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ Simple 2-option confirmation for re-planning, rebuild, replace plans, commit.
|
||||
4-option escalation for review escalation (max retries exceeded).
|
||||
- question: "Phase {N} has failed verification {attempt} times. How should we proceed?"
|
||||
- header: "Escalate"
|
||||
- options: Accept gaps | Re-plan (via /gsd-plan-phase) | Debug (via /gsd-debug) | Retry
|
||||
- options: Accept gaps | Re-plan (via /gsd:plan-phase) | Debug (via /gsd:debug) | Retry
|
||||
|
||||
## Pattern: multi-option-gaps
|
||||
4-option gap handler for review gaps-found.
|
||||
@@ -78,7 +78,7 @@ Up to 4 suggested next actions with selection (status, resume workflows).
|
||||
3-option confirmation for quick task scope validation.
|
||||
- question: "This task looks complex. Proceed as quick task or use full planning?"
|
||||
- header: "Scope"
|
||||
- options: Quick task | Full plan (via /gsd-plan-phase) | Revise
|
||||
- options: Quick task | Full plan (via /gsd:plan-phase) | Revise
|
||||
|
||||
## Pattern: depth-select
|
||||
3-option depth selection for planning workflow preferences.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user