gsd-lite 0.5.2 → 0.5.4
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/commands/doctor.md +2 -2
- package/commands/start.md +1 -1
- package/hooks/gsd-statusline.cjs +4 -3
- package/package.json +1 -1
- package/src/schema.js +67 -2
- package/src/server.js +27 -9
- package/src/tools/state.js +7 -4
|
@@ -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.4",
|
|
17
17
|
"keywords": [
|
|
18
18
|
"orchestration",
|
|
19
19
|
"mcp",
|
package/commands/doctor.md
CHANGED
|
@@ -19,7 +19,7 @@ Check if `.gsd/state.json` exists:
|
|
|
19
19
|
|
|
20
20
|
## STEP 2: MCP Server Health
|
|
21
21
|
|
|
22
|
-
Call the `
|
|
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 `
|
|
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/start.md
CHANGED
|
@@ -12,7 +12,7 @@ argument-hint: Optional feature or project description
|
|
|
12
12
|
|
|
13
13
|
## STEP 0 — 已有项目检测
|
|
14
14
|
|
|
15
|
-
调用 `
|
|
15
|
+
调用 `health` 工具(MCP tool 名称: health)。如果返回 state_exists=true:
|
|
16
16
|
- 告知用户: "检测到进行中的 GSD 项目。"
|
|
17
17
|
- 提供选项:
|
|
18
18
|
- (a) 恢复执行 → 转到 `/gsd:resume`
|
package/hooks/gsd-statusline.cjs
CHANGED
|
@@ -60,10 +60,11 @@ process.stdin.on('end', () => {
|
|
|
60
60
|
|
|
61
61
|
// Context window display (USED percentage scaled to usable context)
|
|
62
62
|
// Claude Code reserves ~16.5% for autocompact buffer (configurable via env)
|
|
63
|
-
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));
|
|
64
64
|
let ctx = '';
|
|
65
65
|
if (remaining != null) {
|
|
66
|
-
const
|
|
66
|
+
const divisor = 100 - AUTO_COMPACT_BUFFER_PCT;
|
|
67
|
+
const usableRemaining = divisor > 0 ? Math.max(0, ((remaining - AUTO_COMPACT_BUFFER_PCT) / divisor) * 100) : 0;
|
|
67
68
|
const used = Math.max(0, Math.min(100, Math.round(100 - usableRemaining)));
|
|
68
69
|
|
|
69
70
|
// Write bridge file for context-monitor PostToolUse hook (skip if remaining unchanged)
|
|
@@ -73,7 +74,7 @@ process.stdin.on('end', () => {
|
|
|
73
74
|
let needsWrite = true;
|
|
74
75
|
try {
|
|
75
76
|
const existing = JSON.parse(fs.readFileSync(bridgePath, 'utf8'));
|
|
76
|
-
if (existing.remaining_percentage === remaining) needsWrite = false;
|
|
77
|
+
if (existing.remaining_percentage === remaining && existing.has_gsd === hasGsd) needsWrite = false;
|
|
77
78
|
} catch { /* no existing file */ }
|
|
78
79
|
if (needsWrite) {
|
|
79
80
|
const tmpBridge = bridgePath + '.tmp';
|
package/package.json
CHANGED
package/src/schema.js
CHANGED
|
@@ -17,6 +17,23 @@ export const WORKFLOW_MODES = [
|
|
|
17
17
|
'failed',
|
|
18
18
|
];
|
|
19
19
|
|
|
20
|
+
// Valid workflow_mode transitions — unlisted transitions are rejected by validateStateUpdate.
|
|
21
|
+
// Terminal states (completed, failed) are guarded separately by the FROM-terminal check in state-update.
|
|
22
|
+
export const WORKFLOW_TRANSITIONS = {
|
|
23
|
+
planning: ['executing_task', 'paused_by_user'],
|
|
24
|
+
executing_task: ['planning', 'reviewing_task', 'reviewing_phase', 'awaiting_user', 'awaiting_clear', 'paused_by_user', 'reconcile_workspace', 'replan_required', 'research_refresh_needed', 'failed'],
|
|
25
|
+
reviewing_task: ['executing_task', 'reviewing_phase', 'awaiting_user', 'awaiting_clear', 'paused_by_user', 'failed'],
|
|
26
|
+
reviewing_phase: ['executing_task', 'awaiting_user', 'awaiting_clear', 'paused_by_user', 'completed', 'failed'],
|
|
27
|
+
awaiting_user: ['executing_task', 'reviewing_task', 'reviewing_phase', 'paused_by_user', 'awaiting_clear'],
|
|
28
|
+
awaiting_clear: ['executing_task', 'paused_by_user'],
|
|
29
|
+
paused_by_user: ['executing_task', 'awaiting_user', 'awaiting_clear', 'reconcile_workspace', 'replan_required', 'research_refresh_needed', 'reviewing_task', 'reviewing_phase'],
|
|
30
|
+
reconcile_workspace: ['executing_task', 'paused_by_user'],
|
|
31
|
+
replan_required: ['executing_task', 'paused_by_user'],
|
|
32
|
+
research_refresh_needed: ['executing_task', 'reviewing_task', 'reviewing_phase', 'paused_by_user'],
|
|
33
|
+
completed: [], // terminal — guarded by FROM-terminal check
|
|
34
|
+
failed: [], // terminal — guarded by FROM-terminal check
|
|
35
|
+
};
|
|
36
|
+
|
|
20
37
|
export const TASK_LIFECYCLE = {
|
|
21
38
|
pending: ['running', 'blocked'],
|
|
22
39
|
running: ['checkpointed', 'blocked', 'failed', 'accepted'], // accepted: auto-accept for L0/review_required=false (atomic, skips checkpointed)
|
|
@@ -154,11 +171,28 @@ export function validateStateUpdate(state, updates) {
|
|
|
154
171
|
|
|
155
172
|
for (const key of Object.keys(updates)) {
|
|
156
173
|
switch (key) {
|
|
157
|
-
case 'workflow_mode':
|
|
174
|
+
case 'workflow_mode': {
|
|
158
175
|
if (!WORKFLOW_MODES.includes(updates.workflow_mode)) {
|
|
159
176
|
errors.push(`Invalid workflow_mode: ${updates.workflow_mode}`);
|
|
177
|
+
break;
|
|
178
|
+
}
|
|
179
|
+
// Transition whitelist — reject unlisted transitions
|
|
180
|
+
const currentMode = state.workflow_mode;
|
|
181
|
+
if (currentMode && updates.workflow_mode !== currentMode) {
|
|
182
|
+
const allowed = WORKFLOW_TRANSITIONS[currentMode];
|
|
183
|
+
if (allowed && !allowed.includes(updates.workflow_mode)) {
|
|
184
|
+
errors.push(`Invalid workflow_mode transition: '${currentMode}' → '${updates.workflow_mode}' (allowed: ${allowed.join(', ') || 'none (terminal state)'})`);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
// Guard: 'completed' requires all phases accepted
|
|
188
|
+
if (updates.workflow_mode === 'completed' && Array.isArray(state.phases)) {
|
|
189
|
+
const unfinished = state.phases.filter(p => p.lifecycle !== 'accepted');
|
|
190
|
+
if (unfinished.length > 0) {
|
|
191
|
+
errors.push(`Cannot set workflow_mode to 'completed': ${unfinished.length} phase(s) not accepted (${unfinished.map(p => `${p.id}:${p.lifecycle}`).join(', ')})`);
|
|
192
|
+
}
|
|
160
193
|
}
|
|
161
194
|
break;
|
|
195
|
+
}
|
|
162
196
|
case 'current_phase':
|
|
163
197
|
if (!Number.isFinite(updates.current_phase)) {
|
|
164
198
|
errors.push('current_phase must be a finite number');
|
|
@@ -383,8 +417,12 @@ export function validateState(state) {
|
|
|
383
417
|
}
|
|
384
418
|
}
|
|
385
419
|
}
|
|
386
|
-
// P2-9: workflow_mode consistency — completed project
|
|
420
|
+
// P2-9: workflow_mode consistency — completed project requires all phases accepted
|
|
387
421
|
if (state.workflow_mode === 'completed' && Array.isArray(state.phases)) {
|
|
422
|
+
const unfinishedPhases = state.phases.filter(p => p.lifecycle !== 'accepted');
|
|
423
|
+
if (unfinishedPhases.length > 0) {
|
|
424
|
+
errors.push(`Completed project has ${unfinishedPhases.length} unfinished phase(s): ${unfinishedPhases.map(p => `${p.id}:${p.lifecycle}`).join(', ')}`);
|
|
425
|
+
}
|
|
388
426
|
for (const phase of state.phases) {
|
|
389
427
|
for (const task of (phase.todo || [])) {
|
|
390
428
|
if (task.lifecycle === 'running') {
|
|
@@ -688,6 +726,33 @@ export function createInitialState({ project, phases }) {
|
|
|
688
726
|
}
|
|
689
727
|
}
|
|
690
728
|
|
|
729
|
+
// Validate requires references: must be structured objects with valid targets
|
|
730
|
+
for (const [pi, p] of phases.entries()) {
|
|
731
|
+
for (const [ti, t] of (p.tasks || []).entries()) {
|
|
732
|
+
const taskId = `${pi + 1}.${t.index ?? (ti + 1)}`;
|
|
733
|
+
for (const dep of (t.requires || [])) {
|
|
734
|
+
if (typeof dep === 'string') {
|
|
735
|
+
return { error: true, message: `Task ${taskId}: requires entry "${dep}" must be an object {kind: "task"|"phase", id: "..."}, not a string` };
|
|
736
|
+
}
|
|
737
|
+
if (!isPlainObject(dep) || !dep.kind || !dep.id) {
|
|
738
|
+
return { error: true, message: `Task ${taskId}: requires entries must be objects with kind ("task"|"phase") and id` };
|
|
739
|
+
}
|
|
740
|
+
if (!['task', 'phase'].includes(dep.kind)) {
|
|
741
|
+
return { error: true, message: `Task ${taskId}: requires entry kind must be "task" or "phase" (got "${dep.kind}")` };
|
|
742
|
+
}
|
|
743
|
+
if (dep.kind === 'task' && !seenIds.has(String(dep.id))) {
|
|
744
|
+
return { error: true, message: `Task ${taskId}: requires references non-existent task "${dep.id}" (valid IDs: ${[...seenIds].join(', ')})` };
|
|
745
|
+
}
|
|
746
|
+
if (dep.kind === 'phase') {
|
|
747
|
+
const phaseId = Number(dep.id);
|
|
748
|
+
if (!Number.isFinite(phaseId) || phaseId < 1 || phaseId > phases.length) {
|
|
749
|
+
return { error: true, message: `Task ${taskId}: requires references non-existent phase "${dep.id}" (valid: 1-${phases.length})` };
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
|
|
691
756
|
// M-7: Detect circular dependencies within each phase (Kahn's algorithm)
|
|
692
757
|
for (const [pi, p] of phases.entries()) {
|
|
693
758
|
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: '
|
|
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
|
},
|
|
@@ -90,7 +90,7 @@ const TOOLS = [
|
|
|
90
90
|
properties: {
|
|
91
91
|
updates: {
|
|
92
92
|
type: 'object',
|
|
93
|
-
description: 'Key-value pairs of canonical fields
|
|
93
|
+
description: 'Key-value pairs of canonical fields: workflow_mode, current_phase, current_task, current_review, git_head, plan_version, schema_version, total_phases, project, decisions, context, evidence, research',
|
|
94
94
|
},
|
|
95
95
|
},
|
|
96
96
|
required: ['updates'],
|
|
@@ -105,7 +105,13 @@ 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
|
|
108
|
+
description: 'Optional precomputed verification — all three keys required, exit_code 0 = passed',
|
|
109
|
+
properties: {
|
|
110
|
+
lint: { type: 'object', properties: { exit_code: { type: 'number' } }, required: ['exit_code'] },
|
|
111
|
+
typecheck: { type: 'object', properties: { exit_code: { type: 'number' } }, required: ['exit_code'] },
|
|
112
|
+
test: { type: 'object', properties: { exit_code: { type: 'number' } }, required: ['exit_code'] },
|
|
113
|
+
},
|
|
114
|
+
required: ['lint', 'typecheck', 'test'],
|
|
109
115
|
},
|
|
110
116
|
run_verify: {
|
|
111
117
|
type: 'boolean',
|
|
@@ -133,7 +139,10 @@ const TOOLS = [
|
|
|
133
139
|
inputSchema: {
|
|
134
140
|
type: 'object',
|
|
135
141
|
properties: {
|
|
136
|
-
result: {
|
|
142
|
+
result: {
|
|
143
|
+
type: 'object',
|
|
144
|
+
description: 'Executor result: {task_id: string, outcome: "checkpointed"|"blocked"|"failed", summary: string, checkpoint_commit: string|null, files_changed: string[], decisions: [{id, summary, rationale}], blockers: [{description}], contract_changed: boolean, evidence: string[]}',
|
|
145
|
+
},
|
|
137
146
|
},
|
|
138
147
|
required: ['result'],
|
|
139
148
|
},
|
|
@@ -144,7 +153,10 @@ const TOOLS = [
|
|
|
144
153
|
inputSchema: {
|
|
145
154
|
type: 'object',
|
|
146
155
|
properties: {
|
|
147
|
-
result: {
|
|
156
|
+
result: {
|
|
157
|
+
type: 'object',
|
|
158
|
+
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}',
|
|
159
|
+
},
|
|
148
160
|
},
|
|
149
161
|
required: ['result'],
|
|
150
162
|
},
|
|
@@ -155,9 +167,12 @@ const TOOLS = [
|
|
|
155
167
|
inputSchema: {
|
|
156
168
|
type: 'object',
|
|
157
169
|
properties: {
|
|
158
|
-
result: {
|
|
159
|
-
|
|
160
|
-
|
|
170
|
+
result: {
|
|
171
|
+
type: 'object',
|
|
172
|
+
description: 'Researcher result: {decision_ids: string[], volatility: "low"|"medium"|"high", expires_at: ISO8601 string, sources: [{id: string, type: string, ref: string}]}',
|
|
173
|
+
},
|
|
174
|
+
decision_index: { type: 'object', description: 'Decision index keyed by decision id, each value: {summary: string, source?: string, expires_at?: ISO8601}' },
|
|
175
|
+
artifacts: { type: 'object', description: 'Markdown contents keyed by filename: {STACK.md, ARCHITECTURE.md, PITFALLS.md, SUMMARY.md}' },
|
|
161
176
|
},
|
|
162
177
|
required: ['result', 'decision_index', 'artifacts'],
|
|
163
178
|
},
|
|
@@ -168,7 +183,10 @@ const TOOLS = [
|
|
|
168
183
|
inputSchema: {
|
|
169
184
|
type: 'object',
|
|
170
185
|
properties: {
|
|
171
|
-
result: {
|
|
186
|
+
result: {
|
|
187
|
+
type: 'object',
|
|
188
|
+
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[]}',
|
|
189
|
+
},
|
|
172
190
|
},
|
|
173
191
|
required: ['result'],
|
|
174
192
|
},
|
package/src/tools/state.js
CHANGED
|
@@ -341,10 +341,13 @@ function verificationPassed(verification) {
|
|
|
341
341
|
|
|
342
342
|
function verificationSummary(verification) {
|
|
343
343
|
if (!verification || typeof verification !== 'object') return 'no verification details';
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
344
|
+
const parts = ['lint', 'typecheck', 'test'].map((key) => {
|
|
345
|
+
const v = verification[key];
|
|
346
|
+
if (!v) return `${key}:missing`;
|
|
347
|
+
if (typeof v !== 'object' || !('exit_code' in v)) return `${key}:invalid-format (expected {exit_code: number})`;
|
|
348
|
+
return `${key}:${v.exit_code === 0 ? 'pass' : `fail(${v.exit_code})`}`;
|
|
349
|
+
});
|
|
350
|
+
return parts.join(', ');
|
|
348
351
|
}
|
|
349
352
|
|
|
350
353
|
export async function phaseComplete({
|