@orquentra/codebuddy 2.9.0 → 2.15.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 (117) hide show
  1. package/ARCHITECTURE.md +98 -0
  2. package/CLAUDE_CODEX.md +33 -0
  3. package/README.md +114 -9
  4. package/dist/{adapters-S4HHTWJW.js → adapters-OJRSVFV3.js} +2 -2
  5. package/dist/{chunk-GXOML4UZ.js → chunk-4BSO3HST.js} +126 -18
  6. package/dist/chunk-4BSO3HST.js.map +1 -0
  7. package/dist/{chunk-CAEPHLHC.js → chunk-57642NGU.js} +2 -44
  8. package/dist/chunk-57642NGU.js.map +1 -0
  9. package/dist/{chunk-ESZXCZIN.js → chunk-6YI2D5F5.js} +33 -5
  10. package/dist/chunk-6YI2D5F5.js.map +1 -0
  11. package/dist/{chunk-GKNYMNFM.js → chunk-7IPSDDO5.js} +10 -3
  12. package/dist/chunk-7IPSDDO5.js.map +1 -0
  13. package/dist/chunk-BTTQWSDS.js +372 -0
  14. package/dist/chunk-BTTQWSDS.js.map +1 -0
  15. package/dist/{chunk-2WH2Q4TA.js → chunk-D5E7SGI5.js} +304 -77
  16. package/dist/chunk-D5E7SGI5.js.map +1 -0
  17. package/dist/chunk-EMWGLKPR.js +45 -0
  18. package/dist/chunk-EMWGLKPR.js.map +1 -0
  19. package/dist/chunk-GQKWUHMA.js +20 -0
  20. package/dist/chunk-GQKWUHMA.js.map +1 -0
  21. package/dist/chunk-ITXE27SZ.js +358 -0
  22. package/dist/chunk-ITXE27SZ.js.map +1 -0
  23. package/dist/{chunk-UK6XTS2Y.js → chunk-JJQDVGF7.js} +23 -23
  24. package/dist/chunk-JJQDVGF7.js.map +1 -0
  25. package/dist/{chunk-F4BJ3DXW.js → chunk-KQCP3ZYH.js} +1273 -331
  26. package/dist/chunk-KQCP3ZYH.js.map +1 -0
  27. package/dist/{chunk-QN76UJP7.js → chunk-KYRM3HPP.js} +89 -21
  28. package/dist/chunk-KYRM3HPP.js.map +1 -0
  29. package/dist/{chunk-KNR2P2UY.js → chunk-M75SN4D4.js} +19 -4
  30. package/dist/chunk-M75SN4D4.js.map +1 -0
  31. package/dist/{chunk-J4T6HHI4.js → chunk-RZ266NHE.js} +538 -317
  32. package/dist/chunk-RZ266NHE.js.map +1 -0
  33. package/dist/chunk-U7KDM62P.js +43 -0
  34. package/dist/chunk-U7KDM62P.js.map +1 -0
  35. package/dist/chunk-UATJCD2T.js +368 -0
  36. package/dist/chunk-UATJCD2T.js.map +1 -0
  37. package/dist/{chunk-ENKDFLOO.js → chunk-UOTKHSQ5.js} +25 -8
  38. package/dist/chunk-UOTKHSQ5.js.map +1 -0
  39. package/dist/{chunk-TLWGZVPC.js → chunk-UR3DF74I.js} +1007 -132
  40. package/dist/chunk-UR3DF74I.js.map +1 -0
  41. package/dist/chunk-UTPC66MV.js +128 -0
  42. package/dist/chunk-UTPC66MV.js.map +1 -0
  43. package/dist/{chunk-W7GAXL6C.js → chunk-V7PT6ER5.js} +6 -5
  44. package/dist/chunk-V7PT6ER5.js.map +1 -0
  45. package/dist/chunk-WHKXUXHE.js +156 -0
  46. package/dist/chunk-WHKXUXHE.js.map +1 -0
  47. package/dist/{chunk-SRA4MDVG.js → chunk-YMXFSHXT.js} +978 -950
  48. package/dist/chunk-YMXFSHXT.js.map +1 -0
  49. package/dist/cli/index.js +23 -9
  50. package/dist/cli/index.js.map +1 -1
  51. package/dist/{codebuddy-DO7ZhoWn.d.ts → codebuddy-CHqMptZB.d.ts} +35 -0
  52. package/dist/config-TXQJCCVI.js +15 -0
  53. package/dist/expand-KWYGNCKZ.js +98 -0
  54. package/dist/expand-KWYGNCKZ.js.map +1 -0
  55. package/dist/file-vectors-MKXTSGGH.js +19 -0
  56. package/dist/index-facts-4MTEHKMN.js +38 -0
  57. package/dist/index-pass-TBDUEZCB.js +14 -0
  58. package/dist/index.d.ts +6 -4
  59. package/dist/index.js +25 -23
  60. package/dist/install-E3JWINTN.js +31 -0
  61. package/dist/langgraph/index.d.ts +1 -1
  62. package/dist/lean-MLTAFS5L.js +29 -0
  63. package/dist/local-75DJECVR.js +48 -0
  64. package/dist/local-llm-MECVJTVU.js +19 -0
  65. package/dist/{main-YJKSYFPT.js → main-RLMKQZW4.js} +999 -328
  66. package/dist/main-RLMKQZW4.js.map +1 -0
  67. package/dist/{memory-TGHVTBME.js → memory-PFFVMTCH.js} +9 -6
  68. package/dist/{memory-TGHVTBME.js.map → memory-PFFVMTCH.js.map} +1 -1
  69. package/dist/{operations-OG4ISWIM.js → operations-STR7S5UO.js} +8 -7
  70. package/dist/operations-STR7S5UO.js.map +1 -0
  71. package/dist/{pretool-command-PRFOIHMW.js → pretool-command-B5GY5T6E.js} +2 -2
  72. package/dist/pretool-command-B5GY5T6E.js.map +1 -0
  73. package/dist/prompt-gate-TDTFFQW2.js +8 -0
  74. package/dist/prompt-gate-TDTFFQW2.js.map +1 -0
  75. package/dist/{rerank-IQAEWLQR.js → rerank-XXKWDI5Q.js} +7 -4
  76. package/dist/{rerank-IQAEWLQR.js.map → rerank-XXKWDI5Q.js.map} +1 -1
  77. package/dist/{runtime-GCSEIAVD.js → runtime-SWFWGTQ4.js} +18 -9
  78. package/dist/runtime-SWFWGTQ4.js.map +1 -0
  79. package/dist/summaries-QXWFFDUZ.js +31 -0
  80. package/dist/summaries-QXWFFDUZ.js.map +1 -0
  81. package/dist/{zvec-XDZNZ2OL.js → zvec-F73IBWO5.js} +13 -6
  82. package/dist/zvec-F73IBWO5.js.map +1 -0
  83. package/package.json +13 -3
  84. package/dist/chunk-2WH2Q4TA.js.map +0 -1
  85. package/dist/chunk-6NZEZJYD.js +0 -206
  86. package/dist/chunk-6NZEZJYD.js.map +0 -1
  87. package/dist/chunk-CAEPHLHC.js.map +0 -1
  88. package/dist/chunk-ENKDFLOO.js.map +0 -1
  89. package/dist/chunk-ESZXCZIN.js.map +0 -1
  90. package/dist/chunk-F4BJ3DXW.js.map +0 -1
  91. package/dist/chunk-GKNYMNFM.js.map +0 -1
  92. package/dist/chunk-GXOML4UZ.js.map +0 -1
  93. package/dist/chunk-ITQO632W.js +0 -673
  94. package/dist/chunk-ITQO632W.js.map +0 -1
  95. package/dist/chunk-J4T6HHI4.js.map +0 -1
  96. package/dist/chunk-KNR2P2UY.js.map +0 -1
  97. package/dist/chunk-QN76UJP7.js.map +0 -1
  98. package/dist/chunk-SFMIMLXR.js +0 -63
  99. package/dist/chunk-SFMIMLXR.js.map +0 -1
  100. package/dist/chunk-SRA4MDVG.js.map +0 -1
  101. package/dist/chunk-TLWGZVPC.js.map +0 -1
  102. package/dist/chunk-UK6XTS2Y.js.map +0 -1
  103. package/dist/chunk-W7GAXL6C.js.map +0 -1
  104. package/dist/config-GEZFLXK6.js +0 -12
  105. package/dist/install-BXM56QCD.js +0 -19
  106. package/dist/lean-command-7DFW32MA.js +0 -19
  107. package/dist/main-YJKSYFPT.js.map +0 -1
  108. package/dist/summaries-ML53R7EL.js +0 -21
  109. /package/dist/{adapters-S4HHTWJW.js.map → adapters-OJRSVFV3.js.map} +0 -0
  110. /package/dist/{config-GEZFLXK6.js.map → config-TXQJCCVI.js.map} +0 -0
  111. /package/dist/{install-BXM56QCD.js.map → file-vectors-MKXTSGGH.js.map} +0 -0
  112. /package/dist/{lean-command-7DFW32MA.js.map → index-facts-4MTEHKMN.js.map} +0 -0
  113. /package/dist/{operations-OG4ISWIM.js.map → index-pass-TBDUEZCB.js.map} +0 -0
  114. /package/dist/{pretool-command-PRFOIHMW.js.map → install-E3JWINTN.js.map} +0 -0
  115. /package/dist/{runtime-GCSEIAVD.js.map → lean-MLTAFS5L.js.map} +0 -0
  116. /package/dist/{summaries-ML53R7EL.js.map → local-75DJECVR.js.map} +0 -0
  117. /package/dist/{zvec-XDZNZ2OL.js.map → local-llm-MECVJTVU.js.map} +0 -0
@@ -1,18 +1,16 @@
1
- import {
2
- renderLeanBlock,
3
- runLeanHookCommand
4
- } from "./chunk-UK6XTS2Y.js";
5
1
  import {
6
2
  runPreToolHookCommand
7
- } from "./chunk-ENKDFLOO.js";
3
+ } from "./chunk-UOTKHSQ5.js";
8
4
  import {
9
- failSoft,
10
- readHookInput,
11
- writeStdout
12
- } from "./chunk-FJOTP632.js";
5
+ LOCAL_LLM_SIZES,
6
+ addLocalLlm,
7
+ printLocalLlmStatus,
8
+ removeLocalLlm
9
+ } from "./chunk-UTPC66MV.js";
13
10
  import {
14
11
  WORKFLOW_CLIENTS,
15
12
  clientDisplayName,
13
+ clientHookCapabilities,
16
14
  clientHooksInstalled,
17
15
  defaultRulesMode,
18
16
  detectClient,
@@ -29,20 +27,16 @@ import {
29
27
  removeCodexEntry,
30
28
  renderManagedBlock,
31
29
  renderWorkflowTemplate,
30
+ runBridgeHook,
31
+ runCodexHook,
32
32
  setHooks,
33
33
  uninstallClient,
34
34
  writeProjectMcpConfig
35
- } from "./chunk-J4T6HHI4.js";
35
+ } from "./chunk-RZ266NHE.js";
36
36
  import {
37
- AUTOMATION_MODES,
38
- CLIENT_ADAPTERS,
39
- CLIENT_IDS,
40
- LIFECYCLE_EVENTS,
41
- MODE_LABELS,
42
- parseClient,
43
- parseMode,
44
- supportedModes
45
- } from "./chunk-W7GAXL6C.js";
37
+ renderLeanBlock,
38
+ runLeanHook
39
+ } from "./chunk-JJQDVGF7.js";
46
40
  import {
47
41
  isDockerDaemonRunning,
48
42
  tryStartDockerDesktop,
@@ -53,8 +47,40 @@ import {
53
47
  postgresStatus,
54
48
  postgresUp
55
49
  } from "./chunk-CSRD5OQA.js";
50
+ import {
51
+ failSoft,
52
+ readHookInput,
53
+ writeStdout
54
+ } from "./chunk-FJOTP632.js";
55
+ import {
56
+ loadLlmSummaries,
57
+ summarizeRepository
58
+ } from "./chunk-ITXE27SZ.js";
59
+ import {
60
+ listIndexFacts,
61
+ pruneIndexFacts
62
+ } from "./chunk-UATJCD2T.js";
63
+ import {
64
+ chatJson,
65
+ coolingDown,
66
+ usageToday
67
+ } from "./chunk-6YI2D5F5.js";
68
+ import {
69
+ runIndexModelPass
70
+ } from "./chunk-U7KDM62P.js";
71
+ import {
72
+ AUTOMATION_MODES,
73
+ CLIENT_ADAPTERS,
74
+ CLIENT_IDS,
75
+ LIFECYCLE_EVENTS,
76
+ MODE_LABELS,
77
+ parseClient,
78
+ parseMode,
79
+ supportedModes
80
+ } from "./chunk-V7PT6ER5.js";
56
81
  import {
57
82
  TELEMETRY_NOTICE,
83
+ findReuse,
58
84
  flushTelemetry,
59
85
  generateSuggestions,
60
86
  migrateMemoryToFiles,
@@ -68,13 +94,9 @@ import {
68
94
  traceCliCommand,
69
95
  watchRepository,
70
96
  writeTelemetryState
71
- } from "./chunk-2WH2Q4TA.js";
72
- import {
73
- DEFAULT_ZVEC_MODEL,
74
- buildZvecIndex,
75
- zvecStatus
76
- } from "./chunk-GXOML4UZ.js";
97
+ } from "./chunk-D5E7SGI5.js";
77
98
  import {
99
+ HookStagingStore,
78
100
  PlanFileStore,
79
101
  PlanLifecycle,
80
102
  ReviewStore,
@@ -83,29 +105,62 @@ import {
83
105
  assessRisk,
84
106
  buildBeforeEditContext,
85
107
  buildBootstrapContext,
108
+ findCodexRollouts,
109
+ forgetJournal,
110
+ importCodexRollout,
111
+ journalLag,
112
+ latestUnfinished,
113
+ listJournals,
86
114
  openHookSemantics,
115
+ readCheckpoint,
116
+ renderCheckpointNow,
87
117
  renderContextBlock,
88
118
  resolveRiskPaths,
89
119
  runCaptureHook,
120
+ runCheckpointHook,
90
121
  runContextHook,
91
122
  runResetHook,
123
+ runResumeHook,
92
124
  runStageHook,
93
- runSyncHook
94
- } from "./chunk-F4BJ3DXW.js";
95
- import {
96
- SessionCapsuleLedger,
97
- detectTestCommand,
98
- estimateTokens
99
- } from "./chunk-GKNYMNFM.js";
125
+ runSyncHook,
126
+ syncReuseVectors
127
+ } from "./chunk-KQCP3ZYH.js";
100
128
  import {
101
129
  parseTraceLines,
102
130
  readTraces,
103
131
  tracePath
104
132
  } from "./chunk-YN7TEIUH.js";
133
+ import {
134
+ SessionCapsuleLedger,
135
+ detectTestCommand,
136
+ estimateTokens
137
+ } from "./chunk-7IPSDDO5.js";
105
138
  import {
106
139
  hookRoot
107
140
  } from "./chunk-TGFTKWOR.js";
141
+ import "./chunk-GQKWUHMA.js";
108
142
  import "./chunk-DFV2SK6C.js";
143
+ import {
144
+ DEFAULT_ZVEC_MODEL,
145
+ buildZvecIndex,
146
+ zvecStatus
147
+ } from "./chunk-4BSO3HST.js";
148
+ import {
149
+ buildArchitectureMap,
150
+ generateGraphifyGraph,
151
+ indexRepository,
152
+ loadFreshIndex,
153
+ loadGraphifyMap,
154
+ loadProjectArchitectureMap,
155
+ neighbours,
156
+ queryMap,
157
+ setupGraphify,
158
+ syncFileVectors
159
+ } from "./chunk-UR3DF74I.js";
160
+ import {
161
+ extractSymbolTable,
162
+ supportsSymbols
163
+ } from "./chunk-Y7D7PUBE.js";
109
164
  import {
110
165
  bootstrapDatabase,
111
166
  buildProviderFromConfig,
@@ -117,58 +172,25 @@ import {
117
172
  pingDatabase,
118
173
  runDoctor,
119
174
  runMigrations
120
- } from "./chunk-QN76UJP7.js";
175
+ } from "./chunk-KYRM3HPP.js";
176
+ import "./chunk-57642NGU.js";
121
177
  import {
122
178
  MemoryFileStore,
123
179
  VERSION
124
- } from "./chunk-SRA4MDVG.js";
180
+ } from "./chunk-YMXFSHXT.js";
125
181
  import {
126
182
  computeFactHash,
127
183
  generateEntityId
128
- } from "./chunk-CAEPHLHC.js";
129
- import "./chunk-6JCKMIQY.js";
130
- import {
131
- localRuntimeInstalled,
132
- modelCacheDir
133
- } from "./chunk-3QGW53GD.js";
134
- import {
135
- loadLlmSummaries,
136
- summarizeRepository
137
- } from "./chunk-6NZEZJYD.js";
138
- import {
139
- chatJson,
140
- coolingDown,
141
- usageToday
142
- } from "./chunk-ESZXCZIN.js";
143
- import {
144
- generateGraphifyGraph,
145
- loadGraphifyMap,
146
- setupGraphify,
147
- syncFileVectors
148
- } from "./chunk-TLWGZVPC.js";
149
- import {
150
- buildArchitectureMap,
151
- indexRepository,
152
- loadFreshIndex,
153
- neighbours,
154
- queryMap
155
- } from "./chunk-ITQO632W.js";
156
- import {
157
- extractSymbolTable,
158
- supportsSymbols
159
- } from "./chunk-Y7D7PUBE.js";
184
+ } from "./chunk-EMWGLKPR.js";
160
185
  import {
161
186
  redactPersisted
162
187
  } from "./chunk-AL6VBTNA.js";
188
+ import "./chunk-6JCKMIQY.js";
163
189
  import {
164
- DEFAULT_CLOUD_EMBEDDING_MODELS,
165
- DEFAULT_RERANK_MODELS,
166
- LOCAL_EMBEDDING_MODEL,
167
- getModelDefinition
168
- } from "./chunk-HPOR6YXA.js";
169
- import {
190
+ mergeGlobalConfig,
191
+ readGlobalConfig,
170
192
  resolveLlmConfig
171
- } from "./chunk-SFMIMLXR.js";
193
+ } from "./chunk-WHKXUXHE.js";
172
194
  import {
173
195
  PLAN_POLICIES,
174
196
  configPath,
@@ -186,13 +208,26 @@ import {
186
208
  setPlanPolicy,
187
209
  setZvecEnabled,
188
210
  writeConfigFile
189
- } from "./chunk-KNR2P2UY.js";
190
- import "./chunk-CG5B3LYG.js";
211
+ } from "./chunk-M75SN4D4.js";
212
+ import {
213
+ writeAtomic
214
+ } from "./chunk-CG5B3LYG.js";
215
+ import "./chunk-BTTQWSDS.js";
216
+ import {
217
+ localRuntimeInstalled,
218
+ modelCacheDir
219
+ } from "./chunk-3QGW53GD.js";
220
+ import {
221
+ DEFAULT_CLOUD_EMBEDDING_MODELS,
222
+ DEFAULT_RERANK_MODELS,
223
+ LOCAL_EMBEDDING_MODEL,
224
+ getModelDefinition
225
+ } from "./chunk-HPOR6YXA.js";
191
226
  import "./chunk-PZ5AY32C.js";
192
227
 
193
228
  // src/cli/main.ts
194
229
  import { Command } from "commander";
195
- import pc23 from "picocolors";
230
+ import pc25 from "picocolors";
196
231
 
197
232
  // src/integrations/github.ts
198
233
  import { constants } from "fs";
@@ -770,6 +805,7 @@ var CONFORMANCE = {
770
805
  const servers = json(result.stdout);
771
806
  if (!Array.isArray(servers)) return [fail("mcp", "codex mcp list --json returned no JSON")];
772
807
  return [
808
+ await clientHooksInstalled(root, "codex") ? pass("hooks", ".codex/hooks.json is installed; review trust in Codex /hooks") : skip("hooks", "not installed in this repo"),
773
809
  servers.some((server) => server.name === name2) ? pass("mcp", `codex mcp list has ${name2}`) : skip("mcp", `no ${name2} entry (clients install codex --global adds it)`)
774
810
  ];
775
811
  },
@@ -863,7 +899,7 @@ function registerClientsCommands(program, runSafely2) {
863
899
  console.log(pc2.dim("Per-turn token cost per client: codebuddy clients cost"));
864
900
  });
865
901
  });
866
- clients.command("install").argument("<client>", `One of: ${CLIENT_IDS.join(", ")}.`).requiredOption("--mode <mode>", `One of: ${AUTOMATION_MODES.join(", ")}.`).option("--namespace <name>", "Namespace for the MCP entry (defaults to config/folder).").option("--global", "Also add the MCP entry to the global config (Codex, Hermes).").description("Apply one automation mode: rules block, hooks, and MCP config.").action(
902
+ clients.command("install").argument("<client>", `One of: ${CLIENT_IDS.join(", ")}.`).requiredOption("--mode <mode>", `One of: ${AUTOMATION_MODES.join(", ")}.`).option("--namespace <name>", "Namespace for the MCP entry (defaults to config/folder).").option("--global", "Also add the MCP entry to the global config (Codex, Hermes).").option("--capture", "Capture turns into memory even in lean mode (Claude Code).").option("--no-capture", "Inject context without capturing turns (Claude Code).").description("Apply one automation mode: rules block, hooks, and MCP config.").action(
867
903
  async (client, opts) => {
868
904
  await runSafely2(async () => {
869
905
  const mode = parseMode(opts.mode);
@@ -871,7 +907,8 @@ function registerClientsCommands(program, runSafely2) {
871
907
  client: parseClient(client),
872
908
  mode,
873
909
  ...opts.namespace ? { namespace: opts.namespace } : {},
874
- ...opts.global ? { global: true } : {}
910
+ ...opts.global ? { global: true } : {},
911
+ ...opts.capture !== void 0 ? { capture: opts.capture } : {}
875
912
  });
876
913
  printReport2(report, `${MODE_LABELS[mode]} mode`);
877
914
  });
@@ -983,6 +1020,30 @@ function registerContextCommands(program, runSafely2) {
983
1020
  printBootstrap(result);
984
1021
  });
985
1022
  });
1023
+ context.command("reuse").argument("<task...>", "What you are about to implement.").option("--json", "Print machine-readable JSON.").description(
1024
+ "Existing helpers to reuse for a task: signature, a call site and the covering test."
1025
+ ).action(async (taskParts, opts) => {
1026
+ await runSafely2(async () => {
1027
+ const root = process.cwd();
1028
+ const { manifest } = await loadFreshIndex(root);
1029
+ const picks = await findReuse(
1030
+ root,
1031
+ taskParts.join(" "),
1032
+ manifest,
1033
+ await loadProjectArchitectureMap(root, manifest)
1034
+ );
1035
+ if (opts.json) {
1036
+ console.log(JSON.stringify(picks, null, 2));
1037
+ return;
1038
+ }
1039
+ if (picks.length === 0) console.log(pc3.dim("no existing helper clearly matches"));
1040
+ for (const pick of picks) {
1041
+ console.log(`${pick.path}:${pick.startLine}-${pick.endLine} ${pick.signature}`);
1042
+ if (pick.usage) console.log(pc3.dim(` used at ${pick.usage}`));
1043
+ if (pick.test) console.log(pc3.dim(` test ${pick.test}`));
1044
+ }
1045
+ });
1046
+ });
986
1047
  context.command("preview").argument("[task...]", "The task you are about to work on.").option("--paths <paths>", "Comma-separated repo-relative paths to target.").option("--plan <id>", "Resolve target files from a CodeBuddy plan.").option("--git", "Resolve target files from current git changes.").option("--json", "Print machine-readable JSON.").description("Show the context CodeBuddy would assemble before editing the given files.").action(
987
1048
  async (taskParts, opts) => {
988
1049
  await runSafely2(async () => {
@@ -1720,15 +1781,22 @@ function registerGitHubCommands(program, runSafely2) {
1720
1781
  import { execFile as execFile2, spawn as spawn3 } from "child_process";
1721
1782
  import { relative as relative2 } from "path";
1722
1783
  import pc7 from "picocolors";
1784
+
1785
+ // src/hooks/lean-command.ts
1786
+ async function runLeanHookCommand() {
1787
+ await failSoft(async () => {
1788
+ await runLeanHook(await readHookInput(), writeStdout);
1789
+ });
1790
+ }
1791
+
1792
+ // src/cli/commands/hooks.ts
1723
1793
  function registerHooksCommands(program, runSafely2) {
1724
1794
  const hooks = program.command("hooks").description("Enforce automatic recall/capture via client lifecycle hooks.");
1725
1795
  hooks.command("install").option(
1726
1796
  "--client <client>",
1727
- "Client with a CodeBuddy hook adapter (claude, opencode, hermes).",
1797
+ "Client with a CodeBuddy hook adapter (claude, codex, opencode, hermes).",
1728
1798
  "claude"
1729
- ).description(
1730
- "Add CodeBuddy's recall/capture hooks (.claude/settings.json, .opencode/plugins/codebuddy.js or .hermes/plugins/codebuddy/)."
1731
- ).action(async (opts) => {
1799
+ ).description("Add CodeBuddy's recall/capture hooks for the selected client.").action(async (opts) => {
1732
1800
  await runSafely2(async () => {
1733
1801
  const client = assertHookClient(opts.client);
1734
1802
  const step = await setHooks(process.cwd(), true, client);
@@ -1750,7 +1818,7 @@ function registerHooksCommands(program, runSafely2) {
1750
1818
  });
1751
1819
  hooks.command("uninstall").option(
1752
1820
  "--client <client>",
1753
- "Client with a CodeBuddy hook adapter (claude, opencode, hermes).",
1821
+ "Client with a CodeBuddy hook adapter (claude, codex, opencode, hermes).",
1754
1822
  "claude"
1755
1823
  ).description("Remove CodeBuddy's hooks for a client (user hooks untouched).").action(async (opts) => {
1756
1824
  await runSafely2(async () => {
@@ -1764,14 +1832,43 @@ function registerHooksCommands(program, runSafely2) {
1764
1832
  });
1765
1833
  hooks.command("status").option(
1766
1834
  "--client <client>",
1767
- "Client with a CodeBuddy hook adapter (claude, opencode, hermes).",
1835
+ "Client with a CodeBuddy hook adapter (claude, codex, opencode, hermes).",
1768
1836
  "claude"
1769
- ).description("Report whether CodeBuddy hooks are installed.").action(async (opts) => {
1837
+ ).description("Report which CodeBuddy hooks are installed and what they do.").action(async (opts) => {
1770
1838
  await runSafely2(async () => {
1771
- const installed = await clientHooksInstalled(process.cwd(), parseClient(opts.client));
1839
+ const caps = await clientHookCapabilities(process.cwd(), parseClient(opts.client));
1840
+ if (!caps?.injection) {
1841
+ console.log(pc7.dim("CodeBuddy hooks: not installed"));
1842
+ return;
1843
+ }
1844
+ console.log(pc7.green("CodeBuddy hooks: installed"));
1845
+ console.log(` injection: ${caps.injection}`);
1846
+ console.log(
1847
+ ` capture: ${caps.capture ? "on (edits staged, turn end captured)" : "off (turns are not captured into memory)"}`
1848
+ );
1772
1849
  console.log(
1773
- installed ? pc7.green("CodeBuddy hooks: installed") : pc7.dim("CodeBuddy hooks: not installed")
1850
+ ` recovery: ${caps.recovery ? "on (pre-compaction checkpoint, unfinished-task resume)" : "off"}`
1774
1851
  );
1852
+ console.log(` compaction reset: ${caps.reset ? "on" : "off"}`);
1853
+ console.log(` read guard: ${caps.guard ? "on" : "off"}`);
1854
+ });
1855
+ });
1856
+ hooks.command("codex").option("--injection <mode>", "full or lean", "full").option("--capture", "Capture visible lifecycle events", false).description("[hook] Native Codex event adapter (JSON output, no permission changes).").action(async (opts) => {
1857
+ let result = {};
1858
+ await failSoft(async () => {
1859
+ if (opts.injection !== "full" && opts.injection !== "lean")
1860
+ throw new Error("injection must be full or lean");
1861
+ result = await runCodexHook(
1862
+ await readHookInput(),
1863
+ opts.injection,
1864
+ opts.capture
1865
+ );
1866
+ });
1867
+ await writeStdout(JSON.stringify(result));
1868
+ });
1869
+ hooks.command("bridge").description("[hook] Capture normalized host events and restore checkpoint context.").action(async () => {
1870
+ await failSoft(async () => {
1871
+ await runBridgeHook(await readHookInput(), writeStdout);
1775
1872
  });
1776
1873
  });
1777
1874
  hooks.command("context").description("[hook] Emit compact project context for injection at prompt submit.").action(async () => {
@@ -1824,6 +1921,16 @@ function registerHooksCommands(program, runSafely2) {
1824
1921
  await runResetHook(await readHookInput(), writeStdout);
1825
1922
  });
1826
1923
  });
1924
+ hooks.command("checkpoint").description("[hook] Journal the transcript before compaction (PreCompact).").action(async () => {
1925
+ await failSoft(async () => {
1926
+ await runCheckpointHook(await readHookInput());
1927
+ });
1928
+ });
1929
+ hooks.command("resume").description("[hook] Print another session's unfinished task checkpoint, if any.").action(async () => {
1930
+ await failSoft(async () => {
1931
+ await runResumeHook(await readHookInput(), writeStdout);
1932
+ });
1933
+ });
1827
1934
  }
1828
1935
  function assertHookClient(value) {
1829
1936
  const client = parseClient(value);
@@ -1898,8 +2005,9 @@ var KEY_ENV = { groq: "GROQ_API_KEY", openrouter: "OPENROUTER_API_KEY" };
1898
2005
  var WATCH_BATCH = 8;
1899
2006
  async function llmStatusReport(root, env = process.env) {
1900
2007
  const block = (await readConfigFile(root)).llm;
1901
- const provider = block?.provider ?? "groq";
1902
2008
  const config = await resolveLlmConfig(root, env);
2009
+ const provider = config.enabled ? config.provider : block?.provider ?? "groq";
2010
+ const keyName = provider === "local" ? "" : KEY_ENV[provider];
1903
2011
  const model = config.enabled ? config.model : block?.model ?? null;
1904
2012
  const key = `${provider}:${model}`;
1905
2013
  const { manifest } = await loadFreshIndex(root);
@@ -1908,8 +2016,9 @@ async function llmStatusReport(root, env = process.env) {
1908
2016
  reason: config.enabled ? "on" : config.reason,
1909
2017
  provider,
1910
2018
  model,
1911
- keyEnv: env.CODEBUDDY_LLM_API_KEY ? "CODEBUDDY_LLM_API_KEY" : KEY_ENV[provider],
1912
- keyPresent: Boolean(env.CODEBUDDY_LLM_API_KEY || env[KEY_ENV[provider]]),
2019
+ keyEnv: provider === "local" ? "none needed" : env.CODEBUDDY_LLM_API_KEY ? "CODEBUDDY_LLM_API_KEY" : keyName,
2020
+ // Enabled implies a key (environment or saved by `codebuddy use`).
2021
+ keyPresent: provider === "local" || config.enabled || Boolean(env.CODEBUDDY_LLM_API_KEY || env[keyName]),
1913
2022
  features: config.enabled ? config.features : null,
1914
2023
  usageToday: (await usageToday(root))[key] ?? 0,
1915
2024
  dailyTokenBudget: config.enabled ? config.dailyTokenBudget : block?.dailyTokenBudget ?? 4e5,
@@ -1950,7 +2059,7 @@ function registerLlmCommands(program, runSafely2) {
1950
2059
  if (!report.keyPresent) console.log(pc8.dim(`next: export ${report.keyEnv}=<your key>`));
1951
2060
  });
1952
2061
  });
1953
- llm.command("enable").option("--provider <provider>", "groq or openrouter", "groq").option("--model <model>", "Model id (default: llama-3.1-8b-instant on groq).").description("Turn the layer on in .codebuddy/config.json (the key stays in your environment).").action(async (opts) => {
2062
+ llm.command("enable").option("--provider <provider>", "groq or openrouter", "groq").option("--model <model>", "Model id (default: qwen/qwen3.8-27b).").description("Turn the layer on in .codebuddy/config.json (the key stays in your environment).").action(async (opts) => {
1954
2063
  await runSafely2(async () => {
1955
2064
  if (opts.provider !== "groq" && opts.provider !== "openrouter") {
1956
2065
  throw new Error("--provider must be groq or openrouter");
@@ -2006,13 +2115,36 @@ function registerLlmCommands(program, runSafely2) {
2006
2115
  // src/cli/commands/indexing.ts
2007
2116
  var VECTOR_BUDGET_MS = 10 * 6e4;
2008
2117
  function registerIndexCommands(program, runSafely2) {
2009
- program.command("index").option("--full", "Re-summarize every file, ignoring existing content hashes.").option("--json", "Print machine-readable JSON.").option("--no-vectors", "Skip embedding source files for task \u2192 file ordering.").description("Index the repository: content hashes and lightweight file summaries.").action(async (opts) => {
2118
+ program.command("index").option("--full", "Re-summarize every file, ignoring existing content hashes.").option("--json", "Print machine-readable JSON.").option("--no-vectors", "Skip embedding source files for task \u2192 file ordering.").option(
2119
+ "--cards",
2120
+ "Also write intent cards and index facts with the local (or BYOK) model, then embed the cards."
2121
+ ).description("Index the repository: content hashes and lightweight file summaries.").action(async (opts) => {
2010
2122
  await runSafely2(async () => {
2011
2123
  const result = await indexRepository({ ...opts.full ? { full: true } : {} });
2012
2124
  const local = opts.vectors && (await loadRuntimeConfig(process.cwd(), { requireDatabase: false }).catch(() => null))?.provider.type === "local";
2125
+ const vectorStarted = Date.now();
2013
2126
  const vectors = local ? await syncFileVectors(process.cwd(), VECTOR_BUDGET_MS) : null;
2127
+ const reuseVectors = local ? await syncReuseVectors(process.cwd(), VECTOR_BUDGET_MS - (Date.now() - vectorStarted)) : null;
2128
+ const cardsStarted = Date.now();
2129
+ const model = opts.cards ? await runIndexModelPass(process.cwd(), {
2130
+ ...opts.json ? {} : {
2131
+ onProgress: (done, total) => process.stderr.write(`\r cards: ${done}/${total}`)
2132
+ }
2133
+ }) : null;
2134
+ if (model && !opts.json) process.stderr.write("\n");
2014
2135
  if (opts.json) {
2015
- console.log(JSON.stringify({ ...result, ...vectors ? { vectors } : {} }, null, 2));
2136
+ console.log(
2137
+ JSON.stringify(
2138
+ {
2139
+ ...result,
2140
+ ...vectors ? { vectors } : {},
2141
+ ...reuseVectors ? { reuseVectors } : {},
2142
+ ...model ? { model: { ...model, ms: Date.now() - cardsStarted } } : {}
2143
+ },
2144
+ null,
2145
+ 2
2146
+ )
2147
+ );
2016
2148
  return;
2017
2149
  }
2018
2150
  printResult(result);
@@ -2025,6 +2157,7 @@ function registerIndexCommands(program, runSafely2) {
2025
2157
  } else if (local) {
2026
2158
  console.log(pc9.dim(" file vectors: skipped (local embedding runtime not available)"));
2027
2159
  }
2160
+ if (model) printModelPass(model, Date.now() - cardsStarted);
2028
2161
  });
2029
2162
  });
2030
2163
  program.command("watch").option("--debounce <ms>", "Coalesce file-change bursts within this window.", Number).option("--no-llm", "Do not write BYOK model summaries after index passes.").description("Watch the repository and incrementally re-index on change (Ctrl+C to stop).").action(async (opts) => {
@@ -2051,6 +2184,24 @@ function registerIndexCommands(program, runSafely2) {
2051
2184
  });
2052
2185
  });
2053
2186
  }
2187
+ function printModelPass(model, ms) {
2188
+ if (!model.cards && !model.facts) {
2189
+ console.log(pc9.dim(" cards: skipped (no model; codebuddy models add local-llm)"));
2190
+ return;
2191
+ }
2192
+ if (model.cards) {
2193
+ console.log(
2194
+ pc9.dim(
2195
+ ` cards: ${model.cards.summarized} written, ${model.cards.remaining} left (${model.cards.stoppedBy}), ${model.cardVectors} embedded, ${Math.round(ms / 1e3)} s`
2196
+ )
2197
+ );
2198
+ }
2199
+ if (model.facts) {
2200
+ console.log(
2201
+ pc9.dim(` index facts: ${model.facts.written} written, ${model.facts.removed} replaced`)
2202
+ );
2203
+ }
2204
+ }
2054
2205
  function printResult(result, compact = false) {
2055
2206
  const summary = `${result.totalIndexed} indexed (${pc9.green(`+${result.added}`)} ${pc9.yellow(`~${result.changed}`)} ${pc9.dim(`=${result.unchanged}`)} ${pc9.red(`-${result.removed}`)}) in ${result.durationMs}ms`;
2056
2207
  console.log(compact ? pc9.dim(summary) : summary);
@@ -2202,8 +2353,59 @@ async function runInitWizard() {
2202
2353
  }
2203
2354
  }
2204
2355
 
2205
- // src/cli/commands/learn.ts
2356
+ // src/cli/commands/journal.ts
2206
2357
  import pc10 from "picocolors";
2358
+ function registerJournalCommands(program, runSafely2) {
2359
+ const journal = program.command("journal").description("Inspect or delete session journals and active-task checkpoints.");
2360
+ journal.command("list", { isDefault: true }).description("List sessions with a checkpoint, newest first.").action(async () => {
2361
+ await runSafely2(async () => {
2362
+ const sessions = await listJournals(process.cwd());
2363
+ if (sessions.length === 0) console.log(pc10.dim("no session journals yet"));
2364
+ for (const { session, updatedAt } of sessions) console.log(`${updatedAt} ${session}`);
2365
+ });
2366
+ });
2367
+ journal.command("show").argument("<session>").description("Print a session's active-task checkpoint as JSON.").action(async (session) => {
2368
+ await runSafely2(async () => {
2369
+ const checkpoint = await readCheckpoint(process.cwd(), session);
2370
+ console.log(checkpoint ? JSON.stringify(checkpoint, null, 2) : pc10.dim("no checkpoint"));
2371
+ });
2372
+ });
2373
+ journal.command("resume").description("Print the newest unfinished task checkpoint (any session or agent).").action(async () => {
2374
+ await runSafely2(async () => {
2375
+ const checkpoint = await latestUnfinished(process.cwd(), void 0);
2376
+ console.log(
2377
+ checkpoint ? await renderCheckpointNow(
2378
+ process.cwd(),
2379
+ checkpoint,
2380
+ `unfinished, from session ${checkpoint.session}`
2381
+ ) : pc10.dim("no unfinished task in the last 3 days")
2382
+ );
2383
+ });
2384
+ });
2385
+ journal.command("import-codex").option("--all", "Import every Codex rollout for this repository, not only the newest.").description("Journal Codex rollouts (~/.codex/sessions) whose working directory is this repo.").action(async (opts) => {
2386
+ await runSafely2(async () => {
2387
+ const rollouts = await findCodexRollouts(process.cwd());
2388
+ if (rollouts.length === 0) {
2389
+ console.log(pc10.dim("no Codex rollout for this repository"));
2390
+ return;
2391
+ }
2392
+ for (const rollout of opts.all ? rollouts : rollouts.slice(0, 1)) {
2393
+ const result = await importCodexRollout(process.cwd(), rollout);
2394
+ console.log(`codex-${rollout.id}: ${result ? `${result.added} new events` : "busy"}`);
2395
+ }
2396
+ });
2397
+ });
2398
+ journal.command("forget").argument("[session]").option("--all", "Delete every session's journal.").description("Delete a session's journal, cursor and checkpoint.").action(async (session, opts) => {
2399
+ await runSafely2(async () => {
2400
+ if (!session && !opts.all) throw new Error("Name a session or pass --all.");
2401
+ await forgetJournal(process.cwd(), opts.all ? void 0 : session);
2402
+ console.log(pc10.yellow(opts.all ? "deleted all session journals" : `deleted ${session}`));
2403
+ });
2404
+ });
2405
+ }
2406
+
2407
+ // src/cli/commands/learn.ts
2408
+ import pc11 from "picocolors";
2207
2409
 
2208
2410
  // src/learning/outcomes.ts
2209
2411
  async function recordChangeOutcome(input) {
@@ -2264,15 +2466,15 @@ function registerLearnCommand(program, runSafely2) {
2264
2466
  ...opts.commit ? { commit: opts.commit } : {},
2265
2467
  ...opts.plan ? { planId: opts.plan } : {}
2266
2468
  });
2267
- console.log(`${pc10.green("recorded")} ${result.id} (${result.category})`);
2268
- console.log(pc10.dim(result.path));
2469
+ console.log(`${pc11.green("recorded")} ${result.id} (${result.category})`);
2470
+ console.log(pc11.dim(result.path));
2269
2471
  });
2270
2472
  }
2271
2473
  );
2272
2474
  }
2273
2475
 
2274
2476
  // src/cli/commands/map.ts
2275
- import pc11 from "picocolors";
2477
+ import pc12 from "picocolors";
2276
2478
  function registerMapCommands(program, runSafely2) {
2277
2479
  const map = program.command("map").description("Query the lightweight architecture map.");
2278
2480
  map.command("build").description("Scan the current repository and summarize the module graph.").action(async () => {
@@ -2292,7 +2494,7 @@ function registerMapCommands(program, runSafely2) {
2292
2494
  return;
2293
2495
  }
2294
2496
  if (edges.length === 0) {
2295
- console.log(pc11.dim("no dependencies found"));
2497
+ console.log(pc12.dim("no dependencies found"));
2296
2498
  return;
2297
2499
  }
2298
2500
  for (const edge of edges) console.log(`${edge.from} -> ${edge.to}`);
@@ -2307,7 +2509,7 @@ function registerMapCommands(program, runSafely2) {
2307
2509
  return;
2308
2510
  }
2309
2511
  if (result.edges.length === 0) {
2310
- console.log(pc11.dim("no dependents found"));
2512
+ console.log(pc12.dim("no dependents found"));
2311
2513
  return;
2312
2514
  }
2313
2515
  for (const edge of result.edges) console.log(`${edge.from} -> ${edge.to}`);
@@ -2318,9 +2520,39 @@ function registerMapCommands(program, runSafely2) {
2318
2520
  // src/cli/commands/memory-review.ts
2319
2521
  import { spawn as spawn4 } from "child_process";
2320
2522
  import { relative as relative3 } from "path";
2321
- import pc12 from "picocolors";
2523
+ import pc13 from "picocolors";
2322
2524
  function registerMemoryCommands(program, runSafely2) {
2323
2525
  const memory = program.command("memory").description("Inspect and curate CodeBuddy memory.");
2526
+ memory.command("list").option(
2527
+ "--origin <origin>",
2528
+ "Only facts from this origin: index (extracted from the repository)."
2529
+ ).option("--json", "Print machine-readable JSON.").description("List durable facts (Markdown under .codebuddy/memory/facts/).").action(async (opts) => {
2530
+ await runSafely2(async () => {
2531
+ if (opts.origin !== void 0 && opts.origin !== "index") {
2532
+ throw new Error(`Unknown origin "${opts.origin}"; supported: index.`);
2533
+ }
2534
+ const facts = opts.origin === void 0 ? await new MemoryFileStore().listFacts() : await listIndexFacts(process.cwd());
2535
+ if (opts.json) {
2536
+ console.log(JSON.stringify(facts, null, 2));
2537
+ return;
2538
+ }
2539
+ if (facts.length === 0) console.log(pc13.dim("no facts"));
2540
+ for (const fact of facts) {
2541
+ const source = fact.verification[0] ?? fact.paths[0];
2542
+ console.log(`${pc13.cyan(fact.id)} ${fact.content}${source ? pc13.dim(` [${source}]`) : ""}`);
2543
+ }
2544
+ });
2545
+ });
2546
+ memory.command("prune").requiredOption("--origin <origin>", "Which facts to delete: index.").description("Delete facts by origin (index: re-extracted on the next index pass).").action(async (opts) => {
2547
+ await runSafely2(async () => {
2548
+ if (opts.origin !== "index")
2549
+ throw new Error(`Unknown origin "${opts.origin}"; supported: index.`);
2550
+ const removed = await pruneIndexFacts(process.cwd());
2551
+ console.log(
2552
+ `${removed} index fact(s) removed ${pc13.dim("(run `codebuddy hooks sync` to update the database)")}`
2553
+ );
2554
+ });
2555
+ });
2324
2556
  const review = memory.command("review").description("Review memory candidates captured automatically after turns.");
2325
2557
  review.command("list", { isDefault: true }).option("--json", "Print machine-readable JSON.").description("List pending review candidates.").action(async (opts) => {
2326
2558
  await runSafely2(async () => {
@@ -2330,11 +2562,11 @@ function registerMemoryCommands(program, runSafely2) {
2330
2562
  return;
2331
2563
  }
2332
2564
  if (items.length === 0) {
2333
- console.log(pc12.dim("no pending review items"));
2565
+ console.log(pc13.dim("no pending review items"));
2334
2566
  return;
2335
2567
  }
2336
2568
  for (const item of items) console.log(renderRow(item));
2337
- console.log(pc12.dim(`
2569
+ console.log(pc13.dim(`
2338
2570
  ${items.length} pending. approve <id> | reject <id> | show <id>`));
2339
2571
  });
2340
2572
  });
@@ -2348,7 +2580,7 @@ ${items.length} pending. approve <id> | reject <id> | show <id>`));
2348
2580
  const factId = await approveReviewItem(id, {
2349
2581
  ...opts.incident !== void 0 ? { incidentId: opts.incident } : {}
2350
2582
  });
2351
- console.log(`${pc12.green("approved")} ${id} \u2192 ${factId}`);
2583
+ console.log(`${pc13.green("approved")} ${id} \u2192 ${factId}`);
2352
2584
  });
2353
2585
  });
2354
2586
  review.command("reject").argument("<id>").description("Discard a review candidate without saving it.").action(async (id) => {
@@ -2356,7 +2588,7 @@ ${items.length} pending. approve <id> | reject <id> | show <id>`));
2356
2588
  const store = new ReviewStore();
2357
2589
  await store.read(id);
2358
2590
  await store.delete(id);
2359
- console.log(`${pc12.yellow("rejected")} ${id}`);
2591
+ console.log(`${pc13.yellow("rejected")} ${id}`);
2360
2592
  });
2361
2593
  });
2362
2594
  review.command("edit").argument("<id>").description("Open a review candidate in $EDITOR and re-validate on save.").action(async (id) => {
@@ -2365,44 +2597,44 @@ ${items.length} pending. approve <id> | reject <id> | show <id>`));
2365
2597
  const item = await store.read(id);
2366
2598
  await openEditor(item.path);
2367
2599
  await store.read(id);
2368
- console.log(pc12.dim("saved and validated"));
2600
+ console.log(pc13.dim("saved and validated"));
2369
2601
  });
2370
2602
  });
2371
2603
  }
2372
2604
  function renderRow(item) {
2373
- const flag = item.sensitive ? pc12.red(" \u26A0 sensitive") : "";
2374
- return `${classColor(item.class)} ${pc12.dim(item.id)} ${truncate(item.content, 70)}${flag}`;
2605
+ const flag = item.sensitive ? pc13.red(" \u26A0 sensitive") : "";
2606
+ return `${classColor(item.class)} ${pc13.dim(item.id)} ${truncate(item.content, 70)}${flag}`;
2375
2607
  }
2376
2608
  function renderItem(item) {
2377
2609
  const lines = [
2378
- `${classColor(item.class)} ${pc12.dim(item.id)}`,
2379
- `${pc12.bold("reason:")} ${item.reason}`,
2380
- `${pc12.bold("confidence:")} ${item.confidence.toFixed(2)}`,
2381
- `${pc12.bold("triple:")} ${item.subject} \xB7 ${item.predicate} \xB7 ${item.object}`
2610
+ `${classColor(item.class)} ${pc13.dim(item.id)}`,
2611
+ `${pc13.bold("reason:")} ${item.reason}`,
2612
+ `${pc13.bold("confidence:")} ${item.confidence.toFixed(2)}`,
2613
+ `${pc13.bold("triple:")} ${item.subject} \xB7 ${item.predicate} \xB7 ${item.object}`
2382
2614
  ];
2383
- if (item.paths.length > 0) lines.push(`${pc12.bold("paths:")} ${item.paths.join(", ")}`);
2384
- if (item.severity) lines.push(`${pc12.bold("severity:")} ${item.severity}`);
2615
+ if (item.paths.length > 0) lines.push(`${pc13.bold("paths:")} ${item.paths.join(", ")}`);
2616
+ if (item.severity) lines.push(`${pc13.bold("severity:")} ${item.severity}`);
2385
2617
  if (item.resolvesIncidents) {
2386
2618
  lines.push(
2387
- `${pc12.bold("resolves:")} ${item.resolvesIncidents.join(", ") || "(no candidate; approve with --incident <id>)"}`
2619
+ `${pc13.bold("resolves:")} ${item.resolvesIncidents.join(", ") || "(no candidate; approve with --incident <id>)"}`
2388
2620
  );
2389
2621
  }
2390
- if (item.sensitive) lines.push(pc12.red(`\u26A0 sensitive: ${item.sensitiveReasons.join(", ")}`));
2391
- lines.push("", item.content, "", pc12.dim(relative3(process.cwd(), item.path)));
2622
+ if (item.sensitive) lines.push(pc13.red(`\u26A0 sensitive: ${item.sensitiveReasons.join(", ")}`));
2623
+ lines.push("", item.content, "", pc13.dim(relative3(process.cwd(), item.path)));
2392
2624
  return lines.join("\n");
2393
2625
  }
2394
2626
  function classColor(memoryClass) {
2395
2627
  switch (memoryClass) {
2396
2628
  case "incident":
2397
- return pc12.red(memoryClass);
2629
+ return pc13.red(memoryClass);
2398
2630
  case "decision":
2399
- return pc12.cyan(memoryClass);
2631
+ return pc13.cyan(memoryClass);
2400
2632
  case "fact":
2401
- return pc12.green(memoryClass);
2633
+ return pc13.green(memoryClass);
2402
2634
  case "note":
2403
- return pc12.yellow(memoryClass);
2635
+ return pc13.yellow(memoryClass);
2404
2636
  default:
2405
- return pc12.dim(memoryClass);
2637
+ return pc13.dim(memoryClass);
2406
2638
  }
2407
2639
  }
2408
2640
  function truncate(value, max) {
@@ -2422,7 +2654,7 @@ function openEditor(path) {
2422
2654
  import { existsSync } from "fs";
2423
2655
  import { join as join5 } from "path";
2424
2656
  import { spinner as spinner2 } from "@clack/prompts";
2425
- import pc13 from "picocolors";
2657
+ import pc14 from "picocolors";
2426
2658
 
2427
2659
  // evals/fact-relevance.json
2428
2660
  var fact_relevance_default = {
@@ -2686,9 +2918,9 @@ async function calibrateEmbedder(embed) {
2686
2918
  var EMBEDDERS = ["local", "huggingface", "openrouter"];
2687
2919
  var RERANKERS = ["off", "local", "huggingface", "openrouter"];
2688
2920
  var KEY_ENV2 = { huggingface: "HF_TOKEN", openrouter: "OPENROUTER_API_KEY" };
2689
- var ok = (text3) => `${pc13.green("\u2713")} ${text3}`;
2690
- var warn = (text3) => `${pc13.yellow("!")} ${text3}`;
2691
- var row = (label, value) => ` ${pc13.dim(label.padEnd(11))}${value}`;
2921
+ var ok = (text3) => `${pc14.green("\u2713")} ${text3}`;
2922
+ var warn = (text3) => `${pc14.yellow("!")} ${text3}`;
2923
+ var row = (label, value) => ` ${pc14.dim(label.padEnd(11))}${value}`;
2692
2924
  async function withSpinner(label, work, done) {
2693
2925
  const progress = spinner2();
2694
2926
  progress.start(label);
@@ -2697,7 +2929,7 @@ async function withSpinner(label, work, done) {
2697
2929
  progress.stop(done(value));
2698
2930
  return value;
2699
2931
  } catch (error) {
2700
- progress.error(pc13.red(`\u2717 ${label}`));
2932
+ progress.error(pc14.red(`\u2717 ${label}`));
2701
2933
  throw error;
2702
2934
  }
2703
2935
  }
@@ -2736,8 +2968,8 @@ async function printStatus() {
2736
2968
  const model = provider.type === "local" ? LOCAL_EMBEDDING_MODEL.id : provider.model ?? DEFAULT_CLOUD_EMBEDDING_MODELS[provider.type];
2737
2969
  const floor = provider.relevanceFloor ?? getModelDefinition(model)?.relevanceFloor;
2738
2970
  const next = [];
2739
- console.log(pc13.bold("Embeddings"));
2740
- console.log(row("provider", `${provider.type} ${pc13.dim(model)}`));
2971
+ console.log(pc14.bold("Embeddings"));
2972
+ console.log(row("provider", `${provider.type} ${pc14.dim(model)}`));
2741
2973
  if (provider.type === "local" && !localRuntimeInstalled()) {
2742
2974
  console.log(
2743
2975
  row("runtime", warn("@huggingface/transformers is not installed: dense recall is off"))
@@ -2748,7 +2980,7 @@ async function printStatus() {
2748
2980
  console.log(
2749
2981
  row(
2750
2982
  "model",
2751
- ready ? ok(`downloaded ${pc13.dim(modelCacheDir())}`) : warn("not downloaded yet (~90 MB)")
2983
+ ready ? ok(`downloaded ${pc14.dim(modelCacheDir())}`) : warn("not downloaded yet (~90 MB)")
2752
2984
  )
2753
2985
  );
2754
2986
  if (!ready) next.push("codebuddy models pull");
@@ -2765,21 +2997,21 @@ async function printStatus() {
2765
2997
  console.log(
2766
2998
  row(
2767
2999
  "floor",
2768
- floor !== void 0 ? `${floor} ${pc13.dim(provider.relevanceFloor !== void 0 ? "(calibrated for this project)" : "(built-in)")}` : warn("not calibrated: dense recall is off for this model")
3000
+ floor !== void 0 ? `${floor} ${pc14.dim(provider.relevanceFloor !== void 0 ? "(calibrated for this project)" : "(built-in)")}` : warn("not calibrated: dense recall is off for this model")
2769
3001
  )
2770
3002
  );
2771
3003
  if (floor === void 0) next.push("codebuddy models calibrate");
2772
3004
  const rerank = file.rerank?.provider ?? "off";
2773
- console.log(pc13.bold("Reranker"));
3005
+ console.log(pc14.bold("Reranker"));
2774
3006
  if (rerank === "off") {
2775
3007
  console.log(
2776
- row("provider", `off ${pc13.dim("(default: no reranker beat dense order in evals)")}`)
3008
+ row("provider", `off ${pc14.dim("(default: no reranker beat dense order in evals)")}`)
2777
3009
  );
2778
3010
  } else {
2779
3011
  console.log(
2780
3012
  row(
2781
3013
  "provider",
2782
- `${rerank} ${pc13.dim(file.rerank?.model ?? DEFAULT_RERANK_MODELS[rerank].id)}`
3014
+ `${rerank} ${pc14.dim(file.rerank?.model ?? DEFAULT_RERANK_MODELS[rerank].id)}`
2783
3015
  )
2784
3016
  );
2785
3017
  if (rerank !== "local") {
@@ -2792,7 +3024,11 @@ async function printStatus() {
2792
3024
  );
2793
3025
  }
2794
3026
  }
2795
- console.log(pc13.bold("Memory index"));
3027
+ {
3028
+ const { cardProgress } = await import("./summaries-QXWFFDUZ.js");
3029
+ await printLocalLlmStatus(await cardProgress(process.cwd()).catch(() => null));
3030
+ }
3031
+ console.log(pc14.bold("Memory index"));
2796
3032
  if (process.env.CODEBUDDY_SEMANTIC === "0") {
2797
3033
  console.log(row("recall", warn("CODEBUDDY_SEMANTIC=0: lexical only")));
2798
3034
  }
@@ -2813,7 +3049,7 @@ async function printStatus() {
2813
3049
  console.log(
2814
3050
  row(
2815
3051
  "vectors",
2816
- `${counts.ready} ready` + (counts.pending ? `, ${counts.pending} pending` : "") + (counts.failed ? pc13.red(`, ${counts.failed} failed`) : "") + (counts.other ? pc13.yellow(`, ${counts.other} from another model`) : "")
3052
+ `${counts.ready} ready` + (counts.pending ? `, ${counts.pending} pending` : "") + (counts.failed ? pc14.red(`, ${counts.failed} failed`) : "") + (counts.other ? pc14.yellow(`, ${counts.other} from another model`) : "")
2817
3053
  )
2818
3054
  );
2819
3055
  if (counts.pending || counts.other)
@@ -2822,9 +3058,9 @@ async function printStatus() {
2822
3058
  }
2823
3059
  console.log(
2824
3060
  next.length > 0 ? `
2825
- ${pc13.bold("Next")}
2826
- ${next.map((step) => ` ${pc13.cyan(step)}`).join("\n")}` : `
2827
- ${ok("Semantic recall is ready.")} ${pc13.dim('Try: codebuddy context preview "<task>"')}`
3061
+ ${pc14.bold("Next")}
3062
+ ${next.map((step) => ` ${pc14.cyan(step)}`).join("\n")}` : `
3063
+ ${ok("Semantic recall is ready.")} ${pc14.dim('Try: codebuddy context preview "<task>"')}`
2828
3064
  );
2829
3065
  }
2830
3066
  function registerModelsCommands(program, runSafely2) {
@@ -2861,27 +3097,27 @@ Examples:
2861
3097
  ...opts.floor !== void 0 ? { relevanceFloor: opts.floor } : {}
2862
3098
  });
2863
3099
  const model = type === "local" ? LOCAL_EMBEDDING_MODEL.id : opts.model ?? DEFAULT_CLOUD_EMBEDDING_MODELS[type];
2864
- console.log(ok(`Embeddings: ${type} ${pc13.dim(model)}`));
3100
+ console.log(ok(`Embeddings: ${type} ${pc14.dim(model)}`));
2865
3101
  const missingKey = type !== "local" && !keyPresent(type, current?.apiKey);
2866
3102
  if (missingKey) {
2867
3103
  console.log(
2868
3104
  warn(
2869
- `Set ${KEY_ENV2[type]} in your shell (keys are never stored in config), then run ${pc13.cyan("codebuddy hooks sync")}.`
3105
+ `Set ${KEY_ENV2[type]} in your shell (keys are never stored in config), then run ${pc14.cyan("codebuddy hooks sync")}.`
2870
3106
  )
2871
3107
  );
2872
- console.log(pc13.dim(" Existing vectors keep working until the new model can embed."));
3108
+ console.log(pc14.dim(" Existing vectors keep working until the new model can embed."));
2873
3109
  }
2874
3110
  if (opts.floor === void 0 && getModelDefinition(model)?.relevanceFloor === void 0) {
2875
3111
  console.log(
2876
3112
  warn(
2877
- `No floor for ${model} yet: run ${pc13.cyan("codebuddy models calibrate")} to turn on dense recall.`
3113
+ `No floor for ${model} yet: run ${pc14.cyan("codebuddy models calibrate")} to turn on dense recall.`
2878
3114
  )
2879
3115
  );
2880
3116
  }
2881
3117
  if (!missingKey) {
2882
3118
  syncInBackground(process.cwd(), true);
2883
3119
  console.log(
2884
- pc13.dim(" Re-embedding existing memory in the background (codebuddy hooks sync).")
3120
+ pc14.dim(" Re-embedding existing memory in the background (codebuddy hooks sync).")
2885
3121
  );
2886
3122
  }
2887
3123
  });
@@ -2908,13 +3144,36 @@ before keeping one on. A reranker never admits a fact, it only orders them.`
2908
3144
  }
2909
3145
  const reranker = choice;
2910
3146
  console.log(
2911
- ok(`Reranker: ${reranker} ${pc13.dim(opts.model ?? DEFAULT_RERANK_MODELS[reranker].id)}`)
3147
+ ok(`Reranker: ${reranker} ${pc14.dim(opts.model ?? DEFAULT_RERANK_MODELS[reranker].id)}`)
2912
3148
  );
2913
3149
  if (reranker !== "local" && !keyPresent(reranker, (await readConfigFile()).provider?.apiKey)) {
2914
3150
  console.log(warn(`Set ${KEY_ENV2[reranker]}; without it reranking is skipped.`));
2915
3151
  }
2916
3152
  if (reranker === "local")
2917
- console.log(pc13.dim(" Run codebuddy models pull to download it now."));
3153
+ console.log(pc14.dim(" Run codebuddy models pull to download it now."));
3154
+ });
3155
+ });
3156
+ models.command("add").argument("<name>", "Optional model to install: local-llm.").option("--size <size>", `Local LLM size: ${LOCAL_LLM_SIZES.join(" | ")}.`, "default").option("-y, --yes", "Install the runtime without asking.").description(
3157
+ "Download the optional local LLM (Qwen3-0.6B by default) that writes file cards and index facts."
3158
+ ).action(async (name2, opts) => {
3159
+ await runSafely2(async () => {
3160
+ if (name2 !== "local-llm") throw new Error(`Unknown model "${name2}"; try: local-llm.`);
3161
+ if (!LOCAL_LLM_SIZES.includes(opts.size)) {
3162
+ throw new Error(`--size must be one of ${LOCAL_LLM_SIZES.join(", ")}.`);
3163
+ }
3164
+ const added = await addLocalLlm({
3165
+ size: opts.size,
3166
+ ...opts.yes ? { yes: true } : {}
3167
+ });
3168
+ if (!added) process.exitCode = 1;
3169
+ });
3170
+ });
3171
+ models.command("remove").argument("<name>", "Optional model to remove: local-llm.").description(
3172
+ "Delete the local LLM weights; CodeBuddy falls back to keyword/vector-only behaviour."
3173
+ ).action(async (name2) => {
3174
+ await runSafely2(async () => {
3175
+ if (name2 !== "local-llm") throw new Error(`Unknown model "${name2}"; try: local-llm.`);
3176
+ await removeLocalLlm();
2918
3177
  });
2919
3178
  });
2920
3179
  models.command("pull").description("Download the local models now instead of on first use.").action(async () => {
@@ -2925,7 +3184,7 @@ before keeping one on. A reranker never admits a fact, it only orders them.`
2925
3184
  await withSpinner(
2926
3185
  `Downloading ${LOCAL_EMBEDDING_MODEL.id}`,
2927
3186
  () => new LocalModelProvider().embed({ input: "warm up" }),
2928
- () => ok(`${LOCAL_EMBEDDING_MODEL.id} ready ${pc13.dim(`${Date.now() - started} ms`)}`)
3187
+ () => ok(`${LOCAL_EMBEDDING_MODEL.id} ready ${pc14.dim(`${Date.now() - started} ms`)}`)
2929
3188
  );
2930
3189
  if (file.rerank?.provider === "local") {
2931
3190
  const id = file.rerank.model ?? DEFAULT_RERANK_MODELS.local.id;
@@ -2934,10 +3193,10 @@ before keeping one on. A reranker never admits a fact, it only orders them.`
2934
3193
  await withSpinner(
2935
3194
  `Downloading ${id}`,
2936
3195
  () => rerankLocally({ id, ...revision ? { revision } : {} }, "warm", ["up"]),
2937
- () => ok(`${id} ready ${pc13.dim(`${Date.now() - started} ms`)}`)
3196
+ () => ok(`${id} ready ${pc14.dim(`${Date.now() - started} ms`)}`)
2938
3197
  );
2939
3198
  }
2940
- console.log(pc13.dim(` Cache: ${modelCacheDir()}`));
3199
+ console.log(pc14.dim(` Cache: ${modelCacheDir()}`));
2941
3200
  });
2942
3201
  });
2943
3202
  models.command("calibrate").option("--dry-run", "Measure only; do not save the floor.").description(
@@ -2955,19 +3214,19 @@ before keeping one on. A reranker never admits a fact, it only orders them.`
2955
3214
  console.log(
2956
3215
  row(
2957
3216
  "ranking",
2958
- `AUC ${result.auc.toFixed(3)} \xB7 right fact first ${pct(result.recallAt1)} ${pc13.dim("(held-out tasks)")}`
3217
+ `AUC ${result.auc.toFixed(3)} \xB7 right fact first ${pct(result.recallAt1)} ${pc14.dim("(held-out tasks)")}`
2959
3218
  )
2960
3219
  );
2961
3220
  console.log(
2962
3221
  row(
2963
3222
  "floor",
2964
- `${result.best.floor}: precision ${pct(result.test.precision)}, recall ${pct(result.test.recall)}, silent on ${pct(result.test.abstain)} of unrelated tasks ${pc13.dim("(chosen on dev, measured on test)")}`
3223
+ `${result.best.floor}: precision ${pct(result.test.precision)}, recall ${pct(result.test.recall)}, silent on ${pct(result.test.abstain)} of unrelated tasks ${pc14.dim("(chosen on dev, measured on test)")}`
2965
3224
  )
2966
3225
  );
2967
3226
  console.log(
2968
3227
  row(
2969
3228
  "recall floor",
2970
- `${result.recall.dev.floor}: precision ${pct(result.recall.test.precision)}, recall ${pct(result.recall.test.recall)} ` + pc13.dim("(explicit recall: best F2 on dev, measured on test)")
3229
+ `${result.recall.dev.floor}: precision ${pct(result.recall.test.precision)}, recall ${pct(result.recall.test.recall)} ` + pc14.dim("(explicit recall: best F2 on dev, measured on test)")
2971
3230
  )
2972
3231
  );
2973
3232
  if (opts.dryRun) return;
@@ -2986,10 +3245,238 @@ before keeping one on. A reranker never admits a fact, it only orders them.`
2986
3245
  });
2987
3246
  }
2988
3247
 
2989
- // src/cli/commands/plan.ts
3248
+ // src/cli/commands/output.ts
2990
3249
  import { spawn as spawn5 } from "child_process";
3250
+ import { constants as constants2 } from "os";
3251
+ import pc15 from "picocolors";
3252
+
3253
+ // src/output/reduce.ts
3254
+ import { createHash } from "crypto";
3255
+ import { mkdir as mkdir2, readdir, readFile as readFile3, rm as rm2 } from "fs/promises";
3256
+ import { join as join6 } from "path";
3257
+ var OUTPUT_LIMITS = {
3258
+ /** At or below either, output is never reduced. */
3259
+ minLines: 80,
3260
+ minBytes: 6e3,
3261
+ head: 20,
3262
+ tail: 30,
3263
+ contextBefore: 2,
3264
+ contextAfter: 4,
3265
+ /** A reduction must save at least this share, or the original is printed. */
3266
+ minSaving: 0.3,
3267
+ /** Stored bytes per run; beyond this the stored copy says it was cut. */
3268
+ storeBytes: 2e7,
3269
+ retentionDays: 7,
3270
+ /** Lines one `output show` may print. */
3271
+ showLines: 400
3272
+ };
3273
+ var SIGNAL = /\b(error|errors|fail|failed|failure|failing|exception|traceback|panic|fatal|assert(?:ion)?|warn(?:ing)?|denied|not found|undefined|segfault|timeout|timed out)\b|✗|×|✖|FAIL|ERR!/i;
3274
+ function outputDir(root) {
3275
+ return join6(root, ".codebuddy", "cache", "outputs");
3276
+ }
3277
+ var shape = (line) => line.replace(/\d+/g, "#").trim();
3278
+ function reduceOutput(text3, id) {
3279
+ const lines = text3.split("\n");
3280
+ if (lines.length <= OUTPUT_LIMITS.minLines || Buffer.byteLength(text3) <= OUTPUT_LIMITS.minBytes) {
3281
+ return null;
3282
+ }
3283
+ if (text3.includes("\0")) return null;
3284
+ const keep = /* @__PURE__ */ new Set();
3285
+ for (let i = 0; i < Math.min(OUTPUT_LIMITS.head, lines.length); i++) keep.add(i);
3286
+ for (let i = Math.max(0, lines.length - OUTPUT_LIMITS.tail); i < lines.length; i++) keep.add(i);
3287
+ lines.forEach((line, index2) => {
3288
+ if (!SIGNAL.test(line)) return;
3289
+ const from = Math.max(0, index2 - OUTPUT_LIMITS.contextBefore);
3290
+ const to = Math.min(lines.length - 1, index2 + OUTPUT_LIMITS.contextAfter);
3291
+ for (let i = from; i <= to; i++) keep.add(i);
3292
+ });
3293
+ const out = [];
3294
+ let index = 0;
3295
+ while (index < lines.length) {
3296
+ if (!keep.has(index)) {
3297
+ const start = index;
3298
+ while (index < lines.length && !keep.has(index)) index++;
3299
+ out.push(
3300
+ `\u2026 ${index - start} lines omitted (codebuddy output show ${id} --range ${start + 1}:${index})`
3301
+ );
3302
+ continue;
3303
+ }
3304
+ const line = lines[index];
3305
+ const same = SIGNAL.test(line) ? (other) => other === line : (other) => shape(other) === shape(line);
3306
+ let run = index + 1;
3307
+ while (run < lines.length && keep.has(run) && same(lines[run])) run++;
3308
+ out.push(line);
3309
+ if (run - index > 2) {
3310
+ out.push(
3311
+ `\u2026 ${run - index - 1} similar lines (codebuddy output show ${id} --range ${index + 2}:${run})`
3312
+ );
3313
+ index = run;
3314
+ } else index++;
3315
+ }
3316
+ const reduced = out.join("\n");
3317
+ return Buffer.byteLength(reduced) <= Buffer.byteLength(text3) * (1 - OUTPUT_LIMITS.minSaving) ? reduced : null;
3318
+ }
3319
+ async function storeOutput(root, command, output, exitCode, now = /* @__PURE__ */ new Date()) {
3320
+ const truncated = Buffer.byteLength(output) > OUTPUT_LIMITS.storeBytes;
3321
+ const body = truncated ? `${output.slice(0, OUTPUT_LIMITS.storeBytes)}
3322
+ [codebuddy: stored copy cut at ${OUTPUT_LIMITS.storeBytes} bytes]` : output;
3323
+ const id = createHash("sha256").update(`${command}\0${now.toISOString()}\0${body}`).digest("hex").slice(0, 12);
3324
+ const meta = {
3325
+ id,
3326
+ command,
3327
+ exitCode,
3328
+ createdAt: now.toISOString(),
3329
+ expiresAt: new Date(now.getTime() + OUTPUT_LIMITS.retentionDays * 864e5).toISOString(),
3330
+ lines: body.split("\n").length,
3331
+ bytes: Buffer.byteLength(body),
3332
+ truncated
3333
+ };
3334
+ const dir = outputDir(root);
3335
+ await mkdir2(dir, { recursive: true, mode: 448 });
3336
+ await writeAtomic(join6(dir, `${id}.log`), body, { mode: 384 });
3337
+ await writeAtomic(join6(dir, `${id}.json`), `${JSON.stringify(meta)}
3338
+ `, { mode: 384 });
3339
+ await pruneOutputs(root, now).catch(() => void 0);
3340
+ return meta;
3341
+ }
3342
+ async function pruneOutputs(root, now = /* @__PURE__ */ new Date()) {
3343
+ const dir = outputDir(root);
3344
+ let removed = 0;
3345
+ for (const name2 of (await readdir(dir).catch(() => [])).filter(
3346
+ (n) => n.endsWith(".json")
3347
+ )) {
3348
+ const meta = await readMeta(root, name2.slice(0, -5));
3349
+ if (meta && Date.parse(meta.expiresAt) <= now.getTime()) {
3350
+ await rm2(join6(dir, `${meta.id}.log`), { force: true });
3351
+ await rm2(join6(dir, name2), { force: true });
3352
+ removed++;
3353
+ }
3354
+ }
3355
+ return removed;
3356
+ }
3357
+ async function readMeta(root, id) {
3358
+ if (!/^[a-f0-9]{12}$/.test(id)) return null;
3359
+ try {
3360
+ return JSON.parse(await readFile3(join6(outputDir(root), `${id}.json`), "utf8"));
3361
+ } catch {
3362
+ return null;
3363
+ }
3364
+ }
3365
+ async function showOutput(root, id, options = {}) {
3366
+ const meta = await readMeta(root, id);
3367
+ if (!meta) return { status: "missing", id };
3368
+ if (Date.parse(meta.expiresAt) <= (options.now ?? /* @__PURE__ */ new Date()).getTime()) {
3369
+ return { status: "expired", id };
3370
+ }
3371
+ const body = await readFile3(join6(outputDir(root), `${id}.log`), "utf8").catch(() => null);
3372
+ if (body === null) return { status: "missing", id };
3373
+ const lines = body.split("\n");
3374
+ let picked;
3375
+ if (options.grep) {
3376
+ const pattern = new RegExp(options.grep, "i");
3377
+ const set = /* @__PURE__ */ new Set();
3378
+ lines.forEach((line, index) => {
3379
+ if (!pattern.test(line)) return;
3380
+ for (let i = Math.max(0, index - 2); i <= Math.min(lines.length - 1, index + 2); i++)
3381
+ set.add(i);
3382
+ });
3383
+ picked = [...set].sort((a, b) => a - b);
3384
+ } else {
3385
+ const [from, to] = options.range ?? [1, lines.length];
3386
+ picked = [];
3387
+ for (let i = Math.max(1, from); i <= Math.min(lines.length, to); i++) picked.push(i - 1);
3388
+ }
3389
+ const shown = picked.slice(0, OUTPUT_LIMITS.showLines);
3390
+ return {
3391
+ status: "ok",
3392
+ meta,
3393
+ text: shown.map((i) => `${i + 1}: ${lines[i]}`).join("\n"),
3394
+ more: picked.length > shown.length
3395
+ };
3396
+ }
3397
+ async function listOutputs(root) {
3398
+ const names = await readdir(outputDir(root)).catch(() => []);
3399
+ const metas = await Promise.all(
3400
+ names.filter((n) => n.endsWith(".json")).map((n) => readMeta(root, n.slice(0, -5)))
3401
+ );
3402
+ return metas.filter((meta) => meta !== null).sort((a, b) => b.createdAt.localeCompare(a.createdAt));
3403
+ }
3404
+
3405
+ // src/cli/commands/output.ts
3406
+ function registerOutputCommands(program, runSafely2) {
3407
+ program.command("run").argument("<command...>", "The command and its arguments (after --).").description(
3408
+ "Run a command; print a reduced view of long output and keep the original for `codebuddy output show`."
3409
+ ).action(async (parts) => {
3410
+ await runSafely2(async () => {
3411
+ const command = parts.join(" ");
3412
+ const { output: output2, exitCode } = await capture(parts);
3413
+ const root = process.cwd();
3414
+ const provisional = reduceOutput(output2, "xxxxxxxxxxxx");
3415
+ if (provisional === null) {
3416
+ process.stdout.write(output2);
3417
+ } else {
3418
+ const stored = await storeOutput(root, command, output2, exitCode);
3419
+ const reduced = reduceOutput(output2, stored.id) ?? output2;
3420
+ process.stdout.write(`${reduced}
3421
+ `);
3422
+ process.stdout.write(
3423
+ `[codebuddy run: exit ${exitCode ?? "signal"}; ${stored.lines} lines shown as ${reduced.split("\n").length}; full output: codebuddy output show ${stored.id} [--grep RE | --range A:B], kept until ${stored.expiresAt.slice(0, 10)}]
3424
+ `
3425
+ );
3426
+ }
3427
+ process.exitCode = exitCode ?? 1;
3428
+ });
3429
+ });
3430
+ const output = program.command("output").description("Stored originals of `codebuddy run` output.");
3431
+ output.command("list", { isDefault: true }).description("List stored outputs, newest first.").action(async () => {
3432
+ await runSafely2(async () => {
3433
+ const stored = await listOutputs(process.cwd());
3434
+ if (stored.length === 0) console.log(pc15.dim("no stored outputs"));
3435
+ for (const meta of stored) {
3436
+ console.log(
3437
+ `${meta.id} exit ${meta.exitCode ?? "-"} ${meta.lines} lines until ${meta.expiresAt.slice(0, 10)} ${meta.command.slice(0, 80)}`
3438
+ );
3439
+ }
3440
+ });
3441
+ });
3442
+ output.command("show").argument("<id>").option("--range <a:b>", "1-based inclusive line range.").option("--grep <pattern>", "Lines matching a regular expression, with 2 lines of context.").description("Print exact stored lines (at most 400 per call).").action(async (id, opts) => {
3443
+ await runSafely2(async () => {
3444
+ const range = opts.range?.match(/^(\d+):(\d+)$/);
3445
+ if (opts.range && !range) throw new Error("--range must look like 120:180");
3446
+ const result = await showOutput(process.cwd(), id, {
3447
+ ...range ? { range: [Number(range[1]), Number(range[2])] } : {},
3448
+ ...opts.grep ? { grep: opts.grep } : {}
3449
+ });
3450
+ if (result.status !== "ok") {
3451
+ throw new Error(`stored output ${id} is ${result.status}`);
3452
+ }
3453
+ console.log(result.text);
3454
+ if (result.more) console.log(pc15.dim("\u2026 more lines; narrow --range or --grep"));
3455
+ });
3456
+ });
3457
+ }
3458
+ function capture(parts) {
3459
+ return new Promise((resolve6, reject) => {
3460
+ const [file, ...args] = parts;
3461
+ const child = parts.length === 1 ? spawn5(file, { shell: true, stdio: ["inherit", "pipe", "pipe"] }) : spawn5(file, args, { stdio: ["inherit", "pipe", "pipe"] });
3462
+ const chunks = [];
3463
+ child.stdout.on("data", (chunk) => chunks.push(chunk));
3464
+ child.stderr.on("data", (chunk) => chunks.push(chunk));
3465
+ child.on("error", reject);
3466
+ child.on(
3467
+ "close",
3468
+ (code, signal) => resolve6({
3469
+ output: Buffer.concat(chunks).toString("utf8"),
3470
+ exitCode: code ?? (signal ? 128 + (constants2.signals[signal] ?? 0) : null)
3471
+ })
3472
+ );
3473
+ });
3474
+ }
3475
+
3476
+ // src/cli/commands/plan.ts
3477
+ import { spawn as spawn6 } from "child_process";
2991
3478
  import { relative as relative4 } from "path";
2992
- import pc14 from "picocolors";
3479
+ import pc16 from "picocolors";
2993
3480
  function makeLifecycle(cwd = process.cwd()) {
2994
3481
  const store = new PlanFileStore(cwd);
2995
3482
  return { store, lifecycle: new PlanLifecycle(store) };
@@ -2997,67 +3484,67 @@ function makeLifecycle(cwd = process.cwd()) {
2997
3484
  function statusColor(status) {
2998
3485
  switch (status) {
2999
3486
  case "draft":
3000
- return pc14.dim(status);
3487
+ return pc16.dim(status);
3001
3488
  case "approved":
3002
- return pc14.cyan(status);
3489
+ return pc16.cyan(status);
3003
3490
  case "executing":
3004
- return pc14.yellow(status);
3491
+ return pc16.yellow(status);
3005
3492
  case "complete":
3006
- return pc14.green(status);
3493
+ return pc16.green(status);
3007
3494
  case "abandoned":
3008
- return pc14.red(status);
3495
+ return pc16.red(status);
3009
3496
  }
3010
3497
  }
3011
3498
  function renderPlan(plan) {
3012
3499
  const lines = [];
3013
- lines.push(`${pc14.bold(plan.title)} ${statusColor(plan.status)}`);
3014
- lines.push(pc14.dim(plan.id));
3500
+ lines.push(`${pc16.bold(plan.title)} ${statusColor(plan.status)}`);
3501
+ lines.push(pc16.dim(plan.id));
3015
3502
  lines.push("");
3016
- lines.push(`${pc14.bold("Brief:")} ${plan.brief}`);
3503
+ lines.push(`${pc16.bold("Brief:")} ${plan.brief}`);
3017
3504
  if (plan.filesToTouch.length > 0) {
3018
3505
  lines.push("");
3019
- lines.push(pc14.bold("Files to touch:"));
3506
+ lines.push(pc16.bold("Files to touch:"));
3020
3507
  for (const file of plan.filesToTouch) {
3021
3508
  lines.push(` ${file.action.padEnd(6)} ${file.path}${file.notes ? ` \u2014 ${file.notes}` : ""}`);
3022
3509
  }
3023
3510
  }
3024
3511
  if (plan.testsToAdd.length > 0) {
3025
3512
  lines.push("");
3026
- lines.push(pc14.bold("Tests to add:"));
3513
+ lines.push(pc16.bold("Tests to add:"));
3027
3514
  for (const test of plan.testsToAdd) {
3028
3515
  lines.push(` ${test.path}${test.description ? ` \u2014 ${test.description}` : ""}`);
3029
3516
  }
3030
3517
  }
3031
3518
  if (plan.outOfScope.length > 0) {
3032
3519
  lines.push("");
3033
- lines.push(pc14.bold("Out of scope:"));
3520
+ lines.push(pc16.bold("Out of scope:"));
3034
3521
  for (const item of plan.outOfScope) lines.push(` - ${item}`);
3035
3522
  }
3036
3523
  if (plan.risks.length > 0) {
3037
3524
  lines.push("");
3038
- lines.push(pc14.bold("Risks:"));
3525
+ lines.push(pc16.bold("Risks:"));
3039
3526
  for (const item of plan.risks) lines.push(` - ${item}`);
3040
3527
  }
3041
3528
  if (plan.commitSha) {
3042
3529
  lines.push("");
3043
- lines.push(`${pc14.bold("Commit:")} ${plan.commitSha}`);
3530
+ lines.push(`${pc16.bold("Commit:")} ${plan.commitSha}`);
3044
3531
  }
3045
3532
  lines.push("");
3046
- lines.push(pc14.dim("\u2500".repeat(40)));
3533
+ lines.push(pc16.dim("\u2500".repeat(40)));
3047
3534
  lines.push(plan.body);
3048
3535
  return lines.join("\n");
3049
3536
  }
3050
3537
  function openEditor2(path) {
3051
3538
  const editor = process.env.EDITOR ?? process.env.VISUAL ?? "vi";
3052
3539
  return new Promise((resolve6, reject) => {
3053
- const child = spawn5(editor, [path], { stdio: "inherit" });
3540
+ const child = spawn6(editor, [path], { stdio: "inherit" });
3054
3541
  child.on("error", reject);
3055
3542
  child.on("close", () => resolve6());
3056
3543
  });
3057
3544
  }
3058
3545
  function runGit(args) {
3059
3546
  return new Promise((resolve6, reject) => {
3060
- const child = spawn5("git", args, { stdio: ["ignore", "pipe", "pipe"] });
3547
+ const child = spawn6("git", args, { stdio: ["ignore", "pipe", "pipe"] });
3061
3548
  let stdout = "";
3062
3549
  let stderr = "";
3063
3550
  child.stdout.on("data", (c) => {
@@ -3082,14 +3569,14 @@ function registerPlanCommands(program, runSafely2) {
3082
3569
  title: opts.title ?? brief,
3083
3570
  brief
3084
3571
  });
3085
- console.log(`${pc14.green("created")} ${created.id} (${statusColor(created.status)})`);
3572
+ console.log(`${pc16.green("created")} ${created.id} (${statusColor(created.status)})`);
3086
3573
  if (opts.edit !== false && process.stdin.isTTY) {
3087
3574
  await openEditor2(created.path);
3088
3575
  const { store } = makeLifecycle();
3089
3576
  await store.readPlan(created.id);
3090
- console.log(pc14.dim("saved and validated"));
3577
+ console.log(pc16.dim("saved and validated"));
3091
3578
  }
3092
- console.log(pc14.dim(relative4(process.cwd(), created.path)));
3579
+ console.log(pc16.dim(relative4(process.cwd(), created.path)));
3093
3580
  });
3094
3581
  });
3095
3582
  plan.command("list").option("--status <status>", "Filter by status.").option("--all", "Include completed and abandoned plans.").description("List plans in the current namespace.").action(async (opts) => {
@@ -3101,11 +3588,11 @@ function registerPlanCommands(program, runSafely2) {
3101
3588
  else if (!opts.all)
3102
3589
  plans = plans.filter((p) => p.status !== "complete" && p.status !== "abandoned");
3103
3590
  if (plans.length === 0) {
3104
- console.log(pc14.dim("no plans"));
3591
+ console.log(pc16.dim("no plans"));
3105
3592
  return;
3106
3593
  }
3107
3594
  for (const p of plans) {
3108
- console.log(`${statusColor(p.status).padEnd(20)} ${pc14.bold(p.title)} ${pc14.dim(p.id)}`);
3595
+ console.log(`${statusColor(p.status).padEnd(20)} ${pc16.bold(p.title)} ${pc16.dim(p.id)}`);
3109
3596
  }
3110
3597
  });
3111
3598
  });
@@ -3121,7 +3608,7 @@ function registerPlanCommands(program, runSafely2) {
3121
3608
  const existing = await store.readPlan(id);
3122
3609
  await openEditor2(existing.path);
3123
3610
  await store.readPlan(id);
3124
- console.log(pc14.dim("saved and validated"));
3611
+ console.log(pc16.dim("saved and validated"));
3125
3612
  });
3126
3613
  });
3127
3614
  plan.command("approve").argument("<id>").description("Promote a draft to approved (one active plan per namespace).").action(async (id) => {
@@ -3145,7 +3632,7 @@ function registerPlanCommands(program, runSafely2) {
3145
3632
  ...opts.commit ? { commitSha: opts.commit } : {},
3146
3633
  ...opts.notes ? { notes: opts.notes } : {}
3147
3634
  });
3148
- if (!opts.commit) console.log(pc14.yellow("note: no --commit supplied"));
3635
+ if (!opts.commit) console.log(pc16.yellow("note: no --commit supplied"));
3149
3636
  console.log(`${next.id} \u2192 ${statusColor(next.status)}`);
3150
3637
  });
3151
3638
  });
@@ -3164,7 +3651,7 @@ function registerPlanCommands(program, runSafely2) {
3164
3651
  const args = ["diff", opts.from, ...opts.to ? [opts.to] : [], "--", relPath];
3165
3652
  const result = await runGit(args);
3166
3653
  if (result.stdout.trim().length === 0) {
3167
- console.log(pc14.dim("no changes"));
3654
+ console.log(pc16.dim("no changes"));
3168
3655
  } else {
3169
3656
  process.stdout.write(result.stdout);
3170
3657
  }
@@ -3181,13 +3668,13 @@ function registerPlanCommands(program, runSafely2) {
3181
3668
  throw new Error(`Invalid policy "${mode}". Use one of: ${PLAN_POLICIES.join(", ")}.`);
3182
3669
  }
3183
3670
  await setPlanPolicy(mode);
3184
- console.log(`${pc14.green("plan policy")} = ${mode}`);
3671
+ console.log(`${pc16.green("plan policy")} = ${mode}`);
3185
3672
  });
3186
3673
  });
3187
3674
  }
3188
3675
 
3189
3676
  // src/cli/commands/risk.ts
3190
- import pc15 from "picocolors";
3677
+ import pc17 from "picocolors";
3191
3678
  function registerRiskCommands(program, runSafely2) {
3192
3679
  const risk = program.command("risk").description("Assess evidence-backed change risk.");
3193
3680
  risk.command("assess").option("--plan <id>", "Assess paths listed in a CodeBuddy plan.").option("--paths <paths>", "Comma-separated repo-relative paths to assess.").option("--git", "Assess staged, unstaged, and untracked git changes.").option("--limit <n>", "Maximum risk items to show.", Number).option("--json", "Print machine-readable JSON.").option("--explain", "Show evidence for each risk item.").description("Assess risk for a plan, explicit paths, or current git changes.").action(
@@ -3215,31 +3702,31 @@ function parsePaths3(value) {
3215
3702
  }
3216
3703
  function printAssessment(assessment, explain) {
3217
3704
  if (assessment.items.length === 0) {
3218
- console.log(pc15.dim("no risk evidence found"));
3705
+ console.log(pc17.dim("no risk evidence found"));
3219
3706
  return;
3220
3707
  }
3221
3708
  for (const item of assessment.items) {
3222
3709
  console.log(
3223
- `${pc15.bold(item.path)} score ${scoreColor2(item.score)} category ${pc15.cyan(item.category)}`
3710
+ `${pc17.bold(item.path)} score ${scoreColor2(item.score)} category ${pc17.cyan(item.category)}`
3224
3711
  );
3225
3712
  console.log(` ${item.reason}`);
3226
3713
  if (explain) {
3227
3714
  for (const evidence of item.evidence) console.log(` \u21B3 ${formatEvidence(evidence)}`);
3228
3715
  } else {
3229
- console.log(pc15.dim(` ${item.evidence.length} evidence item(s)`));
3716
+ console.log(pc17.dim(` ${item.evidence.length} evidence item(s)`));
3230
3717
  }
3231
3718
  }
3232
3719
  console.log(
3233
- pc15.dim(
3720
+ pc17.dim(
3234
3721
  `
3235
3722
  ${assessment.stats.itemsProduced} item(s), ${assessment.stats.signalsRun} signal(s), ${assessment.stats.pathsConsidered} path(s) considered`
3236
3723
  )
3237
3724
  );
3238
3725
  }
3239
3726
  function scoreColor2(score) {
3240
- if (score >= 75) return pc15.red(String(score));
3241
- if (score >= 50) return pc15.yellow(String(score));
3242
- return pc15.green(String(score));
3727
+ if (score >= 75) return pc17.red(String(score));
3728
+ if (score >= 50) return pc17.yellow(String(score));
3729
+ return pc17.green(String(score));
3243
3730
  }
3244
3731
  function formatEvidence(evidence) {
3245
3732
  switch (evidence.kind) {
@@ -3261,7 +3748,7 @@ function truncate2(value, max) {
3261
3748
 
3262
3749
  // src/cli/commands/rules.ts
3263
3750
  import { relative as relative5 } from "path";
3264
- import pc16 from "picocolors";
3751
+ import pc18 from "picocolors";
3265
3752
  async function resolveMode(client, value) {
3266
3753
  if (value) {
3267
3754
  const mode = parseMode(value);
@@ -3301,7 +3788,7 @@ function registerRulesCommands(program, runSafely2) {
3301
3788
  `${actionLabel(result.action)} ${clientDisplayName(client)} workflow in ${where}`
3302
3789
  );
3303
3790
  if (result.action !== "unchanged") {
3304
- console.log(pc16.dim(" managed block between codebuddy:workflow markers; safe to re-run"));
3791
+ console.log(pc18.dim(" managed block between codebuddy:workflow markers; safe to re-run"));
3305
3792
  }
3306
3793
  });
3307
3794
  });
@@ -3309,20 +3796,20 @@ function registerRulesCommands(program, runSafely2) {
3309
3796
  function actionLabel(action) {
3310
3797
  switch (action) {
3311
3798
  case "created":
3312
- return pc16.green("created");
3799
+ return pc18.green("created");
3313
3800
  case "added":
3314
- return pc16.green("added");
3801
+ return pc18.green("added");
3315
3802
  case "updated":
3316
- return pc16.cyan("updated");
3803
+ return pc18.cyan("updated");
3317
3804
  case "removed":
3318
- return pc16.yellow("removed");
3805
+ return pc18.yellow("removed");
3319
3806
  default:
3320
- return pc16.dim("unchanged");
3807
+ return pc18.dim("unchanged");
3321
3808
  }
3322
3809
  }
3323
3810
 
3324
3811
  // src/cli/commands/suggest.ts
3325
- import pc17 from "picocolors";
3812
+ import pc19 from "picocolors";
3326
3813
  function parsePaths4(value) {
3327
3814
  return value.split(",").map((path) => path.trim()).filter(Boolean);
3328
3815
  }
@@ -3344,7 +3831,7 @@ function registerSuggestCommand(program, runSafely2) {
3344
3831
  }
3345
3832
  printSuggestions(result.suggestions);
3346
3833
  console.log(
3347
- pc17.dim(
3834
+ pc19.dim(
3348
3835
  `
3349
3836
  ${result.stats.produced} suggestion(s) across ${result.stats.pathsConsidered} path(s)`
3350
3837
  )
@@ -3355,72 +3842,72 @@ ${result.stats.produced} suggestion(s) across ${result.stats.pathsConsidered} pa
3355
3842
  }
3356
3843
  function printSuggestions(suggestions) {
3357
3844
  if (suggestions.length === 0) {
3358
- console.log(pc17.dim("no suggestions \u2014 nothing high-signal to report"));
3845
+ console.log(pc19.dim("no suggestions \u2014 nothing high-signal to report"));
3359
3846
  return;
3360
3847
  }
3361
3848
  for (const suggestion of suggestions) {
3362
- console.log(`${severityBadge2(suggestion.severity)} ${pc17.bold(suggestion.title)}`);
3849
+ console.log(`${severityBadge2(suggestion.severity)} ${pc19.bold(suggestion.title)}`);
3363
3850
  console.log(` ${suggestion.detail}`);
3364
3851
  for (const evidence of suggestion.evidence) {
3365
- console.log(pc17.dim(` \u21B3 ${evidence.kind}: ${evidence.detail}`));
3852
+ console.log(pc19.dim(` \u21B3 ${evidence.kind}: ${evidence.detail}`));
3366
3853
  }
3367
- if (suggestion.commandHint) console.log(pc17.dim(` \u2192 ${suggestion.commandHint}`));
3854
+ if (suggestion.commandHint) console.log(pc19.dim(` \u2192 ${suggestion.commandHint}`));
3368
3855
  }
3369
3856
  }
3370
3857
  function severityBadge2(severity) {
3371
3858
  switch (severity) {
3372
3859
  case "high":
3373
- return pc17.red("[high]");
3860
+ return pc19.red("[high]");
3374
3861
  case "medium":
3375
- return pc17.yellow("[med ]");
3862
+ return pc19.yellow("[med ]");
3376
3863
  case "low":
3377
- return pc17.cyan("[low ]");
3864
+ return pc19.cyan("[low ]");
3378
3865
  default:
3379
- return pc17.dim("[info]");
3866
+ return pc19.dim("[info]");
3380
3867
  }
3381
3868
  }
3382
3869
 
3383
3870
  // src/cli/commands/symbols.ts
3384
- import { readFile as readFile3 } from "fs/promises";
3871
+ import { readFile as readFile4 } from "fs/promises";
3385
3872
  import { resolve as resolve5 } from "path";
3386
- import pc18 from "picocolors";
3873
+ import pc20 from "picocolors";
3387
3874
  function registerSymbolsCommand(program, runSafely2) {
3388
3875
  program.command("symbols").argument("<path>", "Repo-relative or absolute path to a code file.").option("--json", "Print machine-readable JSON.").description("List a file's top-level symbols (functions, classes, types, exports).").action(async (path, opts) => {
3389
3876
  await runSafely2(async () => {
3390
3877
  if (!supportsSymbols(path)) {
3391
- console.log(pc18.dim(`no symbol extraction for ${path} (unsupported language)`));
3878
+ console.log(pc20.dim(`no symbol extraction for ${path} (unsupported language)`));
3392
3879
  return;
3393
3880
  }
3394
- const content = await readFile3(resolve5(path), "utf8");
3881
+ const content = await readFile4(resolve5(path), "utf8");
3395
3882
  const table = extractSymbolTable(content, path);
3396
3883
  if (opts.json) {
3397
3884
  console.log(JSON.stringify({ path, symbols: table }, null, 2));
3398
3885
  return;
3399
3886
  }
3400
3887
  if (table.length === 0) {
3401
- console.log(pc18.dim("no top-level symbols found"));
3888
+ console.log(pc20.dim("no top-level symbols found"));
3402
3889
  return;
3403
3890
  }
3404
3891
  for (const symbol of table) {
3405
- const badge = symbol.exported ? pc18.green("export") : pc18.dim("local ");
3892
+ const badge = symbol.exported ? pc20.green("export") : pc20.dim("local ");
3406
3893
  console.log(
3407
- `${badge} ${pc18.cyan(symbol.kind.padEnd(9))} ${pc18.bold(symbol.name)} ${pc18.dim(`L${symbol.startLine}-${symbol.endLine}`)}`
3894
+ `${badge} ${pc20.cyan(symbol.kind.padEnd(9))} ${pc20.bold(symbol.name)} ${pc20.dim(`L${symbol.startLine}-${symbol.endLine}`)}`
3408
3895
  );
3409
3896
  }
3410
- console.log(pc18.dim(`
3897
+ console.log(pc20.dim(`
3411
3898
  ${table.length} symbol(s)`));
3412
3899
  });
3413
3900
  });
3414
3901
  }
3415
3902
 
3416
3903
  // src/cli/commands/telemetry.ts
3417
- import pc19 from "picocolors";
3904
+ import pc21 from "picocolors";
3418
3905
 
3419
3906
  // src/telemetry/collector.ts
3420
- import { createHash, timingSafeEqual } from "crypto";
3421
- import { appendFile, mkdir as mkdir2, readdir, readFile as readFile4 } from "fs/promises";
3907
+ import { createHash as createHash2, timingSafeEqual } from "crypto";
3908
+ import { appendFile, mkdir as mkdir3, readdir as readdir2, readFile as readFile5 } from "fs/promises";
3422
3909
  import { createServer as createServer2 } from "http";
3423
- import { join as join6 } from "path";
3910
+ import { join as join7 } from "path";
3424
3911
  import { z as z2 } from "zod";
3425
3912
 
3426
3913
  // src/telemetry/admin-page.ts
@@ -3945,7 +4432,7 @@ var batchSchema = z2.object({
3945
4432
  });
3946
4433
  async function startCollector(options) {
3947
4434
  const { dataDir } = options;
3948
- await mkdir2(dataDir, { recursive: true });
4435
+ await mkdir3(dataDir, { recursive: true });
3949
4436
  const registry = await loadRegistry(dataDir);
3950
4437
  let summaryCache;
3951
4438
  let npmCache;
@@ -3993,7 +4480,7 @@ async function startCollector(options) {
3993
4480
  if (!registry.has(batch.installId)) {
3994
4481
  registry.set(batch.installId, receivedAt);
3995
4482
  await appendFile(
3996
- join6(dataDir, "installs.jsonl"),
4483
+ join7(dataDir, "installs.jsonl"),
3997
4484
  `${JSON.stringify({ installId: batch.installId, firstSeen: receivedAt })}
3998
4485
  `
3999
4486
  );
@@ -4001,7 +4488,7 @@ async function startCollector(options) {
4001
4488
  const { events, ...meta } = batch;
4002
4489
  const lines = events.map((event) => JSON.stringify({ ...meta, ...event, receivedAt }));
4003
4490
  await appendFile(
4004
- join6(dataDir, `events-${receivedAt.slice(0, 10)}.jsonl`),
4491
+ join7(dataDir, `events-${receivedAt.slice(0, 10)}.jsonl`),
4005
4492
  `${lines.join("\n")}
4006
4493
  `
4007
4494
  );
@@ -4098,7 +4585,7 @@ function passwordMatches(request, password3) {
4098
4585
  if (!header.startsWith("Basic ")) return false;
4099
4586
  const decoded = Buffer.from(header.slice(6), "base64").toString("utf8");
4100
4587
  const given = decoded.slice(decoded.indexOf(":") + 1);
4101
- const digest = (value) => createHash("sha256").update(value).digest();
4588
+ const digest = (value) => createHash2("sha256").update(value).digest();
4102
4589
  return timingSafeEqual(digest(given), digest(password3));
4103
4590
  }
4104
4591
  function readBody(request) {
@@ -4122,10 +4609,10 @@ function listen2(server, port) {
4122
4609
  }
4123
4610
  async function readEvents(dataDir, days, now = Date.now()) {
4124
4611
  const since = new Date(now - (days - 1) * 864e5).toISOString().slice(0, 10);
4125
- const files = (await readdir(dataDir).catch(() => [])).filter((file) => /^events-\d{4}-\d{2}-\d{2}\.jsonl$/.test(file) && file.slice(7, 17) >= since).sort();
4612
+ const files = (await readdir2(dataDir).catch(() => [])).filter((file) => /^events-\d{4}-\d{2}-\d{2}\.jsonl$/.test(file) && file.slice(7, 17) >= since).sort();
4126
4613
  const events = [];
4127
4614
  for (const file of files) {
4128
- for (const line of (await readFile4(join6(dataDir, file), "utf8")).split("\n")) {
4615
+ for (const line of (await readFile5(join7(dataDir, file), "utf8")).split("\n")) {
4129
4616
  if (!line) continue;
4130
4617
  try {
4131
4618
  events.push(JSON.parse(line));
@@ -4400,8 +4887,8 @@ function installTrace(events, prefix) {
4400
4887
  }
4401
4888
  async function loadRegistry(dataDir) {
4402
4889
  const registry = /* @__PURE__ */ new Map();
4403
- const path = join6(dataDir, "installs.jsonl");
4404
- const raw = await readFile4(path, "utf8").catch(() => null);
4890
+ const path = join7(dataDir, "installs.jsonl");
4891
+ const raw = await readFile5(path, "utf8").catch(() => null);
4405
4892
  if (raw !== null) {
4406
4893
  for (const line of raw.split("\n")) {
4407
4894
  try {
@@ -4446,10 +4933,10 @@ function registerTelemetryCommands(program, runSafely2) {
4446
4933
  const state = await readTelemetryState();
4447
4934
  const blocked = telemetryBlocked(state);
4448
4935
  console.log(
4449
- blocked ? `${pc19.yellow("\u25CB")} telemetry off (${blocked})` : `${pc19.green("\u25CF")} telemetry on`
4936
+ blocked ? `${pc21.yellow("\u25CB")} telemetry off (${blocked})` : `${pc21.green("\u25CF")} telemetry on`
4450
4937
  );
4451
- console.log(pc19.dim(` install id ${state.installId} (${telemetryStatePath()})`));
4452
- if (telemetryUrl()) console.log(pc19.dim(` collector ${telemetryUrl()}`));
4938
+ console.log(pc21.dim(` install id ${state.installId} (${telemetryStatePath()})`));
4939
+ if (telemetryUrl()) console.log(pc21.dim(` collector ${telemetryUrl()}`));
4453
4940
  });
4454
4941
  });
4455
4942
  telemetry.command("enable").description("Allow anonymous usage data from this machine.").action(async () => {
@@ -4460,7 +4947,7 @@ function registerTelemetryCommands(program, runSafely2) {
4460
4947
  noticeAt: state.noticeAt ?? (/* @__PURE__ */ new Date()).toISOString()
4461
4948
  });
4462
4949
  console.log(TELEMETRY_NOTICE);
4463
- console.log(pc19.green("Telemetry enabled."));
4950
+ console.log(pc21.green("Telemetry enabled."));
4464
4951
  });
4465
4952
  });
4466
4953
  telemetry.command("disable").description("Stop sending anonymous usage data from this machine.").action(async () => {
@@ -4488,14 +4975,14 @@ function registerTelemetryCommands(program, runSafely2) {
4488
4975
  adminPort: opts.adminPort,
4489
4976
  ...process.env.CODEBUDDY_ADMIN_PASSWORD ? { adminPassword: process.env.CODEBUDDY_ADMIN_PASSWORD } : {}
4490
4977
  });
4491
- console.log(`${pc19.green("\u25CF")} ingest ${pc19.bold(collector.ingestUrl)}`);
4492
- console.log(`${pc19.green("\u25CF")} admin ${pc19.bold(collector.adminUrl)}`);
4978
+ console.log(`${pc21.green("\u25CF")} ingest ${pc21.bold(collector.ingestUrl)}`);
4979
+ console.log(`${pc21.green("\u25CF")} admin ${pc21.bold(collector.adminUrl)}`);
4493
4980
  console.log(
4494
- pc19.dim(
4981
+ pc21.dim(
4495
4982
  process.env.CODEBUDDY_ADMIN_PASSWORD ? " admin panel is password-protected (CODEBUDDY_ADMIN_PASSWORD)." : " admin panel has no password: keep it private, or set CODEBUDDY_ADMIN_PASSWORD."
4496
4983
  )
4497
4984
  );
4498
- console.log(pc19.dim(` events in ${opts.data}. Ctrl+C to stop.`));
4985
+ console.log(pc21.dim(` events in ${opts.data}. Ctrl+C to stop.`));
4499
4986
  await new Promise((resolve6) => {
4500
4987
  const stop = () => void collector.close().then(resolve6);
4501
4988
  process.once("SIGINT", stop);
@@ -4506,54 +4993,10 @@ function registerTelemetryCommands(program, runSafely2) {
4506
4993
  }
4507
4994
 
4508
4995
  // src/cli/commands/use-command.ts
4996
+ import { execFile as execFile3, spawn as spawn7 } from "child_process";
4509
4997
  import { basename as basename2 } from "path";
4510
4998
  import { confirm as confirm2, isCancel as isCancel2, log as log2, note as note2, password as password2, select, spinner as spinner3, text as text2 } from "@clack/prompts";
4511
- import pc20 from "picocolors";
4512
-
4513
- // src/core/global-config.ts
4514
- import { chmod, mkdir as mkdir3, readFile as readFile5, writeFile as writeFile3 } from "fs/promises";
4515
- import { homedir } from "os";
4516
- import { dirname as dirname3, join as join7 } from "path";
4517
- import { z as z3 } from "zod";
4518
- var GLOBAL_DIR = ".codebuddy";
4519
- var GLOBAL_FILE = "global.json";
4520
- var globalConfigSchema = z3.object({
4521
- hfToken: z3.string().min(1).optional(),
4522
- databaseUrl: z3.string().min(1).optional(),
4523
- // Where the codebuddy install lives on this machine. Cached so future
4524
- // `codebuddy use` calls in unrelated projects can find the CLI entry
4525
- // without re-resolving via process.argv every time.
4526
- installPath: z3.string().optional()
4527
- });
4528
- function globalConfigPath(home = homedir()) {
4529
- return join7(home, GLOBAL_DIR, GLOBAL_FILE);
4530
- }
4531
- async function readGlobalConfig(home = homedir()) {
4532
- const path = globalConfigPath(home);
4533
- try {
4534
- const raw = await readFile5(path, "utf8");
4535
- return globalConfigSchema.parse(JSON.parse(raw));
4536
- } catch (error) {
4537
- if (error.code === "ENOENT") return {};
4538
- throw error;
4539
- }
4540
- }
4541
- async function writeGlobalConfig(next, home = homedir()) {
4542
- const path = globalConfigPath(home);
4543
- await mkdir3(dirname3(path), { recursive: true, mode: 448 });
4544
- await writeFile3(path, `${JSON.stringify(next, null, 2)}
4545
- `, { mode: 384 });
4546
- await chmod(path, 384);
4547
- return { path };
4548
- }
4549
- async function mergeGlobalConfig(patch, home = homedir()) {
4550
- const current = await readGlobalConfig(home);
4551
- const merged = { ...current, ...patch };
4552
- const { path } = await writeGlobalConfig(merged, home);
4553
- return { path, merged };
4554
- }
4555
-
4556
- // src/cli/commands/use-command.ts
4999
+ import pc22 from "picocolors";
4557
5000
  function bail2() {
4558
5001
  log2.warn("Aborted.");
4559
5002
  process.exit(1);
@@ -4572,6 +5015,131 @@ async function pingOnce2(databaseUrl) {
4572
5015
  await client.close().catch(() => void 0);
4573
5016
  }
4574
5017
  }
5018
+ async function setupZvec(configured) {
5019
+ if (configured === false) {
5020
+ log2.info(`zvec-grep: disabled in config (enable with ${pc22.cyan("codebuddy zvec enable")}).`);
5021
+ return;
5022
+ }
5023
+ let report = await zvecStatus(process.cwd()).catch(() => null);
5024
+ if (report && report.version === null && process.env.CODEBUDDY_ZVEC !== "0") {
5025
+ const install = spinner3();
5026
+ install.start(`Installing zvec-grep (${ZVEC_PACKAGE})`);
5027
+ const code2 = await npmGlobalInstall(ZVEC_PACKAGE);
5028
+ install.stop(code2 === 0 ? "zvec-grep installed." : `zvec-grep install failed (exit ${code2}).`);
5029
+ if (code2 === 0) report = await zvecStatus(process.cwd()).catch(() => null);
5030
+ }
5031
+ if (!report?.supported) {
5032
+ log2.warn(
5033
+ `zvec-grep: ${report?.reason ?? "unavailable"}; keyword-only file discovery. Install with ${pc22.cyan("npm install -g @zvec/zvec-grep")}, then ${pc22.cyan("codebuddy zvec index && codebuddy zvec enable")}.`
5034
+ );
5035
+ return;
5036
+ }
5037
+ const zvecSpinner = spinner3();
5038
+ zvecSpinner.start("Building the zvec-grep semantic index");
5039
+ const code = report.indexReady ? 0 : await buildZvecIndex(process.cwd(), { quiet: true });
5040
+ if (code !== 0) {
5041
+ zvecSpinner.stop(`zvec-grep index failed (exit ${code}); keyword-only file discovery.`);
5042
+ return;
5043
+ }
5044
+ await setZvecEnabled(true);
5045
+ zvecSpinner.stop("zvec-grep semantic file discovery enabled.");
5046
+ }
5047
+ var ZVEC_PACKAGE = "@zvec/zvec-grep@0.2";
5048
+ function npmGlobalInstall(spec) {
5049
+ return new Promise((resolve6) => {
5050
+ execFile3(
5051
+ "npm",
5052
+ ["install", "-g", spec],
5053
+ { timeout: 10 * 6e4 },
5054
+ (error) => resolve6(error ? typeof error.code === "number" ? error.code : 1 : 0)
5055
+ );
5056
+ });
5057
+ }
5058
+ async function setupEmbeddings(embeddings) {
5059
+ if (embeddings !== "local" || process.env.CODEBUDDY_SEMANTIC === "0") return;
5060
+ const pull = spinner3();
5061
+ pull.start("Downloading the local embedding model (~90 MB, once)");
5062
+ try {
5063
+ const { LocalModelProvider } = await import("./local-ST4WMRCG.js");
5064
+ await new LocalModelProvider().embed({ input: "warm up" });
5065
+ pull.stop("Local embedding model ready.");
5066
+ } catch (error) {
5067
+ pull.stop(
5068
+ `Embedding model unavailable (${error.message.split("\n")[0]}); recall stays lexical until ${pc22.cyan("codebuddy models pull")} succeeds.`
5069
+ );
5070
+ }
5071
+ }
5072
+ function startBackgroundIndex(withModel) {
5073
+ const entry = process.argv[1];
5074
+ if (!entry) return;
5075
+ const child = spawn7(process.execPath, [entry, "index", "--cards"], {
5076
+ cwd: process.cwd(),
5077
+ detached: true,
5078
+ stdio: "ignore"
5079
+ });
5080
+ child.unref();
5081
+ log2.info(
5082
+ withModel ? "Building file vectors, intent cards and project facts in the background." : "Building file vectors in the background."
5083
+ );
5084
+ }
5085
+ async function setupLocalLlm(choice, interactive) {
5086
+ if (choice === false || process.env.CODEBUDDY_LOCAL_LLM === "0") {
5087
+ log2.info(`Local LLM: skipped (add later with ${pc22.cyan("codebuddy models add local-llm")}).`);
5088
+ return false;
5089
+ }
5090
+ const { installedLocalModel, machineCanCard } = await import("./local-75DJECVR.js");
5091
+ let installed = await installedLocalModel() !== null;
5092
+ const machine = machineCanCard();
5093
+ if (!installed && !machine.ok) {
5094
+ if (!await setupCloudQwen(machine.reason, interactive)) return false;
5095
+ installed = true;
5096
+ }
5097
+ if (!installed) {
5098
+ const { addLocalLlm: addLocalLlm2 } = await import("./local-llm-MECVJTVU.js");
5099
+ installed = await addLocalLlm2({ yes: true, interactive });
5100
+ if (!installed) {
5101
+ log2.warn(
5102
+ `Local LLM not installed; file discovery uses keywords and vectors only. Retry with ${pc22.cyan("codebuddy models add local-llm")}.`
5103
+ );
5104
+ return false;
5105
+ }
5106
+ }
5107
+ return installed;
5108
+ }
5109
+ async function setupCloudQwen(reason, interactive) {
5110
+ const existing = (await readConfigFile()).llm;
5111
+ if (existing && existing.provider !== "local") return existing.enabled;
5112
+ const fromEnv = process.env.GROQ_API_KEY ? "groq" : process.env.OPENROUTER_API_KEY ? "openrouter" : null;
5113
+ const saved = await readGlobalConfig().catch(
5114
+ () => ({})
5115
+ );
5116
+ let chosen = fromEnv ?? (saved.groqApiKey ? "groq" : saved.openrouterApiKey ? "openrouter" : null);
5117
+ if (!chosen && interactive) {
5118
+ log2.info(
5119
+ `This machine is too small for the local model (${reason}), so CodeBuddy can use Qwen in the cloud. Get a free Groq key at ${pc22.cyan("https://console.groq.com/keys")}.`
5120
+ );
5121
+ const key = unwrap2(
5122
+ await password2({
5123
+ message: "Groq API key (Enter to skip; saved to ~/.codebuddy/global.json)"
5124
+ })
5125
+ ).trim();
5126
+ if (key) {
5127
+ await mergeGlobalConfig({ groqApiKey: key });
5128
+ chosen = "groq";
5129
+ }
5130
+ }
5131
+ if (!chosen) {
5132
+ log2.warn(
5133
+ `Local LLM: this machine is too small for it (${reason}). For cloud Qwen, get a free key at ${pc22.cyan("https://console.groq.com/keys")} and rerun ${pc22.cyan("codebuddy use")} (or ${pc22.cyan("export GROQ_API_KEY=\u2026")} and ${pc22.cyan("codebuddy llm enable --provider groq")}). Keyword + vector discovery work meanwhile.`
5134
+ );
5135
+ return false;
5136
+ }
5137
+ await setLlmEnabled(true, { provider: chosen });
5138
+ log2.info(
5139
+ `LLM: this machine is too small for the local model (${reason}); using Qwen (qwen/qwen3.8-27b) on ${chosen}. Redacted file heads and signatures are sent to it; turn off with \`codebuddy llm disable\`.`
5140
+ );
5141
+ return true;
5142
+ }
4575
5143
  function sanitizeNamespace(input) {
4576
5144
  return input.replace(/[^a-zA-Z0-9_.:-]/g, "-").slice(0, 128) || "default";
4577
5145
  }
@@ -4592,15 +5160,15 @@ async function runUseCommand(options = {}) {
4592
5160
  })
4593
5161
  ));
4594
5162
  note2(
4595
- `Project folder: ${pc20.bold(process.cwd())}
4596
- Namespace: ${pc20.bold(namespace)}`,
5163
+ `Project folder: ${pc22.bold(process.cwd())}
5164
+ Namespace: ${pc22.bold(namespace)}`,
4597
5165
  "codebuddy use"
4598
5166
  );
4599
5167
  const stored = await readGlobalConfig();
4600
5168
  const projectConfig = await readConfigFile();
4601
5169
  let embeddings = projectConfig.provider?.type;
4602
5170
  if (embeddings) {
4603
- log2.info(`Embeddings: ${embeddings} (change with ${pc20.cyan("codebuddy models use")}).`);
5171
+ log2.info(`Embeddings: ${embeddings} (change with ${pc22.cyan("codebuddy models use")}).`);
4604
5172
  } else if (!interactive) {
4605
5173
  embeddings = "local";
4606
5174
  } else {
@@ -4641,7 +5209,7 @@ Namespace: ${pc20.bold(namespace)}`,
4641
5209
  log2.info("Using HF_TOKEN from environment.");
4642
5210
  } else if (stored.hfToken) {
4643
5211
  hfToken = stored.hfToken;
4644
- log2.info(`Reusing saved Hugging Face token (${pc20.dim("~/.codebuddy/global.json")}).`);
5212
+ log2.info(`Reusing saved Hugging Face token (${pc22.dim("~/.codebuddy/global.json")}).`);
4645
5213
  } else if (!interactive) {
4646
5214
  throw new Error(
4647
5215
  "Hugging Face embeddings need a token: set HF_TOKEN (no terminal to prompt for it)."
@@ -4757,6 +5325,9 @@ Namespace: ${pc20.bold(namespace)}`,
4757
5325
  tokenBudget: existingConfig.tokenBudget ?? 4e3,
4758
5326
  graphify: { enabled: graphifyConfigured, graphPath: "graphify-out/graph.json" }
4759
5327
  });
5328
+ await setupEmbeddings(embeddings);
5329
+ await setupZvec(existingConfig.zvec?.enabled);
5330
+ startBackgroundIndex(await setupLocalLlm(options.localLlm, interactive));
4760
5331
  const graphifyMessage = graphifySetup?.message ?? "Graphify disabled; CodeBuddy MCP remains available.";
4761
5332
  await writeProjectMcpConfig({
4762
5333
  repositoryRoot: process.cwd(),
@@ -4775,7 +5346,7 @@ Namespace: ${pc20.bold(namespace)}`,
4775
5346
  databaseUrl
4776
5347
  });
4777
5348
  log2.success(
4778
- `Registered ${pc20.bold(install.serverKey)} in Claude Desktop. Restart with \u2318Q to activate.`
5349
+ `Registered ${pc22.bold(install.serverKey)} in Claude Desktop. Restart with \u2318Q to activate.`
4779
5350
  );
4780
5351
  } catch (error) {
4781
5352
  log2.warn(`Claude Desktop registration skipped: ${error.message}`);
@@ -4790,7 +5361,7 @@ Namespace: ${pc20.bold(namespace)}`,
4790
5361
  databaseUrl
4791
5362
  });
4792
5363
  log2.success(
4793
- `Registered ${pc20.bold(install.serverKey)} in Codex. Restart Codex to activate.`
5364
+ `Registered ${pc22.bold(install.serverKey)} in Codex. Restart Codex to activate.`
4794
5365
  );
4795
5366
  } catch (error) {
4796
5367
  log2.warn(`Codex registration skipped: ${error.message}`);
@@ -4798,24 +5369,24 @@ Namespace: ${pc20.bold(namespace)}`,
4798
5369
  }
4799
5370
  note2(
4800
5371
  [
4801
- `${pc20.green("\u2713")} CodeBuddy is ready for ${pc20.bold(folderName)}.`,
4802
- `${pc20.green("\u2713")} Scaffolded ${pc20.cyan(".codebuddy/policies.yaml")} for project-specific guardrails.`,
4803
- `${pc20.green("\u2713")} ${graphifyMessage}`,
4804
- `${pc20.green("\u2713")} Added agent workflow instructions to ${pc20.cyan("CLAUDE.md")} and ${pc20.cyan("AGENTS.md")}.`,
5372
+ `${pc22.green("\u2713")} CodeBuddy is ready for ${pc22.bold(folderName)}.`,
5373
+ `${pc22.green("\u2713")} Scaffolded ${pc22.cyan(".codebuddy/policies.yaml")} for project-specific guardrails.`,
5374
+ `${pc22.green("\u2713")} ${graphifyMessage}`,
5375
+ `${pc22.green("\u2713")} Added agent workflow instructions to ${pc22.cyan("CLAUDE.md")} and ${pc22.cyan("AGENTS.md")}.`,
4805
5376
  ``,
4806
5377
  `Next:`,
4807
5378
  ` 1. Restart Claude Desktop and Codex.`,
4808
- ` 2. Ask Claude or Codex to use the ${pc20.cyan("context_pack")} tool for each coding task.`,
5379
+ ` 2. Ask Claude or Codex to use the ${pc22.cyan("context_pack")} tool for each coding task.`,
4809
5380
  ` 3. Indexing, Graphify, Markdown memory import, and embeddings now update automatically while the MCP server is running.`,
4810
- ` 4. Run ${pc20.cyan("codebuddy doctor")} or ${pc20.cyan("codebuddy db doctor")} if you want a full health check.`,
5381
+ ` 4. Run ${pc22.cyan("codebuddy doctor")} or ${pc22.cyan("codebuddy db doctor")} if you want a full health check.`,
4811
5382
  ``,
4812
5383
  `Manage:`,
4813
- ` codebuddy claude list ${pc20.dim("# see every project wired up")}`,
4814
- ` codebuddy codex list ${pc20.dim("# see Codex entries")}`,
4815
- ` codebuddy db test ${pc20.dim("# quick Postgres connectivity check")}`,
5384
+ ` codebuddy claude list ${pc22.dim("# see every project wired up")}`,
5385
+ ` codebuddy codex list ${pc22.dim("# see Codex entries")}`,
5386
+ ` codebuddy db test ${pc22.dim("# quick Postgres connectivity check")}`,
4816
5387
  ` codebuddy claude remove codebuddy-${namespace}`,
4817
5388
  ` codebuddy codex remove codebuddy-${namespace}`,
4818
- ` codebuddy postgres down ${pc20.dim("# stop the DB (data preserved)")}`
5389
+ ` codebuddy postgres down ${pc22.dim("# stop the DB (data preserved)")}`
4819
5390
  ].join("\n"),
4820
5391
  "Done"
4821
5392
  );
@@ -4826,7 +5397,7 @@ Namespace: ${pc20.bold(namespace)}`,
4826
5397
 
4827
5398
  // src/cli/commands/worklog.ts
4828
5399
  import { relative as relative6 } from "path";
4829
- import pc21 from "picocolors";
5400
+ import pc23 from "picocolors";
4830
5401
  function registerWorklogCommands(program, runSafely2) {
4831
5402
  const worklog = program.command("worklog").description("Inspect the cross-chat worklog of completed turns.");
4832
5403
  worklog.command("list", { isDefault: true }).option("--limit <n>", "Maximum entries to show.", "20").option("--json", "Print machine-readable JSON.").description("List worklog entries, newest first.").action(async (opts) => {
@@ -4837,7 +5408,7 @@ function registerWorklogCommands(program, runSafely2) {
4837
5408
  return;
4838
5409
  }
4839
5410
  if (entries.length === 0) {
4840
- console.log(pc21.dim("no worklog entries yet"));
5411
+ console.log(pc23.dim("no worklog entries yet"));
4841
5412
  return;
4842
5413
  }
4843
5414
  for (const entry of entries) console.log(renderRow2(entry));
@@ -4851,36 +5422,36 @@ function registerWorklogCommands(program, runSafely2) {
4851
5422
  worklog.command("latest").description("Show the most recent worklog entry.").action(async () => {
4852
5423
  await runSafely2(async () => {
4853
5424
  const [entry] = await new WorklogStore().list();
4854
- console.log(entry ? renderEntry(entry) : pc21.dim("no worklog entries yet"));
5425
+ console.log(entry ? renderEntry(entry) : pc23.dim("no worklog entries yet"));
4855
5426
  });
4856
5427
  });
4857
5428
  }
4858
5429
  function renderRow2(entry) {
4859
5430
  const summary = entry.summary.replace(/\s+/g, " ");
4860
5431
  const short2 = summary.length <= 70 ? summary : `${summary.slice(0, 69)}\u2026`;
4861
- return `${pc21.dim(entry.createdAt.slice(0, 16))} ${pc21.dim(entry.id)} ${short2} ${pc21.dim(`(${entry.files.length} file(s))`)}`;
5432
+ return `${pc23.dim(entry.createdAt.slice(0, 16))} ${pc23.dim(entry.id)} ${short2} ${pc23.dim(`(${entry.files.length} file(s))`)}`;
4862
5433
  }
4863
5434
  function renderEntry(entry) {
4864
- const lines = [`${pc21.bold(entry.id)} ${pc21.dim(entry.createdAt)} ${pc21.dim(entry.namespace)}`];
4865
- if (entry.sourceSessionId) lines.push(`${pc21.bold("session:")} ${entry.sourceSessionId}`);
4866
- if (entry.sourcePlanId) lines.push(`${pc21.bold("plan:")} ${entry.sourcePlanId}`);
4867
- if (entry.commitSha) lines.push(`${pc21.bold("commit:")} ${entry.commitSha}`);
4868
- if (entry.files.length > 0) lines.push(`${pc21.bold("files:")} ${entry.files.join(", ")}`);
5435
+ const lines = [`${pc23.bold(entry.id)} ${pc23.dim(entry.createdAt)} ${pc23.dim(entry.namespace)}`];
5436
+ if (entry.sourceSessionId) lines.push(`${pc23.bold("session:")} ${entry.sourceSessionId}`);
5437
+ if (entry.sourcePlanId) lines.push(`${pc23.bold("plan:")} ${entry.sourcePlanId}`);
5438
+ if (entry.commitSha) lines.push(`${pc23.bold("commit:")} ${entry.commitSha}`);
5439
+ if (entry.files.length > 0) lines.push(`${pc23.bold("files:")} ${entry.files.join(", ")}`);
4869
5440
  if (entry.verification.length > 0) {
4870
- lines.push(`${pc21.bold("verification:")}`, ...entry.verification.map((line) => ` - ${line}`));
5441
+ lines.push(`${pc23.bold("verification:")}`, ...entry.verification.map((line) => ` - ${line}`));
4871
5442
  }
4872
5443
  if (entry.savedMemory.length > 0) {
4873
- lines.push(`${pc21.bold("saved memory:")} ${entry.savedMemory.join(", ")}`);
5444
+ lines.push(`${pc23.bold("saved memory:")} ${entry.savedMemory.join(", ")}`);
4874
5445
  }
4875
5446
  if (entry.queuedForReview.length > 0) {
4876
- lines.push(`${pc21.bold("queued for review:")} ${entry.queuedForReview.join(", ")}`);
5447
+ lines.push(`${pc23.bold("queued for review:")} ${entry.queuedForReview.join(", ")}`);
4877
5448
  }
4878
- lines.push("", entry.summary, "", pc21.dim(relative6(process.cwd(), entry.path)));
5449
+ lines.push("", entry.summary, "", pc23.dim(relative6(process.cwd(), entry.path)));
4879
5450
  return lines.join("\n");
4880
5451
  }
4881
5452
 
4882
5453
  // src/cli/commands/zvec.ts
4883
- import pc22 from "picocolors";
5454
+ import pc24 from "picocolors";
4884
5455
  function registerZvecCommands(program, runSafely2) {
4885
5456
  const zvec = program.command("zvec").description("Optional zvec-grep semantic file discovery for the context tools.");
4886
5457
  zvec.command("status", { isDefault: true }).option("--json", "Print machine-readable JSON.").description("Show whether zvec-grep is enabled, installed, supported and indexed.").action(async (opts) => {
@@ -4890,22 +5461,22 @@ function registerZvecCommands(program, runSafely2) {
4890
5461
  console.log(JSON.stringify(report, null, 2));
4891
5462
  return;
4892
5463
  }
4893
- const mark = (ok2) => ok2 ? pc22.green("yes") : pc22.yellow("no");
5464
+ const mark = (ok2) => ok2 ? pc24.green("yes") : pc24.yellow("no");
4894
5465
  console.log(`enabled: ${mark(report.enabled)}`);
4895
5466
  console.log(`binary: ${report.binary}`);
4896
5467
  console.log(
4897
- `version: ${report.version ?? pc22.yellow("not found")}${report.version && !report.supported ? pc22.yellow(" (unsupported; need 0.2.x)") : ""}`
5468
+ `version: ${report.version ?? pc24.yellow("not found")}${report.version && !report.supported ? pc24.yellow(" (unsupported; need 0.2.x)") : ""}`
4898
5469
  );
4899
5470
  console.log(`index ready: ${mark(report.indexReady)}`);
4900
5471
  console.log(
4901
- `zg server: ${report.server} ${pc22.dim("(running = faster queries; optional)")}`
5472
+ `zg server: ${report.server} ${pc24.dim("(running = faster queries; optional)")}`
4902
5473
  );
4903
5474
  console.log(`status: ${report.reason}`);
4904
- if (!report.version) console.log(pc22.dim("install: npm install -g @zvec/zvec-grep"));
5475
+ if (!report.version) console.log(pc24.dim("install: npm install -g @zvec/zvec-grep"));
4905
5476
  else if (report.supported && !report.indexReady)
4906
- console.log(pc22.dim("next: codebuddy zvec index"));
5477
+ console.log(pc24.dim("next: codebuddy zvec index"));
4907
5478
  else if (report.indexReady && !report.enabled)
4908
- console.log(pc22.dim("next: codebuddy zvec enable"));
5479
+ console.log(pc24.dim("next: codebuddy zvec enable"));
4909
5480
  });
4910
5481
  });
4911
5482
  zvec.command("index").option("--model <model>", "Embedding model for a new index.", DEFAULT_ZVEC_MODEL).option("--rebuild", "Rebuild the index from scratch (e.g. after changing model).").description("Build or refresh the zg index for this repository (explicit; never automatic).").action(async (opts) => {
@@ -4915,21 +5486,21 @@ function registerZvecCommands(program, runSafely2) {
4915
5486
  ...opts.rebuild ? { rebuild: true } : {}
4916
5487
  });
4917
5488
  if (code !== 0) throw new Error(`zg index exited with code ${code}`);
4918
- console.log(pc22.dim("next: codebuddy zvec enable (.zvec-grep/ ignores itself in git)"));
5489
+ console.log(pc24.dim("next: codebuddy zvec enable (.zvec-grep/ ignores itself in git)"));
4919
5490
  });
4920
5491
  });
4921
5492
  zvec.command("enable").description("Use zvec-grep candidates in context_pack / context_before_edit.").action(async () => {
4922
5493
  await runSafely2(async () => {
4923
5494
  await setZvecEnabled(true);
4924
5495
  console.log(
4925
- `${pc22.green("enabled")} zvec-grep retrieval (falls back to keywords if unavailable)`
5496
+ `${pc24.green("enabled")} zvec-grep retrieval (falls back to keywords if unavailable)`
4926
5497
  );
4927
5498
  });
4928
5499
  });
4929
5500
  zvec.command("disable").description("Stop using zvec-grep; keyword ranking only.").action(async () => {
4930
5501
  await runSafely2(async () => {
4931
5502
  await setZvecEnabled(false);
4932
- console.log(`${pc22.yellow("disabled")} zvec-grep retrieval`);
5503
+ console.log(`${pc24.yellow("disabled")} zvec-grep retrieval`);
4933
5504
  });
4934
5505
  });
4935
5506
  }
@@ -4950,7 +5521,7 @@ function createCli() {
4950
5521
  ).argument("[namespace]", "Override the namespace (defaults to current folder name).").option("--postgres-url <url>", "Postgres URL to use for this project.").option("--skip-claude", "Do not register with Claude Desktop.").option("--skip-codex", "Do not register with Codex.").option("--graphify", "Enable Graphify setup without prompting.").option("--no-graphify", "Skip Graphify setup without prompting.").option(
4951
5522
  "--docker",
4952
5523
  "Start CodeBuddy's bundled Postgres container when the default DB is unavailable."
4953
- ).option("--no-docker", "Do not offer to start CodeBuddy's bundled Postgres container.").option("-y, --yes", "Never prompt: use flags and defaults (automatic without a TTY).").option("--non-interactive", "Same as --yes.").action(
5524
+ ).option("--no-docker", "Do not offer to start CodeBuddy's bundled Postgres container.").option("--no-local-llm", "Skip installing the local LLM (Qwen3-0.6B, ~400 MB).").option("-y, --yes", "Never prompt: use flags and defaults (automatic without a TTY).").option("--non-interactive", "Same as --yes.").action(
4954
5525
  async (namespace, opts) => {
4955
5526
  await runSafely(async () => {
4956
5527
  await runUseCommand({
@@ -4960,6 +5531,7 @@ function createCli() {
4960
5531
  ...opts.skipCodex !== void 0 ? { skipCodex: opts.skipCodex } : {},
4961
5532
  ...opts.graphify !== void 0 ? { graphify: opts.graphify } : {},
4962
5533
  ...opts.docker !== void 0 ? { useDocker: opts.docker } : {},
5534
+ ...opts.localLlm !== void 0 ? { localLlm: opts.localLlm } : {},
4963
5535
  ...opts.yes || opts.nonInteractive ? { interactive: false } : {}
4964
5536
  });
4965
5537
  });
@@ -5199,7 +5771,47 @@ function createCli() {
5199
5771
  });
5200
5772
  program.command("stats").description("Show CodeBuddy storage and usage stats.").action(async () => {
5201
5773
  await withRuntime(async (runtime) => {
5202
- console.log(JSON.stringify(await runtime.repository.getStats(), null, 2));
5774
+ console.log(
5775
+ JSON.stringify(
5776
+ { ...await runtime.repository.getStats(), ...await indexModelStats(process.cwd()) },
5777
+ null,
5778
+ 2
5779
+ )
5780
+ );
5781
+ });
5782
+ });
5783
+ program.command("storage").option("--json", "Print machine-readable JSON.").description(
5784
+ "Memory storage health: Markdown/database drift, backup coverage, embedding backlog and spaces, capture lag."
5785
+ ).action(async (opts) => {
5786
+ await runSafely(async () => {
5787
+ const root = process.cwd();
5788
+ const capture2 = {
5789
+ stagedNotCaptured: await new HookStagingStore(root).pending(),
5790
+ journalBehind: (await journalLag(root)).filter((entry) => entry.behindBytes !== 0)
5791
+ };
5792
+ let database;
5793
+ try {
5794
+ const runtime = await createRuntime(await loadRuntimeConfig());
5795
+ try {
5796
+ database = await runtime.memory.storageReport();
5797
+ } finally {
5798
+ await runtime.close();
5799
+ }
5800
+ } catch (error) {
5801
+ database = { unavailable: error.message };
5802
+ }
5803
+ const index = await indexModelStats(root);
5804
+ if (opts.json) {
5805
+ console.log(JSON.stringify({ database, capture: capture2, ...index }, null, 2));
5806
+ return;
5807
+ }
5808
+ printStorage(database, capture2);
5809
+ console.log(
5810
+ `intent cards: ${index.cards.carded} / ${index.cards.eligible} source files; index facts: ${index.indexFacts}`
5811
+ );
5812
+ if ("unavailable" in database || database.markdownOnly.length + database.dbOnly.length > 0) {
5813
+ process.exitCode = 1;
5814
+ }
5203
5815
  });
5204
5816
  });
5205
5817
  program.command("doctor").description("Check CodeBuddy environment health.").option("--skip-model-check", "Do not call Hugging Face models.").action(async (opts) => {
@@ -5228,6 +5840,8 @@ function createCli() {
5228
5840
  registerLearnCommand(program, runSafely);
5229
5841
  registerMemoryCommands(program, runSafely);
5230
5842
  registerWorklogCommands(program, runSafely);
5843
+ registerJournalCommands(program, runSafely);
5844
+ registerOutputCommands(program, runSafely);
5231
5845
  registerZvecCommands(program, runSafely);
5232
5846
  registerLlmCommands(program, runSafely);
5233
5847
  registerSuggestCommand(program, runSafely);
@@ -5255,7 +5869,20 @@ var HELP_GROUPS = [
5255
5869
  ["Get started:", ["use", "init", "doctor", "hooks", "clients", "models", "dashboard"]],
5256
5870
  [
5257
5871
  "Daily work:",
5258
- ["context", "plan", "change", "risk", "suggest", "memory", "worklog", "map", "symbols"]
5872
+ [
5873
+ "context",
5874
+ "plan",
5875
+ "change",
5876
+ "risk",
5877
+ "suggest",
5878
+ "memory",
5879
+ "worklog",
5880
+ "journal",
5881
+ "run",
5882
+ "output",
5883
+ "map",
5884
+ "symbols"
5885
+ ]
5259
5886
  ],
5260
5887
  ["Indexing and search:", ["index", "watch", "reindex", "zvec", "graphify", "llm"]],
5261
5888
  [
@@ -5265,6 +5892,7 @@ var HELP_GROUPS = [
5265
5892
  "db",
5266
5893
  "migrate",
5267
5894
  "stats",
5895
+ "storage",
5268
5896
  "inspect",
5269
5897
  "namespaces",
5270
5898
  "list-facts",
@@ -5301,6 +5929,14 @@ Quick start:
5301
5929
  see exactly what an agent would be given`
5302
5930
  );
5303
5931
  }
5932
+ async function indexModelStats(root) {
5933
+ const { cardProgress } = await import("./summaries-QXWFFDUZ.js");
5934
+ const { listIndexFacts: listIndexFacts2 } = await import("./index-facts-4MTEHKMN.js");
5935
+ return {
5936
+ cards: await cardProgress(root).catch(() => ({ carded: 0, eligible: 0 })),
5937
+ indexFacts: (await listIndexFacts2(root).catch(() => [])).length
5938
+ };
5939
+ }
5304
5940
  async function withRuntime(fn) {
5305
5941
  await runSafely(async () => {
5306
5942
  const runtime = await createRuntime(await loadRuntimeConfig());
@@ -5315,7 +5951,7 @@ async function runSafely(fn) {
5315
5951
  try {
5316
5952
  await fn();
5317
5953
  } catch (error) {
5318
- console.error(pc23.red(String(redactSecrets(error.message))));
5954
+ console.error(pc25.red(String(redactSecrets(error.message))));
5319
5955
  process.exitCode = 1;
5320
5956
  }
5321
5957
  }
@@ -5329,9 +5965,9 @@ function parseDuration(value) {
5329
5965
  return amount * 60 * 1e3;
5330
5966
  }
5331
5967
  function printDoctor(report) {
5332
- const ok2 = pc23.green("ok");
5333
- const bad = pc23.red("fail");
5334
- const warn2 = pc23.yellow("warn");
5968
+ const ok2 = pc25.green("ok");
5969
+ const bad = pc25.red("fail");
5970
+ const warn2 = pc25.yellow("warn");
5335
5971
  console.log(`scaffold root: ${report.scaffold.codebuddyDir.ok ? ok2 : bad}`);
5336
5972
  if (report.scaffold.codebuddyDir.warning)
5337
5973
  console.log(` ${report.scaffold.codebuddyDir.warning}`);
@@ -5357,7 +5993,7 @@ function printDoctor(report) {
5357
5993
  );
5358
5994
  console.log(`estimated daily cap remaining: ${report.usage.estimatedDailyCapRemaining}`);
5359
5995
  if (report.usage.warning80Percent)
5360
- console.log(pc23.yellow("usage warning: daily cap is over 80% used"));
5996
+ console.log(pc25.yellow("usage warning: daily cap is over 80% used"));
5361
5997
  console.log(
5362
5998
  `config permissions: ${report.config.exists ? report.config.mode : "missing"} ${report.config.ok ? ok2 : warn2}`
5363
5999
  );
@@ -5367,9 +6003,9 @@ function printDoctor(report) {
5367
6003
  }
5368
6004
  }
5369
6005
  function printDbDoctor(report) {
5370
- const ok2 = pc23.green("ok");
5371
- const bad = pc23.red("fail");
5372
- const warn2 = pc23.yellow("warn");
6006
+ const ok2 = pc25.green("ok");
6007
+ const bad = pc25.red("fail");
6008
+ const warn2 = pc25.yellow("warn");
5373
6009
  console.log(`DB connectivity: ${report.db.ok ? ok2 : bad}`);
5374
6010
  if (report.db.postgresUrlSource) console.log(` url source: ${report.db.postgresUrlSource}`);
5375
6011
  if (report.db.error) console.log(` ${report.db.error}`);
@@ -5382,7 +6018,42 @@ function printDbDoctor(report) {
5382
6018
  }
5383
6019
  if (report.namespace.warning) console.log(` ${report.namespace.warning}`);
5384
6020
  }
6021
+ function printStorage(database, capture2) {
6022
+ if ("unavailable" in database) {
6023
+ console.log(pc25.yellow(`database: unavailable (${database.unavailable})`));
6024
+ } else {
6025
+ const drift = database.markdownOnly.length + database.dbOnly.length;
6026
+ console.log(
6027
+ `facts: ${database.markdownFacts} Markdown, ${database.databaseFacts} database${database.invalidFiles > 0 ? `, ${database.invalidFiles} unreadable file(s)` : ""}`
6028
+ );
6029
+ console.log(
6030
+ drift === 0 ? pc25.green("sync: Markdown and database agree") : pc25.yellow(
6031
+ `sync: ${database.markdownOnly.length} file(s) not in the database, ${database.dbOnly.length} row(s) without a file (run \`codebuddy reindex\`)`
6032
+ )
6033
+ );
6034
+ if (database.unbacked.length > 0) {
6035
+ console.log(
6036
+ pc25.yellow(
6037
+ `backup: ${database.unbacked.length} database-only fact(s) have no Markdown copy`
6038
+ )
6039
+ );
6040
+ }
6041
+ const e = database.embeddings;
6042
+ console.log(
6043
+ `embeddings: ${e.ready} ready, ${e.pending} pending, ${e.processing} processing, ${e.failed} failed${database.oldestPendingAt ? ` (oldest pending ${database.oldestPendingAt})` : ""}`
6044
+ );
6045
+ for (const space of database.spaces) {
6046
+ console.log(` space ${space.model} ${space.space ?? "(legacy)"}: ${space.count}`);
6047
+ }
6048
+ if (database.spaces.length > 1) {
6049
+ console.log(pc25.yellow(" more than one embedding space: run `codebuddy reindex --respace`"));
6050
+ }
6051
+ }
6052
+ console.log(
6053
+ capture2.stagedNotCaptured.length === 0 && capture2.journalBehind.length === 0 ? pc25.green("capture: nothing pending") : `capture: ${capture2.stagedNotCaptured.length} session(s) with uncaptured edits, ${capture2.journalBehind.length} journal(s) behind their transcript`
6054
+ );
6055
+ }
5385
6056
  export {
5386
6057
  createCli
5387
6058
  };
5388
- //# sourceMappingURL=main-YJKSYFPT.js.map
6059
+ //# sourceMappingURL=main-RLMKQZW4.js.map