simt-dev 0.3.0 → 0.4.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/CHANGELOG.md +172 -0
- package/README.md +138 -74
- package/docs/decisions/ADR-073-a-kernel-is-a-smaller-projection-the-repository-asks-for.md +1 -1
- package/docs/decisions/ADR-077-the-command-card-leads-the-region-and-reads-committed-evidence.md +1 -1
- package/docs/decisions/ADR-083-a-budget-is-met-by-cutting-the-kernel-and-hard-is-the-floor.md +153 -0
- package/docs/decisions/ADR-084-a-workspace-carries-its-repeated-rules-once.md +100 -0
- package/docs/decisions/ADR-085-the-region-ends-with-the-names-it-kept-out.md +72 -0
- package/docs/decisions/ADR-086-the-stable-prefix-comes-first-and-the-lock-records-both-digests.md +69 -0
- package/docs/decisions/ADR-087-a-run-is-evidence-for-one-tree.md +67 -0
- package/docs/decisions/ADR-088-a-marked-line-is-enforced-by-a-checker.md +59 -0
- package/docs/decisions/ADR-089-the-local-index-is-a-cache-of-git-blobs.md +52 -0
- package/docs/decisions/ADR-090-an-optional-adapter-is-off-until-named.md +55 -0
- package/docs/decisions/ADR-091-the-map-ranks-files-a-task-mentions.md +60 -0
- package/docs/decisions/ADR-092-tests-are-imports-conventions-and-coverage.md +60 -0
- package/docs/decisions/ADR-093-why-names-what-governs-a-file.md +58 -0
- package/docs/decisions/ADR-094-a-change-runs-the-tests-that-import-it.md +44 -0
- package/docs/decisions/ADR-095-gate-output-keeps-the-failures.md +31 -0
- package/docs/decisions/ADR-096-impact-names-who-a-change-reaches.md +38 -0
- package/package.json +2 -2
- package/rules/core.md +2 -2
- package/src/agenthooks/policy.mjs +10 -2
- package/src/agenthooks/prompt.mjs +81 -0
- package/src/agenthooks/render.mjs +138 -17
- package/src/argv.mjs +34 -1
- package/src/bench/grade.mjs +3 -0
- package/src/bench/run.mjs +85 -39
- package/src/bench/variants.mjs +456 -0
- package/src/brief/assemble.mjs +346 -0
- package/src/brief/kind.mjs +134 -0
- package/src/brief/packet.mjs +197 -0
- package/src/ci/workflow.mjs +11 -1
- package/src/commands/agenthook.mjs +84 -5
- package/src/commands/bench.mjs +53 -16
- package/src/commands/brief.mjs +78 -0
- package/src/commands/compare.mjs +10 -2
- package/src/commands/decide.mjs +116 -0
- package/src/commands/evidence.mjs +96 -0
- package/src/commands/gate.mjs +165 -0
- package/src/commands/help.mjs +65 -3
- package/src/commands/hooks.mjs +45 -5
- package/src/commands/impact.mjs +205 -0
- package/src/commands/index.mjs +52 -1
- package/src/commands/lessons.mjs +127 -0
- package/src/commands/local.mjs +97 -0
- package/src/commands/map.mjs +126 -0
- package/src/commands/repoindex.mjs +55 -0
- package/src/commands/rules.mjs +17 -2
- package/src/commands/shared.mjs +398 -0
- package/src/commands/skills.mjs +3 -17
- package/src/commands/skillscompile.mjs +255 -0
- package/src/commands/sync.mjs +87 -3
- package/src/commands/tests.mjs +145 -0
- package/src/commands/tokens.mjs +30 -1
- package/src/commands/why.mjs +172 -0
- package/src/compare/learn.mjs +680 -0
- package/src/decisions/index.mjs +116 -65
- package/src/federation/client.mjs +188 -0
- package/src/federation/config.mjs +134 -0
- package/src/federation/query.mjs +48 -0
- package/src/graph/compile.mjs +475 -0
- package/src/graph/spec.mjs +1 -1
- package/src/hooks/dispatcher.mjs +15 -0
- package/src/hooks/shim.mjs +31 -0
- package/src/index/compact.mjs +228 -0
- package/src/index/extract.mjs +331 -0
- package/src/index/gate.mjs +89 -0
- package/src/index/govern.mjs +234 -0
- package/src/index/impact.mjs +186 -0
- package/src/index/map.mjs +627 -0
- package/src/index/persist.mjs +239 -0
- package/src/index/store.mjs +515 -0
- package/src/index/tests.mjs +294 -0
- package/src/kernel/budget.mjs +100 -0
- package/src/kernel/checks.mjs +378 -0
- package/src/kernel/variants.mjs +353 -0
- package/src/ledger/index.mjs +147 -0
- package/src/local/client.mjs +482 -0
- package/src/local/config.mjs +234 -0
- package/src/mcp/tools.mjs +138 -1
- package/src/measure/context.mjs +7 -5
- package/src/memory/decide.mjs +462 -0
- package/src/memory/lessons.mjs +858 -0
- package/src/projection/disclosure.mjs +92 -0
- package/src/projection/lock.mjs +125 -0
- package/src/projection/manifest.mjs +52 -0
- package/src/projection/markers.mjs +56 -17
- package/src/projection/project.mjs +24 -17
- package/src/projection/rules.mjs +407 -49
- package/src/projection/shared.mjs +428 -0
- package/src/projection/targets.mjs +2 -1
- package/src/toolwords.mjs +8 -0
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
// The two on-disk forms of the index (ADR-089, ADR-090).
|
|
2
|
+
//
|
|
3
|
+
// sqlite when this Node can open it, JSON-lines otherwise. Both store the same
|
|
4
|
+
// rows. This file is the format; store.mjs is the walk that fills it. They are
|
|
5
|
+
// split because the walk and the format together crossed the review trigger.
|
|
6
|
+
|
|
7
|
+
import { mkdir, readFile, stat, writeFile } from 'node:fs/promises';
|
|
8
|
+
import { dirname, join } from 'node:path';
|
|
9
|
+
|
|
10
|
+
export const SQLITE_FILE = '.simt-dev/index.sqlite';
|
|
11
|
+
export const JSONL_FILE = '.simt-dev/index.jsonl';
|
|
12
|
+
export const SCHEMA_V = '3';
|
|
13
|
+
|
|
14
|
+
const SCHEMA = `
|
|
15
|
+
create table if not exists files(path text primary key, blob text, lang text, bytes int, pkg text);
|
|
16
|
+
create table if not exists symbols(id integer primary key, path text, name text, kind text, line int, sig text, exported int);
|
|
17
|
+
create table if not exists edges(src text, dst text, kind text, weight real);
|
|
18
|
+
create virtual table if not exists fts using fts5(kind, ref, body);
|
|
19
|
+
create table if not exists embeddings(path text primary key, blob text, model text, dims int, vec blob);
|
|
20
|
+
create table if not exists meta(key text primary key, value text);
|
|
21
|
+
`;
|
|
22
|
+
|
|
23
|
+
export function encodeVectorBlob(vec) {
|
|
24
|
+
if (!Array.isArray(vec) && !(vec instanceof Float32Array)) return Buffer.alloc(0);
|
|
25
|
+
const f32 = vec instanceof Float32Array ? vec : Float32Array.from(vec, (v) => Number(v) || 0);
|
|
26
|
+
return Buffer.from(f32.buffer, f32.byteOffset, f32.byteLength);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function decodeVectorBlob(blob) {
|
|
30
|
+
if (!blob) return new Float32Array(0);
|
|
31
|
+
if (blob instanceof Float32Array) return blob;
|
|
32
|
+
if (Array.isArray(blob)) return Float32Array.from(blob, (v) => Number(v) || 0);
|
|
33
|
+
const buf = Buffer.isBuffer(blob)
|
|
34
|
+
? blob
|
|
35
|
+
: blob instanceof Uint8Array
|
|
36
|
+
? Buffer.from(blob.buffer, blob.byteOffset, blob.byteLength)
|
|
37
|
+
: typeof blob === 'string'
|
|
38
|
+
? Buffer.from(blob, 'base64')
|
|
39
|
+
: Buffer.alloc(0);
|
|
40
|
+
const count = Math.floor(buf.byteLength / 4);
|
|
41
|
+
if (count === 0) return new Float32Array(0);
|
|
42
|
+
const copy = new Uint8Array(count * 4);
|
|
43
|
+
copy.set(new Uint8Array(buf.buffer, buf.byteOffset, count * 4));
|
|
44
|
+
return new Float32Array(copy.buffer);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export async function writeSqlite(root, next, removed, symbols, edges, fts, embeddings = []) {
|
|
48
|
+
const { DatabaseSync } = await import('node:sqlite');
|
|
49
|
+
const file = join(root, SQLITE_FILE);
|
|
50
|
+
await mkdir(dirname(file), { recursive: true });
|
|
51
|
+
const db = new DatabaseSync(file);
|
|
52
|
+
try {
|
|
53
|
+
db.exec(SCHEMA);
|
|
54
|
+
db.exec('begin');
|
|
55
|
+
const upsert = db.prepare(`insert into files(path, blob, lang, bytes, pkg) values (?, ?, ?, ?, ?)
|
|
56
|
+
on conflict(path) do update set blob = excluded.blob, lang = excluded.lang, bytes = excluded.bytes, pkg = excluded.pkg`);
|
|
57
|
+
const dropSymbols = db.prepare('delete from symbols where path = ?');
|
|
58
|
+
const dropEdges = db.prepare('delete from edges where src = ? or dst = ?');
|
|
59
|
+
const dropFts = db.prepare('delete from fts where ref = ?');
|
|
60
|
+
const dropEmbed = db.prepare('delete from embeddings where path = ?');
|
|
61
|
+
const dropFile = db.prepare('delete from files where path = ?');
|
|
62
|
+
const addSymbol = db.prepare('insert into symbols(path, name, kind, line, sig, exported) values (?, ?, ?, ?, ?, ?)');
|
|
63
|
+
const addEdge = db.prepare('insert into edges(src, dst, kind, weight) values (?, ?, ?, ?)');
|
|
64
|
+
const addFts = db.prepare('insert into fts(kind, ref, body) values (?, ?, ?)');
|
|
65
|
+
const upsertEmbed = db.prepare(`insert into embeddings(path, blob, model, dims, vec) values (?, ?, ?, ?, ?)
|
|
66
|
+
on conflict(path) do update set blob = excluded.blob, model = excluded.model, dims = excluded.dims, vec = excluded.vec`);
|
|
67
|
+
for (const row of next) {
|
|
68
|
+
upsert.run(row.path, row.blob, row.lang, row.bytes, row.pkg);
|
|
69
|
+
dropSymbols.run(row.path);
|
|
70
|
+
dropEdges.run(row.path, row.path);
|
|
71
|
+
dropFts.run(row.path);
|
|
72
|
+
dropEmbed.run(row.path);
|
|
73
|
+
}
|
|
74
|
+
for (const path of removed) {
|
|
75
|
+
dropFile.run(path);
|
|
76
|
+
dropSymbols.run(path);
|
|
77
|
+
dropEdges.run(path, path);
|
|
78
|
+
dropFts.run(path);
|
|
79
|
+
dropEmbed.run(path);
|
|
80
|
+
}
|
|
81
|
+
for (const row of symbols) addSymbol.run(row.path, row.name, row.kind, row.line, row.sig, row.exported);
|
|
82
|
+
for (const row of edges) addEdge.run(row.src, row.dst, row.kind, row.weight);
|
|
83
|
+
for (const row of fts) addFts.run(row.kind, row.ref, row.body);
|
|
84
|
+
for (const row of embeddings) {
|
|
85
|
+
const vecBlob = Buffer.isBuffer(row.vec) || row.vec instanceof Uint8Array ? row.vec : encodeVectorBlob(row.vec);
|
|
86
|
+
const dims = Number.isInteger(row.dims) ? row.dims : Math.floor(vecBlob.byteLength / 4);
|
|
87
|
+
upsertEmbed.run(row.path, row.blob ?? '', row.model ?? '', dims, vecBlob);
|
|
88
|
+
}
|
|
89
|
+
db.prepare('insert into meta(key, value) values (?, ?) on conflict(key) do update set value = excluded.value').run('schema', SCHEMA_V);
|
|
90
|
+
db.exec('commit');
|
|
91
|
+
} catch (error) {
|
|
92
|
+
try { db.exec('rollback'); } catch { /* a failed begin has nothing to undo */ }
|
|
93
|
+
throw error;
|
|
94
|
+
} finally {
|
|
95
|
+
db.close();
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export async function writeJsonl(root, previous, next, removed, symbols, edges, fts, force, embeddings = []) {
|
|
100
|
+
const gone = new Set(removed);
|
|
101
|
+
const replaced = new Map(next.map((row) => [row.path, row]));
|
|
102
|
+
const drop = new Set([...replaced.keys(), ...gone]);
|
|
103
|
+
const files = [];
|
|
104
|
+
for (const row of previous) {
|
|
105
|
+
if (gone.has(row.path)) continue;
|
|
106
|
+
files.push(replaced.get(row.path) ?? row);
|
|
107
|
+
replaced.delete(row.path);
|
|
108
|
+
}
|
|
109
|
+
for (const row of replaced.values()) files.push(row);
|
|
110
|
+
files.sort(byPath);
|
|
111
|
+
const keptSymbols = force ? [] : (await readJsonl(root, 'symbols')).filter((row) => !drop.has(row.path));
|
|
112
|
+
const keptEdges = force ? [] : (await readJsonl(root, 'edges')).filter((row) => !drop.has(row.src) && !drop.has(row.dst));
|
|
113
|
+
const keptFts = force ? [] : (await readJsonl(root, 'fts')).filter((row) => !drop.has(row.ref));
|
|
114
|
+
const newEmbedPaths = new Set(embeddings.map((row) => row.path));
|
|
115
|
+
const keptEmbeddings = force
|
|
116
|
+
? []
|
|
117
|
+
: (await readJsonl(root, 'embeddings')).filter((row) => !drop.has(row.path) && !newEmbedPaths.has(row.path));
|
|
118
|
+
const allSymbols = [...keptSymbols, ...symbols].sort(bySymbol);
|
|
119
|
+
const allEdges = [...keptEdges, ...edges].sort(byEdge);
|
|
120
|
+
const allFts = [...keptFts, ...fts].sort((a, b) => cmp(a.ref, b.ref));
|
|
121
|
+
const allEmbeddings = [...keptEmbeddings, ...embeddings].sort(byPath);
|
|
122
|
+
const file = join(root, JSONL_FILE);
|
|
123
|
+
await mkdir(dirname(file), { recursive: true });
|
|
124
|
+
const lines = [
|
|
125
|
+
JSON.stringify({ table: 'meta', key: 'schema', value: SCHEMA_V }),
|
|
126
|
+
...files.map((row) => JSON.stringify({ table: 'files', path: row.path, blob: row.blob, lang: row.lang, bytes: row.bytes, pkg: row.pkg })),
|
|
127
|
+
...allSymbols.map((row) => JSON.stringify({ table: 'symbols', path: row.path, name: row.name, kind: row.kind, line: row.line, sig: row.sig, exported: row.exported })),
|
|
128
|
+
...allEdges.map((row) => JSON.stringify({ table: 'edges', src: row.src, dst: row.dst, kind: row.kind, weight: row.weight })),
|
|
129
|
+
...allFts.map((row) => JSON.stringify({ table: 'fts', kind: row.kind, ref: row.ref, body: row.body })),
|
|
130
|
+
...allEmbeddings.map((row) => {
|
|
131
|
+
const vecBlob = Buffer.isBuffer(row.vec) || row.vec instanceof Uint8Array ? Buffer.from(row.vec.buffer, row.vec.byteOffset, row.vec.byteLength) : encodeVectorBlob(row.vec);
|
|
132
|
+
const dims = Number.isInteger(row.dims) ? row.dims : Math.floor(vecBlob.byteLength / 4);
|
|
133
|
+
return JSON.stringify({
|
|
134
|
+
table: 'embeddings',
|
|
135
|
+
path: row.path,
|
|
136
|
+
blob: row.blob ?? '',
|
|
137
|
+
model: row.model ?? '',
|
|
138
|
+
dims,
|
|
139
|
+
vec: vecBlob.toString('base64'),
|
|
140
|
+
});
|
|
141
|
+
}),
|
|
142
|
+
];
|
|
143
|
+
await writeFile(file, `${lines.join('\n')}\n`);
|
|
144
|
+
return { symbols: allSymbols.length, edges: allEdges.length, embeddings: allEmbeddings.length };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export async function readJsonl(root, table) {
|
|
148
|
+
let text;
|
|
149
|
+
try {
|
|
150
|
+
text = await readFile(join(root, JSONL_FILE), 'utf8');
|
|
151
|
+
} catch {
|
|
152
|
+
return [];
|
|
153
|
+
}
|
|
154
|
+
const rows = [];
|
|
155
|
+
for (const line of text.split('\n')) {
|
|
156
|
+
if (line === '') continue;
|
|
157
|
+
const row = JSON.parse(line);
|
|
158
|
+
if (row.table !== table) continue;
|
|
159
|
+
if (table === 'meta') rows.push({ key: row.key, value: row.value });
|
|
160
|
+
else if (table === 'files') rows.push({ path: row.path, blob: row.blob, lang: row.lang, bytes: row.bytes, pkg: row.pkg });
|
|
161
|
+
else if (table === 'symbols') rows.push({ path: row.path, name: row.name, kind: row.kind, line: row.line, sig: row.sig, exported: row.exported });
|
|
162
|
+
else if (table === 'edges') rows.push({ src: row.src, dst: row.dst, kind: row.kind, weight: row.weight });
|
|
163
|
+
else if (table === 'fts') rows.push({ kind: row.kind, ref: row.ref, body: row.body });
|
|
164
|
+
else if (table === 'embeddings') {
|
|
165
|
+
const vec = decodeVectorBlob(row.vec);
|
|
166
|
+
rows.push({
|
|
167
|
+
path: row.path,
|
|
168
|
+
blob: row.blob ?? '',
|
|
169
|
+
model: row.model ?? '',
|
|
170
|
+
dims: Number.isInteger(row.dims) ? row.dims : vec.length,
|
|
171
|
+
vec,
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
if (table === 'symbols') rows.sort(bySymbol);
|
|
176
|
+
else if (table === 'edges') rows.sort(byEdge);
|
|
177
|
+
else if (table === 'fts') rows.sort((a, b) => cmp(a.ref, b.ref));
|
|
178
|
+
else rows.sort(byPath);
|
|
179
|
+
return rows;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export async function readSqliteRows(root, sql, params = []) {
|
|
183
|
+
let DatabaseSync;
|
|
184
|
+
try {
|
|
185
|
+
({ DatabaseSync } = await import('node:sqlite'));
|
|
186
|
+
} catch {
|
|
187
|
+
return [];
|
|
188
|
+
}
|
|
189
|
+
const file = join(root, SQLITE_FILE);
|
|
190
|
+
try {
|
|
191
|
+
await stat(file);
|
|
192
|
+
} catch {
|
|
193
|
+
return [];
|
|
194
|
+
}
|
|
195
|
+
const db = new DatabaseSync(file);
|
|
196
|
+
try {
|
|
197
|
+
return db.prepare(sql).all(...params);
|
|
198
|
+
} catch {
|
|
199
|
+
return [];
|
|
200
|
+
} finally {
|
|
201
|
+
db.close();
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export async function readSchema(root, backend) {
|
|
206
|
+
if (backend === 'sqlite') {
|
|
207
|
+
const rows = await readSqliteRows(root, "select value from meta where key = 'schema'");
|
|
208
|
+
return rows[0]?.value ?? '';
|
|
209
|
+
}
|
|
210
|
+
const rows = await readJsonl(root, 'meta');
|
|
211
|
+
return rows.find((row) => row.key === 'schema')?.value ?? '';
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export async function countSqlite(root) {
|
|
215
|
+
const symbols = await readSqliteRows(root, 'select count(*) as n from symbols');
|
|
216
|
+
const edges = await readSqliteRows(root, 'select count(*) as n from edges');
|
|
217
|
+
const embeddings = await readSqliteRows(root, 'select count(*) as n from embeddings');
|
|
218
|
+
return {
|
|
219
|
+
symbols: Number(symbols[0]?.n ?? 0),
|
|
220
|
+
edges: Number(edges[0]?.n ?? 0),
|
|
221
|
+
embeddings: Number(embeddings[0]?.n ?? 0),
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function byPath(a, b) {
|
|
226
|
+
return cmp(a.path, b.path);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function bySymbol(a, b) {
|
|
230
|
+
return cmp(a.path, b.path) || a.line - b.line || cmp(a.name, b.name) || cmp(a.kind, b.kind);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function byEdge(a, b) {
|
|
234
|
+
return cmp(a.src, b.src) || cmp(a.dst, b.dst) || cmp(a.kind, b.kind);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function cmp(a, b) {
|
|
238
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
239
|
+
}
|