gsd-opencode 1.22.0 → 1.30.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 (157) hide show
  1. package/agents/gsd-advisor-researcher.md +112 -0
  2. package/agents/gsd-assumptions-analyzer.md +110 -0
  3. package/agents/gsd-codebase-mapper.md +1 -2
  4. package/agents/gsd-debugger.md +119 -2
  5. package/agents/gsd-executor.md +25 -4
  6. package/agents/gsd-integration-checker.md +1 -2
  7. package/agents/gsd-nyquist-auditor.md +1 -2
  8. package/agents/gsd-phase-researcher.md +151 -5
  9. package/agents/gsd-plan-checker.md +71 -5
  10. package/agents/gsd-planner.md +50 -4
  11. package/agents/gsd-project-researcher.md +29 -3
  12. package/agents/gsd-research-synthesizer.md +1 -2
  13. package/agents/gsd-roadmapper.md +30 -2
  14. package/agents/gsd-ui-auditor.md +445 -0
  15. package/agents/gsd-ui-checker.md +305 -0
  16. package/agents/gsd-ui-researcher.md +368 -0
  17. package/agents/gsd-user-profiler.md +173 -0
  18. package/agents/gsd-verifier.md +124 -4
  19. package/commands/gsd/gsd-add-backlog.md +76 -0
  20. package/commands/gsd/gsd-audit-uat.md +24 -0
  21. package/commands/gsd/gsd-autonomous.md +41 -0
  22. package/commands/gsd/gsd-debug.md +5 -0
  23. package/commands/gsd/gsd-discuss-phase.md +10 -36
  24. package/commands/gsd/gsd-do.md +30 -0
  25. package/commands/gsd/gsd-execute-phase.md +20 -2
  26. package/commands/gsd/gsd-fast.md +30 -0
  27. package/commands/gsd/gsd-forensics.md +56 -0
  28. package/commands/gsd/gsd-list-workspaces.md +19 -0
  29. package/commands/gsd/gsd-manager.md +39 -0
  30. package/commands/gsd/gsd-milestone-summary.md +51 -0
  31. package/commands/gsd/gsd-new-workspace.md +44 -0
  32. package/commands/gsd/gsd-next.md +24 -0
  33. package/commands/gsd/gsd-note.md +34 -0
  34. package/commands/gsd/gsd-plan-phase.md +3 -1
  35. package/commands/gsd/gsd-plant-seed.md +28 -0
  36. package/commands/gsd/gsd-pr-branch.md +25 -0
  37. package/commands/gsd/gsd-profile-user.md +46 -0
  38. package/commands/gsd/gsd-quick.md +4 -2
  39. package/commands/gsd/gsd-reapply-patches.md +10 -6
  40. package/commands/gsd/gsd-remove-workspace.md +26 -0
  41. package/commands/gsd/gsd-research-phase.md +5 -0
  42. package/commands/gsd/gsd-resume-work.md +1 -1
  43. package/commands/gsd/gsd-review-backlog.md +61 -0
  44. package/commands/gsd/gsd-review.md +37 -0
  45. package/commands/gsd/gsd-session-report.md +19 -0
  46. package/commands/gsd/gsd-set-profile.md +24 -23
  47. package/commands/gsd/gsd-ship.md +23 -0
  48. package/commands/gsd/gsd-stats.md +18 -0
  49. package/commands/gsd/gsd-thread.md +127 -0
  50. package/commands/gsd/gsd-ui-phase.md +34 -0
  51. package/commands/gsd/gsd-ui-review.md +32 -0
  52. package/commands/gsd/gsd-workstreams.md +66 -0
  53. package/get-shit-done/bin/gsd-tools.cjs +410 -84
  54. package/get-shit-done/bin/lib/commands.cjs +429 -18
  55. package/get-shit-done/bin/lib/config.cjs +318 -45
  56. package/get-shit-done/bin/lib/core.cjs +822 -84
  57. package/get-shit-done/bin/lib/frontmatter.cjs +78 -41
  58. package/get-shit-done/bin/lib/init.cjs +836 -104
  59. package/get-shit-done/bin/lib/milestone.cjs +44 -33
  60. package/get-shit-done/bin/lib/model-profiles.cjs +68 -0
  61. package/get-shit-done/bin/lib/phase.cjs +293 -306
  62. package/get-shit-done/bin/lib/profile-output.cjs +952 -0
  63. package/get-shit-done/bin/lib/profile-pipeline.cjs +539 -0
  64. package/get-shit-done/bin/lib/roadmap.cjs +55 -24
  65. package/get-shit-done/bin/lib/security.cjs +382 -0
  66. package/get-shit-done/bin/lib/state.cjs +363 -53
  67. package/get-shit-done/bin/lib/template.cjs +2 -2
  68. package/get-shit-done/bin/lib/uat.cjs +282 -0
  69. package/get-shit-done/bin/lib/verify.cjs +104 -36
  70. package/get-shit-done/bin/lib/workstream.cjs +491 -0
  71. package/get-shit-done/references/checkpoints.md +12 -10
  72. package/get-shit-done/references/decimal-phase-calculation.md +2 -3
  73. package/get-shit-done/references/git-integration.md +47 -0
  74. package/get-shit-done/references/model-profile-resolution.md +2 -0
  75. package/get-shit-done/references/model-profiles.md +62 -16
  76. package/get-shit-done/references/phase-argument-parsing.md +2 -2
  77. package/get-shit-done/references/planning-config.md +3 -1
  78. package/get-shit-done/references/user-profiling.md +681 -0
  79. package/get-shit-done/references/workstream-flag.md +58 -0
  80. package/get-shit-done/templates/UAT.md +21 -3
  81. package/get-shit-done/templates/UI-SPEC.md +100 -0
  82. package/get-shit-done/templates/claude-md.md +122 -0
  83. package/get-shit-done/templates/config.json +10 -3
  84. package/get-shit-done/templates/context.md +61 -6
  85. package/get-shit-done/templates/dev-preferences.md +21 -0
  86. package/get-shit-done/templates/discussion-log.md +63 -0
  87. package/get-shit-done/templates/phase-prompt.md +46 -5
  88. package/get-shit-done/templates/project.md +2 -0
  89. package/get-shit-done/templates/state.md +2 -2
  90. package/get-shit-done/templates/user-profile.md +146 -0
  91. package/get-shit-done/workflows/add-phase.md +2 -2
  92. package/get-shit-done/workflows/add-tests.md +4 -4
  93. package/get-shit-done/workflows/add-todo.md +3 -3
  94. package/get-shit-done/workflows/audit-milestone.md +13 -5
  95. package/get-shit-done/workflows/audit-uat.md +109 -0
  96. package/get-shit-done/workflows/autonomous.md +891 -0
  97. package/get-shit-done/workflows/check-todos.md +2 -2
  98. package/get-shit-done/workflows/cleanup.md +4 -4
  99. package/get-shit-done/workflows/complete-milestone.md +9 -6
  100. package/get-shit-done/workflows/diagnose-issues.md +15 -3
  101. package/get-shit-done/workflows/discovery-phase.md +2 -2
  102. package/get-shit-done/workflows/discuss-phase-assumptions.md +653 -0
  103. package/get-shit-done/workflows/discuss-phase.md +411 -38
  104. package/get-shit-done/workflows/do.md +104 -0
  105. package/get-shit-done/workflows/execute-phase.md +405 -18
  106. package/get-shit-done/workflows/execute-plan.md +77 -12
  107. package/get-shit-done/workflows/fast.md +105 -0
  108. package/get-shit-done/workflows/forensics.md +265 -0
  109. package/get-shit-done/workflows/health.md +28 -6
  110. package/get-shit-done/workflows/help.md +124 -7
  111. package/get-shit-done/workflows/insert-phase.md +2 -2
  112. package/get-shit-done/workflows/list-phase-assumptions.md +2 -2
  113. package/get-shit-done/workflows/list-workspaces.md +56 -0
  114. package/get-shit-done/workflows/manager.md +362 -0
  115. package/get-shit-done/workflows/map-codebase.md +74 -13
  116. package/get-shit-done/workflows/milestone-summary.md +223 -0
  117. package/get-shit-done/workflows/new-milestone.md +120 -18
  118. package/get-shit-done/workflows/new-project.md +178 -39
  119. package/get-shit-done/workflows/new-workspace.md +237 -0
  120. package/get-shit-done/workflows/next.md +97 -0
  121. package/get-shit-done/workflows/node-repair.md +92 -0
  122. package/get-shit-done/workflows/note.md +156 -0
  123. package/get-shit-done/workflows/pause-work.md +62 -8
  124. package/get-shit-done/workflows/plan-milestone-gaps.md +4 -5
  125. package/get-shit-done/workflows/plan-phase.md +332 -33
  126. package/get-shit-done/workflows/plant-seed.md +169 -0
  127. package/get-shit-done/workflows/pr-branch.md +129 -0
  128. package/get-shit-done/workflows/profile-user.md +450 -0
  129. package/get-shit-done/workflows/progress.md +145 -20
  130. package/get-shit-done/workflows/quick.md +205 -49
  131. package/get-shit-done/workflows/remove-phase.md +2 -2
  132. package/get-shit-done/workflows/remove-workspace.md +90 -0
  133. package/get-shit-done/workflows/research-phase.md +11 -3
  134. package/get-shit-done/workflows/resume-project.md +35 -16
  135. package/get-shit-done/workflows/review.md +228 -0
  136. package/get-shit-done/workflows/session-report.md +146 -0
  137. package/get-shit-done/workflows/set-profile.md +2 -2
  138. package/get-shit-done/workflows/settings.md +80 -11
  139. package/get-shit-done/workflows/ship.md +228 -0
  140. package/get-shit-done/workflows/stats.md +60 -0
  141. package/get-shit-done/workflows/transition.md +147 -20
  142. package/get-shit-done/workflows/ui-phase.md +302 -0
  143. package/get-shit-done/workflows/ui-review.md +165 -0
  144. package/get-shit-done/workflows/update.md +108 -25
  145. package/get-shit-done/workflows/validate-phase.md +15 -8
  146. package/get-shit-done/workflows/verify-phase.md +16 -5
  147. package/get-shit-done/workflows/verify-work.md +72 -18
  148. package/package.json +1 -1
  149. package/skills/gsd-audit-milestone/SKILL.md +29 -0
  150. package/skills/gsd-cleanup/SKILL.md +19 -0
  151. package/skills/gsd-complete-milestone/SKILL.md +131 -0
  152. package/skills/gsd-discuss-phase/SKILL.md +54 -0
  153. package/skills/gsd-execute-phase/SKILL.md +49 -0
  154. package/skills/gsd-plan-phase/SKILL.md +37 -0
  155. package/skills/gsd-ui-phase/SKILL.md +24 -0
  156. package/skills/gsd-ui-review/SKILL.md +24 -0
  157. package/skills/gsd-verify-work/SKILL.md +30 -0
@@ -6,7 +6,7 @@
6
6
  * Replaces repetitive inline bash patterns across ~50 GSD command/workflow/agent files.
7
7
  * Centralizes: config parsing, model resolution, phase lookup, git commits, summary verification.
8
8
  *
9
- * Usage: node gsd-tools.cjs <command> [args] [--raw]
9
+ * Usage: node gsd-tools.cjs <command> [args] [--raw] [--pick <field>]
10
10
  *
11
11
  * Atomic Commands:
12
12
  * state load Load project config + state
@@ -14,9 +14,13 @@
14
14
  * state update <field> <value> Update a STATE.md field
15
15
  * state get [section] Get STATE.md content or section
16
16
  * state patch --field val ... Batch update STATE.md fields
17
+ * state begin-phase --phase N --name S --plans C Update STATE.md for new phase start
18
+ * state signal-waiting --type T --question Q --options "A|B" --phase P write WAITING.json signal
19
+ * state signal-resume Remove WAITING.json signal
17
20
  * resolve-model <agent-type> Get model for agent based on profile
18
21
  * find-phase <phase> Find phase directory by number
19
- * commit <message> [--files f1 f2] Commit planning docs
22
+ * commit <message> [--files f1 f2] [--no-verify] Commit planning docs
23
+ * commit-to-subrepo <msg> --files f1 f2 Route commits to sub-repos
20
24
  * verify-summary <path> Verify a SUMMARY.md file
21
25
  * generate-slug <text> Convert text to URL-safe slug
22
26
  * current-timestamp [format] Get timestamp (full|date|filename)
@@ -32,7 +36,7 @@
32
36
  *
33
37
  * Phase Operations:
34
38
  * phase next-decimal <phase> Calculate next decimal phase number
35
- * phase add <description> Append new phase to roadmap + create dir
39
+ * phase add <description> [--id ID] Append new phase to roadmap + create dir
36
40
  * phase insert <after> <description> Insert decimal phase after existing
37
41
  * phase remove <phase> [--force] Remove phase, renumber all subsequent
38
42
  * phase complete <phase> Mark phase done, update state + roadmap
@@ -54,6 +58,7 @@
54
58
  * Validation:
55
59
  * validate consistency Check phase numbering, disk/roadmap sync
56
60
  * validate health [--repair] Check .planning/ integrity, optionally repair
61
+ * validate agents Check GSD agent installation status
57
62
  *
58
63
  * Progress:
59
64
  * progress [json|table|bar] Render progress in various formats
@@ -61,6 +66,10 @@
61
66
  * Todos:
62
67
  * todo complete <filename> Move todo from pending to completed
63
68
  *
69
+ * UAT Audit:
70
+ * audit-uat Scan all phases for unresolved UAT/verification items
71
+ * uat render-checkpoint --file <path> Render the current UAT checkpoint block
72
+ *
64
73
  * Scaffolding:
65
74
  * scaffold context --phase <N> Create CONTEXT.md template
66
75
  * scaffold uat --phase <N> Create UAT.md template
@@ -128,7 +137,8 @@
128
137
 
129
138
  const fs = require('fs');
130
139
  const path = require('path');
131
- const { error } = require('./lib/core.cjs');
140
+ const core = require('./lib/core.cjs');
141
+ const { error, findProjectRoot, getActiveWorkstream } = core;
132
142
  const state = require('./lib/state.cjs');
133
143
  const phase = require('./lib/phase.cjs');
134
144
  const roadmap = require('./lib/roadmap.cjs');
@@ -139,6 +149,49 @@ const milestone = require('./lib/milestone.cjs');
139
149
  const commands = require('./lib/commands.cjs');
140
150
  const init = require('./lib/init.cjs');
141
151
  const frontmatter = require('./lib/frontmatter.cjs');
152
+ const profilePipeline = require('./lib/profile-pipeline.cjs');
153
+ const profileOutput = require('./lib/profile-output.cjs');
154
+ const workstream = require('./lib/workstream.cjs');
155
+
156
+ // ─── Arg parsing helpers ──────────────────────────────────────────────────────
157
+
158
+ /**
159
+ * Extract named --flag <value> pairs from an args array.
160
+ * Returns an object mapping flag names to their values (null if absent).
161
+ * Flags listed in `booleanFlags` are treated as boolean (no value consumed).
162
+ *
163
+ * parseNamedArgs(args, 'phase', 'plan') → { phase: '3', plan: '1' }
164
+ * parseNamedArgs(args, [], ['amend', 'force']) → { amend: true, force: false }
165
+ */
166
+ function parseNamedArgs(args, valueFlags = [], booleanFlags = []) {
167
+ const result = {};
168
+ for (const flag of valueFlags) {
169
+ const idx = args.indexOf(`--${flag}`);
170
+ result[flag] = idx !== -1 && args[idx + 1] !== undefined && !args[idx + 1].startsWith('--')
171
+ ? args[idx + 1]
172
+ : null;
173
+ }
174
+ for (const flag of booleanFlags) {
175
+ result[flag] = args.includes(`--${flag}`);
176
+ }
177
+ return result;
178
+ }
179
+
180
+ /**
181
+ * Collect all tokens after --flag until the next --flag or end of args.
182
+ * Handles multi-word values like --name Foo Bar Version 1.
183
+ * Returns null if the flag is absent.
184
+ */
185
+ function parseMultiwordArg(args, flag) {
186
+ const idx = args.indexOf(`--${flag}`);
187
+ if (idx === -1) return null;
188
+ const tokens = [];
189
+ for (let i = idx + 1; i < args.length; i++) {
190
+ if (args[i].startsWith('--')) break;
191
+ tokens.push(args[i]);
192
+ }
193
+ return tokens.length > 0 ? tokens.join(' ') : null;
194
+ }
142
195
 
143
196
  // ─── CLI Router ───────────────────────────────────────────────────────────────
144
197
 
@@ -165,16 +218,137 @@ async function main() {
165
218
  error(`Invalid --cwd: ${cwd}`);
166
219
  }
167
220
 
221
+ // Resolve worktree root: in a linked worktree, .planning/ lives in the main worktree.
222
+ // However, in monorepo worktrees where the subdirectory itself owns .planning/,
223
+ // skip worktree resolution — the CWD is already the correct project root.
224
+ const { resolveWorktreeRoot } = require('./lib/core.cjs');
225
+ if (!fs.existsSync(path.join(cwd, '.planning'))) {
226
+ const worktreeRoot = resolveWorktreeRoot(cwd);
227
+ if (worktreeRoot !== cwd) {
228
+ cwd = worktreeRoot;
229
+ }
230
+ }
231
+
232
+ // Optional workstream override for parallel milestone work.
233
+ // Priority: --ws flag > GSD_WORKSTREAM env var > active-workstream file > null (flat mode)
234
+ const wsEqArg = args.find(arg => arg.startsWith('--ws='));
235
+ const wsIdx = args.indexOf('--ws');
236
+ let ws = null;
237
+ if (wsEqArg) {
238
+ ws = wsEqArg.slice('--ws='.length).trim();
239
+ if (!ws) error('Missing value for --ws');
240
+ args.splice(args.indexOf(wsEqArg), 1);
241
+ } else if (wsIdx !== -1) {
242
+ ws = args[wsIdx + 1];
243
+ if (!ws || ws.startsWith('--')) error('Missing value for --ws');
244
+ args.splice(wsIdx, 2);
245
+ } else if (process.env.GSD_WORKSTREAM) {
246
+ ws = process.env.GSD_WORKSTREAM.trim();
247
+ } else {
248
+ ws = getActiveWorkstream(cwd);
249
+ }
250
+ // Validate workstream name to prevent path traversal attacks.
251
+ if (ws && !/^[a-zA-Z0-9_-]+$/.test(ws)) {
252
+ error('Invalid workstream name: must be alphanumeric, hyphens, and underscores only');
253
+ }
254
+ // Set env var so all modules (planningDir, planningPaths) auto-resolve workstream paths
255
+ if (ws) {
256
+ process.env.GSD_WORKSTREAM = ws;
257
+ }
258
+
168
259
  const rawIndex = args.indexOf('--raw');
169
260
  const raw = rawIndex !== -1;
170
261
  if (rawIndex !== -1) args.splice(rawIndex, 1);
171
262
 
263
+ // --pick <name>: extract a single field from JSON output (replaces jq dependency).
264
+ // Supports dot-notation (e.g., --pick workflow.research) and bracket notation
265
+ // for arrays (e.g., --pick directories[-1]).
266
+ const pickIdx = args.indexOf('--pick');
267
+ let pickField = null;
268
+ if (pickIdx !== -1) {
269
+ pickField = args[pickIdx + 1];
270
+ if (!pickField || pickField.startsWith('--')) error('Missing value for --pick');
271
+ args.splice(pickIdx, 2);
272
+ }
273
+
172
274
  const command = args[0];
173
275
 
174
276
  if (!command) {
175
- error('Usage: gsd-tools <command> [args] [--raw] [--cwd <path>]\nCommands: state, resolve-model, find-phase, commit, verify-summary, verify, frontmatter, template, generate-slug, current-timestamp, list-todos, verify-path-exists, config-ensure-section, init');
277
+ error('Usage: gsd-tools <command> [args] [--raw] [--pick <field>] [--cwd <path>] [--ws <name>]\nCommands: state, resolve-model, find-phase, commit, verify-summary, verify, frontmatter, template, generate-slug, current-timestamp, list-todos, verify-path-exists, config-ensure-section, config-new-project, init, workstream');
278
+ }
279
+
280
+ // Multi-repo guard: resolve project root for commands that read/write .planning/.
281
+ // Skip for pure-utility commands that don't touch .planning/ to avoid unnecessary
282
+ // filesystem traversal on every invocation.
283
+ const SKIP_ROOT_RESOLUTION = new Set([
284
+ 'generate-slug', 'current-timestamp', 'verify-path-exists',
285
+ 'verify-summary', 'template', 'frontmatter',
286
+ ]);
287
+ if (!SKIP_ROOT_RESOLUTION.has(command)) {
288
+ cwd = findProjectRoot(cwd);
289
+ }
290
+
291
+ // When --pick is active, intercept stdout to extract the requested field.
292
+ if (pickField) {
293
+ const origWriteSync = fs.writeSync;
294
+ const chunks = [];
295
+ fs.writeSync = function (fd, data, ...rest) {
296
+ if (fd === 1) { chunks.push(String(data)); return; }
297
+ return origWriteSync.call(fs, fd, data, ...rest);
298
+ };
299
+ const cleanup = () => {
300
+ fs.writeSync = origWriteSync;
301
+ const captured = chunks.join('');
302
+ let jsonStr = captured;
303
+ if (jsonStr.startsWith('@file:')) {
304
+ jsonStr = fs.readFileSync(jsonStr.slice(6), 'utf-8');
305
+ }
306
+ try {
307
+ const obj = JSON.parse(jsonStr);
308
+ const value = extractField(obj, pickField);
309
+ const result = value === null || value === undefined ? '' : String(value);
310
+ origWriteSync.call(fs, 1, result);
311
+ } catch {
312
+ origWriteSync.call(fs, 1, captured);
313
+ }
314
+ };
315
+ try {
316
+ await runCommand(command, args, cwd, raw);
317
+ cleanup();
318
+ } catch (e) {
319
+ fs.writeSync = origWriteSync;
320
+ throw e;
321
+ }
322
+ return;
176
323
  }
177
324
 
325
+ await runCommand(command, args, cwd, raw);
326
+ }
327
+
328
+ /**
329
+ * Extract a field from an object using dot-notation and bracket syntax.
330
+ * Supports: 'field', 'parent.child', 'arr[-1]', 'arr[0]'
331
+ */
332
+ function extractField(obj, fieldPath) {
333
+ const parts = fieldPath.split('.');
334
+ let current = obj;
335
+ for (const part of parts) {
336
+ if (current === null || current === undefined) return undefined;
337
+ const bracketMatch = part.match(/^(.+?)\[(-?\d+)]$/);
338
+ if (bracketMatch) {
339
+ const key = bracketMatch[1];
340
+ const index = parseInt(bracketMatch[2], 10);
341
+ current = current[key];
342
+ if (!Array.isArray(current)) return undefined;
343
+ current = index < 0 ? current[current.length + index] : current[index];
344
+ } else {
345
+ current = current[part];
346
+ }
347
+ }
348
+ return current;
349
+ }
350
+
351
+ async function runCommand(command, args, cwd, raw) {
178
352
  switch (command) {
179
353
  case 'state': {
180
354
  const subcommand = args[1];
@@ -197,50 +371,29 @@ async function main() {
197
371
  } else if (subcommand === 'advance-plan') {
198
372
  state.cmdStateAdvancePlan(cwd, raw);
199
373
  } else if (subcommand === 'record-metric') {
200
- const phaseIdx = args.indexOf('--phase');
201
- const planIdx = args.indexOf('--plan');
202
- const durationIdx = args.indexOf('--duration');
203
- const tasksIdx = args.indexOf('--tasks');
204
- const filesIdx = args.indexOf('--files');
205
- state.cmdStateRecordMetric(cwd, {
206
- phase: phaseIdx !== -1 ? args[phaseIdx + 1] : null,
207
- plan: planIdx !== -1 ? args[planIdx + 1] : null,
208
- duration: durationIdx !== -1 ? args[durationIdx + 1] : null,
209
- tasks: tasksIdx !== -1 ? args[tasksIdx + 1] : null,
210
- files: filesIdx !== -1 ? args[filesIdx + 1] : null,
211
- }, raw);
374
+ const { phase: p, plan, duration, tasks, files } = parseNamedArgs(args, ['phase', 'plan', 'duration', 'tasks', 'files']);
375
+ state.cmdStateRecordMetric(cwd, { phase: p, plan, duration, tasks, files }, raw);
212
376
  } else if (subcommand === 'update-progress') {
213
377
  state.cmdStateUpdateProgress(cwd, raw);
214
378
  } else if (subcommand === 'add-decision') {
215
- const phaseIdx = args.indexOf('--phase');
216
- const summaryIdx = args.indexOf('--summary');
217
- const summaryFileIdx = args.indexOf('--summary-file');
218
- const rationaleIdx = args.indexOf('--rationale');
219
- const rationaleFileIdx = args.indexOf('--rationale-file');
220
- state.cmdStateAddDecision(cwd, {
221
- phase: phaseIdx !== -1 ? args[phaseIdx + 1] : null,
222
- summary: summaryIdx !== -1 ? args[summaryIdx + 1] : null,
223
- summary_file: summaryFileIdx !== -1 ? args[summaryFileIdx + 1] : null,
224
- rationale: rationaleIdx !== -1 ? args[rationaleIdx + 1] : '',
225
- rationale_file: rationaleFileIdx !== -1 ? args[rationaleFileIdx + 1] : null,
226
- }, raw);
379
+ const { phase: p, summary, 'summary-file': summary_file, rationale, 'rationale-file': rationale_file } = parseNamedArgs(args, ['phase', 'summary', 'summary-file', 'rationale', 'rationale-file']);
380
+ state.cmdStateAddDecision(cwd, { phase: p, summary, summary_file, rationale: rationale || '', rationale_file }, raw);
227
381
  } else if (subcommand === 'add-blocker') {
228
- const textIdx = args.indexOf('--text');
229
- const textFileIdx = args.indexOf('--text-file');
230
- state.cmdStateAddBlocker(cwd, {
231
- text: textIdx !== -1 ? args[textIdx + 1] : null,
232
- text_file: textFileIdx !== -1 ? args[textFileIdx + 1] : null,
233
- }, raw);
382
+ const { text, 'text-file': text_file } = parseNamedArgs(args, ['text', 'text-file']);
383
+ state.cmdStateAddBlocker(cwd, { text, text_file }, raw);
234
384
  } else if (subcommand === 'resolve-blocker') {
235
- const textIdx = args.indexOf('--text');
236
- state.cmdStateResolveBlocker(cwd, textIdx !== -1 ? args[textIdx + 1] : null, raw);
385
+ state.cmdStateResolveBlocker(cwd, parseNamedArgs(args, ['text']).text, raw);
237
386
  } else if (subcommand === 'record-session') {
238
- const stoppedIdx = args.indexOf('--stopped-at');
239
- const resumeIdx = args.indexOf('--resume-file');
240
- state.cmdStateRecordSession(cwd, {
241
- stopped_at: stoppedIdx !== -1 ? args[stoppedIdx + 1] : null,
242
- resume_file: resumeIdx !== -1 ? args[resumeIdx + 1] : 'None',
243
- }, raw);
387
+ const { 'stopped-at': stopped_at, 'resume-file': resume_file } = parseNamedArgs(args, ['stopped-at', 'resume-file']);
388
+ state.cmdStateRecordSession(cwd, { stopped_at, resume_file: resume_file || 'None' }, raw);
389
+ } else if (subcommand === 'begin-phase') {
390
+ const { phase: p, name, plans } = parseNamedArgs(args, ['phase', 'name', 'plans']);
391
+ state.cmdStateBeginPhase(cwd, p, name, plans !== null ? parseInt(plans, 10) : null, raw);
392
+ } else if (subcommand === 'signal-waiting') {
393
+ const { type, question, options, phase: p } = parseNamedArgs(args, ['type', 'question', 'options', 'phase']);
394
+ state.cmdSignalWaiting(cwd, type, question, options, p, raw);
395
+ } else if (subcommand === 'signal-resume') {
396
+ state.cmdSignalResume(cwd, raw);
244
397
  } else {
245
398
  state.cmdStateLoad(cwd, raw);
246
399
  }
@@ -259,6 +412,7 @@ async function main() {
259
412
 
260
413
  case 'commit': {
261
414
  const amend = args.includes('--amend');
415
+ const noVerify = args.includes('--no-verify');
262
416
  const filesIndex = args.indexOf('--files');
263
417
  // Collect all positional args between command name and first flag,
264
418
  // then join them — handles both quoted ("multi word msg") and
@@ -267,7 +421,15 @@ async function main() {
267
421
  const messageArgs = args.slice(1, endIndex).filter(a => !a.startsWith('--'));
268
422
  const message = messageArgs.join(' ') || undefined;
269
423
  const files = filesIndex !== -1 ? args.slice(filesIndex + 1).filter(a => !a.startsWith('--')) : [];
270
- commands.cmdCommit(cwd, message, files, raw, amend);
424
+ commands.cmdCommit(cwd, message, files, raw, amend, noVerify);
425
+ break;
426
+ }
427
+
428
+ case 'commit-to-subrepo': {
429
+ const message = args[1];
430
+ const filesIndex = args.indexOf('--files');
431
+ const files = filesIndex !== -1 ? args.slice(filesIndex + 1).filter(a => !a.startsWith('--')) : [];
432
+ commands.cmdCommitToSubrepo(cwd, message, files, raw);
271
433
  break;
272
434
  }
273
435
 
@@ -285,19 +447,18 @@ async function main() {
285
447
  template.cmdTemplateSelect(cwd, args[2], raw);
286
448
  } else if (subcommand === 'fill') {
287
449
  const templateType = args[2];
288
- const phaseIdx = args.indexOf('--phase');
289
- const planIdx = args.indexOf('--plan');
290
- const nameIdx = args.indexOf('--name');
291
- const typeIdx = args.indexOf('--type');
292
- const waveIdx = args.indexOf('--wave');
293
- const fieldsIdx = args.indexOf('--fields');
450
+ const { phase, plan, name, type, wave, fields: fieldsRaw } = parseNamedArgs(args, ['phase', 'plan', 'name', 'type', 'wave', 'fields']);
451
+ let fields = {};
452
+ if (fieldsRaw) {
453
+ const { safeJsonParse } = require('./lib/security.cjs');
454
+ const result = safeJsonParse(fieldsRaw, { label: '--fields' });
455
+ if (!result.ok) error(result.error);
456
+ fields = result.value;
457
+ }
294
458
  template.cmdTemplateFill(cwd, templateType, {
295
- phase: phaseIdx !== -1 ? args[phaseIdx + 1] : null,
296
- plan: planIdx !== -1 ? args[planIdx + 1] : null,
297
- name: nameIdx !== -1 ? args[nameIdx + 1] : null,
298
- type: typeIdx !== -1 ? args[typeIdx + 1] : 'execute',
299
- wave: waveIdx !== -1 ? args[waveIdx + 1] : '1',
300
- fields: fieldsIdx !== -1 ? JSON.parse(args[fieldsIdx + 1]) : {},
459
+ phase, plan, name, fields,
460
+ type: type || 'execute',
461
+ wave: wave || '1',
301
462
  }, raw);
302
463
  } else {
303
464
  error('Unknown template subcommand. Available: select, fill');
@@ -309,18 +470,14 @@ async function main() {
309
470
  const subcommand = args[1];
310
471
  const file = args[2];
311
472
  if (subcommand === 'get') {
312
- const fieldIdx = args.indexOf('--field');
313
- frontmatter.cmdFrontmatterGet(cwd, file, fieldIdx !== -1 ? args[fieldIdx + 1] : null, raw);
473
+ frontmatter.cmdFrontmatterGet(cwd, file, parseNamedArgs(args, ['field']).field, raw);
314
474
  } else if (subcommand === 'set') {
315
- const fieldIdx = args.indexOf('--field');
316
- const valueIdx = args.indexOf('--value');
317
- frontmatter.cmdFrontmatterSet(cwd, file, fieldIdx !== -1 ? args[fieldIdx + 1] : null, valueIdx !== -1 ? args[valueIdx + 1] : undefined, raw);
475
+ const { field, value } = parseNamedArgs(args, ['field', 'value']);
476
+ frontmatter.cmdFrontmatterSet(cwd, file, field, value !== null ? value : undefined, raw);
318
477
  } else if (subcommand === 'merge') {
319
- const dataIdx = args.indexOf('--data');
320
- frontmatter.cmdFrontmatterMerge(cwd, file, dataIdx !== -1 ? args[dataIdx + 1] : null, raw);
478
+ frontmatter.cmdFrontmatterMerge(cwd, file, parseNamedArgs(args, ['data']).data, raw);
321
479
  } else if (subcommand === 'validate') {
322
- const schemaIdx = args.indexOf('--schema');
323
- frontmatter.cmdFrontmatterValidate(cwd, file, schemaIdx !== -1 ? args[schemaIdx + 1] : null, raw);
480
+ frontmatter.cmdFrontmatterValidate(cwd, file, parseNamedArgs(args, ['schema']).schema, raw);
324
481
  } else {
325
482
  error('Unknown frontmatter subcommand. Available: get, set, merge, validate');
326
483
  }
@@ -377,11 +534,26 @@ async function main() {
377
534
  break;
378
535
  }
379
536
 
537
+ case "config-set-model-profile": {
538
+ config.cmdConfigSetModelProfile(cwd, args[1], raw);
539
+ break;
540
+ }
541
+
380
542
  case 'config-get': {
381
543
  config.cmdConfigGet(cwd, args[1], raw);
382
544
  break;
383
545
  }
384
546
 
547
+ case 'config-new-project': {
548
+ config.cmdConfigNewProject(cwd, args[1], raw);
549
+ break;
550
+ }
551
+
552
+ case 'agent-skills': {
553
+ init.cmdAgentSkills(cwd, args[1], raw);
554
+ break;
555
+ }
556
+
385
557
  case 'history-digest': {
386
558
  commands.cmdHistoryDigest(cwd, raw);
387
559
  break;
@@ -433,7 +605,18 @@ async function main() {
433
605
  if (subcommand === 'next-decimal') {
434
606
  phase.cmdPhaseNextDecimal(cwd, args[2], raw);
435
607
  } else if (subcommand === 'add') {
436
- phase.cmdPhaseAdd(cwd, args.slice(2).join(' '), raw);
608
+ const idIdx = args.indexOf('--id');
609
+ let customId = null;
610
+ const descArgs = [];
611
+ for (let i = 2; i < args.length; i++) {
612
+ if (args[i] === '--id' && i + 1 < args.length) {
613
+ customId = args[i + 1];
614
+ i++; // skip value
615
+ } else {
616
+ descArgs.push(args[i]);
617
+ }
618
+ }
619
+ phase.cmdPhaseAdd(cwd, descArgs.join(' '), raw, customId);
437
620
  } else if (subcommand === 'insert') {
438
621
  phase.cmdPhaseInsert(cwd, args[2], args.slice(3).join(' '), raw);
439
622
  } else if (subcommand === 'remove') {
@@ -450,18 +633,8 @@ async function main() {
450
633
  case 'milestone': {
451
634
  const subcommand = args[1];
452
635
  if (subcommand === 'complete') {
453
- const nameIndex = args.indexOf('--name');
636
+ const milestoneName = parseMultiwordArg(args, 'name');
454
637
  const archivePhases = args.includes('--archive-phases');
455
- // Collect --name value (everything after --name until next flag or end)
456
- let milestoneName = null;
457
- if (nameIndex !== -1) {
458
- const nameArgs = [];
459
- for (let i = nameIndex + 1; i < args.length; i++) {
460
- if (args[i].startsWith('--')) break;
461
- nameArgs.push(args[i]);
462
- }
463
- milestoneName = nameArgs.join(' ') || null;
464
- }
465
638
  milestone.cmdMilestoneComplete(cwd, args[2], { name: milestoneName, archivePhases }, raw);
466
639
  } else {
467
640
  error('Unknown milestone subcommand. Available: complete');
@@ -476,8 +649,10 @@ async function main() {
476
649
  } else if (subcommand === 'health') {
477
650
  const repairFlag = args.includes('--repair');
478
651
  verify.cmdValidateHealth(cwd, { repair: repairFlag }, raw);
652
+ } else if (subcommand === 'agents') {
653
+ verify.cmdValidateAgents(cwd, raw);
479
654
  } else {
480
- error('Unknown validate subcommand. Available: consistency, health');
655
+ error('Unknown validate subcommand. Available: consistency, health, agents');
481
656
  }
482
657
  break;
483
658
  }
@@ -488,23 +663,47 @@ async function main() {
488
663
  break;
489
664
  }
490
665
 
666
+ case 'audit-uat': {
667
+ const uat = require('./lib/uat.cjs');
668
+ uat.cmdAuditUat(cwd, raw);
669
+ break;
670
+ }
671
+
672
+ case 'uat': {
673
+ const subcommand = args[1];
674
+ const uat = require('./lib/uat.cjs');
675
+ if (subcommand === 'render-checkpoint') {
676
+ const options = parseNamedArgs(args, ['file']);
677
+ uat.cmdRenderCheckpoint(cwd, options, raw);
678
+ } else {
679
+ error('Unknown uat subcommand. Available: render-checkpoint');
680
+ }
681
+ break;
682
+ }
683
+
684
+ case 'stats': {
685
+ const subcommand = args[1] || 'json';
686
+ commands.cmdStats(cwd, subcommand, raw);
687
+ break;
688
+ }
689
+
491
690
  case 'todo': {
492
691
  const subcommand = args[1];
493
692
  if (subcommand === 'complete') {
494
693
  commands.cmdTodoComplete(cwd, args[2], raw);
694
+ } else if (subcommand === 'match-phase') {
695
+ commands.cmdTodoMatchPhase(cwd, args[2], raw);
495
696
  } else {
496
- error('Unknown todo subcommand. Available: complete');
697
+ error('Unknown todo subcommand. Available: complete, match-phase');
497
698
  }
498
699
  break;
499
700
  }
500
701
 
501
702
  case 'scaffold': {
502
703
  const scaffoldType = args[1];
503
- const phaseIndex = args.indexOf('--phase');
504
- const nameIndex = args.indexOf('--name');
505
704
  const scaffoldOptions = {
506
- phase: phaseIndex !== -1 ? args[phaseIndex + 1] : null,
507
- name: nameIndex !== -1 ? args.slice(nameIndex + 1).join(' ') : null,
705
+ phase: parseNamedArgs(args, ['phase']).phase,
706
+ name: parseMultiwordArg(args, 'name'),
508
707
  };
509
708
  commands.cmdScaffold(cwd, scaffoldType, scaffoldOptions, raw);
510
709
  break;
@@ -549,8 +748,20 @@ async function main() {
549
748
  case 'progress':
550
749
  init.cmdInitProgress(cwd, raw);
551
750
  break;
751
+ case 'manager':
752
+ init.cmdInitManager(cwd, raw);
753
+ break;
754
+ case 'new-workspace':
755
+ init.cmdInitNewWorkspace(cwd, raw);
756
+ break;
757
+ case 'list-workspaces':
758
+ init.cmdInitListWorkspaces(cwd, raw);
759
+ break;
760
+ case 'remove-workspace':
761
+ init.cmdInitRemoveWorkspace(cwd, args[2], raw);
762
+ break;
552
763
  default:
553
- error(`Unknown init workflow: ${workflow}\nAvailable: execute-phase, plan-phase, new-project, new-milestone, quick, resume, verify-work, phase-op, todos, milestone-op, map-codebase, progress`);
764
+ error(`Unknown init workflow: ${workflow}\nAvailable: execute-phase, plan-phase, new-project, new-milestone, quick, resume, verify-work, phase-op, todos, milestone-op, map-codebase, progress, manager, new-workspace, list-workspaces, remove-workspace`);
554
765
  }
555
766
  break;
556
767
  }
@@ -584,6 +795,121 @@ async function main() {
584
795
  break;
585
796
  }
586
797
 
798
+ // ─── Profiling Pipeline ────────────────────────────────────────────────
799
+
800
+ case 'scan-sessions': {
801
+ const pathIdx = args.indexOf('--path');
802
+ const sessionsPath = pathIdx !== -1 ? args[pathIdx + 1] : null;
803
+ const verboseFlag = args.includes('--verbose');
804
+ const jsonFlag = args.includes('--json');
805
+ await profilePipeline.cmdScanSessions(sessionsPath, { verbose: verboseFlag, json: jsonFlag }, raw);
806
+ break;
807
+ }
808
+
809
+ case 'extract-messages': {
810
+ const sessionIdx = args.indexOf('--session');
811
+ const sessionId = sessionIdx !== -1 ? args[sessionIdx + 1] : null;
812
+ const limitIdx = args.indexOf('--limit');
813
+ const limit = limitIdx !== -1 ? parseInt(args[limitIdx + 1], 10) : null;
814
+ const pathIdx = args.indexOf('--path');
815
+ const sessionsPath = pathIdx !== -1 ? args[pathIdx + 1] : null;
816
+ const projectArg = args[1];
817
+ if (!projectArg || projectArg.startsWith('--')) {
818
+ error('Usage: gsd-tools extract-messages <project> [--session <id>] [--limit N] [--path <dir>]\nRun scan-sessions first to see available projects.');
819
+ }
820
+ await profilePipeline.cmdExtractMessages(projectArg, { sessionId, limit }, raw, sessionsPath);
821
+ break;
822
+ }
823
+
824
+ case 'profile-sample': {
825
+ const pathIdx = args.indexOf('--path');
826
+ const sessionsPath = pathIdx !== -1 ? args[pathIdx + 1] : null;
827
+ const limitIdx = args.indexOf('--limit');
828
+ const limit = limitIdx !== -1 ? parseInt(args[limitIdx + 1], 10) : 150;
829
+ const maxPerIdx = args.indexOf('--max-per-project');
830
+ const maxPerProject = maxPerIdx !== -1 ? parseInt(args[maxPerIdx + 1], 10) : null;
831
+ const maxCharsIdx = args.indexOf('--max-chars');
832
+ const maxChars = maxCharsIdx !== -1 ? parseInt(args[maxCharsIdx + 1], 10) : 500;
833
+ await profilePipeline.cmdProfileSample(sessionsPath, { limit, maxPerProject, maxChars }, raw);
834
+ break;
835
+ }
836
+
837
+ // ─── Profile Output ──────────────────────────────────────────────────
838
+
839
+ case 'write-profile': {
840
+ const inputIdx = args.indexOf('--input');
841
+ const inputPath = inputIdx !== -1 ? args[inputIdx + 1] : null;
842
+ if (!inputPath) error('--input <analysis-json-path> is required');
843
+ const outputIdx = args.indexOf('--output');
844
+ const outputPath = outputIdx !== -1 ? args[outputIdx + 1] : null;
845
+ profileOutput.cmdWriteProfile(cwd, { input: inputPath, output: outputPath }, raw);
846
+ break;
847
+ }
848
+
849
+ case 'profile-questionnaire': {
850
+ const answersIdx = args.indexOf('--answers');
851
+ const answers = answersIdx !== -1 ? args[answersIdx + 1] : null;
852
+ profileOutput.cmdProfileQuestionnaire({ answers }, raw);
853
+ break;
854
+ }
855
+
856
+ case 'generate-dev-preferences': {
857
+ const analysisIdx = args.indexOf('--analysis');
858
+ const analysisPath = analysisIdx !== -1 ? args[analysisIdx + 1] : null;
859
+ const outputIdx = args.indexOf('--output');
860
+ const outputPath = outputIdx !== -1 ? args[outputIdx + 1] : null;
861
+ const stackIdx = args.indexOf('--stack');
862
+ const stack = stackIdx !== -1 ? args[stackIdx + 1] : null;
863
+ profileOutput.cmdGenerateDevPreferences(cwd, { analysis: analysisPath, output: outputPath, stack }, raw);
864
+ break;
865
+ }
866
+
867
+ case 'generate-OpenCode-profile': {
868
+ const analysisIdx = args.indexOf('--analysis');
869
+ const analysisPath = analysisIdx !== -1 ? args[analysisIdx + 1] : null;
870
+ const outputIdx = args.indexOf('--output');
871
+ const outputPath = outputIdx !== -1 ? args[outputIdx + 1] : null;
872
+ const globalFlag = args.includes('--global');
873
+ profileOutput.cmdGenerateClaudeProfile(cwd, { analysis: analysisPath, output: outputPath, global: globalFlag }, raw);
874
+ break;
875
+ }
876
+
877
+ case 'generate-OpenCode-md': {
878
+ const outputIdx = args.indexOf('--output');
879
+ const outputPath = outputIdx !== -1 ? args[outputIdx + 1] : null;
880
+ const autoFlag = args.includes('--auto');
881
+ const forceFlag = args.includes('--force');
882
+ profileOutput.cmdGenerateClaudeMd(cwd, { output: outputPath, auto: autoFlag, force: forceFlag }, raw);
883
+ break;
884
+ }
885
+
886
+ case 'workstream': {
887
+ const subcommand = args[1];
888
+ if (subcommand === 'create') {
889
+ const migrateNameIdx = args.indexOf('--migrate-name');
890
+ const noMigrate = args.includes('--no-migrate');
891
+ workstream.cmdWorkstreamCreate(cwd, args[2], {
892
+ migrate: !noMigrate,
893
+ migrateName: migrateNameIdx !== -1 ? args[migrateNameIdx + 1] : null,
894
+ }, raw);
895
+ } else if (subcommand === 'list') {
896
+ workstream.cmdWorkstreamList(cwd, raw);
897
+ } else if (subcommand === 'status') {
898
+ workstream.cmdWorkstreamStatus(cwd, args[2], raw);
899
+ } else if (subcommand === 'complete') {
900
+ workstream.cmdWorkstreamComplete(cwd, args[2], {}, raw);
901
+ } else if (subcommand === 'set') {
902
+ workstream.cmdWorkstreamSet(cwd, args[2], raw);
903
+ } else if (subcommand === 'get') {
904
+ workstream.cmdWorkstreamGet(cwd, raw);
905
+ } else if (subcommand === 'progress') {
906
+ workstream.cmdWorkstreamProgress(cwd, raw);
907
+ } else {
908
+ error('Unknown workstream subcommand. Available: create, list, status, complete, set, get, progress');
909
+ }
910
+ break;
911
+ }
912
+
587
913
  default:
588
914
  error(`Unknown command: ${command}`);
589
915
  }