claude-memory-hub 0.9.4 → 0.9.6
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 +48 -0
- package/dist/cli.js +46 -12
- package/dist/hooks/post-compact.js +73 -29
- package/dist/hooks/post-tool-use.js +27 -3
- package/dist/hooks/pre-compact.js +73 -29
- package/dist/hooks/session-end.js +68 -28
- package/dist/hooks/user-prompt-submit.js +27 -3
- package/dist/index.js +5 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,54 @@ Format follows [Keep a Changelog](https://keepachangelog.com/).
|
|
|
5
5
|
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
+
## [0.9.6] - 2026-04-03
|
|
9
|
+
|
|
10
|
+
Richer session capture — Agent results, higher limits, cleaner summaries.
|
|
11
|
+
|
|
12
|
+
### Enhancements
|
|
13
|
+
|
|
14
|
+
- **Agent/Skill result capture** — `tool_response` from Agent and Skill tools is now saved into entity `context` field (up to 800 chars). Previously only the prompt was captured, losing all agent output. This is the biggest data quality improvement — multi-agent workflows now produce meaningful summaries
|
|
15
|
+
- **Higher summary limits** — `user_prompt` 200→500 chars, `decisions` 3→5 entries with context, `errors` 2→5 entries, `notes` 2→5 entries. CLI summarizer bumped to 6K prompt / 2K output (was 4K/1K). Decision entities now include agent/skill results in summary text
|
|
16
|
+
- **IDE/system tag stripping in summarizer** — `<ide_opened_file>`, `<ide_selection>`, `<system-reminder>`, `<local-command-*>`, `<command-*>` tags are now stripped at both rule-based and CLI summarizer stages. Prevents tag noise from polluting L3 summaries
|
|
17
|
+
- **PostCompact summary cap** — compact summaries exceeding 5,000 chars are truncated (was unbounded — seen 22K in production). Reduces DB bloat and improves search relevance
|
|
18
|
+
- **Broader observation heuristics** — added patterns for: refactoring, dependency changes, test results, deployments, scaffolding, data risks, user task/feature requests. Captures more meaningful observations from tool output and user prompts
|
|
19
|
+
|
|
20
|
+
### Before vs After
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
Before: "Task: <ide_opened_file>...</ide_opened_file>. Files (49): ..." (1165 chars, noisy)
|
|
24
|
+
After: "Task: fix broken hooks in memory-hub. Files (15): ... Decisions: agent:debugger: investigated... → Found temp path issue..." (richer, clean)
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## [0.9.5] - 2026-04-03
|
|
30
|
+
|
|
31
|
+
Stable install path — hooks no longer break after reboot or bunx cache cleanup.
|
|
32
|
+
|
|
33
|
+
### Bug Fixes
|
|
34
|
+
|
|
35
|
+
- **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
|
|
36
|
+
- **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
|
|
37
|
+
- **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
|
|
38
|
+
- **`install.sh` updated** — shell-based installer uses the same stable path strategy with full bun binary resolution
|
|
39
|
+
|
|
40
|
+
### How It Works
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
bunx claude-memory-hub install
|
|
44
|
+
1. Downloads package to temp dir (bunx behavior)
|
|
45
|
+
2. Copies dist/*.js + dist/hooks/*.js → ~/.claude-memory-hub/dist/ ← NEW
|
|
46
|
+
3. Registers hooks pointing to ~/.claude-memory-hub/dist/hooks/ ← STABLE
|
|
47
|
+
4. Registers MCP server pointing to ~/.claude-memory-hub/dist/index.js
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Upgrade Note
|
|
51
|
+
|
|
52
|
+
Run `bunx claude-memory-hub@latest install` to fix broken hooks. No data loss — only hook paths are updated.
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
8
56
|
## [0.9.4] - 2026-04-02
|
|
9
57
|
|
|
10
58
|
Windows path fix — backslashes no longer eaten by bash.
|
package/dist/cli.js
CHANGED
|
@@ -1669,7 +1669,7 @@ var init_importer = __esm(() => {
|
|
|
1669
1669
|
});
|
|
1670
1670
|
|
|
1671
1671
|
// src/cli/main.ts
|
|
1672
|
-
import { existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync, writeFileSync } from "fs";
|
|
1672
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync, writeFileSync, readdirSync } from "fs";
|
|
1673
1673
|
import { homedir as homedir5 } from "os";
|
|
1674
1674
|
import { join as join5, resolve, dirname } from "path";
|
|
1675
1675
|
|
|
@@ -1967,6 +1967,7 @@ import { spawnSync } from "child_process";
|
|
|
1967
1967
|
var CLAUDE_DIR = join5(homedir5(), ".claude");
|
|
1968
1968
|
var SETTINGS_PATH = join5(CLAUDE_DIR, "settings.json");
|
|
1969
1969
|
var PKG_DIR = resolve(dirname(import.meta.dir));
|
|
1970
|
+
var STABLE_DIR = join5(homedir5(), ".claude-memory-hub");
|
|
1970
1971
|
function shellPath(p) {
|
|
1971
1972
|
const normalized = p.replace(/\\/g, "/");
|
|
1972
1973
|
return normalized.includes(" ") ? `"${normalized}"` : normalized;
|
|
@@ -1988,11 +1989,37 @@ function getBunPath() {
|
|
|
1988
1989
|
}
|
|
1989
1990
|
return "bun";
|
|
1990
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
|
+
}
|
|
1991
2018
|
function getHookPath(hookName) {
|
|
1992
|
-
return shellPath(join5(
|
|
2019
|
+
return shellPath(join5(STABLE_DIR, "dist", "hooks", `${hookName}.js`));
|
|
1993
2020
|
}
|
|
1994
2021
|
function getMcpServerPath() {
|
|
1995
|
-
return shellPath(join5(
|
|
2022
|
+
return shellPath(join5(STABLE_DIR, "dist", "index.js"));
|
|
1996
2023
|
}
|
|
1997
2024
|
function loadSettings() {
|
|
1998
2025
|
if (!existsSync5(SETTINGS_PATH))
|
|
@@ -2012,7 +2039,16 @@ function saveSettings(settings) {
|
|
|
2012
2039
|
function install() {
|
|
2013
2040
|
console.log(`claude-memory-hub \u2014 install
|
|
2014
2041
|
`);
|
|
2015
|
-
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...`);
|
|
2016
2052
|
const mcpPath = getMcpServerPath();
|
|
2017
2053
|
const bunBin = getBunPath();
|
|
2018
2054
|
const result = spawnSync("claude", ["mcp", "add", "claude-memory-hub", "-s", "user", "--", bunBin, "run", mcpPath], {
|
|
@@ -2044,14 +2080,12 @@ function install() {
|
|
|
2044
2080
|
for (const [event, scriptPath] of hookEntries) {
|
|
2045
2081
|
const hooks = settings.hooks;
|
|
2046
2082
|
hooks[event] ??= [];
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
registered++;
|
|
2054
|
-
}
|
|
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++;
|
|
2055
2089
|
}
|
|
2056
2090
|
saveSettings(settings);
|
|
2057
2091
|
console.log(` ${registered} hook(s) registered. (${5 - registered} already existed)`);
|
|
@@ -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,
|
|
@@ -495,37 +500,46 @@ function sanitizeFtsQuery(query) {
|
|
|
495
500
|
}
|
|
496
501
|
|
|
497
502
|
// src/summarizer/summarizer-prompts.ts
|
|
503
|
+
function stripNoiseTags(text) {
|
|
504
|
+
return text.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, "").replace(/<local-command-caveat>[\s\S]*?<\/local-command-caveat>\s*/g, "").replace(/<command-name>[\s\S]*?<\/command-name>\s*/g, "").replace(/<command-message>[\s\S]*?<\/command-message>\s*/g, "").replace(/<command-args>[\s\S]*?<\/command-args>\s*/g, "").replace(/<local-command-stdout>[\s\S]*?<\/local-command-stdout>\s*/g, "").trim();
|
|
505
|
+
}
|
|
498
506
|
function buildRuleBasedSummary(session, files, errors, decisions, notes = []) {
|
|
499
507
|
const parts = [];
|
|
500
508
|
if (session.user_prompt) {
|
|
501
|
-
|
|
509
|
+
const cleanPrompt = stripNoiseTags(session.user_prompt);
|
|
510
|
+
if (cleanPrompt) {
|
|
511
|
+
parts.push(`Task: ${cleanPrompt.slice(0, 500)}.`);
|
|
512
|
+
}
|
|
502
513
|
}
|
|
503
514
|
if (files.length > 0) {
|
|
504
|
-
const listed = files.slice(0,
|
|
505
|
-
parts.push(`Files (${files.length}): ${listed}${files.length >
|
|
515
|
+
const listed = files.slice(0, 15).join(", ");
|
|
516
|
+
parts.push(`Files (${files.length}): ${listed}${files.length > 15 ? ` (+${files.length - 15} more)` : ""}.`);
|
|
506
517
|
}
|
|
507
518
|
if (decisions.length > 0) {
|
|
508
|
-
const listed = decisions.slice(0,
|
|
519
|
+
const listed = decisions.slice(0, 5).map((d) => {
|
|
520
|
+
const base = d.entity_value.slice(0, 150);
|
|
521
|
+
const ctx = d.context ? ` \u2192 ${d.context.slice(0, 200)}` : "";
|
|
522
|
+
return base + ctx;
|
|
523
|
+
}).join("; ");
|
|
509
524
|
parts.push(`Decisions: ${listed}.`);
|
|
510
525
|
}
|
|
511
526
|
if (errors.length > 0) {
|
|
512
|
-
const
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
}
|
|
527
|
+
const errorLines = errors.slice(0, 5).map((e) => {
|
|
528
|
+
const ctx = e.context ? ` (${e.context.slice(0, 100)})` : "";
|
|
529
|
+
return `${e.entity_value.slice(0, 150)}${ctx}`;
|
|
530
|
+
});
|
|
531
|
+
parts.push(`Errors (${errors.length}): ${errorLines.join("; ")}.`);
|
|
518
532
|
}
|
|
519
533
|
if (notes.length > 0) {
|
|
520
|
-
parts.push(`Notes: ${notes.slice(-
|
|
534
|
+
parts.push(`Notes: ${notes.slice(-5).join("; ").slice(0, 500)}.`);
|
|
521
535
|
}
|
|
522
536
|
return parts.join(" ") || `Session in project ${session.project}.`;
|
|
523
537
|
}
|
|
524
538
|
|
|
525
539
|
// src/summarizer/cli-summarizer.ts
|
|
526
540
|
var log2 = createLogger("cli-summarizer");
|
|
527
|
-
var MAX_PROMPT_CHARS =
|
|
528
|
-
var MAX_OUTPUT_CHARS =
|
|
541
|
+
var MAX_PROMPT_CHARS = 6000;
|
|
542
|
+
var MAX_OUTPUT_CHARS = 2000;
|
|
529
543
|
var DEFAULT_TIMEOUT_MS = 30000;
|
|
530
544
|
var _cliAvailable;
|
|
531
545
|
var _cliCheckedAt = 0;
|
|
@@ -545,27 +559,30 @@ function isClaudeCliAvailable() {
|
|
|
545
559
|
_cliCheckedAt = Date.now();
|
|
546
560
|
return _cliAvailable;
|
|
547
561
|
}
|
|
562
|
+
function stripNoise(text) {
|
|
563
|
+
return text.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, "").replace(/<local-command-[\w-]+>[\s\S]*?<\/local-command-[\w-]+>\s*/g, "").replace(/<command-[\w-]+>[\s\S]*?<\/command-[\w-]+>\s*/g, "").trim();
|
|
564
|
+
}
|
|
548
565
|
function buildCliPrompt(ctx) {
|
|
549
566
|
const sections = [
|
|
550
|
-
"Summarize this coding session in
|
|
551
|
-
"Focus on: what was accomplished, key decisions, important findings.",
|
|
567
|
+
"Summarize this coding session in 3-5 plain sentences. No markdown, no headers, no code blocks.",
|
|
568
|
+
"Focus on: what was accomplished, key decisions, important findings, tools/agents used.",
|
|
552
569
|
"",
|
|
553
570
|
`Project: ${ctx.project}`
|
|
554
571
|
];
|
|
555
572
|
if (ctx.files.length > 0) {
|
|
556
|
-
sections.push(`Files modified: ${ctx.files.slice(0,
|
|
573
|
+
sections.push(`Files modified: ${ctx.files.slice(0, 15).join(", ")}`);
|
|
557
574
|
}
|
|
558
575
|
if (ctx.errors.length > 0) {
|
|
559
|
-
sections.push(`Errors resolved: ${ctx.errors.slice(0, 5).join("; ")}`);
|
|
576
|
+
sections.push(`Errors resolved: ${ctx.errors.slice(0, 5).map(stripNoise).join("; ")}`);
|
|
560
577
|
}
|
|
561
578
|
if (ctx.decisions.length > 0) {
|
|
562
|
-
sections.push(`Decisions: ${ctx.decisions.slice(0,
|
|
579
|
+
sections.push(`Decisions: ${ctx.decisions.slice(0, 8).map(stripNoise).join("; ")}`);
|
|
563
580
|
}
|
|
564
581
|
if (ctx.notes.length > 0) {
|
|
565
|
-
sections.push(`Notes: ${ctx.notes.slice(0,
|
|
582
|
+
sections.push(`Notes: ${ctx.notes.slice(0, 5).map(stripNoise).join("; ")}`);
|
|
566
583
|
}
|
|
567
584
|
if (ctx.observations.length > 0) {
|
|
568
|
-
sections.push(`Key observations: ${ctx.observations.slice(0,
|
|
585
|
+
sections.push(`Key observations: ${ctx.observations.slice(0, 5).map(stripNoise).join("; ")}`);
|
|
569
586
|
}
|
|
570
587
|
let prompt = sections.join(`
|
|
571
588
|
`);
|
|
@@ -666,19 +683,23 @@ class SessionSummarizer {
|
|
|
666
683
|
const hasModified = this.sessionStore.hasModifiedFiles(session_id);
|
|
667
684
|
if (!hasModified && errors.length === 0 && decisions.length === 0 && notes.length === 0 && observations.length === 0)
|
|
668
685
|
return;
|
|
669
|
-
const obsValues = observations.slice(0,
|
|
686
|
+
const obsValues = observations.slice(0, 8).map((o) => o.entity_value);
|
|
670
687
|
let summaryText;
|
|
671
688
|
let tier = "rule-based";
|
|
672
689
|
const llmMode = process.env["CLAUDE_MEMORY_HUB_LLM"] ?? "auto";
|
|
673
690
|
if (llmMode !== "rule-based") {
|
|
691
|
+
const decisionDetails = decisions.slice(0, 8).map((d) => {
|
|
692
|
+
const ctx2 = d.context ? ` \u2192 ${d.context.slice(0, 200)}` : "";
|
|
693
|
+
return d.entity_value.slice(0, 150) + ctx2;
|
|
694
|
+
});
|
|
674
695
|
const ctx = {
|
|
675
696
|
sessionId: session_id,
|
|
676
697
|
project,
|
|
677
698
|
files,
|
|
678
|
-
errors: errors.slice(0, 5).map((e) => e.entity_value.slice(0,
|
|
679
|
-
decisions:
|
|
680
|
-
notes: notes.slice(0,
|
|
681
|
-
observations: obsValues.slice(0,
|
|
699
|
+
errors: errors.slice(0, 5).map((e) => e.entity_value.slice(0, 150)),
|
|
700
|
+
decisions: decisionDetails,
|
|
701
|
+
notes: notes.slice(0, 5),
|
|
702
|
+
observations: obsValues.slice(0, 5)
|
|
682
703
|
};
|
|
683
704
|
summaryText = await tryCliSummary(ctx);
|
|
684
705
|
if (summaryText)
|
|
@@ -769,10 +790,14 @@ async function handlePostCompact(hook, project) {
|
|
|
769
790
|
const files = store.getSessionFiles(hook.session_id);
|
|
770
791
|
const decisions = store.getSessionDecisions(hook.session_id);
|
|
771
792
|
const errors = store.getSessionErrors(hook.session_id);
|
|
793
|
+
const MAX_COMPACT_SUMMARY = 5000;
|
|
794
|
+
const summary = hook.compact_summary.length > MAX_COMPACT_SUMMARY ? hook.compact_summary.slice(0, MAX_COMPACT_SUMMARY - 20) + `
|
|
795
|
+
|
|
796
|
+
[truncated]` : hook.compact_summary;
|
|
772
797
|
ltStore.upsertSummary({
|
|
773
798
|
session_id: hook.session_id,
|
|
774
799
|
project,
|
|
775
|
-
summary
|
|
800
|
+
summary,
|
|
776
801
|
files_touched: JSON.stringify(files.slice(0, 50)),
|
|
777
802
|
decisions: JSON.stringify(decisions.slice(0, 20).map((d) => d.entity_value)),
|
|
778
803
|
errors_fixed: JSON.stringify(errors.slice(0, 10).map((e) => e.entity_value.slice(0, 100))),
|
|
@@ -893,18 +918,26 @@ function extractCodePatterns(content) {
|
|
|
893
918
|
var TOOL_OUTPUT_HEURISTICS = [
|
|
894
919
|
{ pattern: /\b(IMPORTANT|CRITICAL|WARNING|BREAKING)\b/i, importance: 4, label: "important" },
|
|
895
920
|
{ pattern: /\b(DEPRECATED|SECURITY|VULNERABILITY)\b/i, importance: 4, label: "security" },
|
|
921
|
+
{ pattern: /\b(migration failed|data loss|corrupt)/i, importance: 4, label: "data-risk" },
|
|
896
922
|
{ pattern: /\b(decision:|decided to|NOTE:|conclusion:)/i, importance: 3, label: "decision-note" },
|
|
897
923
|
{ pattern: /\b(discovered|found that|learned|realized|root cause)\b/i, importance: 3, label: "discovery" },
|
|
898
924
|
{ pattern: /\b(workaround:|alternative:|instead of|switched to)/i, importance: 3, label: "approach-change" },
|
|
925
|
+
{ pattern: /\b(refactored?|migrated?|upgraded?|replaced)\b/i, importance: 3, label: "refactor" },
|
|
926
|
+
{ pattern: /\b(installed|added dependency|npm install|bun add)\b/i, importance: 2, label: "dependency" },
|
|
899
927
|
{ pattern: /\b(TODO:|FIXME:|HACK:|WORKAROUND:)/i, importance: 2, label: "todo-note" },
|
|
900
928
|
{ pattern: /\b(performance:|bottleneck|slow|timeout|OOM)/i, importance: 2, label: "performance" },
|
|
929
|
+
{ pattern: /\b(created|scaffolded|initialized|bootstrapped)\b/i, importance: 2, label: "creation" },
|
|
930
|
+
{ pattern: /\b(tests? (?:pass|fail)|coverage|assertion)/i, importance: 2, label: "test-result" },
|
|
931
|
+
{ pattern: /\b(deployed|published|released|pushed to)\b/i, importance: 2, label: "deployment" },
|
|
901
932
|
{ pattern: /^>\s+.{10,}/m, importance: 2, label: "quoted" }
|
|
902
933
|
];
|
|
903
934
|
var PROMPT_HEURISTICS = [
|
|
904
935
|
{ pattern: /\b(IMPORTANT|CRITICAL|MUST)\b/i, importance: 4, label: "user-important" },
|
|
905
936
|
{ pattern: /\b(remember that|note that|I decided|we should|keep in mind)\b/i, importance: 3, label: "user-note" },
|
|
906
937
|
{ pattern: /\b(don't|do not|never|avoid|stop)\b/i, importance: 3, label: "user-constraint" },
|
|
907
|
-
{ pattern: /\b(
|
|
938
|
+
{ pattern: /\b(fix|debug|investigate|analyze|resolve)\b/i, importance: 2, label: "user-task" },
|
|
939
|
+
{ pattern: /\b(prefer|always use|convention is|pattern is)\b/i, importance: 2, label: "user-preference" },
|
|
940
|
+
{ pattern: /\b(implement|build|create|add feature|integrate)\b/i, importance: 2, label: "user-feature" }
|
|
908
941
|
];
|
|
909
942
|
var MAX_VALUE_LENGTH = 500;
|
|
910
943
|
var MIN_INPUT_LENGTH = 20;
|
|
@@ -1013,13 +1046,15 @@ function extractEntities(hook, promptNumber = 0) {
|
|
|
1013
1046
|
case "Agent": {
|
|
1014
1047
|
const subagentType = stringField2(tool_input, "subagent_type") ?? "general-purpose";
|
|
1015
1048
|
const prompt = stringField2(tool_input, "prompt") ?? "";
|
|
1016
|
-
|
|
1049
|
+
const agentResult = extractAgentResult(tool_response);
|
|
1050
|
+
raw.push(makeEntity(session_id, project, tool_name, "decision", `agent:${subagentType}: ${prompt.slice(0, 200)}`, 3, now, promptNumber, agentResult || undefined));
|
|
1017
1051
|
break;
|
|
1018
1052
|
}
|
|
1019
1053
|
case "Skill": {
|
|
1020
1054
|
const skillName = stringField2(tool_input, "skill") ?? "unknown";
|
|
1021
1055
|
const args = stringField2(tool_input, "args") ?? "";
|
|
1022
|
-
|
|
1056
|
+
const skillResult = extractAgentResult(tool_response);
|
|
1057
|
+
raw.push(makeEntity(session_id, project, tool_name, "decision", `skill:${skillName} ${args.slice(0, 120)}`.trim(), 2, now, promptNumber, skillResult || undefined));
|
|
1023
1058
|
break;
|
|
1024
1059
|
}
|
|
1025
1060
|
default:
|
|
@@ -1050,6 +1085,15 @@ function stringField2(obj, key) {
|
|
|
1050
1085
|
function deriveProject(hook) {
|
|
1051
1086
|
return "unknown";
|
|
1052
1087
|
}
|
|
1088
|
+
function extractAgentResult(response) {
|
|
1089
|
+
if (!response)
|
|
1090
|
+
return;
|
|
1091
|
+
const r = response;
|
|
1092
|
+
const text = typeof r === "string" ? r : stringField2(r, "result") ?? stringField2(r, "output") ?? stringField2(r, "content") ?? stringField2(r, "text");
|
|
1093
|
+
if (!text)
|
|
1094
|
+
return;
|
|
1095
|
+
return text.length > 800 ? text.slice(0, 797) + "..." : text;
|
|
1096
|
+
}
|
|
1053
1097
|
function extractFileFromBashCmd(cmd) {
|
|
1054
1098
|
const patterns = [
|
|
1055
1099
|
/(?:cp|mv)\s+\S+\s+(\S+\.[\w]+)/,
|
|
@@ -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,
|
|
@@ -597,18 +602,26 @@ function extractCodePatterns(content) {
|
|
|
597
602
|
var TOOL_OUTPUT_HEURISTICS = [
|
|
598
603
|
{ pattern: /\b(IMPORTANT|CRITICAL|WARNING|BREAKING)\b/i, importance: 4, label: "important" },
|
|
599
604
|
{ pattern: /\b(DEPRECATED|SECURITY|VULNERABILITY)\b/i, importance: 4, label: "security" },
|
|
605
|
+
{ pattern: /\b(migration failed|data loss|corrupt)/i, importance: 4, label: "data-risk" },
|
|
600
606
|
{ pattern: /\b(decision:|decided to|NOTE:|conclusion:)/i, importance: 3, label: "decision-note" },
|
|
601
607
|
{ pattern: /\b(discovered|found that|learned|realized|root cause)\b/i, importance: 3, label: "discovery" },
|
|
602
608
|
{ pattern: /\b(workaround:|alternative:|instead of|switched to)/i, importance: 3, label: "approach-change" },
|
|
609
|
+
{ pattern: /\b(refactored?|migrated?|upgraded?|replaced)\b/i, importance: 3, label: "refactor" },
|
|
610
|
+
{ pattern: /\b(installed|added dependency|npm install|bun add)\b/i, importance: 2, label: "dependency" },
|
|
603
611
|
{ pattern: /\b(TODO:|FIXME:|HACK:|WORKAROUND:)/i, importance: 2, label: "todo-note" },
|
|
604
612
|
{ pattern: /\b(performance:|bottleneck|slow|timeout|OOM)/i, importance: 2, label: "performance" },
|
|
613
|
+
{ pattern: /\b(created|scaffolded|initialized|bootstrapped)\b/i, importance: 2, label: "creation" },
|
|
614
|
+
{ pattern: /\b(tests? (?:pass|fail)|coverage|assertion)/i, importance: 2, label: "test-result" },
|
|
615
|
+
{ pattern: /\b(deployed|published|released|pushed to)\b/i, importance: 2, label: "deployment" },
|
|
605
616
|
{ pattern: /^>\s+.{10,}/m, importance: 2, label: "quoted" }
|
|
606
617
|
];
|
|
607
618
|
var PROMPT_HEURISTICS = [
|
|
608
619
|
{ pattern: /\b(IMPORTANT|CRITICAL|MUST)\b/i, importance: 4, label: "user-important" },
|
|
609
620
|
{ pattern: /\b(remember that|note that|I decided|we should|keep in mind)\b/i, importance: 3, label: "user-note" },
|
|
610
621
|
{ pattern: /\b(don't|do not|never|avoid|stop)\b/i, importance: 3, label: "user-constraint" },
|
|
611
|
-
{ pattern: /\b(
|
|
622
|
+
{ pattern: /\b(fix|debug|investigate|analyze|resolve)\b/i, importance: 2, label: "user-task" },
|
|
623
|
+
{ pattern: /\b(prefer|always use|convention is|pattern is)\b/i, importance: 2, label: "user-preference" },
|
|
624
|
+
{ pattern: /\b(implement|build|create|add feature|integrate)\b/i, importance: 2, label: "user-feature" }
|
|
612
625
|
];
|
|
613
626
|
var MAX_VALUE_LENGTH = 500;
|
|
614
627
|
var MIN_INPUT_LENGTH = 20;
|
|
@@ -717,13 +730,15 @@ function extractEntities(hook, promptNumber = 0) {
|
|
|
717
730
|
case "Agent": {
|
|
718
731
|
const subagentType = stringField2(tool_input, "subagent_type") ?? "general-purpose";
|
|
719
732
|
const prompt = stringField2(tool_input, "prompt") ?? "";
|
|
720
|
-
|
|
733
|
+
const agentResult = extractAgentResult(tool_response);
|
|
734
|
+
raw.push(makeEntity(session_id, project, tool_name, "decision", `agent:${subagentType}: ${prompt.slice(0, 200)}`, 3, now, promptNumber, agentResult || undefined));
|
|
721
735
|
break;
|
|
722
736
|
}
|
|
723
737
|
case "Skill": {
|
|
724
738
|
const skillName = stringField2(tool_input, "skill") ?? "unknown";
|
|
725
739
|
const args = stringField2(tool_input, "args") ?? "";
|
|
726
|
-
|
|
740
|
+
const skillResult = extractAgentResult(tool_response);
|
|
741
|
+
raw.push(makeEntity(session_id, project, tool_name, "decision", `skill:${skillName} ${args.slice(0, 120)}`.trim(), 2, now, promptNumber, skillResult || undefined));
|
|
727
742
|
break;
|
|
728
743
|
}
|
|
729
744
|
default:
|
|
@@ -754,6 +769,15 @@ function stringField2(obj, key) {
|
|
|
754
769
|
function deriveProject(hook) {
|
|
755
770
|
return "unknown";
|
|
756
771
|
}
|
|
772
|
+
function extractAgentResult(response) {
|
|
773
|
+
if (!response)
|
|
774
|
+
return;
|
|
775
|
+
const r = response;
|
|
776
|
+
const text = typeof r === "string" ? r : stringField2(r, "result") ?? stringField2(r, "output") ?? stringField2(r, "content") ?? stringField2(r, "text");
|
|
777
|
+
if (!text)
|
|
778
|
+
return;
|
|
779
|
+
return text.length > 800 ? text.slice(0, 797) + "..." : text;
|
|
780
|
+
}
|
|
757
781
|
function extractFileFromBashCmd(cmd) {
|
|
758
782
|
const patterns = [
|
|
759
783
|
/(?:cp|mv)\s+\S+\s+(\S+\.[\w]+)/,
|
|
@@ -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,
|
|
@@ -495,37 +500,46 @@ function sanitizeFtsQuery(query) {
|
|
|
495
500
|
}
|
|
496
501
|
|
|
497
502
|
// src/summarizer/summarizer-prompts.ts
|
|
503
|
+
function stripNoiseTags(text) {
|
|
504
|
+
return text.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, "").replace(/<local-command-caveat>[\s\S]*?<\/local-command-caveat>\s*/g, "").replace(/<command-name>[\s\S]*?<\/command-name>\s*/g, "").replace(/<command-message>[\s\S]*?<\/command-message>\s*/g, "").replace(/<command-args>[\s\S]*?<\/command-args>\s*/g, "").replace(/<local-command-stdout>[\s\S]*?<\/local-command-stdout>\s*/g, "").trim();
|
|
505
|
+
}
|
|
498
506
|
function buildRuleBasedSummary(session, files, errors, decisions, notes = []) {
|
|
499
507
|
const parts = [];
|
|
500
508
|
if (session.user_prompt) {
|
|
501
|
-
|
|
509
|
+
const cleanPrompt = stripNoiseTags(session.user_prompt);
|
|
510
|
+
if (cleanPrompt) {
|
|
511
|
+
parts.push(`Task: ${cleanPrompt.slice(0, 500)}.`);
|
|
512
|
+
}
|
|
502
513
|
}
|
|
503
514
|
if (files.length > 0) {
|
|
504
|
-
const listed = files.slice(0,
|
|
505
|
-
parts.push(`Files (${files.length}): ${listed}${files.length >
|
|
515
|
+
const listed = files.slice(0, 15).join(", ");
|
|
516
|
+
parts.push(`Files (${files.length}): ${listed}${files.length > 15 ? ` (+${files.length - 15} more)` : ""}.`);
|
|
506
517
|
}
|
|
507
518
|
if (decisions.length > 0) {
|
|
508
|
-
const listed = decisions.slice(0,
|
|
519
|
+
const listed = decisions.slice(0, 5).map((d) => {
|
|
520
|
+
const base = d.entity_value.slice(0, 150);
|
|
521
|
+
const ctx = d.context ? ` \u2192 ${d.context.slice(0, 200)}` : "";
|
|
522
|
+
return base + ctx;
|
|
523
|
+
}).join("; ");
|
|
509
524
|
parts.push(`Decisions: ${listed}.`);
|
|
510
525
|
}
|
|
511
526
|
if (errors.length > 0) {
|
|
512
|
-
const
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
}
|
|
527
|
+
const errorLines = errors.slice(0, 5).map((e) => {
|
|
528
|
+
const ctx = e.context ? ` (${e.context.slice(0, 100)})` : "";
|
|
529
|
+
return `${e.entity_value.slice(0, 150)}${ctx}`;
|
|
530
|
+
});
|
|
531
|
+
parts.push(`Errors (${errors.length}): ${errorLines.join("; ")}.`);
|
|
518
532
|
}
|
|
519
533
|
if (notes.length > 0) {
|
|
520
|
-
parts.push(`Notes: ${notes.slice(-
|
|
534
|
+
parts.push(`Notes: ${notes.slice(-5).join("; ").slice(0, 500)}.`);
|
|
521
535
|
}
|
|
522
536
|
return parts.join(" ") || `Session in project ${session.project}.`;
|
|
523
537
|
}
|
|
524
538
|
|
|
525
539
|
// src/summarizer/cli-summarizer.ts
|
|
526
540
|
var log2 = createLogger("cli-summarizer");
|
|
527
|
-
var MAX_PROMPT_CHARS =
|
|
528
|
-
var MAX_OUTPUT_CHARS =
|
|
541
|
+
var MAX_PROMPT_CHARS = 6000;
|
|
542
|
+
var MAX_OUTPUT_CHARS = 2000;
|
|
529
543
|
var DEFAULT_TIMEOUT_MS = 30000;
|
|
530
544
|
var _cliAvailable;
|
|
531
545
|
var _cliCheckedAt = 0;
|
|
@@ -545,27 +559,30 @@ function isClaudeCliAvailable() {
|
|
|
545
559
|
_cliCheckedAt = Date.now();
|
|
546
560
|
return _cliAvailable;
|
|
547
561
|
}
|
|
562
|
+
function stripNoise(text) {
|
|
563
|
+
return text.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, "").replace(/<local-command-[\w-]+>[\s\S]*?<\/local-command-[\w-]+>\s*/g, "").replace(/<command-[\w-]+>[\s\S]*?<\/command-[\w-]+>\s*/g, "").trim();
|
|
564
|
+
}
|
|
548
565
|
function buildCliPrompt(ctx) {
|
|
549
566
|
const sections = [
|
|
550
|
-
"Summarize this coding session in
|
|
551
|
-
"Focus on: what was accomplished, key decisions, important findings.",
|
|
567
|
+
"Summarize this coding session in 3-5 plain sentences. No markdown, no headers, no code blocks.",
|
|
568
|
+
"Focus on: what was accomplished, key decisions, important findings, tools/agents used.",
|
|
552
569
|
"",
|
|
553
570
|
`Project: ${ctx.project}`
|
|
554
571
|
];
|
|
555
572
|
if (ctx.files.length > 0) {
|
|
556
|
-
sections.push(`Files modified: ${ctx.files.slice(0,
|
|
573
|
+
sections.push(`Files modified: ${ctx.files.slice(0, 15).join(", ")}`);
|
|
557
574
|
}
|
|
558
575
|
if (ctx.errors.length > 0) {
|
|
559
|
-
sections.push(`Errors resolved: ${ctx.errors.slice(0, 5).join("; ")}`);
|
|
576
|
+
sections.push(`Errors resolved: ${ctx.errors.slice(0, 5).map(stripNoise).join("; ")}`);
|
|
560
577
|
}
|
|
561
578
|
if (ctx.decisions.length > 0) {
|
|
562
|
-
sections.push(`Decisions: ${ctx.decisions.slice(0,
|
|
579
|
+
sections.push(`Decisions: ${ctx.decisions.slice(0, 8).map(stripNoise).join("; ")}`);
|
|
563
580
|
}
|
|
564
581
|
if (ctx.notes.length > 0) {
|
|
565
|
-
sections.push(`Notes: ${ctx.notes.slice(0,
|
|
582
|
+
sections.push(`Notes: ${ctx.notes.slice(0, 5).map(stripNoise).join("; ")}`);
|
|
566
583
|
}
|
|
567
584
|
if (ctx.observations.length > 0) {
|
|
568
|
-
sections.push(`Key observations: ${ctx.observations.slice(0,
|
|
585
|
+
sections.push(`Key observations: ${ctx.observations.slice(0, 5).map(stripNoise).join("; ")}`);
|
|
569
586
|
}
|
|
570
587
|
let prompt = sections.join(`
|
|
571
588
|
`);
|
|
@@ -666,19 +683,23 @@ class SessionSummarizer {
|
|
|
666
683
|
const hasModified = this.sessionStore.hasModifiedFiles(session_id);
|
|
667
684
|
if (!hasModified && errors.length === 0 && decisions.length === 0 && notes.length === 0 && observations.length === 0)
|
|
668
685
|
return;
|
|
669
|
-
const obsValues = observations.slice(0,
|
|
686
|
+
const obsValues = observations.slice(0, 8).map((o) => o.entity_value);
|
|
670
687
|
let summaryText;
|
|
671
688
|
let tier = "rule-based";
|
|
672
689
|
const llmMode = process.env["CLAUDE_MEMORY_HUB_LLM"] ?? "auto";
|
|
673
690
|
if (llmMode !== "rule-based") {
|
|
691
|
+
const decisionDetails = decisions.slice(0, 8).map((d) => {
|
|
692
|
+
const ctx2 = d.context ? ` \u2192 ${d.context.slice(0, 200)}` : "";
|
|
693
|
+
return d.entity_value.slice(0, 150) + ctx2;
|
|
694
|
+
});
|
|
674
695
|
const ctx = {
|
|
675
696
|
sessionId: session_id,
|
|
676
697
|
project,
|
|
677
698
|
files,
|
|
678
|
-
errors: errors.slice(0, 5).map((e) => e.entity_value.slice(0,
|
|
679
|
-
decisions:
|
|
680
|
-
notes: notes.slice(0,
|
|
681
|
-
observations: obsValues.slice(0,
|
|
699
|
+
errors: errors.slice(0, 5).map((e) => e.entity_value.slice(0, 150)),
|
|
700
|
+
decisions: decisionDetails,
|
|
701
|
+
notes: notes.slice(0, 5),
|
|
702
|
+
observations: obsValues.slice(0, 5)
|
|
682
703
|
};
|
|
683
704
|
summaryText = await tryCliSummary(ctx);
|
|
684
705
|
if (summaryText)
|
|
@@ -769,10 +790,14 @@ async function handlePostCompact(hook, project) {
|
|
|
769
790
|
const files = store.getSessionFiles(hook.session_id);
|
|
770
791
|
const decisions = store.getSessionDecisions(hook.session_id);
|
|
771
792
|
const errors = store.getSessionErrors(hook.session_id);
|
|
793
|
+
const MAX_COMPACT_SUMMARY = 5000;
|
|
794
|
+
const summary = hook.compact_summary.length > MAX_COMPACT_SUMMARY ? hook.compact_summary.slice(0, MAX_COMPACT_SUMMARY - 20) + `
|
|
795
|
+
|
|
796
|
+
[truncated]` : hook.compact_summary;
|
|
772
797
|
ltStore.upsertSummary({
|
|
773
798
|
session_id: hook.session_id,
|
|
774
799
|
project,
|
|
775
|
-
summary
|
|
800
|
+
summary,
|
|
776
801
|
files_touched: JSON.stringify(files.slice(0, 50)),
|
|
777
802
|
decisions: JSON.stringify(decisions.slice(0, 20).map((d) => d.entity_value)),
|
|
778
803
|
errors_fixed: JSON.stringify(errors.slice(0, 10).map((e) => e.entity_value.slice(0, 100))),
|
|
@@ -893,18 +918,26 @@ function extractCodePatterns(content) {
|
|
|
893
918
|
var TOOL_OUTPUT_HEURISTICS = [
|
|
894
919
|
{ pattern: /\b(IMPORTANT|CRITICAL|WARNING|BREAKING)\b/i, importance: 4, label: "important" },
|
|
895
920
|
{ pattern: /\b(DEPRECATED|SECURITY|VULNERABILITY)\b/i, importance: 4, label: "security" },
|
|
921
|
+
{ pattern: /\b(migration failed|data loss|corrupt)/i, importance: 4, label: "data-risk" },
|
|
896
922
|
{ pattern: /\b(decision:|decided to|NOTE:|conclusion:)/i, importance: 3, label: "decision-note" },
|
|
897
923
|
{ pattern: /\b(discovered|found that|learned|realized|root cause)\b/i, importance: 3, label: "discovery" },
|
|
898
924
|
{ pattern: /\b(workaround:|alternative:|instead of|switched to)/i, importance: 3, label: "approach-change" },
|
|
925
|
+
{ pattern: /\b(refactored?|migrated?|upgraded?|replaced)\b/i, importance: 3, label: "refactor" },
|
|
926
|
+
{ pattern: /\b(installed|added dependency|npm install|bun add)\b/i, importance: 2, label: "dependency" },
|
|
899
927
|
{ pattern: /\b(TODO:|FIXME:|HACK:|WORKAROUND:)/i, importance: 2, label: "todo-note" },
|
|
900
928
|
{ pattern: /\b(performance:|bottleneck|slow|timeout|OOM)/i, importance: 2, label: "performance" },
|
|
929
|
+
{ pattern: /\b(created|scaffolded|initialized|bootstrapped)\b/i, importance: 2, label: "creation" },
|
|
930
|
+
{ pattern: /\b(tests? (?:pass|fail)|coverage|assertion)/i, importance: 2, label: "test-result" },
|
|
931
|
+
{ pattern: /\b(deployed|published|released|pushed to)\b/i, importance: 2, label: "deployment" },
|
|
901
932
|
{ pattern: /^>\s+.{10,}/m, importance: 2, label: "quoted" }
|
|
902
933
|
];
|
|
903
934
|
var PROMPT_HEURISTICS = [
|
|
904
935
|
{ pattern: /\b(IMPORTANT|CRITICAL|MUST)\b/i, importance: 4, label: "user-important" },
|
|
905
936
|
{ pattern: /\b(remember that|note that|I decided|we should|keep in mind)\b/i, importance: 3, label: "user-note" },
|
|
906
937
|
{ pattern: /\b(don't|do not|never|avoid|stop)\b/i, importance: 3, label: "user-constraint" },
|
|
907
|
-
{ pattern: /\b(
|
|
938
|
+
{ pattern: /\b(fix|debug|investigate|analyze|resolve)\b/i, importance: 2, label: "user-task" },
|
|
939
|
+
{ pattern: /\b(prefer|always use|convention is|pattern is)\b/i, importance: 2, label: "user-preference" },
|
|
940
|
+
{ pattern: /\b(implement|build|create|add feature|integrate)\b/i, importance: 2, label: "user-feature" }
|
|
908
941
|
];
|
|
909
942
|
var MAX_VALUE_LENGTH = 500;
|
|
910
943
|
var MIN_INPUT_LENGTH = 20;
|
|
@@ -1013,13 +1046,15 @@ function extractEntities(hook, promptNumber = 0) {
|
|
|
1013
1046
|
case "Agent": {
|
|
1014
1047
|
const subagentType = stringField2(tool_input, "subagent_type") ?? "general-purpose";
|
|
1015
1048
|
const prompt = stringField2(tool_input, "prompt") ?? "";
|
|
1016
|
-
|
|
1049
|
+
const agentResult = extractAgentResult(tool_response);
|
|
1050
|
+
raw.push(makeEntity(session_id, project, tool_name, "decision", `agent:${subagentType}: ${prompt.slice(0, 200)}`, 3, now, promptNumber, agentResult || undefined));
|
|
1017
1051
|
break;
|
|
1018
1052
|
}
|
|
1019
1053
|
case "Skill": {
|
|
1020
1054
|
const skillName = stringField2(tool_input, "skill") ?? "unknown";
|
|
1021
1055
|
const args = stringField2(tool_input, "args") ?? "";
|
|
1022
|
-
|
|
1056
|
+
const skillResult = extractAgentResult(tool_response);
|
|
1057
|
+
raw.push(makeEntity(session_id, project, tool_name, "decision", `skill:${skillName} ${args.slice(0, 120)}`.trim(), 2, now, promptNumber, skillResult || undefined));
|
|
1023
1058
|
break;
|
|
1024
1059
|
}
|
|
1025
1060
|
default:
|
|
@@ -1050,6 +1085,15 @@ function stringField2(obj, key) {
|
|
|
1050
1085
|
function deriveProject(hook) {
|
|
1051
1086
|
return "unknown";
|
|
1052
1087
|
}
|
|
1088
|
+
function extractAgentResult(response) {
|
|
1089
|
+
if (!response)
|
|
1090
|
+
return;
|
|
1091
|
+
const r = response;
|
|
1092
|
+
const text = typeof r === "string" ? r : stringField2(r, "result") ?? stringField2(r, "output") ?? stringField2(r, "content") ?? stringField2(r, "text");
|
|
1093
|
+
if (!text)
|
|
1094
|
+
return;
|
|
1095
|
+
return text.length > 800 ? text.slice(0, 797) + "..." : text;
|
|
1096
|
+
}
|
|
1053
1097
|
function extractFileFromBashCmd(cmd) {
|
|
1054
1098
|
const patterns = [
|
|
1055
1099
|
/(?:cp|mv)\s+\S+\s+(\S+\.[\w]+)/,
|
|
@@ -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,
|
|
@@ -597,18 +602,26 @@ function extractCodePatterns(content) {
|
|
|
597
602
|
var TOOL_OUTPUT_HEURISTICS = [
|
|
598
603
|
{ pattern: /\b(IMPORTANT|CRITICAL|WARNING|BREAKING)\b/i, importance: 4, label: "important" },
|
|
599
604
|
{ pattern: /\b(DEPRECATED|SECURITY|VULNERABILITY)\b/i, importance: 4, label: "security" },
|
|
605
|
+
{ pattern: /\b(migration failed|data loss|corrupt)/i, importance: 4, label: "data-risk" },
|
|
600
606
|
{ pattern: /\b(decision:|decided to|NOTE:|conclusion:)/i, importance: 3, label: "decision-note" },
|
|
601
607
|
{ pattern: /\b(discovered|found that|learned|realized|root cause)\b/i, importance: 3, label: "discovery" },
|
|
602
608
|
{ pattern: /\b(workaround:|alternative:|instead of|switched to)/i, importance: 3, label: "approach-change" },
|
|
609
|
+
{ pattern: /\b(refactored?|migrated?|upgraded?|replaced)\b/i, importance: 3, label: "refactor" },
|
|
610
|
+
{ pattern: /\b(installed|added dependency|npm install|bun add)\b/i, importance: 2, label: "dependency" },
|
|
603
611
|
{ pattern: /\b(TODO:|FIXME:|HACK:|WORKAROUND:)/i, importance: 2, label: "todo-note" },
|
|
604
612
|
{ pattern: /\b(performance:|bottleneck|slow|timeout|OOM)/i, importance: 2, label: "performance" },
|
|
613
|
+
{ pattern: /\b(created|scaffolded|initialized|bootstrapped)\b/i, importance: 2, label: "creation" },
|
|
614
|
+
{ pattern: /\b(tests? (?:pass|fail)|coverage|assertion)/i, importance: 2, label: "test-result" },
|
|
615
|
+
{ pattern: /\b(deployed|published|released|pushed to)\b/i, importance: 2, label: "deployment" },
|
|
605
616
|
{ pattern: /^>\s+.{10,}/m, importance: 2, label: "quoted" }
|
|
606
617
|
];
|
|
607
618
|
var PROMPT_HEURISTICS = [
|
|
608
619
|
{ pattern: /\b(IMPORTANT|CRITICAL|MUST)\b/i, importance: 4, label: "user-important" },
|
|
609
620
|
{ pattern: /\b(remember that|note that|I decided|we should|keep in mind)\b/i, importance: 3, label: "user-note" },
|
|
610
621
|
{ pattern: /\b(don't|do not|never|avoid|stop)\b/i, importance: 3, label: "user-constraint" },
|
|
611
|
-
{ pattern: /\b(
|
|
622
|
+
{ pattern: /\b(fix|debug|investigate|analyze|resolve)\b/i, importance: 2, label: "user-task" },
|
|
623
|
+
{ pattern: /\b(prefer|always use|convention is|pattern is)\b/i, importance: 2, label: "user-preference" },
|
|
624
|
+
{ pattern: /\b(implement|build|create|add feature|integrate)\b/i, importance: 2, label: "user-feature" }
|
|
612
625
|
];
|
|
613
626
|
var MAX_VALUE_LENGTH = 500;
|
|
614
627
|
var MIN_INPUT_LENGTH = 20;
|
|
@@ -717,13 +730,15 @@ function extractEntities(hook, promptNumber = 0) {
|
|
|
717
730
|
case "Agent": {
|
|
718
731
|
const subagentType = stringField2(tool_input, "subagent_type") ?? "general-purpose";
|
|
719
732
|
const prompt = stringField2(tool_input, "prompt") ?? "";
|
|
720
|
-
|
|
733
|
+
const agentResult = extractAgentResult(tool_response);
|
|
734
|
+
raw.push(makeEntity(session_id, project, tool_name, "decision", `agent:${subagentType}: ${prompt.slice(0, 200)}`, 3, now, promptNumber, agentResult || undefined));
|
|
721
735
|
break;
|
|
722
736
|
}
|
|
723
737
|
case "Skill": {
|
|
724
738
|
const skillName = stringField2(tool_input, "skill") ?? "unknown";
|
|
725
739
|
const args = stringField2(tool_input, "args") ?? "";
|
|
726
|
-
|
|
740
|
+
const skillResult = extractAgentResult(tool_response);
|
|
741
|
+
raw.push(makeEntity(session_id, project, tool_name, "decision", `skill:${skillName} ${args.slice(0, 120)}`.trim(), 2, now, promptNumber, skillResult || undefined));
|
|
727
742
|
break;
|
|
728
743
|
}
|
|
729
744
|
default:
|
|
@@ -754,6 +769,15 @@ function stringField2(obj, key) {
|
|
|
754
769
|
function deriveProject(hook) {
|
|
755
770
|
return "unknown";
|
|
756
771
|
}
|
|
772
|
+
function extractAgentResult(response) {
|
|
773
|
+
if (!response)
|
|
774
|
+
return;
|
|
775
|
+
const r = response;
|
|
776
|
+
const text = typeof r === "string" ? r : stringField2(r, "result") ?? stringField2(r, "output") ?? stringField2(r, "content") ?? stringField2(r, "text");
|
|
777
|
+
if (!text)
|
|
778
|
+
return;
|
|
779
|
+
return text.length > 800 ? text.slice(0, 797) + "..." : text;
|
|
780
|
+
}
|
|
757
781
|
function extractFileFromBashCmd(cmd) {
|
|
758
782
|
const patterns = [
|
|
759
783
|
/(?:cp|mv)\s+\S+\s+(\S+\.[\w]+)/,
|
|
@@ -1777,37 +1801,46 @@ function projectFromCwd(cwd) {
|
|
|
1777
1801
|
}
|
|
1778
1802
|
|
|
1779
1803
|
// src/summarizer/summarizer-prompts.ts
|
|
1804
|
+
function stripNoiseTags(text) {
|
|
1805
|
+
return text.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, "").replace(/<local-command-caveat>[\s\S]*?<\/local-command-caveat>\s*/g, "").replace(/<command-name>[\s\S]*?<\/command-name>\s*/g, "").replace(/<command-message>[\s\S]*?<\/command-message>\s*/g, "").replace(/<command-args>[\s\S]*?<\/command-args>\s*/g, "").replace(/<local-command-stdout>[\s\S]*?<\/local-command-stdout>\s*/g, "").trim();
|
|
1806
|
+
}
|
|
1780
1807
|
function buildRuleBasedSummary(session, files, errors, decisions, notes = []) {
|
|
1781
1808
|
const parts = [];
|
|
1782
1809
|
if (session.user_prompt) {
|
|
1783
|
-
|
|
1810
|
+
const cleanPrompt = stripNoiseTags(session.user_prompt);
|
|
1811
|
+
if (cleanPrompt) {
|
|
1812
|
+
parts.push(`Task: ${cleanPrompt.slice(0, 500)}.`);
|
|
1813
|
+
}
|
|
1784
1814
|
}
|
|
1785
1815
|
if (files.length > 0) {
|
|
1786
|
-
const listed = files.slice(0,
|
|
1787
|
-
parts.push(`Files (${files.length}): ${listed}${files.length >
|
|
1816
|
+
const listed = files.slice(0, 15).join(", ");
|
|
1817
|
+
parts.push(`Files (${files.length}): ${listed}${files.length > 15 ? ` (+${files.length - 15} more)` : ""}.`);
|
|
1788
1818
|
}
|
|
1789
1819
|
if (decisions.length > 0) {
|
|
1790
|
-
const listed = decisions.slice(0,
|
|
1820
|
+
const listed = decisions.slice(0, 5).map((d) => {
|
|
1821
|
+
const base = d.entity_value.slice(0, 150);
|
|
1822
|
+
const ctx = d.context ? ` \u2192 ${d.context.slice(0, 200)}` : "";
|
|
1823
|
+
return base + ctx;
|
|
1824
|
+
}).join("; ");
|
|
1791
1825
|
parts.push(`Decisions: ${listed}.`);
|
|
1792
1826
|
}
|
|
1793
1827
|
if (errors.length > 0) {
|
|
1794
|
-
const
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
}
|
|
1828
|
+
const errorLines = errors.slice(0, 5).map((e) => {
|
|
1829
|
+
const ctx = e.context ? ` (${e.context.slice(0, 100)})` : "";
|
|
1830
|
+
return `${e.entity_value.slice(0, 150)}${ctx}`;
|
|
1831
|
+
});
|
|
1832
|
+
parts.push(`Errors (${errors.length}): ${errorLines.join("; ")}.`);
|
|
1800
1833
|
}
|
|
1801
1834
|
if (notes.length > 0) {
|
|
1802
|
-
parts.push(`Notes: ${notes.slice(-
|
|
1835
|
+
parts.push(`Notes: ${notes.slice(-5).join("; ").slice(0, 500)}.`);
|
|
1803
1836
|
}
|
|
1804
1837
|
return parts.join(" ") || `Session in project ${session.project}.`;
|
|
1805
1838
|
}
|
|
1806
1839
|
|
|
1807
1840
|
// src/summarizer/cli-summarizer.ts
|
|
1808
1841
|
var log5 = createLogger("cli-summarizer");
|
|
1809
|
-
var MAX_PROMPT_CHARS =
|
|
1810
|
-
var MAX_OUTPUT_CHARS =
|
|
1842
|
+
var MAX_PROMPT_CHARS = 6000;
|
|
1843
|
+
var MAX_OUTPUT_CHARS = 2000;
|
|
1811
1844
|
var DEFAULT_TIMEOUT_MS = 30000;
|
|
1812
1845
|
var _cliAvailable;
|
|
1813
1846
|
var _cliCheckedAt = 0;
|
|
@@ -1827,27 +1860,30 @@ function isClaudeCliAvailable() {
|
|
|
1827
1860
|
_cliCheckedAt = Date.now();
|
|
1828
1861
|
return _cliAvailable;
|
|
1829
1862
|
}
|
|
1863
|
+
function stripNoise(text) {
|
|
1864
|
+
return text.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, "").replace(/<local-command-[\w-]+>[\s\S]*?<\/local-command-[\w-]+>\s*/g, "").replace(/<command-[\w-]+>[\s\S]*?<\/command-[\w-]+>\s*/g, "").trim();
|
|
1865
|
+
}
|
|
1830
1866
|
function buildCliPrompt(ctx) {
|
|
1831
1867
|
const sections = [
|
|
1832
|
-
"Summarize this coding session in
|
|
1833
|
-
"Focus on: what was accomplished, key decisions, important findings.",
|
|
1868
|
+
"Summarize this coding session in 3-5 plain sentences. No markdown, no headers, no code blocks.",
|
|
1869
|
+
"Focus on: what was accomplished, key decisions, important findings, tools/agents used.",
|
|
1834
1870
|
"",
|
|
1835
1871
|
`Project: ${ctx.project}`
|
|
1836
1872
|
];
|
|
1837
1873
|
if (ctx.files.length > 0) {
|
|
1838
|
-
sections.push(`Files modified: ${ctx.files.slice(0,
|
|
1874
|
+
sections.push(`Files modified: ${ctx.files.slice(0, 15).join(", ")}`);
|
|
1839
1875
|
}
|
|
1840
1876
|
if (ctx.errors.length > 0) {
|
|
1841
|
-
sections.push(`Errors resolved: ${ctx.errors.slice(0, 5).join("; ")}`);
|
|
1877
|
+
sections.push(`Errors resolved: ${ctx.errors.slice(0, 5).map(stripNoise).join("; ")}`);
|
|
1842
1878
|
}
|
|
1843
1879
|
if (ctx.decisions.length > 0) {
|
|
1844
|
-
sections.push(`Decisions: ${ctx.decisions.slice(0,
|
|
1880
|
+
sections.push(`Decisions: ${ctx.decisions.slice(0, 8).map(stripNoise).join("; ")}`);
|
|
1845
1881
|
}
|
|
1846
1882
|
if (ctx.notes.length > 0) {
|
|
1847
|
-
sections.push(`Notes: ${ctx.notes.slice(0,
|
|
1883
|
+
sections.push(`Notes: ${ctx.notes.slice(0, 5).map(stripNoise).join("; ")}`);
|
|
1848
1884
|
}
|
|
1849
1885
|
if (ctx.observations.length > 0) {
|
|
1850
|
-
sections.push(`Key observations: ${ctx.observations.slice(0,
|
|
1886
|
+
sections.push(`Key observations: ${ctx.observations.slice(0, 5).map(stripNoise).join("; ")}`);
|
|
1851
1887
|
}
|
|
1852
1888
|
let prompt = sections.join(`
|
|
1853
1889
|
`);
|
|
@@ -1948,19 +1984,23 @@ class SessionSummarizer {
|
|
|
1948
1984
|
const hasModified = this.sessionStore.hasModifiedFiles(session_id);
|
|
1949
1985
|
if (!hasModified && errors.length === 0 && decisions.length === 0 && notes.length === 0 && observations.length === 0)
|
|
1950
1986
|
return;
|
|
1951
|
-
const obsValues = observations.slice(0,
|
|
1987
|
+
const obsValues = observations.slice(0, 8).map((o) => o.entity_value);
|
|
1952
1988
|
let summaryText;
|
|
1953
1989
|
let tier = "rule-based";
|
|
1954
1990
|
const llmMode = process.env["CLAUDE_MEMORY_HUB_LLM"] ?? "auto";
|
|
1955
1991
|
if (llmMode !== "rule-based") {
|
|
1992
|
+
const decisionDetails = decisions.slice(0, 8).map((d) => {
|
|
1993
|
+
const ctx2 = d.context ? ` \u2192 ${d.context.slice(0, 200)}` : "";
|
|
1994
|
+
return d.entity_value.slice(0, 150) + ctx2;
|
|
1995
|
+
});
|
|
1956
1996
|
const ctx = {
|
|
1957
1997
|
sessionId: session_id,
|
|
1958
1998
|
project,
|
|
1959
1999
|
files,
|
|
1960
|
-
errors: errors.slice(0, 5).map((e) => e.entity_value.slice(0,
|
|
1961
|
-
decisions:
|
|
1962
|
-
notes: notes.slice(0,
|
|
1963
|
-
observations: obsValues.slice(0,
|
|
2000
|
+
errors: errors.slice(0, 5).map((e) => e.entity_value.slice(0, 150)),
|
|
2001
|
+
decisions: decisionDetails,
|
|
2002
|
+
notes: notes.slice(0, 5),
|
|
2003
|
+
observations: obsValues.slice(0, 5)
|
|
1964
2004
|
};
|
|
1965
2005
|
summaryText = await tryCliSummary(ctx);
|
|
1966
2006
|
if (summaryText)
|
|
@@ -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,
|
|
@@ -597,18 +602,26 @@ function extractCodePatterns(content) {
|
|
|
597
602
|
var TOOL_OUTPUT_HEURISTICS = [
|
|
598
603
|
{ pattern: /\b(IMPORTANT|CRITICAL|WARNING|BREAKING)\b/i, importance: 4, label: "important" },
|
|
599
604
|
{ pattern: /\b(DEPRECATED|SECURITY|VULNERABILITY)\b/i, importance: 4, label: "security" },
|
|
605
|
+
{ pattern: /\b(migration failed|data loss|corrupt)/i, importance: 4, label: "data-risk" },
|
|
600
606
|
{ pattern: /\b(decision:|decided to|NOTE:|conclusion:)/i, importance: 3, label: "decision-note" },
|
|
601
607
|
{ pattern: /\b(discovered|found that|learned|realized|root cause)\b/i, importance: 3, label: "discovery" },
|
|
602
608
|
{ pattern: /\b(workaround:|alternative:|instead of|switched to)/i, importance: 3, label: "approach-change" },
|
|
609
|
+
{ pattern: /\b(refactored?|migrated?|upgraded?|replaced)\b/i, importance: 3, label: "refactor" },
|
|
610
|
+
{ pattern: /\b(installed|added dependency|npm install|bun add)\b/i, importance: 2, label: "dependency" },
|
|
603
611
|
{ pattern: /\b(TODO:|FIXME:|HACK:|WORKAROUND:)/i, importance: 2, label: "todo-note" },
|
|
604
612
|
{ pattern: /\b(performance:|bottleneck|slow|timeout|OOM)/i, importance: 2, label: "performance" },
|
|
613
|
+
{ pattern: /\b(created|scaffolded|initialized|bootstrapped)\b/i, importance: 2, label: "creation" },
|
|
614
|
+
{ pattern: /\b(tests? (?:pass|fail)|coverage|assertion)/i, importance: 2, label: "test-result" },
|
|
615
|
+
{ pattern: /\b(deployed|published|released|pushed to)\b/i, importance: 2, label: "deployment" },
|
|
605
616
|
{ pattern: /^>\s+.{10,}/m, importance: 2, label: "quoted" }
|
|
606
617
|
];
|
|
607
618
|
var PROMPT_HEURISTICS = [
|
|
608
619
|
{ pattern: /\b(IMPORTANT|CRITICAL|MUST)\b/i, importance: 4, label: "user-important" },
|
|
609
620
|
{ pattern: /\b(remember that|note that|I decided|we should|keep in mind)\b/i, importance: 3, label: "user-note" },
|
|
610
621
|
{ pattern: /\b(don't|do not|never|avoid|stop)\b/i, importance: 3, label: "user-constraint" },
|
|
611
|
-
{ pattern: /\b(
|
|
622
|
+
{ pattern: /\b(fix|debug|investigate|analyze|resolve)\b/i, importance: 2, label: "user-task" },
|
|
623
|
+
{ pattern: /\b(prefer|always use|convention is|pattern is)\b/i, importance: 2, label: "user-preference" },
|
|
624
|
+
{ pattern: /\b(implement|build|create|add feature|integrate)\b/i, importance: 2, label: "user-feature" }
|
|
612
625
|
];
|
|
613
626
|
var MAX_VALUE_LENGTH = 500;
|
|
614
627
|
var MIN_INPUT_LENGTH = 20;
|
|
@@ -717,13 +730,15 @@ function extractEntities(hook, promptNumber = 0) {
|
|
|
717
730
|
case "Agent": {
|
|
718
731
|
const subagentType = stringField2(tool_input, "subagent_type") ?? "general-purpose";
|
|
719
732
|
const prompt = stringField2(tool_input, "prompt") ?? "";
|
|
720
|
-
|
|
733
|
+
const agentResult = extractAgentResult(tool_response);
|
|
734
|
+
raw.push(makeEntity(session_id, project, tool_name, "decision", `agent:${subagentType}: ${prompt.slice(0, 200)}`, 3, now, promptNumber, agentResult || undefined));
|
|
721
735
|
break;
|
|
722
736
|
}
|
|
723
737
|
case "Skill": {
|
|
724
738
|
const skillName = stringField2(tool_input, "skill") ?? "unknown";
|
|
725
739
|
const args = stringField2(tool_input, "args") ?? "";
|
|
726
|
-
|
|
740
|
+
const skillResult = extractAgentResult(tool_response);
|
|
741
|
+
raw.push(makeEntity(session_id, project, tool_name, "decision", `skill:${skillName} ${args.slice(0, 120)}`.trim(), 2, now, promptNumber, skillResult || undefined));
|
|
727
742
|
break;
|
|
728
743
|
}
|
|
729
744
|
default:
|
|
@@ -754,6 +769,15 @@ function stringField2(obj, key) {
|
|
|
754
769
|
function deriveProject(hook) {
|
|
755
770
|
return "unknown";
|
|
756
771
|
}
|
|
772
|
+
function extractAgentResult(response) {
|
|
773
|
+
if (!response)
|
|
774
|
+
return;
|
|
775
|
+
const r = response;
|
|
776
|
+
const text = typeof r === "string" ? r : stringField2(r, "result") ?? stringField2(r, "output") ?? stringField2(r, "content") ?? stringField2(r, "text");
|
|
777
|
+
if (!text)
|
|
778
|
+
return;
|
|
779
|
+
return text.length > 800 ? text.slice(0, 797) + "..." : text;
|
|
780
|
+
}
|
|
757
781
|
function extractFileFromBashCmd(cmd) {
|
|
758
782
|
const patterns = [
|
|
759
783
|
/(?:cp|mv)\s+\S+\s+(\S+\.[\w]+)/,
|
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,
|
package/package.json
CHANGED