claude-memory-hub 0.9.2 → 0.9.4
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 +32 -0
- package/README.md +1 -0
- package/dist/cli.js +59 -6
- package/dist/hooks/post-compact.js +14 -2
- package/dist/hooks/post-tool-use.js +11 -2
- package/dist/hooks/pre-compact.js +14 -2
- package/dist/hooks/session-end.js +14 -2
- package/dist/hooks/user-prompt-submit.js +11 -2
- package/dist/index.js +5 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,38 @@ Format follows [Keep a Changelog](https://keepachangelog.com/).
|
|
|
5
5
|
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
+
## [0.9.4] - 2026-04-02
|
|
9
|
+
|
|
10
|
+
Windows path fix — backslashes no longer eaten by bash.
|
|
11
|
+
|
|
12
|
+
### Bug Fixes
|
|
13
|
+
|
|
14
|
+
- **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
|
|
15
|
+
- **`where` output parsing on Windows** — `where bun` returns `\r\n` line endings; now splits on `/\r?\n/` instead of `\n`
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## [0.9.3] - 2026-04-02
|
|
20
|
+
|
|
21
|
+
Summary quality improvements — cleaner data in, garbage data out.
|
|
22
|
+
|
|
23
|
+
### Summary Quality
|
|
24
|
+
|
|
25
|
+
- **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
|
|
26
|
+
- **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
|
|
27
|
+
- **`hasModifiedFiles()` method** — new SessionStore method checks for `file_modified` or `file_created` entities efficiently
|
|
28
|
+
|
|
29
|
+
### New CLI Command
|
|
30
|
+
|
|
31
|
+
- **`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
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
bunx claude-memory-hub prune --dry-run # preview
|
|
35
|
+
bunx claude-memory-hub prune # delete
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
8
40
|
## [0.9.2] - 2026-04-02
|
|
9
41
|
|
|
10
42
|
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 (
|
|
@@ -1954,29 +1967,32 @@ 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
|
+
function shellPath(p) {
|
|
1971
|
+
const normalized = p.replace(/\\/g, "/");
|
|
1972
|
+
return normalized.includes(" ") ? `"${normalized}"` : normalized;
|
|
1973
|
+
}
|
|
1957
1974
|
function getBunPath() {
|
|
1958
1975
|
const result = spawnSync(process.platform === "win32" ? "where" : "which", ["bun"], {
|
|
1959
1976
|
encoding: "utf-8"
|
|
1960
1977
|
});
|
|
1961
|
-
const resolved = result.stdout?.trim().split(
|
|
1962
|
-
`)[0]?.trim();
|
|
1978
|
+
const resolved = result.stdout?.trim().split(/\r?\n/)[0]?.trim();
|
|
1963
1979
|
if (resolved && existsSync5(resolved))
|
|
1964
|
-
return resolved;
|
|
1980
|
+
return shellPath(resolved);
|
|
1965
1981
|
const candidates = [
|
|
1966
1982
|
join5(homedir5(), ".bun", "bin", "bun"),
|
|
1967
1983
|
join5(homedir5(), ".bun", "bin", "bun.exe")
|
|
1968
1984
|
];
|
|
1969
1985
|
for (const c of candidates) {
|
|
1970
1986
|
if (existsSync5(c))
|
|
1971
|
-
return c;
|
|
1987
|
+
return shellPath(c);
|
|
1972
1988
|
}
|
|
1973
1989
|
return "bun";
|
|
1974
1990
|
}
|
|
1975
1991
|
function getHookPath(hookName) {
|
|
1976
|
-
return join5(PKG_DIR, "dist", "hooks", `${hookName}.js`);
|
|
1992
|
+
return shellPath(join5(PKG_DIR, "dist", "hooks", `${hookName}.js`));
|
|
1977
1993
|
}
|
|
1978
1994
|
function getMcpServerPath() {
|
|
1979
|
-
return join5(PKG_DIR, "dist", "index.js");
|
|
1995
|
+
return shellPath(join5(PKG_DIR, "dist", "index.js"));
|
|
1980
1996
|
}
|
|
1981
1997
|
function loadSettings() {
|
|
1982
1998
|
if (!existsSync5(SETTINGS_PATH))
|
|
@@ -2223,6 +2239,42 @@ switch (command) {
|
|
|
2223
2239
|
console.log(`Deleted: ${result.sessions_deleted} sessions, ${result.entities_deleted} entities, ${result.embeddings_deleted} embeddings`);
|
|
2224
2240
|
break;
|
|
2225
2241
|
}
|
|
2242
|
+
case "prune": {
|
|
2243
|
+
const { getDatabase: getDatabase2 } = (init_schema(), __toCommonJS(exports_schema));
|
|
2244
|
+
const db = getDatabase2();
|
|
2245
|
+
const dryRun = process.argv.includes("--dry-run");
|
|
2246
|
+
console.log(`claude-memory-hub \u2014 prune low-quality summaries${dryRun ? " (dry run)" : ""}
|
|
2247
|
+
`);
|
|
2248
|
+
const garbage = db.query(`SELECT id, session_id, summary FROM long_term_summaries
|
|
2249
|
+
WHERE length(summary) < 50
|
|
2250
|
+
OR summary LIKE '%Session worked on%'
|
|
2251
|
+
OR summary LIKE '%Session in project%'
|
|
2252
|
+
OR summary LIKE '%<ide_%'
|
|
2253
|
+
OR summary LIKE '%<system-reminder>%'
|
|
2254
|
+
OR (files_touched = '[]' AND decisions = '[]' AND errors_fixed = '[]' AND length(summary) < 100)`).all();
|
|
2255
|
+
if (garbage.length === 0) {
|
|
2256
|
+
console.log(" No low-quality summaries found. Database is clean.");
|
|
2257
|
+
break;
|
|
2258
|
+
}
|
|
2259
|
+
console.log(` Found ${garbage.length} low-quality summaries:`);
|
|
2260
|
+
for (const g of garbage.slice(0, 10)) {
|
|
2261
|
+
console.log(` [${g.id}] "${g.summary.slice(0, 80)}${g.summary.length > 80 ? "..." : ""}"`);
|
|
2262
|
+
}
|
|
2263
|
+
if (garbage.length > 10)
|
|
2264
|
+
console.log(` ... and ${garbage.length - 10} more`);
|
|
2265
|
+
if (dryRun) {
|
|
2266
|
+
console.log(`
|
|
2267
|
+
Dry run \u2014 no changes made. Remove --dry-run to delete.`);
|
|
2268
|
+
} else {
|
|
2269
|
+
const ids = garbage.map((g) => g.id);
|
|
2270
|
+
const placeholders = ids.map(() => "?").join(",");
|
|
2271
|
+
db.run(`DELETE FROM long_term_summaries WHERE id IN (${placeholders})`, ids);
|
|
2272
|
+
db.run(`DELETE FROM embeddings WHERE doc_type = 'summary' AND doc_id IN (${placeholders})`, ids.map(String));
|
|
2273
|
+
console.log(`
|
|
2274
|
+
Deleted ${garbage.length} summaries + related embeddings.`);
|
|
2275
|
+
}
|
|
2276
|
+
break;
|
|
2277
|
+
}
|
|
2226
2278
|
default:
|
|
2227
2279
|
console.log(`claude-memory-hub \u2014 persistent memory for Claude Code
|
|
2228
2280
|
`);
|
|
@@ -2237,6 +2289,7 @@ switch (command) {
|
|
|
2237
2289
|
console.log(" export Export data as JSONL (--since T, --table T)");
|
|
2238
2290
|
console.log(" import Import JSONL from stdin (--dry-run)");
|
|
2239
2291
|
console.log(" cleanup Remove old data (--days N, default 90)");
|
|
2292
|
+
console.log(" prune Remove low-quality summaries (--dry-run)");
|
|
2240
2293
|
console.log(`
|
|
2241
2294
|
Usage: npx claude-memory-hub <command>`);
|
|
2242
2295
|
break;
|
|
@@ -407,6 +407,11 @@ class SessionStore {
|
|
|
407
407
|
WHERE session_id = ? AND entity_type IN ('file_read','file_modified','file_created')
|
|
408
408
|
ORDER BY importance DESC, created_at DESC`).all(session_id).map((r) => r.entity_value);
|
|
409
409
|
}
|
|
410
|
+
hasModifiedFiles(session_id) {
|
|
411
|
+
const row = this.db.query(`SELECT COUNT(*) as c FROM entities
|
|
412
|
+
WHERE session_id = ? AND entity_type IN ('file_modified','file_created') LIMIT 1`).get(session_id);
|
|
413
|
+
return (row?.c ?? 0) > 0;
|
|
414
|
+
}
|
|
410
415
|
insertNote(note) {
|
|
411
416
|
this.db.run("INSERT INTO session_notes(session_id, content, created_at) VALUES (?, ?, ?)", [note.session_id, note.content, note.created_at]);
|
|
412
417
|
}
|
|
@@ -658,6 +663,9 @@ class SessionSummarizer {
|
|
|
658
663
|
const notes = this.sessionStore.getSessionNotes(session_id).map((n) => n.content);
|
|
659
664
|
if (files.length === 0 && errors.length === 0 && notes.length === 0)
|
|
660
665
|
return;
|
|
666
|
+
const hasModified = this.sessionStore.hasModifiedFiles(session_id);
|
|
667
|
+
if (!hasModified && errors.length === 0 && decisions.length === 0 && notes.length === 0 && observations.length === 0)
|
|
668
|
+
return;
|
|
661
669
|
const obsValues = observations.slice(0, 5).map((o) => o.entity_value);
|
|
662
670
|
let summaryText;
|
|
663
671
|
let tier = "rule-based";
|
|
@@ -1912,6 +1920,9 @@ function safeJson2(text, fallback) {
|
|
|
1912
1920
|
|
|
1913
1921
|
// src/capture/hook-handler.ts
|
|
1914
1922
|
import { basename as basename3 } from "path";
|
|
1923
|
+
function stripIdeTags(prompt) {
|
|
1924
|
+
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();
|
|
1925
|
+
}
|
|
1915
1926
|
async function handlePostToolUse(hook, project) {
|
|
1916
1927
|
const store = new SessionStore;
|
|
1917
1928
|
store.upsertSession({
|
|
@@ -1947,14 +1958,15 @@ async function handlePostToolUse(hook, project) {
|
|
|
1947
1958
|
async function handleUserPromptSubmit(hook, project) {
|
|
1948
1959
|
const store = new SessionStore;
|
|
1949
1960
|
const ltStore = new LongTermStore;
|
|
1961
|
+
const cleanPrompt = stripIdeTags(hook.prompt);
|
|
1950
1962
|
store.upsertSession({
|
|
1951
1963
|
id: hook.session_id,
|
|
1952
1964
|
project,
|
|
1953
1965
|
started_at: Date.now(),
|
|
1954
|
-
user_prompt: hook.prompt.slice(0, 500),
|
|
1966
|
+
user_prompt: cleanPrompt.slice(0, 500) || hook.prompt.slice(0, 500),
|
|
1955
1967
|
status: "active"
|
|
1956
1968
|
});
|
|
1957
|
-
const promptObs = extractObservationFromPrompt(hook.prompt, hook.session_id, project, 0);
|
|
1969
|
+
const promptObs = extractObservationFromPrompt(cleanPrompt || hook.prompt, hook.session_id, project, 0);
|
|
1958
1970
|
if (promptObs)
|
|
1959
1971
|
store.insertEntity({ ...promptObs, project });
|
|
1960
1972
|
const results = ltStore.search(hook.prompt, 3);
|
|
@@ -407,6 +407,11 @@ class SessionStore {
|
|
|
407
407
|
WHERE session_id = ? AND entity_type IN ('file_read','file_modified','file_created')
|
|
408
408
|
ORDER BY importance DESC, created_at DESC`).all(session_id).map((r) => r.entity_value);
|
|
409
409
|
}
|
|
410
|
+
hasModifiedFiles(session_id) {
|
|
411
|
+
const row = this.db.query(`SELECT COUNT(*) as c FROM entities
|
|
412
|
+
WHERE session_id = ? AND entity_type IN ('file_modified','file_created') LIMIT 1`).get(session_id);
|
|
413
|
+
return (row?.c ?? 0) > 0;
|
|
414
|
+
}
|
|
410
415
|
insertNote(note) {
|
|
411
416
|
this.db.run("INSERT INTO session_notes(session_id, content, created_at) VALUES (?, ?, ?)", [note.session_id, note.content, note.created_at]);
|
|
412
417
|
}
|
|
@@ -1619,6 +1624,9 @@ function safeJson2(text, fallback) {
|
|
|
1619
1624
|
|
|
1620
1625
|
// src/capture/hook-handler.ts
|
|
1621
1626
|
import { basename as basename3 } from "path";
|
|
1627
|
+
function stripIdeTags(prompt) {
|
|
1628
|
+
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();
|
|
1629
|
+
}
|
|
1622
1630
|
async function handlePostToolUse(hook, project) {
|
|
1623
1631
|
const store = new SessionStore;
|
|
1624
1632
|
store.upsertSession({
|
|
@@ -1654,14 +1662,15 @@ async function handlePostToolUse(hook, project) {
|
|
|
1654
1662
|
async function handleUserPromptSubmit(hook, project) {
|
|
1655
1663
|
const store = new SessionStore;
|
|
1656
1664
|
const ltStore = new LongTermStore;
|
|
1665
|
+
const cleanPrompt = stripIdeTags(hook.prompt);
|
|
1657
1666
|
store.upsertSession({
|
|
1658
1667
|
id: hook.session_id,
|
|
1659
1668
|
project,
|
|
1660
1669
|
started_at: Date.now(),
|
|
1661
|
-
user_prompt: hook.prompt.slice(0, 500),
|
|
1670
|
+
user_prompt: cleanPrompt.slice(0, 500) || hook.prompt.slice(0, 500),
|
|
1662
1671
|
status: "active"
|
|
1663
1672
|
});
|
|
1664
|
-
const promptObs = extractObservationFromPrompt(hook.prompt, hook.session_id, project, 0);
|
|
1673
|
+
const promptObs = extractObservationFromPrompt(cleanPrompt || hook.prompt, hook.session_id, project, 0);
|
|
1665
1674
|
if (promptObs)
|
|
1666
1675
|
store.insertEntity({ ...promptObs, project });
|
|
1667
1676
|
const results = ltStore.search(hook.prompt, 3);
|
|
@@ -407,6 +407,11 @@ class SessionStore {
|
|
|
407
407
|
WHERE session_id = ? AND entity_type IN ('file_read','file_modified','file_created')
|
|
408
408
|
ORDER BY importance DESC, created_at DESC`).all(session_id).map((r) => r.entity_value);
|
|
409
409
|
}
|
|
410
|
+
hasModifiedFiles(session_id) {
|
|
411
|
+
const row = this.db.query(`SELECT COUNT(*) as c FROM entities
|
|
412
|
+
WHERE session_id = ? AND entity_type IN ('file_modified','file_created') LIMIT 1`).get(session_id);
|
|
413
|
+
return (row?.c ?? 0) > 0;
|
|
414
|
+
}
|
|
410
415
|
insertNote(note) {
|
|
411
416
|
this.db.run("INSERT INTO session_notes(session_id, content, created_at) VALUES (?, ?, ?)", [note.session_id, note.content, note.created_at]);
|
|
412
417
|
}
|
|
@@ -658,6 +663,9 @@ class SessionSummarizer {
|
|
|
658
663
|
const notes = this.sessionStore.getSessionNotes(session_id).map((n) => n.content);
|
|
659
664
|
if (files.length === 0 && errors.length === 0 && notes.length === 0)
|
|
660
665
|
return;
|
|
666
|
+
const hasModified = this.sessionStore.hasModifiedFiles(session_id);
|
|
667
|
+
if (!hasModified && errors.length === 0 && decisions.length === 0 && notes.length === 0 && observations.length === 0)
|
|
668
|
+
return;
|
|
661
669
|
const obsValues = observations.slice(0, 5).map((o) => o.entity_value);
|
|
662
670
|
let summaryText;
|
|
663
671
|
let tier = "rule-based";
|
|
@@ -1912,6 +1920,9 @@ function safeJson2(text, fallback) {
|
|
|
1912
1920
|
|
|
1913
1921
|
// src/capture/hook-handler.ts
|
|
1914
1922
|
import { basename as basename3 } from "path";
|
|
1923
|
+
function stripIdeTags(prompt) {
|
|
1924
|
+
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();
|
|
1925
|
+
}
|
|
1915
1926
|
async function handlePostToolUse(hook, project) {
|
|
1916
1927
|
const store = new SessionStore;
|
|
1917
1928
|
store.upsertSession({
|
|
@@ -1947,14 +1958,15 @@ async function handlePostToolUse(hook, project) {
|
|
|
1947
1958
|
async function handleUserPromptSubmit(hook, project) {
|
|
1948
1959
|
const store = new SessionStore;
|
|
1949
1960
|
const ltStore = new LongTermStore;
|
|
1961
|
+
const cleanPrompt = stripIdeTags(hook.prompt);
|
|
1950
1962
|
store.upsertSession({
|
|
1951
1963
|
id: hook.session_id,
|
|
1952
1964
|
project,
|
|
1953
1965
|
started_at: Date.now(),
|
|
1954
|
-
user_prompt: hook.prompt.slice(0, 500),
|
|
1966
|
+
user_prompt: cleanPrompt.slice(0, 500) || hook.prompt.slice(0, 500),
|
|
1955
1967
|
status: "active"
|
|
1956
1968
|
});
|
|
1957
|
-
const promptObs = extractObservationFromPrompt(hook.prompt, hook.session_id, project, 0);
|
|
1969
|
+
const promptObs = extractObservationFromPrompt(cleanPrompt || hook.prompt, hook.session_id, project, 0);
|
|
1958
1970
|
if (promptObs)
|
|
1959
1971
|
store.insertEntity({ ...promptObs, project });
|
|
1960
1972
|
const results = ltStore.search(hook.prompt, 3);
|
|
@@ -407,6 +407,11 @@ class SessionStore {
|
|
|
407
407
|
WHERE session_id = ? AND entity_type IN ('file_read','file_modified','file_created')
|
|
408
408
|
ORDER BY importance DESC, created_at DESC`).all(session_id).map((r) => r.entity_value);
|
|
409
409
|
}
|
|
410
|
+
hasModifiedFiles(session_id) {
|
|
411
|
+
const row = this.db.query(`SELECT COUNT(*) as c FROM entities
|
|
412
|
+
WHERE session_id = ? AND entity_type IN ('file_modified','file_created') LIMIT 1`).get(session_id);
|
|
413
|
+
return (row?.c ?? 0) > 0;
|
|
414
|
+
}
|
|
410
415
|
insertNote(note) {
|
|
411
416
|
this.db.run("INSERT INTO session_notes(session_id, content, created_at) VALUES (?, ?, ?)", [note.session_id, note.content, note.created_at]);
|
|
412
417
|
}
|
|
@@ -1619,6 +1624,9 @@ function safeJson2(text, fallback) {
|
|
|
1619
1624
|
|
|
1620
1625
|
// src/capture/hook-handler.ts
|
|
1621
1626
|
import { basename as basename3 } from "path";
|
|
1627
|
+
function stripIdeTags(prompt) {
|
|
1628
|
+
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();
|
|
1629
|
+
}
|
|
1622
1630
|
async function handlePostToolUse(hook, project) {
|
|
1623
1631
|
const store = new SessionStore;
|
|
1624
1632
|
store.upsertSession({
|
|
@@ -1654,14 +1662,15 @@ async function handlePostToolUse(hook, project) {
|
|
|
1654
1662
|
async function handleUserPromptSubmit(hook, project) {
|
|
1655
1663
|
const store = new SessionStore;
|
|
1656
1664
|
const ltStore = new LongTermStore;
|
|
1665
|
+
const cleanPrompt = stripIdeTags(hook.prompt);
|
|
1657
1666
|
store.upsertSession({
|
|
1658
1667
|
id: hook.session_id,
|
|
1659
1668
|
project,
|
|
1660
1669
|
started_at: Date.now(),
|
|
1661
|
-
user_prompt: hook.prompt.slice(0, 500),
|
|
1670
|
+
user_prompt: cleanPrompt.slice(0, 500) || hook.prompt.slice(0, 500),
|
|
1662
1671
|
status: "active"
|
|
1663
1672
|
});
|
|
1664
|
-
const promptObs = extractObservationFromPrompt(hook.prompt, hook.session_id, project, 0);
|
|
1673
|
+
const promptObs = extractObservationFromPrompt(cleanPrompt || hook.prompt, hook.session_id, project, 0);
|
|
1665
1674
|
if (promptObs)
|
|
1666
1675
|
store.insertEntity({ ...promptObs, project });
|
|
1667
1676
|
const results = ltStore.search(hook.prompt, 3);
|
|
@@ -1936,6 +1945,9 @@ class SessionSummarizer {
|
|
|
1936
1945
|
const notes = this.sessionStore.getSessionNotes(session_id).map((n) => n.content);
|
|
1937
1946
|
if (files.length === 0 && errors.length === 0 && notes.length === 0)
|
|
1938
1947
|
return;
|
|
1948
|
+
const hasModified = this.sessionStore.hasModifiedFiles(session_id);
|
|
1949
|
+
if (!hasModified && errors.length === 0 && decisions.length === 0 && notes.length === 0 && observations.length === 0)
|
|
1950
|
+
return;
|
|
1939
1951
|
const obsValues = observations.slice(0, 5).map((o) => o.entity_value);
|
|
1940
1952
|
let summaryText;
|
|
1941
1953
|
let tier = "rule-based";
|
|
@@ -407,6 +407,11 @@ class SessionStore {
|
|
|
407
407
|
WHERE session_id = ? AND entity_type IN ('file_read','file_modified','file_created')
|
|
408
408
|
ORDER BY importance DESC, created_at DESC`).all(session_id).map((r) => r.entity_value);
|
|
409
409
|
}
|
|
410
|
+
hasModifiedFiles(session_id) {
|
|
411
|
+
const row = this.db.query(`SELECT COUNT(*) as c FROM entities
|
|
412
|
+
WHERE session_id = ? AND entity_type IN ('file_modified','file_created') LIMIT 1`).get(session_id);
|
|
413
|
+
return (row?.c ?? 0) > 0;
|
|
414
|
+
}
|
|
410
415
|
insertNote(note) {
|
|
411
416
|
this.db.run("INSERT INTO session_notes(session_id, content, created_at) VALUES (?, ?, ?)", [note.session_id, note.content, note.created_at]);
|
|
412
417
|
}
|
|
@@ -1619,6 +1624,9 @@ function safeJson2(text, fallback) {
|
|
|
1619
1624
|
|
|
1620
1625
|
// src/capture/hook-handler.ts
|
|
1621
1626
|
import { basename as basename3 } from "path";
|
|
1627
|
+
function stripIdeTags(prompt) {
|
|
1628
|
+
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();
|
|
1629
|
+
}
|
|
1622
1630
|
async function handlePostToolUse(hook, project) {
|
|
1623
1631
|
const store = new SessionStore;
|
|
1624
1632
|
store.upsertSession({
|
|
@@ -1654,14 +1662,15 @@ async function handlePostToolUse(hook, project) {
|
|
|
1654
1662
|
async function handleUserPromptSubmit(hook, project) {
|
|
1655
1663
|
const store = new SessionStore;
|
|
1656
1664
|
const ltStore = new LongTermStore;
|
|
1665
|
+
const cleanPrompt = stripIdeTags(hook.prompt);
|
|
1657
1666
|
store.upsertSession({
|
|
1658
1667
|
id: hook.session_id,
|
|
1659
1668
|
project,
|
|
1660
1669
|
started_at: Date.now(),
|
|
1661
|
-
user_prompt: hook.prompt.slice(0, 500),
|
|
1670
|
+
user_prompt: cleanPrompt.slice(0, 500) || hook.prompt.slice(0, 500),
|
|
1662
1671
|
status: "active"
|
|
1663
1672
|
});
|
|
1664
|
-
const promptObs = extractObservationFromPrompt(hook.prompt, hook.session_id, project, 0);
|
|
1673
|
+
const promptObs = extractObservationFromPrompt(cleanPrompt || hook.prompt, hook.session_id, project, 0);
|
|
1665
1674
|
if (promptObs)
|
|
1666
1675
|
store.insertEntity({ ...promptObs, project });
|
|
1667
1676
|
const results = ltStore.search(hook.prompt, 3);
|
package/dist/index.js
CHANGED
|
@@ -14171,6 +14171,11 @@ class SessionStore {
|
|
|
14171
14171
|
WHERE session_id = ? AND entity_type IN ('file_read','file_modified','file_created')
|
|
14172
14172
|
ORDER BY importance DESC, created_at DESC`).all(session_id).map((r) => r.entity_value);
|
|
14173
14173
|
}
|
|
14174
|
+
hasModifiedFiles(session_id) {
|
|
14175
|
+
const row = this.db.query(`SELECT COUNT(*) as c FROM entities
|
|
14176
|
+
WHERE session_id = ? AND entity_type IN ('file_modified','file_created') LIMIT 1`).get(session_id);
|
|
14177
|
+
return (row?.c ?? 0) > 0;
|
|
14178
|
+
}
|
|
14174
14179
|
insertNote(note) {
|
|
14175
14180
|
this.db.run("INSERT INTO session_notes(session_id, content, created_at) VALUES (?, ?, ?)", [note.session_id, note.content, note.created_at]);
|
|
14176
14181
|
}
|
package/package.json
CHANGED