@orquentra/codebuddy 2.6.2 → 2.8.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 (115) hide show
  1. package/README.md +10 -0
  2. package/dist/adapters-S4HHTWJW.js +24 -0
  3. package/dist/{chunk-IBLJOHJL.js → chunk-3QGW53GD.js} +2 -2
  4. package/dist/{chunk-IMFUYEZA.js → chunk-6DONJW3W.js} +232 -64
  5. package/dist/chunk-6DONJW3W.js.map +1 -0
  6. package/dist/{chunk-JFDRBPYF.js → chunk-6JCKMIQY.js} +2 -2
  7. package/dist/{chunk-S6VEG4S2.js → chunk-6KGYAFG7.js} +28 -5
  8. package/dist/chunk-6KGYAFG7.js.map +1 -0
  9. package/dist/{chunk-OWWCDRXW.js → chunk-6NZEZJYD.js} +9 -7
  10. package/dist/chunk-6NZEZJYD.js.map +1 -0
  11. package/dist/{chunk-GDJH7XJC.js → chunk-AL6VBTNA.js} +77 -3
  12. package/dist/chunk-AL6VBTNA.js.map +1 -0
  13. package/dist/{chunk-XBJQKCLU.js → chunk-CAEPHLHC.js} +13 -9
  14. package/dist/chunk-CAEPHLHC.js.map +1 -0
  15. package/dist/chunk-CG5B3LYG.js +66 -0
  16. package/dist/chunk-CG5B3LYG.js.map +1 -0
  17. package/dist/{chunk-IHQM2ZDV.js → chunk-DFV2SK6C.js} +1 -1
  18. package/dist/{chunk-IHQM2ZDV.js.map → chunk-DFV2SK6C.js.map} +1 -1
  19. package/dist/{chunk-QBDJXGG5.js → chunk-EJQVGFH2.js} +93 -22
  20. package/dist/chunk-EJQVGFH2.js.map +1 -0
  21. package/dist/{chunk-5MQOE4J6.js → chunk-ESZXCZIN.js} +3 -3
  22. package/dist/{chunk-J2EAZ4MA.js → chunk-GKNYMNFM.js} +22 -25
  23. package/dist/chunk-GKNYMNFM.js.map +1 -0
  24. package/dist/{chunk-5FW3PMNP.js → chunk-GXOML4UZ.js} +76 -11
  25. package/dist/chunk-GXOML4UZ.js.map +1 -0
  26. package/dist/{chunk-JPHJBCZB.js → chunk-HPOR6YXA.js} +3 -2
  27. package/dist/chunk-HPOR6YXA.js.map +1 -0
  28. package/dist/chunk-ITQO632W.js +673 -0
  29. package/dist/chunk-ITQO632W.js.map +1 -0
  30. package/dist/chunk-J4T6HHI4.js +1444 -0
  31. package/dist/chunk-J4T6HHI4.js.map +1 -0
  32. package/dist/{chunk-5KZY6OTG.js → chunk-KNR2P2UY.js} +33 -83
  33. package/dist/chunk-KNR2P2UY.js.map +1 -0
  34. package/dist/{chunk-2XDPKFUW.js → chunk-NY2YXP3R.js} +46 -20
  35. package/dist/chunk-NY2YXP3R.js.map +1 -0
  36. package/dist/{chunk-RAJJSCIP.js → chunk-SFMIMLXR.js} +2 -2
  37. package/dist/chunk-TGFTKWOR.js +16 -0
  38. package/dist/chunk-TGFTKWOR.js.map +1 -0
  39. package/dist/{chunk-NERKZSSE.js → chunk-TLWGZVPC.js} +395 -686
  40. package/dist/chunk-TLWGZVPC.js.map +1 -0
  41. package/dist/{chunk-B6CO63SQ.js → chunk-UK6XTS2Y.js} +24 -10
  42. package/dist/chunk-UK6XTS2Y.js.map +1 -0
  43. package/dist/chunk-W7GAXL6C.js +104 -0
  44. package/dist/chunk-W7GAXL6C.js.map +1 -0
  45. package/dist/chunk-XL2HLACE.js +55 -0
  46. package/dist/chunk-XL2HLACE.js.map +1 -0
  47. package/dist/chunk-Y7D7PUBE.js +321 -0
  48. package/dist/chunk-Y7D7PUBE.js.map +1 -0
  49. package/dist/{chunk-R7CD6EXS.js → chunk-ZNLSCV3J.js} +578 -381
  50. package/dist/chunk-ZNLSCV3J.js.map +1 -0
  51. package/dist/cli/index.js +5 -5
  52. package/dist/cli/index.js.map +1 -1
  53. package/dist/{cloud-7WM3YLI4.js → cloud-LXZUR3U2.js} +3 -3
  54. package/dist/{codebuddy-B_pCaQrV.d.ts → codebuddy-DO7ZhoWn.d.ts} +15 -1
  55. package/dist/config-GEZFLXK6.js +12 -0
  56. package/dist/index.d.ts +46 -3
  57. package/dist/index.js +23 -19
  58. package/dist/install-BXM56QCD.js +19 -0
  59. package/dist/langgraph/index.d.ts +1 -1
  60. package/dist/lean-command-7DFW32MA.js +19 -0
  61. package/dist/{local-5MZURLCZ.js → local-ST4WMRCG.js} +3 -3
  62. package/dist/{main-MTBQZGJV.js → main-QUASSFP7.js} +987 -1741
  63. package/dist/main-QUASSFP7.js.map +1 -0
  64. package/dist/{memory-KIC5FJVW.js → memory-TGHVTBME.js} +9 -8
  65. package/dist/memory-TGHVTBME.js.map +1 -0
  66. package/dist/operations-WJR7AZZF.js +26 -0
  67. package/dist/pretool-command-JTKX6G3M.js +13 -0
  68. package/dist/{rerank-5QZ3PNUW.js → rerank-IQAEWLQR.js} +6 -5
  69. package/dist/rerank-IQAEWLQR.js.map +1 -0
  70. package/dist/{runtime-CUBOOPZO.js → runtime-OQQ22VJF.js} +15 -11
  71. package/dist/summaries-ML53R7EL.js +21 -0
  72. package/dist/summaries-ML53R7EL.js.map +1 -0
  73. package/dist/{zvec-MSRKT7IV.js → zvec-XDZNZ2OL.js} +11 -3
  74. package/dist/zvec-XDZNZ2OL.js.map +1 -0
  75. package/package.json +1 -1
  76. package/src/db/migrations/0006_fact_idempotency_key.sql +2 -0
  77. package/src/db/migrations/meta/0006_snapshot.json +1258 -0
  78. package/src/db/migrations/meta/_journal.json +7 -0
  79. package/dist/chunk-2XDPKFUW.js.map +0 -1
  80. package/dist/chunk-5FW3PMNP.js.map +0 -1
  81. package/dist/chunk-5KZY6OTG.js.map +0 -1
  82. package/dist/chunk-B6CO63SQ.js.map +0 -1
  83. package/dist/chunk-GDJH7XJC.js.map +0 -1
  84. package/dist/chunk-IMFUYEZA.js.map +0 -1
  85. package/dist/chunk-J2EAZ4MA.js.map +0 -1
  86. package/dist/chunk-JPHJBCZB.js.map +0 -1
  87. package/dist/chunk-NERKZSSE.js.map +0 -1
  88. package/dist/chunk-OWWCDRXW.js.map +0 -1
  89. package/dist/chunk-PQK3GKZH.js +0 -98
  90. package/dist/chunk-PQK3GKZH.js.map +0 -1
  91. package/dist/chunk-QBDJXGG5.js.map +0 -1
  92. package/dist/chunk-R7CD6EXS.js.map +0 -1
  93. package/dist/chunk-S6VEG4S2.js.map +0 -1
  94. package/dist/chunk-XBJQKCLU.js.map +0 -1
  95. package/dist/config-DEVHUDIU.js +0 -11
  96. package/dist/lean-command-HTQCPH76.js +0 -15
  97. package/dist/main-MTBQZGJV.js.map +0 -1
  98. package/dist/memory-KIC5FJVW.js.map +0 -1
  99. package/dist/operations-POCHY3WR.js +0 -25
  100. package/dist/pretool-command-AX6JXP25.js +0 -10
  101. package/dist/rerank-5QZ3PNUW.js.map +0 -1
  102. package/dist/summaries-SB5L2TSW.js +0 -18
  103. /package/dist/{cloud-7WM3YLI4.js.map → adapters-S4HHTWJW.js.map} +0 -0
  104. /package/dist/{chunk-IBLJOHJL.js.map → chunk-3QGW53GD.js.map} +0 -0
  105. /package/dist/{chunk-JFDRBPYF.js.map → chunk-6JCKMIQY.js.map} +0 -0
  106. /package/dist/{chunk-5MQOE4J6.js.map → chunk-ESZXCZIN.js.map} +0 -0
  107. /package/dist/{chunk-RAJJSCIP.js.map → chunk-SFMIMLXR.js.map} +0 -0
  108. /package/dist/{config-DEVHUDIU.js.map → cloud-LXZUR3U2.js.map} +0 -0
  109. /package/dist/{lean-command-HTQCPH76.js.map → config-GEZFLXK6.js.map} +0 -0
  110. /package/dist/{local-5MZURLCZ.js.map → install-BXM56QCD.js.map} +0 -0
  111. /package/dist/{operations-POCHY3WR.js.map → lean-command-7DFW32MA.js.map} +0 -0
  112. /package/dist/{pretool-command-AX6JXP25.js.map → local-ST4WMRCG.js.map} +0 -0
  113. /package/dist/{runtime-CUBOOPZO.js.map → operations-WJR7AZZF.js.map} +0 -0
  114. /package/dist/{summaries-SB5L2TSW.js.map → pretool-command-JTKX6G3M.js.map} +0 -0
  115. /package/dist/{zvec-MSRKT7IV.js.map → runtime-OQQ22VJF.js.map} +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/clients/install.ts","../src/hooks/hermes-plugin.ts","../src/hooks/managed-plugin.ts","../src/hooks/opencode-plugin.ts","../src/hooks/settings.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} from \"../hooks/install.js\";\nimport 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 & { mode: AutomationMode },\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 return apply(options, options.mode, options.mode !== \"lean\");\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): 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 );\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): Promise<ClientStep | null> {\n const result = await setClientHooks(root, client, enabled, variant);\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 === null && 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 { readFile } from \"node:fs/promises\";\nimport { dirname, join, resolve } from \"node:path\";\nimport { type ManagedPlugin, managedPluginInstalled, setManagedPlugin } from \"./managed-plugin.js\";\nimport 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(variant: HookVariant): string {\n return variant === \"lean\"\n ? `${HERMES_PLUGIN_MARKER} v1 lean\n# Generated by \"codebuddy clients install hermes --mode lean\"; 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 = {\"lean\": 2}\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(\"lean\", 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 : `${HERMES_PLUGIN_MARKER} v1\n# Generated by \"codebuddy clients install hermes --mode hooks\"; 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 = {\"context\": 20, \"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\": []}\n )\n\n\ndef _pre_llm_call(session_id=\"\", turn_id=\"\", user_message=\"\", **_):\n _turn(turn_id, session_id)\n payload = {\"session_id\": session_id, \"hook_event_name\": \"pre_llm_call\", \"ephemeral_context\": True}\n if isinstance(user_message, str) and user_message.strip():\n payload[\"prompt\"] = user_message\n block = _hook(\"context\", 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=\"\", **_):\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 })\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\n\ndef _post_llm_call(session_id=\"\", turn_id=\"\", assistant_response=\"\", **_):\n if isinstance(assistant_response, str) and assistant_response.strip():\n _turn(turn_id, session_id)[\"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\": []}\n payload = {\"session_id\": turn[\"session\"], \"hook_event_name\": \"on_session_end\"}\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(repositoryRoot: string, variant: HookVariant = \"full\"): ManagedPlugin {\n const dir = hermesPluginDir(repositoryRoot);\n return {\n marker: HERMES_PLUGIN_MARKER,\n files: [\n {\n path: join(dir, \"plugin.yaml\"),\n content: variant === \"lean\" ? HERMES_LEAN_MANIFEST : HERMES_PLUGIN_MANIFEST,\n },\n { path: join(dir, \"__init__.py\"), content: renderHermesPlugin(variant) },\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): Promise<boolean> {\n return setManagedPlugin(plugin(repositoryRoot, variant), enabled);\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 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(variant: HookVariant): string {\n return variant === \"lean\"\n ? `${OPENCODE_PLUGIN_MARKER} v1 lean\n// Generated by \\`codebuddy clients install opencode --mode lean\\`; removed by\n// \\`codebuddy clients uninstall opencode\\`. Local edits are overwritten.\nimport { spawn } from \"node:child_process\";\n\nconst TIMEOUT_MS = { lean: 2000, 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(\"lean\", { ...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 : `${OPENCODE_PLUGIN_MARKER} v1\n// Generated by \\`codebuddy clients install opencode --mode hooks\\`; 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 = { context: 20000, 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 turn = (sessionID) => {\n if (!turns.has(sessionID)) turns.set(sessionID, { text: \"\", commands: [] });\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 const block = (\n await hook(\"context\", { ...base(input.sessionID, \"chat.message\"), ...(prompt ? { prompt } : {}) })\n ).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 \"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 });\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 }\n },\n \"experimental.text.complete\": async (input, output) => {\n if (output.text.trim()) turn(input.sessionID).text = output.text;\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?.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 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(repositoryRoot: string, variant: HookVariant = \"full\"): ManagedPlugin {\n const path = opencodePluginPath(repositoryRoot);\n return {\n marker: OPENCODE_PLUGIN_MARKER,\n files: [{ path, content: renderOpencodePlugin(variant) }],\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): Promise<boolean> {\n return setManagedPlugin(plugin(repositoryRoot, variant), enabled);\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 { 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 * - 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\nconst HOOK_DEFS: HookDef[] = [\n { event: \"UserPromptSubmit\", command: `${HOOK_COMMAND_PREFIX} context` },\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 // After compaction or /clear the model no longer holds earlier context, so\n // capsules the session ledger marked as sent must be sent again.\n { event: \"SessionStart\", matcher: \"compact|clear\", command: `${HOOK_COMMAND_PREFIX} reset` },\n TOOL_GUARD_DEF,\n];\n\n/** Lean mode: prompt pointers only, plus the reset that keeps their dedupe correct. */\nconst LEAN_HOOK_DEFS: HookDef[] = [\n { event: \"UserPromptSubmit\", command: `${HOOK_COMMAND_PREFIX} lean` },\n { event: \"SessionStart\", matcher: \"compact|clear\", command: `${HOOK_COMMAND_PREFIX} reset` },\n TOOL_GUARD_DEF,\n];\n\nexport type HookVariant = \"full\" | \"lean\";\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): { settings: HookSettings; changed: boolean } {\n const defs = variant === \"lean\" ? LEAN_HOOK_DEFS : HOOK_DEFS;\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\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 {\n CLIENT_ADAPTERS,\n CLIENT_IDS,\n type ClientId,\n type HookSurface,\n} from \"../clients/adapters.js\";\nimport {\n hermesPluginDir,\n hermesPluginInstalled,\n hermesPluginVariant,\n setHermesPlugin,\n} from \"./hermes-plugin.js\";\nimport {\n opencodePluginInstalled,\n opencodePluginPath,\n opencodePluginVariant,\n setOpencodePlugin,\n} from \"./opencode-plugin.js\";\nimport {\n claudeSettingsPath,\n type HookVariant,\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: (root: string, enabled: boolean, variant: HookVariant) => Promise<boolean>;\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) => {\n const path = claudeSettingsPath(root);\n const current = await readSettings(path);\n const { settings, changed } = enabled ? upsertHooks(current, variant) : removeHooks(current);\n if (changed) await writeSettings(path, settings);\n return changed;\n },\n },\n \"opencode-plugin\": {\n path: opencodePluginPath,\n installed: opencodePluginInstalled,\n variant: opencodePluginVariant,\n set: setOpencodePlugin,\n },\n \"hermes-plugin\": {\n path: hermesPluginDir,\n installed: hermesPluginInstalled,\n variant: hermesPluginVariant,\n set: setHermesPlugin,\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): Promise<{ target: string; changed: boolean } | null> {\n const surface = CLIENT_ADAPTERS[client].hooks;\n if (!surface) return null;\n const { path, set } = SURFACES[surface];\n return { target: path(root), changed: await set(root, enabled, variant) };\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: \"core\",\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 exactly which MCP tool to call, when, and what to do when a\n * tool or service is unavailable. The text references only tools that actually\n * ship (04.2–04.6): context_pack, context_bootstrap, context_before_edit,\n * context_after_turn, code_suggestions.\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 MODE_LABELS,\n} from \"../clients/adapters.js\";\n\nexport const WORKFLOW_CLIENTS = CLIENT_IDS;\nexport type WorkflowClient = ClientId;\n\n/** The shared body, split where the automation mode changes who acts. */\nconst INTRO = [\n \"You have CodeBuddy available as a local, MCP-backed project-context engine.\",\n \"Use it to work like a careful senior engineer, not to dump broad context.\",\n \"\",\n];\n\nconst START_AGENT = [\n \"## At the start of a substantive coding turn\",\n \"\",\n \"1. When project context is needed, call `context_pack` with the user's task and a token budget (start\",\n \" with 4000). It automatically finds relevant files and returns bounded symbols,\",\n \" plan, policy, incidents, risks, architecture neighbours, and team facts.\",\n \" Reuse evidence already present. For a clearly identified small edit, read the\",\n \" needed source directly; fetch context for unknown dependencies or project constraints.\",\n \" Read missing function bodies before editing. If safety evidence was omitted,\",\n \" narrow the targets or increase the budget before proceeding.\",\n \"2. Use `context_bootstrap` only when you need broad project orientation beyond\",\n \" the task pack. Use `context_before_edit` when you already have exact paths\",\n \" or a plan and need a narrower follow-up.\",\n \"3. Prior work from earlier chats arrives as `handoffs` with freshness\",\n \" (`changedSince`, `missing`); re-check those files before relying on it.\",\n \"4. On repeated context calls in one conversation, pass a stable `sessionId` and\",\n \" the previous response's `delivery.deliveryId` as `ack`. Unchanged sections\",\n \" are then omitted and listed in `delivery.unchanged`. If earlier output is no\",\n \" longer in your context (e.g. after compaction), pass `full: true`.\",\n \"\",\n];\n\nconst START_HOOKS = [\n \"## At the start of a substantive coding turn\",\n \"\",\n \"CodeBuddy hooks inject a `CodeBuddy project context (auto-recalled)` block into\",\n \"each prompt: project, plan, policies, facts, incidents, handoffs, current\",\n \"changes, and risk. Agents without CodeBuddy hooks (no such block in the prompt)\",\n \"follow the agent-driven flow: `context_pack` first, `context_after_turn` last.\",\n \"\",\n \"1. Start from the injected block; do not call `context_bootstrap` for orientation\",\n \" it already covers. Its `task files` are CodeBuddy's best match; when it names\",\n \" none, call `context_pack` with the task before searching the repository yourself.\",\n \"2. Read ranges, not whole files: `Read` with offset/limit, or `sed -n 'A,Bp'`.\",\n \" Large files read whole (`Read` without a range, `cat`, `less`) are denied with an\",\n \" outline of their symbols and line ranges; read the range you need from it.\",\n \"3. Call `context_pack` (or `context_before_edit` with exact paths) when the\",\n \" task needs symbols, neighbours, or file-level evidence the block lacks. Pass the\",\n \" block's `session` value as `sessionId` and the previous `delivery.deliveryId`\",\n \" as `ack` so unchanged sections are not resent.\",\n \"4. Prior work from earlier chats arrives as handoffs with freshness; re-check\",\n \" those files before relying on it.\",\n \"\",\n];\n\nconst MIDDLE = [\n \"## While implementing\",\n \"\",\n \"- Prefer the smallest sufficient set of edits; preserve existing behavior.\",\n \"- Honor risk warnings and policy rules for the files you touch; call out risk\",\n \" before editing sensitive areas.\",\n \"- Do not re-request broad context if the task pack already suffices.\",\n \"\",\n \"## Before finalizing\",\n \"\",\n \"- Consider calling `code_suggestions` (with `paths`, `planId`, or `useGit`) for\",\n \" read-only, evidence-backed quality findings; resolve high-severity ones.\",\n \"- Run the smallest relevant verification (tests, lint, typecheck) and fix\",\n \" failures before presenting completion.\",\n \"\",\n];\n\nconst AFTER_AGENT = [\n \"## After a meaningful turn\",\n \"\",\n \"- Call `context_after_turn` with a concise `summary`, the `changedFiles`, and a\",\n \" `planId` if one is active, plus `verification` (commands run and results)\",\n \" so the turn's worklog handoff records it. CodeBuddy auto-saves confident facts, decisions,\",\n \" and incidents, queues uncertain or sensitive items for review, and drops\",\n \" chatter. Never paste secrets, tokens, or credentials into the summary.\",\n \"\",\n];\n\nconst AFTER_HOOKS = [\n \"## After a meaningful turn\",\n \"\",\n \"- Do not call `context_after_turn`: the turn-end hook captures the turn once, from\",\n \" the real change set and the verification commands you ran. A second capture\",\n \" would record a divergent worklog entry.\",\n \"- Never paste secrets, tokens, or credentials into your final message; it is the\",\n \" capture summary.\",\n \"\",\n];\n\nconst TAIL = [\n \"## If a tool or service is unavailable (degraded mode)\",\n \"\",\n \"- A CodeBuddy MCP tool errors or is missing: proceed using the repository\",\n \" itself as the source of truth; do not block on it.\",\n \"- Postgres is unreachable: the file-based tools (context, plan, risk, map,\",\n \" suggest) still work; recall and embeddings may be degraded — say so instead\",\n \" of inventing remembered facts.\",\n \"- Retrieval returns no context: read the relevant files directly rather than\",\n \" guessing; the working tree is authoritative.\",\n \"\",\n \"## Never\",\n \"\",\n \"- Invent behavior the codebase does not support.\",\n \"- Ignore failing tests or lint.\",\n \"- Make speculative, wide changes without evidence.\",\n \"- Save low-confidence project memory as a durable fact.\",\n];\n\nfunction renderBody(mode: Exclude<AutomationMode, \"mcp-only\">): string {\n return [\n ...INTRO,\n ...(mode === \"hooks\" ? START_HOOKS : START_AGENT),\n ...MIDDLE,\n ...(mode === \"hooks\" ? AFTER_HOOKS : AFTER_AGENT),\n ...TAIL,\n ].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 \"This section is managed by CodeBuddy (`codebuddy clients install <client> --mode <mode>`)\",\n `and lives in \\`${file}\\`. Edit outside the managed markers to customize.`,\n `Automation mode: \\`${mode}\\` (${MODE_LABELS[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?: \"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: \"core\",\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","readFile","dirname","plugin","dirname","readFile","readFile","dirname","join","resolve","join","resolve","plugin","dirname","readFile","mkdir","readFile","dirname","join","resolve","join","resolve","readFile","mkdir","dirname","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,67 +1,12 @@
1
- // src/core/markdown-store-fs.ts
2
- import { lstat, open, rename } from "fs/promises";
3
- import { join, relative, resolve, sep } from "path";
4
- async function assertNoSymlinkBetween(root, target) {
5
- const absoluteRoot = resolve(root);
6
- const absoluteTarget = resolve(target);
7
- const relativeTarget = relative(absoluteRoot, absoluteTarget);
8
- if (relativeTarget === ".." || relativeTarget.startsWith(`..${sep}`)) {
9
- throw new Error(`Path escapes repository root: ${target}`);
10
- }
11
- let current = absoluteRoot;
12
- const parts = relativeTarget.split(sep).filter(Boolean);
13
- for (const part of parts) {
14
- current = join(current, part);
15
- try {
16
- const info = await lstat(current);
17
- if (info.isSymbolicLink()) {
18
- throw new Error(`Refusing to use symlinked path: ${current}`);
19
- }
20
- } catch (error) {
21
- if (error.code === "ENOENT") return;
22
- throw error;
23
- }
24
- }
25
- }
26
- async function writeAtomic(path, document, options) {
27
- const temporaryPath = `${path}.${process.pid}.${Date.now()}.tmp`;
28
- const handle = await open(temporaryPath, "wx", options?.mode ?? 384);
29
- try {
30
- await handle.writeFile(document, "utf8");
31
- await handle.sync();
32
- } finally {
33
- await handle.close();
34
- }
35
- await rename(temporaryPath, path);
36
- }
37
- function parseFrontMatter(document) {
38
- if (!document.startsWith("---\n")) {
39
- throw new Error("Markdown file must start with YAML front matter.");
40
- }
41
- const closingIndex = document.indexOf("\n---\n", 4);
42
- if (closingIndex === -1) {
43
- throw new Error("Markdown file has unterminated YAML front matter.");
44
- }
45
- return {
46
- frontMatter: document.slice(4, closingIndex),
47
- content: document.slice(closingIndex + 5)
48
- };
49
- }
50
- function composeMarkdown(frontMatterYaml, body) {
51
- const trimmedFrontMatter = frontMatterYaml.trimEnd();
52
- const content = body.endsWith("\n") ? body : `${body}
53
- `;
54
- return `---
55
- ${trimmedFrontMatter}
56
- ---
57
- ${content}`;
58
- }
1
+ import {
2
+ writeAtomic
3
+ } from "./chunk-CG5B3LYG.js";
59
4
 
60
5
  // src/core/config-file.ts
61
6
  import { execFile } from "child_process";
62
7
  import { constants } from "fs";
63
8
  import { access, chmod, mkdir, readdir, readFile, stat, writeFile } from "fs/promises";
64
- import { basename, join as join2, resolve as resolve2 } from "path";
9
+ import { basename, join, resolve } from "path";
65
10
  import { z } from "zod";
66
11
  var CONFIG_DIR = ".codebuddy";
67
12
  var CONFIG_FILE = "config.json";
@@ -99,7 +44,8 @@ var fileSchema = z.object({
99
44
  type: z.enum(["local", "huggingface", "openrouter"]).default("local"),
100
45
  apiKey: z.string().min(1).optional(),
101
46
  model: z.string().min(1).optional(),
102
- relevanceFloor: z.number().min(-1).max(1).optional()
47
+ relevanceFloor: z.number().min(-1).max(1).optional(),
48
+ recallFloor: z.number().min(-1).max(1).optional()
103
49
  }).optional(),
104
50
  /** Optional cross-encoder that orders recalled facts (off by default). */
105
51
  rerank: z.object({
@@ -113,6 +59,11 @@ var fileSchema = z.object({
113
59
  enabled: z.boolean(),
114
60
  graphPath: z.string().min(1).optional()
115
61
  }).optional(),
62
+ /** Git churn risk signal (`risk/signals/churn.ts`); defaults keep the 30-day window. */
63
+ risk: z.object({
64
+ churnWindowDays: z.number().int().positive().max(3650).optional(),
65
+ churnMode: z.enum(["window", "lifetime-size"]).optional()
66
+ }).optional(),
116
67
  /**
117
68
  * Optional hosted small-model layer (bring your own key). Keys are read from
118
69
  * the environment only, never from this file (`src/llm/config.ts`).
@@ -134,18 +85,20 @@ var fileSchema = z.object({
134
85
  zvec: z.object({
135
86
  enabled: z.boolean(),
136
87
  binary: z.string().min(1).optional(),
137
- timeoutMs: z.number().int().positive().max(1e4).optional()
88
+ timeoutMs: z.number().int().positive().max(1e4).optional(),
89
+ /** Task IDF coverage a vector-only file needs (0..1, default 0.3; docs/zvec-floor.md). */
90
+ minScore: z.number().min(0).max(1).optional()
138
91
  }).optional()
139
92
  });
140
93
  function configPath(cwd = process.cwd()) {
141
- return join2(cwd, CONFIG_DIR, CONFIG_FILE);
94
+ return join(cwd, CONFIG_DIR, CONFIG_FILE);
142
95
  }
143
96
  async function initProjectScaffold(cwd = process.cwd()) {
144
- const codebuddyDir = join2(cwd, CONFIG_DIR);
145
- await mkdir(join2(codebuddyDir, "memory", "facts"), { recursive: true, mode: 448 });
146
- await mkdir(join2(codebuddyDir, "memory", "summaries"), { recursive: true, mode: 448 });
147
- await mkdir(join2(codebuddyDir, "plans"), { recursive: true, mode: 448 });
148
- const gitignorePath = join2(codebuddyDir, ".gitignore");
97
+ const codebuddyDir = join(cwd, CONFIG_DIR);
98
+ await mkdir(join(codebuddyDir, "memory", "facts"), { recursive: true, mode: 448 });
99
+ await mkdir(join(codebuddyDir, "memory", "summaries"), { recursive: true, mode: 448 });
100
+ await mkdir(join(codebuddyDir, "plans"), { recursive: true, mode: 448 });
101
+ const gitignorePath = join(codebuddyDir, ".gitignore");
149
102
  try {
150
103
  await access(gitignorePath, constants.F_OK);
151
104
  let gitignore = await readFile(gitignorePath, "utf8");
@@ -162,7 +115,7 @@ ${missing.join("\n")}
162
115
  } catch {
163
116
  await writeFile(gitignorePath, PROJECT_GITIGNORE, { mode: 420 });
164
117
  }
165
- const policyPath = join2(codebuddyDir, POLICY_FILE);
118
+ const policyPath = join(codebuddyDir, POLICY_FILE);
166
119
  try {
167
120
  await access(policyPath, constants.F_OK);
168
121
  } catch {
@@ -235,7 +188,7 @@ function withoutRepositorySuppliedSecrets(config, path) {
235
188
  var warnedTracked = /* @__PURE__ */ new Set();
236
189
  var trackedCache = /* @__PURE__ */ new Map();
237
190
  function trackedByGit(root) {
238
- const key = resolve2(root);
191
+ const key = resolve(root);
239
192
  let pending = trackedCache.get(key);
240
193
  if (!pending) {
241
194
  pending = new Promise((done) => {
@@ -281,7 +234,7 @@ async function migrateLegacyNamespace(cwd = process.cwd()) {
281
234
  return target;
282
235
  }
283
236
  async function hasMemoryInNamespace(cwd, namespace) {
284
- const memoryDir = join2(cwd, ".codebuddy", "memory");
237
+ const memoryDir = join(cwd, ".codebuddy", "memory");
285
238
  let entries;
286
239
  try {
287
240
  entries = await readdir(memoryDir, { recursive: true });
@@ -291,7 +244,7 @@ async function hasMemoryInNamespace(cwd, namespace) {
291
244
  const line = new RegExp(`^namespace: ['"]?${namespace}['"]?$`, "m");
292
245
  for (const entry of entries) {
293
246
  if (!entry.endsWith(".md")) continue;
294
- const text = await readFile(join2(memoryDir, entry), "utf8").catch(() => "");
247
+ const text = await readFile(join(memoryDir, entry), "utf8").catch(() => "");
295
248
  if (line.test(text)) return true;
296
249
  }
297
250
  return false;
@@ -313,7 +266,8 @@ async function loadRuntimeConfig(cwd = process.cwd(), options = {}) {
313
266
  type: file.provider?.type ?? "local",
314
267
  ...hfToken ? { apiKey: hfToken } : {},
315
268
  ...file.provider?.model ? { model: file.provider.model } : {},
316
- ...file.provider?.relevanceFloor !== void 0 ? { relevanceFloor: file.provider.relevanceFloor } : {}
269
+ ...file.provider?.relevanceFloor !== void 0 ? { relevanceFloor: file.provider.relevanceFloor } : {},
270
+ ...file.provider?.recallFloor !== void 0 ? { recallFloor: file.provider.recallFloor } : {}
317
271
  },
318
272
  ...file.tokenBudget ? { tokenBudget: file.tokenBudget } : {},
319
273
  ...file.graphify ? { graphify: file.graphify } : {}
@@ -341,12 +295,12 @@ async function checkConfigPermissions(cwd = process.cwd()) {
341
295
  }
342
296
  }
343
297
  async function checkProjectScaffold(cwd = process.cwd()) {
344
- const codebuddyDir = join2(cwd, CONFIG_DIR);
345
- const memoryFactsDir = join2(codebuddyDir, "memory", "facts");
346
- const memorySummariesDir = join2(codebuddyDir, "memory", "summaries");
347
- const plansDir = join2(codebuddyDir, "plans");
348
- const gitignorePath = join2(codebuddyDir, ".gitignore");
349
- const policyPath = join2(codebuddyDir, POLICY_FILE);
298
+ const codebuddyDir = join(cwd, CONFIG_DIR);
299
+ const memoryFactsDir = join(codebuddyDir, "memory", "facts");
300
+ const memorySummariesDir = join(codebuddyDir, "memory", "summaries");
301
+ const plansDir = join(codebuddyDir, "plans");
302
+ const gitignorePath = join(codebuddyDir, ".gitignore");
303
+ const policyPath = join(codebuddyDir, POLICY_FILE);
350
304
  const [
351
305
  codebuddyDirExists,
352
306
  memoryFactsDirExists,
@@ -485,10 +439,6 @@ async function pathExists(path) {
485
439
  }
486
440
 
487
441
  export {
488
- assertNoSymlinkBetween,
489
- writeAtomic,
490
- parseFrontMatter,
491
- composeMarkdown,
492
442
  PLAN_POLICIES,
493
443
  configPath,
494
444
  initProjectScaffold,
@@ -509,4 +459,4 @@ export {
509
459
  setConfigBlock,
510
460
  redactSecrets
511
461
  };
512
- //# sourceMappingURL=chunk-5KZY6OTG.js.map
462
+ //# sourceMappingURL=chunk-KNR2P2UY.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/config-file.ts"],"sourcesContent":["import { execFile } from \"node:child_process\";\nimport { constants } from \"node:fs\";\nimport { access, chmod, mkdir, readdir, readFile, stat, writeFile } from \"node:fs/promises\";\nimport { basename, join, resolve } from \"node:path\";\nimport { z } from \"zod\";\nimport { writeAtomic } from \"./markdown-store-fs.js\";\nimport type { CodeBuddyConfig } from \"./types.js\";\n\nexport const CONFIG_DIR = \".codebuddy\";\nexport const CONFIG_FILE = \"config.json\";\nexport const POLICY_FILE = \"policies.yaml\";\n\nconst PROJECT_GITIGNORE = [\n \"# CodeBuddy local-only files\",\n \"/config.json\",\n \"/cache/\",\n \"/memory/review/\",\n \"/memory/review-archive/\",\n \"/plans/.locks/\",\n \"/memory/.locks/\",\n \"\",\n \"# Keep these reviewable project artifacts shareable by default:\",\n \"# /memory/facts/*.md\",\n \"# /memory/summaries/*.md\",\n \"# /plans/*.md\",\n \"# If your project memory is private, ignore /memory/ in the repo root .gitignore.\",\n \"\",\n].join(\"\\n\");\n\nconst DEFAULT_POLICY_FILE = [\n \"rules:\",\n \" - id: auth-sensitive\",\n ' pattern: \"src/auth/**\"',\n ' message: \"Auth changes need careful review.\"',\n \" weight: 70\",\n \"\",\n].join(\"\\n\");\n\n/**\n * Plan policy controls how strongly CodeBuddy nudges agents toward writing\n * a plan before substantive work:\n * - off: plans are never suggested or required.\n * - suggest: agents are reminded to plan, but small tasks stay lightweight (default).\n * - required: agents should refuse substantive changes without an active plan.\n */\nexport const PLAN_POLICIES = [\"off\", \"suggest\", \"required\"] as const;\nexport type PlanPolicy = (typeof PLAN_POLICIES)[number];\n\nconst fileSchema = z.object({\n postgresUrl: z.string().min(1).optional(),\n namespace: z.string().min(1).optional(),\n tokenBudget: z.number().int().positive().optional(),\n provider: z\n .object({\n type: z.enum([\"local\", \"huggingface\", \"openrouter\"]).default(\"local\"),\n apiKey: z.string().min(1).optional(),\n model: z.string().min(1).optional(),\n relevanceFloor: z.number().min(-1).max(1).optional(),\n recallFloor: z.number().min(-1).max(1).optional(),\n })\n .optional(),\n /** Optional cross-encoder that orders recalled facts (off by default). */\n rerank: z\n .object({\n provider: z.enum([\"off\", \"local\", \"huggingface\", \"openrouter\"]).default(\"off\"),\n model: z.string().min(1).optional(),\n })\n .optional(),\n plan: z\n .object({\n policy: z.enum(PLAN_POLICIES).default(\"suggest\"),\n })\n .optional(),\n graphify: z\n .object({\n enabled: z.boolean(),\n graphPath: z.string().min(1).optional(),\n })\n .optional(),\n /** Git churn risk signal (`risk/signals/churn.ts`); defaults keep the 30-day window. */\n risk: z\n .object({\n churnWindowDays: z.number().int().positive().max(3650).optional(),\n churnMode: z.enum([\"window\", \"lifetime-size\"]).optional(),\n })\n .optional(),\n /**\n * Optional hosted small-model layer (bring your own key). Keys are read from\n * the environment only, never from this file (`src/llm/config.ts`).\n */\n llm: z\n .object({\n enabled: z.boolean(),\n provider: z.enum([\"groq\", \"openrouter\"]).optional(),\n model: z.string().min(1).optional(),\n fallback: z\n .object({ provider: z.enum([\"groq\", \"openrouter\"]), model: z.string().min(1) })\n .optional(),\n timeoutMs: z.number().int().min(100).max(5_000).optional(),\n dailyTokenBudget: z.number().int().positive().optional(),\n features: z\n .object({\n summaries: z.boolean().optional(),\n rerank: z.boolean().optional(),\n memory: z.boolean().optional(),\n })\n .optional(),\n })\n .optional(),\n /** Optional local semantic retrieval via zvec-grep (`zg`). Off unless enabled. */\n zvec: z\n .object({\n enabled: z.boolean(),\n binary: z.string().min(1).optional(),\n timeoutMs: z.number().int().positive().max(10_000).optional(),\n /** Task IDF coverage a vector-only file needs (0..1, default 0.3; docs/zvec-floor.md). */\n minScore: z.number().min(0).max(1).optional(),\n })\n .optional(),\n});\n\nexport type CodeBuddyFileConfig = z.infer<typeof fileSchema>;\n\nexport type ConfigPermissionReport = {\n path: string;\n exists: boolean;\n mode: string | null;\n ok: boolean;\n warning?: string;\n};\n\nexport type ProjectScaffoldReport = {\n codebuddyDir: { path: string; exists: boolean; ok: boolean; warning?: string };\n memoryFactsDir: { path: string; exists: boolean; ok: boolean; warning?: string };\n memorySummariesDir: { path: string; exists: boolean; ok: boolean; warning?: string };\n plansDir: { path: string; exists: boolean; ok: boolean; warning?: string };\n gitignore: { path: string; exists: boolean; ok: boolean; warning?: string };\n policies: { path: string; exists: boolean; ok: boolean; warning?: string };\n};\n\nexport function configPath(cwd = process.cwd()): string {\n return join(cwd, CONFIG_DIR, CONFIG_FILE);\n}\n\nexport async function initProjectScaffold(cwd = process.cwd()): Promise<{\n codebuddyDir: string;\n gitignorePath: string;\n policyPath: string;\n}> {\n const codebuddyDir = join(cwd, CONFIG_DIR);\n await mkdir(join(codebuddyDir, \"memory\", \"facts\"), { recursive: true, mode: 0o700 });\n await mkdir(join(codebuddyDir, \"memory\", \"summaries\"), { recursive: true, mode: 0o700 });\n await mkdir(join(codebuddyDir, \"plans\"), { recursive: true, mode: 0o700 });\n\n const gitignorePath = join(codebuddyDir, \".gitignore\");\n try {\n await access(gitignorePath, constants.F_OK);\n let gitignore = await readFile(gitignorePath, \"utf8\");\n const lines = new Set(gitignore.split(/\\r?\\n/));\n const missing = [\"/memory/review/\", \"/memory/review-archive/\"].filter(\n (line) => !lines.has(line),\n );\n if (missing.length > 0) {\n gitignore = `${gitignore.trimEnd()}\\n${missing.join(\"\\n\")}\\n`;\n await writeFile(gitignorePath, gitignore, { mode: 0o644 });\n }\n } catch {\n await writeFile(gitignorePath, PROJECT_GITIGNORE, { mode: 0o644 });\n }\n\n const policyPath = join(codebuddyDir, POLICY_FILE);\n try {\n await access(policyPath, constants.F_OK);\n } catch {\n await writeFile(policyPath, DEFAULT_POLICY_FILE, { mode: 0o644 });\n }\n\n return { codebuddyDir, gitignorePath, policyPath };\n}\n\nexport async function initConfigFile(\n cwd = process.cwd(),\n): Promise<{ path: string; created: boolean }> {\n const path = configPath(cwd);\n await initProjectScaffold(cwd);\n try {\n await access(path, constants.F_OK);\n await chmod(path, 0o600);\n return { path, created: false };\n } catch {\n const template: CodeBuddyFileConfig = {\n postgresUrl: process.env.DATABASE_URL ?? \"postgres://localhost:5432/codebuddy\",\n // Default to the folder name (sanitized) so a fresh scaffold agrees with\n // doctor's expected default instead of warning immediately after init.\n namespace: defaultNamespaceForCwd(cwd),\n provider: { type: \"local\" },\n tokenBudget: 4000,\n };\n await writeFile(path, `${JSON.stringify(template, null, 2)}\\n`, { mode: 0o600 });\n await chmod(path, 0o600);\n return { path, created: true };\n }\n}\n\n/** Persist a complete project config without changing its private permissions. */\nexport async function writeConfigFile(\n config: CodeBuddyFileConfig,\n cwd = process.cwd(),\n): Promise<{ path: string }> {\n const path = configPath(cwd);\n await initProjectScaffold(cwd);\n await writeFile(path, `${JSON.stringify(config, null, 2)}\\n`, { mode: 0o600 });\n await chmod(path, 0o600);\n return { path };\n}\n\nexport async function readConfigFile(cwd = process.cwd()): Promise<CodeBuddyFileConfig> {\n const path = configPath(cwd);\n let parsed: CodeBuddyFileConfig;\n try {\n const raw = await readFile(path, \"utf8\");\n parsed = fileSchema.parse(JSON.parse(raw));\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") return {};\n throw error;\n }\n if ((parsed.zvec?.binary !== undefined || parsed.postgresUrl) && (await trackedByGit(cwd))) {\n return withoutRepositorySuppliedSecrets(parsed, path);\n }\n return parsed;\n}\n\n/**\n * `init` gitignores `.codebuddy/config.json`, so a tracked one came with the\n * repository, not from this user. A cloned repository must not choose a\n * program for CodeBuddy to run (`zvec.binary`) or a database to send project\n * memory to (`postgresUrl`); those two keys are ignored in that case and must\n * come from the environment (DATABASE_URL) or an untracked config.\n */\nfunction withoutRepositorySuppliedSecrets(\n config: CodeBuddyFileConfig,\n path: string,\n): CodeBuddyFileConfig {\n const { postgresUrl: _postgresUrl, ...rest } = config;\n const ignored = [\n ...(config.postgresUrl ? [\"postgresUrl\"] : []),\n ...(config.zvec?.binary !== undefined ? [\"zvec.binary\"] : []),\n ];\n if (!warnedTracked.has(path)) {\n warnedTracked.add(path);\n process.stderr.write(\n `codebuddy: ignoring ${ignored.join(\" and \")} in ${path}: the file is tracked by git, so it came with the repository. ` +\n \"Untrack it (git rm --cached) or set DATABASE_URL.\\n\",\n );\n }\n if (!rest.zvec) return rest;\n const { binary: _binary, ...zvec } = rest.zvec;\n return { ...rest, zvec };\n}\n\nconst warnedTracked = new Set<string>();\nconst trackedCache = new Map<string, Promise<boolean>>();\n\n/** Whether `.codebuddy/config.json` under `root` is tracked by git (cached per process). */\nfunction trackedByGit(root: string): Promise<boolean> {\n const key = resolve(root);\n let pending = trackedCache.get(key);\n if (!pending) {\n pending = new Promise((done) => {\n execFile(\n \"git\",\n [\"ls-files\", \"--error-unmatch\", \"--\", \".codebuddy/config.json\"],\n { cwd: key, timeout: 5_000 },\n (error) => done(error === null),\n );\n });\n trackedCache.set(key, pending);\n }\n return pending;\n}\n\n/**\n * The ONE namespace-resolution rule, shared by the DB/MCP runtime, every\n * file-based CLI command, and doctor. Precedence:\n * 1. CODEBUDDY_NAMESPACE env\n * 2. `.codebuddy/config.json` `namespace`\n * 3. the current folder name (sanitized) — matching what doctor reports as\n * the expected default.\n */\nexport function sanitizeNamespace(value: string): string {\n return value.replace(/[^a-zA-Z0-9_.:-]/g, \"-\").slice(0, 128);\n}\n\n/** Folder-name fallback used when neither env nor config specify a namespace. */\nexport function defaultNamespaceForCwd(cwd = process.cwd()): string {\n return sanitizeNamespace(basename(cwd)) || \"default\";\n}\n\nexport async function resolveNamespaceFrom(cwd = process.cwd()): Promise<string> {\n if (process.env.CODEBUDDY_NAMESPACE) return process.env.CODEBUDDY_NAMESPACE;\n const file = await readConfigFile(cwd);\n return file.namespace ?? defaultNamespaceForCwd(cwd);\n}\n\n/** Scaffolds before 2.0 wrote this literal, so every project shared one namespace. */\nconst LEGACY_NAMESPACE = \"default\";\n\n/**\n * Rewrites a legacy `\"namespace\": \"default\"` in `.codebuddy/config.json` to the\n * folder's default and returns the new namespace, or null when nothing changed.\n * Skipped when the file is tracked by git (it belongs to the repository) or\n * when Markdown memory was saved under \"default\" (it would drop out of recall).\n */\nexport async function migrateLegacyNamespace(cwd = process.cwd()): Promise<string | null> {\n const target = defaultNamespaceForCwd(cwd);\n if (target === LEGACY_NAMESPACE) return null;\n const path = configPath(cwd);\n let raw: Record<string, unknown>;\n try {\n raw = JSON.parse(await readFile(path, \"utf8\")) as Record<string, unknown>;\n } catch {\n return null;\n }\n if (raw.namespace !== LEGACY_NAMESPACE) return null;\n if ((await trackedByGit(cwd)) || (await hasMemoryInNamespace(cwd, LEGACY_NAMESPACE))) {\n return null;\n }\n await writeAtomic(path, `${JSON.stringify({ ...raw, namespace: target }, null, 2)}\\n`);\n return target;\n}\n\nasync function hasMemoryInNamespace(cwd: string, namespace: string): Promise<boolean> {\n const memoryDir = join(cwd, \".codebuddy\", \"memory\");\n let entries: string[];\n try {\n entries = await readdir(memoryDir, { recursive: true });\n } catch {\n return false;\n }\n const line = new RegExp(`^namespace: ['\"]?${namespace}['\"]?$`, \"m\");\n for (const entry of entries) {\n if (!entry.endsWith(\".md\")) continue;\n const text = await readFile(join(memoryDir, entry), \"utf8\").catch(() => \"\");\n if (line.test(text)) return true;\n }\n return false;\n}\n\nexport const MISSING_DATABASE_URL =\n \"DATABASE_URL or .codebuddy/config.json postgresUrl is required.\";\n\n/**\n * `requireDatabase: false` (the MCP server) returns an empty `postgresUrl`\n * instead of throwing, so file-based tools still run without PostgreSQL;\n * `createRuntime` rejects the empty URL with the same message.\n */\nexport async function loadRuntimeConfig(\n cwd = process.cwd(),\n options: { requireDatabase?: boolean } = {},\n): Promise<CodeBuddyConfig> {\n const projectRoot = process.env.CODEBUDDY_PROJECT_ROOT ?? cwd;\n const file = await readConfigFile(projectRoot);\n const postgresUrl = process.env.DATABASE_URL ?? file.postgresUrl ?? \"\";\n if (!postgresUrl && options.requireDatabase !== false) {\n throw new Error(MISSING_DATABASE_URL);\n }\n const hfToken = process.env.HF_TOKEN ?? file.provider?.apiKey;\n return {\n postgresUrl,\n namespace:\n process.env.CODEBUDDY_NAMESPACE ?? file.namespace ?? defaultNamespaceForCwd(projectRoot),\n projectRoot,\n provider: {\n type: file.provider?.type ?? \"local\",\n ...(hfToken ? { apiKey: hfToken } : {}),\n ...(file.provider?.model ? { model: file.provider.model } : {}),\n ...(file.provider?.relevanceFloor !== undefined\n ? { relevanceFloor: file.provider.relevanceFloor }\n : {}),\n ...(file.provider?.recallFloor !== undefined\n ? { recallFloor: file.provider.recallFloor }\n : {}),\n },\n ...(file.tokenBudget ? { tokenBudget: file.tokenBudget } : {}),\n ...(file.graphify ? { graphify: file.graphify } : {}),\n };\n}\n\nexport async function checkConfigPermissions(cwd = process.cwd()): Promise<ConfigPermissionReport> {\n const path = configPath(cwd);\n try {\n const info = await stat(path);\n const modeNumber = info.mode & 0o777;\n const mode = modeNumber.toString(8).padStart(4, \"0\");\n // Windows has no POSIX mode bits (stat reports 0o666/0o444 whatever chmod\n // did); the file is protected by the user profile's ACL instead.\n const ok = process.platform === \"win32\" || modeNumber === 0o600;\n return {\n path,\n exists: true,\n mode,\n ok,\n ...(ok ? {} : { warning: \".codebuddy/config.json should be chmod 0600.\" }),\n };\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") {\n return { path, exists: false, mode: null, ok: true };\n }\n throw error;\n }\n}\n\nexport async function checkProjectScaffold(cwd = process.cwd()): Promise<ProjectScaffoldReport> {\n const codebuddyDir = join(cwd, CONFIG_DIR);\n const memoryFactsDir = join(codebuddyDir, \"memory\", \"facts\");\n const memorySummariesDir = join(codebuddyDir, \"memory\", \"summaries\");\n const plansDir = join(codebuddyDir, \"plans\");\n const gitignorePath = join(codebuddyDir, \".gitignore\");\n const policyPath = join(codebuddyDir, POLICY_FILE);\n\n const [\n codebuddyDirExists,\n memoryFactsDirExists,\n memorySummariesDirExists,\n plansDirExists,\n gitignoreExists,\n policiesExist,\n ] = await Promise.all([\n pathExists(codebuddyDir),\n pathExists(memoryFactsDir),\n pathExists(memorySummariesDir),\n pathExists(plansDir),\n pathExists(gitignorePath),\n pathExists(policyPath),\n ]);\n\n return {\n codebuddyDir: {\n path: codebuddyDir,\n exists: codebuddyDirExists,\n ok: codebuddyDirExists,\n ...(codebuddyDirExists\n ? {}\n : { warning: \"Run `codebuddy use` or `codebuddy init` to create the project scaffold.\" }),\n },\n memoryFactsDir: {\n path: memoryFactsDir,\n exists: memoryFactsDirExists,\n ok: memoryFactsDirExists,\n ...(memoryFactsDirExists ? {} : { warning: \"Missing .codebuddy/memory/facts directory.\" }),\n },\n memorySummariesDir: {\n path: memorySummariesDir,\n exists: memorySummariesDirExists,\n ok: memorySummariesDirExists,\n ...(memorySummariesDirExists\n ? {}\n : { warning: \"Missing .codebuddy/memory/summaries directory.\" }),\n },\n plansDir: {\n path: plansDir,\n exists: plansDirExists,\n ok: plansDirExists,\n ...(plansDirExists ? {} : { warning: \"Missing .codebuddy/plans directory.\" }),\n },\n gitignore: {\n path: gitignorePath,\n exists: gitignoreExists,\n ok: gitignoreExists,\n ...(gitignoreExists ? {} : { warning: \"Missing .codebuddy/.gitignore.\" }),\n },\n policies: {\n path: policyPath,\n exists: policiesExist,\n ok: policiesExist,\n ...(policiesExist\n ? {}\n : {\n warning:\n \"Missing .codebuddy/policies.yaml. Run `codebuddy use` or `codebuddy init` to scaffold it.\",\n }),\n },\n };\n}\n\n/** Read the plan policy, honoring CODEBUDDY_PLAN_POLICY then the config file. */\nexport async function loadPlanPolicy(cwd = process.cwd()): Promise<PlanPolicy> {\n const fromEnv = process.env.CODEBUDDY_PLAN_POLICY;\n if (fromEnv && (PLAN_POLICIES as readonly string[]).includes(fromEnv)) {\n return fromEnv as PlanPolicy;\n }\n const file = await readConfigFile(cwd);\n return file.plan?.policy ?? \"suggest\";\n}\n\n/** Persist the plan policy into `.codebuddy/config.json`, creating it if needed. */\nexport async function setPlanPolicy(policy: PlanPolicy, cwd = process.cwd()): Promise<void> {\n await initConfigFile(cwd);\n const path = configPath(cwd);\n const raw = JSON.parse(await readFile(path, \"utf8\")) as Record<string, unknown>;\n raw.plan = { ...(raw.plan as Record<string, unknown> | undefined), policy };\n // Atomic rewrite: this file holds the postgresUrl and provider apiKey.\n await writeAtomic(path, `${JSON.stringify(raw, null, 2)}\\n`, { mode: 0o600 });\n}\n\n/** Turn optional zvec-grep retrieval on or off in `.codebuddy/config.json`. */\nexport async function setZvecEnabled(enabled: boolean, cwd = process.cwd()): Promise<void> {\n await initConfigFile(cwd);\n const path = configPath(cwd);\n const raw = JSON.parse(await readFile(path, \"utf8\")) as Record<string, unknown>;\n raw.zvec = { ...(raw.zvec as Record<string, unknown> | undefined), enabled };\n await writeAtomic(path, `${JSON.stringify(raw, null, 2)}\\n`, { mode: 0o600 });\n}\n\n/** Turn the BYOK LLM layer on or off; never writes a key (keys live in the environment). */\nexport async function setLlmEnabled(\n enabled: boolean,\n options: { provider?: \"groq\" | \"openrouter\"; model?: string } = {},\n cwd = process.cwd(),\n): Promise<void> {\n await initConfigFile(cwd);\n const path = configPath(cwd);\n const raw = JSON.parse(await readFile(path, \"utf8\")) as Record<string, unknown>;\n raw.llm = {\n ...(raw.llm as Record<string, unknown> | undefined),\n enabled,\n ...(options.provider ? { provider: options.provider } : {}),\n ...(options.model ? { model: options.model } : {}),\n };\n await writeAtomic(path, `${JSON.stringify(raw, null, 2)}\\n`, { mode: 0o600 });\n}\n\n/**\n * Replace one top-level block of `.codebuddy/config.json` (`provider`,\n * `rerank`), keeping every other key and the private file mode. The result\n * must still parse, so a bad value never reaches disk.\n */\nexport async function setConfigBlock(\n key: \"provider\" | \"rerank\",\n value: Record<string, unknown>,\n cwd = process.cwd(),\n): Promise<void> {\n await initConfigFile(cwd);\n const path = configPath(cwd);\n const raw = JSON.parse(await readFile(path, \"utf8\")) as Record<string, unknown>;\n raw[key] = value;\n fileSchema.parse(raw);\n await writeAtomic(path, `${JSON.stringify(raw, null, 2)}\\n`, { mode: 0o600 });\n}\n\nexport function redactSecrets(value: unknown): unknown {\n if (typeof value === \"string\") {\n let redacted = value;\n // Provider keys (HF, BYOK LLM) literally anywhere in a string.\n for (const key of [\n process.env.HF_TOKEN,\n process.env.GROQ_API_KEY,\n process.env.OPENROUTER_API_KEY,\n process.env.CODEBUDDY_LLM_API_KEY,\n ]) {\n if (key) redacted = redacted.split(key).join(\"[REDACTED]\");\n }\n // Credentials embedded in connection URLs (postgres://user:pass@host…).\n // The password — not the username — is the secret.\n redacted = redacted.replace(\n /(\\b[a-z][a-z0-9+.-]*:\\/\\/[^:/\\s@]+:)[^/\\s@]+(@)/gi,\n \"$1[REDACTED]$2\",\n );\n return redacted;\n }\n if (Array.isArray(value)) return value.map(redactSecrets);\n if (!value || typeof value !== \"object\") return value;\n return Object.fromEntries(\n Object.entries(value as Record<string, unknown>).map(([key, nested]) => [\n key,\n /token|apiKey|authorization|secret/i.test(key) ? \"[REDACTED]\" : redactSecrets(nested),\n ]),\n );\n}\n\nasync function pathExists(path: string): Promise<boolean> {\n try {\n await access(path, constants.F_OK);\n return true;\n } catch {\n return false;\n }\n}\n"],"mappings":"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","names":[]}