@gmickel/gno 2.7.1 → 2.8.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 +3 -2
- package/assets/skill/SKILL.md +8 -1
- package/assets/skill/cli-reference.md +8 -1
- package/assets/skill/examples.md +2 -1
- package/assets/skill/mcp-reference.md +3 -1
- package/assets/spa-production.json.gz +0 -0
- package/browser-extension/artifacts/{gno-browser-clipper-v2.7.1.zip → gno-browser-clipper-v2.8.0.zip} +0 -0
- package/browser-extension/artifacts/gno-browser-clipper-v2.8.0.zip.sha256 +1 -0
- package/browser-extension/dist/manifest.json +1 -1
- package/package.json +1 -1
- package/spec/cli.md +48 -6
- package/spec/db/schema.sql +0 -1
- package/spec/mcp.md +18 -3
- package/spec/output-schemas/audit-report.schema.json +18 -4
- package/spec/output-schemas/backlinks.schema.json +4 -0
- package/spec/output-schemas/collection-list.schema.json +13 -0
- package/spec/output-schemas/graph.schema.json +2 -0
- package/spec/output-schemas/links-list.schema.json +4 -0
- package/spec/output-schemas/status.schema.json +15 -0
- package/src/cli/commands/audit.ts +23 -4
- package/src/cli/commands/collection/list.ts +39 -5
- package/src/cli/commands/embed.ts +3 -3
- package/src/cli/commands/links.ts +34 -131
- package/src/cli/commands/shared.ts +7 -0
- package/src/cli/commands/status.ts +5 -0
- package/src/cli/program.ts +12 -2
- package/src/config/loader.ts +43 -0
- package/src/config/types.ts +8 -0
- package/src/core/audit-contract.ts +16 -4
- package/src/core/audit-freshness.ts +11 -1
- package/src/core/audit-links.ts +145 -25
- package/src/core/audit-provenance.ts +11 -4
- package/src/core/audit-workspace.ts +19 -4
- package/src/core/audit.ts +67 -15
- package/src/core/context-compiler.ts +3 -0
- package/src/core/context-evidence.ts +11 -0
- package/src/core/graph-edge-confidence.ts +23 -1
- package/src/core/host-paths.ts +1 -0
- package/src/core/knowledge-impact.ts +28 -0
- package/src/core/link-workspace.ts +324 -0
- package/src/core/retrieval-replay-candidate.ts +6 -0
- package/src/core/retrieval-trace-request.ts +3 -0
- package/src/index.ts +14 -1
- package/src/ingestion/graph-reconciliation.ts +77 -15
- package/src/ingestion/source-availability/darwin-path.ts +9 -3
- package/src/ingestion/sync.ts +22 -1
- package/src/ingestion/types.ts +14 -0
- package/src/llm/inference-scope.ts +4 -3
- package/src/mcp/http-egress.ts +42 -3
- package/src/mcp/tools/audit.ts +11 -2
- package/src/mcp/tools/changes.ts +1 -0
- package/src/mcp/tools/links.ts +3 -0
- package/src/mcp/tools/sessions.ts +33 -4
- package/src/mcp/tools/status.ts +3 -0
- package/src/pipeline/expansion.ts +19 -31
- package/src/pipeline/graph-retrieval.ts +22 -2
- package/src/pipeline/hybrid.ts +1 -1
- package/src/pipeline/types.ts +6 -3
- package/src/serve/findings-pass.ts +1 -1
- package/src/serve/public/pages/GraphView.tsx +2 -0
- package/src/serve/routes/changes.ts +6 -1
- package/src/serve/routes/links.ts +13 -0
- package/src/serve/routes/sessions.ts +41 -53
- package/src/sessions/config-refresh.ts +111 -0
- package/src/store/migrations/033-drop-documents-active-index.ts +30 -0
- package/src/store/migrations/034-collection-link-workspace.ts +47 -0
- package/src/store/migrations/index.ts +4 -0
- package/src/store/sqlite/adapter.ts +390 -231
- package/src/store/sqlite/eligibility.ts +8 -2
- package/src/store/sqlite/graph-link-resolver.ts +252 -5
- package/src/store/sqlite/graph-neighbors.ts +147 -40
- package/src/store/sqlite/graph-reference-state.ts +13 -2
- package/src/store/sqlite/workspace-link-resolver.ts +654 -0
- package/src/store/types.ts +49 -3
- package/src/store/vector/stats.ts +1 -1
- package/browser-extension/artifacts/gno-browser-clipper-v2.7.1.zip.sha256 +0 -1
|
@@ -5,8 +5,6 @@
|
|
|
5
5
|
* @module src/cli/commands/links
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { basename } from "node:path";
|
|
9
|
-
|
|
10
8
|
import type {
|
|
11
9
|
DocEdgeRow,
|
|
12
10
|
DocLinkRow,
|
|
@@ -14,7 +12,6 @@ import type {
|
|
|
14
12
|
StorePort,
|
|
15
13
|
} from "../../store/types";
|
|
16
14
|
|
|
17
|
-
import { normalizeWikiName } from "../../core/links";
|
|
18
15
|
import { resolveDocRef } from "../../core/ref-parser";
|
|
19
16
|
import { initStore } from "./shared";
|
|
20
17
|
|
|
@@ -51,6 +48,8 @@ export interface LinkWithResolution {
|
|
|
51
48
|
resolved: boolean;
|
|
52
49
|
resolvedDocid?: string;
|
|
53
50
|
resolvedUri?: string;
|
|
51
|
+
/** Collection of the resolved target (may differ from the source's). */
|
|
52
|
+
resolvedCollection?: string;
|
|
54
53
|
}
|
|
55
54
|
|
|
56
55
|
export interface SemanticLinkItem {
|
|
@@ -155,6 +154,8 @@ export interface BacklinkItem {
|
|
|
155
154
|
sourceDocid: string;
|
|
156
155
|
sourceUri: string;
|
|
157
156
|
sourceTitle?: string;
|
|
157
|
+
/** Collection of the linking document. */
|
|
158
|
+
sourceCollection?: string;
|
|
158
159
|
linkText?: string;
|
|
159
160
|
startLine: number;
|
|
160
161
|
startCol: number;
|
|
@@ -233,96 +234,13 @@ export type SimilarResult =
|
|
|
233
234
|
| { success: true; data: SimilarResponse }
|
|
234
235
|
| { success: false; error: string; isValidation?: boolean };
|
|
235
236
|
|
|
236
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
237
|
-
// Helper: Build resolution indexes (cached per collection)
|
|
238
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
239
|
-
|
|
240
|
-
interface ResolutionIndexes {
|
|
241
|
-
// Map: normalized wiki name -> DocumentRow
|
|
242
|
-
wikiIndex: Map<string, DocumentRow>;
|
|
243
|
-
// Map: relPath -> DocumentRow
|
|
244
|
-
pathIndex: Map<string, DocumentRow>;
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
/** Normalize markdown link path for matching (strip ./, collapse ..) */
|
|
248
|
-
function normalizeMarkdownPath(path: string): string {
|
|
249
|
-
// Strip leading ./
|
|
250
|
-
let normalized = path.replace(/^\.\//, "");
|
|
251
|
-
// Collapse simple parent refs (a/b/../c -> a/c)
|
|
252
|
-
while (normalized.includes("/../")) {
|
|
253
|
-
normalized = normalized.replace(/[^/]+\/\.\.\//, "");
|
|
254
|
-
}
|
|
255
|
-
return normalized;
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
async function buildResolutionIndexes(
|
|
259
|
-
store: StorePort,
|
|
260
|
-
collection: string,
|
|
261
|
-
cache: Map<string, ResolutionIndexes>
|
|
262
|
-
): Promise<ResolutionIndexes> {
|
|
263
|
-
const cached = cache.get(collection);
|
|
264
|
-
if (cached) {
|
|
265
|
-
return cached;
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
const indexes: ResolutionIndexes = {
|
|
269
|
-
wikiIndex: new Map(),
|
|
270
|
-
pathIndex: new Map(),
|
|
271
|
-
};
|
|
272
|
-
|
|
273
|
-
const docsResult = await store.listDocuments(collection);
|
|
274
|
-
if (!docsResult.ok) {
|
|
275
|
-
// Collection may not exist or store error - return empty indexes
|
|
276
|
-
// Links to this collection will show as unresolved
|
|
277
|
-
cache.set(collection, indexes);
|
|
278
|
-
return indexes;
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
for (const d of docsResult.value) {
|
|
282
|
-
if (!d.active) continue;
|
|
283
|
-
|
|
284
|
-
// Index by relPath for markdown links (exact match)
|
|
285
|
-
indexes.pathIndex.set(d.relPath, d);
|
|
286
|
-
|
|
287
|
-
// Also index by normalized path (without ./) for common variants
|
|
288
|
-
const normalizedPath = normalizeMarkdownPath(d.relPath);
|
|
289
|
-
if (
|
|
290
|
-
normalizedPath !== d.relPath &&
|
|
291
|
-
!indexes.pathIndex.has(normalizedPath)
|
|
292
|
-
) {
|
|
293
|
-
indexes.pathIndex.set(normalizedPath, d);
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
// Index by normalized title for wiki links
|
|
297
|
-
if (d.title) {
|
|
298
|
-
const wikiKey = normalizeWikiName(d.title);
|
|
299
|
-
indexes.wikiIndex.set(wikiKey, d);
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
// Also index by filename stem as fallback for wiki links
|
|
303
|
-
const stem = basename(d.relPath).replace(/\.[^.]+$/, "");
|
|
304
|
-
if (stem) {
|
|
305
|
-
const stemKey = normalizeWikiName(stem);
|
|
306
|
-
// Don't overwrite title match
|
|
307
|
-
if (!indexes.wikiIndex.has(stemKey)) {
|
|
308
|
-
indexes.wikiIndex.set(stemKey, d);
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
cache.set(collection, indexes);
|
|
314
|
-
return indexes;
|
|
315
|
-
}
|
|
316
|
-
|
|
317
237
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
318
238
|
// Helper: Map DocLinkRow to output format (avoids null leakage)
|
|
319
239
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
320
240
|
|
|
321
241
|
function mapLinkToOutput(
|
|
322
242
|
link: DocLinkRow,
|
|
323
|
-
resolved:
|
|
324
|
-
resolvedDocid?: string,
|
|
325
|
-
resolvedUri?: string
|
|
243
|
+
resolved: { docid: string; uri: string; collection?: string } | null
|
|
326
244
|
): LinkWithResolution {
|
|
327
245
|
return {
|
|
328
246
|
targetRef: link.targetRef,
|
|
@@ -336,9 +254,12 @@ function mapLinkToOutput(
|
|
|
336
254
|
startCol: link.startCol,
|
|
337
255
|
endLine: link.endLine,
|
|
338
256
|
endCol: link.endCol,
|
|
339
|
-
resolved,
|
|
340
|
-
...(
|
|
341
|
-
|
|
257
|
+
resolved: resolved !== null,
|
|
258
|
+
...(resolved && {
|
|
259
|
+
resolvedDocid: resolved.docid,
|
|
260
|
+
resolvedUri: resolved.uri,
|
|
261
|
+
...(resolved.collection && { resolvedCollection: resolved.collection }),
|
|
262
|
+
}),
|
|
342
263
|
};
|
|
343
264
|
}
|
|
344
265
|
|
|
@@ -443,48 +364,29 @@ export async function linksList(
|
|
|
443
364
|
return a.startCol - b.startCol;
|
|
444
365
|
});
|
|
445
366
|
|
|
446
|
-
//
|
|
447
|
-
|
|
448
|
-
const
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
// Safe fallback for targetRefNorm
|
|
464
|
-
const targetNorm = link.targetRefNorm || link.targetRef;
|
|
465
|
-
|
|
466
|
-
if (link.linkType === "wiki") {
|
|
467
|
-
// Wiki links: match by normalized title or filename
|
|
468
|
-
const wikiKey = normalizeWikiName(targetNorm);
|
|
469
|
-
resolvedDoc = indexes.wikiIndex.get(wikiKey);
|
|
470
|
-
} else {
|
|
471
|
-
// Markdown links: match by relPath (try exact, then normalized)
|
|
472
|
-
resolvedDoc = indexes.pathIndex.get(targetNorm);
|
|
473
|
-
if (!resolvedDoc) {
|
|
474
|
-
const normalizedTarget = normalizeMarkdownPath(targetNorm);
|
|
475
|
-
resolvedDoc = indexes.pathIndex.get(normalizedTarget);
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
linksWithResolution.push(
|
|
480
|
-
mapLinkToOutput(
|
|
481
|
-
link,
|
|
482
|
-
!!resolvedDoc,
|
|
483
|
-
resolvedDoc?.docid,
|
|
484
|
-
resolvedDoc?.uri
|
|
485
|
-
)
|
|
486
|
-
);
|
|
367
|
+
// Resolve with the shared link resolver (workspace-aware for plain wiki
|
|
368
|
+
// links whose source sits in a link workspace).
|
|
369
|
+
const resolvedResult = await store.resolveLinks(
|
|
370
|
+
links.map((link) => ({
|
|
371
|
+
targetRefNorm: link.targetRefNorm || link.targetRef,
|
|
372
|
+
targetCollection: link.targetCollection ?? doc.collection,
|
|
373
|
+
linkType: link.linkType,
|
|
374
|
+
source: {
|
|
375
|
+
collection: doc.collection,
|
|
376
|
+
relPath: doc.relPath,
|
|
377
|
+
explicit: Boolean(link.targetCollection),
|
|
378
|
+
},
|
|
379
|
+
}))
|
|
380
|
+
);
|
|
381
|
+
if (!resolvedResult.ok) {
|
|
382
|
+
return { success: false, error: resolvedResult.error.message };
|
|
487
383
|
}
|
|
384
|
+
const linksWithResolution: LinkWithResolution[] = links.map(
|
|
385
|
+
(link, index) => {
|
|
386
|
+
const resolved = resolvedResult.value[index] ?? null;
|
|
387
|
+
return mapLinkToOutput(link, resolved);
|
|
388
|
+
}
|
|
389
|
+
);
|
|
488
390
|
|
|
489
391
|
const resolvedCount = linksWithResolution.filter((l) => l.resolved).length;
|
|
490
392
|
|
|
@@ -606,6 +508,7 @@ export async function backlinks(
|
|
|
606
508
|
sourceDocid: bl.sourceDocid,
|
|
607
509
|
sourceUri: bl.sourceDocUri,
|
|
608
510
|
...(bl.sourceDocTitle && { sourceTitle: bl.sourceDocTitle }),
|
|
511
|
+
...(bl.sourceCollection && { sourceCollection: bl.sourceCollection }),
|
|
609
512
|
...(bl.linkText && { linkText: bl.linkText }),
|
|
610
513
|
startLine: bl.startLine,
|
|
611
514
|
startCol: bl.startCol,
|
|
@@ -178,6 +178,13 @@ export function formatSyncResultLines(
|
|
|
178
178
|
`Rechunked ${syncResult.rechunkedMirrors} cached mirrors. Run gno embed if embedding was skipped.`
|
|
179
179
|
);
|
|
180
180
|
}
|
|
181
|
+
if (syncResult.graphRebuild) {
|
|
182
|
+
lines.push(
|
|
183
|
+
syncResult.graphRebuild === "resolver-upgrade"
|
|
184
|
+
? "Link graph rebuilt: link resolution was upgraded."
|
|
185
|
+
: "Link graph rebuilt: collection settings or link workspace membership changed."
|
|
186
|
+
);
|
|
187
|
+
}
|
|
181
188
|
|
|
182
189
|
for (const c of syncResult.collections) {
|
|
183
190
|
lines.push(`${c.collection}:`);
|
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
import { isConnectorActivationComplete } from "../../core/activation-connector-health";
|
|
22
22
|
import { buildActivationStatus } from "../../core/activation-status";
|
|
23
23
|
import { formatChunkingStatus } from "../../core/chunking-status";
|
|
24
|
+
import { formatLinkWorkspace } from "../../core/link-workspace";
|
|
24
25
|
import {
|
|
25
26
|
buildMemoryStatus,
|
|
26
27
|
formatMemoryStatusLines,
|
|
@@ -140,6 +141,8 @@ function formatTerminal(
|
|
|
140
141
|
` ${c.name}: ${c.activeDocuments} docs, ${c.totalChunks} chunks` +
|
|
141
142
|
(c.embeddedChunks > 0 ? `, ${c.embeddedChunks} embedded` : "")
|
|
142
143
|
);
|
|
144
|
+
const workspace = formatLinkWorkspace(c);
|
|
145
|
+
if (workspace) lines.push(` Link workspace: ${workspace}`);
|
|
143
146
|
}
|
|
144
147
|
}
|
|
145
148
|
|
|
@@ -395,6 +398,8 @@ export function formatStatus(
|
|
|
395
398
|
collections: s.collections.map((c) => ({
|
|
396
399
|
name: c.name,
|
|
397
400
|
path: c.path,
|
|
401
|
+
...(c.workspaceRoot ? { workspaceRoot: c.workspaceRoot } : {}),
|
|
402
|
+
workspaceSource: c.workspaceSource ?? "none",
|
|
398
403
|
documentCount: c.activeDocuments,
|
|
399
404
|
chunkCount: c.totalChunks,
|
|
400
405
|
embeddedCount: c.embeddedChunks,
|
package/src/cli/program.ts
CHANGED
|
@@ -1694,7 +1694,10 @@ function wireOnboardingCommands(program: Command): void {
|
|
|
1694
1694
|
collectRepeatableValue,
|
|
1695
1695
|
[]
|
|
1696
1696
|
)
|
|
1697
|
-
.option(
|
|
1697
|
+
.option(
|
|
1698
|
+
"--max-findings <count>",
|
|
1699
|
+
"maximum returned findings (1-100000, or all)"
|
|
1700
|
+
)
|
|
1698
1701
|
.option("--max-age-days <days>", "explicit age review signal", Number)
|
|
1699
1702
|
.option(
|
|
1700
1703
|
"--orphan-root <uri>",
|
|
@@ -1736,7 +1739,7 @@ function wireOnboardingCommands(program: Command): void {
|
|
|
1736
1739
|
collections: cmdOpts.collection as string[],
|
|
1737
1740
|
paths: cmdOpts.path as string[],
|
|
1738
1741
|
tags: cmdOpts.tag as string[],
|
|
1739
|
-
maxFindings: cmdOpts.maxFindings as
|
|
1742
|
+
maxFindings: cmdOpts.maxFindings as string | undefined,
|
|
1740
1743
|
maxAgeDays: cmdOpts.maxAgeDays as number | undefined,
|
|
1741
1744
|
orphanRoots: cmdOpts.orphanRoot as string[],
|
|
1742
1745
|
orphanIgnorePrefixes: cmdOpts.orphanIgnorePrefix as string[],
|
|
@@ -4749,6 +4752,12 @@ function wireKnowledgeDeltaCommands(program: Command): void {
|
|
|
4749
4752
|
program
|
|
4750
4753
|
.command("impact <doc>")
|
|
4751
4754
|
.description("Find bounded inbound knowledge dependencies")
|
|
4755
|
+
.option(
|
|
4756
|
+
"-c, --collection <name>",
|
|
4757
|
+
"only traverse these collections (repeatable; default all)",
|
|
4758
|
+
collectRepeatableValue,
|
|
4759
|
+
[]
|
|
4760
|
+
)
|
|
4752
4761
|
.option("--max-depth <n>", "maximum dependency depth", "3")
|
|
4753
4762
|
.option("--max-nodes <n>", "maximum returned nodes", "100")
|
|
4754
4763
|
.option("--max-edges <n>", "maximum traversed evidence edges", "250")
|
|
@@ -4764,6 +4773,7 @@ function wireKnowledgeDeltaCommands(program: Command): void {
|
|
|
4764
4773
|
const result = await impact(
|
|
4765
4774
|
doc,
|
|
4766
4775
|
{
|
|
4776
|
+
collections: cmdOpts.collection as string[],
|
|
4767
4777
|
maxDepth: parsePositiveInt("max-depth", cmdOpts.maxDepth),
|
|
4768
4778
|
maxNodes: parsePositiveInt("max-nodes", cmdOpts.maxNodes),
|
|
4769
4779
|
maxEdges: parsePositiveInt("max-edges", cmdOpts.maxEdges),
|
package/src/config/loader.ts
CHANGED
|
@@ -7,6 +7,10 @@
|
|
|
7
7
|
|
|
8
8
|
import type { ZodError } from "zod";
|
|
9
9
|
|
|
10
|
+
import {
|
|
11
|
+
validateWorkspaceRootSetting,
|
|
12
|
+
workspaceRootSettingMessage,
|
|
13
|
+
} from "../core/link-workspace";
|
|
10
14
|
import {
|
|
11
15
|
normalizeConfigContentTypes,
|
|
12
16
|
type ConfigWarning,
|
|
@@ -132,6 +136,18 @@ export async function loadConfigFromPath(
|
|
|
132
136
|
};
|
|
133
137
|
}
|
|
134
138
|
|
|
139
|
+
const workspaceIssues = validateCollectionWorkspaceRoots(result.data);
|
|
140
|
+
if (workspaceIssues.length > 0) {
|
|
141
|
+
return {
|
|
142
|
+
ok: false,
|
|
143
|
+
error: {
|
|
144
|
+
code: "VALIDATION_ERROR",
|
|
145
|
+
message: `Config validation failed: ${workspaceIssues.map((issue) => issue.message).join("; ")}`,
|
|
146
|
+
issues: workspaceIssues,
|
|
147
|
+
},
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
135
151
|
const normalized = normalizeConfigContentTypes(result.data);
|
|
136
152
|
return {
|
|
137
153
|
ok: true,
|
|
@@ -140,6 +156,33 @@ export async function loadConfigFromPath(
|
|
|
140
156
|
};
|
|
141
157
|
}
|
|
142
158
|
|
|
159
|
+
/**
|
|
160
|
+
* Validate explicit `workspaceRoot` settings against the filesystem: each must
|
|
161
|
+
* be absolute, exist, and contain its collection root. Messages name the
|
|
162
|
+
* collection so the error is actionable.
|
|
163
|
+
*/
|
|
164
|
+
export function validateCollectionWorkspaceRoots(
|
|
165
|
+
config: Config
|
|
166
|
+
): ZodError["issues"] {
|
|
167
|
+
const issues: ZodError["issues"] = [];
|
|
168
|
+
for (const [index, collection] of config.collections.entries()) {
|
|
169
|
+
if (typeof collection.workspaceRoot !== "string") continue;
|
|
170
|
+
const error = validateWorkspaceRootSetting(
|
|
171
|
+
collection.path,
|
|
172
|
+
collection.workspaceRoot
|
|
173
|
+
);
|
|
174
|
+
if (error) {
|
|
175
|
+
issues.push({
|
|
176
|
+
code: "custom",
|
|
177
|
+
message: workspaceRootSettingMessage(collection.name, error),
|
|
178
|
+
path: ["collections", index, "workspaceRoot"],
|
|
179
|
+
input: collection.workspaceRoot,
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return issues;
|
|
184
|
+
}
|
|
185
|
+
|
|
143
186
|
/**
|
|
144
187
|
* Load config, returning null if not found (convenience wrapper).
|
|
145
188
|
* Throws on parse/validation errors.
|
package/src/config/types.ts
CHANGED
|
@@ -166,6 +166,14 @@ export const CollectionSchema = z.object({
|
|
|
166
166
|
*/
|
|
167
167
|
sourceAvailability: SourceAvailabilitySchema.optional(),
|
|
168
168
|
|
|
169
|
+
/**
|
|
170
|
+
* Link workspace for plain wiki links. Omitted: auto-detect the nearest
|
|
171
|
+
* `.obsidian/` ancestor-or-self. An absolute path joins the collection to
|
|
172
|
+
* that workspace root (it must contain the collection root); `false` keeps
|
|
173
|
+
* the collection's links collection-scoped.
|
|
174
|
+
*/
|
|
175
|
+
workspaceRoot: z.union([z.string().min(1), z.literal(false)]).optional(),
|
|
176
|
+
|
|
169
177
|
/**
|
|
170
178
|
* Declares the collection as a GNO-managed memory substrate: `remember`
|
|
171
179
|
* writes fact files here and refuses every collection without the flag.
|
|
@@ -11,8 +11,11 @@ export const AUDIT_RULE_SET_VERSION = "1.0" as const;
|
|
|
11
11
|
|
|
12
12
|
/** Default returned finding cap; totals remain exact when truncated. */
|
|
13
13
|
export const AUDIT_DEFAULT_MAX_FINDINGS = 100;
|
|
14
|
-
/** Hard upper bound for `--max-findings` / MCP maxFindings. */
|
|
15
|
-
export const AUDIT_MAX_FINDINGS_LIMIT =
|
|
14
|
+
/** Hard numeric upper bound for `--max-findings` / MCP maxFindings. */
|
|
15
|
+
export const AUDIT_MAX_FINDINGS_LIMIT = 100_000;
|
|
16
|
+
/** Returns every finding of the bounded audit snapshot. */
|
|
17
|
+
export const AUDIT_MAX_FINDINGS_ALL = "all" as const;
|
|
18
|
+
export type AuditMaxFindings = number | typeof AUDIT_MAX_FINDINGS_ALL;
|
|
16
19
|
export const AUDIT_MAX_EVIDENCE_PER_FINDING = 8;
|
|
17
20
|
export const AUDIT_MAX_GUIDANCE_PER_FINDING = 4;
|
|
18
21
|
export const AUDIT_MAX_EVIDENCE_DETAIL_CHARS = 512;
|
|
@@ -201,7 +204,14 @@ export interface AuditCounts {
|
|
|
201
204
|
|
|
202
205
|
export interface AuditTruncation {
|
|
203
206
|
findingsTruncated: boolean;
|
|
204
|
-
maxFindings:
|
|
207
|
+
maxFindings: AuditMaxFindings;
|
|
208
|
+
/**
|
|
209
|
+
* The bounded audit snapshot (documents or links) was cut; finding totals
|
|
210
|
+
* cover the snapshot only and are not complete-index totals.
|
|
211
|
+
*/
|
|
212
|
+
snapshotTruncated: boolean;
|
|
213
|
+
/** Evidence items, candidate lists or evidence detail text were clipped. */
|
|
214
|
+
evidenceTruncated: boolean;
|
|
205
215
|
}
|
|
206
216
|
|
|
207
217
|
export interface AuditTiming {
|
|
@@ -255,6 +265,8 @@ export interface AuditRuleContribution {
|
|
|
255
265
|
findings?: AuditFindingDraft[];
|
|
256
266
|
/** Exact findings before any evaluator-side payload cap. */
|
|
257
267
|
findingCount?: number;
|
|
268
|
+
/** Evaluator clipped evidence (for example a long tied-candidate list). */
|
|
269
|
+
evidenceTruncated?: boolean;
|
|
258
270
|
examinedCount?: number;
|
|
259
271
|
durationMs?: number;
|
|
260
272
|
skipReason?: string | null;
|
|
@@ -283,7 +295,7 @@ export interface AuditRunInput {
|
|
|
283
295
|
capabilities: AuditCapabilitySnapshot;
|
|
284
296
|
captureFingerprints: AuditFingerprintCapture;
|
|
285
297
|
rules: readonly AuditRuleEvaluator[];
|
|
286
|
-
maxFindings?:
|
|
298
|
+
maxFindings?: AuditMaxFindings;
|
|
287
299
|
maxAttempts?: number;
|
|
288
300
|
gnoVersion?: string;
|
|
289
301
|
clock?: () => Date;
|
|
@@ -33,6 +33,8 @@ export interface AuditFreshnessOptions {
|
|
|
33
33
|
now: Date;
|
|
34
34
|
agePolicy?: AuditAgePolicy;
|
|
35
35
|
truncated?: boolean;
|
|
36
|
+
/** Effective per-rule cap; defaults to FRESHNESS_AUDIT_MAX_FINDINGS_PER_RULE. */
|
|
37
|
+
maxFindingsPerRule?: number;
|
|
36
38
|
}
|
|
37
39
|
|
|
38
40
|
const finding = (input: {
|
|
@@ -68,6 +70,7 @@ const rule = (input: {
|
|
|
68
70
|
skipped?: boolean;
|
|
69
71
|
message: string;
|
|
70
72
|
reason?: string;
|
|
73
|
+
maxFindingsPerRule?: number;
|
|
71
74
|
}): AuditRuleContribution => ({
|
|
72
75
|
ruleId: input.ruleId,
|
|
73
76
|
category: "freshness",
|
|
@@ -83,7 +86,10 @@ const rule = (input: {
|
|
|
83
86
|
message: input.message,
|
|
84
87
|
findings: [...input.findings]
|
|
85
88
|
.sort(compareAuditFindingDrafts)
|
|
86
|
-
.slice(
|
|
89
|
+
.slice(
|
|
90
|
+
0,
|
|
91
|
+
input.maxFindingsPerRule ?? FRESHNESS_AUDIT_MAX_FINDINGS_PER_RULE
|
|
92
|
+
),
|
|
87
93
|
findingCount: input.findings.length,
|
|
88
94
|
examinedCount: input.examinedCount,
|
|
89
95
|
skipReason: input.reason ?? null,
|
|
@@ -186,6 +192,7 @@ export const evaluateFreshnessAudit = (
|
|
|
186
192
|
: undefined;
|
|
187
193
|
return [
|
|
188
194
|
rule({
|
|
195
|
+
maxFindingsPerRule: options.maxFindingsPerRule,
|
|
189
196
|
ruleId: "freshness.source-readable",
|
|
190
197
|
findings: unavailable,
|
|
191
198
|
examinedCount: documents.length,
|
|
@@ -197,6 +204,7 @@ export const evaluateFreshnessAudit = (
|
|
|
197
204
|
reason,
|
|
198
205
|
}),
|
|
199
206
|
rule({
|
|
207
|
+
maxFindingsPerRule: options.maxFindingsPerRule,
|
|
200
208
|
ruleId: "freshness.source-index-drift",
|
|
201
209
|
findings: drift,
|
|
202
210
|
examinedCount: byteComparableCount,
|
|
@@ -207,6 +215,7 @@ export const evaluateFreshnessAudit = (
|
|
|
207
215
|
reason,
|
|
208
216
|
}),
|
|
209
217
|
rule({
|
|
218
|
+
maxFindingsPerRule: options.maxFindingsPerRule,
|
|
210
219
|
ruleId: "freshness.index-revision",
|
|
211
220
|
findings: staleRevision,
|
|
212
221
|
examinedCount: documents.length,
|
|
@@ -217,6 +226,7 @@ export const evaluateFreshnessAudit = (
|
|
|
217
226
|
reason,
|
|
218
227
|
}),
|
|
219
228
|
rule({
|
|
229
|
+
maxFindingsPerRule: options.maxFindingsPerRule,
|
|
220
230
|
ruleId: "freshness.configured-age-signal",
|
|
221
231
|
findings: ageSignals,
|
|
222
232
|
examinedCount: documents.length,
|