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.
Files changed (73) hide show
  1. package/AGENT_SKILL.md +12 -0
  2. package/README.md +765 -2
  3. package/bin/larkit-mcp.js +8 -0
  4. package/bin/larkit.js +532 -0
  5. package/larkit.config.example.json +12 -0
  6. package/package.json +65 -4
  7. package/scripts/.tmp-npm-webauth.mjs +49 -0
  8. package/scripts/build-index.mjs +66 -0
  9. package/scripts/check-syntax.mjs +28 -0
  10. package/scripts/login-worker.mjs +50 -0
  11. package/scripts/publish-pack.mjs +277 -0
  12. package/scripts/verify-bootstrap.mjs +238 -0
  13. package/scripts/verify-console.mjs +60 -0
  14. package/scripts/verify-index.mjs +59 -0
  15. package/scripts/verify-mcp.mjs +215 -0
  16. package/skills/larkit/SKILL.md +120 -0
  17. package/skills/larkit/references/console.md +86 -0
  18. package/skills/larkit/references/playbooks.md +177 -0
  19. package/skills/larkit/references/tools.md +115 -0
  20. package/skills/larkit/references/truth-model.md +117 -0
  21. package/src/auth/login.js +171 -0
  22. package/src/auth/service.js +213 -0
  23. package/src/auth/store.js +213 -0
  24. package/src/bootstrap/channel.js +201 -0
  25. package/src/bootstrap/cli-install.js +184 -0
  26. package/src/bootstrap/download.js +182 -0
  27. package/src/bootstrap/init.js +672 -0
  28. package/src/bootstrap/pack-cli.js +97 -0
  29. package/src/bootstrap/pack.js +541 -0
  30. package/src/bootstrap/report.js +62 -0
  31. package/src/bootstrap/skills.js +223 -0
  32. package/src/bootstrap/state.js +66 -0
  33. package/src/bootstrap/tar.js +417 -0
  34. package/src/console/browser.js +338 -0
  35. package/src/console/client.js +827 -0
  36. package/src/console/dispatch.js +101 -0
  37. package/src/docs/client.js +284 -0
  38. package/src/indexer/anchors.js +366 -0
  39. package/src/indexer/db.js +449 -0
  40. package/src/indexer/extract-assets.js +285 -0
  41. package/src/indexer/extract-deb.js +56 -0
  42. package/src/indexer/format-pool.js +64 -0
  43. package/src/indexer/format-worker.js +132 -0
  44. package/src/indexer/format.js +239 -0
  45. package/src/indexer/protocol.js +123 -0
  46. package/src/mcp/server.js +433 -0
  47. package/src/probe/client.js +225 -0
  48. package/src/probe/openapi.js +194 -0
  49. package/src/protocol/catalog.js +146 -0
  50. package/src/protocol/data/README.md +15 -0
  51. package/src/protocol/data/bundle.json +1 -0
  52. package/src/protocol/data/commands.json +12896 -0
  53. package/src/protocol/data/constants.json +8 -0
  54. package/src/protocol/data/reference/PROTOCOL.md +66 -0
  55. package/src/protocol/data/reference/endpoints.json +46 -0
  56. package/src/protocol/data/schema.json.gz +0 -0
  57. package/src/protocol/descriptor.js +358 -0
  58. package/src/protocol/schema.js +235 -0
  59. package/src/protocol/transport.js +387 -0
  60. package/src/shared/browser.js +70 -0
  61. package/src/shared/config.js +90 -0
  62. package/src/shared/paths.js +151 -0
  63. package/src/shared/quiet.js +12 -0
  64. package/src/shared/util.js +124 -0
  65. package/src/truth/fts.js +24 -0
  66. package/src/truth/search.js +554 -0
  67. package/src/truth/verified.js +107 -0
  68. package/verified_truths/README.md +28 -0
  69. package/verified_truths/card-json-2.0-official.md +55 -0
  70. package/verified_truths/client-ws-gateway-boundaries.md +33 -0
  71. package/verified_truths/feishu-developer-console-apis.md +104 -0
  72. package/workers/truth-api/src/index.js +180 -0
  73. 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 };