@mthanhlm/autodev 0.1.0 → 0.2.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/PUBLISH.md +1 -1
- package/README.md +27 -6
- package/agents/autodev-codebase-domain.md +16 -0
- package/agents/autodev-codebase-quality.md +16 -0
- package/agents/autodev-codebase-runtime.md +16 -0
- package/agents/autodev-codebase-structure.md +16 -0
- package/agents/autodev-review-integration.md +21 -0
- package/agents/autodev-review-polish.md +21 -0
- package/agents/autodev-review-quality.md +21 -0
- package/agents/autodev-review-security.md +21 -0
- package/autodev/bin/autodev-tools.cjs +406 -90
- package/autodev/templates/codebase/domain.md +13 -0
- package/autodev/templates/codebase/quality.md +13 -0
- package/autodev/templates/codebase/runtime.md +13 -0
- package/autodev/templates/codebase/structure.md +13 -0
- package/autodev/templates/codebase/summary.md +13 -0
- package/autodev/templates/config.json +6 -1
- package/autodev/templates/plan.md +4 -0
- package/autodev/templates/project-state.md +11 -0
- package/autodev/templates/project.md +3 -0
- package/autodev/templates/requirements.md +2 -0
- package/autodev/templates/review.md +24 -0
- package/autodev/templates/roadmap.md +3 -3
- package/autodev/templates/state.md +5 -4
- package/autodev/templates/summary.md +3 -1
- package/autodev/templates/track-state.md +12 -0
- package/autodev/templates/track.md +17 -0
- package/autodev/templates/uat.md +5 -2
- package/autodev/workflows/autodev.md +62 -0
- package/autodev/workflows/cleanup.md +45 -0
- package/autodev/workflows/execute-phase.md +15 -6
- package/autodev/workflows/explore-codebase.md +57 -0
- package/autodev/workflows/help.md +45 -12
- package/autodev/workflows/new-project.md +41 -14
- package/autodev/workflows/plan-phase.md +19 -8
- package/autodev/workflows/progress.md +3 -1
- package/autodev/workflows/review-phase.md +59 -0
- package/autodev/workflows/verify-work.md +17 -7
- package/bin/install.js +78 -18
- package/commands/autodev/cleanup.md +23 -0
- package/commands/autodev/execute-phase.md +2 -2
- package/commands/autodev/explore-codebase.md +33 -0
- package/commands/autodev/help.md +1 -1
- package/commands/autodev/index.md +35 -0
- package/commands/autodev/new-project.md +6 -4
- package/commands/autodev/plan-phase.md +2 -2
- package/commands/autodev/progress.md +1 -1
- package/commands/autodev/review-phase.md +29 -0
- package/commands/autodev/verify-work.md +2 -2
- package/hooks/autodev-session-state.sh +1 -1
- package/package.json +5 -2
|
@@ -4,8 +4,13 @@ const fs = require('fs');
|
|
|
4
4
|
const path = require('path');
|
|
5
5
|
|
|
6
6
|
const DEFAULT_CONFIG = {
|
|
7
|
+
project: {
|
|
8
|
+
type: null
|
|
9
|
+
},
|
|
7
10
|
workflow: {
|
|
8
|
-
research: false
|
|
11
|
+
research: false,
|
|
12
|
+
review_after_execute: true,
|
|
13
|
+
codebase_parallel_agents: 4
|
|
9
14
|
},
|
|
10
15
|
execution: {
|
|
11
16
|
parallel: false
|
|
@@ -24,10 +29,26 @@ const DEFAULT_CONFIG = {
|
|
|
24
29
|
}
|
|
25
30
|
};
|
|
26
31
|
|
|
32
|
+
const CODEBASE_FILES = ['structure.md', 'domain.md', 'runtime.md', 'quality.md', 'summary.md'];
|
|
33
|
+
|
|
27
34
|
function autodevDir(cwd) {
|
|
28
35
|
return path.join(cwd, '.autodev');
|
|
29
36
|
}
|
|
30
37
|
|
|
38
|
+
function rootPaths(cwd) {
|
|
39
|
+
const root = autodevDir(cwd);
|
|
40
|
+
return {
|
|
41
|
+
root,
|
|
42
|
+
config: path.join(root, 'config.json'),
|
|
43
|
+
project: path.join(root, 'PROJECT.md'),
|
|
44
|
+
state: path.join(root, 'STATE.md'),
|
|
45
|
+
activeTrack: path.join(root, 'ACTIVE_TRACK'),
|
|
46
|
+
codebaseDir: path.join(root, 'codebase'),
|
|
47
|
+
codebaseSummary: path.join(root, 'codebase', 'summary.md'),
|
|
48
|
+
tracksDir: path.join(root, 'tracks')
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
31
52
|
function readText(filePath) {
|
|
32
53
|
try {
|
|
33
54
|
return fs.readFileSync(filePath, 'utf8');
|
|
@@ -44,98 +65,226 @@ function readJson(filePath, fallback = null) {
|
|
|
44
65
|
}
|
|
45
66
|
}
|
|
46
67
|
|
|
68
|
+
function fileExists(filePath) {
|
|
69
|
+
try {
|
|
70
|
+
return fs.existsSync(filePath);
|
|
71
|
+
} catch {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
47
76
|
function slugify(value) {
|
|
48
77
|
return String(value)
|
|
49
78
|
.toLowerCase()
|
|
50
79
|
.replace(/[^a-z0-9]+/g, '-')
|
|
51
80
|
.replace(/^-+|-+$/g, '')
|
|
52
|
-
.replace(/-+/g, '-') || '
|
|
81
|
+
.replace(/-+/g, '-') || 'track';
|
|
53
82
|
}
|
|
54
83
|
|
|
55
84
|
function padPhase(number) {
|
|
56
85
|
return String(number).padStart(2, '0');
|
|
57
86
|
}
|
|
58
87
|
|
|
88
|
+
function detectExistingCodebase(cwd) {
|
|
89
|
+
const knownFiles = [
|
|
90
|
+
'package.json',
|
|
91
|
+
'package-lock.json',
|
|
92
|
+
'pnpm-lock.yaml',
|
|
93
|
+
'yarn.lock',
|
|
94
|
+
'tsconfig.json',
|
|
95
|
+
'jsconfig.json',
|
|
96
|
+
'pyproject.toml',
|
|
97
|
+
'requirements.txt',
|
|
98
|
+
'Pipfile',
|
|
99
|
+
'Cargo.toml',
|
|
100
|
+
'go.mod',
|
|
101
|
+
'pom.xml',
|
|
102
|
+
'build.gradle',
|
|
103
|
+
'Dockerfile'
|
|
104
|
+
];
|
|
105
|
+
const knownDirs = [
|
|
106
|
+
'src',
|
|
107
|
+
'app',
|
|
108
|
+
'lib',
|
|
109
|
+
'server',
|
|
110
|
+
'client',
|
|
111
|
+
'backend',
|
|
112
|
+
'frontend',
|
|
113
|
+
'components',
|
|
114
|
+
'pages',
|
|
115
|
+
'api',
|
|
116
|
+
'tests',
|
|
117
|
+
'__tests__'
|
|
118
|
+
];
|
|
119
|
+
|
|
120
|
+
if (knownFiles.some(name => fileExists(path.join(cwd, name)))) {
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (knownDirs.some(name => fileExists(path.join(cwd, name)))) {
|
|
125
|
+
return true;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
try {
|
|
129
|
+
const entries = fs.readdirSync(cwd, { withFileTypes: true });
|
|
130
|
+
return entries.some(entry => {
|
|
131
|
+
if (entry.name === '.autodev' || entry.name === '.claude' || entry.name.startsWith('.')) {
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
return entry.isDirectory() || /\.(js|ts|tsx|jsx|py|go|rs|java|rb|php|cs|cpp|c|swift|kt|sql|json|yaml|yml|md)$/.test(entry.name);
|
|
135
|
+
});
|
|
136
|
+
} catch {
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function mergeSection(defaultValue, actualValue) {
|
|
142
|
+
if (defaultValue && typeof defaultValue === 'object' && !Array.isArray(defaultValue)) {
|
|
143
|
+
return {
|
|
144
|
+
...defaultValue,
|
|
145
|
+
...((actualValue && typeof actualValue === 'object' && !Array.isArray(actualValue)) ? actualValue : {})
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
return actualValue !== undefined ? actualValue : defaultValue;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function loadConfig(cwd) {
|
|
152
|
+
const config = readJson(rootPaths(cwd).config, {}) || {};
|
|
153
|
+
return {
|
|
154
|
+
project: mergeSection(DEFAULT_CONFIG.project, config.project),
|
|
155
|
+
workflow: mergeSection(DEFAULT_CONFIG.workflow, config.workflow),
|
|
156
|
+
execution: mergeSection(DEFAULT_CONFIG.execution, config.execution),
|
|
157
|
+
git: mergeSection(DEFAULT_CONFIG.git, config.git),
|
|
158
|
+
hooks: mergeSection(DEFAULT_CONFIG.hooks, config.hooks)
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function readActiveTrack(cwd) {
|
|
163
|
+
const value = readText(rootPaths(cwd).activeTrack);
|
|
164
|
+
return value ? value.trim() || null : null;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function trackDir(cwd, slug) {
|
|
168
|
+
return path.join(rootPaths(cwd).tracksDir, slug);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function trackPaths(cwd, slug = readActiveTrack(cwd)) {
|
|
172
|
+
if (!slug) {
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const dir = trackDir(cwd, slug);
|
|
177
|
+
return {
|
|
178
|
+
slug,
|
|
179
|
+
dir,
|
|
180
|
+
track: path.join(dir, 'TRACK.md'),
|
|
181
|
+
requirements: path.join(dir, 'REQUIREMENTS.md'),
|
|
182
|
+
roadmap: path.join(dir, 'ROADMAP.md'),
|
|
183
|
+
state: path.join(dir, 'STATE.md'),
|
|
184
|
+
phasesDir: path.join(dir, 'phases')
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function listTracks(cwd) {
|
|
189
|
+
const tracksDir = rootPaths(cwd).tracksDir;
|
|
190
|
+
if (!fileExists(tracksDir)) {
|
|
191
|
+
return [];
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return fs.readdirSync(tracksDir, { withFileTypes: true })
|
|
195
|
+
.filter(entry => entry.isDirectory())
|
|
196
|
+
.map(entry => {
|
|
197
|
+
const paths = trackPaths(cwd, entry.name);
|
|
198
|
+
const trackName = (() => {
|
|
199
|
+
const content = readText(paths.track);
|
|
200
|
+
const match = content && content.match(/^#\s+Track:\s*(.+)$/m);
|
|
201
|
+
return match ? match[1].trim() : entry.name;
|
|
202
|
+
})();
|
|
203
|
+
|
|
204
|
+
return {
|
|
205
|
+
slug: entry.name,
|
|
206
|
+
name: trackName,
|
|
207
|
+
paths,
|
|
208
|
+
active: readActiveTrack(cwd) === entry.name
|
|
209
|
+
};
|
|
210
|
+
})
|
|
211
|
+
.sort((left, right) => left.slug.localeCompare(right.slug));
|
|
212
|
+
}
|
|
213
|
+
|
|
59
214
|
function parseRoadmap(content) {
|
|
60
215
|
const phases = [];
|
|
61
216
|
if (!content) {
|
|
62
217
|
return phases;
|
|
63
218
|
}
|
|
64
219
|
|
|
65
|
-
const matcher = /^##\s+Phase\s+(\d+)\s*:\s*(.+)$/gm;
|
|
220
|
+
const matcher = /^##\s+Phase\s+(\d+)(?:\s+\[([a-z-]+)\])?\s*:\s*(.+)$/gm;
|
|
66
221
|
let match;
|
|
67
222
|
while ((match = matcher.exec(content)) !== null) {
|
|
68
223
|
phases.push({
|
|
69
224
|
number: Number(match[1]),
|
|
70
|
-
|
|
225
|
+
type: match[2] || 'feature',
|
|
226
|
+
name: match[3].trim()
|
|
71
227
|
});
|
|
72
228
|
}
|
|
73
229
|
return phases;
|
|
74
230
|
}
|
|
75
231
|
|
|
76
|
-
function phasePaths(cwd, phase) {
|
|
77
|
-
const
|
|
78
|
-
|
|
232
|
+
function phasePaths(cwd, slug, phase) {
|
|
233
|
+
const track = trackPaths(cwd, slug);
|
|
234
|
+
if (!track) {
|
|
235
|
+
return null;
|
|
236
|
+
}
|
|
237
|
+
|
|
79
238
|
const prefix = padPhase(phase.number);
|
|
239
|
+
const dirName = `${prefix}-${phase.type}-${slugify(phase.name)}`;
|
|
240
|
+
const dir = path.join(track.phasesDir, dirName);
|
|
80
241
|
return {
|
|
81
242
|
...phase,
|
|
82
243
|
padded: prefix,
|
|
83
244
|
dir,
|
|
84
245
|
planPath: path.join(dir, `${prefix}-PLAN.md`),
|
|
85
246
|
summaryPath: path.join(dir, `${prefix}-SUMMARY.md`),
|
|
247
|
+
reviewPath: path.join(dir, `${prefix}-REVIEW.md`),
|
|
86
248
|
uatPath: path.join(dir, `${prefix}-UAT.md`)
|
|
87
249
|
};
|
|
88
250
|
}
|
|
89
251
|
|
|
90
|
-
function
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
...(config.workflow || {})
|
|
96
|
-
},
|
|
97
|
-
execution: {
|
|
98
|
-
...DEFAULT_CONFIG.execution,
|
|
99
|
-
...(config.execution || {})
|
|
100
|
-
},
|
|
101
|
-
git: {
|
|
102
|
-
...DEFAULT_CONFIG.git,
|
|
103
|
-
...(config.git || {})
|
|
104
|
-
},
|
|
105
|
-
hooks: {
|
|
106
|
-
...DEFAULT_CONFIG.hooks,
|
|
107
|
-
...(config.hooks || {})
|
|
108
|
-
}
|
|
109
|
-
};
|
|
110
|
-
}
|
|
252
|
+
function listPhases(cwd, slug = readActiveTrack(cwd)) {
|
|
253
|
+
const track = trackPaths(cwd, slug);
|
|
254
|
+
if (!track || !fileExists(track.roadmap)) {
|
|
255
|
+
return [];
|
|
256
|
+
}
|
|
111
257
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
const
|
|
116
|
-
const
|
|
117
|
-
const
|
|
118
|
-
const uatExists = fs.existsSync(details.uatPath);
|
|
258
|
+
return parseRoadmap(readText(track.roadmap)).map(phase => {
|
|
259
|
+
const details = phasePaths(cwd, slug, phase);
|
|
260
|
+
const planExists = fileExists(details.planPath);
|
|
261
|
+
const summaryExists = fileExists(details.summaryPath);
|
|
262
|
+
const reviewExists = fileExists(details.reviewPath);
|
|
263
|
+
const uatExists = fileExists(details.uatPath);
|
|
119
264
|
const status = uatExists
|
|
120
265
|
? 'verified'
|
|
121
|
-
:
|
|
122
|
-
? '
|
|
123
|
-
:
|
|
124
|
-
? '
|
|
125
|
-
:
|
|
266
|
+
: reviewExists
|
|
267
|
+
? 'reviewed'
|
|
268
|
+
: summaryExists
|
|
269
|
+
? 'executed'
|
|
270
|
+
: planExists
|
|
271
|
+
? 'planned'
|
|
272
|
+
: 'pending';
|
|
126
273
|
|
|
127
274
|
return {
|
|
128
275
|
...details,
|
|
276
|
+
trackSlug: slug,
|
|
129
277
|
planExists,
|
|
130
278
|
summaryExists,
|
|
279
|
+
reviewExists,
|
|
131
280
|
uatExists,
|
|
132
281
|
status
|
|
133
282
|
};
|
|
134
283
|
});
|
|
135
284
|
}
|
|
136
285
|
|
|
137
|
-
function resolvePhase(cwd, requestedPhase, mode) {
|
|
138
|
-
const phases = listPhases(cwd);
|
|
286
|
+
function resolvePhase(cwd, slug, requestedPhase, mode) {
|
|
287
|
+
const phases = listPhases(cwd, slug);
|
|
139
288
|
if (phases.length === 0) {
|
|
140
289
|
return null;
|
|
141
290
|
}
|
|
@@ -153,93 +302,204 @@ function resolvePhase(cwd, requestedPhase, mode) {
|
|
|
153
302
|
return phases.find(phase => phase.planExists && !phase.summaryExists) || null;
|
|
154
303
|
}
|
|
155
304
|
|
|
305
|
+
if (mode === 'review') {
|
|
306
|
+
return phases.find(phase => phase.summaryExists && !phase.reviewExists) || null;
|
|
307
|
+
}
|
|
308
|
+
|
|
156
309
|
if (mode === 'verify') {
|
|
157
|
-
return phases.find(phase => phase.
|
|
158
|
-
|| [...phases].reverse().find(phase => phase.
|
|
310
|
+
return phases.find(phase => phase.reviewExists && !phase.uatExists)
|
|
311
|
+
|| [...phases].reverse().find(phase => phase.reviewExists)
|
|
159
312
|
|| null;
|
|
160
313
|
}
|
|
161
314
|
|
|
162
315
|
return phases[0];
|
|
163
316
|
}
|
|
164
317
|
|
|
165
|
-
function
|
|
166
|
-
|
|
167
|
-
|
|
318
|
+
function buildRoute(cwd) {
|
|
319
|
+
const paths = rootPaths(cwd);
|
|
320
|
+
const config = loadConfig(cwd);
|
|
321
|
+
const existingCodebase = detectExistingCodebase(cwd);
|
|
322
|
+
const projectType = config.project.type || (existingCodebase ? 'brownfield' : 'greenfield');
|
|
323
|
+
const projectExists = fileExists(paths.project);
|
|
324
|
+
const codebaseMapExists = fileExists(paths.codebaseSummary);
|
|
325
|
+
const activeTrack = readActiveTrack(cwd);
|
|
326
|
+
const tracks = listTracks(cwd);
|
|
327
|
+
|
|
328
|
+
if (!projectExists) {
|
|
329
|
+
return {
|
|
330
|
+
kind: 'init_project',
|
|
331
|
+
command: '/autodev-new-project',
|
|
332
|
+
reason: existingCodebase ? 'existing_code_detected_without_project_state' : 'project_state_missing',
|
|
333
|
+
projectType
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
if (projectType === 'brownfield' && !codebaseMapExists) {
|
|
338
|
+
return {
|
|
339
|
+
kind: 'explore_codebase',
|
|
340
|
+
command: '/autodev-explore-codebase',
|
|
341
|
+
reason: 'brownfield_project_needs_codebase_map',
|
|
342
|
+
projectType
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
if (!activeTrack) {
|
|
347
|
+
return {
|
|
348
|
+
kind: 'track_select',
|
|
349
|
+
command: '/autodev',
|
|
350
|
+
reason: tracks.length > 0 ? 'no_active_track_selected' : 'no_tracks_created',
|
|
351
|
+
projectType
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
const track = trackPaths(cwd, activeTrack);
|
|
356
|
+
if (!track || !fileExists(track.track) || !fileExists(track.roadmap) || !fileExists(track.state)) {
|
|
357
|
+
return {
|
|
358
|
+
kind: 'track_setup',
|
|
359
|
+
command: '/autodev',
|
|
360
|
+
reason: 'active_track_missing_required_artifacts',
|
|
361
|
+
projectType,
|
|
362
|
+
trackSlug: activeTrack
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
const phases = listPhases(cwd, activeTrack);
|
|
367
|
+
if (phases.length === 0) {
|
|
368
|
+
return {
|
|
369
|
+
kind: 'track_setup',
|
|
370
|
+
command: '/autodev',
|
|
371
|
+
reason: 'active_track_has_no_phases',
|
|
372
|
+
projectType,
|
|
373
|
+
trackSlug: activeTrack
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
const nextReview = phases.find(phase => phase.summaryExists && !phase.reviewExists);
|
|
378
|
+
if (nextReview && config.workflow.review_after_execute !== false) {
|
|
379
|
+
return {
|
|
380
|
+
kind: 'review_phase',
|
|
381
|
+
command: `/autodev-review-phase ${nextReview.number}`,
|
|
382
|
+
reason: 'phase_executed_but_not_reviewed',
|
|
383
|
+
projectType,
|
|
384
|
+
trackSlug: activeTrack,
|
|
385
|
+
phaseNumber: nextReview.number
|
|
386
|
+
};
|
|
168
387
|
}
|
|
169
388
|
|
|
170
|
-
const nextVerify =
|
|
389
|
+
const nextVerify = phases.find(phase => phase.reviewExists && !phase.uatExists);
|
|
171
390
|
if (nextVerify) {
|
|
172
|
-
return
|
|
391
|
+
return {
|
|
392
|
+
kind: 'verify_phase',
|
|
393
|
+
command: `/autodev-verify-work ${nextVerify.number}`,
|
|
394
|
+
reason: 'phase_reviewed_but_not_verified',
|
|
395
|
+
projectType,
|
|
396
|
+
trackSlug: activeTrack,
|
|
397
|
+
phaseNumber: nextVerify.number
|
|
398
|
+
};
|
|
173
399
|
}
|
|
174
400
|
|
|
175
|
-
const nextExecute =
|
|
401
|
+
const nextExecute = phases.find(phase => phase.planExists && !phase.summaryExists);
|
|
176
402
|
if (nextExecute) {
|
|
177
|
-
return
|
|
403
|
+
return {
|
|
404
|
+
kind: 'execute_phase',
|
|
405
|
+
command: `/autodev-execute-phase ${nextExecute.number}`,
|
|
406
|
+
reason: 'phase_planned_but_not_executed',
|
|
407
|
+
projectType,
|
|
408
|
+
trackSlug: activeTrack,
|
|
409
|
+
phaseNumber: nextExecute.number
|
|
410
|
+
};
|
|
178
411
|
}
|
|
179
412
|
|
|
180
|
-
const nextPlan =
|
|
413
|
+
const nextPlan = phases.find(phase => !phase.planExists);
|
|
181
414
|
if (nextPlan) {
|
|
182
|
-
return
|
|
415
|
+
return {
|
|
416
|
+
kind: 'plan_phase',
|
|
417
|
+
command: `/autodev-plan-phase ${nextPlan.number}`,
|
|
418
|
+
reason: 'phase_not_planned',
|
|
419
|
+
projectType,
|
|
420
|
+
trackSlug: activeTrack,
|
|
421
|
+
phaseNumber: nextPlan.number
|
|
422
|
+
};
|
|
183
423
|
}
|
|
184
424
|
|
|
185
|
-
return
|
|
425
|
+
return {
|
|
426
|
+
kind: 'track_complete',
|
|
427
|
+
command: '/autodev-cleanup',
|
|
428
|
+
reason: 'active_track_fully_verified',
|
|
429
|
+
projectType,
|
|
430
|
+
trackSlug: activeTrack
|
|
431
|
+
};
|
|
186
432
|
}
|
|
187
433
|
|
|
188
434
|
function buildProgress(cwd) {
|
|
189
|
-
const
|
|
190
|
-
const initialized =
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
verified: 0
|
|
200
|
-
},
|
|
201
|
-
nextCommand: '/autodev-new-project'
|
|
202
|
-
};
|
|
203
|
-
}
|
|
435
|
+
const paths = rootPaths(cwd);
|
|
436
|
+
const initialized = fileExists(paths.project);
|
|
437
|
+
const existingCodebase = detectExistingCodebase(cwd);
|
|
438
|
+
const config = loadConfig(cwd);
|
|
439
|
+
const projectType = config.project.type || (existingCodebase ? 'brownfield' : 'greenfield');
|
|
440
|
+
const activeTrack = readActiveTrack(cwd);
|
|
441
|
+
const tracks = listTracks(cwd);
|
|
442
|
+
const phases = activeTrack ? listPhases(cwd, activeTrack) : [];
|
|
443
|
+
const codebaseMapExists = fileExists(paths.codebaseSummary);
|
|
444
|
+
const route = buildRoute(cwd);
|
|
204
445
|
|
|
205
|
-
const phases = listPhases(cwd);
|
|
206
446
|
return {
|
|
207
|
-
initialized
|
|
447
|
+
initialized,
|
|
448
|
+
projectType,
|
|
449
|
+
existingCodebase,
|
|
450
|
+
codebaseMapExists,
|
|
451
|
+
activeTrack,
|
|
452
|
+
tracks,
|
|
208
453
|
phases,
|
|
209
454
|
counts: {
|
|
455
|
+
trackCount: tracks.length,
|
|
210
456
|
total: phases.length,
|
|
211
457
|
planned: phases.filter(phase => phase.planExists).length,
|
|
212
458
|
executed: phases.filter(phase => phase.summaryExists).length,
|
|
459
|
+
reviewed: phases.filter(phase => phase.reviewExists).length,
|
|
213
460
|
verified: phases.filter(phase => phase.uatExists).length
|
|
214
461
|
},
|
|
215
|
-
|
|
462
|
+
route,
|
|
463
|
+
nextCommand: route.command
|
|
216
464
|
};
|
|
217
465
|
}
|
|
218
466
|
|
|
219
467
|
function renderProgressTable(progress) {
|
|
220
468
|
if (!progress.initialized) {
|
|
221
|
-
return 'No .autodev project found.\nNext: /autodev
|
|
469
|
+
return 'No .autodev project found.\nNext: /autodev';
|
|
222
470
|
}
|
|
223
471
|
|
|
224
472
|
const lines = [
|
|
225
473
|
'Autodev Progress',
|
|
226
474
|
'',
|
|
475
|
+
`Project Type: ${progress.projectType}`,
|
|
476
|
+
`Codebase Map: ${progress.codebaseMapExists ? 'ready' : 'missing'}`,
|
|
477
|
+
`Tracks: ${progress.counts.trackCount}`,
|
|
478
|
+
`Active Track: ${progress.activeTrack || 'none'}`,
|
|
227
479
|
`Phases: ${progress.counts.total}`,
|
|
228
480
|
`Planned: ${progress.counts.planned}`,
|
|
229
481
|
`Executed: ${progress.counts.executed}`,
|
|
482
|
+
`Reviewed: ${progress.counts.reviewed}`,
|
|
230
483
|
`Verified: ${progress.counts.verified}`,
|
|
231
|
-
`Next: ${progress.
|
|
484
|
+
`Next: ${progress.route.command}`,
|
|
232
485
|
'',
|
|
233
|
-
'| Phase | Name | Plan | Summary | UAT | Status |',
|
|
234
|
-
'| --- | --- | --- | --- | --- | --- |'
|
|
486
|
+
'| Phase | Type | Name | Plan | Summary | Review | UAT | Status |',
|
|
487
|
+
'| --- | --- | --- | --- | --- | --- | --- | --- |'
|
|
235
488
|
];
|
|
236
489
|
|
|
237
490
|
for (const phase of progress.phases) {
|
|
238
491
|
lines.push(
|
|
239
|
-
`| ${phase.number} | ${phase.name} | ${phase.planExists ? 'yes' : 'no'} | ${phase.summaryExists ? 'yes' : 'no'} | ${phase.uatExists ? 'yes' : 'no'} | ${phase.status} |`
|
|
492
|
+
`| ${phase.number} | ${phase.type} | ${phase.name} | ${phase.planExists ? 'yes' : 'no'} | ${phase.summaryExists ? 'yes' : 'no'} | ${phase.reviewExists ? 'yes' : 'no'} | ${phase.uatExists ? 'yes' : 'no'} | ${phase.status} |`
|
|
240
493
|
);
|
|
241
494
|
}
|
|
242
495
|
|
|
496
|
+
if (progress.tracks.length > 0) {
|
|
497
|
+
lines.push('', '| Track | Active | Name |', '| --- | --- | --- |');
|
|
498
|
+
for (const track of progress.tracks) {
|
|
499
|
+
lines.push(`| ${track.slug} | ${track.active ? 'yes' : 'no'} | ${track.name} |`);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
243
503
|
return lines.join('\n');
|
|
244
504
|
}
|
|
245
505
|
|
|
@@ -249,30 +509,67 @@ function dotGet(object, key) {
|
|
|
249
509
|
), object);
|
|
250
510
|
}
|
|
251
511
|
|
|
512
|
+
function codebasePaths(cwd) {
|
|
513
|
+
const codebaseDir = rootPaths(cwd).codebaseDir;
|
|
514
|
+
return Object.fromEntries(CODEBASE_FILES.map(name => [name.replace(/\.md$/, ''), path.join(codebaseDir, name)]));
|
|
515
|
+
}
|
|
516
|
+
|
|
252
517
|
function initPayload(cwd, mode, requestedPhase) {
|
|
253
|
-
const
|
|
518
|
+
const paths = rootPaths(cwd);
|
|
254
519
|
const config = loadConfig(cwd);
|
|
255
|
-
const
|
|
520
|
+
const route = buildRoute(cwd);
|
|
521
|
+
const existingCodebase = detectExistingCodebase(cwd);
|
|
522
|
+
const projectType = config.project.type || (existingCodebase ? 'brownfield' : 'greenfield');
|
|
523
|
+
const activeTrack = readActiveTrack(cwd);
|
|
524
|
+
const tracks = listTracks(cwd);
|
|
525
|
+
const track = trackPaths(cwd, activeTrack);
|
|
526
|
+
const phaseMode = mode === 'review-phase' ? 'review' : mode === 'verify-work' ? 'verify' : mode === 'execute-phase' ? 'execute' : mode === 'plan-phase' ? 'plan' : null;
|
|
527
|
+
const phase = phaseMode && activeTrack ? resolvePhase(cwd, activeTrack, requestedPhase, phaseMode) : null;
|
|
528
|
+
const codebase = codebasePaths(cwd);
|
|
529
|
+
|
|
256
530
|
return {
|
|
257
531
|
cwd,
|
|
258
|
-
autodev_exists:
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
532
|
+
autodev_exists: fileExists(paths.root),
|
|
533
|
+
project_exists: fileExists(paths.project),
|
|
534
|
+
existing_code_detected: existingCodebase,
|
|
535
|
+
project_type: projectType,
|
|
536
|
+
config_path: paths.config,
|
|
537
|
+
project_path: paths.project,
|
|
538
|
+
project_state_path: paths.state,
|
|
539
|
+
active_track_path: paths.activeTrack,
|
|
540
|
+
active_track: activeTrack,
|
|
541
|
+
track_count: tracks.length,
|
|
542
|
+
tracks: tracks.map(trackItem => ({
|
|
543
|
+
slug: trackItem.slug,
|
|
544
|
+
name: trackItem.name,
|
|
545
|
+
active: trackItem.active,
|
|
546
|
+
path: trackItem.paths.dir
|
|
547
|
+
})),
|
|
548
|
+
codebase_dir: paths.codebaseDir,
|
|
549
|
+
codebase_map_exists: fileExists(paths.codebaseSummary),
|
|
550
|
+
codebase_paths: codebase,
|
|
551
|
+
track_path: track ? track.dir : null,
|
|
552
|
+
track_doc_path: track ? track.track : null,
|
|
553
|
+
requirements_path: track ? track.requirements : null,
|
|
554
|
+
roadmap_path: track ? track.roadmap : null,
|
|
555
|
+
track_state_path: track ? track.state : null,
|
|
556
|
+
phases_dir: track ? track.phasesDir : null,
|
|
557
|
+
route,
|
|
266
558
|
phase_found: Boolean(phase),
|
|
267
559
|
phase_number: phase ? phase.number : null,
|
|
560
|
+
phase_type: phase ? phase.type : null,
|
|
268
561
|
phase_name: phase ? phase.name : null,
|
|
269
562
|
phase_dir: phase ? phase.dir : null,
|
|
270
563
|
plan_path: phase ? phase.planPath : null,
|
|
271
564
|
summary_path: phase ? phase.summaryPath : null,
|
|
565
|
+
review_path: phase ? phase.reviewPath : null,
|
|
272
566
|
uat_path: phase ? phase.uatPath : null,
|
|
273
567
|
plan_exists: phase ? phase.planExists : false,
|
|
274
568
|
summary_exists: phase ? phase.summaryExists : false,
|
|
275
|
-
|
|
569
|
+
review_exists: phase ? phase.reviewExists : false,
|
|
570
|
+
uat_exists: phase ? phase.uatExists : false,
|
|
571
|
+
workflow: config.workflow,
|
|
572
|
+
git_mode: config.git.mode
|
|
276
573
|
};
|
|
277
574
|
}
|
|
278
575
|
|
|
@@ -309,7 +606,7 @@ function main() {
|
|
|
309
606
|
process.exit(1);
|
|
310
607
|
}
|
|
311
608
|
const value = dotGet(loadConfig(cwd), key);
|
|
312
|
-
if (typeof value === 'object') {
|
|
609
|
+
if (typeof value === 'object' && value !== null) {
|
|
313
610
|
printJson(value);
|
|
314
611
|
} else if (value !== undefined) {
|
|
315
612
|
process.stdout.write(`${String(value)}\n`);
|
|
@@ -317,6 +614,17 @@ function main() {
|
|
|
317
614
|
return;
|
|
318
615
|
}
|
|
319
616
|
|
|
617
|
+
if (command === 'route') {
|
|
618
|
+
const mode = rest[0] || 'json';
|
|
619
|
+
const route = buildRoute(cwd);
|
|
620
|
+
if (mode === 'json') {
|
|
621
|
+
printJson(route);
|
|
622
|
+
return;
|
|
623
|
+
}
|
|
624
|
+
process.stdout.write(`${route.command}\n`);
|
|
625
|
+
return;
|
|
626
|
+
}
|
|
627
|
+
|
|
320
628
|
if (command === 'init') {
|
|
321
629
|
const mode = rest[0];
|
|
322
630
|
const phase = rest[1];
|
|
@@ -339,8 +647,16 @@ if (require.main === module) {
|
|
|
339
647
|
module.exports = {
|
|
340
648
|
autodevDir,
|
|
341
649
|
buildProgress,
|
|
650
|
+
buildRoute,
|
|
651
|
+
detectExistingCodebase,
|
|
652
|
+
initPayload,
|
|
653
|
+
listPhases,
|
|
654
|
+
listTracks,
|
|
342
655
|
loadConfig,
|
|
343
656
|
parseRoadmap,
|
|
657
|
+
readActiveTrack,
|
|
344
658
|
renderProgressTable,
|
|
345
|
-
resolvePhase
|
|
659
|
+
resolvePhase,
|
|
660
|
+
rootPaths,
|
|
661
|
+
trackPaths
|
|
346
662
|
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Codebase Quality
|
|
2
|
+
|
|
3
|
+
## Tests
|
|
4
|
+
- [Existing test setup and obvious gaps]
|
|
5
|
+
|
|
6
|
+
## Conventions
|
|
7
|
+
- [Naming, file organization, style patterns]
|
|
8
|
+
|
|
9
|
+
## Risks
|
|
10
|
+
- [Security, stability, or maintainability concerns]
|
|
11
|
+
|
|
12
|
+
## Tech Debt
|
|
13
|
+
- [Important debt likely to affect the active track]
|