gsdd-cli 0.16.1 → 0.18.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/README.md +111 -61
- package/agents/README.md +1 -1
- package/bin/adapters/claude.mjs +8 -1
- package/bin/adapters/codex.mjs +5 -1
- package/bin/adapters/opencode.mjs +7 -1
- package/bin/gsdd.mjs +25 -20
- package/bin/lib/evidence-contract.mjs +112 -0
- package/bin/lib/health-truth.mjs +29 -31
- package/bin/lib/health.mjs +61 -106
- package/bin/lib/init-flow.mjs +91 -34
- package/bin/lib/init-runtime.mjs +46 -25
- package/bin/lib/init.mjs +3 -7
- package/bin/lib/lifecycle-preflight.mjs +333 -0
- package/bin/lib/lifecycle-state.mjs +476 -0
- package/bin/lib/manifest.mjs +24 -20
- package/bin/lib/phase.mjs +81 -26
- package/bin/lib/provenance.mjs +295 -54
- package/bin/lib/rendering.mjs +70 -12
- package/bin/lib/runtime-freshness.mjs +264 -0
- package/bin/lib/session-fingerprint.mjs +106 -0
- package/distilled/DESIGN.md +707 -13
- package/distilled/EVIDENCE-INDEX.md +166 -1
- package/distilled/README.md +44 -28
- package/distilled/SKILL.md +4 -4
- package/distilled/templates/agents.block.md +1 -1
- package/distilled/workflows/audit-milestone.md +50 -0
- package/distilled/workflows/complete-milestone.md +38 -2
- package/distilled/workflows/execute.md +16 -3
- package/distilled/workflows/map-codebase.md +15 -4
- package/distilled/workflows/new-milestone.md +53 -24
- package/distilled/workflows/new-project.md +44 -25
- package/distilled/workflows/pause.md +1 -1
- package/distilled/workflows/plan.md +187 -74
- package/distilled/workflows/progress.md +135 -31
- package/distilled/workflows/quick.md +20 -12
- package/distilled/workflows/resume.md +152 -65
- package/distilled/workflows/verify.md +55 -20
- package/docs/BROWNFIELD-PROOF.md +95 -0
- package/docs/RUNTIME-SUPPORT.md +77 -0
- package/docs/USER-GUIDE.md +443 -0
- package/docs/VERIFICATION-DISCIPLINE.md +59 -0
- package/docs/claude/context-monitor.md +98 -0
- package/docs/proof/consumer-node-cli/README.md +37 -0
- package/docs/proof/consumer-node-cli/ROADMAP.md +14 -0
- package/docs/proof/consumer-node-cli/SPEC.md +17 -0
- package/docs/proof/consumer-node-cli/brief.md +9 -0
- package/docs/proof/consumer-node-cli/phases/01-foundation/01-01-PLAN.md +34 -0
- package/docs/proof/consumer-node-cli/phases/01-foundation/01-01-SUMMARY.md +10 -0
- package/docs/proof/consumer-node-cli/phases/01-foundation/01-VERIFICATION.md +30 -0
- package/package.json +26 -20
package/bin/lib/health-truth.mjs
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
import { existsSync, readFileSync, readdirSync } from 'fs';
|
|
2
2
|
import { join } from 'path';
|
|
3
|
+
import { evaluateLifecycleState } from './lifecycle-state.mjs';
|
|
4
|
+
import {
|
|
5
|
+
getRuntimeFreshnessRepairGuidance,
|
|
6
|
+
summarizeRuntimeFreshnessIssues,
|
|
7
|
+
} from './runtime-freshness.mjs';
|
|
8
|
+
import { checkDrift } from './session-fingerprint.mjs';
|
|
3
9
|
|
|
4
|
-
export const TRUTH_CHECK_IDS = ['W7', 'W8', 'W9', 'W10'];
|
|
10
|
+
export const TRUTH_CHECK_IDS = ['W7', 'W8', 'W9', 'W10', 'W11', 'W12'];
|
|
5
11
|
|
|
6
|
-
export function runTruthChecks(planningDir, frameworkDir, actualCheckIds) {
|
|
12
|
+
export function runTruthChecks(planningDir, frameworkDir, actualCheckIds, options = {}) {
|
|
7
13
|
const warnings = [];
|
|
8
14
|
const designPath = join(frameworkDir, 'distilled', 'DESIGN.md');
|
|
9
15
|
const readmePath = join(frameworkDir, 'distilled', 'README.md');
|
|
@@ -11,6 +17,7 @@ export function runTruthChecks(planningDir, frameworkDir, actualCheckIds) {
|
|
|
11
17
|
const gapsPath = join(frameworkDir, '.internal-research', 'gaps.md');
|
|
12
18
|
const specPath = join(planningDir, 'SPEC.md');
|
|
13
19
|
const roadmapPath = join(planningDir, 'ROADMAP.md');
|
|
20
|
+
const lifecycle = evaluateLifecycleState({ planningDir });
|
|
14
21
|
|
|
15
22
|
if (existsSync(designPath)) {
|
|
16
23
|
const documentedIds = extractHealthTableIds(readFileSync(designPath, 'utf-8'));
|
|
@@ -69,16 +76,7 @@ export function runTruthChecks(planningDir, frameworkDir, actualCheckIds) {
|
|
|
69
76
|
}
|
|
70
77
|
|
|
71
78
|
if (existsSync(specPath) && existsSync(roadmapPath)) {
|
|
72
|
-
const
|
|
73
|
-
const roadmap = readFileSync(roadmapPath, 'utf-8');
|
|
74
|
-
const checkedRequirements = extractCheckedRequirements(spec);
|
|
75
|
-
const roadmapRequirements = extractRoadmapRequirements(roadmap);
|
|
76
|
-
const mismatches = [];
|
|
77
|
-
for (const [requirementId, roadmapStatus] of roadmapRequirements.entries()) {
|
|
78
|
-
const specChecked = checkedRequirements.has(requirementId);
|
|
79
|
-
if (roadmapStatus && !specChecked) mismatches.push(`${requirementId} phase complete but SPEC unchecked`);
|
|
80
|
-
if (!roadmapStatus && specChecked) mismatches.push(`${requirementId} SPEC checked but phase incomplete`);
|
|
81
|
-
}
|
|
79
|
+
const mismatches = lifecycle.requirementAlignment.mismatches;
|
|
82
80
|
if (mismatches.length > 0) {
|
|
83
81
|
warnings.push({
|
|
84
82
|
id: 'W10',
|
|
@@ -89,6 +87,25 @@ export function runTruthChecks(planningDir, frameworkDir, actualCheckIds) {
|
|
|
89
87
|
}
|
|
90
88
|
}
|
|
91
89
|
|
|
90
|
+
if (options.runtimeFreshnessReport?.issueCount > 0) {
|
|
91
|
+
warnings.push({
|
|
92
|
+
id: 'W11',
|
|
93
|
+
severity: 'WARN',
|
|
94
|
+
message: `Installed generated runtime and workflow-helper surfaces drift from current render output (${summarizeRuntimeFreshnessIssues(options.runtimeFreshnessReport)})`,
|
|
95
|
+
fix: getRuntimeFreshnessRepairGuidance(options.runtimeFreshnessReport),
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const drift = checkDrift(planningDir);
|
|
100
|
+
if (drift.drifted) {
|
|
101
|
+
warnings.push({
|
|
102
|
+
id: 'W12',
|
|
103
|
+
severity: 'WARN',
|
|
104
|
+
message: `Planning state drifted since last recorded session (${drift.details.join('; ')})`,
|
|
105
|
+
fix: 'Review the changes, then run a lifecycle workflow to update the fingerprint',
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
92
109
|
return warnings;
|
|
93
110
|
}
|
|
94
111
|
|
|
@@ -167,22 +184,3 @@ function extractSection(content, startMarker, endMarker) {
|
|
|
167
184
|
function normalizeContent(content) {
|
|
168
185
|
return content.replace(/\r\n/g, '\n');
|
|
169
186
|
}
|
|
170
|
-
|
|
171
|
-
function extractCheckedRequirements(content) {
|
|
172
|
-
const checked = new Set();
|
|
173
|
-
for (const result of content.matchAll(/- \[x\] \*\*\[([A-Z0-9-]+)]\*\*/g)) {
|
|
174
|
-
checked.add(result[1]);
|
|
175
|
-
}
|
|
176
|
-
return checked;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
function extractRoadmapRequirements(content) {
|
|
180
|
-
const mapped = new Map();
|
|
181
|
-
for (const result of content.matchAll(/- \[([ x-])] \*\*Phase\s+\d+[a-z]?:.*?\*\*\s+—\s+\[([^\]]+)]/g)) {
|
|
182
|
-
const requirementIds = result[2].split(',').map((id) => id.trim());
|
|
183
|
-
for (const requirementId of requirementIds) {
|
|
184
|
-
mapped.set(requirementId, result[1].toLowerCase() === 'x');
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
return mapped;
|
|
188
|
-
}
|
package/bin/lib/health.mjs
CHANGED
|
@@ -8,10 +8,12 @@ import { join } from 'path';
|
|
|
8
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
|
+
import { evaluateLifecycleState } from './lifecycle-state.mjs';
|
|
12
|
+
import { evaluateRuntimeFreshness } from './runtime-freshness.mjs';
|
|
11
13
|
|
|
12
14
|
/**
|
|
13
15
|
* Factory function returning the health command.
|
|
14
|
-
* ctx
|
|
16
|
+
* ctx should provide: { frameworkVersion, workflows }
|
|
15
17
|
*/
|
|
16
18
|
export function createCmdHealth(ctx) {
|
|
17
19
|
return async function cmdHealth(...healthArgs) {
|
|
@@ -19,7 +21,7 @@ export function createCmdHealth(ctx) {
|
|
|
19
21
|
const cwd = process.cwd();
|
|
20
22
|
const planningDir = join(cwd, '.planning');
|
|
21
23
|
const frameworkSourceMode = isFrameworkSourceRepo(cwd);
|
|
22
|
-
const healthCheckIds = ['E1', 'E2', 'E3', 'E4', 'E5', 'E6', 'E7', 'E8', 'W1', 'W2', 'W3', 'W4', 'W5', 'W6', ...TRUTH_CHECK_IDS, 'I1', 'I2', 'I3'];
|
|
24
|
+
const healthCheckIds = ['E1', 'E2', 'E3', 'E4', 'E5', 'E6', 'E7', 'E8', 'W1', 'W2', 'W3', 'W4', 'W5', 'W6', ...TRUTH_CHECK_IDS, 'I1', 'I2', 'I3'];
|
|
23
25
|
|
|
24
26
|
// Pre-init guard
|
|
25
27
|
if (!existsSync(join(planningDir, 'config.json'))) {
|
|
@@ -41,8 +43,10 @@ export function createCmdHealth(ctx) {
|
|
|
41
43
|
// E1: config.json missing (already handled by pre-init guard, but keep for completeness)
|
|
42
44
|
// E2: config.json missing required fields
|
|
43
45
|
let config = null;
|
|
46
|
+
let configOk = false;
|
|
44
47
|
try {
|
|
45
48
|
config = JSON.parse(readFileSync(join(planningDir, 'config.json'), 'utf-8'));
|
|
49
|
+
configOk = true;
|
|
46
50
|
const requiredFields = ['researchDepth', 'modelProfile', 'initVersion'];
|
|
47
51
|
const missing = requiredFields.filter((f) => !(f in config));
|
|
48
52
|
if (missing.length > 0) {
|
|
@@ -53,8 +57,10 @@ export function createCmdHealth(ctx) {
|
|
|
53
57
|
}
|
|
54
58
|
|
|
55
59
|
// E3: templates/ missing
|
|
56
|
-
const templatesDir = join(planningDir, 'templates');
|
|
57
|
-
const
|
|
60
|
+
const templatesDir = join(planningDir, 'templates');
|
|
61
|
+
const runtimeHelpersDir = join(planningDir, 'bin');
|
|
62
|
+
const hasTemplatesDir = existsSync(templatesDir);
|
|
63
|
+
const hasRuntimeHelpersDir = existsSync(runtimeHelpersDir);
|
|
58
64
|
const rolesDir = join(templatesDir, 'roles');
|
|
59
65
|
const delegatesDir = join(templatesDir, 'delegates');
|
|
60
66
|
const hasRolesDir = hasTemplatesDir && existsSync(rolesDir);
|
|
@@ -117,76 +123,60 @@ export function createCmdHealth(ctx) {
|
|
|
117
123
|
// --- WARNING checks ---
|
|
118
124
|
|
|
119
125
|
// W1: generation-manifest.json missing
|
|
120
|
-
const manifest = skipInstalledTemplateChecks ? null : readManifest(planningDir);
|
|
121
|
-
if (!manifest && !skipInstalledTemplateChecks) {
|
|
122
|
-
warnings.push({ id: 'W1', severity: 'WARN', message: 'generation-manifest.json missing', fix: 'Run `gsdd update
|
|
123
|
-
}
|
|
126
|
+
const manifest = skipInstalledTemplateChecks ? null : readManifest(planningDir);
|
|
127
|
+
if (!manifest && !skipInstalledTemplateChecks) {
|
|
128
|
+
warnings.push({ id: 'W1', severity: 'WARN', message: 'generation-manifest.json missing', fix: 'Run `gsdd update` to create' });
|
|
129
|
+
}
|
|
124
130
|
|
|
125
131
|
// W2 + W3: template/role hash mismatches and missing files
|
|
126
132
|
if (manifest && hasTemplatesDir) {
|
|
127
|
-
const allCategories = [
|
|
128
|
-
{ name: 'delegates', dir: delegatesDir, hashes: hasDelegatesDir ? manifest.templates?.delegates : null },
|
|
129
|
-
{ name: 'research', dir: join(templatesDir, 'research'), hashes: manifest.templates?.research },
|
|
130
|
-
{ name: 'codebase', dir: join(templatesDir, 'codebase'), hashes: manifest.templates?.codebase },
|
|
131
|
-
{ name: 'root templates', dir: templatesDir, hashes: manifest.templates?.root },
|
|
132
|
-
{ name: 'roles', dir: rolesDir, hashes: hasRolesDir ? manifest.roles : null },
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
133
|
+
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' },
|
|
140
|
+
];
|
|
141
|
+
|
|
142
|
+
for (const cat of allCategories) {
|
|
143
|
+
if (!cat.hashes) continue;
|
|
144
|
+
const result = detectModifications(cat.dir, cat.hashes);
|
|
145
|
+
if (result.modified.length > 0) {
|
|
146
|
+
warnings.push({ id: 'W2', severity: 'WARN', message: `${cat.name}: ${result.modified.length} file(s) modified locally (${result.modified.join(', ')})`, fix: `Intentional? Run \`${cat.fixCommand}\` to reset` });
|
|
147
|
+
}
|
|
148
|
+
if (result.missing.length > 0) {
|
|
149
|
+
warnings.push({ id: 'W3', severity: 'WARN', message: `${cat.name}: ${result.missing.length} file(s) missing from disk (${result.missing.join(', ')})`, fix: `Run \`${cat.fixCommand}\` to restore` });
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
146
153
|
|
|
147
154
|
// W4: ROADMAP.md references phases not found in .planning/phases/
|
|
148
155
|
const roadmapPath = join(planningDir, 'ROADMAP.md');
|
|
149
156
|
const phasesDir = join(planningDir, 'phases');
|
|
150
157
|
const roadmap = existsSync(roadmapPath) ? readFileSync(roadmapPath, 'utf-8') : null;
|
|
151
|
-
const
|
|
158
|
+
const lifecycle = evaluateLifecycleState({ planningDir });
|
|
152
159
|
|
|
153
160
|
if (roadmap && existsSync(phasesDir)) {
|
|
154
|
-
const
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
}
|
|
161
|
-
if (line.includes('</details>')) {
|
|
162
|
-
inDetails = false;
|
|
163
|
-
continue;
|
|
164
|
-
}
|
|
165
|
-
if (inDetails) continue;
|
|
166
|
-
const match = line.match(/^\s*[-*]\s*\[([x-])\]\s*\*\*Phase\s+(\d+[a-z]?)/i);
|
|
167
|
-
if (match) phaseLabels.push(normalizePhaseLabel(match[2]));
|
|
168
|
-
}
|
|
169
|
-
for (const label of phaseLabels) {
|
|
170
|
-
const hasFile = phaseArtifacts.some((artifact) => normalizePhaseLabel(artifact.phasePrefix) === label);
|
|
171
|
-
if (!hasFile) {
|
|
172
|
-
warnings.push({ id: 'W4', severity: 'WARN', message: `ROADMAP.md references active Phase ${label} but no files found in .planning/phases/`, fix: 'Create missing phase dirs or update ROADMAP' });
|
|
173
|
-
}
|
|
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
|
+
});
|
|
174
168
|
}
|
|
175
169
|
}
|
|
176
170
|
|
|
177
171
|
// W5: Phase dir has PLAN but no SUMMARY (stale in-progress)
|
|
178
|
-
if (
|
|
179
|
-
const
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
);
|
|
187
|
-
if (!hasSummary) {
|
|
188
|
-
warnings.push({ id: 'W5', severity: 'WARN', message: `${plan.displayPath} exists but no matching SUMMARY found (stale in-progress?)`, fix: 'Resume or complete the phase' });
|
|
189
|
-
}
|
|
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
|
+
});
|
|
190
180
|
}
|
|
191
181
|
}
|
|
192
182
|
|
|
@@ -202,7 +192,11 @@ export function createCmdHealth(ctx) {
|
|
|
202
192
|
warnings.push({ id: 'W6', severity: 'WARN', message: 'No adapter surfaces detected', fix: 'Run `gsdd init --tools <platform>`' });
|
|
203
193
|
}
|
|
204
194
|
|
|
205
|
-
|
|
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 }));
|
|
206
200
|
|
|
207
201
|
// --- INFO checks ---
|
|
208
202
|
|
|
@@ -212,20 +206,12 @@ export function createCmdHealth(ctx) {
|
|
|
212
206
|
}
|
|
213
207
|
|
|
214
208
|
// I2: Phase completion count
|
|
215
|
-
if (
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
if (match) {
|
|
222
|
-
total++;
|
|
223
|
-
if (match[1] === 'x') done++;
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
if (total > 0) {
|
|
227
|
-
info.push({ id: 'I2', severity: 'INFO', message: `Phases: ${done}/${total} completed` });
|
|
228
|
-
}
|
|
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
|
+
});
|
|
229
215
|
}
|
|
230
216
|
|
|
231
217
|
// I3: Which adapters are installed
|
|
@@ -268,34 +254,3 @@ function isFrameworkSourceRepo(cwd) {
|
|
|
268
254
|
return existsSync(join(cwd, 'distilled', 'templates')) && existsSync(join(cwd, 'distilled', 'workflows'));
|
|
269
255
|
}
|
|
270
256
|
|
|
271
|
-
function normalizePhaseLabel(value) {
|
|
272
|
-
if (!value) return null;
|
|
273
|
-
return value.toLowerCase().replace(/^0+(\d)/, '$1');
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
function listPhaseArtifacts(phasesDir) {
|
|
277
|
-
const artifacts = [];
|
|
278
|
-
for (const entry of readdirSync(phasesDir, { withFileTypes: true })) {
|
|
279
|
-
const entryPath = join(phasesDir, entry.name);
|
|
280
|
-
if (entry.isFile()) {
|
|
281
|
-
artifacts.push(createPhaseArtifact('', entry.name));
|
|
282
|
-
continue;
|
|
283
|
-
}
|
|
284
|
-
if (!entry.isDirectory()) continue;
|
|
285
|
-
for (const child of readdirSync(entryPath, { withFileTypes: true })) {
|
|
286
|
-
if (child.isFile()) {
|
|
287
|
-
artifacts.push(createPhaseArtifact(entry.name, child.name));
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
return artifacts;
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
function createPhaseArtifact(dir, name) {
|
|
295
|
-
return {
|
|
296
|
-
dir,
|
|
297
|
-
name,
|
|
298
|
-
displayPath: dir ? `${dir}/${name}` : name,
|
|
299
|
-
phasePrefix: name.match(/^(\d+[a-z]?(?:\.\d+)?)-/i)?.[1] || dir.match(/^(\d+[a-z]?(?:\.\d+)?)-/i)?.[1] || null,
|
|
300
|
-
};
|
|
301
|
-
}
|
package/bin/lib/init-flow.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync, cpSync } from 'fs';
|
|
2
|
-
import { join, isAbsolute } from 'path';
|
|
3
|
-
import { renderSkillContent } from './rendering.mjs';
|
|
4
|
-
import { buildManifest, writeManifest } from './manifest.mjs';
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, cpSync } from 'fs';
|
|
2
|
+
import { join, isAbsolute } from 'path';
|
|
3
|
+
import { renderPlanningCliLauncher, renderSkillContent } from './rendering.mjs';
|
|
4
|
+
import { buildManifest, readManifest, writeManifest } from './manifest.mjs';
|
|
5
5
|
import { parseFlagValue, parseToolsFlag, parseAutoFlag } from './cli-utils.mjs';
|
|
6
6
|
import { buildDefaultConfig, COST_PROFILES, RIGOR_PROFILES } from './models.mjs';
|
|
7
7
|
import { installProjectTemplates, refreshTemplates } from './templates.mjs';
|
|
@@ -106,8 +106,11 @@ export function createCmdInit(ctx) {
|
|
|
106
106
|
console.log(' - copied project brief to .planning/PROJECT_BRIEF.md');
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
-
generateOpenStandardSkills(ctx.cwd, ctx.workflows);
|
|
110
|
-
console.log(' - generated open-standard skills (.agents/skills/gsdd-*)');
|
|
109
|
+
generateOpenStandardSkills(ctx.cwd, ctx.workflows);
|
|
110
|
+
console.log(' - generated open-standard skills (.agents/skills/gsdd-*)');
|
|
111
|
+
|
|
112
|
+
generatePlanningCliLauncher(ctx);
|
|
113
|
+
console.log(' - generated local workflow helper (.planning/bin/gsdd.mjs)');
|
|
111
114
|
|
|
112
115
|
for (const adapter of resolveAdapters(ctx.adapters, interactiveSession.adapterTargets)) {
|
|
113
116
|
adapter.generate();
|
|
@@ -144,15 +147,25 @@ export function createCmdUpdate(ctx) {
|
|
|
144
147
|
updated = true;
|
|
145
148
|
}
|
|
146
149
|
|
|
147
|
-
if (platforms.length > 0 || existsSync(join(ctx.cwd, '.agents', 'skills'))) {
|
|
148
|
-
if (isDry) {
|
|
149
|
-
console.log(' - would update open-standard skills (.agents/skills/gsdd-*)');
|
|
150
|
-
} else {
|
|
151
|
-
generateOpenStandardSkills(ctx.cwd, ctx.workflows);
|
|
150
|
+
if (platforms.length > 0 || existsSync(join(ctx.cwd, '.agents', 'skills'))) {
|
|
151
|
+
if (isDry) {
|
|
152
|
+
console.log(' - would update open-standard skills (.agents/skills/gsdd-*)');
|
|
153
|
+
} else {
|
|
154
|
+
generateOpenStandardSkills(ctx.cwd, ctx.workflows);
|
|
152
155
|
console.log(' - updated open-standard skills (.agents/skills/gsdd-*)');
|
|
153
|
-
}
|
|
154
|
-
updated = true;
|
|
155
|
-
}
|
|
156
|
+
}
|
|
157
|
+
updated = true;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (existsSync(ctx.planningDir)) {
|
|
161
|
+
if (isDry) {
|
|
162
|
+
console.log(' - would update local workflow helper (.planning/bin/gsdd.mjs)');
|
|
163
|
+
} else {
|
|
164
|
+
generatePlanningCliLauncher(ctx);
|
|
165
|
+
console.log(' - updated local workflow helper (.planning/bin/gsdd.mjs)');
|
|
166
|
+
}
|
|
167
|
+
updated = true;
|
|
168
|
+
}
|
|
156
169
|
|
|
157
170
|
for (const adapter of getAdaptersToUpdate(ctx.adapters, platforms)) {
|
|
158
171
|
if (isDry) {
|
|
@@ -167,26 +180,70 @@ export function createCmdUpdate(ctx) {
|
|
|
167
180
|
|
|
168
181
|
if (!updated) {
|
|
169
182
|
console.log(' - no adapters found to update (run `gsdd init` first)');
|
|
170
|
-
} else if (isDry) {
|
|
171
|
-
console.log('\nDry run complete. No files were written.\n');
|
|
172
|
-
} else {
|
|
173
|
-
if (
|
|
174
|
-
const manifest =
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
183
|
+
} else if (isDry) {
|
|
184
|
+
console.log('\nDry run complete. No files were written.\n');
|
|
185
|
+
} else {
|
|
186
|
+
if (existsSync(ctx.planningDir)) {
|
|
187
|
+
const manifest = buildUpdateManifest({
|
|
188
|
+
planningDir: ctx.planningDir,
|
|
189
|
+
frameworkVersion: ctx.frameworkVersion,
|
|
190
|
+
updateTemplates: doTemplates,
|
|
191
|
+
});
|
|
192
|
+
if (manifest) {
|
|
193
|
+
writeManifest(ctx.planningDir, manifest);
|
|
194
|
+
console.log(' - updated generation manifest');
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
console.log('\nAdapters updated.\n');
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function generateOpenStandardSkills(cwd, workflows) {
|
|
203
|
+
for (const workflow of workflows) {
|
|
204
|
+
const dir = join(cwd, '.agents', 'skills', workflow.name);
|
|
205
|
+
mkdirSync(dir, { recursive: true });
|
|
206
|
+
writeFileSync(join(dir, 'SKILL.md'), renderSkillContent(workflow));
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function generatePlanningCliLauncher(ctx) {
|
|
211
|
+
const dir = join(ctx.planningDir, 'bin');
|
|
212
|
+
mkdirSync(dir, { recursive: true });
|
|
213
|
+
writeFileSync(
|
|
214
|
+
join(dir, 'gsdd.mjs'),
|
|
215
|
+
renderPlanningCliLauncher({
|
|
216
|
+
packageName: ctx.packageName,
|
|
217
|
+
packageVersion: ctx.packageVersion,
|
|
218
|
+
})
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function buildUpdateManifest({ planningDir, frameworkVersion, updateTemplates }) {
|
|
223
|
+
const existingManifest = readManifest(planningDir);
|
|
224
|
+
const nextManifest = buildManifest({ planningDir, frameworkVersion });
|
|
225
|
+
|
|
226
|
+
if (existingManifest && !updateTemplates) {
|
|
227
|
+
nextManifest.templates = existingManifest.templates ?? nextManifest.templates;
|
|
228
|
+
nextManifest.roles = existingManifest.roles ?? nextManifest.roles;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (existingManifest && manifestsEqualIgnoringTimestamp(existingManifest, nextManifest)) {
|
|
232
|
+
return null;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return nextManifest;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function manifestsEqualIgnoringTimestamp(left, right) {
|
|
239
|
+
return JSON.stringify(stripManifestTimestamp(left)) === JSON.stringify(stripManifestTimestamp(right));
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function stripManifestTimestamp(manifest) {
|
|
243
|
+
if (!manifest || typeof manifest !== 'object') return manifest;
|
|
244
|
+
const { generatedAt, ...rest } = manifest;
|
|
245
|
+
return rest;
|
|
246
|
+
}
|
|
190
247
|
|
|
191
248
|
async function ensureConfig({ cwd, planningDir, isAuto, promptApi, preselectedConfig = null }) {
|
|
192
249
|
const configFile = join(planningDir, 'config.json');
|
package/bin/lib/init-runtime.mjs
CHANGED
|
@@ -2,37 +2,37 @@ const RUNTIME_OPTIONS = [
|
|
|
2
2
|
{
|
|
3
3
|
id: 'claude',
|
|
4
4
|
label: 'Claude Code',
|
|
5
|
-
description: 'Directly validated native skills, commands, and agents',
|
|
5
|
+
description: 'Directly validated native skills, commands, and agents with local freshness checks',
|
|
6
6
|
kind: 'native',
|
|
7
7
|
},
|
|
8
8
|
{
|
|
9
9
|
id: 'opencode',
|
|
10
10
|
label: 'OpenCode',
|
|
11
|
-
description: 'Directly validated native slash commands and agents',
|
|
11
|
+
description: 'Directly validated native slash commands and agents with local freshness checks',
|
|
12
12
|
kind: 'native',
|
|
13
13
|
},
|
|
14
14
|
{
|
|
15
15
|
id: 'codex',
|
|
16
16
|
label: 'Codex CLI',
|
|
17
|
-
description: 'Directly validated portable skills plus native checker agents',
|
|
17
|
+
description: 'Directly validated portable skills plus native checker agents with local freshness checks',
|
|
18
18
|
kind: 'native',
|
|
19
19
|
},
|
|
20
20
|
{
|
|
21
21
|
id: 'cursor',
|
|
22
22
|
label: 'Cursor',
|
|
23
|
-
description: 'Qualified support via skills-native slash commands from .agents/skills/',
|
|
23
|
+
description: 'Qualified support via skills-native slash commands from .agents/skills/ with the same local freshness checks',
|
|
24
24
|
kind: 'skills_native',
|
|
25
25
|
},
|
|
26
26
|
{
|
|
27
27
|
id: 'copilot',
|
|
28
28
|
label: 'GitHub Copilot',
|
|
29
|
-
description: 'Qualified support via skills-native slash commands from .agents/skills/',
|
|
29
|
+
description: 'Qualified support via skills-native slash commands from .agents/skills/ with the same local freshness checks',
|
|
30
30
|
kind: 'skills_native',
|
|
31
31
|
},
|
|
32
32
|
{
|
|
33
33
|
id: 'gemini',
|
|
34
34
|
label: 'Gemini CLI',
|
|
35
|
-
description: 'Qualified support via skills-native slash commands from .agents/skills/',
|
|
35
|
+
description: 'Qualified support via skills-native slash commands from .agents/skills/ with the same local freshness checks',
|
|
36
36
|
kind: 'skills_native',
|
|
37
37
|
},
|
|
38
38
|
];
|
|
@@ -161,25 +161,31 @@ export function getPostInitRoutingLines(selectedRuntimes) {
|
|
|
161
161
|
|
|
162
162
|
export function getHelpText() {
|
|
163
163
|
return `
|
|
164
|
-
gsdd -
|
|
165
|
-
|
|
164
|
+
gsdd - Workspine CLI
|
|
165
|
+
Repo-native delivery spine for long-horizon AI-assisted work across coding runtimes.
|
|
166
166
|
|
|
167
167
|
Usage: gsdd <command> [args]
|
|
168
168
|
|
|
169
|
-
Commands:
|
|
170
|
-
init [--tools <platform>] [--auto] [--brief <file>]
|
|
171
|
-
Launch guided install wizard in TTYs, or use --tools for manual/headless setup
|
|
172
|
-
--auto: non-interactive mode with smart defaults (requires --tools)
|
|
173
|
-
--brief <file>: copy project brief to .planning/PROJECT_BRIEF.md
|
|
169
|
+
Commands:
|
|
170
|
+
init [--tools <platform>] [--auto] [--brief <file>]
|
|
171
|
+
Launch guided install wizard in TTYs, or use --tools for manual/headless setup
|
|
172
|
+
--auto: non-interactive mode with smart defaults (requires --tools)
|
|
173
|
+
--brief <file>: copy project brief to .planning/PROJECT_BRIEF.md
|
|
174
174
|
update [--tools <platform>] [--templates] [--dry]
|
|
175
175
|
Regenerate adapters from latest framework sources
|
|
176
176
|
--templates: also refresh .planning/templates/ and roles
|
|
177
177
|
--dry: preview changes without writing files
|
|
178
|
-
health [--json] Check workspace integrity (healthy/degraded/broken)
|
|
179
|
-
models [subcommand] Inspect or update model profile / runtime overrides
|
|
180
|
-
find-phase [N] Show phase info as JSON (for agent consumption)
|
|
181
|
-
verify <N> Run artifact checks for phase N
|
|
182
|
-
scaffold phase <N> [name] Create a new phase plan file
|
|
178
|
+
health [--json] Check workspace integrity (healthy/degraded/broken)
|
|
179
|
+
models [subcommand] Inspect or update model profile / runtime overrides
|
|
180
|
+
find-phase [N] Show phase info as JSON (for agent consumption)
|
|
181
|
+
verify <N> Run artifact checks for phase N
|
|
182
|
+
scaffold phase <N> [name] Create a new phase plan file
|
|
183
|
+
file-op <copy|delete|regex-sub>
|
|
184
|
+
Run deterministic workspace-confined file copy/delete/text mutation
|
|
185
|
+
phase-status <N> <status> Update ROADMAP.md phase status ([ ] / [-] / [x])
|
|
186
|
+
lifecycle-preflight <surface> [phase]
|
|
187
|
+
Inspect deterministic lifecycle gate results for a workflow surface
|
|
188
|
+
help Show this summary
|
|
183
189
|
|
|
184
190
|
Platforms (for --tools):
|
|
185
191
|
claude Generate Claude Code skills (.claude/skills/gsdd-*), commands (.claude/commands/gsdd-*.md), and native agents (.claude/agents/gsdd-*.md)
|
|
@@ -192,13 +198,16 @@ Platforms (for --tools):
|
|
|
192
198
|
all Generate all adapters (Claude, OpenCode, Codex, AGENTS.md, Cursor, Copilot, Gemini)
|
|
193
199
|
|
|
194
200
|
Notes:
|
|
195
|
-
- init always generates open-standard skills at .agents/skills/gsdd-* (portable workflow entrypoints)
|
|
201
|
+
- init always generates open-standard skills at .agents/skills/gsdd-* (portable workflow entrypoints)
|
|
202
|
+
- init also generates .planning/bin/gsdd.mjs so workflow-embedded helper commands do not depend on a global gsdd binary
|
|
203
|
+
- Workspine is the public product name; the retained package, command, workflow, and workspace contracts stay gsdd-cli, gsdd, gsdd-*, and .planning/
|
|
196
204
|
- running plain \`gsdd init\` in a terminal opens the guided runtime-selection wizard
|
|
197
205
|
- the wizard lets you pick runtimes first, then separately decide whether repo-wide AGENTS.md governance is worth installing
|
|
206
|
+
- \`gsdd health\` compares any installed generated runtime surfaces against current render output and points back to \`gsdd update\` when they drift
|
|
198
207
|
- directly validated launch surfaces in this repo are Claude Code, OpenCode, and Codex CLI
|
|
199
208
|
- Cursor, Copilot, and Gemini are qualified support through the shared .agents/skills/ surface plus optional governance
|
|
200
209
|
- --tools remains the advanced/manual path and preserves legacy runtime aliases for backward compatibility
|
|
201
|
-
- --tools codex generates .codex/agents/gsdd-plan-checker.toml (portable skill is the entry surface)
|
|
210
|
+
- --tools codex generates .codex/agents/gsdd-plan-checker.toml (portable skill is the entry surface; $gsdd-plan is plan-only until explicit $gsdd-execute)
|
|
202
211
|
- root AGENTS.md is only written on init when explicitly requested via --tools agents, --tools all, or the wizard governance opt-in
|
|
203
212
|
|
|
204
213
|
Examples:
|
|
@@ -219,8 +228,20 @@ Examples:
|
|
|
219
228
|
npx gsdd-cli verify 1
|
|
220
229
|
npx gsdd-cli scaffold phase 4 Payments
|
|
221
230
|
|
|
222
|
-
Workflows (run via skills/adapters generated by init, not direct CLI):
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
231
|
+
Workflows (run via skills/adapters generated by init, not direct CLI):
|
|
232
|
+
gsdd-new-project Full initializer: questioning, brownfield audit, research, spec, roadmap
|
|
233
|
+
gsdd-map-codebase Map or refresh brownfield codebase context
|
|
234
|
+
gsdd-plan Research, plan, and fresh-context plan check for a phase
|
|
235
|
+
gsdd-execute Execute a phase plan and write phase summaries
|
|
236
|
+
gsdd-verify Verify a completed phase with 3-level checks
|
|
237
|
+
gsdd-verify-work Conversational UAT validation for user-facing behavior
|
|
238
|
+
gsdd-audit-milestone Cross-phase integration, requirements coverage, and E2E audit
|
|
239
|
+
gsdd-complete-milestone Archive a shipped milestone and collapse roadmap state
|
|
240
|
+
gsdd-new-milestone Start the next milestone with goals, requirements, and phases
|
|
241
|
+
gsdd-plan-milestone-gaps Turn milestone-audit gaps into closure phases
|
|
242
|
+
gsdd-quick Bounded brownfield lane for sub-hour work
|
|
243
|
+
gsdd-pause Save session context to checkpoint
|
|
244
|
+
gsdd-resume Restore context and route to the next action
|
|
245
|
+
gsdd-progress Read-only status and routing surface
|
|
246
|
+
`;
|
|
247
|
+
}
|
package/bin/lib/init.mjs
CHANGED
|
@@ -4,13 +4,9 @@ import { createCmdInit, createCmdUpdate } from './init-flow.mjs';
|
|
|
4
4
|
import { createInitPromptApi, promptChoiceList } from './init-prompts.mjs';
|
|
5
5
|
import { buildRuntimeChoices, detectPlatforms, getHelpText, normalizeRequestedTools } from './init-runtime.mjs';
|
|
6
6
|
|
|
7
|
-
function cmdHelp() {
|
|
8
|
-
console.log(
|
|
9
|
-
|
|
10
|
-
Run deterministic workspace-confined file copy/delete/text mutation
|
|
11
|
-
phase-status <N> <status> Update ROADMAP.md phase status ([ ] / [-] / [x])
|
|
12
|
-
`);
|
|
13
|
-
}
|
|
7
|
+
function cmdHelp() {
|
|
8
|
+
console.log(getHelpText().trimEnd());
|
|
9
|
+
}
|
|
14
10
|
|
|
15
11
|
export {
|
|
16
12
|
buildRuntimeChoices,
|