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.
Files changed (128) 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 +12 -140
  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 +13 -0
  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/runtime.js +24 -9
  54. package/build/security.d.ts +17 -1
  55. package/build/security.js +40 -6
  56. package/build/server.js +41 -9
  57. package/build/session/analytics.d.ts +8 -7
  58. package/build/session/analytics.js +95 -75
  59. package/build/session/db.d.ts +10 -1
  60. package/build/session/db.js +67 -8
  61. package/build/session/extract.js +10 -2
  62. package/build/session/project-attribution.d.ts +73 -0
  63. package/build/session/project-attribution.js +231 -0
  64. package/build/store.d.ts +4 -0
  65. package/build/store.js +58 -9
  66. package/build/types.d.ts +8 -0
  67. package/cli.bundle.mjs +135 -121
  68. package/configs/antigravity/GEMINI.md +31 -36
  69. package/configs/claude-code/CLAUDE.md +31 -37
  70. package/configs/codex/AGENTS.md +35 -49
  71. package/configs/cursor/context-mode.mdc +24 -25
  72. package/configs/gemini-cli/GEMINI.md +30 -36
  73. package/configs/jetbrains-copilot/copilot-instructions.md +59 -0
  74. package/configs/jetbrains-copilot/hooks.json +16 -0
  75. package/configs/jetbrains-copilot/mcp.json +8 -0
  76. package/configs/kilo/AGENTS.md +30 -36
  77. package/configs/kiro/KIRO.md +30 -36
  78. package/configs/kiro/agent.json +1 -1
  79. package/configs/openclaw/AGENTS.md +30 -36
  80. package/configs/opencode/AGENTS.md +30 -36
  81. package/configs/pi/AGENTS.md +31 -36
  82. package/configs/qwen-code/QWEN.md +63 -0
  83. package/configs/vscode-copilot/copilot-instructions.md +30 -36
  84. package/configs/zed/AGENTS.md +31 -36
  85. package/hooks/codex/posttooluse.mjs +7 -7
  86. package/hooks/codex/pretooluse.mjs +3 -3
  87. package/hooks/codex/sessionstart.mjs +2 -1
  88. package/hooks/core/formatters.mjs +24 -0
  89. package/hooks/core/routing.mjs +40 -15
  90. package/hooks/core/tool-naming.mjs +2 -0
  91. package/hooks/cursor/posttooluse.mjs +7 -7
  92. package/hooks/cursor/pretooluse.mjs +3 -3
  93. package/hooks/cursor/sessionstart.mjs +2 -1
  94. package/hooks/cursor/stop.mjs +2 -2
  95. package/hooks/ensure-deps.mjs +22 -10
  96. package/hooks/gemini-cli/aftertool.mjs +8 -8
  97. package/hooks/gemini-cli/beforetool.mjs +3 -2
  98. package/hooks/gemini-cli/precompress.mjs +2 -2
  99. package/hooks/gemini-cli/sessionstart.mjs +12 -4
  100. package/hooks/jetbrains-copilot/posttooluse.mjs +61 -0
  101. package/hooks/jetbrains-copilot/precompact.mjs +54 -0
  102. package/hooks/jetbrains-copilot/pretooluse.mjs +27 -0
  103. package/hooks/jetbrains-copilot/sessionstart.mjs +119 -0
  104. package/hooks/kiro/posttooluse.mjs +6 -7
  105. package/hooks/kiro/pretooluse.mjs +3 -2
  106. package/hooks/posttooluse.mjs +8 -8
  107. package/hooks/precompact.mjs +3 -4
  108. package/hooks/pretooluse.mjs +5 -4
  109. package/hooks/routing-block.mjs +35 -33
  110. package/hooks/session-attribution.bundle.mjs +1 -0
  111. package/hooks/session-db.bundle.mjs +27 -8
  112. package/hooks/session-extract.bundle.mjs +2 -1
  113. package/hooks/session-helpers.mjs +44 -3
  114. package/hooks/session-loaders.mjs +37 -0
  115. package/hooks/sessionstart.mjs +5 -5
  116. package/hooks/userpromptsubmit.mjs +26 -9
  117. package/hooks/vscode-copilot/posttooluse.mjs +8 -8
  118. package/hooks/vscode-copilot/precompact.mjs +2 -2
  119. package/hooks/vscode-copilot/pretooluse.mjs +3 -2
  120. package/hooks/vscode-copilot/sessionstart.mjs +2 -2
  121. package/insight/server.mjs +237 -25
  122. package/insight/src/lib/api.ts +2 -1
  123. package/insight/src/routes/index.tsx +16 -3
  124. package/insight/src/routes/search.tsx +1 -1
  125. package/openclaw.plugin.json +1 -1
  126. package/package.json +11 -2
  127. package/server.bundle.mjs +94 -80
  128. 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,7 +91,7 @@ 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 || {};
@@ -158,12 +159,12 @@ await initSecurity(resolve(__hookDir, "..", "build"));
158
159
 
159
160
  // ─── Read stdin ───
160
161
  const raw = await readStdin();
161
- const input = JSON.parse(raw);
162
+ const input = parseStdin(raw);
162
163
  const tool = input.tool_name ?? "";
163
164
  const toolInput = input.tool_input ?? {};
164
165
 
165
166
  // ─── Route and format response ───
166
- 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));
167
168
  const response = formatDecision("claude-code", decision);
168
169
  if (response !== null) {
169
170
  process.stdout.write(JSON.stringify(response) + "\n");