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.
Files changed (72) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.openclaw-plugin/openclaw.plugin.json +1 -1
  4. package/.openclaw-plugin/package.json +1 -1
  5. package/README.md +22 -18
  6. package/build/adapters/claude-code/index.js +26 -9
  7. package/build/adapters/copilot-base.d.ts +3 -3
  8. package/build/adapters/cursor/hooks.js +8 -0
  9. package/build/adapters/cursor/index.js +4 -1
  10. package/build/adapters/gemini-cli/hooks.d.ts +6 -1
  11. package/build/adapters/gemini-cli/hooks.js +7 -1
  12. package/build/adapters/gemini-cli/index.js +12 -0
  13. package/build/adapters/kiro/hooks.js +4 -0
  14. package/build/adapters/kiro/index.d.ts +9 -2
  15. package/build/adapters/kiro/index.js +49 -27
  16. package/build/adapters/opencode/index.js +11 -5
  17. package/build/adapters/qwen-code/index.js +18 -0
  18. package/build/adapters/vscode-copilot/hooks.d.ts +0 -4
  19. package/build/adapters/vscode-copilot/hooks.js +6 -6
  20. package/build/cli.js +93 -12
  21. package/build/openclaw/mcp-tools.d.ts +54 -0
  22. package/build/openclaw/mcp-tools.js +198 -0
  23. package/build/openclaw-plugin.d.ts +9 -0
  24. package/build/openclaw-plugin.js +132 -16
  25. package/build/opencode-plugin.d.ts +29 -4
  26. package/build/opencode-plugin.js +154 -7
  27. package/build/pi-extension.js +123 -29
  28. package/build/server.d.ts +1 -0
  29. package/build/server.js +26 -1
  30. package/build/session/analytics.js +36 -13
  31. package/build/session/extract.d.ts +1 -1
  32. package/build/session/extract.js +46 -1
  33. package/cli.bundle.mjs +133 -132
  34. package/hooks/core/platform-detect.mjs +49 -0
  35. package/hooks/core/routing.mjs +13 -1
  36. package/hooks/cursor/afteragentresponse.mjs +74 -0
  37. package/hooks/ensure-deps.mjs +28 -12
  38. package/hooks/gemini-cli/beforeagent.mjs +99 -0
  39. package/hooks/kiro/agentspawn.mjs +97 -0
  40. package/hooks/kiro/userpromptsubmit.mjs +88 -0
  41. package/hooks/posttooluse.mjs +90 -80
  42. package/hooks/precompact.mjs +56 -46
  43. package/hooks/pretooluse.mjs +161 -167
  44. package/hooks/routing-block.mjs +2 -2
  45. package/hooks/run-hook.mjs +82 -0
  46. package/hooks/session-extract.bundle.mjs +2 -2
  47. package/hooks/sessionstart.mjs +187 -153
  48. package/hooks/userpromptsubmit.mjs +69 -58
  49. package/hooks/vscode-copilot/sessionstart.mjs +13 -14
  50. package/openclaw.plugin.json +1 -1
  51. package/package.json +2 -1
  52. package/scripts/heal-better-sqlite3.mjs +108 -0
  53. package/scripts/postinstall.mjs +27 -0
  54. package/server.bundle.mjs +79 -79
  55. package/skills/UPSTREAM-CREDITS.md +51 -0
  56. package/skills/context-mode-ops/SKILL.md +147 -0
  57. package/skills/diagnose/SKILL.md +122 -0
  58. package/skills/diagnose/scripts/hitl-loop.template.sh +41 -0
  59. package/skills/grill-me/SKILL.md +15 -0
  60. package/skills/grill-with-docs/ADR-FORMAT.md +47 -0
  61. package/skills/grill-with-docs/CONTEXT-FORMAT.md +77 -0
  62. package/skills/grill-with-docs/SKILL.md +93 -0
  63. package/skills/improve-codebase-architecture/DEEPENING.md +37 -0
  64. package/skills/improve-codebase-architecture/INTERFACE-DESIGN.md +44 -0
  65. package/skills/improve-codebase-architecture/LANGUAGE.md +53 -0
  66. package/skills/improve-codebase-architecture/SKILL.md +76 -0
  67. package/skills/tdd/SKILL.md +114 -0
  68. package/skills/tdd/deep-modules.md +33 -0
  69. package/skills/tdd/interface-design.md +31 -0
  70. package/skills/tdd/mocking.md +59 -0
  71. package/skills/tdd/refactoring.md +10 -0
  72. package/skills/tdd/tests.md +61 -0
@@ -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 { readStdin, parseStdin, getSessionId, getSessionDBPath, getInputProjectDir } from "./session-helpers.mjs";
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
- // Resolve absolute path for imports — relative dynamic imports can fail
21
- // when Claude Code invokes hooks from a different working directory.
22
- const HOOK_DIR = dirname(fileURLToPath(import.meta.url));
23
- const { loadSessionDB, loadExtract, loadProjectAttribution } = createSessionLoaders(HOOK_DIR);
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
- try {
26
- const raw = await readStdin();
27
- const input = parseStdin(raw);
28
- const projectDir = getInputProjectDir(input);
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
- const { extractEvents } = await loadExtract();
31
- const { resolveProjectAttributions } = await loadProjectAttribution();
32
- const { SessionDB } = await loadSessionDB();
34
+ try {
35
+ const raw = await readStdin();
36
+ const input = parseStdin(raw);
37
+ const projectDir = getInputProjectDir(input);
33
38
 
34
- const dbPath = getSessionDBPath();
35
- const db = new SessionDB({ dbPath });
36
- const sessionId = getSessionId(input);
39
+ const { extractEvents } = await loadExtract();
40
+ const { resolveProjectAttributions } = await loadProjectAttribution();
41
+ const { SessionDB } = await loadSessionDB();
37
42
 
38
- // Ensure session meta exists
39
- db.ensureSession(sessionId, projectDir);
43
+ const dbPath = getSessionDBPath();
44
+ const db = new SessionDB({ dbPath });
45
+ const sessionId = getSessionId(input);
40
46
 
41
- // Extract and store events
42
- const events = extractEvents({
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
- attributeAndInsertEvents(db, sessionId, events, input, projectDir, "PostToolUse", resolveProjectAttributions);
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
- // ─── Category 18: Rejected-approach read PreToolUse marker ───
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
- // ─── Category 27: Latency — read cross-hook marker and emit event if slow ───
75
- try {
76
- const toolName = input.tool_name ?? "";
77
- if (toolName) {
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
- startTime = parseInt(readFileSync(markerPath, "utf-8").trim(), 10);
82
- unlinkSync(markerPath);
83
- } catch {
84
- // No marker — pretooluse didn't write one or already consumed
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
- if (startTime && !isNaN(startTime)) {
87
- const duration = Date.now() - startTime;
88
- if (duration > 5000) {
89
- db.insertEvent(sessionId, {
90
- type: "tool_latency",
91
- category: "latency",
92
- data: `${toolName}: ${duration}ms`,
93
- priority: 3,
94
- }, "PostToolUse");
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
- db.close();
101
- } catch {
102
- // PostToolUse must never block the session — silent fallback
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
+ });
@@ -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 { readStdin, parseStdin, getSessionId, getSessionDBPath, resolveConfigDir } from "./session-helpers.mjs";
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
- // Resolve absolute path for imports
19
- const HOOK_DIR = dirname(fileURLToPath(import.meta.url));
20
- const { loadSessionDB, loadSnapshot } = createSessionLoaders(HOOK_DIR);
21
- const DEBUG_LOG = join(resolveConfigDir(), "context-mode", "precompact-debug.log");
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
- try {
24
- const raw = await readStdin();
25
- const input = parseStdin(raw);
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
- const { buildResumeSnapshot } = await loadSnapshot();
28
- const { SessionDB } = await loadSessionDB();
32
+ try {
33
+ const raw = await readStdin();
34
+ const input = parseStdin(raw);
29
35
 
30
- const dbPath = getSessionDBPath();
31
- const db = new SessionDB({ dbPath });
32
- const sessionId = getSessionId(input);
36
+ const { buildResumeSnapshot } = await loadSnapshot();
37
+ const { SessionDB } = await loadSessionDB();
33
38
 
34
- // Get all events for this session
35
- const events = db.getEvents(sessionId);
39
+ const dbPath = getSessionDBPath();
40
+ const db = new SessionDB({ dbPath });
41
+ const sessionId = getSessionId(input);
36
42
 
37
- if (events.length > 0) {
38
- const stats = db.getSessionStats(sessionId);
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
- db.upsertResume(sessionId, snapshot, events.length);
44
- db.incrementCompactCount(sessionId);
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
- // Write compaction category event for analytics
47
- const fileEvents = events.filter(e => e.category === "file");
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
- db.close();
57
- } catch (err) {
58
- try {
59
- appendFileSync(DEBUG_LOG, `[${new Date().toISOString()}] ${err.message}\n`);
60
- } catch {
61
- // Silent fallback
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
+ });