context-mode 1.0.89 → 1.0.90
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/openclaw.plugin.json +1 -1
- package/.openclaw-plugin/package.json +1 -1
- package/README.md +184 -60
- package/build/adapters/antigravity/index.d.ts +3 -5
- package/build/adapters/antigravity/index.js +7 -35
- package/build/adapters/base.d.ts +27 -0
- package/build/adapters/base.js +59 -0
- package/build/adapters/claude-code/index.d.ts +9 -25
- package/build/adapters/claude-code/index.js +12 -140
- package/build/adapters/claude-code-base.d.ts +49 -0
- package/build/adapters/claude-code-base.js +113 -0
- package/build/adapters/client-map.js +5 -0
- package/build/adapters/codex/hooks.d.ts +21 -14
- package/build/adapters/codex/hooks.js +22 -15
- package/build/adapters/codex/index.d.ts +6 -10
- package/build/adapters/codex/index.js +13 -43
- package/build/adapters/copilot-base.d.ts +78 -0
- package/build/adapters/copilot-base.js +281 -0
- package/build/adapters/cursor/index.d.ts +3 -5
- package/build/adapters/cursor/index.js +6 -34
- package/build/adapters/detect.d.ts +7 -0
- package/build/adapters/detect.js +57 -56
- package/build/adapters/gemini-cli/index.d.ts +3 -5
- package/build/adapters/gemini-cli/index.js +7 -35
- 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 +51 -0
- package/build/adapters/jetbrains-copilot/hooks.js +82 -0
- package/build/adapters/jetbrains-copilot/index.d.ts +24 -0
- package/build/adapters/jetbrains-copilot/index.js +119 -0
- package/build/adapters/kiro/hooks.d.ts +14 -0
- package/build/adapters/kiro/hooks.js +23 -0
- package/build/adapters/kiro/index.d.ts +3 -5
- package/build/adapters/kiro/index.js +10 -38
- package/build/adapters/openclaw/index.d.ts +3 -4
- package/build/adapters/openclaw/index.js +6 -22
- package/build/adapters/opencode/index.d.ts +2 -3
- package/build/adapters/opencode/index.js +5 -16
- package/build/adapters/qwen-code/index.d.ts +39 -0
- package/build/adapters/qwen-code/index.js +199 -0
- package/build/adapters/types.d.ts +1 -1
- package/build/adapters/vscode-copilot/index.d.ts +16 -46
- package/build/adapters/vscode-copilot/index.js +29 -320
- package/build/adapters/zed/index.d.ts +3 -5
- package/build/adapters/zed/index.js +7 -35
- package/build/cli.js +13 -0
- package/build/lifecycle.d.ts +23 -0
- package/build/lifecycle.js +54 -13
- package/build/opencode-plugin.d.ts +19 -7
- package/build/opencode-plugin.js +19 -7
- package/build/runtime.js +24 -9
- package/build/security.d.ts +17 -1
- package/build/security.js +40 -6
- package/build/server.js +41 -9
- package/build/session/analytics.d.ts +8 -7
- package/build/session/analytics.js +95 -75
- package/build/session/db.d.ts +10 -1
- package/build/session/db.js +67 -8
- package/build/session/extract.js +10 -2
- package/build/session/project-attribution.d.ts +73 -0
- package/build/session/project-attribution.js +231 -0
- package/build/store.d.ts +4 -0
- package/build/store.js +58 -9
- package/build/types.d.ts +8 -0
- package/cli.bundle.mjs +135 -121
- package/configs/antigravity/GEMINI.md +31 -36
- package/configs/claude-code/CLAUDE.md +31 -37
- package/configs/codex/AGENTS.md +35 -49
- package/configs/cursor/context-mode.mdc +24 -25
- package/configs/gemini-cli/GEMINI.md +30 -36
- package/configs/jetbrains-copilot/copilot-instructions.md +59 -0
- package/configs/jetbrains-copilot/hooks.json +16 -0
- package/configs/jetbrains-copilot/mcp.json +8 -0
- package/configs/kilo/AGENTS.md +30 -36
- package/configs/kiro/KIRO.md +30 -36
- package/configs/kiro/agent.json +1 -1
- package/configs/openclaw/AGENTS.md +30 -36
- package/configs/opencode/AGENTS.md +30 -36
- package/configs/pi/AGENTS.md +31 -36
- package/configs/qwen-code/QWEN.md +63 -0
- package/configs/vscode-copilot/copilot-instructions.md +30 -36
- package/configs/zed/AGENTS.md +31 -36
- package/hooks/codex/posttooluse.mjs +7 -7
- package/hooks/codex/pretooluse.mjs +3 -3
- package/hooks/codex/sessionstart.mjs +2 -1
- package/hooks/core/formatters.mjs +24 -0
- package/hooks/core/routing.mjs +40 -15
- package/hooks/core/tool-naming.mjs +2 -0
- package/hooks/cursor/posttooluse.mjs +7 -7
- package/hooks/cursor/pretooluse.mjs +3 -3
- package/hooks/cursor/sessionstart.mjs +2 -1
- package/hooks/cursor/stop.mjs +2 -2
- package/hooks/ensure-deps.mjs +22 -10
- package/hooks/gemini-cli/aftertool.mjs +8 -8
- package/hooks/gemini-cli/beforetool.mjs +3 -2
- package/hooks/gemini-cli/precompress.mjs +2 -2
- package/hooks/gemini-cli/sessionstart.mjs +12 -4
- package/hooks/jetbrains-copilot/posttooluse.mjs +61 -0
- package/hooks/jetbrains-copilot/precompact.mjs +54 -0
- package/hooks/jetbrains-copilot/pretooluse.mjs +27 -0
- package/hooks/jetbrains-copilot/sessionstart.mjs +119 -0
- package/hooks/kiro/posttooluse.mjs +6 -7
- package/hooks/kiro/pretooluse.mjs +3 -2
- package/hooks/posttooluse.mjs +8 -8
- package/hooks/precompact.mjs +3 -4
- package/hooks/pretooluse.mjs +5 -4
- package/hooks/routing-block.mjs +35 -33
- package/hooks/session-attribution.bundle.mjs +1 -0
- package/hooks/session-db.bundle.mjs +27 -8
- package/hooks/session-extract.bundle.mjs +2 -1
- package/hooks/session-helpers.mjs +44 -3
- package/hooks/session-loaders.mjs +37 -0
- package/hooks/sessionstart.mjs +5 -5
- package/hooks/userpromptsubmit.mjs +26 -9
- package/hooks/vscode-copilot/posttooluse.mjs +8 -8
- package/hooks/vscode-copilot/precompact.mjs +2 -2
- package/hooks/vscode-copilot/pretooluse.mjs +3 -2
- package/hooks/vscode-copilot/sessionstart.mjs +2 -2
- package/insight/server.mjs +237 -25
- package/insight/src/lib/api.ts +2 -1
- package/insight/src/routes/index.tsx +16 -3
- package/insight/src/routes/search.tsx +1 -1
- package/openclaw.plugin.json +1 -1
- package/package.json +11 -2
- package/server.bundle.mjs +94 -80
- package/skills/ctx-insight/SKILL.md +1 -1
|
@@ -10,16 +10,17 @@ import { fileURLToPath } from "node:url";
|
|
|
10
10
|
import { readStdin } from "../core/stdin.mjs";
|
|
11
11
|
import { routePreToolUse, initSecurity } from "../core/routing.mjs";
|
|
12
12
|
import { formatDecision } from "../core/formatters.mjs";
|
|
13
|
+
import { parseStdin, getSessionId, VSCODE_OPTS } from "../session-helpers.mjs";
|
|
13
14
|
|
|
14
15
|
const __hookDir = dirname(fileURLToPath(import.meta.url));
|
|
15
16
|
await initSecurity(resolve(__hookDir, "..", "..", "build"));
|
|
16
17
|
|
|
17
18
|
const raw = await readStdin();
|
|
18
|
-
const input =
|
|
19
|
+
const input = parseStdin(raw);
|
|
19
20
|
const tool = input.tool_name ?? "";
|
|
20
21
|
const toolInput = input.tool_input ?? {};
|
|
21
22
|
|
|
22
|
-
const decision = routePreToolUse(tool, toolInput, process.env.VSCODE_CWD || process.env.CLAUDE_PROJECT_DIR, "vscode-copilot");
|
|
23
|
+
const decision = routePreToolUse(tool, toolInput, process.env.VSCODE_CWD || process.env.CLAUDE_PROJECT_DIR, "vscode-copilot", getSessionId(input, VSCODE_OPTS));
|
|
23
24
|
const response = formatDecision("vscode-copilot", decision);
|
|
24
25
|
if (response !== null) {
|
|
25
26
|
process.stdout.write(JSON.stringify(response) + "\n");
|
|
@@ -19,7 +19,7 @@ const toolNamer = createToolNamer("vscode-copilot");
|
|
|
19
19
|
const ROUTING_BLOCK = createRoutingBlock(toolNamer);
|
|
20
20
|
import { writeSessionEventsFile, buildSessionDirective, getSessionEvents, getLatestSessionEvents } from "../session-directive.mjs";
|
|
21
21
|
import {
|
|
22
|
-
readStdin, getSessionId, getSessionDBPath, getSessionEventsPath, getCleanupFlagPath,
|
|
22
|
+
readStdin, parseStdin, getSessionId, getSessionDBPath, getSessionEventsPath, getCleanupFlagPath,
|
|
23
23
|
getProjectDir, VSCODE_OPTS,
|
|
24
24
|
} from "../session-helpers.mjs";
|
|
25
25
|
import { join } from "node:path";
|
|
@@ -35,7 +35,7 @@ let additionalContext = ROUTING_BLOCK;
|
|
|
35
35
|
|
|
36
36
|
try {
|
|
37
37
|
const raw = await readStdin();
|
|
38
|
-
const input =
|
|
38
|
+
const input = parseStdin(raw);
|
|
39
39
|
const source = input.source ?? "startup";
|
|
40
40
|
|
|
41
41
|
if (source === "compact") {
|
package/insight/server.mjs
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { readFileSync, readdirSync, statSync, existsSync, mkdirSync } from "node:fs";
|
|
12
|
-
import { join, dirname, extname } from "node:path";
|
|
12
|
+
import { join, dirname, extname, normalize } from "node:path";
|
|
13
13
|
import { homedir } from "node:os";
|
|
14
14
|
import { fileURLToPath } from "node:url";
|
|
15
15
|
import { createServer as createHttpServer } from "node:http";
|
|
@@ -70,6 +70,107 @@ function safeGet(db, sql, params = []) {
|
|
|
70
70
|
try { return db.prepare(sql).get(...params); } catch { return null; }
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
+
function hasColumn(db, table, column) {
|
|
74
|
+
try {
|
|
75
|
+
const rows = db.prepare(`PRAGMA table_xinfo(${table})`).all();
|
|
76
|
+
return rows.some(r => r.name === column);
|
|
77
|
+
} catch {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const UNKNOWN_PROJECT_KEY = "__unknown__";
|
|
83
|
+
|
|
84
|
+
function normalizeFsPath(path) {
|
|
85
|
+
const norm = normalize(String(path || "")).replace(/\\/g, "/");
|
|
86
|
+
if (norm.length <= 1) return norm;
|
|
87
|
+
return norm.replace(/\/+$/, "");
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function parseFileSearchPath(data) {
|
|
91
|
+
const marker = " in ";
|
|
92
|
+
const idx = String(data || "").lastIndexOf(marker);
|
|
93
|
+
if (idx < 0) return null;
|
|
94
|
+
const p = String(data || "").slice(idx + marker.length).trim();
|
|
95
|
+
return p || null;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function isLikelyPath(value) {
|
|
99
|
+
const v = String(value || "");
|
|
100
|
+
return v.includes("/") || v.includes("\\") || v.startsWith(".") || /^[A-Za-z]:[\\/]/.test(v);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function legacyProjectAttribution(db) {
|
|
104
|
+
const origins = new Map(
|
|
105
|
+
safeAll(db, "SELECT session_id, project_dir FROM session_meta")
|
|
106
|
+
.map((r) => [r.session_id, r.project_dir || UNKNOWN_PROJECT_KEY]),
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
const events = safeAll(db, `SELECT id, session_id, type, data FROM session_events ORDER BY id ASC`);
|
|
110
|
+
const lastProjectBySession = new Map();
|
|
111
|
+
const projectAgg = new Map();
|
|
112
|
+
let unknownEvents = 0;
|
|
113
|
+
|
|
114
|
+
function addProject(projectDir, sessionId) {
|
|
115
|
+
const key = projectDir || UNKNOWN_PROJECT_KEY;
|
|
116
|
+
const existing = projectAgg.get(key) || { project_dir: key, sessionsSet: new Set(), events: 0, compacts: 0, avg_confidence: 0, high_conf_events: 0 };
|
|
117
|
+
existing.events += 1;
|
|
118
|
+
existing.sessionsSet.add(sessionId);
|
|
119
|
+
projectAgg.set(key, existing);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
for (const ev of events) {
|
|
123
|
+
const sessionId = ev.session_id;
|
|
124
|
+
const origin = origins.get(sessionId) || UNKNOWN_PROJECT_KEY;
|
|
125
|
+
const last = lastProjectBySession.get(sessionId) || "";
|
|
126
|
+
let projectDir = "";
|
|
127
|
+
|
|
128
|
+
if (ev.type === "cwd" && isLikelyPath(ev.data)) {
|
|
129
|
+
projectDir = normalizeFsPath(ev.data);
|
|
130
|
+
} else if (ev.type === "file_read" || ev.type === "file_write" || ev.type === "file_edit" || ev.type === "rule") {
|
|
131
|
+
if (isLikelyPath(ev.data)) {
|
|
132
|
+
const p = normalizeFsPath(ev.data);
|
|
133
|
+
if (origin !== UNKNOWN_PROJECT_KEY && (p === origin || p.startsWith(`${origin}/`))) projectDir = origin;
|
|
134
|
+
else projectDir = p.includes("/") ? p.slice(0, p.lastIndexOf("/")) : p;
|
|
135
|
+
}
|
|
136
|
+
} else if (ev.type === "file_search") {
|
|
137
|
+
const p = parseFileSearchPath(ev.data);
|
|
138
|
+
if (p && isLikelyPath(p)) {
|
|
139
|
+
const pp = normalizeFsPath(p);
|
|
140
|
+
if (origin !== UNKNOWN_PROJECT_KEY && (pp === origin || pp.startsWith(`${origin}/`))) projectDir = origin;
|
|
141
|
+
else projectDir = pp;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (!projectDir) {
|
|
146
|
+
projectDir = last || origin || UNKNOWN_PROJECT_KEY;
|
|
147
|
+
}
|
|
148
|
+
if (!projectDir || projectDir === UNKNOWN_PROJECT_KEY) unknownEvents += 1;
|
|
149
|
+
|
|
150
|
+
addProject(projectDir, sessionId);
|
|
151
|
+
if (projectDir && projectDir !== UNKNOWN_PROJECT_KEY) {
|
|
152
|
+
lastProjectBySession.set(sessionId, projectDir);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const rows = [...projectAgg.values()].map((r) => ({
|
|
157
|
+
project_dir: r.project_dir,
|
|
158
|
+
sessions: r.sessionsSet.size,
|
|
159
|
+
events: r.events,
|
|
160
|
+
compacts: 0,
|
|
161
|
+
avg_confidence: 0,
|
|
162
|
+
high_conf_events: 0,
|
|
163
|
+
})).sort((a, b) => b.events - a.events);
|
|
164
|
+
|
|
165
|
+
return {
|
|
166
|
+
projectRows: rows,
|
|
167
|
+
total_events: events.length,
|
|
168
|
+
unknown_events: unknownEvents,
|
|
169
|
+
avg_confidence: 0,
|
|
170
|
+
high_conf_events: 0,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
73
174
|
function listDBFiles(dir) {
|
|
74
175
|
if (!existsSync(dir)) return [];
|
|
75
176
|
return readdirSync(dir)
|
|
@@ -362,13 +463,21 @@ function apiAnalytics() {
|
|
|
362
463
|
GROUP BY se.session_id, se.data HAVING edit_count > 1
|
|
363
464
|
ORDER BY edit_count DESC LIMIT 20`)
|
|
364
465
|
);
|
|
365
|
-
const gitActivity = queryAllSessionDBs(db =>
|
|
366
|
-
|
|
367
|
-
|
|
466
|
+
const gitActivity = queryAllSessionDBs(db => {
|
|
467
|
+
if (hasColumn(db, "session_events", "project_dir")) {
|
|
468
|
+
return safeAll(db, `SELECT se.data as action, se.created_at, se.session_id,
|
|
469
|
+
COALESCE(NULLIF(se.project_dir, ''), sm.project_dir, '${UNKNOWN_PROJECT_KEY}') as project_dir,
|
|
470
|
+
sm.started_at as session_start
|
|
471
|
+
FROM session_events se
|
|
472
|
+
LEFT JOIN session_meta sm ON se.session_id = sm.session_id
|
|
473
|
+
WHERE se.type = 'git' ORDER BY se.created_at DESC LIMIT 20`);
|
|
474
|
+
}
|
|
475
|
+
return safeAll(db, `SELECT se.data as action, se.created_at, se.session_id,
|
|
476
|
+
COALESCE(sm.project_dir, '${UNKNOWN_PROJECT_KEY}') as project_dir, sm.started_at as session_start
|
|
368
477
|
FROM session_events se
|
|
369
|
-
JOIN session_meta sm ON se.session_id = sm.session_id
|
|
370
|
-
WHERE se.type = 'git' ORDER BY se.created_at DESC LIMIT 20`)
|
|
371
|
-
);
|
|
478
|
+
LEFT JOIN session_meta sm ON se.session_id = sm.session_id
|
|
479
|
+
WHERE se.type = 'git' ORDER BY se.created_at DESC LIMIT 20`);
|
|
480
|
+
});
|
|
372
481
|
const rawSubagents = queryAllSessionDBs(db =>
|
|
373
482
|
safeAll(db, `SELECT data as task, created_at, session_id FROM session_events
|
|
374
483
|
WHERE type = 'subagent' ORDER BY created_at ASC`)
|
|
@@ -393,12 +502,34 @@ function apiAnalytics() {
|
|
|
393
502
|
timeSavedMin: parallelBursts.reduce((a, b) => a + (b.length - 1) * 2, 0),
|
|
394
503
|
burstDetails: parallelBursts.map(b => ({ size: b.length, time: b[0].created_at })),
|
|
395
504
|
};
|
|
396
|
-
const projectActivity = queryAllSessionDBs(db =>
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
505
|
+
const projectActivity = queryAllSessionDBs(db => {
|
|
506
|
+
if (hasColumn(db, "session_events", "project_dir")) {
|
|
507
|
+
return safeAll(db, `SELECT
|
|
508
|
+
COALESCE(NULLIF(se.project_dir, ''), '${UNKNOWN_PROJECT_KEY}') as project_dir,
|
|
509
|
+
COUNT(DISTINCT se.session_id) as sessions,
|
|
510
|
+
COUNT(*) as events,
|
|
511
|
+
0 as compacts,
|
|
512
|
+
AVG(COALESCE(se.attribution_confidence, 0)) as avg_confidence,
|
|
513
|
+
SUM(CASE WHEN COALESCE(se.attribution_confidence, 0) >= 0.8 THEN 1 ELSE 0 END) as high_conf_events
|
|
514
|
+
FROM session_events se
|
|
515
|
+
GROUP BY project_dir
|
|
516
|
+
ORDER BY events DESC
|
|
517
|
+
LIMIT 20`);
|
|
518
|
+
}
|
|
519
|
+
return legacyProjectAttribution(db).projectRows;
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
const attributionSummary = queryAllSessionDBs(db => {
|
|
523
|
+
if (hasColumn(db, "session_events", "project_dir")) {
|
|
524
|
+
return safeAll(db, `SELECT
|
|
525
|
+
COUNT(*) as total_events,
|
|
526
|
+
SUM(CASE WHEN COALESCE(project_dir, '') = '' THEN 1 ELSE 0 END) as unknown_events,
|
|
527
|
+
AVG(COALESCE(attribution_confidence, 0)) as avg_confidence,
|
|
528
|
+
SUM(CASE WHEN COALESCE(attribution_confidence, 0) >= 0.8 THEN 1 ELSE 0 END) as high_conf_events
|
|
529
|
+
FROM session_events`);
|
|
530
|
+
}
|
|
531
|
+
return [legacyProjectAttribution(db)];
|
|
532
|
+
});
|
|
402
533
|
const hourlyPattern = queryAllSessionDBs(db =>
|
|
403
534
|
safeAll(db, `SELECT CAST(strftime('%H', created_at) AS INTEGER) as hour, COUNT(*) as count
|
|
404
535
|
FROM session_events WHERE created_at IS NOT NULL
|
|
@@ -441,13 +572,22 @@ function apiAnalytics() {
|
|
|
441
572
|
);
|
|
442
573
|
|
|
443
574
|
// 2. Personal Commit Rate — commits per session
|
|
444
|
-
const commitRate = queryAllSessionDBs(db =>
|
|
445
|
-
|
|
575
|
+
const commitRate = queryAllSessionDBs(db => {
|
|
576
|
+
if (hasColumn(db, "session_events", "project_dir")) {
|
|
577
|
+
return safeAll(db, `SELECT
|
|
578
|
+
sm.session_id,
|
|
579
|
+
COALESCE(NULLIF(MAX(CASE WHEN se.type = 'git' THEN se.project_dir END), ''), sm.project_dir, '${UNKNOWN_PROJECT_KEY}') as project_dir,
|
|
580
|
+
SUM(CASE WHEN se.type = 'git' AND se.data = 'commit' THEN 1 ELSE 0 END) as commits
|
|
581
|
+
FROM session_meta sm
|
|
582
|
+
LEFT JOIN session_events se ON se.session_id = sm.session_id
|
|
583
|
+
GROUP BY sm.session_id`);
|
|
584
|
+
}
|
|
585
|
+
return safeAll(db, `SELECT sm.session_id, COALESCE(sm.project_dir, '${UNKNOWN_PROJECT_KEY}') as project_dir,
|
|
446
586
|
SUM(CASE WHEN se.type = 'git' AND se.data = 'commit' THEN 1 ELSE 0 END) as commits
|
|
447
587
|
FROM session_meta sm
|
|
448
588
|
LEFT JOIN session_events se ON se.session_id = sm.session_id
|
|
449
|
-
GROUP BY sm.session_id`)
|
|
450
|
-
);
|
|
589
|
+
GROUP BY sm.session_id`);
|
|
590
|
+
});
|
|
451
591
|
|
|
452
592
|
// 3. Sandbox Adoption — context-mode MCP tool usage vs total
|
|
453
593
|
const sandboxAdoption = queryAllSessionDBs(db =>
|
|
@@ -482,6 +622,59 @@ function apiAnalytics() {
|
|
|
482
622
|
sandbox_calls: (a.sandbox_calls || 0) + (b.sandbox_calls || 0),
|
|
483
623
|
total_calls: (a.total_calls || 0) + (b.total_calls || 0),
|
|
484
624
|
}), { sandbox_calls: 0, total_calls: 0 });
|
|
625
|
+
const attributionSchemaCoverage = queryAllSessionDBs(db => [{
|
|
626
|
+
has_attribution_columns: hasColumn(db, "session_events", "project_dir") ? 1 : 0,
|
|
627
|
+
}]);
|
|
628
|
+
const fallbackOnly = attributionSchemaCoverage.length > 0
|
|
629
|
+
&& attributionSchemaCoverage.every((r) => !r.has_attribution_columns);
|
|
630
|
+
|
|
631
|
+
const mergedProjectActivity = mergeByKey(projectActivity, "project_dir", (a, b) => {
|
|
632
|
+
const aEvents = Number(a.events || 0);
|
|
633
|
+
const bEvents = Number(b.events || 0);
|
|
634
|
+
const aWeighted = Number(
|
|
635
|
+
(a.weighted_confidence_sum ?? (Number(a.avg_confidence || 0) * aEvents)) || 0,
|
|
636
|
+
);
|
|
637
|
+
const bWeighted = Number(
|
|
638
|
+
(b.weighted_confidence_sum ?? (Number(b.avg_confidence || 0) * bEvents)) || 0,
|
|
639
|
+
);
|
|
640
|
+
return {
|
|
641
|
+
project_dir: a.project_dir,
|
|
642
|
+
sessions: (a.sessions || 0) + (b.sessions || 0),
|
|
643
|
+
events: aEvents + bEvents,
|
|
644
|
+
compacts: (a.compacts || 0) + (b.compacts || 0),
|
|
645
|
+
weighted_confidence_sum: aWeighted + bWeighted,
|
|
646
|
+
high_conf_events: (a.high_conf_events || 0) + (b.high_conf_events || 0),
|
|
647
|
+
};
|
|
648
|
+
})
|
|
649
|
+
.map((p) => ({
|
|
650
|
+
project_dir: p.project_dir,
|
|
651
|
+
sessions: p.sessions || 0,
|
|
652
|
+
events: p.events || 0,
|
|
653
|
+
compacts: p.compacts || 0,
|
|
654
|
+
avg_confidence: (p.events || 0) > 0 ? (p.weighted_confidence_sum || 0) / p.events : 0,
|
|
655
|
+
high_conf_events: p.high_conf_events || 0,
|
|
656
|
+
}))
|
|
657
|
+
.sort((a, b) => (b.events || 0) - (a.events || 0));
|
|
658
|
+
const nonUnknownProjects = mergedProjectActivity.filter((p) => p.project_dir !== UNKNOWN_PROJECT_KEY);
|
|
659
|
+
|
|
660
|
+
const attributionAgg = attributionSummary.reduce((a, b) => ({
|
|
661
|
+
total_events: (a.total_events || 0) + (b.total_events || 0),
|
|
662
|
+
unknown_events: (a.unknown_events || 0) + (b.unknown_events || 0),
|
|
663
|
+
high_conf_events: (a.high_conf_events || 0) + (b.high_conf_events || 0),
|
|
664
|
+
// weighted sum for avg_confidence
|
|
665
|
+
weighted_confidence_sum: (a.weighted_confidence_sum || 0) + ((b.avg_confidence || 0) * (b.total_events || 0)),
|
|
666
|
+
}), { total_events: 0, unknown_events: 0, high_conf_events: 0, weighted_confidence_sum: 0 });
|
|
667
|
+
|
|
668
|
+
const attributedEvents = Math.max(0, attributionAgg.total_events - attributionAgg.unknown_events);
|
|
669
|
+
const unknownPct = attributionAgg.total_events > 0
|
|
670
|
+
? Math.round(1000 * attributionAgg.unknown_events / attributionAgg.total_events) / 10
|
|
671
|
+
: 100;
|
|
672
|
+
const avgConfidencePct = attributionAgg.total_events > 0
|
|
673
|
+
? Math.round(1000 * attributionAgg.weighted_confidence_sum / attributionAgg.total_events) / 10
|
|
674
|
+
: 0;
|
|
675
|
+
const highConfidencePct = attributionAgg.total_events > 0
|
|
676
|
+
? Math.round(1000 * attributionAgg.high_conf_events / attributionAgg.total_events) / 10
|
|
677
|
+
: 0;
|
|
485
678
|
|
|
486
679
|
return {
|
|
487
680
|
totals: {
|
|
@@ -498,7 +691,7 @@ function apiAnalytics() {
|
|
|
498
691
|
totalTasks: tasks.length, totalPrompts: prompts.length,
|
|
499
692
|
promptsPerSession: sessionDurations.length > 0
|
|
500
693
|
? Math.round(10 * prompts.length / sessionDurations.length) / 10 : 0,
|
|
501
|
-
uniqueProjects:
|
|
694
|
+
uniqueProjects: nonUnknownProjects.length,
|
|
502
695
|
totalCommits: commitRate.reduce((a, b) => a + (b.commits || 0), 0),
|
|
503
696
|
commitsPerSession: sessionDurations.length > 0
|
|
504
697
|
? Math.round(10 * commitRate.reduce((a, b) => a + (b.commits || 0), 0) / sessionDurations.length) / 10 : 0,
|
|
@@ -507,6 +700,15 @@ function apiAnalytics() {
|
|
|
507
700
|
totalRules: rulesFreshness.length,
|
|
508
701
|
totalEditTestCycles: editTestCycles.reduce((a, b) => a + (b.cycles || 0), 0),
|
|
509
702
|
},
|
|
703
|
+
attribution: {
|
|
704
|
+
totalEvents: attributionAgg.total_events,
|
|
705
|
+
attributedEvents,
|
|
706
|
+
unknownEvents: attributionAgg.unknown_events,
|
|
707
|
+
unknownPct,
|
|
708
|
+
avgConfidencePct,
|
|
709
|
+
highConfidencePct,
|
|
710
|
+
isFallbackOnly: fallbackOnly,
|
|
711
|
+
},
|
|
510
712
|
sessionsByDate: mergeByKey(sessionsByDate, "date", (a, b) => ({
|
|
511
713
|
date: a.date, count: a.count + b.count, events: a.events + b.events, compacts: a.compacts + b.compacts
|
|
512
714
|
})),
|
|
@@ -518,9 +720,7 @@ function apiAnalytics() {
|
|
|
518
720
|
timeToFirstCommit,
|
|
519
721
|
exploreExecRatio: exploreExecRatio.reduce((a, b) => ({ explore: (a.explore||0)+(b.explore||0), execute: (a.execute||0)+(b.execute||0), total: (a.total||0)+(b.total||0) }), { explore: 0, execute: 0, total: 0 }),
|
|
520
722
|
reworkData, gitActivity, subagents,
|
|
521
|
-
projectActivity:
|
|
522
|
-
project_dir: a.project_dir, sessions: a.sessions + b.sessions, events: a.events + b.events, compacts: (a.compacts||0)+(b.compacts||0)
|
|
523
|
-
})).sort((a, b) => b.events - a.events),
|
|
723
|
+
projectActivity: mergedProjectActivity,
|
|
524
724
|
hourlyPattern: mergeByKey(hourlyPattern, "hour", (a, b) => ({ hour: a.hour, count: a.count + b.count })),
|
|
525
725
|
weeklyTrend: mergeByKey(weeklyTrend, "week", (a, b) => ({ week: a.week, sessions: a.sessions + b.sessions, events: a.events + b.events })),
|
|
526
726
|
tasks, prompts,
|
|
@@ -586,6 +786,7 @@ function serveStaticFile(pathname) {
|
|
|
586
786
|
// ── Server (dual runtime) ────────────────────────────────
|
|
587
787
|
|
|
588
788
|
const indexHTML = readFileSync(join(DIST_DIR, "index.html"), "utf8");
|
|
789
|
+
const API_JSON_HEADERS = { "Content-Type": "application/json" };
|
|
589
790
|
|
|
590
791
|
if (isBun) {
|
|
591
792
|
// Bun: use Bun.serve
|
|
@@ -597,7 +798,7 @@ if (isBun) {
|
|
|
597
798
|
const data = route(req.method, url.pathname, url.searchParams);
|
|
598
799
|
if (data !== null) {
|
|
599
800
|
return new Response(JSON.stringify(data), {
|
|
600
|
-
headers:
|
|
801
|
+
headers: API_JSON_HEADERS,
|
|
601
802
|
});
|
|
602
803
|
}
|
|
603
804
|
if (url.pathname.startsWith("/assets/") || url.pathname.match(/\.\w{2,4}$/)) {
|
|
@@ -613,9 +814,7 @@ if (isBun) {
|
|
|
613
814
|
// Node: use http.createServer
|
|
614
815
|
const server = createHttpServer((req, res) => {
|
|
615
816
|
const url = new URL(req.url, `http://localhost:${PORT}`);
|
|
616
|
-
|
|
617
|
-
res.setHeader("Access-Control-Allow-Methods", "GET, DELETE, OPTIONS");
|
|
618
|
-
if (req.method === "OPTIONS") { res.writeHead(204); res.end(); return; }
|
|
817
|
+
if (req.method === "OPTIONS") { res.writeHead(405); res.end(); return; }
|
|
619
818
|
|
|
620
819
|
const data = route(req.method, url.pathname, url.searchParams);
|
|
621
820
|
if (data !== null) {
|
|
@@ -637,6 +836,19 @@ if (isBun) {
|
|
|
637
836
|
server.listen(PORT, "127.0.0.1");
|
|
638
837
|
}
|
|
639
838
|
|
|
839
|
+
// Parent watchdog: exit when the MCP process that spawned us disappears.
|
|
840
|
+
// Fallback for SIGKILL / crash paths where shutdown() cannot run.
|
|
841
|
+
const PARENT_PID = Number(process.env.INSIGHT_PARENT_PID);
|
|
842
|
+
if (Number.isFinite(PARENT_PID) && PARENT_PID > 0) {
|
|
843
|
+
setInterval(() => {
|
|
844
|
+
try {
|
|
845
|
+
process.kill(PARENT_PID, 0);
|
|
846
|
+
} catch {
|
|
847
|
+
process.exit(0);
|
|
848
|
+
}
|
|
849
|
+
}, 5000).unref();
|
|
850
|
+
}
|
|
851
|
+
|
|
640
852
|
console.log(`\n context-mode Insight`);
|
|
641
853
|
console.log(` http://localhost:${PORT}`);
|
|
642
854
|
console.log(` Runtime: ${isBun ? "Bun" : "Node.js"}\n`);
|
package/insight/src/lib/api.ts
CHANGED
|
@@ -40,7 +40,8 @@ export interface AnalyticsData {
|
|
|
40
40
|
exploreExecRatio: { explore: number; execute: number; total: number };
|
|
41
41
|
reworkData: { session_id: string; file: string; edit_count: number }[];
|
|
42
42
|
gitActivity: { action: string; created_at: string; session_id: string; project_dir: string; session_start: string }[];
|
|
43
|
-
projectActivity: { project_dir: string; sessions: number; events: number }[];
|
|
43
|
+
projectActivity: { project_dir: string; sessions: number; events: number; avg_confidence?: number; high_conf_events?: number }[];
|
|
44
|
+
attribution?: { totalEvents: number; attributedEvents: number; unknownEvents: number; unknownPct: number; avgConfidencePct: number; highConfidencePct: number; isFallbackOnly: boolean };
|
|
44
45
|
hourlyPattern: { hour: number; count: number }[];
|
|
45
46
|
weeklyTrend: { week: string; sessions: number; events: number }[];
|
|
46
47
|
tasks: { task: string; created_at: string }[];
|
|
@@ -505,18 +505,31 @@ function Dashboard() {
|
|
|
505
505
|
<div className="grid grid-cols-3 gap-4 mb-4">
|
|
506
506
|
<Mini label="Projects" value={t.uniqueProjects} />
|
|
507
507
|
<Mini label="Top Project" value={topProject?.project_dir?.split("/").pop() || "-"} color="text-emerald-500" />
|
|
508
|
-
<Mini label="
|
|
508
|
+
<Mini label="Match Quality" value={data.attribution?.avgConfidencePct != null ? (data.attribution.avgConfidencePct >= 80 ? "Strong" : data.attribution.avgConfidencePct >= 55 ? "Fair" : "Weak") : "-"} color={data.attribution && data.attribution.avgConfidencePct >= 80 ? "text-emerald-500" : data.attribution && data.attribution.avgConfidencePct >= 55 ? "text-amber-500" : "text-red-400"} />
|
|
509
509
|
</div>
|
|
510
|
+
{data.attribution?.isFallbackOnly && (
|
|
511
|
+
<div className="mb-3 px-3 py-2 rounded-md bg-muted/50 border border-border text-xs text-muted-foreground flex items-center gap-1.5">
|
|
512
|
+
<Lightbulb className="h-3 w-3 shrink-0" />
|
|
513
|
+
Limited tracking detail — project time is estimated from session data
|
|
514
|
+
</div>
|
|
515
|
+
)}
|
|
510
516
|
<div className="space-y-2.5 pt-2 border-t border-border">
|
|
511
517
|
{data.projectActivity.slice(0, 6).map((p, i) => {
|
|
512
518
|
const maxEv = data.projectActivity[0]?.events || 1;
|
|
513
519
|
const pct = Math.round((p.events / maxEv) * 100);
|
|
514
520
|
const name = p.project_dir?.split("/").filter(Boolean).slice(-2).join("/") || "Unknown";
|
|
521
|
+
const conf = p.avg_confidence != null ? Math.round(p.avg_confidence * 100) : null;
|
|
522
|
+
const qualityLabel = conf != null ? (conf >= 80 ? "Strong" : conf >= 55 ? "Fair" : "Weak") : null;
|
|
523
|
+
const qualityIcon = conf != null ? (conf >= 80 ? "✓" : conf >= 55 ? "~" : "!") : null;
|
|
524
|
+
const qualityColor = conf != null ? (conf >= 80 ? "text-emerald-500" : conf >= 55 ? "text-amber-500" : "text-red-400") : "";
|
|
515
525
|
return (
|
|
516
526
|
<div key={i}>
|
|
517
527
|
<div className="flex justify-between text-xs mb-1">
|
|
518
|
-
<span className="font-mono truncate max-w-[
|
|
519
|
-
<span className="text-muted-foreground tabular-nums">
|
|
528
|
+
<span className="font-mono truncate max-w-[200px]">{name}</span>
|
|
529
|
+
<span className="text-muted-foreground tabular-nums">
|
|
530
|
+
{p.sessions} sessions · {p.events} events
|
|
531
|
+
{qualityLabel != null && <span className={`ml-1.5 text-[11px] font-medium ${qualityColor}`} title={`Match quality: ${conf}%`}>{qualityIcon} {qualityLabel}</span>}
|
|
532
|
+
</span>
|
|
520
533
|
</div>
|
|
521
534
|
<div className="h-1.5 bg-secondary rounded-full overflow-hidden">
|
|
522
535
|
<div className="h-full rounded-full transition-all" style={{ width: `${pct}%`, background: COLORS[i % COLORS.length] }} />
|
|
@@ -79,7 +79,7 @@ function SearchPage() {
|
|
|
79
79
|
<pre
|
|
80
80
|
className="text-xs text-muted-foreground whitespace-pre-wrap break-words font-mono leading-relaxed"
|
|
81
81
|
dangerouslySetInnerHTML={{
|
|
82
|
-
__html: (r.highlighted
|
|
82
|
+
__html: (r.highlighted ? esc(r.highlighted) : esc(r.content))
|
|
83
83
|
.replace(/«/g, '<mark class="bg-amber-500/20 text-foreground rounded px-0.5">')
|
|
84
84
|
.replace(/»/g, "</mark>"),
|
|
85
85
|
}}
|
package/openclaw.plugin.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"name": "Context Mode",
|
|
4
4
|
"kind": "tool",
|
|
5
5
|
"description": "OpenClaw plugin that saves 98% of your context window. Sandboxed code execution in 11 languages, FTS5 knowledge base with BM25 ranking, and intent-driven search.",
|
|
6
|
-
"version": "1.0.
|
|
6
|
+
"version": "1.0.90",
|
|
7
7
|
"sandbox": {
|
|
8
8
|
"mode": "permissive",
|
|
9
9
|
"filesystem_access": "full",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "context-mode",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.90",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "MCP plugin that saves 98% of your context window. Works with Claude Code, Gemini CLI, VS Code Copilot, OpenCode, and Codex CLI. Sandboxed code execution, FTS5 knowledge base, and intent-driven search.",
|
|
6
6
|
"author": "Mert Koseoğlu",
|
|
@@ -19,13 +19,22 @@
|
|
|
19
19
|
"sandbox",
|
|
20
20
|
"code-execution",
|
|
21
21
|
"fts5",
|
|
22
|
-
"bm25"
|
|
22
|
+
"bm25",
|
|
23
|
+
"pi-package"
|
|
23
24
|
],
|
|
24
25
|
"repository": {
|
|
25
26
|
"type": "git",
|
|
26
27
|
"url": "https://github.com/mksglu/context-mode"
|
|
27
28
|
},
|
|
28
29
|
"homepage": "https://github.com/mksglu/context-mode#readme",
|
|
30
|
+
"pi": {
|
|
31
|
+
"extensions": [
|
|
32
|
+
"./build/pi-extension.js"
|
|
33
|
+
],
|
|
34
|
+
"skills": [
|
|
35
|
+
"./skills"
|
|
36
|
+
]
|
|
37
|
+
},
|
|
29
38
|
"openclaw": {
|
|
30
39
|
"extensions": [
|
|
31
40
|
"./build/openclaw-plugin.js"
|