context-mode 1.0.106 → 1.0.108
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 +22 -18
- package/build/adapters/claude-code/index.js +26 -9
- package/build/adapters/copilot-base.d.ts +3 -3
- package/build/adapters/cursor/hooks.js +8 -0
- package/build/adapters/cursor/index.js +4 -1
- package/build/adapters/gemini-cli/hooks.d.ts +6 -1
- package/build/adapters/gemini-cli/hooks.js +7 -1
- package/build/adapters/gemini-cli/index.js +12 -0
- package/build/adapters/kiro/hooks.js +4 -0
- package/build/adapters/kiro/index.d.ts +9 -2
- package/build/adapters/kiro/index.js +49 -27
- package/build/adapters/opencode/index.js +11 -5
- package/build/adapters/qwen-code/index.js +18 -0
- package/build/adapters/vscode-copilot/hooks.d.ts +0 -4
- package/build/adapters/vscode-copilot/hooks.js +6 -6
- package/build/cli.js +93 -12
- package/build/openclaw/mcp-tools.d.ts +54 -0
- package/build/openclaw/mcp-tools.js +198 -0
- package/build/openclaw-plugin.d.ts +9 -0
- package/build/openclaw-plugin.js +132 -16
- package/build/opencode-plugin.d.ts +29 -4
- package/build/opencode-plugin.js +154 -7
- package/build/pi-extension.js +123 -29
- package/build/server.d.ts +1 -0
- package/build/server.js +26 -1
- package/build/session/analytics.js +36 -13
- package/build/session/extract.d.ts +1 -1
- package/build/session/extract.js +46 -1
- package/cli.bundle.mjs +133 -132
- package/hooks/core/platform-detect.mjs +49 -0
- package/hooks/core/routing.mjs +13 -1
- package/hooks/cursor/afteragentresponse.mjs +74 -0
- package/hooks/ensure-deps.mjs +28 -12
- package/hooks/gemini-cli/beforeagent.mjs +99 -0
- package/hooks/kiro/agentspawn.mjs +97 -0
- package/hooks/kiro/userpromptsubmit.mjs +88 -0
- package/hooks/posttooluse.mjs +90 -80
- package/hooks/precompact.mjs +56 -46
- package/hooks/pretooluse.mjs +161 -167
- package/hooks/routing-block.mjs +2 -2
- package/hooks/run-hook.mjs +82 -0
- package/hooks/session-extract.bundle.mjs +2 -2
- package/hooks/sessionstart.mjs +187 -153
- package/hooks/userpromptsubmit.mjs +69 -58
- package/hooks/vscode-copilot/sessionstart.mjs +13 -14
- package/openclaw.plugin.json +1 -1
- package/package.json +2 -1
- package/scripts/heal-better-sqlite3.mjs +108 -0
- package/scripts/postinstall.mjs +27 -0
- package/server.bundle.mjs +79 -79
- package/skills/UPSTREAM-CREDITS.md +51 -0
- package/skills/context-mode-ops/SKILL.md +147 -0
- package/skills/diagnose/SKILL.md +122 -0
- package/skills/diagnose/scripts/hitl-loop.template.sh +41 -0
- package/skills/grill-me/SKILL.md +15 -0
- package/skills/grill-with-docs/ADR-FORMAT.md +47 -0
- package/skills/grill-with-docs/CONTEXT-FORMAT.md +77 -0
- package/skills/grill-with-docs/SKILL.md +93 -0
- package/skills/improve-codebase-architecture/DEEPENING.md +37 -0
- package/skills/improve-codebase-architecture/INTERFACE-DESIGN.md +44 -0
- package/skills/improve-codebase-architecture/LANGUAGE.md +53 -0
- package/skills/improve-codebase-architecture/SKILL.md +76 -0
- package/skills/tdd/SKILL.md +114 -0
- package/skills/tdd/deep-modules.md +33 -0
- package/skills/tdd/interface-design.md +31 -0
- package/skills/tdd/mocking.md +59 -0
- package/skills/tdd/refactoring.md +10 -0
- package/skills/tdd/tests.md +61 -0
package/hooks/posttooluse.mjs
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import "./suppress-stderr.mjs";
|
|
3
|
-
import "./ensure-deps.mjs";
|
|
4
2
|
/**
|
|
5
3
|
* PostToolUse hook for context-mode session continuity.
|
|
6
4
|
*
|
|
@@ -8,98 +6,110 @@ import "./ensure-deps.mjs";
|
|
|
8
6
|
* them in the per-project SessionDB for later resume snapshot building.
|
|
9
7
|
*
|
|
10
8
|
* Must be fast (<20ms). No network, no LLM, just SQLite writes.
|
|
9
|
+
*
|
|
10
|
+
* Crash-resilience: wrapped via runHook (#414).
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import {
|
|
14
|
-
import { createSessionLoaders, attributeAndInsertEvents } from "./session-loaders.mjs";
|
|
15
|
-
import { dirname, resolve } from "node:path";
|
|
16
|
-
import { fileURLToPath } from "node:url";
|
|
17
|
-
import { readFileSync, unlinkSync } from "node:fs";
|
|
18
|
-
import { tmpdir } from "node:os";
|
|
13
|
+
import { runHook } from "./run-hook.mjs";
|
|
19
14
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
15
|
+
await runHook(async () => {
|
|
16
|
+
const {
|
|
17
|
+
readStdin,
|
|
18
|
+
parseStdin,
|
|
19
|
+
getSessionId,
|
|
20
|
+
getSessionDBPath,
|
|
21
|
+
getInputProjectDir,
|
|
22
|
+
} = await import("./session-helpers.mjs");
|
|
23
|
+
const { createSessionLoaders, attributeAndInsertEvents } = await import("./session-loaders.mjs");
|
|
24
|
+
const { dirname, resolve } = await import("node:path");
|
|
25
|
+
const { fileURLToPath } = await import("node:url");
|
|
26
|
+
const { readFileSync, unlinkSync } = await import("node:fs");
|
|
27
|
+
const { tmpdir } = await import("node:os");
|
|
24
28
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
const
|
|
28
|
-
const
|
|
29
|
+
// Resolve absolute path for imports — relative dynamic imports can fail
|
|
30
|
+
// when Claude Code invokes hooks from a different working directory.
|
|
31
|
+
const HOOK_DIR = dirname(fileURLToPath(import.meta.url));
|
|
32
|
+
const { loadSessionDB, loadExtract, loadProjectAttribution } = createSessionLoaders(HOOK_DIR);
|
|
29
33
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
34
|
+
try {
|
|
35
|
+
const raw = await readStdin();
|
|
36
|
+
const input = parseStdin(raw);
|
|
37
|
+
const projectDir = getInputProjectDir(input);
|
|
33
38
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
39
|
+
const { extractEvents } = await loadExtract();
|
|
40
|
+
const { resolveProjectAttributions } = await loadProjectAttribution();
|
|
41
|
+
const { SessionDB } = await loadSessionDB();
|
|
37
42
|
|
|
38
|
-
|
|
39
|
-
|
|
43
|
+
const dbPath = getSessionDBPath();
|
|
44
|
+
const db = new SessionDB({ dbPath });
|
|
45
|
+
const sessionId = getSessionId(input);
|
|
40
46
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
tool_name: input.tool_name,
|
|
44
|
-
tool_input: input.tool_input ?? {},
|
|
45
|
-
tool_response: typeof input.tool_response === "string"
|
|
46
|
-
? input.tool_response
|
|
47
|
-
: JSON.stringify(input.tool_response ?? ""),
|
|
48
|
-
tool_output: input.tool_output,
|
|
49
|
-
});
|
|
47
|
+
// Ensure session meta exists
|
|
48
|
+
db.ensureSession(sessionId, projectDir);
|
|
50
49
|
|
|
51
|
-
|
|
50
|
+
// Extract and store events
|
|
51
|
+
const events = extractEvents({
|
|
52
|
+
tool_name: input.tool_name,
|
|
53
|
+
tool_input: input.tool_input ?? {},
|
|
54
|
+
tool_response: typeof input.tool_response === "string"
|
|
55
|
+
? input.tool_response
|
|
56
|
+
: JSON.stringify(input.tool_response ?? ""),
|
|
57
|
+
tool_output: input.tool_output,
|
|
58
|
+
});
|
|
52
59
|
|
|
53
|
-
|
|
54
|
-
try {
|
|
55
|
-
const rejectedPath = resolve(tmpdir(), `context-mode-rejected-${sessionId}.txt`);
|
|
56
|
-
let rejectedData;
|
|
57
|
-
try {
|
|
58
|
-
rejectedData = readFileSync(rejectedPath, "utf-8").trim();
|
|
59
|
-
unlinkSync(rejectedPath);
|
|
60
|
-
} catch { /* no marker */ }
|
|
61
|
-
if (rejectedData) {
|
|
62
|
-
const colonIdx = rejectedData.indexOf(":");
|
|
63
|
-
const rejTool = colonIdx > 0 ? rejectedData.slice(0, colonIdx) : rejectedData;
|
|
64
|
-
const rejReason = colonIdx > 0 ? rejectedData.slice(colonIdx + 1) : "denied";
|
|
65
|
-
db.insertEvent(sessionId, {
|
|
66
|
-
type: "rejected",
|
|
67
|
-
category: "rejected-approach",
|
|
68
|
-
data: `${rejTool}: ${rejReason}`,
|
|
69
|
-
priority: 2,
|
|
70
|
-
}, "PreToolUse");
|
|
71
|
-
}
|
|
72
|
-
} catch { /* best-effort */ }
|
|
60
|
+
attributeAndInsertEvents(db, sessionId, events, input, projectDir, "PostToolUse", resolveProjectAttributions);
|
|
73
61
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
const markerPath = resolve(tmpdir(), `context-mode-latency-${sessionId}-${toolName}.txt`);
|
|
79
|
-
let startTime;
|
|
62
|
+
// ─── Category 18: Rejected-approach — read PreToolUse marker ───
|
|
63
|
+
try {
|
|
64
|
+
const rejectedPath = resolve(tmpdir(), `context-mode-rejected-${sessionId}.txt`);
|
|
65
|
+
let rejectedData;
|
|
80
66
|
try {
|
|
81
|
-
|
|
82
|
-
unlinkSync(
|
|
83
|
-
} catch {
|
|
84
|
-
|
|
67
|
+
rejectedData = readFileSync(rejectedPath, "utf-8").trim();
|
|
68
|
+
unlinkSync(rejectedPath);
|
|
69
|
+
} catch { /* no marker */ }
|
|
70
|
+
if (rejectedData) {
|
|
71
|
+
const colonIdx = rejectedData.indexOf(":");
|
|
72
|
+
const rejTool = colonIdx > 0 ? rejectedData.slice(0, colonIdx) : rejectedData;
|
|
73
|
+
const rejReason = colonIdx > 0 ? rejectedData.slice(colonIdx + 1) : "denied";
|
|
74
|
+
db.insertEvent(sessionId, {
|
|
75
|
+
type: "rejected",
|
|
76
|
+
category: "rejected-approach",
|
|
77
|
+
data: `${rejTool}: ${rejReason}`,
|
|
78
|
+
priority: 2,
|
|
79
|
+
}, "PreToolUse");
|
|
85
80
|
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
81
|
+
} catch { /* best-effort */ }
|
|
82
|
+
|
|
83
|
+
// ─── Category 27: Latency — read cross-hook marker and emit event if slow ───
|
|
84
|
+
try {
|
|
85
|
+
const toolName = input.tool_name ?? "";
|
|
86
|
+
if (toolName) {
|
|
87
|
+
const markerPath = resolve(tmpdir(), `context-mode-latency-${sessionId}-${toolName}.txt`);
|
|
88
|
+
let startTime;
|
|
89
|
+
try {
|
|
90
|
+
startTime = parseInt(readFileSync(markerPath, "utf-8").trim(), 10);
|
|
91
|
+
unlinkSync(markerPath);
|
|
92
|
+
} catch {
|
|
93
|
+
// No marker — pretooluse didn't write one or already consumed
|
|
94
|
+
}
|
|
95
|
+
if (startTime && !isNaN(startTime)) {
|
|
96
|
+
const duration = Date.now() - startTime;
|
|
97
|
+
if (duration > 5000) {
|
|
98
|
+
db.insertEvent(sessionId, {
|
|
99
|
+
type: "tool_latency",
|
|
100
|
+
category: "latency",
|
|
101
|
+
data: `${toolName}: ${duration}ms`,
|
|
102
|
+
priority: 3,
|
|
103
|
+
}, "PostToolUse");
|
|
104
|
+
}
|
|
95
105
|
}
|
|
96
106
|
}
|
|
97
|
-
}
|
|
98
|
-
} catch { /* latency tracking is best-effort */ }
|
|
107
|
+
} catch { /* latency tracking is best-effort */ }
|
|
99
108
|
|
|
100
|
-
|
|
101
|
-
} catch {
|
|
102
|
-
|
|
103
|
-
}
|
|
109
|
+
db.close();
|
|
110
|
+
} catch {
|
|
111
|
+
// PostToolUse must never block the session — silent fallback
|
|
112
|
+
}
|
|
104
113
|
|
|
105
|
-
// PostToolUse hooks don't need hookSpecificOutput
|
|
114
|
+
// PostToolUse hooks don't need hookSpecificOutput
|
|
115
|
+
});
|
package/hooks/precompact.mjs
CHANGED
|
@@ -1,66 +1,76 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import "./suppress-stderr.mjs";
|
|
3
|
-
import "./ensure-deps.mjs";
|
|
4
2
|
/**
|
|
5
3
|
* PreCompact hook for context-mode session continuity.
|
|
6
4
|
*
|
|
7
5
|
* Triggered when Claude Code is about to compact the conversation.
|
|
8
6
|
* Reads all captured session events, builds a priority-sorted resume
|
|
9
7
|
* snapshot (<2KB XML), and stores it for injection after compact.
|
|
8
|
+
*
|
|
9
|
+
* Crash-resilience: wrapped via runHook (#414).
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import {
|
|
13
|
-
import { createSessionLoaders } from "./session-loaders.mjs";
|
|
14
|
-
import { appendFileSync } from "node:fs";
|
|
15
|
-
import { join, dirname } from "node:path";
|
|
16
|
-
import { fileURLToPath } from "node:url";
|
|
12
|
+
import { runHook } from "./run-hook.mjs";
|
|
17
13
|
|
|
18
|
-
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
14
|
+
await runHook(async () => {
|
|
15
|
+
const {
|
|
16
|
+
readStdin,
|
|
17
|
+
parseStdin,
|
|
18
|
+
getSessionId,
|
|
19
|
+
getSessionDBPath,
|
|
20
|
+
resolveConfigDir,
|
|
21
|
+
} = await import("./session-helpers.mjs");
|
|
22
|
+
const { createSessionLoaders } = await import("./session-loaders.mjs");
|
|
23
|
+
const { appendFileSync } = await import("node:fs");
|
|
24
|
+
const { join, dirname } = await import("node:path");
|
|
25
|
+
const { fileURLToPath } = await import("node:url");
|
|
22
26
|
|
|
23
|
-
|
|
24
|
-
const
|
|
25
|
-
const
|
|
27
|
+
// Resolve absolute path for imports
|
|
28
|
+
const HOOK_DIR = dirname(fileURLToPath(import.meta.url));
|
|
29
|
+
const { loadSessionDB, loadSnapshot } = createSessionLoaders(HOOK_DIR);
|
|
30
|
+
const DEBUG_LOG = join(resolveConfigDir(), "context-mode", "precompact-debug.log");
|
|
26
31
|
|
|
27
|
-
|
|
28
|
-
|
|
32
|
+
try {
|
|
33
|
+
const raw = await readStdin();
|
|
34
|
+
const input = parseStdin(raw);
|
|
29
35
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const sessionId = getSessionId(input);
|
|
36
|
+
const { buildResumeSnapshot } = await loadSnapshot();
|
|
37
|
+
const { SessionDB } = await loadSessionDB();
|
|
33
38
|
|
|
34
|
-
|
|
35
|
-
|
|
39
|
+
const dbPath = getSessionDBPath();
|
|
40
|
+
const db = new SessionDB({ dbPath });
|
|
41
|
+
const sessionId = getSessionId(input);
|
|
36
42
|
|
|
37
|
-
|
|
38
|
-
const
|
|
39
|
-
const snapshot = buildResumeSnapshot(events, {
|
|
40
|
-
compactCount: (stats?.compact_count ?? 0) + 1,
|
|
41
|
-
});
|
|
43
|
+
// Get all events for this session
|
|
44
|
+
const events = db.getEvents(sessionId);
|
|
42
45
|
|
|
43
|
-
|
|
44
|
-
|
|
46
|
+
if (events.length > 0) {
|
|
47
|
+
const stats = db.getSessionStats(sessionId);
|
|
48
|
+
const snapshot = buildResumeSnapshot(events, {
|
|
49
|
+
compactCount: (stats?.compact_count ?? 0) + 1,
|
|
50
|
+
});
|
|
45
51
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
db.insertEvent(sessionId, {
|
|
49
|
-
type: "compaction_summary",
|
|
50
|
-
category: "compaction",
|
|
51
|
-
data: `Session compacted. ${events.length} events, ${fileEvents.length} files touched.`,
|
|
52
|
-
priority: 1,
|
|
53
|
-
}, "PreCompact");
|
|
54
|
-
}
|
|
52
|
+
db.upsertResume(sessionId, snapshot, events.length);
|
|
53
|
+
db.incrementCompactCount(sessionId);
|
|
55
54
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
55
|
+
// Write compaction category event for analytics
|
|
56
|
+
const fileEvents = events.filter(e => e.category === "file");
|
|
57
|
+
db.insertEvent(sessionId, {
|
|
58
|
+
type: "compaction_summary",
|
|
59
|
+
category: "compaction",
|
|
60
|
+
data: `Session compacted. ${events.length} events, ${fileEvents.length} files touched.`,
|
|
61
|
+
priority: 1,
|
|
62
|
+
}, "PreCompact");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
db.close();
|
|
66
|
+
} catch (err) {
|
|
67
|
+
try {
|
|
68
|
+
appendFileSync(DEBUG_LOG, `[${new Date().toISOString()}] ${err.message}\n`);
|
|
69
|
+
} catch {
|
|
70
|
+
// Silent fallback
|
|
71
|
+
}
|
|
62
72
|
}
|
|
63
|
-
}
|
|
64
73
|
|
|
65
|
-
// PreCompact doesn't need hookSpecificOutput
|
|
66
|
-
console.log(JSON.stringify({}));
|
|
74
|
+
// PreCompact doesn't need hookSpecificOutput
|
|
75
|
+
console.log(JSON.stringify({}));
|
|
76
|
+
});
|