eklavya 1.18.2 → 1.19.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/dist/assets/dashboard.html +559 -14
- package/dist/assets/tutor/references/focus-and-level.md +29 -0
- package/dist/cli.js +648 -20
- package/dist/cli.js.map +1 -1
- package/dist/config-path.js +156 -0
- package/dist/config-path.js.map +1 -0
- package/dist/config.js +231 -2
- package/dist/config.js.map +1 -1
- package/dist/dashboard.js +355 -0
- package/dist/dashboard.js.map +1 -1
- package/dist/eval/retrieval-score.js +77 -0
- package/dist/eval/retrieval-score.js.map +1 -0
- package/dist/hooks/capture-tool.js +107 -0
- package/dist/hooks/capture-tool.js.map +1 -0
- package/dist/hooks/checkpoint-quiz.js +5 -6
- package/dist/hooks/checkpoint-quiz.js.map +1 -1
- package/dist/hooks/lib.js +7 -33
- package/dist/hooks/lib.js.map +1 -1
- package/dist/hooks/memory-lib.js +181 -0
- package/dist/hooks/memory-lib.js.map +1 -0
- package/dist/hooks/prompt-submit-nudge.js +56 -20
- package/dist/hooks/prompt-submit-nudge.js.map +1 -1
- package/dist/hooks/session-start.js +73 -74
- package/dist/hooks/session-start.js.map +1 -1
- package/dist/hooks/stop-quiz-check.js +44 -7
- package/dist/hooks/stop-quiz-check.js.map +1 -1
- package/dist/install.js +37 -0
- package/dist/install.js.map +1 -1
- package/dist/memory/capture.js +123 -0
- package/dist/memory/capture.js.map +1 -0
- package/dist/memory/code.js +186 -0
- package/dist/memory/code.js.map +1 -0
- package/dist/memory/collections.js +87 -0
- package/dist/memory/collections.js.map +1 -0
- package/dist/memory/embed.js +81 -0
- package/dist/memory/embed.js.map +1 -0
- package/dist/memory/hosts.js +71 -0
- package/dist/memory/hosts.js.map +1 -0
- package/dist/memory/identity.js +74 -0
- package/dist/memory/identity.js.map +1 -0
- package/dist/memory/import.js +916 -0
- package/dist/memory/import.js.map +1 -0
- package/dist/memory/learning.js +148 -0
- package/dist/memory/learning.js.map +1 -0
- package/dist/memory/notify.js +220 -0
- package/dist/memory/notify.js.map +1 -0
- package/dist/memory/privacy.js +117 -0
- package/dist/memory/privacy.js.map +1 -0
- package/dist/memory/provider.js +155 -0
- package/dist/memory/provider.js.map +1 -0
- package/dist/memory/recall.js +327 -0
- package/dist/memory/recall.js.map +1 -0
- package/dist/memory/replay.js +174 -0
- package/dist/memory/replay.js.map +1 -0
- package/dist/memory/search.js +143 -0
- package/dist/memory/search.js.map +1 -0
- package/dist/memory/spool.js +93 -0
- package/dist/memory/spool.js.map +1 -0
- package/dist/memory/store.js +399 -0
- package/dist/memory/store.js.map +1 -0
- package/dist/memory/summarize.js +178 -0
- package/dist/memory/summarize.js.map +1 -0
- package/dist/memory/sync.js +595 -0
- package/dist/memory/sync.js.map +1 -0
- package/dist/memory/tokens.js +55 -0
- package/dist/memory/tokens.js.map +1 -0
- package/dist/memory/worker.js +207 -0
- package/dist/memory/worker.js.map +1 -0
- package/dist/migrations/009_memory.sql +226 -0
- package/dist/migrations/010_import.sql +26 -0
- package/dist/migrations/011_sync.sql +76 -0
- package/dist/migrations/012_job_backoff.sql +13 -0
- package/dist/migrations/013_batch_provenance.sql +18 -0
- package/dist/migrations/014_batch_events_index.sql +18 -0
- package/dist/plugin/.claude-plugin/plugin.json +1 -1
- package/dist/plugin/agents/tutor.md +3 -1
- package/dist/plugin/hooks/CLAUDE.md +30 -6
- package/dist/plugin/hooks/hooks.json +10 -0
- package/dist/plugin/skills/CLAUDE.md +17 -7
- package/dist/plugin/skills/memory/SKILL.md +88 -0
- package/dist/plugin/skills/setup/SKILL.md +16 -2
- package/dist/plugin/skills/tutor/references/focus-and-level.md +29 -0
- package/dist/time.js +41 -0
- package/dist/time.js.map +1 -0
- package/dist/tools/code_tools.js +73 -0
- package/dist/tools/code_tools.js.map +1 -0
- package/dist/tools/collection_tools.js +92 -0
- package/dist/tools/collection_tools.js.map +1 -0
- package/dist/tools/config_tools.js +104 -1
- package/dist/tools/config_tools.js.map +1 -1
- package/dist/tools/index.js +15 -0
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/memory_read_tools.js +277 -0
- package/dist/tools/memory_read_tools.js.map +1 -0
- package/dist/tools/memory_write_tools.js +115 -0
- package/dist/tools/memory_write_tools.js.map +1 -0
- package/dist/user-skill/eklavya/SKILL.md +64 -8
- package/package.json +2 -1
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
import { nowIso } from '../time.js';
|
|
3
|
+
import { embedLocal, LOCAL_DIM, LOCAL_EMBEDDER, toBlob } from './embed.js';
|
|
4
|
+
import { entryUid, receiptUid } from './identity.js';
|
|
5
|
+
/**
|
|
6
|
+
* Appends one accepted event. Returns the row id and whether it was new.
|
|
7
|
+
*
|
|
8
|
+
* `INSERT OR IGNORE` on `event_uid` is the convergence point for hook capture
|
|
9
|
+
* and transcript replay (PRD CAP-02): whichever arrives second is a no-op, and
|
|
10
|
+
* neither needs to know the other exists.
|
|
11
|
+
*/
|
|
12
|
+
export function appendEvent(db, input) {
|
|
13
|
+
const occurredAt = input.occurredAt ?? nowIso();
|
|
14
|
+
const info = db
|
|
15
|
+
.prepare(`INSERT OR IGNORE INTO evidence_events
|
|
16
|
+
(event_uid, project, checkout, session_id, agent_id, host, source, kind, tool,
|
|
17
|
+
title, body, files, occurred_at, received_at, redacted)
|
|
18
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
19
|
+
.run(input.eventUid, input.project, input.checkout ?? null, input.sessionId, input.agentId ?? null, input.host ?? 'claude-code', input.source ?? 'hook', input.kind, input.tool ?? null, input.title ?? null, input.body, input.files && input.files.length ? JSON.stringify(input.files) : null, occurredAt, nowIso(), input.redacted ? 1 : 0);
|
|
20
|
+
if (info.changes > 0)
|
|
21
|
+
return { id: Number(info.lastInsertRowid), inserted: true };
|
|
22
|
+
const existing = db
|
|
23
|
+
.prepare('SELECT id FROM evidence_events WHERE event_uid = ?')
|
|
24
|
+
.get(input.eventUid);
|
|
25
|
+
return { id: existing?.id ?? 0, inserted: false };
|
|
26
|
+
}
|
|
27
|
+
export function eventsForSession(db, sessionId, limit = 200) {
|
|
28
|
+
return db
|
|
29
|
+
.prepare('SELECT * FROM evidence_events WHERE session_id = ? ORDER BY occurred_at DESC LIMIT ?')
|
|
30
|
+
.all(sessionId, limit);
|
|
31
|
+
}
|
|
32
|
+
export function pendingEventCount(db, project) {
|
|
33
|
+
const row = project
|
|
34
|
+
? db
|
|
35
|
+
.prepare("SELECT COUNT(*) AS n FROM evidence_events WHERE status = 'accepted' AND project = ?")
|
|
36
|
+
.get(project)
|
|
37
|
+
: db.prepare("SELECT COUNT(*) AS n FROM evidence_events WHERE status = 'accepted'").get();
|
|
38
|
+
return row.n;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Claims the accepted events of one session into an immutable batch and queues
|
|
42
|
+
* a job for it. The claim and the queue are one transaction: a batch with no
|
|
43
|
+
* job is work that silently never happens, and a job with no batch is a request
|
|
44
|
+
* that cannot be reconstructed (PRD DATA-03).
|
|
45
|
+
*/
|
|
46
|
+
export function batchSession(db, opts) {
|
|
47
|
+
const max = opts.maxEvents ?? 60;
|
|
48
|
+
return db.transaction(() => {
|
|
49
|
+
const events = db
|
|
50
|
+
.prepare(`SELECT id, length(body) AS bytes FROM evidence_events
|
|
51
|
+
WHERE status = 'accepted' AND session_id = ? AND project = ?
|
|
52
|
+
ORDER BY occurred_at LIMIT ?`)
|
|
53
|
+
.all(opts.sessionId, opts.project, max);
|
|
54
|
+
if (events.length === 0)
|
|
55
|
+
return null;
|
|
56
|
+
const bytes = events.reduce((sum, e) => sum + e.bytes, 0);
|
|
57
|
+
const batchId = Number(db
|
|
58
|
+
.prepare(`INSERT INTO memory_batches (project, session_id, reason, event_count, byte_count, created_at)
|
|
59
|
+
VALUES (?, ?, ?, ?, ?, ?)`)
|
|
60
|
+
.run(opts.project, opts.sessionId, opts.reason, events.length, bytes, nowIso()).lastInsertRowid);
|
|
61
|
+
const claim = db.prepare("UPDATE evidence_events SET status = 'batched', batch_id = ? WHERE id = ?");
|
|
62
|
+
for (const e of events)
|
|
63
|
+
claim.run(batchId, e.id);
|
|
64
|
+
const jobId = Number(db
|
|
65
|
+
.prepare(`INSERT INTO memory_jobs (batch_id, kind, status, created_at, updated_at)
|
|
66
|
+
VALUES (?, 'summarize', 'pending', ?, ?)`)
|
|
67
|
+
.run(batchId, nowIso(), nowIso()).lastInsertRowid);
|
|
68
|
+
return { batchId, jobId, eventCount: events.length };
|
|
69
|
+
})();
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Takes the next runnable job under a lease.
|
|
73
|
+
*
|
|
74
|
+
* The lease, not a process list, is what makes "one worker per job" true. A
|
|
75
|
+
* crashed worker's lease expires and the job becomes claimable again; a worker
|
|
76
|
+
* that merely hangs cannot corrupt anything, because completion is a conditional
|
|
77
|
+
* update on the lease it still holds.
|
|
78
|
+
*
|
|
79
|
+
* `next_attempt` is the second gate: a job that just failed transiently is
|
|
80
|
+
* still `pending`, and without it the next hook — seconds later — would claim
|
|
81
|
+
* it again and spend another attempt on a provider that has not recovered.
|
|
82
|
+
*/
|
|
83
|
+
export function claimJob(db, owner, leaseSeconds = 120) {
|
|
84
|
+
const now = nowIso();
|
|
85
|
+
const until = new Date(Date.now() + leaseSeconds * 1000).toISOString();
|
|
86
|
+
return db.transaction(() => {
|
|
87
|
+
const job = db
|
|
88
|
+
.prepare(`SELECT * FROM memory_jobs
|
|
89
|
+
WHERE (next_attempt IS NULL OR next_attempt <= ?)
|
|
90
|
+
AND (status = 'pending' OR (status = 'claimed' AND (lease_until IS NULL OR lease_until < ?)))
|
|
91
|
+
ORDER BY id LIMIT 1`)
|
|
92
|
+
.get(now, now);
|
|
93
|
+
if (!job)
|
|
94
|
+
return null;
|
|
95
|
+
db.prepare(`UPDATE memory_jobs
|
|
96
|
+
SET status = 'claimed', attempts = attempts + 1, lease_owner = ?, lease_until = ?, updated_at = ?
|
|
97
|
+
WHERE id = ?`).run(owner, until, now, job.id);
|
|
98
|
+
return { ...job, status: 'claimed', attempts: job.attempts + 1, lease_owner: owner, lease_until: until };
|
|
99
|
+
})();
|
|
100
|
+
}
|
|
101
|
+
export function finishJob(db, jobId, owner) {
|
|
102
|
+
db.prepare(`UPDATE memory_jobs SET status = 'done', lease_owner = NULL, lease_until = NULL, updated_at = ?
|
|
103
|
+
WHERE id = ? AND lease_owner = ?`).run(nowIso(), jobId, owner);
|
|
104
|
+
}
|
|
105
|
+
/** First retry floor, doubled per attempt up to `RETRY_CEILING_MS`. */
|
|
106
|
+
const RETRY_BASE_MS = 30_000;
|
|
107
|
+
/** Five minutes. Past that the queue is waiting on a human, not on a blip. */
|
|
108
|
+
const RETRY_CEILING_MS = 300_000;
|
|
109
|
+
/**
|
|
110
|
+
* Records a failure and decides whether — and when — the job may run again.
|
|
111
|
+
*
|
|
112
|
+
* The classification is the point. A timeout should be retried; a rejected API
|
|
113
|
+
* key should not be retried a thousand times at the developer's expense, and a
|
|
114
|
+
* batch whose provider output will never parse should stop rather than spin.
|
|
115
|
+
*
|
|
116
|
+
* A retryable failure carries a floor as well as a verdict. `processPending`
|
|
117
|
+
* runs at every session seam, so "pending" alone means "claimable by whatever
|
|
118
|
+
* happens next", and a provider having a bad minute would take all five
|
|
119
|
+
* attempts inside it. Half the window is fixed and half is jitter, because
|
|
120
|
+
* several checkouts sharing one database and one dead provider otherwise wake
|
|
121
|
+
* up in the same second and retry as a group.
|
|
122
|
+
*/
|
|
123
|
+
export function failJob(db, jobId, owner, errorClass, message, maxAttempts = 5, random = Math.random) {
|
|
124
|
+
const row = db.prepare('SELECT attempts FROM memory_jobs WHERE id = ?').get(jobId);
|
|
125
|
+
const attempts = row?.attempts ?? 0;
|
|
126
|
+
const terminal = errorClass === 'permanent' || errorClass === 'malformed' || attempts >= maxAttempts;
|
|
127
|
+
const paused = errorClass === 'auth' || errorClass === 'quota';
|
|
128
|
+
const window = Math.min(RETRY_CEILING_MS, RETRY_BASE_MS * 2 ** Math.max(0, attempts - 1));
|
|
129
|
+
db.prepare(`UPDATE memory_jobs
|
|
130
|
+
SET status = ?, last_error = ?, error_class = ?, next_attempt = ?,
|
|
131
|
+
lease_owner = NULL, lease_until = NULL, updated_at = ?
|
|
132
|
+
WHERE id = ? AND lease_owner = ?`).run(terminal ? 'failed' : paused ? 'paused' : 'pending', message.slice(0, 500), errorClass, terminal || paused
|
|
133
|
+
? null
|
|
134
|
+
: new Date(Date.now() + window / 2 + random() * (window / 2)).toISOString(), nowIso(), jobId, owner);
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Puts every paused job back in the queue, and returns how many moved.
|
|
138
|
+
*
|
|
139
|
+
* `failJob` parks an auth or quota failure at 'paused' and nothing else ever
|
|
140
|
+
* moves it, so this is the only way out — deliberately, and deliberately not
|
|
141
|
+
* automatic. Running `eklavya memory process` is the developer saying they have
|
|
142
|
+
* repaired the credential; a hook doing it on their behalf would re-spend a
|
|
143
|
+
* rejected key at every session seam and never say why it stopped again.
|
|
144
|
+
*
|
|
145
|
+
* The attempt count resets with it: a queue paused on its fifth attempt is one
|
|
146
|
+
* that would otherwise be marked permanently failed by the first call after the
|
|
147
|
+
* repair, which is the same dead end with a different label.
|
|
148
|
+
*/
|
|
149
|
+
export function resumePaused(db) {
|
|
150
|
+
return db
|
|
151
|
+
.prepare(`UPDATE memory_jobs
|
|
152
|
+
SET status = 'pending', attempts = 0, next_attempt = NULL,
|
|
153
|
+
lease_owner = NULL, lease_until = NULL, updated_at = ?
|
|
154
|
+
WHERE status = 'paused'`)
|
|
155
|
+
.run(nowIso()).changes;
|
|
156
|
+
}
|
|
157
|
+
export function batchEvents(db, batchId) {
|
|
158
|
+
return db
|
|
159
|
+
.prepare('SELECT * FROM evidence_events WHERE batch_id = ? ORDER BY occurred_at')
|
|
160
|
+
.all(batchId);
|
|
161
|
+
}
|
|
162
|
+
export function batchById(db, batchId) {
|
|
163
|
+
return db.prepare('SELECT id, project, session_id FROM memory_batches WHERE id = ?').get(batchId);
|
|
164
|
+
}
|
|
165
|
+
/** Writes an entry, its links, its tags and its vector in one transaction. */
|
|
166
|
+
export function insertEntry(db, input) {
|
|
167
|
+
const occurredAt = input.occurredAt ?? nowIso();
|
|
168
|
+
const uid = input.entryUid ??
|
|
169
|
+
entryUid({
|
|
170
|
+
project: input.project,
|
|
171
|
+
title: input.title,
|
|
172
|
+
occurredAt,
|
|
173
|
+
salt: String(input.batchId ?? crypto.randomUUID()),
|
|
174
|
+
});
|
|
175
|
+
return db.transaction(() => {
|
|
176
|
+
const existing = db.prepare('SELECT id FROM memory_entries WHERE entry_uid = ?').get(uid);
|
|
177
|
+
if (existing)
|
|
178
|
+
return existing.id;
|
|
179
|
+
const id = Number(db
|
|
180
|
+
.prepare(`INSERT INTO memory_entries
|
|
181
|
+
(entry_uid, project, session_id, batch_id, kind, type, title, narrative, facts, files,
|
|
182
|
+
generator, confidence, occurred_at, created_at, import_source)
|
|
183
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
184
|
+
.run(uid, input.project, input.sessionId ?? null, input.batchId ?? null, input.kind ?? 'observation', input.type ?? null, input.title, input.narrative ?? '', input.facts?.length ? JSON.stringify(input.facts) : null, input.files?.length ? JSON.stringify(input.files) : null, input.generator ?? 'local-v1', input.confidence ?? null, occurredAt, nowIso(), input.importSource ?? null).lastInsertRowid);
|
|
185
|
+
const tag = db.prepare('INSERT OR IGNORE INTO memory_entry_tags (entry_id, tag) VALUES (?, ?)');
|
|
186
|
+
for (const t of input.tags ?? [])
|
|
187
|
+
tag.run(id, t.toLowerCase());
|
|
188
|
+
const link = db.prepare('INSERT OR IGNORE INTO memory_entry_events (entry_id, event_id) VALUES (?, ?)');
|
|
189
|
+
for (const eventId of input.eventIds ?? [])
|
|
190
|
+
link.run(id, eventId);
|
|
191
|
+
indexVector(db, id, vectorText(input));
|
|
192
|
+
return id;
|
|
193
|
+
})();
|
|
194
|
+
}
|
|
195
|
+
function vectorText(input) {
|
|
196
|
+
return [input.title, input.narrative ?? '', (input.facts ?? []).join(' '), (input.files ?? []).join(' ')].join('\n');
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Rewrites an entry's content in place and re-indexes it.
|
|
200
|
+
*
|
|
201
|
+
* The one entry that legitimately changes is the session summary: it rolls up a
|
|
202
|
+
* session that is still running, so every seam has more to say than the last.
|
|
203
|
+
* A correction still supersedes rather than edits (MEM-03) — this is not a
|
|
204
|
+
* correction, and neither of the other two options survives being done once a
|
|
205
|
+
* turn. Superseding leaves a dead row per turn in the timeline; hard deleting
|
|
206
|
+
* cascades away the `context_receipt_items` that record what a past recall
|
|
207
|
+
* already cost, rewriting a ledger after the fact.
|
|
208
|
+
*
|
|
209
|
+
* FTS keeps itself in step through the `memory_entries_au` trigger; the vector
|
|
210
|
+
* has no trigger, so it is refreshed here.
|
|
211
|
+
*/
|
|
212
|
+
export function replaceEntry(db, id, input) {
|
|
213
|
+
db.transaction(() => {
|
|
214
|
+
db.prepare(`UPDATE memory_entries
|
|
215
|
+
SET type = ?, title = ?, narrative = ?, facts = ?, files = ?, confidence = ?, occurred_at = ?
|
|
216
|
+
WHERE id = ?`).run(input.type ?? null, input.title, input.narrative ?? '', input.facts?.length ? JSON.stringify(input.facts) : null, input.files?.length ? JSON.stringify(input.files) : null, input.confidence ?? null, input.occurredAt ?? nowIso(), id);
|
|
217
|
+
db.prepare('DELETE FROM memory_entry_tags WHERE entry_id = ?').run(id);
|
|
218
|
+
const tag = db.prepare('INSERT OR IGNORE INTO memory_entry_tags (entry_id, tag) VALUES (?, ?)');
|
|
219
|
+
for (const t of input.tags ?? [])
|
|
220
|
+
tag.run(id, t.toLowerCase());
|
|
221
|
+
indexVector(db, id, vectorText(input));
|
|
222
|
+
})();
|
|
223
|
+
}
|
|
224
|
+
export function indexVector(db, entryId, text) {
|
|
225
|
+
const vec = embedLocal(text);
|
|
226
|
+
db.prepare(`INSERT INTO memory_vectors (entry_id, embedder_id, dim, vec) VALUES (?, ?, ?, ?)
|
|
227
|
+
ON CONFLICT(entry_id, embedder_id) DO UPDATE SET vec = excluded.vec, dim = excluded.dim`).run(entryId, LOCAL_EMBEDDER, LOCAL_DIM, toBlob(vec));
|
|
228
|
+
}
|
|
229
|
+
export function entryById(db, id) {
|
|
230
|
+
return db.prepare('SELECT * FROM memory_entries WHERE id = ?').get(id);
|
|
231
|
+
}
|
|
232
|
+
export function entriesByIds(db, ids) {
|
|
233
|
+
if (!ids.length)
|
|
234
|
+
return [];
|
|
235
|
+
const placeholders = ids.map(() => '?').join(',');
|
|
236
|
+
return db
|
|
237
|
+
.prepare(`SELECT * FROM memory_entries WHERE id IN (${placeholders})`)
|
|
238
|
+
.all(...ids);
|
|
239
|
+
}
|
|
240
|
+
export function entryEvents(db, entryId) {
|
|
241
|
+
return db
|
|
242
|
+
.prepare(`SELECT e.* FROM evidence_events e
|
|
243
|
+
JOIN memory_entry_events m ON m.event_id = e.id
|
|
244
|
+
WHERE m.entry_id = ? ORDER BY e.occurred_at`)
|
|
245
|
+
.all(entryId);
|
|
246
|
+
}
|
|
247
|
+
export function entryTags(db, entryId) {
|
|
248
|
+
return db.prepare('SELECT tag FROM memory_entry_tags WHERE entry_id = ?').all(entryId).map((r) => r.tag);
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* A correction supersedes rather than edits (PRD MEM-03): the stale claim keeps
|
|
252
|
+
* its evidence links and its place in the audit trail, and retrieval prefers the
|
|
253
|
+
* row that replaced it.
|
|
254
|
+
*/
|
|
255
|
+
export function supersedeEntry(db, staleId, replacementId) {
|
|
256
|
+
db.prepare('UPDATE memory_entries SET superseded_by = ? WHERE id = ?').run(replacementId, staleId);
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Soft delete by default so a deletion is auditable, with a hard mode that also
|
|
260
|
+
* removes the derived index rows — a deletion that leaves the vector behind is
|
|
261
|
+
* a deletion the search can still surface (PRD SEC-02).
|
|
262
|
+
*/
|
|
263
|
+
export function deleteEntry(db, id, hard = false) {
|
|
264
|
+
if (hard) {
|
|
265
|
+
db.prepare('DELETE FROM memory_entries WHERE id = ?').run(id);
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
db.transaction(() => {
|
|
269
|
+
db.prepare('UPDATE memory_entries SET deleted_at = ? WHERE id = ?').run(nowIso(), id);
|
|
270
|
+
db.prepare('DELETE FROM memory_vectors WHERE entry_id = ?').run(id);
|
|
271
|
+
db.prepare(`INSERT INTO memory_fts(memory_fts, rowid, title, narrative, facts, files)
|
|
272
|
+
SELECT 'delete', id, title, narrative, facts, files FROM memory_entries WHERE id = ?`).run(id);
|
|
273
|
+
})();
|
|
274
|
+
}
|
|
275
|
+
/** Stable ordering and a cursor-able offset (PRD RET-02). */
|
|
276
|
+
export function timeline(db, filter = {}) {
|
|
277
|
+
const where = [];
|
|
278
|
+
const args = [];
|
|
279
|
+
if (filter.project) {
|
|
280
|
+
where.push('project = ?');
|
|
281
|
+
args.push(filter.project);
|
|
282
|
+
}
|
|
283
|
+
if (filter.sessionId) {
|
|
284
|
+
where.push('session_id = ?');
|
|
285
|
+
args.push(filter.sessionId);
|
|
286
|
+
}
|
|
287
|
+
if (filter.kind) {
|
|
288
|
+
where.push('kind = ?');
|
|
289
|
+
args.push(filter.kind);
|
|
290
|
+
}
|
|
291
|
+
if (filter.type) {
|
|
292
|
+
where.push('type = ?');
|
|
293
|
+
args.push(filter.type);
|
|
294
|
+
}
|
|
295
|
+
if (filter.since) {
|
|
296
|
+
where.push('occurred_at >= ?');
|
|
297
|
+
args.push(filter.since);
|
|
298
|
+
}
|
|
299
|
+
if (filter.until) {
|
|
300
|
+
where.push('occurred_at <= ?');
|
|
301
|
+
args.push(filter.until);
|
|
302
|
+
}
|
|
303
|
+
if (!filter.includeDeleted)
|
|
304
|
+
where.push('deleted_at IS NULL');
|
|
305
|
+
const sql = `SELECT * FROM memory_entries
|
|
306
|
+
${where.length ? `WHERE ${where.join(' AND ')}` : ''}
|
|
307
|
+
ORDER BY occurred_at DESC, id DESC
|
|
308
|
+
LIMIT ? OFFSET ?`;
|
|
309
|
+
return db.prepare(sql).all(...args, filter.limit ?? 50, filter.offset ?? 0);
|
|
310
|
+
}
|
|
311
|
+
export function countEntries(db, project) {
|
|
312
|
+
const row = project
|
|
313
|
+
? db
|
|
314
|
+
.prepare('SELECT COUNT(*) AS n FROM memory_entries WHERE project = ? AND deleted_at IS NULL')
|
|
315
|
+
.get(project)
|
|
316
|
+
: db.prepare('SELECT COUNT(*) AS n FROM memory_entries WHERE deleted_at IS NULL').get();
|
|
317
|
+
return row.n;
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Writes one reuse receipt (PRD MET-01). `delivery` is the honesty valve: an
|
|
321
|
+
* adapter that cannot confirm the host consumed the context records `prepared`,
|
|
322
|
+
* and nothing derived from that row is ever shown as a confirmed saving.
|
|
323
|
+
*/
|
|
324
|
+
export function recordReceipt(db, opts) {
|
|
325
|
+
const base = opts.items.reduce((s, i) => s + i.sourceTokens, 0);
|
|
326
|
+
const delivered = opts.items.reduce((s, i) => s + i.sentTokens, 0) + (opts.wrapperTokens ?? 0);
|
|
327
|
+
return db.transaction(() => {
|
|
328
|
+
const id = Number(db
|
|
329
|
+
.prepare(`INSERT INTO context_receipts
|
|
330
|
+
(receipt_uid, project, session_id, scope, method, base_tokens, delivered_tokens,
|
|
331
|
+
item_count, delivery, created_at)
|
|
332
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
333
|
+
.run(receiptUid(), opts.project, opts.sessionId ?? null, opts.scope, opts.method, base, delivered, opts.items.length, opts.delivery, nowIso()).lastInsertRowid);
|
|
334
|
+
const item = db.prepare(`INSERT OR REPLACE INTO context_receipt_items (receipt_id, entry_id, source_tokens, sent_tokens, stage)
|
|
335
|
+
VALUES (?, ?, ?, ?, ?)`);
|
|
336
|
+
for (const i of opts.items)
|
|
337
|
+
item.run(id, i.entryId, i.sourceTokens, i.sentTokens, i.stage ?? 'index');
|
|
338
|
+
return id;
|
|
339
|
+
})();
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* Charges a later detail fetch to the receipt that proposed the entry, so the
|
|
343
|
+
* episode's percentage falls to what the reuse actually cost instead of keeping
|
|
344
|
+
* the optimistic index-only figure (PRD MET-01).
|
|
345
|
+
*/
|
|
346
|
+
export function chargeDetail(db, receiptId, entryId, sentTokens) {
|
|
347
|
+
db.transaction(() => {
|
|
348
|
+
db.prepare(`INSERT OR REPLACE INTO context_receipt_items (receipt_id, entry_id, source_tokens, sent_tokens, stage)
|
|
349
|
+
VALUES (?, ?, 0, ?, 'detail')`).run(receiptId, entryId, sentTokens);
|
|
350
|
+
db.prepare(`UPDATE context_receipts SET delivered_tokens =
|
|
351
|
+
(SELECT COALESCE(SUM(sent_tokens), 0) FROM context_receipt_items WHERE receipt_id = ?)
|
|
352
|
+
WHERE id = ?`).run(receiptId, receiptId);
|
|
353
|
+
})();
|
|
354
|
+
}
|
|
355
|
+
export function receiptTotals(db, project, sinceDays) {
|
|
356
|
+
const where = [];
|
|
357
|
+
const args = [];
|
|
358
|
+
if (project) {
|
|
359
|
+
where.push('project = ?');
|
|
360
|
+
args.push(project);
|
|
361
|
+
}
|
|
362
|
+
if (sinceDays) {
|
|
363
|
+
where.push("created_at >= datetime('now', ?)");
|
|
364
|
+
args.push(`-${sinceDays} days`);
|
|
365
|
+
}
|
|
366
|
+
const row = db
|
|
367
|
+
.prepare(`SELECT COALESCE(SUM(CASE WHEN delivery = 'confirmed' THEN base_tokens ELSE 0 END), 0) AS base,
|
|
368
|
+
COALESCE(SUM(CASE WHEN delivery = 'confirmed' THEN delivered_tokens ELSE 0 END), 0) AS delivered,
|
|
369
|
+
COUNT(*) AS receipts,
|
|
370
|
+
COALESCE(SUM(CASE WHEN delivery = 'confirmed' THEN 1 ELSE 0 END), 0) AS confirmed
|
|
371
|
+
FROM context_receipts ${where.length ? `WHERE ${where.join(' AND ')}` : ''}`)
|
|
372
|
+
.get(...args);
|
|
373
|
+
return row;
|
|
374
|
+
}
|
|
375
|
+
/**
|
|
376
|
+
* A concept candidate derived from evidence (PRD LRN-02).
|
|
377
|
+
*
|
|
378
|
+
* `status = 'candidate'` is load-bearing: this row is a proposal, not a
|
|
379
|
+
* concept. Nothing here touches `mastery`, `attempts` or a gate — exposure is
|
|
380
|
+
* not assessment, and the only path to a passing attempt is still answering a
|
|
381
|
+
* question.
|
|
382
|
+
*/
|
|
383
|
+
export function addCandidate(db, input) {
|
|
384
|
+
return Number(db
|
|
385
|
+
.prepare(`INSERT INTO learning_sources (entry_id, event_id, slug, name, domain, confidence, status, project, created_at)
|
|
386
|
+
VALUES (?, ?, ?, ?, ?, ?, 'candidate', ?, ?)`)
|
|
387
|
+
.run(input.entryId ?? null, input.eventId ?? null, input.slug, input.name, input.domain, input.confidence, input.project ?? null, nowIso()).lastInsertRowid);
|
|
388
|
+
}
|
|
389
|
+
export function pendingCandidates(db, project, limit = 20) {
|
|
390
|
+
const sql = project
|
|
391
|
+
? `SELECT * FROM learning_sources WHERE status = 'candidate' AND project = ?
|
|
392
|
+
ORDER BY confidence DESC, id LIMIT ?`
|
|
393
|
+
: `SELECT * FROM learning_sources WHERE status = 'candidate' ORDER BY confidence DESC, id LIMIT ?`;
|
|
394
|
+
return (project ? db.prepare(sql).all(project, limit) : db.prepare(sql).all(limit));
|
|
395
|
+
}
|
|
396
|
+
export function resolveCandidate(db, id, status, conceptId) {
|
|
397
|
+
db.prepare('UPDATE learning_sources SET status = ?, concept_id = ? WHERE id = ?').run(status, conceptId ?? null, id);
|
|
398
|
+
}
|
|
399
|
+
//# sourceMappingURL=store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/memory/store.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,aAAa,CAAC;AAEjC,OAAO,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AACpC,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAC3E,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAiDrD;;;;;;GAMG;AACH,MAAM,UAAU,WAAW,CAAC,EAAM,EAAE,KAAoB;IACtD,MAAM,UAAU,GAAG,KAAK,CAAC,UAAU,IAAI,MAAM,EAAE,CAAC;IAChD,MAAM,IAAI,GAAG,EAAE;SACZ,OAAO,CACN;;;4DAGsD,CACvD;SACA,GAAG,CACF,KAAK,CAAC,QAAQ,EACd,KAAK,CAAC,OAAO,EACb,KAAK,CAAC,QAAQ,IAAI,IAAI,EACtB,KAAK,CAAC,SAAS,EACf,KAAK,CAAC,OAAO,IAAI,IAAI,EACrB,KAAK,CAAC,IAAI,IAAI,aAAa,EAC3B,KAAK,CAAC,MAAM,IAAI,MAAM,EACtB,KAAK,CAAC,IAAI,EACV,KAAK,CAAC,IAAI,IAAI,IAAI,EAClB,KAAK,CAAC,KAAK,IAAI,IAAI,EACnB,KAAK,CAAC,IAAI,EACV,KAAK,CAAC,KAAK,IAAI,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EACtE,UAAU,EACV,MAAM,EAAE,EACR,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CACvB,CAAC;IAEJ,IAAI,IAAI,CAAC,OAAO,GAAG,CAAC;QAAE,OAAO,EAAE,EAAE,EAAE,MAAM,CAAC,IAAI,CAAC,eAAe,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAClF,MAAM,QAAQ,GAAG,EAAE;SAChB,OAAO,CAAC,oDAAoD,CAAC;SAC7D,GAAG,CAAC,KAAK,CAAC,QAAQ,CAA+B,CAAC;IACrD,OAAO,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,IAAI,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;AACpD,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,EAAM,EAAE,SAAiB,EAAE,KAAK,GAAG,GAAG;IACrE,OAAO,EAAE;SACN,OAAO,CAAC,sFAAsF,CAAC;SAC/F,GAAG,CAAC,SAAS,EAAE,KAAK,CAAkB,CAAC;AAC5C,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,EAAM,EAAE,OAAgB;IACxD,MAAM,GAAG,GAAG,OAAO;QACjB,CAAC,CAAE,EAAE;aACA,OAAO,CAAC,qFAAqF,CAAC;aAC9F,GAAG,CAAC,OAAO,CAAmB;QACnC,CAAC,CAAE,EAAE,CAAC,OAAO,CAAC,qEAAqE,CAAC,CAAC,GAAG,EAEpF,CAAC;IACP,OAAO,GAAG,CAAC,CAAC,CAAC;AACf,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAC1B,EAAM,EACN,IAAgF;IAEhF,MAAM,GAAG,GAAG,IAAI,CAAC,SAAS,IAAI,EAAE,CAAC;IACjC,OAAO,EAAE,CAAC,WAAW,CAAC,GAAG,EAAE;QACzB,MAAM,MAAM,GAAG,EAAE;aACd,OAAO,CACN;;sCAE8B,CAC/B;aACA,GAAG,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,OAAO,EAAE,GAAG,CAAoC,CAAC;QAC7E,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC;QAErC,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;QAC1D,MAAM,OAAO,GAAG,MAAM,CACpB,EAAE;aACC,OAAO,CACN;qCAC2B,CAC5B;aACA,GAAG,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC,eAAe,CAClG,CAAC;QAEF,MAAM,KAAK,GAAG,EAAE,CAAC,OAAO,CAAC,0EAA0E,CAAC,CAAC;QACrG,KAAK,MAAM,CAAC,IAAI,MAAM;YAAE,KAAK,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;QAEjD,MAAM,KAAK,GAAG,MAAM,CAClB,EAAE;aACC,OAAO,CACN;oDAC0C,CAC3C;aACA,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC,eAAe,CACpD,CAAC;QAEF,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC;IACvD,CAAC,CAAC,EAAE,CAAC;AACP,CAAC;AAeD;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,QAAQ,CAAC,EAAM,EAAE,KAAa,EAAE,YAAY,GAAG,GAAG;IAChE,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC;IACrB,MAAM,KAAK,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,YAAY,GAAG,IAAI,CAAC,CAAC,WAAW,EAAE,CAAC;IACvE,OAAO,EAAE,CAAC,WAAW,CAAC,GAAG,EAAE;QACzB,MAAM,GAAG,GAAG,EAAE;aACX,OAAO,CACN;;;6BAGqB,CACtB;aACA,GAAG,CAAC,GAAG,EAAE,GAAG,CAAuB,CAAC;QACvC,IAAI,CAAC,GAAG;YAAE,OAAO,IAAI,CAAC;QACtB,EAAE,CAAC,OAAO,CACR;;oBAEc,CACf,CAAC,GAAG,CAAC,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC;QACjC,OAAO,EAAE,GAAG,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,CAAC,QAAQ,GAAG,CAAC,EAAE,WAAW,EAAE,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC;IAC3G,CAAC,CAAC,EAAE,CAAC;AACP,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,EAAM,EAAE,KAAa,EAAE,KAAa;IAC5D,EAAE,CAAC,OAAO,CACR;sCACkC,CACnC,CAAC,GAAG,CAAC,MAAM,EAAE,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;AAChC,CAAC;AAED,uEAAuE;AACvE,MAAM,aAAa,GAAG,MAAM,CAAC;AAC7B,8EAA8E;AAC9E,MAAM,gBAAgB,GAAG,OAAO,CAAC;AAEjC;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,OAAO,CACrB,EAAM,EACN,KAAa,EACb,KAAa,EACb,UAAmF,EACnF,OAAe,EACf,WAAW,GAAG,CAAC,EACf,SAAuB,IAAI,CAAC,MAAM;IAElC,MAAM,GAAG,GAAG,EAAE,CAAC,OAAO,CAAC,+CAA+C,CAAC,CAAC,GAAG,CAAC,KAAK,CAEpE,CAAC;IACd,MAAM,QAAQ,GAAG,GAAG,EAAE,QAAQ,IAAI,CAAC,CAAC;IACpC,MAAM,QAAQ,GAAG,UAAU,KAAK,WAAW,IAAI,UAAU,KAAK,WAAW,IAAI,QAAQ,IAAI,WAAW,CAAC;IACrG,MAAM,MAAM,GAAG,UAAU,KAAK,MAAM,IAAI,UAAU,KAAK,OAAO,CAAC;IAC/D,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,gBAAgB,EAAE,aAAa,GAAG,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,GAAG,CAAC,CAAC,CAAC,CAAC;IAC1F,EAAE,CAAC,OAAO,CACR;;;sCAGkC,CACnC,CAAC,GAAG,CACH,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,EACnD,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EACrB,UAAU,EACV,QAAQ,IAAI,MAAM;QAChB,CAAC,CAAC,IAAI;QACN,CAAC,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,MAAM,GAAG,CAAC,GAAG,MAAM,EAAE,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,EAC7E,MAAM,EAAE,EACR,KAAK,EACL,KAAK,CACN,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,YAAY,CAAC,EAAM;IACjC,OAAO,EAAE;SACN,OAAO,CACN;;;+BAGyB,CAC1B;SACA,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC;AAC3B,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,EAAM,EAAE,OAAe;IACjD,OAAO,EAAE;SACN,OAAO,CAAC,uEAAuE,CAAC;SAChF,GAAG,CAAC,OAAO,CAAkB,CAAC;AACnC,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,EAAM,EAAE,OAAe;IAC/C,OAAO,EAAE,CAAC,OAAO,CAAC,iEAAiE,CAAC,CAAC,GAAG,CAAC,OAAO,CAEnF,CAAC;AAChB,CAAC;AA0CD,8EAA8E;AAC9E,MAAM,UAAU,WAAW,CAAC,EAAM,EAAE,KAAiB;IACnD,MAAM,UAAU,GAAG,KAAK,CAAC,UAAU,IAAI,MAAM,EAAE,CAAC;IAChD,MAAM,GAAG,GACP,KAAK,CAAC,QAAQ;QACd,QAAQ,CAAC;YACP,OAAO,EAAE,KAAK,CAAC,OAAO;YACtB,KAAK,EAAE,KAAK,CAAC,KAAK;YAClB,UAAU;YACV,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,OAAO,IAAI,MAAM,CAAC,UAAU,EAAE,CAAC;SACnD,CAAC,CAAC;IAEL,OAAO,EAAE,CAAC,WAAW,CAAC,GAAG,EAAE;QACzB,MAAM,QAAQ,GAAG,EAAE,CAAC,OAAO,CAAC,mDAAmD,CAAC,CAAC,GAAG,CAAC,GAAG,CAE3E,CAAC;QACd,IAAI,QAAQ;YAAE,OAAO,QAAQ,CAAC,EAAE,CAAC;QAEjC,MAAM,EAAE,GAAG,MAAM,CACf,EAAE;aACC,OAAO,CACN;;;gEAGsD,CACvD;aACA,GAAG,CACF,GAAG,EACH,KAAK,CAAC,OAAO,EACb,KAAK,CAAC,SAAS,IAAI,IAAI,EACvB,KAAK,CAAC,OAAO,IAAI,IAAI,EACrB,KAAK,CAAC,IAAI,IAAI,aAAa,EAC3B,KAAK,CAAC,IAAI,IAAI,IAAI,EAClB,KAAK,CAAC,KAAK,EACX,KAAK,CAAC,SAAS,IAAI,EAAE,EACrB,KAAK,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EACxD,KAAK,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EACxD,KAAK,CAAC,SAAS,IAAI,UAAU,EAC7B,KAAK,CAAC,UAAU,IAAI,IAAI,EACxB,UAAU,EACV,MAAM,EAAE,EACR,KAAK,CAAC,YAAY,IAAI,IAAI,CAC3B,CAAC,eAAe,CACpB,CAAC;QAEF,MAAM,GAAG,GAAG,EAAE,CAAC,OAAO,CAAC,uEAAuE,CAAC,CAAC;QAChG,KAAK,MAAM,CAAC,IAAI,KAAK,CAAC,IAAI,IAAI,EAAE;YAAE,GAAG,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC;QAE/D,MAAM,IAAI,GAAG,EAAE,CAAC,OAAO,CAAC,8EAA8E,CAAC,CAAC;QACxG,KAAK,MAAM,OAAO,IAAI,KAAK,CAAC,QAAQ,IAAI,EAAE;YAAE,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC;QAElE,WAAW,CAAC,EAAE,EAAE,EAAE,EAAE,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC;QACvC,OAAO,EAAE,CAAC;IACZ,CAAC,CAAC,EAAE,CAAC;AACP,CAAC;AAED,SAAS,UAAU,CAAC,KAAiB;IACnC,OAAO,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,SAAS,IAAI,EAAE,EAAE,CAAC,KAAK,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,KAAK,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACvH,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,YAAY,CAAC,EAAM,EAAE,EAAU,EAAE,KAAiB;IAChE,EAAE,CAAC,WAAW,CAAC,GAAG,EAAE;QAClB,EAAE,CAAC,OAAO,CACR;;oBAEc,CACf,CAAC,GAAG,CACH,KAAK,CAAC,IAAI,IAAI,IAAI,EAClB,KAAK,CAAC,KAAK,EACX,KAAK,CAAC,SAAS,IAAI,EAAE,EACrB,KAAK,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EACxD,KAAK,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EACxD,KAAK,CAAC,UAAU,IAAI,IAAI,EACxB,KAAK,CAAC,UAAU,IAAI,MAAM,EAAE,EAC5B,EAAE,CACH,CAAC;QACF,EAAE,CAAC,OAAO,CAAC,kDAAkD,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACvE,MAAM,GAAG,GAAG,EAAE,CAAC,OAAO,CAAC,uEAAuE,CAAC,CAAC;QAChG,KAAK,MAAM,CAAC,IAAI,KAAK,CAAC,IAAI,IAAI,EAAE;YAAE,GAAG,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC;QAC/D,WAAW,CAAC,EAAE,EAAE,EAAE,EAAE,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC;IACzC,CAAC,CAAC,EAAE,CAAC;AACP,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,EAAM,EAAE,OAAe,EAAE,IAAY;IAC/D,MAAM,GAAG,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC;IAC7B,EAAE,CAAC,OAAO,CACR;6FACyF,CAC1F,CAAC,GAAG,CAAC,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;AACzD,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,EAAM,EAAE,EAAU;IAC1C,OAAO,EAAE,CAAC,OAAO,CAAC,2CAA2C,CAAC,CAAC,GAAG,CAAC,EAAE,CAAyB,CAAC;AACjG,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,EAAM,EAAE,GAAa;IAChD,IAAI,CAAC,GAAG,CAAC,MAAM;QAAE,OAAO,EAAE,CAAC;IAC3B,MAAM,YAAY,GAAG,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAClD,OAAO,EAAE;SACN,OAAO,CAAC,6CAA6C,YAAY,GAAG,CAAC;SACrE,GAAG,CAAC,GAAG,GAAG,CAAe,CAAC;AAC/B,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,EAAM,EAAE,OAAe;IACjD,OAAO,EAAE;SACN,OAAO,CACN;;mDAE6C,CAC9C;SACA,GAAG,CAAC,OAAO,CAAkB,CAAC;AACnC,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,EAAM,EAAE,OAAe;IAC/C,OAAQ,EAAE,CAAC,OAAO,CAAC,sDAAsD,CAAC,CAAC,GAAG,CAAC,OAAO,CAElF,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;AACzB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAAC,EAAM,EAAE,OAAe,EAAE,aAAqB;IAC3E,EAAE,CAAC,OAAO,CAAC,0DAA0D,CAAC,CAAC,GAAG,CAAC,aAAa,EAAE,OAAO,CAAC,CAAC;AACrG,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,WAAW,CAAC,EAAM,EAAE,EAAU,EAAE,IAAI,GAAG,KAAK;IAC1D,IAAI,IAAI,EAAE,CAAC;QACT,EAAE,CAAC,OAAO,CAAC,yCAAyC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAC9D,OAAO;IACT,CAAC;IACD,EAAE,CAAC,WAAW,CAAC,GAAG,EAAE;QAClB,EAAE,CAAC,OAAO,CAAC,uDAAuD,CAAC,CAAC,GAAG,CAAC,MAAM,EAAE,EAAE,EAAE,CAAC,CAAC;QACtF,EAAE,CAAC,OAAO,CAAC,+CAA+C,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACpE,EAAE,CAAC,OAAO,CACR;4FACsF,CACvF,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IACZ,CAAC,CAAC,EAAE,CAAC;AACP,CAAC;AAeD,6DAA6D;AAC7D,MAAM,UAAU,QAAQ,CAAC,EAAM,EAAE,SAAyB,EAAE;IAC1D,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,MAAM,IAAI,GAAc,EAAE,CAAC;IAC3B,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;QACnB,KAAK,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QAC1B,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAC5B,CAAC;IACD,IAAI,MAAM,CAAC,SAAS,EAAE,CAAC;QACrB,KAAK,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;QAC7B,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IAC9B,CAAC;IACD,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC;QAChB,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QACvB,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IACzB,CAAC;IACD,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC;QAChB,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QACvB,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IACzB,CAAC;IACD,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;QACjB,KAAK,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;QAC/B,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAC1B,CAAC;IACD,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;QACjB,KAAK,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;QAC/B,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAC1B,CAAC;IACD,IAAI,CAAC,MAAM,CAAC,cAAc;QAAE,KAAK,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC;IAE7D,MAAM,GAAG,GAAG;OACP,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE;;sBAEnC,CAAC;IACrB,OAAO,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,GAAG,IAAI,EAAE,MAAM,CAAC,KAAK,IAAI,EAAE,EAAE,MAAM,CAAC,MAAM,IAAI,CAAC,CAAe,CAAC;AAC5F,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,EAAM,EAAE,OAAuB;IAC1D,MAAM,GAAG,GAAG,OAAO;QACjB,CAAC,CAAE,EAAE;aACA,OAAO,CAAC,mFAAmF,CAAC;aAC5F,GAAG,CAAC,OAAO,CAAmB;QACnC,CAAC,CAAE,EAAE,CAAC,OAAO,CAAC,mEAAmE,CAAC,CAAC,GAAG,EAElF,CAAC;IACP,OAAO,GAAG,CAAC,CAAC,CAAC;AACf,CAAC;AASD;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAC3B,EAAM,EACN,IAQC;IAED,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC;IAChE,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,aAAa,IAAI,CAAC,CAAC,CAAC;IAC/F,OAAO,EAAE,CAAC,WAAW,CAAC,GAAG,EAAE;QACzB,MAAM,EAAE,GAAG,MAAM,CACf,EAAE;aACC,OAAO,CACN;;;iDAGuC,CACxC;aACA,GAAG,CACF,UAAU,EAAE,EACZ,IAAI,CAAC,OAAO,EACZ,IAAI,CAAC,SAAS,IAAI,IAAI,EACtB,IAAI,CAAC,KAAK,EACV,IAAI,CAAC,MAAM,EACX,IAAI,EACJ,SAAS,EACT,IAAI,CAAC,KAAK,CAAC,MAAM,EACjB,IAAI,CAAC,QAAQ,EACb,MAAM,EAAE,CACT,CAAC,eAAe,CACpB,CAAC;QACF,MAAM,IAAI,GAAG,EAAE,CAAC,OAAO,CACrB;8BACwB,CACzB,CAAC;QACF,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,KAAK;YAAE,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,YAAY,EAAE,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC,KAAK,IAAI,OAAO,CAAC,CAAC;QACtG,OAAO,EAAE,CAAC;IACZ,CAAC,CAAC,EAAE,CAAC;AACP,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,YAAY,CAAC,EAAM,EAAE,SAAiB,EAAE,OAAe,EAAE,UAAkB;IACzF,EAAE,CAAC,WAAW,CAAC,GAAG,EAAE;QAClB,EAAE,CAAC,OAAO,CACR;qCAC+B,CAChC,CAAC,GAAG,CAAC,SAAS,EAAE,OAAO,EAAE,UAAU,CAAC,CAAC;QACtC,EAAE,CAAC,OAAO,CACR;;oBAEc,CACf,CAAC,GAAG,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;IAC9B,CAAC,CAAC,EAAE,CAAC;AACP,CAAC;AASD,MAAM,UAAU,aAAa,CAAC,EAAM,EAAE,OAAuB,EAAE,SAAkB;IAC/E,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,MAAM,IAAI,GAAc,EAAE,CAAC;IAC3B,IAAI,OAAO,EAAE,CAAC;QACZ,KAAK,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QAC1B,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACrB,CAAC;IACD,IAAI,SAAS,EAAE,CAAC;QACd,KAAK,CAAC,IAAI,CAAC,kCAAkC,CAAC,CAAC;QAC/C,IAAI,CAAC,IAAI,CAAC,IAAI,SAAS,OAAO,CAAC,CAAC;IAClC,CAAC;IACD,MAAM,GAAG,GAAG,EAAE;SACX,OAAO,CACN;;;;+BAIyB,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAC9E;SACA,GAAG,CAAC,GAAG,IAAI,CAAkB,CAAC;IACjC,OAAO,GAAG,CAAC;AACb,CAAC;AAYD;;;;;;;GAOG;AACH,MAAM,UAAU,YAAY,CAAC,EAAM,EAAE,KAAqB;IACxD,OAAO,MAAM,CACX,EAAE;SACC,OAAO,CACN;sDAC8C,CAC/C;SACA,GAAG,CACF,KAAK,CAAC,OAAO,IAAI,IAAI,EACrB,KAAK,CAAC,OAAO,IAAI,IAAI,EACrB,KAAK,CAAC,IAAI,EACV,KAAK,CAAC,IAAI,EACV,KAAK,CAAC,MAAM,EACZ,KAAK,CAAC,UAAU,EAChB,KAAK,CAAC,OAAO,IAAI,IAAI,EACrB,MAAM,EAAE,CACT,CAAC,eAAe,CACpB,CAAC;AACJ,CAAC;AAgBD,MAAM,UAAU,iBAAiB,CAAC,EAAM,EAAE,OAAuB,EAAE,KAAK,GAAG,EAAE;IAC3E,MAAM,GAAG,GAAG,OAAO;QACjB,CAAC,CAAC;4CACsC;QACxC,CAAC,CAAC,gGAAgG,CAAC;IACrG,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAmB,CAAC;AACxG,CAAC;AAED,MAAM,UAAU,gBAAgB,CAC9B,EAAM,EACN,EAAU,EACV,MAA6C,EAC7C,SAAyB;IAEzB,EAAE,CAAC,OAAO,CAAC,qEAAqE,CAAC,CAAC,GAAG,CACnF,MAAM,EACN,SAAS,IAAI,IAAI,EACjB,EAAE,CACH,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
function jsonList(value) {
|
|
3
|
+
if (!value)
|
|
4
|
+
return [];
|
|
5
|
+
try {
|
|
6
|
+
const parsed = JSON.parse(value);
|
|
7
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
// A malformed files column is a bug elsewhere; here it is simply no files.
|
|
11
|
+
return [];
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
function filesOf(rows) {
|
|
15
|
+
const seen = new Set();
|
|
16
|
+
for (const row of rows)
|
|
17
|
+
for (const f of jsonList(row.files))
|
|
18
|
+
seen.add(f);
|
|
19
|
+
return [...seen];
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Tags from the paths the work touched.
|
|
23
|
+
*
|
|
24
|
+
* Path segments are a genuinely good signal for "what area was this" — `src/auth`
|
|
25
|
+
* and `migrations` say more about a batch than any keyword in its prose — and
|
|
26
|
+
* they cost nothing. Extensions add the language.
|
|
27
|
+
*/
|
|
28
|
+
function tagsFor(files, events) {
|
|
29
|
+
const tags = new Set();
|
|
30
|
+
for (const file of files) {
|
|
31
|
+
const ext = path.extname(file).replace('.', '').toLowerCase();
|
|
32
|
+
if (ext && ext.length <= 5)
|
|
33
|
+
tags.add(ext);
|
|
34
|
+
for (const segment of path.dirname(file).split(/[\\/]/)) {
|
|
35
|
+
const clean = segment.toLowerCase();
|
|
36
|
+
if (clean && clean !== '.' && clean !== 'src' && clean.length > 2 && !clean.startsWith('.')) {
|
|
37
|
+
tags.add(clean);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
if (events.some((e) => e.kind === 'tool_error'))
|
|
42
|
+
tags.add('failure');
|
|
43
|
+
return [...tags].slice(0, 12);
|
|
44
|
+
}
|
|
45
|
+
const TYPE_HINTS = [
|
|
46
|
+
[/\b(fix|bug|broken|regression|crash|failing)\b/i, 'bugfix'],
|
|
47
|
+
[/\b(refactor|extract|rename|clean ?up|simplify)\b/i, 'refactor'],
|
|
48
|
+
[/\b(decide|decision|chose|instead of|tradeoff|trade-off)\b/i, 'decision'],
|
|
49
|
+
[/\b(add|implement|build|introduce|support)\b/i, 'feature'],
|
|
50
|
+
[/\b(why|turns out|discovered|found that|because)\b/i, 'discovery'],
|
|
51
|
+
];
|
|
52
|
+
function typeFor(text, events) {
|
|
53
|
+
for (const [re, type] of TYPE_HINTS)
|
|
54
|
+
if (re.test(text))
|
|
55
|
+
return type;
|
|
56
|
+
return events.some((e) => e.kind === 'tool_error') ? 'bugfix' : 'change';
|
|
57
|
+
}
|
|
58
|
+
function firstLine(text, max = 90) {
|
|
59
|
+
const line = text.split('\n').map((l) => l.trim()).find(Boolean) ?? '';
|
|
60
|
+
return line.length > max ? `${line.slice(0, max - 1)}…` : line;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* The default summariser: no network, no key, no waiting.
|
|
64
|
+
*
|
|
65
|
+
* It is extractive rather than generative — it quotes and counts what the
|
|
66
|
+
* evidence already says instead of writing prose about it. That ceiling is
|
|
67
|
+
* real: it cannot explain *why* a change was made unless the developer said so
|
|
68
|
+
* in a prompt. It is also why it can run inside a hook without a budget.
|
|
69
|
+
*/
|
|
70
|
+
export class LocalSummarizer {
|
|
71
|
+
id = 'local-v1';
|
|
72
|
+
async summarize(input) {
|
|
73
|
+
const { events } = input;
|
|
74
|
+
if (!events.length)
|
|
75
|
+
return [];
|
|
76
|
+
// A batch of nothing but session markers is a session in which nothing
|
|
77
|
+
// happened. Recording "session started" as a memory is how a corpus fills
|
|
78
|
+
// with rows that push real work out of a bounded recall.
|
|
79
|
+
if (events.every((e) => e.kind === 'lifecycle'))
|
|
80
|
+
return [];
|
|
81
|
+
const prompts = events.filter((e) => e.kind === 'prompt');
|
|
82
|
+
const errors = events.filter((e) => e.kind === 'tool_error');
|
|
83
|
+
const edits = events.filter((e) => e.kind === 'file_edit');
|
|
84
|
+
const files = filesOf(events);
|
|
85
|
+
const intent = prompts[0]?.body ?? events.find((e) => e.title)?.title ?? '';
|
|
86
|
+
const title = firstLine(intent) || (files[0] ? `Worked on ${files[0]}` : 'Session work');
|
|
87
|
+
const lines = [];
|
|
88
|
+
if (intent)
|
|
89
|
+
lines.push(`Asked: ${firstLine(intent, 200)}`);
|
|
90
|
+
if (edits.length)
|
|
91
|
+
lines.push(`Edited ${edits.length} time(s) across ${files.length} file(s).`);
|
|
92
|
+
if (errors.length)
|
|
93
|
+
lines.push(`${errors.length} tool failure(s) during the work.`);
|
|
94
|
+
for (const later of prompts.slice(1, 4))
|
|
95
|
+
lines.push(`Then asked: ${firstLine(later.body, 160)}`);
|
|
96
|
+
const facts = [];
|
|
97
|
+
for (const error of errors.slice(0, 3))
|
|
98
|
+
facts.push(`Failed: ${firstLine(error.body, 160)}`);
|
|
99
|
+
for (const file of files.slice(0, 6))
|
|
100
|
+
facts.push(`Touched ${file}`);
|
|
101
|
+
return [
|
|
102
|
+
{
|
|
103
|
+
title,
|
|
104
|
+
type: typeFor(`${intent}\n${events.map((e) => e.title ?? '').join('\n')}`, events),
|
|
105
|
+
narrative: lines.join('\n'),
|
|
106
|
+
facts,
|
|
107
|
+
files,
|
|
108
|
+
tags: tagsFor(files, events),
|
|
109
|
+
// Extractive, so the claims are as reliable as their sources — but it
|
|
110
|
+
// did not understand anything, and a confidence of 1 would say it did.
|
|
111
|
+
confidence: 0.5,
|
|
112
|
+
eventIds: events.map((e) => e.id),
|
|
113
|
+
},
|
|
114
|
+
];
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/** How many observations a summary will name before it stops listing them. */
|
|
118
|
+
const SUMMARY_MAX_LINES = 12;
|
|
119
|
+
/**
|
|
120
|
+
* The session summary (PRD MEM-01, PAR-03).
|
|
121
|
+
*
|
|
122
|
+
* An observation answers "what happened in this batch". A session summary
|
|
123
|
+
* answers the only question the *next* session opens with — where did I leave
|
|
124
|
+
* off — and it exists because recall hands back the top of the timeline. Without
|
|
125
|
+
* one, a developer resuming on Monday is handed whichever ten tool calls
|
|
126
|
+
* happened to end Friday, rather than what Friday was about. Until now only the
|
|
127
|
+
* Claude Mem importer ever wrote `kind = 'session_summary'`, so an imported
|
|
128
|
+
* history had them and a natively captured one never did.
|
|
129
|
+
*
|
|
130
|
+
* It rolls up the session's own observations rather than re-reading the raw
|
|
131
|
+
* evidence. The evidence has already been read once: a second pass would mean a
|
|
132
|
+
* second provider request per session, which is exactly the wait LRN-04 keeps
|
|
133
|
+
* out of a seam, and evidence the summariser already judged not worth keeping
|
|
134
|
+
* should not come back in through a different door.
|
|
135
|
+
*
|
|
136
|
+
* Returns null when there is nothing to say, because an entry is forever and a
|
|
137
|
+
* summary of nothing is noise in every future recall. Two observations is the
|
|
138
|
+
* floor, deliberately: with one, the summary is that observation retyped, and a
|
|
139
|
+
* near-duplicate row competing with its own source for a bounded recall budget
|
|
140
|
+
* costs more than it tells anyone.
|
|
141
|
+
*/
|
|
142
|
+
export function summarizeSession(observations) {
|
|
143
|
+
if (observations.length < 2)
|
|
144
|
+
return null;
|
|
145
|
+
const ordered = [...observations].sort((a, b) => a.occurred_at.localeCompare(b.occurred_at));
|
|
146
|
+
const files = filesOf(ordered);
|
|
147
|
+
const counts = new Map();
|
|
148
|
+
for (const o of ordered)
|
|
149
|
+
counts.set(o.type ?? 'change', (counts.get(o.type ?? 'change') ?? 0) + 1);
|
|
150
|
+
const commonest = [...counts].sort((a, b) => b[1] - a[1])[0][0];
|
|
151
|
+
const listed = ordered.slice(0, SUMMARY_MAX_LINES);
|
|
152
|
+
const narrative = [
|
|
153
|
+
`${ordered.length} observations across ${files.length} file(s) in this session.`,
|
|
154
|
+
...listed.map((o) => `- ${o.type ?? 'change'}: ${firstLine(o.title, 120)}`),
|
|
155
|
+
ordered.length > listed.length ? `- …and ${ordered.length - listed.length} more.` : '',
|
|
156
|
+
]
|
|
157
|
+
.filter(Boolean)
|
|
158
|
+
.join('\n');
|
|
159
|
+
return {
|
|
160
|
+
title: `Session: ${firstLine(ordered[0].title, 80)}`,
|
|
161
|
+
type: commonest,
|
|
162
|
+
narrative,
|
|
163
|
+
facts: files.slice(0, 10).map((f) => `Touched ${f}`),
|
|
164
|
+
files,
|
|
165
|
+
// `session` first so the tag survives the cap on a session that touched a
|
|
166
|
+
// deep tree, and so a summary is filterable as one.
|
|
167
|
+
tags: ['session', ...tagsFor(files, [])].slice(0, 12),
|
|
168
|
+
// No more reliable than the least reliable thing underneath it.
|
|
169
|
+
confidence: Math.min(...ordered.map((o) => o.confidence ?? 0.5)),
|
|
170
|
+
// Deliberately no event links. `pruneEvidence` keeps any event an entry
|
|
171
|
+
// still points at, so a summary citing a whole session would pin that
|
|
172
|
+
// session's raw evidence past `memory.retention_days` for ever — the one
|
|
173
|
+
// promise SEC-02 makes about raw capture. The price is a receipt that
|
|
174
|
+
// scores this row as pure overhead; the other trade is worse.
|
|
175
|
+
eventIds: [],
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
//# sourceMappingURL=summarize.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"summarize.js","sourceRoot":"","sources":["../../src/memory/summarize.ts"],"names":[],"mappings":"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"}
|