gsdd-cli 0.18.4 → 0.18.5

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 CHANGED
@@ -88,13 +88,17 @@ Invariant suites (I-series), guard suites (G-series), and scenario suites (S-ser
88
88
  npx gsdd-cli init
89
89
  ```
90
90
 
91
- This creates:
91
+ This creates:
92
92
 
93
- 1. `.planning/` — durable workspace with templates, role contracts, and config
94
- 2. `.agents/skills/gsdd-*` — portable workflow entrypoints
95
- 3. Tool-specific adapters you choose in the install wizard (Claude skills/commands/agents, OpenCode commands/agents, Codex agents, optional governance)
96
-
97
- Then run the new-project workflow to produce `.planning/SPEC.md` and `.planning/ROADMAP.md`.
93
+ 1. `.planning/` — durable workspace with templates, role contracts, config, and internal local helpers
94
+ 2. `.agents/skills/gsdd-*` — portable workflow entrypoints your runtime invokes as `/gsdd-*` or `$gsdd-*`
95
+ 3. Tool-specific adapters you choose in the install wizard (Claude skills/commands/agents, OpenCode commands/agents, Codex agents, optional governance)
96
+
97
+ Then pick the first workflow lane that matches your situation:
98
+
99
+ - `gsdd-new-project` for greenfield work, fuzzy brownfield work, or milestone-shaped work
100
+ - `gsdd-quick` for a concrete bounded brownfield change
101
+ - `gsdd-map-codebase` first when the repo is unfamiliar, risky, or needs a deeper baseline before choosing a lane
98
102
 
99
103
  In a terminal, `gsdd init` now opens a guided install wizard:
100
104
 
@@ -102,9 +106,9 @@ In a terminal, `gsdd init` now opens a guided install wizard:
102
106
  - Step 2: decide separately whether repo-wide `AGENTS.md` governance is worth installing
103
107
  - Step 3: configure planning defaults in the same guided flow
104
108
 
105
- Portable `.agents/skills/gsdd-*` skills are always generated. The wizard controls extra native adapters and optional governance, not the portable baseline.
106
- When those generated surfaces exist locally, `gsdd health` checks them against current render output instead of asking you to trust manual review.
107
- `init` generates a local `.planning/bin/gsdd*` helper surface. Workflow-embedded helper commands still use `node .planning/bin/gsdd.mjs ...` as the portable contract, so execution-time lifecycle helpers do not depend on a global `gsdd` binary after init.
109
+ Portable `.agents/skills/gsdd-*` skills are always generated. They are the shared workflow entry surface. The wizard controls extra native adapters and optional governance, not the portable baseline.
110
+ When generated surfaces exist locally, `gsdd health` checks them against current render output instead of asking you to trust manual review.
111
+ `init` also generates `.planning/bin/gsdd*`, but that is an internal local helper surface for workflow-embedded lifecycle mechanics. It is not the normal first-run user entrypoint.
108
112
 
109
113
  ### Launch Proof Status
110
114
 
@@ -123,11 +127,16 @@ Start with the public proof pack:
123
127
 
124
128
  Runtime floor: Node 20+.
125
129
 
126
- Your tool determines how you invoke workflows:
130
+ Your tool determines how you invoke workflows after `npx gsdd-cli init`:
127
131
 
128
132
  - **Claude Code / OpenCode / Cursor / Copilot / Gemini:** Use slash commands directly — `/gsdd-new-project`, `/gsdd-plan`, etc.
129
133
  - **Codex CLI:** Use skill references — `$gsdd-new-project`, `$gsdd-plan`, etc. `$gsdd-plan` writes the plan and stops; start a separate `$gsdd-execute` run when you want implementation to begin.
130
- - **Other AI tools:** Open `.agents/skills/gsdd-<workflow>/SKILL.md` and follow the instructions.
134
+ - **Other AI tools:** Open `.agents/skills/gsdd-<workflow>/SKILL.md` and follow the instructions manually.
135
+
136
+ For repair and refresh:
137
+
138
+ - `gsdd health` checks whether installed generated surfaces still match the current renderer output.
139
+ - `npx gsdd-cli update` regenerates installed generated surfaces when they drift or when you want the latest shipped wording/templates. If `gsdd` is already on your PATH, `gsdd update` is the same repair path.
131
140
 
132
141
  If you generate the root `AGENTS.md` block, it adds the framework's behavioral governance. For Cursor, Copilot, and Gemini, that governance is optional discipline on top of native skill discovery — not the mechanism that makes workflows discoverable.
133
142
 
@@ -162,13 +171,15 @@ npx gsdd-cli init --tools all # All of the above
162
171
  | **Cursor / Copilot / Gemini** | Same core workflow | Skills-native discovery from `.agents/skills/`; optional root `AGENTS.md` block adds behavioral governance, and the generated skill surface is freshness-checked locally |
163
172
  | **Other AI tools** | Fallback only | Open `.agents/skills/gsdd-*/SKILL.md` directly |
164
173
 
165
- ### Updating
174
+ ### Updating And Repair
166
175
 
167
176
  ```bash
168
- npx gsdd-cli update # Regenerate adapters from latest sources
169
- npx gsdd-cli update --tools claude # Update specific platform only
170
- npx gsdd-cli update --templates # Refresh .planning/templates/ and role contracts from framework source
171
- ```
177
+ npx gsdd-cli update # Regenerate adapters from latest sources
178
+ npx gsdd-cli update --tools claude # Update specific platform only
179
+ npx gsdd-cli update --templates # Refresh .planning/templates/ and role contracts from framework source
180
+ ```
181
+
182
+ Use `gsdd health` first when you want a status check. Use `npx gsdd-cli update` when the generated runtime-facing surfaces are missing, drifted, or you want the latest generated output. If a runtime is only in the qualified-support tier, `health` and `update` still cover generated-surface drift; they do not imply parity-level runtime proof.
172
183
 
173
184
  ### Non-Interactive Mode (CI / Automation)
174
185
 
@@ -325,18 +336,24 @@ Workflows are agent skills or commands, not plain shell utilities. How you invok
325
336
 
326
337
  ## CLI Commands
327
338
 
328
- | Command | What it does |
329
- |---------|--------------|
330
- | `gsdd init [--tools <platform>]` | Set up `.planning/`, generate adapters |
331
- | `gsdd update [--tools <platform>] [--templates]` | Regenerate adapters; `--templates` refreshes `.planning/templates/` and role contracts |
332
- | `gsdd health [--json]` | Check workspace integrity (healthy/degraded/broken) |
333
- | `gsdd file-op <copy\|delete\|regex-sub>` | Run deterministic workspace-confined file copy, delete, and regex substitution |
334
- | `gsdd find-phase [N]` | Show phase info as JSON (for agent consumption) |
335
- | `gsdd phase-status <N> <status>` | Update a single ROADMAP phase status through the status-aware helper |
336
- | `gsdd verify <N>` | Run artifact checks for phase N |
337
- | `gsdd scaffold phase <N> [name]` | Create a new phase plan file |
338
- | `gsdd models [show\|profile\|set\|...]` | Inspect and manage model profile propagation |
339
- | `gsdd help` | Show all commands |
339
+ | Command | What it does |
340
+ |---------|--------------|
341
+ | `npx gsdd-cli init [--tools <platform>]` | First-run setup: create `.planning/`, `.agents/skills/`, and selected runtime adapters |
342
+ | `gsdd health [--json]` | Check installed generated surfaces and workspace state (healthy/degraded/broken) |
343
+ | `npx gsdd-cli update [--tools <platform>] [--templates]` | Regenerate installed runtime surfaces; `--templates` also refreshes `.planning/templates/` and role contracts |
344
+ | `gsdd models [show\|profile\|set\|...]` | Inspect and manage model profile propagation |
345
+ | `gsdd help` | Show all commands, including advanced/internal helpers |
346
+
347
+ Advanced/internal helpers kept available for automation, workflow internals, or repair:
348
+
349
+ | Command | What it does |
350
+ |---------|--------------|
351
+ | `gsdd file-op <copy\|delete\|regex-sub>` | Deterministic workspace-confined file copy, delete, and regex substitution |
352
+ | `gsdd lifecycle-preflight <surface> [phase]` | Inspect deterministic lifecycle gate results for a workflow surface |
353
+ | `gsdd find-phase [N]` | Show phase info as JSON (for agent consumption) |
354
+ | `gsdd phase-status <N> <status>` | Update a single ROADMAP phase status through the status-aware helper |
355
+ | `gsdd verify <N>` | Run artifact checks for phase N |
356
+ | `gsdd scaffold phase <N> [name]` | Create a new phase plan file |
340
357
 
341
358
  ---
342
359
 
@@ -1,6 +1,7 @@
1
- import { cpSync, existsSync, mkdirSync, readFileSync, statSync, unlinkSync, writeFileSync } from 'fs';
2
- import { dirname, isAbsolute, relative, resolve } from 'path';
3
- import { output, parseFlagValue } from './cli-utils.mjs';
1
+ import { cpSync, existsSync, mkdirSync, readFileSync, statSync, unlinkSync, writeFileSync } from 'fs';
2
+ import { dirname, isAbsolute, relative, resolve } from 'path';
3
+ import { output, parseFlagValue } from './cli-utils.mjs';
4
+ import { resolveWorkspaceContext } from './workspace-root.mjs';
4
5
 
5
6
  class FileOpError extends Error {}
6
7
 
@@ -133,21 +134,26 @@ function cmdRegexSub(cwd, args) {
133
134
  output({ operation: 'regex-sub', target: targetArg, changed, replacements: replacementCount });
134
135
  }
135
136
 
136
- export function cmdFileOp(...args) {
137
- const cwd = process.cwd();
138
- const [operation, ...rest] = args;
139
-
140
- try {
141
- switch (operation) {
142
- case 'copy':
143
- cmdCopy(cwd, rest);
144
- return;
145
- case 'delete':
146
- cmdDelete(cwd, rest);
147
- return;
148
- case 'regex-sub':
149
- cmdRegexSub(cwd, rest);
150
- return;
137
+ export function cmdFileOp(...args) {
138
+ const { args: normalizedArgs, workspaceRoot, invalid, error } = resolveWorkspaceContext(args);
139
+ if (invalid) {
140
+ console.error(error);
141
+ process.exitCode = 1;
142
+ return;
143
+ }
144
+ const [operation, ...rest] = normalizedArgs;
145
+
146
+ try {
147
+ switch (operation) {
148
+ case 'copy':
149
+ cmdCopy(workspaceRoot, rest);
150
+ return;
151
+ case 'delete':
152
+ cmdDelete(workspaceRoot, rest);
153
+ return;
154
+ case 'regex-sub':
155
+ cmdRegexSub(workspaceRoot, rest);
156
+ return;
151
157
  default:
152
158
  fail('Usage: gsdd file-op <copy|delete|regex-sub> ...');
153
159
  }
@@ -6,21 +6,31 @@
6
6
  import { existsSync, readFileSync, readdirSync } from 'fs';
7
7
  import { join } from 'path';
8
8
  import { readManifest, detectModifications } from './manifest.mjs';
9
- import { output } from './cli-utils.mjs';
10
- import { runTruthChecks, TRUTH_CHECK_IDS } from './health-truth.mjs';
11
- import { evaluateLifecycleState } from './lifecycle-state.mjs';
12
- import { evaluateRuntimeFreshness } from './runtime-freshness.mjs';
9
+ import { output } from './cli-utils.mjs';
10
+ import { runTruthChecks, TRUTH_CHECK_IDS } from './health-truth.mjs';
11
+ import { evaluateLifecycleState } from './lifecycle-state.mjs';
12
+ import { evaluateRuntimeFreshness } from './runtime-freshness.mjs';
13
+ import { resolveWorkspaceContext } from './workspace-root.mjs';
13
14
 
14
15
  /**
15
16
  * Factory function returning the health command.
16
17
  * ctx should provide: { frameworkVersion, workflows }
17
18
  */
18
19
  export function createCmdHealth(ctx) {
19
- return async function cmdHealth(...healthArgs) {
20
- const jsonMode = healthArgs.includes('--json');
21
- const cwd = process.cwd();
22
- const planningDir = join(cwd, '.planning');
23
- const frameworkSourceMode = isFrameworkSourceRepo(cwd);
20
+ return async function cmdHealth(...healthArgs) {
21
+ const jsonMode = healthArgs.includes('--json');
22
+ const { planningDir, workspaceRoot, invalid, error } = resolveWorkspaceContext(healthArgs);
23
+ if (invalid) {
24
+ if (jsonMode) {
25
+ output({ status: 'broken', errors: [{ id: 'E1', severity: 'ERROR', message: error, fix: 'Pass --workspace-root with a real path or remove the flag.' }], warnings: [], info: [] });
26
+ } else {
27
+ console.log(error);
28
+ }
29
+ process.exitCode = 1;
30
+ return;
31
+ }
32
+ const cwd = workspaceRoot;
33
+ const frameworkSourceMode = isFrameworkSourceRepo(cwd);
24
34
  const healthCheckIds = ['E1', 'E2', 'E3', 'E4', 'E5', 'E6', 'E7', 'E8', 'W1', 'W2', 'W3', 'W4', 'W5', 'W6', ...TRUTH_CHECK_IDS, 'I1', 'I2', 'I3'];
25
35
 
26
36
  // Pre-init guard
@@ -122,12 +122,12 @@ export function createCmdInit(ctx) {
122
122
  writeManifest(ctx.planningDir, manifest);
123
123
  console.log(' - wrote generation manifest');
124
124
 
125
- console.log('\nGSDD initialized.');
126
- printInitSummary(interactiveSession.config ?? buildDefaultConfig({ autoAdvance: isAuto }));
127
- console.log('Next: run the new-project workflow to produce SPEC.md and ROADMAP.md:\n');
128
- printPostInitRouting(interactiveSession.selectedRuntimes);
129
- };
130
- }
125
+ console.log('\nGSDD initialized.');
126
+ printInitSummary(interactiveSession.config ?? buildDefaultConfig({ autoAdvance: isAuto }));
127
+ console.log('Next: choose the starting lane that fits your repo and current scope:\n');
128
+ printPostInitRouting(interactiveSession.selectedRuntimes);
129
+ };
130
+ }
131
131
 
132
132
  export function createCmdUpdate(ctx) {
133
133
  return function cmdUpdate(...updateArgs) {
@@ -147,17 +147,17 @@ export function resolveWizardAdapterTargets(selectedRuntimes, installGovernance)
147
147
  return adapterTargets;
148
148
  }
149
149
 
150
- export function getPostInitRoutingLines(selectedRuntimes) {
151
- const lines = [];
152
- if (selectedRuntimes.includes('claude')) lines.push(' Claude Code: /gsdd-new-project');
153
- if (selectedRuntimes.includes('opencode')) lines.push(' OpenCode: /gsdd-new-project');
154
- if (selectedRuntimes.includes('codex')) lines.push(' Codex CLI: $gsdd-new-project');
155
- if (selectedRuntimes.includes('cursor')) lines.push(' Cursor: /gsdd-new-project');
156
- if (selectedRuntimes.includes('copilot')) lines.push(' Copilot: /gsdd-new-project');
157
- if (selectedRuntimes.includes('gemini')) lines.push(' Gemini CLI: /gsdd-new-project');
158
- lines.push(' Any tool: open .agents/skills/gsdd-new-project/SKILL.md');
159
- return lines;
160
- }
150
+ export function getPostInitRoutingLines(selectedRuntimes) {
151
+ const lines = [];
152
+ if (selectedRuntimes.includes('claude')) lines.push(' Claude Code: /gsdd-new-project | /gsdd-quick | /gsdd-map-codebase');
153
+ if (selectedRuntimes.includes('opencode')) lines.push(' OpenCode: /gsdd-new-project | /gsdd-quick | /gsdd-map-codebase');
154
+ if (selectedRuntimes.includes('codex')) lines.push(' Codex CLI: $gsdd-new-project | $gsdd-quick | $gsdd-map-codebase');
155
+ if (selectedRuntimes.includes('cursor')) lines.push(' Cursor: /gsdd-new-project | /gsdd-quick | /gsdd-map-codebase');
156
+ if (selectedRuntimes.includes('copilot')) lines.push(' Copilot: /gsdd-new-project | /gsdd-quick | /gsdd-map-codebase');
157
+ if (selectedRuntimes.includes('gemini')) lines.push(' Gemini CLI: /gsdd-new-project | /gsdd-quick | /gsdd-map-codebase');
158
+ lines.push(' Any tool: open .agents/skills/gsdd-new-project/SKILL.md, gsdd-quick/SKILL.md, or gsdd-map-codebase/SKILL.md');
159
+ return lines;
160
+ }
161
161
 
162
162
  export function getHelpText() {
163
163
  return `
@@ -197,22 +197,24 @@ Platforms (for --tools):
197
197
  gemini Generate root AGENTS.md governance block; workflows are already discovered natively from .agents/skills/ (legacy alias kept for backward compatibility)
198
198
  all Generate all adapters (Claude, OpenCode, Codex, AGENTS.md, Cursor, Copilot, Gemini)
199
199
 
200
- Notes:
201
- - init always generates open-standard skills at .agents/skills/gsdd-* (portable workflow entrypoints)
202
- - init also generates a local .planning/bin/gsdd* helper surface so workflow-embedded helper commands do not depend on a global gsdd binary
200
+ Notes:
201
+ - init always generates open-standard skills at .agents/skills/gsdd-*; this is the shared workflow entry surface
202
+ - init also generates a local .planning/bin/gsdd* helper surface for workflow-embedded lifecycle helpers; it is internal/advanced, not the normal first-run user entrypoint
203
203
  - Workspine is the public product name; the retained package, command, workflow, and workspace contracts stay gsdd-cli, gsdd, gsdd-*, and .planning/
204
- - running plain \`gsdd init\` in a terminal opens the guided runtime-selection wizard
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
207
- - directly validated launch surfaces in this repo are Claude Code, OpenCode, and Codex CLI
208
- - Cursor, Copilot, and Gemini are qualified support through the shared .agents/skills/ surface plus optional governance
209
- - --tools remains the advanced/manual path and preserves legacy runtime aliases for backward compatibility
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)
211
- - root AGENTS.md is only written on init when explicitly requested via --tools agents, --tools all, or the wizard governance opt-in
212
-
213
- Examples:
214
- npx gsdd-cli init
215
- npx gsdd-cli init --tools claude
204
+ - running plain \`gsdd init\` in a terminal opens the guided runtime-selection wizard
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
207
+ - directly validated launch surfaces in this repo are Claude Code, OpenCode, and Codex CLI
208
+ - Cursor, Copilot, and Gemini are qualified support through the shared .agents/skills/ surface plus optional governance
209
+ - --tools remains the advanced/manual path and preserves legacy runtime aliases for backward compatibility
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)
211
+ - root AGENTS.md is only written on init when explicitly requested via --tools agents, --tools all, or the wizard governance opt-in
212
+ - normal user path: npx gsdd-cli init -> run /gsdd-* or $gsdd-* -> gsdd health -> npx gsdd-cli update when repair or refresh is needed
213
+ - post-init, choose your starting lane honestly: new-project for greenfield or fuzzy/milestone work, quick for a concrete bounded change, map-codebase first when the repo needs deeper orientation
214
+
215
+ Examples:
216
+ npx gsdd-cli init
217
+ npx gsdd-cli init --tools claude
216
218
  npx gsdd-cli init --tools cursor
217
219
  npx gsdd-cli init --auto --tools claude --brief project-idea.md
218
220
  npx gsdd-cli init --auto --tools all
@@ -230,7 +232,7 @@ Examples:
230
232
 
231
233
  Workflows (run via skills/adapters generated by init, not direct CLI):
232
234
  gsdd-new-project Full initializer: questioning, brownfield audit, research, spec, roadmap
233
- gsdd-map-codebase Map or refresh brownfield codebase context
235
+ gsdd-map-codebase Map or refresh brownfield codebase context before choosing or refreshing a work lane
234
236
  gsdd-plan Research, plan, and fresh-context plan check for a phase
235
237
  gsdd-execute Execute a phase plan and write phase summaries
236
238
  gsdd-verify Verify a completed phase with 3-level checks
@@ -243,5 +245,15 @@ Workflows (run via skills/adapters generated by init, not direct CLI):
243
245
  gsdd-pause Save session context to checkpoint
244
246
  gsdd-resume Restore context and route to the next action
245
247
  gsdd-progress Read-only status and routing surface
248
+
249
+ Starting lanes after init:
250
+ gsdd-new-project Greenfield, fuzzy brownfield scope, or milestone-shaped work
251
+ gsdd-quick Concrete bounded brownfield change
252
+ gsdd-map-codebase Deeper brownfield orientation before choosing the lane above
253
+
254
+ Advanced/internal helpers (kept available, but not the primary first-run user story):
255
+ lifecycle-preflight Inspect deterministic lifecycle gate results for a workflow surface
256
+ phase-status Update ROADMAP.md phase status through the local helper surface
257
+ file-op Deterministic workspace-confined file copy/delete/text mutation
246
258
  `;
247
259
  }
@@ -1,9 +1,10 @@
1
1
  import { existsSync, readFileSync } from 'fs';
2
2
  import { join } from 'path';
3
- import { output } from './cli-utils.mjs';
4
- import { describeEvidenceSurface } from './evidence-contract.mjs';
5
- import { evaluateLifecycleState, normalizePhaseToken } from './lifecycle-state.mjs';
6
- import { checkDrift } from './session-fingerprint.mjs';
3
+ import { output } from './cli-utils.mjs';
4
+ import { describeEvidenceSurface } from './evidence-contract.mjs';
5
+ import { evaluateLifecycleState, normalizePhaseToken } from './lifecycle-state.mjs';
6
+ import { checkDrift } from './session-fingerprint.mjs';
7
+ import { resolveWorkspaceContext } from './workspace-root.mjs';
7
8
 
8
9
  const SURFACE_POLICIES = {
9
10
  progress: {
@@ -297,10 +298,14 @@ function blocker(code, message, artifacts) {
297
298
  return { code, message, artifacts };
298
299
  }
299
300
 
300
- export function cmdLifecyclePreflight(...args) {
301
- const cwd = process.cwd();
302
- const planningDir = join(cwd, '.planning');
303
- const [surface, maybePhase, ...rest] = args;
301
+ export function cmdLifecyclePreflight(...args) {
302
+ const { args: normalizedArgs, planningDir, invalid, error } = resolveWorkspaceContext(args);
303
+ if (invalid) {
304
+ console.error(error);
305
+ process.exitCode = 1;
306
+ return;
307
+ }
308
+ const [surface, maybePhase, ...rest] = normalizedArgs;
304
309
 
305
310
  if (!surface) {
306
311
  console.error('Usage: gsdd lifecycle-preflight <surface> [phase] [--expects-mutation <none|phase-status>]');
package/bin/lib/phase.mjs CHANGED
@@ -3,10 +3,11 @@
3
3
  // IMPORTANT: No module-scope process.cwd() — ESM caching means sub-modules
4
4
  // evaluate once, so CWD must be computed inside function bodies.
5
5
 
6
- import { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync } from 'fs';
7
- import { join, basename } from 'path';
8
- import { output } from './cli-utils.mjs';
9
- import { writeFingerprint } from './session-fingerprint.mjs';
6
+ import { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync } from 'fs';
7
+ import { join, basename } from 'path';
8
+ import { output } from './cli-utils.mjs';
9
+ import { writeFingerprint } from './session-fingerprint.mjs';
10
+ import { resolveWorkspaceContext } from './workspace-root.mjs';
10
11
 
11
12
  const PHASE_STATUS_MARKERS = {
12
13
  not_started: '[ ]',
@@ -151,11 +152,15 @@ export function updateRoadmapPhaseStatus(roadmap, phaseNumber, status) {
151
152
  return updated;
152
153
  }
153
154
 
154
- export function cmdPhaseStatus(...args) {
155
- const cwd = process.cwd();
156
- const planningDir = join(cwd, '.planning');
157
- const roadmapPath = join(planningDir, 'ROADMAP.md');
158
- const [phaseNumber, status] = args;
155
+ export function cmdPhaseStatus(...args) {
156
+ const { args: normalizedArgs, planningDir, invalid, error } = resolveWorkspaceContext(args);
157
+ if (invalid) {
158
+ console.error(error);
159
+ process.exitCode = 1;
160
+ return;
161
+ }
162
+ const roadmapPath = join(planningDir, 'ROADMAP.md');
163
+ const [phaseNumber, status] = normalizedArgs;
159
164
 
160
165
  if (!phaseNumber || !status) {
161
166
  console.error('Usage: gsdd phase-status <phase-number> <not_started|todo|in_progress|done>');
@@ -184,10 +189,14 @@ export function cmdPhaseStatus(...args) {
184
189
  }
185
190
  }
186
191
 
187
- export function cmdFindPhase(...args) {
188
- const cwd = process.cwd();
189
- const planningDir = join(cwd, '.planning');
190
- const phaseNum = args[0];
192
+ export function cmdFindPhase(...args) {
193
+ const { args: normalizedArgs, planningDir, invalid, error } = resolveWorkspaceContext(args);
194
+ if (invalid) {
195
+ output({ error });
196
+ process.exitCode = 1;
197
+ return;
198
+ }
199
+ const phaseNum = normalizedArgs[0];
191
200
 
192
201
  if (!existsSync(planningDir)) {
193
202
  output({ error: 'No .planning/ directory found. Run `gsdd init` then the new-project workflow first.' });
@@ -234,10 +243,14 @@ export function cmdFindPhase(...args) {
234
243
  });
235
244
  }
236
245
 
237
- export function cmdVerify(...args) {
238
- const cwd = process.cwd();
239
- const planningDir = join(cwd, '.planning');
240
- const phaseNum = args[0];
246
+ export function cmdVerify(...args) {
247
+ const { args: normalizedArgs, workspaceRoot, planningDir, invalid, error } = resolveWorkspaceContext(args);
248
+ if (invalid) {
249
+ console.error(error);
250
+ process.exitCode = 1;
251
+ return;
252
+ }
253
+ const phaseNum = normalizedArgs[0];
241
254
  if (!phaseNum) {
242
255
  console.error('Usage: gsdd verify <phase-number>');
243
256
  process.exitCode = 1; return;
@@ -271,7 +284,7 @@ export function cmdVerify(...args) {
271
284
  }
272
285
 
273
286
  const results = expectedFiles.map((f) => {
274
- const fullPath = join(cwd, f);
287
+ const fullPath = join(workspaceRoot, f);
275
288
  const exists = existsSync(fullPath);
276
289
  let substantive = false;
277
290
  if (exists) {
@@ -292,7 +305,7 @@ export function cmdVerify(...args) {
292
305
  for (const r of results) {
293
306
  if (!r.exists) continue;
294
307
  try {
295
- const content = readFileSync(join(cwd, r.file), 'utf-8');
308
+ const content = readFileSync(join(workspaceRoot, r.file), 'utf-8');
296
309
  const lines = content.split('\n');
297
310
  lines.forEach((line, i) => {
298
311
  if (/TODO|FIXME|HACK|XXX/.test(line)) {
@@ -317,10 +330,14 @@ export function cmdVerify(...args) {
317
330
  });
318
331
  }
319
332
 
320
- export function cmdScaffold(...args) {
321
- const cwd = process.cwd();
322
- const planningDir = join(cwd, '.planning');
323
- const [type, ...rest] = args;
333
+ export function cmdScaffold(...args) {
334
+ const { args: normalizedArgs, planningDir, invalid, error } = resolveWorkspaceContext(args);
335
+ if (invalid) {
336
+ console.error(error);
337
+ process.exitCode = 1;
338
+ return;
339
+ }
340
+ const [type, ...rest] = normalizedArgs;
324
341
 
325
342
  if (type !== 'phase') {
326
343
  console.error('Usage: gsdd scaffold phase <number> [name]');
@@ -2,9 +2,19 @@ import { existsSync, readFileSync } from 'fs';
2
2
  import { dirname, join } from 'path';
3
3
  import { fileURLToPath } from 'url';
4
4
 
5
- const __filename = fileURLToPath(import.meta.url);
6
- const __dirname = dirname(__filename);
7
- const DISTILLED_DIR = join(__dirname, '..', '..', 'distilled');
5
+ const __filename = fileURLToPath(import.meta.url);
6
+ const __dirname = dirname(__filename);
7
+ const DISTILLED_DIR = join(__dirname, '..', '..', 'distilled');
8
+ const HELPER_LIB_FILES = Object.freeze([
9
+ 'cli-utils.mjs',
10
+ 'evidence-contract.mjs',
11
+ 'file-ops.mjs',
12
+ 'lifecycle-preflight.mjs',
13
+ 'lifecycle-state.mjs',
14
+ 'phase.mjs',
15
+ 'session-fingerprint.mjs',
16
+ 'workspace-root.mjs',
17
+ ]);
8
18
 
9
19
  function getWorkflowContent(workflowFile) {
10
20
  const filePath = join(DISTILLED_DIR, 'workflows', workflowFile);
@@ -30,71 +40,57 @@ agent: ${workflow.agent}
30
40
  ${workflowContent}`;
31
41
  }
32
42
 
33
- function renderPlanningCliLauncher({ packageName = 'gsdd-cli', packageVersion }) {
34
- if (!packageVersion) {
35
- throw new Error('renderPlanningCliLauncher requires packageVersion');
36
- }
43
+ function renderPlanningCliLauncher() {
44
+ return `#!/usr/bin/env node
37
45
 
38
- const packageSpec = `${packageName}@${packageVersion}`;
46
+ import { cmdFileOp } from './lib/file-ops.mjs';
47
+ import { cmdLifecyclePreflight } from './lib/lifecycle-preflight.mjs';
48
+ import { cmdPhaseStatus } from './lib/phase.mjs';
49
+ import { bootstrapHelperWorkspace } from './lib/workspace-root.mjs';
39
50
 
40
- return `#!/usr/bin/env node
51
+ bootstrapHelperWorkspace(import.meta.url);
41
52
 
42
- import { spawnSync } from 'node:child_process';
53
+ const COMMANDS = {
54
+ 'file-op': cmdFileOp,
55
+ 'lifecycle-preflight': cmdLifecyclePreflight,
56
+ 'phase-status': cmdPhaseStatus,
57
+ };
43
58
 
44
- const packageSpec = ${JSON.stringify(packageSpec)};
45
- const args = process.argv.slice(2);
46
- const env = { ...process.env };
59
+ function printHelp() {
60
+ console.log([
61
+ 'Usage: node .planning/bin/gsdd.mjs <command> [args]',
62
+ '',
63
+ 'Local workflow helper commands:',
64
+ ' file-op <copy|delete|regex-sub>',
65
+ ' Run deterministic workspace-confined file operations',
66
+ ' phase-status <N> <status> Update ROADMAP.md phase status ([ ] / [-] / [x])',
67
+ ' lifecycle-preflight <surface> [phase]',
68
+ ' Inspect lifecycle gate results for a workflow surface',
69
+ '',
70
+ 'Advanced option:',
71
+ ' --workspace-root <path> Override workspace root discovery when needed',
72
+ ].join('\\n'));
73
+ }
47
74
 
48
- function forwardResult(result, fallbackMessage) {
49
- if (result.error) {
50
- console.error(fallbackMessage ?? result.error.message);
51
- process.exitCode = 1;
52
- return;
53
- }
75
+ async function main() {
76
+ const [command, ...args] = process.argv.slice(2);
54
77
 
55
- if (typeof result.status === 'number') {
56
- process.exitCode = result.status;
78
+ if (!command || command === 'help' || command === '--help') {
79
+ printHelp();
57
80
  return;
58
81
  }
59
82
 
60
- if (result.signal) {
83
+ const handler = COMMANDS[command];
84
+ if (!handler) {
85
+ printHelp();
61
86
  process.exitCode = 1;
62
- }
63
- }
64
-
65
- function runPackagedCli() {
66
- if (process.platform === 'win32') {
67
- const powershellScript = [
68
- '$argList = @($env:GSDD_LAUNCH_ARGS | ConvertFrom-Json)',
69
- \`& npm exec --yes "--package=\${packageSpec}" -- gsdd @argList\`,
70
- 'exit $LASTEXITCODE',
71
- ].join('; ');
72
-
73
- return spawnSync('powershell.exe', ['-NoProfile', '-Command', powershellScript], {
74
- stdio: 'inherit',
75
- env: {
76
- ...env,
77
- GSDD_LAUNCH_ARGS: JSON.stringify(args),
78
- },
79
- });
87
+ return;
80
88
  }
81
89
 
82
- return spawnSync('npm', ['exec', '--yes', \`--package=\${packageSpec}\`, '--', 'gsdd', ...args], {
83
- stdio: 'inherit',
84
- env,
85
- });
90
+ await handler(...args);
86
91
  }
87
92
 
88
- if (env.GSDD_CLI_PATH) {
89
- const localResult = spawnSync(process.execPath, [env.GSDD_CLI_PATH, ...args], {
90
- stdio: 'inherit',
91
- env,
92
- });
93
- forwardResult(localResult, 'Failed to run the local GSDD CLI path from GSDD_CLI_PATH.');
94
- } else {
95
- const packagedResult = runPackagedCli();
96
- forwardResult(packagedResult, \`Failed to run \${packageSpec} via npm exec.\`);
97
- }
93
+ await main();
98
94
  `;
99
95
  }
100
96
 
@@ -113,11 +109,23 @@ node "%~dp0gsdd.mjs" %*
113
109
  `;
114
110
  }
115
111
 
116
- function buildPlanningCliHelperEntries({ packageName = 'gsdd-cli', packageVersion }) {
112
+ function renderPlanningCliPowerShellShim() {
113
+ return `#!/usr/bin/env pwsh
114
+ $scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
115
+ node (Join-Path $scriptDir 'gsdd.mjs') @args
116
+ exit $LASTEXITCODE
117
+ `;
118
+ }
119
+
120
+ function readHelperLibContent(fileName) {
121
+ return readFileSync(join(__dirname, fileName), 'utf-8');
122
+ }
123
+
124
+ function buildPlanningCliHelperEntries() {
117
125
  return [
118
126
  {
119
127
  relativePath: 'bin/gsdd.mjs',
120
- content: renderPlanningCliLauncher({ packageName, packageVersion }),
128
+ content: renderPlanningCliLauncher(),
121
129
  },
122
130
  {
123
131
  relativePath: 'bin/gsdd',
@@ -127,6 +135,14 @@ function buildPlanningCliHelperEntries({ packageName = 'gsdd-cli', packageVersio
127
135
  relativePath: 'bin/gsdd.cmd',
128
136
  content: renderPlanningCliCmdShim(),
129
137
  },
138
+ {
139
+ relativePath: 'bin/gsdd.ps1',
140
+ content: renderPlanningCliPowerShellShim(),
141
+ },
142
+ ...HELPER_LIB_FILES.map((fileName) => ({
143
+ relativePath: `bin/lib/${fileName}`,
144
+ content: readHelperLibContent(fileName),
145
+ })),
130
146
  ];
131
147
  }
132
148
 
@@ -203,11 +203,13 @@ export function collectExpectedRuntimeSurfaceGroups({ cwd = process.cwd(), workf
203
203
  ];
204
204
  }
205
205
 
206
- export function evaluateRuntimeFreshness({ cwd = process.cwd(), workflows = [] }) {
207
- const groups = collectExpectedRuntimeSurfaceGroups({ cwd, workflows }).map((group) => {
208
- const installed = existsSync(join(cwd, group.root));
209
- const comparisons = installed
210
- ? group.entries.map((entry) => compareGeneratedFile({
206
+ export function evaluateRuntimeFreshness({ cwd = process.cwd(), workflows = [] }) {
207
+ const groups = collectExpectedRuntimeSurfaceGroups({ cwd, workflows }).map((group) => {
208
+ const installed = group.runtime === 'workspace-helper'
209
+ ? existsSync(join(cwd, '.planning'))
210
+ : existsSync(join(cwd, group.root));
211
+ const comparisons = installed
212
+ ? group.entries.map((entry) => compareGeneratedFile({
211
213
  cwd,
212
214
  runtime: group.runtime,
213
215
  relativePath: entry.relativePath,
@@ -0,0 +1,131 @@
1
+ import { existsSync } from 'fs';
2
+ import { dirname, join, resolve } from 'path';
3
+ import { fileURLToPath } from 'url';
4
+
5
+ function normalizePath(value, cwd) {
6
+ return resolve(cwd, String(value));
7
+ }
8
+
9
+ function hasPlanningMarker(root) {
10
+ return existsSync(join(root, '.planning', 'config.json')) || existsSync(join(root, '.planning'));
11
+ }
12
+
13
+ export function consumeWorkspaceRootArg(rawArgs = []) {
14
+ const args = [];
15
+ let workspaceRootArg = null;
16
+ let invalid = false;
17
+
18
+ for (let index = 0; index < rawArgs.length; index += 1) {
19
+ const arg = rawArgs[index];
20
+ if (arg !== '--workspace-root') {
21
+ args.push(arg);
22
+ continue;
23
+ }
24
+
25
+ const value = rawArgs[index + 1] ?? null;
26
+ if (!value || value.startsWith('--')) {
27
+ invalid = true;
28
+ continue;
29
+ }
30
+
31
+ workspaceRootArg = value;
32
+ index += 1;
33
+ }
34
+
35
+ return { args, workspaceRootArg, invalid };
36
+ }
37
+
38
+ export function findWorkspaceRoot(startDir = process.cwd()) {
39
+ let current = resolve(startDir);
40
+
41
+ while (true) {
42
+ if (hasPlanningMarker(current)) return current;
43
+ const parent = dirname(current);
44
+ if (parent === current) return null;
45
+ current = parent;
46
+ }
47
+ }
48
+
49
+ export function deriveWorkspaceRootFromHelperLocation(entryFileUrl) {
50
+ if (!entryFileUrl) return null;
51
+
52
+ const entryPath = entryFileUrl.startsWith('file:')
53
+ ? fileURLToPath(entryFileUrl)
54
+ : resolve(String(entryFileUrl));
55
+ const binDir = dirname(entryPath);
56
+ const planningDir = dirname(binDir);
57
+ const workspaceRoot = dirname(planningDir);
58
+
59
+ if (hasPlanningMarker(workspaceRoot) && binDir === join(planningDir, 'bin')) {
60
+ return workspaceRoot;
61
+ }
62
+
63
+ return null;
64
+ }
65
+
66
+ export function resolveWorkspaceContext(rawArgs = [], { cwd = process.cwd(), env = process.env } = {}) {
67
+ const { args, workspaceRootArg, invalid } = consumeWorkspaceRootArg(rawArgs);
68
+ if (invalid) {
69
+ return {
70
+ args,
71
+ invalid: true,
72
+ error: 'Usage: --workspace-root <path>',
73
+ workspaceRoot: resolve(cwd),
74
+ planningDir: join(resolve(cwd), '.planning'),
75
+ };
76
+ }
77
+
78
+ if (workspaceRootArg) {
79
+ const explicitRoot = normalizePath(workspaceRootArg, cwd);
80
+ if (!hasPlanningMarker(explicitRoot)) {
81
+ return {
82
+ args,
83
+ invalid: true,
84
+ error: `Workspace root does not contain .planning/: ${workspaceRootArg}`,
85
+ workspaceRoot: explicitRoot,
86
+ planningDir: join(explicitRoot, '.planning'),
87
+ };
88
+ }
89
+ }
90
+
91
+ const candidates = [];
92
+
93
+ if (workspaceRootArg) candidates.push(normalizePath(workspaceRootArg, cwd));
94
+ if (env.GSDD_WORKSPACE_ROOT) candidates.push(normalizePath(env.GSDD_WORKSPACE_ROOT, cwd));
95
+
96
+ const discovered = findWorkspaceRoot(cwd);
97
+ if (discovered) candidates.push(discovered);
98
+
99
+ candidates.push(resolve(cwd));
100
+
101
+ for (const candidate of candidates) {
102
+ if (hasPlanningMarker(candidate)) {
103
+ return {
104
+ args,
105
+ invalid: false,
106
+ workspaceRoot: candidate,
107
+ planningDir: join(candidate, '.planning'),
108
+ };
109
+ }
110
+ }
111
+
112
+ const fallbackRoot = candidates[0] ?? resolve(cwd);
113
+ return {
114
+ args,
115
+ invalid: false,
116
+ workspaceRoot: fallbackRoot,
117
+ planningDir: join(fallbackRoot, '.planning'),
118
+ };
119
+ }
120
+
121
+ export function bootstrapHelperWorkspace(entryFileUrl, env = process.env) {
122
+ const helperRoot = deriveWorkspaceRootFromHelperLocation(entryFileUrl);
123
+ if (!helperRoot) return null;
124
+ env.GSDD_WORKSPACE_ROOT = helperRoot;
125
+ try {
126
+ process.chdir(helperRoot);
127
+ } catch {
128
+ // best-effort: commands also resolve from env/upward search
129
+ }
130
+ return helperRoot;
131
+ }
@@ -2584,18 +2584,20 @@ Sub-gap (b) was closed by D28's `<persistence>` mandate and guarded by G30. Sub-
2584
2584
 
2585
2585
  ## D58 - Local Workflow Helper Launcher
2586
2586
 
2587
- **Decision (2026-04-22):** Workflow-embedded CLI helper commands must run through a generated local helper surface under `.planning/bin/`, with `.planning/bin/gsdd.mjs` as the canonical launcher, instead of assuming a bare `gsdd` binary is available on the consumer repo's PATH.
2587
+ **Decision (2026-04-22, revised 2026-04-23):** Workflow-embedded CLI helper commands must run through a generated local helper runtime under `.planning/bin/`, with `.planning/bin/gsdd.mjs` as the canonical launcher and copied support modules under `.planning/bin/lib/`, instead of assuming a bare `gsdd` binary is available on the consumer repo's PATH or proxying helper execution back through `npm exec` at workflow runtime.
2588
2588
 
2589
2589
  **Context:**
2590
2590
  - Public onboarding already leads with `npx gsdd-cli init`, which works even when the package is not globally installed.
2591
2591
  - The authored workflow surfaces had drifted into a different assumption: embedded helper commands such as `lifecycle-preflight`, `file-op`, and `phase-status` were written as bare `gsdd ...` invocations.
2592
2592
  - That split contract caused consumer friction in the exact place the deterministic helper seam was supposed to help: a workflow could initialize successfully, then fail later because the repo did not have a global `gsdd` on PATH.
2593
2593
  - The helper surface must stay out of `.agents/` ownership so it does not pollute unrelated `.agents` folders or leak into generated governance.
2594
+ - The first `.planning/bin/gsdd.mjs` repair still left the wrong runtime dependency in place: the generated file was a trampoline back through `npm exec --package=gsdd-cli@... -- gsdd ...`, so helper execution still depended on npm/package resolution and shell quirks at the exact moment deterministic local mechanics were supposed to be the reliable fallback.
2594
2595
 
2595
2596
  **Decision:**
2596
- - Generate `.planning/bin/gsdd.mjs` plus thin repo-local shell shims (`.planning/bin/gsdd`, `.planning/bin/gsdd.cmd`) on `gsdd init` for every initialized workspace.
2597
+ - Generate `.planning/bin/gsdd.mjs` as a self-contained local helper runtime plus repo-local shell shims (`.planning/bin/gsdd`, `.planning/bin/gsdd.cmd`, `.planning/bin/gsdd.ps1`) on `gsdd init` for every initialized workspace.
2597
2598
  - Regenerate that helper surface on `gsdd update` whenever `.planning/` exists.
2598
- - Keep the launcher machine-independent by resolving back to the published `gsdd-cli` package version used for generation rather than embedding framework checkout paths, and invoke the packaged `gsdd` bin explicitly through `npm exec --package=... -- gsdd ...` instead of the fragile bare `npx <package>` form.
2599
+ - Copy the minimal helper support modules into `.planning/bin/lib/` so workflow-time helper execution needs only Node, not npm/package-manager resolution.
2600
+ - Bootstrap the workspace root from the generated helper location and shared root-resolution logic so helper commands operate on repo truth instead of raw `process.cwd()`.
2599
2601
  - Route workflow-embedded helper commands through `node .planning/bin/gsdd.mjs ...` for the deterministic helper seam:
2600
2602
  - `lifecycle-preflight`
2601
2603
  - `file-op`
@@ -2606,10 +2608,16 @@ Sub-gap (b) was closed by D28's `<persistence>` mandate and guarded by G30. Sub-
2606
2608
  - It preserves the existing skills-first architecture instead of inventing a second discovery or governance path.
2607
2609
  - It fixes the actual consumer DX failure at the point where workflows invoke deterministic helper commands.
2608
2610
  - It keeps ownership aligned with `.planning/`, which already holds the other local runtime mechanics and generation-manifest state.
2611
+ - It removes npm/package fetches from the helper hot path, which is the stronger cross-platform ownership model for Linux, WSL, and Windows consumers.
2612
+ - It lets both the generated helper runtime and the main CLI share one root-resolution seam instead of relying on repo-root `cwd` as an unstated precondition.
2609
2613
 
2610
2614
  **Evidence:**
2611
2615
  - `bin/lib/init-flow.mjs`
2612
2616
  - `bin/lib/rendering.mjs`
2617
+ - `bin/lib/workspace-root.mjs`
2618
+ - `bin/lib/file-ops.mjs`
2619
+ - `bin/lib/phase.mjs`
2620
+ - `bin/lib/lifecycle-preflight.mjs`
2613
2621
  - `bin/lib/runtime-freshness.mjs`
2614
2622
  - `bin/lib/manifest.mjs`
2615
2623
  - `bin/lib/health.mjs`
@@ -2619,12 +2627,14 @@ Sub-gap (b) was closed by D28's `<persistence>` mandate and guarded by G30. Sub-
2619
2627
  - `tests/gsdd.init.test.cjs`
2620
2628
  - `tests/gsdd.health.test.cjs`
2621
2629
  - `tests/gsdd.manifest.test.cjs`
2630
+ - `tests/phase.test.cjs`
2622
2631
  - `tests/gsdd.scenarios.test.cjs`
2623
2632
 
2624
2633
  **Consequences:**
2625
- - Consumer repos no longer need a global `gsdd` binary for workflow-embedded helper mechanics after init.
2634
+ - Consumer repos no longer need a global `gsdd` binary or workflow-time `npm exec` trampoline for embedded helper mechanics after init.
2626
2635
  - Helper-command freshness is now owned under `.planning/` without widening `.agents` install detection.
2627
2636
  - Generated governance remains compact and routing-focused because helper-surface instructions stay out of `AGENTS.md`.
2637
+ - Cross-platform proof is stronger for the local-helper seam itself, but direct live validation still needs to stay conservative by environment: the repo now has focused tests plus Windows fixture proof; Linux/WSL live consumer validation remains a separate evidence question.
2628
2638
 
2629
2639
  ## Maintenance
2630
2640
 
@@ -445,11 +445,12 @@
445
445
  - `get-shit-done/workflows/new-project.md`, `get-shit-done/workflows/quick.md`
446
446
 
447
447
  ## D58 — Local Workflow Helper Launcher
448
- - `bin/lib/rendering.mjs` (`renderPlanningCliLauncher` and `.planning/bin/gsdd*` shim rendering for a machine-independent local helper surface)
448
+ - `bin/lib/rendering.mjs` (`renderPlanningCliLauncher`, `.planning/bin/gsdd*` shim rendering, and copied `.planning/bin/lib/*` helper-runtime support modules)
449
449
  - `bin/lib/init-flow.mjs` (`gsdd init` / `gsdd update` generation of `.planning/bin/gsdd*`)
450
+ - `bin/lib/workspace-root.mjs`, `bin/lib/file-ops.mjs`, `bin/lib/phase.mjs`, `bin/lib/lifecycle-preflight.mjs`
450
451
  - `bin/lib/runtime-freshness.mjs`, `bin/lib/manifest.mjs`, `bin/lib/health.mjs`, `bin/lib/health-truth.mjs`
451
452
  - `distilled/workflows/execute.md`, `distilled/workflows/verify.md`, `distilled/workflows/resume.md`, `distilled/workflows/progress.md`
452
- - `tests/gsdd.init.test.cjs`, `tests/gsdd.health.test.cjs`, `tests/gsdd.manifest.test.cjs`, `tests/gsdd.scenarios.test.cjs`
453
+ - `tests/gsdd.init.test.cjs`, `tests/gsdd.health.test.cjs`, `tests/gsdd.manifest.test.cjs`, `tests/phase.test.cjs`, `tests/gsdd.scenarios.test.cjs`
453
454
 
454
455
  ---
455
456
 
@@ -28,24 +28,31 @@ These runtimes use the same portable workflow surfaces, but they do not carry eq
28
28
 
29
29
  Any tool that can read the generated markdown workflows can still use the framework manually, but that is outside the current native-proof story.
30
30
 
31
- ## Current runtime surfaces
32
-
33
- | Runtime | Current claim | Entry surface | Notes |
34
- | --- | --- | --- | --- |
35
- | Claude Code | Directly validated | `.claude/skills/`, `.claude/commands/`, `.claude/agents/` | Native surface was a mandatory Phase 32 validation target; installed generated files are freshness-checked locally |
36
- | OpenCode | Directly validated | `.opencode/commands/`, `.opencode/agents/` | Native command and checker path; installed generated files are freshness-checked locally |
37
- | Codex CLI | Directly validated | `.agents/skills/gsdd-*` plus `.codex/agents/gsdd-plan-checker.toml` | Portable skill entry, native checker adapter, mandatory Phase 32 validation target |
31
+ ## Current runtime surfaces
32
+
33
+ Two surfaces matter for users:
34
+
35
+ - `.agents/skills/gsdd-*` is the shared workflow entry surface. Depending on the runtime, users invoke those workflows as `/gsdd-*`, `$gsdd-*`, or by opening the skill markdown directly.
36
+ - `.planning/bin/gsdd*` is an internal local helper surface used by workflow-embedded lifecycle mechanics after init. It is not the primary user entry surface.
37
+
38
+ | Runtime | Current claim | Entry surface | Notes |
39
+ | --- | --- | --- | --- |
40
+ | Claude Code | Directly validated | `.claude/skills/`, `.claude/commands/`, `.claude/agents/` | Native surface was a mandatory Phase 32 validation target; installed generated files are freshness-checked locally |
41
+ | OpenCode | Directly validated | `.opencode/commands/`, `.opencode/agents/` | Native command and checker path; installed generated files are freshness-checked locally |
42
+ | Codex CLI | Directly validated | `.agents/skills/gsdd-*` plus `.codex/agents/gsdd-plan-checker.toml` | Portable skill entry remains the user workflow surface; the native checker adapter supports plan checking |
38
43
  | Cursor | Same core workflow | `.agents/skills/gsdd-*` | Skills-native path; generated skill files are freshness-checked locally, but the runtime is not claimed as parity-validated |
39
44
  | GitHub Copilot | Same core workflow | `.agents/skills/gsdd-*` | Skills-native path; generated skill files are freshness-checked locally, but the runtime is not claimed as parity-validated |
40
45
  | Gemini CLI | Same core workflow | `.agents/skills/gsdd-*` | Skills-native path; governance is optional, generated skill files are freshness-checked locally, and parity is not claimed |
41
46
 
42
47
  ## Generated-surface freshness
43
48
 
44
- The authored source contract stays in `distilled/workflows/*`. Generated runtime-facing files are trusted only through deterministic rendering:
45
-
46
- - `gsdd health` compares any installed generated surfaces under `.agents/skills/`, `.claude/`, `.opencode/`, and `.codex/` against current render output.
47
- - `gsdd update` regenerates drifted generated surfaces from the authored workflow and delegate sources.
48
- - Missing generated surfaces are not treated as drift unless the corresponding runtime surface is actually installed locally.
49
+ The authored source contract stays in `distilled/workflows/*`. Generated runtime-facing files are trusted only through deterministic rendering:
50
+
51
+ - `gsdd health` compares any installed generated surfaces under `.planning/bin/`, `.agents/skills/`, `.claude/`, `.opencode/`, and `.codex/` against current render output.
52
+ - `npx gsdd-cli update` regenerates drifted generated surfaces from the authored workflow and delegate sources.
53
+ - Missing generated surfaces are not treated as drift unless the corresponding runtime surface is actually installed locally.
54
+
55
+ This is a generated-surface repair story, not a blanket runtime-parity claim. `health` and `update` can tell you whether installed files are current and can deterministically regenerate them. They cannot make an unsupported runtime become directly validated.
49
56
 
50
57
  ## What stays portable
51
58
 
@@ -74,4 +81,4 @@ Portable contract does not mean equal UX everywhere.
74
81
  - `docs/BROWNFIELD-PROOF.md`
75
82
  - `docs/proof/consumer-node-cli/README.md`
76
83
  - `docs/VERIFICATION-DISCIPLINE.md`
77
- - `gsdd health` / `gsdd update`
84
+ - `gsdd health` / `npx gsdd-cli update`
@@ -178,24 +178,48 @@ The 7 check dimensions: requirement coverage, task completeness, dependency corr
178
178
  | `gsdd-resume` | Restore context from checkpoint and route to next action | Starting a new session |
179
179
  | `gsdd-progress` | Show project status and route to next action | "Where am I?" |
180
180
 
181
- ### CLI Commands
182
-
183
- | Command | Purpose |
184
- |---------|---------|
185
- | `gsdd init [--tools <platform>]` | Set up `.planning/`, generate adapters |
186
- | `gsdd update [--tools <platform>]` | Regenerate adapters from latest sources |
187
- | `gsdd update --templates` | Refresh role contracts and delegates (warns about user modifications) |
188
- | `gsdd find-phase [N]` | Show phase info as JSON (for agent consumption) |
189
- | `gsdd verify <N>` | Run artifact checks for phase N |
190
- | `gsdd scaffold phase <N> [name]` | Create a new phase plan file |
191
- | `gsdd models show` | Display effective model state across all runtimes |
192
- | `gsdd models profile <tier>` | Set global model profile (`quality`/`balanced`/`budget`) |
193
- | `gsdd models agent-profile --agent <id> --profile <tier>` | Per-agent semantic override |
194
- | `gsdd models set --runtime <rt> --agent <id> --model <id>` | Exact runtime model override |
195
- | `gsdd models clear --runtime <rt> --agent <id>` | Remove runtime override |
181
+ ### CLI Commands
182
+
183
+ | Command | Purpose |
184
+ |---------|---------|
185
+ | `npx gsdd-cli init [--tools <platform>]` | First-run setup: create `.planning/`, `.agents/skills/`, and selected runtime adapters |
186
+ | `gsdd health [--json]` | Check installed generated surfaces and workspace state |
187
+ | `npx gsdd-cli update [--tools <platform>]` | Regenerate installed runtime surfaces from latest sources |
188
+ | `npx gsdd-cli update --templates` | Refresh role contracts and delegates (warns about user modifications) |
189
+ | `gsdd models show` | Display effective model state across all runtimes |
190
+ | `gsdd models profile <tier>` | Set global model profile (`quality`/`balanced`/`budget`) |
191
+ | `gsdd models agent-profile --agent <id> --profile <tier>` | Per-agent semantic override |
192
+ | `gsdd models set --runtime <rt> --agent <id> --model <id>` | Exact runtime model override |
193
+ | `gsdd models clear --runtime <rt> --agent <id>` | Remove runtime override |
196
194
  | `gsdd help` | Show all commands |
197
195
 
198
- `init` generates a local `.planning/bin/gsdd*` helper surface. Workflow-embedded helper commands still use `node .planning/bin/gsdd.mjs ...` as the portable contract, so lifecycle preflight, file-op, and phase-status mechanics do not depend on a global `gsdd` binary after init.
196
+ Normal user flow:
197
+
198
+ 1. Run `npx gsdd-cli init`.
199
+ 2. Enter workflows through your runtime surface: `/gsdd-*` or `$gsdd-*`.
200
+ 3. Use `gsdd health` to check installed generated surfaces.
201
+ 4. Use `npx gsdd-cli update` when generated surfaces drift or you want the latest shipped output.
202
+
203
+ Surface split:
204
+
205
+ - `.agents/skills/gsdd-*` is the workflow entry surface.
206
+ - `.planning/bin/gsdd*` is an internal local helper surface used by workflow-embedded lifecycle mechanics. It is kept available, but it is not the normal first-run user entrypoint.
207
+
208
+ Advanced/internal helper commands remain available:
209
+
210
+ | Command | Purpose |
211
+ |---------|---------|
212
+ | `gsdd file-op <copy\|delete\|regex-sub>` | Deterministic workspace-confined file copy, delete, and text mutation |
213
+ | `gsdd phase-status <N> <status>` | Update a single ROADMAP phase status through the local helper surface |
214
+ | `gsdd lifecycle-preflight <surface> [phase]` | Inspect deterministic lifecycle gate results for a workflow surface |
215
+
216
+ Other CLI commands that remain available outside the first-run path:
217
+
218
+ | Command | Purpose |
219
+ |---------|---------|
220
+ | `gsdd find-phase [N]` | Show phase info as JSON (for agent consumption) |
221
+ | `gsdd verify <N>` | Run artifact checks for phase N |
222
+ | `gsdd scaffold phase <N> [name]` | Create a new phase plan file |
199
223
 
200
224
  ### Platform flags for `--tools`
201
225
 
@@ -304,10 +328,11 @@ Cursor, Copilot, and Gemini use the same core workflow through installed `.agent
304
328
  - `Codex`: `$gsdd-plan-milestone-gaps` when audit findings need closure work, or `$gsdd-complete-milestone -> $gsdd-new-milestone` when the milestone is ready to ship
305
329
  - `Cursor / Copilot / Gemini`: use the matching slash commands once the skills are installed, with the same routing as above
306
330
 
307
- ### Existing Codebase
308
-
309
- `npx gsdd-cli init`
310
-
331
+ ### Existing Codebase
332
+
333
+ `npx gsdd-cli init`
334
+
335
+ - Choose one starting lane after init:
311
336
  - `Claude/OpenCode`: `/gsdd-quick` for a concrete bounded change, `/gsdd-new-project` for fuzzy or milestone-shaped work, or `/gsdd-map-codebase` first when the repo needs a deeper brownfield baseline
312
337
  - `Codex`: `$gsdd-quick` for a concrete bounded change, `$gsdd-new-project` for fuzzy or milestone-shaped work, or `$gsdd-map-codebase` first when the repo needs a deeper brownfield baseline
313
338
  - `Cursor / Copilot / Gemini`: `/gsdd-quick`, `/gsdd-new-project`, or `/gsdd-map-codebase` from the slash command menu once the skills are installed, using the same routing rules above
@@ -369,13 +394,19 @@ Run `gsdd-progress`. It reads all artifacts and tells you where you are and what
369
394
 
370
395
  Do not re-run `gsdd-execute`. Use `gsdd-quick` for targeted fixes, or `gsdd-verify` to systematically identify issues.
371
396
 
372
- ### Template Refresh After Update
397
+ ### Template Refresh After Update
373
398
 
374
399
  ```bash
375
400
  npx gsdd-cli update --templates # Refreshes role contracts and delegates
376
401
  ```
377
402
 
378
- If you've modified any templates, the generation manifest detects this and warns you before overwriting. The SHA-256 hash of each generated file is tracked in `.planning/generation-manifest.json`.
403
+ If you've modified any templates, the generation manifest detects this and warns you before overwriting. The SHA-256 hash of each generated file is tracked in `.planning/generation-manifest.json`.
404
+
405
+ ### Generated Surfaces Drift Or A Runtime Command Goes Missing
406
+
407
+ Start with `gsdd health`. If it reports drift or missing installed generated surfaces, run `npx gsdd-cli update` for the whole workspace or `npx gsdd-cli update --tools <runtime>` for a specific runtime.
408
+
409
+ That repair path is deterministic for generated files. It does not imply that every runtime has equal native ergonomics or equal validation depth.
379
410
 
380
411
  ### Model Costs Too High
381
412
 
@@ -407,7 +438,9 @@ Switch to budget profile: `gsdd models profile budget`. Disable research and pla
407
438
  bin/
408
439
  gsdd # POSIX shim for the local helper surface
409
440
  gsdd.cmd # Windows shim for the local helper surface
410
- gsdd.mjs # Canonical local workflow-helper launcher
441
+ gsdd.mjs # Canonical self-contained local helper runtime
442
+ gsdd.ps1 # PowerShell shim for the local helper surface
443
+ lib/ # Copied helper-runtime support modules
411
444
  generation-manifest.json # SHA-256 hashes for template versioning
412
445
  .continue-here.md # Session checkpoint (created by pause, consumed by resume)
413
446
  research/ # Domain research outputs
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gsdd-cli",
3
- "version": "0.18.4",
3
+ "version": "0.18.5",
4
4
  "description": "Workspine — a repo-native delivery spine for long-horizon AI-assisted work, with directly validated support for Claude Code, Codex CLI, and OpenCode, published as gsdd-cli.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -58,4 +58,4 @@
58
58
  "url": "https://github.com/PatrickSys/workspine/issues"
59
59
  },
60
60
  "homepage": "https://github.com/PatrickSys/workspine#readme"
61
- }
61
+ }