larkit 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENT_SKILL.md +12 -0
- package/README.md +765 -2
- package/bin/larkit-mcp.js +8 -0
- package/bin/larkit.js +532 -0
- package/larkit.config.example.json +12 -0
- package/package.json +65 -4
- package/scripts/.tmp-npm-webauth.mjs +49 -0
- package/scripts/build-index.mjs +66 -0
- package/scripts/check-syntax.mjs +28 -0
- package/scripts/login-worker.mjs +50 -0
- package/scripts/publish-pack.mjs +277 -0
- package/scripts/verify-bootstrap.mjs +238 -0
- package/scripts/verify-console.mjs +60 -0
- package/scripts/verify-index.mjs +59 -0
- package/scripts/verify-mcp.mjs +215 -0
- package/skills/larkit/SKILL.md +120 -0
- package/skills/larkit/references/console.md +86 -0
- package/skills/larkit/references/playbooks.md +177 -0
- package/skills/larkit/references/tools.md +115 -0
- package/skills/larkit/references/truth-model.md +117 -0
- package/src/auth/login.js +171 -0
- package/src/auth/service.js +213 -0
- package/src/auth/store.js +213 -0
- package/src/bootstrap/channel.js +201 -0
- package/src/bootstrap/cli-install.js +184 -0
- package/src/bootstrap/download.js +182 -0
- package/src/bootstrap/init.js +672 -0
- package/src/bootstrap/pack-cli.js +97 -0
- package/src/bootstrap/pack.js +541 -0
- package/src/bootstrap/report.js +62 -0
- package/src/bootstrap/skills.js +223 -0
- package/src/bootstrap/state.js +66 -0
- package/src/bootstrap/tar.js +417 -0
- package/src/console/browser.js +338 -0
- package/src/console/client.js +827 -0
- package/src/console/dispatch.js +101 -0
- package/src/docs/client.js +284 -0
- package/src/indexer/anchors.js +366 -0
- package/src/indexer/db.js +449 -0
- package/src/indexer/extract-assets.js +285 -0
- package/src/indexer/extract-deb.js +56 -0
- package/src/indexer/format-pool.js +64 -0
- package/src/indexer/format-worker.js +132 -0
- package/src/indexer/format.js +239 -0
- package/src/indexer/protocol.js +123 -0
- package/src/mcp/server.js +433 -0
- package/src/probe/client.js +225 -0
- package/src/probe/openapi.js +194 -0
- package/src/protocol/catalog.js +146 -0
- package/src/protocol/data/README.md +15 -0
- package/src/protocol/data/bundle.json +1 -0
- package/src/protocol/data/commands.json +12896 -0
- package/src/protocol/data/constants.json +8 -0
- package/src/protocol/data/reference/PROTOCOL.md +66 -0
- package/src/protocol/data/reference/endpoints.json +46 -0
- package/src/protocol/data/schema.json.gz +0 -0
- package/src/protocol/descriptor.js +358 -0
- package/src/protocol/schema.js +235 -0
- package/src/protocol/transport.js +387 -0
- package/src/shared/browser.js +70 -0
- package/src/shared/config.js +90 -0
- package/src/shared/paths.js +151 -0
- package/src/shared/quiet.js +12 -0
- package/src/shared/util.js +124 -0
- package/src/truth/fts.js +24 -0
- package/src/truth/search.js +554 -0
- package/src/truth/verified.js +107 -0
- package/verified_truths/README.md +28 -0
- package/verified_truths/card-json-2.0-official.md +55 -0
- package/verified_truths/client-ws-gateway-boundaries.md +33 -0
- package/verified_truths/feishu-developer-console-apis.md +104 -0
- package/workers/truth-api/src/index.js +180 -0
- package/workers/truth-api/wrangler.toml +31 -0
|
@@ -0,0 +1,449 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import readline from "node:readline";
|
|
4
|
+
import { DatabaseSync } from "node:sqlite";
|
|
5
|
+
import {
|
|
6
|
+
ANCHORS_JSONL,
|
|
7
|
+
PROTOCOL_CATALOG_JSON,
|
|
8
|
+
PROTOCOL_DOCS_JSON,
|
|
9
|
+
TRUTH_DB,
|
|
10
|
+
VERIFIED_DIR,
|
|
11
|
+
} from "../shared/paths.js";
|
|
12
|
+
import {
|
|
13
|
+
ensureDir,
|
|
14
|
+
log,
|
|
15
|
+
readJson,
|
|
16
|
+
truncate,
|
|
17
|
+
writeJson,
|
|
18
|
+
} from "../shared/util.js";
|
|
19
|
+
import { normalizeSearchText, searchTextFor } from "../truth/fts.js";
|
|
20
|
+
import { readVerifiedFiles, splitVerifiedSections } from "../truth/verified.js";
|
|
21
|
+
|
|
22
|
+
const SCHEMA = `
|
|
23
|
+
CREATE TABLE protocol_catalog (
|
|
24
|
+
id INTEGER PRIMARY KEY,
|
|
25
|
+
cmd INTEGER,
|
|
26
|
+
name TEXT,
|
|
27
|
+
request_type TEXT,
|
|
28
|
+
response_type TEXT,
|
|
29
|
+
kind TEXT,
|
|
30
|
+
version INTEGER,
|
|
31
|
+
schema_available INTEGER,
|
|
32
|
+
source TEXT,
|
|
33
|
+
schema_summary TEXT
|
|
34
|
+
);
|
|
35
|
+
CREATE VIRTUAL TABLE protocol_catalog_fts USING fts5(
|
|
36
|
+
name, request_type, response_type, kind, source, schema_summary, search_text
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
CREATE TABLE code_anchors (
|
|
40
|
+
id INTEGER PRIMARY KEY,
|
|
41
|
+
scope TEXT NOT NULL,
|
|
42
|
+
file_path TEXT NOT NULL,
|
|
43
|
+
line_number INTEGER NOT NULL,
|
|
44
|
+
anchor_type TEXT NOT NULL,
|
|
45
|
+
literal_value TEXT NOT NULL,
|
|
46
|
+
context_snippet TEXT
|
|
47
|
+
);
|
|
48
|
+
CREATE VIRTUAL TABLE code_anchors_fts USING fts5(
|
|
49
|
+
file_path, literal_value, context_snippet, anchor_type, scope, search_text
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
CREATE TABLE verified_truths (
|
|
53
|
+
id INTEGER PRIMARY KEY,
|
|
54
|
+
topic TEXT,
|
|
55
|
+
section TEXT,
|
|
56
|
+
path TEXT NOT NULL,
|
|
57
|
+
line_start INTEGER,
|
|
58
|
+
line_end INTEGER,
|
|
59
|
+
verified_at TEXT,
|
|
60
|
+
source TEXT,
|
|
61
|
+
confidence TEXT,
|
|
62
|
+
body TEXT
|
|
63
|
+
);
|
|
64
|
+
CREATE VIRTUAL TABLE verified_fts USING fts5(
|
|
65
|
+
topic, section, body, path, search_text
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
CREATE TABLE protocol_docs (
|
|
69
|
+
id INTEGER PRIMARY KEY,
|
|
70
|
+
title TEXT,
|
|
71
|
+
path TEXT,
|
|
72
|
+
line_start INTEGER,
|
|
73
|
+
line_end INTEGER,
|
|
74
|
+
body TEXT
|
|
75
|
+
);
|
|
76
|
+
CREATE VIRTUAL TABLE protocol_docs_fts USING fts5(
|
|
77
|
+
title, body, path, search_text
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
CREATE TABLE meta (
|
|
81
|
+
key TEXT PRIMARY KEY,
|
|
82
|
+
value TEXT
|
|
83
|
+
);
|
|
84
|
+
`;
|
|
85
|
+
|
|
86
|
+
function begin(db) {
|
|
87
|
+
db.exec("BEGIN");
|
|
88
|
+
}
|
|
89
|
+
function commit(db) {
|
|
90
|
+
db.exec("COMMIT");
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function createSchema(db) {
|
|
94
|
+
db.exec("PRAGMA journal_mode=DELETE;");
|
|
95
|
+
db.exec("PRAGMA synchronous=OFF;");
|
|
96
|
+
db.exec("PRAGMA temp_store=MEMORY;");
|
|
97
|
+
db.exec("PRAGMA cache_size=-262144;");
|
|
98
|
+
db.exec(SCHEMA);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function insertProtocolCatalog(db, catalog) {
|
|
102
|
+
const insert = db.prepare(
|
|
103
|
+
`INSERT INTO protocol_catalog
|
|
104
|
+
(cmd, name, request_type, response_type, kind, version, schema_available, source, schema_summary)
|
|
105
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
106
|
+
);
|
|
107
|
+
const insertFts = db.prepare(
|
|
108
|
+
`INSERT INTO protocol_catalog_fts
|
|
109
|
+
(rowid, name, request_type, response_type, kind, source, schema_summary, search_text)
|
|
110
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
111
|
+
);
|
|
112
|
+
begin(db);
|
|
113
|
+
for (const entry of catalog) {
|
|
114
|
+
const result = insert.run(
|
|
115
|
+
entry.cmd ?? null,
|
|
116
|
+
entry.name || "",
|
|
117
|
+
entry.request_type || "",
|
|
118
|
+
entry.response_type || "",
|
|
119
|
+
entry.kind || "",
|
|
120
|
+
entry.version ?? null,
|
|
121
|
+
entry.schema_available ? 1 : 0,
|
|
122
|
+
entry.source || "",
|
|
123
|
+
entry.schema_summary || "",
|
|
124
|
+
);
|
|
125
|
+
const rowid = Number(result.lastInsertRowid);
|
|
126
|
+
insertFts.run(
|
|
127
|
+
rowid,
|
|
128
|
+
entry.name || "",
|
|
129
|
+
entry.request_type || "",
|
|
130
|
+
entry.response_type || "",
|
|
131
|
+
entry.kind || "",
|
|
132
|
+
entry.source || "",
|
|
133
|
+
entry.schema_summary || "",
|
|
134
|
+
searchTextFor([
|
|
135
|
+
entry.cmd,
|
|
136
|
+
entry.name,
|
|
137
|
+
entry.request_type,
|
|
138
|
+
entry.response_type,
|
|
139
|
+
entry.kind,
|
|
140
|
+
entry.source,
|
|
141
|
+
entry.schema_summary,
|
|
142
|
+
]),
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
commit(db);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
async function insertAnchors(db) {
|
|
149
|
+
if (!fs.existsSync(ANCHORS_JSONL)) {
|
|
150
|
+
throw new Error(`missing ${ANCHORS_JSONL}; run anchors first`);
|
|
151
|
+
}
|
|
152
|
+
const insert = db.prepare(
|
|
153
|
+
`INSERT INTO code_anchors
|
|
154
|
+
(scope, file_path, line_number, anchor_type, literal_value, context_snippet)
|
|
155
|
+
VALUES (?, ?, ?, ?, ?, ?)`,
|
|
156
|
+
);
|
|
157
|
+
const insertFts = db.prepare(
|
|
158
|
+
`INSERT INTO code_anchors_fts
|
|
159
|
+
(rowid, file_path, literal_value, context_snippet, anchor_type, scope, search_text)
|
|
160
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
161
|
+
);
|
|
162
|
+
const reader = readline.createInterface({
|
|
163
|
+
input: fs.createReadStream(ANCHORS_JSONL, { encoding: "utf8" }),
|
|
164
|
+
crlfDelay: Infinity,
|
|
165
|
+
});
|
|
166
|
+
let count = 0;
|
|
167
|
+
begin(db);
|
|
168
|
+
for await (const line of reader) {
|
|
169
|
+
if (!line.trim()) continue;
|
|
170
|
+
let anchor;
|
|
171
|
+
try {
|
|
172
|
+
anchor = JSON.parse(line);
|
|
173
|
+
} catch {
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
const result = insert.run(
|
|
177
|
+
anchor.scope || "web",
|
|
178
|
+
anchor.file_path,
|
|
179
|
+
anchor.line_number,
|
|
180
|
+
anchor.anchor_type,
|
|
181
|
+
anchor.literal_value,
|
|
182
|
+
anchor.context_snippet || "",
|
|
183
|
+
);
|
|
184
|
+
const rowid = Number(result.lastInsertRowid);
|
|
185
|
+
insertFts.run(
|
|
186
|
+
rowid,
|
|
187
|
+
anchor.file_path,
|
|
188
|
+
anchor.literal_value,
|
|
189
|
+
anchor.context_snippet || "",
|
|
190
|
+
anchor.anchor_type,
|
|
191
|
+
anchor.scope || "web",
|
|
192
|
+
searchTextFor([
|
|
193
|
+
anchor.literal_value,
|
|
194
|
+
anchor.context_snippet,
|
|
195
|
+
anchor.file_path,
|
|
196
|
+
anchor.anchor_type,
|
|
197
|
+
anchor.scope,
|
|
198
|
+
]),
|
|
199
|
+
);
|
|
200
|
+
count++;
|
|
201
|
+
if (count % 2000 === 0) {
|
|
202
|
+
commit(db);
|
|
203
|
+
begin(db);
|
|
204
|
+
if (count % 20000 === 0) log(`[db] ${count} anchors`);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
commit(db);
|
|
208
|
+
return count;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function insertVerified(db, files) {
|
|
212
|
+
const insert = db.prepare(
|
|
213
|
+
`INSERT INTO verified_truths
|
|
214
|
+
(topic, section, path, line_start, line_end, verified_at, source, confidence, body)
|
|
215
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
216
|
+
);
|
|
217
|
+
const insertFts = db.prepare(
|
|
218
|
+
`INSERT INTO verified_fts
|
|
219
|
+
(rowid, topic, section, body, path, search_text)
|
|
220
|
+
VALUES (?, ?, ?, ?, ?, ?)`,
|
|
221
|
+
);
|
|
222
|
+
begin(db);
|
|
223
|
+
let count = 0;
|
|
224
|
+
for (const file of files) {
|
|
225
|
+
for (const section of file.sections) {
|
|
226
|
+
const result = insert.run(
|
|
227
|
+
file.topic,
|
|
228
|
+
section.section,
|
|
229
|
+
file.path,
|
|
230
|
+
section.line_start,
|
|
231
|
+
section.line_end,
|
|
232
|
+
file.meta.verified_at || "",
|
|
233
|
+
file.meta.source || "",
|
|
234
|
+
file.meta.confidence || "",
|
|
235
|
+
section.body,
|
|
236
|
+
);
|
|
237
|
+
insertFts.run(
|
|
238
|
+
Number(result.lastInsertRowid),
|
|
239
|
+
file.topic,
|
|
240
|
+
section.section,
|
|
241
|
+
section.body,
|
|
242
|
+
file.path,
|
|
243
|
+
searchTextFor([
|
|
244
|
+
file.topic,
|
|
245
|
+
section.section,
|
|
246
|
+
section.body,
|
|
247
|
+
file.path,
|
|
248
|
+
file.meta.source,
|
|
249
|
+
]),
|
|
250
|
+
);
|
|
251
|
+
count++;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
commit(db);
|
|
255
|
+
return count;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function insertProtocolDocs(db, docs) {
|
|
259
|
+
const insert = db.prepare(
|
|
260
|
+
`INSERT INTO protocol_docs
|
|
261
|
+
(title, path, line_start, line_end, body)
|
|
262
|
+
VALUES (?, ?, ?, ?, ?)`,
|
|
263
|
+
);
|
|
264
|
+
const insertFts = db.prepare(
|
|
265
|
+
`INSERT INTO protocol_docs_fts
|
|
266
|
+
(rowid, title, body, path, search_text)
|
|
267
|
+
VALUES (?, ?, ?, ?, ?)`,
|
|
268
|
+
);
|
|
269
|
+
begin(db);
|
|
270
|
+
for (const doc of docs) {
|
|
271
|
+
const result = insert.run(
|
|
272
|
+
doc.title,
|
|
273
|
+
doc.path,
|
|
274
|
+
doc.line_start,
|
|
275
|
+
doc.line_end,
|
|
276
|
+
doc.body,
|
|
277
|
+
);
|
|
278
|
+
insertFts.run(
|
|
279
|
+
Number(result.lastInsertRowid),
|
|
280
|
+
doc.title,
|
|
281
|
+
doc.body,
|
|
282
|
+
doc.path,
|
|
283
|
+
searchTextFor([doc.title, doc.body, doc.path]),
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
commit(db);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
export async function buildDatabase() {
|
|
290
|
+
const catalogFile = readJson(PROTOCOL_CATALOG_JSON, null);
|
|
291
|
+
const docsFile = readJson(PROTOCOL_DOCS_JSON, null);
|
|
292
|
+
if (!catalogFile?.entries?.length) {
|
|
293
|
+
throw new Error(`missing ${PROTOCOL_CATALOG_JSON}; run protocol first`);
|
|
294
|
+
}
|
|
295
|
+
if (!docsFile?.entries?.length) {
|
|
296
|
+
throw new Error(`missing ${PROTOCOL_DOCS_JSON}; run protocol first`);
|
|
297
|
+
}
|
|
298
|
+
if (!fs.existsSync(ANCHORS_JSONL)) {
|
|
299
|
+
throw new Error(`missing ${ANCHORS_JSONL}; run anchors first`);
|
|
300
|
+
}
|
|
301
|
+
ensureDir(path.dirname(TRUTH_DB));
|
|
302
|
+
const tmp = `${TRUTH_DB}.tmp`;
|
|
303
|
+
for (const suffix of ["", "-wal", "-shm"]) {
|
|
304
|
+
try {
|
|
305
|
+
fs.rmSync(`${tmp}${suffix}`, { force: true });
|
|
306
|
+
} catch {
|
|
307
|
+
// ignore
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
const db = new DatabaseSync(tmp);
|
|
311
|
+
try {
|
|
312
|
+
createSchema(db);
|
|
313
|
+
insertProtocolCatalog(db, catalogFile.entries);
|
|
314
|
+
const anchorCount = await insertAnchors(db);
|
|
315
|
+
const verifiedFiles = readVerifiedFiles();
|
|
316
|
+
const verifiedCount = insertVerified(db, verifiedFiles);
|
|
317
|
+
insertProtocolDocs(db, docsFile.entries);
|
|
318
|
+
db.exec(
|
|
319
|
+
"CREATE INDEX idx_anchors_path ON code_anchors(file_path, line_number);",
|
|
320
|
+
);
|
|
321
|
+
db.exec("CREATE INDEX idx_anchors_literal ON code_anchors(literal_value);");
|
|
322
|
+
db.exec("CREATE INDEX idx_anchors_type ON code_anchors(anchor_type);");
|
|
323
|
+
db.exec("CREATE INDEX idx_protocol_cmd ON protocol_catalog(cmd);");
|
|
324
|
+
db.exec("CREATE INDEX idx_protocol_name ON protocol_catalog(name);");
|
|
325
|
+
db.exec("CREATE INDEX idx_verified_path ON verified_truths(path);");
|
|
326
|
+
db.exec("CREATE INDEX idx_docs_path ON protocol_docs(path);");
|
|
327
|
+
db.exec("INSERT INTO code_anchors_fts(code_anchors_fts) VALUES('optimize');");
|
|
328
|
+
db.exec("INSERT INTO protocol_catalog_fts(protocol_catalog_fts) VALUES('optimize');");
|
|
329
|
+
db.exec("INSERT INTO verified_fts(verified_fts) VALUES('optimize');");
|
|
330
|
+
db.exec("INSERT INTO protocol_docs_fts(protocol_docs_fts) VALUES('optimize');");
|
|
331
|
+
const stat = db.prepare("SELECT COUNT(*) AS c FROM code_anchors").get();
|
|
332
|
+
const catalogCount = db
|
|
333
|
+
.prepare("SELECT COUNT(*) AS c FROM protocol_catalog")
|
|
334
|
+
.get();
|
|
335
|
+
const final = {
|
|
336
|
+
generatedAt: new Date().toISOString(),
|
|
337
|
+
protocolCatalog: Number(catalogCount.c),
|
|
338
|
+
codeAnchors: Number(stat.c),
|
|
339
|
+
verifiedSections: verifiedCount,
|
|
340
|
+
protocolDocs: docsFile.entries.length,
|
|
341
|
+
};
|
|
342
|
+
const insertMeta = db.prepare(
|
|
343
|
+
"INSERT OR REPLACE INTO meta(key, value) VALUES (?, ?)",
|
|
344
|
+
);
|
|
345
|
+
for (const [key, value] of Object.entries(final)) {
|
|
346
|
+
insertMeta.run(key, String(value));
|
|
347
|
+
}
|
|
348
|
+
db.exec("PRAGMA optimize;");
|
|
349
|
+
db.close();
|
|
350
|
+
fs.renameSync(tmp, TRUTH_DB);
|
|
351
|
+
for (const suffix of ["-wal", "-shm"]) {
|
|
352
|
+
try {
|
|
353
|
+
fs.rmSync(`${TRUTH_DB}${suffix}`, { force: true });
|
|
354
|
+
} catch {
|
|
355
|
+
// ignore
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
writeJson(path.join(path.dirname(TRUTH_DB), "truth.meta.json"), final);
|
|
359
|
+
log(
|
|
360
|
+
`[db] truth.db: ${final.protocolCatalog} commands, ${final.codeAnchors} anchors, ${final.verifiedSections} verified sections, ${final.protocolDocs} protocol docs`,
|
|
361
|
+
);
|
|
362
|
+
return final;
|
|
363
|
+
} catch (error) {
|
|
364
|
+
try {
|
|
365
|
+
db.close();
|
|
366
|
+
} catch {
|
|
367
|
+
// ignore
|
|
368
|
+
}
|
|
369
|
+
throw error;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
export function openTruthDb({ readonly = true } = {}) {
|
|
374
|
+
if (!fs.existsSync(TRUTH_DB)) {
|
|
375
|
+
throw new Error(
|
|
376
|
+
`truth.db not found at ${TRUTH_DB}; run "larkit init" (or "node scripts/build-index.mjs" in a checkout) first`,
|
|
377
|
+
);
|
|
378
|
+
}
|
|
379
|
+
const db = new DatabaseSync(TRUTH_DB, { readOnly: readonly });
|
|
380
|
+
return db;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/** Incrementally update one verified markdown file in an already-built DB. */
|
|
384
|
+
export function syncVerifiedFile(db, filePath) {
|
|
385
|
+
const text = fs.readFileSync(filePath, "utf8");
|
|
386
|
+
const dbPath = `verified_truths/${path.basename(filePath)}`;
|
|
387
|
+
const parsed = splitVerifiedSections(dbPath, text);
|
|
388
|
+
const rows = db
|
|
389
|
+
.prepare("SELECT id FROM verified_truths WHERE path = ?")
|
|
390
|
+
.all(dbPath);
|
|
391
|
+
if (rows.length > 0) {
|
|
392
|
+
const ids = rows.map((r) => Number(r.id));
|
|
393
|
+
const placeholders = ids.map(() => "?").join(",");
|
|
394
|
+
db.prepare(
|
|
395
|
+
`DELETE FROM verified_fts WHERE rowid IN (${placeholders})`,
|
|
396
|
+
).run(...ids);
|
|
397
|
+
db.prepare("DELETE FROM verified_truths WHERE path = ?").run(dbPath);
|
|
398
|
+
}
|
|
399
|
+
const insert = db.prepare(
|
|
400
|
+
`INSERT INTO verified_truths
|
|
401
|
+
(topic, section, path, line_start, line_end, verified_at, source, confidence, body)
|
|
402
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
403
|
+
);
|
|
404
|
+
const insertFts = db.prepare(
|
|
405
|
+
`INSERT INTO verified_fts
|
|
406
|
+
(rowid, topic, section, body, path, search_text)
|
|
407
|
+
VALUES (?, ?, ?, ?, ?, ?)`,
|
|
408
|
+
);
|
|
409
|
+
begin(db);
|
|
410
|
+
for (const section of parsed.sections) {
|
|
411
|
+
const result = insert.run(
|
|
412
|
+
parsed.topic,
|
|
413
|
+
section.section,
|
|
414
|
+
dbPath,
|
|
415
|
+
section.line_start,
|
|
416
|
+
section.line_end,
|
|
417
|
+
parsed.meta.verified_at || "",
|
|
418
|
+
parsed.meta.source || "",
|
|
419
|
+
parsed.meta.confidence || "",
|
|
420
|
+
section.body,
|
|
421
|
+
);
|
|
422
|
+
insertFts.run(
|
|
423
|
+
Number(result.lastInsertRowid),
|
|
424
|
+
parsed.topic,
|
|
425
|
+
section.section,
|
|
426
|
+
section.body,
|
|
427
|
+
filePath,
|
|
428
|
+
searchTextFor([
|
|
429
|
+
parsed.topic,
|
|
430
|
+
section.section,
|
|
431
|
+
section.body,
|
|
432
|
+
filePath,
|
|
433
|
+
parsed.meta.source,
|
|
434
|
+
]),
|
|
435
|
+
);
|
|
436
|
+
}
|
|
437
|
+
commit(db);
|
|
438
|
+
return parsed.sections.length;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
export function listVerifiedFilesOnDisk() {
|
|
442
|
+
if (!fs.existsSync(VERIFIED_DIR)) return [];
|
|
443
|
+
return fs
|
|
444
|
+
.readdirSync(VERIFIED_DIR)
|
|
445
|
+
.filter((name) => name.endsWith(".md") && name.toLowerCase() !== "readme.md")
|
|
446
|
+
.map((name) => path.join(VERIFIED_DIR, name));
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
export { normalizeSearchText, truncate };
|