@howlil/ez-agents 2.0.0 → 2.0.1

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 (106) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +93 -93
  3. package/agents/ez-plan-checker.md +2 -2
  4. package/agents/ez-research-synthesizer.md +1 -1
  5. package/agents/ez-ui-researcher.md +1 -1
  6. package/agents/ez-verifier.md +1 -1
  7. package/bin/install.js +132 -132
  8. package/get-shit-done/bin/lib/assistant-adapter.cjs +205 -205
  9. package/get-shit-done/bin/lib/audit-exec.cjs +150 -150
  10. package/get-shit-done/bin/lib/auth.cjs +175 -175
  11. package/get-shit-done/bin/lib/circuit-breaker.cjs +118 -118
  12. package/get-shit-done/bin/lib/commands.cjs +666 -666
  13. package/get-shit-done/bin/lib/config.cjs +183 -183
  14. package/get-shit-done/bin/lib/core.cjs +495 -495
  15. package/get-shit-done/bin/lib/file-lock.cjs +236 -236
  16. package/get-shit-done/bin/lib/frontmatter.cjs +299 -299
  17. package/get-shit-done/bin/lib/fs-utils.cjs +153 -153
  18. package/get-shit-done/bin/lib/git-utils.cjs +203 -203
  19. package/get-shit-done/bin/lib/health-check.cjs +163 -163
  20. package/get-shit-done/bin/lib/index.cjs +113 -113
  21. package/get-shit-done/bin/lib/init.cjs +710 -710
  22. package/get-shit-done/bin/lib/logger.cjs +117 -117
  23. package/get-shit-done/bin/lib/milestone.cjs +241 -241
  24. package/get-shit-done/bin/lib/model-provider.cjs +146 -146
  25. package/get-shit-done/bin/lib/phase.cjs +908 -908
  26. package/get-shit-done/bin/lib/retry.cjs +119 -119
  27. package/get-shit-done/bin/lib/roadmap.cjs +305 -305
  28. package/get-shit-done/bin/lib/safe-exec.cjs +128 -128
  29. package/get-shit-done/bin/lib/safe-path.cjs +130 -130
  30. package/get-shit-done/bin/lib/state.cjs +721 -721
  31. package/get-shit-done/bin/lib/temp-file.cjs +239 -239
  32. package/get-shit-done/bin/lib/template.cjs +222 -222
  33. package/get-shit-done/bin/lib/test-file-lock.cjs +112 -112
  34. package/get-shit-done/bin/lib/test-graceful.cjs +93 -93
  35. package/get-shit-done/bin/lib/test-logger.cjs +60 -60
  36. package/get-shit-done/bin/lib/test-safe-exec.cjs +38 -38
  37. package/get-shit-done/bin/lib/test-safe-path.cjs +33 -33
  38. package/get-shit-done/bin/lib/test-temp-file.cjs +125 -125
  39. package/get-shit-done/bin/lib/timeout-exec.cjs +62 -62
  40. package/get-shit-done/bin/lib/verify.cjs +820 -820
  41. package/get-shit-done/references/checkpoints.md +776 -776
  42. package/get-shit-done/references/questioning.md +162 -162
  43. package/get-shit-done/references/tdd.md +263 -263
  44. package/get-shit-done/templates/codebase/concerns.md +310 -310
  45. package/get-shit-done/templates/codebase/conventions.md +307 -307
  46. package/get-shit-done/templates/codebase/integrations.md +280 -280
  47. package/get-shit-done/templates/codebase/stack.md +186 -186
  48. package/get-shit-done/templates/codebase/testing.md +480 -480
  49. package/get-shit-done/templates/config.json +37 -37
  50. package/get-shit-done/templates/continue-here.md +78 -78
  51. package/get-shit-done/templates/milestone-archive.md +123 -123
  52. package/get-shit-done/templates/milestone.md +115 -115
  53. package/get-shit-done/templates/requirements.md +231 -231
  54. package/get-shit-done/templates/research-project/ARCHITECTURE.md +204 -204
  55. package/get-shit-done/templates/research-project/FEATURES.md +147 -147
  56. package/get-shit-done/templates/research-project/PITFALLS.md +200 -200
  57. package/get-shit-done/templates/research-project/STACK.md +120 -120
  58. package/get-shit-done/templates/research-project/SUMMARY.md +170 -170
  59. package/get-shit-done/templates/retrospective.md +54 -54
  60. package/get-shit-done/templates/roadmap.md +202 -202
  61. package/get-shit-done/templates/summary-minimal.md +41 -41
  62. package/get-shit-done/templates/summary-standard.md +48 -48
  63. package/get-shit-done/templates/summary.md +248 -248
  64. package/get-shit-done/templates/user-setup.md +311 -311
  65. package/get-shit-done/templates/verification-report.md +322 -322
  66. package/get-shit-done/workflows/add-phase.md +112 -112
  67. package/get-shit-done/workflows/add-tests.md +351 -351
  68. package/get-shit-done/workflows/add-todo.md +158 -158
  69. package/get-shit-done/workflows/audit-milestone.md +332 -332
  70. package/get-shit-done/workflows/autonomous.md +743 -743
  71. package/get-shit-done/workflows/check-todos.md +177 -177
  72. package/get-shit-done/workflows/cleanup.md +152 -152
  73. package/get-shit-done/workflows/complete-milestone.md +766 -766
  74. package/get-shit-done/workflows/diagnose-issues.md +219 -219
  75. package/get-shit-done/workflows/discovery-phase.md +289 -289
  76. package/get-shit-done/workflows/discuss-phase.md +762 -762
  77. package/get-shit-done/workflows/execute-phase.md +468 -468
  78. package/get-shit-done/workflows/execute-plan.md +483 -483
  79. package/get-shit-done/workflows/health.md +159 -159
  80. package/get-shit-done/workflows/help.md +492 -492
  81. package/get-shit-done/workflows/insert-phase.md +130 -130
  82. package/get-shit-done/workflows/list-phase-assumptions.md +178 -178
  83. package/get-shit-done/workflows/map-codebase.md +316 -316
  84. package/get-shit-done/workflows/new-milestone.md +384 -384
  85. package/get-shit-done/workflows/new-project.md +1111 -1111
  86. package/get-shit-done/workflows/node-repair.md +92 -92
  87. package/get-shit-done/workflows/pause-work.md +122 -122
  88. package/get-shit-done/workflows/plan-milestone-gaps.md +274 -274
  89. package/get-shit-done/workflows/plan-phase.md +651 -651
  90. package/get-shit-done/workflows/progress.md +382 -382
  91. package/get-shit-done/workflows/quick.md +610 -610
  92. package/get-shit-done/workflows/remove-phase.md +155 -155
  93. package/get-shit-done/workflows/research-phase.md +74 -74
  94. package/get-shit-done/workflows/resume-project.md +307 -307
  95. package/get-shit-done/workflows/set-profile.md +81 -81
  96. package/get-shit-done/workflows/settings.md +242 -242
  97. package/get-shit-done/workflows/stats.md +57 -57
  98. package/get-shit-done/workflows/transition.md +544 -544
  99. package/get-shit-done/workflows/ui-phase.md +290 -290
  100. package/get-shit-done/workflows/ui-review.md +157 -157
  101. package/get-shit-done/workflows/update.md +320 -320
  102. package/get-shit-done/workflows/validate-phase.md +167 -167
  103. package/get-shit-done/workflows/verify-phase.md +243 -243
  104. package/package.json +1 -1
  105. package/scripts/build-hooks.js +43 -43
  106. package/scripts/run-tests.cjs +29 -29
@@ -1,710 +1,710 @@
1
- /**
2
- * Init — Compound init commands for workflow bootstrapping
3
- */
4
-
5
- const fs = require('fs');
6
- const path = require('path');
7
- const { execSync } = require('child_process');
8
- const { loadConfig, resolveModelInternal, findPhaseInternal, getRoadmapPhaseInternal, pathExistsInternal, generateSlugInternal, getMilestoneInfo, normalizePhaseName, toPosixPath, output, error } = require('./core.cjs');
9
-
10
- function cmdInitExecutePhase(cwd, phase, raw) {
11
- if (!phase) {
12
- error('phase required for init execute-phase');
13
- }
14
-
15
- const config = loadConfig(cwd);
16
- const phaseInfo = findPhaseInternal(cwd, phase);
17
- const milestone = getMilestoneInfo(cwd);
18
-
19
- const roadmapPhase = getRoadmapPhaseInternal(cwd, phase);
20
- const reqMatch = roadmapPhase?.section?.match(/^\*\*Requirements\*\*:[^\S\n]*([^\n]*)$/m);
21
- const reqExtracted = reqMatch
22
- ? reqMatch[1].replace(/[\[\]]/g, '').split(',').map(s => s.trim()).filter(Boolean).join(', ')
23
- : null;
24
- const phase_req_ids = (reqExtracted && reqExtracted !== 'TBD') ? reqExtracted : null;
25
-
26
- const result = {
27
- // Models
28
- executor_model: resolveModelInternal(cwd, 'gsd-executor'),
29
- verifier_model: resolveModelInternal(cwd, 'gsd-verifier'),
30
-
31
- // Config flags
32
- commit_docs: config.commit_docs,
33
- parallelization: config.parallelization,
34
- branching_strategy: config.branching_strategy,
35
- phase_branch_template: config.phase_branch_template,
36
- milestone_branch_template: config.milestone_branch_template,
37
- verifier_enabled: config.verifier,
38
-
39
- // Phase info
40
- phase_found: !!phaseInfo,
41
- phase_dir: phaseInfo?.directory || null,
42
- phase_number: phaseInfo?.phase_number || null,
43
- phase_name: phaseInfo?.phase_name || null,
44
- phase_slug: phaseInfo?.phase_slug || null,
45
- phase_req_ids,
46
-
47
- // Plan inventory
48
- plans: phaseInfo?.plans || [],
49
- summaries: phaseInfo?.summaries || [],
50
- incomplete_plans: phaseInfo?.incomplete_plans || [],
51
- plan_count: phaseInfo?.plans?.length || 0,
52
- incomplete_count: phaseInfo?.incomplete_plans?.length || 0,
53
-
54
- // Branch name (pre-computed)
55
- branch_name: config.branching_strategy === 'phase' && phaseInfo
56
- ? config.phase_branch_template
57
- .replace('{phase}', phaseInfo.phase_number)
58
- .replace('{slug}', phaseInfo.phase_slug || 'phase')
59
- : config.branching_strategy === 'milestone'
60
- ? config.milestone_branch_template
61
- .replace('{milestone}', milestone.version)
62
- .replace('{slug}', generateSlugInternal(milestone.name) || 'milestone')
63
- : null,
64
-
65
- // Milestone info
66
- milestone_version: milestone.version,
67
- milestone_name: milestone.name,
68
- milestone_slug: generateSlugInternal(milestone.name),
69
-
70
- // File existence
71
- state_exists: pathExistsInternal(cwd, '.planning/STATE.md'),
72
- roadmap_exists: pathExistsInternal(cwd, '.planning/ROADMAP.md'),
73
- config_exists: pathExistsInternal(cwd, '.planning/config.json'),
74
- // File paths
75
- state_path: '.planning/STATE.md',
76
- roadmap_path: '.planning/ROADMAP.md',
77
- config_path: '.planning/config.json',
78
- };
79
-
80
- output(result, raw);
81
- }
82
-
83
- function cmdInitPlanPhase(cwd, phase, raw) {
84
- if (!phase) {
85
- error('phase required for init plan-phase');
86
- }
87
-
88
- const config = loadConfig(cwd);
89
- const phaseInfo = findPhaseInternal(cwd, phase);
90
-
91
- const roadmapPhase = getRoadmapPhaseInternal(cwd, phase);
92
- const reqMatch = roadmapPhase?.section?.match(/^\*\*Requirements\*\*:[^\S\n]*([^\n]*)$/m);
93
- const reqExtracted = reqMatch
94
- ? reqMatch[1].replace(/[\[\]]/g, '').split(',').map(s => s.trim()).filter(Boolean).join(', ')
95
- : null;
96
- const phase_req_ids = (reqExtracted && reqExtracted !== 'TBD') ? reqExtracted : null;
97
-
98
- const result = {
99
- // Models
100
- researcher_model: resolveModelInternal(cwd, 'gsd-phase-researcher'),
101
- planner_model: resolveModelInternal(cwd, 'gsd-planner'),
102
- checker_model: resolveModelInternal(cwd, 'gsd-plan-checker'),
103
-
104
- // Workflow flags
105
- research_enabled: config.research,
106
- plan_checker_enabled: config.plan_checker,
107
- nyquist_validation_enabled: config.nyquist_validation,
108
- commit_docs: config.commit_docs,
109
-
110
- // Phase info
111
- phase_found: !!phaseInfo,
112
- phase_dir: phaseInfo?.directory || null,
113
- phase_number: phaseInfo?.phase_number || null,
114
- phase_name: phaseInfo?.phase_name || null,
115
- phase_slug: phaseInfo?.phase_slug || null,
116
- padded_phase: phaseInfo?.phase_number ? normalizePhaseName(phaseInfo.phase_number) : null,
117
- phase_req_ids,
118
-
119
- // Existing artifacts
120
- has_research: phaseInfo?.has_research || false,
121
- has_context: phaseInfo?.has_context || false,
122
- has_plans: (phaseInfo?.plans?.length || 0) > 0,
123
- plan_count: phaseInfo?.plans?.length || 0,
124
-
125
- // Environment
126
- planning_exists: pathExistsInternal(cwd, '.planning'),
127
- roadmap_exists: pathExistsInternal(cwd, '.planning/ROADMAP.md'),
128
-
129
- // File paths
130
- state_path: '.planning/STATE.md',
131
- roadmap_path: '.planning/ROADMAP.md',
132
- requirements_path: '.planning/REQUIREMENTS.md',
133
- };
134
-
135
- if (phaseInfo?.directory) {
136
- // Find *-CONTEXT.md in phase directory
137
- const phaseDirFull = path.join(cwd, phaseInfo.directory);
138
- try {
139
- const files = fs.readdirSync(phaseDirFull);
140
- const contextFile = files.find(f => f.endsWith('-CONTEXT.md') || f === 'CONTEXT.md');
141
- if (contextFile) {
142
- result.context_path = toPosixPath(path.join(phaseInfo.directory, contextFile));
143
- }
144
- const researchFile = files.find(f => f.endsWith('-RESEARCH.md') || f === 'RESEARCH.md');
145
- if (researchFile) {
146
- result.research_path = toPosixPath(path.join(phaseInfo.directory, researchFile));
147
- }
148
- const verificationFile = files.find(f => f.endsWith('-VERIFICATION.md') || f === 'VERIFICATION.md');
149
- if (verificationFile) {
150
- result.verification_path = toPosixPath(path.join(phaseInfo.directory, verificationFile));
151
- }
152
- const uatFile = files.find(f => f.endsWith('-UAT.md') || f === 'UAT.md');
153
- if (uatFile) {
154
- result.uat_path = toPosixPath(path.join(phaseInfo.directory, uatFile));
155
- }
156
- } catch {}
157
- }
158
-
159
- output(result, raw);
160
- }
161
-
162
- function cmdInitNewProject(cwd, raw) {
163
- const config = loadConfig(cwd);
164
-
165
- // Detect Brave Search API key availability
166
- const homedir = require('os').homedir();
167
- const braveKeyFile = path.join(homedir, '.gsd', 'brave_api_key');
168
- const hasBraveSearch = !!(process.env.BRAVE_API_KEY || fs.existsSync(braveKeyFile));
169
-
170
- // Detect existing code
171
- let hasCode = false;
172
- let hasPackageFile = false;
173
- try {
174
- const files = execSync('find . -maxdepth 3 \\( -name "*.ts" -o -name "*.js" -o -name "*.py" -o -name "*.go" -o -name "*.rs" -o -name "*.swift" -o -name "*.java" \\) 2>/dev/null | grep -v node_modules | grep -v .git | head -5', {
175
- cwd,
176
- encoding: 'utf-8',
177
- stdio: ['pipe', 'pipe', 'pipe'],
178
- });
179
- hasCode = files.trim().length > 0;
180
- } catch {}
181
-
182
- hasPackageFile = pathExistsInternal(cwd, 'package.json') ||
183
- pathExistsInternal(cwd, 'requirements.txt') ||
184
- pathExistsInternal(cwd, 'Cargo.toml') ||
185
- pathExistsInternal(cwd, 'go.mod') ||
186
- pathExistsInternal(cwd, 'Package.swift');
187
-
188
- const result = {
189
- // Models
190
- researcher_model: resolveModelInternal(cwd, 'gsd-project-researcher'),
191
- synthesizer_model: resolveModelInternal(cwd, 'gsd-research-synthesizer'),
192
- roadmapper_model: resolveModelInternal(cwd, 'gsd-roadmapper'),
193
-
194
- // Config
195
- commit_docs: config.commit_docs,
196
-
197
- // Existing state
198
- project_exists: pathExistsInternal(cwd, '.planning/PROJECT.md'),
199
- has_codebase_map: pathExistsInternal(cwd, '.planning/codebase'),
200
- planning_exists: pathExistsInternal(cwd, '.planning'),
201
-
202
- // Brownfield detection
203
- has_existing_code: hasCode,
204
- has_package_file: hasPackageFile,
205
- is_brownfield: hasCode || hasPackageFile,
206
- needs_codebase_map: (hasCode || hasPackageFile) && !pathExistsInternal(cwd, '.planning/codebase'),
207
-
208
- // Git state
209
- has_git: pathExistsInternal(cwd, '.git'),
210
-
211
- // Enhanced search
212
- brave_search_available: hasBraveSearch,
213
-
214
- // File paths
215
- project_path: '.planning/PROJECT.md',
216
- };
217
-
218
- output(result, raw);
219
- }
220
-
221
- function cmdInitNewMilestone(cwd, raw) {
222
- const config = loadConfig(cwd);
223
- const milestone = getMilestoneInfo(cwd);
224
-
225
- const result = {
226
- // Models
227
- researcher_model: resolveModelInternal(cwd, 'gsd-project-researcher'),
228
- synthesizer_model: resolveModelInternal(cwd, 'gsd-research-synthesizer'),
229
- roadmapper_model: resolveModelInternal(cwd, 'gsd-roadmapper'),
230
-
231
- // Config
232
- commit_docs: config.commit_docs,
233
- research_enabled: config.research,
234
-
235
- // Current milestone
236
- current_milestone: milestone.version,
237
- current_milestone_name: milestone.name,
238
-
239
- // File existence
240
- project_exists: pathExistsInternal(cwd, '.planning/PROJECT.md'),
241
- roadmap_exists: pathExistsInternal(cwd, '.planning/ROADMAP.md'),
242
- state_exists: pathExistsInternal(cwd, '.planning/STATE.md'),
243
-
244
- // File paths
245
- project_path: '.planning/PROJECT.md',
246
- roadmap_path: '.planning/ROADMAP.md',
247
- state_path: '.planning/STATE.md',
248
- };
249
-
250
- output(result, raw);
251
- }
252
-
253
- function cmdInitQuick(cwd, description, raw) {
254
- const config = loadConfig(cwd);
255
- const now = new Date();
256
- const slug = description ? generateSlugInternal(description)?.substring(0, 40) : null;
257
-
258
- // Generate collision-resistant quick task ID: YYMMDD-xxx
259
- // xxx = 2-second precision blocks since midnight, encoded as 3-char Base36 (lowercase)
260
- // Range: 000 (00:00:00) to xbz (23:59:58), guaranteed 3 chars for any time of day.
261
- // Provides ~2s uniqueness window per user — practically collision-free across a team.
262
- const yy = String(now.getFullYear()).slice(-2);
263
- const mm = String(now.getMonth() + 1).padStart(2, '0');
264
- const dd = String(now.getDate()).padStart(2, '0');
265
- const dateStr = yy + mm + dd;
266
- const secondsSinceMidnight = now.getHours() * 3600 + now.getMinutes() * 60 + now.getSeconds();
267
- const timeBlocks = Math.floor(secondsSinceMidnight / 2);
268
- const timeEncoded = timeBlocks.toString(36).padStart(3, '0');
269
- const quickId = dateStr + '-' + timeEncoded;
270
-
271
- const result = {
272
- // Models
273
- planner_model: resolveModelInternal(cwd, 'gsd-planner'),
274
- executor_model: resolveModelInternal(cwd, 'gsd-executor'),
275
- checker_model: resolveModelInternal(cwd, 'gsd-plan-checker'),
276
- verifier_model: resolveModelInternal(cwd, 'gsd-verifier'),
277
-
278
- // Config
279
- commit_docs: config.commit_docs,
280
-
281
- // Quick task info
282
- quick_id: quickId,
283
- slug: slug,
284
- description: description || null,
285
-
286
- // Timestamps
287
- date: now.toISOString().split('T')[0],
288
- timestamp: now.toISOString(),
289
-
290
- // Paths
291
- quick_dir: '.planning/quick',
292
- task_dir: slug ? `.planning/quick/${quickId}-${slug}` : null,
293
-
294
- // File existence
295
- roadmap_exists: pathExistsInternal(cwd, '.planning/ROADMAP.md'),
296
- planning_exists: pathExistsInternal(cwd, '.planning'),
297
-
298
- };
299
-
300
- output(result, raw);
301
- }
302
-
303
- function cmdInitResume(cwd, raw) {
304
- const config = loadConfig(cwd);
305
-
306
- // Check for interrupted agent
307
- let interruptedAgentId = null;
308
- try {
309
- interruptedAgentId = fs.readFileSync(path.join(cwd, '.planning', 'current-agent-id.txt'), 'utf-8').trim();
310
- } catch {}
311
-
312
- const result = {
313
- // File existence
314
- state_exists: pathExistsInternal(cwd, '.planning/STATE.md'),
315
- roadmap_exists: pathExistsInternal(cwd, '.planning/ROADMAP.md'),
316
- project_exists: pathExistsInternal(cwd, '.planning/PROJECT.md'),
317
- planning_exists: pathExistsInternal(cwd, '.planning'),
318
-
319
- // File paths
320
- state_path: '.planning/STATE.md',
321
- roadmap_path: '.planning/ROADMAP.md',
322
- project_path: '.planning/PROJECT.md',
323
-
324
- // Agent state
325
- has_interrupted_agent: !!interruptedAgentId,
326
- interrupted_agent_id: interruptedAgentId,
327
-
328
- // Config
329
- commit_docs: config.commit_docs,
330
- };
331
-
332
- output(result, raw);
333
- }
334
-
335
- function cmdInitVerifyWork(cwd, phase, raw) {
336
- if (!phase) {
337
- error('phase required for init verify-work');
338
- }
339
-
340
- const config = loadConfig(cwd);
341
- const phaseInfo = findPhaseInternal(cwd, phase);
342
-
343
- const result = {
344
- // Models
345
- planner_model: resolveModelInternal(cwd, 'gsd-planner'),
346
- checker_model: resolveModelInternal(cwd, 'gsd-plan-checker'),
347
-
348
- // Config
349
- commit_docs: config.commit_docs,
350
-
351
- // Phase info
352
- phase_found: !!phaseInfo,
353
- phase_dir: phaseInfo?.directory || null,
354
- phase_number: phaseInfo?.phase_number || null,
355
- phase_name: phaseInfo?.phase_name || null,
356
-
357
- // Existing artifacts
358
- has_verification: phaseInfo?.has_verification || false,
359
- };
360
-
361
- output(result, raw);
362
- }
363
-
364
- function cmdInitPhaseOp(cwd, phase, raw) {
365
- const config = loadConfig(cwd);
366
- let phaseInfo = findPhaseInternal(cwd, phase);
367
-
368
- // Fallback to ROADMAP.md if no directory exists (e.g., Plans: TBD)
369
- if (!phaseInfo) {
370
- const roadmapPhase = getRoadmapPhaseInternal(cwd, phase);
371
- if (roadmapPhase?.found) {
372
- const phaseName = roadmapPhase.phase_name;
373
- phaseInfo = {
374
- found: true,
375
- directory: null,
376
- phase_number: roadmapPhase.phase_number,
377
- phase_name: phaseName,
378
- phase_slug: phaseName ? phaseName.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '') : null,
379
- plans: [],
380
- summaries: [],
381
- incomplete_plans: [],
382
- has_research: false,
383
- has_context: false,
384
- has_verification: false,
385
- };
386
- }
387
- }
388
-
389
- const result = {
390
- // Config
391
- commit_docs: config.commit_docs,
392
- brave_search: config.brave_search,
393
-
394
- // Phase info
395
- phase_found: !!phaseInfo,
396
- phase_dir: phaseInfo?.directory || null,
397
- phase_number: phaseInfo?.phase_number || null,
398
- phase_name: phaseInfo?.phase_name || null,
399
- phase_slug: phaseInfo?.phase_slug || null,
400
- padded_phase: phaseInfo?.phase_number ? normalizePhaseName(phaseInfo.phase_number) : null,
401
-
402
- // Existing artifacts
403
- has_research: phaseInfo?.has_research || false,
404
- has_context: phaseInfo?.has_context || false,
405
- has_plans: (phaseInfo?.plans?.length || 0) > 0,
406
- has_verification: phaseInfo?.has_verification || false,
407
- plan_count: phaseInfo?.plans?.length || 0,
408
-
409
- // File existence
410
- roadmap_exists: pathExistsInternal(cwd, '.planning/ROADMAP.md'),
411
- planning_exists: pathExistsInternal(cwd, '.planning'),
412
-
413
- // File paths
414
- state_path: '.planning/STATE.md',
415
- roadmap_path: '.planning/ROADMAP.md',
416
- requirements_path: '.planning/REQUIREMENTS.md',
417
- };
418
-
419
- if (phaseInfo?.directory) {
420
- const phaseDirFull = path.join(cwd, phaseInfo.directory);
421
- try {
422
- const files = fs.readdirSync(phaseDirFull);
423
- const contextFile = files.find(f => f.endsWith('-CONTEXT.md') || f === 'CONTEXT.md');
424
- if (contextFile) {
425
- result.context_path = toPosixPath(path.join(phaseInfo.directory, contextFile));
426
- }
427
- const researchFile = files.find(f => f.endsWith('-RESEARCH.md') || f === 'RESEARCH.md');
428
- if (researchFile) {
429
- result.research_path = toPosixPath(path.join(phaseInfo.directory, researchFile));
430
- }
431
- const verificationFile = files.find(f => f.endsWith('-VERIFICATION.md') || f === 'VERIFICATION.md');
432
- if (verificationFile) {
433
- result.verification_path = toPosixPath(path.join(phaseInfo.directory, verificationFile));
434
- }
435
- const uatFile = files.find(f => f.endsWith('-UAT.md') || f === 'UAT.md');
436
- if (uatFile) {
437
- result.uat_path = toPosixPath(path.join(phaseInfo.directory, uatFile));
438
- }
439
- } catch {}
440
- }
441
-
442
- output(result, raw);
443
- }
444
-
445
- function cmdInitTodos(cwd, area, raw) {
446
- const config = loadConfig(cwd);
447
- const now = new Date();
448
-
449
- // List todos (reuse existing logic)
450
- const pendingDir = path.join(cwd, '.planning', 'todos', 'pending');
451
- let count = 0;
452
- const todos = [];
453
-
454
- try {
455
- const files = fs.readdirSync(pendingDir).filter(f => f.endsWith('.md'));
456
- for (const file of files) {
457
- try {
458
- const content = fs.readFileSync(path.join(pendingDir, file), 'utf-8');
459
- const createdMatch = content.match(/^created:\s*(.+)$/m);
460
- const titleMatch = content.match(/^title:\s*(.+)$/m);
461
- const areaMatch = content.match(/^area:\s*(.+)$/m);
462
- const todoArea = areaMatch ? areaMatch[1].trim() : 'general';
463
-
464
- if (area && todoArea !== area) continue;
465
-
466
- count++;
467
- todos.push({
468
- file,
469
- created: createdMatch ? createdMatch[1].trim() : 'unknown',
470
- title: titleMatch ? titleMatch[1].trim() : 'Untitled',
471
- area: todoArea,
472
- path: '.planning/todos/pending/' + file,
473
- });
474
- } catch {}
475
- }
476
- } catch {}
477
-
478
- const result = {
479
- // Config
480
- commit_docs: config.commit_docs,
481
-
482
- // Timestamps
483
- date: now.toISOString().split('T')[0],
484
- timestamp: now.toISOString(),
485
-
486
- // Todo inventory
487
- todo_count: count,
488
- todos,
489
- area_filter: area || null,
490
-
491
- // Paths
492
- pending_dir: '.planning/todos/pending',
493
- completed_dir: '.planning/todos/completed',
494
-
495
- // File existence
496
- planning_exists: pathExistsInternal(cwd, '.planning'),
497
- todos_dir_exists: pathExistsInternal(cwd, '.planning/todos'),
498
- pending_dir_exists: pathExistsInternal(cwd, '.planning/todos/pending'),
499
- };
500
-
501
- output(result, raw);
502
- }
503
-
504
- function cmdInitMilestoneOp(cwd, raw) {
505
- const config = loadConfig(cwd);
506
- const milestone = getMilestoneInfo(cwd);
507
-
508
- // Count phases
509
- let phaseCount = 0;
510
- let completedPhases = 0;
511
- const phasesDir = path.join(cwd, '.planning', 'phases');
512
- try {
513
- const entries = fs.readdirSync(phasesDir, { withFileTypes: true });
514
- const dirs = entries.filter(e => e.isDirectory()).map(e => e.name);
515
- phaseCount = dirs.length;
516
-
517
- // Count phases with summaries (completed)
518
- for (const dir of dirs) {
519
- try {
520
- const phaseFiles = fs.readdirSync(path.join(phasesDir, dir));
521
- const hasSummary = phaseFiles.some(f => f.endsWith('-SUMMARY.md') || f === 'SUMMARY.md');
522
- if (hasSummary) completedPhases++;
523
- } catch {}
524
- }
525
- } catch {}
526
-
527
- // Check archive
528
- const archiveDir = path.join(cwd, '.planning', 'archive');
529
- let archivedMilestones = [];
530
- try {
531
- archivedMilestones = fs.readdirSync(archiveDir, { withFileTypes: true })
532
- .filter(e => e.isDirectory())
533
- .map(e => e.name);
534
- } catch {}
535
-
536
- const result = {
537
- // Config
538
- commit_docs: config.commit_docs,
539
-
540
- // Current milestone
541
- milestone_version: milestone.version,
542
- milestone_name: milestone.name,
543
- milestone_slug: generateSlugInternal(milestone.name),
544
-
545
- // Phase counts
546
- phase_count: phaseCount,
547
- completed_phases: completedPhases,
548
- all_phases_complete: phaseCount > 0 && phaseCount === completedPhases,
549
-
550
- // Archive
551
- archived_milestones: archivedMilestones,
552
- archive_count: archivedMilestones.length,
553
-
554
- // File existence
555
- project_exists: pathExistsInternal(cwd, '.planning/PROJECT.md'),
556
- roadmap_exists: pathExistsInternal(cwd, '.planning/ROADMAP.md'),
557
- state_exists: pathExistsInternal(cwd, '.planning/STATE.md'),
558
- archive_exists: pathExistsInternal(cwd, '.planning/archive'),
559
- phases_dir_exists: pathExistsInternal(cwd, '.planning/phases'),
560
- };
561
-
562
- output(result, raw);
563
- }
564
-
565
- function cmdInitMapCodebase(cwd, raw) {
566
- const config = loadConfig(cwd);
567
-
568
- // Check for existing codebase maps
569
- const codebaseDir = path.join(cwd, '.planning', 'codebase');
570
- let existingMaps = [];
571
- try {
572
- existingMaps = fs.readdirSync(codebaseDir).filter(f => f.endsWith('.md'));
573
- } catch {}
574
-
575
- const result = {
576
- // Models
577
- mapper_model: resolveModelInternal(cwd, 'gsd-codebase-mapper'),
578
-
579
- // Config
580
- commit_docs: config.commit_docs,
581
- search_gitignored: config.search_gitignored,
582
- parallelization: config.parallelization,
583
-
584
- // Paths
585
- codebase_dir: '.planning/codebase',
586
-
587
- // Existing maps
588
- existing_maps: existingMaps,
589
- has_maps: existingMaps.length > 0,
590
-
591
- // File existence
592
- planning_exists: pathExistsInternal(cwd, '.planning'),
593
- codebase_dir_exists: pathExistsInternal(cwd, '.planning/codebase'),
594
- };
595
-
596
- output(result, raw);
597
- }
598
-
599
- function cmdInitProgress(cwd, raw) {
600
- const config = loadConfig(cwd);
601
- const milestone = getMilestoneInfo(cwd);
602
-
603
- // Analyze phases
604
- const phasesDir = path.join(cwd, '.planning', 'phases');
605
- const phases = [];
606
- let currentPhase = null;
607
- let nextPhase = null;
608
-
609
- try {
610
- const entries = fs.readdirSync(phasesDir, { withFileTypes: true });
611
- const dirs = entries.filter(e => e.isDirectory()).map(e => e.name).sort();
612
-
613
- for (const dir of dirs) {
614
- const match = dir.match(/^(\d+(?:\.\d+)*)-?(.*)/);
615
- const phaseNumber = match ? match[1] : dir;
616
- const phaseName = match && match[2] ? match[2] : null;
617
-
618
- const phasePath = path.join(phasesDir, dir);
619
- const phaseFiles = fs.readdirSync(phasePath);
620
-
621
- const plans = phaseFiles.filter(f => f.endsWith('-PLAN.md') || f === 'PLAN.md');
622
- const summaries = phaseFiles.filter(f => f.endsWith('-SUMMARY.md') || f === 'SUMMARY.md');
623
- const hasResearch = phaseFiles.some(f => f.endsWith('-RESEARCH.md') || f === 'RESEARCH.md');
624
-
625
- const status = summaries.length >= plans.length && plans.length > 0 ? 'complete' :
626
- plans.length > 0 ? 'in_progress' :
627
- hasResearch ? 'researched' : 'pending';
628
-
629
- const phaseInfo = {
630
- number: phaseNumber,
631
- name: phaseName,
632
- directory: '.planning/phases/' + dir,
633
- status,
634
- plan_count: plans.length,
635
- summary_count: summaries.length,
636
- has_research: hasResearch,
637
- };
638
-
639
- phases.push(phaseInfo);
640
-
641
- // Find current (first incomplete with plans) and next (first pending)
642
- if (!currentPhase && (status === 'in_progress' || status === 'researched')) {
643
- currentPhase = phaseInfo;
644
- }
645
- if (!nextPhase && status === 'pending') {
646
- nextPhase = phaseInfo;
647
- }
648
- }
649
- } catch {}
650
-
651
- // Check for paused work
652
- let pausedAt = null;
653
- try {
654
- const state = fs.readFileSync(path.join(cwd, '.planning', 'STATE.md'), 'utf-8');
655
- const pauseMatch = state.match(/\*\*Paused At:\*\*\s*(.+)/);
656
- if (pauseMatch) pausedAt = pauseMatch[1].trim();
657
- } catch {}
658
-
659
- const result = {
660
- // Models
661
- executor_model: resolveModelInternal(cwd, 'gsd-executor'),
662
- planner_model: resolveModelInternal(cwd, 'gsd-planner'),
663
-
664
- // Config
665
- commit_docs: config.commit_docs,
666
-
667
- // Milestone
668
- milestone_version: milestone.version,
669
- milestone_name: milestone.name,
670
-
671
- // Phase overview
672
- phases,
673
- phase_count: phases.length,
674
- completed_count: phases.filter(p => p.status === 'complete').length,
675
- in_progress_count: phases.filter(p => p.status === 'in_progress').length,
676
-
677
- // Current state
678
- current_phase: currentPhase,
679
- next_phase: nextPhase,
680
- paused_at: pausedAt,
681
- has_work_in_progress: !!currentPhase,
682
-
683
- // File existence
684
- project_exists: pathExistsInternal(cwd, '.planning/PROJECT.md'),
685
- roadmap_exists: pathExistsInternal(cwd, '.planning/ROADMAP.md'),
686
- state_exists: pathExistsInternal(cwd, '.planning/STATE.md'),
687
- // File paths
688
- state_path: '.planning/STATE.md',
689
- roadmap_path: '.planning/ROADMAP.md',
690
- project_path: '.planning/PROJECT.md',
691
- config_path: '.planning/config.json',
692
- };
693
-
694
- output(result, raw);
695
- }
696
-
697
- module.exports = {
698
- cmdInitExecutePhase,
699
- cmdInitPlanPhase,
700
- cmdInitNewProject,
701
- cmdInitNewMilestone,
702
- cmdInitQuick,
703
- cmdInitResume,
704
- cmdInitVerifyWork,
705
- cmdInitPhaseOp,
706
- cmdInitTodos,
707
- cmdInitMilestoneOp,
708
- cmdInitMapCodebase,
709
- cmdInitProgress,
710
- };
1
+ /**
2
+ * Init — Compound init commands for workflow bootstrapping
3
+ */
4
+
5
+ const fs = require('fs');
6
+ const path = require('path');
7
+ const { execSync } = require('child_process');
8
+ const { loadConfig, resolveModelInternal, findPhaseInternal, getRoadmapPhaseInternal, pathExistsInternal, generateSlugInternal, getMilestoneInfo, normalizePhaseName, toPosixPath, output, error } = require('./core.cjs');
9
+
10
+ function cmdInitExecutePhase(cwd, phase, raw) {
11
+ if (!phase) {
12
+ error('phase required for init execute-phase');
13
+ }
14
+
15
+ const config = loadConfig(cwd);
16
+ const phaseInfo = findPhaseInternal(cwd, phase);
17
+ const milestone = getMilestoneInfo(cwd);
18
+
19
+ const roadmapPhase = getRoadmapPhaseInternal(cwd, phase);
20
+ const reqMatch = roadmapPhase?.section?.match(/^\*\*Requirements\*\*:[^\S\n]*([^\n]*)$/m);
21
+ const reqExtracted = reqMatch
22
+ ? reqMatch[1].replace(/[\[\]]/g, '').split(',').map(s => s.trim()).filter(Boolean).join(', ')
23
+ : null;
24
+ const phase_req_ids = (reqExtracted && reqExtracted !== 'TBD') ? reqExtracted : null;
25
+
26
+ const result = {
27
+ // Models
28
+ executor_model: resolveModelInternal(cwd, 'gsd-executor'),
29
+ verifier_model: resolveModelInternal(cwd, 'gsd-verifier'),
30
+
31
+ // Config flags
32
+ commit_docs: config.commit_docs,
33
+ parallelization: config.parallelization,
34
+ branching_strategy: config.branching_strategy,
35
+ phase_branch_template: config.phase_branch_template,
36
+ milestone_branch_template: config.milestone_branch_template,
37
+ verifier_enabled: config.verifier,
38
+
39
+ // Phase info
40
+ phase_found: !!phaseInfo,
41
+ phase_dir: phaseInfo?.directory || null,
42
+ phase_number: phaseInfo?.phase_number || null,
43
+ phase_name: phaseInfo?.phase_name || null,
44
+ phase_slug: phaseInfo?.phase_slug || null,
45
+ phase_req_ids,
46
+
47
+ // Plan inventory
48
+ plans: phaseInfo?.plans || [],
49
+ summaries: phaseInfo?.summaries || [],
50
+ incomplete_plans: phaseInfo?.incomplete_plans || [],
51
+ plan_count: phaseInfo?.plans?.length || 0,
52
+ incomplete_count: phaseInfo?.incomplete_plans?.length || 0,
53
+
54
+ // Branch name (pre-computed)
55
+ branch_name: config.branching_strategy === 'phase' && phaseInfo
56
+ ? config.phase_branch_template
57
+ .replace('{phase}', phaseInfo.phase_number)
58
+ .replace('{slug}', phaseInfo.phase_slug || 'phase')
59
+ : config.branching_strategy === 'milestone'
60
+ ? config.milestone_branch_template
61
+ .replace('{milestone}', milestone.version)
62
+ .replace('{slug}', generateSlugInternal(milestone.name) || 'milestone')
63
+ : null,
64
+
65
+ // Milestone info
66
+ milestone_version: milestone.version,
67
+ milestone_name: milestone.name,
68
+ milestone_slug: generateSlugInternal(milestone.name),
69
+
70
+ // File existence
71
+ state_exists: pathExistsInternal(cwd, '.planning/STATE.md'),
72
+ roadmap_exists: pathExistsInternal(cwd, '.planning/ROADMAP.md'),
73
+ config_exists: pathExistsInternal(cwd, '.planning/config.json'),
74
+ // File paths
75
+ state_path: '.planning/STATE.md',
76
+ roadmap_path: '.planning/ROADMAP.md',
77
+ config_path: '.planning/config.json',
78
+ };
79
+
80
+ output(result, raw);
81
+ }
82
+
83
+ function cmdInitPlanPhase(cwd, phase, raw) {
84
+ if (!phase) {
85
+ error('phase required for init plan-phase');
86
+ }
87
+
88
+ const config = loadConfig(cwd);
89
+ const phaseInfo = findPhaseInternal(cwd, phase);
90
+
91
+ const roadmapPhase = getRoadmapPhaseInternal(cwd, phase);
92
+ const reqMatch = roadmapPhase?.section?.match(/^\*\*Requirements\*\*:[^\S\n]*([^\n]*)$/m);
93
+ const reqExtracted = reqMatch
94
+ ? reqMatch[1].replace(/[\[\]]/g, '').split(',').map(s => s.trim()).filter(Boolean).join(', ')
95
+ : null;
96
+ const phase_req_ids = (reqExtracted && reqExtracted !== 'TBD') ? reqExtracted : null;
97
+
98
+ const result = {
99
+ // Models
100
+ researcher_model: resolveModelInternal(cwd, 'gsd-phase-researcher'),
101
+ planner_model: resolveModelInternal(cwd, 'gsd-planner'),
102
+ checker_model: resolveModelInternal(cwd, 'gsd-plan-checker'),
103
+
104
+ // Workflow flags
105
+ research_enabled: config.research,
106
+ plan_checker_enabled: config.plan_checker,
107
+ nyquist_validation_enabled: config.nyquist_validation,
108
+ commit_docs: config.commit_docs,
109
+
110
+ // Phase info
111
+ phase_found: !!phaseInfo,
112
+ phase_dir: phaseInfo?.directory || null,
113
+ phase_number: phaseInfo?.phase_number || null,
114
+ phase_name: phaseInfo?.phase_name || null,
115
+ phase_slug: phaseInfo?.phase_slug || null,
116
+ padded_phase: phaseInfo?.phase_number ? normalizePhaseName(phaseInfo.phase_number) : null,
117
+ phase_req_ids,
118
+
119
+ // Existing artifacts
120
+ has_research: phaseInfo?.has_research || false,
121
+ has_context: phaseInfo?.has_context || false,
122
+ has_plans: (phaseInfo?.plans?.length || 0) > 0,
123
+ plan_count: phaseInfo?.plans?.length || 0,
124
+
125
+ // Environment
126
+ planning_exists: pathExistsInternal(cwd, '.planning'),
127
+ roadmap_exists: pathExistsInternal(cwd, '.planning/ROADMAP.md'),
128
+
129
+ // File paths
130
+ state_path: '.planning/STATE.md',
131
+ roadmap_path: '.planning/ROADMAP.md',
132
+ requirements_path: '.planning/REQUIREMENTS.md',
133
+ };
134
+
135
+ if (phaseInfo?.directory) {
136
+ // Find *-CONTEXT.md in phase directory
137
+ const phaseDirFull = path.join(cwd, phaseInfo.directory);
138
+ try {
139
+ const files = fs.readdirSync(phaseDirFull);
140
+ const contextFile = files.find(f => f.endsWith('-CONTEXT.md') || f === 'CONTEXT.md');
141
+ if (contextFile) {
142
+ result.context_path = toPosixPath(path.join(phaseInfo.directory, contextFile));
143
+ }
144
+ const researchFile = files.find(f => f.endsWith('-RESEARCH.md') || f === 'RESEARCH.md');
145
+ if (researchFile) {
146
+ result.research_path = toPosixPath(path.join(phaseInfo.directory, researchFile));
147
+ }
148
+ const verificationFile = files.find(f => f.endsWith('-VERIFICATION.md') || f === 'VERIFICATION.md');
149
+ if (verificationFile) {
150
+ result.verification_path = toPosixPath(path.join(phaseInfo.directory, verificationFile));
151
+ }
152
+ const uatFile = files.find(f => f.endsWith('-UAT.md') || f === 'UAT.md');
153
+ if (uatFile) {
154
+ result.uat_path = toPosixPath(path.join(phaseInfo.directory, uatFile));
155
+ }
156
+ } catch {}
157
+ }
158
+
159
+ output(result, raw);
160
+ }
161
+
162
+ function cmdInitNewProject(cwd, raw) {
163
+ const config = loadConfig(cwd);
164
+
165
+ // Detect Brave Search API key availability
166
+ const homedir = require('os').homedir();
167
+ const braveKeyFile = path.join(homedir, '.gsd', 'brave_api_key');
168
+ const hasBraveSearch = !!(process.env.BRAVE_API_KEY || fs.existsSync(braveKeyFile));
169
+
170
+ // Detect existing code
171
+ let hasCode = false;
172
+ let hasPackageFile = false;
173
+ try {
174
+ const files = execSync('find . -maxdepth 3 \\( -name "*.ts" -o -name "*.js" -o -name "*.py" -o -name "*.go" -o -name "*.rs" -o -name "*.swift" -o -name "*.java" \\) 2>/dev/null | grep -v node_modules | grep -v .git | head -5', {
175
+ cwd,
176
+ encoding: 'utf-8',
177
+ stdio: ['pipe', 'pipe', 'pipe'],
178
+ });
179
+ hasCode = files.trim().length > 0;
180
+ } catch {}
181
+
182
+ hasPackageFile = pathExistsInternal(cwd, 'package.json') ||
183
+ pathExistsInternal(cwd, 'requirements.txt') ||
184
+ pathExistsInternal(cwd, 'Cargo.toml') ||
185
+ pathExistsInternal(cwd, 'go.mod') ||
186
+ pathExistsInternal(cwd, 'Package.swift');
187
+
188
+ const result = {
189
+ // Models
190
+ researcher_model: resolveModelInternal(cwd, 'gsd-project-researcher'),
191
+ synthesizer_model: resolveModelInternal(cwd, 'gsd-research-synthesizer'),
192
+ roadmapper_model: resolveModelInternal(cwd, 'gsd-roadmapper'),
193
+
194
+ // Config
195
+ commit_docs: config.commit_docs,
196
+
197
+ // Existing state
198
+ project_exists: pathExistsInternal(cwd, '.planning/PROJECT.md'),
199
+ has_codebase_map: pathExistsInternal(cwd, '.planning/codebase'),
200
+ planning_exists: pathExistsInternal(cwd, '.planning'),
201
+
202
+ // Brownfield detection
203
+ has_existing_code: hasCode,
204
+ has_package_file: hasPackageFile,
205
+ is_brownfield: hasCode || hasPackageFile,
206
+ needs_codebase_map: (hasCode || hasPackageFile) && !pathExistsInternal(cwd, '.planning/codebase'),
207
+
208
+ // Git state
209
+ has_git: pathExistsInternal(cwd, '.git'),
210
+
211
+ // Enhanced search
212
+ brave_search_available: hasBraveSearch,
213
+
214
+ // File paths
215
+ project_path: '.planning/PROJECT.md',
216
+ };
217
+
218
+ output(result, raw);
219
+ }
220
+
221
+ function cmdInitNewMilestone(cwd, raw) {
222
+ const config = loadConfig(cwd);
223
+ const milestone = getMilestoneInfo(cwd);
224
+
225
+ const result = {
226
+ // Models
227
+ researcher_model: resolveModelInternal(cwd, 'gsd-project-researcher'),
228
+ synthesizer_model: resolveModelInternal(cwd, 'gsd-research-synthesizer'),
229
+ roadmapper_model: resolveModelInternal(cwd, 'gsd-roadmapper'),
230
+
231
+ // Config
232
+ commit_docs: config.commit_docs,
233
+ research_enabled: config.research,
234
+
235
+ // Current milestone
236
+ current_milestone: milestone.version,
237
+ current_milestone_name: milestone.name,
238
+
239
+ // File existence
240
+ project_exists: pathExistsInternal(cwd, '.planning/PROJECT.md'),
241
+ roadmap_exists: pathExistsInternal(cwd, '.planning/ROADMAP.md'),
242
+ state_exists: pathExistsInternal(cwd, '.planning/STATE.md'),
243
+
244
+ // File paths
245
+ project_path: '.planning/PROJECT.md',
246
+ roadmap_path: '.planning/ROADMAP.md',
247
+ state_path: '.planning/STATE.md',
248
+ };
249
+
250
+ output(result, raw);
251
+ }
252
+
253
+ function cmdInitQuick(cwd, description, raw) {
254
+ const config = loadConfig(cwd);
255
+ const now = new Date();
256
+ const slug = description ? generateSlugInternal(description)?.substring(0, 40) : null;
257
+
258
+ // Generate collision-resistant quick task ID: YYMMDD-xxx
259
+ // xxx = 2-second precision blocks since midnight, encoded as 3-char Base36 (lowercase)
260
+ // Range: 000 (00:00:00) to xbz (23:59:58), guaranteed 3 chars for any time of day.
261
+ // Provides ~2s uniqueness window per user — practically collision-free across a team.
262
+ const yy = String(now.getFullYear()).slice(-2);
263
+ const mm = String(now.getMonth() + 1).padStart(2, '0');
264
+ const dd = String(now.getDate()).padStart(2, '0');
265
+ const dateStr = yy + mm + dd;
266
+ const secondsSinceMidnight = now.getHours() * 3600 + now.getMinutes() * 60 + now.getSeconds();
267
+ const timeBlocks = Math.floor(secondsSinceMidnight / 2);
268
+ const timeEncoded = timeBlocks.toString(36).padStart(3, '0');
269
+ const quickId = dateStr + '-' + timeEncoded;
270
+
271
+ const result = {
272
+ // Models
273
+ planner_model: resolveModelInternal(cwd, 'gsd-planner'),
274
+ executor_model: resolveModelInternal(cwd, 'gsd-executor'),
275
+ checker_model: resolveModelInternal(cwd, 'gsd-plan-checker'),
276
+ verifier_model: resolveModelInternal(cwd, 'gsd-verifier'),
277
+
278
+ // Config
279
+ commit_docs: config.commit_docs,
280
+
281
+ // Quick task info
282
+ quick_id: quickId,
283
+ slug: slug,
284
+ description: description || null,
285
+
286
+ // Timestamps
287
+ date: now.toISOString().split('T')[0],
288
+ timestamp: now.toISOString(),
289
+
290
+ // Paths
291
+ quick_dir: '.planning/quick',
292
+ task_dir: slug ? `.planning/quick/${quickId}-${slug}` : null,
293
+
294
+ // File existence
295
+ roadmap_exists: pathExistsInternal(cwd, '.planning/ROADMAP.md'),
296
+ planning_exists: pathExistsInternal(cwd, '.planning'),
297
+
298
+ };
299
+
300
+ output(result, raw);
301
+ }
302
+
303
+ function cmdInitResume(cwd, raw) {
304
+ const config = loadConfig(cwd);
305
+
306
+ // Check for interrupted agent
307
+ let interruptedAgentId = null;
308
+ try {
309
+ interruptedAgentId = fs.readFileSync(path.join(cwd, '.planning', 'current-agent-id.txt'), 'utf-8').trim();
310
+ } catch {}
311
+
312
+ const result = {
313
+ // File existence
314
+ state_exists: pathExistsInternal(cwd, '.planning/STATE.md'),
315
+ roadmap_exists: pathExistsInternal(cwd, '.planning/ROADMAP.md'),
316
+ project_exists: pathExistsInternal(cwd, '.planning/PROJECT.md'),
317
+ planning_exists: pathExistsInternal(cwd, '.planning'),
318
+
319
+ // File paths
320
+ state_path: '.planning/STATE.md',
321
+ roadmap_path: '.planning/ROADMAP.md',
322
+ project_path: '.planning/PROJECT.md',
323
+
324
+ // Agent state
325
+ has_interrupted_agent: !!interruptedAgentId,
326
+ interrupted_agent_id: interruptedAgentId,
327
+
328
+ // Config
329
+ commit_docs: config.commit_docs,
330
+ };
331
+
332
+ output(result, raw);
333
+ }
334
+
335
+ function cmdInitVerifyWork(cwd, phase, raw) {
336
+ if (!phase) {
337
+ error('phase required for init verify-work');
338
+ }
339
+
340
+ const config = loadConfig(cwd);
341
+ const phaseInfo = findPhaseInternal(cwd, phase);
342
+
343
+ const result = {
344
+ // Models
345
+ planner_model: resolveModelInternal(cwd, 'gsd-planner'),
346
+ checker_model: resolveModelInternal(cwd, 'gsd-plan-checker'),
347
+
348
+ // Config
349
+ commit_docs: config.commit_docs,
350
+
351
+ // Phase info
352
+ phase_found: !!phaseInfo,
353
+ phase_dir: phaseInfo?.directory || null,
354
+ phase_number: phaseInfo?.phase_number || null,
355
+ phase_name: phaseInfo?.phase_name || null,
356
+
357
+ // Existing artifacts
358
+ has_verification: phaseInfo?.has_verification || false,
359
+ };
360
+
361
+ output(result, raw);
362
+ }
363
+
364
+ function cmdInitPhaseOp(cwd, phase, raw) {
365
+ const config = loadConfig(cwd);
366
+ let phaseInfo = findPhaseInternal(cwd, phase);
367
+
368
+ // Fallback to ROADMAP.md if no directory exists (e.g., Plans: TBD)
369
+ if (!phaseInfo) {
370
+ const roadmapPhase = getRoadmapPhaseInternal(cwd, phase);
371
+ if (roadmapPhase?.found) {
372
+ const phaseName = roadmapPhase.phase_name;
373
+ phaseInfo = {
374
+ found: true,
375
+ directory: null,
376
+ phase_number: roadmapPhase.phase_number,
377
+ phase_name: phaseName,
378
+ phase_slug: phaseName ? phaseName.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '') : null,
379
+ plans: [],
380
+ summaries: [],
381
+ incomplete_plans: [],
382
+ has_research: false,
383
+ has_context: false,
384
+ has_verification: false,
385
+ };
386
+ }
387
+ }
388
+
389
+ const result = {
390
+ // Config
391
+ commit_docs: config.commit_docs,
392
+ brave_search: config.brave_search,
393
+
394
+ // Phase info
395
+ phase_found: !!phaseInfo,
396
+ phase_dir: phaseInfo?.directory || null,
397
+ phase_number: phaseInfo?.phase_number || null,
398
+ phase_name: phaseInfo?.phase_name || null,
399
+ phase_slug: phaseInfo?.phase_slug || null,
400
+ padded_phase: phaseInfo?.phase_number ? normalizePhaseName(phaseInfo.phase_number) : null,
401
+
402
+ // Existing artifacts
403
+ has_research: phaseInfo?.has_research || false,
404
+ has_context: phaseInfo?.has_context || false,
405
+ has_plans: (phaseInfo?.plans?.length || 0) > 0,
406
+ has_verification: phaseInfo?.has_verification || false,
407
+ plan_count: phaseInfo?.plans?.length || 0,
408
+
409
+ // File existence
410
+ roadmap_exists: pathExistsInternal(cwd, '.planning/ROADMAP.md'),
411
+ planning_exists: pathExistsInternal(cwd, '.planning'),
412
+
413
+ // File paths
414
+ state_path: '.planning/STATE.md',
415
+ roadmap_path: '.planning/ROADMAP.md',
416
+ requirements_path: '.planning/REQUIREMENTS.md',
417
+ };
418
+
419
+ if (phaseInfo?.directory) {
420
+ const phaseDirFull = path.join(cwd, phaseInfo.directory);
421
+ try {
422
+ const files = fs.readdirSync(phaseDirFull);
423
+ const contextFile = files.find(f => f.endsWith('-CONTEXT.md') || f === 'CONTEXT.md');
424
+ if (contextFile) {
425
+ result.context_path = toPosixPath(path.join(phaseInfo.directory, contextFile));
426
+ }
427
+ const researchFile = files.find(f => f.endsWith('-RESEARCH.md') || f === 'RESEARCH.md');
428
+ if (researchFile) {
429
+ result.research_path = toPosixPath(path.join(phaseInfo.directory, researchFile));
430
+ }
431
+ const verificationFile = files.find(f => f.endsWith('-VERIFICATION.md') || f === 'VERIFICATION.md');
432
+ if (verificationFile) {
433
+ result.verification_path = toPosixPath(path.join(phaseInfo.directory, verificationFile));
434
+ }
435
+ const uatFile = files.find(f => f.endsWith('-UAT.md') || f === 'UAT.md');
436
+ if (uatFile) {
437
+ result.uat_path = toPosixPath(path.join(phaseInfo.directory, uatFile));
438
+ }
439
+ } catch {}
440
+ }
441
+
442
+ output(result, raw);
443
+ }
444
+
445
+ function cmdInitTodos(cwd, area, raw) {
446
+ const config = loadConfig(cwd);
447
+ const now = new Date();
448
+
449
+ // List todos (reuse existing logic)
450
+ const pendingDir = path.join(cwd, '.planning', 'todos', 'pending');
451
+ let count = 0;
452
+ const todos = [];
453
+
454
+ try {
455
+ const files = fs.readdirSync(pendingDir).filter(f => f.endsWith('.md'));
456
+ for (const file of files) {
457
+ try {
458
+ const content = fs.readFileSync(path.join(pendingDir, file), 'utf-8');
459
+ const createdMatch = content.match(/^created:\s*(.+)$/m);
460
+ const titleMatch = content.match(/^title:\s*(.+)$/m);
461
+ const areaMatch = content.match(/^area:\s*(.+)$/m);
462
+ const todoArea = areaMatch ? areaMatch[1].trim() : 'general';
463
+
464
+ if (area && todoArea !== area) continue;
465
+
466
+ count++;
467
+ todos.push({
468
+ file,
469
+ created: createdMatch ? createdMatch[1].trim() : 'unknown',
470
+ title: titleMatch ? titleMatch[1].trim() : 'Untitled',
471
+ area: todoArea,
472
+ path: '.planning/todos/pending/' + file,
473
+ });
474
+ } catch {}
475
+ }
476
+ } catch {}
477
+
478
+ const result = {
479
+ // Config
480
+ commit_docs: config.commit_docs,
481
+
482
+ // Timestamps
483
+ date: now.toISOString().split('T')[0],
484
+ timestamp: now.toISOString(),
485
+
486
+ // Todo inventory
487
+ todo_count: count,
488
+ todos,
489
+ area_filter: area || null,
490
+
491
+ // Paths
492
+ pending_dir: '.planning/todos/pending',
493
+ completed_dir: '.planning/todos/completed',
494
+
495
+ // File existence
496
+ planning_exists: pathExistsInternal(cwd, '.planning'),
497
+ todos_dir_exists: pathExistsInternal(cwd, '.planning/todos'),
498
+ pending_dir_exists: pathExistsInternal(cwd, '.planning/todos/pending'),
499
+ };
500
+
501
+ output(result, raw);
502
+ }
503
+
504
+ function cmdInitMilestoneOp(cwd, raw) {
505
+ const config = loadConfig(cwd);
506
+ const milestone = getMilestoneInfo(cwd);
507
+
508
+ // Count phases
509
+ let phaseCount = 0;
510
+ let completedPhases = 0;
511
+ const phasesDir = path.join(cwd, '.planning', 'phases');
512
+ try {
513
+ const entries = fs.readdirSync(phasesDir, { withFileTypes: true });
514
+ const dirs = entries.filter(e => e.isDirectory()).map(e => e.name);
515
+ phaseCount = dirs.length;
516
+
517
+ // Count phases with summaries (completed)
518
+ for (const dir of dirs) {
519
+ try {
520
+ const phaseFiles = fs.readdirSync(path.join(phasesDir, dir));
521
+ const hasSummary = phaseFiles.some(f => f.endsWith('-SUMMARY.md') || f === 'SUMMARY.md');
522
+ if (hasSummary) completedPhases++;
523
+ } catch {}
524
+ }
525
+ } catch {}
526
+
527
+ // Check archive
528
+ const archiveDir = path.join(cwd, '.planning', 'archive');
529
+ let archivedMilestones = [];
530
+ try {
531
+ archivedMilestones = fs.readdirSync(archiveDir, { withFileTypes: true })
532
+ .filter(e => e.isDirectory())
533
+ .map(e => e.name);
534
+ } catch {}
535
+
536
+ const result = {
537
+ // Config
538
+ commit_docs: config.commit_docs,
539
+
540
+ // Current milestone
541
+ milestone_version: milestone.version,
542
+ milestone_name: milestone.name,
543
+ milestone_slug: generateSlugInternal(milestone.name),
544
+
545
+ // Phase counts
546
+ phase_count: phaseCount,
547
+ completed_phases: completedPhases,
548
+ all_phases_complete: phaseCount > 0 && phaseCount === completedPhases,
549
+
550
+ // Archive
551
+ archived_milestones: archivedMilestones,
552
+ archive_count: archivedMilestones.length,
553
+
554
+ // File existence
555
+ project_exists: pathExistsInternal(cwd, '.planning/PROJECT.md'),
556
+ roadmap_exists: pathExistsInternal(cwd, '.planning/ROADMAP.md'),
557
+ state_exists: pathExistsInternal(cwd, '.planning/STATE.md'),
558
+ archive_exists: pathExistsInternal(cwd, '.planning/archive'),
559
+ phases_dir_exists: pathExistsInternal(cwd, '.planning/phases'),
560
+ };
561
+
562
+ output(result, raw);
563
+ }
564
+
565
+ function cmdInitMapCodebase(cwd, raw) {
566
+ const config = loadConfig(cwd);
567
+
568
+ // Check for existing codebase maps
569
+ const codebaseDir = path.join(cwd, '.planning', 'codebase');
570
+ let existingMaps = [];
571
+ try {
572
+ existingMaps = fs.readdirSync(codebaseDir).filter(f => f.endsWith('.md'));
573
+ } catch {}
574
+
575
+ const result = {
576
+ // Models
577
+ mapper_model: resolveModelInternal(cwd, 'gsd-codebase-mapper'),
578
+
579
+ // Config
580
+ commit_docs: config.commit_docs,
581
+ search_gitignored: config.search_gitignored,
582
+ parallelization: config.parallelization,
583
+
584
+ // Paths
585
+ codebase_dir: '.planning/codebase',
586
+
587
+ // Existing maps
588
+ existing_maps: existingMaps,
589
+ has_maps: existingMaps.length > 0,
590
+
591
+ // File existence
592
+ planning_exists: pathExistsInternal(cwd, '.planning'),
593
+ codebase_dir_exists: pathExistsInternal(cwd, '.planning/codebase'),
594
+ };
595
+
596
+ output(result, raw);
597
+ }
598
+
599
+ function cmdInitProgress(cwd, raw) {
600
+ const config = loadConfig(cwd);
601
+ const milestone = getMilestoneInfo(cwd);
602
+
603
+ // Analyze phases
604
+ const phasesDir = path.join(cwd, '.planning', 'phases');
605
+ const phases = [];
606
+ let currentPhase = null;
607
+ let nextPhase = null;
608
+
609
+ try {
610
+ const entries = fs.readdirSync(phasesDir, { withFileTypes: true });
611
+ const dirs = entries.filter(e => e.isDirectory()).map(e => e.name).sort();
612
+
613
+ for (const dir of dirs) {
614
+ const match = dir.match(/^(\d+(?:\.\d+)*)-?(.*)/);
615
+ const phaseNumber = match ? match[1] : dir;
616
+ const phaseName = match && match[2] ? match[2] : null;
617
+
618
+ const phasePath = path.join(phasesDir, dir);
619
+ const phaseFiles = fs.readdirSync(phasePath);
620
+
621
+ const plans = phaseFiles.filter(f => f.endsWith('-PLAN.md') || f === 'PLAN.md');
622
+ const summaries = phaseFiles.filter(f => f.endsWith('-SUMMARY.md') || f === 'SUMMARY.md');
623
+ const hasResearch = phaseFiles.some(f => f.endsWith('-RESEARCH.md') || f === 'RESEARCH.md');
624
+
625
+ const status = summaries.length >= plans.length && plans.length > 0 ? 'complete' :
626
+ plans.length > 0 ? 'in_progress' :
627
+ hasResearch ? 'researched' : 'pending';
628
+
629
+ const phaseInfo = {
630
+ number: phaseNumber,
631
+ name: phaseName,
632
+ directory: '.planning/phases/' + dir,
633
+ status,
634
+ plan_count: plans.length,
635
+ summary_count: summaries.length,
636
+ has_research: hasResearch,
637
+ };
638
+
639
+ phases.push(phaseInfo);
640
+
641
+ // Find current (first incomplete with plans) and next (first pending)
642
+ if (!currentPhase && (status === 'in_progress' || status === 'researched')) {
643
+ currentPhase = phaseInfo;
644
+ }
645
+ if (!nextPhase && status === 'pending') {
646
+ nextPhase = phaseInfo;
647
+ }
648
+ }
649
+ } catch {}
650
+
651
+ // Check for paused work
652
+ let pausedAt = null;
653
+ try {
654
+ const state = fs.readFileSync(path.join(cwd, '.planning', 'STATE.md'), 'utf-8');
655
+ const pauseMatch = state.match(/\*\*Paused At:\*\*\s*(.+)/);
656
+ if (pauseMatch) pausedAt = pauseMatch[1].trim();
657
+ } catch {}
658
+
659
+ const result = {
660
+ // Models
661
+ executor_model: resolveModelInternal(cwd, 'gsd-executor'),
662
+ planner_model: resolveModelInternal(cwd, 'gsd-planner'),
663
+
664
+ // Config
665
+ commit_docs: config.commit_docs,
666
+
667
+ // Milestone
668
+ milestone_version: milestone.version,
669
+ milestone_name: milestone.name,
670
+
671
+ // Phase overview
672
+ phases,
673
+ phase_count: phases.length,
674
+ completed_count: phases.filter(p => p.status === 'complete').length,
675
+ in_progress_count: phases.filter(p => p.status === 'in_progress').length,
676
+
677
+ // Current state
678
+ current_phase: currentPhase,
679
+ next_phase: nextPhase,
680
+ paused_at: pausedAt,
681
+ has_work_in_progress: !!currentPhase,
682
+
683
+ // File existence
684
+ project_exists: pathExistsInternal(cwd, '.planning/PROJECT.md'),
685
+ roadmap_exists: pathExistsInternal(cwd, '.planning/ROADMAP.md'),
686
+ state_exists: pathExistsInternal(cwd, '.planning/STATE.md'),
687
+ // File paths
688
+ state_path: '.planning/STATE.md',
689
+ roadmap_path: '.planning/ROADMAP.md',
690
+ project_path: '.planning/PROJECT.md',
691
+ config_path: '.planning/config.json',
692
+ };
693
+
694
+ output(result, raw);
695
+ }
696
+
697
+ module.exports = {
698
+ cmdInitExecutePhase,
699
+ cmdInitPlanPhase,
700
+ cmdInitNewProject,
701
+ cmdInitNewMilestone,
702
+ cmdInitQuick,
703
+ cmdInitResume,
704
+ cmdInitVerifyWork,
705
+ cmdInitPhaseOp,
706
+ cmdInitTodos,
707
+ cmdInitMilestoneOp,
708
+ cmdInitMapCodebase,
709
+ cmdInitProgress,
710
+ };