@mthanhlm/autodev 0.4.4 → 0.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.
Files changed (75) hide show
  1. package/.claude-plugin/plugin.json +2 -2
  2. package/PUBLISH.md +9 -40
  3. package/README.md +70 -104
  4. package/autodev/bin/autodev-tools.cjs +521 -990
  5. package/autodev/templates/brief.md +19 -0
  6. package/autodev/templates/context.md +16 -0
  7. package/autodev/templates/plan.md +26 -46
  8. package/autodev/templates/run.md +20 -0
  9. package/bin/install.js +219 -422
  10. package/commands/autodev/index.md +117 -9
  11. package/commands/autodev/status.md +22 -0
  12. package/hooks/autodev-auto-format.js +3 -3
  13. package/hooks/autodev-git-guard.js +5 -7
  14. package/hooks/autodev-paths.js +3 -3
  15. package/package.json +4 -5
  16. package/scripts/run-tests.cjs +10 -0
  17. package/agents/autodev-codebase-domain.md +0 -25
  18. package/agents/autodev-codebase-quality.md +0 -25
  19. package/agents/autodev-codebase-runtime.md +0 -25
  20. package/agents/autodev-codebase-structure.md +0 -25
  21. package/agents/autodev-review-integration.md +0 -30
  22. package/agents/autodev-review-polish.md +0 -30
  23. package/agents/autodev-review-quality.md +0 -30
  24. package/agents/autodev-review-security.md +0 -30
  25. package/agents/autodev-task-worker.md +0 -39
  26. package/autodev/templates/codebase/domain.md +0 -13
  27. package/autodev/templates/codebase/quality.md +0 -13
  28. package/autodev/templates/codebase/runtime.md +0 -13
  29. package/autodev/templates/codebase/structure.md +0 -13
  30. package/autodev/templates/codebase/summary.md +0 -13
  31. package/autodev/templates/config.json +0 -22
  32. package/autodev/templates/project-state.md +0 -15
  33. package/autodev/templates/project.md +0 -24
  34. package/autodev/templates/requirements.md +0 -14
  35. package/autodev/templates/review.md +0 -27
  36. package/autodev/templates/roadmap.md +0 -17
  37. package/autodev/templates/state.md +0 -15
  38. package/autodev/templates/summary.md +0 -22
  39. package/autodev/templates/task-summary.md +0 -18
  40. package/autodev/templates/task.md +0 -23
  41. package/autodev/templates/track-state.md +0 -16
  42. package/autodev/templates/track.md +0 -24
  43. package/autodev/templates/uat.md +0 -18
  44. package/autodev/workflows/autodev-auto.md +0 -62
  45. package/autodev/workflows/autodev.md +0 -83
  46. package/autodev/workflows/cleanup.md +0 -51
  47. package/autodev/workflows/execute-phase.md +0 -144
  48. package/autodev/workflows/explore-codebase.md +0 -70
  49. package/autodev/workflows/help.md +0 -113
  50. package/autodev/workflows/new-project.md +0 -108
  51. package/autodev/workflows/plan-phase.md +0 -134
  52. package/autodev/workflows/progress.md +0 -18
  53. package/autodev/workflows/review-phase.md +0 -80
  54. package/autodev/workflows/review-plan.md +0 -55
  55. package/autodev/workflows/review-task.md +0 -70
  56. package/autodev/workflows/verify-work.md +0 -71
  57. package/commands/autodev/auto.md +0 -27
  58. package/commands/autodev/cleanup.md +0 -23
  59. package/commands/autodev/execute-phase.md +0 -29
  60. package/commands/autodev/explore-codebase.md +0 -33
  61. package/commands/autodev/help.md +0 -18
  62. package/commands/autodev/new-project.md +0 -30
  63. package/commands/autodev/plan-phase.md +0 -26
  64. package/commands/autodev/progress.md +0 -18
  65. package/commands/autodev/review-phase.md +0 -29
  66. package/commands/autodev/review-task.md +0 -25
  67. package/commands/autodev/verify-work.md +0 -24
  68. package/hooks/autodev-context-monitor.js +0 -59
  69. package/hooks/autodev-phase-boundary.sh +0 -49
  70. package/hooks/autodev-prompt-guard.js +0 -78
  71. package/hooks/autodev-read-guard.js +0 -42
  72. package/hooks/autodev-session-state.sh +0 -51
  73. package/hooks/autodev-statusline.js +0 -83
  74. package/hooks/autodev-workflow-guard.js +0 -43
  75. package/hooks/hooks.json +0 -89
@@ -3,34 +3,21 @@
3
3
  const fs = require('fs');
4
4
  const path = require('path');
5
5
 
6
- const DEFAULT_RUN_MODE = 'checkpointed';
7
- const AUTO_RETRY_LIMIT = 2;
8
-
9
- const DEFAULT_CONFIG = {
10
- project: {
11
- type: null
12
- },
13
- workflow: {
14
- research: false,
15
- review_after_execute: true
16
- },
17
- execution: {},
18
- git: {
19
- mode: 'read-only'
20
- },
21
- hooks: {
6
+ const DEFAULT_STATE = {
7
+ version: 1,
8
+ project_type: null,
9
+ stage: 'briefing',
10
+ current_item: null,
11
+ last_result: 'unverified',
12
+ last_run_at: null,
13
+ last_run_path: null,
14
+ settings: {
15
+ git_mode: 'read-only',
22
16
  auto_format: true,
23
- context_warnings: true,
24
- read_guard: true,
25
- workflow_guard: true,
26
- prompt_guard: true,
27
- git_guard: true,
28
- session_state: true,
29
- phase_boundary: true
17
+ background_tasks_disabled: true
30
18
  }
31
19
  };
32
20
 
33
- const CODEBASE_FILES = ['structure.md', 'domain.md', 'runtime.md', 'quality.md', 'summary.md'];
34
21
  const CODE_FILE_EXTENSIONS = new Set([
35
22
  '.js', '.jsx', '.cjs', '.mjs',
36
23
  '.ts', '.tsx',
@@ -51,8 +38,12 @@ const CODE_FILE_EXTENSIONS = new Set([
51
38
  '.kts',
52
39
  '.scala'
53
40
  ]);
41
+
54
42
  const NESTED_CODE_MARKER_FILES = new Set([
55
43
  'package.json',
44
+ 'package-lock.json',
45
+ 'pnpm-lock.yaml',
46
+ 'yarn.lock',
56
47
  'tsconfig.json',
57
48
  'jsconfig.json',
58
49
  'pyproject.toml',
@@ -64,6 +55,7 @@ const NESTED_CODE_MARKER_FILES = new Set([
64
55
  'build.gradle',
65
56
  'Dockerfile'
66
57
  ]);
58
+
67
59
  const IGNORED_SCAN_DIRS = new Set([
68
60
  '.autodev',
69
61
  '.claude',
@@ -76,47 +68,24 @@ const IGNORED_SCAN_DIRS = new Set([
76
68
  'vendor'
77
69
  ]);
78
70
 
79
- function findWorkspaceRoot(startDir) {
80
- let cursor = path.resolve(startDir || process.cwd());
81
- let gitRoot = null;
82
-
83
- while (true) {
84
- if (fileExists(path.join(cursor, '.autodev'))) {
85
- return cursor;
86
- }
87
-
88
- if (!gitRoot && fileExists(path.join(cursor, '.git'))) {
89
- gitRoot = cursor;
90
- }
91
-
92
- const parent = path.dirname(cursor);
93
- if (parent === cursor) {
94
- break;
95
- }
96
- cursor = parent;
71
+ function fileExists(filePath) {
72
+ try {
73
+ return fs.existsSync(filePath);
74
+ } catch {
75
+ return false;
97
76
  }
98
-
99
- return gitRoot || path.resolve(startDir || process.cwd());
100
77
  }
101
78
 
102
- function autodevDir(cwd) {
103
- return path.join(findWorkspaceRoot(cwd), '.autodev');
79
+ function isFile(filePath) {
80
+ try {
81
+ return fs.statSync(filePath).isFile();
82
+ } catch {
83
+ return false;
84
+ }
104
85
  }
105
86
 
106
- function rootPaths(cwd) {
107
- const workspaceRoot = findWorkspaceRoot(cwd);
108
- const root = autodevDir(cwd);
109
- return {
110
- workspaceRoot,
111
- root,
112
- config: path.join(root, 'config.json'),
113
- project: path.join(root, 'PROJECT.md'),
114
- state: path.join(root, 'STATE.md'),
115
- activeTrack: path.join(root, 'ACTIVE_TRACK'),
116
- codebaseDir: path.join(root, 'codebase'),
117
- codebaseSummary: path.join(root, 'codebase', 'summary.md'),
118
- tracksDir: path.join(root, 'tracks')
119
- };
87
+ function ensureDir(dirPath) {
88
+ fs.mkdirSync(dirPath, { recursive: true });
120
89
  }
121
90
 
122
91
  function readText(filePath) {
@@ -135,32 +104,59 @@ function readJson(filePath, fallback = null) {
135
104
  }
136
105
  }
137
106
 
138
- function fileExists(filePath) {
139
- try {
140
- return fs.existsSync(filePath);
141
- } catch {
142
- return false;
143
- }
107
+ function writeJson(filePath, value) {
108
+ ensureDir(path.dirname(filePath));
109
+ fs.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`, 'utf8');
144
110
  }
145
111
 
146
112
  function slugify(value) {
147
- return String(value)
113
+ return String(value || 'run')
148
114
  .toLowerCase()
149
115
  .replace(/[^a-z0-9]+/g, '-')
150
116
  .replace(/^-+|-+$/g, '')
151
- .replace(/-+/g, '-') || 'track';
117
+ .replace(/-+/g, '-') || 'run';
152
118
  }
153
119
 
154
- function padPhase(number) {
155
- return String(number).padStart(2, '0');
120
+ function findWorkspaceRoot(startDir) {
121
+ let cursor = path.resolve(startDir || process.cwd());
122
+ let gitRoot = null;
123
+
124
+ while (true) {
125
+ if (fileExists(path.join(cursor, '.autodev'))) {
126
+ return cursor;
127
+ }
128
+
129
+ if (!gitRoot && fileExists(path.join(cursor, '.git'))) {
130
+ gitRoot = cursor;
131
+ }
132
+
133
+ const parent = path.dirname(cursor);
134
+ if (parent === cursor) {
135
+ break;
136
+ }
137
+ cursor = parent;
138
+ }
139
+
140
+ return gitRoot || path.resolve(startDir || process.cwd());
156
141
  }
157
142
 
158
- function normalizeRunMode(value) {
159
- return value === 'auto' ? 'auto' : DEFAULT_RUN_MODE;
143
+ function autodevDir(cwd) {
144
+ return path.join(findWorkspaceRoot(cwd), '.autodev');
160
145
  }
161
146
 
162
- function routeCommandForRunMode(runMode) {
163
- return normalizeRunMode(runMode) === 'auto' ? '/autodev-auto' : '/autodev';
147
+ function rootPaths(cwd) {
148
+ const workspaceRoot = findWorkspaceRoot(cwd);
149
+ const root = autodevDir(cwd);
150
+ return {
151
+ workspaceRoot,
152
+ root,
153
+ brief: path.join(root, 'brief.md'),
154
+ context: path.join(root, 'context.md'),
155
+ plan: path.join(root, 'plan.md'),
156
+ state: path.join(root, 'state.json'),
157
+ runsDir: path.join(root, 'runs'),
158
+ legacyDir: path.join(root, 'legacy')
159
+ };
164
160
  }
165
161
 
166
162
  function codeFileNameLooksReal(entryName) {
@@ -206,22 +202,11 @@ function directoryContainsCodeMarker(dirPath, depth = 2) {
206
202
 
207
203
  function detectExistingCodebase(cwd) {
208
204
  const workspaceRoot = findWorkspaceRoot(cwd);
209
- const knownFiles = [
210
- 'package.json',
211
- 'package-lock.json',
212
- 'pnpm-lock.yaml',
213
- 'yarn.lock',
214
- 'tsconfig.json',
215
- 'jsconfig.json',
216
- 'pyproject.toml',
217
- 'requirements.txt',
218
- 'Pipfile',
219
- 'Cargo.toml',
220
- 'go.mod',
221
- 'pom.xml',
222
- 'build.gradle',
223
- 'Dockerfile'
224
- ];
205
+
206
+ if ([...NESTED_CODE_MARKER_FILES].some(name => fileExists(path.join(workspaceRoot, name)))) {
207
+ return true;
208
+ }
209
+
225
210
  const knownDirs = [
226
211
  'src',
227
212
  'app',
@@ -237,10 +222,6 @@ function detectExistingCodebase(cwd) {
237
222
  '__tests__'
238
223
  ];
239
224
 
240
- if (knownFiles.some(name => fileExists(path.join(workspaceRoot, name)))) {
241
- return true;
242
- }
243
-
244
225
  if (knownDirs.some(name => fileExists(path.join(workspaceRoot, name)))) {
245
226
  return true;
246
227
  }
@@ -248,7 +229,7 @@ function detectExistingCodebase(cwd) {
248
229
  try {
249
230
  const entries = fs.readdirSync(workspaceRoot, { withFileTypes: true });
250
231
  return entries.some(entry => {
251
- if (entry.name === '.autodev' || entry.name === '.claude' || entry.name.startsWith('.')) {
232
+ if (entry.name.startsWith('.') || IGNORED_SCAN_DIRS.has(entry.name)) {
252
233
  return false;
253
234
  }
254
235
 
@@ -256,7 +237,7 @@ function detectExistingCodebase(cwd) {
256
237
  return codeFileNameLooksReal(entry.name);
257
238
  }
258
239
 
259
- if (!entry.isDirectory() || IGNORED_SCAN_DIRS.has(entry.name)) {
240
+ if (!entry.isDirectory()) {
260
241
  return false;
261
242
  }
262
243
 
@@ -267,970 +248,507 @@ function detectExistingCodebase(cwd) {
267
248
  }
268
249
  }
269
250
 
270
- function mergeSection(defaultValue, actualValue) {
271
- if (defaultValue && typeof defaultValue === 'object' && !Array.isArray(defaultValue)) {
272
- return {
273
- ...defaultValue,
274
- ...((actualValue && typeof actualValue === 'object' && !Array.isArray(actualValue)) ? actualValue : {})
275
- };
251
+ function normalizeProjectType(value, fallbackCwd = process.cwd()) {
252
+ if (value === 'brownfield' || value === 'greenfield') {
253
+ return value;
276
254
  }
277
- return actualValue !== undefined ? actualValue : defaultValue;
255
+ return detectExistingCodebase(fallbackCwd) ? 'brownfield' : 'greenfield';
278
256
  }
279
257
 
280
- function loadConfig(cwd) {
281
- const config = readJson(rootPaths(cwd).config, {}) || {};
282
- return {
283
- project: mergeSection(DEFAULT_CONFIG.project, config.project),
284
- workflow: mergeSection(DEFAULT_CONFIG.workflow, config.workflow),
285
- execution: mergeSection(DEFAULT_CONFIG.execution, config.execution),
286
- git: mergeSection(DEFAULT_CONFIG.git, config.git),
287
- hooks: mergeSection(DEFAULT_CONFIG.hooks, config.hooks)
288
- };
258
+ function normalizeStage(value) {
259
+ const allowed = new Set(['briefing', 'context', 'planning', 'execution', 'blocked', 'complete']);
260
+ return allowed.has(value) ? value : DEFAULT_STATE.stage;
289
261
  }
290
262
 
291
- function readActiveTrack(cwd) {
292
- const value = readText(rootPaths(cwd).activeTrack);
293
- return value ? value.trim() || null : null;
294
- }
263
+ function normalizeCurrentItem(value) {
264
+ if (value === null || value === undefined || value === '' || value === 'none') {
265
+ return null;
266
+ }
295
267
 
296
- function trackDir(cwd, slug) {
297
- return path.join(rootPaths(cwd).tracksDir, slug);
268
+ const raw = String(value).trim();
269
+ return /^\d+$/.test(raw) ? raw.padStart(2, '0') : null;
298
270
  }
299
271
 
300
- function trackPaths(cwd, slug = readActiveTrack(cwd)) {
301
- if (!slug) {
272
+ function loadState(cwd) {
273
+ const paths = rootPaths(cwd);
274
+ const state = readJson(paths.state, null);
275
+ if (!state || typeof state !== 'object' || Array.isArray(state)) {
302
276
  return null;
303
277
  }
304
278
 
305
- const dir = trackDir(cwd, slug);
306
279
  return {
307
- slug,
308
- dir,
309
- track: path.join(dir, 'TRACK.md'),
310
- requirements: path.join(dir, 'REQUIREMENTS.md'),
311
- roadmap: path.join(dir, 'ROADMAP.md'),
312
- state: path.join(dir, 'STATE.md'),
313
- phasesDir: path.join(dir, 'phases')
280
+ ...DEFAULT_STATE,
281
+ ...state,
282
+ project_type: normalizeProjectType(state.project_type, cwd),
283
+ stage: normalizeStage(state.stage),
284
+ current_item: normalizeCurrentItem(state.current_item),
285
+ settings: {
286
+ ...DEFAULT_STATE.settings,
287
+ ...((state.settings && typeof state.settings === 'object') ? state.settings : {})
288
+ }
314
289
  };
315
290
  }
316
291
 
317
- function listTracks(cwd) {
318
- const tracksDir = rootPaths(cwd).tracksDir;
319
- if (!fileExists(tracksDir)) {
320
- return [];
321
- }
322
-
323
- return fs.readdirSync(tracksDir, { withFileTypes: true })
324
- .filter(entry => entry.isDirectory())
325
- .map(entry => {
326
- const paths = trackPaths(cwd, entry.name);
327
- const trackName = (() => {
328
- const content = readText(paths.track);
329
- const match = content && content.match(/^#\s+Track:\s*(.+)$/m);
330
- return match ? match[1].trim() : entry.name;
331
- })();
332
-
333
- return {
334
- slug: entry.name,
335
- name: trackName,
336
- paths,
337
- active: readActiveTrack(cwd) === entry.name
338
- };
339
- })
340
- .sort((left, right) => left.slug.localeCompare(right.slug));
292
+ function saveState(cwd, state) {
293
+ const paths = rootPaths(cwd);
294
+ writeJson(paths.state, {
295
+ ...DEFAULT_STATE,
296
+ ...state,
297
+ project_type: normalizeProjectType(state.project_type, cwd),
298
+ stage: normalizeStage(state.stage),
299
+ current_item: normalizeCurrentItem(state.current_item),
300
+ settings: {
301
+ ...DEFAULT_STATE.settings,
302
+ ...((state.settings && typeof state.settings === 'object') ? state.settings : {})
303
+ }
304
+ });
341
305
  }
342
306
 
343
- function parseRoadmap(content) {
344
- const phases = [];
345
- if (!content) {
346
- return phases;
307
+ function archiveLegacyState(cwd) {
308
+ const paths = rootPaths(cwd);
309
+ if (!fileExists(paths.root) || fileExists(paths.state)) {
310
+ return null;
347
311
  }
348
312
 
349
- const matcher = /^##\s+Phase\s+(\d+)(?:\s+\[([a-z-]+)\])?\s*:\s*(.+)$/gm;
350
- let match;
351
- while ((match = matcher.exec(content)) !== null) {
352
- phases.push({
353
- number: Number(match[1]),
354
- type: match[2] || 'feature',
355
- name: match[3].trim()
356
- });
357
- }
358
- return phases;
359
- }
313
+ const entries = fs.readdirSync(paths.root).filter(entry => entry !== 'legacy');
314
+ const legacyMarkers = new Set([
315
+ 'ACTIVE_TRACK',
316
+ 'PROJECT.md',
317
+ 'STATE.md',
318
+ 'TRACK.md',
319
+ 'codebase',
320
+ 'tracks'
321
+ ]);
360
322
 
361
- function phasePaths(cwd, slug, phase) {
362
- const track = trackPaths(cwd, slug);
363
- if (!track) {
323
+ if (!entries.some(entry => legacyMarkers.has(entry) || /\.(md|json)$/.test(entry))) {
364
324
  return null;
365
325
  }
366
326
 
367
- const prefix = padPhase(phase.number);
368
- const dirName = `${prefix}-${phase.type}-${slugify(phase.name)}`;
369
- const dir = path.join(track.phasesDir, dirName);
370
- return {
371
- ...phase,
372
- padded: prefix,
373
- dir,
374
- planPath: path.join(dir, `${prefix}-PLAN.md`),
375
- summaryPath: path.join(dir, `${prefix}-SUMMARY.md`),
376
- reviewPath: path.join(dir, `${prefix}-REVIEW.md`),
377
- uatPath: path.join(dir, `${prefix}-UAT.md`)
378
- };
379
- }
327
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
328
+ const archiveDir = path.join(paths.legacyDir, timestamp);
329
+ ensureDir(archiveDir);
380
330
 
381
- function readSingleLineField(content, label) {
382
- if (!content) {
383
- return null;
331
+ for (const entry of entries) {
332
+ fs.renameSync(path.join(paths.root, entry), path.join(archiveDir, entry));
384
333
  }
385
- const match = content.match(new RegExp(`^${label}:\\s*(.+)$`, 'mi'));
386
- return match ? match[1].trim() : null;
387
- }
388
334
 
389
- function readNumericField(content, label, fallback = 0) {
390
- const raw = readSingleLineField(content, label);
391
- if (raw && /^\d+$/.test(raw)) {
392
- return Number(raw);
393
- }
394
- return fallback;
335
+ return archiveDir;
395
336
  }
396
337
 
397
- function parseStateSnapshot(content) {
398
- if (!content) {
399
- return null;
400
- }
338
+ function bootstrapProject(cwd, options = {}) {
339
+ const paths = rootPaths(cwd);
340
+ const archivedLegacyPath = archiveLegacyState(cwd);
341
+ ensureDir(paths.root);
342
+ ensureDir(paths.runsDir);
343
+
344
+ const current = loadState(cwd);
345
+ const nextState = current || {
346
+ ...DEFAULT_STATE,
347
+ project_type: normalizeProjectType(options.projectType, cwd)
348
+ };
401
349
 
402
- const currentPhaseRaw = readSingleLineField(content, 'Current Phase');
403
- const currentPhaseNumber = currentPhaseRaw && /^\d+$/.test(currentPhaseRaw)
404
- ? Number(currentPhaseRaw)
405
- : null;
350
+ saveState(cwd, nextState);
406
351
 
407
352
  return {
408
- currentPhase: currentPhaseNumber,
409
- currentPhaseType: readSingleLineField(content, 'Current Phase Type'),
410
- currentStep: readSingleLineField(content, 'Current Step'),
411
- currentTask: readSingleLineField(content, 'Current Task'),
412
- currentTaskStatus: readSingleLineField(content, 'Current Task Status'),
413
- status: readSingleLineField(content, 'Status'),
414
- nextCommand: readSingleLineField(content, 'Next Command'),
415
- runMode: normalizeRunMode(readSingleLineField(content, 'Run Mode')),
416
- autoRetryCount: readNumericField(content, 'Auto Retry Count', 0)
353
+ cwd,
354
+ workspace_root: paths.workspaceRoot,
355
+ autodev_root: paths.root,
356
+ state_path: paths.state,
357
+ brief_path: paths.brief,
358
+ context_path: paths.context,
359
+ plan_path: paths.plan,
360
+ runs_dir: paths.runsDir,
361
+ existing_code_detected: detectExistingCodebase(cwd),
362
+ archived_legacy_path: archivedLegacyPath,
363
+ state: loadState(cwd)
417
364
  };
418
365
  }
419
366
 
420
- function readStateSnapshot(filePath) {
421
- return parseStateSnapshot(readText(filePath));
422
- }
423
-
424
- function isBlockedTaskStatus(value) {
425
- return typeof value === 'string' && /^blocked/i.test(value.trim());
367
+ function itemIdFromTitleNumber(value) {
368
+ return String(value).padStart(2, '0');
426
369
  }
427
370
 
428
- function parseTaskDependsOn(value) {
429
- if (!value || /^none$/i.test(value)) {
430
- return [];
431
- }
432
-
433
- return String(value)
434
- .split(/[,\s]+/)
371
+ function parseInlineList(value) {
372
+ return String(value || '')
373
+ .split(',')
435
374
  .map(part => part.trim())
436
- .filter(Boolean)
437
- .map(part => {
438
- const match = part.match(/(\d+)/);
439
- return match ? Number(match[1]) : null;
440
- })
441
- .filter(number => Number.isFinite(number));
442
- }
443
-
444
- function readTaskTitle(content, fallbackNumber) {
445
- if (!content) {
446
- return `Task ${String(fallbackNumber).padStart(2, '0')}`;
447
- }
448
-
449
- const heading = content.match(/^#\s+Task\s+\d+\s*:\s*(.+)$/mi);
450
- if (heading) {
451
- return heading[1].trim();
452
- }
453
-
454
- const generic = content.match(/^#\s+(.+)$/m);
455
- return generic ? generic[1].trim() : `Task ${String(fallbackNumber).padStart(2, '0')}`;
375
+ .filter(Boolean);
456
376
  }
457
377
 
458
- function listTasksForPhaseDetails(phaseDetails) {
459
- if (!phaseDetails || !fileExists(phaseDetails.dir)) {
460
- return [];
378
+ function parseItemSection(sectionContent) {
379
+ const lines = sectionContent.split('\n');
380
+ const titleMatch = lines[0].match(/^##\s+Item\s+(\d+)\s*:\s*(.+)$/);
381
+ if (!titleMatch) {
382
+ return null;
461
383
  }
462
384
 
463
- const entries = fs.readdirSync(phaseDetails.dir, { withFileTypes: true })
464
- .filter(entry => entry.isFile())
465
- .map(entry => entry.name);
466
-
467
- const tasks = entries
468
- .map(name => {
469
- const match = name.match(/^TASK-(\d+)\.md$/);
470
- if (!match) {
471
- return null;
472
- }
473
-
474
- const number = Number(match[1]);
475
- const padded = String(number).padStart(2, '0');
476
- const taskPath = path.join(phaseDetails.dir, name);
477
- const summaryPath = path.join(phaseDetails.dir, `TASK-${padded}-SUMMARY.md`);
478
- const content = readText(taskPath);
479
- const status = readSingleLineField(content, 'Status') || 'pending';
480
- const dependsOn = parseTaskDependsOn(readSingleLineField(content, 'Depends On'));
481
- const summaryExists = fileExists(summaryPath);
482
-
483
- return {
484
- number,
485
- padded,
486
- title: readTaskTitle(content, number),
487
- status,
488
- dependsOn,
489
- taskPath,
490
- summaryPath,
491
- summaryExists
492
- };
493
- })
494
- .filter(Boolean)
495
- .sort((left, right) => left.number - right.number);
496
-
497
- const doneSet = new Set(tasks.filter(task => task.summaryExists).map(task => task.number));
498
-
499
- return tasks.map(task => ({
500
- ...task,
501
- ready: !task.summaryExists && task.dependsOn.every(dep => doneSet.has(dep)),
502
- effectiveStatus: task.summaryExists ? 'done' : task.status
503
- }));
504
- }
505
-
506
- function nextExecutableTask(tasks) {
507
- return tasks.find(task => !task.summaryExists && task.ready) || null;
508
- }
509
-
510
- function lastCompletedTask(tasks) {
511
- const completed = tasks.filter(task => task.summaryExists);
512
- return completed.length > 0 ? completed[completed.length - 1] : null;
513
- }
514
-
515
- function lastTaskNumber(tasks) {
516
- return tasks.reduce((highest, task) => Math.max(highest, task.number), 0);
517
- }
518
-
519
- function hasDependencyDeadlock(tasks) {
520
- return tasks.some(task => !task.summaryExists) && !nextExecutableTask(tasks);
521
- }
522
-
523
- function buildRouteResult(route, runMode, options = {}) {
524
- const normalizedRunMode = normalizeRunMode(runMode);
525
- return {
526
- ...route,
527
- command: routeCommandForRunMode(normalizedRunMode),
528
- run_mode: normalizedRunMode,
529
- auto_continuable: normalizedRunMode === 'auto' ? options.autoContinuable !== false : false,
530
- pause_reason: normalizedRunMode === 'auto' ? (options.pauseReason || null) : null
531
- };
532
- }
533
-
534
- function phaseTaskSnapshot(phase) {
535
- const tasks = phase ? listTasksForPhaseDetails(phase) : [];
536
- return {
537
- tasks,
538
- nextTask: nextExecutableTask(tasks),
539
- dependencyDeadlock: hasDependencyDeadlock(tasks)
385
+ const item = {
386
+ id: itemIdFromTitleNumber(titleMatch[1]),
387
+ number: Number(titleMatch[1]),
388
+ title: titleMatch[2].trim(),
389
+ status: 'pending',
390
+ goal: '',
391
+ files: [],
392
+ acceptance: [],
393
+ verification: []
540
394
  };
541
- }
542
395
 
543
- function listPhases(cwd, slug = readActiveTrack(cwd)) {
544
- const track = trackPaths(cwd, slug);
545
- if (!track || !fileExists(track.roadmap)) {
546
- return [];
547
- }
548
-
549
- return parseRoadmap(readText(track.roadmap)).map(phase => {
550
- const details = phasePaths(cwd, slug, phase);
551
- const planExists = fileExists(details.planPath);
552
- const summaryExists = fileExists(details.summaryPath);
553
- const reviewExists = fileExists(details.reviewPath);
554
- const uatExists = fileExists(details.uatPath);
555
- const tasks = listTasksForPhaseDetails(details);
556
- const taskCount = tasks.length;
557
- const taskDoneCount = tasks.filter(task => task.summaryExists).length;
558
- const status = uatExists
559
- ? 'verified'
560
- : reviewExists
561
- ? 'reviewed'
562
- : summaryExists
563
- ? 'executed'
564
- : planExists && taskCount > 0 && taskDoneCount === taskCount
565
- ? 'tasks_complete'
566
- : planExists && taskDoneCount > 0
567
- ? 'executing'
568
- : planExists
569
- ? 'planned'
570
- : 'pending';
396
+ let activeSection = null;
571
397
 
572
- return {
573
- ...details,
574
- trackSlug: slug,
575
- planExists,
576
- summaryExists,
577
- reviewExists,
578
- uatExists,
579
- taskCount,
580
- taskDoneCount,
581
- nextTask: nextExecutableTask(tasks)?.number || null,
582
- status
583
- };
584
- });
585
- }
398
+ for (let index = 1; index < lines.length; index += 1) {
399
+ const line = lines[index];
400
+ const trimmed = line.trim();
586
401
 
587
- function resolvePhase(cwd, slug, requestedPhase, mode) {
588
- const phases = listPhases(cwd, slug);
589
- if (phases.length === 0) {
590
- return null;
591
- }
402
+ if (!trimmed) {
403
+ continue;
404
+ }
592
405
 
593
- if (requestedPhase) {
594
- const numeric = Number(requestedPhase);
595
- return phases.find(phase => phase.number === numeric) || null;
596
- }
406
+ const statusMatch = trimmed.match(/^Status:\s*(.+)$/);
407
+ if (statusMatch) {
408
+ const rawStatus = statusMatch[1].trim().toLowerCase();
409
+ item.status = ['pending', 'in_progress', 'done', 'blocked'].includes(rawStatus)
410
+ ? rawStatus
411
+ : 'pending';
412
+ activeSection = null;
413
+ continue;
414
+ }
597
415
 
598
- const track = trackPaths(cwd, slug);
599
- const trackState = track ? readStateSnapshot(track.state) : null;
600
- const currentStatePhase = trackState?.currentPhase
601
- ? phases.find(phase => phase.number === trackState.currentPhase) || null
602
- : null;
416
+ const goalMatch = trimmed.match(/^Goal:\s*(.+)$/);
417
+ if (goalMatch) {
418
+ item.goal = goalMatch[1].trim();
419
+ activeSection = null;
420
+ continue;
421
+ }
603
422
 
604
- if (mode === 'plan') {
605
- if (currentStatePhase && (
606
- isBlockedTaskStatus(trackState.currentTaskStatus)
607
- || trackState.currentStep === 'planning'
608
- || trackState.currentStep === 'plan_review'
609
- )) {
610
- return currentStatePhase;
423
+ const filesMatch = trimmed.match(/^Files:\s*(.+)$/);
424
+ if (filesMatch) {
425
+ item.files = parseInlineList(filesMatch[1]);
426
+ activeSection = null;
427
+ continue;
611
428
  }
612
- return phases.find(phase => !phase.planExists) || phases[0];
613
- }
614
429
 
615
- if (mode === 'execute') {
616
- if (currentStatePhase && (
617
- trackState?.currentStep === 'execution'
618
- || trackState?.currentStep === 'task_review'
619
- )) {
620
- return currentStatePhase;
430
+ const sectionMatch = trimmed.match(/^(Files|Acceptance|Verification):\s*$/);
431
+ if (sectionMatch) {
432
+ activeSection = sectionMatch[1].toLowerCase();
433
+ continue;
621
434
  }
622
- return phases.find(phase => phase.planExists && !phase.summaryExists) || null;
623
- }
624
435
 
625
- if (mode === 'task_review') {
626
- if (currentStatePhase && trackState?.currentStep === 'task_review') {
627
- return currentStatePhase;
436
+ if (!trimmed.startsWith('- ')) {
437
+ activeSection = null;
438
+ continue;
628
439
  }
629
- return phases.find(phase => phase.planExists && !phase.summaryExists && phase.taskDoneCount > 0) || null;
630
- }
631
440
 
632
- if (mode === 'review') {
633
- if (currentStatePhase && trackState?.currentStep === 'review') {
634
- return currentStatePhase;
441
+ const value = trimmed.slice(2).trim();
442
+ if (!value) {
443
+ continue;
635
444
  }
636
- return phases.find(phase => phase.summaryExists && !phase.reviewExists) || null;
637
- }
638
445
 
639
- if (mode === 'verify') {
640
- if (currentStatePhase && trackState?.currentStep === 'verification') {
641
- return currentStatePhase;
446
+ if (activeSection === 'files') {
447
+ item.files.push(value);
448
+ } else if (activeSection === 'acceptance') {
449
+ item.acceptance.push(value);
450
+ } else if (activeSection === 'verification') {
451
+ item.verification.push(value);
642
452
  }
643
- return phases.find(phase => phase.reviewExists && !phase.uatExists)
644
- || [...phases].reverse().find(phase => phase.reviewExists)
645
- || null;
646
453
  }
647
454
 
648
- return phases[0];
455
+ return item;
649
456
  }
650
457
 
651
- function buildRoute(cwd, options = {}) {
652
- const runMode = normalizeRunMode(options.runMode);
653
- const paths = rootPaths(cwd);
654
- const config = loadConfig(cwd);
655
- const existingCodebase = detectExistingCodebase(cwd);
656
- const projectType = config.project.type || (existingCodebase ? 'brownfield' : 'greenfield');
657
- const projectExists = fileExists(paths.project);
658
- const codebaseMapExists = fileExists(paths.codebaseSummary);
659
- const activeTrack = readActiveTrack(cwd);
660
- const tracks = listTracks(cwd);
661
-
662
- if (!projectExists) {
663
- return buildRouteResult({
664
- kind: 'init_project',
665
- manualCommand: '/autodev-new-project',
666
- reason: existingCodebase ? 'existing_code_detected_without_project_state' : 'project_state_missing',
667
- projectType
668
- }, runMode, {
669
- autoContinuable: false,
670
- pauseReason: 'missing_intent'
671
- });
458
+ function parsePlan(content) {
459
+ if (!content) {
460
+ return {
461
+ goal: '',
462
+ done_definition: [],
463
+ items: []
464
+ };
672
465
  }
673
466
 
674
- if (projectType === 'brownfield' && !codebaseMapExists) {
675
- return buildRouteResult({
676
- kind: 'explore_codebase',
677
- manualCommand: '/autodev-explore-codebase',
678
- reason: 'brownfield_project_needs_codebase_map',
679
- projectType
680
- }, runMode);
467
+ const goalMatch = content.match(/^Goal:\s*(.+)$/mi);
468
+ const doneDefinition = [];
469
+ const doneMatch = content.match(/^Done Definition:\s*\n((?:- .+\n?)*)/mi);
470
+ if (doneMatch) {
471
+ for (const line of doneMatch[1].split('\n')) {
472
+ const bullet = line.trim().match(/^- (.+)$/);
473
+ if (bullet) {
474
+ doneDefinition.push(bullet[1].trim());
475
+ }
476
+ }
681
477
  }
682
478
 
683
- if (!activeTrack) {
684
- return buildRouteResult({
685
- kind: 'track_select',
686
- manualCommand: null,
687
- reason: tracks.length > 0 ? 'no_active_track_selected' : 'no_tracks_created',
688
- projectType
689
- }, runMode, {
690
- autoContinuable: false,
691
- pauseReason: 'track_selection'
692
- });
693
- }
479
+ const headings = [...content.matchAll(/^##\s+Item\s+\d+\s*:\s*.+$/gm)];
480
+ const items = [];
694
481
 
695
- const track = trackPaths(cwd, activeTrack);
696
- if (!track || !fileExists(track.track) || !fileExists(track.roadmap) || !fileExists(track.state)) {
697
- return buildRouteResult({
698
- kind: 'track_setup',
699
- manualCommand: null,
700
- reason: 'active_track_missing_required_artifacts',
701
- projectType,
702
- trackSlug: activeTrack
703
- }, runMode, {
704
- autoContinuable: false,
705
- pauseReason: 'missing_intent'
706
- });
482
+ for (let index = 0; index < headings.length; index += 1) {
483
+ const start = headings[index].index;
484
+ const end = index + 1 < headings.length ? headings[index + 1].index : content.length;
485
+ const section = content.slice(start, end).trim();
486
+ const item = parseItemSection(section);
487
+ if (item) {
488
+ items.push(item);
489
+ }
707
490
  }
708
491
 
709
- const phases = listPhases(cwd, activeTrack);
710
- const trackState = readStateSnapshot(track.state);
711
- if (phases.length === 0) {
712
- return buildRouteResult({
713
- kind: 'track_setup',
714
- manualCommand: null,
715
- reason: 'active_track_has_no_phases',
716
- projectType,
717
- trackSlug: activeTrack
718
- }, runMode, {
719
- autoContinuable: false,
720
- pauseReason: 'missing_intent'
721
- });
722
- }
492
+ return {
493
+ goal: goalMatch ? goalMatch[1].trim() : '',
494
+ done_definition: doneDefinition,
495
+ items
496
+ };
497
+ }
723
498
 
724
- const currentStatePhase = trackState?.currentPhase
725
- ? phases.find(phase => phase.number === trackState.currentPhase) || null
499
+ function determineNextItem(items, preferredItemId) {
500
+ const preferred = preferredItemId
501
+ ? items.find(item => item.id === normalizeCurrentItem(preferredItemId))
726
502
  : null;
727
503
 
728
- if (currentStatePhase && (
729
- isBlockedTaskStatus(trackState?.currentTaskStatus)
730
- )) {
731
- const retryLimitHit = runMode === 'auto' && (trackState?.autoRetryCount || 0) >= AUTO_RETRY_LIMIT;
732
- return buildRouteResult({
733
- kind: 'plan_phase',
734
- manualCommand: `/autodev-plan-phase ${currentStatePhase.number}`,
735
- reason: 'blocked_phase_requires_replanning',
736
- projectType,
737
- trackSlug: activeTrack,
738
- phaseNumber: currentStatePhase.number
739
- }, runMode, retryLimitHit
740
- ? {
741
- autoContinuable: false,
742
- pauseReason: 'auto_retry_limit'
743
- }
744
- : {});
745
- }
746
-
747
- if (currentStatePhase && trackState?.currentStep === 'execution') {
748
- return buildRouteResult({
749
- kind: 'execute_phase',
750
- manualCommand: `/autodev-execute-phase ${currentStatePhase.number}`,
751
- reason: 'phase_execution_in_progress',
752
- projectType,
753
- trackSlug: activeTrack,
754
- phaseNumber: currentStatePhase.number
755
- }, runMode);
756
- }
757
-
758
- if (currentStatePhase && trackState?.currentStep === 'task_review') {
759
- if (runMode === 'auto') {
760
- const snapshot = phaseTaskSnapshot(currentStatePhase);
761
- if (snapshot.dependencyDeadlock) {
762
- return buildRouteResult({
763
- kind: 'plan_phase',
764
- manualCommand: `/autodev-plan-phase ${currentStatePhase.number}`,
765
- reason: 'dependency_deadlock_requires_replanning',
766
- projectType,
767
- trackSlug: activeTrack,
768
- phaseNumber: currentStatePhase.number
769
- }, runMode, {
770
- autoContinuable: false,
771
- pauseReason: 'dependency_deadlock'
772
- });
773
- }
774
-
775
- if (currentStatePhase.summaryExists) {
776
- return buildRouteResult({
777
- kind: 'review_phase',
778
- manualCommand: `/autodev-review-phase ${currentStatePhase.number}`,
779
- reason: 'auto_skips_task_review_after_phase_summary',
780
- projectType,
781
- trackSlug: activeTrack,
782
- phaseNumber: currentStatePhase.number
783
- }, runMode);
784
- }
785
-
786
- return buildRouteResult({
787
- kind: 'execute_phase',
788
- manualCommand: `/autodev-execute-phase ${currentStatePhase.number}`,
789
- reason: snapshot.nextTask ? 'auto_skips_task_review_checkpoint' : 'phase_execution_in_progress',
790
- projectType,
791
- trackSlug: activeTrack,
792
- phaseNumber: currentStatePhase.number
793
- }, runMode);
794
- }
795
-
796
- return buildRouteResult({
797
- kind: 'task_review',
798
- manualCommand: `/autodev-review-task ${currentStatePhase.number}`,
799
- reason: 'task_execution_checkpoint_pending_user_review',
800
- projectType,
801
- trackSlug: activeTrack,
802
- phaseNumber: currentStatePhase.number
803
- }, runMode);
804
- }
805
-
806
- if (currentStatePhase && trackState?.currentStep === 'plan_review') {
807
- if (runMode === 'auto') {
808
- return buildRouteResult({
809
- kind: 'execute_phase',
810
- manualCommand: `/autodev-execute-phase ${currentStatePhase.number}`,
811
- reason: 'auto_skips_plan_review_checkpoint',
812
- projectType,
813
- trackSlug: activeTrack,
814
- phaseNumber: currentStatePhase.number
815
- }, runMode);
816
- }
817
-
818
- return buildRouteResult({
819
- kind: 'plan_review',
820
- manualCommand: `/autodev-execute-phase ${currentStatePhase.number}`,
821
- reason: 'phase_plan_awaits_review',
822
- projectType,
823
- trackSlug: activeTrack,
824
- phaseNumber: currentStatePhase.number
825
- }, runMode);
504
+ if (preferred && ['pending', 'in_progress'].includes(preferred.status)) {
505
+ return preferred;
826
506
  }
827
507
 
828
- if (currentStatePhase && trackState?.currentStep === 'review' && !currentStatePhase.reviewExists) {
829
- return buildRouteResult({
830
- kind: 'review_phase',
831
- manualCommand: `/autodev-review-phase ${currentStatePhase.number}`,
832
- reason: 'phase_review_in_progress',
833
- projectType,
834
- trackSlug: activeTrack,
835
- phaseNumber: currentStatePhase.number
836
- }, runMode);
837
- }
508
+ return items.find(item => item.status === 'in_progress')
509
+ || items.find(item => item.status === 'pending')
510
+ || null;
511
+ }
838
512
 
839
- if (currentStatePhase && trackState?.currentStep === 'verification' && !currentStatePhase.uatExists) {
840
- return buildRouteResult({
841
- kind: 'verify_phase',
842
- manualCommand: `/autodev-verify-work ${currentStatePhase.number}`,
843
- reason: 'phase_verification_in_progress',
844
- projectType,
845
- trackSlug: activeTrack,
846
- phaseNumber: currentStatePhase.number
847
- }, runMode);
513
+ function buildRoute(cwd) {
514
+ const paths = rootPaths(cwd);
515
+ const state = loadState(cwd) || {
516
+ ...DEFAULT_STATE,
517
+ project_type: normalizeProjectType(null, cwd)
518
+ };
519
+ const plan = parsePlan(readText(paths.plan));
520
+ const briefExists = fileExists(paths.brief);
521
+ const contextExists = fileExists(paths.context);
522
+ const projectType = normalizeProjectType(state.project_type, cwd);
523
+ const blockedItem = plan.items.find(item => item.status === 'blocked') || null;
524
+ const nextItem = determineNextItem(plan.items, state.current_item);
525
+ const allDone = plan.items.length > 0 && plan.items.every(item => item.status === 'done');
526
+
527
+ if (!briefExists) {
528
+ return {
529
+ kind: 'init_brief',
530
+ reason: 'brief_missing',
531
+ command: '/autodev'
532
+ };
848
533
  }
849
534
 
850
- const nextReview = phases.find(phase => phase.summaryExists && !phase.reviewExists);
851
- if (nextReview && config.workflow.review_after_execute !== false) {
852
- return buildRouteResult({
853
- kind: 'review_phase',
854
- manualCommand: `/autodev-review-phase ${nextReview.number}`,
855
- reason: 'phase_executed_but_not_reviewed',
856
- projectType,
857
- trackSlug: activeTrack,
858
- phaseNumber: nextReview.number
859
- }, runMode);
535
+ if (projectType === 'brownfield' && !contextExists) {
536
+ return {
537
+ kind: 'build_context',
538
+ reason: 'brownfield_context_missing',
539
+ command: '/autodev'
540
+ };
860
541
  }
861
542
 
862
- const nextVerify = phases.find(phase => phase.reviewExists && !phase.uatExists);
863
- if (nextVerify) {
864
- return buildRouteResult({
865
- kind: 'verify_phase',
866
- manualCommand: `/autodev-verify-work ${nextVerify.number}`,
867
- reason: 'phase_reviewed_but_not_verified',
868
- projectType,
869
- trackSlug: activeTrack,
870
- phaseNumber: nextVerify.number
871
- }, runMode);
543
+ if (plan.items.length === 0) {
544
+ return {
545
+ kind: 'plan',
546
+ reason: 'plan_missing_or_empty',
547
+ command: '/autodev'
548
+ };
872
549
  }
873
550
 
874
- const nextTaskReview = phases.find(phase => phase.planExists && !phase.summaryExists && phase.taskDoneCount > 0);
875
- if (nextTaskReview) {
876
- if (runMode === 'auto') {
877
- const snapshot = phaseTaskSnapshot(nextTaskReview);
878
- if (snapshot.dependencyDeadlock) {
879
- return buildRouteResult({
880
- kind: 'plan_phase',
881
- manualCommand: `/autodev-plan-phase ${nextTaskReview.number}`,
882
- reason: 'dependency_deadlock_requires_replanning',
883
- projectType,
884
- trackSlug: activeTrack,
885
- phaseNumber: nextTaskReview.number
886
- }, runMode, {
887
- autoContinuable: false,
888
- pauseReason: 'dependency_deadlock'
889
- });
890
- }
891
-
892
- return buildRouteResult({
893
- kind: 'execute_phase',
894
- manualCommand: `/autodev-execute-phase ${nextTaskReview.number}`,
895
- reason: 'auto_skips_task_review_checkpoint',
896
- projectType,
897
- trackSlug: activeTrack,
898
- phaseNumber: nextTaskReview.number
899
- }, runMode);
900
- }
901
-
902
- return buildRouteResult({
903
- kind: 'task_review',
904
- manualCommand: `/autodev-review-task ${nextTaskReview.number}`,
905
- reason: 'phase_task_completed_awaiting_user_review',
906
- projectType,
907
- trackSlug: activeTrack,
908
- phaseNumber: nextTaskReview.number
909
- }, runMode);
551
+ if (state.stage === 'blocked' || blockedItem) {
552
+ return {
553
+ kind: 'repair_plan',
554
+ reason: blockedItem ? 'plan_item_blocked' : 'state_marked_blocked',
555
+ command: '/autodev',
556
+ current_item: blockedItem ? blockedItem.id : state.current_item
557
+ };
910
558
  }
911
559
 
912
- const nextPlannedReview = phases.find(phase => phase.planExists && !phase.summaryExists);
913
- if (nextPlannedReview) {
914
- if (runMode === 'auto') {
915
- return buildRouteResult({
916
- kind: 'execute_phase',
917
- manualCommand: `/autodev-execute-phase ${nextPlannedReview.number}`,
918
- reason: 'auto_skips_plan_review_checkpoint',
919
- projectType,
920
- trackSlug: activeTrack,
921
- phaseNumber: nextPlannedReview.number
922
- }, runMode);
923
- }
924
-
925
- return buildRouteResult({
926
- kind: 'plan_review',
927
- manualCommand: `/autodev-execute-phase ${nextPlannedReview.number}`,
928
- reason: 'phase_planned_awaiting_user_review',
929
- projectType,
930
- trackSlug: activeTrack,
931
- phaseNumber: nextPlannedReview.number
932
- }, runMode);
560
+ if (nextItem) {
561
+ return {
562
+ kind: 'execute',
563
+ reason: nextItem.status === 'in_progress' ? 'item_in_progress' : 'pending_plan_item',
564
+ command: '/autodev',
565
+ current_item: nextItem.id
566
+ };
933
567
  }
934
568
 
935
- const nextPlan = phases.find(phase => !phase.planExists);
936
- if (nextPlan) {
937
- return buildRouteResult({
938
- kind: 'plan_phase',
939
- manualCommand: `/autodev-plan-phase ${nextPlan.number}`,
940
- reason: 'phase_not_planned',
941
- projectType,
942
- trackSlug: activeTrack,
943
- phaseNumber: nextPlan.number
944
- }, runMode);
569
+ if (allDone) {
570
+ return {
571
+ kind: 'complete',
572
+ reason: 'all_plan_items_done',
573
+ command: '/autodev'
574
+ };
945
575
  }
946
576
 
947
- return buildRouteResult({
948
- kind: 'track_complete',
949
- manualCommand: '/autodev-cleanup',
950
- reason: 'active_track_fully_verified',
951
- projectType,
952
- trackSlug: activeTrack
953
- }, runMode, {
954
- autoContinuable: false
955
- });
577
+ return {
578
+ kind: 'plan',
579
+ reason: 'plan_needs_refresh',
580
+ command: '/autodev'
581
+ };
956
582
  }
957
583
 
958
- function buildProgress(cwd, options = {}) {
959
- const runMode = normalizeRunMode(options.runMode);
584
+ function buildStatus(cwd) {
960
585
  const paths = rootPaths(cwd);
961
- const initialized = fileExists(paths.project);
962
586
  const existingCodebase = detectExistingCodebase(cwd);
963
- const config = loadConfig(cwd);
964
- const projectType = config.project.type || (existingCodebase ? 'brownfield' : 'greenfield');
965
- const activeTrack = readActiveTrack(cwd);
966
- const tracks = listTracks(cwd);
967
- const phases = activeTrack ? listPhases(cwd, activeTrack) : [];
968
- const codebaseMapExists = fileExists(paths.codebaseSummary);
969
- const route = buildRoute(cwd, { runMode });
587
+ const autodevExists = fileExists(paths.root);
588
+ const state = loadState(cwd);
589
+ const projectType = normalizeProjectType(state?.project_type, cwd);
590
+ const plan = parsePlan(readText(paths.plan));
591
+ const counts = {
592
+ total: plan.items.length,
593
+ pending: plan.items.filter(item => item.status === 'pending').length,
594
+ in_progress: plan.items.filter(item => item.status === 'in_progress').length,
595
+ done: plan.items.filter(item => item.status === 'done').length,
596
+ blocked: plan.items.filter(item => item.status === 'blocked').length
597
+ };
598
+ const route = buildRoute(cwd);
599
+ const currentItem = determineNextItem(plan.items, state?.current_item);
970
600
 
971
601
  return {
972
- initialized,
973
- runMode,
974
- projectType,
975
- existingCodebase,
976
- codebaseMapExists,
977
- activeTrack,
978
- tracks,
979
- phases,
980
- counts: {
981
- trackCount: tracks.length,
982
- total: phases.length,
983
- planned: phases.filter(phase => phase.planExists).length,
984
- executed: phases.filter(phase => phase.summaryExists).length,
985
- reviewed: phases.filter(phase => phase.reviewExists).length,
986
- verified: phases.filter(phase => phase.uatExists).length,
987
- tasks: phases.reduce((total, phase) => total + phase.taskCount, 0),
988
- tasksDone: phases.reduce((total, phase) => total + phase.taskDoneCount, 0)
989
- },
990
- route,
991
- nextCommand: route.command,
992
- manualNextCommand: route.manualCommand || null,
993
- autoContinuable: route.auto_continuable,
994
- pauseReason: route.pause_reason
602
+ cwd,
603
+ workspace_root: paths.workspaceRoot,
604
+ autodev_root: paths.root,
605
+ autodev_exists: autodevExists,
606
+ state_exists: fileExists(paths.state),
607
+ existing_code_detected: existingCodebase,
608
+ project_type: projectType,
609
+ stage: state?.stage || DEFAULT_STATE.stage,
610
+ current_item: currentItem ? currentItem.id : normalizeCurrentItem(state?.current_item),
611
+ last_result: state?.last_result || DEFAULT_STATE.last_result,
612
+ last_run_at: state?.last_run_at || null,
613
+ last_run_path: state?.last_run_path || null,
614
+ settings: state?.settings || { ...DEFAULT_STATE.settings },
615
+ brief_path: paths.brief,
616
+ brief_exists: fileExists(paths.brief),
617
+ context_path: paths.context,
618
+ context_exists: fileExists(paths.context),
619
+ context_required: projectType === 'brownfield',
620
+ plan_path: paths.plan,
621
+ plan_exists: fileExists(paths.plan),
622
+ plan_goal: plan.goal,
623
+ plan_done_definition: plan.done_definition,
624
+ plan_items: plan.items,
625
+ counts,
626
+ route
995
627
  };
996
628
  }
997
629
 
998
- function renderProgressTable(progress) {
999
- if (!progress.initialized) {
1000
- return 'No .autodev project found.\nNext: /autodev';
1001
- }
1002
-
630
+ function renderStatusTable(status) {
1003
631
  const lines = [
1004
- 'Autodev Progress',
632
+ 'Autodev Status',
1005
633
  '',
1006
- `Run Mode: ${progress.runMode}`,
1007
- `Project Type: ${progress.projectType}`,
1008
- `Codebase Map: ${progress.codebaseMapExists ? 'ready' : 'missing'}`,
1009
- `Tracks: ${progress.counts.trackCount}`,
1010
- `Active Track: ${progress.activeTrack || 'none'}`,
1011
- `Phases: ${progress.counts.total}`,
1012
- `Planned: ${progress.counts.planned}`,
1013
- `Executed: ${progress.counts.executed}`,
1014
- `Reviewed: ${progress.counts.reviewed}`,
1015
- `Verified: ${progress.counts.verified}`,
1016
- `Tasks: ${progress.counts.tasks}`,
1017
- `Task Summaries: ${progress.counts.tasksDone}`,
1018
- `Next: ${progress.route.command}`,
1019
- progress.route.manualCommand ? `Manual Shortcut: ${progress.route.manualCommand}` : null,
1020
- progress.runMode === 'auto' ? `Auto Continue: ${progress.route.auto_continuable ? 'yes' : 'no'}` : null,
1021
- progress.runMode === 'auto' ? `Pause Reason: ${progress.route.pause_reason || 'none'}` : null,
1022
- '',
1023
- '| Phase | Type | Name | Tasks | Done | Plan | Summary | Review | UAT | Status |',
1024
- '| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |'
1025
- ].filter(Boolean);
634
+ `Project Type: ${status.project_type}`,
635
+ `Stage: ${status.stage}`,
636
+ `Brief: ${status.brief_exists ? 'ready' : 'missing'}`,
637
+ `Context: ${status.context_required ? (status.context_exists ? 'ready' : 'missing') : 'not required'}`,
638
+ `Plan: ${status.plan_exists ? `${status.counts.total} item(s)` : 'missing'}`,
639
+ `Current Item: ${status.current_item || 'none'}`,
640
+ `Last Result: ${status.last_result}`,
641
+ `Next: ${status.route.command}`,
642
+ `Route: ${status.route.kind} (${status.route.reason})`
643
+ ];
1026
644
 
1027
- for (const phase of progress.phases) {
645
+ if (status.plan_items.length > 0) {
1028
646
  lines.push(
1029
- `| ${phase.number} | ${phase.type} | ${phase.name} | ${phase.taskCount} | ${phase.taskDoneCount} | ${phase.planExists ? 'yes' : 'no'} | ${phase.summaryExists ? 'yes' : 'no'} | ${phase.reviewExists ? 'yes' : 'no'} | ${phase.uatExists ? 'yes' : 'no'} | ${phase.status} |`
647
+ '',
648
+ '| Item | Status | Title |',
649
+ '| --- | --- | --- |'
1030
650
  );
1031
- }
1032
651
 
1033
- if (progress.tracks.length > 0) {
1034
- lines.push('', '| Track | Active | Name |', '| --- | --- | --- |');
1035
- for (const track of progress.tracks) {
1036
- lines.push(`| ${track.slug} | ${track.active ? 'yes' : 'no'} | ${track.name} |`);
652
+ for (const item of status.plan_items) {
653
+ lines.push(`| ${item.id} | ${item.status} | ${item.title} |`);
1037
654
  }
1038
655
  }
1039
656
 
1040
657
  return lines.join('\n');
1041
658
  }
1042
659
 
1043
- function dotGet(object, key) {
1044
- return key.split('.').reduce((current, part) => (
1045
- current && Object.prototype.hasOwnProperty.call(current, part) ? current[part] : undefined
1046
- ), object);
1047
- }
660
+ function updateState(cwd, updates = {}) {
661
+ const current = loadState(cwd) || bootstrapProject(cwd).state;
662
+ const next = {
663
+ ...current,
664
+ ...updates,
665
+ project_type: updates.project_type !== undefined
666
+ ? normalizeProjectType(updates.project_type, cwd)
667
+ : current.project_type,
668
+ stage: updates.stage !== undefined ? normalizeStage(updates.stage) : current.stage,
669
+ current_item: updates.current_item !== undefined
670
+ ? normalizeCurrentItem(updates.current_item)
671
+ : current.current_item,
672
+ settings: {
673
+ ...current.settings,
674
+ ...((updates.settings && typeof updates.settings === 'object') ? updates.settings : {})
675
+ }
676
+ };
677
+
678
+ if (updates.last_run_path !== undefined || updates.last_result !== undefined || updates.touch === true) {
679
+ next.last_run_at = new Date().toISOString();
680
+ }
1048
681
 
1049
- function codebasePaths(cwd) {
1050
- const codebaseDir = rootPaths(cwd).codebaseDir;
1051
- return Object.fromEntries(CODEBASE_FILES.map(name => [name.replace(/\.md$/, ''), path.join(codebaseDir, name)]));
682
+ saveState(cwd, next);
683
+ return loadState(cwd);
1052
684
  }
1053
685
 
1054
- function buildStatus(cwd, options = {}) {
1055
- const runMode = normalizeRunMode(options.runMode);
686
+ function createRunReport(cwd, kind = 'run') {
1056
687
  const paths = rootPaths(cwd);
1057
- const route = buildRoute(cwd, { runMode });
1058
- const config = loadConfig(cwd);
1059
- const existingCodebase = detectExistingCodebase(cwd);
1060
- const projectType = config.project.type || (existingCodebase ? 'brownfield' : 'greenfield');
1061
- const activeTrack = readActiveTrack(cwd);
1062
- const tracks = listTracks(cwd);
688
+ ensureDir(paths.runsDir);
1063
689
 
1064
- return {
1065
- cwd,
1066
- run_mode: runMode,
1067
- workspace_root: paths.workspaceRoot,
1068
- autodev_exists: fileExists(paths.root),
1069
- project_exists: fileExists(paths.project),
1070
- existing_code_detected: existingCodebase,
1071
- project_type: projectType,
1072
- project_path: paths.project,
1073
- project_state_path: paths.state,
1074
- active_track_path: paths.activeTrack,
1075
- active_track: activeTrack,
1076
- track_count: tracks.length,
1077
- tracks: tracks.map(track => ({
1078
- slug: track.slug,
1079
- name: track.name,
1080
- active: track.active
1081
- })),
1082
- codebase_map_exists: fileExists(paths.codebaseSummary),
1083
- auto_continuable: route.auto_continuable,
1084
- pause_reason: route.pause_reason,
1085
- route
1086
- };
1087
- }
690
+ const stamp = new Date().toISOString().replace(/[:.]/g, '-');
691
+ const filePath = path.join(paths.runsDir, `${stamp}-${slugify(kind)}.md`);
692
+ const content = [
693
+ '# Run Report',
694
+ '',
695
+ `Kind: ${kind}`,
696
+ `Started: ${new Date().toISOString()}`,
697
+ 'Outcome: pending',
698
+ '',
699
+ '## Observed',
700
+ '- [facts only]',
701
+ '',
702
+ '## Inferred',
703
+ '- [none]',
704
+ '',
705
+ '## Files Changed',
706
+ '- [path]',
707
+ '',
708
+ '## Verification',
709
+ '- [command and result]',
710
+ '',
711
+ '## Unknowns',
712
+ '- [none]',
713
+ ''
714
+ ].join('\n');
1088
715
 
1089
- function buildCleanupPayload(cwd, options = {}) {
1090
- const runMode = normalizeRunMode(options.runMode);
1091
- const paths = rootPaths(cwd);
1092
- const route = buildRoute(cwd, { runMode });
1093
- const activeTrack = readActiveTrack(cwd);
1094
- const track = trackPaths(cwd, activeTrack);
1095
- const phases = activeTrack ? listPhases(cwd, activeTrack) : [];
1096
- const verifiedPhases = phases.filter(phase => phase.uatExists);
1097
- const completedPhases = phases.filter(phase => phase.summaryExists || phase.reviewExists || phase.uatExists);
716
+ fs.writeFileSync(filePath, content, 'utf8');
1098
717
 
1099
718
  return {
1100
- cwd,
1101
- run_mode: runMode,
1102
- autodev_exists: fileExists(paths.root),
1103
- project_exists: fileExists(paths.project),
1104
- project_state_path: paths.state,
1105
- active_track_path: paths.activeTrack,
1106
- active_track: activeTrack,
1107
- track_path: track ? track.dir : null,
1108
- track_state_path: track ? track.state : null,
1109
- phases_dir: track ? track.phasesDir : null,
1110
- archive_root: path.join(paths.root, 'archive'),
1111
- auto_continuable: false,
1112
- pause_reason: runMode === 'auto' ? 'cleanup_confirmation' : null,
1113
- route,
1114
- phase_counts: {
1115
- total: phases.length,
1116
- completed: completedPhases.length,
1117
- verified: verifiedPhases.length
1118
- },
1119
- verified_phase_numbers: verifiedPhases.map(phase => phase.number),
1120
- verified_phase_dirs: verifiedPhases.map(phase => phase.dir)
719
+ kind,
720
+ path: filePath
1121
721
  };
1122
722
  }
1123
723
 
1124
- function initPayload(cwd, mode, requestedPhase, options = {}) {
1125
- const runMode = normalizeRunMode(options.runMode);
1126
- const paths = rootPaths(cwd);
1127
- const config = loadConfig(cwd);
1128
- const route = buildRoute(cwd, { runMode });
1129
- const existingCodebase = detectExistingCodebase(cwd);
1130
- const projectType = config.project.type || (existingCodebase ? 'brownfield' : 'greenfield');
1131
- const activeTrack = readActiveTrack(cwd);
1132
- const tracks = listTracks(cwd);
1133
- const track = trackPaths(cwd, activeTrack);
1134
- const phaseMode = mode === 'review-phase'
1135
- ? 'review'
1136
- : mode === 'verify-work'
1137
- ? 'verify'
1138
- : mode === 'review-task'
1139
- ? 'task_review'
1140
- : mode === 'execute-phase' || mode === 'review-plan'
1141
- ? 'execute'
1142
- : mode === 'plan-phase'
1143
- ? 'plan'
1144
- : null;
1145
- const phase = phaseMode && activeTrack ? resolvePhase(cwd, activeTrack, requestedPhase, phaseMode) : null;
1146
- const tasks = phase ? listTasksForPhaseDetails(phase) : [];
1147
- const nextTask = nextExecutableTask(tasks);
1148
- const lastCompleted = lastCompletedTask(tasks);
1149
- const highestTaskNumber = lastTaskNumber(tasks);
1150
- const dependencyDeadlock = hasDependencyDeadlock(tasks);
1151
- const trackStateSnapshot = track ? readStateSnapshot(track.state) : null;
1152
- const currentTaskNumber = (() => {
1153
- const raw = trackStateSnapshot?.currentTask;
1154
- if (raw && /^\d+$/.test(raw)) {
1155
- return Number(raw);
724
+ function dotGet(object, key) {
725
+ return key.split('.').reduce((current, part) => (
726
+ current && Object.prototype.hasOwnProperty.call(current, part) ? current[part] : undefined
727
+ ), object);
728
+ }
729
+
730
+ function parseOptions(args) {
731
+ const options = {};
732
+
733
+ for (let index = 0; index < args.length; index += 1) {
734
+ const arg = args[index];
735
+ if (!arg.startsWith('--')) {
736
+ continue;
1156
737
  }
1157
- return lastCompleted ? lastCompleted.number : null;
1158
- })();
1159
- const currentTask = tasks.find(task => task.number === currentTaskNumber) || lastCompleted || null;
1160
- const codebase = codebasePaths(cwd);
1161
738
 
1162
- return {
1163
- cwd,
1164
- run_mode: runMode,
1165
- autodev_exists: fileExists(paths.root),
1166
- project_exists: fileExists(paths.project),
1167
- existing_code_detected: existingCodebase,
1168
- project_type: projectType,
1169
- config_path: paths.config,
1170
- project_path: paths.project,
1171
- project_state_path: paths.state,
1172
- active_track_path: paths.activeTrack,
1173
- active_track: activeTrack,
1174
- track_count: tracks.length,
1175
- tracks: tracks.map(trackItem => ({
1176
- slug: trackItem.slug,
1177
- name: trackItem.name,
1178
- active: trackItem.active,
1179
- path: trackItem.paths.dir
1180
- })),
1181
- codebase_dir: paths.codebaseDir,
1182
- codebase_map_exists: fileExists(paths.codebaseSummary),
1183
- codebase_paths: codebase,
1184
- track_path: track ? track.dir : null,
1185
- track_doc_path: track ? track.track : null,
1186
- requirements_path: track ? track.requirements : null,
1187
- roadmap_path: track ? track.roadmap : null,
1188
- track_state_path: track ? track.state : null,
1189
- track_state: trackStateSnapshot,
1190
- phases_dir: track ? track.phasesDir : null,
1191
- auto_continuable: route.auto_continuable,
1192
- pause_reason: route.pause_reason,
1193
- route,
1194
- phase_found: Boolean(phase),
1195
- phase_number: phase ? phase.number : null,
1196
- phase_type: phase ? phase.type : null,
1197
- phase_name: phase ? phase.name : null,
1198
- phase_dir: phase ? phase.dir : null,
1199
- plan_path: phase ? phase.planPath : null,
1200
- summary_path: phase ? phase.summaryPath : null,
1201
- review_path: phase ? phase.reviewPath : null,
1202
- uat_path: phase ? phase.uatPath : null,
1203
- plan_exists: phase ? phase.planExists : false,
1204
- summary_exists: phase ? phase.summaryExists : false,
1205
- review_exists: phase ? phase.reviewExists : false,
1206
- uat_exists: phase ? phase.uatExists : false,
1207
- tasks_dir: phase ? phase.dir : null,
1208
- task_count: tasks.length,
1209
- task_done_count: tasks.filter(task => task.summaryExists).length,
1210
- all_tasks_done: tasks.length > 0 && tasks.every(task => task.summaryExists),
1211
- dependency_deadlock: dependencyDeadlock,
1212
- last_task_number: highestTaskNumber,
1213
- next_task_number: nextTask ? nextTask.number : null,
1214
- next_task_path: nextTask ? nextTask.taskPath : null,
1215
- next_task_summary_path: nextTask ? nextTask.summaryPath : null,
1216
- current_task_number: currentTask ? currentTask.number : null,
1217
- current_task_path: currentTask ? currentTask.taskPath : null,
1218
- current_task_summary_path: currentTask ? currentTask.summaryPath : null,
1219
- last_completed_task_number: lastCompleted ? lastCompleted.number : null,
1220
- last_completed_task_summary_path: lastCompleted ? lastCompleted.summaryPath : null,
1221
- tasks: tasks.map(task => ({
1222
- number: task.number,
1223
- title: task.title,
1224
- status: task.effectiveStatus,
1225
- depends_on: task.dependsOn,
1226
- ready: task.ready,
1227
- task_path: task.taskPath,
1228
- summary_path: task.summaryPath,
1229
- summary_exists: task.summaryExists
1230
- })),
1231
- workflow: config.workflow,
1232
- git_mode: config.git.mode
1233
- };
739
+ const key = arg.slice(2).replace(/-/g, '_');
740
+ const next = args[index + 1];
741
+
742
+ if (!next || next.startsWith('--')) {
743
+ options[key] = true;
744
+ continue;
745
+ }
746
+
747
+ options[key] = next;
748
+ index += 1;
749
+ }
750
+
751
+ return options;
1234
752
  }
1235
753
 
1236
754
  function printJson(value) {
@@ -1238,10 +756,8 @@ function printJson(value) {
1238
756
  }
1239
757
 
1240
758
  function main() {
1241
- const rawArgs = process.argv.slice(2);
1242
- const runMode = rawArgs.includes('--auto') ? 'auto' : DEFAULT_RUN_MODE;
1243
- const args = rawArgs.filter(arg => arg !== '--auto');
1244
759
  const cwd = process.cwd();
760
+ const args = process.argv.slice(2);
1245
761
 
1246
762
  if (args.length === 0) {
1247
763
  process.stderr.write('autodev-tools: command required\n');
@@ -1250,14 +766,27 @@ function main() {
1250
766
 
1251
767
  const [command, ...rest] = args;
1252
768
 
1253
- if (command === 'progress') {
1254
- const mode = rest[0] || 'table';
1255
- const progress = buildProgress(cwd, { runMode });
1256
- if (mode === 'json') {
1257
- printJson(progress);
769
+ if (command === 'bootstrap') {
770
+ const options = parseOptions(rest);
771
+ printJson(bootstrapProject(cwd, {
772
+ projectType: options.project_type
773
+ }));
774
+ return;
775
+ }
776
+
777
+ if (command === 'status') {
778
+ const mode = rest[0] === 'table' ? 'table' : 'json';
779
+ const status = buildStatus(cwd);
780
+ if (mode === 'table') {
781
+ process.stdout.write(`${renderStatusTable(status)}\n`);
1258
782
  return;
1259
783
  }
1260
- process.stdout.write(`${renderProgressTable(progress)}\n`);
784
+ printJson(status);
785
+ return;
786
+ }
787
+
788
+ if (command === 'route') {
789
+ printJson(buildRoute(cwd));
1261
790
  return;
1262
791
  }
1263
792
 
@@ -1267,7 +796,9 @@ function main() {
1267
796
  process.stderr.write('autodev-tools: config key required\n');
1268
797
  process.exit(1);
1269
798
  }
1270
- const value = dotGet(loadConfig(cwd), key);
799
+
800
+ const state = loadState(cwd) || DEFAULT_STATE;
801
+ const value = dotGet(state, key);
1271
802
  if (typeof value === 'object' && value !== null) {
1272
803
  printJson(value);
1273
804
  } else if (value !== undefined) {
@@ -1276,35 +807,41 @@ function main() {
1276
807
  return;
1277
808
  }
1278
809
 
1279
- if (command === 'status') {
1280
- printJson(buildStatus(cwd, { runMode }));
1281
- return;
1282
- }
1283
-
1284
- if (command === 'cleanup') {
1285
- printJson(buildCleanupPayload(cwd, { runMode }));
1286
- return;
1287
- }
810
+ if (command === 'update') {
811
+ const options = parseOptions(rest);
812
+ const updates = {};
1288
813
 
1289
- if (command === 'route') {
1290
- const mode = rest[0] || 'json';
1291
- const route = buildRoute(cwd, { runMode });
1292
- if (mode === 'json') {
1293
- printJson(route);
1294
- return;
814
+ if (options.project_type !== undefined) {
815
+ updates.project_type = options.project_type;
1295
816
  }
1296
- process.stdout.write(`${route.command}\n`);
817
+ if (options.stage !== undefined) {
818
+ updates.stage = options.stage;
819
+ }
820
+ if (options.current_item !== undefined) {
821
+ updates.current_item = options.current_item;
822
+ }
823
+ if (options.last_result !== undefined) {
824
+ updates.last_result = options.last_result;
825
+ }
826
+ if (options.last_run_path !== undefined) {
827
+ updates.last_run_path = options.last_run_path;
828
+ }
829
+ if (options.auto_format !== undefined) {
830
+ updates.settings = {
831
+ auto_format: options.auto_format !== 'false'
832
+ };
833
+ }
834
+ if (options.touch === true) {
835
+ updates.touch = true;
836
+ }
837
+
838
+ printJson(updateState(cwd, updates));
1297
839
  return;
1298
840
  }
1299
841
 
1300
- if (command === 'init') {
1301
- const mode = rest[0];
1302
- const phase = rest[1];
1303
- if (!mode) {
1304
- process.stderr.write('autodev-tools: init mode required\n');
1305
- process.exit(1);
1306
- }
1307
- printJson(initPayload(cwd, mode, phase, { runMode }));
842
+ if (command === 'create-run') {
843
+ const kind = rest[0] || 'run';
844
+ printJson(createRunReport(cwd, kind));
1308
845
  return;
1309
846
  }
1310
847
 
@@ -1317,26 +854,20 @@ if (require.main === module) {
1317
854
  }
1318
855
 
1319
856
  module.exports = {
857
+ DEFAULT_STATE,
1320
858
  autodevDir,
1321
- buildCleanupPayload,
1322
- buildProgress,
859
+ bootstrapProject,
1323
860
  buildRoute,
1324
861
  buildStatus,
862
+ createRunReport,
1325
863
  detectExistingCodebase,
1326
864
  findWorkspaceRoot,
1327
- initPayload,
1328
- listPhases,
1329
- listTracks,
1330
- loadConfig,
1331
- parseRoadmap,
1332
- readActiveTrack,
1333
- renderProgressTable,
1334
- resolvePhase,
865
+ loadState,
866
+ normalizeCurrentItem,
867
+ normalizeProjectType,
868
+ parsePlan,
869
+ renderStatusTable,
1335
870
  rootPaths,
1336
- trackPaths,
1337
- listTasksForPhaseDetails,
1338
- lastCompletedTask,
1339
- lastTaskNumber,
1340
- hasDependencyDeadlock,
1341
- nextExecutableTask
871
+ saveState,
872
+ updateState
1342
873
  };