import type { AgentFn, Role, ThreadContext, WorkflowMessage } from "@uncaged/nerve-core"; import type { LlmExtractorConfig } from "@uncaged/nerve-workflow-utils"; import { createRole } from "@uncaged/nerve-workflow-utils"; import { z } from "zod"; import { resolveWorkdir } from "../lib/workdir.js"; import type { AnswererMeta } from "./answerer.js"; import type { QuestionerMeta } from "./questioner.js"; export const explorerMetaSchema = z.object({ patches: z.array( z.object({ card: z.string(), section: z.string(), }), ), new_cards: z.array(z.string()), }); export type ExplorerMeta = z.infer; export type CreateExplorerRoleDeps = { extract: LlmExtractorConfig; }; function lastMeta(messages: WorkflowMessage[], role: string): M | undefined { for (let i = messages.length - 1; i >= 0; i--) { if (messages[i].role === role) { return messages[i].meta as M; } } return undefined; } export function explorerPrompt(ctx: ThreadContext): string { const messages = ctx.steps as unknown as WorkflowMessage[]; const threadId = ctx.start.meta.threadId; const qm = lastMeta(messages, "questioner"); const am = lastMeta(messages, "answerer"); const cwd = resolveWorkdir(ctx.start); const unanswered = am?.results.filter((r) => !r.found).map((r) => r.id) ?? []; return `You are the **explorer** in a knowledge-extraction workflow. ## Context - Thread: \`nerve thread ${threadId}\` - Working directory (repo root for paths): ${cwd} - Current knowledge card (questioner): ${qm?.card ?? "(unknown)"} ## Unanswered question ids ${JSON.stringify(unanswered)} Use the prior answerer results in the thread to map ids to full question text when you read messages above. ## Task For each unanswered question, **read the codebase** as needed, then either: - Add a new markdown file under \`.knowledge/\`, or - Patch an existing card (prefer updating the card listed above when appropriate). After any write or patch to \`.knowledge\`, run: \`\`\`bash nerve knowledge sync \`\`\` from this repo root (${cwd}), and fix failures until sync succeeds. ## Output meta Report \`patches\` as { card, section } entries for cards you edited (section is a short heading or path hint). Report \`new_cards\` as repo-relative paths for brand-new files you created (e.g. \`.knowledge/new-topic.md\`). Do not claim work you did not perform.`; } export function createExplorerRole( adapter: AgentFn, { extract }: CreateExplorerRoleDeps, ): Role { return createRole( adapter, async (ctx: ThreadContext) => explorerPrompt(ctx), explorerMetaSchema, extract, ); }