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