@mxalbert/context-mode 2.0.0

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 (365) hide show
  1. package/.claude-plugin/marketplace.json +33 -0
  2. package/.claude-plugin/plugin.json +31 -0
  3. package/.codex-plugin/hooks.json +65 -0
  4. package/.codex-plugin/mcp.json +13 -0
  5. package/.codex-plugin/plugin.json +31 -0
  6. package/.openclaw-plugin/index.ts +12 -0
  7. package/.openclaw-plugin/openclaw.plugin.json +38 -0
  8. package/.openclaw-plugin/package.json +28 -0
  9. package/LICENSE +94 -0
  10. package/README.md +1619 -0
  11. package/bin/statusline.mjs +378 -0
  12. package/build/adapters/antigravity/index.d.ts +51 -0
  13. package/build/adapters/antigravity/index.js +169 -0
  14. package/build/adapters/antigravity-cli/index.d.ts +51 -0
  15. package/build/adapters/antigravity-cli/index.js +342 -0
  16. package/build/adapters/base.d.ts +92 -0
  17. package/build/adapters/base.js +135 -0
  18. package/build/adapters/claude-code/config.d.ts +8 -0
  19. package/build/adapters/claude-code/config.js +8 -0
  20. package/build/adapters/claude-code/hooks.d.ts +106 -0
  21. package/build/adapters/claude-code/hooks.js +183 -0
  22. package/build/adapters/claude-code/index.d.ts +80 -0
  23. package/build/adapters/claude-code/index.js +569 -0
  24. package/build/adapters/claude-code-base.d.ts +49 -0
  25. package/build/adapters/claude-code-base.js +113 -0
  26. package/build/adapters/client-map.d.ts +10 -0
  27. package/build/adapters/client-map.js +42 -0
  28. package/build/adapters/codex/config.d.ts +8 -0
  29. package/build/adapters/codex/config.js +8 -0
  30. package/build/adapters/codex/hooks.d.ts +55 -0
  31. package/build/adapters/codex/hooks.js +64 -0
  32. package/build/adapters/codex/index.d.ts +87 -0
  33. package/build/adapters/codex/index.js +995 -0
  34. package/build/adapters/codex/paths.d.ts +1 -0
  35. package/build/adapters/codex/paths.js +12 -0
  36. package/build/adapters/codex/usage.d.ts +107 -0
  37. package/build/adapters/codex/usage.js +227 -0
  38. package/build/adapters/copilot-base.d.ts +94 -0
  39. package/build/adapters/copilot-base.js +297 -0
  40. package/build/adapters/copilot-cli/hooks.d.ts +33 -0
  41. package/build/adapters/copilot-cli/hooks.js +64 -0
  42. package/build/adapters/copilot-cli/index.d.ts +48 -0
  43. package/build/adapters/copilot-cli/index.js +341 -0
  44. package/build/adapters/cursor/config.d.ts +4 -0
  45. package/build/adapters/cursor/config.js +4 -0
  46. package/build/adapters/cursor/hooks.d.ts +55 -0
  47. package/build/adapters/cursor/hooks.js +82 -0
  48. package/build/adapters/cursor/index.d.ts +64 -0
  49. package/build/adapters/cursor/index.js +517 -0
  50. package/build/adapters/detect.d.ts +136 -0
  51. package/build/adapters/detect.js +623 -0
  52. package/build/adapters/gemini-cli/config.d.ts +8 -0
  53. package/build/adapters/gemini-cli/config.js +8 -0
  54. package/build/adapters/gemini-cli/hooks.d.ts +83 -0
  55. package/build/adapters/gemini-cli/hooks.js +115 -0
  56. package/build/adapters/gemini-cli/index.d.ts +80 -0
  57. package/build/adapters/gemini-cli/index.js +483 -0
  58. package/build/adapters/jetbrains-copilot/config.d.ts +8 -0
  59. package/build/adapters/jetbrains-copilot/config.js +8 -0
  60. package/build/adapters/jetbrains-copilot/hooks.d.ts +59 -0
  61. package/build/adapters/jetbrains-copilot/hooks.js +87 -0
  62. package/build/adapters/jetbrains-copilot/index.d.ts +31 -0
  63. package/build/adapters/jetbrains-copilot/index.js +131 -0
  64. package/build/adapters/kimi/config.d.ts +8 -0
  65. package/build/adapters/kimi/config.js +8 -0
  66. package/build/adapters/kimi/hooks.d.ts +28 -0
  67. package/build/adapters/kimi/hooks.js +34 -0
  68. package/build/adapters/kimi/index.d.ts +66 -0
  69. package/build/adapters/kimi/index.js +537 -0
  70. package/build/adapters/kimi/paths.d.ts +21 -0
  71. package/build/adapters/kimi/paths.js +52 -0
  72. package/build/adapters/kimi/usage.d.ts +82 -0
  73. package/build/adapters/kimi/usage.js +217 -0
  74. package/build/adapters/kiro/hooks.d.ts +61 -0
  75. package/build/adapters/kiro/hooks.js +94 -0
  76. package/build/adapters/kiro/index.d.ts +61 -0
  77. package/build/adapters/kiro/index.js +301 -0
  78. package/build/adapters/omp/index.d.ts +65 -0
  79. package/build/adapters/omp/index.js +182 -0
  80. package/build/adapters/omp/plugin.d.ts +81 -0
  81. package/build/adapters/omp/plugin.js +331 -0
  82. package/build/adapters/omp/usage.d.ts +49 -0
  83. package/build/adapters/omp/usage.js +110 -0
  84. package/build/adapters/openclaw/config.d.ts +8 -0
  85. package/build/adapters/openclaw/config.js +8 -0
  86. package/build/adapters/openclaw/hooks.d.ts +50 -0
  87. package/build/adapters/openclaw/hooks.js +61 -0
  88. package/build/adapters/openclaw/index.d.ts +74 -0
  89. package/build/adapters/openclaw/index.js +439 -0
  90. package/build/adapters/openclaw/mcp-tools.d.ts +54 -0
  91. package/build/adapters/openclaw/mcp-tools.js +207 -0
  92. package/build/adapters/openclaw/plugin.d.ts +141 -0
  93. package/build/adapters/openclaw/plugin.js +818 -0
  94. package/build/adapters/openclaw/session-db.d.ts +55 -0
  95. package/build/adapters/openclaw/session-db.js +88 -0
  96. package/build/adapters/openclaw/usage.d.ts +34 -0
  97. package/build/adapters/openclaw/usage.js +52 -0
  98. package/build/adapters/openclaw/workspace-router.d.ts +29 -0
  99. package/build/adapters/openclaw/workspace-router.js +64 -0
  100. package/build/adapters/opencode/config.d.ts +8 -0
  101. package/build/adapters/opencode/config.js +8 -0
  102. package/build/adapters/opencode/hooks.d.ts +38 -0
  103. package/build/adapters/opencode/hooks.js +50 -0
  104. package/build/adapters/opencode/index.d.ts +144 -0
  105. package/build/adapters/opencode/index.js +638 -0
  106. package/build/adapters/opencode/plugin.d.ts +249 -0
  107. package/build/adapters/opencode/plugin.js +1536 -0
  108. package/build/adapters/opencode/v2.d.ts +117 -0
  109. package/build/adapters/opencode/v2.js +224 -0
  110. package/build/adapters/opencode/zod3tov4.d.ts +1 -0
  111. package/build/adapters/opencode/zod3tov4.js +132 -0
  112. package/build/adapters/pi/extension.d.ts +81 -0
  113. package/build/adapters/pi/extension.js +861 -0
  114. package/build/adapters/pi/index.d.ts +57 -0
  115. package/build/adapters/pi/index.js +173 -0
  116. package/build/adapters/pi/mcp-bridge.d.ts +268 -0
  117. package/build/adapters/pi/mcp-bridge.js +894 -0
  118. package/build/adapters/qwen-code/hooks.d.ts +26 -0
  119. package/build/adapters/qwen-code/hooks.js +29 -0
  120. package/build/adapters/qwen-code/index.d.ts +40 -0
  121. package/build/adapters/qwen-code/index.js +361 -0
  122. package/build/adapters/qwen-code/usage.d.ts +90 -0
  123. package/build/adapters/qwen-code/usage.js +222 -0
  124. package/build/adapters/types.d.ts +360 -0
  125. package/build/adapters/types.js +118 -0
  126. package/build/adapters/vscode-copilot/config.d.ts +8 -0
  127. package/build/adapters/vscode-copilot/config.js +8 -0
  128. package/build/adapters/vscode-copilot/hooks.d.ts +71 -0
  129. package/build/adapters/vscode-copilot/hooks.js +98 -0
  130. package/build/adapters/vscode-copilot/index.d.ts +32 -0
  131. package/build/adapters/vscode-copilot/index.js +222 -0
  132. package/build/adapters/zed/index.d.ts +40 -0
  133. package/build/adapters/zed/index.js +163 -0
  134. package/build/cli.d.ts +34 -0
  135. package/build/cli.js +1828 -0
  136. package/build/db-base.d.ts +250 -0
  137. package/build/db-base.js +756 -0
  138. package/build/executor.d.ts +72 -0
  139. package/build/executor.js +678 -0
  140. package/build/exit-classify.d.ts +19 -0
  141. package/build/exit-classify.js +12 -0
  142. package/build/fetch/blocks.d.ts +145 -0
  143. package/build/fetch/blocks.js +244 -0
  144. package/build/fetch/extract.d.ts +68 -0
  145. package/build/fetch/extract.js +123 -0
  146. package/build/fetch/page-store.d.ts +66 -0
  147. package/build/fetch/page-store.js +174 -0
  148. package/build/fetch-cache.d.ts +13 -0
  149. package/build/fetch-cache.js +15 -0
  150. package/build/lifecycle.d.ts +124 -0
  151. package/build/lifecycle.js +278 -0
  152. package/build/package-identity.d.ts +26 -0
  153. package/build/package-identity.js +28 -0
  154. package/build/runPool.d.ts +36 -0
  155. package/build/runPool.js +51 -0
  156. package/build/runtime.d.ts +71 -0
  157. package/build/runtime.js +659 -0
  158. package/build/search/auto-memory.d.ts +47 -0
  159. package/build/search/auto-memory.js +170 -0
  160. package/build/search/ctx-search-schema.d.ts +90 -0
  161. package/build/search/ctx-search-schema.js +135 -0
  162. package/build/search/flood-guard.d.ts +57 -0
  163. package/build/search/flood-guard.js +80 -0
  164. package/build/search/unified.d.ts +56 -0
  165. package/build/search/unified.js +104 -0
  166. package/build/security.d.ts +206 -0
  167. package/build/security.js +780 -0
  168. package/build/server.d.ts +240 -0
  169. package/build/server.js +5034 -0
  170. package/build/session/analytics.d.ts +773 -0
  171. package/build/session/analytics.js +2420 -0
  172. package/build/session/db.d.ts +468 -0
  173. package/build/session/db.js +1330 -0
  174. package/build/session/error-classifier.d.ts +87 -0
  175. package/build/session/error-classifier.js +303 -0
  176. package/build/session/event-emit.d.ts +48 -0
  177. package/build/session/event-emit.js +101 -0
  178. package/build/session/extract.d.ts +333 -0
  179. package/build/session/extract.js +2688 -0
  180. package/build/session/model-prices.json +429 -0
  181. package/build/session/persist-tool-calls.d.ts +54 -0
  182. package/build/session/persist-tool-calls.js +105 -0
  183. package/build/session/pricing.d.ts +64 -0
  184. package/build/session/pricing.js +151 -0
  185. package/build/session/project-attribution.d.ts +73 -0
  186. package/build/session/project-attribution.js +245 -0
  187. package/build/session/purge.d.ts +138 -0
  188. package/build/session/purge.js +240 -0
  189. package/build/session/retrieval-marker.d.ts +39 -0
  190. package/build/session/retrieval-marker.js +65 -0
  191. package/build/session/snapshot.d.ts +46 -0
  192. package/build/session/snapshot.js +532 -0
  193. package/build/store-directory.d.ts +56 -0
  194. package/build/store-directory.js +254 -0
  195. package/build/store.d.ts +147 -0
  196. package/build/store.js +1714 -0
  197. package/build/truncate.d.ts +57 -0
  198. package/build/truncate.js +142 -0
  199. package/build/types.d.ts +117 -0
  200. package/build/types.js +20 -0
  201. package/build/util/claude-config.d.ts +32 -0
  202. package/build/util/claude-config.js +84 -0
  203. package/build/util/hook-config.d.ts +27 -0
  204. package/build/util/hook-config.js +76 -0
  205. package/build/util/jsonc.d.ts +14 -0
  206. package/build/util/jsonc.js +104 -0
  207. package/build/util/plugin-cache-integrity.d.ts +51 -0
  208. package/build/util/plugin-cache-integrity.js +146 -0
  209. package/build/util/project-dir.d.ts +149 -0
  210. package/build/util/project-dir.js +340 -0
  211. package/build/util/sibling-mcp.d.ts +79 -0
  212. package/build/util/sibling-mcp.js +181 -0
  213. package/cli.bundle.mjs +1495 -0
  214. package/configs/antigravity/GEMINI.md +68 -0
  215. package/configs/antigravity/mcp_config.json +7 -0
  216. package/configs/antigravity-cli/hooks/hooks.json +37 -0
  217. package/configs/antigravity-cli/hooks.json +37 -0
  218. package/configs/antigravity-cli/mcp_config.json +10 -0
  219. package/configs/antigravity-cli/plugin.json +14 -0
  220. package/configs/antigravity-cli/rules/context-mode.md +77 -0
  221. package/configs/antigravity-cli/skills/context-mode/SKILL.md +77 -0
  222. package/configs/claude-code/CLAUDE.md +91 -0
  223. package/configs/codex/AGENTS.md +99 -0
  224. package/configs/codex/config.toml +8 -0
  225. package/configs/codex/hooks.json +47 -0
  226. package/configs/copilot-cli/.github/plugin/plugin.json +23 -0
  227. package/configs/copilot-cli/.mcp.json +12 -0
  228. package/configs/copilot-cli/README.md +47 -0
  229. package/configs/copilot-cli/hooks.json +41 -0
  230. package/configs/copilot-cli/skills/context-mode/SKILL.md +38 -0
  231. package/configs/cursor/context-mode.mdc +76 -0
  232. package/configs/cursor/hooks.json +21 -0
  233. package/configs/cursor/mcp.json +7 -0
  234. package/configs/gemini-cli/GEMINI.md +88 -0
  235. package/configs/gemini-cli/mcp.json +7 -0
  236. package/configs/gemini-cli/settings.json +60 -0
  237. package/configs/jetbrains-copilot/copilot-instructions.md +80 -0
  238. package/configs/jetbrains-copilot/hooks.json +16 -0
  239. package/configs/jetbrains-copilot/mcp.json +7 -0
  240. package/configs/kilo/AGENTS.md +84 -0
  241. package/configs/kilo/kilo.json +6 -0
  242. package/configs/kimi/hooks.json +54 -0
  243. package/configs/kiro/KIRO.md +84 -0
  244. package/configs/kiro/agent.json +18 -0
  245. package/configs/kiro/mcp.json +7 -0
  246. package/configs/omp/SYSTEM.md +85 -0
  247. package/configs/omp/mcp.json +7 -0
  248. package/configs/openclaw/AGENTS.md +87 -0
  249. package/configs/openclaw/openclaw.json +13 -0
  250. package/configs/opencode/AGENTS.md +84 -0
  251. package/configs/opencode/opencode.json +6 -0
  252. package/configs/pi/AGENTS.md +3 -0
  253. package/configs/qwen-code/QWEN.md +91 -0
  254. package/configs/vscode-copilot/copilot-instructions.md +80 -0
  255. package/configs/vscode-copilot/hooks.json +16 -0
  256. package/configs/vscode-copilot/mcp.json +7 -0
  257. package/configs/zed/AGENTS.md +68 -0
  258. package/hooks/antigravity-cli/payload.mjs +98 -0
  259. package/hooks/antigravity-cli/posttooluse.mjs +138 -0
  260. package/hooks/antigravity-cli/pretooluse.mjs +78 -0
  261. package/hooks/antigravity-cli/stop.mjs +58 -0
  262. package/hooks/auto-injection.mjs +102 -0
  263. package/hooks/cache-heal-utils.mjs +379 -0
  264. package/hooks/codex/platform.mjs +1 -0
  265. package/hooks/codex/posttooluse.mjs +66 -0
  266. package/hooks/codex/precompact.mjs +70 -0
  267. package/hooks/codex/pretooluse.mjs +42 -0
  268. package/hooks/codex/sessionstart.mjs +121 -0
  269. package/hooks/codex/stop.mjs +138 -0
  270. package/hooks/codex/userpromptsubmit.mjs +75 -0
  271. package/hooks/copilot-cli/posttooluse.mjs +79 -0
  272. package/hooks/copilot-cli/precompact.mjs +66 -0
  273. package/hooks/copilot-cli/pretooluse.mjs +41 -0
  274. package/hooks/copilot-cli/sessionstart.mjs +121 -0
  275. package/hooks/copilot-cli/stop.mjs +59 -0
  276. package/hooks/copilot-cli/userpromptsubmit.mjs +77 -0
  277. package/hooks/core/codex-caps.mjs +112 -0
  278. package/hooks/core/formatters.mjs +356 -0
  279. package/hooks/core/mcp-ready.mjs +109 -0
  280. package/hooks/core/platform-detect.mjs +49 -0
  281. package/hooks/core/routing.mjs +1050 -0
  282. package/hooks/core/stdin.mjs +90 -0
  283. package/hooks/core/tool-naming.mjs +57 -0
  284. package/hooks/cursor/afteragentresponse.mjs +74 -0
  285. package/hooks/cursor/hooks.json +31 -0
  286. package/hooks/cursor/posttooluse.mjs +72 -0
  287. package/hooks/cursor/pretooluse.mjs +26 -0
  288. package/hooks/cursor/sessionstart.mjs +97 -0
  289. package/hooks/cursor/stop.mjs +48 -0
  290. package/hooks/ensure-deps.mjs +254 -0
  291. package/hooks/formatters/claude-code.mjs +113 -0
  292. package/hooks/formatters/cursor.mjs +37 -0
  293. package/hooks/formatters/gemini-cli.mjs +55 -0
  294. package/hooks/formatters/vscode-copilot.mjs +55 -0
  295. package/hooks/gemini-cli/aftermodel.mjs +70 -0
  296. package/hooks/gemini-cli/aftertool.mjs +61 -0
  297. package/hooks/gemini-cli/beforeagent.mjs +99 -0
  298. package/hooks/gemini-cli/beforetool.mjs +27 -0
  299. package/hooks/gemini-cli/precompress.mjs +55 -0
  300. package/hooks/gemini-cli/sessionstart.mjs +137 -0
  301. package/hooks/heal-partial-install.mjs +712 -0
  302. package/hooks/hooks.json +143 -0
  303. package/hooks/jetbrains-copilot/posttooluse.mjs +61 -0
  304. package/hooks/jetbrains-copilot/precompact.mjs +55 -0
  305. package/hooks/jetbrains-copilot/pretooluse.mjs +27 -0
  306. package/hooks/jetbrains-copilot/sessionstart.mjs +124 -0
  307. package/hooks/kimi/platform.mjs +1 -0
  308. package/hooks/kimi/posttooluse.mjs +72 -0
  309. package/hooks/kimi/precompact.mjs +80 -0
  310. package/hooks/kimi/pretooluse.mjs +42 -0
  311. package/hooks/kimi/sessionend.mjs +61 -0
  312. package/hooks/kimi/sessionstart.mjs +113 -0
  313. package/hooks/kimi/stop.mjs +132 -0
  314. package/hooks/kimi/userpromptsubmit.mjs +90 -0
  315. package/hooks/kiro/agentspawn.mjs +97 -0
  316. package/hooks/kiro/posttooluse.mjs +51 -0
  317. package/hooks/kiro/pretooluse.mjs +64 -0
  318. package/hooks/kiro/userpromptsubmit.mjs +88 -0
  319. package/hooks/normalize-hooks.mjs +323 -0
  320. package/hooks/platform-bridge.mjs +340 -0
  321. package/hooks/posttooluse.mjs +225 -0
  322. package/hooks/precompact.mjs +100 -0
  323. package/hooks/pretooluse.mjs +227 -0
  324. package/hooks/qwen-code/platform.mjs +1 -0
  325. package/hooks/qwen-code/stop.mjs +168 -0
  326. package/hooks/routing-block.mjs +105 -0
  327. package/hooks/run-hook.mjs +95 -0
  328. package/hooks/security.bundle.mjs +2 -0
  329. package/hooks/session-attribution.bundle.mjs +1 -0
  330. package/hooks/session-db.bundle.mjs +145 -0
  331. package/hooks/session-directive.mjs +520 -0
  332. package/hooks/session-extract.bundle.mjs +3 -0
  333. package/hooks/session-helpers.mjs +428 -0
  334. package/hooks/session-loaders.mjs +448 -0
  335. package/hooks/session-snapshot.bundle.mjs +32 -0
  336. package/hooks/sessionstart.mjs +464 -0
  337. package/hooks/stop.mjs +82 -0
  338. package/hooks/suppress-stderr.mjs +25 -0
  339. package/hooks/userpromptsubmit.mjs +108 -0
  340. package/hooks/vscode-copilot/posttooluse.mjs +61 -0
  341. package/hooks/vscode-copilot/precompact.mjs +55 -0
  342. package/hooks/vscode-copilot/pretooluse.mjs +27 -0
  343. package/hooks/vscode-copilot/sessionstart.mjs +129 -0
  344. package/openclaw.plugin.json +38 -0
  345. package/package.json +132 -0
  346. package/scripts/heal-better-sqlite3.mjs +430 -0
  347. package/scripts/heal-installed-plugins.mjs +635 -0
  348. package/scripts/plugin-cache-integrity.mjs +248 -0
  349. package/scripts/postinstall.mjs +397 -0
  350. package/server.bundle.mjs +1421 -0
  351. package/server.js +5 -0
  352. package/skills/.ignore +7 -0
  353. package/skills/context-mode/SKILL.md +300 -0
  354. package/skills/context-mode/references/anti-patterns.md +283 -0
  355. package/skills/context-mode/references/patterns-javascript.md +298 -0
  356. package/skills/context-mode/references/patterns-python.md +304 -0
  357. package/skills/context-mode/references/patterns-shell.md +277 -0
  358. package/skills/ctx-doctor/SKILL.md +22 -0
  359. package/skills/ctx-index/SKILL.md +46 -0
  360. package/skills/ctx-insight/SKILL.md +24 -0
  361. package/skills/ctx-purge/SKILL.md +49 -0
  362. package/skills/ctx-search/SKILL.md +35 -0
  363. package/skills/ctx-stats/SKILL.md +26 -0
  364. package/skills/ctx-upgrade/SKILL.md +31 -0
  365. package/start.mjs +616 -0
@@ -0,0 +1,57 @@
1
+ /**
2
+ * truncate — Pure string truncation and escaping utilities for context-mode.
3
+ *
4
+ * These helpers are used by the core ContentStore (chunking) and
5
+ * SessionDB (snapshot building). They are extracted here so any
6
+ * consumer can import them without pulling in the full store or executor.
7
+ */
8
+ /**
9
+ * Return a prefix of `str` no longer than `maxChars` UTF-16 code units, with
10
+ * the cut backed off by one unit if it would split a surrogate pair. Mirrors
11
+ * the surrogate-safety semantics of the internal `byteSafePrefix`, but uses
12
+ * a character budget rather than a byte budget.
13
+ *
14
+ * Use this instead of bare `str.slice(0, n)` whenever the result is later
15
+ * JSON-encoded for an RFC 8259-strict consumer (e.g. tool_result payloads
16
+ * sent back to the host LLM API). `JSON.stringify` will emit a lone high
17
+ * surrogate as a literal `\uD8xx` escape, which is invalid JSON.
18
+ *
19
+ * @param str - Input string.
20
+ * @param maxChars - Hard cap in UTF-16 code units.
21
+ */
22
+ export declare function charSafePrefix(str: string, maxChars: number): string;
23
+ /**
24
+ * Serialize a value to JSON, then truncate the result to `maxBytes` bytes.
25
+ * If truncation occurs, the string is cut at a UTF-8-safe boundary and
26
+ * "... [truncated]" is appended. The result is NOT guaranteed to be valid
27
+ * JSON after truncation — it is suitable only for display/logging.
28
+ *
29
+ * The returned string is always <= `maxBytes` bytes. When `maxBytes` is
30
+ * smaller than the marker, the marker itself is byte-safely truncated.
31
+ *
32
+ * @param value - Any JSON-serializable value.
33
+ * @param maxBytes - Maximum byte length of the returned string.
34
+ * @param indent - JSON indentation spaces (default 2). Pass 0 for compact.
35
+ */
36
+ export declare function truncateJSON(value: unknown, maxBytes: number, indent?: number): string;
37
+ /**
38
+ * Escape a string for safe embedding in an XML or HTML attribute or text node.
39
+ * Replaces the five XML-reserved characters: `&`, `<`, `>`, `"`, `'`.
40
+ *
41
+ * Used by the resume snapshot template builder to embed user content in
42
+ * `<tool_response>` and `<user_message>` XML tags without breaking the
43
+ * structured prompt format.
44
+ */
45
+ export declare function escapeXML(str: string): string;
46
+ /**
47
+ * Return `str` unchanged if it fits within `maxBytes`, otherwise return a
48
+ * byte-safe slice with an ellipsis appended. Useful for single-value fields
49
+ * (e.g., tool response strings) where head+tail splitting is not needed.
50
+ *
51
+ * The returned string is always <= `maxBytes` bytes. When `maxBytes` is
52
+ * smaller than the ellipsis marker, the marker itself is byte-safely truncated.
53
+ *
54
+ * @param str - Input string.
55
+ * @param maxBytes - Hard byte cap.
56
+ */
57
+ export declare function capBytes(str: string, maxBytes: number): string;
@@ -0,0 +1,142 @@
1
+ /**
2
+ * truncate — Pure string truncation and escaping utilities for context-mode.
3
+ *
4
+ * These helpers are used by the core ContentStore (chunking) and
5
+ * SessionDB (snapshot building). They are extracted here so any
6
+ * consumer can import them without pulling in the full store or executor.
7
+ */
8
+ // ─────────────────────────────────────────────────────────
9
+ // Internal: byte-safe prefix
10
+ // ─────────────────────────────────────────────────────────
11
+ /**
12
+ * Return the longest character-prefix of `str` whose UTF-8 encoding is at
13
+ * most `maxBytes` bytes. Uses binary search to avoid O(n²) scanning. Returns
14
+ * "" when `maxBytes` is <= 0 so callers never exceed their budget.
15
+ *
16
+ * Guards against splitting a UTF-16 surrogate pair: if the prefix would end
17
+ * on a lone high surrogate, back off one code unit so the result round-trips
18
+ * through UTF-8 without producing a U+FFFD replacement character.
19
+ */
20
+ function byteSafePrefix(str, maxBytes) {
21
+ if (maxBytes <= 0)
22
+ return "";
23
+ if (Buffer.byteLength(str) <= maxBytes)
24
+ return str;
25
+ let lo = 0;
26
+ let hi = str.length;
27
+ while (lo < hi) {
28
+ const mid = (lo + hi + 1) >> 1;
29
+ if (Buffer.byteLength(str.slice(0, mid)) <= maxBytes) {
30
+ lo = mid;
31
+ }
32
+ else {
33
+ hi = mid - 1;
34
+ }
35
+ }
36
+ // If we landed between a high and low surrogate, back off so the prefix
37
+ // ends on a valid code point boundary.
38
+ if (lo > 0) {
39
+ const code = str.charCodeAt(lo - 1);
40
+ if (code >= 0xd800 && code <= 0xdbff)
41
+ lo -= 1;
42
+ }
43
+ return str.slice(0, lo);
44
+ }
45
+ // ─────────────────────────────────────────────────────────
46
+ // Char-count prefix (UTF-16 surrogate-safe)
47
+ // ─────────────────────────────────────────────────────────
48
+ /**
49
+ * Return a prefix of `str` no longer than `maxChars` UTF-16 code units, with
50
+ * the cut backed off by one unit if it would split a surrogate pair. Mirrors
51
+ * the surrogate-safety semantics of the internal `byteSafePrefix`, but uses
52
+ * a character budget rather than a byte budget.
53
+ *
54
+ * Use this instead of bare `str.slice(0, n)` whenever the result is later
55
+ * JSON-encoded for an RFC 8259-strict consumer (e.g. tool_result payloads
56
+ * sent back to the host LLM API). `JSON.stringify` will emit a lone high
57
+ * surrogate as a literal `\uD8xx` escape, which is invalid JSON.
58
+ *
59
+ * @param str - Input string.
60
+ * @param maxChars - Hard cap in UTF-16 code units.
61
+ */
62
+ export function charSafePrefix(str, maxChars) {
63
+ if (maxChars <= 0)
64
+ return "";
65
+ if (str.length <= maxChars)
66
+ return str;
67
+ let end = maxChars;
68
+ const code = str.charCodeAt(end - 1);
69
+ if (code >= 0xd800 && code <= 0xdbff)
70
+ end -= 1;
71
+ return str.slice(0, end);
72
+ }
73
+ // ─────────────────────────────────────────────────────────
74
+ // JSON truncation
75
+ // ─────────────────────────────────────────────────────────
76
+ /**
77
+ * Serialize a value to JSON, then truncate the result to `maxBytes` bytes.
78
+ * If truncation occurs, the string is cut at a UTF-8-safe boundary and
79
+ * "... [truncated]" is appended. The result is NOT guaranteed to be valid
80
+ * JSON after truncation — it is suitable only for display/logging.
81
+ *
82
+ * The returned string is always <= `maxBytes` bytes. When `maxBytes` is
83
+ * smaller than the marker, the marker itself is byte-safely truncated.
84
+ *
85
+ * @param value - Any JSON-serializable value.
86
+ * @param maxBytes - Maximum byte length of the returned string.
87
+ * @param indent - JSON indentation spaces (default 2). Pass 0 for compact.
88
+ */
89
+ export function truncateJSON(value, maxBytes, indent = 2) {
90
+ const serialized = JSON.stringify(value, null, indent) ?? "null";
91
+ if (Buffer.byteLength(serialized) <= maxBytes)
92
+ return serialized;
93
+ const marker = "... [truncated]";
94
+ const markerBytes = Buffer.byteLength(marker);
95
+ // Degenerate budget: can't fit serialized content + marker. Fit as much of
96
+ // the marker as we can so the return still honors `maxBytes`.
97
+ if (maxBytes <= markerBytes)
98
+ return byteSafePrefix(marker, maxBytes);
99
+ return byteSafePrefix(serialized, maxBytes - markerBytes) + marker;
100
+ }
101
+ // ─────────────────────────────────────────────────────────
102
+ // XML / HTML escaping
103
+ // ─────────────────────────────────────────────────────────
104
+ /**
105
+ * Escape a string for safe embedding in an XML or HTML attribute or text node.
106
+ * Replaces the five XML-reserved characters: `&`, `<`, `>`, `"`, `'`.
107
+ *
108
+ * Used by the resume snapshot template builder to embed user content in
109
+ * `<tool_response>` and `<user_message>` XML tags without breaking the
110
+ * structured prompt format.
111
+ */
112
+ export function escapeXML(str) {
113
+ return str
114
+ .replace(/&/g, "&amp;")
115
+ .replace(/</g, "&lt;")
116
+ .replace(/>/g, "&gt;")
117
+ .replace(/"/g, "&quot;")
118
+ .replace(/'/g, "&apos;");
119
+ }
120
+ // ─────────────────────────────────────────────────────────
121
+ // maxBytes guard
122
+ // ─────────────────────────────────────────────────────────
123
+ /**
124
+ * Return `str` unchanged if it fits within `maxBytes`, otherwise return a
125
+ * byte-safe slice with an ellipsis appended. Useful for single-value fields
126
+ * (e.g., tool response strings) where head+tail splitting is not needed.
127
+ *
128
+ * The returned string is always <= `maxBytes` bytes. When `maxBytes` is
129
+ * smaller than the ellipsis marker, the marker itself is byte-safely truncated.
130
+ *
131
+ * @param str - Input string.
132
+ * @param maxBytes - Hard byte cap.
133
+ */
134
+ export function capBytes(str, maxBytes) {
135
+ if (Buffer.byteLength(str) <= maxBytes)
136
+ return str;
137
+ const marker = "...";
138
+ const markerBytes = Buffer.byteLength(marker);
139
+ if (maxBytes <= markerBytes)
140
+ return byteSafePrefix(marker, maxBytes);
141
+ return byteSafePrefix(str, maxBytes - markerBytes) + marker;
142
+ }
@@ -0,0 +1,117 @@
1
+ /**
2
+ * types — Shared type definitions for context-mode packages.
3
+ *
4
+ * Contains interfaces that are genuinely shared between the core (ContentStore,
5
+ * PolyglotExecutor) and the session domain (SessionDB, event extraction).
6
+ * Import from "./types.js".
7
+ */
8
+ /** Tool call representation used during event extraction from Claude messages. */
9
+ export interface ToolCall {
10
+ toolName: string;
11
+ toolInput: Record<string, unknown>;
12
+ toolResponse?: string;
13
+ isError?: boolean;
14
+ }
15
+ /** User message representation used during event extraction. */
16
+ export interface UserMessage {
17
+ content: string;
18
+ timestamp?: string;
19
+ }
20
+ /**
21
+ * Session event as stored in SessionDB.
22
+ * Each event captures a discrete unit of session activity (tool use, user
23
+ * message, assistant response summary, etc.) for later resume-snapshot
24
+ * reconstruction.
25
+ */
26
+ export interface SessionEvent {
27
+ type: string;
28
+ category: string;
29
+ data: string;
30
+ priority: number;
31
+ data_hash: string;
32
+ /**
33
+ * Best-effort project attribution for this event.
34
+ * Empty string means unattributed/unknown.
35
+ */
36
+ project_dir?: string;
37
+ attribution_source?: string;
38
+ /** 0..1 confidence score for project attribution. */
39
+ attribution_confidence?: number;
40
+ }
41
+ /**
42
+ * Result returned by PolyglotExecutor after running a code snippet.
43
+ * Shared here so SessionDB can record execution outcomes without importing
44
+ * the full executor module.
45
+ */
46
+ export interface ExecResult {
47
+ stdout: string;
48
+ stderr: string;
49
+ exitCode: number;
50
+ timedOut: boolean;
51
+ /** Process was detached and continues running in the background. */
52
+ backgrounded?: boolean;
53
+ }
54
+ /**
55
+ * Result returned after indexing content into the knowledge base.
56
+ * Shared so session tooling can record what was indexed without importing
57
+ * ContentStore.
58
+ */
59
+ export interface IndexResult {
60
+ sourceId: number;
61
+ label: string;
62
+ totalChunks: number;
63
+ codeChunks: number;
64
+ }
65
+ /**
66
+ * A single search result returned from FTS5 BM25-ranked lookup.
67
+ * Shared for consumers that display or log results outside of ContentStore.
68
+ */
69
+ export interface SearchResult {
70
+ title: string;
71
+ content: string;
72
+ source: string;
73
+ rank: number;
74
+ contentType: "code" | "prose";
75
+ matchLayer?: "porter" | "trigram" | "fuzzy" | "rrf" | "rrf-fuzzy";
76
+ highlighted?: string;
77
+ timestamp?: string;
78
+ /**
79
+ * Session-id attribution copied from the `chunks.session_id` column
80
+ * (legacy unattributed chunks carry an empty string). Used by the
81
+ * `ctx_search` per-project filter (#737) to scope shared-DB results
82
+ * to the current project via the 2-step IN-clause strategy.
83
+ */
84
+ sessionId?: string;
85
+ }
86
+ /**
87
+ * Aggregate statistics for a ContentStore instance.
88
+ */
89
+ export interface StoreStats {
90
+ sources: number;
91
+ chunks: number;
92
+ codeChunks: number;
93
+ }
94
+ /**
95
+ * Structured representation of a session resume snapshot, suitable for
96
+ * injecting into a new conversation as context. Generated from stored
97
+ * SessionEvents by the snapshot builder.
98
+ */
99
+ export interface ResumeSnapshot {
100
+ /** ISO-8601 timestamp of when the snapshot was generated. */
101
+ generatedAt: string;
102
+ /** Human-readable summary of the session to this point. */
103
+ summary: string;
104
+ /** Ordered list of events selected for the snapshot (priority-filtered). */
105
+ events: SessionEvent[];
106
+ }
107
+ /**
108
+ * Priority levels for SessionEvent records. Higher numbers are more important
109
+ * and are retained when the snapshot budget is tight.
110
+ */
111
+ export declare const EventPriority: {
112
+ readonly LOW: 1;
113
+ readonly NORMAL: 2;
114
+ readonly HIGH: 3;
115
+ readonly CRITICAL: 4;
116
+ };
117
+ export type EventPriorityLevel = (typeof EventPriority)[keyof typeof EventPriority];
package/build/types.js ADDED
@@ -0,0 +1,20 @@
1
+ /**
2
+ * types — Shared type definitions for context-mode packages.
3
+ *
4
+ * Contains interfaces that are genuinely shared between the core (ContentStore,
5
+ * PolyglotExecutor) and the session domain (SessionDB, event extraction).
6
+ * Import from "./types.js".
7
+ */
8
+ // ─────────────────────────────────────────────────────────
9
+ // Priority constants
10
+ // ─────────────────────────────────────────────────────────
11
+ /**
12
+ * Priority levels for SessionEvent records. Higher numbers are more important
13
+ * and are retained when the snapshot budget is tight.
14
+ */
15
+ export const EventPriority = {
16
+ LOW: 1,
17
+ NORMAL: 2,
18
+ HIGH: 3,
19
+ CRITICAL: 4,
20
+ };
@@ -0,0 +1,32 @@
1
+ export declare function resolveClaudeConfigDir(env?: NodeJS.ProcessEnv): string;
2
+ /** Resolve the global settings.json path, honoring CLAUDE_CONFIG_DIR. */
3
+ export declare function resolveClaudeGlobalSettingsPath(env?: NodeJS.ProcessEnv): string;
4
+ /**
5
+ * Issue #451 round-3: cross-adapter deny-policy parity.
6
+ *
7
+ * `resolveClaudeGlobalSettingsPath` hardcodes the `.claude` segment, so
8
+ * non-Claude adapters (cursor, codex, qwen-code, gemini-cli, jetbrains-copilot,
9
+ * vscode-copilot, etc.) never had their global settings consulted by the
10
+ * security policy reader. This helper returns the union of:
11
+ *
12
+ * 1. The currently-detected adapter's home-rooted settings.json (when the
13
+ * adapter is non-claude — claude is already covered by entry 2).
14
+ * 2. The claude global settings.json (always — defense in depth).
15
+ *
16
+ * Static import of `../adapters/detect.js` is safe — detect.ts only imports
17
+ * `node:` builtins, `./types.js` (type-only), and `./client-map.js` (pure
18
+ * data). It does NOT import claude-config back, so no cycle.
19
+ *
20
+ * History: this used `createRequire(import.meta.url).resolve(...)` to lazy-
21
+ * load detect at call time. That pattern requires `require(esm)`, which is
22
+ * flag-gated on Node 22.x before 22.12 (`--experimental-require-module`).
23
+ * CI run 25877550371 on Node 22.5 silently failed every detect.* call —
24
+ * the catch block ate the error and every cross-adapter deny-policy test
25
+ * returned an empty policy list. Static import sidesteps the require(esm)
26
+ * gate entirely, so the same code works on every supported Node version
27
+ * (20.x, 22.5, 22.12+, 24+) without needing the experimental flag.
28
+ *
29
+ * The returned array is deduplicated and order-stable: adapter-specific path
30
+ * first (most specific), claude global second (fallback).
31
+ */
32
+ export declare function resolveAdapterGlobalSettingsPaths(env?: NodeJS.ProcessEnv): string[];
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Claude Code config directory resolver — single source of truth.
3
+ *
4
+ * Issue #460 follow-up: every Claude-aware reader (adapters, security policy
5
+ * loader, hook helpers) MUST agree on where global settings live. Hardcoding
6
+ * `~/.claude` in any one reader silently breaks `CLAUDE_CONFIG_DIR` for that
7
+ * code path, producing policy drift that is invisible until a user sets the
8
+ * env var and watches their settings get ignored.
9
+ *
10
+ * Mirrors the contract of `hooks/session-helpers.mjs::resolveConfigDir` and
11
+ * `ClaudeCodeAdapter.getConfigDir`:
12
+ * - env unset, empty string, or whitespace-only → ~/.claude
13
+ * - env starts with `~`, `~/`, or `~\` → expanded against homedir()
14
+ * - otherwise → resolved to absolute (relative paths anchor to cwd)
15
+ *
16
+ * Whitespace guard: shells that quote-pad the env value (`CLAUDE_CONFIG_DIR=" "`)
17
+ * would otherwise resolve to `cwd/<spaces>` — silently writing settings into
18
+ * the project tree. Trim before the truthy check so quote-padding falls back
19
+ * to `~/.claude` like a sane default.
20
+ *
21
+ * Cross-platform note: tilde regex strips a single leading `/` OR `\` so
22
+ * `~\Users\foo` works on Windows. `path.resolve` handles drive-letter joining.
23
+ */
24
+ import { resolve } from "node:path";
25
+ import { homedir } from "node:os";
26
+ import { detectPlatform, getSessionDirSegments } from "../adapters/detect.js";
27
+ export function resolveClaudeConfigDir(env = process.env) {
28
+ const envVal = env.CLAUDE_CONFIG_DIR;
29
+ if (envVal && envVal.trim() !== "") {
30
+ if (envVal.startsWith("~")) {
31
+ return resolve(homedir(), envVal.replace(/^~[/\\]?/, ""));
32
+ }
33
+ return resolve(envVal);
34
+ }
35
+ return resolve(homedir(), ".claude");
36
+ }
37
+ /** Resolve the global settings.json path, honoring CLAUDE_CONFIG_DIR. */
38
+ export function resolveClaudeGlobalSettingsPath(env = process.env) {
39
+ return resolve(resolveClaudeConfigDir(env), "settings.json");
40
+ }
41
+ /**
42
+ * Issue #451 round-3: cross-adapter deny-policy parity.
43
+ *
44
+ * `resolveClaudeGlobalSettingsPath` hardcodes the `.claude` segment, so
45
+ * non-Claude adapters (cursor, codex, qwen-code, gemini-cli, jetbrains-copilot,
46
+ * vscode-copilot, etc.) never had their global settings consulted by the
47
+ * security policy reader. This helper returns the union of:
48
+ *
49
+ * 1. The currently-detected adapter's home-rooted settings.json (when the
50
+ * adapter is non-claude — claude is already covered by entry 2).
51
+ * 2. The claude global settings.json (always — defense in depth).
52
+ *
53
+ * Static import of `../adapters/detect.js` is safe — detect.ts only imports
54
+ * `node:` builtins, `./types.js` (type-only), and `./client-map.js` (pure
55
+ * data). It does NOT import claude-config back, so no cycle.
56
+ *
57
+ * History: this used `createRequire(import.meta.url).resolve(...)` to lazy-
58
+ * load detect at call time. That pattern requires `require(esm)`, which is
59
+ * flag-gated on Node 22.x before 22.12 (`--experimental-require-module`).
60
+ * CI run 25877550371 on Node 22.5 silently failed every detect.* call —
61
+ * the catch block ate the error and every cross-adapter deny-policy test
62
+ * returned an empty policy list. Static import sidesteps the require(esm)
63
+ * gate entirely, so the same code works on every supported Node version
64
+ * (20.x, 22.5, 22.12+, 24+) without needing the experimental flag.
65
+ *
66
+ * The returned array is deduplicated and order-stable: adapter-specific path
67
+ * first (most specific), claude global second (fallback).
68
+ */
69
+ export function resolveAdapterGlobalSettingsPaths(env = process.env) {
70
+ const paths = [];
71
+ const detected = detectPlatform();
72
+ if (detected.platform !== "claude-code") {
73
+ const segments = getSessionDirSegments(detected.platform);
74
+ if (segments && segments.length > 0) {
75
+ paths.push(resolve(homedir(), ...segments, "settings.json"));
76
+ }
77
+ }
78
+ // Always include claude global as fallback (defense in depth).
79
+ const claudePath = resolveClaudeGlobalSettingsPath(env);
80
+ if (!paths.includes(claudePath)) {
81
+ paths.push(claudePath);
82
+ }
83
+ return paths;
84
+ }
@@ -0,0 +1,27 @@
1
+ import { type HookAdapter } from "../adapters/types.js";
2
+ export declare function getCommandsFromHookEntry(entry: unknown): string[];
3
+ /**
4
+ * Extract the hook script path from a hook command string.
5
+ *
6
+ * Post Algo-D2 this is a thin wrapper around `parseNodeCommand` with a
7
+ * single legacy fallback retained for stale-entry cleanup
8
+ * (`configureAllHooks` walks pre-v1.0.124 settings.json shapes that
9
+ * predate `buildNodeCommand`). The legacy branches are deliberately
10
+ * narrow:
11
+ *
12
+ * 1) Canonical: `"<nodePath>" "<scriptPath>.mjs"` — `parseNodeCommand`
13
+ * handles this; round-trips with `buildNodeCommand`.
14
+ * 2) Legacy quoted: `node "<scriptPath>.mjs"` — emitted by claude-code
15
+ * pre-D3. The script segment is fully quoted, no whitespace
16
+ * ambiguity.
17
+ * 3) Legacy unquoted: `node <scriptPath>.mjs` — only when the entire
18
+ * command is whitespace-safe (exactly two whitespace-separated
19
+ * tokens). The #548 wire shape — `node C:/Users/High Ground …` —
20
+ * contains internal whitespace so this branch refuses it. Returns
21
+ * `null` instead of grabbing the tail after the last whitespace.
22
+ *
23
+ * Anything else returns `null`, letting the doctor (Algo-D1) fall
24
+ * through to direct `existsSync` instead of trusting the regex.
25
+ */
26
+ export declare function extractHookScriptPath(command: string): string | null;
27
+ export declare function getHookScriptPaths(adapter: HookAdapter, pluginRoot: string): string[];
@@ -0,0 +1,76 @@
1
+ import { parseNodeCommand } from "../adapters/types.js";
2
+ export function getCommandsFromHookEntry(entry) {
3
+ const commands = [];
4
+ if (entry && typeof entry === "object") {
5
+ const command = entry.command;
6
+ if (typeof command === "string")
7
+ commands.push(command);
8
+ const hooks = entry.hooks;
9
+ if (Array.isArray(hooks)) {
10
+ for (const hook of hooks) {
11
+ if (hook && typeof hook === "object") {
12
+ const nestedCommand = hook.command;
13
+ if (typeof nestedCommand === "string")
14
+ commands.push(nestedCommand);
15
+ }
16
+ }
17
+ }
18
+ }
19
+ return commands;
20
+ }
21
+ /**
22
+ * Extract the hook script path from a hook command string.
23
+ *
24
+ * Post Algo-D2 this is a thin wrapper around `parseNodeCommand` with a
25
+ * single legacy fallback retained for stale-entry cleanup
26
+ * (`configureAllHooks` walks pre-v1.0.124 settings.json shapes that
27
+ * predate `buildNodeCommand`). The legacy branches are deliberately
28
+ * narrow:
29
+ *
30
+ * 1) Canonical: `"<nodePath>" "<scriptPath>.mjs"` — `parseNodeCommand`
31
+ * handles this; round-trips with `buildNodeCommand`.
32
+ * 2) Legacy quoted: `node "<scriptPath>.mjs"` — emitted by claude-code
33
+ * pre-D3. The script segment is fully quoted, no whitespace
34
+ * ambiguity.
35
+ * 3) Legacy unquoted: `node <scriptPath>.mjs` — only when the entire
36
+ * command is whitespace-safe (exactly two whitespace-separated
37
+ * tokens). The #548 wire shape — `node C:/Users/High Ground …` —
38
+ * contains internal whitespace so this branch refuses it. Returns
39
+ * `null` instead of grabbing the tail after the last whitespace.
40
+ *
41
+ * Anything else returns `null`, letting the doctor (Algo-D1) fall
42
+ * through to direct `existsSync` instead of trusting the regex.
43
+ */
44
+ export function extractHookScriptPath(command) {
45
+ const parsed = parseNodeCommand(command);
46
+ if (parsed) {
47
+ return parsed.scriptPath.endsWith(".mjs") ? parsed.scriptPath : null;
48
+ }
49
+ // Legacy quoted: `node "/path/with spaces/x.mjs"` (pre-D3 claude-code emit).
50
+ const legacyQuoted = command.match(/^\s*node\s+"([^"]+\.mjs)"\s*$/);
51
+ if (legacyQuoted)
52
+ return legacyQuoted[1];
53
+ // Legacy unquoted: `node /path/x.mjs` — refuses internal whitespace
54
+ // by anchoring both tokens. The #548 ambiguous shape has 3+ tokens
55
+ // (spaces in the path) and falls through to `null`.
56
+ const legacyBare = command.match(/^\s*node\s+(\S+\.mjs)\s*$/);
57
+ if (legacyBare)
58
+ return legacyBare[1];
59
+ return null;
60
+ }
61
+ export function getHookScriptPaths(adapter, pluginRoot) {
62
+ const paths = new Set();
63
+ const hookConfig = adapter.generateHookConfig(pluginRoot);
64
+ for (const entries of Object.values(hookConfig)) {
65
+ if (!Array.isArray(entries))
66
+ continue;
67
+ for (const entry of entries) {
68
+ for (const command of getCommandsFromHookEntry(entry)) {
69
+ const scriptPath = extractHookScriptPath(command);
70
+ if (scriptPath)
71
+ paths.add(scriptPath);
72
+ }
73
+ }
74
+ }
75
+ return [...paths];
76
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * util/jsonc — string-aware JSONC comment + trailing-comma stripping and a
3
+ * tolerant parse. Several agent CLIs ship config files as JSONC (VS Code
4
+ * `mcp.json`, Zed `settings.json`), so a strict `JSON.parse` false-fails on a
5
+ * perfectly valid commented file. Use `parseJsonc` whenever reading a
6
+ * platform config we did not write ourselves.
7
+ */
8
+ /** Strip `//` line + `/* *​/` block comments and trailing commas, string-aware. */
9
+ export declare function stripJsonComments(str: string): string;
10
+ /**
11
+ * Parse JSON or JSONC. Tries strict `JSON.parse` first (fast, exact), then a
12
+ * comment/trailing-comma-stripped parse. Returns `undefined` when both fail.
13
+ */
14
+ export declare function parseJsonc<T = unknown>(raw: string): T | undefined;
@@ -0,0 +1,104 @@
1
+ /**
2
+ * util/jsonc — string-aware JSONC comment + trailing-comma stripping and a
3
+ * tolerant parse. Several agent CLIs ship config files as JSONC (VS Code
4
+ * `mcp.json`, Zed `settings.json`), so a strict `JSON.parse` false-fails on a
5
+ * perfectly valid commented file. Use `parseJsonc` whenever reading a
6
+ * platform config we did not write ourselves.
7
+ */
8
+ /** Strip `//` line + `/* *​/` block comments and trailing commas, string-aware. */
9
+ export function stripJsonComments(str) {
10
+ let out = "";
11
+ let inString = false;
12
+ let escaped = false;
13
+ let inBlockComment = false;
14
+ for (let i = 0; i < str.length; i++) {
15
+ const c = str[i];
16
+ const next = str[i + 1];
17
+ if (inBlockComment) {
18
+ if (c === "*" && next === "/") {
19
+ inBlockComment = false;
20
+ i++;
21
+ }
22
+ continue;
23
+ }
24
+ if (escaped) {
25
+ out += c;
26
+ escaped = false;
27
+ continue;
28
+ }
29
+ if (c === "\\") {
30
+ out += c;
31
+ escaped = inString;
32
+ continue;
33
+ }
34
+ if (c === '"') {
35
+ inString = !inString;
36
+ out += c;
37
+ continue;
38
+ }
39
+ if (!inString && c === "/" && next === "/") {
40
+ while (i < str.length && str[i] !== "\n")
41
+ i++;
42
+ if (i < str.length)
43
+ out += "\n";
44
+ continue;
45
+ }
46
+ if (!inString && c === "/" && next === "*") {
47
+ inBlockComment = true;
48
+ i++;
49
+ continue;
50
+ }
51
+ out += c;
52
+ }
53
+ // Trailing-comma removal, string-aware. The scan above already removed
54
+ // comments, so this second pass over `out` only needs to track string state:
55
+ // a comma is "trailing" when the next significant char is `}` or `]`. Doing
56
+ // it here — instead of a post-hoc trailing-comma regex over the whole string —
57
+ // preserves commas inside string literals (e.g. "[1, ]"), which that regex
58
+ // silently corrupted to "[1 ]". See #787 review.
59
+ let result = "";
60
+ let inStr = false;
61
+ let esc = false;
62
+ for (let i = 0; i < out.length; i++) {
63
+ const c = out[i];
64
+ if (esc) {
65
+ result += c;
66
+ esc = false;
67
+ continue;
68
+ }
69
+ if (c === "\\") {
70
+ result += c;
71
+ esc = inStr;
72
+ continue;
73
+ }
74
+ if (c === '"') {
75
+ inStr = !inStr;
76
+ result += c;
77
+ continue;
78
+ }
79
+ if (!inStr && c === ",") {
80
+ let j = i + 1;
81
+ while (j < out.length && (out[j] === " " || out[j] === "\t" || out[j] === "\r" || out[j] === "\n"))
82
+ j++;
83
+ if (out[j] === "}" || out[j] === "]")
84
+ continue;
85
+ }
86
+ result += c;
87
+ }
88
+ return result;
89
+ }
90
+ /**
91
+ * Parse JSON or JSONC. Tries strict `JSON.parse` first (fast, exact), then a
92
+ * comment/trailing-comma-stripped parse. Returns `undefined` when both fail.
93
+ */
94
+ export function parseJsonc(raw) {
95
+ for (const candidate of [raw, stripJsonComments(raw)]) {
96
+ try {
97
+ return JSON.parse(candidate);
98
+ }
99
+ catch {
100
+ /* try next */
101
+ }
102
+ }
103
+ return undefined;
104
+ }