gsdd-cli 0.3.1 → 0.16.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.
Files changed (43) hide show
  1. package/README.md +44 -29
  2. package/agents/DISTILLATION.md +15 -13
  3. package/agents/planner.md +2 -0
  4. package/bin/adapters/agents.mjs +1 -0
  5. package/bin/adapters/claude.mjs +12 -3
  6. package/bin/adapters/codex.mjs +4 -0
  7. package/bin/adapters/opencode.mjs +13 -4
  8. package/bin/gsdd.mjs +20 -5
  9. package/bin/lib/cli-utils.mjs +1 -1
  10. package/bin/lib/file-ops.mjs +161 -0
  11. package/bin/lib/health-truth.mjs +188 -0
  12. package/bin/lib/health.mjs +65 -12
  13. package/bin/lib/init-flow.mjs +50 -3
  14. package/bin/lib/init-prompts.mjs +22 -83
  15. package/bin/lib/init-runtime.mjs +9 -7
  16. package/bin/lib/init.mjs +5 -1
  17. package/bin/lib/models.mjs +19 -4
  18. package/bin/lib/phase.mjs +100 -14
  19. package/bin/lib/plan-constants.mjs +30 -0
  20. package/bin/lib/provenance.mjs +146 -0
  21. package/bin/lib/templates.mjs +17 -0
  22. package/distilled/DESIGN.md +345 -47
  23. package/distilled/EVIDENCE-INDEX.md +297 -0
  24. package/distilled/README.md +44 -20
  25. package/distilled/SKILL.md +89 -85
  26. package/distilled/templates/agents.block.md +13 -84
  27. package/distilled/templates/agents.md +0 -7
  28. package/distilled/templates/delegates/plan-checker.md +6 -3
  29. package/distilled/workflows/audit-milestone.md +6 -6
  30. package/distilled/workflows/complete-milestone.md +297 -0
  31. package/distilled/workflows/execute.md +188 -19
  32. package/distilled/workflows/map-codebase.md +5 -3
  33. package/distilled/workflows/new-milestone.md +249 -0
  34. package/distilled/workflows/new-project.md +5 -6
  35. package/distilled/workflows/pause.md +40 -6
  36. package/distilled/workflows/plan-milestone-gaps.md +183 -0
  37. package/distilled/workflows/plan.md +75 -11
  38. package/distilled/workflows/progress.md +42 -19
  39. package/distilled/workflows/quick.md +14 -10
  40. package/distilled/workflows/resume.md +121 -11
  41. package/distilled/workflows/verify-work.md +260 -0
  42. package/distilled/workflows/verify.md +124 -33
  43. package/package.json +7 -5
@@ -0,0 +1,188 @@
1
+ import { existsSync, readFileSync, readdirSync } from 'fs';
2
+ import { join } from 'path';
3
+
4
+ export const TRUTH_CHECK_IDS = ['W7', 'W8', 'W9', 'W10'];
5
+
6
+ export function runTruthChecks(planningDir, frameworkDir, actualCheckIds) {
7
+ const warnings = [];
8
+ const designPath = join(frameworkDir, 'distilled', 'DESIGN.md');
9
+ const readmePath = join(frameworkDir, 'distilled', 'README.md');
10
+ const workflowsDir = join(frameworkDir, 'distilled', 'workflows');
11
+ const gapsPath = join(frameworkDir, '.internal-research', 'gaps.md');
12
+ const specPath = join(planningDir, 'SPEC.md');
13
+ const roadmapPath = join(planningDir, 'ROADMAP.md');
14
+
15
+ if (existsSync(designPath)) {
16
+ const documentedIds = extractHealthTableIds(readFileSync(designPath, 'utf-8'));
17
+ const missingFromDesign = actualCheckIds.filter((id) => !documentedIds.includes(id));
18
+ const extraInDesign = documentedIds.filter((id) => !actualCheckIds.includes(id));
19
+ if (missingFromDesign.length > 0 || extraInDesign.length > 0) {
20
+ const parts = [];
21
+ if (missingFromDesign.length > 0) parts.push(`missing in DESIGN.md: ${missingFromDesign.join(', ')}`);
22
+ if (extraInDesign.length > 0) parts.push(`extra in DESIGN.md: ${extraInDesign.join(', ')}`);
23
+ warnings.push({
24
+ id: 'W7',
25
+ severity: 'WARN',
26
+ message: `DESIGN.md health check table is out of sync (${parts.join('; ')})`,
27
+ fix: 'Update distilled/DESIGN.md section 20 to match the implemented health checks',
28
+ });
29
+ }
30
+ }
31
+
32
+ if (existsSync(readmePath) && existsSync(workflowsDir)) {
33
+ const readme = readFileSync(readmePath, 'utf-8');
34
+ const workflowFiles = readdirSync(workflowsDir).filter((entry) => entry.endsWith('.md')).sort();
35
+ const statusEntries = extractReadmeStatusEntries(readme);
36
+ const treeEntries = extractReadmeWorkflowTreeEntries(readme);
37
+ const issues = [];
38
+ if (statusEntries.length !== workflowFiles.length) {
39
+ issues.push(`status table ${statusEntries.length} != workflows dir ${workflowFiles.length}`);
40
+ }
41
+ if (treeEntries.length !== workflowFiles.length) {
42
+ issues.push(`framework tree ${treeEntries.length} != workflows dir ${workflowFiles.length}`);
43
+ }
44
+ const missingStatus = workflowFiles.filter((name) => !statusEntries.includes(name));
45
+ const missingTree = workflowFiles.filter((name) => !treeEntries.includes(name));
46
+ if (missingStatus.length > 0) issues.push(`missing from status table: ${missingStatus.join(', ')}`);
47
+ if (missingTree.length > 0) issues.push(`missing from framework tree: ${missingTree.join(', ')}`);
48
+ if (issues.length > 0) {
49
+ warnings.push({
50
+ id: 'W8',
51
+ severity: 'WARN',
52
+ message: `distilled/README.md workflow inventory is out of sync (${issues.join('; ')})`,
53
+ fix: 'Update distilled/README.md workflow status table and framework file tree to match distilled/workflows/',
54
+ });
55
+ }
56
+ }
57
+
58
+ if (existsSync(gapsPath)) {
59
+ const gapRefs = extractRepoLocalPaths(readFileSync(gapsPath, 'utf-8'));
60
+ const missingRefs = gapRefs.filter((ref) => !existsSync(join(frameworkDir, ref)));
61
+ if (missingRefs.length > 0) {
62
+ warnings.push({
63
+ id: 'W9',
64
+ severity: 'WARN',
65
+ message: `gaps.md references missing repo-local paths (${missingRefs.join(', ')})`,
66
+ fix: 'Annotate stale gap references as resolved or update them to current repo truth',
67
+ });
68
+ }
69
+ }
70
+
71
+ if (existsSync(specPath) && existsSync(roadmapPath)) {
72
+ const spec = readFileSync(specPath, 'utf-8');
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
+ }
82
+ if (mismatches.length > 0) {
83
+ warnings.push({
84
+ id: 'W10',
85
+ severity: 'WARN',
86
+ message: `ROADMAP/SPEC requirement status drift (${mismatches.join('; ')})`,
87
+ fix: 'Reconcile .planning/ROADMAP.md phase completion markers with .planning/SPEC.md requirement checkboxes',
88
+ });
89
+ }
90
+ }
91
+
92
+ return warnings;
93
+ }
94
+
95
+ function extractHealthTableIds(content) {
96
+ const section = extractSection(content, '## 20. Workspace Health Diagnostics', '## 21.');
97
+ if (!section) return [];
98
+ return [...normalizeContent(section).matchAll(/^\|\s*([EWI]\d+)\s*\|/gm)].map((result) => result[1]);
99
+ }
100
+
101
+ function extractReadmeStatusEntries(content) {
102
+ const section = extractSection(content, '## Current Status', 'Architecture notes:');
103
+ if (!section) return [];
104
+ return [...normalizeContent(section).matchAll(/\|\s*`([^`]+\.md)`\s*\|/g)].map((result) => result[1]);
105
+ }
106
+
107
+ function extractReadmeWorkflowTreeEntries(content) {
108
+ const section = extractSection(content, '## Files In This Framework', '## ');
109
+ const match = normalizeContent(section || '').match(/```[\s\S]*?workflows\/\n([\s\S]*?)\n\s*templates\//);
110
+ if (!match) return [];
111
+ return match[1]
112
+ .split('\n')
113
+ .map((line) => line.trim())
114
+ .filter((line) => line.endsWith('.md'));
115
+ }
116
+
117
+ function extractRepoLocalPaths(content) {
118
+ const allowedRoots = [
119
+ 'AGENTS.md',
120
+ 'README.md',
121
+ 'CHANGELOG.md',
122
+ 'SPEC.md',
123
+ 'package.json',
124
+ '.planning/',
125
+ '.internal-research/',
126
+ '.agents/',
127
+ '.claude/',
128
+ '.opencode/',
129
+ '.codex/',
130
+ '.worktrees/',
131
+ 'bin/',
132
+ 'distilled/',
133
+ 'tests/',
134
+ 'fixtures/',
135
+ 'agents/',
136
+ ];
137
+ const refs = new Set();
138
+ for (const result of content.matchAll(/`([^`]+)`/g)) {
139
+ const value = result[1].trim();
140
+ if (!looksLikeRepoLocalPath(value, allowedRoots)) continue;
141
+ refs.add(value.replace(/\\/g, '/').replace(/\/$/, ''));
142
+ }
143
+ return [...refs];
144
+ }
145
+
146
+ function looksLikeRepoLocalPath(value, allowedRoots) {
147
+ if (value.includes(' ')) return false;
148
+ if (value.startsWith('/')) return false;
149
+ if (value.startsWith('$')) return false;
150
+ if (value.startsWith('..')) return false;
151
+ if (value.includes('://')) return false;
152
+ if (value.startsWith('feat/') || value.startsWith('fix/') || value.startsWith('pr') || value.startsWith('origin/')) return false;
153
+ if (allowedRoots.some((root) => value === root || value.startsWith(root))) return true;
154
+ return false;
155
+ }
156
+
157
+ function extractSection(content, startMarker, endMarker) {
158
+ const normalized = normalizeContent(content);
159
+ const start = normalized.indexOf(startMarker);
160
+ if (start === -1) return '';
161
+ const rest = normalized.slice(start);
162
+ if (!endMarker) return rest;
163
+ const end = rest.indexOf(endMarker, startMarker.length);
164
+ return end === -1 ? rest : rest.slice(0, end);
165
+ }
166
+
167
+ function normalizeContent(content) {
168
+ return content.replace(/\r\n/g, '\n');
169
+ }
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
+ }
@@ -7,6 +7,7 @@ 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';
10
11
 
11
12
  /**
12
13
  * Factory function returning the health command.
@@ -17,6 +18,8 @@ export function createCmdHealth(ctx) {
17
18
  const jsonMode = healthArgs.includes('--json');
18
19
  const cwd = process.cwd();
19
20
  const planningDir = join(cwd, '.planning');
21
+ 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'];
20
23
 
21
24
  // Pre-init guard
22
25
  if (!existsSync(join(planningDir, 'config.json'))) {
@@ -56,10 +59,11 @@ export function createCmdHealth(ctx) {
56
59
  const delegatesDir = join(templatesDir, 'delegates');
57
60
  const hasRolesDir = hasTemplatesDir && existsSync(rolesDir);
58
61
  const hasDelegatesDir = hasTemplatesDir && existsSync(delegatesDir);
62
+ const skipInstalledTemplateChecks = !hasTemplatesDir && frameworkSourceMode;
59
63
 
60
- if (!hasTemplatesDir) {
64
+ if (!hasTemplatesDir && !skipInstalledTemplateChecks) {
61
65
  errors.push({ id: 'E3', severity: 'ERROR', message: '.planning/templates/ missing', fix: 'Run `gsdd update --templates`' });
62
- } else {
66
+ } else if (hasTemplatesDir) {
63
67
  // E4: roles/ missing or empty
64
68
  if (!hasRolesDir) {
65
69
  errors.push({ id: 'E4', severity: 'ERROR', message: '.planning/templates/roles/ missing', fix: 'Run `gsdd update --templates`' });
@@ -79,13 +83,42 @@ export function createCmdHealth(ctx) {
79
83
  errors.push({ id: 'E5', severity: 'ERROR', message: '.planning/templates/delegates/ has 0 delegate files', fix: 'Run `gsdd update --templates`' });
80
84
  }
81
85
  }
86
+
87
+ // E6: research/ missing or empty
88
+ const researchDir = join(templatesDir, 'research');
89
+ if (!existsSync(researchDir)) {
90
+ errors.push({ id: 'E6', severity: 'ERROR', message: '.planning/templates/research/ missing', fix: 'Run `gsdd update --templates`' });
91
+ } else {
92
+ const researchFiles = readdirSync(researchDir).filter((f) => f.endsWith('.md'));
93
+ if (researchFiles.length === 0) {
94
+ errors.push({ id: 'E6', severity: 'ERROR', message: '.planning/templates/research/ has 0 template files', fix: 'Run `gsdd update --templates`' });
95
+ }
96
+ }
97
+
98
+ // E7: codebase/ missing or empty
99
+ const codebaseDir = join(templatesDir, 'codebase');
100
+ if (!existsSync(codebaseDir)) {
101
+ errors.push({ id: 'E7', severity: 'ERROR', message: '.planning/templates/codebase/ missing', fix: 'Run `gsdd update --templates`' });
102
+ } else {
103
+ const codebaseFiles = readdirSync(codebaseDir).filter((f) => f.endsWith('.md'));
104
+ if (codebaseFiles.length === 0) {
105
+ errors.push({ id: 'E7', severity: 'ERROR', message: '.planning/templates/codebase/ has 0 template files', fix: 'Run `gsdd update --templates`' });
106
+ }
107
+ }
108
+
109
+ // E8: critical root template files missing
110
+ const requiredRootFiles = ['spec.md', 'roadmap.md', 'auth-matrix.md'];
111
+ const missingRoot = requiredRootFiles.filter((f) => !existsSync(join(templatesDir, f)));
112
+ if (missingRoot.length > 0) {
113
+ errors.push({ id: 'E8', severity: 'ERROR', message: `.planning/templates/ missing critical root files: ${missingRoot.join(', ')}`, fix: 'Run `gsdd update --templates`' });
114
+ }
82
115
  }
83
116
 
84
117
  // --- WARNING checks ---
85
118
 
86
119
  // W1: generation-manifest.json missing
87
- const manifest = readManifest(planningDir);
88
- if (!manifest) {
120
+ const manifest = skipInstalledTemplateChecks ? null : readManifest(planningDir);
121
+ if (!manifest && !skipInstalledTemplateChecks) {
89
122
  warnings.push({ id: 'W1', severity: 'WARN', message: 'generation-manifest.json missing', fix: 'Run `gsdd update --templates` to create' });
90
123
  }
91
124
 
@@ -118,16 +151,25 @@ export function createCmdHealth(ctx) {
118
151
  const phaseArtifacts = existsSync(phasesDir) ? listPhaseArtifacts(phasesDir) : [];
119
152
 
120
153
  if (roadmap && existsSync(phasesDir)) {
121
- const phaseNums = [];
154
+ const phaseLabels = [];
155
+ let inDetails = false;
122
156
  for (const line of roadmap.split('\n')) {
123
- const match = line.match(/^\s*[-*]\s*\[[ x-]\]\s*\*\*Phase\s+(\d+)/i);
124
- if (match) phaseNums.push(parseInt(match[1], 10));
157
+ if (line.includes('<details>') && !line.includes('</details>')) {
158
+ inDetails = true;
159
+ continue;
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]));
125
168
  }
126
- for (const num of phaseNums) {
127
- const padded = String(num).padStart(2, '0');
128
- const hasFile = phaseArtifacts.some((artifact) => artifact.phasePrefix === padded || artifact.phasePrefix === String(num));
169
+ for (const label of phaseLabels) {
170
+ const hasFile = phaseArtifacts.some((artifact) => normalizePhaseLabel(artifact.phasePrefix) === label);
129
171
  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' });
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' });
131
173
  }
132
174
  }
133
175
  }
@@ -160,6 +202,8 @@ export function createCmdHealth(ctx) {
160
202
  warnings.push({ id: 'W6', severity: 'WARN', message: 'No adapter surfaces detected', fix: 'Run `gsdd init --tools <platform>`' });
161
203
  }
162
204
 
205
+ warnings.push(...runTruthChecks(planningDir, cwd, healthCheckIds));
206
+
163
207
  // --- INFO checks ---
164
208
 
165
209
  // I1: generation manifest was produced by a different framework version
@@ -220,6 +264,15 @@ export function createCmdHealth(ctx) {
220
264
  };
221
265
  }
222
266
 
267
+ function isFrameworkSourceRepo(cwd) {
268
+ return existsSync(join(cwd, 'distilled', 'templates')) && existsSync(join(cwd, 'distilled', 'workflows'));
269
+ }
270
+
271
+ function normalizePhaseLabel(value) {
272
+ if (!value) return null;
273
+ return value.toLowerCase().replace(/^0+(\d)/, '$1');
274
+ }
275
+
223
276
  function listPhaseArtifacts(phasesDir) {
224
277
  const artifacts = [];
225
278
  for (const entry of readdirSync(phasesDir, { withFileTypes: true })) {
@@ -243,6 +296,6 @@ function createPhaseArtifact(dir, name) {
243
296
  dir,
244
297
  name,
245
298
  displayPath: dir ? `${dir}/${name}` : name,
246
- phasePrefix: name.match(/^(\d+(?:\.\d+)?)-/)?.[1] || dir.match(/^(\d+(?:\.\d+)?)-/)?.[1] || null,
299
+ phasePrefix: name.match(/^(\d+[a-z]?(?:\.\d+)?)-/i)?.[1] || dir.match(/^(\d+[a-z]?(?:\.\d+)?)-/i)?.[1] || null,
247
300
  };
248
301
  }
@@ -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 SDD workflow\n');
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('\nSDD initialized.');
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
+ }
@@ -1,5 +1,5 @@
1
1
  import * as readline from 'readline';
2
- import { DEFAULT_GIT_PROTOCOL, normalizeModelProfile } from './models.mjs';
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 researchDepth = await promptSingleSelect({
54
+ const rigor = await promptSingleSelect({
55
55
  input,
56
56
  output,
57
- title: 'Research depth',
57
+ title: 'Rigor',
58
58
  choices: [
59
- { value: 'balanced', label: 'balanced', description: 'SOTA research per phase (recommended)' },
60
- { value: 'fast', label: 'fast', description: 'Skip deeper domain research and plan from current context' },
61
- { value: 'deep', label: 'deep', description: 'Exhaustive research sweeps and parallel researchers' },
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: 0,
63
+ defaultIndex: 1,
64
64
  });
65
- const parallelization = await promptSingleSelect({
65
+ const cost = await promptSingleSelect({
66
66
  input,
67
67
  output,
68
- title: 'Parallelization',
69
- choices: yesNoChoices('Run independent agents in parallel?', true),
70
- defaultIndex: 0,
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: 'balanced', label: 'balanced', description: 'Capable default for most agents (recommended)' },
85
- { value: 'quality', label: 'quality', description: 'Most capable model for research and roadmap work' },
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 gitProtocol = await promptGitProtocol(cwd, { input, output });
87
+ const rigorConfig = resolveRigor(rigor);
88
+ const costConfig = resolveCost(cost);
120
89
 
121
90
  return {
122
- researchDepth,
123
- parallelization,
91
+ ...rigorConfig,
92
+ ...costConfig,
124
93
  commitDocs,
125
- modelProfile: normalizeModelProfile(modelProfile),
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.` },
@@ -2,37 +2,37 @@ const RUNTIME_OPTIONS = [
2
2
  {
3
3
  id: 'claude',
4
4
  label: 'Claude Code',
5
- description: 'Native skills, commands, and agents',
5
+ description: 'Directly validated native skills, commands, and agents',
6
6
  kind: 'native',
7
7
  },
8
8
  {
9
9
  id: 'opencode',
10
10
  label: 'OpenCode',
11
- description: 'Native slash commands and agents',
11
+ description: 'Directly validated native slash commands and agents',
12
12
  kind: 'native',
13
13
  },
14
14
  {
15
15
  id: 'codex',
16
16
  label: 'Codex CLI',
17
- description: 'Portable skills plus native checker agents',
17
+ description: 'Directly validated portable skills plus native checker agents',
18
18
  kind: 'native',
19
19
  },
20
20
  {
21
21
  id: 'cursor',
22
22
  label: 'Cursor',
23
- description: 'Skills-native slash commands from .agents/skills/',
23
+ description: 'Qualified support via skills-native slash commands from .agents/skills/',
24
24
  kind: 'skills_native',
25
25
  },
26
26
  {
27
27
  id: 'copilot',
28
28
  label: 'GitHub Copilot',
29
- description: 'Skills-native slash commands from .agents/skills/',
29
+ description: 'Qualified support via skills-native slash commands from .agents/skills/',
30
30
  kind: 'skills_native',
31
31
  },
32
32
  {
33
33
  id: 'gemini',
34
34
  label: 'Gemini CLI',
35
- description: 'Skills-native slash commands from .agents/skills/',
35
+ description: 'Qualified support via skills-native slash commands from .agents/skills/',
36
36
  kind: 'skills_native',
37
37
  },
38
38
  ];
@@ -189,12 +189,14 @@ Platforms (for --tools):
189
189
  cursor Generate root AGENTS.md governance block; workflows are already discovered natively from .agents/skills/ (legacy alias kept for backward compatibility)
190
190
  copilot Generate root AGENTS.md governance block; workflows are already discovered natively from .agents/skills/ (legacy alias kept for backward compatibility)
191
191
  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-based surfaces)
192
+ all Generate all adapters (Claude, OpenCode, Codex, AGENTS.md, Cursor, Copilot, Gemini)
193
193
 
194
194
  Notes:
195
195
  - init always generates open-standard skills at .agents/skills/gsdd-* (portable workflow entrypoints)
196
196
  - running plain \`gsdd init\` in a terminal opens the guided runtime-selection wizard
197
197
  - the wizard lets you pick runtimes first, then separately decide whether repo-wide AGENTS.md governance is worth installing
198
+ - directly validated launch surfaces in this repo are Claude Code, OpenCode, and Codex CLI
199
+ - Cursor, Copilot, and Gemini are qualified support through the shared .agents/skills/ surface plus optional governance
198
200
  - --tools remains the advanced/manual path and preserves legacy runtime aliases for backward compatibility
199
201
  - --tools codex generates .codex/agents/gsdd-plan-checker.toml (portable skill is the entry surface)
200
202
  - root AGENTS.md is only written on init when explicitly requested via --tools agents, --tools all, or the wizard governance opt-in
package/bin/lib/init.mjs CHANGED
@@ -5,7 +5,11 @@ import { createInitPromptApi, promptChoiceList } from './init-prompts.mjs';
5
5
  import { buildRuntimeChoices, detectPlatforms, getHelpText, normalizeRequestedTools } from './init-runtime.mjs';
6
6
 
7
7
  function cmdHelp() {
8
- console.log(getHelpText());
8
+ console.log(`${getHelpText().trimEnd()}
9
+ file-op <copy|delete|regex-sub>
10
+ Run deterministic workspace-confined file copy/delete/text mutation
11
+ phase-status <N> <status> Update ROADMAP.md phase status ([ ] / [-] / [x])
12
+ `);
9
13
  }
10
14
 
11
15
  export {