@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.
Files changed (41) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +365 -0
  3. package/chunks/chunk-E5A3SCDJ.js +246 -0
  4. package/chunks/chunk-G5VYCA6O.js +69 -0
  5. package/chunks/chunk-H76V36OF.js +1029 -0
  6. package/chunks/chunk-HAPVUJ6A.js +238 -0
  7. package/chunks/chunk-IEKAYVA3.js +137 -0
  8. package/chunks/chunk-IGKYKEKT.js +43 -0
  9. package/chunks/chunk-IIET2K6D.js +7728 -0
  10. package/chunks/chunk-ITIVYGUG.js +347 -0
  11. package/chunks/chunk-JAWZ7ANC.js +208 -0
  12. package/chunks/chunk-JZU37VQ5.js +714 -0
  13. package/chunks/chunk-KC6NRZ7U.js +198 -0
  14. package/chunks/chunk-KDRROLVN.js +433 -0
  15. package/chunks/chunk-L7JICQBW.js +1006 -0
  16. package/chunks/chunk-MINFB5LT.js +1479 -0
  17. package/chunks/chunk-MJ74G5RB.js +5816 -0
  18. package/chunks/chunk-S4TBVCL2.js +2158 -0
  19. package/chunks/chunk-SMVJRPAH.js +2753 -0
  20. package/chunks/chunk-U6OLJ36B.js +438 -0
  21. package/chunks/chunk-URGEODS5.js +752 -0
  22. package/chunks/chunk-YSU3BWV6.js +123 -0
  23. package/chunks/consolidation-indexer-TOTTDZXW.js +21 -0
  24. package/chunks/day-consolidation-NKO63HZQ.js +24 -0
  25. package/chunks/graphrag-ZI2FSU7S.js +13 -0
  26. package/chunks/hierarchical-temporal-search-ZD46UMKR.js +8 -0
  27. package/chunks/hybrid-search-ZVLZVGFS.js +19 -0
  28. package/chunks/memory-store-KNJPMBLQ.js +17 -0
  29. package/chunks/periodic-consolidation-BPKOZDGB.js +10 -0
  30. package/chunks/postgres-3ZXBYTPC.js +8 -0
  31. package/chunks/recall-GMVHWQWW.js +20 -0
  32. package/chunks/search-7HZETVMZ.js +18 -0
  33. package/chunks/session-store-XKPGKXUS.js +44 -0
  34. package/chunks/sqlite-JPF5TICX.js +152 -0
  35. package/chunks/src-6GVZTUH6.js +12 -0
  36. package/chunks/src-QAXOD5SB.js +273 -0
  37. package/chunks/suncalc-NOHGYHDU.js +186 -0
  38. package/chunks/tree-RSHKDTCR.js +10 -0
  39. package/gateway.js +61944 -0
  40. package/package.json +51 -0
  41. 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