context-mode 1.0.88 → 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.
Files changed (132) 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 +184 -60
  6. package/build/adapters/antigravity/index.d.ts +3 -5
  7. package/build/adapters/antigravity/index.js +7 -35
  8. package/build/adapters/base.d.ts +27 -0
  9. package/build/adapters/base.js +59 -0
  10. package/build/adapters/claude-code/index.d.ts +9 -25
  11. package/build/adapters/claude-code/index.js +27 -141
  12. package/build/adapters/claude-code-base.d.ts +49 -0
  13. package/build/adapters/claude-code-base.js +113 -0
  14. package/build/adapters/client-map.js +5 -0
  15. package/build/adapters/codex/hooks.d.ts +21 -14
  16. package/build/adapters/codex/hooks.js +22 -15
  17. package/build/adapters/codex/index.d.ts +6 -10
  18. package/build/adapters/codex/index.js +13 -43
  19. package/build/adapters/copilot-base.d.ts +78 -0
  20. package/build/adapters/copilot-base.js +281 -0
  21. package/build/adapters/cursor/index.d.ts +3 -5
  22. package/build/adapters/cursor/index.js +6 -34
  23. package/build/adapters/detect.d.ts +7 -0
  24. package/build/adapters/detect.js +57 -56
  25. package/build/adapters/gemini-cli/index.d.ts +3 -5
  26. package/build/adapters/gemini-cli/index.js +7 -35
  27. package/build/adapters/jetbrains-copilot/config.d.ts +8 -0
  28. package/build/adapters/jetbrains-copilot/config.js +8 -0
  29. package/build/adapters/jetbrains-copilot/hooks.d.ts +51 -0
  30. package/build/adapters/jetbrains-copilot/hooks.js +82 -0
  31. package/build/adapters/jetbrains-copilot/index.d.ts +24 -0
  32. package/build/adapters/jetbrains-copilot/index.js +119 -0
  33. package/build/adapters/kiro/hooks.d.ts +14 -0
  34. package/build/adapters/kiro/hooks.js +23 -0
  35. package/build/adapters/kiro/index.d.ts +3 -5
  36. package/build/adapters/kiro/index.js +10 -38
  37. package/build/adapters/openclaw/index.d.ts +3 -4
  38. package/build/adapters/openclaw/index.js +6 -22
  39. package/build/adapters/opencode/index.d.ts +2 -3
  40. package/build/adapters/opencode/index.js +5 -16
  41. package/build/adapters/qwen-code/index.d.ts +39 -0
  42. package/build/adapters/qwen-code/index.js +199 -0
  43. package/build/adapters/types.d.ts +1 -1
  44. package/build/adapters/vscode-copilot/index.d.ts +16 -46
  45. package/build/adapters/vscode-copilot/index.js +29 -320
  46. package/build/adapters/zed/index.d.ts +3 -5
  47. package/build/adapters/zed/index.js +7 -35
  48. package/build/cli.js +113 -47
  49. package/build/lifecycle.d.ts +23 -0
  50. package/build/lifecycle.js +54 -13
  51. package/build/opencode-plugin.d.ts +19 -7
  52. package/build/opencode-plugin.js +19 -7
  53. package/build/pi-extension.js +24 -7
  54. package/build/runtime.js +24 -9
  55. package/build/security.d.ts +17 -1
  56. package/build/security.js +40 -6
  57. package/build/server.js +129 -21
  58. package/build/session/analytics.d.ts +8 -7
  59. package/build/session/analytics.js +95 -75
  60. package/build/session/db.d.ts +10 -1
  61. package/build/session/db.js +67 -8
  62. package/build/session/extract.js +10 -2
  63. package/build/session/project-attribution.d.ts +73 -0
  64. package/build/session/project-attribution.js +231 -0
  65. package/build/store.d.ts +7 -0
  66. package/build/store.js +117 -18
  67. package/build/truncate.d.ts +6 -0
  68. package/build/truncate.js +51 -29
  69. package/build/types.d.ts +8 -0
  70. package/cli.bundle.mjs +157 -136
  71. package/configs/antigravity/GEMINI.md +31 -36
  72. package/configs/claude-code/CLAUDE.md +31 -37
  73. package/configs/codex/AGENTS.md +35 -49
  74. package/configs/cursor/context-mode.mdc +24 -25
  75. package/configs/gemini-cli/GEMINI.md +30 -36
  76. package/configs/jetbrains-copilot/copilot-instructions.md +59 -0
  77. package/configs/jetbrains-copilot/hooks.json +16 -0
  78. package/configs/jetbrains-copilot/mcp.json +8 -0
  79. package/configs/kilo/AGENTS.md +30 -36
  80. package/configs/kiro/KIRO.md +30 -36
  81. package/configs/kiro/agent.json +1 -1
  82. package/configs/openclaw/AGENTS.md +30 -36
  83. package/configs/opencode/AGENTS.md +30 -36
  84. package/configs/pi/AGENTS.md +31 -36
  85. package/configs/qwen-code/QWEN.md +63 -0
  86. package/configs/vscode-copilot/copilot-instructions.md +30 -36
  87. package/configs/zed/AGENTS.md +31 -36
  88. package/hooks/codex/posttooluse.mjs +7 -7
  89. package/hooks/codex/pretooluse.mjs +3 -3
  90. package/hooks/codex/sessionstart.mjs +2 -1
  91. package/hooks/core/formatters.mjs +24 -0
  92. package/hooks/core/routing.mjs +40 -15
  93. package/hooks/core/tool-naming.mjs +2 -0
  94. package/hooks/cursor/posttooluse.mjs +7 -7
  95. package/hooks/cursor/pretooluse.mjs +3 -3
  96. package/hooks/cursor/sessionstart.mjs +2 -1
  97. package/hooks/cursor/stop.mjs +2 -2
  98. package/hooks/ensure-deps.mjs +22 -10
  99. package/hooks/gemini-cli/aftertool.mjs +8 -8
  100. package/hooks/gemini-cli/beforetool.mjs +3 -2
  101. package/hooks/gemini-cli/precompress.mjs +2 -2
  102. package/hooks/gemini-cli/sessionstart.mjs +12 -4
  103. package/hooks/jetbrains-copilot/posttooluse.mjs +61 -0
  104. package/hooks/jetbrains-copilot/precompact.mjs +54 -0
  105. package/hooks/jetbrains-copilot/pretooluse.mjs +27 -0
  106. package/hooks/jetbrains-copilot/sessionstart.mjs +119 -0
  107. package/hooks/kiro/posttooluse.mjs +6 -7
  108. package/hooks/kiro/pretooluse.mjs +3 -2
  109. package/hooks/posttooluse.mjs +8 -8
  110. package/hooks/precompact.mjs +3 -4
  111. package/hooks/pretooluse.mjs +43 -20
  112. package/hooks/routing-block.mjs +35 -33
  113. package/hooks/session-attribution.bundle.mjs +1 -0
  114. package/hooks/session-db.bundle.mjs +27 -8
  115. package/hooks/session-extract.bundle.mjs +2 -1
  116. package/hooks/session-helpers.mjs +44 -3
  117. package/hooks/session-loaders.mjs +37 -0
  118. package/hooks/session-snapshot.bundle.mjs +14 -14
  119. package/hooks/sessionstart.mjs +5 -5
  120. package/hooks/userpromptsubmit.mjs +26 -9
  121. package/hooks/vscode-copilot/posttooluse.mjs +8 -8
  122. package/hooks/vscode-copilot/precompact.mjs +2 -2
  123. package/hooks/vscode-copilot/pretooluse.mjs +3 -2
  124. package/hooks/vscode-copilot/sessionstart.mjs +2 -2
  125. package/insight/server.mjs +262 -32
  126. package/insight/src/lib/api.ts +2 -1
  127. package/insight/src/routes/index.tsx +16 -3
  128. package/insight/src/routes/search.tsx +1 -1
  129. package/openclaw.plugin.json +1 -1
  130. package/package.json +11 -2
  131. package/server.bundle.mjs +117 -99
  132. package/skills/ctx-insight/SKILL.md +1 -1
@@ -10,32 +10,34 @@ import "../ensure-deps.mjs";
10
10
  * Must be fast (<20ms). No network, no LLM, just SQLite writes.
11
11
  */
12
12
 
13
- import { readStdin, getSessionId, getSessionDBPath, getProjectDir, GEMINI_OPTS } from "../session-helpers.mjs";
14
- import { createSessionLoaders } from "../session-loaders.mjs";
13
+ import { readStdin, parseStdin, getSessionId, getSessionDBPath, getInputProjectDir, GEMINI_OPTS } from "../session-helpers.mjs";
14
+ import { createSessionLoaders, attributeAndInsertEvents } from "../session-loaders.mjs";
15
15
  import { appendFileSync } from "node:fs";
16
16
  import { join, dirname } from "node:path";
17
17
  import { homedir } from "node:os";
18
18
  import { fileURLToPath } from "node:url";
19
19
 
20
20
  const HOOK_DIR = dirname(fileURLToPath(import.meta.url));
21
- const { loadSessionDB, loadExtract } = createSessionLoaders(HOOK_DIR);
21
+ const { loadSessionDB, loadExtract, loadProjectAttribution } = createSessionLoaders(HOOK_DIR);
22
22
  const OPTS = GEMINI_OPTS;
23
23
  const DEBUG_LOG = join(homedir(), ".gemini", "context-mode", "aftertool-debug.log");
24
24
 
25
25
  try {
26
26
  const raw = await readStdin();
27
- const input = JSON.parse(raw);
27
+ const input = parseStdin(raw);
28
+ const projectDir = getInputProjectDir(input, OPTS);
28
29
 
29
30
  appendFileSync(DEBUG_LOG, `[${new Date().toISOString()}] CALL: ${input.tool_name}\n`);
30
31
 
31
32
  const { extractEvents } = await loadExtract();
33
+ const { resolveProjectAttributions } = await loadProjectAttribution();
32
34
  const { SessionDB } = await loadSessionDB();
33
35
 
34
36
  const dbPath = getSessionDBPath(OPTS);
35
37
  const db = new SessionDB({ dbPath });
36
38
  const sessionId = getSessionId(input, OPTS);
37
39
 
38
- db.ensureSession(sessionId, getProjectDir(OPTS));
40
+ db.ensureSession(sessionId, projectDir);
39
41
 
40
42
  const events = extractEvents({
41
43
  tool_name: input.tool_name,
@@ -46,9 +48,7 @@ try {
46
48
  tool_output: input.tool_output,
47
49
  });
48
50
 
49
- for (const event of events) {
50
- db.insertEvent(sessionId, event, "AfterTool");
51
- }
51
+ attributeAndInsertEvents(db, sessionId, events, input, projectDir, "AfterTool", resolveProjectAttributions);
52
52
 
53
53
  appendFileSync(DEBUG_LOG, `[${new Date().toISOString()}] OK: ${input.tool_name} → ${events.length} events\n`);
54
54
  db.close();
@@ -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, GEMINI_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 = JSON.parse(raw);
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.GEMINI_PROJECT_DIR || process.env.CLAUDE_PROJECT_DIR, "gemini-cli");
23
+ const decision = routePreToolUse(tool, toolInput, process.env.GEMINI_PROJECT_DIR || process.env.CLAUDE_PROJECT_DIR, "gemini-cli", getSessionId(input, GEMINI_OPTS));
23
24
  const response = formatDecision("gemini-cli", decision);
24
25
  if (response !== null) {
25
26
  process.stdout.write(JSON.stringify(response) + "\n");
@@ -9,7 +9,7 @@ import "../ensure-deps.mjs";
9
9
  * snapshot (<2KB XML), and stores it for injection after compress.
10
10
  */
11
11
 
12
- import { readStdin, getSessionId, getSessionDBPath, GEMINI_OPTS } from "../session-helpers.mjs";
12
+ import { readStdin, parseStdin, getSessionId, getSessionDBPath, GEMINI_OPTS } from "../session-helpers.mjs";
13
13
  import { createSessionLoaders } from "../session-loaders.mjs";
14
14
  import { appendFileSync } from "node:fs";
15
15
  import { join, dirname } from "node:path";
@@ -23,7 +23,7 @@ const DEBUG_LOG = join(homedir(), ".gemini", "context-mode", "precompress-debug.
23
23
 
24
24
  try {
25
25
  const raw = await readStdin();
26
- const input = JSON.parse(raw);
26
+ const input = parseStdin(raw);
27
27
 
28
28
  const { buildResumeSnapshot } = await loadSnapshot();
29
29
  const { SessionDB } = await loadSessionDB();
@@ -18,7 +18,7 @@ const toolNamer = createToolNamer("gemini-cli");
18
18
  const ROUTING_BLOCK = createRoutingBlock(toolNamer);
19
19
  import { writeSessionEventsFile, buildSessionDirective, getSessionEvents, getLatestSessionEvents } from "../session-directive.mjs";
20
20
  import {
21
- readStdin, getSessionId, getSessionDBPath, getSessionEventsPath, getCleanupFlagPath,
21
+ readStdin, parseStdin, getSessionId, getSessionDBPath, getSessionEventsPath, getCleanupFlagPath,
22
22
  getProjectDir, GEMINI_OPTS,
23
23
  } from "../session-helpers.mjs";
24
24
  import { createSessionLoaders } from "../session-loaders.mjs";
@@ -35,7 +35,7 @@ let additionalContext = ROUTING_BLOCK;
35
35
 
36
36
  try {
37
37
  const raw = await readStdin();
38
- const input = JSON.parse(raw);
38
+ const input = parseStdin(raw);
39
39
  const source = input.source ?? "startup";
40
40
 
41
41
  if (source === "compact") {
@@ -118,5 +118,13 @@ try {
118
118
  } catch { /* ignore logging failure */ }
119
119
  }
120
120
 
121
- const output = `SessionStart:compact hook success: Success\nSessionStart hook additional context: \n${additionalContext}`;
122
- process.stdout.write(output);
121
+ // Emit structured JSON rather than plain text so Gemini CLI treats the
122
+ // routing block as hook metadata instead of user-visible output (#299).
123
+ // Matches the format already used by Claude Code and VS Code Copilot
124
+ // SessionStart hooks.
125
+ console.log(JSON.stringify({
126
+ hookSpecificOutput: {
127
+ hookEventName: "SessionStart",
128
+ additionalContext,
129
+ },
130
+ }));
@@ -0,0 +1,61 @@
1
+ #!/usr/bin/env node
2
+ import "../suppress-stderr.mjs";
3
+ import "../ensure-deps.mjs";
4
+ /**
5
+ * JetBrains Copilot PostToolUse hook — session event capture.
6
+ *
7
+ * Captures session events from tool calls (13 categories) and stores
8
+ * them in the per-project SessionDB for later resume snapshot building.
9
+ *
10
+ * Must be fast (<20ms). No network, no LLM, just SQLite writes.
11
+ */
12
+
13
+ import { createSessionLoaders, attributeAndInsertEvents } from "../session-loaders.mjs";
14
+ import { readStdin, parseStdin, getSessionId, getSessionDBPath, getInputProjectDir, JETBRAINS_OPTS } from "../session-helpers.mjs";
15
+ import { appendFileSync } from "node:fs";
16
+ import { join, dirname } from "node:path";
17
+ import { fileURLToPath } from "node:url";
18
+ import { homedir } from "node:os";
19
+
20
+ const HOOK_DIR = dirname(fileURLToPath(import.meta.url));
21
+ const { loadSessionDB, loadExtract, loadProjectAttribution } = createSessionLoaders(HOOK_DIR);
22
+ const OPTS = JETBRAINS_OPTS;
23
+ const DEBUG_LOG = join(homedir(), ".config", "JetBrains", "context-mode", "posttooluse-debug.log");
24
+
25
+ try {
26
+ const raw = await readStdin();
27
+ const input = parseStdin(raw);
28
+ const projectDir = getInputProjectDir(input, OPTS);
29
+
30
+ appendFileSync(DEBUG_LOG, `[${new Date().toISOString()}] CALL: ${input.tool_name}\n`);
31
+
32
+ const { extractEvents } = await loadExtract();
33
+ const { resolveProjectAttributions } = await loadProjectAttribution();
34
+ const { SessionDB } = await loadSessionDB();
35
+
36
+ const dbPath = getSessionDBPath(OPTS);
37
+ const db = new SessionDB({ dbPath });
38
+ const sessionId = getSessionId(input, OPTS);
39
+
40
+ db.ensureSession(sessionId, projectDir);
41
+
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
+ });
50
+
51
+ attributeAndInsertEvents(db, sessionId, events, input, projectDir, "PostToolUse", resolveProjectAttributions);
52
+
53
+ appendFileSync(DEBUG_LOG, `[${new Date().toISOString()}] OK: ${input.tool_name} → ${events.length} events\n`);
54
+ db.close();
55
+ } catch (err) {
56
+ try {
57
+ appendFileSync(DEBUG_LOG, `[${new Date().toISOString()}] ERR: ${err?.message || err}\n`);
58
+ } catch { /* silent */ }
59
+ }
60
+
61
+ // PostToolUse — no stdout output
@@ -0,0 +1,54 @@
1
+ #!/usr/bin/env node
2
+ import "../suppress-stderr.mjs";
3
+ import "../ensure-deps.mjs";
4
+ /**
5
+ * JetBrains Copilot PreCompact hook — snapshot generation.
6
+ *
7
+ * Triggered when JetBrains Copilot is about to compact the conversation.
8
+ * Reads all captured session events, builds a priority-sorted resume
9
+ * snapshot (<2KB XML), and stores it for injection after compact.
10
+ */
11
+
12
+ import { createSessionLoaders } from "../session-loaders.mjs";
13
+ import { readStdin, parseStdin, getSessionId, getSessionDBPath, JETBRAINS_OPTS } from "../session-helpers.mjs";
14
+ import { appendFileSync } from "node:fs";
15
+ import { join, dirname } from "node:path";
16
+ import { fileURLToPath } from "node:url";
17
+ import { homedir } from "node:os";
18
+
19
+ const HOOK_DIR = dirname(fileURLToPath(import.meta.url));
20
+ const { loadSessionDB, loadSnapshot } = createSessionLoaders(HOOK_DIR);
21
+ const OPTS = JETBRAINS_OPTS;
22
+ const DEBUG_LOG = join(homedir(), ".config", "JetBrains", "context-mode", "precompact-debug.log");
23
+
24
+ try {
25
+ const raw = await readStdin();
26
+ const input = parseStdin(raw);
27
+
28
+ const { buildResumeSnapshot } = await loadSnapshot();
29
+ const { SessionDB } = await loadSessionDB();
30
+
31
+ const dbPath = getSessionDBPath(OPTS);
32
+ const db = new SessionDB({ dbPath });
33
+ const sessionId = getSessionId(input, OPTS);
34
+
35
+ const events = db.getEvents(sessionId);
36
+
37
+ if (events.length > 0) {
38
+ const stats = db.getSessionStats(sessionId);
39
+ const snapshot = buildResumeSnapshot(events, {
40
+ compactCount: (stats?.compact_count ?? 0) + 1,
41
+ });
42
+
43
+ db.upsertResume(sessionId, snapshot, events.length);
44
+ db.incrementCompactCount(sessionId);
45
+ }
46
+
47
+ db.close();
48
+ } catch (err) {
49
+ try {
50
+ appendFileSync(DEBUG_LOG, `[${new Date().toISOString()}] ${err?.message || err}\n`);
51
+ } catch { /* silent */ }
52
+ }
53
+
54
+ // PreCompact — no stdout output needed
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env node
2
+ import "../suppress-stderr.mjs";
3
+ /**
4
+ * JetBrains Copilot PreToolUse hook for context-mode
5
+ * Thin wrapper — uses shared routing core, no self-heal, no Claude Code-specific logic.
6
+ */
7
+
8
+ import { dirname, resolve } from "node:path";
9
+ import { fileURLToPath } from "node:url";
10
+ import { readStdin } from "../core/stdin.mjs";
11
+ import { routePreToolUse, initSecurity } from "../core/routing.mjs";
12
+ import { formatDecision } from "../core/formatters.mjs";
13
+ import { parseStdin, getSessionId, JETBRAINS_OPTS } from "../session-helpers.mjs";
14
+
15
+ const __hookDir = dirname(fileURLToPath(import.meta.url));
16
+ await initSecurity(resolve(__hookDir, "..", "..", "build"));
17
+
18
+ const raw = await readStdin();
19
+ const input = parseStdin(raw);
20
+ const tool = input.tool_name ?? "";
21
+ const toolInput = input.tool_input ?? {};
22
+
23
+ const decision = routePreToolUse(tool, toolInput, process.env.IDEA_INITIAL_DIRECTORY || process.env.CLAUDE_PROJECT_DIR, "jetbrains-copilot", getSessionId(input, JETBRAINS_OPTS));
24
+ const response = formatDecision("jetbrains-copilot", decision);
25
+ if (response !== null) {
26
+ process.stdout.write(JSON.stringify(response) + "\n");
27
+ }
@@ -0,0 +1,119 @@
1
+ #!/usr/bin/env node
2
+ import "../suppress-stderr.mjs";
3
+ import "../ensure-deps.mjs";
4
+ /**
5
+ * JetBrains Copilot SessionStart hook for context-mode
6
+ *
7
+ * Session lifecycle management:
8
+ * - "startup" → Cleanup old sessions, capture instruction file rules
9
+ * - "compact" → Write events file, inject session knowledge directive
10
+ * - "resume" → Load previous session events, inject directive
11
+ * - "clear" → No action needed
12
+ */
13
+
14
+ import { createSessionLoaders } from "../session-loaders.mjs";
15
+ import { createRoutingBlock } from "../routing-block.mjs";
16
+ import { createToolNamer } from "../core/tool-naming.mjs";
17
+
18
+ const toolNamer = createToolNamer("jetbrains-copilot");
19
+ const ROUTING_BLOCK = createRoutingBlock(toolNamer);
20
+ import { writeSessionEventsFile, buildSessionDirective, getSessionEvents, getLatestSessionEvents } from "../session-directive.mjs";
21
+ import {
22
+ readStdin, parseStdin, getSessionId, getSessionDBPath, getSessionEventsPath, getCleanupFlagPath,
23
+ getProjectDir, JETBRAINS_OPTS,
24
+ } from "../session-helpers.mjs";
25
+ import { join } from "node:path";
26
+ import { readFileSync, unlinkSync } from "node:fs";
27
+ import { fileURLToPath, pathToFileURL } from "node:url";
28
+ import { homedir } from "node:os";
29
+
30
+ const HOOK_DIR = fileURLToPath(new URL(".", import.meta.url));
31
+ const { loadSessionDB } = createSessionLoaders(HOOK_DIR);
32
+ const OPTS = JETBRAINS_OPTS;
33
+
34
+ let additionalContext = ROUTING_BLOCK;
35
+
36
+ try {
37
+ const raw = await readStdin();
38
+ const input = parseStdin(raw);
39
+ const source = input.source ?? "startup";
40
+
41
+ if (source === "compact") {
42
+ const { SessionDB } = await loadSessionDB();
43
+ const dbPath = getSessionDBPath(OPTS);
44
+ const db = new SessionDB({ dbPath });
45
+ const sessionId = getSessionId(input, OPTS);
46
+ const resume = db.getResume(sessionId);
47
+
48
+ if (resume && !resume.consumed) {
49
+ db.markResumeConsumed(sessionId);
50
+ }
51
+
52
+ const events = getSessionEvents(db, sessionId);
53
+ if (events.length > 0) {
54
+ const eventMeta = writeSessionEventsFile(events, getSessionEventsPath(OPTS));
55
+ additionalContext += buildSessionDirective("compact", eventMeta, toolNamer);
56
+ }
57
+
58
+ db.close();
59
+ } else if (source === "resume") {
60
+ try { unlinkSync(getCleanupFlagPath(OPTS)); } catch { /* no flag */ }
61
+
62
+ const { SessionDB } = await loadSessionDB();
63
+ const dbPath = getSessionDBPath(OPTS);
64
+ const db = new SessionDB({ dbPath });
65
+
66
+ const events = getLatestSessionEvents(db);
67
+ if (events.length > 0) {
68
+ const eventMeta = writeSessionEventsFile(events, getSessionEventsPath(OPTS));
69
+ additionalContext += buildSessionDirective("resume", eventMeta, toolNamer);
70
+ }
71
+
72
+ db.close();
73
+ } else if (source === "startup") {
74
+ const { SessionDB } = await loadSessionDB();
75
+ const dbPath = getSessionDBPath(OPTS);
76
+ const db = new SessionDB({ dbPath });
77
+ try { unlinkSync(getSessionEventsPath(OPTS)); } catch { /* no stale file */ }
78
+
79
+ db.cleanupOldSessions(7);
80
+ db.db.exec(`DELETE FROM session_events WHERE session_id NOT IN (SELECT session_id FROM session_meta)`);
81
+
82
+ const sessionId = getSessionId(input, OPTS);
83
+ const projectDir = getProjectDir(OPTS);
84
+ db.ensureSession(sessionId, projectDir);
85
+
86
+ const ruleFilePaths = [
87
+ join(projectDir, ".github", "copilot-instructions.md"),
88
+ ];
89
+ for (const p of ruleFilePaths) {
90
+ try {
91
+ const content = readFileSync(p, "utf-8");
92
+ if (content.trim()) {
93
+ db.insertEvent(sessionId, { type: "rule", category: "rule", data: p, priority: 1 });
94
+ db.insertEvent(sessionId, { type: "rule_content", category: "rule", data: content, priority: 1 });
95
+ }
96
+ } catch { /* file doesn't exist — skip */ }
97
+ }
98
+
99
+ db.close();
100
+ }
101
+ // "clear" — no action needed
102
+ } catch (err) {
103
+ try {
104
+ const { appendFileSync } = await import("node:fs");
105
+ const { join: pjoin } = await import("node:path");
106
+ const { homedir: hd } = await import("node:os");
107
+ appendFileSync(
108
+ pjoin(hd(), ".config", "JetBrains", "context-mode", "sessionstart-debug.log"),
109
+ `[${new Date().toISOString()}] ${err?.message || err}\n${err?.stack || ""}\n`,
110
+ );
111
+ } catch { /* ignore logging failure */ }
112
+ }
113
+
114
+ console.log(JSON.stringify({
115
+ hookSpecificOutput: {
116
+ hookEventName: "SessionStart",
117
+ additionalContext,
118
+ },
119
+ }));
@@ -8,20 +8,21 @@ import "../ensure-deps.mjs";
8
8
  * Source: https://kiro.dev/docs/cli/hooks/
9
9
  */
10
10
 
11
- import { readStdin, getSessionId, getSessionDBPath, getInputProjectDir, KIRO_OPTS } from "../session-helpers.mjs";
12
- import { createSessionLoaders } from "../session-loaders.mjs";
11
+ import { readStdin, parseStdin, getSessionId, getSessionDBPath, getInputProjectDir, KIRO_OPTS } from "../session-helpers.mjs";
12
+ import { createSessionLoaders, attributeAndInsertEvents } from "../session-loaders.mjs";
13
13
  import { dirname } from "node:path";
14
14
  import { fileURLToPath } from "node:url";
15
15
 
16
16
  const HOOK_DIR = dirname(fileURLToPath(import.meta.url));
17
- const { loadSessionDB, loadExtract } = createSessionLoaders(HOOK_DIR);
17
+ const { loadSessionDB, loadExtract, loadProjectAttribution } = createSessionLoaders(HOOK_DIR);
18
18
  const OPTS = KIRO_OPTS;
19
19
 
20
20
  try {
21
21
  const raw = await readStdin();
22
- const input = JSON.parse(raw);
22
+ const input = parseStdin(raw);
23
23
 
24
24
  const { extractEvents } = await loadExtract();
25
+ const { resolveProjectAttributions } = await loadProjectAttribution();
25
26
  const { SessionDB } = await loadSessionDB();
26
27
 
27
28
  const dbPath = getSessionDBPath(OPTS);
@@ -40,9 +41,7 @@ try {
40
41
  tool_output: input.tool_output,
41
42
  });
42
43
 
43
- for (const event of events) {
44
- db.insertEvent(sessionId, event, "PostToolUse");
45
- }
44
+ attributeAndInsertEvents(db, sessionId, events, input, projectDir, "PostToolUse", resolveProjectAttributions);
46
45
 
47
46
  db.close();
48
47
  } catch {
@@ -13,18 +13,19 @@ import { dirname, resolve } from "node:path";
13
13
  import { fileURLToPath } from "node:url";
14
14
  import { readStdin } from "../core/stdin.mjs";
15
15
  import { routePreToolUse, initSecurity } from "../core/routing.mjs";
16
+ import { parseStdin, getSessionId, KIRO_OPTS } from "../session-helpers.mjs";
16
17
 
17
18
  const __hookDir = dirname(fileURLToPath(import.meta.url));
18
19
  await initSecurity(resolve(__hookDir, "..", "..", "build"));
19
20
 
20
21
  const raw = await readStdin();
21
- const input = JSON.parse(raw);
22
+ const input = parseStdin(raw);
22
23
  // Kiro stdin: { hook_event_name, cwd, tool_name, tool_input }
23
24
  const tool = input.tool_name ?? "";
24
25
  const toolInput = input.tool_input ?? {};
25
26
  const projectDir = input.cwd ?? process.cwd();
26
27
 
27
- const decision = routePreToolUse(tool, toolInput, projectDir, "kiro");
28
+ const decision = routePreToolUse(tool, toolInput, projectDir, "kiro", getSessionId(input, KIRO_OPTS));
28
29
 
29
30
  if (!decision) process.exit(0);
30
31
 
@@ -10,21 +10,23 @@ import "./ensure-deps.mjs";
10
10
  * Must be fast (<20ms). No network, no LLM, just SQLite writes.
11
11
  */
12
12
 
13
- import { readStdin, getSessionId, getSessionDBPath } from "./session-helpers.mjs";
14
- import { createSessionLoaders } from "./session-loaders.mjs";
13
+ import { readStdin, parseStdin, getSessionId, getSessionDBPath, getInputProjectDir } from "./session-helpers.mjs";
14
+ import { createSessionLoaders, attributeAndInsertEvents } from "./session-loaders.mjs";
15
15
  import { dirname } from "node:path";
16
16
  import { fileURLToPath } from "node:url";
17
17
 
18
18
  // Resolve absolute path for imports — relative dynamic imports can fail
19
19
  // when Claude Code invokes hooks from a different working directory.
20
20
  const HOOK_DIR = dirname(fileURLToPath(import.meta.url));
21
- const { loadSessionDB, loadExtract } = createSessionLoaders(HOOK_DIR);
21
+ const { loadSessionDB, loadExtract, loadProjectAttribution } = createSessionLoaders(HOOK_DIR);
22
22
 
23
23
  try {
24
24
  const raw = await readStdin();
25
- const input = JSON.parse(raw);
25
+ const input = parseStdin(raw);
26
+ const projectDir = getInputProjectDir(input);
26
27
 
27
28
  const { extractEvents } = await loadExtract();
29
+ const { resolveProjectAttributions } = await loadProjectAttribution();
28
30
  const { SessionDB } = await loadSessionDB();
29
31
 
30
32
  const dbPath = getSessionDBPath();
@@ -32,7 +34,7 @@ try {
32
34
  const sessionId = getSessionId(input);
33
35
 
34
36
  // Ensure session meta exists
35
- db.ensureSession(sessionId, process.env.CLAUDE_PROJECT_DIR || process.cwd());
37
+ db.ensureSession(sessionId, projectDir);
36
38
 
37
39
  // Extract and store events
38
40
  const events = extractEvents({
@@ -44,9 +46,7 @@ try {
44
46
  tool_output: input.tool_output,
45
47
  });
46
48
 
47
- for (const event of events) {
48
- db.insertEvent(sessionId, event, "PostToolUse");
49
- }
49
+ attributeAndInsertEvents(db, sessionId, events, input, projectDir, "PostToolUse", resolveProjectAttributions);
50
50
 
51
51
  db.close();
52
52
  } catch {
@@ -9,21 +9,20 @@ import "./ensure-deps.mjs";
9
9
  * snapshot (<2KB XML), and stores it for injection after compact.
10
10
  */
11
11
 
12
- import { readStdin, getSessionId, getSessionDBPath } from "./session-helpers.mjs";
12
+ import { readStdin, parseStdin, getSessionId, getSessionDBPath, resolveConfigDir } from "./session-helpers.mjs";
13
13
  import { createSessionLoaders } from "./session-loaders.mjs";
14
14
  import { appendFileSync } from "node:fs";
15
15
  import { join, dirname } from "node:path";
16
- import { homedir } from "node:os";
17
16
  import { fileURLToPath } from "node:url";
18
17
 
19
18
  // Resolve absolute path for imports
20
19
  const HOOK_DIR = dirname(fileURLToPath(import.meta.url));
21
20
  const { loadSessionDB, loadSnapshot } = createSessionLoaders(HOOK_DIR);
22
- const DEBUG_LOG = join(homedir(), ".claude", "context-mode", "precompact-debug.log");
21
+ const DEBUG_LOG = join(resolveConfigDir(), "context-mode", "precompact-debug.log");
23
22
 
24
23
  try {
25
24
  const raw = await readStdin();
26
- const input = JSON.parse(raw);
25
+ const input = parseStdin(raw);
27
26
 
28
27
  const { buildResumeSnapshot } = await loadSnapshot();
29
28
  const { SessionDB } = await loadSessionDB();
@@ -18,6 +18,7 @@ import { homedir, tmpdir } from "node:os";
18
18
  import { readStdin } from "./core/stdin.mjs";
19
19
  import { routePreToolUse, initSecurity } from "./core/routing.mjs";
20
20
  import { formatDecision } from "./core/formatters.mjs";
21
+ import { parseStdin, getSessionId, resolveConfigDir } from "./session-helpers.mjs";
21
22
 
22
23
  // ─── Manual recursive copy (avoids cpSync libuv crash on non-ASCII paths, Windows + Node 24) ───
23
24
  function copyDirSync(src, dest) {
@@ -73,7 +74,7 @@ try {
73
74
 
74
75
  // 2. Update installed_plugins.json → point to correct version dir
75
76
  // Skip if not present (e.g. CI / non-Claude-Code environments)
76
- const ipPath = resolve(homedir(), ".claude", "plugins", "installed_plugins.json");
77
+ const ipPath = resolve(resolveConfigDir(), "plugins", "installed_plugins.json");
77
78
  if (existsSync(ipPath)) {
78
79
  const ip = JSON.parse(readFileSync(ipPath, "utf-8"));
79
80
  for (const [key, entries] of Object.entries(ip.plugins || {})) {
@@ -90,30 +91,52 @@ try {
90
91
  // 3. Update hook paths + matcher in settings.json for ALL hook types (#187)
91
92
  // Previously only fixed PreToolUse — SessionStart, PostToolUse, PreCompact,
92
93
  // UserPromptSubmit paths remained stale after marketplace auto-update.
93
- const settingsPath = resolve(homedir(), ".claude", "settings.json");
94
+ const settingsPath = resolve(resolveConfigDir(), "settings.json");
94
95
  try {
95
96
  const settings = JSON.parse(readFileSync(settingsPath, "utf-8"));
96
97
  const allHooks = settings.hooks || {};
97
98
  let changed = false;
98
99
 
99
- for (const hookType of Object.keys(allHooks)) {
100
- const entries = allHooks[hookType];
101
- if (!Array.isArray(entries)) continue;
102
-
103
- for (const entry of entries) {
104
- // Fix deprecated Task-only matcher (PreToolUse only)
105
- if (hookType === "PreToolUse" && entry.matcher?.includes("Task") && !entry.matcher.includes("Agent")) {
106
- entry.matcher = entry.matcher.replace("Task", "Agent|Task");
100
+ // If hooks.json is present, the plugin system owns hook registration.
101
+ // Remove any settings.json context-mode entries to prevent duplicate concurrent
102
+ // hook processes that cause "non-blocking hook error" on every tool call.
103
+ const hooksJsonPath = resolve(myRoot, "hooks", "hooks.json");
104
+ if (existsSync(hooksJsonPath)) {
105
+ for (const hookType of Object.keys(allHooks)) {
106
+ const entries = allHooks[hookType];
107
+ if (!Array.isArray(entries)) continue;
108
+ const filtered = entries.filter(
109
+ (entry) =>
110
+ !entry.hooks?.some(
111
+ (h) => h.command?.includes(".mjs") && h.command?.includes("context-mode"),
112
+ ),
113
+ );
114
+ if (filtered.length !== entries.length) {
115
+ allHooks[hookType] = filtered;
107
116
  changed = true;
108
117
  }
109
- // Rewrite stale context-mode hook paths to point to current version
110
- for (const h of (entry.hooks || [])) {
111
- if (h.command && h.command.includes(".mjs") && h.command.includes("context-mode") && !h.command.includes(targetDir)) {
112
- // Extract the script filename (e.g., sessionstart.mjs, pretooluse.mjs)
113
- const scriptMatch = h.command.match(/([a-z]+\.mjs)\s*"?\s*$/);
114
- if (scriptMatch) {
115
- h.command = "node " + resolve(targetDir, "hooks", scriptMatch[1]);
116
- changed = true;
118
+ }
119
+ } else {
120
+ // Legacy: hooks.json absent rewrite stale paths to current version dir.
121
+ for (const hookType of Object.keys(allHooks)) {
122
+ const entries = allHooks[hookType];
123
+ if (!Array.isArray(entries)) continue;
124
+
125
+ for (const entry of entries) {
126
+ // Fix deprecated Task-only matcher (PreToolUse only)
127
+ if (hookType === "PreToolUse" && entry.matcher?.includes("Task") && !entry.matcher.includes("Agent")) {
128
+ entry.matcher = entry.matcher.replace("Task", "Agent|Task");
129
+ changed = true;
130
+ }
131
+ // Rewrite stale context-mode hook paths to point to current version
132
+ for (const h of (entry.hooks || [])) {
133
+ if (h.command && h.command.includes(".mjs") && h.command.includes("context-mode") && !h.command.includes(targetDir)) {
134
+ // Extract the script filename (e.g., sessionstart.mjs, pretooluse.mjs)
135
+ const scriptMatch = h.command.match(/([a-z]+\.mjs)\s*"?\s*$/);
136
+ if (scriptMatch) {
137
+ h.command = "node " + resolve(targetDir, "hooks", scriptMatch[1]);
138
+ changed = true;
139
+ }
117
140
  }
118
141
  }
119
142
  }
@@ -136,12 +159,12 @@ await initSecurity(resolve(__hookDir, "..", "build"));
136
159
 
137
160
  // ─── Read stdin ───
138
161
  const raw = await readStdin();
139
- const input = JSON.parse(raw);
162
+ const input = parseStdin(raw);
140
163
  const tool = input.tool_name ?? "";
141
164
  const toolInput = input.tool_input ?? {};
142
165
 
143
166
  // ─── Route and format response ───
144
- const decision = routePreToolUse(tool, toolInput, process.env.CLAUDE_PROJECT_DIR, "claude-code");
167
+ const decision = routePreToolUse(tool, toolInput, process.env.CLAUDE_PROJECT_DIR, "claude-code", getSessionId(input));
145
168
  const response = formatDecision("claude-code", decision);
146
169
  if (response !== null) {
147
170
  process.stdout.write(JSON.stringify(response) + "\n");