gsd-lite 0.5.0 → 0.5.2

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.0",
16
+ "version": "0.5.2",
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.2",
4
4
  "description": "AI orchestration tool for Claude Code — GSD management shell + Superpowers quality core",
5
5
  "author": {
6
6
  "name": "sdsrss",
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
+ 调用 `gsd 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 来判断语言。
@@ -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
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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gsd-lite",
3
- "version": "0.5.0",
3
+ "version": "0.5.2",
4
4
  "description": "AI orchestration tool for Claude Code — GSD management shell + Superpowers quality core",
5
5
  "type": "module",
6
6
  "bin": {
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 {