Skip to content
Closed
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
26 changes: 1 addition & 25 deletions src/agent/directors/gaasbot/package.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,37 +79,13 @@ describe("gaasbotPackage", () => {
expect(gaasbotPackage.modelRole).toBe("plan");
});

test("optionalSkills is style, philosophy, and native-integration", () => {
test("optionalSkills is philosophy and native-integration", () => {
expect(gaasbotPackage.optionalSkills).toEqual([
"style",
"philosophy",
"native-integration",
]);
});

test("systemPrompt carries the CTO voice strands (contract, not phrasing)", () => {
const p = gaasbotPackage.systemPrompt;
expect(p).toMatch(/squash PR commits/i);
expect(p).toMatch(/hooks must be on/i);
expect(p).toMatch(/loose coupling|composability/i);
expect(p).toMatch(/owns the constraint|owning layer/i);
expect(p).toMatch(/statically-typed|static types/i);
expect(p).toMatch(/Push back when/i);
expect(p).toMatch(/Stay flexible when/i);
expect(p).toMatch(/symptom-chasing/i);
expect(p).toMatch(/parent\/operator/i);
});

test("CTO voice grants no ship/implement/merge-block/spawn powers", () => {
const p = gaasbotPackage.systemPrompt;
expect(p).not.toMatch(
/you (may|can|will|should) (ship|implement|merge|spawn|block)/i,
);
expect(p).not.toMatch(/go ahead and (ship|implement|merge)/i);
expect(p).not.toMatch(/merge-block(ing|er)? (powers|authority)/i);
expect(p).not.toMatch(/act as (a|the) (gate|implementer|orchestrator)/i);
});

test("primaryIntent and outOfLane match risk counsel lane", () => {
expect(gaasbotPackage.primaryIntent).toMatch(/[Rr]isk counsel/i);
expect(gaasbotPackage.description).toMatch(/[Rr]isk counsel/i);
Expand Down
16 changes: 1 addition & 15 deletions src/agent/directors/gaasbot/package.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ import { REVIEW_TOOLS } from "../tool-sets.js";
/**
* Risk counsel worker (CL-7028). Package id/path remains `gaasbot`.
* Strategic risk/sequencing advice — not a hard gate, not implement, not Greybeard/Counsel.
* CTO voice ported from abklabs/agents plugins/gaas/agents/gaasbot.md @ 6e16b6c
* (6e16b6c not resolvable locally; ported from the local HEAD copy instead).
*/
export const gaasbotPackage: DirectorPackage = {
id: "gaasbot",
Expand All @@ -18,7 +16,7 @@ export const gaasbotPackage: DirectorPackage = {
"applying product fixes",
],
description: "Risk counsel — strategic ship/sequencing advice, not a gate",
optionalSkills: ["style", "philosophy", "native-integration"],
optionalSkills: ["philosophy", "native-integration"],
tools: { allow: REVIEW_TOOLS },
spawn: { maySpawn: false },
tier: "leaf",
Expand All @@ -40,17 +38,5 @@ DONE GATE: Stop when the brief's risk/sequencing ask is answered OR Blockers are

OUT OF LANE: shipping product code, architecture gate ownership (Greybeard), eng plan authorship (Counsel), merge-block theater without evidence, becoming Builder/Critic/orchestrator as primary.

CTO VOICE (ported from the GaaS original): direct, conversational, professional without stuffy. Plain language, occasionally colorful. No padding, no hedged softeners — when something is wrong, say so and move on. "user" means the parent/operator. No emojis.

Git discipline: squash PR commits before merging. Git hooks must be on — a commit that bypasses checks means the setup is broken. Run the repo check gate before opening a PR.

Architecture opinions: composability and loose coupling — interfaces over implementations, plugins over monoliths. Move logic to the layer that owns the constraint instead of working around it downstream. Expose hooks and plugin points rather than bespoke forks per use case. Start with the greatest hits — ship the common cases, expand deliberately. Flag experimental work behind flags. Accept old shapes without over-engineering backwards compatibility; duplicate a type rather than couple packages through types.

Tech preferences (pragmatic, maintained, out of the way — new tools only when they solve a real problem): strict static types that catch bugs at compile time; explicit inspectable builds; broad-compatibility open-source licenses; modern runtimes without polyfill or transpilation layers.

Push back when: complexity is proposed for a hypothetical future; type assertions stand in for validation; state lives where it does not belong; layers pile up without owning a constraint. Stay flexible when: the current code is a known hack; an external contributor has a legitimate use case (offer a fitting alternative, do not just close the door); shipped beats perfect — documented temporary workarounds are fine; docs pseudo-code does not need to compile.

How to respond: be direct and specific — what to change and why, with codebase references and a concrete alternative. Reason architecture from the principles above; weigh prioritization against business impact and simplicity. Say "I don't know" over feigning certainty. Call out symptom-chasing and redirect to the owning layer.

Findings: risk and sequencing advice — blockers, ship-with-note, filed-for-later, and the unraised miss.`,
};
46 changes: 3 additions & 43 deletions src/agent/directors/greybeard/package.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,52 +22,12 @@ describe("greybeardPackage", () => {
expect(p).not.toMatch(/architecture director/i);
});

test("systemPrompt frames value as analysis via Corbits read tools", () => {
test("systemPrompt teaches judgment for architecture approach", () => {
const p = greybeardPackage.systemPrompt;
expect(p).toMatch(/value is analysis/i);
expect(p).toContain("read_file");
expect(p).toContain("grep");
expect(p).toContain("ask_director");
});

test("systemPrompt carries an ordered review checklist", () => {
const p = greybeardPackage.systemPrompt;
expect(p).toMatch(/Review checklist/);
expect(p).toMatch(/architectural claim/);
expect(p).toContain("Judge the approach");
expect(p).toMatch(/constraint ownership|owns constraints/i);
expect(p).toMatch(/anti-patterns/);
expect(p).toMatch(/Rank risks/);
const checklistIdx = p.search(/Review checklist/);
expect(checklistIdx).toBeGreaterThan(-1);
const checklist = p.slice(checklistIdx);
const claimIdx = checklist.search(/architectural claim/);
const ownershipIdx = checklist.search(/constraint ownership|owns constraints/i);
const holesIdx = checklist.search(/anti-patterns/);
const risksIdx = checklist.search(/Rank risks/);
const verdictIdx = checklist.search(/hold \/ revise \/ block/);
expect(claimIdx).toBeGreaterThan(-1);
expect(ownershipIdx).toBeGreaterThan(claimIdx);
expect(holesIdx).toBeGreaterThan(ownershipIdx);
expect(risksIdx).toBeGreaterThan(holesIdx);
expect(verdictIdx).toBeGreaterThan(risksIdx);
});

test("systemPrompt ends the checklist with the hold/revise/block verdict triad", () => {
const p = greybeardPackage.systemPrompt;
expect(p).toMatch(/hold \/ revise \/ block/);
expect(p).toMatch(/hold \/ revise \/ block|verdict/i);
expect(p).toMatch(/backward-compatibility|backward compatibility/i);
const risksIdx = p.search(/Rank risks/);
const triadIdx = p.search(/hold \/ revise \/ block/);
expect(risksIdx).toBeGreaterThan(-1);
expect(triadIdx).toBeGreaterThan(risksIdx);
});

test("systemPrompt has no self-spawn language", () => {
const p = greybeardPackage.systemPrompt;
expect(p).not.toMatch(/spawn.*greybeard/i);
expect(p).not.toContain('agent="greybeard"');
expect(p).not.toMatch(/spawn yourself/i);
expect(p).not.toMatch(/spawn a (greybeard|reviewer)/i);
});

test("systemPrompt allows limited spawn without fake caps or scheduler language", () => {
Expand Down
14 changes: 3 additions & 11 deletions src/agent/directors/greybeard/package.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,7 @@ import { ORCHESTRATOR_TOOLS } from "../tool-sets.js";

/**
* Greybeard nested orchestrator (CL-7019).
* Review checklist ported from the GaaS greybeard original (CL-7662) — the
* GaaS source was unavailable locally, so this is a Corbits-idiom restoration
* rather than a 1:1 copy. Self-read deviation: the GaaS delegate-for-review
* shape becomes read_file/grep/ask_director first, spawn only on a concrete
* unknown. Architecture judgment with limited spawn — never ships product code.
* Architecture judgment with limited spawn — never ships product code.
*/
export const greybeardPackage: DirectorPackage = {
id: "greybeard",
Expand All @@ -30,16 +26,12 @@ You are Greybeard — not a second Skywalker, not Critic (code defects with evid

Follow style and philosophy conventions (baked into this prompt) when reviewing plans or approaches — skills are active constraints, not background docs.

Your value is analysis, not delegation: reach the judgment yourself with
targeted reads (read_file, grep) and pointed questions (ask_director)
before considering a spawn.

Review checklist — work the list in order:
Judge the approach:
1. Name the architectural claim under review (boundary, ownership, invariant, or BC surface).
2. Decide whether the proposed approach owns constraints at the right layer — or only chases symptoms.
3. Call out holes, anti-patterns, missing invariants, product/architecture/implementation misalignment, and duplication that should be refactor or API expansion instead.
4. Rank risks for long-term maintainability and backward compatibility.
5. Report a clear verdict: hold / revise / block — with the why, not checklist theater.
5. Report a clear verdict: hold / revise / block — with the why, not a checklist theater.

Spawn only when a concrete unknown blocks that judgment. Package spawn rules allow intern (mechanical shell), explorer (map/read), and critic (code evidence). When spawning critic, pass non-empty success_criteria (runtime fail-closes without it). intern and explorer remain optional. Prefer doing the review yourself with mounted read/search tools. Do not invent numeric spawn caps or act as a scheduler — width follows the unknown, not a soft ladder. Spawn then idle; reports arrive as mailbox mail — do not poll.

Expand Down
18 changes: 0 additions & 18 deletions src/plugins/agent-plugins.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,22 +176,4 @@ describe("resolveAgentPluginProfiles", () => {
),
).toBe(true);
});

test("warns once when a prompt file is missing and still loads the profile", async () => {
const { mod, config } = agentModule("p1", [
{ id: "scout", systemPromptPath: "prompts/does-not-exist.md" },
]);
mod.dir = "/tmp/wt-cl-6724-missing-prompt-dir";
const warnings: string[] = [];
const profiles = await resolveAgentPluginProfiles([mod], config, (msg) =>
warnings.push(msg),
);
expect(profiles.length).toBe(1);
expect(defined(profiles[0]).systemPromptRole).toBeUndefined();
expect(warnings.length).toBe(1);
expect(warnings[0]).toContain('"p1"');
expect(warnings[0]).toContain('"scout"');
expect(warnings[0]).toContain("prompts/does-not-exist.md");
expect(defined(warnings[0])).toMatch(/unreadable|missing/i);
});
});
16 changes: 6 additions & 10 deletions src/plugins/agent-plugins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,18 +90,14 @@ export async function resolveAgentPluginProfiles(
profile.systemPromptRole === undefined &&
mod.dir !== undefined
) {
const promptPath = join(mod.dir, profile.systemPromptPath);
try {
const promptRaw = await readFile(promptPath, "utf8");
profile.systemPromptRole = promptRaw.trim();
} catch (err) {
const reason =
(err instanceof Error ? err.message : String(err))
.split("\n")[0]
?.trim() || "unknown error";
onWarning(
`plugin "${mod.manifest.id}" agent "${profile.id}" systemPromptPath "${profile.systemPromptPath}" unreadable (${promptPath}): ${reason}`,
const promptRaw = await readFile(
join(mod.dir, profile.systemPromptPath),
"utf8",
);
profile.systemPromptRole = promptRaw.trim();
} catch {
// Missing prompt file is non-fatal — the profile loads without a role.
}
}
// Provenance for search_agents: Claude marketplace installs stamp
Expand Down
Loading