@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
@@ -1,6 +1,6 @@
1
1
  import { r as projectLayout } from "../layout-KWoE_Mqn.mjs";
2
2
  import { a as sanitizeSessionId, c as sessionsDir, i as loadSessionState, n as fuseHarnessHome, o as saveSessionState, r as fusengineCache, s as sessionStatePath, t as claudeHome } from "../home-state-oUGFB4ds.mjs";
3
- import { A as trackMcpResearch, At as defaultStateDir, B as generateProjectMap, C as seoPostToolUse, Ct as trimLogFile, D as securityAdvisoryForPatch, Dt as claudeMdKey, E as securityAdvisory, Et as projectContext, F as dispatchAipilot, Ft as loadSecurityState, G as countFiles, H as writeTree, I as dispatchLessons, It as saveSecurityState, J as lessonsArchiveFileFor, K as getFileDesc, L as cartoSessionStart, Lt as securityStateDir, M as trackEnrichment, Mt as trackFile, N as dispatchLifecycle, Nt as normalizeEvent, O as postTrackingSideEffects, Ot as promptSubmitContext, P as aipilotPostToolUse, Pt as isoUtc, R as generateEcosystemMap, Rt as securityStatePath, S as postEditContext, St as removeOldFiles, T as dispatchMemory, Tt as gitContext, U as loadEnriched, V as isProject, W as mergeLines, X as lessonsStateFileFor, Y as lessonsFileFor, _ as preCommitGate, _t as readRules, a as recordActivity, at as saveApexState, b as extractSymbols, bt as pruneEmptyDirs, c as MCP_TTL_MS, ct as trackAgentMemory, d as isMcpTool, dt as validateSolidGate, f as queryOf, ft as checkFileSize, g as gate, gt as injectRules, h as TRIVIAL_BUDGET, ht as solidDetectStart, i as respond, it as cleanupSession, j as trackSkillRead, jt as projectHash, k as trackWatchResearch, kt as taskContext, l as WEBFETCH_TTL_MS, lt as subagentCacheContext, m as REQUIRED_AGENTS, mt as detectSolidProfile, n as activityFor, nt as trackSessionChanges, o as mcpPostStore, ot as logToolFailure, p as DEFAULT_WINDOW_MS, pt as countLoc, q as listChildren, r as handlePre, rt as validateRulesLoaded, s as mcpPreIntercept, st as validateTeammateOutput, t as handleHook, tt as postEditTypescript, u as cacheQueryOf, ut as validateTailwind, v as detectDuplication, vt as runSessionStartCleanups, w as seoPostToolUseResponse, wt as devContext, x as lifecycleStdout, xt as purgeTtlTree, y as dryGate, yt as sessionStartCore, z as writePluginMap, zt as todayUtc } from "../handle-CBaOcFZq.mjs";
3
+ import { A as trackWatchResearch, At as taskContext, B as writePluginMap, Bt as todayUtc, C as postEditContext, Ct as removeOldFiles, D as securityAdvisory, Dt as projectContext, E as dispatchMemory, Et as gitContext, F as aipilotPostToolUse, Ft as isoUtc, G as mergeLines, H as isProject, I as dispatchAipilot, It as loadSecurityState, J as listChildren, K as countFiles, L as dispatchLessons, Lt as saveSecurityState, M as trackSkillRead, Mt as projectHash, N as trackEnrichment, Nt as trackFile, O as securityAdvisoryForPatch, Ot as claudeMdKey, P as dispatchLifecycle, Pt as normalizeEvent, R as cartoSessionStart, Rt as securityStateDir, S as lifecycleStdout, St as purgeTtlTree, T as seoPostToolUseResponse, Tt as devContext, U as writeTree, V as generateProjectMap, W as loadEnriched, X as lessonsFileFor, Y as lessonsArchiveFileFor, Z as lessonsStateFileFor, _ as gateCommandCandidates, _t as injectRules, a as recordActivity, at as cleanupSession, b as dryGate, bt as sessionStartCore, c as MCP_TTL_MS, ct as validateTeammateOutput, d as isMcpTool, dt as validateTailwind, f as queryOf, ft as validateSolidGate, g as gate, gt as solidDetectStart, h as TRIVIAL_BUDGET, ht as detectSolidProfile, i as respond, it as validateRulesLoaded, j as trackMcpResearch, jt as defaultStateDir, k as postTrackingSideEffects, kt as promptSubmitContext, l as WEBFETCH_TTL_MS, lt as trackAgentMemory, m as REQUIRED_AGENTS, mt as countLoc, n as activityFor, nt as postEditTypescript, o as mcpPostStore, ot as saveApexState, p as DEFAULT_WINDOW_MS, pt as checkFileSize, q as getFileDesc, r as handlePre, rt as trackSessionChanges, s as mcpPreIntercept, st as logToolFailure, t as handleHook, u as cacheQueryOf, ut as subagentCacheContext, v as preCommitGate, vt as readRules, w as seoPostToolUse, wt as trimLogFile, x as extractSymbols, xt as pruneEmptyDirs, y as detectDuplication, yt as runSessionStartCleanups, z as generateEcosystemMap, zt as securityStatePath } from "../handle-C43gA-Pr.mjs";
4
4
  //#region src/runtime/storage.ts
5
5
  /**
6
6
  * The project's single state dir (`<root>/.harness`) — neutral + harness-agnostic,
@@ -10,4 +10,4 @@ function harnessStateDir(root) {
10
10
  return projectLayout(root).stateDir;
11
11
  }
12
12
  //#endregion
13
- export { DEFAULT_WINDOW_MS, MCP_TTL_MS, REQUIRED_AGENTS, TRIVIAL_BUDGET, WEBFETCH_TTL_MS, activityFor, aipilotPostToolUse, cacheQueryOf, cartoSessionStart, checkFileSize, claudeHome, claudeMdKey, cleanupSession, countFiles, countLoc, defaultStateDir, detectDuplication, detectSolidProfile, devContext, dispatchAipilot, dispatchLessons, dispatchLifecycle, dispatchMemory, dryGate, extractSymbols, fuseHarnessHome, fusengineCache, gate, generateEcosystemMap, generateProjectMap, getFileDesc, gitContext, handleHook, handlePre, harnessStateDir, injectRules, isMcpTool, isProject, isoUtc, lessonsArchiveFileFor, lessonsFileFor, lessonsStateFileFor, lifecycleStdout, listChildren, loadEnriched, loadSecurityState, loadSessionState, logToolFailure, mcpPostStore, mcpPreIntercept, mergeLines, normalizeEvent, postEditContext, postEditTypescript, postTrackingSideEffects, preCommitGate, projectContext, projectHash, promptSubmitContext, pruneEmptyDirs, purgeTtlTree, queryOf, readRules, recordActivity, removeOldFiles, respond, runSessionStartCleanups, sanitizeSessionId, saveApexState, saveSecurityState, saveSessionState, securityAdvisory, securityAdvisoryForPatch, securityStateDir, securityStatePath, seoPostToolUse, seoPostToolUseResponse, sessionStartCore, sessionStatePath, sessionsDir, solidDetectStart, subagentCacheContext, taskContext, todayUtc, trackAgentMemory, trackEnrichment, trackFile, trackMcpResearch, trackSessionChanges, trackSkillRead, trackWatchResearch, trimLogFile, validateRulesLoaded, validateSolidGate, validateTailwind, validateTeammateOutput, writePluginMap, writeTree };
13
+ export { DEFAULT_WINDOW_MS, MCP_TTL_MS, REQUIRED_AGENTS, TRIVIAL_BUDGET, WEBFETCH_TTL_MS, activityFor, aipilotPostToolUse, cacheQueryOf, cartoSessionStart, checkFileSize, claudeHome, claudeMdKey, cleanupSession, countFiles, countLoc, defaultStateDir, detectDuplication, detectSolidProfile, devContext, dispatchAipilot, dispatchLessons, dispatchLifecycle, dispatchMemory, dryGate, extractSymbols, fuseHarnessHome, fusengineCache, gate, gateCommandCandidates, generateEcosystemMap, generateProjectMap, getFileDesc, gitContext, handleHook, handlePre, harnessStateDir, injectRules, isMcpTool, isProject, isoUtc, lessonsArchiveFileFor, lessonsFileFor, lessonsStateFileFor, lifecycleStdout, listChildren, loadEnriched, loadSecurityState, loadSessionState, logToolFailure, mcpPostStore, mcpPreIntercept, mergeLines, normalizeEvent, postEditContext, postEditTypescript, postTrackingSideEffects, preCommitGate, projectContext, projectHash, promptSubmitContext, pruneEmptyDirs, purgeTtlTree, queryOf, readRules, recordActivity, removeOldFiles, respond, runSessionStartCleanups, sanitizeSessionId, saveApexState, saveSecurityState, saveSessionState, securityAdvisory, securityAdvisoryForPatch, securityStateDir, securityStatePath, seoPostToolUse, seoPostToolUseResponse, sessionStartCore, sessionStatePath, sessionsDir, solidDetectStart, subagentCacheContext, taskContext, todayUtc, trackAgentMemory, trackEnrichment, trackFile, trackMcpResearch, trackSessionChanges, trackSkillRead, trackWatchResearch, trimLogFile, validateRulesLoaded, validateSolidGate, validateTailwind, validateTeammateOutput, writePluginMap, writeTree };
@@ -8,6 +8,8 @@ type PromptKind = "ask" | "block" | "inform";
8
8
  */
9
9
  interface Prompt {
10
10
  kind: PromptKind;
11
+ /** Stable policy identifier for confirmation diagnostics. */
12
+ ruleId?: string;
11
13
  /** Short title, e.g. "SOLID file-size limit". */
12
14
  title: string;
13
15
  /** Why this fired. */
@@ -1,6 +1,6 @@
1
1
  import { a as resolveMaxLines, c as storeBudget, i as hookBudget } from "./limits-DG5pl-5s.mjs";
2
2
  import { l as ttlLabel, t as HOME_DIR } from "./dotenv-BLBkBTww.mjs";
3
- import { A as moduleAwarePath, S as isHooksPath, T as isStoresPath, _ as rolloutVerdict, at as DEV_KEYWORDS, it as maskCommentsOnly, nt as projectCaps, rt as maskCommentsAndStrings, tt as nearestManifestDir, x as isComponentsPath, y as declaresInterface } from "./evaluate-C1VSI5pb.mjs";
3
+ import { A as moduleAwarePath, S as isHooksPath, T as isStoresPath, _ as rolloutVerdict, at as maskCommentsOnly, it as maskCommentsAndStrings, nt as nearestManifestDir, ot as DEV_KEYWORDS, rt as projectCaps, x as isComponentsPath, y as declaresInterface } from "./evaluate-CMiIqHeH.mjs";
4
4
  import { d as countFrameworkCodeLines, l as PLUGINS_DIR, u as SOLID_REF } from "./home-state-oUGFB4ds.mjs";
5
5
  import { r as isDocConsulted, t as formatDocDeny } from "./doc-helpers-CWZegVdR.mjs";
6
6
  import { t as routeReferences } from "./router-PKVNBHge.mjs";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fusengine/harness",
3
- "version": "0.1.90",
3
+ "version": "0.1.91",
4
4
  "description": "Harness-agnostic toolkit for AI coding agents: runtime harness detection (Claude Code, Codex, Cursor, Cline, Gemini, Aider...), pure policy core (env config, project/framework detection, SOLID/file-size limits, APEX freshness, guard patterns, portable prompts), cache, project memory, ref routing, state/locks, statusline, per-harness adapters (Claude/Cursor/Cline/Gemini) and a cli-mode harness-check binary. Bun-native, with a built dist for Node + bundlers.",
5
5
  "type": "module",
6
6
  "module": "src/index.ts",
@@ -121,6 +121,7 @@
121
121
  }
122
122
  },
123
123
  "files": [
124
+ "src/**/*.ts",
124
125
  "dist",
125
126
  "assets",
126
127
  "README.md",
@@ -138,7 +139,7 @@
138
139
  "test": "bun test",
139
140
  "sim": "bun test test/sim/",
140
141
  "typecheck": "tsc --noEmit",
141
- "docs:api": "typedoc",
142
+ "docs:api": "bun install --cwd tools/api-docs --frozen-lockfile && bun run --cwd tools/api-docs generate",
142
143
  "build": "tsdown src/index.ts src/config/index.ts src/util/index.ts src/detect/index.ts src/policy/index.ts src/prompt/index.ts src/memory/index.ts src/cache/index.ts src/freshness/index.ts src/refs/index.ts src/statusline/index.ts src/cli/index.ts src/cli/bin.ts src/init/index.ts src/tracking/index.ts src/runtime/index.ts src/adapters/claude/index.ts src/adapters/codex/index.ts src/adapters/cursor/index.ts src/adapters/cline/index.ts src/adapters/gemini/index.ts src/adapters/hermes/index.ts src/adapters/kimi/index.ts --dts --format esm --clean --out-dir dist",
143
144
  "prepublishOnly": "bun test && tsc --noEmit && bun run build"
144
145
  },
@@ -0,0 +1,92 @@
1
+ /** Claude Code adapter — the thin Claude-only shim over the portable policy core; reads the hook stdin payload and emits hookSpecificOutput responses. */
2
+ import { evaluate } from "../../policy/evaluate";
3
+ import { formatPrompt, type Prompt } from "../../prompt/types";
4
+ import { readStdin } from "../../util/runtime-io";
5
+ import { commandToString } from "../../runtime/command-string";
6
+
7
+ /** Subset of the Claude Code hook stdin payload we consume. */
8
+ export interface ClaudeHookInput {
9
+ hook_event_name?: string;
10
+ tool_name?: string;
11
+ tool_input?: { file_path?: string; content?: string; new_string?: string; command?: string | string[] };
12
+ cwd?: string;
13
+ /** Shared Claude/Codex hook field — Codex maps approval_policy=never to "bypassPermissions" (adapters/codex/permission-mode.ts). */ permission_mode?: string;
14
+ }
15
+
16
+ /** Read & parse the Claude hook payload from stdin (empty object on bad input). */
17
+ export async function readClaudeInput(): Promise<ClaudeHookInput> {
18
+ const text = await readStdin();
19
+ if (!text.trim()) return {};
20
+ try {
21
+ const parsed: unknown = JSON.parse(text);
22
+ return typeof parsed === "object" && parsed !== null ? (parsed as ClaudeHookInput) : {};
23
+ } catch {
24
+ return {};
25
+ }
26
+ }
27
+
28
+ /** A `deny` hook response for a given event. PreToolUse-only field. */
29
+ export function denyResponse(event: string, reason: string): string {
30
+ return JSON.stringify({
31
+ hookSpecificOutput: { hookEventName: event, permissionDecision: "deny", permissionDecisionReason: reason },
32
+ });
33
+ }
34
+
35
+ /**
36
+ * A PostToolUse (and Stop/UserPromptSubmit) `block` response: those events
37
+ * ignore `permissionDecision` (PreToolUse-only) and only honor the top-level
38
+ * `decision`/`reason` keys, which feed `reason` back to Claude.
39
+ */
40
+ export function blockResponse(reason: string): string {
41
+ return JSON.stringify({ decision: "block", reason });
42
+ }
43
+
44
+ /** An `additionalContext` injection response. */
45
+ export function contextResponse(event: string, text: string): string {
46
+ return JSON.stringify({ hookSpecificOutput: { hookEventName: event, additionalContext: text } });
47
+ }
48
+
49
+ /** A raw `systemMessage` notice — shown to the user without blocking the tool. Mirrors the shared Python `hook_output.allow_pass`/`post_pass` convention. */
50
+ export function systemMessage(text: string): string {
51
+ return JSON.stringify({ systemMessage: text });
52
+ }
53
+
54
+ /** An `inform` response with a user-visible notice (Python `allow_pass`/`post_pass` parity): a pure pass reuses {@link systemMessage} alone; with agent-facing `context` both channels merge in one JSON. */
55
+ export function informResponse(event: string, notice: string, context: string): string {
56
+ if (!context) return systemMessage(notice);
57
+ return JSON.stringify({ systemMessage: notice, hookSpecificOutput: { hookEventName: event, additionalContext: context } });
58
+ }
59
+
60
+ /** Attach a `systemMessage`, MERGING it with any already present (`\n`-joined) instead of overwriting — several producers may each call this on the same stdout in one hook invocation. */
61
+ export function attachSystemMessage(stdout: string, notice: string): string {
62
+ try {
63
+ const parsed = JSON.parse(stdout) as Record<string, unknown>, prev = typeof parsed.systemMessage === "string" ? parsed.systemMessage : "";
64
+ return JSON.stringify({ ...parsed, systemMessage: prev ? `${prev}\n${notice}` : notice });
65
+ } catch { return systemMessage(notice); }
66
+ }
67
+
68
+ /** Render a portable {@link Prompt} as a Claude Code hook response: `block` → deny, `ask` → interactive `permissionDecision`, `inform` → `additionalContext` (+ `systemMessage` when `userMessage` is set). */
69
+ export function toClaudeResponse(event: string, prompt: Prompt): string {
70
+ const reason = formatPrompt(prompt);
71
+ if (prompt.kind === "block") return denyResponse(event, reason);
72
+ if (prompt.kind === "ask") {
73
+ return JSON.stringify({ hookSpecificOutput: { hookEventName: event, permissionDecision: "ask", permissionDecisionReason: reason } });
74
+ }
75
+ if (prompt.userMessage) return informResponse(event, prompt.userMessage, prompt.reason ? reason : "");
76
+ return contextResponse(event, reason);
77
+ }
78
+
79
+ /** Run the bundled policy over a Claude payload and return the native response string (deny/ask/additionalContext), or null to allow. */
80
+ export function guard(input: ClaudeHookInput): string | null {
81
+ const result = evaluate({
82
+ tool: input.tool_name ?? "Write",
83
+ filePath: input.tool_input?.file_path,
84
+ content: input.tool_input?.content ?? input.tool_input?.new_string,
85
+ command: commandToString(input.tool_input?.command),
86
+ });
87
+ if (result.decision === "allow" || !result.prompt) return null;
88
+ return toClaudeResponse(input.hook_event_name ?? "PreToolUse", result.prompt);
89
+ }
90
+
91
+ /** @deprecated use {@link guard}. Kept for back-compat. */
92
+ export const fileSizeGuard: typeof guard = guard;
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Cline adapter (hook-mode). Schema per docs.cline.bot / .clinerules/hooks (2026):
3
+ * `PreToolUse` blocks via `{ cancel: true }`; it cannot modify tool parameters.
4
+ */
5
+ import { evaluate } from "../../policy/evaluate";
6
+ import { formatPrompt } from "../../prompt/types";
7
+
8
+ /** `PreToolUse` stdin payload (subset). */
9
+ export interface ClineHookInput {
10
+ hookName?: string;
11
+ preToolUse?: {
12
+ toolName?: string;
13
+ parameters?: { path?: string; content?: string; command?: string };
14
+ };
15
+ }
16
+
17
+ /** Hook stdout response. */
18
+ export interface ClineResponse {
19
+ cancel?: boolean;
20
+ errorMessage?: string;
21
+ contextModification?: string;
22
+ }
23
+
24
+ /** Evaluate a tool use; cancel on a hard block, otherwise inject context. */
25
+ export function preToolUse(input: ClineHookInput): ClineResponse {
26
+ const t = input.preToolUse;
27
+ const r = evaluate({
28
+ tool: t?.toolName ?? "write_to_file",
29
+ filePath: t?.parameters?.path,
30
+ content: t?.parameters?.content,
31
+ command: t?.parameters?.command,
32
+ });
33
+ if (r.decision === "allow" || !r.prompt) return {};
34
+ const msg = formatPrompt(r.prompt);
35
+ return r.prompt.kind === "block" ? { cancel: true, errorMessage: msg } : { contextModification: msg };
36
+ }
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Parser for Codex's freeform `apply_patch` tool payload — the platform's PRIMARY
3
+ * edit primitive. The PreToolUse hook delivers `tool_name: "apply_patch"` with
4
+ * `tool_input: { command: <raw patch text> }` (verified in the Codex source:
5
+ * `core/src/tools/handlers/apply_patch.rs::pre_tool_use_payload`, which emits
6
+ * `json!({ "command": <freeform patch> })`). The grammar is the official
7
+ * apply-patch Lark spec (`apply-patch/src/parser.rs`): a single patch envelope
8
+ * can carry MULTIPLE files.
9
+ *
10
+ * Content precision (for the file-size / DRY gates that key off `content`):
11
+ * - Add File: EXACT — every `+` line is the new file verbatim.
12
+ * - Update File: APPROXIMATE — only the hunk "new side" (context ` ` + added `+`
13
+ * lines) is reconstructed, not the full post-edit file. It is a lower bound on
14
+ * the file's size (callers pair it with the on-disk count for oversized-file
15
+ * edits), never the exact result.
16
+ * - Delete File: no content (empty).
17
+ */
18
+
19
+ /** One file touched by an apply_patch envelope. */
20
+ export interface PatchedFile {
21
+ path: string;
22
+ content: string;
23
+ op: "add" | "update" | "delete";
24
+ }
25
+
26
+ const BEGIN = "*** Begin Patch";
27
+ const END = "*** End Patch";
28
+ const ADD = "*** Add File: ";
29
+ const DEL = "*** Delete File: ";
30
+ const UPD = "*** Update File: ";
31
+ const MOVE = "*** Move to: ";
32
+ const EOF = "*** End of File";
33
+ const ENV = "*** Environment ID: ";
34
+
35
+ /**
36
+ * Parse a Codex freeform patch into its per-file changes. Lenient on whitespace
37
+ * around structural markers (as Codex's own parser is); returns `[]` when no
38
+ * recognizable hunk is present (malformed input fails open, not closed).
39
+ * @param text - Raw patch body from `tool_input.command`.
40
+ * @returns One {@link PatchedFile} per Add/Update/Delete hunk, in order.
41
+ */
42
+ export function parseApplyPatch(text: string): PatchedFile[] {
43
+ const files: PatchedFile[] = [];
44
+ let cur: PatchedFile | null = null;
45
+ let buf: string[] = [];
46
+ const flush = (): void => {
47
+ if (cur) {
48
+ cur.content = buf.join("\n");
49
+ files.push(cur);
50
+ }
51
+ cur = null;
52
+ buf = [];
53
+ };
54
+ for (const line of text.split("\n")) {
55
+ const marker = line.trimStart();
56
+ if (marker === BEGIN || marker.startsWith(ENV)) continue;
57
+ if (marker === END) {
58
+ flush();
59
+ continue;
60
+ }
61
+ if (marker.startsWith(ADD)) {
62
+ flush();
63
+ cur = { path: marker.slice(ADD.length).trim(), content: "", op: "add" };
64
+ continue;
65
+ }
66
+ if (marker.startsWith(DEL)) {
67
+ flush();
68
+ files.push({ path: marker.slice(DEL.length).trim(), content: "", op: "delete" });
69
+ continue;
70
+ }
71
+ if (marker.startsWith(UPD)) {
72
+ flush();
73
+ cur = { path: marker.slice(UPD.length).trim(), content: "", op: "update" };
74
+ continue;
75
+ }
76
+ if (marker.startsWith(MOVE)) {
77
+ if (cur) cur.path = marker.slice(MOVE.length).trim();
78
+ continue;
79
+ }
80
+ if (marker === EOF || !cur) continue;
81
+ if (cur.op === "add") {
82
+ if (line.startsWith("+")) buf.push(line.slice(1));
83
+ continue;
84
+ }
85
+ // Update hunk: keep the "new side" (added `+` + context ` `), drop removed `-`
86
+ // and `@@` chunk headers.
87
+ if (line.startsWith("@@")) continue;
88
+ if (line.startsWith("+")) buf.push(line.slice(1));
89
+ else if (line.startsWith("-")) continue;
90
+ else buf.push(line.startsWith(" ") ? line.slice(1) : line);
91
+ }
92
+ flush();
93
+ return files;
94
+ }
@@ -0,0 +1,84 @@
1
+ /**
2
+ * OpenAI Codex CLI adapter (hook-mode). Codex's `PreToolUse` hook (since 2026)
3
+ * uses the SAME envelope as Claude Code — `tool_name`/`tool_input` in,
4
+ * `hookSpecificOutput.permissionDecision` out — so it shares Claude's readers.
5
+ * Config lives at `.codex/hooks.json`.
6
+ *
7
+ * Two Codex-specific quirks this adapter closes (audit 2026-07-06):
8
+ * 1. `apply_patch` is Codex's PRIMARY edit primitive; its payload is a freeform
9
+ * patch in `tool_input.command`, with NO `file_path`/`content`. Claude's guard
10
+ * keyed off those fields, so the SOLID/DRY gates saw NOTHING (enforcement 0%).
11
+ * Here the patch is parsed and each file is judged (one violating hunk denies).
12
+ * 2. Codex parses but NEVER honors `permissionDecision: "ask"` (deny-only) — an
13
+ * `ask` silently fails open (verified: `pre_tool_use.rs` test
14
+ * `unsupported_permission_decision_fails_open`). Every `ask` is downgraded to
15
+ * an explicit deny.
16
+ */
17
+ import { evaluate } from "../../policy/evaluate";
18
+ import { countLines } from "../../policy/file-size";
19
+ import { formatPrompt, type Prompt } from "../../prompt/types";
20
+ import { parseApplyPatch } from "./apply-patch";
21
+ import { commandToString } from "../../runtime/command-string";
22
+ import { canonicalizeCodexShellTool } from "../../runtime/codex-shell-tool";
23
+ import { canonicalizeMcpToolName } from "../../runtime/mcp-tool-name";
24
+ import { contextResponse, denyResponse, informResponse, type ClaudeHookInput } from "../claude";
25
+ import { isBypassPermissions } from "./permission-mode";
26
+
27
+ export { readClaudeInput as readCodexInput, denyResponse, contextResponse, informResponse, type ClaudeHookInput as CodexHookInput } from "../claude";
28
+
29
+ const ASK_PREFIX = "[downgraded from ask — Codex has no interactive approval]";
30
+
31
+ /**
32
+ * Render a portable {@link Prompt} as a Codex hook response, `ask` → explicit deny.
33
+ * NOTE: the REAL wired route is `harness hook codex` → handleHook → respond.ts
34
+ * (source of truth for the ask→deny downgrade); this thin export exists for
35
+ * direct package consumers and is kept aligned so it never silently diverges.
36
+ */
37
+ export function toCodexResponse(prompt: Prompt): string {
38
+ const message = formatPrompt(prompt);
39
+ if (prompt.kind === "inform") {
40
+ return prompt.userMessage ? informResponse("PreToolUse", prompt.userMessage, prompt.reason ? message : "") : contextResponse("PreToolUse", message);
41
+ }
42
+ if (prompt.kind === "ask") return denyResponse("PreToolUse", `${ASK_PREFIX}\n${message}`);
43
+ return denyResponse("PreToolUse", message);
44
+ }
45
+
46
+ /** OR the per-file SOLID verdict of an `apply_patch` payload — first block wins. */
47
+ function applyPatchPrompt(command: string): Prompt | null {
48
+ for (const f of parseApplyPatch(command)) {
49
+ if (f.op === "delete") continue;
50
+ const tool = f.op === "add" ? "Write" : "Edit";
51
+ const r = evaluate({ tool, filePath: f.path, content: f.content, existingLines: countLines(f.content) });
52
+ if (r.decision !== "allow" && r.prompt) return r.prompt;
53
+ }
54
+ return null;
55
+ }
56
+
57
+ /** Portable single-tool verdict for non-`apply_patch` Codex tools. */
58
+ function resolvePrompt(input: ClaudeHookInput): Prompt | null {
59
+ const i = input.tool_input;
60
+ const r = evaluate({
61
+ // Code Mode-wrapped exec_command surfaces its raw function-tool name here
62
+ // instead of "Bash" (see codex-shell-tool.ts) — canonicalize so the SOLID/
63
+ // protected-path/bash-write guards recognize it like a native Bash call.
64
+ tool: canonicalizeCodexShellTool("codex", canonicalizeMcpToolName("codex", input.tool_name ?? "Write")),
65
+ filePath: i?.file_path,
66
+ content: i?.content ?? i?.new_string,
67
+ command: commandToString(i?.command),
68
+ neverApproval: isBypassPermissions(input.permission_mode),
69
+ });
70
+ return r.decision === "allow" || !r.prompt ? null : r.prompt;
71
+ }
72
+
73
+ /**
74
+ * Run the bundled policy over a Codex payload and return the native response
75
+ * string (deny/additionalContext), or null to allow. `apply_patch` is fanned
76
+ * into per-file checks; every other tool routes through the portable policy.
77
+ */
78
+ export function guard(input: ClaudeHookInput): string | null {
79
+ const prompt =
80
+ input.tool_name === "apply_patch"
81
+ ? applyPatchPrompt(commandToString(input.tool_input?.command) ?? "")
82
+ : resolvePrompt(input);
83
+ return prompt ? toCodexResponse(prompt) : null;
84
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Codex maps `AskForApproval::Never` -> `permission_mode: "bypassPermissions"`
3
+ * in its hook payload (codex-rs/core/src/hook_runtime.rs::hook_permission_mode)
4
+ * — the only value that isolates `never`; the other three approval policies
5
+ * (`untrusted`, `on-failure`, `on-request`) all collapse to `"default"`.
6
+ * Verified at openai/codex@342e4d4b, hook_runtime.rs run_pre_tool_use_hooks +
7
+ * pre_tool_use.rs PreToolUseRequest. Undocumented implementation detail, not
8
+ * a public contract — see the mapping-regression test that pins this string.
9
+ * @param permissionMode - The payload's `permission_mode` field, if present.
10
+ * @returns True only on an EXACT match — fail-closed. A near-miss
11
+ * (`"BypassPermissions"`, `"bypassPermissions "`, or a substring match) must
12
+ * never accidentally grant the neverApproval exemption.
13
+ */
14
+ export function isBypassPermissions(permissionMode: string | undefined): boolean {
15
+ return permissionMode === "bypassPermissions";
16
+ }
@@ -0,0 +1,46 @@
1
+ import { isAbsolute, normalize, relative } from "node:path";
2
+ import { realpathSync } from "node:fs";
3
+
4
+ /** Preserve a Cursor path value only when it is non-empty and NUL-free. */
5
+ export function cursorPath(value: unknown): string | undefined {
6
+ return typeof value === "string" && value.length > 0 && !value.includes("\0") ? value : undefined;
7
+ }
8
+
9
+ /** Validate and normalize an absolute path supplied by Cursor. */
10
+ export function cursorAbsolutePath(value: unknown): string | undefined {
11
+ const candidate = cursorPath(value);
12
+ if (!candidate || !isAbsolute(candidate)) return undefined;
13
+ const path = normalize(candidate);
14
+ try {
15
+ return realpathSync.native(path);
16
+ } catch {
17
+ return path;
18
+ }
19
+ }
20
+
21
+ /** Preserve distinct, validated Cursor workspace roots in wire order. */
22
+ export function cursorWorkspaceRoots(value: unknown): string[] {
23
+ if (!Array.isArray(value)) return [];
24
+ const roots = value.map(cursorAbsolutePath).filter((root): root is string => root !== undefined);
25
+ return [...new Set(roots)];
26
+ }
27
+
28
+ function contains(root: string, filePath: string): boolean {
29
+ const rel = relative(root, filePath);
30
+ return rel === "" || (!rel.startsWith("..") && !isAbsolute(rel));
31
+ }
32
+
33
+ /** Select Cursor's project scope without replacing a valid payload cwd. */
34
+ export function cursorProjectCwd(
35
+ cwd: string | undefined,
36
+ workspaceRoots: readonly string[],
37
+ filePath: string | undefined,
38
+ fallback: string,
39
+ ): string {
40
+ if (cwd) return cwd;
41
+ if (filePath) {
42
+ const matches = workspaceRoots.filter((root) => contains(root, filePath));
43
+ if (matches.length > 0) return matches.sort((a, b) => b.length - a.length)[0]!;
44
+ }
45
+ return workspaceRoots[0] ?? fallback;
46
+ }
@@ -0,0 +1,38 @@
1
+ import type { CursorEventContract } from "./interfaces/types";
2
+
3
+ const EVENT_CONTRACTS = {
4
+ sessionStart: { phase: "pre", lifecycle: "SessionStart", response: "session-context", blockable: false, known: true },
5
+ sessionEnd: { phase: "post", lifecycle: "SessionEnd", response: "neutral", blockable: false, known: true },
6
+ beforeSubmitPrompt: { phase: "pre", lifecycle: "UserPromptSubmit", response: "submit-control", blockable: true, known: true },
7
+ preCompact: { phase: "pre", lifecycle: "PreCompact", response: "compact-notice", blockable: false, known: true },
8
+ subagentStart: { phase: "pre", lifecycle: "SubagentStart", response: "permission", blockable: true, known: true },
9
+ subagentStop: { phase: "post", lifecycle: "SubagentStop", response: "followup", blockable: false, known: true },
10
+ preToolUse: { phase: "pre", lifecycle: "PreToolUse", response: "permission", blockable: true, known: true },
11
+ postToolUse: { phase: "post", lifecycle: "PostToolUse", response: "post-context", blockable: false, known: true },
12
+ postToolUseFailure: { phase: "post", lifecycle: "PostToolUseFailure", response: "neutral", blockable: false, known: true },
13
+ beforeShellExecution: { phase: "pre", lifecycle: "BeforeShellExecution", response: "permission", blockable: true, known: true },
14
+ afterShellExecution: { phase: "post", lifecycle: "AfterShellExecution", response: "neutral", blockable: false, known: true },
15
+ beforeMCPExecution: { phase: "pre", lifecycle: "BeforeMCPExecution", response: "permission", blockable: true, known: true },
16
+ afterMCPExecution: { phase: "post", lifecycle: "AfterMCPExecution", response: "neutral", blockable: false, known: true },
17
+ beforeReadFile: { phase: "pre", lifecycle: "BeforeReadFile", response: "permission", blockable: true, known: true },
18
+ afterFileEdit: { phase: "post", lifecycle: "AfterFileEdit", response: "neutral", blockable: false, known: true },
19
+ beforeTabFileRead: { phase: "pre", lifecycle: "BeforeTabFileRead", response: "permission", blockable: true, known: true },
20
+ afterTabFileEdit: { phase: "post", lifecycle: "AfterTabFileEdit", response: "neutral", blockable: false, known: true },
21
+ afterAgentResponse: { phase: "post", lifecycle: "AfterAgentResponse", response: "neutral", blockable: false, known: true },
22
+ afterAgentThought: { phase: "post", lifecycle: "AfterAgentThought", response: "neutral", blockable: false, known: true },
23
+ stop: { phase: "post", lifecycle: "Stop", response: "followup", blockable: false, known: true },
24
+ workspaceOpen: { phase: "pre", lifecycle: "WorkspaceOpen", response: "plugin-paths", blockable: false, known: true },
25
+ } as const satisfies Record<string, CursorEventContract>;
26
+
27
+ const UNKNOWN_EVENT: CursorEventContract = {
28
+ phase: "post",
29
+ lifecycle: null,
30
+ response: "neutral",
31
+ blockable: false,
32
+ known: false,
33
+ };
34
+
35
+ /** Return explicit routing and response metadata for a Cursor event name. */
36
+ export function cursorEventContract(eventName: string): CursorEventContract {
37
+ return EVENT_CONTRACTS[eventName as keyof typeof EVENT_CONTRACTS] ?? UNKNOWN_EVENT;
38
+ }
@@ -0,0 +1,43 @@
1
+ /** Cursor hook adapter; post-edit handling remains observe-only. */
2
+ import { evaluate } from "../../policy/evaluate";
3
+ import { formatPrompt } from "../../prompt/types";
4
+ import { extractCursorEvent } from "./normalize";
5
+ import type { CursorShellPayload, CursorToolPayload, CursorEditPayload, CursorResponse, CursorEditResponse } from "./interfaces/types";
6
+
7
+ export type { CursorShellPayload, CursorToolPayload, CursorEditPayload, CursorResponse, CursorEditResponse } from "./interfaces/types";
8
+
9
+ function guardCursor(payload: object): CursorResponse {
10
+ const event = extractCursorEvent(payload);
11
+ const result = evaluate({ tool: event.tool, filePath: event.filePath, content: event.content, oldString: event.oldString, command: event.command });
12
+ if (result.decision === "allow" || !result.prompt) return { permission: "allow" };
13
+ const message = formatPrompt(result.prompt);
14
+ const userMessage = result.prompt.kind === "ask"
15
+ ? `[downgraded from ask — Cursor preToolUse does not reliably enforce ask]\n${message}`
16
+ : message;
17
+ return { permission: "deny", user_message: userMessage, agent_message: message };
18
+ }
19
+
20
+ function namedPayload(payload: object, eventName: string): object {
21
+ return Object.hasOwn(payload, "hook_event_name") ? payload : { ...payload, hook_event_name: eventName };
22
+ }
23
+
24
+ /** Guard a shell command (git/install policies). */
25
+ export function beforeShellExecution(payload: CursorShellPayload): CursorResponse {
26
+ return guardCursor(namedPayload(payload, "beforeShellExecution"));
27
+ }
28
+
29
+ /** Guard a generic Cursor tool call using the same extraction as the runtime. */
30
+ export function preToolUse(payload: CursorToolPayload): CursorResponse {
31
+ return guardCursor(namedPayload(payload, "preToolUse"));
32
+ }
33
+
34
+ /**
35
+ * Complete an observed file edit without emitting pre-execution permission
36
+ * fields. Cursor does not define a callback schema for this post hook.
37
+ * @param payload - The `afterFileEdit` stdin payload.
38
+ * @returns An empty successful response.
39
+ */
40
+ export function afterFileEdit(payload: CursorEditPayload): CursorEditResponse {
41
+ void payload;
42
+ return {};
43
+ }
@@ -0,0 +1,88 @@
1
+ /** `beforeShellExecution` stdin payload (subset). */
2
+ export interface CursorShellPayload {
3
+ command?: string;
4
+ cwd?: string;
5
+ sandbox?: boolean;
6
+ workspace_roots?: string[];
7
+ hook_event_name?: string;
8
+ }
9
+
10
+ /** `preToolUse` stdin payload subset consumed by the adapter. */
11
+ export interface CursorToolPayload {
12
+ tool_name?: string;
13
+ tool_input?: Record<string, unknown>;
14
+ }
15
+
16
+ /** `afterFileEdit` stdin payload (subset). */
17
+ export interface CursorEditPayload {
18
+ file_path?: string;
19
+ edits?: { old_string: string; new_string: string }[];
20
+ }
21
+
22
+ /** One extracted Cursor file edit used by runtime post fan-out. */
23
+ export interface CursorExtractedFile {
24
+ filePath: string;
25
+ oldString?: string;
26
+ content: string;
27
+ op: "update";
28
+ }
29
+
30
+ /** Shared Cursor extraction result consumed by runtime and public adapter. */
31
+ export interface CursorExtractedEvent {
32
+ eventName: string;
33
+ lifecycleEvent: string | null;
34
+ responseKind: CursorResponseKind;
35
+ blockable: boolean;
36
+ cwd?: string;
37
+ workspaceRoots?: string[];
38
+ phase: "pre" | "post";
39
+ tool: string;
40
+ input: Record<string, unknown>;
41
+ filePath?: string;
42
+ content?: string;
43
+ oldString?: string;
44
+ command?: string;
45
+ /** Distinct beforeMCPExecution root and nested commands, in wire order. */
46
+ commandCandidates?: string[];
47
+ files?: CursorExtractedFile[];
48
+ }
49
+
50
+ /** Native Cursor stdout contract selected for one hook event. */
51
+ export type CursorResponseKind =
52
+ | "permission"
53
+ | "post-context"
54
+ | "session-context"
55
+ | "submit-control"
56
+ | "followup"
57
+ | "compact-notice"
58
+ | "plugin-paths"
59
+ | "neutral";
60
+
61
+ /** Routing metadata for a documented Cursor lifecycle event. */
62
+ export interface CursorEventContract {
63
+ phase: "pre" | "post";
64
+ lifecycle: string | null;
65
+ response: CursorResponseKind;
66
+ blockable: boolean;
67
+ known: boolean;
68
+ }
69
+
70
+ /**
71
+ * Empty afterFileEdit callback. Cursor documents no output fields for this
72
+ * post hook, so pre-execution permission fields are intentionally impossible.
73
+ */
74
+ export type CursorEditResponse = Record<string, never>;
75
+
76
+ /**
77
+ * `beforeShellExecution` stdout response. Message keys are snake_case:
78
+ * Cursor silently ignores camelCase `userMessage`/`agentMessage` (#141516,
79
+ * regression persists through v2.0.77+ — forum #142589), matching the
80
+ * schema respond.ts emits for the cursor harness.
81
+ */
82
+ export interface CursorResponse {
83
+ permission: "allow" | "deny" | "ask";
84
+ /** User-visible message — snake_case required (#141516, #142589). */
85
+ user_message?: string;
86
+ /** Agent-visible message — snake_case required (#141516, #142589). */
87
+ agent_message?: string;
88
+ }