context-mode 1.0.111 → 1.0.113
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 +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/.openclaw-plugin/index.ts +3 -2
- package/.openclaw-plugin/openclaw.plugin.json +1 -1
- package/.openclaw-plugin/package.json +1 -1
- package/README.md +152 -34
- package/bin/statusline.mjs +144 -127
- package/build/adapters/base.d.ts +8 -5
- package/build/adapters/base.js +8 -18
- package/build/adapters/claude-code/index.d.ts +24 -3
- package/build/adapters/claude-code/index.js +44 -11
- package/build/adapters/codex/hooks.d.ts +10 -5
- package/build/adapters/codex/hooks.js +10 -5
- package/build/adapters/codex/index.d.ts +17 -5
- package/build/adapters/codex/index.js +337 -37
- package/build/adapters/codex/paths.d.ts +1 -0
- package/build/adapters/codex/paths.js +12 -0
- package/build/adapters/cursor/index.d.ts +6 -0
- package/build/adapters/cursor/index.js +83 -2
- package/build/adapters/detect.d.ts +1 -1
- package/build/adapters/detect.js +29 -6
- package/build/adapters/omp/index.d.ts +65 -0
- package/build/adapters/omp/index.js +182 -0
- package/build/adapters/omp/plugin.d.ts +75 -0
- package/build/adapters/omp/plugin.js +220 -0
- package/build/adapters/openclaw/mcp-tools.d.ts +54 -0
- package/build/adapters/openclaw/mcp-tools.js +198 -0
- package/build/adapters/openclaw/plugin.d.ts +130 -0
- package/build/adapters/openclaw/plugin.js +629 -0
- package/build/adapters/openclaw/workspace-router.d.ts +29 -0
- package/build/adapters/openclaw/workspace-router.js +64 -0
- package/build/adapters/opencode/plugin.d.ts +145 -0
- package/build/adapters/opencode/plugin.js +457 -0
- package/build/adapters/pi/extension.d.ts +26 -0
- package/build/adapters/pi/extension.js +552 -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 +113 -0
- package/build/adapters/pi/mcp-bridge.js +251 -0
- package/build/adapters/types.d.ts +11 -6
- package/build/cli.js +186 -170
- package/build/db-base.d.ts +15 -2
- package/build/db-base.js +50 -5
- package/build/executor.d.ts +2 -0
- package/build/executor.js +15 -2
- package/build/runPool.d.ts +36 -0
- package/build/runPool.js +51 -0
- package/build/runtime.js +64 -5
- package/build/search/auto-memory.js +6 -4
- package/build/security.js +30 -10
- package/build/server.d.ts +23 -1
- package/build/server.js +662 -182
- package/build/session/analytics.d.ts +404 -1
- package/build/session/analytics.js +1347 -42
- package/build/session/db.d.ts +114 -5
- package/build/session/db.js +275 -27
- package/build/session/event-emit.d.ts +48 -0
- package/build/session/event-emit.js +101 -0
- package/build/session/extract.d.ts +1 -0
- package/build/session/extract.js +79 -12
- package/build/session/purge.d.ts +111 -0
- package/build/session/purge.js +138 -0
- package/build/store.d.ts +7 -0
- package/build/store.js +69 -6
- package/build/util/claude-config.d.ts +26 -0
- package/build/util/claude-config.js +91 -0
- package/build/util/hook-config.d.ts +4 -0
- package/build/util/hook-config.js +39 -0
- package/build/util/project-dir.d.ts +49 -0
- package/build/util/project-dir.js +67 -0
- package/cli.bundle.mjs +411 -208
- package/configs/antigravity/GEMINI.md +0 -3
- package/configs/claude-code/CLAUDE.md +1 -4
- package/configs/codex/AGENTS.md +1 -4
- package/configs/codex/config.toml +3 -0
- package/configs/codex/hooks.json +8 -0
- package/configs/cursor/context-mode.mdc +0 -3
- package/configs/gemini-cli/GEMINI.md +0 -3
- package/configs/jetbrains-copilot/copilot-instructions.md +0 -3
- package/configs/kilo/AGENTS.md +0 -3
- package/configs/kiro/KIRO.md +0 -3
- package/configs/omp/SYSTEM.md +85 -0
- package/configs/omp/mcp.json +7 -0
- package/configs/openclaw/AGENTS.md +0 -3
- package/configs/opencode/AGENTS.md +0 -3
- package/configs/pi/AGENTS.md +0 -3
- package/configs/qwen-code/QWEN.md +1 -4
- package/configs/vscode-copilot/copilot-instructions.md +0 -3
- package/configs/zed/AGENTS.md +0 -3
- package/hooks/codex/posttooluse.mjs +9 -2
- package/hooks/codex/precompact.mjs +69 -0
- package/hooks/codex/sessionstart.mjs +13 -9
- package/hooks/codex/stop.mjs +1 -2
- package/hooks/codex/userpromptsubmit.mjs +1 -2
- package/hooks/core/routing.mjs +237 -18
- package/hooks/cursor/afteragentresponse.mjs +1 -1
- package/hooks/cursor/hooks.json +31 -0
- package/hooks/cursor/posttooluse.mjs +1 -1
- package/hooks/cursor/sessionstart.mjs +5 -5
- package/hooks/cursor/stop.mjs +1 -1
- package/hooks/ensure-deps.mjs +12 -13
- package/hooks/gemini-cli/aftertool.mjs +1 -1
- package/hooks/gemini-cli/beforeagent.mjs +1 -1
- package/hooks/gemini-cli/precompress.mjs +3 -2
- package/hooks/gemini-cli/sessionstart.mjs +9 -9
- package/hooks/jetbrains-copilot/posttooluse.mjs +1 -1
- package/hooks/jetbrains-copilot/precompact.mjs +3 -2
- package/hooks/jetbrains-copilot/sessionstart.mjs +9 -9
- package/hooks/kiro/agentspawn.mjs +5 -5
- package/hooks/kiro/posttooluse.mjs +2 -2
- package/hooks/kiro/userpromptsubmit.mjs +1 -1
- package/hooks/posttooluse.mjs +45 -0
- package/hooks/precompact.mjs +17 -0
- package/hooks/pretooluse.mjs +23 -0
- package/hooks/routing-block.mjs +0 -12
- package/hooks/run-hook.mjs +16 -3
- package/hooks/session-db.bundle.mjs +27 -18
- package/hooks/session-extract.bundle.mjs +2 -2
- package/hooks/session-helpers.mjs +101 -64
- package/hooks/sessionstart.mjs +51 -2
- package/hooks/vscode-copilot/posttooluse.mjs +1 -1
- package/hooks/vscode-copilot/precompact.mjs +3 -2
- package/hooks/vscode-copilot/sessionstart.mjs +9 -9
- package/openclaw.plugin.json +1 -1
- package/package.json +14 -8
- package/server.bundle.mjs +349 -147
- package/start.mjs +16 -4
- package/skills/UPSTREAM-CREDITS.md +0 -51
- package/skills/context-mode-ops/SKILL.md +0 -299
- package/skills/context-mode-ops/agent-teams.md +0 -198
- package/skills/context-mode-ops/communication.md +0 -224
- package/skills/context-mode-ops/marketing.md +0 -124
- package/skills/context-mode-ops/release.md +0 -214
- package/skills/context-mode-ops/review-pr.md +0 -269
- package/skills/context-mode-ops/tdd.md +0 -329
- package/skills/context-mode-ops/triage-issue.md +0 -266
- package/skills/context-mode-ops/validation.md +0 -307
- package/skills/diagnose/SKILL.md +0 -122
- package/skills/diagnose/scripts/hitl-loop.template.sh +0 -41
- package/skills/grill-me/SKILL.md +0 -15
- package/skills/grill-with-docs/ADR-FORMAT.md +0 -47
- package/skills/grill-with-docs/CONTEXT-FORMAT.md +0 -77
- package/skills/grill-with-docs/SKILL.md +0 -93
- package/skills/improve-codebase-architecture/DEEPENING.md +0 -37
- package/skills/improve-codebase-architecture/INTERFACE-DESIGN.md +0 -44
- package/skills/improve-codebase-architecture/LANGUAGE.md +0 -53
- package/skills/improve-codebase-architecture/SKILL.md +0 -76
- package/skills/tdd/SKILL.md +0 -114
- package/skills/tdd/deep-modules.md +0 -33
- package/skills/tdd/interface-design.md +0 -31
- package/skills/tdd/mocking.md +0 -59
- package/skills/tdd/refactoring.md +0 -10
- package/skills/tdd/tests.md +0 -61
package/build/cli.js
CHANGED
|
@@ -20,6 +20,22 @@ import { resolve, dirname, join } from "node:path";
|
|
|
20
20
|
import { tmpdir, devNull, homedir } from "node:os";
|
|
21
21
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
22
22
|
import { detectRuntimes, getRuntimeSummary, hasBunRuntime, getAvailableLanguages, } from "./runtime.js";
|
|
23
|
+
import { getHookScriptPaths } from "./util/hook-config.js";
|
|
24
|
+
import { resolveClaudeConfigDir } from "./util/claude-config.js";
|
|
25
|
+
// Private 16-LOC copy of browserOpenArgv. Canonical version lives in src/server.ts;
|
|
26
|
+
// duplicated here so the cli bundle does not pull server.ts top-level boot side effects.
|
|
27
|
+
// Keep in sync — pure data, no I/O.
|
|
28
|
+
function browserOpenArgv(url, platform) {
|
|
29
|
+
if (platform === "darwin")
|
|
30
|
+
return [{ cmd: "open", args: [url] }];
|
|
31
|
+
if (platform === "win32") {
|
|
32
|
+
return [{ cmd: "cmd", args: ["/c", "start", "", url] }];
|
|
33
|
+
}
|
|
34
|
+
return [
|
|
35
|
+
{ cmd: "xdg-open", args: [url] },
|
|
36
|
+
{ cmd: "sensible-browser", args: [url] },
|
|
37
|
+
];
|
|
38
|
+
}
|
|
23
39
|
// ── Adapter imports ──────────────────────────────────────
|
|
24
40
|
import { detectPlatform, getAdapter } from "./adapters/detect.js";
|
|
25
41
|
/* -------------------------------------------------------
|
|
@@ -55,6 +71,7 @@ const HOOK_MAP = {
|
|
|
55
71
|
"codex": {
|
|
56
72
|
pretooluse: "hooks/codex/pretooluse.mjs",
|
|
57
73
|
posttooluse: "hooks/codex/posttooluse.mjs",
|
|
74
|
+
precompact: "hooks/codex/precompact.mjs",
|
|
58
75
|
sessionstart: "hooks/codex/sessionstart.mjs",
|
|
59
76
|
userpromptsubmit: "hooks/codex/userpromptsubmit.mjs",
|
|
60
77
|
stop: "hooks/codex/stop.mjs",
|
|
@@ -104,7 +121,11 @@ if (args[0] === "doctor") {
|
|
|
104
121
|
doctor().then((code) => process.exit(code));
|
|
105
122
|
}
|
|
106
123
|
else if (args[0] === "upgrade") {
|
|
107
|
-
upgrade()
|
|
124
|
+
upgrade().catch((err) => {
|
|
125
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
126
|
+
p.log.error(color.red(message));
|
|
127
|
+
process.exit(1);
|
|
128
|
+
});
|
|
108
129
|
}
|
|
109
130
|
else if (args[0] === "hook") {
|
|
110
131
|
hookDispatch(args[1], args[2]);
|
|
@@ -151,33 +172,20 @@ export function npmExec(command, opts = {}) {
|
|
|
151
172
|
export function openInBrowser(url, platform = process.platform, runner = nodeExecFile) {
|
|
152
173
|
const opts = { stdio: "ignore" };
|
|
153
174
|
const hint = () => console.error(`\nCould not auto-open browser. Open manually: ${url}`);
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
else {
|
|
164
|
-
// linux/bsd: try xdg-open, fall back to sensible-browser.
|
|
165
|
-
try {
|
|
166
|
-
runner("xdg-open", [url], opts);
|
|
167
|
-
}
|
|
168
|
-
catch {
|
|
169
|
-
try {
|
|
170
|
-
runner("sensible-browser", [url], opts);
|
|
171
|
-
}
|
|
172
|
-
catch {
|
|
173
|
-
hint();
|
|
174
|
-
}
|
|
175
|
-
}
|
|
175
|
+
// Platform→argv mapping is canonical in src/server.ts; mirrored privately
|
|
176
|
+
// above to avoid pulling server boot side effects into the cli bundle.
|
|
177
|
+
const attempts = browserOpenArgv(url, platform);
|
|
178
|
+
let opened = false;
|
|
179
|
+
for (const { cmd, args } of attempts) {
|
|
180
|
+
try {
|
|
181
|
+
runner(cmd, args, opts);
|
|
182
|
+
opened = true;
|
|
183
|
+
break;
|
|
176
184
|
}
|
|
185
|
+
catch { /* try next fallback */ }
|
|
177
186
|
}
|
|
178
|
-
|
|
187
|
+
if (!opened)
|
|
179
188
|
hint();
|
|
180
|
-
}
|
|
181
189
|
}
|
|
182
190
|
function defaultPluginRoot() {
|
|
183
191
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -331,22 +339,35 @@ async function doctor() {
|
|
|
331
339
|
if (result.status === "pass") {
|
|
332
340
|
p.log.success(color.green(`${result.check}: PASS`) + ` — ${result.message}`);
|
|
333
341
|
}
|
|
342
|
+
else if (result.status === "warn") {
|
|
343
|
+
p.log.warn(color.yellow(`${result.check}: WARN`) +
|
|
344
|
+
` — ${result.message}` +
|
|
345
|
+
(result.fix ? color.dim(`\n Run: ${result.fix}`) : ""));
|
|
346
|
+
}
|
|
334
347
|
else {
|
|
335
348
|
p.log.error(color.red(`${result.check}: FAIL`) +
|
|
336
349
|
` — ${result.message}` +
|
|
337
350
|
(result.fix ? color.dim(`\n Run: ${result.fix}`) : ""));
|
|
338
351
|
}
|
|
339
352
|
}
|
|
340
|
-
// Hook
|
|
341
|
-
p.log.step("Checking hook
|
|
342
|
-
const
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
p.log.success(color.green("Hook script exists: PASS") + color.dim(` — ${hookScriptPath}`));
|
|
353
|
+
// Hook scripts exist
|
|
354
|
+
p.log.step("Checking hook scripts...");
|
|
355
|
+
const hookScriptPaths = getHookScriptPaths(adapter, pluginRoot);
|
|
356
|
+
if (hookScriptPaths.length === 0) {
|
|
357
|
+
p.log.success(color.green("Hook scripts: PASS") + color.dim(" — no direct .mjs script paths to verify"));
|
|
346
358
|
}
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
359
|
+
else {
|
|
360
|
+
for (const scriptPath of hookScriptPaths) {
|
|
361
|
+
const absolutePath = resolve(pluginRoot, scriptPath);
|
|
362
|
+
try {
|
|
363
|
+
accessSync(absolutePath, constants.R_OK);
|
|
364
|
+
p.log.success(color.green("Hook script exists: PASS") + color.dim(` — ${absolutePath}`));
|
|
365
|
+
}
|
|
366
|
+
catch {
|
|
367
|
+
p.log.error(color.red("Hook script exists: FAIL") +
|
|
368
|
+
color.dim(` — not found at ${absolutePath}`));
|
|
369
|
+
}
|
|
370
|
+
}
|
|
350
371
|
}
|
|
351
372
|
// Plugin registration — adapter-aware
|
|
352
373
|
p.log.step(`Checking ${adapter.name} plugin registration...`);
|
|
@@ -568,7 +589,9 @@ async function upgrade() {
|
|
|
568
589
|
// commit and CC keeps reporting the old version even after our cache dir is
|
|
569
590
|
// updated — users then see "ctx-upgrade succeeded" but nothing actually
|
|
570
591
|
// changed at the plugin-system level.
|
|
571
|
-
|
|
592
|
+
// Issue #460 round-3: route through resolveClaudeConfigDir so users who
|
|
593
|
+
// relocate their CC config root keep the marketplace clone in the same tree.
|
|
594
|
+
const marketplaceDir = resolve(resolveClaudeConfigDir(), "plugins", "marketplaces", "context-mode");
|
|
572
595
|
if (existsSync(join(marketplaceDir, ".git"))) {
|
|
573
596
|
s.start("Syncing marketplace clone");
|
|
574
597
|
try {
|
|
@@ -606,156 +629,140 @@ async function upgrade() {
|
|
|
606
629
|
if (newVersion === localVersion) {
|
|
607
630
|
p.log.success(color.green("Already on latest") + ` — v${localVersion}`);
|
|
608
631
|
rmSync(tmpDir, { recursive: true, force: true });
|
|
609
|
-
return;
|
|
610
632
|
}
|
|
611
633
|
else {
|
|
612
634
|
p.log.info(`Update available: ${color.yellow("v" + localVersion)} → ${color.green("v" + newVersion)}`);
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
635
|
+
// Step 2: Install dependencies + build
|
|
636
|
+
s.start("Installing dependencies & building");
|
|
637
|
+
npmExecFile(["install", "--no-audit", "--no-fund"], {
|
|
638
|
+
cwd: srcDir,
|
|
639
|
+
stdio: "pipe",
|
|
640
|
+
timeout: 120000,
|
|
641
|
+
});
|
|
642
|
+
npmExecFile(["run", "build"], {
|
|
643
|
+
cwd: srcDir,
|
|
644
|
+
stdio: "pipe",
|
|
645
|
+
timeout: 60000,
|
|
646
|
+
});
|
|
647
|
+
s.stop("Built successfully");
|
|
648
|
+
// Step 3: Update in-place
|
|
649
|
+
s.start("Updating files in-place");
|
|
650
|
+
// Old version dirs are cleaned lazily by sessionstart.mjs (age-gated >1h)
|
|
651
|
+
// to avoid breaking active sessions that still reference them (#181).
|
|
652
|
+
// Read files list from cloned repo's package.json so new directories
|
|
653
|
+
// (like insight/) are automatically included without chicken-and-egg issues
|
|
654
|
+
// where the old CLI doesn't know about new directories.
|
|
655
|
+
const clonedPkg = JSON.parse(readFileSync(resolve(srcDir, "package.json"), "utf-8"));
|
|
656
|
+
const items = [
|
|
657
|
+
...(clonedPkg.files || []),
|
|
658
|
+
"src", "package.json",
|
|
659
|
+
];
|
|
660
|
+
for (const item of items) {
|
|
661
|
+
try {
|
|
662
|
+
rmSync(resolve(pluginRoot, item), { recursive: true, force: true });
|
|
663
|
+
cpSync(resolve(srcDir, item), resolve(pluginRoot, item), { recursive: true });
|
|
664
|
+
}
|
|
665
|
+
catch { /* some files may not exist in source */ }
|
|
643
666
|
}
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
args: ["${CLAUDE_PLUGIN_ROOT}/start.mjs"],
|
|
667
|
+
// Write .mcp.json with CLAUDE_PLUGIN_ROOT placeholder (fixes #411).
|
|
668
|
+
// Absolute paths bake-in the current pluginRoot dir, which sessionstart.mjs
|
|
669
|
+
// (#181) deletes after upgrade — breaking MCP server resolution. The literal
|
|
670
|
+
// ${CLAUDE_PLUGIN_ROOT} placeholder is resolved by Claude at load-time and
|
|
671
|
+
// stays valid across version cleanups. Matches .claude-plugin/plugin.json.
|
|
672
|
+
const mcpConfig = {
|
|
673
|
+
mcpServers: {
|
|
674
|
+
"context-mode": {
|
|
675
|
+
command: "node",
|
|
676
|
+
args: ["${CLAUDE_PLUGIN_ROOT}/start.mjs"],
|
|
677
|
+
},
|
|
656
678
|
},
|
|
657
|
-
}
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
// already present. Earlier code ran `npm rebuild better-sqlite3`
|
|
678
|
-
// unconditionally — its internal prebuild-install spawn raced with
|
|
679
|
-
// the prior install's tree-prune, intermittently failing to resolve
|
|
680
|
-
// `rc/index.js` and printing a scary "rebuild warning" even though
|
|
681
|
-
// the binding was healthy. Pre-check eliminates the race for the
|
|
682
|
-
// 99% case (binding survived install).
|
|
683
|
-
if (existsSync(bsqBindingPath)) {
|
|
684
|
-
s.stop(color.green("Native addons OK") + color.dim(" — binding present"));
|
|
685
|
-
changes.push("better-sqlite3 binding already present (no rebuild needed)");
|
|
686
|
-
}
|
|
687
|
-
else {
|
|
688
|
-
// Binding actually missing — delegate to the shared 3-layer heal
|
|
689
|
-
// (scripts/heal-better-sqlite3.mjs, PR #410) instead of raw
|
|
690
|
-
// `npm rebuild`. Single source of truth across postinstall +
|
|
691
|
-
// ensure-deps + cli upgrade. Layer A spawns prebuild-install
|
|
692
|
-
// directly via process.execPath, bypassing PATH/MSVC and the
|
|
693
|
-
// npm-internal rc-resolution race that bit `npm rebuild`.
|
|
679
|
+
};
|
|
680
|
+
writeFileSync(resolve(pluginRoot, ".mcp.json"), JSON.stringify(mcpConfig, null, 2) + "\n");
|
|
681
|
+
s.stop(color.green(`Updated in-place to v${newVersion}`));
|
|
682
|
+
// Fix registry — adapter-aware
|
|
683
|
+
adapter.updatePluginRegistry(pluginRoot, newVersion);
|
|
684
|
+
p.log.info(color.dim(" Registry synced to " + pluginRoot));
|
|
685
|
+
// Install production deps
|
|
686
|
+
s.start("Installing production dependencies");
|
|
687
|
+
npmExecFile(["install", "--production", "--no-audit", "--no-fund"], {
|
|
688
|
+
cwd: pluginRoot,
|
|
689
|
+
stdio: "pipe",
|
|
690
|
+
timeout: 60000,
|
|
691
|
+
});
|
|
692
|
+
s.stop("Dependencies ready");
|
|
693
|
+
if (detection.platform !== 'opencode' && detection.platform !== 'kilo') {
|
|
694
|
+
// Verify native addons through the same bootstrap start.mjs imports.
|
|
695
|
+
// On modern Node, the ABI-specific cache file is the compatibility marker;
|
|
696
|
+
// the active binding alone may be stale from a previous Node ABI.
|
|
697
|
+
s.start("Verifying native addon ABI");
|
|
698
|
+
const bsqAbiCachePath = resolve(pluginRoot, "node_modules", "better-sqlite3", "build", "Release", `better_sqlite3.abi${process.versions.modules}.node`);
|
|
694
699
|
try {
|
|
695
|
-
const
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
700
|
+
const ensureDepsPath = resolve(pluginRoot, "hooks", "ensure-deps.mjs");
|
|
701
|
+
if (!existsSync(ensureDepsPath)) {
|
|
702
|
+
throw new Error(`missing ${ensureDepsPath}`);
|
|
703
|
+
}
|
|
704
|
+
await import(`${pathToFileURL(ensureDepsPath).href}?upgrade=${Date.now()}`);
|
|
705
|
+
if (existsSync(bsqAbiCachePath)) {
|
|
706
|
+
s.stop(color.green("Native addons OK") + color.dim(" — ABI cache present"));
|
|
707
|
+
changes.push(`better-sqlite3 ABI ${process.versions.modules} cache ready`);
|
|
701
708
|
}
|
|
702
709
|
else {
|
|
703
|
-
s.stop(color.yellow("Native addon
|
|
704
|
-
p.log.warn(color.dim(`
|
|
710
|
+
s.stop(color.yellow("Native addon ABI cache missing"));
|
|
711
|
+
p.log.warn(color.dim(` Try manually: cd "${pluginRoot}" && npm rebuild better-sqlite3`));
|
|
705
712
|
}
|
|
706
713
|
}
|
|
707
714
|
catch (err) {
|
|
708
715
|
const message = err instanceof Error ? err.message : String(err);
|
|
709
|
-
s.stop(color.yellow("Native addon
|
|
710
|
-
p.log.warn(color.yellow("better-sqlite3
|
|
716
|
+
s.stop(color.yellow("Native addon ABI bootstrap unavailable"));
|
|
717
|
+
p.log.warn(color.yellow("better-sqlite3 ABI repair did not run") +
|
|
711
718
|
` — ${message}` +
|
|
712
719
|
color.dim(`\n Try manually: cd "${pluginRoot}" && npm rebuild better-sqlite3`));
|
|
713
720
|
}
|
|
714
721
|
}
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
const
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
722
|
+
// Update global npm
|
|
723
|
+
s.start("Updating npm global package");
|
|
724
|
+
try {
|
|
725
|
+
npmExecFile(["install", "-g", pluginRoot, "--no-audit", "--no-fund"], {
|
|
726
|
+
stdio: "pipe",
|
|
727
|
+
timeout: 30000,
|
|
728
|
+
});
|
|
729
|
+
s.stop(color.green("npm global updated"));
|
|
730
|
+
changes.push("Updated npm global package");
|
|
731
|
+
}
|
|
732
|
+
catch {
|
|
733
|
+
s.stop(color.yellow("npm global update skipped"));
|
|
734
|
+
p.log.info(color.dim(" Could not update global npm — may need sudo or standalone install"));
|
|
735
|
+
}
|
|
736
|
+
// Cleanup
|
|
737
|
+
rmSync(tmpDir, { recursive: true, force: true });
|
|
738
|
+
// Sync skills to the active install path from installed_plugins.json (#228).
|
|
739
|
+
// Only targets the ACTUAL directory Claude Code reads from — not spraying everywhere.
|
|
740
|
+
// Issue #460 round-3: honor $CLAUDE_CONFIG_DIR so the registry lookup
|
|
741
|
+
// tracks relocated CC config trees.
|
|
742
|
+
try {
|
|
743
|
+
const registryPath = resolve(resolveClaudeConfigDir(), "plugins", "installed_plugins.json");
|
|
744
|
+
if (existsSync(registryPath)) {
|
|
745
|
+
const registry = JSON.parse(readFileSync(registryPath, "utf-8"));
|
|
746
|
+
const entries = registry?.plugins?.["context-mode@context-mode"];
|
|
747
|
+
if (Array.isArray(entries)) {
|
|
748
|
+
for (const entry of entries) {
|
|
749
|
+
const installPath = entry.installPath;
|
|
750
|
+
if (installPath && installPath !== pluginRoot && existsSync(installPath)) {
|
|
751
|
+
const srcSkills = resolve(srcDir, "skills");
|
|
752
|
+
if (existsSync(srcSkills)) {
|
|
753
|
+
cpSync(srcSkills, resolve(installPath, "skills"), { recursive: true });
|
|
754
|
+
changes.push(`Synced skills to active install path`);
|
|
755
|
+
}
|
|
747
756
|
}
|
|
748
757
|
}
|
|
749
758
|
}
|
|
750
759
|
}
|
|
751
760
|
}
|
|
761
|
+
catch { /* best effort — registry may not exist or be malformed */ }
|
|
762
|
+
changes.push(`Updated v${localVersion} → v${newVersion}`);
|
|
763
|
+
p.log.success(color.green("Plugin reinstalled from GitHub!") +
|
|
764
|
+
color.dim(` — v${newVersion}`));
|
|
752
765
|
}
|
|
753
|
-
catch { /* best effort — registry may not exist or be malformed */ }
|
|
754
|
-
changes.push(newVersion !== localVersion
|
|
755
|
-
? `Updated v${localVersion} → v${newVersion}`
|
|
756
|
-
: `Reinstalled v${localVersion} from GitHub`);
|
|
757
|
-
p.log.success(color.green("Plugin reinstalled from GitHub!") +
|
|
758
|
-
color.dim(` — v${newVersion}`));
|
|
759
766
|
}
|
|
760
767
|
catch (err) {
|
|
761
768
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -783,12 +790,18 @@ async function upgrade() {
|
|
|
783
790
|
}
|
|
784
791
|
// Step 4: Configure hooks — adapter-aware
|
|
785
792
|
p.log.step(`Configuring ${adapter.name} hooks...`);
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
793
|
+
try {
|
|
794
|
+
const hookChanges = adapter.configureAllHooks(pluginRoot);
|
|
795
|
+
for (const change of hookChanges) {
|
|
796
|
+
p.log.info(color.dim(` ${change}`));
|
|
797
|
+
changes.push(change);
|
|
798
|
+
}
|
|
799
|
+
p.log.success(color.green("Hooks configured") + color.dim(` — ${adapter.name}`));
|
|
800
|
+
}
|
|
801
|
+
catch (err) {
|
|
802
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
803
|
+
throw new Error(`Hook configuration failed: ${message}`);
|
|
790
804
|
}
|
|
791
|
-
p.log.success(color.green("Hooks configured") + color.dim(` — ${adapter.name}`));
|
|
792
805
|
// Step 5: Set hook script permissions — adapter-aware
|
|
793
806
|
p.log.step("Setting hook script permissions...");
|
|
794
807
|
const permSet = adapter.setHookPermissions(pluginRoot);
|
|
@@ -854,14 +867,17 @@ function statuslineForward() {
|
|
|
854
867
|
// marketplace clone (#418-synced, stable across upgrades) and to the path
|
|
855
868
|
// Claude Code itself loads from (installed_plugins.json) keeps the bar
|
|
856
869
|
// alive instead of silently going blank.
|
|
870
|
+
// Issue #460 round-3: marketplace + registry paths must follow
|
|
871
|
+
// $CLAUDE_CONFIG_DIR so relocated CC trees still find the statusline binary.
|
|
872
|
+
const claudeRoot = resolveClaudeConfigDir();
|
|
857
873
|
const candidates = [
|
|
858
874
|
resolve(getPluginRoot(), "bin", "statusline.mjs"),
|
|
859
|
-
resolve(
|
|
875
|
+
resolve(claudeRoot, "plugins", "marketplaces", "context-mode", "bin", "statusline.mjs"),
|
|
860
876
|
];
|
|
861
877
|
// installed_plugins.json may list one or more install paths CC actually
|
|
862
878
|
// loads from. Prefer those if they exist.
|
|
863
879
|
try {
|
|
864
|
-
const registryPath = resolve(
|
|
880
|
+
const registryPath = resolve(claudeRoot, "plugins", "installed_plugins.json");
|
|
865
881
|
if (existsSync(registryPath)) {
|
|
866
882
|
const registry = JSON.parse(readFileSync(registryPath, "utf-8"));
|
|
867
883
|
const entries = registry?.plugins?.["context-mode@context-mode"];
|
package/build/db-base.d.ts
CHANGED
|
@@ -48,11 +48,24 @@ export declare class NodeSQLiteAdapter {
|
|
|
48
48
|
transaction(fn: (...args: any[]) => any): any;
|
|
49
49
|
close(): void;
|
|
50
50
|
}
|
|
51
|
+
/**
|
|
52
|
+
* Probe whether the supplied node:sqlite DatabaseSync constructor links a
|
|
53
|
+
* SQLite build that includes the FTS5 module. Some Node.js Linux builds
|
|
54
|
+
* (e.g. v22.14.0 on Ubuntu) ship node:sqlite without FTS5 even though the
|
|
55
|
+
* import succeeds, which silently breaks ctx_search/ctx_batch_execute and
|
|
56
|
+
* the doctor's FTS5 check (issue #461).
|
|
57
|
+
*
|
|
58
|
+
* Returns true only when a `CREATE VIRTUAL TABLE … USING fts5(x)` statement
|
|
59
|
+
* succeeds. Always returns false on any failure (constructor throw, missing
|
|
60
|
+
* module, etc.) so the caller can fall through to better-sqlite3, whose
|
|
61
|
+
* bundled SQLite always ships with FTS5.
|
|
62
|
+
*/
|
|
63
|
+
export declare function nodeSqliteHasFts5(DatabaseSync: any): boolean;
|
|
51
64
|
/**
|
|
52
65
|
* Lazy-load the SQLite driver for the current runtime.
|
|
53
66
|
* Bun → bun:sqlite via BunSQLiteAdapter (issue #45).
|
|
54
|
-
* Linux Node → node:sqlite via NodeSQLiteAdapter (
|
|
55
|
-
* Other Node → better-sqlite3 (native addon).
|
|
67
|
+
* Linux Node → node:sqlite via NodeSQLiteAdapter when it ships FTS5 (#228, #461).
|
|
68
|
+
* Other Node (or Linux Node without FTS5) → better-sqlite3 (native addon).
|
|
56
69
|
*/
|
|
57
70
|
export declare function loadDatabase(): typeof DatabaseConstructor;
|
|
58
71
|
/**
|
package/build/db-base.js
CHANGED
|
@@ -155,11 +155,40 @@ export class NodeSQLiteAdapter {
|
|
|
155
155
|
// Lazy loader
|
|
156
156
|
// ─────────────────────────────────────────────────────────
|
|
157
157
|
let _Database = null;
|
|
158
|
+
/**
|
|
159
|
+
* Probe whether the supplied node:sqlite DatabaseSync constructor links a
|
|
160
|
+
* SQLite build that includes the FTS5 module. Some Node.js Linux builds
|
|
161
|
+
* (e.g. v22.14.0 on Ubuntu) ship node:sqlite without FTS5 even though the
|
|
162
|
+
* import succeeds, which silently breaks ctx_search/ctx_batch_execute and
|
|
163
|
+
* the doctor's FTS5 check (issue #461).
|
|
164
|
+
*
|
|
165
|
+
* Returns true only when a `CREATE VIRTUAL TABLE … USING fts5(x)` statement
|
|
166
|
+
* succeeds. Always returns false on any failure (constructor throw, missing
|
|
167
|
+
* module, etc.) so the caller can fall through to better-sqlite3, whose
|
|
168
|
+
* bundled SQLite always ships with FTS5.
|
|
169
|
+
*/
|
|
170
|
+
export function nodeSqliteHasFts5(DatabaseSync) {
|
|
171
|
+
let probe = null;
|
|
172
|
+
try {
|
|
173
|
+
probe = new DatabaseSync(":memory:");
|
|
174
|
+
probe.exec("CREATE VIRTUAL TABLE __fts5_probe USING fts5(x)");
|
|
175
|
+
return true;
|
|
176
|
+
}
|
|
177
|
+
catch {
|
|
178
|
+
return false;
|
|
179
|
+
}
|
|
180
|
+
finally {
|
|
181
|
+
try {
|
|
182
|
+
probe?.close();
|
|
183
|
+
}
|
|
184
|
+
catch { /* probe never opened or already closed */ }
|
|
185
|
+
}
|
|
186
|
+
}
|
|
158
187
|
/**
|
|
159
188
|
* Lazy-load the SQLite driver for the current runtime.
|
|
160
189
|
* Bun → bun:sqlite via BunSQLiteAdapter (issue #45).
|
|
161
|
-
* Linux Node → node:sqlite via NodeSQLiteAdapter (
|
|
162
|
-
* Other Node → better-sqlite3 (native addon).
|
|
190
|
+
* Linux Node → node:sqlite via NodeSQLiteAdapter when it ships FTS5 (#228, #461).
|
|
191
|
+
* Other Node (or Linux Node without FTS5) → better-sqlite3 (native addon).
|
|
163
192
|
*/
|
|
164
193
|
export function loadDatabase() {
|
|
165
194
|
if (!_Database) {
|
|
@@ -185,8 +214,20 @@ export function loadDatabase() {
|
|
|
185
214
|
else if (process.platform === "linux") {
|
|
186
215
|
// Linux — try node:sqlite to avoid native addon SIGSEGV (nodejs/node#62515).
|
|
187
216
|
// node:sqlite is built into Node >= 22.5, no flag needed since 22.13.
|
|
217
|
+
// Probe FTS5 support before committing — some Linux Node builds ship
|
|
218
|
+
// node:sqlite without FTS5, which would silently break ctx_search (#461).
|
|
219
|
+
// The probe runs at most once per process (cached via _Database below),
|
|
220
|
+
// so the cost of opening an in-memory DatabaseSync is negligible.
|
|
221
|
+
let DatabaseSync = null;
|
|
188
222
|
try {
|
|
189
|
-
|
|
223
|
+
// Array.join() prevents esbuild from resolving the specifier at bundle time
|
|
224
|
+
// (mirrors the bun:sqlite branch above).
|
|
225
|
+
({ DatabaseSync } = require(["node", "sqlite"].join(":")));
|
|
226
|
+
}
|
|
227
|
+
catch {
|
|
228
|
+
DatabaseSync = null;
|
|
229
|
+
}
|
|
230
|
+
if (DatabaseSync && nodeSqliteHasFts5(DatabaseSync)) {
|
|
190
231
|
_Database = function NodeDatabaseFactory(path, opts) {
|
|
191
232
|
const raw = new DatabaseSync(path, {
|
|
192
233
|
readOnly: opts?.readonly ?? false,
|
|
@@ -194,8 +235,12 @@ export function loadDatabase() {
|
|
|
194
235
|
return new NodeSQLiteAdapter(raw);
|
|
195
236
|
};
|
|
196
237
|
}
|
|
197
|
-
|
|
198
|
-
// node:sqlite
|
|
238
|
+
else {
|
|
239
|
+
// node:sqlite missing or built without FTS5 — fall through to better-sqlite3.
|
|
240
|
+
// Trade-off: this reintroduces the native-addon path that #228 routed
|
|
241
|
+
// around (Linux SIGSEGV per nodejs/node#62515). Deliberate — a visible
|
|
242
|
+
// crash on the rare unstable build is preferable to a silent
|
|
243
|
+
// "no such module: fts5" on every ctx_search call.
|
|
199
244
|
_Database = require("better-sqlite3");
|
|
200
245
|
}
|
|
201
246
|
}
|
package/build/executor.d.ts
CHANGED
|
@@ -11,6 +11,8 @@ export declare function buildScriptFilename(language: Language, platform: NodeJS
|
|
|
11
11
|
export declare function buildSpawnOptions(platform: NodeJS.Platform): {
|
|
12
12
|
windowsHide: boolean;
|
|
13
13
|
};
|
|
14
|
+
/** Pure helper — exported for unit testing. Restores parent PATH after shell startup. */
|
|
15
|
+
export declare function buildShellScriptContent(code: string, inheritedPath: string | undefined, platform: NodeJS.Platform): string;
|
|
14
16
|
interface ExecuteOptions {
|
|
15
17
|
language: Language;
|
|
16
18
|
code: string;
|
package/build/executor.js
CHANGED
|
@@ -42,6 +42,15 @@ export function buildScriptFilename(language, platform, shellPath) {
|
|
|
42
42
|
export function buildSpawnOptions(platform) {
|
|
43
43
|
return { windowsHide: platform === "win32" };
|
|
44
44
|
}
|
|
45
|
+
function quoteForPosixShell(value) {
|
|
46
|
+
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
47
|
+
}
|
|
48
|
+
/** Pure helper — exported for unit testing. Restores parent PATH after shell startup. */
|
|
49
|
+
export function buildShellScriptContent(code, inheritedPath, platform) {
|
|
50
|
+
if (platform === "win32" || !inheritedPath)
|
|
51
|
+
return code;
|
|
52
|
+
return `export PATH=${quoteForPosixShell(inheritedPath)}\n${code}`;
|
|
53
|
+
}
|
|
45
54
|
/**
|
|
46
55
|
* Resolve the real OS temp directory, bypassing any TMPDIR env override.
|
|
47
56
|
* os.tmpdir() reads TMPDIR from the environment, which some shells/tools
|
|
@@ -173,7 +182,7 @@ export class PolyglotExecutor {
|
|
|
173
182
|
}
|
|
174
183
|
const fp = join(tmpDir, buildScriptFilename(language, process.platform, language === "shell" ? this.#runtimes.shell : null));
|
|
175
184
|
if (language === "shell") {
|
|
176
|
-
writeFileSync(fp, code, { encoding: "utf-8", mode: 0o700 });
|
|
185
|
+
writeFileSync(fp, buildShellScriptContent(code, process.env.PATH, process.platform), { encoding: "utf-8", mode: 0o700 });
|
|
177
186
|
}
|
|
178
187
|
else {
|
|
179
188
|
writeFileSync(fp, code, "utf-8");
|
|
@@ -210,7 +219,11 @@ export class PolyglotExecutor {
|
|
|
210
219
|
return new Promise((res) => {
|
|
211
220
|
// Only .cmd/.bat shims need shell on Windows; real executables don't.
|
|
212
221
|
// Using shell: true globally causes process-tree kill issues with MSYS2/Git Bash.
|
|
213
|
-
|
|
222
|
+
// "bun" is included as defense-in-depth: bunCommand() prefers absolute
|
|
223
|
+
// .exe paths now (#506), but if it falls back to the bare "bun" string
|
|
224
|
+
// on Windows that resolution typically goes through a `bun.cmd` shim
|
|
225
|
+
// (npm i -g bun) which CreateProcess can't execute without cmd.exe.
|
|
226
|
+
const needsShell = isWin && ["tsx", "ts-node", "elixir", "bun"].includes(cmd[0]);
|
|
214
227
|
// On Windows with Git Bash, pass the script as `bash -c "source /posix/path"`
|
|
215
228
|
// rather than `bash /path/to/script.sh`. This avoids MSYS2 path mangling
|
|
216
229
|
// while still allowing MSYS_NO_PATHCONV to protect non-ASCII paths in commands.
|