@hegemonart/get-design-done 1.20.0 → 1.22.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 (69) hide show
  1. package/.claude-plugin/marketplace.json +9 -12
  2. package/.claude-plugin/plugin.json +8 -31
  3. package/CHANGELOG.md +200 -0
  4. package/README.md +48 -7
  5. package/bin/gdd-sdk +55 -0
  6. package/hooks/_hook-emit.js +81 -0
  7. package/hooks/gdd-bash-guard.js +8 -0
  8. package/hooks/gdd-decision-injector.js +2 -0
  9. package/hooks/gdd-protected-paths.js +8 -0
  10. package/hooks/gdd-trajectory-capture.js +64 -0
  11. package/hooks/hooks.json +9 -0
  12. package/package.json +19 -47
  13. package/reference/codex-tools.md +53 -0
  14. package/reference/gemini-tools.md +53 -0
  15. package/reference/registry.json +14 -0
  16. package/scripts/cli/gdd-events.mjs +283 -0
  17. package/scripts/e2e/run-headless.ts +514 -0
  18. package/scripts/lib/cli/commands/audit.ts +382 -0
  19. package/scripts/lib/cli/commands/init.ts +217 -0
  20. package/scripts/lib/cli/commands/query.ts +329 -0
  21. package/scripts/lib/cli/commands/run.ts +656 -0
  22. package/scripts/lib/cli/commands/stage.ts +468 -0
  23. package/scripts/lib/cli/index.ts +167 -0
  24. package/scripts/lib/cli/parse-args.ts +336 -0
  25. package/scripts/lib/connection-probe/index.cjs +263 -0
  26. package/scripts/lib/context-engine/index.ts +116 -0
  27. package/scripts/lib/context-engine/manifest.ts +69 -0
  28. package/scripts/lib/context-engine/truncate.ts +282 -0
  29. package/scripts/lib/context-engine/types.ts +59 -0
  30. package/scripts/lib/discuss-parallel-runner/aggregator.ts +448 -0
  31. package/scripts/lib/discuss-parallel-runner/discussants.ts +430 -0
  32. package/scripts/lib/discuss-parallel-runner/index.ts +223 -0
  33. package/scripts/lib/discuss-parallel-runner/types.ts +184 -0
  34. package/scripts/lib/event-chain.cjs +177 -0
  35. package/scripts/lib/event-stream/index.ts +31 -1
  36. package/scripts/lib/event-stream/reader.ts +139 -0
  37. package/scripts/lib/event-stream/types.ts +155 -1
  38. package/scripts/lib/event-stream/writer.ts +65 -8
  39. package/scripts/lib/explore-parallel-runner/index.ts +294 -0
  40. package/scripts/lib/explore-parallel-runner/mappers.ts +290 -0
  41. package/scripts/lib/explore-parallel-runner/synthesizer.ts +295 -0
  42. package/scripts/lib/explore-parallel-runner/types.ts +139 -0
  43. package/scripts/lib/harness/detect.ts +90 -0
  44. package/scripts/lib/harness/index.ts +64 -0
  45. package/scripts/lib/harness/tool-map.ts +142 -0
  46. package/scripts/lib/init-runner/index.ts +396 -0
  47. package/scripts/lib/init-runner/researchers.ts +245 -0
  48. package/scripts/lib/init-runner/scaffold.ts +224 -0
  49. package/scripts/lib/init-runner/synthesizer.ts +224 -0
  50. package/scripts/lib/init-runner/types.ts +143 -0
  51. package/scripts/lib/logger/index.ts +251 -0
  52. package/scripts/lib/logger/sinks.ts +269 -0
  53. package/scripts/lib/logger/types.ts +110 -0
  54. package/scripts/lib/pipeline-runner/human-gate.ts +134 -0
  55. package/scripts/lib/pipeline-runner/index.ts +527 -0
  56. package/scripts/lib/pipeline-runner/stage-handlers.ts +339 -0
  57. package/scripts/lib/pipeline-runner/state-machine.ts +144 -0
  58. package/scripts/lib/pipeline-runner/types.ts +183 -0
  59. package/scripts/lib/redact.cjs +122 -0
  60. package/scripts/lib/session-runner/errors.ts +406 -0
  61. package/scripts/lib/session-runner/index.ts +715 -0
  62. package/scripts/lib/session-runner/transcript.ts +189 -0
  63. package/scripts/lib/session-runner/types.ts +144 -0
  64. package/scripts/lib/tool-scoping/index.ts +219 -0
  65. package/scripts/lib/tool-scoping/parse-agent-tools.ts +207 -0
  66. package/scripts/lib/tool-scoping/stage-scopes.ts +139 -0
  67. package/scripts/lib/tool-scoping/types.ts +77 -0
  68. package/scripts/lib/trajectory/index.cjs +126 -0
  69. package/scripts/lib/transports/ws.cjs +179 -0
@@ -0,0 +1,468 @@
1
+ // scripts/lib/cli/commands/stage.ts — Plan 21-09 Task 3 (SDK-21).
2
+ //
3
+ // `gdd-sdk stage <name>` — run a single pipeline stage. Delegates to
4
+ // `pipeline-runner.run()` with `stages: [<name>]` for design-pipeline
5
+ // stages. `--parallel` routes explore/discuss through their dedicated
6
+ // parallel runners instead.
7
+ //
8
+ // Stage vocabulary (positional arg):
9
+ // brief | explore | plan | design | verify — 5-stage design pipeline
10
+ // discuss — parallel discussant leaf
11
+ //
12
+ // `--parallel` modifier:
13
+ // explore --parallel → exploreParallelRunner.run()
14
+ // discuss --parallel → discussParallelRunner.run()
15
+ // discuss (no --parallel) → error (discuss is leaf-only)
16
+ //
17
+ // Exit codes: same as `run` (0 completed, 1 halted, 2 awaiting-gate, 3 arg).
18
+
19
+ import { readFileSync } from 'node:fs';
20
+ import { resolve as resolvePath } from 'node:path';
21
+
22
+ import {
23
+ run as defaultPipelineRun,
24
+ type PipelineConfig,
25
+ type PipelineResult,
26
+ type Stage,
27
+ } from '../../pipeline-runner/index.ts';
28
+ import {
29
+ run as defaultExploreParallelRun,
30
+ type ExploreRunnerResult,
31
+ } from '../../explore-parallel-runner/index.ts';
32
+ import {
33
+ run as defaultDiscussParallelRun,
34
+ type DiscussRunnerResult,
35
+ } from '../../discuss-parallel-runner/index.ts';
36
+ import { getLogger } from '../../logger/index.ts';
37
+
38
+ import {
39
+ coerceFlags,
40
+ COMMON_FLAGS,
41
+ type FlagSpec,
42
+ type ParsedArgs,
43
+ } from '../parse-args.ts';
44
+
45
+ // ---------------------------------------------------------------------------
46
+ // Flag spec + help.
47
+ // ---------------------------------------------------------------------------
48
+
49
+ const STAGE_FLAGS: readonly FlagSpec[] = [
50
+ ...COMMON_FLAGS,
51
+ { name: 'parallel', type: 'boolean', default: false },
52
+ { name: 'prompt-file', type: 'string' },
53
+ { name: 'synthesizer-prompt-file', type: 'string' },
54
+ { name: 'aggregator-prompt-file', type: 'string' },
55
+ ];
56
+
57
+ const USAGE = `gdd-sdk stage <name> [flags]
58
+
59
+ Run a single stage.
60
+
61
+ Names:
62
+ brief | explore | plan | design | verify — design pipeline stages
63
+ discuss — discussion leaf (requires --parallel)
64
+
65
+ Flags:
66
+ --parallel Route explore/discuss to their parallel runners
67
+ --prompt-file <path> Path to prompt body for the stage
68
+ --synthesizer-prompt-file (explore --parallel) synthesizer prompt
69
+ --aggregator-prompt-file (discuss --parallel) aggregator prompt
70
+ --budget-usd <n> Budget cap (default 2.0)
71
+ --max-turns <n> Turn cap (default 40)
72
+ --concurrency <n> Parallel runner concurrency (default 4)
73
+ --cwd <dir> Working dir (default: current)
74
+ --json Emit JSON to stdout
75
+ --text Human-readable (default)
76
+
77
+ Exit codes:
78
+ 0 completed
79
+ 1 halted
80
+ 2 awaiting-gate
81
+ 3 arg/config error
82
+ `;
83
+
84
+ // ---------------------------------------------------------------------------
85
+ // Public deps.
86
+ // ---------------------------------------------------------------------------
87
+
88
+ export type PipelineRunFn = typeof defaultPipelineRun;
89
+ export type ExploreParallelRunFn = typeof defaultExploreParallelRun;
90
+ export type DiscussParallelRunFn = typeof defaultDiscussParallelRun;
91
+
92
+ export interface StageCommandDeps {
93
+ readonly pipelineRun?: PipelineRunFn;
94
+ readonly exploreParallelRun?: ExploreParallelRunFn;
95
+ readonly discussParallelRun?: DiscussParallelRunFn;
96
+ readonly stdout?: NodeJS.WritableStream;
97
+ readonly stderr?: NodeJS.WritableStream;
98
+ }
99
+
100
+ // ---------------------------------------------------------------------------
101
+ // Entry point.
102
+ // ---------------------------------------------------------------------------
103
+
104
+ const VALID_STAGE_NAMES = new Set<string>([
105
+ 'brief',
106
+ 'explore',
107
+ 'plan',
108
+ 'design',
109
+ 'verify',
110
+ 'discuss',
111
+ ]);
112
+
113
+ export async function stageCommand(
114
+ args: ParsedArgs,
115
+ deps: StageCommandDeps = {},
116
+ ): Promise<number> {
117
+ const stdout = deps.stdout ?? process.stdout;
118
+ const stderr = deps.stderr ?? process.stderr;
119
+
120
+ if (args.flags['help'] === true || args.flags['h'] === true) {
121
+ stdout.write(USAGE);
122
+ return 0;
123
+ }
124
+
125
+ // First positional after the subcommand is the stage name.
126
+ const stageName: string | undefined = args.positionals[0];
127
+ if (stageName === undefined || stageName.length === 0) {
128
+ stderr.write('gdd-sdk stage: missing stage name\n');
129
+ stderr.write(USAGE);
130
+ return 3;
131
+ }
132
+
133
+ if (!VALID_STAGE_NAMES.has(stageName)) {
134
+ stderr.write(
135
+ `gdd-sdk stage: "${stageName}" is not one of brief|explore|plan|design|verify|discuss\n`,
136
+ );
137
+ return 3;
138
+ }
139
+
140
+ let flags: Record<string, unknown>;
141
+ try {
142
+ flags = coerceFlags(args, STAGE_FLAGS);
143
+ } catch (err) {
144
+ stderr.write(`gdd-sdk stage: ${errMessage(err)}\n`);
145
+ return 3;
146
+ }
147
+
148
+ const parallel: boolean = flags['parallel'] === true;
149
+ const cwd: string =
150
+ typeof flags['cwd'] === 'string' ? (flags['cwd'] as string) : process.cwd();
151
+
152
+ // `discuss` is leaf-only: always requires --parallel.
153
+ if (stageName === 'discuss') {
154
+ if (!parallel) {
155
+ stderr.write('gdd-sdk stage discuss: requires --parallel\n');
156
+ return 3;
157
+ }
158
+ return await runDiscussParallel(flags, cwd, stdout, stderr, deps);
159
+ }
160
+
161
+ // explore --parallel route.
162
+ if (stageName === 'explore' && parallel) {
163
+ return await runExploreParallel(flags, cwd, stdout, stderr, deps);
164
+ }
165
+
166
+ // Regular design-pipeline stage via pipeline-runner.
167
+ return await runPipelineStage(
168
+ stageName as Stage,
169
+ flags,
170
+ cwd,
171
+ stdout,
172
+ stderr,
173
+ deps,
174
+ );
175
+ }
176
+
177
+ // ---------------------------------------------------------------------------
178
+ // Path 1: single design-pipeline stage via pipeline-runner.
179
+ // ---------------------------------------------------------------------------
180
+
181
+ async function runPipelineStage(
182
+ stage: Stage,
183
+ flags: Record<string, unknown>,
184
+ cwd: string,
185
+ stdout: NodeJS.WritableStream,
186
+ stderr: NodeJS.WritableStream,
187
+ deps: StageCommandDeps,
188
+ ): Promise<number> {
189
+ // Prompt resolution.
190
+ let promptBody: string;
191
+ try {
192
+ promptBody = loadSingleStagePrompt(stage, flags, cwd);
193
+ } catch (err) {
194
+ stderr.write(`gdd-sdk stage: ${errMessage(err)}\n`);
195
+ return 3;
196
+ }
197
+
198
+ const budget = {
199
+ usdLimit:
200
+ typeof flags['budget-usd'] === 'number' ? (flags['budget-usd'] as number) : 2.0,
201
+ inputTokensLimit:
202
+ typeof flags['budget-input-tokens'] === 'number'
203
+ ? (flags['budget-input-tokens'] as number)
204
+ : 200_000,
205
+ outputTokensLimit:
206
+ typeof flags['budget-output-tokens'] === 'number'
207
+ ? (flags['budget-output-tokens'] as number)
208
+ : 50_000,
209
+ perStage: true as const,
210
+ };
211
+
212
+ const maxTurnsPerStage: number =
213
+ typeof flags['max-turns'] === 'number' ? (flags['max-turns'] as number) : 40;
214
+
215
+ const prompts: Record<Stage, string> = {
216
+ brief: '',
217
+ explore: '',
218
+ plan: '',
219
+ design: '',
220
+ verify: '',
221
+ };
222
+ prompts[stage] = promptBody;
223
+
224
+ const config: PipelineConfig = {
225
+ stages: [stage],
226
+ prompts,
227
+ budget,
228
+ maxTurnsPerStage,
229
+ stageRetries: 1,
230
+ cwd,
231
+ };
232
+
233
+ const pipelineRun: PipelineRunFn = deps.pipelineRun ?? defaultPipelineRun;
234
+ let result: PipelineResult;
235
+ try {
236
+ result = await pipelineRun(config);
237
+ } catch (err) {
238
+ try {
239
+ getLogger().error('cli.stage.unexpected_error', {
240
+ stage,
241
+ error: err instanceof Error ? err.message : String(err),
242
+ });
243
+ } catch {
244
+ // swallow
245
+ }
246
+ stderr.write(`gdd-sdk stage: unexpected error: ${errMessage(err)}\n`);
247
+ return 3;
248
+ }
249
+
250
+ if (flags['json'] === true) {
251
+ stdout.write(JSON.stringify(result, null, 2) + '\n');
252
+ } else {
253
+ stdout.write(`stage ${stage}: ${result.status}\n`);
254
+ for (const o of result.outcomes) {
255
+ stdout.write(` ${o.stage}: ${o.status} (retries=${o.retries})\n`);
256
+ }
257
+ }
258
+
259
+ if (result.status === 'completed' || result.status === 'stopped-after') return 0;
260
+ if (result.status === 'awaiting-gate') return 2;
261
+ return 1;
262
+ }
263
+
264
+ // ---------------------------------------------------------------------------
265
+ // Path 2: explore --parallel via explore-parallel-runner.
266
+ // ---------------------------------------------------------------------------
267
+
268
+ async function runExploreParallel(
269
+ flags: Record<string, unknown>,
270
+ cwd: string,
271
+ stdout: NodeJS.WritableStream,
272
+ stderr: NodeJS.WritableStream,
273
+ deps: StageCommandDeps,
274
+ ): Promise<number> {
275
+ let synthesizerPrompt: string;
276
+ try {
277
+ synthesizerPrompt = loadExploreSynthesizerPrompt(flags, cwd);
278
+ } catch (err) {
279
+ stderr.write(`gdd-sdk stage explore --parallel: ${errMessage(err)}\n`);
280
+ return 3;
281
+ }
282
+
283
+ const budget = {
284
+ usdLimit:
285
+ typeof flags['budget-usd'] === 'number' ? (flags['budget-usd'] as number) : 2.0,
286
+ inputTokensLimit:
287
+ typeof flags['budget-input-tokens'] === 'number'
288
+ ? (flags['budget-input-tokens'] as number)
289
+ : 200_000,
290
+ outputTokensLimit:
291
+ typeof flags['budget-output-tokens'] === 'number'
292
+ ? (flags['budget-output-tokens'] as number)
293
+ : 50_000,
294
+ };
295
+ const maxTurnsPerMapper: number =
296
+ typeof flags['max-turns'] === 'number' ? (flags['max-turns'] as number) : 40;
297
+ const concurrency: number =
298
+ typeof flags['concurrency'] === 'number' ? (flags['concurrency'] as number) : 4;
299
+
300
+ const exploreRun: ExploreParallelRunFn =
301
+ deps.exploreParallelRun ?? defaultExploreParallelRun;
302
+
303
+ let result: ExploreRunnerResult;
304
+ try {
305
+ result = await exploreRun({
306
+ budget,
307
+ maxTurnsPerMapper,
308
+ concurrency,
309
+ synthesizerPrompt,
310
+ synthesizerBudget: budget,
311
+ synthesizerMaxTurns: maxTurnsPerMapper,
312
+ cwd,
313
+ });
314
+ } catch (err) {
315
+ stderr.write(
316
+ `gdd-sdk stage explore --parallel: unexpected error: ${errMessage(err)}\n`,
317
+ );
318
+ return 3;
319
+ }
320
+
321
+ if (flags['json'] === true) {
322
+ stdout.write(JSON.stringify(result, null, 2) + '\n');
323
+ } else {
324
+ stdout.write(
325
+ `explore --parallel: synth=${result.synthesizer.status}, ` +
326
+ `parallel=${result.parallel_count}, serial=${result.serial_count}\n`,
327
+ );
328
+ for (const m of result.mappers) {
329
+ stdout.write(` mapper ${m.name}: ${m.status}\n`);
330
+ }
331
+ }
332
+
333
+ // Treat non-completed synthesizer as a halt.
334
+ if (result.synthesizer.status === 'completed') return 0;
335
+ return 1;
336
+ }
337
+
338
+ // ---------------------------------------------------------------------------
339
+ // Path 3: discuss --parallel via discuss-parallel-runner.
340
+ // ---------------------------------------------------------------------------
341
+
342
+ async function runDiscussParallel(
343
+ flags: Record<string, unknown>,
344
+ cwd: string,
345
+ stdout: NodeJS.WritableStream,
346
+ stderr: NodeJS.WritableStream,
347
+ deps: StageCommandDeps,
348
+ ): Promise<number> {
349
+ const aggregatorPrompt = loadOptionalAggregatorPrompt(flags, cwd);
350
+
351
+ const budget = {
352
+ usdLimit:
353
+ typeof flags['budget-usd'] === 'number' ? (flags['budget-usd'] as number) : 2.0,
354
+ inputTokensLimit:
355
+ typeof flags['budget-input-tokens'] === 'number'
356
+ ? (flags['budget-input-tokens'] as number)
357
+ : 200_000,
358
+ outputTokensLimit:
359
+ typeof flags['budget-output-tokens'] === 'number'
360
+ ? (flags['budget-output-tokens'] as number)
361
+ : 50_000,
362
+ };
363
+ const maxTurnsPerDiscussant: number =
364
+ typeof flags['max-turns'] === 'number' ? (flags['max-turns'] as number) : 40;
365
+ const concurrency: number =
366
+ typeof flags['concurrency'] === 'number' ? (flags['concurrency'] as number) : 4;
367
+
368
+ const discussRun: DiscussParallelRunFn =
369
+ deps.discussParallelRun ?? defaultDiscussParallelRun;
370
+
371
+ let result: DiscussRunnerResult;
372
+ try {
373
+ result = await discussRun({
374
+ budget,
375
+ maxTurnsPerDiscussant,
376
+ aggregatorBudget: budget,
377
+ aggregatorMaxTurns: maxTurnsPerDiscussant,
378
+ concurrency,
379
+ cwd,
380
+ ...(aggregatorPrompt !== undefined ? { aggregatorPrompt } : {}),
381
+ });
382
+ } catch (err) {
383
+ // discuss-parallel-runner throws OperationFailedError when all
384
+ // discussants fail — surface as exit 1.
385
+ stderr.write(`gdd-sdk stage discuss --parallel: ${errMessage(err)}\n`);
386
+ return 1;
387
+ }
388
+
389
+ if (flags['json'] === true) {
390
+ stdout.write(JSON.stringify(result, null, 2) + '\n');
391
+ } else {
392
+ stdout.write(
393
+ `discuss --parallel: contributions=${result.contributions.length}, ` +
394
+ `themes=${result.aggregated.themes.length}, ` +
395
+ `questions=${result.aggregated.questions.length}\n`,
396
+ );
397
+ }
398
+ return 0;
399
+ }
400
+
401
+ // ---------------------------------------------------------------------------
402
+ // Helpers.
403
+ // ---------------------------------------------------------------------------
404
+
405
+ function loadSingleStagePrompt(
406
+ stage: Stage,
407
+ flags: Record<string, unknown>,
408
+ cwd: string,
409
+ ): string {
410
+ const file = flags['prompt-file'];
411
+ if (typeof file === 'string' && file.length > 0) {
412
+ const abs = resolvePath(cwd, file);
413
+ try {
414
+ return readFileSync(abs, 'utf8');
415
+ } catch (err) {
416
+ throw new Error(`cannot read prompt file "${file}": ${errMessage(err)}`);
417
+ }
418
+ }
419
+ // Convention: `.design/prompts/<stage>.md`.
420
+ const conv = resolvePath(cwd, '.design/prompts', `${stage}.md`);
421
+ try {
422
+ return readFileSync(conv, 'utf8');
423
+ } catch {
424
+ // Fall back to a minimal default — tests may supply nothing.
425
+ return `Run the ${stage} stage. Follow SKILL.md for the stage.`;
426
+ }
427
+ }
428
+
429
+ function loadExploreSynthesizerPrompt(
430
+ flags: Record<string, unknown>,
431
+ cwd: string,
432
+ ): string {
433
+ const file = flags['synthesizer-prompt-file'];
434
+ if (typeof file === 'string' && file.length > 0) {
435
+ const abs = resolvePath(cwd, file);
436
+ try {
437
+ return readFileSync(abs, 'utf8');
438
+ } catch (err) {
439
+ throw new Error(
440
+ `cannot read synthesizer prompt file "${file}": ${errMessage(err)}`,
441
+ );
442
+ }
443
+ }
444
+ // Reasonable default; callers can override.
445
+ return (
446
+ 'Synthesize .design/DESIGN-PATTERNS.md from the streaming mapper ' +
447
+ 'outputs in .design/map/.'
448
+ );
449
+ }
450
+
451
+ function loadOptionalAggregatorPrompt(
452
+ flags: Record<string, unknown>,
453
+ cwd: string,
454
+ ): string | undefined {
455
+ const file = flags['aggregator-prompt-file'];
456
+ if (typeof file !== 'string' || file.length === 0) return undefined;
457
+ const abs = resolvePath(cwd, file);
458
+ try {
459
+ return readFileSync(abs, 'utf8');
460
+ } catch {
461
+ return undefined;
462
+ }
463
+ }
464
+
465
+ function errMessage(err: unknown): string {
466
+ if (err instanceof Error) return err.message;
467
+ return String(err);
468
+ }
@@ -0,0 +1,167 @@
1
+ // scripts/lib/cli/index.ts — Plan 21-09 Task 7 (SDK-21).
2
+ //
3
+ // Main dispatcher for the `gdd-sdk` CLI. Invoked by `bin/gdd-sdk` (the
4
+ // CJS trampoline) with `--experimental-strip-types` so TS runs without
5
+ // a build step.
6
+ //
7
+ // Responsibilities:
8
+ // * Parse argv via parseArgs().
9
+ // * Route by first positional to the matching subcommand module.
10
+ // * Print top-level USAGE on bare `gdd-sdk` / `-h` / `--help`.
11
+ // * Exit with the subcommand's return code. Unknown subcommands
12
+ // exit 3 and print USAGE to stderr.
13
+ //
14
+ // The bottom of this file has the `main()` bootstrap that the trampoline
15
+ // invokes. It catches any accidental throw (subcommands are contracted
16
+ // not to throw) and translates to exit code 3.
17
+
18
+ import { parseArgs, type ParsedArgs } from './parse-args.ts';
19
+ import { runCommand } from './commands/run.ts';
20
+ import { stageCommand } from './commands/stage.ts';
21
+ import { queryCommand } from './commands/query.ts';
22
+ import { auditCommand } from './commands/audit.ts';
23
+ import { initCommand } from './commands/init.ts';
24
+
25
+ // ---------------------------------------------------------------------------
26
+ // Top-level USAGE.
27
+ // ---------------------------------------------------------------------------
28
+
29
+ export const USAGE = `gdd-sdk <command> [flags]
30
+
31
+ Commands:
32
+ run Run the full design pipeline headlessly.
33
+ stage <name> Run a single stage (brief|explore|plan|design|verify|discuss).
34
+ query <op> Typed STATE.md read operations.
35
+ audit Probe connections + dry-run verify.
36
+ init Bootstrap a new project.
37
+
38
+ Use 'gdd-sdk <command> -h' for command-specific flags.
39
+
40
+ Exit codes (general):
41
+ 0 success
42
+ 1 halted / regression / already-initialized
43
+ 2 awaiting-gate / partial (init)
44
+ 3 argument / config error
45
+ `;
46
+
47
+ // ---------------------------------------------------------------------------
48
+ // Deps.
49
+ // ---------------------------------------------------------------------------
50
+
51
+ export interface DispatcherDeps {
52
+ readonly stdout?: NodeJS.WritableStream;
53
+ readonly stderr?: NodeJS.WritableStream;
54
+ readonly commands?: {
55
+ readonly run?: typeof runCommand;
56
+ readonly stage?: typeof stageCommand;
57
+ readonly query?: typeof queryCommand;
58
+ readonly audit?: typeof auditCommand;
59
+ readonly init?: typeof initCommand;
60
+ };
61
+ }
62
+
63
+ // ---------------------------------------------------------------------------
64
+ // Dispatcher.
65
+ // ---------------------------------------------------------------------------
66
+
67
+ /**
68
+ * Dispatch a parsed ParsedArgs to the appropriate subcommand. Exported
69
+ * for tests (they can construct a ParsedArgs manually and assert the
70
+ * exit code + captured stdout/stderr).
71
+ */
72
+ export async function dispatch(
73
+ parsed: ParsedArgs,
74
+ deps: DispatcherDeps = {},
75
+ ): Promise<number> {
76
+ const stdout = deps.stdout ?? process.stdout;
77
+ const stderr = deps.stderr ?? process.stderr;
78
+ const commands = {
79
+ run: deps.commands?.run ?? runCommand,
80
+ stage: deps.commands?.stage ?? stageCommand,
81
+ query: deps.commands?.query ?? queryCommand,
82
+ audit: deps.commands?.audit ?? auditCommand,
83
+ init: deps.commands?.init ?? initCommand,
84
+ };
85
+
86
+ // Bare invocation or top-level help → USAGE.
87
+ if (parsed.subcommand === null) {
88
+ stdout.write(USAGE);
89
+ return 0;
90
+ }
91
+ if (
92
+ (parsed.flags['help'] === true || parsed.flags['h'] === true) &&
93
+ // Top-level --help (no subcommand recognized yet; --help before the
94
+ // first positional lands here). Subcommands also honor --help
95
+ // themselves, so this branch only fires for `gdd-sdk --help`.
96
+ parsed.positionals.length === 0 &&
97
+ !KNOWN_SUBCOMMANDS.has(parsed.subcommand)
98
+ ) {
99
+ stdout.write(USAGE);
100
+ return 0;
101
+ }
102
+
103
+ switch (parsed.subcommand) {
104
+ case 'run':
105
+ return await commands.run(parsed, { stdout, stderr });
106
+ case 'stage':
107
+ return await commands.stage(parsed, { stdout, stderr });
108
+ case 'query':
109
+ return await commands.query(parsed, { stdout, stderr });
110
+ case 'audit':
111
+ return await commands.audit(parsed, { stdout, stderr });
112
+ case 'init':
113
+ return await commands.init(parsed, { stdout, stderr });
114
+ default:
115
+ stderr.write(
116
+ `gdd-sdk: unknown subcommand "${parsed.subcommand}"\n${USAGE}`,
117
+ );
118
+ return 3;
119
+ }
120
+ }
121
+
122
+ const KNOWN_SUBCOMMANDS: ReadonlySet<string> = new Set([
123
+ 'run',
124
+ 'stage',
125
+ 'query',
126
+ 'audit',
127
+ 'init',
128
+ ]);
129
+
130
+ // ---------------------------------------------------------------------------
131
+ // Bootstrap entry point — called by bin/gdd-sdk trampoline.
132
+ // ---------------------------------------------------------------------------
133
+
134
+ /**
135
+ * Top-level main. Parses process.argv.slice(2), dispatches, prints
136
+ * USAGE + exit 3 on any uncaught error. Exported so tests can invoke
137
+ * it directly (bypassing the trampoline).
138
+ */
139
+ export async function main(
140
+ argv: readonly string[] = process.argv.slice(2),
141
+ deps: DispatcherDeps = {},
142
+ ): Promise<number> {
143
+ const parsed = parseArgs(argv);
144
+ return await dispatch(parsed, deps);
145
+ }
146
+
147
+ // ---------------------------------------------------------------------------
148
+ // When executed directly by the trampoline, invoke main() + exit.
149
+ // ---------------------------------------------------------------------------
150
+
151
+ // Only self-invoke when this module IS the entry point. We detect that
152
+ // by checking process.argv[1] (trampoline passes this file's path).
153
+ //
154
+ // `import.meta.url` is the POSIX idiom but this TS runs under --experimental-strip-types
155
+ // as Node16 modules — we use process.argv to stay ESM/CJS-agnostic.
156
+
157
+ const entryPath: string = (process.argv[1] ?? '').replace(/\\/g, '/');
158
+ if (entryPath.endsWith('/scripts/lib/cli/index.ts')) {
159
+ main().then(
160
+ (code) => process.exit(code),
161
+ (err) => {
162
+ // eslint-disable-next-line no-console
163
+ console.error('gdd-sdk: unexpected error:', err);
164
+ process.exit(3);
165
+ },
166
+ );
167
+ }