gsdd-cli 0.18.4 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +625 -607
  3. package/agents/DISTILLATION.md +421 -421
  4. package/agents/README.md +62 -62
  5. package/agents/approach-explorer.md +361 -361
  6. package/agents/debugger.md +82 -82
  7. package/agents/executor.md +394 -394
  8. package/agents/integration-checker.md +318 -318
  9. package/agents/mapper.md +103 -103
  10. package/agents/planner.md +313 -313
  11. package/agents/researcher.md +84 -84
  12. package/agents/roadmapper.md +296 -296
  13. package/agents/synthesizer.md +236 -236
  14. package/agents/verifier.md +337 -337
  15. package/bin/adapters/agents.mjs +34 -34
  16. package/bin/adapters/claude.mjs +191 -191
  17. package/bin/adapters/codex.mjs +85 -85
  18. package/bin/adapters/index.mjs +20 -20
  19. package/bin/adapters/opencode.mjs +278 -278
  20. package/bin/gsdd.mjs +123 -116
  21. package/bin/lib/cli-utils.mjs +28 -28
  22. package/bin/lib/evidence-contract.mjs +112 -112
  23. package/bin/lib/file-ops.mjs +209 -161
  24. package/bin/lib/health-truth.mjs +181 -178
  25. package/bin/lib/health.mjs +265 -235
  26. package/bin/lib/init-flow.mjs +252 -236
  27. package/bin/lib/init-prompts.mjs +248 -247
  28. package/bin/lib/init-runtime.mjs +225 -212
  29. package/bin/lib/init.mjs +17 -17
  30. package/bin/lib/lifecycle-preflight.mjs +360 -333
  31. package/bin/lib/lifecycle-state.mjs +351 -267
  32. package/bin/lib/manifest.mjs +116 -114
  33. package/bin/lib/models.mjs +411 -411
  34. package/bin/lib/phase.mjs +397 -378
  35. package/bin/lib/plan-constants.mjs +30 -30
  36. package/bin/lib/provenance.mjs +109 -106
  37. package/bin/lib/rendering.mjs +178 -130
  38. package/bin/lib/runtime-freshness.mjs +221 -219
  39. package/bin/lib/templates.mjs +225 -224
  40. package/bin/lib/workspace-root.mjs +132 -0
  41. package/distilled/DESIGN.md +2347 -2327
  42. package/distilled/EVIDENCE-INDEX.md +397 -394
  43. package/distilled/README.md +196 -193
  44. package/distilled/SKILL.md +86 -85
  45. package/distilled/templates/agents.block.md +21 -21
  46. package/distilled/templates/agents.md +6 -6
  47. package/distilled/templates/approach.md +232 -232
  48. package/distilled/templates/auth-matrix.md +78 -78
  49. package/distilled/templates/brownfield-change/CHANGE.md +99 -0
  50. package/distilled/templates/brownfield-change/HANDOFF.md +38 -0
  51. package/distilled/templates/brownfield-change/VERIFICATION.md +56 -0
  52. package/distilled/templates/codebase/architecture.md +110 -110
  53. package/distilled/templates/codebase/concerns.md +95 -95
  54. package/distilled/templates/codebase/conventions.md +193 -193
  55. package/distilled/templates/codebase/stack.md +96 -96
  56. package/distilled/templates/delegates/approach-explorer.md +25 -25
  57. package/distilled/templates/delegates/mapper-arch.md +26 -26
  58. package/distilled/templates/delegates/mapper-concerns.md +27 -27
  59. package/distilled/templates/delegates/mapper-quality.md +28 -28
  60. package/distilled/templates/delegates/mapper-tech.md +25 -25
  61. package/distilled/templates/delegates/plan-checker.md +68 -68
  62. package/distilled/templates/delegates/researcher-architecture.md +30 -30
  63. package/distilled/templates/delegates/researcher-features.md +30 -30
  64. package/distilled/templates/delegates/researcher-pitfalls.md +30 -30
  65. package/distilled/templates/delegates/researcher-stack.md +30 -30
  66. package/distilled/templates/delegates/researcher-synthesizer.md +31 -31
  67. package/distilled/templates/research/architecture.md +57 -57
  68. package/distilled/templates/research/features.md +23 -23
  69. package/distilled/templates/research/pitfalls.md +46 -46
  70. package/distilled/templates/research/stack.md +45 -45
  71. package/distilled/templates/research/summary.md +67 -67
  72. package/distilled/templates/roadmap.md +74 -62
  73. package/distilled/templates/spec.md +110 -110
  74. package/distilled/workflows/audit-milestone.md +275 -271
  75. package/distilled/workflows/complete-milestone.md +336 -332
  76. package/distilled/workflows/execute.md +454 -449
  77. package/distilled/workflows/map-codebase.md +253 -253
  78. package/distilled/workflows/new-milestone.md +242 -238
  79. package/distilled/workflows/new-project.md +398 -398
  80. package/distilled/workflows/pause.md +160 -156
  81. package/distilled/workflows/plan-milestone-gaps.md +183 -183
  82. package/distilled/workflows/plan.md +451 -447
  83. package/distilled/workflows/progress.md +227 -223
  84. package/distilled/workflows/quick.md +351 -347
  85. package/distilled/workflows/resume.md +220 -212
  86. package/distilled/workflows/verify-work.md +260 -260
  87. package/distilled/workflows/verify.md +431 -429
  88. package/docs/BROWNFIELD-PROOF.md +95 -95
  89. package/docs/RUNTIME-SUPPORT.md +93 -75
  90. package/docs/USER-GUIDE.md +440 -399
  91. package/docs/VERIFICATION-DISCIPLINE.md +59 -59
  92. package/docs/claude/context-monitor.md +98 -98
  93. package/docs/proof/consumer-node-cli/README.md +37 -37
  94. package/docs/proof/consumer-node-cli/ROADMAP.md +14 -14
  95. package/docs/proof/consumer-node-cli/SPEC.md +17 -17
  96. package/docs/proof/consumer-node-cli/brief.md +9 -9
  97. package/docs/proof/consumer-node-cli/phases/01-foundation/01-01-PLAN.md +34 -34
  98. package/docs/proof/consumer-node-cli/phases/01-foundation/01-01-SUMMARY.md +10 -10
  99. package/docs/proof/consumer-node-cli/phases/01-foundation/01-VERIFICATION.md +30 -30
  100. package/package.json +62 -61
@@ -1,30 +1,30 @@
1
- export const PLAN_CHECK_DIMENSIONS = [
2
- 'requirement_coverage',
3
- 'task_completeness',
4
- 'dependency_correctness',
5
- 'key_link_completeness',
6
- 'scope_sanity',
7
- 'must_have_quality',
8
- 'context_compliance',
9
- 'goal_achievement',
10
- 'approach_alignment',
11
- ];
12
-
13
- export const MAX_CHECKER_CYCLES = 3;
14
-
15
- export const CHECKER_STATUSES = ['passed', 'issues_found'];
16
-
17
- export const CHECKER_JSON_SCHEMA = {
18
- status: 'passed | issues_found',
19
- summary: 'string',
20
- issues: [
21
- {
22
- dimension: 'string',
23
- severity: 'blocker | warning',
24
- description: 'string',
25
- plan: 'string',
26
- task: 'string',
27
- fix_hint: 'string',
28
- },
29
- ],
30
- };
1
+ export const PLAN_CHECK_DIMENSIONS = [
2
+ 'requirement_coverage',
3
+ 'task_completeness',
4
+ 'dependency_correctness',
5
+ 'key_link_completeness',
6
+ 'scope_sanity',
7
+ 'must_have_quality',
8
+ 'context_compliance',
9
+ 'goal_achievement',
10
+ 'approach_alignment',
11
+ ];
12
+
13
+ export const MAX_CHECKER_CYCLES = 3;
14
+
15
+ export const CHECKER_STATUSES = ['passed', 'issues_found'];
16
+
17
+ export const CHECKER_JSON_SCHEMA = {
18
+ status: 'passed | issues_found',
19
+ summary: 'string',
20
+ issues: [
21
+ {
22
+ dimension: 'string',
23
+ severity: 'blocker | warning',
24
+ description: 'string',
25
+ plan: 'string',
26
+ task: 'string',
27
+ fix_hint: 'string',
28
+ },
29
+ ],
30
+ };
@@ -1,56 +1,59 @@
1
- function normalizePrState(prState) {
2
- if (!prState) return 'none';
3
- return String(prState).trim().toLowerCase();
4
- }
5
-
6
- function normalizeCount(value) {
7
- if (value === 'unknown' || value === null || value === undefined) return 'unknown';
8
- const numeric = Number(value);
9
- return Number.isFinite(numeric) ? numeric : 'unknown';
10
- }
11
-
12
- function normalizeCheckpointWorkflow(workflow) {
13
- const normalized = String(workflow || 'generic').trim().toLowerCase();
14
- return ['phase', 'quick', 'generic'].includes(normalized) ? normalized : 'generic';
15
- }
16
-
1
+ function normalizePrState(prState) {
2
+ if (!prState) return 'none';
3
+ return String(prState).trim().toLowerCase();
4
+ }
5
+
6
+ function normalizeCount(value) {
7
+ if (value === 'unknown' || value === null || value === undefined) return 'unknown';
8
+ const numeric = Number(value);
9
+ return Number.isFinite(numeric) ? numeric : 'unknown';
10
+ }
11
+
12
+ function normalizeCheckpointWorkflow(workflow) {
13
+ const normalized = String(workflow || 'generic').trim().toLowerCase();
14
+ return ['phase', 'quick', 'generic'].includes(normalized) ? normalized : 'generic';
15
+ }
16
+
17
17
  export function classifyCheckpointRouting(workflow) {
18
18
  const normalizedWorkflow = normalizeCheckpointWorkflow(workflow);
19
19
  const progressBlocks = normalizedWorkflow === 'phase' || normalizedWorkflow === 'quick';
20
-
21
- return {
22
- workflow: normalizedWorkflow,
23
- routingClass: progressBlocks ? 'blocking' : 'informational',
24
- progressBlocks,
25
- resumeOwnsCleanup: true,
26
- };
27
- }
28
-
20
+
21
+ return {
22
+ workflow: normalizedWorkflow,
23
+ routingClass: progressBlocks ? 'blocking' : 'informational',
24
+ progressBlocks,
25
+ resumeOwnsCleanup: true,
26
+ };
27
+ }
28
+
29
29
  export function parseGitStatusShort(statusText = '') {
30
- const lines = statusText
31
- .replace(/\r\n/g, '\n')
32
- .split('\n')
33
- .map((line) => line.trimEnd())
34
- .filter(Boolean);
35
-
36
- const files = [];
37
- for (const line of lines) {
38
- const match = line.match(/^(.)(.)\s+(.+)$/);
39
- if (!match) continue;
40
-
41
- const indexStatus = match[1];
42
- const worktreeStatus = match[2];
43
- if (indexStatus === '!' && worktreeStatus === '!') continue;
44
-
45
- const filePath = match[3].replace(/\\/g, '/');
46
- files.push({
47
- path: filePath,
48
- staged: indexStatus !== ' ' && indexStatus !== '?' && indexStatus !== '!',
49
- unstaged: worktreeStatus !== ' ' && worktreeStatus !== '?' && worktreeStatus !== '!',
50
- untracked: indexStatus === '?' || worktreeStatus === '?',
51
- });
52
- }
53
-
30
+ const lines = statusText
31
+ .replace(/\r\n/g, '\n')
32
+ .split('\n')
33
+ .map((line) => line.trimEnd())
34
+ .filter(Boolean);
35
+
36
+ const files = [];
37
+ for (const line of lines) {
38
+ const match = line.match(/^(.)(.)\s+(.+)$/);
39
+ if (!match) continue;
40
+
41
+ const indexStatus = match[1];
42
+ const worktreeStatus = match[2];
43
+ if (indexStatus === '!' && worktreeStatus === '!') continue;
44
+
45
+ const rawPath = match[3].replace(/\\/g, '/');
46
+ const renameMatch = rawPath.match(/^(.*?)\s+->\s+(.*?)$/);
47
+ const filePath = renameMatch ? renameMatch[2] : rawPath;
48
+ files.push({
49
+ path: filePath,
50
+ fromPath: renameMatch ? renameMatch[1] : null,
51
+ staged: indexStatus !== ' ' && indexStatus !== '?' && indexStatus !== '!',
52
+ unstaged: worktreeStatus !== ' ' && worktreeStatus !== '?' && worktreeStatus !== '!',
53
+ untracked: indexStatus === '?' || worktreeStatus === '?',
54
+ });
55
+ }
56
+
54
57
  return {
55
58
  files,
56
59
  stagedCount: files.filter((file) => file.staged).length,
@@ -206,55 +209,55 @@ export function buildProvenanceSnapshot({
206
209
  });
207
210
 
208
211
  const warnings = [];
209
-
210
- if (status.dirty) {
211
- warnings.push({
212
- id: 'dirty_worktree',
213
- severity: 'warning',
214
- summary: 'Local worktree contains staged, unstaged, or untracked changes.',
215
- });
216
- }
217
-
218
- if (commitsAheadOfMain !== 'unknown' && commitsAheadOfMain > 0) {
219
- warnings.push({
220
- id: 'ahead_of_main',
221
- severity: 'warning',
222
- summary: `${commitsAheadOfMain} commit(s) are ahead of main on the current branch.`,
223
- });
224
- }
225
-
226
- if (commitsAheadOfRemote !== 'unknown' && commitsAheadOfRemote > 0) {
227
- warnings.push({
228
- id: 'unpushed_commits',
229
- severity: 'warning',
230
- summary: `${commitsAheadOfRemote} commit(s) are ahead of the tracked remote branch.`,
231
- });
232
- }
233
-
234
- if (prState === 'none') {
235
- warnings.push({
236
- id: 'missing_pr',
237
- severity: 'warning',
238
- summary: 'No pull request is associated with the current branch.',
239
- });
240
- }
241
-
242
- if (git.staleBranch) {
243
- warnings.push({
244
- id: 'stale_branch',
245
- severity: 'warning',
246
- summary: 'The current branch is stale or spent relative to the intended integration surface.',
247
- });
248
- }
249
-
250
- if (git.mixedScope) {
251
- warnings.push({
252
- id: 'mixed_scope',
253
- severity: 'warning',
254
- summary: 'The current worktree appears to mix multiple write scopes or phases.',
255
- });
256
- }
257
-
212
+
213
+ if (status.dirty) {
214
+ warnings.push({
215
+ id: 'dirty_worktree',
216
+ severity: 'warning',
217
+ summary: 'Local worktree contains staged, unstaged, or untracked changes.',
218
+ });
219
+ }
220
+
221
+ if (commitsAheadOfMain !== 'unknown' && commitsAheadOfMain > 0) {
222
+ warnings.push({
223
+ id: 'ahead_of_main',
224
+ severity: 'warning',
225
+ summary: `${commitsAheadOfMain} commit(s) are ahead of main on the current branch.`,
226
+ });
227
+ }
228
+
229
+ if (commitsAheadOfRemote !== 'unknown' && commitsAheadOfRemote > 0) {
230
+ warnings.push({
231
+ id: 'unpushed_commits',
232
+ severity: 'warning',
233
+ summary: `${commitsAheadOfRemote} commit(s) are ahead of the tracked remote branch.`,
234
+ });
235
+ }
236
+
237
+ if (prState === 'none') {
238
+ warnings.push({
239
+ id: 'missing_pr',
240
+ severity: 'warning',
241
+ summary: 'No pull request is associated with the current branch.',
242
+ });
243
+ }
244
+
245
+ if (git.staleBranch) {
246
+ warnings.push({
247
+ id: 'stale_branch',
248
+ severity: 'warning',
249
+ summary: 'The current branch is stale or spent relative to the intended integration surface.',
250
+ });
251
+ }
252
+
253
+ if (git.mixedScope) {
254
+ warnings.push({
255
+ id: 'mixed_scope',
256
+ severity: 'warning',
257
+ summary: 'The current worktree appears to mix multiple write scopes or phases.',
258
+ });
259
+ }
260
+
258
261
  if (git.materialCheckpointMismatch) {
259
262
  warnings.push({
260
263
  id: 'checkpoint_mismatch',
@@ -299,14 +302,14 @@ export function buildProvenanceSnapshot({
299
302
  },
300
303
  git: {
301
304
  branch: git.branch || 'unknown',
302
- prState,
303
- commitsAheadOfMain,
304
- commitsAheadOfRemote,
305
- stagedCount: status.stagedCount,
306
- unstagedCount: status.unstagedCount,
307
- untrackedCount: status.untrackedCount,
308
- dirty: status.dirty,
309
- },
305
+ prState,
306
+ commitsAheadOfMain,
307
+ commitsAheadOfRemote,
308
+ stagedCount: status.stagedCount,
309
+ unstagedCount: status.unstagedCount,
310
+ untrackedCount: status.untrackedCount,
311
+ dirty: status.dirty,
312
+ },
310
313
  integrationSurface: {
311
314
  staleBranch: Boolean(git.staleBranch),
312
315
  mixedScope: Boolean(git.mixedScope),
@@ -1,100 +1,128 @@
1
- import { existsSync, readFileSync } from 'fs';
2
- import { dirname, join } from 'path';
3
- import { fileURLToPath } from 'url';
4
-
5
- const __filename = fileURLToPath(import.meta.url);
6
- const __dirname = dirname(__filename);
7
- const DISTILLED_DIR = join(__dirname, '..', '..', 'distilled');
8
-
9
- function getWorkflowContent(workflowFile) {
10
- const filePath = join(DISTILLED_DIR, 'workflows', workflowFile);
11
- if (existsSync(filePath)) return readFileSync(filePath, 'utf-8');
12
- return `<!-- Workflow file not found: ${workflowFile} -->\n`;
13
- }
14
-
15
- function getDelegateContent(delegateFile) {
16
- const filePath = join(DISTILLED_DIR, 'templates', 'delegates', delegateFile);
17
- if (existsSync(filePath)) return readFileSync(filePath, 'utf-8');
18
- return `<!-- Delegate file not found: ${delegateFile} -->\n`;
19
- }
20
-
1
+ import { existsSync, readFileSync } from 'fs';
2
+ import { dirname, join } from 'path';
3
+ import { fileURLToPath } from 'url';
4
+
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
+ ]);
18
+
19
+ function getWorkflowContent(workflowFile) {
20
+ const filePath = join(DISTILLED_DIR, 'workflows', workflowFile);
21
+ if (existsSync(filePath)) return readFileSync(filePath, 'utf-8');
22
+ return `<!-- Workflow file not found: ${workflowFile} -->\n`;
23
+ }
24
+
25
+ function getDelegateContent(delegateFile) {
26
+ const filePath = join(DISTILLED_DIR, 'templates', 'delegates', delegateFile);
27
+ if (existsSync(filePath)) return readFileSync(filePath, 'utf-8');
28
+ return `<!-- Delegate file not found: ${delegateFile} -->\n`;
29
+ }
30
+
21
31
  function renderSkillContent(workflow) {
22
32
  const workflowContent = getWorkflowContent(workflow.workflow);
23
33
  return `---
24
34
  name: ${workflow.name}
25
35
  description: ${workflow.description}
26
- context: fork
27
- agent: ${workflow.agent}
28
- ---
29
-
36
+ context: fork
37
+ agent: ${workflow.agent}
38
+ ---
39
+
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, consumeWorkspaceRootArg, resolveWorkspaceContext } from './lib/workspace-root.mjs';
39
50
 
40
- return `#!/usr/bin/env node
51
+ const COMMANDS = {
52
+ 'file-op': cmdFileOp,
53
+ 'lifecycle-preflight': cmdLifecyclePreflight,
54
+ 'phase-status': cmdPhaseStatus,
55
+ };
41
56
 
42
- import { spawnSync } from 'node:child_process';
57
+ function printHelp() {
58
+ console.log([
59
+ 'Usage: node .planning/bin/gsdd.mjs [--workspace-root <path>] <command> [args]',
60
+ '',
61
+ 'Local workflow helper commands:',
62
+ ' file-op <copy|delete|regex-sub>',
63
+ ' Run deterministic workspace-confined file operations',
64
+ ' Example: node .planning/bin/gsdd.mjs file-op delete .planning/.continue-here.bak --missing ok',
65
+ ' phase-status <N> <status> Update ROADMAP.md phase status ([ ] / [-] / [x])',
66
+ ' Example: node .planning/bin/gsdd.mjs phase-status 1 done',
67
+ ' lifecycle-preflight <surface> [phase]',
68
+ ' Inspect lifecycle gate results for a workflow surface',
69
+ ' Example: node .planning/bin/gsdd.mjs lifecycle-preflight verify 1 --expects-mutation phase-status',
70
+ '',
71
+ 'Advanced option:',
72
+ ' --workspace-root <path> Override workspace root discovery before or after the subcommand',
73
+ ].join('\\n'));
74
+ }
43
75
 
44
- const packageSpec = ${JSON.stringify(packageSpec)};
45
- const args = process.argv.slice(2);
46
- const env = { ...process.env };
76
+ function applyWorkspaceRootOverride(workspaceRootArg) {
77
+ if (!workspaceRootArg) {
78
+ bootstrapHelperWorkspace(import.meta.url);
79
+ return true;
80
+ }
47
81
 
48
- function forwardResult(result, fallbackMessage) {
49
- if (result.error) {
50
- console.error(fallbackMessage ?? result.error.message);
82
+ const context = resolveWorkspaceContext(['--workspace-root', workspaceRootArg]);
83
+ if (context.invalid) {
84
+ console.error(context.error);
51
85
  process.exitCode = 1;
52
- return;
86
+ return false;
53
87
  }
54
88
 
55
- if (typeof result.status === 'number') {
56
- process.exitCode = result.status;
57
- return;
89
+ process.env.GSDD_WORKSPACE_ROOT = context.workspaceRoot;
90
+ try {
91
+ process.chdir(context.workspaceRoot);
92
+ } catch {
93
+ // best-effort: command handlers also resolve from GSDD_WORKSPACE_ROOT
58
94
  }
95
+ return true;
96
+ }
59
97
 
60
- if (result.signal) {
98
+ async function main() {
99
+ const parsed = consumeWorkspaceRootArg(process.argv.slice(2));
100
+ if (parsed.invalid) {
101
+ console.error('Usage: --workspace-root <path>');
61
102
  process.exitCode = 1;
103
+ return;
62
104
  }
63
- }
64
105
 
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
- });
106
+ if (!applyWorkspaceRootOverride(parsed.workspaceRootArg)) return;
107
+
108
+ const [command, ...args] = parsed.args;
109
+
110
+ if (!command || command === 'help' || command === '--help') {
111
+ printHelp();
112
+ return;
80
113
  }
81
114
 
82
- return spawnSync('npm', ['exec', '--yes', \`--package=\${packageSpec}\`, '--', 'gsdd', ...args], {
83
- stdio: 'inherit',
84
- env,
85
- });
86
- }
115
+ const handler = COMMANDS[command];
116
+ if (!handler) {
117
+ printHelp();
118
+ process.exitCode = 1;
119
+ return;
120
+ }
87
121
 
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.\`);
122
+ await handler(...args);
97
123
  }
124
+
125
+ await main();
98
126
  `;
99
127
  }
100
128
 
@@ -113,11 +141,23 @@ node "%~dp0gsdd.mjs" %*
113
141
  `;
114
142
  }
115
143
 
116
- function buildPlanningCliHelperEntries({ packageName = 'gsdd-cli', packageVersion }) {
144
+ function renderPlanningCliPowerShellShim() {
145
+ return `#!/usr/bin/env pwsh
146
+ $scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
147
+ node (Join-Path $scriptDir 'gsdd.mjs') @args
148
+ exit $LASTEXITCODE
149
+ `;
150
+ }
151
+
152
+ function readHelperLibContent(fileName) {
153
+ return readFileSync(join(__dirname, fileName), 'utf-8');
154
+ }
155
+
156
+ function buildPlanningCliHelperEntries() {
117
157
  return [
118
158
  {
119
159
  relativePath: 'bin/gsdd.mjs',
120
- content: renderPlanningCliLauncher({ packageName, packageVersion }),
160
+ content: renderPlanningCliLauncher(),
121
161
  },
122
162
  {
123
163
  relativePath: 'bin/gsdd',
@@ -127,6 +167,14 @@ function buildPlanningCliHelperEntries({ packageName = 'gsdd-cli', packageVersio
127
167
  relativePath: 'bin/gsdd.cmd',
128
168
  content: renderPlanningCliCmdShim(),
129
169
  },
170
+ {
171
+ relativePath: 'bin/gsdd.ps1',
172
+ content: renderPlanningCliPowerShellShim(),
173
+ },
174
+ ...HELPER_LIB_FILES.map((fileName) => ({
175
+ relativePath: `bin/lib/${fileName}`,
176
+ content: readHelperLibContent(fileName),
177
+ })),
130
178
  ];
131
179
  }
132
180
 
@@ -135,62 +183,62 @@ function buildPortableSkillEntries(workflows) {
135
183
  relativePath: `.agents/skills/${workflow.name}/SKILL.md`,
136
184
  content: renderSkillContent(workflow),
137
185
  }));
138
- }
139
-
140
- function renderOpenCodeCommandContent(workflow) {
141
- const workflowContent = getWorkflowContent(workflow.workflow);
142
- return `---
143
- description: ${workflow.description}
144
- ---
145
-
146
- ${workflowContent}`;
147
- }
148
-
149
- function renderAgentsBoundedBlock() {
150
- const blockPath = join(DISTILLED_DIR, 'templates', 'agents.block.md');
151
- if (existsSync(blockPath)) return readFileSync(blockPath, 'utf-8').trim();
152
- return '## GSDD Governance (Generated)\n\n- Framework: GSDD\n- Planning: .planning/\n- Workflows: .agents/skills/gsdd-*/SKILL.md';
153
- }
154
-
155
- function renderAgentsFileContent() {
156
- const templatePath = join(DISTILLED_DIR, 'templates', 'agents.md');
157
- if (existsSync(templatePath)) {
158
- const template = readFileSync(templatePath, 'utf-8');
159
- return template.replace('{{GSDD_BLOCK}}', renderAgentsBoundedBlock()).trimEnd() + '\n';
160
- }
161
- const block = renderAgentsBoundedBlock();
162
- return `# AGENTS.md - GSDD Governance\n\n<!-- BEGIN GSDD -->\n${block}\n<!-- END GSDD -->\n`;
163
- }
164
-
165
- function escapeRegExp(value) {
166
- return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
167
- }
168
-
169
- function upsertBoundedBlock(existing, blockContent) {
170
- const begin = '<!-- BEGIN GSDD -->';
171
- const end = '<!-- END GSDD -->';
172
- const bounded = `${begin}\n${blockContent.trimEnd()}\n${end}`;
173
-
174
- const re = new RegExp(`${escapeRegExp(begin)}[\\s\\S]*?${escapeRegExp(end)}`, 'm');
175
- if (re.test(existing)) return existing.replace(re, bounded);
176
-
177
- const lines = existing.split(/\r?\n/);
178
- const h1Idx = lines.findIndex((line) => /^#\s+/.test(line));
179
- if (h1Idx !== -1) {
180
- const insertAt = h1Idx + 1;
181
- const out = [
182
- ...lines.slice(0, insertAt),
183
- '',
184
- bounded,
185
- '',
186
- ...lines.slice(insertAt),
187
- ];
188
- return out.join('\n').replace(/\n{3,}/g, '\n\n');
189
- }
190
-
191
- return `${bounded}\n\n${existing}`.replace(/\n{3,}/g, '\n\n');
192
- }
193
-
186
+ }
187
+
188
+ function renderOpenCodeCommandContent(workflow) {
189
+ const workflowContent = getWorkflowContent(workflow.workflow);
190
+ return `---
191
+ description: ${workflow.description}
192
+ ---
193
+
194
+ ${workflowContent}`;
195
+ }
196
+
197
+ function renderAgentsBoundedBlock() {
198
+ const blockPath = join(DISTILLED_DIR, 'templates', 'agents.block.md');
199
+ if (existsSync(blockPath)) return readFileSync(blockPath, 'utf-8').trim();
200
+ return '## GSDD Governance (Generated)\n\n- Framework: GSDD\n- Planning: .planning/\n- Workflows: .agents/skills/gsdd-*/SKILL.md';
201
+ }
202
+
203
+ function renderAgentsFileContent() {
204
+ const templatePath = join(DISTILLED_DIR, 'templates', 'agents.md');
205
+ if (existsSync(templatePath)) {
206
+ const template = readFileSync(templatePath, 'utf-8');
207
+ return template.replace('{{GSDD_BLOCK}}', renderAgentsBoundedBlock()).trimEnd() + '\n';
208
+ }
209
+ const block = renderAgentsBoundedBlock();
210
+ return `# AGENTS.md - GSDD Governance\n\n<!-- BEGIN GSDD -->\n${block}\n<!-- END GSDD -->\n`;
211
+ }
212
+
213
+ function escapeRegExp(value) {
214
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
215
+ }
216
+
217
+ function upsertBoundedBlock(existing, blockContent) {
218
+ const begin = '<!-- BEGIN GSDD -->';
219
+ const end = '<!-- END GSDD -->';
220
+ const bounded = `${begin}\n${blockContent.trimEnd()}\n${end}`;
221
+
222
+ const re = new RegExp(`${escapeRegExp(begin)}[\\s\\S]*?${escapeRegExp(end)}`, 'm');
223
+ if (re.test(existing)) return existing.replace(re, bounded);
224
+
225
+ const lines = existing.split(/\r?\n/);
226
+ const h1Idx = lines.findIndex((line) => /^#\s+/.test(line));
227
+ if (h1Idx !== -1) {
228
+ const insertAt = h1Idx + 1;
229
+ const out = [
230
+ ...lines.slice(0, insertAt),
231
+ '',
232
+ bounded,
233
+ '',
234
+ ...lines.slice(insertAt),
235
+ ];
236
+ return out.join('\n').replace(/\n{3,}/g, '\n\n');
237
+ }
238
+
239
+ return `${bounded}\n\n${existing}`.replace(/\n{3,}/g, '\n\n');
240
+ }
241
+
194
242
  export {
195
243
  buildPlanningCliHelperEntries,
196
244
  buildPortableSkillEntries,