gsdd-cli 0.18.3 → 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 +45 -28
- package/bin/lib/file-ops.mjs +24 -18
- package/bin/lib/health.mjs +19 -9
- package/bin/lib/init-flow.mjs +6 -6
- package/bin/lib/init-runtime.mjs +39 -27
- package/bin/lib/lifecycle-preflight.mjs +13 -8
- package/bin/lib/phase.mjs +40 -23
- package/bin/lib/rendering.mjs +72 -56
- package/bin/lib/runtime-freshness.mjs +7 -5
- package/bin/lib/workspace-root.mjs +131 -0
- package/distilled/DESIGN.md +14 -4
- package/distilled/EVIDENCE-INDEX.md +3 -2
- package/docs/RUNTIME-SUPPORT.md +20 -13
- package/docs/USER-GUIDE.md +56 -23
- package/package.json +2 -2
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
|
|
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
|
|
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
|
|
107
|
-
`init` generates
|
|
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>]` |
|
|
331
|
-
| `gsdd
|
|
332
|
-
| `gsdd
|
|
333
|
-
| `gsdd
|
|
334
|
-
| `gsdd
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
|
339
|
-
|
|
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
|
|
package/bin/lib/file-ops.mjs
CHANGED
|
@@ -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
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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
|
}
|
package/bin/lib/health.mjs
CHANGED
|
@@ -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
|
|
22
|
-
|
|
23
|
-
|
|
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
|
package/bin/lib/init-flow.mjs
CHANGED
|
@@ -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:
|
|
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) {
|
package/bin/lib/init-runtime.mjs
CHANGED
|
@@ -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
|
|
202
|
-
- init also generates a local .planning/bin/gsdd* helper surface
|
|
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
|
-
|
|
214
|
-
|
|
215
|
-
|
|
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
|
|
302
|
-
|
|
303
|
-
|
|
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
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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
|
|
189
|
-
|
|
190
|
-
|
|
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
|
|
239
|
-
|
|
240
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
322
|
-
|
|
323
|
-
|
|
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]');
|
package/bin/lib/rendering.mjs
CHANGED
|
@@ -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(
|
|
34
|
-
|
|
35
|
-
throw new Error('renderPlanningCliLauncher requires packageVersion');
|
|
36
|
-
}
|
|
43
|
+
function renderPlanningCliLauncher() {
|
|
44
|
+
return `#!/usr/bin/env node
|
|
37
45
|
|
|
38
|
-
|
|
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
|
-
|
|
51
|
+
bootstrapHelperWorkspace(import.meta.url);
|
|
41
52
|
|
|
42
|
-
|
|
53
|
+
const COMMANDS = {
|
|
54
|
+
'file-op': cmdFileOp,
|
|
55
|
+
'lifecycle-preflight': cmdLifecyclePreflight,
|
|
56
|
+
'phase-status': cmdPhaseStatus,
|
|
57
|
+
};
|
|
43
58
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
|
49
|
-
|
|
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 (
|
|
56
|
-
|
|
78
|
+
if (!command || command === 'help' || command === '--help') {
|
|
79
|
+
printHelp();
|
|
57
80
|
return;
|
|
58
81
|
}
|
|
59
82
|
|
|
60
|
-
|
|
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
|
-
|
|
83
|
-
stdio: 'inherit',
|
|
84
|
-
env,
|
|
85
|
-
});
|
|
90
|
+
await handler(...args);
|
|
86
91
|
}
|
|
87
92
|
|
|
88
|
-
|
|
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
|
|
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(
|
|
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 =
|
|
209
|
-
|
|
210
|
-
|
|
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
|
+
}
|
package/distilled/DESIGN.md
CHANGED
|
@@ -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
|
|
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
|
|
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
|
-
-
|
|
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
|
|
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
|
|
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
|
|
package/docs/RUNTIME-SUPPORT.md
CHANGED
|
@@ -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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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`
|
package/docs/USER-GUIDE.md
CHANGED
|
@@ -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>]` |
|
|
186
|
-
| `gsdd
|
|
187
|
-
| `gsdd update --
|
|
188
|
-
| `gsdd
|
|
189
|
-
| `gsdd
|
|
190
|
-
| `gsdd
|
|
191
|
-
| `gsdd models
|
|
192
|
-
| `gsdd models
|
|
193
|
-
| `gsdd models
|
|
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
|
-
|
|
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
|
|
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.
|
|
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
|
+
}
|