@fusengine/harness 0.1.90 → 0.1.92

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (422) hide show
  1. package/dist/adapters/claude/index.d.mts +1 -1
  2. package/dist/adapters/claude/index.mjs +1 -1
  3. package/dist/adapters/cline/index.mjs +1 -1
  4. package/dist/adapters/codex/index.d.mts +2 -2
  5. package/dist/adapters/codex/index.mjs +3 -2
  6. package/dist/adapters/cursor/index.d.mts +15 -28
  7. package/dist/adapters/cursor/index.mjs +35 -46
  8. package/dist/adapters/gemini/index.mjs +1 -1
  9. package/dist/adapters/hermes/index.d.mts +1 -1
  10. package/dist/adapters/hermes/index.mjs +1 -1
  11. package/dist/adapters/kimi/index.d.mts +1 -1
  12. package/dist/adapters/kimi/index.mjs +1 -1
  13. package/dist/{claude-5bC8TDAz.mjs → claude-Ckv2_TgP.mjs} +3 -43
  14. package/dist/cli/bin.mjs +429 -16
  15. package/dist/cli/index.mjs +1 -1
  16. package/dist/command-string-CALMTnwN.mjs +42 -0
  17. package/dist/{evaluate-C1VSI5pb.mjs → evaluate-CMiIqHeH.mjs} +261 -13
  18. package/dist/{handle-CBaOcFZq.mjs → handle-BF1dZFjY.mjs} +1194 -144
  19. package/dist/{hermes-kP6NUQlG.mjs → hermes-B9-p_3IF.mjs} +3 -2
  20. package/dist/{index-DBgnIZn9.d.mts → index-B8PG82su.d.mts} +1 -1
  21. package/dist/{index-D4C2-IIc.d.mts → index-SmKYkk2N.d.mts} +1 -1
  22. package/dist/index.d.mts +2 -2
  23. package/dist/index.mjs +2 -2
  24. package/dist/init/index.d.mts +1 -1
  25. package/dist/init/index.mjs +1 -1
  26. package/dist/{kimi-D6X7b8wF.mjs → kimi-G2wcSh5-.mjs} +3 -2
  27. package/dist/normalize-Dy8g9Ybl.mjs +397 -0
  28. package/dist/policy/index.d.mts +1 -1
  29. package/dist/policy/index.mjs +2 -2
  30. package/dist/prompt/index.d.mts +1 -1
  31. package/dist/{run-CdIlUtjv.mjs → run-DkrzC0gb.mjs} +1 -1
  32. package/dist/{run-TAXNRSpD.mjs → run-GHAMqcOn.mjs} +26 -3
  33. package/dist/runtime/index.d.mts +37 -16
  34. package/dist/runtime/index.mjs +2 -2
  35. package/dist/{types-C0-igRKr.d.mts → types-Bh0jEF3_.d.mts} +2 -0
  36. package/dist/{validate-DZFtAbLP.mjs → validate-KjZ1X9tH.mjs} +1 -1
  37. package/package.json +3 -2
  38. package/src/adapters/claude/index.ts +92 -0
  39. package/src/adapters/cline/index.ts +36 -0
  40. package/src/adapters/codex/apply-patch.ts +94 -0
  41. package/src/adapters/codex/index.ts +84 -0
  42. package/src/adapters/codex/permission-mode.ts +16 -0
  43. package/src/adapters/cursor/context-budget.ts +144 -0
  44. package/src/adapters/cursor/context-limit.ts +115 -0
  45. package/src/adapters/cursor/context.ts +65 -0
  46. package/src/adapters/cursor/events.ts +38 -0
  47. package/src/adapters/cursor/index.ts +43 -0
  48. package/src/adapters/cursor/interfaces/context-budget.ts +25 -0
  49. package/src/adapters/cursor/interfaces/types.ts +88 -0
  50. package/src/adapters/cursor/native-response.ts +39 -0
  51. package/src/adapters/cursor/native-schemas.ts +161 -0
  52. package/src/adapters/cursor/normalize.ts +188 -0
  53. package/src/adapters/cursor/plugin-root.ts +103 -0
  54. package/src/adapters/cursor/respond.ts +170 -0
  55. package/src/adapters/gemini/index.ts +36 -0
  56. package/src/adapters/hermes/index.ts +55 -0
  57. package/src/adapters/hermes/interfaces/types.ts +26 -0
  58. package/src/adapters/kimi/index.ts +96 -0
  59. package/src/adapters/kimi/interfaces/types.ts +55 -0
  60. package/src/cache/compact.ts +55 -0
  61. package/src/cache/index.ts +5 -0
  62. package/src/cache/io.ts +38 -0
  63. package/src/cache/mcp-response.ts +39 -0
  64. package/src/cache/mcp-store.ts +70 -0
  65. package/src/cache/store.ts +93 -0
  66. package/src/changelog/fetch.ts +79 -0
  67. package/src/cli/bin.ts +100 -0
  68. package/src/cli/cursor-event-scanner.ts +175 -0
  69. package/src/cli/cursor-stdin-reader.ts +65 -0
  70. package/src/cli/doctor.ts +96 -0
  71. package/src/cli/hook-io.ts +130 -0
  72. package/src/cli/hook-sound.ts +49 -0
  73. package/src/cli/index.ts +1 -0
  74. package/src/cli/json-primitive-scanner.ts +83 -0
  75. package/src/cli/run.ts +36 -0
  76. package/src/cli/scope.ts +31 -0
  77. package/src/codex-rules/build.ts +63 -0
  78. package/src/codex-rules/index.ts +8 -0
  79. package/src/codex-rules/mapping.ts +42 -0
  80. package/src/codex-rules/rules/absolute-paths.ts +52 -0
  81. package/src/codex-rules/rules/ask.ts +44 -0
  82. package/src/codex-rules/rules/chmod-ask.ts +57 -0
  83. package/src/codex-rules/rules/dd.ts +23 -0
  84. package/src/codex-rules/rules/diskutil.ts +33 -0
  85. package/src/codex-rules/rules/mkfs.ts +42 -0
  86. package/src/codex-rules/rules/privilege.ts +49 -0
  87. package/src/codex-rules/rules/remote-fetch.ts +31 -0
  88. package/src/codex-rules/rules/rm-variants.ts +58 -0
  89. package/src/codex-rules/rules/single-commands.ts +57 -0
  90. package/src/codex-rules/skip-list.ts +25 -0
  91. package/src/codex-rules/starlark.ts +46 -0
  92. package/src/codex-rules/types.ts +36 -0
  93. package/src/config/dotenv.ts +55 -0
  94. package/src/config/env.ts +10 -0
  95. package/src/config/home-dir.ts +36 -0
  96. package/src/config/index.ts +5 -0
  97. package/src/config/layout.ts +52 -0
  98. package/src/config/limits.ts +56 -0
  99. package/src/config/ttl.ts +30 -0
  100. package/src/detect/harness.ts +73 -0
  101. package/src/detect/index.ts +1 -0
  102. package/src/detect/interfaces/types.ts +18 -0
  103. package/src/freshness/agent-evidence-record.ts +95 -0
  104. package/src/freshness/agent-evidence.ts +95 -0
  105. package/src/freshness/codex-spawn-evidence.ts +91 -0
  106. package/src/freshness/doc-helpers.ts +90 -0
  107. package/src/freshness/evidence-harvest-io.ts +72 -0
  108. package/src/freshness/evidence-harvest.ts +61 -0
  109. package/src/freshness/explore-tools.ts +95 -0
  110. package/src/freshness/index.ts +2 -0
  111. package/src/freshness/query-framework.ts +73 -0
  112. package/src/freshness/ref-evidence.ts +49 -0
  113. package/src/freshness/ref-journal.ts +98 -0
  114. package/src/freshness/trivial-edit-counter.ts +30 -0
  115. package/src/index.ts +11 -0
  116. package/src/init/index.ts +2 -0
  117. package/src/init/run.ts +32 -0
  118. package/src/init/templates.ts +75 -0
  119. package/src/memory/gitignore.ts +14 -0
  120. package/src/memory/index.ts +3 -0
  121. package/src/memory/registry.ts +39 -0
  122. package/src/memory/session-roots.ts +99 -0
  123. package/src/memory/state.ts +58 -0
  124. package/src/policy/apex-authorization.ts +100 -0
  125. package/src/policy/apex-gates.ts +91 -0
  126. package/src/policy/apex-target-steps.ts +73 -0
  127. package/src/policy/apex-target.ts +49 -0
  128. package/src/policy/apex-task-context.ts +81 -0
  129. package/src/policy/apex.ts +94 -0
  130. package/src/policy/cartographer/build-tree.ts +69 -0
  131. package/src/policy/cartographer/describe.ts +53 -0
  132. package/src/policy/cartographer/entry.ts +38 -0
  133. package/src/policy/cartographer/frontmatter.ts +48 -0
  134. package/src/policy/cartographer/indicators.ts +34 -0
  135. package/src/policy/claude-md-context.ts +66 -0
  136. package/src/policy/conventions/index.ts +11 -0
  137. package/src/policy/conventions/interfaces.ts +71 -0
  138. package/src/policy/conventions/langs.ts +53 -0
  139. package/src/policy/conventions/query.ts +49 -0
  140. package/src/policy/conventions/react-hooks.ts +18 -0
  141. package/src/policy/conventions/store-or-query.ts +21 -0
  142. package/src/policy/conventions/stores.ts +56 -0
  143. package/src/policy/conventions/strip.ts +87 -0
  144. package/src/policy/conventions/verdict.ts +38 -0
  145. package/src/policy/creation-intent.ts +11 -0
  146. package/src/policy/deny-loop.ts +88 -0
  147. package/src/policy/design/allowed-write.ts +28 -0
  148. package/src/policy/design/content-checks.ts +45 -0
  149. package/src/policy/design/corpus-fs.ts +21 -0
  150. package/src/policy/design/corpus-resolve-cache.ts +49 -0
  151. package/src/policy/design/corpus-resolve.ts +99 -0
  152. package/src/policy/design/corpus.ts +92 -0
  153. package/src/policy/design/design-system-rules.ts +45 -0
  154. package/src/policy/design/flag.ts +30 -0
  155. package/src/policy/design/gates-pipeline.ts +105 -0
  156. package/src/policy/design/gates.ts +87 -0
  157. package/src/policy/design/screenshot-tools.ts +24 -0
  158. package/src/policy/design/skill-evidence.ts +34 -0
  159. package/src/policy/design/skill-gate.ts +81 -0
  160. package/src/policy/design/skill-triggers.ts +51 -0
  161. package/src/policy/design/state.ts +92 -0
  162. package/src/policy/design/template-urls.ts +54 -0
  163. package/src/policy/design/transitions.ts +68 -0
  164. package/src/policy/detect-claude-md-project-type.ts +32 -0
  165. package/src/policy/detect-framework.ts +99 -0
  166. package/src/policy/detect-primitive-checks.ts +100 -0
  167. package/src/policy/detect-primitive-lib.ts +55 -0
  168. package/src/policy/detect-project.ts +99 -0
  169. package/src/policy/edit-outcome.ts +53 -0
  170. package/src/policy/evaluate.ts +81 -0
  171. package/src/policy/expert-agents.ts +60 -0
  172. package/src/policy/file-size-scope.ts +32 -0
  173. package/src/policy/file-size.ts +99 -0
  174. package/src/policy/framework-solid-exclude.ts +52 -0
  175. package/src/policy/framework-solid-extended.ts +93 -0
  176. package/src/policy/framework-solid-gates-systems.ts +99 -0
  177. package/src/policy/framework-solid-gates.ts +98 -0
  178. package/src/policy/framework-solid.ts +88 -0
  179. package/src/policy/gemini-mcp-gate.ts +83 -0
  180. package/src/policy/git-gates.ts +40 -0
  181. package/src/policy/guards/bash-command-anchor.ts +40 -0
  182. package/src/policy/guards/bash-write-patterns.ts +140 -0
  183. package/src/policy/guards/bash-write-redirects.ts +201 -0
  184. package/src/policy/guards/bash-write-safe-paths.ts +62 -0
  185. package/src/policy/guards/bash-write.ts +76 -0
  186. package/src/policy/guards/context.ts +12 -0
  187. package/src/policy/guards/index.ts +61 -0
  188. package/src/policy/guards/install.ts +22 -0
  189. package/src/policy/guards/interface-separation-ext.ts +41 -0
  190. package/src/policy/guards/interface-separation.ts +100 -0
  191. package/src/policy/guards/protected-path.ts +100 -0
  192. package/src/policy/guards/security.ts +97 -0
  193. package/src/policy/index.ts +19 -0
  194. package/src/policy/interfaces/types.ts +46 -0
  195. package/src/policy/js-gate-route.ts +51 -0
  196. package/src/policy/lessons/lesson-gate.ts +0 -0
  197. package/src/policy/lessons/trigger-index.ts +84 -0
  198. package/src/policy/lessons/types.ts +31 -0
  199. package/src/policy/module-layout.ts +31 -0
  200. package/src/policy/nearest-manifest.ts +75 -0
  201. package/src/policy/never-approval.ts +95 -0
  202. package/src/policy/patterns.ts +80 -0
  203. package/src/policy/seo/validate.ts +51 -0
  204. package/src/policy/shadcn-project.ts +22 -0
  205. package/src/policy/shadcn-skill-gate.ts +97 -0
  206. package/src/policy/shell-read-refs.ts +96 -0
  207. package/src/policy/skill-path.ts +46 -0
  208. package/src/policy/skill-patterns/laravel.ts +40 -0
  209. package/src/policy/skill-patterns/nextjs.ts +29 -0
  210. package/src/policy/skill-patterns/react.ts +27 -0
  211. package/src/policy/skill-patterns/shadcn-subskills.ts +33 -0
  212. package/src/policy/skill-patterns/shadcn.ts +66 -0
  213. package/src/policy/skill-patterns/swift.ts +30 -0
  214. package/src/policy/skill-patterns/tailwind.ts +40 -0
  215. package/src/policy/skill-source.ts +52 -0
  216. package/src/policy/skill-trigger-patterns.ts +33 -0
  217. package/src/policy/skill-triggers.ts +108 -0
  218. package/src/policy/tailwind-skill-gate.ts +63 -0
  219. package/src/policy/trivial-edits.ts +52 -0
  220. package/src/policy/verbosity.ts +39 -0
  221. package/src/prompt/index.ts +1 -0
  222. package/src/prompt/types.ts +36 -0
  223. package/src/refs/discover.ts +69 -0
  224. package/src/refs/frontmatter.ts +24 -0
  225. package/src/refs/index.ts +4 -0
  226. package/src/refs/loader.ts +59 -0
  227. package/src/refs/ref-key.ts +41 -0
  228. package/src/refs/router.ts +63 -0
  229. package/src/refs/types.ts +25 -0
  230. package/src/runtime/activity.ts +99 -0
  231. package/src/runtime/apply-patch-gate.ts +41 -0
  232. package/src/runtime/burst-window.ts +21 -0
  233. package/src/runtime/codex-shell-tool.ts +37 -0
  234. package/src/runtime/command-string.ts +37 -0
  235. package/src/runtime/confirm/codex-confirm.ts +126 -0
  236. package/src/runtime/confirm/codex-prompt-origin.ts +16 -0
  237. package/src/runtime/confirm/confirm-code.ts +33 -0
  238. package/src/runtime/confirm/confirm-gate.ts +65 -0
  239. package/src/runtime/confirm/confirm-irreversible.ts +32 -0
  240. package/src/runtime/confirm/confirm-pending.ts +34 -0
  241. package/src/runtime/confirm/confirm-state.ts +56 -0
  242. package/src/runtime/confirm/confirm-subagent.ts +112 -0
  243. package/src/runtime/confirm/confirm-submit.ts +38 -0
  244. package/src/runtime/core-guards-active.ts +84 -0
  245. package/src/runtime/core-guards-wired.ts +47 -0
  246. package/src/runtime/deny-loop-store.ts +94 -0
  247. package/src/runtime/deny-notice.ts +52 -0
  248. package/src/runtime/design-cache-resolve.ts +51 -0
  249. package/src/runtime/design-content-gate.ts +95 -0
  250. package/src/runtime/design-files-gate.ts +114 -0
  251. package/src/runtime/design-helpers.ts +93 -0
  252. package/src/runtime/design-lifecycle.ts +39 -0
  253. package/src/runtime/design-read-credit.ts +28 -0
  254. package/src/runtime/design.ts +116 -0
  255. package/src/runtime/dev-context.ts +43 -0
  256. package/src/runtime/dry-patterns.ts +32 -0
  257. package/src/runtime/dry.ts +96 -0
  258. package/src/runtime/fragment-registry.ts +33 -0
  259. package/src/runtime/framework-skill-gate.ts +79 -0
  260. package/src/runtime/fs-cleanup.ts +71 -0
  261. package/src/runtime/gate-apex.ts +81 -0
  262. package/src/runtime/gate-helpers.ts +52 -0
  263. package/src/runtime/gate-input.ts +26 -0
  264. package/src/runtime/gate.ts +128 -0
  265. package/src/runtime/handle-post.ts +104 -0
  266. package/src/runtime/handle-pre.ts +137 -0
  267. package/src/runtime/handle-scope-async.ts +29 -0
  268. package/src/runtime/handle-types.ts +27 -0
  269. package/src/runtime/handle.ts +197 -0
  270. package/src/runtime/home-state.ts +55 -0
  271. package/src/runtime/index.ts +18 -0
  272. package/src/runtime/inform.ts +28 -0
  273. package/src/runtime/inject-budget-recap.ts +47 -0
  274. package/src/runtime/inject-budget.ts +81 -0
  275. package/src/runtime/inject-context.ts +85 -0
  276. package/src/runtime/inject-dedup-exclusive.ts +98 -0
  277. package/src/runtime/inject-dedup.ts +89 -0
  278. package/src/runtime/is-agent-tool.ts +19 -0
  279. package/src/runtime/lifecycle/agent-files.ts +49 -0
  280. package/src/runtime/lifecycle/agent-memory.ts +82 -0
  281. package/src/runtime/lifecycle/agent-transcript.ts +80 -0
  282. package/src/runtime/lifecycle/aipilot/analytics.ts +99 -0
  283. package/src/runtime/lifecycle/aipilot/apex-task-store.ts +61 -0
  284. package/src/runtime/lifecycle/aipilot/cache-base.ts +48 -0
  285. package/src/runtime/lifecycle/aipilot/cache-doc.ts +100 -0
  286. package/src/runtime/lifecycle/aipilot/cache-lessons.ts +58 -0
  287. package/src/runtime/lifecycle/aipilot/cache-test.ts +70 -0
  288. package/src/runtime/lifecycle/aipilot/curate-lessons.ts +64 -0
  289. package/src/runtime/lifecycle/aipilot/dispatch-aipilot.ts +106 -0
  290. package/src/runtime/lifecycle/aipilot/doc-cache-gate.ts +69 -0
  291. package/src/runtime/lifecycle/aipilot/inject-apex.ts +98 -0
  292. package/src/runtime/lifecycle/aipilot/inject-doc.ts +62 -0
  293. package/src/runtime/lifecycle/aipilot/inject-explore.ts +64 -0
  294. package/src/runtime/lifecycle/aipilot/inject-lessons.ts +61 -0
  295. package/src/runtime/lifecycle/aipilot/inject-test.ts +48 -0
  296. package/src/runtime/lifecycle/aipilot/lesson-archive.ts +60 -0
  297. package/src/runtime/lifecycle/aipilot/lesson-inject.ts +98 -0
  298. package/src/runtime/lifecycle/aipilot/lesson-parse.ts +74 -0
  299. package/src/runtime/lifecycle/aipilot/lessons.ts +72 -0
  300. package/src/runtime/lifecycle/aipilot/promote-global-lessons.ts +92 -0
  301. package/src/runtime/lifecycle/aipilot/solid-compliance.ts +69 -0
  302. package/src/runtime/lifecycle/aipilot/solid-notice.ts +47 -0
  303. package/src/runtime/lifecycle/aipilot/solid-transcript.ts +60 -0
  304. package/src/runtime/lifecycle/aipilot/source-scan.ts +56 -0
  305. package/src/runtime/lifecycle/aipilot/sync-task.ts +75 -0
  306. package/src/runtime/lifecycle/aipilot/transcript.ts +88 -0
  307. package/src/runtime/lifecycle/aipilot/types.ts +72 -0
  308. package/src/runtime/lifecycle/auto-document-reads.ts +73 -0
  309. package/src/runtime/lifecycle/cartographer/detect.ts +57 -0
  310. package/src/runtime/lifecycle/cartographer/ecosystem-map.ts +56 -0
  311. package/src/runtime/lifecycle/cartographer/fs-util.ts +64 -0
  312. package/src/runtime/lifecycle/cartographer/merge.ts +51 -0
  313. package/src/runtime/lifecycle/cartographer/project-map.ts +49 -0
  314. package/src/runtime/lifecycle/cartographer/scan-hooks.ts +48 -0
  315. package/src/runtime/lifecycle/cartographer/scan.ts +81 -0
  316. package/src/runtime/lifecycle/cartographer/session-start.ts +33 -0
  317. package/src/runtime/lifecycle/cartographer/track-enrichment.ts +45 -0
  318. package/src/runtime/lifecycle/cartographer/write-plugin-map.ts +43 -0
  319. package/src/runtime/lifecycle/cartographer/write-tree.ts +44 -0
  320. package/src/runtime/lifecycle/changelog-research.ts +43 -0
  321. package/src/runtime/lifecycle/check-file-size.ts +61 -0
  322. package/src/runtime/lifecycle/codex-resync/fingerprint.ts +63 -0
  323. package/src/runtime/lifecycle/codex-resync/lock.ts +46 -0
  324. package/src/runtime/lifecycle/codex-resync/managed-destination.ts +39 -0
  325. package/src/runtime/lifecycle/codex-resync/materialize-agents.ts +57 -0
  326. package/src/runtime/lifecycle/codex-resync/plugin-files.ts +65 -0
  327. package/src/runtime/lifecycle/codex-resync/plugin-roots.ts +62 -0
  328. package/src/runtime/lifecycle/codex-resync/resync.ts +45 -0
  329. package/src/runtime/lifecycle/codex-resync/symlink-commands.ts +25 -0
  330. package/src/runtime/lifecycle/dispatch.ts +109 -0
  331. package/src/runtime/lifecycle/failure-lesson.ts +55 -0
  332. package/src/runtime/lifecycle/index.ts +34 -0
  333. package/src/runtime/lifecycle/inject-rules.ts +50 -0
  334. package/src/runtime/lifecycle/instructions-loaded.ts +22 -0
  335. package/src/runtime/lifecycle/kimi-rules-native.ts +44 -0
  336. package/src/runtime/lifecycle/lessons/dispatch.ts +88 -0
  337. package/src/runtime/lifecycle/lessons/reminder.ts +89 -0
  338. package/src/runtime/lifecycle/lessons/state.ts +23 -0
  339. package/src/runtime/lifecycle/memory/agent-lesson.ts +35 -0
  340. package/src/runtime/lifecycle/memory/capture-error.ts +41 -0
  341. package/src/runtime/lifecycle/memory/client.ts +65 -0
  342. package/src/runtime/lifecycle/memory/dispatch.ts +42 -0
  343. package/src/runtime/lifecycle/memory/recall.ts +30 -0
  344. package/src/runtime/lifecycle/memory/salience.ts +31 -0
  345. package/src/runtime/lifecycle/memory/state.ts +49 -0
  346. package/src/runtime/lifecycle/memory/track-ops.ts +15 -0
  347. package/src/runtime/lifecycle/post-compact.ts +38 -0
  348. package/src/runtime/lifecycle/post-edit-ts.ts +49 -0
  349. package/src/runtime/lifecycle/post-tracking.ts +40 -0
  350. package/src/runtime/lifecycle/pre-compact.ts +38 -0
  351. package/src/runtime/lifecycle/rules-root.ts +99 -0
  352. package/src/runtime/lifecycle/security/check-skill.ts +63 -0
  353. package/src/runtime/lifecycle/security/scan-patterns.ts +53 -0
  354. package/src/runtime/lifecycle/security/scan.ts +76 -0
  355. package/src/runtime/lifecycle/security/skill-state.ts +47 -0
  356. package/src/runtime/lifecycle/security/track-mcp.ts +29 -0
  357. package/src/runtime/lifecycle/security/track-skill-read.ts +31 -0
  358. package/src/runtime/lifecycle/seo/post-tool-use.ts +45 -0
  359. package/src/runtime/lifecycle/session-end.ts +17 -0
  360. package/src/runtime/lifecycle/session-start.ts +57 -0
  361. package/src/runtime/lifecycle/snapshot/board.ts +27 -0
  362. package/src/runtime/lifecycle/snapshot/format.ts +60 -0
  363. package/src/runtime/lifecycle/snapshot/git.ts +71 -0
  364. package/src/runtime/lifecycle/snapshot/index.ts +52 -0
  365. package/src/runtime/lifecycle/snapshot/version.ts +34 -0
  366. package/src/runtime/lifecycle/solid-detect.ts +56 -0
  367. package/src/runtime/lifecycle/stop-core.ts +35 -0
  368. package/src/runtime/lifecycle/subagent-cache.ts +61 -0
  369. package/src/runtime/lifecycle/task-completed.ts +94 -0
  370. package/src/runtime/lifecycle/teammate-idle-check.ts +63 -0
  371. package/src/runtime/lifecycle/teammate-idle.ts +28 -0
  372. package/src/runtime/lifecycle/tool-failure.ts +24 -0
  373. package/src/runtime/lifecycle/track-changes.ts +61 -0
  374. package/src/runtime/lifecycle/validate-solid.ts +70 -0
  375. package/src/runtime/lifecycle/validate-tailwind.ts +80 -0
  376. package/src/runtime/lifecycle-bridge.ts +74 -0
  377. package/src/runtime/mcp-key.ts +44 -0
  378. package/src/runtime/mcp-tool-name.ts +92 -0
  379. package/src/runtime/mcp.ts +91 -0
  380. package/src/runtime/modular.ts +77 -0
  381. package/src/runtime/normalize.ts +107 -0
  382. package/src/runtime/notices.ts +91 -0
  383. package/src/runtime/notification-sound.ts +49 -0
  384. package/src/runtime/notifications.ts +77 -0
  385. package/src/runtime/paths.ts +59 -0
  386. package/src/runtime/post-fanout.ts +41 -0
  387. package/src/runtime/post-outcome.ts +61 -0
  388. package/src/runtime/pre-allow.ts +97 -0
  389. package/src/runtime/precommit.ts +60 -0
  390. package/src/runtime/prompt-text.ts +38 -0
  391. package/src/runtime/receipt-capture.ts +34 -0
  392. package/src/runtime/record.ts +26 -0
  393. package/src/runtime/respond.ts +92 -0
  394. package/src/runtime/solid-file-size-gate.ts +58 -0
  395. package/src/runtime/solid-pre.ts +53 -0
  396. package/src/runtime/storage.ts +9 -0
  397. package/src/statusline/colors.ts +31 -0
  398. package/src/statusline/constants.ts +17 -0
  399. package/src/statusline/formatters.ts +54 -0
  400. package/src/statusline/index.ts +4 -0
  401. package/src/statusline/progress-bar.ts +46 -0
  402. package/src/tracking/codex-post-failure.ts +73 -0
  403. package/src/tracking/index.ts +2 -0
  404. package/src/tracking/integrity.ts +99 -0
  405. package/src/tracking/one-shot-dedup.ts +34 -0
  406. package/src/tracking/one-shot-failure.ts +42 -0
  407. package/src/tracking/one-shot-store.ts +95 -0
  408. package/src/tracking/one-shot.ts +93 -0
  409. package/src/tracking/receipts.ts +95 -0
  410. package/src/tracking/session-state.ts +94 -0
  411. package/src/tracking/store.ts +96 -0
  412. package/src/tracking/track-compact.ts +100 -0
  413. package/src/tracking/track-diff.ts +50 -0
  414. package/src/tracking/track-journal.ts +97 -0
  415. package/src/tracking/track-lock-sync.ts +83 -0
  416. package/src/tracking/track-lock.ts +67 -0
  417. package/src/util/compact-json.ts +23 -0
  418. package/src/util/index.ts +2 -0
  419. package/src/util/json-io.ts +47 -0
  420. package/src/util/project-root.ts +41 -0
  421. package/src/util/runtime-io.ts +85 -0
  422. package/src/util/semver.ts +30 -0
@@ -0,0 +1,100 @@
1
+ import type { Prompt } from "../../prompt/types";
2
+ import type { GuardContext } from "./context";
3
+ import { moduleAwarePath } from "../module-layout";
4
+ import { maskCommentsAndStrings } from "../conventions/strip";
5
+ import { langOfPath, lexProfileOf } from "../conventions/langs";
6
+ import { declaresExportedTypeAlias } from "../conventions/interfaces";
7
+ import { rolloutVerdict } from "../conventions/verdict";
8
+ import { extendedInterfaceViolation } from "./interface-separation-ext";
9
+
10
+ /** TS/JS component files: top-level `interface`/`type Foo`. */
11
+ export const TS_DECL_RE: RegExp = /^\s*(export\s+)?(interface|type)\s+[A-Z]/m;
12
+ /** Python view models: class subclassing a schema/protocol base. */
13
+ export const PY_MODEL_RE: RegExp = /^\s*class\s+\w+\s*\([^)]*\b(?:BaseModel|TypedDict|Protocol)\b/m;
14
+ /**
15
+ * PHP controllers: top-level `interface`, `abstract class`, or a concrete
16
+ * `class …Interface/DTO/Request`. Union of the TS-only `abstract class` rule
17
+ * and the Python rule (`class [A-Z].*(Interface|DTO|Request)`, enforce-interfaces.py:16).
18
+ */
19
+ export const PHP_DECL_RE: RegExp = /^\s*(?:abstract\s+class\b|interface\b|class\s+[A-Z].*(?:Interface|DTO|Request))/m;
20
+ /** Swift views: top-level `protocol Foo`. */
21
+ export const SWIFT_PROTO_RE: RegExp = /^\s*protocol\s+[A-Z]/m;
22
+ /** Go handlers/controllers: top-level `type Foo interface`. */
23
+ export const GO_DECL_RE: RegExp = /^\s*type\s+[A-Z]\w*\s+interface\b/m;
24
+ /** Java/Kotlin controllers/handlers: top-level `interface`/`record`. */
25
+ export const JAVA_DECL_RE: RegExp = /^\s*(?:public\s+|private\s+|protected\s+|internal\s+)?(?:interface|record)\s+[A-Z]/m;
26
+ /** Rust: top-level `trait Foo` declaration (captures the name for impl co-location). */
27
+ export const RUST_DECL_RE: RegExp = /^\s*(?:pub(?:\(\w+\))?\s+)?trait\s+([A-Z]\w*)/m;
28
+
29
+ /**
30
+ * Blocks top-level interface/type/protocol declarations in component, view or
31
+ * controller files (Interface Segregation), when BOTH the path category AND
32
+ * the content pattern match. Content is MASKED first (conventions/strip.ts):
33
+ * declarations in comments/strings/templates/heredocs never trigger. Legacy
34
+ * regexes keep their exact values (parity tests) — every case that blocked
35
+ * before still blocks with the same message (byte-parity). Two extension
36
+ * classes ship ADVISORY via {@link rolloutVerdict} (`FUSE_CONVENTIONS_MODE`,
37
+ * Amendment 5): exported type aliases redirected to `src/types/` (Amendment 2),
38
+ * and the widenings of `interface-separation-ext.ts` (public protocol,
39
+ * sealed/fun interface, unexported Go top-level). Rust `impl<T>` stays a
40
+ * known FN. Parity: the Python port inspected `Write` only; we also fire on
41
+ * `Edit`, and fragments accept singular+plural dirs (`view/` + `views/`).
42
+ */
43
+ export function interfaceSeparationGuard(ctx: GuardContext): Prompt | null {
44
+ if (ctx.tool !== "Write" && ctx.tool !== "Edit") return null;
45
+ const path: string | undefined = ctx.filePath;
46
+ const content: string | undefined = ctx.content;
47
+ if (!path || !content) return null;
48
+ const lang = langOfPath(path);
49
+ const masked = maskCommentsAndStrings(content, lexProfileOf(lang ?? "ts"));
50
+
51
+ const blockWith = (msg: string, action: string): Prompt => ({
52
+ kind: "block",
53
+ title: "Separate the interface",
54
+ reason: `SOLID VIOLATION: ${msg}`,
55
+ actions: [action],
56
+ });
57
+
58
+ const inAny = (...frags: string[]): boolean =>
59
+ // Segment-anchored: `view/` must be a real dir — `overview/`, `livetest-app/`
60
+ // never match (same FP class as solid-transcript G3); fragment at path start OK.
61
+ frags.some((f) => path.includes(`/${f}`) || path.startsWith(f));
62
+ if (/\.(tsx|jsx|vue|svelte)$/.test(path) && TS_DECL_RE.test(masked)) {
63
+ if (!/^\s*(export\s+)?(declare\s+)?interface\s+\w/.test(masked) && declaresExportedTypeAlias(content)) {
64
+ return rolloutVerdict(blockWith(
65
+ "Type alias in component file. Move to modules/[feature]/src/types/",
66
+ "Move the type alias to modules/[feature]/src/types/",
67
+ ), true);
68
+ }
69
+ return blockWith(
70
+ "Interface/type in component file. Move to modules/[feature]/src/interfaces/",
71
+ "Move the interface/type to modules/[feature]/src/interfaces/",
72
+ );
73
+ }
74
+ if (/\.py$/.test(path) && inAny("view/", "views/", "controller/", "controllers/", "route/", "routes/") && PY_MODEL_RE.test(masked)) {
75
+ return blockWith("Type class in view file. Move to src/interfaces/", "Move the type class to src/interfaces/");
76
+ }
77
+ if (/\.go$/.test(path) && inAny("handler/", "handlers/", "controller/", "controllers/") && GO_DECL_RE.test(masked)) {
78
+ return blockWith("Interface in handler file. Move to internal/interfaces/", "Move the interface to internal/interfaces/");
79
+ }
80
+ if (/\.(java|kt)$/.test(path) && inAny("controller/", "controllers/", "handler/", "handlers/") && JAVA_DECL_RE.test(masked)) {
81
+ return blockWith("Interface in controller file. Move to interfaces/ package", "Move the interface to the interfaces/ package");
82
+ }
83
+ if (/\.php$/.test(path) && inAny("Controller/", "Controllers/", "Handler/", "Handlers/") && PHP_DECL_RE.test(masked)) {
84
+ return blockWith("Interface in controller file. Move to app/Contracts/", "Move the interface to app/Contracts/");
85
+ }
86
+ if (/\.swift$/.test(path) && inAny("View/", "Views/", "Component/", "Components/") && SWIFT_PROTO_RE.test(masked)) {
87
+ return blockWith("Protocol in view file. Move to Sources/Interfaces/", "Move the protocol to Sources/Interfaces/");
88
+ }
89
+ if (/\.rs$/.test(path) && !inAny("traits/", "interfaces/") && !path.endsWith("traits.rs")) {
90
+ const m = RUST_DECL_RE.exec(masked);
91
+ const name = m?.[1];
92
+ if (name && new RegExp(`impl\\s+${name}\\s+for\\s+\\w+`).test(masked)) {
93
+ const dest = moduleAwarePath(path, "traits.rs", "a dedicated traits.rs / interfaces module");
94
+ return blockWith(`Trait declared alongside its implementation. Move the trait to ${dest}`, `Move the trait to ${dest}`);
95
+ }
96
+ }
97
+ const ext = extendedInterfaceViolation(path, content);
98
+ if (ext) return rolloutVerdict(blockWith(ext.msg, ext.action), true);
99
+ return null;
100
+ }
@@ -0,0 +1,100 @@
1
+ import type { Prompt } from "../../prompt/types";
2
+ import type { GuardContext } from "./context";
3
+ import { shellOutputRedirects } from "./bash-write-redirects";
4
+
5
+ /**
6
+ * Path fragments that mark a location as internal/generated state.
7
+ *
8
+ * Parity with safe_paths.py: `~/.fuse-harness/cache` is a *writable*
9
+ * cache the harness owns (lessons, MCP cache, per-type state) — only the
10
+ * `cache/sessions` subtree is protected, not the whole cache.
11
+ */
12
+ export const PROTECTED_FRAGMENTS: readonly string[] = [
13
+ ".claude/plugins/marketplaces",
14
+ ".claude/plugins/cache",
15
+ ".claude/logs/00-apex",
16
+ ".fuse-harness/cache/sessions",
17
+ ".claude/apex/",
18
+ ".harness/track",
19
+ ".harness/memory/state",
20
+ // Precondition of the neverApproval exemption (evaluate.ts): blocks a Codex
21
+ // agent from self-elevating via its own allow rule — sandbox_mode never
22
+ // covers the user's ~/.codex/, only a project-local one.
23
+ ".codex/config.toml",
24
+ ".codex/rules/",
25
+ ];
26
+
27
+ /**
28
+ * Matches a real `.git` directory segment (`/.git/`, `~/.git`, leading or
29
+ * trailing `.git`) without matching unrelated names like `foo.git/` or
30
+ * `.github/`. Kept separate from the substring fragments for precise scoping.
31
+ */
32
+ export const PROTECTED_GIT_RE: RegExp = /(?:^|[/~\s])\.git(?:\/|$)/;
33
+
34
+ /** Standard block response for any protected-path violation. */
35
+ const BLOCK: Prompt = {
36
+ kind: "block",
37
+ title: "Protected path",
38
+ reason: "This is internal/generated enforcement state — do not edit it directly.",
39
+ actions: ["Edit the source, not the generated/cache/state copy"],
40
+ };
41
+
42
+ /** Returns true if `str` references a protected fragment or a real `.git` segment. */
43
+ function containsProtected(str: string): boolean {
44
+ return PROTECTED_FRAGMENTS.some((f: string): boolean => str.includes(f)) || PROTECTED_GIT_RE.test(str);
45
+ }
46
+
47
+ /** Strips surrounding quotes from a captured shell token. */
48
+ function unquote(t: string): string {
49
+ return t.replace(/^['"]|['"]$/g, "");
50
+ }
51
+
52
+ /**
53
+ * Extracts the genuine write *targets* of a shell command, so a protected path
54
+ * appearing only as a *read source* (e.g. `grep x .claude/apex/ > out.txt`,
55
+ * `cat .git/config > /dev/null`) is not mistaken for a write.
56
+ *
57
+ * Covers `>` / `>>` redirects (skipping `2>`, `&>`, `1>` fd-redirects and
58
+ * `/dev/null`), `tee`, `dd of=`, and the destination / in-place file of
59
+ * `cp` / `mv` / `sed -i` / `perl -i` (last path token of the segment).
60
+ * Best-effort: obfuscated shell (base64, indirection, process substitution)
61
+ * can still evade this — residual risk, mitigated by the freshness gate.
62
+ */
63
+ function extractWriteTargets(cmd: string): string[] {
64
+ const out: string[] = [];
65
+ const push = (t: string | undefined): void => {
66
+ const v: string = unquote((t ?? "").trim());
67
+ if (v && v !== "/dev/null") out.push(v);
68
+ };
69
+ for (const redirect of shellOutputRedirects(cmd)) push(redirect.target);
70
+ for (const m of cmd.matchAll(/\btee\b(?:\s+-\S+)*\s+('[^']+'|"[^"]+"|\S+)/g)) push(m[1]);
71
+ for (const m of cmd.matchAll(/\bdd\b[^|;&]*\bof=('[^']+'|"[^"]+"|\S+)/g)) push(m[1]);
72
+ for (const seg of cmd.split(/[;&|]+/)) {
73
+ if (!/\b(?:cp|mv)\b|\b(?:sed|perl)\b[^|;&]*\s-i/.test(seg)) continue;
74
+ const head: string = seg.split(/[<>]/)[0] ?? "";
75
+ const toks: string[] = head.trim().split(/\s+/).filter((t: string): boolean => Boolean(t) && !t.startsWith("-"));
76
+ push(toks[toks.length - 1]);
77
+ }
78
+ return out;
79
+ }
80
+
81
+ /**
82
+ * Blocks direct edits to internal/generated state directories.
83
+ *
84
+ * Covers:
85
+ * - Write / Edit tool calls whose `filePath` targets a protected fragment.
86
+ * - Bash commands whose actual write *target* is a protected fragment
87
+ * (read sources are ignored; see `extractWriteTargets`).
88
+ *
89
+ * @param ctx - The guard context (tool, filePath, command).
90
+ * @returns A blocking {@link Prompt}, or null to allow.
91
+ */
92
+ export function protectedPathGuard(ctx: GuardContext): Prompt | null {
93
+ if ((ctx.tool === "Write" || ctx.tool === "Edit") && ctx.filePath) {
94
+ if (containsProtected(ctx.filePath)) return BLOCK;
95
+ }
96
+ if (ctx.tool === "Bash" && ctx.command) {
97
+ if (extractWriteTargets(ctx.command).some(containsProtected)) return BLOCK;
98
+ }
99
+ return null;
100
+ }
@@ -0,0 +1,97 @@
1
+ import type { Prompt } from "../../prompt/types";
2
+ import type { GuardContext } from "./context";
3
+
4
+ /** A pattern paired with the violation label to name in the deny/ask reason. */
5
+ export interface LabeledPattern {
6
+ re: RegExp;
7
+ label: string;
8
+ }
9
+
10
+ /** Critical patterns that must always be blocked — parity `security_rules.py`'s cumulated violation names. */
11
+ export const CRITICAL_PATTERNS: LabeledPattern[] = [
12
+ { re: /\brm\s+(?:-[a-z]*\s+)*-[a-z]*[rf][a-z]*\s+(?:-[a-z]+\s+)*(?:\/|~)(?:\s|$)/, label: "DANGEROUS PATTERN: recursive delete of / or ~" },
13
+ { re: /:\(\)\s*\{\s*:\s*\|\s*:\s*&\s*\}\s*;\s*:/, label: "DANGEROUS PATTERN: fork bomb" },
14
+ { re: /\b(?:curl|wget)\b[^|]*\|\s*(?:sudo\s+)?(?:ba|z|da|k)?sh\b/, label: "DANGEROUS PATTERN: remote script piped to a shell" },
15
+ { re: /\bchmod\s+(?:-[a-zA-Z]+\s+)*777\s+\//, label: "DANGEROUS PATTERN: chmod 777 on /" },
16
+ { re: /\bmkfs(?:\.[a-z0-9]+)?\b/, label: "CRITICAL: Detected dangerous command 'mkfs'" },
17
+ { re: /\bdd\b[^\n]*\bof=\/dev\/(?:r?disk|sd|hd|nvme|mmcblk|vd|xvd)/i, label: "CRITICAL: Detected dangerous command 'dd if='" },
18
+ { re: /\bshred\b/, label: "CRITICAL: Detected dangerous command 'shred'" },
19
+ { re: /\bfdisk\b/, label: "CRITICAL: Detected dangerous command 'fdisk'" },
20
+ { re: /\bdiskutil\s+(?:erase|partitionDisk)/i, label: "CRITICAL: Detected dangerous command 'diskutil erase'" },
21
+ { re: /(?:>|>>)\s*\/dev\/(?:sda|hda|nvme)/, label: "DANGEROUS PATTERN: redirect to a raw disk device" },
22
+ { re: /\brm\s+(?:-[a-z]*\s+)*-[a-z]*[rf][a-z]*\s+(?:-[a-z]+\s+)*\/(?:etc|usr|var|bin|sbin|boot|lib)\b/, label: "DANGEROUS PATTERN: recursive delete of a system directory" },
23
+ // Privilege escalation — DENY (parity: security_rules.PRIVILEGE_COMMANDS, has_critical=True).
24
+ { re: /(?:^|[\s;|&])sudo(?:[\s;|&]|$)/, label: "PRIVILEGE ESCALATION: sudo" },
25
+ { re: /(?:^|[\s;|&])su(?:[\s;|&]|$)/, label: "PRIVILEGE ESCALATION: su" },
26
+ { re: /(?:^|[\s;|&])doas(?:[\s;|&]|$)/, label: "PRIVILEGE ESCALATION: doas" },
27
+ { re: /(?:^|[\s;|&])passwd(?:[\s;|&]|$)/, label: "PRIVILEGE ESCALATION: passwd" },
28
+ // `del` — DENY (parity: security_rules.CRITICAL_COMMANDS token match).
29
+ { re: /(?:^|[\s;|&])del(?:[\s;|&]|$)/, label: "CRITICAL: Detected dangerous command 'del'" },
30
+ ];
31
+
32
+ /** Patterns that warrant explicit confirmation before running — parity `security_rules.py`'s ask-level violation names. */
33
+ export const ASK_PATTERNS: LabeledPattern[] = [
34
+ { re: /\bchmod\s+(?:-[a-zA-Z]+\s+)*777\b/, label: "DANGEROUS PATTERN: chmod 777" },
35
+ { re: /\bchown\s+-R\b/, label: "DANGEROUS PATTERN: recursive chown" },
36
+ { re: /\beval\s/, label: "DANGEROUS PATTERN: eval" },
37
+ { re: /(?:>|>>|\btee\b)\s*\/etc\//, label: "DANGEROUS PATTERN: write to /etc" },
38
+ { re: /\brm\s/, label: "DELETE: 'rm' permanently deletes - confirmation required" },
39
+ { re: /\bunlink\s/, label: "DELETE: 'unlink' command detected - confirmation required" },
40
+ ];
41
+
42
+ /**
43
+ * Strip heredoc bodies so their content isn't matched as a command (false positives).
44
+ * Two-phase scan: a backreference-free opener regex finds `<<[-]DELIM`, then the body
45
+ * is cut up to the closing delimiter via `indexOf` — O(n), no catastrophic backtracking.
46
+ */
47
+ function stripHeredoc(cmd: string): string {
48
+ const opener = /<<-?\s*['"]?(\w+)['"]?/g;
49
+ let out = cmd;
50
+ let m: RegExpExecArray | null;
51
+ while ((m = opener.exec(out)) !== null) {
52
+ const delim: string = m[1] ?? "";
53
+ // Closer may be indented (the `<<-` form strips leading tabs); `\b` ends the word.
54
+ const closer = new RegExp(`\\n[ \\t]*${delim}\\b`);
55
+ const rest: string = out.slice(m.index + m[0].length);
56
+ const hit: RegExpMatchArray | null = closer.exec(rest);
57
+ if (!hit || hit.index === undefined) break;
58
+ const end: number = m.index + m[0].length + hit.index + hit[0].length;
59
+ out = `${out.slice(0, m.index)} ${out.slice(end)}`;
60
+ opener.lastIndex = m.index;
61
+ }
62
+ return out;
63
+ }
64
+
65
+ /**
66
+ * Guards against dangerous Bash commands. Parity `security_rules.validate_command`
67
+ * + `security-guard.py`: ACCUMULATE every matched violation, then deny when any
68
+ * critical matched else ask, joining all violation labels in the reason. The
69
+ * generic `rm` ask is exempt when the command targets a trash location (parity
70
+ * `not re.search('trash', cmd)`).
71
+ */
72
+ export function securityGuard(ctx: GuardContext): Prompt | null {
73
+ if (ctx.tool !== "Bash" || !ctx.command) return null;
74
+ const cmd: string = stripHeredoc(ctx.command);
75
+ const hasTrash: boolean = /trash/i.test(cmd);
76
+
77
+ const critical: string[] = CRITICAL_PATTERNS.filter(({ re }) => re.test(cmd)).map((p) => p.label);
78
+ const ask: string[] = ASK_PATTERNS.filter(
79
+ ({ re, label }) => re.test(cmd) && !(hasTrash && label.startsWith("DELETE: 'rm'")),
80
+ ).map((p) => p.label);
81
+ if (critical.length === 0 && ask.length === 0) return null;
82
+
83
+ if (critical.length > 0) {
84
+ return {
85
+ kind: "block",
86
+ title: "Dangerous command",
87
+ reason: `${[...critical, ...ask].join(", ")}.`,
88
+ actions: ["Remove the destructive command", "Scope the operation to a specific safe path"],
89
+ };
90
+ }
91
+ return {
92
+ kind: "ask",
93
+ title: "Dangerous command",
94
+ reason: `${ask.join(", ")}.`,
95
+ actions: ["Confirm this command is intended", "Run with least privilege"],
96
+ };
97
+ }
@@ -0,0 +1,19 @@
1
+ export * from "./detect-project";
2
+ export * from "./detect-framework";
3
+ export * from "./file-size";
4
+ export * from "./patterns";
5
+ export * from "./evaluate";
6
+ export * from "./apex";
7
+ export * from "./guards";
8
+ export * from "./creation-intent";
9
+ export * from "./verbosity";
10
+ export * from "./framework-solid";
11
+ export * from "./skill-triggers";
12
+ export * from "./claude-md-context";
13
+ export * from "./apex-task-context";
14
+ export * from "./cartographer/indicators";
15
+ export * from "./cartographer/frontmatter";
16
+ export * from "./cartographer/entry";
17
+ export * from "./cartographer/describe";
18
+ export * from "./seo/validate";
19
+ export * from "./conventions/index";
@@ -0,0 +1,46 @@
1
+ import type { Prompt } from "../../prompt/types";
2
+
3
+ /** Harness-agnostic input to {@link evaluate}. */
4
+ export interface PolicyContext {
5
+ /** Tool name (e.g. "Write", "Edit", "Bash"). */
6
+ tool: string;
7
+ filePath?: string;
8
+ content?: string;
9
+ command?: string;
10
+ /** Optional override for the SOLID max-lines limit. */
11
+ maxLines?: number;
12
+ /** Subagent type — `Explore`/`Plan` are exempt from the file-size gate. */
13
+ agentType?: string;
14
+ /** Line count of the existing on-disk file (so an Edit on an oversized file blocks). */
15
+ existingLines?: number;
16
+ /** Edit only: full on-disk content, read once by the runtime (runtime/gate-helpers.ts::existingLineCounts) — lets evaluate() compute the exact post-edit line count instead of judging the stale pre-edit count alone. Undefined when unreadable/missing (fail-closed to the existingLines-only behavior). */
17
+ existingContent?: string;
18
+ /** Edit only: the tool_input.old_string being replaced (runtime/normalize.ts). Undefined for Write (no such field) — fail-closed. */
19
+ oldString?: string;
20
+ /** Edit only: the tool_input.replace_all flag — every occurrence of old_string is replaced, not just the first. */
21
+ isReplaceAll?: boolean;
22
+
23
+ /** Codex-only, populated by handle-pre.ts from the resolved `permission_mode` of an `approval_policy=never` session (adapters/codex/permission-mode.ts) — auto-approve gate, wired through evaluate.ts's anti-chaining check. */
24
+ neverApproval?: boolean;
25
+ }
26
+
27
+ /** Harness-agnostic policy decision (+ a portable prompt for adapters to render). */
28
+ export interface PolicyResult {
29
+ decision: "allow" | "deny" | "warn";
30
+ message: string | null;
31
+ prompt?: Prompt;
32
+ meta?: Record<string, unknown>;
33
+ }
34
+
35
+ /** Phrasing for the "view pending / mark in-progress / mark done" step trio (APEX task-context injection, steps 3/4/7). See `apex-target-steps.ts`. */
36
+ export interface ApexTaskSteps {
37
+ step3: string;
38
+ step4: string;
39
+ step7: string;
40
+ }
41
+
42
+ /** Phrasing for the "before starting / when done" pair (APEX sub-agent injection, SubagentStart). See `apex-target-steps.ts`. */
43
+ export interface ApexAgentSteps {
44
+ beforeStart: string;
45
+ whenDone: string;
46
+ }
@@ -0,0 +1,51 @@
1
+ import { dirname, resolve } from "node:path";
2
+ import { nearestManifestDir, projectCaps, type Cap } from "./nearest-manifest";
3
+
4
+ /**
5
+ * JS framework the SOLID gate routes on, resolved MANIFEST-FIRST (H1), or
6
+ * `null` when the project declares no JS-framework capability — the caller
7
+ * then falls back to the legacy content-marker routing, byte-identical.
8
+ */
9
+ export type JsGateRoute = "nextjs" | "react" | null;
10
+
11
+ /**
12
+ * Per-process memo of manifest-dir → capabilities. Hook invocations are
13
+ * short-lived processes, so a stale manifest is impossible in production; the
14
+ * memo only saves a re-read when one process judges several files (tests).
15
+ */
16
+ const capsCache = new Map<string, Set<Cap>>();
17
+
18
+ /** Resolve the nearest manifest's capabilities for `filePath` (memoized per dir). */
19
+ function capsFor(filePath: string, cwd: string): Set<Cap> {
20
+ const dir = nearestManifestDir(dirname(resolve(cwd, filePath)));
21
+ if (!dir) return new Set();
22
+ const hit = capsCache.get(dir);
23
+ if (hit) return hit;
24
+ const caps = projectCaps(dir);
25
+ capsCache.set(dir, caps);
26
+ return caps;
27
+ }
28
+
29
+ /**
30
+ * Pick the JS gate from the PROJECT's declared framework, not the file's
31
+ * content markers (H1): a bare client component (hooks but no `next` marker)
32
+ * in a Next.js app must still hit nextGate's `'use client'` rule — content
33
+ * routing sent it to reactGate and the file landed, crashing at runtime.
34
+ * `nextjs` wins over `react` (every Next app ships react); a TanStack
35
+ * Start/Router or plain-react project routes to reactGate. Fail-open: any
36
+ * error, or no JS-framework capability (no manifest, backend, vue), yields
37
+ * `null` so the legacy NEXT_RE content routing applies, byte-identical.
38
+ * @param filePath - Path of the file being written/edited.
39
+ * @param cwd - Root to resolve a relative `filePath` against (default cwd).
40
+ * @returns The gate route, or null for the legacy content-based fallback.
41
+ */
42
+ export function jsGateRoute(filePath: string, cwd: string = process.cwd()): JsGateRoute {
43
+ try {
44
+ const caps = capsFor(filePath, cwd);
45
+ if (caps.has("nextjs")) return "nextjs";
46
+ if (caps.has("react") || caps.has("tanstack-start") || caps.has("tanstack-router")) return "react";
47
+ return null;
48
+ } catch {
49
+ return null;
50
+ }
51
+ }
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Compile the triggered-lesson index from `MEMORY/LESSON.md`. A lesson is a
3
+ * bullet (`- [YYYY-MM-DD HH:MM] ...`); it opts into decision-time injection by
4
+ * ending with a `[TRIGGERS tool:.. path:.. error:.. keyword:..]` line. Lessons
5
+ * WITHOUT that tag are skipped here (they keep the SessionStart block behavior —
6
+ * zero regression). Parsed once per file version (mtime-memoized).
7
+ */
8
+ import { readFileSync, statSync } from "node:fs";
9
+ import type { LessonEntry, Triggers } from "./types";
10
+
11
+ /** Matches a trailing `[TRIGGERS ...]` line (its body captured). */
12
+ const TRIGGER_RE = /^\[TRIGGERS\s+(.+?)\]$/;
13
+
14
+ /** Comma list for `key:` in a trigger body (values are space-delimited). */
15
+ function list(body: string, key: string): string[] {
16
+ const m = body.match(new RegExp(`\\b${key}:([^\\s\\]]+)`));
17
+ const val = m?.[1];
18
+ return val ? val.split(",").filter(Boolean) : [];
19
+ }
20
+
21
+ /** Parse a `[TRIGGERS ...]` body into predicates (error is a single regex). */
22
+ function parseTriggers(body: string): Triggers {
23
+ const err = body.match(/\berror:([^\s\]]+)/);
24
+ return { tools: list(body, "tool"), paths: list(body, "path"), error: err?.[1], keywords: list(body, "keyword") };
25
+ }
26
+
27
+ /** Collapse to a single ≤3-line compact string (cap length). */
28
+ function compact(text: string): string {
29
+ const one = text.replace(/\s+/g, " ").trim();
30
+ return one.length > 280 ? `${one.slice(0, 277)}…` : one;
31
+ }
32
+
33
+ /**
34
+ * Parse LESSON.md content into triggered entries. A bullet's text spans its
35
+ * `- ` line plus any following non-blank continuation lines up to the next
36
+ * bullet; a `[TRIGGERS ...]` continuation line arms it.
37
+ * @param content - Raw LESSON.md text.
38
+ * @returns Entries that declared triggers (others skipped).
39
+ */
40
+ export function parseLessons(content: string): LessonEntry[] {
41
+ const lines = content.split("\n");
42
+ const out: LessonEntry[] = [];
43
+ for (let i = 0; i < lines.length; i++) {
44
+ const line = lines[i];
45
+ if (line === undefined || !line.startsWith("- ")) continue;
46
+ let text = line.slice(2);
47
+ let triggers: Triggers | null = null;
48
+ for (let j = i + 1; j < lines.length; j++) {
49
+ const cont = lines[j];
50
+ if (cont === undefined || cont.trim() === "" || cont.startsWith("- ")) break;
51
+ const body = cont.trim().match(TRIGGER_RE)?.[1];
52
+ if (body !== undefined) triggers = parseTriggers(body);
53
+ else text += ` ${cont.trim()}`;
54
+ }
55
+ if (triggers) out.push({ text: compact(text), triggers });
56
+ }
57
+ return out;
58
+ }
59
+
60
+ let memo: { key: string; entries: LessonEntry[] } | null = null;
61
+
62
+ /**
63
+ * Compile (once per file version) the triggered-lesson index from `file`.
64
+ * Memoized by path+mtime: re-parses only when LESSON.md changes.
65
+ * @param file - Absolute path to MEMORY/LESSON.md.
66
+ * @returns The compiled entries (missing/unreadable file → empty).
67
+ */
68
+ export function lessonIndex(file: string): LessonEntry[] {
69
+ let key: string;
70
+ try {
71
+ key = `${file}:${statSync(file).mtimeMs}`;
72
+ } catch {
73
+ return [];
74
+ }
75
+ if (memo?.key === key) return memo.entries;
76
+ let entries: LessonEntry[] = [];
77
+ try {
78
+ entries = parseLessons(readFileSync(file, "utf-8"));
79
+ } catch {
80
+ entries = [];
81
+ }
82
+ memo = { key, entries };
83
+ return entries;
84
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Interfaces for the decision-time lesson engine: a lesson may carry an optional
3
+ * `[TRIGGERS ...]` tag that indexes it by tool/path/error/keyword so the ONE
4
+ * relevant lesson can be injected at the PreToolUse it applies to (instead of
5
+ * the whole LESSON.md block dumped at SessionStart).
6
+ */
7
+
8
+ /** Parsed predicates from a lesson's optional `[TRIGGERS ...]` tag. */
9
+ export interface Triggers {
10
+ /** Exact tool names (e.g. `Write`, `Bash`) — highest-specificity match. */
11
+ readonly tools: readonly string[];
12
+ /** Globs (`*`, `**`) matched against `tool_input.file_path`. */
13
+ readonly paths: readonly string[];
14
+ /** Regex source matched against a prior error, when supplied. */
15
+ readonly error?: string;
16
+ /** Substrings matched against the JSON of `tool_input` — lowest specificity. */
17
+ readonly keywords: readonly string[];
18
+ }
19
+
20
+ /** A compiled lesson: compact text (≤3 lines) plus its trigger predicates. */
21
+ export interface LessonEntry {
22
+ readonly text: string;
23
+ readonly triggers: Triggers;
24
+ }
25
+
26
+ /**
27
+ * Cooldown gate reused from `../../runtime/inject-dedup`: `true` = emit now
28
+ * (first hit / window elapsed), `false` = still inside the window. Injected so
29
+ * the gate never grows its own window mechanism (team DRY).
30
+ */
31
+ export type OncePerWindow = (key: string, windowMs: number) => boolean;
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Module-aware suggestion helper, shared by every framework SOLID gate
3
+ * (react/next/laravel/swift/go/rust). Detection is purely path-based — no
4
+ * filesystem access — so it works identically for both Write (full content,
5
+ * real path) and Edit (snippet, real path).
6
+ */
7
+
8
+ /** Matches a `modules/<name>/` path segment anywhere in the file path. */
9
+ const MODULE_SEGMENT_RE: RegExp = /(?:^|\/)modules\/([^/]+)\//;
10
+
11
+ /**
12
+ * Extract the module name from a `modules/<name>/…` path segment.
13
+ * @param filePath - path of the file under validation
14
+ * @returns the module name, or `undefined` when the file isn't inside `modules/<name>/`
15
+ */
16
+ export function moduleName(filePath: string): string | undefined {
17
+ return MODULE_SEGMENT_RE.exec(filePath)?.[1];
18
+ }
19
+
20
+ /**
21
+ * Build a module-aware destination suggestion. When `filePath` sits inside a
22
+ * `modules/<name>/` directory, nests `subpath` under that module; otherwise
23
+ * falls back to the language's own default convention.
24
+ * @param filePath - path of the file under validation
25
+ * @param subpath - subpath to nest inside `modules/<name>/` when a module is detected
26
+ * @param fallback - suggestion to use when no module is detected
27
+ */
28
+ export function moduleAwarePath(filePath: string, subpath: string, fallback: string): string {
29
+ const name = moduleName(filePath);
30
+ return name ? `modules/${name}/${subpath}` : fallback;
31
+ }
@@ -0,0 +1,75 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { dirname, join, resolve } from "node:path";
3
+ import { detectProjectType } from "./detect-project";
4
+
5
+ /** Real project capabilities inferred from a manifest's deps + config files. */
6
+ export type Cap =
7
+ | "react" | "nextjs" | "vue" | "svelte" | "angular" | "nuxt"
8
+ | "laravel" | "swift" | "tailwind"
9
+ | "tanstack-start" | "tanstack-router" | "tanstack-query" | "vue-query"
10
+ | "zustand" | "pinia";
11
+
12
+ /** Manifest filenames that anchor a project root (any one marks the dir). */
13
+ const MANIFESTS = ["package.json", "composer.json", "Package.swift", "Cargo.toml", "go.mod", "pyproject.toml"];
14
+
15
+ /**
16
+ * Walk up from `startDir` to the NEAREST ancestor holding any manifest file.
17
+ * Unlike {@link projectRootOrNull} (which prefers the `.git` boundary and would
18
+ * skip a nested `scripts/package.json`), this returns the closest manifest dir,
19
+ * so an inner monorepo package wins over the repo root.
20
+ * @param startDir - Directory to begin the walk-up from.
21
+ * @returns The nearest manifest directory, or `null` at the filesystem root.
22
+ */
23
+ export function nearestManifestDir(startDir: string): string | null {
24
+ let current = resolve(startDir);
25
+ for (;;) {
26
+ if (MANIFESTS.some((m) => existsSync(join(current, m)))) return current;
27
+ const parent = dirname(current);
28
+ if (parent === current) return null;
29
+ current = parent;
30
+ }
31
+ }
32
+
33
+ /**
34
+ * Read the REAL capabilities of the project rooted at `dir`: JS-framework deps
35
+ * from `package.json` (which {@link detectProjectType} does NOT scan), plus the
36
+ * non-JS/config signals detectProjectType already covers (laravel/swift/
37
+ * tailwind) — reused, not duplicated. Fail-open: any parse error yields whatever
38
+ * was gathered so far, never throws.
39
+ * @param dir - A manifest directory (from {@link nearestManifestDir}), or null.
40
+ * @returns The set of detected capabilities (empty when `dir` is null).
41
+ */
42
+ export function projectCaps(dir: string | null): Set<Cap> {
43
+ const caps = new Set<Cap>();
44
+ if (!dir) return caps;
45
+ try {
46
+ const pkgPath = join(dir, "package.json");
47
+ if (existsSync(pkgPath)) {
48
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8")) as {
49
+ dependencies?: Record<string, string>;
50
+ devDependencies?: Record<string, string>;
51
+ };
52
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
53
+ if ("react" in deps || "react-dom" in deps) caps.add("react");
54
+ if ("next" in deps) caps.add("nextjs");
55
+ if ("vue" in deps) caps.add("vue");
56
+ if ("svelte" in deps) caps.add("svelte");
57
+ if ("@angular/core" in deps) caps.add("angular");
58
+ if ("nuxt" in deps) caps.add("nuxt");
59
+ if ("tailwindcss" in deps) caps.add("tailwind");
60
+ if ("@tanstack/react-start" in deps) caps.add("tanstack-start");
61
+ if ("@tanstack/react-router" in deps) caps.add("tanstack-router");
62
+ if ("@tanstack/react-query" in deps) caps.add("tanstack-query");
63
+ if ("@tanstack/vue-query" in deps) caps.add("vue-query");
64
+ if ("zustand" in deps) caps.add("zustand");
65
+ if ("pinia" in deps) caps.add("pinia");
66
+ }
67
+ const type = detectProjectType(dir);
68
+ if (type === "laravel") caps.add("laravel");
69
+ if (type === "swift") caps.add("swift");
70
+ if (type === "tailwind") caps.add("tailwind");
71
+ } catch {
72
+ // fail-open: return what we have
73
+ }
74
+ return caps;
75
+ }