@phuetz/code-buddy 2.0.0 → 2.1.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 (403) hide show
  1. package/README.fr.md +56 -0
  2. package/README.md +73 -16
  3. package/codebuddy-runtime.json +5 -5
  4. package/dist/agent/codebuddy-agent.d.ts +18 -0
  5. package/dist/agent/codebuddy-agent.js +45 -8
  6. package/dist/agent/execution/agent-executor.d.ts +0 -9
  7. package/dist/agent/execution/agent-executor.js +165 -12
  8. package/dist/agent/execution/context-pipeline.d.ts +9 -0
  9. package/dist/agent/execution/context-pipeline.js +64 -2
  10. package/dist/agent/execution/tool-loop-guard.d.ts +76 -0
  11. package/dist/agent/execution/tool-loop-guard.js +165 -0
  12. package/dist/agent/execution/tool-selection-strategy.js +19 -2
  13. package/dist/agent/facades/session-facade.d.ts +0 -6
  14. package/dist/agent/facades/session-facade.js +0 -45
  15. package/dist/agent/hermes-claw-migrate.d.ts +0 -21
  16. package/dist/agent/hermes-claw-migrate.js +9 -4
  17. package/dist/agent/hermes-protocol-gateways.js +8 -1
  18. package/dist/agent/repo-profiler.js +17 -4
  19. package/dist/agent/self-improvement/authored-tool-runtime.js +10 -2
  20. package/dist/agent/self-improvement/evolution/ast-novelty.d.ts +5 -0
  21. package/dist/agent/self-improvement/evolution/ast-novelty.js +58 -23
  22. package/dist/agent/self-improvement/evolution/code-variant-store.d.ts +3 -0
  23. package/dist/agent/self-improvement/evolution/evolution-engine.js +2 -0
  24. package/dist/agent/self-improvement/evolution/protected-paths.js +6 -0
  25. package/dist/agent/self-improvement/evolution/variant-fitness.d.ts +13 -2
  26. package/dist/agent/self-improvement/evolution/variant-fitness.js +184 -58
  27. package/dist/agent/self-improvement/evolution/worktree-scorer.d.ts +2 -0
  28. package/dist/agent/self-improvement/evolution/worktree-scorer.js +15 -0
  29. package/dist/agent/self-improvement/evolutionary-archive.d.ts +2 -1
  30. package/dist/agent/self-improvement/evolutionary-archive.js +34 -5
  31. package/dist/agent/self-improvement/proposal-store.d.ts +1 -0
  32. package/dist/agent/self-improvement/proposal-store.js +79 -17
  33. package/dist/agent/self-improvement/skill-apply-journal.d.ts +44 -0
  34. package/dist/agent/self-improvement/skill-apply-journal.js +178 -0
  35. package/dist/agent/self-improvement/skill-behavior-benchmark.d.ts +3 -0
  36. package/dist/agent/self-improvement/skill-behavior-benchmark.js +19 -0
  37. package/dist/agent/self-improvement/skill-behavior-gate.d.ts +34 -0
  38. package/dist/agent/self-improvement/skill-behavior-gate.js +96 -0
  39. package/dist/agent/self-improvement/skill-engine.d.ts +22 -0
  40. package/dist/agent/self-improvement/skill-engine.js +385 -13
  41. package/dist/agent/self-improvement/skill-gate.d.ts +2 -0
  42. package/dist/agent/self-improvement/skill-gate.js +17 -2
  43. package/dist/agent/self-improvement/skill-mutator.d.ts +6 -1
  44. package/dist/agent/self-improvement/skill-mutator.js +51 -15
  45. package/dist/agent/self-improvement/skill-types.d.ts +2 -1
  46. package/dist/agent/self-improvement/types.d.ts +11 -0
  47. package/dist/agent/tool-handler.d.ts +1 -1
  48. package/dist/agent/tool-handler.js +28 -9
  49. package/dist/auth/profile-manager.js +1 -1
  50. package/dist/channels/companion-channel-turn.d.ts +53 -4
  51. package/dist/channels/companion-channel-turn.js +218 -16
  52. package/dist/channels/slash-parity.d.ts +4 -6
  53. package/dist/channels/slash-parity.js +9 -44
  54. package/dist/cli/session-commands.d.ts +9 -0
  55. package/dist/cli/session-commands.js +45 -5
  56. package/dist/cli/session-picker.d.ts +43 -0
  57. package/dist/cli/session-picker.js +128 -0
  58. package/dist/codebuddy/a2a-call-tool-defs.d.ts +26 -0
  59. package/dist/codebuddy/a2a-call-tool-defs.js +12 -0
  60. package/dist/codebuddy/fleet-tool-defs.d.ts +1 -0
  61. package/dist/codebuddy/fleet-tool-defs.js +24 -0
  62. package/dist/codebuddy/providers/chatgpt-headers.d.ts +18 -0
  63. package/dist/codebuddy/providers/chatgpt-headers.js +37 -0
  64. package/dist/codebuddy/providers/provider-chatgpt-responses.d.ts +2 -1
  65. package/dist/codebuddy/providers/provider-chatgpt-responses.js +10 -23
  66. package/dist/codebuddy/providers/provider-openai-compat.js +1 -1
  67. package/dist/codebuddy/ragchat-tool-defs.d.ts +31 -0
  68. package/dist/codebuddy/ragchat-tool-defs.js +19 -0
  69. package/dist/codebuddy/resource-catalog-tool-defs.d.ts +28 -0
  70. package/dist/codebuddy/resource-catalog-tool-defs.js +18 -0
  71. package/dist/codebuddy/tool-definitions/agent-tools.js +2 -2
  72. package/dist/codebuddy/tool-definitions/code-exec-tools.js +5 -1
  73. package/dist/codebuddy/tool-definitions/cron-tools.js +6 -0
  74. package/dist/codebuddy/tool-definitions/self-describe-tools.js +6 -1
  75. package/dist/codebuddy/tools.d.ts +0 -6
  76. package/dist/codebuddy/tools.js +25 -3
  77. package/dist/commands/cli/daemon-commands.js +3 -1
  78. package/dist/commands/cli/evolve-command.js +21 -5
  79. package/dist/commands/cli/fleet-collaboration-commands.d.ts +2 -0
  80. package/dist/commands/cli/fleet-collaboration-commands.js +57 -0
  81. package/dist/commands/cli/fleet-commands.js +29 -0
  82. package/dist/commands/cli/fleet-mission-commands.d.ts +3 -0
  83. package/dist/commands/cli/fleet-mission-commands.js +72 -0
  84. package/dist/commands/cli/fleet-rooms-commands.d.ts +10 -0
  85. package/dist/commands/cli/fleet-rooms-commands.js +289 -0
  86. package/dist/commands/cli/tools-commands.js +2 -1
  87. package/dist/commands/cli/triage-command.d.ts +8 -0
  88. package/dist/commands/cli/triage-command.js +72 -0
  89. package/dist/commands/cli/utility-commands.js +25 -1
  90. package/dist/commands/client-dispatcher.d.ts +7 -0
  91. package/dist/commands/client-dispatcher.js +73 -21
  92. package/dist/commands/cron-cli/index.d.ts +3 -1
  93. package/dist/commands/cron-cli/index.js +25 -0
  94. package/dist/commands/device-auth.d.ts +10 -0
  95. package/dist/commands/device-auth.js +91 -0
  96. package/dist/commands/enhanced-command-handler.d.ts +9 -0
  97. package/dist/commands/enhanced-command-handler.js +28 -6
  98. package/dist/commands/handlers/agent-handlers.d.ts +1 -1
  99. package/dist/commands/handlers/agent-handlers.js +4 -4
  100. package/dist/commands/handlers/channel-handlers.js +53 -2
  101. package/dist/commands/handlers/export-handlers.d.ts +3 -2
  102. package/dist/commands/handlers/export-handlers.js +55 -28
  103. package/dist/commands/handlers/extra-handlers.js +5 -1
  104. package/dist/commands/handlers/grill-me-handler.js +26 -18
  105. package/dist/commands/handlers/memory-handlers.d.ts +6 -2
  106. package/dist/commands/handlers/memory-handlers.js +35 -15
  107. package/dist/commands/handlers/missing-handlers.d.ts +2 -2
  108. package/dist/commands/handlers/missing-handlers.js +4 -4
  109. package/dist/commands/handlers/resources-handler.d.ts +12 -0
  110. package/dist/commands/handlers/resources-handler.js +52 -0
  111. package/dist/commands/handlers/starter-handlers.d.ts +1 -1
  112. package/dist/commands/handlers/starter-handlers.js +32 -2
  113. package/dist/commands/handlers/switch-handler.d.ts +1 -1
  114. package/dist/commands/handlers/switch-handler.js +15 -0
  115. package/dist/commands/handlers/test-handlers.js +4 -86
  116. package/dist/commands/handlers/ui-handlers.js +11 -5
  117. package/dist/commands/login-chatgpt.d.ts +8 -0
  118. package/dist/commands/login-chatgpt.js +17 -0
  119. package/dist/commands/mcp-import.d.ts +11 -0
  120. package/dist/commands/mcp-import.js +67 -0
  121. package/dist/commands/mcp-invoke.d.ts +2 -0
  122. package/dist/commands/mcp-invoke.js +68 -0
  123. package/dist/commands/mcp.js +4 -0
  124. package/dist/commands/pipeline.d.ts +7 -1
  125. package/dist/commands/pipeline.js +104 -7
  126. package/dist/commands/resources.d.ts +2 -0
  127. package/dist/commands/resources.js +64 -0
  128. package/dist/commands/skills-cli/index.js +35 -12
  129. package/dist/commands/slash/builtin-commands.js +16 -3
  130. package/dist/commands/slash/index.d.ts +3 -1
  131. package/dist/commands/slash/index.js +2 -0
  132. package/dist/commands/slash/surfaces.d.ts +53 -0
  133. package/dist/commands/slash/surfaces.js +177 -0
  134. package/dist/commands/slash/types.d.ts +17 -0
  135. package/dist/commands/try.d.ts +1 -1
  136. package/dist/commands/try.js +1 -1
  137. package/dist/companion/companion-identity.d.ts +43 -0
  138. package/dist/companion/companion-identity.js +136 -0
  139. package/dist/companion/companion-toolset.d.ts +66 -0
  140. package/dist/companion/companion-toolset.js +368 -0
  141. package/dist/companion/companion-turn.d.ts +10 -2
  142. package/dist/companion/companion-turn.js +38 -1
  143. package/dist/config/code-exec-policy.d.ts +25 -0
  144. package/dist/config/code-exec-policy.js +39 -0
  145. package/dist/config/model-tools.d.ts +9 -0
  146. package/dist/config/model-tools.js +4 -0
  147. package/dist/context/context-manager-v2.js +2 -2
  148. package/dist/context/lm-resizer-compressor.js +3 -2
  149. package/dist/context/lm-resizer-diagnostics.d.ts +9 -0
  150. package/dist/context/lm-resizer-diagnostics.js +30 -0
  151. package/dist/context/tool-output-masking.js +3 -4
  152. package/dist/curator/curator.d.ts +6 -0
  153. package/dist/curator/curator.js +53 -3
  154. package/dist/daemon/cron-agent-bridge.d.ts +6 -0
  155. package/dist/daemon/cron-agent-bridge.js +31 -4
  156. package/dist/doctor/index.d.ts +11 -2
  157. package/dist/doctor/index.js +84 -21
  158. package/dist/doctor/integrations.d.ts +179 -0
  159. package/dist/doctor/integrations.js +227 -0
  160. package/dist/doctor/triage.d.ts +108 -0
  161. package/dist/doctor/triage.js +304 -0
  162. package/dist/fleet/collaboration.d.ts +44 -0
  163. package/dist/fleet/collaboration.js +170 -0
  164. package/dist/fleet/fleet-listener.d.ts +1 -0
  165. package/dist/fleet/fleet-listener.js +87 -46
  166. package/dist/fleet/fleet-registry.d.ts +2 -0
  167. package/dist/fleet/resource-catalog.d.ts +245 -0
  168. package/dist/fleet/resource-catalog.js +211 -0
  169. package/dist/fleet/rooms/room-access.d.ts +138 -0
  170. package/dist/fleet/rooms/room-access.js +196 -0
  171. package/dist/fleet/rooms/room-client.d.ts +158 -0
  172. package/dist/fleet/rooms/room-client.js +685 -0
  173. package/dist/fleet/rooms/room-event.d.ts +139 -0
  174. package/dist/fleet/rooms/room-event.js +330 -0
  175. package/dist/fleet/rooms/room-filter.d.ts +48 -0
  176. package/dist/fleet/rooms/room-filter.js +152 -0
  177. package/dist/fleet/rooms/room-hub.d.ts +154 -0
  178. package/dist/fleet/rooms/room-hub.js +363 -0
  179. package/dist/fleet/rooms/room-identity.d.ts +26 -0
  180. package/dist/fleet/rooms/room-identity.js +82 -0
  181. package/dist/fleet/rooms/room-lock.d.ts +34 -0
  182. package/dist/fleet/rooms/room-lock.js +180 -0
  183. package/dist/fleet/rooms/room-observations.d.ts +7 -0
  184. package/dist/fleet/rooms/room-observations.js +61 -0
  185. package/dist/fleet/rooms/room-server.d.ts +28 -0
  186. package/dist/fleet/rooms/room-server.js +75 -0
  187. package/dist/fleet/rooms/room-store.d.ts +119 -0
  188. package/dist/fleet/rooms/room-store.js +448 -0
  189. package/dist/fleet/rooms/room-ws-bridge.d.ts +27 -0
  190. package/dist/fleet/rooms/room-ws-bridge.js +137 -0
  191. package/dist/fleet/sensory-mission-bridge.d.ts +42 -0
  192. package/dist/fleet/sensory-mission-bridge.js +136 -0
  193. package/dist/git/worktree-sessions.js +1 -0
  194. package/dist/harness/contract.d.ts +4 -4
  195. package/dist/harness/fleet-supervisor.d.ts +16 -0
  196. package/dist/harness/fleet-supervisor.js +64 -0
  197. package/dist/harness/index.d.ts +4 -0
  198. package/dist/harness/index.js +4 -0
  199. package/dist/harness/mission-runner.d.ts +3 -0
  200. package/dist/harness/mission-runner.js +53 -0
  201. package/dist/harness/mission-store.d.ts +93 -0
  202. package/dist/harness/mission-store.js +297 -0
  203. package/dist/harness/tool-harness.d.ts +56 -0
  204. package/dist/harness/tool-harness.js +153 -0
  205. package/dist/hooks/use-enhanced-input.js +48 -4
  206. package/dist/hooks/use-input-handler.d.ts +1 -0
  207. package/dist/hooks/use-input-handler.js +82 -22
  208. package/dist/hooks/use-input-history.js +1 -0
  209. package/dist/identity/lisa-introspection.js +5 -2
  210. package/dist/identity/operational-self-model.d.ts +2 -0
  211. package/dist/identity/operational-self-model.js +4 -0
  212. package/dist/index.js +52 -6
  213. package/dist/mcp/client.js +6 -0
  214. package/dist/mcp/config.d.ts +20 -0
  215. package/dist/mcp/config.js +121 -8
  216. package/dist/mcp/import-normalize.d.ts +12 -0
  217. package/dist/mcp/import-normalize.js +114 -0
  218. package/dist/mcp/transports.d.ts +10 -5
  219. package/dist/mcp/transports.js +61 -99
  220. package/dist/mcp/types.d.ts +5 -0
  221. package/dist/media/comfyui-recipe-contract.d.ts +7 -7
  222. package/dist/memory/facts-memory.d.ts +11 -1
  223. package/dist/memory/facts-memory.js +47 -8
  224. package/dist/memory/memory-candidate-queue.d.ts +2 -2
  225. package/dist/memory/memory-candidate-queue.js +6 -6
  226. package/dist/memory/persistent-memory.d.ts +13 -8
  227. package/dist/memory/persistent-memory.js +77 -30
  228. package/dist/nodes/device-node.js +5 -9
  229. package/dist/observability/mobile-supervision-gateway-contract.js +20 -7
  230. package/dist/observability/run-event-writer.d.ts +21 -0
  231. package/dist/observability/run-event-writer.js +81 -0
  232. package/dist/observability/run-store.d.ts +5 -0
  233. package/dist/observability/run-store.js +18 -13
  234. package/dist/observability/run-trajectory.js +1 -1
  235. package/dist/optimization/prompt-cache.d.ts +19 -2
  236. package/dist/optimization/prompt-cache.js +41 -6
  237. package/dist/orchestration/orchestrator.d.ts +21 -0
  238. package/dist/orchestration/orchestrator.js +122 -15
  239. package/dist/persistence/session-content.d.ts +8 -0
  240. package/dist/persistence/session-content.js +76 -0
  241. package/dist/persistence/session-handoff.d.ts +64 -0
  242. package/dist/persistence/session-handoff.js +80 -0
  243. package/dist/persistence/session-history.d.ts +4 -0
  244. package/dist/persistence/session-history.js +38 -0
  245. package/dist/persistence/session-store.d.ts +9 -0
  246. package/dist/persistence/session-store.js +48 -20
  247. package/dist/personas/persona-manager.js +17 -1
  248. package/dist/plugins/code-explorer/CodeExplorerManager.d.ts +10 -4
  249. package/dist/plugins/code-explorer/CodeExplorerManager.js +69 -16
  250. package/dist/plugins/plugin-manager.js +10 -1
  251. package/dist/prompts/prompt-manager.js +11 -4
  252. package/dist/prompts/system-base.js +6 -2
  253. package/dist/protocols/a2a/codebuddy-executor.d.ts +11 -2
  254. package/dist/protocols/a2a/codebuddy-executor.js +9 -8
  255. package/dist/protocols/a2a/index.d.ts +1 -1
  256. package/dist/protocols/a2a/jsonrpc-v1.d.ts +41 -0
  257. package/dist/protocols/a2a/jsonrpc-v1.js +145 -0
  258. package/dist/protocols/a2a/peer-config.d.ts +20 -0
  259. package/dist/protocols/a2a/peer-config.js +55 -0
  260. package/dist/providers/codex-oauth.js +30 -6
  261. package/dist/providers/model-provider-compat.js +2 -1
  262. package/dist/sandbox/os-sandbox.d.ts +1 -1
  263. package/dist/scheduler/cron-scheduler.d.ts +15 -2
  264. package/dist/scheduler/cron-scheduler.js +35 -4
  265. package/dist/scheduler/job-notepad.d.ts +69 -0
  266. package/dist/scheduler/job-notepad.js +339 -0
  267. package/dist/security/audit-logger.d.ts +1 -1
  268. package/dist/security/compute-confinement.d.ts +4 -0
  269. package/dist/security/compute-confinement.js +89 -0
  270. package/dist/security/session-encryption.d.ts +4 -0
  271. package/dist/security/session-encryption.js +31 -0
  272. package/dist/sensory/voice-loop.d.ts +17 -0
  273. package/dist/sensory/voice-loop.js +72 -15
  274. package/dist/server/auth/device-session-context.d.ts +9 -0
  275. package/dist/server/auth/device-session-context.js +13 -0
  276. package/dist/server/auth/device-store.d.ts +89 -0
  277. package/dist/server/auth/device-store.js +261 -0
  278. package/dist/server/auth/device-token.d.ts +3 -0
  279. package/dist/server/auth/device-token.js +6 -0
  280. package/dist/server/auth/jwt.js +4 -0
  281. package/dist/server/index.d.ts +0 -10
  282. package/dist/server/index.js +28 -0
  283. package/dist/server/middleware/auth.js +3 -2
  284. package/dist/server/routes/a2a-jsonrpc.d.ts +9 -0
  285. package/dist/server/routes/a2a-jsonrpc.js +66 -0
  286. package/dist/server/routes/a2a-protocol.d.ts +12 -0
  287. package/dist/server/routes/a2a-protocol.js +1 -1
  288. package/dist/server/routes/device-auth.d.ts +4 -0
  289. package/dist/server/routes/device-auth.js +40 -0
  290. package/dist/server/types.d.ts +4 -0
  291. package/dist/server/websocket/handler.d.ts +13 -0
  292. package/dist/server/websocket/handler.js +102 -17
  293. package/dist/services/prompt-builder.d.ts +2 -0
  294. package/dist/services/prompt-builder.js +13 -5
  295. package/dist/services/runtime-settings-context.d.ts +73 -0
  296. package/dist/services/runtime-settings-context.js +69 -0
  297. package/dist/skills/executor.js +2 -0
  298. package/dist/skills/hub.js +16 -14
  299. package/dist/skills/local-inventory.d.ts +21 -0
  300. package/dist/skills/local-inventory.js +72 -0
  301. package/dist/skills/skill-loader.js +2 -2
  302. package/dist/skills/skill-usage-store.d.ts +49 -0
  303. package/dist/skills/skill-usage-store.js +121 -0
  304. package/dist/testing/ai-integration-tests.js +8 -5
  305. package/dist/themes/theme-manager.d.ts +12 -0
  306. package/dist/themes/theme-manager.js +29 -0
  307. package/dist/themes/theme-schema.d.ts +24 -24
  308. package/dist/tools/a2a-call-tool.d.ts +34 -0
  309. package/dist/tools/a2a-call-tool.js +75 -0
  310. package/dist/tools/bash/streaming-executor.d.ts +1 -0
  311. package/dist/tools/bash/streaming-executor.js +78 -90
  312. package/dist/tools/code-exec-preflight-runner.d.ts +41 -0
  313. package/dist/tools/code-exec-preflight-runner.js +254 -0
  314. package/dist/tools/code-exec-preflight.d.ts +44 -0
  315. package/dist/tools/code-exec-preflight.js +501 -0
  316. package/dist/tools/code-exec-tool.d.ts +48 -1
  317. package/dist/tools/code-exec-tool.js +191 -47
  318. package/dist/tools/comment-watcher.js +29 -12
  319. package/dist/tools/core-code-inspection.d.ts +56 -0
  320. package/dist/tools/core-code-inspection.js +86 -0
  321. package/dist/tools/create-skill-tool.d.ts +2 -3
  322. package/dist/tools/create-skill-tool.js +16 -75
  323. package/dist/tools/cronjob-tool.d.ts +1 -0
  324. package/dist/tools/cronjob-tool.js +1 -0
  325. package/dist/tools/env-doctor-tool.d.ts +2 -0
  326. package/dist/tools/env-doctor-tool.js +4 -3
  327. package/dist/tools/execute-code-rpc-invoker.d.ts +1 -1
  328. package/dist/tools/execute-code-rpc-invoker.js +35 -19
  329. package/dist/tools/execute-code-runner.d.ts +2 -0
  330. package/dist/tools/execute-code-runner.js +52 -17
  331. package/dist/tools/fleet-room-tool.d.ts +104 -0
  332. package/dist/tools/fleet-room-tool.js +124 -0
  333. package/dist/tools/integration-tool-hints.d.ts +4 -0
  334. package/dist/tools/integration-tool-hints.js +19 -0
  335. package/dist/tools/list-peers-tool.d.ts +3 -0
  336. package/dist/tools/list-peers-tool.js +11 -4
  337. package/dist/tools/media-generation-tool.d.ts +16 -1
  338. package/dist/tools/media-generation-tool.js +341 -9
  339. package/dist/tools/merge-conflict-tool.js +53 -25
  340. package/dist/tools/metadata.d.ts +1 -5
  341. package/dist/tools/metadata.js +21 -28
  342. package/dist/tools/ragchat-tool.d.ts +39 -0
  343. package/dist/tools/ragchat-tool.js +105 -0
  344. package/dist/tools/registry/fleet-tools.d.ts +9 -0
  345. package/dist/tools/registry/fleet-tools.js +22 -1
  346. package/dist/tools/registry/knowledge-tools.d.ts +2 -2
  347. package/dist/tools/registry/knowledge-tools.js +2 -2
  348. package/dist/tools/registry/lessons-tools.d.ts +7 -0
  349. package/dist/tools/registry/lessons-tools.js +43 -3
  350. package/dist/tools/registry/memory-tools.d.ts +1 -1
  351. package/dist/tools/registry/memory-tools.js +8 -8
  352. package/dist/tools/registry/research-tools.d.ts +1 -0
  353. package/dist/tools/registry/research-tools.js +4 -13
  354. package/dist/tools/registry/self-describe-tools.d.ts +1 -1
  355. package/dist/tools/registry/self-describe-tools.js +43 -30
  356. package/dist/tools/resource-catalog-tool.d.ts +38 -0
  357. package/dist/tools/resource-catalog-tool.js +46 -0
  358. package/dist/tools/skills-inspection-tool.js +30 -1
  359. package/dist/tools/stream-tool-output.d.ts +3 -0
  360. package/dist/tools/stream-tool-output.js +33 -0
  361. package/dist/tools/tool-call-scheduler.d.ts +10 -0
  362. package/dist/tools/tool-call-scheduler.js +58 -0
  363. package/dist/tools/tool-effect.d.ts +5 -0
  364. package/dist/tools/tool-effect.js +26 -0
  365. package/dist/tools/tool-search.d.ts +12 -11
  366. package/dist/tools/tool-search.js +67 -26
  367. package/dist/tools/types.d.ts +6 -0
  368. package/dist/ui/components/ChatHistory.d.ts +1 -1
  369. package/dist/ui/components/ChatHistory.js +2 -2
  370. package/dist/ui/components/ChatInput.d.ts +2 -1
  371. package/dist/ui/components/ChatInput.js +42 -88
  372. package/dist/ui/components/ChatInterface.d.ts +4 -2
  373. package/dist/ui/components/ChatInterface.js +27 -63
  374. package/dist/ui/components/CommandSuggestions.d.ts +0 -1
  375. package/dist/ui/components/CommandSuggestions.js +4 -6
  376. package/dist/ui/components/FileAutocomplete.js +1 -1
  377. package/dist/ui/components/KeyboardHelp.js +8 -6
  378. package/dist/ui/components/ModelSelection.js +6 -5
  379. package/dist/ui/components/StatusBar.d.ts +2 -1
  380. package/dist/ui/components/StatusBar.js +8 -7
  381. package/dist/ui/context/theme-context.js +3 -2
  382. package/dist/ui/index.d.ts +1 -1
  383. package/dist/ui/index.js +1 -1
  384. package/dist/ui/utils/markdown-renderer.js +4 -13
  385. package/dist/utils/atomic-write.d.ts +22 -0
  386. package/dist/utils/atomic-write.js +29 -0
  387. package/dist/utils/bounded-output.d.ts +16 -0
  388. package/dist/utils/bounded-output.js +63 -0
  389. package/dist/utils/device-store-file.d.ts +4 -0
  390. package/dist/utils/device-store-file.js +40 -0
  391. package/dist/utils/export-manager.d.ts +9 -0
  392. package/dist/utils/export-manager.js +19 -0
  393. package/dist/utils/first-use-hints.d.ts +25 -0
  394. package/dist/utils/first-use-hints.js +58 -0
  395. package/dist/utils/history-manager.js +1 -1
  396. package/dist/utils/update-notifier.js +16 -18
  397. package/dist/wizard/environment-detection.d.ts +5 -1
  398. package/dist/wizard/environment-detection.js +49 -7
  399. package/dist/wizard/onboarding.d.ts +5 -1
  400. package/dist/wizard/onboarding.js +90 -30
  401. package/dist/wizard/provider-onboarding.d.ts +2 -0
  402. package/dist/wizard/provider-onboarding.js +55 -17
  403. package/package.json +7 -3
@@ -29,8 +29,14 @@ export const PROTECTED_GLOBS = [
29
29
  'src/agent/self-improvement/paired-runner.ts',
30
30
  'src/agent/self-improvement/tool-proposer.ts',
31
31
  'src/agent/self-improvement/self-knowledge.ts',
32
+ // Application receipts and replay must not be rewritten by a scored candidate.
33
+ 'src/agent/self-improvement/skill-apply-journal.ts',
34
+ 'src/agent/self-improvement/skill-engine.ts',
35
+ 'src/agent/self-improvement/evolutionary-archive.ts',
32
36
  // Security scanners the skill firewall relies on
33
37
  'src/security/skill-scanner.ts',
38
+ 'src/security/compute-confinement.ts',
39
+ 'src/agent/self-improvement/authored-tool-runtime.ts',
34
40
  'src/security/dangerous-patterns.ts',
35
41
  // Tests that verify the gates / self-improvement (used by the fitness unit-tests component)
36
42
  'tests/agent/self-improvement/**',
@@ -19,6 +19,8 @@ export interface FitnessContext {
19
19
  checkoutDir: string;
20
20
  /** Per-process timeout for a component's subprocess. */
21
21
  timeoutMs?: number;
22
+ /** Cancel the subprocess and its process group. */
23
+ signal?: AbortSignal;
22
24
  /** Env for subprocesses. Callers may pass a SCRUBBED env (no host secrets) for untrusted variants. */
23
25
  env?: NodeJS.ProcessEnv;
24
26
  }
@@ -46,7 +48,14 @@ export interface FitnessReport {
46
48
  /** Component names that regressed vs a baseline (score dropped or pass→fail). */
47
49
  regressions: string[];
48
50
  }
49
- /** Run a subprocess in the checkout, capped output + timeout. Never throws (resolves with code). */
51
+ /** Execute checkout-owned JavaScript CLIs directly, preserving argv on every OS. */
52
+ export declare function runCheckoutCli(modulePath: string, args: string[], ctx: FitnessContext): Promise<{
53
+ code: number;
54
+ stdout: string;
55
+ stderr: string;
56
+ timedOut: boolean;
57
+ }>;
58
+ /** Run a bounded subprocess; terminate its process group on timeout. */
50
59
  export declare function runProc(cmd: string, args: string[], ctx: FitnessContext): Promise<{
51
60
  code: number;
52
61
  stdout: string;
@@ -79,8 +88,10 @@ export declare function unitTestsComponent(patterns: string[], weight?: number):
79
88
  * STOCHASTIC (LLM calls) — opt-in. Requires the checkout to be built (dist/index.js).
80
89
  */
81
90
  export declare function evalTasksComponent(tasks?: string[], weight?: number): FitnessComponent;
91
+ /** Protected, offline task objective. Guard checks can be green while task success improves. */
92
+ export declare function harnessTasksComponent(weight?: number): FitnessComponent;
82
93
  /** Default deterministic set → a fast, reproducible baseline (no LLM, no build). */
83
- export declare function defaultDeterministicComponents(): FitnessComponent[];
94
+ export declare function defaultDeterministicComponents(guardsOnly?: boolean): FitnessComponent[];
84
95
  export declare function detectRegressions(baseline: FitnessReport, current: ComponentResult[], eps?: number): string[];
85
96
  /** Run components, aggregate to a weighted [0,1] fitness, flag regressions vs an optional baseline. */
86
97
  export declare function computeFitness(ctx: FitnessContext, components: FitnessComponent[], baseline?: FitnessReport): Promise<FitnessReport>;
@@ -14,60 +14,131 @@
14
14
  *
15
15
  * @module agent/self-improvement/evolution/variant-fitness
16
16
  */
17
- import { spawn } from 'child_process';
18
- import { existsSync, readdirSync, statSync } from 'fs';
17
+ import { spawn, execFile } from 'child_process';
18
+ import { existsSync, readdirSync, statSync, mkdtempSync, readFileSync, rmSync } from 'fs';
19
19
  import { join } from 'path';
20
+ import { tmpdir } from 'node:os';
21
+ import { stripVTControlCharacters } from 'node:util';
22
+ import { createRequire } from 'node:module';
23
+ import { dirname, resolve } from 'node:path';
20
24
  const DEFAULT_TIMEOUT_MS = 10 * 60 * 1000;
21
25
  function msg(e) {
22
26
  return e instanceof Error ? e.message : String(e);
23
27
  }
24
- /** Run a subprocess in the checkout, capped output + timeout. Never throws (resolves with code). */
28
+ /** Execute checkout-owned JavaScript CLIs directly, preserving argv on every OS. */
29
+ export function runCheckoutCli(modulePath, args, ctx) {
30
+ try {
31
+ const require = createRequire(resolve(ctx.checkoutDir, 'package.json'));
32
+ // Vitest exports its manifest but deliberately does not export its CLI subpath.
33
+ const cli = modulePath === 'vitest/vitest.mjs'
34
+ ? join(dirname(require.resolve('vitest/package.json')), 'vitest.mjs')
35
+ : require.resolve(modulePath);
36
+ return runProc(process.execPath, [cli, ...args], ctx);
37
+ }
38
+ catch (error) {
39
+ return Promise.resolve({ code: 1, stdout: '', stderr: msg(error), timedOut: false });
40
+ }
41
+ }
42
+ /** Run a bounded subprocess; terminate its process group on timeout. */
25
43
  export function runProc(cmd, args, ctx) {
26
44
  return new Promise((resolve) => {
45
+ const MAX = 1_000_000;
46
+ const windows = process.platform === 'win32';
27
47
  let stdout = '';
28
48
  let stderr = '';
29
49
  let timedOut = false;
30
- const MAX = 1_000_000;
50
+ let settled = false;
51
+ let stopping = false;
31
52
  let child;
53
+ let timer;
54
+ let escalation;
55
+ const stopTree = (signal) => {
56
+ const pid = child?.pid;
57
+ if (!pid)
58
+ return Promise.resolve();
59
+ if (windows) {
60
+ return new Promise(done => {
61
+ execFile('taskkill', ['/F', '/T', '/PID', String(pid)], { windowsHide: true, timeout: 2000 }, error => {
62
+ if (error) {
63
+ try {
64
+ child?.kill(signal);
65
+ }
66
+ catch { /* already gone */ }
67
+ }
68
+ done();
69
+ });
70
+ });
71
+ }
72
+ try {
73
+ process.kill(-pid, signal);
74
+ }
75
+ catch { /* group already gone */ }
76
+ return Promise.resolve();
77
+ };
78
+ const settle = (code) => {
79
+ if (settled)
80
+ return;
81
+ settled = true;
82
+ clearTimeout(timer);
83
+ clearTimeout(escalation);
84
+ ctx.signal?.removeEventListener('abort', abort);
85
+ child?.stdout?.destroy();
86
+ child?.stderr?.destroy();
87
+ child?.unref();
88
+ resolve({ code, stdout, stderr, timedOut });
89
+ };
90
+ const stop = (code, timeout) => {
91
+ if (settled || stopping)
92
+ return;
93
+ stopping = true;
94
+ timedOut = timeout;
95
+ clearTimeout(timer);
96
+ void stopTree('SIGTERM');
97
+ escalation = setTimeout(() => {
98
+ void stopTree('SIGKILL').finally(() => settle(code));
99
+ }, 250);
100
+ };
101
+ const abort = () => {
102
+ stderr = (stderr + 'Operation cancelled').slice(0, MAX);
103
+ stop(130, false);
104
+ };
105
+ if (ctx.signal?.aborted) {
106
+ stderr = 'Operation cancelled before spawn';
107
+ settle(130);
108
+ return;
109
+ }
32
110
  try {
33
- child = spawn(cmd, args, { cwd: ctx.checkoutDir, env: ctx.env ?? process.env });
111
+ child = spawn(cmd, args, {
112
+ cwd: ctx.checkoutDir, env: ctx.env ?? process.env,
113
+ detached: !windows,
114
+ windowsHide: true,
115
+ stdio: ['ignore', 'pipe', 'pipe'],
116
+ });
34
117
  }
35
- catch (err) {
36
- resolve({ code: 1, stdout: '', stderr: msg(err), timedOut: false });
118
+ catch (error) {
119
+ stderr = msg(error).slice(0, MAX);
120
+ settle(1);
37
121
  return;
38
122
  }
39
- const timer = setTimeout(() => {
40
- timedOut = true;
41
- try {
42
- child.kill('SIGTERM');
43
- setTimeout(() => {
44
- try {
45
- child.kill('SIGKILL');
46
- }
47
- catch {
48
- /* already gone */
49
- }
50
- }, 1000);
51
- }
52
- catch {
53
- /* ignore */
54
- }
55
- }, ctx.timeoutMs ?? DEFAULT_TIMEOUT_MS);
56
- child.stdout?.on('data', (d) => {
57
- if (stdout.length < MAX)
58
- stdout += d.toString();
123
+ ctx.signal?.addEventListener('abort', abort, { once: true });
124
+ timer = setTimeout(() => stop(1, true), ctx.timeoutMs ?? DEFAULT_TIMEOUT_MS);
125
+ if (ctx.signal?.aborted)
126
+ abort();
127
+ child.stdout?.on('data', chunk => { stdout += chunk.toString().slice(0, Math.max(0, MAX - stdout.length)); });
128
+ child.stderr?.on('data', chunk => { stderr += chunk.toString().slice(0, Math.max(0, MAX - stderr.length)); });
129
+ child.once('error', error => {
130
+ if (settled)
131
+ return;
132
+ stderr = (stderr + msg(error)).slice(0, MAX);
133
+ if (!stopping)
134
+ void stopTree('SIGKILL').finally(() => settle(1));
59
135
  });
60
- child.stderr?.on('data', (d) => {
61
- if (stderr.length < MAX)
62
- stderr += d.toString();
63
- });
64
- child.on('error', (err) => {
136
+ child.once('close', code => {
137
+ if (settled || stopping)
138
+ return;
65
139
  clearTimeout(timer);
66
- resolve({ code: 1, stdout, stderr: stderr + msg(err), timedOut });
67
- });
68
- child.on('close', (code) => {
69
- clearTimeout(timer);
70
- resolve({ code: code ?? 1, stdout, stderr, timedOut });
140
+ // A completed evaluation must not leave background work in its checkout.
141
+ void stopTree('SIGKILL').finally(() => settle(code ?? 1));
71
142
  });
72
143
  });
73
144
  }
@@ -76,8 +147,17 @@ function lastLines(s, n = 8) {
76
147
  }
77
148
  /** Parse vitest summary lines: "Tests 3 passed (3)" / "Tests 2 failed | 5 passed (7)". */
78
149
  export function parseVitestCounts(out) {
79
- const passed = /(\d+)\s+passed/.exec(out);
80
- const failed = /(\d+)\s+failed/.exec(out);
150
+ try {
151
+ const report = JSON.parse(out);
152
+ const passed = report.numPassedTests;
153
+ const failed = report.numFailedTests;
154
+ if (typeof passed === 'number' && Number.isSafeInteger(passed) && passed >= 0 && typeof failed === 'number' && Number.isSafeInteger(failed) && failed >= 0)
155
+ return { passed, failed };
156
+ }
157
+ catch { /* Compatibility for recorded human-readable summaries. */ }
158
+ const summary = stripVTControlCharacters(out).split('\n').find(line => /^\s*Tests\s/.test(line)) ?? '';
159
+ const passed = /(\d+)\s+passed/.exec(summary);
160
+ const failed = /(\d+)\s+failed/.exec(summary);
81
161
  return { passed: passed ? Number(passed[1]) : 0, failed: failed ? Number(failed[1]) : 0 };
82
162
  }
83
163
  export function listEvalTasks(checkoutDir) {
@@ -101,7 +181,7 @@ export function typecheckComponent(weight = 3) {
101
181
  weight,
102
182
  deterministic: true,
103
183
  async run(ctx) {
104
- const r = await runProc('npx', ['tsc', '--noEmit'], ctx);
184
+ const r = await runCheckoutCli('typescript/bin/tsc', ['--noEmit'], ctx);
105
185
  const passed = r.code === 0 && !r.timedOut;
106
186
  return {
107
187
  name: 'typecheck',
@@ -134,18 +214,27 @@ export function unitTestsComponent(patterns, weight = 4) {
134
214
  weight,
135
215
  deterministic: true,
136
216
  async run(ctx) {
137
- const r = await runProc('npx', ['vitest', 'run', ...patterns], ctx);
138
- const { passed, failed } = parseVitestCounts(r.stdout + r.stderr);
139
- const total = passed + failed;
140
- const { score, passed: passedFlag } = scoreVitestRun(passed, failed, r.code, r.timedOut);
141
- return {
142
- name: 'unit-tests',
143
- weight,
144
- score,
145
- passed: passedFlag,
146
- detail: total > 0 ? `${passed} passed / ${failed} failed` : `no tests collected — ${lastLines(r.stdout + r.stderr, 4)}`,
147
- metrics: { passed, failed },
148
- };
217
+ const dir = mkdtempSync(join(tmpdir(), 'cb-fitness-'));
218
+ try {
219
+ const file = join(dir, 'vitest.json');
220
+ const r = await runCheckoutCli('vitest/vitest.mjs', ['run', ...patterns, '--reporter=json', `--outputFile=${file}`], ctx);
221
+ // A separate report avoids mixing tool/subprocess stdout with the machine-readable results.
222
+ if (statSync(file).size > 16 * 1024 * 1024)
223
+ throw new Error('Test report exceeds 16 MiB');
224
+ const raw = readFileSync(file, 'utf8');
225
+ const parsed = JSON.parse(raw);
226
+ if (!Number.isSafeInteger(parsed.numPassedTests) || !Number.isSafeInteger(parsed.numFailedTests) || Number(parsed.numPassedTests) < 0 || Number(parsed.numFailedTests) < 0)
227
+ throw new Error('Invalid test report counts');
228
+ const { passed, failed } = parseVitestCounts(raw);
229
+ const { score, passed: passedFlag } = scoreVitestRun(passed, failed, r.code, r.timedOut);
230
+ return { name: 'unit-tests', weight, score, passed: passedFlag, detail: `${passed} passed / ${failed} failed`, metrics: { passed, failed } };
231
+ }
232
+ catch (error) {
233
+ return { name: 'unit-tests', weight, score: 0, passed: false, detail: `Missing or invalid Vitest report: ${msg(error)}` };
234
+ }
235
+ finally {
236
+ rmSync(dir, { recursive: true, force: true });
237
+ }
149
238
  },
150
239
  };
151
240
  }
@@ -163,26 +252,60 @@ export function evalTasksComponent(tasks, weight = 5) {
163
252
  if (all.length === 0) {
164
253
  return { name: 'eval-tasks', weight, score: 0, passed: false, detail: 'no eval tasks found' };
165
254
  }
255
+ const build = await runCheckoutCli('typescript/bin/tsc', [], ctx);
256
+ if (build.code !== 0 || build.timedOut)
257
+ return { name: 'eval-tasks', weight, score: 0, passed: false, detail: 'candidate build failed' };
166
258
  let pass = 0;
259
+ let completed = true;
260
+ const outcomes = {};
167
261
  for (const t of all) {
168
262
  const r = await runProc(process.execPath, ['eval/run-task.mjs', t], ctx);
169
- if (r.code === 0 && !r.timedOut)
263
+ const ok = r.code === 0 && !r.timedOut;
264
+ if (ok)
170
265
  pass++;
266
+ if (r.timedOut)
267
+ completed = false;
268
+ outcomes[`task:${t}`] = ok ? 1 : 0;
171
269
  }
172
270
  return {
173
271
  name: 'eval-tasks',
174
272
  weight,
175
273
  score: pass / all.length,
176
- passed: pass === all.length,
274
+ // Completion is the gate; task success is a graded optimization signal.
275
+ passed: completed,
177
276
  detail: `${pass}/${all.length} eval tasks passed`,
178
- metrics: { passed: pass, total: all.length },
277
+ metrics: { passed: pass, total: all.length, ...outcomes },
179
278
  };
180
279
  },
181
280
  };
182
281
  }
282
+ /** Protected, offline task objective. Guard checks can be green while task success improves. */
283
+ export function harnessTasksComponent(weight = 5) {
284
+ return {
285
+ name: 'harness-tasks', weight, deterministic: true,
286
+ async run(ctx) {
287
+ const build = await runCheckoutCli('typescript/bin/tsc', [], ctx);
288
+ if (build.code !== 0 || build.timedOut)
289
+ return { name: 'harness-tasks', weight, score: 0, passed: false, detail: 'candidate build failed' };
290
+ const result = await runProc(process.execPath, ['eval/harness-benchmark.mjs'], ctx);
291
+ try {
292
+ const lines = result.stdout.trim().split('\n');
293
+ const report = JSON.parse(lines[lines.length - 1] ?? '');
294
+ const entries = Object.entries(report.results ?? {});
295
+ if (result.code !== 0 || result.timedOut || report.kind !== 'harness_benchmark' || !entries.length || entries.some(([, value]) => typeof value !== 'boolean'))
296
+ throw new Error('Invalid harness benchmark report');
297
+ const pass = entries.filter(([, value]) => value).length;
298
+ return { name: 'harness-tasks', weight, passed: true, score: pass / entries.length, detail: `${pass}/${entries.length} harness tasks passed`, metrics: Object.fromEntries(entries.map(([id, value]) => [`task:${id}`, value ? 1 : 0])) };
299
+ }
300
+ catch {
301
+ return { name: 'harness-tasks', weight, score: 0, passed: false, detail: 'harness benchmark failed or emitted an invalid report' };
302
+ }
303
+ },
304
+ };
305
+ }
183
306
  /** Default deterministic set → a fast, reproducible baseline (no LLM, no build). */
184
- export function defaultDeterministicComponents() {
185
- return [typecheckComponent(), unitTestsComponent(['tests/agent/self-improvement'])];
307
+ export function defaultDeterministicComponents(guardsOnly = false) {
308
+ return [typecheckComponent(guardsOnly ? 0 : 3), unitTestsComponent(['tests/agent/self-improvement'], guardsOnly ? 0 : 4)];
186
309
  }
187
310
  // ---- Aggregation (pure, unit-tested) ------------------------------------------------------
188
311
  export function detectRegressions(baseline, current, eps = 1e-9) {
@@ -192,9 +315,12 @@ export function detectRegressions(baseline, current, eps = 1e-9) {
192
315
  const b = byName.get(r.name);
193
316
  if (!b)
194
317
  continue;
195
- if (r.score < b.score - eps || (b.passed && !r.passed))
318
+ if (r.score < b.score - eps || (b.passed && !r.passed) || Object.entries(b.metrics ?? {}).some(([key, value]) => key.startsWith('task:') && (r.metrics?.[key] ?? -1) < value))
196
319
  out.push(r.name);
197
320
  }
321
+ for (const b of baseline.components)
322
+ if (!current.some(c => c.name === b.name))
323
+ out.push(b.name);
198
324
  return out;
199
325
  }
200
326
  /** Run components, aggregate to a weighted [0,1] fitness, flag regressions vs an optional baseline. */
@@ -42,3 +42,5 @@ export interface ScoreBranchResult {
42
42
  * cleanup; throws only if the worktree cannot be created.
43
43
  */
44
44
  export declare function scoreBranchInWorktree(branch: string, opts?: ScoreBranchOptions): Promise<ScoreBranchResult>;
45
+ /** Score exactly a pinned commit, including when that commit is already checked out elsewhere. */
46
+ export declare function scoreReferenceInWorktree(ref: string, opts?: ScoreBranchOptions): Promise<ScoreBranchResult>;
@@ -10,6 +10,8 @@
10
10
  *
11
11
  * @module agent/self-improvement/evolution/worktree-scorer
12
12
  */
13
+ import { execFileSync } from 'node:child_process';
14
+ import { randomUUID } from 'node:crypto';
13
15
  import { existsSync, symlinkSync } from 'fs';
14
16
  import { join } from 'path';
15
17
  import { logger } from '../../../utils/logger.js';
@@ -96,4 +98,17 @@ function linkNodeModules(basePath, worktreePath) {
96
98
  logger.warn(`[evolve] node_modules symlink failed: ${err instanceof Error ? err.message : String(err)}`);
97
99
  }
98
100
  }
101
+ /** Score exactly a pinned commit, including when that commit is already checked out elsewhere. */
102
+ export async function scoreReferenceInWorktree(ref, opts = {}) {
103
+ const cwd = opts.basePath ?? process.cwd();
104
+ const sha = execFileSync('git', ['rev-parse', '--verify', `${ref}^{commit}`], { cwd, encoding: 'utf8' }).trim();
105
+ const branch = `codebuddy/evaluate/${randomUUID()}`;
106
+ execFileSync('git', ['branch', branch, sha], { cwd });
107
+ try {
108
+ return await scoreBranchInWorktree(branch, { ...opts, baselineRef: sha });
109
+ }
110
+ finally {
111
+ execFileSync('git', ['branch', '-D', branch], { cwd, stdio: 'ignore' });
112
+ }
113
+ }
99
114
  //# sourceMappingURL=worktree-scorer.js.map
@@ -13,6 +13,7 @@ export interface EvolutionaryArchiveOptions {
13
13
  now?: () => Date;
14
14
  }
15
15
  export declare class EvolutionaryArchive {
16
+ readonly workDir: string;
16
17
  private readonly filePath;
17
18
  private readonly now;
18
19
  constructor(options?: EvolutionaryArchiveOptions);
@@ -20,7 +21,7 @@ export declare class EvolutionaryArchive {
20
21
  private read;
21
22
  private write;
22
23
  list(): ArchiveEntry[];
23
- /** Append a validated improvement. Returns the stored entry. */
24
+ /** Append a validated improvement. Idempotent on kind+proposalId+targetScenarioId. */
24
25
  append(entry: Omit<ArchiveEntry, 'createdAt' | 'reviewedBy'> & {
25
26
  reviewedBy?: string;
26
27
  }): ArchiveEntry;
@@ -11,17 +11,35 @@ import path from 'path';
11
11
  import { readJsonAtomicSync, writeJsonAtomicSync } from '../../utils/atomic-write.js';
12
12
  export const SELF_IMPROVEMENT_ARCHIVE_SCHEMA_VERSION = 1;
13
13
  export class EvolutionaryArchive {
14
+ workDir;
14
15
  filePath;
15
16
  now;
16
17
  constructor(options = {}) {
17
- const root = options.workDir ?? process.cwd();
18
- this.filePath = path.join(root, '.codebuddy', 'self-improvement', 'archive.json');
18
+ this.workDir = options.workDir ?? process.cwd();
19
+ this.filePath = path.join(this.workDir, '.codebuddy', 'self-improvement', 'archive.json');
19
20
  this.now = options.now ?? (() => new Date());
20
21
  }
21
22
  get path() {
22
23
  return this.filePath;
23
24
  }
24
- read() {
25
+ read(strict = false) {
26
+ if (strict) {
27
+ let raw;
28
+ try {
29
+ raw = fs.readFileSync(this.filePath, 'utf8');
30
+ }
31
+ catch (error) {
32
+ if (error.code === 'ENOENT') {
33
+ return { schemaVersion: SELF_IMPROVEMENT_ARCHIVE_SCHEMA_VERSION, entries: [] };
34
+ }
35
+ throw error;
36
+ }
37
+ const parsed = JSON.parse(raw);
38
+ if (!parsed || parsed.schemaVersion !== SELF_IMPROVEMENT_ARCHIVE_SCHEMA_VERSION || !Array.isArray(parsed.entries)) {
39
+ throw new Error('Invalid evolution archive; refusing to overwrite evidence');
40
+ }
41
+ return parsed;
42
+ }
25
43
  try {
26
44
  const parsed = readJsonAtomicSync(this.filePath, {}, { mode: 0o600 });
27
45
  if (Array.isArray(parsed.entries)) {
@@ -40,9 +58,20 @@ export class EvolutionaryArchive {
40
58
  list() {
41
59
  return this.read().entries;
42
60
  }
43
- /** Append a validated improvement. Returns the stored entry. */
61
+ /** Append a validated improvement. Idempotent on kind+proposalId+targetScenarioId. */
44
62
  append(entry) {
45
- const file = this.read();
63
+ const file = this.read(true);
64
+ const existing = file.entries.find((stored) => stored.proposalId === entry.proposalId &&
65
+ stored.kind === entry.kind &&
66
+ stored.targetScenarioId === entry.targetScenarioId);
67
+ if (existing) {
68
+ const previousSha = existing.evidence?.artifactSha256;
69
+ const nextSha = entry.evidence?.artifactSha256;
70
+ if (typeof previousSha === 'string' && typeof nextSha === 'string' && previousSha !== nextSha) {
71
+ throw new Error(`archive fingerprint conflict for ${entry.kind}:${entry.proposalId}:${entry.targetScenarioId}`);
72
+ }
73
+ return existing;
74
+ }
46
75
  const stored = {
47
76
  ...entry,
48
77
  createdAt: this.now().toISOString(),
@@ -36,6 +36,7 @@ export declare class PendingProposalStore {
36
36
  readonly dir: string;
37
37
  constructor(options?: PendingProposalStoreOptions);
38
38
  pathFor(kind: PendingProposalKind, scenarioId: string): string;
39
+ legacyPathFor(kind: PendingProposalKind, scenarioId: string): string;
39
40
  saveTool(record: Omit<PendingToolProposalRecord, 'schemaVersion' | 'kind'>): PendingToolProposalRecord;
40
41
  saveSkill(record: Omit<PendingSkillProposalRecord, 'schemaVersion' | 'kind'>): PendingSkillProposalRecord;
41
42
  loadTool(scenarioId: string): PendingToolProposalRecord | null;
@@ -10,11 +10,15 @@
10
10
  */
11
11
  import fs from 'fs';
12
12
  import path from 'path';
13
+ import { createHash } from 'crypto';
13
14
  import { readJsonAtomicSync, writeJsonAtomicSync } from '../../utils/atomic-write.js';
14
15
  export const PENDING_PROPOSAL_SCHEMA_VERSION = 1;
15
16
  function safeSegment(id) {
16
17
  const cleaned = id.replace(/[^a-zA-Z0-9._-]+/g, '-').replace(/^-+|-+$/g, '');
17
- return cleaned || 'proposal';
18
+ return cleaned.slice(0, 80) || 'proposal';
19
+ }
20
+ function scenarioHash(id) {
21
+ return createHash('sha256').update(id).digest('hex').slice(0, 16);
18
22
  }
19
23
  export class PendingProposalStore {
20
24
  dir;
@@ -23,6 +27,9 @@ export class PendingProposalStore {
23
27
  this.dir = path.join(root, '.codebuddy', 'self-improvement', 'proposals');
24
28
  }
25
29
  pathFor(kind, scenarioId) {
30
+ return path.join(this.dir, `${kind}-${safeSegment(scenarioId)}-${scenarioHash(scenarioId)}.json`);
31
+ }
32
+ legacyPathFor(kind, scenarioId) {
26
33
  return path.join(this.dir, `${kind}-${safeSegment(scenarioId)}.json`);
27
34
  }
28
35
  saveTool(record) {
@@ -44,38 +51,93 @@ export class PendingProposalStore {
44
51
  return stored;
45
52
  }
46
53
  loadTool(scenarioId) {
47
- const parsed = this.read(this.pathFor('tool', scenarioId));
48
- if (!parsed || parsed.kind !== 'tool')
49
- return null;
50
- return parsed;
54
+ const parsed = this.read(this.pathFor('tool', scenarioId), 'tool', scenarioId);
55
+ if (parsed && parsed.kind === 'tool')
56
+ return parsed;
57
+ const legacyParsed = this.read(this.legacyPathFor('tool', scenarioId), 'tool', scenarioId);
58
+ if (legacyParsed && legacyParsed.kind === 'tool')
59
+ return legacyParsed;
60
+ return null;
51
61
  }
52
62
  loadSkill(scenarioId) {
53
- const parsed = this.read(this.pathFor('skill', scenarioId));
54
- if (!parsed || parsed.kind !== 'skill')
55
- return null;
56
- return parsed;
63
+ const parsed = this.read(this.pathFor('skill', scenarioId), 'skill', scenarioId);
64
+ if (parsed && parsed.kind === 'skill')
65
+ return parsed;
66
+ const legacyParsed = this.read(this.legacyPathFor('skill', scenarioId), 'skill', scenarioId);
67
+ if (legacyParsed && legacyParsed.kind === 'skill')
68
+ return legacyParsed;
69
+ return null;
57
70
  }
58
71
  remove(kind, scenarioId) {
59
- const filePath = this.pathFor(kind, scenarioId);
60
- if (!fs.existsSync(filePath))
61
- return false;
62
- fs.unlinkSync(filePath);
63
- return true;
72
+ let removed = false;
73
+ const canonicalPath = this.pathFor(kind, scenarioId);
74
+ if (fs.existsSync(canonicalPath)) {
75
+ const parsed = this.read(canonicalPath, kind, scenarioId);
76
+ if (parsed) {
77
+ fs.unlinkSync(canonicalPath);
78
+ removed = true;
79
+ }
80
+ }
81
+ const legacyPath = this.legacyPathFor(kind, scenarioId);
82
+ if (fs.existsSync(legacyPath)) {
83
+ const parsed = this.read(legacyPath, kind, scenarioId);
84
+ if (parsed && parsed.scenarioId === scenarioId) {
85
+ fs.unlinkSync(legacyPath);
86
+ removed = true;
87
+ }
88
+ }
89
+ return removed;
64
90
  }
65
91
  write(filePath, record) {
66
92
  fs.mkdirSync(path.dirname(filePath), { recursive: true });
67
93
  writeJsonAtomicSync(filePath, record, { mode: 0o600 });
68
94
  }
69
- read(filePath) {
95
+ read(filePath, expectedKind, expectedScenarioId) {
70
96
  try {
97
+ if (!fs.existsSync(filePath))
98
+ return null;
71
99
  const parsed = readJsonAtomicSync(filePath, null, { mode: 0o600 });
72
- if (!parsed)
100
+ if (!parsed || typeof parsed !== 'object')
73
101
  return null;
74
102
  if (parsed.schemaVersion !== PENDING_PROPOSAL_SCHEMA_VERSION)
75
103
  return null;
76
104
  if (parsed.kind !== 'tool' && parsed.kind !== 'skill')
77
105
  return null;
78
- if (typeof parsed.scenarioId !== 'string' || !parsed.proposal || !parsed.gate)
106
+ if (expectedKind && parsed.kind !== expectedKind)
107
+ return null;
108
+ if (typeof parsed.scenarioId !== 'string' || !parsed.scenarioId.trim())
109
+ return null;
110
+ if (expectedScenarioId !== undefined && parsed.scenarioId !== expectedScenarioId)
111
+ return null;
112
+ // Validate proposal
113
+ const proposal = parsed.proposal;
114
+ if (!proposal || typeof proposal !== 'object')
115
+ return null;
116
+ if (typeof proposal.id !== 'string' || !proposal.id.trim())
117
+ return null;
118
+ if (proposal.targetScenarioId !== parsed.scenarioId)
119
+ return null;
120
+ // Validate usable spec fields
121
+ const spec = proposal.spec;
122
+ if (!spec || typeof spec !== 'object')
123
+ return null;
124
+ if (typeof spec.name !== 'string' || !spec.name.trim())
125
+ return null;
126
+ if (parsed.kind === 'tool') {
127
+ if (typeof spec.code !== 'string')
128
+ return null;
129
+ }
130
+ else if (parsed.kind === 'skill') {
131
+ if (typeof spec.content !== 'string')
132
+ return null;
133
+ }
134
+ // Validate gate identities
135
+ const gate = parsed.gate;
136
+ if (!gate || typeof gate !== 'object')
137
+ return null;
138
+ if (typeof gate.accepted !== 'boolean')
139
+ return null;
140
+ if (gate.scenarioId !== parsed.scenarioId || gate.proposalId !== proposal.id)
79
141
  return null;
80
142
  return parsed;
81
143
  }