@gmickel/gno 2.7.1 → 2.8.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 +3 -2
- package/assets/skill/SKILL.md +11 -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/skill/recipes/memory-scoped-recall.md +10 -5
- package/assets/spa-production.json.gz +0 -0
- package/browser-extension/artifacts/{gno-browser-clipper-v2.7.1.zip → gno-browser-clipper-v2.8.1.zip} +0 -0
- package/browser-extension/artifacts/gno-browser-clipper-v2.8.1.zip.sha256 +1 -0
- package/browser-extension/dist/manifest.json +1 -1
- package/package.json +1 -1
- package/spec/cli.md +86 -9
- package/spec/db/schema.sql +0 -1
- package/spec/mcp.md +26 -7
- 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/memory-recall.schema.json +1 -1
- package/spec/output-schemas/status.schema.json +18 -3
- 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/graph.ts +3 -1
- package/src/cli/commands/links.ts +61 -180
- package/src/cli/commands/shared.ts +7 -0
- package/src/cli/commands/status.ts +6 -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 +197 -25
- package/src/core/audit-outside-index.ts +215 -0
- package/src/core/audit-provenance.ts +11 -4
- package/src/core/audit-workspace.ts +30 -9
- package/src/core/audit.ts +76 -16
- 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-inventory-markdown.ts +2 -3
- package/src/core/link-workspace.ts +324 -0
- package/src/core/links.ts +40 -17
- package/src/core/memory-recall.ts +254 -15
- package/src/core/memory-types.ts +12 -0
- package/src/core/memory.ts +2 -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 +27 -4
- 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 +74 -93
- package/src/mcp/tools/sessions.ts +33 -4
- package/src/mcp/tools/status.ts +4 -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/search.ts +2 -0
- package/src/pipeline/types.ts +10 -3
- package/src/sdk/client.ts +1 -0
- package/src/serve/findings-pass.ts +1 -1
- package/src/serve/public/components/editor/MarkdownPreview.tsx +5 -3
- package/src/serve/public/pages/GraphView.tsx +2 -0
- package/src/serve/routes/changes.ts +6 -1
- package/src/serve/routes/graph.ts +3 -1
- package/src/serve/routes/links.ts +45 -50
- package/src/serve/routes/sessions.ts +41 -53
- package/src/serve/server.ts +2 -1
- package/src/serve/status.ts +1 -0
- 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 +477 -337
- package/src/store/sqlite/eligibility.ts +8 -2
- package/src/store/sqlite/graph-link-resolver.ts +259 -5
- package/src/store/sqlite/graph-neighbors.ts +147 -40
- package/src/store/sqlite/graph-reference-state.ts +13 -2
- package/src/store/sqlite/graph-similarity.ts +96 -0
- package/src/store/sqlite/workspace-link-resolver.ts +742 -0
- package/src/store/types.ts +64 -5
- package/src/store/vector/stats.ts +1 -1
- package/src/store/vector/status.ts +27 -0
- package/src/store/vector/stored-vectors.ts +158 -0
- package/src/store/vector/types.ts +6 -0
- package/src/store/vector/variant-search.ts +30 -14
- package/browser-extension/artifacts/gno-browser-clipper-v2.7.1.zip.sha256 +0 -1
|
@@ -9,6 +9,7 @@ import type { DocumentRow } from "../store/types";
|
|
|
9
9
|
import type {
|
|
10
10
|
AuditCategory,
|
|
11
11
|
AuditFingerprints,
|
|
12
|
+
AuditMaxFindings,
|
|
12
13
|
AuditRunResult,
|
|
13
14
|
AuditScope,
|
|
14
15
|
} from "./audit";
|
|
@@ -26,10 +27,12 @@ import {
|
|
|
26
27
|
AUDIT_RULE_SET_VERSION,
|
|
27
28
|
canonicalAuditJson,
|
|
28
29
|
hashAuditCanonical,
|
|
30
|
+
resolveAuditMaxFindings,
|
|
29
31
|
runAudit,
|
|
30
32
|
} from "./audit";
|
|
31
33
|
import { evaluateFreshnessAudit } from "./audit-freshness";
|
|
32
34
|
import { evaluateLinkAudit } from "./audit-links";
|
|
35
|
+
import { markOutsideIndexLinks } from "./audit-outside-index";
|
|
33
36
|
import { evaluateProvenanceAudit } from "./audit-provenance";
|
|
34
37
|
import {
|
|
35
38
|
extractCaptureSourceFromFrontmatter,
|
|
@@ -55,7 +58,7 @@ export interface WorkspaceAuditOptions {
|
|
|
55
58
|
collectionFilters?: readonly string[];
|
|
56
59
|
pathFilters?: readonly string[];
|
|
57
60
|
tagFilters?: readonly string[];
|
|
58
|
-
maxFindings?:
|
|
61
|
+
maxFindings?: AuditMaxFindings;
|
|
59
62
|
agePolicy?: AuditFreshnessOptions["agePolicy"];
|
|
60
63
|
orphanRoots?: readonly string[];
|
|
61
64
|
orphanIgnorePrefixes?: readonly string[];
|
|
@@ -347,7 +350,8 @@ const filterLinkSnapshot = (
|
|
|
347
350
|
snapshot: AuditLinkSnapshot,
|
|
348
351
|
selectedIds: ReadonlySet<number>,
|
|
349
352
|
selectedDocuments: readonly DocumentRow[],
|
|
350
|
-
selectionTruncated: boolean
|
|
353
|
+
selectionTruncated: boolean,
|
|
354
|
+
scopeCollections: readonly string[]
|
|
351
355
|
): AuditLinkSnapshot => {
|
|
352
356
|
const links = snapshot.links.filter(
|
|
353
357
|
(link) =>
|
|
@@ -362,6 +366,9 @@ const filterLinkSnapshot = (
|
|
|
362
366
|
// Preserve graph-wide documents as duplicate-mirror evidence while the
|
|
363
367
|
// explicit id set prevents findings outside the requested audit scope.
|
|
364
368
|
auditedDocumentIds: [...selectedIds],
|
|
369
|
+
...(scopeCollections.length > 0
|
|
370
|
+
? { scopeCollections: [...scopeCollections] }
|
|
371
|
+
: {}),
|
|
365
372
|
links,
|
|
366
373
|
totals: { documents: selectedDocuments.length, links: outgoingTotal },
|
|
367
374
|
truncated: {
|
|
@@ -423,14 +430,20 @@ const loadWorkspaceSnapshot = async (
|
|
|
423
430
|
? captureAuditLinkSnapshot(options.store.getRawDb())
|
|
424
431
|
: emptyLinkSnapshot();
|
|
425
432
|
const selectedIds = new Set(selected.documents.map(({ id }) => id));
|
|
433
|
+
const links = filterLinkSnapshot(
|
|
434
|
+
rawLinks,
|
|
435
|
+
selectedIds,
|
|
436
|
+
selected.documents,
|
|
437
|
+
selected.truncated,
|
|
438
|
+
filters.collections
|
|
439
|
+
);
|
|
426
440
|
return {
|
|
427
441
|
documents: observed,
|
|
428
|
-
links:
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
),
|
|
442
|
+
links: options.categories.includes("links")
|
|
443
|
+
? await markOutsideIndexLinks(options.store.getRawDb(), links, {
|
|
444
|
+
signal: options.signal,
|
|
445
|
+
})
|
|
446
|
+
: links,
|
|
434
447
|
truncated: selected.truncated,
|
|
435
448
|
};
|
|
436
449
|
};
|
|
@@ -596,6 +609,12 @@ export const runWorkspaceAudit = async (
|
|
|
596
609
|
snapshots.set(attempt, pending);
|
|
597
610
|
return pending;
|
|
598
611
|
};
|
|
612
|
+
// Per-family caps follow the effective report cap; runAudit rejects an
|
|
613
|
+
// invalid value before any rule runs.
|
|
614
|
+
const resolvedMaxFindings = resolveAuditMaxFindings(options.maxFindings);
|
|
615
|
+
const maxFindingsPerRule = resolvedMaxFindings.ok
|
|
616
|
+
? resolvedMaxFindings.limit
|
|
617
|
+
: 0;
|
|
599
618
|
const result = await runAudit({
|
|
600
619
|
scope,
|
|
601
620
|
capabilities: {
|
|
@@ -645,6 +664,7 @@ export const runWorkspaceAudit = async (
|
|
|
645
664
|
...evaluateLinkAudit(snapshot.links, {
|
|
646
665
|
rootUris: options.orphanRoots ?? [],
|
|
647
666
|
ignorePathPrefixes: options.orphanIgnorePrefixes ?? [],
|
|
667
|
+
maxFindingsPerRule,
|
|
648
668
|
})
|
|
649
669
|
);
|
|
650
670
|
await options.onProgress?.({
|
|
@@ -657,7 +677,7 @@ export const runWorkspaceAudit = async (
|
|
|
657
677
|
contributions.push(
|
|
658
678
|
...evaluateProvenanceAudit(
|
|
659
679
|
snapshot.documents.map(({ provenance }) => provenance),
|
|
660
|
-
{ truncated: snapshot.truncated }
|
|
680
|
+
{ truncated: snapshot.truncated, maxFindingsPerRule }
|
|
661
681
|
)
|
|
662
682
|
);
|
|
663
683
|
await options.onProgress?.({
|
|
@@ -674,6 +694,7 @@ export const runWorkspaceAudit = async (
|
|
|
674
694
|
now: options.now ?? new Date(),
|
|
675
695
|
agePolicy: options.agePolicy,
|
|
676
696
|
truncated: snapshot.truncated,
|
|
697
|
+
maxFindingsPerRule,
|
|
677
698
|
}
|
|
678
699
|
)
|
|
679
700
|
);
|
package/src/core/audit.ts
CHANGED
|
@@ -6,6 +6,7 @@ import type {
|
|
|
6
6
|
AuditFinding,
|
|
7
7
|
AuditFingerprints,
|
|
8
8
|
AuditForbiddenStoreMethod,
|
|
9
|
+
AuditMaxFindings,
|
|
9
10
|
AuditReport,
|
|
10
11
|
AuditReportStatus,
|
|
11
12
|
AuditRuleContribution,
|
|
@@ -24,6 +25,9 @@ import {
|
|
|
24
25
|
AUDIT_DEFAULT_MAX_FINDINGS,
|
|
25
26
|
AUDIT_FORBIDDEN_STORE_METHODS,
|
|
26
27
|
AUDIT_MAX_CODE_CHARS,
|
|
28
|
+
AUDIT_MAX_EVIDENCE_DETAIL_CHARS,
|
|
29
|
+
AUDIT_MAX_EVIDENCE_PER_FINDING,
|
|
30
|
+
AUDIT_MAX_FINDINGS_ALL,
|
|
27
31
|
AUDIT_MAX_FINDINGS_LIMIT,
|
|
28
32
|
AUDIT_MAX_IDENTIFIER_CHARS,
|
|
29
33
|
AUDIT_MAX_MESSAGE_CHARS,
|
|
@@ -127,23 +131,61 @@ export const normalizeAuditScope = (
|
|
|
127
131
|
};
|
|
128
132
|
};
|
|
129
133
|
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
|
|
134
|
+
export const AUDIT_MAX_FINDINGS_RANGE_MESSAGE = `maxFindings must be an integer between 1 and ${AUDIT_MAX_FINDINGS_LIMIT}, or "${AUDIT_MAX_FINDINGS_ALL}"`;
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Validate `maxFindings`. `limit` is the effective numeric cap (Infinity for
|
|
138
|
+
* `all`); `reported` is the value echoed in the report's truncation block.
|
|
139
|
+
*/
|
|
140
|
+
export const resolveAuditMaxFindings = (
|
|
141
|
+
value: AuditMaxFindings | undefined
|
|
142
|
+
):
|
|
143
|
+
| { ok: true; limit: number; reported: AuditMaxFindings }
|
|
144
|
+
| { ok: false; error: string } => {
|
|
145
|
+
if (value === AUDIT_MAX_FINDINGS_ALL) {
|
|
146
|
+
return {
|
|
147
|
+
ok: true,
|
|
148
|
+
limit: Number.POSITIVE_INFINITY,
|
|
149
|
+
reported: AUDIT_MAX_FINDINGS_ALL,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
133
152
|
const maxFindings = value ?? AUDIT_DEFAULT_MAX_FINDINGS;
|
|
134
153
|
if (
|
|
135
154
|
!Number.isSafeInteger(maxFindings) ||
|
|
136
155
|
maxFindings < 1 ||
|
|
137
156
|
maxFindings > AUDIT_MAX_FINDINGS_LIMIT
|
|
138
157
|
) {
|
|
139
|
-
return {
|
|
140
|
-
ok: false,
|
|
141
|
-
error: `maxFindings must be an integer between 1 and ${AUDIT_MAX_FINDINGS_LIMIT}`,
|
|
142
|
-
};
|
|
158
|
+
return { ok: false, error: AUDIT_MAX_FINDINGS_RANGE_MESSAGE };
|
|
143
159
|
}
|
|
144
|
-
return { ok: true, maxFindings };
|
|
160
|
+
return { ok: true, limit: maxFindings, reported: maxFindings };
|
|
145
161
|
};
|
|
146
162
|
|
|
163
|
+
/** Parse a CLI `--max-findings` value: a positive integer or `all`. */
|
|
164
|
+
export const parseAuditMaxFindingsInput = (
|
|
165
|
+
raw: string
|
|
166
|
+
): AuditMaxFindings | undefined => {
|
|
167
|
+
const value = raw.trim();
|
|
168
|
+
if (value.toLowerCase() === AUDIT_MAX_FINDINGS_ALL) {
|
|
169
|
+
return AUDIT_MAX_FINDINGS_ALL;
|
|
170
|
+
}
|
|
171
|
+
if (!/^\d+$/.test(value)) return undefined;
|
|
172
|
+
const parsed = Number(value);
|
|
173
|
+
return resolveAuditMaxFindings(parsed).ok ? parsed : undefined;
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
const draftEvidenceTruncated = (contribution: AuditRuleContribution): boolean =>
|
|
177
|
+
contribution.evidenceTruncated === true ||
|
|
178
|
+
(contribution.findings ?? []).some(
|
|
179
|
+
(draft) =>
|
|
180
|
+
draft.evidence.length > AUDIT_MAX_EVIDENCE_PER_FINDING ||
|
|
181
|
+
draft.evidence.some(
|
|
182
|
+
(item) =>
|
|
183
|
+
item.detail !== undefined &&
|
|
184
|
+
Array.from(normalizeAuditText(item.detail)).length >
|
|
185
|
+
AUDIT_MAX_EVIDENCE_DETAIL_CHARS
|
|
186
|
+
)
|
|
187
|
+
);
|
|
188
|
+
|
|
147
189
|
const fingerprintsEqual = (
|
|
148
190
|
left: AuditFingerprints,
|
|
149
191
|
right: AuditFingerprints
|
|
@@ -184,8 +226,16 @@ const materializeRule = (
|
|
|
184
226
|
)
|
|
185
227
|
.sort(compareAuditFindings);
|
|
186
228
|
|
|
229
|
+
// Only warning/error findings fail a passing rule; info findings of an
|
|
230
|
+
// informational rule (links.outside-index) leave it passing.
|
|
187
231
|
let status = contribution.status;
|
|
188
|
-
if (
|
|
232
|
+
if (
|
|
233
|
+
status === "pass" &&
|
|
234
|
+
findings.some(
|
|
235
|
+
(finding) =>
|
|
236
|
+
finding.severity === "error" || finding.severity === "warning"
|
|
237
|
+
)
|
|
238
|
+
) {
|
|
189
239
|
status = "fail";
|
|
190
240
|
}
|
|
191
241
|
|
|
@@ -231,7 +281,8 @@ const buildReportFromRules = (input: {
|
|
|
231
281
|
versions: AuditVersions;
|
|
232
282
|
rules: AuditRuleResult[];
|
|
233
283
|
status: AuditReportStatus;
|
|
234
|
-
maxFindings: number;
|
|
284
|
+
maxFindings: { limit: number; reported: AuditMaxFindings };
|
|
285
|
+
evidenceTruncated: boolean;
|
|
235
286
|
startedAt: string;
|
|
236
287
|
completedAt: string;
|
|
237
288
|
snapshotMs: number;
|
|
@@ -261,9 +312,9 @@ const buildReportFromRules = (input: {
|
|
|
261
312
|
(sum, rule) => sum + rule.findingCount,
|
|
262
313
|
0
|
|
263
314
|
);
|
|
264
|
-
const truncated = exactFindingCount > input.maxFindings;
|
|
315
|
+
const truncated = exactFindingCount > input.maxFindings.limit;
|
|
265
316
|
const returnedFindings = truncated
|
|
266
|
-
? uniqueFindings.slice(0, input.maxFindings)
|
|
317
|
+
? uniqueFindings.slice(0, input.maxFindings.limit)
|
|
267
318
|
: uniqueFindings;
|
|
268
319
|
const returnedFindingIds = new Set(
|
|
269
320
|
returnedFindings.map((finding) => finding.id)
|
|
@@ -308,7 +359,11 @@ const buildReportFromRules = (input: {
|
|
|
308
359
|
},
|
|
309
360
|
truncation: {
|
|
310
361
|
findingsTruncated: truncated,
|
|
311
|
-
maxFindings: input.maxFindings,
|
|
362
|
+
maxFindings: input.maxFindings.reported,
|
|
363
|
+
snapshotTruncated: rules.some(
|
|
364
|
+
(rule) => rule.skipReason === "snapshot_truncated"
|
|
365
|
+
),
|
|
366
|
+
evidenceTruncated: input.evidenceTruncated,
|
|
312
367
|
},
|
|
313
368
|
timing: {
|
|
314
369
|
snapshotMs: Math.max(0, Math.round(input.snapshotMs)),
|
|
@@ -329,7 +384,7 @@ export async function runAudit(input: AuditRunInput): Promise<AuditRunResult> {
|
|
|
329
384
|
if (!scopeResult.ok) {
|
|
330
385
|
return { ok: false, exit: "invalid", error: scopeResult.error };
|
|
331
386
|
}
|
|
332
|
-
const maxFindingsResult =
|
|
387
|
+
const maxFindingsResult = resolveAuditMaxFindings(input.maxFindings);
|
|
333
388
|
if (!maxFindingsResult.ok) {
|
|
334
389
|
return { ok: false, exit: "invalid", error: maxFindingsResult.error };
|
|
335
390
|
}
|
|
@@ -355,6 +410,7 @@ export async function runAudit(input: AuditRunInput): Promise<AuditRunResult> {
|
|
|
355
410
|
let snapshotMs = 0;
|
|
356
411
|
let rulesMs = 0;
|
|
357
412
|
let lastRules: AuditRuleResult[] = [];
|
|
413
|
+
let lastEvidenceTruncated = false;
|
|
358
414
|
let lastFingerprints: AuditFingerprints | null = null;
|
|
359
415
|
let snapshotChanged = false;
|
|
360
416
|
let failed = false;
|
|
@@ -405,6 +461,7 @@ export async function runAudit(input: AuditRunInput): Promise<AuditRunResult> {
|
|
|
405
461
|
try {
|
|
406
462
|
const contributions = await collectContributions(input.rules, ruleCtx);
|
|
407
463
|
lastRules = contributions.map(materializeRule);
|
|
464
|
+
lastEvidenceTruncated = contributions.some(draftEvidenceTruncated);
|
|
408
465
|
} catch (cause) {
|
|
409
466
|
if (input.signal?.aborted) {
|
|
410
467
|
lastRules = [cancellationRule()];
|
|
@@ -453,6 +510,7 @@ export async function runAudit(input: AuditRunInput): Promise<AuditRunResult> {
|
|
|
453
510
|
}
|
|
454
511
|
// Discard rule results from a drifted attempt; retry with a fresh snapshot.
|
|
455
512
|
lastRules = [];
|
|
513
|
+
lastEvidenceTruncated = false;
|
|
456
514
|
}
|
|
457
515
|
|
|
458
516
|
const completedAt = clock();
|
|
@@ -484,7 +542,8 @@ export async function runAudit(input: AuditRunInput): Promise<AuditRunResult> {
|
|
|
484
542
|
},
|
|
485
543
|
],
|
|
486
544
|
status: "failed",
|
|
487
|
-
maxFindings: maxFindingsResult
|
|
545
|
+
maxFindings: maxFindingsResult,
|
|
546
|
+
evidenceTruncated: false,
|
|
488
547
|
startedAt: runStartedAt.toISOString(),
|
|
489
548
|
completedAt: completedAt.toISOString(),
|
|
490
549
|
snapshotMs,
|
|
@@ -531,7 +590,8 @@ export async function runAudit(input: AuditRunInput): Promise<AuditRunResult> {
|
|
|
531
590
|
rules: rulesForReport,
|
|
532
591
|
snapshotChanged: false,
|
|
533
592
|
}),
|
|
534
|
-
maxFindings: maxFindingsResult
|
|
593
|
+
maxFindings: maxFindingsResult,
|
|
594
|
+
evidenceTruncated: lastEvidenceTruncated,
|
|
535
595
|
startedAt: runStartedAt.toISOString(),
|
|
536
596
|
completedAt: completedAt.toISOString(),
|
|
537
597
|
snapshotMs,
|
|
@@ -355,6 +355,9 @@ export const planContextEvidence = async <T, P>(
|
|
|
355
355
|
await deps.retrieve({
|
|
356
356
|
query,
|
|
357
357
|
collection,
|
|
358
|
+
// The primary search is partitioned per collection; graph expansion
|
|
359
|
+
// may bridge between the requested collections, never beyond them.
|
|
360
|
+
...(collections.length > 0 ? { graphCollections: collections } : {}),
|
|
358
361
|
noExpand: true,
|
|
359
362
|
queryModes,
|
|
360
363
|
tagsAll: input.tagsAll,
|
|
@@ -40,6 +40,7 @@ import {
|
|
|
40
40
|
import { planContextEvidence } from "./context-compiler";
|
|
41
41
|
import { projectContextEvidenceMetadata } from "./context-evidence-metadata";
|
|
42
42
|
import { createEgressLineage, resolveEgressLineage } from "./egress-provenance";
|
|
43
|
+
import { linkResolutionFingerprintInput } from "./link-workspace";
|
|
43
44
|
import { projectRecordEvidenceMetadata } from "./record-metadata";
|
|
44
45
|
import {
|
|
45
46
|
extractInclusiveLines,
|
|
@@ -205,6 +206,13 @@ const canonicalIndexSnapshot = (
|
|
|
205
206
|
),
|
|
206
207
|
});
|
|
207
208
|
|
|
209
|
+
const linkResolutionEntry = (
|
|
210
|
+
rows: Parameters<typeof linkResolutionFingerprintInput>[0]
|
|
211
|
+
): { linkResolution?: ReturnType<typeof linkResolutionFingerprintInput> } => {
|
|
212
|
+
const linkResolution = linkResolutionFingerprintInput(rows);
|
|
213
|
+
return linkResolution ? { linkResolution } : {};
|
|
214
|
+
};
|
|
215
|
+
|
|
208
216
|
/** Capture one strict, content-free index/context snapshot before or after work. */
|
|
209
217
|
export const captureContextEvidenceSnapshot = async (
|
|
210
218
|
store: ContextEvidenceStore,
|
|
@@ -284,6 +292,9 @@ export const captureContextEvidenceSnapshot = async (
|
|
|
284
292
|
indexFingerprint: hashJson({
|
|
285
293
|
snapshots: indexSnapshots,
|
|
286
294
|
egressLineage,
|
|
295
|
+
// Link workspace membership decides graph neighbours; a membership or
|
|
296
|
+
// resolver change is index drift. Absent without any workspace.
|
|
297
|
+
...linkResolutionEntry(collectionRows),
|
|
287
298
|
}),
|
|
288
299
|
};
|
|
289
300
|
};
|
|
@@ -17,8 +17,30 @@ export const GRAPH_EDGE_CONFIDENCE_RANK: Record<GraphEdgeConfidence, number> = {
|
|
|
17
17
|
export function classifyResolvedGraphEdge(
|
|
18
18
|
linkType: "wiki" | "markdown",
|
|
19
19
|
matchRank: number | null,
|
|
20
|
-
matchCount: number | null
|
|
20
|
+
matchCount: number | null,
|
|
21
|
+
/** Workspace resolution reason; semantic, independent of legacy ranks. */
|
|
22
|
+
reason?: string
|
|
21
23
|
): { confidence: GraphEdgeConfidence; audit: GraphEdgeAudit } {
|
|
24
|
+
if (linkType === "wiki" && reason !== undefined && reason !== "title") {
|
|
25
|
+
switch (reason) {
|
|
26
|
+
case "workspace-path":
|
|
27
|
+
case "collection-path":
|
|
28
|
+
return {
|
|
29
|
+
confidence: "explicit",
|
|
30
|
+
audit: { resolution: "exact-path", matchCount: 1 },
|
|
31
|
+
};
|
|
32
|
+
case "exact-name":
|
|
33
|
+
return {
|
|
34
|
+
confidence: "explicit",
|
|
35
|
+
audit: { resolution: "exact-name", matchCount: 1 },
|
|
36
|
+
};
|
|
37
|
+
default:
|
|
38
|
+
return {
|
|
39
|
+
confidence: "inferred",
|
|
40
|
+
audit: { resolution: "tie-break", matchCount: 1 },
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
}
|
|
22
44
|
if (linkType === "markdown") {
|
|
23
45
|
return {
|
|
24
46
|
confidence: "explicit",
|
package/src/core/host-paths.ts
CHANGED
|
@@ -41,6 +41,12 @@ export interface KnowledgeImpactResult {
|
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
export interface KnowledgeImpactInput {
|
|
44
|
+
/**
|
|
45
|
+
* Collection scope: only documents in these collections are traversed or
|
|
46
|
+
* returned, and a document outside them is never used as a bridge.
|
|
47
|
+
* Omitted or empty means every indexed collection.
|
|
48
|
+
*/
|
|
49
|
+
collections?: string[];
|
|
44
50
|
maxDepth?: number;
|
|
45
51
|
maxNodes?: number;
|
|
46
52
|
maxEdges?: number;
|
|
@@ -118,8 +124,30 @@ export async function analyzeKnowledgeImpact(
|
|
|
118
124
|
isValidation: true,
|
|
119
125
|
};
|
|
120
126
|
}
|
|
127
|
+
const scope = [...new Set(input.collections ?? [])].sort();
|
|
128
|
+
if (scope.length > 0) {
|
|
129
|
+
const known = await store.getCollections();
|
|
130
|
+
if (!known.ok) return { success: false, error: known.error.message };
|
|
131
|
+
const names = new Set(known.value.map((row) => row.name));
|
|
132
|
+
const missing = scope.find((name) => !names.has(name));
|
|
133
|
+
if (missing) {
|
|
134
|
+
return {
|
|
135
|
+
success: false,
|
|
136
|
+
error: `Collection not found: ${missing}`,
|
|
137
|
+
isValidation: true,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
if (!scope.includes(resolved.doc.collection)) {
|
|
141
|
+
return {
|
|
142
|
+
success: false,
|
|
143
|
+
error: `Document is outside the requested collections: ${ref}`,
|
|
144
|
+
isValidation: true,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
}
|
|
121
148
|
const traversal = await store.queryGraphTraversal(resolved.doc.id, {
|
|
122
149
|
direction: "in",
|
|
150
|
+
...(scope.length > 0 ? { collections: scope } : {}),
|
|
123
151
|
maxDepth: values.maxDepth,
|
|
124
152
|
maxNodes: values.maxNodes,
|
|
125
153
|
frontierLimit: values.frontierLimit,
|
|
@@ -23,7 +23,7 @@ import {
|
|
|
23
23
|
pushInventoryToken,
|
|
24
24
|
} from "./link-inventory-opaque";
|
|
25
25
|
import { isRelevantDestination } from "./link-relevance";
|
|
26
|
-
import { parseTargetParts } from "./links";
|
|
26
|
+
import { parseTargetParts, splitWikiLinkContent } from "./links";
|
|
27
27
|
|
|
28
28
|
const EXTERNAL_URL_REGEX = /^[a-z][a-z0-9+.-]*:/i;
|
|
29
29
|
|
|
@@ -51,8 +51,7 @@ export function inventoryWikiLinks(
|
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
const content = match[1] ?? "";
|
|
54
|
-
const
|
|
55
|
-
const targetPart = pipeIndex >= 0 ? content.slice(0, pipeIndex) : content;
|
|
54
|
+
const targetPart = splitWikiLinkContent(content).target;
|
|
56
55
|
const trimmedTarget = targetPart.trim();
|
|
57
56
|
if (!trimmedTarget) continue;
|
|
58
57
|
const leadingWs = targetPart.length - targetPart.trimStart().length;
|