Merge pull request #735 from trek-e/fix/699-plan-slice-empty-scaffold
fix: reject empty scaffold plan files in plan-slice artifact verification (#699)
This commit is contained in:
commit
3354f6300c
2 changed files with 74 additions and 0 deletions
|
|
@ -130,6 +130,16 @@ export function verifyExpectedArtifact(unitType: string, unitId: string, base: s
|
|||
if (!absPath) return unitType === "replan-slice";
|
||||
if (!existsSync(absPath)) return false;
|
||||
|
||||
// plan-slice must produce a plan with actual task entries, not just a scaffold.
|
||||
// The plan file may exist from a prior discussion/context step with only headings
|
||||
// but no tasks. Without this check the artifact is considered "complete" and the
|
||||
// unit gets skipped — but deriveState still returns phase:"planning" because the
|
||||
// plan has no tasks, creating an infinite skip loop (#699).
|
||||
if (unitType === "plan-slice") {
|
||||
const planContent = readFileSync(absPath, "utf-8");
|
||||
if (!/^- \[[xX ]\] \*\*T\d+:/m.test(planContent)) return false;
|
||||
}
|
||||
|
||||
// execute-task must also have its checkbox marked [x] in the slice plan
|
||||
if (unitType === "execute-task") {
|
||||
const parts = unitId.split("/");
|
||||
|
|
|
|||
|
|
@ -320,3 +320,67 @@ test("verifyExpectedArtifact detects roadmap [x] change despite parse cache", ()
|
|||
cleanup(base);
|
||||
}
|
||||
});
|
||||
|
||||
// ─── verifyExpectedArtifact: plan-slice empty scaffold regression (#699) ──
|
||||
|
||||
test("verifyExpectedArtifact rejects plan-slice with empty scaffold", () => {
|
||||
const base = makeTmpBase();
|
||||
try {
|
||||
const sliceDir = join(base, ".gsd", "milestones", "M001", "slices", "S01");
|
||||
mkdirSync(sliceDir, { recursive: true });
|
||||
writeFileSync(join(sliceDir, "S01-PLAN.md"), "# S01: Test Slice\n\n## Tasks\n\n");
|
||||
assert.strictEqual(
|
||||
verifyExpectedArtifact("plan-slice", "M001/S01", base),
|
||||
false,
|
||||
"Empty scaffold should not be treated as completed artifact",
|
||||
);
|
||||
} finally {
|
||||
cleanup(base);
|
||||
}
|
||||
});
|
||||
|
||||
test("verifyExpectedArtifact accepts plan-slice with actual tasks", () => {
|
||||
const base = makeTmpBase();
|
||||
try {
|
||||
const sliceDir = join(base, ".gsd", "milestones", "M001", "slices", "S01");
|
||||
mkdirSync(sliceDir, { recursive: true });
|
||||
writeFileSync(join(sliceDir, "S01-PLAN.md"), [
|
||||
"# S01: Test Slice",
|
||||
"",
|
||||
"## Tasks",
|
||||
"",
|
||||
"- [ ] **T01: Implement feature** `est:2h`",
|
||||
"- [ ] **T02: Write tests** `est:1h`",
|
||||
].join("\n"));
|
||||
assert.strictEqual(
|
||||
verifyExpectedArtifact("plan-slice", "M001/S01", base),
|
||||
true,
|
||||
"Plan with task entries should be treated as completed artifact",
|
||||
);
|
||||
} finally {
|
||||
cleanup(base);
|
||||
}
|
||||
});
|
||||
|
||||
test("verifyExpectedArtifact accepts plan-slice with completed tasks", () => {
|
||||
const base = makeTmpBase();
|
||||
try {
|
||||
const sliceDir = join(base, ".gsd", "milestones", "M001", "slices", "S01");
|
||||
mkdirSync(sliceDir, { recursive: true });
|
||||
writeFileSync(join(sliceDir, "S01-PLAN.md"), [
|
||||
"# S01: Test Slice",
|
||||
"",
|
||||
"## Tasks",
|
||||
"",
|
||||
"- [x] **T01: Implement feature** `est:2h`",
|
||||
"- [ ] **T02: Write tests** `est:1h`",
|
||||
].join("\n"));
|
||||
assert.strictEqual(
|
||||
verifyExpectedArtifact("plan-slice", "M001/S01", base),
|
||||
true,
|
||||
"Plan with completed task entries should be treated as completed artifact",
|
||||
);
|
||||
} finally {
|
||||
cleanup(base);
|
||||
}
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue