context-mode 1.0.169 → 2.0.0-next.6

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 (382) hide show
  1. package/.claude-plugin/marketplace.json +4 -22
  2. package/.claude-plugin/plugin.json +9 -24
  3. package/.codex-plugin/hooks.json +18 -33
  4. package/.codex-plugin/mcp.json +5 -4
  5. package/.codex-plugin/plugin.json +8 -27
  6. package/SHA256SUMS +37 -0
  7. package/claude-code/skills/ctx-doctor/SKILL.md +6 -0
  8. package/claude-code/skills/ctx-stats/SKILL.md +6 -0
  9. package/claude-code/skills/ctx-upgrade/SKILL.md +6 -0
  10. package/cli.mjs +6859 -0
  11. package/codex/skills/ctx-doctor/SKILL.md +6 -0
  12. package/codex/skills/ctx-stats/SKILL.md +6 -0
  13. package/codex/skills/ctx-upgrade/SKILL.md +6 -0
  14. package/copilot/plugin.json +14 -0
  15. package/fixtures/hook-roundtrip.json +22 -0
  16. package/gemini-extension.json +14 -0
  17. package/hook.mjs +202 -0
  18. package/hooks/claude-code.json +50 -0
  19. package/hooks/codex.json +50 -0
  20. package/hooks/copilot-cli.json +33 -0
  21. package/hooks/hooks.json +16 -111
  22. package/hooks/kiro.json +69 -0
  23. package/hooks/qwen.json +50 -0
  24. package/hooks/vscode.json +40 -0
  25. package/hooks/zcode.json +53 -0
  26. package/opencode/plugin.js +22491 -0
  27. package/package.json +32 -114
  28. package/pi/index.js +50876 -0
  29. package/pi/skills/ctx-doctor/SKILL.md +6 -0
  30. package/pi/skills/ctx-stats/SKILL.md +6 -0
  31. package/pi/skills/ctx-upgrade/SKILL.md +6 -0
  32. package/qwen-extension.json +15 -0
  33. package/run.mjs +5639 -0
  34. package/search.mjs +5639 -0
  35. package/server.mjs +48214 -0
  36. package/skills/ctx-doctor/SKILL.md +3 -19
  37. package/skills/ctx-stats/SKILL.md +3 -23
  38. package/skills/ctx-upgrade/SKILL.md +3 -28
  39. package/.openclaw-plugin/index.ts +0 -12
  40. package/.openclaw-plugin/openclaw.plugin.json +0 -38
  41. package/.openclaw-plugin/package.json +0 -28
  42. package/LICENSE +0 -94
  43. package/README.md +0 -1619
  44. package/bin/statusline.mjs +0 -378
  45. package/build/adapters/antigravity/index.d.ts +0 -51
  46. package/build/adapters/antigravity/index.js +0 -169
  47. package/build/adapters/antigravity-cli/index.d.ts +0 -51
  48. package/build/adapters/antigravity-cli/index.js +0 -342
  49. package/build/adapters/base.d.ts +0 -92
  50. package/build/adapters/base.js +0 -135
  51. package/build/adapters/claude-code/config.d.ts +0 -8
  52. package/build/adapters/claude-code/config.js +0 -8
  53. package/build/adapters/claude-code/hooks.d.ts +0 -106
  54. package/build/adapters/claude-code/hooks.js +0 -183
  55. package/build/adapters/claude-code/index.d.ts +0 -80
  56. package/build/adapters/claude-code/index.js +0 -569
  57. package/build/adapters/claude-code-base.d.ts +0 -49
  58. package/build/adapters/claude-code-base.js +0 -113
  59. package/build/adapters/client-map.d.ts +0 -10
  60. package/build/adapters/client-map.js +0 -42
  61. package/build/adapters/codex/config.d.ts +0 -8
  62. package/build/adapters/codex/config.js +0 -8
  63. package/build/adapters/codex/hooks.d.ts +0 -55
  64. package/build/adapters/codex/hooks.js +0 -64
  65. package/build/adapters/codex/index.d.ts +0 -87
  66. package/build/adapters/codex/index.js +0 -992
  67. package/build/adapters/codex/paths.d.ts +0 -1
  68. package/build/adapters/codex/paths.js +0 -12
  69. package/build/adapters/codex/usage.d.ts +0 -107
  70. package/build/adapters/codex/usage.js +0 -227
  71. package/build/adapters/copilot-base.d.ts +0 -94
  72. package/build/adapters/copilot-base.js +0 -297
  73. package/build/adapters/copilot-cli/hooks.d.ts +0 -33
  74. package/build/adapters/copilot-cli/hooks.js +0 -64
  75. package/build/adapters/copilot-cli/index.d.ts +0 -48
  76. package/build/adapters/copilot-cli/index.js +0 -341
  77. package/build/adapters/cursor/config.d.ts +0 -4
  78. package/build/adapters/cursor/config.js +0 -4
  79. package/build/adapters/cursor/hooks.d.ts +0 -55
  80. package/build/adapters/cursor/hooks.js +0 -82
  81. package/build/adapters/cursor/index.d.ts +0 -64
  82. package/build/adapters/cursor/index.js +0 -517
  83. package/build/adapters/detect.d.ts +0 -136
  84. package/build/adapters/detect.js +0 -623
  85. package/build/adapters/gemini-cli/config.d.ts +0 -8
  86. package/build/adapters/gemini-cli/config.js +0 -8
  87. package/build/adapters/gemini-cli/hooks.d.ts +0 -83
  88. package/build/adapters/gemini-cli/hooks.js +0 -115
  89. package/build/adapters/gemini-cli/index.d.ts +0 -80
  90. package/build/adapters/gemini-cli/index.js +0 -483
  91. package/build/adapters/jetbrains-copilot/config.d.ts +0 -8
  92. package/build/adapters/jetbrains-copilot/config.js +0 -8
  93. package/build/adapters/jetbrains-copilot/hooks.d.ts +0 -59
  94. package/build/adapters/jetbrains-copilot/hooks.js +0 -87
  95. package/build/adapters/jetbrains-copilot/index.d.ts +0 -31
  96. package/build/adapters/jetbrains-copilot/index.js +0 -131
  97. package/build/adapters/kimi/config.d.ts +0 -8
  98. package/build/adapters/kimi/config.js +0 -8
  99. package/build/adapters/kimi/hooks.d.ts +0 -28
  100. package/build/adapters/kimi/hooks.js +0 -34
  101. package/build/adapters/kimi/index.d.ts +0 -66
  102. package/build/adapters/kimi/index.js +0 -537
  103. package/build/adapters/kimi/paths.d.ts +0 -21
  104. package/build/adapters/kimi/paths.js +0 -52
  105. package/build/adapters/kimi/usage.d.ts +0 -82
  106. package/build/adapters/kimi/usage.js +0 -217
  107. package/build/adapters/kiro/hooks.d.ts +0 -61
  108. package/build/adapters/kiro/hooks.js +0 -94
  109. package/build/adapters/kiro/index.d.ts +0 -61
  110. package/build/adapters/kiro/index.js +0 -301
  111. package/build/adapters/omp/index.d.ts +0 -65
  112. package/build/adapters/omp/index.js +0 -182
  113. package/build/adapters/omp/plugin.d.ts +0 -81
  114. package/build/adapters/omp/plugin.js +0 -331
  115. package/build/adapters/omp/usage.d.ts +0 -49
  116. package/build/adapters/omp/usage.js +0 -110
  117. package/build/adapters/openclaw/config.d.ts +0 -8
  118. package/build/adapters/openclaw/config.js +0 -8
  119. package/build/adapters/openclaw/hooks.d.ts +0 -50
  120. package/build/adapters/openclaw/hooks.js +0 -61
  121. package/build/adapters/openclaw/index.d.ts +0 -74
  122. package/build/adapters/openclaw/index.js +0 -439
  123. package/build/adapters/openclaw/mcp-tools.d.ts +0 -54
  124. package/build/adapters/openclaw/mcp-tools.js +0 -207
  125. package/build/adapters/openclaw/plugin.d.ts +0 -141
  126. package/build/adapters/openclaw/plugin.js +0 -818
  127. package/build/adapters/openclaw/session-db.d.ts +0 -55
  128. package/build/adapters/openclaw/session-db.js +0 -88
  129. package/build/adapters/openclaw/usage.d.ts +0 -34
  130. package/build/adapters/openclaw/usage.js +0 -52
  131. package/build/adapters/openclaw/workspace-router.d.ts +0 -29
  132. package/build/adapters/openclaw/workspace-router.js +0 -64
  133. package/build/adapters/opencode/config.d.ts +0 -8
  134. package/build/adapters/opencode/config.js +0 -8
  135. package/build/adapters/opencode/hooks.d.ts +0 -38
  136. package/build/adapters/opencode/hooks.js +0 -50
  137. package/build/adapters/opencode/index.d.ts +0 -67
  138. package/build/adapters/opencode/index.js +0 -463
  139. package/build/adapters/opencode/plugin.d.ts +0 -184
  140. package/build/adapters/opencode/plugin.js +0 -558
  141. package/build/adapters/opencode/zod3tov4.d.ts +0 -1
  142. package/build/adapters/opencode/zod3tov4.js +0 -132
  143. package/build/adapters/pi/extension.d.ts +0 -81
  144. package/build/adapters/pi/extension.js +0 -861
  145. package/build/adapters/pi/index.d.ts +0 -57
  146. package/build/adapters/pi/index.js +0 -173
  147. package/build/adapters/pi/mcp-bridge.d.ts +0 -268
  148. package/build/adapters/pi/mcp-bridge.js +0 -894
  149. package/build/adapters/qwen-code/hooks.d.ts +0 -26
  150. package/build/adapters/qwen-code/hooks.js +0 -29
  151. package/build/adapters/qwen-code/index.d.ts +0 -40
  152. package/build/adapters/qwen-code/index.js +0 -361
  153. package/build/adapters/qwen-code/usage.d.ts +0 -90
  154. package/build/adapters/qwen-code/usage.js +0 -222
  155. package/build/adapters/types.d.ts +0 -360
  156. package/build/adapters/types.js +0 -118
  157. package/build/adapters/vscode-copilot/config.d.ts +0 -8
  158. package/build/adapters/vscode-copilot/config.js +0 -8
  159. package/build/adapters/vscode-copilot/hooks.d.ts +0 -71
  160. package/build/adapters/vscode-copilot/hooks.js +0 -98
  161. package/build/adapters/vscode-copilot/index.d.ts +0 -32
  162. package/build/adapters/vscode-copilot/index.js +0 -222
  163. package/build/adapters/zed/index.d.ts +0 -40
  164. package/build/adapters/zed/index.js +0 -163
  165. package/build/cli.d.ts +0 -34
  166. package/build/cli.js +0 -1827
  167. package/build/db-base.d.ts +0 -179
  168. package/build/db-base.js +0 -569
  169. package/build/executor.d.ts +0 -72
  170. package/build/executor.js +0 -678
  171. package/build/exit-classify.d.ts +0 -19
  172. package/build/exit-classify.js +0 -12
  173. package/build/fetch-cache.d.ts +0 -13
  174. package/build/fetch-cache.js +0 -15
  175. package/build/lifecycle.d.ts +0 -124
  176. package/build/lifecycle.js +0 -278
  177. package/build/runPool.d.ts +0 -36
  178. package/build/runPool.js +0 -51
  179. package/build/runtime.d.ts +0 -71
  180. package/build/runtime.js +0 -659
  181. package/build/search/auto-memory.d.ts +0 -47
  182. package/build/search/auto-memory.js +0 -170
  183. package/build/search/ctx-search-schema.d.ts +0 -90
  184. package/build/search/ctx-search-schema.js +0 -135
  185. package/build/search/flood-guard.d.ts +0 -57
  186. package/build/search/flood-guard.js +0 -80
  187. package/build/search/unified.d.ts +0 -56
  188. package/build/search/unified.js +0 -104
  189. package/build/security.d.ts +0 -206
  190. package/build/security.js +0 -780
  191. package/build/server.d.ts +0 -184
  192. package/build/server.js +0 -4563
  193. package/build/session/analytics.d.ts +0 -773
  194. package/build/session/analytics.js +0 -2420
  195. package/build/session/db.d.ts +0 -468
  196. package/build/session/db.js +0 -1308
  197. package/build/session/error-classifier.d.ts +0 -87
  198. package/build/session/error-classifier.js +0 -303
  199. package/build/session/event-emit.d.ts +0 -48
  200. package/build/session/event-emit.js +0 -101
  201. package/build/session/extract.d.ts +0 -333
  202. package/build/session/extract.js +0 -2688
  203. package/build/session/model-prices.json +0 -429
  204. package/build/session/persist-tool-calls.d.ts +0 -54
  205. package/build/session/persist-tool-calls.js +0 -105
  206. package/build/session/pricing.d.ts +0 -64
  207. package/build/session/pricing.js +0 -151
  208. package/build/session/project-attribution.d.ts +0 -73
  209. package/build/session/project-attribution.js +0 -245
  210. package/build/session/purge.d.ts +0 -138
  211. package/build/session/purge.js +0 -240
  212. package/build/session/retrieval-marker.d.ts +0 -39
  213. package/build/session/retrieval-marker.js +0 -65
  214. package/build/session/snapshot.d.ts +0 -46
  215. package/build/session/snapshot.js +0 -532
  216. package/build/store-directory.d.ts +0 -56
  217. package/build/store-directory.js +0 -254
  218. package/build/store.d.ts +0 -147
  219. package/build/store.js +0 -1714
  220. package/build/truncate.d.ts +0 -57
  221. package/build/truncate.js +0 -142
  222. package/build/types.d.ts +0 -117
  223. package/build/types.js +0 -20
  224. package/build/util/claude-config.d.ts +0 -32
  225. package/build/util/claude-config.js +0 -84
  226. package/build/util/hook-config.d.ts +0 -27
  227. package/build/util/hook-config.js +0 -76
  228. package/build/util/jsonc.d.ts +0 -14
  229. package/build/util/jsonc.js +0 -104
  230. package/build/util/plugin-cache-integrity.d.ts +0 -51
  231. package/build/util/plugin-cache-integrity.js +0 -146
  232. package/build/util/project-dir.d.ts +0 -149
  233. package/build/util/project-dir.js +0 -340
  234. package/build/util/sibling-mcp.d.ts +0 -79
  235. package/build/util/sibling-mcp.js +0 -181
  236. package/cli.bundle.mjs +0 -1236
  237. package/configs/antigravity/GEMINI.md +0 -68
  238. package/configs/antigravity/mcp_config.json +0 -7
  239. package/configs/antigravity-cli/hooks/hooks.json +0 -37
  240. package/configs/antigravity-cli/hooks.json +0 -37
  241. package/configs/antigravity-cli/mcp_config.json +0 -10
  242. package/configs/antigravity-cli/plugin.json +0 -14
  243. package/configs/antigravity-cli/rules/context-mode.md +0 -77
  244. package/configs/antigravity-cli/skills/context-mode/SKILL.md +0 -77
  245. package/configs/claude-code/CLAUDE.md +0 -91
  246. package/configs/codex/AGENTS.md +0 -99
  247. package/configs/codex/config.toml +0 -8
  248. package/configs/codex/hooks.json +0 -47
  249. package/configs/copilot-cli/.github/plugin/plugin.json +0 -23
  250. package/configs/copilot-cli/.mcp.json +0 -12
  251. package/configs/copilot-cli/README.md +0 -47
  252. package/configs/copilot-cli/hooks.json +0 -41
  253. package/configs/copilot-cli/skills/context-mode/SKILL.md +0 -38
  254. package/configs/cursor/context-mode.mdc +0 -76
  255. package/configs/cursor/hooks.json +0 -21
  256. package/configs/cursor/mcp.json +0 -7
  257. package/configs/gemini-cli/GEMINI.md +0 -88
  258. package/configs/gemini-cli/mcp.json +0 -7
  259. package/configs/gemini-cli/settings.json +0 -60
  260. package/configs/jetbrains-copilot/copilot-instructions.md +0 -80
  261. package/configs/jetbrains-copilot/hooks.json +0 -16
  262. package/configs/jetbrains-copilot/mcp.json +0 -7
  263. package/configs/kilo/AGENTS.md +0 -84
  264. package/configs/kilo/kilo.json +0 -6
  265. package/configs/kimi/hooks.json +0 -54
  266. package/configs/kiro/KIRO.md +0 -84
  267. package/configs/kiro/agent.json +0 -18
  268. package/configs/kiro/mcp.json +0 -7
  269. package/configs/omp/SYSTEM.md +0 -85
  270. package/configs/omp/mcp.json +0 -7
  271. package/configs/openclaw/AGENTS.md +0 -87
  272. package/configs/openclaw/openclaw.json +0 -13
  273. package/configs/opencode/AGENTS.md +0 -84
  274. package/configs/opencode/opencode.json +0 -6
  275. package/configs/pi/AGENTS.md +0 -3
  276. package/configs/qwen-code/QWEN.md +0 -91
  277. package/configs/vscode-copilot/copilot-instructions.md +0 -80
  278. package/configs/vscode-copilot/hooks.json +0 -16
  279. package/configs/vscode-copilot/mcp.json +0 -7
  280. package/configs/zed/AGENTS.md +0 -68
  281. package/hooks/antigravity-cli/payload.mjs +0 -98
  282. package/hooks/antigravity-cli/posttooluse.mjs +0 -138
  283. package/hooks/antigravity-cli/pretooluse.mjs +0 -78
  284. package/hooks/antigravity-cli/stop.mjs +0 -58
  285. package/hooks/auto-injection.mjs +0 -102
  286. package/hooks/cache-heal-utils.mjs +0 -379
  287. package/hooks/codex/platform.mjs +0 -1
  288. package/hooks/codex/posttooluse.mjs +0 -66
  289. package/hooks/codex/precompact.mjs +0 -70
  290. package/hooks/codex/pretooluse.mjs +0 -42
  291. package/hooks/codex/sessionstart.mjs +0 -121
  292. package/hooks/codex/stop.mjs +0 -138
  293. package/hooks/codex/userpromptsubmit.mjs +0 -75
  294. package/hooks/copilot-cli/posttooluse.mjs +0 -79
  295. package/hooks/copilot-cli/precompact.mjs +0 -66
  296. package/hooks/copilot-cli/pretooluse.mjs +0 -41
  297. package/hooks/copilot-cli/sessionstart.mjs +0 -121
  298. package/hooks/copilot-cli/stop.mjs +0 -59
  299. package/hooks/copilot-cli/userpromptsubmit.mjs +0 -77
  300. package/hooks/core/codex-caps.mjs +0 -112
  301. package/hooks/core/formatters.mjs +0 -356
  302. package/hooks/core/mcp-ready.mjs +0 -109
  303. package/hooks/core/platform-detect.mjs +0 -49
  304. package/hooks/core/routing.mjs +0 -1017
  305. package/hooks/core/stdin.mjs +0 -90
  306. package/hooks/core/tool-naming.mjs +0 -57
  307. package/hooks/cursor/afteragentresponse.mjs +0 -74
  308. package/hooks/cursor/hooks.json +0 -31
  309. package/hooks/cursor/posttooluse.mjs +0 -72
  310. package/hooks/cursor/pretooluse.mjs +0 -26
  311. package/hooks/cursor/sessionstart.mjs +0 -97
  312. package/hooks/cursor/stop.mjs +0 -48
  313. package/hooks/ensure-deps.mjs +0 -254
  314. package/hooks/formatters/claude-code.mjs +0 -113
  315. package/hooks/formatters/cursor.mjs +0 -37
  316. package/hooks/formatters/gemini-cli.mjs +0 -55
  317. package/hooks/formatters/vscode-copilot.mjs +0 -55
  318. package/hooks/gemini-cli/aftermodel.mjs +0 -70
  319. package/hooks/gemini-cli/aftertool.mjs +0 -61
  320. package/hooks/gemini-cli/beforeagent.mjs +0 -99
  321. package/hooks/gemini-cli/beforetool.mjs +0 -27
  322. package/hooks/gemini-cli/precompress.mjs +0 -55
  323. package/hooks/gemini-cli/sessionstart.mjs +0 -137
  324. package/hooks/heal-partial-install.mjs +0 -712
  325. package/hooks/jetbrains-copilot/posttooluse.mjs +0 -61
  326. package/hooks/jetbrains-copilot/precompact.mjs +0 -55
  327. package/hooks/jetbrains-copilot/pretooluse.mjs +0 -27
  328. package/hooks/jetbrains-copilot/sessionstart.mjs +0 -124
  329. package/hooks/kimi/platform.mjs +0 -1
  330. package/hooks/kimi/posttooluse.mjs +0 -72
  331. package/hooks/kimi/precompact.mjs +0 -80
  332. package/hooks/kimi/pretooluse.mjs +0 -42
  333. package/hooks/kimi/sessionend.mjs +0 -61
  334. package/hooks/kimi/sessionstart.mjs +0 -113
  335. package/hooks/kimi/stop.mjs +0 -132
  336. package/hooks/kimi/userpromptsubmit.mjs +0 -90
  337. package/hooks/kiro/agentspawn.mjs +0 -97
  338. package/hooks/kiro/posttooluse.mjs +0 -51
  339. package/hooks/kiro/pretooluse.mjs +0 -64
  340. package/hooks/kiro/userpromptsubmit.mjs +0 -88
  341. package/hooks/normalize-hooks.mjs +0 -323
  342. package/hooks/platform-bridge.mjs +0 -340
  343. package/hooks/posttooluse.mjs +0 -225
  344. package/hooks/precompact.mjs +0 -100
  345. package/hooks/pretooluse.mjs +0 -227
  346. package/hooks/qwen-code/platform.mjs +0 -1
  347. package/hooks/qwen-code/stop.mjs +0 -168
  348. package/hooks/routing-block.mjs +0 -105
  349. package/hooks/run-hook.mjs +0 -95
  350. package/hooks/security.bundle.mjs +0 -2
  351. package/hooks/session-attribution.bundle.mjs +0 -1
  352. package/hooks/session-db.bundle.mjs +0 -144
  353. package/hooks/session-directive.mjs +0 -520
  354. package/hooks/session-extract.bundle.mjs +0 -3
  355. package/hooks/session-helpers.mjs +0 -428
  356. package/hooks/session-loaders.mjs +0 -448
  357. package/hooks/session-snapshot.bundle.mjs +0 -32
  358. package/hooks/sessionstart.mjs +0 -464
  359. package/hooks/stop.mjs +0 -82
  360. package/hooks/suppress-stderr.mjs +0 -25
  361. package/hooks/userpromptsubmit.mjs +0 -108
  362. package/hooks/vscode-copilot/posttooluse.mjs +0 -61
  363. package/hooks/vscode-copilot/precompact.mjs +0 -55
  364. package/hooks/vscode-copilot/pretooluse.mjs +0 -27
  365. package/hooks/vscode-copilot/sessionstart.mjs +0 -129
  366. package/openclaw.plugin.json +0 -38
  367. package/scripts/heal-better-sqlite3.mjs +0 -430
  368. package/scripts/heal-installed-plugins.mjs +0 -598
  369. package/scripts/plugin-cache-integrity.mjs +0 -248
  370. package/scripts/postinstall.mjs +0 -384
  371. package/server.bundle.mjs +0 -1162
  372. package/skills/.ignore +0 -7
  373. package/skills/context-mode/SKILL.md +0 -300
  374. package/skills/context-mode/references/anti-patterns.md +0 -283
  375. package/skills/context-mode/references/patterns-javascript.md +0 -298
  376. package/skills/context-mode/references/patterns-python.md +0 -304
  377. package/skills/context-mode/references/patterns-shell.md +0 -277
  378. package/skills/ctx-index/SKILL.md +0 -46
  379. package/skills/ctx-insight/SKILL.md +0 -24
  380. package/skills/ctx-purge/SKILL.md +0 -49
  381. package/skills/ctx-search/SKILL.md +0 -35
  382. package/start.mjs +0 -616
@@ -1,248 +0,0 @@
1
- /**
2
- * Plugin cache integrity check (Algo-D4 + Algo-D5).
3
- *
4
- * Algorithmic defense against #550: a partial install (interrupted npm
5
- * install, broken marketplace pull, half-finished /ctx-upgrade) leaves
6
- * start.mjs spawnable but a critical sibling (server.bundle.mjs,
7
- * cli.bundle.mjs, hooks/<event>.mjs, …) missing. The MCP child then
8
- * dies silently downstream and the user sees an opaque "MCP server
9
- * failed to start" with no actionable signal.
10
- *
11
- * The expected sibling tree is DERIVED from `package.json files[]` —
12
- * the npm publish source of truth. Adding a new entry there auto-
13
- * extends the integrity check; no parallel hardcoded list to maintain
14
- * (the trap that bites every project that hand-rolls "list of files
15
- * that must exist at runtime").
16
- *
17
- * Two consumers:
18
- * 1. start.mjs at boot — calls assertPluginCacheIntegrity, on !ok
19
- * writes a structured CONTEXT_MODE_PARTIAL_INSTALL stderr block
20
- * and exits 2. Fail-fast — the alternative is a downstream stack
21
- * trace from `import("./server.bundle.mjs")` that hides the
22
- * actual root cause.
23
- * 2. src/cli.ts ctx doctor (Algo-D5) — same helper, same answer,
24
- * surfaced as a HealthCheck so users get the diagnostic without
25
- * restarting the MCP server.
26
- *
27
- * Pure JS, Node.js built-ins only. Ships in package.json files[] so
28
- * users running off the npm tarball get the same code path the
29
- * developer ran during `pretest`.
30
- */
31
- import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
32
- import { join, relative, sep } from "node:path";
33
-
34
- /**
35
- * Walk a directory recursively, returning a flat list of relative file
36
- * paths (using `/` as separator inside the returned strings). Skips
37
- * unreadable entries silently — the integrity check operates on what
38
- * IS readable; missing entries are reported by the caller.
39
- */
40
- function listFilesRecursive(absDir, baseAbs) {
41
- const out = [];
42
- let entries;
43
- try {
44
- entries = readdirSync(absDir);
45
- } catch {
46
- return out; // unreadable — caller will report the parent as missing
47
- }
48
- for (const name of entries) {
49
- const full = join(absDir, name);
50
- let st;
51
- try {
52
- st = statSync(full);
53
- } catch {
54
- continue;
55
- }
56
- if (st.isDirectory()) {
57
- out.push(...listFilesRecursive(full, baseAbs));
58
- } else {
59
- out.push(relative(baseAbs, full));
60
- }
61
- }
62
- return out;
63
- }
64
-
65
- /**
66
- * Compute the expected sibling tree for a given pluginRoot, derived
67
- * from the supplied `package.json files[]` array.
68
- *
69
- * Algorithm:
70
- * - Each entry in files[] is resolved against pluginRoot.
71
- * - If it points to a directory → list every file inside recursively.
72
- * - If it points to a file → kept as-is.
73
- * - Entries that don't exist at probe-time are EXCLUDED from the
74
- * manifest (they show up as `missing` in the assert step instead).
75
- * This avoids the trap of "manifest contains paths that have never
76
- * existed" — the manifest is a snapshot of WHAT IS, not WHAT WAS
77
- * PUBLISHED.
78
- *
79
- * Returns relative paths (relative to pluginRoot). Used by both
80
- * assertPluginCacheIntegrity and the doctor surface.
81
- */
82
- export function derivePluginManifest({ pkg, pluginRoot }) {
83
- if (!pkg || !Array.isArray(pkg.files)) return [];
84
- const manifest = new Set();
85
- for (const entry of pkg.files) {
86
- if (typeof entry !== "string" || !entry) continue;
87
- const absEntry = join(pluginRoot, entry);
88
- if (!existsSync(absEntry)) continue;
89
- let st;
90
- try {
91
- st = statSync(absEntry);
92
- } catch {
93
- continue;
94
- }
95
- if (st.isDirectory()) {
96
- for (const f of listFilesRecursive(absEntry, pluginRoot)) manifest.add(f);
97
- } else {
98
- manifest.add(entry);
99
- }
100
- }
101
- return [...manifest];
102
- }
103
-
104
- /**
105
- * LEGACY_FALLBACK — the v1.0.126 hardcoded REQUIRED_RUNTIME_SIBLINGS,
106
- * preserved verbatim. Forms the union seed for the algorithmic set so
107
- * the post-558 contract is strictly additive over the pre-558 contract
108
- * (no required sibling ever silently disappears).
109
- *
110
- * Also acts as a safety net when `package.json` is unreadable — the
111
- * boot gate stays loud even if the publish manifest is corrupted.
112
- */
113
- const LEGACY_FALLBACK = Object.freeze([
114
- "server.bundle.mjs",
115
- "cli.bundle.mjs",
116
- join("hooks", "pretooluse.mjs"),
117
- join("hooks", "posttooluse.mjs"),
118
- join("hooks", "precompact.mjs"),
119
- join("hooks", "sessionstart.mjs"),
120
- join("hooks", "userpromptsubmit.mjs"),
121
- ]);
122
-
123
- /**
124
- * SOFT_FALLBACK_BUNDLES — bundles that already implement
125
- * bundle-first / build-fallback resolution (via session-loaders.mjs or
126
- * session-helpers.mjs). Their absence on a published install is
127
- * gracefully recoverable, so they MUST NOT join the fail-fast boot
128
- * gate — the gate would refuse to start a working install.
129
- *
130
- * The security bundle is intentionally NOT here: its absence creates a
131
- * silent fail-OPEN regression (#558), so it IS boot-critical.
132
- */
133
- const SOFT_FALLBACK_BUNDLES = new Set([
134
- "hooks/session-extract.bundle.mjs",
135
- "hooks/session-snapshot.bundle.mjs",
136
- "hooks/session-db.bundle.mjs",
137
- "hooks/session-attribution.bundle.mjs",
138
- ]);
139
-
140
- /**
141
- * Algorithmically extract every esbuild output path from
142
- * `package.json scripts.bundle`. The bundle script is the SINGLE
143
- * SOURCE OF TRUTH for "what bundles this build produces" — parsing
144
- * its `--outfile=…` arguments avoids the parallel-list trap that
145
- * bit Algo-D4 v1.0.126 (the hardcoded REQUIRED list lagged the
146
- * actual bundle output).
147
- *
148
- * Returns POSIX-style relative paths (forward slashes) for stable
149
- * comparison with SOFT_FALLBACK_BUNDLES. Caller normalizes to
150
- * `path.join` shape before pluginRoot-relative resolution.
151
- */
152
- function extractBundleOutfiles(pkg) {
153
- const script = pkg?.scripts?.bundle;
154
- if (typeof script !== "string") return [];
155
- const out = new Set();
156
- // Match every `--outfile=<path>` token (path is whitespace-delimited
157
- // because the script chains commands with `&&`).
158
- const re = /--outfile=(\S+)/g;
159
- let m;
160
- while ((m = re.exec(script)) !== null) {
161
- out.add(m[1]);
162
- }
163
- return [...out];
164
- }
165
-
166
- /**
167
- * Algorithmic — derive the boot-critical sibling set as the union of:
168
- * 1. LEGACY_FALLBACK (the v1.0.126 contract, preserved verbatim).
169
- * 2. Every esbuild output path from `package.json scripts.bundle`
170
- * that is NOT in SOFT_FALLBACK_BUNDLES.
171
- *
172
- * Why algorithmic instead of hardcoded:
173
- *
174
- * v1.0.126 shipped Algo-D4 with a hardcoded REQUIRED_RUNTIME_SIBLINGS
175
- * array that omitted `hooks/security.bundle.mjs` (the bundle didn't
176
- * ship until v1.0.127). The hardcoded list would need manual
177
- * extension every time a runtime bundle is added — the same trap
178
- * would re-bite the next bundle. Deriving from `scripts.bundle`
179
- * closes the trap: any new bundle output is auto-gated unless it
180
- * joins the soft-fallback whitelist (which is itself an explicit
181
- * architectural decision, not a maintenance burden). (#558)
182
- *
183
- * Returns OS-native-separator relative paths (suitable for
184
- * `path.join(pluginRoot, …)`).
185
- *
186
- * If `package.json` is unreadable, returns LEGACY_FALLBACK as a
187
- * safety net so the boot gate never goes silent due to a parse
188
- * error in the publish manifest.
189
- */
190
- export function getRequiredRuntimeSiblings(pluginRoot) {
191
- let pkg;
192
- try {
193
- pkg = JSON.parse(readFileSync(join(pluginRoot, "package.json"), "utf-8"));
194
- } catch {
195
- return [...LEGACY_FALLBACK];
196
- }
197
- const required = new Set(LEGACY_FALLBACK);
198
- for (const outfile of extractBundleOutfiles(pkg)) {
199
- // Normalize to POSIX for soft-fallback membership check —
200
- // scripts.bundle is hand-authored with forward slashes already,
201
- // but be defensive in case a Windows-authored package.json ever
202
- // reaches us.
203
- const posix = outfile.split(sep).join("/");
204
- if (SOFT_FALLBACK_BUNDLES.has(posix)) continue;
205
- // Convert back to OS-native sep for downstream filesystem ops.
206
- required.add(posix.split("/").join(sep));
207
- }
208
- return [...required];
209
- }
210
-
211
- /**
212
- * Verify boot-critical siblings exist at pluginRoot.
213
- *
214
- * Returns `{ ok, missing }`. Pure — does NOT touch process.exit or
215
- * stderr. The caller (start.mjs at boot, src/cli.ts at doctor) decides
216
- * the failure surface (fail-fast exit 2 vs. doctor diagnostic).
217
- *
218
- * Required-set is computed by `getRequiredRuntimeSiblings()` —
219
- * algorithmically derived from `package.json files[]` filtered to the
220
- * RUNTIME_CRITICAL_PATTERN. Drift between publish manifest and runtime
221
- * contract becomes architecturally impossible (#558).
222
- */
223
- export function assertPluginCacheIntegrity({ pluginRoot }) {
224
- const missing = [];
225
- for (const rel of getRequiredRuntimeSiblings(pluginRoot)) {
226
- const abs = join(pluginRoot, rel);
227
- if (!existsSync(abs)) missing.push(abs);
228
- }
229
- return { ok: missing.length === 0, missing };
230
- }
231
-
232
- /**
233
- * Format the structured stderr block start.mjs emits when integrity
234
- * fails. Marker line `CONTEXT_MODE_PARTIAL_INSTALL` lets external
235
- * monitoring grep for the exact failure mode without parsing free-form
236
- * text. Keep the format stable across versions.
237
- */
238
- export function formatPartialInstallReport({ pluginRoot, missing }) {
239
- const lines = [
240
- "CONTEXT_MODE_PARTIAL_INSTALL",
241
- ` pluginRoot: ${pluginRoot}`,
242
- " missing:",
243
- ...missing.map((m) => ` - ${m}`),
244
- " fix: rm -rf the install dir and re-pull (marketplace) or run `npm install -g context-mode` again.",
245
- "",
246
- ];
247
- return lines.join("\n");
248
- }
@@ -1,384 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * postinstall — cross-platform post-install tasks
4
- *
5
- * 1. OpenClaw detection (print helper message)
6
- * 2. Windows global install: fix broken bin→node_modules path
7
- * when nvm4w places the shim and node_modules in different directories.
8
- * Creates a directory junction so npm's %~dp0\node_modules\... resolves.
9
- */
10
-
11
- import { existsSync, mkdirSync, readFileSync, writeFileSync, symlinkSync, lstatSync, unlinkSync } from "node:fs";
12
- import { execSync } from "node:child_process";
13
- import { dirname, resolve, join, sep } from "node:path";
14
- import { fileURLToPath } from "node:url";
15
- import { homedir } from "node:os";
16
- import { healBetterSqlite3Binding } from "./heal-better-sqlite3.mjs";
17
- import { healInstalledPlugins, healSettingsEnabledPlugins, healPluginJsonMcpServers, sweepStaleMcpJson } from "./heal-installed-plugins.mjs";
18
-
19
- const __dirname = dirname(fileURLToPath(import.meta.url));
20
- const pkgRoot = resolve(__dirname, "..");
21
-
22
- // ── -2. Issue #564 — Linux SIGSEGV class hard-fail (v1.0.132) ────────
23
- // On Linux + Node < 22.5 + no Bun, better-sqlite3's native addon is
24
- // vulnerable to V8 calling `madvise(MADV_DONTNEED)` on memory ranges
25
- // that overlap the addon's `.got.plt` section, corrupting resolved
26
- // symbol addresses and causing sporadic SIGSEGV (1-4/hour) — see
27
- // https://github.com/nodejs/node/issues/62515 and our internal #564.
28
- //
29
- // node:sqlite (built-in, no native addon, no .got.plt to corrupt) ships
30
- // from Node 22.5 onward — that is the contract `hasModernSqlite()` in
31
- // src/db-base.ts encodes. Six prior fixes (#228, #331, #461, #540,
32
- // #551, #556) silently assumed users had Node >= 22.5 on Linux; #564
33
- // is the second confirmed report (after #556) of the same SIGSEGV
34
- // class on Node 20.
35
- //
36
- // The architect mandate for v1.0.132 is HARD-FAIL, not warn-then-
37
- // degrade. `engines.node >= 22.5.0` in package.json is cosmetic under
38
- // the default npm `engine-strict=false`, so the contract has to be
39
- // enforced HERE — preinstall/postinstall is the only place that can
40
- // `process.exit(1)` across npm/pnpm/yarn.
41
- //
42
- // Linux + Bun is allowed through (bun:sqlite sidesteps better-sqlite3
43
- // entirely). Non-Linux platforms are unaffected by the madvise bug
44
- // and pass through unchanged.
45
- {
46
- const isLinux = process.platform === "linux";
47
- const hasBun =
48
- typeof globalThis.Bun !== "undefined" ||
49
- typeof process.versions.bun === "string";
50
- const [majStr, minStr] = (process.versions.node ?? "0.0.0").split(".");
51
- const major = Number(majStr);
52
- const minor = Number(minStr);
53
- const hasModernNode =
54
- Number.isFinite(major) &&
55
- Number.isFinite(minor) &&
56
- (major > 22 || (major === 22 && minor >= 5));
57
- if (isLinux && !hasBun && !hasModernNode) {
58
- process.stderr.write(
59
- "\n" +
60
- "context-mode: install aborted\n" +
61
- " Linux + Node " + (process.versions.node ?? "?") + " is unsupported.\n" +
62
- " context-mode requires Node.js >= 22.5 (or Bun) on Linux to avoid the\n" +
63
- " V8 madvise(MADV_DONTNEED) SIGSEGV affecting better-sqlite3 (1-4/hour).\n" +
64
- " Tracking: https://github.com/nodejs/node/issues/62515\n" +
65
- " https://github.com/mksglu/context-mode/issues/564\n" +
66
- "\n" +
67
- " Fix: upgrade Node (recommended)\n" +
68
- " nvm install 22.5 && nvm use 22.5\n" +
69
- " npm install -g context-mode\n" +
70
- "\n" +
71
- " Or: run under Bun\n" +
72
- " curl -fsSL https://bun.sh/install | bash\n" +
73
- " bun add -g context-mode\n" +
74
- "\n",
75
- );
76
- process.exit(1);
77
- }
78
- }
79
-
80
- /**
81
- * True when running as a real `npm install -g context-mode`. We use this
82
- * to keep contributors' local `npm install` runs from rewriting their HOME's
83
- * Claude Code registry (would be very surprising during dev).
84
- *
85
- * Heuristic: npm sets `npm_config_global=true` for global installs AND the
86
- * package directory has no nearby `.git` (a contributor's clone always
87
- * does). Both signals must agree.
88
- */
89
- function isGlobalInstall() {
90
- if (process.env.npm_config_global !== "true") return false;
91
- // Walk up a few levels looking for .git — contributors always have one.
92
- let dir = pkgRoot;
93
- for (let i = 0; i < 4; i++) {
94
- if (existsSync(join(dir, ".git"))) return false;
95
- const parent = dirname(dir);
96
- if (parent === dir) break;
97
- dir = parent;
98
- }
99
- return true;
100
- }
101
-
102
- /**
103
- * Validate that a path is safe to interpolate into a cmd.exe command.
104
- * Rejects characters that could enable command injection via cmd.exe.
105
- */
106
- function isSafeWindowsPath(p) {
107
- return !/[&|<>"^%\r\n]/.test(p);
108
- }
109
-
110
- // ── -1. v1.0.114 hotfix — installed_plugins.json registry repair ─────
111
- // /ctx-upgrade in v1.0.113 poisoned the registry (entry.version drifted
112
- // + enabledPlugins emptied), making Claude Code's plugin loader skip
113
- // context-mode entirely. start.mjs HEAL 3+4 fix this on every MCP boot,
114
- // but already-broken users have no MCP to boot — they need the heal to
115
- // run from npm postinstall. Shared module so both call sites stay in
116
- // sync. Only runs in real `npm install -g` to avoid surprising
117
- // contributors. Best effort, never blocks install. (#46915 follow-up.)
118
- if (isGlobalInstall()) {
119
- try {
120
- const registryPath = resolve(homedir(), ".claude", "plugins", "installed_plugins.json");
121
- const pluginCacheRoot = resolve(homedir(), ".claude", "plugins", "cache");
122
- const result = healInstalledPlugins({
123
- registryPath,
124
- pluginCacheRoot,
125
- pluginKey: "context-mode@context-mode",
126
- });
127
- if (result.skipped === "no-registry") {
128
- // Standalone npm user (no Claude Code) — silent success.
129
- process.stderr.write("context-mode: install OK, no Claude Code registry found\n");
130
- } else if (result.error) {
131
- process.stderr.write(`context-mode: install OK, registry heal skipped (${result.error})\n`);
132
- } else if (result.healed && result.healed.length > 0) {
133
- process.stderr.write(`context-mode: healed installed_plugins.json (${result.healed.join(", ")})\n`);
134
- } else {
135
- process.stderr.write("context-mode: install OK, no heal needed\n");
136
- }
137
- } catch (err) {
138
- // Never block install on a heal failure.
139
- try {
140
- process.stderr.write(`context-mode: install OK, heal aborted (${(err && err.message) || err})\n`);
141
- } catch { /* truly best effort */ }
142
- }
143
-
144
- // v1.0.116: also heal settings.json.enabledPlugins (the file Claude Code's
145
- // plugin loader actually reads). v1.0.114 only touched installed_plugins.json.
146
- try {
147
- const settingsPath = resolve(homedir(), ".claude", "settings.json");
148
- const r = healSettingsEnabledPlugins({
149
- settingsPath,
150
- pluginKey: "context-mode@context-mode",
151
- });
152
- if (r.healed && r.healed.length > 0) {
153
- process.stderr.write(`context-mode: healed settings.json (${r.healed.join(", ")})\n`);
154
- }
155
- // skipped/error: silent — already covered by the prior heal's stderr line.
156
- } catch { /* never block install */ }
157
-
158
- // v1.0.119: Layer 5b (Issue #523). Heal .claude-plugin/plugin.json's
159
- // mcpServers["context-mode"].args[0] when /ctx-upgrade left a tmpdir-prefixed
160
- // path baked in. Iterates EVERY installed cache entry's installPath so
161
- // already-broken users self-recover the next time `npm install -g context-mode`
162
- // runs. Best effort, never blocks install.
163
- try {
164
- const ipPath = resolve(homedir(), ".claude", "plugins", "installed_plugins.json");
165
- const cacheRoot = resolve(homedir(), ".claude", "plugins", "cache");
166
- if (existsSync(ipPath)) {
167
- const ip = JSON.parse(readFileSync(ipPath, "utf-8"));
168
- const entries = (ip && ip.plugins && ip.plugins["context-mode@context-mode"]) || [];
169
- let healedAny = false;
170
- if (Array.isArray(entries)) {
171
- for (const entry of entries) {
172
- const installPath = entry && entry.installPath;
173
- if (typeof installPath !== "string" || !installPath) continue;
174
- try {
175
- const r = healPluginJsonMcpServers({
176
- pluginRoot: installPath,
177
- pluginCacheRoot: cacheRoot,
178
- pluginKey: "context-mode@context-mode",
179
- });
180
- if (r && Array.isArray(r.healed) && r.healed.length > 0) {
181
- healedAny = true;
182
- }
183
- } catch { /* per-entry best effort */ }
184
- }
185
- }
186
- // Issue #609 — Layer 6: sweep stale `.mcp.json` files from every
187
- // per-version cache dir. Replaces the previous per-entry healMcpJsonArgs
188
- // loop (v1.0.122) — `.mcp.json` is no longer written from cli.ts so
189
- // remaining files in the cache are stale carry-forwards that block
190
- // future auto-updates from working cleanly. Single sweep per install.
191
- try {
192
- const sweepResult = sweepStaleMcpJson({
193
- pluginCacheRoot: cacheRoot,
194
- pluginKey: "context-mode@context-mode",
195
- });
196
- if (sweepResult && Array.isArray(sweepResult.removed) && sweepResult.removed.length > 0) {
197
- process.stderr.write(`context-mode: swept ${sweepResult.removed.length} stale .mcp.json file(s) (Issue #609)\n`);
198
- }
199
- } catch { /* never block install */ }
200
- if (healedAny) {
201
- process.stderr.write("context-mode: healed mcpServers args (Issue #523)\n");
202
- }
203
- }
204
- } catch { /* never block install */ }
205
- }
206
-
207
- // ── 0. Self-heal Layer 3: Backward symlink for stale registry (anthropics/claude-code#46915) ──
208
- // When this install completes, installed_plugins.json may still point to an old
209
- // non-existent path. Create a symlink from that old path → our new directory.
210
- try {
211
- const ipPath = resolve(homedir(), ".claude", "plugins", "installed_plugins.json");
212
- if (existsSync(ipPath)) {
213
- const ip = JSON.parse(readFileSync(ipPath, "utf-8"));
214
- const cacheRoot = resolve(homedir(), ".claude", "plugins", "cache");
215
- for (const [key, entries] of Object.entries(ip.plugins || {})) {
216
- if (key !== "context-mode@context-mode") continue;
217
- for (const entry of entries) {
218
- const rp = entry.installPath;
219
- if (!rp || existsSync(rp)) continue;
220
- // Path traversal guard
221
- if (!resolve(rp).startsWith(cacheRoot + sep)) continue;
222
- // Remove dangling symlink
223
- try { if (lstatSync(rp).isSymbolicLink()) unlinkSync(rp); } catch {}
224
- const rpParent = dirname(rp);
225
- if (!existsSync(rpParent)) mkdirSync(rpParent, { recursive: true });
226
- try {
227
- symlinkSync(pkgRoot, rp, process.platform === "win32" ? "junction" : undefined);
228
- } catch { /* may fail if path is locked or permissions */ }
229
- }
230
- }
231
- }
232
- } catch { /* best effort — don't block install */ }
233
-
234
- // ── 1. OpenClaw detection ────────────────────────────────────────────
235
- if (process.env.OPENCLAW_STATE_DIR) {
236
- console.log("\n OpenClaw detected. Run: npm run install:openclaw\n");
237
- }
238
-
239
- // ── 2. Windows global install — nvm4w junction fix ───────────────────
240
- // npm's .cmd shim resolves modules via %~dp0\node_modules\<pkg>\...
241
- // On nvm4w the shim lives at C:\nvm4w\nodejs\ but node_modules is at
242
- // C:\Users\<USER>\AppData\Roaming\npm\node_modules\. The relative path
243
- // breaks because they're on different prefixes.
244
- //
245
- // Fix: detect the mismatch and create a directory junction so the shim
246
- // can reach us through the expected relative path.
247
-
248
- if (process.platform === "win32" && process.env.npm_config_global === "true") {
249
- try {
250
- // npm prefix is where both the .cmd shims and node_modules live
251
- // Use npm_config_prefix env (set during install) or fall back to `npm config get prefix`
252
- // Note: `npm bin -g` was removed in npm v9+, so we use prefix instead
253
- const prefix = (
254
- process.env.npm_config_prefix ||
255
- execSync("npm config get prefix", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim()
256
- );
257
-
258
- const actualPkgDir = pkgRoot;
259
-
260
- // npm's .cmd shim uses %~dp0\node_modules\<pkg>\... to find the entry point.
261
- // On nvm4w, stale shims at C:\nvm4w\nodejs\ may exist alongside correct ones
262
- // at the npm prefix. We create junctions at ALL known shim locations.
263
- const shimDirs = new Set([prefix]);
264
-
265
- // Detect stale shim locations via `where` command
266
- try {
267
- const whereOutput = execSync("where context-mode.cmd", {
268
- encoding: "utf-8",
269
- stdio: ["pipe", "pipe", "pipe"],
270
- }).trim();
271
- for (const line of whereOutput.split(/\r?\n/)) {
272
- if (line.endsWith("context-mode.cmd")) {
273
- shimDirs.add(dirname(line));
274
- }
275
- }
276
- } catch { /* where may fail if not installed yet */ }
277
-
278
- for (const shimDir of shimDirs) {
279
- const expectedPkgDir = join(shimDir, "node_modules", "context-mode");
280
-
281
- if (
282
- resolve(expectedPkgDir).toLowerCase() !== resolve(actualPkgDir).toLowerCase() &&
283
- !existsSync(expectedPkgDir)
284
- ) {
285
- const expectedNodeModules = join(shimDir, "node_modules");
286
- if (!existsSync(expectedNodeModules)) {
287
- mkdirSync(expectedNodeModules, { recursive: true });
288
- }
289
-
290
- // Create directory junction (no admin privileges needed on Windows 10+)
291
- // Validate paths to prevent cmd.exe injection via shell metacharacters
292
- if (!isSafeWindowsPath(expectedPkgDir) || !isSafeWindowsPath(actualPkgDir)) {
293
- console.warn(` context-mode: skipping junction — path contains unsafe characters`);
294
- } else {
295
- execSync(`mklink /J "${expectedPkgDir}" "${actualPkgDir}"`, {
296
- shell: "cmd.exe",
297
- stdio: "pipe",
298
- });
299
- console.log(`\n context-mode: created junction for nvm4w compatibility`);
300
- console.log(` ${expectedPkgDir} → ${actualPkgDir}\n`);
301
- }
302
- }
303
- }
304
-
305
- // Also fix stale shims that reference old bin entry (build/cli.js → cli.bundle.mjs)
306
- try {
307
- const whereOutput = execSync("where context-mode.cmd", {
308
- encoding: "utf-8",
309
- stdio: ["pipe", "pipe", "pipe"],
310
- }).trim();
311
- for (const line of whereOutput.split(/\r?\n/)) {
312
- if (line.endsWith("context-mode.cmd")) {
313
- const content = readFileSync(line, "utf-8");
314
- if (content.includes("build\\cli.js") || content.includes("build/cli.js")) {
315
- // Rewrite stale shim to use cli.bundle.mjs
316
- const fixed = content
317
- .replace(/build[\\\/]cli\.js/g, "cli.bundle.mjs");
318
- writeFileSync(line, fixed);
319
- console.log(` context-mode: fixed stale shim at ${line}`);
320
- }
321
- }
322
- }
323
- } catch { /* best effort */ }
324
- } catch {
325
- // Best effort — don't block install. User can use npx as fallback.
326
- }
327
- }
328
-
329
- // ── 3. Native binding self-heal — better-sqlite3 (#408) ──────────────
330
- // On Windows, `npm rebuild` falls through to node-gyp without MSVC; bypass
331
- // that by spawning prebuild-install directly. Cross-platform safety net —
332
- // the binding can also go missing on macOS/Linux when prebuilds are stale
333
- // or the install was interrupted.
334
- //
335
- // Logic lives in scripts/heal-better-sqlite3.mjs (shared with
336
- // hooks/ensure-deps.mjs so there's one source of truth).
337
- try { healBetterSqlite3Binding(pkgRoot); } catch { /* best effort — don't block install */ }
338
-
339
- // ── 4. Hook normalization at install time (#414) ─────────────────────
340
- // hooks/hooks.json + .claude-plugin/plugin.json ship with `${CLAUDE_PLUGIN_ROOT}`
341
- // + bare `node` command. On Windows + Claude Code that combination triggers
342
- // `cjs/loader:1479 MODULE_NOT_FOUND` (placeholder mangling, MSYS path issues,
343
- // PATH lookup failure). start.mjs normalizes on every MCP boot, but normalizing
344
- // here too closes the gap for the very first hook fire after a fresh install
345
- // (before any MCP server has run).
346
- //
347
- // Guard 1: only run on REAL `npm install -g context-mode`. A contributor's
348
- // `npm install` from a git clone (or CI checkout) must NOT mutate the
349
- // source-tracked `.claude-plugin/plugin.json` — doing so substitutes the
350
- // literal `${CLAUDE_PLUGIN_ROOT}` with an absolute path and trips
351
- // `scripts/assert-asymmetric-drift.mjs` (Issue #531) in the build chain.
352
- // Reuses `isGlobalInstall()` (section -1 already gates that way); the
353
- // `.git` walk inside it is what keeps contributor / CI installs untouched.
354
- //
355
- // Guard 2: /ctx-upgrade clones the repo to `<tmpdir>/context-mode-upgrade-<epoch>/`
356
- // and runs `npm install` there before `cpSync`-ing files into the real pluginRoot
357
- // (src/cli.ts). The tmpdir has no `.git`, so `isGlobalInstall()` returns
358
- // true there — we need this second check to skip the staging dir. Without
359
- // it, pkgRoot is the tmpdir → hooks.json gets the tmpdir's absolute paths
360
- // baked in → cpSync copies that poisoned hooks.json into the real plugin
361
- // dir → tmpdir is later cleaned → every hook fires with MODULE_NOT_FOUND.
362
- // start.mjs normalizes correctly on the next MCP boot from the real
363
- // pluginRoot anyway.
364
- const TMPDIR_UPGRADE_RE = /[/\\]context-mode-upgrade-\d+[/\\]?$/;
365
- if (isGlobalInstall() && !TMPDIR_UPGRADE_RE.test(pkgRoot)) {
366
- try {
367
- // #738: probe for Bun ≥1.0 so the post-install hooks.json rewrite picks
368
- // the faster runtime where available. Probe failures (e.g. build not
369
- // present yet during `npm install` itself) fall through to nodePath.
370
- let jsRuntimePath;
371
- try {
372
- const { resolveHookRuntime } = await import("../build/runtime.js");
373
- const r = resolveHookRuntime();
374
- if (r.isBun) jsRuntimePath = r.path;
375
- } catch { /* best effort — fall through */ }
376
- const { normalizeHooksOnStartup } = await import("../hooks/normalize-hooks.mjs");
377
- normalizeHooksOnStartup({
378
- pluginRoot: pkgRoot,
379
- nodePath: process.execPath,
380
- jsRuntimePath,
381
- platform: process.platform,
382
- });
383
- } catch { /* best effort — never block install */ }
384
- }