@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
@@ -3,11 +3,11 @@ import { a as harnessHome, c as resolveTtlSec } from "./dotenv-BLBkBTww.mjs";
3
3
  import { r as projectLayout } from "./layout-KWoE_Mqn.mjs";
4
4
  import { i as walkUpFor, n as projectRoot, r as projectRootOrNull, t as isCodeFile } from "./project-root-3kk7gCOp.mjs";
5
5
  import { t as detectHarness } from "./harness-BMuLJ9lm.mjs";
6
- import { $ as matchPatterns, D as isVendorPath, G as isFileSizeScoped, J as GIT_BLOCKED, K as resolveSolidRefFramework, W as computeEditResultLines, b as interfaceDeclLevel, et as detectFramework, g as conventionsMode, lt as requiredArchSkill, n as FAIL_CLOSED, ot as detectModularArchitecture, rt as maskCommentsAndStrings, st as detectProjectType$1, t as evaluate } from "./evaluate-C1VSI5pb.mjs";
6
+ import { D as isVendorPath, G as computeEditResultLines, K as isFileSizeScoped, V as shellOutputRedirects, Y as GIT_BLOCKED, b as interfaceDeclLevel, ct as detectProjectType$1, et as matchPatterns, g as conventionsMode, it as maskCommentsAndStrings, n as FAIL_CLOSED, q as resolveSolidRefFramework, st as detectModularArchitecture, t as evaluate, tt as detectFramework, ut as requiredArchSkill } from "./evaluate-CMiIqHeH.mjs";
7
7
  import { a as sanitizeSessionId, c as sessionsDir, d as countFrameworkCodeLines, f as countLines, i as loadSessionState, l as PLUGINS_DIR, n as fuseHarnessHome, o as saveSessionState, p as evaluateFileSize, r as fusengineCache, t as claudeHome } from "./home-state-oUGFB4ds.mjs";
8
8
  import { a as writeJsonFile, i as readJsonFile, r as hashText, t as atomicWrite } from "./json-io-DisYd2fb.mjs";
9
9
  import { r as isDocConsulted } from "./doc-helpers-CWZegVdR.mjs";
10
- import { D as frameworkSolidGate, F as isExcludedSwiftPath, G as evaluateApex, H as PRE_AUTH_GATES, P as isExcludedJsPath, R as capVerbosity, S as parseField, T as usesTailwindUtilities, V as POST_AUTH_GATES, W as docConsultedGate, b as scanPlugin, c as EXCLUDE_DIRS$1, d as buildApexTaskInjection, g as buildClaudeMdContext, l as PROJECT_INDICATORS, n as missingSeoElements, o as parseEnrichment, p as apexAgentSteps, r as descFromText, s as parseEntry, t as isHtmlLike, v as apexDocName, w as skillTriggerGate, y as harnessHomeSegment, z as detectCreationIntent } from "./validate-DZFtAbLP.mjs";
10
+ import { D as frameworkSolidGate, F as isExcludedSwiftPath, G as evaluateApex, H as PRE_AUTH_GATES, P as isExcludedJsPath, R as capVerbosity, S as parseField, T as usesTailwindUtilities, V as POST_AUTH_GATES, W as docConsultedGate, b as scanPlugin, c as EXCLUDE_DIRS$1, d as buildApexTaskInjection, g as buildClaudeMdContext, l as PROJECT_INDICATORS, n as missingSeoElements, o as parseEnrichment, p as apexAgentSteps, r as descFromText, s as parseEntry, t as isHtmlLike, v as apexDocName, w as skillTriggerGate, y as harnessHomeSegment, z as detectCreationIntent } from "./validate-KjZ1X9tH.mjs";
11
11
  import { n as findMarketplacePlugins, r as readPluginMeta, t as resolveSkillPath } from "./skill-path-DVML3zfp.mjs";
12
12
  import { t as formatPrompt } from "./types-ernB1Dy3.mjs";
13
13
  import { a as nowStamp, l as throttleMs, n as readRoots, o as readState, s as setStateField, t as addRoot } from "./registry-IhHk2KlT.mjs";
@@ -15,9 +15,11 @@ import { d as loadIndex, i as cacheLookupMeta, n as webfetchCacheWrite, o as cac
15
15
  import { t as loadRefs } from "./loader-AGz4nK7d.mjs";
16
16
  import { C as apexAuthorizationGate, S as trivialCount, _ as recordBrainstormRequired, a as journalLogPath, b as recordTarget, c as appendEvent, d as signTrack, f as verifyTrack, g as recordAgent, h as emptyTrack, i as withTrack, l as withTrackLockSync, m as agentsFresh, o as readTrackSync, p as writeLastNonce, r as trackJournalEnabled, s as diffTrackEvents, t as loadTrack, u as LOCK_FAILED, v as recordDoc, x as recordTrivialEdit, y as recordRefRead } from "./store-5-ZPKb0u.mjs";
17
17
  import { i as parseApplyPatch, n as canonicalizeMcpToolName, r as canonicalizeCodexShellTool, t as isBypassPermissions } from "./permission-mode-B-SFyR0X.mjs";
18
- import { _ as commandToString, d as collectFiles, f as pathExists, g as writeText, h as spawnCapture, i as denyResponse, l as systemMessage, m as sleep, n as blockResponse, p as readText$1, r as contextResponse, s as informResponse, t as attachSystemMessage } from "./claude-5bC8TDAz.mjs";
19
- import { i as toKimiResponse, n as kimiDenyResponse } from "./kimi-D6X7b8wF.mjs";
20
- import { r as toHermesResponse } from "./hermes-kP6NUQlG.mjs";
18
+ import { t as commandToString } from "./command-string-CALMTnwN.mjs";
19
+ import { n as cursorProjectCwd, r as cursorEventContract, t as extractCursorEvent } from "./normalize-BjG6unTj.mjs";
20
+ import { d as collectFiles, f as pathExists, g as writeText, h as spawnCapture, i as denyResponse, l as systemMessage, m as sleep, n as blockResponse, p as readText$1, r as contextResponse, s as informResponse, t as attachSystemMessage } from "./claude-Ckv2_TgP.mjs";
21
+ import { i as toKimiResponse, n as kimiDenyResponse } from "./kimi-G2wcSh5-.mjs";
22
+ import { r as toHermesResponse } from "./hermes-B9-p_3IF.mjs";
21
23
  import { basename, dirname, extname, isAbsolute, join, normalize, relative, resolve, sep } from "node:path";
22
24
  import { appendFileSync, copyFileSync, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, readlinkSync, renameSync, rmSync, rmdirSync, statSync, symlinkSync, unlinkSync, writeFileSync } from "node:fs";
23
25
  import { homedir } from "node:os";
@@ -69,9 +71,7 @@ function str(v) {
69
71
  return typeof v === "string" ? v : void 0;
70
72
  }
71
73
  /**
72
- * Normalize a harness hook payload into a uniform event. Handles Cline's nested
73
- * `preToolUse`/`postToolUse` shape and the top-level `tool_name`/`tool_input`
74
- * shape used by Claude, Codex, Gemini, and Cursor.
74
+ * Normalize a hook payload, including Cline nesting and native Cursor events.
75
75
  */
76
76
  function normalizeEvent(id, payload) {
77
77
  if (id === "cline") {
@@ -88,6 +88,12 @@ function normalizeEvent(id, payload) {
88
88
  command: str(params.command)
89
89
  };
90
90
  }
91
+ if (id === "cursor") return {
92
+ ...extractCursorEvent(payload),
93
+ sessionId: str(payload.session_id) ?? str(payload.conversation_id) ?? "",
94
+ agentType: str(payload.agent_type),
95
+ permissionMode: str(payload.permission_mode)
96
+ };
91
97
  const event = str(payload.hook_event_name) ?? "";
92
98
  const input = payload.tool_input ?? payload;
93
99
  const tool = canonicalizeCodexShellTool(id, canonicalizeMcpToolName(id, str(payload.tool_name) ?? ""));
@@ -97,7 +103,9 @@ function normalizeEvent(id, payload) {
97
103
  input,
98
104
  sessionId: str(payload.session_id) ?? str(payload.conversation_id) ?? "",
99
105
  agentType: str(payload.agent_type) ?? str(input.subagent_type),
100
- permissionMode: str(payload.permission_mode)
106
+ permissionMode: str(payload.permission_mode),
107
+ toolUseId: str(payload.tool_use_id),
108
+ cwd: str(payload.cwd)
101
109
  };
102
110
  if (tool === "apply_patch") {
103
111
  const files = parseApplyPatch(str(input.command) ?? str(payload.command) ?? "").map((f) => ({
@@ -3064,7 +3072,7 @@ function lessonsStateFileFor(root) {
3064
3072
  //#endregion
3065
3073
  //#region src/tracking/one-shot-store.ts
3066
3074
  /** A fresh, empty state — always spread (`{ ...EMPTY }`) so the const is never shared. */
3067
- const EMPTY = {
3075
+ const EMPTY$1 = {
3068
3076
  gates: {},
3069
3077
  firstTry: 0,
3070
3078
  corrected: 0,
@@ -3076,7 +3084,7 @@ const EMPTY = {
3076
3084
  * gates/pending older than `windowMs`. Keeps the "7d" window honest, bounds size.
3077
3085
  */
3078
3086
  function pruneState(s, now, windowMs) {
3079
- if (now - s.updatedAt >= windowMs) return { ...EMPTY };
3087
+ if (now - s.updatedAt >= windowMs) return { ...EMPTY$1 };
3080
3088
  const gates = {};
3081
3089
  for (const [k, g] of Object.entries(s.gates)) if (now - g.lastTs < windowMs) gates[k] = g;
3082
3090
  const pending = {};
@@ -3309,16 +3317,16 @@ const SIDECAR$1 = "one-shot.json";
3309
3317
  /** Retention window: 7 days. Aggregates and pending denies older than this are pruned. */
3310
3318
  const WINDOW_MS = 10080 * 60 * 1e3;
3311
3319
  /** Load the state, or a fresh copy when missing/corrupt. */
3312
- function loadState(path) {
3320
+ function loadState$1(path) {
3313
3321
  try {
3314
- if (!existsSync(path)) return { ...EMPTY };
3322
+ if (!existsSync(path)) return { ...EMPTY$1 };
3315
3323
  const d = JSON.parse(readFileSync(path, "utf8"));
3316
3324
  return d && typeof d === "object" && !Array.isArray(d) ? {
3317
- ...EMPTY,
3325
+ ...EMPTY$1,
3318
3326
  ...d
3319
- } : { ...EMPTY };
3327
+ } : { ...EMPTY$1 };
3320
3328
  } catch {
3321
- return { ...EMPTY };
3329
+ return { ...EMPTY$1 };
3322
3330
  }
3323
3331
  }
3324
3332
  /**
@@ -3341,7 +3349,7 @@ function recordOneShot(prompt, input, opts) {
3341
3349
  });
3342
3350
  if (!burstFirst(op, prompt ? `deny:${prompt.title}` : "allow", opts)) return;
3343
3351
  const path = join(opts.dir, SIDECAR$1);
3344
- let s = pruneState(loadState(path), opts.now, WINDOW_MS);
3352
+ let s = pruneState(loadState$1(path), opts.now, WINDOW_MS);
3345
3353
  s = prompt ? applyDeny(s, prompt.title, op, opts.now) : applyAllow(s, op, opts.now, input.content != null || input.command != null);
3346
3354
  atomicWrite(path, JSON.stringify(s));
3347
3355
  } catch {}
@@ -3356,7 +3364,7 @@ function recordOneShot(prompt, input, opts) {
3356
3364
  */
3357
3365
  function oneShotSummary(cwd) {
3358
3366
  try {
3359
- return formatSummary(pruneState(loadState(join(defaultStateDir(cwd), SIDECAR$1)), Date.now(), WINDOW_MS));
3367
+ return formatSummary(pruneState(loadState$1(join(defaultStateDir(cwd), SIDECAR$1)), Date.now(), WINDOW_MS));
3360
3368
  } catch {
3361
3369
  return "";
3362
3370
  }
@@ -3401,7 +3409,7 @@ function recordFailure(tool, opts) {
3401
3409
  try {
3402
3410
  if (!burstFirst(`fail:${tool}`, "failure", opts)) return;
3403
3411
  const path = join(opts.dir, SIDECAR$1);
3404
- const next = applyFailure(pruneState(loadState(path), opts.now, WINDOW_MS), tool, opts.now);
3412
+ const next = applyFailure(pruneState(loadState$1(path), opts.now, WINDOW_MS), tool, opts.now);
3405
3413
  atomicWrite(path, JSON.stringify(next));
3406
3414
  } catch {}
3407
3415
  }
@@ -6646,13 +6654,281 @@ async function autoDocumentRead(filePath, now = Date.now(), id = "claude-code")
6646
6654
  return systemMessage(`📖 [${docType}] ${fname} logged`);
6647
6655
  }
6648
6656
  //#endregion
6657
+ //#region src/adapters/cursor/native-response.ts
6658
+ const stringValue = (value) => typeof value === "string";
6659
+ const booleanValue = (value) => typeof value === "boolean";
6660
+ const plainRecord = (value) => {
6661
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
6662
+ try {
6663
+ const prototype = Object.getPrototypeOf(value);
6664
+ return prototype === Object.prototype || prototype === null;
6665
+ } catch {
6666
+ return false;
6667
+ }
6668
+ };
6669
+ function jsonChildren(value) {
6670
+ const keys = Reflect.ownKeys(value);
6671
+ const descriptors = Object.getOwnPropertyDescriptors(value);
6672
+ if (Array.isArray(value)) {
6673
+ if (Object.getPrototypeOf(value) !== Array.prototype) return null;
6674
+ const length = descriptors.length;
6675
+ if (!length || !("value" in length) || !Number.isSafeInteger(length.value) || length.value < 0) return null;
6676
+ if (keys.length !== length.value + 1 || keys.some((key) => typeof key === "symbol")) return null;
6677
+ const children = [];
6678
+ for (let index = 0; index < length.value; index += 1) {
6679
+ const descriptor = descriptors[String(index)];
6680
+ if (!descriptor?.enumerable || !("value" in descriptor)) return null;
6681
+ children.push(descriptor.value);
6682
+ }
6683
+ return children;
6684
+ }
6685
+ if (!plainRecord(value) || keys.some((key) => typeof key === "symbol")) return null;
6686
+ const children = [];
6687
+ for (const key of keys) {
6688
+ const descriptor = descriptors[key];
6689
+ if (!descriptor?.enumerable || !("value" in descriptor)) return null;
6690
+ children.push(descriptor.value);
6691
+ }
6692
+ return children;
6693
+ }
6694
+ function jsonValue(root) {
6695
+ const active = /* @__PURE__ */ new WeakSet();
6696
+ const stack = [{ value: root }];
6697
+ while (stack.length > 0) {
6698
+ const frame = stack.pop();
6699
+ if (frame.leave) {
6700
+ active.delete(frame.value);
6701
+ continue;
6702
+ }
6703
+ const { value } = frame;
6704
+ if (value === null || typeof value === "string" || typeof value === "boolean") continue;
6705
+ if (typeof value === "number") {
6706
+ if (!Number.isFinite(value)) return false;
6707
+ continue;
6708
+ }
6709
+ if (typeof value !== "object" || active.has(value)) return false;
6710
+ let children;
6711
+ try {
6712
+ children = jsonChildren(value);
6713
+ } catch {
6714
+ return false;
6715
+ }
6716
+ if (!children) return false;
6717
+ active.add(value);
6718
+ stack.push({
6719
+ value,
6720
+ leave: true
6721
+ });
6722
+ for (let index = children.length - 1; index >= 0; index -= 1) stack.push({ value: children[index] });
6723
+ }
6724
+ return true;
6725
+ }
6726
+ const recordValue = (value) => plainRecord(value) && jsonValue(value);
6727
+ const stringRecord = (value) => {
6728
+ if (!recordValue(value)) return false;
6729
+ try {
6730
+ return Object.values(Object.getOwnPropertyDescriptors(value)).every((descriptor) => "value" in descriptor && typeof descriptor.value === "string");
6731
+ } catch {
6732
+ return false;
6733
+ }
6734
+ };
6735
+ const stringArray = (value) => Array.isArray(value) && value.every(stringValue);
6736
+ const permission = (...values) => (value) => typeof value === "string" && values.includes(value);
6737
+ const EMPTY = { fields: {} };
6738
+ const FOLLOWUP = { fields: { followup_message: stringValue } };
6739
+ const PERMISSION_ASK = {
6740
+ fields: {
6741
+ permission: permission("allow", "deny", "ask"),
6742
+ user_message: stringValue,
6743
+ agent_message: stringValue
6744
+ },
6745
+ required: ["permission"]
6746
+ };
6747
+ const NATIVE_SCHEMAS = {
6748
+ sessionStart: { fields: {
6749
+ env: stringRecord,
6750
+ additional_context: stringValue,
6751
+ continue: booleanValue,
6752
+ user_message: stringValue
6753
+ } },
6754
+ sessionEnd: EMPTY,
6755
+ beforeSubmitPrompt: {
6756
+ fields: {
6757
+ continue: booleanValue,
6758
+ user_message: stringValue
6759
+ },
6760
+ required: ["continue"]
6761
+ },
6762
+ preCompact: { fields: { user_message: stringValue } },
6763
+ subagentStart: {
6764
+ fields: {
6765
+ permission: permission("allow", "deny"),
6766
+ user_message: stringValue
6767
+ },
6768
+ required: ["permission"]
6769
+ },
6770
+ subagentStop: FOLLOWUP,
6771
+ preToolUse: {
6772
+ fields: {
6773
+ ...PERMISSION_ASK.fields,
6774
+ updated_input: recordValue
6775
+ },
6776
+ required: ["permission"]
6777
+ },
6778
+ postToolUse: { fields: {
6779
+ updated_mcp_tool_output: recordValue,
6780
+ additional_context: stringValue
6781
+ } },
6782
+ postToolUseFailure: EMPTY,
6783
+ beforeShellExecution: PERMISSION_ASK,
6784
+ afterShellExecution: EMPTY,
6785
+ beforeMCPExecution: PERMISSION_ASK,
6786
+ afterMCPExecution: EMPTY,
6787
+ beforeReadFile: {
6788
+ fields: {
6789
+ permission: permission("allow", "deny"),
6790
+ user_message: stringValue
6791
+ },
6792
+ required: ["permission"]
6793
+ },
6794
+ afterFileEdit: EMPTY,
6795
+ beforeTabFileRead: {
6796
+ fields: { permission: permission("allow", "deny") },
6797
+ required: ["permission"]
6798
+ },
6799
+ afterTabFileEdit: EMPTY,
6800
+ afterAgentResponse: EMPTY,
6801
+ afterAgentThought: EMPTY,
6802
+ stop: FOLLOWUP,
6803
+ workspaceOpen: { fields: { pluginPaths: stringArray } }
6804
+ };
6805
+ function isNativeCursorResponse(value, eventName) {
6806
+ try {
6807
+ if (!recordValue(value)) return false;
6808
+ const schema = NATIVE_SCHEMAS[eventName];
6809
+ if (!schema) return false;
6810
+ const descriptors = Object.getOwnPropertyDescriptors(value);
6811
+ if (schema.required?.some((field) => !Object.hasOwn(descriptors, field))) return false;
6812
+ return Reflect.ownKeys(descriptors).every((field) => {
6813
+ if (typeof field !== "string") return false;
6814
+ const descriptor = descriptors[field];
6815
+ if (!Object.hasOwn(schema.fields, field)) return false;
6816
+ const validate = schema.fields[field];
6817
+ return descriptor?.enumerable === true && "value" in descriptor && validate !== void 0 && validate(descriptor.value);
6818
+ });
6819
+ } catch {
6820
+ return false;
6821
+ }
6822
+ }
6823
+ /** Preserve raw stdout only when JSON.parse proves a documented native Cursor response. */
6824
+ function parseNativeCursorStdout(stdout, eventName) {
6825
+ if (typeof stdout !== "string") return null;
6826
+ try {
6827
+ return isNativeCursorResponse(JSON.parse(stdout), eventName) ? stdout : null;
6828
+ } catch {
6829
+ return null;
6830
+ }
6831
+ }
6832
+ //#endregion
6833
+ //#region src/adapters/cursor/respond.ts
6834
+ const AGENT_MESSAGE_EVENTS = /* @__PURE__ */ new Set([
6835
+ "preToolUse",
6836
+ "beforeShellExecution",
6837
+ "beforeMCPExecution"
6838
+ ]);
6839
+ const USER_MESSAGE_EVENTS = /* @__PURE__ */ new Set([
6840
+ "preToolUse",
6841
+ "beforeShellExecution",
6842
+ "beforeMCPExecution",
6843
+ "beforeReadFile",
6844
+ "subagentStart"
6845
+ ]);
6846
+ function permissionMessages(eventName, userMessage, agentMessage) {
6847
+ return {
6848
+ ...userMessage && USER_MESSAGE_EVENTS.has(eventName) ? { user_message: userMessage } : {},
6849
+ ...agentMessage && AGENT_MESSAGE_EVENTS.has(eventName) ? { agent_message: agentMessage } : {}
6850
+ };
6851
+ }
6852
+ function joinMessages(...values) {
6853
+ return values.filter((value) => typeof value === "string" && value.length > 0).join("\n");
6854
+ }
6855
+ function isRecord(value) {
6856
+ return typeof value === "object" && value !== null && !Array.isArray(value);
6857
+ }
6858
+ /** Render a portable policy prompt using the native Cursor event contract. */
6859
+ function toCursorResponse(prompt, eventName) {
6860
+ const contract = cursorEventContract(eventName);
6861
+ const message = formatPrompt(prompt);
6862
+ if (!contract.known || contract.response === "neutral" || contract.response === "plugin-paths") return "{}";
6863
+ if (contract.response === "post-context" || contract.response === "session-context") return JSON.stringify({ additional_context: message });
6864
+ if (contract.response === "followup") return JSON.stringify({ followup_message: message });
6865
+ if (contract.response === "compact-notice") return JSON.stringify({ user_message: prompt.userMessage ?? message });
6866
+ if (contract.response === "submit-control") return JSON.stringify({
6867
+ continue: prompt.kind !== "block",
6868
+ user_message: prompt.userMessage ?? message
6869
+ });
6870
+ if (prompt.kind === "inform") return JSON.stringify({
6871
+ permission: "allow",
6872
+ ...permissionMessages(eventName, prompt.userMessage, prompt.reason ? message : void 0)
6873
+ });
6874
+ const userMessage = prompt.kind === "ask" ? `[downgraded from ask — Cursor does not enforce approval for this event]\n${message}` : message;
6875
+ return JSON.stringify({
6876
+ permission: "deny",
6877
+ ...permissionMessages(eventName, userMessage, userMessage)
6878
+ });
6879
+ }
6880
+ /** Convert a shared lifecycle handler's output to the native Cursor envelope. */
6881
+ function toCursorLifecycleResponse(stdout, eventName) {
6882
+ const contract = cursorEventContract(eventName);
6883
+ const native = parseNativeCursorStdout(stdout, eventName);
6884
+ if (native !== null) return native;
6885
+ let text = stdout;
6886
+ let decision;
6887
+ let userMessage = "";
6888
+ let agentMessage = "";
6889
+ let decisionMessage = "";
6890
+ let structured = false;
6891
+ try {
6892
+ const parsed = JSON.parse(stdout);
6893
+ const subagentAsk = eventName === "subagentStart" && isRecord(parsed) && Object.hasOwn(parsed, "permission") && parsed.permission === "ask";
6894
+ const shared = isRecord(parsed) ? parsed : {};
6895
+ structured = true;
6896
+ decision = shared.hookSpecificOutput?.permissionDecision ?? (subagentAsk ? "ask" : void 0);
6897
+ userMessage = shared.systemMessage ?? (subagentAsk && Object.hasOwn(shared, "user_message") && typeof shared.user_message === "string" ? shared.user_message : "");
6898
+ decisionMessage = joinMessages(shared.hookSpecificOutput?.permissionDecisionReason, shared.reason);
6899
+ agentMessage = joinMessages(shared.hookSpecificOutput?.additionalContext, decisionMessage);
6900
+ text = joinMessages(agentMessage, userMessage, shared.followup_message);
6901
+ } catch {
6902
+ text = stdout;
6903
+ }
6904
+ if (contract.response === "neutral") return "{}";
6905
+ if (!text) return contract.response === "permission" ? "{\"permission\":\"allow\"}" : "{}";
6906
+ if (contract.response === "session-context" || contract.response === "post-context") return JSON.stringify({ additional_context: text });
6907
+ if (contract.response === "permission") {
6908
+ const permission = decision === "deny" || decision === "ask" ? "deny" : "allow";
6909
+ const denied = permission === "deny";
6910
+ if (eventName === "subagentStart" && !denied) return "{\"permission\":\"allow\"}";
6911
+ return JSON.stringify({
6912
+ permission,
6913
+ ...permissionMessages(eventName, userMessage || (denied ? decisionMessage || agentMessage : ""), agentMessage || (denied ? decisionMessage || userMessage : structured ? "" : text))
6914
+ });
6915
+ }
6916
+ if (contract.response === "followup") return JSON.stringify({ followup_message: text });
6917
+ if (contract.response === "compact-notice") return JSON.stringify({ user_message: text });
6918
+ if (contract.response === "submit-control") return JSON.stringify({
6919
+ continue: true,
6920
+ user_message: text
6921
+ });
6922
+ return "{}";
6923
+ }
6924
+ //#endregion
6649
6925
  //#region src/runtime/lifecycle-bridge.ts
6650
6926
  /** Raw event name from a payload (Cline lacks one; lifecycle is Claude-only). */
6651
6927
  function rawEvent(payload) {
6652
6928
  return typeof payload.hook_event_name === "string" ? payload.hook_event_name : "";
6653
6929
  }
6654
6930
  /** Extract the `additionalContext` body from a `contextResponse(...)` stdout string ("" when empty/unparseable). */
6655
- function additionalContextOf(stdout) {
6931
+ function additionalContextOf$1(stdout) {
6656
6932
  if (!stdout) return "";
6657
6933
  try {
6658
6934
  return JSON.parse(stdout).hookSpecificOutput?.additionalContext ?? "";
@@ -6673,14 +6949,30 @@ function additionalContextOf(stdout) {
6673
6949
  * @returns The native stdout, or `null` when unhandled.
6674
6950
  */
6675
6951
  function lifecycleStdout(payload, cwd, scope, now, id = "claude-code") {
6676
- return dispatchLifecycle({
6677
- event: rawEvent(payload),
6952
+ const wireEvent = rawEvent(payload);
6953
+ if (id !== "cursor") return dispatchLifecycle({
6954
+ event: wireEvent,
6678
6955
  payload,
6679
6956
  cwd,
6680
6957
  scope,
6681
6958
  now,
6682
6959
  id
6683
6960
  });
6961
+ const lifecycleEvent = cursorEventContract(wireEvent).lifecycle;
6962
+ if (lifecycleEvent === null) return null;
6963
+ const sessionId = typeof payload.session_id === "string" ? payload.session_id : payload.conversation_id;
6964
+ const stdout = dispatchLifecycle({
6965
+ event: lifecycleEvent,
6966
+ payload: {
6967
+ ...payload,
6968
+ session_id: sessionId
6969
+ },
6970
+ cwd,
6971
+ scope,
6972
+ now,
6973
+ id
6974
+ });
6975
+ return stdout === null ? null : toCursorLifecycleResponse(stdout, wireEvent);
6684
6976
  }
6685
6977
  /**
6686
6978
  * Post-edit additions for core-scope PostToolUse: auto-document a Read of a
@@ -6702,7 +6994,7 @@ async function postEditContext(scope, event, now, id = "claude-code") {
6702
6994
  const sniper = trackSessionChanges(event.sessionId, event.filePath, void 0, now);
6703
6995
  const lint = postEditTypescript(event.filePath);
6704
6996
  if (!sniper || !lint) return sniper || lint;
6705
- return contextResponse("PostToolUse", `${additionalContextOf(sniper)}\n\n${additionalContextOf(lint)}`);
6997
+ return contextResponse("PostToolUse", `${additionalContextOf$1(sniper)}\n\n${additionalContextOf$1(lint)}`);
6706
6998
  }
6707
6999
  //#endregion
6708
7000
  //#region src/policy/trivial-edits.ts
@@ -7694,11 +7986,37 @@ const TRIVIAL_BUDGET = 4;
7694
7986
  * {@link withDenyLoop} rewrite an identical retried deny (decision unchanged).
7695
7987
  */
7696
7988
  async function gate(input) {
7697
- const prompt = await runGates(input);
7989
+ return finalizeGate(input, await runGates(input), input.command);
7990
+ }
7991
+ /**
7992
+ * Gate independent Cursor command candidates without merging their shell syntax.
7993
+ * Stateful outcome bookkeeping runs once, against the command that decided the
7994
+ * aggregate result. A single distinct candidate follows {@link gate} verbatim.
7995
+ */
7996
+ async function gateCommandCandidates(input, candidates) {
7997
+ const distinct = [...new Set(candidates)];
7998
+ if (distinct.length <= 1) return gate(input);
7999
+ let decisive;
8000
+ for (const command of distinct) {
8001
+ const prompt = await runGates({
8002
+ ...input,
8003
+ command
8004
+ });
8005
+ if (!prompt) continue;
8006
+ if (!decisive || promptRank(prompt) > promptRank(decisive.prompt)) decisive = {
8007
+ prompt,
8008
+ command
8009
+ };
8010
+ if (prompt.kind === "block") break;
8011
+ }
8012
+ return finalizeGate(input, decisive?.prompt ?? null, decisive?.command ?? input.command);
8013
+ }
8014
+ /** Persist one final gate outcome and apply deny-loop enrichment exactly once. */
8015
+ function finalizeGate(input, prompt, command) {
7698
8016
  const op = {
7699
8017
  filePath: input.filePath,
7700
8018
  content: input.content,
7701
- command: input.command
8019
+ command
7702
8020
  };
7703
8021
  const dir = dirname(input.trackFile);
7704
8022
  recordOneShot(prompt, op, {
@@ -7713,6 +8031,10 @@ async function gate(input) {
7713
8031
  sessionId: input.sessionId
7714
8032
  });
7715
8033
  }
8034
+ /** Higher numeric values dominate while equal values preserve wire order. */
8035
+ function promptRank(prompt) {
8036
+ return prompt.kind === "block" ? 3 : prompt.kind === "ask" ? 2 : 1;
8037
+ }
7716
8038
  /** Stateless guards, then the trivial fast path, then the stateful APEX gates. */
7717
8039
  async function runGates(input) {
7718
8040
  const precommit = preCommitGate(input.tool, input.command, input.cwd);
@@ -7900,8 +8222,8 @@ async function recordActivity(file, activity) {
7900
8222
  * - gemini-cli/cline: their real hook schemas have no interactive "ask"
7901
8223
  * state (deny is the only blocking outcome), so `ask` and `inform` already
7902
8224
  * both resolve to non-blocking context injection — unchanged.
7903
- * - cursor: `ask` keeps its current best-effort `permission:"ask"` shape;
7904
- * only `inform` is fixed to a non-blocking `permission:"allow"` note.
8225
+ * - cursor: `ask` degrades to a native `permission:"deny"` because Cursor does
8226
+ * not reliably apply `ask` on `preToolUse`; `inform` stays non-blocking.
7905
8227
  * - hermes: delegated to the adapter's `toHermesResponse` — `block` ->
7906
8228
  * `{decision:"block",reason}`; `ask`/`inform` degrade to non-blocking
7907
8229
  * `{context}` (Hermes has no interactive "ask" state).
@@ -7955,25 +8277,7 @@ function respond(id, prompt, event = "PreToolUse") {
7955
8277
  systemMessage: userMessage
7956
8278
  });
7957
8279
  return JSON.stringify({ hookSpecificOutput: { additionalContext: message } });
7958
- case "cursor":
7959
- if (kind === "inform") {
7960
- if (userMessage) return JSON.stringify({
7961
- permission: "allow",
7962
- user_message: userMessage,
7963
- ...reason ? { agent_message: message } : {}
7964
- });
7965
- return JSON.stringify({
7966
- permission: "allow",
7967
- user_message: message,
7968
- agent_message: message
7969
- });
7970
- }
7971
- return JSON.stringify({
7972
- permission: kind === "block" ? "deny" : "ask",
7973
- continue: false,
7974
- user_message: message,
7975
- agent_message: message
7976
- });
8280
+ case "cursor": return toCursorResponse(prompt, event === "PreToolUse" ? "preToolUse" : event);
7977
8281
  case "hermes": return toHermesResponse(prompt);
7978
8282
  case "kimi": return toKimiResponse(prompt);
7979
8283
  case "cline":
@@ -8658,8 +8962,8 @@ function segments(command) {
8658
8962
  }
8659
8963
  /** Strip a redirection (`>`, `>>`, `<`, `2>`, `&>`, …) and everything after — its target is WRITTEN, not read. */
8660
8964
  function beforeRedirect(segment) {
8661
- const m = segment.match(/\s(?:\d*>{1,2}|<|&>)\s*\S/);
8662
- return m ? segment.slice(0, m.index) : segment;
8965
+ const first = shellOutputRedirects(segment)[0];
8966
+ return first ? segment.slice(0, first.start) : segment;
8663
8967
  }
8664
8968
  /** Naive shell tokenizer: whitespace-split, stripping one matching layer of quotes per token. */
8665
8969
  function tokenize(segment) {
@@ -9155,7 +9459,7 @@ async function allowOutcome(id, event, payload, mcpDir, cwd, evidence, corpusRoo
9155
9459
  stdout: respond(id, userMessage ? {
9156
9460
  ...lesson,
9157
9461
  userMessage
9158
- } : lesson),
9462
+ } : lesson, event.eventName ?? "PreToolUse"),
9159
9463
  exit: 0
9160
9464
  };
9161
9465
  }
@@ -9170,12 +9474,12 @@ async function allowOutcome(id, event, payload, mcpDir, cwd, evidence, corpusRoo
9170
9474
  title: "APEX freshness",
9171
9475
  reason: "",
9172
9476
  userMessage
9173
- }),
9477
+ }, event.eventName ?? "PreToolUse"),
9174
9478
  exit: 0
9175
9479
  };
9176
9480
  }
9177
9481
  return {
9178
- stdout: notice ? respond(id, notice) : "",
9482
+ stdout: notice ? respond(id, notice, event.eventName ?? "PreToolUse") : "",
9179
9483
  exit: 0
9180
9484
  };
9181
9485
  }
@@ -9316,7 +9620,7 @@ function getPendingDeny(sessionIdRaw, home = homedir()) {
9316
9620
  //#endregion
9317
9621
  //#region src/runtime/confirm/confirm-state.ts
9318
9622
  /** Confirmation freshness window (G2) — 5 minutes, matches the validated prototype. */
9319
- const TTL_MS = 300 * 1e3;
9623
+ const TTL_MS$1 = 300 * 1e3;
9320
9624
  /**
9321
9625
  * Pose a confirmation token for this session's pending action. No-op while
9322
9626
  * {@link isSubagentActive} (G0, confirm-subagent.ts) — the sole gate that
@@ -9356,7 +9660,7 @@ function consumeConfirmToken(sessionIdRaw, hash, now, home = homedir()) {
9356
9660
  if (!sid) return false;
9357
9661
  const tok = loadSessionState(sid, home).confirmToken;
9358
9662
  if (!tok || tok.hash !== hash) return false;
9359
- if (now - tok.ts > TTL_MS) {
9663
+ if (now - tok.ts > TTL_MS$1) {
9360
9664
  dropConfirmToken(sid, home);
9361
9665
  return false;
9362
9666
  }
@@ -9364,6 +9668,141 @@ function consumeConfirmToken(sessionIdRaw, hash, now, home = homedir()) {
9364
9668
  return true;
9365
9669
  }
9366
9670
  //#endregion
9671
+ //#region src/runtime/confirm/codex-confirm.ts
9672
+ const TTL_MS = 300 * 1e3;
9673
+ const STATE_VERSION = 1;
9674
+ function statePath(sid, home) {
9675
+ return join(sessionsDir(home), `codex-confirm-${sid}.json`);
9676
+ }
9677
+ function lockDir(sid, home) {
9678
+ return join(sessionsDir(home), ".confirm-locks", sid);
9679
+ }
9680
+ function loadState(sid, home) {
9681
+ const path = statePath(sid, home);
9682
+ if (!existsSync(path)) return {};
9683
+ const parsed = JSON.parse(readFileSync(path, "utf8"));
9684
+ return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) ? parsed : {};
9685
+ }
9686
+ function saveState(sid, state, home) {
9687
+ mkdirSync(sessionsDir(home), {
9688
+ recursive: true,
9689
+ mode: 448
9690
+ });
9691
+ atomicWrite(statePath(sid, home), JSON.stringify(state, null, 2));
9692
+ }
9693
+ /** Canonical identity of one Codex shell action and its display token. */
9694
+ function codexAction(tool, cwd, command, now) {
9695
+ const canonicalCommand = commandToString(command);
9696
+ if (!canonicalCommand) return null;
9697
+ const identity = JSON.stringify({
9698
+ version: STATE_VERSION,
9699
+ harness: "codex",
9700
+ tool,
9701
+ cwd: resolve(cwd),
9702
+ command: canonicalCommand
9703
+ });
9704
+ return {
9705
+ hash: hashForAction(identity),
9706
+ code: displayCodeForAction(identity),
9707
+ command: canonicalCommand,
9708
+ ts: now
9709
+ };
9710
+ }
9711
+ /** Atomically authorize or reject a Codex action, recording the current denial when needed. */
9712
+ function authorizeCodexAction(sessionIdRaw, action, toolUseId, now, home = homedir()) {
9713
+ const sid = sanitizeSessionId(sessionIdRaw);
9714
+ if (!sid) return {
9715
+ allow: false,
9716
+ reason: "state-io"
9717
+ };
9718
+ try {
9719
+ const result = withTrackLockSync(lockDir(sid, home), () => {
9720
+ const state = loadState(sid, home);
9721
+ const token = state.codexConfirmToken;
9722
+ const receipt = state.codexConfirmReceipt;
9723
+ const receiptExpired = receipt !== void 0 && now - receipt.ts > TTL_MS;
9724
+ if (!receiptExpired && receipt?.hash === action.hash && receipt.toolUseId === toolUseId && toolUseId) return { allow: true };
9725
+ let reason = "no-token";
9726
+ if (token) if (token.hash !== action.hash) reason = "mismatch";
9727
+ else if (now - token.ts > TTL_MS) reason = "expired";
9728
+ else if (!toolUseId) reason = "missing-tool-use-id";
9729
+ else {
9730
+ const { codexConfirmToken: _token, codexConfirmPending: _pending, ...rest } = state;
9731
+ saveState(sid, {
9732
+ ...rest,
9733
+ codexConfirmReceipt: {
9734
+ ...action,
9735
+ toolUseId
9736
+ }
9737
+ }, home);
9738
+ return { allow: true };
9739
+ }
9740
+ else if (receipt?.hash === action.hash) reason = receiptExpired ? "expired" : "already-consumed";
9741
+ if (reason === "missing-tool-use-id") return {
9742
+ allow: false,
9743
+ reason
9744
+ };
9745
+ const pending = state.codexConfirmPending;
9746
+ const stablePending = pending?.hash === action.hash ? pending : action;
9747
+ const { codexConfirmToken: _token, codexConfirmReceipt: _receipt, ...withoutReceipt } = state;
9748
+ const rest = receiptExpired ? withoutReceipt : {
9749
+ ...withoutReceipt,
9750
+ codexConfirmReceipt: receipt
9751
+ };
9752
+ saveState(sid, {
9753
+ ...rest,
9754
+ codexConfirmPending: stablePending
9755
+ }, home);
9756
+ return {
9757
+ allow: false,
9758
+ reason
9759
+ };
9760
+ });
9761
+ return result === LOCK_FAILED ? {
9762
+ allow: false,
9763
+ reason: "state-io"
9764
+ } : result;
9765
+ } catch {
9766
+ return {
9767
+ allow: false,
9768
+ reason: "state-io"
9769
+ };
9770
+ }
9771
+ }
9772
+ /**
9773
+ * Atomically arm the last Codex denial, consuming its pending record exactly once.
9774
+ * A classified root prompt may bypass G0; classified subagent/unknown prompts fail closed.
9775
+ */
9776
+ function submitCodexConfirmation(sessionIdRaw, text, now, home = homedir(), origin) {
9777
+ const sid = sanitizeSessionId(sessionIdRaw);
9778
+ if (!sid) return;
9779
+ const refusal = /\b(non|no|stop|annule|cancel|abort|nope|laisse tomber|pas maintenant)\b/i.test(text);
9780
+ const typedCode = text.trim().match(/^confirm[ _-]*([0-9a-f]{4})$/i)?.[1];
9781
+ try {
9782
+ withTrackLockSync(lockDir(sid, home), () => {
9783
+ const state = loadState(sid, home);
9784
+ if (origin !== void 0 && origin !== "root") return;
9785
+ if (refusal) {
9786
+ const { codexConfirmPending: _pending, codexConfirmToken: _token, ...rest } = state;
9787
+ saveState(sid, rest, home);
9788
+ return;
9789
+ }
9790
+ if (!typedCode) return;
9791
+ if (origin === void 0 && isSubagentActive(sid, now, home)) return;
9792
+ const pending = state.codexConfirmPending;
9793
+ if (!pending || pending.code.toLowerCase() !== typedCode.toLowerCase()) return;
9794
+ const { codexConfirmPending: _pending, ...rest } = state;
9795
+ saveState(sid, {
9796
+ ...rest,
9797
+ codexConfirmToken: {
9798
+ ...pending,
9799
+ ts: now
9800
+ }
9801
+ }, home);
9802
+ });
9803
+ } catch {}
9804
+ }
9805
+ //#endregion
9367
9806
  //#region src/runtime/confirm/confirm-gate.ts
9368
9807
  /**
9369
9808
  * Harnesses where `respond.ts` silently downgrades `kind: "ask"` to a hard
@@ -9387,9 +9826,23 @@ const DEGRADES_ASK_TO_DENY = /* @__PURE__ */ new Set(["codex", "kimi"]);
9387
9826
  * @param now - Epoch ms.
9388
9827
  * @param home - Test-only OS home override.
9389
9828
  */
9390
- function confirmGate(id, prompt, command, sessionId, now, home) {
9829
+ function confirmGate(id, prompt, command, sessionId, now, home, codex) {
9391
9830
  if (prompt.kind !== "ask" || !DEGRADES_ASK_TO_DENY.has(id) || !command || isIrreversible(command)) return null;
9392
9831
  try {
9832
+ if (id === "codex" && codex) {
9833
+ const action = codexAction(codex.tool, codex.cwd, command, now);
9834
+ if (!action) return null;
9835
+ const verdict = authorizeCodexAction(sessionId, action, codex.toolUseId, now, home);
9836
+ if (verdict.allow) return verdict;
9837
+ const diagnostic = `rule ID: ${prompt.ruleId ?? "policy:ask"}\ncanonical command: ${action.command}\nexpected token: CONFIRM ${action.code}\nrejection: ${verdict.reason}`;
9838
+ return {
9839
+ allow: false,
9840
+ prompt: {
9841
+ ...prompt,
9842
+ reason: `${prompt.reason}\nPour autoriser, réponds : CONFIRM ${action.code}\n${diagnostic}`
9843
+ }
9844
+ };
9845
+ }
9393
9846
  const hash = hashForAction(command);
9394
9847
  if (consumeConfirmToken(sessionId, hash, now, home)) return { allow: true };
9395
9848
  const code = displayCodeForAction(command);
@@ -9430,9 +9883,19 @@ async function handlePre(ctx) {
9430
9883
  }
9431
9884
  const designBlock = designGate(payload, event, designCacheDir, opts.cwd, opts.corpusRoot);
9432
9885
  if (designBlock) return {
9433
- stdout: withDenyNotice(id, respond(id, designBlock), designBlock, event.sessionId, dirname(file), opts.now),
9886
+ stdout: withDenyNotice(id, respond(id, designBlock, event.eventName ?? "PreToolUse"), designBlock, event.sessionId, dirname(file), opts.now),
9434
9887
  exit: 0
9435
9888
  };
9889
+ if (id === "cursor" && (event.eventName === "beforeReadFile" || event.eventName === "beforeTabFileRead")) {
9890
+ const readPolicy = evaluate({
9891
+ tool: event.tool,
9892
+ filePath: event.filePath
9893
+ });
9894
+ return {
9895
+ stdout: readPolicy.prompt ? respond(id, readPolicy.prompt, event.eventName) : JSON.stringify({ permission: "allow" }),
9896
+ exit: 0
9897
+ };
9898
+ }
9436
9899
  if (opts.scope === "security") return {
9437
9900
  stdout: event.files?.length ? securityAdvisoryForPatch(event.files, opts.now) : securityAdvisory(event.tool, event.filePath ?? "", opts.now),
9438
9901
  exit: 0
@@ -9448,11 +9911,12 @@ async function handlePre(ctx) {
9448
9911
  if (event.files && event.files.length > 0) {
9449
9912
  const patchPrompt = applyPatchGate(event.files, opts.cwd);
9450
9913
  if (patchPrompt) return {
9451
- stdout: withDenyNotice(id, respond(id, patchPrompt), patchPrompt, event.sessionId, dirname(file), opts.now),
9914
+ stdout: withDenyNotice(id, respond(id, patchPrompt, event.eventName ?? "PreToolUse"), patchPrompt, event.sessionId, dirname(file), opts.now),
9452
9915
  exit: 0
9453
9916
  };
9454
9917
  }
9455
- const prompt = await gate({
9918
+ const refs = opts.refsDir ? await loadRefs(opts.refsDir) : void 0;
9919
+ const gateInput = {
9456
9920
  sessionId: event.sessionId,
9457
9921
  framework,
9458
9922
  tool: event.tool,
@@ -9460,7 +9924,7 @@ async function handlePre(ctx) {
9460
9924
  content: event.content,
9461
9925
  command: event.command,
9462
9926
  cwd: opts.cwd,
9463
- refs: opts.refsDir ? await loadRefs(opts.refsDir) : void 0,
9927
+ refs,
9464
9928
  isReplaceAll: event.input.replace_all === true,
9465
9929
  oldString: event.oldString,
9466
9930
  agentType: event.agentType,
@@ -9470,9 +9934,14 @@ async function handlePre(ctx) {
9470
9934
  trackFile: file,
9471
9935
  transcriptPath: typeof payload.transcript_path === "string" ? payload.transcript_path : void 0,
9472
9936
  neverApproval: id === "codex" && isBypassPermissions(event.permissionMode)
9473
- });
9937
+ };
9938
+ const prompt = id === "cursor" && (event.commandCandidates?.length ?? 0) > 1 ? await gateCommandCandidates(gateInput, event.commandCandidates) : await gate(gateInput);
9474
9939
  if (prompt) {
9475
- const confirm = confirmGate(id, prompt, event.command, event.sessionId, opts.now, opts.home);
9940
+ const confirm = confirmGate(id, prompt, event.command, event.sessionId, opts.now, opts.home, id === "codex" ? {
9941
+ tool: event.tool,
9942
+ cwd: event.cwd ?? opts.cwd,
9943
+ toolUseId: event.toolUseId
9944
+ } : void 0);
9476
9945
  if (confirm?.allow) return allowOutcome(id, event, payload, designCacheDir, opts.cwd, {
9477
9946
  trackFile: file,
9478
9947
  windowMs: opts.windowMs,
@@ -9480,7 +9949,7 @@ async function handlePre(ctx) {
9480
9949
  }, opts.corpusRoot);
9481
9950
  const finalPrompt = confirm ? confirm.prompt : prompt;
9482
9951
  return {
9483
- stdout: withDenyNotice(id, respond(id, finalPrompt), finalPrompt, event.sessionId, dirname(file), opts.now),
9952
+ stdout: withDenyNotice(id, respond(id, finalPrompt, event.eventName ?? "PreToolUse"), finalPrompt, event.sessionId, dirname(file), opts.now),
9484
9953
  exit: 0
9485
9954
  };
9486
9955
  }
@@ -9847,14 +10316,14 @@ function recordCodexPostFailure(tool, result, opts) {
9847
10316
  //#endregion
9848
10317
  //#region src/runtime/post-fanout.ts
9849
10318
  /**
9850
- * Fan a Codex `apply_patch` envelope's `event.files` into one synthetic
10319
+ * Fan Codex `apply_patch` or Cursor `afterFileEdit` `event.files` into one
9851
10320
  * per-file event per touched file, so the per-file PostToolUse gates (SOLID
9852
10321
  * size, Tailwind, tracking, post-edit context) see EACH file instead of the
9853
10322
  * whole patch (whose own `filePath`/`content` are always undefined). `add`
9854
10323
  * maps to `Write`, `update` to `Edit` (mirrors the Pre-phase `applyPatchGate`'s
9855
10324
  * tool mapping); `delete`/`move` map to a tool none of those Write|Edit-gated
9856
10325
  * checks recognize, so they no-op on it without special-casing each gate.
9857
- * Returns `[event]` unchanged for every non-`apply_patch` tool/harness.
10326
+ * Returns `[event]` unchanged when no per-file changes are present.
9858
10327
  * @param event - The normalized PostToolUse event.
9859
10328
  * @returns One event per touched file, or the original event.
9860
10329
  */
@@ -9864,7 +10333,8 @@ function fanOutFiles(event) {
9864
10333
  ...event,
9865
10334
  tool: f.op === "add" ? "Write" : f.op === "update" ? "Edit" : "apply_patch:delete",
9866
10335
  filePath: f.filePath,
9867
- content: f.op === "delete" ? void 0 : f.content
10336
+ content: f.op === "delete" ? void 0 : f.content,
10337
+ oldString: f.oldString
9868
10338
  }));
9869
10339
  }
9870
10340
  /**
@@ -9884,7 +10354,106 @@ function firstFileMatch(files, check) {
9884
10354
  return "";
9885
10355
  }
9886
10356
  //#endregion
10357
+ //#region src/runtime/post-outcome.ts
10358
+ /** Extract the additional-context body from a Claude-shaped post response. */
10359
+ function additionalContextOf(stdout) {
10360
+ if (!stdout) return "";
10361
+ try {
10362
+ return JSON.parse(stdout).hookSpecificOutput?.additionalContext ?? "";
10363
+ } catch {
10364
+ return "";
10365
+ }
10366
+ }
10367
+ function cursorPostToolOutput(eventName, designWarn, userMessage, extra) {
10368
+ if (!/^postToolUse$/i.test(eventName)) return "";
10369
+ const context = [
10370
+ designWarn ? formatPrompt(designWarn) : "",
10371
+ designWarn?.userMessage,
10372
+ userMessage,
10373
+ additionalContextOf(extra)
10374
+ ].filter(Boolean).join("\n");
10375
+ return context ? JSON.stringify({ additional_context: context }) : "";
10376
+ }
10377
+ /**
10378
+ * Assemble the final PostToolUse response after all side effects and deny paths.
10379
+ * @param input - Final assembly values captured at their original evaluation times.
10380
+ * @returns The native hook outcome.
10381
+ */
10382
+ function postOutcome(input) {
10383
+ const { id, agentId, sessionId, now, cwd, activities, files, designCacheDir, designWarn, extra, cursorAfterFileEdit, cursorEventName } = input;
10384
+ const noticeLines = [];
10385
+ for (const f of files) {
10386
+ const notice = designPassNotice({
10387
+ agentId,
10388
+ tool: f.tool,
10389
+ filePath: f.filePath ?? "",
10390
+ content: f.content ?? "",
10391
+ url: "",
10392
+ phase: "post"
10393
+ }, designCacheDir);
10394
+ if (notice?.userMessage) noticeLines.push(notice.userMessage);
10395
+ }
10396
+ const notice = noticeLines.length ? {
10397
+ kind: "inform",
10398
+ title: "Design pipeline",
10399
+ reason: "",
10400
+ userMessage: noticeLines.join("\n")
10401
+ } : null;
10402
+ const refNotice = refCreditNoticeFor(activities, sessionId, now, defaultStateDir(cwd));
10403
+ const userMessage = [notice?.userMessage, refNotice].filter(Boolean).join("\n") || void 0;
10404
+ if (cursorAfterFileEdit) return {
10405
+ stdout: "{}",
10406
+ exit: 0
10407
+ };
10408
+ if (id === "cursor") return {
10409
+ stdout: cursorPostToolOutput(cursorEventName, designWarn, userMessage, extra),
10410
+ exit: 0
10411
+ };
10412
+ if (designWarn) return {
10413
+ stdout: respond(id, userMessage ? {
10414
+ ...designWarn,
10415
+ userMessage
10416
+ } : designWarn, "PostToolUse"),
10417
+ exit: 0
10418
+ };
10419
+ if (!userMessage) return {
10420
+ stdout: extra,
10421
+ exit: 0
10422
+ };
10423
+ const withUserMessage = notice ? {
10424
+ ...notice,
10425
+ userMessage
10426
+ } : {
10427
+ kind: "inform",
10428
+ title: "Compliance",
10429
+ reason: "",
10430
+ userMessage
10431
+ };
10432
+ if (!extra) return {
10433
+ stdout: respond(id, withUserMessage, "PostToolUse"),
10434
+ exit: 0
10435
+ };
10436
+ if (id === "claude-code" || id === "codex") return {
10437
+ stdout: attachSystemMessage(extra, userMessage),
10438
+ exit: 0
10439
+ };
10440
+ if (id === "kimi") return {
10441
+ stdout: respond(id, withUserMessage, "PostToolUse"),
10442
+ exit: 0
10443
+ };
10444
+ return {
10445
+ stdout: respond(id, withUserMessage, "PostToolUse") || extra,
10446
+ exit: 0
10447
+ };
10448
+ }
10449
+ //#endregion
9887
10450
  //#region src/runtime/handle-post.ts
10451
+ function isCursorAfterFileEdit(id, payload) {
10452
+ if (id !== "cursor") return false;
10453
+ const hookEvent = typeof payload.hook_event_name === "string" ? payload.hook_event_name : "";
10454
+ if (hookEvent) return /^afterFileEdit$/i.test(hookEvent);
10455
+ return typeof payload.file_path === "string" && Array.isArray(payload.edits);
10456
+ }
9888
10457
  /**
9889
10458
  * Run the PostToolUse pipeline: store the MCP response, emit a design warning,
9890
10459
  * record the activity into the session track, apply per-scope side-effects (SEO
@@ -9907,8 +10476,14 @@ function firstFileMatch(files, check) {
9907
10476
  */
9908
10477
  async function handlePost(ctx) {
9909
10478
  const { id, payload, event, framework, mcpDir, file, opts } = ctx;
10479
+ const cursorAfterFileEdit = isCursorAfterFileEdit(id, payload);
9910
10480
  const designCacheDir = ctx.designCacheDir ?? mcpDir;
9911
- const response = payload.tool_response ?? payload.tool_output;
10481
+ let response = payload.tool_response ?? payload.tool_output;
10482
+ if (id === "cursor" && /^afterMCPExecution$/i.test(event.eventName ?? "")) try {
10483
+ response = typeof payload.result_json === "string" ? JSON.parse(payload.result_json) : payload.result_json;
10484
+ } catch {
10485
+ response = void 0;
10486
+ }
9912
10487
  mcpPostStore(event.tool, event.input, response, mcpDir);
9913
10488
  const designWarn = designGate(payload, event, designCacheDir, opts.cwd, opts.corpusRoot);
9914
10489
  const activities = activityFor({
@@ -9932,27 +10507,27 @@ async function handlePost(ctx) {
9932
10507
  const files = fanOutFiles(event);
9933
10508
  for (const f of files) postTrackingSideEffects(opts.scope ?? "core", f, f.input, opts.now, payload, opts.cwd);
9934
10509
  const seoDeny = opts.scope === "seo" ? seoPostToolUseResponse(payload) : null;
9935
- if (seoDeny) return {
10510
+ if (seoDeny && !cursorAfterFileEdit) return {
9936
10511
  stdout: seoDeny,
9937
10512
  exit: 0
9938
10513
  };
9939
10514
  if (opts.scope === "solid") {
9940
10515
  const solidWarn = firstFileMatch(files, checkFileSize);
9941
- if (solidWarn) return {
10516
+ if (solidWarn && !cursorAfterFileEdit) return {
9942
10517
  stdout: solidWarn,
9943
10518
  exit: 0
9944
10519
  };
9945
10520
  }
9946
10521
  if (opts.scope === "tailwindcss") {
9947
10522
  const tailwindWarn = firstFileMatch(files, validateTailwind);
9948
- if (tailwindWarn) return {
10523
+ if (tailwindWarn && !cursorAfterFileEdit) return {
9949
10524
  stdout: tailwindWarn,
9950
10525
  exit: 0
9951
10526
  };
9952
10527
  }
9953
10528
  if (opts.scope === "aipilot" && (event.tool === "TaskCreate" || event.tool === "TaskUpdate" || event.tool === "Write" || event.tool === "Edit")) {
9954
10529
  const out = await aipilotPostToolUse(payload, opts.cwd, id);
9955
- if (out) return {
10530
+ if (out && !cursorAfterFileEdit) return {
9956
10531
  stdout: out,
9957
10532
  exit: 0
9958
10533
  };
@@ -9962,63 +10537,20 @@ async function handlePost(ctx) {
9962
10537
  extra = await postEditContext(opts.scope ?? "core", f, opts.now, id);
9963
10538
  if (extra) break;
9964
10539
  }
9965
- const agentId = typeof payload.agent_id === "string" ? payload.agent_id : "";
9966
- const noticeLines = [];
9967
- for (const f of files) {
9968
- const n = designPassNotice({
9969
- agentId,
9970
- tool: f.tool,
9971
- filePath: f.filePath ?? "",
9972
- content: f.content ?? "",
9973
- url: "",
9974
- phase: "post"
9975
- }, designCacheDir);
9976
- if (n?.userMessage) noticeLines.push(n.userMessage);
9977
- }
9978
- const notice = noticeLines.length ? {
9979
- kind: "inform",
9980
- title: "Design pipeline",
9981
- reason: "",
9982
- userMessage: noticeLines.join("\n")
9983
- } : null;
9984
- const refNotice = refCreditNoticeFor(activities, event.sessionId, opts.now, defaultStateDir(opts.cwd));
9985
- const userMessage = [notice?.userMessage, refNotice].filter(Boolean).join("\n") || void 0;
9986
- if (designWarn) return {
9987
- stdout: respond(id, userMessage ? {
9988
- ...designWarn,
9989
- userMessage
9990
- } : designWarn, "PostToolUse"),
9991
- exit: 0
9992
- };
9993
- if (!userMessage) return {
9994
- stdout: extra,
9995
- exit: 0
9996
- };
9997
- const withUserMessage = notice ? {
9998
- ...notice,
9999
- userMessage
10000
- } : {
10001
- kind: "inform",
10002
- title: "Compliance",
10003
- reason: "",
10004
- userMessage
10005
- };
10006
- if (!extra) return {
10007
- stdout: respond(id, withUserMessage, "PostToolUse"),
10008
- exit: 0
10009
- };
10010
- if (id === "claude-code" || id === "codex") return {
10011
- stdout: attachSystemMessage(extra, userMessage),
10012
- exit: 0
10013
- };
10014
- if (id === "kimi") return {
10015
- stdout: respond(id, withUserMessage, "PostToolUse"),
10016
- exit: 0
10017
- };
10018
- return {
10019
- stdout: respond(id, withUserMessage, "PostToolUse") || extra,
10020
- exit: 0
10021
- };
10540
+ return postOutcome({
10541
+ id,
10542
+ agentId: typeof payload.agent_id === "string" ? payload.agent_id : "",
10543
+ sessionId: event.sessionId,
10544
+ now: opts.now,
10545
+ cwd: opts.cwd,
10546
+ activities,
10547
+ files,
10548
+ designCacheDir,
10549
+ designWarn,
10550
+ extra,
10551
+ cursorAfterFileEdit,
10552
+ cursorEventName: typeof payload.hook_event_name === "string" ? payload.hook_event_name : ""
10553
+ });
10022
10554
  }
10023
10555
  //#endregion
10024
10556
  //#region src/runtime/handle-scope-async.ts
@@ -10038,9 +10570,12 @@ async function handlePost(ctx) {
10038
10570
  * @returns The native stdout when intercepted, or `null` to fall through.
10039
10571
  */
10040
10572
  async function asyncScopeStdout(scope, event, payload, cwd, now, id = "claude-code") {
10041
- if (scope === "aipilot") return dispatchAipilot(event, payload, cwd, now, void 0, id);
10042
- if (scope === "memory") return dispatchMemory(event, payload, cwd, now);
10043
- return null;
10573
+ const dispatchedEvent = id === "cursor" ? cursorEventContract(event).lifecycle : event;
10574
+ if (dispatchedEvent === null) return null;
10575
+ let stdout = null;
10576
+ if (scope === "aipilot") stdout = await dispatchAipilot(dispatchedEvent, payload, cwd, now, void 0, id);
10577
+ if (scope === "memory") stdout = await dispatchMemory(dispatchedEvent, payload, cwd, now);
10578
+ return id === "cursor" && stdout !== null ? toCursorLifecycleResponse(stdout, event) : stdout;
10044
10579
  }
10045
10580
  //#endregion
10046
10581
  //#region src/runtime/design-cache-resolve.ts
@@ -10509,6 +11044,18 @@ function handleConfirmSubmit(sessionId, text, now, home) {
10509
11044
  } catch {}
10510
11045
  }
10511
11046
  //#endregion
11047
+ //#region src/runtime/confirm/codex-prompt-origin.ts
11048
+ const AGENT_FIELDS = ["agent_id", "agent_type"];
11049
+ /**
11050
+ * Classify Codex prompt provenance from its optional agent metadata.
11051
+ * Missing metadata is the observed root shape; malformed metadata fails closed.
11052
+ */
11053
+ function codexPromptOrigin(payload) {
11054
+ const present = AGENT_FIELDS.filter((field) => Object.hasOwn(payload, field));
11055
+ if (present.length === 0) return "root";
11056
+ return present.every((field) => typeof payload[field] === "string" && payload[field].trim().length > 0) ? "subagent" : "unknown";
11057
+ }
11058
+ //#endregion
10512
11059
  //#region src/runtime/handle.ts
10513
11060
  /** Raw Claude hook event name from a payload (empty when absent). */
10514
11061
  function rawEventName(payload) {
@@ -10520,8 +11067,18 @@ function rawEventName(payload) {
10520
11067
  * POST event it records the activity into the track. The loop that makes the
10521
11068
  * package behave like the Claude plugin, on any harness.
10522
11069
  */
10523
- async function handleHook(id, payload, opts) {
11070
+ async function handleHookCore(id, payload, opts) {
10524
11071
  const event = normalizeEvent(id, payload);
11072
+ if (id === "cursor") {
11073
+ const cursorCwd = cursorProjectCwd(event.cwd, event.workspaceRoots ?? [], event.filePath, opts.cwd);
11074
+ if (cursorCwd !== opts.cwd) opts = {
11075
+ ...opts,
11076
+ cwd: cursorCwd
11077
+ };
11078
+ }
11079
+ const rawPrompt = payload.prompt;
11080
+ const userPrompt = typeof rawPrompt === "string" || Array.isArray(rawPrompt) ? promptText(rawPrompt) : void 0;
11081
+ if (id === "codex" && rawEventName(payload) === "UserPromptSubmit" && userPrompt !== void 0) submitCodexConfirmation(event.sessionId, userPrompt, opts.now, opts.home, codexPromptOrigin(payload));
10525
11082
  resetFragmentRegistry();
10526
11083
  const layout = projectLayout(opts.cwd);
10527
11084
  const file = trackFile(event.sessionId, defaultStateDir(opts.cwd));
@@ -10543,10 +11100,8 @@ async function handleHook(id, payload, opts) {
10543
11100
  stdout: id === "claude-code" ? attachBudgetRecap(life, rawEventName(payload), event.sessionId, opts.cwd, opts.now) : life,
10544
11101
  exit: 0
10545
11102
  };
10546
- const rawPrompt = payload.prompt;
10547
- const userPrompt = typeof rawPrompt === "string" || Array.isArray(rawPrompt) ? promptText(rawPrompt) : void 0;
10548
11103
  if (userPrompt !== void 0) {
10549
- handleConfirmSubmit(event.sessionId, userPrompt, opts.now, opts.home);
11104
+ if (id !== "codex") handleConfirmSubmit(event.sessionId, userPrompt, opts.now, opts.home);
10550
11105
  await withTrack(file, (track) => recordBrainstormRequired(track, detectCreationIntent(userPrompt)));
10551
11106
  return {
10552
11107
  stdout: promptSubmitContext(userPrompt, opts.cwd, id),
@@ -10574,5 +11129,17 @@ async function handleHook(id, payload, opts) {
10574
11129
  opts
10575
11130
  });
10576
11131
  }
11132
+ /**
11133
+ * Run one hook and adapt every Cursor scope outcome at the common runtime exit.
11134
+ * Other harnesses retain the core handler's stdout and exit status unchanged.
11135
+ */
11136
+ async function handleHook(id, payload, opts) {
11137
+ const outcome = await handleHookCore(id, payload, opts);
11138
+ if (id !== "cursor") return outcome;
11139
+ return {
11140
+ ...outcome,
11141
+ stdout: toCursorLifecycleResponse(outcome.stdout, rawEventName(payload))
11142
+ };
11143
+ }
10577
11144
  //#endregion
10578
- export { runningVersion as $, trackMcpResearch as A, defaultStateDir as At, generateProjectMap as B, seoPostToolUse as C, trimLogFile as Ct, securityAdvisoryForPatch as D, claudeMdKey as Dt, securityAdvisory as E, projectContext as Et, dispatchAipilot as F, loadSecurityState as Ft, countFiles as G, writeTree as H, dispatchLessons as I, saveSecurityState as It, lessonsArchiveFileFor as J, getFileDesc as K, cartoSessionStart as L, securityStateDir as Lt, trackEnrichment as M, trackFile as Mt, dispatchLifecycle as N, normalizeEvent as Nt, postTrackingSideEffects as O, promptSubmitContext as Ot, aipilotPostToolUse as P, isoUtc as Pt, runDoctor as Q, generateEcosystemMap as R, securityStatePath as Rt, postEditContext as S, removeOldFiles as St, dispatchMemory as T, gitContext as Tt, loadEnriched as U, isProject as V, mergeLines as W, lessonsStateFileFor as X, lessonsFileFor as Y, notify as Z, preCommitGate as _, readRules as _t, recordActivity as a, saveApexState as at, extractSymbols as b, pruneEmptyDirs as bt, MCP_TTL_MS as c, trackAgentMemory as ct, isMcpTool as d, validateSolidGate as dt, versionBanner as et, queryOf as f, checkFileSize as ft, gate as g, injectRules as gt, TRIVIAL_BUDGET as h, solidDetectStart as ht, respond as i, cleanupSession as it, trackSkillRead as j, projectHash$1 as jt, trackWatchResearch as k, taskContext as kt, WEBFETCH_TTL_MS as l, subagentCacheContext as lt, REQUIRED_AGENTS as m, detectSolidProfile as mt, activityFor as n, trackSessionChanges as nt, mcpPostStore as o, logToolFailure as ot, DEFAULT_WINDOW_MS as p, countLoc as pt, listChildren as q, handlePre as r, validateRulesLoaded as rt, mcpPreIntercept as s, validateTeammateOutput as st, handleHook as t, postEditTypescript as tt, cacheQueryOf as u, validateTailwind as ut, detectDuplication as v, runSessionStartCleanups as vt, seoPostToolUseResponse as w, devContext as wt, lifecycleStdout as x, purgeTtlTree as xt, dryGate as y, sessionStartCore as yt, writePluginMap as z, todayUtc as zt };
11145
+ export { runDoctor as $, trackWatchResearch as A, taskContext as At, writePluginMap as B, todayUtc as Bt, postEditContext as C, removeOldFiles as Ct, securityAdvisory as D, projectContext as Dt, dispatchMemory as E, gitContext as Et, aipilotPostToolUse as F, isoUtc as Ft, mergeLines as G, isProject as H, dispatchAipilot as I, loadSecurityState as It, listChildren as J, countFiles as K, dispatchLessons as L, saveSecurityState as Lt, trackSkillRead as M, projectHash$1 as Mt, trackEnrichment as N, trackFile as Nt, securityAdvisoryForPatch as O, claudeMdKey as Ot, dispatchLifecycle as P, normalizeEvent as Pt, notify as Q, cartoSessionStart as R, securityStateDir as Rt, lifecycleStdout as S, purgeTtlTree as St, seoPostToolUseResponse as T, devContext as Tt, writeTree as U, generateProjectMap as V, loadEnriched as W, lessonsFileFor as X, lessonsArchiveFileFor as Y, lessonsStateFileFor as Z, gateCommandCandidates as _, injectRules as _t, recordActivity as a, cleanupSession as at, dryGate as b, sessionStartCore as bt, MCP_TTL_MS as c, validateTeammateOutput as ct, isMcpTool as d, validateTailwind as dt, runningVersion as et, queryOf as f, validateSolidGate as ft, gate as g, solidDetectStart as gt, TRIVIAL_BUDGET as h, detectSolidProfile as ht, respond as i, validateRulesLoaded as it, trackMcpResearch as j, defaultStateDir as jt, postTrackingSideEffects as k, promptSubmitContext as kt, WEBFETCH_TTL_MS as l, trackAgentMemory as lt, REQUIRED_AGENTS as m, countLoc as mt, activityFor as n, postEditTypescript as nt, mcpPostStore as o, saveApexState as ot, DEFAULT_WINDOW_MS as p, checkFileSize as pt, getFileDesc as q, handlePre as r, trackSessionChanges as rt, mcpPreIntercept as s, logToolFailure as st, handleHook as t, versionBanner as tt, cacheQueryOf as u, subagentCacheContext as ut, preCommitGate as v, readRules as vt, seoPostToolUse as w, trimLogFile as wt, extractSymbols as x, pruneEmptyDirs as xt, detectDuplication as y, runSessionStartCleanups as yt, generateEcosystemMap as z, securityStatePath as zt };