@fusengine/harness 0.1.90 → 0.1.91

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (417) hide show
  1. package/dist/adapters/claude/index.d.mts +1 -1
  2. package/dist/adapters/claude/index.mjs +1 -1
  3. package/dist/adapters/cline/index.mjs +1 -1
  4. package/dist/adapters/codex/index.d.mts +2 -2
  5. package/dist/adapters/codex/index.mjs +3 -2
  6. package/dist/adapters/cursor/index.d.mts +15 -28
  7. package/dist/adapters/cursor/index.mjs +35 -46
  8. package/dist/adapters/gemini/index.mjs +1 -1
  9. package/dist/adapters/hermes/index.d.mts +1 -1
  10. package/dist/adapters/hermes/index.mjs +1 -1
  11. package/dist/adapters/kimi/index.d.mts +1 -1
  12. package/dist/adapters/kimi/index.mjs +1 -1
  13. package/dist/{claude-5bC8TDAz.mjs → claude-Ckv2_TgP.mjs} +3 -43
  14. package/dist/cli/bin.mjs +429 -16
  15. package/dist/cli/index.mjs +1 -1
  16. package/dist/command-string-CALMTnwN.mjs +42 -0
  17. package/dist/{evaluate-C1VSI5pb.mjs → evaluate-CMiIqHeH.mjs} +261 -13
  18. package/dist/{handle-CBaOcFZq.mjs → handle-C43gA-Pr.mjs} +701 -134
  19. package/dist/{hermes-kP6NUQlG.mjs → hermes-B9-p_3IF.mjs} +3 -2
  20. package/dist/{index-DBgnIZn9.d.mts → index-B8PG82su.d.mts} +1 -1
  21. package/dist/{index-D4C2-IIc.d.mts → index-SmKYkk2N.d.mts} +1 -1
  22. package/dist/index.d.mts +2 -2
  23. package/dist/index.mjs +2 -2
  24. package/dist/init/index.d.mts +1 -1
  25. package/dist/init/index.mjs +1 -1
  26. package/dist/{kimi-D6X7b8wF.mjs → kimi-G2wcSh5-.mjs} +3 -2
  27. package/dist/normalize-BjG6unTj.mjs +317 -0
  28. package/dist/policy/index.d.mts +1 -1
  29. package/dist/policy/index.mjs +2 -2
  30. package/dist/prompt/index.d.mts +1 -1
  31. package/dist/{run-CdIlUtjv.mjs → run-DkrzC0gb.mjs} +1 -1
  32. package/dist/{run-TAXNRSpD.mjs → run-GHAMqcOn.mjs} +26 -3
  33. package/dist/runtime/index.d.mts +27 -16
  34. package/dist/runtime/index.mjs +2 -2
  35. package/dist/{types-C0-igRKr.d.mts → types-Bh0jEF3_.d.mts} +2 -0
  36. package/dist/{validate-DZFtAbLP.mjs → validate-KjZ1X9tH.mjs} +1 -1
  37. package/package.json +3 -2
  38. package/src/adapters/claude/index.ts +92 -0
  39. package/src/adapters/cline/index.ts +36 -0
  40. package/src/adapters/codex/apply-patch.ts +94 -0
  41. package/src/adapters/codex/index.ts +84 -0
  42. package/src/adapters/codex/permission-mode.ts +16 -0
  43. package/src/adapters/cursor/context.ts +46 -0
  44. package/src/adapters/cursor/events.ts +38 -0
  45. package/src/adapters/cursor/index.ts +43 -0
  46. package/src/adapters/cursor/interfaces/types.ts +88 -0
  47. package/src/adapters/cursor/native-response.ts +158 -0
  48. package/src/adapters/cursor/normalize.ts +123 -0
  49. package/src/adapters/cursor/respond.ts +123 -0
  50. package/src/adapters/gemini/index.ts +36 -0
  51. package/src/adapters/hermes/index.ts +55 -0
  52. package/src/adapters/hermes/interfaces/types.ts +26 -0
  53. package/src/adapters/kimi/index.ts +96 -0
  54. package/src/adapters/kimi/interfaces/types.ts +55 -0
  55. package/src/cache/compact.ts +55 -0
  56. package/src/cache/index.ts +5 -0
  57. package/src/cache/io.ts +38 -0
  58. package/src/cache/mcp-response.ts +39 -0
  59. package/src/cache/mcp-store.ts +70 -0
  60. package/src/cache/store.ts +93 -0
  61. package/src/changelog/fetch.ts +79 -0
  62. package/src/cli/bin.ts +100 -0
  63. package/src/cli/cursor-event-scanner.ts +175 -0
  64. package/src/cli/cursor-stdin-reader.ts +65 -0
  65. package/src/cli/doctor.ts +96 -0
  66. package/src/cli/hook-io.ts +130 -0
  67. package/src/cli/hook-sound.ts +49 -0
  68. package/src/cli/index.ts +1 -0
  69. package/src/cli/json-primitive-scanner.ts +83 -0
  70. package/src/cli/run.ts +36 -0
  71. package/src/cli/scope.ts +31 -0
  72. package/src/codex-rules/build.ts +63 -0
  73. package/src/codex-rules/index.ts +8 -0
  74. package/src/codex-rules/mapping.ts +42 -0
  75. package/src/codex-rules/rules/absolute-paths.ts +52 -0
  76. package/src/codex-rules/rules/ask.ts +44 -0
  77. package/src/codex-rules/rules/chmod-ask.ts +57 -0
  78. package/src/codex-rules/rules/dd.ts +23 -0
  79. package/src/codex-rules/rules/diskutil.ts +33 -0
  80. package/src/codex-rules/rules/mkfs.ts +42 -0
  81. package/src/codex-rules/rules/privilege.ts +49 -0
  82. package/src/codex-rules/rules/remote-fetch.ts +31 -0
  83. package/src/codex-rules/rules/rm-variants.ts +58 -0
  84. package/src/codex-rules/rules/single-commands.ts +57 -0
  85. package/src/codex-rules/skip-list.ts +25 -0
  86. package/src/codex-rules/starlark.ts +46 -0
  87. package/src/codex-rules/types.ts +36 -0
  88. package/src/config/dotenv.ts +55 -0
  89. package/src/config/env.ts +10 -0
  90. package/src/config/home-dir.ts +36 -0
  91. package/src/config/index.ts +5 -0
  92. package/src/config/layout.ts +52 -0
  93. package/src/config/limits.ts +56 -0
  94. package/src/config/ttl.ts +30 -0
  95. package/src/detect/harness.ts +73 -0
  96. package/src/detect/index.ts +1 -0
  97. package/src/detect/interfaces/types.ts +18 -0
  98. package/src/freshness/agent-evidence-record.ts +95 -0
  99. package/src/freshness/agent-evidence.ts +95 -0
  100. package/src/freshness/codex-spawn-evidence.ts +91 -0
  101. package/src/freshness/doc-helpers.ts +90 -0
  102. package/src/freshness/evidence-harvest-io.ts +72 -0
  103. package/src/freshness/evidence-harvest.ts +61 -0
  104. package/src/freshness/explore-tools.ts +95 -0
  105. package/src/freshness/index.ts +2 -0
  106. package/src/freshness/query-framework.ts +73 -0
  107. package/src/freshness/ref-evidence.ts +49 -0
  108. package/src/freshness/ref-journal.ts +98 -0
  109. package/src/freshness/trivial-edit-counter.ts +30 -0
  110. package/src/index.ts +11 -0
  111. package/src/init/index.ts +2 -0
  112. package/src/init/run.ts +32 -0
  113. package/src/init/templates.ts +75 -0
  114. package/src/memory/gitignore.ts +14 -0
  115. package/src/memory/index.ts +3 -0
  116. package/src/memory/registry.ts +39 -0
  117. package/src/memory/session-roots.ts +99 -0
  118. package/src/memory/state.ts +58 -0
  119. package/src/policy/apex-authorization.ts +100 -0
  120. package/src/policy/apex-gates.ts +91 -0
  121. package/src/policy/apex-target-steps.ts +73 -0
  122. package/src/policy/apex-target.ts +49 -0
  123. package/src/policy/apex-task-context.ts +81 -0
  124. package/src/policy/apex.ts +94 -0
  125. package/src/policy/cartographer/build-tree.ts +69 -0
  126. package/src/policy/cartographer/describe.ts +53 -0
  127. package/src/policy/cartographer/entry.ts +38 -0
  128. package/src/policy/cartographer/frontmatter.ts +48 -0
  129. package/src/policy/cartographer/indicators.ts +34 -0
  130. package/src/policy/claude-md-context.ts +66 -0
  131. package/src/policy/conventions/index.ts +11 -0
  132. package/src/policy/conventions/interfaces.ts +71 -0
  133. package/src/policy/conventions/langs.ts +53 -0
  134. package/src/policy/conventions/query.ts +49 -0
  135. package/src/policy/conventions/react-hooks.ts +18 -0
  136. package/src/policy/conventions/store-or-query.ts +21 -0
  137. package/src/policy/conventions/stores.ts +56 -0
  138. package/src/policy/conventions/strip.ts +87 -0
  139. package/src/policy/conventions/verdict.ts +38 -0
  140. package/src/policy/creation-intent.ts +11 -0
  141. package/src/policy/deny-loop.ts +88 -0
  142. package/src/policy/design/allowed-write.ts +28 -0
  143. package/src/policy/design/content-checks.ts +45 -0
  144. package/src/policy/design/corpus-fs.ts +21 -0
  145. package/src/policy/design/corpus-resolve-cache.ts +49 -0
  146. package/src/policy/design/corpus-resolve.ts +99 -0
  147. package/src/policy/design/corpus.ts +92 -0
  148. package/src/policy/design/design-system-rules.ts +45 -0
  149. package/src/policy/design/flag.ts +30 -0
  150. package/src/policy/design/gates-pipeline.ts +105 -0
  151. package/src/policy/design/gates.ts +87 -0
  152. package/src/policy/design/screenshot-tools.ts +24 -0
  153. package/src/policy/design/skill-evidence.ts +34 -0
  154. package/src/policy/design/skill-gate.ts +81 -0
  155. package/src/policy/design/skill-triggers.ts +51 -0
  156. package/src/policy/design/state.ts +92 -0
  157. package/src/policy/design/template-urls.ts +54 -0
  158. package/src/policy/design/transitions.ts +68 -0
  159. package/src/policy/detect-claude-md-project-type.ts +32 -0
  160. package/src/policy/detect-framework.ts +99 -0
  161. package/src/policy/detect-primitive-checks.ts +100 -0
  162. package/src/policy/detect-primitive-lib.ts +55 -0
  163. package/src/policy/detect-project.ts +99 -0
  164. package/src/policy/edit-outcome.ts +53 -0
  165. package/src/policy/evaluate.ts +81 -0
  166. package/src/policy/expert-agents.ts +60 -0
  167. package/src/policy/file-size-scope.ts +32 -0
  168. package/src/policy/file-size.ts +99 -0
  169. package/src/policy/framework-solid-exclude.ts +52 -0
  170. package/src/policy/framework-solid-extended.ts +93 -0
  171. package/src/policy/framework-solid-gates-systems.ts +99 -0
  172. package/src/policy/framework-solid-gates.ts +98 -0
  173. package/src/policy/framework-solid.ts +88 -0
  174. package/src/policy/gemini-mcp-gate.ts +83 -0
  175. package/src/policy/git-gates.ts +40 -0
  176. package/src/policy/guards/bash-command-anchor.ts +40 -0
  177. package/src/policy/guards/bash-write-patterns.ts +140 -0
  178. package/src/policy/guards/bash-write-redirects.ts +201 -0
  179. package/src/policy/guards/bash-write-safe-paths.ts +62 -0
  180. package/src/policy/guards/bash-write.ts +76 -0
  181. package/src/policy/guards/context.ts +12 -0
  182. package/src/policy/guards/index.ts +61 -0
  183. package/src/policy/guards/install.ts +22 -0
  184. package/src/policy/guards/interface-separation-ext.ts +41 -0
  185. package/src/policy/guards/interface-separation.ts +100 -0
  186. package/src/policy/guards/protected-path.ts +100 -0
  187. package/src/policy/guards/security.ts +97 -0
  188. package/src/policy/index.ts +19 -0
  189. package/src/policy/interfaces/types.ts +46 -0
  190. package/src/policy/js-gate-route.ts +51 -0
  191. package/src/policy/lessons/lesson-gate.ts +0 -0
  192. package/src/policy/lessons/trigger-index.ts +84 -0
  193. package/src/policy/lessons/types.ts +31 -0
  194. package/src/policy/module-layout.ts +31 -0
  195. package/src/policy/nearest-manifest.ts +75 -0
  196. package/src/policy/never-approval.ts +95 -0
  197. package/src/policy/patterns.ts +80 -0
  198. package/src/policy/seo/validate.ts +51 -0
  199. package/src/policy/shadcn-project.ts +22 -0
  200. package/src/policy/shadcn-skill-gate.ts +97 -0
  201. package/src/policy/shell-read-refs.ts +96 -0
  202. package/src/policy/skill-path.ts +46 -0
  203. package/src/policy/skill-patterns/laravel.ts +40 -0
  204. package/src/policy/skill-patterns/nextjs.ts +29 -0
  205. package/src/policy/skill-patterns/react.ts +27 -0
  206. package/src/policy/skill-patterns/shadcn-subskills.ts +33 -0
  207. package/src/policy/skill-patterns/shadcn.ts +66 -0
  208. package/src/policy/skill-patterns/swift.ts +30 -0
  209. package/src/policy/skill-patterns/tailwind.ts +40 -0
  210. package/src/policy/skill-source.ts +52 -0
  211. package/src/policy/skill-trigger-patterns.ts +33 -0
  212. package/src/policy/skill-triggers.ts +108 -0
  213. package/src/policy/tailwind-skill-gate.ts +63 -0
  214. package/src/policy/trivial-edits.ts +52 -0
  215. package/src/policy/verbosity.ts +39 -0
  216. package/src/prompt/index.ts +1 -0
  217. package/src/prompt/types.ts +36 -0
  218. package/src/refs/discover.ts +69 -0
  219. package/src/refs/frontmatter.ts +24 -0
  220. package/src/refs/index.ts +4 -0
  221. package/src/refs/loader.ts +59 -0
  222. package/src/refs/ref-key.ts +41 -0
  223. package/src/refs/router.ts +63 -0
  224. package/src/refs/types.ts +25 -0
  225. package/src/runtime/activity.ts +99 -0
  226. package/src/runtime/apply-patch-gate.ts +41 -0
  227. package/src/runtime/burst-window.ts +21 -0
  228. package/src/runtime/codex-shell-tool.ts +37 -0
  229. package/src/runtime/command-string.ts +37 -0
  230. package/src/runtime/confirm/codex-confirm.ts +126 -0
  231. package/src/runtime/confirm/codex-prompt-origin.ts +16 -0
  232. package/src/runtime/confirm/confirm-code.ts +33 -0
  233. package/src/runtime/confirm/confirm-gate.ts +65 -0
  234. package/src/runtime/confirm/confirm-irreversible.ts +32 -0
  235. package/src/runtime/confirm/confirm-pending.ts +34 -0
  236. package/src/runtime/confirm/confirm-state.ts +56 -0
  237. package/src/runtime/confirm/confirm-subagent.ts +112 -0
  238. package/src/runtime/confirm/confirm-submit.ts +38 -0
  239. package/src/runtime/core-guards-active.ts +84 -0
  240. package/src/runtime/core-guards-wired.ts +47 -0
  241. package/src/runtime/deny-loop-store.ts +94 -0
  242. package/src/runtime/deny-notice.ts +52 -0
  243. package/src/runtime/design-cache-resolve.ts +51 -0
  244. package/src/runtime/design-content-gate.ts +95 -0
  245. package/src/runtime/design-files-gate.ts +114 -0
  246. package/src/runtime/design-helpers.ts +93 -0
  247. package/src/runtime/design-lifecycle.ts +39 -0
  248. package/src/runtime/design-read-credit.ts +28 -0
  249. package/src/runtime/design.ts +116 -0
  250. package/src/runtime/dev-context.ts +43 -0
  251. package/src/runtime/dry-patterns.ts +32 -0
  252. package/src/runtime/dry.ts +96 -0
  253. package/src/runtime/fragment-registry.ts +33 -0
  254. package/src/runtime/framework-skill-gate.ts +79 -0
  255. package/src/runtime/fs-cleanup.ts +71 -0
  256. package/src/runtime/gate-apex.ts +81 -0
  257. package/src/runtime/gate-helpers.ts +52 -0
  258. package/src/runtime/gate-input.ts +26 -0
  259. package/src/runtime/gate.ts +128 -0
  260. package/src/runtime/handle-post.ts +104 -0
  261. package/src/runtime/handle-pre.ts +137 -0
  262. package/src/runtime/handle-scope-async.ts +29 -0
  263. package/src/runtime/handle-types.ts +27 -0
  264. package/src/runtime/handle.ts +116 -0
  265. package/src/runtime/home-state.ts +55 -0
  266. package/src/runtime/index.ts +18 -0
  267. package/src/runtime/inform.ts +28 -0
  268. package/src/runtime/inject-budget-recap.ts +47 -0
  269. package/src/runtime/inject-budget.ts +81 -0
  270. package/src/runtime/inject-context.ts +85 -0
  271. package/src/runtime/inject-dedup-exclusive.ts +98 -0
  272. package/src/runtime/inject-dedup.ts +89 -0
  273. package/src/runtime/is-agent-tool.ts +19 -0
  274. package/src/runtime/lifecycle/agent-files.ts +49 -0
  275. package/src/runtime/lifecycle/agent-memory.ts +82 -0
  276. package/src/runtime/lifecycle/agent-transcript.ts +80 -0
  277. package/src/runtime/lifecycle/aipilot/analytics.ts +99 -0
  278. package/src/runtime/lifecycle/aipilot/apex-task-store.ts +61 -0
  279. package/src/runtime/lifecycle/aipilot/cache-base.ts +48 -0
  280. package/src/runtime/lifecycle/aipilot/cache-doc.ts +100 -0
  281. package/src/runtime/lifecycle/aipilot/cache-lessons.ts +58 -0
  282. package/src/runtime/lifecycle/aipilot/cache-test.ts +70 -0
  283. package/src/runtime/lifecycle/aipilot/curate-lessons.ts +64 -0
  284. package/src/runtime/lifecycle/aipilot/dispatch-aipilot.ts +100 -0
  285. package/src/runtime/lifecycle/aipilot/doc-cache-gate.ts +69 -0
  286. package/src/runtime/lifecycle/aipilot/inject-apex.ts +98 -0
  287. package/src/runtime/lifecycle/aipilot/inject-doc.ts +62 -0
  288. package/src/runtime/lifecycle/aipilot/inject-explore.ts +64 -0
  289. package/src/runtime/lifecycle/aipilot/inject-lessons.ts +61 -0
  290. package/src/runtime/lifecycle/aipilot/inject-test.ts +48 -0
  291. package/src/runtime/lifecycle/aipilot/lesson-archive.ts +60 -0
  292. package/src/runtime/lifecycle/aipilot/lesson-inject.ts +98 -0
  293. package/src/runtime/lifecycle/aipilot/lesson-parse.ts +74 -0
  294. package/src/runtime/lifecycle/aipilot/lessons.ts +72 -0
  295. package/src/runtime/lifecycle/aipilot/promote-global-lessons.ts +92 -0
  296. package/src/runtime/lifecycle/aipilot/solid-compliance.ts +69 -0
  297. package/src/runtime/lifecycle/aipilot/solid-notice.ts +47 -0
  298. package/src/runtime/lifecycle/aipilot/solid-transcript.ts +60 -0
  299. package/src/runtime/lifecycle/aipilot/source-scan.ts +56 -0
  300. package/src/runtime/lifecycle/aipilot/sync-task.ts +75 -0
  301. package/src/runtime/lifecycle/aipilot/transcript.ts +88 -0
  302. package/src/runtime/lifecycle/aipilot/types.ts +72 -0
  303. package/src/runtime/lifecycle/auto-document-reads.ts +73 -0
  304. package/src/runtime/lifecycle/cartographer/detect.ts +57 -0
  305. package/src/runtime/lifecycle/cartographer/ecosystem-map.ts +56 -0
  306. package/src/runtime/lifecycle/cartographer/fs-util.ts +64 -0
  307. package/src/runtime/lifecycle/cartographer/merge.ts +51 -0
  308. package/src/runtime/lifecycle/cartographer/project-map.ts +49 -0
  309. package/src/runtime/lifecycle/cartographer/scan-hooks.ts +48 -0
  310. package/src/runtime/lifecycle/cartographer/scan.ts +81 -0
  311. package/src/runtime/lifecycle/cartographer/session-start.ts +33 -0
  312. package/src/runtime/lifecycle/cartographer/track-enrichment.ts +45 -0
  313. package/src/runtime/lifecycle/cartographer/write-plugin-map.ts +43 -0
  314. package/src/runtime/lifecycle/cartographer/write-tree.ts +44 -0
  315. package/src/runtime/lifecycle/changelog-research.ts +43 -0
  316. package/src/runtime/lifecycle/check-file-size.ts +61 -0
  317. package/src/runtime/lifecycle/codex-resync/fingerprint.ts +63 -0
  318. package/src/runtime/lifecycle/codex-resync/lock.ts +46 -0
  319. package/src/runtime/lifecycle/codex-resync/managed-destination.ts +39 -0
  320. package/src/runtime/lifecycle/codex-resync/materialize-agents.ts +57 -0
  321. package/src/runtime/lifecycle/codex-resync/plugin-files.ts +65 -0
  322. package/src/runtime/lifecycle/codex-resync/plugin-roots.ts +62 -0
  323. package/src/runtime/lifecycle/codex-resync/resync.ts +45 -0
  324. package/src/runtime/lifecycle/codex-resync/symlink-commands.ts +25 -0
  325. package/src/runtime/lifecycle/dispatch.ts +109 -0
  326. package/src/runtime/lifecycle/failure-lesson.ts +51 -0
  327. package/src/runtime/lifecycle/index.ts +34 -0
  328. package/src/runtime/lifecycle/inject-rules.ts +50 -0
  329. package/src/runtime/lifecycle/instructions-loaded.ts +22 -0
  330. package/src/runtime/lifecycle/kimi-rules-native.ts +44 -0
  331. package/src/runtime/lifecycle/lessons/dispatch.ts +88 -0
  332. package/src/runtime/lifecycle/lessons/reminder.ts +89 -0
  333. package/src/runtime/lifecycle/lessons/state.ts +23 -0
  334. package/src/runtime/lifecycle/memory/agent-lesson.ts +35 -0
  335. package/src/runtime/lifecycle/memory/capture-error.ts +41 -0
  336. package/src/runtime/lifecycle/memory/client.ts +65 -0
  337. package/src/runtime/lifecycle/memory/dispatch.ts +42 -0
  338. package/src/runtime/lifecycle/memory/recall.ts +30 -0
  339. package/src/runtime/lifecycle/memory/salience.ts +31 -0
  340. package/src/runtime/lifecycle/memory/state.ts +49 -0
  341. package/src/runtime/lifecycle/memory/track-ops.ts +15 -0
  342. package/src/runtime/lifecycle/post-compact.ts +38 -0
  343. package/src/runtime/lifecycle/post-edit-ts.ts +49 -0
  344. package/src/runtime/lifecycle/post-tracking.ts +40 -0
  345. package/src/runtime/lifecycle/pre-compact.ts +38 -0
  346. package/src/runtime/lifecycle/rules-root.ts +83 -0
  347. package/src/runtime/lifecycle/security/check-skill.ts +63 -0
  348. package/src/runtime/lifecycle/security/scan-patterns.ts +53 -0
  349. package/src/runtime/lifecycle/security/scan.ts +76 -0
  350. package/src/runtime/lifecycle/security/skill-state.ts +47 -0
  351. package/src/runtime/lifecycle/security/track-mcp.ts +29 -0
  352. package/src/runtime/lifecycle/security/track-skill-read.ts +31 -0
  353. package/src/runtime/lifecycle/seo/post-tool-use.ts +45 -0
  354. package/src/runtime/lifecycle/session-end.ts +17 -0
  355. package/src/runtime/lifecycle/session-start.ts +57 -0
  356. package/src/runtime/lifecycle/snapshot/board.ts +27 -0
  357. package/src/runtime/lifecycle/snapshot/format.ts +60 -0
  358. package/src/runtime/lifecycle/snapshot/git.ts +71 -0
  359. package/src/runtime/lifecycle/snapshot/index.ts +52 -0
  360. package/src/runtime/lifecycle/snapshot/version.ts +34 -0
  361. package/src/runtime/lifecycle/solid-detect.ts +56 -0
  362. package/src/runtime/lifecycle/stop-core.ts +35 -0
  363. package/src/runtime/lifecycle/subagent-cache.ts +61 -0
  364. package/src/runtime/lifecycle/task-completed.ts +94 -0
  365. package/src/runtime/lifecycle/teammate-idle-check.ts +63 -0
  366. package/src/runtime/lifecycle/teammate-idle.ts +28 -0
  367. package/src/runtime/lifecycle/tool-failure.ts +24 -0
  368. package/src/runtime/lifecycle/track-changes.ts +61 -0
  369. package/src/runtime/lifecycle/validate-solid.ts +70 -0
  370. package/src/runtime/lifecycle/validate-tailwind.ts +80 -0
  371. package/src/runtime/lifecycle-bridge.ts +74 -0
  372. package/src/runtime/mcp-key.ts +44 -0
  373. package/src/runtime/mcp-tool-name.ts +92 -0
  374. package/src/runtime/mcp.ts +91 -0
  375. package/src/runtime/modular.ts +77 -0
  376. package/src/runtime/normalize.ts +107 -0
  377. package/src/runtime/notices.ts +91 -0
  378. package/src/runtime/notification-sound.ts +49 -0
  379. package/src/runtime/notifications.ts +77 -0
  380. package/src/runtime/paths.ts +59 -0
  381. package/src/runtime/post-fanout.ts +41 -0
  382. package/src/runtime/post-outcome.ts +61 -0
  383. package/src/runtime/pre-allow.ts +97 -0
  384. package/src/runtime/precommit.ts +60 -0
  385. package/src/runtime/prompt-text.ts +38 -0
  386. package/src/runtime/receipt-capture.ts +34 -0
  387. package/src/runtime/record.ts +26 -0
  388. package/src/runtime/respond.ts +92 -0
  389. package/src/runtime/solid-file-size-gate.ts +58 -0
  390. package/src/runtime/solid-pre.ts +53 -0
  391. package/src/runtime/storage.ts +9 -0
  392. package/src/statusline/colors.ts +31 -0
  393. package/src/statusline/constants.ts +17 -0
  394. package/src/statusline/formatters.ts +54 -0
  395. package/src/statusline/index.ts +4 -0
  396. package/src/statusline/progress-bar.ts +46 -0
  397. package/src/tracking/codex-post-failure.ts +73 -0
  398. package/src/tracking/index.ts +2 -0
  399. package/src/tracking/integrity.ts +99 -0
  400. package/src/tracking/one-shot-dedup.ts +34 -0
  401. package/src/tracking/one-shot-failure.ts +42 -0
  402. package/src/tracking/one-shot-store.ts +95 -0
  403. package/src/tracking/one-shot.ts +93 -0
  404. package/src/tracking/receipts.ts +95 -0
  405. package/src/tracking/session-state.ts +94 -0
  406. package/src/tracking/store.ts +96 -0
  407. package/src/tracking/track-compact.ts +100 -0
  408. package/src/tracking/track-diff.ts +50 -0
  409. package/src/tracking/track-journal.ts +97 -0
  410. package/src/tracking/track-lock-sync.ts +83 -0
  411. package/src/tracking/track-lock.ts +67 -0
  412. package/src/util/compact-json.ts +23 -0
  413. package/src/util/index.ts +2 -0
  414. package/src/util/json-io.ts +47 -0
  415. package/src/util/project-root.ts +41 -0
  416. package/src/util/runtime-io.ts +85 -0
  417. package/src/util/semver.ts +30 -0
@@ -0,0 +1,32 @@
1
+ import { detectFramework } from "./detect-framework";
2
+
3
+ /**
4
+ * File-size gate scope — parity `enforce-file-size.py` CODE_EXT (both pre- and
5
+ * post-tool-use variants; deliberately excludes `.css`, matching the product
6
+ * decision already applied to `runtime/gate-helpers.ts::isApexScoped` for the
7
+ * sibling `require-apex-agents.py`). NOT `util/project-root.ts::isCodeFile`, a
8
+ * broader general-purpose predicate whose other two callers (`cli/run.ts`,
9
+ * `lifecycle/lessons/dispatch.ts`) are unrelated to this Python source.
10
+ */
11
+ const FILE_SIZE_CODE_EXT = /\.(ts|tsx|js|jsx|py|go|rs|java|php|cpp|c|rb|swift|kt|dart|vue|svelte|astro)$/;
12
+
13
+ /** True when `filePath` is in scope for the SOLID file-size gate (see {@link FILE_SIZE_CODE_EXT}). */
14
+ export function isFileSizeScoped(filePath: string): boolean {
15
+ return FILE_SIZE_CODE_EXT.test(filePath);
16
+ }
17
+
18
+ /**
19
+ * Resolve the SOLID_REF framework key for a file — now project-real via
20
+ * {@link detectFramework} (nearest-manifest caps ∩ file signal), NOT the old
21
+ * extension-only default that mislabeled every `.ts` as react (the "twin bug" of
22
+ * the require-solid-read pipeline; it only escaped when `next.config` sat in the
23
+ * SAME dir). A backend `.ts` in a react project — or any `.ts` in this generic
24
+ * Bun repo — now resolves to "generic". Content is unavailable at this call site
25
+ * (evaluate.ts:59 passes only the path), so `.tsx`/`.jsx` still resolve via caps
26
+ * while `.ts`/`.js` lacking a content signal fall to "generic". Return values
27
+ * stay in the existing union; fail-open to "generic".
28
+ * @param filePath - Absolute path of the file being written/edited.
29
+ */
30
+ export function resolveSolidRefFramework(filePath: string): string {
31
+ return detectFramework(filePath, "");
32
+ }
@@ -0,0 +1,99 @@
1
+ import { basename } from "node:path";
2
+ import { resolveMaxLines, splitTarget } from "../config/limits";
3
+
4
+ /**
5
+ * Fixed marketplace plugins root — parity with Python `enforce-file-size.py`'s
6
+ * literal, unexpanded `~/...` string. Exported: reused by
7
+ * `policy/apex.ts::solidReadGate` for its "no reference matched" deny message.
8
+ */
9
+ export const PLUGINS_DIR = "~/.claude/plugins/marketplaces/fusengine-plugins/plugins";
10
+
11
+ /**
12
+ * Skill-dir fragment per framework — parity with Python
13
+ * `enforce-file-size.py::get_solid_ref()` (falls back to `generic/`). Exported:
14
+ * reused by `policy/apex.ts::solidReadGate` (see {@link PLUGINS_DIR}).
15
+ */
16
+ export const SOLID_REF: Record<string, string> = {
17
+ react: "react-expert/skills/solid-react/",
18
+ nextjs: "nextjs-expert/skills/solid-nextjs/",
19
+ "tanstack-start": "tanstack-start-expert/skills/solid-tanstack-start/",
20
+ laravel: "laravel-expert/skills/solid-php/",
21
+ swift: "swift-apple-expert/skills/solid-swift/",
22
+ go: "solid/skills/solid-go/",
23
+ rust: "solid/skills/solid-rust/",
24
+ };
25
+
26
+ /** Verdict from {@link evaluateFileSize}. */
27
+ export interface FileSizeVerdict {
28
+ ok: boolean;
29
+ lines: number;
30
+ max: number;
31
+ message: string | null;
32
+ }
33
+
34
+ /**
35
+ * Count physical lines — parity with the Python `enforce-file-size.py`
36
+ * (`sum(1 for _ in f)`): every line counts (blanks and comments included), and a
37
+ * single trailing newline does not add a phantom line. The SOLID ceiling is
38
+ * measured on raw file length, not substantive code, to match the upstream plugin.
39
+ */
40
+ export function countLines(content: string): number {
41
+ if (content === "") return 0;
42
+ return content.split("\n").length - (content.endsWith("\n") ? 1 : 0);
43
+ }
44
+
45
+ /**
46
+ * Count non-empty, non-comment lines — parity with the Python `count_code_lines`
47
+ * shared by the framework-specific SOLID validators: `_shared/scripts/validate_solid_common.py`
48
+ * (imported by `nextjs-expert/scripts/validate-nextjs-solid.py` and
49
+ * `swift-apple-expert/scripts/validate-swift-solid.py`), duplicated verbatim in
50
+ * `react-expert/scripts/validate-react-solid.py` / `laravel-expert/scripts/validate-laravel-solid.py`.
51
+ * Strips blank lines and lines starting with `//` or `*` — a SINGLE fixed rule
52
+ * for all 4 callers (the Python `comment` param defaults to, and every real
53
+ * call site leaves it at, `"//"` — never per-language despite covering
54
+ * ts/tsx/js/jsx, php and swift).
55
+ *
56
+ * Deliberately distinct from two other "code-only" counters already in this
57
+ * repo, neither of which is a faithful substitute here:
58
+ * - `countLoc` (`runtime/lifecycle/check-file-size.ts`): a genuinely
59
+ * per-language table (PHP additionally strips `#`, Python strips
60
+ * `#`/`"""`/`'''`) — ported from the unrelated `solid/scripts/check-file-size.py`.
61
+ * - `countCodeLines` (`runtime/lifecycle/aipilot/solid-compliance.ts`): also
62
+ * strips `#` — ported from `ai-pilot/scripts/check-solid-compliance.py`.
63
+ * Reusing either would silently strip PHP `#`/Python-style comments that the
64
+ * real react/nextjs/laravel/swift validators do NOT strip.
65
+ * @param content - The file content to measure.
66
+ */
67
+ export function countFrameworkCodeLines(content: string): number {
68
+ let count = 0;
69
+ for (const raw of content.split("\n")) {
70
+ const line = raw.trim();
71
+ if (!line || line.startsWith("//") || line.startsWith("*")) continue;
72
+ count++;
73
+ }
74
+ return count;
75
+ }
76
+
77
+ /**
78
+ * Evaluate a file's line count against the SOLID limit.
79
+ * @param lines - the file's line count
80
+ * @param max - the limit (defaults to `resolveMaxLines()`)
81
+ */
82
+ export function evaluateFileSize(
83
+ lines: number,
84
+ max: number = resolveMaxLines(),
85
+ filePath = "",
86
+ framework = "generic",
87
+ displayLines: number = lines,
88
+ ): FileSizeVerdict {
89
+ if (lines <= max) return { ok: true, lines, max, message: null };
90
+ const split = splitTarget(max);
91
+ const fname = filePath ? basename(filePath) : "file";
92
+ const ref = SOLID_REF[framework] ?? "generic/";
93
+ return {
94
+ ok: false,
95
+ lines,
96
+ max,
97
+ message: `BLOCKED: '${fname}' has ${displayLines} lines (max: ${max}). TO SPLIT: 1) Read SOLID rules: ${PLUGINS_DIR}/${ref} 2) Create new module files (<${split} lines each) 3) Use Write to replace '${fname}' with <${max} lines version.`,
98
+ };
99
+ }
@@ -0,0 +1,52 @@
1
+ /** Build/dep paths excluded from React/Next.js SOLID gating — segment-anchored (parity `r"/(node_modules|dist|build|\.next)/"`), NOT a bare substring, so `distance.ts`/`rebuild/` stay gated. */
2
+ const JS_EXCLUDE_RE: RegExp = /(^|\/)(node_modules|dist|build|\.next)(\/|$)/;
3
+ /** Vendored dependency paths excluded from Laravel/PHP SOLID gating. */
4
+ const PHP_EXCLUDE_RE: RegExp = /\/vendor\//;
5
+ /** Derived/build artifact paths excluded from Swift SOLID gating. */
6
+ const SWIFT_EXCLUDE_RE: RegExp = /(\.build|DerivedData|Pods)/;
7
+
8
+ /**
9
+ * Whether a JS/TS (React/Next.js) file path is an excluded build artifact.
10
+ * Matches the Python validators' early-return guard to avoid false positives.
11
+ * @param filePath - absolute path of the file under validation
12
+ */
13
+ export function isExcludedJsPath(filePath: string): boolean {
14
+ return JS_EXCLUDE_RE.test(filePath);
15
+ }
16
+
17
+ /**
18
+ * Whether a PHP (Laravel) file path is a vendored dependency to skip.
19
+ * @param filePath - absolute path of the file under validation
20
+ */
21
+ export function isExcludedPhpPath(filePath: string): boolean {
22
+ return PHP_EXCLUDE_RE.test(filePath);
23
+ }
24
+
25
+ /**
26
+ * Whether a Swift file path is a derived/build artifact to skip.
27
+ * @param filePath - absolute path of the file under validation
28
+ */
29
+ export function isExcludedSwiftPath(filePath: string): boolean {
30
+ return SWIFT_EXCLUDE_RE.test(filePath);
31
+ }
32
+
33
+ /**
34
+ * This harness's own gate-detection source files. They hold the React/Next
35
+ * detection literals as regex source text (client-directive string, hook/
36
+ * event names, store/query/route signals) — scanning them as JS/TS would
37
+ * self-match the very heuristics they define. Exempt by exact filename or by
38
+ * the conventions/ detector directory, not by broad directory, so a genuine
39
+ * SOLID violation elsewhere in src/policy/ stays gated by the
40
+ * framework-agnostic line-size check in policy/evaluate.ts, untouched.
41
+ */
42
+ const SELF_GATE_EXCLUDE_RE: RegExp = /(^|\/)src\/policy\/(framework-solid-gates(-systems)?\.ts|framework-solid-extended\.ts|detect-framework\.ts|conventions\/[^/]+\.ts|guards\/interface-separation-ext\.ts)$/;
43
+
44
+ /**
45
+ * Whether a JS/TS file path is this harness's own gate-detection source —
46
+ * exempt from the pattern heuristics (not the line-size ceiling) to avoid
47
+ * self-blocking on regex literals that look like the code they detect.
48
+ * @param filePath - absolute path of the file under validation
49
+ */
50
+ export function isSelfGateSourcePath(filePath: string): boolean {
51
+ return SELF_GATE_EXCLUDE_RE.test(filePath);
52
+ }
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Extended convention violations — the NEW rules of the conventions module
3
+ * (owner Amendments 3-5), all advisory-first: the caller wraps them with
4
+ * {@link rolloutVerdict}, so they surface as non-blocking `inform` until
5
+ * `FUSE_CONVENTIONS_MODE=deny`. Only fires when the legacy gates found
6
+ * nothing (no double-reporting of cases the legacy reactGate already blocks).
7
+ *
8
+ * Rules: (1) custom hook outside `hooks/` — widened to `export default`/
9
+ * `async` syntaxes and to the nextjs/tanstack family (legacy covered react
10
+ * only); (2) hook file over its line budget — `hookBudget`, ratio 0.3 of the
11
+ * global `FUSE_SOLID_MAX_LINES` limit (default 30; one variable drives every
12
+ * budget, owner decision — no per-file-kind env vars); (3) TanStack Start
13
+ * route files kept routing-only (no interface declarations in `src/routes/`);
14
+ * (4) store declared outside `stores/` or over `storeBudget` (ratio 0.4);
15
+ * (5) TanStack Query hook defined in a component/page (only when the
16
+ * `@tanstack/*-query` cap is detected in the nearest manifest — `query/` or
17
+ * `hooks/` are both accepted homes); (6) component file misplaced inside
18
+ * `src/hooks|stores|query|interfaces|types/` (deterministic case only).
19
+ */
20
+ import { isHooksPath, isStoresPath, isComponentsPath } from "./conventions/langs";
21
+ import { maskCommentsAndStrings } from "./conventions/strip";
22
+ import { declaresCustomHook } from "./conventions/react-hooks";
23
+ import { declaresInterface } from "./conventions/interfaces";
24
+ import { declaresStore } from "./conventions/stores";
25
+ import { declaresQueryHook, queryCapActive } from "./conventions/query";
26
+ import { isStoreOrQueryDefinition } from "./conventions/store-or-query";
27
+ import { countFrameworkCodeLines } from "./file-size";
28
+ import { hookBudget, resolveMaxLines, storeBudget } from "../config/limits";
29
+
30
+ /** TanStack Start content signal (file routes, server fns, start/router imports). */
31
+ const TANSTACK_RE = /createFileRoute|createServerFn|from ['"]@tanstack\/react-(start|router)/;
32
+ /** A custom-hook source file under a hooks directory. */
33
+ const HOOK_FILE_RE = /(^|\/)use[A-Z][^/]*\.(ts|tsx|js|jsx)$/;
34
+
35
+ /**
36
+ * Collect the extended convention violations for a JS/TS-family file.
37
+ * @param filePath - The written/edited file's path.
38
+ * @param content - Its raw content (masked internally).
39
+ * @returns The violation messages (empty when clean or out of scope).
40
+ */
41
+ export function extendedViolations(filePath: string, content: string): string[] {
42
+ const v: string[] = [];
43
+ if (!/\.(tsx|ts|jsx|js|vue)$/.test(filePath)) return v;
44
+ const masked = maskCommentsAndStrings(content, "c");
45
+ // Store/query-definition files are governed by their own rules below,
46
+ // never by the hook-location rule (signature+cap-gated exemption, F0.2).
47
+ if (declaresCustomHook(content) && !isHooksPath(filePath) && !isStoreOrQueryDefinition(filePath, content)) {
48
+ v.push("Custom hook defined outside hooks/ directory. Move to modules/[feature]/src/hooks/.");
49
+ }
50
+ if (isHooksPath(filePath) && HOOK_FILE_RE.test(filePath)) {
51
+ const max = hookBudget(resolveMaxLines());
52
+ const lines = countFrameworkCodeLines(masked);
53
+ if (lines > max) v.push(`Hook file has ${lines} lines (limit: ${max}). Extract smaller hooks.`);
54
+ }
55
+ if (filePath.includes("/routes/") && TANSTACK_RE.test(masked) && declaresInterface(filePath, content)) {
56
+ v.push("Route files are routing-only. Move the interface to src/modules/[feature]/src/interfaces/.");
57
+ }
58
+ // (4) Stores: location + budget (signature-strict detection — see stores.ts).
59
+ const storeLib = declaresStore(content, "zustand") ? "zustand" : declaresStore(content, "pinia") ? "pinia" : null;
60
+ if (storeLib && !isStoresPath(filePath)) {
61
+ v.push("Store defined outside stores/ directory. Move to modules/[feature]/src/stores/.");
62
+ }
63
+ if (storeLib && isStoresPath(filePath)) {
64
+ const max = storeBudget(resolveMaxLines());
65
+ const lines = countFrameworkCodeLines(masked);
66
+ if (lines > max) v.push(`Store file has ${lines} lines (limit: ${max}).`);
67
+ }
68
+ // (5) TanStack Query definition in a component/page — only with the cap.
69
+ if (isComponentOrPagePath(filePath) && declaresQueryHook(content) && queryCapActive(filePath)) {
70
+ v.push("Query hook defined outside query/ directory. Move to modules/[feature]/src/query/.");
71
+ }
72
+ // (6) Component misplaced inside a non-component src/ subdirectory.
73
+ if (/\.(tsx|vue)$/.test(filePath)) {
74
+ const m = /\/src\/(hooks|stores|query|interfaces|types)\//.exec(filePath);
75
+ if (m && declaresComponent(masked)) {
76
+ v.push(`Component does not belong in ${m[1]}/. Move to modules/[feature]/components/.`);
77
+ }
78
+ }
79
+ return v;
80
+ }
81
+
82
+ /** Component/page directories where a store or query DEFINITION is a violation. */
83
+ function isComponentOrPagePath(filePath: string): boolean {
84
+ return isComponentsPath(filePath) || /\/(pages|app|routes)\//.test(filePath);
85
+ }
86
+
87
+ /** True when the file declares an exported component (function/const, Vue SFC
88
+ * `export default {}` or `defineComponent(` included — audit F0 option). */
89
+ function declaresComponent(masked: string): boolean {
90
+ return /^\s*export\s+(?:default\s+)?(?:function|const)\s+[A-Z]\w*/m.test(masked)
91
+ || /^\s*export\s+default\s*\{/m.test(masked)
92
+ || /defineComponent\s*\(/.test(masked);
93
+ }
@@ -0,0 +1,99 @@
1
+ import { resolveMaxLines } from "../config/limits";
2
+ import { countFrameworkCodeLines } from "./file-size";
3
+ import { moduleAwarePath } from "./module-layout";
4
+ import { maskCommentsAndStrings } from "./conventions/strip";
5
+
6
+ /**
7
+ * Systems-language SOLID gates (Go, Rust). Split out of `framework-solid-gates.ts`
8
+ * to stay under the SOLID line ceiling — the JS/framework gates already sit
9
+ * near it, and Go/Rust share no logic with React/Next/Laravel/Swift beyond the
10
+ * `moduleAwarePath` helper.
11
+ */
12
+
13
+ /** Go top-level `type Foo interface` declaration. */
14
+ const GO_INTERFACE_DECL_RE: RegExp = /^type\s+[A-Z]\w*\s+interface\b/m;
15
+ /** Go method with a (pointer) receiver: `func (r *Foo) Bar(...)` — receiver type may be unexported (`repo`), unlike the interface it implements. */
16
+ const GO_METHOD_RE: RegExp = /^func\s*\(\s*\w+\s+\*?[A-Za-z]\w*\)\s+\w+/m;
17
+ /** Rust top-level `trait Foo` declaration (captures the name for impl co-location). */
18
+ const RUST_TRAIT_DECL_RE: RegExp = /^(pub(\(\w+\))?\s+)?trait\s+([A-Z]\w*)/m;
19
+ /** Rust top-level `struct`/`enum`/`trait` declaration. */
20
+ const RUST_TYPE_DECL_RE: RegExp = /^(pub\s+)?(struct|enum|trait)\s+[A-Z]/m;
21
+ /** Rust top-level `impl` block. */
22
+ const RUST_IMPL_RE: RegExp = /^(pub\s+)?impl\b/m;
23
+ /** Rust `unsafe fn/impl/trait/{` usage. */
24
+ const RUST_UNSAFE_RE: RegExp = /\bunsafe\s*(fn|impl|trait|\{)/;
25
+ /** `// SAFETY:` justification comment (case-insensitive). */
26
+ const RUST_SAFETY_COMMENT_RE: RegExp = /\/\/\s*SAFETY:/i;
27
+ /** Rust entry-point file: `main.rs`. */
28
+ const RUST_MAIN_RE: RegExp = /(^|\/)main\.rs$/;
29
+ /** Rust `src/bin/*.rs` binary target. */
30
+ const RUST_BIN_RE: RegExp = /\/bin\/[^/]+\.rs$/;
31
+ /** A single top-level `struct`/`enum` declaration (used for the clap-Args exemption). */
32
+ const RUST_STRUCT_ENUM_RE: RegExp = /^(pub\s+)?(struct|enum)\s+[A-Z]\w*/gm;
33
+ /** clap `#[derive(...Parser...)]` attribute. */
34
+ const RUST_DERIVE_PARSER_RE: RegExp = /#\[derive\([^)]*Parser[^)]*\)\]/;
35
+
36
+ /** Go: line limit, interface co-located with its (pointer-receiver) implementation. */
37
+ export function goGate(filePath: string, content: string, fileLines?: number): string[] {
38
+ const v: string[] = [];
39
+ content = maskCommentsAndStrings(content, "c");
40
+ const max = resolveMaxLines();
41
+ const lines = fileLines ?? countFrameworkCodeLines(content);
42
+ if (lines > max) {
43
+ const dest = moduleAwarePath(filePath, "{services,handlers}.go", "an internal/ package by responsibility");
44
+ v.push(`File has ${lines} lines (limit: ${max}). Split into ${dest}.`);
45
+ }
46
+ const isExempt = filePath.endsWith("_test.go") || /\/mocks?\//.test(filePath) || /\/interfaces\//.test(filePath);
47
+ if (!isExempt && GO_INTERFACE_DECL_RE.test(content) && GO_METHOD_RE.test(content)) {
48
+ const dest = moduleAwarePath(filePath, "interfaces/", "internal/interfaces/ (consumer-side, Go idiom)");
49
+ v.push(`Interface declared alongside its implementation. Move the interface to ${dest}.`);
50
+ }
51
+ return v;
52
+ }
53
+
54
+ /**
55
+ * Whether `content`'s only top-level `struct`/`enum` is a clap `#[derive(...Parser...)]`
56
+ * args struct — the god-main.rs check's sole exemption (a CLI entry point wiring
57
+ * its own arg struct is not "business logic").
58
+ */
59
+ function isClapArgsOnly(content: string): boolean {
60
+ const matches = [...content.matchAll(RUST_STRUCT_ENUM_RE)];
61
+ if (matches.length !== 1) return false;
62
+ const idx = matches[0]?.index;
63
+ if (idx === undefined) return false;
64
+ const before = content.slice(0, idx).split("\n");
65
+ for (let i = before.length - 1, checked = 0; i >= 0 && checked < 3; i--) {
66
+ const line = (before[i] ?? "").trim();
67
+ if (!line) continue;
68
+ checked++;
69
+ return RUST_DERIVE_PARSER_RE.test(line);
70
+ }
71
+ return false;
72
+ }
73
+
74
+ /** Rust: line limit, trait co-location, god-main.rs, unsafe without `// SAFETY:`. */
75
+ export function rustGate(filePath: string, content: string, fileLines?: number): string[] {
76
+ const v: string[] = [];
77
+ const raw = content;
78
+ content = maskCommentsAndStrings(content, "c");
79
+ const max = resolveMaxLines();
80
+ const lines = fileLines ?? countFrameworkCodeLines(content);
81
+ if (lines > max) {
82
+ const dest = moduleAwarePath(filePath, "{...}.rs", "mod.rs + submodules");
83
+ v.push(`File has ${lines} lines (limit: ${max}). Split into ${dest}.`);
84
+ }
85
+ if (!/\/(traits|interfaces)\//.test(filePath) && !filePath.endsWith("traits.rs")) {
86
+ const m = RUST_TRAIT_DECL_RE.exec(content);
87
+ const name = m?.[3];
88
+ if (name && new RegExp(`impl\\s+${name}\\s+for\\s+\\w+`).test(content)) {
89
+ const dest = moduleAwarePath(filePath, "traits.rs", "a dedicated traits.rs / interfaces module");
90
+ v.push(`Trait declared alongside its implementation. Move the trait to ${dest}.`);
91
+ }
92
+ }
93
+ const isMain = RUST_MAIN_RE.test(filePath) || RUST_BIN_RE.test(filePath);
94
+ if (isMain && (RUST_TYPE_DECL_RE.test(content) || RUST_IMPL_RE.test(content)) && !isClapArgsOnly(content))
95
+ v.push("Entry point contains business logic — keep main.rs to wiring only (parse args, init, call lib.rs/app::run()).");
96
+ if (RUST_UNSAFE_RE.test(content) && !RUST_SAFETY_COMMENT_RE.test(raw))
97
+ v.push("unsafe without a // SAFETY: comment. Justify the invariant above each unsafe block.");
98
+ return v;
99
+ }
@@ -0,0 +1,98 @@
1
+ import { resolveMaxLines } from "../config/limits";
2
+ import { countFrameworkCodeLines } from "./file-size";
3
+ import { moduleAwarePath } from "./module-layout";
4
+ import { maskCommentsAndStrings, maskCommentsOnly } from "./conventions/strip";
5
+ import { isStoreOrQueryDefinition } from "./conventions/store-or-query";
6
+
7
+ /** Custom hook export (React): `export function/const use[A-Z]`. */
8
+ const HOOK_RE: RegExp = /^export (function|const) use[A-Z]/m;
9
+ /** Top-level TS interface/type declaration. */
10
+ const TS_DECL_RE: RegExp = /^(export )?(interface|type) [A-Z]/m;
11
+ /** Client-only React hooks that require the `'use client'` directive. */
12
+ const CLIENT_HOOK_RE: RegExp = /\b(useState|useEffect|useRef|onClick|onChange)\b/;
13
+ /** PHP top-level `interface` declaration. */
14
+ const PHP_INTERFACE_RE: RegExp = /^interface /m;
15
+ /** Swift top-level `protocol` declaration. */
16
+ const SWIFT_PROTOCOL_RE: RegExp = /^protocol /m;
17
+ /** Swift type declaration (`class`/`struct`) opening a body. */
18
+ const SWIFT_TYPE_RE: RegExp = /^(class|struct) [^\n{]* \{/m;
19
+
20
+ /** React: line limit, interface separation, custom hooks under `/hooks/`. */
21
+ export function reactGate(filePath: string, content: string, fileLines?: number): string[] {
22
+ const v: string[] = [];
23
+ const raw = content; // store/query detectors probe raw imports (F0.1)
24
+ content = maskCommentsAndStrings(content, "c");
25
+ const max = resolveMaxLines();
26
+ const lines = fileLines ?? countFrameworkCodeLines(content);
27
+ if (lines > max) v.push(`File has ${lines} lines (limit: ${max}). Split to hooks/, components/, or utils/.`);
28
+ if (filePath.includes("/components/") && TS_DECL_RE.test(content)) {
29
+ const dest = moduleAwarePath(filePath, "src/interfaces/", "src/interfaces/ or src/types/");
30
+ v.push(`Interface/type in component. Move to ${dest}.`);
31
+ }
32
+ // Store/query-definition files are NOT hooks: the legacy hook rule would
33
+ // hard-deny `useXStore`/`useUsers` into hooks/, shadowing the store/query
34
+ // conventions (owner E1/E5). The exemption itself is signature+cap gated
35
+ // (owner F0.2): no real import / no `@tanstack/*-query` dep → hook rule
36
+ // fires as before, byte-identical.
37
+ if (HOOK_RE.test(content) && !filePath.includes("/hooks/") && !isStoreOrQueryDefinition(filePath, raw))
38
+ v.push("Custom hook defined outside hooks/ directory. Move to hooks/.");
39
+ return v;
40
+ }
41
+
42
+ /** Next.js: adaptive line limit, interface separation, `'use client'`. */
43
+ export function nextGate(filePath: string, content: string, fileLines?: number): string[] {
44
+ const v: string[] = [];
45
+ const raw = content; // the directive is a string literal — masking blanks it (F1.1)
46
+ content = maskCommentsAndStrings(content, "c");
47
+ const base = resolveMaxLines();
48
+ const max = /(page|layout|loading|error|not-found)\.(tsx|ts)$/.test(filePath) ? base + 50 : base;
49
+ const lines = fileLines ?? countFrameworkCodeLines(content);
50
+ if (lines > max) v.push(`File has ${lines} lines (limit: ${max}). Split to lib/, hooks/, or components/.`);
51
+ if (/\/(app|components|modules)\//.test(filePath) && !filePath.includes("/interfaces/") && TS_DECL_RE.test(content)) {
52
+ const dest = moduleAwarePath(filePath, "src/interfaces/", "src/interfaces/");
53
+ v.push(`Interface/type in component. Move to ${dest}.`);
54
+ }
55
+ if (CLIENT_HOOK_RE.test(content)) {
56
+ // Directive check on comment-masked content only: a `// "use client"`
57
+ // comment is NOT a directive, but the real string literal stays readable.
58
+ const head = maskCommentsOnly(raw, "c").split("\n").slice(0, 5).join("\n");
59
+ if (!head.includes("'use client'") && !head.includes('"use client"'))
60
+ v.push("Client hooks detected but 'use client' directive missing at top.");
61
+ }
62
+ return v;
63
+ }
64
+
65
+ /** Laravel/PHP: line limit, interface outside `/Contracts/`, fat controller (>80). */
66
+ export function laravelGate(filePath: string, content: string, fileLines?: number): string[] {
67
+ const v: string[] = [];
68
+ content = maskCommentsAndStrings(content, "php");
69
+ const lines = fileLines ?? countFrameworkCodeLines(content);
70
+ const max = resolveMaxLines();
71
+ if (lines > max) v.push(`File has ${lines} lines (limit: ${max}). Split using Services, Actions, or Traits.`);
72
+ if (PHP_INTERFACE_RE.test(content) && !filePath.includes("/Contracts/")) {
73
+ const dest = moduleAwarePath(filePath, "Contracts/", "app/Contracts/ or FuseCore/{Module}/App/Contracts/");
74
+ v.push(`Interface defined outside Contracts/. Move to ${dest}.`);
75
+ }
76
+ if (filePath.includes("/Controllers/") && lines > 80)
77
+ v.push(`Fat controller (${lines} lines). Extract logic to Services or Actions.`);
78
+ return v;
79
+ }
80
+
81
+ /** Swift: adaptive limit, protocol separation, @MainActor, Sendable. */
82
+ export function swiftGate(filePath: string, content: string, fileLines?: number): string[] {
83
+ const v: string[] = [];
84
+ content = maskCommentsAndStrings(content, "c");
85
+ const base = resolveMaxLines();
86
+ const max = /(View|Screen)\.swift$/.test(filePath) ? base + 50 : base;
87
+ const lines = fileLines ?? countFrameworkCodeLines(content);
88
+ if (lines > max) v.push(`File has ${lines} lines (limit: ${max}). Extract to ViewModels, Services, or subviews.`);
89
+ if (SWIFT_PROTOCOL_RE.test(content) && !filePath.includes("/Protocols/")) {
90
+ const dest = moduleAwarePath(filePath, "Protocols/", "Protocols/ directory");
91
+ v.push(`Protocol defined outside Protocols/ directory. Move to ${dest}.`);
92
+ }
93
+ if (filePath.endsWith("ViewModel.swift") && !content.includes("@MainActor"))
94
+ v.push("ViewModel missing @MainActor annotation.");
95
+ if (SWIFT_TYPE_RE.test(content) && content.includes("async ") && !content.includes("Sendable"))
96
+ v.push("Type uses async but doesn't conform to Sendable.");
97
+ return v;
98
+ }
@@ -0,0 +1,88 @@
1
+ import type { Prompt } from "../prompt/types";
2
+ import {
3
+ isExcludedJsPath,
4
+ isExcludedPhpPath,
5
+ isExcludedSwiftPath,
6
+ isSelfGateSourcePath,
7
+ } from "./framework-solid-exclude";
8
+ import { laravelGate, nextGate, reactGate, swiftGate } from "./framework-solid-gates";
9
+ import { goGate, rustGate } from "./framework-solid-gates-systems";
10
+ import { jsGateRoute } from "./js-gate-route";
11
+ import { extendedViolations } from "./framework-solid-extended";
12
+ import { rolloutVerdict } from "./conventions/verdict";
13
+
14
+ /** Next.js detection: directive, runtime types, or a `next` import. */
15
+ const NEXT_RE: RegExp = /(use client|use server|NextRequest|NextResponse|from ['"]next)/;
16
+
17
+ /** Build a SOLID `block` prompt from one or more violation messages. */
18
+ function block(filePath: string, violations: string[]): Prompt {
19
+ return {
20
+ kind: "block",
21
+ title: "SOLID violation",
22
+ reason: `SOLID VIOLATION in ${filePath}: ${violations.join(" ")}`,
23
+ actions: violations,
24
+ };
25
+ }
26
+
27
+ /** Run the JS/TS gate (React or Next.js), honoring build-artifact exclusions. */
28
+ function jsViolations(filePath: string, content: string, fileLines?: number): string[] {
29
+ if (isExcludedJsPath(filePath) || isSelfGateSourcePath(filePath)) return [];
30
+ // H1: manifest-first routing — the PROJECT's declared framework picks the
31
+ // gate, so a marker-less client component in a Next.js app still gets the
32
+ // directive rule (content routing sent it to reactGate → runtime crash).
33
+ // No JS-framework capability (no manifest, backend, vue) → legacy NEXT_RE
34
+ // content routing, byte-identical (fixtures/sim without manifests rely on it).
35
+ const route = jsGateRoute(filePath);
36
+ if (route === "nextjs") return nextGate(filePath, content, fileLines);
37
+ if (route === "react") return reactGate(filePath, content, fileLines);
38
+ return NEXT_RE.test(content)
39
+ ? nextGate(filePath, content, fileLines)
40
+ : reactGate(filePath, content, fileLines);
41
+ }
42
+
43
+ /**
44
+ * Framework-specific SOLID gate. Dispatches by extension/path to the matching
45
+ * validator (React, Next.js, Laravel, Swift, Go, Rust) and returns a blocking
46
+ * {@link Prompt} when any BLOCKING rule fires, or `null` when clean. Excluded
47
+ * build/dependency paths (node_modules, dist, build, .next, vendor, .build,
48
+ * DerivedData, Pods) early-return `null` to avoid false positives. Go and Rust
49
+ * have no exclusion list yet (no vendor/target FPs reported) — additive only.
50
+ * @param filePath - absolute path of the file being written/edited
51
+ * @param content - the file (or new) content under validation
52
+ * @param fileLines - full on-disk line count (set on Edit so a partial
53
+ * `new_string` snippet still judges the whole file, mirroring the base
54
+ * file-size guard / Python `get_full_file_content`). Omit on Write.
55
+ */
56
+ export function frameworkSolidGate(filePath: string, content: string, fileLines?: number): Prompt | null {
57
+ if (!filePath || !content) return null;
58
+ // TanStack Router's generated route tree is never edited (Amendment 8 —
59
+ // protected artifact, like the existing protected-path guard).
60
+ if (filePath.endsWith("routeTree.gen.ts")) {
61
+ return {
62
+ kind: "block",
63
+ title: "Generated file",
64
+ reason: `SOLID VIOLATION in ${filePath}: routeTree.gen.ts is generated by TanStack Router — never edit it directly.`,
65
+ actions: ["Edit the source route files instead, then regenerate"],
66
+ };
67
+ }
68
+ let violations: string[] = [];
69
+ if (filePath.endsWith(".php")) {
70
+ if (isExcludedPhpPath(filePath)) return null;
71
+ violations = laravelGate(filePath, content, fileLines);
72
+ } else if (filePath.endsWith(".swift")) {
73
+ if (isExcludedSwiftPath(filePath)) return null;
74
+ violations = swiftGate(filePath, content, fileLines);
75
+ } else if (/\.(tsx|ts|jsx|js)$/.test(filePath)) {
76
+ violations = jsViolations(filePath, content, fileLines);
77
+ } else if (filePath.endsWith(".go")) {
78
+ violations = goGate(filePath, content, fileLines);
79
+ } else if (filePath.endsWith(".rs")) {
80
+ violations = rustGate(filePath, content, fileLines);
81
+ }
82
+ if (violations.length) return block(filePath, violations);
83
+ // Legacy gates clean: the NEW convention rules surface advisory-first —
84
+ // same exclusions as the JS path (build artifacts + self-gate sources).
85
+ if (!/\.(tsx|ts|jsx|js|vue)$/.test(filePath) || isExcludedJsPath(filePath) || isSelfGateSourcePath(filePath)) return null;
86
+ const extended = extendedViolations(filePath, content);
87
+ return extended.length ? rolloutVerdict(block(filePath, extended), true) : null;
88
+ }
@@ -0,0 +1,83 @@
1
+ /**
2
+ * @module gemini-mcp-gate
3
+ * OPT-IN gate porting core-guards `enforce-gemini-mcp.py`: when enabled via
4
+ * `FUSE_ENFORCE_GEMINI_MCP`, blocks a UI write (`.tsx/.jsx/.vue/.svelte`) that
5
+ * hand-writes >= 3 Tailwind utility classes unless a `mcp__gemini-design__*`
6
+ * call was made this session. DISABLED BY DEFAULT — the flag is read fresh on
7
+ * every call, so nothing is gated unless the owner explicitly turns it on (a
8
+ * gate blocking a common dev action must never be always-on).
9
+ * @packageDocumentation
10
+ */
11
+ import type { Prompt } from "../prompt/types";
12
+ import type { AuthEntry } from "../freshness/doc-helpers";
13
+
14
+ /** UI file extensions the gate polices (parity enforce-gemini-mcp.py UI_EXT). */
15
+ const UI_EXT_RE = /\.(tsx|jsx|vue|svelte)$/;
16
+ /** Paths exempt from the gate (parity EXEMPT_DIRS). */
17
+ const GEMINI_EXEMPT_RE = /(node_modules|dist|build|\.next|\.claude)\//;
18
+ /** A live `mcp__gemini-design__*` call recorded this session proves consultation. */
19
+ const GEMINI_MCP_SOURCE_RE = /gemini-mcp/i;
20
+ /** Tailwind utility patterns (parity TAILWIND_PATTERNS). */
21
+ const TW_PATTERNS: readonly RegExp[] = [
22
+ /\bflex\b/, /\bgrid\b/, /\bp-\d/, /\bpx-\d/, /\bpy-\d/,
23
+ /\bm-\d/, /\bmx-\d/, /\bmy-\d/, /\bmt-\d/, /\bmb-\d/,
24
+ /\bbg-\w+/, /\btext-\w+/, /\brounded/, /\bshadow/,
25
+ /\bborder\b/, /\bgap-\d/, /\bw-\w+/, /\bh-\w+/,
26
+ /\bjustify-\w+/, /\bitems-\w+/, /\bspace-\w+-\d/,
27
+ ];
28
+ /** Min distinct Tailwind patterns before the gate fires (parity MIN_TAILWIND_CLASSES). */
29
+ const MIN_TW = 3;
30
+ /** Min newlines an Edit's new_string must have to be gated (parity MIN_LINES_FOR_EDIT). */
31
+ const MIN_LINES_EDIT = 2;
32
+
33
+ /** OPT-IN: off unless `FUSE_ENFORCE_GEMINI_MCP` is `1`/`true`. Read fresh each call (dynamic). */
34
+ export function geminiMcpEnforced(): boolean {
35
+ const v = process.env.FUSE_ENFORCE_GEMINI_MCP;
36
+ return v === "1" || v === "true";
37
+ }
38
+
39
+ /** Count distinct Tailwind utility patterns present in `content` (parity count_tailwind_classes). */
40
+ function countTailwindClasses(content: string): number {
41
+ return TW_PATTERNS.reduce((n, re) => (re.test(content) ? n + 1 : n), 0);
42
+ }
43
+
44
+ /** Session evidence the gate consumes. */
45
+ export interface GeminiEvidence {
46
+ authorizations?: Record<string, AuthEntry>;
47
+ sessionId: string;
48
+ }
49
+
50
+ /** True when a `mcp__gemini-design__*` call was recorded this session (parity gemini_was_called). */
51
+ export function geminiMcpConsulted(authorizations: Record<string, AuthEntry> | undefined, sessionId: string): boolean {
52
+ if (!authorizations) return false;
53
+ return Object.values(authorizations).some(
54
+ (a) => a.doc_sessions?.includes(sessionId) && (a.sources ?? (a.source ? [a.source] : [])).some((s) => GEMINI_MCP_SOURCE_RE.test(s)),
55
+ );
56
+ }
57
+
58
+ /**
59
+ * Gate a UI write when `FUSE_ENFORCE_GEMINI_MCP` is on: block hand-written
60
+ * Tailwind (>= 3 classes) in a `.tsx/.jsx/.vue/.svelte` file unless a Gemini
61
+ * Design MCP call was made this session. Returns `null` when disabled (default),
62
+ * out of scope, or already satisfied.
63
+ * @param tool - the tool name.
64
+ * @param filePath - the file being written.
65
+ * @param content - the written content (new_string on Edit).
66
+ * @param ev - session evidence (authorizations + sessionId).
67
+ */
68
+ export function geminiMcpGate(tool: string, filePath: string, content: string, ev: GeminiEvidence): Prompt | null {
69
+ if (!geminiMcpEnforced()) return null;
70
+ if (tool !== "Write" && tool !== "Edit") return null;
71
+ if (!filePath || !UI_EXT_RE.test(filePath) || GEMINI_EXEMPT_RE.test(filePath)) return null;
72
+ if (!content) return null;
73
+ if (tool === "Edit" && (content.match(/\n/g)?.length ?? 0) < MIN_LINES_EDIT) return null;
74
+ if (countTailwindClasses(content) < MIN_TW) return null;
75
+ if (geminiMcpConsulted(ev.authorizations, ev.sessionId)) return null;
76
+ return {
77
+ kind: "block",
78
+ title: "Gemini Design MCP",
79
+ reason:
80
+ "BLOCKED: UI code with Tailwind detected but Gemini Design MCP not used. Use mcp__gemini-design__create_frontend, modify_frontend, or snippet_frontend BEFORE writing UI code manually.",
81
+ actions: ["Call mcp__gemini-design__create_frontend / modify_frontend / snippet_frontend, then retry"],
82
+ };
83
+ }