gsd-lite 0.5.0 → 0.5.3

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 (44) 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 +0 -0
  5. package/agents/debugger.md +0 -0
  6. package/agents/executor.md +0 -0
  7. package/agents/researcher.md +0 -0
  8. package/agents/reviewer.md +0 -0
  9. package/commands/doctor.md +2 -2
  10. package/commands/prd.md +0 -0
  11. package/commands/resume.md +0 -0
  12. package/commands/start.md +9 -0
  13. package/commands/status.md +0 -0
  14. package/commands/stop.md +0 -0
  15. package/hooks/context-monitor.js +0 -0
  16. package/hooks/gsd-auto-update.cjs +0 -0
  17. package/hooks/gsd-context-monitor.cjs +0 -0
  18. package/hooks/gsd-session-init.cjs +33 -2
  19. package/hooks/gsd-statusline.cjs +7 -3
  20. package/hooks/hooks.json +0 -0
  21. package/install.js +28 -6
  22. package/launcher.js +0 -0
  23. package/package.json +1 -1
  24. package/references/anti-rationalization-full.md +0 -0
  25. package/references/evidence-spec.md +0 -0
  26. package/references/execution-loop.md +0 -0
  27. package/references/git-worktrees.md +0 -0
  28. package/references/questioning.md +0 -0
  29. package/references/review-classification.md +0 -0
  30. package/references/state-diagram.md +0 -0
  31. package/references/testing-patterns.md +0 -0
  32. package/src/schema.js +51 -5
  33. package/src/server.js +20 -8
  34. package/src/tools/orchestrator.js +0 -0
  35. package/src/tools/state.js +0 -0
  36. package/src/tools/verify.js +0 -0
  37. package/src/utils.js +0 -0
  38. package/uninstall.js +0 -0
  39. package/workflows/debugging.md +0 -0
  40. package/workflows/deviation-rules.md +0 -0
  41. package/workflows/execution-flow.md +0 -0
  42. package/workflows/research.md +0 -0
  43. package/workflows/review-cycle.md +0 -0
  44. 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.5.0",
16
+ "version": "0.5.3",
17
17
  "keywords": [
18
18
  "orchestration",
19
19
  "mcp",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gsd",
3
- "version": "0.5.0",
3
+ "version": "0.5.3",
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
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -19,7 +19,7 @@ Check if `.gsd/state.json` exists:
19
19
 
20
20
  ## STEP 2: MCP Server Health
21
21
 
22
- Call the `gsd health` MCP tool:
22
+ Call the `health` MCP tool:
23
23
  - If returns `status: "ok"`: record PASS with server version
24
24
  - If returns error or unreachable: record FAIL with error message
25
25
  - Note: if MCP server is not available at all (tool not found), record FAIL "MCP server not registered"
@@ -51,7 +51,7 @@ Check if `.gsd/.state-lock` exists:
51
51
 
52
52
  Check for update-related information:
53
53
  - Read `~/.claude/gsd/package.json` for installed version
54
- - Compare with the version from `gsd health` tool response
54
+ - Compare with the version from `health` tool response
55
55
  - If versions match: record PASS with version number
56
56
  - If mismatch: record WARN "Version mismatch: installed={x}, server={y}"
57
57
  - If `~/.claude/gsd/.update-pending` exists: record INFO "Update pending, will apply on next session"
package/commands/prd.md CHANGED
File without changes
File without changes
package/commands/start.md CHANGED
@@ -10,6 +10,15 @@ argument-hint: Optional feature or project description
10
10
 
11
11
  <process>
12
12
 
13
+ ## STEP 0 — 已有项目检测
14
+
15
+ 调用 `health` 工具(MCP tool 名称: health)。如果返回 state_exists=true:
16
+ - 告知用户: "检测到进行中的 GSD 项目。"
17
+ - 提供选项:
18
+ - (a) 恢复执行 → 转到 `/gsd:resume`
19
+ - (b) 重新开始 → 继续 STEP 1(现有 state.json 将被覆盖)
20
+ - 等待用户选择后再继续
21
+
13
22
  ## STEP 1 — 语言检测
14
23
 
15
24
  用户输入语言 = 后续所有输出语言。不需要读 CLAUDE.md 来判断语言。
File without changes
package/commands/stop.md CHANGED
File without changes
File without changes
File without changes
File without changes
@@ -66,7 +66,38 @@ setTimeout(() => process.exit(0), 4000).unref();
66
66
  }
67
67
  } catch { /* silent */ }
68
68
 
69
- // ── Phase 3: Show notification from previous background auto-install ──
69
+ // ── Phase 3: Self-heal .mcp.json in plugin directories ──
70
+ // If .mcp.json is missing (e.g. git operations deleted it), regenerate it
71
+ // so the plugin system can register the GSD MCP server.
72
+ try {
73
+ const pluginsPath = path.join(claudeDir, 'plugins', 'installed_plugins.json');
74
+ if (fs.existsSync(pluginsPath)) {
75
+ const plugins = JSON.parse(fs.readFileSync(pluginsPath, 'utf8'));
76
+ const gsdEntry = plugins.plugins?.['gsd@gsd']?.[0];
77
+ if (gsdEntry) {
78
+ const mcpContent = JSON.stringify({
79
+ mcpServers: {
80
+ gsd: { command: 'node', args: ['${CLAUDE_PLUGIN_ROOT}/launcher.js'] },
81
+ },
82
+ }, null, 2) + '\n';
83
+ // Check marketplace dir
84
+ const marketplaceDir = path.join(claudeDir, 'plugins', 'marketplaces', 'gsd');
85
+ const marketplaceMcp = path.join(marketplaceDir, '.mcp.json');
86
+ if (fs.existsSync(marketplaceDir) && !fs.existsSync(marketplaceMcp)) {
87
+ fs.writeFileSync(marketplaceMcp, mcpContent);
88
+ }
89
+ // Check plugin cache dir
90
+ if (gsdEntry.installPath) {
91
+ const cacheMcp = path.join(gsdEntry.installPath, '.mcp.json');
92
+ if (fs.existsSync(gsdEntry.installPath) && !fs.existsSync(cacheMcp)) {
93
+ fs.writeFileSync(cacheMcp, mcpContent);
94
+ }
95
+ }
96
+ }
97
+ }
98
+ } catch { /* silent */ }
99
+
100
+ // ── Phase 4: Show notification from previous background auto-update ──
70
101
  try {
71
102
  const notifPath = path.join(claudeDir, 'gsd', 'runtime', 'update-notification.json');
72
103
  if (fs.existsSync(notifPath)) {
@@ -82,7 +113,7 @@ setTimeout(() => process.exit(0), 4000).unref();
82
113
  }
83
114
  } catch { /* silent */ }
84
115
 
85
- // ── Phase 4: Spawn background auto-update (non-blocking) ──
116
+ // ── Phase 5: Spawn background auto-update (non-blocking) ──
86
117
  // Detached child handles check + download + install; throttled by shouldCheck()
87
118
  try {
88
119
  const { spawn } = require('node:child_process');
@@ -49,7 +49,10 @@ process.stdin.on('end', () => {
49
49
  if (state.current_task && state.current_phase) {
50
50
  const phase = (state.phases || []).find(p => p.id === state.current_phase);
51
51
  const t = phase?.todo?.find(t => t.id === state.current_task);
52
- if (t) task = `${t.id} ${t.name}`;
52
+ if (t) {
53
+ const name = t.name.length > 40 ? t.name.substring(0, 40) + '...' : t.name;
54
+ task = `${t.id} ${name}`;
55
+ }
53
56
  }
54
57
  } catch {
55
58
  // No state.json or parse error — skip task display
@@ -57,10 +60,11 @@ process.stdin.on('end', () => {
57
60
 
58
61
  // Context window display (USED percentage scaled to usable context)
59
62
  // Claude Code reserves ~16.5% for autocompact buffer (configurable via env)
60
- const AUTO_COMPACT_BUFFER_PCT = Number(process.env.GSD_AUTOCOMPACT_BUFFER) || 16.5;
63
+ const AUTO_COMPACT_BUFFER_PCT = Math.min(99, Math.max(0, Number(process.env.GSD_AUTOCOMPACT_BUFFER) || 16.5));
61
64
  let ctx = '';
62
65
  if (remaining != null) {
63
- const usableRemaining = Math.max(0, ((remaining - AUTO_COMPACT_BUFFER_PCT) / (100 - AUTO_COMPACT_BUFFER_PCT)) * 100);
66
+ const divisor = 100 - AUTO_COMPACT_BUFFER_PCT;
67
+ const usableRemaining = divisor > 0 ? Math.max(0, ((remaining - AUTO_COMPACT_BUFFER_PCT) / divisor) * 100) : 0;
64
68
  const used = Math.max(0, Math.min(100, Math.round(100 - usableRemaining)));
65
69
 
66
70
  // Write bridge file for context-monitor PostToolUse hook (skip if remaining unchanged)
package/hooks/hooks.json CHANGED
File without changes
package/install.js CHANGED
@@ -23,6 +23,16 @@ const HOOK_REGISTRY = [
23
23
 
24
24
  function log(msg) { console.log(msg); }
25
25
 
26
+ function isInstalledAsPlugin(claudeDir) {
27
+ try {
28
+ const pluginsPath = join(claudeDir, 'plugins', 'installed_plugins.json');
29
+ const data = JSON.parse(readFileSync(pluginsPath, 'utf-8'));
30
+ return !!data.plugins?.['gsd@gsd'];
31
+ } catch {
32
+ return false;
33
+ }
34
+ }
35
+
26
36
  function registerStatusLine(settings, statuslineScriptPath) {
27
37
  const command = `node ${JSON.stringify(statuslineScriptPath)}`;
28
38
  // Don't overwrite non-GSD statusLine
@@ -142,8 +152,11 @@ export function main() {
142
152
  log(' [dry-run] Would install runtime dependencies');
143
153
  }
144
154
 
145
- // 8. Register MCP server in settings.json
155
+ // 8. Register MCP server + hooks in settings.json
156
+ // When installed as a plugin, the plugin system handles MCP via .mcp.json,
157
+ // so we skip manual MCP registration to avoid name collisions.
146
158
  const settingsPath = join(CLAUDE_DIR, 'settings.json');
159
+ const isPluginInstall = isInstalledAsPlugin(CLAUDE_DIR);
147
160
  if (!DRY_RUN) {
148
161
  let settings = {};
149
162
  try {
@@ -157,10 +170,20 @@ export function main() {
157
170
  if (!settings.mcpServers) settings.mcpServers = {};
158
171
  // Remove legacy "gsd-lite" server entry from older versions
159
172
  delete settings.mcpServers['gsd-lite'];
160
- settings.mcpServers.gsd = {
161
- command: 'node',
162
- args: [join(RUNTIME_DIR, 'src', 'server.js')],
163
- };
173
+
174
+ if (isPluginInstall) {
175
+ // Plugin system handles MCP via .mcp.json — remove stale manual entry
176
+ if (settings.mcpServers.gsd) {
177
+ delete settings.mcpServers.gsd;
178
+ log(' ✓ Removed manual MCP entry (plugin .mcp.json handles registration)');
179
+ }
180
+ } else {
181
+ settings.mcpServers.gsd = {
182
+ command: 'node',
183
+ args: [join(RUNTIME_DIR, 'src', 'server.js')],
184
+ };
185
+ log(' ✓ MCP server registered in settings.json');
186
+ }
164
187
 
165
188
  // Register statusLine (top-level setting) and hooks
166
189
  if (!settings.hooks) settings.hooks = {};
@@ -174,7 +197,6 @@ export function main() {
174
197
  const tmpSettings = settingsPath + `.${process.pid}-${Date.now()}.tmp`;
175
198
  writeFileSync(tmpSettings, JSON.stringify(settings, null, 2) + '\n');
176
199
  renameSync(tmpSettings, settingsPath);
177
- log(' ✓ MCP server registered in settings.json');
178
200
  if (statusLineRegistered || hooksRegistered) {
179
201
  log(' ✓ GSD-Lite hooks registered in settings.json');
180
202
  }
package/launcher.js CHANGED
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gsd-lite",
3
- "version": "0.5.0",
3
+ "version": "0.5.3",
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
File without changes
File without changes
File without changes
package/src/schema.js CHANGED
@@ -98,8 +98,12 @@ export function validateResearchDecisionIndex(decisionIndex, requiredIds = []) {
98
98
  if ('source' in entry && (typeof entry.source !== 'string' || entry.source.length === 0)) {
99
99
  errors.push(`decision_index.${id}.source must be a non-empty string`);
100
100
  }
101
- if ('expires_at' in entry && (typeof entry.expires_at !== 'string' || entry.expires_at.length === 0)) {
102
- errors.push(`decision_index.${id}.expires_at must be a non-empty string`);
101
+ if ('expires_at' in entry) {
102
+ if (typeof entry.expires_at !== 'string' || entry.expires_at.length === 0) {
103
+ errors.push(`decision_index.${id}.expires_at must be a non-empty string`);
104
+ } else if (Number.isNaN(Date.parse(entry.expires_at))) {
105
+ errors.push(`decision_index.${id}.expires_at must be a valid ISO 8601 date (got "${entry.expires_at}")`);
106
+ }
103
107
  }
104
108
  }
105
109
 
@@ -169,6 +173,12 @@ export function validateStateUpdate(state, updates) {
169
173
  if (updates.current_review !== null && !isPlainObject(updates.current_review)) {
170
174
  errors.push('current_review must be an object or null');
171
175
  }
176
+ if (isPlainObject(updates.current_review) && 'scope' in updates.current_review) {
177
+ const validScopes = ['task', 'phase'];
178
+ if (!validScopes.includes(updates.current_review.scope)) {
179
+ errors.push(`current_review.scope must be one of: ${validScopes.join(', ')} (got "${updates.current_review.scope}")`);
180
+ }
181
+ }
172
182
  break;
173
183
  case 'git_head':
174
184
  if (updates.git_head !== null && typeof updates.git_head !== 'string') {
@@ -306,9 +316,12 @@ export function validateState(state) {
306
316
  if ('volatility' in state.research && !['low', 'medium', 'high'].includes(state.research.volatility)) {
307
317
  errors.push('research.volatility must be low|medium|high');
308
318
  }
309
- if ('expires_at' in state.research
310
- && (typeof state.research.expires_at !== 'string' || state.research.expires_at.length === 0)) {
311
- errors.push('research.expires_at must be a non-empty string');
319
+ if ('expires_at' in state.research) {
320
+ if (typeof state.research.expires_at !== 'string' || state.research.expires_at.length === 0) {
321
+ errors.push('research.expires_at must be a non-empty string');
322
+ } else if (Number.isNaN(Date.parse(state.research.expires_at))) {
323
+ errors.push(`research.expires_at must be a valid ISO 8601 date (got "${state.research.expires_at}")`);
324
+ }
312
325
  }
313
326
  if ('files' in state.research && !Array.isArray(state.research.files)) {
314
327
  errors.push('research.files must be an array');
@@ -335,6 +348,12 @@ export function validateState(state) {
335
348
  if (state.current_review !== null && !isPlainObject(state.current_review)) {
336
349
  errors.push('current_review must be an object or null');
337
350
  }
351
+ if (isPlainObject(state.current_review) && 'scope' in state.current_review) {
352
+ const validScopes = ['task', 'phase'];
353
+ if (!validScopes.includes(state.current_review.scope)) {
354
+ errors.push(`current_review.scope must be one of: ${validScopes.join(', ')} (got "${state.current_review.scope}")`);
355
+ }
356
+ }
338
357
  if (!isPlainObject(state.evidence)) {
339
358
  errors.push('evidence must be an object');
340
359
  } else {
@@ -669,6 +688,33 @@ export function createInitialState({ project, phases }) {
669
688
  }
670
689
  }
671
690
 
691
+ // Validate requires references: must be structured objects with valid targets
692
+ for (const [pi, p] of phases.entries()) {
693
+ for (const [ti, t] of (p.tasks || []).entries()) {
694
+ const taskId = `${pi + 1}.${t.index ?? (ti + 1)}`;
695
+ for (const dep of (t.requires || [])) {
696
+ if (typeof dep === 'string') {
697
+ return { error: true, message: `Task ${taskId}: requires entry "${dep}" must be an object {kind: "task"|"phase", id: "..."}, not a string` };
698
+ }
699
+ if (!isPlainObject(dep) || !dep.kind || !dep.id) {
700
+ return { error: true, message: `Task ${taskId}: requires entries must be objects with kind ("task"|"phase") and id` };
701
+ }
702
+ if (!['task', 'phase'].includes(dep.kind)) {
703
+ return { error: true, message: `Task ${taskId}: requires entry kind must be "task" or "phase" (got "${dep.kind}")` };
704
+ }
705
+ if (dep.kind === 'task' && !seenIds.has(String(dep.id))) {
706
+ return { error: true, message: `Task ${taskId}: requires references non-existent task "${dep.id}" (valid IDs: ${[...seenIds].join(', ')})` };
707
+ }
708
+ if (dep.kind === 'phase') {
709
+ const phaseId = Number(dep.id);
710
+ if (!Number.isFinite(phaseId) || phaseId < 1 || phaseId > phases.length) {
711
+ return { error: true, message: `Task ${taskId}: requires references non-existent phase "${dep.id}" (valid: 1-${phases.length})` };
712
+ }
713
+ }
714
+ }
715
+ }
716
+ }
717
+
672
718
  // M-7: Detect circular dependencies within each phase (Kahn's algorithm)
673
719
  for (const [pi, p] of phases.entries()) {
674
720
  const tasks = p.tasks || [];
package/src/server.js CHANGED
@@ -52,7 +52,7 @@ const TOOLS = [
52
52
  name: { type: 'string', description: 'Task name (required)' },
53
53
  index: { type: 'number', description: 'Task index within phase (default: auto)' },
54
54
  level: { type: 'string', description: 'Complexity level: L0/L1/L2/L3 (default: L1)' },
55
- requires: { type: 'array', description: 'Dependency list (default: [])' },
55
+ requires: { type: 'array', description: 'Dependencies: [{kind: "task"|"phase", id: "1.1", gate?: "checkpoint"|"accepted"|"phase_complete"}] (default: [])' },
56
56
  review_required: { type: 'boolean', description: 'Whether review is needed (default: true)' },
57
57
  verification_required: { type: 'boolean', description: 'Whether verification is needed (default: true)' },
58
58
  },
@@ -105,7 +105,7 @@ const TOOLS = [
105
105
  phase_id: { type: 'number', description: 'Phase number to complete' },
106
106
  verification: {
107
107
  type: 'object',
108
- description: 'Optional precomputed verification result object with lint/typecheck/test exit codes',
108
+ description: 'Optional precomputed verification: {lint: {exit_code: number}, typecheck: {exit_code: number}, test: {exit_code: number}} — all three keys required, exit_code 0 = passed',
109
109
  },
110
110
  run_verify: {
111
111
  type: 'boolean',
@@ -133,7 +133,10 @@ const TOOLS = [
133
133
  inputSchema: {
134
134
  type: 'object',
135
135
  properties: {
136
- result: { type: 'object', description: 'Executor result payload' },
136
+ result: {
137
+ type: 'object',
138
+ description: 'Executor result: {task_id: string, outcome: "checkpointed"|"blocked"|"failed", summary: string, checkpoint_commit: string|null, files_changed: string[], decisions: string[], blockers: object[], contract_changed: boolean, evidence: object[]}',
139
+ },
137
140
  },
138
141
  required: ['result'],
139
142
  },
@@ -144,7 +147,10 @@ const TOOLS = [
144
147
  inputSchema: {
145
148
  type: 'object',
146
149
  properties: {
147
- result: { type: 'object', description: 'Debugger result payload' },
150
+ result: {
151
+ type: 'object',
152
+ description: 'Debugger result: {task_id: string, outcome: "root_cause_found"|"fix_suggested"|"failed", root_cause: string, evidence: object[], hypothesis_tested: [{hypothesis: string, result: "confirmed"|"rejected", evidence: string}], fix_direction: string, fix_attempts: integer, blockers: object[], architecture_concern: boolean}',
153
+ },
148
154
  },
149
155
  required: ['result'],
150
156
  },
@@ -155,9 +161,12 @@ const TOOLS = [
155
161
  inputSchema: {
156
162
  type: 'object',
157
163
  properties: {
158
- result: { type: 'object', description: 'Researcher result payload' },
159
- decision_index: { type: 'object', description: 'Decision index keyed by decision id' },
160
- artifacts: { type: 'object', description: 'Markdown artifact contents keyed by file name' },
164
+ result: {
165
+ type: 'object',
166
+ description: 'Researcher result: {decision_ids: string[], volatility: "low"|"medium"|"high", expires_at: ISO8601 string, sources: [{id: string, type: string, ref: string}]}',
167
+ },
168
+ decision_index: { type: 'object', description: 'Decision index keyed by decision id, each value: {summary: string, source?: string, expires_at?: ISO8601}' },
169
+ artifacts: { type: 'object', description: 'Markdown contents keyed by filename: {STACK.md, ARCHITECTURE.md, PITFALLS.md, SUMMARY.md}' },
161
170
  },
162
171
  required: ['result', 'decision_index', 'artifacts'],
163
172
  },
@@ -168,7 +177,10 @@ const TOOLS = [
168
177
  inputSchema: {
169
178
  type: 'object',
170
179
  properties: {
171
- result: { type: 'object', description: 'Reviewer result payload' },
180
+ result: {
181
+ type: 'object',
182
+ description: 'Reviewer result: {scope: "task"|"phase", scope_id: string|number, review_level: "L2"|"L1-batch", spec_passed: boolean, quality_passed: boolean, critical_issues: object[], important_issues: object[], minor_issues: object[], accepted_tasks: string[], rework_tasks: string[], evidence: object[]}',
183
+ },
172
184
  },
173
185
  required: ['result'],
174
186
  },
File without changes
File without changes
File without changes
package/src/utils.js CHANGED
File without changes
package/uninstall.js CHANGED
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes