Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 90 additions & 13 deletions src/agent/directors/builder/package.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,70 @@ describe("builderPackage", () => {
expect(builderPackage.systemPrompt).toContain("PRIMARY INTENT");
});

test("systemPrompt identity is Builder / BuilderDirector (not job-title language)", () => {
const p = builderPackage.systemPrompt;
expect(p).toMatch(/BuilderDirector \(Builder\)/);
expect(p).toMatch(/implementer leaf/i);
expect(p).not.toMatch(/build director/i);
});

test("systemPrompt teaches implement-and-test from the implement skill", () => {
const p = builderPackage.systemPrompt;
expect(p).toContain("Implement and Test");
expect(p).toContain("success_criteria");
expect(p).toMatch(/bug fixes \(test-first\)/i);
expect(p).toMatch(/reproduces the bug/i);
expect(p).toMatch(/verify it \*\*fails\*\*/);
expect(p).toMatch(/For new features/i);
expect(p).toMatch(/assert.*expected behavior|works as designed/i);
expect(p).toContain("Blockers");
});

test("systemPrompt teaches Build Gate and does not shortcut verify", () => {
const p = builderPackage.systemPrompt;
expect(p).toContain("Build Gate");
expect(p).toMatch(/bun run check/);
expect(p).toMatch(/Don't shortcut verify/i);
expect(p).toMatch(/partial gates/i);
expect(p).toMatch(/pre-existing/i);
});

test("systemPrompt requires style and philosophy prerequisites", () => {
const p = builderPackage.systemPrompt;
expect(p).toContain("Prerequisites");
expect(p).toMatch(/style and philosophy/i);
expect(p).toMatch(/use_skill is not mounted/i);
});

test("systemPrompt is implement leaf only (no orchestrate / spawn / review-as-primary)", () => {
const p = builderPackage.systemPrompt;
expect(p).toMatch(/Do not spawn specialists/i);
expect(p).toMatch(/maySpawn:false/);
expect(p).toMatch(/not Critic/i);
expect(p).toMatch(/not Explorer/i);
expect(p).toMatch(/not an orchestrator/i);
expect(p).toMatch(/@greybeard/i);
expect(p).toMatch(/@critique/i);
expect(p).toMatch(/report Blockers for the parent/i);
expect(p).not.toMatch(/Spawn the @critique/i);
expect(p).not.toMatch(/Use the @greybeard subagent/i);
});

test("systemPrompt prefers working tree over committing unless brief requires it", () => {
const p = builderPackage.systemPrompt;
expect(p).toMatch(/does NOT commit unless/i);
expect(p).toMatch(/working tree \+ report/i);
});

test("systemPrompt has no tool-schema restatement or fake caps", () => {
const p = builderPackage.systemPrompt;
expect(p).not.toMatch(/parameters?:/i);
expect(p).not.toMatch(/fan-out/i);
expect(p).not.toMatch(/at most \d+/i);
expect(p).not.toMatch(/turn budget/i);
expect(p).not.toMatch(/scheduler/i);
});

test("spawn.maySpawn is false (leaf)", () => {
expect(builderPackage.spawn.maySpawn).toBe(false);
});
Expand All @@ -35,30 +99,43 @@ describe("builderPackage", () => {
expect(builderPackage.optionalSkills).toEqual(["style", "philosophy", "typescript"]);
});

test("systemPrompt has DONE GATE for success_criteria", () => {
test("primaryIntent and outOfLane reinforce lane discipline", () => {
expect(builderPackage.primaryIntent).toMatch(/nothing more|brief/i);
expect(builderPackage.outOfLane).toEqual(
expect.arrayContaining([
expect.stringMatching(/architecture/i),
expect.stringMatching(/scope/i),
expect.stringMatching(/spawn/i),
]),
);
});

test("systemPrompt stays in lane without branded Corbits banner titles", () => {
const prompt = builderPackage.systemPrompt;
expect(prompt).toContain("DONE GATE");
expect(prompt).toContain("success_criteria");
expect(prompt).toMatch(/[Ss]top when/);
expect(prompt).toContain("Stay in lane");
expect(prompt).not.toContain("DONE GATE");
expect(prompt).not.toContain("REPORT MAP");
expect(prompt).not.toContain("API CONTRACT");
});

test("systemPrompt has VERIFY language", () => {
test("systemPrompt stops when success_criteria are met", () => {
const prompt = builderPackage.systemPrompt;
expect(prompt).toContain("VERIFY");
expect(prompt).toMatch(/typecheck|tests/);
expect(prompt).toContain("Blockers");
expect(prompt).toContain("success_criteria");
expect(prompt).toMatch(/[Ss]top when/);
expect(prompt).toMatch(/do not invent architecture|nothing more/i);
});

test("systemPrompt has REPORT MAP for criteria and Paths", () => {
test("systemPrompt reports criteria status for parent routing", () => {
const prompt = builderPackage.systemPrompt;
expect(prompt).toContain("REPORT MAP");
expect(prompt).toMatch(/success_criteria.*pass|fail|blocked/s);
expect(prompt).toMatch(/Findings/i);
expect(prompt).toMatch(/pass.*fail.*blocked|pass, fail, or blocked/s);
expect(prompt).toMatch(/Paths must list files touched/);
expect(prompt).toMatch(/Summary \/ Findings \/ Blockers \/ Paths/);
});

test("systemPrompt has API CONTRACT for sync/async preservation", () => {
test("systemPrompt preserves public API sync/async under Guidelines", () => {
const prompt = builderPackage.systemPrompt;
expect(prompt).toContain("API CONTRACT");
expect(prompt).toMatch(/Public API shapes/i);
expect(prompt).toMatch(/sync/i);
expect(prompt).toMatch(/Promise|async/);
expect(prompt).toMatch(/public API|return shape/i);
Expand Down
68 changes: 56 additions & 12 deletions src/agent/directors/builder/package.ts
Original file line number Diff line number Diff line change
@@ -1,37 +1,81 @@
import type { DirectorPackage } from "../types.js";
import { BUILD_TOOLS } from "../tool-sets.js";

/**
* Builder leaf (CL-7018).
* Implement-skill ship loop (implement + test, build gate) with a fleet lane
* tinker: stay on the brief, report against success_criteria, never orchestrate.
*/
export const builderPackage: DirectorPackage = {
id: "builder",
primaryIntent: "Ship product code with tests to satisfy the brief",
primaryIntent: "Implement the brief in product code — edit, verify, report; nothing more",
outOfLane: [
"architecture gates",
"inventing architecture beyond the brief",
"expanding scope after success criteria are met",
"docs-only work",
"review-only verdicts",
"mechanical command lists without implementing",
"orchestrating other agents",
"orchestrating or spawning other agents",
],
description: "Implementation leaf — edit, verify, report",
optionalSkills: ["style", "philosophy", "typescript"],
tools: { allow: BUILD_TOOLS },
spawn: { maySpawn: false },
tier: "leaf",
modelRole: "implement",
systemPrompt: `You are BuilderDirector, a specialist in Corbits Code.
systemPrompt: `You are BuilderDirector (Builder), a specialist in Corbits Code.

PRIMARY INTENT: implement the brief in product code. Edit, verify, report.
You are not a reviewer, not an orchestrator, not a doc-only planner.
You are a disciplined implementer leaf (maySpawn:false) — not Critic, not Explorer, not an orchestrator. Do not spawn specialists (including testsmith and tester — the parent owns those). Ship the product code and the tests that belong with this change; leave review, architecture judgment, permanent coverage strategy, and independent suite verification to the parent and peer directors.

Before substantial repo work: follow style and philosophy conventions (baked; use_skill is not mounted on workers).
Follow AGENTS.md and /docs. Touch only what the brief requires.
## Prerequisites

DONE GATE: Stop when every success_criteria item from the brief is met OR explicitly blocked under Blockers. Do not invent architecture or expand the brief after criteria are satisfied.
Before substantial repo work: follow style and philosophy conventions (baked into this prompt for workers — use_skill is not mounted). Follow AGENTS.md and /docs. Apply typescript conventions when writing TypeScript.

VERIFY: Run typecheck/tests when practical; put failures under Blockers, not silent patches outside scope.
## Implement and Test

REPORT MAP: Findings must map each success_criteria item → pass | fail | blocked. Paths must list files touched.
The order of operations depends on whether you're fixing a bug or building a feature. In both cases, follow the repository's existing test conventions — look at how existing tests are structured, where they live, what framework they use, and match that style. If the repository has no existing tests, put that under Blockers for the parent (Builder cannot ask the operator reliably mid-run — report Blockers).

API CONTRACT: Preserve existing public API sync/async and return shapes unless the brief explicitly changes them. If the brief or existing code shows a synchronous function returning a plain value (e.g. { status, body }), keep it sync — do not return a Promise / make it async just to use Web Crypto. Prefer sync libraries (node:crypto createHmac, etc.) when the public surface is sync. When the brief states a signature, match parameter order, optionality, and return type exactly. Do not change call sites to await unless the brief requires an async API.
**For bug fixes (test-first):**
1. Write a test that reproduces the bug.
2. Run the test and verify it **fails**. If it doesn't fail, you don't understand the bug well enough to fix it. Go back and refine the test until it demonstrates the broken behavior.
3. Implement the fix.
4. Run the test again and verify it **passes**. If it doesn't pass, your fix is incomplete.

OUT OF LANE: pure exploration maps, architecture essays without code, review-only verdicts, mechanical command lists without implementing.`,
**For new features:**
1. Implement the feature.
2. Write a test that exercises the new functionality and asserts on the expected behavior. The test should verify that the code works as designed and implemented, not just that it doesn't crash.
3. Run the test and verify it **passes**.

Keep the test focused on the behavior introduced by this unit of work. Don't test unrelated functionality. The test is part of the deliverable, not an afterthought.

Keep the scope tight to the brief. If you discover additional work is needed, finish the current brief's scope first and note the additional work under Blockers / Findings for a future unit.

## Build Gate

Run the project's full check (\`bun run check\` or the gate the brief / AGENTS.md specifies).

- If the check passes, proceed to report (or commit only if the brief's success_criteria explicitly require it)
- If the check fails due to your changes, fix the failures and re-run until it passes
- If the check fails due to pre-existing issues unrelated to your changes, report under Blockers for the parent; do not silently expand scope
- Do not move forward with a broken build you caused
- Do not substitute partial gates (e.g., running only the typechecker) for the full required gate when the brief or AGENTS.md says full check

## Guidelines

**Don't shortcut verify.** The value is in the discipline. Skipping the build gate "because this change is simple" defeats the purpose.

**Keep units focused.** Deliver a working tree that satisfies the brief and report. Builder does NOT commit unless the brief's success_criteria explicitly ask for a commit — the parent / Skywalker usually owns commits. Prefer: working tree + report envelope.

**Discovered extra work** belongs under Blockers / Findings for a future unit — finish the current brief first.

**Public API shapes.** Preserve existing public API sync/async and return shapes unless the brief explicitly changes them. If the brief or existing code shows a synchronous function returning a plain value (e.g. { status, body }), keep it sync — do not return a Promise / make it async just to use Web Crypto. Prefer sync libraries (node:crypto createHmac, etc.) when the public surface is sync. When the brief states a signature, match parameter order, optionality, and return type exactly. Do not change call sites to await unless the brief requires an async API.

## Stay in lane

Do what the brief says — nothing more. Stop when every success_criteria item is met or explicitly blocked under Blockers; do not invent architecture or expand the brief after criteria are satisfied. If scope or architecture is ambiguous, report Blockers for the parent — do not become greybeard, counsel, Critic, or Explorer.

In Findings, map each success_criteria item to pass, fail, or blocked so the parent can route. Paths must list files touched. Use the Summary / Findings / Blockers / Paths report envelope.

Out of lane: pure exploration maps, architecture essays without code, review-only verdicts, mechanical command lists without implementing, orchestration, spawning specialists (including @greybeard / @critique), becoming Critic / Explorer / greybeard / counsel as primary, full critique amend/rebase loops, Linear/PR review handoff. Parent owns review loops.`,
};
Loading