sf snapshot: pre-dispatch, uncommitted changes after 42m inactivity
This commit is contained in:
parent
061985b226
commit
bffd6c22fc
1 changed files with 109 additions and 10 deletions
|
|
@ -2,14 +2,18 @@ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { describe, expect, test } from "vitest";
|
||||
import { formatFailureContext } from "../verification-gate.js";
|
||||
import { checkCrossTaskSignatures } from "../post-execution-checks.js";
|
||||
import { getPriorSliceCompletionBlocker } from "../dispatch-guard.js";
|
||||
import { extractPackageReferences } from "../pre-execution-checks.js";
|
||||
import {
|
||||
evaluateRunawayGuard,
|
||||
resetRunawayGuardState,
|
||||
} from "../auto-runaway-guard.js";
|
||||
import { runCustomVerification } from "../custom-verification.js";
|
||||
import { getPriorSliceCompletionBlocker } from "../dispatch-guard.js";
|
||||
import { checkCrossTaskSignatures } from "../post-execution-checks.js";
|
||||
import {
|
||||
extractPackageReferences,
|
||||
normalizeFilePath,
|
||||
} from "../pre-execution-checks.js";
|
||||
import { formatFailureContext, isLikelyCommand } from "../verification-gate.js";
|
||||
|
||||
// ─── Bug 1: formatFailureContext double-truncation ─────────────────────────
|
||||
|
||||
|
|
@ -44,8 +48,20 @@ describe("formatFailureContext", () => {
|
|||
const result = {
|
||||
passed: false,
|
||||
checks: [
|
||||
{ command: "npm run lint", exitCode: 1, stdout: "", stderr: stderrA, durationMs: 100 },
|
||||
{ command: "npm run test", exitCode: 1, stdout: "", stderr: stderrB, durationMs: 100 },
|
||||
{
|
||||
command: "npm run lint",
|
||||
exitCode: 1,
|
||||
stdout: "",
|
||||
stderr: stderrA,
|
||||
durationMs: 100,
|
||||
},
|
||||
{
|
||||
command: "npm run test",
|
||||
exitCode: 1,
|
||||
stdout: "",
|
||||
stderr: stderrB,
|
||||
durationMs: 100,
|
||||
},
|
||||
],
|
||||
discoverySource: "preference" as const,
|
||||
timestamp: Date.now(),
|
||||
|
|
@ -135,10 +151,10 @@ describe("getPriorSliceCompletionBlocker", () => {
|
|||
writeFileSync(
|
||||
join(base, ".sf", "milestones", "M001", "ROADMAP.md"),
|
||||
"# M001\n\n" +
|
||||
"- [ ] S01 — first slice\n" +
|
||||
" - depends: S02\n" +
|
||||
"- [ ] S02 — second slice\n" +
|
||||
"- [ ] S03 — third slice\n",
|
||||
"- [ ] S01 — first slice\n" +
|
||||
" - depends: S02\n" +
|
||||
"- [ ] S02 — second slice\n" +
|
||||
"- [ ] S03 — third slice\n",
|
||||
);
|
||||
|
||||
const blocker = getPriorSliceCompletionBlocker(
|
||||
|
|
@ -299,3 +315,86 @@ describe("evaluateRunawayGuard", () => {
|
|||
expect(decision.action).toBe("warn");
|
||||
});
|
||||
});
|
||||
|
||||
// ─── Bug 6: isLikelyCommand prose guard gaps ───────────────────────────────
|
||||
|
||||
describe("isLikelyCommand", () => {
|
||||
test("treats single non-command token as prose", () => {
|
||||
expect(isLikelyCommand("hello")).toBe(false);
|
||||
expect(isLikelyCommand("world")).toBe(false);
|
||||
});
|
||||
|
||||
test("treats single known command token as command", () => {
|
||||
expect(isLikelyCommand("npm")).toBe(true);
|
||||
expect(isLikelyCommand("node")).toBe(true);
|
||||
});
|
||||
|
||||
test("treats single-letter first token with 2+ words as prose", () => {
|
||||
expect(isLikelyCommand("a quick test")).toBe(false);
|
||||
expect(isLikelyCommand("it works now")).toBe(false);
|
||||
expect(isLikelyCommand("i am here")).toBe(false);
|
||||
});
|
||||
|
||||
test("still treats short command-like strings as commands", () => {
|
||||
expect(isLikelyCommand("npm test")).toBe(true);
|
||||
expect(isLikelyCommand("./script.sh")).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
// ─── Bug 7: custom-verification standalone shell operators ─────────────────
|
||||
|
||||
describe("runCustomVerification shell-command policy", () => {
|
||||
test("pauses on standalone pipe operator", () => {
|
||||
const base = mkdtempSync(join(tmpdir(), "sf-cv-pipe-"));
|
||||
try {
|
||||
writeFileSync(
|
||||
join(base, "DEFINITION.yaml"),
|
||||
`steps:\n - id: step-1\n produces: []\n verify:\n policy: shell-command\n command: echo hello | cat\n`,
|
||||
);
|
||||
const result = runCustomVerification(base, "step-1");
|
||||
expect(result).toBe("pause");
|
||||
} finally {
|
||||
rmSync(base, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test("pauses on background operator", () => {
|
||||
const base = mkdtempSync(join(tmpdir(), "sf-cv-bg-"));
|
||||
try {
|
||||
writeFileSync(
|
||||
join(base, "DEFINITION.yaml"),
|
||||
`steps:\n - id: step-1\n produces: []\n verify:\n policy: shell-command\n command: echo hello \u0026\n`,
|
||||
);
|
||||
const result = runCustomVerification(base, "step-1");
|
||||
expect(result).toBe("pause");
|
||||
} finally {
|
||||
rmSync(base, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test("allows safe command without pipe or background", () => {
|
||||
const base = mkdtempSync(join(tmpdir(), "sf-cv-safe-"));
|
||||
try {
|
||||
writeFileSync(
|
||||
join(base, "DEFINITION.yaml"),
|
||||
`steps:\n - id: step-1\n produces: []\n verify:\n policy: shell-command\n command: echo hello\n`,
|
||||
);
|
||||
const result = runCustomVerification(base, "step-1");
|
||||
expect(result).toBe("continue");
|
||||
} finally {
|
||||
rmSync(base, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// ─── Bug 8: normalizeFilePath JSDoc filesystem warning ─────────────────────
|
||||
|
||||
describe("normalizeFilePath", () => {
|
||||
test("does NOT make path safe for direct filesystem use", () => {
|
||||
// The function normalizes for comparison only; traversal sequences remain
|
||||
const normalized = normalizeFilePath("../../etc/passwd");
|
||||
expect(normalized).toBe("../../etc/passwd");
|
||||
// A consumer that passed this straight to readFileSync would escape
|
||||
// the intended directory. The JSDoc warns about this explicitly.
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue