@fusengine/harness 0.1.90 → 0.1.92

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (422) hide show
  1. package/dist/adapters/claude/index.d.mts +1 -1
  2. package/dist/adapters/claude/index.mjs +1 -1
  3. package/dist/adapters/cline/index.mjs +1 -1
  4. package/dist/adapters/codex/index.d.mts +2 -2
  5. package/dist/adapters/codex/index.mjs +3 -2
  6. package/dist/adapters/cursor/index.d.mts +15 -28
  7. package/dist/adapters/cursor/index.mjs +35 -46
  8. package/dist/adapters/gemini/index.mjs +1 -1
  9. package/dist/adapters/hermes/index.d.mts +1 -1
  10. package/dist/adapters/hermes/index.mjs +1 -1
  11. package/dist/adapters/kimi/index.d.mts +1 -1
  12. package/dist/adapters/kimi/index.mjs +1 -1
  13. package/dist/{claude-5bC8TDAz.mjs → claude-Ckv2_TgP.mjs} +3 -43
  14. package/dist/cli/bin.mjs +429 -16
  15. package/dist/cli/index.mjs +1 -1
  16. package/dist/command-string-CALMTnwN.mjs +42 -0
  17. package/dist/{evaluate-C1VSI5pb.mjs → evaluate-CMiIqHeH.mjs} +261 -13
  18. package/dist/{handle-CBaOcFZq.mjs → handle-BF1dZFjY.mjs} +1194 -144
  19. package/dist/{hermes-kP6NUQlG.mjs → hermes-B9-p_3IF.mjs} +3 -2
  20. package/dist/{index-DBgnIZn9.d.mts → index-B8PG82su.d.mts} +1 -1
  21. package/dist/{index-D4C2-IIc.d.mts → index-SmKYkk2N.d.mts} +1 -1
  22. package/dist/index.d.mts +2 -2
  23. package/dist/index.mjs +2 -2
  24. package/dist/init/index.d.mts +1 -1
  25. package/dist/init/index.mjs +1 -1
  26. package/dist/{kimi-D6X7b8wF.mjs → kimi-G2wcSh5-.mjs} +3 -2
  27. package/dist/normalize-Dy8g9Ybl.mjs +397 -0
  28. package/dist/policy/index.d.mts +1 -1
  29. package/dist/policy/index.mjs +2 -2
  30. package/dist/prompt/index.d.mts +1 -1
  31. package/dist/{run-CdIlUtjv.mjs → run-DkrzC0gb.mjs} +1 -1
  32. package/dist/{run-TAXNRSpD.mjs → run-GHAMqcOn.mjs} +26 -3
  33. package/dist/runtime/index.d.mts +37 -16
  34. package/dist/runtime/index.mjs +2 -2
  35. package/dist/{types-C0-igRKr.d.mts → types-Bh0jEF3_.d.mts} +2 -0
  36. package/dist/{validate-DZFtAbLP.mjs → validate-KjZ1X9tH.mjs} +1 -1
  37. package/package.json +3 -2
  38. package/src/adapters/claude/index.ts +92 -0
  39. package/src/adapters/cline/index.ts +36 -0
  40. package/src/adapters/codex/apply-patch.ts +94 -0
  41. package/src/adapters/codex/index.ts +84 -0
  42. package/src/adapters/codex/permission-mode.ts +16 -0
  43. package/src/adapters/cursor/context-budget.ts +144 -0
  44. package/src/adapters/cursor/context-limit.ts +115 -0
  45. package/src/adapters/cursor/context.ts +65 -0
  46. package/src/adapters/cursor/events.ts +38 -0
  47. package/src/adapters/cursor/index.ts +43 -0
  48. package/src/adapters/cursor/interfaces/context-budget.ts +25 -0
  49. package/src/adapters/cursor/interfaces/types.ts +88 -0
  50. package/src/adapters/cursor/native-response.ts +39 -0
  51. package/src/adapters/cursor/native-schemas.ts +161 -0
  52. package/src/adapters/cursor/normalize.ts +188 -0
  53. package/src/adapters/cursor/plugin-root.ts +103 -0
  54. package/src/adapters/cursor/respond.ts +170 -0
  55. package/src/adapters/gemini/index.ts +36 -0
  56. package/src/adapters/hermes/index.ts +55 -0
  57. package/src/adapters/hermes/interfaces/types.ts +26 -0
  58. package/src/adapters/kimi/index.ts +96 -0
  59. package/src/adapters/kimi/interfaces/types.ts +55 -0
  60. package/src/cache/compact.ts +55 -0
  61. package/src/cache/index.ts +5 -0
  62. package/src/cache/io.ts +38 -0
  63. package/src/cache/mcp-response.ts +39 -0
  64. package/src/cache/mcp-store.ts +70 -0
  65. package/src/cache/store.ts +93 -0
  66. package/src/changelog/fetch.ts +79 -0
  67. package/src/cli/bin.ts +100 -0
  68. package/src/cli/cursor-event-scanner.ts +175 -0
  69. package/src/cli/cursor-stdin-reader.ts +65 -0
  70. package/src/cli/doctor.ts +96 -0
  71. package/src/cli/hook-io.ts +130 -0
  72. package/src/cli/hook-sound.ts +49 -0
  73. package/src/cli/index.ts +1 -0
  74. package/src/cli/json-primitive-scanner.ts +83 -0
  75. package/src/cli/run.ts +36 -0
  76. package/src/cli/scope.ts +31 -0
  77. package/src/codex-rules/build.ts +63 -0
  78. package/src/codex-rules/index.ts +8 -0
  79. package/src/codex-rules/mapping.ts +42 -0
  80. package/src/codex-rules/rules/absolute-paths.ts +52 -0
  81. package/src/codex-rules/rules/ask.ts +44 -0
  82. package/src/codex-rules/rules/chmod-ask.ts +57 -0
  83. package/src/codex-rules/rules/dd.ts +23 -0
  84. package/src/codex-rules/rules/diskutil.ts +33 -0
  85. package/src/codex-rules/rules/mkfs.ts +42 -0
  86. package/src/codex-rules/rules/privilege.ts +49 -0
  87. package/src/codex-rules/rules/remote-fetch.ts +31 -0
  88. package/src/codex-rules/rules/rm-variants.ts +58 -0
  89. package/src/codex-rules/rules/single-commands.ts +57 -0
  90. package/src/codex-rules/skip-list.ts +25 -0
  91. package/src/codex-rules/starlark.ts +46 -0
  92. package/src/codex-rules/types.ts +36 -0
  93. package/src/config/dotenv.ts +55 -0
  94. package/src/config/env.ts +10 -0
  95. package/src/config/home-dir.ts +36 -0
  96. package/src/config/index.ts +5 -0
  97. package/src/config/layout.ts +52 -0
  98. package/src/config/limits.ts +56 -0
  99. package/src/config/ttl.ts +30 -0
  100. package/src/detect/harness.ts +73 -0
  101. package/src/detect/index.ts +1 -0
  102. package/src/detect/interfaces/types.ts +18 -0
  103. package/src/freshness/agent-evidence-record.ts +95 -0
  104. package/src/freshness/agent-evidence.ts +95 -0
  105. package/src/freshness/codex-spawn-evidence.ts +91 -0
  106. package/src/freshness/doc-helpers.ts +90 -0
  107. package/src/freshness/evidence-harvest-io.ts +72 -0
  108. package/src/freshness/evidence-harvest.ts +61 -0
  109. package/src/freshness/explore-tools.ts +95 -0
  110. package/src/freshness/index.ts +2 -0
  111. package/src/freshness/query-framework.ts +73 -0
  112. package/src/freshness/ref-evidence.ts +49 -0
  113. package/src/freshness/ref-journal.ts +98 -0
  114. package/src/freshness/trivial-edit-counter.ts +30 -0
  115. package/src/index.ts +11 -0
  116. package/src/init/index.ts +2 -0
  117. package/src/init/run.ts +32 -0
  118. package/src/init/templates.ts +75 -0
  119. package/src/memory/gitignore.ts +14 -0
  120. package/src/memory/index.ts +3 -0
  121. package/src/memory/registry.ts +39 -0
  122. package/src/memory/session-roots.ts +99 -0
  123. package/src/memory/state.ts +58 -0
  124. package/src/policy/apex-authorization.ts +100 -0
  125. package/src/policy/apex-gates.ts +91 -0
  126. package/src/policy/apex-target-steps.ts +73 -0
  127. package/src/policy/apex-target.ts +49 -0
  128. package/src/policy/apex-task-context.ts +81 -0
  129. package/src/policy/apex.ts +94 -0
  130. package/src/policy/cartographer/build-tree.ts +69 -0
  131. package/src/policy/cartographer/describe.ts +53 -0
  132. package/src/policy/cartographer/entry.ts +38 -0
  133. package/src/policy/cartographer/frontmatter.ts +48 -0
  134. package/src/policy/cartographer/indicators.ts +34 -0
  135. package/src/policy/claude-md-context.ts +66 -0
  136. package/src/policy/conventions/index.ts +11 -0
  137. package/src/policy/conventions/interfaces.ts +71 -0
  138. package/src/policy/conventions/langs.ts +53 -0
  139. package/src/policy/conventions/query.ts +49 -0
  140. package/src/policy/conventions/react-hooks.ts +18 -0
  141. package/src/policy/conventions/store-or-query.ts +21 -0
  142. package/src/policy/conventions/stores.ts +56 -0
  143. package/src/policy/conventions/strip.ts +87 -0
  144. package/src/policy/conventions/verdict.ts +38 -0
  145. package/src/policy/creation-intent.ts +11 -0
  146. package/src/policy/deny-loop.ts +88 -0
  147. package/src/policy/design/allowed-write.ts +28 -0
  148. package/src/policy/design/content-checks.ts +45 -0
  149. package/src/policy/design/corpus-fs.ts +21 -0
  150. package/src/policy/design/corpus-resolve-cache.ts +49 -0
  151. package/src/policy/design/corpus-resolve.ts +99 -0
  152. package/src/policy/design/corpus.ts +92 -0
  153. package/src/policy/design/design-system-rules.ts +45 -0
  154. package/src/policy/design/flag.ts +30 -0
  155. package/src/policy/design/gates-pipeline.ts +105 -0
  156. package/src/policy/design/gates.ts +87 -0
  157. package/src/policy/design/screenshot-tools.ts +24 -0
  158. package/src/policy/design/skill-evidence.ts +34 -0
  159. package/src/policy/design/skill-gate.ts +81 -0
  160. package/src/policy/design/skill-triggers.ts +51 -0
  161. package/src/policy/design/state.ts +92 -0
  162. package/src/policy/design/template-urls.ts +54 -0
  163. package/src/policy/design/transitions.ts +68 -0
  164. package/src/policy/detect-claude-md-project-type.ts +32 -0
  165. package/src/policy/detect-framework.ts +99 -0
  166. package/src/policy/detect-primitive-checks.ts +100 -0
  167. package/src/policy/detect-primitive-lib.ts +55 -0
  168. package/src/policy/detect-project.ts +99 -0
  169. package/src/policy/edit-outcome.ts +53 -0
  170. package/src/policy/evaluate.ts +81 -0
  171. package/src/policy/expert-agents.ts +60 -0
  172. package/src/policy/file-size-scope.ts +32 -0
  173. package/src/policy/file-size.ts +99 -0
  174. package/src/policy/framework-solid-exclude.ts +52 -0
  175. package/src/policy/framework-solid-extended.ts +93 -0
  176. package/src/policy/framework-solid-gates-systems.ts +99 -0
  177. package/src/policy/framework-solid-gates.ts +98 -0
  178. package/src/policy/framework-solid.ts +88 -0
  179. package/src/policy/gemini-mcp-gate.ts +83 -0
  180. package/src/policy/git-gates.ts +40 -0
  181. package/src/policy/guards/bash-command-anchor.ts +40 -0
  182. package/src/policy/guards/bash-write-patterns.ts +140 -0
  183. package/src/policy/guards/bash-write-redirects.ts +201 -0
  184. package/src/policy/guards/bash-write-safe-paths.ts +62 -0
  185. package/src/policy/guards/bash-write.ts +76 -0
  186. package/src/policy/guards/context.ts +12 -0
  187. package/src/policy/guards/index.ts +61 -0
  188. package/src/policy/guards/install.ts +22 -0
  189. package/src/policy/guards/interface-separation-ext.ts +41 -0
  190. package/src/policy/guards/interface-separation.ts +100 -0
  191. package/src/policy/guards/protected-path.ts +100 -0
  192. package/src/policy/guards/security.ts +97 -0
  193. package/src/policy/index.ts +19 -0
  194. package/src/policy/interfaces/types.ts +46 -0
  195. package/src/policy/js-gate-route.ts +51 -0
  196. package/src/policy/lessons/lesson-gate.ts +0 -0
  197. package/src/policy/lessons/trigger-index.ts +84 -0
  198. package/src/policy/lessons/types.ts +31 -0
  199. package/src/policy/module-layout.ts +31 -0
  200. package/src/policy/nearest-manifest.ts +75 -0
  201. package/src/policy/never-approval.ts +95 -0
  202. package/src/policy/patterns.ts +80 -0
  203. package/src/policy/seo/validate.ts +51 -0
  204. package/src/policy/shadcn-project.ts +22 -0
  205. package/src/policy/shadcn-skill-gate.ts +97 -0
  206. package/src/policy/shell-read-refs.ts +96 -0
  207. package/src/policy/skill-path.ts +46 -0
  208. package/src/policy/skill-patterns/laravel.ts +40 -0
  209. package/src/policy/skill-patterns/nextjs.ts +29 -0
  210. package/src/policy/skill-patterns/react.ts +27 -0
  211. package/src/policy/skill-patterns/shadcn-subskills.ts +33 -0
  212. package/src/policy/skill-patterns/shadcn.ts +66 -0
  213. package/src/policy/skill-patterns/swift.ts +30 -0
  214. package/src/policy/skill-patterns/tailwind.ts +40 -0
  215. package/src/policy/skill-source.ts +52 -0
  216. package/src/policy/skill-trigger-patterns.ts +33 -0
  217. package/src/policy/skill-triggers.ts +108 -0
  218. package/src/policy/tailwind-skill-gate.ts +63 -0
  219. package/src/policy/trivial-edits.ts +52 -0
  220. package/src/policy/verbosity.ts +39 -0
  221. package/src/prompt/index.ts +1 -0
  222. package/src/prompt/types.ts +36 -0
  223. package/src/refs/discover.ts +69 -0
  224. package/src/refs/frontmatter.ts +24 -0
  225. package/src/refs/index.ts +4 -0
  226. package/src/refs/loader.ts +59 -0
  227. package/src/refs/ref-key.ts +41 -0
  228. package/src/refs/router.ts +63 -0
  229. package/src/refs/types.ts +25 -0
  230. package/src/runtime/activity.ts +99 -0
  231. package/src/runtime/apply-patch-gate.ts +41 -0
  232. package/src/runtime/burst-window.ts +21 -0
  233. package/src/runtime/codex-shell-tool.ts +37 -0
  234. package/src/runtime/command-string.ts +37 -0
  235. package/src/runtime/confirm/codex-confirm.ts +126 -0
  236. package/src/runtime/confirm/codex-prompt-origin.ts +16 -0
  237. package/src/runtime/confirm/confirm-code.ts +33 -0
  238. package/src/runtime/confirm/confirm-gate.ts +65 -0
  239. package/src/runtime/confirm/confirm-irreversible.ts +32 -0
  240. package/src/runtime/confirm/confirm-pending.ts +34 -0
  241. package/src/runtime/confirm/confirm-state.ts +56 -0
  242. package/src/runtime/confirm/confirm-subagent.ts +112 -0
  243. package/src/runtime/confirm/confirm-submit.ts +38 -0
  244. package/src/runtime/core-guards-active.ts +84 -0
  245. package/src/runtime/core-guards-wired.ts +47 -0
  246. package/src/runtime/deny-loop-store.ts +94 -0
  247. package/src/runtime/deny-notice.ts +52 -0
  248. package/src/runtime/design-cache-resolve.ts +51 -0
  249. package/src/runtime/design-content-gate.ts +95 -0
  250. package/src/runtime/design-files-gate.ts +114 -0
  251. package/src/runtime/design-helpers.ts +93 -0
  252. package/src/runtime/design-lifecycle.ts +39 -0
  253. package/src/runtime/design-read-credit.ts +28 -0
  254. package/src/runtime/design.ts +116 -0
  255. package/src/runtime/dev-context.ts +43 -0
  256. package/src/runtime/dry-patterns.ts +32 -0
  257. package/src/runtime/dry.ts +96 -0
  258. package/src/runtime/fragment-registry.ts +33 -0
  259. package/src/runtime/framework-skill-gate.ts +79 -0
  260. package/src/runtime/fs-cleanup.ts +71 -0
  261. package/src/runtime/gate-apex.ts +81 -0
  262. package/src/runtime/gate-helpers.ts +52 -0
  263. package/src/runtime/gate-input.ts +26 -0
  264. package/src/runtime/gate.ts +128 -0
  265. package/src/runtime/handle-post.ts +104 -0
  266. package/src/runtime/handle-pre.ts +137 -0
  267. package/src/runtime/handle-scope-async.ts +29 -0
  268. package/src/runtime/handle-types.ts +27 -0
  269. package/src/runtime/handle.ts +197 -0
  270. package/src/runtime/home-state.ts +55 -0
  271. package/src/runtime/index.ts +18 -0
  272. package/src/runtime/inform.ts +28 -0
  273. package/src/runtime/inject-budget-recap.ts +47 -0
  274. package/src/runtime/inject-budget.ts +81 -0
  275. package/src/runtime/inject-context.ts +85 -0
  276. package/src/runtime/inject-dedup-exclusive.ts +98 -0
  277. package/src/runtime/inject-dedup.ts +89 -0
  278. package/src/runtime/is-agent-tool.ts +19 -0
  279. package/src/runtime/lifecycle/agent-files.ts +49 -0
  280. package/src/runtime/lifecycle/agent-memory.ts +82 -0
  281. package/src/runtime/lifecycle/agent-transcript.ts +80 -0
  282. package/src/runtime/lifecycle/aipilot/analytics.ts +99 -0
  283. package/src/runtime/lifecycle/aipilot/apex-task-store.ts +61 -0
  284. package/src/runtime/lifecycle/aipilot/cache-base.ts +48 -0
  285. package/src/runtime/lifecycle/aipilot/cache-doc.ts +100 -0
  286. package/src/runtime/lifecycle/aipilot/cache-lessons.ts +58 -0
  287. package/src/runtime/lifecycle/aipilot/cache-test.ts +70 -0
  288. package/src/runtime/lifecycle/aipilot/curate-lessons.ts +64 -0
  289. package/src/runtime/lifecycle/aipilot/dispatch-aipilot.ts +106 -0
  290. package/src/runtime/lifecycle/aipilot/doc-cache-gate.ts +69 -0
  291. package/src/runtime/lifecycle/aipilot/inject-apex.ts +98 -0
  292. package/src/runtime/lifecycle/aipilot/inject-doc.ts +62 -0
  293. package/src/runtime/lifecycle/aipilot/inject-explore.ts +64 -0
  294. package/src/runtime/lifecycle/aipilot/inject-lessons.ts +61 -0
  295. package/src/runtime/lifecycle/aipilot/inject-test.ts +48 -0
  296. package/src/runtime/lifecycle/aipilot/lesson-archive.ts +60 -0
  297. package/src/runtime/lifecycle/aipilot/lesson-inject.ts +98 -0
  298. package/src/runtime/lifecycle/aipilot/lesson-parse.ts +74 -0
  299. package/src/runtime/lifecycle/aipilot/lessons.ts +72 -0
  300. package/src/runtime/lifecycle/aipilot/promote-global-lessons.ts +92 -0
  301. package/src/runtime/lifecycle/aipilot/solid-compliance.ts +69 -0
  302. package/src/runtime/lifecycle/aipilot/solid-notice.ts +47 -0
  303. package/src/runtime/lifecycle/aipilot/solid-transcript.ts +60 -0
  304. package/src/runtime/lifecycle/aipilot/source-scan.ts +56 -0
  305. package/src/runtime/lifecycle/aipilot/sync-task.ts +75 -0
  306. package/src/runtime/lifecycle/aipilot/transcript.ts +88 -0
  307. package/src/runtime/lifecycle/aipilot/types.ts +72 -0
  308. package/src/runtime/lifecycle/auto-document-reads.ts +73 -0
  309. package/src/runtime/lifecycle/cartographer/detect.ts +57 -0
  310. package/src/runtime/lifecycle/cartographer/ecosystem-map.ts +56 -0
  311. package/src/runtime/lifecycle/cartographer/fs-util.ts +64 -0
  312. package/src/runtime/lifecycle/cartographer/merge.ts +51 -0
  313. package/src/runtime/lifecycle/cartographer/project-map.ts +49 -0
  314. package/src/runtime/lifecycle/cartographer/scan-hooks.ts +48 -0
  315. package/src/runtime/lifecycle/cartographer/scan.ts +81 -0
  316. package/src/runtime/lifecycle/cartographer/session-start.ts +33 -0
  317. package/src/runtime/lifecycle/cartographer/track-enrichment.ts +45 -0
  318. package/src/runtime/lifecycle/cartographer/write-plugin-map.ts +43 -0
  319. package/src/runtime/lifecycle/cartographer/write-tree.ts +44 -0
  320. package/src/runtime/lifecycle/changelog-research.ts +43 -0
  321. package/src/runtime/lifecycle/check-file-size.ts +61 -0
  322. package/src/runtime/lifecycle/codex-resync/fingerprint.ts +63 -0
  323. package/src/runtime/lifecycle/codex-resync/lock.ts +46 -0
  324. package/src/runtime/lifecycle/codex-resync/managed-destination.ts +39 -0
  325. package/src/runtime/lifecycle/codex-resync/materialize-agents.ts +57 -0
  326. package/src/runtime/lifecycle/codex-resync/plugin-files.ts +65 -0
  327. package/src/runtime/lifecycle/codex-resync/plugin-roots.ts +62 -0
  328. package/src/runtime/lifecycle/codex-resync/resync.ts +45 -0
  329. package/src/runtime/lifecycle/codex-resync/symlink-commands.ts +25 -0
  330. package/src/runtime/lifecycle/dispatch.ts +109 -0
  331. package/src/runtime/lifecycle/failure-lesson.ts +55 -0
  332. package/src/runtime/lifecycle/index.ts +34 -0
  333. package/src/runtime/lifecycle/inject-rules.ts +50 -0
  334. package/src/runtime/lifecycle/instructions-loaded.ts +22 -0
  335. package/src/runtime/lifecycle/kimi-rules-native.ts +44 -0
  336. package/src/runtime/lifecycle/lessons/dispatch.ts +88 -0
  337. package/src/runtime/lifecycle/lessons/reminder.ts +89 -0
  338. package/src/runtime/lifecycle/lessons/state.ts +23 -0
  339. package/src/runtime/lifecycle/memory/agent-lesson.ts +35 -0
  340. package/src/runtime/lifecycle/memory/capture-error.ts +41 -0
  341. package/src/runtime/lifecycle/memory/client.ts +65 -0
  342. package/src/runtime/lifecycle/memory/dispatch.ts +42 -0
  343. package/src/runtime/lifecycle/memory/recall.ts +30 -0
  344. package/src/runtime/lifecycle/memory/salience.ts +31 -0
  345. package/src/runtime/lifecycle/memory/state.ts +49 -0
  346. package/src/runtime/lifecycle/memory/track-ops.ts +15 -0
  347. package/src/runtime/lifecycle/post-compact.ts +38 -0
  348. package/src/runtime/lifecycle/post-edit-ts.ts +49 -0
  349. package/src/runtime/lifecycle/post-tracking.ts +40 -0
  350. package/src/runtime/lifecycle/pre-compact.ts +38 -0
  351. package/src/runtime/lifecycle/rules-root.ts +99 -0
  352. package/src/runtime/lifecycle/security/check-skill.ts +63 -0
  353. package/src/runtime/lifecycle/security/scan-patterns.ts +53 -0
  354. package/src/runtime/lifecycle/security/scan.ts +76 -0
  355. package/src/runtime/lifecycle/security/skill-state.ts +47 -0
  356. package/src/runtime/lifecycle/security/track-mcp.ts +29 -0
  357. package/src/runtime/lifecycle/security/track-skill-read.ts +31 -0
  358. package/src/runtime/lifecycle/seo/post-tool-use.ts +45 -0
  359. package/src/runtime/lifecycle/session-end.ts +17 -0
  360. package/src/runtime/lifecycle/session-start.ts +57 -0
  361. package/src/runtime/lifecycle/snapshot/board.ts +27 -0
  362. package/src/runtime/lifecycle/snapshot/format.ts +60 -0
  363. package/src/runtime/lifecycle/snapshot/git.ts +71 -0
  364. package/src/runtime/lifecycle/snapshot/index.ts +52 -0
  365. package/src/runtime/lifecycle/snapshot/version.ts +34 -0
  366. package/src/runtime/lifecycle/solid-detect.ts +56 -0
  367. package/src/runtime/lifecycle/stop-core.ts +35 -0
  368. package/src/runtime/lifecycle/subagent-cache.ts +61 -0
  369. package/src/runtime/lifecycle/task-completed.ts +94 -0
  370. package/src/runtime/lifecycle/teammate-idle-check.ts +63 -0
  371. package/src/runtime/lifecycle/teammate-idle.ts +28 -0
  372. package/src/runtime/lifecycle/tool-failure.ts +24 -0
  373. package/src/runtime/lifecycle/track-changes.ts +61 -0
  374. package/src/runtime/lifecycle/validate-solid.ts +70 -0
  375. package/src/runtime/lifecycle/validate-tailwind.ts +80 -0
  376. package/src/runtime/lifecycle-bridge.ts +74 -0
  377. package/src/runtime/mcp-key.ts +44 -0
  378. package/src/runtime/mcp-tool-name.ts +92 -0
  379. package/src/runtime/mcp.ts +91 -0
  380. package/src/runtime/modular.ts +77 -0
  381. package/src/runtime/normalize.ts +107 -0
  382. package/src/runtime/notices.ts +91 -0
  383. package/src/runtime/notification-sound.ts +49 -0
  384. package/src/runtime/notifications.ts +77 -0
  385. package/src/runtime/paths.ts +59 -0
  386. package/src/runtime/post-fanout.ts +41 -0
  387. package/src/runtime/post-outcome.ts +61 -0
  388. package/src/runtime/pre-allow.ts +97 -0
  389. package/src/runtime/precommit.ts +60 -0
  390. package/src/runtime/prompt-text.ts +38 -0
  391. package/src/runtime/receipt-capture.ts +34 -0
  392. package/src/runtime/record.ts +26 -0
  393. package/src/runtime/respond.ts +92 -0
  394. package/src/runtime/solid-file-size-gate.ts +58 -0
  395. package/src/runtime/solid-pre.ts +53 -0
  396. package/src/runtime/storage.ts +9 -0
  397. package/src/statusline/colors.ts +31 -0
  398. package/src/statusline/constants.ts +17 -0
  399. package/src/statusline/formatters.ts +54 -0
  400. package/src/statusline/index.ts +4 -0
  401. package/src/statusline/progress-bar.ts +46 -0
  402. package/src/tracking/codex-post-failure.ts +73 -0
  403. package/src/tracking/index.ts +2 -0
  404. package/src/tracking/integrity.ts +99 -0
  405. package/src/tracking/one-shot-dedup.ts +34 -0
  406. package/src/tracking/one-shot-failure.ts +42 -0
  407. package/src/tracking/one-shot-store.ts +95 -0
  408. package/src/tracking/one-shot.ts +93 -0
  409. package/src/tracking/receipts.ts +95 -0
  410. package/src/tracking/session-state.ts +94 -0
  411. package/src/tracking/store.ts +96 -0
  412. package/src/tracking/track-compact.ts +100 -0
  413. package/src/tracking/track-diff.ts +50 -0
  414. package/src/tracking/track-journal.ts +97 -0
  415. package/src/tracking/track-lock-sync.ts +83 -0
  416. package/src/tracking/track-lock.ts +67 -0
  417. package/src/util/compact-json.ts +23 -0
  418. package/src/util/index.ts +2 -0
  419. package/src/util/json-io.ts +47 -0
  420. package/src/util/project-root.ts +41 -0
  421. package/src/util/runtime-io.ts +85 -0
  422. package/src/util/semver.ts +30 -0
@@ -0,0 +1,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
+ }
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Filesystem helpers for the cartographer tree walk. Ports the fs parts of
3
+ * `describe.py` (file desc) and `write_recursive.py` (children + counts).
4
+ */
5
+ import { readFileSync, readdirSync } from "node:fs";
6
+ import { extname, join } from "node:path";
7
+ import { descFromText } from "../../../policy/cartographer/describe";
8
+ import { parseField } from "../../../policy/cartographer/frontmatter";
9
+
10
+ /**
11
+ * Read a file and derive its one-line description (frontmatter / heading /
12
+ * comment). "" on any error or when nothing is found.
13
+ * @param filePath - Absolute path to the file.
14
+ * @returns The description, or "".
15
+ */
16
+ export function getFileDesc(filePath: string): string {
17
+ let text = "";
18
+ try {
19
+ text = readFileSync(filePath, "utf-8");
20
+ } catch {
21
+ return "";
22
+ }
23
+ const suffix = extname(filePath);
24
+ const mdField = suffix === ".md" ? parseField(text, "description") : "";
25
+ return descFromText(suffix, text, mdField);
26
+ }
27
+
28
+ /**
29
+ * Recursively count files whose relative path parts are all visible (no leading
30
+ * "." or "_") and none excluded. Best-effort (partial count on errors).
31
+ * @param dir - Directory to count under.
32
+ * @param exclude - Directory/name set to skip.
33
+ * @returns The file count.
34
+ */
35
+ export function countFiles(dir: string, exclude: ReadonlySet<string>): number {
36
+ let total = 0;
37
+ try {
38
+ for (const e of readdirSync(dir, { withFileTypes: true })) {
39
+ if (e.name.startsWith(".") || e.name.startsWith("_") || exclude.has(e.name)) continue;
40
+ if (e.isDirectory()) total += countFiles(join(dir, e.name), exclude);
41
+ else if (e.isFile()) total += 1;
42
+ }
43
+ } catch { /* best effort */ }
44
+ return total;
45
+ }
46
+
47
+ /** Absolute children of `source`, split into dirs/files, sorted by full path. */
48
+ export function listChildren(source: string, exclude: ReadonlySet<string>): { dirs: string[]; files: string[] } {
49
+ const dirs: string[] = [];
50
+ const files: string[] = [];
51
+ let entries;
52
+ try {
53
+ entries = readdirSync(source, { withFileTypes: true });
54
+ } catch {
55
+ return { dirs, files };
56
+ }
57
+ for (const e of entries) {
58
+ if (e.name.startsWith(".") || e.name.startsWith("_") || exclude.has(e.name)) continue;
59
+ const abs = join(source, e.name);
60
+ if (e.isDirectory()) dirs.push(abs);
61
+ else if (e.isFile()) files.push(abs);
62
+ }
63
+ return { dirs: dirs.sort(), files: files.sort() };
64
+ }
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Index merge — preserves enriched descriptions across regenerations. Ports
3
+ * `merge_index.py` (merge_lines + .enriched.json sidecar).
4
+ */
5
+ import { existsSync, readFileSync } from "node:fs";
6
+ import { dirname, join } from "node:path";
7
+ import { parseEntry } from "../../../policy/cartographer/entry";
8
+
9
+ /**
10
+ * Load the `.enriched.json` sidecar's `entries` map for an output index.
11
+ * @param outputIndexPath - Path to the index.md being written.
12
+ * @returns The path→desc enrichment map (possibly empty).
13
+ */
14
+ export function loadEnriched(outputIndexPath: string): Record<string, string> {
15
+ const sidecar = join(dirname(outputIndexPath), ".enriched.json");
16
+ try {
17
+ if (!existsSync(sidecar)) return {};
18
+ const data = JSON.parse(readFileSync(sidecar, "utf-8")) as { entries?: Record<string, string> };
19
+ return data.entries ?? {};
20
+ } catch {
21
+ return {};
22
+ }
23
+ }
24
+
25
+ /**
26
+ * Merge freshly generated lines with prior descriptions: enriched sidecar wins,
27
+ * else a longer pre-existing description is preserved.
28
+ * @param newLines - The freshly generated index lines.
29
+ * @param outputIndexPath - Path to the existing index.md (if any).
30
+ * @returns The merged lines.
31
+ */
32
+ export function mergeLines(newLines: string[], outputIndexPath: string): string[] {
33
+ const enriched = loadEnriched(outputIndexPath);
34
+ const existingDescs: Record<string, string> = {};
35
+ if (existsSync(outputIndexPath)) {
36
+ try {
37
+ for (const line of readFileSync(outputIndexPath, "utf-8").split("\n")) {
38
+ const e = parseEntry(line);
39
+ if (e) existingDescs[e.path] = e.desc;
40
+ }
41
+ } catch { /* ignore */ }
42
+ }
43
+ return newLines.map((line) => {
44
+ const e = parseEntry(line);
45
+ if (!e) return line;
46
+ if (e.path in enriched) return `${e.prefix}[${e.name}](${e.path}) — ${enriched[e.path]}`;
47
+ const old = existingDescs[e.path] ?? "";
48
+ if (old.length > e.desc.length) return `${e.prefix}[${e.name}](${e.path}) — ${old}`;
49
+ return line;
50
+ });
51
+ }
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Project map generation. Ports `generate_project_map.py` (project map only).
3
+ */
4
+ import { existsSync, statSync } from "node:fs";
5
+ import { homedir } from "node:os";
6
+ import { join, resolve } from "node:path";
7
+ import { EXCLUDE_DIRS, PROJECT_INDICATORS } from "../../../policy/cartographer/indicators";
8
+ import { writeTree } from "./write-tree";
9
+
10
+ /** True when `dir` is a real directory. */
11
+ function isDirectory(dir: string): boolean {
12
+ try {
13
+ return statSync(dir).isDirectory();
14
+ } catch {
15
+ return false;
16
+ }
17
+ }
18
+
19
+ /**
20
+ * True when `dir` looks like a project root (has an indicator file) and is not
21
+ * the home directory or filesystem root.
22
+ * @param dir - Directory to test.
23
+ * @returns Whether `dir` is a project root.
24
+ */
25
+ export function isProject(dir: string): boolean {
26
+ const resolved = resolve(dir);
27
+ const home = resolve(homedir());
28
+ if (resolved === home || resolved === "/") return false;
29
+ for (const f of PROJECT_INDICATORS) {
30
+ if (existsSync(join(dir, f))) return true;
31
+ }
32
+ return false;
33
+ }
34
+
35
+ /**
36
+ * Generate the `.cartographer/project` index tree for `cwd` when it is a real
37
+ * project directory. Always returns "" (no additionalContext emitted).
38
+ * @param cwd - The working directory.
39
+ * @param outputDir - Override for the output tree root.
40
+ * @returns "" (side-effect only).
41
+ */
42
+ export function generateProjectMap(cwd: string, outputDir?: string): string {
43
+ const projectDir = resolve(cwd);
44
+ const out = outputDir ?? join(projectDir, ".cartographer", "project");
45
+ if (!isDirectory(projectDir)) return "";
46
+ if (!isProject(projectDir)) return "";
47
+ writeTree(projectDir, out, "", EXCLUDE_DIRS);
48
+ return "";
49
+ }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Hook scanning (fs). Ports `_scan_hooks` from `scan_plugins.py`: reads a
3
+ * plugin's `hooks/hooks.json` and reduces it to a single `("hooks", "<events>", "")`
4
+ * row. The `hooks` value (or the whole document) is either a MAP of event →
5
+ * entries (keys are the events) or a LIST of entries each carrying an `event`.
6
+ */
7
+ import { existsSync, readFileSync } from "node:fs";
8
+ import { join } from "node:path";
9
+ import type { ScanRow } from "../../../policy/cartographer/build-tree";
10
+
11
+ /** True for a non-null plain object (excludes arrays), mirroring Python `isinstance(x, dict)`. */
12
+ function isPlainObject(v: unknown): v is Record<string, unknown> {
13
+ return typeof v === "object" && v !== null && !Array.isArray(v);
14
+ }
15
+
16
+ /** Sort unique non-empty strings alpha (byte-order). */
17
+ function sortedEvents(events: Iterable<string>): string[] {
18
+ return [...new Set([...events].filter((e) => e))].sort((a, b) => a.localeCompare(b, "en"));
19
+ }
20
+
21
+ /** Derive hook event names from the `hooks.json` data (map keys, or list `event` fields). */
22
+ function hookEvents(hooksData: unknown): string[] {
23
+ if (isPlainObject(hooksData)) {
24
+ return sortedEvents(Object.keys(hooksData).filter((k) => !k.startsWith("_")));
25
+ }
26
+ if (Array.isArray(hooksData)) {
27
+ return sortedEvents(hooksData.map((h) => (isPlainObject(h) ? String(h.event ?? "") : "")));
28
+ }
29
+ return [];
30
+ }
31
+
32
+ /**
33
+ * Scan `hooks/hooks.json` into a single `("hooks", "<events>", "")` row.
34
+ * @param root - Absolute plugin directory.
35
+ * @returns The single hooks row, or `[]` when absent/empty/unreadable.
36
+ */
37
+ export function scanHooks(root: string): ScanRow[] {
38
+ const file = join(root, "hooks", "hooks.json");
39
+ if (!existsSync(file)) return [];
40
+ try {
41
+ const raw: unknown = JSON.parse(readFileSync(file, "utf-8"));
42
+ const hooksData = isPlainObject(raw) ? (raw.hooks ?? raw) : {};
43
+ const events = hookEvents(hooksData);
44
+ return events.length ? [["hooks", events.join(", "), ""]] : [];
45
+ } catch {
46
+ return [];
47
+ }
48
+ }
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Plugin scanning (fs). Ports `scan_plugins.py`: turns a plugin's
3
+ * agents/skills/commands/hooks into ordered `[type, name, desc]` rows.
4
+ */
5
+ import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
6
+ import { extname, join } from "node:path";
7
+ import type { ScanRow } from "../../../policy/cartographer/build-tree";
8
+ import { parseBodyDesc, parseField } from "../../../policy/cartographer/frontmatter";
9
+ import { scanHooks } from "./scan-hooks";
10
+
11
+ /** Sorted entry names of `dir` (alpha, byte-order), or `[]` on error. */
12
+ function sortedNames(dir: string): string[] {
13
+ try {
14
+ return readdirSync(dir).sort((a, b) => a.localeCompare(b, "en"));
15
+ } catch {
16
+ return [];
17
+ }
18
+ }
19
+
20
+ /** Read a `.md` frontmatter field from a file path, "" when missing/unreadable. */
21
+ function fileField(path: string, field: string): string {
22
+ try {
23
+ return parseField(readFileSync(path, "utf-8"), field);
24
+ } catch {
25
+ return "";
26
+ }
27
+ }
28
+
29
+ /** Scan `agents/*.md` → `("agent", name, desc[:50])` rows. */
30
+ export function scanAgents(root: string): ScanRow[] {
31
+ const dir = join(root, "agents");
32
+ return sortedNames(dir)
33
+ .filter((n) => extname(n) === ".md")
34
+ .map((n): ScanRow => {
35
+ const f = join(dir, n);
36
+ const name = fileField(f, "name") || n.replace(/\.md$/, "");
37
+ return ["agent", name, fileField(f, "description").slice(0, 50)];
38
+ });
39
+ }
40
+
41
+ /** Scan `skills/<dir>/SKILL.md` → `("skill", dir, desc)` rows. */
42
+ function scanSkills(root: string): ScanRow[] {
43
+ const dir = join(root, "skills");
44
+ const rows: ScanRow[] = [];
45
+ for (const name of sortedNames(dir)) {
46
+ try {
47
+ if (!statSync(join(dir, name)).isDirectory()) continue;
48
+ } catch {
49
+ continue;
50
+ }
51
+ const skillMd = join(dir, name, "SKILL.md");
52
+ let desc = "";
53
+ if (existsSync(skillMd)) {
54
+ desc = fileField(skillMd, "description");
55
+ if (!desc) {
56
+ try {
57
+ desc = parseBodyDesc(readFileSync(skillMd, "utf-8"));
58
+ } catch { /* keep "" */ }
59
+ }
60
+ }
61
+ rows.push(["skill", name, desc || "(no description)"]);
62
+ }
63
+ return rows;
64
+ }
65
+
66
+ /** Scan `commands/*.md` → `("command", "/name", desc[:50])` rows. */
67
+ function scanCommands(root: string): ScanRow[] {
68
+ const dir = join(root, "commands");
69
+ return sortedNames(dir)
70
+ .filter((n) => extname(n) === ".md")
71
+ .map((n): ScanRow => ["command", `/${n.replace(/\.md$/, "")}`, fileField(join(dir, n), "description").slice(0, 50)]);
72
+ }
73
+
74
+ /**
75
+ * Scan a single plugin directory into ordered `[type, name, desc]` rows.
76
+ * @param pluginDir - Absolute plugin directory.
77
+ * @returns The agents + skills + commands + hooks rows.
78
+ */
79
+ export function scanPlugin(pluginDir: string): ScanRow[] {
80
+ return [...scanAgents(pluginDir), ...scanSkills(pluginDir), ...scanCommands(pluginDir), ...scanHooks(pluginDir)];
81
+ }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Cartographer SessionStart handler. Ports BOTH halves of the Python maps:
3
+ * `generate_project_map.py` (regenerate `.cartographer/project`) and
4
+ * `generate_map.py` (regenerate the plugin ecosystem map), emitting the
5
+ * navigation context from the latter as additionalContext.
6
+ */
7
+ import { resolve } from "node:path";
8
+ import { generateProjectMap } from "./project-map";
9
+ import { generateEcosystemMap } from "./ecosystem-map";
10
+ import { contextResponse } from "../../../adapters/claude";
11
+
12
+ /**
13
+ * Resolve the marketplace plugins dir from `CLAUDE_PLUGIN_ROOT`, mirroring the
14
+ * Python hook which passes `${CLAUDE_PLUGIN_ROOT}/..` to `generate_map.py`.
15
+ * @returns The plugins dir (env `/..`), or `undefined` to fall back to auto-detect.
16
+ */
17
+ function pluginsDirFromEnv(): string | undefined {
18
+ const root = process.env.CLAUDE_PLUGIN_ROOT;
19
+ return root ? resolve(root, "..") : undefined;
20
+ }
21
+
22
+ /**
23
+ * Regenerate the project map + plugin ecosystem map for `cwd` on SessionStart.
24
+ * Emits the ecosystem navigation context as additionalContext (or "").
25
+ * @param cwd - The working directory.
26
+ * @param now - Clock for the ecosystem map banner timestamp.
27
+ * @returns The SessionStart additionalContext response, or "".
28
+ */
29
+ export function cartoSessionStart(cwd: string, now: number = Date.now()): string {
30
+ generateProjectMap(cwd);
31
+ const ctx = generateEcosystemMap(now, pluginsDirFromEnv());
32
+ return ctx ? contextResponse("SessionStart", ctx) : "";
33
+ }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Enrichment tracker (PostToolUse Edit/Write on `.cartographer/**\/index.md`).
3
+ * Ports `track-enrichment.py`: persists manual descriptions to a sidecar so the
4
+ * next regeneration can preserve them.
5
+ */
6
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
7
+ import { dirname, join } from "node:path";
8
+ import { parseEnrichment } from "../../../policy/cartographer/entry";
9
+
10
+ /** Shape of the `.enriched.json` sidecar. */
11
+ interface EnrichedSidecar {
12
+ version: number;
13
+ entries: Record<string, string>;
14
+ }
15
+
16
+ /**
17
+ * Record manually-edited descriptions from a cartographer `index.md` into the
18
+ * adjacent `.enriched.json` sidecar. No-op for unrelated paths. No stdout.
19
+ * @param filePath - The edited file path.
20
+ */
21
+ export function trackEnrichment(filePath: string): void {
22
+ if (!filePath || !filePath.includes(".cartographer") || !filePath.endsWith("index.md")) return;
23
+ if (!existsSync(filePath)) return;
24
+ const sidecar = join(dirname(filePath), ".enriched.json");
25
+ let existing: EnrichedSidecar = { version: 1, entries: {} };
26
+ if (existsSync(sidecar)) {
27
+ try {
28
+ existing = JSON.parse(readFileSync(sidecar, "utf-8")) as EnrichedSidecar;
29
+ } catch { /* keep default */ }
30
+ }
31
+ const entries = (existing.entries ??= {});
32
+ let text = "";
33
+ try {
34
+ text = readFileSync(filePath, "utf-8");
35
+ } catch {
36
+ return;
37
+ }
38
+ for (const line of text.split("\n")) {
39
+ const e = parseEnrichment(line);
40
+ if (e) entries[e[0]] = e[1];
41
+ }
42
+ try {
43
+ writeFileSync(sidecar, JSON.stringify(existing, null, 2) + "\n", "utf-8");
44
+ } catch { /* best effort */ }
45
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Per-plugin map writer (fs). Ports `write_plugin_map.py`: writes a level-2
3
+ * `<plugin>/index.md` (indented linked tree) then recurses agents/skills/
4
+ * commands into deeper index trees. Reuses `buildTree`, `mergeLines`, `writeTree`.
5
+ */
6
+ import { mkdirSync, statSync, writeFileSync } from "node:fs";
7
+ import { join } from "node:path";
8
+ import { buildTree, type ScanRow } from "../../../policy/cartographer/build-tree";
9
+ import { mergeLines } from "./merge";
10
+ import { writeTree } from "./write-tree";
11
+
12
+ /** True when `dir` is a real directory. */
13
+ function isDir(dir: string): boolean {
14
+ try {
15
+ return statSync(dir).isDirectory();
16
+ } catch {
17
+ return false;
18
+ }
19
+ }
20
+
21
+ /**
22
+ * Write `<outputDir>/<pluginName>/index.md` (indented linked tree) and recurse
23
+ * agents/skills/commands into their own index trees rooted there.
24
+ * @param outputDir - The map root directory.
25
+ * @param pluginName - Display name of the plugin (the index subfolder).
26
+ * @param version - Plugin version ("" to omit).
27
+ * @param items - The scanned `[type, name, desc]` rows.
28
+ * @param pluginPath - Absolute source plugin directory (for recursion).
29
+ */
30
+ export function writePluginMap(outputDir: string, pluginName: string, version: string, items: ReadonlyArray<ScanRow>, pluginPath: string): void {
31
+ const pluginDir = join(outputDir, pluginName);
32
+ mkdirSync(pluginDir, { recursive: true });
33
+ const ver = version ? ` (v${version})` : "";
34
+ const tree = items.length ? buildTree(items, true) : "└── (empty)";
35
+ const newLines = `# ${pluginName}${ver}\n\n${tree}`.split("\n");
36
+ const indexPath = join(pluginDir, "index.md");
37
+ writeFileSync(indexPath, mergeLines(newLines, indexPath).join("\n") + "\n", "utf-8");
38
+
39
+ for (const section of ["agents", "skills", "commands"]) {
40
+ const src = join(pluginPath, section);
41
+ if (isDir(src)) writeTree(src, join(pluginDir, section), "../index.md");
42
+ }
43
+ }
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Recursive index.md tree writer. Ports `write_recursive.py`.
3
+ */
4
+ import { mkdirSync, writeFileSync } from "node:fs";
5
+ import { basename, join } from "node:path";
6
+ import { countFiles, getFileDesc, listChildren } from "./fs-util";
7
+ import { mergeLines } from "./merge";
8
+
9
+ /**
10
+ * Write `index.md` files mirroring `source` under `output`, recursing into
11
+ * subdirectories. Directory lines carry a file-count hint; file lines carry a
12
+ * derived description and link to the real absolute source path.
13
+ * @param source - Absolute source directory.
14
+ * @param output - Absolute output directory for the index tree.
15
+ * @param back - Relative `← back` link target ("" at the root).
16
+ * @param exclude - Directory/name set to skip.
17
+ */
18
+ export function writeTree(source: string, output: string, back: string = "", exclude?: ReadonlySet<string>): void {
19
+ const ex = exclude ?? new Set<string>();
20
+ mkdirSync(output, { recursive: true });
21
+ const { dirs, files } = listChildren(source, ex);
22
+ const lines: string[] = [`# ${basename(source)}\n`];
23
+ if (back) lines.push(`> [← back](${back})\n`);
24
+ const total = dirs.length + files.length;
25
+ let idx = 0;
26
+ for (const d of dirs) {
27
+ idx += 1;
28
+ const conn = idx === total ? "└──" : "├──";
29
+ const count = countFiles(d, ex);
30
+ const hint = count ? ` — ${count} files` : "";
31
+ lines.push(`${conn} [${basename(d)}/](./${basename(d)}/index.md)${hint}`);
32
+ writeTree(d, join(output, basename(d)), "../index.md", exclude);
33
+ }
34
+ for (const f of files) {
35
+ idx += 1;
36
+ const conn = idx === total ? "└──" : "├──";
37
+ const desc = getFileDesc(f);
38
+ const suffix = desc ? ` — ${desc}` : "";
39
+ lines.push(`${conn} [${basename(f)}](${f})${suffix}`);
40
+ }
41
+ const indexPath = join(output, "index.md");
42
+ const merged = mergeLines(lines, indexPath);
43
+ writeFileSync(indexPath, merged.join("\n") + "\n", "utf-8");
44
+ }