gsdd-cli 0.18.4 → 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 +625 -607
  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 +209 -161
  24. package/bin/lib/health-truth.mjs +181 -178
  25. package/bin/lib/health.mjs +265 -235
  26. package/bin/lib/init-flow.mjs +252 -236
  27. package/bin/lib/init-prompts.mjs +248 -247
  28. package/bin/lib/init-runtime.mjs +225 -212
  29. package/bin/lib/init.mjs +17 -17
  30. package/bin/lib/lifecycle-preflight.mjs +360 -333
  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 +397 -378
  35. package/bin/lib/plan-constants.mjs +30 -30
  36. package/bin/lib/provenance.mjs +109 -106
  37. package/bin/lib/rendering.mjs +178 -130
  38. package/bin/lib/runtime-freshness.mjs +221 -219
  39. package/bin/lib/templates.mjs +225 -224
  40. package/bin/lib/workspace-root.mjs +132 -0
  41. package/distilled/DESIGN.md +2347 -2327
  42. package/distilled/EVIDENCE-INDEX.md +397 -394
  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 +93 -75
  90. package/docs/USER-GUIDE.md +440 -399
  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,142 +1,163 @@
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
- import { output } from './cli-utils.mjs';
10
- import { runTruthChecks, TRUTH_CHECK_IDS } from './health-truth.mjs';
11
- import { evaluateLifecycleState } from './lifecycle-state.mjs';
12
- import { evaluateRuntimeFreshness } from './runtime-freshness.mjs';
13
-
14
- /**
15
- * Factory function returning the health command.
16
- * ctx should provide: { frameworkVersion, workflows }
17
- */
18
- export function createCmdHealth(ctx) {
19
- return async function cmdHealth(...healthArgs) {
20
- const jsonMode = healthArgs.includes('--json');
21
- const cwd = process.cwd();
22
- const planningDir = join(cwd, '.planning');
23
- const frameworkSourceMode = isFrameworkSourceRepo(cwd);
24
- const healthCheckIds = ['E1', 'E2', 'E3', 'E4', 'E5', 'E6', 'E7', 'E8', 'W1', 'W2', 'W3', 'W4', 'W5', 'W6', ...TRUTH_CHECK_IDS, 'I1', 'I2', 'I3'];
25
-
26
- // Pre-init guard
27
- if (!existsSync(join(planningDir, 'config.json'))) {
28
- if (jsonMode) {
29
- output({ status: 'broken', errors: [{ id: 'E1', severity: 'ERROR', message: '.planning/config.json missing', fix: 'Run `gsdd init`' }], warnings: [], info: [] });
30
- } else {
31
- console.log('Not initialized. Run `gsdd init`.');
32
- }
33
- process.exitCode = 1;
34
- return;
35
- }
36
-
37
- const errors = [];
38
- const warnings = [];
39
- const info = [];
40
-
41
- // --- ERROR checks ---
42
-
43
- // E1: config.json missing (already handled by pre-init guard, but keep for completeness)
44
- // E2: config.json missing required fields
45
- let config = null;
46
- let configOk = false;
47
- try {
48
- config = JSON.parse(readFileSync(join(planningDir, 'config.json'), 'utf-8'));
49
- configOk = true;
50
- const requiredFields = ['researchDepth', 'modelProfile', 'initVersion'];
51
- const missing = requiredFields.filter((f) => !(f in config));
52
- if (missing.length > 0) {
53
- errors.push({ id: 'E2', severity: 'ERROR', message: `config.json missing required fields: ${missing.join(', ')}`, fix: 'Run `gsdd init` to regenerate' });
54
- }
55
- } catch {
56
- errors.push({ id: 'E1', severity: 'ERROR', message: '.planning/config.json is unparseable', fix: 'Run `gsdd init`' });
57
- }
58
-
59
- // E3: templates/ missing
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
+ import { output } from './cli-utils.mjs';
10
+ import { runTruthChecks, TRUTH_CHECK_IDS } from './health-truth.mjs';
11
+ import { evaluateLifecycleState } from './lifecycle-state.mjs';
12
+ import { evaluateRuntimeFreshness } from './runtime-freshness.mjs';
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) {
20
+ return async function cmdHealth(...healthArgs) {
21
+ const jsonMode = healthArgs.includes('--json');
22
+ const { planningDir, workspaceRoot, invalid, error } = resolveWorkspaceContext(healthArgs);
23
+ if (invalid) {
24
+ if (jsonMode) {
25
+ output({ status: 'broken', errors: [{ id: 'E1', severity: 'ERROR', message: error, fix: 'Pass --workspace-root with a real path or remove the flag.' }], warnings: [], info: [] });
26
+ } else {
27
+ console.log(error);
28
+ }
29
+ process.exitCode = 1;
30
+ return;
31
+ }
32
+ const cwd = workspaceRoot;
33
+ const frameworkSourceMode = isFrameworkSourceRepo(cwd);
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
60
70
  const templatesDir = join(planningDir, 'templates');
61
71
  const runtimeHelpersDir = join(planningDir, 'bin');
62
72
  const hasTemplatesDir = existsSync(templatesDir);
63
73
  const hasRuntimeHelpersDir = existsSync(runtimeHelpersDir);
64
- const rolesDir = join(templatesDir, 'roles');
65
- const delegatesDir = join(templatesDir, 'delegates');
66
- const hasRolesDir = hasTemplatesDir && existsSync(rolesDir);
67
- const hasDelegatesDir = hasTemplatesDir && existsSync(delegatesDir);
68
- const skipInstalledTemplateChecks = !hasTemplatesDir && frameworkSourceMode;
69
-
70
- if (!hasTemplatesDir && !skipInstalledTemplateChecks) {
71
- errors.push({ id: 'E3', severity: 'ERROR', message: '.planning/templates/ missing', fix: 'Run `gsdd update --templates`' });
72
- } else if (hasTemplatesDir) {
73
- // E4: roles/ missing or empty
74
- if (!hasRolesDir) {
75
- errors.push({ id: 'E4', severity: 'ERROR', message: '.planning/templates/roles/ missing', fix: 'Run `gsdd update --templates`' });
76
- } else {
77
- const roleFiles = readdirSync(rolesDir).filter((f) => f.endsWith('.md'));
78
- if (roleFiles.length === 0) {
79
- errors.push({ id: 'E4', severity: 'ERROR', message: '.planning/templates/roles/ has 0 role files', fix: 'Run `gsdd update --templates`' });
80
- }
81
- }
82
-
83
- // E5: delegates/ missing or empty
84
- if (!hasDelegatesDir) {
85
- errors.push({ id: 'E5', severity: 'ERROR', message: '.planning/templates/delegates/ missing', fix: 'Run `gsdd update --templates`' });
86
- } else {
87
- const delegateFiles = readdirSync(delegatesDir).filter((f) => f.endsWith('.md'));
88
- if (delegateFiles.length === 0) {
89
- errors.push({ id: 'E5', severity: 'ERROR', message: '.planning/templates/delegates/ has 0 delegate files', fix: 'Run `gsdd update --templates`' });
90
- }
91
- }
92
-
93
- // E6: research/ missing or empty
94
- const researchDir = join(templatesDir, 'research');
95
- if (!existsSync(researchDir)) {
96
- errors.push({ id: 'E6', severity: 'ERROR', message: '.planning/templates/research/ missing', fix: 'Run `gsdd update --templates`' });
97
- } else {
98
- const researchFiles = readdirSync(researchDir).filter((f) => f.endsWith('.md'));
99
- if (researchFiles.length === 0) {
100
- errors.push({ id: 'E6', severity: 'ERROR', message: '.planning/templates/research/ has 0 template files', fix: 'Run `gsdd update --templates`' });
101
- }
102
- }
103
-
104
- // E7: codebase/ missing or empty
105
- const codebaseDir = join(templatesDir, 'codebase');
106
- if (!existsSync(codebaseDir)) {
107
- errors.push({ id: 'E7', severity: 'ERROR', message: '.planning/templates/codebase/ missing', fix: 'Run `gsdd update --templates`' });
108
- } else {
109
- const codebaseFiles = readdirSync(codebaseDir).filter((f) => f.endsWith('.md'));
110
- if (codebaseFiles.length === 0) {
111
- errors.push({ id: 'E7', severity: 'ERROR', message: '.planning/templates/codebase/ has 0 template files', fix: 'Run `gsdd update --templates`' });
112
- }
113
- }
114
-
115
- // E8: critical root template files missing
116
- const requiredRootFiles = ['spec.md', 'roadmap.md', 'auth-matrix.md'];
117
- const missingRoot = requiredRootFiles.filter((f) => !existsSync(join(templatesDir, f)));
118
- if (missingRoot.length > 0) {
119
- errors.push({ id: 'E8', severity: 'ERROR', message: `.planning/templates/ missing critical root files: ${missingRoot.join(', ')}`, fix: 'Run `gsdd update --templates`' });
120
- }
121
- }
122
-
123
- // --- WARNING checks ---
124
-
125
- // 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
126
146
  const manifest = skipInstalledTemplateChecks ? null : readManifest(planningDir);
127
147
  if (!manifest && !skipInstalledTemplateChecks) {
128
- 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' });
129
149
  }
130
-
131
- // W2 + W3: template/role hash mismatches and missing files
132
- if (manifest && hasTemplatesDir) {
150
+
151
+ // W2 + W3: template/role hash mismatches and missing files
152
+ if (manifest && hasTemplatesDir) {
133
153
  const allCategories = [
134
- { name: 'delegates', dir: delegatesDir, hashes: hasDelegatesDir ? manifest.templates?.delegates : null, fixCommand: 'gsdd update --templates' },
135
- { name: 'research', dir: join(templatesDir, 'research'), hashes: manifest.templates?.research, fixCommand: 'gsdd update --templates' },
136
- { name: 'codebase', dir: join(templatesDir, 'codebase'), hashes: manifest.templates?.codebase, fixCommand: 'gsdd update --templates' },
137
- { name: 'root templates', dir: templatesDir, hashes: manifest.templates?.root, fixCommand: 'gsdd update --templates' },
138
- { name: 'roles', dir: rolesDir, hashes: hasRolesDir ? manifest.roles : null, fixCommand: 'gsdd update --templates' },
139
- { 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' },
140
161
  ];
141
162
 
142
163
  for (const cat of allCategories) {
@@ -150,107 +171,116 @@ export function createCmdHealth(ctx) {
150
171
  }
151
172
  }
152
173
  }
153
-
154
- // W4: ROADMAP.md references phases not found in .planning/phases/
155
- const roadmapPath = join(planningDir, 'ROADMAP.md');
156
- const phasesDir = join(planningDir, 'phases');
157
- const roadmap = existsSync(roadmapPath) ? readFileSync(roadmapPath, 'utf-8') : null;
158
- const lifecycle = evaluateLifecycleState({ planningDir });
159
-
160
- if (roadmap && existsSync(phasesDir)) {
161
- for (const phase of lifecycle.phases.filter((entry) => entry.status !== 'not_started' && !entry.hasArtifacts)) {
162
- warnings.push({
163
- id: 'W4',
164
- severity: 'WARN',
165
- message: `ROADMAP.md references active Phase ${phase.number} but no files found in .planning/phases/`,
166
- fix: 'Create missing phase dirs or update ROADMAP',
167
- });
168
- }
169
- }
170
-
171
- // W5: Phase dir has PLAN but no SUMMARY (stale in-progress)
172
- if (lifecycle.incompletePlans.length > 0) {
173
- for (const plan of lifecycle.incompletePlans) {
174
- warnings.push({
175
- id: 'W5',
176
- severity: 'WARN',
177
- message: `${plan.displayPath} exists but no matching SUMMARY found (stale in-progress?)`,
178
- fix: 'Resume or complete the phase',
179
- });
180
- }
181
- }
182
-
183
- // W6: No adapter surfaces detected
184
- const adapterPaths = [
185
- join(cwd, '.agents', 'skills'),
186
- join(cwd, '.claude'),
187
- join(cwd, '.opencode'),
188
- join(cwd, '.codex'),
189
- ];
190
- const hasAnyAdapter = adapterPaths.some((p) => existsSync(p));
191
- if (!hasAnyAdapter) {
192
- warnings.push({ id: 'W6', severity: 'WARN', message: 'No adapter surfaces detected', fix: 'Run `gsdd init --tools <platform>`' });
193
- }
194
-
195
- const runtimeFreshnessReport = configOk && Array.isArray(ctx.workflows)
196
- ? evaluateRuntimeFreshness({ cwd, workflows: ctx.workflows })
197
- : null;
198
-
199
- warnings.push(...runTruthChecks(planningDir, cwd, healthCheckIds, { runtimeFreshnessReport }));
200
-
201
- // --- INFO checks ---
202
-
203
- // I1: generation manifest was produced by a different framework version
204
- if (manifest && manifest.frameworkVersion && manifest.frameworkVersion !== ctx.frameworkVersion) {
205
- info.push({ id: 'I1', severity: 'INFO', message: `Generation manifest frameworkVersion (${manifest.frameworkVersion}) differs from current framework version (${ctx.frameworkVersion})`, fix: 'Run `gsdd update --templates`' });
206
- }
207
-
208
- // I2: Phase completion count
209
- if (lifecycle.counts.total > 0) {
210
- info.push({
211
- id: 'I2',
212
- severity: 'INFO',
213
- message: `Phases: ${lifecycle.counts.completed}/${lifecycle.counts.total} completed`,
214
- });
215
- }
216
-
217
- // I3: Which adapters are installed
218
- const installedAdapters = [];
219
- if (existsSync(join(cwd, '.agents', 'skills'))) installedAdapters.push('open-standard-skills');
220
- if (existsSync(join(cwd, '.claude'))) installedAdapters.push('claude');
221
- if (existsSync(join(cwd, '.opencode'))) installedAdapters.push('opencode');
222
- if (existsSync(join(cwd, '.codex'))) installedAdapters.push('codex');
223
- if (existsSync(join(cwd, 'AGENTS.md'))) installedAdapters.push('agents');
224
- if (installedAdapters.length > 0) {
225
- info.push({ id: 'I3', severity: 'INFO', message: `Adapters installed: ${installedAdapters.join(', ')}` });
226
- }
227
-
228
- // --- Verdict ---
229
- const hasErrors = errors.length > 0;
230
- const hasWarnings = warnings.length > 0;
231
- const status = hasErrors ? 'broken' : hasWarnings ? 'degraded' : 'healthy';
232
-
233
- if (hasErrors) process.exitCode = 1;
234
-
235
- if (jsonMode) {
236
- output({ status, errors, warnings, info });
237
- } else {
238
- console.log(`\ngsdd health workspace integrity check\n`);
239
- if (errors.length > 0) {
240
- for (const e of errors) console.log(` ERROR: [${e.id}] ${e.message}\n Fix: ${e.fix}`);
241
- }
242
- if (warnings.length > 0) {
243
- for (const w of warnings) console.log(` WARN: [${w.id}] ${w.message}\n Fix: ${w.fix}`);
244
- }
245
- if (info.length > 0) {
246
- for (const i of info) console.log(` INFO: [${i.id}] ${i.message}${i.fix ? `\n Fix: ${i.fix}` : ''}`);
247
- }
248
- console.log(`\n Verdict: ${status.toUpperCase()}\n`);
249
- }
250
- };
251
- }
252
-
253
- function isFrameworkSourceRepo(cwd) {
254
- return existsSync(join(cwd, 'distilled', 'templates')) && existsSync(join(cwd, 'distilled', 'workflows'));
255
- }
256
-
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
+ }