@fusengine/harness 0.1.90 → 0.1.91

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (417) hide show
  1. package/dist/adapters/claude/index.d.mts +1 -1
  2. package/dist/adapters/claude/index.mjs +1 -1
  3. package/dist/adapters/cline/index.mjs +1 -1
  4. package/dist/adapters/codex/index.d.mts +2 -2
  5. package/dist/adapters/codex/index.mjs +3 -2
  6. package/dist/adapters/cursor/index.d.mts +15 -28
  7. package/dist/adapters/cursor/index.mjs +35 -46
  8. package/dist/adapters/gemini/index.mjs +1 -1
  9. package/dist/adapters/hermes/index.d.mts +1 -1
  10. package/dist/adapters/hermes/index.mjs +1 -1
  11. package/dist/adapters/kimi/index.d.mts +1 -1
  12. package/dist/adapters/kimi/index.mjs +1 -1
  13. package/dist/{claude-5bC8TDAz.mjs → claude-Ckv2_TgP.mjs} +3 -43
  14. package/dist/cli/bin.mjs +429 -16
  15. package/dist/cli/index.mjs +1 -1
  16. package/dist/command-string-CALMTnwN.mjs +42 -0
  17. package/dist/{evaluate-C1VSI5pb.mjs → evaluate-CMiIqHeH.mjs} +261 -13
  18. package/dist/{handle-CBaOcFZq.mjs → handle-C43gA-Pr.mjs} +701 -134
  19. package/dist/{hermes-kP6NUQlG.mjs → hermes-B9-p_3IF.mjs} +3 -2
  20. package/dist/{index-DBgnIZn9.d.mts → index-B8PG82su.d.mts} +1 -1
  21. package/dist/{index-D4C2-IIc.d.mts → index-SmKYkk2N.d.mts} +1 -1
  22. package/dist/index.d.mts +2 -2
  23. package/dist/index.mjs +2 -2
  24. package/dist/init/index.d.mts +1 -1
  25. package/dist/init/index.mjs +1 -1
  26. package/dist/{kimi-D6X7b8wF.mjs → kimi-G2wcSh5-.mjs} +3 -2
  27. package/dist/normalize-BjG6unTj.mjs +317 -0
  28. package/dist/policy/index.d.mts +1 -1
  29. package/dist/policy/index.mjs +2 -2
  30. package/dist/prompt/index.d.mts +1 -1
  31. package/dist/{run-CdIlUtjv.mjs → run-DkrzC0gb.mjs} +1 -1
  32. package/dist/{run-TAXNRSpD.mjs → run-GHAMqcOn.mjs} +26 -3
  33. package/dist/runtime/index.d.mts +27 -16
  34. package/dist/runtime/index.mjs +2 -2
  35. package/dist/{types-C0-igRKr.d.mts → types-Bh0jEF3_.d.mts} +2 -0
  36. package/dist/{validate-DZFtAbLP.mjs → validate-KjZ1X9tH.mjs} +1 -1
  37. package/package.json +3 -2
  38. package/src/adapters/claude/index.ts +92 -0
  39. package/src/adapters/cline/index.ts +36 -0
  40. package/src/adapters/codex/apply-patch.ts +94 -0
  41. package/src/adapters/codex/index.ts +84 -0
  42. package/src/adapters/codex/permission-mode.ts +16 -0
  43. package/src/adapters/cursor/context.ts +46 -0
  44. package/src/adapters/cursor/events.ts +38 -0
  45. package/src/adapters/cursor/index.ts +43 -0
  46. package/src/adapters/cursor/interfaces/types.ts +88 -0
  47. package/src/adapters/cursor/native-response.ts +158 -0
  48. package/src/adapters/cursor/normalize.ts +123 -0
  49. package/src/adapters/cursor/respond.ts +123 -0
  50. package/src/adapters/gemini/index.ts +36 -0
  51. package/src/adapters/hermes/index.ts +55 -0
  52. package/src/adapters/hermes/interfaces/types.ts +26 -0
  53. package/src/adapters/kimi/index.ts +96 -0
  54. package/src/adapters/kimi/interfaces/types.ts +55 -0
  55. package/src/cache/compact.ts +55 -0
  56. package/src/cache/index.ts +5 -0
  57. package/src/cache/io.ts +38 -0
  58. package/src/cache/mcp-response.ts +39 -0
  59. package/src/cache/mcp-store.ts +70 -0
  60. package/src/cache/store.ts +93 -0
  61. package/src/changelog/fetch.ts +79 -0
  62. package/src/cli/bin.ts +100 -0
  63. package/src/cli/cursor-event-scanner.ts +175 -0
  64. package/src/cli/cursor-stdin-reader.ts +65 -0
  65. package/src/cli/doctor.ts +96 -0
  66. package/src/cli/hook-io.ts +130 -0
  67. package/src/cli/hook-sound.ts +49 -0
  68. package/src/cli/index.ts +1 -0
  69. package/src/cli/json-primitive-scanner.ts +83 -0
  70. package/src/cli/run.ts +36 -0
  71. package/src/cli/scope.ts +31 -0
  72. package/src/codex-rules/build.ts +63 -0
  73. package/src/codex-rules/index.ts +8 -0
  74. package/src/codex-rules/mapping.ts +42 -0
  75. package/src/codex-rules/rules/absolute-paths.ts +52 -0
  76. package/src/codex-rules/rules/ask.ts +44 -0
  77. package/src/codex-rules/rules/chmod-ask.ts +57 -0
  78. package/src/codex-rules/rules/dd.ts +23 -0
  79. package/src/codex-rules/rules/diskutil.ts +33 -0
  80. package/src/codex-rules/rules/mkfs.ts +42 -0
  81. package/src/codex-rules/rules/privilege.ts +49 -0
  82. package/src/codex-rules/rules/remote-fetch.ts +31 -0
  83. package/src/codex-rules/rules/rm-variants.ts +58 -0
  84. package/src/codex-rules/rules/single-commands.ts +57 -0
  85. package/src/codex-rules/skip-list.ts +25 -0
  86. package/src/codex-rules/starlark.ts +46 -0
  87. package/src/codex-rules/types.ts +36 -0
  88. package/src/config/dotenv.ts +55 -0
  89. package/src/config/env.ts +10 -0
  90. package/src/config/home-dir.ts +36 -0
  91. package/src/config/index.ts +5 -0
  92. package/src/config/layout.ts +52 -0
  93. package/src/config/limits.ts +56 -0
  94. package/src/config/ttl.ts +30 -0
  95. package/src/detect/harness.ts +73 -0
  96. package/src/detect/index.ts +1 -0
  97. package/src/detect/interfaces/types.ts +18 -0
  98. package/src/freshness/agent-evidence-record.ts +95 -0
  99. package/src/freshness/agent-evidence.ts +95 -0
  100. package/src/freshness/codex-spawn-evidence.ts +91 -0
  101. package/src/freshness/doc-helpers.ts +90 -0
  102. package/src/freshness/evidence-harvest-io.ts +72 -0
  103. package/src/freshness/evidence-harvest.ts +61 -0
  104. package/src/freshness/explore-tools.ts +95 -0
  105. package/src/freshness/index.ts +2 -0
  106. package/src/freshness/query-framework.ts +73 -0
  107. package/src/freshness/ref-evidence.ts +49 -0
  108. package/src/freshness/ref-journal.ts +98 -0
  109. package/src/freshness/trivial-edit-counter.ts +30 -0
  110. package/src/index.ts +11 -0
  111. package/src/init/index.ts +2 -0
  112. package/src/init/run.ts +32 -0
  113. package/src/init/templates.ts +75 -0
  114. package/src/memory/gitignore.ts +14 -0
  115. package/src/memory/index.ts +3 -0
  116. package/src/memory/registry.ts +39 -0
  117. package/src/memory/session-roots.ts +99 -0
  118. package/src/memory/state.ts +58 -0
  119. package/src/policy/apex-authorization.ts +100 -0
  120. package/src/policy/apex-gates.ts +91 -0
  121. package/src/policy/apex-target-steps.ts +73 -0
  122. package/src/policy/apex-target.ts +49 -0
  123. package/src/policy/apex-task-context.ts +81 -0
  124. package/src/policy/apex.ts +94 -0
  125. package/src/policy/cartographer/build-tree.ts +69 -0
  126. package/src/policy/cartographer/describe.ts +53 -0
  127. package/src/policy/cartographer/entry.ts +38 -0
  128. package/src/policy/cartographer/frontmatter.ts +48 -0
  129. package/src/policy/cartographer/indicators.ts +34 -0
  130. package/src/policy/claude-md-context.ts +66 -0
  131. package/src/policy/conventions/index.ts +11 -0
  132. package/src/policy/conventions/interfaces.ts +71 -0
  133. package/src/policy/conventions/langs.ts +53 -0
  134. package/src/policy/conventions/query.ts +49 -0
  135. package/src/policy/conventions/react-hooks.ts +18 -0
  136. package/src/policy/conventions/store-or-query.ts +21 -0
  137. package/src/policy/conventions/stores.ts +56 -0
  138. package/src/policy/conventions/strip.ts +87 -0
  139. package/src/policy/conventions/verdict.ts +38 -0
  140. package/src/policy/creation-intent.ts +11 -0
  141. package/src/policy/deny-loop.ts +88 -0
  142. package/src/policy/design/allowed-write.ts +28 -0
  143. package/src/policy/design/content-checks.ts +45 -0
  144. package/src/policy/design/corpus-fs.ts +21 -0
  145. package/src/policy/design/corpus-resolve-cache.ts +49 -0
  146. package/src/policy/design/corpus-resolve.ts +99 -0
  147. package/src/policy/design/corpus.ts +92 -0
  148. package/src/policy/design/design-system-rules.ts +45 -0
  149. package/src/policy/design/flag.ts +30 -0
  150. package/src/policy/design/gates-pipeline.ts +105 -0
  151. package/src/policy/design/gates.ts +87 -0
  152. package/src/policy/design/screenshot-tools.ts +24 -0
  153. package/src/policy/design/skill-evidence.ts +34 -0
  154. package/src/policy/design/skill-gate.ts +81 -0
  155. package/src/policy/design/skill-triggers.ts +51 -0
  156. package/src/policy/design/state.ts +92 -0
  157. package/src/policy/design/template-urls.ts +54 -0
  158. package/src/policy/design/transitions.ts +68 -0
  159. package/src/policy/detect-claude-md-project-type.ts +32 -0
  160. package/src/policy/detect-framework.ts +99 -0
  161. package/src/policy/detect-primitive-checks.ts +100 -0
  162. package/src/policy/detect-primitive-lib.ts +55 -0
  163. package/src/policy/detect-project.ts +99 -0
  164. package/src/policy/edit-outcome.ts +53 -0
  165. package/src/policy/evaluate.ts +81 -0
  166. package/src/policy/expert-agents.ts +60 -0
  167. package/src/policy/file-size-scope.ts +32 -0
  168. package/src/policy/file-size.ts +99 -0
  169. package/src/policy/framework-solid-exclude.ts +52 -0
  170. package/src/policy/framework-solid-extended.ts +93 -0
  171. package/src/policy/framework-solid-gates-systems.ts +99 -0
  172. package/src/policy/framework-solid-gates.ts +98 -0
  173. package/src/policy/framework-solid.ts +88 -0
  174. package/src/policy/gemini-mcp-gate.ts +83 -0
  175. package/src/policy/git-gates.ts +40 -0
  176. package/src/policy/guards/bash-command-anchor.ts +40 -0
  177. package/src/policy/guards/bash-write-patterns.ts +140 -0
  178. package/src/policy/guards/bash-write-redirects.ts +201 -0
  179. package/src/policy/guards/bash-write-safe-paths.ts +62 -0
  180. package/src/policy/guards/bash-write.ts +76 -0
  181. package/src/policy/guards/context.ts +12 -0
  182. package/src/policy/guards/index.ts +61 -0
  183. package/src/policy/guards/install.ts +22 -0
  184. package/src/policy/guards/interface-separation-ext.ts +41 -0
  185. package/src/policy/guards/interface-separation.ts +100 -0
  186. package/src/policy/guards/protected-path.ts +100 -0
  187. package/src/policy/guards/security.ts +97 -0
  188. package/src/policy/index.ts +19 -0
  189. package/src/policy/interfaces/types.ts +46 -0
  190. package/src/policy/js-gate-route.ts +51 -0
  191. package/src/policy/lessons/lesson-gate.ts +0 -0
  192. package/src/policy/lessons/trigger-index.ts +84 -0
  193. package/src/policy/lessons/types.ts +31 -0
  194. package/src/policy/module-layout.ts +31 -0
  195. package/src/policy/nearest-manifest.ts +75 -0
  196. package/src/policy/never-approval.ts +95 -0
  197. package/src/policy/patterns.ts +80 -0
  198. package/src/policy/seo/validate.ts +51 -0
  199. package/src/policy/shadcn-project.ts +22 -0
  200. package/src/policy/shadcn-skill-gate.ts +97 -0
  201. package/src/policy/shell-read-refs.ts +96 -0
  202. package/src/policy/skill-path.ts +46 -0
  203. package/src/policy/skill-patterns/laravel.ts +40 -0
  204. package/src/policy/skill-patterns/nextjs.ts +29 -0
  205. package/src/policy/skill-patterns/react.ts +27 -0
  206. package/src/policy/skill-patterns/shadcn-subskills.ts +33 -0
  207. package/src/policy/skill-patterns/shadcn.ts +66 -0
  208. package/src/policy/skill-patterns/swift.ts +30 -0
  209. package/src/policy/skill-patterns/tailwind.ts +40 -0
  210. package/src/policy/skill-source.ts +52 -0
  211. package/src/policy/skill-trigger-patterns.ts +33 -0
  212. package/src/policy/skill-triggers.ts +108 -0
  213. package/src/policy/tailwind-skill-gate.ts +63 -0
  214. package/src/policy/trivial-edits.ts +52 -0
  215. package/src/policy/verbosity.ts +39 -0
  216. package/src/prompt/index.ts +1 -0
  217. package/src/prompt/types.ts +36 -0
  218. package/src/refs/discover.ts +69 -0
  219. package/src/refs/frontmatter.ts +24 -0
  220. package/src/refs/index.ts +4 -0
  221. package/src/refs/loader.ts +59 -0
  222. package/src/refs/ref-key.ts +41 -0
  223. package/src/refs/router.ts +63 -0
  224. package/src/refs/types.ts +25 -0
  225. package/src/runtime/activity.ts +99 -0
  226. package/src/runtime/apply-patch-gate.ts +41 -0
  227. package/src/runtime/burst-window.ts +21 -0
  228. package/src/runtime/codex-shell-tool.ts +37 -0
  229. package/src/runtime/command-string.ts +37 -0
  230. package/src/runtime/confirm/codex-confirm.ts +126 -0
  231. package/src/runtime/confirm/codex-prompt-origin.ts +16 -0
  232. package/src/runtime/confirm/confirm-code.ts +33 -0
  233. package/src/runtime/confirm/confirm-gate.ts +65 -0
  234. package/src/runtime/confirm/confirm-irreversible.ts +32 -0
  235. package/src/runtime/confirm/confirm-pending.ts +34 -0
  236. package/src/runtime/confirm/confirm-state.ts +56 -0
  237. package/src/runtime/confirm/confirm-subagent.ts +112 -0
  238. package/src/runtime/confirm/confirm-submit.ts +38 -0
  239. package/src/runtime/core-guards-active.ts +84 -0
  240. package/src/runtime/core-guards-wired.ts +47 -0
  241. package/src/runtime/deny-loop-store.ts +94 -0
  242. package/src/runtime/deny-notice.ts +52 -0
  243. package/src/runtime/design-cache-resolve.ts +51 -0
  244. package/src/runtime/design-content-gate.ts +95 -0
  245. package/src/runtime/design-files-gate.ts +114 -0
  246. package/src/runtime/design-helpers.ts +93 -0
  247. package/src/runtime/design-lifecycle.ts +39 -0
  248. package/src/runtime/design-read-credit.ts +28 -0
  249. package/src/runtime/design.ts +116 -0
  250. package/src/runtime/dev-context.ts +43 -0
  251. package/src/runtime/dry-patterns.ts +32 -0
  252. package/src/runtime/dry.ts +96 -0
  253. package/src/runtime/fragment-registry.ts +33 -0
  254. package/src/runtime/framework-skill-gate.ts +79 -0
  255. package/src/runtime/fs-cleanup.ts +71 -0
  256. package/src/runtime/gate-apex.ts +81 -0
  257. package/src/runtime/gate-helpers.ts +52 -0
  258. package/src/runtime/gate-input.ts +26 -0
  259. package/src/runtime/gate.ts +128 -0
  260. package/src/runtime/handle-post.ts +104 -0
  261. package/src/runtime/handle-pre.ts +137 -0
  262. package/src/runtime/handle-scope-async.ts +29 -0
  263. package/src/runtime/handle-types.ts +27 -0
  264. package/src/runtime/handle.ts +116 -0
  265. package/src/runtime/home-state.ts +55 -0
  266. package/src/runtime/index.ts +18 -0
  267. package/src/runtime/inform.ts +28 -0
  268. package/src/runtime/inject-budget-recap.ts +47 -0
  269. package/src/runtime/inject-budget.ts +81 -0
  270. package/src/runtime/inject-context.ts +85 -0
  271. package/src/runtime/inject-dedup-exclusive.ts +98 -0
  272. package/src/runtime/inject-dedup.ts +89 -0
  273. package/src/runtime/is-agent-tool.ts +19 -0
  274. package/src/runtime/lifecycle/agent-files.ts +49 -0
  275. package/src/runtime/lifecycle/agent-memory.ts +82 -0
  276. package/src/runtime/lifecycle/agent-transcript.ts +80 -0
  277. package/src/runtime/lifecycle/aipilot/analytics.ts +99 -0
  278. package/src/runtime/lifecycle/aipilot/apex-task-store.ts +61 -0
  279. package/src/runtime/lifecycle/aipilot/cache-base.ts +48 -0
  280. package/src/runtime/lifecycle/aipilot/cache-doc.ts +100 -0
  281. package/src/runtime/lifecycle/aipilot/cache-lessons.ts +58 -0
  282. package/src/runtime/lifecycle/aipilot/cache-test.ts +70 -0
  283. package/src/runtime/lifecycle/aipilot/curate-lessons.ts +64 -0
  284. package/src/runtime/lifecycle/aipilot/dispatch-aipilot.ts +100 -0
  285. package/src/runtime/lifecycle/aipilot/doc-cache-gate.ts +69 -0
  286. package/src/runtime/lifecycle/aipilot/inject-apex.ts +98 -0
  287. package/src/runtime/lifecycle/aipilot/inject-doc.ts +62 -0
  288. package/src/runtime/lifecycle/aipilot/inject-explore.ts +64 -0
  289. package/src/runtime/lifecycle/aipilot/inject-lessons.ts +61 -0
  290. package/src/runtime/lifecycle/aipilot/inject-test.ts +48 -0
  291. package/src/runtime/lifecycle/aipilot/lesson-archive.ts +60 -0
  292. package/src/runtime/lifecycle/aipilot/lesson-inject.ts +98 -0
  293. package/src/runtime/lifecycle/aipilot/lesson-parse.ts +74 -0
  294. package/src/runtime/lifecycle/aipilot/lessons.ts +72 -0
  295. package/src/runtime/lifecycle/aipilot/promote-global-lessons.ts +92 -0
  296. package/src/runtime/lifecycle/aipilot/solid-compliance.ts +69 -0
  297. package/src/runtime/lifecycle/aipilot/solid-notice.ts +47 -0
  298. package/src/runtime/lifecycle/aipilot/solid-transcript.ts +60 -0
  299. package/src/runtime/lifecycle/aipilot/source-scan.ts +56 -0
  300. package/src/runtime/lifecycle/aipilot/sync-task.ts +75 -0
  301. package/src/runtime/lifecycle/aipilot/transcript.ts +88 -0
  302. package/src/runtime/lifecycle/aipilot/types.ts +72 -0
  303. package/src/runtime/lifecycle/auto-document-reads.ts +73 -0
  304. package/src/runtime/lifecycle/cartographer/detect.ts +57 -0
  305. package/src/runtime/lifecycle/cartographer/ecosystem-map.ts +56 -0
  306. package/src/runtime/lifecycle/cartographer/fs-util.ts +64 -0
  307. package/src/runtime/lifecycle/cartographer/merge.ts +51 -0
  308. package/src/runtime/lifecycle/cartographer/project-map.ts +49 -0
  309. package/src/runtime/lifecycle/cartographer/scan-hooks.ts +48 -0
  310. package/src/runtime/lifecycle/cartographer/scan.ts +81 -0
  311. package/src/runtime/lifecycle/cartographer/session-start.ts +33 -0
  312. package/src/runtime/lifecycle/cartographer/track-enrichment.ts +45 -0
  313. package/src/runtime/lifecycle/cartographer/write-plugin-map.ts +43 -0
  314. package/src/runtime/lifecycle/cartographer/write-tree.ts +44 -0
  315. package/src/runtime/lifecycle/changelog-research.ts +43 -0
  316. package/src/runtime/lifecycle/check-file-size.ts +61 -0
  317. package/src/runtime/lifecycle/codex-resync/fingerprint.ts +63 -0
  318. package/src/runtime/lifecycle/codex-resync/lock.ts +46 -0
  319. package/src/runtime/lifecycle/codex-resync/managed-destination.ts +39 -0
  320. package/src/runtime/lifecycle/codex-resync/materialize-agents.ts +57 -0
  321. package/src/runtime/lifecycle/codex-resync/plugin-files.ts +65 -0
  322. package/src/runtime/lifecycle/codex-resync/plugin-roots.ts +62 -0
  323. package/src/runtime/lifecycle/codex-resync/resync.ts +45 -0
  324. package/src/runtime/lifecycle/codex-resync/symlink-commands.ts +25 -0
  325. package/src/runtime/lifecycle/dispatch.ts +109 -0
  326. package/src/runtime/lifecycle/failure-lesson.ts +51 -0
  327. package/src/runtime/lifecycle/index.ts +34 -0
  328. package/src/runtime/lifecycle/inject-rules.ts +50 -0
  329. package/src/runtime/lifecycle/instructions-loaded.ts +22 -0
  330. package/src/runtime/lifecycle/kimi-rules-native.ts +44 -0
  331. package/src/runtime/lifecycle/lessons/dispatch.ts +88 -0
  332. package/src/runtime/lifecycle/lessons/reminder.ts +89 -0
  333. package/src/runtime/lifecycle/lessons/state.ts +23 -0
  334. package/src/runtime/lifecycle/memory/agent-lesson.ts +35 -0
  335. package/src/runtime/lifecycle/memory/capture-error.ts +41 -0
  336. package/src/runtime/lifecycle/memory/client.ts +65 -0
  337. package/src/runtime/lifecycle/memory/dispatch.ts +42 -0
  338. package/src/runtime/lifecycle/memory/recall.ts +30 -0
  339. package/src/runtime/lifecycle/memory/salience.ts +31 -0
  340. package/src/runtime/lifecycle/memory/state.ts +49 -0
  341. package/src/runtime/lifecycle/memory/track-ops.ts +15 -0
  342. package/src/runtime/lifecycle/post-compact.ts +38 -0
  343. package/src/runtime/lifecycle/post-edit-ts.ts +49 -0
  344. package/src/runtime/lifecycle/post-tracking.ts +40 -0
  345. package/src/runtime/lifecycle/pre-compact.ts +38 -0
  346. package/src/runtime/lifecycle/rules-root.ts +83 -0
  347. package/src/runtime/lifecycle/security/check-skill.ts +63 -0
  348. package/src/runtime/lifecycle/security/scan-patterns.ts +53 -0
  349. package/src/runtime/lifecycle/security/scan.ts +76 -0
  350. package/src/runtime/lifecycle/security/skill-state.ts +47 -0
  351. package/src/runtime/lifecycle/security/track-mcp.ts +29 -0
  352. package/src/runtime/lifecycle/security/track-skill-read.ts +31 -0
  353. package/src/runtime/lifecycle/seo/post-tool-use.ts +45 -0
  354. package/src/runtime/lifecycle/session-end.ts +17 -0
  355. package/src/runtime/lifecycle/session-start.ts +57 -0
  356. package/src/runtime/lifecycle/snapshot/board.ts +27 -0
  357. package/src/runtime/lifecycle/snapshot/format.ts +60 -0
  358. package/src/runtime/lifecycle/snapshot/git.ts +71 -0
  359. package/src/runtime/lifecycle/snapshot/index.ts +52 -0
  360. package/src/runtime/lifecycle/snapshot/version.ts +34 -0
  361. package/src/runtime/lifecycle/solid-detect.ts +56 -0
  362. package/src/runtime/lifecycle/stop-core.ts +35 -0
  363. package/src/runtime/lifecycle/subagent-cache.ts +61 -0
  364. package/src/runtime/lifecycle/task-completed.ts +94 -0
  365. package/src/runtime/lifecycle/teammate-idle-check.ts +63 -0
  366. package/src/runtime/lifecycle/teammate-idle.ts +28 -0
  367. package/src/runtime/lifecycle/tool-failure.ts +24 -0
  368. package/src/runtime/lifecycle/track-changes.ts +61 -0
  369. package/src/runtime/lifecycle/validate-solid.ts +70 -0
  370. package/src/runtime/lifecycle/validate-tailwind.ts +80 -0
  371. package/src/runtime/lifecycle-bridge.ts +74 -0
  372. package/src/runtime/mcp-key.ts +44 -0
  373. package/src/runtime/mcp-tool-name.ts +92 -0
  374. package/src/runtime/mcp.ts +91 -0
  375. package/src/runtime/modular.ts +77 -0
  376. package/src/runtime/normalize.ts +107 -0
  377. package/src/runtime/notices.ts +91 -0
  378. package/src/runtime/notification-sound.ts +49 -0
  379. package/src/runtime/notifications.ts +77 -0
  380. package/src/runtime/paths.ts +59 -0
  381. package/src/runtime/post-fanout.ts +41 -0
  382. package/src/runtime/post-outcome.ts +61 -0
  383. package/src/runtime/pre-allow.ts +97 -0
  384. package/src/runtime/precommit.ts +60 -0
  385. package/src/runtime/prompt-text.ts +38 -0
  386. package/src/runtime/receipt-capture.ts +34 -0
  387. package/src/runtime/record.ts +26 -0
  388. package/src/runtime/respond.ts +92 -0
  389. package/src/runtime/solid-file-size-gate.ts +58 -0
  390. package/src/runtime/solid-pre.ts +53 -0
  391. package/src/runtime/storage.ts +9 -0
  392. package/src/statusline/colors.ts +31 -0
  393. package/src/statusline/constants.ts +17 -0
  394. package/src/statusline/formatters.ts +54 -0
  395. package/src/statusline/index.ts +4 -0
  396. package/src/statusline/progress-bar.ts +46 -0
  397. package/src/tracking/codex-post-failure.ts +73 -0
  398. package/src/tracking/index.ts +2 -0
  399. package/src/tracking/integrity.ts +99 -0
  400. package/src/tracking/one-shot-dedup.ts +34 -0
  401. package/src/tracking/one-shot-failure.ts +42 -0
  402. package/src/tracking/one-shot-store.ts +95 -0
  403. package/src/tracking/one-shot.ts +93 -0
  404. package/src/tracking/receipts.ts +95 -0
  405. package/src/tracking/session-state.ts +94 -0
  406. package/src/tracking/store.ts +96 -0
  407. package/src/tracking/track-compact.ts +100 -0
  408. package/src/tracking/track-diff.ts +50 -0
  409. package/src/tracking/track-journal.ts +97 -0
  410. package/src/tracking/track-lock-sync.ts +83 -0
  411. package/src/tracking/track-lock.ts +67 -0
  412. package/src/util/compact-json.ts +23 -0
  413. package/src/util/index.ts +2 -0
  414. package/src/util/json-io.ts +47 -0
  415. package/src/util/project-root.ts +41 -0
  416. package/src/util/runtime-io.ts +85 -0
  417. package/src/util/semver.ts +30 -0
@@ -0,0 +1,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
+ }
@@ -0,0 +1,73 @@
1
+ /**
2
+ * PostToolUse (matcher "Read") for the core scope: auto-document important
3
+ * reads (SKILL.md/README/CLAUDE.md/docs/references) into
4
+ * `.claude/apex/docs/task-<current>-<framework>.md`, once per file per task.
5
+ * Ports `post-tool-use/auto-document-reads.py`.
6
+ */
7
+ import { appendFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
8
+ import { basename, dirname, join } from "node:path";
9
+ import { readJsonFile } from "../../util/json-io";
10
+ import { detectProjectType } from "../../policy/detect-project";
11
+ import { projectRootOrNull } from "../../util/project-root";
12
+ import { systemMessage } from "../../adapters/claude";
13
+ import { harnessHomeSegment } from "../../policy/apex-target";
14
+ import type { ApexTaskFile } from "./aipilot/types";
15
+
16
+ /** Read-tool file paths worth auto-documenting. */
17
+ const DOC_PATTERNS = [
18
+ /(SKILL\.md|README\.md|CLAUDE\.md)$/,
19
+ /\/docs\/.*\.md$/,
20
+ /\/references\/.*\.md$/,
21
+ /skills\/[^/]+\/SKILL\.md$/,
22
+ ];
23
+
24
+ /** First matching marker (in order) labels the doc-type; "File" otherwise. */
25
+ const TYPE_MAP: Array<[string, string]> = [
26
+ ["SKILL.md", "Skill"], ["README.md", "README"], ["CLAUDE.md", "Rules"],
27
+ ["/references/", "Reference"], ["/docs/", "Doc"],
28
+ ];
29
+
30
+ /** The doc-type label for a matched file path. */
31
+ function docTypeOf(filePath: string): string {
32
+ return TYPE_MAP.find(([marker]) => filePath.includes(marker))?.[1] ?? "File";
33
+ }
34
+
35
+ /**
36
+ * Auto-document a Read of a SKILL.md/README/CLAUDE.md/docs/references file
37
+ * into the target apex docs dir (`.claude/apex/docs/`, `.codex/apex/docs/`,
38
+ * ...) as `task-<current>-<framework>.md` (skipped when the file is already
39
+ * logged, or no project root is found from `filePath`).
40
+ * @param filePath - The path passed to the Read tool.
41
+ * @param now - Clock (defaults to `Date.now()`).
42
+ * @param id - Harness target id (defaults to "claude-code" — zero-regression default).
43
+ * @returns The native `systemMessage` stdout, or "" when nothing was logged.
44
+ */
45
+ export async function autoDocumentRead(filePath: string, now: number = Date.now(), id: string = "claude-code"): Promise<string> {
46
+ if (!filePath || !DOC_PATTERNS.some((p) => p.test(filePath))) return "";
47
+ const root = projectRootOrNull(dirname(filePath));
48
+ if (!root) return "";
49
+
50
+ const seg = harnessHomeSegment(id);
51
+ const framework = detectProjectType(root);
52
+ const taskData = await readJsonFile<ApexTaskFile>(join(root, seg, "apex", "task.json"));
53
+ const current = taskData?.current_task ?? "1";
54
+
55
+ const docDir = join(root, seg, "apex", "docs");
56
+ const docFile = join(docDir, `task-${current}-${framework}.md`);
57
+ const ts = new Date(now).toISOString().replace(/\.\d{3}Z$/, "Z");
58
+ const fname = basename(filePath);
59
+ const docType = docTypeOf(filePath);
60
+
61
+ try {
62
+ mkdirSync(docDir, { recursive: true });
63
+ if (!existsSync(docFile)) {
64
+ const fc = framework.charAt(0).toUpperCase() + framework.slice(1);
65
+ writeFileSync(docFile, `# Task ${current} - ${fc} Documentation\n## Consulted: ${ts} | Source: skill:Read\n## Key Info\n\n`, "utf-8");
66
+ }
67
+ if (readFileSync(docFile, "utf-8").includes(`\`${fname}\``)) return "";
68
+ appendFileSync(docFile, `- **[${docType}]** \`${fname}\` - ${ts}\n`, "utf-8");
69
+ } catch {
70
+ return "";
71
+ }
72
+ return systemMessage(`📖 [${docType}] ${fname} logged`);
73
+ }
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Plugin discovery (fs). Ports `detect_plugins.py`: marketplace `plugins` dir
3
+ * resolution + `plugin.json` meta reading.
4
+ */
5
+ import { existsSync, readFileSync, readdirSync } from "node:fs";
6
+ import { homedir } from "node:os";
7
+ import { join } from "node:path";
8
+ import { HOME_DIR } from "../../../config/dotenv";
9
+ import { detectHarness } from "../../../detect/harness";
10
+ import type { HarnessId } from "../../../detect/harness";
11
+
12
+ /** Sorted entry names of `dir` (alpha, byte-order), or `[]` on error. */
13
+ function sortedNames(dir: string): string[] {
14
+ try {
15
+ return readdirSync(dir).sort((a, b) => a.localeCompare(b, "en"));
16
+ } catch {
17
+ return [];
18
+ }
19
+ }
20
+
21
+ /**
22
+ * Read `[version, name]` from `<pluginPath>/.claude-plugin/plugin.json`.
23
+ * @param pluginPath - Absolute plugin directory.
24
+ * @returns The `[version, name]` pair (both "" when absent/unreadable).
25
+ */
26
+ export function readPluginMeta(pluginPath: string): [string, string] {
27
+ const pj = join(pluginPath, ".claude-plugin", "plugin.json");
28
+ if (!existsSync(pj)) return ["", ""];
29
+ try {
30
+ const meta = JSON.parse(readFileSync(pj, "utf-8")) as { version?: string; name?: string };
31
+ return [meta.version ?? "", meta.name ?? ""];
32
+ } catch {
33
+ return ["", ""];
34
+ }
35
+ }
36
+
37
+ /**
38
+ * Auto-detect the marketplace `plugins` dir that contains `cartographer`,
39
+ * falling back to the first marketplace with a `plugins` dir, else `cwd`.
40
+ * Ports `find_marketplace_plugins`, but harness-agnostic: the config dir is
41
+ * derived from the detected harness (`.claude`, `.codex`, `.cursor`, …) via the
42
+ * shared `HOME_DIR` mapping instead of a hardcoded `.claude`.
43
+ * @param home - Home directory (defaults to `~`).
44
+ * @param id - Detected harness id (defaults to runtime detection).
45
+ * @returns The resolved plugins directory.
46
+ */
47
+ export function findMarketplacePlugins(home: string = homedir(), id: HarnessId = detectHarness().id): string {
48
+ const mp = join(home, HOME_DIR[id] ?? ".claude", "plugins", "marketplaces");
49
+ const markets = sortedNames(mp);
50
+ for (const m of markets) {
51
+ if (existsSync(join(mp, m, "plugins", "cartographer"))) return join(mp, m, "plugins");
52
+ }
53
+ for (const m of markets) {
54
+ if (existsSync(join(mp, m, "plugins"))) return join(mp, m, "plugins");
55
+ }
56
+ return process.cwd();
57
+ }
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Ecosystem (plugin) map generation (fs). Ports `generate_map.py`: scans every
3
+ * installed plugin into a level-1 `.cartographer/index.md` + per-plugin level-2+
4
+ * trees, preserving enriched descriptions. Reuses `findMarketplacePlugins`,
5
+ * `readPluginMeta`, `scanPlugin`, `mergeLines`, `writePluginMap`.
6
+ */
7
+ import { mkdirSync, readdirSync, statSync, writeFileSync } from "node:fs";
8
+ import { join, resolve } from "node:path";
9
+ import { findMarketplacePlugins, readPluginMeta } from "./detect";
10
+ import { scanPlugin } from "./scan";
11
+ import { mergeLines } from "./merge";
12
+ import { writePluginMap } from "./write-plugin-map";
13
+
14
+ function pluginDirs(dir: string): string[] {
15
+ let entries: string[] = [];
16
+ try { entries = readdirSync(dir); } catch { return []; }
17
+ return entries
18
+ .filter((n) => !n.startsWith("_") && !n.startsWith("."))
19
+ .filter((n) => { try { return statSync(join(dir, n)).isDirectory(); } catch { return false; } })
20
+ .sort((a, b) => a.localeCompare(b, "en"));
21
+ }
22
+
23
+ function utcStamp(now: number): string {
24
+ return new Date(now).toISOString().slice(0, 16).replace("T", " ");
25
+ }
26
+
27
+ /**
28
+ * Generate the plugin ecosystem map under `<pluginsDir>/.cartographer`.
29
+ * @param now - Clock for the banner timestamp.
30
+ * @param pluginsDirOverride - Override for the marketplace plugins directory.
31
+ * @returns The map navigation context, or "".
32
+ */
33
+ export function generateEcosystemMap(now: number, pluginsDirOverride?: string): string {
34
+ const pluginsDir = resolve(pluginsDirOverride ?? findMarketplacePlugins());
35
+ try { if (!statSync(pluginsDir).isDirectory()) return ""; } catch { return ""; }
36
+ const outputDir = join(pluginsDir, ".cartographer");
37
+ mkdirSync(outputDir, { recursive: true });
38
+ const dirs = pluginDirs(pluginsDir);
39
+ const lines = [`# Ecosystem Map (${dirs.length} plugins)\n`, `> Auto-generated by cartographer — ${utcStamp(now)}\n`];
40
+ for (const name of dirs) {
41
+ const pluginPath = join(pluginsDir, name);
42
+ const [version, pkgName] = readPluginMeta(pluginPath);
43
+ const display = pkgName || name;
44
+ const items = scanPlugin(pluginPath);
45
+ const agents = items.filter(([t]) => t === "agent").map(([, n]) => n);
46
+ const ver = version ? ` (v${version})` : "";
47
+ lines.push(`- [${display}](./${display}/index.md)${ver} → ${agents.length ? agents.join(", ") : "(no agents)"}`);
48
+ writePluginMap(outputDir, display, version, items, pluginPath);
49
+ writePluginMap(pluginPath, ".cartographer", version, items, pluginPath);
50
+ }
51
+ const indexPath = join(outputDir, "index.md");
52
+ writeFileSync(indexPath, mergeLines(lines, indexPath).join("\n") + "\n", "utf-8");
53
+ return `Project map: .cartographer/project/index.md — navigate project files. ` +
54
+ `Plugin skills map: ${outputDir}/index.md — navigate agent skills. ` +
55
+ `Branches link to deeper index.md, leaves link to real files.`;
56
+ }