pi-mega-compact 0.8.19 → 0.8.21

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 (72) hide show
  1. package/README.md +8 -2
  2. package/dist/extensions/dashboard-client/src/hooks/useApi.js +51 -0
  3. package/dist/extensions/dashboard-client/src/hooks/useSSE.js +63 -0
  4. package/dist/extensions/mega-compact-s38.test.js +28 -2
  5. package/dist/extensions/mega-events/agent-handlers.js +16 -0
  6. package/dist/extensions/mega-events/error-classifier.js +8 -3
  7. package/extensions/dashboard-client/dist/assets/{AchievementTiles-DVgFowc-.js → AchievementTiles-QPo46Pwg.js} +2 -2
  8. package/extensions/dashboard-client/dist/assets/{AchievementTiles-DVgFowc-.js.map → AchievementTiles-QPo46Pwg.js.map} +1 -1
  9. package/extensions/dashboard-client/dist/assets/{AchievementsTab-Bg86TWPU.js → AchievementsTab-BDJxRXRY.js} +2 -2
  10. package/extensions/dashboard-client/dist/assets/{AchievementsTab-Bg86TWPU.js.map → AchievementsTab-BDJxRXRY.js.map} +1 -1
  11. package/extensions/dashboard-client/dist/assets/{CacheTab-DBzoH_q_.js → CacheTab-CUMqXQjd.js} +2 -2
  12. package/extensions/dashboard-client/dist/assets/{CacheTab-DBzoH_q_.js.map → CacheTab-CUMqXQjd.js.map} +1 -1
  13. package/extensions/dashboard-client/dist/assets/{ConfigTab-Bj12LgTh.js → ConfigTab-j1dRzurG.js} +2 -2
  14. package/extensions/dashboard-client/dist/assets/{ConfigTab-Bj12LgTh.js.map → ConfigTab-j1dRzurG.js.map} +1 -1
  15. package/extensions/dashboard-client/dist/assets/{EventsTab-EC0wMsp_.js → EventsTab-CMglXK4p.js} +2 -2
  16. package/extensions/dashboard-client/dist/assets/{EventsTab-EC0wMsp_.js.map → EventsTab-CMglXK4p.js.map} +1 -1
  17. package/extensions/dashboard-client/dist/assets/{GameTab-CISQitbu.js → GameTab-fpaDoyxi.js} +2 -2
  18. package/extensions/dashboard-client/dist/assets/{GameTab-CISQitbu.js.map → GameTab-fpaDoyxi.js.map} +1 -1
  19. package/extensions/dashboard-client/dist/assets/{MetricsTab-us4vVG5D.js → MetricsTab-CWG2crG_.js} +2 -2
  20. package/extensions/dashboard-client/dist/assets/{MetricsTab-us4vVG5D.js.map → MetricsTab-CWG2crG_.js.map} +1 -1
  21. package/extensions/dashboard-client/dist/assets/OverviewTab-BaV2D2nf.js +2 -0
  22. package/extensions/dashboard-client/dist/assets/OverviewTab-BaV2D2nf.js.map +1 -0
  23. package/extensions/dashboard-client/dist/assets/{ReposTab-CiU9AJkb.js → ReposTab-C-0kuZ_0.js} +2 -2
  24. package/extensions/dashboard-client/dist/assets/{ReposTab-CiU9AJkb.js.map → ReposTab-C-0kuZ_0.js.map} +1 -1
  25. package/extensions/dashboard-client/dist/assets/{SessionsTab-DaCUvTC7.js → SessionsTab-CSYhddCA.js} +2 -2
  26. package/extensions/dashboard-client/dist/assets/{SessionsTab-DaCUvTC7.js.map → SessionsTab-CSYhddCA.js.map} +1 -1
  27. package/extensions/dashboard-client/dist/assets/{TimeSavedCard-BAUSCzq5.js → TimeSavedCard-Dy4KRjSM.js} +2 -2
  28. package/extensions/dashboard-client/dist/assets/{TimeSavedCard-BAUSCzq5.js.map → TimeSavedCard-Dy4KRjSM.js.map} +1 -1
  29. package/extensions/dashboard-client/dist/assets/{index-5MPZyayK.js → index-D_WtU2TV.js} +3 -3
  30. package/extensions/dashboard-client/dist/assets/{index-5MPZyayK.js.map → index-D_WtU2TV.js.map} +1 -1
  31. package/extensions/dashboard-client/dist/assets/{index-CDkmYP9l.css → index-og3ghxyY.css} +1 -1
  32. package/extensions/dashboard-client/dist/assets/{useSSE-BSpmWZZi.js → useSSE-C9yVJlGP.js} +2 -2
  33. package/extensions/dashboard-client/dist/assets/{useSSE-BSpmWZZi.js.map → useSSE-C9yVJlGP.js.map} +1 -1
  34. package/extensions/dashboard-client/dist/index.html +2 -2
  35. package/extensions/dashboard-client/package-lock.json +1 -1
  36. package/extensions/dashboard-client/package.json +1 -1
  37. package/extensions/dashboard-client/src/components/SessionContextGauges.tsx +181 -0
  38. package/extensions/dashboard-client/src/main.tsx +1 -0
  39. package/extensions/dashboard-client/src/styles/session-gauges.css +85 -0
  40. package/extensions/dashboard-client/src/tabs/OverviewTab.tsx +13 -11
  41. package/extensions/mega-compact-s38.test.ts +32 -2
  42. package/extensions/mega-events/agent-handlers.ts +15 -0
  43. package/extensions/mega-events/error-classifier.ts +8 -2
  44. package/package.json +1 -1
  45. package/dist/extensions/dashboard-server/helpers.js +0 -37
  46. package/dist/extensions/dashboard-server/html/all-repos-tab.js +0 -26
  47. package/dist/extensions/dashboard-server/html/body-open.js +0 -23
  48. package/dist/extensions/dashboard-server/html/current-repo-tab.js +0 -130
  49. package/dist/extensions/dashboard-server/html/head-open.js +0 -16
  50. package/dist/extensions/dashboard-server/html/high-score-tab.js +0 -25
  51. package/dist/extensions/dashboard-server/html/repo-detail-modal.js +0 -26
  52. package/dist/extensions/dashboard-server/html/script.js +0 -259
  53. package/dist/extensions/dashboard-server/html/styles.js +0 -103
  54. package/dist/extensions/dashboard-server/html/summary-tab.js +0 -19
  55. package/dist/extensions/dashboard-server/html-template.js +0 -41
  56. package/dist/src/store/sqlite/connection.js +0 -35
  57. package/dist/src/store/sqlite/index-store.js +0 -167
  58. package/dist/src/store/sqlite/memory.js +0 -54
  59. package/dist/src/store/sqlite/minhash-lsh.js +0 -47
  60. package/dist/src/store/sqlite/sessions.js +0 -39
  61. package/dist/src/store/sqlite/transaction.js +0 -19
  62. package/dist/src/vectorStore/add.js +0 -260
  63. package/dist/src/vectorStore/dedup.js +0 -52
  64. package/dist/src/vectorStore/index.js +0 -10
  65. package/dist/src/vectorStore/queries.js +0 -83
  66. package/dist/src/vectorStore/search.js +0 -95
  67. package/dist/src/vectorStore/session.js +0 -19
  68. package/dist/src/vectorStore/store.js +0 -105
  69. package/dist/src/vectorStore/types.js +0 -6
  70. package/dist/src/vectorStore/utils.js +0 -23
  71. package/extensions/dashboard-client/dist/assets/OverviewTab-eJ7vFgBE.js +0 -2
  72. package/extensions/dashboard-client/dist/assets/OverviewTab-eJ7vFgBE.js.map +0 -1
@@ -1,103 +0,0 @@
1
- /**
2
- * Inline CSS for the dashboard.
3
- *
4
- * Extracted verbatim from the original monolithic `html-template.ts`.
5
- * Kept as a single cohesive stylesheet — splitting CSS into per-tab files
6
- * would fragment the design system (shared tokens, e.g. `.card`, `.stat-grid`,
7
- * `.meter-*` are reused across all tabs).
8
- */
9
- export function styles() {
10
- return `<style>
11
- * { margin: 0; padding: 0; box-sizing: border-box; }
12
- body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; background: #0d1117; color: #c9d1d9; padding: 24px; line-height: 1.5; }
13
- h1 { font-size: 20px; font-weight: 600; margin-bottom: 20px; display: flex; align-items: center; gap: 10px; color: #f0f6fc; }
14
- h1 .tier { background: #1f6feb; color: #fff; font-size: 11px; font-weight: 700; padding: 2px 8px; border-radius: 10px; text-transform: uppercase; letter-spacing: .5px; }
15
- .grid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-bottom: 20px; }
16
- .card { background: #161b22; border: 1px solid #30363d; border-radius: 8px; padding: 16px; }
17
- .card.safe { border-color: #238636; }
18
- .card.safe h2 { color: #3fb950; }
19
- .safe-note { font-size: 12px; color: #8b949e; margin: 12px 0 0; line-height: 1.5; }
20
- .value.ok { color: #3fb950; }
21
- .label {
22
- cursor: help;
23
- border-bottom: 1px dotted #484f58;
24
- }
25
- .card.legend { grid-column: 1 / -1; }
26
- .legend-list { margin: 0; padding-left: 18px; color: #c9d1d9; }
27
- .legend-list li { margin-bottom: 8px; line-height: 1.5; }
28
- .legend-list b { color: #f0f6fc; }
29
- .legend-note { font-size: 12px; color: #8b949e; margin: 12px 0 0; font-style: italic; }
30
- .card h2 { font-size: 12px; text-transform: uppercase; letter-spacing: .5px; color: #8b949e; margin-bottom: 12px; font-weight: 600; }
31
- .meter-track { background: #21262d; border-radius: 4px; height: 20px; overflow: hidden; margin: 8px 0; }
32
- .meter-fill { height: 100%; border-radius: 4px; transition: width .6s ease; min-width: 2px; }
33
- .meter-green { background: #238636; }
34
- .meter-yellow { background: #d29922; }
35
- .meter-red { background: #f85149; }
36
- .meter-label { font-size: 24px; font-weight: 700; color: #f0f6fc; }
37
- .meter-sub { font-size: 12px; color: #8b949e; }
38
- .status-row { display: flex; align-items: center; gap: 8px; margin: 6px 0; font-size: 14px; }
39
- .status-row .bullet { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }
40
- .bullet-on { background: #3fb950; box-shadow: 0 0 6px #3fb95088; }
41
- .bullet-off { background: #484f58; }
42
- .bullet-na { background: #d29922; }
43
- .state-text { font-size: 13px; color: #8b949e; margin-top: 8px; font-family: monospace; }
44
- .stat-grid { display: grid; grid-template-columns: auto 1fr; gap: 4px 12px; font-size: 14px; }
45
- .stat-grid .label { color: #8b949e; }
46
- .stat-grid .value { color: #f0f6fc; font-weight: 600; font-family: monospace; }
47
- .conf-grid { display: grid; grid-template-columns: auto 1fr; gap: 4px 12px; font-size: 14px; }
48
- .conf-grid .label { color: #8b949e; }
49
- .conf-grid .value { color: #f0f6fc; font-family: monospace; }
50
- .events { background: #161b22; border: 1px solid #30363d; border-radius: 8px; padding: 16px; }
51
- .events h2 { font-size: 12px; text-transform: uppercase; letter-spacing: .5px; color: #8b949e; margin-bottom: 12px; font-weight: 600; }
52
- .events-wrap { max-height: 240px; overflow-y: auto; font-family: monospace; font-size: 12px; }
53
- .ev { padding: 3px 0; border-bottom: 1px solid #21262d; display: flex; gap: 8px; align-items: baseline; }
54
- .ev:last-child { border-bottom: none; }
55
- .ev-type { font-weight: 700; min-width: 70px; text-align: right; }
56
- .ev-type-compact { color: #3fb950; }
57
- .ev-type-recall { color: #a371f7; }
58
- .ev-time { color: #484f58; font-size: 10px; min-width: 80px; }
59
- .ev-detail { color: #8b949e; flex: 1; }
60
- .updated { font-size: 11px; color: #484f58; margin-top: 16px; text-align: right; }
61
- .empty { color: #484f58; font-style: italic; font-size: 13px; padding: 8px 0; }
62
- .offline-banner { background: #f8514922; border: 1px solid #f85149; border-radius: 6px; padding: 10px 16px; margin-bottom: 16px; font-size: 13px; color: #f85149; display: none; }
63
- .tabs { display: flex; gap: 8px; margin-bottom: 20px; }
64
- .tab { background: #161b22; color: #8b949e; border: 1px solid #30363d; border-radius: 6px; padding: 8px 16px; font-size: 13px; font-weight: 600; cursor: pointer; transition: all .15s ease; }
65
- .tab:hover { color: #c9d1d9; border-color: #484f58; }
66
- .tab.active { background: #1f6feb; color: #fff; border-color: #1f6feb; }
67
- .tab.future { opacity: .7; }
68
- .tab .soon { font-size: 9px; font-weight: 700; background: #6e40c9; color: #fff; padding: 1px 6px; border-radius: 8px; margin-left: 6px; text-transform: uppercase; letter-spacing: .5px; }
69
- .tab-panel { display: none; }
70
- .tab-panel.active { display: block; }
71
- .summary-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 16px; margin-bottom: 20px; }
72
- .summary-card { background: #161b22; border: 1px solid #30363d; border-radius: 8px; padding: 16px; }
73
- .summary-card .num { font-size: 24px; font-weight: 700; color: #f0f6fc; }
74
- .summary-card .lbl { font-size: 12px; color: #8b949e; text-transform: uppercase; letter-spacing: .5px; margin-top: 4px; }
75
- table.repos { width: 100%; border-collapse: collapse; background: #161b22; border: 1px solid #30363d; border-radius: 8px; overflow: hidden; }
76
- table.repos th, table.repos td { text-align: left; padding: 10px 14px; font-size: 13px; border-bottom: 1px solid #21262d; }
77
- table.repos th { color: #8b949e; text-transform: uppercase; letter-spacing: .5px; font-size: 11px; background: #0d1117; }
78
- table.repos td.num { font-family: monospace; color: #f0f6fc; text-align: right; }
79
- table.repos tr:last-child td { border-bottom: none; }
80
- table.repos tr:hover td { background: #1c2128; }
81
- .repo-model { color: #a371f7; }
82
- .repo-none { color: #484f58; font-style: italic; }
83
- .updated { font-size: 11px; color: #484f58; margin-top: 16px; text-align: right; }
84
- .model-pill { background: #6e40c9; color: #fff; font-size: 11px; font-weight: 700; padding: 2px 8px; border-radius: 10px; text-transform: uppercase; letter-spacing: .5px; }
85
- .card.cost h2 { color: #a371f7; }
86
- .cost-usd { font-size: 22px; font-weight: 700; color: #3fb950; }
87
- .cost-sub { font-size: 12px; color: #8b949e; margin-top: 4px; }
88
- .repo-link { cursor: pointer; }
89
- .repo-link:hover td { color: #58a6ff; }
90
- .repo-detail { position: fixed; inset: 0; background: rgba(0,0,0,.6); display: none; align-items: center; justify-content: center; z-index: 50; }
91
- .repo-detail.open { display: flex; }
92
- .repo-detail-box { background: #161b22; border: 1px solid #30363d; border-radius: 10px; padding: 24px; width: 560px; max-width: 92vw; max-height: 86vh; overflow-y: auto; }
93
- .repo-detail-box h2 { font-size: 14px; color: #f0f6fc; margin-bottom: 14px; display: flex; justify-content: space-between; align-items: center; }
94
- .repo-close { cursor: pointer; color: #8b949e; font-size: 20px; line-height: 1; border: none; background: none; padding: 0 4px; }
95
- .repo-close:hover { color: #f0f6fc; }
96
- .repo-path { font-size: 11px; color: #484f58; word-break: break-all; margin: -8px 0 12px; }
97
- .placeholder { text-align: center; padding: 48px 16px; color: #8b949e; }
98
- .placeholder .em { font-size: 40px; margin-bottom: 12px; }
99
- .placeholder h2 { font-size: 16px; color: #f0f6fc; margin-bottom: 8px; }
100
- .placeholder p { font-size: 13px; line-height: 1.6; max-width: 420px; margin: 0 auto; }
101
- </style>
102
- `;
103
- }
@@ -1,19 +0,0 @@
1
- /**
2
- * "Summary" tab panel — aggregate counts across all registered repos.
3
- *
4
- * Populated by the index poller in `script.ts`.
5
- */
6
- export function summaryTab() {
7
- return `<!-- Summary (aggregate across all repos) -->
8
- <div class="tab-panel" id="panel-summary">
9
- <div class="summary-grid">
10
- <div class="summary-card"><div class="num" id="sm-repos">0</div><div class="lbl">Repositories</div></div>
11
- <div class="summary-card"><div class="num" id="sm-checkpoints">0</div><div class="lbl">Total Checkpoints</div></div>
12
- <div class="summary-card"><div class="num" id="sm-saved">0</div><div class="lbl">Total Tokens Saved</div></div>
13
- <div class="summary-card"><div class="num" id="sm-bytes">0 B</div><div class="lbl">Compressed-Original</div></div>
14
- </div>
15
- <div class="updated" id="sm-updated"></div>
16
- </div>
17
-
18
- `;
19
- }
@@ -1,41 +0,0 @@
1
- /**
2
- * HTML template for the dashboard server — composer.
3
- *
4
- * Originally a single 518-line monolithic file returning one large HTML page
5
- * template. Split into per-concern fragments under `./html/` (each tab is its
6
- * own file; plus shared styles, header chrome, the repo-detail modal, and the
7
- * client script). This file composes them back into the full document.
8
- *
9
- * `tierName` is threaded into the heading badge and the Configuration card.
10
- *
11
- * Fragment layout:
12
- * headOpen() — <!DOCTYPE html>…<title>
13
- * styles() — <style>…</style> (shared CSS, design-system level)
14
- * bodyOpen(tier) — </head><body>…<h1>…</nav> (incl. High Score future tab)
15
- * currentRepoTab(tier) — panel-current (cards + event stream + in-panel table)
16
- * allReposTab() — panel-all (machine-wide repo table)
17
- * summaryTab() — panel-summary (aggregate cards)
18
- * highScoreTab() — panel-highscore (FUTURE placeholder, next milestone)
19
- * repoDetailModal() — per-repo overlay (position-independent)
20
- * script() — <script>…</script></body></html>
21
- */
22
- import { headOpen } from "./html/head-open.js";
23
- import { styles } from "./html/styles.js";
24
- import { bodyOpen } from "./html/body-open.js";
25
- import { currentRepoTab } from "./html/current-repo-tab.js";
26
- import { allReposTab } from "./html/all-repos-tab.js";
27
- import { summaryTab } from "./html/summary-tab.js";
28
- import { highScoreTab } from "./html/high-score-tab.js";
29
- import { repoDetailModal } from "./html/repo-detail-modal.js";
30
- import { script } from "./html/script.js";
31
- export function dashboardHtml(tierName) {
32
- return (headOpen() +
33
- styles() +
34
- bodyOpen(tierName) +
35
- currentRepoTab(tierName) +
36
- repoDetailModal() +
37
- allReposTab() +
38
- summaryTab() +
39
- highScoreTab() +
40
- script());
41
- }
@@ -1,35 +0,0 @@
1
- /**
2
- * Per-repo SQLite connection management (open / reuse / close).
3
- *
4
- * In-process cache so the same stateDir reuses one connection (and so a fresh
5
- * VectorStore over the same dir shares the open DB). Cross-process durability
6
- * comes from reopening the same file path — proven by the integration test.
7
- */
8
- import { DatabaseSync } from "node:sqlite";
9
- import { existsSync, mkdirSync } from "node:fs";
10
- import { join } from "node:path";
11
- import { getStateDir } from "../../store.js";
12
- import { initSchema } from "./schema.js";
13
- const cache = new Map();
14
- /** Open (or reuse) the SQLite store for a state dir. */
15
- export function openStore(stateDir = getStateDir()) {
16
- const existing = cache.get(stateDir);
17
- if (existing)
18
- return existing;
19
- if (!existsSync(stateDir))
20
- mkdirSync(stateDir, { recursive: true });
21
- const db = new DatabaseSync(join(stateDir, "sqlite.db"));
22
- db.exec("PRAGMA journal_mode = WAL");
23
- db.exec("PRAGMA foreign_keys = ON");
24
- initSchema(db);
25
- cache.set(stateDir, db);
26
- return db;
27
- }
28
- /** Close and evict a cached connection (test teardown only). */
29
- export function closeStore(stateDir) {
30
- const db = cache.get(stateDir);
31
- if (db) {
32
- db.close();
33
- cache.delete(stateDir);
34
- }
35
- }
@@ -1,167 +0,0 @@
1
- /**
2
- * Global machine-wide index store (Phase 5b).
3
- *
4
- * A single SQLite DB, separate from every per-repo store, that aggregates one
5
- * row per repo this machine has run on. The multi-repo dashboard (Summary /
6
- * All-repos tabs) reads it so ONE dashboard can show every repo's checkpoints,
7
- * tokens saved, and active model — instead of a per-repo dashboard that only
8
- * ever sees the repo it was launched from.
9
- *
10
- * Written by every pi process on repo-switch (bindRepo) + model capture; read by
11
- * the dashboard server. Concurrency across 10+ pi processes is handled by WAL +
12
- * infrequent idempotent upserts (ON CONFLICT). Fully local (PREVENT-PI-004).
13
- */
14
- import { DatabaseSync } from "node:sqlite";
15
- import { existsSync, mkdirSync } from "node:fs";
16
- import { homedir, tmpdir } from "node:os";
17
- import { join } from "node:path";
18
- /** Resolve the machine-wide index directory (env-overridable). */
19
- export function getIndexDir() {
20
- const override = process.env.MEGACOMPACT_INDEX_DIR;
21
- if (override && override.trim() !== "")
22
- return override;
23
- // homedir() can throw in exotic sandboxes; fall back to tmpdir.
24
- try {
25
- return join(homedir(), ".mega-compact-index");
26
- }
27
- catch {
28
- return join(tmpdir(), ".mega-compact-index");
29
- }
30
- }
31
- let indexCache;
32
- let indexCacheDir;
33
- /** Open (or reuse) the machine-wide index DB. WAL for concurrent writers. */
34
- export function openIndexStore(indexDir = getIndexDir()) {
35
- if (indexCache && indexCacheDir === indexDir)
36
- return indexCache;
37
- if (!existsSync(indexDir))
38
- mkdirSync(indexDir, { recursive: true });
39
- const iddb = new DatabaseSync(join(indexDir, "index.sqlite"));
40
- iddb.exec("PRAGMA journal_mode = WAL");
41
- iddb.exec("PRAGMA busy_timeout = 3000"); // tolerate brief cross-process write contention
42
- iddb.exec(`
43
- CREATE TABLE IF NOT EXISTS repo_registry (
44
- repo_root TEXT PRIMARY KEY,
45
- display_name TEXT,
46
- state_dir TEXT NOT NULL,
47
- first_seen INTEGER,
48
- last_seen INTEGER,
49
- last_compacted_at INTEGER,
50
- checkpoint_count INTEGER DEFAULT 0,
51
- tokens_saved INTEGER DEFAULT 0,
52
- compressed_original_bytes INTEGER DEFAULT 0,
53
- provider TEXT,
54
- provider_name TEXT,
55
- model_name TEXT,
56
- input_rate REAL,
57
- output_rate REAL,
58
- model_captured_at INTEGER
59
- );
60
- CREATE INDEX IF NOT EXISTS idx_registry_last_seen ON repo_registry(last_seen DESC);
61
- `);
62
- indexCache = iddb;
63
- indexCacheDir = indexDir;
64
- return iddb;
65
- }
66
- /**
67
- * Upsert a repo's aggregate stats into the global index. Called on repo-switch
68
- * (infrequent). Preserves first_seen + the model columns on update (model is
69
- * written separately by recordRepoModel so we never clobber it here with nulls).
70
- */
71
- export function upsertRepoRegistry(row, indexDir = getIndexDir()) {
72
- const db = openIndexStore(indexDir);
73
- const now = Date.now();
74
- db.prepare(`INSERT INTO repo_registry
75
- (repo_root, display_name, state_dir, first_seen, last_seen, last_compacted_at,
76
- checkpoint_count, tokens_saved, compressed_original_bytes)
77
- VALUES (@repo_root, @display_name, @state_dir, @now, @now, @last_compacted_at,
78
- @checkpoint_count, @tokens_saved, @compressed_original_bytes)
79
- ON CONFLICT(repo_root) DO UPDATE SET
80
- display_name = excluded.display_name,
81
- state_dir = excluded.state_dir,
82
- last_seen = excluded.last_seen,
83
- last_compacted_at = COALESCE(excluded.last_compacted_at, repo_registry.last_compacted_at),
84
- checkpoint_count = excluded.checkpoint_count,
85
- tokens_saved = excluded.tokens_saved,
86
- compressed_original_bytes = excluded.compressed_original_bytes`).run({
87
- repo_root: row.repoRoot,
88
- display_name: row.displayName,
89
- state_dir: row.stateDir,
90
- now,
91
- last_compacted_at: row.lastCompactedAt ?? null,
92
- checkpoint_count: row.checkpointCount,
93
- tokens_saved: row.tokensSaved,
94
- compressed_original_bytes: row.compressedOriginalBytes,
95
- });
96
- }
97
- /**
98
- * Record the active model/provider for a repo in the global index (denormalized
99
- * so the All-repos table shows model without opening each repo's DB). Upserts a
100
- * bare registry row if the repo isn't registered yet.
101
- */
102
- export function recordRepoModel(repoRoot, model, indexDir = getIndexDir()) {
103
- const db = openIndexStore(indexDir);
104
- const now = Date.now();
105
- db.prepare(`INSERT INTO repo_registry
106
- (repo_root, display_name, state_dir, first_seen, last_seen,
107
- provider, provider_name, model_name, input_rate, output_rate, model_captured_at)
108
- VALUES (@repo_root, @display_name, @state_dir, @now, @now,
109
- @provider, @provider_name, @model_name, @input_rate, @output_rate, @now)
110
- ON CONFLICT(repo_root) DO UPDATE SET
111
- last_seen = excluded.last_seen,
112
- provider = excluded.provider,
113
- provider_name = excluded.provider_name,
114
- model_name = excluded.model_name,
115
- input_rate = excluded.input_rate,
116
- output_rate = excluded.output_rate,
117
- model_captured_at = excluded.model_captured_at`).run({
118
- repo_root: repoRoot,
119
- display_name: model.displayName,
120
- state_dir: model.stateDir,
121
- now,
122
- provider: model.provider,
123
- provider_name: model.providerName,
124
- model_name: model.modelName,
125
- input_rate: model.inputRate,
126
- output_rate: model.outputRate,
127
- });
128
- }
129
- function mapRegistryRow(row) {
130
- return {
131
- repoRoot: row.repo_root,
132
- displayName: row.display_name ?? "",
133
- stateDir: row.state_dir,
134
- firstSeen: row.first_seen ?? 0,
135
- lastSeen: row.last_seen ?? 0,
136
- lastCompactedAt: row.last_compacted_at ?? null,
137
- checkpointCount: row.checkpoint_count ?? 0,
138
- tokensSaved: row.tokens_saved ?? 0,
139
- compressedOriginalBytes: row.compressed_original_bytes ?? 0,
140
- provider: row.provider ?? null,
141
- providerName: row.provider_name ?? null,
142
- modelName: row.model_name ?? null,
143
- inputRate: row.input_rate ?? null,
144
- outputRate: row.output_rate ?? null,
145
- modelCapturedAt: row.model_captured_at ?? null,
146
- };
147
- }
148
- /** All registered repos, most-recently-seen first. */
149
- export function listRepoRegistry(indexDir = getIndexDir()) {
150
- const db = openIndexStore(indexDir);
151
- const rows = db.prepare("SELECT * FROM repo_registry ORDER BY last_seen DESC").all();
152
- return rows.map(mapRegistryRow);
153
- }
154
- /** A single repo's registry row, or undefined. */
155
- export function getRepoRegistry(repoRoot, indexDir = getIndexDir()) {
156
- const db = openIndexStore(indexDir);
157
- const row = db.prepare("SELECT * FROM repo_registry WHERE repo_root = ?").get(repoRoot);
158
- return row ? mapRegistryRow(row) : undefined;
159
- }
160
- /** Close the cached index connection (test teardown only). */
161
- export function closeIndexStore() {
162
- if (indexCache) {
163
- indexCache.close();
164
- indexCache = undefined;
165
- indexCacheDir = undefined;
166
- }
167
- }
@@ -1,54 +0,0 @@
1
- /**
2
- * Durable "save to memory" store (taken over from memory extensions).
3
- *
4
- * One SQLite store for user-saved memories, scoped by repo. Mirrors the
5
- * lessons/sessions pattern: all state lives in SQLite from day one. All queries
6
- * are parameterized (PREVENT-002).
7
- */
8
- import { getStateDir } from "../../store.js";
9
- import { openStore } from "./connection.js";
10
- /** Save a memory to the current repo's store. Returns the new row id. */
11
- export function addMemory(memory, repo, stateDir = getStateDir()) {
12
- const db = openStore(stateDir);
13
- const now = Math.floor(Date.now() / 1000);
14
- const res = db
15
- .prepare(`INSERT INTO memories(repo, kind, content, tags, created_at, last_recalled_at)
16
- VALUES(?, ?, ?, ?, ?, NULL)`)
17
- .run(repo ?? null, memory.kind ?? "note", memory.content, JSON.stringify(memory.tags ?? []), now);
18
- return Number(res.lastInsertRowid);
19
- }
20
- /** List recent memories for a repo (or all repos when repo is null). */
21
- export function listMemories(repo, limit = 50, stateDir = getStateDir()) {
22
- const db = openStore(stateDir);
23
- const rows = repo
24
- ? db.prepare("SELECT * FROM memories WHERE repo = ? ORDER BY created_at DESC LIMIT ?").all(repo, limit)
25
- : db.prepare("SELECT * FROM memories ORDER BY created_at DESC LIMIT ?").all(limit);
26
- return rows.map(mapMemoryRow);
27
- }
28
- /** Substring search across content + tags. */
29
- export function searchMemories(query, repo = null, limit = 50, stateDir = getStateDir()) {
30
- const db = openStore(stateDir);
31
- const like = `%${query}%`;
32
- const rows = repo
33
- ? db.prepare("SELECT * FROM memories WHERE repo = ? AND (content LIKE ? OR tags LIKE ?) ORDER BY created_at DESC LIMIT ?").all(repo, like, like, limit)
34
- : db.prepare("SELECT * FROM memories WHERE content LIKE ? OR tags LIKE ? ORDER BY created_at DESC LIMIT ?").all(like, like, limit);
35
- return rows.map(mapMemoryRow);
36
- }
37
- /** Mark a memory as recalled (updates last_recalled_at). Returns true if found. */
38
- export function recallMemory(id, stateDir = getStateDir()) {
39
- const db = openStore(stateDir);
40
- const now = Math.floor(Date.now() / 1000);
41
- const res = db.prepare("UPDATE memories SET last_recalled_at = ? WHERE id = ?").run(now, id);
42
- return res.changes > 0;
43
- }
44
- function mapMemoryRow(row) {
45
- return {
46
- id: row.id,
47
- repo: row.repo ?? null,
48
- kind: row.kind ?? "note",
49
- content: row.content ?? "",
50
- tags: row.tags ? JSON.parse(row.tags) : [],
51
- createdAt: row.created_at ?? 0,
52
- lastRecalledAt: row.last_recalled_at ?? null,
53
- };
54
- }
@@ -1,47 +0,0 @@
1
- /**
2
- * Sprint 11: MinHash signatures + LSH bucket persistence and candidate lookup.
3
- *
4
- * All queries are parameterized (PREVENT-002) — never string-concatenated.
5
- */
6
- import { getStateDir, normalizeSessionId } from "../../store.js";
7
- import { openStore } from "./connection.js";
8
- import { withTx } from "./transaction.js";
9
- /** Persist a checkpoint's MinHash signature (idempotent by chunk_id + version). */
10
- export function upsertMinhashSignature(chunkId, sessionId, signatureVersion, signatures, stateDir = getStateDir()) {
11
- const db = openStore(stateDir);
12
- const sid = normalizeSessionId(sessionId);
13
- db.prepare(`INSERT INTO minhash_signatures(chunk_id, session_id, signature_version, signatures)
14
- VALUES(?, ?, ?, ?)
15
- ON CONFLICT(chunk_id, signature_version) DO UPDATE SET
16
- session_id=excluded.session_id, signatures=excluded.signatures`).run(chunkId, sid, signatureVersion, JSON.stringify(signatures));
17
- }
18
- /** Persist LSH bucket memberships for a chunk (one row per bucket key). */
19
- export function insertLshBuckets(chunkId, sessionId, signatureVersion, bucketKeys, stateDir = getStateDir()) {
20
- const db = openStore(stateDir);
21
- const sid = normalizeSessionId(sessionId);
22
- const del = db.prepare("DELETE FROM dedup_lsh_buckets WHERE chunk_id = ?");
23
- const ins = db.prepare("INSERT OR IGNORE INTO dedup_lsh_buckets(bucket_key, chunk_id, session_id, signature_version) VALUES(?, ?, ?, ?)");
24
- withTx(db, () => {
25
- del.run(chunkId);
26
- for (const key of bucketKeys)
27
- ins.run(key, chunkId, sid, signatureVersion);
28
- });
29
- }
30
- /**
31
- * Candidate chunk_ids sharing any LSH bucket with `bucketKeys`, scoped to the
32
- * session, capped at `limit`. Single query (no N loops) — QA #15 amplification
33
- * guard. Returns DISTINCT chunk_ids excluding `excludeChunkId` (the new row).
34
- */
35
- export function lshCandidateChunks(bucketKeys, sessionId, excludeChunkId, stateDir = getStateDir(), limit = 100) {
36
- if (bucketKeys.length === 0)
37
- return [];
38
- const db = openStore(stateDir);
39
- const sid = normalizeSessionId(sessionId);
40
- const placeholders = bucketKeys.map(() => "?").join(",");
41
- const rows = db
42
- .prepare(`SELECT DISTINCT chunk_id FROM dedup_lsh_buckets
43
- WHERE bucket_key IN (${placeholders}) AND session_id = ? AND chunk_id != ?
44
- LIMIT ?`)
45
- .all(...bucketKeys, sid, excludeChunkId, limit);
46
- return rows.map((r) => r.chunk_id);
47
- }
@@ -1,39 +0,0 @@
1
- /**
2
- * Future-feature foundation: resume sessions, daily activity log, lessons learned.
3
- *
4
- * Scaffolded tables + minimal helpers so all store data lives in SQLite from
5
- * day one. Full UI/recall for these lands in later sprints. All queries are
6
- * parameterized (PREVENT-002).
7
- */
8
- import { getStateDir, normalizeSessionId } from "../../store.js";
9
- import { openStore } from "./connection.js";
10
- /** Upsert a `sessions` row (resume + per-repo session history). */
11
- export function touchSession(sessionId, repo, stateDir = getStateDir()) {
12
- const db = openStore(stateDir);
13
- const sid = normalizeSessionId(sessionId);
14
- const existing = db
15
- .prepare("SELECT started_at FROM sessions WHERE session_id = ?")
16
- .get(sid);
17
- const now = Math.floor(Date.now() / 1000);
18
- if (!existing) {
19
- db.prepare(`INSERT INTO sessions(session_id, repo, started_at, last_compacted_at, status)
20
- VALUES(?, ?, ?, ?, 'active')`).run(sid, repo ?? null, now, now);
21
- }
22
- else {
23
- db.prepare("UPDATE sessions SET last_compacted_at = ?, repo = COALESCE(?, repo), status = 'active' WHERE session_id = ?").run(now, repo ?? null, sid);
24
- }
25
- }
26
- /** Append a `daily_log` entry (day = YYYY-MM-DD, local-naive from Date). */
27
- export function logDaily(sessionId, event, detail, tokensSaved, stateDir = getStateDir()) {
28
- const db = openStore(stateDir);
29
- const day = new Date().toISOString().slice(0, 10);
30
- const now = Math.floor(Date.now() / 1000);
31
- db.prepare(`INSERT INTO daily_log(day, session_id, event, detail, tokens_saved, ts)
32
- VALUES(?, ?, ?, ?, ?, ?)`).run(day, normalizeSessionId(sessionId), event, detail ?? null, tokensSaved, now);
33
- }
34
- /** Append a `lessons` entry (future lessons-learned browse/recall). */
35
- export function addLesson(sessionId, repo, lesson, stateDir = getStateDir()) {
36
- const db = openStore(stateDir);
37
- const now = Math.floor(Date.now() / 1000);
38
- db.prepare(`INSERT INTO lessons(session_id, repo, lesson, ts) VALUES(?, ?, ?, ?)`).run(normalizeSessionId(sessionId), repo ?? null, lesson, now);
39
- }
@@ -1,19 +0,0 @@
1
- /**
2
- * Transaction wrapper using SAVEPOINT so it nests safely under an outer
3
- * transaction (unlike `BEGIN`, which SQLite rejects when one is already open).
4
- *
5
- * Mirrors better-sqlite3's `db.transaction(fn)` semantics — callers that wrap a
6
- * batch in withTx (e.g. backfill) can still call helpers that also use withTx.
7
- */
8
- export function withTx(db, fn) {
9
- db.exec("SAVEPOINT mc_tx");
10
- try {
11
- fn();
12
- db.exec("RELEASE mc_tx");
13
- }
14
- catch (e) {
15
- db.exec("ROLLBACK TO mc_tx");
16
- db.exec("RELEASE mc_tx");
17
- throw e;
18
- }
19
- }