diff --git a/agents/shakespeare/README.md b/agents/shakespeare/README.md new file mode 100644 index 000000000..e8596c7b8 --- /dev/null +++ b/agents/shakespeare/README.md @@ -0,0 +1,35 @@ +# @corbits/code-agent-shakespeare + +## What this is + +Ready `@corbits/code-agent-shakespeare` agent + independently importable components: +`agent`, `defineAgent`, `director`, `tools`, `systemPrompt`, `config`. + +## Consume (runLocal) + +```ts +import { agent } from "@corbits/code-agent-shakespeare"; +const run = runLocal(defining(agent), env); +``` + +## Consume (deploy runWorkflow / hub / sidecar) + +Hand the same `AgentDefinition` to a workflow — the package never exports a +workflow itself. + +## Remix components + +```ts +import { director, tools, systemPrompt, config } from "@corbits/code-agent-shakespeare"; +defineAgent({ director, ... }); // or swap tools / systemPrompt / config +``` + +Each of `director`, `tools`, `systemPrompt`, and `config` is a named, +independently importable part. + +## Single authority + +`prompt.ts` `systemPrompt.build()` is byte-stable (the raw card, no formatting). +`toolset.ts` `tools` literal equals the `DOCS_TOOLS` surface constant in +`src/agent/directors/tool-sets.ts` (no `run_shell`); drift is enforced by the +in-tree drift-guard `src/agent/directors/shakespeare/package.test.ts`. diff --git a/agents/shakespeare/package.json b/agents/shakespeare/package.json new file mode 100644 index 000000000..299e58e48 --- /dev/null +++ b/agents/shakespeare/package.json @@ -0,0 +1,13 @@ +{ + "name": "@corbits/code-agent-shakespeare", + "version": "0.1.0", + "private": true, + "type": "module", + "license": "SEE LICENSE IN LICENSE.md", + "exports": { + ".": { + "types": "./src/index.ts", + "default": "./src/index.ts" + } + } +} diff --git a/agents/shakespeare/src/agent.ts b/agents/shakespeare/src/agent.ts new file mode 100644 index 000000000..6cfe96ffd --- /dev/null +++ b/agents/shakespeare/src/agent.ts @@ -0,0 +1,17 @@ +import { + defineAgent as coreDefineAgent, + type AgentDefinition, +} from "@intx/agent"; +import { systemPrompt } from "./prompt.js"; + +export const agent: AgentDefinition = defineAgent(); + +export function defineAgent(): AgentDefinition { + return coreDefineAgent({ + id: "shakespeare", + systemPrompt: systemPrompt.build(), + tools: [], + capabilities: [], + inference: { sources: [] }, + }); +} diff --git a/agents/shakespeare/src/config.ts b/agents/shakespeare/src/config.ts new file mode 100644 index 000000000..385c48cb8 --- /dev/null +++ b/agents/shakespeare/src/config.ts @@ -0,0 +1,9 @@ +export const config = { + spawn: { maySpawn: false }, + tier: "leaf", + modelRole: "docs", +} as const satisfies { + spawn: { maySpawn: boolean }; + tier: string; + modelRole: string; +}; diff --git a/agents/shakespeare/src/director.ts b/agents/shakespeare/src/director.ts new file mode 100644 index 000000000..f65818174 --- /dev/null +++ b/agents/shakespeare/src/director.ts @@ -0,0 +1,20 @@ +import { tools } from "./toolset.js"; +import { systemPrompt } from "./prompt.js"; +import { config } from "./config.js"; + +export const director = { + id: "shakespeare", + primaryIntent: "Maintain product, architecture, and implementation docs", + outOfLane: [ + "shipping product features", + "pure code review", + "orchestration / fleet control", + "acting as reviewer or implementer", + ], + description: "Docs maintenance — PRODUCT / ARCHITECTURE / IMPLEMENTATION", + systemPrompt: systemPrompt.build(), + tools: { allow: [...tools] }, + spawn: config.spawn, + tier: config.tier, + modelRole: config.modelRole, +} as const; diff --git a/agents/shakespeare/src/index.ts b/agents/shakespeare/src/index.ts new file mode 100644 index 000000000..c2c15f6c6 --- /dev/null +++ b/agents/shakespeare/src/index.ts @@ -0,0 +1,5 @@ +export { agent, defineAgent } from "./agent.js"; +export { director } from "./director.js"; +export { tools } from "./toolset.js"; +export { systemPrompt } from "./prompt.js"; +export { config } from "./config.js"; diff --git a/src/agent/directors/shakespeare/package.ts b/agents/shakespeare/src/prompt.ts similarity index 79% rename from src/agent/directors/shakespeare/package.ts rename to agents/shakespeare/src/prompt.ts index 5bf21267a..572253471 100644 --- a/src/agent/directors/shakespeare/package.ts +++ b/agents/shakespeare/src/prompt.ts @@ -1,22 +1,6 @@ -import type { DirectorPackage } from "../types.js"; -import { DOCS_TOOLS } from "../tool-sets.js"; - -/** - * Shakespeare worker (CL-7029). - * Docs maintenance — PRODUCT / ARCHITECTURE / IMPLEMENTATION only; docs core baked in. - * Package id/path stays `shakespeare` (global rename is out of scope). - */ -export const shakespearePackage: DirectorPackage = { - id: "shakespeare", - primaryIntent: "Maintain product, architecture, and implementation docs", - outOfLane: [ - "shipping product features", - "pure code review", - "orchestration / fleet control", - "acting as reviewer or implementer", - ], - description: "Docs maintenance — PRODUCT / ARCHITECTURE / IMPLEMENTATION", - systemPrompt: `You are ShakespeareDirector (Shakespeare), a specialist in Corbits Code. +// The app formats the identity header via formatDirectorSystemPrompt. + +const CARD = `You are ShakespeareDirector (Shakespeare), a specialist in Corbits Code. PRIMARY INTENT: maintain PRODUCT.md, ARCHITECTURE.md, and IMPLEMENTATION.md. Route input to the correct doc, detect gaps, surface questions for completeness, and keep cross-doc consistency. You are the docs lane only — not Coder, not Reviewer, not an orchestrator. @@ -72,9 +56,9 @@ Confirm what changed and where. Summarize consistency/gap follow-ups. Map each s DONE GATE: Stop when every success_criteria item from the brief is met OR explicitly blocked under Blockers. Do not invent architecture campaigns or expand the brief after criteria are satisfied. If the ask needs product code, review, or brand/DESIGN.md, report Blockers — do not become Coder, Reviewer, or Designer. -OUT OF LANE: shipping product features, pure code review, orchestration, treating docs as optional, DESIGN.md ownership, becoming Coder or Reviewer as primary.`, - tools: { allow: DOCS_TOOLS }, - spawn: { maySpawn: false }, - tier: "leaf", - modelRole: "docs", +OUT OF LANE: shipping product features, pure code review, orchestration, treating docs as optional, DESIGN.md ownership, becoming Coder or Reviewer as primary.`; + +export const systemPrompt = { + theme: "shakespeare", + build: (): string => CARD, }; diff --git a/agents/shakespeare/src/toolset.ts b/agents/shakespeare/src/toolset.ts new file mode 100644 index 000000000..f221dbaf9 --- /dev/null +++ b/agents/shakespeare/src/toolset.ts @@ -0,0 +1,14 @@ +export const tools: readonly string[] = [ + "read_file", + "grep", + "search_files", + "list_dir", + "lsp", + "web_fetch", + "web_search", + "skill_search", + "use_skill", + "write_file", + "edit_file", + "delete_file", +] as const; diff --git a/agents/shakespeare/tsconfig.json b/agents/shakespeare/tsconfig.json new file mode 100644 index 000000000..0d7100cbc --- /dev/null +++ b/agents/shakespeare/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "noEmit": true + }, + "include": ["src/**/*.ts"] +} diff --git a/bun.lock b/bun.lock index 95f06b9f9..71a4669c4 100644 --- a/bun.lock +++ b/bun.lock @@ -52,6 +52,9 @@ }, "agents/artist": { "name": "@corbits/code-agent-artist", + }, + "agents/shakespeare": { + "name": "@corbits/code-agent-shakespeare", "version": "0.1.0", }, "packages/first-class-providers": { @@ -246,6 +249,7 @@ "@better-fetch/fetch": ["@better-fetch/fetch@1.3.2", "", {}, "sha512-Gs7n99b5tqUC6cQAPbV0uED3IraHB6xQbHLQ/C3l7ZFafHScOx9pQ+DYmP5blbLFShVWLqxNUlI9wi4xU/X+ow=="], "@corbits/code-agent-artist": ["@corbits/code-agent-artist@workspace:agents/artist"], + "@corbits/code-agent-shakespeare": ["@corbits/code-agent-shakespeare@workspace:agents/shakespeare"], "@corbits/codex-provider": ["@corbits/codex-provider@0.1.1", "", { "dependencies": { "arktype": "^2.2.3" }, "peerDependencies": { "@corbits/oauth-core": "^0.2.0", "@corbits/openai-responses": "^0.2.0", "@intx/inference": "^0.4.0", "@intx/types": "^0.4.0" } }, "sha512-MiGA5djQNtRpHqLvJAGdFgHEbUl808AbveMPwt7krLjOH0i20DyElpitHPZF3ltCXkFMhcKIoyTAEpRDqeB/Rw=="], diff --git a/scripts/dead-export-allowlist.txt b/scripts/dead-export-allowlist.txt index 2769cf3a3..bf9856198 100644 --- a/scripts/dead-export-allowlist.txt +++ b/scripts/dead-export-allowlist.txt @@ -61,3 +61,7 @@ agents/artist/src/index.ts: agent agents/artist/src/index.ts: defineAgent agents/artist/src/config.ts: satisfies agents/artist/src/config.ts: boolean +agents/shakespeare/src/index.ts: agent +agents/shakespeare/src/index.ts: defineAgent +agents/shakespeare/src/config.ts: satisfies +agents/shakespeare/src/config.ts: boolean diff --git a/src/agent/directors/registry.ts b/src/agent/directors/registry.ts index 18231564c..2c5ed6b65 100644 --- a/src/agent/directors/registry.ts +++ b/src/agent/directors/registry.ts @@ -1,3 +1,4 @@ +import { director as shakespeareDirector } from "@corbits/code-agent-shakespeare"; import type { AgentProfile, CapabilityFilter } from "../profile-types.js"; import { director as artistDirector } from "@corbits/code-agent-artist"; import { coderPackage } from "./coder/package.js"; @@ -8,7 +9,6 @@ import { plannerPackage } from "./planner/package.js"; import { proberPackage } from "./prober/package.js"; import { qaLeadPackage } from "./qa-lead/package.js"; import { reviewerPackage } from "./reviewer/package.js"; -import { shakespearePackage } from "./shakespeare/package.js"; import { wardenPackage } from "./warden/package.js"; import { formatDirectorSystemPrompt } from "./identity.js"; import { @@ -45,7 +45,7 @@ export const DIRECTOR_REGISTRY: Readonly> = designer: designerPackage, artist: artistDirector, warden: wardenPackage, - shakespeare: shakespearePackage, + shakespeare: shakespeareDirector, prober: proberPackage, "qa-lead": qaLeadPackage, }; diff --git a/src/agent/directors/shakespeare/package.test.ts b/src/agent/directors/shakespeare/package.test.ts new file mode 100644 index 000000000..f1f4e8497 --- /dev/null +++ b/src/agent/directors/shakespeare/package.test.ts @@ -0,0 +1,48 @@ +import { describe, expect, test } from "bun:test"; + +import { + director, + tools, + config, + systemPrompt, +} from "@corbits/code-agent-shakespeare"; +import { DOCS_TOOLS } from "../tool-sets.js"; +import { DIRECTOR_REGISTRY } from "../registry.js"; +import type { DirectorPackage } from "../types.js"; + +describe("shakespearePackage", () => { + test("workspace package satisfies the director contract and feeds the registry", () => { + const asDirector: DirectorPackage = director; + expect(asDirector.id).toBe("shakespeare"); + expect(DIRECTOR_REGISTRY.shakespeare).toBe(director); + expect([...director.tools.allow]).toEqual([...DOCS_TOOLS]); + expect(new Set(director.tools.allow).size).toBe( + director.tools.allow.length, + ); + }); + + test("spawn/tier/modelRole match config", () => { + expect(director.spawn).toEqual(config.spawn); + expect(director.tier).toBe(config.tier); + expect(director.modelRole).toBe(config.modelRole); + expect(director.spawn.maySpawn).toBe(false); + }); + + test("prompt authority is byte-stable", () => { + expect(director.systemPrompt).toBe(systemPrompt.build()); + expect(systemPrompt.build()).toBe(systemPrompt.build()); + }); + + test("docs surface omits run_shell and stays leaf-scoped", () => { + expect([...tools]).toEqual([...DOCS_TOOLS]); + expect(tools).not.toContain("run_shell"); + expect(tools).not.toContain("spawn_agent"); + expect(tools).not.toContain("ask_operator"); + expect(tools).not.toContain("wait_agents"); + expect(tools).not.toContain("search_agents"); + }); + + test("tools allowlist has no duplicates", () => { + expect(new Set([...tools]).size).toBe(tools.length); + }); +}); diff --git a/tsconfig.json b/tsconfig.json index 19b14b736..df0b1cca4 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,6 +6,7 @@ "baseUrl": ".", "paths": { "@corbits/code-agent-artist": ["./agents/artist/src/index.ts"], + "@corbits/code-agent-shakespeare": ["./agents/shakespeare/src/index.ts"], "@corbits/provider-opencode-go": ["./packages/opencode-go/src/index.ts"], "@corbits/first-class-providers": [ "./packages/first-class-providers/src/index.ts"