claude-memory-hub 0.8.1 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +26 -0
- package/README.md +43 -15
- package/dist/hooks/post-compact.js +75 -23
- package/dist/hooks/post-tool-use.js +77 -25
- package/dist/hooks/pre-compact.js +75 -23
- package/dist/hooks/session-end.js +77 -25
- package/dist/hooks/user-prompt-submit.js +75 -23
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,32 @@ Format follows [Keep a Changelog](https://keepachangelog.com/).
|
|
|
5
5
|
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
+
## [0.9.0] - 2026-04-02
|
|
9
|
+
|
|
10
|
+
Smart context budget allocation — memory never gets pushed out by lower-priority content.
|
|
11
|
+
|
|
12
|
+
### Smart Budget Allocation (breaking change in injection behavior)
|
|
13
|
+
|
|
14
|
+
- **Priority-based `fitWithinBudget()`** — replaces naive sequential concatenation. Four content sections now compete for 8,000 chars with explicit priorities: P1 memory (min 500 chars) > P2 CLAUDE.md (min 200 chars) > P3 resource advice (droppable) > P4 overhead warning (droppable). When total fits, everything is kept. When over budget, lowest priority sections are shrunk or dropped first
|
|
15
|
+
- **Memory context guaranteed** — past session context always gets first claim on budget. Previously could be truncated when CLAUDE.md + advice consumed too much space
|
|
16
|
+
|
|
17
|
+
### CLAUDE.md Adaptive Compression
|
|
18
|
+
|
|
19
|
+
- **3-level `formatForInjection()`** — CLAUDE.md summary now adapts to available budget: Level 3 (full: headings + token cost, >500 chars), Level 2 (compact: file + token cost, >200 chars), Level 1 (minimal: file names only, <200 chars). Previously always used full format regardless of remaining space
|
|
20
|
+
|
|
21
|
+
### Overhead Warning Injection
|
|
22
|
+
|
|
23
|
+
- **Auto-inject warning when unused resources > 10K tokens** — UserPromptSubmit hook now analyzes `OverheadReport` and injects a one-line note if unused skills/agents exceed 10,000 listing tokens. Points user to `memory_context_budget` tool for details. Zero-cost when overhead is acceptable
|
|
24
|
+
|
|
25
|
+
### Context Injection Limits
|
|
26
|
+
|
|
27
|
+
- **UserPromptSubmit cap doubled** — `MAX_CHARS` increased from 4,500 (~1,125 tokens) to 8,000 (~2,000 tokens)
|
|
28
|
+
- **Proactive retrieval cap doubled** — `MAX_INJECTION_CHARS` increased from 1,500 (~375 tokens) to 3,000 (~750 tokens)
|
|
29
|
+
- **Proactive summary slice increased** — per-result summary increased from 200 to 400 chars for richer mid-session context
|
|
30
|
+
- **Memory result summary increased** — session-start per-result summary increased from 300 to 400 chars
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
8
34
|
## [0.8.1] - 2026-04-02
|
|
9
35
|
|
|
10
36
|
Token-budget-aware MCP tools + proactive mid-session memory retrieval.
|
package/README.md
CHANGED
|
@@ -17,6 +17,31 @@ Zero API key. Zero Python. Zero config. One install command.
|
|
|
17
17
|
|
|
18
18
|
---
|
|
19
19
|
|
|
20
|
+
## Why memory-hub?
|
|
21
|
+
|
|
22
|
+
**Claude Code forgets everything.** Every session starts from zero. Auto-compact destroys 90% of your context. You lose files, decisions, errors — hours of work, gone.
|
|
23
|
+
|
|
24
|
+
**claude-memory-hub fixes this.** One install command. No API key. No Python. No Docker.
|
|
25
|
+
|
|
26
|
+
What makes it different? **The Compact Interceptor** — something no other memory tool has. When Claude Code auto-compacts at 200K tokens, memory-hub *tells the compact engine what matters*. PreCompact hook injects priority instructions. PostCompact hook saves the full summary. Result: 90% context salvage instead of vaporization.
|
|
27
|
+
|
|
28
|
+
But it doesn't stop there:
|
|
29
|
+
- **Cross-session memory** — past work auto-injected when you start a new session
|
|
30
|
+
- **3-engine hybrid search** — FTS5 + TF-IDF + semantic embeddings (384-dim, offline)
|
|
31
|
+
- **Proactive retrieval** — detects topic shifts mid-session, injects relevant context automatically
|
|
32
|
+
- **91 unit tests**, batch queue (75ms→3ms), JSONL export/import, browser UI
|
|
33
|
+
- **Multi-agent ready** — subagents share memory for free via MCP
|
|
34
|
+
|
|
35
|
+
Built for developers who use Claude Code daily and are tired of repeating themselves.
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
bunx claude-memory-hub install
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
That's it. Your Claude now remembers.
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
20
45
|
## The Problem
|
|
21
46
|
|
|
22
47
|
Claude Code forgets everything between sessions. Within long sessions, auto-compact destroys 90% of context. Search is keyword-only with no ranking.
|
|
@@ -49,6 +74,8 @@ Search: Keyword-only, no semantic ranking
|
|
|
49
74
|
| Multi-agent memory sharing | -- | -- | **Yes (free)** |
|
|
50
75
|
| Permission-aware (approved only) | -- | -- | **Yes** |
|
|
51
76
|
| Data export/import (JSONL) | -- | -- | **Yes** |
|
|
77
|
+
| Smart budget allocation (priority-based) | -- | -- | **Yes** |
|
|
78
|
+
| Overhead warning (unused resources) | -- | -- | **Yes** |
|
|
52
79
|
| Hook batching (3ms vs 75ms) | -- | -- | **Yes** |
|
|
53
80
|
| Browser UI | -- | Yes | **Yes** |
|
|
54
81
|
| Health monitoring + auto-cleanup | -- | -- | **Yes** |
|
|
@@ -156,10 +183,10 @@ User prompt contains "remember that we use TypeScript strict"
|
|
|
156
183
|
## Architecture
|
|
157
184
|
|
|
158
185
|
```
|
|
159
|
-
|
|
160
|
-
│ Claude Code
|
|
161
|
-
│
|
|
162
|
-
│ 5 Lifecycle Hooks
|
|
186
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
187
|
+
│ Claude Code │
|
|
188
|
+
│ │
|
|
189
|
+
│ 5 Lifecycle Hooks │
|
|
163
190
|
│ ┌───────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
|
164
191
|
│ │ PostToolUse │ │ PreCompact │ │ PostCompact │ │
|
|
165
192
|
│ │ batch queue │ │ inject │ │ save summary │ │
|
|
@@ -169,25 +196,25 @@ User prompt contains "remember that we use TypeScript strict"
|
|
|
169
196
|
│ │UserPrompt │ │ │ Stop │ │
|
|
170
197
|
│ │Submit: inject│ │ │ session end │ │
|
|
171
198
|
│ │past context │ │ │ summarize │ │
|
|
172
|
-
│ └──────────────┘ │ └──────────────┘ │
|
|
199
|
+
│ └──────────────┘ │ └──────────────┘ │
|
|
173
200
|
│ │ │
|
|
174
201
|
│ MCP Server (stdio, long-lived) │
|
|
175
202
|
│ ┌─────────────────────────────────────────────────────┐ │
|
|
176
203
|
│ │ memory_recall memory_search (L1 index) │ │
|
|
177
204
|
│ │ memory_entities memory_timeline (L2 context) │ │
|
|
178
205
|
│ │ memory_session_notes memory_fetch (L3 full) │ │
|
|
179
|
-
│ │ memory_store memory_context_budget
|
|
180
|
-
│ │ memory_health
|
|
181
|
-
│ │
|
|
182
|
-
│ │ L1 WorkingMemory: read-through cache over L2
|
|
206
|
+
│ │ memory_store memory_context_budget │ │
|
|
207
|
+
│ │ memory_health │ │
|
|
208
|
+
│ │ │ │
|
|
209
|
+
│ │ L1 WorkingMemory: read-through cache over L2 │ │
|
|
183
210
|
│ └─────────────────────────────────────────────────────┘ │
|
|
184
|
-
│
|
|
185
|
-
│ Resource Intelligence Browser UI (:37888)
|
|
211
|
+
│ │
|
|
212
|
+
│ Resource Intelligence Browser UI (:37888) │
|
|
186
213
|
│ ┌──────────────────┐ ┌──────────────────┐ │
|
|
187
214
|
│ │ scan → track → │ │ search, browse, │ │
|
|
188
|
-
│ │ analyze overhead
|
|
215
|
+
│ │ analyze overhead │ │ stats, health │ │
|
|
189
216
|
│ └──────────────────┘ └──────────────────┘ │
|
|
190
|
-
|
|
217
|
+
└─────────────────────────────────────────────────────────────┘
|
|
191
218
|
│
|
|
192
219
|
┌─────────┴──────────┐
|
|
193
220
|
│ SQLite + FTS5 │
|
|
@@ -195,7 +222,7 @@ User prompt contains "remember that we use TypeScript strict"
|
|
|
195
222
|
│ memory-hub/ │
|
|
196
223
|
│ │
|
|
197
224
|
│ memory.db │
|
|
198
|
-
│ batch/queue.jsonl
|
|
225
|
+
│ batch/queue.jsonl│
|
|
199
226
|
│ logs/ │
|
|
200
227
|
└────────────────────┘
|
|
201
228
|
```
|
|
@@ -216,7 +243,7 @@ User prompt contains "remember that we use TypeScript strict"
|
|
|
216
243
|
│ files, errors, decisions Per-session scope │
|
|
217
244
|
│ observations (14 patterns) Importance scored 1-5 │
|
|
218
245
|
├─────────────────────────────────────────────────────┤
|
|
219
|
-
│ L3: LongTermStore SQLite + FTS5 + TF-IDF
|
|
246
|
+
│ L3: LongTermStore SQLite + FTS5 + TF-IDF │
|
|
220
247
|
│ Cross-session summaries <100ms access │
|
|
221
248
|
│ Hybrid ranked search Persistent forever │
|
|
222
249
|
│ Semantic embeddings 3-layer progressive │
|
|
@@ -387,6 +414,7 @@ Migration is idempotent — safe to run multiple times with zero duplicates.
|
|
|
387
414
|
| **v0.7.0** | Honest resource analysis, semantic search scaling, batch embeddings, 14 observation patterns, DB auto-cleanup, summarizer retry |
|
|
388
415
|
| **v0.8.0** | 91 unit tests (was 0%), L1 read-through cache, PostToolUse batch queue (75ms→3ms), JSONL export/import, data cleanup CLI, CI/CD auto-publish |
|
|
389
416
|
| **v0.8.1** | Token-budget-aware MCP tools (`max_tokens`), proactive mid-session memory retrieval (topic-shift detection), session-end batch flush |
|
|
417
|
+
| **v0.9.0** | Smart budget allocation (priority-based, memory never pushed out), CLAUDE.md adaptive compression (3 levels), overhead warning auto-injection, doubled injection limits |
|
|
390
418
|
|
|
391
419
|
See [CHANGELOG.md](CHANGELOG.md) for full details.
|
|
392
420
|
|
|
@@ -1717,7 +1717,7 @@ function safeJson(text, fallback) {
|
|
|
1717
1717
|
|
|
1718
1718
|
// src/context/injection-validator.ts
|
|
1719
1719
|
var log5 = createLogger("injection-validator");
|
|
1720
|
-
var MAX_CHARS =
|
|
1720
|
+
var MAX_CHARS = 8000;
|
|
1721
1721
|
|
|
1722
1722
|
class InjectionValidator {
|
|
1723
1723
|
registry;
|
|
@@ -1829,15 +1829,26 @@ class ClaudeMdTracker {
|
|
|
1829
1829
|
tokenCost: r.token_cost
|
|
1830
1830
|
}));
|
|
1831
1831
|
}
|
|
1832
|
-
formatForInjection(entries) {
|
|
1832
|
+
formatForInjection(entries, maxChars) {
|
|
1833
1833
|
if (entries.length === 0)
|
|
1834
1834
|
return "";
|
|
1835
|
-
const
|
|
1835
|
+
const minimal = `CLAUDE.md: ${entries.map((e) => basename2(dirname(e.path)) + "/" + basename2(e.path)).join(", ")}`;
|
|
1836
|
+
if (maxChars !== undefined && maxChars < 200)
|
|
1837
|
+
return minimal;
|
|
1838
|
+
const compactLines = ["**Active CLAUDE.md rules:**"];
|
|
1839
|
+
for (const e of entries) {
|
|
1840
|
+
compactLines.push(`- ${basename2(dirname(e.path))}/${basename2(e.path)} (~${e.tokenCost} tok)`);
|
|
1841
|
+
}
|
|
1842
|
+
const compact = compactLines.join(`
|
|
1843
|
+
`);
|
|
1844
|
+
if (maxChars !== undefined && maxChars < 500)
|
|
1845
|
+
return compact;
|
|
1846
|
+
const fullLines = ["**Active CLAUDE.md rules:**"];
|
|
1836
1847
|
for (const e of entries) {
|
|
1837
1848
|
const headings = e.sections.map((s) => s.heading).join(", ");
|
|
1838
|
-
|
|
1849
|
+
fullLines.push(`- ${basename2(dirname(e.path))}/${basename2(e.path)} (~${e.tokenCost} tok): ${headings || "no sections"}`);
|
|
1839
1850
|
}
|
|
1840
|
-
return
|
|
1851
|
+
return fullLines.join(`
|
|
1841
1852
|
`);
|
|
1842
1853
|
}
|
|
1843
1854
|
computeHash(content) {
|
|
@@ -1955,36 +1966,77 @@ async function handleUserPromptSubmit(hook, project) {
|
|
|
1955
1966
|
plan.recommendations = validator.filterAliveRecommendations(plan.recommendations);
|
|
1956
1967
|
plan.skipped = validator.filterAliveRecommendations(plan.skipped);
|
|
1957
1968
|
const advice = loader.formatContextAdvice(plan);
|
|
1958
|
-
|
|
1959
|
-
if (results.length > 0) {
|
|
1960
|
-
lines.push("**Past session context:**");
|
|
1961
|
-
for (const r of results) {
|
|
1962
|
-
const date = new Date(r.created_at).toLocaleDateString();
|
|
1963
|
-
const files = safeJson3(r.files_touched, []);
|
|
1964
|
-
lines.push(`- [${date}, ${r.project}] ${r.summary.slice(0, 300)}`);
|
|
1965
|
-
if (files.length > 0)
|
|
1966
|
-
lines.push(` Files: ${files.slice(0, 5).join(", ")}`);
|
|
1967
|
-
}
|
|
1968
|
-
}
|
|
1969
|
-
if (advice)
|
|
1970
|
-
lines.push("", advice);
|
|
1969
|
+
let mdSummary = "";
|
|
1971
1970
|
if (hook.cwd) {
|
|
1972
1971
|
try {
|
|
1973
1972
|
const mdTracker = new ClaudeMdTracker;
|
|
1974
1973
|
const mdEntries = mdTracker.scanAndUpdate(hook.cwd, project);
|
|
1975
|
-
|
|
1976
|
-
if (mdSummary)
|
|
1977
|
-
lines.push("", mdSummary);
|
|
1974
|
+
mdSummary = mdTracker.formatForInjection(mdEntries);
|
|
1978
1975
|
const tracker = new ResourceTracker;
|
|
1979
1976
|
for (const entry of mdEntries) {
|
|
1980
1977
|
tracker.trackUsage(hook.session_id, project, "claude_md", entry.path, entry.tokenCost);
|
|
1981
1978
|
}
|
|
1982
1979
|
} catch {}
|
|
1983
1980
|
}
|
|
1984
|
-
|
|
1985
|
-
|
|
1981
|
+
let overheadWarning = "";
|
|
1982
|
+
try {
|
|
1983
|
+
const overhead = await registry.getOverheadReport(project);
|
|
1984
|
+
const unusedTokens = overhead.potential_savings.if_remove_unused_skills + overhead.potential_savings.if_remove_unused_agents;
|
|
1985
|
+
if (unusedTokens > 1e4) {
|
|
1986
|
+
const unusedCount = overhead.usage_analysis.skills_never_used.length + overhead.usage_analysis.agents_never_used.length;
|
|
1987
|
+
overheadWarning = `Note: ${unusedCount} unused resources (~${unusedTokens} listing tok overhead). Run \`memory_context_budget\` for details.`;
|
|
1988
|
+
}
|
|
1989
|
+
} catch {}
|
|
1990
|
+
const memorySection = buildMemorySection(results);
|
|
1991
|
+
const safeContext = validator.validate(fitWithinBudget(memorySection, mdSummary, advice, overheadWarning));
|
|
1986
1992
|
return { additionalContext: safeContext };
|
|
1987
1993
|
}
|
|
1994
|
+
function fitWithinBudget(memoryText, mdText, adviceText, overheadText) {
|
|
1995
|
+
const MAX_CHARS2 = 8000;
|
|
1996
|
+
const sections = [
|
|
1997
|
+
{ text: memoryText, priority: 1, minChars: 500 },
|
|
1998
|
+
{ text: mdText, priority: 2, minChars: 200 },
|
|
1999
|
+
{ text: adviceText, priority: 3, minChars: 0 },
|
|
2000
|
+
{ text: overheadText, priority: 4, minChars: 0 }
|
|
2001
|
+
].filter((s) => s.text.length > 0);
|
|
2002
|
+
const totalNeeded = sections.reduce((sum, s) => sum + s.text.length, 0);
|
|
2003
|
+
if (totalNeeded <= MAX_CHARS2) {
|
|
2004
|
+
return sections.map((s) => s.text).join(`
|
|
2005
|
+
|
|
2006
|
+
`);
|
|
2007
|
+
}
|
|
2008
|
+
let remaining = MAX_CHARS2;
|
|
2009
|
+
const allocated = [];
|
|
2010
|
+
sections.sort((a, b) => a.priority - b.priority);
|
|
2011
|
+
for (const section of sections) {
|
|
2012
|
+
if (remaining <= 0)
|
|
2013
|
+
break;
|
|
2014
|
+
if (section.text.length <= remaining) {
|
|
2015
|
+
allocated.push(section.text);
|
|
2016
|
+
remaining -= section.text.length + 2;
|
|
2017
|
+
} else if (remaining >= section.minChars) {
|
|
2018
|
+
allocated.push(section.text.slice(0, remaining));
|
|
2019
|
+
remaining = 0;
|
|
2020
|
+
}
|
|
2021
|
+
}
|
|
2022
|
+
return allocated.join(`
|
|
2023
|
+
|
|
2024
|
+
`);
|
|
2025
|
+
}
|
|
2026
|
+
function buildMemorySection(results) {
|
|
2027
|
+
if (results.length === 0)
|
|
2028
|
+
return "";
|
|
2029
|
+
const lines = ["**Past session context:**"];
|
|
2030
|
+
for (const r of results) {
|
|
2031
|
+
const date = new Date(r.created_at).toLocaleDateString();
|
|
2032
|
+
const files = safeJson3(r.files_touched, []);
|
|
2033
|
+
lines.push(`- [${date}, ${r.project}] ${r.summary.slice(0, 400)}`);
|
|
2034
|
+
if (files.length > 0)
|
|
2035
|
+
lines.push(` Files: ${files.slice(0, 5).join(", ")}`);
|
|
2036
|
+
}
|
|
2037
|
+
return lines.join(`
|
|
2038
|
+
`);
|
|
2039
|
+
}
|
|
1988
2040
|
async function handleSessionEnd(hook, project) {
|
|
1989
2041
|
const store = new SessionStore;
|
|
1990
2042
|
store.completeSession(hook.session_id);
|
|
@@ -1424,7 +1424,7 @@ function safeJson(text, fallback) {
|
|
|
1424
1424
|
|
|
1425
1425
|
// src/context/injection-validator.ts
|
|
1426
1426
|
var log3 = createLogger("injection-validator");
|
|
1427
|
-
var MAX_CHARS =
|
|
1427
|
+
var MAX_CHARS = 8000;
|
|
1428
1428
|
|
|
1429
1429
|
class InjectionValidator {
|
|
1430
1430
|
registry;
|
|
@@ -1536,15 +1536,26 @@ class ClaudeMdTracker {
|
|
|
1536
1536
|
tokenCost: r.token_cost
|
|
1537
1537
|
}));
|
|
1538
1538
|
}
|
|
1539
|
-
formatForInjection(entries) {
|
|
1539
|
+
formatForInjection(entries, maxChars) {
|
|
1540
1540
|
if (entries.length === 0)
|
|
1541
1541
|
return "";
|
|
1542
|
-
const
|
|
1542
|
+
const minimal = `CLAUDE.md: ${entries.map((e) => basename2(dirname(e.path)) + "/" + basename2(e.path)).join(", ")}`;
|
|
1543
|
+
if (maxChars !== undefined && maxChars < 200)
|
|
1544
|
+
return minimal;
|
|
1545
|
+
const compactLines = ["**Active CLAUDE.md rules:**"];
|
|
1546
|
+
for (const e of entries) {
|
|
1547
|
+
compactLines.push(`- ${basename2(dirname(e.path))}/${basename2(e.path)} (~${e.tokenCost} tok)`);
|
|
1548
|
+
}
|
|
1549
|
+
const compact = compactLines.join(`
|
|
1550
|
+
`);
|
|
1551
|
+
if (maxChars !== undefined && maxChars < 500)
|
|
1552
|
+
return compact;
|
|
1553
|
+
const fullLines = ["**Active CLAUDE.md rules:**"];
|
|
1543
1554
|
for (const e of entries) {
|
|
1544
1555
|
const headings = e.sections.map((s) => s.heading).join(", ");
|
|
1545
|
-
|
|
1556
|
+
fullLines.push(`- ${basename2(dirname(e.path))}/${basename2(e.path)} (~${e.tokenCost} tok): ${headings || "no sections"}`);
|
|
1546
1557
|
}
|
|
1547
|
-
return
|
|
1558
|
+
return fullLines.join(`
|
|
1548
1559
|
`);
|
|
1549
1560
|
}
|
|
1550
1561
|
computeHash(content) {
|
|
@@ -1662,36 +1673,77 @@ async function handleUserPromptSubmit(hook, project) {
|
|
|
1662
1673
|
plan.recommendations = validator.filterAliveRecommendations(plan.recommendations);
|
|
1663
1674
|
plan.skipped = validator.filterAliveRecommendations(plan.skipped);
|
|
1664
1675
|
const advice = loader.formatContextAdvice(plan);
|
|
1665
|
-
|
|
1666
|
-
if (results.length > 0) {
|
|
1667
|
-
lines.push("**Past session context:**");
|
|
1668
|
-
for (const r of results) {
|
|
1669
|
-
const date = new Date(r.created_at).toLocaleDateString();
|
|
1670
|
-
const files = safeJson3(r.files_touched, []);
|
|
1671
|
-
lines.push(`- [${date}, ${r.project}] ${r.summary.slice(0, 300)}`);
|
|
1672
|
-
if (files.length > 0)
|
|
1673
|
-
lines.push(` Files: ${files.slice(0, 5).join(", ")}`);
|
|
1674
|
-
}
|
|
1675
|
-
}
|
|
1676
|
-
if (advice)
|
|
1677
|
-
lines.push("", advice);
|
|
1676
|
+
let mdSummary = "";
|
|
1678
1677
|
if (hook.cwd) {
|
|
1679
1678
|
try {
|
|
1680
1679
|
const mdTracker = new ClaudeMdTracker;
|
|
1681
1680
|
const mdEntries = mdTracker.scanAndUpdate(hook.cwd, project);
|
|
1682
|
-
|
|
1683
|
-
if (mdSummary)
|
|
1684
|
-
lines.push("", mdSummary);
|
|
1681
|
+
mdSummary = mdTracker.formatForInjection(mdEntries);
|
|
1685
1682
|
const tracker = new ResourceTracker;
|
|
1686
1683
|
for (const entry of mdEntries) {
|
|
1687
1684
|
tracker.trackUsage(hook.session_id, project, "claude_md", entry.path, entry.tokenCost);
|
|
1688
1685
|
}
|
|
1689
1686
|
} catch {}
|
|
1690
1687
|
}
|
|
1691
|
-
|
|
1692
|
-
|
|
1688
|
+
let overheadWarning = "";
|
|
1689
|
+
try {
|
|
1690
|
+
const overhead = await registry.getOverheadReport(project);
|
|
1691
|
+
const unusedTokens = overhead.potential_savings.if_remove_unused_skills + overhead.potential_savings.if_remove_unused_agents;
|
|
1692
|
+
if (unusedTokens > 1e4) {
|
|
1693
|
+
const unusedCount = overhead.usage_analysis.skills_never_used.length + overhead.usage_analysis.agents_never_used.length;
|
|
1694
|
+
overheadWarning = `Note: ${unusedCount} unused resources (~${unusedTokens} listing tok overhead). Run \`memory_context_budget\` for details.`;
|
|
1695
|
+
}
|
|
1696
|
+
} catch {}
|
|
1697
|
+
const memorySection = buildMemorySection(results);
|
|
1698
|
+
const safeContext = validator.validate(fitWithinBudget(memorySection, mdSummary, advice, overheadWarning));
|
|
1693
1699
|
return { additionalContext: safeContext };
|
|
1694
1700
|
}
|
|
1701
|
+
function fitWithinBudget(memoryText, mdText, adviceText, overheadText) {
|
|
1702
|
+
const MAX_CHARS2 = 8000;
|
|
1703
|
+
const sections = [
|
|
1704
|
+
{ text: memoryText, priority: 1, minChars: 500 },
|
|
1705
|
+
{ text: mdText, priority: 2, minChars: 200 },
|
|
1706
|
+
{ text: adviceText, priority: 3, minChars: 0 },
|
|
1707
|
+
{ text: overheadText, priority: 4, minChars: 0 }
|
|
1708
|
+
].filter((s) => s.text.length > 0);
|
|
1709
|
+
const totalNeeded = sections.reduce((sum, s) => sum + s.text.length, 0);
|
|
1710
|
+
if (totalNeeded <= MAX_CHARS2) {
|
|
1711
|
+
return sections.map((s) => s.text).join(`
|
|
1712
|
+
|
|
1713
|
+
`);
|
|
1714
|
+
}
|
|
1715
|
+
let remaining = MAX_CHARS2;
|
|
1716
|
+
const allocated = [];
|
|
1717
|
+
sections.sort((a, b) => a.priority - b.priority);
|
|
1718
|
+
for (const section of sections) {
|
|
1719
|
+
if (remaining <= 0)
|
|
1720
|
+
break;
|
|
1721
|
+
if (section.text.length <= remaining) {
|
|
1722
|
+
allocated.push(section.text);
|
|
1723
|
+
remaining -= section.text.length + 2;
|
|
1724
|
+
} else if (remaining >= section.minChars) {
|
|
1725
|
+
allocated.push(section.text.slice(0, remaining));
|
|
1726
|
+
remaining = 0;
|
|
1727
|
+
}
|
|
1728
|
+
}
|
|
1729
|
+
return allocated.join(`
|
|
1730
|
+
|
|
1731
|
+
`);
|
|
1732
|
+
}
|
|
1733
|
+
function buildMemorySection(results) {
|
|
1734
|
+
if (results.length === 0)
|
|
1735
|
+
return "";
|
|
1736
|
+
const lines = ["**Past session context:**"];
|
|
1737
|
+
for (const r of results) {
|
|
1738
|
+
const date = new Date(r.created_at).toLocaleDateString();
|
|
1739
|
+
const files = safeJson3(r.files_touched, []);
|
|
1740
|
+
lines.push(`- [${date}, ${r.project}] ${r.summary.slice(0, 400)}`);
|
|
1741
|
+
if (files.length > 0)
|
|
1742
|
+
lines.push(` Files: ${files.slice(0, 5).join(", ")}`);
|
|
1743
|
+
}
|
|
1744
|
+
return lines.join(`
|
|
1745
|
+
`);
|
|
1746
|
+
}
|
|
1695
1747
|
async function handleSessionEnd(hook, project) {
|
|
1696
1748
|
const store = new SessionStore;
|
|
1697
1749
|
store.completeSession(hook.session_id);
|
|
@@ -1841,7 +1893,7 @@ var log6 = createLogger("proactive-retrieval");
|
|
|
1841
1893
|
var DATA_DIR2 = join6(homedir5(), ".claude-memory-hub");
|
|
1842
1894
|
var PROACTIVE_DIR = join6(DATA_DIR2, "proactive");
|
|
1843
1895
|
var TOOL_CALL_INTERVAL = 15;
|
|
1844
|
-
var MAX_INJECTION_CHARS =
|
|
1896
|
+
var MAX_INJECTION_CHARS = 3000;
|
|
1845
1897
|
function evaluateProactiveInjection(sessionId, toolName, toolInput, toolResponse) {
|
|
1846
1898
|
const state = loadState(sessionId);
|
|
1847
1899
|
state.toolCallCount++;
|
|
@@ -1869,7 +1921,7 @@ function evaluateProactiveInjection(sessionId, toolName, toolInput, toolResponse
|
|
|
1869
1921
|
const lines = [`**Relevant past context** (topic: ${currentTopic}):`];
|
|
1870
1922
|
for (const r of results) {
|
|
1871
1923
|
const date = new Date(r.created_at).toLocaleDateString();
|
|
1872
|
-
lines.push(`- [${date}] ${r.summary.slice(0,
|
|
1924
|
+
lines.push(`- [${date}] ${r.summary.slice(0, 400)}`);
|
|
1873
1925
|
const files = safeJson4(r.files_touched, []);
|
|
1874
1926
|
if (files.length > 0)
|
|
1875
1927
|
lines.push(` Files: ${files.slice(0, 3).join(", ")}`);
|
|
@@ -1717,7 +1717,7 @@ function safeJson(text, fallback) {
|
|
|
1717
1717
|
|
|
1718
1718
|
// src/context/injection-validator.ts
|
|
1719
1719
|
var log5 = createLogger("injection-validator");
|
|
1720
|
-
var MAX_CHARS =
|
|
1720
|
+
var MAX_CHARS = 8000;
|
|
1721
1721
|
|
|
1722
1722
|
class InjectionValidator {
|
|
1723
1723
|
registry;
|
|
@@ -1829,15 +1829,26 @@ class ClaudeMdTracker {
|
|
|
1829
1829
|
tokenCost: r.token_cost
|
|
1830
1830
|
}));
|
|
1831
1831
|
}
|
|
1832
|
-
formatForInjection(entries) {
|
|
1832
|
+
formatForInjection(entries, maxChars) {
|
|
1833
1833
|
if (entries.length === 0)
|
|
1834
1834
|
return "";
|
|
1835
|
-
const
|
|
1835
|
+
const minimal = `CLAUDE.md: ${entries.map((e) => basename2(dirname(e.path)) + "/" + basename2(e.path)).join(", ")}`;
|
|
1836
|
+
if (maxChars !== undefined && maxChars < 200)
|
|
1837
|
+
return minimal;
|
|
1838
|
+
const compactLines = ["**Active CLAUDE.md rules:**"];
|
|
1839
|
+
for (const e of entries) {
|
|
1840
|
+
compactLines.push(`- ${basename2(dirname(e.path))}/${basename2(e.path)} (~${e.tokenCost} tok)`);
|
|
1841
|
+
}
|
|
1842
|
+
const compact = compactLines.join(`
|
|
1843
|
+
`);
|
|
1844
|
+
if (maxChars !== undefined && maxChars < 500)
|
|
1845
|
+
return compact;
|
|
1846
|
+
const fullLines = ["**Active CLAUDE.md rules:**"];
|
|
1836
1847
|
for (const e of entries) {
|
|
1837
1848
|
const headings = e.sections.map((s) => s.heading).join(", ");
|
|
1838
|
-
|
|
1849
|
+
fullLines.push(`- ${basename2(dirname(e.path))}/${basename2(e.path)} (~${e.tokenCost} tok): ${headings || "no sections"}`);
|
|
1839
1850
|
}
|
|
1840
|
-
return
|
|
1851
|
+
return fullLines.join(`
|
|
1841
1852
|
`);
|
|
1842
1853
|
}
|
|
1843
1854
|
computeHash(content) {
|
|
@@ -1955,36 +1966,77 @@ async function handleUserPromptSubmit(hook, project) {
|
|
|
1955
1966
|
plan.recommendations = validator.filterAliveRecommendations(plan.recommendations);
|
|
1956
1967
|
plan.skipped = validator.filterAliveRecommendations(plan.skipped);
|
|
1957
1968
|
const advice = loader.formatContextAdvice(plan);
|
|
1958
|
-
|
|
1959
|
-
if (results.length > 0) {
|
|
1960
|
-
lines.push("**Past session context:**");
|
|
1961
|
-
for (const r of results) {
|
|
1962
|
-
const date = new Date(r.created_at).toLocaleDateString();
|
|
1963
|
-
const files = safeJson3(r.files_touched, []);
|
|
1964
|
-
lines.push(`- [${date}, ${r.project}] ${r.summary.slice(0, 300)}`);
|
|
1965
|
-
if (files.length > 0)
|
|
1966
|
-
lines.push(` Files: ${files.slice(0, 5).join(", ")}`);
|
|
1967
|
-
}
|
|
1968
|
-
}
|
|
1969
|
-
if (advice)
|
|
1970
|
-
lines.push("", advice);
|
|
1969
|
+
let mdSummary = "";
|
|
1971
1970
|
if (hook.cwd) {
|
|
1972
1971
|
try {
|
|
1973
1972
|
const mdTracker = new ClaudeMdTracker;
|
|
1974
1973
|
const mdEntries = mdTracker.scanAndUpdate(hook.cwd, project);
|
|
1975
|
-
|
|
1976
|
-
if (mdSummary)
|
|
1977
|
-
lines.push("", mdSummary);
|
|
1974
|
+
mdSummary = mdTracker.formatForInjection(mdEntries);
|
|
1978
1975
|
const tracker = new ResourceTracker;
|
|
1979
1976
|
for (const entry of mdEntries) {
|
|
1980
1977
|
tracker.trackUsage(hook.session_id, project, "claude_md", entry.path, entry.tokenCost);
|
|
1981
1978
|
}
|
|
1982
1979
|
} catch {}
|
|
1983
1980
|
}
|
|
1984
|
-
|
|
1985
|
-
|
|
1981
|
+
let overheadWarning = "";
|
|
1982
|
+
try {
|
|
1983
|
+
const overhead = await registry.getOverheadReport(project);
|
|
1984
|
+
const unusedTokens = overhead.potential_savings.if_remove_unused_skills + overhead.potential_savings.if_remove_unused_agents;
|
|
1985
|
+
if (unusedTokens > 1e4) {
|
|
1986
|
+
const unusedCount = overhead.usage_analysis.skills_never_used.length + overhead.usage_analysis.agents_never_used.length;
|
|
1987
|
+
overheadWarning = `Note: ${unusedCount} unused resources (~${unusedTokens} listing tok overhead). Run \`memory_context_budget\` for details.`;
|
|
1988
|
+
}
|
|
1989
|
+
} catch {}
|
|
1990
|
+
const memorySection = buildMemorySection(results);
|
|
1991
|
+
const safeContext = validator.validate(fitWithinBudget(memorySection, mdSummary, advice, overheadWarning));
|
|
1986
1992
|
return { additionalContext: safeContext };
|
|
1987
1993
|
}
|
|
1994
|
+
function fitWithinBudget(memoryText, mdText, adviceText, overheadText) {
|
|
1995
|
+
const MAX_CHARS2 = 8000;
|
|
1996
|
+
const sections = [
|
|
1997
|
+
{ text: memoryText, priority: 1, minChars: 500 },
|
|
1998
|
+
{ text: mdText, priority: 2, minChars: 200 },
|
|
1999
|
+
{ text: adviceText, priority: 3, minChars: 0 },
|
|
2000
|
+
{ text: overheadText, priority: 4, minChars: 0 }
|
|
2001
|
+
].filter((s) => s.text.length > 0);
|
|
2002
|
+
const totalNeeded = sections.reduce((sum, s) => sum + s.text.length, 0);
|
|
2003
|
+
if (totalNeeded <= MAX_CHARS2) {
|
|
2004
|
+
return sections.map((s) => s.text).join(`
|
|
2005
|
+
|
|
2006
|
+
`);
|
|
2007
|
+
}
|
|
2008
|
+
let remaining = MAX_CHARS2;
|
|
2009
|
+
const allocated = [];
|
|
2010
|
+
sections.sort((a, b) => a.priority - b.priority);
|
|
2011
|
+
for (const section of sections) {
|
|
2012
|
+
if (remaining <= 0)
|
|
2013
|
+
break;
|
|
2014
|
+
if (section.text.length <= remaining) {
|
|
2015
|
+
allocated.push(section.text);
|
|
2016
|
+
remaining -= section.text.length + 2;
|
|
2017
|
+
} else if (remaining >= section.minChars) {
|
|
2018
|
+
allocated.push(section.text.slice(0, remaining));
|
|
2019
|
+
remaining = 0;
|
|
2020
|
+
}
|
|
2021
|
+
}
|
|
2022
|
+
return allocated.join(`
|
|
2023
|
+
|
|
2024
|
+
`);
|
|
2025
|
+
}
|
|
2026
|
+
function buildMemorySection(results) {
|
|
2027
|
+
if (results.length === 0)
|
|
2028
|
+
return "";
|
|
2029
|
+
const lines = ["**Past session context:**"];
|
|
2030
|
+
for (const r of results) {
|
|
2031
|
+
const date = new Date(r.created_at).toLocaleDateString();
|
|
2032
|
+
const files = safeJson3(r.files_touched, []);
|
|
2033
|
+
lines.push(`- [${date}, ${r.project}] ${r.summary.slice(0, 400)}`);
|
|
2034
|
+
if (files.length > 0)
|
|
2035
|
+
lines.push(` Files: ${files.slice(0, 5).join(", ")}`);
|
|
2036
|
+
}
|
|
2037
|
+
return lines.join(`
|
|
2038
|
+
`);
|
|
2039
|
+
}
|
|
1988
2040
|
async function handleSessionEnd(hook, project) {
|
|
1989
2041
|
const store = new SessionStore;
|
|
1990
2042
|
store.completeSession(hook.session_id);
|
|
@@ -1424,7 +1424,7 @@ function safeJson(text, fallback) {
|
|
|
1424
1424
|
|
|
1425
1425
|
// src/context/injection-validator.ts
|
|
1426
1426
|
var log3 = createLogger("injection-validator");
|
|
1427
|
-
var MAX_CHARS =
|
|
1427
|
+
var MAX_CHARS = 8000;
|
|
1428
1428
|
|
|
1429
1429
|
class InjectionValidator {
|
|
1430
1430
|
registry;
|
|
@@ -1536,15 +1536,26 @@ class ClaudeMdTracker {
|
|
|
1536
1536
|
tokenCost: r.token_cost
|
|
1537
1537
|
}));
|
|
1538
1538
|
}
|
|
1539
|
-
formatForInjection(entries) {
|
|
1539
|
+
formatForInjection(entries, maxChars) {
|
|
1540
1540
|
if (entries.length === 0)
|
|
1541
1541
|
return "";
|
|
1542
|
-
const
|
|
1542
|
+
const minimal = `CLAUDE.md: ${entries.map((e) => basename2(dirname(e.path)) + "/" + basename2(e.path)).join(", ")}`;
|
|
1543
|
+
if (maxChars !== undefined && maxChars < 200)
|
|
1544
|
+
return minimal;
|
|
1545
|
+
const compactLines = ["**Active CLAUDE.md rules:**"];
|
|
1546
|
+
for (const e of entries) {
|
|
1547
|
+
compactLines.push(`- ${basename2(dirname(e.path))}/${basename2(e.path)} (~${e.tokenCost} tok)`);
|
|
1548
|
+
}
|
|
1549
|
+
const compact = compactLines.join(`
|
|
1550
|
+
`);
|
|
1551
|
+
if (maxChars !== undefined && maxChars < 500)
|
|
1552
|
+
return compact;
|
|
1553
|
+
const fullLines = ["**Active CLAUDE.md rules:**"];
|
|
1543
1554
|
for (const e of entries) {
|
|
1544
1555
|
const headings = e.sections.map((s) => s.heading).join(", ");
|
|
1545
|
-
|
|
1556
|
+
fullLines.push(`- ${basename2(dirname(e.path))}/${basename2(e.path)} (~${e.tokenCost} tok): ${headings || "no sections"}`);
|
|
1546
1557
|
}
|
|
1547
|
-
return
|
|
1558
|
+
return fullLines.join(`
|
|
1548
1559
|
`);
|
|
1549
1560
|
}
|
|
1550
1561
|
computeHash(content) {
|
|
@@ -1662,36 +1673,77 @@ async function handleUserPromptSubmit(hook, project) {
|
|
|
1662
1673
|
plan.recommendations = validator.filterAliveRecommendations(plan.recommendations);
|
|
1663
1674
|
plan.skipped = validator.filterAliveRecommendations(plan.skipped);
|
|
1664
1675
|
const advice = loader.formatContextAdvice(plan);
|
|
1665
|
-
|
|
1666
|
-
if (results.length > 0) {
|
|
1667
|
-
lines.push("**Past session context:**");
|
|
1668
|
-
for (const r of results) {
|
|
1669
|
-
const date = new Date(r.created_at).toLocaleDateString();
|
|
1670
|
-
const files = safeJson3(r.files_touched, []);
|
|
1671
|
-
lines.push(`- [${date}, ${r.project}] ${r.summary.slice(0, 300)}`);
|
|
1672
|
-
if (files.length > 0)
|
|
1673
|
-
lines.push(` Files: ${files.slice(0, 5).join(", ")}`);
|
|
1674
|
-
}
|
|
1675
|
-
}
|
|
1676
|
-
if (advice)
|
|
1677
|
-
lines.push("", advice);
|
|
1676
|
+
let mdSummary = "";
|
|
1678
1677
|
if (hook.cwd) {
|
|
1679
1678
|
try {
|
|
1680
1679
|
const mdTracker = new ClaudeMdTracker;
|
|
1681
1680
|
const mdEntries = mdTracker.scanAndUpdate(hook.cwd, project);
|
|
1682
|
-
|
|
1683
|
-
if (mdSummary)
|
|
1684
|
-
lines.push("", mdSummary);
|
|
1681
|
+
mdSummary = mdTracker.formatForInjection(mdEntries);
|
|
1685
1682
|
const tracker = new ResourceTracker;
|
|
1686
1683
|
for (const entry of mdEntries) {
|
|
1687
1684
|
tracker.trackUsage(hook.session_id, project, "claude_md", entry.path, entry.tokenCost);
|
|
1688
1685
|
}
|
|
1689
1686
|
} catch {}
|
|
1690
1687
|
}
|
|
1691
|
-
|
|
1692
|
-
|
|
1688
|
+
let overheadWarning = "";
|
|
1689
|
+
try {
|
|
1690
|
+
const overhead = await registry.getOverheadReport(project);
|
|
1691
|
+
const unusedTokens = overhead.potential_savings.if_remove_unused_skills + overhead.potential_savings.if_remove_unused_agents;
|
|
1692
|
+
if (unusedTokens > 1e4) {
|
|
1693
|
+
const unusedCount = overhead.usage_analysis.skills_never_used.length + overhead.usage_analysis.agents_never_used.length;
|
|
1694
|
+
overheadWarning = `Note: ${unusedCount} unused resources (~${unusedTokens} listing tok overhead). Run \`memory_context_budget\` for details.`;
|
|
1695
|
+
}
|
|
1696
|
+
} catch {}
|
|
1697
|
+
const memorySection = buildMemorySection(results);
|
|
1698
|
+
const safeContext = validator.validate(fitWithinBudget(memorySection, mdSummary, advice, overheadWarning));
|
|
1693
1699
|
return { additionalContext: safeContext };
|
|
1694
1700
|
}
|
|
1701
|
+
function fitWithinBudget(memoryText, mdText, adviceText, overheadText) {
|
|
1702
|
+
const MAX_CHARS2 = 8000;
|
|
1703
|
+
const sections = [
|
|
1704
|
+
{ text: memoryText, priority: 1, minChars: 500 },
|
|
1705
|
+
{ text: mdText, priority: 2, minChars: 200 },
|
|
1706
|
+
{ text: adviceText, priority: 3, minChars: 0 },
|
|
1707
|
+
{ text: overheadText, priority: 4, minChars: 0 }
|
|
1708
|
+
].filter((s) => s.text.length > 0);
|
|
1709
|
+
const totalNeeded = sections.reduce((sum, s) => sum + s.text.length, 0);
|
|
1710
|
+
if (totalNeeded <= MAX_CHARS2) {
|
|
1711
|
+
return sections.map((s) => s.text).join(`
|
|
1712
|
+
|
|
1713
|
+
`);
|
|
1714
|
+
}
|
|
1715
|
+
let remaining = MAX_CHARS2;
|
|
1716
|
+
const allocated = [];
|
|
1717
|
+
sections.sort((a, b) => a.priority - b.priority);
|
|
1718
|
+
for (const section of sections) {
|
|
1719
|
+
if (remaining <= 0)
|
|
1720
|
+
break;
|
|
1721
|
+
if (section.text.length <= remaining) {
|
|
1722
|
+
allocated.push(section.text);
|
|
1723
|
+
remaining -= section.text.length + 2;
|
|
1724
|
+
} else if (remaining >= section.minChars) {
|
|
1725
|
+
allocated.push(section.text.slice(0, remaining));
|
|
1726
|
+
remaining = 0;
|
|
1727
|
+
}
|
|
1728
|
+
}
|
|
1729
|
+
return allocated.join(`
|
|
1730
|
+
|
|
1731
|
+
`);
|
|
1732
|
+
}
|
|
1733
|
+
function buildMemorySection(results) {
|
|
1734
|
+
if (results.length === 0)
|
|
1735
|
+
return "";
|
|
1736
|
+
const lines = ["**Past session context:**"];
|
|
1737
|
+
for (const r of results) {
|
|
1738
|
+
const date = new Date(r.created_at).toLocaleDateString();
|
|
1739
|
+
const files = safeJson3(r.files_touched, []);
|
|
1740
|
+
lines.push(`- [${date}, ${r.project}] ${r.summary.slice(0, 400)}`);
|
|
1741
|
+
if (files.length > 0)
|
|
1742
|
+
lines.push(` Files: ${files.slice(0, 5).join(", ")}`);
|
|
1743
|
+
}
|
|
1744
|
+
return lines.join(`
|
|
1745
|
+
`);
|
|
1746
|
+
}
|
|
1695
1747
|
async function handleSessionEnd(hook, project) {
|
|
1696
1748
|
const store = new SessionStore;
|
|
1697
1749
|
store.completeSession(hook.session_id);
|
|
@@ -2029,7 +2081,7 @@ var log9 = createLogger("proactive-retrieval");
|
|
|
2029
2081
|
var DATA_DIR = join5(homedir4(), ".claude-memory-hub");
|
|
2030
2082
|
var PROACTIVE_DIR = join5(DATA_DIR, "proactive");
|
|
2031
2083
|
var TOOL_CALL_INTERVAL = 15;
|
|
2032
|
-
var MAX_INJECTION_CHARS =
|
|
2084
|
+
var MAX_INJECTION_CHARS = 3000;
|
|
2033
2085
|
function evaluateProactiveInjection(sessionId, toolName, toolInput, toolResponse) {
|
|
2034
2086
|
const state = loadState(sessionId);
|
|
2035
2087
|
state.toolCallCount++;
|
|
@@ -2057,7 +2109,7 @@ function evaluateProactiveInjection(sessionId, toolName, toolInput, toolResponse
|
|
|
2057
2109
|
const lines = [`**Relevant past context** (topic: ${currentTopic}):`];
|
|
2058
2110
|
for (const r of results) {
|
|
2059
2111
|
const date = new Date(r.created_at).toLocaleDateString();
|
|
2060
|
-
lines.push(`- [${date}] ${r.summary.slice(0,
|
|
2112
|
+
lines.push(`- [${date}] ${r.summary.slice(0, 400)}`);
|
|
2061
2113
|
const files = safeJson4(r.files_touched, []);
|
|
2062
2114
|
if (files.length > 0)
|
|
2063
2115
|
lines.push(` Files: ${files.slice(0, 3).join(", ")}`);
|
|
@@ -1424,7 +1424,7 @@ function safeJson(text, fallback) {
|
|
|
1424
1424
|
|
|
1425
1425
|
// src/context/injection-validator.ts
|
|
1426
1426
|
var log3 = createLogger("injection-validator");
|
|
1427
|
-
var MAX_CHARS =
|
|
1427
|
+
var MAX_CHARS = 8000;
|
|
1428
1428
|
|
|
1429
1429
|
class InjectionValidator {
|
|
1430
1430
|
registry;
|
|
@@ -1536,15 +1536,26 @@ class ClaudeMdTracker {
|
|
|
1536
1536
|
tokenCost: r.token_cost
|
|
1537
1537
|
}));
|
|
1538
1538
|
}
|
|
1539
|
-
formatForInjection(entries) {
|
|
1539
|
+
formatForInjection(entries, maxChars) {
|
|
1540
1540
|
if (entries.length === 0)
|
|
1541
1541
|
return "";
|
|
1542
|
-
const
|
|
1542
|
+
const minimal = `CLAUDE.md: ${entries.map((e) => basename2(dirname(e.path)) + "/" + basename2(e.path)).join(", ")}`;
|
|
1543
|
+
if (maxChars !== undefined && maxChars < 200)
|
|
1544
|
+
return minimal;
|
|
1545
|
+
const compactLines = ["**Active CLAUDE.md rules:**"];
|
|
1546
|
+
for (const e of entries) {
|
|
1547
|
+
compactLines.push(`- ${basename2(dirname(e.path))}/${basename2(e.path)} (~${e.tokenCost} tok)`);
|
|
1548
|
+
}
|
|
1549
|
+
const compact = compactLines.join(`
|
|
1550
|
+
`);
|
|
1551
|
+
if (maxChars !== undefined && maxChars < 500)
|
|
1552
|
+
return compact;
|
|
1553
|
+
const fullLines = ["**Active CLAUDE.md rules:**"];
|
|
1543
1554
|
for (const e of entries) {
|
|
1544
1555
|
const headings = e.sections.map((s) => s.heading).join(", ");
|
|
1545
|
-
|
|
1556
|
+
fullLines.push(`- ${basename2(dirname(e.path))}/${basename2(e.path)} (~${e.tokenCost} tok): ${headings || "no sections"}`);
|
|
1546
1557
|
}
|
|
1547
|
-
return
|
|
1558
|
+
return fullLines.join(`
|
|
1548
1559
|
`);
|
|
1549
1560
|
}
|
|
1550
1561
|
computeHash(content) {
|
|
@@ -1662,36 +1673,77 @@ async function handleUserPromptSubmit(hook, project) {
|
|
|
1662
1673
|
plan.recommendations = validator.filterAliveRecommendations(plan.recommendations);
|
|
1663
1674
|
plan.skipped = validator.filterAliveRecommendations(plan.skipped);
|
|
1664
1675
|
const advice = loader.formatContextAdvice(plan);
|
|
1665
|
-
|
|
1666
|
-
if (results.length > 0) {
|
|
1667
|
-
lines.push("**Past session context:**");
|
|
1668
|
-
for (const r of results) {
|
|
1669
|
-
const date = new Date(r.created_at).toLocaleDateString();
|
|
1670
|
-
const files = safeJson3(r.files_touched, []);
|
|
1671
|
-
lines.push(`- [${date}, ${r.project}] ${r.summary.slice(0, 300)}`);
|
|
1672
|
-
if (files.length > 0)
|
|
1673
|
-
lines.push(` Files: ${files.slice(0, 5).join(", ")}`);
|
|
1674
|
-
}
|
|
1675
|
-
}
|
|
1676
|
-
if (advice)
|
|
1677
|
-
lines.push("", advice);
|
|
1676
|
+
let mdSummary = "";
|
|
1678
1677
|
if (hook.cwd) {
|
|
1679
1678
|
try {
|
|
1680
1679
|
const mdTracker = new ClaudeMdTracker;
|
|
1681
1680
|
const mdEntries = mdTracker.scanAndUpdate(hook.cwd, project);
|
|
1682
|
-
|
|
1683
|
-
if (mdSummary)
|
|
1684
|
-
lines.push("", mdSummary);
|
|
1681
|
+
mdSummary = mdTracker.formatForInjection(mdEntries);
|
|
1685
1682
|
const tracker = new ResourceTracker;
|
|
1686
1683
|
for (const entry of mdEntries) {
|
|
1687
1684
|
tracker.trackUsage(hook.session_id, project, "claude_md", entry.path, entry.tokenCost);
|
|
1688
1685
|
}
|
|
1689
1686
|
} catch {}
|
|
1690
1687
|
}
|
|
1691
|
-
|
|
1692
|
-
|
|
1688
|
+
let overheadWarning = "";
|
|
1689
|
+
try {
|
|
1690
|
+
const overhead = await registry.getOverheadReport(project);
|
|
1691
|
+
const unusedTokens = overhead.potential_savings.if_remove_unused_skills + overhead.potential_savings.if_remove_unused_agents;
|
|
1692
|
+
if (unusedTokens > 1e4) {
|
|
1693
|
+
const unusedCount = overhead.usage_analysis.skills_never_used.length + overhead.usage_analysis.agents_never_used.length;
|
|
1694
|
+
overheadWarning = `Note: ${unusedCount} unused resources (~${unusedTokens} listing tok overhead). Run \`memory_context_budget\` for details.`;
|
|
1695
|
+
}
|
|
1696
|
+
} catch {}
|
|
1697
|
+
const memorySection = buildMemorySection(results);
|
|
1698
|
+
const safeContext = validator.validate(fitWithinBudget(memorySection, mdSummary, advice, overheadWarning));
|
|
1693
1699
|
return { additionalContext: safeContext };
|
|
1694
1700
|
}
|
|
1701
|
+
function fitWithinBudget(memoryText, mdText, adviceText, overheadText) {
|
|
1702
|
+
const MAX_CHARS2 = 8000;
|
|
1703
|
+
const sections = [
|
|
1704
|
+
{ text: memoryText, priority: 1, minChars: 500 },
|
|
1705
|
+
{ text: mdText, priority: 2, minChars: 200 },
|
|
1706
|
+
{ text: adviceText, priority: 3, minChars: 0 },
|
|
1707
|
+
{ text: overheadText, priority: 4, minChars: 0 }
|
|
1708
|
+
].filter((s) => s.text.length > 0);
|
|
1709
|
+
const totalNeeded = sections.reduce((sum, s) => sum + s.text.length, 0);
|
|
1710
|
+
if (totalNeeded <= MAX_CHARS2) {
|
|
1711
|
+
return sections.map((s) => s.text).join(`
|
|
1712
|
+
|
|
1713
|
+
`);
|
|
1714
|
+
}
|
|
1715
|
+
let remaining = MAX_CHARS2;
|
|
1716
|
+
const allocated = [];
|
|
1717
|
+
sections.sort((a, b) => a.priority - b.priority);
|
|
1718
|
+
for (const section of sections) {
|
|
1719
|
+
if (remaining <= 0)
|
|
1720
|
+
break;
|
|
1721
|
+
if (section.text.length <= remaining) {
|
|
1722
|
+
allocated.push(section.text);
|
|
1723
|
+
remaining -= section.text.length + 2;
|
|
1724
|
+
} else if (remaining >= section.minChars) {
|
|
1725
|
+
allocated.push(section.text.slice(0, remaining));
|
|
1726
|
+
remaining = 0;
|
|
1727
|
+
}
|
|
1728
|
+
}
|
|
1729
|
+
return allocated.join(`
|
|
1730
|
+
|
|
1731
|
+
`);
|
|
1732
|
+
}
|
|
1733
|
+
function buildMemorySection(results) {
|
|
1734
|
+
if (results.length === 0)
|
|
1735
|
+
return "";
|
|
1736
|
+
const lines = ["**Past session context:**"];
|
|
1737
|
+
for (const r of results) {
|
|
1738
|
+
const date = new Date(r.created_at).toLocaleDateString();
|
|
1739
|
+
const files = safeJson3(r.files_touched, []);
|
|
1740
|
+
lines.push(`- [${date}, ${r.project}] ${r.summary.slice(0, 400)}`);
|
|
1741
|
+
if (files.length > 0)
|
|
1742
|
+
lines.push(` Files: ${files.slice(0, 5).join(", ")}`);
|
|
1743
|
+
}
|
|
1744
|
+
return lines.join(`
|
|
1745
|
+
`);
|
|
1746
|
+
}
|
|
1695
1747
|
async function handleSessionEnd(hook, project) {
|
|
1696
1748
|
const store = new SessionStore;
|
|
1697
1749
|
store.completeSession(hook.session_id);
|
package/package.json
CHANGED