@mthanhlm/autodev 0.4.3 → 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 -91
- package/autodev/bin/autodev-tools.cjs +587 -811
- 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 +229 -342
- 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 -13
- 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 -13
- 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 -14
- package/autodev/templates/track.md +0 -24
- package/autodev/templates/uat.md +0 -18
- package/autodev/workflows/autodev.md +0 -79
- package/autodev/workflows/cleanup.md +0 -51
- package/autodev/workflows/execute-phase.md +0 -127
- package/autodev/workflows/explore-codebase.md +0 -66
- package/autodev/workflows/help.md +0 -110
- package/autodev/workflows/new-project.md +0 -101
- package/autodev/workflows/plan-phase.md +0 -126
- package/autodev/workflows/progress.md +0 -18
- package/autodev/workflows/review-phase.md +0 -73
- package/autodev/workflows/review-plan.md +0 -55
- package/autodev/workflows/review-task.md +0 -70
- package/autodev/workflows/verify-work.md +0 -57
- 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 -55
- package/hooks/autodev-read-guard.js +0 -42
- package/hooks/autodev-session-state.sh +0 -51
- package/hooks/autodev-statusline.js +0 -78
- package/hooks/autodev-workflow-guard.js +0 -43
- package/hooks/hooks.json +0 -89
|
@@ -3,31 +3,119 @@
|
|
|
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
|
-
mode: 'read-only'
|
|
17
|
-
},
|
|
18
|
-
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',
|
|
19
16
|
auto_format: true,
|
|
20
|
-
|
|
21
|
-
read_guard: true,
|
|
22
|
-
workflow_guard: true,
|
|
23
|
-
prompt_guard: true,
|
|
24
|
-
git_guard: true,
|
|
25
|
-
session_state: true,
|
|
26
|
-
phase_boundary: true
|
|
17
|
+
background_tasks_disabled: true
|
|
27
18
|
}
|
|
28
19
|
};
|
|
29
20
|
|
|
30
|
-
const
|
|
21
|
+
const CODE_FILE_EXTENSIONS = new Set([
|
|
22
|
+
'.js', '.jsx', '.cjs', '.mjs',
|
|
23
|
+
'.ts', '.tsx',
|
|
24
|
+
'.py',
|
|
25
|
+
'.go',
|
|
26
|
+
'.rs',
|
|
27
|
+
'.java',
|
|
28
|
+
'.rb',
|
|
29
|
+
'.php',
|
|
30
|
+
'.cs',
|
|
31
|
+
'.cpp',
|
|
32
|
+
'.cc',
|
|
33
|
+
'.c',
|
|
34
|
+
'.h',
|
|
35
|
+
'.hpp',
|
|
36
|
+
'.swift',
|
|
37
|
+
'.kt',
|
|
38
|
+
'.kts',
|
|
39
|
+
'.scala'
|
|
40
|
+
]);
|
|
41
|
+
|
|
42
|
+
const NESTED_CODE_MARKER_FILES = new Set([
|
|
43
|
+
'package.json',
|
|
44
|
+
'package-lock.json',
|
|
45
|
+
'pnpm-lock.yaml',
|
|
46
|
+
'yarn.lock',
|
|
47
|
+
'tsconfig.json',
|
|
48
|
+
'jsconfig.json',
|
|
49
|
+
'pyproject.toml',
|
|
50
|
+
'requirements.txt',
|
|
51
|
+
'Pipfile',
|
|
52
|
+
'Cargo.toml',
|
|
53
|
+
'go.mod',
|
|
54
|
+
'pom.xml',
|
|
55
|
+
'build.gradle',
|
|
56
|
+
'Dockerfile'
|
|
57
|
+
]);
|
|
58
|
+
|
|
59
|
+
const IGNORED_SCAN_DIRS = new Set([
|
|
60
|
+
'.autodev',
|
|
61
|
+
'.claude',
|
|
62
|
+
'.git',
|
|
63
|
+
'node_modules',
|
|
64
|
+
'dist',
|
|
65
|
+
'build',
|
|
66
|
+
'coverage',
|
|
67
|
+
'tmp',
|
|
68
|
+
'vendor'
|
|
69
|
+
]);
|
|
70
|
+
|
|
71
|
+
function fileExists(filePath) {
|
|
72
|
+
try {
|
|
73
|
+
return fs.existsSync(filePath);
|
|
74
|
+
} catch {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function isFile(filePath) {
|
|
80
|
+
try {
|
|
81
|
+
return fs.statSync(filePath).isFile();
|
|
82
|
+
} catch {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function ensureDir(dirPath) {
|
|
88
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function readText(filePath) {
|
|
92
|
+
try {
|
|
93
|
+
return fs.readFileSync(filePath, 'utf8');
|
|
94
|
+
} catch {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function readJson(filePath, fallback = null) {
|
|
100
|
+
try {
|
|
101
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
102
|
+
} catch {
|
|
103
|
+
return fallback;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function writeJson(filePath, value) {
|
|
108
|
+
ensureDir(path.dirname(filePath));
|
|
109
|
+
fs.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`, 'utf8');
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function slugify(value) {
|
|
113
|
+
return String(value || 'run')
|
|
114
|
+
.toLowerCase()
|
|
115
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
116
|
+
.replace(/^-+|-+$/g, '')
|
|
117
|
+
.replace(/-+/g, '-') || 'run';
|
|
118
|
+
}
|
|
31
119
|
|
|
32
120
|
function findWorkspaceRoot(startDir) {
|
|
33
121
|
let cursor = path.resolve(startDir || process.cwd());
|
|
@@ -62,70 +150,63 @@ function rootPaths(cwd) {
|
|
|
62
150
|
return {
|
|
63
151
|
workspaceRoot,
|
|
64
152
|
root,
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
tracksDir: path.join(root, 'tracks')
|
|
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')
|
|
72
159
|
};
|
|
73
160
|
}
|
|
74
161
|
|
|
75
|
-
function
|
|
76
|
-
|
|
77
|
-
return fs.readFileSync(filePath, 'utf8');
|
|
78
|
-
} catch {
|
|
79
|
-
return null;
|
|
80
|
-
}
|
|
162
|
+
function codeFileNameLooksReal(entryName) {
|
|
163
|
+
return CODE_FILE_EXTENSIONS.has(path.extname(entryName).toLowerCase());
|
|
81
164
|
}
|
|
82
165
|
|
|
83
|
-
function
|
|
84
|
-
|
|
85
|
-
return
|
|
86
|
-
} catch {
|
|
87
|
-
return fallback;
|
|
166
|
+
function directoryContainsCodeMarker(dirPath, depth = 2) {
|
|
167
|
+
if (depth < 0 || !fileExists(dirPath)) {
|
|
168
|
+
return false;
|
|
88
169
|
}
|
|
89
|
-
}
|
|
90
170
|
|
|
91
|
-
|
|
171
|
+
let entries;
|
|
92
172
|
try {
|
|
93
|
-
|
|
173
|
+
entries = fs.readdirSync(dirPath, { withFileTypes: true });
|
|
94
174
|
} catch {
|
|
95
175
|
return false;
|
|
96
176
|
}
|
|
97
|
-
}
|
|
98
177
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
.
|
|
105
|
-
|
|
178
|
+
for (const entry of entries) {
|
|
179
|
+
if (entry.name.startsWith('.')) {
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const fullPath = path.join(dirPath, entry.name);
|
|
184
|
+
if (entry.isFile()) {
|
|
185
|
+
if (codeFileNameLooksReal(entry.name) || NESTED_CODE_MARKER_FILES.has(entry.name)) {
|
|
186
|
+
return true;
|
|
187
|
+
}
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (!entry.isDirectory() || depth === 0 || IGNORED_SCAN_DIRS.has(entry.name)) {
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
106
194
|
|
|
107
|
-
|
|
108
|
-
|
|
195
|
+
if (directoryContainsCodeMarker(fullPath, depth - 1)) {
|
|
196
|
+
return true;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return false;
|
|
109
201
|
}
|
|
110
202
|
|
|
111
203
|
function detectExistingCodebase(cwd) {
|
|
112
204
|
const workspaceRoot = findWorkspaceRoot(cwd);
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
'tsconfig.json',
|
|
119
|
-
'jsconfig.json',
|
|
120
|
-
'pyproject.toml',
|
|
121
|
-
'requirements.txt',
|
|
122
|
-
'Pipfile',
|
|
123
|
-
'Cargo.toml',
|
|
124
|
-
'go.mod',
|
|
125
|
-
'pom.xml',
|
|
126
|
-
'build.gradle',
|
|
127
|
-
'Dockerfile'
|
|
128
|
-
];
|
|
205
|
+
|
|
206
|
+
if ([...NESTED_CODE_MARKER_FILES].some(name => fileExists(path.join(workspaceRoot, name)))) {
|
|
207
|
+
return true;
|
|
208
|
+
}
|
|
209
|
+
|
|
129
210
|
const knownDirs = [
|
|
130
211
|
'src',
|
|
131
212
|
'app',
|
|
@@ -141,10 +222,6 @@ function detectExistingCodebase(cwd) {
|
|
|
141
222
|
'__tests__'
|
|
142
223
|
];
|
|
143
224
|
|
|
144
|
-
if (knownFiles.some(name => fileExists(path.join(workspaceRoot, name)))) {
|
|
145
|
-
return true;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
225
|
if (knownDirs.some(name => fileExists(path.join(workspaceRoot, name)))) {
|
|
149
226
|
return true;
|
|
150
227
|
}
|
|
@@ -152,842 +229,526 @@ function detectExistingCodebase(cwd) {
|
|
|
152
229
|
try {
|
|
153
230
|
const entries = fs.readdirSync(workspaceRoot, { withFileTypes: true });
|
|
154
231
|
return entries.some(entry => {
|
|
155
|
-
if (entry.name
|
|
232
|
+
if (entry.name.startsWith('.') || IGNORED_SCAN_DIRS.has(entry.name)) {
|
|
156
233
|
return false;
|
|
157
234
|
}
|
|
158
|
-
|
|
235
|
+
|
|
236
|
+
if (entry.isFile()) {
|
|
237
|
+
return codeFileNameLooksReal(entry.name);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (!entry.isDirectory()) {
|
|
241
|
+
return false;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return directoryContainsCodeMarker(path.join(workspaceRoot, entry.name));
|
|
159
245
|
});
|
|
160
246
|
} catch {
|
|
161
247
|
return false;
|
|
162
248
|
}
|
|
163
249
|
}
|
|
164
250
|
|
|
165
|
-
function
|
|
166
|
-
if (
|
|
167
|
-
return
|
|
168
|
-
...defaultValue,
|
|
169
|
-
...((actualValue && typeof actualValue === 'object' && !Array.isArray(actualValue)) ? actualValue : {})
|
|
170
|
-
};
|
|
251
|
+
function normalizeProjectType(value, fallbackCwd = process.cwd()) {
|
|
252
|
+
if (value === 'brownfield' || value === 'greenfield') {
|
|
253
|
+
return value;
|
|
171
254
|
}
|
|
172
|
-
return
|
|
255
|
+
return detectExistingCodebase(fallbackCwd) ? 'brownfield' : 'greenfield';
|
|
173
256
|
}
|
|
174
257
|
|
|
175
|
-
function
|
|
176
|
-
const
|
|
177
|
-
return
|
|
178
|
-
project: mergeSection(DEFAULT_CONFIG.project, config.project),
|
|
179
|
-
workflow: mergeSection(DEFAULT_CONFIG.workflow, config.workflow),
|
|
180
|
-
execution: mergeSection(DEFAULT_CONFIG.execution, config.execution),
|
|
181
|
-
git: mergeSection(DEFAULT_CONFIG.git, config.git),
|
|
182
|
-
hooks: mergeSection(DEFAULT_CONFIG.hooks, config.hooks)
|
|
183
|
-
};
|
|
258
|
+
function normalizeStage(value) {
|
|
259
|
+
const allowed = new Set(['briefing', 'context', 'planning', 'execution', 'blocked', 'complete']);
|
|
260
|
+
return allowed.has(value) ? value : DEFAULT_STATE.stage;
|
|
184
261
|
}
|
|
185
262
|
|
|
186
|
-
function
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
}
|
|
263
|
+
function normalizeCurrentItem(value) {
|
|
264
|
+
if (value === null || value === undefined || value === '' || value === 'none') {
|
|
265
|
+
return null;
|
|
266
|
+
}
|
|
190
267
|
|
|
191
|
-
|
|
192
|
-
return
|
|
268
|
+
const raw = String(value).trim();
|
|
269
|
+
return /^\d+$/.test(raw) ? raw.padStart(2, '0') : null;
|
|
193
270
|
}
|
|
194
271
|
|
|
195
|
-
function
|
|
196
|
-
|
|
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)) {
|
|
197
276
|
return null;
|
|
198
277
|
}
|
|
199
278
|
|
|
200
|
-
const dir = trackDir(cwd, slug);
|
|
201
279
|
return {
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
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
|
+
}
|
|
209
289
|
};
|
|
210
290
|
}
|
|
211
291
|
|
|
212
|
-
function
|
|
213
|
-
const
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
return match ? match[1].trim() : entry.name;
|
|
226
|
-
})();
|
|
227
|
-
|
|
228
|
-
return {
|
|
229
|
-
slug: entry.name,
|
|
230
|
-
name: trackName,
|
|
231
|
-
paths,
|
|
232
|
-
active: readActiveTrack(cwd) === entry.name
|
|
233
|
-
};
|
|
234
|
-
})
|
|
235
|
-
.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
|
+
});
|
|
236
305
|
}
|
|
237
306
|
|
|
238
|
-
function
|
|
239
|
-
const
|
|
240
|
-
if (!
|
|
241
|
-
return
|
|
307
|
+
function archiveLegacyState(cwd) {
|
|
308
|
+
const paths = rootPaths(cwd);
|
|
309
|
+
if (!fileExists(paths.root) || fileExists(paths.state)) {
|
|
310
|
+
return null;
|
|
242
311
|
}
|
|
243
312
|
|
|
244
|
-
const
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
return phases;
|
|
254
|
-
}
|
|
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
|
+
]);
|
|
255
322
|
|
|
256
|
-
|
|
257
|
-
const track = trackPaths(cwd, slug);
|
|
258
|
-
if (!track) {
|
|
323
|
+
if (!entries.some(entry => legacyMarkers.has(entry) || /\.(md|json)$/.test(entry))) {
|
|
259
324
|
return null;
|
|
260
325
|
}
|
|
261
326
|
|
|
262
|
-
const
|
|
263
|
-
const
|
|
264
|
-
|
|
265
|
-
return {
|
|
266
|
-
...phase,
|
|
267
|
-
padded: prefix,
|
|
268
|
-
dir,
|
|
269
|
-
planPath: path.join(dir, `${prefix}-PLAN.md`),
|
|
270
|
-
summaryPath: path.join(dir, `${prefix}-SUMMARY.md`),
|
|
271
|
-
reviewPath: path.join(dir, `${prefix}-REVIEW.md`),
|
|
272
|
-
uatPath: path.join(dir, `${prefix}-UAT.md`)
|
|
273
|
-
};
|
|
274
|
-
}
|
|
327
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
328
|
+
const archiveDir = path.join(paths.legacyDir, timestamp);
|
|
329
|
+
ensureDir(archiveDir);
|
|
275
330
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
return null;
|
|
331
|
+
for (const entry of entries) {
|
|
332
|
+
fs.renameSync(path.join(paths.root, entry), path.join(archiveDir, entry));
|
|
279
333
|
}
|
|
280
|
-
|
|
281
|
-
return
|
|
334
|
+
|
|
335
|
+
return archiveDir;
|
|
282
336
|
}
|
|
283
337
|
|
|
284
|
-
function
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
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
|
+
};
|
|
288
349
|
|
|
289
|
-
|
|
290
|
-
const currentPhaseNumber = currentPhaseRaw && /^\d+$/.test(currentPhaseRaw)
|
|
291
|
-
? Number(currentPhaseRaw)
|
|
292
|
-
: null;
|
|
350
|
+
saveState(cwd, nextState);
|
|
293
351
|
|
|
294
352
|
return {
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
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)
|
|
302
364
|
};
|
|
303
365
|
}
|
|
304
366
|
|
|
305
|
-
function
|
|
306
|
-
return
|
|
367
|
+
function itemIdFromTitleNumber(value) {
|
|
368
|
+
return String(value).padStart(2, '0');
|
|
307
369
|
}
|
|
308
370
|
|
|
309
|
-
function
|
|
310
|
-
return
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
function parseTaskDependsOn(value) {
|
|
314
|
-
if (!value || /^none$/i.test(value)) {
|
|
315
|
-
return [];
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
return String(value)
|
|
319
|
-
.split(/[,\s]+/)
|
|
371
|
+
function parseInlineList(value) {
|
|
372
|
+
return String(value || '')
|
|
373
|
+
.split(',')
|
|
320
374
|
.map(part => part.trim())
|
|
321
|
-
.filter(Boolean)
|
|
322
|
-
.map(part => {
|
|
323
|
-
const match = part.match(/(\d+)/);
|
|
324
|
-
return match ? Number(match[1]) : null;
|
|
325
|
-
})
|
|
326
|
-
.filter(number => Number.isFinite(number));
|
|
375
|
+
.filter(Boolean);
|
|
327
376
|
}
|
|
328
377
|
|
|
329
|
-
function
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
const heading = content.match(/^#\s+Task\s+\d+\s*:\s*(.+)$/mi);
|
|
335
|
-
if (heading) {
|
|
336
|
-
return heading[1].trim();
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
const generic = content.match(/^#\s+(.+)$/m);
|
|
340
|
-
return generic ? generic[1].trim() : `Task ${String(fallbackNumber).padStart(2, '0')}`;
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
function listTasksForPhaseDetails(phaseDetails) {
|
|
344
|
-
if (!phaseDetails || !fileExists(phaseDetails.dir)) {
|
|
345
|
-
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;
|
|
346
383
|
}
|
|
347
384
|
|
|
348
|
-
const
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
const number = Number(match[1]);
|
|
360
|
-
const padded = String(number).padStart(2, '0');
|
|
361
|
-
const taskPath = path.join(phaseDetails.dir, name);
|
|
362
|
-
const summaryPath = path.join(phaseDetails.dir, `TASK-${padded}-SUMMARY.md`);
|
|
363
|
-
const content = readText(taskPath);
|
|
364
|
-
const status = readSingleLineField(content, 'Status') || 'pending';
|
|
365
|
-
const dependsOn = parseTaskDependsOn(readSingleLineField(content, 'Depends On'));
|
|
366
|
-
const summaryExists = fileExists(summaryPath);
|
|
367
|
-
|
|
368
|
-
return {
|
|
369
|
-
number,
|
|
370
|
-
padded,
|
|
371
|
-
title: readTaskTitle(content, number),
|
|
372
|
-
status,
|
|
373
|
-
dependsOn,
|
|
374
|
-
taskPath,
|
|
375
|
-
summaryPath,
|
|
376
|
-
summaryExists
|
|
377
|
-
};
|
|
378
|
-
})
|
|
379
|
-
.filter(Boolean)
|
|
380
|
-
.sort((left, right) => left.number - right.number);
|
|
381
|
-
|
|
382
|
-
const doneSet = new Set(tasks.filter(task => task.summaryExists).map(task => task.number));
|
|
383
|
-
|
|
384
|
-
return tasks.map(task => ({
|
|
385
|
-
...task,
|
|
386
|
-
ready: !task.summaryExists && task.dependsOn.every(dep => doneSet.has(dep)),
|
|
387
|
-
effectiveStatus: task.summaryExists ? 'done' : task.status
|
|
388
|
-
}));
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
function nextExecutableTask(tasks) {
|
|
392
|
-
return tasks.find(task => !task.summaryExists && task.ready) || null;
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
function lastCompletedTask(tasks) {
|
|
396
|
-
const completed = tasks.filter(task => task.summaryExists);
|
|
397
|
-
return completed.length > 0 ? completed[completed.length - 1] : null;
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
function lastTaskNumber(tasks) {
|
|
401
|
-
return tasks.reduce((highest, task) => Math.max(highest, task.number), 0);
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
function hasDependencyDeadlock(tasks) {
|
|
405
|
-
return tasks.some(task => !task.summaryExists) && !nextExecutableTask(tasks);
|
|
406
|
-
}
|
|
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: []
|
|
394
|
+
};
|
|
407
395
|
|
|
408
|
-
|
|
409
|
-
const track = trackPaths(cwd, slug);
|
|
410
|
-
if (!track || !fileExists(track.roadmap)) {
|
|
411
|
-
return [];
|
|
412
|
-
}
|
|
396
|
+
let activeSection = null;
|
|
413
397
|
|
|
414
|
-
|
|
415
|
-
const
|
|
416
|
-
const
|
|
417
|
-
const summaryExists = fileExists(details.summaryPath);
|
|
418
|
-
const reviewExists = fileExists(details.reviewPath);
|
|
419
|
-
const uatExists = fileExists(details.uatPath);
|
|
420
|
-
const tasks = listTasksForPhaseDetails(details);
|
|
421
|
-
const taskCount = tasks.length;
|
|
422
|
-
const taskDoneCount = tasks.filter(task => task.summaryExists).length;
|
|
423
|
-
const status = uatExists
|
|
424
|
-
? 'verified'
|
|
425
|
-
: reviewExists
|
|
426
|
-
? 'reviewed'
|
|
427
|
-
: summaryExists
|
|
428
|
-
? 'executed'
|
|
429
|
-
: planExists && taskCount > 0 && taskDoneCount === taskCount
|
|
430
|
-
? 'tasks_complete'
|
|
431
|
-
: planExists && taskDoneCount > 0
|
|
432
|
-
? 'executing'
|
|
433
|
-
: planExists
|
|
434
|
-
? 'planned'
|
|
435
|
-
: 'pending';
|
|
398
|
+
for (let index = 1; index < lines.length; index += 1) {
|
|
399
|
+
const line = lines[index];
|
|
400
|
+
const trimmed = line.trim();
|
|
436
401
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
planExists,
|
|
441
|
-
summaryExists,
|
|
442
|
-
reviewExists,
|
|
443
|
-
uatExists,
|
|
444
|
-
taskCount,
|
|
445
|
-
taskDoneCount,
|
|
446
|
-
nextTask: nextExecutableTask(tasks)?.number || null,
|
|
447
|
-
status
|
|
448
|
-
};
|
|
449
|
-
});
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
function resolvePhase(cwd, slug, requestedPhase, mode) {
|
|
453
|
-
const phases = listPhases(cwd, slug);
|
|
454
|
-
if (phases.length === 0) {
|
|
455
|
-
return null;
|
|
456
|
-
}
|
|
402
|
+
if (!trimmed) {
|
|
403
|
+
continue;
|
|
404
|
+
}
|
|
457
405
|
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
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
|
+
}
|
|
462
415
|
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
416
|
+
const goalMatch = trimmed.match(/^Goal:\s*(.+)$/);
|
|
417
|
+
if (goalMatch) {
|
|
418
|
+
item.goal = goalMatch[1].trim();
|
|
419
|
+
activeSection = null;
|
|
420
|
+
continue;
|
|
421
|
+
}
|
|
468
422
|
|
|
469
|
-
|
|
470
|
-
if (
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
)) {
|
|
475
|
-
return currentStatePhase;
|
|
423
|
+
const filesMatch = trimmed.match(/^Files:\s*(.+)$/);
|
|
424
|
+
if (filesMatch) {
|
|
425
|
+
item.files = parseInlineList(filesMatch[1]);
|
|
426
|
+
activeSection = null;
|
|
427
|
+
continue;
|
|
476
428
|
}
|
|
477
|
-
return phases.find(phase => !phase.planExists) || phases[0];
|
|
478
|
-
}
|
|
479
429
|
|
|
480
|
-
|
|
481
|
-
if (
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
)) {
|
|
485
|
-
return currentStatePhase;
|
|
430
|
+
const sectionMatch = trimmed.match(/^(Files|Acceptance|Verification):\s*$/);
|
|
431
|
+
if (sectionMatch) {
|
|
432
|
+
activeSection = sectionMatch[1].toLowerCase();
|
|
433
|
+
continue;
|
|
486
434
|
}
|
|
487
|
-
return phases.find(phase => phase.planExists && !phase.summaryExists) || null;
|
|
488
|
-
}
|
|
489
435
|
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
436
|
+
if (!trimmed.startsWith('- ')) {
|
|
437
|
+
activeSection = null;
|
|
438
|
+
continue;
|
|
493
439
|
}
|
|
494
|
-
return phases.find(phase => phase.planExists && !phase.summaryExists && phase.taskDoneCount > 0) || null;
|
|
495
|
-
}
|
|
496
440
|
|
|
497
|
-
|
|
498
|
-
if (
|
|
499
|
-
|
|
441
|
+
const value = trimmed.slice(2).trim();
|
|
442
|
+
if (!value) {
|
|
443
|
+
continue;
|
|
500
444
|
}
|
|
501
|
-
return phases.find(phase => phase.summaryExists && !phase.reviewExists) || null;
|
|
502
|
-
}
|
|
503
445
|
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
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);
|
|
507
452
|
}
|
|
508
|
-
return phases.find(phase => phase.reviewExists && !phase.uatExists)
|
|
509
|
-
|| [...phases].reverse().find(phase => phase.reviewExists)
|
|
510
|
-
|| null;
|
|
511
453
|
}
|
|
512
454
|
|
|
513
|
-
return
|
|
455
|
+
return item;
|
|
514
456
|
}
|
|
515
457
|
|
|
516
|
-
function
|
|
517
|
-
|
|
518
|
-
const config = loadConfig(cwd);
|
|
519
|
-
const existingCodebase = detectExistingCodebase(cwd);
|
|
520
|
-
const projectType = config.project.type || (existingCodebase ? 'brownfield' : 'greenfield');
|
|
521
|
-
const projectExists = fileExists(paths.project);
|
|
522
|
-
const codebaseMapExists = fileExists(paths.codebaseSummary);
|
|
523
|
-
const activeTrack = readActiveTrack(cwd);
|
|
524
|
-
const tracks = listTracks(cwd);
|
|
525
|
-
|
|
526
|
-
if (!projectExists) {
|
|
458
|
+
function parsePlan(content) {
|
|
459
|
+
if (!content) {
|
|
527
460
|
return {
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
reason: existingCodebase ? 'existing_code_detected_without_project_state' : 'project_state_missing',
|
|
532
|
-
projectType
|
|
461
|
+
goal: '',
|
|
462
|
+
done_definition: [],
|
|
463
|
+
items: []
|
|
533
464
|
};
|
|
534
465
|
}
|
|
535
466
|
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
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
|
+
}
|
|
544
477
|
}
|
|
545
478
|
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
kind: 'track_select',
|
|
549
|
-
command: '/autodev',
|
|
550
|
-
manualCommand: null,
|
|
551
|
-
reason: tracks.length > 0 ? 'no_active_track_selected' : 'no_tracks_created',
|
|
552
|
-
projectType
|
|
553
|
-
};
|
|
554
|
-
}
|
|
479
|
+
const headings = [...content.matchAll(/^##\s+Item\s+\d+\s*:\s*.+$/gm)];
|
|
480
|
+
const items = [];
|
|
555
481
|
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
trackSlug: activeTrack
|
|
565
|
-
};
|
|
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
|
+
}
|
|
566
490
|
}
|
|
567
491
|
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
manualCommand: null,
|
|
575
|
-
reason: 'active_track_has_no_phases',
|
|
576
|
-
projectType,
|
|
577
|
-
trackSlug: activeTrack
|
|
578
|
-
};
|
|
579
|
-
}
|
|
492
|
+
return {
|
|
493
|
+
goal: goalMatch ? goalMatch[1].trim() : '',
|
|
494
|
+
done_definition: doneDefinition,
|
|
495
|
+
items
|
|
496
|
+
};
|
|
497
|
+
}
|
|
580
498
|
|
|
581
|
-
|
|
582
|
-
|
|
499
|
+
function determineNextItem(items, preferredItemId) {
|
|
500
|
+
const preferred = preferredItemId
|
|
501
|
+
? items.find(item => item.id === normalizeCurrentItem(preferredItemId))
|
|
583
502
|
: null;
|
|
584
503
|
|
|
585
|
-
if (
|
|
586
|
-
|
|
587
|
-
)) {
|
|
588
|
-
return {
|
|
589
|
-
kind: 'plan_phase',
|
|
590
|
-
command: '/autodev',
|
|
591
|
-
manualCommand: `/autodev-plan-phase ${currentStatePhase.number}`,
|
|
592
|
-
reason: 'blocked_phase_requires_replanning',
|
|
593
|
-
projectType,
|
|
594
|
-
trackSlug: activeTrack,
|
|
595
|
-
phaseNumber: currentStatePhase.number
|
|
596
|
-
};
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
if (currentStatePhase && trackState?.currentStep === 'execution') {
|
|
600
|
-
return {
|
|
601
|
-
kind: 'execute_phase',
|
|
602
|
-
command: '/autodev',
|
|
603
|
-
manualCommand: `/autodev-execute-phase ${currentStatePhase.number}`,
|
|
604
|
-
reason: 'phase_execution_in_progress',
|
|
605
|
-
projectType,
|
|
606
|
-
trackSlug: activeTrack,
|
|
607
|
-
phaseNumber: currentStatePhase.number
|
|
608
|
-
};
|
|
609
|
-
}
|
|
610
|
-
|
|
611
|
-
if (currentStatePhase && trackState?.currentStep === 'task_review') {
|
|
612
|
-
return {
|
|
613
|
-
kind: 'task_review',
|
|
614
|
-
command: '/autodev',
|
|
615
|
-
manualCommand: `/autodev-review-task ${currentStatePhase.number}`,
|
|
616
|
-
reason: 'task_execution_checkpoint_pending_user_review',
|
|
617
|
-
projectType,
|
|
618
|
-
trackSlug: activeTrack,
|
|
619
|
-
phaseNumber: currentStatePhase.number
|
|
620
|
-
};
|
|
621
|
-
}
|
|
622
|
-
|
|
623
|
-
if (currentStatePhase && trackState?.currentStep === 'plan_review') {
|
|
624
|
-
return {
|
|
625
|
-
kind: 'plan_review',
|
|
626
|
-
command: '/autodev',
|
|
627
|
-
manualCommand: `/autodev-execute-phase ${currentStatePhase.number}`,
|
|
628
|
-
reason: 'phase_plan_awaits_review',
|
|
629
|
-
projectType,
|
|
630
|
-
trackSlug: activeTrack,
|
|
631
|
-
phaseNumber: currentStatePhase.number
|
|
632
|
-
};
|
|
504
|
+
if (preferred && ['pending', 'in_progress'].includes(preferred.status)) {
|
|
505
|
+
return preferred;
|
|
633
506
|
}
|
|
634
507
|
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
manualCommand: `/autodev-review-phase ${currentStatePhase.number}`,
|
|
640
|
-
reason: 'phase_review_in_progress',
|
|
641
|
-
projectType,
|
|
642
|
-
trackSlug: activeTrack,
|
|
643
|
-
phaseNumber: currentStatePhase.number
|
|
644
|
-
};
|
|
645
|
-
}
|
|
508
|
+
return items.find(item => item.status === 'in_progress')
|
|
509
|
+
|| items.find(item => item.status === 'pending')
|
|
510
|
+
|| null;
|
|
511
|
+
}
|
|
646
512
|
|
|
647
|
-
|
|
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) {
|
|
648
528
|
return {
|
|
649
|
-
kind: '
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
reason: 'phase_verification_in_progress',
|
|
653
|
-
projectType,
|
|
654
|
-
trackSlug: activeTrack,
|
|
655
|
-
phaseNumber: currentStatePhase.number
|
|
529
|
+
kind: 'init_brief',
|
|
530
|
+
reason: 'brief_missing',
|
|
531
|
+
command: '/autodev'
|
|
656
532
|
};
|
|
657
533
|
}
|
|
658
534
|
|
|
659
|
-
|
|
660
|
-
if (nextReview && config.workflow.review_after_execute !== false) {
|
|
535
|
+
if (projectType === 'brownfield' && !contextExists) {
|
|
661
536
|
return {
|
|
662
|
-
kind: '
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
reason: 'phase_executed_but_not_reviewed',
|
|
666
|
-
projectType,
|
|
667
|
-
trackSlug: activeTrack,
|
|
668
|
-
phaseNumber: nextReview.number
|
|
537
|
+
kind: 'build_context',
|
|
538
|
+
reason: 'brownfield_context_missing',
|
|
539
|
+
command: '/autodev'
|
|
669
540
|
};
|
|
670
541
|
}
|
|
671
542
|
|
|
672
|
-
|
|
673
|
-
if (nextVerify) {
|
|
543
|
+
if (plan.items.length === 0) {
|
|
674
544
|
return {
|
|
675
|
-
kind: '
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
reason: 'phase_reviewed_but_not_verified',
|
|
679
|
-
projectType,
|
|
680
|
-
trackSlug: activeTrack,
|
|
681
|
-
phaseNumber: nextVerify.number
|
|
545
|
+
kind: 'plan',
|
|
546
|
+
reason: 'plan_missing_or_empty',
|
|
547
|
+
command: '/autodev'
|
|
682
548
|
};
|
|
683
549
|
}
|
|
684
550
|
|
|
685
|
-
|
|
686
|
-
if (nextTaskReview) {
|
|
551
|
+
if (state.stage === 'blocked' || blockedItem) {
|
|
687
552
|
return {
|
|
688
|
-
kind: '
|
|
553
|
+
kind: 'repair_plan',
|
|
554
|
+
reason: blockedItem ? 'plan_item_blocked' : 'state_marked_blocked',
|
|
689
555
|
command: '/autodev',
|
|
690
|
-
|
|
691
|
-
reason: 'phase_task_completed_awaiting_user_review',
|
|
692
|
-
projectType,
|
|
693
|
-
trackSlug: activeTrack,
|
|
694
|
-
phaseNumber: nextTaskReview.number
|
|
556
|
+
current_item: blockedItem ? blockedItem.id : state.current_item
|
|
695
557
|
};
|
|
696
558
|
}
|
|
697
559
|
|
|
698
|
-
|
|
699
|
-
if (nextPlannedReview) {
|
|
560
|
+
if (nextItem) {
|
|
700
561
|
return {
|
|
701
|
-
kind: '
|
|
562
|
+
kind: 'execute',
|
|
563
|
+
reason: nextItem.status === 'in_progress' ? 'item_in_progress' : 'pending_plan_item',
|
|
702
564
|
command: '/autodev',
|
|
703
|
-
|
|
704
|
-
reason: 'phase_planned_awaiting_user_review',
|
|
705
|
-
projectType,
|
|
706
|
-
trackSlug: activeTrack,
|
|
707
|
-
phaseNumber: nextPlannedReview.number
|
|
565
|
+
current_item: nextItem.id
|
|
708
566
|
};
|
|
709
567
|
}
|
|
710
568
|
|
|
711
|
-
|
|
712
|
-
if (nextPlan) {
|
|
569
|
+
if (allDone) {
|
|
713
570
|
return {
|
|
714
|
-
kind: '
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
reason: 'phase_not_planned',
|
|
718
|
-
projectType,
|
|
719
|
-
trackSlug: activeTrack,
|
|
720
|
-
phaseNumber: nextPlan.number
|
|
571
|
+
kind: 'complete',
|
|
572
|
+
reason: 'all_plan_items_done',
|
|
573
|
+
command: '/autodev'
|
|
721
574
|
};
|
|
722
575
|
}
|
|
723
576
|
|
|
724
577
|
return {
|
|
725
|
-
kind: '
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
reason: 'active_track_fully_verified',
|
|
729
|
-
projectType,
|
|
730
|
-
trackSlug: activeTrack
|
|
578
|
+
kind: 'plan',
|
|
579
|
+
reason: 'plan_needs_refresh',
|
|
580
|
+
command: '/autodev'
|
|
731
581
|
};
|
|
732
582
|
}
|
|
733
583
|
|
|
734
|
-
function
|
|
584
|
+
function buildStatus(cwd) {
|
|
735
585
|
const paths = rootPaths(cwd);
|
|
736
|
-
const initialized = fileExists(paths.project);
|
|
737
586
|
const existingCodebase = detectExistingCodebase(cwd);
|
|
738
|
-
const
|
|
739
|
-
const
|
|
740
|
-
const
|
|
741
|
-
const
|
|
742
|
-
const
|
|
743
|
-
|
|
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
|
+
};
|
|
744
598
|
const route = buildRoute(cwd);
|
|
599
|
+
const currentItem = determineNextItem(plan.items, state?.current_item);
|
|
745
600
|
|
|
746
601
|
return {
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
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
|
|
767
627
|
};
|
|
768
628
|
}
|
|
769
629
|
|
|
770
|
-
function
|
|
771
|
-
if (!progress.initialized) {
|
|
772
|
-
return 'No .autodev project found.\nNext: /autodev';
|
|
773
|
-
}
|
|
774
|
-
|
|
630
|
+
function renderStatusTable(status) {
|
|
775
631
|
const lines = [
|
|
776
|
-
'Autodev
|
|
632
|
+
'Autodev Status',
|
|
777
633
|
'',
|
|
778
|
-
`Project Type: ${
|
|
779
|
-
`
|
|
780
|
-
`
|
|
781
|
-
`
|
|
782
|
-
`
|
|
783
|
-
`
|
|
784
|
-
`
|
|
785
|
-
`
|
|
786
|
-
`
|
|
787
|
-
|
|
788
|
-
`Task Summaries: ${progress.counts.tasksDone}`,
|
|
789
|
-
`Next: ${progress.route.command}`,
|
|
790
|
-
progress.route.manualCommand ? `Manual Shortcut: ${progress.route.manualCommand}` : null,
|
|
791
|
-
'',
|
|
792
|
-
'| Phase | Type | Name | Tasks | Done | Plan | Summary | Review | UAT | Status |',
|
|
793
|
-
'| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |'
|
|
794
|
-
].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
|
+
];
|
|
795
644
|
|
|
796
|
-
|
|
645
|
+
if (status.plan_items.length > 0) {
|
|
797
646
|
lines.push(
|
|
798
|
-
|
|
647
|
+
'',
|
|
648
|
+
'| Item | Status | Title |',
|
|
649
|
+
'| --- | --- | --- |'
|
|
799
650
|
);
|
|
800
|
-
}
|
|
801
651
|
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
for (const track of progress.tracks) {
|
|
805
|
-
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} |`);
|
|
806
654
|
}
|
|
807
655
|
}
|
|
808
656
|
|
|
809
657
|
return lines.join('\n');
|
|
810
658
|
}
|
|
811
659
|
|
|
812
|
-
function
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
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
|
+
}
|
|
817
681
|
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
return Object.fromEntries(CODEBASE_FILES.map(name => [name.replace(/\.md$/, ''), path.join(codebaseDir, name)]));
|
|
682
|
+
saveState(cwd, next);
|
|
683
|
+
return loadState(cwd);
|
|
821
684
|
}
|
|
822
685
|
|
|
823
|
-
function
|
|
686
|
+
function createRunReport(cwd, kind = 'run') {
|
|
824
687
|
const paths = rootPaths(cwd);
|
|
825
|
-
|
|
826
|
-
const config = loadConfig(cwd);
|
|
827
|
-
const existingCodebase = detectExistingCodebase(cwd);
|
|
828
|
-
const projectType = config.project.type || (existingCodebase ? 'brownfield' : 'greenfield');
|
|
829
|
-
const activeTrack = readActiveTrack(cwd);
|
|
830
|
-
const tracks = listTracks(cwd);
|
|
688
|
+
ensureDir(paths.runsDir);
|
|
831
689
|
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
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');
|
|
853
715
|
|
|
854
|
-
|
|
855
|
-
const paths = rootPaths(cwd);
|
|
856
|
-
const route = buildRoute(cwd);
|
|
857
|
-
const activeTrack = readActiveTrack(cwd);
|
|
858
|
-
const track = trackPaths(cwd, activeTrack);
|
|
859
|
-
const phases = activeTrack ? listPhases(cwd, activeTrack) : [];
|
|
860
|
-
const verifiedPhases = phases.filter(phase => phase.uatExists);
|
|
861
|
-
const completedPhases = phases.filter(phase => phase.summaryExists || phase.reviewExists || phase.uatExists);
|
|
716
|
+
fs.writeFileSync(filePath, content, 'utf8');
|
|
862
717
|
|
|
863
718
|
return {
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
project_exists: fileExists(paths.project),
|
|
867
|
-
project_state_path: paths.state,
|
|
868
|
-
active_track_path: paths.activeTrack,
|
|
869
|
-
active_track: activeTrack,
|
|
870
|
-
track_path: track ? track.dir : null,
|
|
871
|
-
track_state_path: track ? track.state : null,
|
|
872
|
-
phases_dir: track ? track.phasesDir : null,
|
|
873
|
-
archive_root: path.join(paths.root, 'archive'),
|
|
874
|
-
route,
|
|
875
|
-
phase_counts: {
|
|
876
|
-
total: phases.length,
|
|
877
|
-
completed: completedPhases.length,
|
|
878
|
-
verified: verifiedPhases.length
|
|
879
|
-
},
|
|
880
|
-
verified_phase_numbers: verifiedPhases.map(phase => phase.number),
|
|
881
|
-
verified_phase_dirs: verifiedPhases.map(phase => phase.dir)
|
|
719
|
+
kind,
|
|
720
|
+
path: filePath
|
|
882
721
|
};
|
|
883
722
|
}
|
|
884
723
|
|
|
885
|
-
function
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
const
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
: mode === 'review-task'
|
|
899
|
-
? 'task_review'
|
|
900
|
-
: mode === 'execute-phase' || mode === 'review-plan'
|
|
901
|
-
? 'execute'
|
|
902
|
-
: mode === 'plan-phase'
|
|
903
|
-
? 'plan'
|
|
904
|
-
: null;
|
|
905
|
-
const phase = phaseMode && activeTrack ? resolvePhase(cwd, activeTrack, requestedPhase, phaseMode) : null;
|
|
906
|
-
const tasks = phase ? listTasksForPhaseDetails(phase) : [];
|
|
907
|
-
const nextTask = nextExecutableTask(tasks);
|
|
908
|
-
const lastCompleted = lastCompletedTask(tasks);
|
|
909
|
-
const highestTaskNumber = lastTaskNumber(tasks);
|
|
910
|
-
const dependencyDeadlock = hasDependencyDeadlock(tasks);
|
|
911
|
-
const trackStateSnapshot = track ? readStateSnapshot(track.state) : null;
|
|
912
|
-
const currentTaskNumber = (() => {
|
|
913
|
-
const raw = trackStateSnapshot?.currentTask;
|
|
914
|
-
if (raw && /^\d+$/.test(raw)) {
|
|
915
|
-
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;
|
|
916
737
|
}
|
|
917
|
-
return lastCompleted ? lastCompleted.number : null;
|
|
918
|
-
})();
|
|
919
|
-
const currentTask = tasks.find(task => task.number === currentTaskNumber) || lastCompleted || null;
|
|
920
|
-
const codebase = codebasePaths(cwd);
|
|
921
738
|
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
slug: trackItem.slug,
|
|
936
|
-
name: trackItem.name,
|
|
937
|
-
active: trackItem.active,
|
|
938
|
-
path: trackItem.paths.dir
|
|
939
|
-
})),
|
|
940
|
-
codebase_dir: paths.codebaseDir,
|
|
941
|
-
codebase_map_exists: fileExists(paths.codebaseSummary),
|
|
942
|
-
codebase_paths: codebase,
|
|
943
|
-
track_path: track ? track.dir : null,
|
|
944
|
-
track_doc_path: track ? track.track : null,
|
|
945
|
-
requirements_path: track ? track.requirements : null,
|
|
946
|
-
roadmap_path: track ? track.roadmap : null,
|
|
947
|
-
track_state_path: track ? track.state : null,
|
|
948
|
-
track_state: trackStateSnapshot,
|
|
949
|
-
phases_dir: track ? track.phasesDir : null,
|
|
950
|
-
route,
|
|
951
|
-
phase_found: Boolean(phase),
|
|
952
|
-
phase_number: phase ? phase.number : null,
|
|
953
|
-
phase_type: phase ? phase.type : null,
|
|
954
|
-
phase_name: phase ? phase.name : null,
|
|
955
|
-
phase_dir: phase ? phase.dir : null,
|
|
956
|
-
plan_path: phase ? phase.planPath : null,
|
|
957
|
-
summary_path: phase ? phase.summaryPath : null,
|
|
958
|
-
review_path: phase ? phase.reviewPath : null,
|
|
959
|
-
uat_path: phase ? phase.uatPath : null,
|
|
960
|
-
plan_exists: phase ? phase.planExists : false,
|
|
961
|
-
summary_exists: phase ? phase.summaryExists : false,
|
|
962
|
-
review_exists: phase ? phase.reviewExists : false,
|
|
963
|
-
uat_exists: phase ? phase.uatExists : false,
|
|
964
|
-
tasks_dir: phase ? phase.dir : null,
|
|
965
|
-
task_count: tasks.length,
|
|
966
|
-
task_done_count: tasks.filter(task => task.summaryExists).length,
|
|
967
|
-
all_tasks_done: tasks.length > 0 && tasks.every(task => task.summaryExists),
|
|
968
|
-
dependency_deadlock: dependencyDeadlock,
|
|
969
|
-
last_task_number: highestTaskNumber,
|
|
970
|
-
next_task_number: nextTask ? nextTask.number : null,
|
|
971
|
-
next_task_path: nextTask ? nextTask.taskPath : null,
|
|
972
|
-
next_task_summary_path: nextTask ? nextTask.summaryPath : null,
|
|
973
|
-
current_task_number: currentTask ? currentTask.number : null,
|
|
974
|
-
current_task_path: currentTask ? currentTask.taskPath : null,
|
|
975
|
-
current_task_summary_path: currentTask ? currentTask.summaryPath : null,
|
|
976
|
-
last_completed_task_number: lastCompleted ? lastCompleted.number : null,
|
|
977
|
-
last_completed_task_summary_path: lastCompleted ? lastCompleted.summaryPath : null,
|
|
978
|
-
tasks: tasks.map(task => ({
|
|
979
|
-
number: task.number,
|
|
980
|
-
title: task.title,
|
|
981
|
-
status: task.effectiveStatus,
|
|
982
|
-
depends_on: task.dependsOn,
|
|
983
|
-
ready: task.ready,
|
|
984
|
-
task_path: task.taskPath,
|
|
985
|
-
summary_path: task.summaryPath,
|
|
986
|
-
summary_exists: task.summaryExists
|
|
987
|
-
})),
|
|
988
|
-
workflow: config.workflow,
|
|
989
|
-
git_mode: config.git.mode
|
|
990
|
-
};
|
|
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;
|
|
991
752
|
}
|
|
992
753
|
|
|
993
754
|
function printJson(value) {
|
|
@@ -995,8 +756,8 @@ function printJson(value) {
|
|
|
995
756
|
}
|
|
996
757
|
|
|
997
758
|
function main() {
|
|
998
|
-
const args = process.argv.slice(2);
|
|
999
759
|
const cwd = process.cwd();
|
|
760
|
+
const args = process.argv.slice(2);
|
|
1000
761
|
|
|
1001
762
|
if (args.length === 0) {
|
|
1002
763
|
process.stderr.write('autodev-tools: command required\n');
|
|
@@ -1005,14 +766,27 @@ function main() {
|
|
|
1005
766
|
|
|
1006
767
|
const [command, ...rest] = args;
|
|
1007
768
|
|
|
1008
|
-
if (command === '
|
|
1009
|
-
const
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
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`);
|
|
1013
782
|
return;
|
|
1014
783
|
}
|
|
1015
|
-
|
|
784
|
+
printJson(status);
|
|
785
|
+
return;
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
if (command === 'route') {
|
|
789
|
+
printJson(buildRoute(cwd));
|
|
1016
790
|
return;
|
|
1017
791
|
}
|
|
1018
792
|
|
|
@@ -1022,7 +796,9 @@ function main() {
|
|
|
1022
796
|
process.stderr.write('autodev-tools: config key required\n');
|
|
1023
797
|
process.exit(1);
|
|
1024
798
|
}
|
|
1025
|
-
|
|
799
|
+
|
|
800
|
+
const state = loadState(cwd) || DEFAULT_STATE;
|
|
801
|
+
const value = dotGet(state, key);
|
|
1026
802
|
if (typeof value === 'object' && value !== null) {
|
|
1027
803
|
printJson(value);
|
|
1028
804
|
} else if (value !== undefined) {
|
|
@@ -1031,35 +807,41 @@ function main() {
|
|
|
1031
807
|
return;
|
|
1032
808
|
}
|
|
1033
809
|
|
|
1034
|
-
if (command === '
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
}
|
|
1038
|
-
|
|
1039
|
-
if (command === 'cleanup') {
|
|
1040
|
-
printJson(buildCleanupPayload(cwd));
|
|
1041
|
-
return;
|
|
1042
|
-
}
|
|
810
|
+
if (command === 'update') {
|
|
811
|
+
const options = parseOptions(rest);
|
|
812
|
+
const updates = {};
|
|
1043
813
|
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
if (
|
|
1048
|
-
|
|
1049
|
-
return;
|
|
814
|
+
if (options.project_type !== undefined) {
|
|
815
|
+
updates.project_type = options.project_type;
|
|
816
|
+
}
|
|
817
|
+
if (options.stage !== undefined) {
|
|
818
|
+
updates.stage = options.stage;
|
|
1050
819
|
}
|
|
1051
|
-
|
|
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));
|
|
1052
839
|
return;
|
|
1053
840
|
}
|
|
1054
841
|
|
|
1055
|
-
if (command === '
|
|
1056
|
-
const
|
|
1057
|
-
|
|
1058
|
-
if (!mode) {
|
|
1059
|
-
process.stderr.write('autodev-tools: init mode required\n');
|
|
1060
|
-
process.exit(1);
|
|
1061
|
-
}
|
|
1062
|
-
printJson(initPayload(cwd, mode, phase));
|
|
842
|
+
if (command === 'create-run') {
|
|
843
|
+
const kind = rest[0] || 'run';
|
|
844
|
+
printJson(createRunReport(cwd, kind));
|
|
1063
845
|
return;
|
|
1064
846
|
}
|
|
1065
847
|
|
|
@@ -1072,26 +854,20 @@ if (require.main === module) {
|
|
|
1072
854
|
}
|
|
1073
855
|
|
|
1074
856
|
module.exports = {
|
|
857
|
+
DEFAULT_STATE,
|
|
1075
858
|
autodevDir,
|
|
1076
|
-
|
|
1077
|
-
buildProgress,
|
|
859
|
+
bootstrapProject,
|
|
1078
860
|
buildRoute,
|
|
1079
861
|
buildStatus,
|
|
862
|
+
createRunReport,
|
|
1080
863
|
detectExistingCodebase,
|
|
1081
864
|
findWorkspaceRoot,
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
readActiveTrack,
|
|
1088
|
-
renderProgressTable,
|
|
1089
|
-
resolvePhase,
|
|
865
|
+
loadState,
|
|
866
|
+
normalizeCurrentItem,
|
|
867
|
+
normalizeProjectType,
|
|
868
|
+
parsePlan,
|
|
869
|
+
renderStatusTable,
|
|
1090
870
|
rootPaths,
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
lastCompletedTask,
|
|
1094
|
-
lastTaskNumber,
|
|
1095
|
-
hasDependencyDeadlock,
|
|
1096
|
-
nextExecutableTask
|
|
871
|
+
saveState,
|
|
872
|
+
updateState
|
|
1097
873
|
};
|