@gmickel/gno 1.33.0 → 1.34.1
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 +7 -2
- package/assets/skill/SKILL.md +19 -0
- package/browser-extension/artifacts/{gno-browser-clipper-v1.33.0.zip → gno-browser-clipper-v1.34.1.zip} +0 -0
- package/browser-extension/artifacts/gno-browser-clipper-v1.34.1.zip.sha256 +1 -0
- package/browser-extension/dist/manifest.json +1 -1
- package/package.json +1 -1
- package/spec/cli.md +46 -0
- package/spec/mcp.md +21 -0
- package/spec/output-schemas/audit-report.schema.json +284 -0
- package/src/cli/commands/audit.ts +231 -0
- package/src/cli/commands/models/pull.ts +32 -3
- package/src/cli/errors.ts +9 -2
- package/src/cli/program.ts +112 -0
- package/src/core/audit-contract.ts +296 -0
- package/src/core/audit-freshness.ts +233 -0
- package/src/core/audit-links.ts +222 -0
- package/src/core/audit-provenance.ts +154 -0
- package/src/core/audit-report.ts +318 -0
- package/src/core/audit-workspace.ts +678 -0
- package/src/core/audit.ts +569 -0
- package/src/core/capture.ts +196 -3
- package/src/core/document-capabilities.ts +9 -8
- package/src/core/record-metadata.ts +33 -0
- package/src/mcp/http-egress.ts +8 -0
- package/src/mcp/tools/audit.ts +97 -0
- package/src/mcp/tools/index.ts +13 -0
- package/src/store/sqlite/adapter.ts +138 -91
- package/src/store/sqlite/graph-link-bulk-resolver.ts +191 -0
- package/src/store/sqlite/graph-link-resolver.ts +241 -2
- package/browser-extension/artifacts/gno-browser-clipper-v1.33.0.zip.sha256 +0 -1
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import type { Database } from "bun:sqlite";
|
|
8
8
|
|
|
9
9
|
import { stripWikiMdExt } from "../../core/links";
|
|
10
|
+
import { resolveGraphLinkTargetsBulk } from "./graph-link-bulk-resolver";
|
|
10
11
|
|
|
11
12
|
export interface GraphLinkTarget {
|
|
12
13
|
targetRefNorm: string;
|
|
@@ -22,6 +23,60 @@ export interface ResolvedGraphLinkTarget {
|
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
const MAX_SQL_PARAMS = 900;
|
|
26
|
+
const BULK_RESOLUTION_THRESHOLD = 128;
|
|
27
|
+
export const AUDIT_LINK_SNAPSHOT_MAX_DOCUMENTS = 50_000;
|
|
28
|
+
export const AUDIT_LINK_SNAPSHOT_MAX_LINKS = 50_000;
|
|
29
|
+
|
|
30
|
+
export interface AuditLinkSnapshotDocument {
|
|
31
|
+
id: number;
|
|
32
|
+
docid: string;
|
|
33
|
+
uri: string;
|
|
34
|
+
collection: string;
|
|
35
|
+
relPath: string;
|
|
36
|
+
recordSourcePath: string | null;
|
|
37
|
+
title: string | null;
|
|
38
|
+
mirrorHash: string | null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface AuditLinkSnapshotLink {
|
|
42
|
+
sourceId: number;
|
|
43
|
+
sourceDocid: string;
|
|
44
|
+
sourceUri: string;
|
|
45
|
+
sourceCollection: string;
|
|
46
|
+
sourceRelPath: string;
|
|
47
|
+
targetRef: string;
|
|
48
|
+
targetRefNorm: string;
|
|
49
|
+
targetAnchor: string | null;
|
|
50
|
+
targetCollection: string;
|
|
51
|
+
linkType: "wiki" | "markdown";
|
|
52
|
+
startLine: number;
|
|
53
|
+
startCol: number;
|
|
54
|
+
endLine: number;
|
|
55
|
+
endCol: number;
|
|
56
|
+
resolved: ResolvedGraphLinkTarget | null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface AuditLinkSnapshot {
|
|
60
|
+
documents: AuditLinkSnapshotDocument[];
|
|
61
|
+
/** Optional finding scope when documents also include graph-wide evidence. */
|
|
62
|
+
auditedDocumentIds?: number[];
|
|
63
|
+
links: AuditLinkSnapshotLink[];
|
|
64
|
+
totals: { documents: number; links: number };
|
|
65
|
+
truncated: { documents: boolean; links: boolean };
|
|
66
|
+
metrics: {
|
|
67
|
+
documentRowsExamined: number;
|
|
68
|
+
linkRowsExamined: number;
|
|
69
|
+
uniqueTargetsResolved: number;
|
|
70
|
+
batchedResolution: true;
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface AuditLinkSnapshotOptions {
|
|
75
|
+
collections?: readonly string[];
|
|
76
|
+
pathPrefixes?: readonly string[];
|
|
77
|
+
maxDocuments?: number;
|
|
78
|
+
maxLinks?: number;
|
|
79
|
+
}
|
|
25
80
|
|
|
26
81
|
const chunkArray = <T>(items: T[], chunkSize: number): T[][] => {
|
|
27
82
|
const chunks: T[][] = [];
|
|
@@ -36,7 +91,7 @@ const suffixMatch = (targetExpr: string, valueExpr: string): string =>
|
|
|
36
91
|
AND (length(${targetExpr}) = length(${valueExpr})
|
|
37
92
|
OR substr(${targetExpr}, -length(${valueExpr}) - 1, 1) = '/'))`;
|
|
38
93
|
|
|
39
|
-
const
|
|
94
|
+
export const resolveGraphLinkTargetsSql = (
|
|
40
95
|
db: Database,
|
|
41
96
|
targets: GraphLinkTarget[]
|
|
42
97
|
): Array<ResolvedGraphLinkTarget | null> => {
|
|
@@ -234,8 +289,192 @@ export function resolveGraphLinkTargets(
|
|
|
234
289
|
originalToUniqueIndex.push(uniqueIndex);
|
|
235
290
|
}
|
|
236
291
|
|
|
237
|
-
|
|
292
|
+
if (uniqueTargets.length > BULK_RESOLUTION_THRESHOLD) {
|
|
293
|
+
const bulkResults = resolveGraphLinkTargetsBulk(db, uniqueTargets);
|
|
294
|
+
if (bulkResults) {
|
|
295
|
+
return originalToUniqueIndex.map(
|
|
296
|
+
(uniqueIndex) => bulkResults[uniqueIndex] ?? null
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const uniqueResults = resolveGraphLinkTargetsSql(db, uniqueTargets);
|
|
238
302
|
return originalToUniqueIndex.map(
|
|
239
303
|
(uniqueIndex) => uniqueResults[uniqueIndex] ?? null
|
|
240
304
|
);
|
|
241
305
|
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Capture one bounded, read-only link inventory using set-oriented SQL and the
|
|
309
|
+
* same target resolver as graph expansion. No query is issued per finding.
|
|
310
|
+
*/
|
|
311
|
+
export function captureAuditLinkSnapshot(
|
|
312
|
+
db: Database,
|
|
313
|
+
options: AuditLinkSnapshotOptions = {}
|
|
314
|
+
): AuditLinkSnapshot {
|
|
315
|
+
const maxDocuments = Math.max(
|
|
316
|
+
1,
|
|
317
|
+
Math.min(
|
|
318
|
+
AUDIT_LINK_SNAPSHOT_MAX_DOCUMENTS,
|
|
319
|
+
options.maxDocuments ?? AUDIT_LINK_SNAPSHOT_MAX_DOCUMENTS
|
|
320
|
+
)
|
|
321
|
+
);
|
|
322
|
+
const maxLinks = Math.max(
|
|
323
|
+
1,
|
|
324
|
+
Math.min(
|
|
325
|
+
AUDIT_LINK_SNAPSHOT_MAX_LINKS,
|
|
326
|
+
options.maxLinks ?? AUDIT_LINK_SNAPSHOT_MAX_LINKS
|
|
327
|
+
)
|
|
328
|
+
);
|
|
329
|
+
const conditions = ["d.active = 1"];
|
|
330
|
+
const params: string[] = [];
|
|
331
|
+
const collections = [...new Set(options.collections ?? [])]
|
|
332
|
+
.map((value) => value.normalize("NFC").trim())
|
|
333
|
+
.filter(Boolean)
|
|
334
|
+
.sort();
|
|
335
|
+
if (collections.length > 0) {
|
|
336
|
+
conditions.push(
|
|
337
|
+
`d.collection IN (${collections.map(() => "?").join(",")})`
|
|
338
|
+
);
|
|
339
|
+
params.push(...collections);
|
|
340
|
+
}
|
|
341
|
+
const prefixes = [...new Set(options.pathPrefixes ?? [])]
|
|
342
|
+
.map((value) => value.normalize("NFC").trim().replace(/^\/+/, ""))
|
|
343
|
+
.filter(Boolean)
|
|
344
|
+
.sort();
|
|
345
|
+
if (prefixes.length > 0) {
|
|
346
|
+
conditions.push(
|
|
347
|
+
`(${prefixes.map(() => "COALESCE(NULLIF(d.record_source_path, ''), d.rel_path) = ? OR COALESCE(NULLIF(d.record_source_path, ''), d.rel_path) LIKE ? ESCAPE '\\'").join(" OR ")})`
|
|
348
|
+
);
|
|
349
|
+
for (const prefix of prefixes) {
|
|
350
|
+
const escaped = prefix
|
|
351
|
+
.replaceAll("\\", "\\\\")
|
|
352
|
+
.replaceAll("%", "\\%")
|
|
353
|
+
.replaceAll("_", "\\_");
|
|
354
|
+
params.push(prefix, `${escaped}/%`);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
const where = conditions.join(" AND ");
|
|
358
|
+
const totalDocuments =
|
|
359
|
+
db
|
|
360
|
+
.query<{ count: number }, string[]>(
|
|
361
|
+
`SELECT COUNT(*) AS count FROM documents d WHERE ${where}`
|
|
362
|
+
)
|
|
363
|
+
.get(...params)?.count ?? 0;
|
|
364
|
+
const documentRows = db
|
|
365
|
+
.query<
|
|
366
|
+
{
|
|
367
|
+
id: number;
|
|
368
|
+
docid: string;
|
|
369
|
+
uri: string;
|
|
370
|
+
collection: string;
|
|
371
|
+
rel_path: string;
|
|
372
|
+
record_source_path: string | null;
|
|
373
|
+
title: string | null;
|
|
374
|
+
mirror_hash: string | null;
|
|
375
|
+
},
|
|
376
|
+
(string | number)[]
|
|
377
|
+
>(
|
|
378
|
+
`SELECT d.id, d.docid, d.uri, d.collection, d.rel_path, d.record_source_path, d.title, d.mirror_hash
|
|
379
|
+
FROM documents d WHERE ${where} ORDER BY d.id LIMIT ?`
|
|
380
|
+
)
|
|
381
|
+
.all(...params, maxDocuments);
|
|
382
|
+
const totalLinks =
|
|
383
|
+
db
|
|
384
|
+
.query<{ count: number }, string[]>(
|
|
385
|
+
`SELECT COUNT(*) AS count
|
|
386
|
+
FROM doc_links dl
|
|
387
|
+
JOIN documents d ON d.id = dl.source_doc_id
|
|
388
|
+
WHERE ${where} AND dl.source = 'parsed'`
|
|
389
|
+
)
|
|
390
|
+
.get(...params)?.count ?? 0;
|
|
391
|
+
const rawLinks = db
|
|
392
|
+
.query<
|
|
393
|
+
{
|
|
394
|
+
source_id: number;
|
|
395
|
+
source_docid: string;
|
|
396
|
+
source_uri: string;
|
|
397
|
+
source_collection: string;
|
|
398
|
+
source_rel_path: string;
|
|
399
|
+
target_ref: string;
|
|
400
|
+
target_ref_norm: string;
|
|
401
|
+
target_anchor: string | null;
|
|
402
|
+
target_collection: string | null;
|
|
403
|
+
link_type: "wiki" | "markdown";
|
|
404
|
+
start_line: number;
|
|
405
|
+
start_col: number;
|
|
406
|
+
end_line: number;
|
|
407
|
+
end_col: number;
|
|
408
|
+
},
|
|
409
|
+
(string | number)[]
|
|
410
|
+
>(
|
|
411
|
+
`SELECT d.id AS source_id, d.docid AS source_docid,
|
|
412
|
+
d.uri AS source_uri, d.collection AS source_collection,
|
|
413
|
+
COALESCE(NULLIF(d.record_source_path, ''), d.rel_path) AS source_rel_path, dl.target_ref,
|
|
414
|
+
dl.target_ref_norm, dl.target_anchor, dl.target_collection,
|
|
415
|
+
dl.link_type, dl.start_line, dl.start_col,
|
|
416
|
+
dl.end_line, dl.end_col
|
|
417
|
+
FROM doc_links dl
|
|
418
|
+
JOIN documents d ON d.id = dl.source_doc_id
|
|
419
|
+
WHERE ${where} AND dl.source = 'parsed'
|
|
420
|
+
ORDER BY d.id, dl.start_line, dl.start_col, dl.id
|
|
421
|
+
LIMIT ?`
|
|
422
|
+
)
|
|
423
|
+
.all(...params, maxLinks);
|
|
424
|
+
const targets = rawLinks.map((row) => ({
|
|
425
|
+
targetRefNorm: row.target_ref_norm,
|
|
426
|
+
targetCollection: row.target_collection ?? row.source_collection,
|
|
427
|
+
linkType: row.link_type,
|
|
428
|
+
}));
|
|
429
|
+
const resolutions = resolveGraphLinkTargets(db, targets);
|
|
430
|
+
const uniqueTargetsResolved = new Set(
|
|
431
|
+
targets.map((target) =>
|
|
432
|
+
JSON.stringify([
|
|
433
|
+
target.linkType,
|
|
434
|
+
target.targetCollection,
|
|
435
|
+
target.targetRefNorm,
|
|
436
|
+
])
|
|
437
|
+
)
|
|
438
|
+
).size;
|
|
439
|
+
|
|
440
|
+
return {
|
|
441
|
+
documents: documentRows.map((row) => ({
|
|
442
|
+
id: row.id,
|
|
443
|
+
docid: row.docid,
|
|
444
|
+
uri: row.uri,
|
|
445
|
+
collection: row.collection,
|
|
446
|
+
relPath: row.rel_path,
|
|
447
|
+
recordSourcePath: row.record_source_path,
|
|
448
|
+
title: row.title,
|
|
449
|
+
mirrorHash: row.mirror_hash,
|
|
450
|
+
})),
|
|
451
|
+
links: rawLinks.map((row, index) => ({
|
|
452
|
+
sourceId: row.source_id,
|
|
453
|
+
sourceDocid: row.source_docid,
|
|
454
|
+
sourceUri: row.source_uri,
|
|
455
|
+
sourceCollection: row.source_collection,
|
|
456
|
+
sourceRelPath: row.source_rel_path,
|
|
457
|
+
targetRef: row.target_ref,
|
|
458
|
+
targetRefNorm: row.target_ref_norm,
|
|
459
|
+
targetAnchor: row.target_anchor,
|
|
460
|
+
targetCollection: row.target_collection ?? row.source_collection,
|
|
461
|
+
linkType: row.link_type,
|
|
462
|
+
startLine: row.start_line,
|
|
463
|
+
startCol: row.start_col,
|
|
464
|
+
endLine: row.end_line,
|
|
465
|
+
endCol: row.end_col,
|
|
466
|
+
resolved: resolutions[index] ?? null,
|
|
467
|
+
})),
|
|
468
|
+
totals: { documents: totalDocuments, links: totalLinks },
|
|
469
|
+
truncated: {
|
|
470
|
+
documents: totalDocuments > documentRows.length,
|
|
471
|
+
links: totalLinks > rawLinks.length,
|
|
472
|
+
},
|
|
473
|
+
metrics: {
|
|
474
|
+
documentRowsExamined: documentRows.length,
|
|
475
|
+
linkRowsExamined: rawLinks.length,
|
|
476
|
+
uniqueTargetsResolved,
|
|
477
|
+
batchedResolution: true,
|
|
478
|
+
},
|
|
479
|
+
};
|
|
480
|
+
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
c398c09680951afa0b5d436f3b92cf3dfb6e49427b9df83830ce899d3b03d7e4 gno-browser-clipper-v1.33.0.zip
|