@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,430 @@
1
+ /**
2
+ * Self-heal a missing better-sqlite3 native binding (#408).
3
+ *
4
+ * Single source of truth for the 3-layer heal used by both
5
+ * `scripts/postinstall.mjs` (install-time) and `hooks/ensure-deps.mjs`
6
+ * (runtime). Keeping one implementation avoids the duplicated logic the
7
+ * maintainer flagged on PR #410.
8
+ *
9
+ * Background:
10
+ * On Windows, `npm rebuild better-sqlite3` falls through to `node-gyp`
11
+ * when prebuild-install is not on cmd.exe PATH, then dies for users
12
+ * without Visual Studio C++ tooling. We bypass that by spawning
13
+ * prebuild-install JS directly with `process.execPath`.
14
+ *
15
+ * On macOS / Linux, when conda's `python3` is first on PATH (very
16
+ * common data-science setup), node-gyp picks it up via its `python3`
17
+ * PATH fallback and fails to build on Node 26 (arm64). We defend by
18
+ * pinning PYTHON + npm_config_python to a "safe" interpreter and
19
+ * stripping CONDA_* keys before shelling out to npm / node-gyp (#533).
20
+ *
21
+ * Layered heal:
22
+ * A. Spawn prebuild-install via process.execPath — bypasses PATH/MSVC.
23
+ * B. `npm install better-sqlite3` (re-resolves tree, NOT `npm rebuild`).
24
+ * C. Write actionable stderr message naming `npm install better-sqlite3`
25
+ * and the Windows / #408 context.
26
+ *
27
+ * Best-effort posture: every layer is wrapped in try/catch and the
28
+ * function never throws. Caller will fail naturally on first DB open if
29
+ * heal could not produce a working binding.
30
+ *
31
+ * @see https://github.com/mxalbert1996/context-mode/issues/408
32
+ * @see https://github.com/mxalbert1996/context-mode/issues/533
33
+ *
34
+ * Windows VS 2026+ detection:
35
+ * node-gyp has a hardcoded internal-version→year map. VS 2026 (internal
36
+ * major 18) was absent from older node-gyp builds, causing "unknown version"
37
+ * failures on machines that only have VS 2026 installed. Rather than
38
+ * extending the map (which would break again with VS 2029, etc.), we query
39
+ * vswhere's `displayName` property ("Visual Studio Community 2026") and
40
+ * extract the 4-digit year with a regex. `catalog_productLineVersion` is NOT
41
+ * used — it returns the internal major ("18") on VS 2026, not the year.
42
+ */
43
+
44
+ import { existsSync as fsExistsSync } from "node:fs";
45
+ import { execSync, execFileSync, spawnSync } from "node:child_process";
46
+ import { resolve } from "node:path";
47
+ import { createRequire } from "node:module";
48
+
49
+ /**
50
+ * Conda installation path prefixes that must NEVER be selected as the
51
+ * Python interpreter for node-gyp. Conda's Python ships environment
52
+ * activation hooks and a custom site-packages layout that breaks
53
+ * better-sqlite3's native build on Node 26 arm64 (#533).
54
+ */
55
+ const CONDA_PATH_PATTERNS = [
56
+ /^\/opt\/anaconda/i,
57
+ /^\/opt\/miniconda/i,
58
+ /\/miniforge\d*\//i,
59
+ /\/anaconda\d*\//i,
60
+ /\/miniconda\d*\//i,
61
+ /\/\.conda\//i,
62
+ /\/conda\//i,
63
+ ];
64
+
65
+ /**
66
+ * CONDA_* environment keys that must be stripped from the child env
67
+ * before spawning npm / node-gyp. Even after pinning PYTHON, leaving
68
+ * CONDA_PREFIX intact causes npm lifecycle scripts to re-activate
69
+ * conda's shims via .npmrc / shell rc files.
70
+ */
71
+ const CONDA_ENV_KEYS = [
72
+ "CONDA_PREFIX",
73
+ "CONDA_DEFAULT_ENV",
74
+ "CONDA_EXE",
75
+ "CONDA_PROMPT_MODIFIER",
76
+ "CONDA_SHLVL",
77
+ "CONDA_PYTHON_EXE",
78
+ ];
79
+
80
+ /**
81
+ * Decide whether a candidate python path is "safe" — i.e. not under
82
+ * any known conda installation prefix.
83
+ *
84
+ * @param {string} candidate - absolute path to a python interpreter
85
+ * @returns {boolean}
86
+ */
87
+ function isSafePythonPath(candidate) {
88
+ if (!candidate) return false;
89
+ return !CONDA_PATH_PATTERNS.some((rx) => rx.test(candidate));
90
+ }
91
+
92
+ /**
93
+ * Resolve a "safe" python interpreter that node-gyp can drive without
94
+ * conda activation noise. Pure / side-effect-free / dependency-injected
95
+ * so the unit test can exercise it on any host.
96
+ *
97
+ * Strategy:
98
+ * - darwin: prefer /usr/bin/python3 (Apple's system Python, ships
99
+ * with every macOS 10.15+ install). If absent, return null.
100
+ * - linux: scan PATH for the first python3 that is not under a
101
+ * conda prefix. If none, return null.
102
+ * - win32: not affected — node-gyp uses the py launcher, not PATH.
103
+ *
104
+ * @param {object} [deps]
105
+ * @param {string} [deps.platform] - process.platform override
106
+ * @param {NodeJS.ProcessEnv} [deps.env] - environment to inspect
107
+ * @param {(p: string) => boolean} [deps.existsSync] - fs probe override
108
+ * @returns {string | null}
109
+ */
110
+ export function resolveSafePython({
111
+ platform = process.platform,
112
+ env = process.env,
113
+ existsSync = fsExistsSync,
114
+ } = {}) {
115
+ if (platform === "darwin") {
116
+ // Apple-shipped Python is the safe choice — it is outside any
117
+ // conda prefix by definition and node-gyp builds against it
118
+ // cleanly on arm64.
119
+ return existsSync("/usr/bin/python3") ? "/usr/bin/python3" : null;
120
+ }
121
+ if (platform === "linux") {
122
+ const pathEntries = (env.PATH || "").split(":").filter(Boolean);
123
+ for (const dir of pathEntries) {
124
+ const candidate = `${dir}/python3`;
125
+ if (existsSync(candidate) && isSafePythonPath(candidate)) {
126
+ return candidate;
127
+ }
128
+ }
129
+ return null;
130
+ }
131
+ // Windows / other — no override needed.
132
+ return null;
133
+ }
134
+
135
+ /**
136
+ * Detect whether the current environment is conda-activated. Used to
137
+ * decide whether to emit the override breadcrumb (we don't spam stderr
138
+ * for users who never had conda interference).
139
+ *
140
+ * @param {NodeJS.ProcessEnv} [env]
141
+ * @returns {boolean}
142
+ */
143
+ function isCondaActive(env = process.env) {
144
+ if (env.CONDA_PREFIX || env.CONDA_DEFAULT_ENV) return true;
145
+ const pathEntries = (env.PATH || "").split(process.platform === "win32" ? ";" : ":");
146
+ return pathEntries.some((dir) => !isSafePythonPath(dir + "/python3"));
147
+ }
148
+
149
+ /**
150
+ * Detect the installed Visual Studio year string via vswhere.exe.
151
+ *
152
+ * Uses the `displayName` property (e.g. "Visual Studio Community 2026")
153
+ * and extracts the 4-digit year with a regex. This is more reliable than
154
+ * `catalog_productLineVersion`, which returns the internal major version
155
+ * number ("18") on VS 2026 instead of the year — making it useless as a
156
+ * direct msvs_version value without a mapping table.
157
+ *
158
+ * `displayName` has consistently included the branded year across every
159
+ * VS release (2017, 2019, 2022, 2026) and will continue to do so because
160
+ * it is the user-visible product name Microsoft ships.
161
+ *
162
+ * Dependency-injected so unit tests can exercise all branches without
163
+ * spawning a real process or requiring vswhere to be present on the host.
164
+ *
165
+ * Returns null on non-Windows, when vswhere is absent, or on any error.
166
+ *
167
+ * Timeout: 15s. Cold-disk vswhere queries on HDD-backed Windows CI runners
168
+ * with multiple VS installs have been observed to exceed the previous 5s
169
+ * budget (see ARCH-REVIEW #571 Part B). 15s comfortably covers slow-disk
170
+ * scenarios without freezing /ctx-upgrade.
171
+ *
172
+ * Year sanity cap: the regex matches any 21st-century 4-digit year, but
173
+ * we additionally reject anything > currentYear+5. Corrupted vswhere
174
+ * output or a future MS rebrand could surface a bogus "2099"; passing
175
+ * that through to `npm_config_msvs_version` would fail node-gyp
176
+ * silently. Cap-and-null lets the caller fall back to node-gyp's own
177
+ * detection and we log a single stderr breadcrumb for support triage.
178
+ *
179
+ * @param {object} [deps]
180
+ * @param {string} [deps.platform] - process.platform override
181
+ * @param {(p: string) => boolean} [deps.existsSync] - fs probe override
182
+ * @param {(cmd: string, opts: object) => string} [deps.exec] - execSync override
183
+ * @param {() => number} [deps.now] - clock override for sanity cap (test seam)
184
+ * @returns {string | null}
185
+ */
186
+ export function detectWindowsVsYear({
187
+ platform = process.platform,
188
+ existsSync = fsExistsSync,
189
+ exec = execSync,
190
+ now = () => new Date().getFullYear(),
191
+ } = {}) {
192
+ if (platform !== "win32") return null;
193
+ try {
194
+ const vswhere =
195
+ "C:\\Program Files (x86)\\Microsoft Visual Studio\\Installer\\vswhere.exe";
196
+ if (!existsSync(vswhere)) return null;
197
+ const displayName = exec(
198
+ `"${vswhere}" -latest -property displayName`,
199
+ { encoding: "utf-8", stdio: "pipe", timeout: 15000 },
200
+ ).trim();
201
+ // "Visual Studio Community 2026" → "2026"
202
+ const match = displayName.match(/\b(20\d{2})\b/);
203
+ if (!match) return null;
204
+ const year = Number(match[1]);
205
+ const ceiling = now() + 5;
206
+ if (year > ceiling) {
207
+ // Fail LOUD, not silent: poisoning npm_config_msvs_version with
208
+ // a bogus year would manifest as opaque node-gyp errors deep in
209
+ // the rebuild. Surface a breadcrumb and return null so the caller
210
+ // falls back to node-gyp's own version detection.
211
+ try {
212
+ process.stderr.write(
213
+ `[context-mode] vswhere displayName reports VS year ${year} ` +
214
+ `(> ${ceiling}); ignoring as likely corrupted output. ` +
215
+ `Falling back to node-gyp default detection.\n`,
216
+ );
217
+ } catch { /* stderr unavailable — proceed silently */ }
218
+ return null;
219
+ }
220
+ return match[1];
221
+ } catch {
222
+ return null;
223
+ }
224
+ }
225
+
226
+ /**
227
+ * Build the child-process env for an npm/node-gyp invocation. Pins
228
+ * PYTHON + npm_config_python to the resolved safe interpreter, strips
229
+ * CONDA_* keys, prepends /usr/bin to PATH on darwin so any downstream
230
+ * PATH-based python3 lookup resolves to system python, and on Windows
231
+ * injects npm_config_msvs_version from vswhere so node-gyp finds VS
232
+ * regardless of which major version is installed.
233
+ *
234
+ * @param {string | null} safePython - output of resolveSafePython()
235
+ * @param {NodeJS.ProcessEnv} [base] - starting env (defaults to process.env)
236
+ * @returns {NodeJS.ProcessEnv}
237
+ */
238
+ function buildSafeEnv(safePython, base = process.env) {
239
+ const env = { ...base };
240
+ if (safePython) {
241
+ // node-gyp reads env.PYTHON (see lib/find-python.js — second slot in
242
+ // its `checks` array, before any PATH-based fallback).
243
+ env.PYTHON = safePython;
244
+ // npm passes npm_config_python through to node-gyp as --python,
245
+ // which sits in the FIRST slot of node-gyp's `checks` array — even
246
+ // higher priority than env.PYTHON. Set both for belt-and-suspenders.
247
+ env.npm_config_python = safePython;
248
+ }
249
+ // Wipe every CONDA_* key so npm lifecycle scripts can't re-shim
250
+ // python3 via shell activation hooks.
251
+ for (const key of CONDA_ENV_KEYS) {
252
+ delete env[key];
253
+ }
254
+ // Prepend /usr/bin on darwin so any sub-script that does `python3`
255
+ // unqualified still resolves to /usr/bin/python3.
256
+ if (process.platform === "darwin" && env.PATH) {
257
+ const parts = env.PATH.split(":");
258
+ if (parts[0] !== "/usr/bin") {
259
+ env.PATH = "/usr/bin:" + parts.filter((p) => p !== "/usr/bin").join(":");
260
+ }
261
+ }
262
+ // ── Windows: pin npm_config_msvs_version via vswhere (#VS2026) ───────
263
+ // node-gyp defaults to VS 2022. On machines that only have VS 2026 (or
264
+ // a later release) installed the build fails with "unknown version" or
265
+ // "msvs_version does not match". Querying vswhere directly gives us the
266
+ // correct year string without a hardcoded mapping table.
267
+ if (process.platform === "win32" && !env.npm_config_msvs_version) {
268
+ const year = detectWindowsVsYear();
269
+ if (year) env.npm_config_msvs_version = year;
270
+ }
271
+ return env;
272
+ }
273
+
274
+ /**
275
+ * Self-heal a missing better_sqlite3.node binding.
276
+ *
277
+ * @param {string} pkgRoot - the directory containing node_modules/better-sqlite3
278
+ * @returns {{ healed: boolean, reason?: string }}
279
+ */
280
+ export function healBetterSqlite3Binding(pkgRoot) {
281
+ try {
282
+ const bsqRoot = resolve(pkgRoot, "node_modules", "better-sqlite3");
283
+ const bindingPath = resolve(bsqRoot, "build", "Release", "better_sqlite3.node");
284
+ const npmBin = process.platform === "win32" ? "npm.cmd" : "npm";
285
+
286
+ // ── Conda defense (#533) ─────────────────────────────────────────
287
+ // Resolve once up front; reuse across all child spawns. The probe
288
+ // is cheap (one stat call on darwin, a PATH walk on linux).
289
+ const safePython = resolveSafePython();
290
+ const condaActive = isCondaActive();
291
+ const childEnv = buildSafeEnv(safePython, process.env);
292
+
293
+ if (condaActive && safePython) {
294
+ // Emit a single breadcrumb so support requests are
295
+ // self-diagnosing. Best-effort: stderr may be unavailable in
296
+ // postinstall captured by npm logs.
297
+ try {
298
+ process.stderr.write(
299
+ `[context-mode] conda python detected on PATH — overriding with ` +
300
+ `PYTHON=${safePython} for better-sqlite3 build (#533).\n`,
301
+ );
302
+ } catch { /* stderr unavailable — proceed silently */ }
303
+ }
304
+
305
+ if (!fsExistsSync(bsqRoot)) {
306
+ // ── Package itself missing (#514) ───────────────────────────────
307
+ // npm@7+ silently drops optionalDependencies whose engines field
308
+ // does not match the running Node version (Node 26 vs
309
+ // better-sqlite3@12.x → silent skip, package never written).
310
+ // Even after promoting the package back to dependencies, an
311
+ // existing install where the package directory was previously
312
+ // skipped will still have an empty slot. Take ownership and
313
+ // install the package by name with --no-optional, which forces
314
+ // npm to install the named package even if it would otherwise
315
+ // be filtered out as an optional dep.
316
+ if (condaActive && !safePython) {
317
+ // Conda is active AND we couldn't find a system fallback. The
318
+ // install will almost certainly fail. Surface a distinct reason
319
+ // code so /ctx-upgrade can print conda-specific remediation.
320
+ return { healed: false, reason: "python-conda-blocked" };
321
+ }
322
+ try {
323
+ execFileSync(
324
+ npmBin,
325
+ [
326
+ "install",
327
+ "better-sqlite3",
328
+ "--no-optional",
329
+ "--no-save",
330
+ "--no-audit",
331
+ "--no-fund",
332
+ ],
333
+ {
334
+ cwd: pkgRoot,
335
+ stdio: "pipe",
336
+ timeout: 180000,
337
+ shell: process.platform === "win32",
338
+ env: childEnv,
339
+ },
340
+ );
341
+ } catch {
342
+ // Install failed — surface the cause via the manual-required
343
+ // exit so the caller (cli.ts upgrade verifier) reports it.
344
+ return { healed: false, reason: "package-missing" };
345
+ }
346
+ // Re-check after install. If npm wrote the package AND its
347
+ // postinstall produced the binding, we're done. Otherwise fall
348
+ // through into the binding-missing flow below.
349
+ if (fsExistsSync(bindingPath)) {
350
+ return { healed: true, reason: "package-installed" };
351
+ }
352
+ if (!fsExistsSync(bsqRoot)) {
353
+ // npm reported success but the directory is still absent.
354
+ // This indicates the engine-mismatch silent-skip is still in
355
+ // effect (e.g. npm < 7 or pnpm without --shamefully-hoist).
356
+ return { healed: false, reason: "package-missing" };
357
+ }
358
+ // Package present but binding still missing — recurse into
359
+ // the existing 3-layer heal that owns prebuild-install / npm
360
+ // install / actionable-stderr.
361
+ }
362
+
363
+ if (fsExistsSync(bindingPath)) {
364
+ return { healed: true, reason: "binding-present" };
365
+ }
366
+
367
+ // ── Layer A: spawn prebuild-install directly via process.execPath ──
368
+ // Bypasses cmd.exe PATH and MSVC requirement.
369
+ try {
370
+ let prebuildBin = null;
371
+ try {
372
+ const req = createRequire(resolve(bsqRoot, "package.json"));
373
+ prebuildBin = req.resolve("prebuild-install/bin");
374
+ } catch { /* fall through to manual walk */ }
375
+ if (!prebuildBin) {
376
+ const candidates = [
377
+ resolve(bsqRoot, "node_modules", "prebuild-install", "bin.js"),
378
+ resolve(pkgRoot, "node_modules", "prebuild-install", "bin.js"),
379
+ ];
380
+ for (const c of candidates) {
381
+ if (fsExistsSync(c)) { prebuildBin = c; break; }
382
+ }
383
+ }
384
+ if (prebuildBin) {
385
+ const r = spawnSync(
386
+ process.execPath,
387
+ [prebuildBin, "--target", process.versions.node, "--runtime", "node"],
388
+ { cwd: bsqRoot, stdio: "pipe", timeout: 120000, env: childEnv },
389
+ );
390
+ if (r.status === 0 && fsExistsSync(bindingPath)) {
391
+ return { healed: true, reason: "prebuild-install" };
392
+ }
393
+ }
394
+ } catch { /* best effort — try Layer B */ }
395
+
396
+ // ── Layer B: `npm install better-sqlite3` — NOT `npm rebuild` ──
397
+ // Re-resolves tree and re-runs prebuild-install via the package's
398
+ // own install script. Avoids the rebuild → node-gyp fall-through.
399
+ try {
400
+ execSync(
401
+ `${npmBin} install better-sqlite3 --no-package-lock --no-save --silent`,
402
+ { cwd: pkgRoot, stdio: "pipe", timeout: 120000, shell: true, env: childEnv },
403
+ );
404
+ if (fsExistsSync(bindingPath)) {
405
+ return { healed: true, reason: "npm-install" };
406
+ }
407
+ } catch { /* best effort — fall through to Layer C */ }
408
+
409
+ // ── Layer C: actionable stderr — give the user a real next step ──
410
+ try {
411
+ const condaHint = condaActive && !safePython
412
+ ? " Conda python detected on PATH and no system /usr/bin/python3 fallback found (#533).\n" +
413
+ " Deactivate conda (`conda deactivate`) or install system python3, then retry.\n"
414
+ : "";
415
+ process.stderr.write(
416
+ "\n[context-mode] better-sqlite3 native binding could not be installed automatically.\n" +
417
+ " This is a known issue on Windows when prebuild-install is not on PATH (#408).\n" +
418
+ condaHint +
419
+ " Workaround: run `npm install better-sqlite3` from the plugin directory.\n\n",
420
+ );
421
+ } catch { /* stderr unavailable — give up silently */ }
422
+ if (condaActive && !safePython) {
423
+ return { healed: false, reason: "python-conda-blocked" };
424
+ }
425
+ return { healed: false, reason: "manual-required" };
426
+ } catch {
427
+ // Outermost guard — never throw, never block the caller.
428
+ return { healed: false, reason: "manual-required" };
429
+ }
430
+ }