@howlil/ez-agents 2.0.0 → 3.0.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 (145) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +157 -110
  3. package/README.zh-CN.md +84 -84
  4. package/agents/ez-plan-checker.md +2 -2
  5. package/agents/ez-research-synthesizer.md +1 -1
  6. package/agents/ez-ui-auditor.md +0 -2
  7. package/agents/ez-ui-checker.md +2 -4
  8. package/agents/ez-ui-researcher.md +0 -2
  9. package/agents/ez-verifier.md +1 -1
  10. package/bin/install.js +211 -211
  11. package/commands/ez/debug.md +1 -1
  12. package/commands/ez/map-codebase.md +1 -1
  13. package/commands/ez/reapply-patches.md +3 -3
  14. package/commands/ez/research-phase.md +1 -1
  15. package/{get-shit-done → ez-agents}/bin/ez-tools.cjs +1 -1
  16. package/{get-shit-done → ez-agents}/bin/lib/assistant-adapter.cjs +205 -205
  17. package/{get-shit-done → ez-agents}/bin/lib/audit-exec.cjs +150 -150
  18. package/{get-shit-done → ez-agents}/bin/lib/auth.cjs +175 -175
  19. package/{get-shit-done → ez-agents}/bin/lib/circuit-breaker.cjs +118 -118
  20. package/{get-shit-done → ez-agents}/bin/lib/commands.cjs +666 -666
  21. package/{get-shit-done → ez-agents}/bin/lib/config.cjs +183 -183
  22. package/{get-shit-done → ez-agents}/bin/lib/core.cjs +495 -495
  23. package/{get-shit-done → ez-agents}/bin/lib/file-lock.cjs +236 -236
  24. package/{get-shit-done → ez-agents}/bin/lib/frontmatter.cjs +299 -299
  25. package/{get-shit-done → ez-agents}/bin/lib/fs-utils.cjs +153 -153
  26. package/{get-shit-done → ez-agents}/bin/lib/git-utils.cjs +203 -203
  27. package/{get-shit-done → ez-agents}/bin/lib/health-check.cjs +163 -163
  28. package/{get-shit-done → ez-agents}/bin/lib/index.cjs +113 -113
  29. package/{get-shit-done → ez-agents}/bin/lib/init.cjs +710 -710
  30. package/{get-shit-done → ez-agents}/bin/lib/logger.cjs +117 -117
  31. package/{get-shit-done → ez-agents}/bin/lib/milestone.cjs +241 -241
  32. package/{get-shit-done → ez-agents}/bin/lib/model-provider.cjs +146 -146
  33. package/{get-shit-done → ez-agents}/bin/lib/phase.cjs +908 -908
  34. package/{get-shit-done → ez-agents}/bin/lib/retry.cjs +119 -119
  35. package/{get-shit-done → ez-agents}/bin/lib/roadmap.cjs +305 -305
  36. package/{get-shit-done → ez-agents}/bin/lib/safe-exec.cjs +128 -128
  37. package/{get-shit-done → ez-agents}/bin/lib/safe-path.cjs +130 -130
  38. package/{get-shit-done → ez-agents}/bin/lib/state.cjs +721 -721
  39. package/{get-shit-done → ez-agents}/bin/lib/temp-file.cjs +239 -239
  40. package/{get-shit-done → ez-agents}/bin/lib/template.cjs +222 -222
  41. package/{get-shit-done → ez-agents}/bin/lib/test-file-lock.cjs +112 -112
  42. package/{get-shit-done → ez-agents}/bin/lib/test-graceful.cjs +93 -93
  43. package/{get-shit-done → ez-agents}/bin/lib/test-logger.cjs +60 -60
  44. package/{get-shit-done → ez-agents}/bin/lib/test-safe-exec.cjs +38 -38
  45. package/{get-shit-done → ez-agents}/bin/lib/test-safe-path.cjs +33 -33
  46. package/{get-shit-done → ez-agents}/bin/lib/test-temp-file.cjs +125 -125
  47. package/{get-shit-done → ez-agents}/bin/lib/timeout-exec.cjs +62 -62
  48. package/{get-shit-done → ez-agents}/bin/lib/verify.cjs +820 -820
  49. package/{get-shit-done → ez-agents}/references/checkpoints.md +776 -776
  50. package/{get-shit-done → ez-agents}/references/questioning.md +162 -162
  51. package/{get-shit-done → ez-agents}/references/tdd.md +263 -263
  52. package/{get-shit-done → ez-agents}/templates/codebase/concerns.md +310 -310
  53. package/{get-shit-done → ez-agents}/templates/codebase/conventions.md +307 -307
  54. package/{get-shit-done → ez-agents}/templates/codebase/integrations.md +280 -280
  55. package/{get-shit-done → ez-agents}/templates/codebase/stack.md +186 -186
  56. package/{get-shit-done → ez-agents}/templates/codebase/testing.md +480 -480
  57. package/{get-shit-done → ez-agents}/templates/config.json +37 -37
  58. package/{get-shit-done → ez-agents}/templates/continue-here.md +78 -78
  59. package/{get-shit-done → ez-agents}/templates/milestone-archive.md +123 -123
  60. package/{get-shit-done → ez-agents}/templates/milestone.md +115 -115
  61. package/{get-shit-done → ez-agents}/templates/requirements.md +231 -231
  62. package/{get-shit-done → ez-agents}/templates/research-project/ARCHITECTURE.md +204 -204
  63. package/{get-shit-done → ez-agents}/templates/research-project/FEATURES.md +147 -147
  64. package/{get-shit-done → ez-agents}/templates/research-project/PITFALLS.md +200 -200
  65. package/{get-shit-done → ez-agents}/templates/research-project/STACK.md +120 -120
  66. package/{get-shit-done → ez-agents}/templates/research-project/SUMMARY.md +170 -170
  67. package/{get-shit-done → ez-agents}/templates/retrospective.md +54 -54
  68. package/{get-shit-done → ez-agents}/templates/roadmap.md +202 -202
  69. package/{get-shit-done → ez-agents}/templates/summary-minimal.md +41 -41
  70. package/{get-shit-done → ez-agents}/templates/summary-standard.md +48 -48
  71. package/{get-shit-done → ez-agents}/templates/summary.md +248 -248
  72. package/{get-shit-done → ez-agents}/templates/user-setup.md +311 -311
  73. package/{get-shit-done → ez-agents}/templates/verification-report.md +322 -322
  74. package/{get-shit-done → ez-agents}/workflows/add-phase.md +112 -112
  75. package/{get-shit-done → ez-agents}/workflows/add-tests.md +351 -351
  76. package/{get-shit-done → ez-agents}/workflows/add-todo.md +158 -158
  77. package/{get-shit-done → ez-agents}/workflows/audit-milestone.md +332 -332
  78. package/{get-shit-done → ez-agents}/workflows/autonomous.md +743 -743
  79. package/{get-shit-done → ez-agents}/workflows/check-todos.md +177 -177
  80. package/{get-shit-done → ez-agents}/workflows/cleanup.md +152 -152
  81. package/{get-shit-done → ez-agents}/workflows/complete-milestone.md +766 -766
  82. package/ez-agents/workflows/debug.md +0 -0
  83. package/{get-shit-done → ez-agents}/workflows/diagnose-issues.md +219 -219
  84. package/{get-shit-done → ez-agents}/workflows/discovery-phase.md +289 -289
  85. package/{get-shit-done → ez-agents}/workflows/discuss-phase.md +762 -762
  86. package/{get-shit-done → ez-agents}/workflows/execute-phase.md +468 -468
  87. package/{get-shit-done → ez-agents}/workflows/execute-plan.md +483 -483
  88. package/{get-shit-done → ez-agents}/workflows/health.md +159 -159
  89. package/{get-shit-done → ez-agents}/workflows/help.md +492 -492
  90. package/{get-shit-done → ez-agents}/workflows/insert-phase.md +130 -130
  91. package/{get-shit-done → ez-agents}/workflows/list-phase-assumptions.md +178 -178
  92. package/{get-shit-done → ez-agents}/workflows/map-codebase.md +316 -316
  93. package/{get-shit-done → ez-agents}/workflows/new-milestone.md +384 -384
  94. package/{get-shit-done → ez-agents}/workflows/new-project.md +1111 -1111
  95. package/{get-shit-done → ez-agents}/workflows/node-repair.md +92 -92
  96. package/{get-shit-done → ez-agents}/workflows/pause-work.md +122 -122
  97. package/{get-shit-done → ez-agents}/workflows/plan-milestone-gaps.md +274 -274
  98. package/{get-shit-done → ez-agents}/workflows/plan-phase.md +651 -651
  99. package/{get-shit-done → ez-agents}/workflows/progress.md +382 -382
  100. package/{get-shit-done → ez-agents}/workflows/quick.md +610 -610
  101. package/{get-shit-done → ez-agents}/workflows/remove-phase.md +155 -155
  102. package/{get-shit-done → ez-agents}/workflows/research-phase.md +74 -74
  103. package/{get-shit-done → ez-agents}/workflows/resume-project.md +307 -307
  104. package/{get-shit-done → ez-agents}/workflows/set-profile.md +81 -81
  105. package/{get-shit-done → ez-agents}/workflows/settings.md +242 -242
  106. package/{get-shit-done → ez-agents}/workflows/stats.md +57 -57
  107. package/{get-shit-done → ez-agents}/workflows/transition.md +544 -544
  108. package/{get-shit-done → ez-agents}/workflows/ui-phase.md +290 -290
  109. package/{get-shit-done → ez-agents}/workflows/ui-review.md +157 -157
  110. package/{get-shit-done → ez-agents}/workflows/update.md +320 -320
  111. package/{get-shit-done → ez-agents}/workflows/validate-phase.md +167 -167
  112. package/{get-shit-done → ez-agents}/workflows/verify-phase.md +243 -243
  113. package/{get-shit-done → ez-agents}/workflows/verify-work.md +5 -5
  114. package/hooks/dist/ez-check-update.js +81 -0
  115. package/hooks/dist/ez-context-monitor.js +141 -0
  116. package/hooks/dist/ez-statusline.js +115 -0
  117. package/package.json +13 -3
  118. package/scripts/build-hooks.js +43 -43
  119. package/scripts/run-tests.cjs +29 -29
  120. /package/{get-shit-done → ez-agents}/references/continuation-format.md +0 -0
  121. /package/{get-shit-done → ez-agents}/references/decimal-phase-calculation.md +0 -0
  122. /package/{get-shit-done → ez-agents}/references/git-integration.md +0 -0
  123. /package/{get-shit-done → ez-agents}/references/git-planning-commit.md +0 -0
  124. /package/{get-shit-done → ez-agents}/references/model-profile-resolution.md +0 -0
  125. /package/{get-shit-done → ez-agents}/references/model-profiles.md +0 -0
  126. /package/{get-shit-done → ez-agents}/references/phase-argument-parsing.md +0 -0
  127. /package/{get-shit-done → ez-agents}/references/planning-config.md +0 -0
  128. /package/{get-shit-done → ez-agents}/references/ui-brand.md +0 -0
  129. /package/{get-shit-done → ez-agents}/references/verification-patterns.md +0 -0
  130. /package/{get-shit-done → ez-agents}/templates/DEBUG.md +0 -0
  131. /package/{get-shit-done → ez-agents}/templates/UAT.md +0 -0
  132. /package/{get-shit-done → ez-agents}/templates/UI-SPEC.md +0 -0
  133. /package/{get-shit-done → ez-agents}/templates/VALIDATION.md +0 -0
  134. /package/{get-shit-done → ez-agents}/templates/codebase/architecture.md +0 -0
  135. /package/{get-shit-done → ez-agents}/templates/codebase/structure.md +0 -0
  136. /package/{get-shit-done → ez-agents}/templates/context.md +0 -0
  137. /package/{get-shit-done → ez-agents}/templates/copilot-instructions.md +0 -0
  138. /package/{get-shit-done → ez-agents}/templates/debug-subagent-prompt.md +0 -0
  139. /package/{get-shit-done → ez-agents}/templates/discovery.md +0 -0
  140. /package/{get-shit-done → ez-agents}/templates/phase-prompt.md +0 -0
  141. /package/{get-shit-done → ez-agents}/templates/planner-subagent-prompt.md +0 -0
  142. /package/{get-shit-done → ez-agents}/templates/project.md +0 -0
  143. /package/{get-shit-done → ez-agents}/templates/research.md +0 -0
  144. /package/{get-shit-done → ez-agents}/templates/state.md +0 -0
  145. /package/{get-shit-done → ez-agents}/templates/summary-complex.md +0 -0
@@ -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, 'ez-executor'),
29
+ verifier_model: resolveModelInternal(cwd, 'ez-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, 'ez-phase-researcher'),
101
+ planner_model: resolveModelInternal(cwd, 'ez-planner'),
102
+ checker_model: resolveModelInternal(cwd, 'ez-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, 'ez-project-researcher'),
191
+ synthesizer_model: resolveModelInternal(cwd, 'ez-research-synthesizer'),
192
+ roadmapper_model: resolveModelInternal(cwd, 'ez-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, 'ez-project-researcher'),
228
+ synthesizer_model: resolveModelInternal(cwd, 'ez-research-synthesizer'),
229
+ roadmapper_model: resolveModelInternal(cwd, 'ez-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, 'ez-planner'),
274
+ executor_model: resolveModelInternal(cwd, 'ez-executor'),
275
+ checker_model: resolveModelInternal(cwd, 'ez-plan-checker'),
276
+ verifier_model: resolveModelInternal(cwd, 'ez-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, 'ez-planner'),
346
+ checker_model: resolveModelInternal(cwd, 'ez-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, 'ez-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, 'ez-executor'),
662
+ planner_model: resolveModelInternal(cwd, 'ez-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
+ };