@gmickel/gno 2.8.0 → 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 +1 -1
- package/assets/skill/SKILL.md +5 -2
- 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.8.0.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 +41 -6
- package/spec/mcp.md +8 -4
- package/spec/output-schemas/memory-recall.schema.json +1 -1
- package/spec/output-schemas/status.schema.json +3 -3
- package/src/cli/commands/graph.ts +3 -1
- package/src/cli/commands/links.ts +27 -49
- package/src/cli/commands/status.ts +1 -0
- package/src/core/audit-links.ts +56 -4
- package/src/core/audit-outside-index.ts +215 -0
- package/src/core/audit-workspace.ts +13 -7
- package/src/core/audit.ts +9 -1
- package/src/core/link-inventory-markdown.ts +2 -3
- 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/ingestion/sync.ts +5 -3
- package/src/mcp/tools/links.ts +71 -93
- package/src/mcp/tools/status.ts +1 -0
- package/src/pipeline/search.ts +2 -0
- package/src/pipeline/types.ts +4 -0
- package/src/sdk/client.ts +1 -0
- package/src/serve/public/components/editor/MarkdownPreview.tsx +5 -3
- package/src/serve/routes/graph.ts +3 -1
- package/src/serve/routes/links.ts +32 -50
- package/src/serve/server.ts +2 -1
- package/src/serve/status.ts +1 -0
- package/src/store/sqlite/adapter.ts +87 -106
- package/src/store/sqlite/graph-link-resolver.ts +7 -0
- package/src/store/sqlite/graph-similarity.ts +96 -0
- package/src/store/sqlite/workspace-link-resolver.ts +119 -31
- package/src/store/types.ts +15 -2
- 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.8.0.zip.sha256 +0 -1
package/README.md
CHANGED
|
@@ -139,7 +139,7 @@ See the [guide](docs/COMPILED-CONTEXT.md).
|
|
|
139
139
|
|
|
140
140
|
<!-- public-truth:current-version -->
|
|
141
141
|
|
|
142
|
-
> Current source version: **v2.8.
|
|
142
|
+
> Current source version: **v2.8.1**. See [CHANGELOG.md](./CHANGELOG.md).
|
|
143
143
|
|
|
144
144
|
<!-- /public-truth -->
|
|
145
145
|
|
package/assets/skill/SKILL.md
CHANGED
|
@@ -355,8 +355,11 @@ rewrite, persist findings, judge factual truth, or replace retrieval.
|
|
|
355
355
|
`--max-findings` accepts `all` (MCP `maxFindings: "all"`) to export every
|
|
356
356
|
finding. Link findings carry `referenceKind`, `resolutionStatus`, and
|
|
357
357
|
`resolvedScope` in their evidence detail; ambiguous vault links list the tied
|
|
358
|
-
`candidates`.
|
|
359
|
-
|
|
358
|
+
`candidates`. `links.outside-index` `info` findings are link targets that
|
|
359
|
+
exist in the vault but are not indexed (attachments, excluded or unindexed
|
|
360
|
+
folders); Obsidian resolves them, so do not report them as broken links.
|
|
361
|
+
Report `truncation.snapshotTruncated` as "totals cover the bounded snapshot,
|
|
362
|
+
not the whole index".
|
|
360
363
|
|
|
361
364
|
Treat exit `4` as a complete report with findings. Exit `5` or report status
|
|
362
365
|
`partial`/`changed_during_audit` means evidence is unavailable, inconclusive,
|
|
@@ -32,11 +32,16 @@ the flag.
|
|
|
32
32
|
Superseded records never appear.
|
|
33
33
|
- `budget.omitted` > 0: facts matched but did not fit. Narrow the query
|
|
34
34
|
or raise the budget.
|
|
35
|
-
- Empty `facts` plus a `hint`:
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
35
|
+
- Empty `facts` plus a `hint`: the hint says why. "No memories in scope
|
|
36
|
+
yet" means nothing is stored there; "No memories in scope matched this
|
|
37
|
+
query" means facts exist but none shares a word with the question, so
|
|
38
|
+
retry with other words before concluding memory is silent. The hints
|
|
39
|
+
name `gno remember` for when the user wants to store one.
|
|
40
|
+
- `retrieval.mode: "lexical"`: the collection has no cached embeddings.
|
|
41
|
+
Questions still work (question words are dropped and facts sharing a
|
|
42
|
+
content word are returned), but a paraphrase with no word in common
|
|
43
|
+
misses; embed the collection (`gno embed <collection>`) to match by
|
|
44
|
+
meaning.
|
|
40
45
|
|
|
41
46
|
3. Answer from the facts and cite each by its `gno://` URI. When memory is
|
|
42
47
|
silent or stale, fall through to `gno search` / `gno query` on the
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3f23832d1ed8b19e24eef29d6ea888a8c84009e39c61981d0b665c95cd7d792e gno-browser-clipper-v2.8.1.zip
|
package/package.json
CHANGED
package/spec/cli.md
CHANGED
|
@@ -240,7 +240,12 @@ the model with `id`, `model`, `dimensions`, `state` (`active`|`shadow`),
|
|
|
240
240
|
`provenance` (building runtime, e.g. `CUDA, Bun 1.4.2`),
|
|
241
241
|
`compatibleRuntimes` (runtimes that read it) and `incompatibleRuntimes`.
|
|
242
242
|
Terminal output prints a `Vector partitions:` block
|
|
243
|
-
unless there is exactly one healthy partition.
|
|
243
|
+
unless there is exactly one healthy partition. `collections`, `totalDocuments`
|
|
244
|
+
and `totalChunks` cover configured collections only: a collection removed from
|
|
245
|
+
config (for example by `gno collection remove`) is not reported even while its
|
|
246
|
+
rows remain in the index until the next `gno update`. `totalChunks` counts the
|
|
247
|
+
distinct chunks of active documents; chunks left by deleted documents do not
|
|
248
|
+
count. Per-collection chunk totals remain deduplicated by canonical chunk;
|
|
244
249
|
embedded counts require matching current inputs for every active owner within
|
|
245
250
|
that collection. Status reads persisted identity and coverage without loading
|
|
246
251
|
models. Legacy storage remains the fallback before variant authority; ambiguous
|
|
@@ -949,9 +954,9 @@ healthy.
|
|
|
949
954
|
Link findings resolve with the workspace-aware resolver (see
|
|
950
955
|
docs/ARCHITECTURE.md "Resolution"). Their evidence `detail` JSON carries
|
|
951
956
|
separate `referenceKind` (`wiki-name`, `wiki-path`, `explicit-collection`,
|
|
952
|
-
`markdown`), `resolutionStatus` (`unresolved`, `ambiguous`)
|
|
953
|
-
`resolvedScope` (`same-collection`, `cross-collection`,
|
|
954
|
-
or null when unresolved). Ambiguous workspace links add `candidateCount` and
|
|
957
|
+
`markdown`), `resolutionStatus` (`unresolved`, `ambiguous`, `outside-index`)
|
|
958
|
+
and `resolvedScope` (`same-collection`, `cross-collection`,
|
|
959
|
+
`explicit-collection`, or null when unresolved). Ambiguous workspace links add `candidateCount` and
|
|
955
960
|
`candidates` (tied candidate URIs in canonical path order); in a
|
|
956
961
|
collection-scoped audit, candidates outside the requested collections are
|
|
957
962
|
counted in `candidatesWithheld` and never named, and a list longer than the
|
|
@@ -960,6 +965,27 @@ Orphans stay "no incoming or outgoing resolved links", with connectivity drawn
|
|
|
960
965
|
from the whole index even when the audited documents are scoped; a tied link
|
|
961
966
|
connects nothing.
|
|
962
967
|
|
|
968
|
+
A plain wiki link or embed from a document in a link workspace whose target
|
|
969
|
+
is not an indexed document but exists as a file inside the workspace root (a
|
|
970
|
+
non-Markdown attachment, a note in an unindexed folder, a note excluded by a
|
|
971
|
+
collection pattern) is not reported by `links.local-targets`. It is an `info`
|
|
972
|
+
finding of `links.outside-index` (evidence kind `outside-index-target`,
|
|
973
|
+
`resolutionStatus: "outside-index"`). That rule stays `pass`, so these
|
|
974
|
+
findings never change the exit code. The check is existence-only: each
|
|
975
|
+
involved workspace root is listed once per run (hidden files and folders
|
|
976
|
+
skipped, symlinked folders not descended, a symlink counted only when it
|
|
977
|
+
resolves to a regular file inside the workspace, at most 200,000 files), no
|
|
978
|
+
file is opened, and no graph edge is created. Targets match files by the workspace
|
|
979
|
+
link rules; a non-Markdown target needs its extension, `.md` is optional. A
|
|
980
|
+
target missing from the listing stays unresolved; an unreadable folder or the
|
|
981
|
+
file bound makes the listing incomplete, which the `links.outside-index` rule
|
|
982
|
+
message states once.
|
|
983
|
+
|
|
984
|
+
The wiki link parser reads Obsidian's table-escaped alias `[[Note\|Alias]]`
|
|
985
|
+
as target `Note` with alias `Alias`. Markdown link text may contain balanced
|
|
986
|
+
square brackets (`[see [1]](note.md)`); a destination containing a square
|
|
987
|
+
bracket is not a link.
|
|
988
|
+
|
|
963
989
|
The JSON contract is versioned as `gno://schemas/audit-report@1.0`. Finding IDs
|
|
964
990
|
are stable SHA-256 identities derived from rule, normalized subject/location,
|
|
965
991
|
and load-bearing evidence. Wall-clock timing and traversal order do not affect
|
|
@@ -2014,6 +2040,12 @@ gno recall <query> --scope <scope> [--scope <scope>...] [--collection <name>] [-
|
|
|
2014
2040
|
`retrieval.semanticUnavailable` explaining why). Query expansion, graph
|
|
2015
2041
|
expansion, and reranking are disabled. Scope and supersession filtering run
|
|
2016
2042
|
inside the retrieval query; superseded facts are never returned.
|
|
2043
|
+
- The lexical leg searches the query's content words (question and function
|
|
2044
|
+
words dropped; quoted phrases and `-term` exclusions kept). Facts containing
|
|
2045
|
+
every content word are returned first; when none does, facts sharing any
|
|
2046
|
+
content word are returned in BM25 order, dropping those scoring below 0.1
|
|
2047
|
+
of the best match.
|
|
2048
|
+
- The budget is filled in retrieval-rank order.
|
|
2017
2049
|
- Budget: at most `--max-facts` facts (default 8) under `--max-tokens`
|
|
2018
2050
|
(default 512). Both must be positive integers. Recall never downloads a
|
|
2019
2051
|
model.
|
|
@@ -2023,8 +2055,11 @@ gno recall <query> --scope <scope> [--scope <scope>...] [--collection <name>] [-
|
|
|
2023
2055
|
(`caller`, `session`, `issuedAt`, `memoryIds`, `spanHashes`, `digest`) plus
|
|
2024
2056
|
`budget` and `retrieval`. Derived output inherits the strictest source
|
|
2025
2057
|
egress policy (`egressLineage`).
|
|
2026
|
-
- Empty recall prints
|
|
2027
|
-
(`
|
|
2058
|
+
- Empty recall prints a hint (`hint` in JSON) and exits 0. The hint
|
|
2059
|
+
distinguishes an empty scope (`No memories in scope yet. Store one with:
|
|
2060
|
+
gno remember ...`), a populated scope with no match (`No memories in scope
|
|
2061
|
+
matched this query. ...`, also naming `gno remember`), and matches that did
|
|
2062
|
+
not fit the token budget.
|
|
2028
2063
|
|
|
2029
2064
|
**Output:** `--json` prints the shared `RecallResult`. Terminal output lists
|
|
2030
2065
|
numbered facts with URI, text, scopes, hash, and identity, then `Budget:`,
|
package/spec/mcp.md
CHANGED
|
@@ -361,7 +361,10 @@ modifies source files, config, index rows, graph edges, daemon state, or a
|
|
|
361
361
|
persisted audit baseline.
|
|
362
362
|
|
|
363
363
|
Rules report `pass`, `fail`, `skip`, `unavailable`, or `inconclusive`; reports
|
|
364
|
-
are `complete`, `partial`, `changed_during_audit`, or `failed`.
|
|
364
|
+
are `complete`, `partial`, `changed_during_audit`, or `failed`. A passing rule
|
|
365
|
+
may carry `info` findings (`links.outside-index`: link targets that exist as
|
|
366
|
+
files in the link workspace but are not indexed); only `warning` and `error`
|
|
367
|
+
findings fail a rule. Link semantics match `gno audit` (spec/cli.md). Unavailable or
|
|
365
368
|
changing evidence never appears healthy. Stable finding IDs are derived from
|
|
366
369
|
rule, normalized subject/location, and evidence fingerprint. Responses are
|
|
367
370
|
canonically ordered and bounded while retaining exact pre-truncation totals.
|
|
@@ -1459,8 +1462,9 @@ Budgeted, cited, current-state recall from a memory-managed collection
|
|
|
1459
1462
|
the vector leg did not run
|
|
1460
1463
|
- `egressLineage` — strictest source policy across returned facts (absent when
|
|
1461
1464
|
empty)
|
|
1462
|
-
- `hint` —
|
|
1463
|
-
|
|
1465
|
+
- `hint` — present only when no fact was returned; says why: the scope holds
|
|
1466
|
+
no current fact (names `gno remember`), nothing in scope matched the query,
|
|
1467
|
+
or the matches did not fit `maxTokens`
|
|
1464
1468
|
|
|
1465
1469
|
**Identity:** `caller` is the MCP client implementation name from the
|
|
1466
1470
|
`initialize` handshake (`mcp` when absent); `session` is the Streamable HTTP
|
|
@@ -2185,7 +2189,7 @@ Find semantically similar documents using vector embeddings.
|
|
|
2185
2189
|
**Algorithm:**
|
|
2186
2190
|
|
|
2187
2191
|
1. Get all chunks for the source document
|
|
2188
|
-
2. Retrieve
|
|
2192
|
+
2. Retrieve each chunk's stored embedding from the active vector partition (legacy `content_vectors` only before any partition activates); no model is loaded
|
|
2189
2193
|
3. Compute average embedding across all chunks
|
|
2190
2194
|
4. Search for nearest neighbors using sqlite-vec
|
|
2191
2195
|
5. Exclude self and filter by collection if not crossCollection
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
},
|
|
39
39
|
"hint": {
|
|
40
40
|
"type": "string",
|
|
41
|
-
"description": "
|
|
41
|
+
"description": "Why nothing was returned (empty scope, no match in scope, or matches over the token budget); present only when no fact was returned."
|
|
42
42
|
}
|
|
43
43
|
},
|
|
44
44
|
"$defs": {
|
|
@@ -98,7 +98,7 @@
|
|
|
98
98
|
},
|
|
99
99
|
"collections": {
|
|
100
100
|
"type": "array",
|
|
101
|
-
"description": "
|
|
101
|
+
"description": "Statistics for configured collections; a collection removed from config is omitted even before its index rows are pruned",
|
|
102
102
|
"items": {
|
|
103
103
|
"type": "object",
|
|
104
104
|
"required": ["name", "documentCount", "chunkCount", "embeddedCount"],
|
|
@@ -146,12 +146,12 @@
|
|
|
146
146
|
},
|
|
147
147
|
"totalDocuments": {
|
|
148
148
|
"type": "integer",
|
|
149
|
-
"description": "
|
|
149
|
+
"description": "Active documents across configured collections",
|
|
150
150
|
"minimum": 0
|
|
151
151
|
},
|
|
152
152
|
"totalChunks": {
|
|
153
153
|
"type": "integer",
|
|
154
|
-
"description": "
|
|
154
|
+
"description": "Distinct chunks of active documents across configured collections",
|
|
155
155
|
"minimum": 0
|
|
156
156
|
},
|
|
157
157
|
"embeddingBacklog": {
|
|
@@ -14,6 +14,7 @@ import type {
|
|
|
14
14
|
|
|
15
15
|
import { normalizeContentTypes } from "../../config";
|
|
16
16
|
import { diagnoseGraphQuery } from "../../core/graph-query";
|
|
17
|
+
import { getActivePreset } from "../../llm/registry";
|
|
17
18
|
import { initStore } from "./shared";
|
|
18
19
|
|
|
19
20
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -125,7 +126,7 @@ export async function graph(
|
|
|
125
126
|
if (!initResult.ok) {
|
|
126
127
|
return { success: false, error: initResult.error };
|
|
127
128
|
}
|
|
128
|
-
const { store } = initResult;
|
|
129
|
+
const { store, config } = initResult;
|
|
129
130
|
|
|
130
131
|
try {
|
|
131
132
|
const storeOptions: GetGraphOptions = {
|
|
@@ -136,6 +137,7 @@ export async function graph(
|
|
|
136
137
|
threshold: options.threshold,
|
|
137
138
|
linkedOnly: !options.includeIsolated,
|
|
138
139
|
similarTopK: options.similarTopK,
|
|
140
|
+
embedModel: getActivePreset(config).embed,
|
|
139
141
|
};
|
|
140
142
|
|
|
141
143
|
const result = await store.getGraph(storeOptions);
|
|
@@ -5,12 +5,7 @@
|
|
|
5
5
|
* @module src/cli/commands/links
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import type {
|
|
9
|
-
DocEdgeRow,
|
|
10
|
-
DocLinkRow,
|
|
11
|
-
DocumentRow,
|
|
12
|
-
StorePort,
|
|
13
|
-
} from "../../store/types";
|
|
8
|
+
import type { DocEdgeRow, DocLinkRow, StorePort } from "../../store/types";
|
|
14
9
|
|
|
15
10
|
import { resolveDocRef } from "../../core/ref-parser";
|
|
16
11
|
import { initStore } from "./shared";
|
|
@@ -592,27 +587,23 @@ export async function similar(
|
|
|
592
587
|
}
|
|
593
588
|
const db = store.getRawDb();
|
|
594
589
|
|
|
595
|
-
//
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
)
|
|
613
|
-
.get(doc.mirrorHash, embedModel);
|
|
614
|
-
|
|
615
|
-
if (!fallbackRow) {
|
|
590
|
+
// Stored vector of the document's first chunk, from the active partition
|
|
591
|
+
const {
|
|
592
|
+
readStoredDocumentVectors,
|
|
593
|
+
resolveStoredVectorSource,
|
|
594
|
+
similarityHitDocuments,
|
|
595
|
+
storedVectorSearchOptions,
|
|
596
|
+
} = await import("../../store/vector/stored-vectors.js");
|
|
597
|
+
const source = resolveStoredVectorSource(db, modelPreset.embed);
|
|
598
|
+
const [embedding] =
|
|
599
|
+
readStoredDocumentVectors(
|
|
600
|
+
db,
|
|
601
|
+
source,
|
|
602
|
+
[{ id: doc.id, mirrorHash: doc.mirrorHash }],
|
|
603
|
+
{ firstChunkOnly: true }
|
|
604
|
+
).get(doc.id) ?? [];
|
|
605
|
+
|
|
606
|
+
if (!embedding) {
|
|
616
607
|
return {
|
|
617
608
|
success: false,
|
|
618
609
|
error: "Document has no embeddings. Run: gno embed",
|
|
@@ -621,9 +612,6 @@ export async function similar(
|
|
|
621
612
|
}
|
|
622
613
|
|
|
623
614
|
// Normalize embedding for cosine similarity
|
|
624
|
-
const { decodeEmbedding } =
|
|
625
|
-
await import("../../store/vector/sqlite-vec.js");
|
|
626
|
-
const embedding = decodeEmbedding(fallbackRow.embedding);
|
|
627
615
|
const dimensions = embedding.length;
|
|
628
616
|
let norm = 0;
|
|
629
617
|
for (let i = 0; i < dimensions; i++) {
|
|
@@ -641,7 +629,7 @@ export async function similar(
|
|
|
641
629
|
const { createVectorIndexPort } =
|
|
642
630
|
await import("../../store/vector/sqlite-vec.js");
|
|
643
631
|
const vecResult = await createVectorIndexPort(db, {
|
|
644
|
-
model:
|
|
632
|
+
model: modelPreset.embed,
|
|
645
633
|
dimensions,
|
|
646
634
|
});
|
|
647
635
|
if (!vecResult.ok) {
|
|
@@ -663,13 +651,13 @@ export async function similar(
|
|
|
663
651
|
const searchResult = await vectorIndex.searchNearest(
|
|
664
652
|
embedding,
|
|
665
653
|
candidateLimit,
|
|
666
|
-
|
|
654
|
+
storedVectorSearchOptions(source)
|
|
667
655
|
);
|
|
668
656
|
if (!searchResult.ok) {
|
|
669
657
|
return { success: false, error: searchResult.error.message };
|
|
670
658
|
}
|
|
671
659
|
|
|
672
|
-
//
|
|
660
|
+
// Candidate documents from a single listDocuments call
|
|
673
661
|
const docsResult = crossCollection
|
|
674
662
|
? await store.listDocuments()
|
|
675
663
|
: await store.listDocuments(doc.collection);
|
|
@@ -678,28 +666,18 @@ export async function similar(
|
|
|
678
666
|
return { success: false, error: docsResult.error.message };
|
|
679
667
|
}
|
|
680
668
|
|
|
681
|
-
|
|
682
|
-
for (const d of docsResult.value) {
|
|
683
|
-
if (d.active && d.mirrorHash) {
|
|
684
|
-
// Only keep first doc per hash (they have same content)
|
|
685
|
-
if (!docsByHash.has(d.mirrorHash)) {
|
|
686
|
-
docsByHash.set(d.mirrorHash, d);
|
|
687
|
-
}
|
|
688
|
-
}
|
|
689
|
-
}
|
|
690
|
-
|
|
691
|
-
// Map results to documents, excluding self
|
|
669
|
+
// Map hits to their owning documents, excluding self
|
|
692
670
|
const similarItems: SimilarItem[] = [];
|
|
693
671
|
const seenDocids = new Set<string>();
|
|
694
672
|
|
|
695
|
-
for (const
|
|
673
|
+
for (const { document: d, distance } of similarityHitDocuments(
|
|
674
|
+
searchResult.value,
|
|
675
|
+
docsResult.value.filter((d) => d.active && d.mirrorHash)
|
|
676
|
+
)) {
|
|
696
677
|
if (similarItems.length >= limit) {
|
|
697
678
|
break;
|
|
698
679
|
}
|
|
699
680
|
|
|
700
|
-
const d = docsByHash.get(vec.mirrorHash);
|
|
701
|
-
if (!d) continue;
|
|
702
|
-
|
|
703
681
|
// Exclude self
|
|
704
682
|
if (d.docid === doc.docid) continue;
|
|
705
683
|
|
|
@@ -708,7 +686,7 @@ export async function similar(
|
|
|
708
686
|
|
|
709
687
|
// Compute similarity score from cosine distance
|
|
710
688
|
// sqlite-vec with cosine metric returns distance where similarity = 1 - distance
|
|
711
|
-
const score = Math.max(0, Math.min(1, 1 -
|
|
689
|
+
const score = Math.max(0, Math.min(1, 1 - distance));
|
|
712
690
|
|
|
713
691
|
if (score < threshold) continue;
|
|
714
692
|
|
|
@@ -339,6 +339,7 @@ export async function status(
|
|
|
339
339
|
const statusResult = await store.getStatus({
|
|
340
340
|
embedModel: resolveModelUri(config, "embed"),
|
|
341
341
|
chunking: config.chunking ?? {},
|
|
342
|
+
configuredCollections: config.collections.map(({ name }) => name),
|
|
342
343
|
});
|
|
343
344
|
if (!statusResult.ok) {
|
|
344
345
|
return { success: false, error: statusResult.error.message };
|
package/src/core/audit-links.ts
CHANGED
|
@@ -13,8 +13,11 @@ import {
|
|
|
13
13
|
compareAuditFindingDrafts,
|
|
14
14
|
} from "./audit";
|
|
15
15
|
|
|
16
|
-
/**
|
|
17
|
-
|
|
16
|
+
/**
|
|
17
|
+
* 1.1: workspace-wide resolution and reference/resolution/scope evidence.
|
|
18
|
+
* 1.2: `links.outside-index` for workspace files that are not indexed.
|
|
19
|
+
*/
|
|
20
|
+
export const LINK_AUDIT_RULE_VERSION = "1.2" as const;
|
|
18
21
|
/** Default per-rule cap; runs pass the effective `maxFindings` instead. */
|
|
19
22
|
export const LINK_AUDIT_MAX_FINDINGS_PER_RULE = 1000;
|
|
20
23
|
|
|
@@ -70,6 +73,7 @@ const linkFinding = (
|
|
|
70
73
|
onEvidenceTruncated: () => void
|
|
71
74
|
): AuditFindingDraft => {
|
|
72
75
|
const ambiguous = (link.resolved?.matchCount ?? 0) > 1;
|
|
76
|
+
const outsideIndex = !ambiguous && link.outsideIndex === true;
|
|
73
77
|
const target = `${link.targetCollection}:${link.targetRef}`;
|
|
74
78
|
const kind = referenceKind(link);
|
|
75
79
|
const tied = (link.resolved?.candidates ?? []).map((candidate) =>
|
|
@@ -101,7 +105,11 @@ const linkFinding = (
|
|
|
101
105
|
matchRank: link.resolved?.matchRank ?? null,
|
|
102
106
|
normalizedTarget: link.targetRefNorm,
|
|
103
107
|
referenceKind: kind,
|
|
104
|
-
resolutionStatus: ambiguous
|
|
108
|
+
resolutionStatus: ambiguous
|
|
109
|
+
? "ambiguous"
|
|
110
|
+
: outsideIndex
|
|
111
|
+
? "outside-index"
|
|
112
|
+
: "unresolved",
|
|
105
113
|
resolvedScope,
|
|
106
114
|
};
|
|
107
115
|
// Candidate URIs are added while the detail stays within the evidence
|
|
@@ -140,6 +148,26 @@ const linkFinding = (
|
|
|
140
148
|
candidates.uris.pop();
|
|
141
149
|
}
|
|
142
150
|
if (candidates.truncated) onEvidenceTruncated();
|
|
151
|
+
if (outsideIndex) {
|
|
152
|
+
return {
|
|
153
|
+
subject: link.sourceUri,
|
|
154
|
+
location: lineLocation(link.startLine, link.startCol),
|
|
155
|
+
severity: "info",
|
|
156
|
+
message: `Link target exists outside the index: ${target}`,
|
|
157
|
+
evidence: [
|
|
158
|
+
{
|
|
159
|
+
kind: "outside-index-target",
|
|
160
|
+
summary: target,
|
|
161
|
+
uri: link.sourceUri,
|
|
162
|
+
path: link.sourceRelPath,
|
|
163
|
+
detail: render(),
|
|
164
|
+
},
|
|
165
|
+
],
|
|
166
|
+
guidance: [
|
|
167
|
+
"No action needed; index the target's folder to add it to the link graph",
|
|
168
|
+
],
|
|
169
|
+
};
|
|
170
|
+
}
|
|
143
171
|
return {
|
|
144
172
|
subject: link.sourceUri,
|
|
145
173
|
location: lineLocation(link.startLine, link.startCol),
|
|
@@ -210,6 +238,7 @@ export const evaluateLinkAudit = (
|
|
|
210
238
|
policy: AuditOrphanPolicy
|
|
211
239
|
): AuditRuleContribution[] => {
|
|
212
240
|
const unresolved: AuditFindingDraft[] = [];
|
|
241
|
+
const outsideIndex: AuditFindingDraft[] = [];
|
|
213
242
|
const ambiguous: AuditFindingDraft[] = [];
|
|
214
243
|
const connected = new Set<number>();
|
|
215
244
|
const auditedDocumentIds = new Set(
|
|
@@ -229,7 +258,11 @@ export const evaluateLinkAudit = (
|
|
|
229
258
|
for (const link of snapshot.links) {
|
|
230
259
|
const sourceAudited = auditedDocumentIds.has(link.sourceId);
|
|
231
260
|
if (!link.resolved) {
|
|
232
|
-
if (sourceAudited)
|
|
261
|
+
if (sourceAudited) {
|
|
262
|
+
(link.outsideIndex === true ? outsideIndex : unresolved).push(
|
|
263
|
+
finding(link)
|
|
264
|
+
);
|
|
265
|
+
}
|
|
233
266
|
continue;
|
|
234
267
|
}
|
|
235
268
|
// A tied workspace link is not a resolved link: it connects nothing.
|
|
@@ -327,6 +360,25 @@ export const evaluateLinkAudit = (
|
|
|
327
360
|
findingCount: orphanFindings.length,
|
|
328
361
|
skipReason: partial ? "snapshot_truncated" : null,
|
|
329
362
|
},
|
|
363
|
+
{
|
|
364
|
+
// Informational: Obsidian resolves these links, so they never fail
|
|
365
|
+
// the audit. Info findings keep the rule passing.
|
|
366
|
+
...common,
|
|
367
|
+
ruleId: "links.outside-index",
|
|
368
|
+
category: "links",
|
|
369
|
+
status: partial ? ("inconclusive" as const) : ("pass" as const),
|
|
370
|
+
message: [
|
|
371
|
+
partial
|
|
372
|
+
? "Outside-index scan was truncated"
|
|
373
|
+
: `${outsideIndex.length} links point to existing files outside the index`,
|
|
374
|
+
...(snapshot.outsideIndexDiagnostic
|
|
375
|
+
? [snapshot.outsideIndexDiagnostic]
|
|
376
|
+
: []),
|
|
377
|
+
].join("; "),
|
|
378
|
+
findings: boundedFindings(outsideIndex, maxFindingsPerRule),
|
|
379
|
+
findingCount: outsideIndex.length,
|
|
380
|
+
skipReason: partial ? "snapshot_truncated" : null,
|
|
381
|
+
},
|
|
330
382
|
{
|
|
331
383
|
...common,
|
|
332
384
|
ruleId: "links.parser-boundary",
|