gsd-lite 0.5.10 → 0.5.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -13,7 +13,7 @@
13
13
  "name": "gsd",
14
14
  "source": "./",
15
15
  "description": "AI orchestration tool — GSD management shell + Superpowers quality core. 5 commands, 4 agents, 5 workflows, MCP server, context monitoring.",
16
- "version": "0.5.10",
16
+ "version": "0.5.13",
17
17
  "keywords": [
18
18
  "orchestration",
19
19
  "mcp",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gsd",
3
- "version": "0.5.10",
3
+ "version": "0.5.13",
4
4
  "description": "AI orchestration tool for Claude Code — GSD management shell + Superpowers quality core",
5
5
  "author": {
6
6
  "name": "sdsrss",
package/README.md CHANGED
@@ -10,9 +10,9 @@ GSD-Lite is an AI orchestration tool for [Claude Code](https://docs.anthropic.co
10
10
 
11
11
  ### Structured Execution Engine
12
12
  - **Phase-based project management** — Break work into phases with ordered tasks, dependency tracking, and handoff gates
13
- - **State machine orchestration** — 11 workflow modes with precise state transitions, persistent to `state.json`
13
+ - **State machine orchestration** — 12 workflow modes with precise state transitions, persistent to `state.json`
14
14
  - **Automatic task scheduling** — Gate-aware dependency resolution determines what runs next
15
- - **Session resilience** — Stop anytime, resume exactly where you left off — even across Claude Code restarts
15
+ - **Session resilience** — Stop anytime, resume exactly where you left off — crash protection via Stop hook auto-saves state markers
16
16
 
17
17
  ### Quality Discipline (Built-in, Not Optional)
18
18
  - **TDD enforcement** — "No production code without a failing test first" baked into every executor dispatch
@@ -26,9 +26,16 @@ GSD-Lite is an AI orchestration tool for [Claude Code](https://docs.anthropic.co
26
26
  - **Blocked task handling** — Blocked tasks are parked; execution continues with remaining tasks
27
27
  - **Rework propagation** — Critical review issues cascade invalidation to dependent tasks
28
28
 
29
+ ### Adaptive Review & Parallel Execution
30
+ - **Confidence-based review adjustment** — Executor self-assesses confidence (high/medium/low); orchestrator auto-adjusts review level accordingly
31
+ - **Impact analysis before review** — Reviewer runs impact analysis on multi-file changes to catch missed downstream effects
32
+ - **Parallel task scheduling** — Independent tasks within the same phase are identified for concurrent dispatch
33
+ - **Auto PR suggestion** — Phase/project completion prompts PR creation with evidence summary
34
+
29
35
  ### Context Protection
30
36
  - **Subagent isolation** — Each task runs in its own agent context, preventing cross-contamination
31
37
  - **StatusLine monitoring** — Real-time context health tracking via Claude Code StatusLine
38
+ - **Session lifecycle hooks** — Stop hook writes crash marker; SessionStart injects project status into CLAUDE.md; resume detects non-graceful exits
32
39
  - **Evidence-based verification** — Every claim backed by command output, not assertions
33
40
  - **Research with TTL** — Research artifacts include volatility ratings and expiration dates
34
41
 
@@ -41,7 +48,7 @@ User → discuss + research (confirm requirements) → approve plan → auto-exe
41
48
  (code→review→verify→advance)
42
49
  ```
43
50
 
44
- ### 5 Commands
51
+ ### 6 Commands
45
52
 
46
53
  | Command | Purpose |
47
54
  |---------|---------|
@@ -50,6 +57,7 @@ User → discuss + research (confirm requirements) → approve plan → auto-exe
50
57
  | `/gsd:resume` | Resume execution from saved state |
51
58
  | `/gsd:status` | View project progress dashboard |
52
59
  | `/gsd:stop` | Save state and pause execution |
60
+ | `/gsd:doctor` | Diagnostic checks on GSD-Lite installation and project health |
53
61
 
54
62
  ### 4 Agents
55
63
 
@@ -60,7 +68,7 @@ User → discuss + research (confirm requirements) → approve plan → auto-exe
60
68
  | **researcher** | Ecosystem research (Context7 → official docs → web) | Confidence scoring + TTL |
61
69
  | **debugger** | 4-phase systematic root cause analysis | Root Cause Iron Law |
62
70
 
63
- ### MCP Server (10 Tools)
71
+ ### MCP Server (11 Tools)
64
72
 
65
73
  | Tool | Purpose |
66
74
  |------|---------|
@@ -68,6 +76,7 @@ User → discuss + research (confirm requirements) → approve plan → auto-exe
68
76
  | `state-init` | Initialize `.gsd/` directory with project structure |
69
77
  | `state-read` | Read state with optional field filtering |
70
78
  | `state-update` | Update canonical fields with lifecycle validation |
79
+ | `state-patch` | Incrementally modify plan (add/remove/reorder tasks, update fields, add dependencies) |
71
80
  | `phase-complete` | Complete a phase after verifying handoff gates |
72
81
  | `orchestrator-resume` | Resume orchestration from current state |
73
82
  | `orchestrator-handle-executor-result` | Process executor output, advance lifecycle |
@@ -202,33 +211,45 @@ All state lives in `.gsd/state.json` — a single source of truth with:
202
211
 
203
212
  | Dimension | GSD | GSD-Lite |
204
213
  |-----------|-----|----------|
205
- | Commands | 32 | **5** |
214
+ | Commands | 32 | **6** |
206
215
  | Agents | 12 | **4** |
207
216
  | Source files | 100+ | **~35** |
208
217
  | Installer | 2465 lines | **~80 lines** |
209
218
  | User interactions | 6+ confirmations | **Typically 2** |
210
219
  | TDD / Anti-rationalization | No | **Yes** |
211
- | State machine recovery | Partial | **Full (11 modes)** |
220
+ | State machine recovery | Partial | **Full (12 modes)** |
212
221
  | Evidence-based verification | No | **Yes** |
213
222
 
214
223
  ## Project Structure
215
224
 
216
225
  ```
217
226
  gsd-lite/
218
- ├── src/ # MCP Server + tools (~3300 lines)
219
- │ ├── server.js # MCP Server entry (10 tools)
227
+ ├── src/ # MCP Server + tools
228
+ │ ├── server.js # MCP Server entry (11 tools)
220
229
  │ ├── schema.js # State schema + lifecycle validation
221
- │ ├── utils.js # Shared utilities (atomic writes, git)
230
+ │ ├── utils.js # Shared utilities (atomic writes, git, file lock)
222
231
  │ └── tools/
223
- │ ├── state.js # State CRUD + evidence + propagation
224
- │ ├── orchestrator.js # Orchestration logic + agent handlers
232
+ │ ├── state/ # State management (modular)
233
+ ├── constants.js # Error codes, lock infrastructure
234
+ │ │ ├── crud.js # CRUD operations + plan patching
235
+ │ │ ├── logic.js # Task scheduling, propagation, research
236
+ │ │ └── index.js # Re-exports
237
+ │ ├── orchestrator/ # Orchestration logic (modular)
238
+ │ │ ├── helpers.js # Shared constants, preflight, dispatch
239
+ │ │ ├── resume.js # Workflow resume state machine
240
+ │ │ ├── executor.js # Executor result handler
241
+ │ │ ├── reviewer.js # Reviewer result handler
242
+ │ │ ├── debugger.js # Debugger result handler
243
+ │ │ ├── researcher.js # Researcher result handler
244
+ │ │ └── index.js # Re-exports
225
245
  │ └── verify.js # lint/typecheck/test verification
226
- ├── commands/ # 5 slash commands
227
- ├── agents/ # 4 subagent prompts
228
- ├── workflows/ # 5 core workflows (TDD, review, debug, research, deviation)
229
- ├── references/ # 8 reference docs (execution loop, state diagram, evidence spec, ...)
230
- ├── hooks/ # Context monitoring (StatusLine + PostToolUse)
231
- ├── tests/ # 674 tests (unit + simulation + E2E)
246
+ ├── commands/ # 6 slash commands (start, prd, resume, status, stop, doctor)
247
+ ├── agents/ # 4 subagent prompts (executor, reviewer, researcher, debugger)
248
+ ├── workflows/ # 6 core workflows (TDD, review, debug, research, deviation, execution-flow)
249
+ ├── references/ # 8 reference docs
250
+ ├── hooks/ # Session lifecycle (StatusLine + PostToolUse + SessionStart + Stop + AutoUpdate)
251
+ │ └── lib/ # Shared hook utilities (gsd-finder)
252
+ ├── tests/ # 822 tests (unit + simulation + E2E)
232
253
  ├── cli.js # Install/uninstall CLI entry
233
254
  ├── install.js # Installation script
234
255
  └── uninstall.js # Uninstall script
@@ -237,7 +258,7 @@ gsd-lite/
237
258
  ## Testing
238
259
 
239
260
  ```bash
240
- npm test # Run all 674 tests
261
+ npm test # Run all 822 tests
241
262
  npm run test:coverage # Tests + coverage report (94%+ lines, 81%+ branches)
242
263
  npm run lint # Biome lint
243
264
  node --test tests/file.js # Run a single test file
@@ -55,6 +55,7 @@ tools: Read, Write, Edit, Bash, Grep, Glob
55
55
  "decisions": ["[DECISION] use optimistic locking by version column"],
56
56
  "blockers": [],
57
57
  "contract_changed": true,
58
+ "confidence": "high",
58
59
  "evidence": [
59
60
  {"id": "ev:test:users-update", "scope": "task:2.3"},
60
61
  {"id": "ev:typecheck:phase-2", "scope": "task:2.3"}
@@ -67,6 +68,13 @@ tools: Read, Write, Edit, Bash, Grep, Glob
67
68
  - 改了共享类型定义 / 接口 → true
68
69
  - 只改了内部实现逻辑、不影响外部调用方 → false
69
70
  - 拿不准时 → true (安全优先)
71
+
72
+ `confidence` 判定指南 (用于审查级别自动调整):
73
+ - "high" — 测试全通过 + 改动明确 + 无意外复杂度
74
+ - "medium" — 测试通过但有不确定性 (边界条件、并发、外部依赖)
75
+ - "low" — 有已知风险/跳过的测试/不确定的副作用
76
+ - 拿不准时 → "medium"
77
+ - 编排器会根据 confidence 自动升/降审查级别
70
78
  </result_contract>
71
79
 
72
80
  <uncertainty_handling>
@@ -27,6 +27,9 @@ tools: Read, Write, Bash, WebSearch, WebFetch, mcp__plugin_context7_context7__*
27
27
  关键推荐生成 decision id,供 plan/task 的 `research_basis` 引用
28
28
 
29
29
  <result_contract>
30
+ 编排器调用 `orchestrator-handle-researcher-result` 需要三个参数:
31
+
32
+ **1. result** — 研究元数据:
30
33
  ```json
31
34
  {
32
35
  "decision_ids": ["decision:jwt-rotation"],
@@ -37,6 +40,27 @@ tools: Read, Write, Bash, WebSearch, WebFetch, mcp__plugin_context7_context7__*
37
40
  ]
38
41
  }
39
42
  ```
43
+
44
+ **2. decision_index** — 以 decision id 为 key 的索引对象 (每个 decision_ids 中的 id 必须在此出现):
45
+ ```json
46
+ {
47
+ "decision:jwt-rotation": {
48
+ "summary": "Use refresh token rotation for JWT auth",
49
+ "source": "Context7",
50
+ "expires_at": "2026-03-16T10:30:00Z"
51
+ }
52
+ }
53
+ ```
54
+
55
+ **3. artifacts** — 四个研究文档的 Markdown 内容 (上方 research_output 中的四个文件):
56
+ ```json
57
+ {
58
+ "STACK.md": "# 技术栈推荐\n...",
59
+ "ARCHITECTURE.md": "# 架构模式\n...",
60
+ "PITFALLS.md": "# 领域陷阱\n...",
61
+ "SUMMARY.md": "# 摘要\n..."
62
+ }
63
+ ```
40
64
  </result_contract>
41
65
  </research_output>
42
66
 
@@ -58,12 +58,26 @@ L2 关键任务 → 单任务独立 review
58
58
  - 拿不准时 → 升一级处理
59
59
  </review_strategy>
60
60
 
61
+ <impact_analysis>
62
+ ## 审查前影响分析 (多文件变更时)
63
+
64
+ 当 `files_changed` 包含 3+ 文件,或涉及跨模块修改时:
65
+ 1. 使用 `code-graph-mcp impact <主要变更的函数/类名>` 分析影响范围
66
+ 2. 检查调用方是否都已被修改或兼容
67
+ 3. 将未覆盖的影响范围标注为 Critical issue
68
+
69
+ 这能发现 executor 遗漏的下游影响,是审查增值的关键步骤。
70
+ 单文件内部修改可跳过此步骤。
71
+ 如 `code-graph-mcp` 不可用,改用 Grep/Glob 手动追踪变更函数的调用方。
72
+ </impact_analysis>
73
+
61
74
  <stage_1_spec_review>
62
75
  检查代码是否符合任务规格:
63
76
  - 所有需求都实现了吗?
64
77
  - 有没有多余的实现 (YAGNI)?
65
78
  - 接口/API 是否符合计划?
66
79
  - 测试是否覆盖了需求中的每个场景?
80
+ - 影响分析发现的调用方是否都已适配?
67
81
  结果: ✅ 通过 / ❌ 列出不符合项 (附具体代码位置)
68
82
  </stage_1_spec_review>
69
83
 
@@ -28,6 +28,14 @@ description: Resume project execution from saved state with workspace validation
28
28
  <HARD-GATE id="resume-preflight">
29
29
  必须在恢复执行前完成所有校验,按以下优先级顺序:
30
30
 
31
+ 0. **Session End 检查:**
32
+ - 检查 `.gsd/.session-end` 文件是否存在
33
+ - 如果存在:
34
+ - 读取内容,向用户展示: "⚠️ 上次 session 在 {ended_at} 非正常结束,当时处于 {workflow_mode_was} (Phase {current_phase} / Task {current_task})"
35
+ - 删除 `.session-end` 文件
36
+ - 继续后续校验 (不覆写 workflow_mode — 由下面的校验决定)
37
+ - 如果不存在 → 跳过,继续后续校验
38
+
31
39
  1. **Git HEAD 校验:**
32
40
  - 运行 `git rev-parse HEAD` 获取当前 HEAD
33
41
  - 如果与 state.json 中的 `git_head` 不同:
@@ -2,8 +2,10 @@
2
2
  // GSD-Lite SessionStart hook
3
3
  // 1. Cleans up stale temp files (throttled to once/day).
4
4
  // 2. Auto-registers statusLine in settings.json if not already configured.
5
- // 3. Shows notification if a previous background update completed or found a new version.
6
- // 4. Spawns background auto-update (detached, non-blocking).
5
+ // 3. Self-heals .mcp.json if missing.
6
+ // 4. Shows notification if a previous background update completed or found a new version.
7
+ // 5. Spawns background auto-update (detached, non-blocking).
8
+ // 6. Injects GSD project progress into stdout + CLAUDE.md (if active project found).
7
9
  // Idempotent: skips if statusLine already points to gsd-statusline, preserves
8
10
  // third-party statuslines.
9
11
 
@@ -124,4 +126,104 @@ setTimeout(() => process.exit(0), 4000).unref();
124
126
  );
125
127
  child.unref();
126
128
  } catch { /* silent — never block session start */ }
129
+
130
+ // ── Phase 6: GSD Project Progress Injection ──
131
+ // If an active GSD project exists, inject progress into stdout (additionalContext)
132
+ // and write a status block into CLAUDE.md for persistent visibility.
133
+ try {
134
+ const { findGsdDir, readState, getProgress } = require('./lib/gsd-finder.cjs');
135
+ const cwd = process.cwd();
136
+ const gsdDir = findGsdDir(cwd);
137
+ if (gsdDir) {
138
+ const state = readState(gsdDir);
139
+ const progress = getProgress(state);
140
+ if (progress) {
141
+ // Check for .session-end marker (previous non-graceful exit)
142
+ const markerPath = path.join(gsdDir, '.session-end');
143
+ let sessionEndInfo = null;
144
+ try {
145
+ if (fs.existsSync(markerPath)) {
146
+ sessionEndInfo = JSON.parse(fs.readFileSync(markerPath, 'utf8'));
147
+ }
148
+ } catch { /* skip */ }
149
+
150
+ // Stdout: only output session-end warning (crash recovery), skip routine progress
151
+ // Routine progress is handled by CLAUDE.md injection below — avoids noise
152
+ const shortHead = progress.gitHead ? progress.gitHead.substring(0, 7) : 'n/a';
153
+ if (sessionEndInfo) {
154
+ console.log(`⚠️ GSD: Previous session ended unexpectedly at ${sessionEndInfo.ended_at} (was: ${sessionEndInfo.workflow_mode_was}). Run /gsd:resume to recover.`);
155
+ }
156
+
157
+ // Write status block to CLAUDE.md
158
+ const projectRoot = path.dirname(gsdDir);
159
+ const claudeMdPath = path.join(projectRoot, 'CLAUDE.md');
160
+ const BEGIN_MARKER = '<!-- GSD-STATUS-BEGIN -->';
161
+ const END_MARKER = '<!-- GSD-STATUS-END -->';
162
+
163
+ const statusBlock = [
164
+ BEGIN_MARKER,
165
+ `### GSD Project: ${progress.project}`,
166
+ `- Phase: ${progress.currentPhase || '?'}/${progress.totalPhases} (${progress.phaseName})`,
167
+ `- Task: ${progress.currentTask || 'none'}${progress.taskName ? ` (${progress.taskName})` : ''}`,
168
+ `- Mode: ${progress.workflowMode}`,
169
+ `- Progress: ${progress.acceptedTasks}/${progress.totalTasks} tasks done`,
170
+ `- Last checkpoint: ${shortHead}`,
171
+ sessionEndInfo ? `- ⚠️ Previous session ended unexpectedly (${sessionEndInfo.ended_at})` : null,
172
+ END_MARKER,
173
+ ].filter(Boolean).join('\n');
174
+
175
+ try {
176
+ let content = '';
177
+ try {
178
+ content = fs.readFileSync(claudeMdPath, 'utf8');
179
+ } catch { /* file doesn't exist yet — will create */ }
180
+
181
+ const beginIdx = content.indexOf(BEGIN_MARKER);
182
+ const endIdx = content.indexOf(END_MARKER);
183
+
184
+ let newContent;
185
+ if (beginIdx !== -1 && endIdx !== -1) {
186
+ // Replace existing block
187
+ newContent = content.substring(0, beginIdx) + statusBlock + content.substring(endIdx + END_MARKER.length);
188
+ } else {
189
+ // Append to end (with blank line separator)
190
+ const separator = content.length > 0 && !content.endsWith('\n\n') ? (content.endsWith('\n') ? '\n' : '\n\n') : '';
191
+ newContent = content + separator + statusBlock + '\n';
192
+ }
193
+
194
+ // Only write if content changed
195
+ if (newContent !== content) {
196
+ const tmpClaude = claudeMdPath + `.gsd-tmp-${process.pid}`;
197
+ fs.writeFileSync(tmpClaude, newContent);
198
+ fs.renameSync(tmpClaude, claudeMdPath);
199
+ }
200
+ } catch (e) {
201
+ if (process.env.GSD_DEBUG) process.stderr.write(`gsd-session-init: CLAUDE.md write failed: ${e.message}\n`);
202
+ }
203
+ }
204
+ } else {
205
+ // No active GSD project — clean up stale CLAUDE.md block if it exists
206
+ try {
207
+ const claudeMdPath = path.join(cwd, 'CLAUDE.md');
208
+ const BEGIN_MARKER = '<!-- GSD-STATUS-BEGIN -->';
209
+ const END_MARKER = '<!-- GSD-STATUS-END -->';
210
+ const content = fs.readFileSync(claudeMdPath, 'utf8');
211
+ const beginIdx = content.indexOf(BEGIN_MARKER);
212
+ const endIdx = content.indexOf(END_MARKER);
213
+ if (beginIdx !== -1 && endIdx !== -1) {
214
+ // Remove the block and any trailing newline
215
+ let newContent = content.substring(0, beginIdx) + content.substring(endIdx + END_MARKER.length);
216
+ // Clean up extra blank lines left behind
217
+ newContent = newContent.replace(/\n{3,}/g, '\n\n').trimEnd() + '\n';
218
+ if (newContent !== content) {
219
+ const tmpClaude = claudeMdPath + `.gsd-tmp-${process.pid}`;
220
+ fs.writeFileSync(tmpClaude, newContent);
221
+ fs.renameSync(tmpClaude, claudeMdPath);
222
+ }
223
+ }
224
+ } catch { /* no CLAUDE.md or no block to clean — skip */ }
225
+ }
226
+ } catch (e) {
227
+ if (process.env.GSD_DEBUG) process.stderr.write(`gsd-session-init phase 6: ${e.message}\n`);
228
+ }
127
229
  })().catch(() => {});
@@ -0,0 +1,69 @@
1
+ #!/usr/bin/env node
2
+ // GSD-Lite Stop hook — Crash Protection
3
+ //
4
+ // Runs when Claude Code session ends (exit, /clear, crash).
5
+ // If an active GSD project is found, writes a .session-end marker file
6
+ // so that /gsd:resume can detect the non-graceful exit and inform the user.
7
+ //
8
+ // Design decisions:
9
+ // - Does NOT modify state.json directly (avoids bypassing schema validation)
10
+ // - Uses a marker file (.gsd/.session-end) that resume preflight checks
11
+ // - Only acts on active sessions (not completed/failed/paused)
12
+ // - Timeout guard: exits after 4s (hook timeout is 5s)
13
+
14
+ 'use strict';
15
+
16
+ const fs = require('node:fs');
17
+ const path = require('node:path');
18
+ const { findGsdDir, readState } = require('./lib/gsd-finder.cjs');
19
+
20
+ // Safety: exit after 4s regardless
21
+ setTimeout(() => process.exit(0), 4000).unref();
22
+
23
+ const TERMINAL_MODES = ['completed', 'failed', 'paused_by_user'];
24
+
25
+ (async () => {
26
+ const cwd = process.cwd();
27
+ const gsdDir = findGsdDir(cwd);
28
+ if (!gsdDir) process.exit(0);
29
+
30
+ const state = readState(gsdDir);
31
+ if (!state) process.exit(0);
32
+
33
+ // Only write marker for active (non-terminal, non-paused) sessions
34
+ if (TERMINAL_MODES.includes(state.workflow_mode)) process.exit(0);
35
+
36
+ // Get current git HEAD
37
+ let gitHead = state.git_head || '';
38
+ try {
39
+ const { execSync } = require('node:child_process');
40
+ gitHead = execSync('git rev-parse HEAD', {
41
+ cwd: path.dirname(gsdDir),
42
+ timeout: 2000,
43
+ encoding: 'utf8',
44
+ }).trim();
45
+ } catch { /* keep existing git_head */ }
46
+
47
+ // Write .session-end marker
48
+ const marker = {
49
+ ended_at: new Date().toISOString(),
50
+ workflow_mode_was: state.workflow_mode,
51
+ current_phase: state.current_phase,
52
+ current_task: state.current_task,
53
+ git_head: gitHead,
54
+ reason: 'session_stop',
55
+ };
56
+
57
+ const markerPath = path.join(gsdDir, '.session-end');
58
+ const tmpPath = markerPath + `.${process.pid}.tmp`;
59
+ try {
60
+ fs.writeFileSync(tmpPath, JSON.stringify(marker, null, 2) + '\n');
61
+ fs.renameSync(tmpPath, markerPath);
62
+ } catch (e) {
63
+ // Clean up tmp if rename failed
64
+ try { fs.unlinkSync(tmpPath); } catch { /* ignore */ }
65
+ if (process.env.GSD_DEBUG) {
66
+ process.stderr.write(`gsd-session-stop: ${e.message}\n`);
67
+ }
68
+ }
69
+ })().catch(() => {});
package/hooks/hooks.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "description": "GSD-Lite hooks: statusline auto-registration + context health monitor",
2
+ "description": "GSD-Lite hooks: statusline + context monitor + session lifecycle",
3
3
  "hooks": {
4
4
  "SessionStart": [
5
5
  {
@@ -24,6 +24,18 @@
24
24
  }
25
25
  ]
26
26
  }
27
+ ],
28
+ "Stop": [
29
+ {
30
+ "matcher": "*",
31
+ "hooks": [
32
+ {
33
+ "type": "command",
34
+ "command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/gsd-session-stop.cjs\"",
35
+ "timeout": 5
36
+ }
37
+ ]
38
+ }
27
39
  ]
28
40
  }
29
41
  }
@@ -0,0 +1,84 @@
1
+ #!/usr/bin/env node
2
+ // Shared utilities for GSD hooks.
3
+ // findGsdDir: walk up from startDir looking for .gsd/state.json
4
+ // readState: parse .gsd/state.json, return null on failure
5
+ // getProgress: compute progress summary from state
6
+
7
+ 'use strict';
8
+
9
+ const fs = require('node:fs');
10
+ const path = require('node:path');
11
+
12
+ /**
13
+ * Walk from startDir up to filesystem root looking for a .gsd directory
14
+ * that contains state.json. Returns the absolute path to .gsd or null.
15
+ */
16
+ function findGsdDir(startDir) {
17
+ let dir = startDir;
18
+ while (true) {
19
+ const candidate = path.join(dir, '.gsd');
20
+ try {
21
+ if (fs.statSync(candidate).isDirectory()) {
22
+ // Only return if state.json exists (not just an empty .gsd dir)
23
+ if (fs.existsSync(path.join(candidate, 'state.json'))) return candidate;
24
+ }
25
+ } catch { /* skip */ }
26
+ const parent = path.dirname(dir);
27
+ if (parent === dir) return null;
28
+ dir = parent;
29
+ }
30
+ }
31
+
32
+ /**
33
+ * Read and parse .gsd/state.json. Returns parsed object or null on any failure.
34
+ */
35
+ function readState(gsdDir) {
36
+ try {
37
+ return JSON.parse(fs.readFileSync(path.join(gsdDir, 'state.json'), 'utf8'));
38
+ } catch {
39
+ return null;
40
+ }
41
+ }
42
+
43
+ /**
44
+ * Compute progress summary from state object.
45
+ * Returns { project, workflowMode, currentPhase, totalPhases, currentTask,
46
+ * phaseName, taskName, acceptedTasks, totalTasks, gitHead }
47
+ */
48
+ function getProgress(state) {
49
+ if (!state) return null;
50
+
51
+ const phases = state.phases || [];
52
+ let acceptedTasks = 0;
53
+ let totalTasks = 0;
54
+ let phaseName = '';
55
+ let taskName = '';
56
+
57
+ for (const phase of phases) {
58
+ const todos = phase.todo || [];
59
+ totalTasks += todos.length;
60
+ acceptedTasks += todos.filter(t => t.lifecycle === 'accepted').length;
61
+ if (phase.id === state.current_phase) {
62
+ phaseName = phase.name || `Phase ${phase.id}`;
63
+ const task = todos.find(t => t.id === state.current_task);
64
+ if (task) {
65
+ taskName = task.name || '';
66
+ }
67
+ }
68
+ }
69
+
70
+ return {
71
+ project: state.project || 'Unknown',
72
+ workflowMode: state.workflow_mode || 'unknown',
73
+ currentPhase: state.current_phase,
74
+ totalPhases: state.total_phases || phases.length,
75
+ currentTask: state.current_task,
76
+ phaseName,
77
+ taskName,
78
+ acceptedTasks,
79
+ totalTasks,
80
+ gitHead: state.git_head || '',
81
+ };
82
+ }
83
+
84
+ module.exports = { findGsdDir, readState, getProgress };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gsd-lite",
3
- "version": "0.5.10",
3
+ "version": "0.5.13",
4
4
  "description": "AI orchestration tool for Claude Code — GSD management shell + Superpowers quality core",
5
5
  "type": "module",
6
6
  "bin": {
@@ -79,14 +79,14 @@ task:3.1 -> phase 3, task 1
79
79
 
80
80
  此函数用于 evidence 归档时判断 evidence 所属 phase。
81
81
 
82
- 来源: `parseScopePhase()` in `src/tools/state.js`
82
+ 来源: `parseScopePhase()` in `src/tools/state/`
83
83
 
84
84
  ## 容量限制与自动裁剪
85
85
 
86
86
  ### MAX_EVIDENCE_ENTRIES
87
87
 
88
88
  - 硬限制: `200` 条
89
- - 定义位置: `src/tools/state.js` 顶层常量
89
+ - 定义位置: `src/tools/state/` 顶层常量
90
90
 
91
91
  ### 自动裁剪触发
92
92
 
@@ -163,4 +163,4 @@ _pruneEvidenceFromState()
163
163
  - 更新时机: executor checkpointed / blocked / failed 时从 result.evidence 覆写
164
164
  - 清空时机: `propagateInvalidation()` 或 reviewer 标记 rework 时清空为 `[]`
165
165
 
166
- 来源: `addEvidence()`, `_pruneEvidenceFromState()`, `pruneEvidence()`, `phaseComplete()` in `src/tools/state.js`; `handleExecutorResult()`, `handleReviewerResult()` in `src/tools/orchestrator.js`
166
+ 来源: `addEvidence()`, `_pruneEvidenceFromState()`, `pruneEvidence()`, `phaseComplete()` in `src/tools/state/`; `handleExecutorResult()`, `handleReviewerResult()` in `src/tools/orchestrator.js`
@@ -34,7 +34,7 @@ task.level 当前值?
34
34
  └── 否 -> 保持当前级别
35
35
  ```
36
36
 
37
- 来源: `reclassifyReviewLevel()` in `src/tools/state.js`
37
+ 来源: `reclassifyReviewLevel()` in `src/tools/state/`
38
38
 
39
39
  ## 审查流程
40
40
 
@@ -215,4 +215,4 @@ stateDiagram-v2
215
215
  **Research 刷新后恢复**:
216
216
  `storeResearch()` 中: 如果 `workflow_mode === 'research_refresh_needed'`,调用 `inferWorkflowModeAfterResearch()` 根据 `current_review` 状态推断恢复到 `reviewing_phase` / `reviewing_task` / `executing_task`。
217
217
 
218
- 来源: `WORKFLOW_MODES` in `src/schema.js`, `resumeWorkflow()`, `evaluatePreflight()` in `src/tools/orchestrator.js`, `storeResearch()` in `src/tools/state.js`
218
+ 来源: `WORKFLOW_MODES` in `src/schema.js`, `resumeWorkflow()`, `evaluatePreflight()` in `src/tools/orchestrator.js`, `storeResearch()` in `src/tools/state/`
package/src/schema.js CHANGED
@@ -586,6 +586,10 @@ export function validateExecutorResult(r) {
586
586
  if (r.outcome === 'checkpointed' && typeof r.checkpoint_commit !== 'string') {
587
587
  errors.push('checkpointed outcome requires checkpoint_commit');
588
588
  }
589
+ // confidence is optional; when present must be one of the valid values
590
+ if ('confidence' in r && !['high', 'medium', 'low'].includes(r.confidence)) {
591
+ errors.push('confidence must be "high", "medium", or "low"');
592
+ }
589
593
  return { valid: errors.length === 0, errors };
590
594
  }
591
595
 
@@ -595,8 +599,8 @@ export function validateExecutorResult(r) {
595
599
  export function validateReviewerResult(r) {
596
600
  const errors = [];
597
601
  if (!['task', 'phase'].includes(r.scope)) errors.push('invalid scope');
598
- if (!(typeof r.scope_id === 'string' || typeof r.scope_id === 'number') || r.scope_id === '') {
599
- errors.push('missing scope_id');
602
+ if (!(typeof r.scope_id === 'string' || typeof r.scope_id === 'number') || r.scope_id === '' || r.scope_id === 0) {
603
+ errors.push('missing or invalid scope_id');
600
604
  }
601
605
  if (!['L2', 'L1-batch', 'L1'].includes(r.review_level)) errors.push('invalid review_level (expected L2, L1-batch, or L1)');
602
606
  if (typeof r.spec_passed !== 'boolean') errors.push('spec_passed must be boolean');
@@ -712,6 +716,8 @@ export function createInitialState({ project, phases }) {
712
716
  if (!Array.isArray(phases)) {
713
717
  return { error: true, message: 'phases must be an array' };
714
718
  }
719
+ // Note: empty phases is allowed here for internal/test use;
720
+ // the public API guard is in init() which rejects phases.length === 0.
715
721
  // Validate task names and uniqueness before creating state
716
722
  const seenIds = new Set();
717
723
  for (const [pi, p] of phases.entries()) {
@@ -741,6 +747,10 @@ export function createInitialState({ project, phases }) {
741
747
  if (!['task', 'phase'].includes(dep.kind)) {
742
748
  return { error: true, message: `Task ${taskId}: requires entry kind must be "task" or "phase" (got "${dep.kind}")` };
743
749
  }
750
+ const validGates = ['checkpoint', 'accepted', 'phase_complete'];
751
+ if (dep.gate && !validGates.includes(dep.gate)) {
752
+ return { error: true, message: `Task ${taskId}: requires entry gate must be one of ${validGates.join(', ')} (got "${dep.gate}")` };
753
+ }
744
754
  if (dep.kind === 'task' && !seenIds.has(String(dep.id))) {
745
755
  return { error: true, message: `Task ${taskId}: requires references non-existent task "${dep.id}" (valid IDs: ${[...seenIds].join(', ')})` };
746
756
  }