@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,95 @@
1
+ import type { Prompt } from "../prompt/types";
2
+ import { RALPH_SAFE } from "./patterns";
3
+
4
+ /**
5
+ * Shell metacharacters that turn a "simple" command into a chained/composite
6
+ * one: `&&`, `||`, `;`, `|`, a lone background `&` (POSIX control operator —
7
+ * `cmd1 & cmd2` runs BOTH; `&(?!>)` excludes bash's `&>`/`&>>` redirects, which
8
+ * cannot introduce a second command), a backtick, `$(`, or a literal newline.
9
+ * Bounded alternation, no nested quantifiers — no ReDoS. Quote-unaware by
10
+ * design: a quoted `"R&D"` gates as ask (fail-closed).
11
+ */
12
+ const CHAIN_RE = /&&|\|\||[;|`]|&(?!>)|\$\(|\n/;
13
+
14
+ /**
15
+ * True when `cmd` has no chaining operator. evaluate.ts's neverApproval
16
+ * exemption must NEVER fire on a composite command — `git commit -m x &&
17
+ * git push --force` would otherwise slip a blocked op past the RALPH_SAFE
18
+ * subset check, since that check is a `startsWith` on the leading verb only.
19
+ */
20
+ export function isSingleCommand(cmd: string): boolean {
21
+ return !CHAIN_RE.test(cmd);
22
+ }
23
+
24
+ /**
25
+ * Commands exempt from `ask` under Codex `approval_policy=never` — a superset
26
+ * of {@link RALPH_SAFE} plus `git push`. Kept as its OWN list, never merged
27
+ * into RALPH_SAFE: the RALPH_MODE path (evaluate.ts's `ralphSafe`) has no
28
+ * equivalent to {@link isSafePushForm}, only the shared GIT_BLOCKED +
29
+ * isSingleCommand guards — adding push to RALPH_SAFE would auto-allow
30
+ * `git push origin --delete branch` under RALPH_MODE, since GIT_BLOCKED only
31
+ * covers force-push, not remote deletion. Only the neverApproval path in
32
+ * evaluate.ts consults NEVER_SAFE, and it always pairs it with
33
+ * isSafePushForm for any push entry.
34
+ */
35
+ export const NEVER_SAFE: ReadonlyArray<string> = [...RALPH_SAFE, "git push"];
36
+
37
+ /**
38
+ * `--delete`/`-d` (git-push(1): "deleted from the remote repository", no
39
+ * `--force` needed), `--mirror` (force-updates AND deletes remote-only refs),
40
+ * `--prune` (removes remote refs with no local counterpart) — all delete or
41
+ * force-overwrite a remote ref without going through the `--force` flag
42
+ * GIT_BLOCKED already checks. Boundary-anchored like patterns.ts's FORCE_FLAG
43
+ * so `-d` never matches inside a longer token (e.g. a branch name).
44
+ */
45
+ const PUSH_DESTRUCTIVE_FLAG = /\s(?:--delete|--mirror|--prune|-d)(?:\s|=|$)/;
46
+ /** A refspec colon (e.g. `git push origin :branch`) deletes the remote ref — the legacy form of `--delete` (git-push(1): "prefixing all refs with a colon"). */
47
+ const REFSPEC_COLON = /\s:\S/;
48
+ /**
49
+ * A leading `+` on a pushed refspec force-updates the remote ref on a
50
+ * non-fast-forward — git-push(1): "The `+` is optional and does the same thing
51
+ * as `--force`". It carries NO `--force`/`-f` flag, so GIT_BLOCKED's FORCE_FLAG
52
+ * misses it; this guard is the `--force` twin. The `\s` anchor makes `+` open
53
+ * its token (`+main`, `+src:dst`), never a mid-token `feature+x` ref name.
54
+ */
55
+ const REFSPEC_FORCE = /\s\+\S/;
56
+
57
+ /**
58
+ * True unless `cmd` is a `git push` carrying an irreversible remote-mutation
59
+ * form GIT_BLOCKED doesn't already cover: `--delete`/`-d`/`--mirror`/`--prune`,
60
+ * a `:refspec` remote-delete, or a `+refspec` force-update (the `--force` twin
61
+ * FORCE_FLAG misses). Plain `--force`/`-f` stays GIT_BLOCKED's job, checked
62
+ * upstream in evaluate.ts. Non-push commands always pass — this guard exists
63
+ * solely to narrow the `git push` entry of {@link NEVER_SAFE}.
64
+ */
65
+ export function isSafePushForm(cmd: string): boolean {
66
+ if (!cmd.startsWith("git push")) return true;
67
+ return !PUSH_DESTRUCTIVE_FLAG.test(cmd) && !REFSPEC_COLON.test(cmd) && !REFSPEC_FORCE.test(cmd);
68
+ }
69
+
70
+ /** Next steps offered on the auto-approve notice — the only channel left once `ask` is unavailable. */
71
+ const ACTIONS: readonly string[] = [
72
+ "Set approval_policy=on-request for interactive confirmation",
73
+ "Or set RALPH_MODE=1 to auto-approve silently (no notice)",
74
+ "Or run the command manually outside the agent loop",
75
+ ];
76
+
77
+ /**
78
+ * Builds the auto-approve `inform` prompt for a command evaluate.ts has
79
+ * already proven safe (RALPH_SAFE subset, not GIT_BLOCKED, not chained —
80
+ * see {@link isSingleCommand}). Pure formatting only; the 3-condition
81
+ * decision itself lives in evaluate.ts, where the anti-chaining guarantee
82
+ * must stay visible and auditable.
83
+ * @param cmd - The already-vetted command.
84
+ */
85
+ export function buildNeverApprovalPrompt(cmd: string): Prompt {
86
+ const trimmed = cmd.trim();
87
+ const reason = `Auto-approved "${trimmed}" under approval_policy=never.`;
88
+ return {
89
+ kind: "inform",
90
+ title: "Auto-approved (approval_policy=never)",
91
+ reason,
92
+ actions: [...ACTIONS],
93
+ userMessage: `[fuse-harness] Auto-approved "${trimmed}" — approval_policy=never has no ask channel. Destructive git commands still deny. Set approval_policy=on-request, or RALPH_MODE=1, to keep asking.`,
94
+ };
95
+ }
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Guard pattern data, ported verbatim from the fusengine git/install guards.
3
+ * Note (faithful): `--force` also matches `--force-with-lease` — preserved
4
+ * from the source guard, now as an explicit alternation instead of an
5
+ * accidental substring match (see {@link GIT_BLOCKED}).
6
+ */
7
+
8
+ /**
9
+ * Token-boundary suffix for a destructive flag: only whitespace, `=`, or
10
+ * end-of-string may follow — never another word character. Without this, a
11
+ * flag regex matches as a plain substring of any argument (a branch/file name
12
+ * like `fix/guard-false-positives` contains `-f`, `feature-force-refactor`
13
+ * contains `-force`), turning a rename into a false-positive hard block
14
+ * (proven live: `git push -u origin fix/api-keys-fossil` denied as
15
+ * "Destructive git command"). No leading-boundary group is needed on the flag
16
+ * side — every pattern below prefixes the flag with a literal `\s`, and the
17
+ * flag can never be the first character of the whole command since a git
18
+ * verb (`git push`, `git reset`, ...) always precedes it.
19
+ */
20
+ const FLAG_END = "(?:\\s|=|$)";
21
+ /** `-f` / `--force` / `--force-with-lease`, boundary-anchored on both flag forms. */
22
+ const FORCE_FLAG = `\\s(?:--force(?:-with-lease)?|-f)${FLAG_END}`;
23
+ /**
24
+ * `git clean` short-flag cluster containing both `f` and `d` in any order
25
+ * (`-fd`, `-df`, `-fdx`, `-fdX`, `-xfd`…): a MORE destructive superset than a
26
+ * bare `-fd` — must stay blocked, never downgrade to `git clean`'s GIT_ASK.
27
+ */
28
+ const CLEAN_FD_FLAG = `\\s-(?=[a-zA-Z]*f)(?=[a-zA-Z]*d)[a-zA-Z]+${FLAG_END}`;
29
+
30
+ /** Destructive git operations to block outright. */
31
+ export const GIT_BLOCKED: ReadonlyArray<RegExp> = [
32
+ new RegExp(`git push.*${FORCE_FLAG}`),
33
+ new RegExp(`git reset.*\\s--hard${FLAG_END}`),
34
+ new RegExp(`git clean.*${CLEAN_FD_FLAG}`),
35
+ new RegExp(`git branch.*\\s-D${FLAG_END}`),
36
+ new RegExp(`git rebase.*${FORCE_FLAG}`),
37
+ ];
38
+
39
+ /** Git operations that warrant a confirmation prompt. */
40
+ export const GIT_ASK: ReadonlyArray<RegExp> = [
41
+ /git push/, /git checkout/, /git reset/, /git rebase/, /git merge/,
42
+ /git stash/, /git clean/, /git rm/, /git mv/, /git restore/,
43
+ /git revert/, /git cherry-pick/, /git commit/, /git add/, /git branch -d/,
44
+ ];
45
+
46
+ /** System-level package installs (need confirmation). */
47
+ export const SYSTEM_INSTALL: ReadonlyArray<RegExp> = [
48
+ /brew install/, /brew upgrade/, /brew cask/, /apt install/, /apt-get install/,
49
+ /dnf install/, /pacman -S/,
50
+ ];
51
+
52
+ /** Project-level package installs. */
53
+ export const PROJECT_INSTALL: ReadonlyArray<RegExp> = [
54
+ /npm install/, /npm i /, /yarn add/, /pnpm add/, /pip install/, /pip3 install/,
55
+ /composer require/, /bun add/, /bun install/, /cargo install/, /go install/,
56
+ /gem install/, /pipx install/,
57
+ ];
58
+
59
+ /** True when `cmd` matches any pattern in `patterns`. */
60
+ export function matchPatterns(cmd: string, patterns: ReadonlyArray<RegExp>): boolean {
61
+ return patterns.some((re) => re.test(cmd));
62
+ }
63
+
64
+ /** Git commands exempt from confirmation when Ralph mode is on (parity git-guard.py RALPH_SAFE). */
65
+ export const RALPH_SAFE: ReadonlyArray<string> = [
66
+ "git add", "git commit", "git checkout -b", "git branch --show-current",
67
+ "git status", "git diff", "git log",
68
+ ];
69
+
70
+ /**
71
+ * OPT-IN autonomous "Ralph" mode: OFF unless `RALPH_MODE` is `1`/`true`, read
72
+ * fresh on each call. Only the env var activates it — the Python source also
73
+ * auto-enabled on a `.claude/ralph/prd.json` file or a `feature/*` branch, but
74
+ * those silent activations are dropped here: a mode that removes git/install
75
+ * confirmations must never turn on implicitly (owner: default OFF, env opt-in).
76
+ */
77
+ export function isRalphMode(): boolean {
78
+ const v = process.env.RALPH_MODE;
79
+ return v === "1" || v === "true";
80
+ }
@@ -0,0 +1,51 @@
1
+ /**
2
+ * SEO completeness checks — pure text in, missing-items out (no fs). Ports the
3
+ * `validate()` of `seo/hooks/validate-seo.ts` using regex instead of cheerio so
4
+ * the harness keeps zero runtime HTML-parser dependency (presence-only gate).
5
+ */
6
+
7
+ /** Extensions the SEO hook validates (HTML-like rendered output). */
8
+ const HTML_LIKE = /\.(html|astro|tsx|vue|blade\.php)$/;
9
+
10
+ /** True when `path` is an HTML-like file the SEO hook should validate. */
11
+ export function isHtmlLike(path: string): boolean {
12
+ return HTML_LIKE.test(path);
13
+ }
14
+
15
+ /** True when a `<tag …>…</tag>` carries non-empty inner text. */
16
+ function hasNonEmptyTag(html: string, tag: string): boolean {
17
+ const m = new RegExp(`<${tag}[\\s>][\\s\\S]*?</${tag}>`, "i").exec(html);
18
+ return !!m && m[0].replace(/<[^>]*>/g, "").trim().length > 0;
19
+ }
20
+
21
+ /** True when a `<meta …>` whose `selAttr=selVal` has a non-empty `content`. */
22
+ function hasMetaContent(html: string, selAttr: string, selVal: string): boolean {
23
+ const re = new RegExp(`<meta\\b[^>]*\\b${selAttr}\\s*=\\s*["']${selVal}["'][^>]*>`, "i");
24
+ const tag = re.exec(html)?.[0] ?? "";
25
+ const c = /\bcontent\s*=\s*["']([^"']*)["']/i.exec(tag)?.[1] ?? "";
26
+ return c.trim().length > 0;
27
+ }
28
+
29
+ /** True when a `<link rel="canonical">` carries a non-empty `href`. */
30
+ function hasCanonical(html: string): boolean {
31
+ const tag = /<link\b[^>]*\brel\s*=\s*["']canonical["'][^>]*>/i.exec(html)?.[0] ?? "";
32
+ return (/\bhref\s*=\s*["']([^"']*)["']/i.exec(tag)?.[1] ?? "").trim().length > 0;
33
+ }
34
+
35
+ /**
36
+ * Report the SEO elements missing from HTML-like content (title, meta
37
+ * description, OG title/description/image, canonical, JSON-LD schema).
38
+ * @param html - The file content.
39
+ * @returns The missing element labels (empty when complete).
40
+ */
41
+ export function missingSeoElements(html: string): string[] {
42
+ const missing: string[] = [];
43
+ if (!hasNonEmptyTag(html, "title")) missing.push("<title>");
44
+ if (!hasMetaContent(html, "name", "description")) missing.push("<meta name='description'>");
45
+ if (!hasMetaContent(html, "property", "og:title")) missing.push("og:title");
46
+ if (!hasMetaContent(html, "property", "og:description")) missing.push("og:description");
47
+ if (!hasMetaContent(html, "property", "og:image")) missing.push("og:image");
48
+ if (!hasCanonical(html)) missing.push("canonical");
49
+ if (!/<script\b[^>]*type\s*=\s*["']application\/ld\+json["']/i.test(html)) missing.push("JSON-LD schema");
50
+ return missing;
51
+ }
@@ -0,0 +1,22 @@
1
+ import { existsSync } from "node:fs";
2
+ import { join } from "node:path";
3
+
4
+ /** Directories whose presence indicates shadcn/ui is installed. */
5
+ const UI_DIRS: ReadonlyArray<string> = [
6
+ "src/components/ui",
7
+ "components/ui",
8
+ "src/modules/cores/shadcn/components/ui",
9
+ ];
10
+
11
+ /**
12
+ * Whether `cwd` is a shadcn/ui project, ported from the shared Python
13
+ * `is_shadcn_project` (`shadcn_patterns.py`): true when a `components.json`
14
+ * file exists, or any known `components/ui` directory exists under the root.
15
+ * Used to skip `*-shadcn` sub-skill requirements when shadcn is not installed.
16
+ * @param cwd - project root directory to scan.
17
+ * @returns `true` when shadcn/ui is detected on disk.
18
+ */
19
+ export function isShadcnProject(cwd: string): boolean {
20
+ if (existsSync(join(cwd, "components.json"))) return true;
21
+ return UI_DIRS.some((dir) => existsSync(join(cwd, dir)));
22
+ }
@@ -0,0 +1,97 @@
1
+ /**
2
+ * @module shadcn-skill-gate
3
+ * Standalone shadcn/ui skill gate, ported from the shadcn-expert plugin's
4
+ * `check-skill-loaded.py`. Distinct from the `*-shadcn` sub-skill entries
5
+ * consumed by react/nextjs (`./skill-patterns/shadcn.ts`, the HTML-detection
6
+ * patterns): this polices ANY Write/Edit in a shadcn-scoped path
7
+ * (components|ui|shadcn|components.json), independent of the detected
8
+ * `framework` — mirroring the plugin's own PreToolUse hook, which runs
9
+ * alongside (not instead of) the react/nextjs framework gate.
10
+ * @packageDocumentation
11
+ */
12
+ import type { Prompt } from "../prompt/types";
13
+ import type { AuthEntry } from "../freshness/doc-helpers";
14
+ import { skillTriggerGate } from "./skill-triggers";
15
+ import { docConsultedGate } from "./apex";
16
+
17
+ /** File extensions the shadcn gate polices (source: `\.(tsx|jsx|css|scss|json)$`). */
18
+ const SHADCN_FILE_RE = /\.(tsx|jsx|css|scss|json)$/;
19
+ /**
20
+ * Vendored/build paths exempt from the gate. Uses `(^|\/)` (not just a
21
+ * leading `/`) so a relative root-start path like `node_modules/x` is also
22
+ * exempt — mirrors the same deliberate widening already applied in
23
+ * `design/skill-gate.ts`'s `EXEMPT_RE`, not the Python's stricter `/x/`-only match.
24
+ */
25
+ const EXEMPT_RE = /(^|\/)(node_modules|dist|build)\//;
26
+ /** Path fragments that put a write in shadcn scope (source: `(components|ui|shadcn|components\.json)`). */
27
+ const SHADCN_PATH_RE = /(components|ui|shadcn|components\.json)/;
28
+ /** A read reference proving one of the two named base skills was consulted (Phase 1). */
29
+ const SHADCN_BASE_SKILL_RE = /skills\/(shadcn-detection|shadcn-components)\//;
30
+ /** Session `authorizations` source proving a live `mcp__shadcn__*` call was made this session (Phase 1, option 3). */
31
+ const SHADCN_MCP_SOURCE_RE = /shadcn-mcp/i;
32
+
33
+ /** Session evidence the gate consumes: read references + doc authorizations. */
34
+ export interface ShadcnEvidence {
35
+ refsRead: readonly string[];
36
+ authorizations?: Record<string, AuthEntry>;
37
+ sessionId: string;
38
+ }
39
+
40
+ /**
41
+ * True when this Write/Edit is shadcn-scoped: a `.tsx/.jsx/.css/.scss/.json`
42
+ * file under a `components|ui|shadcn|components.json` path, outside vendored
43
+ * dirs. Ports `check-skill-loaded.py`'s file-path filter (lines 35-42).
44
+ */
45
+ export function isShadcnWrite(tool: string, filePath: string): boolean {
46
+ if (tool !== "Write" && tool !== "Edit") return false;
47
+ if (!SHADCN_FILE_RE.test(filePath) || EXEMPT_RE.test(filePath)) return false;
48
+ return SHADCN_PATH_RE.test(filePath);
49
+ }
50
+
51
+ /** True when `shadcn-detection/` or `shadcn-components/` was read this session (Phase 1 base skill). */
52
+ export function shadcnBaseSkillRead(refsRead: readonly string[]): boolean {
53
+ return refsRead.some((p) => SHADCN_BASE_SKILL_RE.test(p));
54
+ }
55
+
56
+ /** True when a `mcp__shadcn__*` tool call was recorded this session (Phase 1's 3rd unblock option). */
57
+ export function shadcnMcpConsulted(authorizations: Record<string, AuthEntry> | undefined, sessionId: string): boolean {
58
+ if (!authorizations) return false;
59
+ return Object.values(authorizations).some(
60
+ (a) => a.doc_sessions?.includes(sessionId) && (a.sources ?? (a.source ? [a.source] : [])).some((s) => SHADCN_MCP_SOURCE_RE.test(s)),
61
+ );
62
+ }
63
+
64
+ /**
65
+ * Gate a shadcn-scoped write. Phase 1 requires a base skill read
66
+ * (shadcn-detection or shadcn-components); Phase 2 requires the domain
67
+ * sub-skill(s) matching the written content, via {@link skillTriggerGate}
68
+ * keyed on the "shadcn" framework (registered in `SKILL_TRIGGERS`); Phase 3
69
+ * requires doc research, delegated to the existing {@link docConsultedGate}
70
+ * (Context7 AND Exa, or a web fallback alone — not the Python's strict AND).
71
+ * Returns a blocking {@link Prompt}, or `null` to allow.
72
+ */
73
+ export function shadcnSkillGate(
74
+ tool: string,
75
+ filePath: string,
76
+ content: string,
77
+ ev: ShadcnEvidence,
78
+ ): Prompt | null {
79
+ if (!isShadcnWrite(tool, filePath)) return null;
80
+ if (!shadcnBaseSkillRead(ev.refsRead) && !shadcnMcpConsulted(ev.authorizations, ev.sessionId)) {
81
+ return {
82
+ kind: "block",
83
+ title: "shadcn/ui skill",
84
+ reason: "BLOCKED: shadcn skill not consulted. Read shadcn-detection/SKILL.md or shadcn-components/SKILL.md, or use mcp__shadcn__search_items_in_registries, then retry.",
85
+ actions: ["Read skills/shadcn-detection/ or skills/shadcn-components/, or call mcp__shadcn__search_items_in_registries, then retry"],
86
+ };
87
+ }
88
+ const domainBlock = skillTriggerGate("shadcn", content, ev.refsRead);
89
+ if (domainBlock) return domainBlock;
90
+ return docConsultedGate({
91
+ sessionId: ev.sessionId,
92
+ framework: "shadcn",
93
+ filePath,
94
+ content,
95
+ authorizations: ev.authorizations,
96
+ });
97
+ }
@@ -0,0 +1,96 @@
1
+ /**
2
+ * @module shell-read-refs
3
+ * Codex teammates frequently read skill/SOLID reference `.md` files through a
4
+ * shell Bash call (`cat`, `head`, …) instead of a native `Read` — the
5
+ * refsRead/ref-journal freshness gates only ever credited a native `Read`
6
+ * (`src/runtime/activity.ts`'s `READ_TOOLS` branch), so a shell-read skill
7
+ * consultation was invisible to every SOLID/skill gate. This detects the
8
+ * `.md` paths a KNOWN read-only command targets in a Bash `command`, for a
9
+ * caller to fold into the SAME `{kind:"ref", path, ts}` activity
10
+ * `src/runtime/record.ts` already persists (no new store, no new gate).
11
+ *
12
+ * Fail-open by construction: only a whitelisted read-only command name
13
+ * credits its `.md` arguments — a non-read command (`echo`, `mv`, `tee`, …)
14
+ * sharing a chained/piped segment is never credited, and `sed -i`/
15
+ * `--in-place` (a MUTATION despite the `sed` name) is explicitly excluded.
16
+ * @packageDocumentation
17
+ */
18
+ import { commandToString } from "../runtime/command-string";
19
+ import { shellOutputRedirects } from "./guards/bash-write-redirects";
20
+
21
+ /** Read-only shell commands that can target a `.md` reference by path. */
22
+ const READ_COMMANDS = new Set(["cat", "head", "tail", "sed", "rg", "ripgrep", "less", "more", "bat"]);
23
+
24
+ /** POSIX shells whose `-c`-style argv (or inline string) wraps a real script. */
25
+ const SHELL_BINS = new Set(["bash", "sh", "zsh", "dash"]);
26
+
27
+ /** `sed -i` / `sed --in-place` mutates the file in place — never a read. */
28
+ const SED_INPLACE = /(^|\s)(-i\b|--in-place\b)/;
29
+
30
+ /** Split a command string on `&&`, `||`, `;`, `|`, and newlines — each side scanned independently. */
31
+ function segments(command: string): string[] {
32
+ return command.split(/&&|\|\||[;|\n]/);
33
+ }
34
+
35
+ /** Strip a redirection (`>`, `>>`, `<`, `2>`, `&>`, …) and everything after — its target is WRITTEN, not read. */
36
+ function beforeRedirect(segment: string): string {
37
+ const first = shellOutputRedirects(segment)[0];
38
+ return first ? segment.slice(0, first.start) : segment;
39
+ }
40
+
41
+ /** Naive shell tokenizer: whitespace-split, stripping one matching layer of quotes per token. */
42
+ function tokenize(segment: string): string[] {
43
+ const tokens = segment.match(/(?:"[^"]*"|'[^']*'|\S+)/g) ?? [];
44
+ return tokens.map((t) => (/^(['"]).*\1$/.test(t) ? t.slice(1, -1) : t));
45
+ }
46
+
47
+ /** Index of the first token that isn't an env-assignment prefix (`KEY=VAL cmd …`). */
48
+ function firstCommandIndex(tokens: string[]): number {
49
+ let i = 0;
50
+ while (i < tokens.length && /^[A-Za-z_][A-Za-z0-9_]*=/.test(tokens[i] ?? "")) i++;
51
+ return i;
52
+ }
53
+
54
+ /** The inner script of a `sh|bash|zsh|dash -c <script>` wrapper (as ONE token, since {@link tokenize} keeps a quoted phrase whole), or undefined when `tokens` isn't that shape. */
55
+ function unwrapShellC(tokens: string[]): string | undefined {
56
+ const i = firstCommandIndex(tokens);
57
+ const bin = tokens[i]?.slice(tokens[i].lastIndexOf("/") + 1);
58
+ if (!bin || !SHELL_BINS.has(bin)) return undefined;
59
+ const flag = tokens[i + 1];
60
+ if (!flag || !/^-[a-z]*c$/.test(flag)) return undefined;
61
+ return tokens[i + 2];
62
+ }
63
+
64
+ /** Credit the `.md` arguments of one segment if its command is a whitelisted read; recurses once into a `sh -c` wrapper. */
65
+ function scanSegment(raw: string, out: string[]): void {
66
+ const segment = beforeRedirect(raw);
67
+ const tokens = tokenize(segment);
68
+ const inner = unwrapShellC(tokens);
69
+ if (inner !== undefined) {
70
+ for (const s of segments(inner)) scanSegment(s, out);
71
+ return;
72
+ }
73
+ const i = firstCommandIndex(tokens);
74
+ const name = tokens[i]?.slice(tokens[i].lastIndexOf("/") + 1);
75
+ if (!name || !READ_COMMANDS.has(name)) return;
76
+ if (name === "sed" && SED_INPLACE.test(segment)) return;
77
+ for (const t of tokens.slice(i + 1)) {
78
+ if (t.endsWith(".md") && !t.startsWith("-")) out.push(t);
79
+ }
80
+ }
81
+
82
+ /**
83
+ * The `.md` paths a read-only shell command reads in `command` (a Bash
84
+ * `tool_input.command` — plain string or Codex's argv-array form, both
85
+ * normalized via {@link commandToString}). Empty for a non-read command,
86
+ * `sed -i` in-place, or an unparseable/absent command — fail-open, never a
87
+ * false credit.
88
+ * @param command - Raw `tool_input.command` value (string | string[] | unknown).
89
+ */
90
+ export function shellReadRefPaths(command: unknown): string[] {
91
+ const str = commandToString(command);
92
+ if (!str) return [];
93
+ const out: string[] = [];
94
+ for (const seg of segments(str)) scanSegment(seg, out);
95
+ return out;
96
+ }
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Dynamic skill-path resolution (fs). Replaces a hardcoded `PLUGIN_DIR`
3
+ * table (framework -> plugin dir name), which can drift from what's actually
4
+ * installed: scans installed marketplace plugins for a `skills/<name>/SKILL.md`
5
+ * that exists ON DISK, returning the first real absolute path found — never a
6
+ * guessed one.
7
+ *
8
+ * Deliberately lighter than the cartographer's own `scanSkills` (which
9
+ * frontmatter-parses every skill in a plugin): this runs once per missing
10
+ * sub-skill on every gated Write/Edit, so it does a single flat `readdirSync`
11
+ * of the plugins dir + one `existsSync` per candidate — no file reads, no
12
+ * recursion, no `statSync`.
13
+ */
14
+ import { existsSync, readdirSync } from "node:fs";
15
+ import { join } from "node:path";
16
+ import { findMarketplacePlugins } from "../runtime/lifecycle/cartographer/detect";
17
+ import { PLUGINS_DIR } from "./file-size";
18
+
19
+ /** Sorted entry names of `dir` (alpha, byte-order), or `[]` on error. */
20
+ function pluginDirNames(dir: string): string[] {
21
+ try {
22
+ return readdirSync(dir).sort((a, b) => a.localeCompare(b, "en"));
23
+ } catch {
24
+ return [];
25
+ }
26
+ }
27
+
28
+ /**
29
+ * Resolve the real, installed `SKILL.md` path for `skillName`.
30
+ * @param skillName - the sub-skill directory name (e.g. "solid-react",
31
+ * "shadcn-detection", "react-shadcn").
32
+ * @param pluginsDirOverride - override for the marketplace plugins dir
33
+ * (tests); defaults to the auto-detected marketplace root.
34
+ * @returns the absolute `<pluginDir>/skills/<skillName>/SKILL.md` of the
35
+ * first installed plugin that actually has it, or the generic
36
+ * `${PLUGINS_DIR}/<skillName>` fallback when none is found (CI/test
37
+ * environments without a marketplace).
38
+ */
39
+ export function resolveSkillPath(skillName: string, pluginsDirOverride?: string): string {
40
+ const pluginsDir = pluginsDirOverride ?? findMarketplacePlugins();
41
+ for (const dir of pluginDirNames(pluginsDir)) {
42
+ const candidate = join(pluginsDir, dir, "skills", skillName, "SKILL.md");
43
+ if (existsSync(candidate)) return candidate;
44
+ }
45
+ return `${PLUGINS_DIR}/${skillName}`;
46
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Laravel skill-trigger patterns, ported verbatim from `laravel_patterns.py`
3
+ * (consumed by `laravel_skill_triggers.py`). Matched case-insensitively.
4
+ */
5
+
6
+ /** Map of Laravel sub-skill name → triggering PHP code patterns. */
7
+ export const LARAVEL_TRIGGERS: Readonly<Record<string, ReadonlyArray<string>>> = {
8
+ "fusecore": ["FuseCore\\\\[A-Za-z]+\\\\App\\\\", "use HasModule\\b",
9
+ "ModuleServiceProvider\\b", "ModuleInterface\\b"],
10
+ "laravel-eloquent": ["(extends Model|HasFactory|belongsTo|hasMany|hasOne|morphTo)\\b",
11
+ "\\$this->belongsToMany|->with\\(|->whereHas\\(",
12
+ "(Eloquent|Model)::(find|where|create|update|all)\\b"],
13
+ "laravel-api": ["(JsonResource|ResourceCollection|apiResource)\\b",
14
+ "Route::(get|post|put|delete|apiResource)\\(",
15
+ "(response\\(\\)->json|Request \\$request)\\b"],
16
+ "laravel-auth": ["(Auth::|auth\\(\\)|Sanctum|Passport|Socialite)\\b",
17
+ "(Gate::|Policy|can\\(|authorize)\\b",
18
+ "(middleware\\(['\"]auth|LoginController|RegisterController)\\b"],
19
+ "laravel-livewire": ["(extends Component|Livewire|wire:|#\\[On)\\b",
20
+ "(mount|render|emit|dispatch)\\(\\)", "@livewire|<livewire:"],
21
+ "laravel-queues": ["(implements ShouldQueue|dispatch\\(|Bus::)\\b",
22
+ "(Queue::|Job|Batch|Chain)\\b", "(onQueue|onConnection|tries|backoff)\\b"],
23
+ "laravel-billing": ["(Billable|subscription|Cashier)\\b",
24
+ "(createSubscription|newSubscription|charge)\\("],
25
+ "laravel-stripe-connect": ["(StripeConnect|connectAccount|onboardingUrl)\\b",
26
+ "(paymentIntent|transfer|payout|splitPayment)\\b",
27
+ "Stripe\\\\\\\\(Account|Transfer|PaymentIntent)\\b"],
28
+ "laravel-testing": ["(extends TestCase|RefreshDatabase|WithFaker)\\b",
29
+ "(assertStatus|assertJson|assertSee|assertRedirect)\\(",
30
+ "(factory\\(|Pest|it\\(|test\\(|expect\\()\\b"],
31
+ "laravel-migrations": ["(Schema::|Blueprint|->table|->create)\\b",
32
+ "(->string|->integer|->boolean|->foreignId|->index)\\(", "extends Migration\\b"],
33
+ "laravel-blade": ["(@extends|@section|@yield|@component|@slot)\\b",
34
+ "(@if|@foreach|@include|@push|@stack)\\b", "(Blade::|x-[a-z])\\b"],
35
+ "laravel-permission": ["(hasRole|givePermissionTo|assignRole|spatie)\\b",
36
+ "(Permission|Role)::(create|findByName)\\b", "@can\\b|@role\\b|middleware.*role:"],
37
+ "laravel-i18n": ["(__\\(|trans\\(|trans_choice\\(|@lang)\\b",
38
+ "Lang::|->locale\\(|setLocale\\b"],
39
+ "laravel-vite": ["(@vite|@viteReactRefresh|Vite::)\\b"],
40
+ };
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Next.js skill-trigger patterns, ported verbatim from `nextjs_skill_triggers.py`.
3
+ * Matched case-insensitively (source `re.IGNORECASE`).
4
+ */
5
+ import { SHADCN } from "./shadcn";
6
+
7
+ /** Map of Next.js sub-skill name → triggering code patterns. */
8
+ export const NEXTJS_TRIGGERS: Readonly<Record<string, ReadonlyArray<string>>> = {
9
+ "better-auth": ["(authClient|betterAuth|createAuthClient)\\b",
10
+ "(signIn|signUp|signOut|useSession|getSession)\\b", "auth\\.(api|handler)\\b",
11
+ "(prismaAdapter|drizzleAdapter|mongodbAdapter)\\b",
12
+ "(twoFactor|passkey|magicLink|emailOtp|organization)\\b",
13
+ "(apiKey|bearer|jwt|sso|scim|captcha|anonymous)\\b", "from\\s+['\"].*better-auth"],
14
+ "nextjs-tanstack-form": ["(useForm|useAppForm|createFormHook|formOptions)\\b",
15
+ "(mergeForm|formApi|FieldApi|FormApi)\\b",
16
+ "form\\.(Field|Subscribe|handleSubmit)\\b", "(zodValidator|onServerValidate)\\b",
17
+ "from\\s+['\"]@tanstack/(react-form|zod-form-adapter)"],
18
+ "prisma-7": ["(PrismaClient|prismaAdapter)\\b", "prisma\\.(\\w+\\.\\w+|\\$\\w+)",
19
+ "(globalForPrisma|\\$transaction|\\$queryRaw|\\$executeRaw)\\b",
20
+ "from\\s+['\"](@prisma|\\..*generated.*prisma)"],
21
+ "nextjs-shadcn": SHADCN,
22
+ "nextjs-zustand": ["(create|createStore)\\(\\s*\\(\\s*set", "from\\s+['\"]zustand(/\\w+)?\"",
23
+ "(useShallow|useStore|skipHydration)\\b", "\\.(getState|setState|subscribe)\\(\\)",
24
+ "(persist|devtools|immer)\\("],
25
+ "nextjs-i18n": ["(useTranslations|useLocale|useMessages|useFormatter)\\b",
26
+ "(getTranslations|getLocale|getMessages|getFormatter)\\b",
27
+ "(NextIntlClientProvider|defineRouting)\\b", "from\\s+['\"]next-intl(/\\w+)?\"",
28
+ "\\bt\\(\\s*['\"]"],
29
+ };
@@ -0,0 +1,27 @@
1
+ /**
2
+ * React skill-trigger patterns, ported verbatim from `react_skill_triggers.py`.
3
+ * Matched case-insensitively (source `re.IGNORECASE`).
4
+ */
5
+ import { SHADCN } from "./shadcn";
6
+
7
+ /** Map of React sub-skill name → triggering code patterns. */
8
+ export const REACT_TRIGGERS: Readonly<Record<string, ReadonlyArray<string>>> = {
9
+ "react-19": ["\\buse\\b\\s*\\(", "useOptimistic\\b", "useActionState\\b",
10
+ "useEffectEvent\\b", "<Activity\\b", "from\\s+['\"]react['\"]"],
11
+ "react-tanstack-router": ["(createRouter|createRoute|createRootRoute)\\b",
12
+ "(useNavigate|useParams|useSearch|useLoaderData)\\b",
13
+ "from\\s+['\"]@tanstack/(react-router|router)",
14
+ "(routeTree|createFileRoute|createLazyFileRoute)\\b"],
15
+ "react-forms": ["(useForm|useAppForm|createFormHook|formOptions)\\b",
16
+ "(mergeForm|formApi|FieldApi|FormApi)\\b",
17
+ "form\\.(Field|Subscribe|handleSubmit)\\b",
18
+ "from\\s+['\"]@tanstack/(react-form|zod-form-adapter)"],
19
+ "react-state": ["(create|createStore)\\(\\s*\\(\\s*set", "from\\s+['\"]zustand(/\\w+)?\"",
20
+ "(useShallow|useStore|skipHydration)\\b", "(persist|devtools|immer)\\("],
21
+ "react-testing": ["(render|screen|fireEvent|waitFor)\\b",
22
+ "from\\s+['\"]@testing-library/react",
23
+ "(describe|it|expect|vi\\.|jest\\.)\\b", "from\\s+['\"]vitest"],
24
+ "react-shadcn": SHADCN,
25
+ "react-i18n": ["(useTranslation|Trans)\\b", "from\\s+['\"]react-i18next",
26
+ "\\bt\\(\\s*['\"]", "i18n\\.(language|changeLanguage)"],
27
+ };
@@ -0,0 +1,33 @@
1
+ /**
2
+ * shadcn/ui domain skill-trigger patterns, ported verbatim from
3
+ * `shadcn_skill_triggers.py` (the standalone shadcn-expert plugin, distinct
4
+ * from the `SHADCN` HTML-detection patterns in `./shadcn.ts` consumed by
5
+ * react/nextjs). 5 sub-skills. Matched case-insensitively (source `re.IGNORECASE`).
6
+ */
7
+
8
+ /** Map of shadcn/ui sub-skill name → triggering code patterns. */
9
+ export const SHADCN_TRIGGERS: Readonly<Record<string, ReadonlyArray<string>>> = {
10
+ "shadcn-detection": [
11
+ "components\\.json", "@radix-ui/", "@base-ui/",
12
+ "data-\\[state=", "data-\\[disabled\\]",
13
+ ],
14
+ "shadcn-components": [
15
+ "from\\s+['\"].*components/ui/",
16
+ "<(Button|Input|Select|Dialog|Card|Table|Tabs|Badge)\\b",
17
+ "(Popover|Tooltip|Sheet|Drawer|Command|Accordion)\\b",
18
+ ],
19
+ "shadcn-theming": [
20
+ "--(primary|secondary|muted|accent|destructive|foreground):",
21
+ "(cssVariables|themeConfig|globals\\.css)\\b",
22
+ ":root\\s*\\{|\\.dark\\s*\\{",
23
+ ],
24
+ "shadcn-registries": [
25
+ "mcp__shadcn__(search|view|get_add_command|list_items)",
26
+ "bunx.*shadcn@latest\\s+add\\b",
27
+ "(registries|@shadcn|@acme)\\b",
28
+ ],
29
+ "shadcn-migration": [
30
+ "(@radix-ui.*@base-ui|@base-ui.*@radix-ui)",
31
+ "(migrat|convert|switch).*(radix|base.?ui)",
32
+ ],
33
+ };