@fusengine/harness 0.1.89 → 0.1.91

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (419) hide show
  1. package/dist/adapters/claude/index.d.mts +1 -1
  2. package/dist/adapters/claude/index.mjs +1 -1
  3. package/dist/adapters/cline/index.mjs +1 -1
  4. package/dist/adapters/codex/index.d.mts +2 -2
  5. package/dist/adapters/codex/index.mjs +5 -4
  6. package/dist/adapters/cursor/index.d.mts +15 -28
  7. package/dist/adapters/cursor/index.mjs +35 -46
  8. package/dist/adapters/gemini/index.mjs +1 -1
  9. package/dist/adapters/hermes/index.d.mts +1 -1
  10. package/dist/adapters/hermes/index.mjs +1 -1
  11. package/dist/adapters/kimi/index.d.mts +1 -1
  12. package/dist/adapters/kimi/index.mjs +1 -1
  13. package/dist/{claude-5bC8TDAz.mjs → claude-Ckv2_TgP.mjs} +3 -43
  14. package/dist/cli/bin.mjs +429 -16
  15. package/dist/cli/index.mjs +1 -1
  16. package/dist/command-string-CALMTnwN.mjs +42 -0
  17. package/dist/{evaluate-C1VSI5pb.mjs → evaluate-CMiIqHeH.mjs} +261 -13
  18. package/dist/{handle-DHhukRdh.mjs → handle-C43gA-Pr.mjs} +737 -150
  19. package/dist/{hermes-kP6NUQlG.mjs → hermes-B9-p_3IF.mjs} +3 -2
  20. package/dist/{index-DBgnIZn9.d.mts → index-B8PG82su.d.mts} +1 -1
  21. package/dist/{index-D4C2-IIc.d.mts → index-SmKYkk2N.d.mts} +1 -1
  22. package/dist/index.d.mts +2 -2
  23. package/dist/index.mjs +2 -2
  24. package/dist/init/index.d.mts +1 -1
  25. package/dist/init/index.mjs +1 -1
  26. package/dist/{kimi-D6X7b8wF.mjs → kimi-G2wcSh5-.mjs} +3 -2
  27. package/dist/normalize-BjG6unTj.mjs +317 -0
  28. package/dist/permission-mode-B-SFyR0X.mjs +226 -0
  29. package/dist/policy/index.d.mts +1 -1
  30. package/dist/policy/index.mjs +2 -2
  31. package/dist/prompt/index.d.mts +1 -1
  32. package/dist/{run-CdIlUtjv.mjs → run-DkrzC0gb.mjs} +1 -1
  33. package/dist/{run-TAXNRSpD.mjs → run-GHAMqcOn.mjs} +26 -3
  34. package/dist/runtime/index.d.mts +27 -16
  35. package/dist/runtime/index.mjs +2 -2
  36. package/dist/{types-C0-igRKr.d.mts → types-Bh0jEF3_.d.mts} +2 -0
  37. package/dist/{validate-DZFtAbLP.mjs → validate-KjZ1X9tH.mjs} +1 -1
  38. package/package.json +3 -2
  39. package/src/adapters/claude/index.ts +92 -0
  40. package/src/adapters/cline/index.ts +36 -0
  41. package/src/adapters/codex/apply-patch.ts +94 -0
  42. package/src/adapters/codex/index.ts +84 -0
  43. package/src/adapters/codex/permission-mode.ts +16 -0
  44. package/src/adapters/cursor/context.ts +46 -0
  45. package/src/adapters/cursor/events.ts +38 -0
  46. package/src/adapters/cursor/index.ts +43 -0
  47. package/src/adapters/cursor/interfaces/types.ts +88 -0
  48. package/src/adapters/cursor/native-response.ts +158 -0
  49. package/src/adapters/cursor/normalize.ts +123 -0
  50. package/src/adapters/cursor/respond.ts +123 -0
  51. package/src/adapters/gemini/index.ts +36 -0
  52. package/src/adapters/hermes/index.ts +55 -0
  53. package/src/adapters/hermes/interfaces/types.ts +26 -0
  54. package/src/adapters/kimi/index.ts +96 -0
  55. package/src/adapters/kimi/interfaces/types.ts +55 -0
  56. package/src/cache/compact.ts +55 -0
  57. package/src/cache/index.ts +5 -0
  58. package/src/cache/io.ts +38 -0
  59. package/src/cache/mcp-response.ts +39 -0
  60. package/src/cache/mcp-store.ts +70 -0
  61. package/src/cache/store.ts +93 -0
  62. package/src/changelog/fetch.ts +79 -0
  63. package/src/cli/bin.ts +100 -0
  64. package/src/cli/cursor-event-scanner.ts +175 -0
  65. package/src/cli/cursor-stdin-reader.ts +65 -0
  66. package/src/cli/doctor.ts +96 -0
  67. package/src/cli/hook-io.ts +130 -0
  68. package/src/cli/hook-sound.ts +49 -0
  69. package/src/cli/index.ts +1 -0
  70. package/src/cli/json-primitive-scanner.ts +83 -0
  71. package/src/cli/run.ts +36 -0
  72. package/src/cli/scope.ts +31 -0
  73. package/src/codex-rules/build.ts +63 -0
  74. package/src/codex-rules/index.ts +8 -0
  75. package/src/codex-rules/mapping.ts +42 -0
  76. package/src/codex-rules/rules/absolute-paths.ts +52 -0
  77. package/src/codex-rules/rules/ask.ts +44 -0
  78. package/src/codex-rules/rules/chmod-ask.ts +57 -0
  79. package/src/codex-rules/rules/dd.ts +23 -0
  80. package/src/codex-rules/rules/diskutil.ts +33 -0
  81. package/src/codex-rules/rules/mkfs.ts +42 -0
  82. package/src/codex-rules/rules/privilege.ts +49 -0
  83. package/src/codex-rules/rules/remote-fetch.ts +31 -0
  84. package/src/codex-rules/rules/rm-variants.ts +58 -0
  85. package/src/codex-rules/rules/single-commands.ts +57 -0
  86. package/src/codex-rules/skip-list.ts +25 -0
  87. package/src/codex-rules/starlark.ts +46 -0
  88. package/src/codex-rules/types.ts +36 -0
  89. package/src/config/dotenv.ts +55 -0
  90. package/src/config/env.ts +10 -0
  91. package/src/config/home-dir.ts +36 -0
  92. package/src/config/index.ts +5 -0
  93. package/src/config/layout.ts +52 -0
  94. package/src/config/limits.ts +56 -0
  95. package/src/config/ttl.ts +30 -0
  96. package/src/detect/harness.ts +73 -0
  97. package/src/detect/index.ts +1 -0
  98. package/src/detect/interfaces/types.ts +18 -0
  99. package/src/freshness/agent-evidence-record.ts +95 -0
  100. package/src/freshness/agent-evidence.ts +95 -0
  101. package/src/freshness/codex-spawn-evidence.ts +91 -0
  102. package/src/freshness/doc-helpers.ts +90 -0
  103. package/src/freshness/evidence-harvest-io.ts +72 -0
  104. package/src/freshness/evidence-harvest.ts +61 -0
  105. package/src/freshness/explore-tools.ts +95 -0
  106. package/src/freshness/index.ts +2 -0
  107. package/src/freshness/query-framework.ts +73 -0
  108. package/src/freshness/ref-evidence.ts +49 -0
  109. package/src/freshness/ref-journal.ts +98 -0
  110. package/src/freshness/trivial-edit-counter.ts +30 -0
  111. package/src/index.ts +11 -0
  112. package/src/init/index.ts +2 -0
  113. package/src/init/run.ts +32 -0
  114. package/src/init/templates.ts +75 -0
  115. package/src/memory/gitignore.ts +14 -0
  116. package/src/memory/index.ts +3 -0
  117. package/src/memory/registry.ts +39 -0
  118. package/src/memory/session-roots.ts +99 -0
  119. package/src/memory/state.ts +58 -0
  120. package/src/policy/apex-authorization.ts +100 -0
  121. package/src/policy/apex-gates.ts +91 -0
  122. package/src/policy/apex-target-steps.ts +73 -0
  123. package/src/policy/apex-target.ts +49 -0
  124. package/src/policy/apex-task-context.ts +81 -0
  125. package/src/policy/apex.ts +94 -0
  126. package/src/policy/cartographer/build-tree.ts +69 -0
  127. package/src/policy/cartographer/describe.ts +53 -0
  128. package/src/policy/cartographer/entry.ts +38 -0
  129. package/src/policy/cartographer/frontmatter.ts +48 -0
  130. package/src/policy/cartographer/indicators.ts +34 -0
  131. package/src/policy/claude-md-context.ts +66 -0
  132. package/src/policy/conventions/index.ts +11 -0
  133. package/src/policy/conventions/interfaces.ts +71 -0
  134. package/src/policy/conventions/langs.ts +53 -0
  135. package/src/policy/conventions/query.ts +49 -0
  136. package/src/policy/conventions/react-hooks.ts +18 -0
  137. package/src/policy/conventions/store-or-query.ts +21 -0
  138. package/src/policy/conventions/stores.ts +56 -0
  139. package/src/policy/conventions/strip.ts +87 -0
  140. package/src/policy/conventions/verdict.ts +38 -0
  141. package/src/policy/creation-intent.ts +11 -0
  142. package/src/policy/deny-loop.ts +88 -0
  143. package/src/policy/design/allowed-write.ts +28 -0
  144. package/src/policy/design/content-checks.ts +45 -0
  145. package/src/policy/design/corpus-fs.ts +21 -0
  146. package/src/policy/design/corpus-resolve-cache.ts +49 -0
  147. package/src/policy/design/corpus-resolve.ts +99 -0
  148. package/src/policy/design/corpus.ts +92 -0
  149. package/src/policy/design/design-system-rules.ts +45 -0
  150. package/src/policy/design/flag.ts +30 -0
  151. package/src/policy/design/gates-pipeline.ts +105 -0
  152. package/src/policy/design/gates.ts +87 -0
  153. package/src/policy/design/screenshot-tools.ts +24 -0
  154. package/src/policy/design/skill-evidence.ts +34 -0
  155. package/src/policy/design/skill-gate.ts +81 -0
  156. package/src/policy/design/skill-triggers.ts +51 -0
  157. package/src/policy/design/state.ts +92 -0
  158. package/src/policy/design/template-urls.ts +54 -0
  159. package/src/policy/design/transitions.ts +68 -0
  160. package/src/policy/detect-claude-md-project-type.ts +32 -0
  161. package/src/policy/detect-framework.ts +99 -0
  162. package/src/policy/detect-primitive-checks.ts +100 -0
  163. package/src/policy/detect-primitive-lib.ts +55 -0
  164. package/src/policy/detect-project.ts +99 -0
  165. package/src/policy/edit-outcome.ts +53 -0
  166. package/src/policy/evaluate.ts +81 -0
  167. package/src/policy/expert-agents.ts +60 -0
  168. package/src/policy/file-size-scope.ts +32 -0
  169. package/src/policy/file-size.ts +99 -0
  170. package/src/policy/framework-solid-exclude.ts +52 -0
  171. package/src/policy/framework-solid-extended.ts +93 -0
  172. package/src/policy/framework-solid-gates-systems.ts +99 -0
  173. package/src/policy/framework-solid-gates.ts +98 -0
  174. package/src/policy/framework-solid.ts +88 -0
  175. package/src/policy/gemini-mcp-gate.ts +83 -0
  176. package/src/policy/git-gates.ts +40 -0
  177. package/src/policy/guards/bash-command-anchor.ts +40 -0
  178. package/src/policy/guards/bash-write-patterns.ts +140 -0
  179. package/src/policy/guards/bash-write-redirects.ts +201 -0
  180. package/src/policy/guards/bash-write-safe-paths.ts +62 -0
  181. package/src/policy/guards/bash-write.ts +76 -0
  182. package/src/policy/guards/context.ts +12 -0
  183. package/src/policy/guards/index.ts +61 -0
  184. package/src/policy/guards/install.ts +22 -0
  185. package/src/policy/guards/interface-separation-ext.ts +41 -0
  186. package/src/policy/guards/interface-separation.ts +100 -0
  187. package/src/policy/guards/protected-path.ts +100 -0
  188. package/src/policy/guards/security.ts +97 -0
  189. package/src/policy/index.ts +19 -0
  190. package/src/policy/interfaces/types.ts +46 -0
  191. package/src/policy/js-gate-route.ts +51 -0
  192. package/src/policy/lessons/lesson-gate.ts +0 -0
  193. package/src/policy/lessons/trigger-index.ts +84 -0
  194. package/src/policy/lessons/types.ts +31 -0
  195. package/src/policy/module-layout.ts +31 -0
  196. package/src/policy/nearest-manifest.ts +75 -0
  197. package/src/policy/never-approval.ts +95 -0
  198. package/src/policy/patterns.ts +80 -0
  199. package/src/policy/seo/validate.ts +51 -0
  200. package/src/policy/shadcn-project.ts +22 -0
  201. package/src/policy/shadcn-skill-gate.ts +97 -0
  202. package/src/policy/shell-read-refs.ts +96 -0
  203. package/src/policy/skill-path.ts +46 -0
  204. package/src/policy/skill-patterns/laravel.ts +40 -0
  205. package/src/policy/skill-patterns/nextjs.ts +29 -0
  206. package/src/policy/skill-patterns/react.ts +27 -0
  207. package/src/policy/skill-patterns/shadcn-subskills.ts +33 -0
  208. package/src/policy/skill-patterns/shadcn.ts +66 -0
  209. package/src/policy/skill-patterns/swift.ts +30 -0
  210. package/src/policy/skill-patterns/tailwind.ts +40 -0
  211. package/src/policy/skill-source.ts +52 -0
  212. package/src/policy/skill-trigger-patterns.ts +33 -0
  213. package/src/policy/skill-triggers.ts +108 -0
  214. package/src/policy/tailwind-skill-gate.ts +63 -0
  215. package/src/policy/trivial-edits.ts +52 -0
  216. package/src/policy/verbosity.ts +39 -0
  217. package/src/prompt/index.ts +1 -0
  218. package/src/prompt/types.ts +36 -0
  219. package/src/refs/discover.ts +69 -0
  220. package/src/refs/frontmatter.ts +24 -0
  221. package/src/refs/index.ts +4 -0
  222. package/src/refs/loader.ts +59 -0
  223. package/src/refs/ref-key.ts +41 -0
  224. package/src/refs/router.ts +63 -0
  225. package/src/refs/types.ts +25 -0
  226. package/src/runtime/activity.ts +99 -0
  227. package/src/runtime/apply-patch-gate.ts +41 -0
  228. package/src/runtime/burst-window.ts +21 -0
  229. package/src/runtime/codex-shell-tool.ts +37 -0
  230. package/src/runtime/command-string.ts +37 -0
  231. package/src/runtime/confirm/codex-confirm.ts +126 -0
  232. package/src/runtime/confirm/codex-prompt-origin.ts +16 -0
  233. package/src/runtime/confirm/confirm-code.ts +33 -0
  234. package/src/runtime/confirm/confirm-gate.ts +65 -0
  235. package/src/runtime/confirm/confirm-irreversible.ts +32 -0
  236. package/src/runtime/confirm/confirm-pending.ts +34 -0
  237. package/src/runtime/confirm/confirm-state.ts +56 -0
  238. package/src/runtime/confirm/confirm-subagent.ts +112 -0
  239. package/src/runtime/confirm/confirm-submit.ts +38 -0
  240. package/src/runtime/core-guards-active.ts +84 -0
  241. package/src/runtime/core-guards-wired.ts +47 -0
  242. package/src/runtime/deny-loop-store.ts +94 -0
  243. package/src/runtime/deny-notice.ts +52 -0
  244. package/src/runtime/design-cache-resolve.ts +51 -0
  245. package/src/runtime/design-content-gate.ts +95 -0
  246. package/src/runtime/design-files-gate.ts +114 -0
  247. package/src/runtime/design-helpers.ts +93 -0
  248. package/src/runtime/design-lifecycle.ts +39 -0
  249. package/src/runtime/design-read-credit.ts +28 -0
  250. package/src/runtime/design.ts +116 -0
  251. package/src/runtime/dev-context.ts +43 -0
  252. package/src/runtime/dry-patterns.ts +32 -0
  253. package/src/runtime/dry.ts +96 -0
  254. package/src/runtime/fragment-registry.ts +33 -0
  255. package/src/runtime/framework-skill-gate.ts +79 -0
  256. package/src/runtime/fs-cleanup.ts +71 -0
  257. package/src/runtime/gate-apex.ts +81 -0
  258. package/src/runtime/gate-helpers.ts +52 -0
  259. package/src/runtime/gate-input.ts +26 -0
  260. package/src/runtime/gate.ts +128 -0
  261. package/src/runtime/handle-post.ts +104 -0
  262. package/src/runtime/handle-pre.ts +137 -0
  263. package/src/runtime/handle-scope-async.ts +29 -0
  264. package/src/runtime/handle-types.ts +27 -0
  265. package/src/runtime/handle.ts +116 -0
  266. package/src/runtime/home-state.ts +55 -0
  267. package/src/runtime/index.ts +18 -0
  268. package/src/runtime/inform.ts +28 -0
  269. package/src/runtime/inject-budget-recap.ts +47 -0
  270. package/src/runtime/inject-budget.ts +81 -0
  271. package/src/runtime/inject-context.ts +85 -0
  272. package/src/runtime/inject-dedup-exclusive.ts +98 -0
  273. package/src/runtime/inject-dedup.ts +89 -0
  274. package/src/runtime/is-agent-tool.ts +19 -0
  275. package/src/runtime/lifecycle/agent-files.ts +49 -0
  276. package/src/runtime/lifecycle/agent-memory.ts +82 -0
  277. package/src/runtime/lifecycle/agent-transcript.ts +80 -0
  278. package/src/runtime/lifecycle/aipilot/analytics.ts +99 -0
  279. package/src/runtime/lifecycle/aipilot/apex-task-store.ts +61 -0
  280. package/src/runtime/lifecycle/aipilot/cache-base.ts +48 -0
  281. package/src/runtime/lifecycle/aipilot/cache-doc.ts +100 -0
  282. package/src/runtime/lifecycle/aipilot/cache-lessons.ts +58 -0
  283. package/src/runtime/lifecycle/aipilot/cache-test.ts +70 -0
  284. package/src/runtime/lifecycle/aipilot/curate-lessons.ts +64 -0
  285. package/src/runtime/lifecycle/aipilot/dispatch-aipilot.ts +100 -0
  286. package/src/runtime/lifecycle/aipilot/doc-cache-gate.ts +69 -0
  287. package/src/runtime/lifecycle/aipilot/inject-apex.ts +98 -0
  288. package/src/runtime/lifecycle/aipilot/inject-doc.ts +62 -0
  289. package/src/runtime/lifecycle/aipilot/inject-explore.ts +64 -0
  290. package/src/runtime/lifecycle/aipilot/inject-lessons.ts +61 -0
  291. package/src/runtime/lifecycle/aipilot/inject-test.ts +48 -0
  292. package/src/runtime/lifecycle/aipilot/lesson-archive.ts +60 -0
  293. package/src/runtime/lifecycle/aipilot/lesson-inject.ts +98 -0
  294. package/src/runtime/lifecycle/aipilot/lesson-parse.ts +74 -0
  295. package/src/runtime/lifecycle/aipilot/lessons.ts +72 -0
  296. package/src/runtime/lifecycle/aipilot/promote-global-lessons.ts +92 -0
  297. package/src/runtime/lifecycle/aipilot/solid-compliance.ts +69 -0
  298. package/src/runtime/lifecycle/aipilot/solid-notice.ts +47 -0
  299. package/src/runtime/lifecycle/aipilot/solid-transcript.ts +60 -0
  300. package/src/runtime/lifecycle/aipilot/source-scan.ts +56 -0
  301. package/src/runtime/lifecycle/aipilot/sync-task.ts +75 -0
  302. package/src/runtime/lifecycle/aipilot/transcript.ts +88 -0
  303. package/src/runtime/lifecycle/aipilot/types.ts +72 -0
  304. package/src/runtime/lifecycle/auto-document-reads.ts +73 -0
  305. package/src/runtime/lifecycle/cartographer/detect.ts +57 -0
  306. package/src/runtime/lifecycle/cartographer/ecosystem-map.ts +56 -0
  307. package/src/runtime/lifecycle/cartographer/fs-util.ts +64 -0
  308. package/src/runtime/lifecycle/cartographer/merge.ts +51 -0
  309. package/src/runtime/lifecycle/cartographer/project-map.ts +49 -0
  310. package/src/runtime/lifecycle/cartographer/scan-hooks.ts +48 -0
  311. package/src/runtime/lifecycle/cartographer/scan.ts +81 -0
  312. package/src/runtime/lifecycle/cartographer/session-start.ts +33 -0
  313. package/src/runtime/lifecycle/cartographer/track-enrichment.ts +45 -0
  314. package/src/runtime/lifecycle/cartographer/write-plugin-map.ts +43 -0
  315. package/src/runtime/lifecycle/cartographer/write-tree.ts +44 -0
  316. package/src/runtime/lifecycle/changelog-research.ts +43 -0
  317. package/src/runtime/lifecycle/check-file-size.ts +61 -0
  318. package/src/runtime/lifecycle/codex-resync/fingerprint.ts +63 -0
  319. package/src/runtime/lifecycle/codex-resync/lock.ts +46 -0
  320. package/src/runtime/lifecycle/codex-resync/managed-destination.ts +39 -0
  321. package/src/runtime/lifecycle/codex-resync/materialize-agents.ts +57 -0
  322. package/src/runtime/lifecycle/codex-resync/plugin-files.ts +65 -0
  323. package/src/runtime/lifecycle/codex-resync/plugin-roots.ts +62 -0
  324. package/src/runtime/lifecycle/codex-resync/resync.ts +45 -0
  325. package/src/runtime/lifecycle/codex-resync/symlink-commands.ts +25 -0
  326. package/src/runtime/lifecycle/dispatch.ts +109 -0
  327. package/src/runtime/lifecycle/failure-lesson.ts +51 -0
  328. package/src/runtime/lifecycle/index.ts +34 -0
  329. package/src/runtime/lifecycle/inject-rules.ts +50 -0
  330. package/src/runtime/lifecycle/instructions-loaded.ts +22 -0
  331. package/src/runtime/lifecycle/kimi-rules-native.ts +44 -0
  332. package/src/runtime/lifecycle/lessons/dispatch.ts +88 -0
  333. package/src/runtime/lifecycle/lessons/reminder.ts +89 -0
  334. package/src/runtime/lifecycle/lessons/state.ts +23 -0
  335. package/src/runtime/lifecycle/memory/agent-lesson.ts +35 -0
  336. package/src/runtime/lifecycle/memory/capture-error.ts +41 -0
  337. package/src/runtime/lifecycle/memory/client.ts +65 -0
  338. package/src/runtime/lifecycle/memory/dispatch.ts +42 -0
  339. package/src/runtime/lifecycle/memory/recall.ts +30 -0
  340. package/src/runtime/lifecycle/memory/salience.ts +31 -0
  341. package/src/runtime/lifecycle/memory/state.ts +49 -0
  342. package/src/runtime/lifecycle/memory/track-ops.ts +15 -0
  343. package/src/runtime/lifecycle/post-compact.ts +38 -0
  344. package/src/runtime/lifecycle/post-edit-ts.ts +49 -0
  345. package/src/runtime/lifecycle/post-tracking.ts +40 -0
  346. package/src/runtime/lifecycle/pre-compact.ts +38 -0
  347. package/src/runtime/lifecycle/rules-root.ts +83 -0
  348. package/src/runtime/lifecycle/security/check-skill.ts +63 -0
  349. package/src/runtime/lifecycle/security/scan-patterns.ts +53 -0
  350. package/src/runtime/lifecycle/security/scan.ts +76 -0
  351. package/src/runtime/lifecycle/security/skill-state.ts +47 -0
  352. package/src/runtime/lifecycle/security/track-mcp.ts +29 -0
  353. package/src/runtime/lifecycle/security/track-skill-read.ts +31 -0
  354. package/src/runtime/lifecycle/seo/post-tool-use.ts +45 -0
  355. package/src/runtime/lifecycle/session-end.ts +17 -0
  356. package/src/runtime/lifecycle/session-start.ts +57 -0
  357. package/src/runtime/lifecycle/snapshot/board.ts +27 -0
  358. package/src/runtime/lifecycle/snapshot/format.ts +60 -0
  359. package/src/runtime/lifecycle/snapshot/git.ts +71 -0
  360. package/src/runtime/lifecycle/snapshot/index.ts +52 -0
  361. package/src/runtime/lifecycle/snapshot/version.ts +34 -0
  362. package/src/runtime/lifecycle/solid-detect.ts +56 -0
  363. package/src/runtime/lifecycle/stop-core.ts +35 -0
  364. package/src/runtime/lifecycle/subagent-cache.ts +61 -0
  365. package/src/runtime/lifecycle/task-completed.ts +94 -0
  366. package/src/runtime/lifecycle/teammate-idle-check.ts +63 -0
  367. package/src/runtime/lifecycle/teammate-idle.ts +28 -0
  368. package/src/runtime/lifecycle/tool-failure.ts +24 -0
  369. package/src/runtime/lifecycle/track-changes.ts +61 -0
  370. package/src/runtime/lifecycle/validate-solid.ts +70 -0
  371. package/src/runtime/lifecycle/validate-tailwind.ts +80 -0
  372. package/src/runtime/lifecycle-bridge.ts +74 -0
  373. package/src/runtime/mcp-key.ts +44 -0
  374. package/src/runtime/mcp-tool-name.ts +92 -0
  375. package/src/runtime/mcp.ts +91 -0
  376. package/src/runtime/modular.ts +77 -0
  377. package/src/runtime/normalize.ts +107 -0
  378. package/src/runtime/notices.ts +91 -0
  379. package/src/runtime/notification-sound.ts +49 -0
  380. package/src/runtime/notifications.ts +77 -0
  381. package/src/runtime/paths.ts +59 -0
  382. package/src/runtime/post-fanout.ts +41 -0
  383. package/src/runtime/post-outcome.ts +61 -0
  384. package/src/runtime/pre-allow.ts +97 -0
  385. package/src/runtime/precommit.ts +60 -0
  386. package/src/runtime/prompt-text.ts +38 -0
  387. package/src/runtime/receipt-capture.ts +34 -0
  388. package/src/runtime/record.ts +26 -0
  389. package/src/runtime/respond.ts +92 -0
  390. package/src/runtime/solid-file-size-gate.ts +58 -0
  391. package/src/runtime/solid-pre.ts +53 -0
  392. package/src/runtime/storage.ts +9 -0
  393. package/src/statusline/colors.ts +31 -0
  394. package/src/statusline/constants.ts +17 -0
  395. package/src/statusline/formatters.ts +54 -0
  396. package/src/statusline/index.ts +4 -0
  397. package/src/statusline/progress-bar.ts +46 -0
  398. package/src/tracking/codex-post-failure.ts +73 -0
  399. package/src/tracking/index.ts +2 -0
  400. package/src/tracking/integrity.ts +99 -0
  401. package/src/tracking/one-shot-dedup.ts +34 -0
  402. package/src/tracking/one-shot-failure.ts +42 -0
  403. package/src/tracking/one-shot-store.ts +95 -0
  404. package/src/tracking/one-shot.ts +93 -0
  405. package/src/tracking/receipts.ts +95 -0
  406. package/src/tracking/session-state.ts +94 -0
  407. package/src/tracking/store.ts +96 -0
  408. package/src/tracking/track-compact.ts +100 -0
  409. package/src/tracking/track-diff.ts +50 -0
  410. package/src/tracking/track-journal.ts +97 -0
  411. package/src/tracking/track-lock-sync.ts +83 -0
  412. package/src/tracking/track-lock.ts +67 -0
  413. package/src/util/compact-json.ts +23 -0
  414. package/src/util/index.ts +2 -0
  415. package/src/util/json-io.ts +47 -0
  416. package/src/util/project-root.ts +41 -0
  417. package/src/util/runtime-io.ts +85 -0
  418. package/src/util/semver.ts +30 -0
  419. package/dist/permission-mode-B0MUl7FT.mjs +0 -136
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Lesson aggregation, dedup, merge, and edit categorization for the ai-pilot
3
+ * scope. Ported from the ai-pilot plugin's `cache/lesson-aggregator.ts` +
4
+ * `cache/lesson-helpers.ts` (now removed).
5
+ */
6
+ import { existsSync, readdirSync } from "node:fs";
7
+ import { join } from "node:path";
8
+ import { homedir } from "node:os";
9
+ import { readJsonFile } from "../../../util/json-io";
10
+ import { cacheBaseDir } from "./cache-base";
11
+ import type { EditEntry, LessonEntry } from "./types";
12
+
13
+ /** Deduplicate lessons by `error_type:pattern`, summing counts. */
14
+ export function dedupLessons(lessons: LessonEntry[]): LessonEntry[] {
15
+ const groups = new Map<string, LessonEntry[]>();
16
+ for (const l of lessons) {
17
+ const key = `${l.error_type}:${l.pattern}`;
18
+ const group = groups.get(key);
19
+ if (group) group.push(l); else groups.set(key, [l]);
20
+ }
21
+ return [...groups.values()].map((entries) => {
22
+ const first = entries[0] as LessonEntry;
23
+ return {
24
+ error_type: first.error_type,
25
+ pattern: first.pattern,
26
+ fix: first.fix,
27
+ last_seen: first.last_seen,
28
+ count: entries.reduce((s, e) => s + e.count, 0),
29
+ files: [...new Set(entries.flatMap((e) => e.files))],
30
+ code: { line: [...new Set(entries.flatMap((e) => e.code?.line ?? []))].slice(0, 5) },
31
+ };
32
+ }).sort((a, b) => b.count - a.count);
33
+ }
34
+
35
+ /** Aggregate all local lesson JSON files into a flat deduplicated list. */
36
+ export async function aggregateLocalLessons(cacheDir: string): Promise<LessonEntry[]> {
37
+ if (!existsSync(cacheDir)) return [];
38
+ const files = readdirSync(cacheDir).filter((f) => f.endsWith(".json"));
39
+ const all: LessonEntry[] = [];
40
+ for (const f of files) {
41
+ const data = await readJsonFile<{ errors?: LessonEntry[] }>(join(cacheDir, f));
42
+ if (data?.errors) all.push(...data.errors);
43
+ }
44
+ return dedupLessons(all);
45
+ }
46
+
47
+ /** Load global lessons for a stack (stack-specific + universal). */
48
+ export async function loadGlobalLessons(stack: string, home: string = homedir()): Promise<LessonEntry[]> {
49
+ const globalDir = join(cacheBaseDir(home), "lessons", "_global");
50
+ const result: LessonEntry[] = [];
51
+ for (const name of [`${stack}.json`, "universal.json"]) {
52
+ const data = await readJsonFile<LessonEntry[]>(join(globalDir, name));
53
+ if (data) result.push(...data);
54
+ }
55
+ return result;
56
+ }
57
+
58
+ /** Merge local + global lessons, dedup by type+pattern, sort by count desc. */
59
+ export function mergeLessons(local: LessonEntry[], global: LessonEntry[]): LessonEntry[] {
60
+ return dedupLessons([...local, ...global]);
61
+ }
62
+
63
+ /** Categorize an edit entry by analyzing the new code content. */
64
+ export function categorizeEdit(edit: EditEntry): string {
65
+ const n = edit.newStr.toLowerCase();
66
+ if (n.includes("use client")) return "missing_directive";
67
+ if (n.includes("displayname")) return "missing_display_name";
68
+ if (/onkeydown|tabindex|role=/.test(n)) return "missing_a11y";
69
+ if (/try|catch/.test(n)) return "missing_error_handling";
70
+ if (/\?\?|if.*null/.test(n)) return "null_safety";
71
+ return "code_fix";
72
+ }
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Promote high-frequency project lessons (occurrence count >= 3) to the global,
3
+ * stack-scoped cache (`cache/lessons/_global/<stack>.json`, cap 25).
4
+ * Ports the ai-pilot `promote-global-lessons.ts` (a spawned subprocess) as an
5
+ * in-process call from `cacheSniperLessons`. Pure side-effect.
6
+ */
7
+ import { homedir } from "node:os";
8
+ import { mkdirSync, readdirSync } from "node:fs";
9
+ import { join } from "node:path";
10
+ import { readJsonFile, writeJsonFile } from "../../../util/json-io";
11
+ import { cacheBaseDir } from "./cache-base";
12
+ import { logCacheEvent } from "./analytics";
13
+ import type { LessonEntry } from "./types";
14
+
15
+ /** A promoted global lesson tagged with the project hashes that produced it. */
16
+ export interface GlobalLesson extends LessonEntry {
17
+ source_projects: string[];
18
+ }
19
+
20
+ const MIN_COUNT = 3;
21
+ const MAX_GLOBAL = 25;
22
+
23
+ /** Group lessons by `error_type:pattern`. */
24
+ function groupByKey<T extends LessonEntry>(lessons: T[]): T[][] {
25
+ const groups = new Map<string, T[]>();
26
+ for (const l of lessons) {
27
+ const key = `${l.error_type}:${l.pattern}`;
28
+ (groups.get(key) ?? groups.set(key, []).get(key)!).push(l);
29
+ }
30
+ return [...groups.values()];
31
+ }
32
+
33
+ /** Read every project lesson file's `errors` into one flat (un-deduped) list. */
34
+ async function aggregateErrors(cacheDir: string): Promise<LessonEntry[]> {
35
+ let files: string[];
36
+ try { files = readdirSync(cacheDir).filter((f) => f.endsWith(".json")); } catch { return []; }
37
+ const all: LessonEntry[] = [];
38
+ for (const f of files) {
39
+ const data = await readJsonFile<{ errors?: LessonEntry[] }>(join(cacheDir, f));
40
+ if (data?.errors) all.push(...data.errors);
41
+ }
42
+ return all;
43
+ }
44
+
45
+ /** Keep groups seen >= MIN_COUNT times, collapsed to a single lesson each. */
46
+ function filterFrequent(lessons: LessonEntry[]): LessonEntry[] {
47
+ return groupByKey(lessons).filter((e) => e.length >= MIN_COUNT).map((entries) => {
48
+ const first = entries[0] as LessonEntry;
49
+ return {
50
+ error_type: first.error_type, pattern: first.pattern, fix: first.fix, last_seen: first.last_seen,
51
+ count: entries.length,
52
+ files: [...new Set(entries.flatMap((e) => e.files))],
53
+ code: { line: [...new Set(entries.flatMap((e) => e.code?.line ?? []))].slice(0, 5) },
54
+ };
55
+ });
56
+ }
57
+
58
+ /** Merge tagged candidates into existing global lessons; dedup, sum, sort, cap. */
59
+ function mergeGlobal(existing: GlobalLesson[], candidates: LessonEntry[], projHash: string): GlobalLesson[] {
60
+ const tagged: GlobalLesson[] = candidates.map((c) => ({ ...c, source_projects: [projHash] }));
61
+ return groupByKey([...existing, ...tagged]).map((entries) => {
62
+ const first = entries[0] as GlobalLesson;
63
+ return {
64
+ error_type: first.error_type, pattern: first.pattern, fix: first.fix, last_seen: first.last_seen,
65
+ count: entries.reduce((sum, e) => sum + e.count, 0),
66
+ files: [...new Set(entries.flatMap((e) => e.files))],
67
+ code: { line: [...new Set(entries.flatMap((e) => e.code?.line ?? []))].slice(0, 5) },
68
+ source_projects: [...new Set(entries.flatMap((e) => e.source_projects ?? []))],
69
+ };
70
+ }).sort((a, b) => b.count - a.count).slice(0, MAX_GLOBAL);
71
+ }
72
+
73
+ /**
74
+ * Promote high-frequency lessons from `cacheDir` to the global stack file.
75
+ * @param cacheDir - Per-project lessons cache dir to scan.
76
+ * @param stack - Detected project stack (`nextjs`, `laravel`, …, `universal`).
77
+ * @param projHash - Source project hash (tags promoted entries).
78
+ * @param home - Home dir (defaults to `~`).
79
+ */
80
+ export async function promoteGlobalLessons(cacheDir: string, stack: string, projHash: string, home: string = homedir()): Promise<void> {
81
+ const candidates = filterFrequent(await aggregateErrors(cacheDir));
82
+ if (candidates.length === 0) return;
83
+
84
+ const globalDir = join(cacheBaseDir(home), "lessons", "_global");
85
+ mkdirSync(globalDir, { recursive: true });
86
+ const globalFile = join(globalDir, `${stack}.json`);
87
+ const existing = (await readJsonFile<GlobalLesson[]>(globalFile)) ?? [];
88
+ const merged = mergeGlobal(existing, candidates, projHash);
89
+
90
+ await writeJsonFile(globalFile, merged, true);
91
+ logCacheEvent("lessons", "promoted", projHash, { count: merged.length, stack }, home);
92
+ }
@@ -0,0 +1,69 @@
1
+ /**
2
+ * PostToolUse (matcher "Write|Edit") for the ai-pilot scope: validate SOLID
3
+ * file-size and interface-location compliance right after a code write.
4
+ * Ports `check-solid-compliance.py`.
5
+ */
6
+ import { readText, pathExists } from "../../../util/runtime-io";
7
+ import { contextResponse } from "../../../adapters/claude";
8
+ import { resolveMaxLines, splitTarget } from "../../../config/limits";
9
+ import { maskCommentsAndStrings } from "../../../policy/conventions/strip";
10
+ import { isVendorPath } from "../../../policy/conventions/langs";
11
+
12
+ const CODE_EXTENSIONS = /\.(ts|tsx|js|jsx|py|php|swift|go|rs|rb|java|astro)$/;
13
+ const COMPONENT_DIR = /\/(components|pages|views)\//;
14
+ const BACKEND_DIR = /\/(Controllers|Models|Services)\//;
15
+ const TS_INTERFACE_RE = /^(export )?(interface|type) [A-Z]/m;
16
+ const PHP_INTERFACE_RE = /^interface /m;
17
+
18
+ /** Count non-empty, non-comment (`//`/`#`/`*`-prefixed) lines. Shared with {@link checkSolidFromTranscript}. */
19
+ export function countCodeLines(content: string): number {
20
+ let count = 0;
21
+ for (const raw of content.split("\n")) {
22
+ const s = raw.trim();
23
+ if (!s || s.startsWith("//") || s.startsWith("#") || s.startsWith("*")) continue;
24
+ count++;
25
+ }
26
+ return count;
27
+ }
28
+
29
+ /**
30
+ * Validate SOLID compliance for a just-written file.
31
+ * @param payload - The raw PostToolUse hook payload (`tool_name`, `tool_input.file_path`).
32
+ * @returns The PostToolUse `additionalContext` response, or `""` when clean/not applicable.
33
+ */
34
+ export function checkSolidCompliance(payload: Record<string, unknown>): string {
35
+ const tool = String(payload.tool_name ?? "");
36
+ if (tool !== "Write" && tool !== "Edit") return "";
37
+ const input = (payload.tool_input as Record<string, unknown> | undefined) ?? {};
38
+ const filePath = typeof input.file_path === "string" ? input.file_path : "";
39
+ if (!filePath || !CODE_EXTENSIONS.test(filePath) || !pathExists(filePath)) return "";
40
+ if (isVendorPath(filePath)) return ""; // dependency/build dirs are never convention-gated
41
+
42
+ let content: string;
43
+ try {
44
+ content = readText(filePath);
45
+ } catch {
46
+ return "";
47
+ }
48
+
49
+ const violations: string[] = [];
50
+ const max = resolveMaxLines();
51
+ const split = splitTarget(max);
52
+ const lc = countCodeLines(content);
53
+ if (lc > max) violations.push(`FILE SIZE: ${lc} lines (max: ${max})`);
54
+ else if (lc > split) violations.push(`FILE SIZE WARNING: ${lc} lines (split at ${split})`);
55
+
56
+ // Per-extension matching on MASKED content (no cross-language regex bleed,
57
+ // no comment/string false positives).
58
+ const masked = maskCommentsAndStrings(content, filePath.endsWith(".php") ? "php" : filePath.endsWith(".py") ? "py" : "c");
59
+ if (COMPONENT_DIR.test(filePath) && /\.(ts|tsx|js|jsx|astro)$/.test(filePath)) {
60
+ if (TS_INTERFACE_RE.test(masked)) violations.push("INTERFACE LOCATION: Move to src/interfaces/");
61
+ } else if (BACKEND_DIR.test(filePath) && filePath.endsWith(".php")) {
62
+ if (PHP_INTERFACE_RE.test(masked)) violations.push("INTERFACE LOCATION: Move to app/Contracts/");
63
+ }
64
+
65
+ if (violations.length === 0) return "";
66
+ const name = filePath.split("/").pop() ?? filePath;
67
+ const message = `SOLID COMPLIANCE CHECK: ${name}\n\n${violations.join("\n")}\nINSTRUCTION: Fix violations before continuing.\nRun sniper agent for full validation.`;
68
+ return contextResponse("PostToolUse", message);
69
+ }
@@ -0,0 +1,47 @@
1
+ /**
2
+ * SubagentStop SOLID-notice dedup (anti-loop): `checkSolidFromTranscript` is
3
+ * stateless, so an unchanged violation set re-emitted the same
4
+ * `additionalContext` on EVERY SubagentStop — re-invoking the agent in an
5
+ * observed ~15-repeat loop. The dedup fingerprint is a SHA-256 hash of the
6
+ * sorted `file:message` set, persisted per agent key in the canonical
7
+ * `.harness/track/solid-notice.json` sidecar (`config/layout.ts`), written
8
+ * under the synchronous track lock (this hook runs in an ephemeral sync
9
+ * process). Semantics: identical fingerprint within the TTL → silence;
10
+ * changed/absent/expired fingerprint → emit once and record (the TTL derives
11
+ * from `FUSE_ENFORCE_TTL_SEC`, no new env var). On lock contention the notice
12
+ * is emitted anyway (visibility beats a muted violation) but not recorded.
13
+ */
14
+ import { createHash } from "node:crypto";
15
+ import { readFileSync } from "node:fs";
16
+ import { dirname } from "node:path";
17
+ import { atomicWrite } from "../../../util/json-io";
18
+ import { LOCK_FAILED, withTrackLockSync } from "../../../tracking/track-lock-sync";
19
+
20
+ /** SHA-256 (truncated) of the sorted violation set — the dedup fingerprint. */
21
+ export function noticeFingerprint(violations: string[]): string {
22
+ return createHash("sha256").update([...violations].sort().join("\n")).digest("hex").slice(0, 16);
23
+ }
24
+
25
+ /** Sidecar shape: agent key → last emitted fingerprint + timestamp. */
26
+ type NoticeState = Record<string, { hash: string; at: number }>;
27
+
28
+ /**
29
+ * Decide whether the notice for `key` may be emitted, recording it when so.
30
+ * @param noticeFile - `projectLayout(cwd).solidNoticeFile`.
31
+ * @param key - Agent/transcript identity (e.g. `agent_transcript_path`).
32
+ * @param hash - The current set's fingerprint ({@link noticeFingerprint}).
33
+ * @param now - Clock (epoch ms).
34
+ * @param ttlMs - Reminder window (derived from `FUSE_ENFORCE_TTL_SEC`).
35
+ */
36
+ export function shouldEmitNotice(noticeFile: string, key: string, hash: string, now: number, ttlMs: number): boolean {
37
+ let state: NoticeState = {};
38
+ try { state = JSON.parse(readFileSync(noticeFile, "utf8")) as NoticeState; } catch { /* absent/corrupt → emit */ }
39
+ const prev = state[key];
40
+ if (prev && prev.hash === hash && now - prev.at < ttlMs) return false;
41
+ const next: NoticeState = { ...state, [key]: { hash, at: now } };
42
+ const ran = withTrackLockSync(dirname(noticeFile), () => {
43
+ try { atomicWrite(noticeFile, JSON.stringify(next)); } catch { /* fail-open */ }
44
+ });
45
+ void (ran === LOCK_FAILED); // emit regardless (see module doc)
46
+ return true;
47
+ }
@@ -0,0 +1,60 @@
1
+ /**
2
+ * SubagentStop (matcher "") for the ai-pilot scope: check SOLID file-size and
3
+ * interface-location compliance for every file a sub-agent wrote/edited, per
4
+ * the transcript. Ports `check-solid-from-transcript.py`. Fires unconditionally
5
+ * for every sub-agent, unlike the sniper/research-expert-scoped caches here.
6
+ */
7
+ import { readText, pathExists } from "../../../util/runtime-io";
8
+ import { contextResponse } from "../../../adapters/claude";
9
+ import { resolveMaxLines } from "../../../config/limits";
10
+ import { resolveTtlSec } from "../../../config/ttl";
11
+ import { projectLayout } from "../../../config/layout";
12
+ import { transcriptFilePaths } from "./transcript";
13
+ import { countCodeLines } from "./solid-compliance";
14
+ import { noticeFingerprint, shouldEmitNotice } from "./solid-notice";
15
+
16
+ const CODE_EXTENSIONS = /\.(ts|tsx|js|jsx|py|go|rs|java|php|cpp|c|rb|swift|kt|dart|vue|svelte|astro)$/;
17
+ const INTERFACE_PATTERN = /^(export )?(interface|type) [A-Z]/m;
18
+ /** Segment-anchored dirs (a substring like `app/` matched `livetest-app/`). */
19
+ const INTERFACE_DIRS_RE = /\/(components|pages|views|app)\//;
20
+ /** Canonical homes — never a violation there (owner decision: types/ is
21
+ * canonical; Contracts/ is the Laravel home taught by solid-php/laravelGate). */
22
+ const INTERFACE_EXEMPT_RE = /\/(interfaces|types|Contracts|contracts)\//;
23
+
24
+ /**
25
+ * Check every Write/Edit target in a subagent transcript against the SOLID
26
+ * line-count ceiling and interface-location convention. Deduped per
27
+ * transcript (`solid-notice.ts`): an unchanged violation set is emitted once
28
+ * per TTL window, never in a loop.
29
+ * @param transcript - Path to the agent JSONL transcript (`agent_transcript_path`).
30
+ * @param cwd - Project root (locates the `.harness/track` dedup sidecar).
31
+ * @param now - Clock (tests inject it).
32
+ * @returns The SubagentStop `additionalContext` response, or `""` when clean/deduped.
33
+ */
34
+ export async function checkSolidFromTranscript(transcript: string | undefined, cwd: string = process.cwd(), now: number = Date.now()): Promise<string> {
35
+ if (!transcript || !pathExists(transcript)) return "";
36
+ const files = await transcriptFilePaths(transcript, ["Write", "Edit"]);
37
+ const max = resolveMaxLines();
38
+ const violations: string[] = [];
39
+
40
+ for (const fp of files.sort()) {
41
+ if (!pathExists(fp) || !CODE_EXTENSIONS.test(fp)) continue;
42
+ const name = fp.split("/").pop() ?? fp;
43
+ let content: string;
44
+ try {
45
+ content = readText(fp);
46
+ } catch {
47
+ continue;
48
+ }
49
+ const lc = countCodeLines(content);
50
+ if (lc > max) violations.push(`SOLID: ${name} = ${lc} lines (max ${max})`);
51
+ if (INTERFACE_DIRS_RE.test(fp) && !INTERFACE_EXEMPT_RE.test(fp) && INTERFACE_PATTERN.test(content)) {
52
+ const dest = fp.endsWith(".php") ? "app/Contracts/" : "interfaces/";
53
+ violations.push(`SOLID: ${name}: move interfaces to ${dest}`);
54
+ }
55
+ }
56
+
57
+ if (violations.length === 0) return "";
58
+ if (!shouldEmitNotice(projectLayout(cwd).solidNoticeFile, transcript, noticeFingerprint(violations), now, resolveTtlSec(process.env) * 1000)) return "";
59
+ return contextResponse("SubagentStop", `## SOLID VIOLATIONS DETECTED (subagent output)\n${violations.join("\n")}\nRun sniper to fix these issues.`);
60
+ }
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Source-file scanning + project-stack detection for the ai-pilot scope.
3
+ * Ported from the ai-pilot plugin's `cache/source-collector.ts` +
4
+ * the stack detection in `cache/lesson-helpers.ts` (now removed).
5
+ */
6
+ import { readdirSync } from "node:fs";
7
+ import { join } from "node:path";
8
+ import { collectFiles } from "../../../util/runtime-io";
9
+
10
+ /** Source extensions to collect (monorepo-aware, dot-prefixed for matching). */
11
+ const SRC_EXTS = new Set([".ts", ".tsx", ".js", ".jsx"]);
12
+ /** Roots walked: `src`, `app`, plus each child `src` under `apps/` and `packages/`. */
13
+ const TOP_DIRS = ["src", "app"] as const;
14
+ const NESTED_PARENTS = ["apps", "packages"] as const;
15
+
16
+ /** Collect the existing monorepo `src` roots nested under `apps/` and `packages/`. */
17
+ function nestedRoots(projectPath: string): string[] {
18
+ const roots: string[] = [];
19
+ for (const parent of NESTED_PARENTS) {
20
+ try {
21
+ for (const e of readdirSync(join(projectPath, parent), { withFileTypes: true })) {
22
+ if (e.isDirectory()) roots.push(join(projectPath, parent, e.name, "src"));
23
+ }
24
+ } catch { /* parent dir may not exist */ }
25
+ }
26
+ return roots;
27
+ }
28
+
29
+ /**
30
+ * Scan source files in `projectPath` (monorepo-aware), capped at `maxFiles`.
31
+ * Node+Bun portable: walks `node:fs` recursively (replaces the Bun `Glob`).
32
+ * @param projectPath - Absolute project root.
33
+ * @param maxFiles - Max files to collect (default 200).
34
+ * @returns Absolute paths matching the source extensions.
35
+ */
36
+ export async function scanSourceFiles(projectPath: string, maxFiles = 200): Promise<string[]> {
37
+ const files: string[] = [];
38
+ const roots = [...TOP_DIRS.map((d) => join(projectPath, d)), ...nestedRoots(projectPath)];
39
+ for (const root of roots) {
40
+ collectFiles(root, SRC_EXTS, files, maxFiles);
41
+ if (files.length >= maxFiles) break;
42
+ }
43
+ return files;
44
+ }
45
+
46
+ /** Detect the project stack from config files in the project root. */
47
+ export function detectStack(projectPath: string): string {
48
+ try {
49
+ const entries = readdirSync(projectPath);
50
+ if (entries.some((f) => f.startsWith("next.config"))) return "nextjs";
51
+ if (entries.includes("composer.json")) return "laravel";
52
+ if (entries.some((f) => f.endsWith(".xcodeproj")) || entries.includes("Package.swift")) return "swift";
53
+ if (entries.some((f) => f.startsWith("tailwind.config"))) return "tailwindcss";
54
+ } catch { /* fallback */ }
55
+ return "universal";
56
+ }
@@ -0,0 +1,75 @@
1
+ /**
2
+ * PostToolUse (matcher "TaskCreate|TaskUpdate") for the ai-pilot scope:
3
+ * synchronize Claude task tools with `.claude/apex/task.json` and prompt an
4
+ * auto-commit on completion when git changes are detected. Ports
5
+ * `sync-task-tracking.ts`.
6
+ */
7
+ import { existsSync } from "node:fs";
8
+ import { join } from "node:path";
9
+ import { readJsonFile } from "../../../util/json-io";
10
+ import { spawnCapture } from "../../../util/runtime-io";
11
+ import { contextResponse } from "../../../adapters/claude";
12
+ import { acquireLock, taskCreate, taskStart, taskComplete } from "./apex-task-store";
13
+ import { harnessHomeSegment } from "../../../policy/apex-target";
14
+ import type { ApexTaskFile } from "./types";
15
+
16
+ /** True when the project has uncommitted git changes. */
17
+ async function hasGitChanges(cwd: string): Promise<boolean> {
18
+ try {
19
+ return spawnCapture("git", ["status", "--porcelain"], cwd).trim().length > 0;
20
+ } catch { return false; }
21
+ }
22
+
23
+ /** Handle a completed task: emit the commit reminder (or the no-change note). */
24
+ async function onComplete(taskFile: string, taskId: string, projectRoot: string): Promise<string> {
25
+ await taskComplete(taskFile, taskId);
26
+ if (!(await hasGitChanges(projectRoot))) {
27
+ return contextResponse("PostToolUse", "Task completed. No changes to commit.");
28
+ }
29
+ const data = await readJsonFile<ApexTaskFile>(taskFile);
30
+ const subject = data?.tasks[taskId]?.subject ?? "Task";
31
+ return contextResponse("PostToolUse", `Task #${taskId} completed: ${subject}\n\nChanges detected. MANDATORY: Run /fuse-commit-pro:commit to commit with smart detection.`);
32
+ }
33
+
34
+ /**
35
+ * PostToolUse TaskCreate/TaskUpdate handler. Claude-only by design: Codex never
36
+ * emits these native task tools, so the `toolName` matcher below intentionally
37
+ * stays as-is — only the target apex-dir segment is harness-aware.
38
+ * @param payload - The raw hook payload (`tool_name`, `tool_input`, `tool_response`).
39
+ * @param cwd - Fallback project root (uses `CLAUDE_PROJECT_DIR` first).
40
+ * @param id - Harness target id (defaults to "claude-code" — zero-regression default).
41
+ * @returns The native hook stdout (possibly empty).
42
+ */
43
+ export async function syncTaskTracking(payload: Record<string, unknown>, cwd: string, id: string = "claude-code"): Promise<string> {
44
+ const toolName = String(payload.tool_name ?? "");
45
+ if (toolName !== "TaskCreate" && toolName !== "TaskUpdate") return "";
46
+
47
+ const projectRoot = process.env.CLAUDE_PROJECT_DIR ?? cwd;
48
+ const seg = harnessHomeSegment(id);
49
+ const taskFile = join(projectRoot, seg, "apex", "task.json");
50
+ if (!existsSync(taskFile)) return "";
51
+
52
+ const unlock = await acquireLock(join(projectRoot, seg, "apex", ".task.lock"), 10000);
53
+ if (!unlock) return "";
54
+ try {
55
+ const ti = (payload.tool_input ?? {}) as Record<string, string>;
56
+ if (toolName === "TaskCreate") {
57
+ const existing = (await readJsonFile<ApexTaskFile>(taskFile))?.tasks ?? {};
58
+ const respId = (payload.tool_response as { id?: string } | undefined)?.id;
59
+ const taskId = respId ?? String(Math.max(0, ...Object.keys(existing).map(Number)) + 1);
60
+ await taskCreate(taskFile, taskId, ti.subject ?? "", ti.description ?? "");
61
+ return "";
62
+ }
63
+ const taskId = ti.taskId ?? "";
64
+ if (!taskId) return "";
65
+ const newStatus = ti.status ?? "";
66
+ if (newStatus === "in_progress") {
67
+ const blocked = (ti.addBlockedBy as unknown as string[] | undefined)?.join(",") ?? "";
68
+ await taskStart(taskFile, taskId, ti.subject || undefined, ti.description || undefined, blocked || undefined);
69
+ }
70
+ if (newStatus === "completed") return onComplete(taskFile, taskId, projectRoot);
71
+ return "";
72
+ } finally {
73
+ await unlock();
74
+ }
75
+ }
@@ -0,0 +1,88 @@
1
+ /**
2
+ * JSONL transcript parsing helpers for the ai-pilot scope: project-root
3
+ * detection from file paths, file-path extraction, Edit extraction, and the
4
+ * assistant report extraction. Ported into the harness from the ai-pilot
5
+ * plugin's `cache/project-detect.ts` + `cache/lesson-helpers.ts` (now removed).
6
+ */
7
+ import { readdirSync } from "node:fs";
8
+ import { readText } from "../../../util/runtime-io";
9
+ import type { EditEntry } from "./types";
10
+
11
+ const ROOT_MARKERS = [".git", ".hg", "turbo.json", "nx.json", "lerna.json", "pnpm-workspace.yaml"];
12
+ const PKG_MARKERS = ["package.json", "composer.json", "Package.swift", "Cargo.toml", "go.mod", "pyproject.toml", "Gemfile", "pom.xml"];
13
+
14
+ /** Detect the project root by walking up from the first file path to a marker. */
15
+ export function projectRootFromPaths(filePaths: string[]): string | null {
16
+ const firstPath = filePaths[0];
17
+ if (!firstPath) return null;
18
+ const lastSlash = firstPath.lastIndexOf("/");
19
+ if (lastSlash <= 0) return null;
20
+ let dir = firstPath.substring(0, lastSlash);
21
+ let bestRoot: string | null = null;
22
+ while (dir && dir !== "/" && dir.length > 1) {
23
+ try {
24
+ const entries = readdirSync(dir);
25
+ if (ROOT_MARKERS.some((m) => entries.includes(m))) return dir;
26
+ if (PKG_MARKERS.some((m) => entries.includes(m))) bestRoot = dir;
27
+ } catch { break; }
28
+ const parentSlash = dir.lastIndexOf("/");
29
+ if (parentSlash <= 0) break;
30
+ dir = dir.substring(0, parentSlash);
31
+ }
32
+ return bestRoot;
33
+ }
34
+
35
+ /** Extract absolute file paths from tool_use entries in a JSONL transcript, optionally restricted to `toolNames` (default: any tool). */
36
+ export async function transcriptFilePaths(transcriptPath: string, toolNames?: readonly string[]): Promise<string[]> {
37
+ const text = readText(transcriptPath);
38
+ const paths = new Set<string>();
39
+ for (const line of text.split("\n").filter(Boolean)) {
40
+ try {
41
+ const content = (JSON.parse(line) as { message?: { content?: unknown } })?.message?.content;
42
+ if (!Array.isArray(content)) continue;
43
+ for (const block of content) {
44
+ if (block?.type !== "tool_use") continue;
45
+ if (toolNames && !toolNames.includes(block.name)) continue;
46
+ const fp = block.input?.file_path ?? block.input?.path ?? "";
47
+ if (typeof fp === "string" && fp.startsWith("/")) paths.add(fp);
48
+ }
49
+ } catch { /* skip malformed */ }
50
+ }
51
+ return [...paths];
52
+ }
53
+
54
+ /** Extract deduplicated Edit tool_use entries (keyed by basename) from a transcript. */
55
+ export async function transcriptEdits(transcriptPath: string): Promise<EditEntry[]> {
56
+ const text = readText(transcriptPath);
57
+ const edits: EditEntry[] = [];
58
+ for (const line of text.split("\n").filter(Boolean)) {
59
+ try {
60
+ const content = (JSON.parse(line) as { message?: { content?: unknown } })?.message?.content;
61
+ if (!Array.isArray(content)) continue;
62
+ for (const block of content) {
63
+ if (block?.type === "tool_use" && block.name === "Edit" && block.input?.file_path) {
64
+ edits.push({ file: block.input.file_path, oldStr: block.input.old_string ?? "", newStr: block.input.new_string ?? "" });
65
+ }
66
+ }
67
+ } catch { /* skip malformed */ }
68
+ }
69
+ const seen = new Map<string, EditEntry>();
70
+ for (const e of edits) seen.set(e.file.split("/").pop() ?? e.file, e);
71
+ return [...seen.values()];
72
+ }
73
+
74
+ /** Extract the last assistant text report (first 500 lines) from a transcript. */
75
+ export async function transcriptReport(transcriptPath: string): Promise<string> {
76
+ const text = readText(transcriptPath);
77
+ let lastReport = "";
78
+ for (const line of text.split("\n").filter(Boolean)) {
79
+ try {
80
+ const entry = JSON.parse(line) as { message?: { role?: string; content?: { type?: string; text?: string }[] } };
81
+ if (entry?.message?.role !== "assistant") continue;
82
+ for (const block of entry.message.content ?? []) {
83
+ if (block.type === "text" && block.text) lastReport = block.text;
84
+ }
85
+ } catch { /* skip malformed */ }
86
+ }
87
+ return lastReport.split("\n").slice(0, 500).join("\n");
88
+ }
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Cache + APEX interfaces for the ai-pilot scope (separated from implementation
3
+ * per SOLID interface-segregation).
4
+ */
5
+
6
+ /** Index file for the per-project documentation cache. */
7
+ export interface CacheIndex {
8
+ project: string;
9
+ docs: CacheEntry[];
10
+ }
11
+
12
+ /** Single cached document entry in the doc index. */
13
+ export interface CacheEntry {
14
+ hash: string;
15
+ library: string;
16
+ topic: string;
17
+ timestamp: string;
18
+ size_kb: number;
19
+ }
20
+
21
+ /** Lesson entry extracted from a sniper transcript. */
22
+ export interface LessonEntry {
23
+ error_type: string;
24
+ pattern: string;
25
+ fix: string;
26
+ count: number;
27
+ last_seen: string;
28
+ files: string[];
29
+ code: { line: string[] };
30
+ }
31
+
32
+ /** An Edit tool_use extracted from a sniper transcript. */
33
+ export interface EditEntry {
34
+ file: string;
35
+ oldStr: string;
36
+ newStr: string;
37
+ }
38
+
39
+ /** Cached lint/test result for a single source file. */
40
+ export interface TestResult {
41
+ checksum: string;
42
+ eslint: "pass" | "fail";
43
+ tsc: "pass" | "fail";
44
+ last_tested: string;
45
+ }
46
+
47
+ /** Test cache structure stored on disk. */
48
+ export interface TestCache {
49
+ timestamp: string;
50
+ files: Record<string, TestResult>;
51
+ }
52
+
53
+ /** A task entry in `.claude/apex/task.json`. */
54
+ export interface ApexTask {
55
+ subject: string;
56
+ description: string;
57
+ status: string;
58
+ phase: string;
59
+ started_at?: string;
60
+ completed_at?: string;
61
+ created_at?: string;
62
+ doc_consulted: Record<string, unknown>;
63
+ files_modified: string[];
64
+ blockedBy?: string[];
65
+ }
66
+
67
+ /** Structure of `.claude/apex/task.json`. */
68
+ export interface ApexTaskFile {
69
+ current_task: string;
70
+ created_at: string;
71
+ tasks: Record<string, ApexTask>;
72
+ }