gsdd-cli 0.18.5 → 0.19.1
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/LICENSE +21 -21
- package/README.md +610 -608
- package/agents/DISTILLATION.md +421 -421
- package/agents/README.md +62 -62
- package/agents/approach-explorer.md +370 -361
- package/agents/debugger.md +82 -82
- package/agents/executor.md +473 -394
- package/agents/integration-checker.md +318 -318
- package/agents/mapper.md +103 -103
- package/agents/planner.md +342 -313
- package/agents/researcher.md +84 -84
- package/agents/roadmapper.md +296 -296
- package/agents/synthesizer.md +236 -236
- package/agents/verifier.md +337 -337
- package/bin/adapters/agents.mjs +34 -34
- package/bin/adapters/claude.mjs +193 -191
- package/bin/adapters/codex.mjs +85 -85
- package/bin/adapters/index.mjs +20 -20
- package/bin/adapters/opencode.mjs +280 -278
- package/bin/gsdd.mjs +123 -116
- package/bin/lib/cli-utils.mjs +28 -28
- package/bin/lib/evidence-contract.mjs +325 -112
- package/bin/lib/file-ops.mjs +186 -144
- package/bin/lib/health-truth.mjs +196 -178
- package/bin/lib/health.mjs +246 -226
- package/bin/lib/init-flow.mjs +247 -231
- package/bin/lib/init-prompts.mjs +248 -247
- package/bin/lib/init-runtime.mjs +193 -190
- package/bin/lib/init.mjs +17 -17
- package/bin/lib/lifecycle-preflight.mjs +760 -326
- package/bin/lib/lifecycle-state.mjs +356 -267
- package/bin/lib/manifest.mjs +116 -114
- package/bin/lib/models.mjs +411 -411
- package/bin/lib/phase.mjs +365 -358
- package/bin/lib/plan-constants.mjs +35 -30
- package/bin/lib/provenance.mjs +109 -106
- package/bin/lib/rendering.mjs +119 -83
- package/bin/lib/runtime-freshness.mjs +214 -214
- package/bin/lib/session-fingerprint.mjs +91 -14
- package/bin/lib/templates.mjs +225 -224
- package/bin/lib/workspace-root.mjs +2 -1
- package/distilled/DESIGN.md +2461 -2323
- package/distilled/EVIDENCE-INDEX.md +418 -392
- package/distilled/README.md +196 -193
- package/distilled/SKILL.md +86 -85
- package/distilled/templates/agents.block.md +21 -21
- package/distilled/templates/agents.md +6 -6
- package/distilled/templates/approach.md +272 -232
- package/distilled/templates/auth-matrix.md +78 -78
- package/distilled/templates/brownfield-change/CHANGE.md +99 -0
- package/distilled/templates/brownfield-change/HANDOFF.md +38 -0
- package/distilled/templates/brownfield-change/VERIFICATION.md +56 -0
- package/distilled/templates/codebase/architecture.md +110 -110
- package/distilled/templates/codebase/concerns.md +95 -95
- package/distilled/templates/codebase/conventions.md +193 -193
- package/distilled/templates/codebase/stack.md +96 -96
- package/distilled/templates/delegates/approach-explorer.md +28 -25
- package/distilled/templates/delegates/mapper-arch.md +26 -26
- package/distilled/templates/delegates/mapper-concerns.md +27 -27
- package/distilled/templates/delegates/mapper-quality.md +28 -28
- package/distilled/templates/delegates/mapper-tech.md +25 -25
- package/distilled/templates/delegates/plan-checker.md +78 -68
- package/distilled/templates/delegates/researcher-architecture.md +30 -30
- package/distilled/templates/delegates/researcher-features.md +30 -30
- package/distilled/templates/delegates/researcher-pitfalls.md +30 -30
- package/distilled/templates/delegates/researcher-stack.md +30 -30
- package/distilled/templates/delegates/researcher-synthesizer.md +31 -31
- package/distilled/templates/research/architecture.md +57 -57
- package/distilled/templates/research/features.md +23 -23
- package/distilled/templates/research/pitfalls.md +46 -46
- package/distilled/templates/research/stack.md +45 -45
- package/distilled/templates/research/summary.md +67 -67
- package/distilled/templates/roadmap.md +74 -62
- package/distilled/templates/spec.md +110 -110
- package/distilled/workflows/audit-milestone.md +303 -271
- package/distilled/workflows/complete-milestone.md +349 -332
- package/distilled/workflows/execute.md +457 -450
- package/distilled/workflows/map-codebase.md +253 -253
- package/distilled/workflows/new-milestone.md +242 -238
- package/distilled/workflows/new-project.md +398 -398
- package/distilled/workflows/pause.md +160 -156
- package/distilled/workflows/plan-milestone-gaps.md +183 -183
- package/distilled/workflows/plan.md +454 -448
- package/distilled/workflows/progress.md +227 -223
- package/distilled/workflows/quick.md +351 -347
- package/distilled/workflows/resume.md +220 -212
- package/distilled/workflows/verify-work.md +260 -260
- package/distilled/workflows/verify.md +431 -429
- package/docs/BROWNFIELD-PROOF.md +95 -95
- package/docs/RUNTIME-SUPPORT.md +80 -69
- package/docs/USER-GUIDE.md +394 -386
- package/docs/VERIFICATION-DISCIPLINE.md +59 -59
- package/docs/claude/context-monitor.md +98 -98
- package/docs/proof/consumer-node-cli/README.md +37 -37
- package/docs/proof/consumer-node-cli/ROADMAP.md +14 -14
- package/docs/proof/consumer-node-cli/SPEC.md +17 -17
- package/docs/proof/consumer-node-cli/brief.md +9 -9
- package/docs/proof/consumer-node-cli/phases/01-foundation/01-01-PLAN.md +34 -34
- package/docs/proof/consumer-node-cli/phases/01-foundation/01-01-SUMMARY.md +10 -10
- package/docs/proof/consumer-node-cli/phases/01-foundation/01-VERIFICATION.md +30 -30
- package/package.json +62 -61
package/bin/lib/health.mjs
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
|
-
// health.mjs — Workspace integrity diagnostics
|
|
2
|
-
//
|
|
3
|
-
// IMPORTANT: No module-scope process.cwd() — ESM caching means sub-modules
|
|
4
|
-
// evaluate once, so CWD must be computed inside function bodies.
|
|
5
|
-
|
|
6
|
-
import { existsSync, readFileSync, readdirSync } from 'fs';
|
|
7
|
-
import { join } from 'path';
|
|
8
|
-
import { readManifest, detectModifications } from './manifest.mjs';
|
|
1
|
+
// health.mjs — Workspace integrity diagnostics
|
|
2
|
+
//
|
|
3
|
+
// IMPORTANT: No module-scope process.cwd() — ESM caching means sub-modules
|
|
4
|
+
// evaluate once, so CWD must be computed inside function bodies.
|
|
5
|
+
|
|
6
|
+
import { existsSync, readFileSync, readdirSync } from 'fs';
|
|
7
|
+
import { join } from 'path';
|
|
8
|
+
import { readManifest, detectModifications } from './manifest.mjs';
|
|
9
9
|
import { output } from './cli-utils.mjs';
|
|
10
10
|
import { runTruthChecks, TRUTH_CHECK_IDS } from './health-truth.mjs';
|
|
11
11
|
import { evaluateLifecycleState } from './lifecycle-state.mjs';
|
|
12
12
|
import { evaluateRuntimeFreshness } from './runtime-freshness.mjs';
|
|
13
13
|
import { resolveWorkspaceContext } from './workspace-root.mjs';
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Factory function returning the health command.
|
|
17
|
-
* ctx should provide: { frameworkVersion, workflows }
|
|
18
|
-
*/
|
|
19
|
-
export function createCmdHealth(ctx) {
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Factory function returning the health command.
|
|
17
|
+
* ctx should provide: { frameworkVersion, workflows }
|
|
18
|
+
*/
|
|
19
|
+
export function createCmdHealth(ctx) {
|
|
20
20
|
return async function cmdHealth(...healthArgs) {
|
|
21
21
|
const jsonMode = healthArgs.includes('--json');
|
|
22
22
|
const { planningDir, workspaceRoot, invalid, error } = resolveWorkspaceContext(healthArgs);
|
|
@@ -31,122 +31,133 @@ export function createCmdHealth(ctx) {
|
|
|
31
31
|
}
|
|
32
32
|
const cwd = workspaceRoot;
|
|
33
33
|
const frameworkSourceMode = isFrameworkSourceRepo(cwd);
|
|
34
|
-
const healthCheckIds = ['E1', 'E2', 'E3', 'E4', 'E5', 'E6', 'E7', 'E8', 'W1', 'W2', 'W3', 'W4', 'W5', 'W6', ...TRUTH_CHECK_IDS, 'I1', 'I2', 'I3'];
|
|
35
|
-
|
|
36
|
-
// Pre-init guard
|
|
37
|
-
if (!existsSync(join(planningDir, 'config.json'))) {
|
|
38
|
-
if (jsonMode) {
|
|
39
|
-
output({ status: 'broken', errors: [{ id: 'E1', severity: 'ERROR', message: '.planning/config.json missing', fix: 'Run `gsdd init`' }], warnings: [], info: [] });
|
|
40
|
-
} else {
|
|
41
|
-
console.log('Not initialized. Run `gsdd init`.');
|
|
42
|
-
}
|
|
43
|
-
process.exitCode = 1;
|
|
44
|
-
return;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const errors = [];
|
|
48
|
-
const warnings = [];
|
|
49
|
-
const info = [];
|
|
50
|
-
|
|
51
|
-
// --- ERROR checks ---
|
|
52
|
-
|
|
53
|
-
// E1: config.json missing (already handled by pre-init guard, but keep for completeness)
|
|
54
|
-
// E2: config.json missing required fields
|
|
55
|
-
let config = null;
|
|
56
|
-
let configOk = false;
|
|
57
|
-
try {
|
|
58
|
-
config = JSON.parse(readFileSync(join(planningDir, 'config.json'), 'utf-8'));
|
|
59
|
-
configOk = true;
|
|
60
|
-
const requiredFields = ['researchDepth', 'modelProfile', 'initVersion'];
|
|
61
|
-
const missing = requiredFields.filter((f) => !(f in config));
|
|
62
|
-
if (missing.length > 0) {
|
|
63
|
-
errors.push({ id: 'E2', severity: 'ERROR', message: `config.json missing required fields: ${missing.join(', ')}`, fix: 'Run `gsdd init` to regenerate' });
|
|
64
|
-
}
|
|
65
|
-
} catch {
|
|
66
|
-
errors.push({ id: 'E1', severity: 'ERROR', message: '.planning/config.json is unparseable', fix: 'Run `gsdd init`' });
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// E3: templates/ missing
|
|
34
|
+
const healthCheckIds = ['E1', 'E2', 'E3', 'E4', 'E5', 'E6', 'E7', 'E8', 'E9', 'W1', 'W2', 'W3', 'W4', 'W5', 'W6', ...TRUTH_CHECK_IDS, 'I1', 'I2', 'I3'];
|
|
35
|
+
|
|
36
|
+
// Pre-init guard
|
|
37
|
+
if (!existsSync(join(planningDir, 'config.json'))) {
|
|
38
|
+
if (jsonMode) {
|
|
39
|
+
output({ status: 'broken', errors: [{ id: 'E1', severity: 'ERROR', message: '.planning/config.json missing', fix: 'Run `npx -y gsdd-cli init`' }], warnings: [], info: [] });
|
|
40
|
+
} else {
|
|
41
|
+
console.log('Not initialized. Run `npx -y gsdd-cli init`. If `gsdd` is installed globally, `gsdd init` is also fine.');
|
|
42
|
+
}
|
|
43
|
+
process.exitCode = 1;
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const errors = [];
|
|
48
|
+
const warnings = [];
|
|
49
|
+
const info = [];
|
|
50
|
+
|
|
51
|
+
// --- ERROR checks ---
|
|
52
|
+
|
|
53
|
+
// E1: config.json missing (already handled by pre-init guard, but keep for completeness)
|
|
54
|
+
// E2: config.json missing required fields
|
|
55
|
+
let config = null;
|
|
56
|
+
let configOk = false;
|
|
57
|
+
try {
|
|
58
|
+
config = JSON.parse(readFileSync(join(planningDir, 'config.json'), 'utf-8'));
|
|
59
|
+
configOk = true;
|
|
60
|
+
const requiredFields = ['researchDepth', 'modelProfile', 'initVersion'];
|
|
61
|
+
const missing = requiredFields.filter((f) => !(f in config));
|
|
62
|
+
if (missing.length > 0) {
|
|
63
|
+
errors.push({ id: 'E2', severity: 'ERROR', message: `config.json missing required fields: ${missing.join(', ')}`, fix: 'Run `npx -y gsdd-cli init` to regenerate' });
|
|
64
|
+
}
|
|
65
|
+
} catch {
|
|
66
|
+
errors.push({ id: 'E1', severity: 'ERROR', message: '.planning/config.json is unparseable', fix: 'Run `npx -y gsdd-cli init`' });
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// E3: templates/ missing
|
|
70
70
|
const templatesDir = join(planningDir, 'templates');
|
|
71
71
|
const runtimeHelpersDir = join(planningDir, 'bin');
|
|
72
72
|
const hasTemplatesDir = existsSync(templatesDir);
|
|
73
73
|
const hasRuntimeHelpersDir = existsSync(runtimeHelpersDir);
|
|
74
|
-
const rolesDir = join(templatesDir, 'roles');
|
|
75
|
-
const delegatesDir = join(templatesDir, 'delegates');
|
|
76
|
-
const hasRolesDir = hasTemplatesDir && existsSync(rolesDir);
|
|
77
|
-
const hasDelegatesDir = hasTemplatesDir && existsSync(delegatesDir);
|
|
78
|
-
const skipInstalledTemplateChecks = !hasTemplatesDir && frameworkSourceMode;
|
|
79
|
-
|
|
80
|
-
if (!hasTemplatesDir && !skipInstalledTemplateChecks) {
|
|
81
|
-
errors.push({ id: 'E3', severity: 'ERROR', message: '.planning/templates/ missing', fix: 'Run `gsdd update --templates`' });
|
|
82
|
-
} else if (hasTemplatesDir) {
|
|
83
|
-
// E4: roles/ missing or empty
|
|
84
|
-
if (!hasRolesDir) {
|
|
85
|
-
errors.push({ id: 'E4', severity: 'ERROR', message: '.planning/templates/roles/ missing', fix: 'Run `gsdd update --templates`' });
|
|
86
|
-
} else {
|
|
87
|
-
const roleFiles = readdirSync(rolesDir).filter((f) => f.endsWith('.md'));
|
|
88
|
-
if (roleFiles.length === 0) {
|
|
89
|
-
errors.push({ id: 'E4', severity: 'ERROR', message: '.planning/templates/roles/ has 0 role files', fix: 'Run `gsdd update --templates`' });
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// E5: delegates/ missing or empty
|
|
94
|
-
if (!hasDelegatesDir) {
|
|
95
|
-
errors.push({ id: 'E5', severity: 'ERROR', message: '.planning/templates/delegates/ missing', fix: 'Run `gsdd update --templates`' });
|
|
96
|
-
} else {
|
|
97
|
-
const delegateFiles = readdirSync(delegatesDir).filter((f) => f.endsWith('.md'));
|
|
98
|
-
if (delegateFiles.length === 0) {
|
|
99
|
-
errors.push({ id: 'E5', severity: 'ERROR', message: '.planning/templates/delegates/ has 0 delegate files', fix: 'Run `gsdd update --templates`' });
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// E6: research/ missing or empty
|
|
104
|
-
const researchDir = join(templatesDir, 'research');
|
|
105
|
-
if (!existsSync(researchDir)) {
|
|
106
|
-
errors.push({ id: 'E6', severity: 'ERROR', message: '.planning/templates/research/ missing', fix: 'Run `gsdd update --templates`' });
|
|
107
|
-
} else {
|
|
108
|
-
const researchFiles = readdirSync(researchDir).filter((f) => f.endsWith('.md'));
|
|
109
|
-
if (researchFiles.length === 0) {
|
|
110
|
-
errors.push({ id: 'E6', severity: 'ERROR', message: '.planning/templates/research/ has 0 template files', fix: 'Run `gsdd update --templates`' });
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// E7: codebase/ missing or empty
|
|
115
|
-
const codebaseDir = join(templatesDir, 'codebase');
|
|
116
|
-
if (!existsSync(codebaseDir)) {
|
|
117
|
-
errors.push({ id: 'E7', severity: 'ERROR', message: '.planning/templates/codebase/ missing', fix: 'Run `gsdd update --templates`' });
|
|
118
|
-
} else {
|
|
119
|
-
const codebaseFiles = readdirSync(codebaseDir).filter((f) => f.endsWith('.md'));
|
|
120
|
-
if (codebaseFiles.length === 0) {
|
|
121
|
-
errors.push({ id: 'E7', severity: 'ERROR', message: '.planning/templates/codebase/ has 0 template files', fix: 'Run `gsdd update --templates`' });
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// E8: critical root template files missing
|
|
126
|
-
const requiredRootFiles = ['spec.md', 'roadmap.md', 'auth-matrix.md'];
|
|
127
|
-
const missingRoot = requiredRootFiles.filter((f) => !existsSync(join(templatesDir, f)));
|
|
128
|
-
if (missingRoot.length > 0) {
|
|
129
|
-
errors.push({ id: 'E8', severity: 'ERROR', message: `.planning/templates/ missing critical root files: ${missingRoot.join(', ')}`, fix: 'Run `gsdd update --templates`' });
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
74
|
+
const rolesDir = join(templatesDir, 'roles');
|
|
75
|
+
const delegatesDir = join(templatesDir, 'delegates');
|
|
76
|
+
const hasRolesDir = hasTemplatesDir && existsSync(rolesDir);
|
|
77
|
+
const hasDelegatesDir = hasTemplatesDir && existsSync(delegatesDir);
|
|
78
|
+
const skipInstalledTemplateChecks = !hasTemplatesDir && frameworkSourceMode;
|
|
79
|
+
|
|
80
|
+
if (!hasTemplatesDir && !skipInstalledTemplateChecks) {
|
|
81
|
+
errors.push({ id: 'E3', severity: 'ERROR', message: '.planning/templates/ missing', fix: 'Run `npx -y gsdd-cli update --templates`' });
|
|
82
|
+
} else if (hasTemplatesDir) {
|
|
83
|
+
// E4: roles/ missing or empty
|
|
84
|
+
if (!hasRolesDir) {
|
|
85
|
+
errors.push({ id: 'E4', severity: 'ERROR', message: '.planning/templates/roles/ missing', fix: 'Run `npx -y gsdd-cli update --templates`' });
|
|
86
|
+
} else {
|
|
87
|
+
const roleFiles = readdirSync(rolesDir).filter((f) => f.endsWith('.md'));
|
|
88
|
+
if (roleFiles.length === 0) {
|
|
89
|
+
errors.push({ id: 'E4', severity: 'ERROR', message: '.planning/templates/roles/ has 0 role files', fix: 'Run `npx -y gsdd-cli update --templates`' });
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// E5: delegates/ missing or empty
|
|
94
|
+
if (!hasDelegatesDir) {
|
|
95
|
+
errors.push({ id: 'E5', severity: 'ERROR', message: '.planning/templates/delegates/ missing', fix: 'Run `npx -y gsdd-cli update --templates`' });
|
|
96
|
+
} else {
|
|
97
|
+
const delegateFiles = readdirSync(delegatesDir).filter((f) => f.endsWith('.md'));
|
|
98
|
+
if (delegateFiles.length === 0) {
|
|
99
|
+
errors.push({ id: 'E5', severity: 'ERROR', message: '.planning/templates/delegates/ has 0 delegate files', fix: 'Run `npx -y gsdd-cli update --templates`' });
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// E6: research/ missing or empty
|
|
104
|
+
const researchDir = join(templatesDir, 'research');
|
|
105
|
+
if (!existsSync(researchDir)) {
|
|
106
|
+
errors.push({ id: 'E6', severity: 'ERROR', message: '.planning/templates/research/ missing', fix: 'Run `npx -y gsdd-cli update --templates`' });
|
|
107
|
+
} else {
|
|
108
|
+
const researchFiles = readdirSync(researchDir).filter((f) => f.endsWith('.md'));
|
|
109
|
+
if (researchFiles.length === 0) {
|
|
110
|
+
errors.push({ id: 'E6', severity: 'ERROR', message: '.planning/templates/research/ has 0 template files', fix: 'Run `npx -y gsdd-cli update --templates`' });
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// E7: codebase/ missing or empty
|
|
115
|
+
const codebaseDir = join(templatesDir, 'codebase');
|
|
116
|
+
if (!existsSync(codebaseDir)) {
|
|
117
|
+
errors.push({ id: 'E7', severity: 'ERROR', message: '.planning/templates/codebase/ missing', fix: 'Run `npx -y gsdd-cli update --templates`' });
|
|
118
|
+
} else {
|
|
119
|
+
const codebaseFiles = readdirSync(codebaseDir).filter((f) => f.endsWith('.md'));
|
|
120
|
+
if (codebaseFiles.length === 0) {
|
|
121
|
+
errors.push({ id: 'E7', severity: 'ERROR', message: '.planning/templates/codebase/ has 0 template files', fix: 'Run `npx -y gsdd-cli update --templates`' });
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// E8: critical root template files missing
|
|
126
|
+
const requiredRootFiles = ['spec.md', 'roadmap.md', 'auth-matrix.md'];
|
|
127
|
+
const missingRoot = requiredRootFiles.filter((f) => !existsSync(join(templatesDir, f)));
|
|
128
|
+
if (missingRoot.length > 0) {
|
|
129
|
+
errors.push({ id: 'E8', severity: 'ERROR', message: `.planning/templates/ missing critical root files: ${missingRoot.join(', ')}`, fix: 'Run `npx -y gsdd-cli update --templates`' });
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const brownfieldChangeDir = join(templatesDir, 'brownfield-change');
|
|
133
|
+
if (!existsSync(brownfieldChangeDir)) {
|
|
134
|
+
errors.push({ id: 'E9', severity: 'ERROR', message: '.planning/templates/brownfield-change/ missing', fix: 'Run `npx -y gsdd-cli update --templates`' });
|
|
135
|
+
} else {
|
|
136
|
+
const missingBrownfield = ['CHANGE.md', 'HANDOFF.md', 'VERIFICATION.md'].filter((file) => !existsSync(join(brownfieldChangeDir, file)));
|
|
137
|
+
if (missingBrownfield.length > 0) {
|
|
138
|
+
errors.push({ id: 'E9', severity: 'ERROR', message: `.planning/templates/brownfield-change/ missing critical files: ${missingBrownfield.join(', ')}`, fix: 'Run `npx -y gsdd-cli update --templates`' });
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// --- WARNING checks ---
|
|
144
|
+
|
|
145
|
+
// W1: generation-manifest.json missing
|
|
136
146
|
const manifest = skipInstalledTemplateChecks ? null : readManifest(planningDir);
|
|
137
147
|
if (!manifest && !skipInstalledTemplateChecks) {
|
|
138
|
-
warnings.push({ id: 'W1', severity: 'WARN', message: 'generation-manifest.json missing', fix: 'Run `gsdd update` to create' });
|
|
148
|
+
warnings.push({ id: 'W1', severity: 'WARN', message: 'generation-manifest.json missing', fix: 'Run `npx -y gsdd-cli update` to create' });
|
|
139
149
|
}
|
|
140
|
-
|
|
141
|
-
// W2 + W3: template/role hash mismatches and missing files
|
|
142
|
-
if (manifest && hasTemplatesDir) {
|
|
150
|
+
|
|
151
|
+
// W2 + W3: template/role hash mismatches and missing files
|
|
152
|
+
if (manifest && hasTemplatesDir) {
|
|
143
153
|
const allCategories = [
|
|
144
|
-
{ name: 'delegates', dir: delegatesDir, hashes: hasDelegatesDir ? manifest.templates?.delegates : null, fixCommand: 'gsdd update --templates' },
|
|
145
|
-
{ name: 'research', dir: join(templatesDir, 'research'), hashes: manifest.templates?.research, fixCommand: 'gsdd update --templates' },
|
|
146
|
-
{ name: 'codebase', dir: join(templatesDir, 'codebase'), hashes: manifest.templates?.codebase, fixCommand: 'gsdd update --templates' },
|
|
147
|
-
{ name: '
|
|
148
|
-
{ name: '
|
|
149
|
-
{ name: '
|
|
154
|
+
{ name: 'delegates', dir: delegatesDir, hashes: hasDelegatesDir ? manifest.templates?.delegates : null, fixCommand: 'npx -y gsdd-cli update --templates' },
|
|
155
|
+
{ name: 'research', dir: join(templatesDir, 'research'), hashes: manifest.templates?.research, fixCommand: 'npx -y gsdd-cli update --templates' },
|
|
156
|
+
{ name: 'codebase', dir: join(templatesDir, 'codebase'), hashes: manifest.templates?.codebase, fixCommand: 'npx -y gsdd-cli update --templates' },
|
|
157
|
+
{ name: 'brownfield-change', dir: join(templatesDir, 'brownfield-change'), hashes: manifest.templates?.brownfieldChange, fixCommand: 'npx -y gsdd-cli update --templates' },
|
|
158
|
+
{ name: 'root templates', dir: templatesDir, hashes: manifest.templates?.root, fixCommand: 'npx -y gsdd-cli update --templates' },
|
|
159
|
+
{ name: 'roles', dir: rolesDir, hashes: hasRolesDir ? manifest.roles : null, fixCommand: 'npx -y gsdd-cli update --templates' },
|
|
160
|
+
{ name: 'runtime helpers', dir: planningDir, hashes: hasRuntimeHelpersDir ? manifest.runtimeHelpers : null, fixCommand: 'npx -y gsdd-cli update' },
|
|
150
161
|
];
|
|
151
162
|
|
|
152
163
|
for (const cat of allCategories) {
|
|
@@ -160,107 +171,116 @@ export function createCmdHealth(ctx) {
|
|
|
160
171
|
}
|
|
161
172
|
}
|
|
162
173
|
}
|
|
163
|
-
|
|
164
|
-
// W4: ROADMAP.md references phases not found in .planning/phases/
|
|
165
|
-
const roadmapPath = join(planningDir, 'ROADMAP.md');
|
|
166
|
-
const phasesDir = join(planningDir, 'phases');
|
|
167
|
-
const roadmap = existsSync(roadmapPath) ? readFileSync(roadmapPath, 'utf-8') : null;
|
|
168
|
-
const lifecycle = evaluateLifecycleState({ planningDir });
|
|
169
|
-
|
|
170
|
-
if (roadmap && existsSync(phasesDir)) {
|
|
171
|
-
for (const phase of lifecycle.phases.filter((entry) => entry.status !== 'not_started' && !entry.
|
|
172
|
-
warnings.push({
|
|
173
|
-
id: 'W4',
|
|
174
|
-
severity: 'WARN',
|
|
175
|
-
message: `ROADMAP.md references active Phase ${phase.number} but no files found in .planning/phases/`,
|
|
176
|
-
fix: 'Create missing phase dirs or update ROADMAP',
|
|
177
|
-
});
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
// W5: Phase dir has PLAN but no SUMMARY (stale in-progress)
|
|
182
|
-
if (lifecycle.incompletePlans.length > 0) {
|
|
183
|
-
for (const plan of lifecycle.incompletePlans) {
|
|
184
|
-
warnings.push({
|
|
185
|
-
id: 'W5',
|
|
186
|
-
severity: 'WARN',
|
|
187
|
-
message: `${plan.displayPath} exists but no matching SUMMARY found (stale in-progress?)`,
|
|
188
|
-
fix: 'Resume or complete the phase',
|
|
189
|
-
});
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
// W6: No adapter surfaces detected
|
|
194
|
-
const adapterPaths = [
|
|
195
|
-
join(cwd, '.agents', 'skills'),
|
|
196
|
-
join(cwd, '.claude'),
|
|
197
|
-
join(cwd, '.opencode'),
|
|
198
|
-
join(cwd, '.codex'),
|
|
199
|
-
];
|
|
200
|
-
const hasAnyAdapter = adapterPaths.some((p) => existsSync(p));
|
|
201
|
-
if (!hasAnyAdapter) {
|
|
202
|
-
warnings.push({ id: 'W6', severity: 'WARN', message: 'No adapter surfaces detected', fix: 'Run `gsdd init --tools <platform>`' });
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
const runtimeFreshnessReport = configOk && Array.isArray(ctx.workflows)
|
|
206
|
-
? evaluateRuntimeFreshness({ cwd, workflows: ctx.workflows })
|
|
207
|
-
: null;
|
|
208
|
-
|
|
209
|
-
warnings.push(...runTruthChecks(planningDir, cwd, healthCheckIds, { runtimeFreshnessReport }))
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
if (
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
if (
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
console.log(`\
|
|
259
|
-
|
|
260
|
-
};
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
174
|
+
|
|
175
|
+
// W4: ROADMAP.md references phases not found in .planning/phases/
|
|
176
|
+
const roadmapPath = join(planningDir, 'ROADMAP.md');
|
|
177
|
+
const phasesDir = join(planningDir, 'phases');
|
|
178
|
+
const roadmap = existsSync(roadmapPath) ? readFileSync(roadmapPath, 'utf-8') : null;
|
|
179
|
+
const lifecycle = evaluateLifecycleState({ planningDir });
|
|
180
|
+
|
|
181
|
+
if (roadmap && existsSync(phasesDir)) {
|
|
182
|
+
for (const phase of lifecycle.phases.filter((entry) => entry.status !== 'not_started' && !entry.hasLifecycleArtifacts)) {
|
|
183
|
+
warnings.push({
|
|
184
|
+
id: 'W4',
|
|
185
|
+
severity: 'WARN',
|
|
186
|
+
message: `ROADMAP.md references active Phase ${phase.number} but no files found in .planning/phases/`,
|
|
187
|
+
fix: 'Create missing phase dirs or update ROADMAP',
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// W5: Phase dir has PLAN but no SUMMARY (stale in-progress)
|
|
193
|
+
if (lifecycle.incompletePlans.length > 0) {
|
|
194
|
+
for (const plan of lifecycle.incompletePlans) {
|
|
195
|
+
warnings.push({
|
|
196
|
+
id: 'W5',
|
|
197
|
+
severity: 'WARN',
|
|
198
|
+
message: `${plan.displayPath} exists but no matching SUMMARY found (stale in-progress?)`,
|
|
199
|
+
fix: 'Resume or complete the phase',
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// W6: No adapter surfaces detected
|
|
205
|
+
const adapterPaths = [
|
|
206
|
+
join(cwd, '.agents', 'skills'),
|
|
207
|
+
join(cwd, '.claude'),
|
|
208
|
+
join(cwd, '.opencode'),
|
|
209
|
+
join(cwd, '.codex'),
|
|
210
|
+
];
|
|
211
|
+
const hasAnyAdapter = adapterPaths.some((p) => existsSync(p));
|
|
212
|
+
if (!hasAnyAdapter) {
|
|
213
|
+
warnings.push({ id: 'W6', severity: 'WARN', message: 'No adapter surfaces detected', fix: 'Run `npx -y gsdd-cli init --tools <platform>`' });
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const runtimeFreshnessReport = configOk && Array.isArray(ctx.workflows)
|
|
217
|
+
? evaluateRuntimeFreshness({ cwd, workflows: ctx.workflows })
|
|
218
|
+
: null;
|
|
219
|
+
|
|
220
|
+
warnings.push(...runTruthChecks(planningDir, cwd, healthCheckIds, { runtimeFreshnessReport }).map((warning) => {
|
|
221
|
+
if (warning.id !== 'W10') return warning;
|
|
222
|
+
return {
|
|
223
|
+
...warning,
|
|
224
|
+
message: warning.message.replace(
|
|
225
|
+
/^ROADMAP\/SPEC requirement status drift/,
|
|
226
|
+
'ROADMAP lifecycle status drift (requirement checkbox and/or overview/detail phase status mismatch)'
|
|
227
|
+
),
|
|
228
|
+
fix: 'Reconcile .planning/ROADMAP.md overview/detail phase markers and .planning/SPEC.md requirement checkboxes',
|
|
229
|
+
};
|
|
230
|
+
}));
|
|
231
|
+
|
|
232
|
+
// --- INFO checks ---
|
|
233
|
+
|
|
234
|
+
// I1: generation manifest was produced by a different framework version
|
|
235
|
+
if (manifest && manifest.frameworkVersion && manifest.frameworkVersion !== ctx.frameworkVersion) {
|
|
236
|
+
info.push({ id: 'I1', severity: 'INFO', message: `Generation manifest frameworkVersion (${manifest.frameworkVersion}) differs from current framework version (${ctx.frameworkVersion})`, fix: 'Run `npx -y gsdd-cli update --templates`' });
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// I2: Phase completion count
|
|
240
|
+
if (lifecycle.counts.total > 0) {
|
|
241
|
+
info.push({
|
|
242
|
+
id: 'I2',
|
|
243
|
+
severity: 'INFO',
|
|
244
|
+
message: `Phases: ${lifecycle.counts.completed}/${lifecycle.counts.total} completed`,
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// I3: Which adapters are installed
|
|
249
|
+
const installedAdapters = [];
|
|
250
|
+
if (existsSync(join(cwd, '.agents', 'skills'))) installedAdapters.push('open-standard-skills');
|
|
251
|
+
if (existsSync(join(cwd, '.claude'))) installedAdapters.push('claude');
|
|
252
|
+
if (existsSync(join(cwd, '.opencode'))) installedAdapters.push('opencode');
|
|
253
|
+
if (existsSync(join(cwd, '.codex'))) installedAdapters.push('codex');
|
|
254
|
+
if (existsSync(join(cwd, 'AGENTS.md'))) installedAdapters.push('agents');
|
|
255
|
+
if (installedAdapters.length > 0) {
|
|
256
|
+
info.push({ id: 'I3', severity: 'INFO', message: `Adapters installed: ${installedAdapters.join(', ')}` });
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// --- Verdict ---
|
|
260
|
+
const hasErrors = errors.length > 0;
|
|
261
|
+
const hasWarnings = warnings.length > 0;
|
|
262
|
+
const status = hasErrors ? 'broken' : hasWarnings ? 'degraded' : 'healthy';
|
|
263
|
+
|
|
264
|
+
if (hasErrors) process.exitCode = 1;
|
|
265
|
+
|
|
266
|
+
if (jsonMode) {
|
|
267
|
+
output({ status, errors, warnings, info });
|
|
268
|
+
} else {
|
|
269
|
+
console.log(`\ngsdd health — workspace integrity check\n`);
|
|
270
|
+
if (errors.length > 0) {
|
|
271
|
+
for (const e of errors) console.log(` ERROR: [${e.id}] ${e.message}\n Fix: ${e.fix}`);
|
|
272
|
+
}
|
|
273
|
+
if (warnings.length > 0) {
|
|
274
|
+
for (const w of warnings) console.log(` WARN: [${w.id}] ${w.message}\n Fix: ${w.fix}`);
|
|
275
|
+
}
|
|
276
|
+
if (info.length > 0) {
|
|
277
|
+
for (const i of info) console.log(` INFO: [${i.id}] ${i.message}${i.fix ? `\n Fix: ${i.fix}` : ''}`);
|
|
278
|
+
}
|
|
279
|
+
console.log(`\n Verdict: ${status.toUpperCase()}\n`);
|
|
280
|
+
}
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function isFrameworkSourceRepo(cwd) {
|
|
285
|
+
return existsSync(join(cwd, 'distilled', 'templates')) && existsSync(join(cwd, 'distilled', 'workflows'));
|
|
286
|
+
}
|