context-mode 0.9.21 → 1.0.0
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/hooks/hooks.json +46 -4
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +4 -4
- package/README.md +377 -191
- package/build/adapters/claude-code/config.d.ts +8 -0
- package/build/adapters/claude-code/config.js +8 -0
- package/build/adapters/claude-code/hooks.d.ts +53 -0
- package/build/adapters/claude-code/hooks.js +88 -0
- package/build/adapters/claude-code/index.d.ts +50 -0
- package/build/adapters/claude-code/index.js +523 -0
- package/build/adapters/codex/config.d.ts +8 -0
- package/build/adapters/codex/config.js +8 -0
- package/build/adapters/codex/hooks.d.ts +21 -0
- package/build/adapters/codex/hooks.js +27 -0
- package/build/adapters/codex/index.d.ts +44 -0
- package/build/adapters/codex/index.js +223 -0
- package/build/adapters/detect.d.ts +26 -0
- package/build/adapters/detect.js +131 -0
- package/build/adapters/gemini-cli/config.d.ts +8 -0
- package/build/adapters/gemini-cli/config.js +8 -0
- package/build/adapters/gemini-cli/hooks.d.ts +44 -0
- package/build/adapters/gemini-cli/hooks.js +64 -0
- package/build/adapters/gemini-cli/index.d.ts +57 -0
- package/build/adapters/gemini-cli/index.js +468 -0
- package/build/adapters/opencode/config.d.ts +8 -0
- package/build/adapters/opencode/config.js +8 -0
- package/build/adapters/opencode/hooks.d.ts +38 -0
- package/build/adapters/opencode/hooks.js +50 -0
- package/build/adapters/opencode/index.d.ts +52 -0
- package/build/adapters/opencode/index.js +386 -0
- package/build/adapters/types.d.ts +218 -0
- package/build/adapters/types.js +13 -0
- package/build/adapters/vscode-copilot/config.d.ts +8 -0
- package/build/adapters/vscode-copilot/config.js +8 -0
- package/build/adapters/vscode-copilot/hooks.d.ts +49 -0
- package/build/adapters/vscode-copilot/hooks.js +76 -0
- package/build/adapters/vscode-copilot/index.d.ts +58 -0
- package/build/adapters/vscode-copilot/index.js +512 -0
- package/build/cli.d.ts +9 -6
- package/build/cli.js +133 -423
- package/build/db-base.d.ts +84 -0
- package/build/db-base.js +128 -0
- package/build/executor.d.ts +6 -7
- package/build/executor.js +111 -51
- package/build/opencode-plugin.d.ts +37 -0
- package/build/opencode-plugin.js +118 -0
- package/build/runtime.js +1 -1
- package/build/server.js +436 -117
- package/build/session/db.d.ts +110 -0
- package/build/session/db.js +285 -0
- package/build/session/extract.d.ts +51 -0
- package/build/session/extract.js +407 -0
- package/build/session/snapshot.d.ts +70 -0
- package/build/session/snapshot.js +309 -0
- package/build/store.d.ts +4 -22
- package/build/store.js +67 -55
- package/build/truncate.d.ts +59 -0
- package/build/truncate.js +157 -0
- package/build/types.d.ts +101 -0
- package/build/types.js +20 -0
- package/configs/claude-code/CLAUDE.md +62 -0
- package/configs/codex/AGENTS.md +58 -0
- package/configs/codex/config.toml +5 -0
- package/configs/gemini-cli/GEMINI.md +58 -0
- package/configs/gemini-cli/mcp.json +7 -0
- package/configs/gemini-cli/settings.json +49 -0
- package/configs/opencode/AGENTS.md +58 -0
- package/configs/opencode/opencode.json +10 -0
- package/configs/vscode-copilot/copilot-instructions.md +58 -0
- package/configs/vscode-copilot/hooks.json +16 -0
- package/configs/vscode-copilot/mcp.json +8 -0
- package/hooks/core/formatters.mjs +86 -0
- package/hooks/core/routing.mjs +262 -0
- package/hooks/core/stdin.mjs +19 -0
- package/hooks/formatters/claude-code.mjs +57 -0
- package/hooks/formatters/gemini-cli.mjs +55 -0
- package/hooks/formatters/vscode-copilot.mjs +55 -0
- package/hooks/gemini-cli/aftertool.mjs +58 -0
- package/hooks/gemini-cli/beforetool.mjs +25 -0
- package/hooks/gemini-cli/precompress.mjs +51 -0
- package/hooks/gemini-cli/sessionstart.mjs +117 -0
- package/hooks/hooks.json +46 -4
- package/hooks/posttooluse.mjs +53 -0
- package/hooks/precompact.mjs +55 -0
- package/hooks/pretooluse.mjs +23 -266
- package/hooks/routing-block.mjs +19 -6
- package/hooks/session-directive.mjs +353 -0
- package/hooks/session-helpers.mjs +112 -0
- package/hooks/sessionstart.mjs +123 -16
- package/hooks/userpromptsubmit.mjs +58 -0
- package/hooks/vscode-copilot/posttooluse.mjs +58 -0
- package/hooks/vscode-copilot/precompact.mjs +51 -0
- package/hooks/vscode-copilot/pretooluse.mjs +25 -0
- package/hooks/vscode-copilot/sessionstart.mjs +115 -0
- package/package.json +20 -17
- package/skills/context-mode/SKILL.md +49 -49
- package/skills/{doctor → ctx-doctor}/SKILL.md +3 -3
- package/skills/{stats → ctx-stats}/SKILL.md +3 -3
- package/skills/{upgrade → ctx-upgrade}/SKILL.md +3 -3
- package/start.mjs +47 -0
- package/hooks/pretooluse.sh +0 -147
- package/server.bundle.mjs +0 -341
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session event extraction — pure functions, zero side effects.
|
|
3
|
+
* Extracts structured events from Claude Code tool calls and user messages.
|
|
4
|
+
*
|
|
5
|
+
* All 13 event categories as specified in PRD Section 3.
|
|
6
|
+
*/
|
|
7
|
+
// ── Internal helpers ───────────────────────────────────────────────────────
|
|
8
|
+
/** Truncate a string to at most `max` characters. */
|
|
9
|
+
function truncate(value, max = 300) {
|
|
10
|
+
if (value == null)
|
|
11
|
+
return "";
|
|
12
|
+
if (value.length <= max)
|
|
13
|
+
return value;
|
|
14
|
+
return value.slice(0, max);
|
|
15
|
+
}
|
|
16
|
+
/** Serialise an unknown value to a string, then truncate. */
|
|
17
|
+
function truncateAny(value, max = 300) {
|
|
18
|
+
if (value == null)
|
|
19
|
+
return "";
|
|
20
|
+
const str = typeof value === "string" ? value : JSON.stringify(value);
|
|
21
|
+
return truncate(str, max);
|
|
22
|
+
}
|
|
23
|
+
// ── Category extractors ────────────────────────────────────────────────────
|
|
24
|
+
/**
|
|
25
|
+
* Category 1 & 2: rule + file
|
|
26
|
+
*
|
|
27
|
+
* CLAUDE.md / .claude/ reads → emit both a "rule" event (priority 1) AND a
|
|
28
|
+
* "file_read" event (priority 1) because the file is being actively accessed.
|
|
29
|
+
*
|
|
30
|
+
* Other Edit/Write/Read tool calls → emit a file_edit / file_write / file_read
|
|
31
|
+
* event (priority 1).
|
|
32
|
+
*/
|
|
33
|
+
function extractFileAndRule(input) {
|
|
34
|
+
const { tool_name, tool_input, tool_response } = input;
|
|
35
|
+
const events = [];
|
|
36
|
+
if (tool_name === "Read") {
|
|
37
|
+
const filePath = String(tool_input["file_path"] ?? "");
|
|
38
|
+
// Rule detection: CLAUDE.md or anything inside a .claude/ directory
|
|
39
|
+
const isRuleFile = /CLAUDE\.md$|\.claude[\\/]/i.test(filePath);
|
|
40
|
+
if (isRuleFile) {
|
|
41
|
+
events.push({
|
|
42
|
+
type: "rule",
|
|
43
|
+
category: "rule",
|
|
44
|
+
data: truncate(filePath),
|
|
45
|
+
priority: 1,
|
|
46
|
+
});
|
|
47
|
+
// Capture rule content so it survives context compaction
|
|
48
|
+
if (tool_response && tool_response.length > 0) {
|
|
49
|
+
events.push({
|
|
50
|
+
type: "rule_content",
|
|
51
|
+
category: "rule",
|
|
52
|
+
data: truncate(tool_response, 5000),
|
|
53
|
+
priority: 1,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
// Always emit file_read for any Read call
|
|
58
|
+
events.push({
|
|
59
|
+
type: "file_read",
|
|
60
|
+
category: "file",
|
|
61
|
+
data: truncate(filePath),
|
|
62
|
+
priority: 1,
|
|
63
|
+
});
|
|
64
|
+
return events;
|
|
65
|
+
}
|
|
66
|
+
if (tool_name === "Edit") {
|
|
67
|
+
const filePath = String(tool_input["file_path"] ?? "");
|
|
68
|
+
events.push({
|
|
69
|
+
type: "file",
|
|
70
|
+
category: "file",
|
|
71
|
+
data: truncate(filePath),
|
|
72
|
+
priority: 1,
|
|
73
|
+
});
|
|
74
|
+
return events;
|
|
75
|
+
}
|
|
76
|
+
if (tool_name === "Write") {
|
|
77
|
+
const filePath = String(tool_input["file_path"] ?? "");
|
|
78
|
+
events.push({
|
|
79
|
+
type: "file_write",
|
|
80
|
+
category: "file",
|
|
81
|
+
data: truncate(filePath),
|
|
82
|
+
priority: 1,
|
|
83
|
+
});
|
|
84
|
+
return events;
|
|
85
|
+
}
|
|
86
|
+
// Glob — file pattern exploration
|
|
87
|
+
if (tool_name === "Glob") {
|
|
88
|
+
const pattern = String(tool_input["pattern"] ?? "");
|
|
89
|
+
events.push({
|
|
90
|
+
type: "file_glob",
|
|
91
|
+
category: "file",
|
|
92
|
+
data: truncate(pattern),
|
|
93
|
+
priority: 3,
|
|
94
|
+
});
|
|
95
|
+
return events;
|
|
96
|
+
}
|
|
97
|
+
// Grep — code search
|
|
98
|
+
if (tool_name === "Grep") {
|
|
99
|
+
const searchPattern = String(tool_input["pattern"] ?? "");
|
|
100
|
+
const searchPath = String(tool_input["path"] ?? "");
|
|
101
|
+
events.push({
|
|
102
|
+
type: "file_search",
|
|
103
|
+
category: "file",
|
|
104
|
+
data: truncate(`${searchPattern} in ${searchPath}`),
|
|
105
|
+
priority: 3,
|
|
106
|
+
});
|
|
107
|
+
return events;
|
|
108
|
+
}
|
|
109
|
+
return events;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Category 4: cwd
|
|
113
|
+
* Matches the first `cd <path>` in a Bash command (handles quoted paths).
|
|
114
|
+
*/
|
|
115
|
+
function extractCwd(input) {
|
|
116
|
+
if (input.tool_name !== "Bash")
|
|
117
|
+
return [];
|
|
118
|
+
const cmd = String(input.tool_input["command"] ?? "");
|
|
119
|
+
// Match: cd "path" | cd 'path' | cd path
|
|
120
|
+
const cdMatch = cmd.match(/\bcd\s+("([^"]+)"|'([^']+)'|(\S+))/);
|
|
121
|
+
if (!cdMatch)
|
|
122
|
+
return [];
|
|
123
|
+
const dir = cdMatch[2] ?? cdMatch[3] ?? cdMatch[4] ?? "";
|
|
124
|
+
return [{
|
|
125
|
+
type: "cwd",
|
|
126
|
+
category: "cwd",
|
|
127
|
+
data: truncate(dir),
|
|
128
|
+
priority: 2,
|
|
129
|
+
}];
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Category 5: error
|
|
133
|
+
* Detects failures from bash exit codes / error patterns, or an explicit
|
|
134
|
+
* isError flag in tool_output.
|
|
135
|
+
*/
|
|
136
|
+
function extractError(input) {
|
|
137
|
+
const { tool_name, tool_input, tool_response, tool_output } = input;
|
|
138
|
+
const response = String(tool_response ?? "");
|
|
139
|
+
const isErrorFlag = tool_output?.isError === true;
|
|
140
|
+
const isBashError = tool_name === "Bash" &&
|
|
141
|
+
/exit code [1-9]|error:|Error:|FAIL|failed/i.test(response);
|
|
142
|
+
if (!isBashError && !isErrorFlag)
|
|
143
|
+
return [];
|
|
144
|
+
return [{
|
|
145
|
+
type: "error_tool",
|
|
146
|
+
category: "error",
|
|
147
|
+
data: truncate(response, 300),
|
|
148
|
+
priority: 2,
|
|
149
|
+
}];
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Category 11: git
|
|
153
|
+
* Matches common git operations from Bash commands.
|
|
154
|
+
*/
|
|
155
|
+
const GIT_PATTERNS = [
|
|
156
|
+
{ pattern: /\bgit\s+checkout\b/, operation: "branch" },
|
|
157
|
+
{ pattern: /\bgit\s+commit\b/, operation: "commit" },
|
|
158
|
+
{ pattern: /\bgit\s+merge\s+\S+/, operation: "merge" },
|
|
159
|
+
{ pattern: /\bgit\s+rebase\b/, operation: "rebase" },
|
|
160
|
+
{ pattern: /\bgit\s+stash\b/, operation: "stash" },
|
|
161
|
+
{ pattern: /\bgit\s+push\b/, operation: "push" },
|
|
162
|
+
{ pattern: /\bgit\s+pull\b/, operation: "pull" },
|
|
163
|
+
{ pattern: /\bgit\s+log\b/, operation: "log" },
|
|
164
|
+
{ pattern: /\bgit\s+diff\b/, operation: "diff" },
|
|
165
|
+
{ pattern: /\bgit\s+status\b/, operation: "status" },
|
|
166
|
+
{ pattern: /\bgit\s+branch\b/, operation: "branch" },
|
|
167
|
+
{ pattern: /\bgit\s+reset\b/, operation: "reset" },
|
|
168
|
+
];
|
|
169
|
+
function extractGit(input) {
|
|
170
|
+
if (input.tool_name !== "Bash")
|
|
171
|
+
return [];
|
|
172
|
+
const cmd = String(input.tool_input["command"] ?? "");
|
|
173
|
+
const match = GIT_PATTERNS.find(p => p.pattern.test(cmd));
|
|
174
|
+
if (!match)
|
|
175
|
+
return [];
|
|
176
|
+
return [{
|
|
177
|
+
type: "git",
|
|
178
|
+
category: "git",
|
|
179
|
+
data: truncate(match.operation),
|
|
180
|
+
priority: 2,
|
|
181
|
+
}];
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Category 3: task
|
|
185
|
+
* TodoWrite / TaskCreate / TaskUpdate tool calls.
|
|
186
|
+
*/
|
|
187
|
+
function extractTask(input) {
|
|
188
|
+
const TASK_TOOLS = new Set(["TodoWrite", "TaskCreate", "TaskUpdate"]);
|
|
189
|
+
if (!TASK_TOOLS.has(input.tool_name))
|
|
190
|
+
return [];
|
|
191
|
+
return [{
|
|
192
|
+
type: "task",
|
|
193
|
+
category: "task",
|
|
194
|
+
data: truncate(JSON.stringify(input.tool_input), 300),
|
|
195
|
+
priority: 1,
|
|
196
|
+
}];
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Category 8: env
|
|
200
|
+
* Environment setup commands in Bash: venv, export, nvm, pyenv, conda, rbenv.
|
|
201
|
+
*/
|
|
202
|
+
const ENV_PATTERNS = [
|
|
203
|
+
/\bsource\s+\S*activate\b/,
|
|
204
|
+
/\bexport\s+\w+=/,
|
|
205
|
+
/\bnvm\s+use\b/,
|
|
206
|
+
/\bpyenv\s+(shell|local|global)\b/,
|
|
207
|
+
/\bconda\s+activate\b/,
|
|
208
|
+
/\brbenv\s+(shell|local|global)\b/,
|
|
209
|
+
/\bnpm\s+install\b/,
|
|
210
|
+
/\bnpm\s+ci\b/,
|
|
211
|
+
/\bpip\s+install\b/,
|
|
212
|
+
/\bbun\s+install\b/,
|
|
213
|
+
/\byarn\s+(add|install)\b/,
|
|
214
|
+
/\bpnpm\s+(add|install)\b/,
|
|
215
|
+
];
|
|
216
|
+
function extractEnv(input) {
|
|
217
|
+
if (input.tool_name !== "Bash")
|
|
218
|
+
return [];
|
|
219
|
+
const cmd = String(input.tool_input["command"] ?? "");
|
|
220
|
+
const isEnvCmd = ENV_PATTERNS.some(p => p.test(cmd));
|
|
221
|
+
if (!isEnvCmd)
|
|
222
|
+
return [];
|
|
223
|
+
return [{
|
|
224
|
+
type: "env",
|
|
225
|
+
category: "env",
|
|
226
|
+
data: truncate(cmd),
|
|
227
|
+
priority: 2,
|
|
228
|
+
}];
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Category 10: skill
|
|
232
|
+
* Skill tool invocations.
|
|
233
|
+
*/
|
|
234
|
+
function extractSkill(input) {
|
|
235
|
+
if (input.tool_name !== "Skill")
|
|
236
|
+
return [];
|
|
237
|
+
const skillName = String(input.tool_input["skill"] ?? "");
|
|
238
|
+
return [{
|
|
239
|
+
type: "skill",
|
|
240
|
+
category: "skill",
|
|
241
|
+
data: truncate(skillName),
|
|
242
|
+
priority: 3,
|
|
243
|
+
}];
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Category 9: subagent
|
|
247
|
+
* Agent tool calls (subagent dispatches).
|
|
248
|
+
*/
|
|
249
|
+
function extractSubagent(input) {
|
|
250
|
+
if (input.tool_name !== "Agent")
|
|
251
|
+
return [];
|
|
252
|
+
const prompt = String(input.tool_input["prompt"] ?? input.tool_input["description"] ?? "");
|
|
253
|
+
return [{
|
|
254
|
+
type: "subagent",
|
|
255
|
+
category: "subagent",
|
|
256
|
+
data: truncate(prompt, 300),
|
|
257
|
+
priority: 3,
|
|
258
|
+
}];
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Category 14: mcp
|
|
262
|
+
* MCP tool calls (context7, playwright, claude-mem, ctx-stats, etc.).
|
|
263
|
+
*/
|
|
264
|
+
function extractMcp(input) {
|
|
265
|
+
const { tool_name, tool_input } = input;
|
|
266
|
+
if (!tool_name.startsWith("mcp__"))
|
|
267
|
+
return [];
|
|
268
|
+
// Extract readable tool name: last segment after __
|
|
269
|
+
const parts = tool_name.split("__");
|
|
270
|
+
const toolShort = parts[parts.length - 1] || tool_name;
|
|
271
|
+
// Extract first string argument for context
|
|
272
|
+
const firstArg = Object.values(tool_input).find((v) => typeof v === "string");
|
|
273
|
+
const argStr = firstArg ? `: ${truncate(String(firstArg), 100)}` : "";
|
|
274
|
+
return [{
|
|
275
|
+
type: "mcp",
|
|
276
|
+
category: "mcp",
|
|
277
|
+
data: truncate(`${toolShort}${argStr}`),
|
|
278
|
+
priority: 3,
|
|
279
|
+
}];
|
|
280
|
+
}
|
|
281
|
+
// ── User-message extractors ────────────────────────────────────────────────
|
|
282
|
+
/**
|
|
283
|
+
* Category 6: decision
|
|
284
|
+
* User corrections / approach selections.
|
|
285
|
+
*/
|
|
286
|
+
const DECISION_PATTERNS = [
|
|
287
|
+
/\b(don'?t|do not|never|always|instead|rather|prefer)\b/i,
|
|
288
|
+
/\b(use|switch to|go with|pick|choose)\s+\w+\s+(instead|over|not)\b/i,
|
|
289
|
+
/\b(no,?\s+(use|do|try|make))\b/i,
|
|
290
|
+
// Turkish patterns
|
|
291
|
+
/\b(hayır|hayir|evet|böyle|boyle|degil|değil|yerine|kullan)\b/i,
|
|
292
|
+
];
|
|
293
|
+
function extractDecision(message) {
|
|
294
|
+
const isDecision = DECISION_PATTERNS.some(p => p.test(message));
|
|
295
|
+
if (!isDecision)
|
|
296
|
+
return [];
|
|
297
|
+
return [{
|
|
298
|
+
type: "decision",
|
|
299
|
+
category: "decision",
|
|
300
|
+
data: truncate(message, 300),
|
|
301
|
+
priority: 2,
|
|
302
|
+
}];
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Category 7: role
|
|
306
|
+
* Persona / behavioral directive patterns.
|
|
307
|
+
*/
|
|
308
|
+
const ROLE_PATTERNS = [
|
|
309
|
+
/\b(act as|you are|behave like|pretend|role of|persona)\b/i,
|
|
310
|
+
/\b(senior|staff|principal|lead)\s+(engineer|developer|architect)\b/i,
|
|
311
|
+
// Turkish patterns
|
|
312
|
+
/\b(gibi davran|rolünde|olarak çalış)\b/i,
|
|
313
|
+
];
|
|
314
|
+
function extractRole(message) {
|
|
315
|
+
const isRole = ROLE_PATTERNS.some(p => p.test(message));
|
|
316
|
+
if (!isRole)
|
|
317
|
+
return [];
|
|
318
|
+
return [{
|
|
319
|
+
type: "role",
|
|
320
|
+
category: "role",
|
|
321
|
+
data: truncate(message, 300),
|
|
322
|
+
priority: 3,
|
|
323
|
+
}];
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Category 13: intent
|
|
327
|
+
* Session mode classification from user messages.
|
|
328
|
+
*/
|
|
329
|
+
const INTENT_PATTERNS = [
|
|
330
|
+
{ mode: "investigate", pattern: /\b(why|how does|explain|understand|what is|analyze|debug|look into)\b/i },
|
|
331
|
+
{ mode: "implement", pattern: /\b(create|add|build|implement|write|make|develop|fix)\b/i },
|
|
332
|
+
{ mode: "discuss", pattern: /\b(think about|consider|should we|what if|pros and cons|opinion)\b/i },
|
|
333
|
+
{ mode: "review", pattern: /\b(review|check|audit|verify|test|validate)\b/i },
|
|
334
|
+
];
|
|
335
|
+
function extractIntent(message) {
|
|
336
|
+
const match = INTENT_PATTERNS.find(({ pattern }) => pattern.test(message));
|
|
337
|
+
if (!match)
|
|
338
|
+
return [];
|
|
339
|
+
return [{
|
|
340
|
+
type: "intent",
|
|
341
|
+
category: "intent",
|
|
342
|
+
data: truncate(match.mode),
|
|
343
|
+
priority: 4,
|
|
344
|
+
}];
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* Category 12: data
|
|
348
|
+
* Large user-pasted data references (message > 1KB).
|
|
349
|
+
*/
|
|
350
|
+
function extractData(message) {
|
|
351
|
+
if (message.length <= 1024)
|
|
352
|
+
return [];
|
|
353
|
+
return [{
|
|
354
|
+
type: "data",
|
|
355
|
+
category: "data",
|
|
356
|
+
data: truncate(message, 200),
|
|
357
|
+
priority: 4,
|
|
358
|
+
}];
|
|
359
|
+
}
|
|
360
|
+
// ── Public API ─────────────────────────────────────────────────────────────
|
|
361
|
+
/**
|
|
362
|
+
* Extract session events from a PostToolUse hook input.
|
|
363
|
+
*
|
|
364
|
+
* Accepts the raw hook JSON shape (snake_case keys) as received from stdin.
|
|
365
|
+
* Returns an array of zero or more SessionEvents. Never throws.
|
|
366
|
+
*/
|
|
367
|
+
export function extractEvents(input) {
|
|
368
|
+
try {
|
|
369
|
+
const events = [];
|
|
370
|
+
// File + Rule (handles Read/Edit/Write)
|
|
371
|
+
events.push(...extractFileAndRule(input));
|
|
372
|
+
// Bash-based extractors (may overlap on the same command)
|
|
373
|
+
events.push(...extractCwd(input));
|
|
374
|
+
events.push(...extractError(input));
|
|
375
|
+
events.push(...extractGit(input));
|
|
376
|
+
events.push(...extractEnv(input));
|
|
377
|
+
// Tool-specific extractors
|
|
378
|
+
events.push(...extractTask(input));
|
|
379
|
+
events.push(...extractSkill(input));
|
|
380
|
+
events.push(...extractSubagent(input));
|
|
381
|
+
events.push(...extractMcp(input));
|
|
382
|
+
return events;
|
|
383
|
+
}
|
|
384
|
+
catch {
|
|
385
|
+
// Graceful degradation: if extraction fails, session continues normally
|
|
386
|
+
return [];
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* Extract session events from a UserPromptSubmit hook input (user message text).
|
|
391
|
+
*
|
|
392
|
+
* Handles: decision, role, intent, data categories.
|
|
393
|
+
* Returns an array of zero or more SessionEvents. Never throws.
|
|
394
|
+
*/
|
|
395
|
+
export function extractUserEvents(message) {
|
|
396
|
+
try {
|
|
397
|
+
const events = [];
|
|
398
|
+
events.push(...extractDecision(message));
|
|
399
|
+
events.push(...extractRole(message));
|
|
400
|
+
events.push(...extractIntent(message));
|
|
401
|
+
events.push(...extractData(message));
|
|
402
|
+
return events;
|
|
403
|
+
}
|
|
404
|
+
catch {
|
|
405
|
+
return [];
|
|
406
|
+
}
|
|
407
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Snapshot builder — converts stored SessionEvents into an XML resume snapshot.
|
|
3
|
+
*
|
|
4
|
+
* Pure functions only. No database access, no file system, no side effects.
|
|
5
|
+
* The output XML is injected into Claude's context after a compact event to
|
|
6
|
+
* restore session awareness.
|
|
7
|
+
*
|
|
8
|
+
* Budget: default 2048 bytes, allocated by priority tier:
|
|
9
|
+
* P1 (file, task, rule): 50% = ~1024 bytes
|
|
10
|
+
* P2 (cwd, error, decision, env, git): 35% = ~716 bytes
|
|
11
|
+
* P3-P4 (subagent, skill, role, data, intent): 15% = ~308 bytes
|
|
12
|
+
*/
|
|
13
|
+
/** Stored event as read from SessionDB. */
|
|
14
|
+
export interface StoredEvent {
|
|
15
|
+
type: string;
|
|
16
|
+
category: string;
|
|
17
|
+
data: string;
|
|
18
|
+
priority: number;
|
|
19
|
+
created_at?: string;
|
|
20
|
+
}
|
|
21
|
+
export interface BuildSnapshotOpts {
|
|
22
|
+
maxBytes?: number;
|
|
23
|
+
compactCount?: number;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Render <active_files> from file events.
|
|
27
|
+
* Deduplicates by path, counts operations, keeps the last 10 files.
|
|
28
|
+
*/
|
|
29
|
+
export declare function renderActiveFiles(fileEvents: StoredEvent[]): string;
|
|
30
|
+
/**
|
|
31
|
+
* Render <task_state> from task events.
|
|
32
|
+
* Shows the most recent task state (last event's data).
|
|
33
|
+
*/
|
|
34
|
+
export declare function renderTaskState(taskEvents: StoredEvent[]): string;
|
|
35
|
+
/**
|
|
36
|
+
* Render <rules> from rule events.
|
|
37
|
+
* Lists each unique rule source path + content summaries.
|
|
38
|
+
*/
|
|
39
|
+
export declare function renderRules(ruleEvents: StoredEvent[]): string;
|
|
40
|
+
/**
|
|
41
|
+
* Render <decisions> from decision events.
|
|
42
|
+
*/
|
|
43
|
+
export declare function renderDecisions(decisionEvents: StoredEvent[]): string;
|
|
44
|
+
/**
|
|
45
|
+
* Render <environment> from cwd, env, and git events.
|
|
46
|
+
*/
|
|
47
|
+
export declare function renderEnvironment(cwdEvent: StoredEvent | undefined, envEvents: StoredEvent[], gitEvent: StoredEvent | undefined): string;
|
|
48
|
+
/**
|
|
49
|
+
* Render <errors_resolved> from error events.
|
|
50
|
+
*/
|
|
51
|
+
export declare function renderErrors(errorEvents: StoredEvent[]): string;
|
|
52
|
+
/**
|
|
53
|
+
* Render <intent> from the most recent intent event.
|
|
54
|
+
*/
|
|
55
|
+
export declare function renderIntent(intentEvent: StoredEvent): string;
|
|
56
|
+
/**
|
|
57
|
+
* Render <mcp_tools> from MCP tool call events.
|
|
58
|
+
* Deduplicates by tool name, shows usage count.
|
|
59
|
+
*/
|
|
60
|
+
export declare function renderMcpTools(mcpEvents: StoredEvent[]): string;
|
|
61
|
+
/**
|
|
62
|
+
* Build a resume snapshot XML string from stored session events.
|
|
63
|
+
*
|
|
64
|
+
* Algorithm:
|
|
65
|
+
* 1. Group events by category
|
|
66
|
+
* 2. Render each section
|
|
67
|
+
* 3. Assemble by priority tier with budget trimming
|
|
68
|
+
* 4. If over maxBytes, drop lowest priority sections first
|
|
69
|
+
*/
|
|
70
|
+
export declare function buildResumeSnapshot(events: StoredEvent[], opts?: BuildSnapshotOpts): string;
|