@yugenlab/vaayu 0.1.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/LICENSE +21 -0
- package/README.md +365 -0
- package/chunks/chunk-E5A3SCDJ.js +246 -0
- package/chunks/chunk-G5VYCA6O.js +69 -0
- package/chunks/chunk-H76V36OF.js +1029 -0
- package/chunks/chunk-HAPVUJ6A.js +238 -0
- package/chunks/chunk-IEKAYVA3.js +137 -0
- package/chunks/chunk-IGKYKEKT.js +43 -0
- package/chunks/chunk-IIET2K6D.js +7728 -0
- package/chunks/chunk-ITIVYGUG.js +347 -0
- package/chunks/chunk-JAWZ7ANC.js +208 -0
- package/chunks/chunk-JZU37VQ5.js +714 -0
- package/chunks/chunk-KC6NRZ7U.js +198 -0
- package/chunks/chunk-KDRROLVN.js +433 -0
- package/chunks/chunk-L7JICQBW.js +1006 -0
- package/chunks/chunk-MINFB5LT.js +1479 -0
- package/chunks/chunk-MJ74G5RB.js +5816 -0
- package/chunks/chunk-S4TBVCL2.js +2158 -0
- package/chunks/chunk-SMVJRPAH.js +2753 -0
- package/chunks/chunk-U6OLJ36B.js +438 -0
- package/chunks/chunk-URGEODS5.js +752 -0
- package/chunks/chunk-YSU3BWV6.js +123 -0
- package/chunks/consolidation-indexer-TOTTDZXW.js +21 -0
- package/chunks/day-consolidation-NKO63HZQ.js +24 -0
- package/chunks/graphrag-ZI2FSU7S.js +13 -0
- package/chunks/hierarchical-temporal-search-ZD46UMKR.js +8 -0
- package/chunks/hybrid-search-ZVLZVGFS.js +19 -0
- package/chunks/memory-store-KNJPMBLQ.js +17 -0
- package/chunks/periodic-consolidation-BPKOZDGB.js +10 -0
- package/chunks/postgres-3ZXBYTPC.js +8 -0
- package/chunks/recall-GMVHWQWW.js +20 -0
- package/chunks/search-7HZETVMZ.js +18 -0
- package/chunks/session-store-XKPGKXUS.js +44 -0
- package/chunks/sqlite-JPF5TICX.js +152 -0
- package/chunks/src-6GVZTUH6.js +12 -0
- package/chunks/src-QAXOD5SB.js +273 -0
- package/chunks/suncalc-NOHGYHDU.js +186 -0
- package/chunks/tree-RSHKDTCR.js +10 -0
- package/gateway.js +61944 -0
- package/package.json +51 -0
- package/pair-cli.js +133 -0
|
@@ -0,0 +1,752 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DatabaseManager
|
|
3
|
+
} from "./chunk-U6OLJ36B.js";
|
|
4
|
+
import {
|
|
5
|
+
getChitraguptaHome
|
|
6
|
+
} from "./chunk-KC6NRZ7U.js";
|
|
7
|
+
|
|
8
|
+
// ../chitragupta/packages/smriti/src/periodic-consolidation.ts
|
|
9
|
+
import fs from "node:fs";
|
|
10
|
+
import path from "node:path";
|
|
11
|
+
function projectHash(project) {
|
|
12
|
+
let h = 2166136261;
|
|
13
|
+
for (let i = 0; i < project.length; i++) {
|
|
14
|
+
h ^= project.charCodeAt(i);
|
|
15
|
+
h = h * 16777619 >>> 0;
|
|
16
|
+
}
|
|
17
|
+
return (h >>> 0).toString(16).slice(0, 4);
|
|
18
|
+
}
|
|
19
|
+
var PeriodicConsolidation = class {
|
|
20
|
+
_project;
|
|
21
|
+
_home;
|
|
22
|
+
_hash;
|
|
23
|
+
_baseDir;
|
|
24
|
+
constructor(config) {
|
|
25
|
+
this._project = config.project;
|
|
26
|
+
this._home = config.chitraguptaHome ?? getChitraguptaHome();
|
|
27
|
+
this._hash = projectHash(this._project);
|
|
28
|
+
this._baseDir = path.join(this._home, "consolidated", this._hash);
|
|
29
|
+
}
|
|
30
|
+
// ── Public API ────────────────────────────────────────────────────────
|
|
31
|
+
/**
|
|
32
|
+
* Run monthly consolidation for a specific calendar month.
|
|
33
|
+
*
|
|
34
|
+
* Queries all sessions created within [year-month-01, year-month+1-01),
|
|
35
|
+
* aggregates statistics, collects vasanas/vidhis/samskaras created in
|
|
36
|
+
* that window, generates a Markdown report, indexes it into FTS5, and
|
|
37
|
+
* logs the run to the consolidation_log table.
|
|
38
|
+
*
|
|
39
|
+
* @param year - The calendar year (e.g. 2026).
|
|
40
|
+
* @param month - The calendar month (1-12).
|
|
41
|
+
* @returns The consolidation report with stats and file path.
|
|
42
|
+
*/
|
|
43
|
+
async monthly(year, month) {
|
|
44
|
+
const t0 = Date.now();
|
|
45
|
+
const period = `${year}-${String(month).padStart(2, "0")}`;
|
|
46
|
+
const { startMs, endMs } = this._monthRange(year, month);
|
|
47
|
+
const dbm = DatabaseManager.instance(this._home);
|
|
48
|
+
const agentDb = dbm.get("agent");
|
|
49
|
+
const graphDb = dbm.get("graph");
|
|
50
|
+
const sessions = agentDb.prepare(
|
|
51
|
+
`SELECT id, title, turn_count, cost, tokens, created_at
|
|
52
|
+
FROM sessions
|
|
53
|
+
WHERE project = ? AND created_at >= ? AND created_at < ?
|
|
54
|
+
ORDER BY created_at ASC`
|
|
55
|
+
).all(this._project, startMs, endMs);
|
|
56
|
+
const sessionIds = sessions.map((s) => s.id);
|
|
57
|
+
const totalTurns = sessions.reduce((sum, s) => sum + s.turn_count, 0);
|
|
58
|
+
const totalTokens = sessions.reduce((sum, s) => sum + (s.tokens ?? 0), 0);
|
|
59
|
+
const totalCost = sessions.reduce((sum, s) => sum + (s.cost ?? 0), 0);
|
|
60
|
+
const toolSet = /* @__PURE__ */ new Set();
|
|
61
|
+
if (sessionIds.length > 0) {
|
|
62
|
+
const placeholders = sessionIds.map(() => "?").join(",");
|
|
63
|
+
const turns = agentDb.prepare(
|
|
64
|
+
`SELECT tool_calls FROM turns
|
|
65
|
+
WHERE session_id IN (${placeholders}) AND tool_calls IS NOT NULL`
|
|
66
|
+
).all(...sessionIds);
|
|
67
|
+
for (const turn of turns) {
|
|
68
|
+
if (!turn.tool_calls) continue;
|
|
69
|
+
try {
|
|
70
|
+
const calls = JSON.parse(turn.tool_calls);
|
|
71
|
+
for (const call of calls) {
|
|
72
|
+
if (call.name) toolSet.add(call.name);
|
|
73
|
+
}
|
|
74
|
+
} catch {
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
const vasanas = agentDb.prepare(
|
|
79
|
+
`SELECT name, description, strength, valence, stability
|
|
80
|
+
FROM vasanas
|
|
81
|
+
WHERE (project = ? OR project IS NULL)
|
|
82
|
+
AND created_at >= ? AND created_at < ?
|
|
83
|
+
ORDER BY strength DESC`
|
|
84
|
+
).all(this._project, startMs, endMs);
|
|
85
|
+
const vidhis = agentDb.prepare(
|
|
86
|
+
`SELECT name, steps, success_rate, learned_from
|
|
87
|
+
FROM vidhis
|
|
88
|
+
WHERE project = ?
|
|
89
|
+
AND created_at >= ? AND created_at < ?
|
|
90
|
+
ORDER BY success_rate DESC`
|
|
91
|
+
).all(this._project, startMs, endMs);
|
|
92
|
+
const samskaras = agentDb.prepare(
|
|
93
|
+
`SELECT id, pattern_type, pattern_content, confidence, observation_count
|
|
94
|
+
FROM samskaras
|
|
95
|
+
WHERE (project = ? OR project IS NULL)
|
|
96
|
+
AND updated_at >= ? AND updated_at < ?
|
|
97
|
+
ORDER BY confidence DESC
|
|
98
|
+
LIMIT 20`
|
|
99
|
+
).all(this._project, startMs, endMs);
|
|
100
|
+
const newNodes = graphDb.prepare(
|
|
101
|
+
`SELECT COUNT(*) AS cnt FROM nodes
|
|
102
|
+
WHERE created_at >= ? AND created_at < ?`
|
|
103
|
+
).get(startMs, endMs)?.cnt ?? 0;
|
|
104
|
+
const newEdges = graphDb.prepare(
|
|
105
|
+
`SELECT COUNT(*) AS cnt FROM edges
|
|
106
|
+
WHERE recorded_at >= ? AND recorded_at < ?`
|
|
107
|
+
).get(startMs, endMs)?.cnt ?? 0;
|
|
108
|
+
const stats = {
|
|
109
|
+
sessions: sessions.length,
|
|
110
|
+
turns: totalTurns,
|
|
111
|
+
tokens: totalTokens,
|
|
112
|
+
cost: totalCost,
|
|
113
|
+
vasanasCreated: vasanas.length,
|
|
114
|
+
vidhisCreated: vidhis.length,
|
|
115
|
+
samskarasActive: samskaras.length
|
|
116
|
+
};
|
|
117
|
+
const recommendations = this._generateRecommendations(
|
|
118
|
+
stats,
|
|
119
|
+
vasanas,
|
|
120
|
+
vidhis,
|
|
121
|
+
samskaras,
|
|
122
|
+
toolSet
|
|
123
|
+
);
|
|
124
|
+
const markdown = this._buildMonthlyMarkdown(
|
|
125
|
+
period,
|
|
126
|
+
stats,
|
|
127
|
+
vasanas,
|
|
128
|
+
vidhis,
|
|
129
|
+
samskaras,
|
|
130
|
+
toolSet,
|
|
131
|
+
newNodes,
|
|
132
|
+
newEdges,
|
|
133
|
+
recommendations
|
|
134
|
+
);
|
|
135
|
+
const filePath = this.getReportPath("monthly", period);
|
|
136
|
+
this._writeReport(filePath, markdown);
|
|
137
|
+
try {
|
|
138
|
+
const { indexConsolidationSummary } = await import("./consolidation-indexer-TOTTDZXW.js");
|
|
139
|
+
await indexConsolidationSummary("monthly", period, markdown, this._project);
|
|
140
|
+
} catch {
|
|
141
|
+
}
|
|
142
|
+
this._indexIntoFts(agentDb, markdown);
|
|
143
|
+
const durationMs = Date.now() - t0;
|
|
144
|
+
this._logConsolidation(agentDb, {
|
|
145
|
+
project: this._project,
|
|
146
|
+
cycleType: "monthly",
|
|
147
|
+
cycleId: `monthly-${period}`,
|
|
148
|
+
vasanasCreated: vasanas.length,
|
|
149
|
+
vidhisCreated: vidhis.length,
|
|
150
|
+
samskarasProcessed: samskaras.length,
|
|
151
|
+
sessionsProcessed: sessions.length,
|
|
152
|
+
status: "success",
|
|
153
|
+
createdAt: Date.now()
|
|
154
|
+
});
|
|
155
|
+
return {
|
|
156
|
+
type: "monthly",
|
|
157
|
+
period,
|
|
158
|
+
project: this._project,
|
|
159
|
+
filePath,
|
|
160
|
+
markdown,
|
|
161
|
+
stats,
|
|
162
|
+
durationMs
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Run yearly consolidation for a specific calendar year.
|
|
167
|
+
*
|
|
168
|
+
* Reads all 12 monthly reports (generating any missing ones), aggregates
|
|
169
|
+
* annual statistics, identifies trends and growth areas, generates a
|
|
170
|
+
* yearly Markdown report, VACUUMs the SQLite databases, and logs the run.
|
|
171
|
+
*
|
|
172
|
+
* @param year - The calendar year (e.g. 2026).
|
|
173
|
+
* @returns The consolidation report with stats and file path.
|
|
174
|
+
*/
|
|
175
|
+
async yearly(year) {
|
|
176
|
+
const t0 = Date.now();
|
|
177
|
+
const period = String(year);
|
|
178
|
+
const { startMs, endMs } = this._yearRange(year);
|
|
179
|
+
const dbm = DatabaseManager.instance(this._home);
|
|
180
|
+
const agentDb = dbm.get("agent");
|
|
181
|
+
const graphDb = dbm.get("graph");
|
|
182
|
+
const monthlyReports = [];
|
|
183
|
+
for (let m = 1; m <= 12; m++) {
|
|
184
|
+
const mp = `${year}-${String(m).padStart(2, "0")}`;
|
|
185
|
+
const mPath = this.getReportPath("monthly", mp);
|
|
186
|
+
if (!fs.existsSync(mPath)) {
|
|
187
|
+
const { startMs: ms, endMs: me } = this._monthRange(year, m);
|
|
188
|
+
if (me <= Date.now()) {
|
|
189
|
+
const count = agentDb.prepare(
|
|
190
|
+
`SELECT COUNT(*) AS cnt FROM sessions
|
|
191
|
+
WHERE project = ? AND created_at >= ? AND created_at < ?`
|
|
192
|
+
).get(this._project, ms, me)?.cnt ?? 0;
|
|
193
|
+
if (count > 0) {
|
|
194
|
+
monthlyReports.push(await this.monthly(year, m));
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
} else {
|
|
199
|
+
const content = fs.readFileSync(mPath, "utf-8");
|
|
200
|
+
monthlyReports.push({
|
|
201
|
+
type: "monthly",
|
|
202
|
+
period: mp,
|
|
203
|
+
project: this._project,
|
|
204
|
+
filePath: mPath,
|
|
205
|
+
markdown: content,
|
|
206
|
+
stats: this._extractStatsFromMarkdown(content),
|
|
207
|
+
durationMs: 0
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
const annualStats = {
|
|
212
|
+
sessions: 0,
|
|
213
|
+
turns: 0,
|
|
214
|
+
tokens: 0,
|
|
215
|
+
cost: 0,
|
|
216
|
+
vasanasCreated: 0,
|
|
217
|
+
vidhisCreated: 0,
|
|
218
|
+
samskarasActive: 0
|
|
219
|
+
};
|
|
220
|
+
for (const r of monthlyReports) {
|
|
221
|
+
annualStats.sessions += r.stats.sessions;
|
|
222
|
+
annualStats.turns += r.stats.turns;
|
|
223
|
+
annualStats.tokens += r.stats.tokens;
|
|
224
|
+
annualStats.cost += r.stats.cost;
|
|
225
|
+
annualStats.vasanasCreated += r.stats.vasanasCreated;
|
|
226
|
+
annualStats.vidhisCreated += r.stats.vidhisCreated;
|
|
227
|
+
annualStats.samskarasActive += r.stats.samskarasActive;
|
|
228
|
+
}
|
|
229
|
+
const allVasanas = agentDb.prepare(
|
|
230
|
+
`SELECT name, description, strength, valence, stability
|
|
231
|
+
FROM vasanas
|
|
232
|
+
WHERE (project = ? OR project IS NULL)
|
|
233
|
+
AND created_at >= ? AND created_at < ?
|
|
234
|
+
ORDER BY strength DESC`
|
|
235
|
+
).all(this._project, startMs, endMs);
|
|
236
|
+
const allVidhis = agentDb.prepare(
|
|
237
|
+
`SELECT name, steps, success_rate, learned_from
|
|
238
|
+
FROM vidhis
|
|
239
|
+
WHERE project = ?
|
|
240
|
+
AND created_at >= ? AND created_at < ?
|
|
241
|
+
ORDER BY success_rate DESC`
|
|
242
|
+
).all(this._project, startMs, endMs);
|
|
243
|
+
const allSamskaras = agentDb.prepare(
|
|
244
|
+
`SELECT id, pattern_type, pattern_content, confidence, observation_count
|
|
245
|
+
FROM samskaras
|
|
246
|
+
WHERE (project = ? OR project IS NULL)
|
|
247
|
+
AND updated_at >= ? AND updated_at < ?
|
|
248
|
+
ORDER BY confidence DESC
|
|
249
|
+
LIMIT 30`
|
|
250
|
+
).all(this._project, startMs, endMs);
|
|
251
|
+
const yearNodes = graphDb.prepare(
|
|
252
|
+
`SELECT COUNT(*) AS cnt FROM nodes
|
|
253
|
+
WHERE created_at >= ? AND created_at < ?`
|
|
254
|
+
).get(startMs, endMs)?.cnt ?? 0;
|
|
255
|
+
const yearEdges = graphDb.prepare(
|
|
256
|
+
`SELECT COUNT(*) AS cnt FROM edges
|
|
257
|
+
WHERE recorded_at >= ? AND recorded_at < ?`
|
|
258
|
+
).get(startMs, endMs)?.cnt ?? 0;
|
|
259
|
+
let prevYearStats = null;
|
|
260
|
+
const prevPath = this.getReportPath("yearly", String(year - 1));
|
|
261
|
+
if (fs.existsSync(prevPath)) {
|
|
262
|
+
const prevContent = fs.readFileSync(prevPath, "utf-8");
|
|
263
|
+
prevYearStats = this._extractStatsFromMarkdown(prevContent);
|
|
264
|
+
}
|
|
265
|
+
const trends = this._analyzeTrends(monthlyReports);
|
|
266
|
+
const markdown = this._buildYearlyMarkdown(
|
|
267
|
+
period,
|
|
268
|
+
annualStats,
|
|
269
|
+
allVasanas,
|
|
270
|
+
allVidhis,
|
|
271
|
+
allSamskaras,
|
|
272
|
+
yearNodes,
|
|
273
|
+
yearEdges,
|
|
274
|
+
monthlyReports,
|
|
275
|
+
trends,
|
|
276
|
+
prevYearStats
|
|
277
|
+
);
|
|
278
|
+
const filePath = this.getReportPath("yearly", period);
|
|
279
|
+
this._writeReport(filePath, markdown);
|
|
280
|
+
try {
|
|
281
|
+
const { indexConsolidationSummary } = await import("./consolidation-indexer-TOTTDZXW.js");
|
|
282
|
+
await indexConsolidationSummary("yearly", period, markdown, this._project);
|
|
283
|
+
} catch {
|
|
284
|
+
}
|
|
285
|
+
this._indexIntoFts(agentDb, markdown);
|
|
286
|
+
dbm.vacuum("agent");
|
|
287
|
+
dbm.vacuum("graph");
|
|
288
|
+
dbm.vacuum("vectors");
|
|
289
|
+
const durationMs = Date.now() - t0;
|
|
290
|
+
this._logConsolidation(agentDb, {
|
|
291
|
+
project: this._project,
|
|
292
|
+
cycleType: "yearly",
|
|
293
|
+
cycleId: `yearly-${period}`,
|
|
294
|
+
vasanasCreated: allVasanas.length,
|
|
295
|
+
vidhisCreated: allVidhis.length,
|
|
296
|
+
samskarasProcessed: allSamskaras.length,
|
|
297
|
+
sessionsProcessed: annualStats.sessions,
|
|
298
|
+
status: "success",
|
|
299
|
+
createdAt: Date.now()
|
|
300
|
+
});
|
|
301
|
+
return {
|
|
302
|
+
type: "yearly",
|
|
303
|
+
period,
|
|
304
|
+
project: this._project,
|
|
305
|
+
filePath,
|
|
306
|
+
markdown,
|
|
307
|
+
stats: annualStats,
|
|
308
|
+
durationMs
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Check whether a monthly report already exists on disk.
|
|
313
|
+
*
|
|
314
|
+
* @param year - Calendar year.
|
|
315
|
+
* @param month - Calendar month (1-12).
|
|
316
|
+
*/
|
|
317
|
+
hasMonthlyReport(year, month) {
|
|
318
|
+
const period = `${year}-${String(month).padStart(2, "0")}`;
|
|
319
|
+
return fs.existsSync(this.getReportPath("monthly", period));
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Check whether a yearly report already exists on disk.
|
|
323
|
+
*
|
|
324
|
+
* @param year - Calendar year.
|
|
325
|
+
*/
|
|
326
|
+
hasYearlyReport(year) {
|
|
327
|
+
return fs.existsSync(this.getReportPath("yearly", String(year)));
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Get the absolute file path for a report.
|
|
331
|
+
*
|
|
332
|
+
* @param type - 'monthly' or 'yearly'.
|
|
333
|
+
* @param period - 'YYYY-MM' for monthly, 'YYYY' for yearly.
|
|
334
|
+
* @returns Absolute path to the report Markdown file.
|
|
335
|
+
*/
|
|
336
|
+
getReportPath(type, period) {
|
|
337
|
+
const filename = `${period}.md`;
|
|
338
|
+
return path.join(this._baseDir, type, filename);
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* List all existing reports for this project.
|
|
342
|
+
*
|
|
343
|
+
* @returns Array of report descriptors sorted by period.
|
|
344
|
+
*/
|
|
345
|
+
listReports() {
|
|
346
|
+
const reports = [];
|
|
347
|
+
for (const type of ["monthly", "yearly"]) {
|
|
348
|
+
const dir = path.join(this._baseDir, type);
|
|
349
|
+
if (!fs.existsSync(dir)) continue;
|
|
350
|
+
const files = fs.readdirSync(dir).filter((f) => f.endsWith(".md")).sort();
|
|
351
|
+
for (const file of files) {
|
|
352
|
+
reports.push({
|
|
353
|
+
type,
|
|
354
|
+
period: file.replace(/\.md$/, ""),
|
|
355
|
+
path: path.join(dir, file)
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
return reports;
|
|
360
|
+
}
|
|
361
|
+
// ── Private Helpers ───────────────────────────────────────────────────
|
|
362
|
+
/**
|
|
363
|
+
* Compute the Unix epoch ms range [start, end) for a calendar month.
|
|
364
|
+
*/
|
|
365
|
+
_monthRange(year, month) {
|
|
366
|
+
const start = new Date(Date.UTC(year, month - 1, 1));
|
|
367
|
+
const end = new Date(Date.UTC(year, month, 1));
|
|
368
|
+
return { startMs: start.getTime(), endMs: end.getTime() };
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* Compute the Unix epoch ms range [start, end) for a calendar year.
|
|
372
|
+
*/
|
|
373
|
+
_yearRange(year) {
|
|
374
|
+
const start = new Date(Date.UTC(year, 0, 1));
|
|
375
|
+
const end = new Date(Date.UTC(year + 1, 0, 1));
|
|
376
|
+
return { startMs: start.getTime(), endMs: end.getTime() };
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* Write a Markdown report to disk, creating parent directories as needed.
|
|
380
|
+
*/
|
|
381
|
+
_writeReport(filePath, content) {
|
|
382
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
383
|
+
fs.writeFileSync(filePath, content, "utf-8");
|
|
384
|
+
fs.chmodSync(filePath, 384);
|
|
385
|
+
}
|
|
386
|
+
/**
|
|
387
|
+
* Index report text into the FTS5 virtual table for full-text search.
|
|
388
|
+
*
|
|
389
|
+
* Inserts the report content as a synthetic turn so it surfaces in
|
|
390
|
+
* memory search queries.
|
|
391
|
+
*/
|
|
392
|
+
_indexIntoFts(agentDb, content) {
|
|
393
|
+
try {
|
|
394
|
+
agentDb.prepare("INSERT INTO turns_fts(content) VALUES (?)").run(content);
|
|
395
|
+
} catch {
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* Log a consolidation run to the consolidation_log table.
|
|
400
|
+
*/
|
|
401
|
+
_logConsolidation(agentDb, entry) {
|
|
402
|
+
try {
|
|
403
|
+
agentDb.prepare(
|
|
404
|
+
`INSERT INTO consolidation_log
|
|
405
|
+
(project, cycle_type, cycle_id, vasanas_created, vidhis_created,
|
|
406
|
+
samskaras_processed, sessions_processed, status, created_at)
|
|
407
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
408
|
+
).run(
|
|
409
|
+
entry.project,
|
|
410
|
+
entry.cycleType,
|
|
411
|
+
entry.cycleId,
|
|
412
|
+
entry.vasanasCreated,
|
|
413
|
+
entry.vidhisCreated,
|
|
414
|
+
entry.samskarasProcessed,
|
|
415
|
+
entry.sessionsProcessed,
|
|
416
|
+
entry.status,
|
|
417
|
+
entry.createdAt
|
|
418
|
+
);
|
|
419
|
+
} catch {
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* Extract stats from a previously generated Markdown report.
|
|
424
|
+
* Parses the Summary section for key numbers.
|
|
425
|
+
*/
|
|
426
|
+
_extractStatsFromMarkdown(md) {
|
|
427
|
+
const stats = {
|
|
428
|
+
sessions: 0,
|
|
429
|
+
turns: 0,
|
|
430
|
+
tokens: 0,
|
|
431
|
+
cost: 0,
|
|
432
|
+
vasanasCreated: 0,
|
|
433
|
+
vidhisCreated: 0,
|
|
434
|
+
samskarasActive: 0
|
|
435
|
+
};
|
|
436
|
+
const num = (pattern) => {
|
|
437
|
+
const match = md.match(pattern);
|
|
438
|
+
if (!match) return 0;
|
|
439
|
+
return parseFloat(match[1].replace(/,/g, "")) || 0;
|
|
440
|
+
};
|
|
441
|
+
stats.sessions = num(/\*\*Sessions\*\*:\s*([\d,]+)/);
|
|
442
|
+
stats.turns = num(/\*\*Turns\*\*:\s*([\d,]+)/);
|
|
443
|
+
stats.tokens = num(/\*\*Total Tokens\*\*:\s*([\d,]+)/);
|
|
444
|
+
stats.cost = num(/\*\*Estimated Cost\*\*:\s*\$?([\d,.]+)/);
|
|
445
|
+
const vasanaSection = md.match(/## Vasanas Crystallized\n[\s\S]*?(?=\n##|$)/);
|
|
446
|
+
if (vasanaSection) {
|
|
447
|
+
stats.vasanasCreated = (vasanaSection[0].match(/^\|(?!\s*-)[^|]+\|/gm) || []).length - 1;
|
|
448
|
+
if (stats.vasanasCreated < 0) stats.vasanasCreated = 0;
|
|
449
|
+
}
|
|
450
|
+
const vidhiSection = md.match(/## Vidhis Extracted\n[\s\S]*?(?=\n##|$)/);
|
|
451
|
+
if (vidhiSection) {
|
|
452
|
+
stats.vidhisCreated = (vidhiSection[0].match(/^\|(?!\s*-)[^|]+\|/gm) || []).length - 1;
|
|
453
|
+
if (stats.vidhisCreated < 0) stats.vidhisCreated = 0;
|
|
454
|
+
}
|
|
455
|
+
const samskaraSection = md.match(/## Top Samskaras\n[\s\S]*?(?=\n##|$)/);
|
|
456
|
+
if (samskaraSection) {
|
|
457
|
+
stats.samskarasActive = (samskaraSection[0].match(/^\|(?!\s*-)[^|]+\|/gm) || []).length - 1;
|
|
458
|
+
if (stats.samskarasActive < 0) stats.samskarasActive = 0;
|
|
459
|
+
}
|
|
460
|
+
return stats;
|
|
461
|
+
}
|
|
462
|
+
/**
|
|
463
|
+
* Generate actionable recommendations based on consolidation data.
|
|
464
|
+
*/
|
|
465
|
+
_generateRecommendations(stats, vasanas, vidhis, samskaras, tools) {
|
|
466
|
+
const recs = [];
|
|
467
|
+
if (stats.sessions > 0 && stats.cost > 0) {
|
|
468
|
+
const costPerSession = stats.cost / stats.sessions;
|
|
469
|
+
if (costPerSession > 1) {
|
|
470
|
+
recs.push(
|
|
471
|
+
`Average cost per session is $${costPerSession.toFixed(2)} \u2014 consider using lighter models for routine tasks.`
|
|
472
|
+
);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
if (stats.turns > 0 && stats.tokens > 0) {
|
|
476
|
+
const tokensPerTurn = Math.round(stats.tokens / stats.turns);
|
|
477
|
+
if (tokensPerTurn > 5e3) {
|
|
478
|
+
recs.push(
|
|
479
|
+
`High token usage per turn (${tokensPerTurn.toLocaleString()} avg) \u2014 review context window usage and compaction settings.`
|
|
480
|
+
);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
const negativeVasanas = vasanas.filter((v) => v.valence === "negative");
|
|
484
|
+
if (negativeVasanas.length > 0) {
|
|
485
|
+
recs.push(
|
|
486
|
+
`${negativeVasanas.length} negative vasana(s) detected: ${negativeVasanas.map((v) => v.name).join(", ")}. Investigate root causes.`
|
|
487
|
+
);
|
|
488
|
+
}
|
|
489
|
+
const weakVidhis = vidhis.filter((v) => v.success_rate < 0.5);
|
|
490
|
+
if (weakVidhis.length > 0) {
|
|
491
|
+
recs.push(
|
|
492
|
+
`${weakVidhis.length} vidhi(s) with sub-50% success rate \u2014 consider refining or deprecating: ${weakVidhis.map((v) => v.name).join(", ")}.`
|
|
493
|
+
);
|
|
494
|
+
}
|
|
495
|
+
const strongSamskaras = samskaras.filter((s) => s.confidence > 0.8 && s.observation_count >= 5);
|
|
496
|
+
if (strongSamskaras.length > 0) {
|
|
497
|
+
recs.push(
|
|
498
|
+
`${strongSamskaras.length} high-confidence samskara(s) may be ready for vasana crystallization.`
|
|
499
|
+
);
|
|
500
|
+
}
|
|
501
|
+
if (tools.size > 0 && stats.sessions >= 5) {
|
|
502
|
+
const toolsPerSession = tools.size / stats.sessions;
|
|
503
|
+
if (toolsPerSession < 1.5) {
|
|
504
|
+
recs.push(
|
|
505
|
+
"Low tool diversity \u2014 explore additional tools to improve efficiency."
|
|
506
|
+
);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
if (recs.length === 0) {
|
|
510
|
+
recs.push("All metrics within healthy ranges. Keep up the momentum.");
|
|
511
|
+
}
|
|
512
|
+
return recs;
|
|
513
|
+
}
|
|
514
|
+
/**
|
|
515
|
+
* Analyze trends across monthly reports within a year.
|
|
516
|
+
*/
|
|
517
|
+
_analyzeTrends(reports) {
|
|
518
|
+
const trends = [];
|
|
519
|
+
if (reports.length < 2) return trends;
|
|
520
|
+
const sessionCounts = reports.map((r) => r.stats.sessions);
|
|
521
|
+
const firstHalf = sessionCounts.slice(0, Math.floor(sessionCounts.length / 2));
|
|
522
|
+
const secondHalf = sessionCounts.slice(Math.floor(sessionCounts.length / 2));
|
|
523
|
+
const avgFirst = firstHalf.reduce((a, b) => a + b, 0) / (firstHalf.length || 1);
|
|
524
|
+
const avgSecond = secondHalf.reduce((a, b) => a + b, 0) / (secondHalf.length || 1);
|
|
525
|
+
if (avgSecond > avgFirst * 1.3) {
|
|
526
|
+
trends.push("Session volume increased significantly in the second half of the year.");
|
|
527
|
+
} else if (avgSecond < avgFirst * 0.7) {
|
|
528
|
+
trends.push("Session volume decreased notably in the second half of the year.");
|
|
529
|
+
}
|
|
530
|
+
const costs = reports.map((r) => r.stats.cost);
|
|
531
|
+
const totalCostFirstHalf = costs.slice(0, Math.floor(costs.length / 2)).reduce((a, b) => a + b, 0);
|
|
532
|
+
const totalCostSecondHalf = costs.slice(Math.floor(costs.length / 2)).reduce((a, b) => a + b, 0);
|
|
533
|
+
if (totalCostSecondHalf > 0 && totalCostFirstHalf > 0) {
|
|
534
|
+
if (totalCostSecondHalf < totalCostFirstHalf * 0.8) {
|
|
535
|
+
trends.push("Cost efficiency improved over the year \u2014 spending decreased while activity continued.");
|
|
536
|
+
} else if (totalCostSecondHalf > totalCostFirstHalf * 1.5) {
|
|
537
|
+
trends.push("Spending increased substantially \u2014 review model selection and caching strategies.");
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
const totalVasanas = reports.reduce((s, r) => s + r.stats.vasanasCreated, 0);
|
|
541
|
+
const totalVidhis = reports.reduce((s, r) => s + r.stats.vidhisCreated, 0);
|
|
542
|
+
if (totalVasanas > 10) {
|
|
543
|
+
trends.push(`Strong behavioral crystallization: ${totalVasanas} vasanas formed across the year.`);
|
|
544
|
+
}
|
|
545
|
+
if (totalVidhis > 5) {
|
|
546
|
+
trends.push(`Active procedural learning: ${totalVidhis} vidhis extracted from repeated patterns.`);
|
|
547
|
+
}
|
|
548
|
+
if (trends.length === 0) {
|
|
549
|
+
trends.push("Steady, consistent usage throughout the year with no significant inflection points.");
|
|
550
|
+
}
|
|
551
|
+
return trends;
|
|
552
|
+
}
|
|
553
|
+
// ── Markdown Builders ─────────────────────────────────────────────────
|
|
554
|
+
/**
|
|
555
|
+
* Build the Markdown content for a monthly report.
|
|
556
|
+
*/
|
|
557
|
+
_buildMonthlyMarkdown(period, stats, vasanas, vidhis, samskaras, tools, newNodes, newEdges, recommendations) {
|
|
558
|
+
const lines = [];
|
|
559
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
560
|
+
lines.push(`# Monthly Consolidation \u2014 ${this._project} \u2014 ${period}`);
|
|
561
|
+
lines.push(`> Generated: ${now}`);
|
|
562
|
+
lines.push("");
|
|
563
|
+
lines.push("## Summary");
|
|
564
|
+
lines.push(`- **Sessions**: ${stats.sessions}`);
|
|
565
|
+
lines.push(`- **Turns**: ${stats.turns}`);
|
|
566
|
+
lines.push(`- **Tools Used**: ${tools.size > 0 ? [...tools].sort().join(", ") : "none"}`);
|
|
567
|
+
lines.push(`- **Total Tokens**: ${stats.tokens.toLocaleString()}`);
|
|
568
|
+
lines.push(`- **Estimated Cost**: $${stats.cost.toFixed(4)}`);
|
|
569
|
+
lines.push("");
|
|
570
|
+
lines.push("## Vasanas Crystallized");
|
|
571
|
+
if (vasanas.length === 0) {
|
|
572
|
+
lines.push("_No vasanas crystallized this month._");
|
|
573
|
+
} else {
|
|
574
|
+
lines.push("| Tendency | Strength | Valence | Stability |");
|
|
575
|
+
lines.push("|----------|----------|---------|-----------|");
|
|
576
|
+
for (const v of vasanas) {
|
|
577
|
+
lines.push(
|
|
578
|
+
`| ${this._esc(v.name)} | ${v.strength.toFixed(2)} | ${v.valence} | ${v.stability.toFixed(2)} |`
|
|
579
|
+
);
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
lines.push("");
|
|
583
|
+
lines.push("## Vidhis Extracted");
|
|
584
|
+
if (vidhis.length === 0) {
|
|
585
|
+
lines.push("_No vidhis extracted this month._");
|
|
586
|
+
} else {
|
|
587
|
+
lines.push("| Procedure | Steps | Success Rate | Sessions |");
|
|
588
|
+
lines.push("|-----------|-------|--------------|----------|");
|
|
589
|
+
for (const v of vidhis) {
|
|
590
|
+
const stepCount = this._countSteps(v.steps);
|
|
591
|
+
const sessionCount = this._countJsonArray(v.learned_from);
|
|
592
|
+
lines.push(
|
|
593
|
+
`| ${this._esc(v.name)} | ${stepCount} | ${v.success_rate.toFixed(2)} | ${sessionCount} |`
|
|
594
|
+
);
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
lines.push("");
|
|
598
|
+
lines.push("## Top Samskaras");
|
|
599
|
+
if (samskaras.length === 0) {
|
|
600
|
+
lines.push("_No active samskaras this month._");
|
|
601
|
+
} else {
|
|
602
|
+
lines.push("| Pattern | Type | Confidence | Observations |");
|
|
603
|
+
lines.push("|---------|------|------------|--------------|");
|
|
604
|
+
for (const s of samskaras) {
|
|
605
|
+
lines.push(
|
|
606
|
+
`| ${this._esc(this._truncate(s.pattern_content, 60))} | ${s.pattern_type} | ${s.confidence.toFixed(2)} | ${s.observation_count} |`
|
|
607
|
+
);
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
lines.push("");
|
|
611
|
+
lines.push("## Knowledge Graph Growth");
|
|
612
|
+
lines.push(`- New nodes: ${newNodes}`);
|
|
613
|
+
lines.push(`- New edges: ${newEdges}`);
|
|
614
|
+
lines.push("");
|
|
615
|
+
lines.push("## Recommendations");
|
|
616
|
+
for (const rec of recommendations) {
|
|
617
|
+
lines.push(`- ${rec}`);
|
|
618
|
+
}
|
|
619
|
+
lines.push("");
|
|
620
|
+
return lines.join("\n");
|
|
621
|
+
}
|
|
622
|
+
/**
|
|
623
|
+
* Build the Markdown content for a yearly report.
|
|
624
|
+
*/
|
|
625
|
+
_buildYearlyMarkdown(period, stats, vasanas, vidhis, samskaras, yearNodes, yearEdges, monthlyReports, trends, prevYearStats) {
|
|
626
|
+
const lines = [];
|
|
627
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
628
|
+
lines.push(`# Yearly Consolidation \u2014 ${this._project} \u2014 ${period}`);
|
|
629
|
+
lines.push(`> Generated: ${now}`);
|
|
630
|
+
lines.push("");
|
|
631
|
+
lines.push("## Annual Summary");
|
|
632
|
+
lines.push(`- **Sessions**: ${stats.sessions}`);
|
|
633
|
+
lines.push(`- **Turns**: ${stats.turns}`);
|
|
634
|
+
lines.push(`- **Total Tokens**: ${stats.tokens.toLocaleString()}`);
|
|
635
|
+
lines.push(`- **Estimated Cost**: $${stats.cost.toFixed(4)}`);
|
|
636
|
+
lines.push(`- **Vasanas Crystallized**: ${stats.vasanasCreated}`);
|
|
637
|
+
lines.push(`- **Vidhis Extracted**: ${stats.vidhisCreated}`);
|
|
638
|
+
lines.push(`- **Samskaras Active**: ${stats.samskarasActive}`);
|
|
639
|
+
lines.push("");
|
|
640
|
+
if (prevYearStats) {
|
|
641
|
+
lines.push("## Year-over-Year Comparison");
|
|
642
|
+
lines.push("| Metric | Previous Year | This Year | Change |");
|
|
643
|
+
lines.push("|--------|---------------|-----------|--------|");
|
|
644
|
+
const yoy = (label, prev, curr) => {
|
|
645
|
+
const delta = curr - prev;
|
|
646
|
+
const pct = prev > 0 ? (delta / prev * 100).toFixed(1) : "N/A";
|
|
647
|
+
const sign = delta >= 0 ? "+" : "";
|
|
648
|
+
return `| ${label} | ${prev} | ${curr} | ${sign}${typeof pct === "string" && pct !== "N/A" ? pct + "%" : pct} |`;
|
|
649
|
+
};
|
|
650
|
+
lines.push(yoy("Sessions", prevYearStats.sessions, stats.sessions));
|
|
651
|
+
lines.push(yoy("Turns", prevYearStats.turns, stats.turns));
|
|
652
|
+
lines.push(yoy("Tokens", prevYearStats.tokens, stats.tokens));
|
|
653
|
+
lines.push(yoy("Vasanas", prevYearStats.vasanasCreated, stats.vasanasCreated));
|
|
654
|
+
lines.push(yoy("Vidhis", prevYearStats.vidhisCreated, stats.vidhisCreated));
|
|
655
|
+
lines.push("");
|
|
656
|
+
}
|
|
657
|
+
if (monthlyReports.length > 0) {
|
|
658
|
+
lines.push("## Monthly Breakdown");
|
|
659
|
+
lines.push("| Month | Sessions | Turns | Tokens | Cost |");
|
|
660
|
+
lines.push("|-------|----------|-------|--------|------|");
|
|
661
|
+
for (const r of monthlyReports) {
|
|
662
|
+
lines.push(
|
|
663
|
+
`| ${r.period} | ${r.stats.sessions} | ${r.stats.turns} | ${r.stats.tokens.toLocaleString()} | $${r.stats.cost.toFixed(4)} |`
|
|
664
|
+
);
|
|
665
|
+
}
|
|
666
|
+
lines.push("");
|
|
667
|
+
}
|
|
668
|
+
lines.push("## Trends");
|
|
669
|
+
for (const t of trends) {
|
|
670
|
+
lines.push(`- ${t}`);
|
|
671
|
+
}
|
|
672
|
+
lines.push("");
|
|
673
|
+
lines.push("## Top Vasanas of the Year");
|
|
674
|
+
if (vasanas.length === 0) {
|
|
675
|
+
lines.push("_No vasanas crystallized this year._");
|
|
676
|
+
} else {
|
|
677
|
+
lines.push("| Tendency | Strength | Valence | Stability |");
|
|
678
|
+
lines.push("|----------|----------|---------|-----------|");
|
|
679
|
+
for (const v of vasanas.slice(0, 15)) {
|
|
680
|
+
lines.push(
|
|
681
|
+
`| ${this._esc(v.name)} | ${v.strength.toFixed(2)} | ${v.valence} | ${v.stability.toFixed(2)} |`
|
|
682
|
+
);
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
lines.push("");
|
|
686
|
+
lines.push("## Top Vidhis of the Year");
|
|
687
|
+
if (vidhis.length === 0) {
|
|
688
|
+
lines.push("_No vidhis extracted this year._");
|
|
689
|
+
} else {
|
|
690
|
+
lines.push("| Procedure | Steps | Success Rate | Sessions |");
|
|
691
|
+
lines.push("|-----------|-------|--------------|----------|");
|
|
692
|
+
for (const v of vidhis.slice(0, 15)) {
|
|
693
|
+
const stepCount = this._countSteps(v.steps);
|
|
694
|
+
const sessionCount = this._countJsonArray(v.learned_from);
|
|
695
|
+
lines.push(
|
|
696
|
+
`| ${this._esc(v.name)} | ${stepCount} | ${v.success_rate.toFixed(2)} | ${sessionCount} |`
|
|
697
|
+
);
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
lines.push("");
|
|
701
|
+
lines.push("## Top Samskaras of the Year");
|
|
702
|
+
if (samskaras.length === 0) {
|
|
703
|
+
lines.push("_No active samskaras this year._");
|
|
704
|
+
} else {
|
|
705
|
+
lines.push("| Pattern | Type | Confidence | Observations |");
|
|
706
|
+
lines.push("|---------|------|------------|--------------|");
|
|
707
|
+
for (const s of samskaras.slice(0, 20)) {
|
|
708
|
+
lines.push(
|
|
709
|
+
`| ${this._esc(this._truncate(s.pattern_content, 60))} | ${s.pattern_type} | ${s.confidence.toFixed(2)} | ${s.observation_count} |`
|
|
710
|
+
);
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
lines.push("");
|
|
714
|
+
lines.push("## Knowledge Graph Growth");
|
|
715
|
+
lines.push(`- New nodes: ${yearNodes}`);
|
|
716
|
+
lines.push(`- New edges: ${yearEdges}`);
|
|
717
|
+
lines.push("");
|
|
718
|
+
lines.push("## Database Maintenance");
|
|
719
|
+
lines.push("- VACUUM executed on agent.db, graph.db, vectors.db");
|
|
720
|
+
lines.push("");
|
|
721
|
+
return lines.join("\n");
|
|
722
|
+
}
|
|
723
|
+
// ── Utility ───────────────────────────────────────────────────────────
|
|
724
|
+
/** Escape pipe characters for Markdown table cells. */
|
|
725
|
+
_esc(s) {
|
|
726
|
+
return s.replace(/\|/g, "\\|").replace(/\n/g, " ");
|
|
727
|
+
}
|
|
728
|
+
/** Truncate a string to a maximum length, appending ellipsis. */
|
|
729
|
+
_truncate(s, max) {
|
|
730
|
+
if (s.length <= max) return s;
|
|
731
|
+
return s.slice(0, max - 3) + "...";
|
|
732
|
+
}
|
|
733
|
+
/** Parse a JSON array string and return its length, or 0 on failure. */
|
|
734
|
+
_countJsonArray(json) {
|
|
735
|
+
if (!json) return 0;
|
|
736
|
+
try {
|
|
737
|
+
const arr = JSON.parse(json);
|
|
738
|
+
return Array.isArray(arr) ? arr.length : 0;
|
|
739
|
+
} catch {
|
|
740
|
+
return 0;
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
/** Parse a steps JSON and return the count. */
|
|
744
|
+
_countSteps(json) {
|
|
745
|
+
return this._countJsonArray(json);
|
|
746
|
+
}
|
|
747
|
+
};
|
|
748
|
+
|
|
749
|
+
export {
|
|
750
|
+
PeriodicConsolidation
|
|
751
|
+
};
|
|
752
|
+
//# sourceMappingURL=chunk-URGEODS5.js.map
|