context-mode 1.0.169 → 2.0.0-next.5

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 (379) hide show
  1. package/.claude-plugin/marketplace.json +4 -22
  2. package/.claude-plugin/plugin.json +9 -24
  3. package/SHA256SUMS +31 -0
  4. package/claude-code/skills/ctx-doctor/SKILL.md +6 -0
  5. package/claude-code/skills/ctx-stats/SKILL.md +6 -0
  6. package/claude-code/skills/ctx-upgrade/SKILL.md +6 -0
  7. package/cli.mjs +6744 -0
  8. package/copilot/plugin.json +14 -0
  9. package/fixtures/hook-roundtrip.json +22 -0
  10. package/gemini-extension.json +14 -0
  11. package/hook.mjs +202 -0
  12. package/hooks/claude-code.json +50 -0
  13. package/hooks/codex.json +50 -0
  14. package/hooks/copilot-cli.json +33 -0
  15. package/hooks/hooks.json +16 -111
  16. package/hooks/kiro.json +69 -0
  17. package/hooks/qwen.json +50 -0
  18. package/hooks/vscode.json +40 -0
  19. package/hooks/zcode.json +53 -0
  20. package/opencode/plugin.js +22456 -0
  21. package/package.json +29 -114
  22. package/pi/index.js +50841 -0
  23. package/pi/skills/ctx-doctor/SKILL.md +6 -0
  24. package/pi/skills/ctx-stats/SKILL.md +6 -0
  25. package/pi/skills/ctx-upgrade/SKILL.md +6 -0
  26. package/qwen-extension.json +15 -0
  27. package/run.mjs +5633 -0
  28. package/search.mjs +5633 -0
  29. package/server.mjs +48198 -0
  30. package/skills/ctx-doctor/SKILL.md +3 -19
  31. package/skills/ctx-stats/SKILL.md +3 -23
  32. package/skills/ctx-upgrade/SKILL.md +3 -28
  33. package/.codex-plugin/hooks.json +0 -65
  34. package/.codex-plugin/mcp.json +0 -13
  35. package/.codex-plugin/plugin.json +0 -31
  36. package/.openclaw-plugin/index.ts +0 -12
  37. package/.openclaw-plugin/openclaw.plugin.json +0 -38
  38. package/.openclaw-plugin/package.json +0 -28
  39. package/LICENSE +0 -94
  40. package/README.md +0 -1619
  41. package/bin/statusline.mjs +0 -378
  42. package/build/adapters/antigravity/index.d.ts +0 -51
  43. package/build/adapters/antigravity/index.js +0 -169
  44. package/build/adapters/antigravity-cli/index.d.ts +0 -51
  45. package/build/adapters/antigravity-cli/index.js +0 -342
  46. package/build/adapters/base.d.ts +0 -92
  47. package/build/adapters/base.js +0 -135
  48. package/build/adapters/claude-code/config.d.ts +0 -8
  49. package/build/adapters/claude-code/config.js +0 -8
  50. package/build/adapters/claude-code/hooks.d.ts +0 -106
  51. package/build/adapters/claude-code/hooks.js +0 -183
  52. package/build/adapters/claude-code/index.d.ts +0 -80
  53. package/build/adapters/claude-code/index.js +0 -569
  54. package/build/adapters/claude-code-base.d.ts +0 -49
  55. package/build/adapters/claude-code-base.js +0 -113
  56. package/build/adapters/client-map.d.ts +0 -10
  57. package/build/adapters/client-map.js +0 -42
  58. package/build/adapters/codex/config.d.ts +0 -8
  59. package/build/adapters/codex/config.js +0 -8
  60. package/build/adapters/codex/hooks.d.ts +0 -55
  61. package/build/adapters/codex/hooks.js +0 -64
  62. package/build/adapters/codex/index.d.ts +0 -87
  63. package/build/adapters/codex/index.js +0 -992
  64. package/build/adapters/codex/paths.d.ts +0 -1
  65. package/build/adapters/codex/paths.js +0 -12
  66. package/build/adapters/codex/usage.d.ts +0 -107
  67. package/build/adapters/codex/usage.js +0 -227
  68. package/build/adapters/copilot-base.d.ts +0 -94
  69. package/build/adapters/copilot-base.js +0 -297
  70. package/build/adapters/copilot-cli/hooks.d.ts +0 -33
  71. package/build/adapters/copilot-cli/hooks.js +0 -64
  72. package/build/adapters/copilot-cli/index.d.ts +0 -48
  73. package/build/adapters/copilot-cli/index.js +0 -341
  74. package/build/adapters/cursor/config.d.ts +0 -4
  75. package/build/adapters/cursor/config.js +0 -4
  76. package/build/adapters/cursor/hooks.d.ts +0 -55
  77. package/build/adapters/cursor/hooks.js +0 -82
  78. package/build/adapters/cursor/index.d.ts +0 -64
  79. package/build/adapters/cursor/index.js +0 -517
  80. package/build/adapters/detect.d.ts +0 -136
  81. package/build/adapters/detect.js +0 -623
  82. package/build/adapters/gemini-cli/config.d.ts +0 -8
  83. package/build/adapters/gemini-cli/config.js +0 -8
  84. package/build/adapters/gemini-cli/hooks.d.ts +0 -83
  85. package/build/adapters/gemini-cli/hooks.js +0 -115
  86. package/build/adapters/gemini-cli/index.d.ts +0 -80
  87. package/build/adapters/gemini-cli/index.js +0 -483
  88. package/build/adapters/jetbrains-copilot/config.d.ts +0 -8
  89. package/build/adapters/jetbrains-copilot/config.js +0 -8
  90. package/build/adapters/jetbrains-copilot/hooks.d.ts +0 -59
  91. package/build/adapters/jetbrains-copilot/hooks.js +0 -87
  92. package/build/adapters/jetbrains-copilot/index.d.ts +0 -31
  93. package/build/adapters/jetbrains-copilot/index.js +0 -131
  94. package/build/adapters/kimi/config.d.ts +0 -8
  95. package/build/adapters/kimi/config.js +0 -8
  96. package/build/adapters/kimi/hooks.d.ts +0 -28
  97. package/build/adapters/kimi/hooks.js +0 -34
  98. package/build/adapters/kimi/index.d.ts +0 -66
  99. package/build/adapters/kimi/index.js +0 -537
  100. package/build/adapters/kimi/paths.d.ts +0 -21
  101. package/build/adapters/kimi/paths.js +0 -52
  102. package/build/adapters/kimi/usage.d.ts +0 -82
  103. package/build/adapters/kimi/usage.js +0 -217
  104. package/build/adapters/kiro/hooks.d.ts +0 -61
  105. package/build/adapters/kiro/hooks.js +0 -94
  106. package/build/adapters/kiro/index.d.ts +0 -61
  107. package/build/adapters/kiro/index.js +0 -301
  108. package/build/adapters/omp/index.d.ts +0 -65
  109. package/build/adapters/omp/index.js +0 -182
  110. package/build/adapters/omp/plugin.d.ts +0 -81
  111. package/build/adapters/omp/plugin.js +0 -331
  112. package/build/adapters/omp/usage.d.ts +0 -49
  113. package/build/adapters/omp/usage.js +0 -110
  114. package/build/adapters/openclaw/config.d.ts +0 -8
  115. package/build/adapters/openclaw/config.js +0 -8
  116. package/build/adapters/openclaw/hooks.d.ts +0 -50
  117. package/build/adapters/openclaw/hooks.js +0 -61
  118. package/build/adapters/openclaw/index.d.ts +0 -74
  119. package/build/adapters/openclaw/index.js +0 -439
  120. package/build/adapters/openclaw/mcp-tools.d.ts +0 -54
  121. package/build/adapters/openclaw/mcp-tools.js +0 -207
  122. package/build/adapters/openclaw/plugin.d.ts +0 -141
  123. package/build/adapters/openclaw/plugin.js +0 -818
  124. package/build/adapters/openclaw/session-db.d.ts +0 -55
  125. package/build/adapters/openclaw/session-db.js +0 -88
  126. package/build/adapters/openclaw/usage.d.ts +0 -34
  127. package/build/adapters/openclaw/usage.js +0 -52
  128. package/build/adapters/openclaw/workspace-router.d.ts +0 -29
  129. package/build/adapters/openclaw/workspace-router.js +0 -64
  130. package/build/adapters/opencode/config.d.ts +0 -8
  131. package/build/adapters/opencode/config.js +0 -8
  132. package/build/adapters/opencode/hooks.d.ts +0 -38
  133. package/build/adapters/opencode/hooks.js +0 -50
  134. package/build/adapters/opencode/index.d.ts +0 -67
  135. package/build/adapters/opencode/index.js +0 -463
  136. package/build/adapters/opencode/plugin.d.ts +0 -184
  137. package/build/adapters/opencode/plugin.js +0 -558
  138. package/build/adapters/opencode/zod3tov4.d.ts +0 -1
  139. package/build/adapters/opencode/zod3tov4.js +0 -132
  140. package/build/adapters/pi/extension.d.ts +0 -81
  141. package/build/adapters/pi/extension.js +0 -861
  142. package/build/adapters/pi/index.d.ts +0 -57
  143. package/build/adapters/pi/index.js +0 -173
  144. package/build/adapters/pi/mcp-bridge.d.ts +0 -268
  145. package/build/adapters/pi/mcp-bridge.js +0 -894
  146. package/build/adapters/qwen-code/hooks.d.ts +0 -26
  147. package/build/adapters/qwen-code/hooks.js +0 -29
  148. package/build/adapters/qwen-code/index.d.ts +0 -40
  149. package/build/adapters/qwen-code/index.js +0 -361
  150. package/build/adapters/qwen-code/usage.d.ts +0 -90
  151. package/build/adapters/qwen-code/usage.js +0 -222
  152. package/build/adapters/types.d.ts +0 -360
  153. package/build/adapters/types.js +0 -118
  154. package/build/adapters/vscode-copilot/config.d.ts +0 -8
  155. package/build/adapters/vscode-copilot/config.js +0 -8
  156. package/build/adapters/vscode-copilot/hooks.d.ts +0 -71
  157. package/build/adapters/vscode-copilot/hooks.js +0 -98
  158. package/build/adapters/vscode-copilot/index.d.ts +0 -32
  159. package/build/adapters/vscode-copilot/index.js +0 -222
  160. package/build/adapters/zed/index.d.ts +0 -40
  161. package/build/adapters/zed/index.js +0 -163
  162. package/build/cli.d.ts +0 -34
  163. package/build/cli.js +0 -1827
  164. package/build/db-base.d.ts +0 -179
  165. package/build/db-base.js +0 -569
  166. package/build/executor.d.ts +0 -72
  167. package/build/executor.js +0 -678
  168. package/build/exit-classify.d.ts +0 -19
  169. package/build/exit-classify.js +0 -12
  170. package/build/fetch-cache.d.ts +0 -13
  171. package/build/fetch-cache.js +0 -15
  172. package/build/lifecycle.d.ts +0 -124
  173. package/build/lifecycle.js +0 -278
  174. package/build/runPool.d.ts +0 -36
  175. package/build/runPool.js +0 -51
  176. package/build/runtime.d.ts +0 -71
  177. package/build/runtime.js +0 -659
  178. package/build/search/auto-memory.d.ts +0 -47
  179. package/build/search/auto-memory.js +0 -170
  180. package/build/search/ctx-search-schema.d.ts +0 -90
  181. package/build/search/ctx-search-schema.js +0 -135
  182. package/build/search/flood-guard.d.ts +0 -57
  183. package/build/search/flood-guard.js +0 -80
  184. package/build/search/unified.d.ts +0 -56
  185. package/build/search/unified.js +0 -104
  186. package/build/security.d.ts +0 -206
  187. package/build/security.js +0 -780
  188. package/build/server.d.ts +0 -184
  189. package/build/server.js +0 -4563
  190. package/build/session/analytics.d.ts +0 -773
  191. package/build/session/analytics.js +0 -2420
  192. package/build/session/db.d.ts +0 -468
  193. package/build/session/db.js +0 -1308
  194. package/build/session/error-classifier.d.ts +0 -87
  195. package/build/session/error-classifier.js +0 -303
  196. package/build/session/event-emit.d.ts +0 -48
  197. package/build/session/event-emit.js +0 -101
  198. package/build/session/extract.d.ts +0 -333
  199. package/build/session/extract.js +0 -2688
  200. package/build/session/model-prices.json +0 -429
  201. package/build/session/persist-tool-calls.d.ts +0 -54
  202. package/build/session/persist-tool-calls.js +0 -105
  203. package/build/session/pricing.d.ts +0 -64
  204. package/build/session/pricing.js +0 -151
  205. package/build/session/project-attribution.d.ts +0 -73
  206. package/build/session/project-attribution.js +0 -245
  207. package/build/session/purge.d.ts +0 -138
  208. package/build/session/purge.js +0 -240
  209. package/build/session/retrieval-marker.d.ts +0 -39
  210. package/build/session/retrieval-marker.js +0 -65
  211. package/build/session/snapshot.d.ts +0 -46
  212. package/build/session/snapshot.js +0 -532
  213. package/build/store-directory.d.ts +0 -56
  214. package/build/store-directory.js +0 -254
  215. package/build/store.d.ts +0 -147
  216. package/build/store.js +0 -1714
  217. package/build/truncate.d.ts +0 -57
  218. package/build/truncate.js +0 -142
  219. package/build/types.d.ts +0 -117
  220. package/build/types.js +0 -20
  221. package/build/util/claude-config.d.ts +0 -32
  222. package/build/util/claude-config.js +0 -84
  223. package/build/util/hook-config.d.ts +0 -27
  224. package/build/util/hook-config.js +0 -76
  225. package/build/util/jsonc.d.ts +0 -14
  226. package/build/util/jsonc.js +0 -104
  227. package/build/util/plugin-cache-integrity.d.ts +0 -51
  228. package/build/util/plugin-cache-integrity.js +0 -146
  229. package/build/util/project-dir.d.ts +0 -149
  230. package/build/util/project-dir.js +0 -340
  231. package/build/util/sibling-mcp.d.ts +0 -79
  232. package/build/util/sibling-mcp.js +0 -181
  233. package/cli.bundle.mjs +0 -1236
  234. package/configs/antigravity/GEMINI.md +0 -68
  235. package/configs/antigravity/mcp_config.json +0 -7
  236. package/configs/antigravity-cli/hooks/hooks.json +0 -37
  237. package/configs/antigravity-cli/hooks.json +0 -37
  238. package/configs/antigravity-cli/mcp_config.json +0 -10
  239. package/configs/antigravity-cli/plugin.json +0 -14
  240. package/configs/antigravity-cli/rules/context-mode.md +0 -77
  241. package/configs/antigravity-cli/skills/context-mode/SKILL.md +0 -77
  242. package/configs/claude-code/CLAUDE.md +0 -91
  243. package/configs/codex/AGENTS.md +0 -99
  244. package/configs/codex/config.toml +0 -8
  245. package/configs/codex/hooks.json +0 -47
  246. package/configs/copilot-cli/.github/plugin/plugin.json +0 -23
  247. package/configs/copilot-cli/.mcp.json +0 -12
  248. package/configs/copilot-cli/README.md +0 -47
  249. package/configs/copilot-cli/hooks.json +0 -41
  250. package/configs/copilot-cli/skills/context-mode/SKILL.md +0 -38
  251. package/configs/cursor/context-mode.mdc +0 -76
  252. package/configs/cursor/hooks.json +0 -21
  253. package/configs/cursor/mcp.json +0 -7
  254. package/configs/gemini-cli/GEMINI.md +0 -88
  255. package/configs/gemini-cli/mcp.json +0 -7
  256. package/configs/gemini-cli/settings.json +0 -60
  257. package/configs/jetbrains-copilot/copilot-instructions.md +0 -80
  258. package/configs/jetbrains-copilot/hooks.json +0 -16
  259. package/configs/jetbrains-copilot/mcp.json +0 -7
  260. package/configs/kilo/AGENTS.md +0 -84
  261. package/configs/kilo/kilo.json +0 -6
  262. package/configs/kimi/hooks.json +0 -54
  263. package/configs/kiro/KIRO.md +0 -84
  264. package/configs/kiro/agent.json +0 -18
  265. package/configs/kiro/mcp.json +0 -7
  266. package/configs/omp/SYSTEM.md +0 -85
  267. package/configs/omp/mcp.json +0 -7
  268. package/configs/openclaw/AGENTS.md +0 -87
  269. package/configs/openclaw/openclaw.json +0 -13
  270. package/configs/opencode/AGENTS.md +0 -84
  271. package/configs/opencode/opencode.json +0 -6
  272. package/configs/pi/AGENTS.md +0 -3
  273. package/configs/qwen-code/QWEN.md +0 -91
  274. package/configs/vscode-copilot/copilot-instructions.md +0 -80
  275. package/configs/vscode-copilot/hooks.json +0 -16
  276. package/configs/vscode-copilot/mcp.json +0 -7
  277. package/configs/zed/AGENTS.md +0 -68
  278. package/hooks/antigravity-cli/payload.mjs +0 -98
  279. package/hooks/antigravity-cli/posttooluse.mjs +0 -138
  280. package/hooks/antigravity-cli/pretooluse.mjs +0 -78
  281. package/hooks/antigravity-cli/stop.mjs +0 -58
  282. package/hooks/auto-injection.mjs +0 -102
  283. package/hooks/cache-heal-utils.mjs +0 -379
  284. package/hooks/codex/platform.mjs +0 -1
  285. package/hooks/codex/posttooluse.mjs +0 -66
  286. package/hooks/codex/precompact.mjs +0 -70
  287. package/hooks/codex/pretooluse.mjs +0 -42
  288. package/hooks/codex/sessionstart.mjs +0 -121
  289. package/hooks/codex/stop.mjs +0 -138
  290. package/hooks/codex/userpromptsubmit.mjs +0 -75
  291. package/hooks/copilot-cli/posttooluse.mjs +0 -79
  292. package/hooks/copilot-cli/precompact.mjs +0 -66
  293. package/hooks/copilot-cli/pretooluse.mjs +0 -41
  294. package/hooks/copilot-cli/sessionstart.mjs +0 -121
  295. package/hooks/copilot-cli/stop.mjs +0 -59
  296. package/hooks/copilot-cli/userpromptsubmit.mjs +0 -77
  297. package/hooks/core/codex-caps.mjs +0 -112
  298. package/hooks/core/formatters.mjs +0 -356
  299. package/hooks/core/mcp-ready.mjs +0 -109
  300. package/hooks/core/platform-detect.mjs +0 -49
  301. package/hooks/core/routing.mjs +0 -1017
  302. package/hooks/core/stdin.mjs +0 -90
  303. package/hooks/core/tool-naming.mjs +0 -57
  304. package/hooks/cursor/afteragentresponse.mjs +0 -74
  305. package/hooks/cursor/hooks.json +0 -31
  306. package/hooks/cursor/posttooluse.mjs +0 -72
  307. package/hooks/cursor/pretooluse.mjs +0 -26
  308. package/hooks/cursor/sessionstart.mjs +0 -97
  309. package/hooks/cursor/stop.mjs +0 -48
  310. package/hooks/ensure-deps.mjs +0 -254
  311. package/hooks/formatters/claude-code.mjs +0 -113
  312. package/hooks/formatters/cursor.mjs +0 -37
  313. package/hooks/formatters/gemini-cli.mjs +0 -55
  314. package/hooks/formatters/vscode-copilot.mjs +0 -55
  315. package/hooks/gemini-cli/aftermodel.mjs +0 -70
  316. package/hooks/gemini-cli/aftertool.mjs +0 -61
  317. package/hooks/gemini-cli/beforeagent.mjs +0 -99
  318. package/hooks/gemini-cli/beforetool.mjs +0 -27
  319. package/hooks/gemini-cli/precompress.mjs +0 -55
  320. package/hooks/gemini-cli/sessionstart.mjs +0 -137
  321. package/hooks/heal-partial-install.mjs +0 -712
  322. package/hooks/jetbrains-copilot/posttooluse.mjs +0 -61
  323. package/hooks/jetbrains-copilot/precompact.mjs +0 -55
  324. package/hooks/jetbrains-copilot/pretooluse.mjs +0 -27
  325. package/hooks/jetbrains-copilot/sessionstart.mjs +0 -124
  326. package/hooks/kimi/platform.mjs +0 -1
  327. package/hooks/kimi/posttooluse.mjs +0 -72
  328. package/hooks/kimi/precompact.mjs +0 -80
  329. package/hooks/kimi/pretooluse.mjs +0 -42
  330. package/hooks/kimi/sessionend.mjs +0 -61
  331. package/hooks/kimi/sessionstart.mjs +0 -113
  332. package/hooks/kimi/stop.mjs +0 -132
  333. package/hooks/kimi/userpromptsubmit.mjs +0 -90
  334. package/hooks/kiro/agentspawn.mjs +0 -97
  335. package/hooks/kiro/posttooluse.mjs +0 -51
  336. package/hooks/kiro/pretooluse.mjs +0 -64
  337. package/hooks/kiro/userpromptsubmit.mjs +0 -88
  338. package/hooks/normalize-hooks.mjs +0 -323
  339. package/hooks/platform-bridge.mjs +0 -340
  340. package/hooks/posttooluse.mjs +0 -225
  341. package/hooks/precompact.mjs +0 -100
  342. package/hooks/pretooluse.mjs +0 -227
  343. package/hooks/qwen-code/platform.mjs +0 -1
  344. package/hooks/qwen-code/stop.mjs +0 -168
  345. package/hooks/routing-block.mjs +0 -105
  346. package/hooks/run-hook.mjs +0 -95
  347. package/hooks/security.bundle.mjs +0 -2
  348. package/hooks/session-attribution.bundle.mjs +0 -1
  349. package/hooks/session-db.bundle.mjs +0 -144
  350. package/hooks/session-directive.mjs +0 -520
  351. package/hooks/session-extract.bundle.mjs +0 -3
  352. package/hooks/session-helpers.mjs +0 -428
  353. package/hooks/session-loaders.mjs +0 -448
  354. package/hooks/session-snapshot.bundle.mjs +0 -32
  355. package/hooks/sessionstart.mjs +0 -464
  356. package/hooks/stop.mjs +0 -82
  357. package/hooks/suppress-stderr.mjs +0 -25
  358. package/hooks/userpromptsubmit.mjs +0 -108
  359. package/hooks/vscode-copilot/posttooluse.mjs +0 -61
  360. package/hooks/vscode-copilot/precompact.mjs +0 -55
  361. package/hooks/vscode-copilot/pretooluse.mjs +0 -27
  362. package/hooks/vscode-copilot/sessionstart.mjs +0 -129
  363. package/openclaw.plugin.json +0 -38
  364. package/scripts/heal-better-sqlite3.mjs +0 -430
  365. package/scripts/heal-installed-plugins.mjs +0 -598
  366. package/scripts/plugin-cache-integrity.mjs +0 -248
  367. package/scripts/postinstall.mjs +0 -384
  368. package/server.bundle.mjs +0 -1162
  369. package/skills/.ignore +0 -7
  370. package/skills/context-mode/SKILL.md +0 -300
  371. package/skills/context-mode/references/anti-patterns.md +0 -283
  372. package/skills/context-mode/references/patterns-javascript.md +0 -298
  373. package/skills/context-mode/references/patterns-python.md +0 -304
  374. package/skills/context-mode/references/patterns-shell.md +0 -277
  375. package/skills/ctx-index/SKILL.md +0 -46
  376. package/skills/ctx-insight/SKILL.md +0 -24
  377. package/skills/ctx-purge/SKILL.md +0 -49
  378. package/skills/ctx-search/SKILL.md +0 -35
  379. package/start.mjs +0 -616
@@ -1,430 +0,0 @@
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/mksglu/context-mode/issues/408
32
- * @see https://github.com/mksglu/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
- }