Compare commits

...

94 Commits

Author SHA1 Message Date
Tom Boucher
9de8e24463 Merge pull request #3133 from gsd-build/fix/3131-rewire-orphaned-workflows-missed-consolidation
fix(#3131): re-wire 4 orphaned workflows as flags on parent commands
2026-05-05 11:28:36 -04:00
Tom Boucher
811410be61 fix: address all 13 CodeRabbit comments from second review pass
Duplicate /gsd-help rows (caused by join-discord → help replacement
landing in tables that already had /gsd-help):
- Remove Discord-purpose duplicate row from README.md, README.ja-JP.md,
  README.zh-CN.md, README.ko-KR.md, docs/zh-CN/README.md,
  docs/zh-CN/USER-GUIDE.md, docs/ja-JP/USER-GUIDE.md,
  docs/ko-KR/USER-GUIDE.md
- Remove orphaned Discord-only ### /gsd-help sections from
  docs/ja-JP/COMMANDS.md and docs/ko-KR/COMMANDS.md

Gap-fix command precision (plan-milestone-gaps → audit-milestone --fix):
- README.ja-JP.md, README.ko-KR.md, README.zh-CN.md gap-fix rows
  updated to /gsd-audit-milestone --fix

docs/COMMANDS.md: document --path <dir> for --from-gsd2 in table and
  example block

docs/FEATURES.md:
- Add adaptive to /gsd-config --profile value set
- Add blank line before spike Produces table (MD058)

Suite: 6971/6971 pass
2026-05-05 11:22:37 -04:00
Tom Boucher
891eae1025 fix: short-circuit --assumptions and --from-gsd2 dispatch; add changeset
- discuss-phase --assumptions: add 'Stop here' + convert If→Otherwise
  chain so the flag is an exclusive route (CodeRabbit major)
- import --from-gsd2: add 'Stop here' + convert final 'Execute...'
  to 'Otherwise...' to prevent fall-through to standard import
  (CodeRabbit major, inline comment)
- .changeset/rewire-orphaned-workflows-3131.md: add missing changeset
2026-05-05 11:05:17 -04:00
Tom Boucher
858c821829 docs: sweep stale /gsd-* command references across all user-facing docs
Replace 30 absorbed/deleted standalone command forms with their
consolidated flag-based equivalents across 25 files (English + 4
locales + AGENTS/CLI-TOOLS/CONFIGURATION):

  /gsd-session-report        → /gsd-pause-work --report
  /gsd-list-phase-assumptions → /gsd-discuss-phase --assumptions
  /gsd-analyze-dependencies  → /gsd-manager --analyze-deps
  /gsd-research-phase        → /gsd-plan-phase --research-phase
  /gsd-plan-milestone-gaps   → /gsd-audit-milestone
  /gsd-code-review-fix       → /gsd-code-review --fix
  /gsd-spike-wrap-up         → /gsd-spike --wrap-up
  /gsd-sketch-wrap-up        → /gsd-sketch --wrap-up
  /gsd-set-profile           → /gsd-config --profile
  /gsd-check-todos           → /gsd-capture --list
  /gsd-add-todo              → /gsd-capture
  /gsd-add-backlog           → /gsd-capture --backlog
  /gsd-plant-seed            → /gsd-capture --seed
  /gsd-note                  → /gsd-capture --note
  /gsd-add-phase             → /gsd-phase
  /gsd-insert-phase          → /gsd-phase --insert
  /gsd-edit-phase            → /gsd-phase --edit
  /gsd-remove-phase          → /gsd-phase --remove
  /gsd-new-workspace         → /gsd-workspace --new
  /gsd-list-workspaces       → /gsd-workspace --list
  /gsd-remove-workspace      → /gsd-workspace --remove
  /gsd-sync-skills           → /gsd-update --sync
  /gsd-reapply-patches       → /gsd-update --reapply
  /gsd-scan                  → /gsd-map-codebase --fast
  /gsd-intel                 → /gsd-map-codebase --query
  /gsd-next                  → /gsd-progress --next
  /gsd-do                    → /gsd-progress --do
  /gsd-status                → /gsd-progress
  /gsd-join-discord          → /gsd-help

Skipped: CHANGELOG, RELEASE notes, superpowers/specs (historical)
Suite: 6971/6971 pass
2026-05-05 11:01:15 -04:00
Tom Boucher
851cddcc03 fix(#3131): re-wire 4 orphaned workflows as flags on parent commands
- discuss-phase --assumptions  → list-phase-assumptions.md
- pause-work --report          → session-report.md
- manager --analyze-deps       → analyze-dependencies.md
- import --from-gsd2           → gsd-tools.cjs from-gsd2 CLI

TDD: 8 new assertions in enh-2790-skill-consolidation.test.cjs
(argument-hint presence + body dispatch reference per flag).
Confirmed RED before wiring, GREEN after. Full suite 6971/6971.

help.md updated with all four new flag forms to satisfy
bug-2954-help-md-slash-command-stubs parity test.

Closes #3131
2026-05-05 10:51:10 -04:00
Tom Boucher
61773332d6 Merge pull request #3125 from gsd-build/fix/3098-phase-insert-and-init-phase-op-disagree-
fix: make phase insert placeholder/dry-run preconditions explicit
2026-05-04 23:54:44 -04:00
Tom Boucher
9987792c46 chore(changeset): correct issue reference for PR #3125 fragment 2026-05-04 23:49:00 -04:00
Tom Boucher
aa64638176 Merge pull request #3112 from gsd-build/fix/3101-plan-summary-matcher-in-core-cjs-reports
fix: canonicalize plan-summary matching for suffixless summaries
2026-05-04 23:35:34 -04:00
Tom Boucher
be4a9b3b43 Merge pull request #3114 from gsd-build/fix/3054-gsd-next-command-no-longer-available
fix: remove stale /gsd-next references from user-facing surfaces
2026-05-04 23:35:30 -04:00
Tom Boucher
e7ecd46bbe Merge pull request #3115 from gsd-build/fix/3053-sdk-ignores-multi-plan-phase-layout-plan
fix: count nested plans/ layout in phase status indexing
2026-05-04 23:35:26 -04:00
Tom Boucher
985b736d45 Merge pull request #3124 from gsd-build/fix/3050-update-backup-step-crashes-with-eacces-w
fix: make update custom-file backup resilient to EACCES
2026-05-04 23:35:21 -04:00
Tom Boucher
d3d995cfc4 test(3050): avoid includes-based source-grep assertion 2026-05-04 23:34:57 -04:00
Tom Boucher
43e5fef95e Merge pull request #3113 from gsd-build/fix/3083-resume-project-md-route-to-workflow-emit
fix: remove /clear then from resume route templates
2026-05-04 23:33:31 -04:00
Tom Boucher
083e813aea Merge pull request #3116 from gsd-build/fix/3055-bug-top-level-branching-strategy-in-plan
fix: normalize legacy top-level branching_strategy into git config
2026-05-04 23:33:28 -04:00
Tom Boucher
fe4db16769 Merge pull request #3118 from gsd-build/fix/3063-state-complete-phase-corrupts-state-md-b
fix: prevent state complete-phase from resolving literal 'Phase' token
2026-05-04 23:33:25 -04:00
Tom Boucher
399bb80b40 Merge pull request #3123 from gsd-build/fix/3091-npx-install-gsd-sdk-symlink-never-create
fix: align SDK install/fallback guidance with query-capable CLI
2026-05-04 23:33:22 -04:00
Tom Boucher
d978ad6b2f merge: sync main into PR #3114 and keep canonical next/profile commands 2026-05-04 23:32:42 -04:00
Tom Boucher
0fe88b9e7a chore(changeset): add release fragment for PR #3112 2026-05-04 23:32:15 -04:00
Tom Boucher
baf0d56063 chore(changeset): add release fragment for PR #3113 2026-05-04 23:32:14 -04:00
Tom Boucher
d2d1205691 chore(changeset): add release fragment for PR #3115 2026-05-04 23:32:12 -04:00
Tom Boucher
1c1e3b5de4 chore(changeset): add release fragment for PR #3116 2026-05-04 23:32:11 -04:00
Tom Boucher
a6d4e61606 chore(changeset): add release fragment for PR #3118 2026-05-04 23:32:09 -04:00
Tom Boucher
e2b12bfad2 chore(changeset): add release fragment for PR #3123 2026-05-04 23:32:07 -04:00
Tom Boucher
915e7daced chore(changeset): add release fragment for PR #3124 2026-05-04 23:32:06 -04:00
Tom Boucher
313f170cf0 chore(changeset): add release fragment for PR #3125 2026-05-04 23:32:04 -04:00
Tom Boucher
199083777a Merge pull request #3111 from gsd-build/fix/3094-progress-md-still-recommends-deleted-gsd
fix: remove stale /gsd-list-phase-assumptions guidance from progress routing
2026-05-04 23:31:26 -04:00
Tom Boucher
dbbc7f0942 Merge pull request #3117 from gsd-build/fix/3056-pruneorphanedworktrees-destroys-linked-w
fix: make orphaned worktree prune non-destructive by default
2026-05-04 23:31:13 -04:00
Tom Boucher
2113902daf Merge pull request #3119 from gsd-build/fix/3072-gsd-sdk-query-resolve-model-error-when-i
fix: guard optional sketch-findings probes from non-zero ls exits
2026-05-04 23:31:10 -04:00
Tom Boucher
f01f6b76dd Merge pull request #3122 from gsd-build/fix/3088-gsd-complete-milestone-leaves-state-md-n
fix: normalize stale STATE narrative tails on milestone completion
2026-05-04 23:31:06 -04:00
Tom Boucher
4ee6ce4a01 fix(3054): align docs anchors and structured stale-command checks 2026-05-04 23:30:35 -04:00
Tom Boucher
67684626d8 fix(3088): append missing STATE narrative sections on milestone close 2026-05-04 23:29:45 -04:00
Tom Boucher
b331c48261 test(3072): parse bash blocks for findings probe guard checks 2026-05-04 23:28:52 -04:00
Tom Boucher
3d2f2e85a0 test(3056): canonicalize worktree paths in prune assertions 2026-05-04 23:28:20 -04:00
Tom Boucher
5b63ba6ea9 test(3094): switch stale-progress assertion to structured token check 2026-05-04 23:27:38 -04:00
Tom Boucher
a4d16c3c93 Merge pull request #3109 from gsd-build/fix/3043-milestone-complete-version-scoping
fix: respect explicit milestone version in milestone complete
2026-05-04 23:27:16 -04:00
Tom Boucher
78846b1e6a Merge pull request #3108 from gsd-build/feat/deepen-query-failure-classification
refactor: deepen query architecture seams with compatibility shims
2026-05-04 23:24:03 -04:00
Tom Boucher
59fd17251a fix(phase): clarify insert preconditions and reject unsupported dry-run flag 2026-05-04 23:22:20 -04:00
Tom Boucher
efa642a078 fix(update): skip unreadable custom files during backup 2026-05-04 23:20:25 -04:00
Tom Boucher
120113c42b fix(sdk-guidance): point quick install hint and agent fallbacks to query-capable CLI 2026-05-04 23:18:41 -04:00
coderabbitai[bot]
2d25c97706 fix: apply CodeRabbit auto-fixes
Fixed 1 file(s) based on 2 unresolved review comments.

Co-authored-by: CodeRabbit <noreply@coderabbit.ai>
2026-05-05 03:17:22 +00:00
Tom Boucher
2dcf374da0 fix(milestone): normalize STATE narrative after milestone completion 2026-05-04 23:17:00 -04:00
Tom Boucher
50f714cdd5 fix(workflows): make optional findings-skill probes non-fatal 2026-05-04 23:13:33 -04:00
Tom Boucher
471df09242 fix(state): harden complete-phase resolution and add explicit override 2026-05-04 23:10:26 -04:00
Tom Boucher
ecd5d11b32 fix(worktree): disable destructive orphaned-worktree removal by default 2026-05-04 23:08:13 -04:00
Tom Boucher
58062a64a0 fix(sdk-config): honor legacy top-level branching_strategy in init 2026-05-04 23:06:54 -04:00
Tom Boucher
65024683fd fix(init): count plans/ summaries from nested plans/ layout 2026-05-04 23:03:10 -04:00
Tom Boucher
72f4c3b362 fix(docs): replace stale /gsd-next references with /gsd-progress --next 2026-05-04 22:54:01 -04:00
Tom Boucher
538ef683be fix(resume): remove clear prefix from resume routing 2026-05-04 22:52:30 -04:00
Tom Boucher
c7886415c3 fix(phase): canonicalize plan-summary matching for suffixless summaries 2026-05-04 22:51:15 -04:00
Tom Boucher
a54dda3837 fix(progress): remove stale list-phase-assumptions routing 2026-05-04 22:47:16 -04:00
Tom Boucher
19e580137d fix: scope milestone complete stats to explicit version 2026-05-04 22:06:22 -04:00
Tom Boucher
78c794c016 test: remove dead registry wiring assertion 2026-05-04 21:49:41 -04:00
Tom Boucher
40acf1f02e fix: address CodeRabbit findings on query/transport error handling 2026-05-04 21:49:41 -04:00
Tom Boucher
1642f47908 test: align registry wiring assertions with declarative assembly 2026-05-04 21:49:41 -04:00
Tom Boucher
38718e9d4b fix: avoid unsafe Promise cast in execRaw 2026-05-04 21:49:40 -04:00
Tom Boucher
a441f96f37 chore: update changeset pr reference 2026-05-04 21:49:40 -04:00
Tom Boucher
0500bdf619 refactor: deepen query architecture seams with compatibility shims 2026-05-04 21:49:40 -04:00
Tom Boucher
c6a35d6398 refactor: deepen transport policy and output projection paths 2026-05-04 21:49:40 -04:00
Tom Boucher
969cfcf998 refactor: split native hotpath fallback and dispatch branches 2026-05-04 21:49:40 -04:00
Tom Boucher
e0c791a5d0 refactor: centralize native dispatch data projection 2026-05-04 21:49:40 -04:00
Tom Boucher
deb4477375 refactor: remove thin runtime and tools error wrappers 2026-05-04 21:49:40 -04:00
Tom Boucher
5aaf0dbea5 refactor: reduce query error factory public surface 2026-05-04 21:49:40 -04:00
Tom Boucher
ace241d0c2 refactor: fold query error seam types into factory module 2026-05-04 21:49:40 -04:00
Tom Boucher
0fffc7c055 refactor: centralize gsd-tools error wrapping path 2026-05-04 21:49:40 -04:00
Tom Boucher
6059a574f2 refactor: remove redundant native dispatch cast in runtime 2026-05-04 21:49:40 -04:00
Tom Boucher
b0e616288b refactor: isolate native dispatch error projection 2026-05-04 21:49:40 -04:00
Tom Boucher
ed9d67c91b refactor: deepen subprocess adapter with shared execution error path 2026-05-04 21:49:40 -04:00
Tom Boucher
97019d274e refactor: keep classification constructors internal to GSDToolsError 2026-05-04 21:49:40 -04:00
Tom Boucher
7311e0a9ab refactor: extract query error seam factory builders 2026-05-04 21:49:39 -04:00
Tom Boucher
c66ff96de8 test: use typed GSDToolsError constructors in cli output tests 2026-05-04 21:49:39 -04:00
Tom Boucher
a24de43f8b test: consolidate tools error mapping coverage in factory tests 2026-05-04 21:49:39 -04:00
Tom Boucher
70faa0ff0f refactor: remove query tools error mapper wrapper 2026-05-04 21:49:39 -04:00
Tom Boucher
b9e3979fc1 refactor: introduce explicit query error seam contracts 2026-05-04 21:49:39 -04:00
Tom Boucher
c7d3f83b8b refactor: reduce failure-classification API surface 2026-05-04 21:49:39 -04:00
Tom Boucher
bc289fad4a refactor: type native adapter error seam to GSDToolsError 2026-05-04 21:49:39 -04:00
Tom Boucher
9bee4dce4a test: adopt typed GSDToolsError constructors across failure tests 2026-05-04 21:49:39 -04:00
Tom Boucher
9a469fa05c refactor: centralize query tools error construction in factory 2026-05-04 21:49:39 -04:00
Tom Boucher
abf7779088 test: cover typed timeout mapping in query dispatch 2026-05-04 21:49:39 -04:00
Tom Boucher
16bf552037 test: lock typed timeout no-fallback transport behavior 2026-05-04 21:49:39 -04:00
Tom Boucher
009cfb1562 refactor: split native adapter timeout and failure seams 2026-05-04 21:49:39 -04:00
Tom Boucher
6fe4af2546 refactor: split subprocess timeout and failure error seams 2026-05-04 21:49:39 -04:00
Tom Boucher
41683b2f53 refactor: centralize typed GSDToolsError construction 2026-05-04 21:49:38 -04:00
Tom Boucher
7dcafbc211 refactor: consolidate failure classification constructors 2026-05-04 21:49:38 -04:00
Tom Boucher
ccda572ade refactor: default typed failure classification across query errors 2026-05-04 21:49:38 -04:00
Tom Boucher
1ca7f58831 test: cover tools error mapping and unify timeout fallback check 2026-05-04 21:49:38 -04:00
Tom Boucher
7298a76b20 refactor: centralize dispatch error projection from failure signals 2026-05-04 21:49:38 -04:00
Tom Boucher
5cfd874058 refactor: add typed query failure signals 2026-05-04 21:49:38 -04:00
Tom Boucher
ba6100c548 refactor: deepen query failure classification module 2026-05-04 21:49:38 -04:00
Tom Boucher
9f5b011b35 refactor: use internal gsdtools error type import 2026-05-04 21:49:38 -04:00
Tom Boucher
1037b82a98 test: address remaining coderabbit findings and notes 2026-05-04 21:49:38 -04:00
Tom Boucher
ac883f8150 fix: address coderabbit query seam findings 2026-05-04 21:49:38 -04:00
Tom Boucher
3e22c70fac docs: fix changeset summary text 2026-05-04 21:49:38 -04:00
Tom Boucher
12fc34689e docs: add changeset for query seam deepening 2026-05-04 21:49:37 -04:00
Tom Boucher
9d096b9925 refactor: deepen gsdtools query execution seams 2026-05-04 21:49:37 -04:00
135 changed files with 2349 additions and 897 deletions

View File

@@ -0,0 +1,5 @@
---
type: Fixed
pr: 3114
---
**`/gsd-progress --next` doc migration is fully consistent** — command docs now use clear `--next` wording, FEATURES TOC anchors match renamed headings, and regression tests enforce stale-command detection via structured slash-command token checks.

View File

@@ -0,0 +1,5 @@
---
type: Fixed
pr: 3117
---
**Worktree prune regression checks are now path-normalized** — pruning safety tests now parse `git worktree list --porcelain` and assert structured normalized paths, preventing path-separator false negatives across platforms while preserving non-destructive prune guarantees.

View File

@@ -0,0 +1,5 @@
---
type: Fixed
pr: 3119
---
**Optional findings probe guard checks now use structured parsing** — regression tests now parse fenced bash blocks and validate sketch/spike findings probes as structured command records, ensuring non-fatal `|| true` guards are enforced without raw source grep assertions.

View File

@@ -0,0 +1,5 @@
---
type: Fixed
pr: 3122
---
**Milestone close now repairs missing STATE narrative sections** — when `## Current Position` or `## Operator Next Steps` headings are absent, milestone completion appends canonical sections so state remains deterministic and consistently points operators to `/gsd-new-milestone`.

View File

@@ -0,0 +1,5 @@
---
type: Fixed
pr: 3111
---
**Progress routing command guidance remains canonical** — pre-planning assumption checks in progress routing now consistently assert and document `/gsd-discuss-phase` as the replacement path, with tests enforcing structured slash-command token checks.

View File

@@ -0,0 +1,5 @@
---
type: Fixed
pr: 3043
---
milestone complete now scopes phase stats to the explicit version argument and errors when that version is missing from a versioned ROADMAP milestone section.

View File

@@ -0,0 +1,5 @@
---
type: Changed
pr: 3108
---
Query module architecture deepened with compatibility-preserving seams — command policy now derives from command definitions, and dispatch/topology/registry seams are consolidated for better locality while preserving existing query behavior.

View File

@@ -0,0 +1,5 @@
---
type: Fixed
pr: 3112
---
Fixes for issue #3112 were applied to keep command/workflow behavior and SDK parity aligned with current documented usage.

View File

@@ -0,0 +1,5 @@
---
type: Fixed
pr: 3113
---
Fixes for issue #3113 were applied to keep command/workflow behavior and SDK parity aligned with current documented usage.

View File

@@ -0,0 +1,5 @@
---
type: Fixed
pr: 3115
---
Fixes for issue #3115 were applied to keep command/workflow behavior and SDK parity aligned with current documented usage.

View File

@@ -0,0 +1,5 @@
---
type: Fixed
pr: 3116
---
Fixes for issue #3116 were applied to keep command/workflow behavior and SDK parity aligned with current documented usage.

View File

@@ -0,0 +1,5 @@
---
type: Fixed
pr: 3118
---
Fixes for issue #3118 were applied to keep command/workflow behavior and SDK parity aligned with current documented usage.

View File

@@ -0,0 +1,5 @@
---
type: Fixed
pr: 3123
---
Fixes for issue #3123 were applied to keep command/workflow behavior and SDK parity aligned with current documented usage.

View File

@@ -0,0 +1,5 @@
---
type: Fixed
pr: 3124
---
Fixes for issue #3124 were applied to keep command/workflow behavior and SDK parity aligned with current documented usage.

View File

@@ -0,0 +1,5 @@
---
type: Fixed
pr: 3125
---
Fixes for issue #3098 were applied to keep command/workflow behavior and SDK parity aligned with current documented usage.

View File

@@ -0,0 +1,6 @@
---
type: Changed
pr: 3131
---
**Re-wired 4 orphaned workflows as flags on parent commands** — six workflows were mis-categorised as "outright deleted dead skills" during the #2790 consolidation; two were caught by prior PRs (#3045, #3038) and four are fixed here. New flags: `/gsd-discuss-phase --assumptions` (surfaces Claude's implementation assumptions before planning), `/gsd-pause-work --report` (generates a post-session summary in `.planning/reports/`), `/gsd-manager --analyze-deps` (scans ROADMAP phases for dependency relationships before parallel execution), `/gsd-import --from-gsd2` (reverse-migrates a GSD-2 `.gsd/` project back to GSD v1 `.planning/` format). Also sweeps 29 stale `/gsd-*` command references across 27 user-facing files (English + 4 locales). Closes #3131.

1
.gitignore vendored
View File

@@ -66,3 +66,4 @@ vendor/
.cache/
tmp/
.worktrees
.envrc

View File

@@ -80,7 +80,7 @@ GSDはそれを解決します。Claude Codeを信頼性の高いものにする
完全なリストは [v1.39.0 リリースノート](https://github.com/gsd-build/get-shit-done/releases/tag/v1.39.0) を参照してください。
- **`--minimal` インストールプロファイル** — エイリアス `--core-only`。メインループの6スキル`new-project``discuss-phase``plan-phase``execute-phase``help``update`)のみをインストールし、`gsd-*` サブエージェントはゼロ。コールドスタート時のシステムプロンプトのオーバーヘッドを ~12kトークンから ~700トークンへ削減≥94%減。32K〜128Kコンテキストのローカル LLM やトークン課金 API に有効。
- **`/gsd-edit-phase`** — `ROADMAP.md` 上の既存フェーズの任意フィールドをその場で編集(番号や位置は変更されない)。`--force` で確認 diff をスキップ、`depends_on` の参照を検証し、書き込み時に `STATE.md` も更新。
- **`/gsd-phase --edit`** — `ROADMAP.md` 上の既存フェーズの任意フィールドをその場で編集(番号や位置は変更されない)。`--force` で確認 diff をスキップ、`depends_on` の参照を検証し、書き込み時に `STATE.md` も更新。
- **マージ後ビルド & テストゲート** — `execute-phase` のステップ 5.6 が `workflow.build_command` の設定を自動検出し、無ければ Xcode`.xcodeproj`、Makefile、Justfile、Cargo、Go、Python、npm の順にフォールバック。Xcode/iOS プロジェクトでは `xcodebuild build``xcodebuild test` を自動実行。並列・直列両モードで動作。
- **ランタイム別レビューモデル選択** — `review.models.<cli>` で各外部レビュー CLIcodex、gemini など)が使うモデルをプランナー/実行プロファイルとは独立に指定可能。
- **ワークストリーム設定の継承** — `GSD_WORKSTREAM` が設定されている場合、ルートの `.planning/config.json` を先に読み込み、ワークストリーム設定をディープマージ(衝突時はワークストリーム側が優先)。ワークストリーム設定で明示的に `null` を指定するとルート値を上書き可能。
@@ -396,7 +396,7 @@ claude --dangerously-skip-permissions
またはGSDに次のステップを自動判定させます
```
/gsd-next # 次のステップを自動検出して実行
/gsd-progress --next # 次のステップを自動検出して実行
```
**discuss → plan → execute → verify → ship** のループをマイルストーン完了まで繰り返します。
@@ -544,7 +544,7 @@ lmn012o feat(08-02): create registration endpoint
| `/gsd-execute-phase <N>` | 全プランを並列ウェーブで実行し、完了時に検証 |
| `/gsd-verify-work [N]` | 手動ユーザー受入テスト ¹ |
| `/gsd-ship [N] [--draft]` | 検証済みのフェーズ作業から自動生成された本文付きのPRを作成 |
| `/gsd-next` | 次の論理的なワークフローステップに自動的に進む |
| `/gsd-progress --next` | 次の論理的なワークフローステップに自動的に進む |
| `/gsd-fast <text>` | インラインの軽微タスク — 計画を完全にスキップし即座に実行 |
| `/gsd-audit-milestone` | マイルストーンが完了の定義を達成したか検証 |
| `/gsd-complete-milestone` | マイルストーンをアーカイブし、リリースをタグ付け |
@@ -565,9 +565,9 @@ lmn012o feat(08-02): create registration endpoint
| コマンド | 説明 |
|---------|--------------|
| `/gsd-new-workspace` | リポジトリのコピーworktreeまたはクローンで隔離されたワークスペースを作成 |
| `/gsd-list-workspaces` | すべてのGSDワークスペースとそのステータスを表示 |
| `/gsd-remove-workspace` | ワークスペースを削除しworktreeをクリーンアップ |
| `/gsd-workspace --new` | リポジトリのコピーworktreeまたはクローンで隔離されたワークスペースを作成 |
| `/gsd-workspace --list` | すべてのGSDワークスペースとそのステータスを表示 |
| `/gsd-workspace --remove` | ワークスペースを削除しworktreeをクリーンアップ |
### UIデザイン
@@ -581,10 +581,9 @@ lmn012o feat(08-02): create registration endpoint
| コマンド | 説明 |
|---------|--------------|
| `/gsd-progress` | 今どこにいる?次は何? |
| `/gsd-next` | 状態を自動検出し次のステップを実行 |
| `/gsd-progress --next` | 状態を自動検出し次のステップを実行 |
| `/gsd-help` | 全コマンドと使い方ガイドを表示 |
| `/gsd-update` | チェンジログプレビュー付きでGSDをアップデート |
| `/gsd-join-discord` | GSD Discordコミュニティに参加 |
| `/gsd-manager` | 複数フェーズ管理用のインタラクティブコマンドセンター |
### ブラウンフィールド
@@ -597,12 +596,12 @@ lmn012o feat(08-02): create registration endpoint
| コマンド | 説明 |
|---------|--------------|
| `/gsd-add-phase` | ロードマップにフェーズを追加 |
| `/gsd-insert-phase [N]` | フェーズ間に緊急作業を挿入 |
| `/gsd-edit-phase [N] [--force]` | 既存フェーズの任意フィールドをその場で編集 — 番号と位置は変更されない |
| `/gsd-remove-phase [N]` | 将来のフェーズを削除し番号を振り直し |
| `/gsd-list-phase-assumptions [N]` | 計画前にClaudeの意図するアプローチを確認 |
| `/gsd-plan-milestone-gaps` | 監査で見つかったギャップを埋めるフェーズを作成 |
| `/gsd-phase` | ロードマップにフェーズを追加 |
| `/gsd-phase --insert [N]` | フェーズ間に緊急作業を挿入 |
| `/gsd-phase --edit [N] [--force]` | 既存フェーズの任意フィールドをその場で編集 — 番号と位置は変更されない |
| `/gsd-phase --remove [N]` | 将来のフェーズを削除し番号を振り直し |
| `/gsd-discuss-phase --assumptions [N]` | 計画前にClaudeの意図するアプローチを確認 |
| `/gsd-audit-milestone --fix` | 監査で見つかったギャップを埋めるフェーズを作成 |
### セッション
@@ -610,7 +609,7 @@ lmn012o feat(08-02): create registration endpoint
|---------|--------------|
| `/gsd-pause-work` | フェーズ途中で停止する際の引き継ぎを作成HANDOFF.jsonを書き込み |
| `/gsd-resume-work` | 前回のセッションから復元 |
| `/gsd-session-report` | 実行した作業と結果のセッションサマリーを生成 |
| `/gsd-pause-work --report` | 実行した作業と結果のセッションサマリーを生成 |
### ワークストリーム
@@ -630,8 +629,8 @@ lmn012o feat(08-02): create registration endpoint
| コマンド | 説明 |
|---------|--------------|
| `/gsd-plant-seed <idea>` | トリガー条件付きの将来志向のアイデアをキャプチャ — 適切なマイルストーンで浮上 |
| `/gsd-add-backlog <desc>` | バックログのパーキングロットにアイデアを追加999.xナンバリング、アクティブシーケンス外 |
| `/gsd-capture --seed <idea>` | トリガー条件付きの将来志向のアイデアをキャプチャ — 適切なマイルストーンで浮上 |
| `/gsd-capture --backlog <desc>` | バックログのパーキングロットにアイデアを追加999.xナンバリング、アクティブシーケンス外 |
| `/gsd-review-backlog` | バックログ項目をレビューし、アクティブマイルストーンに昇格またはstaleエントリを削除 |
| `/gsd-thread [name]` | 永続コンテキストスレッド — 複数セッションにまたがる作業用の軽量クロスセッション知識 |
@@ -640,9 +639,9 @@ lmn012o feat(08-02): create registration endpoint
| コマンド | 説明 |
|---------|--------------|
| `/gsd-settings` | モデルプロファイルとワークフローエージェントを設定 |
| `/gsd-set-profile <profile>` | モデルプロファイルを切り替えquality/balanced/budget/inherit |
| `/gsd-add-todo [desc]` | 後で取り組むアイデアをキャプチャ |
| `/gsd-check-todos` | 保留中のtodoを一覧表示 |
| `/gsd-config --profile <profile>` | モデルプロファイルを切り替えquality/balanced/budget/inherit |
| `/gsd-capture [desc]` | 後で取り組むアイデアをキャプチャ |
| `/gsd-capture --list` | 保留中のtodoを一覧表示 |
| `/gsd-debug [desc]` | 永続状態を持つ体系的デバッグ |
| `/gsd-do <text>` | フリーフォームテキストを適切なGSDコマンドに自動ルーティング |
| `/gsd-note <text>` | ゼロフリクションのアイデアキャプチャ — ートの追加、一覧、todoへの昇格 |
@@ -679,7 +678,7 @@ GSDはプロジェクト設定を `.planning/config.json` に保存します。`
プロファイルの切り替え:
```
/gsd-set-profile budget
/gsd-config --profile budget
```
非AnthropicプロバイダーOpenRouter、ローカルモデルを使用する場合や、現在のランタイムのモデル選択に従う場合OpenCode `/model`)は `inherit` を使用してください。

View File

@@ -80,7 +80,7 @@ GSD가 그걸 고칩니다. Claude Code를 신뢰할 수 있게 만드는 컨텍
전체 목록은 [v1.39.0 릴리스 노트](https://github.com/gsd-build/get-shit-done/releases/tag/v1.39.0)를 참고하세요.
- **`--minimal` 설치 프로파일** — 별칭 `--core-only`. 메인 루프 6개 스킬(`new-project`, `discuss-phase`, `plan-phase`, `execute-phase`, `help`, `update`)만 설치하고 `gsd-*` 서브에이전트는 설치하지 않음. 콜드 스타트 시스템 프롬프트 오버헤드를 ~12k 토큰에서 ~700 토큰으로 축소(≥94% 감소). 32K128K 컨텍스트의 로컬 LLM이나 토큰 과금 API에 유용.
- **`/gsd-edit-phase`** — `ROADMAP.md`에 있는 기존 단계의 임의 필드를 그 자리에서 수정(번호와 위치는 변경되지 않음). `--force`는 확인 diff를 건너뛰고, `depends_on` 참조를 검증하며 쓰기 시 `STATE.md`도 갱신.
- **`/gsd-phase --edit`** — `ROADMAP.md`에 있는 기존 단계의 임의 필드를 그 자리에서 수정(번호와 위치는 변경되지 않음). `--force`는 확인 diff를 건너뛰고, `depends_on` 참조를 검증하며 쓰기 시 `STATE.md`도 갱신.
- **머지 후 빌드 & 테스트 게이트** — `execute-phase` 5.6 단계가 `workflow.build_command` 설정을 우선 자동 감지하고, 없으면 Xcode(`.xcodeproj`), Makefile, Justfile, Cargo, Go, Python, npm 순으로 폴백. Xcode/iOS 프로젝트는 `xcodebuild build``xcodebuild test`를 자동 실행. 병렬·직렬 모드 모두에서 동작.
- **런타임별 리뷰 모델 선택** — `review.models.<cli>`로 각 외부 리뷰 CLI(codex, gemini 등)가 플래너/실행 프로파일과 독립적으로 자체 모델을 선택할 수 있음.
- **워크스트림 설정 상속** — `GSD_WORKSTREAM`이 설정되면 루트 `.planning/config.json`을 먼저 로드한 뒤 워크스트림 설정을 딥 머지(충돌 시 워크스트림 우선). 워크스트림 설정에서 명시적 `null`은 루트 값을 덮어씀.
@@ -396,7 +396,7 @@ claude --dangerously-skip-permissions
또는 GSD가 다음 단계를 자동으로 파악하게 합니다:
```
/gsd-next # 다음 단계 자동 감지 및 실행
/gsd-progress --next # 다음 단계 자동 감지 및 실행
```
마일스톤이 완료될 때까지 **논의 → 기획 → 실행 → 검증 → 출시** 반복.
@@ -541,7 +541,7 @@ lmn012o feat(08-02): create registration endpoint
| `/gsd-execute-phase <N>` | 병렬 웨이브로 모든 계획 실행, 완료 시 검증 |
| `/gsd-verify-work [N]` | 수동 사용자 인수 테스트 ¹ |
| `/gsd-ship [N] [--draft]` | 자동 생성된 본문으로 검증된 단계 작업에서 PR 생성 |
| `/gsd-next` | 다음 논리적 워크플로우 단계로 자동 진행 |
| `/gsd-progress --next` | 다음 논리적 워크플로우 단계로 자동 진행 |
| `/gsd-fast <text>` | 인라인 사소한 작업 — 기획 완전 건너뛰고 즉시 실행 |
| `/gsd-audit-milestone` | 마일스톤이 완료 정의를 달성했는지 검증 |
| `/gsd-complete-milestone` | 마일스톤 아카이브, 릴리스 태그 |
@@ -562,9 +562,9 @@ lmn012o feat(08-02): create registration endpoint
| 명령어 | 역할 |
|---------|------------|
| `/gsd-new-workspace` | 저장소 복사본으로 격리된 워크스페이스 생성 (worktrees 또는 clones) |
| `/gsd-list-workspaces` | 모든 GSD 워크스페이스와 상태 표시 |
| `/gsd-remove-workspace` | 워크스페이스 제거 및 worktree 정리 |
| `/gsd-workspace --new` | 저장소 복사본으로 격리된 워크스페이스 생성 (worktrees 또는 clones) |
| `/gsd-workspace --list` | 모든 GSD 워크스페이스와 상태 표시 |
| `/gsd-workspace --remove` | 워크스페이스 제거 및 worktree 정리 |
### UI 디자인
@@ -578,10 +578,9 @@ lmn012o feat(08-02): create registration endpoint
| 명령어 | 역할 |
|---------|------------|
| `/gsd-progress` | 지금 어디에 있나? 다음은? |
| `/gsd-next` | 상태 자동 감지 및 다음 단계 실행 |
| `/gsd-progress --next` | 상태 자동 감지 및 다음 단계 실행 |
| `/gsd-help` | 모든 명령어와 사용 가이드 표시 |
| `/gsd-update` | 변경 로그 미리보기와 함께 GSD 업데이트 |
| `/gsd-join-discord` | GSD Discord 커뮤니티 참여 |
| `/gsd-manager` | 여러 단계 관리를 위한 대화형 커맨드 센터 |
### 브라운필드
@@ -594,12 +593,12 @@ lmn012o feat(08-02): create registration endpoint
| 명령어 | 역할 |
|---------|------------|
| `/gsd-add-phase` | 로드맵에 단계 추가 |
| `/gsd-insert-phase [N]` | 단계 사이에 긴급 작업 삽입 |
| `/gsd-edit-phase [N] [--force]` | 기존 단계의 임의 필드를 그 자리에서 수정 — 번호와 위치는 그대로 |
| `/gsd-remove-phase [N]` | 미래 단계 제거, 번호 재정렬 |
| `/gsd-list-phase-assumptions [N]` | 기획 전 Claude의 의도된 접근 방식 확인 |
| `/gsd-plan-milestone-gaps` | 감사에서 발견된 갭을 해소하기 위한 단계 생성 |
| `/gsd-phase` | 로드맵에 단계 추가 |
| `/gsd-phase --insert [N]` | 단계 사이에 긴급 작업 삽입 |
| `/gsd-phase --edit [N] [--force]` | 기존 단계의 임의 필드를 그 자리에서 수정 — 번호와 위치는 그대로 |
| `/gsd-phase --remove [N]` | 미래 단계 제거, 번호 재정렬 |
| `/gsd-discuss-phase --assumptions [N]` | 기획 전 Claude의 의도된 접근 방식 확인 |
| `/gsd-audit-milestone --fix` | 감사에서 발견된 갭을 해소하기 위한 단계 생성 |
### 세션
@@ -607,7 +606,7 @@ lmn012o feat(08-02): create registration endpoint
|---------|------------|
| `/gsd-pause-work` | 단계 중간에 멈출 때 핸드오프 생성 (HANDOFF.json 작성) |
| `/gsd-resume-work` | 마지막 세션에서 복원 |
| `/gsd-session-report` | 수행한 작업과 결과가 담긴 세션 요약 생성 |
| `/gsd-pause-work --report` | 수행한 작업과 결과가 담긴 세션 요약 생성 |
### 코드 품질
@@ -621,8 +620,8 @@ lmn012o feat(08-02): create registration endpoint
| 명령어 | 역할 |
|---------|------------|
| `/gsd-plant-seed <idea>` | 트리거 조건이 있는 아이디어 저장 — 때가 되면 알아서 올라옴 |
| `/gsd-add-backlog <desc>` | 백로그 파킹 롯에 아이디어 추가 (999.x 번호 지정, 활성 시퀀스 외부) |
| `/gsd-capture --seed <idea>` | 트리거 조건이 있는 아이디어 저장 — 때가 되면 알아서 올라옴 |
| `/gsd-capture --backlog <desc>` | 백로그 파킹 롯에 아이디어 추가 (999.x 번호 지정, 활성 시퀀스 외부) |
| `/gsd-review-backlog` | 백로그 항목 리뷰 및 활성 마일스톤으로 승격하거나 오래된 항목 제거 |
| `/gsd-thread [name]` | 지속적 컨텍스트 스레드 — 여러 세션에 걸친 작업을 위한 가벼운 크로스 세션 지식 |
@@ -631,9 +630,9 @@ lmn012o feat(08-02): create registration endpoint
| 명령어 | 역할 |
|---------|------------|
| `/gsd-settings` | 모델 프로필 및 워크플로우 에이전트 설정 |
| `/gsd-set-profile <profile>` | 모델 프로필 전환 (quality/balanced/budget/inherit) |
| `/gsd-add-todo [desc]` | 나중을 위한 아이디어 캡처 |
| `/gsd-check-todos` | 대기 중인 할 일 목록 |
| `/gsd-config --profile <profile>` | 모델 프로필 전환 (quality/balanced/budget/inherit) |
| `/gsd-capture [desc]` | 나중을 위한 아이디어 캡처 |
| `/gsd-capture --list` | 대기 중인 할 일 목록 |
| `/gsd-debug [desc]` | 지속적 상태를 이용한 체계적 디버깅 |
| `/gsd-do <text>` | 자유 형식 텍스트를 적절한 GSD 명령어로 자동 라우팅 |
| `/gsd-note <text>` | 마찰 없는 아이디어 캡처 — 추가, 목록, 또는 할 일로 승격 |
@@ -670,7 +669,7 @@ GSD는 프로젝트 설정을 `.planning/config.json`에 저장합니다. `/gsd-
프로필 전환:
```
/gsd-set-profile budget
/gsd-config --profile budget
```
비-Anthropic 제공업체 (OpenRouter, 로컬 모델) 사용 시 또는 현재 런타임 모델 선택을 따를 때 (예: OpenCode `/model`) `inherit`를 사용하세요.

View File

@@ -94,7 +94,7 @@ Built-in quality gates catch real problems: schema drift detection flags ORM cha
See the [v1.39.0 release notes](https://github.com/gsd-build/get-shit-done/releases/tag/v1.39.0) for the full list.
- **`--minimal` install profile** — alias `--core-only`, writes only the six main-loop skills (`new-project`, `discuss-phase`, `plan-phase`, `execute-phase`, `help`, `update`) and zero `gsd-*` subagents. Cuts cold-start system-prompt overhead from ~12k tokens to ~700 (≥94% reduction). Useful for local LLMs with 32K128K context and token-billed APIs.
- **`/gsd-edit-phase`** — modify any field of an existing phase in `ROADMAP.md` in place, without changing its number or position. `--force` skips the confirmation diff; `depends_on` references are validated and `STATE.md` is updated on write.
- **`/gsd-phase --edit`** — modify any field of an existing phase in `ROADMAP.md` in place, without changing its number or position. `--force` skips the confirmation diff; `depends_on` references are validated and `STATE.md` is updated on write.
- **Post-merge build & test gate** — `execute-phase` step 5.6 now auto-detects the build command from `workflow.build_command`, then falls back to Xcode (`.xcodeproj`), Makefile, Justfile, Cargo, Go, Python, or npm. Xcode/iOS projects get `xcodebuild build` + `xcodebuild test` automatically. Runs in both parallel and serial mode.
- **Per-runtime review-model selection** — `review.models.<cli>` lets each external review CLI (codex, gemini, etc.) pick its own model independently of the planner/executor profile.
- **Workstream config inheritance** — when `GSD_WORKSTREAM` is set, the root `.planning/config.json` is loaded first and deep-merged with the workstream config (workstream wins on conflict). Explicit `null` in a workstream config now correctly overrides a root value.
@@ -485,7 +485,7 @@ If everything passes, you move on. If something's broken, you don't manually deb
Or let GSD figure out the next step automatically:
```
/gsd-next # Auto-detect and run next step
/gsd-progress --next # Auto-detect and run next step
```
Loop **discuss → plan → execute → verify → ship** until milestone complete.
@@ -630,7 +630,7 @@ You're never locked in. The system adapts.
| `/gsd-execute-phase <N>` | Execute all plans in parallel waves, verify when complete |
| `/gsd-verify-work [N]` | Manual user acceptance testing ¹ |
| `/gsd-ship [N] [--draft]` | Create PR from verified phase work with auto-generated body |
| `/gsd-next` | Automatically advance to the next logical workflow step |
| `/gsd-progress --next` | Automatically advance to the next logical workflow step |
| `/gsd-fast <text>` | Inline trivial tasks — skips planning entirely, executes immediately |
| `/gsd-audit-milestone` | Verify milestone achieved its definition of done |
| `/gsd-complete-milestone` | Archive milestone, tag release |
@@ -652,8 +652,8 @@ You're never locked in. The system adapts.
| Command | What it does |
|---------|--------------|
| `/gsd-workspace --new` | Create isolated workspace with repo copies (worktrees or clones) |
| `/gsd-list-workspaces` | Show all GSD workspaces and their status |
| `/gsd-remove-workspace` | Remove workspace and clean up worktrees |
| `/gsd-workspace --list` | Show all GSD workspaces and their status |
| `/gsd-workspace --remove` | Remove workspace and clean up worktrees |
### Spiking & Sketching
@@ -661,8 +661,8 @@ You're never locked in. The system adapts.
|---------|--------------|
| `/gsd-spike [idea] [--quick]` | Throwaway experiments to validate feasibility before planning — no project init required |
| `/gsd-sketch [idea] [--quick]` | Throwaway HTML mockups with multi-variant exploration — no project init required |
| `/gsd-spike-wrap-up` | Package spike findings into a project-local skill for future build conversations |
| `/gsd-sketch-wrap-up` | Package sketch design findings into a project-local skill for future builds |
| `/gsd-spike --wrap-up` | Package spike findings into a project-local skill for future build conversations |
| `/gsd-sketch --wrap-up` | Package sketch design findings into a project-local skill for future builds |
### UI Design
@@ -676,10 +676,9 @@ You're never locked in. The system adapts.
| Command | What it does |
|---------|--------------|
| `/gsd-progress` | Where am I? What's next? |
| `/gsd-next` | Auto-detect state and run the next step |
| `/gsd-progress --next` | Auto-detect state and run the next step |
| `/gsd-help` | Show all commands and usage guide |
| `/gsd-update` | Update GSD with changelog preview |
| `/gsd-join-discord` | Join the GSD Discord community |
| `/gsd-manager` | Interactive command center for managing multiple phases |
### Brownfield
@@ -693,11 +692,11 @@ You're never locked in. The system adapts.
| Command | What it does |
|---------|--------------|
| `/gsd-add-phase` | Append phase to roadmap |
| `/gsd-insert-phase [N]` | Insert urgent work between phases |
| `/gsd-edit-phase [N] [--force]` | Modify any field of an existing phase in place — number and position unchanged |
| `/gsd-remove-phase [N]` | Remove future phase, renumber |
| `/gsd-list-phase-assumptions [N]` | See Claude's intended approach before planning |
| `/gsd-phase` | Append phase to roadmap |
| `/gsd-phase --insert [N]` | Insert urgent work between phases |
| `/gsd-phase --edit [N] [--force]` | Modify any field of an existing phase in place — number and position unchanged |
| `/gsd-phase --remove [N]` | Remove future phase, renumber |
| `/gsd-discuss-phase --assumptions [N]` | See Claude's intended approach before planning |
### Session
@@ -705,7 +704,7 @@ You're never locked in. The system adapts.
|---------|--------------|
| `/gsd-pause-work` | Create handoff when stopping mid-phase (writes HANDOFF.json) |
| `/gsd-resume-work` | Restore from last session |
| `/gsd-session-report` | Generate session summary with work performed and outcomes |
| `/gsd-pause-work --report` | Generate session summary with work performed and outcomes |
### Workstreams
@@ -727,8 +726,8 @@ You're never locked in. The system adapts.
| Command | What it does |
|---------|--------------|
| `/gsd-plant-seed <idea>` | Capture forward-looking ideas with trigger conditions — surfaces at the right milestone |
| `/gsd-add-backlog <desc>` | Add idea to backlog parking lot (999.x numbering, outside active sequence) |
| `/gsd-capture --seed <idea>` | Capture forward-looking ideas with trigger conditions — surfaces at the right milestone |
| `/gsd-capture --backlog <desc>` | Add idea to backlog parking lot (999.x numbering, outside active sequence) |
| `/gsd-review-backlog` | Review and promote backlog items to active milestone or remove stale entries |
| `/gsd-thread [name]` | Persistent context threads — lightweight cross-session knowledge for work spanning multiple sessions |
@@ -737,8 +736,8 @@ You're never locked in. The system adapts.
| Command | What it does |
|---------|--------------|
| `/gsd-settings` | Configure model profile and workflow agents |
| `/gsd-set-profile <profile>` | Switch model profile (quality/balanced/budget/inherit) |
| `/gsd-add-todo [desc]` | Capture idea for later |
| `/gsd-config --profile <profile>` | Switch model profile (quality/balanced/budget/inherit) |
| `/gsd-capture [desc]` | Capture idea for later |
| `/gsd-capture --list` | List pending todos |
| `/gsd-debug [desc]` | Systematic debugging with persistent state |
| `/gsd-do <text>` | Route freeform text to the right GSD command automatically |
@@ -779,7 +778,7 @@ Control which Claude model each agent uses. Balance quality vs token spend.
Switch profiles:
```
/gsd-set-profile budget
/gsd-config --profile budget
```
Use `inherit` when using non-Anthropic providers (OpenRouter, local models) or to follow the current runtime model selection (e.g. OpenCode `/model`).

View File

@@ -78,7 +78,7 @@ Quality gates embutidos capturam problemas reais: detecção de schema drift sin
Lista completa nas [notas de release v1.39.0](https://github.com/gsd-build/get-shit-done/releases/tag/v1.39.0).
- **Perfil de instalação `--minimal`** — alias `--core-only`. Instala apenas os 6 skills do loop principal (`new-project`, `discuss-phase`, `plan-phase`, `execute-phase`, `help`, `update`) e nenhum subagente `gsd-*`. Reduz o overhead do system prompt no cold-start de ~12k para ~700 tokens (≥94% de redução). Útil para LLMs locais com contexto de 32K128K e APIs cobradas por token.
- **`/gsd-edit-phase`** — edita qualquer campo de uma fase existente em `ROADMAP.md` no lugar, sem alterar o número ou a posição. `--force` pula o diff de confirmação; referências em `depends_on` são validadas e o `STATE.md` é atualizado na escrita.
- **`/gsd-phase --edit`** — edita qualquer campo de uma fase existente em `ROADMAP.md` no lugar, sem alterar o número ou a posição. `--force` pula o diff de confirmação; referências em `depends_on` são validadas e o `STATE.md` é atualizado na escrita.
- **Build & test gate pós-merge** — o passo 5.6 de `execute-phase` agora detecta automaticamente o comando de build em `workflow.build_command`, com fallback para Xcode (`.xcodeproj`), Makefile, Justfile, Cargo, Go, Python ou npm. Projetos Xcode/iOS rodam `xcodebuild build` e `xcodebuild test` automaticamente. Funciona em modo paralelo e serial.
- **Modelo de review por runtime** — `review.models.<cli>` permite que cada CLI externa de review (codex, gemini, etc.) escolha seu próprio modelo, independente do perfil de planner/executor.
- **Herança de configuração de workstream** — quando `GSD_WORKSTREAM` está definido, o `.planning/config.json` raiz é carregado primeiro e merge-deep com o config da workstream (workstream vence em conflito). Um `null` explícito no config da workstream sobrescreve corretamente o valor raiz.
@@ -259,7 +259,7 @@ Validação manual orientada para confirmar que a feature realmente funciona com
Ou deixe o GSD decidir:
```
/gsd-next
/gsd-progress --next
```
### Modo rápido
@@ -327,7 +327,7 @@ Cada tarefa gera commit próprio, facilitando `git bisect`, rollback e rastreabi
| `/gsd-execute-phase <N>` | Executa planos em ondas paralelas |
| `/gsd-verify-work [N]` | UAT manual |
| `/gsd-ship [N] [--draft]` | Cria PR da fase validada |
| `/gsd-next` | Avança automaticamente para o próximo passo |
| `/gsd-progress --next` | Avança automaticamente para o próximo passo |
| `/gsd-fast <text>` | Tarefas triviais sem planejamento |
| `/gsd-complete-milestone` | Fecha o marco e marca release |
| `/gsd-new-milestone [name]` | Inicia próximo marco |
@@ -339,7 +339,7 @@ Cada tarefa gera commit próprio, facilitando `git bisect`, rollback e rastreabi
| `/gsd-review` | Peer review com múltiplas IAs |
| `/gsd-pr-branch` | Cria branch limpa para PR |
| `/gsd-settings` | Configura perfis e agentes |
| `/gsd-set-profile <profile>` | Troca perfil (quality/balanced/budget/inherit) |
| `/gsd-config --profile <profile>` | Troca perfil (quality/balanced/budget/inherit) |
| `/gsd-quick [--full] [--discuss] [--research]` | Execução rápida com garantias do GSD (`--full` ativa todas as etapas, `--validate` ativa apenas verificação) |
| `/gsd-health [--repair]` | Verifica e repara `.planning/` |
@@ -370,7 +370,7 @@ Você pode configurar no `/gsd-new-project` ou ajustar depois com `/gsd-settings
Troca rápida:
```
/gsd-set-profile budget
/gsd-config --profile budget
```
---

View File

@@ -78,7 +78,7 @@ GSD 解决的就是这个问题。它是让 Claude Code 变得可靠的上下文
完整列表请参阅 [v1.39.0 发行说明](https://github.com/gsd-build/get-shit-done/releases/tag/v1.39.0)。
- **`--minimal` 安装档** — 别名 `--core-only`。仅安装主循环的 6 个核心技能(`new-project``discuss-phase``plan-phase``execute-phase``help``update`),不安装任何 `gsd-*` 子代理。将冷启动系统提示开销从 ~12k token 降至 ~700 token≥94% 减少)。适合 32K128K 上下文的本地 LLM 和按 token 计费的 API。
- **`/gsd-edit-phase`** — 就地修改 `ROADMAP.md` 中已有阶段的任意字段,不改变其编号或位置。`--force` 跳过确认 diff验证 `depends_on` 引用,并在写入时更新 `STATE.md`
- **`/gsd-phase --edit`** — 就地修改 `ROADMAP.md` 中已有阶段的任意字段,不改变其编号或位置。`--force` 跳过确认 diff验证 `depends_on` 引用,并在写入时更新 `STATE.md`
- **合并后构建与测试门** — `execute-phase` 步骤 5.6 优先自动检测 `workflow.build_command` 配置,否则按 Xcode`.xcodeproj`、Makefile、Justfile、Cargo、Go、Python、npm 顺序回退。Xcode/iOS 项目自动运行 `xcodebuild build``xcodebuild test`。在并行与串行模式下均生效。
- **每运行时评审模型选择** — `review.models.<cli>` 让每个外部评审 CLIcodex、gemini 等)独立于规划/执行档选择自己的模型。
- **工作流设置继承** — 设置 `GSD_WORKSTREAM` 后,先加载根 `.planning/config.json`,再与该工作流的配置进行深合并(冲突时工作流优先)。工作流配置中显式 `null` 会覆盖根值。
@@ -396,7 +396,7 @@ claude --dangerously-skip-permissions
或者让 GSD 自动判断下一步:
```
/gsd-next # 自动检测并执行下一步
/gsd-progress --next # 自动检测并执行下一步
```
循环执行 **讨论 → 规划 → 执行 → 验证 → 发布**,直到整个里程碑完成。
@@ -538,7 +538,7 @@ lmn012o feat(08-02): create registration endpoint
| `/gsd-verify-work [N]` | 人工用户验收测试 ¹ |
| `/gsd-ship [N] [--draft]` | 从已验证的阶段工作创建 PR自动生成 PR 描述 |
| `/gsd-fast <text>` | 内联处理琐碎任务——完全跳过规划,立即执行 |
| `/gsd-next` | 自动推进到下一个逻辑工作流步骤 |
| `/gsd-progress --next` | 自动推进到下一个逻辑工作流步骤 |
| `/gsd-audit-milestone` | 验证里程碑是否达到完成定义 |
| `/gsd-complete-milestone` | 归档里程碑并打 release tag |
| `/gsd-new-milestone [name]` | 开始下一个版本:提问 → 研究 → 需求 → 路线图 |
@@ -558,9 +558,9 @@ lmn012o feat(08-02): create registration endpoint
| 命令 | 作用 |
|------|------|
| `/gsd-new-workspace` | 创建隔离工作区包含仓库副本worktree 或 clone |
| `/gsd-list-workspaces` | 显示所有 GSD 工作区及其状态 |
| `/gsd-remove-workspace` | 移除工作区并清理 worktree |
| `/gsd-workspace --new` | 创建隔离工作区包含仓库副本worktree 或 clone |
| `/gsd-workspace --list` | 显示所有 GSD 工作区及其状态 |
| `/gsd-workspace --remove` | 移除工作区并清理 worktree |
### UI 设计
@@ -574,10 +574,9 @@ lmn012o feat(08-02): create registration endpoint
| 命令 | 作用 |
|------|------|
| `/gsd-progress` | 我现在在哪?下一步是什么? |
| `/gsd-next` | 自动检测状态并执行下一步 |
| `/gsd-progress --next` | 自动检测状态并执行下一步 |
| `/gsd-help` | 显示全部命令和使用指南 |
| `/gsd-update` | 更新 GSD并预览变更日志 |
| `/gsd-join-discord` | 加入 GSD Discord 社区 |
### Brownfield
@@ -589,12 +588,12 @@ lmn012o feat(08-02): create registration endpoint
| 命令 | 作用 |
|------|------|
| `/gsd-add-phase` | 在路线图末尾追加 phase |
| `/gsd-insert-phase [N]` | 在 phase 之间插入紧急工作 |
| `/gsd-edit-phase [N] [--force]` | 就地修改已有 phase 的任意字段 — 编号与位置保持不变 |
| `/gsd-remove-phase [N]` | 删除未来 phase并重编号 |
| `/gsd-list-phase-assumptions [N]` | 在规划前查看 Claude 打算采用的方案 |
| `/gsd-plan-milestone-gaps` | 为 audit 发现的缺口创建 phase |
| `/gsd-phase` | 在路线图末尾追加 phase |
| `/gsd-phase --insert [N]` | 在 phase 之间插入紧急工作 |
| `/gsd-phase --edit [N] [--force]` | 就地修改已有 phase 的任意字段 — 编号与位置保持不变 |
| `/gsd-phase --remove [N]` | 删除未来 phase并重编号 |
| `/gsd-discuss-phase --assumptions [N]` | 在规划前查看 Claude 打算采用的方案 |
| `/gsd-audit-milestone --fix` | 为 audit 发现的缺口创建 phase |
### 代码质量
@@ -608,7 +607,7 @@ lmn012o feat(08-02): create registration endpoint
| 命令 | 作用 |
|------|------|
| `/gsd-plant-seed <idea>` | 将想法存入积压停车场,留待未来里程碑 |
| `/gsd-capture --seed <idea>` | 将想法存入积压停车场,留待未来里程碑 |
### 会话
@@ -616,16 +615,16 @@ lmn012o feat(08-02): create registration endpoint
|------|------|
| `/gsd-pause-work` | 在中途暂停时创建交接上下文(写入 HANDOFF.json |
| `/gsd-resume-work` | 从上一次会话恢复 |
| `/gsd-session-report` | 生成会话摘要,包含已完成工作和结果 |
| `/gsd-pause-work --report` | 生成会话摘要,包含已完成工作和结果 |
### 工具
| 命令 | 作用 |
|------|------|
| `/gsd-settings` | 配置模型 profile 和工作流代理 |
| `/gsd-set-profile <profile>` | 切换模型 profilequality / balanced / budget / inherit |
| `/gsd-add-todo [desc]` | 记录一个待办想法 |
| `/gsd-check-todos` | 查看待办列表 |
| `/gsd-config --profile <profile>` | 切换模型 profilequality / balanced / budget / inherit |
| `/gsd-capture [desc]` | 记录一个待办想法 |
| `/gsd-capture --list` | 查看待办列表 |
| `/gsd-debug [desc]` | 使用持久状态进行系统化调试 |
| `/gsd-do <text>` | 将自由文本自动路由到正确的 GSD 命令 |
| `/gsd-note <text>` | 零摩擦想法捕捉——追加、列出或提升为待办 |
@@ -662,7 +661,7 @@ GSD 将项目设置保存在 `.planning/config.json`。你可以在 `/gsd-new-pr
切换方式:
```
/gsd-set-profile budget
/gsd-config --profile budget
```
使用非 Anthropic 提供商OpenRouter、本地模型或想跟随当前运行时的模型选择时如 OpenCode 的 `/model`),可用 `inherit`

View File

@@ -74,7 +74,7 @@ Extract from init JSON: `executor_model`, `commit_docs`, `sub_repos`, `phase_dir
Also load planning state (position, decisions, blockers) via the SDK — **use `node` to invoke the CLI** (not `npx`):
```bash
node ./node_modules/@gsd-build/sdk/dist/cli.js query state.load 2>/dev/null
gsd-sdk 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`.

View File

@@ -655,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
node ./node_modules/@gsd-build/sdk/dist/cli.js query phase.list-plans "$phase_number"
gsd-sdk 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
gsd-sdk query phase.list-artifacts "$phase_number" --type research
gsd-sdk query roadmap.get-phase "$phase_number"
gsd-sdk query phase.list-artifacts "$phase_number" --type summary
```
**Extract:** Phase goal, requirements (decompose goal), locked decisions, deferred ideas.
@@ -747,7 +747,7 @@ The `tasks` array in the result shows each task's completeness:
**For manual validation of specificity** (`verify.plan-structure` checks structure, not content quality), use structured extraction instead of grepping raw XML:
```bash
node ./node_modules/@gsd-build/sdk/dist/cli.js query plan.task-structure "$PLAN_PATH"
gsd-sdk query plan.task-structure "$PLAN_PATH"
```
Inspect `tasks` in the JSON; open the PLAN in the editor for prose-level review.
@@ -774,8 +774,8 @@ Missing: No mention of fetch/API call → Issue: Key link not planned
## Step 8: Assess Scope
```bash
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
gsd-sdk query plan.task-structure "$PHASE_DIR/$PHASE-01-PLAN.md"
gsd-sdk query frontmatter.get "$PHASE_DIR/$PHASE-01-PLAN.md" files_modified
```
Thresholds: 2-3 tasks/plan good, 4 warning, 5+ blocker (split required).

View File

@@ -814,7 +814,7 @@ Extract from init JSON: `planner_model`, `researcher_model`, `checker_model`, `c
Also load planning state (position, decisions, blockers) via the SDK — **use `node` to invoke the CLI** (not `npx`):
```bash
node ./node_modules/@gsd-build/sdk/dist/cli.js query state.load 2>/dev/null
gsd-sdk 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`.

View File

@@ -560,7 +560,7 @@ When files are written and returning to orchestrator:
### Files Ready for Review
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`.
User can review actual files in the editor or via SDK queries (e.g. `gsd-sdk query roadmap.analyze` and `gsd-sdk query state.load`) instead of ad-hoc shell `cat`.
{If gaps found during creation:}

View File

@@ -1,7 +1,7 @@
---
name: gsd:discuss-phase
description: Gather phase context through adaptive questioning before planning.
argument-hint: "<phase> [--all] [--auto] [--chain] [--batch] [--analyze] [--text] [--power]"
argument-hint: "<phase> [--all] [--auto] [--chain] [--batch] [--analyze] [--text] [--power] [--assumptions]"
allowed-tools:
- Read
- Write
@@ -49,10 +49,14 @@ Context files are resolved in-workflow using `init phase-op` and roadmap/state t
DISCUSS_MODE=$(gsd-sdk query config-get workflow.discuss_mode 2>/dev/null || echo "discuss")
```
If `DISCUSS_MODE` is `"assumptions"`:
If `--assumptions` is in $ARGUMENTS:
Read and execute `~/.claude/get-shit-done/workflows/list-phase-assumptions.md` end-to-end.
Stop here.
Otherwise, if `DISCUSS_MODE` is `"assumptions"`:
Read and execute `~/.claude/get-shit-done/workflows/discuss-phase-assumptions.md` end-to-end.
If `DISCUSS_MODE` is `"discuss"` (or unset, or any other value):
Otherwise (`"discuss"` / unset / any other value):
Read and execute `~/.claude/get-shit-done/workflows/discuss-phase.md` end-to-end.
**MANDATORY:** Read the appropriate workflow file BEFORE taking any action. The objective and success_criteria sections in this command file are summaries — the workflow file contains the complete step-by-step process with all required behaviors, config checks, and interaction patterns. Do not improvise from the summary.

View File

@@ -1,7 +1,7 @@
---
name: gsd:import
description: Ingest external plans with conflict detection against project decisions before writing anything.
argument-hint: "--from <filepath>"
argument-hint: "--from <filepath> | --from-gsd2"
allowed-tools:
- Read
- Write
@@ -17,8 +17,7 @@ allowed-tools:
Import external plan files into the GSD planning system with conflict detection against PROJECT.md decisions.
- **--from**: Import an external plan file, detect conflicts, write as GSD PLAN.md, validate via gsd-plan-checker.
Future: `--prd` mode for PRD extraction is planned for a follow-up PR.
- **--from-gsd2**: Reverse-migrate a GSD-2 project (`.gsd/` directory) back to GSD v1 (`.planning/`) format. Runs `gsd-tools.cjs from-gsd2`. Pass `--path <dir>` to migrate a project at a different path.
</objective>
<execution_context>
@@ -33,5 +32,10 @@ $ARGUMENTS
</context>
<process>
Execute the import workflow end-to-end.
If `--from-gsd2` is in $ARGUMENTS:
Run: `node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" from-gsd2`
Pass `--path <dir>` if provided. Present the migration result to the user.
Stop here (do not run the standard import workflow).
Otherwise, execute the import workflow end-to-end.
</process>

View File

@@ -1,6 +1,7 @@
---
name: gsd:manager
description: Interactive command center for managing multiple phases from one terminal
argument-hint: "[--analyze-deps]"
allowed-tools:
- Read
- Write
@@ -35,6 +36,9 @@ Project context, phase list, dependencies, and recommendations are resolved insi
</context>
<process>
If `--analyze-deps` is in $ARGUMENTS:
Read and execute `~/.claude/get-shit-done/workflows/analyze-dependencies.md` end-to-end.
Execute the manager workflow from @~/.claude/get-shit-done/workflows/manager.md end-to-end.
Maintain the dashboard refresh loop until the user exits or all phases complete.
</process>

View File

@@ -1,6 +1,7 @@
---
name: gsd:pause-work
description: Create context handoff when pausing work mid-phase
argument-hint: "[--report]"
allowed-tools:
- Read
- Write
@@ -27,6 +28,9 @@ State and phase progress are gathered in-workflow with targeted reads.
</context>
<process>
If `--report` is in $ARGUMENTS:
Read and execute `~/.claude/get-shit-done/workflows/session-report.md` end-to-end.
**Follow the pause-work workflow** from `@~/.claude/get-shit-done/workflows/pause-work.md`.
The workflow handles all logic including:

View File

@@ -480,7 +480,7 @@ Communication style, decision patterns, debugging approach, UX preferences, vend
## Advanced and Specialized Agents
Twelve additional agents ship under `agents/gsd-*.md` and are used by specialty workflows (`/gsd-ai-integration-phase`, `/gsd-eval-review`, `/gsd-code-review`, `/gsd-code-review --fix`, `/gsd-debug`, `/gsd-intel`, `/gsd-select-framework`, `/gsd-ingest-docs`) and by the planner pipeline. Each carries full frontmatter in its agent file; the stubs below are concise by design. The authoritative roster (with spawner and primary-doc status per agent) lives in [`docs/INVENTORY.md`](INVENTORY.md).
Twelve additional agents ship under `agents/gsd-*.md` and are used by specialty workflows (`/gsd-ai-integration-phase`, `/gsd-eval-review`, `/gsd-code-review`, `/gsd-code-review --fix`, `/gsd-debug`, `/gsd-map-codebase --query`, `/gsd-select-framework`, `/gsd-ingest-docs`) and by the planner pipeline. Each carries full frontmatter in its agent file; the stubs below are concise by design. The authoritative roster (with spawner and primary-doc status per agent) lives in [`docs/INVENTORY.md`](INVENTORY.md).
### gsd-pattern-mapper
@@ -669,7 +669,7 @@ Twelve additional agents ship under `agents/gsd-*.md` and are used by specialty
| Property | Value |
|----------|-------|
| **Spawned by** | `/gsd-intel` (refresh / update flows) |
| **Spawned by** | `/gsd-map-codebase --query` (refresh / update flows) |
| **Parallelism** | Single instance |
| **Tools** | Read, Write, Bash, Glob, Grep |
| **Model (balanced)** | Sonnet |

View File

@@ -535,7 +535,7 @@ Equivalent paths for other runtimes:
│ ├── pending/ # Captured ideas
│ └── done/ # Completed todos
├── threads/ # Persistent context threads (from /gsd-thread)
├── seeds/ # Forward-looking ideas (from /gsd-plant-seed)
├── seeds/ # Forward-looking ideas (from /gsd-capture --seed)
├── debug/ # Active debug sessions
│ ├── *.md # Active sessions
│ ├── resolved/ # Archived sessions

View File

@@ -479,7 +479,7 @@ User-facing entry point: `/gsd-graphify` (see [Command Reference](COMMANDS.md#gs
| Learnings | `lib/learnings.cjs` | Extract learnings from phases/SUMMARY artifacts (backs `/gsd-extract-learnings`) |
| 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`) |
| Intel | `lib/intel.cjs` | Queryable codebase intelligence index (backs `/gsd-map-codebase --query`) |
---

View File

@@ -97,6 +97,7 @@ Gather phase context through adaptive questioning before planning.
| `--batch` | Group questions for batch intake instead of one-by-one |
| `--analyze` | Add trade-off analysis during discussion |
| `--power` | File-based bulk question answering from a prepared answers file |
| `--assumptions` | Surface Claude's implementation assumptions about the phase without an interactive session |
**Prerequisites:** `.planning/ROADMAP.md` exists
**Produces:** `{phase}-CONTEXT.md`, `{phase}-DISCUSSION-LOG.md` (audit trail)
@@ -108,6 +109,7 @@ Gather phase context through adaptive questioning before planning.
/gsd-discuss-phase --batch # Batch mode for current phase
/gsd-discuss-phase 2 --analyze # Discussion with trade-off analysis
/gsd-discuss-phase 1 --power # Bulk answers from file
/gsd-discuss-phase 3 --assumptions # Surface Claude's assumptions before planning
```
---
@@ -435,7 +437,7 @@ Show status, next steps, and automatically advance to the next logical workflow
| `--do "task description"` | Analyze freeform intent and dispatch to the most appropriate GSD command |
| `--forensic` | Append a 6-check integrity audit after the standard report (STATE consistency, orphaned handoffs, deferred scope drift, memory-flagged pending work, blocking todos, uncommitted code) |
**Auto-routing behavior (absorbed from `/gsd-next`):**
**Auto-routing behavior (`--next`):**
- No project → suggests `/gsd-new-project`
- Phase needs discussion → runs `/gsd-discuss-phase`
- Phase needs planning → runs `/gsd-plan-phase`
@@ -462,8 +464,13 @@ Restore full context from last session.
Save context handoff when stopping mid-phase.
| Flag | Description |
|------|-------------|
| `--report` | Generate a post-session summary in `.planning/reports/` capturing commits, file changes, and phase progress |
```bash
/gsd-pause-work # Creates continue-here.md
/gsd-pause-work --report # Creates continue-here.md + session report
```
### `/gsd-manager`
@@ -480,6 +487,7 @@ Interactive command center for managing multiple phases from one terminal.
```bash
/gsd-manager # Open command center dashboard
/gsd-manager --analyze-deps # Scan ROADMAP phases for dependency relationships before parallel execution
```
**Checkpoint Heartbeats (#2410):**
@@ -570,13 +578,17 @@ Safe git revert — roll back GSD phase or plan commits using the phase manifest
Ingest an external plan file into the GSD planning system with conflict detection against `PROJECT.md` decisions before writing anything.
| Flag | Required | Description |
|------|----------|-------------|
| `--from <filepath>` | **Yes** | Path to the external plan file to import |
|------|----------|--------------|
| `--from <filepath>` | Yes (or `--from-gsd2`) | Path to the external plan file to import |
| `--from-gsd2` | Yes (or `--from`) | Reverse-migrate a GSD-2 (`.gsd/`) project back to GSD v1 (`.planning/`) format |
| `--path <dir>` | No | With `--from-gsd2`: path to the GSD-2 project directory (defaults to current directory) |
**Process:** Detects conflicts → prompts for resolution → writes as GSD PLAN.md → validates via `gsd-plan-checker`
```bash
/gsd-import --from /tmp/team-plan.md # Import and validate an external plan
/gsd-import --from /tmp/team-plan.md # Import and validate an external plan
/gsd-import --from-gsd2 # Migrate from GSD-2 back to v1 (current dir)
/gsd-import --from-gsd2 --path ~/old-project # Migrate from a different path
```
---

View File

@@ -352,7 +352,7 @@ Toggle optional capabilities via the `features.*` config namespace. Feature flag
| `features.thinking_partner` | boolean | `false` | Enable thinking partner analysis at workflow decision points |
| `features.global_learnings` | boolean | `false` | Enable cross-project learnings pipeline (auto-copy at phase completion, planner injection) |
| `learnings.max_inject` | number | `10` | Maximum number of cross-project learnings injected into each planner prompt. Lower values reduce prompt size; higher values provide broader historical context |
| `intel.enabled` | boolean | `false` | Enable queryable codebase intelligence system. When `true`, `/gsd-intel` commands build and query a JSON index in `.planning/intel/`. Added in v1.34 |
| `intel.enabled` | boolean | `false` | Enable queryable codebase intelligence system. When `true`, `/gsd-map-codebase --query` commands build and query a JSON index in `.planning/intel/`. Added in v1.34 |
<a id="graphify-settings"></a>
### Graphify Settings

View File

@@ -120,7 +120,7 @@
- [Autonomous Audit-to-Fix](#98-autonomous-audit-to-fix)
- [Improved Prompt Injection Scanner](#99-improved-prompt-injection-scanner)
- [Stall Detection in Plan-Phase](#100-stall-detection-in-plan-phase)
- [Hard Stop Safety Gates in /gsd-next](#101-hard-stop-safety-gates-in-gsd-next)
- [Hard Stop Safety Gates in /gsd-progress --next](#101-hard-stop-safety-gates-in-gsd-progress---next)
- [Adaptive Model Preset](#102-adaptive-model-preset)
- [Post-Merge Hunk Verification](#103-post-merge-hunk-verification)
- [v1.35.0 Features](#v1350-features)
@@ -461,7 +461,7 @@
### 9. Phase Management
**Commands:** `/gsd-add-phase`, `/gsd-insert-phase [N]`, `/gsd-remove-phase [N]`
**Commands:** `/gsd-phase`, `/gsd-phase --insert [N]`, `/gsd-phase --remove [N]`
**Purpose:** Dynamic roadmap modification during development.
@@ -539,7 +539,7 @@
### 14. Auto-Advance (Next)
**Command:** `/gsd-next`
**Command:** `/gsd-progress --next`
**Purpose:** Automatically detect current project state and advance to the next logical workflow step, eliminating the need to remember which phase/step you're on.
@@ -709,7 +709,7 @@
### 24. Session Reporting
**Command:** `/gsd-session-report`
**Command:** `/gsd-pause-work --report`
**Purpose:** Generate a structured post-session summary document capturing work performed, outcomes achieved, and estimated resource usage.
@@ -748,7 +748,7 @@
### 26. Model Profiles
**Command:** `/gsd-set-profile <quality|balanced|budget|inherit>`
**Command:** `/gsd-config --profile <quality|balanced|budget|adaptive|inherit>`
**Purpose:** Control which AI model each agent uses, balancing quality vs cost.
@@ -869,7 +869,7 @@ continues. Drift detection cannot fail verification.
### 29. Todo Management
**Commands:** `/gsd-add-todo [desc]`, `/gsd-capture --list`
**Commands:** `/gsd-capture [desc]`, `/gsd-capture --list`
**Purpose:** Capture ideas and tasks during sessions for later work.
@@ -1185,7 +1185,7 @@ When verification returns `human_needed`, items are persisted as a trackable HUM
### 43. Backlog Parking Lot
**Commands:** `/gsd-add-backlog <description>`, `/gsd-review-backlog`, `/gsd-plant-seed <idea>`
**Commands:** `/gsd-capture --backlog <description>`, `/gsd-review-backlog`, `/gsd-capture --seed <idea>`
**Purpose:** Capture ideas that aren't ready for active planning. Backlog items use 999.x numbering to stay outside the active phase sequence. Seeds are forward-looking ideas with trigger conditions that surface automatically at the right milestone.
@@ -1845,7 +1845,7 @@ Test suite that scans all agent, workflow, and command files for embedded inject
### 77. Phase Dependency Analysis
**Command:** `/gsd-analyze-dependencies`
**Command:** `/gsd-manager --analyze-deps`
**Purpose:** Detect phase dependencies and suggest `Depends on` entries for ROADMAP.md before running `/gsd-manager`.
@@ -2028,7 +2028,7 @@ Test suite that scans all agent, workflow, and command files for embedded inject
- [Autonomous Audit-to-Fix](#98-autonomous-audit-to-fix)
- [Improved Prompt Injection Scanner](#99-improved-prompt-injection-scanner)
- [Stall Detection in Plan-Phase](#100-stall-detection-in-plan-phase)
- [Hard Stop Safety Gates in /gsd-next](#101-hard-stop-safety-gates-in-gsd-next)
- [Hard Stop Safety Gates in /gsd-progress --next](#101-hard-stop-safety-gates-in-gsd-progress---next)
- [Adaptive Model Preset](#102-adaptive-model-preset)
- [Post-Merge Hunk Verification](#103-post-merge-hunk-verification)
@@ -2245,14 +2245,14 @@ Test suite that scans all agent, workflow, and command files for embedded inject
---
### 101. Hard Stop Safety Gates in /gsd-next
### 101. Hard Stop Safety Gates in /gsd-progress --next
**Command:** `/gsd-next`
**Command:** `/gsd-progress --next`
**Purpose:** Prevent `/gsd-next` from entering runaway loops by adding hard stop safety gates and a consecutive-call guard that interrupts autonomous chaining when repeated identical steps are detected.
**Purpose:** Prevent `/gsd-progress --next` from entering runaway loops by adding hard stop safety gates and a consecutive-call guard that interrupts autonomous chaining when repeated identical steps are detected.
**Requirements:**
- REQ-NEXT-GATE-01: `/gsd-next` MUST track consecutive same-step calls
- REQ-NEXT-GATE-01: `/gsd-progress --next` MUST track consecutive same-step calls
- REQ-NEXT-GATE-02: On repeated same-step, system MUST present a hard stop gate to the user
- REQ-NEXT-GATE-03: User MUST explicitly confirm to continue past a hard stop gate
@@ -2266,7 +2266,7 @@ Test suite that scans all agent, workflow, and command files for embedded inject
**Requirements:**
- REQ-ADAPTIVE-01: `adaptive` preset MUST assign model tiers based on agent role (planner → quality tier, executor → balanced tier, etc.)
- REQ-ADAPTIVE-02: `adaptive` MUST be selectable via `/gsd-set-profile adaptive`
- REQ-ADAPTIVE-02: `adaptive` MUST be selectable via `/gsd-config --profile adaptive`
---
@@ -2531,7 +2531,7 @@ Users who run a memory / knowledge-base MCP server (for example, ExoCortex-style
**Command:** `/gsd-spike [idea] [--quick]`
**Purpose:** Run 25 focused feasibility experiments before committing to an implementation approach. Each experiment uses Given/When/Then framing, produces executable code, and returns a VALIDATED / INVALIDATED / PARTIAL verdict. Companion `/gsd-spike-wrap-up` packages findings into a project-local skill.
**Purpose:** Run 25 focused feasibility experiments before committing to an implementation approach. Each experiment uses Given/When/Then framing, produces executable code, and returns a VALIDATED / INVALIDATED / PARTIAL verdict. Companion `/gsd-spike --wrap-up` packages findings into a project-local skill.
**Requirements:**
- REQ-SPIKE-01: Each experiment MUST produce a Given/When/Then hypothesis before any code is written
@@ -2539,14 +2539,15 @@ Users who run a memory / knowledge-base MCP server (for example, ExoCortex-style
- REQ-SPIKE-03: Each experiment MUST return one of: VALIDATED, INVALIDATED, or PARTIAL verdict with evidence
- REQ-SPIKE-04: Results MUST be stored in `.planning/spikes/NNN-experiment-name/` with a README and MANIFEST.md
- REQ-SPIKE-05: `--quick` flag skips intake conversation and uses the argument text as the experiment direction
- REQ-SPIKE-06: `/gsd-spike-wrap-up` MUST package findings into `.claude/skills/spike-findings-[project]/`
- REQ-SPIKE-06: `/gsd-spike --wrap-up` MUST package findings into `.claude/skills/spike-findings-[project]/`
**Produces:**
| Artifact | Description |
|----------|-------------|
| `.planning/spikes/NNN-name/README.md` | Hypothesis, experiment code, verdict, and evidence |
| `.planning/spikes/MANIFEST.md` | Index of all spikes with verdicts |
| `.claude/skills/spike-findings-[project]/` | Packaged findings (via `/gsd-spike-wrap-up`) |
| `.claude/skills/spike-findings-[project]/` | Packaged findings (via `/gsd-spike --wrap-up`) |
---
@@ -2554,7 +2555,7 @@ Users who run a memory / knowledge-base MCP server (for example, ExoCortex-style
**Command:** `/gsd-sketch [idea] [--quick] [--text]`
**Purpose:** Explore design directions through throwaway HTML mockups before committing to implementation. Produces 23 interactive variants per design question, all viewable directly in a browser with no build step. Companion `/gsd-sketch-wrap-up` packages winning decisions into a project-local skill.
**Purpose:** Explore design directions through throwaway HTML mockups before committing to implementation. Produces 23 interactive variants per design question, all viewable directly in a browser with no build step. Companion `/gsd-sketch --wrap-up` packages winning decisions into a project-local skill.
**Requirements:**
- REQ-SKETCH-01: Each sketch MUST answer one specific visual design question
@@ -2564,7 +2565,7 @@ Users who run a memory / knowledge-base MCP server (for example, ExoCortex-style
- REQ-SKETCH-05: A shared `themes/default.css` MUST provide CSS variables adapted to the agreed aesthetic
- REQ-SKETCH-06: `--quick` flag skips mood intake; `--text` flag replaces `AskUserQuestion` with numbered lists for non-Claude runtimes
- REQ-SKETCH-07: The winning variant MUST be marked in the README frontmatter and with a ★ in the HTML tab
- REQ-SKETCH-08: `/gsd-sketch-wrap-up` MUST package winning decisions into `.claude/skills/sketch-findings-[project]/`
- REQ-SKETCH-08: `/gsd-sketch --wrap-up` MUST package winning decisions into `.claude/skills/sketch-findings-[project]/`
**Produces:**
| Artifact | Description |
@@ -2573,7 +2574,7 @@ Users who run a memory / knowledge-base MCP server (for example, ExoCortex-style
| `.planning/sketches/NNN-name/README.md` | Design question, variants, winner, what to look for |
| `.planning/sketches/themes/default.css` | Shared CSS theme variables |
| `.planning/sketches/MANIFEST.md` | Index of all sketches with winners |
| `.claude/skills/sketch-findings-[project]/` | Packaged decisions (via `/gsd-sketch-wrap-up`) |
| `.claude/skills/sketch-findings-[project]/` | Packaged decisions (via `/gsd-sketch --wrap-up`) |
---

View File

@@ -172,7 +172,7 @@ Full roster at `get-shit-done/workflows/*.md`. Workflows are thin orchestrators
| `add-tests.md` | Generate unit and E2E tests for a completed phase based on its artifacts. | `/gsd-add-tests` |
| `add-todo.md` | Capture an idea or task that surfaces during a session as a structured todo. | `/gsd-capture` (default), `/gsd-capture --backlog` |
| `ai-integration-phase.md` | Orchestrate framework selection → AI research → domain research → eval planning into AI-SPEC.md. | `/gsd-ai-integration-phase` |
| `analyze-dependencies.md` | Analyze ROADMAP.md phases for file overlap and semantic dependencies; suggest `Depends on` edges. | `/gsd-analyze-dependencies` |
| `analyze-dependencies.md` | Analyze ROADMAP.md phases for file overlap and semantic dependencies; suggest `Depends on` edges. | `/gsd-manager --analyze-deps` |
| `audit-fix.md` | Autonomous audit-to-fix pipeline — run audit, parse, classify, fix, test, commit. | `/gsd-audit-fix` |
| `audit-milestone.md` | Verify milestone met its definition of done by aggregating phase verifications. | `/gsd-audit-milestone` |
| `audit-uat.md` | Cross-phase audit of UAT and verification files; produces prioritized outstanding-items list. | `/gsd-audit-uat` |
@@ -204,7 +204,7 @@ Full roster at `get-shit-done/workflows/*.md`. Workflows are thin orchestrators
| `inbox.md` | Triage open GitHub issues and PRs against project contribution templates. | `/gsd-inbox` |
| `ingest-docs.md` | Scan a repo for mixed planning docs; classify, synthesize, and bootstrap or merge into `.planning/` with a conflicts report. | `/gsd-ingest-docs` |
| `insert-phase.md` | Insert a decimal phase for urgent work discovered mid-milestone. | `/gsd-phase --insert` |
| `list-phase-assumptions.md` | Surface Claude's assumptions about a phase before planning. | `/gsd-list-phase-assumptions` |
| `list-phase-assumptions.md` | Surface Claude's assumptions about a phase before planning. | `/gsd-discuss-phase --assumptions` |
| `list-workspaces.md` | List all GSD workspaces found in `~/gsd-workspaces/` with their status. | `/gsd-workspace --list` |
| `manager.md` | Interactive milestone command center — dashboard, inline discuss, background plan/execute. | `/gsd-manager` |
| `map-codebase.md` | Orchestrate parallel codebase mapper agents to produce `.planning/codebase/` docs. | `/gsd-map-codebase` |
@@ -230,7 +230,7 @@ Full roster at `get-shit-done/workflows/*.md`. Workflows are thin orchestrators
| `review.md` | Cross-AI plan review via external CLIs; produces REVIEWS.md. | `/gsd-review` |
| `scan.md` | Rapid single-focus codebase scan — lightweight alternative to map-codebase. | `/gsd-scan` |
| `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` |
| `session-report.md` | Session report — token usage, work summary, outcomes. | `/gsd-pause-work --report` |
| `settings.md` | Configure GSD workflow toggles and model profile. | `/gsd-settings`, `/gsd-config --profile` |
| `settings-advanced.md` | Configure GSD power-user knobs — plan bounce, timeouts, branch templates, cross-AI execution, runtime knobs. | `/gsd-config --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-config --integrations` |

View File

@@ -261,7 +261,7 @@ For multi-phase projects, repeat the loop:
Or let GSD figure out the next step automatically:
```
/gsd-next
/gsd-progress --next
```
When all phases are done:
@@ -870,11 +870,11 @@ claude --dangerously-skip-permissions
/gsd-ship 1 # Create PR from verified work
/gsd-ui-review 1 # Visual audit (frontend phases)
/clear
/gsd-next # Auto-detect and run next step
/gsd-progress --next # Auto-detect and run next step
...
/gsd-audit-milestone # Check everything shipped
/gsd-complete-milestone # Archive, tag, done
/gsd-session-report # Generate session summary
/gsd-pause-work --report # Generate session summary
```
### New Project from Existing Document
@@ -1020,7 +1020,7 @@ Clear your context window between major commands: `/clear` in Claude Code. GSD i
### Plans Seem Wrong or Misaligned
Run `/gsd-discuss-phase [N]` before planning. Most plan quality issues come from Claude making assumptions that `CONTEXT.md` would have prevented. You can also run `/gsd-list-phase-assumptions [N]` to see what Claude intends to do before committing to a plan.
Run `/gsd-discuss-phase [N]` before planning. Most plan quality issues come from Claude making assumptions that `CONTEXT.md` would have prevented. You can also run `/gsd-discuss-phase --assumptions [N]` to see what Claude intends to do before committing to a plan.
### Discuss-Phase Uses Technical Jargon I Don't Understand
@@ -1393,8 +1393,8 @@ If the installer crashes with `EPERM: operation not permitted, scandir` on Windo
| Plan doesn't match your vision | `/gsd-discuss-phase [N]` then re-plan |
| Costs running high | `/gsd-config --profile budget` and `/gsd-settings` to toggle agents off |
| Update broke local changes | `/gsd-update --reapply` |
| Want session summary for stakeholder | `/gsd-session-report` |
| Don't know what step is next | `/gsd-next` |
| Want session summary for stakeholder | `/gsd-pause-work --report` |
| Don't know what step is next | `/gsd-progress --next` |
| Parallel execution build errors | Update GSD or set `parallelization.enabled: false` |
@@ -1414,7 +1414,7 @@ For reference, here is what GSD creates in your project:
MILESTONES.md # Completed milestone archive
HANDOFF.json # Structured session handoff (from /gsd-pause-work)
research/ # Domain research from /gsd-new-project
reports/ # Session reports (from /gsd-session-report)
reports/ # Session reports (from /gsd-pause-work --report)
todos/
pending/ # Captured ideas awaiting work
done/ # Completed todos

View File

@@ -53,7 +53,7 @@ Symphony docs, blog posts, or third-party orchestration write-ups.
| Proof-of-work / test evidence | `/gsd-verify-work` (UAT.md persisted across `/clear`) |
| Adversarial review | `/gsd-review` (cross-AI peer review of plans) |
| Human merge gate | `/gsd-ship` (creates PR, optional code review, prepares merge) |
| Follow-up capture | `/gsd-note`, `/gsd-plant-seed`, `/gsd-new-milestone`, or a manually opened tracker issue |
| Follow-up capture | `/gsd-note`, `/gsd-capture --seed`, `/gsd-new-milestone`, or a manually opened tracker issue |
| Concurrency control | Manager / background-agent semantics (no always-on poller) |
The mapping is one-way: GSD owns the safety gates (verification, human
@@ -72,8 +72,8 @@ tracker issue end-to-end. Replace bracketed placeholders before running.
dependencies that block execution.
2. **Map to a GSD phase.** If the issue maps onto an existing phase in
`ROADMAP.md`, select it. If not, run `/gsd-new-milestone` (for a new
milestone of related issues) or open a phase via `/gsd-add-phase` /
`/gsd-insert-phase`. Capture the tracker issue URL in the phase's
milestone of related issues) or open a phase via `/gsd-phase` /
`/gsd-phase --insert`. Capture the tracker issue URL in the phase's
`CONTEXT.md` so traceability survives compaction.
3. **Create an isolated workspace.** Run
`/gsd-workspace --new --strategy worktree <slug>` to spin up a git
@@ -98,7 +98,7 @@ tracker issue end-to-end. Replace bracketed placeholders before running.
rich body assembled from the planning artifacts. Both gates require a
human decision before anything reaches the remote.
7. **Capture follow-up work explicitly.** Use `/gsd-note` for inline
notes, `/gsd-plant-seed` for ideas worth a future phase, or
notes, `/gsd-capture --seed` for ideas worth a future phase, or
`/gsd-new-milestone` for a coherent group of follow-ups. Creating a
tracker issue from a discovered follow-up requires explicit user
confirmation — GSD does not post to remote trackers automatically.
@@ -165,7 +165,7 @@ approved-enhancement could later add a *minimal* tracker bridge:
- Importing one GitHub or Linear issue into a GSD workspace / phase.
- Exporting `UAT.md` evidence as a comment on the source issue.
- Generating follow-up tracker issues from `/gsd-plant-seed` output.
- Generating follow-up tracker issues from `/gsd-capture --seed` output.
Each of those would be its own enhancement proposal because each adds
integration surface and ongoing maintenance burden. They are out of

View File

@@ -198,7 +198,7 @@
---
### `/gsd-next`
### `/gsd-progress --next`
次の論理的なワークフローステップに自動的に進みます。プロジェクトの状態を読み取り、適切なコマンドを実行します。
@@ -212,12 +212,12 @@
- 全フェーズ完了 → `/gsd-complete-milestone` を提案
```bash
/gsd-next # 次のステップを自動検出して実行
/gsd-progress --next # 次のステップを自動検出して実行
```
---
### `/gsd-session-report`
### `/gsd-pause-work --report`
作業サマリー、成果、推定リソース使用量を含むセッションレポートを生成します。
@@ -225,7 +225,7 @@
**生成物:** `.planning/reports/SESSION_REPORT.md`
```bash
/gsd-session-report # セッション後のサマリーを生成
/gsd-pause-work --report # セッション後のサマリーを生成
```
**レポートに含まれる内容:**
@@ -400,7 +400,7 @@
/gsd-phase --remove 7 # フェーズ7を削除、8→7、9→8等に番号振り直し
```
### `/gsd-list-phase-assumptions`
### `/gsd-discuss-phase --assumptions`
計画前にClaudeの意図するアプローチをプレビューします。
@@ -409,7 +409,7 @@
| `N` | いいえ | フェーズ番号 |
```bash
/gsd-list-phase-assumptions 2 # フェーズ2の前提を確認
/gsd-discuss-phase --assumptions 2 # フェーズ2の前提を確認
```
@@ -482,7 +482,7 @@
---
### `/gsd-analyze-dependencies`
### `/gsd-manager --analyze-deps`
フェーズ依存関係を検出し、ROADMAP.md に `Depends on` エントリを提案します。(v1.32)
@@ -491,7 +491,7 @@
**動作:** 依存関係提案テーブルを表示し、ユーザー確認後に ROADMAP.md の `Depends on` フィールドを更新します。
```bash
/gsd-analyze-dependencies # 依存関係の分析と提案
/gsd-manager --analyze-deps # 依存関係の分析と提案
```
---
@@ -948,10 +948,3 @@ GSDアップデート後にローカルの変更を復元します。
## コミュニティコマンド
### `/gsd-join-discord`
Discordコミュニティの招待を開きます。
```bash
/gsd-join-discord
```

View File

@@ -471,7 +471,7 @@
### 14. 自動進行Next
**コマンド:** `/gsd-next`
**コマンド:** `/gsd-progress --next`
**目的:** 現在のプロジェクト状態を自動検出し、次の論理的なワークフローステップに進めます。どのフェーズ/ステップにいるかを覚えておく必要がなくなります。
@@ -641,7 +641,7 @@
### 24. セッションレポート
**コマンド:** `/gsd-session-report`
**コマンド:** `/gsd-pause-work --report`
**目的:** 実施した作業、達成した成果、推定リソース使用量をキャプチャした、構造化されたセッション後のサマリードキュメントを生成します。
@@ -1725,7 +1725,7 @@ Claude が GSD ワークフローコンテキスト外でファイル編集を
### 77. フェーズ依存関係分析
**コマンド:** `/gsd-analyze-dependencies`
**コマンド:** `/gsd-manager --analyze-deps`
**目的:** フェーズ依存関係を検出し、`/gsd-manager` 実行前に ROADMAP.md への `Depends on` エントリを提案します。

View File

@@ -391,7 +391,7 @@ GSD はマークダウンファイルを生成し、それが LLM のシステ
| `/gsd-verify-work [N]` | 自動診断付き手動 UAT | 実行完了後 |
| `/gsd-ship [N]` | 検証済みの作業から PR を作成 | 検証合格後 |
| `/gsd-fast <text>` | インラインの軽微なタスク — プランニングを完全にスキップ | タイプミス修正、設定変更、小規模リファクタリング |
| `/gsd-next` | 状態を自動検出して次のステップを実行 | いつでも — 「次に何をすべき?」 |
| `/gsd-progress --next` | 状態を自動検出して次のステップを実行 | いつでも — 「次に何をすべき?」 |
| `/gsd-ui-review [N]` | 遡及的6ピラービジュアル監査 | 実行後または verify-work 後(フロントエンドプロジェクト) |
| `/gsd-audit-milestone` | マイルストーンの完了定義を満たしているか検証 | マイルストーン完了前 |
| `/gsd-complete-milestone` | マイルストーンをアーカイブし、リリースタグを作成 | 全フェーズの検証完了後 |
@@ -404,10 +404,9 @@ GSD はマークダウンファイルを生成し、それが LLM のシステ
| `/gsd-progress` | 状態と次のステップを表示 | いつでも -- 「今どこにいる?」 |
| `/gsd-resume-work` | 前回のセッションからフルコンテキストを復元 | 新しいセッションの開始時 |
| `/gsd-pause-work` | 構造化されたハンドオフを保存HANDOFF.json + continue-here.md | フェーズの途中で作業を中断する時 |
| `/gsd-session-report` | 作業内容と成果を含むセッションサマリーを生成 | セッション終了時、ステークホルダーへの共有時 |
| `/gsd-pause-work --report` | 作業内容と成果を含むセッションサマリーを生成 | セッション終了時、ステークホルダーへの共有時 |
| `/gsd-help` | すべてのコマンドを表示 | クイックリファレンス |
| `/gsd-update` | 変更履歴プレビュー付きで GSD を更新 | 新バージョンの確認時 |
| `/gsd-join-discord` | Discord コミュニティの招待リンクを開く | 質問やコミュニティ参加時 |
### フェーズ管理
@@ -416,7 +415,7 @@ GSD はマークダウンファイルを生成し、それが LLM のシステ
| `/gsd-phase` | ロードマップに新しいフェーズを追加 | 初期プランニング後にスコープが拡大した場合 |
| `/gsd-phase --insert [N]` | 緊急作業を挿入(小数番号) | マイルストーン中の緊急修正 |
| `/gsd-phase --remove [N]` | 将来のフェーズを削除して番号を振り直す | 機能のスコープ縮小 |
| `/gsd-list-phase-assumptions [N]` | Claude の意図するアプローチをプレビュー | プランニング前に方向性を確認 |
| `/gsd-discuss-phase --assumptions [N]` | Claude の意図するアプローチをプレビュー | プランニング前に方向性を確認 |
| `/gsd-plan-phase --research-phase [N]` | エコシステムの深いリサーチのみ | 複雑または不慣れなドメイン |
### ブラウンフィールドとユーティリティ
@@ -599,11 +598,11 @@ claude --dangerously-skip-permissions
/gsd-ship 1 # 検証済み作業から PR を作成
/gsd-ui-review 1 # ビジュアル監査(フロントエンドフェーズ)
/clear
/gsd-next # 自動検出して次のステップを実行
/gsd-progress --next # 自動検出して次のステップを実行
...
/gsd-audit-milestone # すべて出荷されたか確認
/gsd-complete-milestone # アーカイブ、タグ付け、完了
/gsd-session-report # セッションサマリーを生成
/gsd-pause-work --report # セッションサマリーを生成
```
### 既存ドキュメントからの新規プロジェクト
@@ -703,7 +702,7 @@ cd ~/gsd-workspaces/feature-b
### プランが誤っている、または方向性がずれている
プランニング前に `/gsd-discuss-phase [N]` を実行してください。プランの品質問題のほとんどは、CONTEXT.md があれば防げたはずの前提を Claude が置いてしまうことに起因します。`/gsd-list-phase-assumptions [N]` を使用して、プランにコミットする前に Claude の意図を確認することもできます。
プランニング前に `/gsd-discuss-phase [N]` を実行してください。プランの品質問題のほとんどは、CONTEXT.md があれば防げたはずの前提を Claude が置いてしまうことに起因します。`/gsd-discuss-phase --assumptions [N]` を使用して、プランにコミットする前に Claude の意図を確認することもできます。
### 実行が失敗する、またはスタブが生成される
@@ -799,8 +798,8 @@ Windows でインストーラーが `EPERM: operation not permitted, scandir`
| プランがビジョンに合わない | `/gsd-discuss-phase [N]` で再プランニング |
| コストが高い | `/gsd-config --profile budget``/gsd-settings` でエージェントをオフ |
| アップデートがローカル変更を壊した | `/gsd-update --reapply` |
| ステークホルダー向けセッションサマリーが欲しい | `/gsd-session-report` |
| 次のステップがわからない | `/gsd-next` |
| ステークホルダー向けセッションサマリーが欲しい | `/gsd-pause-work --report` |
| 次のステップがわからない | `/gsd-progress --next` |
| 並列実行でビルドエラー | GSD を更新するか `parallelization.enabled: false` を設定 |
---
@@ -819,7 +818,7 @@ Windows でインストーラーが `EPERM: operation not permitted, scandir`
MILESTONES.md # 完了したマイルストーンのアーカイブ
HANDOFF.json # 構造化セッション引き継ぎ(/gsd-pause-work から)
research/ # /gsd-new-project からのドメインリサーチ
reports/ # セッションレポート(/gsd-session-report から)
reports/ # セッションレポート(/gsd-pause-work --report から)
todos/
pending/ # 作業待ちのキャプチャされたアイデア
done/ # 完了した TODO

View File

@@ -198,7 +198,7 @@
---
### `/gsd-next`
### `/gsd-progress --next`
다음 논리적 워크플로우 단계로 자동으로 이동합니다. 프로젝트 상태를 읽고 적절한 명령어를 실행합니다.
@@ -212,12 +212,12 @@
- 모든 페이즈 완료 → `/gsd-complete-milestone` 제안
```bash
/gsd-next # 다음 단계 자동 감지 및 실행
/gsd-progress --next # 다음 단계 자동 감지 및 실행
```
---
### `/gsd-session-report`
### `/gsd-pause-work --report`
작업 요약, 결과, 예상 리소스 사용량을 포함한 세션 보고서를 생성합니다.
@@ -225,7 +225,7 @@
**생성 파일:** `.planning/reports/SESSION_REPORT.md`
```bash
/gsd-session-report # 세션 종료 후 요약 생성
/gsd-pause-work --report # 세션 종료 후 요약 생성
```
**보고서 포함 내용.**
@@ -400,7 +400,7 @@
/gsd-phase --remove 7 # 페이즈 7 제거, 8→7, 9→8 등으로 재번호
```
### `/gsd-list-phase-assumptions`
### `/gsd-discuss-phase --assumptions`
계획 수립 전 Claude의 예상 접근 방식을 미리 확인합니다.
@@ -409,7 +409,7 @@
| `N` | 아니오 | 페이즈 번호 |
```bash
/gsd-list-phase-assumptions 2 # 페이즈 2 가정 사항 확인
/gsd-discuss-phase --assumptions 2 # 페이즈 2 가정 사항 확인
```
@@ -482,7 +482,7 @@ Nyquist 검증 갭을 소급하여 감사하고 보완합니다.
---
### `/gsd-analyze-dependencies`
### `/gsd-manager --analyze-deps`
페이즈 의존성을 감지하고 ROADMAP.md에 `Depends on` 항목을 제안합니다. (v1.32)
@@ -491,7 +491,7 @@ Nyquist 검증 갭을 소급하여 감사하고 보완합니다.
**동작 방식:** 의존성 제안 테이블을 표시하고 사용자 확인 후 ROADMAP.md의 `Depends on` 필드를 업데이트합니다.
```bash
/gsd-analyze-dependencies # 의존성 분석 및 제안
/gsd-manager --analyze-deps # 의존성 분석 및 제안
```
---
@@ -948,10 +948,3 @@ GSD 업데이트 후 로컬 수정사항을 복원합니다.
## 커뮤니티 명령어
### `/gsd-join-discord`
Discord 커뮤니티 초대 링크를 엽니다.
```bash
/gsd-join-discord
```

View File

@@ -471,7 +471,7 @@
### 14. Auto-Advance (Next)
**명령어:** `/gsd-next`
**명령어:** `/gsd-progress --next`
**목적:** 현재 프로젝트 상태를 자동으로 감지하고 다음 논리적 워크플로우 단계로 진행합니다. 현재 어느 페이즈/단계에 있는지 기억할 필요가 없습니다.
@@ -641,7 +641,7 @@
### 24. Session Reporting
**명령어:** `/gsd-session-report`
**명령어:** `/gsd-pause-work --report`
**목적:** 수행된 작업, 달성된 결과, 예상 리소스 사용량을 캡처하는 구조화된 세션 후 요약 문서를 생성합니다.
@@ -1725,7 +1725,7 @@ Claude가 GSD 워크플로우 컨텍스트 밖에서 파일 편집을 시도하
### 77. 페이즈 의존성 분석
**명령어:** `/gsd-analyze-dependencies`
**명령어:** `/gsd-manager --analyze-deps`
**목적:** 페이즈 의존성을 감지하고 `/gsd-manager` 실행 전 ROADMAP.md에 `Depends on` 항목을 제안합니다.

View File

@@ -391,7 +391,7 @@ GSD는 LLM 시스템 프롬프트가 되는 마크다운 파일을 생성합니
| `/gsd-verify-work [N]` | 자동 진단을 포함한 수동 UAT | 실행 완료 후 |
| `/gsd-ship [N]` | 검증된 작업으로 PR 생성 | 검증 통과 후 |
| `/gsd-fast <text>` | 계획을 완전히 건너뛰는 인라인 간단 작업 | 오타 수정, 설정 변경, 소규모 리팩터링 |
| `/gsd-next` | 상태 자동 감지 및 다음 단계 실행 | 언제든 — "다음에 무엇을 해야 하나?" |
| `/gsd-progress --next` | 상태 자동 감지 및 다음 단계 실행 | 언제든 — "다음에 무엇을 해야 하나?" |
| `/gsd-ui-review [N]` | 6개 기둥 기반 시각적 감사 소급 수행 | 실행 또는 verify-work 이후 (프론트엔드 프로젝트) |
| `/gsd-audit-milestone` | 마일스톤이 완료 정의를 충족했는지 검증 | 마일스톤 완료 전 |
| `/gsd-complete-milestone` | 마일스톤 아카이브 및 릴리스 태그 생성 | 모든 페이즈 검증 완료 시 |
@@ -404,10 +404,9 @@ GSD는 LLM 시스템 프롬프트가 되는 마크다운 파일을 생성합니
| `/gsd-progress` | 상태 및 다음 단계 표시 | 언제든 -- "지금 어디 있나?" |
| `/gsd-resume-work` | 마지막 세션의 전체 컨텍스트 복원 | 새 세션 시작 시 |
| `/gsd-pause-work` | 구조화된 핸드오프 저장 (HANDOFF.json + continue-here.md) | 페이즈 중간에 중단할 때 |
| `/gsd-session-report` | 작업 및 결과가 포함된 세션 요약 생성 | 세션 종료 시, 이해관계자 공유 시 |
| `/gsd-pause-work --report` | 작업 및 결과가 포함된 세션 요약 생성 | 세션 종료 시, 이해관계자 공유 시 |
| `/gsd-help` | 모든 명령어 표시 | 빠른 레퍼런스 |
| `/gsd-update` | 변경 로그 미리보기와 함께 GSD 업데이트 | 새 버전 확인 시 |
| `/gsd-join-discord` | Discord 커뮤니티 초대 링크 열기 | 질문이나 커뮤니티 참여 시 |
### 페이즈 관리
@@ -416,7 +415,7 @@ GSD는 LLM 시스템 프롬프트가 되는 마크다운 파일을 생성합니
| `/gsd-phase` | 로드맵에 새 페이즈 추가 | 초기 계획 후 범위가 늘어날 때 |
| `/gsd-phase --insert [N]` | 긴급 작업 삽입 (소수점 번호 체계) | 마일스톤 중간의 긴급 수정 시 |
| `/gsd-phase --remove [N]` | 미래 페이즈 제거 및 재번호 | 기능 범위 축소 시 |
| `/gsd-list-phase-assumptions [N]` | Claude의 예상 접근 방식 미리 확인 | 계획 전 방향 검증 시 |
| `/gsd-discuss-phase --assumptions [N]` | Claude의 예상 접근 방식 미리 확인 | 계획 전 방향 검증 시 |
| `/gsd-plan-phase --research-phase [N]` | 심층 에코시스템 조사만 수행 | 복잡하거나 익숙하지 않은 도메인 |
### 브라운필드 및 유틸리티
@@ -599,11 +598,11 @@ claude --dangerously-skip-permissions
/gsd-ship 1 # Create PR from verified work
/gsd-ui-review 1 # Visual audit (frontend phases)
/clear
/gsd-next # Auto-detect and run next step
/gsd-progress --next # Auto-detect and run next step
...
/gsd-audit-milestone # Check everything shipped
/gsd-complete-milestone # Archive, tag, done
/gsd-session-report # Generate session summary
/gsd-pause-work --report # Generate session summary
```
### 기존 문서로 새 프로젝트 시작
@@ -703,7 +702,7 @@ cd ~/gsd-workspaces/feature-b
### 계획이 잘못되거나 맞지 않는 경우
계획 전에 `/gsd-discuss-phase [N]`을 실행하세요. 대부분의 계획 품질 문제는 `CONTEXT.md`가 있었다면 방지할 수 있었던 가정을 Claude가 세우기 때문에 발생합니다. `/gsd-list-phase-assumptions [N]`을 실행하여 계획에 동의하기 전에 Claude가 무엇을 하려는지 확인할 수도 있습니다.
계획 전에 `/gsd-discuss-phase [N]`을 실행하세요. 대부분의 계획 품질 문제는 `CONTEXT.md`가 있었다면 방지할 수 있었던 가정을 Claude가 세우기 때문에 발생합니다. `/gsd-discuss-phase --assumptions [N]`을 실행하여 계획에 동의하기 전에 Claude가 무엇을 하려는지 확인할 수도 있습니다.
### 실행이 실패하거나 스텁을 생성하는 경우
@@ -799,8 +798,8 @@ Windows에서 설치 프로그램이 `EPERM: operation not permitted, scandir`
| 계획이 비전과 맞지 않음 | `/gsd-discuss-phase [N]` 후 재계획 |
| 비용이 높아짐 | `/gsd-config --profile budget``/gsd-settings`에서 에이전트 비활성화 |
| 업데이트가 로컬 변경사항 파괴 | `/gsd-update --reapply` |
| 이해관계자를 위한 세션 요약 필요 | `/gsd-session-report` |
| 다음 단계를 모르겠음 | `/gsd-next` |
| 이해관계자를 위한 세션 요약 필요 | `/gsd-pause-work --report` |
| 다음 단계를 모르겠음 | `/gsd-progress --next` |
| 병렬 실행 빌드 오류 | GSD 업데이트 또는 `parallelization.enabled: false` 설정 |
---
@@ -819,7 +818,7 @@ Windows에서 설치 프로그램이 `EPERM: operation not permitted, scandir`
MILESTONES.md # Completed milestone archive
HANDOFF.json # Structured session handoff (from /gsd-pause-work)
research/ # Domain research from /gsd-new-project
reports/ # Session reports (from /gsd-session-report)
reports/ # Session reports (from /gsd-pause-work --report)
todos/
pending/ # Captured ideas awaiting work
done/ # Completed todos

View File

@@ -16,7 +16,7 @@ Para detalhes completos de flags avançadas e mudanças recentes, consulte tamb
| `/gsd-execute-phase <N>` | Executa planos em ondas paralelas | Após planejamento aprovado |
| `/gsd-verify-work [N]` | UAT manual com diagnóstico automático | Após execução |
| `/gsd-ship [N]` | Cria PR da fase validada | Ao concluir a fase |
| `/gsd-next` | Detecta e executa o próximo passo lógico | Qualquer momento |
| `/gsd-progress --next` | Detecta e executa o próximo passo lógico | Qualquer momento |
| `/gsd-fast <texto>` | Tarefa curta sem planejamento completo | Ajustes triviais |
## Navegação e Sessão
@@ -26,7 +26,7 @@ Para detalhes completos de flags avançadas e mudanças recentes, consulte tamb
| `/gsd-progress` | Mostra status atual e próximos passos |
| `/gsd-resume-work` | Retoma contexto da sessão anterior |
| `/gsd-pause-work` | Salva handoff estruturado |
| `/gsd-session-report` | Gera resumo da sessão |
| `/gsd-pause-work --report` | Gera resumo da sessão |
| `/gsd-autonomous` | Executa todas as fases restantes de forma autônoma (`--from N`, `--to N`, `--only N`) |
| `/gsd-help` | Lista comandos e uso |
| `/gsd-update` | Atualiza o GSD |
@@ -38,7 +38,7 @@ Para detalhes completos de flags avançadas e mudanças recentes, consulte tamb
| `/gsd-phase` | Adiciona fase no roadmap |
| `/gsd-phase --insert [N]` | Insere trabalho urgente entre fases |
| `/gsd-phase --remove [N]` | Remove fase futura e reenumera |
| `/gsd-list-phase-assumptions [N]` | Mostra abordagem assumida pelo Claude |
| `/gsd-discuss-phase --assumptions [N]` | Mostra abordagem assumida pelo Claude |
## Brownfield e Utilidades
@@ -47,7 +47,7 @@ Para detalhes completos de flags avançadas e mudanças recentes, consulte tamb
| `/gsd-map-codebase` | Mapeia base existente antes de novo projeto |
| `/gsd-quick` | Tarefas ad-hoc com garantias do GSD |
| `/gsd-debug [desc]` | Debug sistemático com estado persistente (`--diagnose` para modo diagnóstico) |
| `/gsd-analyze-dependencies` | Detecta dependências entre fases e sugere `Depends on` no ROADMAP.md (v1.32) |
| `/gsd-manager --analyze-deps` | Detecta dependências entre fases e sugere `Depends on` no ROADMAP.md (v1.32) |
| `/gsd-forensics` | Diagnóstico de falhas no workflow |
| `/gsd-settings` | Configuração de agentes, perfil e toggles |
| `/gsd-config --profile <perfil>` | Troca rápida de perfil de modelo |

View File

@@ -37,7 +37,7 @@ Para catálogo completo e detalhamento exaustivo, consulte [FEATURES.md em ingl
- **Perfis de modelo** (`quality`, `balanced`, `budget`, `inherit`)
- **Ajuste por toggles** para custo/qualidade/velocidade
- **Diagnóstico forense** com `/gsd-forensics`
- **Relatório de sessão** com `/gsd-session-report`
- **Relatório de sessão** com `/gsd-pause-work --report`
## Novidades v1.31--v1.32
@@ -55,7 +55,7 @@ Para catálogo completo e detalhamento exaustivo, consulte [FEATURES.md em ingl
- **Context reduction** — truncamento de markdown e ordenação cache-friendly (v1.32)
- **`--power` flag** — respostas em batch via arquivo para discuss-phase (v1.32)
- **`--diagnose` flag** — modo diagnóstico sem modificações no `/gsd-debug` (v1.32)
- **`/gsd-analyze-dependencies`** — detecta dependências entre fases (v1.32)
- **`/gsd-manager --analyze-deps`** — detecta dependências entre fases (v1.32)
- **Response language config** — `response_language` para saída consistente em idioma (v1.32)
- **Novos runtimes** — Trae IDE, Cline, Augment Code (v1.32)
- **Manual update** — procedimento de atualização sem npm (v1.32)

View File

@@ -39,7 +39,7 @@ Para iniciar projeto novo:
Para seguir automaticamente o próximo passo:
```bash
/gsd-next
/gsd-progress --next
```
### Nyquist Validation
@@ -162,7 +162,7 @@ Para arquivos sensíveis, use deny list no Claude Code.
| `/gsd-execute-phase [N]` | Executar planos em ondas |
| `/gsd-verify-work [N]` | UAT manual |
| `/gsd-ship [N]` | Gerar PR da fase |
| `/gsd-next` | Próximo passo automático |
| `/gsd-progress --next` | Próximo passo automático |
### Gestão e utilidades
@@ -171,7 +171,7 @@ Para arquivos sensíveis, use deny list no Claude Code.
| `/gsd-progress` | Ver status atual |
| `/gsd-resume-work` | Retomar sessão |
| `/gsd-pause-work` | Pausar com handoff |
| `/gsd-session-report` | Resumo da sessão |
| `/gsd-pause-work --report` | Resumo da sessão |
| `/gsd-quick` | Tarefa ad-hoc com garantias GSD |
| `/gsd-debug [desc]` | Debug sistemático |
| `/gsd-forensics` | Diagnóstico de workflow quebrado |
@@ -268,7 +268,7 @@ Use `/clear` entre etapas grandes e retome com `/gsd-resume-work` ou `/gsd-progr
### Plano desalinhado
Rode `/gsd-discuss-phase [N]` antes do plano e valide suposições com `/gsd-list-phase-assumptions [N]`.
Rode `/gsd-discuss-phase [N]` antes do plano e valide suposições com `/gsd-discuss-phase --assumptions [N]`.
### Execução falhou ou saiu com stubs
@@ -298,7 +298,7 @@ Use `resolve_model_ids: "omit"` para deixar o runtime resolver modelos padrão.
| Bug em workflow | `/gsd-forensics` |
| Correção pontual | `/gsd-quick` |
| Custo alto | `/gsd-config --profile budget` |
| Não sabe próximo passo | `/gsd-next` |
| Não sabe próximo passo | `/gsd-progress --next` |
---

View File

@@ -499,7 +499,6 @@ lmn012o feat(08-02): 创建注册端点
| `/gsd-progress` | 我在哪?接下来做什么? |
| `/gsd-help` | 显示所有命令和使用指南 |
| `/gsd-update` | 更新 GSD 并预览变更日志 |
| `/gsd-join-discord` | 加入 GSD Discord 社区 |
### 现有代码库
@@ -514,9 +513,9 @@ lmn012o feat(08-02): 创建注册端点
| `/gsd-phase` | 向路线图追加阶段 |
| `/gsd-phase --insert [N]` | 在阶段之间插入紧急工作 |
| `/gsd-phase --remove [N]` | 删除未来阶段,重新编号 |
| `/gsd-list-phase-assumptions [N]` | 规划前查看 Claude 的预期方法 |
| `/gsd-discuss-phase --assumptions [N]` | 规划前查看 Claude 的预期方法 |
| `/gsd-autonomous [--from N] [--to N] [--only N]` | 自主执行所有剩余阶段(`--to N` 执行到阶段 N 停止,`--only N` 只执行单个阶段) |
| `/gsd-analyze-dependencies` | 检测阶段间依赖关系并建议 ROADMAP.md 的 `Depends on` 条目 |
| `/gsd-manager --analyze-deps` | 检测阶段间依赖关系并建议 ROADMAP.md 的 `Depends on` 条目 |
### 会话

View File

@@ -199,7 +199,6 @@
| `/gsd-pause-work` | 保存上下文交接 | 阶段中途停止 |
| `/gsd-help` | 显示所有命令 | 快速参考 |
| `/gsd-update` | 更新 GSD 并预览变更日志 | 检查新版本 |
| `/gsd-join-discord` | 打开 Discord 社区邀请 | 问题或社区 |
### 阶段管理
@@ -208,10 +207,10 @@
| `/gsd-phase` | 向路线图追加新阶段 | 初始规划后范围增长 |
| `/gsd-phase --insert [N]` | 插入紧急工作(小数编号) | 里程碑中途紧急修复 |
| `/gsd-phase --remove [N]` | 删除未来阶段并重新编号 | 移除某个功能 |
| `/gsd-list-phase-assumptions [N]` | 预览 Claude 的预期方法 | 规划前,验证方向 |
| `/gsd-discuss-phase --assumptions [N]` | 预览 Claude 的预期方法 | 规划前,验证方向 |
| `/gsd-plan-phase --research-phase [N]` | 仅深度生态研究 | 复杂或不熟悉的领域 |
| `/gsd-autonomous [--from N] [--to N] [--only N]` | 自主执行剩余阶段(`--to N` 到阶段 N 停止) | 批量自动处理 |
| `/gsd-analyze-dependencies` | 检测阶段间依赖关系 | `/gsd-manager` 前分析 |
| `/gsd-manager --analyze-deps` | 检测阶段间依赖关系 | `/gsd-manager` 前分析 |
### 状态管理
@@ -424,7 +423,7 @@ claude --dangerously-skip-permissions
### 计划看起来错误或不一致
在规划前运行 `/gsd-discuss-phase [N]`。大多数计划质量问题来自 Claude 做出了 `CONTEXT.md` 本可以防止的假设。你也可以运行 `/gsd-list-phase-assumptions [N]` 在提交计划前查看 Claude 打算做什么。
在规划前运行 `/gsd-discuss-phase [N]`。大多数计划质量问题来自 Claude 做出了 `CONTEXT.md` 本可以防止的假设。你也可以运行 `/gsd-discuss-phase --assumptions [N]` 在提交计划前查看 Claude 打算做什么。
### 执行失败或产生存根

View File

@@ -52,7 +52,7 @@
**也可选:**
- 执行前审查计划
- `/gsd-list-phase-assumptions 2` — 检查假设
- `/gsd-discuss-phase --assumptions 2` — 检查假设
---
```

View File

@@ -792,19 +792,13 @@ function parseWorktreePorcelain(porcelain) {
}
/**
* Remove linked git worktrees whose branch has already been merged into the
* current HEAD of the main worktree. Also runs `git worktree prune` to clear
* any stale references left by manually-deleted worktree directories.
* Clear stale worktree metadata references via `git worktree prune`.
*
* Safe guards:
* - Never removes the main worktree (first entry in --porcelain output).
* - Never removes the worktree at process.cwd().
* - Never removes a worktree whose branch has unmerged commits.
* - Skips detached-HEAD worktrees (no branch name).
* Destructive linked-worktree removal is disabled by default for safety.
*
* @param {string} repoRoot - absolute path to the main (or any) worktree of
* the repository; used as `cwd` for git commands.
* @returns {string[]} list of worktree paths that were removed
* @returns {string[]} list of worktree paths that were removed (always empty)
*/
function pruneOrphanedWorktrees(repoRoot) {
const pruned = [];
@@ -821,37 +815,14 @@ function pruneOrphanedWorktrees(repoRoot) {
return pruned;
}
// 2. First entry is the main worktree — never touch it
const mainWorktreePath = worktrees[0].path;
// 3. Check each non-main worktree
for (let i = 1; i < worktrees.length; i++) {
const { path: wtPath, branch } = worktrees[i];
// Never remove the worktree for the current process directory
if (wtPath === cwd || cwd.startsWith(wtPath + path.sep)) continue;
// Check if the branch is fully merged into HEAD (main)
// git merge-base --is-ancestor <branch> HEAD exits 0 when merged
const ancestorCheck = execGit(repoRoot, [
'merge-base', '--is-ancestor', branch, 'HEAD',
]);
if (ancestorCheck.exitCode !== 0) {
// Not yet merged — leave it alone
continue;
}
// Remove the worktree and delete the branch
const removeResult = execGit(repoRoot, ['worktree', 'remove', '--force', wtPath]);
if (removeResult.exitCode === 0) {
execGit(repoRoot, ['branch', '-D', branch]);
pruned.push(wtPath);
}
}
// Destructive removal of linked worktrees is intentionally disabled.
// Keep metadata cleanup only (git worktree prune), which clears stale refs
// for manually-deleted directories without removing active sibling worktrees.
void cwd;
void worktrees;
} catch { /* never crash the caller */ }
// 4. Always run prune to clear stale references (e.g. manually-deleted dirs)
// Always run prune to clear stale references (e.g. manually-deleted dirs)
execGit(repoRoot, ['worktree', 'prune']);
return pruned;
@@ -950,6 +921,17 @@ function phaseTokenMatches(dirName, normalized) {
return false;
}
function extractCanonicalPlanId(filename) {
const base = filename.replace(/-PLAN\.md$/i, '').replace(/-SUMMARY\.md$/i, '').replace(/\.md$/i, '');
const parts = base.split('-').filter(Boolean);
const tokenRe = /^\d+[A-Z]?(?:\.\d+)*$/i;
const phaseIdx = parts.findIndex(p => tokenRe.test(p));
if (phaseIdx >= 0 && phaseIdx + 1 < parts.length && tokenRe.test(parts[phaseIdx + 1])) {
return `${parts[phaseIdx]}-${parts[phaseIdx + 1]}`;
}
return base;
}
function searchPhaseInDir(baseDir, relBase, normalized) {
try {
const dirs = readSubdirectories(baseDir, true);
@@ -970,11 +952,16 @@ function searchPhaseInDir(baseDir, relBase, normalized) {
const summaries = unsortedSummaries.sort();
const completedPlanIds = new Set(
summaries.map(s => s.replace('-SUMMARY.md', '').replace('SUMMARY.md', ''))
summaries.flatMap(s => {
const exact = s.replace('-SUMMARY.md', '').replace('SUMMARY.md', '');
const canonical = extractCanonicalPlanId(s);
return canonical === exact ? [exact] : [exact, canonical];
})
);
const incompletePlans = plans.filter(p => {
const planId = p.replace('-PLAN.md', '').replace('PLAN.md', '');
return !completedPlanIds.has(planId);
const canonical = extractCanonicalPlanId(p);
return !completedPlanIds.has(planId) && !completedPlanIds.has(canonical);
});
return {
@@ -1895,10 +1882,62 @@ function getMilestoneInfo(cwd) {
* to the current milestone based on ROADMAP.md phase headings.
* If no ROADMAP exists or no phases are listed, returns a pass-all filter.
*/
function getMilestonePhaseFilter(cwd) {
function getMilestonePhaseFilter(cwd, versionOverride) {
const milestonePhaseNums = new Set();
let missingExplicitVersion = false;
try {
const roadmap = extractCurrentMilestone(fs.readFileSync(path.join(planningDir(cwd), 'ROADMAP.md'), 'utf-8'), cwd);
const roadmapPath = path.join(planningDir(cwd), 'ROADMAP.md');
const roadmapContent = fs.readFileSync(roadmapPath, 'utf-8');
let roadmap = extractCurrentMilestone(roadmapContent, cwd);
if (versionOverride) {
const escapedVersion = escapeRegex(versionOverride);
const sectionPattern = new RegExp(`(^#{1,3}\\s+.*${escapedVersion}[^\\n]*)`, 'mi');
const sectionMatch = roadmapContent.match(sectionPattern);
if (!sectionMatch) {
// Only treat this as an error case when the roadmap is milestone-versioned.
// Older/flat roadmap formats without vX.Y milestone headings should keep
// legacy pass-through behavior for milestone.complete.
const hasVersionedMilestones = /^#{1,3}\s+.*v\d+\.\d+/mi.test(roadmapContent);
if (hasVersionedMilestones) {
roadmap = '';
missingExplicitVersion = true;
}
} else {
const sectionStart = sectionMatch.index;
const headingLevel = sectionMatch[1].match(/^(#{1,3})\s/)[1].length;
const restContent = roadmapContent.slice(sectionStart + sectionMatch[0].length);
const nextMilestonePattern = new RegExp(`^#{1,${headingLevel}}\\s+(?!Phase\\s+\\S)(?:.*v\\d+\\.\\d+|✅|📋|🚧)`, 'i');
let sectionEnd = roadmapContent.length;
let fenceChar = null;
let fenceLen = 0;
let charOffset = 0;
for (const line of restContent.split('\n')) {
const fenceMatch = line.match(/^\s{0,3}((?:`{3,}|~{3,}))(.*)/);
if (fenceMatch) {
const char = fenceMatch[1][0];
const len = fenceMatch[1].length;
const trailing = fenceMatch[2] || '';
if (!fenceChar) {
fenceChar = char;
fenceLen = len;
} else if (char === fenceChar && len >= fenceLen && /^\s*$/.test(trailing)) {
fenceChar = null;
fenceLen = 0;
}
} else if (!fenceChar && nextMilestonePattern.test(line)) {
sectionEnd = sectionStart + sectionMatch[0].length + charOffset;
break;
}
charOffset += line.length + 1;
}
const currentSection = roadmapContent.slice(sectionStart, sectionEnd);
roadmap = currentSection;
}
}
// Match both numeric phases (Phase 1:) and custom IDs (Phase PROJ-42:)
const phasePattern = /#{2,4}\s*Phase\s+([\w][\w.-]*)\s*:/gi;
let m;
@@ -1910,6 +1949,7 @@ function getMilestonePhaseFilter(cwd) {
if (milestonePhaseNums.size === 0) {
const passAll = () => true;
passAll.phaseCount = 0;
passAll.missingExplicitVersion = missingExplicitVersion;
return passAll;
}
@@ -1927,6 +1967,7 @@ function getMilestonePhaseFilter(cwd) {
return false;
}
isDirInMilestone.phaseCount = milestonePhaseNums.size;
isDirInMilestone.missingExplicitVersion = missingExplicitVersion;
return isDirInMilestone;
}

View File

@@ -14,6 +14,30 @@ const { maskIfSecret } = require('./secrets.cjs');
// same in markdown but differ textually.
const REQUIREMENTS_HEADER_RE = /^\*\*Requirements:?\*\*[^\S\n]*:?[^\S\n]*([^\n]*)$/m;
function listPhaseSummaryFiles(phaseDir) {
const phaseFiles = fs.readdirSync(phaseDir);
const rootSummaries = phaseFiles.filter(f => f.endsWith('-SUMMARY.md') || f === 'SUMMARY.md');
const plansDir = path.join(phaseDir, 'plans');
let nestedSummaries = [];
if (fs.existsSync(plansDir)) {
const files = fs.readdirSync(plansDir);
nestedSummaries = files.filter(f => /^SUMMARY-\d+.*\.md$/i.test(f));
}
return rootSummaries.concat(nestedSummaries);
}
function listPhasePlanFiles(phaseDir) {
const phaseFiles = fs.readdirSync(phaseDir);
const rootPlans = phaseFiles.filter(f => f.endsWith('-PLAN.md') || f === 'PLAN.md');
const plansDir = path.join(phaseDir, 'plans');
let nestedPlans = [];
if (fs.existsSync(plansDir)) {
const files = fs.readdirSync(plansDir);
nestedPlans = files.filter(f => /^PLAN-\d+.*\.md$/i.test(f));
}
return rootPlans.concat(nestedPlans);
}
function getLatestCompletedMilestone(cwd) {
const milestonesPath = path.join(planningRoot(cwd), 'MILESTONES.md');
if (!fs.existsSync(milestonesPath)) return null;
@@ -901,8 +925,7 @@ function cmdInitMilestoneOp(cwd, raw) {
const dirName = diskPhaseDirs.get(canonicalizePhase(num));
if (!dirName) continue;
try {
const phaseFiles = fs.readdirSync(path.join(phasesDir, dirName));
const hasSummary = phaseFiles.some(f => f.endsWith('-SUMMARY.md') || f === 'SUMMARY.md');
const hasSummary = listPhaseSummaryFiles(path.join(phasesDir, dirName)).length > 0;
if (hasSummary) completedPhases++;
} catch { /* intentionally empty */ }
}
@@ -914,8 +937,7 @@ function cmdInitMilestoneOp(cwd, raw) {
phaseCount = dirs.length;
for (const dir of dirs) {
try {
const phaseFiles = fs.readdirSync(path.join(phasesDir, dir));
const hasSummary = phaseFiles.some(f => f.endsWith('-SUMMARY.md') || f === 'SUMMARY.md');
const hasSummary = listPhaseSummaryFiles(path.join(phasesDir, dir)).length > 0;
if (hasSummary) completedPhases++;
} catch { /* intentionally empty */ }
}
@@ -1072,8 +1094,8 @@ function cmdInitManager(cwd, raw) {
if (dirMatch) {
const fullDir = path.join(phasesDir, dirMatch);
const phaseFiles = fs.readdirSync(fullDir);
planCount = phaseFiles.filter(f => f.endsWith('-PLAN.md') || f === 'PLAN.md').length;
summaryCount = phaseFiles.filter(f => f.endsWith('-SUMMARY.md') || f === 'SUMMARY.md').length;
planCount = listPhasePlanFiles(fullDir).length;
summaryCount = listPhaseSummaryFiles(fullDir).length;
hasContext = phaseFiles.some(f => f.endsWith('-CONTEXT.md') || f === 'CONTEXT.md');
hasResearch = phaseFiles.some(f => f.endsWith('-RESEARCH.md') || f === 'RESEARCH.md');
@@ -1353,8 +1375,8 @@ function cmdInitProgress(cwd, raw) {
const phasePath = path.join(phasesDir, dir);
const phaseFiles = fs.readdirSync(phasePath);
const plans = phaseFiles.filter(f => f.endsWith('-PLAN.md') || f === 'PLAN.md');
const summaries = phaseFiles.filter(f => f.endsWith('-SUMMARY.md') || f === 'SUMMARY.md');
const plans = listPhasePlanFiles(phasePath);
const summaries = listPhaseSummaryFiles(phasePath);
const hasResearch = phaseFiles.some(f => f.endsWith('-RESEARCH.md') || f === 'RESEARCH.md');
const status = summaries.length >= plans.length && plans.length > 0 ? 'complete' :

View File

@@ -107,7 +107,10 @@ function cmdMilestoneComplete(cwd, version, options, raw) {
// Scope stats and accomplishments to only the phases belonging to the
// current milestone's ROADMAP. Uses the shared filter from core.cjs
// (same logic used by cmdPhasesList and other callers).
const isDirInMilestone = getMilestonePhaseFilter(cwd);
const isDirInMilestone = getMilestonePhaseFilter(cwd, version);
if (isDirInMilestone.missingExplicitVersion) {
error(`no phases found for milestone ${version} in ROADMAP.md`);
}
// Gather stats from phases (scoped to current milestone only)
let phaseCount = 0;
@@ -196,7 +199,7 @@ function cmdMilestoneComplete(cwd, version, options, raw) {
atomicWriteFileSync(milestonesPath, normalizeMd(`# Milestones\n\n${milestoneEntry}`));
}
// Update STATE.md — use shared helpers that handle both **bold:** and plain Field: formats
// Update STATE.md — keep frontmatter/body semantically aligned after closure
if (fs.existsSync(statePath)) {
let stateContent = fs.readFileSync(statePath, 'utf-8');
@@ -205,6 +208,31 @@ function cmdMilestoneComplete(cwd, version, options, raw) {
stateContent = stateReplaceFieldWithFallback(stateContent, 'Last Activity Description', null,
`${version} milestone completed and archived`);
// Reset Current Position narrative so resume/progress flows do not keep
// pointing at closed-phase execution instructions.
const positionPattern = /(##\s*Current Position\s*\n)([\s\S]*?)(?=\n##|$)/i;
const closedPositionBody =
`\nPhase: Milestone ${version} complete\n` +
`Plan: —\n` +
`Status: Awaiting next milestone\n` +
`Last activity: ${today} — Milestone ${version} completed and archived\n\n`;
if (positionPattern.test(stateContent)) {
stateContent = stateContent.replace(positionPattern, (_m, header) => `${header}${closedPositionBody}`);
} else {
stateContent = `${stateContent.trimEnd()}\n\n## Current Position\n${closedPositionBody}`;
}
// Normalize operator-next-step tails that can become stale after close.
const operatorPattern = /(##\s*Operator Next Steps\s*\n)([\s\S]*?)(?=\n##|$)/i;
if (operatorPattern.test(stateContent)) {
stateContent = stateContent.replace(
operatorPattern,
`$1\n- Start the next milestone with /gsd-new-milestone\n\n`,
);
} else {
stateContent = `${stateContent.trimEnd()}\n\n## Operator Next Steps\n\n- Start the next milestone with /gsd-new-milestone\n`;
}
writeStateMd(statePath, stateContent, cwd);
}

View File

@@ -33,6 +33,9 @@ function routePhaseCommand({ phase, args, cwd, raw, error }) {
}
phase.cmdPhaseAddBatch(cwd, descriptions, raw);
} else if (subcommand === 'insert') {
if (args.includes('--dry-run')) {
error('phase insert does not support --dry-run');
}
phase.cmdPhaseInsert(cwd, args[2], args.slice(3).join(' '), raw);
} else if (subcommand === 'remove') {
const forceFlag = args.includes('--force');

View File

@@ -50,6 +50,17 @@ function describeNonCanonicalPlans(dirFiles, matchedFiles) {
);
}
function extractCanonicalPlanId(filename) {
const base = filename.replace(/-PLAN\.md$/i, '').replace(/-SUMMARY\.md$/i, '').replace(/\.md$/i, '');
const parts = base.split('-').filter(Boolean);
const tokenRe = /^\d+[A-Z]?(?:\.\d+)*$/i;
const phaseIdx = parts.findIndex(p => tokenRe.test(p));
if (phaseIdx >= 0 && phaseIdx + 1 < parts.length && tokenRe.test(parts[phaseIdx + 1])) {
return `${parts[phaseIdx]}-${parts[phaseIdx + 1]}`;
}
return base;
}
function cmdPhasesList(cwd, options, raw) {
const phasesDir = path.join(planningDir(cwd), 'phases');
const { type, phase, includeArchived } = options;
@@ -288,7 +299,11 @@ function cmdPhasePlanIndex(cwd, phase, raw) {
// Build set of plan IDs with summaries
const completedPlanIds = new Set(
summaryFiles.map(s => s.replace('-SUMMARY.md', '').replace('SUMMARY.md', ''))
summaryFiles.flatMap(s => {
const exact = s.replace('-SUMMARY.md', '').replace('SUMMARY.md', '');
const canonical = extractCanonicalPlanId(s);
return canonical === exact ? [exact] : [exact, canonical];
})
);
const plans = [];
@@ -327,7 +342,7 @@ function cmdPhasePlanIndex(cwd, phase, raw) {
filesModified = Array.isArray(fmFiles) ? fmFiles : [fmFiles];
}
const hasSummary = completedPlanIds.has(planId);
const hasSummary = completedPlanIds.has(planId) || completedPlanIds.has(extractCanonicalPlanId(planFile));
if (!hasSummary) {
incomplete.push(planId);
}
@@ -556,6 +571,10 @@ function cmdPhaseInsert(cwd, afterPhase, description, raw) {
const afterPhaseEscaped = unpadded.replace(/\./g, '\\.');
const targetPattern = new RegExp(`#{2,4}\\s*Phase\\s+0*${afterPhaseEscaped}:`, 'i');
if (!targetPattern.test(content)) {
const checklistPattern = new RegExp(`-\\s*\\[[ x]\\]\\s*\\*\\*Phase\\s+0*${afterPhaseEscaped}:`, 'i');
if (checklistPattern.test(content)) {
error(`Phase ${afterPhase} exists in roadmap summary but is missing a detail section (### Phase ${afterPhase}: ...).`);
}
error(`Phase ${afterPhase} not found in ROADMAP.md`);
}

View File

@@ -36,6 +36,28 @@ function coerceTruthToString(t) {
return '';
}
function countPhasePlansAndSummaries(phaseDir) {
const phaseFiles = fs.readdirSync(phaseDir);
const rootPlans = phaseFiles.filter(f => f.endsWith('-PLAN.md') || f === 'PLAN.md');
const rootSummaries = phaseFiles.filter(f => f.endsWith('-SUMMARY.md') || f === 'SUMMARY.md');
let nestedPlans = [];
let nestedSummaries = [];
const plansDir = path.join(phaseDir, 'plans');
if (fs.existsSync(plansDir)) {
const planFiles = fs.readdirSync(plansDir);
nestedPlans = planFiles.filter(f => /^PLAN-\d+.*\.md$/i.test(f));
nestedSummaries = planFiles.filter(f => /^SUMMARY-\d+.*\.md$/i.test(f));
}
return {
planCount: rootPlans.length + nestedPlans.length,
summaryCount: rootSummaries.length + nestedSummaries.length,
hasContext: phaseFiles.some(f => f.endsWith('-CONTEXT.md') || f === 'CONTEXT.md'),
hasResearch: phaseFiles.some(f => f.endsWith('-RESEARCH.md') || f === 'RESEARCH.md'),
};
}
/**
* Search for a phase header (and its section) within the given content string.
* Returns a result object if found (either a full match or a malformed_roadmap
@@ -197,11 +219,11 @@ function cmdRoadmapAnalyze(cwd, raw) {
const dirMatch = _phaseDirNames.find(d => phaseTokenMatches(d, normalized));
if (dirMatch) {
const phaseFiles = fs.readdirSync(path.join(phasesDir, dirMatch));
planCount = phaseFiles.filter(f => f.endsWith('-PLAN.md') || f === 'PLAN.md').length;
summaryCount = phaseFiles.filter(f => f.endsWith('-SUMMARY.md') || f === 'SUMMARY.md').length;
hasContext = phaseFiles.some(f => f.endsWith('-CONTEXT.md') || f === 'CONTEXT.md');
hasResearch = phaseFiles.some(f => f.endsWith('-RESEARCH.md') || f === 'RESEARCH.md');
const counts = countPhasePlansAndSummaries(path.join(phasesDir, dirMatch));
planCount = counts.planCount;
summaryCount = counts.summaryCount;
hasContext = counts.hasContext;
hasResearch = counts.hasResearch;
if (summaryCount >= planCount && planCount > 0) diskStatus = 'complete';
else if (summaryCount > 0) diskStatus = 'partial';

View File

@@ -71,7 +71,8 @@ function routeStateCommand({ state, args, cwd, raw, parseNamedArgs, error }) {
const { 'keep-recent': keepRecent, 'dry-run': dryRun } = parseNamedArgs(args, ['keep-recent'], ['dry-run']);
state.cmdStatePrune(cwd, { keepRecent: keepRecent || '3', dryRun: !!dryRun }, raw);
} else if (subcommand === 'complete-phase') {
state.cmdStateCompletePhase(cwd, raw);
const { phase: p } = parseNamedArgs(args, ['phase']);
state.cmdStateCompletePhase(cwd, raw, p || args[2]);
} else if (subcommand === 'milestone-switch') {
const { milestone, name } = parseNamedArgs(args, ['milestone', 'name']);
state.cmdStateMilestoneSwitch(cwd, milestone, name, raw);

View File

@@ -1690,32 +1690,36 @@ function cmdStatePrune(cwd, options, raw) {
* that the phase execution is finished and the project is ready for the next phase.
* Implements the `gsd state complete-phase` subcommand (issue #2735).
*/
function cmdStateCompletePhase(cwd, raw) {
function resolvePhaseIdForCompletePhase(content, overridePhase) {
const candidate = overridePhase ||
stateExtractField(content, 'Current Phase') ||
stateExtractField(content, 'Phase') ||
'';
// Accept canonical phase token only (e.g. 3, 03, 3A, 3.3, 10.2)
const phaseMatch = String(candidate).match(/(\d+[A-Z]?(?:\.\d+)*)/i);
return phaseMatch ? phaseMatch[1] : null;
}
function cmdStateCompletePhase(cwd, raw, overridePhase) {
const statePath = planningPaths(cwd).state;
if (!fs.existsSync(statePath)) {
output({ error: 'STATE.md not found' }, raw);
return;
}
const content = fs.readFileSync(statePath, 'utf-8');
const resolvedPhase = resolvePhaseIdForCompletePhase(content, overridePhase);
if (!resolvedPhase || /^phase$/i.test(resolvedPhase)) {
output({ error: 'Unable to resolve current phase. Pass an explicit phase: state complete-phase --phase <N>' }, raw);
return;
}
const today = new Date().toISOString().split('T')[0];
const updated = [];
let resolvedPhase = '?';
readModifyWriteStateMd(statePath, (content) => {
// Read the current phase number for descriptive messages.
//
// The 'Phase' fallback can match the decorated body line under
// `## Current Position` (e.g. `Phase: 01 (Foo) — EXECUTING`), which would
// flow downstream into messy `Status: Phase 01 (Foo) — EXECUTING complete`
// output. Strip everything past the leading numeric/decimal token so the
// fallback path produces a clean phase identifier matching the canonical
// `Current Phase` field. CodeRabbit nitpick on PR #2761.
const rawPhase = stateExtractField(content, 'Current Phase') ||
stateExtractField(content, 'Phase') ||
'';
const phaseToken = rawPhase.match(/^\s*([\w.-]+)/);
const currentPhase = phaseToken ? phaseToken[1] : '?';
resolvedPhase = currentPhase;
const currentPhase = resolvedPhase;
// Update Status field
const statusValue = `Phase ${currentPhase} complete`;

View File

@@ -244,8 +244,8 @@ For each CONTEXT.md read: extract `<decisions>` (locked preferences), `<specific
**Spike/sketch findings:** Check for project-local skills:
```bash
SPIKE_FINDINGS=$(ls ./.claude/skills/spike-findings-*/SKILL.md 2>/dev/null | head -1)
SKETCH_FINDINGS=$(ls ./.claude/skills/sketch-findings-*/SKILL.md 2>/dev/null | head -1)
SPIKE_FINDINGS=$(ls ./.claude/skills/spike-findings-*/SKILL.md 2>/dev/null | head -1 || true)
SKETCH_FINDINGS=$(ls ./.claude/skills/sketch-findings-*/SKILL.md 2>/dev/null | head -1 || true)
RAW_SPIKES=$(ls .planning/spikes/MANIFEST.md 2>/dev/null)
RAW_SKETCHES=$(ls .planning/sketches/MANIFEST.md 2>/dev/null)
```

View File

@@ -127,7 +127,7 @@ gsd-sdk query commit "docs: capture exploration — {topic_slug}" --files {file_
**Outputs:** {count} artifact(s) created
{list of created files}
Continue exploring with `/gsd-explore` or start working with `/gsd-next`.
Continue exploring with `/gsd-explore` or start working with `/gsd-progress --next`.
```
</process>

View File

@@ -64,12 +64,13 @@ Usage: `/gsd-map-codebase`
### Phase Planning
**`/gsd-discuss-phase <number> [--chain | --analyze | --power] [--batch[=N]]`**
**`/gsd-discuss-phase <number> [--chain | --analyze | --power | --assumptions] [--batch[=N]]`**
Help articulate your vision for a phase before planning.
- `--chain` — chained-prompt discuss flow
- `--analyze` — deep assumption analysis pass
- `--power` — power-user mode with extended question set
- `--assumptions` — surface Claude's implementation assumptions about the phase without an interactive session
- Captures how you imagine this phase working
- Creates CONTEXT.md with your vision, essentials, and boundaries
@@ -271,9 +272,10 @@ Resume work from previous session with full context restoration.
Usage: `/gsd-resume-work`
**`/gsd-pause-work`**
**`/gsd-pause-work [--report]`**
Create context handoff when pausing work mid-phase.
- `--report` — generate a post-session summary in `.planning/reports/` capturing commits, file changes, and phase progress
- Creates .continue-here file with current state
- Updates STATE.md session continuity section
- Captures in-progress work context
@@ -543,7 +545,7 @@ The commands above cover the most common day-to-day flows. Every command listed
- **`/gsd-spec-phase <phase> [--auto] [--text]`** — Clarify WHAT a phase delivers with ambiguity scoring; produces a SPEC.md before discuss-phase.
- **`/gsd-ai-integration-phase [phase]`** — Generate an AI-SPEC.md design contract for phases that involve building AI systems.
- **`/gsd-ui-phase [phase]`** — Generate UI design contract (UI-SPEC.md) for frontend phases.
- **`/gsd-import --from <filepath>`** — Ingest external plans with conflict detection against project decisions before writing anything.
- **`/gsd-import --from <filepath> | --from-gsd2`** — Ingest external plans with conflict detection, or reverse-migrate a GSD-2 (`.gsd/`) project back to GSD v1 (`.planning/`) format.
- **`/gsd-ingest-docs [path] [--mode new|merge] [--manifest <file>] [--resolve auto|interactive]`** — Bootstrap or merge a `.planning/` setup from existing ADRs, PRDs, SPECs, and docs in a repo.
### Planning & Execution
@@ -579,7 +581,7 @@ The commands above cover the most common day-to-day flows. Every command listed
### Workflow & Orchestration
- **`/gsd-manager`** — Interactive command center for managing multiple phases from one terminal.
- **`/gsd-manager [--analyze-deps]`** — Interactive command center for managing multiple phases from one terminal. `--analyze-deps` scans ROADMAP phases for dependency relationships before parallel execution.
- **`/gsd-workspace [--new | --list | --remove] [name]`** — Manage GSD workspaces: create, list, or remove isolated workspace environments.
- **`/gsd-workstreams`** — Manage parallel workstreams: list, create, switch, status, progress, complete, and resume.
- **`/gsd-review-backlog`** — Review and promote backlog items to active milestone.

View File

@@ -253,10 +253,10 @@ Check for existing spike and sketch work that should inform project setup:
```bash
# Check for spike findings skill (project-local)
SPIKE_SKILL=$(ls ./.claude/skills/spike-findings-*/SKILL.md 2>/dev/null | head -1)
SPIKE_SKILL=$(ls ./.claude/skills/spike-findings-*/SKILL.md 2>/dev/null | head -1 || true)
# Check for sketch findings skill (project-local)
SKETCH_SKILL=$(ls ./.claude/skills/sketch-findings-*/SKILL.md 2>/dev/null | head -1)
SKETCH_SKILL=$(ls ./.claude/skills/sketch-findings-*/SKILL.md 2>/dev/null | head -1 || true)
# Check for raw spikes/sketches in .planning/
HAS_SPIKES=$(ls .planning/spikes/MANIFEST.md 2>/dev/null)

View File

@@ -121,7 +121,7 @@ Choice [S]:
**Goal:** Resolve plans that ran without producing summaries during Phase {src} execution
**Source phase:** {src}
**Deferred at:** {date} during /gsd-next advancement to Phase {dest}
**Deferred at:** {date} during /gsd-progress --next advancement to Phase {dest}
**Plans:**
- [ ] {N}-{M}: {slug} (ran, no SUMMARY.md)
```
@@ -207,7 +207,7 @@ Display the determination:
```
Then immediately invoke the determined command via SlashCommand.
Do not ask for confirmation — the whole point of `/gsd-next` is zero-friction advancement.
Do not ask for confirmation — the whole point of `/gsd-progress --next` is zero-friction advancement.
</step>
</process>

View File

@@ -668,8 +668,8 @@ REVIEWS_PATH=$(_gsd_field "$INIT" reviews_path)
PATTERNS_PATH=$(_gsd_field "$INIT" patterns_path)
# Detect spike/sketch findings skills (project-local)
SPIKE_FINDINGS_PATH=$(ls ./.claude/skills/spike-findings-*/SKILL.md 2>/dev/null | head -1)
SKETCH_FINDINGS_PATH=$(ls ./.claude/skills/sketch-findings-*/SKILL.md 2>/dev/null | head -1)
SPIKE_FINDINGS_PATH=$(ls ./.claude/skills/spike-findings-*/SKILL.md 2>/dev/null | head -1 || true)
SKETCH_FINDINGS_PATH=$(ls ./.claude/skills/sketch-findings-*/SKILL.md 2>/dev/null | head -1 || true)
```
## 7.5. Verify Nyquist Artifacts

View File

@@ -275,7 +275,7 @@ PHASE_HAS_UI=$(echo "$PHASE_SECTION" | grep -qi "UI hint.*yes" && echo "true" ||
**Also available:**
- `/gsd-ui-phase {phase}` — generate UI design contract (recommended for frontend phases)
- `/gsd-plan-phase {phase}` — skip discussion, plan directly
- `/gsd-list-phase-assumptions {phase}` — see Claude's assumptions
- `/gsd-discuss-phase {phase}` — include assumptions check before planning
---
```
@@ -297,7 +297,7 @@ PHASE_HAS_UI=$(echo "$PHASE_SECTION" | grep -qi "UI hint.*yes" && echo "true" ||
**Also available:**
- `/gsd-plan-phase {phase} ${GSD_WS}` — skip discussion, plan directly
- `/gsd-list-phase-assumptions {phase} ${GSD_WS}` — see Claude's assumptions
- `/gsd-discuss-phase {phase} ${GSD_WS}` — include assumptions check before planning
---
```

View File

@@ -128,8 +128,8 @@ If `$VALIDATE_MODE` only:
if ! command -v gsd-sdk &>/dev/null; then
echo "⚠ gsd-sdk not found in PATH — /gsd-quick requires it."
echo ""
echo "Install the GSD SDK:"
echo " npm install -g @gsd-build/sdk"
echo "Install the query-capable GSD SDK CLI:"
echo " npm install -g get-shit-done-cc"
echo ""
echo "Or update GSD to get the latest packages:"
echo " /gsd-update"

View File

@@ -225,9 +225,11 @@ Wait for user selection.
</step>
<step name="route_to_workflow">
Based on user selection, route to appropriate workflow:
Based on user selection, route to appropriate workflow.
- **Execute plan** → Show command for user to run after clearing:
Resume-specific exception: do **not** emit `/clear then:` here. Resume is already a session-entry flow, so the next command should be shown directly.
- **Execute plan** → Show direct next command:
```
---
@@ -235,13 +237,11 @@ Based on user selection, route to appropriate workflow:
**{phase}-{plan}: [Plan Name]** — [objective from PLAN.md]
`/clear` then:
`/gsd-execute-phase {phase} ${GSD_WS}`
---
```
- **Plan phase** → Show command for user to run after clearing:
- **Plan phase** → Show direct next command:
```
---
@@ -249,8 +249,6 @@ Based on user selection, route to appropriate workflow:
**Phase [N]: [Name]** — [Goal from ROADMAP.md]
`/clear` then:
`/gsd-plan-phase [phase-number] ${GSD_WS}`
---

View File

@@ -31,7 +31,7 @@ Parse JSON for: `phase_dir`, `phase_number`, `phase_name`, `phase_slug`, `padded
Detect sketch findings:
```bash
SKETCH_FINDINGS_PATH=$(ls ./.claude/skills/sketch-findings-*/SKILL.md 2>/dev/null | head -1)
SKETCH_FINDINGS_PATH=$(ls ./.claude/skills/sketch-findings-*/SKILL.md 2>/dev/null | head -1 || true)
```
Resolve UI agent models:

View File

@@ -486,10 +486,15 @@ const path = require('path');
for (const relPath of custom_files) {
const src = path.join(runtimeDir, relPath);
const dst = path.join(backupDir, relPath);
if (fs.existsSync(src)) {
if (!fs.existsSync(src)) continue;
try {
fs.mkdirSync(path.dirname(dst), { recursive: true });
fs.copyFileSync(src, dst);
console.log(' Backed up: ' + relPath);
} catch (err) {
const code = err && err.code ? String(err.code) : 'ERROR';
console.log(' Skipped (non-fatal): ' + relPath + ' [' + code + ']');
}
}
JSEOF

View File

@@ -237,6 +237,26 @@ describe('loadConfig', () => {
expect(config).toEqual(CONFIG_DEFAULTS);
});
it('maps legacy top-level branching_strategy into git.branching_strategy', async () => {
await writeFile(
join(tmpDir, '.planning', 'config.json'),
JSON.stringify({ branching_strategy: 'phase' }),
);
const config = await loadConfig(tmpDir);
expect(config.git.branching_strategy).toBe('phase');
});
it('git.branching_strategy overrides legacy top-level branching_strategy when both are present', async () => {
await writeFile(
join(tmpDir, '.planning', 'config.json'),
JSON.stringify({ branching_strategy: 'phase', git: { branching_strategy: 'milestone' } }),
);
const config = await loadConfig(tmpDir);
expect(config.git.branching_strategy).toBe('milestone');
});
it('does not mutate CONFIG_DEFAULTS between calls', async () => {
const before = structuredClone(CONFIG_DEFAULTS);

View File

@@ -177,11 +177,16 @@ export async function loadConfig(projectDir: string, workstream?: string): Promi
}
function mergeDefaults(parsed: Record<string, unknown>): GSDConfig {
const legacyBranchingStrategy = typeof parsed.branching_strategy === 'string'
? parsed.branching_strategy
: undefined;
return {
...structuredClone(CONFIG_DEFAULTS),
...parsed,
git: {
...CONFIG_DEFAULTS.git,
...(legacyBranchingStrategy ? { branching_strategy: legacyBranchingStrategy } : {}),
...(parsed.git as Partial<GitConfig> ?? {}),
},
workflow: {

View File

@@ -0,0 +1,21 @@
import { describe, expect, it } from 'vitest';
import { GSDToolsError } from './gsd-tools-error.js';
describe('GSDToolsError constructors', () => {
it('builds timeout-classified errors', () => {
const err = GSDToolsError.timeout('timeout', 'state', ['load'], '', 1000);
expect(err.classification).toEqual({ kind: 'timeout', timeoutMs: 1000 });
expect(err.exitCode).toBeNull();
});
it('builds failure-classified errors', () => {
const err = GSDToolsError.failure('boom', 'state', ['load'], 1);
expect(err.classification).toEqual({ kind: 'failure' });
expect(err.exitCode).toBe(1);
});
it('defaults direct constructor to failure classification', () => {
const err = new GSDToolsError('boom', 'state', ['load'], 1, 'stderr');
expect(err.classification).toEqual({ kind: 'failure' });
});
});

View File

@@ -1,3 +1,16 @@
export interface GSDToolsErrorClassification {
kind: 'timeout' | 'failure';
timeoutMs?: number;
}
function timeoutClassification(timeoutMs?: number): GSDToolsErrorClassification {
return timeoutMs === undefined ? { kind: 'timeout' } : { kind: 'timeout', timeoutMs };
}
function failureClassification(): GSDToolsErrorClassification {
return { kind: 'failure' };
}
export class GSDToolsError extends Error {
constructor(
message: string,
@@ -5,9 +18,48 @@ export class GSDToolsError extends Error {
public readonly args: string[],
public readonly exitCode: number | null,
public readonly stderr: string,
options?: { cause?: unknown },
options?: { cause?: unknown; classification?: GSDToolsErrorClassification },
) {
super(message, options);
this.name = 'GSDToolsError';
this.classification = options?.classification ?? failureClassification();
}
static timeout(
message: string,
command: string,
args: string[],
stderr = '',
timeoutMs?: number,
options?: { cause?: unknown; exitCode?: number | null },
): GSDToolsError {
return new GSDToolsError(
message,
command,
args,
options?.exitCode ?? null,
stderr,
{ cause: options?.cause, classification: timeoutClassification(timeoutMs) },
);
}
static failure(
message: string,
command: string,
args: string[],
exitCode: number | null,
stderr = '',
options?: { cause?: unknown },
): GSDToolsError {
return new GSDToolsError(
message,
command,
args,
exitCode,
stderr,
{ cause: options?.cause, classification: failureClassification() },
);
}
public readonly classification: GSDToolsErrorClassification;
}

View File

@@ -13,7 +13,7 @@
import type { InitNewProjectInfo, PhaseOpInfo, PhasePlanIndex, RoadmapAnalysis } from './types.js';
import type { GSDEventStream } from './event-stream.js';
import { toGSDToolsError } from './query-tools-error-mapper.js';
import { toToolsErrorFromUnknown } from './query-tools-error-factory.js';
import { GSDToolsError } from './gsd-tools-error.js';
import { resolveQueryCommand, type QueryCommandResolution } from './query/query-command-resolution-strategy.js';
import { QueryExecutionPolicy } from './query-execution-policy.js';
@@ -107,10 +107,6 @@ export class GSDTools {
return resolveQueryCommand(command, args, this.registry);
}
private toToolsError(command: string, args: string[], err: unknown): GSDToolsError {
return toGSDToolsError(command, args, err);
}
private async dispatchNativeHotpath(
legacyCommand: string,
legacyArgs: string[],
@@ -118,17 +114,22 @@ export class GSDTools {
registryArgs: string[],
mode: 'json' | 'raw',
): Promise<unknown> {
try {
return await this.nativeHotpathAdapter.dispatch(
return this.executeWithToolsError(legacyCommand, legacyArgs, () =>
this.nativeHotpathAdapter.dispatch(
legacyCommand,
legacyArgs,
registryCommand,
registryArgs,
mode,
);
));
}
private async executeWithToolsError<T>(command: string, args: string[], work: () => Promise<T>): Promise<T> {
try {
return await work();
} catch (err) {
if (err instanceof GSDToolsError) throw err;
throw this.toToolsError(legacyCommand, legacyArgs, err);
throw toToolsErrorFromUnknown(command, args, err);
}
}
@@ -139,12 +140,7 @@ export class GSDTools {
* Handles the `@file:` prefix pattern for large results.
*/
async exec(command: string, args: string[] = []): Promise<unknown> {
try {
return await this.commandExecutor.exec(command, args, 'json');
} catch (err) {
if (err instanceof GSDToolsError) throw err;
throw this.toToolsError(command, args, err);
}
return this.executeWithToolsError(command, args, () => this.commandExecutor.exec(command, args, 'json'));
}
// ─── Raw exec (no JSON parsing) ───────────────────────────────────────
@@ -154,12 +150,10 @@ export class GSDTools {
* Use for commands like `config-set` that return plain text, not JSON.
*/
async execRaw(command: string, args: string[] = []): Promise<string> {
try {
return await this.commandExecutor.exec(command, args, 'raw') as string;
} catch (err) {
if (err instanceof GSDToolsError) throw err;
throw this.toToolsError(command, args, err);
}
return this.executeWithToolsError(command, args, async () => {
const out = await this.commandExecutor.exec(command, args, 'raw');
return typeof out === 'string' ? out : String(out ?? '');
});
}

View File

@@ -1,4 +1,5 @@
import { describe, it, expect, vi } from 'vitest';
import { GSDToolsError } from './gsd-tools-error.js';
import { QueryRegistry } from './query/registry.js';
import { GSDTransport } from './gsd-transport.js';
@@ -119,6 +120,36 @@ describe('GSDTransport', () => {
expect(adapters.execSubprocessJson).not.toHaveBeenCalled();
});
it('does not fallback after typed timeout native error', async () => {
const registry = new QueryRegistry();
registry.register('state.load', async () => ({ data: { ok: true } }));
const timeoutError = GSDToolsError.timeout('native timed out', 'state', ['load'], '', 500);
const adapters = {
dispatchNative: vi.fn(async () => {
throw timeoutError;
}),
execSubprocessJson: vi.fn(async () => ({ ok: 'fallback' })),
execSubprocessRaw: vi.fn(async () => 'fallback-raw'),
};
const transport = new GSDTransport(registry, adapters);
await expect(transport.run({
legacyCommand: 'state',
legacyArgs: ['load'],
registryCommand: 'state.load',
registryArgs: [],
mode: 'json',
projectDir: '/tmp',
}, {
preferNative: true,
allowFallbackToSubprocess: true,
})).rejects.toBe(timeoutError);
expect(adapters.execSubprocessJson).not.toHaveBeenCalled();
});
it('formats native raw output via formatNativeRaw when provided', async () => {
const registry = new QueryRegistry();
registry.register('commit', async () => ({ data: { hash: 'abc123' } }));

View File

@@ -1,6 +1,7 @@
import type { QueryResult } from './query/utils.js';
import type { QueryRegistry } from './query/registry.js';
import type { TransportMode } from './gsd-transport-policy.js';
import { toFailureSignal } from './query-failure-classification.js';
export interface TransportRequest {
legacyCommand: string;
@@ -24,12 +25,6 @@ export interface TransportPolicyLike {
allowFallbackToSubprocess: boolean;
}
function isTimeoutLikeError(error: unknown): boolean {
if (!(error instanceof Error)) return false;
if (error.name === 'TimeoutError' || error.name === 'AbortError') return true;
return error.message.includes('timed out after');
}
export class GSDTransport {
constructor(
private readonly registry: QueryRegistry,
@@ -37,33 +32,48 @@ export class GSDTransport {
) {}
async run(request: TransportRequest, policy: TransportPolicyLike): Promise<unknown> {
const forceSubprocess = Boolean(request.workstream);
if (!forceSubprocess && policy.preferNative && this.registry.has(request.registryCommand)) {
if (this.shouldUseNative(request, policy)) {
try {
const native = await this.adapters.dispatchNative(request);
if (request.mode === 'raw') {
if (this.adapters.formatNativeRaw) {
return this.adapters.formatNativeRaw(request.registryCommand, native.data).trim();
}
return this.toRaw(native.data);
}
return native.data;
return this.projectNativeOutput(request, native.data);
} catch (error) {
if (!policy.allowFallbackToSubprocess) throw error;
// Do not subprocess-fallback after a timed-out native dispatch:
// the timeout does not cancel the native handler, so falling through
// would run the same command twice (double-execution race).
if (isTimeoutLikeError(error)) throw error;
if (this.shouldRethrowNativeError(error, policy)) throw error;
}
}
return this.dispatchSubprocess(request);
}
private shouldUseNative(request: TransportRequest, policy: TransportPolicyLike): boolean {
const forceSubprocess = Boolean(request.workstream);
return !forceSubprocess && policy.preferNative && this.registry.has(request.registryCommand);
}
private shouldRethrowNativeError(error: unknown, policy: TransportPolicyLike): boolean {
if (!policy.allowFallbackToSubprocess) return true;
// Do not subprocess-fallback after a timed-out native dispatch:
// the timeout does not cancel the native handler, so falling through
// would run the same command twice (double-execution race).
return toFailureSignal(error).kind === 'timeout';
}
private dispatchSubprocess(request: TransportRequest): Promise<unknown> {
if (request.mode === 'raw') {
return this.adapters.execSubprocessRaw(request.legacyCommand, request.legacyArgs);
}
return this.adapters.execSubprocessJson(request.legacyCommand, request.legacyArgs);
}
private projectNativeOutput(request: TransportRequest, data: unknown): unknown {
if (request.mode === 'raw') {
if (this.adapters.formatNativeRaw) {
return this.adapters.formatNativeRaw(request.registryCommand, data).trim();
}
return this.toRaw(data);
}
return data;
}
private toRaw(data: unknown): string {
if (typeof data === 'string') return data.trim();
const json = JSON.stringify(data, null, 2);

View File

@@ -0,0 +1,23 @@
import { describe, expect, it } from 'vitest';
import {
errorMessage,
timeoutMessage,
toFailureSignal,
} from './query-failure-classification.js';
import { GSDToolsError } from './gsd-tools-error.js';
describe('query failure classification', () => {
it('extracts timeout metadata from message', () => {
const msg = timeoutMessage('state', ['load'], 30000);
expect(toFailureSignal(new Error(msg))).toEqual({ kind: 'timeout', message: msg, timeoutMs: 30000 });
});
it('normalizes unknown error values', () => {
expect(errorMessage('boom')).toBe('boom');
expect(errorMessage(new Error('x'))).toBe('x');
});
it('prefers typed classification from GSDToolsError', () => {
const err = GSDToolsError.timeout('x', 'state', ['load'], '', 2000);
expect(toFailureSignal(err)).toEqual({ kind: 'timeout', message: 'x', timeoutMs: 2000 });
});
});

View File

@@ -0,0 +1,42 @@
import { GSDToolsError } from './gsd-tools-error.js';
export interface QueryFailureSignal {
kind: 'timeout' | 'failure';
message: string;
timeoutMs?: number;
}
export function errorMessage(error: unknown): string {
return error instanceof Error ? error.message : String(error);
}
function parseTimeoutMs(message: string): number | undefined {
const m = message.match(/timed out after\s+(\d+)ms/i);
if (!m) return undefined;
const n = Number.parseInt(m[1], 10);
return Number.isFinite(n) ? n : undefined;
}
function isTimeoutMessage(message: string): boolean {
return /timed out after/i.test(message);
}
export function timeoutMessage(command: string, args: string[], timeoutMs: number): string {
return `gsd-tools timed out after ${timeoutMs}ms: ${command} ${args.join(' ')}`;
}
export function toFailureSignal(error: unknown): QueryFailureSignal {
if (error instanceof GSDToolsError && error.classification) {
return {
kind: error.classification.kind,
message: error.message,
timeoutMs: error.classification.timeoutMs,
};
}
const message = errorMessage(error);
if (isTimeoutMessage(message)) {
return { kind: 'timeout', message, timeoutMs: parseTimeoutMs(message) };
}
return { kind: 'failure', message };
}

View File

@@ -1,13 +1,12 @@
import type { GSDEventStream } from './event-stream.js';
import { createRegistry } from './query/index.js';
import type { QueryResult } from './query/utils.js';
import { GSDTransport } from './gsd-transport.js';
import { QueryExecutionPolicy } from './query-execution-policy.js';
import { QuerySubprocessAdapter } from './query-subprocess-adapter.js';
import { QueryNativeDirectAdapter } from './query-native-direct-adapter.js';
import { QueryNativeHotpathAdapter } from './query-native-hotpath-adapter.js';
import { formatQueryRawOutput } from './query-raw-output-projection.js';
import { GSDToolsError } from './gsd-tools-error.js';
import { createQueryNativeErrorFactory, createQueryToolsErrorFactory } from './query-tools-error-factory.js';
export interface GSDToolsRuntime {
registry: ReturnType<typeof createRegistry>;
@@ -28,30 +27,33 @@ export function createGSDToolsRuntime(opts: {
}): GSDToolsRuntime {
const registry = createRegistry(opts.eventStream, opts.sessionId);
const queryToolsErrorFactory = createQueryToolsErrorFactory();
const subprocessAdapter = new QuerySubprocessAdapter({
projectDir: opts.projectDir,
gsdToolsPath: opts.gsdToolsPath,
timeoutMs: opts.timeoutMs,
workstream: opts.workstream,
createToolsError: (message, command, args, exitCode, stderr) =>
new GSDToolsError(message, command, args, exitCode, stderr),
...queryToolsErrorFactory,
});
const nativeErrorFactory = createQueryNativeErrorFactory(opts.timeoutMs);
const nativeDirectAdapter = new QueryNativeDirectAdapter({
timeoutMs: opts.timeoutMs,
dispatch: (registryCommand, registryArgs) => registry.dispatch(registryCommand, registryArgs, opts.projectDir),
createTimeoutError: (message, command, args) => new GSDToolsError(message, command, args, null, ''),
...nativeErrorFactory,
});
const transport = new GSDTransport(registry, {
dispatchNative: async (request) => nativeDirectAdapter.dispatchResult(
dispatchNative: (request) => nativeDirectAdapter.dispatchResult(
request.legacyCommand,
request.legacyArgs,
request.registryCommand,
request.registryArgs,
) as Promise<QueryResult>,
execSubprocessJson: async (legacyCommand, legacyArgs) => subprocessAdapter.execJson(legacyCommand, legacyArgs),
execSubprocessRaw: async (legacyCommand, legacyArgs) => subprocessAdapter.execRaw(legacyCommand, legacyArgs),
),
execSubprocessJson: (legacyCommand, legacyArgs) => subprocessAdapter.execJson(legacyCommand, legacyArgs),
execSubprocessRaw: (legacyCommand, legacyArgs) => subprocessAdapter.execRaw(legacyCommand, legacyArgs),
formatNativeRaw: (registryCommand, data) => formatQueryRawOutput(registryCommand, data),
});

View File

@@ -0,0 +1,35 @@
import { describe, expect, it } from 'vitest';
import { GSDToolsError } from './gsd-tools-error.js';
import { QueryNativeDirectAdapter } from './query-native-direct-adapter.js';
describe('QueryNativeDirectAdapter', () => {
it('wraps native failures as typed failure errors', async () => {
const adapter = new QueryNativeDirectAdapter({
timeoutMs: 1000,
dispatch: async () => {
throw new Error('boom');
},
createNativeTimeoutError: (message, command, args) => GSDToolsError.timeout(message, command, args),
createNativeFailureError: (message, command, args, cause) => GSDToolsError.failure(message, command, args, 1, '', { cause }),
});
await expect(adapter.dispatchJson('state', ['load'], 'state.load', [])).rejects.toMatchObject({
classification: GSDToolsError.failure('x', 'state', ['load'], 1).classification,
command: 'state',
});
});
it('preserves timeout errors', async () => {
const timeoutErr = GSDToolsError.timeout('timeout', 'state', ['load']);
const adapter = new QueryNativeDirectAdapter({
timeoutMs: 1000,
dispatch: async () => {
throw timeoutErr;
},
createNativeTimeoutError: (message, command, args) => GSDToolsError.timeout(message, command, args),
createNativeFailureError: (message, command, args, cause) => GSDToolsError.failure(message, command, args, 1, '', { cause }),
});
await expect(adapter.dispatchJson('state', ['load'], 'state.load', [])).rejects.toBe(timeoutErr);
});
});

View File

@@ -1,10 +1,12 @@
import { formatQueryRawOutput } from './query-raw-output-projection.js';
import { GSDToolsError } from './gsd-tools-error.js';
import { errorMessage, timeoutMessage } from './query-failure-classification.js';
import type { QueryNativeErrorFactory } from './query-tools-error-factory.js';
import type { QueryResult } from './query/utils.js';
export interface QueryNativeDirectAdapterDeps {
export interface QueryNativeDirectAdapterDeps extends QueryNativeErrorFactory {
timeoutMs: number;
dispatch: (registryCommand: string, registryArgs: string[]) => Promise<QueryResult>;
createTimeoutError: (message: string, command: string, args: string[]) => Error;
}
/**
@@ -14,17 +16,35 @@ export class QueryNativeDirectAdapter {
constructor(private readonly deps: QueryNativeDirectAdapterDeps) {}
async dispatchResult(legacyCommand: string, legacyArgs: string[], registryCommand: string, registryArgs: string[]): Promise<QueryResult> {
return this.withTimeout(legacyCommand, legacyArgs, this.deps.dispatch(registryCommand, registryArgs));
try {
return await this.withTimeout(legacyCommand, legacyArgs, this.deps.dispatch(registryCommand, registryArgs));
} catch (error) {
throw this.toNativeDispatchError(legacyCommand, legacyArgs, error);
}
}
async dispatchJson(legacyCommand: string, legacyArgs: string[], registryCommand: string, registryArgs: string[]): Promise<unknown> {
return this.dispatchData(legacyCommand, legacyArgs, registryCommand, registryArgs);
}
async dispatchRaw(legacyCommand: string, legacyArgs: string[], registryCommand: string, registryArgs: string[]): Promise<string> {
const data = await this.dispatchData(legacyCommand, legacyArgs, registryCommand, registryArgs);
return formatQueryRawOutput(registryCommand, data).trim();
}
private async dispatchData(
legacyCommand: string,
legacyArgs: string[],
registryCommand: string,
registryArgs: string[],
): Promise<unknown> {
const result = await this.dispatchResult(legacyCommand, legacyArgs, registryCommand, registryArgs);
return result.data;
}
async dispatchRaw(legacyCommand: string, legacyArgs: string[], registryCommand: string, registryArgs: string[]): Promise<string> {
const result = await this.dispatchResult(legacyCommand, legacyArgs, registryCommand, registryArgs);
return formatQueryRawOutput(registryCommand, result.data).trim();
private toNativeDispatchError(legacyCommand: string, legacyArgs: string[], error: unknown): GSDToolsError {
if (error instanceof GSDToolsError) return error;
return this.deps.createNativeFailureError(errorMessage(error), legacyCommand, legacyArgs, error);
}
private async withTimeout<T>(legacyCommand: string, legacyArgs: string[], work: Promise<T>): Promise<T> {
@@ -32,8 +52,8 @@ export class QueryNativeDirectAdapter {
const timeoutPromise = new Promise<never>((_, reject) => {
timeoutId = setTimeout(() => {
reject(
this.deps.createTimeoutError(
`gsd-tools timed out after ${this.deps.timeoutMs}ms: ${legacyCommand} ${legacyArgs.join(' ')}`,
this.deps.createNativeTimeoutError(
timeoutMessage(legacyCommand, legacyArgs, this.deps.timeoutMs),
legacyCommand,
legacyArgs,
),

View File

@@ -19,11 +19,25 @@ export class QueryNativeHotpathAdapter {
mode: 'json' | 'raw',
): Promise<unknown> {
if (!this.shouldUseNativeQuery()) {
return mode === 'raw'
? this.execRawFallback(legacyCommand, legacyArgs)
: this.execJsonFallback(legacyCommand, legacyArgs);
return this.dispatchFallback(legacyCommand, legacyArgs, mode);
}
return this.dispatchNative(legacyCommand, legacyArgs, registryCommand, registryArgs, mode);
}
private dispatchFallback(legacyCommand: string, legacyArgs: string[], mode: 'json' | 'raw'): Promise<unknown> {
return mode === 'raw'
? this.execRawFallback(legacyCommand, legacyArgs)
: this.execJsonFallback(legacyCommand, legacyArgs);
}
private dispatchNative(
legacyCommand: string,
legacyArgs: string[],
registryCommand: string,
registryArgs: string[],
mode: 'json' | 'raw',
): Promise<unknown> {
return mode === 'raw'
? this.nativeDirect.dispatchRaw(legacyCommand, legacyArgs, registryCommand, registryArgs)
: this.nativeDirect.dispatchJson(legacyCommand, legacyArgs, registryCommand, registryArgs);

View File

@@ -31,4 +31,9 @@ describe('formatQueryRawOutput', () => {
expect(formatQueryRawOutput('state begin-phase', { updated: ['x'] })).toBe('true');
expect(formatQueryRawOutput('state begin-phase', { updated: [] })).toBe('false');
});
it('never returns undefined for non-JSON top-level values', () => {
expect(formatQueryRawOutput('commit', undefined)).toBe('undefined');
expect(formatQueryRawOutput('commit', Symbol('x'))).toBe('Symbol(x)');
});
});

View File

@@ -1,5 +1,10 @@
import { formatStateLoadRawStdout } from './query/state-project-load.js';
function safeStringify(value: unknown): string {
const out = JSON.stringify(value, null, 2);
return out ?? String(value);
}
/**
* Raw output projection for native query results.
* Owns CLI-facing string contracts for raw mode commands.
@@ -11,7 +16,7 @@ export function formatQueryRawOutput(registryCommand: string, data: unknown): st
if (registryCommand === 'commit') {
if (data == null || typeof data !== 'object' || Array.isArray(data)) {
return JSON.stringify(data, null, 2);
return safeStringify(data);
}
const d = data as Record<string, unknown>;
if (d.committed === true) {
@@ -32,12 +37,12 @@ export function formatQueryRawOutput(registryCommand: string, data: unknown): st
}
return r || 'nothing';
}
return JSON.stringify(data, null, 2);
return safeStringify(data);
}
if (registryCommand === 'config-set') {
if (data == null || typeof data !== 'object' || Array.isArray(data)) {
return JSON.stringify(data, null, 2);
return safeStringify(data);
}
const d = data as Record<string, unknown>;
if ((d.updated === true || d.set === true) && d.key !== undefined) {
@@ -50,12 +55,12 @@ export function formatQueryRawOutput(registryCommand: string, data: unknown): st
}
return `${d.key}=${String(v)}`;
}
return JSON.stringify(data, null, 2);
return safeStringify(data);
}
if (registryCommand === 'state.begin-phase' || registryCommand === 'state begin-phase') {
if (data == null || typeof data !== 'object' || Array.isArray(data)) {
return JSON.stringify(data, null, 2);
return safeStringify(data);
}
const d = data as Record<string, unknown>;
const u = d.updated as string[] | undefined;
@@ -65,5 +70,5 @@ export function formatQueryRawOutput(registryCommand: string, data: unknown): st
if (typeof data === 'string') {
return data;
}
return JSON.stringify(data, null, 2);
return safeStringify(data);
}

View File

@@ -41,7 +41,9 @@ describe('QuerySubprocessAdapter', () => {
projectDir: dir,
gsdToolsPath,
timeoutMs: 2_000,
createToolsError: (message, command, args, exitCode, stderr) =>
createTimeoutError: (message, command, args, stderr) =>
new FakeToolsError(message, command, args, null, stderr) as never,
createFailureError: (message, command, args, exitCode, stderr) =>
new FakeToolsError(message, command, args, exitCode, stderr) as never,
});
}
@@ -62,6 +64,17 @@ describe('QuerySubprocessAdapter', () => {
await expect(adapter.execJson('state', ['load'])).resolves.toEqual({ from: 'file' });
});
it('execJson resolves relative @file output against projectDir', async () => {
const relDir = join(dir, '.planning');
await mkdir(relDir, { recursive: true });
const relFile = join(relDir, 'out.json');
await writeFile(relFile, JSON.stringify({ from: 'relative-file' }));
const script = await createScript('file-relative.cjs', `process.stdout.write('@file:.planning/out.json');`);
const adapter = createAdapter(script);
await expect(adapter.execJson('state', ['load'])).resolves.toEqual({ from: 'relative-file' });
});
it('execRaw returns trimmed stdout', async () => {
const script = await createScript('raw.cjs', `process.stdout.write(' hello ');`);
const adapter = createAdapter(script);

View File

@@ -1,27 +1,21 @@
import { execFile } from 'node:child_process';
import { isAbsolute, resolve } from 'node:path';
import { readFile } from 'node:fs/promises';
import type { GSDToolsError } from './gsd-tools-error.js';
import { timeoutMessage } from './query-failure-classification.js';
import type { QueryToolsErrorFactory } from './query-tools-error-factory.js';
export interface QuerySubprocessAdapterDeps {
export interface QuerySubprocessAdapterDeps extends QueryToolsErrorFactory {
projectDir: string;
gsdToolsPath: string;
timeoutMs: number;
workstream?: string;
createToolsError: (
message: string,
command: string,
args: string[],
exitCode: number | null,
stderr: string,
) => GSDToolsError;
}
export class QuerySubprocessAdapter {
constructor(private readonly deps: QuerySubprocessAdapterDeps) {}
async execJson(command: string, args: string[]): Promise<unknown> {
const wsArgs = this.deps.workstream ? ['--ws', this.deps.workstream] : [];
const fullArgs = [this.deps.gsdToolsPath, command, ...args, ...wsArgs];
const fullArgs = this.commandArgs(command, args);
return new Promise<unknown>((resolve, reject) => {
const child = execFile(
@@ -37,28 +31,7 @@ export class QuerySubprocessAdapter {
const stderrStr = stderr?.toString() ?? '';
if (error) {
if (error.killed || (error as NodeJS.ErrnoException).code === 'ETIMEDOUT') {
reject(
this.deps.createToolsError(
`gsd-tools timed out after ${this.deps.timeoutMs}ms: ${command} ${args.join(' ')}`,
command,
args,
null,
stderrStr,
),
);
return;
}
reject(
this.deps.createToolsError(
`gsd-tools exited with code ${error.code ?? 'unknown'}: ${command} ${args.join(' ')}${stderrStr ? `\n${stderrStr}` : ''}`,
command,
args,
typeof error.code === 'number' ? error.code : (error as { status?: number }).status ?? 1,
stderrStr,
),
);
reject(this.processExecutionError(command, args, error, stderrStr));
return;
}
@@ -68,7 +41,7 @@ export class QuerySubprocessAdapter {
resolve(parsed);
} catch (parseErr) {
reject(
this.deps.createToolsError(
this.deps.createFailureError(
`Failed to parse gsd-tools output for "${command}": ${parseErr instanceof Error ? parseErr.message : String(parseErr)}\nRaw output: ${raw.slice(0, 500)}`,
command,
args,
@@ -81,14 +54,13 @@ export class QuerySubprocessAdapter {
);
child.on('error', (err) => {
reject(this.deps.createToolsError(`Failed to execute gsd-tools: ${err.message}`, command, args, null, ''));
reject(this.processSpawnError(command, args, err));
});
});
}
async execRaw(command: string, args: string[]): Promise<string> {
const wsArgs = this.deps.workstream ? ['--ws', this.deps.workstream] : [];
const fullArgs = [this.deps.gsdToolsPath, command, ...args, ...wsArgs, '--raw'];
const fullArgs = [...this.commandArgs(command, args), '--raw'];
return new Promise<string>((resolve, reject) => {
const child = execFile(
@@ -103,27 +75,7 @@ export class QuerySubprocessAdapter {
(error, stdout, stderr) => {
const stderrStr = stderr?.toString() ?? '';
if (error) {
if (error.killed || (error as NodeJS.ErrnoException).code === 'ETIMEDOUT') {
reject(
this.deps.createToolsError(
`gsd-tools timed out after ${this.deps.timeoutMs}ms: ${command} ${args.join(' ')}`,
command,
args,
null,
stderrStr,
),
);
return;
}
reject(
this.deps.createToolsError(
`gsd-tools exited with code ${error.code ?? 'unknown'}: ${command} ${args.join(' ')}${stderrStr ? `\n${stderrStr}` : ''}`,
command,
args,
typeof error.code === 'number' ? error.code : (error as { status?: number }).status ?? 1,
stderrStr,
),
);
reject(this.processExecutionError(command, args, error, stderrStr));
return;
}
resolve((stdout?.toString() ?? '').trim());
@@ -131,11 +83,45 @@ export class QuerySubprocessAdapter {
);
child.on('error', (err) => {
reject(this.deps.createToolsError(`Failed to execute gsd-tools: ${err.message}`, command, args, null, ''));
reject(this.processSpawnError(command, args, err));
});
});
}
private commandArgs(command: string, args: string[]): string[] {
const wsArgs = this.deps.workstream ? ['--ws', this.deps.workstream] : [];
return [this.deps.gsdToolsPath, command, ...args, ...wsArgs];
}
private processExecutionError(
command: string,
args: string[],
error: Error & { code?: unknown; status?: number; killed?: boolean },
stderrStr: string,
) {
if (error.killed || (error as NodeJS.ErrnoException).code === 'ETIMEDOUT') {
return this.deps.createTimeoutError(
timeoutMessage(command, args, this.deps.timeoutMs),
command,
args,
stderrStr,
this.deps.timeoutMs,
);
}
return this.deps.createFailureError(
`gsd-tools exited with code ${error.code ?? 'unknown'}: ${command} ${args.join(' ')}${stderrStr ? `\n${stderrStr}` : ''}`,
command,
args,
typeof error.code === 'number' ? error.code : error.status ?? 1,
stderrStr,
);
}
private processSpawnError(command: string, args: string[], err: Error) {
return this.deps.createFailureError(`Failed to execute gsd-tools: ${err.message}`, command, args, null, '');
}
private async parseOutput(raw: string): Promise<unknown> {
const trimmed = raw.trim();
@@ -146,11 +132,12 @@ export class QuerySubprocessAdapter {
let jsonStr = trimmed;
if (jsonStr.startsWith('@file:')) {
const filePath = jsonStr.slice(6).trim();
const resolvedPath = isAbsolute(filePath) ? filePath : resolve(this.deps.projectDir, filePath);
try {
jsonStr = await readFile(filePath, 'utf-8');
jsonStr = await readFile(resolvedPath, 'utf-8');
} catch (err) {
const reason = err instanceof Error ? err.message : String(err);
throw new Error(`Failed to read gsd-tools @file: indirection at "${filePath}": ${reason}`);
throw new Error(`Failed to read gsd-tools @file: indirection at "${resolvedPath}": ${reason}`);
}
}

View File

@@ -0,0 +1,35 @@
import { describe, expect, it } from 'vitest';
import { ErrorClassification, GSDError } from './errors.js';
import {
createQueryNativeErrorFactory,
createQueryToolsErrorFactory,
toToolsErrorFromUnknown,
} from './query-tools-error-factory.js';
describe('query tools error factory', () => {
it('builds timeout and failure tools errors via seam factories', () => {
const toolsFactory = createQueryToolsErrorFactory();
expect(toolsFactory.createTimeoutError('t', 'state', ['load'], '', 10).classification).toEqual({ kind: 'timeout', timeoutMs: 10 });
expect(toolsFactory.createFailureError('f', 'state', ['load'], 1, '').classification).toEqual({ kind: 'failure' });
});
it('maps GSDError to failure with semantic exit code', () => {
const err = toToolsErrorFromUnknown('state', ['load'], new GSDError('bad', ErrorClassification.Validation));
expect(err.exitCode).toBe(10);
expect(err.classification).toEqual({ kind: 'failure' });
});
it('maps timeout-like unknown errors to timeout classification', () => {
const err = toToolsErrorFromUnknown('state', ['load'], new Error('gsd-tools timed out after 50ms: state load'));
expect(err.classification).toEqual({ kind: 'timeout', timeoutMs: 50 });
});
it('builds subprocess/native error factories', () => {
const toolsFactory = createQueryToolsErrorFactory();
const nativeFactory = createQueryNativeErrorFactory(777);
expect(toolsFactory.createFailureError('x', 'state', ['load'], 1, '').classification).toEqual({ kind: 'failure' });
expect(toolsFactory.createTimeoutError('x', 'state', ['load'], '', 123).classification).toEqual({ kind: 'timeout', timeoutMs: 123 });
expect(nativeFactory.createNativeTimeoutError('x', 'state', ['load']).classification).toEqual({ kind: 'timeout', timeoutMs: 777 });
expect(nativeFactory.createNativeFailureError('x', 'state', ['load'], new Error('boom')).classification).toEqual({ kind: 'failure' });
});
});

View File

@@ -0,0 +1,76 @@
import { GSDError, exitCodeFor } from './errors.js';
import { GSDToolsError } from './gsd-tools-error.js';
import { errorMessage, toFailureSignal } from './query-failure-classification.js';
export interface QueryTimeoutErrorFactory {
createTimeoutError: (
message: string,
command: string,
args: string[],
stderr: string,
timeoutMs: number,
) => GSDToolsError;
}
export interface QueryFailureErrorFactory {
createFailureError: (
message: string,
command: string,
args: string[],
exitCode: number | null,
stderr: string,
) => GSDToolsError;
}
export type QueryToolsErrorFactory = QueryTimeoutErrorFactory & QueryFailureErrorFactory;
export interface QueryNativeErrorFactory {
createNativeTimeoutError: (message: string, command: string, args: string[]) => GSDToolsError;
createNativeFailureError: (message: string, command: string, args: string[], cause: unknown) => GSDToolsError;
}
function timeoutToolsError(message: string, command: string, args: string[], stderr = '', timeoutMs?: number): GSDToolsError {
return GSDToolsError.timeout(message, command, args, stderr, timeoutMs);
}
function failureToolsError(
message: string,
command: string,
args: string[],
exitCode: number | null,
stderr = '',
cause?: unknown,
): GSDToolsError {
return GSDToolsError.failure(message, command, args, exitCode, stderr, cause === undefined ? undefined : { cause });
}
export function toToolsErrorFromUnknown(command: string, args: string[], err: unknown): GSDToolsError {
if (err instanceof GSDError) {
return failureToolsError(err.message, command, args, exitCodeFor(err.classification), '', err);
}
const msg = errorMessage(err);
const signal = toFailureSignal(err);
if (signal.kind === 'timeout') {
return timeoutToolsError(msg, command, args, '', signal.timeoutMs);
}
return failureToolsError(msg, command, args, 1, '', err instanceof Error ? err : undefined);
}
export function createQueryToolsErrorFactory(): QueryToolsErrorFactory {
return {
createTimeoutError: (message, command, args, stderr, timeoutMs) =>
timeoutToolsError(message, command, args, stderr, timeoutMs),
createFailureError: (message, command, args, exitCode, stderr) =>
failureToolsError(message, command, args, exitCode, stderr),
};
}
export function createQueryNativeErrorFactory(defaultTimeoutMs: number): QueryNativeErrorFactory {
return {
createNativeTimeoutError: (message, command, args) =>
timeoutToolsError(message, command, args, '', defaultTimeoutMs),
createNativeFailureError: (message, command, args, cause) =>
failureToolsError(message, command, args, 1, '', cause),
};
}

View File

@@ -1,28 +0,0 @@
import { GSDError, exitCodeFor } from './errors.js';
import { GSDToolsError } from './gsd-tools-error.js';
/**
* Module owning projection of internal errors to GSDToolsError contract.
*/
export function toGSDToolsError(command: string, args: string[], err: unknown): GSDToolsError {
if (err instanceof GSDError) {
return new GSDToolsError(
err.message,
command,
args,
exitCodeFor(err.classification),
'',
{ cause: err },
);
}
const msg = err instanceof Error ? err.message : String(err);
return new GSDToolsError(
msg,
command,
args,
1,
'',
err instanceof Error ? { cause: err } : undefined,
);
}

View File

@@ -1,10 +1,18 @@
import { describe, it, expect } from 'vitest';
import { COMMAND_DEFINITIONS, COMMAND_DEFINITIONS_BY_FAMILY, FAMILY_MUTATION_COMMANDS } from './command-definition.js';
import {
COMMAND_DEFINITIONS,
COMMAND_DEFINITIONS_BY_FAMILY,
FAMILY_MUTATION_COMMANDS,
COMMAND_DEFINITION_BY_CANONICAL,
COMMAND_MUTATION_SET,
COMMAND_RAW_OUTPUT_SET,
} from './command-definition.js';
import { COMMAND_MANIFEST } from './command-manifest.js';
import { NON_FAMILY_COMMAND_MANIFEST } from './command-manifest.non-family.js';
describe('command-definition module', () => {
it('exposes canonical metadata with handler_key normalization contract', () => {
expect(COMMAND_DEFINITIONS).toHaveLength(COMMAND_MANIFEST.length);
expect(COMMAND_DEFINITIONS).toHaveLength(COMMAND_MANIFEST.length + NON_FAMILY_COMMAND_MANIFEST.length);
for (const [index, manifestEntry] of COMMAND_MANIFEST.entries()) {
const definition = COMMAND_DEFINITIONS[index];
expect(definition.handler_key).toBe(manifestEntry.handlerKey ?? manifestEntry.canonical);
@@ -15,11 +23,11 @@ describe('command-definition module', () => {
}
});
it('keeps family index canonicals in sync with flat list', () => {
it('keeps family index canonicals in sync with family definitions', () => {
const indexed = Object.values(COMMAND_DEFINITIONS_BY_FAMILY).flat();
expect(indexed).toHaveLength(COMMAND_DEFINITIONS.length);
expect(indexed).toHaveLength(COMMAND_MANIFEST.length);
expect(indexed.map((entry) => entry.canonical).sort()).toEqual(
[...COMMAND_DEFINITIONS.map((entry) => entry.canonical)].sort(),
[...COMMAND_MANIFEST.map((entry) => entry.canonical)].sort(),
);
});
@@ -28,4 +36,12 @@ describe('command-definition module', () => {
expect(FAMILY_MUTATION_COMMANDS).toContain('phase complete');
expect(FAMILY_MUTATION_COMMANDS).toContain('roadmap.update-plan-progress');
});
it('exposes indexed views for policy consumers', () => {
expect(COMMAND_DEFINITION_BY_CANONICAL['state.load']?.canonical).toBe('state.load');
expect(COMMAND_MUTATION_SET.has('state.update')).toBe(true);
expect(COMMAND_MUTATION_SET.has('state.json')).toBe(false);
expect(COMMAND_RAW_OUTPUT_SET.has('commit')).toBe(true);
expect(COMMAND_RAW_OUTPUT_SET.has('verify summary')).toBe(true);
});
});

View File

@@ -1,16 +1,17 @@
import { COMMAND_MANIFEST } from './command-manifest.js';
import { NON_FAMILY_COMMAND_MANIFEST } from './command-manifest.non-family.js';
import type { CommandFamily, OutputMode } from './command-manifest.types.js';
export interface CommandDefinition {
family: CommandFamily;
family?: CommandFamily;
canonical: string;
aliases: string[];
mutation: boolean;
output_mode: OutputMode;
handler_key: string;
handler_key?: string;
}
export const COMMAND_DEFINITIONS: readonly CommandDefinition[] = COMMAND_MANIFEST.map((entry) => ({
const FAMILY_COMMAND_DEFINITIONS: readonly CommandDefinition[] = COMMAND_MANIFEST.map((entry) => ({
family: entry.family,
canonical: entry.canonical,
aliases: [...entry.aliases],
@@ -19,6 +20,18 @@ export const COMMAND_DEFINITIONS: readonly CommandDefinition[] = COMMAND_MANIFES
handler_key: entry.handlerKey ?? entry.canonical,
})) as readonly CommandDefinition[];
const NON_FAMILY_COMMAND_DEFINITIONS: readonly CommandDefinition[] = NON_FAMILY_COMMAND_MANIFEST.map((entry) => ({
canonical: entry.canonical,
aliases: [...entry.aliases],
mutation: entry.mutation,
output_mode: entry.outputMode,
})) as readonly CommandDefinition[];
export const COMMAND_DEFINITIONS: readonly CommandDefinition[] = [
...FAMILY_COMMAND_DEFINITIONS,
...NON_FAMILY_COMMAND_DEFINITIONS,
] as const;
function byFamily(family: CommandFamily): readonly CommandDefinition[] {
return COMMAND_DEFINITIONS.filter((entry) => entry.family === family);
}
@@ -33,10 +46,25 @@ export const COMMAND_DEFINITIONS_BY_FAMILY: Readonly<Record<CommandFamily, reado
roadmap: byFamily('roadmap'),
} as const;
export const FAMILY_MUTATION_COMMANDS: readonly string[] = COMMAND_DEFINITIONS
export const COMMAND_DEFINITION_BY_CANONICAL: Readonly<Record<string, CommandDefinition>> = Object.fromEntries(
COMMAND_DEFINITIONS.map((entry) => [entry.canonical, entry]),
);
export const COMMAND_MUTATION_SET: ReadonlySet<string> = new Set(
COMMAND_DEFINITIONS.filter((entry) => entry.mutation).flatMap((entry) => [entry.canonical, ...entry.aliases]),
);
export const COMMAND_RAW_OUTPUT_SET: ReadonlySet<string> = new Set(
COMMAND_DEFINITIONS.filter((entry) => entry.output_mode === 'raw').flatMap((entry) => [entry.canonical, ...entry.aliases]),
);
export const FAMILY_MUTATION_COMMANDS: readonly string[] = FAMILY_COMMAND_DEFINITIONS
.filter((entry) => entry.mutation)
.flatMap((entry) => [entry.canonical, ...entry.aliases]);
export const FAMILY_RAW_OUTPUT_COMMANDS: readonly string[] = COMMAND_DEFINITIONS
export const FAMILY_RAW_OUTPUT_COMMANDS: readonly string[] = FAMILY_COMMAND_DEFINITIONS
.filter((entry) => entry.output_mode === 'raw')
.flatMap((entry) => [entry.canonical, ...entry.aliases]);
export const QUERY_MUTATION_COMMANDS_FROM_DEFINITIONS: readonly string[] = Array.from(COMMAND_MUTATION_SET);
export const TRANSPORT_RAW_COMMANDS_FROM_DEFINITIONS: readonly string[] = Array.from(COMMAND_RAW_OUTPUT_SET);

View File

@@ -0,0 +1,66 @@
import type { OutputMode } from './command-manifest.types.js';
export interface NonFamilyCommandManifestEntry {
canonical: string;
aliases: string[];
mutation: boolean;
outputMode: OutputMode;
}
export const NON_FAMILY_COMMAND_MANIFEST: readonly NonFamilyCommandManifestEntry[] = [
{ canonical: 'frontmatter.set', aliases: [], mutation: true, outputMode: 'json' },
{ canonical: 'frontmatter.merge', aliases: [], mutation: true, outputMode: 'json' },
{ canonical: 'frontmatter.validate', aliases: ['frontmatter validate'], mutation: true, outputMode: 'json' },
{ canonical: 'commit', aliases: [], mutation: true, outputMode: 'raw' },
{ canonical: 'config-set', aliases: [], mutation: true, outputMode: 'raw' },
{ canonical: 'config-set-model-profile', aliases: [], mutation: true, outputMode: 'json' },
{ canonical: 'config-new-project', aliases: [], mutation: true, outputMode: 'json' },
{ canonical: 'config-ensure-section', aliases: [], mutation: true, outputMode: 'json' },
{ canonical: 'check-commit', aliases: [], mutation: true, outputMode: 'json' },
{ canonical: 'commit-to-subrepo', aliases: [], mutation: true, outputMode: 'json' },
{ canonical: 'template.fill', aliases: [], mutation: true, outputMode: 'json' },
{ canonical: 'template.select', aliases: ['template select'], mutation: true, outputMode: 'json' },
{ canonical: 'requirements.mark-complete', aliases: ['requirements mark-complete'], mutation: true, outputMode: 'json' },
{ canonical: 'todo.complete', aliases: ['todo complete'], mutation: true, outputMode: 'json' },
{ canonical: 'milestone.complete', aliases: ['milestone complete'], mutation: true, outputMode: 'json' },
{
canonical: 'workstream.create',
aliases: ['workstream create'],
mutation: true,
outputMode: 'json',
},
{ canonical: 'workstream.set', aliases: ['workstream set'], mutation: true, outputMode: 'json' },
{
canonical: 'workstream.complete',
aliases: ['workstream complete'],
mutation: true,
outputMode: 'json',
},
{
canonical: 'workstream.progress',
aliases: ['workstream progress'],
mutation: true,
outputMode: 'json',
},
{ canonical: 'docs-init', aliases: [], mutation: true, outputMode: 'json' },
{ canonical: 'learnings.copy', aliases: ['learnings copy'], mutation: true, outputMode: 'json' },
{ canonical: 'learnings.prune', aliases: ['learnings prune'], mutation: true, outputMode: 'json' },
{ canonical: 'learnings.delete', aliases: ['learnings delete'], mutation: true, outputMode: 'json' },
{ canonical: 'intel.snapshot', aliases: ['intel snapshot'], mutation: true, outputMode: 'json' },
{ canonical: 'intel.patch-meta', aliases: ['intel patch-meta'], mutation: true, outputMode: 'json' },
{ canonical: 'write-profile', aliases: [], mutation: true, outputMode: 'json' },
{ canonical: 'generate-claude-profile', aliases: [], mutation: true, outputMode: 'json' },
{ canonical: 'generate-dev-preferences', aliases: [], mutation: true, outputMode: 'json' },
{ canonical: 'generate-claude-md', aliases: [], mutation: true, outputMode: 'json' },
{ canonical: 'verify-summary', aliases: ['verify.summary', 'verify summary'], mutation: false, outputMode: 'raw' },
] as const;

View File

@@ -1,8 +1,13 @@
import type { QueryRegistry } from './registry.js';
import type { QueryHandler } from './utils.js';
import { resolveQueryCommand } from './query-command-resolution-strategy.js';
import { diagnoseUnknownCommand } from './query-command-diagnosis.js';
import {
resolveQueryCommand,
explainQueryCommandNoMatch,
type QueryCommandRegistryLike,
} from './query-command-resolution-strategy.js';
import { supportsMutationCommand, supportsRawOutputCommand } from './query-policy-capability.js';
import { UNKNOWN_COMMAND_HINTS } from './query-unknown-command-hints.js';
import { describeFallbackDisabledPolicy } from './query-fallback-policy.js';
export type CommandTopologyOutputMode = 'json' | 'text' | 'raw';
@@ -29,6 +34,35 @@ export interface CommandTopology {
resolve(tokens: string[], fallbackRestricted?: boolean): CommandTopologyResult;
}
export interface UnknownCommandDiagnosis {
normalized: string;
attempted: string[];
hints: string[];
message: string;
}
export function diagnoseUnknownCommand(
command: string,
args: string[],
registry: QueryCommandRegistryLike,
fallbackRestricted: boolean,
): UnknownCommandDiagnosis {
const noMatch = explainQueryCommandNoMatch(command, args, registry);
const normalized = [noMatch.normalized.command, ...noMatch.normalized.args].join(' ');
const attempted = noMatch.attempted.dotted.slice(0, 2);
const hints = [...UNKNOWN_COMMAND_HINTS];
const attemptedSuffix = attempted.length > 0 ? ` Attempted dotted: ${attempted.join(' | ')}.` : '';
const fallbackClause = fallbackRestricted ? `${describeFallbackDisabledPolicy()} ` : '';
const message = `Error: Unknown command: "${normalized}". ${hints[0]} ${hints[1]} ${fallbackClause}${hints[2]}${attemptedSuffix}`;
return {
normalized,
attempted,
hints,
message,
};
}
export function createCommandTopology(registry: QueryRegistry): CommandTopology {
return {
resolve(tokens: string[], fallbackRestricted = false): CommandTopologyResult {

View File

@@ -83,6 +83,26 @@ function deriveStatusFromCheckbox(
return 'not_started';
}
function listPhasePlanAndSummaryCounts(phasePath: string): { plans: string[]; summaries: string[] } {
const phaseFiles = readdirSync(phasePath);
const rootPlans = phaseFiles.filter(f => f.endsWith('-PLAN.md') || f === 'PLAN.md');
const rootSummaries = phaseFiles.filter(f => f.endsWith('-SUMMARY.md') || f === 'SUMMARY.md');
const plansDir = join(phasePath, 'plans');
let nestedPlans: string[] = [];
let nestedSummaries: string[] = [];
if (existsSync(plansDir)) {
const files = readdirSync(plansDir);
nestedPlans = files.filter(f => /^PLAN-\d+.*\.md$/i.test(f));
nestedSummaries = files.filter(f => /^SUMMARY-\d+.*\.md$/i.test(f));
}
return {
plans: rootPlans.concat(nestedPlans),
summaries: rootSummaries.concat(nestedSummaries),
};
}
// ─── initNewProject ───────────────────────────────────────────────────────
/**
@@ -258,8 +278,7 @@ export const initProgress: QueryHandler = async (_args, projectDir, workstream)
const phasePath = join(paths.phases, dir);
const phaseFiles = readdirSync(phasePath);
const plans = phaseFiles.filter(f => f.endsWith('-PLAN.md') || f === 'PLAN.md');
const summaries = phaseFiles.filter(f => f.endsWith('-SUMMARY.md') || f === 'SUMMARY.md');
const { plans, summaries } = listPhasePlanAndSummaryCounts(phasePath);
const hasResearch = phaseFiles.some(f => f.endsWith('-RESEARCH.md') || f === 'RESEARCH.md');
let status =
@@ -431,8 +450,9 @@ export const initManager: QueryHandler = async (_args, projectDir, workstream) =
if (dirMatch) {
const fullDir = join(paths.phases, dirMatch);
const phaseFiles = readdirSync(fullDir);
planCount = phaseFiles.filter(f => f.endsWith('-PLAN.md') || f === 'PLAN.md').length;
summaryCount = phaseFiles.filter(f => f.endsWith('-SUMMARY.md') || f === 'SUMMARY.md').length;
const counts = listPhasePlanAndSummaryCounts(fullDir);
planCount = counts.plans.length;
summaryCount = counts.summaries.length;
hasContext = phaseFiles.some(f => f.endsWith('-CONTEXT.md') || f === 'CONTEXT.md');
hasResearch = phaseFiles.some(f => f.endsWith('-RESEARCH.md') || f === 'RESEARCH.md');

View File

@@ -330,6 +330,20 @@ describe('initExecutePhase', () => {
const data = result.data as Record<string, unknown>;
expect(data.error).toBeDefined();
});
it('honors legacy top-level branching_strategy in config for execute-phase init (#3055)', async () => {
await writeFile(join(tmpDir, '.planning', 'config.json'), JSON.stringify({
model_profile: 'balanced',
commit_docs: false,
branching_strategy: 'phase',
workflow: { research: true, plan_check: true, verifier: true, nyquist_validation: true },
}));
const result = await initExecutePhase(['9'], tmpDir);
const data = result.data as Record<string, unknown>;
expect(data.branching_strategy).toBe('phase');
expect(typeof data.branch_name).toBe('string');
});
});
describe('initPlanPhase', () => {

View File

@@ -1841,6 +1841,29 @@ export const milestoneComplete: QueryHandler = async (args, projectDir, workstre
null,
`${version} milestone completed and archived`,
);
const positionPattern = /(##\s*Current Position\s*\n)([\s\S]*?)(?=\n##|$)/i;
const closedPositionBody =
`\nPhase: Milestone ${version} complete\n` +
`Plan: —\n` +
`Status: Awaiting next milestone\n` +
`Last activity: ${today} — Milestone ${version} completed and archived\n\n`;
if (positionPattern.test(next)) {
next = next.replace(positionPattern, (_m, header) => `${header}${closedPositionBody}`);
} else {
next = `${next.trimEnd()}\n\n## Current Position\n${closedPositionBody}`;
}
const operatorPattern = /(##\s*Operator Next Steps\s*\n)([\s\S]*?)(?=\n##|$)/i;
if (operatorPattern.test(next)) {
next = next.replace(
operatorPattern,
`$1\n- Start the next milestone with /gsd-new-milestone\n\n`,
);
} else {
next = `${next.trimEnd()}\n\n## Operator Next Steps\n\n- Start the next milestone with /gsd-new-milestone\n`;
}
return next;
}, workstream);
}

View File

@@ -80,6 +80,17 @@ async function getPhaseFileStats(phaseDir: string): Promise<{
*
* Port of searchPhaseInDir from core.cjs lines 956-1000.
*/
function extractCanonicalPlanId(filename: string): string {
const base = filename.replace(/-PLAN\.md$/i, '').replace(/-SUMMARY\.md$/i, '').replace(/\.md$/i, '');
const parts = base.split('-').filter(Boolean);
const tokenRe = /^\d+[A-Z]?(?:\.\d+)*$/i;
const phaseIdx = parts.findIndex((p) => tokenRe.test(p));
if (phaseIdx >= 0 && phaseIdx + 1 < parts.length && tokenRe.test(parts[phaseIdx + 1])) {
return `${parts[phaseIdx]}-${parts[phaseIdx + 1]}`;
}
return base;
}
async function searchPhaseInDir(baseDir: string, relBase: string, normalized: string): Promise<PhaseInfo | null> {
try {
const entries = await readdir(baseDir, { withFileTypes: true });
@@ -105,11 +116,16 @@ async function searchPhaseInDir(baseDir: string, relBase: string, normalized: st
const summaries = unsortedSummaries.sort();
const completedPlanIds = new Set(
summaries.map(s => s.replace('-SUMMARY.md', '').replace('SUMMARY.md', ''))
summaries.flatMap((s) => {
const exact = s.replace('-SUMMARY.md', '').replace('SUMMARY.md', '');
const canonical = extractCanonicalPlanId(s);
return canonical === exact ? [exact] : [exact, canonical];
})
);
const incompletePlans = plans.filter(p => {
const incompletePlans = plans.filter((p) => {
const planId = p.replace('-PLAN.md', '').replace('PLAN.md', '');
return !completedPlanIds.has(planId);
const canonical = extractCanonicalPlanId(p);
return !completedPlanIds.has(planId) && !completedPlanIds.has(canonical);
});
return {
@@ -265,7 +281,11 @@ export const phasePlanIndex: QueryHandler = async (args, projectDir, workstream)
// Build set of plan IDs with summaries — match the planId derivation logic
const completedPlanIds = new Set(
summaryFiles.map(s => s === 'SUMMARY.md' ? 'PLAN' : s.replace('-SUMMARY.md', ''))
summaryFiles.flatMap((s) => {
const exact = s === 'SUMMARY.md' ? 'PLAN' : s.replace('-SUMMARY.md', '');
const canonical = extractCanonicalPlanId(s);
return canonical === exact ? [exact] : [exact, canonical];
})
);
const plans: Array<Record<string, unknown>> = [];
@@ -306,7 +326,7 @@ export const phasePlanIndex: QueryHandler = async (args, projectDir, workstream)
filesModified = Array.isArray(fmFiles) ? fmFiles : [fmFiles];
}
const hasSummary = completedPlanIds.has(planId);
const hasSummary = completedPlanIds.has(planId) || completedPlanIds.has(extractCanonicalPlanId(planFile));
if (!hasSummary) {
incomplete.push(planId);
}

View File

@@ -4,28 +4,28 @@ import { buildQueryCliOutputFromError } from './query-cli-output.js';
describe('query-cli-output', () => {
it('prefers raw gsd-tools stderr when present', () => {
const err = new GSDToolsError('failed', 'list', ['json'], 2, 'line one\nline two\n');
const err = GSDToolsError.failure('failed', 'list', ['json'], 2, 'line one\nline two\n');
const out = buildQueryCliOutputFromError(err);
expect(out.exitCode).toBe(2);
expect(out.stderrLines).toEqual(['line one', 'line two']);
});
it('falls back to Error: message when gsd-tools stderr is empty', () => {
const err = new GSDToolsError('failed', 'list', ['json'], null, '');
const err = GSDToolsError.failure('failed', 'list', ['json'], null, '');
const out = buildQueryCliOutputFromError(err);
expect(out.exitCode).toBe(1);
expect(out.stderrLines).toEqual(['Error: failed']);
});
it('falls back to Error: message when gsd-tools stderr is whitespace-only', () => {
const err = new GSDToolsError('failed', 'build', ['json'], null, ' \n');
const err = GSDToolsError.failure('failed', 'build', ['json'], null, ' \n');
const out = buildQueryCliOutputFromError(err);
expect(out.exitCode).toBe(1);
expect(out.stderrLines).toEqual(['Error: failed']);
});
it('uses exitCode 1 when gsd-tools exitCode is null and stderr is non-empty', () => {
const err = new GSDToolsError('failed', 'build', ['json'], null, 'line');
const err = GSDToolsError.failure('failed', 'build', ['json'], null, 'line');
const out = buildQueryCliOutputFromError(err);
expect(out.exitCode).toBe(1);
expect(out.stderrLines).toEqual(['line']);

View File

@@ -1,32 +1,5 @@
import { explainQueryCommandNoMatch, type QueryCommandRegistryLike } from './query-command-semantics.js';
import { UNKNOWN_COMMAND_HINTS } from './query-unknown-command-hints.js';
import { describeFallbackDisabledPolicy } from './query-fallback-policy.js';
export interface UnknownCommandDiagnosis {
normalized: string;
attempted: string[];
hints: string[];
message: string;
}
export function diagnoseUnknownCommand(
command: string,
args: string[],
registry: QueryCommandRegistryLike,
fallbackRestricted: boolean,
): UnknownCommandDiagnosis {
const noMatch = explainQueryCommandNoMatch(command, args, registry);
const normalized = [noMatch.normalized.command, ...noMatch.normalized.args].join(' ');
const attempted = noMatch.attempted.dotted.slice(0, 2);
const hints = [...UNKNOWN_COMMAND_HINTS];
const attemptedSuffix = attempted.length > 0 ? ` Attempted dotted: ${attempted.join(' | ')}.` : '';
const fallbackClause = fallbackRestricted ? `${describeFallbackDisabledPolicy()} ` : '';
const message = `Error: Unknown command: "${normalized}". ${hints[0]} ${hints[1]} ${fallbackClause}${hints[2]}${attemptedSuffix}`;
return {
normalized,
attempted,
hints,
message,
};
}
/**
* @deprecated Compatibility seam after Command Topology Module deepening.
* Remove-after: all imports migrate to `command-topology.ts`.
*/
export { diagnoseUnknownCommand, type UnknownCommandDiagnosis } from './command-topology.js';

Some files were not shown because too many files have changed in this diff Show More