@fusengine/harness 0.1.89 → 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 (419) 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 +5 -4
  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-DHhukRdh.mjs → handle-C43gA-Pr.mjs} +737 -150
  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/permission-mode-B-SFyR0X.mjs +226 -0
  29. package/dist/policy/index.d.mts +1 -1
  30. package/dist/policy/index.mjs +2 -2
  31. package/dist/prompt/index.d.mts +1 -1
  32. package/dist/{run-CdIlUtjv.mjs → run-DkrzC0gb.mjs} +1 -1
  33. package/dist/{run-TAXNRSpD.mjs → run-GHAMqcOn.mjs} +26 -3
  34. package/dist/runtime/index.d.mts +27 -16
  35. package/dist/runtime/index.mjs +2 -2
  36. package/dist/{types-C0-igRKr.d.mts → types-Bh0jEF3_.d.mts} +2 -0
  37. package/dist/{validate-DZFtAbLP.mjs → validate-KjZ1X9tH.mjs} +1 -1
  38. package/package.json +3 -2
  39. package/src/adapters/claude/index.ts +92 -0
  40. package/src/adapters/cline/index.ts +36 -0
  41. package/src/adapters/codex/apply-patch.ts +94 -0
  42. package/src/adapters/codex/index.ts +84 -0
  43. package/src/adapters/codex/permission-mode.ts +16 -0
  44. package/src/adapters/cursor/context.ts +46 -0
  45. package/src/adapters/cursor/events.ts +38 -0
  46. package/src/adapters/cursor/index.ts +43 -0
  47. package/src/adapters/cursor/interfaces/types.ts +88 -0
  48. package/src/adapters/cursor/native-response.ts +158 -0
  49. package/src/adapters/cursor/normalize.ts +123 -0
  50. package/src/adapters/cursor/respond.ts +123 -0
  51. package/src/adapters/gemini/index.ts +36 -0
  52. package/src/adapters/hermes/index.ts +55 -0
  53. package/src/adapters/hermes/interfaces/types.ts +26 -0
  54. package/src/adapters/kimi/index.ts +96 -0
  55. package/src/adapters/kimi/interfaces/types.ts +55 -0
  56. package/src/cache/compact.ts +55 -0
  57. package/src/cache/index.ts +5 -0
  58. package/src/cache/io.ts +38 -0
  59. package/src/cache/mcp-response.ts +39 -0
  60. package/src/cache/mcp-store.ts +70 -0
  61. package/src/cache/store.ts +93 -0
  62. package/src/changelog/fetch.ts +79 -0
  63. package/src/cli/bin.ts +100 -0
  64. package/src/cli/cursor-event-scanner.ts +175 -0
  65. package/src/cli/cursor-stdin-reader.ts +65 -0
  66. package/src/cli/doctor.ts +96 -0
  67. package/src/cli/hook-io.ts +130 -0
  68. package/src/cli/hook-sound.ts +49 -0
  69. package/src/cli/index.ts +1 -0
  70. package/src/cli/json-primitive-scanner.ts +83 -0
  71. package/src/cli/run.ts +36 -0
  72. package/src/cli/scope.ts +31 -0
  73. package/src/codex-rules/build.ts +63 -0
  74. package/src/codex-rules/index.ts +8 -0
  75. package/src/codex-rules/mapping.ts +42 -0
  76. package/src/codex-rules/rules/absolute-paths.ts +52 -0
  77. package/src/codex-rules/rules/ask.ts +44 -0
  78. package/src/codex-rules/rules/chmod-ask.ts +57 -0
  79. package/src/codex-rules/rules/dd.ts +23 -0
  80. package/src/codex-rules/rules/diskutil.ts +33 -0
  81. package/src/codex-rules/rules/mkfs.ts +42 -0
  82. package/src/codex-rules/rules/privilege.ts +49 -0
  83. package/src/codex-rules/rules/remote-fetch.ts +31 -0
  84. package/src/codex-rules/rules/rm-variants.ts +58 -0
  85. package/src/codex-rules/rules/single-commands.ts +57 -0
  86. package/src/codex-rules/skip-list.ts +25 -0
  87. package/src/codex-rules/starlark.ts +46 -0
  88. package/src/codex-rules/types.ts +36 -0
  89. package/src/config/dotenv.ts +55 -0
  90. package/src/config/env.ts +10 -0
  91. package/src/config/home-dir.ts +36 -0
  92. package/src/config/index.ts +5 -0
  93. package/src/config/layout.ts +52 -0
  94. package/src/config/limits.ts +56 -0
  95. package/src/config/ttl.ts +30 -0
  96. package/src/detect/harness.ts +73 -0
  97. package/src/detect/index.ts +1 -0
  98. package/src/detect/interfaces/types.ts +18 -0
  99. package/src/freshness/agent-evidence-record.ts +95 -0
  100. package/src/freshness/agent-evidence.ts +95 -0
  101. package/src/freshness/codex-spawn-evidence.ts +91 -0
  102. package/src/freshness/doc-helpers.ts +90 -0
  103. package/src/freshness/evidence-harvest-io.ts +72 -0
  104. package/src/freshness/evidence-harvest.ts +61 -0
  105. package/src/freshness/explore-tools.ts +95 -0
  106. package/src/freshness/index.ts +2 -0
  107. package/src/freshness/query-framework.ts +73 -0
  108. package/src/freshness/ref-evidence.ts +49 -0
  109. package/src/freshness/ref-journal.ts +98 -0
  110. package/src/freshness/trivial-edit-counter.ts +30 -0
  111. package/src/index.ts +11 -0
  112. package/src/init/index.ts +2 -0
  113. package/src/init/run.ts +32 -0
  114. package/src/init/templates.ts +75 -0
  115. package/src/memory/gitignore.ts +14 -0
  116. package/src/memory/index.ts +3 -0
  117. package/src/memory/registry.ts +39 -0
  118. package/src/memory/session-roots.ts +99 -0
  119. package/src/memory/state.ts +58 -0
  120. package/src/policy/apex-authorization.ts +100 -0
  121. package/src/policy/apex-gates.ts +91 -0
  122. package/src/policy/apex-target-steps.ts +73 -0
  123. package/src/policy/apex-target.ts +49 -0
  124. package/src/policy/apex-task-context.ts +81 -0
  125. package/src/policy/apex.ts +94 -0
  126. package/src/policy/cartographer/build-tree.ts +69 -0
  127. package/src/policy/cartographer/describe.ts +53 -0
  128. package/src/policy/cartographer/entry.ts +38 -0
  129. package/src/policy/cartographer/frontmatter.ts +48 -0
  130. package/src/policy/cartographer/indicators.ts +34 -0
  131. package/src/policy/claude-md-context.ts +66 -0
  132. package/src/policy/conventions/index.ts +11 -0
  133. package/src/policy/conventions/interfaces.ts +71 -0
  134. package/src/policy/conventions/langs.ts +53 -0
  135. package/src/policy/conventions/query.ts +49 -0
  136. package/src/policy/conventions/react-hooks.ts +18 -0
  137. package/src/policy/conventions/store-or-query.ts +21 -0
  138. package/src/policy/conventions/stores.ts +56 -0
  139. package/src/policy/conventions/strip.ts +87 -0
  140. package/src/policy/conventions/verdict.ts +38 -0
  141. package/src/policy/creation-intent.ts +11 -0
  142. package/src/policy/deny-loop.ts +88 -0
  143. package/src/policy/design/allowed-write.ts +28 -0
  144. package/src/policy/design/content-checks.ts +45 -0
  145. package/src/policy/design/corpus-fs.ts +21 -0
  146. package/src/policy/design/corpus-resolve-cache.ts +49 -0
  147. package/src/policy/design/corpus-resolve.ts +99 -0
  148. package/src/policy/design/corpus.ts +92 -0
  149. package/src/policy/design/design-system-rules.ts +45 -0
  150. package/src/policy/design/flag.ts +30 -0
  151. package/src/policy/design/gates-pipeline.ts +105 -0
  152. package/src/policy/design/gates.ts +87 -0
  153. package/src/policy/design/screenshot-tools.ts +24 -0
  154. package/src/policy/design/skill-evidence.ts +34 -0
  155. package/src/policy/design/skill-gate.ts +81 -0
  156. package/src/policy/design/skill-triggers.ts +51 -0
  157. package/src/policy/design/state.ts +92 -0
  158. package/src/policy/design/template-urls.ts +54 -0
  159. package/src/policy/design/transitions.ts +68 -0
  160. package/src/policy/detect-claude-md-project-type.ts +32 -0
  161. package/src/policy/detect-framework.ts +99 -0
  162. package/src/policy/detect-primitive-checks.ts +100 -0
  163. package/src/policy/detect-primitive-lib.ts +55 -0
  164. package/src/policy/detect-project.ts +99 -0
  165. package/src/policy/edit-outcome.ts +53 -0
  166. package/src/policy/evaluate.ts +81 -0
  167. package/src/policy/expert-agents.ts +60 -0
  168. package/src/policy/file-size-scope.ts +32 -0
  169. package/src/policy/file-size.ts +99 -0
  170. package/src/policy/framework-solid-exclude.ts +52 -0
  171. package/src/policy/framework-solid-extended.ts +93 -0
  172. package/src/policy/framework-solid-gates-systems.ts +99 -0
  173. package/src/policy/framework-solid-gates.ts +98 -0
  174. package/src/policy/framework-solid.ts +88 -0
  175. package/src/policy/gemini-mcp-gate.ts +83 -0
  176. package/src/policy/git-gates.ts +40 -0
  177. package/src/policy/guards/bash-command-anchor.ts +40 -0
  178. package/src/policy/guards/bash-write-patterns.ts +140 -0
  179. package/src/policy/guards/bash-write-redirects.ts +201 -0
  180. package/src/policy/guards/bash-write-safe-paths.ts +62 -0
  181. package/src/policy/guards/bash-write.ts +76 -0
  182. package/src/policy/guards/context.ts +12 -0
  183. package/src/policy/guards/index.ts +61 -0
  184. package/src/policy/guards/install.ts +22 -0
  185. package/src/policy/guards/interface-separation-ext.ts +41 -0
  186. package/src/policy/guards/interface-separation.ts +100 -0
  187. package/src/policy/guards/protected-path.ts +100 -0
  188. package/src/policy/guards/security.ts +97 -0
  189. package/src/policy/index.ts +19 -0
  190. package/src/policy/interfaces/types.ts +46 -0
  191. package/src/policy/js-gate-route.ts +51 -0
  192. package/src/policy/lessons/lesson-gate.ts +0 -0
  193. package/src/policy/lessons/trigger-index.ts +84 -0
  194. package/src/policy/lessons/types.ts +31 -0
  195. package/src/policy/module-layout.ts +31 -0
  196. package/src/policy/nearest-manifest.ts +75 -0
  197. package/src/policy/never-approval.ts +95 -0
  198. package/src/policy/patterns.ts +80 -0
  199. package/src/policy/seo/validate.ts +51 -0
  200. package/src/policy/shadcn-project.ts +22 -0
  201. package/src/policy/shadcn-skill-gate.ts +97 -0
  202. package/src/policy/shell-read-refs.ts +96 -0
  203. package/src/policy/skill-path.ts +46 -0
  204. package/src/policy/skill-patterns/laravel.ts +40 -0
  205. package/src/policy/skill-patterns/nextjs.ts +29 -0
  206. package/src/policy/skill-patterns/react.ts +27 -0
  207. package/src/policy/skill-patterns/shadcn-subskills.ts +33 -0
  208. package/src/policy/skill-patterns/shadcn.ts +66 -0
  209. package/src/policy/skill-patterns/swift.ts +30 -0
  210. package/src/policy/skill-patterns/tailwind.ts +40 -0
  211. package/src/policy/skill-source.ts +52 -0
  212. package/src/policy/skill-trigger-patterns.ts +33 -0
  213. package/src/policy/skill-triggers.ts +108 -0
  214. package/src/policy/tailwind-skill-gate.ts +63 -0
  215. package/src/policy/trivial-edits.ts +52 -0
  216. package/src/policy/verbosity.ts +39 -0
  217. package/src/prompt/index.ts +1 -0
  218. package/src/prompt/types.ts +36 -0
  219. package/src/refs/discover.ts +69 -0
  220. package/src/refs/frontmatter.ts +24 -0
  221. package/src/refs/index.ts +4 -0
  222. package/src/refs/loader.ts +59 -0
  223. package/src/refs/ref-key.ts +41 -0
  224. package/src/refs/router.ts +63 -0
  225. package/src/refs/types.ts +25 -0
  226. package/src/runtime/activity.ts +99 -0
  227. package/src/runtime/apply-patch-gate.ts +41 -0
  228. package/src/runtime/burst-window.ts +21 -0
  229. package/src/runtime/codex-shell-tool.ts +37 -0
  230. package/src/runtime/command-string.ts +37 -0
  231. package/src/runtime/confirm/codex-confirm.ts +126 -0
  232. package/src/runtime/confirm/codex-prompt-origin.ts +16 -0
  233. package/src/runtime/confirm/confirm-code.ts +33 -0
  234. package/src/runtime/confirm/confirm-gate.ts +65 -0
  235. package/src/runtime/confirm/confirm-irreversible.ts +32 -0
  236. package/src/runtime/confirm/confirm-pending.ts +34 -0
  237. package/src/runtime/confirm/confirm-state.ts +56 -0
  238. package/src/runtime/confirm/confirm-subagent.ts +112 -0
  239. package/src/runtime/confirm/confirm-submit.ts +38 -0
  240. package/src/runtime/core-guards-active.ts +84 -0
  241. package/src/runtime/core-guards-wired.ts +47 -0
  242. package/src/runtime/deny-loop-store.ts +94 -0
  243. package/src/runtime/deny-notice.ts +52 -0
  244. package/src/runtime/design-cache-resolve.ts +51 -0
  245. package/src/runtime/design-content-gate.ts +95 -0
  246. package/src/runtime/design-files-gate.ts +114 -0
  247. package/src/runtime/design-helpers.ts +93 -0
  248. package/src/runtime/design-lifecycle.ts +39 -0
  249. package/src/runtime/design-read-credit.ts +28 -0
  250. package/src/runtime/design.ts +116 -0
  251. package/src/runtime/dev-context.ts +43 -0
  252. package/src/runtime/dry-patterns.ts +32 -0
  253. package/src/runtime/dry.ts +96 -0
  254. package/src/runtime/fragment-registry.ts +33 -0
  255. package/src/runtime/framework-skill-gate.ts +79 -0
  256. package/src/runtime/fs-cleanup.ts +71 -0
  257. package/src/runtime/gate-apex.ts +81 -0
  258. package/src/runtime/gate-helpers.ts +52 -0
  259. package/src/runtime/gate-input.ts +26 -0
  260. package/src/runtime/gate.ts +128 -0
  261. package/src/runtime/handle-post.ts +104 -0
  262. package/src/runtime/handle-pre.ts +137 -0
  263. package/src/runtime/handle-scope-async.ts +29 -0
  264. package/src/runtime/handle-types.ts +27 -0
  265. package/src/runtime/handle.ts +116 -0
  266. package/src/runtime/home-state.ts +55 -0
  267. package/src/runtime/index.ts +18 -0
  268. package/src/runtime/inform.ts +28 -0
  269. package/src/runtime/inject-budget-recap.ts +47 -0
  270. package/src/runtime/inject-budget.ts +81 -0
  271. package/src/runtime/inject-context.ts +85 -0
  272. package/src/runtime/inject-dedup-exclusive.ts +98 -0
  273. package/src/runtime/inject-dedup.ts +89 -0
  274. package/src/runtime/is-agent-tool.ts +19 -0
  275. package/src/runtime/lifecycle/agent-files.ts +49 -0
  276. package/src/runtime/lifecycle/agent-memory.ts +82 -0
  277. package/src/runtime/lifecycle/agent-transcript.ts +80 -0
  278. package/src/runtime/lifecycle/aipilot/analytics.ts +99 -0
  279. package/src/runtime/lifecycle/aipilot/apex-task-store.ts +61 -0
  280. package/src/runtime/lifecycle/aipilot/cache-base.ts +48 -0
  281. package/src/runtime/lifecycle/aipilot/cache-doc.ts +100 -0
  282. package/src/runtime/lifecycle/aipilot/cache-lessons.ts +58 -0
  283. package/src/runtime/lifecycle/aipilot/cache-test.ts +70 -0
  284. package/src/runtime/lifecycle/aipilot/curate-lessons.ts +64 -0
  285. package/src/runtime/lifecycle/aipilot/dispatch-aipilot.ts +100 -0
  286. package/src/runtime/lifecycle/aipilot/doc-cache-gate.ts +69 -0
  287. package/src/runtime/lifecycle/aipilot/inject-apex.ts +98 -0
  288. package/src/runtime/lifecycle/aipilot/inject-doc.ts +62 -0
  289. package/src/runtime/lifecycle/aipilot/inject-explore.ts +64 -0
  290. package/src/runtime/lifecycle/aipilot/inject-lessons.ts +61 -0
  291. package/src/runtime/lifecycle/aipilot/inject-test.ts +48 -0
  292. package/src/runtime/lifecycle/aipilot/lesson-archive.ts +60 -0
  293. package/src/runtime/lifecycle/aipilot/lesson-inject.ts +98 -0
  294. package/src/runtime/lifecycle/aipilot/lesson-parse.ts +74 -0
  295. package/src/runtime/lifecycle/aipilot/lessons.ts +72 -0
  296. package/src/runtime/lifecycle/aipilot/promote-global-lessons.ts +92 -0
  297. package/src/runtime/lifecycle/aipilot/solid-compliance.ts +69 -0
  298. package/src/runtime/lifecycle/aipilot/solid-notice.ts +47 -0
  299. package/src/runtime/lifecycle/aipilot/solid-transcript.ts +60 -0
  300. package/src/runtime/lifecycle/aipilot/source-scan.ts +56 -0
  301. package/src/runtime/lifecycle/aipilot/sync-task.ts +75 -0
  302. package/src/runtime/lifecycle/aipilot/transcript.ts +88 -0
  303. package/src/runtime/lifecycle/aipilot/types.ts +72 -0
  304. package/src/runtime/lifecycle/auto-document-reads.ts +73 -0
  305. package/src/runtime/lifecycle/cartographer/detect.ts +57 -0
  306. package/src/runtime/lifecycle/cartographer/ecosystem-map.ts +56 -0
  307. package/src/runtime/lifecycle/cartographer/fs-util.ts +64 -0
  308. package/src/runtime/lifecycle/cartographer/merge.ts +51 -0
  309. package/src/runtime/lifecycle/cartographer/project-map.ts +49 -0
  310. package/src/runtime/lifecycle/cartographer/scan-hooks.ts +48 -0
  311. package/src/runtime/lifecycle/cartographer/scan.ts +81 -0
  312. package/src/runtime/lifecycle/cartographer/session-start.ts +33 -0
  313. package/src/runtime/lifecycle/cartographer/track-enrichment.ts +45 -0
  314. package/src/runtime/lifecycle/cartographer/write-plugin-map.ts +43 -0
  315. package/src/runtime/lifecycle/cartographer/write-tree.ts +44 -0
  316. package/src/runtime/lifecycle/changelog-research.ts +43 -0
  317. package/src/runtime/lifecycle/check-file-size.ts +61 -0
  318. package/src/runtime/lifecycle/codex-resync/fingerprint.ts +63 -0
  319. package/src/runtime/lifecycle/codex-resync/lock.ts +46 -0
  320. package/src/runtime/lifecycle/codex-resync/managed-destination.ts +39 -0
  321. package/src/runtime/lifecycle/codex-resync/materialize-agents.ts +57 -0
  322. package/src/runtime/lifecycle/codex-resync/plugin-files.ts +65 -0
  323. package/src/runtime/lifecycle/codex-resync/plugin-roots.ts +62 -0
  324. package/src/runtime/lifecycle/codex-resync/resync.ts +45 -0
  325. package/src/runtime/lifecycle/codex-resync/symlink-commands.ts +25 -0
  326. package/src/runtime/lifecycle/dispatch.ts +109 -0
  327. package/src/runtime/lifecycle/failure-lesson.ts +51 -0
  328. package/src/runtime/lifecycle/index.ts +34 -0
  329. package/src/runtime/lifecycle/inject-rules.ts +50 -0
  330. package/src/runtime/lifecycle/instructions-loaded.ts +22 -0
  331. package/src/runtime/lifecycle/kimi-rules-native.ts +44 -0
  332. package/src/runtime/lifecycle/lessons/dispatch.ts +88 -0
  333. package/src/runtime/lifecycle/lessons/reminder.ts +89 -0
  334. package/src/runtime/lifecycle/lessons/state.ts +23 -0
  335. package/src/runtime/lifecycle/memory/agent-lesson.ts +35 -0
  336. package/src/runtime/lifecycle/memory/capture-error.ts +41 -0
  337. package/src/runtime/lifecycle/memory/client.ts +65 -0
  338. package/src/runtime/lifecycle/memory/dispatch.ts +42 -0
  339. package/src/runtime/lifecycle/memory/recall.ts +30 -0
  340. package/src/runtime/lifecycle/memory/salience.ts +31 -0
  341. package/src/runtime/lifecycle/memory/state.ts +49 -0
  342. package/src/runtime/lifecycle/memory/track-ops.ts +15 -0
  343. package/src/runtime/lifecycle/post-compact.ts +38 -0
  344. package/src/runtime/lifecycle/post-edit-ts.ts +49 -0
  345. package/src/runtime/lifecycle/post-tracking.ts +40 -0
  346. package/src/runtime/lifecycle/pre-compact.ts +38 -0
  347. package/src/runtime/lifecycle/rules-root.ts +83 -0
  348. package/src/runtime/lifecycle/security/check-skill.ts +63 -0
  349. package/src/runtime/lifecycle/security/scan-patterns.ts +53 -0
  350. package/src/runtime/lifecycle/security/scan.ts +76 -0
  351. package/src/runtime/lifecycle/security/skill-state.ts +47 -0
  352. package/src/runtime/lifecycle/security/track-mcp.ts +29 -0
  353. package/src/runtime/lifecycle/security/track-skill-read.ts +31 -0
  354. package/src/runtime/lifecycle/seo/post-tool-use.ts +45 -0
  355. package/src/runtime/lifecycle/session-end.ts +17 -0
  356. package/src/runtime/lifecycle/session-start.ts +57 -0
  357. package/src/runtime/lifecycle/snapshot/board.ts +27 -0
  358. package/src/runtime/lifecycle/snapshot/format.ts +60 -0
  359. package/src/runtime/lifecycle/snapshot/git.ts +71 -0
  360. package/src/runtime/lifecycle/snapshot/index.ts +52 -0
  361. package/src/runtime/lifecycle/snapshot/version.ts +34 -0
  362. package/src/runtime/lifecycle/solid-detect.ts +56 -0
  363. package/src/runtime/lifecycle/stop-core.ts +35 -0
  364. package/src/runtime/lifecycle/subagent-cache.ts +61 -0
  365. package/src/runtime/lifecycle/task-completed.ts +94 -0
  366. package/src/runtime/lifecycle/teammate-idle-check.ts +63 -0
  367. package/src/runtime/lifecycle/teammate-idle.ts +28 -0
  368. package/src/runtime/lifecycle/tool-failure.ts +24 -0
  369. package/src/runtime/lifecycle/track-changes.ts +61 -0
  370. package/src/runtime/lifecycle/validate-solid.ts +70 -0
  371. package/src/runtime/lifecycle/validate-tailwind.ts +80 -0
  372. package/src/runtime/lifecycle-bridge.ts +74 -0
  373. package/src/runtime/mcp-key.ts +44 -0
  374. package/src/runtime/mcp-tool-name.ts +92 -0
  375. package/src/runtime/mcp.ts +91 -0
  376. package/src/runtime/modular.ts +77 -0
  377. package/src/runtime/normalize.ts +107 -0
  378. package/src/runtime/notices.ts +91 -0
  379. package/src/runtime/notification-sound.ts +49 -0
  380. package/src/runtime/notifications.ts +77 -0
  381. package/src/runtime/paths.ts +59 -0
  382. package/src/runtime/post-fanout.ts +41 -0
  383. package/src/runtime/post-outcome.ts +61 -0
  384. package/src/runtime/pre-allow.ts +97 -0
  385. package/src/runtime/precommit.ts +60 -0
  386. package/src/runtime/prompt-text.ts +38 -0
  387. package/src/runtime/receipt-capture.ts +34 -0
  388. package/src/runtime/record.ts +26 -0
  389. package/src/runtime/respond.ts +92 -0
  390. package/src/runtime/solid-file-size-gate.ts +58 -0
  391. package/src/runtime/solid-pre.ts +53 -0
  392. package/src/runtime/storage.ts +9 -0
  393. package/src/statusline/colors.ts +31 -0
  394. package/src/statusline/constants.ts +17 -0
  395. package/src/statusline/formatters.ts +54 -0
  396. package/src/statusline/index.ts +4 -0
  397. package/src/statusline/progress-bar.ts +46 -0
  398. package/src/tracking/codex-post-failure.ts +73 -0
  399. package/src/tracking/index.ts +2 -0
  400. package/src/tracking/integrity.ts +99 -0
  401. package/src/tracking/one-shot-dedup.ts +34 -0
  402. package/src/tracking/one-shot-failure.ts +42 -0
  403. package/src/tracking/one-shot-store.ts +95 -0
  404. package/src/tracking/one-shot.ts +93 -0
  405. package/src/tracking/receipts.ts +95 -0
  406. package/src/tracking/session-state.ts +94 -0
  407. package/src/tracking/store.ts +96 -0
  408. package/src/tracking/track-compact.ts +100 -0
  409. package/src/tracking/track-diff.ts +50 -0
  410. package/src/tracking/track-journal.ts +97 -0
  411. package/src/tracking/track-lock-sync.ts +83 -0
  412. package/src/tracking/track-lock.ts +67 -0
  413. package/src/util/compact-json.ts +23 -0
  414. package/src/util/index.ts +2 -0
  415. package/src/util/json-io.ts +47 -0
  416. package/src/util/project-root.ts +41 -0
  417. package/src/util/runtime-io.ts +85 -0
  418. package/src/util/semver.ts +30 -0
  419. package/dist/permission-mode-B0MUl7FT.mjs +0 -136
@@ -3,21 +3,23 @@ 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";
14
14
  import { d as loadIndex, i as cacheLookupMeta, n as webfetchCacheWrite, o as cacheLookupSubstringMeta, t as mcpCacheWrite, u as extractText } from "./mcp-store-BkBDmuxN.mjs";
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
- import { n as canonicalizeCodexShellTool, r as parseApplyPatch, t as isBypassPermissions } from "./permission-mode-B0MUl7FT.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";
17
+ import { i as parseApplyPatch, n as canonicalizeMcpToolName, r as canonicalizeCodexShellTool, t as isBypassPermissions } from "./permission-mode-B-SFyR0X.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,16 +88,24 @@ 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
- const tool = canonicalizeCodexShellTool(id, str(payload.tool_name) ?? "");
99
+ const tool = canonicalizeCodexShellTool(id, canonicalizeMcpToolName(id, str(payload.tool_name) ?? ""));
94
100
  const base = {
95
101
  phase: /post|after/i.test(event) ? "post" : "pre",
96
102
  tool,
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
  }
@@ -5326,8 +5334,13 @@ const RETRY_DELAYS = [
5326
5334
  1e3,
5327
5335
  2e3
5328
5336
  ];
5329
- /** Extract the longest assistant synthesis + queried library ids from a transcript. */
5330
- async function extractSynthesis(path) {
5337
+ /**
5338
+ * Extract the longest assistant synthesis + queried library ids from a transcript.
5339
+ * @param id - Harness adapter id, used to canonicalize `block.name` (Codex-mangled
5340
+ * underscore tool names) before the {@link TOOL_PATTERN} match — see
5341
+ * {@link cacheDocFromTranscript} for why this is a defensive, unconfirmed-case cover.
5342
+ */
5343
+ async function extractSynthesis(path, id) {
5331
5344
  const lines = readText$1(path).split("\n").filter(Boolean);
5332
5345
  const libraries = [];
5333
5346
  let synthesis = "";
@@ -5337,7 +5350,7 @@ async function extractSynthesis(path) {
5337
5350
  const contents = entry?.message?.content;
5338
5351
  if (!Array.isArray(contents)) continue;
5339
5352
  for (const block of contents) {
5340
- if (block.type === "tool_use" && TOOL_PATTERN.test(block.name ?? "")) {
5353
+ if (block.type === "tool_use" && TOOL_PATTERN.test(canonicalizeMcpToolName(id, block.name ?? ""))) {
5341
5354
  const lib = block.input?.libraryId ?? block.input?.query ?? "";
5342
5355
  if (lib && !libraries.includes(lib)) libraries.push(lib);
5343
5356
  }
@@ -5354,17 +5367,28 @@ async function extractSynthesis(path) {
5354
5367
  * @param transcript - Path to the agent JSONL transcript.
5355
5368
  * @param cwd - Fallback project root.
5356
5369
  * @param home - Home dir (defaults to `~`).
5357
- */
5358
- async function cacheDocFromTranscript(transcript, cwd, home = homedir()) {
5370
+ * @param id - Harness adapter id (defaults to "claude-code"). On `"codex"`,
5371
+ * every `tool_use.name` read from the transcript is canonicalized before the
5372
+ * {@link TOOL_PATTERN} match, defensively covering the SAME Codex-mangled
5373
+ * underscore form as `doc-cache-gate.ts`'s `docCacheGate`. UNLIKE that gate,
5374
+ * this is NOT a confirmed-reproduced bug: the proof that Codex emits the
5375
+ * underscore form (`sanitize_responses_api_tool_name()`, openai/codex#14605,
5376
+ * `SCENARIO_DASH_TOOL` in #18385) is about the live PreToolUse HOOK payload,
5377
+ * never observed here against transcript `tool_use.name` content. The
5378
+ * canonicalization is a no-op when the transcript already carries the dash
5379
+ * form (idempotence proven in `test/mcp-tool-name.test.ts`, test 2), so this
5380
+ * costs nothing if the hypothesis turns out false.
5381
+ */
5382
+ async function cacheDocFromTranscript(transcript, cwd, home = homedir(), id = "claude-code") {
5359
5383
  if (!transcript || !pathExists(transcript)) return;
5360
5384
  const projPath = projectRootFromPaths(await transcriptFilePaths(transcript)) ?? process.env.CLAUDE_PROJECT_DIR ?? cwd;
5361
5385
  const cacheDir = cacheDirFor("doc", projPath, home);
5362
5386
  const docsDir = join(cacheDir, "docs");
5363
- let result = await extractSynthesis(transcript);
5387
+ let result = await extractSynthesis(transcript, id);
5364
5388
  for (const delay of RETRY_DELAYS) {
5365
5389
  if (result.text.length >= MIN_TEXT_SIZE && result.libraries.length > 0) break;
5366
5390
  await sleep(delay);
5367
- result = await extractSynthesis(transcript);
5391
+ result = await extractSynthesis(transcript, id);
5368
5392
  }
5369
5393
  const { text, libraries } = result;
5370
5394
  if (text.length < MIN_TEXT_SIZE || libraries.length === 0) return;
@@ -5762,10 +5786,14 @@ function libraryOf(payload) {
5762
5786
  * @param cwd - Fallback project root (uses `CLAUDE_PROJECT_DIR` first).
5763
5787
  * @param now - Clock (defaults to `Date.now()`).
5764
5788
  * @param home - Home dir (defaults to `~`).
5789
+ * @param id - Harness adapter id (defaults to "claude-code"); on `"codex"`,
5790
+ * `payload.tool_name` is canonicalized before the {@link GATED_TOOLS} match
5791
+ * so the Codex-mangled underscore form (`mcp__context7__query_docs`) gates
5792
+ * exactly like the dash form does on every other harness.
5765
5793
  * @returns A native deny response, or `null` to fall through to the live call.
5766
5794
  */
5767
- async function docCacheGate(payload, cwd, now = Date.now(), home = homedir()) {
5768
- const tool = String(payload.tool_name ?? "");
5795
+ async function docCacheGate(payload, cwd, now = Date.now(), home = homedir(), id = "claude-code") {
5796
+ const tool = canonicalizeMcpToolName(id, String(payload.tool_name ?? ""));
5769
5797
  if (!GATED_TOOLS.test(tool)) return null;
5770
5798
  const library = libraryOf(payload);
5771
5799
  if (!library) return null;
@@ -5981,11 +6009,11 @@ async function onSubagentStart(payload, cwd, now, home, id) {
5981
6009
  const agent = agentTypeOf(payload);
5982
6010
  return combineContext(await injectApexSubagentContext(cwd, home, id), await injectLessonsCache(cwd, home, now), await typeSpecificCache(agent, cwd, now, home));
5983
6011
  }
5984
- /** SubagentStop routing: transcript-driven cache writers, then the universal SOLID check. */
5985
- async function onSubagentStop(payload, cwd, home) {
6012
+ /** SubagentStop routing: transcript-driven cache writers, then the universal SOLID check. `id` selects the harness target (defaults to "claude-code"), threaded into `cacheDocFromTranscript`. */
6013
+ async function onSubagentStop(payload, cwd, home, id) {
5986
6014
  const agent = agentTypeOf(payload);
5987
6015
  const transcript = transcriptOf(payload);
5988
- if (agent.includes("research-expert")) await cacheDocFromTranscript(transcript, cwd, home);
6016
+ if (agent.includes("research-expert")) await cacheDocFromTranscript(transcript, cwd, home, id);
5989
6017
  if (agent.includes("sniper")) {
5990
6018
  await cacheSniperLessons(transcript, cwd, home);
5991
6019
  await cacheTestResults(transcript, cwd, home);
@@ -5999,12 +6027,12 @@ async function onSubagentStop(payload, cwd, home) {
5999
6027
  */
6000
6028
  async function dispatchAipilot(event, payload, cwd, now, home = homedir(), id = "claude-code") {
6001
6029
  if (event === "SubagentStart") return onSubagentStart(payload, cwd, now, home, id);
6002
- if (event === "SubagentStop") return onSubagentStop(payload, cwd, home);
6030
+ if (event === "SubagentStop") return onSubagentStop(payload, cwd, home, id);
6003
6031
  if (event === "SessionEnd" || event === "Stop") {
6004
6032
  await cacheAnalyticsSave(home, now);
6005
6033
  return "";
6006
6034
  }
6007
- if (event === "PreToolUse") return docCacheGate(payload, cwd, now, home);
6035
+ if (event === "PreToolUse") return docCacheGate(payload, cwd, now, home, id);
6008
6036
  return null;
6009
6037
  }
6010
6038
  /** PostToolUse (Write/Edit SOLID check, else TaskCreate/TaskUpdate sync) for the ai-pilot scope; `id` selects the harness target (defaults to "claude-code"). */
@@ -6626,13 +6654,281 @@ async function autoDocumentRead(filePath, now = Date.now(), id = "claude-code")
6626
6654
  return systemMessage(`📖 [${docType}] ${fname} logged`);
6627
6655
  }
6628
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
6629
6925
  //#region src/runtime/lifecycle-bridge.ts
6630
6926
  /** Raw event name from a payload (Cline lacks one; lifecycle is Claude-only). */
6631
6927
  function rawEvent(payload) {
6632
6928
  return typeof payload.hook_event_name === "string" ? payload.hook_event_name : "";
6633
6929
  }
6634
6930
  /** Extract the `additionalContext` body from a `contextResponse(...)` stdout string ("" when empty/unparseable). */
6635
- function additionalContextOf(stdout) {
6931
+ function additionalContextOf$1(stdout) {
6636
6932
  if (!stdout) return "";
6637
6933
  try {
6638
6934
  return JSON.parse(stdout).hookSpecificOutput?.additionalContext ?? "";
@@ -6653,14 +6949,30 @@ function additionalContextOf(stdout) {
6653
6949
  * @returns The native stdout, or `null` when unhandled.
6654
6950
  */
6655
6951
  function lifecycleStdout(payload, cwd, scope, now, id = "claude-code") {
6656
- return dispatchLifecycle({
6657
- event: rawEvent(payload),
6952
+ const wireEvent = rawEvent(payload);
6953
+ if (id !== "cursor") return dispatchLifecycle({
6954
+ event: wireEvent,
6658
6955
  payload,
6659
6956
  cwd,
6660
6957
  scope,
6661
6958
  now,
6662
6959
  id
6663
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);
6664
6976
  }
6665
6977
  /**
6666
6978
  * Post-edit additions for core-scope PostToolUse: auto-document a Read of a
@@ -6682,7 +6994,7 @@ async function postEditContext(scope, event, now, id = "claude-code") {
6682
6994
  const sniper = trackSessionChanges(event.sessionId, event.filePath, void 0, now);
6683
6995
  const lint = postEditTypescript(event.filePath);
6684
6996
  if (!sniper || !lint) return sniper || lint;
6685
- return contextResponse("PostToolUse", `${additionalContextOf(sniper)}\n\n${additionalContextOf(lint)}`);
6997
+ return contextResponse("PostToolUse", `${additionalContextOf$1(sniper)}\n\n${additionalContextOf$1(lint)}`);
6686
6998
  }
6687
6999
  //#endregion
6688
7000
  //#region src/policy/trivial-edits.ts
@@ -7674,11 +7986,37 @@ const TRIVIAL_BUDGET = 4;
7674
7986
  * {@link withDenyLoop} rewrite an identical retried deny (decision unchanged).
7675
7987
  */
7676
7988
  async function gate(input) {
7677
- 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) {
7678
8016
  const op = {
7679
8017
  filePath: input.filePath,
7680
8018
  content: input.content,
7681
- command: input.command
8019
+ command
7682
8020
  };
7683
8021
  const dir = dirname(input.trackFile);
7684
8022
  recordOneShot(prompt, op, {
@@ -7693,6 +8031,10 @@ async function gate(input) {
7693
8031
  sessionId: input.sessionId
7694
8032
  });
7695
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
+ }
7696
8038
  /** Stateless guards, then the trivial fast path, then the stateful APEX gates. */
7697
8039
  async function runGates(input) {
7698
8040
  const precommit = preCommitGate(input.tool, input.command, input.cwd);
@@ -7880,8 +8222,8 @@ async function recordActivity(file, activity) {
7880
8222
  * - gemini-cli/cline: their real hook schemas have no interactive "ask"
7881
8223
  * state (deny is the only blocking outcome), so `ask` and `inform` already
7882
8224
  * both resolve to non-blocking context injection — unchanged.
7883
- * - cursor: `ask` keeps its current best-effort `permission:"ask"` shape;
7884
- * 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.
7885
8227
  * - hermes: delegated to the adapter's `toHermesResponse` — `block` ->
7886
8228
  * `{decision:"block",reason}`; `ask`/`inform` degrade to non-blocking
7887
8229
  * `{context}` (Hermes has no interactive "ask" state).
@@ -7935,25 +8277,7 @@ function respond(id, prompt, event = "PreToolUse") {
7935
8277
  systemMessage: userMessage
7936
8278
  });
7937
8279
  return JSON.stringify({ hookSpecificOutput: { additionalContext: message } });
7938
- case "cursor":
7939
- if (kind === "inform") {
7940
- if (userMessage) return JSON.stringify({
7941
- permission: "allow",
7942
- user_message: userMessage,
7943
- ...reason ? { agent_message: message } : {}
7944
- });
7945
- return JSON.stringify({
7946
- permission: "allow",
7947
- user_message: message,
7948
- agent_message: message
7949
- });
7950
- }
7951
- return JSON.stringify({
7952
- permission: kind === "block" ? "deny" : "ask",
7953
- continue: false,
7954
- user_message: message,
7955
- agent_message: message
7956
- });
8280
+ case "cursor": return toCursorResponse(prompt, event === "PreToolUse" ? "preToolUse" : event);
7957
8281
  case "hermes": return toHermesResponse(prompt);
7958
8282
  case "kimi": return toKimiResponse(prompt);
7959
8283
  case "cline":
@@ -8638,8 +8962,8 @@ function segments(command) {
8638
8962
  }
8639
8963
  /** Strip a redirection (`>`, `>>`, `<`, `2>`, `&>`, …) and everything after — its target is WRITTEN, not read. */
8640
8964
  function beforeRedirect(segment) {
8641
- const m = segment.match(/\s(?:\d*>{1,2}|<|&>)\s*\S/);
8642
- return m ? segment.slice(0, m.index) : segment;
8965
+ const first = shellOutputRedirects(segment)[0];
8966
+ return first ? segment.slice(0, first.start) : segment;
8643
8967
  }
8644
8968
  /** Naive shell tokenizer: whitespace-split, stripping one matching layer of quotes per token. */
8645
8969
  function tokenize(segment) {
@@ -9135,7 +9459,7 @@ async function allowOutcome(id, event, payload, mcpDir, cwd, evidence, corpusRoo
9135
9459
  stdout: respond(id, userMessage ? {
9136
9460
  ...lesson,
9137
9461
  userMessage
9138
- } : lesson),
9462
+ } : lesson, event.eventName ?? "PreToolUse"),
9139
9463
  exit: 0
9140
9464
  };
9141
9465
  }
@@ -9150,12 +9474,12 @@ async function allowOutcome(id, event, payload, mcpDir, cwd, evidence, corpusRoo
9150
9474
  title: "APEX freshness",
9151
9475
  reason: "",
9152
9476
  userMessage
9153
- }),
9477
+ }, event.eventName ?? "PreToolUse"),
9154
9478
  exit: 0
9155
9479
  };
9156
9480
  }
9157
9481
  return {
9158
- stdout: notice ? respond(id, notice) : "",
9482
+ stdout: notice ? respond(id, notice, event.eventName ?? "PreToolUse") : "",
9159
9483
  exit: 0
9160
9484
  };
9161
9485
  }
@@ -9296,7 +9620,7 @@ function getPendingDeny(sessionIdRaw, home = homedir()) {
9296
9620
  //#endregion
9297
9621
  //#region src/runtime/confirm/confirm-state.ts
9298
9622
  /** Confirmation freshness window (G2) — 5 minutes, matches the validated prototype. */
9299
- const TTL_MS = 300 * 1e3;
9623
+ const TTL_MS$1 = 300 * 1e3;
9300
9624
  /**
9301
9625
  * Pose a confirmation token for this session's pending action. No-op while
9302
9626
  * {@link isSubagentActive} (G0, confirm-subagent.ts) — the sole gate that
@@ -9336,7 +9660,7 @@ function consumeConfirmToken(sessionIdRaw, hash, now, home = homedir()) {
9336
9660
  if (!sid) return false;
9337
9661
  const tok = loadSessionState(sid, home).confirmToken;
9338
9662
  if (!tok || tok.hash !== hash) return false;
9339
- if (now - tok.ts > TTL_MS) {
9663
+ if (now - tok.ts > TTL_MS$1) {
9340
9664
  dropConfirmToken(sid, home);
9341
9665
  return false;
9342
9666
  }
@@ -9344,6 +9668,141 @@ function consumeConfirmToken(sessionIdRaw, hash, now, home = homedir()) {
9344
9668
  return true;
9345
9669
  }
9346
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
9347
9806
  //#region src/runtime/confirm/confirm-gate.ts
9348
9807
  /**
9349
9808
  * Harnesses where `respond.ts` silently downgrades `kind: "ask"` to a hard
@@ -9367,9 +9826,23 @@ const DEGRADES_ASK_TO_DENY = /* @__PURE__ */ new Set(["codex", "kimi"]);
9367
9826
  * @param now - Epoch ms.
9368
9827
  * @param home - Test-only OS home override.
9369
9828
  */
9370
- function confirmGate(id, prompt, command, sessionId, now, home) {
9829
+ function confirmGate(id, prompt, command, sessionId, now, home, codex) {
9371
9830
  if (prompt.kind !== "ask" || !DEGRADES_ASK_TO_DENY.has(id) || !command || isIrreversible(command)) return null;
9372
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
+ }
9373
9846
  const hash = hashForAction(command);
9374
9847
  if (consumeConfirmToken(sessionId, hash, now, home)) return { allow: true };
9375
9848
  const code = displayCodeForAction(command);
@@ -9410,9 +9883,19 @@ async function handlePre(ctx) {
9410
9883
  }
9411
9884
  const designBlock = designGate(payload, event, designCacheDir, opts.cwd, opts.corpusRoot);
9412
9885
  if (designBlock) return {
9413
- 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),
9414
9887
  exit: 0
9415
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
+ }
9416
9899
  if (opts.scope === "security") return {
9417
9900
  stdout: event.files?.length ? securityAdvisoryForPatch(event.files, opts.now) : securityAdvisory(event.tool, event.filePath ?? "", opts.now),
9418
9901
  exit: 0
@@ -9428,11 +9911,12 @@ async function handlePre(ctx) {
9428
9911
  if (event.files && event.files.length > 0) {
9429
9912
  const patchPrompt = applyPatchGate(event.files, opts.cwd);
9430
9913
  if (patchPrompt) return {
9431
- 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),
9432
9915
  exit: 0
9433
9916
  };
9434
9917
  }
9435
- const prompt = await gate({
9918
+ const refs = opts.refsDir ? await loadRefs(opts.refsDir) : void 0;
9919
+ const gateInput = {
9436
9920
  sessionId: event.sessionId,
9437
9921
  framework,
9438
9922
  tool: event.tool,
@@ -9440,7 +9924,7 @@ async function handlePre(ctx) {
9440
9924
  content: event.content,
9441
9925
  command: event.command,
9442
9926
  cwd: opts.cwd,
9443
- refs: opts.refsDir ? await loadRefs(opts.refsDir) : void 0,
9927
+ refs,
9444
9928
  isReplaceAll: event.input.replace_all === true,
9445
9929
  oldString: event.oldString,
9446
9930
  agentType: event.agentType,
@@ -9450,9 +9934,14 @@ async function handlePre(ctx) {
9450
9934
  trackFile: file,
9451
9935
  transcriptPath: typeof payload.transcript_path === "string" ? payload.transcript_path : void 0,
9452
9936
  neverApproval: id === "codex" && isBypassPermissions(event.permissionMode)
9453
- });
9937
+ };
9938
+ const prompt = id === "cursor" && (event.commandCandidates?.length ?? 0) > 1 ? await gateCommandCandidates(gateInput, event.commandCandidates) : await gate(gateInput);
9454
9939
  if (prompt) {
9455
- 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);
9456
9945
  if (confirm?.allow) return allowOutcome(id, event, payload, designCacheDir, opts.cwd, {
9457
9946
  trackFile: file,
9458
9947
  windowMs: opts.windowMs,
@@ -9460,7 +9949,7 @@ async function handlePre(ctx) {
9460
9949
  }, opts.corpusRoot);
9461
9950
  const finalPrompt = confirm ? confirm.prompt : prompt;
9462
9951
  return {
9463
- 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),
9464
9953
  exit: 0
9465
9954
  };
9466
9955
  }
@@ -9827,14 +10316,14 @@ function recordCodexPostFailure(tool, result, opts) {
9827
10316
  //#endregion
9828
10317
  //#region src/runtime/post-fanout.ts
9829
10318
  /**
9830
- * Fan a Codex `apply_patch` envelope's `event.files` into one synthetic
10319
+ * Fan Codex `apply_patch` or Cursor `afterFileEdit` `event.files` into one
9831
10320
  * per-file event per touched file, so the per-file PostToolUse gates (SOLID
9832
10321
  * size, Tailwind, tracking, post-edit context) see EACH file instead of the
9833
10322
  * whole patch (whose own `filePath`/`content` are always undefined). `add`
9834
10323
  * maps to `Write`, `update` to `Edit` (mirrors the Pre-phase `applyPatchGate`'s
9835
10324
  * tool mapping); `delete`/`move` map to a tool none of those Write|Edit-gated
9836
10325
  * checks recognize, so they no-op on it without special-casing each gate.
9837
- * Returns `[event]` unchanged for every non-`apply_patch` tool/harness.
10326
+ * Returns `[event]` unchanged when no per-file changes are present.
9838
10327
  * @param event - The normalized PostToolUse event.
9839
10328
  * @returns One event per touched file, or the original event.
9840
10329
  */
@@ -9844,7 +10333,8 @@ function fanOutFiles(event) {
9844
10333
  ...event,
9845
10334
  tool: f.op === "add" ? "Write" : f.op === "update" ? "Edit" : "apply_patch:delete",
9846
10335
  filePath: f.filePath,
9847
- content: f.op === "delete" ? void 0 : f.content
10336
+ content: f.op === "delete" ? void 0 : f.content,
10337
+ oldString: f.oldString
9848
10338
  }));
9849
10339
  }
9850
10340
  /**
@@ -9864,7 +10354,106 @@ function firstFileMatch(files, check) {
9864
10354
  return "";
9865
10355
  }
9866
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
9867
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
+ }
9868
10457
  /**
9869
10458
  * Run the PostToolUse pipeline: store the MCP response, emit a design warning,
9870
10459
  * record the activity into the session track, apply per-scope side-effects (SEO
@@ -9887,8 +10476,14 @@ function firstFileMatch(files, check) {
9887
10476
  */
9888
10477
  async function handlePost(ctx) {
9889
10478
  const { id, payload, event, framework, mcpDir, file, opts } = ctx;
10479
+ const cursorAfterFileEdit = isCursorAfterFileEdit(id, payload);
9890
10480
  const designCacheDir = ctx.designCacheDir ?? mcpDir;
9891
- 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
+ }
9892
10487
  mcpPostStore(event.tool, event.input, response, mcpDir);
9893
10488
  const designWarn = designGate(payload, event, designCacheDir, opts.cwd, opts.corpusRoot);
9894
10489
  const activities = activityFor({
@@ -9912,27 +10507,27 @@ async function handlePost(ctx) {
9912
10507
  const files = fanOutFiles(event);
9913
10508
  for (const f of files) postTrackingSideEffects(opts.scope ?? "core", f, f.input, opts.now, payload, opts.cwd);
9914
10509
  const seoDeny = opts.scope === "seo" ? seoPostToolUseResponse(payload) : null;
9915
- if (seoDeny) return {
10510
+ if (seoDeny && !cursorAfterFileEdit) return {
9916
10511
  stdout: seoDeny,
9917
10512
  exit: 0
9918
10513
  };
9919
10514
  if (opts.scope === "solid") {
9920
10515
  const solidWarn = firstFileMatch(files, checkFileSize);
9921
- if (solidWarn) return {
10516
+ if (solidWarn && !cursorAfterFileEdit) return {
9922
10517
  stdout: solidWarn,
9923
10518
  exit: 0
9924
10519
  };
9925
10520
  }
9926
10521
  if (opts.scope === "tailwindcss") {
9927
10522
  const tailwindWarn = firstFileMatch(files, validateTailwind);
9928
- if (tailwindWarn) return {
10523
+ if (tailwindWarn && !cursorAfterFileEdit) return {
9929
10524
  stdout: tailwindWarn,
9930
10525
  exit: 0
9931
10526
  };
9932
10527
  }
9933
10528
  if (opts.scope === "aipilot" && (event.tool === "TaskCreate" || event.tool === "TaskUpdate" || event.tool === "Write" || event.tool === "Edit")) {
9934
10529
  const out = await aipilotPostToolUse(payload, opts.cwd, id);
9935
- if (out) return {
10530
+ if (out && !cursorAfterFileEdit) return {
9936
10531
  stdout: out,
9937
10532
  exit: 0
9938
10533
  };
@@ -9942,63 +10537,20 @@ async function handlePost(ctx) {
9942
10537
  extra = await postEditContext(opts.scope ?? "core", f, opts.now, id);
9943
10538
  if (extra) break;
9944
10539
  }
9945
- const agentId = typeof payload.agent_id === "string" ? payload.agent_id : "";
9946
- const noticeLines = [];
9947
- for (const f of files) {
9948
- const n = designPassNotice({
9949
- agentId,
9950
- tool: f.tool,
9951
- filePath: f.filePath ?? "",
9952
- content: f.content ?? "",
9953
- url: "",
9954
- phase: "post"
9955
- }, designCacheDir);
9956
- if (n?.userMessage) noticeLines.push(n.userMessage);
9957
- }
9958
- const notice = noticeLines.length ? {
9959
- kind: "inform",
9960
- title: "Design pipeline",
9961
- reason: "",
9962
- userMessage: noticeLines.join("\n")
9963
- } : null;
9964
- const refNotice = refCreditNoticeFor(activities, event.sessionId, opts.now, defaultStateDir(opts.cwd));
9965
- const userMessage = [notice?.userMessage, refNotice].filter(Boolean).join("\n") || void 0;
9966
- if (designWarn) return {
9967
- stdout: respond(id, userMessage ? {
9968
- ...designWarn,
9969
- userMessage
9970
- } : designWarn, "PostToolUse"),
9971
- exit: 0
9972
- };
9973
- if (!userMessage) return {
9974
- stdout: extra,
9975
- exit: 0
9976
- };
9977
- const withUserMessage = notice ? {
9978
- ...notice,
9979
- userMessage
9980
- } : {
9981
- kind: "inform",
9982
- title: "Compliance",
9983
- reason: "",
9984
- userMessage
9985
- };
9986
- if (!extra) return {
9987
- stdout: respond(id, withUserMessage, "PostToolUse"),
9988
- exit: 0
9989
- };
9990
- if (id === "claude-code" || id === "codex") return {
9991
- stdout: attachSystemMessage(extra, userMessage),
9992
- exit: 0
9993
- };
9994
- if (id === "kimi") return {
9995
- stdout: respond(id, withUserMessage, "PostToolUse"),
9996
- exit: 0
9997
- };
9998
- return {
9999
- stdout: respond(id, withUserMessage, "PostToolUse") || extra,
10000
- exit: 0
10001
- };
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
+ });
10002
10554
  }
10003
10555
  //#endregion
10004
10556
  //#region src/runtime/handle-scope-async.ts
@@ -10018,9 +10570,12 @@ async function handlePost(ctx) {
10018
10570
  * @returns The native stdout when intercepted, or `null` to fall through.
10019
10571
  */
10020
10572
  async function asyncScopeStdout(scope, event, payload, cwd, now, id = "claude-code") {
10021
- if (scope === "aipilot") return dispatchAipilot(event, payload, cwd, now, void 0, id);
10022
- if (scope === "memory") return dispatchMemory(event, payload, cwd, now);
10023
- 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;
10024
10579
  }
10025
10580
  //#endregion
10026
10581
  //#region src/runtime/design-cache-resolve.ts
@@ -10489,6 +11044,18 @@ function handleConfirmSubmit(sessionId, text, now, home) {
10489
11044
  } catch {}
10490
11045
  }
10491
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
10492
11059
  //#region src/runtime/handle.ts
10493
11060
  /** Raw Claude hook event name from a payload (empty when absent). */
10494
11061
  function rawEventName(payload) {
@@ -10500,8 +11067,18 @@ function rawEventName(payload) {
10500
11067
  * POST event it records the activity into the track. The loop that makes the
10501
11068
  * package behave like the Claude plugin, on any harness.
10502
11069
  */
10503
- async function handleHook(id, payload, opts) {
11070
+ async function handleHookCore(id, payload, opts) {
10504
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));
10505
11082
  resetFragmentRegistry();
10506
11083
  const layout = projectLayout(opts.cwd);
10507
11084
  const file = trackFile(event.sessionId, defaultStateDir(opts.cwd));
@@ -10523,10 +11100,8 @@ async function handleHook(id, payload, opts) {
10523
11100
  stdout: id === "claude-code" ? attachBudgetRecap(life, rawEventName(payload), event.sessionId, opts.cwd, opts.now) : life,
10524
11101
  exit: 0
10525
11102
  };
10526
- const rawPrompt = payload.prompt;
10527
- const userPrompt = typeof rawPrompt === "string" || Array.isArray(rawPrompt) ? promptText(rawPrompt) : void 0;
10528
11103
  if (userPrompt !== void 0) {
10529
- handleConfirmSubmit(event.sessionId, userPrompt, opts.now, opts.home);
11104
+ if (id !== "codex") handleConfirmSubmit(event.sessionId, userPrompt, opts.now, opts.home);
10530
11105
  await withTrack(file, (track) => recordBrainstormRequired(track, detectCreationIntent(userPrompt)));
10531
11106
  return {
10532
11107
  stdout: promptSubmitContext(userPrompt, opts.cwd, id),
@@ -10554,5 +11129,17 @@ async function handleHook(id, payload, opts) {
10554
11129
  opts
10555
11130
  });
10556
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
+ }
10557
11144
  //#endregion
10558
- 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 };