@fusengine/harness 0.1.90 → 0.1.92

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 (422) hide show
  1. package/dist/adapters/claude/index.d.mts +1 -1
  2. package/dist/adapters/claude/index.mjs +1 -1
  3. package/dist/adapters/cline/index.mjs +1 -1
  4. package/dist/adapters/codex/index.d.mts +2 -2
  5. package/dist/adapters/codex/index.mjs +3 -2
  6. package/dist/adapters/cursor/index.d.mts +15 -28
  7. package/dist/adapters/cursor/index.mjs +35 -46
  8. package/dist/adapters/gemini/index.mjs +1 -1
  9. package/dist/adapters/hermes/index.d.mts +1 -1
  10. package/dist/adapters/hermes/index.mjs +1 -1
  11. package/dist/adapters/kimi/index.d.mts +1 -1
  12. package/dist/adapters/kimi/index.mjs +1 -1
  13. package/dist/{claude-5bC8TDAz.mjs → claude-Ckv2_TgP.mjs} +3 -43
  14. package/dist/cli/bin.mjs +429 -16
  15. package/dist/cli/index.mjs +1 -1
  16. package/dist/command-string-CALMTnwN.mjs +42 -0
  17. package/dist/{evaluate-C1VSI5pb.mjs → evaluate-CMiIqHeH.mjs} +261 -13
  18. package/dist/{handle-CBaOcFZq.mjs → handle-BF1dZFjY.mjs} +1194 -144
  19. package/dist/{hermes-kP6NUQlG.mjs → hermes-B9-p_3IF.mjs} +3 -2
  20. package/dist/{index-DBgnIZn9.d.mts → index-B8PG82su.d.mts} +1 -1
  21. package/dist/{index-D4C2-IIc.d.mts → index-SmKYkk2N.d.mts} +1 -1
  22. package/dist/index.d.mts +2 -2
  23. package/dist/index.mjs +2 -2
  24. package/dist/init/index.d.mts +1 -1
  25. package/dist/init/index.mjs +1 -1
  26. package/dist/{kimi-D6X7b8wF.mjs → kimi-G2wcSh5-.mjs} +3 -2
  27. package/dist/normalize-Dy8g9Ybl.mjs +397 -0
  28. package/dist/policy/index.d.mts +1 -1
  29. package/dist/policy/index.mjs +2 -2
  30. package/dist/prompt/index.d.mts +1 -1
  31. package/dist/{run-CdIlUtjv.mjs → run-DkrzC0gb.mjs} +1 -1
  32. package/dist/{run-TAXNRSpD.mjs → run-GHAMqcOn.mjs} +26 -3
  33. package/dist/runtime/index.d.mts +37 -16
  34. package/dist/runtime/index.mjs +2 -2
  35. package/dist/{types-C0-igRKr.d.mts → types-Bh0jEF3_.d.mts} +2 -0
  36. package/dist/{validate-DZFtAbLP.mjs → validate-KjZ1X9tH.mjs} +1 -1
  37. package/package.json +3 -2
  38. package/src/adapters/claude/index.ts +92 -0
  39. package/src/adapters/cline/index.ts +36 -0
  40. package/src/adapters/codex/apply-patch.ts +94 -0
  41. package/src/adapters/codex/index.ts +84 -0
  42. package/src/adapters/codex/permission-mode.ts +16 -0
  43. package/src/adapters/cursor/context-budget.ts +144 -0
  44. package/src/adapters/cursor/context-limit.ts +115 -0
  45. package/src/adapters/cursor/context.ts +65 -0
  46. package/src/adapters/cursor/events.ts +38 -0
  47. package/src/adapters/cursor/index.ts +43 -0
  48. package/src/adapters/cursor/interfaces/context-budget.ts +25 -0
  49. package/src/adapters/cursor/interfaces/types.ts +88 -0
  50. package/src/adapters/cursor/native-response.ts +39 -0
  51. package/src/adapters/cursor/native-schemas.ts +161 -0
  52. package/src/adapters/cursor/normalize.ts +188 -0
  53. package/src/adapters/cursor/plugin-root.ts +103 -0
  54. package/src/adapters/cursor/respond.ts +170 -0
  55. package/src/adapters/gemini/index.ts +36 -0
  56. package/src/adapters/hermes/index.ts +55 -0
  57. package/src/adapters/hermes/interfaces/types.ts +26 -0
  58. package/src/adapters/kimi/index.ts +96 -0
  59. package/src/adapters/kimi/interfaces/types.ts +55 -0
  60. package/src/cache/compact.ts +55 -0
  61. package/src/cache/index.ts +5 -0
  62. package/src/cache/io.ts +38 -0
  63. package/src/cache/mcp-response.ts +39 -0
  64. package/src/cache/mcp-store.ts +70 -0
  65. package/src/cache/store.ts +93 -0
  66. package/src/changelog/fetch.ts +79 -0
  67. package/src/cli/bin.ts +100 -0
  68. package/src/cli/cursor-event-scanner.ts +175 -0
  69. package/src/cli/cursor-stdin-reader.ts +65 -0
  70. package/src/cli/doctor.ts +96 -0
  71. package/src/cli/hook-io.ts +130 -0
  72. package/src/cli/hook-sound.ts +49 -0
  73. package/src/cli/index.ts +1 -0
  74. package/src/cli/json-primitive-scanner.ts +83 -0
  75. package/src/cli/run.ts +36 -0
  76. package/src/cli/scope.ts +31 -0
  77. package/src/codex-rules/build.ts +63 -0
  78. package/src/codex-rules/index.ts +8 -0
  79. package/src/codex-rules/mapping.ts +42 -0
  80. package/src/codex-rules/rules/absolute-paths.ts +52 -0
  81. package/src/codex-rules/rules/ask.ts +44 -0
  82. package/src/codex-rules/rules/chmod-ask.ts +57 -0
  83. package/src/codex-rules/rules/dd.ts +23 -0
  84. package/src/codex-rules/rules/diskutil.ts +33 -0
  85. package/src/codex-rules/rules/mkfs.ts +42 -0
  86. package/src/codex-rules/rules/privilege.ts +49 -0
  87. package/src/codex-rules/rules/remote-fetch.ts +31 -0
  88. package/src/codex-rules/rules/rm-variants.ts +58 -0
  89. package/src/codex-rules/rules/single-commands.ts +57 -0
  90. package/src/codex-rules/skip-list.ts +25 -0
  91. package/src/codex-rules/starlark.ts +46 -0
  92. package/src/codex-rules/types.ts +36 -0
  93. package/src/config/dotenv.ts +55 -0
  94. package/src/config/env.ts +10 -0
  95. package/src/config/home-dir.ts +36 -0
  96. package/src/config/index.ts +5 -0
  97. package/src/config/layout.ts +52 -0
  98. package/src/config/limits.ts +56 -0
  99. package/src/config/ttl.ts +30 -0
  100. package/src/detect/harness.ts +73 -0
  101. package/src/detect/index.ts +1 -0
  102. package/src/detect/interfaces/types.ts +18 -0
  103. package/src/freshness/agent-evidence-record.ts +95 -0
  104. package/src/freshness/agent-evidence.ts +95 -0
  105. package/src/freshness/codex-spawn-evidence.ts +91 -0
  106. package/src/freshness/doc-helpers.ts +90 -0
  107. package/src/freshness/evidence-harvest-io.ts +72 -0
  108. package/src/freshness/evidence-harvest.ts +61 -0
  109. package/src/freshness/explore-tools.ts +95 -0
  110. package/src/freshness/index.ts +2 -0
  111. package/src/freshness/query-framework.ts +73 -0
  112. package/src/freshness/ref-evidence.ts +49 -0
  113. package/src/freshness/ref-journal.ts +98 -0
  114. package/src/freshness/trivial-edit-counter.ts +30 -0
  115. package/src/index.ts +11 -0
  116. package/src/init/index.ts +2 -0
  117. package/src/init/run.ts +32 -0
  118. package/src/init/templates.ts +75 -0
  119. package/src/memory/gitignore.ts +14 -0
  120. package/src/memory/index.ts +3 -0
  121. package/src/memory/registry.ts +39 -0
  122. package/src/memory/session-roots.ts +99 -0
  123. package/src/memory/state.ts +58 -0
  124. package/src/policy/apex-authorization.ts +100 -0
  125. package/src/policy/apex-gates.ts +91 -0
  126. package/src/policy/apex-target-steps.ts +73 -0
  127. package/src/policy/apex-target.ts +49 -0
  128. package/src/policy/apex-task-context.ts +81 -0
  129. package/src/policy/apex.ts +94 -0
  130. package/src/policy/cartographer/build-tree.ts +69 -0
  131. package/src/policy/cartographer/describe.ts +53 -0
  132. package/src/policy/cartographer/entry.ts +38 -0
  133. package/src/policy/cartographer/frontmatter.ts +48 -0
  134. package/src/policy/cartographer/indicators.ts +34 -0
  135. package/src/policy/claude-md-context.ts +66 -0
  136. package/src/policy/conventions/index.ts +11 -0
  137. package/src/policy/conventions/interfaces.ts +71 -0
  138. package/src/policy/conventions/langs.ts +53 -0
  139. package/src/policy/conventions/query.ts +49 -0
  140. package/src/policy/conventions/react-hooks.ts +18 -0
  141. package/src/policy/conventions/store-or-query.ts +21 -0
  142. package/src/policy/conventions/stores.ts +56 -0
  143. package/src/policy/conventions/strip.ts +87 -0
  144. package/src/policy/conventions/verdict.ts +38 -0
  145. package/src/policy/creation-intent.ts +11 -0
  146. package/src/policy/deny-loop.ts +88 -0
  147. package/src/policy/design/allowed-write.ts +28 -0
  148. package/src/policy/design/content-checks.ts +45 -0
  149. package/src/policy/design/corpus-fs.ts +21 -0
  150. package/src/policy/design/corpus-resolve-cache.ts +49 -0
  151. package/src/policy/design/corpus-resolve.ts +99 -0
  152. package/src/policy/design/corpus.ts +92 -0
  153. package/src/policy/design/design-system-rules.ts +45 -0
  154. package/src/policy/design/flag.ts +30 -0
  155. package/src/policy/design/gates-pipeline.ts +105 -0
  156. package/src/policy/design/gates.ts +87 -0
  157. package/src/policy/design/screenshot-tools.ts +24 -0
  158. package/src/policy/design/skill-evidence.ts +34 -0
  159. package/src/policy/design/skill-gate.ts +81 -0
  160. package/src/policy/design/skill-triggers.ts +51 -0
  161. package/src/policy/design/state.ts +92 -0
  162. package/src/policy/design/template-urls.ts +54 -0
  163. package/src/policy/design/transitions.ts +68 -0
  164. package/src/policy/detect-claude-md-project-type.ts +32 -0
  165. package/src/policy/detect-framework.ts +99 -0
  166. package/src/policy/detect-primitive-checks.ts +100 -0
  167. package/src/policy/detect-primitive-lib.ts +55 -0
  168. package/src/policy/detect-project.ts +99 -0
  169. package/src/policy/edit-outcome.ts +53 -0
  170. package/src/policy/evaluate.ts +81 -0
  171. package/src/policy/expert-agents.ts +60 -0
  172. package/src/policy/file-size-scope.ts +32 -0
  173. package/src/policy/file-size.ts +99 -0
  174. package/src/policy/framework-solid-exclude.ts +52 -0
  175. package/src/policy/framework-solid-extended.ts +93 -0
  176. package/src/policy/framework-solid-gates-systems.ts +99 -0
  177. package/src/policy/framework-solid-gates.ts +98 -0
  178. package/src/policy/framework-solid.ts +88 -0
  179. package/src/policy/gemini-mcp-gate.ts +83 -0
  180. package/src/policy/git-gates.ts +40 -0
  181. package/src/policy/guards/bash-command-anchor.ts +40 -0
  182. package/src/policy/guards/bash-write-patterns.ts +140 -0
  183. package/src/policy/guards/bash-write-redirects.ts +201 -0
  184. package/src/policy/guards/bash-write-safe-paths.ts +62 -0
  185. package/src/policy/guards/bash-write.ts +76 -0
  186. package/src/policy/guards/context.ts +12 -0
  187. package/src/policy/guards/index.ts +61 -0
  188. package/src/policy/guards/install.ts +22 -0
  189. package/src/policy/guards/interface-separation-ext.ts +41 -0
  190. package/src/policy/guards/interface-separation.ts +100 -0
  191. package/src/policy/guards/protected-path.ts +100 -0
  192. package/src/policy/guards/security.ts +97 -0
  193. package/src/policy/index.ts +19 -0
  194. package/src/policy/interfaces/types.ts +46 -0
  195. package/src/policy/js-gate-route.ts +51 -0
  196. package/src/policy/lessons/lesson-gate.ts +0 -0
  197. package/src/policy/lessons/trigger-index.ts +84 -0
  198. package/src/policy/lessons/types.ts +31 -0
  199. package/src/policy/module-layout.ts +31 -0
  200. package/src/policy/nearest-manifest.ts +75 -0
  201. package/src/policy/never-approval.ts +95 -0
  202. package/src/policy/patterns.ts +80 -0
  203. package/src/policy/seo/validate.ts +51 -0
  204. package/src/policy/shadcn-project.ts +22 -0
  205. package/src/policy/shadcn-skill-gate.ts +97 -0
  206. package/src/policy/shell-read-refs.ts +96 -0
  207. package/src/policy/skill-path.ts +46 -0
  208. package/src/policy/skill-patterns/laravel.ts +40 -0
  209. package/src/policy/skill-patterns/nextjs.ts +29 -0
  210. package/src/policy/skill-patterns/react.ts +27 -0
  211. package/src/policy/skill-patterns/shadcn-subskills.ts +33 -0
  212. package/src/policy/skill-patterns/shadcn.ts +66 -0
  213. package/src/policy/skill-patterns/swift.ts +30 -0
  214. package/src/policy/skill-patterns/tailwind.ts +40 -0
  215. package/src/policy/skill-source.ts +52 -0
  216. package/src/policy/skill-trigger-patterns.ts +33 -0
  217. package/src/policy/skill-triggers.ts +108 -0
  218. package/src/policy/tailwind-skill-gate.ts +63 -0
  219. package/src/policy/trivial-edits.ts +52 -0
  220. package/src/policy/verbosity.ts +39 -0
  221. package/src/prompt/index.ts +1 -0
  222. package/src/prompt/types.ts +36 -0
  223. package/src/refs/discover.ts +69 -0
  224. package/src/refs/frontmatter.ts +24 -0
  225. package/src/refs/index.ts +4 -0
  226. package/src/refs/loader.ts +59 -0
  227. package/src/refs/ref-key.ts +41 -0
  228. package/src/refs/router.ts +63 -0
  229. package/src/refs/types.ts +25 -0
  230. package/src/runtime/activity.ts +99 -0
  231. package/src/runtime/apply-patch-gate.ts +41 -0
  232. package/src/runtime/burst-window.ts +21 -0
  233. package/src/runtime/codex-shell-tool.ts +37 -0
  234. package/src/runtime/command-string.ts +37 -0
  235. package/src/runtime/confirm/codex-confirm.ts +126 -0
  236. package/src/runtime/confirm/codex-prompt-origin.ts +16 -0
  237. package/src/runtime/confirm/confirm-code.ts +33 -0
  238. package/src/runtime/confirm/confirm-gate.ts +65 -0
  239. package/src/runtime/confirm/confirm-irreversible.ts +32 -0
  240. package/src/runtime/confirm/confirm-pending.ts +34 -0
  241. package/src/runtime/confirm/confirm-state.ts +56 -0
  242. package/src/runtime/confirm/confirm-subagent.ts +112 -0
  243. package/src/runtime/confirm/confirm-submit.ts +38 -0
  244. package/src/runtime/core-guards-active.ts +84 -0
  245. package/src/runtime/core-guards-wired.ts +47 -0
  246. package/src/runtime/deny-loop-store.ts +94 -0
  247. package/src/runtime/deny-notice.ts +52 -0
  248. package/src/runtime/design-cache-resolve.ts +51 -0
  249. package/src/runtime/design-content-gate.ts +95 -0
  250. package/src/runtime/design-files-gate.ts +114 -0
  251. package/src/runtime/design-helpers.ts +93 -0
  252. package/src/runtime/design-lifecycle.ts +39 -0
  253. package/src/runtime/design-read-credit.ts +28 -0
  254. package/src/runtime/design.ts +116 -0
  255. package/src/runtime/dev-context.ts +43 -0
  256. package/src/runtime/dry-patterns.ts +32 -0
  257. package/src/runtime/dry.ts +96 -0
  258. package/src/runtime/fragment-registry.ts +33 -0
  259. package/src/runtime/framework-skill-gate.ts +79 -0
  260. package/src/runtime/fs-cleanup.ts +71 -0
  261. package/src/runtime/gate-apex.ts +81 -0
  262. package/src/runtime/gate-helpers.ts +52 -0
  263. package/src/runtime/gate-input.ts +26 -0
  264. package/src/runtime/gate.ts +128 -0
  265. package/src/runtime/handle-post.ts +104 -0
  266. package/src/runtime/handle-pre.ts +137 -0
  267. package/src/runtime/handle-scope-async.ts +29 -0
  268. package/src/runtime/handle-types.ts +27 -0
  269. package/src/runtime/handle.ts +197 -0
  270. package/src/runtime/home-state.ts +55 -0
  271. package/src/runtime/index.ts +18 -0
  272. package/src/runtime/inform.ts +28 -0
  273. package/src/runtime/inject-budget-recap.ts +47 -0
  274. package/src/runtime/inject-budget.ts +81 -0
  275. package/src/runtime/inject-context.ts +85 -0
  276. package/src/runtime/inject-dedup-exclusive.ts +98 -0
  277. package/src/runtime/inject-dedup.ts +89 -0
  278. package/src/runtime/is-agent-tool.ts +19 -0
  279. package/src/runtime/lifecycle/agent-files.ts +49 -0
  280. package/src/runtime/lifecycle/agent-memory.ts +82 -0
  281. package/src/runtime/lifecycle/agent-transcript.ts +80 -0
  282. package/src/runtime/lifecycle/aipilot/analytics.ts +99 -0
  283. package/src/runtime/lifecycle/aipilot/apex-task-store.ts +61 -0
  284. package/src/runtime/lifecycle/aipilot/cache-base.ts +48 -0
  285. package/src/runtime/lifecycle/aipilot/cache-doc.ts +100 -0
  286. package/src/runtime/lifecycle/aipilot/cache-lessons.ts +58 -0
  287. package/src/runtime/lifecycle/aipilot/cache-test.ts +70 -0
  288. package/src/runtime/lifecycle/aipilot/curate-lessons.ts +64 -0
  289. package/src/runtime/lifecycle/aipilot/dispatch-aipilot.ts +106 -0
  290. package/src/runtime/lifecycle/aipilot/doc-cache-gate.ts +69 -0
  291. package/src/runtime/lifecycle/aipilot/inject-apex.ts +98 -0
  292. package/src/runtime/lifecycle/aipilot/inject-doc.ts +62 -0
  293. package/src/runtime/lifecycle/aipilot/inject-explore.ts +64 -0
  294. package/src/runtime/lifecycle/aipilot/inject-lessons.ts +61 -0
  295. package/src/runtime/lifecycle/aipilot/inject-test.ts +48 -0
  296. package/src/runtime/lifecycle/aipilot/lesson-archive.ts +60 -0
  297. package/src/runtime/lifecycle/aipilot/lesson-inject.ts +98 -0
  298. package/src/runtime/lifecycle/aipilot/lesson-parse.ts +74 -0
  299. package/src/runtime/lifecycle/aipilot/lessons.ts +72 -0
  300. package/src/runtime/lifecycle/aipilot/promote-global-lessons.ts +92 -0
  301. package/src/runtime/lifecycle/aipilot/solid-compliance.ts +69 -0
  302. package/src/runtime/lifecycle/aipilot/solid-notice.ts +47 -0
  303. package/src/runtime/lifecycle/aipilot/solid-transcript.ts +60 -0
  304. package/src/runtime/lifecycle/aipilot/source-scan.ts +56 -0
  305. package/src/runtime/lifecycle/aipilot/sync-task.ts +75 -0
  306. package/src/runtime/lifecycle/aipilot/transcript.ts +88 -0
  307. package/src/runtime/lifecycle/aipilot/types.ts +72 -0
  308. package/src/runtime/lifecycle/auto-document-reads.ts +73 -0
  309. package/src/runtime/lifecycle/cartographer/detect.ts +57 -0
  310. package/src/runtime/lifecycle/cartographer/ecosystem-map.ts +56 -0
  311. package/src/runtime/lifecycle/cartographer/fs-util.ts +64 -0
  312. package/src/runtime/lifecycle/cartographer/merge.ts +51 -0
  313. package/src/runtime/lifecycle/cartographer/project-map.ts +49 -0
  314. package/src/runtime/lifecycle/cartographer/scan-hooks.ts +48 -0
  315. package/src/runtime/lifecycle/cartographer/scan.ts +81 -0
  316. package/src/runtime/lifecycle/cartographer/session-start.ts +33 -0
  317. package/src/runtime/lifecycle/cartographer/track-enrichment.ts +45 -0
  318. package/src/runtime/lifecycle/cartographer/write-plugin-map.ts +43 -0
  319. package/src/runtime/lifecycle/cartographer/write-tree.ts +44 -0
  320. package/src/runtime/lifecycle/changelog-research.ts +43 -0
  321. package/src/runtime/lifecycle/check-file-size.ts +61 -0
  322. package/src/runtime/lifecycle/codex-resync/fingerprint.ts +63 -0
  323. package/src/runtime/lifecycle/codex-resync/lock.ts +46 -0
  324. package/src/runtime/lifecycle/codex-resync/managed-destination.ts +39 -0
  325. package/src/runtime/lifecycle/codex-resync/materialize-agents.ts +57 -0
  326. package/src/runtime/lifecycle/codex-resync/plugin-files.ts +65 -0
  327. package/src/runtime/lifecycle/codex-resync/plugin-roots.ts +62 -0
  328. package/src/runtime/lifecycle/codex-resync/resync.ts +45 -0
  329. package/src/runtime/lifecycle/codex-resync/symlink-commands.ts +25 -0
  330. package/src/runtime/lifecycle/dispatch.ts +109 -0
  331. package/src/runtime/lifecycle/failure-lesson.ts +55 -0
  332. package/src/runtime/lifecycle/index.ts +34 -0
  333. package/src/runtime/lifecycle/inject-rules.ts +50 -0
  334. package/src/runtime/lifecycle/instructions-loaded.ts +22 -0
  335. package/src/runtime/lifecycle/kimi-rules-native.ts +44 -0
  336. package/src/runtime/lifecycle/lessons/dispatch.ts +88 -0
  337. package/src/runtime/lifecycle/lessons/reminder.ts +89 -0
  338. package/src/runtime/lifecycle/lessons/state.ts +23 -0
  339. package/src/runtime/lifecycle/memory/agent-lesson.ts +35 -0
  340. package/src/runtime/lifecycle/memory/capture-error.ts +41 -0
  341. package/src/runtime/lifecycle/memory/client.ts +65 -0
  342. package/src/runtime/lifecycle/memory/dispatch.ts +42 -0
  343. package/src/runtime/lifecycle/memory/recall.ts +30 -0
  344. package/src/runtime/lifecycle/memory/salience.ts +31 -0
  345. package/src/runtime/lifecycle/memory/state.ts +49 -0
  346. package/src/runtime/lifecycle/memory/track-ops.ts +15 -0
  347. package/src/runtime/lifecycle/post-compact.ts +38 -0
  348. package/src/runtime/lifecycle/post-edit-ts.ts +49 -0
  349. package/src/runtime/lifecycle/post-tracking.ts +40 -0
  350. package/src/runtime/lifecycle/pre-compact.ts +38 -0
  351. package/src/runtime/lifecycle/rules-root.ts +99 -0
  352. package/src/runtime/lifecycle/security/check-skill.ts +63 -0
  353. package/src/runtime/lifecycle/security/scan-patterns.ts +53 -0
  354. package/src/runtime/lifecycle/security/scan.ts +76 -0
  355. package/src/runtime/lifecycle/security/skill-state.ts +47 -0
  356. package/src/runtime/lifecycle/security/track-mcp.ts +29 -0
  357. package/src/runtime/lifecycle/security/track-skill-read.ts +31 -0
  358. package/src/runtime/lifecycle/seo/post-tool-use.ts +45 -0
  359. package/src/runtime/lifecycle/session-end.ts +17 -0
  360. package/src/runtime/lifecycle/session-start.ts +57 -0
  361. package/src/runtime/lifecycle/snapshot/board.ts +27 -0
  362. package/src/runtime/lifecycle/snapshot/format.ts +60 -0
  363. package/src/runtime/lifecycle/snapshot/git.ts +71 -0
  364. package/src/runtime/lifecycle/snapshot/index.ts +52 -0
  365. package/src/runtime/lifecycle/snapshot/version.ts +34 -0
  366. package/src/runtime/lifecycle/solid-detect.ts +56 -0
  367. package/src/runtime/lifecycle/stop-core.ts +35 -0
  368. package/src/runtime/lifecycle/subagent-cache.ts +61 -0
  369. package/src/runtime/lifecycle/task-completed.ts +94 -0
  370. package/src/runtime/lifecycle/teammate-idle-check.ts +63 -0
  371. package/src/runtime/lifecycle/teammate-idle.ts +28 -0
  372. package/src/runtime/lifecycle/tool-failure.ts +24 -0
  373. package/src/runtime/lifecycle/track-changes.ts +61 -0
  374. package/src/runtime/lifecycle/validate-solid.ts +70 -0
  375. package/src/runtime/lifecycle/validate-tailwind.ts +80 -0
  376. package/src/runtime/lifecycle-bridge.ts +74 -0
  377. package/src/runtime/mcp-key.ts +44 -0
  378. package/src/runtime/mcp-tool-name.ts +92 -0
  379. package/src/runtime/mcp.ts +91 -0
  380. package/src/runtime/modular.ts +77 -0
  381. package/src/runtime/normalize.ts +107 -0
  382. package/src/runtime/notices.ts +91 -0
  383. package/src/runtime/notification-sound.ts +49 -0
  384. package/src/runtime/notifications.ts +77 -0
  385. package/src/runtime/paths.ts +59 -0
  386. package/src/runtime/post-fanout.ts +41 -0
  387. package/src/runtime/post-outcome.ts +61 -0
  388. package/src/runtime/pre-allow.ts +97 -0
  389. package/src/runtime/precommit.ts +60 -0
  390. package/src/runtime/prompt-text.ts +38 -0
  391. package/src/runtime/receipt-capture.ts +34 -0
  392. package/src/runtime/record.ts +26 -0
  393. package/src/runtime/respond.ts +92 -0
  394. package/src/runtime/solid-file-size-gate.ts +58 -0
  395. package/src/runtime/solid-pre.ts +53 -0
  396. package/src/runtime/storage.ts +9 -0
  397. package/src/statusline/colors.ts +31 -0
  398. package/src/statusline/constants.ts +17 -0
  399. package/src/statusline/formatters.ts +54 -0
  400. package/src/statusline/index.ts +4 -0
  401. package/src/statusline/progress-bar.ts +46 -0
  402. package/src/tracking/codex-post-failure.ts +73 -0
  403. package/src/tracking/index.ts +2 -0
  404. package/src/tracking/integrity.ts +99 -0
  405. package/src/tracking/one-shot-dedup.ts +34 -0
  406. package/src/tracking/one-shot-failure.ts +42 -0
  407. package/src/tracking/one-shot-store.ts +95 -0
  408. package/src/tracking/one-shot.ts +93 -0
  409. package/src/tracking/receipts.ts +95 -0
  410. package/src/tracking/session-state.ts +94 -0
  411. package/src/tracking/store.ts +96 -0
  412. package/src/tracking/track-compact.ts +100 -0
  413. package/src/tracking/track-diff.ts +50 -0
  414. package/src/tracking/track-journal.ts +97 -0
  415. package/src/tracking/track-lock-sync.ts +83 -0
  416. package/src/tracking/track-lock.ts +67 -0
  417. package/src/util/compact-json.ts +23 -0
  418. package/src/util/index.ts +2 -0
  419. package/src/util/json-io.ts +47 -0
  420. package/src/util/project-root.ts +41 -0
  421. package/src/util/runtime-io.ts +85 -0
  422. package/src/util/semver.ts +30 -0
@@ -0,0 +1,55 @@
1
+ /**
2
+ * @module failure-lesson
3
+ * PostToolUseFailure handler: keep the failure log, record the failure in the
4
+ * one-shot metric, and inject the ONE most-specific lesson whose `error:` trigger
5
+ * matches the failure message — reusing the PreToolUse {@link lessonFor} index and
6
+ * its cooldown (idempotent under the ~11-process fan-out). Fail-open throughout.
7
+ *
8
+ * Claude-Code-only in the sense that no equivalent `PostToolUseFailure` hook
9
+ * exists on Codex or Hermes — but Cursor's own `postToolUseFailure` DOES
10
+ * arrive here too, via `lifecycle-bridge.ts`'s `lifecycleStdout` translating
11
+ * the wire event name and forwarding to `dispatchLifecycle`'s
12
+ * `"PostToolUseFailure"` case (see `handle.ts`'s `cursorRawPayloadProjection`
13
+ * for how `data.tool_name` below arrives already canonicalized on Cursor).
14
+ * @packageDocumentation
15
+ */
16
+ import { homedir } from "node:os";
17
+ import { contextResponse } from "../../adapters/claude";
18
+ import { lessonFor } from "../../policy/lessons/lesson-gate";
19
+ import { lessonsFileFor } from "./lessons/state";
20
+ import { projectRoot } from "../../util/project-root";
21
+ import { oncePerWindow } from "../inject-dedup";
22
+ import { defaultStateDir } from "../paths";
23
+ import { recordFailure } from "../../tracking/one-shot-failure";
24
+ import { logToolFailure } from "./tool-failure";
25
+ import type { OncePerWindow } from "../../policy/lessons/types";
26
+
27
+ /** The failure message across the documented `error` field and defensive fallbacks; "" when none. */
28
+ function failureError(data: Record<string, unknown>): string {
29
+ const raw = data.error ?? data.tool_error ?? data.tool_output;
30
+ if (typeof raw === "string") return raw;
31
+ return raw != null ? JSON.stringify(raw) : "";
32
+ }
33
+
34
+ /**
35
+ * Handle PostToolUseFailure: log the failure, tally it per tool, and inject the
36
+ * matching `error:`-triggered lesson as `additionalContext` ("" when none).
37
+ * @param data - The raw PostToolUseFailure payload (`tool_name`, `error`, `session_id`).
38
+ * @param cwd - Project root.
39
+ * @param home - Home dir (defaults to `~`).
40
+ * @param now - Clock (defaults to `Date.now()`).
41
+ * @param once - Cooldown gate (injected for tests; defaults to {@link oncePerWindow}).
42
+ * @returns The native hook stdout, or "" when nothing to inject.
43
+ */
44
+ export function failureLessonContext(data: Record<string, unknown>, cwd: string, home: string = homedir(), now: number = Date.now(), once: OncePerWindow = oncePerWindow): string {
45
+ logToolFailure(data, home, now);
46
+ const tool = typeof data.tool_name === "string" ? data.tool_name : "unknown";
47
+ const sessionId = typeof data.session_id === "string" ? data.session_id : undefined;
48
+ try { recordFailure(tool, { now, dir: defaultStateDir(cwd), sessionId }); } catch { /* metric never breaks the hook */ }
49
+ const errorMsg = failureError(data);
50
+ if (!errorMsg) return "";
51
+ // tool="" + empty input isolates matching to the `error:` regex (rank 1),
52
+ // reusing the PreToolUse gate's index + cooldown — no second lesson engine.
53
+ const lesson = lessonFor("", {}, { file: lessonsFileFor(projectRoot(cwd)), once, prevError: errorMsg });
54
+ return lesson?.reason ? contextResponse("PostToolUseFailure", lesson.reason) : "";
55
+ }
@@ -0,0 +1,34 @@
1
+ export * from "./session-start";
2
+ export * from "./inject-rules";
3
+ export * from "./solid-detect";
4
+ export * from "./check-file-size";
5
+ export * from "./validate-solid";
6
+ export * from "./validate-tailwind";
7
+ export * from "./subagent-cache";
8
+ export * from "./agent-memory";
9
+ export * from "./teammate-idle";
10
+ export * from "./tool-failure";
11
+ export * from "./pre-compact";
12
+ export * from "./session-end";
13
+ export * from "./instructions-loaded";
14
+ export * from "./track-changes";
15
+ export * from "./post-edit-ts";
16
+ export * from "./dispatch";
17
+ export * from "./post-tracking";
18
+ export * from "./changelog-research";
19
+ export * from "./cartographer/session-start";
20
+ export * from "./cartographer/project-map";
21
+ export * from "./cartographer/track-enrichment";
22
+ export * from "./cartographer/write-tree";
23
+ export * from "./cartographer/merge";
24
+ export * from "./cartographer/fs-util";
25
+ export * from "./security/check-skill";
26
+ export * from "./security/track-skill-read";
27
+ export * from "./security/track-mcp";
28
+ export * from "./security/skill-state";
29
+ export * from "./lessons/dispatch";
30
+ export * from "./lessons/state";
31
+ export * from "./memory/dispatch";
32
+ export * from "./seo/post-tool-use";
33
+ export * from "./cartographer/ecosystem-map";
34
+ export * from "./cartographer/write-plugin-map";
@@ -0,0 +1,50 @@
1
+ import { existsSync, readdirSync, readFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { renderInform } from "../inform";
4
+ import { rulesInAgentsMd } from "./kimi-rules-native";
5
+
6
+ /** Read & concatenate all `*.md` files (sorted) under `rulesDir`. */
7
+ export function readRules(rulesDir: string): string {
8
+ if (!existsSync(rulesDir)) return "";
9
+ let names: string[];
10
+ try {
11
+ names = readdirSync(rulesDir).filter((n) => n.endsWith(".md")).sort();
12
+ } catch {
13
+ return "";
14
+ }
15
+ const parts: string[] = [];
16
+ for (const name of names) {
17
+ try {
18
+ parts.push(readFileSync(join(rulesDir, name), "utf-8"));
19
+ } catch {
20
+ /* skip unreadable rule file */
21
+ }
22
+ }
23
+ return parts.join("\n\n");
24
+ }
25
+
26
+ /**
27
+ * Build the rules injection for claude-rules (SessionStart, UserPromptSubmit,
28
+ * SubagentStart): read `<pluginRoot>/rules/*.md` and emit via {@link renderInform}
29
+ * (Claude-shaped `additionalContext`, or raw text for kimi). Tags the output
30
+ * with the *actual* `hookEventName` — the spec requires it to match the firing
31
+ * event (a hardcoded "SessionStart" is non-conforming and may be dropped on
32
+ * UserPromptSubmit/SubagentStart).
33
+ *
34
+ * On kimi's `UserPromptSubmit` specifically, when the corpus is already fenced
35
+ * inside `<kimiHome>/AGENTS.md` (native load, {@link rulesInAgentsMd}), only the
36
+ * notice is emitted — the re-injected corpus would otherwise dump into the
37
+ * user's terminal on every single prompt for no model-context benefit.
38
+ * `SessionStart`/`SubagentStart` always keep the full corpus (session bootstrap
39
+ * / sub-agent contexts need it regardless of the native AGENTS.md load).
40
+ * @param pluginRoot - `CLAUDE_PLUGIN_ROOT` of the claude-rules plugin.
41
+ * @param event - The firing hook event name (e.g. "SessionStart").
42
+ * @param id - Harness target id (defaults to "claude-code" — zero-regression default).
43
+ * @returns The native hook stdout (possibly empty).
44
+ */
45
+ export function injectRules(pluginRoot: string, event: string, id: string = "claude-code"): string {
46
+ const content = readRules(join(pluginRoot, "rules"));
47
+ if (!content) return "";
48
+ if (id === "kimi" && event === "UserPromptSubmit" && rulesInAgentsMd()) return "rules 00-08 injected";
49
+ return renderInform(id, event, content, "rules 00-08 injected");
50
+ }
@@ -0,0 +1,22 @@
1
+ import { appendFileSync, mkdirSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { homedir } from "node:os";
4
+
5
+ /**
6
+ * Handle InstructionsLoaded: append `load_reason | memory_type | file_path` to
7
+ * the per-session debug log. Ports `instructions-loaded/validate-rules-loaded.py`.
8
+ * No stdout (logging only; InstructionsLoaded has no decision control).
9
+ * @param data - The raw hook payload.
10
+ * @param home - Home dir (defaults to `~`).
11
+ */
12
+ export function validateRulesLoaded(data: Record<string, unknown>, home: string = homedir()): void {
13
+ const filePath = String(data.file_path ?? "");
14
+ const loadReason = String(data.load_reason ?? "");
15
+ const memoryType = String(data.memory_type ?? "");
16
+ const sessionId = String(data.session_id ?? "unknown");
17
+ const dir = join(home, ".claude", "logs", "instructions-loaded");
18
+ try {
19
+ mkdirSync(dir, { recursive: true });
20
+ appendFileSync(join(dir, `${sessionId}.log`), `${loadReason} | ${memoryType} | ${filePath}\n`, "utf-8");
21
+ } catch { /* best effort */ }
22
+ }
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Guard for the kimi notice-only rules injection. Kimi Code CLI has no
3
+ * model-only hook channel (`src/runtime/inform.ts`) — every `UserPromptSubmit`
4
+ * stdout is BOTH appended to the model's context AND rendered raw in the
5
+ * user's terminal. `injectRules` (`inject-rules.ts`) normally re-injects the
6
+ * full ~18 Ko rules corpus on every prompt; when the installer has already
7
+ * merged that same corpus into `<kimiHome>/AGENTS.md` — which Kimi loads
8
+ * natively at session start via its documented `${agents_md}` mechanism,
9
+ * independent of hooks — the re-injection is pure noise the user has to
10
+ * scroll past. Confirmed present, fenced, in the local install:
11
+ * `~/.kimi-code/AGENTS.md:87` / `:335`.
12
+ *
13
+ * Fail-safe semantics: fences absent, file absent, or any read error ⟹
14
+ * `false` — the caller then falls back to the full corpus. A dump is
15
+ * verbose but never wrong; a false `true` would silently drop the rules.
16
+ */
17
+ import { existsSync, readFileSync } from "node:fs";
18
+ import { join } from "node:path";
19
+ import { harnessHome } from "../../config/home-dir";
20
+
21
+ /** Opening fence the fusengine installer writes around the merged corpus. */
22
+ const FENCE_START = "<!-- fusengine:kimi-rules:start -->";
23
+
24
+ /** Closing fence the fusengine installer writes around the merged corpus. */
25
+ const FENCE_END = "<!-- fusengine:kimi-rules:end -->";
26
+
27
+ /**
28
+ * Check whether the kimi rules corpus is already present, fenced, inside
29
+ * `<kimiHome>/AGENTS.md`.
30
+ * @param home - Kimi home dir override (defaults to `harnessHome("kimi")`,
31
+ * which honors `KIMI_CODE_HOME`).
32
+ * @returns `true` only when both fences are found in a readable file.
33
+ */
34
+ export function rulesInAgentsMd(home?: string): boolean {
35
+ try {
36
+ const kimiHome = home ?? harnessHome("kimi");
37
+ const agentsMd = join(kimiHome, "AGENTS.md");
38
+ if (!existsSync(agentsMd)) return false;
39
+ const content = readFileSync(agentsMd, "utf-8");
40
+ return content.includes(FENCE_START) && content.includes(FENCE_END);
41
+ } catch {
42
+ return false;
43
+ }
44
+ }
@@ -0,0 +1,88 @@
1
+ /**
2
+ * fuse-lessons scope dispatch (TS port of the 4 handler scripts). Routes by
3
+ * event: SessionStart/SubagentStart inject `MEMORY/LESSON.md`; Stop reminds the
4
+ * stopping session about ITS OWN projects with unsaved code edits; PostToolUse
5
+ * marks the write to arm/silence the throttle. The reminder + mark logic (incl.
6
+ * the per-`session_id` scoping that fixes the multi-session misdirection) lives
7
+ * in {@link module:runtime/lifecycle/lessons/reminder}; this module keeps the
8
+ * event router + lesson-file injection. Non-fatal by design.
9
+ */
10
+ import { existsSync, readFileSync } from "node:fs";
11
+ import { renderInform } from "../../inform";
12
+ import { projectRoot } from "../../../util/project-root";
13
+ import { atomicWrite } from "../../../util/json-io";
14
+ import { curateLessons } from "../aipilot/curate-lessons";
15
+ import { compressInjection } from "../aipilot/lesson-inject";
16
+ import { capFragment } from "../../inject-budget";
17
+ import { lessonsArchiveFileFor, lessonsFileFor } from "./state";
18
+ import { markWrite, remindWrite } from "./reminder";
19
+
20
+ /**
21
+ * Persist a curation ATOMICALLY and ARCHIVE-FIRST for zero-loss: prepend the
22
+ * moved bullets to LESSON-archive.md, THEN rewrite LESSON.md. On ANY write error
23
+ * the original file is left untouched (returns `original`) so a bullet is never
24
+ * lost — a rare archive-then-trim-fail leaves a duplicate (never a loss), which
25
+ * the next dedup pass reconciles.
26
+ */
27
+ function persistCuration(file: string, root: string, curated: string, archive: string, original: string): string {
28
+ try {
29
+ if (archive) {
30
+ const af = lessonsArchiveFileFor(root);
31
+ const prev = existsSync(af) ? readFileSync(af, "utf-8") : "";
32
+ atomicWrite(af, prev ? `${archive}\n${prev}` : archive);
33
+ }
34
+ atomicWrite(file, curated);
35
+ return curated;
36
+ } catch { return original; }
37
+ }
38
+
39
+ /**
40
+ * Inject `MEMORY/LESSON.md` for `event`. Mechanical curation (dedup + cap→archive)
41
+ * rewrites the FILE; the injected BLOCK is then COMPRESSED (newest bullets whole,
42
+ * older ones distilled to their rule) so a growing file never inflates the
43
+ * SessionStart/SubagentStart context. A hard {@link capFragment} budget is the
44
+ * last-resort backstop on top of compression — the regression that motivated it
45
+ * was this exact block silently reaching ~44k tokens. Any curation report
46
+ * surfaces via systemMessage.
47
+ */
48
+ function injectMemory(cwd: string, event: string, now: number, id: string): string {
49
+ const root = projectRoot(cwd);
50
+ const file = lessonsFileFor(root);
51
+ if (!existsSync(file)) return "";
52
+ let content = "";
53
+ try { content = readFileSync(file, "utf-8").trim(); } catch { return ""; }
54
+ if (!content) return "";
55
+ const { content: curated, archive, report } = curateLessons(content, now, root);
56
+ if (curated !== content) content = persistCuration(file, root, curated, archive, content);
57
+ const body = capFragment("lessons", compressInjection(content));
58
+ const ctx = `Project lessons — never reproduce these:\n${body}\nYou may append OR refine/merge/dedupe bullets in MEMORY/LESSON.md — keep it terse.`;
59
+ const notice = report ? `lessons injected\nLESSON.md curation:\n${report}` : "lessons injected";
60
+ return renderInform(id, event, ctx, notice);
61
+ }
62
+
63
+ /**
64
+ * Route a fuse-lessons event to its handler. Returns the native stdout for
65
+ * context-injecting events (SessionStart/SubagentStart/UserPromptSubmit inject
66
+ * MEMORY/LESSON.md, Stop reminds) or "" for the side-effect-only PostToolUse mark.
67
+ * @param event - The raw hook event name.
68
+ * @param payload - The raw hook payload.
69
+ * @param cwd - Project root for memory injection.
70
+ * @param now - Clock.
71
+ * @param id - Harness target id (defaults to "claude-code" — zero-regression default).
72
+ * @returns The native stdout (possibly empty).
73
+ */
74
+ export function dispatchLessons(event: string, payload: Record<string, unknown>, cwd: string, now: number, id: string = "claude-code"): string {
75
+ switch (event) {
76
+ case "SessionStart":
77
+ case "SubagentStart":
78
+ case "UserPromptSubmit":
79
+ return injectMemory(cwd, event, now, id);
80
+ case "Stop":
81
+ return remindWrite(payload, now);
82
+ case "PostToolUse":
83
+ markWrite(payload, now);
84
+ return "";
85
+ default:
86
+ return "";
87
+ }
88
+ }
@@ -0,0 +1,89 @@
1
+ /**
2
+ * fuse-lessons write-mark + Stop-reminder, scoped by `session_id` when present.
3
+ *
4
+ * WITH a session id (normal Claude Code): each `(session, root)` pair carries
5
+ * its own edit/reminder throttle in {@link module:memory/session-roots}, so a
6
+ * Stop lists and silences ONLY the roots THAT session edited — concurrent
7
+ * sessions on different projects never cross-remind nor steal each other's
8
+ * throttle. WITHOUT a usable session id (a harness that omits it, or the legacy
9
+ * on-disk state) it falls back to the original mono-session behavior: the global
10
+ * flat root registry + the per-project `MEMORY/state.json` throttle.
11
+ */
12
+ import { dirname, resolve } from "node:path";
13
+ import { contextResponse } from "../../../adapters/claude";
14
+ import { sanitizeSessionId } from "../../home-state";
15
+ import { addRoot, readRoots } from "../../../memory/registry";
16
+ import { collectSessionPending, markSessionRoot } from "../../../memory/session-roots";
17
+ import { nowStamp, readState, setStateField, throttleMs } from "../../../memory/state";
18
+ import { isCodeFile, projectRootOrNull } from "../../../util/project-root";
19
+ import { lessonsStateFileFor } from "./state";
20
+
21
+ /** Sanitized session id from a raw hook payload, or null (→ legacy fallback). */
22
+ function sessionOf(payload: Record<string, unknown>): string | null {
23
+ return sanitizeSessionId(payload.session_id);
24
+ }
25
+
26
+ /** Legacy (no session id): pending roots across the global flat registry. */
27
+ function collectLegacyPending(now: number, window: number): string[] {
28
+ const pending: string[] = [];
29
+ for (const root of readRoots()) {
30
+ const stateFile = lessonsStateFileFor(root);
31
+ const { lastRemindedAt, lastCodeEditAt } = readState(stateFile);
32
+ if (lastCodeEditAt <= lastRemindedAt) continue;
33
+ if (now - lastRemindedAt < window) continue;
34
+ pending.push(root);
35
+ setStateField(stateFile, "lastRemindedAt", now);
36
+ }
37
+ return pending;
38
+ }
39
+
40
+ /** Stop reminder body listing each pending project's lessons file. */
41
+ function reminderText(pending: string[]): string {
42
+ const stamp = nowStamp();
43
+ const targets = pending.map((r) => `- ${r}/MEMORY/LESSON.md`).join("\n");
44
+ return `Before ending: if this session hit a mistake/blocker worth never ` +
45
+ `reproducing, append 1-3 COMPACT bullets OR sharpen/merge existing ones ` +
46
+ `(format \`- [${stamp}] what went wrong → do instead\`, use exactly this ` +
47
+ `timestamp) in each project's lessons file below. Skip if nothing ` +
48
+ `notable.\n${targets}`;
49
+ }
50
+
51
+ /**
52
+ * Stop: emit one reminder covering the stopping session's pending projects.
53
+ * @param payload - Raw hook payload (`session_id` selects the scoped path).
54
+ * @param now - Clock.
55
+ * @returns Native Stop stdout, or "" when nothing is pending.
56
+ */
57
+ export function remindWrite(payload: Record<string, unknown>, now: number): string {
58
+ const window = throttleMs();
59
+ const sid = sessionOf(payload);
60
+ const pending = sid ? collectSessionPending(sid, now, window) : collectLegacyPending(now, window);
61
+ if (pending.length === 0) return "";
62
+ return contextResponse("Stop", reminderText(pending));
63
+ }
64
+
65
+ /**
66
+ * PostToolUse: record the edit against the throttle. A code file arms the
67
+ * reminder; writing `MEMORY/LESSON.md` silences it (the lesson was just saved).
68
+ * Session-scoped when `session_id` is present, else the legacy global path.
69
+ * @param payload - Raw hook payload (`tool_input.file_path`, `session_id`).
70
+ * @param now - Clock.
71
+ */
72
+ export function markWrite(payload: Record<string, unknown>, now: number): void {
73
+ const input = payload.tool_input as { file_path?: string } | undefined;
74
+ if (!input?.file_path) return;
75
+ const abs = resolve(input.file_path);
76
+ const root = projectRootOrNull(dirname(abs));
77
+ if (!root) return;
78
+ const isLesson = abs === resolve(root, "MEMORY", "LESSON.md");
79
+ if (!isLesson && !isCodeFile(abs)) return;
80
+ const sid = sessionOf(payload);
81
+ if (sid) {
82
+ markSessionRoot(sid, root, isLesson ? "remindedAt" : "editedAt", now);
83
+ } else if (isLesson) {
84
+ setStateField(lessonsStateFileFor(root), "lastRemindedAt", now);
85
+ } else {
86
+ setStateField(lessonsStateFileFor(root), "lastCodeEditAt", now);
87
+ addRoot(root);
88
+ }
89
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Per-project lessons paths. The `fuse-lessons` plugin stores its lessons under
3
+ * `<root>/MEMORY/` (NOT the harness `.harness/memory/`), so these two path
4
+ * helpers override the layout while ALL state/gitignore/throttle logic is
5
+ * reused from `src/memory` (`setStateField`, `ensureMemoryGitignore`,
6
+ * `readState`, `nowStamp`, `throttleMs`).
7
+ */
8
+ import { join } from "node:path";
9
+
10
+ /** Absolute `<root>/MEMORY/LESSON.md` — the curated, committable lessons file. */
11
+ export function lessonsFileFor(root: string): string {
12
+ return join(root, "MEMORY", "LESSON.md");
13
+ }
14
+
15
+ /** Absolute `<root>/MEMORY/LESSON-archive.md` — cold storage for capped-out bullets. */
16
+ export function lessonsArchiveFileFor(root: string): string {
17
+ return join(root, "MEMORY", "LESSON-archive.md");
18
+ }
19
+
20
+ /** Absolute `<root>/MEMORY/state.json` — machine-local throttle counter. */
21
+ export function lessonsStateFileFor(root: string): string {
22
+ return join(root, "MEMORY", "state.json");
23
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * SubagentStop memory handler. Ports `capture-agent-lesson.py`: log a finished
3
+ * agent's conclusion and, when salient enough, store it as a Graphiti episode.
4
+ * Skips explore-codebase/websearch agents and errored exits.
5
+ */
6
+ import { isoUtc } from "../security/skill-state";
7
+ import { postEpisode } from "./client";
8
+ import { SALIENCE_THRESHOLD, agentSeverity, salience } from "./salience";
9
+ import { appendMemoryLog } from "./state";
10
+
11
+ /** Agents whose conclusions are never captured. */
12
+ const SKIP = new Set(["explore-codebase", "websearch"]);
13
+
14
+ /**
15
+ * Handle SubagentStop: log + maybe store the agent's conclusion. Side-effect
16
+ * only (no stdout).
17
+ * @param payload - The raw hook payload.
18
+ * @param now - Clock.
19
+ */
20
+ export async function captureAgentLesson(payload: Record<string, unknown>, now: number): Promise<void> {
21
+ const name = typeof payload.agent_name === "string" ? payload.agent_name : "unknown";
22
+ const lastMsg = typeof payload.last_assistant_message === "string" ? payload.last_assistant_message : "";
23
+ const exitReason = typeof payload.exit_reason === "string" ? payload.exit_reason : "unknown";
24
+ if (!lastMsg || exitReason === "error" || SKIP.has(name)) return;
25
+ const lesson = lastMsg.slice(0, 1000);
26
+ const ts = isoUtc(now);
27
+ appendMemoryLog("agent-lessons.log", `[${ts}] ${name} | ${exitReason} | ${lesson.slice(0, 80)}...`);
28
+ if (salience(agentSeverity(name)) <= SALIENCE_THRESHOLD) return;
29
+ await postEpisode({
30
+ name: "agent_lesson",
31
+ episode_body: `Agent ${name} conclusion: ${lesson}`,
32
+ source_description: `agent-stop-${name}`,
33
+ reference_time: ts,
34
+ });
35
+ }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * PostToolUse (Bash) memory handler. Ports `auto-capture-error.py`: on a
3
+ * non-zero Bash exit with stderr, store an episode in Graphiti and surface a
4
+ * `<memory-capture>` hint to search past errors / store the eventual solution.
5
+ */
6
+ import { contextResponse } from "../../../adapters/claude";
7
+ import { isoUtc } from "../security/skill-state";
8
+ import { postEpisode } from "./client";
9
+ import { SALIENCE_THRESHOLD, bashSeverity, salience } from "./salience";
10
+
11
+ /** Extract exit code + stderr from a PostToolUse Bash payload (either field). */
12
+ function bashResult(payload: Record<string, unknown>): { exit: string; stderr: string } {
13
+ const r = (payload.tool_result ?? payload.tool_response) as { exit_code?: unknown; stderr?: unknown } | undefined;
14
+ return { exit: String(r?.exit_code ?? "0"), stderr: typeof r?.stderr === "string" ? r.stderr : "" };
15
+ }
16
+
17
+ /**
18
+ * Handle a Bash PostToolUse: capture a failed command's error in neural memory
19
+ * and return the native additionalContext stdout (or "" when nothing to emit).
20
+ * @param payload - The raw hook payload.
21
+ * @param now - Clock.
22
+ * @returns The native stdout (possibly empty).
23
+ */
24
+ export async function captureBashError(payload: Record<string, unknown>, now: number): Promise<string> {
25
+ const { exit, stderr } = bashResult(payload);
26
+ if (exit === "0" || !stderr) return "";
27
+ const sev = bashSeverity(stderr);
28
+ if (salience(sev) <= SALIENCE_THRESHOLD) return "";
29
+ const errorMsg = stderr.slice(0, 500);
30
+ await postEpisode({
31
+ name: "bash_error",
32
+ episode_body: `Bash error (exit ${exit}): ${errorMsg}`,
33
+ source_description: "auto-capture",
34
+ reference_time: isoUtc(now),
35
+ });
36
+ const ctx =
37
+ `Error captured in neural memory (Graphiti).\n` +
38
+ `Search for similar past errors: use mcp__qdrant__qdrant-find with query "${errorMsg}"\n` +
39
+ `If you solve this, store the solution: use mcp__qdrant__qdrant-store`;
40
+ return contextResponse("PostToolUse", ctx);
41
+ }
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Graphiti neural-memory HTTP client (best-effort). Ports the urllib calls in
3
+ * the memory-neural scripts: POST /episodes (store) + POST /search (recall).
4
+ * Every call swallows network errors and honors a 5s timeout, so a hook never
5
+ * fails or hangs when the Graphiti server is absent.
6
+ */
7
+
8
+ const TIMEOUT_MS = 5000;
9
+
10
+ /** Base URL `http://<NEURAL_MEMORY_HOST>:<GRAPHITI_PORT>` (env-overridable). */
11
+ export function neuralBase(env: NodeJS.ProcessEnv = process.env): string {
12
+ const host = env.NEURAL_MEMORY_HOST ?? "localhost";
13
+ const port = env.GRAPHITI_PORT ?? "8000";
14
+ return `http://${host}:${port}`;
15
+ }
16
+
17
+ /** A Graphiti episode payload. */
18
+ export interface Episode {
19
+ name: string;
20
+ episode_body: string;
21
+ source_description: string;
22
+ reference_time: string;
23
+ }
24
+
25
+ /** POST an episode to Graphiti `/episodes`. Resolves silently on any failure. */
26
+ export async function postEpisode(ep: Episode, env: NodeJS.ProcessEnv = process.env): Promise<void> {
27
+ try {
28
+ await fetch(`${neuralBase(env)}/episodes`, {
29
+ method: "POST",
30
+ headers: { "Content-Type": "application/json" },
31
+ body: JSON.stringify(ep),
32
+ signal: AbortSignal.timeout(TIMEOUT_MS),
33
+ });
34
+ } catch { /* best effort */ }
35
+ }
36
+
37
+ /** A single recall hit (content or name). */
38
+ export interface RecallHit {
39
+ content?: string;
40
+ name?: string;
41
+ }
42
+
43
+ /**
44
+ * POST a query to Graphiti `/search`; returns hits or `[]` on any failure
45
+ * (network error, timeout, non-2xx, malformed JSON).
46
+ * @param query - The search query.
47
+ * @param numResults - Max results requested.
48
+ * @param env - Env (for host/port overrides).
49
+ * @returns The recall hits, possibly empty.
50
+ */
51
+ export async function searchMemory(query: string, numResults: number, env: NodeJS.ProcessEnv = process.env): Promise<RecallHit[]> {
52
+ try {
53
+ const resp = await fetch(`${neuralBase(env)}/search`, {
54
+ method: "POST",
55
+ headers: { "Content-Type": "application/json" },
56
+ body: JSON.stringify({ query, num_results: numResults }),
57
+ signal: AbortSignal.timeout(TIMEOUT_MS),
58
+ });
59
+ if (!resp.ok) return [];
60
+ const data = (await resp.json()) as { results?: RecallHit[] };
61
+ return Array.isArray(data.results) ? data.results : [];
62
+ } catch {
63
+ return [];
64
+ }
65
+ }
@@ -0,0 +1,42 @@
1
+ /**
2
+ * fuse-memory-neural scope dispatcher (async; the handlers hit Graphiti over
3
+ * HTTP, best-effort). Routes by event: SessionStart recalls past lessons,
4
+ * PostToolUse captures Bash errors / tracks graphiti+qdrant ops, SubagentStop
5
+ * captures agent conclusions. Returns the native stdout when handled, or `null`
6
+ * to fall through to the generic pipeline.
7
+ */
8
+ import { captureAgentLesson } from "./agent-lesson";
9
+ import { captureBashError } from "./capture-error";
10
+ import { trackMemoryOp } from "./track-ops";
11
+ import { recallOnSession } from "./recall";
12
+
13
+ /** Is this a graphiti/qdrant MCP tool call? */
14
+ function isMemoryTool(tool: string): boolean {
15
+ return tool.startsWith("mcp__graphiti") || tool.startsWith("mcp__qdrant");
16
+ }
17
+
18
+ /**
19
+ * Dispatch a memory-scope lifecycle event to its ported handler.
20
+ * @param event - Raw hook event name.
21
+ * @param payload - Raw hook payload.
22
+ * @param cwd - Project root.
23
+ * @param now - Clock.
24
+ * @returns The native stdout, or `null` when unhandled.
25
+ */
26
+ export async function dispatchMemory(event: string, payload: Record<string, unknown>, cwd: string, now: number): Promise<string | null> {
27
+ if (event === "SessionStart") return recallOnSession(cwd, now);
28
+ if (event === "SubagentStop" || event === "Stop") {
29
+ await captureAgentLesson(payload, now);
30
+ return "";
31
+ }
32
+ if (event === "PostToolUse") {
33
+ const tool = typeof payload.tool_name === "string" ? payload.tool_name : "";
34
+ if (tool === "Bash") return captureBashError(payload, now);
35
+ if (isMemoryTool(tool)) {
36
+ trackMemoryOp(payload, now);
37
+ return "";
38
+ }
39
+ return null;
40
+ }
41
+ return null;
42
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * SessionStart memory handler. Ports `recall-on-session.py`: detect the project
3
+ * type, recall relevant lessons from Graphiti, log the recall, and inject a
4
+ * neural-memory-recall additionalContext block.
5
+ */
6
+ import { basename } from "node:path";
7
+ import { contextResponse } from "../../../adapters/claude";
8
+ import { isoUtc } from "../security/skill-state";
9
+ import { searchMemory } from "./client";
10
+ import { appendMemoryLog, detectProjectType } from "./state";
11
+
12
+ /**
13
+ * Handle SessionStart: recall past lessons for this project and return the
14
+ * native additionalContext stdout (or "" when there is nothing to recall).
15
+ * @param cwd - Project root.
16
+ * @param now - Clock.
17
+ * @returns The native stdout (possibly empty).
18
+ */
19
+ export async function recallOnSession(cwd: string, now: number): Promise<string> {
20
+ const projectType = detectProjectType(cwd);
21
+ const projectName = basename(cwd);
22
+ const hits = await searchMemory(`${projectType} ${projectName} common errors`, 5);
23
+ appendMemoryLog("recalls.log", `[${isoUtc(now)}] session_recall | ${projectType} | ${projectName}`);
24
+ if (hits.length === 0) return "";
25
+ const lessons = hits.slice(0, 5).map((r) => `- ${r.content || r.name || "unknown"}`).join("\n");
26
+ const ctx =
27
+ `Relevant lessons from past sessions:\n${lessons}\n` +
28
+ `For deeper search: use mcp__qdrant__qdrant-find with project-specific queries.`;
29
+ return contextResponse("SessionStart", ctx);
30
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Salience + severity scoring for the memory-neural scope. Ports the
3
+ * `_severity` / `_agent_severity` helpers and the shared salience formula from
4
+ * `auto-capture-error.py` and `capture-agent-lesson.py`.
5
+ */
6
+
7
+ /** Episodes at or below this salience are not stored. */
8
+ export const SALIENCE_THRESHOLD = 0.3;
9
+
10
+ /** Severity (1-10) of a Bash stderr by keyword (mirrors auto-capture-error). */
11
+ export function bashSeverity(stderr: string): number {
12
+ const s = stderr.toLowerCase();
13
+ if (s.includes("fatal") || s.includes("panic")) return 10;
14
+ if (s.includes("error") || s.includes("failed")) return 8;
15
+ if (s.includes("warning")) return 4;
16
+ if (s.includes("deprecated")) return 2;
17
+ return 5;
18
+ }
19
+
20
+ /** Severity (1-10) of a finished agent by name (mirrors capture-agent-lesson). */
21
+ export function agentSeverity(name: string): number {
22
+ if (name === "sniper" || name === "sniper-faster") return 8;
23
+ if (name === "research-expert") return 6;
24
+ if (name.endsWith("-expert")) return 7;
25
+ return 5;
26
+ }
27
+
28
+ /** Salience from severity: 0.40·sev/10 + 0.30 + 0.20·0.5 + 0.10·0.5. */
29
+ export function salience(severity: number): number {
30
+ return (0.4 * severity) / 10 + 0.3 + 0.2 * 0.5 + 0.1 * 0.5;
31
+ }