agentic-toolkit/src/modules/failure-handler.test.ts
Khalim Conn-Kowlessar 1d8a77b29b feat: scaffold agentic-toolkit (runner + skills + setup)
Initial implementation of Domna's agentic toolkit per PRD #1:

- Runner CLI (src/cli.ts) wrapping sandcastle.run() with Docker provider
- Pure modules: PhaseScheduler, PromptBuilder, FailureHandler with tests
- Project Status v2 GraphQL client + parsers with tests
- BranchManager (git/gh wrapper) and LoopOrchestrator (per-tick algorithm)
- Variant-aware: per-ticket (one PR per issue, phase-gated, exit between phases)
  vs single-pr (one PR for the whole DAG, halt on failure)
- /to-project skill that creates a repo-level project, configures the Status
  schema the runner expects, and sets initial issue statuses
- setup.sh that installs Matt Pocock skills + Domna skills via npx skills

Out of scope at v1: remote runners, Slack notifications, stacked PRs,
cross-repo projects, SHA-pinning of upstream skills (tracks HEAD until the
skills CLI supports repo#sha).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-03 12:40:26 +01:00

60 lines
1.7 KiB
TypeScript

import { describe, expect, it } from "vitest";
import type { FailureKind, Variant } from "../types.js";
import { decide } from "./failure-handler.js";
const FAILURES: FailureKind[] = [
"agent-error",
"tests-failed",
"build-failed",
"sandbox-timeout",
"unknown",
];
const VARIANTS: Variant[] = ["per-ticket", "single-pr"];
describe("decide", () => {
describe("first failure", () => {
for (const variant of VARIANTS) {
for (const failure of FAILURES) {
it(`retries on first failure (variant=${variant}, kind=${failure})`, () => {
const action = decide(failure, { variant, retryCount: 0 });
expect(action.kind).toBe("retry");
});
}
}
});
describe("after retry", () => {
for (const failure of FAILURES) {
it(`per-ticket variant skips after second failure (kind=${failure})`, () => {
const action = decide(failure, {
variant: "per-ticket",
retryCount: 1,
});
expect(action.kind).toBe("skip");
if (action.kind === "skip") {
expect(action.reason).toContain(failure);
}
});
it(`single-pr variant halts after second failure (kind=${failure})`, () => {
const action = decide(failure, {
variant: "single-pr",
retryCount: 1,
});
expect(action.kind).toBe("halt");
if (action.kind === "halt") {
expect(action.reason).toContain(failure);
}
});
}
});
it("halts/skips on third+ failure too (defensive)", () => {
expect(
decide("unknown", { variant: "per-ticket", retryCount: 2 }).kind,
).toBe("skip");
expect(decide("unknown", { variant: "single-pr", retryCount: 5 }).kind).toBe(
"halt",
);
});
});