gsdd-cli 0.18.5 → 0.19.0

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 (100) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +609 -608
  3. package/agents/DISTILLATION.md +421 -421
  4. package/agents/README.md +62 -62
  5. package/agents/approach-explorer.md +361 -361
  6. package/agents/debugger.md +82 -82
  7. package/agents/executor.md +394 -394
  8. package/agents/integration-checker.md +318 -318
  9. package/agents/mapper.md +103 -103
  10. package/agents/planner.md +313 -313
  11. package/agents/researcher.md +84 -84
  12. package/agents/roadmapper.md +296 -296
  13. package/agents/synthesizer.md +236 -236
  14. package/agents/verifier.md +337 -337
  15. package/bin/adapters/agents.mjs +34 -34
  16. package/bin/adapters/claude.mjs +191 -191
  17. package/bin/adapters/codex.mjs +85 -85
  18. package/bin/adapters/index.mjs +20 -20
  19. package/bin/adapters/opencode.mjs +278 -278
  20. package/bin/gsdd.mjs +123 -116
  21. package/bin/lib/cli-utils.mjs +28 -28
  22. package/bin/lib/evidence-contract.mjs +112 -112
  23. package/bin/lib/file-ops.mjs +186 -144
  24. package/bin/lib/health-truth.mjs +181 -178
  25. package/bin/lib/health.mjs +246 -226
  26. package/bin/lib/init-flow.mjs +247 -231
  27. package/bin/lib/init-prompts.mjs +248 -247
  28. package/bin/lib/init-runtime.mjs +191 -190
  29. package/bin/lib/init.mjs +17 -17
  30. package/bin/lib/lifecycle-preflight.mjs +347 -325
  31. package/bin/lib/lifecycle-state.mjs +351 -267
  32. package/bin/lib/manifest.mjs +116 -114
  33. package/bin/lib/models.mjs +411 -411
  34. package/bin/lib/phase.mjs +360 -358
  35. package/bin/lib/plan-constants.mjs +30 -30
  36. package/bin/lib/provenance.mjs +109 -106
  37. package/bin/lib/rendering.mjs +115 -83
  38. package/bin/lib/runtime-freshness.mjs +214 -214
  39. package/bin/lib/templates.mjs +225 -224
  40. package/bin/lib/workspace-root.mjs +2 -1
  41. package/distilled/DESIGN.md +2333 -2323
  42. package/distilled/EVIDENCE-INDEX.md +394 -392
  43. package/distilled/README.md +196 -193
  44. package/distilled/SKILL.md +86 -85
  45. package/distilled/templates/agents.block.md +21 -21
  46. package/distilled/templates/agents.md +6 -6
  47. package/distilled/templates/approach.md +232 -232
  48. package/distilled/templates/auth-matrix.md +78 -78
  49. package/distilled/templates/brownfield-change/CHANGE.md +99 -0
  50. package/distilled/templates/brownfield-change/HANDOFF.md +38 -0
  51. package/distilled/templates/brownfield-change/VERIFICATION.md +56 -0
  52. package/distilled/templates/codebase/architecture.md +110 -110
  53. package/distilled/templates/codebase/concerns.md +95 -95
  54. package/distilled/templates/codebase/conventions.md +193 -193
  55. package/distilled/templates/codebase/stack.md +96 -96
  56. package/distilled/templates/delegates/approach-explorer.md +25 -25
  57. package/distilled/templates/delegates/mapper-arch.md +26 -26
  58. package/distilled/templates/delegates/mapper-concerns.md +27 -27
  59. package/distilled/templates/delegates/mapper-quality.md +28 -28
  60. package/distilled/templates/delegates/mapper-tech.md +25 -25
  61. package/distilled/templates/delegates/plan-checker.md +68 -68
  62. package/distilled/templates/delegates/researcher-architecture.md +30 -30
  63. package/distilled/templates/delegates/researcher-features.md +30 -30
  64. package/distilled/templates/delegates/researcher-pitfalls.md +30 -30
  65. package/distilled/templates/delegates/researcher-stack.md +30 -30
  66. package/distilled/templates/delegates/researcher-synthesizer.md +31 -31
  67. package/distilled/templates/research/architecture.md +57 -57
  68. package/distilled/templates/research/features.md +23 -23
  69. package/distilled/templates/research/pitfalls.md +46 -46
  70. package/distilled/templates/research/stack.md +45 -45
  71. package/distilled/templates/research/summary.md +67 -67
  72. package/distilled/templates/roadmap.md +74 -62
  73. package/distilled/templates/spec.md +110 -110
  74. package/distilled/workflows/audit-milestone.md +275 -271
  75. package/distilled/workflows/complete-milestone.md +336 -332
  76. package/distilled/workflows/execute.md +454 -449
  77. package/distilled/workflows/map-codebase.md +253 -253
  78. package/distilled/workflows/new-milestone.md +242 -238
  79. package/distilled/workflows/new-project.md +398 -398
  80. package/distilled/workflows/pause.md +160 -156
  81. package/distilled/workflows/plan-milestone-gaps.md +183 -183
  82. package/distilled/workflows/plan.md +451 -447
  83. package/distilled/workflows/progress.md +227 -223
  84. package/distilled/workflows/quick.md +351 -347
  85. package/distilled/workflows/resume.md +220 -212
  86. package/distilled/workflows/verify-work.md +260 -260
  87. package/distilled/workflows/verify.md +431 -429
  88. package/docs/BROWNFIELD-PROOF.md +95 -95
  89. package/docs/RUNTIME-SUPPORT.md +80 -69
  90. package/docs/USER-GUIDE.md +394 -386
  91. package/docs/VERIFICATION-DISCIPLINE.md +59 -59
  92. package/docs/claude/context-monitor.md +98 -98
  93. package/docs/proof/consumer-node-cli/README.md +37 -37
  94. package/docs/proof/consumer-node-cli/ROADMAP.md +14 -14
  95. package/docs/proof/consumer-node-cli/SPEC.md +17 -17
  96. package/docs/proof/consumer-node-cli/brief.md +9 -9
  97. package/docs/proof/consumer-node-cli/phases/01-foundation/01-01-PLAN.md +34 -34
  98. package/docs/proof/consumer-node-cli/phases/01-foundation/01-01-SUMMARY.md +10 -10
  99. package/docs/proof/consumer-node-cli/phases/01-foundation/01-VERIFICATION.md +30 -30
  100. package/package.json +62 -61
@@ -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
- // --- WARNING checks ---
134
-
135
- // W1: generation-manifest.json missing
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: 'root templates', dir: templatesDir, hashes: manifest.templates?.root, fixCommand: 'gsdd update --templates' },
148
- { name: 'roles', dir: rolesDir, hashes: hasRolesDir ? manifest.roles : null, fixCommand: 'gsdd update --templates' },
149
- { name: 'runtime helpers', dir: planningDir, hashes: hasRuntimeHelpersDir ? manifest.runtimeHelpers : null, fixCommand: 'gsdd update' },
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.hasArtifacts)) {
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
- // --- INFO checks ---
212
-
213
- // I1: generation manifest was produced by a different framework version
214
- if (manifest && manifest.frameworkVersion && manifest.frameworkVersion !== ctx.frameworkVersion) {
215
- info.push({ id: 'I1', severity: 'INFO', message: `Generation manifest frameworkVersion (${manifest.frameworkVersion}) differs from current framework version (${ctx.frameworkVersion})`, fix: 'Run `gsdd update --templates`' });
216
- }
217
-
218
- // I2: Phase completion count
219
- if (lifecycle.counts.total > 0) {
220
- info.push({
221
- id: 'I2',
222
- severity: 'INFO',
223
- message: `Phases: ${lifecycle.counts.completed}/${lifecycle.counts.total} completed`,
224
- });
225
- }
226
-
227
- // I3: Which adapters are installed
228
- const installedAdapters = [];
229
- if (existsSync(join(cwd, '.agents', 'skills'))) installedAdapters.push('open-standard-skills');
230
- if (existsSync(join(cwd, '.claude'))) installedAdapters.push('claude');
231
- if (existsSync(join(cwd, '.opencode'))) installedAdapters.push('opencode');
232
- if (existsSync(join(cwd, '.codex'))) installedAdapters.push('codex');
233
- if (existsSync(join(cwd, 'AGENTS.md'))) installedAdapters.push('agents');
234
- if (installedAdapters.length > 0) {
235
- info.push({ id: 'I3', severity: 'INFO', message: `Adapters installed: ${installedAdapters.join(', ')}` });
236
- }
237
-
238
- // --- Verdict ---
239
- const hasErrors = errors.length > 0;
240
- const hasWarnings = warnings.length > 0;
241
- const status = hasErrors ? 'broken' : hasWarnings ? 'degraded' : 'healthy';
242
-
243
- if (hasErrors) process.exitCode = 1;
244
-
245
- if (jsonMode) {
246
- output({ status, errors, warnings, info });
247
- } else {
248
- console.log(`\ngsdd health workspace integrity check\n`);
249
- if (errors.length > 0) {
250
- for (const e of errors) console.log(` ERROR: [${e.id}] ${e.message}\n Fix: ${e.fix}`);
251
- }
252
- if (warnings.length > 0) {
253
- for (const w of warnings) console.log(` WARN: [${w.id}] ${w.message}\n Fix: ${w.fix}`);
254
- }
255
- if (info.length > 0) {
256
- for (const i of info) console.log(` INFO: [${i.id}] ${i.message}${i.fix ? `\n Fix: ${i.fix}` : ''}`);
257
- }
258
- console.log(`\n Verdict: ${status.toUpperCase()}\n`);
259
- }
260
- };
261
- }
262
-
263
- function isFrameworkSourceRepo(cwd) {
264
- return existsSync(join(cwd, 'distilled', 'templates')) && existsSync(join(cwd, 'distilled', 'workflows'));
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.hasArtifacts)) {
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
+ }