@ikarmic/forge-core 4.4.0 → 5.0.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 (573) hide show
  1. package/LICENSE +44 -21
  2. package/README.md +2 -2
  3. package/dist/agent/agent.d.ts +0 -1
  4. package/dist/agent/agent.js +84 -32
  5. package/dist/agent/architect.d.ts +3 -1
  6. package/dist/agent/architect.js +1 -1
  7. package/dist/agent/cache-hints.d.ts +0 -1
  8. package/dist/agent/cache-hints.js +0 -1
  9. package/dist/agent/cost-router.d.ts +0 -1
  10. package/dist/agent/cost-router.js +2 -1
  11. package/dist/agent/enhance-prompt.d.ts +7 -0
  12. package/dist/agent/enhance-prompt.js +30 -0
  13. package/dist/agent/index.d.ts +0 -1
  14. package/dist/agent/index.js +0 -1
  15. package/dist/agent/loop-guard.d.ts +21 -0
  16. package/dist/agent/loop-guard.js +41 -0
  17. package/dist/agent/prompts.d.ts +0 -1
  18. package/dist/agent/prompts.js +0 -1
  19. package/dist/agent/think-tags.d.ts +0 -1
  20. package/dist/agent/think-tags.js +0 -1
  21. package/dist/agent/verify.d.ts +0 -1
  22. package/dist/agent/verify.js +0 -1
  23. package/dist/ai-comments/index.d.ts +0 -1
  24. package/dist/ai-comments/index.js +0 -1
  25. package/dist/attachments.d.ts +46 -0
  26. package/dist/attachments.js +178 -0
  27. package/dist/browser/cdp.d.ts +0 -1
  28. package/dist/browser/cdp.js +0 -1
  29. package/dist/browser/chrome.d.ts +0 -1
  30. package/dist/browser/chrome.js +0 -1
  31. package/dist/browser/session.d.ts +0 -1
  32. package/dist/browser/session.js +0 -1
  33. package/dist/checkpoints/index.d.ts +0 -1
  34. package/dist/checkpoints/index.js +0 -1
  35. package/dist/checkpoints/store.d.ts +0 -1
  36. package/dist/checkpoints/store.js +0 -1
  37. package/dist/checkpoints/types.d.ts +0 -1
  38. package/dist/checkpoints/types.js +0 -1
  39. package/dist/code-index/builder.d.ts +0 -1
  40. package/dist/code-index/builder.js +4 -1
  41. package/dist/code-index/index-store.d.ts +0 -1
  42. package/dist/code-index/index-store.js +0 -1
  43. package/dist/code-index/parser.d.ts +0 -1
  44. package/dist/code-index/parser.js +0 -1
  45. package/dist/code-index/types.d.ts +0 -1
  46. package/dist/code-index/types.js +0 -1
  47. package/dist/commands/custom-commands.d.ts +24 -0
  48. package/dist/commands/custom-commands.js +98 -0
  49. package/dist/config-file.d.ts +4 -1
  50. package/dist/config-file.js +0 -1
  51. package/dist/config.d.ts +0 -1
  52. package/dist/config.js +0 -1
  53. package/dist/context/compaction.d.ts +0 -1
  54. package/dist/context/compaction.js +17 -3
  55. package/dist/context/mask.d.ts +0 -1
  56. package/dist/context/mask.js +1 -2
  57. package/dist/credentials/index.d.ts +0 -1
  58. package/dist/credentials/index.js +0 -1
  59. package/dist/credentials/types.d.ts +0 -1
  60. package/dist/credentials/types.js +0 -1
  61. package/dist/diff/index.d.ts +0 -1
  62. package/dist/diff/index.js +0 -1
  63. package/dist/diff/unified-diff.d.ts +0 -1
  64. package/dist/diff/unified-diff.js +42 -16
  65. package/dist/git/auto-commit.d.ts +1 -2
  66. package/dist/git/auto-commit.js +17 -5
  67. package/dist/git/index.d.ts +0 -1
  68. package/dist/git/index.js +0 -1
  69. package/dist/github/github-tools.d.ts +83 -0
  70. package/dist/github/github-tools.js +175 -0
  71. package/dist/hooks/agent-runner.d.ts +0 -1
  72. package/dist/hooks/agent-runner.js +0 -1
  73. package/dist/hooks/bus.d.ts +0 -1
  74. package/dist/hooks/bus.js +0 -1
  75. package/dist/hooks/config.d.ts +0 -1
  76. package/dist/hooks/config.js +0 -1
  77. package/dist/hooks/events.d.ts +0 -1
  78. package/dist/hooks/events.js +0 -1
  79. package/dist/hooks/index.d.ts +0 -1
  80. package/dist/hooks/index.js +0 -1
  81. package/dist/hooks/loader.d.ts +0 -1
  82. package/dist/hooks/loader.js +0 -1
  83. package/dist/hooks/runner.d.ts +0 -1
  84. package/dist/hooks/runner.js +0 -1
  85. package/dist/hooks/watch-coalescer.d.ts +0 -1
  86. package/dist/hooks/watch-coalescer.js +0 -1
  87. package/dist/hooks/watch-ignore.d.ts +0 -1
  88. package/dist/hooks/watch-ignore.js +0 -1
  89. package/dist/hooks/watcher.d.ts +0 -1
  90. package/dist/hooks/watcher.js +0 -1
  91. package/dist/index.d.ts +21 -9
  92. package/dist/index.js +16 -5
  93. package/dist/lazy-model.d.ts +0 -1
  94. package/dist/lazy-model.js +0 -1
  95. package/dist/lint/auto-lint.d.ts +0 -1
  96. package/dist/lint/auto-lint.js +0 -1
  97. package/dist/lint/index.d.ts +0 -1
  98. package/dist/lint/index.js +0 -1
  99. package/dist/lsp/client.d.ts +0 -1
  100. package/dist/lsp/client.js +0 -1
  101. package/dist/lsp/diagnostics.d.ts +0 -1
  102. package/dist/lsp/diagnostics.js +0 -1
  103. package/dist/lsp/index.d.ts +0 -1
  104. package/dist/lsp/index.js +0 -1
  105. package/dist/mcp/catalog.d.ts +0 -1
  106. package/dist/mcp/catalog.js +0 -1
  107. package/dist/mcp/index.d.ts +0 -1
  108. package/dist/mcp/index.js +0 -1
  109. package/dist/mcp/json-schema-to-zod.d.ts +0 -1
  110. package/dist/mcp/json-schema-to-zod.js +0 -1
  111. package/dist/mcp/manager.d.ts +0 -1
  112. package/dist/mcp/manager.js +0 -1
  113. package/dist/mcp/marketplace-types.d.ts +0 -1
  114. package/dist/mcp/marketplace-types.js +0 -1
  115. package/dist/mcp/marketplace.d.ts +0 -1
  116. package/dist/mcp/marketplace.js +0 -1
  117. package/dist/mcp/mcp-client.d.ts +0 -1
  118. package/dist/mcp/mcp-client.js +0 -1
  119. package/dist/mcp/mcp-config.d.ts +0 -1
  120. package/dist/mcp/mcp-config.js +0 -1
  121. package/dist/mcp/registry.d.ts +0 -1
  122. package/dist/mcp/registry.js +0 -1
  123. package/dist/memory/memory-store.d.ts +57 -11
  124. package/dist/memory/memory-store.js +236 -70
  125. package/dist/memory/rank.d.ts +20 -0
  126. package/dist/memory/rank.js +64 -0
  127. package/dist/memory/stale.d.ts +18 -0
  128. package/dist/memory/stale.js +45 -0
  129. package/dist/model-alias.d.ts +0 -1
  130. package/dist/model-alias.js +0 -1
  131. package/dist/model-catalog.d.ts +0 -1
  132. package/dist/model-catalog.js +0 -1
  133. package/dist/model-list.d.ts +0 -1
  134. package/dist/model-list.js +0 -1
  135. package/dist/orchestrator/board-tools.d.ts +0 -1
  136. package/dist/orchestrator/board-tools.js +0 -1
  137. package/dist/orchestrator/board.d.ts +0 -1
  138. package/dist/orchestrator/board.js +0 -1
  139. package/dist/orchestrator/index.d.ts +0 -1
  140. package/dist/orchestrator/index.js +0 -1
  141. package/dist/orchestrator/orchestrator.d.ts +0 -1
  142. package/dist/orchestrator/orchestrator.js +0 -1
  143. package/dist/orchestrator/router.d.ts +0 -1
  144. package/dist/orchestrator/router.js +0 -1
  145. package/dist/orchestrator/specialists-registry.d.ts +0 -1
  146. package/dist/orchestrator/specialists-registry.js +0 -1
  147. package/dist/orchestrator/specialists.d.ts +0 -1
  148. package/dist/orchestrator/specialists.js +5 -6
  149. package/dist/orchestrator/types.d.ts +0 -1
  150. package/dist/orchestrator/types.js +0 -1
  151. package/dist/plugins/index.d.ts +0 -1
  152. package/dist/plugins/index.js +0 -1
  153. package/dist/plugins/install.d.ts +0 -1
  154. package/dist/plugins/install.js +0 -1
  155. package/dist/plugins/loader.d.ts +0 -1
  156. package/dist/plugins/loader.js +0 -1
  157. package/dist/policy/audit.d.ts +0 -1
  158. package/dist/policy/audit.js +0 -1
  159. package/dist/policy/policy.d.ts +0 -1
  160. package/dist/policy/policy.js +0 -1
  161. package/dist/policy/proxy.d.ts +0 -1
  162. package/dist/policy/proxy.js +0 -1
  163. package/dist/pricing.d.ts +0 -1
  164. package/dist/pricing.js +0 -1
  165. package/dist/providers.d.ts +0 -1
  166. package/dist/providers.js +0 -1
  167. package/dist/repomap/index.d.ts +0 -1
  168. package/dist/repomap/index.js +0 -1
  169. package/dist/repomap/repo-map.d.ts +0 -1
  170. package/dist/repomap/repo-map.js +0 -1
  171. package/dist/semantic/chunker.d.ts +0 -1
  172. package/dist/semantic/chunker.js +0 -1
  173. package/dist/semantic/embedder.d.ts +0 -1
  174. package/dist/semantic/embedder.js +0 -1
  175. package/dist/semantic/semantic-index.d.ts +0 -1
  176. package/dist/semantic/semantic-index.js +4 -1
  177. package/dist/semantic/vector-store.d.ts +0 -1
  178. package/dist/semantic/vector-store.js +0 -1
  179. package/dist/sessions/index.d.ts +2 -1
  180. package/dist/sessions/index.js +2 -1
  181. package/dist/sessions/prune.d.ts +23 -0
  182. package/dist/sessions/prune.js +35 -0
  183. package/dist/sessions/search.d.ts +22 -0
  184. package/dist/sessions/search.js +66 -0
  185. package/dist/sessions/session-export.d.ts +0 -1
  186. package/dist/sessions/session-export.js +0 -1
  187. package/dist/sessions/session-store.d.ts +0 -1
  188. package/dist/sessions/session-store.js +0 -1
  189. package/dist/sessions/watch.d.ts +0 -1
  190. package/dist/sessions/watch.js +0 -1
  191. package/dist/settings-export.d.ts +0 -1
  192. package/dist/settings-export.js +0 -1
  193. package/dist/skills/activator.d.ts +0 -1
  194. package/dist/skills/activator.js +0 -1
  195. package/dist/skills/index.d.ts +0 -1
  196. package/dist/skills/index.js +0 -1
  197. package/dist/skills/loader.d.ts +0 -1
  198. package/dist/skills/loader.js +0 -1
  199. package/dist/skills/marketplace-types.d.ts +0 -1
  200. package/dist/skills/marketplace-types.js +0 -1
  201. package/dist/skills/marketplace.d.ts +0 -1
  202. package/dist/skills/marketplace.js +0 -1
  203. package/dist/skills/parser.d.ts +0 -1
  204. package/dist/skills/parser.js +0 -1
  205. package/dist/skills/types.d.ts +0 -1
  206. package/dist/skills/types.js +0 -1
  207. package/dist/specs/generate.d.ts +0 -1
  208. package/dist/specs/generate.js +0 -1
  209. package/dist/specs/index.d.ts +2 -1
  210. package/dist/specs/index.js +1 -1
  211. package/dist/specs/store.d.ts +7 -1
  212. package/dist/specs/store.js +33 -1
  213. package/dist/specs/sync.d.ts +29 -0
  214. package/dist/specs/sync.js +155 -0
  215. package/dist/specs/tasks-parser.d.ts +0 -1
  216. package/dist/specs/tasks-parser.js +0 -1
  217. package/dist/specs/types.d.ts +2 -1
  218. package/dist/specs/types.js +0 -1
  219. package/dist/steering/glob.d.ts +0 -1
  220. package/dist/steering/glob.js +0 -1
  221. package/dist/steering/index.d.ts +0 -1
  222. package/dist/steering/index.js +0 -1
  223. package/dist/steering/loader.d.ts +0 -1
  224. package/dist/steering/loader.js +0 -1
  225. package/dist/steering/project-instructions.d.ts +0 -1
  226. package/dist/steering/project-instructions.js +0 -1
  227. package/dist/steering/select.d.ts +0 -1
  228. package/dist/steering/select.js +0 -1
  229. package/dist/steering/types.d.ts +0 -1
  230. package/dist/steering/types.js +0 -1
  231. package/dist/structured/index.d.ts +0 -1
  232. package/dist/structured/index.js +0 -1
  233. package/dist/structured/retry.d.ts +0 -1
  234. package/dist/structured/retry.js +0 -1
  235. package/dist/structured/schema.d.ts +0 -1
  236. package/dist/structured/schema.js +0 -1
  237. package/dist/tools/browser-tools.d.ts +0 -1
  238. package/dist/tools/browser-tools.js +0 -1
  239. package/dist/tools/codebase-search.d.ts +0 -1
  240. package/dist/tools/codebase-search.js +0 -1
  241. package/dist/tools/command-allow.d.ts +0 -1
  242. package/dist/tools/command-allow.js +0 -1
  243. package/dist/tools/edit-file.d.ts +0 -1
  244. package/dist/tools/edit-file.js +0 -1
  245. package/dist/tools/fetch-url.d.ts +0 -1
  246. package/dist/tools/fetch-url.js +0 -1
  247. package/dist/tools/find-references.d.ts +0 -1
  248. package/dist/tools/find-references.js +0 -1
  249. package/dist/tools/find-symbol.d.ts +0 -1
  250. package/dist/tools/find-symbol.js +0 -1
  251. package/dist/tools/ignore-rules.d.ts +15 -0
  252. package/dist/tools/ignore-rules.js +84 -0
  253. package/dist/tools/index.d.ts +3 -3
  254. package/dist/tools/index.js +5 -3
  255. package/dist/tools/list-files.d.ts +3 -1
  256. package/dist/tools/list-files.js +8 -1
  257. package/dist/tools/list-symbols.d.ts +0 -1
  258. package/dist/tools/list-symbols.js +0 -1
  259. package/dist/tools/memory.d.ts +34 -2
  260. package/dist/tools/memory.js +118 -26
  261. package/dist/tools/notebook.d.ts +69 -0
  262. package/dist/tools/notebook.js +163 -0
  263. package/dist/tools/policy.d.ts +0 -1
  264. package/dist/tools/policy.js +0 -1
  265. package/dist/tools/read-file.d.ts +8 -2
  266. package/dist/tools/read-file.js +19 -2
  267. package/dist/tools/read-image.d.ts +0 -1
  268. package/dist/tools/read-image.js +0 -1
  269. package/dist/tools/read-only.d.ts +0 -1
  270. package/dist/tools/read-only.js +0 -1
  271. package/dist/tools/redact.d.ts +0 -1
  272. package/dist/tools/redact.js +0 -1
  273. package/dist/tools/result-cache.d.ts +0 -1
  274. package/dist/tools/result-cache.js +0 -1
  275. package/dist/tools/run-command.d.ts +0 -1
  276. package/dist/tools/run-command.js +0 -1
  277. package/dist/tools/sandbox-loader.d.ts +0 -1
  278. package/dist/tools/sandbox-loader.js +0 -1
  279. package/dist/tools/search-files.d.ts +5 -3
  280. package/dist/tools/search-files.js +8 -1
  281. package/dist/tools/shared.d.ts +0 -1
  282. package/dist/tools/shared.js +0 -1
  283. package/dist/tools/sub-agent.d.ts +0 -1
  284. package/dist/tools/sub-agent.js +0 -1
  285. package/dist/tools/web-search.d.ts +55 -0
  286. package/dist/tools/web-search.js +131 -0
  287. package/dist/tools/write-file.d.ts +0 -1
  288. package/dist/tools/write-file.js +0 -1
  289. package/dist/types.d.ts +43 -2
  290. package/dist/types.js +0 -1
  291. package/dist/verify/detect.d.ts +0 -1
  292. package/dist/verify/detect.js +0 -1
  293. package/dist/verify/index.d.ts +0 -1
  294. package/dist/verify/index.js +0 -1
  295. package/dist/worktree/branch-ops.d.ts +0 -1
  296. package/dist/worktree/branch-ops.js +0 -1
  297. package/dist/worktree/index.d.ts +0 -1
  298. package/dist/worktree/index.js +0 -1
  299. package/dist/worktree/worktree.d.ts +0 -1
  300. package/dist/worktree/worktree.js +0 -1
  301. package/package.json +4 -2
  302. package/dist/agent/agent.d.ts.map +0 -1
  303. package/dist/agent/agent.js.map +0 -1
  304. package/dist/agent/architect.d.ts.map +0 -1
  305. package/dist/agent/architect.js.map +0 -1
  306. package/dist/agent/cache-hints.d.ts.map +0 -1
  307. package/dist/agent/cache-hints.js.map +0 -1
  308. package/dist/agent/cost-router.d.ts.map +0 -1
  309. package/dist/agent/cost-router.js.map +0 -1
  310. package/dist/agent/index.d.ts.map +0 -1
  311. package/dist/agent/index.js.map +0 -1
  312. package/dist/agent/prompts.d.ts.map +0 -1
  313. package/dist/agent/prompts.js.map +0 -1
  314. package/dist/agent/think-tags.d.ts.map +0 -1
  315. package/dist/agent/think-tags.js.map +0 -1
  316. package/dist/agent/verify.d.ts.map +0 -1
  317. package/dist/agent/verify.js.map +0 -1
  318. package/dist/ai-comments/index.d.ts.map +0 -1
  319. package/dist/ai-comments/index.js.map +0 -1
  320. package/dist/browser/cdp.d.ts.map +0 -1
  321. package/dist/browser/cdp.js.map +0 -1
  322. package/dist/browser/chrome.d.ts.map +0 -1
  323. package/dist/browser/chrome.js.map +0 -1
  324. package/dist/browser/session.d.ts.map +0 -1
  325. package/dist/browser/session.js.map +0 -1
  326. package/dist/checkpoints/index.d.ts.map +0 -1
  327. package/dist/checkpoints/index.js.map +0 -1
  328. package/dist/checkpoints/store.d.ts.map +0 -1
  329. package/dist/checkpoints/store.js.map +0 -1
  330. package/dist/checkpoints/types.d.ts.map +0 -1
  331. package/dist/checkpoints/types.js.map +0 -1
  332. package/dist/code-index/builder.d.ts.map +0 -1
  333. package/dist/code-index/builder.js.map +0 -1
  334. package/dist/code-index/index-store.d.ts.map +0 -1
  335. package/dist/code-index/index-store.js.map +0 -1
  336. package/dist/code-index/parser.d.ts.map +0 -1
  337. package/dist/code-index/parser.js.map +0 -1
  338. package/dist/code-index/types.d.ts.map +0 -1
  339. package/dist/code-index/types.js.map +0 -1
  340. package/dist/config-file.d.ts.map +0 -1
  341. package/dist/config-file.js.map +0 -1
  342. package/dist/config.d.ts.map +0 -1
  343. package/dist/config.js.map +0 -1
  344. package/dist/context/compaction.d.ts.map +0 -1
  345. package/dist/context/compaction.js.map +0 -1
  346. package/dist/context/mask.d.ts.map +0 -1
  347. package/dist/context/mask.js.map +0 -1
  348. package/dist/credentials/index.d.ts.map +0 -1
  349. package/dist/credentials/index.js.map +0 -1
  350. package/dist/credentials/types.d.ts.map +0 -1
  351. package/dist/credentials/types.js.map +0 -1
  352. package/dist/diff/index.d.ts.map +0 -1
  353. package/dist/diff/index.js.map +0 -1
  354. package/dist/diff/unified-diff.d.ts.map +0 -1
  355. package/dist/diff/unified-diff.js.map +0 -1
  356. package/dist/git/auto-commit.d.ts.map +0 -1
  357. package/dist/git/auto-commit.js.map +0 -1
  358. package/dist/git/index.d.ts.map +0 -1
  359. package/dist/git/index.js.map +0 -1
  360. package/dist/hooks/agent-runner.d.ts.map +0 -1
  361. package/dist/hooks/agent-runner.js.map +0 -1
  362. package/dist/hooks/bus.d.ts.map +0 -1
  363. package/dist/hooks/bus.js.map +0 -1
  364. package/dist/hooks/config.d.ts.map +0 -1
  365. package/dist/hooks/config.js.map +0 -1
  366. package/dist/hooks/events.d.ts.map +0 -1
  367. package/dist/hooks/events.js.map +0 -1
  368. package/dist/hooks/index.d.ts.map +0 -1
  369. package/dist/hooks/index.js.map +0 -1
  370. package/dist/hooks/loader.d.ts.map +0 -1
  371. package/dist/hooks/loader.js.map +0 -1
  372. package/dist/hooks/runner.d.ts.map +0 -1
  373. package/dist/hooks/runner.js.map +0 -1
  374. package/dist/hooks/watch-coalescer.d.ts.map +0 -1
  375. package/dist/hooks/watch-coalescer.js.map +0 -1
  376. package/dist/hooks/watch-ignore.d.ts.map +0 -1
  377. package/dist/hooks/watch-ignore.js.map +0 -1
  378. package/dist/hooks/watcher.d.ts.map +0 -1
  379. package/dist/hooks/watcher.js.map +0 -1
  380. package/dist/index.d.ts.map +0 -1
  381. package/dist/index.js.map +0 -1
  382. package/dist/lazy-model.d.ts.map +0 -1
  383. package/dist/lazy-model.js.map +0 -1
  384. package/dist/lint/auto-lint.d.ts.map +0 -1
  385. package/dist/lint/auto-lint.js.map +0 -1
  386. package/dist/lint/index.d.ts.map +0 -1
  387. package/dist/lint/index.js.map +0 -1
  388. package/dist/lsp/client.d.ts.map +0 -1
  389. package/dist/lsp/client.js.map +0 -1
  390. package/dist/lsp/diagnostics.d.ts.map +0 -1
  391. package/dist/lsp/diagnostics.js.map +0 -1
  392. package/dist/lsp/index.d.ts.map +0 -1
  393. package/dist/lsp/index.js.map +0 -1
  394. package/dist/mcp/catalog.d.ts.map +0 -1
  395. package/dist/mcp/catalog.js.map +0 -1
  396. package/dist/mcp/index.d.ts.map +0 -1
  397. package/dist/mcp/index.js.map +0 -1
  398. package/dist/mcp/json-schema-to-zod.d.ts.map +0 -1
  399. package/dist/mcp/json-schema-to-zod.js.map +0 -1
  400. package/dist/mcp/manager.d.ts.map +0 -1
  401. package/dist/mcp/manager.js.map +0 -1
  402. package/dist/mcp/marketplace-types.d.ts.map +0 -1
  403. package/dist/mcp/marketplace-types.js.map +0 -1
  404. package/dist/mcp/marketplace.d.ts.map +0 -1
  405. package/dist/mcp/marketplace.js.map +0 -1
  406. package/dist/mcp/mcp-client.d.ts.map +0 -1
  407. package/dist/mcp/mcp-client.js.map +0 -1
  408. package/dist/mcp/mcp-config.d.ts.map +0 -1
  409. package/dist/mcp/mcp-config.js.map +0 -1
  410. package/dist/mcp/registry.d.ts.map +0 -1
  411. package/dist/mcp/registry.js.map +0 -1
  412. package/dist/memory/memory-store.d.ts.map +0 -1
  413. package/dist/memory/memory-store.js.map +0 -1
  414. package/dist/model-alias.d.ts.map +0 -1
  415. package/dist/model-alias.js.map +0 -1
  416. package/dist/model-catalog.d.ts.map +0 -1
  417. package/dist/model-catalog.js.map +0 -1
  418. package/dist/model-list.d.ts.map +0 -1
  419. package/dist/model-list.js.map +0 -1
  420. package/dist/orchestrator/board-tools.d.ts.map +0 -1
  421. package/dist/orchestrator/board-tools.js.map +0 -1
  422. package/dist/orchestrator/board.d.ts.map +0 -1
  423. package/dist/orchestrator/board.js.map +0 -1
  424. package/dist/orchestrator/index.d.ts.map +0 -1
  425. package/dist/orchestrator/index.js.map +0 -1
  426. package/dist/orchestrator/orchestrator.d.ts.map +0 -1
  427. package/dist/orchestrator/orchestrator.js.map +0 -1
  428. package/dist/orchestrator/router.d.ts.map +0 -1
  429. package/dist/orchestrator/router.js.map +0 -1
  430. package/dist/orchestrator/specialists-registry.d.ts.map +0 -1
  431. package/dist/orchestrator/specialists-registry.js.map +0 -1
  432. package/dist/orchestrator/specialists.d.ts.map +0 -1
  433. package/dist/orchestrator/specialists.js.map +0 -1
  434. package/dist/orchestrator/types.d.ts.map +0 -1
  435. package/dist/orchestrator/types.js.map +0 -1
  436. package/dist/plugins/index.d.ts.map +0 -1
  437. package/dist/plugins/index.js.map +0 -1
  438. package/dist/plugins/install.d.ts.map +0 -1
  439. package/dist/plugins/install.js.map +0 -1
  440. package/dist/plugins/loader.d.ts.map +0 -1
  441. package/dist/plugins/loader.js.map +0 -1
  442. package/dist/policy/audit.d.ts.map +0 -1
  443. package/dist/policy/audit.js.map +0 -1
  444. package/dist/policy/policy.d.ts.map +0 -1
  445. package/dist/policy/policy.js.map +0 -1
  446. package/dist/policy/proxy.d.ts.map +0 -1
  447. package/dist/policy/proxy.js.map +0 -1
  448. package/dist/pricing.d.ts.map +0 -1
  449. package/dist/pricing.js.map +0 -1
  450. package/dist/providers.d.ts.map +0 -1
  451. package/dist/providers.js.map +0 -1
  452. package/dist/repomap/index.d.ts.map +0 -1
  453. package/dist/repomap/index.js.map +0 -1
  454. package/dist/repomap/repo-map.d.ts.map +0 -1
  455. package/dist/repomap/repo-map.js.map +0 -1
  456. package/dist/semantic/chunker.d.ts.map +0 -1
  457. package/dist/semantic/chunker.js.map +0 -1
  458. package/dist/semantic/embedder.d.ts.map +0 -1
  459. package/dist/semantic/embedder.js.map +0 -1
  460. package/dist/semantic/semantic-index.d.ts.map +0 -1
  461. package/dist/semantic/semantic-index.js.map +0 -1
  462. package/dist/semantic/vector-store.d.ts.map +0 -1
  463. package/dist/semantic/vector-store.js.map +0 -1
  464. package/dist/sessions/index.d.ts.map +0 -1
  465. package/dist/sessions/index.js.map +0 -1
  466. package/dist/sessions/session-export.d.ts.map +0 -1
  467. package/dist/sessions/session-export.js.map +0 -1
  468. package/dist/sessions/session-store.d.ts.map +0 -1
  469. package/dist/sessions/session-store.js.map +0 -1
  470. package/dist/sessions/watch.d.ts.map +0 -1
  471. package/dist/sessions/watch.js.map +0 -1
  472. package/dist/settings-export.d.ts.map +0 -1
  473. package/dist/settings-export.js.map +0 -1
  474. package/dist/skills/activator.d.ts.map +0 -1
  475. package/dist/skills/activator.js.map +0 -1
  476. package/dist/skills/index.d.ts.map +0 -1
  477. package/dist/skills/index.js.map +0 -1
  478. package/dist/skills/loader.d.ts.map +0 -1
  479. package/dist/skills/loader.js.map +0 -1
  480. package/dist/skills/marketplace-types.d.ts.map +0 -1
  481. package/dist/skills/marketplace-types.js.map +0 -1
  482. package/dist/skills/marketplace.d.ts.map +0 -1
  483. package/dist/skills/marketplace.js.map +0 -1
  484. package/dist/skills/parser.d.ts.map +0 -1
  485. package/dist/skills/parser.js.map +0 -1
  486. package/dist/skills/types.d.ts.map +0 -1
  487. package/dist/skills/types.js.map +0 -1
  488. package/dist/specs/generate.d.ts.map +0 -1
  489. package/dist/specs/generate.js.map +0 -1
  490. package/dist/specs/index.d.ts.map +0 -1
  491. package/dist/specs/index.js.map +0 -1
  492. package/dist/specs/store.d.ts.map +0 -1
  493. package/dist/specs/store.js.map +0 -1
  494. package/dist/specs/tasks-parser.d.ts.map +0 -1
  495. package/dist/specs/tasks-parser.js.map +0 -1
  496. package/dist/specs/types.d.ts.map +0 -1
  497. package/dist/specs/types.js.map +0 -1
  498. package/dist/steering/glob.d.ts.map +0 -1
  499. package/dist/steering/glob.js.map +0 -1
  500. package/dist/steering/index.d.ts.map +0 -1
  501. package/dist/steering/index.js.map +0 -1
  502. package/dist/steering/loader.d.ts.map +0 -1
  503. package/dist/steering/loader.js.map +0 -1
  504. package/dist/steering/project-instructions.d.ts.map +0 -1
  505. package/dist/steering/project-instructions.js.map +0 -1
  506. package/dist/steering/select.d.ts.map +0 -1
  507. package/dist/steering/select.js.map +0 -1
  508. package/dist/steering/types.d.ts.map +0 -1
  509. package/dist/steering/types.js.map +0 -1
  510. package/dist/structured/index.d.ts.map +0 -1
  511. package/dist/structured/index.js.map +0 -1
  512. package/dist/structured/retry.d.ts.map +0 -1
  513. package/dist/structured/retry.js.map +0 -1
  514. package/dist/structured/schema.d.ts.map +0 -1
  515. package/dist/structured/schema.js.map +0 -1
  516. package/dist/tools/browser-tools.d.ts.map +0 -1
  517. package/dist/tools/browser-tools.js.map +0 -1
  518. package/dist/tools/codebase-search.d.ts.map +0 -1
  519. package/dist/tools/codebase-search.js.map +0 -1
  520. package/dist/tools/command-allow.d.ts.map +0 -1
  521. package/dist/tools/command-allow.js.map +0 -1
  522. package/dist/tools/edit-file.d.ts.map +0 -1
  523. package/dist/tools/edit-file.js.map +0 -1
  524. package/dist/tools/fetch-url.d.ts.map +0 -1
  525. package/dist/tools/fetch-url.js.map +0 -1
  526. package/dist/tools/find-references.d.ts.map +0 -1
  527. package/dist/tools/find-references.js.map +0 -1
  528. package/dist/tools/find-symbol.d.ts.map +0 -1
  529. package/dist/tools/find-symbol.js.map +0 -1
  530. package/dist/tools/index.d.ts.map +0 -1
  531. package/dist/tools/index.js.map +0 -1
  532. package/dist/tools/list-files.d.ts.map +0 -1
  533. package/dist/tools/list-files.js.map +0 -1
  534. package/dist/tools/list-symbols.d.ts.map +0 -1
  535. package/dist/tools/list-symbols.js.map +0 -1
  536. package/dist/tools/memory.d.ts.map +0 -1
  537. package/dist/tools/memory.js.map +0 -1
  538. package/dist/tools/policy.d.ts.map +0 -1
  539. package/dist/tools/policy.js.map +0 -1
  540. package/dist/tools/read-file.d.ts.map +0 -1
  541. package/dist/tools/read-file.js.map +0 -1
  542. package/dist/tools/read-image.d.ts.map +0 -1
  543. package/dist/tools/read-image.js.map +0 -1
  544. package/dist/tools/read-only.d.ts.map +0 -1
  545. package/dist/tools/read-only.js.map +0 -1
  546. package/dist/tools/redact.d.ts.map +0 -1
  547. package/dist/tools/redact.js.map +0 -1
  548. package/dist/tools/result-cache.d.ts.map +0 -1
  549. package/dist/tools/result-cache.js.map +0 -1
  550. package/dist/tools/run-command.d.ts.map +0 -1
  551. package/dist/tools/run-command.js.map +0 -1
  552. package/dist/tools/sandbox-loader.d.ts.map +0 -1
  553. package/dist/tools/sandbox-loader.js.map +0 -1
  554. package/dist/tools/search-files.d.ts.map +0 -1
  555. package/dist/tools/search-files.js.map +0 -1
  556. package/dist/tools/shared.d.ts.map +0 -1
  557. package/dist/tools/shared.js.map +0 -1
  558. package/dist/tools/sub-agent.d.ts.map +0 -1
  559. package/dist/tools/sub-agent.js.map +0 -1
  560. package/dist/tools/write-file.d.ts.map +0 -1
  561. package/dist/tools/write-file.js.map +0 -1
  562. package/dist/types.d.ts.map +0 -1
  563. package/dist/types.js.map +0 -1
  564. package/dist/verify/detect.d.ts.map +0 -1
  565. package/dist/verify/detect.js.map +0 -1
  566. package/dist/verify/index.d.ts.map +0 -1
  567. package/dist/verify/index.js.map +0 -1
  568. package/dist/worktree/branch-ops.d.ts.map +0 -1
  569. package/dist/worktree/branch-ops.js.map +0 -1
  570. package/dist/worktree/index.d.ts.map +0 -1
  571. package/dist/worktree/index.js.map +0 -1
  572. package/dist/worktree/worktree.d.ts.map +0 -1
  573. package/dist/worktree/worktree.js.map +0 -1
@@ -22,6 +22,8 @@
22
22
  import { mkdir, readFile, stat, writeFile } from 'node:fs/promises';
23
23
  import { homedir } from 'node:os';
24
24
  import path from 'node:path';
25
+ import { TYPE_WEIGHT, scoreSections } from './rank.js';
26
+ import { checkStale, staleKey } from './stale.js';
25
27
  /** Default location for project memory, relative to cwd. */
26
28
  export const PROJECT_MEMORY_DIR = '.forge';
27
29
  export const PROJECT_MEMORY_FILE = 'MEMORY.md';
@@ -31,6 +33,11 @@ export const GLOBAL_MEMORY_FILE = 'MEMORY.md';
31
33
  export const DEFAULT_MAX_BYTES = 50_000;
32
34
  /** Soft cap on bytes injected into the system prompt. */
33
35
  export const DEFAULT_INJECTION_BUDGET = 8_000;
36
+ const MEMORY_TYPES = ['convention', 'decision', 'correction', 'reference'];
37
+ /** Lowercase, NFKC-normalize and whitespace-collapse a heading so near-duplicates compare equal. */
38
+ export function normalizeHeading(h) {
39
+ return h.normalize('NFKC').toLowerCase().replace(/\s+/g, ' ').trim();
40
+ }
34
41
  /**
35
42
  * Walk up from `cwd` looking for a directory containing `.forge/`. Stops at
36
43
  * the first match; returns null past the filesystem root or past the home
@@ -80,12 +87,7 @@ export function parseMemory(text, source, updatedAt) {
80
87
  const h2 = /^##\s+(.+?)\s*$/.exec(line);
81
88
  if (h2) {
82
89
  if (currentHeading !== null) {
83
- out.push({
84
- heading: currentHeading,
85
- body: currentBody.join('\n').trim(),
86
- source,
87
- updatedAt,
88
- });
90
+ out.push(buildSection(currentHeading, currentBody, source, updatedAt));
89
91
  }
90
92
  currentHeading = h2[1];
91
93
  currentBody = [];
@@ -96,15 +98,69 @@ export function parseMemory(text, source, updatedAt) {
96
98
  // Lines before the first H2 are dropped (title / preamble).
97
99
  }
98
100
  if (currentHeading !== null) {
99
- out.push({
100
- heading: currentHeading,
101
- body: currentBody.join('\n').trim(),
102
- source,
103
- updatedAt,
104
- });
101
+ out.push(buildSection(currentHeading, currentBody, source, updatedAt));
105
102
  }
106
103
  return out;
107
104
  }
105
+ /** Build a section from its heading and raw body lines, peeling off a leading metadata comment. */
106
+ function buildSection(heading, bodyLines, source, fileUpdatedAt) {
107
+ const firstIdx = bodyLines.findIndex((l) => l.trim() !== '');
108
+ const meta = firstIdx === -1 ? null : parseMetaLine(bodyLines[firstIdx]);
109
+ const rest = meta ? bodyLines.slice(firstIdx + 1) : bodyLines;
110
+ return {
111
+ heading,
112
+ body: rest.join('\n').trim(),
113
+ source,
114
+ updatedAt: meta?.updatedAt ?? fileUpdatedAt,
115
+ type: meta?.type ?? 'convention',
116
+ refs: meta?.refs ?? [],
117
+ pinned: meta?.pinned ?? false,
118
+ };
119
+ }
120
+ /**
121
+ * Parse `<!-- forge: key=value, ... -->`. Returns null when the line is not a
122
+ * forge comment. Unknown keys and invalid values are ignored field by field, so a
123
+ * damaged comment degrades to defaults instead of failing the load.
124
+ */
125
+ function parseMetaLine(line) {
126
+ const m = /^<!--\s*forge:(.*?)-->$/.exec(line.trim());
127
+ if (!m)
128
+ return null;
129
+ const meta = {};
130
+ for (const pair of m[1].split(',')) {
131
+ const eq = pair.indexOf('=');
132
+ if (eq === -1)
133
+ continue;
134
+ const key = pair.slice(0, eq).trim();
135
+ const value = pair.slice(eq + 1).trim();
136
+ if (key === 'type' && MEMORY_TYPES.includes(value)) {
137
+ meta.type = value;
138
+ }
139
+ else if (key === 'updated' && /^\d{4}-\d{2}-\d{2}$/.test(value)) {
140
+ const ms = Date.parse(`${value}T00:00:00Z`);
141
+ if (!Number.isNaN(ms))
142
+ meta.updatedAt = ms;
143
+ }
144
+ else if (key === 'refs') {
145
+ const refs = value.split(';').map((r) => r.trim()).filter(Boolean);
146
+ if (refs.length > 0)
147
+ meta.refs = refs;
148
+ }
149
+ else if (key === 'pin' && value === 'true') {
150
+ meta.pinned = true;
151
+ }
152
+ }
153
+ return meta;
154
+ }
155
+ function formatMetaLine(meta) {
156
+ const date = (meta.now ?? new Date()).toISOString().slice(0, 10);
157
+ const parts = [`type=${meta.type ?? 'convention'}`, `updated=${date}`];
158
+ if (meta.refs && meta.refs.length > 0)
159
+ parts.push(`refs=${meta.refs.join(';')}`);
160
+ if (meta.pinned)
161
+ parts.push('pin=true');
162
+ return `<!-- forge: ${parts.join(', ')} -->`;
163
+ }
108
164
  /**
109
165
  * Load both global and project memory. Project sections override global
110
166
  * sections with the same heading. Order: global first (oldest first),
@@ -164,70 +220,117 @@ export async function loadMemory(cwd) {
164
220
  const totalBytes = sources.reduce((sum, s) => sum + s.bytes, 0);
165
221
  return { sections: allSections, sources, totalBytes };
166
222
  }
223
+ /** Cap on bytes spent on pinned sections when the file does not fit the budget. */
224
+ const PIN_BUDGET_BYTES = 2_000;
225
+ /** Cap on bytes spent on the section index. */
226
+ const INDEX_BUDGET_BYTES = 1_000;
227
+ /** Below this many bytes an index is not worth its header; skip it. */
228
+ const MIN_INDEX_BYTES = 60;
167
229
  /**
168
- * Compose a memory injection for the system prompt. Truncates to fit the
169
- * budget by dropping the OLDEST sections first (lowest updatedAt). Returns
170
- * the assembled markdown plus diagnostics.
230
+ * Compose a memory injection for the system prompt.
231
+ *
232
+ * When every section fits the budget, all are injected in file order. When
233
+ * not: pinned sections first (capped), a one-line-per-section index so the
234
+ * model knows what else exists (`memory_read` fetches it), then the sections
235
+ * most relevant to `opts.task` that still fit, best last (closest to the
236
+ * prompt). Everything left out lands in `dropped`. Output never exceeds
237
+ * `budgetBytes`.
171
238
  */
172
- export function injectMemory(loaded, budgetBytes = DEFAULT_INJECTION_BUDGET) {
173
- if (loaded.sections.length === 0) {
239
+ export function injectMemory(loaded, budgetBytes = DEFAULT_INJECTION_BUDGET, opts = {}) {
240
+ const all = loaded.sections;
241
+ if (all.length === 0) {
174
242
  return { markdown: '', sections: [], dropped: [], found: false };
175
243
  }
176
- // Always keep project sections; drop global oldest first.
177
- const all = [...loaded.sections];
178
- const dropped = [];
179
- let kept = [];
180
- // Take sections from the END (newest) downward, dropping older entries
181
- // until the kept set fits. This is the right semantics for "keep the
182
- // most recent / relevant content".
183
- for (let i = all.length - 1; i >= 0; i--) {
184
- const next = all[i];
185
- const trialSize = memoryBytes([...kept, next]);
186
- if (trialSize <= budgetBytes) {
187
- kept.unshift(next);
188
- }
189
- else if (kept.length > 0) {
190
- // Already over budget by the same accounting as the check above:
191
- // nothing more will fit (older sections only get evaluated with the
192
- // same growing `kept` set), so stop here.
193
- break;
194
- }
195
- else {
196
- // Nothing kept yet and this section alone is too big: skip it.
197
- dropped.push(next);
244
+ const stale = opts.stale;
245
+ if (memoryBytes(all, stale) <= budgetBytes) {
246
+ return { markdown: formatSections(all, stale), sections: [...all], dropped: [], found: true };
247
+ }
248
+ const ranked = scoreSections(all, opts.task ?? '').map((r) => r.section);
249
+ const pins = [];
250
+ let used = 0;
251
+ for (const s of ranked.filter((r) => r.pinned)) {
252
+ const size = sectionBytes(s, stale) + 2;
253
+ // A pin bigger than the whole pin cap can never be guaranteed; it competes as a ranked match below.
254
+ if (size <= PIN_BUDGET_BYTES && used + size <= PIN_BUDGET_BYTES && used + size <= budgetBytes) {
255
+ pins.push(s);
256
+ used += size;
198
257
  }
199
258
  }
200
- for (const s of all) {
201
- if (!kept.includes(s))
202
- dropped.push(s);
259
+ const indexCap = Math.min(INDEX_BUDGET_BYTES, Math.floor(budgetBytes * 0.125), budgetBytes - used);
260
+ const index = buildIndex(all, indexCap);
261
+ if (index)
262
+ used += Buffer.byteLength(index, 'utf8') + 2;
263
+ const matches = [];
264
+ for (const s of ranked.filter((r) => !pins.includes(r) && (!r.pinned || sectionBytes(r, stale) + 2 > PIN_BUDGET_BYTES))) {
265
+ const size = sectionBytes(s, stale) + 2;
266
+ if (used + size <= budgetBytes) {
267
+ matches.push(s);
268
+ used += size;
269
+ }
203
270
  }
204
- const markdown = kept.length === 0 ? '' : formatSections(kept);
271
+ // Best match last, closest to the user's prompt.
272
+ const kept = [...pins, ...matches.reverse()];
273
+ const dropped = all.filter((s) => !kept.includes(s));
274
+ const markdown = [index, ...kept.map((s) => formatSection(s, stale))].filter((p) => p.length > 0).join('\n\n');
205
275
  return { markdown, sections: kept, dropped, found: true };
206
276
  }
207
- function sectionBytes(s) {
208
- // Approximate byte length: heading + 2 newlines + body.
209
- return Buffer.byteLength(`## ${s.heading}\n\n${s.body}`, 'utf8');
277
+ /** `## Memory index` with one `- heading (type)` line per section, within `capBytes`; '' if it cannot fit. */
278
+ function buildIndex(sections, capBytes) {
279
+ if (capBytes < MIN_INDEX_BYTES)
280
+ return '';
281
+ const header = '## Memory index\n\n';
282
+ const lines = sections.map((s) => `- ${s.heading} (${s.type})`);
283
+ const render = (shown, hidden) => header + [...shown, ...(hidden > 0 ? [`- …and ${hidden} more`] : [])].join('\n');
284
+ const fits = (text) => Buffer.byteLength(text, 'utf8') + 2 <= capBytes;
285
+ if (fits(render(lines, 0)))
286
+ return render(lines, 0);
287
+ const shown = [];
288
+ for (const line of lines) {
289
+ if (!fits(render([...shown, line], lines.length - shown.length - 1)))
290
+ break;
291
+ shown.push(line);
292
+ }
293
+ const text = render(shown, lines.length - shown.length);
294
+ return fits(text) ? text : '';
295
+ }
296
+ function sectionBytes(s, stale) {
297
+ // Approximate byte length: heading (plus stale marker) + 2 newlines + body.
298
+ return Buffer.byteLength(formatSection(s, stale), 'utf8');
299
+ }
300
+ function memoryBytes(sections, stale) {
301
+ return sections.reduce((sum, s) => sum + sectionBytes(s, stale) + 2, 0); // +2 for separating blank line
210
302
  }
211
- function memoryBytes(sections) {
212
- return sections.reduce((sum, s) => sum + sectionBytes(s) + 2, 0); // +2 for separating blank line
303
+ function formatSection(s, stale) {
304
+ const reasons = stale?.get(staleKey(s));
305
+ const marker = reasons ? ` (possibly stale: ${reasons.join('; ')})` : '';
306
+ return `## ${s.heading}${marker}\n\n${s.body}`;
213
307
  }
214
- function formatSections(sections) {
215
- return sections
216
- .map((s) => `## ${s.heading}\n\n${s.body}`)
217
- .join('\n\n');
308
+ function formatSections(sections, stale) {
309
+ return sections.map((s) => formatSection(s, stale)).join('\n\n');
218
310
  }
219
311
  /**
220
312
  * Compose the system-prompt block for memory injection. Returns the empty
221
313
  * string when no memory is found (so the caller can skip the block).
222
314
  */
223
- export async function buildMemoryBlock(cwd, budgetBytes) {
315
+ export async function buildMemoryBlock(cwd, budgetBytes, opts = {}) {
224
316
  const loaded = await loadMemory(cwd);
225
317
  if (loaded.sections.length === 0)
226
318
  return '';
227
- const injection = injectMemory(loaded, budgetBytes);
319
+ let stale = new Map();
320
+ try {
321
+ stale = await checkStale(loaded.sections, (await findProjectMemoryRoot(cwd)) ?? cwd);
322
+ }
323
+ catch {
324
+ // A failed staleness check must never block memory injection.
325
+ }
326
+ const injection = injectMemory(loaded, budgetBytes, { ...opts, stale });
228
327
  if (!injection.found)
229
328
  return '';
230
- return `<project-memory source="${cwd}">\n${injection.markdown}\n</project-memory>`;
329
+ const hasMarked = injection.sections.some((s) => stale.has(staleKey(s)));
330
+ const preamble = hasMarked
331
+ ? 'Entries marked (possibly stale) name things that may no longer exist; verify before relying on them.\n\n'
332
+ : '';
333
+ return `<project-memory source="${cwd}">\n${preamble}${injection.markdown}\n</project-memory>`;
231
334
  }
232
335
  // Mutation helpers: used by the memory_write tool and the test suite.
233
336
  /**
@@ -259,17 +362,20 @@ export async function writeMemoryFile(absPath, content) {
259
362
  * Append a new section (or replace an existing section with the same
260
363
  * heading) inside a memory file. Returns the new file contents.
261
364
  */
262
- export function composeSectionUpdate(current, heading, body) {
263
- const header = `## ${heading}`;
365
+ export function composeSectionUpdate(current, heading, body, meta) {
366
+ const title = heading.trim();
367
+ const header = `## ${title}`;
368
+ const content = meta ? `${formatMetaLine(meta)}\n${body.trim()}` : body.trim();
264
369
  if (!current.trim()) {
265
- return `${header}\n\n${body.trim()}\n`;
370
+ return `${header}\n\n${content}\n`;
266
371
  }
267
372
  const lines = current.split(/\r?\n/);
268
- const idx = lines.findIndex((l) => /^##\s+/.test(l) && l.slice(3).trim() === heading);
373
+ const wanted = normalizeHeading(title);
374
+ const idx = lines.findIndex((l) => /^##\s+/.test(l) && normalizeHeading(l.slice(3)) === wanted);
269
375
  if (idx === -1) {
270
376
  // Append.
271
377
  const trimmed = current.replace(/\s+$/, '');
272
- return `${trimmed}\n\n${header}\n\n${body.trim()}\n`;
378
+ return `${trimmed}\n\n${header}\n\n${content}\n`;
273
379
  }
274
380
  // Replace existing section: drop from `idx` to next `##` or EOF.
275
381
  let end = lines.length;
@@ -281,28 +387,88 @@ export function composeSectionUpdate(current, heading, body) {
281
387
  }
282
388
  const before = lines.slice(0, idx).join('\n').replace(/\s+$/, '');
283
389
  const after = lines.slice(end).join('\n').replace(/^\s+/, '');
284
- const middle = `${header}\n\n${body.trim()}\n`;
390
+ const middle = `${header}\n\n${content}\n`;
285
391
  return [before, middle, after].filter((s) => s.length > 0).join('\n\n') + '\n';
286
392
  }
393
+ /** Split file text into its preamble and raw `## ` chunks, keeping each chunk verbatim. */
394
+ function splitChunks(content) {
395
+ const preamble = [];
396
+ const chunks = [];
397
+ for (const line of content.split(/\r?\n/)) {
398
+ const h2 = /^##\s+(.+?)\s*$/.exec(line);
399
+ if (h2)
400
+ chunks.push({ heading: h2[1], lines: [line] });
401
+ else if (chunks.length > 0)
402
+ chunks[chunks.length - 1].lines.push(line);
403
+ else
404
+ preamble.push(line);
405
+ }
406
+ return {
407
+ preamble: preamble.join('\n').trim(),
408
+ chunks: chunks.map((c) => ({ heading: c.heading, raw: c.lines.join('\n').trim() })),
409
+ };
410
+ }
411
+ function joinChunks(preamble, chunks) {
412
+ const parts = [preamble, ...chunks.map((c) => c.raw)].filter((p) => p.length > 0);
413
+ return parts.length > 0 ? `${parts.join('\n\n')}\n` : '';
414
+ }
415
+ /**
416
+ * Remove the section whose heading matches `heading` (case and spacing
417
+ * ignored). Returns the file unchanged with `removed: false` when absent.
418
+ */
419
+ export function removeSection(current, heading) {
420
+ const { preamble, chunks } = splitChunks(current);
421
+ const wanted = normalizeHeading(heading);
422
+ const kept = chunks.filter((c) => normalizeHeading(c.heading) !== wanted);
423
+ if (kept.length === chunks.length)
424
+ return { content: current, removed: false };
425
+ return { content: joinChunks(preamble, kept), removed: true };
426
+ }
427
+ /**
428
+ * Bring a memory file under `maxBytes` by dropping whole sections, lowest
429
+ * ranked first: lowest type weight, then oldest `updated`, then earliest in
430
+ * the file. Pinned sections and `keepHeading` are never dropped. If only
431
+ * those remain, the file is returned over the cap rather than losing them.
432
+ */
433
+ export function capMemory(content, maxBytes, keepHeading) {
434
+ if (Buffer.byteLength(content, 'utf8') <= maxBytes)
435
+ return { content, dropped: [] };
436
+ const { preamble, chunks } = splitChunks(content);
437
+ const keep = normalizeHeading(keepHeading);
438
+ const candidates = chunks
439
+ .map((chunk, index) => ({ chunk, index, section: parseMemory(chunk.raw, '<cap>', 0)[0] }))
440
+ .filter((c) => !c.section.pinned && normalizeHeading(c.chunk.heading) !== keep)
441
+ .sort((a, b) => TYPE_WEIGHT[a.section.type] - TYPE_WEIGHT[b.section.type] ||
442
+ a.section.updatedAt - b.section.updatedAt ||
443
+ a.index - b.index);
444
+ const removed = new Set();
445
+ const dropped = [];
446
+ const render = () => joinChunks(preamble, chunks.filter((_, i) => !removed.has(i)));
447
+ let out = render();
448
+ for (const c of candidates) {
449
+ if (Buffer.byteLength(out, 'utf8') <= maxBytes)
450
+ break;
451
+ removed.add(c.index);
452
+ dropped.push(c.chunk.heading);
453
+ out = render();
454
+ }
455
+ return { content: out, dropped };
456
+ }
287
457
  /**
288
458
  * Truncate a memory file to fit under `maxBytes` by dropping the oldest
289
- * sections. Returns the new content. Used by an auto-trim job (not yet
290
- * wired into the agent: see docs/ROADMAP.md).
459
+ * sections (file order). Sections are kept verbatim, metadata comment
460
+ * included. Returns the new content.
291
461
  */
292
462
  export function trimToBytes(content, maxBytes) {
293
463
  const totalBytes = Buffer.byteLength(content, 'utf8');
294
464
  if (totalBytes <= maxBytes)
295
465
  return content;
296
- const sections = parseMemory(content, '<trim>', Date.now());
297
- if (sections.length === 0)
298
- return '';
466
+ let kept = splitChunks(content).chunks;
299
467
  // Drop oldest first.
300
- let kept = sections;
301
- while (kept.length > 0 && Buffer.byteLength(formatSections(kept), 'utf8') > maxBytes) {
468
+ while (kept.length > 0 && Buffer.byteLength(joinChunks('', kept), 'utf8') > maxBytes) {
302
469
  kept = kept.slice(1);
303
470
  }
304
- return formatSections(kept) + (kept.length > 0 ? '\n' : '');
471
+ return joinChunks('', kept);
305
472
  }
306
473
  // Re-export for tests / downstream consumers.
307
474
  export { PROJECT_MEMORY_DIR as _PROJECT_MEMORY_DIR, PROJECT_MEMORY_FILE as _PROJECT_MEMORY_FILE };
308
- //# sourceMappingURL=memory-store.js.map
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Relevance scoring for memory sections. Keyword-only (BM25-style term
3
+ * overlap), no embeddings: at the 8 KB injection scale it is enough, and it
4
+ * keeps Forge provider-agnostic with no extra service.
5
+ */
6
+ import type { MemorySection, MemoryType } from './memory-store.js';
7
+ /** Higher = more costly to ignore. A user correction outranks a plain convention. */
8
+ export declare const TYPE_WEIGHT: Record<MemoryType, number>;
9
+ /** Lowercase alphanumeric tokens of length >= 3, minus stopwords. Duplicates are kept. */
10
+ export declare function tokenize(text: string): string[];
11
+ export interface ScoredSection {
12
+ section: MemorySection;
13
+ score: number;
14
+ }
15
+ /**
16
+ * Score every section against `task`, best first. Ties break on type weight,
17
+ * then newer `updatedAt`, then later position in the input (so with no task
18
+ * the order degrades to v1's "later sections win").
19
+ */
20
+ export declare function scoreSections(sections: MemorySection[], task: string): ScoredSection[];
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Relevance scoring for memory sections. Keyword-only (BM25-style term
3
+ * overlap), no embeddings: at the 8 KB injection scale it is enough, and it
4
+ * keeps Forge provider-agnostic with no extra service.
5
+ */
6
+ const STOPWORDS = new Set([
7
+ 'the', 'and', 'for', 'with', 'that', 'this', 'from', 'are', 'was', 'were', 'will', 'can', 'not',
8
+ 'you', 'your', 'has', 'have', 'had', 'but', 'all', 'any', 'how', 'what', 'when', 'where', 'which',
9
+ 'who', 'why', 'use', 'using', 'into', 'out', 'its', 'our', 'then', 'than', 'too', 'just', 'let',
10
+ ]);
11
+ /** Higher = more costly to ignore. A user correction outranks a plain convention. */
12
+ export const TYPE_WEIGHT = {
13
+ correction: 1.5,
14
+ decision: 1.2,
15
+ convention: 1,
16
+ reference: 0.8,
17
+ };
18
+ const HEADING_WEIGHT = 2;
19
+ const K1 = 1.2;
20
+ const B = 0.75;
21
+ /** Lowercase alphanumeric tokens of length >= 3, minus stopwords. Duplicates are kept. */
22
+ export function tokenize(text) {
23
+ return (text.toLowerCase().match(/[\p{L}\p{N}]+/gu) ?? []).filter((t) => t.length >= 3 && !STOPWORDS.has(t));
24
+ }
25
+ /**
26
+ * Score every section against `task`, best first. Ties break on type weight,
27
+ * then newer `updatedAt`, then later position in the input (so with no task
28
+ * the order degrades to v1's "later sections win").
29
+ */
30
+ export function scoreSections(sections, task) {
31
+ const query = [...new Set(tokenize(task))];
32
+ const docs = sections.map((s) => {
33
+ const heading = tokenize(s.heading);
34
+ const body = tokenize(s.body);
35
+ const tf = new Map();
36
+ for (const t of heading)
37
+ tf.set(t, (tf.get(t) ?? 0) + HEADING_WEIGHT);
38
+ for (const t of body)
39
+ tf.set(t, (tf.get(t) ?? 0) + 1);
40
+ return { tf, length: heading.length * HEADING_WEIGHT + body.length };
41
+ });
42
+ const avgLen = docs.reduce((sum, d) => sum + d.length, 0) / Math.max(docs.length, 1) || 1;
43
+ const df = new Map();
44
+ for (const term of query)
45
+ df.set(term, docs.filter((d) => d.tf.has(term)).length);
46
+ const scored = sections.map((section, index) => {
47
+ const doc = docs[index];
48
+ let score = 0;
49
+ for (const term of query) {
50
+ const f = doc.tf.get(term) ?? 0;
51
+ if (f === 0)
52
+ continue;
53
+ const n = df.get(term);
54
+ const idf = Math.log(1 + (docs.length - n + 0.5) / (n + 0.5));
55
+ score += (idf * (f * (K1 + 1))) / (f + K1 * (1 - B + (B * doc.length) / avgLen));
56
+ }
57
+ return { section, score: score * TYPE_WEIGHT[section.type], index };
58
+ });
59
+ scored.sort((a, b) => b.score - a.score ||
60
+ TYPE_WEIGHT[b.section.type] - TYPE_WEIGHT[a.section.type] ||
61
+ b.section.updatedAt - a.section.updatedAt ||
62
+ b.index - a.index);
63
+ return scored.map(({ section, score }) => ({ section, score }));
64
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Staleness check for memory sections. A section is stale only when a ref it
3
+ * declares (`refs=` in its metadata comment) no longer exists. Age alone never
4
+ * counts: a convention can stay true for years.
5
+ */
6
+ import type { MemorySection } from './memory-store.js';
7
+ export interface StaleOptions {
8
+ /** Checks a `path#symbol` ref. When absent only the path is checked. */
9
+ symbolExists?: (file: string, symbol: string) => Promise<boolean> | boolean;
10
+ }
11
+ /** Key a section in the stale map. */
12
+ export declare function staleKey(s: Pick<MemorySection, 'source' | 'heading'>): string;
13
+ /**
14
+ * Returns reasons keyed by {@link staleKey} for every section with at least
15
+ * one dead ref. A ref that resolves outside `root` is reported without touching
16
+ * the filesystem, so a memory file cannot be used to probe other paths.
17
+ */
18
+ export declare function checkStale(sections: MemorySection[], root: string, opts?: StaleOptions): Promise<Map<string, string[]>>;
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Staleness check for memory sections. A section is stale only when a ref it
3
+ * declares (`refs=` in its metadata comment) no longer exists. Age alone never
4
+ * counts: a convention can stay true for years.
5
+ */
6
+ import { stat } from 'node:fs/promises';
7
+ import path from 'node:path';
8
+ /** Key a section in the stale map. */
9
+ export function staleKey(s) {
10
+ return `${s.source}#${s.heading}`;
11
+ }
12
+ /**
13
+ * Returns reasons keyed by {@link staleKey} for every section with at least
14
+ * one dead ref. A ref that resolves outside `root` is reported without touching
15
+ * the filesystem, so a memory file cannot be used to probe other paths.
16
+ */
17
+ export async function checkStale(sections, root, opts = {}) {
18
+ const out = new Map();
19
+ for (const section of sections) {
20
+ const reasons = [];
21
+ for (const ref of section.refs) {
22
+ const hash = ref.indexOf('#');
23
+ const file = hash === -1 ? ref : ref.slice(0, hash);
24
+ const symbol = hash === -1 ? '' : ref.slice(hash + 1);
25
+ const rel = path.relative(root, path.resolve(root, file));
26
+ if (path.isAbsolute(file) || rel.startsWith('..') || path.isAbsolute(rel)) {
27
+ reasons.push(`${ref} is outside the project`);
28
+ continue;
29
+ }
30
+ try {
31
+ await stat(path.resolve(root, file));
32
+ }
33
+ catch {
34
+ reasons.push(`${file} no longer exists`);
35
+ continue;
36
+ }
37
+ if (symbol && opts.symbolExists && !(await opts.symbolExists(file, symbol))) {
38
+ reasons.push(`${ref} not found`);
39
+ }
40
+ }
41
+ if (reasons.length > 0)
42
+ out.set(staleKey(section), reasons);
43
+ }
44
+ return out;
45
+ }
@@ -13,4 +13,3 @@ export declare function resolveModelAlias(name: string, aliases: ModelAliases |
13
13
  };
14
14
  /** Human-readable alias list for `forge config`; '' when there are none. */
15
15
  export declare function formatAliasTable(aliases: ModelAliases | undefined): string;
16
- //# sourceMappingURL=model-alias.d.ts.map
@@ -47,4 +47,3 @@ export function formatAliasTable(aliases) {
47
47
  const rows = names.map((n) => ` ${n.padEnd(width)} → ${aliases[n]}`);
48
48
  return ['Model aliases (~/.forge/config.json):', ...rows].join('\n');
49
49
  }
50
- //# sourceMappingURL=model-alias.js.map
@@ -11,4 +11,3 @@ export interface ModelCatalogEntry {
11
11
  * short and prefer aliases; anything else is typed as a custom id.
12
12
  */
13
13
  export declare const MODEL_CATALOG: Record<ProviderName, ModelCatalogEntry[]>;
14
- //# sourceMappingURL=model-catalog.d.ts.map
@@ -55,4 +55,3 @@ export const MODEL_CATALOG = {
55
55
  lmstudio: [{ id: 'local-model', label: 'Whatever model is loaded in LM Studio' }],
56
56
  'openai-compatible': [{ id: 'default', label: 'Model id your endpoint expects' }],
57
57
  };
58
- //# sourceMappingURL=model-catalog.js.map
@@ -13,4 +13,3 @@ export declare const LISTABLE_PROVIDERS: readonly ProviderName[];
13
13
  export declare function parseModels(provider: ProviderName, body: unknown): ModelCatalogEntry[];
14
14
  /** Asks the provider which models this key can use. Throws a readable message when it cannot. */
15
15
  export declare function fetchProviderModels(provider: ProviderName, opts?: FetchModelsOptions): Promise<ModelCatalogEntry[]>;
16
- //# sourceMappingURL=model-list.d.ts.map
@@ -85,4 +85,3 @@ export async function fetchProviderModels(provider, opts = {}) {
85
85
  throw new Error('The provider returned no models.');
86
86
  return models;
87
87
  }
88
- //# sourceMappingURL=model-list.js.map
@@ -4,4 +4,3 @@ import type { SpecialistId } from './types.js';
4
4
  export declare const BOARD_TOOL_NAMES: readonly ["board_post", "board_read"];
5
5
  /** Board tools for one specialist; `from` and the read cursor live in the closure. */
6
6
  export declare function createBoardTools(board: MessageBoard, selfId: SpecialistId): ToolDefinition[];
7
- //# sourceMappingURL=board-tools.d.ts.map
@@ -46,4 +46,3 @@ export function createBoardTools(board, selfId) {
46
46
  };
47
47
  return [post, read];
48
48
  }
49
- //# sourceMappingURL=board-tools.js.map
@@ -38,4 +38,3 @@ export declare class MessageBoard {
38
38
  /** How many early messages the ring buffer has dropped. */
39
39
  get trimmed(): number;
40
40
  }
41
- //# sourceMappingURL=board.d.ts.map
@@ -39,4 +39,3 @@ export class MessageBoard {
39
39
  return this.dropped;
40
40
  }
41
41
  }
42
- //# sourceMappingURL=board.js.map
@@ -12,4 +12,3 @@ export { MessageBoard, DEFAULT_BOARD_CAP, type BoardMessage } from './board.js';
12
12
  export { createBoardTools, BOARD_TOOL_NAMES } from './board-tools.js';
13
13
  export { registerSpecialist, unregisterSpecialist, listSpecialists, getRegisteredSpecialist, loadSpecialistsFromConfig, saveSpecialistsToConfig, validateSpecialistConfig, defaultConfigPath, _resetSpecialistsForTests, _customSpecialistCount, type CustomSpecialistConfig, type SpecialistsConfigFile, type LoadSpecialistsResult, } from './specialists-registry.js';
14
14
  export type { BuiltInSpecialistId, OrchestratorPlan, OrchestratorRunResult, OrchestratorStep, OrchestratorStepResult, SpecialistConfig, SpecialistId, } from './types.js';
15
- //# sourceMappingURL=index.d.ts.map
@@ -11,4 +11,3 @@ export { runOrchestrator, runRouter, groupStepsByParallel, resolveEffectiveModel
11
11
  export { MessageBoard, DEFAULT_BOARD_CAP } from './board.js';
12
12
  export { createBoardTools, BOARD_TOOL_NAMES } from './board-tools.js';
13
13
  export { registerSpecialist, unregisterSpecialist, listSpecialists, getRegisteredSpecialist, loadSpecialistsFromConfig, saveSpecialistsToConfig, validateSpecialistConfig, defaultConfigPath, _resetSpecialistsForTests, _customSpecialistCount, } from './specialists-registry.js';
14
- //# sourceMappingURL=index.js.map
@@ -233,4 +233,3 @@ export declare function runRouter(args: {
233
233
  * without going through `Agent.run()`.
234
234
  */
235
235
  export declare function resolveEffectiveModel(spec: SpecialistConfig, baseModel: ModelConfig): ModelConfig;
236
- //# sourceMappingURL=orchestrator.d.ts.map
@@ -494,4 +494,3 @@ async function runSpecialist(args) {
494
494
  const agent = new Agent(config);
495
495
  return agent.run({ task: args.task });
496
496
  }
497
- //# sourceMappingURL=orchestrator.js.map
@@ -24,4 +24,3 @@ export declare const ROUTER_SYSTEM_PROMPT = "You are the IKarmic Forge ORCHESTRA
24
24
  * user's task to the 'code' specialist - better than crashing.
25
25
  */
26
26
  export declare function parseRouterOutput(raw: string, fallbackTask: string): OrchestratorPlan;
27
- //# sourceMappingURL=router.d.ts.map
@@ -120,4 +120,3 @@ function fallbackPlan(task, reason = 'unparseable') {
120
120
  steps: [{ specialist: 'code', task }],
121
121
  };
122
122
  }
123
- //# sourceMappingURL=router.js.map