@orquentra/codebuddy 2.8.0 → 2.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/CLAUDE_CODEX.md +33 -0
  2. package/README.md +27 -3
  3. package/dist/{adapters-S4HHTWJW.js → adapters-OJRSVFV3.js} +2 -2
  4. package/dist/{chunk-GKNYMNFM.js → chunk-4DKYKDHI.js} +6 -1
  5. package/dist/chunk-4DKYKDHI.js.map +1 -0
  6. package/dist/{chunk-6KGYAFG7.js → chunk-7QMHIJBA.js} +30 -3
  7. package/dist/chunk-7QMHIJBA.js.map +1 -0
  8. package/dist/{chunk-6NZEZJYD.js → chunk-ESC4Y3L7.js} +3 -5
  9. package/dist/{chunk-6NZEZJYD.js.map → chunk-ESC4Y3L7.js.map} +1 -1
  10. package/dist/chunk-GQKWUHMA.js +20 -0
  11. package/dist/chunk-GQKWUHMA.js.map +1 -0
  12. package/dist/{chunk-J4T6HHI4.js → chunk-L6BRYZ6G.js} +537 -316
  13. package/dist/chunk-L6BRYZ6G.js.map +1 -0
  14. package/dist/{chunk-UK6XTS2Y.js → chunk-LRQNPYCK.js} +13 -19
  15. package/dist/chunk-LRQNPYCK.js.map +1 -0
  16. package/dist/{chunk-GXOML4UZ.js → chunk-LW75SKZR.js} +2 -2
  17. package/dist/{chunk-EJQVGFH2.js → chunk-UOTKHSQ5.js} +26 -9
  18. package/dist/chunk-UOTKHSQ5.js.map +1 -0
  19. package/dist/{chunk-W7GAXL6C.js → chunk-V7PT6ER5.js} +6 -5
  20. package/dist/chunk-V7PT6ER5.js.map +1 -0
  21. package/dist/{chunk-ZNLSCV3J.js → chunk-XSNW6Y3X.js} +1253 -315
  22. package/dist/chunk-XSNW6Y3X.js.map +1 -0
  23. package/dist/{chunk-6DONJW3W.js → chunk-YJPCS2GP.js} +274 -69
  24. package/dist/chunk-YJPCS2GP.js.map +1 -0
  25. package/dist/{chunk-XL2HLACE.js → chunk-YN7TEIUH.js} +1 -1
  26. package/dist/chunk-YN7TEIUH.js.map +1 -0
  27. package/dist/{chunk-TLWGZVPC.js → chunk-YNSTIMCF.js} +746 -66
  28. package/dist/chunk-YNSTIMCF.js.map +1 -0
  29. package/dist/{chunk-NY2YXP3R.js → chunk-YWDH6DQ6.js} +70 -4
  30. package/dist/chunk-YWDH6DQ6.js.map +1 -0
  31. package/dist/cli/index.js +23 -9
  32. package/dist/cli/index.js.map +1 -1
  33. package/dist/{codebuddy-DO7ZhoWn.d.ts → codebuddy-CHqMptZB.d.ts} +35 -0
  34. package/dist/index.d.ts +6 -4
  35. package/dist/index.js +9 -9
  36. package/dist/install-EU35ERK5.js +30 -0
  37. package/dist/langgraph/index.d.ts +1 -1
  38. package/dist/lean-43ADYTBW.js +28 -0
  39. package/dist/{main-QUASSFP7.js → main-SFC5LV4E.js} +1333 -437
  40. package/dist/main-SFC5LV4E.js.map +1 -0
  41. package/dist/{operations-WJR7AZZF.js → operations-6Z6ZDVBP.js} +3 -3
  42. package/dist/{pretool-command-JTKX6G3M.js → pretool-command-B5GY5T6E.js} +3 -3
  43. package/dist/prompt-gate-TDTFFQW2.js +8 -0
  44. package/dist/{runtime-OQQ22VJF.js → runtime-W5AW32SD.js} +15 -7
  45. package/dist/{summaries-ML53R7EL.js → summaries-32PHOB6L.js} +3 -4
  46. package/dist/{zvec-XDZNZ2OL.js → zvec-24HLAFGN.js} +3 -4
  47. package/dist/zvec-24HLAFGN.js.map +1 -0
  48. package/package.json +11 -2
  49. package/dist/chunk-6DONJW3W.js.map +0 -1
  50. package/dist/chunk-6KGYAFG7.js.map +0 -1
  51. package/dist/chunk-EJQVGFH2.js.map +0 -1
  52. package/dist/chunk-GKNYMNFM.js.map +0 -1
  53. package/dist/chunk-ITQO632W.js +0 -673
  54. package/dist/chunk-ITQO632W.js.map +0 -1
  55. package/dist/chunk-J4T6HHI4.js.map +0 -1
  56. package/dist/chunk-NY2YXP3R.js.map +0 -1
  57. package/dist/chunk-TLWGZVPC.js.map +0 -1
  58. package/dist/chunk-UK6XTS2Y.js.map +0 -1
  59. package/dist/chunk-W7GAXL6C.js.map +0 -1
  60. package/dist/chunk-XL2HLACE.js.map +0 -1
  61. package/dist/chunk-ZNLSCV3J.js.map +0 -1
  62. package/dist/install-BXM56QCD.js +0 -19
  63. package/dist/lean-command-7DFW32MA.js +0 -19
  64. package/dist/main-QUASSFP7.js.map +0 -1
  65. /package/dist/{adapters-S4HHTWJW.js.map → adapters-OJRSVFV3.js.map} +0 -0
  66. /package/dist/{chunk-GXOML4UZ.js.map → chunk-LW75SKZR.js.map} +0 -0
  67. /package/dist/{install-BXM56QCD.js.map → install-EU35ERK5.js.map} +0 -0
  68. /package/dist/{lean-command-7DFW32MA.js.map → lean-43ADYTBW.js.map} +0 -0
  69. /package/dist/{operations-WJR7AZZF.js.map → operations-6Z6ZDVBP.js.map} +0 -0
  70. /package/dist/{pretool-command-JTKX6G3M.js.map → pretool-command-B5GY5T6E.js.map} +0 -0
  71. /package/dist/{runtime-OQQ22VJF.js.map → prompt-gate-TDTFFQW2.js.map} +0 -0
  72. /package/dist/{summaries-ML53R7EL.js.map → runtime-W5AW32SD.js.map} +0 -0
  73. /package/dist/{zvec-XDZNZ2OL.js.map → summaries-32PHOB6L.js.map} +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/clients/install.ts","../src/hooks/codex.ts","../src/hooks/bridge.ts","../src/hooks/settings.ts","../src/hooks/hermes-plugin.ts","../src/hooks/managed-plugin.ts","../src/hooks/opencode-plugin.ts","../src/hooks/install.ts","../src/integrations/project-mcp.ts","../src/templates/install.ts","../src/templates/workflow.ts","../src/cli/commands/codex.ts","../src/cli/commands/claude-desktop.ts","../src/integrations/hermes-config.ts","../src/clients/global-mcp.ts"],"sourcesContent":["import { access } from \"node:fs/promises\";\nimport { join, resolve } from \"node:path\";\nimport { resolveNamespaceFrom } from \"../core/config-file.js\";\nimport {\n clientHooksInstalled,\n clientHooksVariant,\n hookedRulesSiblings,\n setClientHooks,\n supportsCaptureOption,\n} from \"../hooks/install.js\";\nimport { defaultCapture, type HookVariant } from \"../hooks/settings.js\";\nimport { indexRepository } from \"../indexer/engine.js\";\nimport {\n codebuddyMcpServer,\n formatEntry,\n hasMcpServer,\n projectMcpPath,\n removeMcpServer,\n upsertMcpServer,\n} from \"../integrations/project-mcp.js\";\nimport { installWorkflowRules, readManagedMode } from \"../templates/install.js\";\nimport {\n type AutomationMode,\n CLIENT_ADAPTERS,\n type ClientId,\n MODE_LABELS,\n sharedRulesReaders,\n supportedModes,\n} from \"./adapters.js\";\nimport { globalMcpPath, hasGlobalMcp, setGlobalMcp } from \"./global-mcp.js\";\n\n/**\n * Applies one explicit automation mode to one client, across every surface its\n * adapter declares (rules block, lifecycle hooks, project MCP config). Each step\n * reuses the existing idempotent installer and reports what it did, so a re-run\n * is all `unchanged` and `uninstallClient` reverses it without touching user config.\n */\n\nexport type ClientStep = {\n surface: \"rules\" | \"hooks\" | \"mcp\" | \"index\";\n target: string;\n action: string;\n};\nexport type ClientReport = { client: ClientId; steps: ClientStep[]; notes: string[] };\n\ntype ClientOptions = {\n client: ClientId;\n projectRoot?: string;\n namespace?: string;\n /** Also write/remove the entry in the client's global MCP config (Codex, Hermes). */\n global?: boolean;\n};\n\nexport async function installClient(\n options: ClientOptions & {\n mode: AutomationMode;\n /** Hook capture (stage + turn-end capture); defaults to on for hooks, off for lean. */\n capture?: boolean;\n },\n): Promise<ClientReport> {\n const adapter = CLIENT_ADAPTERS[options.client];\n if (!supportedModes(adapter).includes(options.mode)) {\n throw new Error(\n `${adapter.name} does not support ${MODE_LABELS[options.mode]} mode: ${adapter.gap ?? \"no lifecycle hooks\"}.`,\n );\n }\n const variant = options.mode === \"lean\" ? \"lean\" : \"full\";\n if (\n options.capture !== undefined &&\n options.capture !== defaultCapture(variant) &&\n !supportsCaptureOption(options.client)\n ) {\n // Before any write: a half-applied install is worse than a clear refusal.\n throw new Error(\n `${adapter.name} hooks cannot set capture separately: ${variant} hooks ${defaultCapture(variant) ? \"always\" : \"never\"} capture.`,\n );\n }\n return apply(options, options.mode, options.mode !== \"lean\", options.capture);\n}\n\n/** Lean has no MCP server to index the repo, so install builds the index the hook reads. */\nasync function ensureIndex(root: string): Promise<ClientStep> {\n const target = join(root, \".codebuddy\", \"cache\", \"index.json\");\n const existed = await access(target).then(\n () => true,\n () => false,\n );\n if (!existed) await indexRepository({ repositoryRoot: root });\n return { surface: \"index\", target, action: existed ? \"unchanged\" : \"built\" };\n}\n\n/** Remove every CodeBuddy surface for a client; user rules, hooks and servers stay. */\nexport async function uninstallClient(options: ClientOptions): Promise<ClientReport> {\n return apply(options, \"mcp-only\", false);\n}\n\nasync function apply(\n options: ClientOptions,\n mode: AutomationMode,\n keepMcp: boolean,\n capture?: boolean,\n): Promise<ClientReport> {\n const root = resolve(options.projectRoot ?? process.cwd());\n const adapter = CLIENT_ADAPTERS[options.client];\n const steps: ClientStep[] = [];\n const notes: string[] = [];\n\n const namespace = options.namespace ?? (await resolveNamespaceFrom(root));\n const name = `codebuddy-${namespace}`;\n const shared = sharedRulesReaders(options.client);\n // Removing the block would strip the workflow from a sibling client that still\n // has its MCP entry for this namespace (project or global config).\n const liveSiblings =\n mode === \"mcp-only\" || mode === \"lean\" ? await installedWithMcp(root, shared, namespace) : [];\n if (liveSiblings.length > 0) {\n const path = join(root, adapter.rulesFile);\n steps.push({ surface: \"rules\", target: path, action: \"kept\" });\n notes.push(\n `Kept the CodeBuddy block in ${adapter.rulesFile}: still used by ${liveSiblings.map((id) => CLIENT_ADAPTERS[id].name).join(\", \")}.`,\n );\n } else {\n // An agent-driven block would make a hooked sibling retrieve and capture twice.\n const hooked = mode === \"agent\" ? await hookedRulesSiblings(root, options.client) : [];\n const rules = await installWorkflowRules({\n client: options.client,\n projectRoot: root,\n // Lean sends no rules: the block would cost tokens every turn and shadow AGENTS.md.\n mode: mode === \"lean\" ? \"mcp-only\" : hooked.length > 0 ? \"hooks\" : mode,\n });\n steps.push({ surface: \"rules\", target: rules.path, action: rules.action });\n if (hooked.length > 0) {\n notes.push(\n `Kept ${adapter.rulesFile} hook-driven: ${hooked.map((id) => CLIENT_ADAPTERS[id].name).join(\", \")} hooks are installed; agents without hooks follow its agent-driven fallback.`,\n );\n }\n }\n if (shared.length > 0) {\n notes.push(\n `${adapter.rulesFile} is also read by ${shared.map((id) => CLIENT_ADAPTERS[id].name).join(\", \")}.`,\n );\n }\n\n const hooks = await setHooks(\n root,\n mode === \"hooks\" || mode === \"lean\",\n options.client,\n mode === \"lean\" ? \"lean\" : \"full\",\n capture,\n );\n if (hooks) steps.push(hooks);\n if (mode === \"lean\") steps.push(await ensureIndex(root));\n\n if (\"file\" in adapter.mcp) {\n const path = await projectMcpPath(root, adapter.mcp.file, adapter.mcp.format);\n const action = keepMcp\n ? await upsertMcpServer(path, adapter.mcp.format, name, codebuddyMcpServer(root, namespace))\n : await removeMcpServer(path, adapter.mcp.format, name);\n steps.push({ surface: \"mcp\", target: path, action });\n if (action === \"manual\") {\n notes.push(\n `${path} has comments CodeBuddy will not rewrite; ${keepMcp ? \"add\" : \"remove\"} the \"${name}\" entry under \"mcp\" by hand${keepMcp ? `: ${JSON.stringify(formatEntry(adapter.mcp.format, codebuddyMcpServer(root, namespace)))}` : \"\"}.`,\n );\n }\n } else if (options.global) {\n const target = globalMcpPath(adapter.mcp.global);\n const action = await setGlobalMcp(adapter.mcp.global, {\n name,\n root,\n namespace,\n enabled: keepMcp,\n });\n steps.push({ surface: \"mcp\", target, action });\n if (action === \"manual\") {\n notes.push(\n `${target} is not a YAML map CodeBuddy can edit safely; ${keepMcp ? \"add\" : \"remove\"} the \"${name}\" entry under mcp_servers by hand.`,\n );\n }\n } else {\n notes.push(\n `MCP config is global; ${keepMcp ? \"add\" : \"remove\"} it with --global, or: ${adapter.mcp.manual}.`,\n );\n }\n if (adapter.note && mode !== \"mcp-only\") notes.push(`${adapter.name}: ${adapter.note}.`);\n return { client: options.client, steps, notes };\n}\n\nasync function installedWithMcp(\n root: string,\n clients: ClientId[],\n namespace: string,\n): Promise<ClientId[]> {\n const live: ClientId[] = [];\n for (const id of clients) {\n if ((await detectClient(id, root, namespace)).mcp) live.push(id);\n }\n return live;\n}\n\n/** Install or remove a client's CodeBuddy lifecycle hooks; null when it has none. */\nexport async function setHooks(\n root: string,\n enabled: boolean,\n client: ClientId = \"claude\",\n variant: HookVariant = \"full\",\n capture?: boolean,\n): Promise<ClientStep | null> {\n const result = await setClientHooks(root, client, enabled, variant, capture);\n if (!result) return null;\n const action = result.changed ? (enabled ? \"installed\" : \"removed\") : \"unchanged\";\n return { surface: \"hooks\", target: result.target, action };\n}\n\nexport type ClientStatus = {\n client: ClientId;\n /** The effective mode in this repo; \"none\" when nothing project-local is installed. */\n mode: AutomationMode | \"none\";\n rulesMode: AutomationMode | null;\n /** null = this client has no CodeBuddy hook adapter. */\n hooks: boolean | null;\n /** Project MCP entry, or for Codex/Hermes the entry in their global config. */\n mcp: boolean;\n warnings: string[];\n};\n\nexport async function detectClient(\n client: ClientId,\n projectRoot = process.cwd(),\n namespace?: string,\n): Promise<ClientStatus> {\n const root = resolve(projectRoot);\n const adapter = CLIENT_ADAPTERS[client];\n const rulesMode = await readManagedMode(join(root, adapter.rulesFile));\n const hooks = await clientHooksInstalled(root, client);\n const name = `codebuddy-${namespace ?? (await resolveNamespaceFrom(root))}`;\n const mcp =\n \"file\" in adapter.mcp\n ? await hasMcpServer(\n await projectMcpPath(root, adapter.mcp.file, adapter.mcp.format),\n adapter.mcp.format,\n name,\n )\n : await hasGlobalMcp(adapter.mcp.global, name).catch(() => false);\n\n const warnings: string[] = [];\n if (hooks && rulesMode === \"agent\") {\n warnings.push(\n `hooks are installed but ${adapter.rulesFile} is agent-driven: retrieval and capture run twice. Run \\`codebuddy clients install ${client} --mode hooks\\`.`,\n );\n }\n if (rulesMode === \"hooks\" && hooks === false) {\n warnings.push(\n `${adapter.rulesFile} is hook-driven but hooks are not installed: turns are not captured.`,\n );\n }\n // A hookless client reading a hook-driven block follows its agent-driven fallback.\n const effectiveRules = hooks !== true && rulesMode === \"hooks\" ? \"agent\" : rulesMode;\n const mode: ClientStatus[\"mode\"] = hooks\n ? (await clientHooksVariant(root, client)) === \"lean\"\n ? \"lean\"\n : \"hooks\"\n : (effectiveRules ?? (mcp ? \"mcp-only\" : \"none\"));\n return { client, mode, rulesMode, hooks, mcp, warnings };\n}\n","import { join } from \"node:path\";\nimport {\n appendHostEvents,\n type HostJournalEvent,\n readCheckpoint,\n renderCheckpointNow,\n} from \"../journal/journal.js\";\nimport { runBridgeHook } from \"./bridge.js\";\nimport { hookRoot } from \"./root.js\";\nimport {\n extractEditedPaths,\n type HookInput,\n runCaptureHook,\n runResetHook,\n runResumeHook,\n runStageHook,\n} from \"./runtime.js\";\nimport {\n defaultCapture,\n type HookCapabilities,\n type HookGroup,\n type HookSettings,\n type HookVariant,\n readSettings,\n writeSettings,\n} from \"./settings.js\";\n\nconst PREFIX = \"codebuddy hooks codex\";\nconst owned = (command: string) => command === PREFIX || command.startsWith(`${PREFIX} `);\nexport const codexHooksPath = (root: string) => join(root, \".codex\", \"hooks.json\");\n\n/** Native schema verified against Codex 0.169.0 and https://learn.chatgpt.com/docs/hooks. */\nexport async function setCodexHooks(\n root: string,\n enabled: boolean,\n variant: HookVariant = \"full\",\n capture = defaultCapture(variant),\n): Promise<boolean> {\n const path = codexHooksPath(root);\n const before = await readSettings(path);\n const hooks: Record<string, HookGroup[]> = {};\n const next: HookSettings = { ...before, hooks };\n for (const [event, groups] of Object.entries(before.hooks ?? {})) {\n const kept = groups.flatMap((group) => {\n const hooks = group.hooks.filter((hook) => !owned(hook.command));\n return hooks.length ? [{ ...group, hooks }] : [];\n });\n if (kept.length) hooks[event] = kept;\n }\n if (enabled) {\n const command = `${PREFIX} --injection ${variant}${capture ? \" --capture\" : \"\"}`;\n for (const [event, matcher] of [\n [\"UserPromptSubmit\", undefined],\n ...(capture\n ? [\n [\"PostToolUse\", \"Bash|apply_patch|Edit|Write\"],\n [\"Stop\", undefined],\n [\"PreCompact\", undefined],\n [\"Interrupt\", undefined],\n ]\n : []),\n [\"SessionStart\", \"startup|resume|fork|compact|clear\"],\n ] as Array<[string, string | undefined]>) {\n const handler = {\n type: \"command\" as const,\n command,\n timeout: event === \"Interrupt\" ? 3 : 20,\n };\n hooks[event] ??= [];\n hooks[event].push({ ...(matcher ? { matcher } : {}), hooks: [handler] });\n }\n }\n if (!Object.keys(next.hooks ?? {}).length) delete next.hooks;\n if (JSON.stringify(before) === JSON.stringify(next)) return false;\n await writeSettings(path, next);\n return true;\n}\n\nexport async function codexHookCapabilities(root: string): Promise<HookCapabilities> {\n const settings = await readSettings(codexHooksPath(root));\n const commands = (event: string) =>\n (settings.hooks?.[event] ?? [])\n .flatMap((group) => group.hooks.map((hook) => hook.command))\n .filter(owned);\n const prompt = commands(\"UserPromptSubmit\")[0];\n const capture = commands(\"Stop\").length > 0;\n return {\n injection: prompt ? (prompt.includes(\"--injection lean\") ? \"lean\" : \"full\") : null,\n capture,\n recovery: capture && commands(\"PreCompact\").length > 0,\n reset: commands(\"SessionStart\").length > 0,\n guard: false,\n };\n}\n\nexport type CodexHookInput = HookInput & {\n turn_id?: string;\n tool_use_id?: string;\n tool_response?: unknown;\n};\n\n/** Unknown outcomes stay unknown; never turn a model-facing tool string into an assumed pass. */\nfunction toolOutcome(value: unknown): boolean | undefined {\n if (value && typeof value === \"object\") {\n const item = value as Record<string, unknown>;\n if (typeof item.exit_code === \"number\") return item.exit_code === 0;\n if (typeof item.is_error === \"boolean\") return !item.is_error;\n }\n return undefined;\n}\n\n/** No trust/config bypass, no permission decisions, and no unstable rollout parsing. */\nexport async function runCodexHook(\n input: CodexHookInput,\n variant: HookVariant,\n capture: boolean,\n): Promise<Record<string, unknown>> {\n const root = hookRoot(input.cwd);\n const session = input.session_id ?? \"default\";\n const turn = input.turn_id ?? input.session_id ?? \"default\";\n const event = input.hook_event_name;\n let context = \"\";\n const base: HookInput = { cwd: root, session_id: session };\n if (event === \"UserPromptSubmit\") {\n const journal_events: HostJournalEvent[] =\n capture && input.prompt ? [{ id: `${turn}:prompt`, kind: \"user\", text: input.prompt }] : [];\n context = await runBridgeHook({\n ...base,\n ...(input.prompt ? { prompt: input.prompt } : {}),\n injection: variant,\n journal_events,\n });\n } else if (event === \"PostToolUse\" && capture) {\n const id = input.tool_use_id ?? `${turn}:${JSON.stringify(input.tool_input)}`;\n const events: HostJournalEvent[] = [];\n if (input.tool_name === \"Bash\" && typeof input.tool_input?.command === \"string\") {\n events.push({ id, kind: \"command\", ref: id, text: input.tool_input.command });\n const ok = toolOutcome(input.tool_response);\n events.push({\n id: `${id}:result`,\n kind: \"result\",\n ref: id,\n ...(ok !== undefined ? { ok } : {}),\n });\n await appendHostEvents(root, session, events);\n } else {\n if (toolOutcome(input.tool_response) === false) return {};\n const tool_input =\n input.tool_name === \"apply_patch\" ? { patch: input.tool_input?.command } : input.tool_input;\n extractEditedPaths(tool_input).forEach((path, index) => {\n events.push({ id: `${id}:${index}`, kind: \"edit\", path });\n });\n await runStageHook({\n ...base,\n ...(tool_input ? { tool_input } : {}),\n journal_events: events,\n });\n }\n } else if (event === \"Stop\" && capture) {\n const checkpoint = await readCheckpoint(root, session);\n await runCaptureHook({\n ...base,\n ...(input.last_assistant_message\n ? { last_assistant_message: input.last_assistant_message }\n : {}),\n user_prompts: checkpoint?.constraints ?? [],\n shell_commands:\n checkpoint?.verification.map((run) => ({\n command: run.command,\n exit_code: run.ok === null ? null : run.ok ? 0 : 1,\n })) ?? [],\n journal_events: input.last_assistant_message\n ? [{ id: `${turn}:assistant`, kind: \"assistant\", text: input.last_assistant_message }]\n : [],\n });\n } else if (event === \"SessionStart\") {\n if (input.source === \"compact\" || input.source === \"clear\") {\n context = await runResetHook({\n ...base,\n ...(variant === \"full\" ? { hook_event_name: \"SessionStart\" } : {}),\n source: capture ? input.source : \"clear\",\n });\n if (variant === \"lean\" && capture && input.source === \"compact\") {\n const checkpoint = await readCheckpoint(root, session);\n if (checkpoint?.goal)\n context = await renderCheckpointNow(root, checkpoint, \"before compaction\");\n }\n } else if (capture) context = await runResumeHook(base);\n }\n // PreCompact/Interrupt need no transcript work: user/tool events are already durable.\n return context\n ? { hookSpecificOutput: { hookEventName: event, additionalContext: context } }\n : {};\n}\n","import {\n isUnfinished,\n latestUnfinished,\n readCheckpoint,\n renderCheckpointNow,\n} from \"../journal/journal.js\";\nimport { runLeanHook } from \"./lean.js\";\nimport { hookRoot } from \"./root.js\";\nimport { type HookInput, runCheckpointHook, runContextHook } from \"./runtime.js\";\n\n/** Shared adapter path: capture visible events, deliver bounded context and restore task state. */\nexport async function runBridgeHook(\n input: HookInput,\n emit?: (text: string) => Promise<void>,\n): Promise<string> {\n await runCheckpointHook(input);\n const root = hookRoot(input.cwd);\n const session = input.session_id ?? \"default\";\n const recovery = input.bridge_recovery;\n const checkpoint =\n recovery === \"resume\"\n ? await latestUnfinished(root, session)\n : recovery\n ? await readCheckpoint(root, session)\n : null;\n const task =\n checkpoint &&\n (recovery === \"compact\" || isUnfinished(checkpoint)) &&\n (recovery !== \"current\" ||\n checkpoint.editedPaths.length ||\n checkpoint.findings.length ||\n checkpoint.nextAction ||\n checkpoint.verification.length)\n ? await renderCheckpointNow(\n root,\n checkpoint,\n recovery === \"resume\" ? `unfinished, from session ${checkpoint.session}` : \"current task\",\n )\n : \"\";\n let sent = false;\n const output = async (block: string) => {\n await emit?.([block, task].filter(Boolean).join(\"\\n\"));\n sent = true;\n };\n const block =\n input.injection === \"lean\"\n ? await runLeanHook(input, output)\n : await runContextHook(input, output);\n if (!sent && task) await emit?.(task);\n return [block, task].filter(Boolean).join(\"\\n\");\n}\n","import { mkdir, readFile } from \"node:fs/promises\";\nimport { dirname, join, resolve } from \"node:path\";\nimport { writeAtomic } from \"../core/markdown-store-fs.js\";\n\n/**\n * Installs CodeBuddy's lifecycle hooks into a client's settings JSON. Unlike the\n * Markdown workflow templates, settings files are JSON, so we can't use comment\n * markers — instead every CodeBuddy hook command starts with `codebuddy hooks`,\n * which is how we recognise (and cleanly remove) our own entries. Install is\n * idempotent and never touches hooks the user added themselves.\n *\n * Claude Code hooks fire deterministically, independent of the model's choices:\n * - UserPromptSubmit → `codebuddy hooks context` (recall, injected into prompt)\n * - PostToolUse(Edit|Write|MultiEdit) → `codebuddy hooks stage` (track edits)\n * - Stop → `codebuddy hooks capture` (capture durable memory at turn end)\n * - SessionStart(compact|clear) → `codebuddy hooks reset` (forget what was sent;\n * after compaction also re-inject the session's task checkpoint)\n * - PreCompact → `codebuddy hooks checkpoint` (journal before summarizing)\n * - SessionStart(startup|resume) → `codebuddy hooks resume` (another session's\n * unfinished task, if any)\n * - PreToolUse(Read|Grep) → `codebuddy hooks pretool` (outline big Reads, cap Greps;\n * both variants, never grants permission)\n */\n\nexport const HOOK_COMMAND_PREFIX = \"codebuddy hooks\";\n\nexport type HookCommand = { type: \"command\"; command: string };\nexport type HookGroup = { matcher?: string; hooks: HookCommand[] };\nexport type HookSettings = { hooks?: Record<string, HookGroup[]> } & Record<string, unknown>;\n\ntype HookDef = { event: string; command: string; matcher?: string };\n\n/** Token guard for tool output; independent of memory, so lean installs it too. */\nconst TOOL_GUARD_DEF: HookDef = {\n event: \"PreToolUse\",\n matcher: \"Read|Grep|Bash\",\n command: `${HOOK_COMMAND_PREFIX} pretool`,\n};\n\n/**\n * Capture (staging edits + turn-end memory capture) is independent of how\n * context is injected: a quiet, lean injection can still capture, and a full\n * injection can run without capture. Neither is implied by the other.\n */\nconst CAPTURE_DEFS: HookDef[] = [\n {\n event: \"PostToolUse\",\n matcher: \"Edit|Write|MultiEdit\",\n command: `${HOOK_COMMAND_PREFIX} stage`,\n },\n { event: \"Stop\", command: `${HOOK_COMMAND_PREFIX} capture` },\n // Journal the transcript before compaction summarizes it.\n { event: \"PreCompact\", command: `${HOOK_COMMAND_PREFIX} checkpoint` },\n // A new or resumed session sees another session's unfinished task, if any.\n { event: \"SessionStart\", matcher: \"startup|resume\", command: `${HOOK_COMMAND_PREFIX} resume` },\n];\nconst [STAGE_DEF, TURN_END_DEF, CHECKPOINT_DEF, RESUME_DEF] = CAPTURE_DEFS as [\n HookDef,\n HookDef,\n HookDef,\n HookDef,\n];\n\n// After compaction or /clear the model no longer holds earlier context, so\n// capsules the session ledger marked as sent must be sent again.\nconst RESET_DEF: HookDef = {\n event: \"SessionStart\",\n matcher: \"compact|clear\",\n command: `${HOOK_COMMAND_PREFIX} reset`,\n};\n\nconst INJECTION_DEFS: Record<HookVariant, HookDef> = {\n full: { event: \"UserPromptSubmit\", command: `${HOOK_COMMAND_PREFIX} context` },\n /** Lean: prompt pointers only. */\n lean: { event: \"UserPromptSubmit\", command: `${HOOK_COMMAND_PREFIX} lean` },\n};\n\nfunction hookDefs(variant: HookVariant, capture: boolean): HookDef[] {\n return [INJECTION_DEFS[variant], RESET_DEF, ...(capture ? CAPTURE_DEFS : []), TOOL_GUARD_DEF];\n}\n\nexport type HookVariant = \"full\" | \"lean\";\n/** Full hooks capture by default, lean hooks do not; either can be overridden. */\nexport const defaultCapture = (variant: HookVariant): boolean => variant === \"full\";\n\nexport function claudeSettingsPath(repositoryRoot = process.cwd()): string {\n return join(resolve(repositoryRoot), \".claude\", \"settings.json\");\n}\n\n/** Merge CodeBuddy's hook entries into a settings object. Idempotent. */\nexport function upsertHooks(\n settings: HookSettings,\n variant: HookVariant = \"full\",\n capture = defaultCapture(variant),\n): { settings: HookSettings; changed: boolean } {\n const defs = hookDefs(variant, capture);\n const wanted = new Set(defs.map((def) => def.command));\n const next: HookSettings = { ...settings, hooks: { ...(settings.hooks ?? {}) } };\n const hooks = next.hooks as Record<string, HookGroup[]>;\n let changed = false;\n\n // Switching variants: drop the other variant's CodeBuddy commands, keep the user's.\n for (const event of Object.keys(hooks)) {\n const groups: HookGroup[] = [];\n for (const group of hooks[event] ?? []) {\n const kept = (group.hooks ?? []).filter(\n (hook) => !hook.command?.startsWith(HOOK_COMMAND_PREFIX) || wanted.has(hook.command),\n );\n if (kept.length !== (group.hooks ?? []).length) changed = true;\n if (kept.length > 0) groups.push({ ...group, hooks: kept });\n }\n if (groups.length > 0) hooks[event] = groups;\n else delete hooks[event];\n }\n\n for (const def of defs) {\n const groups = [...(hooks[def.event] ?? [])];\n const existingIndex = groups.findIndex((group) =>\n group.hooks?.some((hook) => hook.command === def.command),\n );\n if (existingIndex === -1) {\n groups.push({\n ...(def.matcher ? { matcher: def.matcher } : {}),\n hooks: [{ type: \"command\", command: def.command }],\n });\n changed = true;\n } else {\n // The command exists, but possibly under a stale matcher (e.g. an older\n // install with matcher:\"Edit\" before MultiEdit coverage). Reconcile the\n // group's matcher so coverage matches the current contract.\n const existing = groups[existingIndex];\n if (existing && (def.matcher ?? undefined) !== (existing.matcher ?? undefined)) {\n const { matcher: _oldMatcher, ...rest } = existing;\n groups[existingIndex] = def.matcher ? { ...rest, matcher: def.matcher } : rest;\n changed = true;\n }\n }\n hooks[def.event] = groups;\n }\n\n return { settings: next, changed };\n}\n\n/** Remove every CodeBuddy hook entry, leaving user hooks intact. */\nexport function removeHooks(settings: HookSettings): { settings: HookSettings; changed: boolean } {\n if (!settings.hooks) return { settings, changed: false };\n const next: HookSettings = { ...settings, hooks: { ...settings.hooks } };\n const hooks = next.hooks as Record<string, HookGroup[]>;\n let changed = false;\n\n for (const event of Object.keys(hooks)) {\n const keptGroups: HookGroup[] = [];\n for (const group of hooks[event] ?? []) {\n // Filter INSIDE groups: a user group mixing our hook with their own\n // commands must keep theirs. Dropping whole groups used to silently\n // delete user hooks that happened to share a group with ours.\n const keptHooks = (group.hooks ?? []).filter(\n (hook) => !hook.command?.startsWith(HOOK_COMMAND_PREFIX),\n );\n if (keptHooks.length !== (group.hooks ?? []).length) changed = true;\n if (keptHooks.length === 0) continue; // group was entirely ours\n keptGroups.push({ ...group, hooks: keptHooks });\n }\n if (keptGroups.length === 0) delete hooks[event];\n else hooks[event] = keptGroups;\n }\n\n return { settings: next, changed };\n}\n\nexport function hooksInstalled(settings: HookSettings): boolean {\n const hooks = settings.hooks ?? {};\n return Object.values(hooks).some((groups) =>\n groups.some((group) => group.hooks?.some((h) => h.command?.startsWith(HOOK_COMMAND_PREFIX))),\n );\n}\n\n/** Which CodeBuddy hook set is installed, or null when none is. */\nexport function hooksVariant(settings: HookSettings): HookVariant | null {\n if (!hooksInstalled(settings)) return null;\n const leanCommand = `${HOOK_COMMAND_PREFIX} lean`;\n const lean = Object.values(settings.hooks ?? {}).some((groups) =>\n groups.some((group) => group.hooks?.some((hook) => hook.command === leanCommand)),\n );\n return lean ? \"lean\" : \"full\";\n}\n\n/** What the installed CodeBuddy hooks actually do, read from the settings themselves. */\nexport type HookCapabilities = {\n injection: HookVariant | null;\n /** Edits are staged and each turn end is captured into memory/worklog. */\n capture: boolean;\n /** Pre-compaction checkpoint and unfinished-task recovery at session start. */\n recovery: boolean;\n /** Compaction/clear resets what counts as already sent. */\n reset: boolean;\n /** Large Read/Grep/Bash outputs are guarded. */\n guard: boolean;\n};\n\nexport function hookCapabilities(settings: HookSettings): HookCapabilities {\n const commands = new Set(\n Object.values(settings.hooks ?? {}).flatMap((groups) =>\n groups.flatMap((group) => (group.hooks ?? []).map((hook) => hook.command)),\n ),\n );\n const has = (def: HookDef) => commands.has(def.command);\n return {\n injection: has(INJECTION_DEFS.lean) ? \"lean\" : has(INJECTION_DEFS.full) ? \"full\" : null,\n capture: has(STAGE_DEF) && has(TURN_END_DEF),\n recovery: has(CHECKPOINT_DEF) && has(RESUME_DEF),\n reset: has(RESET_DEF),\n guard: has(TOOL_GUARD_DEF),\n };\n}\n\nexport async function readSettings(path: string): Promise<HookSettings> {\n try {\n return JSON.parse(await readFile(path, \"utf8\")) as HookSettings;\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") return {};\n throw error;\n }\n}\n\nexport async function writeSettings(path: string, settings: HookSettings): Promise<void> {\n await mkdir(dirname(path), { recursive: true });\n // Atomic so a crash mid-write can never truncate the user's settings.json.\n await writeAtomic(path, `${JSON.stringify(settings, null, 2)}\\n`, { mode: 0o644 });\n}\n","import { readFile } from \"node:fs/promises\";\nimport { dirname, join, resolve } from \"node:path\";\nimport { type ManagedPlugin, managedPluginInstalled, setManagedPlugin } from \"./managed-plugin.js\";\nimport { defaultCapture, type HookCapabilities, type HookVariant } from \"./settings.js\";\n\n/**\n * Hermes Agent lifecycle bridge. Hermes loads project plugins from\n * `./.hermes/plugins/<name>/` (plugin.yaml + `__init__.py` with `register(ctx)`),\n * so CodeBuddy writes one managed Python plugin there. Like the OpenCode\n * bridge it only forwards: each hook runs the fail-soft `codebuddy hooks\n * <command>` runtime with a normalized JSON payload on stdin.\n *\n * Verified against Hermes Agent v0.17.0 (hermes_cli/plugins.py, agent/turn_context.py,\n * agent/turn_finalizer.py, model_tools.py, agent/conversation_compression.py):\n * - Project plugins load only with HERMES_ENABLE_PROJECT_PLUGINS=1 and the\n * plugin key in `plugins.enabled` of ~/.hermes/config.yaml. `hermes plugins\n * enable` only knows bundled and user plugins, so that entry is a manual\n * edit (or `clients install --global`).\n * - pre_llm_call: a returned {\"context\"} is appended to this turn's user\n * message at API-call time and never persisted, so the bridge asks for a\n * full block every turn (`ephemeral_context`).\n * - post_tool_call: write_file / patch (`path`, or V4A `patch` text) are\n * staged; terminal commands keep the JSON result's `exit_code`.\n * - post_llm_call: the final assistant text, used as the capture summary.\n * - on_session_end: fires at the end of every turn → capture.\n * - Compaction has no plugin hook and can rotate the session id mid-turn, so\n * a turn keeps the id it started with, and the per-turn full resend (which\n * also resets the session ledger) stands in for a compaction reset.\n */\n\nexport const HERMES_PLUGIN_MARKER = \"# codebuddy-managed: hermes-plugin\";\n\nexport const HERMES_PLUGIN_MANIFEST = `${HERMES_PLUGIN_MARKER} v1\nname: codebuddy\nversion: 1.0.0\nkind: standalone\ndescription: CodeBuddy recall and capture bridge (codebuddy clients install hermes --mode hooks).\nprovides_hooks:\n - pre_llm_call\n - post_tool_call\n - post_llm_call\n - on_session_end\n`;\n\nconst HERMES_LEAN_MANIFEST = `${HERMES_PLUGIN_MARKER} v1 lean\nname: codebuddy\nversion: 1.0.0\nkind: standalone\ndescription: CodeBuddy lean bridge (codebuddy clients install hermes --mode lean).\nprovides_hooks:\n - pre_llm_call\n`;\n\nconst HERMES_HOOK_FN = `def _hook(command, payload):\n \"\"\"Run one fail-soft CodeBuddy hook; its stdout, or \"\" on any failure.\"\"\"\n try:\n done = subprocess.run(\n [\"codebuddy\", \"hooks\", command],\n input=json.dumps({\"cwd\": ROOT, **payload}),\n capture_output=True,\n text=True,\n timeout=TIMEOUT_S[command],\n cwd=ROOT,\n )\n except Exception:\n return \"\"\n return done.stdout if done.returncode == 0 else \"\"\n\n\n`;\n\n/** The generated plugin: `full` bridges every lifecycle event, `lean` only prompts. */\nexport function renderHermesPlugin(\n variant: HookVariant,\n capture = defaultCapture(variant),\n): string {\n const command = variant === \"lean\" ? \"lean\" : \"context\";\n const mode = variant === \"lean\" ? \"lean\" : \"hooks\";\n const captureMarker =\n capture === defaultCapture(variant) ? \"\" : `\\n# codebuddy-capture: ${capture ? \"on\" : \"off\"}`;\n const marker = `${HERMES_PLUGIN_MARKER} v1${variant === \"lean\" ? \" lean\" : \"\"}${captureMarker}${capture ? \"\\n# codebuddy-recovery: v1\" : \"\"}`;\n return !capture\n ? `${marker}\n# Generated by \"codebuddy clients install hermes --mode ${mode}\"; removed by\n# \"codebuddy clients uninstall hermes\". Local edits are overwritten.\n\"\"\"CodeBuddy lean bridge: forwards each prompt to \"codebuddy hooks lean\".\"\"\"\n\nimport json\nimport subprocess\nfrom pathlib import Path\n\n# <root>/.hermes/plugins/codebuddy/__init__.py\nROOT = str(Path(__file__).absolute().parents[3])\nTIMEOUT_S = {\"${command}\": ${variant === \"lean\" ? 2 : 20}}\n\n\n${HERMES_HOOK_FN}def _pre_llm_call(session_id=\"\", user_message=\"\", **_):\n if not isinstance(user_message, str) or not user_message.strip():\n return None\n payload = {\n \"session_id\": session_id,\n \"hook_event_name\": \"pre_llm_call\",\n \"ephemeral_context\": True,\n \"prompt\": user_message,\n }\n block = _hook(\"${command}\", payload).strip()\n return {\"context\": block} if block else None\n\n\ndef register(ctx):\n ctx.register_hook(\"pre_llm_call\", _pre_llm_call)\n`\n : `${marker}\n# Generated by \"codebuddy clients install hermes --mode ${mode}\"; removed by\n# \"codebuddy clients uninstall hermes\". Local edits are overwritten.\n\"\"\"CodeBuddy lifecycle bridge: forwards Hermes hooks to \"codebuddy hooks <command>\".\"\"\"\n\nimport json\nimport subprocess\nimport threading\nfrom pathlib import Path\n\n# <root>/.hermes/plugins/codebuddy/__init__.py\nROOT = str(Path(__file__).absolute().parents[3])\nEDIT_TOOLS = {\"write_file\", \"patch\"}\nTIMEOUT_S = {\"bridge\": 20, \"checkpoint\": 10, \"stage\": 10, \"capture\": 60}\n_lock = threading.Lock()\n_turns = {}\n\n\n${HERMES_HOOK_FN}def _turn(turn_id, session_id):\n # Compression can rotate the session id mid-turn; a turn keeps its first id.\n with _lock:\n return _turns.setdefault(\n turn_id or session_id, {\"session\": session_id, \"text\": \"\", \"commands\": [], \"prompt\": \"\"}\n )\n\n\ndef _pre_llm_call(session_id=\"\", turn_id=\"\", user_message=\"\", is_first_turn=False, **_):\n turn = _turn(turn_id, session_id)\n payload = {\"session_id\": turn[\"session\"], \"hook_event_name\": \"pre_llm_call\", \"ephemeral_context\": True,\n \"injection\": \"${variant}\", \"bridge_recovery\": \"resume\" if is_first_turn else \"current\"}\n if isinstance(user_message, str) and user_message.strip():\n payload[\"prompt\"] = user_message\n turn[\"prompt\"] = user_message\n payload[\"journal_events\"] = [{\"id\": (turn_id or session_id) + \":prompt\", \"kind\": \"user\", \"text\": user_message}]\n block = _hook(\"bridge\", payload).strip()\n return {\"context\": block} if block else None\n\n\ndef _post_tool_call(tool_name=\"\", args=None, result=None, session_id=\"\", turn_id=\"\", tool_call_id=\"\", **_):\n turn = _turn(turn_id, session_id)\n args = args if isinstance(args, dict) else {}\n if tool_name in EDIT_TOOLS:\n # Paths only: write_file content never needs to cross the pipe.\n tool_input = {k: args[k] for k in (\"path\", \"mode\", \"patch\") if k in args}\n _hook(\"stage\", {\n \"session_id\": turn[\"session\"],\n \"hook_event_name\": \"post_tool_call\",\n \"tool_name\": tool_name,\n \"tool_input\": tool_input,\n \"journal_event_id\": tool_call_id or turn_id + \":\" + json.dumps(tool_input, sort_keys=True),\n })\n elif tool_name == \"terminal\" and isinstance(args.get(\"command\"), str):\n exit_code = None\n if not args.get(\"background\"): # a background process has not finished\n try:\n exit_code = json.loads(result).get(\"exit_code\")\n except Exception:\n pass\n turn[\"commands\"].append({\n \"command\": args[\"command\"],\n \"exit_code\": exit_code if type(exit_code) is int else None,\n })\n ref = tool_call_id or turn_id + \":\" + args[\"command\"]\n outcome = {\"id\": ref + \":result\", \"kind\": \"result\", \"ref\": ref}\n if type(exit_code) is int:\n outcome[\"ok\"] = exit_code == 0\n _hook(\"checkpoint\", {\"session_id\": turn[\"session\"], \"journal_events\": [\n {\"id\": ref, \"kind\": \"command\", \"ref\": ref, \"text\": args[\"command\"]}, outcome]})\n\n\ndef _post_llm_call(session_id=\"\", turn_id=\"\", assistant_response=\"\", **_):\n if isinstance(assistant_response, str) and assistant_response.strip():\n turn = _turn(turn_id, session_id)\n turn[\"text\"] = assistant_response\n _hook(\"checkpoint\", {\"session_id\": turn[\"session\"], \"journal_events\": [\n {\"id\": (turn_id or session_id) + \":text:\" + assistant_response, \"kind\": \"assistant\", \"text\": assistant_response}]})\n\n\ndef _on_session_end(session_id=\"\", turn_id=\"\", **_):\n with _lock:\n turn = _turns.pop(turn_id or session_id, None)\n turn = turn or {\"session\": session_id, \"text\": \"\", \"commands\": [], \"prompt\": \"\"}\n payload = {\"session_id\": turn[\"session\"], \"hook_event_name\": \"on_session_end\"}\n if turn.get(\"prompt\"):\n payload[\"user_prompts\"] = [turn[\"prompt\"]]\n if turn[\"text\"]:\n payload[\"last_assistant_message\"] = turn[\"text\"]\n if turn[\"commands\"]:\n payload[\"shell_commands\"] = turn[\"commands\"][-20:]\n _hook(\"capture\", payload)\n\n\ndef register(ctx):\n ctx.register_hook(\"pre_llm_call\", _pre_llm_call)\n ctx.register_hook(\"post_tool_call\", _post_tool_call)\n ctx.register_hook(\"post_llm_call\", _post_llm_call)\n ctx.register_hook(\"on_session_end\", _on_session_end)\n`;\n}\n\nexport const HERMES_PLUGIN_SOURCE = renderHermesPlugin(\"full\");\n\nexport function hermesPluginDir(repositoryRoot: string): string {\n return join(resolve(repositoryRoot), \".hermes\", \"plugins\", \"codebuddy\");\n}\n\nfunction plugin(\n repositoryRoot: string,\n variant: HookVariant = \"full\",\n capture = defaultCapture(variant),\n): ManagedPlugin {\n const dir = hermesPluginDir(repositoryRoot);\n return {\n marker: HERMES_PLUGIN_MARKER,\n files: [\n {\n path: join(dir, \"plugin.yaml\"),\n content: capture ? HERMES_PLUGIN_MANIFEST : HERMES_LEAN_MANIFEST,\n },\n { path: join(dir, \"__init__.py\"), content: renderHermesPlugin(variant, capture) },\n ],\n dirs: [dir, dirname(dir), dirname(dirname(dir))],\n };\n}\n\nexport function hermesPluginInstalled(repositoryRoot: string): Promise<boolean> {\n return managedPluginInstalled(plugin(repositoryRoot));\n}\n\nexport function setHermesPlugin(\n repositoryRoot: string,\n enabled: boolean,\n variant: HookVariant = \"full\",\n capture = defaultCapture(variant),\n): Promise<boolean> {\n return setManagedPlugin(plugin(repositoryRoot, variant, capture), enabled);\n}\n\nexport async function hermesPluginCapabilities(root: string): Promise<HookCapabilities> {\n const injection = await hermesPluginVariant(root);\n const source = injection\n ? await readFile(join(hermesPluginDir(root), \"__init__.py\"), \"utf8\")\n : \"\";\n const explicit = source.match(/^# codebuddy-capture: (on|off)$/m)?.[1];\n return {\n injection,\n capture: explicit ? explicit === \"on\" : injection === \"full\",\n recovery:\n (explicit ? explicit === \"on\" : injection === \"full\") &&\n source.includes(\"codebuddy-recovery: v1\"),\n reset: injection !== null,\n guard: false,\n };\n}\n\n/** Which plugin variant is installed, or null when CodeBuddy's plugin is absent. */\nexport async function hermesPluginVariant(repositoryRoot: string): Promise<HookVariant | null> {\n if (!(await hermesPluginInstalled(repositoryRoot))) return null;\n const source = await readFile(join(hermesPluginDir(repositoryRoot), \"__init__.py\"), \"utf8\");\n return source.startsWith(`${HERMES_PLUGIN_MARKER} v1 lean`) ? \"lean\" : \"full\";\n}\n","import { mkdir, readFile, rm, rmdir } from \"node:fs/promises\";\nimport { dirname } from \"node:path\";\nimport { writeAtomic } from \"../core/markdown-store-fs.js\";\n\n/**\n * A host plugin CodeBuddy owns: files that each start with `marker`. Install\n * and uninstall are idempotent, and a file at one of these paths without the\n * marker is never overwritten or removed.\n */\nexport type ManagedPlugin = {\n marker: string;\n files: Array<{ path: string; content: string }>;\n /** Directories install may create, innermost first; uninstall removes them only while empty. */\n dirs: string[];\n};\n\nasync function read(path: string): Promise<string | null> {\n try {\n return await readFile(path, \"utf8\");\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") return null;\n throw error;\n }\n}\n\nexport async function managedPluginInstalled(plugin: ManagedPlugin): Promise<boolean> {\n for (const file of plugin.files) {\n if (!(await read(file.path))?.startsWith(plugin.marker)) return false;\n }\n return true;\n}\n\n/** Write or remove the plugin; returns whether anything changed. */\nexport async function setManagedPlugin(plugin: ManagedPlugin, enabled: boolean): Promise<boolean> {\n const current = await Promise.all(plugin.files.map((file) => read(file.path)));\n const foreign = plugin.files.find((_, i) => {\n const text = current[i];\n return text != null && !text.startsWith(plugin.marker);\n });\n if (foreign) {\n if (!enabled) return false;\n throw new Error(\n `${foreign.path} exists and is not managed by CodeBuddy; move it aside and re-run.`,\n );\n }\n let changed = false;\n for (const [i, file] of plugin.files.entries()) {\n if (enabled && current[i] !== file.content) {\n await mkdir(dirname(file.path), { recursive: true });\n await writeAtomic(file.path, file.content, { mode: 0o644 });\n changed = true;\n } else if (!enabled && current[i] != null) {\n await rm(file.path, { force: true });\n changed = true;\n }\n }\n if (!enabled && changed) {\n for (const dir of plugin.dirs) await rmdir(dir).catch(() => {});\n }\n return changed;\n}\n","import { readFile } from \"node:fs/promises\";\nimport { dirname, join, resolve } from \"node:path\";\nimport { type ManagedPlugin, managedPluginInstalled, setManagedPlugin } from \"./managed-plugin.js\";\nimport { defaultCapture, type HookCapabilities, type HookVariant } from \"./settings.js\";\n\n/**\n * OpenCode lifecycle bridge. OpenCode loads project plugins from\n * `.opencode/{plugin,plugins}/*.{ts,js}`, so CodeBuddy writes one managed JS\n * plugin there. It only forwards events: each one spawns the same fail-soft\n * `codebuddy hooks <command>` runtime Claude Code uses, with a normalized\n * Claude-shaped JSON payload on stdin. All logic stays in the shared services.\n *\n * Verified against OpenCode 1.18.32 (binary) and @opencode-ai/plugin 1.17.9 types:\n * - chat.message: mutated `output.parts` are saved with the user message and\n * non-ignored text parts reach the model, so the context block is appended\n * as a synthetic text part (prompt-time injection).\n * - tool.execute.after: edit/write (`filePath`), apply_patch/patch (`patchText`)\n * are staged; bash commands and `metadata.exit` feed turn verification.\n * - experimental.text.complete: the latest assistant text, used as the\n * capture summary (OpenCode has no transcript file).\n * - event session.idle → capture; session.compacted → reset.\n * The module must export only plugin functions: OpenCode calls every export.\n */\n\nexport const OPENCODE_PLUGIN_MARKER = \"// codebuddy-managed: opencode-plugin\";\n\nconst OPENCODE_RUNTIME = `const ID_CHARS = \"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz\";\nlet lastMs = 0;\nlet counter = 0;\n\n/** Run one fail-soft CodeBuddy hook; resolves to its stdout, \"\" on any failure. */\nfunction hook(command, payload) {\n return new Promise((done) => {\n let out = \"\";\n let child;\n try {\n child = spawn(\"codebuddy\", [\"hooks\", command], {\n cwd: payload.cwd,\n stdio: [\"pipe\", \"pipe\", \"ignore\"],\n // npm installs a codebuddy.cmd shim on Windows, which spawn only runs through a shell.\n shell: process.platform === \"win32\",\n });\n } catch {\n done(\"\");\n return;\n }\n const timer = setTimeout(() => child.kill(), TIMEOUT_MS[command]);\n child.stdout.setEncoding(\"utf8\");\n child.stdout.on(\"data\", (chunk) => {\n out += chunk;\n });\n child.on(\"error\", () => {\n clearTimeout(timer);\n done(\"\");\n });\n child.on(\"close\", (code) => {\n clearTimeout(timer);\n done(code === 0 ? out : \"\");\n });\n child.stdin.on(\"error\", () => {});\n child.stdin.end(JSON.stringify(payload));\n });\n}\n\n/** An ascending OpenCode part id (prt_ + 48-bit time/counter hex + 14 base62). */\nfunction partId() {\n const now = Date.now();\n if (now !== lastMs) {\n lastMs = now;\n counter = 0;\n }\n counter += 1;\n const time = (BigInt(now) * 4096n + BigInt(counter)) & 0xffffffffffffn;\n let random = \"\";\n for (const byte of crypto.getRandomValues(new Uint8Array(14))) random += ID_CHARS[byte % 62];\n return \"prt_\" + time.toString(16).padStart(12, \"0\") + random;\n}\n\n`;\n\n/** The generated plugin: `full` bridges every lifecycle event, `lean` only prompts and compaction. */\nexport function renderOpencodePlugin(\n variant: HookVariant,\n capture = defaultCapture(variant),\n): string {\n const command = variant === \"lean\" ? \"lean\" : \"context\";\n const mode = variant === \"lean\" ? \"lean\" : \"hooks\";\n const captureMarker =\n capture === defaultCapture(variant) ? \"\" : `\\n// codebuddy-capture: ${capture ? \"on\" : \"off\"}`;\n const marker = `${OPENCODE_PLUGIN_MARKER} v1${variant === \"lean\" ? \" lean\" : \"\"}${captureMarker}${capture ? \"\\n// codebuddy-recovery: v1\" : \"\"}`;\n return !capture\n ? `${marker}\n// Generated by \\`codebuddy clients install opencode --mode ${mode}\\`; removed by\n// \\`codebuddy clients uninstall opencode\\`. Local edits are overwritten.\nimport { spawn } from \"node:child_process\";\n\nconst TIMEOUT_MS = { ${command}: ${variant === \"lean\" ? 2000 : 20000}, reset: 10000 };\n${OPENCODE_RUNTIME}/** @type {import(\"@opencode-ai/plugin\").Plugin} */\nexport const CodeBuddyPlugin = async ({ directory }) => {\n const base = (sessionID, event) => ({ session_id: sessionID, cwd: directory, hook_event_name: event });\n\n return {\n \"chat.message\": async (input, output) => {\n const prompt = output.parts\n .flatMap((part) => (part.type === \"text\" && !part.synthetic ? [part.text] : []))\n .join(\"\\\\n\")\n .trim();\n if (!prompt) return;\n const block = (await hook(\"${command}\", { ...base(input.sessionID, \"chat.message\"), prompt })).trim();\n if (!block) return;\n output.parts.push({\n id: partId(),\n sessionID: input.sessionID,\n messageID: output.message.id,\n type: \"text\",\n text: block,\n synthetic: true,\n });\n },\n event: async ({ event }) => {\n if (event.type === \"session.compacted\") {\n await hook(\"reset\", { ...base(event.properties.sessionID, \"session.compacted\"), source: \"compact\" });\n }\n },\n };\n};\n`\n : `${marker}\n// Generated by \\`codebuddy clients install opencode --mode ${mode}\\`; removed by\n// \\`codebuddy clients uninstall opencode\\`. Local edits are overwritten.\nimport { spawn } from \"node:child_process\";\n\nconst EDIT_TOOLS = new Set([\"edit\", \"write\", \"patch\", \"apply_patch\"]);\nconst TIMEOUT_MS = { bridge: 20000, checkpoint: 10000, stage: 10000, capture: 60000, reset: 10000 };\n${OPENCODE_RUNTIME}/** @type {import(\"@opencode-ai/plugin\").Plugin} */\nexport const CodeBuddyPlugin = async ({ directory }) => {\n /** @type {Map<string, { text: string, commands: Array<{ command: string, exit_code: number | null }> }>} */\n const turns = new Map();\n const sessions = new Set();\n const compacted = new Set();\n const turn = (sessionID) => {\n if (!turns.has(sessionID)) turns.set(sessionID, { text: \"\", commands: [], prompt: \"\" });\n return turns.get(sessionID);\n };\n const base = (sessionID, event) => ({ session_id: sessionID, cwd: directory, hook_event_name: event });\n\n return {\n \"chat.message\": async (input, output) => {\n turns.delete(input.sessionID);\n const prompt = output.parts\n .flatMap((part) => (part.type === \"text\" && !part.synthetic ? [part.text] : []))\n .join(\"\\\\n\")\n .trim();\n turn(input.sessionID).prompt = prompt;\n const block = (\n await hook(\"bridge\", {\n ...base(input.sessionID, \"chat.message\"), ...(prompt ? { prompt } : {}),\n injection: \"${variant}\",\n journal_events: prompt ? [{ id: output.message.id, kind: \"user\", text: prompt }] : [],\n ...(compacted.has(input.sessionID) ? { bridge_recovery: \"compact\" } : !sessions.has(input.sessionID) ? { bridge_recovery: \"resume\" } : {}),\n })\n ).trim();\n if (block) { sessions.add(input.sessionID); compacted.delete(input.sessionID); }\n if (!block) return;\n output.parts.push({\n id: partId(),\n sessionID: input.sessionID,\n messageID: output.message.id,\n type: \"text\",\n text: block,\n synthetic: true,\n });\n },\n \"tool.execute.after\": async (input, output) => {\n if (EDIT_TOOLS.has(input.tool)) {\n await hook(\"stage\", {\n ...base(input.sessionID, \"tool.execute.after\"),\n tool_name: input.tool,\n tool_input: input.args,\n journal_event_id: input.callID,\n });\n } else if (input.tool === \"bash\" && typeof input.args?.command === \"string\") {\n const exit = output.metadata?.exit;\n turn(input.sessionID).commands.push({\n command: input.args.command,\n exit_code: typeof exit === \"number\" ? exit : null,\n });\n await hook(\"checkpoint\", {\n ...base(input.sessionID, \"tool.execute.after\"),\n journal_events: [\n { id: input.callID, kind: \"command\", ref: input.callID, text: input.args.command },\n { id: input.callID + \":result\", kind: \"result\", ref: input.callID, ...(typeof exit === \"number\" ? { ok: exit === 0 } : {}) },\n ],\n });\n }\n },\n \"experimental.text.complete\": async (input, output) => {\n if (output.text.trim()) {\n turn(input.sessionID).text = output.text;\n await hook(\"checkpoint\", {\n ...base(input.sessionID, \"experimental.text.complete\"),\n journal_events: [{ id: (input.messageID || input.sessionID) + \":text:\" + output.text, kind: \"assistant\", text: output.text }],\n });\n }\n },\n event: async ({ event }) => {\n if (event.type === \"session.idle\") {\n const sessionID = event.properties.sessionID;\n const current = turns.get(sessionID);\n turns.delete(sessionID);\n await hook(\"capture\", {\n ...base(sessionID, \"session.idle\"),\n ...(current?.prompt ? { user_prompts: [current.prompt] } : {}),\n ...(current?.text ? { last_assistant_message: current.text } : {}),\n ...(current?.commands.length ? { shell_commands: current.commands.slice(-20) } : {}),\n });\n } else if (event.type === \"session.compacted\") {\n compacted.add(event.properties.sessionID);\n await hook(\"reset\", { ...base(event.properties.sessionID, \"session.compacted\"), source: \"compact\" });\n }\n },\n };\n};\n`;\n}\n\nexport const OPENCODE_PLUGIN_SOURCE = renderOpencodePlugin(\"full\");\n\nexport function opencodePluginPath(repositoryRoot: string): string {\n return join(resolve(repositoryRoot), \".opencode\", \"plugins\", \"codebuddy.js\");\n}\n\nfunction plugin(\n repositoryRoot: string,\n variant: HookVariant = \"full\",\n capture = defaultCapture(variant),\n): ManagedPlugin {\n const path = opencodePluginPath(repositoryRoot);\n return {\n marker: OPENCODE_PLUGIN_MARKER,\n files: [{ path, content: renderOpencodePlugin(variant, capture) }],\n dirs: [dirname(path), dirname(dirname(path))],\n };\n}\n\nexport function opencodePluginInstalled(repositoryRoot: string): Promise<boolean> {\n return managedPluginInstalled(plugin(repositoryRoot));\n}\n\n/** Write or remove CodeBuddy's plugin; a same-named file without the marker is never touched. */\nexport function setOpencodePlugin(\n repositoryRoot: string,\n enabled: boolean,\n variant: HookVariant = \"full\",\n capture = defaultCapture(variant),\n): Promise<boolean> {\n return setManagedPlugin(plugin(repositoryRoot, variant, capture), enabled);\n}\n\nexport async function opencodePluginCapabilities(root: string): Promise<HookCapabilities> {\n const injection = await opencodePluginVariant(root);\n const source = injection ? await readFile(opencodePluginPath(root), \"utf8\") : \"\";\n const explicit = source.match(/^\\/\\/ codebuddy-capture: (on|off)$/m)?.[1];\n return {\n injection,\n capture: explicit ? explicit === \"on\" : injection === \"full\",\n recovery:\n (explicit ? explicit === \"on\" : injection === \"full\") &&\n source.includes(\"codebuddy-recovery: v1\"),\n reset: injection !== null,\n guard: false,\n };\n}\n\n/** Which plugin variant is installed, or null when CodeBuddy's plugin is absent. */\nexport async function opencodePluginVariant(repositoryRoot: string): Promise<HookVariant | null> {\n if (!(await opencodePluginInstalled(repositoryRoot))) return null;\n const source = await readFile(opencodePluginPath(repositoryRoot), \"utf8\");\n return source.startsWith(`${OPENCODE_PLUGIN_MARKER} v1 lean`) ? \"lean\" : \"full\";\n}\n","import {\n CLIENT_ADAPTERS,\n CLIENT_IDS,\n type ClientId,\n type HookSurface,\n} from \"../clients/adapters.js\";\nimport { codexHookCapabilities, codexHooksPath, setCodexHooks } from \"./codex.js\";\nimport {\n hermesPluginCapabilities,\n hermesPluginDir,\n hermesPluginInstalled,\n hermesPluginVariant,\n setHermesPlugin,\n} from \"./hermes-plugin.js\";\nimport {\n opencodePluginCapabilities,\n opencodePluginInstalled,\n opencodePluginPath,\n opencodePluginVariant,\n setOpencodePlugin,\n} from \"./opencode-plugin.js\";\nimport {\n claudeSettingsPath,\n defaultCapture,\n type HookCapabilities,\n type HookVariant,\n hookCapabilities,\n hooksInstalled,\n hooksVariant,\n readSettings,\n removeHooks,\n upsertHooks,\n writeSettings,\n} from \"./settings.js\";\n\n/**\n * One install/detect contract per hook surface in the adapter table, so callers\n * (clients install, hooks install, rules mode) never branch on client names.\n */\nconst SURFACES: Record<\n HookSurface,\n {\n path: (root: string) => string;\n installed: (root: string) => Promise<boolean>;\n variant: (root: string) => Promise<HookVariant | null>;\n /** Returns whether anything changed. */\n set: (\n root: string,\n enabled: boolean,\n variant: HookVariant,\n capture: boolean,\n ) => Promise<boolean>;\n /** null: the surface cannot report (or configure) capture separately from the variant. */\n capabilities?: (root: string) => Promise<HookCapabilities>;\n }\n> = {\n \"claude-settings\": {\n path: claudeSettingsPath,\n installed: async (root) => hooksInstalled(await readSettings(claudeSettingsPath(root))),\n variant: async (root) => hooksVariant(await readSettings(claudeSettingsPath(root))),\n set: async (root, enabled, variant, capture) => {\n const path = claudeSettingsPath(root);\n const current = await readSettings(path);\n const { settings, changed } = enabled\n ? upsertHooks(current, variant, capture)\n : removeHooks(current);\n if (changed) await writeSettings(path, settings);\n return changed;\n },\n capabilities: async (root) => hookCapabilities(await readSettings(claudeSettingsPath(root))),\n },\n \"codex-hooks\": {\n path: codexHooksPath,\n installed: async (root) => (await codexHookCapabilities(root)).injection !== null,\n variant: async (root) => (await codexHookCapabilities(root)).injection,\n set: setCodexHooks,\n capabilities: codexHookCapabilities,\n },\n \"opencode-plugin\": {\n path: opencodePluginPath,\n installed: opencodePluginInstalled,\n variant: opencodePluginVariant,\n set: setOpencodePlugin,\n capabilities: opencodePluginCapabilities,\n },\n \"hermes-plugin\": {\n path: hermesPluginDir,\n installed: hermesPluginInstalled,\n variant: hermesPluginVariant,\n set: setHermesPlugin,\n capabilities: hermesPluginCapabilities,\n },\n};\n\n/** Install or remove a client's CodeBuddy hooks; null when the client has none. */\nexport async function setClientHooks(\n root: string,\n client: ClientId,\n enabled: boolean,\n variant: HookVariant = \"full\",\n capture = defaultCapture(variant),\n): Promise<{ target: string; changed: boolean } | null> {\n const surface = CLIENT_ADAPTERS[client].hooks;\n if (!surface) return null;\n const { path, set, capabilities } = SURFACES[surface];\n if (enabled && !capabilities && capture !== defaultCapture(variant)) {\n // The OpenCode/Hermes plugins bake capture into the variant; say so rather than ignore it.\n throw new Error(\n `${CLIENT_ADAPTERS[client].name} hooks cannot set capture separately: ${variant} hooks ${defaultCapture(variant) ? \"always\" : \"never\"} capture.`,\n );\n }\n return { target: path(root), changed: await set(root, enabled, variant, capture) };\n}\n\n/** Whether a client's hooks can capture independently of the injection variant. */\nexport function supportsCaptureOption(client: ClientId): boolean {\n const surface = CLIENT_ADAPTERS[client].hooks;\n return surface !== undefined && SURFACES[surface].capabilities !== undefined;\n}\n\n/**\n * What a client's installed hooks do. Surfaces that cannot report it\n * separately derive it from the variant (full captures, lean does not).\n */\nexport async function clientHookCapabilities(\n root: string,\n client: ClientId,\n): Promise<HookCapabilities | null> {\n const surface = CLIENT_ADAPTERS[client].hooks;\n if (!surface) return null;\n const { capabilities, variant } = SURFACES[surface];\n if (capabilities) return capabilities(root);\n const installed = await variant(root);\n return {\n injection: installed,\n capture: installed ? defaultCapture(installed) : false,\n recovery: false,\n reset: installed !== null,\n guard: false,\n };\n}\n\n/** Which CodeBuddy hook variant a client has; null when the client has no hook surface. */\nexport async function clientHooksVariant(\n root: string,\n client: ClientId,\n): Promise<HookVariant | null | undefined> {\n const surface = CLIENT_ADAPTERS[client].hooks;\n if (!surface) return undefined;\n return SURFACES[surface].variant(root);\n}\n\n/** Whether a client's CodeBuddy hooks are installed; null when the client has none. */\nexport async function clientHooksInstalled(\n root: string,\n client: ClientId,\n): Promise<boolean | null> {\n const surface = CLIENT_ADAPTERS[client].hooks;\n return surface ? SURFACES[surface].installed(root) : null;\n}\n\n/**\n * Clients other than `client` that read the same rules file and have hooks\n * installed. Their rules block must stay hook-driven, or they retrieve and\n * capture twice; the hook-driven block already tells hookless agents to fall\n * back to the agent-driven flow.\n */\nexport async function hookedRulesSiblings(root: string, client: ClientId): Promise<ClientId[]> {\n const file = CLIENT_ADAPTERS[client].rulesFile;\n const hooked: ClientId[] = [];\n for (const id of CLIENT_IDS) {\n if (\n id !== client &&\n CLIENT_ADAPTERS[id].rulesFile === file &&\n (await clientHooksInstalled(root, id))\n )\n hooked.push(id);\n }\n return hooked;\n}\n\n/** Whether any client reading `client`'s rules file (itself included) has hooks installed. */\nexport async function rulesFileHooked(root: string, client: ClientId): Promise<boolean> {\n return (\n (await clientHooksInstalled(root, client)) === true ||\n (await hookedRulesSiblings(root, client)).length > 0\n );\n}\n","import { access, mkdir, readFile, rm } from \"node:fs/promises\";\nimport { dirname, join, resolve } from \"node:path\";\nimport { writeAtomic } from \"../core/markdown-store-fs.js\";\n\nexport type ProjectMcpServer = {\n command: string;\n args: string[];\n env?: Record<string, string>;\n};\n\nexport type ProjectMcpConfig = {\n mcpServers?: Record<string, ProjectMcpServer>;\n};\n\nexport type WriteProjectMcpConfigOptions = {\n repositoryRoot: string;\n namespace: string;\n graphify: boolean;\n graphPath?: string;\n};\n\n/** Add CodeBuddy and, optionally, Graphify to the project's MCP config. */\nexport async function writeProjectMcpConfig(\n options: WriteProjectMcpConfigOptions,\n): Promise<{ path: string; servers: string[] }> {\n const path = join(options.repositoryRoot, \".mcp.json\");\n const current = await readProjectMcpConfig(path);\n current.mcpServers ??= {};\n current.mcpServers[`codebuddy-${options.namespace}`] = codebuddyMcpServer(\n options.repositoryRoot,\n options.namespace,\n );\n if (options.graphify) {\n current.mcpServers.graphify = {\n command: \"python\",\n args: [\n \"-m\",\n \"graphify.serve\",\n resolve(options.repositoryRoot, options.graphPath ?? \"graphify-out/graph.json\"),\n ],\n env: { CODEBUDDY_PROJECT_ROOT: options.repositoryRoot },\n };\n } else if (current.mcpServers.graphify) {\n delete current.mcpServers.graphify;\n }\n await writeAtomic(path, `${JSON.stringify(current, null, 2)}\\n`, { mode: 0o644 });\n return { path, servers: Object.keys(current.mcpServers).sort() };\n}\n\n/**\n * Project-local MCP config shapes: the common `mcpServers` map (Claude Code\n * `.mcp.json`, Freebuff `.agents/mcp.json`) and OpenCode's `mcp` map of\n * `{ type: \"local\", command: [...], environment }`.\n */\nexport type McpConfigFormat = \"mcpServers\" | \"opencode\";\n\nconst OPENCODE_SCHEMA = \"https://opencode.ai/config.json\";\n\nexport function codebuddyMcpServer(repositoryRoot: string, namespace: string): ProjectMcpServer {\n return {\n command: \"codebuddy\",\n args: [\"serve\"],\n env: {\n CODEBUDDY_PROJECT_ROOT: repositoryRoot,\n CODEBUDDY_NAMESPACE: namespace,\n CODEBUDDY_TOOL_PROFILE: \"lean\",\n },\n };\n}\n\n/**\n * The config file to use: OpenCode reads `opencode.jsonc` too, so when only\n * that one exists it is edited instead of creating a second `opencode.json`.\n */\nexport async function projectMcpPath(\n root: string,\n file: string,\n format: McpConfigFormat,\n): Promise<string> {\n const path = join(root, file);\n if (format !== \"opencode\" || (await exists(path))) return path;\n return (await exists(`${path}c`)) ? `${path}c` : path;\n}\n\nasync function exists(path: string): Promise<boolean> {\n return access(path).then(\n () => true,\n () => false,\n );\n}\n\nexport function formatEntry(format: McpConfigFormat, server: ProjectMcpServer): unknown {\n if (format === \"mcpServers\") return server;\n return {\n type: \"local\",\n command: [server.command, ...server.args],\n ...(server.env ? { environment: server.env } : {}),\n enabled: true,\n };\n}\n\nfunction serversKey(format: McpConfigFormat): \"mcpServers\" | \"mcp\" {\n return format === \"opencode\" ? \"mcp\" : \"mcpServers\";\n}\n\n/**\n * Add or update one named server, keeping every other key the user has. Idempotent.\n * \"manual\" = the file has comments a rewrite would destroy, so nothing was written.\n */\nexport async function upsertMcpServer(\n path: string,\n format: McpConfigFormat,\n name: string,\n server: ProjectMcpServer,\n): Promise<\"created\" | \"updated\" | \"unchanged\" | \"manual\"> {\n const existed = await exists(path);\n const { config, comments } = await readConfig(path);\n const key = serversKey(format);\n const servers = { ...((config[key] as Record<string, unknown> | undefined) ?? {}) };\n const entry = formatEntry(format, server);\n if (JSON.stringify(servers[name]) === JSON.stringify(entry)) return \"unchanged\";\n if (comments) return \"manual\";\n servers[name] = entry;\n const next =\n format === \"opencode\" && !existed\n ? { $schema: OPENCODE_SCHEMA, ...config, [key]: servers }\n : { ...config, [key]: servers };\n await mkdir(dirname(path), { recursive: true });\n await writeAtomic(path, `${JSON.stringify(next, null, 2)}\\n`, { mode: 0o644 });\n return existed ? \"updated\" : \"created\";\n}\n\n/** Remove one named server; delete the file if CodeBuddy's entry was all it held. */\nexport async function removeMcpServer(\n path: string,\n format: McpConfigFormat,\n name: string,\n): Promise<\"removed\" | \"unchanged\" | \"manual\"> {\n const { config, comments } = await readConfig(path);\n const key = serversKey(format);\n const servers = { ...((config[key] as Record<string, unknown> | undefined) ?? {}) };\n if (!(name in servers)) return \"unchanged\";\n if (comments) return \"manual\";\n delete servers[name];\n const next = { ...config, [key]: servers };\n const leftover = Object.keys(next).filter((k) => k !== key && k !== \"$schema\");\n if (Object.keys(servers).length === 0 && leftover.length === 0) await rm(path, { force: true });\n else await writeAtomic(path, `${JSON.stringify(next, null, 2)}\\n`, { mode: 0o644 });\n return \"removed\";\n}\n\nexport async function hasMcpServer(\n path: string,\n format: McpConfigFormat,\n name: string,\n): Promise<boolean> {\n const { config } = await readConfig(path);\n return name in ((config[serversKey(format)] as Record<string, unknown> | undefined) ?? {});\n}\n\n/**\n * Parse JSON with comments and trailing commas (OpenCode accepts both in\n * opencode.json and opencode.jsonc). Strings match first, so `//` or `,}`\n * inside a string value are left alone.\n */\nexport function parseJsonc(raw: string): { value: unknown; comments: boolean } {\n let comments = false;\n const json = raw\n .replace(/\"(?:[^\"\\\\]|\\\\.)*\"|\\/\\/[^\\n]*|\\/\\*[\\s\\S]*?\\*\\//g, (match) => {\n if (match.startsWith('\"')) return match;\n comments = true;\n return \" \";\n })\n .replace(/\"(?:[^\"\\\\]|\\\\.)*\"|,(\\s*[}\\]])/g, (match, tail: string | undefined) => tail ?? match);\n return { value: JSON.parse(json), comments };\n}\n\nasync function readConfig(\n path: string,\n): Promise<{ config: Record<string, unknown>; comments: boolean }> {\n try {\n const { value, comments } = parseJsonc(await readFile(path, \"utf8\"));\n const config = value && typeof value === \"object\" ? (value as Record<string, unknown>) : {};\n return { config, comments };\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") return { config: {}, comments: false };\n throw new Error(`Could not read ${path}: ${(error as Error).message}`);\n }\n}\n\nasync function readProjectMcpConfig(path: string): Promise<ProjectMcpConfig> {\n return (await readConfig(path)).config as ProjectMcpConfig;\n}\n","import { readFile, rm } from \"node:fs/promises\";\nimport { join, resolve } from \"node:path\";\nimport type { AutomationMode } from \"../clients/adapters.js\";\nimport { writeAtomic } from \"../core/markdown-store-fs.js\";\nimport { rulesFileHooked } from \"../hooks/install.js\";\nimport {\n defaultRulesFile,\n parseWorkflowMode,\n renderWorkflowTemplate,\n type WorkflowClient,\n} from \"./workflow.js\";\n\n/**\n * Safe, visible install of the workflow template into a client's Markdown rules\n * file. The template is written inside HTML-comment markers so:\n * - updates are idempotent (the block is replaced, not appended again);\n * - the user's own content around the block is preserved untouched;\n * - the markers make it obvious exactly what CodeBuddy manages.\n * No hidden mutation: the caller sees the resolved path and the action taken.\n */\n\nconst START_MARKER = \"<!-- codebuddy:workflow:start -->\";\nconst END_MARKER = \"<!-- codebuddy:workflow:end -->\";\n\nexport type InstallAction = \"created\" | \"added\" | \"updated\" | \"removed\" | \"unchanged\";\n\nexport type InstallResult = {\n path: string;\n client: WorkflowClient;\n action: InstallAction;\n};\n\nexport type InstallRulesOptions = {\n client: WorkflowClient;\n projectRoot?: string;\n /** Override the target file (defaults to CLAUDE.md / AGENTS.md). */\n targetPath?: string;\n /** `mcp-only` removes the managed block. Defaults to `defaultRulesMode`. */\n mode?: AutomationMode;\n /** Only rewrite an existing managed block; never add one (mode reconciliation). */\n onlyIfManaged?: boolean;\n};\n\nexport function renderManagedBlock(\n client: WorkflowClient,\n mode: Exclude<AutomationMode, \"mcp-only\"> = \"agent\",\n): string {\n return `${START_MARKER}\\n${renderWorkflowTemplate(client, mode)}\\n${END_MARKER}\\n`;\n}\n\nexport async function installWorkflowRules(options: InstallRulesOptions): Promise<InstallResult> {\n const projectRoot = resolve(options.projectRoot ?? process.cwd());\n const path = options.targetPath ?? join(projectRoot, defaultRulesFile(options.client));\n\n const existing = await readIfPresent(path);\n const mode = options.mode ?? (await defaultRulesMode(options.client, projectRoot));\n if (options.onlyIfManaged && (existing === null || !hasManagedBlock(existing))) {\n return { path, client: options.client, action: \"unchanged\" };\n }\n if (mode === \"mcp-only\") {\n const content = existing === null ? null : removeBlock(existing);\n if (content === null || content === existing) {\n return { path, client: options.client, action: \"unchanged\" };\n }\n // Leave no empty file behind when CodeBuddy's block was all it held.\n if (content.trim() === \"\") await rm(path, { force: true });\n else await writeAtomic(path, content, { mode: 0o644 });\n return { path, client: options.client, action: \"removed\" };\n }\n const { content, action } = upsertBlock(existing, renderManagedBlock(options.client, mode));\n\n if (action === \"unchanged\") return { path, client: options.client, action };\n // Atomic: a crash mid-write must never truncate the user's CLAUDE.md/AGENTS.md.\n await writeAtomic(path, content, { mode: 0o644 });\n return { path, client: options.client, action };\n}\n\n/** Compute the new file content and what changed, without touching disk. */\nexport function upsertBlock(\n existing: string | null,\n block: string,\n): { content: string; action: InstallAction } {\n if (existing === null) return { content: block, action: \"created\" };\n\n const start = existing.indexOf(START_MARKER);\n const end = existing.indexOf(END_MARKER);\n if (start !== -1 && end !== -1 && end > start) {\n const before = existing.slice(0, start);\n const after = existing.slice(end + END_MARKER.length).replace(/^\\n/, \"\");\n const replaced = `${before}${block}${after}`;\n return { content: replaced, action: replaced === existing ? \"unchanged\" : \"updated\" };\n }\n\n // No managed block yet — append, keeping the user's content intact.\n const separator =\n existing.length === 0 || existing.endsWith(\"\\n\\n\")\n ? \"\"\n : existing.endsWith(\"\\n\")\n ? \"\\n\"\n : \"\\n\\n\";\n return { content: `${existing}${separator}${block}`, action: \"added\" };\n}\n\n/**\n * Rules follow the installed hooks: hook-driven when CodeBuddy hooks are present\n * for any client reading this rules file, agent-driven otherwise (the hook-driven\n * block tells hookless agents to use the agent flow). Every caller that installs rules\n * without an explicit mode (rules install, `codebuddy use`) gets this, so a\n * re-run never re-adds agent-driven calls the hooks already make.\n */\nexport async function defaultRulesMode(\n client: WorkflowClient,\n projectRoot = process.cwd(),\n): Promise<\"agent\" | \"hooks\"> {\n return (await rulesFileHooked(projectRoot, client)) ? \"hooks\" : \"agent\";\n}\n\n/** The mode of the managed block in a rules file, or null when there is none. */\nexport async function readManagedMode(path: string): Promise<AutomationMode | null> {\n const existing = await readIfPresent(path);\n if (existing === null || !hasManagedBlock(existing)) return null;\n return parseWorkflowMode(existing.slice(existing.indexOf(START_MARKER)));\n}\n\nfunction hasManagedBlock(content: string): boolean {\n const start = content.indexOf(START_MARKER);\n return start !== -1 && content.indexOf(END_MARKER) > start;\n}\n\n/** Drop the managed block (and the blank line that separated it), keeping user text. */\nexport function removeBlock(existing: string): string {\n if (!hasManagedBlock(existing)) return existing;\n const start = existing.indexOf(START_MARKER);\n const end = existing.indexOf(END_MARKER) + END_MARKER.length;\n const before = existing.slice(0, start).replace(/\\n\\n$/, \"\\n\");\n const after = existing.slice(end).replace(/^\\n/, \"\");\n return `${before}${after}`;\n}\n\nasync function readIfPresent(path: string): Promise<string | null> {\n try {\n return await readFile(path, \"utf8\");\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") return null;\n throw error;\n }\n}\n","/**\n * Client workflow templates (Proposal 04 Phase 04.7). These are the concrete\n * rules that make CodeBuddy's automatic context flow reliable in Claude Code and\n * Codex: they say which MCP tool to call, when, and what to do when a tool or\n * service is unavailable.\n *\n * Templates are installed into a client's Markdown rules file inside a clearly\n * delimited managed block (see `install.ts`) so updates are idempotent and the\n * user can always see exactly what CodeBuddy added.\n *\n * The automation mode decides who drives retrieval and capture. Agent-driven\n * rules ask the model to call the tools; hook-driven rules defer to the\n * lifecycle hooks so a turn is not retrieved or captured twice.\n */\n\nimport {\n type AutomationMode,\n CLIENT_ADAPTERS,\n CLIENT_IDS,\n type ClientId,\n} from \"../clients/adapters.js\";\n\nexport const WORKFLOW_CLIENTS = CLIENT_IDS;\nexport type WorkflowClient = ClientId;\n\n/**\n * The block is read on every turn of every session, so it stays a few rules\n * (benchmark v3: the previous 757-token block added cost and changed no\n * behavior). Anything longer belongs in docs or tool descriptions.\n */\nconst HOOKS_RULES = [\n \"- The prompt's `CodeBuddy` block names likely files and line ranges: read those ranges\",\n \" first (ranged `Read` or `sed -n`); search only if they do not fit.\",\n \"- Call `context_pack` only when it names no files. Hooks inject and capture: never call\",\n \" `context_bootstrap` or `context_after_turn`.\",\n \"- `recall` answers project-convention questions.\",\n];\n\nconst AGENT_RULES = [\n \"- In unfamiliar code, call `context_pack` once with the task; read its line ranges first.\",\n \"- `recall` answers project-convention questions; `remember` durable facts (never secrets).\",\n \"- After changing code, call `context_after_turn` (summary, changed files) if listed.\",\n];\n\nconst TAIL = \"- CodeBuddy unavailable (degraded mode): use the repository directly.\";\n\nfunction renderBody(mode: Exclude<AutomationMode, \"mcp-only\">): string {\n return [...(mode === \"hooks\" ? HOOKS_RULES : AGENT_RULES), TAIL].join(\"\\n\");\n}\n\n/** Clients whose rules live in the same file share one managed block. */\nfunction rulesReaders(file: string): string {\n return CLIENT_IDS.filter((id) => CLIENT_ADAPTERS[id].rulesFile === file)\n .map((id) => CLIENT_ADAPTERS[id].name)\n .join(\", \");\n}\n\n/**\n * Render the full template (heading + body). The text depends only on the\n * rules file and mode, so clients sharing AGENTS.md install an identical block.\n */\nexport function renderWorkflowTemplate(\n client: WorkflowClient,\n mode: Exclude<AutomationMode, \"mcp-only\"> = \"agent\",\n): string {\n const file = CLIENT_ADAPTERS[client].rulesFile;\n return [\n `# CodeBuddy workflow (${rulesReaders(file)})`,\n \"\",\n `Managed by CodeBuddy; edit outside the markers. Automation mode: \\`${mode}\\`.`,\n \"\",\n renderBody(mode),\n ].join(\"\\n\");\n}\n\n/** The mode recorded in a rendered block; blocks from before modes existed are agent-driven. */\nexport function parseWorkflowMode(block: string): AutomationMode {\n return block.match(/Automation mode: `(hooks|agent)`/)?.[1] === \"hooks\" ? \"hooks\" : \"agent\";\n}\n\nexport function defaultRulesFile(client: WorkflowClient): string {\n return CLIENT_ADAPTERS[client].rulesFile;\n}\n\nexport function clientDisplayName(client: WorkflowClient): string {\n return CLIENT_ADAPTERS[client].name;\n}\n","import { existsSync } from \"node:fs\";\nimport { mkdir, readFile } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport { writeAtomic } from \"../../core/markdown-store-fs.js\";\nimport { buildEntry, type ClaudeDesktopEntry } from \"./claude-desktop.js\";\n\nexport type CodexConfigEntry = ClaudeDesktopEntry;\n\nexport function codexConfigPath(home = homedir()): string {\n return join(home, \".codex\", \"config.toml\");\n}\n\nexport async function readCodexConfig(path: string): Promise<string> {\n if (!existsSync(path)) return \"\";\n return readFile(path, \"utf8\");\n}\n\nexport async function writeCodexConfig(path: string, content: string): Promise<void> {\n await mkdir(dirname(path), { recursive: true });\n // Atomic + 0600: the TOML carries env secrets (HF token, DATABASE_URL).\n await writeAtomic(path, content, { mode: 0o600 });\n}\n\nexport type InstallCodexEntryOptions = {\n namespace: string;\n projectRoot?: string;\n hfToken?: string;\n databaseUrl?: string;\n serverName?: string;\n toolProfile?: \"lean\" | \"core\" | \"full\";\n};\n\nexport type InstallCodexResult = {\n path: string;\n serverKey: string;\n created: boolean;\n changed: boolean;\n};\n\nexport async function installCodexEntry(\n options: InstallCodexEntryOptions,\n): Promise<InstallCodexResult> {\n const path = codexConfigPath();\n const serverKey = options.serverName ?? `codebuddy-${options.namespace}`;\n const current = await readCodexConfig(path);\n const existed = hasServerBlock(current, serverKey);\n const entry = buildEntry(options);\n if (options.toolProfile)\n entry.env = { ...entry.env, CODEBUDDY_TOOL_PROFILE: options.toolProfile };\n const next = upsertServerBlock(current, serverKey, entry);\n if (next !== current) await writeCodexConfig(path, next);\n return { path, serverKey, created: !existed, changed: next !== current };\n}\n\nexport async function removeCodexEntry(\n serverKey: string,\n): Promise<{ removed: boolean; path: string }> {\n const path = codexConfigPath();\n const current = await readCodexConfig(path);\n const next = removeServerBlock(current, serverKey);\n const removed = next !== current;\n // Drop the blank separator install added, so install + remove is a no-op.\n const trimmed = next.trimEnd();\n if (removed) await writeCodexConfig(path, trimmed ? `${trimmed}\\n` : \"\");\n return { removed, path };\n}\n\nexport async function listCodexEntries(): Promise<{\n path: string;\n entries: Array<{ key: string; namespace: string | null; databaseUrl: string | null }>;\n}> {\n const path = codexConfigPath();\n const config = await readCodexConfig(path);\n const entries = Array.from(config.matchAll(/^\\[mcp_servers\\.\"([^\"]+)\"\\]\\s*$/gm))\n .flatMap((match) => (match[1] ? [match[1]] : []))\n .filter((key) => key === \"codebuddy\" || key.startsWith(\"codebuddy-\"))\n .map((key) => {\n const block = getServerBlock(config, key);\n return {\n key,\n namespace: readEnvValue(block, \"CODEBUDDY_NAMESPACE\"),\n databaseUrl: readEnvValue(block, \"DATABASE_URL\"),\n };\n });\n return { path, entries };\n}\n\nfunction upsertServerBlock(config: string, serverKey: string, entry: CodexConfigEntry): string {\n const trimmed = removeServerBlock(config, serverKey).trimEnd();\n const block = formatServerBlock(serverKey, entry);\n return `${trimmed}${trimmed ? \"\\n\\n\" : \"\"}${block}\\n`;\n}\n\nfunction removeServerBlock(config: string, serverKey: string): string {\n const escaped = escapeRegExp(serverKey);\n const end = String.raw`(?![\\s\\S])`;\n const sectionPattern = String.raw`^\\[mcp_servers\\.\"${escaped}\"\\][\\s\\S]*?(?=^\\[[^\\]]+\\]\\s*$|${end})`;\n const envPattern = String.raw`^\\[mcp_servers\\.\"${escaped}\"\\.env\\][\\s\\S]*?(?=^\\[[^\\]]+\\]\\s*$|${end})`;\n return config\n .replace(new RegExp(sectionPattern, \"gm\"), \"\")\n .replace(new RegExp(envPattern, \"gm\"), \"\");\n}\n\nfunction getServerBlock(config: string, serverKey: string): string {\n const escaped = escapeRegExp(serverKey);\n const end = String.raw`(?![\\s\\S])`;\n const pattern = String.raw`^\\[mcp_servers\\.\"${escaped}\"\\][\\s\\S]*?(?=^\\[mcp_servers\\.\"${escaped}\"\\.env\\][\\s\\S]*?(?=^\\[[^\\]]+\\]\\s*$|${end})|^\\[[^\\]]+\\]\\s*$|${end})`;\n const server = config.match(new RegExp(pattern, \"m\"))?.[0] ?? \"\";\n const env =\n config.match(\n new RegExp(\n String.raw`^\\[mcp_servers\\.\"${escaped}\"\\.env\\][\\s\\S]*?(?=^\\[[^\\]]+\\]\\s*$|${end})`,\n \"m\",\n ),\n )?.[0] ?? \"\";\n return `${server}\\n${env}`;\n}\n\nfunction hasServerBlock(config: string, serverKey: string): boolean {\n return new RegExp(String.raw`^\\[mcp_servers\\.\"${escapeRegExp(serverKey)}\"\\]\\s*$`, \"m\").test(\n config,\n );\n}\n\nfunction formatServerBlock(serverKey: string, entry: CodexConfigEntry): string {\n const lines = [\n `[mcp_servers.${tomlKey(serverKey)}]`,\n `command = ${tomlString(entry.command)}`,\n `args = ${tomlArray(entry.args)}`,\n ];\n if (entry.env && Object.keys(entry.env).length > 0) {\n lines.push(\"\", `[mcp_servers.${tomlKey(serverKey)}.env]`);\n for (const [key, value] of Object.entries(entry.env).sort(([left], [right]) =>\n left.localeCompare(right),\n )) {\n lines.push(`${key} = ${tomlString(value)}`);\n }\n }\n return lines.join(\"\\n\");\n}\n\nfunction readEnvValue(block: string, key: string): string | null {\n return block.match(new RegExp(`^${escapeRegExp(key)}\\\\s*=\\\\s*\"([^\"]*)\"`, \"m\"))?.[1] ?? null;\n}\n\nfunction tomlKey(key: string): string {\n return `\"${key.replaceAll(\"\\\\\", \"\\\\\\\\\").replaceAll('\"', '\\\\\"')}\"`;\n}\n\nfunction tomlString(value: string): string {\n return JSON.stringify(value);\n}\n\nfunction tomlArray(values: string[]): string {\n return `[${values.map(tomlString).join(\", \")}]`;\n}\n\nfunction escapeRegExp(value: string): string {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n","import { existsSync } from \"node:fs\";\nimport { mkdir, readFile } from \"node:fs/promises\";\nimport { dirname, join, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { writeAtomic } from \"../../core/markdown-store-fs.js\";\n\nexport type ClaudeDesktopEntry = {\n command: string;\n args: string[];\n env?: Record<string, string>;\n};\n\nexport type ClaudeDesktopConfig = {\n mcpServers?: Record<string, ClaudeDesktopEntry>;\n};\n\nexport function claudeDesktopConfigPath(home = process.env.HOME ?? \"\"): string {\n if (process.platform === \"darwin\") {\n return join(home, \"Library\", \"Application Support\", \"Claude\", \"claude_desktop_config.json\");\n }\n if (process.platform === \"win32\") {\n return join(process.env.APPDATA ?? home, \"Claude\", \"claude_desktop_config.json\");\n }\n return join(home, \".config\", \"Claude\", \"claude_desktop_config.json\");\n}\n\nexport async function readClaudeConfig(path: string): Promise<ClaudeDesktopConfig> {\n if (!existsSync(path)) return {};\n try {\n return JSON.parse(await readFile(path, \"utf8\")) as ClaudeDesktopConfig;\n } catch (error) {\n throw new Error(\n `Failed to parse Claude Desktop config at ${path}: ${(error as Error).message}`,\n );\n }\n}\n\nexport async function writeClaudeConfig(path: string, config: ClaudeDesktopConfig): Promise<void> {\n await mkdir(dirname(path), { recursive: true });\n // Atomic + 0600: this config carries env secrets (HF token, DATABASE_URL).\n // A crash mid-write must never truncate the user's other MCP entries.\n await writeAtomic(path, `${JSON.stringify(config, null, 2)}\\n`, { mode: 0o600 });\n}\n\n/**\n * Resolve the absolute path to this codebuddy install's CLI entrypoint.\n *\n * Has to cope with three layouts:\n * - dev: `tsx src/cli/index.ts` — argv[1] is the .ts file\n * - built locally: `node dist/cli/index.js serve` — argv[1] is dist/cli/index.js\n * - npm bin: `/usr/local/bin/codebuddy ...` — argv[1] is the resolved bin\n *\n * `process.argv[1]` is the file Node was invoked with, which IS the CLI entry\n * in every supported case. We use it as the source of truth. import.meta.url\n * is unreliable here because tsup bundles src/cli/* into a single dist file,\n * so the source-relative path math (`..`) lands on the SDK barrel\n * (dist/index.js) instead of the CLI (dist/cli/index.js).\n */\nexport function resolveCliEntrypoint(): string {\n const fromArgv = process.argv[1];\n if (fromArgv && existsSync(fromArgv)) return fromArgv;\n\n // Fallback for the unlikely case where argv[1] is missing (eval'd, embedded\n // host, etc): walk the source layout.\n const here = dirname(fileURLToPath(import.meta.url));\n const candidates = [\n resolve(here, \"index.js\"), // bundled: here = dist/cli/\n resolve(here, \"..\", \"cli\", \"index.js\"), // source: here = src/cli/commands/\n resolve(here, \"..\", \"index.js\"),\n ];\n for (const candidate of candidates) {\n if (existsSync(candidate)) return candidate;\n }\n // Last resort — return argv[1] even if it doesn't exist, so the error\n // surfaces with the real path the caller saw rather than a stale guess.\n return fromArgv ?? candidates[0] ?? \"\";\n}\n\nexport type InstallEntryOptions = {\n namespace: string;\n projectRoot?: string;\n hfToken?: string;\n groqApiKey?: string;\n databaseUrl?: string;\n serverName?: string;\n};\n\nexport function buildEntry(options: InstallEntryOptions): ClaudeDesktopEntry {\n const env: Record<string, string> = {\n CODEBUDDY_NAMESPACE: options.namespace,\n CODEBUDDY_PROJECT_ROOT: resolve(options.projectRoot ?? process.cwd()),\n };\n if (options.hfToken) env.HF_TOKEN = options.hfToken;\n if (options.groqApiKey) env.GROQ_API_KEY = options.groqApiKey;\n if (options.databaseUrl) env.DATABASE_URL = options.databaseUrl;\n return {\n // Absolute: Claude Desktop is a GUI app and does not inherit the shell's\n // PATH (nvm, Homebrew), so a bare \"node\" may not resolve or may be another\n // version. Re-run `codebuddy claude install` after switching Node.\n command: process.execPath,\n args: [resolveCliEntrypoint(), \"serve\"],\n env,\n };\n}\n\nexport type InstallResult = {\n path: string;\n serverKey: string;\n created: boolean;\n};\n\nexport async function installClaudeEntry(options: InstallEntryOptions): Promise<InstallResult> {\n const path = claudeDesktopConfigPath();\n const config = await readClaudeConfig(path);\n config.mcpServers ??= {};\n const serverKey = options.serverName ?? `codebuddy-${options.namespace}`;\n const existed = config.mcpServers[serverKey] !== undefined;\n config.mcpServers[serverKey] = buildEntry(options);\n await writeClaudeConfig(path, config);\n return { path, serverKey, created: !existed };\n}\n\nexport async function removeClaudeEntry(\n serverKey: string,\n): Promise<{ removed: boolean; path: string }> {\n const path = claudeDesktopConfigPath();\n const config = await readClaudeConfig(path);\n if (!config.mcpServers || !(serverKey in config.mcpServers)) {\n return { removed: false, path };\n }\n delete config.mcpServers[serverKey];\n await writeClaudeConfig(path, config);\n return { removed: true, path };\n}\n\nexport async function listClaudeEntries(): Promise<{\n path: string;\n entries: Array<{ key: string; namespace: string | null; databaseUrl: string | null }>;\n}> {\n const path = claudeDesktopConfigPath();\n const config = await readClaudeConfig(path);\n const entries = Object.entries(config.mcpServers ?? {})\n .filter(([key]) => key === \"codebuddy\" || key.startsWith(\"codebuddy-\"))\n .map(([key, entry]) => ({\n key,\n namespace: entry.env?.CODEBUDDY_NAMESPACE ?? null,\n databaseUrl: entry.env?.DATABASE_URL ?? null,\n }));\n return { path, entries };\n}\n","import { mkdir, readFile } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport { isDeepStrictEqual } from \"node:util\";\nimport { parse as parseYaml, stringify as stringifyYaml } from \"yaml\";\nimport { writeAtomic } from \"../core/markdown-store-fs.js\";\nimport type { ProjectMcpServer } from \"./project-mcp.js\";\n\n/**\n * Key-scoped edits of Hermes' global `config.yaml`. A YAML round-trip would\n * refold the user's long strings and drop formatting, so only the lines of\n * one `mcp_servers.<name>` entry are rewritten; every other line and comment\n * stays byte-for-byte. Each edit is re-parsed and must equal the old config\n * with exactly that entry changed, otherwise nothing is written (\"manual\").\n */\n\n/** Hermes honours HERMES_HOME, like `hermes` itself. */\nexport function hermesConfigPath(home = homedir()): string {\n return join(process.env.HERMES_HOME ?? join(home, \".hermes\"), \"config.yaml\");\n}\n\ntype Config = Record<string, unknown>;\n\nasync function readRaw(path: string): Promise<string> {\n try {\n return await readFile(path, \"utf8\");\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") return \"\";\n throw error;\n }\n}\n\n/** The parsed top-level map, or null when the file is not a YAML map CodeBuddy can reason about. */\nfunction parseConfig(raw: string): Config | null {\n try {\n const value = parseYaml(raw) ?? {};\n return value && typeof value === \"object\" && !Array.isArray(value) ? (value as Config) : null;\n } catch {\n return null;\n }\n}\n\nfunction servers(config: Config): Record<string, unknown> {\n const value = config.mcp_servers;\n return value && typeof value === \"object\" ? (value as Record<string, unknown>) : {};\n}\n\nconst isItem = (line: string) => line.trim() !== \"\" && !line.trim().startsWith(\"#\");\nconst indentOf = (line: string) => line.length - line.trimStart().length;\n\n/** Text edit of `mcp_servers.<name>`; `entry` null removes it. */\nexport function editMcpServers(raw: string, name: string, entry: unknown | null): string {\n const lines = raw.split(\"\\n\");\n const header = lines.findIndex((line) => /^mcp_servers:\\s*(#.*)?$/.test(line));\n const render = (indent: number) =>\n stringifyYaml({ [name]: entry })\n .trimEnd()\n .split(\"\\n\")\n .map((line) => `${\" \".repeat(indent)}${line}`);\n\n if (header === -1) {\n if (entry === null) return raw;\n // No separator line, so removing the entry restores the file byte-for-byte.\n const body = raw === \"\" || raw.endsWith(\"\\n\") ? raw : `${raw}\\n`;\n return `${body}mcp_servers:\\n${render(2).join(\"\\n\")}\\n`;\n }\n // The block runs until the next line that starts at column 0.\n let end = header + 1;\n while (end < lines.length && !/^\\S/.test(lines[end] ?? \"\")) end++;\n const block = Array.from({ length: end - header - 1 }, (_, i) => header + 1 + i);\n const items = block.filter((i) => isItem(lines[i] ?? \"\"));\n const indent = items.length > 0 ? indentOf(lines[items[0] as number] ?? \"\") : 2;\n const quoted = name.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n const keyLine = new RegExp(`^ {${indent}}(?:${quoted}|\"${quoted}\"|'${quoted}'):\\\\s*(#.*)?$`);\n const start = items.find((i) => keyLine.test(lines[i] ?? \"\"));\n\n if (start === undefined) {\n if (entry === null) return raw;\n const at = (items.at(-1) ?? header) + 1;\n lines.splice(at, 0, ...render(indent));\n return lines.join(\"\\n\");\n }\n // Our entry ends at the next sibling key or sibling-level comment (the user's).\n const next = block.find(\n (i) => i > start && (lines[i] ?? \"\").trim() !== \"\" && indentOf(lines[i] ?? \"\") <= indent,\n );\n let stop = next ?? end;\n while (stop > start + 1 && (lines[stop - 1] ?? \"\").trim() === \"\") stop--;\n lines.splice(start, stop - start, ...(entry === null ? [] : render(indent)));\n // Removing the only entry leaves `mcp_servers:` null; drop the header as well.\n if (entry === null && items.length > 0 && items.every((i) => i >= start && i < stop)) {\n lines.splice(header, 1);\n }\n return lines.join(\"\\n\");\n}\n\nexport type HermesMcpAction = \"created\" | \"updated\" | \"unchanged\" | \"removed\" | \"manual\";\n\n/** Add, update (entry) or remove (null) CodeBuddy's server; never touches anything else. */\nexport async function setHermesMcpServer(\n path: string,\n name: string,\n entry: ProjectMcpServer | null,\n): Promise<HermesMcpAction> {\n const raw = await readRaw(path);\n const before = parseConfig(raw);\n if (!before) return \"manual\";\n const current = servers(before)[name];\n if (entry === null ? current === undefined : isDeepStrictEqual(current, entry)) {\n return \"unchanged\";\n }\n const nextServers = { ...servers(before) };\n if (entry === null) delete nextServers[name];\n else nextServers[name] = entry;\n const { mcp_servers: _, ...rest } = before;\n const expected =\n Object.keys(nextServers).length > 0 ? { ...rest, mcp_servers: nextServers } : rest;\n\n const next = editMcpServers(raw, name, entry);\n if (!isDeepStrictEqual(parseConfig(next), expected)) return \"manual\";\n await mkdir(dirname(path), { recursive: true });\n // Atomic + 0600: Hermes keeps provider keys in this file.\n await writeAtomic(path, next, { mode: 0o600 });\n return entry === null ? \"removed\" : current === undefined ? \"created\" : \"updated\";\n}\n\nexport async function readHermesConfig(path: string): Promise<Config | null> {\n return parseConfig(await readRaw(path));\n}\n\nexport async function hasHermesMcpServer(path: string, name: string): Promise<boolean> {\n const config = await readHermesConfig(path);\n return config !== null && name in servers(config);\n}\n","import {\n codexConfigPath,\n installCodexEntry,\n listCodexEntries,\n removeCodexEntry,\n} from \"../cli/commands/codex.js\";\nimport {\n hasHermesMcpServer,\n hermesConfigPath,\n setHermesMcpServer,\n} from \"../integrations/hermes-config.js\";\nimport { codebuddyMcpServer } from \"../integrations/project-mcp.js\";\n\n/**\n * The CodeBuddy server entry in a client's global MCP config, written only on\n * explicit opt-in (`clients install|uninstall --global`). Codex reuses the\n * `codebuddy codex install` TOML editor; Hermes gets a key-scoped YAML edit.\n */\nexport type GlobalMcp = \"codex\" | \"hermes\";\n\nexport function globalMcpPath(target: GlobalMcp): string {\n return target === \"codex\" ? codexConfigPath() : hermesConfigPath();\n}\n\nexport async function setGlobalMcp(\n target: GlobalMcp,\n options: { name: string; root: string; namespace: string; enabled: boolean },\n): Promise<string> {\n const { name, root, namespace, enabled } = options;\n if (target === \"hermes\") {\n return setHermesMcpServer(\n hermesConfigPath(),\n name,\n enabled ? codebuddyMcpServer(root, namespace) : null,\n );\n }\n if (!enabled) return (await removeCodexEntry(name)).removed ? \"removed\" : \"unchanged\";\n const result = await installCodexEntry({\n namespace,\n projectRoot: root,\n serverName: name,\n toolProfile: \"lean\",\n });\n return result.created ? \"created\" : result.changed ? \"updated\" : \"unchanged\";\n}\n\nexport async function hasGlobalMcp(target: GlobalMcp, name: string): Promise<boolean> {\n if (target === \"hermes\") return hasHermesMcpServer(hermesConfigPath(), name);\n return (await listCodexEntries()).entries.some((entry) => entry.key === name);\n}\n"],"mappings":"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","names":["access","join","resolve","join","block","join","hooks","readFile","dirname","join","resolve","mkdir","readFile","dirname","readFile","plugin","mkdir","dirname","join","resolve","dirname","readFile","readFile","dirname","join","resolve","join","resolve","plugin","dirname","readFile","mkdir","readFile","rm","dirname","join","resolve","join","resolve","servers","mkdir","dirname","rm","readFile","readFile","rm","join","resolve","resolve","join","content","rm","readFile","existsSync","mkdir","readFile","dirname","join","mkdir","readFile","dirname","join","resolve","join","readFile","mkdir","dirname","resolve","join","existsSync","readFile","mkdir","dirname","mkdir","readFile","homedir","dirname","join","homedir","join","readFile","indent","mkdir","dirname","join","access","resolve"]}
@@ -1,27 +1,23 @@
1
- import {
2
- failSoft,
3
- readHookInput,
4
- writeStdout
5
- } from "./chunk-FJOTP632.js";
6
1
  import {
7
2
  SessionCapsuleLedger,
8
3
  detectTestCommand,
9
4
  estimateTokens,
10
5
  findCoveringTest
11
- } from "./chunk-GKNYMNFM.js";
6
+ } from "./chunk-4DKYKDHI.js";
12
7
  import {
13
8
  hookRoot
14
9
  } from "./chunk-TGFTKWOR.js";
10
+ import {
11
+ skipPromptRetrieval
12
+ } from "./chunk-GQKWUHMA.js";
15
13
  import {
16
14
  NON_SOURCE_PATH,
17
15
  discoverTaskPaths,
16
+ loadFreshIndex,
18
17
  loadProjectArchitectureMap,
19
18
  readBoundedSource,
20
19
  taskTerms
21
- } from "./chunk-TLWGZVPC.js";
22
- import {
23
- loadFreshIndex
24
- } from "./chunk-ITQO632W.js";
20
+ } from "./chunk-YNSTIMCF.js";
25
21
  import {
26
22
  loadKnownSecretValues,
27
23
  redactSourceText
@@ -41,6 +37,7 @@ var LEAN_LIMITS = {
41
37
  var LEAN_HOOK_BUDGET_MS = 1500;
42
38
  var RENDER_RESERVE_MS = 300;
43
39
  async function runLeanHook(input, emit, budgetMs = LEAN_HOOK_BUDGET_MS) {
40
+ if (skipPromptRetrieval(input.prompt)) return "";
44
41
  const started = Date.now();
45
42
  const repositoryRoot = hookRoot(input.cwd);
46
43
  const prompt = input.prompt?.trim() ?? "";
@@ -176,15 +173,12 @@ function isReadOnlyPrompt(prompt) {
176
173
  return asks && !EDIT_VERB.test(text);
177
174
  }
178
175
 
179
- // src/hooks/lean-command.ts
180
- async function runLeanHookCommand() {
181
- await failSoft(async () => {
182
- await runLeanHook(await readHookInput(), writeStdout);
183
- });
184
- }
185
-
186
176
  export {
177
+ LEAN_POINTER_PREFIX,
178
+ LEAN_LIMITS,
179
+ LEAN_HOOK_BUDGET_MS,
180
+ runLeanHook,
187
181
  renderLeanBlock,
188
- runLeanHookCommand
182
+ isReadOnlyPrompt
189
183
  };
190
- //# sourceMappingURL=chunk-UK6XTS2Y.js.map
184
+ //# sourceMappingURL=chunk-LRQNPYCK.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/hooks/lean.ts"],"sourcesContent":["/**\n * Lean mode: the prompt hook's only job is to cut the agent's token spend. It\n * injects ranked file pointers (and code spans for read-only questions) and\n * nothing else — no MCP tools, rules block, or memory capture.\n */\nimport { createHash } from \"node:crypto\";\nimport { appendFile, mkdir } from \"node:fs/promises\";\nimport { extname, join } from \"node:path\";\nimport {\n discoverTaskPaths,\n loadProjectArchitectureMap,\n NON_SOURCE_PATH,\n readBoundedSource,\n taskTerms,\n} from \"../context/discovery.js\";\nimport { type ContextCapsule, SessionCapsuleLedger } from \"../context/session-ledger.js\";\nimport { detectTestCommand, findCoveringTest } from \"../context/tests.js\";\nimport { loadKnownSecretValues, redactSourceText } from \"../core/sensitive.js\";\nimport { loadFreshIndex } from \"../indexer/engine.js\";\nimport { estimateTokens } from \"../savings/tokens.js\";\nimport { skipPromptRetrieval } from \"./prompt-gate.js\";\nimport { hookRoot } from \"./root.js\";\nimport type { HookInput } from \"./runtime.js\";\n\nexport const LEAN_POINTER_PREFIX = \"CodeBuddy likely files: \";\nexport const LEAN_LIMITS = {\n paths: 6,\n symbolsPerFile: 3,\n wholeFileLines: 80,\n spanTokens: 1200,\n} as const;\n\nexport type LeanPointer = {\n path: string;\n symbols: Array<{ name: string; startLine: number; endLine: number }>;\n /** The test file that covers this path, when one follows a known convention. */\n test?: string;\n};\n\ntype LoadedPointer = LeanPointer & { lines: string[] };\n\n/** A lean block finished later than this is dropped: the host may already have moved on. */\nexport const LEAN_HOOK_BUDGET_MS = 1500;\n/** Rendering after the BYOK rerank needs this much of the budget. */\nconst RENDER_RESERVE_MS = 300;\n\n/**\n * UserPromptSubmit in lean mode → ranked file pointers, or nothing. Never\n * throws: an error is appended to `.codebuddy/cache/hooks/errors.log` and the\n * turn proceeds as if CodeBuddy were not installed.\n */\nexport async function runLeanHook(\n input: HookInput,\n emit?: (block: string) => Promise<void>,\n budgetMs = LEAN_HOOK_BUDGET_MS,\n): Promise<string> {\n if (skipPromptRetrieval(input.prompt)) return \"\";\n const started = Date.now();\n const repositoryRoot = hookRoot(input.cwd);\n const prompt = input.prompt?.trim() ?? \"\";\n if (prompt === \"\" || prompt.startsWith(\"/\")) return \"\";\n const sessionId = input.session_id ?? \"default\";\n try {\n const { manifest } = await loadFreshIndex(repositoryRoot);\n const discovery = await discoverTaskPaths(\n repositoryRoot,\n prompt,\n manifest,\n loadProjectArchitectureMap(repositoryRoot, manifest),\n // The rerank, and zvec-grep (opt-in, only with a running `zg server`),\n // must answer while rendering still fits in the hook budget.\n {\n semantic: false,\n llmDeadlineAt: started + budgetMs - RENDER_RESERVE_MS,\n zvecDeadlineAt: started + budgetMs - RENDER_RESERVE_MS,\n },\n );\n const terms = taskTerms(prompt);\n const indexed = new Set(Object.keys(manifest.entries));\n const pointers: LeanPointer[] = discovery.paths\n .filter((path) => !NON_SOURCE_PATH.test(path))\n .map((path) => {\n const test = findCoveringTest(path, indexed);\n const table = manifest.entries[path]?.symbolTable ?? [];\n // Symbols the BYOK rerank named come first, then term matches.\n const picked = discovery.symbols?.[path] ?? [];\n const ordered = [\n ...picked.flatMap((name) => table.filter((symbol) => symbol.name === name)),\n ...table.filter(\n (symbol) =>\n !picked.includes(symbol.name) &&\n terms.some((term) => symbol.name.toLowerCase().includes(term)),\n ),\n ];\n return {\n path,\n symbols: ordered.map(({ name, startLine, endLine }) => ({ name, startLine, endLine })),\n ...(test ? { test } : {}),\n };\n });\n const ledger = new SessionCapsuleLedger(repositoryRoot);\n // The host keeps the block for this turn only, so nothing counts as already sent.\n if (input.ephemeral_context) await ledger.reset(sessionId);\n const { text, emitted } = await renderLeanBlock({\n repositoryRoot,\n prompt,\n pointers,\n ledger,\n sessionId,\n testCommand: pointers.length > 0 ? await detectTestCommand(repositoryRoot) : null,\n });\n if (Date.now() - started >= budgetMs) return \"\";\n if (text) await emit?.(text);\n await ledger.markSent(sessionId, emitted);\n return text;\n } catch (error) {\n await logHookError(repositoryRoot, sessionId, error).catch(() => {});\n return \"\";\n }\n}\n\nasync function logHookError(root: string, sessionId: string, error: unknown): Promise<void> {\n const directory = join(root, \".codebuddy\", \"cache\", \"hooks\");\n await mkdir(directory, { recursive: true });\n const message = (error as Error)?.message ?? String(error);\n await appendFile(\n join(directory, \"errors.log\"),\n `${new Date().toISOString()} ${message.replace(/\\s+/g, \" \")} session=${sessionId}\\n`,\n );\n}\n\n/**\n * The lean block for one prompt: a single pointer line, plus current-source\n * spans when the prompt is read-only. Capsules already delivered this session\n * are left out; the caller marks `emitted` as sent once the block is written.\n */\nexport async function renderLeanBlock(input: {\n repositoryRoot: string;\n prompt: string;\n pointers: LeanPointer[];\n ledger: SessionCapsuleLedger;\n sessionId: string;\n /** Sent once per session, after the pointers. */\n testCommand?: string | null;\n}): Promise<{ text: string; emitted: ContextCapsule[] }> {\n const loaded: LoadedPointer[] = [];\n for (const pointer of input.pointers) {\n if (loaded.length === LEAN_LIMITS.paths) break;\n const source = await readBoundedSource(input.repositoryRoot, pointer.path);\n if (source === null) continue; // deleted, oversized or binary since indexing\n loaded.push({\n path: pointer.path,\n symbols: pointer.symbols.slice(0, LEAN_LIMITS.symbolsPerFile),\n ...(pointer.test ? { test: pointer.test } : {}),\n lines: source.split(/\\r?\\n/),\n });\n }\n if (loaded.length === 0) return { text: \"\", emitted: [] };\n const knownSecrets = await loadKnownSecretValues(input.repositoryRoot);\n\n const capsules: ContextCapsule[] = [\n {\n key: \"lean:pointers\",\n text: LEAN_POINTER_PREFIX + loaded.map(describePointer).join(\", \"),\n },\n ...(input.testCommand\n ? [{ key: \"lean:test-command\", text: `CodeBuddy test command: ${input.testCommand}` }]\n : []),\n ];\n if (isReadOnlyPrompt(input.prompt)) {\n let tokens = 0;\n for (const span of loaded.flatMap((pointer) => spansOf(pointer, knownSecrets))) {\n const cost = estimateTokens(span.text);\n if (tokens + cost > LEAN_LIMITS.spanTokens) break;\n tokens += cost;\n capsules.push(span);\n }\n }\n const seen = await input.ledger.seen(input.sessionId, capsules);\n const emitted = capsules.filter((capsule) => !seen.has(capsule.key));\n return { text: emitted.map((capsule) => capsule.text).join(\"\\n\"), emitted };\n}\n\nfunction describePointer(pointer: LoadedPointer): string {\n const ranges = pointer.symbols.map((s) => `${s.name} L${s.startLine}-${s.endLine}`).join(\", \");\n const test = pointer.test ? `test: ${pointer.test}` : \"\";\n const detail = [ranges, test].filter(Boolean).join(\"; \");\n return detail ? `${pointer.path} (${detail})` : pointer.path;\n}\n\n/** Whole small files; otherwise the matched symbol spans. */\nfunction spansOf(pointer: LoadedPointer, knownSecrets: readonly string[]): ContextCapsule[] {\n const ranges =\n pointer.lines.length <= LEAN_LIMITS.wholeFileLines\n ? [{ start: 1, end: pointer.lines.length }]\n : pointer.symbols.map((s) => ({ start: s.startLine, end: s.endLine }));\n const language = extname(pointer.path).slice(1);\n return ranges.map(({ start, end }) => {\n const body = redactSourceText(pointer.lines.slice(start - 1, end).join(\"\\n\"), knownSecrets);\n const hash = createHash(\"sha1\").update(body).digest(\"hex\").slice(0, 12);\n return {\n key: `lean:span:${pointer.path}:${start}-${end}:${hash}`,\n text: `${pointer.path}:L${start}-${end}\\n\\`\\`\\`${language}\\n${body}\\n\\`\\`\\``,\n };\n });\n}\n\nconst FORCED_READ_ONLY =\n /\\b(without editing|without changing|don't change|do not change|don't edit|do not edit)\\b/i;\nconst QUESTION_START = /^(how|what|why|where|which|who|explain|describe|summarize)\\b/i;\nconst EDIT_VERB =\n /\\b(add|change|fix|implement|update|remove|delete|rename|refactor|write|create|make|edit|replace|move|migrate|support|build)\\b/i;\n\n/**\n * Whether a prompt asks a question without asking for edits. Conservative: a\n * false positive costs up to the span budget, a false negative costs nothing.\n */\nexport function isReadOnlyPrompt(prompt: string): boolean {\n const text = prompt.trim();\n if (text === \"\" || text.startsWith(\"/\")) return false;\n if (FORCED_READ_ONLY.test(text)) return true;\n const asks = QUESTION_START.test(text) || text.endsWith(\"?\");\n return asks && !EDIT_VERB.test(text);\n}\n"],"mappings":"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","names":[]}
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  asksForChangelog
3
- } from "./chunk-TLWGZVPC.js";
3
+ } from "./chunk-YNSTIMCF.js";
4
4
  import {
5
5
  readConfigFile
6
6
  } from "./chunk-KNR2P2UY.js";
@@ -386,4 +386,4 @@ export {
386
386
  zvecStatus,
387
387
  buildZvecIndex
388
388
  };
389
- //# sourceMappingURL=chunk-GXOML4UZ.js.map
389
+ //# sourceMappingURL=chunk-LW75SKZR.js.map
@@ -5,7 +5,7 @@ import {
5
5
  } from "./chunk-FJOTP632.js";
6
6
  import {
7
7
  recordTrace
8
- } from "./chunk-XL2HLACE.js";
8
+ } from "./chunk-YN7TEIUH.js";
9
9
  import {
10
10
  hookRoot
11
11
  } from "./chunk-TGFTKWOR.js";
@@ -26,7 +26,8 @@ var TOOL_GUARD = {
26
26
  minTopLevelEntries: 10,
27
27
  outlineEntries: 40,
28
28
  blockLines: 100,
29
- grepHeadLimit: 100
29
+ grepHeadLimit: 100,
30
+ initialReadLines: 200
30
31
  };
31
32
  var SPECIAL_READ = /* @__PURE__ */ new Set([
32
33
  ".png",
@@ -44,8 +45,11 @@ async function decidePreTool(input, env = process.env) {
44
45
  if (env.CODEBUDDY_TOOL_GUARD === "0") return null;
45
46
  const toolInput = input.tool_input ?? {};
46
47
  if (input.tool_name === "Grep") return capGrep(toolInput);
47
- if (input.tool_name === "Read") return guardRead(resolve(input.cwd ?? process.cwd()), toolInput);
48
- if (input.tool_name === "Bash") return guardBash(resolve(input.cwd ?? process.cwd()), toolInput);
48
+ const strict = env.CODEBUDDY_TOOL_GUARD === "strict";
49
+ if (input.tool_name === "Read")
50
+ return guardRead(resolve(input.cwd ?? process.cwd()), toolInput, strict);
51
+ if (input.tool_name === "Bash" && strict)
52
+ return guardBash(resolve(input.cwd ?? process.cwd()), toolInput);
49
53
  return null;
50
54
  }
51
55
  function capGrep(toolInput) {
@@ -57,7 +61,7 @@ function capGrep(toolInput) {
57
61
  }
58
62
  };
59
63
  }
60
- async function largeFile(root, filePath) {
64
+ async function largeFile(root, filePath, includeOutline = true) {
61
65
  const absolute = isAbsolute(filePath) ? resolve(filePath) : resolve(root, filePath);
62
66
  const rel = relative(root, absolute);
63
67
  if (rel === "" || rel === ".." || rel.startsWith(`..${sep}`) || isAbsolute(rel)) return null;
@@ -77,14 +81,27 @@ async function largeFile(root, filePath) {
77
81
  if (lines.at(-1) === "") lines.pop();
78
82
  if (lines.length <= TOOL_GUARD.readLines && size <= TOOL_GUARD.readBytes) return null;
79
83
  const path = rel.replace(/\\/g, "/");
80
- return { path, lines: lines.length, size, outline: outlineOf(path, text, lines) };
84
+ return {
85
+ path,
86
+ lines: lines.length,
87
+ size,
88
+ outline: includeOutline ? outlineOf(path, text, lines) : []
89
+ };
81
90
  }
82
- async function guardRead(root, toolInput) {
91
+ async function guardRead(root, toolInput, strict) {
83
92
  if (toolInput.offset !== void 0 || toolInput.limit !== void 0) return null;
84
93
  const filePath = toolInput.file_path;
85
94
  if (typeof filePath !== "string" || !filePath) return null;
86
- const file = await largeFile(root, filePath);
95
+ const file = await largeFile(root, filePath, strict);
87
96
  if (!file) return null;
97
+ if (!strict)
98
+ return {
99
+ hookSpecificOutput: {
100
+ hookEventName: "PreToolUse",
101
+ updatedInput: { ...toolInput, offset: 1, limit: TOOL_GUARD.initialReadLines },
102
+ additionalContext: `CodeBuddy bounded this unranged Read to lines 1-${TOOL_GUARD.initialReadLines} of ${file.lines}. Continue with an explicit offset/limit or request the full ${file.lines} lines if needed.`
103
+ }
104
+ };
88
105
  return deny(file, [
89
106
  "Read only the part you need with offset and limit (offset = first line, limit = line count).",
90
107
  "To find specific text, use Grep with output_mode content and -n instead of reading the file.",
@@ -212,4 +229,4 @@ async function runPreToolHookCommand() {
212
229
  export {
213
230
  runPreToolHookCommand
214
231
  };
215
- //# sourceMappingURL=chunk-EJQVGFH2.js.map
232
+ //# sourceMappingURL=chunk-UOTKHSQ5.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/hooks/pretool.ts","../src/hooks/pretool-command.ts"],"sourcesContent":["import { open, stat } from \"node:fs/promises\";\nimport { extname, isAbsolute, relative, resolve, sep } from \"node:path\";\nimport { extractSymbolTable, supportsSymbols } from \"../indexer/symbols.js\";\nimport type { HookInput } from \"./runtime.js\";\n\n/**\n * PreToolUse tool guard (Claude Code): cut tool output that would otherwise\n * stay in context for the rest of the session, without touching permissions.\n *\n * - Large unranged Read → bounded first read, with an explicit continuation hint.\n * Any requested offset/limit passes unchanged. Legacy denial is opt-in strict.\n * - Grep in content mode with no head_limit → the same input plus a limit.\n * - Bash passes unchanged by default; strict mode retains the legacy guard.\n * Shell pipelines are never rewritten, so check exit status is preserved.\n *\n * Never emits `permissionDecision: \"allow\"`: Claude Code checks permissions on\n * the (possibly rewritten) input as usual. Verified on Claude Code 2.1.287.\n */\n/**\n * Only files whose full read is clearly expensive: below this, an agent that\n * re-reads the whole file after the outline (observed live) costs a turn for\n * nothing.\n */\nexport const TOOL_GUARD = {\n readLines: 1_000,\n readBytes: 80_000,\n /** Symbols shorter than this are noise in an outline (one-line constants). */\n outlineMinSymbolLines: 3,\n /** Fewer top-level symbols than this: list class methods too. */\n minTopLevelEntries: 10,\n outlineEntries: 40,\n blockLines: 100,\n grepHeadLimit: 100,\n initialReadLines: 200,\n} as const;\n\n/** Read renders these itself (images, PDFs, notebooks); never guard them. */\nconst SPECIAL_READ = new Set([\n \".png\",\n \".jpg\",\n \".jpeg\",\n \".gif\",\n \".webp\",\n \".bmp\",\n \".svg\",\n \".pdf\",\n \".ipynb\",\n]);\nconst MAX_GUARD_BYTES = 2_000_000;\n\nexport type PreToolDecision = {\n hookSpecificOutput: {\n hookEventName: \"PreToolUse\";\n permissionDecision?: \"deny\";\n permissionDecisionReason?: string;\n updatedInput?: Record<string, unknown>;\n additionalContext?: string;\n };\n} | null;\n\nexport async function decidePreTool(\n input: HookInput,\n env: NodeJS.ProcessEnv = process.env,\n): Promise<PreToolDecision> {\n if (env.CODEBUDDY_TOOL_GUARD === \"0\") return null;\n const toolInput = input.tool_input ?? {};\n if (input.tool_name === \"Grep\") return capGrep(toolInput);\n const strict = env.CODEBUDDY_TOOL_GUARD === \"strict\";\n if (input.tool_name === \"Read\")\n return guardRead(resolve(input.cwd ?? process.cwd()), toolInput, strict);\n if (input.tool_name === \"Bash\" && strict)\n return guardBash(resolve(input.cwd ?? process.cwd()), toolInput);\n return null;\n}\n\nfunction capGrep(toolInput: Record<string, unknown>): PreToolDecision {\n if (toolInput.output_mode !== \"content\" || toolInput.head_limit !== undefined) return null;\n return {\n hookSpecificOutput: {\n hookEventName: \"PreToolUse\",\n updatedInput: { ...toolInput, head_limit: TOOL_GUARD.grepHeadLimit },\n },\n };\n}\n\n/** A file inside the repository too large to read whole, with its outline; else null. */\nexport async function largeFile(\n root: string,\n filePath: string,\n includeOutline = true,\n): Promise<{ path: string; lines: number; size: number; outline: string[] } | null> {\n const absolute = isAbsolute(filePath) ? resolve(filePath) : resolve(root, filePath);\n const rel = relative(root, absolute);\n if (rel === \"\" || rel === \"..\" || rel.startsWith(`..${sep}`) || isAbsolute(rel)) return null;\n if (SPECIAL_READ.has(extname(absolute).toLowerCase())) return null;\n let size: number;\n try {\n const info = await stat(absolute);\n if (!info.isFile()) return null;\n size = info.size;\n } catch {\n return null;\n }\n if (size > MAX_GUARD_BYTES) return null; // Read refuses huge files itself.\n const text = await readText(absolute);\n if (text === null) return null;\n const lines = text.split(/\\r?\\n/);\n if (lines.at(-1) === \"\") lines.pop();\n if (lines.length <= TOOL_GUARD.readLines && size <= TOOL_GUARD.readBytes) return null;\n const path = rel.replace(/\\\\/g, \"/\");\n return {\n path,\n lines: lines.length,\n size,\n outline: includeOutline ? outlineOf(path, text, lines) : [],\n };\n}\n\nasync function guardRead(\n root: string,\n toolInput: Record<string, unknown>,\n strict: boolean,\n): Promise<PreToolDecision> {\n if (toolInput.offset !== undefined || toolInput.limit !== undefined) return null;\n const filePath = toolInput.file_path;\n if (typeof filePath !== \"string\" || !filePath) return null;\n const file = await largeFile(root, filePath, strict);\n if (!file) return null;\n if (!strict)\n return {\n hookSpecificOutput: {\n hookEventName: \"PreToolUse\",\n updatedInput: { ...toolInput, offset: 1, limit: TOOL_GUARD.initialReadLines },\n additionalContext: `CodeBuddy bounded this unranged Read to lines 1-${TOOL_GUARD.initialReadLines} of ${file.lines}. Continue with an explicit offset/limit or request the full ${file.lines} lines if needed.`,\n },\n };\n return deny(file, [\n \"Read only the part you need with offset and limit (offset = first line, limit = line count).\",\n \"To find specific text, use Grep with output_mode content and -n instead of reading the file.\",\n `To read the whole file anyway, call Read again with limit: ${file.lines}.`,\n ]);\n}\n\nfunction deny(\n file: { path: string; lines: number; size: number; outline: string[] },\n advice: string[],\n): PreToolDecision {\n return {\n hookSpecificOutput: {\n hookEventName: \"PreToolUse\",\n permissionDecision: \"deny\",\n permissionDecisionReason: [\n `CodeBuddy read guard: ${file.path} is ${file.lines} lines (~${Math.ceil(file.size / 4)} tokens), too large to read whole.`,\n `Outline: ${file.outline.join(\"; \")}`,\n ...advice,\n ].join(\"\\n\"),\n },\n };\n}\n\n/** Programs that print whole files; `head`/`tail` only when asked for more lines than the guard. */\nconst WHOLE_FILE_PRINTERS = new Set([\"cat\", \"less\", \"more\", \"nl\", \"bat\", \"batcat\"]);\n/**\n * Anything that makes a command more than one plain read: pipes, lists,\n * redirection, substitution, globbing over many files. Such commands are never\n * touched, so builds, tests, pipelines and scripts behave exactly as before.\n */\nconst SHELL_SYNTAX = /[|;&<>`$(){}*?[\\]\\n\\\\]/;\n\n/**\n * Bash that would print a large file whole (`cat src/big.ts`, `head -n 5000 x`).\n * Claude reads files through Bash to get around the Read guard; the output then\n * stays in context for the rest of the session. Only a single plain read\n * command of a file over the Read guard's size is denied, with the same outline\n * and how to read a range; every other command passes untouched.\n */\nasync function guardBash(\n root: string,\n toolInput: Record<string, unknown>,\n): Promise<PreToolDecision> {\n const command = typeof toolInput.command === \"string\" ? toolInput.command.trim() : \"\";\n if (!command || SHELL_SYNTAX.test(command)) return null;\n const tokens = (command.match(/\"[^\"]*\"|'[^']*'|\\S+/g) ?? []).map((token) =>\n token.replace(/^([\"'])(.*)\\1$/, \"$2\"),\n );\n // Leading `VAR=value` assignments do not change what runs.\n while (tokens[0] && /^[A-Za-z_][A-Za-z0-9_]*=/.test(tokens[0])) tokens.shift();\n const program = (tokens.shift() ?? \"\").split(\"/\").at(-1) ?? \"\";\n const files: string[] = [];\n if (WHOLE_FILE_PRINTERS.has(program)) {\n for (const token of tokens) if (!token.startsWith(\"-\")) files.push(token);\n } else if (program === \"head\" || program === \"tail\") {\n let lines = 10;\n for (let index = 0; index < tokens.length; index += 1) {\n const token = tokens[index] ?? \"\";\n const count = /^-n$|^--lines$/.test(token)\n ? tokens[++index]\n : (token.match(/^(?:-n|--lines=)\\+?(\\d+)$/)?.[1] ?? token.match(/^-(\\d+)$/)?.[1]);\n if (count !== undefined && /^\\+?\\d+$/.test(count)) lines = Number(count.replace(\"+\", \"\"));\n else if (token === \"-f\" || token === \"-F\" || token === \"--follow\") return null;\n else if (!token.startsWith(\"-\")) files.push(token);\n }\n if (lines <= TOOL_GUARD.readLines) return null;\n } else {\n return null;\n }\n for (const filePath of files) {\n const file = await largeFile(root, filePath);\n if (file) {\n return deny(file, [\n \"Read only the part you need with the Read tool (offset = first line, limit = line count) or `sed -n 'START,ENDp' FILE`.\",\n \"To find specific text, use Grep with output_mode content and -n instead of printing the file.\",\n `To read the whole file anyway, use Read with limit: ${file.lines}.`,\n ]);\n }\n }\n return null;\n}\n\n/** Whole file as UTF-8 text, or null when it looks binary. */\nasync function readText(path: string): Promise<string | null> {\n const handle = await open(path, \"r\");\n try {\n const content = await handle.readFile();\n if (content.includes(0)) return null;\n return content.toString(\"utf8\");\n } finally {\n await handle.close();\n }\n}\n\nfunction outlineOf(path: string, text: string, lines: string[]): string[] {\n const cap = (entries: string[]) =>\n entries.length > TOOL_GUARD.outlineEntries\n ? [\n ...entries.slice(0, TOOL_GUARD.outlineEntries),\n `… ${entries.length - TOOL_GUARD.outlineEntries} more`,\n ]\n : entries;\n const all = supportsSymbols(path) ? extractSymbolTable(text, path) : [];\n if (all[0]?.kind === \"heading\") {\n return cap(all.map((heading) => `${heading.signature.slice(0, 80)} L${heading.startLine}`));\n }\n if (all.length > 0) {\n const table = all.filter(\n (symbol) => symbol.endLine - symbol.startLine + 1 >= TOOL_GUARD.outlineMinSymbolLines,\n );\n const topLevel = table.filter((symbol) => symbol.kind !== \"method\");\n // One big class says little on its own; then list its methods (`Class.method`).\n const symbols = topLevel.length >= TOOL_GUARD.minTopLevelEntries ? topLevel : table;\n if (symbols.length > 0) {\n // The symbol table is capped, so say where the outline stops covering the file.\n const covered = Math.max(...symbols.map((symbol) => symbol.endLine));\n return [\n ...cap(symbols.map((s) => `${s.name} L${s.startLine}-${s.endLine}`)),\n ...(covered < lines.length ? [`not outlined L${covered + 1}-${lines.length}`] : []),\n ];\n }\n }\n const blocks: string[] = [];\n for (let start = 1; start <= lines.length; start += TOOL_GUARD.blockLines) {\n blocks.push(`L${start}-${Math.min(lines.length, start + TOOL_GUARD.blockLines - 1)}`);\n }\n return cap(blocks);\n}\n","import { recordTrace } from \"../tracing/log.js\";\nimport { decidePreTool } from \"./pretool.js\";\nimport { hookRoot } from \"./root.js\";\nimport { failSoft, readHookInput, writeStdout } from \"./stdio.js\";\n\n/** `codebuddy hooks pretool`: runs on every Read/Grep, so it loads only its own module. */\nexport async function runPreToolHookCommand(): Promise<void> {\n await failSoft(async () => {\n const input = await readHookInput();\n const decision = await decidePreTool(input);\n if (!decision) return;\n await writeStdout(JSON.stringify(decision));\n const reason = decision.hookSpecificOutput.permissionDecisionReason;\n if (decision.hookSpecificOutput.permissionDecision === \"deny\" && reason) {\n // Measured, not modelled: the file the agent did not load whole, minus the\n // outline it got instead. Shown by `codebuddy dashboard`.\n const full = Number(reason.match(/\\(~(\\d+) tokens\\)/)?.[1] ?? 0);\n const outline = Math.ceil(reason.length / 4);\n await recordTrace(hookRoot(input.cwd), {\n kind: \"guard\",\n name: input.tool_name ?? \"tool\",\n ms: 0,\n ok: true,\n tokensOut: outline,\n ...(input.session_id ? { session: input.session_id } : {}),\n detail: {\n file: reason.match(/read guard: (\\S+) is/)?.[1] ?? \"\",\n avoided: Math.max(0, full - outline),\n },\n });\n }\n });\n}\n"],"mappings":"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","names":[]}
@@ -21,12 +21,13 @@ var CLIENT_ADAPTERS = {
21
21
  },
22
22
  codex: {
23
23
  name: "Codex",
24
- host: { bin: "codex", verified: "0.163.0" },
24
+ host: { bin: "codex", verified: "0.169.0" },
25
25
  rulesFile: "AGENTS.md",
26
26
  mcp: { global: "codex", manual: "codebuddy codex install (global ~/.codex/config.toml)" },
27
- hookEvents: [],
28
- gap: "no CodeBuddy hook adapter for Codex yet",
29
- framing: "agent mode only; AGENTS.md is part of the instructions every request"
27
+ hookEvents: LIFECYCLE_EVENTS,
28
+ hooks: "codex-hooks",
29
+ note: "Requires Codex hook support (verified 0.169.0), a trusted project and review of the installed definitions in /hooks. CodeBuddy never bypasses hook trust. Rollout import remains available for older sessions.",
30
+ framing: "native hook additionalContext is developer context; project instructions remain in AGENTS.md"
30
31
  },
31
32
  opencode: {
32
33
  name: "OpenCode",
@@ -101,4 +102,4 @@ export {
101
102
  supportedModes,
102
103
  sharedRulesReaders
103
104
  };
104
- //# sourceMappingURL=chunk-W7GAXL6C.js.map
105
+ //# sourceMappingURL=chunk-V7PT6ER5.js.map