@fusengine/harness 0.1.90 → 0.1.91

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 (417) 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-C43gA-Pr.mjs} +701 -134
  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-BjG6unTj.mjs +317 -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 +27 -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.ts +46 -0
  44. package/src/adapters/cursor/events.ts +38 -0
  45. package/src/adapters/cursor/index.ts +43 -0
  46. package/src/adapters/cursor/interfaces/types.ts +88 -0
  47. package/src/adapters/cursor/native-response.ts +158 -0
  48. package/src/adapters/cursor/normalize.ts +123 -0
  49. package/src/adapters/cursor/respond.ts +123 -0
  50. package/src/adapters/gemini/index.ts +36 -0
  51. package/src/adapters/hermes/index.ts +55 -0
  52. package/src/adapters/hermes/interfaces/types.ts +26 -0
  53. package/src/adapters/kimi/index.ts +96 -0
  54. package/src/adapters/kimi/interfaces/types.ts +55 -0
  55. package/src/cache/compact.ts +55 -0
  56. package/src/cache/index.ts +5 -0
  57. package/src/cache/io.ts +38 -0
  58. package/src/cache/mcp-response.ts +39 -0
  59. package/src/cache/mcp-store.ts +70 -0
  60. package/src/cache/store.ts +93 -0
  61. package/src/changelog/fetch.ts +79 -0
  62. package/src/cli/bin.ts +100 -0
  63. package/src/cli/cursor-event-scanner.ts +175 -0
  64. package/src/cli/cursor-stdin-reader.ts +65 -0
  65. package/src/cli/doctor.ts +96 -0
  66. package/src/cli/hook-io.ts +130 -0
  67. package/src/cli/hook-sound.ts +49 -0
  68. package/src/cli/index.ts +1 -0
  69. package/src/cli/json-primitive-scanner.ts +83 -0
  70. package/src/cli/run.ts +36 -0
  71. package/src/cli/scope.ts +31 -0
  72. package/src/codex-rules/build.ts +63 -0
  73. package/src/codex-rules/index.ts +8 -0
  74. package/src/codex-rules/mapping.ts +42 -0
  75. package/src/codex-rules/rules/absolute-paths.ts +52 -0
  76. package/src/codex-rules/rules/ask.ts +44 -0
  77. package/src/codex-rules/rules/chmod-ask.ts +57 -0
  78. package/src/codex-rules/rules/dd.ts +23 -0
  79. package/src/codex-rules/rules/diskutil.ts +33 -0
  80. package/src/codex-rules/rules/mkfs.ts +42 -0
  81. package/src/codex-rules/rules/privilege.ts +49 -0
  82. package/src/codex-rules/rules/remote-fetch.ts +31 -0
  83. package/src/codex-rules/rules/rm-variants.ts +58 -0
  84. package/src/codex-rules/rules/single-commands.ts +57 -0
  85. package/src/codex-rules/skip-list.ts +25 -0
  86. package/src/codex-rules/starlark.ts +46 -0
  87. package/src/codex-rules/types.ts +36 -0
  88. package/src/config/dotenv.ts +55 -0
  89. package/src/config/env.ts +10 -0
  90. package/src/config/home-dir.ts +36 -0
  91. package/src/config/index.ts +5 -0
  92. package/src/config/layout.ts +52 -0
  93. package/src/config/limits.ts +56 -0
  94. package/src/config/ttl.ts +30 -0
  95. package/src/detect/harness.ts +73 -0
  96. package/src/detect/index.ts +1 -0
  97. package/src/detect/interfaces/types.ts +18 -0
  98. package/src/freshness/agent-evidence-record.ts +95 -0
  99. package/src/freshness/agent-evidence.ts +95 -0
  100. package/src/freshness/codex-spawn-evidence.ts +91 -0
  101. package/src/freshness/doc-helpers.ts +90 -0
  102. package/src/freshness/evidence-harvest-io.ts +72 -0
  103. package/src/freshness/evidence-harvest.ts +61 -0
  104. package/src/freshness/explore-tools.ts +95 -0
  105. package/src/freshness/index.ts +2 -0
  106. package/src/freshness/query-framework.ts +73 -0
  107. package/src/freshness/ref-evidence.ts +49 -0
  108. package/src/freshness/ref-journal.ts +98 -0
  109. package/src/freshness/trivial-edit-counter.ts +30 -0
  110. package/src/index.ts +11 -0
  111. package/src/init/index.ts +2 -0
  112. package/src/init/run.ts +32 -0
  113. package/src/init/templates.ts +75 -0
  114. package/src/memory/gitignore.ts +14 -0
  115. package/src/memory/index.ts +3 -0
  116. package/src/memory/registry.ts +39 -0
  117. package/src/memory/session-roots.ts +99 -0
  118. package/src/memory/state.ts +58 -0
  119. package/src/policy/apex-authorization.ts +100 -0
  120. package/src/policy/apex-gates.ts +91 -0
  121. package/src/policy/apex-target-steps.ts +73 -0
  122. package/src/policy/apex-target.ts +49 -0
  123. package/src/policy/apex-task-context.ts +81 -0
  124. package/src/policy/apex.ts +94 -0
  125. package/src/policy/cartographer/build-tree.ts +69 -0
  126. package/src/policy/cartographer/describe.ts +53 -0
  127. package/src/policy/cartographer/entry.ts +38 -0
  128. package/src/policy/cartographer/frontmatter.ts +48 -0
  129. package/src/policy/cartographer/indicators.ts +34 -0
  130. package/src/policy/claude-md-context.ts +66 -0
  131. package/src/policy/conventions/index.ts +11 -0
  132. package/src/policy/conventions/interfaces.ts +71 -0
  133. package/src/policy/conventions/langs.ts +53 -0
  134. package/src/policy/conventions/query.ts +49 -0
  135. package/src/policy/conventions/react-hooks.ts +18 -0
  136. package/src/policy/conventions/store-or-query.ts +21 -0
  137. package/src/policy/conventions/stores.ts +56 -0
  138. package/src/policy/conventions/strip.ts +87 -0
  139. package/src/policy/conventions/verdict.ts +38 -0
  140. package/src/policy/creation-intent.ts +11 -0
  141. package/src/policy/deny-loop.ts +88 -0
  142. package/src/policy/design/allowed-write.ts +28 -0
  143. package/src/policy/design/content-checks.ts +45 -0
  144. package/src/policy/design/corpus-fs.ts +21 -0
  145. package/src/policy/design/corpus-resolve-cache.ts +49 -0
  146. package/src/policy/design/corpus-resolve.ts +99 -0
  147. package/src/policy/design/corpus.ts +92 -0
  148. package/src/policy/design/design-system-rules.ts +45 -0
  149. package/src/policy/design/flag.ts +30 -0
  150. package/src/policy/design/gates-pipeline.ts +105 -0
  151. package/src/policy/design/gates.ts +87 -0
  152. package/src/policy/design/screenshot-tools.ts +24 -0
  153. package/src/policy/design/skill-evidence.ts +34 -0
  154. package/src/policy/design/skill-gate.ts +81 -0
  155. package/src/policy/design/skill-triggers.ts +51 -0
  156. package/src/policy/design/state.ts +92 -0
  157. package/src/policy/design/template-urls.ts +54 -0
  158. package/src/policy/design/transitions.ts +68 -0
  159. package/src/policy/detect-claude-md-project-type.ts +32 -0
  160. package/src/policy/detect-framework.ts +99 -0
  161. package/src/policy/detect-primitive-checks.ts +100 -0
  162. package/src/policy/detect-primitive-lib.ts +55 -0
  163. package/src/policy/detect-project.ts +99 -0
  164. package/src/policy/edit-outcome.ts +53 -0
  165. package/src/policy/evaluate.ts +81 -0
  166. package/src/policy/expert-agents.ts +60 -0
  167. package/src/policy/file-size-scope.ts +32 -0
  168. package/src/policy/file-size.ts +99 -0
  169. package/src/policy/framework-solid-exclude.ts +52 -0
  170. package/src/policy/framework-solid-extended.ts +93 -0
  171. package/src/policy/framework-solid-gates-systems.ts +99 -0
  172. package/src/policy/framework-solid-gates.ts +98 -0
  173. package/src/policy/framework-solid.ts +88 -0
  174. package/src/policy/gemini-mcp-gate.ts +83 -0
  175. package/src/policy/git-gates.ts +40 -0
  176. package/src/policy/guards/bash-command-anchor.ts +40 -0
  177. package/src/policy/guards/bash-write-patterns.ts +140 -0
  178. package/src/policy/guards/bash-write-redirects.ts +201 -0
  179. package/src/policy/guards/bash-write-safe-paths.ts +62 -0
  180. package/src/policy/guards/bash-write.ts +76 -0
  181. package/src/policy/guards/context.ts +12 -0
  182. package/src/policy/guards/index.ts +61 -0
  183. package/src/policy/guards/install.ts +22 -0
  184. package/src/policy/guards/interface-separation-ext.ts +41 -0
  185. package/src/policy/guards/interface-separation.ts +100 -0
  186. package/src/policy/guards/protected-path.ts +100 -0
  187. package/src/policy/guards/security.ts +97 -0
  188. package/src/policy/index.ts +19 -0
  189. package/src/policy/interfaces/types.ts +46 -0
  190. package/src/policy/js-gate-route.ts +51 -0
  191. package/src/policy/lessons/lesson-gate.ts +0 -0
  192. package/src/policy/lessons/trigger-index.ts +84 -0
  193. package/src/policy/lessons/types.ts +31 -0
  194. package/src/policy/module-layout.ts +31 -0
  195. package/src/policy/nearest-manifest.ts +75 -0
  196. package/src/policy/never-approval.ts +95 -0
  197. package/src/policy/patterns.ts +80 -0
  198. package/src/policy/seo/validate.ts +51 -0
  199. package/src/policy/shadcn-project.ts +22 -0
  200. package/src/policy/shadcn-skill-gate.ts +97 -0
  201. package/src/policy/shell-read-refs.ts +96 -0
  202. package/src/policy/skill-path.ts +46 -0
  203. package/src/policy/skill-patterns/laravel.ts +40 -0
  204. package/src/policy/skill-patterns/nextjs.ts +29 -0
  205. package/src/policy/skill-patterns/react.ts +27 -0
  206. package/src/policy/skill-patterns/shadcn-subskills.ts +33 -0
  207. package/src/policy/skill-patterns/shadcn.ts +66 -0
  208. package/src/policy/skill-patterns/swift.ts +30 -0
  209. package/src/policy/skill-patterns/tailwind.ts +40 -0
  210. package/src/policy/skill-source.ts +52 -0
  211. package/src/policy/skill-trigger-patterns.ts +33 -0
  212. package/src/policy/skill-triggers.ts +108 -0
  213. package/src/policy/tailwind-skill-gate.ts +63 -0
  214. package/src/policy/trivial-edits.ts +52 -0
  215. package/src/policy/verbosity.ts +39 -0
  216. package/src/prompt/index.ts +1 -0
  217. package/src/prompt/types.ts +36 -0
  218. package/src/refs/discover.ts +69 -0
  219. package/src/refs/frontmatter.ts +24 -0
  220. package/src/refs/index.ts +4 -0
  221. package/src/refs/loader.ts +59 -0
  222. package/src/refs/ref-key.ts +41 -0
  223. package/src/refs/router.ts +63 -0
  224. package/src/refs/types.ts +25 -0
  225. package/src/runtime/activity.ts +99 -0
  226. package/src/runtime/apply-patch-gate.ts +41 -0
  227. package/src/runtime/burst-window.ts +21 -0
  228. package/src/runtime/codex-shell-tool.ts +37 -0
  229. package/src/runtime/command-string.ts +37 -0
  230. package/src/runtime/confirm/codex-confirm.ts +126 -0
  231. package/src/runtime/confirm/codex-prompt-origin.ts +16 -0
  232. package/src/runtime/confirm/confirm-code.ts +33 -0
  233. package/src/runtime/confirm/confirm-gate.ts +65 -0
  234. package/src/runtime/confirm/confirm-irreversible.ts +32 -0
  235. package/src/runtime/confirm/confirm-pending.ts +34 -0
  236. package/src/runtime/confirm/confirm-state.ts +56 -0
  237. package/src/runtime/confirm/confirm-subagent.ts +112 -0
  238. package/src/runtime/confirm/confirm-submit.ts +38 -0
  239. package/src/runtime/core-guards-active.ts +84 -0
  240. package/src/runtime/core-guards-wired.ts +47 -0
  241. package/src/runtime/deny-loop-store.ts +94 -0
  242. package/src/runtime/deny-notice.ts +52 -0
  243. package/src/runtime/design-cache-resolve.ts +51 -0
  244. package/src/runtime/design-content-gate.ts +95 -0
  245. package/src/runtime/design-files-gate.ts +114 -0
  246. package/src/runtime/design-helpers.ts +93 -0
  247. package/src/runtime/design-lifecycle.ts +39 -0
  248. package/src/runtime/design-read-credit.ts +28 -0
  249. package/src/runtime/design.ts +116 -0
  250. package/src/runtime/dev-context.ts +43 -0
  251. package/src/runtime/dry-patterns.ts +32 -0
  252. package/src/runtime/dry.ts +96 -0
  253. package/src/runtime/fragment-registry.ts +33 -0
  254. package/src/runtime/framework-skill-gate.ts +79 -0
  255. package/src/runtime/fs-cleanup.ts +71 -0
  256. package/src/runtime/gate-apex.ts +81 -0
  257. package/src/runtime/gate-helpers.ts +52 -0
  258. package/src/runtime/gate-input.ts +26 -0
  259. package/src/runtime/gate.ts +128 -0
  260. package/src/runtime/handle-post.ts +104 -0
  261. package/src/runtime/handle-pre.ts +137 -0
  262. package/src/runtime/handle-scope-async.ts +29 -0
  263. package/src/runtime/handle-types.ts +27 -0
  264. package/src/runtime/handle.ts +116 -0
  265. package/src/runtime/home-state.ts +55 -0
  266. package/src/runtime/index.ts +18 -0
  267. package/src/runtime/inform.ts +28 -0
  268. package/src/runtime/inject-budget-recap.ts +47 -0
  269. package/src/runtime/inject-budget.ts +81 -0
  270. package/src/runtime/inject-context.ts +85 -0
  271. package/src/runtime/inject-dedup-exclusive.ts +98 -0
  272. package/src/runtime/inject-dedup.ts +89 -0
  273. package/src/runtime/is-agent-tool.ts +19 -0
  274. package/src/runtime/lifecycle/agent-files.ts +49 -0
  275. package/src/runtime/lifecycle/agent-memory.ts +82 -0
  276. package/src/runtime/lifecycle/agent-transcript.ts +80 -0
  277. package/src/runtime/lifecycle/aipilot/analytics.ts +99 -0
  278. package/src/runtime/lifecycle/aipilot/apex-task-store.ts +61 -0
  279. package/src/runtime/lifecycle/aipilot/cache-base.ts +48 -0
  280. package/src/runtime/lifecycle/aipilot/cache-doc.ts +100 -0
  281. package/src/runtime/lifecycle/aipilot/cache-lessons.ts +58 -0
  282. package/src/runtime/lifecycle/aipilot/cache-test.ts +70 -0
  283. package/src/runtime/lifecycle/aipilot/curate-lessons.ts +64 -0
  284. package/src/runtime/lifecycle/aipilot/dispatch-aipilot.ts +100 -0
  285. package/src/runtime/lifecycle/aipilot/doc-cache-gate.ts +69 -0
  286. package/src/runtime/lifecycle/aipilot/inject-apex.ts +98 -0
  287. package/src/runtime/lifecycle/aipilot/inject-doc.ts +62 -0
  288. package/src/runtime/lifecycle/aipilot/inject-explore.ts +64 -0
  289. package/src/runtime/lifecycle/aipilot/inject-lessons.ts +61 -0
  290. package/src/runtime/lifecycle/aipilot/inject-test.ts +48 -0
  291. package/src/runtime/lifecycle/aipilot/lesson-archive.ts +60 -0
  292. package/src/runtime/lifecycle/aipilot/lesson-inject.ts +98 -0
  293. package/src/runtime/lifecycle/aipilot/lesson-parse.ts +74 -0
  294. package/src/runtime/lifecycle/aipilot/lessons.ts +72 -0
  295. package/src/runtime/lifecycle/aipilot/promote-global-lessons.ts +92 -0
  296. package/src/runtime/lifecycle/aipilot/solid-compliance.ts +69 -0
  297. package/src/runtime/lifecycle/aipilot/solid-notice.ts +47 -0
  298. package/src/runtime/lifecycle/aipilot/solid-transcript.ts +60 -0
  299. package/src/runtime/lifecycle/aipilot/source-scan.ts +56 -0
  300. package/src/runtime/lifecycle/aipilot/sync-task.ts +75 -0
  301. package/src/runtime/lifecycle/aipilot/transcript.ts +88 -0
  302. package/src/runtime/lifecycle/aipilot/types.ts +72 -0
  303. package/src/runtime/lifecycle/auto-document-reads.ts +73 -0
  304. package/src/runtime/lifecycle/cartographer/detect.ts +57 -0
  305. package/src/runtime/lifecycle/cartographer/ecosystem-map.ts +56 -0
  306. package/src/runtime/lifecycle/cartographer/fs-util.ts +64 -0
  307. package/src/runtime/lifecycle/cartographer/merge.ts +51 -0
  308. package/src/runtime/lifecycle/cartographer/project-map.ts +49 -0
  309. package/src/runtime/lifecycle/cartographer/scan-hooks.ts +48 -0
  310. package/src/runtime/lifecycle/cartographer/scan.ts +81 -0
  311. package/src/runtime/lifecycle/cartographer/session-start.ts +33 -0
  312. package/src/runtime/lifecycle/cartographer/track-enrichment.ts +45 -0
  313. package/src/runtime/lifecycle/cartographer/write-plugin-map.ts +43 -0
  314. package/src/runtime/lifecycle/cartographer/write-tree.ts +44 -0
  315. package/src/runtime/lifecycle/changelog-research.ts +43 -0
  316. package/src/runtime/lifecycle/check-file-size.ts +61 -0
  317. package/src/runtime/lifecycle/codex-resync/fingerprint.ts +63 -0
  318. package/src/runtime/lifecycle/codex-resync/lock.ts +46 -0
  319. package/src/runtime/lifecycle/codex-resync/managed-destination.ts +39 -0
  320. package/src/runtime/lifecycle/codex-resync/materialize-agents.ts +57 -0
  321. package/src/runtime/lifecycle/codex-resync/plugin-files.ts +65 -0
  322. package/src/runtime/lifecycle/codex-resync/plugin-roots.ts +62 -0
  323. package/src/runtime/lifecycle/codex-resync/resync.ts +45 -0
  324. package/src/runtime/lifecycle/codex-resync/symlink-commands.ts +25 -0
  325. package/src/runtime/lifecycle/dispatch.ts +109 -0
  326. package/src/runtime/lifecycle/failure-lesson.ts +51 -0
  327. package/src/runtime/lifecycle/index.ts +34 -0
  328. package/src/runtime/lifecycle/inject-rules.ts +50 -0
  329. package/src/runtime/lifecycle/instructions-loaded.ts +22 -0
  330. package/src/runtime/lifecycle/kimi-rules-native.ts +44 -0
  331. package/src/runtime/lifecycle/lessons/dispatch.ts +88 -0
  332. package/src/runtime/lifecycle/lessons/reminder.ts +89 -0
  333. package/src/runtime/lifecycle/lessons/state.ts +23 -0
  334. package/src/runtime/lifecycle/memory/agent-lesson.ts +35 -0
  335. package/src/runtime/lifecycle/memory/capture-error.ts +41 -0
  336. package/src/runtime/lifecycle/memory/client.ts +65 -0
  337. package/src/runtime/lifecycle/memory/dispatch.ts +42 -0
  338. package/src/runtime/lifecycle/memory/recall.ts +30 -0
  339. package/src/runtime/lifecycle/memory/salience.ts +31 -0
  340. package/src/runtime/lifecycle/memory/state.ts +49 -0
  341. package/src/runtime/lifecycle/memory/track-ops.ts +15 -0
  342. package/src/runtime/lifecycle/post-compact.ts +38 -0
  343. package/src/runtime/lifecycle/post-edit-ts.ts +49 -0
  344. package/src/runtime/lifecycle/post-tracking.ts +40 -0
  345. package/src/runtime/lifecycle/pre-compact.ts +38 -0
  346. package/src/runtime/lifecycle/rules-root.ts +83 -0
  347. package/src/runtime/lifecycle/security/check-skill.ts +63 -0
  348. package/src/runtime/lifecycle/security/scan-patterns.ts +53 -0
  349. package/src/runtime/lifecycle/security/scan.ts +76 -0
  350. package/src/runtime/lifecycle/security/skill-state.ts +47 -0
  351. package/src/runtime/lifecycle/security/track-mcp.ts +29 -0
  352. package/src/runtime/lifecycle/security/track-skill-read.ts +31 -0
  353. package/src/runtime/lifecycle/seo/post-tool-use.ts +45 -0
  354. package/src/runtime/lifecycle/session-end.ts +17 -0
  355. package/src/runtime/lifecycle/session-start.ts +57 -0
  356. package/src/runtime/lifecycle/snapshot/board.ts +27 -0
  357. package/src/runtime/lifecycle/snapshot/format.ts +60 -0
  358. package/src/runtime/lifecycle/snapshot/git.ts +71 -0
  359. package/src/runtime/lifecycle/snapshot/index.ts +52 -0
  360. package/src/runtime/lifecycle/snapshot/version.ts +34 -0
  361. package/src/runtime/lifecycle/solid-detect.ts +56 -0
  362. package/src/runtime/lifecycle/stop-core.ts +35 -0
  363. package/src/runtime/lifecycle/subagent-cache.ts +61 -0
  364. package/src/runtime/lifecycle/task-completed.ts +94 -0
  365. package/src/runtime/lifecycle/teammate-idle-check.ts +63 -0
  366. package/src/runtime/lifecycle/teammate-idle.ts +28 -0
  367. package/src/runtime/lifecycle/tool-failure.ts +24 -0
  368. package/src/runtime/lifecycle/track-changes.ts +61 -0
  369. package/src/runtime/lifecycle/validate-solid.ts +70 -0
  370. package/src/runtime/lifecycle/validate-tailwind.ts +80 -0
  371. package/src/runtime/lifecycle-bridge.ts +74 -0
  372. package/src/runtime/mcp-key.ts +44 -0
  373. package/src/runtime/mcp-tool-name.ts +92 -0
  374. package/src/runtime/mcp.ts +91 -0
  375. package/src/runtime/modular.ts +77 -0
  376. package/src/runtime/normalize.ts +107 -0
  377. package/src/runtime/notices.ts +91 -0
  378. package/src/runtime/notification-sound.ts +49 -0
  379. package/src/runtime/notifications.ts +77 -0
  380. package/src/runtime/paths.ts +59 -0
  381. package/src/runtime/post-fanout.ts +41 -0
  382. package/src/runtime/post-outcome.ts +61 -0
  383. package/src/runtime/pre-allow.ts +97 -0
  384. package/src/runtime/precommit.ts +60 -0
  385. package/src/runtime/prompt-text.ts +38 -0
  386. package/src/runtime/receipt-capture.ts +34 -0
  387. package/src/runtime/record.ts +26 -0
  388. package/src/runtime/respond.ts +92 -0
  389. package/src/runtime/solid-file-size-gate.ts +58 -0
  390. package/src/runtime/solid-pre.ts +53 -0
  391. package/src/runtime/storage.ts +9 -0
  392. package/src/statusline/colors.ts +31 -0
  393. package/src/statusline/constants.ts +17 -0
  394. package/src/statusline/formatters.ts +54 -0
  395. package/src/statusline/index.ts +4 -0
  396. package/src/statusline/progress-bar.ts +46 -0
  397. package/src/tracking/codex-post-failure.ts +73 -0
  398. package/src/tracking/index.ts +2 -0
  399. package/src/tracking/integrity.ts +99 -0
  400. package/src/tracking/one-shot-dedup.ts +34 -0
  401. package/src/tracking/one-shot-failure.ts +42 -0
  402. package/src/tracking/one-shot-store.ts +95 -0
  403. package/src/tracking/one-shot.ts +93 -0
  404. package/src/tracking/receipts.ts +95 -0
  405. package/src/tracking/session-state.ts +94 -0
  406. package/src/tracking/store.ts +96 -0
  407. package/src/tracking/track-compact.ts +100 -0
  408. package/src/tracking/track-diff.ts +50 -0
  409. package/src/tracking/track-journal.ts +97 -0
  410. package/src/tracking/track-lock-sync.ts +83 -0
  411. package/src/tracking/track-lock.ts +67 -0
  412. package/src/util/compact-json.ts +23 -0
  413. package/src/util/index.ts +2 -0
  414. package/src/util/json-io.ts +47 -0
  415. package/src/util/project-root.ts +41 -0
  416. package/src/util/runtime-io.ts +85 -0
  417. package/src/util/semver.ts +30 -0
@@ -0,0 +1,158 @@
1
+ type FieldValidator = (value: unknown) => boolean;
2
+
3
+ interface NativeSchema {
4
+ fields: Readonly<Record<string, FieldValidator>>;
5
+ required?: readonly string[];
6
+ }
7
+
8
+ const stringValue: FieldValidator = (value) => typeof value === "string";
9
+ const booleanValue: FieldValidator = (value) => typeof value === "boolean";
10
+ const plainRecord = (value: unknown): value is Record<string, unknown> => {
11
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
12
+ try {
13
+ const prototype = Object.getPrototypeOf(value);
14
+ return prototype === Object.prototype || prototype === null;
15
+ } catch {
16
+ return false;
17
+ }
18
+ };
19
+
20
+ type JsonFrame = { value: unknown; leave?: false } | { value: object; leave: true };
21
+
22
+ function jsonChildren(value: object): unknown[] | null {
23
+ const keys = Reflect.ownKeys(value);
24
+ const descriptors = Object.getOwnPropertyDescriptors(value);
25
+ if (Array.isArray(value)) {
26
+ if (Object.getPrototypeOf(value) !== Array.prototype) return null;
27
+ const length = descriptors.length;
28
+ if (!length || !("value" in length) || !Number.isSafeInteger(length.value) || length.value < 0) return null;
29
+ if (keys.length !== length.value + 1 || keys.some((key) => typeof key === "symbol")) return null;
30
+ const children: unknown[] = [];
31
+ for (let index = 0; index < length.value; index += 1) {
32
+ const descriptor = descriptors[String(index)];
33
+ if (!descriptor?.enumerable || !("value" in descriptor)) return null;
34
+ children.push(descriptor.value);
35
+ }
36
+ return children;
37
+ }
38
+ if (!plainRecord(value) || keys.some((key) => typeof key === "symbol")) return null;
39
+ const children: unknown[] = [];
40
+ for (const key of keys) {
41
+ const descriptor = descriptors[key as string];
42
+ if (!descriptor?.enumerable || !("value" in descriptor)) return null;
43
+ children.push(descriptor.value);
44
+ }
45
+ return children;
46
+ }
47
+
48
+ function jsonValue(root: unknown): boolean {
49
+ const active = new WeakSet<object>();
50
+ const stack: JsonFrame[] = [{ value: root }];
51
+ while (stack.length > 0) {
52
+ const frame = stack.pop()!;
53
+ if (frame.leave) {
54
+ active.delete(frame.value);
55
+ continue;
56
+ }
57
+ const { value } = frame;
58
+ if (value === null || typeof value === "string" || typeof value === "boolean") continue;
59
+ if (typeof value === "number") {
60
+ if (!Number.isFinite(value)) return false;
61
+ continue;
62
+ }
63
+ if (typeof value !== "object" || active.has(value)) return false;
64
+ let children: unknown[] | null;
65
+ try {
66
+ children = jsonChildren(value);
67
+ } catch {
68
+ return false;
69
+ }
70
+ if (!children) return false;
71
+ active.add(value);
72
+ stack.push({ value, leave: true });
73
+ for (let index = children.length - 1; index >= 0; index -= 1) stack.push({ value: children[index] });
74
+ }
75
+ return true;
76
+ }
77
+
78
+ const recordValue: FieldValidator = (value) => plainRecord(value) && jsonValue(value);
79
+ const stringRecord: FieldValidator = (value) => {
80
+ if (!recordValue(value)) return false;
81
+ try {
82
+ return Object.values(Object.getOwnPropertyDescriptors(value as object))
83
+ .every((descriptor) => "value" in descriptor && typeof descriptor.value === "string");
84
+ } catch {
85
+ return false;
86
+ }
87
+ };
88
+ const stringArray: FieldValidator = (value) => Array.isArray(value) && value.every(stringValue);
89
+ const permission = (...values: string[]): FieldValidator => (value) => typeof value === "string" && values.includes(value);
90
+
91
+ const EMPTY: NativeSchema = { fields: {} };
92
+ const FOLLOWUP: NativeSchema = { fields: { followup_message: stringValue } };
93
+ const PERMISSION_ASK: NativeSchema = {
94
+ fields: { permission: permission("allow", "deny", "ask"), user_message: stringValue, agent_message: stringValue },
95
+ required: ["permission"],
96
+ };
97
+
98
+ const NATIVE_SCHEMAS = {
99
+ sessionStart: {
100
+ fields: { env: stringRecord, additional_context: stringValue, continue: booleanValue, user_message: stringValue },
101
+ },
102
+ sessionEnd: EMPTY,
103
+ beforeSubmitPrompt: { fields: { continue: booleanValue, user_message: stringValue }, required: ["continue"] },
104
+ preCompact: { fields: { user_message: stringValue } },
105
+ subagentStart: {
106
+ fields: { permission: permission("allow", "deny"), user_message: stringValue }, required: ["permission"],
107
+ },
108
+ subagentStop: FOLLOWUP,
109
+ preToolUse: {
110
+ fields: { ...PERMISSION_ASK.fields, updated_input: recordValue }, required: ["permission"],
111
+ },
112
+ postToolUse: { fields: { updated_mcp_tool_output: recordValue, additional_context: stringValue } },
113
+ postToolUseFailure: EMPTY,
114
+ beforeShellExecution: PERMISSION_ASK,
115
+ afterShellExecution: EMPTY,
116
+ beforeMCPExecution: PERMISSION_ASK,
117
+ afterMCPExecution: EMPTY,
118
+ beforeReadFile: {
119
+ fields: { permission: permission("allow", "deny"), user_message: stringValue }, required: ["permission"],
120
+ },
121
+ afterFileEdit: EMPTY,
122
+ beforeTabFileRead: { fields: { permission: permission("allow", "deny") }, required: ["permission"] },
123
+ afterTabFileEdit: EMPTY,
124
+ afterAgentResponse: EMPTY,
125
+ afterAgentThought: EMPTY,
126
+ stop: FOLLOWUP,
127
+ workspaceOpen: { fields: { pluginPaths: stringArray } },
128
+ } as const satisfies Record<string, NativeSchema>;
129
+
130
+ function isNativeCursorResponse(value: unknown, eventName: string): boolean {
131
+ try {
132
+ if (!recordValue(value)) return false;
133
+ const schema = NATIVE_SCHEMAS[eventName as keyof typeof NATIVE_SCHEMAS] as NativeSchema | undefined;
134
+ if (!schema) return false;
135
+ const descriptors = Object.getOwnPropertyDescriptors(value as object);
136
+ if (schema.required?.some((field) => !Object.hasOwn(descriptors, field))) return false;
137
+ return Reflect.ownKeys(descriptors).every((field) => {
138
+ if (typeof field !== "string") return false;
139
+ const descriptor = descriptors[field];
140
+ if (!Object.hasOwn(schema.fields, field)) return false;
141
+ const validate = schema.fields[field];
142
+ return descriptor?.enumerable === true && "value" in descriptor
143
+ && validate !== undefined && validate(descriptor.value);
144
+ });
145
+ } catch {
146
+ return false;
147
+ }
148
+ }
149
+
150
+ /** Preserve raw stdout only when JSON.parse proves a documented native Cursor response. */
151
+ export function parseNativeCursorStdout(stdout: string, eventName: string): string | null {
152
+ if (typeof stdout !== "string") return null;
153
+ try {
154
+ return isNativeCursorResponse(JSON.parse(stdout), eventName) ? stdout : null;
155
+ } catch {
156
+ return null;
157
+ }
158
+ }
@@ -0,0 +1,123 @@
1
+ import { commandToString } from "../../runtime/command-string";
2
+ import { cursorEventContract } from "./events";
3
+ import { cursorAbsolutePath, cursorPath, cursorWorkspaceRoots } from "./context";
4
+ import type { CursorExtractedEvent } from "./interfaces/types";
5
+
6
+ function record(value: unknown): Record<string, unknown> | undefined {
7
+ return typeof value === "object" && value !== null && !Array.isArray(value)
8
+ ? value as Record<string, unknown>
9
+ : undefined;
10
+ }
11
+
12
+ function str(value: unknown): string | undefined {
13
+ return typeof value === "string" ? value : undefined;
14
+ }
15
+
16
+ function inputRecord(value: unknown): Record<string, unknown> | undefined {
17
+ const direct = record(value);
18
+ if (direct || typeof value !== "string") return direct;
19
+ try {
20
+ return record(JSON.parse(value));
21
+ } catch {
22
+ return undefined;
23
+ }
24
+ }
25
+
26
+ const MCP_ROOT_FIELDS = ["mcp_server_name", "mcp_server_url", "url", "result_json", "duration"] as const;
27
+
28
+ function cursorMcpInput(raw: Record<string, unknown>, input: Record<string, unknown>): Record<string, unknown> {
29
+ const merged = { ...input };
30
+ for (const field of MCP_ROOT_FIELDS) {
31
+ if (Object.hasOwn(raw, field)) merged[field] = raw[field];
32
+ }
33
+ return merged;
34
+ }
35
+
36
+ function sanitizedCursorInput(input: Record<string, unknown>): Record<string, unknown> {
37
+ const safe = { ...input };
38
+ if (typeof safe.file_path === "string" && cursorPath(safe.file_path) === undefined) delete safe.file_path;
39
+ if (typeof safe.path === "string" && cursorPath(safe.path) === undefined) delete safe.path;
40
+ if (Object.hasOwn(safe, "cwd")) {
41
+ const cwd = cursorAbsolutePath(safe.cwd);
42
+ if (cwd) safe.cwd = cwd;
43
+ else delete safe.cwd;
44
+ }
45
+ if (Object.hasOwn(safe, "workspace_roots")) {
46
+ if (Array.isArray(safe.workspace_roots)) safe.workspace_roots = cursorWorkspaceRoots(safe.workspace_roots);
47
+ else delete safe.workspace_roots;
48
+ }
49
+ return safe;
50
+ }
51
+
52
+ function cursorToolName(raw: Record<string, unknown>, event: string, tool: string | undefined, hasCommand: boolean): string {
53
+ if (hasCommand) return "Bash";
54
+ const server = str(raw.mcp_server_name)?.trim().replace(/[^A-Za-z0-9_-]+/g, "_");
55
+ if (/^(before|after)MCPExecution$/i.test(event) && server && tool && !tool.startsWith("mcp__")) {
56
+ return `mcp__${server}__${tool}`;
57
+ }
58
+ if (tool === "Write") return "Edit";
59
+ return tool ?? "";
60
+ }
61
+
62
+ function cursorCommands(raw: Record<string, unknown>, input: Record<string, unknown>): string[] {
63
+ const candidates = [commandToString(raw.command), commandToString(input.command)]
64
+ .filter((candidate): candidate is string => candidate !== undefined);
65
+ return [...new Set(candidates)];
66
+ }
67
+
68
+ /**
69
+ * Extract one Cursor hook payload into the shared runtime shape.
70
+ *
71
+ * @param payload - Cursor hook stdin payload.
72
+ * @returns Cursor-specific phase, tool, command, and edit fields.
73
+ */
74
+ export function extractCursorEvent(payload: object): CursorExtractedEvent {
75
+ const raw = payload as Record<string, unknown>;
76
+ const hookEvent = str(raw.hook_event_name) ?? "";
77
+ const parsedInput = inputRecord(raw.tool_input);
78
+ const mcpInput = parsedInput && /^(before|after)MCPExecution$/i.test(hookEvent)
79
+ ? cursorMcpInput(raw, parsedInput)
80
+ : parsedInput;
81
+ const input = sanitizedCursorInput(mcpInput ?? raw);
82
+ const contract = cursorEventContract(hookEvent);
83
+ const metadata = {
84
+ eventName: hookEvent,
85
+ lifecycleEvent: contract.lifecycle,
86
+ responseKind: contract.response,
87
+ blockable: contract.blockable,
88
+ cwd: cursorAbsolutePath(raw.cwd),
89
+ workspaceRoots: cursorWorkspaceRoots(raw.workspace_roots),
90
+ };
91
+ if (!contract.known) {
92
+ return { ...metadata, phase: contract.phase, tool: "", input };
93
+ }
94
+ const afterFileEdit = /^afterFileEdit$/i.test(hookEvent);
95
+ const beforeReadFile = /^beforeReadFile$/i.test(hookEvent);
96
+ const beforeTabFileRead = /^beforeTabFileRead$/i.test(hookEvent);
97
+ const edits = Array.isArray(raw.edits)
98
+ ? raw.edits.map(record).filter((edit): edit is Record<string, unknown> => edit !== undefined)
99
+ : [];
100
+ const filePath = cursorPath(raw.file_path);
101
+ if (filePath && edits.length > 0) {
102
+ const files = edits.map((edit) => ({
103
+ filePath,
104
+ oldString: str(edit.old_string),
105
+ content: str(edit.new_string) ?? "",
106
+ op: "update" as const,
107
+ }));
108
+ return { ...metadata, phase: contract.phase, tool: "Edit", input, filePath, content: files.map((file) => file.content).join("\n"), files };
109
+ }
110
+ const commands = cursorCommands(raw, input);
111
+ const command = commands[0];
112
+ return {
113
+ ...metadata,
114
+ phase: contract.phase,
115
+ tool: afterFileEdit ? "Edit" : beforeReadFile || beforeTabFileRead ? "Read" : cursorToolName(raw, hookEvent, str(raw.tool_name), command !== undefined),
116
+ input,
117
+ filePath: cursorPath(input.file_path) ?? cursorPath(input.path),
118
+ content: str(input.content) ?? str(input.new_string),
119
+ oldString: str(input.old_string),
120
+ command,
121
+ commandCandidates: /^beforeMCPExecution$/i.test(hookEvent) && commands.length > 0 ? commands : undefined,
122
+ };
123
+ }
@@ -0,0 +1,123 @@
1
+ import { formatPrompt, type Prompt } from "../../prompt/types";
2
+ import { cursorEventContract } from "./events";
3
+ import { parseNativeCursorStdout } from "./native-response";
4
+
5
+ const AGENT_MESSAGE_EVENTS = new Set([
6
+ "preToolUse",
7
+ "beforeShellExecution",
8
+ "beforeMCPExecution",
9
+ ]);
10
+ const USER_MESSAGE_EVENTS = new Set([
11
+ "preToolUse",
12
+ "beforeShellExecution",
13
+ "beforeMCPExecution",
14
+ "beforeReadFile",
15
+ "subagentStart",
16
+ ]);
17
+
18
+ function permissionMessages(eventName: string, userMessage?: string, agentMessage?: string): Record<string, string> {
19
+ return {
20
+ ...(userMessage && USER_MESSAGE_EVENTS.has(eventName) ? { user_message: userMessage } : {}),
21
+ ...(agentMessage && AGENT_MESSAGE_EVENTS.has(eventName) ? { agent_message: agentMessage } : {}),
22
+ };
23
+ }
24
+
25
+ function joinMessages(...values: unknown[]): string {
26
+ return values.filter((value): value is string => typeof value === "string" && value.length > 0).join("\n");
27
+ }
28
+
29
+ function isRecord(value: unknown): value is Record<string, unknown> {
30
+ return typeof value === "object" && value !== null && !Array.isArray(value);
31
+ }
32
+
33
+ /** Render a portable policy prompt using the native Cursor event contract. */
34
+ export function toCursorResponse(prompt: Prompt, eventName: string): string {
35
+ const contract = cursorEventContract(eventName);
36
+ const message = formatPrompt(prompt);
37
+ if (!contract.known || contract.response === "neutral" || contract.response === "plugin-paths") return "{}";
38
+ if (contract.response === "post-context" || contract.response === "session-context") {
39
+ return JSON.stringify({ additional_context: message });
40
+ }
41
+ if (contract.response === "followup") return JSON.stringify({ followup_message: message });
42
+ if (contract.response === "compact-notice") return JSON.stringify({ user_message: prompt.userMessage ?? message });
43
+ if (contract.response === "submit-control") {
44
+ return JSON.stringify({ continue: prompt.kind !== "block", user_message: prompt.userMessage ?? message });
45
+ }
46
+ if (prompt.kind === "inform") {
47
+ return JSON.stringify({
48
+ permission: "allow",
49
+ ...permissionMessages(eventName, prompt.userMessage, prompt.reason ? message : undefined),
50
+ });
51
+ }
52
+ const userMessage = prompt.kind === "ask"
53
+ ? `[downgraded from ask — Cursor does not enforce approval for this event]\n${message}`
54
+ : message;
55
+ return JSON.stringify({
56
+ permission: "deny",
57
+ ...permissionMessages(eventName, userMessage, userMessage),
58
+ });
59
+ }
60
+
61
+ /** Convert a shared lifecycle handler's output to the native Cursor envelope. */
62
+ export function toCursorLifecycleResponse(stdout: string, eventName: string): string {
63
+ const contract = cursorEventContract(eventName);
64
+ const native = parseNativeCursorStdout(stdout, eventName);
65
+ if (native !== null) return native;
66
+ let text = stdout;
67
+ let decision: "allow" | "deny" | "ask" | undefined;
68
+ let userMessage = "";
69
+ let agentMessage = "";
70
+ let decisionMessage = "";
71
+ let structured = false;
72
+ try {
73
+ const parsed: unknown = JSON.parse(stdout);
74
+ const subagentAsk = eventName === "subagentStart" && isRecord(parsed)
75
+ && Object.hasOwn(parsed, "permission") && parsed.permission === "ask";
76
+ const shared = (isRecord(parsed) ? parsed : {}) as {
77
+ hookSpecificOutput?: {
78
+ additionalContext?: string;
79
+ permissionDecision?: "allow" | "deny" | "ask";
80
+ permissionDecisionReason?: string;
81
+ };
82
+ systemMessage?: string;
83
+ user_message?: string;
84
+ reason?: string;
85
+ followup_message?: string;
86
+ };
87
+ structured = true;
88
+ decision = shared.hookSpecificOutput?.permissionDecision ?? (subagentAsk ? "ask" : undefined);
89
+ userMessage = shared.systemMessage
90
+ ?? (subagentAsk && Object.hasOwn(shared, "user_message") && typeof shared.user_message === "string"
91
+ ? shared.user_message : "");
92
+ decisionMessage = joinMessages(shared.hookSpecificOutput?.permissionDecisionReason, shared.reason);
93
+ agentMessage = joinMessages(shared.hookSpecificOutput?.additionalContext, decisionMessage);
94
+ text = joinMessages(agentMessage, userMessage, shared.followup_message);
95
+ } catch {
96
+ text = stdout;
97
+ }
98
+ if (contract.response === "neutral") return "{}";
99
+ if (!text) return contract.response === "permission" ? '{"permission":"allow"}' : "{}";
100
+ if (contract.response === "session-context" || contract.response === "post-context") {
101
+ return JSON.stringify({ additional_context: text });
102
+ }
103
+ if (contract.response === "permission") {
104
+ const permission = decision === "deny" || decision === "ask" ? "deny" : "allow";
105
+ const denied = permission === "deny";
106
+ // Cursor subagentStart can gate creation but has no model-context channel.
107
+ // Drop shared context and its "injected" notice on allow: preserving either
108
+ // would claim delivery the native event contract cannot perform.
109
+ if (eventName === "subagentStart" && !denied) return '{"permission":"allow"}';
110
+ return JSON.stringify({
111
+ permission,
112
+ ...permissionMessages(
113
+ eventName,
114
+ userMessage || (denied ? decisionMessage || agentMessage : ""),
115
+ agentMessage || (denied ? decisionMessage || userMessage : structured ? "" : text),
116
+ ),
117
+ });
118
+ }
119
+ if (contract.response === "followup") return JSON.stringify({ followup_message: text });
120
+ if (contract.response === "compact-notice") return JSON.stringify({ user_message: text });
121
+ if (contract.response === "submit-control") return JSON.stringify({ continue: true, user_message: text });
122
+ return "{}";
123
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Gemini CLI adapter (hook-mode). Schema per google-gemini/gemini-cli docs/hooks (2026):
3
+ * `BeforeTool` blocks via `{ decision: "deny", reason }` (or exit 2).
4
+ */
5
+ import { evaluate } from "../../policy/evaluate";
6
+ import { formatPrompt } from "../../prompt/types";
7
+
8
+ /** `BeforeTool` stdin payload (subset). */
9
+ export interface GeminiHookInput {
10
+ tool_name?: string;
11
+ tool_input?: { command?: string; path?: string; content?: string };
12
+ hook_event_name?: string;
13
+ }
14
+
15
+ /** Hook stdout response. */
16
+ export interface GeminiResponse {
17
+ decision?: "allow" | "deny";
18
+ reason?: string;
19
+ hookSpecificOutput?: { additionalContext?: string };
20
+ }
21
+
22
+ /** Evaluate a tool use; deny on a hard block, otherwise inject context. */
23
+ export function beforeTool(input: GeminiHookInput): GeminiResponse {
24
+ const i = input.tool_input;
25
+ const r = evaluate({
26
+ tool: input.tool_name ?? "write_file",
27
+ filePath: i?.path,
28
+ content: i?.content,
29
+ command: i?.command,
30
+ });
31
+ if (r.decision === "allow" || !r.prompt) return {};
32
+ const msg = formatPrompt(r.prompt);
33
+ return r.prompt.kind === "block"
34
+ ? { decision: "deny", reason: msg }
35
+ : { hookSpecificOutput: { additionalContext: msg } };
36
+ }
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Hermes Agent adapter (hook-mode). Nous Research's hermes-agent (2026) pipes a
3
+ * JSON payload to shell hooks on stdin and reads stdout back as JSON — config
4
+ * `~/.hermes/config.yaml` under `hooks:`, event `pre_tool_call`, scripts in
5
+ * `~/.hermes/agent-hooks/`. The INPUT shape matches Claude Code
6
+ * (`hook_event_name`/`tool_name`/`tool_input`/`cwd`), so the Claude reader is
7
+ * reused; the OUTPUT does not: a block is `{ decision: "block", reason }`
8
+ * (no `permissionDecision`, no interactive "ask"), `{}` allows, and
9
+ * `{ context }` injects LLM context. Tool fields verified: `terminal` takes
10
+ * `command`; `write_file` takes `path` + `content`.
11
+ */
12
+ import { evaluate } from "../../policy/evaluate";
13
+ import { formatPrompt, type Prompt } from "../../prompt/types";
14
+ import { readClaudeInput } from "../claude";
15
+ import { commandToString } from "../../runtime/command-string";
16
+ import type { HermesHookInput, HermesResponse } from "./interfaces/types";
17
+
18
+ export type { HermesHookInput, HermesResponse } from "./interfaces/types";
19
+
20
+ /** Read & parse the Hermes hook stdin payload (same wire shape as Claude). */
21
+ export async function readHermesInput(): Promise<HermesHookInput> {
22
+ return await readClaudeInput();
23
+ }
24
+
25
+ /**
26
+ * Render a portable {@link Prompt} as a Hermes hook response string:
27
+ * `block` -> `{decision:"block",reason}`; `ask`/`inform` -> non-blocking
28
+ * `{context}` — Hermes has no interactive "ask" state, so both degrade to
29
+ * context injection, mirroring the gemini-cli/cline routing in `respond()`.
30
+ * @param prompt - The portable policy prompt.
31
+ * @returns The native Hermes JSON response string.
32
+ */
33
+ export function toHermesResponse(prompt: Prompt): string {
34
+ const msg = formatPrompt(prompt);
35
+ const res: HermesResponse = prompt.kind === "block" ? { decision: "block", reason: msg } : { context: msg };
36
+ return JSON.stringify(res);
37
+ }
38
+
39
+ /**
40
+ * Run the bundled policy over a Hermes `pre_tool_call` payload and return the
41
+ * native response string, or null to allow (the hook then prints `{}`).
42
+ * @param input - Parsed `pre_tool_call` stdin payload.
43
+ * @returns Native response JSON, or null when the tool use is allowed.
44
+ */
45
+ export function guard(input: HermesHookInput): string | null {
46
+ const t = input.tool_input;
47
+ const r = evaluate({
48
+ tool: input.tool_name ?? "write_file",
49
+ filePath: t?.path ?? t?.file_path,
50
+ content: t?.content,
51
+ command: commandToString(t?.command),
52
+ });
53
+ if (r.decision === "allow" || !r.prompt) return null;
54
+ return toHermesResponse(r.prompt);
55
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Hermes Agent (NousResearch/hermes-agent) shell-hook wire types. Verified 2026
3
+ * against hermes-agent.nousresearch.com/docs/user-guide/features/hooks: hooks
4
+ * are configured in `~/.hermes/config.yaml` under `hooks:`, receive a JSON
5
+ * payload on stdin, and answer with JSON on stdout.
6
+ */
7
+
8
+ /** `pre_tool_call` stdin payload (subset). Same wire shape as Claude Code. */
9
+ export interface HermesHookInput {
10
+ hook_event_name?: string;
11
+ tool_name?: string;
12
+ tool_input?: { path?: string; file_path?: string; content?: string; command?: string | string[] };
13
+ session_id?: string;
14
+ cwd?: string;
15
+ }
16
+
17
+ /**
18
+ * Hook stdout response. `{}` allows; `{ decision: "block", reason }` cancels
19
+ * the tool call; `{ context }` injects LLM context (honored on `pre_llm_call`).
20
+ * Hermes has NO Claude-style `permissionDecision` and no interactive "ask".
21
+ */
22
+ export interface HermesResponse {
23
+ decision?: "block";
24
+ reason?: string;
25
+ context?: string;
26
+ }
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Kimi Code CLI adapter (hook-mode). Moonshot AI's Kimi Code CLI (v0.27.0,
3
+ * 2026) configures hooks in `~/.kimi-code/config.toml` (`[[hooks]]`: event,
4
+ * matcher, command, timeout — exactly 4 fields, any extra breaks config
5
+ * loading) and pipes a JSON payload to `command` on stdin, reading JSON back
6
+ * on stdout. Verified against kimi.com/code/docs/en/kimi-code-cli/
7
+ * customization/hooks.html (2026): the INPUT base shape
8
+ * (`hook_event_name`/`session_id`/`cwd`, `PreToolUse`/`Bash` carrying
9
+ * `tool_name` + `tool_input.command`) matches Claude Code's snake_case wire
10
+ * shape, so the Claude reader is reused — verified live against kimi-code
11
+ * v0.27.0 for both `PreToolUse`/`Bash` and `PreToolUse`/`Write`. Tool fields
12
+ * verified: `Bash` takes `command`; `Write` takes `path` + `content`; `Edit`
13
+ * takes `path` + `old_string`/`new_string` (per `docs/en/reference/tools.md`,
14
+ * moonshotai/kimi-code). The path key is `path`, NOT Claude's `file_path`
15
+ * (see interfaces/types.ts).
16
+ *
17
+ * OUTPUT does not match Claude: only `UserPromptSubmit`, `PreToolUse`, `Stop`
18
+ * are blockable, and the only documented decision is
19
+ * `{"hookSpecificOutput":{"permissionDecision":"deny","permissionDecisionReason"}}`
20
+ * (camelCase, despite the snake_case input — a real Kimi inconsistency). No
21
+ * `"ask"`/`"allow"`/`additionalContext` is documented. Verified live against
22
+ * kimi-code v0.27.0: that deny envelope on stdout at exit 0 blocks the call
23
+ * (exit 2 is NOT required) and `permissionDecisionReason` is re-injected to
24
+ * the model; empty stdout at exit 0 lets the call run. Exit 2 = block, stderr
25
+ * = reason; any other exit/timeout/crash fails open (allow). Whether raw-text
26
+ * stdout at exit 0 (the `inform` case) reaches the context is documented only
27
+ * as "may be" — unverified, needs live test.
28
+ */
29
+ import { evaluate } from "../../policy/evaluate";
30
+ import { formatPrompt, type Prompt } from "../../prompt/types";
31
+ import { readClaudeInput } from "../claude";
32
+ import { commandToString } from "../../runtime/command-string";
33
+ import type { KimiHookInput, KimiResponse } from "./interfaces/types";
34
+
35
+ export type { KimiHookInput, KimiResponse } from "./interfaces/types";
36
+
37
+ /**
38
+ * Downgrade prefix for `ask` — Kimi has no interactive approval channel
39
+ * reachable from a hook. Kimi DOES have an `ask` decision, but only via
40
+ * `[[permission.rules]] decision = "ask"` in config.toml, a separate system
41
+ * a hook's return value cannot invoke.
42
+ */
43
+ const ASK_PREFIX = "[downgraded from ask — Kimi Code has no interactive approval]";
44
+
45
+ /** Read & parse the Kimi hook stdin payload (same wire shape as Claude for the fields we consume). */
46
+ export async function readKimiInput(): Promise<KimiHookInput> {
47
+ return await readClaudeInput();
48
+ }
49
+
50
+ /**
51
+ * Render a portable {@link Prompt} as a Kimi hook response string:
52
+ * `block`/`ask` -> `{hookSpecificOutput:{permissionDecision:"deny",...}}`
53
+ * (only `"deny"` is documented; `ask` is downgraded with {@link ASK_PREFIX});
54
+ * `inform` -> raw text (Kimi appends stdout to context on exit 0 — no
55
+ * documented JSON envelope for non-blocking context injection).
56
+ * @param prompt - The portable policy prompt.
57
+ * @returns The native Kimi response string.
58
+ */
59
+ export function toKimiResponse(prompt: Prompt): string {
60
+ const msg = formatPrompt(prompt);
61
+ if (prompt.kind === "inform") return msg;
62
+ const reason = prompt.kind === "ask" ? `${ASK_PREFIX}\n${msg}` : msg;
63
+ const res: KimiResponse = { hookSpecificOutput: { permissionDecision: "deny", permissionDecisionReason: reason } };
64
+ return JSON.stringify(res);
65
+ }
66
+
67
+ /**
68
+ * Render a raw deny reason as a Kimi hook response — for callers that already
69
+ * hold a fully-formatted message (e.g. the MCP cache-hit notice) rather than a
70
+ * portable {@link Prompt}. Same envelope as {@link toKimiResponse}, minus the
71
+ * `[BLOCKED]` prompt framing and without `hookEventName` (undocumented upstream).
72
+ * @param reason - The fully-formatted deny message.
73
+ * @returns The native Kimi response string.
74
+ */
75
+ export function kimiDenyResponse(reason: string): string {
76
+ const res: KimiResponse = { hookSpecificOutput: { permissionDecision: "deny", permissionDecisionReason: reason } };
77
+ return JSON.stringify(res);
78
+ }
79
+
80
+ /**
81
+ * Run the bundled policy over a Kimi hook payload and return the native
82
+ * response string, or null to allow (the hook then exits 0 with empty stdout).
83
+ * @param input - Parsed hook stdin payload.
84
+ * @returns Native response string, or null when the tool use is allowed.
85
+ */
86
+ export function guard(input: KimiHookInput): string | null {
87
+ const t = input.tool_input;
88
+ const r = evaluate({
89
+ tool: input.tool_name ?? "Write",
90
+ filePath: t?.path ?? t?.file_path,
91
+ content: t?.content ?? t?.new_string,
92
+ command: commandToString(t?.command),
93
+ });
94
+ if (r.decision === "allow" || !r.prompt) return null;
95
+ return toKimiResponse(r.prompt);
96
+ }
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Kimi Code CLI shell-hook wire types. Verified 2026 against
3
+ * kimi.com/code/docs/en/kimi-code-cli/customization/hooks.html (v0.27.0):
4
+ * hooks are configured in `~/.kimi-code/config.toml` under `[[hooks]]`,
5
+ * receive a snake_case JSON payload on stdin, and answer with a camelCase
6
+ * JSON envelope on stdout (a real, documented casing inconsistency).
7
+ */
8
+
9
+ /**
10
+ * `PreToolUse`/`UserPromptSubmit`/`Stop` stdin payload (subset). Same base
11
+ * wire shape as Claude Code (`hook_event_name`/`tool_name`/`tool_input`/
12
+ * `cwd`); `tool_input.command` is confirmed for `Bash` by the docs' example
13
+ * script (`payload.tool_input?.command`). Verified live against kimi-code
14
+ * v0.27.0 on `PreToolUse`/`Bash`: `tool_name` is present at top level (the
15
+ * docs never name it as a stdin key) and `tool_input.command` carries the
16
+ * shell string. The live payload also carries an UNDOCUMENTED `tool_call_id`
17
+ * (e.g. `"tool_LsTnl0gBxM62XuWvKw0GFGHs"`); the harness does not consume it,
18
+ * so it is deliberately absent from this interface. `cwd` is the
19
+ * symlink-resolved path (macOS: `/private/tmp/…` for a shell in `/tmp/…`).
20
+ */
21
+ export interface KimiHookInput {
22
+ hook_event_name?: string;
23
+ tool_name?: string;
24
+ /**
25
+ * Verified against the official tool reference (`docs/en/reference/tools.md`,
26
+ * moonshotai/kimi-code) AND live against kimi-code v0.27.0: `Write` sends
27
+ * `{ path, content, mode? }`, `Edit` sends
28
+ * `{ path, old_string, new_string, replace_all? }` — the path key is `path`,
29
+ * NOT Claude's `file_path`. `file_path` is kept as a tolerated alias (read
30
+ * second) for Claude-shaped replay payloads, mirroring
31
+ * `runtime/normalize.ts` which resolves both on the CLI path.
32
+ */
33
+ tool_input?: {
34
+ path?: string;
35
+ file_path?: string;
36
+ content?: string;
37
+ new_string?: string;
38
+ command?: string | string[];
39
+ };
40
+ session_id?: string;
41
+ cwd?: string;
42
+ }
43
+
44
+ /**
45
+ * Hook stdout response. Only `"deny"` is documented for `permissionDecision`
46
+ * — no `"ask"`/`"allow"` value, and no `additionalContext` field exists on
47
+ * this envelope. Blocking events are `UserPromptSubmit`, `PreToolUse`, and
48
+ * `Stop` only; every other event's return value is ignored.
49
+ */
50
+ export interface KimiResponse {
51
+ hookSpecificOutput?: {
52
+ permissionDecision?: "deny";
53
+ permissionDecisionReason?: string;
54
+ };
55
+ }