gsdd-cli 0.3.1 → 0.18.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.
- package/README.md +131 -67
- package/agents/DISTILLATION.md +15 -13
- package/agents/README.md +1 -1
- package/agents/planner.md +2 -0
- package/bin/adapters/agents.mjs +1 -0
- package/bin/adapters/claude.mjs +20 -4
- package/bin/adapters/codex.mjs +9 -1
- package/bin/adapters/opencode.mjs +20 -5
- package/bin/gsdd.mjs +24 -7
- package/bin/lib/cli-utils.mjs +1 -1
- package/bin/lib/evidence-contract.mjs +112 -0
- package/bin/lib/file-ops.mjs +161 -0
- package/bin/lib/health-truth.mjs +186 -0
- package/bin/lib/health.mjs +72 -67
- package/bin/lib/init-flow.mjs +50 -3
- package/bin/lib/init-prompts.mjs +22 -83
- package/bin/lib/init-runtime.mjs +47 -25
- package/bin/lib/init.mjs +3 -3
- package/bin/lib/lifecycle-preflight.mjs +333 -0
- package/bin/lib/lifecycle-state.mjs +293 -0
- package/bin/lib/models.mjs +19 -4
- package/bin/lib/phase.mjs +159 -18
- package/bin/lib/plan-constants.mjs +30 -0
- package/bin/lib/provenance.mjs +165 -0
- package/bin/lib/rendering.mjs +8 -0
- package/bin/lib/runtime-freshness.mjs +239 -0
- package/bin/lib/session-fingerprint.mjs +106 -0
- package/bin/lib/templates.mjs +17 -0
- package/distilled/DESIGN.md +733 -49
- package/distilled/EVIDENCE-INDEX.md +402 -0
- package/distilled/README.md +73 -33
- package/distilled/SKILL.md +89 -85
- package/distilled/templates/agents.block.md +13 -84
- package/distilled/templates/agents.md +0 -7
- package/distilled/templates/delegates/plan-checker.md +6 -3
- package/distilled/workflows/audit-milestone.md +56 -6
- package/distilled/workflows/complete-milestone.md +333 -0
- package/distilled/workflows/execute.md +201 -19
- package/distilled/workflows/map-codebase.md +17 -4
- package/distilled/workflows/new-milestone.md +262 -0
- package/distilled/workflows/new-project.md +7 -6
- package/distilled/workflows/pause.md +40 -6
- package/distilled/workflows/plan-milestone-gaps.md +183 -0
- package/distilled/workflows/plan.md +77 -11
- package/distilled/workflows/progress.md +107 -29
- package/distilled/workflows/quick.md +23 -12
- package/distilled/workflows/resume.md +135 -12
- package/distilled/workflows/verify-work.md +260 -0
- package/distilled/workflows/verify.md +159 -33
- package/docs/BROWNFIELD-PROOF.md +95 -0
- package/docs/RUNTIME-SUPPORT.md +77 -0
- package/docs/USER-GUIDE.md +439 -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 +38 -29
package/bin/lib/health.mjs
CHANGED
|
@@ -7,16 +7,21 @@ import { existsSync, readFileSync, readdirSync } from 'fs';
|
|
|
7
7
|
import { join } from 'path';
|
|
8
8
|
import { readManifest, detectModifications } from './manifest.mjs';
|
|
9
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';
|
|
10
13
|
|
|
11
14
|
/**
|
|
12
15
|
* Factory function returning the health command.
|
|
13
|
-
* ctx
|
|
16
|
+
* ctx should provide: { frameworkVersion, workflows }
|
|
14
17
|
*/
|
|
15
18
|
export function createCmdHealth(ctx) {
|
|
16
19
|
return async function cmdHealth(...healthArgs) {
|
|
17
20
|
const jsonMode = healthArgs.includes('--json');
|
|
18
21
|
const cwd = process.cwd();
|
|
19
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'];
|
|
20
25
|
|
|
21
26
|
// Pre-init guard
|
|
22
27
|
if (!existsSync(join(planningDir, 'config.json'))) {
|
|
@@ -38,8 +43,10 @@ export function createCmdHealth(ctx) {
|
|
|
38
43
|
// E1: config.json missing (already handled by pre-init guard, but keep for completeness)
|
|
39
44
|
// E2: config.json missing required fields
|
|
40
45
|
let config = null;
|
|
46
|
+
let configOk = false;
|
|
41
47
|
try {
|
|
42
48
|
config = JSON.parse(readFileSync(join(planningDir, 'config.json'), 'utf-8'));
|
|
49
|
+
configOk = true;
|
|
43
50
|
const requiredFields = ['researchDepth', 'modelProfile', 'initVersion'];
|
|
44
51
|
const missing = requiredFields.filter((f) => !(f in config));
|
|
45
52
|
if (missing.length > 0) {
|
|
@@ -56,10 +63,11 @@ export function createCmdHealth(ctx) {
|
|
|
56
63
|
const delegatesDir = join(templatesDir, 'delegates');
|
|
57
64
|
const hasRolesDir = hasTemplatesDir && existsSync(rolesDir);
|
|
58
65
|
const hasDelegatesDir = hasTemplatesDir && existsSync(delegatesDir);
|
|
66
|
+
const skipInstalledTemplateChecks = !hasTemplatesDir && frameworkSourceMode;
|
|
59
67
|
|
|
60
|
-
if (!hasTemplatesDir) {
|
|
68
|
+
if (!hasTemplatesDir && !skipInstalledTemplateChecks) {
|
|
61
69
|
errors.push({ id: 'E3', severity: 'ERROR', message: '.planning/templates/ missing', fix: 'Run `gsdd update --templates`' });
|
|
62
|
-
} else {
|
|
70
|
+
} else if (hasTemplatesDir) {
|
|
63
71
|
// E4: roles/ missing or empty
|
|
64
72
|
if (!hasRolesDir) {
|
|
65
73
|
errors.push({ id: 'E4', severity: 'ERROR', message: '.planning/templates/roles/ missing', fix: 'Run `gsdd update --templates`' });
|
|
@@ -79,13 +87,42 @@ export function createCmdHealth(ctx) {
|
|
|
79
87
|
errors.push({ id: 'E5', severity: 'ERROR', message: '.planning/templates/delegates/ has 0 delegate files', fix: 'Run `gsdd update --templates`' });
|
|
80
88
|
}
|
|
81
89
|
}
|
|
90
|
+
|
|
91
|
+
// E6: research/ missing or empty
|
|
92
|
+
const researchDir = join(templatesDir, 'research');
|
|
93
|
+
if (!existsSync(researchDir)) {
|
|
94
|
+
errors.push({ id: 'E6', severity: 'ERROR', message: '.planning/templates/research/ missing', fix: 'Run `gsdd update --templates`' });
|
|
95
|
+
} else {
|
|
96
|
+
const researchFiles = readdirSync(researchDir).filter((f) => f.endsWith('.md'));
|
|
97
|
+
if (researchFiles.length === 0) {
|
|
98
|
+
errors.push({ id: 'E6', severity: 'ERROR', message: '.planning/templates/research/ has 0 template files', fix: 'Run `gsdd update --templates`' });
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// E7: codebase/ missing or empty
|
|
103
|
+
const codebaseDir = join(templatesDir, 'codebase');
|
|
104
|
+
if (!existsSync(codebaseDir)) {
|
|
105
|
+
errors.push({ id: 'E7', severity: 'ERROR', message: '.planning/templates/codebase/ missing', fix: 'Run `gsdd update --templates`' });
|
|
106
|
+
} else {
|
|
107
|
+
const codebaseFiles = readdirSync(codebaseDir).filter((f) => f.endsWith('.md'));
|
|
108
|
+
if (codebaseFiles.length === 0) {
|
|
109
|
+
errors.push({ id: 'E7', severity: 'ERROR', message: '.planning/templates/codebase/ has 0 template files', fix: 'Run `gsdd update --templates`' });
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// E8: critical root template files missing
|
|
114
|
+
const requiredRootFiles = ['spec.md', 'roadmap.md', 'auth-matrix.md'];
|
|
115
|
+
const missingRoot = requiredRootFiles.filter((f) => !existsSync(join(templatesDir, f)));
|
|
116
|
+
if (missingRoot.length > 0) {
|
|
117
|
+
errors.push({ id: 'E8', severity: 'ERROR', message: `.planning/templates/ missing critical root files: ${missingRoot.join(', ')}`, fix: 'Run `gsdd update --templates`' });
|
|
118
|
+
}
|
|
82
119
|
}
|
|
83
120
|
|
|
84
121
|
// --- WARNING checks ---
|
|
85
122
|
|
|
86
123
|
// W1: generation-manifest.json missing
|
|
87
|
-
const manifest = readManifest(planningDir);
|
|
88
|
-
if (!manifest) {
|
|
124
|
+
const manifest = skipInstalledTemplateChecks ? null : readManifest(planningDir);
|
|
125
|
+
if (!manifest && !skipInstalledTemplateChecks) {
|
|
89
126
|
warnings.push({ id: 'W1', severity: 'WARN', message: 'generation-manifest.json missing', fix: 'Run `gsdd update --templates` to create' });
|
|
90
127
|
}
|
|
91
128
|
|
|
@@ -115,36 +152,28 @@ export function createCmdHealth(ctx) {
|
|
|
115
152
|
const roadmapPath = join(planningDir, 'ROADMAP.md');
|
|
116
153
|
const phasesDir = join(planningDir, 'phases');
|
|
117
154
|
const roadmap = existsSync(roadmapPath) ? readFileSync(roadmapPath, 'utf-8') : null;
|
|
118
|
-
const
|
|
155
|
+
const lifecycle = evaluateLifecycleState({ planningDir });
|
|
119
156
|
|
|
120
157
|
if (roadmap && existsSync(phasesDir)) {
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
const hasFile = phaseArtifacts.some((artifact) => artifact.phasePrefix === padded || artifact.phasePrefix === String(num));
|
|
129
|
-
if (!hasFile) {
|
|
130
|
-
warnings.push({ id: 'W4', severity: 'WARN', message: `ROADMAP.md references Phase ${num} but no files found in .planning/phases/`, fix: 'Create missing phase dirs or update ROADMAP' });
|
|
131
|
-
}
|
|
158
|
+
for (const phase of lifecycle.phases.filter((entry) => entry.status !== 'not_started' && !entry.hasArtifacts)) {
|
|
159
|
+
warnings.push({
|
|
160
|
+
id: 'W4',
|
|
161
|
+
severity: 'WARN',
|
|
162
|
+
message: `ROADMAP.md references active Phase ${phase.number} but no files found in .planning/phases/`,
|
|
163
|
+
fix: 'Create missing phase dirs or update ROADMAP',
|
|
164
|
+
});
|
|
132
165
|
}
|
|
133
166
|
}
|
|
134
167
|
|
|
135
168
|
// W5: Phase dir has PLAN but no SUMMARY (stale in-progress)
|
|
136
|
-
if (
|
|
137
|
-
const
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
);
|
|
145
|
-
if (!hasSummary) {
|
|
146
|
-
warnings.push({ id: 'W5', severity: 'WARN', message: `${plan.displayPath} exists but no matching SUMMARY found (stale in-progress?)`, fix: 'Resume or complete the phase' });
|
|
147
|
-
}
|
|
169
|
+
if (lifecycle.incompletePlans.length > 0) {
|
|
170
|
+
for (const plan of lifecycle.incompletePlans) {
|
|
171
|
+
warnings.push({
|
|
172
|
+
id: 'W5',
|
|
173
|
+
severity: 'WARN',
|
|
174
|
+
message: `${plan.displayPath} exists but no matching SUMMARY found (stale in-progress?)`,
|
|
175
|
+
fix: 'Resume or complete the phase',
|
|
176
|
+
});
|
|
148
177
|
}
|
|
149
178
|
}
|
|
150
179
|
|
|
@@ -160,6 +189,12 @@ export function createCmdHealth(ctx) {
|
|
|
160
189
|
warnings.push({ id: 'W6', severity: 'WARN', message: 'No adapter surfaces detected', fix: 'Run `gsdd init --tools <platform>`' });
|
|
161
190
|
}
|
|
162
191
|
|
|
192
|
+
const runtimeFreshnessReport = configOk && Array.isArray(ctx.workflows)
|
|
193
|
+
? evaluateRuntimeFreshness({ cwd, workflows: ctx.workflows })
|
|
194
|
+
: null;
|
|
195
|
+
|
|
196
|
+
warnings.push(...runTruthChecks(planningDir, cwd, healthCheckIds, { runtimeFreshnessReport }));
|
|
197
|
+
|
|
163
198
|
// --- INFO checks ---
|
|
164
199
|
|
|
165
200
|
// I1: generation manifest was produced by a different framework version
|
|
@@ -168,20 +203,12 @@ export function createCmdHealth(ctx) {
|
|
|
168
203
|
}
|
|
169
204
|
|
|
170
205
|
// I2: Phase completion count
|
|
171
|
-
if (
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
if (match) {
|
|
178
|
-
total++;
|
|
179
|
-
if (match[1] === 'x') done++;
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
if (total > 0) {
|
|
183
|
-
info.push({ id: 'I2', severity: 'INFO', message: `Phases: ${done}/${total} completed` });
|
|
184
|
-
}
|
|
206
|
+
if (lifecycle.counts.total > 0) {
|
|
207
|
+
info.push({
|
|
208
|
+
id: 'I2',
|
|
209
|
+
severity: 'INFO',
|
|
210
|
+
message: `Phases: ${lifecycle.counts.completed}/${lifecycle.counts.total} completed`,
|
|
211
|
+
});
|
|
185
212
|
}
|
|
186
213
|
|
|
187
214
|
// I3: Which adapters are installed
|
|
@@ -220,29 +247,7 @@ export function createCmdHealth(ctx) {
|
|
|
220
247
|
};
|
|
221
248
|
}
|
|
222
249
|
|
|
223
|
-
function
|
|
224
|
-
|
|
225
|
-
for (const entry of readdirSync(phasesDir, { withFileTypes: true })) {
|
|
226
|
-
const entryPath = join(phasesDir, entry.name);
|
|
227
|
-
if (entry.isFile()) {
|
|
228
|
-
artifacts.push(createPhaseArtifact('', entry.name));
|
|
229
|
-
continue;
|
|
230
|
-
}
|
|
231
|
-
if (!entry.isDirectory()) continue;
|
|
232
|
-
for (const child of readdirSync(entryPath, { withFileTypes: true })) {
|
|
233
|
-
if (child.isFile()) {
|
|
234
|
-
artifacts.push(createPhaseArtifact(entry.name, child.name));
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
return artifacts;
|
|
250
|
+
function isFrameworkSourceRepo(cwd) {
|
|
251
|
+
return existsSync(join(cwd, 'distilled', 'templates')) && existsSync(join(cwd, 'distilled', 'workflows'));
|
|
239
252
|
}
|
|
240
253
|
|
|
241
|
-
function createPhaseArtifact(dir, name) {
|
|
242
|
-
return {
|
|
243
|
-
dir,
|
|
244
|
-
name,
|
|
245
|
-
displayPath: dir ? `${dir}/${name}` : name,
|
|
246
|
-
phasePrefix: name.match(/^(\d+(?:\.\d+)?)-/)?.[1] || dir.match(/^(\d+(?:\.\d+)?)-/)?.[1] || null,
|
|
247
|
-
};
|
|
248
|
-
}
|
package/bin/lib/init-flow.mjs
CHANGED
|
@@ -3,7 +3,7 @@ import { join, isAbsolute } from 'path';
|
|
|
3
3
|
import { renderSkillContent } from './rendering.mjs';
|
|
4
4
|
import { buildManifest, writeManifest } from './manifest.mjs';
|
|
5
5
|
import { parseFlagValue, parseToolsFlag, parseAutoFlag } from './cli-utils.mjs';
|
|
6
|
-
import { buildDefaultConfig } from './models.mjs';
|
|
6
|
+
import { buildDefaultConfig, COST_PROFILES, RIGOR_PROFILES } from './models.mjs';
|
|
7
7
|
import { installProjectTemplates, refreshTemplates } from './templates.mjs';
|
|
8
8
|
import {
|
|
9
9
|
detectPlatforms,
|
|
@@ -15,9 +15,34 @@ import {
|
|
|
15
15
|
} from './init-runtime.mjs';
|
|
16
16
|
import { createInitPromptApi } from './init-prompts.mjs';
|
|
17
17
|
|
|
18
|
+
function validateKindContract(adapter, cwd) {
|
|
19
|
+
if (!adapter.subagentFiles) return;
|
|
20
|
+
if (adapter.kind === 'native_capable') {
|
|
21
|
+
const missing = adapter.subagentFiles
|
|
22
|
+
.map(f => join(cwd, f))
|
|
23
|
+
.filter(p => !existsSync(p));
|
|
24
|
+
if (missing.length > 0) {
|
|
25
|
+
console.warn(
|
|
26
|
+
`[WARN] ${adapter.name} adapter (kind=native_capable) missing expected subagent files:\n` +
|
|
27
|
+
missing.map(p => ` - ${p}`).join('\n')
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
} else if (adapter.kind === 'governance_only') {
|
|
31
|
+
const unexpected = adapter.subagentFiles
|
|
32
|
+
.map(f => join(cwd, f))
|
|
33
|
+
.filter(p => existsSync(p));
|
|
34
|
+
if (unexpected.length > 0) {
|
|
35
|
+
console.warn(
|
|
36
|
+
`[WARN] ${adapter.name} adapter (kind=governance_only) unexpectedly generated subagent files:\n` +
|
|
37
|
+
unexpected.map(p => ` - ${p}`).join('\n')
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
18
43
|
export function createCmdInit(ctx) {
|
|
19
44
|
return async function cmdInit(...initArgs) {
|
|
20
|
-
console.log('gsdd init - setting up
|
|
45
|
+
console.log('gsdd init - setting up GSDD workflow\n');
|
|
21
46
|
|
|
22
47
|
const isAuto = parseAutoFlag(initArgs);
|
|
23
48
|
const toolsFlag = parseFlagValue(initArgs, '--tools');
|
|
@@ -86,6 +111,7 @@ export function createCmdInit(ctx) {
|
|
|
86
111
|
|
|
87
112
|
for (const adapter of resolveAdapters(ctx.adapters, interactiveSession.adapterTargets)) {
|
|
88
113
|
adapter.generate();
|
|
114
|
+
validateKindContract(adapter, ctx.cwd);
|
|
89
115
|
console.log(` - ${adapter.summary('generated')}`);
|
|
90
116
|
}
|
|
91
117
|
|
|
@@ -93,7 +119,8 @@ export function createCmdInit(ctx) {
|
|
|
93
119
|
writeManifest(ctx.planningDir, manifest);
|
|
94
120
|
console.log(' - wrote generation manifest');
|
|
95
121
|
|
|
96
|
-
console.log('\
|
|
122
|
+
console.log('\nGSDD initialized.');
|
|
123
|
+
printInitSummary(interactiveSession.config ?? buildDefaultConfig({ autoAdvance: isAuto }));
|
|
97
124
|
console.log('Next: run the new-project workflow to produce SPEC.md and ROADMAP.md:\n');
|
|
98
125
|
printPostInitRouting(interactiveSession.selectedRuntimes);
|
|
99
126
|
};
|
|
@@ -132,6 +159,7 @@ export function createCmdUpdate(ctx) {
|
|
|
132
159
|
console.log(` - would update ${adapter.name} adapter`);
|
|
133
160
|
} else {
|
|
134
161
|
adapter.generate();
|
|
162
|
+
validateKindContract(adapter, ctx.cwd);
|
|
135
163
|
console.log(` - ${adapter.summary('updated')}`);
|
|
136
164
|
}
|
|
137
165
|
updated = true;
|
|
@@ -218,3 +246,22 @@ function printPostInitRouting(selectedRuntimes) {
|
|
|
218
246
|
}
|
|
219
247
|
console.log('');
|
|
220
248
|
}
|
|
249
|
+
|
|
250
|
+
function printInitSummary(config) {
|
|
251
|
+
const rigor = Object.entries(RIGOR_PROFILES).find(([, profile]) => (
|
|
252
|
+
profile.researchDepth === config.researchDepth
|
|
253
|
+
&& profile.workflow.research === config.workflow?.research
|
|
254
|
+
&& profile.workflow.discuss === config.workflow?.discuss
|
|
255
|
+
&& profile.workflow.planCheck === config.workflow?.planCheck
|
|
256
|
+
&& profile.workflow.verifier === config.workflow?.verifier
|
|
257
|
+
))?.[0] ?? 'balanced';
|
|
258
|
+
const cost = Object.entries(COST_PROFILES).find(([, profile]) => (
|
|
259
|
+
profile.modelProfile === config.modelProfile
|
|
260
|
+
&& profile.parallelization === config.parallelization
|
|
261
|
+
))?.[0] ?? 'balanced';
|
|
262
|
+
|
|
263
|
+
console.log(`Rigor: ${rigor} Cost: ${cost} Track .planning/ in git: ${config.commitDocs ? 'yes' : 'no'}`);
|
|
264
|
+
console.log('Workflows: new-project → plan → execute → verify → progress');
|
|
265
|
+
console.log('Edit .planning/config.json to fine-tune (verifier, gitProtocol, individual workflow flags).');
|
|
266
|
+
console.log('');
|
|
267
|
+
}
|
package/bin/lib/init-prompts.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as readline from 'readline';
|
|
2
|
-
import { DEFAULT_GIT_PROTOCOL,
|
|
2
|
+
import { DEFAULT_GIT_PROTOCOL, resolveCost, resolveRigor } from './models.mjs';
|
|
3
3
|
import { buildRuntimeChoices, INIT_VERSION, resolveWizardAdapterTargets } from './init-runtime.mjs';
|
|
4
4
|
|
|
5
5
|
const ANSI = {
|
|
@@ -51,112 +51,51 @@ export function createInitPromptApi({ input = process.stdin, output = process.st
|
|
|
51
51
|
export async function promptForConfig(cwd, { input = process.stdin, output = process.stdout } = {}) {
|
|
52
52
|
output.write(`\n${ANSI.bold}Step 3 of 3 - Configure planning defaults${ANSI.reset}\n`);
|
|
53
53
|
|
|
54
|
-
const
|
|
54
|
+
const rigor = await promptSingleSelect({
|
|
55
55
|
input,
|
|
56
56
|
output,
|
|
57
|
-
title: '
|
|
57
|
+
title: 'Rigor',
|
|
58
58
|
choices: [
|
|
59
|
-
{ value: '
|
|
60
|
-
{ value: '
|
|
61
|
-
{ value: '
|
|
59
|
+
{ value: 'quick', label: 'quick', description: 'Faster setup with lighter planning rigor' },
|
|
60
|
+
{ value: 'balanced', label: 'balanced', description: 'Recommended default for most projects' },
|
|
61
|
+
{ value: 'thorough', label: 'thorough', description: 'Maximum research and plan review rigor' },
|
|
62
62
|
],
|
|
63
|
-
defaultIndex:
|
|
63
|
+
defaultIndex: 1,
|
|
64
64
|
});
|
|
65
|
-
const
|
|
65
|
+
const cost = await promptSingleSelect({
|
|
66
66
|
input,
|
|
67
67
|
output,
|
|
68
|
-
title: '
|
|
69
|
-
choices:
|
|
70
|
-
|
|
68
|
+
title: 'Cost',
|
|
69
|
+
choices: [
|
|
70
|
+
{ value: 'budget', label: 'budget', description: 'Lower-cost model profile and no parallelization' },
|
|
71
|
+
{ value: 'balanced', label: 'balanced', description: 'Recommended cost/quality tradeoff' },
|
|
72
|
+
{ value: 'quality', label: 'quality', description: 'Maximum quality with parallel work enabled' },
|
|
73
|
+
],
|
|
74
|
+
defaultIndex: 1,
|
|
71
75
|
});
|
|
72
76
|
const commitDocs = await promptSingleSelect({
|
|
73
77
|
input,
|
|
74
78
|
output,
|
|
75
79
|
title: 'Planning docs in git',
|
|
76
|
-
choices: yesNoChoices('Track .planning/ in git?', true),
|
|
77
|
-
defaultIndex: 0,
|
|
78
|
-
});
|
|
79
|
-
const modelProfile = await promptSingleSelect({
|
|
80
|
-
input,
|
|
81
|
-
output,
|
|
82
|
-
title: 'Model profile',
|
|
83
80
|
choices: [
|
|
84
|
-
{ value:
|
|
85
|
-
{ value:
|
|
86
|
-
{ value: 'budget', label: 'budget', description: 'Cheapest and fastest model profile' },
|
|
81
|
+
{ value: true, label: 'yes', description: 'Track .planning/ in git.' },
|
|
82
|
+
{ value: false, label: 'no', description: 'Keep .planning/ local only.' },
|
|
87
83
|
],
|
|
88
84
|
defaultIndex: 0,
|
|
89
85
|
});
|
|
90
|
-
const workflowResearch = await promptSingleSelect({
|
|
91
|
-
input,
|
|
92
|
-
output,
|
|
93
|
-
title: 'Workflow research',
|
|
94
|
-
choices: yesNoChoices('Research before planning each phase?', true),
|
|
95
|
-
defaultIndex: 0,
|
|
96
|
-
});
|
|
97
|
-
const workflowDiscuss = await promptSingleSelect({
|
|
98
|
-
input,
|
|
99
|
-
output,
|
|
100
|
-
title: 'Approach discussion',
|
|
101
|
-
choices: yesNoChoices('Explore approaches with the user before planning?', false),
|
|
102
|
-
defaultIndex: 0,
|
|
103
|
-
});
|
|
104
|
-
const workflowPlanCheck = await promptSingleSelect({
|
|
105
|
-
input,
|
|
106
|
-
output,
|
|
107
|
-
title: 'Plan checking',
|
|
108
|
-
choices: yesNoChoices('Run the fresh-context plan checker before execution?', true),
|
|
109
|
-
defaultIndex: 0,
|
|
110
|
-
});
|
|
111
|
-
const workflowVerifier = await promptSingleSelect({
|
|
112
|
-
input,
|
|
113
|
-
output,
|
|
114
|
-
title: 'Phase verification',
|
|
115
|
-
choices: yesNoChoices('Run phase verification after execution?', true),
|
|
116
|
-
defaultIndex: 0,
|
|
117
|
-
});
|
|
118
86
|
|
|
119
|
-
const
|
|
87
|
+
const rigorConfig = resolveRigor(rigor);
|
|
88
|
+
const costConfig = resolveCost(cost);
|
|
120
89
|
|
|
121
90
|
return {
|
|
122
|
-
|
|
123
|
-
|
|
91
|
+
...rigorConfig,
|
|
92
|
+
...costConfig,
|
|
124
93
|
commitDocs,
|
|
125
|
-
|
|
126
|
-
workflow: {
|
|
127
|
-
research: workflowResearch,
|
|
128
|
-
discuss: workflowDiscuss,
|
|
129
|
-
planCheck: workflowPlanCheck,
|
|
130
|
-
verifier: workflowVerifier,
|
|
131
|
-
},
|
|
132
|
-
gitProtocol,
|
|
94
|
+
gitProtocol: { ...DEFAULT_GIT_PROTOCOL },
|
|
133
95
|
initVersion: INIT_VERSION,
|
|
134
96
|
};
|
|
135
97
|
}
|
|
136
98
|
|
|
137
|
-
export async function promptGitProtocol(cwd, { input = process.stdin, output = process.stdout } = {}) {
|
|
138
|
-
if (typeof input.resume === 'function') input.resume();
|
|
139
|
-
output.write(`\n${ANSI.bold}Version control guidance${ANSI.reset}\n`);
|
|
140
|
-
output.write(`${ANSI.dim}This is advisory only. Repo or user conventions still win.${ANSI.reset}\n`);
|
|
141
|
-
const rl = readline.createInterface({ input, output });
|
|
142
|
-
const askQuestion = (query) => new Promise((resolve) => rl.question(query, resolve));
|
|
143
|
-
|
|
144
|
-
try {
|
|
145
|
-
let branch = await askQuestion(' Branching guidance (Enter for default): ');
|
|
146
|
-
branch = branch.trim() || DEFAULT_GIT_PROTOCOL.branch;
|
|
147
|
-
|
|
148
|
-
let commit = await askQuestion(' Commit guidance (Enter for default): ');
|
|
149
|
-
commit = commit.trim() || DEFAULT_GIT_PROTOCOL.commit;
|
|
150
|
-
|
|
151
|
-
let pr = await askQuestion(' PR guidance (Enter for default): ');
|
|
152
|
-
pr = pr.trim() || DEFAULT_GIT_PROTOCOL.pr;
|
|
153
|
-
|
|
154
|
-
return { branch, commit, pr };
|
|
155
|
-
} finally {
|
|
156
|
-
rl.close();
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
|
|
160
99
|
export function yesNoChoices(prompt, defaultYes) {
|
|
161
100
|
return [
|
|
162
101
|
{ value: true, label: 'yes', description: `${prompt} Yes.` },
|
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: '
|
|
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: '
|
|
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: '
|
|
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: '
|
|
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: '
|
|
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: '
|
|
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
|
+
Portable multi-runtime software delivery framework for AI coding agents.
|
|
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)
|
|
@@ -189,14 +195,18 @@ Platforms (for --tools):
|
|
|
189
195
|
cursor Generate root AGENTS.md governance block; workflows are already discovered natively from .agents/skills/ (legacy alias kept for backward compatibility)
|
|
190
196
|
copilot Generate root AGENTS.md governance block; workflows are already discovered natively from .agents/skills/ (legacy alias kept for backward compatibility)
|
|
191
197
|
gemini Generate root AGENTS.md governance block; workflows are already discovered natively from .agents/skills/ (legacy alias kept for backward compatibility)
|
|
192
|
-
all Generate all adapters (Claude, OpenCode, Codex, AGENTS
|
|
198
|
+
all Generate all adapters (Claude, OpenCode, Codex, AGENTS.md, Cursor, Copilot, Gemini)
|
|
193
199
|
|
|
194
200
|
Notes:
|
|
195
201
|
- init always generates open-standard skills at .agents/skills/gsdd-* (portable workflow entrypoints)
|
|
202
|
+
- Workspine is the public product name; the retained package, command, workflow, and workspace contracts stay gsdd-cli, gsdd, gsdd-*, and .planning/
|
|
196
203
|
- running plain \`gsdd init\` in a terminal opens the guided runtime-selection wizard
|
|
197
204
|
- the wizard lets you pick runtimes first, then separately decide whether repo-wide AGENTS.md governance is worth installing
|
|
205
|
+
- \`gsdd health\` compares any installed generated runtime surfaces against current render output and points back to \`gsdd update\` when they drift
|
|
206
|
+
- directly validated launch surfaces in this repo are Claude Code, OpenCode, and Codex CLI
|
|
207
|
+
- Cursor, Copilot, and Gemini are qualified support through the shared .agents/skills/ surface plus optional governance
|
|
198
208
|
- --tools remains the advanced/manual path and preserves legacy runtime aliases for backward compatibility
|
|
199
|
-
- --tools codex generates .codex/agents/gsdd-plan-checker.toml (portable skill is the entry surface)
|
|
209
|
+
- --tools codex generates .codex/agents/gsdd-plan-checker.toml (portable skill is the entry surface; $gsdd-plan is plan-only until explicit $gsdd-execute)
|
|
200
210
|
- root AGENTS.md is only written on init when explicitly requested via --tools agents, --tools all, or the wizard governance opt-in
|
|
201
211
|
|
|
202
212
|
Examples:
|
|
@@ -217,8 +227,20 @@ Examples:
|
|
|
217
227
|
npx gsdd-cli verify 1
|
|
218
228
|
npx gsdd-cli scaffold phase 4 Payments
|
|
219
229
|
|
|
220
|
-
Workflows (run via skills/adapters generated by init, not direct CLI):
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
230
|
+
Workflows (run via skills/adapters generated by init, not direct CLI):
|
|
231
|
+
gsdd-new-project Full initializer: questioning, brownfield audit, research, spec, roadmap
|
|
232
|
+
gsdd-map-codebase Map or refresh brownfield codebase context
|
|
233
|
+
gsdd-plan Research, plan, and fresh-context plan check for a phase
|
|
234
|
+
gsdd-execute Execute a phase plan and write phase summaries
|
|
235
|
+
gsdd-verify Verify a completed phase with 3-level checks
|
|
236
|
+
gsdd-verify-work Conversational UAT validation for user-facing behavior
|
|
237
|
+
gsdd-audit-milestone Cross-phase integration, requirements coverage, and E2E audit
|
|
238
|
+
gsdd-complete-milestone Archive a shipped milestone and collapse roadmap state
|
|
239
|
+
gsdd-new-milestone Start the next milestone with goals, requirements, and phases
|
|
240
|
+
gsdd-plan-milestone-gaps Turn milestone-audit gaps into closure phases
|
|
241
|
+
gsdd-quick Bounded brownfield lane for sub-hour work
|
|
242
|
+
gsdd-pause Save session context to checkpoint
|
|
243
|
+
gsdd-resume Restore context and route to the next action
|
|
244
|
+
gsdd-progress Read-only status and routing surface
|
|
245
|
+
`;
|
|
246
|
+
}
|
package/bin/lib/init.mjs
CHANGED
|
@@ -4,9 +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(getHelpText());
|
|
9
|
-
}
|
|
7
|
+
function cmdHelp() {
|
|
8
|
+
console.log(getHelpText().trimEnd());
|
|
9
|
+
}
|
|
10
10
|
|
|
11
11
|
export {
|
|
12
12
|
buildRuntimeChoices,
|