feat(ultraplan-local): extend project-discovery with review.md
This commit is contained in:
parent
535dce87dc
commit
ebeae010c1
2 changed files with 61 additions and 2 deletions
|
|
@ -20,6 +20,7 @@ import { join } from 'node:path';
|
|||
* architecture: { overview: string|null, gaps: string|null, looseFiles: string[] },
|
||||
* plan: string|null,
|
||||
* progress: string|null,
|
||||
* review: string|null,
|
||||
* }} ProjectArtifacts
|
||||
*/
|
||||
|
||||
|
|
@ -32,6 +33,7 @@ export function discoverProject(projectDir) {
|
|||
architecture: { overview: null, gaps: null, looseFiles: [] },
|
||||
plan: null,
|
||||
progress: null,
|
||||
review: null,
|
||||
};
|
||||
|
||||
if (!projectDir || !existsSync(projectDir) || !statSync(projectDir).isDirectory()) {
|
||||
|
|
@ -47,6 +49,9 @@ export function discoverProject(projectDir) {
|
|||
const progressPath = join(projectDir, 'progress.json');
|
||||
if (existsSync(progressPath) && statSync(progressPath).isFile()) out.progress = progressPath;
|
||||
|
||||
const reviewPath = join(projectDir, 'review.md');
|
||||
if (existsSync(reviewPath) && statSync(reviewPath).isFile()) out.review = reviewPath;
|
||||
|
||||
const researchDir = join(projectDir, 'research');
|
||||
if (existsSync(researchDir) && statSync(researchDir).isDirectory()) {
|
||||
out.research = readdirSync(researchDir)
|
||||
|
|
@ -72,10 +77,11 @@ export function discoverProject(projectDir) {
|
|||
|
||||
/**
|
||||
* Validate that artifact set is consistent for a given pipeline phase.
|
||||
* Phase = 'brief' | 'research' | 'plan' | 'execute'.
|
||||
* Phase = 'brief' | 'research' | 'plan' | 'execute' | 'review'.
|
||||
*/
|
||||
export function checkPhaseRequirements(artifacts, phase) {
|
||||
const errors = [];
|
||||
const warnings = [];
|
||||
if (phase === 'research' && !artifacts.brief) {
|
||||
errors.push({ code: 'PROJECT_NO_BRIEF', message: 'research phase requires brief.md' });
|
||||
}
|
||||
|
|
@ -85,5 +91,16 @@ export function checkPhaseRequirements(artifacts, phase) {
|
|||
if (phase === 'execute' && !artifacts.plan) {
|
||||
errors.push({ code: 'PROJECT_NO_PLAN', message: 'execute phase requires plan.md' });
|
||||
}
|
||||
return { valid: errors.length === 0, errors, warnings: [], parsed: artifacts };
|
||||
if (phase === 'review') {
|
||||
if (!artifacts.brief) {
|
||||
errors.push({ code: 'PROJECT_NO_BRIEF', message: 'review phase requires brief.md' });
|
||||
}
|
||||
if (!artifacts.progress) {
|
||||
warnings.push({
|
||||
code: 'PROJECT_NO_PROGRESS',
|
||||
message: 'review phase: progress.json absent — scope detection will fall back to brief.md mtime',
|
||||
});
|
||||
}
|
||||
}
|
||||
return { valid: errors.length === 0, errors, warnings, parsed: artifacts };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -104,3 +104,45 @@ test('checkPhaseRequirements — happy path', () => {
|
|||
const r = checkPhaseRequirements({ brief: 'x', plan: 'y' }, 'plan');
|
||||
assert.equal(r.valid, true);
|
||||
});
|
||||
|
||||
test('discoverProject — finds review.md when present', () => {
|
||||
const root = setupProject({
|
||||
'brief.md': 'b',
|
||||
'review.md': 'r',
|
||||
});
|
||||
try {
|
||||
const a = discoverProject(root);
|
||||
assert.equal(a.review, join(root, 'review.md'));
|
||||
} finally {
|
||||
rmSync(root, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test('discoverProject — review null when absent', () => {
|
||||
const root = setupProject({
|
||||
'brief.md': 'b',
|
||||
});
|
||||
try {
|
||||
const a = discoverProject(root);
|
||||
assert.equal(a.review, null);
|
||||
} finally {
|
||||
rmSync(root, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test('checkPhaseRequirements — review phase needs brief (error) and tolerates missing progress (warning)', () => {
|
||||
// Missing brief → error
|
||||
const r1 = checkPhaseRequirements({ brief: null, progress: null }, 'review');
|
||||
assert.equal(r1.valid, false);
|
||||
assert.ok(r1.errors.find(e => e.code === 'PROJECT_NO_BRIEF'));
|
||||
|
||||
// Has brief, no progress → valid (with warning)
|
||||
const r2 = checkPhaseRequirements({ brief: 'x', progress: null }, 'review');
|
||||
assert.equal(r2.valid, true, JSON.stringify(r2));
|
||||
assert.ok(r2.warnings.find(w => w.code === 'PROJECT_NO_PROGRESS'));
|
||||
|
||||
// Has both → valid, no warning
|
||||
const r3 = checkPhaseRequirements({ brief: 'x', progress: 'p' }, 'review');
|
||||
assert.equal(r3.valid, true);
|
||||
assert.equal(r3.warnings.length, 0);
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue