feat(sf): add cross-slice and milestone integrity checks to post-execution checks
- Add checkCrossSliceConsistency() to detect key_file conflicts across slices - Add checkMilestoneIntegrity() to verify completed slices have summaries and no active requirements are orphaned - Extend runPostExecutionChecks() signature with optional milestoneId and allSliceTasks parameters - Wire cross-slice task gathering into auto-verification.js call site - Add comprehensive node:test suite for both new checks
This commit is contained in:
parent
338c75fc6f
commit
7b225696cc
3 changed files with 570 additions and 2 deletions
|
|
@ -14,6 +14,13 @@
|
|||
*/
|
||||
import { existsSync, readFileSync } from "node:fs";
|
||||
import { dirname, extname, resolve } from "node:path";
|
||||
import { parseRequirementsSections } from "./md-importer.js";
|
||||
import { parseRoadmap } from "./parsers.js";
|
||||
import {
|
||||
resolveMilestoneFile,
|
||||
resolveSfRootFile,
|
||||
resolveSliceFile,
|
||||
} from "./paths.js";
|
||||
// ─── Import Resolution Check ─────────────────────────────────────────────────
|
||||
/**
|
||||
* Extract relative import paths from TypeScript/JavaScript source code.
|
||||
|
|
@ -363,6 +370,165 @@ function checkNamingConsistency(source, fileName) {
|
|||
}
|
||||
return results;
|
||||
}
|
||||
// ─── Cross-Slice Consistency Check ───────────────────────────────────────────
|
||||
/**
|
||||
* Check whether any key_file in the current task is also referenced by tasks
|
||||
* in other slices. Returns non-blocking warnings for potential conflicts.
|
||||
*/
|
||||
export function checkCrossSliceConsistency(taskRow, allSliceTasks, _basePath) {
|
||||
const results = [];
|
||||
if (!taskRow.key_files || taskRow.key_files.length === 0) {
|
||||
return [
|
||||
{
|
||||
passed: true,
|
||||
blocking: false,
|
||||
category: "cross-slice",
|
||||
target: taskRow.id,
|
||||
message: "No key files to check for cross-slice consistency",
|
||||
},
|
||||
];
|
||||
}
|
||||
if (!allSliceTasks || allSliceTasks.length === 0) {
|
||||
return [
|
||||
{
|
||||
passed: true,
|
||||
blocking: false,
|
||||
category: "cross-slice",
|
||||
target: taskRow.id,
|
||||
message: "No other slice tasks to check against",
|
||||
},
|
||||
];
|
||||
}
|
||||
const otherSliceTasks = allSliceTasks.filter(
|
||||
(t) =>
|
||||
t.slice_id !== taskRow.slice_id && t.key_files && t.key_files.length > 0,
|
||||
);
|
||||
const conflictingSlices = new Map(); // file → Set(sliceIds)
|
||||
for (const file of taskRow.key_files) {
|
||||
for (const other of otherSliceTasks) {
|
||||
if (other.key_files.includes(file)) {
|
||||
if (!conflictingSlices.has(file)) {
|
||||
conflictingSlices.set(file, new Set());
|
||||
}
|
||||
conflictingSlices.get(file).add(other.slice_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (conflictingSlices.size === 0) {
|
||||
return [
|
||||
{
|
||||
passed: true,
|
||||
blocking: false,
|
||||
category: "cross-slice",
|
||||
target: taskRow.id,
|
||||
message: "No cross-slice conflicts detected",
|
||||
},
|
||||
];
|
||||
}
|
||||
for (const [file, sliceIds] of conflictingSlices) {
|
||||
results.push({
|
||||
passed: false,
|
||||
blocking: false,
|
||||
category: "cross-slice",
|
||||
target: file,
|
||||
message: `File '${file}' is also a key file in slice(s) ${Array.from(sliceIds).join(", ")} — verify this is intentional`,
|
||||
});
|
||||
}
|
||||
return results;
|
||||
}
|
||||
// ─── Milestone Integrity Check ───────────────────────────────────────────────
|
||||
/**
|
||||
* Verify milestone-level integrity: completed slices have summaries,
|
||||
* roadmap is consistent, and no active requirements are orphaned.
|
||||
*/
|
||||
export function checkMilestoneIntegrity(milestoneId, basePath) {
|
||||
const results = [];
|
||||
// Check ROADMAP exists and parse it
|
||||
const roadmapPath = resolveMilestoneFile(basePath, milestoneId, "ROADMAP");
|
||||
if (!roadmapPath || !existsSync(roadmapPath)) {
|
||||
results.push({
|
||||
passed: false,
|
||||
blocking: false,
|
||||
category: "milestone",
|
||||
target: milestoneId,
|
||||
message: `Milestone ROADMAP.md not found for ${milestoneId}`,
|
||||
});
|
||||
return results;
|
||||
}
|
||||
let roadmap;
|
||||
try {
|
||||
const content = readFileSync(roadmapPath, "utf-8");
|
||||
roadmap = parseRoadmap(content);
|
||||
} catch (err) {
|
||||
results.push({
|
||||
passed: false,
|
||||
blocking: false,
|
||||
category: "milestone",
|
||||
target: milestoneId,
|
||||
message: `Failed to parse ROADMAP.md for ${milestoneId}: ${err.message}`,
|
||||
});
|
||||
return results;
|
||||
}
|
||||
// Check that slices marked done in ROADMAP have SUMMARY.md files
|
||||
for (const slice of roadmap.slices) {
|
||||
if (slice.done) {
|
||||
const summaryPath = resolveSliceFile(
|
||||
basePath,
|
||||
milestoneId,
|
||||
slice.id,
|
||||
"SUMMARY",
|
||||
);
|
||||
if (!summaryPath || !existsSync(summaryPath)) {
|
||||
results.push({
|
||||
passed: false,
|
||||
blocking: false,
|
||||
category: "milestone",
|
||||
target: `${milestoneId}/${slice.id}`,
|
||||
message: `Slice ${slice.id} is marked done in ROADMAP but missing SUMMARY.md`,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
// Check REQUIREMENTS.md for active requirements belonging to this milestone
|
||||
const reqPath = resolveSfRootFile(basePath, "REQUIREMENTS");
|
||||
if (reqPath && existsSync(reqPath)) {
|
||||
try {
|
||||
const reqContent = readFileSync(reqPath, "utf-8");
|
||||
const requirements = parseRequirementsSections(reqContent);
|
||||
const activeForMilestone = requirements.filter(
|
||||
(r) => r.status === "active" && r.primary_owner === milestoneId,
|
||||
);
|
||||
for (const req of activeForMilestone) {
|
||||
results.push({
|
||||
passed: false,
|
||||
blocking: false,
|
||||
category: "milestone",
|
||||
target: req.id,
|
||||
message: `Active requirement ${req.id} (${req.description || "no description"}) is owned by ${milestoneId} but not yet validated`,
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
results.push({
|
||||
passed: false,
|
||||
blocking: false,
|
||||
category: "milestone",
|
||||
target: milestoneId,
|
||||
message: `Failed to parse REQUIREMENTS.md: ${err.message}`,
|
||||
});
|
||||
}
|
||||
}
|
||||
// If no issues found, return a passing check
|
||||
if (results.length === 0) {
|
||||
results.push({
|
||||
passed: true,
|
||||
blocking: false,
|
||||
category: "milestone",
|
||||
target: milestoneId,
|
||||
message: `Milestone ${milestoneId} integrity checks passed`,
|
||||
});
|
||||
}
|
||||
return results;
|
||||
}
|
||||
// ─── Main Entry Point ────────────────────────────────────────────────────────
|
||||
/**
|
||||
* Run all post-execution checks against a completed task.
|
||||
|
|
@ -370,9 +536,17 @@ function checkNamingConsistency(source, fileName) {
|
|||
* @param taskRow - The completed task row
|
||||
* @param priorTasks - Array of TaskRow from prior completed tasks in the slice
|
||||
* @param basePath - Base path for resolving file references
|
||||
* @param milestoneId - Optional milestone ID for milestone-level checks
|
||||
* @param allSliceTasks - Optional array of all tasks across all slices for cross-slice checks
|
||||
* @returns PostExecutionResult with status, checks, and duration
|
||||
*/
|
||||
export function runPostExecutionChecks(taskRow, priorTasks, basePath) {
|
||||
export function runPostExecutionChecks(
|
||||
taskRow,
|
||||
priorTasks,
|
||||
basePath,
|
||||
milestoneId,
|
||||
allSliceTasks,
|
||||
) {
|
||||
const startTime = Date.now();
|
||||
const allChecks = [];
|
||||
// Run all checks
|
||||
|
|
@ -384,6 +558,19 @@ export function runPostExecutionChecks(taskRow, priorTasks, basePath) {
|
|||
);
|
||||
const patternChecks = checkPatternConsistency(taskRow, priorTasks, basePath);
|
||||
allChecks.push(...importChecks, ...signatureChecks, ...patternChecks);
|
||||
// New checks: cross-slice consistency and milestone integrity
|
||||
if (allSliceTasks && allSliceTasks.length > 0) {
|
||||
const crossSliceChecks = checkCrossSliceConsistency(
|
||||
taskRow,
|
||||
allSliceTasks,
|
||||
basePath,
|
||||
);
|
||||
allChecks.push(...crossSliceChecks);
|
||||
}
|
||||
if (milestoneId) {
|
||||
const milestoneChecks = checkMilestoneIntegrity(milestoneId, basePath);
|
||||
allChecks.push(...milestoneChecks);
|
||||
}
|
||||
const durationMs = Date.now() - startTime;
|
||||
// Determine overall status
|
||||
const hasBlockingFailure = allChecks.some((c) => !c.passed && c.blocking);
|
||||
|
|
|
|||
368
src/resources/extensions/sf/tests/post-execution-checks.test.mjs
Normal file
368
src/resources/extensions/sf/tests/post-execution-checks.test.mjs
Normal file
|
|
@ -0,0 +1,368 @@
|
|||
import assert from "node:assert/strict";
|
||||
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { describe, it } from "node:test";
|
||||
import {
|
||||
checkCrossSliceConsistency,
|
||||
checkMilestoneIntegrity,
|
||||
runPostExecutionChecks,
|
||||
} from "../post-execution-checks.js";
|
||||
|
||||
function makeTempDir(prefix) {
|
||||
return mkdtempSync(join(tmpdir(), prefix));
|
||||
}
|
||||
|
||||
function cleanup(dir) {
|
||||
try {
|
||||
rmSync(dir, { recursive: true, force: true });
|
||||
} catch {}
|
||||
}
|
||||
|
||||
function writeMilestoneFile(base, milestoneId, fileName, content) {
|
||||
const dir = join(base, ".sf", "milestones", milestoneId);
|
||||
mkdirSync(dir, { recursive: true });
|
||||
writeFileSync(join(dir, fileName), content);
|
||||
}
|
||||
|
||||
function writeSliceFile(base, milestoneId, sliceId, fileName, content) {
|
||||
const dir = join(base, ".sf", "milestones", milestoneId, "slices", sliceId);
|
||||
mkdirSync(dir, { recursive: true });
|
||||
writeFileSync(join(dir, fileName), content);
|
||||
}
|
||||
|
||||
// ─── checkCrossSliceConsistency ────────────────────────────────────────────
|
||||
|
||||
describe("checkCrossSliceConsistency", () => {
|
||||
it("passes_when_no_other_slice_touches_same_files", () => {
|
||||
const taskRow = {
|
||||
milestone_id: "M001",
|
||||
slice_id: "S03",
|
||||
id: "T01",
|
||||
key_files: ["src/a.ts", "src/b.ts"],
|
||||
};
|
||||
const allSliceTasks = [
|
||||
{
|
||||
milestone_id: "M001",
|
||||
slice_id: "S01",
|
||||
id: "T01",
|
||||
key_files: ["src/c.ts"],
|
||||
},
|
||||
{
|
||||
milestone_id: "M001",
|
||||
slice_id: "S02",
|
||||
id: "T01",
|
||||
key_files: ["src/d.ts"],
|
||||
},
|
||||
];
|
||||
const results = checkCrossSliceConsistency(taskRow, allSliceTasks, "/tmp");
|
||||
assert.equal(results.length, 1);
|
||||
assert.equal(results[0].passed, true);
|
||||
assert.equal(results[0].category, "cross-slice");
|
||||
});
|
||||
|
||||
it("warns_when_another_slice_has_same_key_file", () => {
|
||||
const taskRow = {
|
||||
milestone_id: "M001",
|
||||
slice_id: "S03",
|
||||
id: "T01",
|
||||
key_files: ["src/shared.ts", "src/b.ts"],
|
||||
};
|
||||
const allSliceTasks = [
|
||||
{
|
||||
milestone_id: "M001",
|
||||
slice_id: "S01",
|
||||
id: "T01",
|
||||
key_files: ["src/shared.ts"],
|
||||
},
|
||||
{
|
||||
milestone_id: "M001",
|
||||
slice_id: "S02",
|
||||
id: "T01",
|
||||
key_files: ["src/d.ts"],
|
||||
},
|
||||
];
|
||||
const results = checkCrossSliceConsistency(taskRow, allSliceTasks, "/tmp");
|
||||
assert.equal(results.length, 1);
|
||||
assert.equal(results[0].passed, false);
|
||||
assert.equal(results[0].blocking, false);
|
||||
assert.equal(results[0].category, "cross-slice");
|
||||
assert.ok(results[0].message.includes("src/shared.ts"));
|
||||
assert.ok(results[0].message.includes("S01"));
|
||||
});
|
||||
|
||||
it("ignores_tasks_in_same_slice", () => {
|
||||
const taskRow = {
|
||||
milestone_id: "M001",
|
||||
slice_id: "S03",
|
||||
id: "T02",
|
||||
key_files: ["src/shared.ts"],
|
||||
};
|
||||
const allSliceTasks = [
|
||||
{
|
||||
milestone_id: "M001",
|
||||
slice_id: "S03",
|
||||
id: "T01",
|
||||
key_files: ["src/shared.ts"],
|
||||
},
|
||||
];
|
||||
const results = checkCrossSliceConsistency(taskRow, allSliceTasks, "/tmp");
|
||||
assert.equal(results.length, 1);
|
||||
assert.equal(results[0].passed, true);
|
||||
});
|
||||
|
||||
it("handles_multiple_conflicts", () => {
|
||||
const taskRow = {
|
||||
milestone_id: "M001",
|
||||
slice_id: "S03",
|
||||
id: "T01",
|
||||
key_files: ["src/a.ts", "src/b.ts"],
|
||||
};
|
||||
const allSliceTasks = [
|
||||
{
|
||||
milestone_id: "M001",
|
||||
slice_id: "S01",
|
||||
id: "T01",
|
||||
key_files: ["src/a.ts"],
|
||||
},
|
||||
{
|
||||
milestone_id: "M001",
|
||||
slice_id: "S02",
|
||||
id: "T01",
|
||||
key_files: ["src/b.ts"],
|
||||
},
|
||||
];
|
||||
const results = checkCrossSliceConsistency(taskRow, allSliceTasks, "/tmp");
|
||||
assert.equal(results.length, 2);
|
||||
assert.equal(results[0].passed, false);
|
||||
assert.equal(results[1].passed, false);
|
||||
});
|
||||
});
|
||||
|
||||
// ─── checkMilestoneIntegrity ───────────────────────────────────────────────
|
||||
|
||||
describe("checkMilestoneIntegrity", () => {
|
||||
it("passes_when_all_complete_slices_have_summaries", () => {
|
||||
const base = makeTempDir("sf-pec-mi-");
|
||||
try {
|
||||
writeMilestoneFile(
|
||||
base,
|
||||
"M001",
|
||||
"M001-ROADMAP.md",
|
||||
[
|
||||
"# M001: Test",
|
||||
"",
|
||||
"## Slice Overview",
|
||||
"",
|
||||
"| ID | Slice | Risk | Depends | Done | After this |",
|
||||
"|----|-------|------|---------|------|------------|",
|
||||
"| S01 | S01 | low | — | ✅ | Done. |",
|
||||
"| S02 | S02 | low | — | ⬜ | Pending. |",
|
||||
"",
|
||||
].join("\n"),
|
||||
);
|
||||
writeSliceFile(base, "M001", "S01", "S01-SUMMARY.md", "# S01 Summary\n");
|
||||
writeFileSync(
|
||||
join(base, ".sf", "REQUIREMENTS.md"),
|
||||
[
|
||||
"## Active",
|
||||
"",
|
||||
"### R001 — Test",
|
||||
"- Class: functional",
|
||||
"- Description: test",
|
||||
"- Why: test",
|
||||
"- Source: test",
|
||||
"- Primary owner: M002",
|
||||
"",
|
||||
].join("\n"),
|
||||
);
|
||||
|
||||
const results = checkMilestoneIntegrity("M001", base);
|
||||
assert.ok(
|
||||
results.every((r) => r.passed),
|
||||
`Expected all checks to pass, got: ${JSON.stringify(results)}`,
|
||||
);
|
||||
} finally {
|
||||
cleanup(base);
|
||||
}
|
||||
});
|
||||
|
||||
it("warns_when_complete_slice_lacks_summary", () => {
|
||||
const base = makeTempDir("sf-pec-mi-");
|
||||
try {
|
||||
writeMilestoneFile(
|
||||
base,
|
||||
"M001",
|
||||
"M001-ROADMAP.md",
|
||||
[
|
||||
"# M001: Test",
|
||||
"",
|
||||
"## Slice Overview",
|
||||
"",
|
||||
"| ID | Slice | Risk | Depends | Done | After this |",
|
||||
"|----|-------|------|---------|------|------------|",
|
||||
"| S01 | S01 | low | — | ✅ | Done. |",
|
||||
"",
|
||||
].join("\n"),
|
||||
);
|
||||
// Intentionally NOT writing S01-SUMMARY.md
|
||||
writeFileSync(
|
||||
join(base, ".sf", "REQUIREMENTS.md"),
|
||||
["## Active", "", "### R001 — Test", "- Primary owner: M001", ""].join(
|
||||
"\n",
|
||||
),
|
||||
);
|
||||
|
||||
const results = checkMilestoneIntegrity("M001", base);
|
||||
const missing = results.find((r) => r.message.includes("SUMMARY"));
|
||||
assert.ok(
|
||||
missing,
|
||||
`Expected missing summary warning, got: ${JSON.stringify(results)}`,
|
||||
);
|
||||
assert.equal(missing.passed, false);
|
||||
assert.equal(missing.blocking, false);
|
||||
} finally {
|
||||
cleanup(base);
|
||||
}
|
||||
});
|
||||
|
||||
it("warns_when_active_requirement_belongs_to_milestone", () => {
|
||||
const base = makeTempDir("sf-pec-mi-");
|
||||
try {
|
||||
writeMilestoneFile(
|
||||
base,
|
||||
"M001",
|
||||
"M001-ROADMAP.md",
|
||||
[
|
||||
"# M001: Test",
|
||||
"",
|
||||
"## Slice Overview",
|
||||
"",
|
||||
"| ID | Slice | Risk | Depends | Done | After this |",
|
||||
"|----|-------|------|---------|------|------------|",
|
||||
"| S01 | S01 | low | — | ✅ | Done. |",
|
||||
"",
|
||||
].join("\n"),
|
||||
);
|
||||
writeSliceFile(base, "M001", "S01", "S01-SUMMARY.md", "# S01 Summary\n");
|
||||
writeFileSync(
|
||||
join(base, ".sf", "REQUIREMENTS.md"),
|
||||
[
|
||||
"## Active",
|
||||
"",
|
||||
"### R001 — Test",
|
||||
"- Class: functional",
|
||||
"- Description: test",
|
||||
"- Why: test",
|
||||
"- Source: test",
|
||||
"- Primary owner: M001",
|
||||
"",
|
||||
].join("\n"),
|
||||
);
|
||||
|
||||
const results = checkMilestoneIntegrity("M001", base);
|
||||
const reqWarning = results.find((r) => r.message.includes("R001"));
|
||||
assert.ok(
|
||||
reqWarning,
|
||||
`Expected active requirement warning, got: ${JSON.stringify(results)}`,
|
||||
);
|
||||
assert.equal(reqWarning.passed, false);
|
||||
assert.equal(reqWarning.blocking, false);
|
||||
} finally {
|
||||
cleanup(base);
|
||||
}
|
||||
});
|
||||
|
||||
it("warns_when_roadmap_missing", () => {
|
||||
const base = makeTempDir("sf-pec-mi-");
|
||||
try {
|
||||
mkdirSync(join(base, ".sf"), { recursive: true });
|
||||
writeFileSync(join(base, ".sf", "REQUIREMENTS.md"), "## Active\n\n");
|
||||
const results = checkMilestoneIntegrity("M001", base);
|
||||
const missing = results.find((r) => r.message.includes("ROADMAP"));
|
||||
assert.ok(
|
||||
missing,
|
||||
`Expected missing roadmap warning, got: ${JSON.stringify(results)}`,
|
||||
);
|
||||
assert.equal(missing.passed, false);
|
||||
assert.equal(missing.blocking, false);
|
||||
} finally {
|
||||
cleanup(base);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// ─── runPostExecutionChecks ────────────────────────────────────────────────
|
||||
|
||||
describe("runPostExecutionChecks", () => {
|
||||
it("includes_cross_slice_and_milestone_checks_when_args_provided", () => {
|
||||
const base = makeTempDir("sf-pec-rpec-");
|
||||
try {
|
||||
writeMilestoneFile(
|
||||
base,
|
||||
"M001",
|
||||
"M001-ROADMAP.md",
|
||||
[
|
||||
"# M001: Test",
|
||||
"",
|
||||
"## Slice Overview",
|
||||
"",
|
||||
"| ID | Slice | Risk | Depends | Done | After this |",
|
||||
"|----|-------|------|---------|------|------------|",
|
||||
"| S01 | S01 | low | — | ✅ | Done. |",
|
||||
"| S02 | S02 | low | — | ⬜ | Pending. |",
|
||||
"",
|
||||
].join("\n"),
|
||||
);
|
||||
writeSliceFile(base, "M001", "S01", "S01-SUMMARY.md", "# S01\n");
|
||||
writeFileSync(join(base, ".sf", "REQUIREMENTS.md"), "## Active\n\n");
|
||||
|
||||
const taskRow = {
|
||||
milestone_id: "M001",
|
||||
slice_id: "S02",
|
||||
id: "T01",
|
||||
key_files: ["src/a.ts"],
|
||||
};
|
||||
const allSliceTasks = [
|
||||
{
|
||||
milestone_id: "M001",
|
||||
slice_id: "S01",
|
||||
id: "T01",
|
||||
key_files: ["src/a.ts"],
|
||||
},
|
||||
];
|
||||
|
||||
const result = runPostExecutionChecks(
|
||||
taskRow,
|
||||
[],
|
||||
base,
|
||||
"M001",
|
||||
allSliceTasks,
|
||||
);
|
||||
const categories = result.checks.map((c) => c.category);
|
||||
assert.ok(
|
||||
categories.includes("cross-slice"),
|
||||
`Expected cross-slice check, got categories: ${categories}`,
|
||||
);
|
||||
assert.ok(
|
||||
categories.includes("milestone"),
|
||||
`Expected milestone check, got categories: ${categories}`,
|
||||
);
|
||||
} finally {
|
||||
cleanup(base);
|
||||
}
|
||||
});
|
||||
|
||||
it("skips_new_checks_when_optional_args_omitted", () => {
|
||||
const taskRow = {
|
||||
milestone_id: "M001",
|
||||
slice_id: "S01",
|
||||
id: "T01",
|
||||
key_files: [],
|
||||
};
|
||||
const result = runPostExecutionChecks(taskRow, [], "/tmp");
|
||||
const categories = result.checks.map((c) => c.category);
|
||||
assert.ok(!categories.includes("cross-slice"));
|
||||
assert.ok(!categories.includes("milestone"));
|
||||
});
|
||||
});
|
||||
|
|
@ -11,6 +11,7 @@
|
|||
*/
|
||||
import { mkdirSync, writeFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import { getErrorMessage } from "../error-utils.js";
|
||||
import { loadFile } from "../files.js";
|
||||
import { parseRoadmap } from "../parsers.js";
|
||||
import { resolveMilestoneFile, resolveSlicePath } from "../paths.js";
|
||||
|
|
@ -45,7 +46,6 @@ import {
|
|||
formatExecuteTaskRecoveryStatus,
|
||||
inspectExecuteTaskDurability,
|
||||
} from "./unit-runtime.js";
|
||||
import { getErrorMessage } from "../error-utils.js";
|
||||
|
||||
function computeTokenCountFromSession(ctx) {
|
||||
const entries = ctx.sessionManager?.getEntries?.() ?? [];
|
||||
|
|
@ -568,10 +568,23 @@ export async function runPostUnitVerification(vctx, pauseAuto) {
|
|||
t.sequence < taskRow.sequence,
|
||||
);
|
||||
// Run post-execution checks
|
||||
// Get all tasks across all slices for cross-slice consistency check
|
||||
const allSliceTasks = [];
|
||||
if (mid) {
|
||||
const slices = getMilestoneSlices(mid);
|
||||
for (const slice of slices) {
|
||||
if (slice.id !== sid) {
|
||||
const sliceTasks = getSliceTasks(mid, slice.id);
|
||||
allSliceTasks.push(...sliceTasks);
|
||||
}
|
||||
}
|
||||
}
|
||||
const postExecResult = runPostExecutionChecks(
|
||||
taskRow,
|
||||
priorTasks,
|
||||
s.basePath,
|
||||
mid,
|
||||
allSliceTasks,
|
||||
);
|
||||
// Store checks for evidence JSON
|
||||
postExecChecks = postExecResult.checks;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue