@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,532 @@
1
+ /**
2
+ * Snapshot builder — converts stored SessionEvents into a reference-based
3
+ * XML resume snapshot.
4
+ *
5
+ * Pure functions only. No database access, no file system, no side effects.
6
+ *
7
+ * The output XML is injected into the LLM's context after a compact event to
8
+ * restore session awareness. Instead of truncated inline data, each section
9
+ * contains a natural summary plus a runnable search tool call that retrieves
10
+ * full details from the indexed knowledge base on demand.
11
+ *
12
+ * Zero truncation. Zero information loss. Full data lives in SessionDB;
13
+ * the snapshot is a table of contents.
14
+ */
15
+ import { escapeXML } from "../truncate.js";
16
+ // ── Helpers ──────────────────────────────────────────────────────────────────
17
+ const MAX_ACTIVE_FILES = 10;
18
+ /**
19
+ * Extract 2-4 keyword phrases from a list of strings for BM25 search queries.
20
+ * Takes actual data values and picks representative terms.
21
+ */
22
+ function buildQueries(items, maxQueries = 4) {
23
+ const unique = [...new Set(items.filter(s => s.length > 0))];
24
+ const selected = unique.slice(0, maxQueries);
25
+ return selected.map(s => {
26
+ // Take the first ~80 chars as a query — enough for BM25 matching
27
+ const trimmed = s.length > 80 ? s.slice(0, 80) : s;
28
+ return trimmed;
29
+ });
30
+ }
31
+ /**
32
+ * Format a runnable tool call block for a section.
33
+ */
34
+ function toolCall(toolName, queries) {
35
+ if (queries.length === 0)
36
+ return "";
37
+ const escaped = queries.map(q => `"${escapeXML(q)}"`).join(", ");
38
+ return `\n For full details:\n ${escapeXML(toolName)}(\n queries: [${escaped}],\n source: "session-events"\n )`;
39
+ }
40
+ // ── Section builders ─────────────────────────────────────────────────────────
41
+ function buildFilesSection(fileEvents, searchTool) {
42
+ if (fileEvents.length === 0)
43
+ return "";
44
+ // Build per-file operation counts
45
+ const fileMap = new Map();
46
+ for (const ev of fileEvents) {
47
+ const path = ev.data;
48
+ let entry = fileMap.get(path);
49
+ if (!entry) {
50
+ entry = { ops: new Map() };
51
+ fileMap.set(path, entry);
52
+ }
53
+ let op;
54
+ if (ev.type === "file_write")
55
+ op = "write";
56
+ else if (ev.type === "file_read")
57
+ op = "read";
58
+ else if (ev.type === "file_edit")
59
+ op = "edit";
60
+ else
61
+ op = ev.type;
62
+ entry.ops.set(op, (entry.ops.get(op) ?? 0) + 1);
63
+ }
64
+ // Limit to last MAX_ACTIVE_FILES files (by insertion order = chronological)
65
+ const entries = Array.from(fileMap.entries());
66
+ const limited = entries.slice(-MAX_ACTIVE_FILES);
67
+ const summaryLines = [];
68
+ const queryTerms = [];
69
+ for (const [path, { ops }] of limited) {
70
+ const opsStr = Array.from(ops.entries())
71
+ .map(([k, v]) => `${k}×${v}`)
72
+ .join(", ");
73
+ // Use just the filename for concise display
74
+ const fileName = path.split("/").pop() ?? path;
75
+ summaryLines.push(` ${escapeXML(fileName)} (${escapeXML(opsStr)})`);
76
+ queryTerms.push(`${fileName} ${Array.from(ops.keys()).join(" ")}`);
77
+ }
78
+ const queries = buildQueries(queryTerms);
79
+ const lines = [
80
+ ` <files count="${fileMap.size}">`,
81
+ ...summaryLines,
82
+ toolCall(searchTool, queries),
83
+ ` </files>`,
84
+ ];
85
+ return lines.join("\n");
86
+ }
87
+ function buildErrorsSection(errorEvents, searchTool) {
88
+ if (errorEvents.length === 0)
89
+ return "";
90
+ const summaryLines = [];
91
+ const queryTerms = [];
92
+ for (const ev of errorEvents) {
93
+ summaryLines.push(` ${escapeXML(ev.data)}`);
94
+ queryTerms.push(ev.data);
95
+ }
96
+ const queries = buildQueries(queryTerms);
97
+ const lines = [
98
+ ` <errors count="${errorEvents.length}">`,
99
+ ...summaryLines,
100
+ toolCall(searchTool, queries),
101
+ ` </errors>`,
102
+ ];
103
+ return lines.join("\n");
104
+ }
105
+ function buildDecisionsSection(decisionEvents, searchTool) {
106
+ if (decisionEvents.length === 0)
107
+ return "";
108
+ const seen = new Set();
109
+ const summaryLines = [];
110
+ const queryTerms = [];
111
+ for (const ev of decisionEvents) {
112
+ if (seen.has(ev.data))
113
+ continue;
114
+ seen.add(ev.data);
115
+ summaryLines.push(` ${escapeXML(ev.data)}`);
116
+ queryTerms.push(ev.data);
117
+ }
118
+ if (summaryLines.length === 0)
119
+ return "";
120
+ const queries = buildQueries(queryTerms);
121
+ const lines = [
122
+ ` <decisions count="${summaryLines.length}">`,
123
+ ...summaryLines,
124
+ toolCall(searchTool, queries),
125
+ ` </decisions>`,
126
+ ];
127
+ return lines.join("\n");
128
+ }
129
+ function buildRulesSection(ruleEvents, searchTool) {
130
+ if (ruleEvents.length === 0)
131
+ return "";
132
+ const seen = new Set();
133
+ const summaryLines = [];
134
+ const queryTerms = [];
135
+ for (const ev of ruleEvents) {
136
+ if (seen.has(ev.data))
137
+ continue;
138
+ seen.add(ev.data);
139
+ if (ev.type === "rule_content") {
140
+ summaryLines.push(` ${escapeXML(ev.data)}`);
141
+ }
142
+ else {
143
+ summaryLines.push(` ${escapeXML(ev.data)}`);
144
+ }
145
+ queryTerms.push(ev.data);
146
+ }
147
+ if (summaryLines.length === 0)
148
+ return "";
149
+ const queries = buildQueries(queryTerms);
150
+ const lines = [
151
+ ` <rules count="${summaryLines.length}">`,
152
+ ...summaryLines,
153
+ toolCall(searchTool, queries),
154
+ ` </rules>`,
155
+ ];
156
+ return lines.join("\n");
157
+ }
158
+ function buildGitSection(gitEvents, searchTool) {
159
+ if (gitEvents.length === 0)
160
+ return "";
161
+ const summaryLines = [];
162
+ const queryTerms = [];
163
+ for (const ev of gitEvents) {
164
+ summaryLines.push(` ${escapeXML(ev.data)}`);
165
+ queryTerms.push(ev.data);
166
+ }
167
+ const queries = buildQueries(queryTerms);
168
+ const lines = [
169
+ ` <git count="${gitEvents.length}">`,
170
+ ...summaryLines,
171
+ toolCall(searchTool, queries),
172
+ ` </git>`,
173
+ ];
174
+ return lines.join("\n");
175
+ }
176
+ /**
177
+ * Render <task_state> from task events.
178
+ * Reconstructs the full task list from create/update events,
179
+ * filters out completed tasks, and renders only pending/in-progress work.
180
+ *
181
+ * TaskCreate events have `{ subject }`, TaskUpdate events have `{ taskId, status }`.
182
+ * Match by chronological order: creates[0] -> lowest taskId from updates.
183
+ */
184
+ export function renderTaskState(taskEvents) {
185
+ if (taskEvents.length === 0)
186
+ return "";
187
+ const creates = [];
188
+ const updates = {};
189
+ for (const ev of taskEvents) {
190
+ try {
191
+ const parsed = JSON.parse(ev.data);
192
+ if (typeof parsed.subject === "string") {
193
+ creates.push(parsed.subject);
194
+ }
195
+ else if (typeof parsed.taskId === "string" && typeof parsed.status === "string") {
196
+ updates[parsed.taskId] = parsed.status;
197
+ }
198
+ }
199
+ catch { /* not JSON */ }
200
+ }
201
+ if (creates.length === 0)
202
+ return "";
203
+ const DONE = new Set(["completed", "deleted", "failed"]);
204
+ // Match creates to updates positionally (creates[0] -> lowest taskId)
205
+ const sortedIds = Object.keys(updates).sort((a, b) => Number(a) - Number(b));
206
+ const pending = [];
207
+ for (let i = 0; i < creates.length; i++) {
208
+ const matchedId = sortedIds[i];
209
+ const status = matchedId ? (updates[matchedId] ?? "pending") : "pending";
210
+ if (!DONE.has(status)) {
211
+ pending.push(creates[i]);
212
+ }
213
+ }
214
+ // All tasks completed — nothing to render
215
+ if (pending.length === 0)
216
+ return "";
217
+ const lines = [];
218
+ for (const task of pending) {
219
+ lines.push(` [pending] ${escapeXML(task)}`);
220
+ }
221
+ return lines.join("\n");
222
+ }
223
+ function buildTaskSection(taskEvents, searchTool) {
224
+ const taskContent = renderTaskState(taskEvents);
225
+ if (!taskContent)
226
+ return "";
227
+ const queryTerms = [];
228
+ for (const ev of taskEvents) {
229
+ try {
230
+ const parsed = JSON.parse(ev.data);
231
+ if (typeof parsed.subject === "string") {
232
+ queryTerms.push(parsed.subject);
233
+ }
234
+ }
235
+ catch { /* not JSON */ }
236
+ }
237
+ const queries = buildQueries(queryTerms);
238
+ const pendingCount = taskContent.split("\n").length;
239
+ const lines = [
240
+ ` <task_state count="${pendingCount}">`,
241
+ taskContent,
242
+ toolCall(searchTool, queries),
243
+ ` </task_state>`,
244
+ ];
245
+ return lines.join("\n");
246
+ }
247
+ function buildEnvironmentSection(cwdEvents, envEvents, searchTool) {
248
+ if (cwdEvents.length === 0 && envEvents.length === 0)
249
+ return "";
250
+ const summaryLines = [];
251
+ const queryTerms = [];
252
+ if (cwdEvents.length > 0) {
253
+ const lastCwd = cwdEvents[cwdEvents.length - 1];
254
+ summaryLines.push(` cwd: ${escapeXML(lastCwd.data)}`);
255
+ queryTerms.push("working directory");
256
+ }
257
+ for (const env of envEvents) {
258
+ summaryLines.push(` ${escapeXML(env.data)}`);
259
+ queryTerms.push(env.data);
260
+ }
261
+ const queries = buildQueries(queryTerms);
262
+ const lines = [
263
+ ` <environment>`,
264
+ ...summaryLines,
265
+ toolCall(searchTool, queries),
266
+ ` </environment>`,
267
+ ];
268
+ return lines.join("\n");
269
+ }
270
+ function buildSubagentsSection(subagentEvents, searchTool) {
271
+ if (subagentEvents.length === 0)
272
+ return "";
273
+ const summaryLines = [];
274
+ const queryTerms = [];
275
+ for (const ev of subagentEvents) {
276
+ const status = ev.type === "subagent_completed" ? "completed"
277
+ : ev.type === "subagent_launched" ? "launched"
278
+ : "unknown";
279
+ summaryLines.push(` [${status}] ${escapeXML(ev.data)}`);
280
+ queryTerms.push(`subagent ${ev.data}`);
281
+ }
282
+ const queries = buildQueries(queryTerms);
283
+ const lines = [
284
+ ` <subagents count="${subagentEvents.length}">`,
285
+ ...summaryLines,
286
+ toolCall(searchTool, queries),
287
+ ` </subagents>`,
288
+ ];
289
+ return lines.join("\n");
290
+ }
291
+ function buildSkillsSection(skillEvents, searchTool) {
292
+ if (skillEvents.length === 0)
293
+ return "";
294
+ // Count invocations per skill name
295
+ const skillCounts = new Map();
296
+ for (const ev of skillEvents) {
297
+ const name = ev.data.split(":")[0].trim();
298
+ skillCounts.set(name, (skillCounts.get(name) ?? 0) + 1);
299
+ }
300
+ const summaryLines = [];
301
+ const queryTerms = [];
302
+ for (const [name, count] of skillCounts) {
303
+ summaryLines.push(` ${escapeXML(name)} (${count}×)`);
304
+ queryTerms.push(`skill ${name} invocation`);
305
+ }
306
+ const queries = buildQueries(queryTerms);
307
+ const lines = [
308
+ ` <skills count="${skillEvents.length}">`,
309
+ ...summaryLines,
310
+ toolCall(searchTool, queries),
311
+ ` </skills>`,
312
+ ];
313
+ return lines.join("\n");
314
+ }
315
+ function buildRolesSection(roleEvents, searchTool) {
316
+ if (roleEvents.length === 0)
317
+ return "";
318
+ const seen = new Set();
319
+ const summaryLines = [];
320
+ const queryTerms = [];
321
+ for (const ev of roleEvents) {
322
+ if (seen.has(ev.data))
323
+ continue;
324
+ seen.add(ev.data);
325
+ summaryLines.push(` ${escapeXML(ev.data)}`);
326
+ queryTerms.push(ev.data);
327
+ }
328
+ if (summaryLines.length === 0)
329
+ return "";
330
+ const queries = buildQueries(queryTerms);
331
+ const lines = [
332
+ ` <roles count="${summaryLines.length}">`,
333
+ ...summaryLines,
334
+ toolCall(searchTool, queries),
335
+ ` </roles>`,
336
+ ];
337
+ return lines.join("\n");
338
+ }
339
+ function buildIntentSection(intentEvents) {
340
+ if (intentEvents.length === 0)
341
+ return "";
342
+ const lastIntent = intentEvents[intentEvents.length - 1];
343
+ return ` <intent mode="${escapeXML(lastIntent.data)}"/>`;
344
+ }
345
+ /**
346
+ * Restore the most recent stated session goal verbatim. Placed at the top of
347
+ * the snapshot (right after how_to_search) so the resuming LLM reads the
348
+ * active objective before anything else and keeps working toward it.
349
+ */
350
+ function buildGoalSection(goalEvents) {
351
+ if (goalEvents.length === 0)
352
+ return "";
353
+ const lastGoal = goalEvents[goalEvents.length - 1];
354
+ return [
355
+ ` <session_goal>`,
356
+ ` The active objective for this session. Keep working toward it until it is met; do not ask the user to restate it.`,
357
+ ` ${escapeXML(lastGoal.data)}`,
358
+ ` </session_goal>`,
359
+ ].join("\n");
360
+ }
361
+ /**
362
+ * Raw-prompt safety net (issue #535):
363
+ * Always surface the most recent user prompts verbatim so the next LLM
364
+ * sees them even if every universal-rule detector misses. Bound per-prompt
365
+ * payload to RECENT_MESSAGE_MAX_CHARS Unicode codepoints; bound the total
366
+ * count to RECENT_MESSAGES_LIMIT to keep the resume block compact.
367
+ */
368
+ const RECENT_MESSAGES_LIMIT = 3;
369
+ const RECENT_MESSAGE_MAX_CHARS = 400;
370
+ function truncateForSnapshot(value, max) {
371
+ const codepoints = [...value];
372
+ if (codepoints.length <= max)
373
+ return value;
374
+ return codepoints.slice(0, max).join("");
375
+ }
376
+ function buildRecentMessagesSection(userPromptEvents) {
377
+ if (userPromptEvents.length === 0)
378
+ return "";
379
+ // Last N in chronological order — newest at the bottom mirrors the
380
+ // way the user reads their own scrollback.
381
+ const recent = userPromptEvents.slice(-RECENT_MESSAGES_LIMIT);
382
+ const items = recent
383
+ .map(ev => {
384
+ const body = truncateForSnapshot(ev.data ?? "", RECENT_MESSAGE_MAX_CHARS);
385
+ if (!body)
386
+ return "";
387
+ return ` <message>${escapeXML(body)}</message>`;
388
+ })
389
+ .filter(Boolean);
390
+ if (items.length === 0)
391
+ return "";
392
+ return [
393
+ ` <recent_user_messages count="${items.length}">`,
394
+ ...items,
395
+ ` </recent_user_messages>`,
396
+ ].join("\n");
397
+ }
398
+ // ── Main builder ─────────────────────────────────────────────────────────────
399
+ /**
400
+ * Build a reference-based resume snapshot XML string from stored session events.
401
+ *
402
+ * Algorithm:
403
+ * 1. Group events by category
404
+ * 2. For each non-empty category, build a summary section with a runnable
405
+ * search tool call containing exact queries for full details
406
+ * 3. Assemble ALL non-empty sections — no priority dropping, no byte budget
407
+ */
408
+ export function buildResumeSnapshot(events, opts) {
409
+ const compactCount = opts?.compactCount ?? 1;
410
+ const searchTool = opts?.searchTool ?? "ctx_search";
411
+ const now = new Date().toISOString();
412
+ // ── Group events by category ──
413
+ const fileEvents = [];
414
+ const taskEvents = [];
415
+ const ruleEvents = [];
416
+ const decisionEvents = [];
417
+ const cwdEvents = [];
418
+ const errorEvents = [];
419
+ const envEvents = [];
420
+ const gitEvents = [];
421
+ const subagentEvents = [];
422
+ const intentEvents = [];
423
+ const goalEvents = [];
424
+ const skillEvents = [];
425
+ const roleEvents = [];
426
+ const userPromptEvents = [];
427
+ for (const ev of events) {
428
+ switch (ev.category) {
429
+ case "file":
430
+ fileEvents.push(ev);
431
+ break;
432
+ case "task":
433
+ taskEvents.push(ev);
434
+ break;
435
+ case "rule":
436
+ ruleEvents.push(ev);
437
+ break;
438
+ case "decision":
439
+ decisionEvents.push(ev);
440
+ break;
441
+ case "cwd":
442
+ cwdEvents.push(ev);
443
+ break;
444
+ case "error":
445
+ errorEvents.push(ev);
446
+ break;
447
+ case "env":
448
+ envEvents.push(ev);
449
+ break;
450
+ case "git":
451
+ gitEvents.push(ev);
452
+ break;
453
+ case "subagent":
454
+ subagentEvents.push(ev);
455
+ break;
456
+ case "intent":
457
+ intentEvents.push(ev);
458
+ break;
459
+ case "goal":
460
+ goalEvents.push(ev);
461
+ break;
462
+ case "skill":
463
+ skillEvents.push(ev);
464
+ break;
465
+ case "role":
466
+ roleEvents.push(ev);
467
+ break;
468
+ case "user-prompt":
469
+ userPromptEvents.push(ev);
470
+ break;
471
+ }
472
+ }
473
+ // ── Build all sections ──
474
+ const sections = [];
475
+ // How-to-search instruction block (always present)
476
+ sections.push(` <how_to_search>
477
+ Each section below contains a summary of prior work.
478
+ For FULL DETAILS, run the exact tool call shown under each section.
479
+ Do NOT ask the user to re-explain prior work. Search first.
480
+ Do NOT invent your own queries — use the ones provided.
481
+ </how_to_search>`);
482
+ // Session goal first — the objective the LLM must keep working toward.
483
+ const goal = buildGoalSection(goalEvents);
484
+ if (goal)
485
+ sections.push(goal);
486
+ const files = buildFilesSection(fileEvents, searchTool);
487
+ if (files)
488
+ sections.push(files);
489
+ const errors = buildErrorsSection(errorEvents, searchTool);
490
+ if (errors)
491
+ sections.push(errors);
492
+ const decisions = buildDecisionsSection(decisionEvents, searchTool);
493
+ if (decisions)
494
+ sections.push(decisions);
495
+ const rules = buildRulesSection(ruleEvents, searchTool);
496
+ if (rules)
497
+ sections.push(rules);
498
+ const git = buildGitSection(gitEvents, searchTool);
499
+ if (git)
500
+ sections.push(git);
501
+ const tasks = buildTaskSection(taskEvents, searchTool);
502
+ if (tasks)
503
+ sections.push(tasks);
504
+ const environment = buildEnvironmentSection(cwdEvents, envEvents, searchTool);
505
+ if (environment)
506
+ sections.push(environment);
507
+ const subagents = buildSubagentsSection(subagentEvents, searchTool);
508
+ if (subagents)
509
+ sections.push(subagents);
510
+ const skills = buildSkillsSection(skillEvents, searchTool);
511
+ if (skills)
512
+ sections.push(skills);
513
+ const roles = buildRolesSection(roleEvents, searchTool);
514
+ if (roles)
515
+ sections.push(roles);
516
+ const intent = buildIntentSection(intentEvents);
517
+ if (intent)
518
+ sections.push(intent);
519
+ // Raw-prompt safety net — always last so it stays adjacent to the next
520
+ // LLM turn and is read after the structured sections.
521
+ const recentMessages = buildRecentMessagesSection(userPromptEvents);
522
+ if (recentMessages)
523
+ sections.push(recentMessages);
524
+ // ── Assemble ──
525
+ const header = `<session_resume events="${events.length}" compact_count="${compactCount}" generated_at="${now}">`;
526
+ const footer = `</session_resume>`;
527
+ const body = sections.join("\n\n");
528
+ if (body) {
529
+ return `${header}\n\n${body}\n\n${footer}`;
530
+ }
531
+ return `${header}\n${footer}`;
532
+ }
@@ -0,0 +1,56 @@
1
+ /**
2
+ * walkDirectory — bounded recursive directory walker for ctx_index (#687).
3
+ *
4
+ * Issue: ctx_index refused directory paths via the security gate at
5
+ * src/store.ts:845 ("refusing to index <path>: not a regular file"). The gate
6
+ * is a TOCTOU defense from #442 round-3 and MUST be preserved — directory
7
+ * support is layered as a separate concern here. Each file produced by
8
+ * walkDirectory is then read via the existing per-file
9
+ * `openSync + fstatSync.isFile()` invariant in `ContentStore.index()`.
10
+ *
11
+ * Reported by @matiasduartee across 4 clients × Windows 11.
12
+ * https://github.com/anthropic-experimental/context-mode/issues/687
13
+ *
14
+ * Design constraints:
15
+ * - No new dependencies (avoid the `ignore` package — issue #687 Diagnose).
16
+ * - Cross-OS: path.sep / path.join everywhere, never raw "/" string ops.
17
+ * - Symlink cycle detection via a resolved-path Set.
18
+ * - Symlink-escape rejection: refuse to follow symlinks that resolve outside
19
+ * the rootPath (defense-in-depth alongside per-file checkFilePathDenyPolicy).
20
+ * - FTS5-blowup guard: hard cap maxFiles (default 200, per Architect).
21
+ */
22
+ export interface WalkOptions {
23
+ /** Glob-ish include patterns. Empty/undefined means include all (subject to extensions). */
24
+ include?: string[];
25
+ /** Glob-ish exclude patterns. Merged with sensible defaults. */
26
+ exclude?: string[];
27
+ /** Max recursion depth from rootPath (0 = root only). Default 5. */
28
+ maxDepth?: number;
29
+ /** Hard cap on total files. Default 200 — FTS5 blow-up guard. */
30
+ maxFiles?: number;
31
+ /** Allowed file extensions (with leading dot). Empty/undefined means default set. */
32
+ extensions?: string[];
33
+ /** Apply nearest .gitignore rules during walk. Default true. */
34
+ respectGitignore?: boolean;
35
+ /** Follow directory symlinks. Default false (cycle hazard + escape risk). */
36
+ followSymlinks?: boolean;
37
+ }
38
+ export interface WalkResult {
39
+ files: string[];
40
+ /** True when maxFiles cap was hit and traversal halted early. */
41
+ capped: boolean;
42
+ /** Total files discovered before cap (for reporting). */
43
+ totalSeen: number;
44
+ }
45
+ /**
46
+ * Walk `rootPath` recursively under the given bounds and return absolute file
47
+ * paths matching the filters. Pure synchronous traversal — no allocations
48
+ * beyond the result array. Symlink cycles are detected via a resolved-path
49
+ * Set; symlink escapes (resolving outside rootPath) are silently skipped.
50
+ */
51
+ export declare function walkDirectory(rootPath: string, opts?: WalkOptions): string[];
52
+ /**
53
+ * Same as walkDirectory but returns capped + totalSeen so callers can surface
54
+ * a "capped at N files" notice in their response.
55
+ */
56
+ export declare function walkDirectoryDetailed(rootPath: string, opts?: WalkOptions): WalkResult;