gsd-lite 0.5.8 → 0.5.10
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.
|
|
16
|
+
"version": "0.5.10",
|
|
17
17
|
"keywords": [
|
|
18
18
|
"orchestration",
|
|
19
19
|
"mcp",
|
|
@@ -227,6 +227,14 @@ function isDevMode() {
|
|
|
227
227
|
|
|
228
228
|
function getInstallMode() {
|
|
229
229
|
if (isDevMode()) return 'dev';
|
|
230
|
+
// Check if installed as a Claude Code plugin (installed_plugins.json has gsd entry)
|
|
231
|
+
// Hook files live at ~/.claude/hooks/ so pluginRoot (=__dirname/..) equals claudeDir,
|
|
232
|
+
// but that doesn't mean it's a manual install — check the plugin registry first.
|
|
233
|
+
try {
|
|
234
|
+
const pluginsFile = path.join(claudeDir, 'plugins', 'installed_plugins.json');
|
|
235
|
+
const plugins = JSON.parse(fs.readFileSync(pluginsFile, 'utf8'));
|
|
236
|
+
if (plugins.plugins?.['gsd@gsd']?.[0]) return 'plugin';
|
|
237
|
+
} catch { /* fall through */ }
|
|
230
238
|
return path.resolve(pluginRoot) === path.resolve(claudeDir)
|
|
231
239
|
? 'manual'
|
|
232
240
|
: 'plugin';
|
package/package.json
CHANGED
package/src/server.js
CHANGED
|
@@ -8,20 +8,30 @@ import { init, read, update, phaseComplete } from './tools/state.js';
|
|
|
8
8
|
const _require = createRequire(import.meta.url);
|
|
9
9
|
const PKG_VERSION = _require('../package.json').version;
|
|
10
10
|
|
|
11
|
-
//
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
// Version drift detection: warn when running code differs from disk or plugin cache
|
|
12
|
+
const _fs = _require('node:fs');
|
|
13
|
+
const _path = _require('node:path');
|
|
14
|
+
const _os = _require('node:os');
|
|
15
15
|
function _checkVersionDrift() {
|
|
16
|
-
if (!_isDevMode) return null;
|
|
17
16
|
try {
|
|
18
|
-
//
|
|
17
|
+
// Strategy 1: Check package.json on disk (dev mode)
|
|
19
18
|
const pkgPath = _require.resolve('../package.json');
|
|
20
19
|
delete _require.cache[pkgPath];
|
|
21
20
|
const diskVersion = _require('../package.json').version;
|
|
22
21
|
if (diskVersion !== PKG_VERSION) {
|
|
23
22
|
return `⚠️ GSD server running v${PKG_VERSION} but code on disk is v${diskVersion}. Run /mcp to restart.`;
|
|
24
23
|
}
|
|
24
|
+
|
|
25
|
+
// Strategy 2: Check plugin cache registry (plugin mode)
|
|
26
|
+
const claudeDir = process.env.CLAUDE_CONFIG_DIR || _path.join(_os.homedir(), '.claude');
|
|
27
|
+
const pluginsFile = _path.join(claudeDir, 'plugins', 'installed_plugins.json');
|
|
28
|
+
if (_fs.existsSync(pluginsFile)) {
|
|
29
|
+
const plugins = JSON.parse(_fs.readFileSync(pluginsFile, 'utf8'));
|
|
30
|
+
const gsdEntry = plugins.plugins?.['gsd@gsd']?.[0];
|
|
31
|
+
if (gsdEntry?.version && gsdEntry.version !== PKG_VERSION) {
|
|
32
|
+
return `⚠️ GSD server running v${PKG_VERSION} but plugin registry has v${gsdEntry.version}. Run /mcp to restart.`;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
25
35
|
} catch { /* ignore */ }
|
|
26
36
|
return null;
|
|
27
37
|
}
|
|
@@ -82,6 +92,7 @@ const TOOLS = [
|
|
|
82
92
|
},
|
|
83
93
|
},
|
|
84
94
|
research: { type: 'boolean', description: 'Whether research directory is needed' },
|
|
95
|
+
force: { type: 'boolean', description: 'Force reinitialize when state.json already exists (default: false)' },
|
|
85
96
|
},
|
|
86
97
|
required: ['project', 'phases'],
|
|
87
98
|
},
|
|
@@ -1112,7 +1112,7 @@ export async function handleReviewerResult({ result, basePath = process.cwd() }
|
|
|
1112
1112
|
if (state.error) return state;
|
|
1113
1113
|
|
|
1114
1114
|
const phase = result.scope === 'phase'
|
|
1115
|
-
? (state.phases || []).find((p) => p.id === result.scope_id) ||
|
|
1115
|
+
? (state.phases || []).find((p) => p.id === Number(result.scope_id)) || null
|
|
1116
1116
|
: getCurrentPhase(state);
|
|
1117
1117
|
if (!phase) {
|
|
1118
1118
|
return { error: true, message: `Phase not found for scope_id ${result.scope_id}` };
|
|
@@ -1141,6 +1141,7 @@ export async function handleReviewerResult({ result, basePath = process.cwd() }
|
|
|
1141
1141
|
taskPatches.push({
|
|
1142
1142
|
id: taskId,
|
|
1143
1143
|
lifecycle: 'needs_revalidation',
|
|
1144
|
+
retry_count: 0,
|
|
1144
1145
|
evidence_refs: [],
|
|
1145
1146
|
last_review_feedback: taskIssues.length > 0 ? taskIssues : null,
|
|
1146
1147
|
});
|
|
@@ -1157,7 +1158,7 @@ export async function handleReviewerResult({ result, basePath = process.cwd() }
|
|
|
1157
1158
|
// Collect propagation-affected task patches (tasks mutated in-memory by propagateInvalidation)
|
|
1158
1159
|
for (const task of (phase.todo || [])) {
|
|
1159
1160
|
if (task.lifecycle === 'needs_revalidation' && !taskPatches.some((p) => p.id === task.id)) {
|
|
1160
|
-
taskPatches.push({ id: task.id, lifecycle: 'needs_revalidation', evidence_refs: [] });
|
|
1161
|
+
taskPatches.push({ id: task.id, lifecycle: 'needs_revalidation', retry_count: 0, evidence_refs: [] });
|
|
1161
1162
|
}
|
|
1162
1163
|
}
|
|
1163
1164
|
|
package/src/tools/state.js
CHANGED
|
@@ -88,6 +88,9 @@ export async function init({ project, phases, research, force = false, basePath
|
|
|
88
88
|
if (!Array.isArray(phases)) {
|
|
89
89
|
return { error: true, code: ERROR_CODES.INVALID_INPUT, message: 'phases must be an array' };
|
|
90
90
|
}
|
|
91
|
+
if (phases.length === 0) {
|
|
92
|
+
return { error: true, code: ERROR_CODES.INVALID_INPUT, message: 'phases must contain at least one phase' };
|
|
93
|
+
}
|
|
91
94
|
const gsdDir = join(basePath, '.gsd');
|
|
92
95
|
const statePath = join(gsdDir, 'state.json');
|
|
93
96
|
ensureLockPathFromStatePath(statePath);
|