agent-recall-core 3.3.14 → 3.3.16

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.
Files changed (59) hide show
  1. package/README.md +404 -148
  2. package/dist/digest/match.d.ts +31 -0
  3. package/dist/digest/match.d.ts.map +1 -0
  4. package/dist/digest/match.js +132 -0
  5. package/dist/digest/match.js.map +1 -0
  6. package/dist/digest/store.d.ts +42 -0
  7. package/dist/digest/store.d.ts.map +1 -0
  8. package/dist/digest/store.js +296 -0
  9. package/dist/digest/store.js.map +1 -0
  10. package/dist/digest/types.d.ts +106 -0
  11. package/dist/digest/types.d.ts.map +1 -0
  12. package/dist/digest/types.js +22 -0
  13. package/dist/digest/types.js.map +1 -0
  14. package/dist/helpers/journal-files.d.ts.map +1 -1
  15. package/dist/helpers/journal-files.js +34 -15
  16. package/dist/helpers/journal-files.js.map +1 -1
  17. package/dist/index.d.ts +9 -0
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +11 -0
  20. package/dist/index.js.map +1 -1
  21. package/dist/palace/awareness.d.ts.map +1 -1
  22. package/dist/palace/awareness.js +20 -14
  23. package/dist/palace/awareness.js.map +1 -1
  24. package/dist/storage/filelock.d.ts +19 -0
  25. package/dist/storage/filelock.d.ts.map +1 -0
  26. package/dist/storage/filelock.js +85 -0
  27. package/dist/storage/filelock.js.map +1 -0
  28. package/dist/storage/fs-utils.d.ts.map +1 -1
  29. package/dist/storage/fs-utils.js +3 -1
  30. package/dist/storage/fs-utils.js.map +1 -1
  31. package/dist/storage/paths.d.ts +8 -0
  32. package/dist/storage/paths.d.ts.map +1 -1
  33. package/dist/storage/paths.js +18 -0
  34. package/dist/storage/paths.js.map +1 -1
  35. package/dist/storage/session.d.ts +25 -0
  36. package/dist/storage/session.d.ts.map +1 -0
  37. package/dist/storage/session.js +59 -0
  38. package/dist/storage/session.js.map +1 -0
  39. package/dist/tools-logic/digest-read.d.ts +7 -0
  40. package/dist/tools-logic/digest-read.d.ts.map +1 -0
  41. package/dist/tools-logic/digest-read.js +22 -0
  42. package/dist/tools-logic/digest-read.js.map +1 -0
  43. package/dist/tools-logic/digest-recall.d.ts +7 -0
  44. package/dist/tools-logic/digest-recall.d.ts.map +1 -0
  45. package/dist/tools-logic/digest-recall.js +32 -0
  46. package/dist/tools-logic/digest-recall.js.map +1 -0
  47. package/dist/tools-logic/digest-store.d.ts +7 -0
  48. package/dist/tools-logic/digest-store.d.ts.map +1 -0
  49. package/dist/tools-logic/digest-store.js +10 -0
  50. package/dist/tools-logic/digest-store.js.map +1 -0
  51. package/dist/tools-logic/journal-capture.d.ts.map +1 -1
  52. package/dist/tools-logic/journal-capture.js +15 -2
  53. package/dist/tools-logic/journal-capture.js.map +1 -1
  54. package/dist/tools-logic/journal-write.d.ts.map +1 -1
  55. package/dist/tools-logic/journal-write.js +15 -2
  56. package/dist/tools-logic/journal-write.js.map +1 -1
  57. package/dist/types.d.ts +1 -1
  58. package/dist/types.js +1 -1
  59. package/package.json +1 -1
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Digest matching — find cached digests relevant to a query.
3
+ *
4
+ * Scoring formula (weights sum to 1.0):
5
+ * score = keywordOverlap * 0.50 + scopeOverlap * 0.30 + recency * 0.20
6
+ *
7
+ * Recency uses Ebbinghaus forgetting curve with Zipf-adjusted half-life:
8
+ * R = e^(-t / S_eff)
9
+ * S_eff = S * max(1, log2(1 + access_count))
10
+ *
11
+ * Access frequency is integrated into decay rather than added as a bonus.
12
+ * A digest accessed 10× has an effective half-life of ~3.5× longer.
13
+ * A digest accessed 100× has ~6.6× longer. Useful digests persist; stale
14
+ * ones decay naturally. Score normalization is preserved (max = 1.0).
15
+ */
16
+ import { type MatchedDigest } from "./types.js";
17
+ /**
18
+ * Compute keyword overlap ratio between two keyword sets.
19
+ * Returns 0..1 where 1 = perfect overlap.
20
+ */
21
+ export declare function keywordOverlap(a: string[], b: string[]): number;
22
+ export interface FindDigestsOptions {
23
+ includeStale?: boolean;
24
+ includeGlobal?: boolean;
25
+ limit?: number;
26
+ }
27
+ /**
28
+ * Find digests matching a query across project + global stores.
29
+ */
30
+ export declare function findMatchingDigests(query: string, project: string, opts?: FindDigestsOptions): MatchedDigest[];
31
+ //# sourceMappingURL=match.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"match.d.ts","sourceRoot":"","sources":["../../src/digest/match.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAKH,OAAO,EAGL,KAAK,aAAa,EAInB,MAAM,YAAY,CAAC;AAQpB;;;GAGG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,MAAM,EAAE,GAAG,MAAM,CAQ/D;AAmBD,MAAM,WAAW,kBAAkB;IACjC,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,MAAM,EACf,IAAI,CAAC,EAAE,kBAAkB,GACxB,aAAa,EAAE,CA0BjB"}
@@ -0,0 +1,132 @@
1
+ /**
2
+ * Digest matching — find cached digests relevant to a query.
3
+ *
4
+ * Scoring formula (weights sum to 1.0):
5
+ * score = keywordOverlap * 0.50 + scopeOverlap * 0.30 + recency * 0.20
6
+ *
7
+ * Recency uses Ebbinghaus forgetting curve with Zipf-adjusted half-life:
8
+ * R = e^(-t / S_eff)
9
+ * S_eff = S * max(1, log2(1 + access_count))
10
+ *
11
+ * Access frequency is integrated into decay rather than added as a bonus.
12
+ * A digest accessed 10× has an effective half-life of ~3.5× longer.
13
+ * A digest accessed 100× has ~6.6× longer. Useful digests persist; stale
14
+ * ones decay naturally. Score normalization is preserved (max = 1.0).
15
+ */
16
+ import { digestDir, digestGlobalDir } from "../storage/paths.js";
17
+ import { readJsonSafe } from "../storage/fs-utils.js";
18
+ import { extractKeywords } from "../helpers/auto-name.js";
19
+ import { MIN_MATCH_THRESHOLD, DIGEST_HALF_LIFE_DAYS, MAX_EXCERPT_LENGTH, } from "./types.js";
20
+ import * as fs from "node:fs";
21
+ import * as path from "node:path";
22
+ // ---------------------------------------------------------------------------
23
+ // Keyword overlap (exported for use by store.ts dedup check)
24
+ // ---------------------------------------------------------------------------
25
+ /**
26
+ * Compute keyword overlap ratio between two keyword sets.
27
+ * Returns 0..1 where 1 = perfect overlap.
28
+ */
29
+ export function keywordOverlap(a, b) {
30
+ if (a.length === 0 && b.length === 0)
31
+ return 0;
32
+ const setB = new Set(b.map((k) => k.toLowerCase()));
33
+ let matches = 0;
34
+ for (const kw of a) {
35
+ if (setB.has(kw.toLowerCase()))
36
+ matches++;
37
+ }
38
+ return matches / Math.max(a.length, b.length);
39
+ }
40
+ // ---------------------------------------------------------------------------
41
+ // Ebbinghaus decay
42
+ // ---------------------------------------------------------------------------
43
+ /**
44
+ * Ebbinghaus retention: R = e^(-t/S)
45
+ * t = time since creation in days, S = half-life in days.
46
+ */
47
+ function ebbinghausDecay(ageMs, halfLifeDays) {
48
+ const ageDays = ageMs / 86_400_000;
49
+ return Math.exp(-ageDays / halfLifeDays);
50
+ }
51
+ /**
52
+ * Find digests matching a query across project + global stores.
53
+ */
54
+ export function findMatchingDigests(query, project, opts) {
55
+ const includeStale = opts?.includeStale ?? false;
56
+ const includeGlobal = opts?.includeGlobal ?? true;
57
+ const limit = opts?.limit ?? 5;
58
+ const queryKeywords = extractKeywords(query, 8);
59
+ if (queryKeywords.length === 0)
60
+ return [];
61
+ const candidates = [];
62
+ const now = Date.now();
63
+ // Search project digests
64
+ const projectDir = digestDir(project);
65
+ const projectEntries = loadEntries(projectDir);
66
+ scoreEntries(projectEntries, queryKeywords, now, includeStale, projectDir, candidates);
67
+ // Search global digests
68
+ if (includeGlobal) {
69
+ const globalDir = digestGlobalDir();
70
+ const globalEntries = loadEntries(globalDir);
71
+ scoreEntries(globalEntries, queryKeywords, now, includeStale, globalDir, candidates);
72
+ }
73
+ // Sort by score descending, take top N
74
+ candidates.sort((a, b) => b.score - a.score);
75
+ return candidates.slice(0, limit);
76
+ }
77
+ // ---------------------------------------------------------------------------
78
+ // Internal
79
+ // ---------------------------------------------------------------------------
80
+ function loadEntries(dir) {
81
+ const idx = readJsonSafe(path.join(dir, "index.json"));
82
+ return idx?.entries ?? [];
83
+ }
84
+ function scoreEntries(entries, queryKeywords, now, includeStale, dir, out) {
85
+ for (const entry of entries) {
86
+ if (entry.stale && !includeStale)
87
+ continue;
88
+ // Keyword overlap with title+keywords
89
+ const kwOverlap = keywordOverlap(queryKeywords, entry.keywords);
90
+ // Scope overlap — separate signal from scope field
91
+ const scopeKeywords = extractKeywords(entry.scope, 6);
92
+ const scopeOverlap = keywordOverlap(queryKeywords, scopeKeywords);
93
+ // Ebbinghaus recency with Zipf-adjusted half-life.
94
+ // High-access digests decay slower: S_eff = S * max(1, log2(1 + n))
95
+ const ageMs = now - new Date(entry.updated).getTime();
96
+ const effectiveHalfLife = DIGEST_HALF_LIFE_DAYS * Math.max(1, Math.log2(1 + entry.access_count));
97
+ const recency = ebbinghausDecay(ageMs, effectiveHalfLife);
98
+ // Final score — weights sum to 1.0, max score = 1.0
99
+ const score = kwOverlap * 0.50 + scopeOverlap * 0.30 + recency * 0.20;
100
+ if (score < MIN_MATCH_THRESHOLD)
101
+ continue;
102
+ const ageHours = Math.round(ageMs / 3_600_000);
103
+ // Read excerpt from content file (use caller-resolved dir, not entry.project)
104
+ const contentFile = path.join(dir, `${entry.id}.md`);
105
+ let excerpt = "";
106
+ if (fs.existsSync(contentFile)) {
107
+ const raw = fs.readFileSync(contentFile, "utf-8");
108
+ // Skip YAML frontmatter, then find first non-heading paragraph
109
+ const body = raw.replace(/^---[\s\S]*?---\n?/, "").trim();
110
+ const firstPara = body
111
+ .split(/\n\n+/)
112
+ .map((p) => p.trim())
113
+ .find((p) => p.length > 0 && !p.startsWith("#")) ?? body;
114
+ excerpt = firstPara.slice(0, MAX_EXCERPT_LENGTH);
115
+ if (firstPara.length > MAX_EXCERPT_LENGTH)
116
+ excerpt += "...";
117
+ }
118
+ out.push({
119
+ id: entry.id,
120
+ title: entry.title,
121
+ scope: entry.scope,
122
+ score: Math.round(score * 1000) / 1000,
123
+ token_estimate: entry.token_estimate,
124
+ stale: entry.stale,
125
+ stale_reason: entry.stale_reason,
126
+ age_hours: ageHours,
127
+ excerpt,
128
+ project: entry.project,
129
+ });
130
+ }
131
+ }
132
+ //# sourceMappingURL=match.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"match.js","sourceRoot":"","sources":["../../src/digest/match.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Digest store — CRUD operations for pre-computed context cache.
3
+ *
4
+ * All writes are atomic via writeJsonAtomic and protected by filelock.
5
+ * Digests are stored as index.json (metadata) + {id}.md (content).
6
+ */
7
+ import { type DigestEntry, type DigestStoreInput, type DigestStoreResult } from "./types.js";
8
+ /**
9
+ * Store a new digest or refresh an existing one if title overlaps.
10
+ */
11
+ export declare function createDigest(input: DigestStoreInput): DigestStoreResult;
12
+ /**
13
+ * Read a digest's metadata and content.
14
+ */
15
+ export declare function readDigest(project: string, id: string, global?: boolean): {
16
+ meta: DigestEntry | null;
17
+ content: string | null;
18
+ };
19
+ /**
20
+ * List all digests for a project.
21
+ */
22
+ export declare function listDigests(project: string, opts?: {
23
+ stale?: boolean;
24
+ global?: boolean;
25
+ }): DigestEntry[];
26
+ /**
27
+ * Mark a digest as stale (soft-delete).
28
+ */
29
+ export declare function markStale(project: string, id: string, reason: string, global?: boolean): void;
30
+ /**
31
+ * Record an access (bump count + timestamp).
32
+ */
33
+ export declare function recordAccess(project: string, id: string, global?: boolean): void;
34
+ /**
35
+ * Check for expired digests and mark them stale. Returns newly staled entries.
36
+ */
37
+ export declare function checkExpiry(project: string, global?: boolean): DigestEntry[];
38
+ /**
39
+ * Permanently delete stale digests older than threshold.
40
+ */
41
+ export declare function pruneStale(project: string, olderThanDays?: number, global?: boolean): number;
42
+ //# sourceMappingURL=store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/digest/store.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AASH,OAAO,EACL,KAAK,WAAW,EAEhB,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EAKvB,MAAM,YAAY,CAAC;AAqDpB;;GAEG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,gBAAgB,GAAG,iBAAiB,CA4DvE;AAED;;GAEG;AACH,wBAAgB,UAAU,CACxB,OAAO,EAAE,MAAM,EACf,EAAE,EAAE,MAAM,EACV,MAAM,CAAC,EAAE,OAAO,GACf;IAAE,IAAI,EAAE,WAAW,GAAG,IAAI,CAAC;IAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAStD;AAED;;GAEG;AACH,wBAAgB,WAAW,CACzB,OAAO,EAAE,MAAM,EACf,IAAI,CAAC,EAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAA;CAAE,GAC3C,WAAW,EAAE,CAUf;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,GAAG,IAAI,CAW7F;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,GAAG,IAAI,CAWhF;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,GAAG,WAAW,EAAE,CAuB5E;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,aAAa,GAAE,MAAW,EAAE,MAAM,CAAC,EAAE,OAAO,GAAG,MAAM,CAwBhG"}
@@ -0,0 +1,296 @@
1
+ /**
2
+ * Digest store — CRUD operations for pre-computed context cache.
3
+ *
4
+ * All writes are atomic via writeJsonAtomic and protected by filelock.
5
+ * Digests are stored as index.json (metadata) + {id}.md (content).
6
+ */
7
+ import * as fs from "node:fs";
8
+ import * as path from "node:path";
9
+ import * as crypto from "node:crypto";
10
+ import { digestDir, digestGlobalDir } from "../storage/paths.js";
11
+ import { ensureDir, readJsonSafe, writeJsonAtomic } from "../storage/fs-utils.js";
12
+ import { withLock } from "../storage/filelock.js";
13
+ import { extractKeywords } from "../helpers/auto-name.js";
14
+ import { DEFAULT_TTL_HOURS, MAX_DIGESTS_PER_PROJECT, REFRESH_OVERLAP_THRESHOLD, } from "./types.js";
15
+ import { keywordOverlap } from "./match.js";
16
+ // ---------------------------------------------------------------------------
17
+ // Index helpers
18
+ // ---------------------------------------------------------------------------
19
+ /** Valid digest ID format: digest-{timestamp}-{8 hex chars} */
20
+ const ID_PATTERN = /^digest-\d+-[a-f0-9]{8}$/;
21
+ function indexPath(dir) {
22
+ return path.join(dir, "index.json");
23
+ }
24
+ function contentPath(dir, id) {
25
+ if (!ID_PATTERN.test(id)) {
26
+ throw new Error(`Invalid digest id: ${id}`);
27
+ }
28
+ return path.join(dir, `${id}.md`);
29
+ }
30
+ function readIndex(dir) {
31
+ const idx = readJsonSafe(indexPath(dir));
32
+ if (idx)
33
+ return idx;
34
+ return { version: "1.0", updated: new Date().toISOString(), entries: [] };
35
+ }
36
+ function writeIndex(dir, index) {
37
+ index.updated = new Date().toISOString();
38
+ writeJsonAtomic(indexPath(dir), index);
39
+ }
40
+ function generateId(title) {
41
+ const ts = Date.now();
42
+ const hash = crypto.createHash("sha256").update(title + ts).digest("hex").slice(0, 8);
43
+ return `digest-${ts}-${hash}`;
44
+ }
45
+ function computeExpiry(ttlHours) {
46
+ if (ttlHours <= 0)
47
+ return null;
48
+ return new Date(Date.now() + ttlHours * 3600_000).toISOString();
49
+ }
50
+ function estimateTokens(content) {
51
+ // Rough heuristic: ~4 bytes per token for English, ~2.5 for CJK-heavy.
52
+ // Use 3.5 as a blend.
53
+ return Math.ceil(content.length / 3.5);
54
+ }
55
+ // ---------------------------------------------------------------------------
56
+ // Public API
57
+ // ---------------------------------------------------------------------------
58
+ /**
59
+ * Store a new digest or refresh an existing one if title overlaps.
60
+ */
61
+ export function createDigest(input) {
62
+ const project = input.project ?? "unknown";
63
+ const isGlobal = input.global ?? false;
64
+ const dir = isGlobal ? digestGlobalDir() : digestDir(project);
65
+ ensureDir(dir);
66
+ const ttl = input.ttl_hours ?? DEFAULT_TTL_HOURS;
67
+ const keywords = extractKeywords(input.title + " " + input.scope, 8);
68
+ const tokenEst = estimateTokens(input.content);
69
+ const now = new Date().toISOString();
70
+ return withLock(`digest-${isGlobal ? "global" : project}`, () => {
71
+ const index = readIndex(dir);
72
+ // Check for existing digest with high keyword overlap → refresh instead of duplicate
73
+ const existing = findOverlappingEntry(index.entries, keywords);
74
+ if (existing) {
75
+ return refreshDigestInternal(dir, index, existing, input.content, tokenEst, ttl, now);
76
+ }
77
+ // Create new entry
78
+ const id = generateId(input.title);
79
+ const entry = {
80
+ id,
81
+ title: input.title,
82
+ scope: input.scope,
83
+ keywords,
84
+ created: now,
85
+ updated: now,
86
+ expires: computeExpiry(ttl),
87
+ ttl_hours: ttl,
88
+ token_estimate: tokenEst,
89
+ source_agent: input.source_agent ?? "unknown",
90
+ source_query: input.source_query ?? "",
91
+ invalidation: resolveInvalidation(input.invalidation),
92
+ access_count: 0,
93
+ last_accessed: now,
94
+ stale: false,
95
+ project: isGlobal ? "__global__" : project,
96
+ };
97
+ // Enforce max digests: prune oldest stale, then oldest by access
98
+ if (index.entries.length >= MAX_DIGESTS_PER_PROJECT) {
99
+ pruneOne(dir, index);
100
+ }
101
+ // Write content file first — orphaned .md is harmless, dangling index entry is not
102
+ fs.writeFileSync(contentPath(dir, id), input.content, "utf-8");
103
+ index.entries.push(entry);
104
+ writeIndex(dir, index);
105
+ return {
106
+ success: true,
107
+ id,
108
+ action: "created",
109
+ token_estimate: tokenEst,
110
+ expires: entry.expires,
111
+ project: entry.project,
112
+ };
113
+ });
114
+ }
115
+ /**
116
+ * Read a digest's metadata and content.
117
+ */
118
+ export function readDigest(project, id, global) {
119
+ if (!ID_PATTERN.test(id))
120
+ return { meta: null, content: null };
121
+ const dir = global ? digestGlobalDir() : digestDir(project);
122
+ const index = readIndex(dir);
123
+ const meta = index.entries.find((e) => e.id === id) ?? null;
124
+ if (!meta)
125
+ return { meta: null, content: null };
126
+ const cPath = contentPath(dir, id);
127
+ const content = fs.existsSync(cPath) ? fs.readFileSync(cPath, "utf-8") : null;
128
+ return { meta, content };
129
+ }
130
+ /**
131
+ * List all digests for a project.
132
+ */
133
+ export function listDigests(project, opts) {
134
+ const dir = opts?.global ? digestGlobalDir() : digestDir(project);
135
+ const index = readIndex(dir);
136
+ let entries = index.entries;
137
+ if (opts?.stale === false) {
138
+ entries = entries.filter((e) => !e.stale);
139
+ }
140
+ else if (opts?.stale === true) {
141
+ entries = entries.filter((e) => e.stale);
142
+ }
143
+ return entries;
144
+ }
145
+ /**
146
+ * Mark a digest as stale (soft-delete).
147
+ */
148
+ export function markStale(project, id, reason, global) {
149
+ const dir = global ? digestGlobalDir() : digestDir(project);
150
+ withLock(`digest-${global ? "global" : project}`, () => {
151
+ const index = readIndex(dir);
152
+ const entry = index.entries.find((e) => e.id === id);
153
+ if (entry) {
154
+ entry.stale = true;
155
+ entry.stale_reason = reason;
156
+ writeIndex(dir, index);
157
+ }
158
+ });
159
+ }
160
+ /**
161
+ * Record an access (bump count + timestamp).
162
+ */
163
+ export function recordAccess(project, id, global) {
164
+ const dir = global ? digestGlobalDir() : digestDir(project);
165
+ withLock(`digest-${global ? "global" : project}`, () => {
166
+ const index = readIndex(dir);
167
+ const entry = index.entries.find((e) => e.id === id);
168
+ if (entry) {
169
+ entry.access_count++;
170
+ entry.last_accessed = new Date().toISOString();
171
+ writeIndex(dir, index);
172
+ }
173
+ });
174
+ }
175
+ /**
176
+ * Check for expired digests and mark them stale. Returns newly staled entries.
177
+ */
178
+ export function checkExpiry(project, global) {
179
+ const dir = global ? digestGlobalDir() : digestDir(project);
180
+ const newlyStale = [];
181
+ withLock(`digest-${global ? "global" : project}`, () => {
182
+ const index = readIndex(dir);
183
+ const now = Date.now();
184
+ for (const entry of index.entries) {
185
+ if (entry.stale)
186
+ continue;
187
+ if (entry.expires && new Date(entry.expires).getTime() < now) {
188
+ entry.stale = true;
189
+ entry.stale_reason = "TTL expired";
190
+ newlyStale.push(entry);
191
+ }
192
+ }
193
+ if (newlyStale.length > 0) {
194
+ writeIndex(dir, index);
195
+ }
196
+ });
197
+ return newlyStale;
198
+ }
199
+ /**
200
+ * Permanently delete stale digests older than threshold.
201
+ */
202
+ export function pruneStale(project, olderThanDays = 30, global) {
203
+ const dir = global ? digestGlobalDir() : digestDir(project);
204
+ let pruned = 0;
205
+ const cutoff = Date.now() - olderThanDays * 86_400_000;
206
+ withLock(`digest-${global ? "global" : project}`, () => {
207
+ const index = readIndex(dir);
208
+ const keep = [];
209
+ for (const entry of index.entries) {
210
+ if (entry.stale && new Date(entry.updated).getTime() < cutoff) {
211
+ const cp = contentPath(dir, entry.id);
212
+ if (fs.existsSync(cp))
213
+ fs.unlinkSync(cp);
214
+ pruned++;
215
+ }
216
+ else {
217
+ keep.push(entry);
218
+ }
219
+ }
220
+ index.entries = keep;
221
+ writeIndex(dir, index);
222
+ });
223
+ return pruned;
224
+ }
225
+ // ---------------------------------------------------------------------------
226
+ // Internal helpers
227
+ // ---------------------------------------------------------------------------
228
+ function findOverlappingEntry(entries, keywords) {
229
+ let best;
230
+ let bestScore = 0;
231
+ for (const entry of entries) {
232
+ if (entry.stale)
233
+ continue;
234
+ const score = keywordOverlap(keywords, entry.keywords);
235
+ if (score > REFRESH_OVERLAP_THRESHOLD && score > bestScore) {
236
+ bestScore = score;
237
+ best = entry;
238
+ }
239
+ }
240
+ return best;
241
+ }
242
+ function refreshDigestInternal(dir, index, existing, newContent, tokenEst, callerTtl, now) {
243
+ existing.updated = now;
244
+ existing.token_estimate = tokenEst;
245
+ existing.access_count++;
246
+ existing.last_accessed = now;
247
+ existing.stale = false;
248
+ existing.stale_reason = undefined;
249
+ // Caller may provide a different TTL on refresh — respect it
250
+ existing.ttl_hours = callerTtl;
251
+ existing.expires = computeExpiry(callerTtl);
252
+ // Write content first — safer ordering
253
+ fs.writeFileSync(contentPath(dir, existing.id), newContent, "utf-8");
254
+ writeIndex(dir, index);
255
+ return {
256
+ success: true,
257
+ id: existing.id,
258
+ action: "refreshed",
259
+ token_estimate: tokenEst,
260
+ expires: existing.expires,
261
+ project: existing.project,
262
+ };
263
+ }
264
+ function resolveInvalidation(partial) {
265
+ return {
266
+ strategy: partial?.strategy ?? "ttl",
267
+ content_hash: partial?.content_hash,
268
+ git_commit: partial?.git_commit,
269
+ watched_paths: partial?.watched_paths,
270
+ };
271
+ }
272
+ function pruneOne(dir, index) {
273
+ // Prefer removing stale entries first
274
+ const staleIdx = index.entries.findIndex((e) => e.stale);
275
+ if (staleIdx >= 0) {
276
+ const removed = index.entries.splice(staleIdx, 1)[0];
277
+ const cp = contentPath(dir, removed.id);
278
+ if (fs.existsSync(cp))
279
+ fs.unlinkSync(cp);
280
+ return;
281
+ }
282
+ // Otherwise remove least-accessed entry (Zipf: low-access entries rarely hit again)
283
+ let minAccess = Infinity;
284
+ let minIdx = 0;
285
+ for (let i = 0; i < index.entries.length; i++) {
286
+ if (index.entries[i].access_count < minAccess) {
287
+ minAccess = index.entries[i].access_count;
288
+ minIdx = i;
289
+ }
290
+ }
291
+ const removed = index.entries.splice(minIdx, 1)[0];
292
+ const cp = contentPath(dir, removed.id);
293
+ if (fs.existsSync(cp))
294
+ fs.unlinkSync(cp);
295
+ }
296
+ //# sourceMappingURL=store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/digest/store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Digest store types — pre-computed context cache for token saving.
3
+ *
4
+ * A digest is a stored computation result (e.g., codebase analysis, business
5
+ * logic extraction) that can be recalled instead of re-computing.
6
+ */
7
+ export type InvalidationStrategy = "ttl" | "content-hash" | "git-commit" | "manual";
8
+ export interface DigestInvalidation {
9
+ strategy: InvalidationStrategy;
10
+ /** SHA-256 of watched files at creation time. */
11
+ content_hash?: string;
12
+ /** Git HEAD commit hash at creation time. */
13
+ git_commit?: string;
14
+ /** File paths to watch — if any change, digest is stale. */
15
+ watched_paths?: string[];
16
+ }
17
+ export interface DigestEntry {
18
+ id: string;
19
+ title: string;
20
+ scope: string;
21
+ keywords: string[];
22
+ created: string;
23
+ updated: string;
24
+ expires: string | null;
25
+ ttl_hours: number;
26
+ token_estimate: number;
27
+ source_agent: string;
28
+ source_query: string;
29
+ invalidation: DigestInvalidation;
30
+ access_count: number;
31
+ last_accessed: string;
32
+ stale: boolean;
33
+ stale_reason?: string;
34
+ project: string;
35
+ }
36
+ export interface DigestIndex {
37
+ version: string;
38
+ updated: string;
39
+ entries: DigestEntry[];
40
+ }
41
+ export interface DigestStoreInput {
42
+ title: string;
43
+ scope: string;
44
+ content: string;
45
+ source_agent?: string;
46
+ source_query?: string;
47
+ ttl_hours?: number;
48
+ invalidation?: Partial<DigestInvalidation>;
49
+ project?: string;
50
+ global?: boolean;
51
+ }
52
+ export interface DigestStoreResult {
53
+ success: boolean;
54
+ id: string;
55
+ action: "created" | "refreshed";
56
+ token_estimate: number;
57
+ expires: string | null;
58
+ project: string;
59
+ }
60
+ export interface DigestRecallInput {
61
+ query: string;
62
+ project?: string;
63
+ include_stale?: boolean;
64
+ include_global?: boolean;
65
+ limit?: number;
66
+ }
67
+ export interface MatchedDigest {
68
+ id: string;
69
+ title: string;
70
+ scope: string;
71
+ score: number;
72
+ token_estimate: number;
73
+ stale: boolean;
74
+ stale_reason?: string;
75
+ age_hours: number;
76
+ excerpt: string;
77
+ project: string;
78
+ }
79
+ export interface DigestRecallResult {
80
+ query: string;
81
+ digests: MatchedDigest[];
82
+ result_count: number;
83
+ tokens_saved_estimate: number;
84
+ }
85
+ export interface DigestReadInput {
86
+ digest_id: string;
87
+ project?: string;
88
+ }
89
+ export interface DigestReadResult {
90
+ success: boolean;
91
+ meta: DigestEntry | null;
92
+ content: string | null;
93
+ }
94
+ /** Default TTL in hours (7 days). */
95
+ export declare const DEFAULT_TTL_HOURS = 168;
96
+ /** Max digests per project before pruning oldest stale. */
97
+ export declare const MAX_DIGESTS_PER_PROJECT = 50;
98
+ /** Min keyword overlap score to consider a match. */
99
+ export declare const MIN_MATCH_THRESHOLD = 0.2;
100
+ /** Overlap ratio above which a store becomes a refresh (dedup). */
101
+ export declare const REFRESH_OVERLAP_THRESHOLD = 0.6;
102
+ /** Ebbinghaus decay constant for digests in days. R = e^(-t/S). S=30 (~37% retention at 30 days). */
103
+ export declare const DIGEST_HALF_LIFE_DAYS = 30;
104
+ /** Max excerpt length in recall results. */
105
+ export declare const MAX_EXCERPT_LENGTH = 300;
106
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/digest/types.ts"],"names":[],"mappings":"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"}