@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.
- package/.claude-plugin/marketplace.json +33 -0
- package/.claude-plugin/plugin.json +31 -0
- package/.codex-plugin/hooks.json +65 -0
- package/.codex-plugin/mcp.json +13 -0
- package/.codex-plugin/plugin.json +31 -0
- package/.openclaw-plugin/index.ts +12 -0
- package/.openclaw-plugin/openclaw.plugin.json +38 -0
- package/.openclaw-plugin/package.json +28 -0
- package/LICENSE +94 -0
- package/README.md +1619 -0
- package/bin/statusline.mjs +378 -0
- package/build/adapters/antigravity/index.d.ts +51 -0
- package/build/adapters/antigravity/index.js +169 -0
- package/build/adapters/antigravity-cli/index.d.ts +51 -0
- package/build/adapters/antigravity-cli/index.js +342 -0
- package/build/adapters/base.d.ts +92 -0
- package/build/adapters/base.js +135 -0
- package/build/adapters/claude-code/config.d.ts +8 -0
- package/build/adapters/claude-code/config.js +8 -0
- package/build/adapters/claude-code/hooks.d.ts +106 -0
- package/build/adapters/claude-code/hooks.js +183 -0
- package/build/adapters/claude-code/index.d.ts +80 -0
- package/build/adapters/claude-code/index.js +569 -0
- package/build/adapters/claude-code-base.d.ts +49 -0
- package/build/adapters/claude-code-base.js +113 -0
- package/build/adapters/client-map.d.ts +10 -0
- package/build/adapters/client-map.js +42 -0
- package/build/adapters/codex/config.d.ts +8 -0
- package/build/adapters/codex/config.js +8 -0
- package/build/adapters/codex/hooks.d.ts +55 -0
- package/build/adapters/codex/hooks.js +64 -0
- package/build/adapters/codex/index.d.ts +87 -0
- package/build/adapters/codex/index.js +995 -0
- package/build/adapters/codex/paths.d.ts +1 -0
- package/build/adapters/codex/paths.js +12 -0
- package/build/adapters/codex/usage.d.ts +107 -0
- package/build/adapters/codex/usage.js +227 -0
- package/build/adapters/copilot-base.d.ts +94 -0
- package/build/adapters/copilot-base.js +297 -0
- package/build/adapters/copilot-cli/hooks.d.ts +33 -0
- package/build/adapters/copilot-cli/hooks.js +64 -0
- package/build/adapters/copilot-cli/index.d.ts +48 -0
- package/build/adapters/copilot-cli/index.js +341 -0
- package/build/adapters/cursor/config.d.ts +4 -0
- package/build/adapters/cursor/config.js +4 -0
- package/build/adapters/cursor/hooks.d.ts +55 -0
- package/build/adapters/cursor/hooks.js +82 -0
- package/build/adapters/cursor/index.d.ts +64 -0
- package/build/adapters/cursor/index.js +517 -0
- package/build/adapters/detect.d.ts +136 -0
- package/build/adapters/detect.js +623 -0
- package/build/adapters/gemini-cli/config.d.ts +8 -0
- package/build/adapters/gemini-cli/config.js +8 -0
- package/build/adapters/gemini-cli/hooks.d.ts +83 -0
- package/build/adapters/gemini-cli/hooks.js +115 -0
- package/build/adapters/gemini-cli/index.d.ts +80 -0
- package/build/adapters/gemini-cli/index.js +483 -0
- package/build/adapters/jetbrains-copilot/config.d.ts +8 -0
- package/build/adapters/jetbrains-copilot/config.js +8 -0
- package/build/adapters/jetbrains-copilot/hooks.d.ts +59 -0
- package/build/adapters/jetbrains-copilot/hooks.js +87 -0
- package/build/adapters/jetbrains-copilot/index.d.ts +31 -0
- package/build/adapters/jetbrains-copilot/index.js +131 -0
- package/build/adapters/kimi/config.d.ts +8 -0
- package/build/adapters/kimi/config.js +8 -0
- package/build/adapters/kimi/hooks.d.ts +28 -0
- package/build/adapters/kimi/hooks.js +34 -0
- package/build/adapters/kimi/index.d.ts +66 -0
- package/build/adapters/kimi/index.js +537 -0
- package/build/adapters/kimi/paths.d.ts +21 -0
- package/build/adapters/kimi/paths.js +52 -0
- package/build/adapters/kimi/usage.d.ts +82 -0
- package/build/adapters/kimi/usage.js +217 -0
- package/build/adapters/kiro/hooks.d.ts +61 -0
- package/build/adapters/kiro/hooks.js +94 -0
- package/build/adapters/kiro/index.d.ts +61 -0
- package/build/adapters/kiro/index.js +301 -0
- package/build/adapters/omp/index.d.ts +65 -0
- package/build/adapters/omp/index.js +182 -0
- package/build/adapters/omp/plugin.d.ts +81 -0
- package/build/adapters/omp/plugin.js +331 -0
- package/build/adapters/omp/usage.d.ts +49 -0
- package/build/adapters/omp/usage.js +110 -0
- package/build/adapters/openclaw/config.d.ts +8 -0
- package/build/adapters/openclaw/config.js +8 -0
- package/build/adapters/openclaw/hooks.d.ts +50 -0
- package/build/adapters/openclaw/hooks.js +61 -0
- package/build/adapters/openclaw/index.d.ts +74 -0
- package/build/adapters/openclaw/index.js +439 -0
- package/build/adapters/openclaw/mcp-tools.d.ts +54 -0
- package/build/adapters/openclaw/mcp-tools.js +207 -0
- package/build/adapters/openclaw/plugin.d.ts +141 -0
- package/build/adapters/openclaw/plugin.js +818 -0
- package/build/adapters/openclaw/session-db.d.ts +55 -0
- package/build/adapters/openclaw/session-db.js +88 -0
- package/build/adapters/openclaw/usage.d.ts +34 -0
- package/build/adapters/openclaw/usage.js +52 -0
- package/build/adapters/openclaw/workspace-router.d.ts +29 -0
- package/build/adapters/openclaw/workspace-router.js +64 -0
- package/build/adapters/opencode/config.d.ts +8 -0
- package/build/adapters/opencode/config.js +8 -0
- package/build/adapters/opencode/hooks.d.ts +38 -0
- package/build/adapters/opencode/hooks.js +50 -0
- package/build/adapters/opencode/index.d.ts +144 -0
- package/build/adapters/opencode/index.js +638 -0
- package/build/adapters/opencode/plugin.d.ts +249 -0
- package/build/adapters/opencode/plugin.js +1536 -0
- package/build/adapters/opencode/v2.d.ts +117 -0
- package/build/adapters/opencode/v2.js +224 -0
- package/build/adapters/opencode/zod3tov4.d.ts +1 -0
- package/build/adapters/opencode/zod3tov4.js +132 -0
- package/build/adapters/pi/extension.d.ts +81 -0
- package/build/adapters/pi/extension.js +861 -0
- package/build/adapters/pi/index.d.ts +57 -0
- package/build/adapters/pi/index.js +173 -0
- package/build/adapters/pi/mcp-bridge.d.ts +268 -0
- package/build/adapters/pi/mcp-bridge.js +894 -0
- package/build/adapters/qwen-code/hooks.d.ts +26 -0
- package/build/adapters/qwen-code/hooks.js +29 -0
- package/build/adapters/qwen-code/index.d.ts +40 -0
- package/build/adapters/qwen-code/index.js +361 -0
- package/build/adapters/qwen-code/usage.d.ts +90 -0
- package/build/adapters/qwen-code/usage.js +222 -0
- package/build/adapters/types.d.ts +360 -0
- package/build/adapters/types.js +118 -0
- package/build/adapters/vscode-copilot/config.d.ts +8 -0
- package/build/adapters/vscode-copilot/config.js +8 -0
- package/build/adapters/vscode-copilot/hooks.d.ts +71 -0
- package/build/adapters/vscode-copilot/hooks.js +98 -0
- package/build/adapters/vscode-copilot/index.d.ts +32 -0
- package/build/adapters/vscode-copilot/index.js +222 -0
- package/build/adapters/zed/index.d.ts +40 -0
- package/build/adapters/zed/index.js +163 -0
- package/build/cli.d.ts +34 -0
- package/build/cli.js +1828 -0
- package/build/db-base.d.ts +250 -0
- package/build/db-base.js +756 -0
- package/build/executor.d.ts +72 -0
- package/build/executor.js +678 -0
- package/build/exit-classify.d.ts +19 -0
- package/build/exit-classify.js +12 -0
- package/build/fetch/blocks.d.ts +145 -0
- package/build/fetch/blocks.js +244 -0
- package/build/fetch/extract.d.ts +68 -0
- package/build/fetch/extract.js +123 -0
- package/build/fetch/page-store.d.ts +66 -0
- package/build/fetch/page-store.js +174 -0
- package/build/fetch-cache.d.ts +13 -0
- package/build/fetch-cache.js +15 -0
- package/build/lifecycle.d.ts +124 -0
- package/build/lifecycle.js +278 -0
- package/build/package-identity.d.ts +26 -0
- package/build/package-identity.js +28 -0
- package/build/runPool.d.ts +36 -0
- package/build/runPool.js +51 -0
- package/build/runtime.d.ts +71 -0
- package/build/runtime.js +659 -0
- package/build/search/auto-memory.d.ts +47 -0
- package/build/search/auto-memory.js +170 -0
- package/build/search/ctx-search-schema.d.ts +90 -0
- package/build/search/ctx-search-schema.js +135 -0
- package/build/search/flood-guard.d.ts +57 -0
- package/build/search/flood-guard.js +80 -0
- package/build/search/unified.d.ts +56 -0
- package/build/search/unified.js +104 -0
- package/build/security.d.ts +206 -0
- package/build/security.js +780 -0
- package/build/server.d.ts +240 -0
- package/build/server.js +5034 -0
- package/build/session/analytics.d.ts +773 -0
- package/build/session/analytics.js +2420 -0
- package/build/session/db.d.ts +468 -0
- package/build/session/db.js +1330 -0
- package/build/session/error-classifier.d.ts +87 -0
- package/build/session/error-classifier.js +303 -0
- package/build/session/event-emit.d.ts +48 -0
- package/build/session/event-emit.js +101 -0
- package/build/session/extract.d.ts +333 -0
- package/build/session/extract.js +2688 -0
- package/build/session/model-prices.json +429 -0
- package/build/session/persist-tool-calls.d.ts +54 -0
- package/build/session/persist-tool-calls.js +105 -0
- package/build/session/pricing.d.ts +64 -0
- package/build/session/pricing.js +151 -0
- package/build/session/project-attribution.d.ts +73 -0
- package/build/session/project-attribution.js +245 -0
- package/build/session/purge.d.ts +138 -0
- package/build/session/purge.js +240 -0
- package/build/session/retrieval-marker.d.ts +39 -0
- package/build/session/retrieval-marker.js +65 -0
- package/build/session/snapshot.d.ts +46 -0
- package/build/session/snapshot.js +532 -0
- package/build/store-directory.d.ts +56 -0
- package/build/store-directory.js +254 -0
- package/build/store.d.ts +147 -0
- package/build/store.js +1714 -0
- package/build/truncate.d.ts +57 -0
- package/build/truncate.js +142 -0
- package/build/types.d.ts +117 -0
- package/build/types.js +20 -0
- package/build/util/claude-config.d.ts +32 -0
- package/build/util/claude-config.js +84 -0
- package/build/util/hook-config.d.ts +27 -0
- package/build/util/hook-config.js +76 -0
- package/build/util/jsonc.d.ts +14 -0
- package/build/util/jsonc.js +104 -0
- package/build/util/plugin-cache-integrity.d.ts +51 -0
- package/build/util/plugin-cache-integrity.js +146 -0
- package/build/util/project-dir.d.ts +149 -0
- package/build/util/project-dir.js +340 -0
- package/build/util/sibling-mcp.d.ts +79 -0
- package/build/util/sibling-mcp.js +181 -0
- package/cli.bundle.mjs +1495 -0
- package/configs/antigravity/GEMINI.md +68 -0
- package/configs/antigravity/mcp_config.json +7 -0
- package/configs/antigravity-cli/hooks/hooks.json +37 -0
- package/configs/antigravity-cli/hooks.json +37 -0
- package/configs/antigravity-cli/mcp_config.json +10 -0
- package/configs/antigravity-cli/plugin.json +14 -0
- package/configs/antigravity-cli/rules/context-mode.md +77 -0
- package/configs/antigravity-cli/skills/context-mode/SKILL.md +77 -0
- package/configs/claude-code/CLAUDE.md +91 -0
- package/configs/codex/AGENTS.md +99 -0
- package/configs/codex/config.toml +8 -0
- package/configs/codex/hooks.json +47 -0
- package/configs/copilot-cli/.github/plugin/plugin.json +23 -0
- package/configs/copilot-cli/.mcp.json +12 -0
- package/configs/copilot-cli/README.md +47 -0
- package/configs/copilot-cli/hooks.json +41 -0
- package/configs/copilot-cli/skills/context-mode/SKILL.md +38 -0
- package/configs/cursor/context-mode.mdc +76 -0
- package/configs/cursor/hooks.json +21 -0
- package/configs/cursor/mcp.json +7 -0
- package/configs/gemini-cli/GEMINI.md +88 -0
- package/configs/gemini-cli/mcp.json +7 -0
- package/configs/gemini-cli/settings.json +60 -0
- package/configs/jetbrains-copilot/copilot-instructions.md +80 -0
- package/configs/jetbrains-copilot/hooks.json +16 -0
- package/configs/jetbrains-copilot/mcp.json +7 -0
- package/configs/kilo/AGENTS.md +84 -0
- package/configs/kilo/kilo.json +6 -0
- package/configs/kimi/hooks.json +54 -0
- package/configs/kiro/KIRO.md +84 -0
- package/configs/kiro/agent.json +18 -0
- package/configs/kiro/mcp.json +7 -0
- package/configs/omp/SYSTEM.md +85 -0
- package/configs/omp/mcp.json +7 -0
- package/configs/openclaw/AGENTS.md +87 -0
- package/configs/openclaw/openclaw.json +13 -0
- package/configs/opencode/AGENTS.md +84 -0
- package/configs/opencode/opencode.json +6 -0
- package/configs/pi/AGENTS.md +3 -0
- package/configs/qwen-code/QWEN.md +91 -0
- package/configs/vscode-copilot/copilot-instructions.md +80 -0
- package/configs/vscode-copilot/hooks.json +16 -0
- package/configs/vscode-copilot/mcp.json +7 -0
- package/configs/zed/AGENTS.md +68 -0
- package/hooks/antigravity-cli/payload.mjs +98 -0
- package/hooks/antigravity-cli/posttooluse.mjs +138 -0
- package/hooks/antigravity-cli/pretooluse.mjs +78 -0
- package/hooks/antigravity-cli/stop.mjs +58 -0
- package/hooks/auto-injection.mjs +102 -0
- package/hooks/cache-heal-utils.mjs +379 -0
- package/hooks/codex/platform.mjs +1 -0
- package/hooks/codex/posttooluse.mjs +66 -0
- package/hooks/codex/precompact.mjs +70 -0
- package/hooks/codex/pretooluse.mjs +42 -0
- package/hooks/codex/sessionstart.mjs +121 -0
- package/hooks/codex/stop.mjs +138 -0
- package/hooks/codex/userpromptsubmit.mjs +75 -0
- package/hooks/copilot-cli/posttooluse.mjs +79 -0
- package/hooks/copilot-cli/precompact.mjs +66 -0
- package/hooks/copilot-cli/pretooluse.mjs +41 -0
- package/hooks/copilot-cli/sessionstart.mjs +121 -0
- package/hooks/copilot-cli/stop.mjs +59 -0
- package/hooks/copilot-cli/userpromptsubmit.mjs +77 -0
- package/hooks/core/codex-caps.mjs +112 -0
- package/hooks/core/formatters.mjs +356 -0
- package/hooks/core/mcp-ready.mjs +109 -0
- package/hooks/core/platform-detect.mjs +49 -0
- package/hooks/core/routing.mjs +1050 -0
- package/hooks/core/stdin.mjs +90 -0
- package/hooks/core/tool-naming.mjs +57 -0
- package/hooks/cursor/afteragentresponse.mjs +74 -0
- package/hooks/cursor/hooks.json +31 -0
- package/hooks/cursor/posttooluse.mjs +72 -0
- package/hooks/cursor/pretooluse.mjs +26 -0
- package/hooks/cursor/sessionstart.mjs +97 -0
- package/hooks/cursor/stop.mjs +48 -0
- package/hooks/ensure-deps.mjs +254 -0
- package/hooks/formatters/claude-code.mjs +113 -0
- package/hooks/formatters/cursor.mjs +37 -0
- package/hooks/formatters/gemini-cli.mjs +55 -0
- package/hooks/formatters/vscode-copilot.mjs +55 -0
- package/hooks/gemini-cli/aftermodel.mjs +70 -0
- package/hooks/gemini-cli/aftertool.mjs +61 -0
- package/hooks/gemini-cli/beforeagent.mjs +99 -0
- package/hooks/gemini-cli/beforetool.mjs +27 -0
- package/hooks/gemini-cli/precompress.mjs +55 -0
- package/hooks/gemini-cli/sessionstart.mjs +137 -0
- package/hooks/heal-partial-install.mjs +712 -0
- package/hooks/hooks.json +143 -0
- package/hooks/jetbrains-copilot/posttooluse.mjs +61 -0
- package/hooks/jetbrains-copilot/precompact.mjs +55 -0
- package/hooks/jetbrains-copilot/pretooluse.mjs +27 -0
- package/hooks/jetbrains-copilot/sessionstart.mjs +124 -0
- package/hooks/kimi/platform.mjs +1 -0
- package/hooks/kimi/posttooluse.mjs +72 -0
- package/hooks/kimi/precompact.mjs +80 -0
- package/hooks/kimi/pretooluse.mjs +42 -0
- package/hooks/kimi/sessionend.mjs +61 -0
- package/hooks/kimi/sessionstart.mjs +113 -0
- package/hooks/kimi/stop.mjs +132 -0
- package/hooks/kimi/userpromptsubmit.mjs +90 -0
- package/hooks/kiro/agentspawn.mjs +97 -0
- package/hooks/kiro/posttooluse.mjs +51 -0
- package/hooks/kiro/pretooluse.mjs +64 -0
- package/hooks/kiro/userpromptsubmit.mjs +88 -0
- package/hooks/normalize-hooks.mjs +323 -0
- package/hooks/platform-bridge.mjs +340 -0
- package/hooks/posttooluse.mjs +225 -0
- package/hooks/precompact.mjs +100 -0
- package/hooks/pretooluse.mjs +227 -0
- package/hooks/qwen-code/platform.mjs +1 -0
- package/hooks/qwen-code/stop.mjs +168 -0
- package/hooks/routing-block.mjs +105 -0
- package/hooks/run-hook.mjs +95 -0
- package/hooks/security.bundle.mjs +2 -0
- package/hooks/session-attribution.bundle.mjs +1 -0
- package/hooks/session-db.bundle.mjs +145 -0
- package/hooks/session-directive.mjs +520 -0
- package/hooks/session-extract.bundle.mjs +3 -0
- package/hooks/session-helpers.mjs +428 -0
- package/hooks/session-loaders.mjs +448 -0
- package/hooks/session-snapshot.bundle.mjs +32 -0
- package/hooks/sessionstart.mjs +464 -0
- package/hooks/stop.mjs +82 -0
- package/hooks/suppress-stderr.mjs +25 -0
- package/hooks/userpromptsubmit.mjs +108 -0
- package/hooks/vscode-copilot/posttooluse.mjs +61 -0
- package/hooks/vscode-copilot/precompact.mjs +55 -0
- package/hooks/vscode-copilot/pretooluse.mjs +27 -0
- package/hooks/vscode-copilot/sessionstart.mjs +129 -0
- package/openclaw.plugin.json +38 -0
- package/package.json +132 -0
- package/scripts/heal-better-sqlite3.mjs +430 -0
- package/scripts/heal-installed-plugins.mjs +635 -0
- package/scripts/plugin-cache-integrity.mjs +248 -0
- package/scripts/postinstall.mjs +397 -0
- package/server.bundle.mjs +1421 -0
- package/server.js +5 -0
- package/skills/.ignore +7 -0
- package/skills/context-mode/SKILL.md +300 -0
- package/skills/context-mode/references/anti-patterns.md +283 -0
- package/skills/context-mode/references/patterns-javascript.md +298 -0
- package/skills/context-mode/references/patterns-python.md +304 -0
- package/skills/context-mode/references/patterns-shell.md +277 -0
- package/skills/ctx-doctor/SKILL.md +22 -0
- package/skills/ctx-index/SKILL.md +46 -0
- package/skills/ctx-insight/SKILL.md +24 -0
- package/skills/ctx-purge/SKILL.md +49 -0
- package/skills/ctx-search/SKILL.md +35 -0
- package/skills/ctx-stats/SKILL.md +26 -0
- package/skills/ctx-upgrade/SKILL.md +31 -0
- 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
|
+
}
|