context-mode 1.0.89 → 1.0.90
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/.openclaw-plugin/openclaw.plugin.json +1 -1
- package/.openclaw-plugin/package.json +1 -1
- package/README.md +184 -60
- package/build/adapters/antigravity/index.d.ts +3 -5
- package/build/adapters/antigravity/index.js +7 -35
- package/build/adapters/base.d.ts +27 -0
- package/build/adapters/base.js +59 -0
- package/build/adapters/claude-code/index.d.ts +9 -25
- package/build/adapters/claude-code/index.js +12 -140
- package/build/adapters/claude-code-base.d.ts +49 -0
- package/build/adapters/claude-code-base.js +113 -0
- package/build/adapters/client-map.js +5 -0
- package/build/adapters/codex/hooks.d.ts +21 -14
- package/build/adapters/codex/hooks.js +22 -15
- package/build/adapters/codex/index.d.ts +6 -10
- package/build/adapters/codex/index.js +13 -43
- package/build/adapters/copilot-base.d.ts +78 -0
- package/build/adapters/copilot-base.js +281 -0
- package/build/adapters/cursor/index.d.ts +3 -5
- package/build/adapters/cursor/index.js +6 -34
- package/build/adapters/detect.d.ts +7 -0
- package/build/adapters/detect.js +57 -56
- package/build/adapters/gemini-cli/index.d.ts +3 -5
- package/build/adapters/gemini-cli/index.js +7 -35
- package/build/adapters/jetbrains-copilot/config.d.ts +8 -0
- package/build/adapters/jetbrains-copilot/config.js +8 -0
- package/build/adapters/jetbrains-copilot/hooks.d.ts +51 -0
- package/build/adapters/jetbrains-copilot/hooks.js +82 -0
- package/build/adapters/jetbrains-copilot/index.d.ts +24 -0
- package/build/adapters/jetbrains-copilot/index.js +119 -0
- package/build/adapters/kiro/hooks.d.ts +14 -0
- package/build/adapters/kiro/hooks.js +23 -0
- package/build/adapters/kiro/index.d.ts +3 -5
- package/build/adapters/kiro/index.js +10 -38
- package/build/adapters/openclaw/index.d.ts +3 -4
- package/build/adapters/openclaw/index.js +6 -22
- package/build/adapters/opencode/index.d.ts +2 -3
- package/build/adapters/opencode/index.js +5 -16
- package/build/adapters/qwen-code/index.d.ts +39 -0
- package/build/adapters/qwen-code/index.js +199 -0
- package/build/adapters/types.d.ts +1 -1
- package/build/adapters/vscode-copilot/index.d.ts +16 -46
- package/build/adapters/vscode-copilot/index.js +29 -320
- package/build/adapters/zed/index.d.ts +3 -5
- package/build/adapters/zed/index.js +7 -35
- package/build/cli.js +13 -0
- package/build/lifecycle.d.ts +23 -0
- package/build/lifecycle.js +54 -13
- package/build/opencode-plugin.d.ts +19 -7
- package/build/opencode-plugin.js +19 -7
- package/build/runtime.js +24 -9
- package/build/security.d.ts +17 -1
- package/build/security.js +40 -6
- package/build/server.js +41 -9
- package/build/session/analytics.d.ts +8 -7
- package/build/session/analytics.js +95 -75
- package/build/session/db.d.ts +10 -1
- package/build/session/db.js +67 -8
- package/build/session/extract.js +10 -2
- package/build/session/project-attribution.d.ts +73 -0
- package/build/session/project-attribution.js +231 -0
- package/build/store.d.ts +4 -0
- package/build/store.js +58 -9
- package/build/types.d.ts +8 -0
- package/cli.bundle.mjs +135 -121
- package/configs/antigravity/GEMINI.md +31 -36
- package/configs/claude-code/CLAUDE.md +31 -37
- package/configs/codex/AGENTS.md +35 -49
- package/configs/cursor/context-mode.mdc +24 -25
- package/configs/gemini-cli/GEMINI.md +30 -36
- package/configs/jetbrains-copilot/copilot-instructions.md +59 -0
- package/configs/jetbrains-copilot/hooks.json +16 -0
- package/configs/jetbrains-copilot/mcp.json +8 -0
- package/configs/kilo/AGENTS.md +30 -36
- package/configs/kiro/KIRO.md +30 -36
- package/configs/kiro/agent.json +1 -1
- package/configs/openclaw/AGENTS.md +30 -36
- package/configs/opencode/AGENTS.md +30 -36
- package/configs/pi/AGENTS.md +31 -36
- package/configs/qwen-code/QWEN.md +63 -0
- package/configs/vscode-copilot/copilot-instructions.md +30 -36
- package/configs/zed/AGENTS.md +31 -36
- package/hooks/codex/posttooluse.mjs +7 -7
- package/hooks/codex/pretooluse.mjs +3 -3
- package/hooks/codex/sessionstart.mjs +2 -1
- package/hooks/core/formatters.mjs +24 -0
- package/hooks/core/routing.mjs +40 -15
- package/hooks/core/tool-naming.mjs +2 -0
- package/hooks/cursor/posttooluse.mjs +7 -7
- package/hooks/cursor/pretooluse.mjs +3 -3
- package/hooks/cursor/sessionstart.mjs +2 -1
- package/hooks/cursor/stop.mjs +2 -2
- package/hooks/ensure-deps.mjs +22 -10
- package/hooks/gemini-cli/aftertool.mjs +8 -8
- package/hooks/gemini-cli/beforetool.mjs +3 -2
- package/hooks/gemini-cli/precompress.mjs +2 -2
- package/hooks/gemini-cli/sessionstart.mjs +12 -4
- package/hooks/jetbrains-copilot/posttooluse.mjs +61 -0
- package/hooks/jetbrains-copilot/precompact.mjs +54 -0
- package/hooks/jetbrains-copilot/pretooluse.mjs +27 -0
- package/hooks/jetbrains-copilot/sessionstart.mjs +119 -0
- package/hooks/kiro/posttooluse.mjs +6 -7
- package/hooks/kiro/pretooluse.mjs +3 -2
- package/hooks/posttooluse.mjs +8 -8
- package/hooks/precompact.mjs +3 -4
- package/hooks/pretooluse.mjs +5 -4
- package/hooks/routing-block.mjs +35 -33
- package/hooks/session-attribution.bundle.mjs +1 -0
- package/hooks/session-db.bundle.mjs +27 -8
- package/hooks/session-extract.bundle.mjs +2 -1
- package/hooks/session-helpers.mjs +44 -3
- package/hooks/session-loaders.mjs +37 -0
- package/hooks/sessionstart.mjs +5 -5
- package/hooks/userpromptsubmit.mjs +26 -9
- package/hooks/vscode-copilot/posttooluse.mjs +8 -8
- package/hooks/vscode-copilot/precompact.mjs +2 -2
- package/hooks/vscode-copilot/pretooluse.mjs +3 -2
- package/hooks/vscode-copilot/sessionstart.mjs +2 -2
- package/insight/server.mjs +237 -25
- package/insight/src/lib/api.ts +2 -1
- package/insight/src/routes/index.tsx +16 -3
- package/insight/src/routes/search.tsx +1 -1
- package/openclaw.plugin.json +1 -1
- package/package.json +11 -2
- package/server.bundle.mjs +94 -80
- package/skills/ctx-insight/SKILL.md +1 -1
package/hooks/routing-block.mjs
CHANGED
|
@@ -18,81 +18,83 @@ export function createRoutingBlock(t, options = {}) {
|
|
|
18
18
|
return `
|
|
19
19
|
<context_window_protection>
|
|
20
20
|
<priority_instructions>
|
|
21
|
-
Raw tool output floods
|
|
21
|
+
Raw tool output floods context window. MUST use context-mode MCP tools. Keep raw data in sandbox.
|
|
22
22
|
</priority_instructions>
|
|
23
23
|
|
|
24
24
|
<tool_selection_hierarchy>
|
|
25
25
|
1. GATHER: ${t("ctx_batch_execute")}(commands, queries)
|
|
26
|
-
- Primary tool
|
|
27
|
-
-
|
|
28
|
-
-
|
|
29
|
-
- label becomes the FTS5 chunk title — use descriptive labels for better search.
|
|
26
|
+
- Primary research tool. Runs commands, auto-indexes, searches. ONE call replaces many steps.
|
|
27
|
+
- Each command: {label: "section header", command: "shell command"}
|
|
28
|
+
- label becomes FTS5 chunk title — descriptive labels improve search.
|
|
30
29
|
2. FOLLOW-UP: ${t("ctx_search")}(queries: ["q1", "q2", ...])
|
|
31
|
-
-
|
|
30
|
+
- All follow-up questions. ONE call, many queries.
|
|
32
31
|
3. PROCESSING: ${t("ctx_execute")}(language, code) | ${t("ctx_execute_file")}(path, language, code)
|
|
33
|
-
-
|
|
32
|
+
- API calls, log analysis, data processing.
|
|
34
33
|
</tool_selection_hierarchy>
|
|
35
34
|
|
|
36
35
|
<forbidden_actions>
|
|
37
|
-
-
|
|
38
|
-
-
|
|
39
|
-
-
|
|
40
|
-
- Bash
|
|
41
|
-
-
|
|
42
|
-
ctx_execute is for
|
|
36
|
+
- NO Bash for commands producing >20 lines output.
|
|
37
|
+
- NO Read for analysis — use execute_file. Read IS correct for files you intend to Edit.
|
|
38
|
+
- NO WebFetch — use ${t("ctx_fetch_and_index")}.
|
|
39
|
+
- Bash ONLY for git/mkdir/rm/mv/navigation.
|
|
40
|
+
- NO ${t("ctx_execute")} or ${t("ctx_execute_file")} for file creation/modification.
|
|
41
|
+
ctx_execute is for analysis, processing, computation only.
|
|
43
42
|
</forbidden_actions>
|
|
44
43
|
|
|
45
44
|
<file_writing_policy>
|
|
46
|
-
ALWAYS use
|
|
47
|
-
NEVER use ${t("ctx_execute")}, ${t("ctx_execute_file")}, or Bash to write
|
|
48
|
-
|
|
45
|
+
ALWAYS use native Write/Edit tools for file creation/modification.
|
|
46
|
+
NEVER use ${t("ctx_execute")}, ${t("ctx_execute_file")}, or Bash to write files.
|
|
47
|
+
Applies to all file types: code, configs, plans, specs, YAML, JSON, markdown.
|
|
49
48
|
</file_writing_policy>
|
|
50
49
|
|
|
51
50
|
<output_constraints>
|
|
52
|
-
<
|
|
51
|
+
<communication_style>
|
|
52
|
+
Terse like caveman. Technical substance exact. Only fluff die.
|
|
53
|
+
Use fragments when clear. Short synonyms (fix not "implement a solution for").
|
|
54
|
+
Technical terms exact. Code blocks unchanged.
|
|
55
|
+
Auto-expand for: security warnings, irreversible actions, user confusion.
|
|
56
|
+
</communication_style>
|
|
53
57
|
<artifact_policy>
|
|
54
|
-
Write artifacts (code, configs, PRDs) to FILES
|
|
55
|
-
Use Edit tool for modifications to existing files.
|
|
58
|
+
Write artifacts (code, configs, PRDs) to FILES. NEVER inline.
|
|
56
59
|
Return only: file path + 1-line description.
|
|
57
60
|
</artifact_policy>
|
|
58
61
|
<response_format>
|
|
59
|
-
|
|
62
|
+
Concise summary:
|
|
60
63
|
- Actions taken (2-3 bullets)
|
|
61
64
|
- File paths created/modified
|
|
62
|
-
- Knowledge base source labels (so parent can search)
|
|
63
65
|
- Key findings
|
|
64
66
|
</response_format>
|
|
65
67
|
</output_constraints>
|
|
66
68
|
${includeCommands ? `
|
|
67
69
|
<ctx_commands>
|
|
68
|
-
|
|
69
|
-
→ Call
|
|
70
|
+
"ctx stats" | "ctx-stats" | "/ctx-stats" | context savings question
|
|
71
|
+
→ Call stats MCP tool, display full output verbatim.
|
|
70
72
|
|
|
71
|
-
|
|
72
|
-
→ Call
|
|
73
|
+
"ctx doctor" | "ctx-doctor" | "/ctx-doctor" | diagnose context-mode
|
|
74
|
+
→ Call doctor MCP tool, run returned shell command, display as checklist.
|
|
73
75
|
|
|
74
|
-
|
|
75
|
-
→ Call
|
|
76
|
+
"ctx upgrade" | "ctx-upgrade" | "/ctx-upgrade" | update context-mode
|
|
77
|
+
→ Call upgrade MCP tool, run returned shell command, display as checklist.
|
|
76
78
|
|
|
77
|
-
|
|
78
|
-
→ Call
|
|
79
|
+
"ctx purge" | "ctx-purge" | "/ctx-purge" | wipe/reset knowledge base
|
|
80
|
+
→ Call purge MCP tool with confirm: true. Warn: irreversible.
|
|
79
81
|
|
|
80
|
-
After /clear or /compact: knowledge base
|
|
82
|
+
After /clear or /compact: knowledge base preserved. Tell user: "context-mode knowledge base preserved. Use \`ctx purge\` to start fresh."
|
|
81
83
|
</ctx_commands>
|
|
82
84
|
` : ''}
|
|
83
85
|
</context_window_protection>`;
|
|
84
86
|
}
|
|
85
87
|
|
|
86
88
|
export function createReadGuidance(t) {
|
|
87
|
-
return '<context_guidance>\n <tip>\n
|
|
89
|
+
return '<context_guidance>\n <tip>\n Reading to Edit? Read is correct — Edit needs content in context.\n Reading to analyze/explore? Use ' + t("ctx_execute_file") + '(path, language, code) — only printed summary enters context.\n </tip>\n</context_guidance>';
|
|
88
90
|
}
|
|
89
91
|
|
|
90
92
|
export function createGrepGuidance(t) {
|
|
91
|
-
return '<context_guidance>\n <tip>\n
|
|
93
|
+
return '<context_guidance>\n <tip>\n May flood context. Use ' + t("ctx_execute") + '(language: "shell", code: "...") to run searches in sandbox. Only printed summary enters context.\n </tip>\n</context_guidance>';
|
|
92
94
|
}
|
|
93
95
|
|
|
94
96
|
export function createBashGuidance(t) {
|
|
95
|
-
return '<context_guidance>\n <tip>\n
|
|
97
|
+
return '<context_guidance>\n <tip>\n May produce large output. Use ' + t("ctx_batch_execute") + '(commands, queries) for multiple commands, ' + t("ctx_execute") + '(language: "shell", code: "...") for single. Only printed summary enters context. Bash only for: git, mkdir, rm, mv, navigation.\n </tip>\n</context_guidance>';
|
|
96
98
|
}
|
|
97
99
|
|
|
98
100
|
// ── Backward compat: static exports defaulting to claude-code ──
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{dirname as d,isAbsolute as a,normalize as g,resolve as j,sep as y}from"node:path";function e(r){let n=g(r).replace(/\\/g,"/");return n.length<=1?n:n.replace(/\/+$/,"")}function u(r,n){return!r||!n?!1:r===n?!0:r.startsWith(`${n}/`)}function D(r){if(!r||r.length===0)return[];let n=r.filter(i=>typeof i=="string"&&i.trim().length>0).map(i=>e(i));return Array.from(new Set(n)).sort((i,s)=>s.length-i.length)}function m(r){let n=" in ",t=r.lastIndexOf(n);if(t<0)return null;let i=r.slice(t+n.length).trim();return i.length>0?i:null}function w(r){return r?r.includes("/")||r.includes("\\")||r.startsWith(".")||/^[A-Za-z]:[\\/]/.test(r):!1}function P(r){if(r.type==="cwd")return{rawPath:r.data,fromCwdEvent:!0};if(r.type==="file_search"){let t=m(r.data);if(t)return{rawPath:t,fromCwdEvent:!1}}return new Set(["file_read","file_write","file_edit","file_glob","rule"]).has(r.type)&&w(r.data)?{rawPath:r.data,fromCwdEvent:!1}:null}function b(r,n){if(!r||r.includes("*")&&!a(r)&&!/^[A-Za-z]:[\\/]/.test(r))return null;if(a(r)||/^[A-Za-z]:[\\/]/.test(r))return e(r);let t=n.lastKnownProjectDir||n.inputProjectDir||n.sessionOriginDir||null;return t?e(j(t,r)):null}function h(r,n,t){let i=D(t.workspaceRoots),s=t.sessionOriginDir?e(t.sessionOriginDir):"",o=t.inputProjectDir?e(t.inputProjectDir):"",c=t.lastKnownProjectDir?e(t.lastKnownProjectDir):"",f=i.find(p=>u(r,p));return f?{projectDir:f,source:"workspace_root",confidence:.98}:o&&u(r,o)?{projectDir:o,source:"input_cwd",confidence:.88}:s&&u(r,s)?{projectDir:s,source:"session_origin",confidence:.82}:c&&u(r,c)?{projectDir:c,source:"last_seen",confidence:.76}:n.type==="cwd"?{projectDir:r,source:"cwd_event",confidence:.9}:{projectDir:new Set(["file_read","file_write","file_edit","rule"]).has(n.type)?e(d(r)):r,source:"event_path",confidence:.7}}function l(r){return r.inputProjectDir?{projectDir:e(r.inputProjectDir),source:"input_cwd",confidence:.45}:r.lastKnownProjectDir?{projectDir:e(r.lastKnownProjectDir),source:"last_seen",confidence:.4}:r.sessionOriginDir?{projectDir:e(r.sessionOriginDir),source:"session_origin",confidence:.35}:{projectDir:"",source:"unknown",confidence:0}}function A(r,n){try{let t=P(r);if(!t)return l(n);let i=b(t.rawPath,n);return i?h(i,r,n):l(n)}catch{return l(n)}}function z(r,n){let t=[],i=n.lastKnownProjectDir?e(n.lastKnownProjectDir):"";for(let s of r){let o=A(s,{...n,lastKnownProjectDir:i||n.lastKnownProjectDir||null});t.push(o),o.projectDir&&o.confidence>=.55&&(i=o.projectDir)}return t}function C(r){let n=Math.max(0,Math.min(1,r));return Math.round(n*100)}function E(r){return r>=.8}function K(r){return e(r)}var O=1;export{O as PROJECT_ATTRIBUTION_VERSION,C as confidenceToPercent,E as isHighConfidenceAttribution,K as normalizeProjectDir,A as resolveProjectAttribution,z as resolveProjectAttributions};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{createRequire as
|
|
1
|
+
import{createRequire as b}from"node:module";import{existsSync as N,unlinkSync as y,renameSync as D}from"node:fs";import{tmpdir as O}from"node:os";import{join as w}from"node:path";var p=class{#t;constructor(t){this.#t=t}pragma(t){let e=this.#t.prepare(`PRAGMA ${t}`).all();if(!e||e.length===0)return;if(e.length>1)return e;let r=Object.values(e[0]);return r.length===1?r[0]:e[0]}exec(t){let s="",e=null;for(let o=0;o<t.length;o++){let a=t[o];if(e)s+=a,a===e&&(e=null);else if(a==="'"||a==='"')s+=a,e=a;else if(a===";"){let c=s.trim();c&&this.#t.prepare(c).run(),s=""}else s+=a}let r=s.trim();return r&&this.#t.prepare(r).run(),this}prepare(t){let s=this.#t.prepare(t);return{run:(...e)=>s.run(...e),get:(...e)=>{let r=s.get(...e);return r===null?void 0:r},all:(...e)=>s.all(...e),iterate:(...e)=>s.iterate(...e)}}transaction(t){return this.#t.transaction(t)}close(){this.#t.close()}},_=class{#t;constructor(t){this.#t=t}pragma(t){let e=this.#t.prepare(`PRAGMA ${t}`).all();if(!e||e.length===0)return;if(e.length>1)return e;let r=Object.values(e[0]);return r.length===1?r[0]:e[0]}exec(t){return this.#t.exec(t),this}prepare(t){let s=this.#t.prepare(t);return{run:(...e)=>s.run(...e),get:(...e)=>s.get(...e),all:(...e)=>s.all(...e),iterate:(...e)=>typeof s.iterate=="function"?s.iterate(...e):s.all(...e)[Symbol.iterator]()}}transaction(t){return(...s)=>{this.#t.exec("BEGIN");try{let e=t(...s);return this.#t.exec("COMMIT"),e}catch(e){throw this.#t.exec("ROLLBACK"),e}}}close(){this.#t.close()}},u=null;function A(){if(!u){let n=b(import.meta.url);if(globalThis.Bun){let t=n(["bun","sqlite"].join(":")).Database;u=function(e,r){let o=new t(e,{readonly:r?.readonly,create:!0}),a=new p(o);return r?.timeout&&a.pragma(`busy_timeout = ${r.timeout}`),a}}else if(process.platform==="linux")try{let{DatabaseSync:t}=n(["node","sqlite"].join(":"));u=function(e,r){let o=new t(e,{readOnly:r?.readonly??!1});return new _(o)}}catch{u=n("better-sqlite3")}else u=n("better-sqlite3")}return u}function g(n){n.pragma("journal_mode = WAL"),n.pragma("synchronous = NORMAL");try{n.pragma("mmap_size = 268435456")}catch{}}function h(n){if(!N(n))for(let t of["-wal","-shm"])try{y(n+t)}catch{}}function C(n){for(let t of["","-wal","-shm"])try{y(n+t)}catch{}}function l(n){try{n.pragma("wal_checkpoint(TRUNCATE)")}catch{}try{n.close()}catch{}}function f(n="context-mode"){return w(O(),`${n}-${process.pid}.db`)}function I(n,t=[100,500,2e3]){let s;for(let e=0;e<=t.length;e++)try{return n()}catch(r){let o=r instanceof Error?r.message:String(r);if(!o.includes("SQLITE_BUSY")&&!o.includes("database is locked"))throw r;if(s=r instanceof Error?r:new Error(o),e<t.length){let a=t[e],c=Date.now();for(;Date.now()-c<a;);}}throw new Error(`SQLITE_BUSY: database is locked after ${t.length} retries. Original error: ${s?.message}`)}function U(n){return n.includes("SQLITE_CORRUPT")||n.includes("SQLITE_NOTADB")||n.includes("database disk image is malformed")||n.includes("file is not a database")}function x(n){let t=Date.now();for(let s of["","-wal","-shm"])try{D(n+s,`${n}${s}.corrupt-${t}`)}catch{}}var d=Symbol.for("__context_mode_live_dbs__"),m=(()=>{let n=globalThis;return n[d]||(n[d]=new Set,process.on("exit",()=>{for(let t of n[d])l(t);n[d].clear()})),n[d]})(),E=class{#t;#e;constructor(t){let s=A();this.#t=t,h(t);let e;try{e=new s(t,{timeout:3e4}),g(e)}catch(r){let o=r instanceof Error?r.message:String(r);if(U(o)){x(t),h(t);try{e=new s(t,{timeout:3e4}),g(e)}catch(a){throw new Error(`Failed to create fresh DB after renaming corrupt file: ${a instanceof Error?a.message:String(a)}`)}}else throw r}this.#e=e,m.add(this.#e),this.initSchema(),this.prepareStatements()}get db(){return this.#e}get dbPath(){return this.#t}close(){m.delete(this.#e),l(this.#e)}withRetry(t){return I(t)}cleanup(){m.delete(this.#e),l(this.#e),C(this.#t)}};import{createHash as R}from"node:crypto";import{execFileSync as M}from"node:child_process";function K(){let n=process.env.CONTEXT_MODE_SESSION_SUFFIX;if(n!==void 0)return n?`__${n}`:"";try{let t=process.cwd(),s=M("git",["worktree","list","--porcelain"],{encoding:"utf-8",timeout:2e3,stdio:["ignore","pipe","ignore"]}).split(/\r?\n/).find(e=>e.startsWith("worktree "))?.replace("worktree ","")?.trim();if(s&&t!==s)return`__${R("sha256").update(t).digest("hex").slice(0,8)}`}catch{}return""}var k=1e3,F=5,i={insertEvent:"insertEvent",getEvents:"getEvents",getEventsByType:"getEventsByType",getEventsByPriority:"getEventsByPriority",getEventsByTypeAndPriority:"getEventsByTypeAndPriority",getEventCount:"getEventCount",getLatestAttributedProject:"getLatestAttributedProject",checkDuplicate:"checkDuplicate",evictLowestPriority:"evictLowestPriority",updateMetaLastEvent:"updateMetaLastEvent",ensureSession:"ensureSession",getSessionStats:"getSessionStats",incrementCompactCount:"incrementCompactCount",upsertResume:"upsertResume",getResume:"getResume",markResumeConsumed:"markResumeConsumed",deleteEvents:"deleteEvents",deleteMeta:"deleteMeta",deleteResume:"deleteResume",getOldSessions:"getOldSessions"},L=class extends E{constructor(t){super(t?.dbPath??f("session"))}stmt(t){return this.stmts.get(t)}initSchema(){try{let s=this.db.pragma("table_xinfo(session_events)").find(e=>e.name==="data_hash");s&&s.hidden!==0&&this.db.exec("DROP TABLE session_events")}catch{}this.db.exec(`
|
|
2
2
|
CREATE TABLE IF NOT EXISTS session_events (
|
|
3
3
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
4
4
|
session_id TEXT NOT NULL,
|
|
@@ -6,6 +6,9 @@ import{createRequire as R}from"node:module";import{existsSync as v,unlinkSync as
|
|
|
6
6
|
category TEXT NOT NULL,
|
|
7
7
|
priority INTEGER NOT NULL DEFAULT 2,
|
|
8
8
|
data TEXT NOT NULL,
|
|
9
|
+
project_dir TEXT NOT NULL DEFAULT '',
|
|
10
|
+
attribution_source TEXT NOT NULL DEFAULT 'unknown',
|
|
11
|
+
attribution_confidence REAL NOT NULL DEFAULT 0,
|
|
9
12
|
source_hook TEXT NOT NULL,
|
|
10
13
|
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
11
14
|
data_hash TEXT NOT NULL DEFAULT ''
|
|
@@ -32,12 +35,28 @@ import{createRequire as R}from"node:module";import{existsSync as v,unlinkSync as
|
|
|
32
35
|
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
33
36
|
consumed INTEGER NOT NULL DEFAULT 0
|
|
34
37
|
);
|
|
35
|
-
`)}prepareStatements(){this.stmts=new Map;let t=(s,e)=>{this.stmts.set(s,this.db.prepare(e))};t(i.insertEvent,`INSERT INTO session_events (
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
`);try{let t=this.db.pragma("table_xinfo(session_events)"),s=new Set(t.map(e=>e.name));s.has("project_dir")||this.db.exec("ALTER TABLE session_events ADD COLUMN project_dir TEXT NOT NULL DEFAULT ''"),s.has("attribution_source")||this.db.exec("ALTER TABLE session_events ADD COLUMN attribution_source TEXT NOT NULL DEFAULT 'unknown'"),s.has("attribution_confidence")||this.db.exec("ALTER TABLE session_events ADD COLUMN attribution_confidence REAL NOT NULL DEFAULT 0"),this.db.exec("CREATE INDEX IF NOT EXISTS idx_session_events_project ON session_events(session_id, project_dir)")}catch{}}prepareStatements(){this.stmts=new Map;let t=(s,e)=>{this.stmts.set(s,this.db.prepare(e))};t(i.insertEvent,`INSERT INTO session_events (
|
|
39
|
+
session_id, type, category, priority, data,
|
|
40
|
+
project_dir, attribution_source, attribution_confidence,
|
|
41
|
+
source_hook, data_hash
|
|
42
|
+
)
|
|
43
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`),t(i.getEvents,`SELECT id, session_id, type, category, priority, data,
|
|
44
|
+
project_dir, attribution_source, attribution_confidence,
|
|
45
|
+
source_hook, created_at, data_hash
|
|
46
|
+
FROM session_events WHERE session_id = ? ORDER BY id ASC LIMIT ?`),t(i.getEventsByType,`SELECT id, session_id, type, category, priority, data,
|
|
47
|
+
project_dir, attribution_source, attribution_confidence,
|
|
48
|
+
source_hook, created_at, data_hash
|
|
49
|
+
FROM session_events WHERE session_id = ? AND type = ? ORDER BY id ASC LIMIT ?`),t(i.getEventsByPriority,`SELECT id, session_id, type, category, priority, data,
|
|
50
|
+
project_dir, attribution_source, attribution_confidence,
|
|
51
|
+
source_hook, created_at, data_hash
|
|
52
|
+
FROM session_events WHERE session_id = ? AND priority >= ? ORDER BY id ASC LIMIT ?`),t(i.getEventsByTypeAndPriority,`SELECT id, session_id, type, category, priority, data,
|
|
53
|
+
project_dir, attribution_source, attribution_confidence,
|
|
54
|
+
source_hook, created_at, data_hash
|
|
55
|
+
FROM session_events WHERE session_id = ? AND type = ? AND priority >= ? ORDER BY id ASC LIMIT ?`),t(i.getEventCount,"SELECT COUNT(*) AS cnt FROM session_events WHERE session_id = ?"),t(i.getLatestAttributedProject,`SELECT project_dir
|
|
56
|
+
FROM session_events
|
|
57
|
+
WHERE session_id = ? AND project_dir != ''
|
|
58
|
+
ORDER BY id DESC
|
|
59
|
+
LIMIT 1`),t(i.checkDuplicate,`SELECT 1 FROM (
|
|
41
60
|
SELECT type, data_hash FROM session_events
|
|
42
61
|
WHERE session_id = ? ORDER BY id DESC LIMIT ?
|
|
43
62
|
) AS recent
|
|
@@ -54,4 +73,4 @@ import{createRequire as R}from"node:module";import{existsSync as v,unlinkSync as
|
|
|
54
73
|
snapshot = excluded.snapshot,
|
|
55
74
|
event_count = excluded.event_count,
|
|
56
75
|
created_at = datetime('now'),
|
|
57
|
-
consumed = 0`),t(i.getResume,"SELECT snapshot, event_count, consumed FROM session_resume WHERE session_id = ?"),t(i.markResumeConsumed,"UPDATE session_resume SET consumed = 1 WHERE session_id = ?"),t(i.deleteEvents,"DELETE FROM session_events WHERE session_id = ?"),t(i.deleteMeta,"DELETE FROM session_meta WHERE session_id = ?"),t(i.deleteResume,"DELETE FROM session_resume WHERE session_id = ?"),t(i.getOldSessions,"SELECT session_id FROM session_meta WHERE started_at < datetime('now', ? || ' days')")}insertEvent(t,s,e="PostToolUse"){let
|
|
76
|
+
consumed = 0`),t(i.getResume,"SELECT snapshot, event_count, consumed FROM session_resume WHERE session_id = ?"),t(i.markResumeConsumed,"UPDATE session_resume SET consumed = 1 WHERE session_id = ?"),t(i.deleteEvents,"DELETE FROM session_events WHERE session_id = ?"),t(i.deleteMeta,"DELETE FROM session_meta WHERE session_id = ?"),t(i.deleteResume,"DELETE FROM session_resume WHERE session_id = ?"),t(i.getOldSessions,"SELECT session_id FROM session_meta WHERE started_at < datetime('now', ? || ' days')")}insertEvent(t,s,e="PostToolUse",r){let o=R("sha256").update(s.data).digest("hex").slice(0,16).toUpperCase(),a=String(r?.projectDir??s.project_dir??"").trim(),c=String(r?.source??s.attribution_source??"unknown"),T=Number(r?.confidence??s.attribution_confidence??0),S=Number.isFinite(T)?Math.max(0,Math.min(1,T)):0,v=this.db.transaction(()=>{if(this.stmt(i.checkDuplicate).get(t,F,s.type,o))return;this.stmt(i.getEventCount).get(t).cnt>=k&&this.stmt(i.evictLowestPriority).run(t),this.stmt(i.insertEvent).run(t,s.type,s.category,s.priority,s.data,a,c,S,e,o),this.stmt(i.updateMetaLastEvent).run(t)});this.withRetry(()=>v())}getEvents(t,s){let e=s?.limit??1e3,r=s?.type,o=s?.minPriority;return r&&o!==void 0?this.stmt(i.getEventsByTypeAndPriority).all(t,r,o,e):r?this.stmt(i.getEventsByType).all(t,r,e):o!==void 0?this.stmt(i.getEventsByPriority).all(t,o,e):this.stmt(i.getEvents).all(t,e)}getEventCount(t){return this.stmt(i.getEventCount).get(t).cnt}getLatestAttributedProjectDir(t){return this.stmt(i.getLatestAttributedProject).get(t)?.project_dir||null}ensureSession(t,s){this.stmt(i.ensureSession).run(t,s)}getSessionStats(t){return this.stmt(i.getSessionStats).get(t)??null}incrementCompactCount(t){this.stmt(i.incrementCompactCount).run(t)}upsertResume(t,s,e){this.stmt(i.upsertResume).run(t,s,e??0)}getResume(t){return this.stmt(i.getResume).get(t)??null}markResumeConsumed(t){this.stmt(i.markResumeConsumed).run(t)}deleteSession(t){this.db.transaction(()=>{this.stmt(i.deleteEvents).run(t),this.stmt(i.deleteResume).run(t),this.stmt(i.deleteMeta).run(t)})()}cleanupOldSessions(t=7){let s=`-${t}`,e=this.stmt(i.getOldSessions).all(s);for(let{session_id:r}of e)this.deleteSession(r);return e.length}};export{L as SessionDB,K as getWorktreeSuffix};
|
|
@@ -1 +1,2 @@
|
|
|
1
|
-
function o(t){return t==null?"":String(t)}function
|
|
1
|
+
function o(t){return t==null?"":String(t)}function u(t){return t==null?"":typeof t=="string"?t:JSON.stringify(t)}function d(t){let{tool_name:e,tool_input:n,tool_response:i}=t,r=[];if(e==="Read"){let s=String(n.file_path??"");return/CLAUDE\.md$|\.claude[\\/]/i.test(s)&&(r.push({type:"rule",category:"rule",data:o(s),priority:1}),i&&i.length>0&&r.push({type:"rule_content",category:"rule",data:o(i),priority:1})),r.push({type:"file_read",category:"file",data:o(s),priority:1}),r}if(e==="Edit"){let s=String(n.file_path??"");return r.push({type:"file_edit",category:"file",data:o(s),priority:1}),r}if(e==="NotebookEdit"){let s=String(n.notebook_path??"");return r.push({type:"file_edit",category:"file",data:o(s),priority:1}),r}if(e==="Write"){let s=String(n.file_path??"");return r.push({type:"file_write",category:"file",data:o(s),priority:1}),r}if(e==="Glob"){let s=String(n.pattern??"");return r.push({type:"file_glob",category:"file",data:o(s),priority:3}),r}if(e==="Grep"){let s=String(n.pattern??""),a=String(n.path??"");return r.push({type:"file_search",category:"file",data:o(`${s} in ${a}`),priority:3}),r}return r}function g(t){if(t.tool_name!=="Bash")return[];let n=String(t.tool_input.command??"").match(/\bcd\s+("([^"]+)"|'([^']+)'|(\S+))/);if(!n)return[];let i=n[2]??n[3]??n[4]??"";return[{type:"cwd",category:"cwd",data:o(i),priority:2}]}function b(t){let{tool_name:e,tool_input:n,tool_response:i,tool_output:r}=t,s=String(i??""),a=r?.isError===!0;return!(e==="Bash"&&/exit code [1-9]|error:|Error:|FAIL|failed/i.test(s))&&!a?[]:[{type:"error_tool",category:"error",data:o(s),priority:2}]}var y=[{pattern:/\bgit\s+checkout\b/,operation:"branch"},{pattern:/\bgit\s+commit\b/,operation:"commit"},{pattern:/\bgit\s+merge\s+\S+/,operation:"merge"},{pattern:/\bgit\s+rebase\b/,operation:"rebase"},{pattern:/\bgit\s+stash\b/,operation:"stash"},{pattern:/\bgit\s+push\b/,operation:"push"},{pattern:/\bgit\s+pull\b/,operation:"pull"},{pattern:/\bgit\s+log\b/,operation:"log"},{pattern:/\bgit\s+diff\b/,operation:"diff"},{pattern:/\bgit\s+status\b/,operation:"status"},{pattern:/\bgit\s+branch\b/,operation:"branch"},{pattern:/\bgit\s+reset\b/,operation:"reset"},{pattern:/\bgit\s+add\b/,operation:"add"},{pattern:/\bgit\s+cherry-pick\b/,operation:"cherry-pick"},{pattern:/\bgit\s+tag\b/,operation:"tag"},{pattern:/\bgit\s+fetch\b/,operation:"fetch"},{pattern:/\bgit\s+clone\b/,operation:"clone"},{pattern:/\bgit\s+worktree\b/,operation:"worktree"}];function f(t){if(t.tool_name!=="Bash")return[];let e=String(t.tool_input.command??""),n=y.find(i=>i.pattern.test(e));return n?[{type:"git",category:"git",data:o(n.operation),priority:2}]:[]}function h(t){return new Set(["TodoWrite","TaskCreate","TaskUpdate"]).has(t.tool_name)?[{type:t.tool_name==="TaskUpdate"?"task_update":t.tool_name==="TaskCreate"?"task_create":"task",category:"task",data:o(JSON.stringify(t.tool_input)),priority:1}]:[]}function _(t){if(t.tool_name==="EnterPlanMode")return[{type:"plan_enter",category:"plan",data:"entered plan mode",priority:2}];if(t.tool_name==="ExitPlanMode"){let e=[],n=t.tool_input.allowedPrompts,i=Array.isArray(n)&&n.length>0?`exited plan mode (allowed: ${u(n.map(s=>typeof s=="object"&&s!==null&&"prompt"in s?String(s.prompt):String(s)).join(", "))})`:"exited plan mode";e.push({type:"plan_exit",category:"plan",data:o(i),priority:2});let r=String(t.tool_response??"").toLowerCase();return r.includes("approved")||r.includes("approve")?e.push({type:"plan_approved",category:"plan",data:"plan approved by user",priority:1}):(r.includes("rejected")||r.includes("decline")||r.includes("denied"))&&e.push({type:"plan_rejected",category:"plan",data:o(`plan rejected: ${t.tool_response??""}`),priority:2}),e}if(t.tool_name==="Write"||t.tool_name==="Edit"){let e=String(t.tool_input.file_path??"");if(/[/\\]\.claude[/\\]plans[/\\]/.test(e))return[{type:"plan_file_write",category:"plan",data:o(`plan file: ${e.split(/[/\\]/).pop()??e}`),priority:2}]}return[]}var m=[/\bsource\s+\S*activate\b/,/\bexport\s+\w+=/,/\bnvm\s+use\b/,/\bpyenv\s+(shell|local|global)\b/,/\bconda\s+activate\b/,/\brbenv\s+(shell|local|global)\b/,/\bnpm\s+install\b/,/\bnpm\s+ci\b/,/\bpip\s+install\b/,/\bbun\s+install\b/,/\byarn\s+(add|install)\b/,/\bpnpm\s+(add|install)\b/,/\bcargo\s+(install|add)\b/,/\bgo\s+(install|get)\b/,/\brustup\b/,/\basdf\b/,/\bvolta\b/,/\bdeno\s+install\b/];function S(t){if(t.tool_name!=="Bash")return[];let e=String(t.tool_input.command??"");if(!m.some(r=>r.test(e)))return[];let i=e.replace(/\bexport\s+(\w+)=\S*/g,"export $1=***");return[{type:"env",category:"env",data:o(i),priority:2}]}function k(t){if(t.tool_name!=="Skill")return[];let e=String(t.tool_input.skill??"");return[{type:"skill",category:"skill",data:o(e),priority:3}]}function E(t){if(t.tool_name!=="Agent")return[];let e=o(String(t.tool_input.prompt??t.tool_input.description??"")),n=t.tool_response?o(String(t.tool_response)):"",i=n.length>0;return[{type:i?"subagent_completed":"subagent_launched",category:"subagent",data:o(i?`[completed] ${e} \u2192 ${n}`:`[launched] ${e}`),priority:i?2:3}]}function v(t){let{tool_name:e,tool_input:n,tool_response:i}=t;if(!e.startsWith("mcp__"))return[];let r=e.split("__"),s=r[r.length-1]||e,a=Object.values(n).find(c=>typeof c=="string"),p=a?`: ${o(String(a))}`:"",l=i&&i.length>0?`
|
|
2
|
+
response: ${o(i)}`:"";return[{type:"mcp",category:"mcp",data:o(`${s}${p}${l}`),priority:3}]}function x(t){if(t.tool_name!=="AskUserQuestion")return[];let e=t.tool_input.questions,n=Array.isArray(e)&&e.length>0?String(e[0].question??""):"",i=o(String(t.tool_response??"")),r=n?`Q: ${o(n)} \u2192 A: ${i}`:`answer: ${i}`;return[{type:"decision_question",category:"decision",data:o(r),priority:2}]}function w(t){if(t.tool_name!=="EnterWorktree")return[];let e=String(t.tool_input.name??"unnamed");return[{type:"worktree",category:"env",data:o(`entered worktree: ${e}`),priority:2}]}var T=[/\b(don'?t|do not|never|always|instead|rather|prefer)\b/i,/\b(use|switch to|go with|pick|choose)\s+\w+\s+(instead|over|not)\b/i,/\b(no,?\s+(use|do|try|make))\b/i,/\b(hayır|hayir|evet|böyle|boyle|degil|değil|yerine|kullan)\b/i];function R(t){return T.some(n=>n.test(t))?[{type:"decision",category:"decision",data:o(t),priority:2}]:[]}var A=[/\b(act as|you are|behave like|pretend|role of|persona)\b/i,/\b(senior|staff|principal|lead)\s+(engineer|developer|architect)\b/i,/\b(gibi davran|rolünde|olarak çalış)\b/i];function I(t){return A.some(n=>n.test(t))?[{type:"role",category:"role",data:o(t),priority:3}]:[]}var $=[{mode:"investigate",pattern:/\b(why|how does|explain|understand|what is|analyze|debug|look into)\b/i},{mode:"implement",pattern:/\b(create|add|build|implement|write|make|develop|fix)\b/i},{mode:"discuss",pattern:/\b(think about|consider|should we|what if|pros and cons|opinion)\b/i},{mode:"review",pattern:/\b(review|check|audit|verify|test|validate)\b/i}];function P(t){let e=$.find(({pattern:n})=>n.test(t));return e?[{type:"intent",category:"intent",data:o(e.mode),priority:4}]:[]}function H(t){return t.length<=1024?[]:[{type:"data",category:"data",data:o(t),priority:4}]}function N(t){try{let e=[];return e.push(...d(t)),e.push(...g(t)),e.push(...b(t)),e.push(...f(t)),e.push(...S(t)),e.push(...h(t)),e.push(..._(t)),e.push(...k(t)),e.push(...E(t)),e.push(...v(t)),e.push(...x(t)),e.push(...w(t)),e}catch{return[]}}function C(t){try{let e=[];return e.push(...R(t)),e.push(...I(t)),e.push(...P(t)),e.push(...H(t)),e}catch{return[]}}export{N as extractEvents,C as extractUserEvents};
|
|
@@ -46,6 +46,7 @@ function getWorktreeSuffix() {
|
|
|
46
46
|
/** Claude Code platform options (default). */
|
|
47
47
|
const CLAUDE_OPTS = {
|
|
48
48
|
configDir: ".claude",
|
|
49
|
+
configDirEnv: "CLAUDE_CONFIG_DIR",
|
|
49
50
|
projectDirEnv: "CLAUDE_PROJECT_DIR",
|
|
50
51
|
sessionIdEnv: "CLAUDE_SESSION_ID",
|
|
51
52
|
};
|
|
@@ -53,6 +54,7 @@ const CLAUDE_OPTS = {
|
|
|
53
54
|
/** Gemini CLI platform options. */
|
|
54
55
|
export const GEMINI_OPTS = {
|
|
55
56
|
configDir: ".gemini",
|
|
57
|
+
configDirEnv: "GEMINI_CLI_HOME",
|
|
56
58
|
projectDirEnv: "GEMINI_PROJECT_DIR",
|
|
57
59
|
sessionIdEnv: undefined,
|
|
58
60
|
};
|
|
@@ -60,6 +62,7 @@ export const GEMINI_OPTS = {
|
|
|
60
62
|
/** VS Code Copilot platform options. */
|
|
61
63
|
export const VSCODE_OPTS = {
|
|
62
64
|
configDir: ".vscode",
|
|
65
|
+
configDirEnv: undefined,
|
|
63
66
|
projectDirEnv: "VSCODE_CWD",
|
|
64
67
|
sessionIdEnv: undefined,
|
|
65
68
|
};
|
|
@@ -67,6 +70,7 @@ export const VSCODE_OPTS = {
|
|
|
67
70
|
/** Cursor platform options. */
|
|
68
71
|
export const CURSOR_OPTS = {
|
|
69
72
|
configDir: ".cursor",
|
|
73
|
+
configDirEnv: undefined,
|
|
70
74
|
projectDirEnv: "CURSOR_CWD",
|
|
71
75
|
sessionIdEnv: "CURSOR_SESSION_ID",
|
|
72
76
|
};
|
|
@@ -74,6 +78,7 @@ export const CURSOR_OPTS = {
|
|
|
74
78
|
/** Codex CLI platform options. */
|
|
75
79
|
export const CODEX_OPTS = {
|
|
76
80
|
configDir: ".codex",
|
|
81
|
+
configDirEnv: "CODEX_HOME",
|
|
77
82
|
projectDirEnv: undefined, // Codex passes cwd in hook stdin, no env var
|
|
78
83
|
sessionIdEnv: undefined, // Uses session_id from hook stdin or ppid fallback
|
|
79
84
|
};
|
|
@@ -81,10 +86,46 @@ export const CODEX_OPTS = {
|
|
|
81
86
|
/** Kiro CLI platform options. */
|
|
82
87
|
export const KIRO_OPTS = {
|
|
83
88
|
configDir: ".kiro",
|
|
89
|
+
configDirEnv: undefined,
|
|
84
90
|
projectDirEnv: undefined, // Kiro CLI provides cwd in hook stdin, no env var
|
|
85
91
|
sessionIdEnv: undefined, // No session ID env var — uses ppid fallback
|
|
86
92
|
};
|
|
87
93
|
|
|
94
|
+
/** JetBrains Copilot platform options. */
|
|
95
|
+
export const JETBRAINS_OPTS = {
|
|
96
|
+
configDir: ".config/JetBrains",
|
|
97
|
+
configDirEnv: undefined,
|
|
98
|
+
projectDirEnv: "IDEA_INITIAL_DIRECTORY",
|
|
99
|
+
sessionIdEnv: undefined,
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Resolve the platform config directory, respecting env var overrides.
|
|
104
|
+
* Platforms like Claude Code (CLAUDE_CONFIG_DIR), Gemini CLI (GEMINI_CLI_HOME),
|
|
105
|
+
* and Codex CLI (CODEX_HOME) allow users to customize the config location.
|
|
106
|
+
* Falls back to ~/<configDir> when no env var is set.
|
|
107
|
+
*/
|
|
108
|
+
export function resolveConfigDir(opts = CLAUDE_OPTS) {
|
|
109
|
+
if (opts.configDirEnv) {
|
|
110
|
+
const envVal = process.env[opts.configDirEnv];
|
|
111
|
+
if (envVal) {
|
|
112
|
+
if (envVal.startsWith("~")) return join(homedir(), envVal.replace(/^~[/\\]?/, ""));
|
|
113
|
+
return envVal;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return join(homedir(), opts.configDir);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Safely parse raw stdin string as JSON.
|
|
121
|
+
* Returns empty object for empty/whitespace/BOM-only input instead of throwing.
|
|
122
|
+
* Strips BOM prefix before parsing. Throws on genuinely malformed JSON.
|
|
123
|
+
*/
|
|
124
|
+
export function parseStdin(raw) {
|
|
125
|
+
const cleaned = raw.replace(/^\uFEFF/, "").trim();
|
|
126
|
+
return cleaned ? JSON.parse(cleaned) : {};
|
|
127
|
+
}
|
|
128
|
+
|
|
88
129
|
/**
|
|
89
130
|
* Read all of stdin as a string (event-based, cross-platform safe).
|
|
90
131
|
*/
|
|
@@ -147,7 +188,7 @@ export function getSessionId(input, opts = CLAUDE_OPTS) {
|
|
|
147
188
|
export function getSessionDBPath(opts = CLAUDE_OPTS) {
|
|
148
189
|
const projectDir = getProjectDir(opts);
|
|
149
190
|
const hash = createHash("sha256").update(projectDir).digest("hex").slice(0, 16);
|
|
150
|
-
const dir = join(
|
|
191
|
+
const dir = join(resolveConfigDir(opts), "context-mode", "sessions");
|
|
151
192
|
mkdirSync(dir, { recursive: true });
|
|
152
193
|
return join(dir, `${hash}${getWorktreeSuffix()}.db`);
|
|
153
194
|
}
|
|
@@ -160,7 +201,7 @@ export function getSessionDBPath(opts = CLAUDE_OPTS) {
|
|
|
160
201
|
export function getSessionEventsPath(opts = CLAUDE_OPTS) {
|
|
161
202
|
const projectDir = getProjectDir(opts);
|
|
162
203
|
const hash = createHash("sha256").update(projectDir).digest("hex").slice(0, 16);
|
|
163
|
-
const dir = join(
|
|
204
|
+
const dir = join(resolveConfigDir(opts), "context-mode", "sessions");
|
|
164
205
|
mkdirSync(dir, { recursive: true });
|
|
165
206
|
return join(dir, `${hash}${getWorktreeSuffix()}-events.md`);
|
|
166
207
|
}
|
|
@@ -173,7 +214,7 @@ export function getSessionEventsPath(opts = CLAUDE_OPTS) {
|
|
|
173
214
|
export function getCleanupFlagPath(opts = CLAUDE_OPTS) {
|
|
174
215
|
const projectDir = getProjectDir(opts);
|
|
175
216
|
const hash = createHash("sha256").update(projectDir).digest("hex").slice(0, 16);
|
|
176
|
-
const dir = join(
|
|
217
|
+
const dir = join(resolveConfigDir(opts), "context-mode", "sessions");
|
|
177
218
|
mkdirSync(dir, { recursive: true });
|
|
178
219
|
return join(dir, `${hash}${getWorktreeSuffix()}.cleanup`);
|
|
179
220
|
}
|
|
@@ -34,6 +34,19 @@ export function createSessionLoaders(hookDir) {
|
|
|
34
34
|
async loadSessionDB() {
|
|
35
35
|
return await loadModule("session-db.bundle.mjs", "db.js");
|
|
36
36
|
},
|
|
37
|
+
async loadProjectAttribution() {
|
|
38
|
+
const bundlePath = join(bundleDir, "session-attribution.bundle.mjs");
|
|
39
|
+
if (existsSync(bundlePath)) {
|
|
40
|
+
return await import(pathToFileURL(bundlePath).href);
|
|
41
|
+
}
|
|
42
|
+
const buildPath = join(buildSession, "project-attribution.js");
|
|
43
|
+
if (existsSync(buildPath)) {
|
|
44
|
+
return await import(pathToFileURL(buildPath).href);
|
|
45
|
+
}
|
|
46
|
+
// Last-resort fallback for dev environments without a fresh build.
|
|
47
|
+
const localPath = join(bundleDir, "project-attribution.mjs");
|
|
48
|
+
return await import(pathToFileURL(localPath).href);
|
|
49
|
+
},
|
|
37
50
|
async loadExtract() {
|
|
38
51
|
return await loadModule("session-extract.bundle.mjs", "extract.js");
|
|
39
52
|
},
|
|
@@ -42,3 +55,27 @@ export function createSessionLoaders(hookDir) {
|
|
|
42
55
|
},
|
|
43
56
|
};
|
|
44
57
|
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Shared helper — resolves project attributions and inserts events into the DB.
|
|
61
|
+
* Eliminates the ~15-line attribution block duplicated across all hook files.
|
|
62
|
+
*
|
|
63
|
+
* @returns {Array} The resolved attributions array (useful when a subsequent
|
|
64
|
+
* attribution block needs `lastKnownProjectDir` from the first).
|
|
65
|
+
*/
|
|
66
|
+
export function attributeAndInsertEvents(db, sessionId, events, input, projectDir, hookName, resolveProjectAttributions) {
|
|
67
|
+
const sessionStats = db.getSessionStats(sessionId);
|
|
68
|
+
const lastKnownProjectDir = typeof db.getLatestAttributedProjectDir === "function"
|
|
69
|
+
? db.getLatestAttributedProjectDir(sessionId)
|
|
70
|
+
: null;
|
|
71
|
+
const attributions = resolveProjectAttributions(events, {
|
|
72
|
+
sessionOriginDir: sessionStats?.project_dir || projectDir,
|
|
73
|
+
inputProjectDir: projectDir,
|
|
74
|
+
workspaceRoots: Array.isArray(input.workspace_roots) ? input.workspace_roots : [],
|
|
75
|
+
lastKnownProjectDir,
|
|
76
|
+
});
|
|
77
|
+
for (let i = 0; i < events.length; i++) {
|
|
78
|
+
db.insertEvent(sessionId, events[i], hookName, attributions[i]);
|
|
79
|
+
}
|
|
80
|
+
return attributions;
|
|
81
|
+
}
|
package/hooks/sessionstart.mjs
CHANGED
|
@@ -20,13 +20,12 @@ import { createToolNamer } from "./core/tool-naming.mjs";
|
|
|
20
20
|
|
|
21
21
|
const toolNamer = createToolNamer("claude-code");
|
|
22
22
|
const ROUTING_BLOCK = createRoutingBlock(toolNamer);
|
|
23
|
-
import { readStdin, getSessionId, getSessionDBPath, getSessionEventsPath, getCleanupFlagPath } from "./session-helpers.mjs";
|
|
23
|
+
import { readStdin, parseStdin, getSessionId, getSessionDBPath, getSessionEventsPath, getCleanupFlagPath, resolveConfigDir } from "./session-helpers.mjs";
|
|
24
24
|
import { writeSessionEventsFile, buildSessionDirective, getSessionEvents, getLatestSessionEvents } from "./session-directive.mjs";
|
|
25
25
|
import { createSessionLoaders } from "./session-loaders.mjs";
|
|
26
26
|
import { join, dirname } from "node:path";
|
|
27
27
|
import { fileURLToPath } from "node:url";
|
|
28
28
|
import { readFileSync, unlinkSync, readdirSync, rmSync, statSync } from "node:fs";
|
|
29
|
-
import { homedir } from "node:os";
|
|
30
29
|
|
|
31
30
|
// Resolve absolute path for imports (fileURLToPath for Windows compat)
|
|
32
31
|
const HOOK_DIR = dirname(fileURLToPath(import.meta.url));
|
|
@@ -36,7 +35,7 @@ let additionalContext = ROUTING_BLOCK;
|
|
|
36
35
|
|
|
37
36
|
try {
|
|
38
37
|
const raw = await readStdin();
|
|
39
|
-
const input =
|
|
38
|
+
const input = parseStdin(raw);
|
|
40
39
|
const source = input.source ?? "startup";
|
|
41
40
|
|
|
42
41
|
if (source === "compact") {
|
|
@@ -93,7 +92,7 @@ try {
|
|
|
93
92
|
const projectDir = process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
94
93
|
db.ensureSession(sessionId, projectDir);
|
|
95
94
|
const claudeMdPaths = [
|
|
96
|
-
join(
|
|
95
|
+
join(resolveConfigDir(), "CLAUDE.md"),
|
|
97
96
|
join(projectDir, "CLAUDE.md"),
|
|
98
97
|
join(projectDir, ".claude", "CLAUDE.md"),
|
|
99
98
|
];
|
|
@@ -140,8 +139,9 @@ try {
|
|
|
140
139
|
const { appendFileSync } = await import("node:fs");
|
|
141
140
|
const { join: pjoin } = await import("node:path");
|
|
142
141
|
const { homedir } = await import("node:os");
|
|
142
|
+
const { resolveConfigDir: _resolve } = await import("./session-helpers.mjs");
|
|
143
143
|
appendFileSync(
|
|
144
|
-
pjoin(
|
|
144
|
+
pjoin(_resolve(), "context-mode", "sessionstart-debug.log"),
|
|
145
145
|
`[${new Date().toISOString()}] ${err?.message || err}\n${err?.stack || ""}\n`,
|
|
146
146
|
);
|
|
147
147
|
} catch { /* ignore logging failure */ }
|
|
@@ -10,17 +10,18 @@ import "./ensure-deps.mjs";
|
|
|
10
10
|
* Must be fast (<10ms). Just a single SQLite write.
|
|
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
|
const HOOK_DIR = dirname(fileURLToPath(import.meta.url));
|
|
19
|
-
const { loadSessionDB, loadExtract } = createSessionLoaders(HOOK_DIR);
|
|
19
|
+
const { loadSessionDB, loadExtract, loadProjectAttribution } = createSessionLoaders(HOOK_DIR);
|
|
20
20
|
|
|
21
21
|
try {
|
|
22
22
|
const raw = await readStdin();
|
|
23
|
-
const input =
|
|
23
|
+
const input = parseStdin(raw);
|
|
24
|
+
const projectDir = getInputProjectDir(input);
|
|
24
25
|
|
|
25
26
|
const prompt = input.prompt ?? input.message ?? "";
|
|
26
27
|
const trimmed = (prompt || "").trim();
|
|
@@ -34,24 +35,40 @@ try {
|
|
|
34
35
|
if (trimmed.length > 0 && !isSystemMessage) {
|
|
35
36
|
const { SessionDB } = await loadSessionDB();
|
|
36
37
|
const { extractUserEvents } = await loadExtract();
|
|
38
|
+
const { resolveProjectAttributions } = await loadProjectAttribution();
|
|
37
39
|
const dbPath = getSessionDBPath();
|
|
38
40
|
const db = new SessionDB({ dbPath });
|
|
39
41
|
const sessionId = getSessionId(input);
|
|
40
42
|
|
|
41
|
-
db.ensureSession(sessionId,
|
|
43
|
+
db.ensureSession(sessionId, projectDir);
|
|
42
44
|
|
|
43
45
|
// 1. Always save the raw prompt
|
|
44
|
-
|
|
46
|
+
const promptEvent = {
|
|
45
47
|
type: "user_prompt",
|
|
46
48
|
category: "prompt",
|
|
47
49
|
data: prompt,
|
|
48
50
|
priority: 1,
|
|
49
|
-
}
|
|
51
|
+
};
|
|
52
|
+
const promptAttributions = attributeAndInsertEvents(
|
|
53
|
+
db, sessionId, [promptEvent], input, projectDir, "UserPromptSubmit", resolveProjectAttributions,
|
|
54
|
+
);
|
|
50
55
|
|
|
51
56
|
// 2. Extract decision/role/intent/data from user message
|
|
52
57
|
const userEvents = extractUserEvents(trimmed);
|
|
53
|
-
|
|
54
|
-
|
|
58
|
+
// Feed lastKnownProjectDir from the first attribution into the second batch
|
|
59
|
+
const savedLastKnown = promptAttributions[0]?.projectDir || null;
|
|
60
|
+
const sessionStats = db.getSessionStats(sessionId);
|
|
61
|
+
const lastKnownProjectDir = typeof db.getLatestAttributedProjectDir === "function"
|
|
62
|
+
? db.getLatestAttributedProjectDir(sessionId)
|
|
63
|
+
: null;
|
|
64
|
+
const userAttributions = resolveProjectAttributions(userEvents, {
|
|
65
|
+
sessionOriginDir: sessionStats?.project_dir || projectDir,
|
|
66
|
+
inputProjectDir: projectDir,
|
|
67
|
+
workspaceRoots: Array.isArray(input.workspace_roots) ? input.workspace_roots : [],
|
|
68
|
+
lastKnownProjectDir: savedLastKnown || lastKnownProjectDir,
|
|
69
|
+
});
|
|
70
|
+
for (let i = 0; i < userEvents.length; i++) {
|
|
71
|
+
db.insertEvent(sessionId, userEvents[i], "UserPromptSubmit", userAttributions[i]);
|
|
55
72
|
}
|
|
56
73
|
|
|
57
74
|
db.close();
|
|
@@ -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 { createSessionLoaders } from "../session-loaders.mjs";
|
|
14
|
-
import { readStdin, getSessionId, getSessionDBPath,
|
|
13
|
+
import { createSessionLoaders, attributeAndInsertEvents } from "../session-loaders.mjs";
|
|
14
|
+
import { readStdin, parseStdin, getSessionId, getSessionDBPath, getInputProjectDir, VSCODE_OPTS } from "../session-helpers.mjs";
|
|
15
15
|
import { appendFileSync } from "node:fs";
|
|
16
16
|
import { join, dirname } from "node:path";
|
|
17
17
|
import { fileURLToPath } from "node:url";
|
|
18
18
|
import { homedir } from "node:os";
|
|
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 = VSCODE_OPTS;
|
|
23
23
|
const DEBUG_LOG = join(homedir(), ".vscode", "context-mode", "posttooluse-debug.log");
|
|
24
24
|
|
|
25
25
|
try {
|
|
26
26
|
const raw = await readStdin();
|
|
27
|
-
const input =
|
|
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,
|
|
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
|
-
|
|
50
|
-
db.insertEvent(sessionId, event, "PostToolUse");
|
|
51
|
-
}
|
|
51
|
+
attributeAndInsertEvents(db, sessionId, events, input, projectDir, "PostToolUse", resolveProjectAttributions);
|
|
52
52
|
|
|
53
53
|
appendFileSync(DEBUG_LOG, `[${new Date().toISOString()}] OK: ${input.tool_name} → ${events.length} events\n`);
|
|
54
54
|
db.close();
|
|
@@ -10,7 +10,7 @@ import "../ensure-deps.mjs";
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import { createSessionLoaders } from "../session-loaders.mjs";
|
|
13
|
-
import { readStdin, getSessionId, getSessionDBPath, VSCODE_OPTS } from "../session-helpers.mjs";
|
|
13
|
+
import { readStdin, parseStdin, getSessionId, getSessionDBPath, VSCODE_OPTS } from "../session-helpers.mjs";
|
|
14
14
|
import { appendFileSync } from "node:fs";
|
|
15
15
|
import { join, dirname } from "node:path";
|
|
16
16
|
import { fileURLToPath } from "node:url";
|
|
@@ -23,7 +23,7 @@ const DEBUG_LOG = join(homedir(), ".vscode", "context-mode", "precompact-debug.l
|
|
|
23
23
|
|
|
24
24
|
try {
|
|
25
25
|
const raw = await readStdin();
|
|
26
|
-
const input =
|
|
26
|
+
const input = parseStdin(raw);
|
|
27
27
|
|
|
28
28
|
const { buildResumeSnapshot } = await loadSnapshot();
|
|
29
29
|
const { SessionDB } = await loadSessionDB();
|