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.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.mcp.json +0 -0
- package/README.md +66 -19
- package/agents/debugger.md +2 -2
- package/agents/executor.md +1 -1
- package/agents/researcher.md +0 -0
- package/agents/reviewer.md +0 -0
- package/commands/doctor.md +0 -0
- package/commands/prd.md +0 -0
- package/commands/resume.md +0 -0
- package/commands/start.md +0 -0
- package/commands/status.md +0 -0
- package/commands/stop.md +0 -2
- package/hooks/context-monitor.js +0 -0
- package/hooks/gsd-auto-update.cjs +23 -5
- package/hooks/gsd-context-monitor.cjs +0 -0
- package/hooks/gsd-session-init.cjs +0 -0
- package/hooks/gsd-session-stop.cjs +0 -0
- package/hooks/gsd-statusline.cjs +0 -0
- package/hooks/hooks.json +0 -0
- package/hooks/lib/gsd-finder.cjs +0 -0
- package/hooks/lib/semver-sort.cjs +33 -5
- package/hooks/lib/statusline-composite.cjs +0 -0
- package/install.js +11 -3
- package/launcher.js +1 -0
- package/package.json +1 -1
- package/references/anti-rationalization-full.md +0 -0
- package/references/evidence-spec.md +0 -0
- package/references/execution-loop.md +0 -0
- package/references/git-worktrees.md +0 -0
- package/references/questioning.md +0 -0
- package/references/review-classification.md +1 -0
- package/references/state-diagram.md +0 -0
- package/references/testing-patterns.md +0 -0
- package/src/schema.js +0 -0
- package/src/server.js +0 -0
- package/src/tools/orchestrator/debugger.js +0 -0
- package/src/tools/orchestrator/executor.js +1 -0
- package/src/tools/orchestrator/helpers.js +0 -0
- package/src/tools/orchestrator/index.js +0 -0
- package/src/tools/orchestrator/researcher.js +0 -0
- package/src/tools/orchestrator/resume.js +9 -1
- package/src/tools/orchestrator/reviewer.js +0 -0
- package/src/tools/state/constants.js +21 -10
- package/src/tools/state/crud.js +6 -6
- package/src/tools/state/index.js +0 -0
- package/src/tools/state/logic.js +5 -6
- package/src/tools/verify.js +0 -0
- package/src/utils.js +0 -0
- package/uninstall.js +0 -0
- package/workflows/debugging.md +0 -0
- package/workflows/deviation-rules.md +1 -1
- package/workflows/execution-flow.md +0 -0
- package/workflows/research.md +0 -0
- package/workflows/review-cycle.md +0 -0
- 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.
|
|
16
|
+
"version": "0.7.0",
|
|
17
17
|
"keywords": [
|
|
18
18
|
"orchestration",
|
|
19
19
|
"mcp",
|
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
|
-
- **
|
|
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
|
-
- **
|
|
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
|
|
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+ | **~
|
|
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
|
|
251
|
-
│
|
|
252
|
-
├──
|
|
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
|
|
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
|
package/agents/debugger.md
CHANGED
package/agents/executor.md
CHANGED
|
@@ -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": ["
|
|
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",
|
package/agents/researcher.md
CHANGED
|
File without changes
|
package/agents/reviewer.md
CHANGED
|
File without changes
|
package/commands/doctor.md
CHANGED
|
File without changes
|
package/commands/prd.md
CHANGED
|
File without changes
|
package/commands/resume.md
CHANGED
|
File without changes
|
package/commands/start.md
CHANGED
|
File without changes
|
package/commands/status.md
CHANGED
|
File without changes
|
package/commands/stop.md
CHANGED
package/hooks/context-monitor.js
CHANGED
|
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
|
|
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
|
|
408
|
-
|
|
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
|
|
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
|
package/hooks/gsd-statusline.cjs
CHANGED
|
File without changes
|
package/hooks/hooks.json
CHANGED
|
File without changes
|
package/hooks/lib/gsd-finder.cjs
CHANGED
|
File without changes
|
|
@@ -2,17 +2,45 @@
|
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* Compare two semver version strings
|
|
6
|
-
*
|
|
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
|
|
13
|
-
const
|
|
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, '..',
|
|
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(
|
|
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
|
|
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
|
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
|
-
|
|
26
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
44
|
-
|
|
45
|
-
|
|
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
|
-
|
|
61
|
+
_mutationQueues.set(queueKey, p.catch(() => {}));
|
|
51
62
|
return p;
|
|
52
63
|
}
|
|
53
64
|
|
package/src/tools/state/crud.js
CHANGED
|
@@ -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) {
|
package/src/tools/state/index.js
CHANGED
|
File without changes
|
package/src/tools/state/logic.js
CHANGED
|
@@ -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
|
-
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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
|
}
|
package/src/tools/verify.js
CHANGED
|
File without changes
|
package/src/utils.js
CHANGED
|
File without changes
|
package/uninstall.js
CHANGED
|
File without changes
|
package/workflows/debugging.md
CHANGED
|
File without changes
|
|
File without changes
|
package/workflows/research.md
CHANGED
|
File without changes
|
|
File without changes
|
package/workflows/tdd-cycle.md
CHANGED
|
File without changes
|