@gamaze/hicortex 0.14.3 → 0.15.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/README.md +6 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +40 -0
- package/dist/cluster.d.ts +51 -0
- package/dist/cluster.js +118 -0
- package/dist/consolidate.d.ts +63 -1
- package/dist/consolidate.js +228 -2
- package/dist/db.js +26 -0
- package/dist/dedup.d.ts +157 -0
- package/dist/dedup.js +445 -0
- package/dist/eval/decay-eval.d.ts +110 -0
- package/dist/eval/decay-eval.js +252 -0
- package/dist/eval/dups.d.ts +100 -0
- package/dist/eval/dups.js +174 -0
- package/dist/eval/eval-db.d.ts +25 -0
- package/dist/eval/eval-db.js +67 -0
- package/dist/eval/graph-eval.d.ts +76 -0
- package/dist/eval/graph-eval.js +200 -0
- package/dist/eval/reflection-census.d.ts +19 -0
- package/dist/eval/reflection-census.js +25 -0
- package/dist/eval/run-eval.d.ts +17 -0
- package/dist/eval/run-eval.js +275 -0
- package/dist/mcp-server.js +30 -11
- package/dist/memory-instructions.d.ts +38 -0
- package/dist/memory-instructions.js +63 -0
- package/dist/nightly.js +4 -0
- package/dist/relink.d.ts +3 -2
- package/dist/relink.js +6 -11
- package/dist/seed-lesson.d.ts +1 -1
- package/dist/seed-lesson.js +1 -1
- package/dist/state.d.ts +10 -0
- package/dist/storage.d.ts +10 -0
- package/dist/storage.js +23 -0
- package/dist/types.d.ts +15 -0
- package/package.json +2 -1
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Read-only snapshot access for the #191 mechanical audit baseline.
|
|
4
|
+
*
|
|
5
|
+
* The eval NEVER touches a live database — it runs against a checkpointed
|
|
6
|
+
* copy (`data/audit-<date>/snapshot.db`, gitignored). This module opens that
|
|
7
|
+
* copy in better-sqlite3's `readonly` mode and loads the sqlite-vec
|
|
8
|
+
* extension the same way `db.ts#initDb` does, WITHOUT calling `initDb()`
|
|
9
|
+
* itself: `initDb` runs schema migrations, which write to the file. A
|
|
10
|
+
* snapshot is assumed to already be at the current schema version (verified
|
|
11
|
+
* by `assertReadonly`, which also proves no migration silently ran).
|
|
12
|
+
*/
|
|
13
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
14
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.openSnapshot = openSnapshot;
|
|
18
|
+
exports.blobToEmbedding = blobToEmbedding;
|
|
19
|
+
const better_sqlite3_1 = __importDefault(require("better-sqlite3"));
|
|
20
|
+
const node_fs_1 = require("node:fs");
|
|
21
|
+
/**
|
|
22
|
+
* Open a DB snapshot for read-only analysis.
|
|
23
|
+
*
|
|
24
|
+
* Throws if the path does not exist, or if a write attempt against the
|
|
25
|
+
* returned connection would (surprisingly) succeed — the second check is a
|
|
26
|
+
* belt-and-suspenders guard against a future better-sqlite3/OS combination
|
|
27
|
+
* where `readonly: true` is silently ignored (e.g. a non-standard
|
|
28
|
+
* filesystem), so a bug can never turn the audit into a mutation of
|
|
29
|
+
* production data.
|
|
30
|
+
*/
|
|
31
|
+
function openSnapshot(dbPath) {
|
|
32
|
+
if (!(0, node_fs_1.existsSync)(dbPath)) {
|
|
33
|
+
throw new Error(`Snapshot DB not found at ${dbPath}`);
|
|
34
|
+
}
|
|
35
|
+
const db = new better_sqlite3_1.default(dbPath, { readonly: true });
|
|
36
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
37
|
+
const sqliteVec = require("sqlite-vec");
|
|
38
|
+
sqliteVec.load(db);
|
|
39
|
+
assertReadonly(db);
|
|
40
|
+
return db;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Verify the connection truly refuses writes. Uses a table guaranteed to
|
|
44
|
+
* exist in any migrated Hicortex DB (`schema_version`) and a no-op-shaped
|
|
45
|
+
* statement (touches a version number that cannot exist) so that even if the
|
|
46
|
+
* guard somehow failed open, the blast radius is a single junk row rather
|
|
47
|
+
* than corruption of real data.
|
|
48
|
+
*/
|
|
49
|
+
function assertReadonly(db) {
|
|
50
|
+
let wroteSuccessfully = false;
|
|
51
|
+
try {
|
|
52
|
+
db.prepare("INSERT INTO schema_version (version, name, applied_at) VALUES (-1, '__eval_readonly_probe__', '')").run();
|
|
53
|
+
wroteSuccessfully = true;
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
// Expected: SQLITE_READONLY. The connection is safe to use.
|
|
57
|
+
}
|
|
58
|
+
if (wroteSuccessfully) {
|
|
59
|
+
throw new Error("Snapshot DB accepted a write — refusing to run the eval against a " +
|
|
60
|
+
"connection that is not truly read-only. Check the better-sqlite3 " +
|
|
61
|
+
"readonly option and filesystem permissions.");
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/** Convert a sqlite-vec embedding BLOB (as read back from `memory_vectors`) to a Float32Array. */
|
|
65
|
+
function blobToEmbedding(blob) {
|
|
66
|
+
return new Float32Array(blob.buffer, blob.byteOffset, blob.byteLength / 4);
|
|
67
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* D6 — link-graph health audit (#191 mechanical baseline).
|
|
3
|
+
*
|
|
4
|
+
* Of the ~6.5k links: how many are meaningful vs near-duplicate noise, does
|
|
5
|
+
* the stored `memory_links.strength` still match a fresh cosine recompute
|
|
6
|
+
* (drift), and how do all these stats look before vs after the
|
|
7
|
+
* `relinkCursor` watermark (the resumable `hicortex relink` migration to the
|
|
8
|
+
* corrected cosine formula, #145).
|
|
9
|
+
*/
|
|
10
|
+
import type Database from "better-sqlite3";
|
|
11
|
+
export interface LinkRow {
|
|
12
|
+
source_id: string;
|
|
13
|
+
target_id: string;
|
|
14
|
+
relationship: string;
|
|
15
|
+
strength: number;
|
|
16
|
+
}
|
|
17
|
+
/** Cosine similarity between two L2-normalized embeddings (dot product). */
|
|
18
|
+
export declare function cosineBetween(a: Float32Array, b: Float32Array): number;
|
|
19
|
+
export declare function byRelationshipCounts(links: LinkRow[]): Record<string, number>;
|
|
20
|
+
/**
|
|
21
|
+
* Partition links by whether their SOURCE memory's rowid has been covered by
|
|
22
|
+
* the `hicortex relink` watermark. `relinkCursor` is the last fully
|
|
23
|
+
* committed rowid (relink.ts) — relink iterates memories by rowid and
|
|
24
|
+
* discovers/refreshes links FROM each one, so a link's source rowid <=
|
|
25
|
+
* cursor means a relink pass has already run for that source (current
|
|
26
|
+
* formula); null cursor (never run) puts everything in "notYetRelinked".
|
|
27
|
+
*/
|
|
28
|
+
export declare function partitionByRelinkCursor(links: LinkRow[], sourceRowid: Map<string, number>, relinkCursor: number | null): {
|
|
29
|
+
relinked: LinkRow[];
|
|
30
|
+
notYetRelinked: LinkRow[];
|
|
31
|
+
};
|
|
32
|
+
export interface HubEntry {
|
|
33
|
+
id: string;
|
|
34
|
+
degree: number;
|
|
35
|
+
project: string | null;
|
|
36
|
+
domain: string | null;
|
|
37
|
+
preview: string;
|
|
38
|
+
}
|
|
39
|
+
export interface DegreeReport {
|
|
40
|
+
memoriesWithLinks: number;
|
|
41
|
+
totalMemories: number;
|
|
42
|
+
degreeHistogram: Record<string, number>;
|
|
43
|
+
topHubs: HubEntry[];
|
|
44
|
+
}
|
|
45
|
+
export declare function runDegreeAudit(db: Database.Database, topN?: number): DegreeReport;
|
|
46
|
+
export interface DriftReport {
|
|
47
|
+
sampleSize: number;
|
|
48
|
+
driftHistogram: Record<string, number>;
|
|
49
|
+
meanAbsDrift: number;
|
|
50
|
+
maxAbsDrift: number;
|
|
51
|
+
skippedMissingEmbedding: number;
|
|
52
|
+
}
|
|
53
|
+
/** Recompute cosine for a random link sample and compare to stored `strength` (post migration-5 rescale, should track closely). */
|
|
54
|
+
export declare function runDriftSample(db: Database.Database, embeddings: Map<string, Float32Array>, sampleSize?: number): DriftReport;
|
|
55
|
+
export interface PartitionStats {
|
|
56
|
+
linkCount: number;
|
|
57
|
+
byRelationship: Record<string, number>;
|
|
58
|
+
dupNoiseLinks: number;
|
|
59
|
+
dupNoiseShare: number;
|
|
60
|
+
}
|
|
61
|
+
export interface GraphReport {
|
|
62
|
+
totalLinks: number;
|
|
63
|
+
byRelationship: Record<string, number>;
|
|
64
|
+
degree: DegreeReport;
|
|
65
|
+
drift: DriftReport;
|
|
66
|
+
dupNoise: {
|
|
67
|
+
threshold: number;
|
|
68
|
+
count: number;
|
|
69
|
+
share: number;
|
|
70
|
+
measured: number;
|
|
71
|
+
};
|
|
72
|
+
relinkCursor: number | null;
|
|
73
|
+
relinkedPartition: PartitionStats;
|
|
74
|
+
notYetRelinkedPartition: PartitionStats;
|
|
75
|
+
}
|
|
76
|
+
export declare function runGraphAudit(db: Database.Database, stateDir?: string): GraphReport;
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* D6 — link-graph health audit (#191 mechanical baseline).
|
|
4
|
+
*
|
|
5
|
+
* Of the ~6.5k links: how many are meaningful vs near-duplicate noise, does
|
|
6
|
+
* the stored `memory_links.strength` still match a fresh cosine recompute
|
|
7
|
+
* (drift), and how do all these stats look before vs after the
|
|
8
|
+
* `relinkCursor` watermark (the resumable `hicortex relink` migration to the
|
|
9
|
+
* corrected cosine formula, #145).
|
|
10
|
+
*/
|
|
11
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
12
|
+
if (k2 === undefined) k2 = k;
|
|
13
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
14
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
15
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
16
|
+
}
|
|
17
|
+
Object.defineProperty(o, k2, desc);
|
|
18
|
+
}) : (function(o, m, k, k2) {
|
|
19
|
+
if (k2 === undefined) k2 = k;
|
|
20
|
+
o[k2] = m[k];
|
|
21
|
+
}));
|
|
22
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
23
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
24
|
+
}) : function(o, v) {
|
|
25
|
+
o["default"] = v;
|
|
26
|
+
});
|
|
27
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
28
|
+
var ownKeys = function(o) {
|
|
29
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
30
|
+
var ar = [];
|
|
31
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
32
|
+
return ar;
|
|
33
|
+
};
|
|
34
|
+
return ownKeys(o);
|
|
35
|
+
};
|
|
36
|
+
return function (mod) {
|
|
37
|
+
if (mod && mod.__esModule) return mod;
|
|
38
|
+
var result = {};
|
|
39
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
40
|
+
__setModuleDefault(result, mod);
|
|
41
|
+
return result;
|
|
42
|
+
};
|
|
43
|
+
})();
|
|
44
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
45
|
+
exports.cosineBetween = cosineBetween;
|
|
46
|
+
exports.byRelationshipCounts = byRelationshipCounts;
|
|
47
|
+
exports.partitionByRelinkCursor = partitionByRelinkCursor;
|
|
48
|
+
exports.runDegreeAudit = runDegreeAudit;
|
|
49
|
+
exports.runDriftSample = runDriftSample;
|
|
50
|
+
exports.runGraphAudit = runGraphAudit;
|
|
51
|
+
const eval_db_js_1 = require("./eval-db.js");
|
|
52
|
+
const storage = __importStar(require("../storage.js"));
|
|
53
|
+
const state_js_1 = require("../state.js");
|
|
54
|
+
const decay_eval_js_1 = require("./decay-eval.js");
|
|
55
|
+
/** Matches the middle D1 duplicate threshold — a link between near-dups is noise, not signal. */
|
|
56
|
+
const DUP_NOISE_THRESHOLD = 0.92;
|
|
57
|
+
const DRIFT_SAMPLE_SIZE = 500;
|
|
58
|
+
const DEGREE_BUCKET_EDGES = [0, 1, 2, 3, 5, 10, 20, 50, 100];
|
|
59
|
+
const DRIFT_BUCKET_EDGES = [0, 0.01, 0.02, 0.05, 0.1, 0.2, 0.3, 0.5, 1.0];
|
|
60
|
+
// ---------------------------------------------------------------------------
|
|
61
|
+
// Pure helpers (unit tested)
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
/** Cosine similarity between two L2-normalized embeddings (dot product). */
|
|
64
|
+
function cosineBetween(a, b) {
|
|
65
|
+
let dot = 0;
|
|
66
|
+
for (let i = 0; i < a.length; i++)
|
|
67
|
+
dot += a[i] * b[i];
|
|
68
|
+
return dot;
|
|
69
|
+
}
|
|
70
|
+
function byRelationshipCounts(links) {
|
|
71
|
+
const counts = {};
|
|
72
|
+
for (const l of links) {
|
|
73
|
+
counts[l.relationship] = (counts[l.relationship] ?? 0) + 1;
|
|
74
|
+
}
|
|
75
|
+
return counts;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Partition links by whether their SOURCE memory's rowid has been covered by
|
|
79
|
+
* the `hicortex relink` watermark. `relinkCursor` is the last fully
|
|
80
|
+
* committed rowid (relink.ts) — relink iterates memories by rowid and
|
|
81
|
+
* discovers/refreshes links FROM each one, so a link's source rowid <=
|
|
82
|
+
* cursor means a relink pass has already run for that source (current
|
|
83
|
+
* formula); null cursor (never run) puts everything in "notYetRelinked".
|
|
84
|
+
*/
|
|
85
|
+
function partitionByRelinkCursor(links, sourceRowid, relinkCursor) {
|
|
86
|
+
const relinked = [];
|
|
87
|
+
const notYetRelinked = [];
|
|
88
|
+
for (const l of links) {
|
|
89
|
+
const rowid = sourceRowid.get(l.source_id);
|
|
90
|
+
const covered = relinkCursor !== null && rowid !== undefined && rowid <= relinkCursor;
|
|
91
|
+
(covered ? relinked : notYetRelinked).push(l);
|
|
92
|
+
}
|
|
93
|
+
return { relinked, notYetRelinked };
|
|
94
|
+
}
|
|
95
|
+
function runDegreeAudit(db, topN = 10) {
|
|
96
|
+
const totalMemories = db.prepare("SELECT COUNT(*) AS c FROM memories").get().c;
|
|
97
|
+
const linkCounts = storage.getAllLinkCounts(db);
|
|
98
|
+
const degreeHistogram = (0, decay_eval_js_1.histogram)([...linkCounts.values()], DEGREE_BUCKET_EDGES);
|
|
99
|
+
const sortedIds = [...linkCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, topN);
|
|
100
|
+
const topHubs = sortedIds.map(([id, degree]) => {
|
|
101
|
+
const mem = storage.getMemory(db, id);
|
|
102
|
+
return {
|
|
103
|
+
id,
|
|
104
|
+
degree,
|
|
105
|
+
project: mem?.project ?? null,
|
|
106
|
+
domain: mem?.domain ?? null,
|
|
107
|
+
preview: (mem?.content ?? "").slice(0, 80),
|
|
108
|
+
};
|
|
109
|
+
});
|
|
110
|
+
return { memoriesWithLinks: linkCounts.size, totalMemories, degreeHistogram, topHubs };
|
|
111
|
+
}
|
|
112
|
+
/** Recompute cosine for a random link sample and compare to stored `strength` (post migration-5 rescale, should track closely). */
|
|
113
|
+
function runDriftSample(db, embeddings, sampleSize = DRIFT_SAMPLE_SIZE) {
|
|
114
|
+
const sample = db
|
|
115
|
+
.prepare("SELECT source_id, target_id, strength FROM memory_links ORDER BY RANDOM() LIMIT ?")
|
|
116
|
+
.all(sampleSize);
|
|
117
|
+
const drifts = [];
|
|
118
|
+
let skipped = 0;
|
|
119
|
+
for (const link of sample) {
|
|
120
|
+
const a = embeddings.get(link.source_id);
|
|
121
|
+
const b = embeddings.get(link.target_id);
|
|
122
|
+
if (!a || !b) {
|
|
123
|
+
skipped++;
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
const recomputed = cosineBetween(a, b);
|
|
127
|
+
drifts.push(Math.abs(recomputed - link.strength));
|
|
128
|
+
}
|
|
129
|
+
const meanAbsDrift = drifts.length > 0 ? drifts.reduce((s, d) => s + d, 0) / drifts.length : 0;
|
|
130
|
+
const maxAbsDrift = drifts.length > 0 ? Math.max(...drifts) : 0;
|
|
131
|
+
return {
|
|
132
|
+
sampleSize: drifts.length,
|
|
133
|
+
driftHistogram: (0, decay_eval_js_1.histogram)(drifts, DRIFT_BUCKET_EDGES),
|
|
134
|
+
meanAbsDrift,
|
|
135
|
+
maxAbsDrift,
|
|
136
|
+
skippedMissingEmbedding: skipped,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
function statsFor(links, embeddings) {
|
|
140
|
+
let dupNoise = 0;
|
|
141
|
+
let measured = 0;
|
|
142
|
+
for (const l of links) {
|
|
143
|
+
const a = embeddings.get(l.source_id);
|
|
144
|
+
const b = embeddings.get(l.target_id);
|
|
145
|
+
if (!a || !b)
|
|
146
|
+
continue;
|
|
147
|
+
measured++;
|
|
148
|
+
if (cosineBetween(a, b) >= DUP_NOISE_THRESHOLD)
|
|
149
|
+
dupNoise++;
|
|
150
|
+
}
|
|
151
|
+
return {
|
|
152
|
+
linkCount: links.length,
|
|
153
|
+
byRelationship: byRelationshipCounts(links),
|
|
154
|
+
dupNoiseLinks: dupNoise,
|
|
155
|
+
dupNoiseShare: measured > 0 ? dupNoise / measured : 0,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
/** Load every stored embedding once as id -> Float32Array. */
|
|
159
|
+
function loadEmbeddings(db) {
|
|
160
|
+
const rows = db.prepare("SELECT id, embedding FROM memory_vectors").all();
|
|
161
|
+
return new Map(rows.map((r) => [r.id, (0, eval_db_js_1.blobToEmbedding)(r.embedding)]));
|
|
162
|
+
}
|
|
163
|
+
function runGraphAudit(db, stateDir) {
|
|
164
|
+
const links = db
|
|
165
|
+
.prepare("SELECT source_id, target_id, relationship, strength FROM memory_links")
|
|
166
|
+
.all();
|
|
167
|
+
const embeddings = loadEmbeddings(db);
|
|
168
|
+
const degree = runDegreeAudit(db);
|
|
169
|
+
const drift = runDriftSample(db, embeddings);
|
|
170
|
+
let dupNoiseCount = 0;
|
|
171
|
+
let dupNoiseMeasured = 0;
|
|
172
|
+
for (const l of links) {
|
|
173
|
+
const a = embeddings.get(l.source_id);
|
|
174
|
+
const b = embeddings.get(l.target_id);
|
|
175
|
+
if (!a || !b)
|
|
176
|
+
continue;
|
|
177
|
+
dupNoiseMeasured++;
|
|
178
|
+
if (cosineBetween(a, b) >= DUP_NOISE_THRESHOLD)
|
|
179
|
+
dupNoiseCount++;
|
|
180
|
+
}
|
|
181
|
+
const relinkCursor = (0, state_js_1.loadState)(stateDir).relinkCursor ?? null;
|
|
182
|
+
const rowidRows = db.prepare("SELECT id, rowid AS rowid FROM memories").all();
|
|
183
|
+
const sourceRowid = new Map(rowidRows.map((r) => [r.id, r.rowid]));
|
|
184
|
+
const { relinked, notYetRelinked } = partitionByRelinkCursor(links, sourceRowid, relinkCursor);
|
|
185
|
+
return {
|
|
186
|
+
totalLinks: links.length,
|
|
187
|
+
byRelationship: byRelationshipCounts(links),
|
|
188
|
+
degree,
|
|
189
|
+
drift,
|
|
190
|
+
dupNoise: {
|
|
191
|
+
threshold: DUP_NOISE_THRESHOLD,
|
|
192
|
+
count: dupNoiseCount,
|
|
193
|
+
share: dupNoiseMeasured > 0 ? dupNoiseCount / dupNoiseMeasured : 0,
|
|
194
|
+
measured: dupNoiseMeasured,
|
|
195
|
+
},
|
|
196
|
+
relinkCursor,
|
|
197
|
+
relinkedPartition: statsFor(relinked, embeddings),
|
|
198
|
+
notYetRelinkedPartition: statsFor(notYetRelinked, embeddings),
|
|
199
|
+
};
|
|
200
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reflection census (#191 mechanical baseline) — lesson output volume over
|
|
3
|
+
* time and the lesson/episode yield ratio. This is NOT a quality read
|
|
4
|
+
* (sampling lessons for actionable-vs-noise is Phase-A grading work,
|
|
5
|
+
* deferred per the issue's owner comment until recall is active) — just the
|
|
6
|
+
* mechanical count.
|
|
7
|
+
*/
|
|
8
|
+
import type Database from "better-sqlite3";
|
|
9
|
+
export interface ReflectionCensus {
|
|
10
|
+
lessonsByDate: Array<{
|
|
11
|
+
date: string;
|
|
12
|
+
count: number;
|
|
13
|
+
}>;
|
|
14
|
+
totalLessons: number;
|
|
15
|
+
totalEpisodes: number;
|
|
16
|
+
/** lessons / episodes — null when there are no episodes to divide by. */
|
|
17
|
+
yieldRatio: number | null;
|
|
18
|
+
}
|
|
19
|
+
export declare function runReflectionCensus(db: Database.Database): ReflectionCensus;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Reflection census (#191 mechanical baseline) — lesson output volume over
|
|
4
|
+
* time and the lesson/episode yield ratio. This is NOT a quality read
|
|
5
|
+
* (sampling lessons for actionable-vs-noise is Phase-A grading work,
|
|
6
|
+
* deferred per the issue's owner comment until recall is active) — just the
|
|
7
|
+
* mechanical count.
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.runReflectionCensus = runReflectionCensus;
|
|
11
|
+
function runReflectionCensus(db) {
|
|
12
|
+
const lessonsByDate = db
|
|
13
|
+
.prepare(`SELECT date(created_at) AS date, COUNT(*) AS count
|
|
14
|
+
FROM memories WHERE memory_type = 'lesson'
|
|
15
|
+
GROUP BY date ORDER BY date`)
|
|
16
|
+
.all();
|
|
17
|
+
const totalLessons = db.prepare("SELECT COUNT(*) AS c FROM memories WHERE memory_type = 'lesson'").get().c;
|
|
18
|
+
const totalEpisodes = db.prepare("SELECT COUNT(*) AS c FROM memories WHERE memory_type = 'episode'").get().c;
|
|
19
|
+
return {
|
|
20
|
+
lessonsByDate,
|
|
21
|
+
totalLessons,
|
|
22
|
+
totalEpisodes,
|
|
23
|
+
yieldRatio: totalEpisodes > 0 ? totalLessons / totalEpisodes : null,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* #191 mechanical audit baseline — orchestrates every section (D1 duplicate
|
|
4
|
+
* rate, D4 decay/prune/no-fit + #192 adoption, D6 link-graph health, the
|
|
5
|
+
* reflection census) into one markdown report.
|
|
6
|
+
*
|
|
7
|
+
* Read-only end to end: opens the snapshot via eval-db.ts (`openSnapshot`,
|
|
8
|
+
* never `initDb()`), never writes to the DB. Not wired into `cli.ts` — this
|
|
9
|
+
* is an internal measurement tool, run via the `eval` npm script:
|
|
10
|
+
*
|
|
11
|
+
* npm run eval -- <snapshot.db> [report.md]
|
|
12
|
+
*
|
|
13
|
+
* `state.json` is expected alongside the snapshot DB (same directory) for
|
|
14
|
+
* the `domainCursor`/`relinkCursor` watermarks. Report path defaults to
|
|
15
|
+
* `eval-report.md` next to the DB.
|
|
16
|
+
*/
|
|
17
|
+
export {};
|