@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.
- package/.claude-plugin/plugin.json +2 -2
- package/PUBLISH.md +9 -40
- package/README.md +70 -104
- package/autodev/bin/autodev-tools.cjs +521 -990
- package/autodev/templates/brief.md +19 -0
- package/autodev/templates/context.md +16 -0
- package/autodev/templates/plan.md +26 -46
- package/autodev/templates/run.md +20 -0
- package/bin/install.js +219 -422
- package/commands/autodev/index.md +117 -9
- package/commands/autodev/status.md +22 -0
- package/hooks/autodev-auto-format.js +3 -3
- package/hooks/autodev-git-guard.js +5 -7
- package/hooks/autodev-paths.js +3 -3
- package/package.json +4 -5
- package/scripts/run-tests.cjs +10 -0
- package/agents/autodev-codebase-domain.md +0 -25
- package/agents/autodev-codebase-quality.md +0 -25
- package/agents/autodev-codebase-runtime.md +0 -25
- package/agents/autodev-codebase-structure.md +0 -25
- package/agents/autodev-review-integration.md +0 -30
- package/agents/autodev-review-polish.md +0 -30
- package/agents/autodev-review-quality.md +0 -30
- package/agents/autodev-review-security.md +0 -30
- package/agents/autodev-task-worker.md +0 -39
- package/autodev/templates/codebase/domain.md +0 -13
- package/autodev/templates/codebase/quality.md +0 -13
- package/autodev/templates/codebase/runtime.md +0 -13
- package/autodev/templates/codebase/structure.md +0 -13
- package/autodev/templates/codebase/summary.md +0 -13
- package/autodev/templates/config.json +0 -22
- package/autodev/templates/project-state.md +0 -15
- package/autodev/templates/project.md +0 -24
- package/autodev/templates/requirements.md +0 -14
- package/autodev/templates/review.md +0 -27
- package/autodev/templates/roadmap.md +0 -17
- package/autodev/templates/state.md +0 -15
- package/autodev/templates/summary.md +0 -22
- package/autodev/templates/task-summary.md +0 -18
- package/autodev/templates/task.md +0 -23
- package/autodev/templates/track-state.md +0 -16
- package/autodev/templates/track.md +0 -24
- package/autodev/templates/uat.md +0 -18
- package/autodev/workflows/autodev-auto.md +0 -62
- package/autodev/workflows/autodev.md +0 -83
- package/autodev/workflows/cleanup.md +0 -51
- package/autodev/workflows/execute-phase.md +0 -144
- package/autodev/workflows/explore-codebase.md +0 -70
- package/autodev/workflows/help.md +0 -113
- package/autodev/workflows/new-project.md +0 -108
- package/autodev/workflows/plan-phase.md +0 -134
- package/autodev/workflows/progress.md +0 -18
- package/autodev/workflows/review-phase.md +0 -80
- package/autodev/workflows/review-plan.md +0 -55
- package/autodev/workflows/review-task.md +0 -70
- package/autodev/workflows/verify-work.md +0 -71
- package/commands/autodev/auto.md +0 -27
- package/commands/autodev/cleanup.md +0 -23
- package/commands/autodev/execute-phase.md +0 -29
- package/commands/autodev/explore-codebase.md +0 -33
- package/commands/autodev/help.md +0 -18
- package/commands/autodev/new-project.md +0 -30
- package/commands/autodev/plan-phase.md +0 -26
- package/commands/autodev/progress.md +0 -18
- package/commands/autodev/review-phase.md +0 -29
- package/commands/autodev/review-task.md +0 -25
- package/commands/autodev/verify-work.md +0 -24
- package/hooks/autodev-context-monitor.js +0 -59
- package/hooks/autodev-phase-boundary.sh +0 -49
- package/hooks/autodev-prompt-guard.js +0 -78
- package/hooks/autodev-read-guard.js +0 -42
- package/hooks/autodev-session-state.sh +0 -51
- package/hooks/autodev-statusline.js +0 -83
- package/hooks/autodev-workflow-guard.js +0 -43
- 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
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
|
|
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
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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
|
|
103
|
-
|
|
79
|
+
function isFile(filePath) {
|
|
80
|
+
try {
|
|
81
|
+
return fs.statSync(filePath).isFile();
|
|
82
|
+
} catch {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
104
85
|
}
|
|
105
86
|
|
|
106
|
-
function
|
|
107
|
-
|
|
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
|
|
139
|
-
|
|
140
|
-
|
|
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, '-') || '
|
|
117
|
+
.replace(/-+/g, '-') || 'run';
|
|
152
118
|
}
|
|
153
119
|
|
|
154
|
-
function
|
|
155
|
-
|
|
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
|
|
159
|
-
return
|
|
143
|
+
function autodevDir(cwd) {
|
|
144
|
+
return path.join(findWorkspaceRoot(cwd), '.autodev');
|
|
160
145
|
}
|
|
161
146
|
|
|
162
|
-
function
|
|
163
|
-
|
|
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
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
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
|
|
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()
|
|
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
|
|
271
|
-
if (
|
|
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
|
|
255
|
+
return detectExistingCodebase(fallbackCwd) ? 'brownfield' : 'greenfield';
|
|
278
256
|
}
|
|
279
257
|
|
|
280
|
-
function
|
|
281
|
-
const
|
|
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
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
}
|
|
263
|
+
function normalizeCurrentItem(value) {
|
|
264
|
+
if (value === null || value === undefined || value === '' || value === 'none') {
|
|
265
|
+
return null;
|
|
266
|
+
}
|
|
295
267
|
|
|
296
|
-
|
|
297
|
-
return
|
|
268
|
+
const raw = String(value).trim();
|
|
269
|
+
return /^\d+$/.test(raw) ? raw.padStart(2, '0') : null;
|
|
298
270
|
}
|
|
299
271
|
|
|
300
|
-
function
|
|
301
|
-
|
|
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
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
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
|
|
318
|
-
const
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
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
|
|
344
|
-
const
|
|
345
|
-
if (!
|
|
346
|
-
return
|
|
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
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
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
|
-
|
|
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
|
|
368
|
-
const
|
|
369
|
-
|
|
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
|
-
|
|
382
|
-
|
|
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
|
-
|
|
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
|
|
398
|
-
|
|
399
|
-
|
|
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
|
-
|
|
403
|
-
const currentPhaseNumber = currentPhaseRaw && /^\d+$/.test(currentPhaseRaw)
|
|
404
|
-
? Number(currentPhaseRaw)
|
|
405
|
-
: null;
|
|
350
|
+
saveState(cwd, nextState);
|
|
406
351
|
|
|
407
352
|
return {
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
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
|
|
421
|
-
return
|
|
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
|
|
429
|
-
|
|
430
|
-
|
|
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
|
|
459
|
-
|
|
460
|
-
|
|
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
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
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
|
-
|
|
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
|
-
|
|
573
|
-
|
|
574
|
-
|
|
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
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
return null;
|
|
591
|
-
}
|
|
402
|
+
if (!trimmed) {
|
|
403
|
+
continue;
|
|
404
|
+
}
|
|
592
405
|
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
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
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
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
|
-
|
|
605
|
-
if (
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
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
|
-
|
|
616
|
-
if (
|
|
617
|
-
|
|
618
|
-
|
|
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
|
-
|
|
626
|
-
|
|
627
|
-
|
|
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
|
-
|
|
633
|
-
if (
|
|
634
|
-
|
|
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
|
-
|
|
640
|
-
|
|
641
|
-
|
|
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
|
|
455
|
+
return item;
|
|
649
456
|
}
|
|
650
457
|
|
|
651
|
-
function
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
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
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
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
|
-
|
|
684
|
-
|
|
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
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
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
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
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
|
-
|
|
725
|
-
|
|
499
|
+
function determineNextItem(items, preferredItemId) {
|
|
500
|
+
const preferred = preferredItemId
|
|
501
|
+
? items.find(item => item.id === normalizeCurrentItem(preferredItemId))
|
|
726
502
|
: null;
|
|
727
503
|
|
|
728
|
-
if (
|
|
729
|
-
|
|
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
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
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
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
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
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
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
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
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
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
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
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
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
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
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
|
|
948
|
-
kind: '
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
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
|
|
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
|
|
964
|
-
const
|
|
965
|
-
const
|
|
966
|
-
const
|
|
967
|
-
const
|
|
968
|
-
|
|
969
|
-
|
|
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
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
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
|
|
999
|
-
if (!progress.initialized) {
|
|
1000
|
-
return 'No .autodev project found.\nNext: /autodev';
|
|
1001
|
-
}
|
|
1002
|
-
|
|
630
|
+
function renderStatusTable(status) {
|
|
1003
631
|
const lines = [
|
|
1004
|
-
'Autodev
|
|
632
|
+
'Autodev Status',
|
|
1005
633
|
'',
|
|
1006
|
-
`
|
|
1007
|
-
`
|
|
1008
|
-
`
|
|
1009
|
-
`
|
|
1010
|
-
`
|
|
1011
|
-
`
|
|
1012
|
-
`
|
|
1013
|
-
`
|
|
1014
|
-
`
|
|
1015
|
-
|
|
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
|
-
|
|
645
|
+
if (status.plan_items.length > 0) {
|
|
1028
646
|
lines.push(
|
|
1029
|
-
|
|
647
|
+
'',
|
|
648
|
+
'| Item | Status | Title |',
|
|
649
|
+
'| --- | --- | --- |'
|
|
1030
650
|
);
|
|
1031
|
-
}
|
|
1032
651
|
|
|
1033
|
-
|
|
1034
|
-
|
|
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
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
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
|
-
|
|
1050
|
-
|
|
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
|
|
1055
|
-
const runMode = normalizeRunMode(options.runMode);
|
|
686
|
+
function createRunReport(cwd, kind = 'run') {
|
|
1056
687
|
const paths = rootPaths(cwd);
|
|
1057
|
-
|
|
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
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1101
|
-
|
|
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
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
const
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
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
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
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 === '
|
|
1254
|
-
const
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 === '
|
|
1280
|
-
|
|
1281
|
-
|
|
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
|
-
|
|
1290
|
-
|
|
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
|
-
|
|
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 === '
|
|
1301
|
-
const
|
|
1302
|
-
|
|
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
|
-
|
|
1322
|
-
buildProgress,
|
|
859
|
+
bootstrapProject,
|
|
1323
860
|
buildRoute,
|
|
1324
861
|
buildStatus,
|
|
862
|
+
createRunReport,
|
|
1325
863
|
detectExistingCodebase,
|
|
1326
864
|
findWorkspaceRoot,
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
readActiveTrack,
|
|
1333
|
-
renderProgressTable,
|
|
1334
|
-
resolvePhase,
|
|
865
|
+
loadState,
|
|
866
|
+
normalizeCurrentItem,
|
|
867
|
+
normalizeProjectType,
|
|
868
|
+
parsePlan,
|
|
869
|
+
renderStatusTable,
|
|
1335
870
|
rootPaths,
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
lastCompletedTask,
|
|
1339
|
-
lastTaskNumber,
|
|
1340
|
-
hasDependencyDeadlock,
|
|
1341
|
-
nextExecutableTask
|
|
871
|
+
saveState,
|
|
872
|
+
updateState
|
|
1342
873
|
};
|