claude-memory-hub 0.9.2 → 0.9.5
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/CHANGELOG.md +59 -0
- package/README.md +1 -0
- package/dist/cli.js +103 -16
- package/dist/hooks/post-compact.js +19 -2
- package/dist/hooks/post-tool-use.js +16 -2
- package/dist/hooks/pre-compact.js +19 -2
- package/dist/hooks/session-end.js +19 -2
- package/dist/hooks/user-prompt-submit.js +16 -2
- package/dist/index.js +10 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,65 @@ Format follows [Keep a Changelog](https://keepachangelog.com/).
|
|
|
5
5
|
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
+
## [0.9.5] - 2026-04-03
|
|
9
|
+
|
|
10
|
+
Stable install path — hooks no longer break after reboot or bunx cache cleanup.
|
|
11
|
+
|
|
12
|
+
### Bug Fixes
|
|
13
|
+
|
|
14
|
+
- **Hooks pointing to temp `bunx` path** — `bunx claude-memory-hub install` registered hooks at `/private/tmp/bunx-*/...` (macOS) or `%TEMP%/bunx-*/...` (Windows). These paths are ephemeral and get deleted on reboot or cache cleanup, causing **all hooks to silently fail** — sessions stop being captured with no error visible to the user
|
|
15
|
+
- **Install now copies `dist/` to `~/.claude-memory-hub/dist/`** — a stable, persistent location under the user's home directory. Both hooks and MCP server reference this path instead of the package install location
|
|
16
|
+
- **Old hook entries auto-replaced** — `install` removes previous claude-memory-hub hook entries before registering new ones, fixing stale paths from prior installs without manual cleanup
|
|
17
|
+
- **`install.sh` updated** — shell-based installer uses the same stable path strategy with full bun binary resolution
|
|
18
|
+
|
|
19
|
+
### How It Works
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
bunx claude-memory-hub install
|
|
23
|
+
1. Downloads package to temp dir (bunx behavior)
|
|
24
|
+
2. Copies dist/*.js + dist/hooks/*.js → ~/.claude-memory-hub/dist/ ← NEW
|
|
25
|
+
3. Registers hooks pointing to ~/.claude-memory-hub/dist/hooks/ ← STABLE
|
|
26
|
+
4. Registers MCP server pointing to ~/.claude-memory-hub/dist/index.js
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### Upgrade Note
|
|
30
|
+
|
|
31
|
+
Run `bunx claude-memory-hub@latest install` to fix broken hooks. No data loss — only hook paths are updated.
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## [0.9.4] - 2026-04-02
|
|
36
|
+
|
|
37
|
+
Windows path fix — backslashes no longer eaten by bash.
|
|
38
|
+
|
|
39
|
+
### Bug Fixes
|
|
40
|
+
|
|
41
|
+
- **Windows backslash paths in hooks** — `C:\Users\Admin\.bun\bin\bun.exe` was passed raw into bash commands, which stripped backslashes → `C:UsersAdmin.bunbinbun.exe`. New `shellPath()` utility converts all paths to forward slashes (`C:/Users/Admin/.bun/bin/bun.exe`) and quotes paths with spaces. Applied to: bun binary path, hook script paths, MCP server path
|
|
42
|
+
- **`where` output parsing on Windows** — `where bun` returns `\r\n` line endings; now splits on `/\r?\n/` instead of `\n`
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## [0.9.3] - 2026-04-02
|
|
47
|
+
|
|
48
|
+
Summary quality improvements — cleaner data in, garbage data out.
|
|
49
|
+
|
|
50
|
+
### Summary Quality
|
|
51
|
+
|
|
52
|
+
- **Strip IDE tags from user_prompt** — `<ide_opened_file>`, `<ide_selection>`, `<system-reminder>` tags are now removed before storing `user_prompt` in L2. Summaries and search results no longer contain IDE noise
|
|
53
|
+
- **Skip low-value sessions** — sessions with only `file_read` entities (browsing, no edits) and no errors/decisions/notes/observations are no longer summarized. Prevents generic "Session in project X" entries from polluting L3
|
|
54
|
+
- **`hasModifiedFiles()` method** — new SessionStore method checks for `file_modified` or `file_created` entities efficiently
|
|
55
|
+
|
|
56
|
+
### New CLI Command
|
|
57
|
+
|
|
58
|
+
- **`prune` command** — removes low-quality summaries from L3: generic text ("Session worked on...", "Session in project..."), IDE tag noise, and empty summaries with no files/decisions/errors. Supports `--dry-run` for safe preview. Also cleans related embeddings
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
bunx claude-memory-hub prune --dry-run # preview
|
|
62
|
+
bunx claude-memory-hub prune # delete
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
8
67
|
## [0.9.2] - 2026-04-02
|
|
9
68
|
|
|
10
69
|
Cross-platform hook reliability — Windows/WSL no longer fails with "bun: command not found".
|
package/README.md
CHANGED
|
@@ -285,6 +285,7 @@ bunx claude-memory-hub reindex # Rebuild TF-IDF + embedding indexes
|
|
|
285
285
|
bunx claude-memory-hub export # Export data as JSONL to stdout
|
|
286
286
|
bunx claude-memory-hub import # Import JSONL from stdin (--dry-run)
|
|
287
287
|
bunx claude-memory-hub cleanup # Remove old data (--days N, default 90)
|
|
288
|
+
bunx claude-memory-hub prune # Remove low-quality summaries (--dry-run)
|
|
288
289
|
```
|
|
289
290
|
|
|
290
291
|
### Requirements
|
package/dist/cli.js
CHANGED
|
@@ -110,6 +110,13 @@ var init_logger = __esm(() => {
|
|
|
110
110
|
});
|
|
111
111
|
|
|
112
112
|
// src/db/schema.ts
|
|
113
|
+
var exports_schema = {};
|
|
114
|
+
__export(exports_schema, {
|
|
115
|
+
initDatabase: () => initDatabase,
|
|
116
|
+
getDbPath: () => getDbPath,
|
|
117
|
+
getDatabase: () => getDatabase,
|
|
118
|
+
closeDatabase: () => closeDatabase
|
|
119
|
+
});
|
|
113
120
|
import { Database } from "bun:sqlite";
|
|
114
121
|
import { existsSync as existsSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
115
122
|
import { homedir as homedir2 } from "os";
|
|
@@ -242,6 +249,12 @@ function getDatabase() {
|
|
|
242
249
|
}
|
|
243
250
|
return _db;
|
|
244
251
|
}
|
|
252
|
+
function closeDatabase() {
|
|
253
|
+
if (_db) {
|
|
254
|
+
_db.close();
|
|
255
|
+
_db = null;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
245
258
|
var log, CREATE_TABLES = `
|
|
246
259
|
-- Migration version tracking
|
|
247
260
|
CREATE TABLE IF NOT EXISTS schema_versions (
|
|
@@ -1656,7 +1669,7 @@ var init_importer = __esm(() => {
|
|
|
1656
1669
|
});
|
|
1657
1670
|
|
|
1658
1671
|
// src/cli/main.ts
|
|
1659
|
-
import { existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync, writeFileSync } from "fs";
|
|
1672
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync, writeFileSync, readdirSync } from "fs";
|
|
1660
1673
|
import { homedir as homedir5 } from "os";
|
|
1661
1674
|
import { join as join5, resolve, dirname } from "path";
|
|
1662
1675
|
|
|
@@ -1954,29 +1967,59 @@ import { spawnSync } from "child_process";
|
|
|
1954
1967
|
var CLAUDE_DIR = join5(homedir5(), ".claude");
|
|
1955
1968
|
var SETTINGS_PATH = join5(CLAUDE_DIR, "settings.json");
|
|
1956
1969
|
var PKG_DIR = resolve(dirname(import.meta.dir));
|
|
1970
|
+
var STABLE_DIR = join5(homedir5(), ".claude-memory-hub");
|
|
1971
|
+
function shellPath(p) {
|
|
1972
|
+
const normalized = p.replace(/\\/g, "/");
|
|
1973
|
+
return normalized.includes(" ") ? `"${normalized}"` : normalized;
|
|
1974
|
+
}
|
|
1957
1975
|
function getBunPath() {
|
|
1958
1976
|
const result = spawnSync(process.platform === "win32" ? "where" : "which", ["bun"], {
|
|
1959
1977
|
encoding: "utf-8"
|
|
1960
1978
|
});
|
|
1961
|
-
const resolved = result.stdout?.trim().split(
|
|
1962
|
-
`)[0]?.trim();
|
|
1979
|
+
const resolved = result.stdout?.trim().split(/\r?\n/)[0]?.trim();
|
|
1963
1980
|
if (resolved && existsSync5(resolved))
|
|
1964
|
-
return resolved;
|
|
1981
|
+
return shellPath(resolved);
|
|
1965
1982
|
const candidates = [
|
|
1966
1983
|
join5(homedir5(), ".bun", "bin", "bun"),
|
|
1967
1984
|
join5(homedir5(), ".bun", "bin", "bun.exe")
|
|
1968
1985
|
];
|
|
1969
1986
|
for (const c of candidates) {
|
|
1970
1987
|
if (existsSync5(c))
|
|
1971
|
-
return c;
|
|
1988
|
+
return shellPath(c);
|
|
1972
1989
|
}
|
|
1973
1990
|
return "bun";
|
|
1974
1991
|
}
|
|
1992
|
+
function copyDistToStableDir() {
|
|
1993
|
+
const srcDist = join5(PKG_DIR, "dist");
|
|
1994
|
+
const destDist = join5(STABLE_DIR, "dist");
|
|
1995
|
+
if (!existsSync5(srcDist)) {
|
|
1996
|
+
throw new Error(`dist/ not found at ${srcDist}. Run 'bun run build:all' first.`);
|
|
1997
|
+
}
|
|
1998
|
+
const destHooks = join5(destDist, "hooks");
|
|
1999
|
+
mkdirSync3(destHooks, { recursive: true });
|
|
2000
|
+
for (const file of readdirSync(srcDist)) {
|
|
2001
|
+
if (file.endsWith(".js")) {
|
|
2002
|
+
const src = join5(srcDist, file);
|
|
2003
|
+
const dest = join5(destDist, file);
|
|
2004
|
+
writeFileSync(dest, readFileSync(src));
|
|
2005
|
+
}
|
|
2006
|
+
}
|
|
2007
|
+
const srcHooks = join5(srcDist, "hooks");
|
|
2008
|
+
if (existsSync5(srcHooks)) {
|
|
2009
|
+
for (const file of readdirSync(srcHooks)) {
|
|
2010
|
+
if (file.endsWith(".js")) {
|
|
2011
|
+
const src = join5(srcHooks, file);
|
|
2012
|
+
const dest = join5(destHooks, file);
|
|
2013
|
+
writeFileSync(dest, readFileSync(src));
|
|
2014
|
+
}
|
|
2015
|
+
}
|
|
2016
|
+
}
|
|
2017
|
+
}
|
|
1975
2018
|
function getHookPath(hookName) {
|
|
1976
|
-
return join5(
|
|
2019
|
+
return shellPath(join5(STABLE_DIR, "dist", "hooks", `${hookName}.js`));
|
|
1977
2020
|
}
|
|
1978
2021
|
function getMcpServerPath() {
|
|
1979
|
-
return join5(
|
|
2022
|
+
return shellPath(join5(STABLE_DIR, "dist", "index.js"));
|
|
1980
2023
|
}
|
|
1981
2024
|
function loadSettings() {
|
|
1982
2025
|
if (!existsSync5(SETTINGS_PATH))
|
|
@@ -1996,7 +2039,16 @@ function saveSettings(settings) {
|
|
|
1996
2039
|
function install() {
|
|
1997
2040
|
console.log(`claude-memory-hub \u2014 install
|
|
1998
2041
|
`);
|
|
1999
|
-
console.log("
|
|
2042
|
+
console.log("0. Copying dist/ to ~/.claude-memory-hub/dist/...");
|
|
2043
|
+
try {
|
|
2044
|
+
copyDistToStableDir();
|
|
2045
|
+
console.log(" Files copied to stable location.");
|
|
2046
|
+
} catch (e) {
|
|
2047
|
+
console.error(` Failed to copy dist/: ${e}`);
|
|
2048
|
+
console.error(" Hooks will reference package location (may break after bunx cleanup).");
|
|
2049
|
+
}
|
|
2050
|
+
console.log(`
|
|
2051
|
+
1. Registering MCP server...`);
|
|
2000
2052
|
const mcpPath = getMcpServerPath();
|
|
2001
2053
|
const bunBin = getBunPath();
|
|
2002
2054
|
const result = spawnSync("claude", ["mcp", "add", "claude-memory-hub", "-s", "user", "--", bunBin, "run", mcpPath], {
|
|
@@ -2028,14 +2080,12 @@ function install() {
|
|
|
2028
2080
|
for (const [event, scriptPath] of hookEntries) {
|
|
2029
2081
|
const hooks = settings.hooks;
|
|
2030
2082
|
hooks[event] ??= [];
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
registered++;
|
|
2038
|
-
}
|
|
2083
|
+
hooks[event] = hooks[event].filter((e) => !JSON.stringify(e).includes("claude-memory-hub"));
|
|
2084
|
+
hooks[event].push({
|
|
2085
|
+
matcher: "",
|
|
2086
|
+
hooks: [{ type: "command", command: `${bunBin} run ${scriptPath}` }]
|
|
2087
|
+
});
|
|
2088
|
+
registered++;
|
|
2039
2089
|
}
|
|
2040
2090
|
saveSettings(settings);
|
|
2041
2091
|
console.log(` ${registered} hook(s) registered. (${5 - registered} already existed)`);
|
|
@@ -2223,6 +2273,42 @@ switch (command) {
|
|
|
2223
2273
|
console.log(`Deleted: ${result.sessions_deleted} sessions, ${result.entities_deleted} entities, ${result.embeddings_deleted} embeddings`);
|
|
2224
2274
|
break;
|
|
2225
2275
|
}
|
|
2276
|
+
case "prune": {
|
|
2277
|
+
const { getDatabase: getDatabase2 } = (init_schema(), __toCommonJS(exports_schema));
|
|
2278
|
+
const db = getDatabase2();
|
|
2279
|
+
const dryRun = process.argv.includes("--dry-run");
|
|
2280
|
+
console.log(`claude-memory-hub \u2014 prune low-quality summaries${dryRun ? " (dry run)" : ""}
|
|
2281
|
+
`);
|
|
2282
|
+
const garbage = db.query(`SELECT id, session_id, summary FROM long_term_summaries
|
|
2283
|
+
WHERE length(summary) < 50
|
|
2284
|
+
OR summary LIKE '%Session worked on%'
|
|
2285
|
+
OR summary LIKE '%Session in project%'
|
|
2286
|
+
OR summary LIKE '%<ide_%'
|
|
2287
|
+
OR summary LIKE '%<system-reminder>%'
|
|
2288
|
+
OR (files_touched = '[]' AND decisions = '[]' AND errors_fixed = '[]' AND length(summary) < 100)`).all();
|
|
2289
|
+
if (garbage.length === 0) {
|
|
2290
|
+
console.log(" No low-quality summaries found. Database is clean.");
|
|
2291
|
+
break;
|
|
2292
|
+
}
|
|
2293
|
+
console.log(` Found ${garbage.length} low-quality summaries:`);
|
|
2294
|
+
for (const g of garbage.slice(0, 10)) {
|
|
2295
|
+
console.log(` [${g.id}] "${g.summary.slice(0, 80)}${g.summary.length > 80 ? "..." : ""}"`);
|
|
2296
|
+
}
|
|
2297
|
+
if (garbage.length > 10)
|
|
2298
|
+
console.log(` ... and ${garbage.length - 10} more`);
|
|
2299
|
+
if (dryRun) {
|
|
2300
|
+
console.log(`
|
|
2301
|
+
Dry run \u2014 no changes made. Remove --dry-run to delete.`);
|
|
2302
|
+
} else {
|
|
2303
|
+
const ids = garbage.map((g) => g.id);
|
|
2304
|
+
const placeholders = ids.map(() => "?").join(",");
|
|
2305
|
+
db.run(`DELETE FROM long_term_summaries WHERE id IN (${placeholders})`, ids);
|
|
2306
|
+
db.run(`DELETE FROM embeddings WHERE doc_type = 'summary' AND doc_id IN (${placeholders})`, ids.map(String));
|
|
2307
|
+
console.log(`
|
|
2308
|
+
Deleted ${garbage.length} summaries + related embeddings.`);
|
|
2309
|
+
}
|
|
2310
|
+
break;
|
|
2311
|
+
}
|
|
2226
2312
|
default:
|
|
2227
2313
|
console.log(`claude-memory-hub \u2014 persistent memory for Claude Code
|
|
2228
2314
|
`);
|
|
@@ -2237,6 +2323,7 @@ switch (command) {
|
|
|
2237
2323
|
console.log(" export Export data as JSONL (--since T, --table T)");
|
|
2238
2324
|
console.log(" import Import JSONL from stdin (--dry-run)");
|
|
2239
2325
|
console.log(" cleanup Remove old data (--days N, default 90)");
|
|
2326
|
+
console.log(" prune Remove low-quality summaries (--dry-run)");
|
|
2240
2327
|
console.log(`
|
|
2241
2328
|
Usage: npx claude-memory-hub <command>`);
|
|
2242
2329
|
break;
|
|
@@ -376,6 +376,11 @@ class SessionStore {
|
|
|
376
376
|
this.db.run("UPDATE sessions SET status = 'completed', ended_at = ? WHERE id = ?", [Date.now(), id]);
|
|
377
377
|
}
|
|
378
378
|
insertEntity(entity) {
|
|
379
|
+
if (entity.entity_type === "decision" || entity.entity_type === "observation") {
|
|
380
|
+
const existing = this.db.query("SELECT COUNT(*) as c FROM entities WHERE session_id = ? AND entity_type = ? AND entity_value = ?").get(entity.session_id, entity.entity_type, entity.entity_value);
|
|
381
|
+
if (existing && existing.c > 0)
|
|
382
|
+
return -1;
|
|
383
|
+
}
|
|
379
384
|
const result = this.db.run(`INSERT INTO entities(session_id, project, tool_name, entity_type, entity_value, context, importance, created_at, prompt_number)
|
|
380
385
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
381
386
|
entity.session_id,
|
|
@@ -407,6 +412,11 @@ class SessionStore {
|
|
|
407
412
|
WHERE session_id = ? AND entity_type IN ('file_read','file_modified','file_created')
|
|
408
413
|
ORDER BY importance DESC, created_at DESC`).all(session_id).map((r) => r.entity_value);
|
|
409
414
|
}
|
|
415
|
+
hasModifiedFiles(session_id) {
|
|
416
|
+
const row = this.db.query(`SELECT COUNT(*) as c FROM entities
|
|
417
|
+
WHERE session_id = ? AND entity_type IN ('file_modified','file_created') LIMIT 1`).get(session_id);
|
|
418
|
+
return (row?.c ?? 0) > 0;
|
|
419
|
+
}
|
|
410
420
|
insertNote(note) {
|
|
411
421
|
this.db.run("INSERT INTO session_notes(session_id, content, created_at) VALUES (?, ?, ?)", [note.session_id, note.content, note.created_at]);
|
|
412
422
|
}
|
|
@@ -658,6 +668,9 @@ class SessionSummarizer {
|
|
|
658
668
|
const notes = this.sessionStore.getSessionNotes(session_id).map((n) => n.content);
|
|
659
669
|
if (files.length === 0 && errors.length === 0 && notes.length === 0)
|
|
660
670
|
return;
|
|
671
|
+
const hasModified = this.sessionStore.hasModifiedFiles(session_id);
|
|
672
|
+
if (!hasModified && errors.length === 0 && decisions.length === 0 && notes.length === 0 && observations.length === 0)
|
|
673
|
+
return;
|
|
661
674
|
const obsValues = observations.slice(0, 5).map((o) => o.entity_value);
|
|
662
675
|
let summaryText;
|
|
663
676
|
let tier = "rule-based";
|
|
@@ -1912,6 +1925,9 @@ function safeJson2(text, fallback) {
|
|
|
1912
1925
|
|
|
1913
1926
|
// src/capture/hook-handler.ts
|
|
1914
1927
|
import { basename as basename3 } from "path";
|
|
1928
|
+
function stripIdeTags(prompt) {
|
|
1929
|
+
return prompt.replace(/<ide_opened_file>[\s\S]*?<\/ide_opened_file>\s*/g, "").replace(/<ide_selection>[\s\S]*?<\/ide_selection>\s*/g, "").replace(/<system-reminder>[\s\S]*?<\/system-reminder>\s*/g, "").trim();
|
|
1930
|
+
}
|
|
1915
1931
|
async function handlePostToolUse(hook, project) {
|
|
1916
1932
|
const store = new SessionStore;
|
|
1917
1933
|
store.upsertSession({
|
|
@@ -1947,14 +1963,15 @@ async function handlePostToolUse(hook, project) {
|
|
|
1947
1963
|
async function handleUserPromptSubmit(hook, project) {
|
|
1948
1964
|
const store = new SessionStore;
|
|
1949
1965
|
const ltStore = new LongTermStore;
|
|
1966
|
+
const cleanPrompt = stripIdeTags(hook.prompt);
|
|
1950
1967
|
store.upsertSession({
|
|
1951
1968
|
id: hook.session_id,
|
|
1952
1969
|
project,
|
|
1953
1970
|
started_at: Date.now(),
|
|
1954
|
-
user_prompt: hook.prompt.slice(0, 500),
|
|
1971
|
+
user_prompt: cleanPrompt.slice(0, 500) || hook.prompt.slice(0, 500),
|
|
1955
1972
|
status: "active"
|
|
1956
1973
|
});
|
|
1957
|
-
const promptObs = extractObservationFromPrompt(hook.prompt, hook.session_id, project, 0);
|
|
1974
|
+
const promptObs = extractObservationFromPrompt(cleanPrompt || hook.prompt, hook.session_id, project, 0);
|
|
1958
1975
|
if (promptObs)
|
|
1959
1976
|
store.insertEntity({ ...promptObs, project });
|
|
1960
1977
|
const results = ltStore.search(hook.prompt, 3);
|
|
@@ -376,6 +376,11 @@ class SessionStore {
|
|
|
376
376
|
this.db.run("UPDATE sessions SET status = 'completed', ended_at = ? WHERE id = ?", [Date.now(), id]);
|
|
377
377
|
}
|
|
378
378
|
insertEntity(entity) {
|
|
379
|
+
if (entity.entity_type === "decision" || entity.entity_type === "observation") {
|
|
380
|
+
const existing = this.db.query("SELECT COUNT(*) as c FROM entities WHERE session_id = ? AND entity_type = ? AND entity_value = ?").get(entity.session_id, entity.entity_type, entity.entity_value);
|
|
381
|
+
if (existing && existing.c > 0)
|
|
382
|
+
return -1;
|
|
383
|
+
}
|
|
379
384
|
const result = this.db.run(`INSERT INTO entities(session_id, project, tool_name, entity_type, entity_value, context, importance, created_at, prompt_number)
|
|
380
385
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
381
386
|
entity.session_id,
|
|
@@ -407,6 +412,11 @@ class SessionStore {
|
|
|
407
412
|
WHERE session_id = ? AND entity_type IN ('file_read','file_modified','file_created')
|
|
408
413
|
ORDER BY importance DESC, created_at DESC`).all(session_id).map((r) => r.entity_value);
|
|
409
414
|
}
|
|
415
|
+
hasModifiedFiles(session_id) {
|
|
416
|
+
const row = this.db.query(`SELECT COUNT(*) as c FROM entities
|
|
417
|
+
WHERE session_id = ? AND entity_type IN ('file_modified','file_created') LIMIT 1`).get(session_id);
|
|
418
|
+
return (row?.c ?? 0) > 0;
|
|
419
|
+
}
|
|
410
420
|
insertNote(note) {
|
|
411
421
|
this.db.run("INSERT INTO session_notes(session_id, content, created_at) VALUES (?, ?, ?)", [note.session_id, note.content, note.created_at]);
|
|
412
422
|
}
|
|
@@ -1619,6 +1629,9 @@ function safeJson2(text, fallback) {
|
|
|
1619
1629
|
|
|
1620
1630
|
// src/capture/hook-handler.ts
|
|
1621
1631
|
import { basename as basename3 } from "path";
|
|
1632
|
+
function stripIdeTags(prompt) {
|
|
1633
|
+
return prompt.replace(/<ide_opened_file>[\s\S]*?<\/ide_opened_file>\s*/g, "").replace(/<ide_selection>[\s\S]*?<\/ide_selection>\s*/g, "").replace(/<system-reminder>[\s\S]*?<\/system-reminder>\s*/g, "").trim();
|
|
1634
|
+
}
|
|
1622
1635
|
async function handlePostToolUse(hook, project) {
|
|
1623
1636
|
const store = new SessionStore;
|
|
1624
1637
|
store.upsertSession({
|
|
@@ -1654,14 +1667,15 @@ async function handlePostToolUse(hook, project) {
|
|
|
1654
1667
|
async function handleUserPromptSubmit(hook, project) {
|
|
1655
1668
|
const store = new SessionStore;
|
|
1656
1669
|
const ltStore = new LongTermStore;
|
|
1670
|
+
const cleanPrompt = stripIdeTags(hook.prompt);
|
|
1657
1671
|
store.upsertSession({
|
|
1658
1672
|
id: hook.session_id,
|
|
1659
1673
|
project,
|
|
1660
1674
|
started_at: Date.now(),
|
|
1661
|
-
user_prompt: hook.prompt.slice(0, 500),
|
|
1675
|
+
user_prompt: cleanPrompt.slice(0, 500) || hook.prompt.slice(0, 500),
|
|
1662
1676
|
status: "active"
|
|
1663
1677
|
});
|
|
1664
|
-
const promptObs = extractObservationFromPrompt(hook.prompt, hook.session_id, project, 0);
|
|
1678
|
+
const promptObs = extractObservationFromPrompt(cleanPrompt || hook.prompt, hook.session_id, project, 0);
|
|
1665
1679
|
if (promptObs)
|
|
1666
1680
|
store.insertEntity({ ...promptObs, project });
|
|
1667
1681
|
const results = ltStore.search(hook.prompt, 3);
|
|
@@ -376,6 +376,11 @@ class SessionStore {
|
|
|
376
376
|
this.db.run("UPDATE sessions SET status = 'completed', ended_at = ? WHERE id = ?", [Date.now(), id]);
|
|
377
377
|
}
|
|
378
378
|
insertEntity(entity) {
|
|
379
|
+
if (entity.entity_type === "decision" || entity.entity_type === "observation") {
|
|
380
|
+
const existing = this.db.query("SELECT COUNT(*) as c FROM entities WHERE session_id = ? AND entity_type = ? AND entity_value = ?").get(entity.session_id, entity.entity_type, entity.entity_value);
|
|
381
|
+
if (existing && existing.c > 0)
|
|
382
|
+
return -1;
|
|
383
|
+
}
|
|
379
384
|
const result = this.db.run(`INSERT INTO entities(session_id, project, tool_name, entity_type, entity_value, context, importance, created_at, prompt_number)
|
|
380
385
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
381
386
|
entity.session_id,
|
|
@@ -407,6 +412,11 @@ class SessionStore {
|
|
|
407
412
|
WHERE session_id = ? AND entity_type IN ('file_read','file_modified','file_created')
|
|
408
413
|
ORDER BY importance DESC, created_at DESC`).all(session_id).map((r) => r.entity_value);
|
|
409
414
|
}
|
|
415
|
+
hasModifiedFiles(session_id) {
|
|
416
|
+
const row = this.db.query(`SELECT COUNT(*) as c FROM entities
|
|
417
|
+
WHERE session_id = ? AND entity_type IN ('file_modified','file_created') LIMIT 1`).get(session_id);
|
|
418
|
+
return (row?.c ?? 0) > 0;
|
|
419
|
+
}
|
|
410
420
|
insertNote(note) {
|
|
411
421
|
this.db.run("INSERT INTO session_notes(session_id, content, created_at) VALUES (?, ?, ?)", [note.session_id, note.content, note.created_at]);
|
|
412
422
|
}
|
|
@@ -658,6 +668,9 @@ class SessionSummarizer {
|
|
|
658
668
|
const notes = this.sessionStore.getSessionNotes(session_id).map((n) => n.content);
|
|
659
669
|
if (files.length === 0 && errors.length === 0 && notes.length === 0)
|
|
660
670
|
return;
|
|
671
|
+
const hasModified = this.sessionStore.hasModifiedFiles(session_id);
|
|
672
|
+
if (!hasModified && errors.length === 0 && decisions.length === 0 && notes.length === 0 && observations.length === 0)
|
|
673
|
+
return;
|
|
661
674
|
const obsValues = observations.slice(0, 5).map((o) => o.entity_value);
|
|
662
675
|
let summaryText;
|
|
663
676
|
let tier = "rule-based";
|
|
@@ -1912,6 +1925,9 @@ function safeJson2(text, fallback) {
|
|
|
1912
1925
|
|
|
1913
1926
|
// src/capture/hook-handler.ts
|
|
1914
1927
|
import { basename as basename3 } from "path";
|
|
1928
|
+
function stripIdeTags(prompt) {
|
|
1929
|
+
return prompt.replace(/<ide_opened_file>[\s\S]*?<\/ide_opened_file>\s*/g, "").replace(/<ide_selection>[\s\S]*?<\/ide_selection>\s*/g, "").replace(/<system-reminder>[\s\S]*?<\/system-reminder>\s*/g, "").trim();
|
|
1930
|
+
}
|
|
1915
1931
|
async function handlePostToolUse(hook, project) {
|
|
1916
1932
|
const store = new SessionStore;
|
|
1917
1933
|
store.upsertSession({
|
|
@@ -1947,14 +1963,15 @@ async function handlePostToolUse(hook, project) {
|
|
|
1947
1963
|
async function handleUserPromptSubmit(hook, project) {
|
|
1948
1964
|
const store = new SessionStore;
|
|
1949
1965
|
const ltStore = new LongTermStore;
|
|
1966
|
+
const cleanPrompt = stripIdeTags(hook.prompt);
|
|
1950
1967
|
store.upsertSession({
|
|
1951
1968
|
id: hook.session_id,
|
|
1952
1969
|
project,
|
|
1953
1970
|
started_at: Date.now(),
|
|
1954
|
-
user_prompt: hook.prompt.slice(0, 500),
|
|
1971
|
+
user_prompt: cleanPrompt.slice(0, 500) || hook.prompt.slice(0, 500),
|
|
1955
1972
|
status: "active"
|
|
1956
1973
|
});
|
|
1957
|
-
const promptObs = extractObservationFromPrompt(hook.prompt, hook.session_id, project, 0);
|
|
1974
|
+
const promptObs = extractObservationFromPrompt(cleanPrompt || hook.prompt, hook.session_id, project, 0);
|
|
1958
1975
|
if (promptObs)
|
|
1959
1976
|
store.insertEntity({ ...promptObs, project });
|
|
1960
1977
|
const results = ltStore.search(hook.prompt, 3);
|
|
@@ -376,6 +376,11 @@ class SessionStore {
|
|
|
376
376
|
this.db.run("UPDATE sessions SET status = 'completed', ended_at = ? WHERE id = ?", [Date.now(), id]);
|
|
377
377
|
}
|
|
378
378
|
insertEntity(entity) {
|
|
379
|
+
if (entity.entity_type === "decision" || entity.entity_type === "observation") {
|
|
380
|
+
const existing = this.db.query("SELECT COUNT(*) as c FROM entities WHERE session_id = ? AND entity_type = ? AND entity_value = ?").get(entity.session_id, entity.entity_type, entity.entity_value);
|
|
381
|
+
if (existing && existing.c > 0)
|
|
382
|
+
return -1;
|
|
383
|
+
}
|
|
379
384
|
const result = this.db.run(`INSERT INTO entities(session_id, project, tool_name, entity_type, entity_value, context, importance, created_at, prompt_number)
|
|
380
385
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
381
386
|
entity.session_id,
|
|
@@ -407,6 +412,11 @@ class SessionStore {
|
|
|
407
412
|
WHERE session_id = ? AND entity_type IN ('file_read','file_modified','file_created')
|
|
408
413
|
ORDER BY importance DESC, created_at DESC`).all(session_id).map((r) => r.entity_value);
|
|
409
414
|
}
|
|
415
|
+
hasModifiedFiles(session_id) {
|
|
416
|
+
const row = this.db.query(`SELECT COUNT(*) as c FROM entities
|
|
417
|
+
WHERE session_id = ? AND entity_type IN ('file_modified','file_created') LIMIT 1`).get(session_id);
|
|
418
|
+
return (row?.c ?? 0) > 0;
|
|
419
|
+
}
|
|
410
420
|
insertNote(note) {
|
|
411
421
|
this.db.run("INSERT INTO session_notes(session_id, content, created_at) VALUES (?, ?, ?)", [note.session_id, note.content, note.created_at]);
|
|
412
422
|
}
|
|
@@ -1619,6 +1629,9 @@ function safeJson2(text, fallback) {
|
|
|
1619
1629
|
|
|
1620
1630
|
// src/capture/hook-handler.ts
|
|
1621
1631
|
import { basename as basename3 } from "path";
|
|
1632
|
+
function stripIdeTags(prompt) {
|
|
1633
|
+
return prompt.replace(/<ide_opened_file>[\s\S]*?<\/ide_opened_file>\s*/g, "").replace(/<ide_selection>[\s\S]*?<\/ide_selection>\s*/g, "").replace(/<system-reminder>[\s\S]*?<\/system-reminder>\s*/g, "").trim();
|
|
1634
|
+
}
|
|
1622
1635
|
async function handlePostToolUse(hook, project) {
|
|
1623
1636
|
const store = new SessionStore;
|
|
1624
1637
|
store.upsertSession({
|
|
@@ -1654,14 +1667,15 @@ async function handlePostToolUse(hook, project) {
|
|
|
1654
1667
|
async function handleUserPromptSubmit(hook, project) {
|
|
1655
1668
|
const store = new SessionStore;
|
|
1656
1669
|
const ltStore = new LongTermStore;
|
|
1670
|
+
const cleanPrompt = stripIdeTags(hook.prompt);
|
|
1657
1671
|
store.upsertSession({
|
|
1658
1672
|
id: hook.session_id,
|
|
1659
1673
|
project,
|
|
1660
1674
|
started_at: Date.now(),
|
|
1661
|
-
user_prompt: hook.prompt.slice(0, 500),
|
|
1675
|
+
user_prompt: cleanPrompt.slice(0, 500) || hook.prompt.slice(0, 500),
|
|
1662
1676
|
status: "active"
|
|
1663
1677
|
});
|
|
1664
|
-
const promptObs = extractObservationFromPrompt(hook.prompt, hook.session_id, project, 0);
|
|
1678
|
+
const promptObs = extractObservationFromPrompt(cleanPrompt || hook.prompt, hook.session_id, project, 0);
|
|
1665
1679
|
if (promptObs)
|
|
1666
1680
|
store.insertEntity({ ...promptObs, project });
|
|
1667
1681
|
const results = ltStore.search(hook.prompt, 3);
|
|
@@ -1936,6 +1950,9 @@ class SessionSummarizer {
|
|
|
1936
1950
|
const notes = this.sessionStore.getSessionNotes(session_id).map((n) => n.content);
|
|
1937
1951
|
if (files.length === 0 && errors.length === 0 && notes.length === 0)
|
|
1938
1952
|
return;
|
|
1953
|
+
const hasModified = this.sessionStore.hasModifiedFiles(session_id);
|
|
1954
|
+
if (!hasModified && errors.length === 0 && decisions.length === 0 && notes.length === 0 && observations.length === 0)
|
|
1955
|
+
return;
|
|
1939
1956
|
const obsValues = observations.slice(0, 5).map((o) => o.entity_value);
|
|
1940
1957
|
let summaryText;
|
|
1941
1958
|
let tier = "rule-based";
|
|
@@ -376,6 +376,11 @@ class SessionStore {
|
|
|
376
376
|
this.db.run("UPDATE sessions SET status = 'completed', ended_at = ? WHERE id = ?", [Date.now(), id]);
|
|
377
377
|
}
|
|
378
378
|
insertEntity(entity) {
|
|
379
|
+
if (entity.entity_type === "decision" || entity.entity_type === "observation") {
|
|
380
|
+
const existing = this.db.query("SELECT COUNT(*) as c FROM entities WHERE session_id = ? AND entity_type = ? AND entity_value = ?").get(entity.session_id, entity.entity_type, entity.entity_value);
|
|
381
|
+
if (existing && existing.c > 0)
|
|
382
|
+
return -1;
|
|
383
|
+
}
|
|
379
384
|
const result = this.db.run(`INSERT INTO entities(session_id, project, tool_name, entity_type, entity_value, context, importance, created_at, prompt_number)
|
|
380
385
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
381
386
|
entity.session_id,
|
|
@@ -407,6 +412,11 @@ class SessionStore {
|
|
|
407
412
|
WHERE session_id = ? AND entity_type IN ('file_read','file_modified','file_created')
|
|
408
413
|
ORDER BY importance DESC, created_at DESC`).all(session_id).map((r) => r.entity_value);
|
|
409
414
|
}
|
|
415
|
+
hasModifiedFiles(session_id) {
|
|
416
|
+
const row = this.db.query(`SELECT COUNT(*) as c FROM entities
|
|
417
|
+
WHERE session_id = ? AND entity_type IN ('file_modified','file_created') LIMIT 1`).get(session_id);
|
|
418
|
+
return (row?.c ?? 0) > 0;
|
|
419
|
+
}
|
|
410
420
|
insertNote(note) {
|
|
411
421
|
this.db.run("INSERT INTO session_notes(session_id, content, created_at) VALUES (?, ?, ?)", [note.session_id, note.content, note.created_at]);
|
|
412
422
|
}
|
|
@@ -1619,6 +1629,9 @@ function safeJson2(text, fallback) {
|
|
|
1619
1629
|
|
|
1620
1630
|
// src/capture/hook-handler.ts
|
|
1621
1631
|
import { basename as basename3 } from "path";
|
|
1632
|
+
function stripIdeTags(prompt) {
|
|
1633
|
+
return prompt.replace(/<ide_opened_file>[\s\S]*?<\/ide_opened_file>\s*/g, "").replace(/<ide_selection>[\s\S]*?<\/ide_selection>\s*/g, "").replace(/<system-reminder>[\s\S]*?<\/system-reminder>\s*/g, "").trim();
|
|
1634
|
+
}
|
|
1622
1635
|
async function handlePostToolUse(hook, project) {
|
|
1623
1636
|
const store = new SessionStore;
|
|
1624
1637
|
store.upsertSession({
|
|
@@ -1654,14 +1667,15 @@ async function handlePostToolUse(hook, project) {
|
|
|
1654
1667
|
async function handleUserPromptSubmit(hook, project) {
|
|
1655
1668
|
const store = new SessionStore;
|
|
1656
1669
|
const ltStore = new LongTermStore;
|
|
1670
|
+
const cleanPrompt = stripIdeTags(hook.prompt);
|
|
1657
1671
|
store.upsertSession({
|
|
1658
1672
|
id: hook.session_id,
|
|
1659
1673
|
project,
|
|
1660
1674
|
started_at: Date.now(),
|
|
1661
|
-
user_prompt: hook.prompt.slice(0, 500),
|
|
1675
|
+
user_prompt: cleanPrompt.slice(0, 500) || hook.prompt.slice(0, 500),
|
|
1662
1676
|
status: "active"
|
|
1663
1677
|
});
|
|
1664
|
-
const promptObs = extractObservationFromPrompt(hook.prompt, hook.session_id, project, 0);
|
|
1678
|
+
const promptObs = extractObservationFromPrompt(cleanPrompt || hook.prompt, hook.session_id, project, 0);
|
|
1665
1679
|
if (promptObs)
|
|
1666
1680
|
store.insertEntity({ ...promptObs, project });
|
|
1667
1681
|
const results = ltStore.search(hook.prompt, 3);
|
package/dist/index.js
CHANGED
|
@@ -14140,6 +14140,11 @@ class SessionStore {
|
|
|
14140
14140
|
this.db.run("UPDATE sessions SET status = 'completed', ended_at = ? WHERE id = ?", [Date.now(), id]);
|
|
14141
14141
|
}
|
|
14142
14142
|
insertEntity(entity) {
|
|
14143
|
+
if (entity.entity_type === "decision" || entity.entity_type === "observation") {
|
|
14144
|
+
const existing = this.db.query("SELECT COUNT(*) as c FROM entities WHERE session_id = ? AND entity_type = ? AND entity_value = ?").get(entity.session_id, entity.entity_type, entity.entity_value);
|
|
14145
|
+
if (existing && existing.c > 0)
|
|
14146
|
+
return -1;
|
|
14147
|
+
}
|
|
14143
14148
|
const result = this.db.run(`INSERT INTO entities(session_id, project, tool_name, entity_type, entity_value, context, importance, created_at, prompt_number)
|
|
14144
14149
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
14145
14150
|
entity.session_id,
|
|
@@ -14171,6 +14176,11 @@ class SessionStore {
|
|
|
14171
14176
|
WHERE session_id = ? AND entity_type IN ('file_read','file_modified','file_created')
|
|
14172
14177
|
ORDER BY importance DESC, created_at DESC`).all(session_id).map((r) => r.entity_value);
|
|
14173
14178
|
}
|
|
14179
|
+
hasModifiedFiles(session_id) {
|
|
14180
|
+
const row = this.db.query(`SELECT COUNT(*) as c FROM entities
|
|
14181
|
+
WHERE session_id = ? AND entity_type IN ('file_modified','file_created') LIMIT 1`).get(session_id);
|
|
14182
|
+
return (row?.c ?? 0) > 0;
|
|
14183
|
+
}
|
|
14174
14184
|
insertNote(note) {
|
|
14175
14185
|
this.db.run("INSERT INTO session_notes(session_id, content, created_at) VALUES (?, ?, ?)", [note.session_id, note.content, note.created_at]);
|
|
14176
14186
|
}
|
package/package.json
CHANGED