gsd-lite 0.6.8 → 0.7.0

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.
Files changed (57) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.mcp.json +0 -0
  4. package/README.md +66 -19
  5. package/agents/debugger.md +2 -2
  6. package/agents/executor.md +1 -1
  7. package/agents/researcher.md +0 -0
  8. package/agents/reviewer.md +0 -0
  9. package/commands/doctor.md +0 -0
  10. package/commands/prd.md +0 -0
  11. package/commands/resume.md +0 -0
  12. package/commands/start.md +0 -0
  13. package/commands/status.md +0 -0
  14. package/commands/stop.md +0 -2
  15. package/hooks/context-monitor.js +0 -0
  16. package/hooks/gsd-auto-update.cjs +23 -5
  17. package/hooks/gsd-context-monitor.cjs +0 -0
  18. package/hooks/gsd-session-init.cjs +0 -0
  19. package/hooks/gsd-session-stop.cjs +0 -0
  20. package/hooks/gsd-statusline.cjs +0 -0
  21. package/hooks/hooks.json +0 -0
  22. package/hooks/lib/gsd-finder.cjs +0 -0
  23. package/hooks/lib/semver-sort.cjs +33 -5
  24. package/hooks/lib/statusline-composite.cjs +0 -0
  25. package/install.js +11 -3
  26. package/launcher.js +1 -0
  27. package/package.json +1 -1
  28. package/references/anti-rationalization-full.md +0 -0
  29. package/references/evidence-spec.md +0 -0
  30. package/references/execution-loop.md +0 -0
  31. package/references/git-worktrees.md +0 -0
  32. package/references/questioning.md +0 -0
  33. package/references/review-classification.md +1 -0
  34. package/references/state-diagram.md +0 -0
  35. package/references/testing-patterns.md +0 -0
  36. package/src/schema.js +0 -0
  37. package/src/server.js +0 -0
  38. package/src/tools/orchestrator/debugger.js +0 -0
  39. package/src/tools/orchestrator/executor.js +1 -0
  40. package/src/tools/orchestrator/helpers.js +0 -0
  41. package/src/tools/orchestrator/index.js +0 -0
  42. package/src/tools/orchestrator/researcher.js +0 -0
  43. package/src/tools/orchestrator/resume.js +9 -1
  44. package/src/tools/orchestrator/reviewer.js +0 -0
  45. package/src/tools/state/constants.js +21 -10
  46. package/src/tools/state/crud.js +6 -6
  47. package/src/tools/state/index.js +0 -0
  48. package/src/tools/state/logic.js +5 -6
  49. package/src/tools/verify.js +0 -0
  50. package/src/utils.js +0 -0
  51. package/uninstall.js +0 -0
  52. package/workflows/debugging.md +0 -0
  53. package/workflows/deviation-rules.md +1 -1
  54. package/workflows/execution-flow.md +0 -0
  55. package/workflows/research.md +0 -0
  56. package/workflows/review-cycle.md +0 -0
  57. package/workflows/tdd-cycle.md +0 -0
@@ -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.6.8",
16
+ "version": "0.7.0",
17
17
  "keywords": [
18
18
  "orchestration",
19
19
  "mcp",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gsd",
3
- "version": "0.6.8",
3
+ "version": "0.7.0",
4
4
  "description": "AI orchestration tool for Claude Code — GSD management shell + Superpowers quality core",
5
5
  "author": {
6
6
  "name": "sdsrss",
package/.mcp.json CHANGED
File without changes
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  > Get Shit Done — AI orchestration for Claude Code
4
4
 
5
- GSD-Lite is an AI orchestration tool for [Claude Code](https://docs.anthropic.com/en/docs/claude-code). It combines structured project management with built-in quality discipline: TDD enforcement, anti-rationalization guards, multi-level code review, and automatic failure recovery — all driven by a state machine that keeps multi-phase projects on track.
5
+ GSD-Lite is an AI orchestration tool for [Claude Code](https://docs.anthropic.com/en/docs/claude-code). It combines structured project management with built-in quality discipline: TDD enforcement, anti-rationalization guards, multi-level code review, and automatic failure recovery — all driven by a 12-state workflow machine that keeps multi-phase projects on track.
6
6
 
7
7
  **Discuss thoroughly, execute automatically.** Have as many rounds of requirement discussion as needed. Once the plan is approved, GSD-Lite auto-executes: coding, self-review, independent review, verification, and phase advancement — with minimal human intervention.
8
8
 
@@ -10,7 +10,7 @@ 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** — 12 workflow modes with precise state transitions, persistent to `state.json`
13
+ - **12-state workflow machine** — `planning executing_task → reviewing_task → reviewing_phase → completed` with precise transitions, persistent to `state.json`
14
14
  - **Automatic task scheduling** — Gate-aware dependency resolution determines what runs next
15
15
  - **Session resilience** — Stop anytime, resume exactly where you left off — crash protection via Stop hook auto-saves state markers
16
16
 
@@ -32,13 +32,19 @@ GSD-Lite is an AI orchestration tool for [Claude Code](https://docs.anthropic.co
32
32
  - **Parallel task scheduling** — Independent tasks within the same phase are identified for concurrent dispatch
33
33
  - **Auto PR suggestion** — Phase/project completion prompts PR creation with evidence summary
34
34
 
35
- ### Context Protection
35
+ ### Context Protection & Monitoring
36
36
  - **Subagent isolation** — Each task runs in its own agent context, preventing cross-contamination
37
- - **StatusLine monitoring** — Real-time context health tracking via Claude Code StatusLine
37
+ - **Real-time context health monitoring** — StatusLine tracks context usage and project phase; composite StatusLine support coexists with other plugins
38
38
  - **Session lifecycle hooks** — Stop hook writes crash marker; SessionStart injects project status into CLAUDE.md; resume detects non-graceful exits
39
39
  - **Evidence-based verification** — Every claim backed by command output, not assertions
40
40
  - **Research with TTL** — Research artifacts include volatility ratings and expiration dates
41
41
 
42
+ ### Auto-Update & Version Management
43
+ - **Automatic update checks** — Checks GitHub Releases every 24 hours with rate-limit backoff
44
+ - **Version drift detection** — Server startup compares running version against disk and plugin registry, warns on mismatch
45
+ - **Smart cache management** — Keeps latest 3 cached versions, auto-prunes old entries
46
+ - **Idempotent installer** — Reinstall anytime without uninstalling; legacy files auto-cleaned
47
+
42
48
  ## Architecture
43
49
 
44
50
  ```
@@ -54,8 +60,8 @@ User → discuss + research (confirm requirements) → approve plan → auto-exe
54
60
  |---------|---------|
55
61
  | `/gsd:start` | Interactive start — discuss requirements, research, plan, then auto-execute |
56
62
  | `/gsd:prd <input>` | Start from a requirements doc or description text |
57
- | `/gsd:resume` | Resume execution from saved state |
58
- | `/gsd:status` | View project progress dashboard |
63
+ | `/gsd:resume` | Resume execution from saved state with workspace validation |
64
+ | `/gsd:status` | View project progress dashboard (derived from canonical state fields) |
59
65
  | `/gsd:stop` | Save state and pause execution |
60
66
  | `/gsd:doctor` | Diagnostic checks on GSD-Lite installation and project health |
61
67
 
@@ -68,6 +74,17 @@ User → discuss + research (confirm requirements) → approve plan → auto-exe
68
74
  | **researcher** | Ecosystem research (Context7 → official docs → web) | Confidence scoring + TTL |
69
75
  | **debugger** | 4-phase systematic root cause analysis | Root Cause Iron Law |
70
76
 
77
+ ### 6 Workflows
78
+
79
+ | Workflow | Purpose |
80
+ |----------|---------|
81
+ | `tdd-cycle` | RED-GREEN-REFACTOR TDD cycle enforcement |
82
+ | `review-cycle` | Two-level review gates and accept/rework decisions |
83
+ | `debugging` | 4-phase root cause analysis process |
84
+ | `research` | Research with confidence scoring and TTL expiration |
85
+ | `deviation-rules` | Anti-rationalization guards and red-flag checklists |
86
+ | `execution-flow` | Complete task execution cycle from dispatch to checkpoint |
87
+
71
88
  ### MCP Server (11 Tools)
72
89
 
73
90
  | Tool | Purpose |
@@ -84,6 +101,19 @@ User → discuss + research (confirm requirements) → approve plan → auto-exe
84
101
  | `orchestrator-handle-researcher-result` | Store research artifacts and decisions |
85
102
  | `orchestrator-handle-debugger-result` | Process root cause analysis, re-dispatch executor |
86
103
 
104
+ ### 8 References
105
+
106
+ | Reference | Content |
107
+ |-----------|---------|
108
+ | `execution-loop` | 9-step execution loop specification (single source of truth) |
109
+ | `review-classification` | Review level classification decision tree (L0/L1/L2) |
110
+ | `evidence-spec` | Evidence validation and citation rules |
111
+ | `state-diagram` | 12-state lifecycle workflow machine diagram |
112
+ | `testing-patterns` | Test structure and patterns |
113
+ | `anti-rationalization-full` | Full red-flag checklist for agents |
114
+ | `git-worktrees` | Git worktree isolation strategy |
115
+ | `questioning` | Requirements clarification patterns |
116
+
87
117
  ## Installation
88
118
 
89
119
  ### Method 1: Claude Code Plugin (Recommended)
@@ -96,7 +126,7 @@ User → discuss + research (confirm requirements) → approve plan → auto-exe
96
126
  /plugin install gsd
97
127
  ```
98
128
 
99
- Automatically registers all commands, agents, workflows, MCP server, and hooks. Run these commands inside a Claude Code session.
129
+ Automatically registers all commands, agents, workflows, MCP server, hooks, and auto-update. Run these commands inside a Claude Code session.
100
130
 
101
131
  ### Method 2: npx
102
132
 
@@ -113,12 +143,14 @@ cd gsd-lite && npm install && node cli.js install
113
143
 
114
144
  Methods 2 & 3 write components to `~/.claude/` and register the MCP server in `settings.json`.
115
145
 
146
+ The installer copies commands, agents, workflows, references, and hooks to `~/.claude/`, and sets up the MCP server runtime in `~/.claude/gsd/`.
147
+
116
148
  Uninstall: `node cli.js uninstall` or `npx gsd-lite uninstall`
117
149
 
118
150
  ## Upgrade
119
151
 
120
152
  ```bash
121
- # Plugin
153
+ # Plugin (auto-update checks GitHub Releases every 24h)
122
154
  /plugin update gsd
123
155
 
124
156
  # npx
@@ -130,6 +162,7 @@ git pull && npm install && node cli.js install
130
162
 
131
163
  - Installer is idempotent — no need to uninstall first
132
164
  - Upgrades from older versions auto-clean legacy files
165
+ - Smart cache management keeps latest 3 versions, prunes old entries
133
166
  - Restart Claude Code after updating to load new MCP server / hooks
134
167
 
135
168
  ## Quick Start
@@ -204,8 +237,10 @@ executor retries → with debugger guidance injected
204
237
  All state lives in `.gsd/state.json` — a single source of truth with:
205
238
  - Canonical fields (whitelist-controlled, schema-validated)
206
239
  - Lifecycle state machine (pending → running → checkpointed → accepted)
240
+ - Optimistic concurrency control (`_version` field with `VERSION_CONFLICT` detection)
207
241
  - Evidence references (command outputs, test results)
208
242
  - Research artifacts and decision index
243
+ - Incremental validation (simple field updates use fast path; phases use full validation)
209
244
 
210
245
  ## Comparison with GSD
211
246
 
@@ -213,20 +248,22 @@ All state lives in `.gsd/state.json` — a single source of truth with:
213
248
  |-----------|-----|----------|
214
249
  | Commands | 32 | **6** |
215
250
  | Agents | 12 | **4** |
216
- | Source files | 100+ | **~48** |
251
+ | Source files | 100+ | **~15** |
217
252
  | Installer | 2465 lines | **~290 lines** |
218
253
  | User interactions | 6+ confirmations | **Typically 2** |
219
254
  | TDD / Anti-rationalization | No | **Yes** |
220
255
  | State machine recovery | Partial | **Full (12 modes)** |
221
256
  | Evidence-based verification | No | **Yes** |
257
+ | Auto-update | No | **Yes** |
258
+ | Context health monitoring | No | **Yes** |
222
259
 
223
260
  ## Project Structure
224
261
 
225
262
  ```
226
263
  gsd-lite/
227
- ├── src/ # MCP Server + tools
228
- │ ├── server.js # MCP Server entry (11 tools)
229
- │ ├── schema.js # State schema + lifecycle validation
264
+ ├── src/ # MCP Server + tools (15 source files)
265
+ │ ├── server.js # MCP Server entry (11 tools + version drift detection)
266
+ │ ├── schema.js # State schema + lifecycle validation + incremental validation
230
267
  │ ├── utils.js # Shared utilities (atomic writes, git, file lock)
231
268
  │ └── tools/
232
269
  │ ├── state/ # State management (modular)
@@ -236,7 +273,7 @@ gsd-lite/
236
273
  │ │ └── index.js # Re-exports
237
274
  │ ├── orchestrator/ # Orchestration logic (modular)
238
275
  │ │ ├── helpers.js # Shared constants, preflight, dispatch
239
- │ │ ├── resume.js # Workflow resume state machine
276
+ │ │ ├── resume.js # Workflow resume state machine (12 modes)
240
277
  │ │ ├── executor.js # Executor result handler
241
278
  │ │ ├── reviewer.js # Reviewer result handler
242
279
  │ │ ├── debugger.js # Debugger result handler
@@ -246,19 +283,24 @@ gsd-lite/
246
283
  ├── commands/ # 6 slash commands (start, prd, resume, status, stop, doctor)
247
284
  ├── agents/ # 4 subagent prompts (executor, reviewer, researcher, debugger)
248
285
  ├── 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/ # 866 tests (unit + simulation + E2E)
286
+ ├── references/ # 8 reference docs (execution-loop, state-diagram, evidence-spec, etc.)
287
+ ├── hooks/ # Session lifecycle hooks
288
+ ├── gsd-auto-update.cjs # Auto-update from GitHub Releases (24h check interval)
289
+ ├── gsd-context-monitor.cjs # Real-time context health monitoring
290
+ │ ├── gsd-session-init.cjs # Session initialization + CLAUDE.md status injection
291
+ │ ├── gsd-session-stop.cjs # Graceful shutdown with crash markers
292
+ │ ├── gsd-statusline.cjs # StatusLine display (composite-aware)
293
+ │ └── lib/ # Shared hook utilities (gsd-finder, composite statusline, semver)
294
+ ├── tests/ # 909 tests (unit + simulation + E2E integration)
253
295
  ├── cli.js # Install/uninstall CLI entry
254
- ├── install.js # Installation script
296
+ ├── install.js # Installation script (plugin-aware, idempotent)
255
297
  └── uninstall.js # Uninstall script
256
298
  ```
257
299
 
258
300
  ## Testing
259
301
 
260
302
  ```bash
261
- npm test # Run all 866 tests
303
+ npm test # Run all 909 tests
262
304
  npm run test:coverage # Tests + coverage report (94%+ lines, 83%+ branches)
263
305
  npm run lint # Biome lint
264
306
  node --test tests/file.js # Run a single test file
@@ -270,6 +312,11 @@ node --test tests/file.js # Run a single test file
270
312
  - [Engineering Tasks](docs/gsd-lite-engineering-tasks.md) — 38 implementation tasks (5 phases, all complete)
271
313
  - [Calibration Notes](docs/calibration-notes.md) — Context threshold and TTL calibration
272
314
 
315
+ ## Requirements
316
+
317
+ - Node.js >= 20.0.0
318
+ - [Claude Code](https://docs.anthropic.com/en/docs/claude-code)
319
+
273
320
  ## License
274
321
 
275
322
  MIT
@@ -49,9 +49,9 @@ Phase 2 模式分析:
49
49
  2. 对比差异,列出所有不同点
50
50
  3. 不要假设"那个不重要"
51
51
 
52
- Phase 3 假设测试:
52
+ Phase 3 假设测试 (通过观察验证,不直接修改代码):
53
53
  1. 明确陈述: "我认为 X 是根因,因为 Y"
54
- 2. 最小变更测试 (一次只改一个变量)
54
+ 2. 通过 Bash 运行测试/添加日志、读取运行时输出来验证假设
55
55
  3. 验证: 有效 → Phase 4 / 无效 → 新假设
56
56
 
57
57
  Phase 4 修复方向建议:
@@ -52,7 +52,7 @@ tools: Read, Write, Edit, Bash, Grep, Glob
52
52
  "summary": "Implemented PUT /api/users/:id endpoint",
53
53
  "checkpoint_commit": "a1b2c3d",
54
54
  "files_changed": ["src/api/users.ts", "tests/users.test.ts"],
55
- "decisions": ["[DECISION] use optimistic locking by version column"],
55
+ "decisions": [{"id": "d1", "summary": "use optimistic locking by version column", "rationale": "prevents concurrent update conflicts"}],
56
56
  "blockers": [],
57
57
  "contract_changed": true,
58
58
  "confidence": "high",
File without changes
File without changes
File without changes
package/commands/prd.md CHANGED
File without changes
File without changes
package/commands/start.md CHANGED
File without changes
File without changes
package/commands/stop.md CHANGED
@@ -29,8 +29,6 @@ description: Save current state and pause project execution
29
29
 
30
30
  使用 `state-update` MCP 工具更新状态,确保通过 schema 校验和乐观锁。
31
31
 
32
- 使用原子写入: 先写 `.gsd/state.json.tmp`,成功后 rename 为 `.gsd/state.json`
33
-
34
32
  ## STEP 3: 确认输出
35
33
 
36
34
  输出: "已暂停。运行 /gsd:resume 继续"
File without changes
@@ -323,7 +323,7 @@ function validateExtractedPackage(extractDir) {
323
323
  const pkgPath = path.join(extractDir, 'package.json');
324
324
  const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
325
325
  if (pkg.name !== 'gsd-lite') return false;
326
- if (!pkg.version || !/^\d+\.\d+\.\d+/.test(pkg.version)) return false;
326
+ if (!pkg.version || !/^\d+\.\d+\.\d+(-[\w.]+)?$/.test(pkg.version)) return false;
327
327
  // Verify install.js exists and is a regular file (lstat rejects symlinks)
328
328
  const installPath = path.join(extractDir, 'install.js');
329
329
  const lstat = fs.lstatSync(installPath);
@@ -404,8 +404,26 @@ async function downloadAndInstall(tarballUrl, verbose = false, token = null) {
404
404
  // Write tarball to file, then extract with spawnSync (no shell)
405
405
  const tarPath = path.join(tmpDir, 'release.tar.gz');
406
406
  fs.writeFileSync(tarPath, tarData);
407
- const tar = spawnSync('tar', ['xzf', tarPath, '-C', tmpDir, '--strip-components=1'], { timeout: 30000 });
408
- if (tar.status !== 0) throw new Error(`tar extract failed: ${(tar.stderr || '').toString().slice(0, 200)}`);
407
+ const stripFlag = process.platform === 'win32' ? [] : ['--strip-components=1'];
408
+ const tar = spawnSync('tar', ['xzf', tarPath, '-C', tmpDir, ...stripFlag], { timeout: 30000 });
409
+ if (tar.status !== 0) {
410
+ const errMsg = (tar.stderr || '').toString().slice(0, 200);
411
+ if (process.platform === 'win32') {
412
+ console.error('[gsd] Auto-update: tar extraction failed on Windows — manual update may be required');
413
+ }
414
+ throw new Error(`tar extract failed: ${errMsg}`);
415
+ }
416
+ // On Windows without --strip-components, the content is nested in a subdirectory
417
+ if (process.platform === 'win32') {
418
+ const entries = fs.readdirSync(tmpDir).filter(e => e !== 'release.tar.gz');
419
+ if (entries.length === 1 && fs.statSync(path.join(tmpDir, entries[0])).isDirectory()) {
420
+ const nested = path.join(tmpDir, entries[0]);
421
+ for (const f of fs.readdirSync(nested)) {
422
+ fs.renameSync(path.join(nested, f), path.join(tmpDir, f));
423
+ }
424
+ fs.rmdirSync(nested);
425
+ }
426
+ }
409
427
 
410
428
  // Validate extracted package before installing
411
429
  if (!validateExtractedPackage(tmpDir)) {
@@ -499,7 +517,7 @@ function pruneOldCacheVersions(cacheBase, keepCount = 3, verbose = false) {
499
517
  try {
500
518
  if (!fs.existsSync(cacheBase)) return;
501
519
  const entries = fs.readdirSync(cacheBase, { withFileTypes: true })
502
- .filter(e => e.isDirectory() && /^\d+\.\d+\.\d+$/.test(e.name))
520
+ .filter(e => e.isDirectory() && /^\d+\.\d+\.\d+(-[\w.]+)?$/.test(e.name))
503
521
  .map(e => e.name);
504
522
  if (entries.length <= keepCount) return;
505
523
 
@@ -546,7 +564,7 @@ function syncPluginCache(extractedDir, verbose = false) {
546
564
  const newPkgPath = path.join(extractedDir, 'package.json');
547
565
  if (!fs.existsSync(newPkgPath)) return;
548
566
  const newVersion = JSON.parse(fs.readFileSync(newPkgPath, 'utf8')).version;
549
- if (!newVersion) return;
567
+ if (!newVersion || !/^\d+\.\d+\.\d+(-[\w.]+)?$/.test(newVersion)) return;
550
568
 
551
569
  // Determine new cache path
552
570
  const cacheBase = path.join(claudeDir, 'plugins', 'cache', 'gsd', 'gsd');
File without changes
File without changes
File without changes
File without changes
package/hooks/hooks.json CHANGED
File without changes
File without changes
@@ -2,17 +2,45 @@
2
2
  'use strict';
3
3
 
4
4
  /**
5
- * Compare two semver version strings (e.g. "1.2.3") for sorting.
6
- * Returns negative if a < b, positive if a > b, 0 if equal.
5
+ * Compare two semver version strings for sorting.
6
+ * Handles pre-release suffixes: 1.0.0-beta.1 < 1.0.0 (per semver spec).
7
7
  * @param {string} a
8
8
  * @param {string} b
9
9
  * @returns {number}
10
10
  */
11
11
  function semverSortComparator(a, b) {
12
- const pa = a.split('.').map(s => parseInt(s, 10) || 0);
13
- const pb = b.split('.').map(s => parseInt(s, 10) || 0);
12
+ const [coreA, preA] = String(a).split('-', 2);
13
+ const [coreB, preB] = String(b).split('-', 2);
14
+ const pa = coreA.split('.').map(s => parseInt(s, 10) || 0);
15
+ const pb = coreB.split('.').map(s => parseInt(s, 10) || 0);
14
16
  for (let i = 0; i < 3; i++) {
15
- if (pa[i] !== pb[i]) return pa[i] - pb[i];
17
+ if ((pa[i] || 0) !== (pb[i] || 0)) return (pa[i] || 0) - (pb[i] || 0);
18
+ }
19
+ // Same core version: pre-release < release (1.0.0-beta < 1.0.0)
20
+ if (preA && !preB) return -1;
21
+ if (!preA && preB) return 1;
22
+ if (preA && preB) {
23
+ // Compare pre-release identifiers left-to-right
24
+ const partsA = preA.split('.');
25
+ const partsB = preB.split('.');
26
+ for (let i = 0; i < Math.max(partsA.length, partsB.length); i++) {
27
+ if (i >= partsA.length) return -1; // fewer fields = lower precedence
28
+ if (i >= partsB.length) return 1;
29
+ const na = parseInt(partsA[i], 10);
30
+ const nb = parseInt(partsB[i], 10);
31
+ const aIsNum = !Number.isNaN(na);
32
+ const bIsNum = !Number.isNaN(nb);
33
+ if (aIsNum && bIsNum) {
34
+ if (na !== nb) return na - nb;
35
+ } else if (aIsNum) {
36
+ return -1; // numeric < string
37
+ } else if (bIsNum) {
38
+ return 1;
39
+ } else {
40
+ const cmp = partsA[i].localeCompare(partsB[i]);
41
+ if (cmp !== 0) return cmp;
42
+ }
43
+ }
16
44
  }
17
45
  return 0;
18
46
  }
File without changes
package/install.js CHANGED
@@ -145,7 +145,7 @@ export function main() {
145
145
  const preserveRuntime = existsSync(runtimeSubdir);
146
146
  let runtimeBackup;
147
147
  if (preserveRuntime) {
148
- runtimeBackup = join(RUNTIME_DIR, '..', '.gsd-runtime-backup');
148
+ runtimeBackup = join(RUNTIME_DIR, '..', `.gsd-runtime-backup-${process.pid}`);
149
149
  try { cpSync(runtimeSubdir, runtimeBackup, { recursive: true }); } catch { runtimeBackup = null; }
150
150
  }
151
151
  rmSync(RUNTIME_DIR, { recursive: true, force: true });
@@ -183,6 +183,11 @@ export function main() {
183
183
  // 6. Stable runtime for MCP server
184
184
  copyDir(join(__dirname, 'src'), join(RUNTIME_DIR, 'src'), 'runtime/src → ~/.claude/gsd/src/');
185
185
  copyFile(join(__dirname, 'package.json'), join(RUNTIME_DIR, 'package.json'), 'runtime/package.json → ~/.claude/gsd/package.json');
186
+ // Copy lock file so `npm ci` works when node_modules are not present (npx scenario)
187
+ const lockFile = join(__dirname, 'package-lock.json');
188
+ if (existsSync(lockFile)) {
189
+ copyFile(lockFile, join(RUNTIME_DIR, 'package-lock.json'), 'runtime/package-lock.json → ~/.claude/gsd/package-lock.json');
190
+ }
186
191
 
187
192
  // 7. Runtime dependencies — copy local node_modules or install fresh (npx hoists deps)
188
193
  const localNM = join(__dirname, 'node_modules');
@@ -190,8 +195,11 @@ export function main() {
190
195
  copyDir(localNM, join(RUNTIME_DIR, 'node_modules'), 'runtime/node_modules (copied)');
191
196
  } else if (!DRY_RUN) {
192
197
  log(' ⧗ Installing runtime dependencies...');
198
+ const lockFile = join(RUNTIME_DIR, 'package-lock.json');
199
+ const hasLockFile = existsSync(lockFile);
200
+ const installCmd = hasLockFile ? 'npm ci --omit=dev' : 'npm install --omit=dev --no-fund --no-audit';
193
201
  try {
194
- execSync('npm ci --omit=dev', { cwd: RUNTIME_DIR, stdio: 'pipe' });
202
+ execSync(installCmd, { cwd: RUNTIME_DIR, stdio: 'pipe' });
195
203
  log(' ✓ runtime dependencies installed');
196
204
  } catch (err) {
197
205
  log(` ✗ Failed to install runtime dependencies: ${err.message}`);
@@ -264,7 +272,7 @@ export function main() {
264
272
  if (existsSync(cacheBase)) {
265
273
  try {
266
274
  const entries = readdirSync(cacheBase, { withFileTypes: true })
267
- .filter(e => e.isDirectory() && /^\d+\.\d+\.\d+$/.test(e.name)).map(e => e.name);
275
+ .filter(e => e.isDirectory() && /^\d+\.\d+\.\d+(-[\w.]+)?$/.test(e.name)).map(e => e.name);
268
276
  if (entries.length > 3) {
269
277
  const sorted = entries.slice().sort(semverSortComparator);
270
278
  // Detect versions with active processes to avoid disrupting running sessions
package/launcher.js CHANGED
@@ -14,6 +14,7 @@ if (!existsSync(join(__dirname, 'node_modules', '@modelcontextprotocol'))) {
14
14
  execSync('npm install --omit=dev --ignore-scripts', {
15
15
  cwd: __dirname,
16
16
  stdio: 'pipe',
17
+ timeout: 60000,
17
18
  });
18
19
  } catch (err) {
19
20
  console.error('Failed to install dependencies:', err.stderr?.toString() || err.message);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gsd-lite",
3
- "version": "0.6.8",
3
+ "version": "0.7.0",
4
4
  "description": "AI orchestration tool for Claude Code — GSD management shell + Superpowers quality core",
5
5
  "type": "module",
6
6
  "bin": {
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -34,6 +34,7 @@ task.level 当前值?
34
34
  └── L0 或 L1
35
35
  ├── executor decisions 含 [LEVEL-UP]? -> 升级为 L2
36
36
  ├── contract_changed: true + task.name 匹配敏感关键词? -> 升级为 L2
37
+ ├── L1 + confidence: 'low'? -> 升级为 L2
37
38
  ├── L1 + confidence: 'high' + !contract_changed + 有 evidence 且无测试失败? -> 降为 L0
38
39
  └── 否 -> 保持当前级别
39
40
  ```
File without changes
File without changes
package/src/schema.js CHANGED
File without changes
package/src/server.js CHANGED
File without changes
File without changes
@@ -23,6 +23,7 @@ export async function handleExecutorResult({ result, basePath = process.cwd() }
23
23
  // Note: read() is outside the state lock. This is safe because the MCP server
24
24
  // processes tool calls sequentially (single-session, promise-queue serialized).
25
25
  // persist() below re-acquires the lock and applies changes atomically.
26
+ // TODO: if MCP SDK supports concurrent tool calls, move this read inside withStateLock.
26
27
  const state = await read({ basePath });
27
28
  if (state.error) return state;
28
29
  const { phase, task } = getPhaseAndTask(state, result.task_id);
File without changes
File without changes
File without changes
@@ -1,5 +1,7 @@
1
1
  import { read, selectRunnableTask } from '../state/index.js';
2
- import { getGitHead } from '../../utils.js';
2
+ import { getGitHead, getGsdDir } from '../../utils.js';
3
+ import { join } from 'node:path';
4
+ import { unlink } from 'node:fs/promises';
3
5
  import {
4
6
  MAX_RESUME_DEPTH,
5
7
  CONTEXT_RESUME_THRESHOLD,
@@ -269,6 +271,12 @@ export async function resumeWorkflow({ basePath = process.cwd(), _depth = 0, unb
269
271
  return state;
270
272
  }
271
273
 
274
+ // Clear session-end marker if present (crash recovery)
275
+ try {
276
+ const gsdDir = await getGsdDir(basePath);
277
+ if (gsdDir) await unlink(join(gsdDir, '.session-end')).catch(() => {});
278
+ } catch {}
279
+
272
280
  // Force-unblock specified tasks before normal resume flow
273
281
  if (Array.isArray(unblock_tasks) && unblock_tasks.length > 0 && _depth === 0) {
274
282
  const phase = getCurrentPhase(state);
File without changes
@@ -22,32 +22,43 @@ export const ERROR_CODES = {
22
22
 
23
23
  // C-1: Serialize all state mutations to prevent TOCTOU races
24
24
  // C-2: Layer cross-process advisory file lock on top of in-process queue
25
- let _mutationQueue = Promise.resolve();
26
- let _fileLockPath = null;
25
+ // Per-basePath keyed maps — safe for multi-project concurrent use
26
+ const _mutationQueues = new Map();
27
+ const _fileLockPaths = new Map();
27
28
 
28
29
  export function setLockPath(lockPath) {
29
- _fileLockPath = lockPath;
30
+ // Legacy API for tests — sets/clears the default (null-key) lock path
31
+ if (lockPath === null) {
32
+ _fileLockPaths.delete(null);
33
+ _mutationQueues.delete(null);
34
+ } else {
35
+ _fileLockPaths.set(null, lockPath);
36
+ }
30
37
  }
31
38
 
32
39
  /**
33
- * Ensure _fileLockPath is set from a known state path.
40
+ * Ensure lock path is set for a given state path.
34
41
  * Must be called before withStateLock in all mutation paths.
35
42
  */
36
43
  export function ensureLockPathFromStatePath(statePath) {
37
44
  if (statePath) {
38
- _fileLockPath = join(dirname(statePath), 'state.lock');
45
+ const lockPath = join(dirname(statePath), 'state.lock');
46
+ _fileLockPaths.set(statePath, lockPath);
39
47
  }
40
48
  }
41
49
 
42
- export function withStateLock(fn) {
43
- const p = _mutationQueue.then(() => {
44
- if (_fileLockPath) {
45
- return withFileLock(_fileLockPath, fn);
50
+ export function withStateLock(fn, statePath) {
51
+ const lockPath = _fileLockPaths.get(statePath) ?? _fileLockPaths.get(null);
52
+ const queueKey = statePath ?? null;
53
+ const prev = _mutationQueues.get(queueKey) ?? Promise.resolve();
54
+ const p = prev.then(() => {
55
+ if (lockPath) {
56
+ return withFileLock(lockPath, fn);
46
57
  }
47
58
  process.stderr.write('[gsd] WARNING: withStateLock called without lock path — cross-process safety not guaranteed\n');
48
59
  return fn();
49
60
  });
50
- _mutationQueue = p.catch(() => {});
61
+ _mutationQueues.set(queueKey, p.catch(() => {}));
51
62
  return p;
52
63
  }
53
64
 
@@ -147,7 +147,7 @@ export async function init({ project, phases, research, force = false, basePath
147
147
  })),
148
148
  research: !!research,
149
149
  };
150
- });
150
+ }, statePath);
151
151
  }
152
152
 
153
153
  /**
@@ -378,7 +378,7 @@ export async function update({ updates, basePath = process.cwd(), expectedVersio
378
378
 
379
379
  await writeJson(statePath, merged);
380
380
  return { success: true, state: merged };
381
- });
381
+ }, statePath);
382
382
  }
383
383
 
384
384
  /**
@@ -592,7 +592,7 @@ export async function phaseComplete({
592
592
  workflow_mode: state.workflow_mode,
593
593
  ...(isCompleted ? { message: 'All phases completed — project finished' } : {}),
594
594
  };
595
- });
595
+ }, statePath);
596
596
  }
597
597
 
598
598
  /**
@@ -639,7 +639,7 @@ export async function addEvidence({ id, data, basePath = process.cwd() }) {
639
639
  state._version = (state._version ?? 0) + 1;
640
640
  await writeJson(statePath, state);
641
641
  return { success: true };
642
- });
642
+ }, statePath);
643
643
  }
644
644
 
645
645
  /**
@@ -712,7 +712,7 @@ export async function pruneEvidence({ currentPhase, basePath = process.cwd() })
712
712
  }
713
713
 
714
714
  return { success: true, archived };
715
- });
715
+ }, statePath);
716
716
  }
717
717
 
718
718
  /**
@@ -792,7 +792,7 @@ export async function patchPlan({ operations, basePath = process.cwd() } = {}) {
792
792
  state._version = (state._version ?? 0) + 1;
793
793
  await writeJson(statePath, state);
794
794
  return { success: true, applied, plan_version: state.plan_version };
795
- });
795
+ }, statePath);
796
796
  }
797
797
 
798
798
  function _applyPatchOp(state, op) {
File without changes
@@ -1,7 +1,6 @@
1
1
  // Automation/business logic functions
2
2
 
3
3
  import { dirname, join } from 'node:path';
4
- import { writeFileSync, unlinkSync } from 'node:fs';
5
4
  import { writeFile, rename, unlink } from 'node:fs/promises';
6
5
  import { ensureDir, readJson, writeJson, getStatePath } from '../../utils.js';
7
6
  import {
@@ -450,7 +449,7 @@ export async function storeResearch({ result, artifacts, decision_index, basePat
450
449
  // state.json write. On recovery (future iteration), presence of this file
451
450
  // indicates a potentially inconsistent research state.
452
451
  const sentinelPath = join(gsdDir, '.research-commit-pending');
453
- writeFileSync(sentinelPath, JSON.stringify({ timestamp: Date.now(), pid: process.pid }));
452
+ await writeFile(sentinelPath, JSON.stringify({ timestamp: Date.now(), pid: process.pid }));
454
453
 
455
454
  // Atomic multi-file write: write all artifacts first, then rename in batch
456
455
  const normalizedArtifacts = normalizeResearchArtifacts(artifacts);
@@ -472,7 +471,7 @@ export async function storeResearch({ result, artifacts, decision_index, basePat
472
471
  for (const { tmp } of tmpPaths) {
473
472
  try { await unlink(tmp); } catch {}
474
473
  }
475
- try { unlinkSync(sentinelPath); } catch {}
474
+ try { await unlink(sentinelPath); } catch {}
476
475
  throw err;
477
476
  }
478
477
 
@@ -509,7 +508,7 @@ export async function storeResearch({ result, artifacts, decision_index, basePat
509
508
 
510
509
  const validation = validateState(state);
511
510
  if (!validation.valid) {
512
- try { unlinkSync(sentinelPath); } catch {}
511
+ try { await unlink(sentinelPath); } catch {}
513
512
  return { error: true, code: ERROR_CODES.VALIDATION_FAILED, message: `State validation failed: ${validation.errors.join('; ')}` };
514
513
  }
515
514
 
@@ -517,7 +516,7 @@ export async function storeResearch({ result, artifacts, decision_index, basePat
517
516
  await writeJson(statePath, state);
518
517
 
519
518
  // Remove sentinel after successful state write — crash consistency window closed
520
- try { unlinkSync(sentinelPath); } catch {}
519
+ try { await unlink(sentinelPath); } catch {}
521
520
 
522
521
  return {
523
522
  success: true,
@@ -527,5 +526,5 @@ export async function storeResearch({ result, artifacts, decision_index, basePat
527
526
  warnings: refreshResult.warnings,
528
527
  research: state.research,
529
528
  };
530
- });
529
+ }, statePath);
531
530
  }
File without changes
package/src/utils.js CHANGED
File without changes
package/uninstall.js CHANGED
File without changes
File without changes
@@ -77,7 +77,7 @@
77
77
 
78
78
  ### STOP: 3 次失败停止
79
79
 
80
- **条件:** 同一错误指纹 (file+line msg[:50]) 出现 3 次。
80
+ **条件:** 同一 task 连续失败 3 (retry_count >= 3)。error_fingerprint 仅用于调试上下文,不参与触发判断。
81
81
 
82
82
  处理:
83
83
  1. 返回 `outcome: "failed"`
File without changes
File without changes
File without changes
File without changes