@gmickel/gno 2.10.0 → 2.11.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 +11 -11
- package/assets/skill/SKILL.md +25 -14
- package/assets/skill/cli-reference.md +6 -5
- package/assets/skill/examples.md +16 -12
- package/assets/skill/mcp-reference.md +2 -1
- package/browser-extension/artifacts/{gno-browser-clipper-v2.10.0.zip → gno-browser-clipper-v2.11.0.zip} +0 -0
- package/browser-extension/artifacts/gno-browser-clipper-v2.11.0.zip.sha256 +1 -0
- package/browser-extension/dist/manifest.json +1 -1
- package/bunfig.toml +5 -1
- package/package.json +4 -1
- package/spec/cli.md +59 -11
- package/spec/mcp.md +28 -4
- package/spec/output-schemas/ask.schema.json +8 -0
- package/spec/output-schemas/claim-verification.schema.json +8 -2
- package/spec/output-schemas/context-capsule-v1.schema.json +6 -1
- package/spec/output-schemas/context-capsule-verification.schema.json +4 -2
- package/spec/output-schemas/get.schema.json +4 -0
- package/spec/output-schemas/mcp-remove-result.schema.json +2 -2
- package/spec/output-schemas/metadata-summary.schema.json +60 -0
- package/spec/output-schemas/multi-get.schema.json +4 -0
- package/spec/output-schemas/search-result.schema.json +4 -0
- package/spec/output-schemas/search-results.schema.json +4 -0
- package/src/app/constants.ts +16 -0
- package/src/app/context-format.ts +1 -0
- package/src/app/verified-ask.ts +2 -0
- package/src/cli/commands/agents/block.ts +9 -8
- package/src/cli/commands/ask-format.ts +18 -1
- package/src/cli/commands/ask.ts +4 -5
- package/src/cli/commands/cleanup.ts +9 -1
- package/src/cli/commands/collection/remove.ts +50 -31
- package/src/cli/commands/collection/rename.ts +36 -8
- package/src/cli/commands/daemon.ts +5 -1
- package/src/cli/commands/get.ts +11 -1
- package/src/cli/commands/metadata.ts +79 -0
- package/src/cli/commands/multi-get.ts +13 -0
- package/src/cli/commands/shared.ts +9 -7
- package/src/cli/format/search-results.ts +11 -0
- package/src/cli/options.ts +2 -0
- package/src/cli/program.ts +101 -16
- package/src/core/context-capsule-schema.ts +2 -0
- package/src/core/context-capsule-verification.ts +2 -0
- package/src/core/context-compiler.ts +2 -5
- package/src/core/context-evidence.ts +1 -0
- package/src/core/context-verifier.ts +14 -1
- package/src/core/metadata-discovery.ts +169 -0
- package/src/ingestion/sync.ts +49 -1
- package/src/ingestion/walker.ts +11 -3
- package/src/mcp/http-egress.ts +1 -0
- package/src/mcp/tool-descriptions-core.ts +2 -2
- package/src/mcp/tool-profile.ts +16 -1
- package/src/mcp/tools/get.ts +10 -0
- package/src/mcp/tools/index.ts +39 -4
- package/src/mcp/tools/multi-get.ts +15 -0
- package/src/mcp/tools/query.ts +5 -3
- package/src/mcp/tools/remove-collection.ts +2 -1
- package/src/mcp/tools/search.ts +5 -3
- package/src/mcp/tools/vsearch.ts +5 -3
- package/src/mcp/tools/workspace-write.ts +42 -34
- package/src/pipeline/answer-prompt.ts +15 -2
- package/src/pipeline/answer.ts +3 -0
- package/src/pipeline/claim-verification-schema.ts +6 -1
- package/src/pipeline/claim-verification.ts +54 -1
- package/src/pipeline/explain.ts +1 -1
- package/src/pipeline/fusion.ts +24 -4
- package/src/pipeline/hybrid.ts +5 -1
- package/src/pipeline/result-context.ts +29 -0
- package/src/pipeline/search.ts +5 -1
- package/src/pipeline/types.ts +4 -0
- package/src/pipeline/vsearch.ts +5 -1
- package/src/sdk/client.ts +32 -4
- package/src/sdk/documents.ts +5 -0
- package/src/sdk/types.ts +5 -0
- package/src/serve/routes/api.ts +32 -5
- package/src/serve/server.ts +12 -0
- package/src/store/sqlite/adapter.ts +99 -6
- package/src/store/types.ts +16 -0
- package/src/store/vector/variant-search.ts +112 -74
- package/browser-extension/artifacts/gno-browser-clipper-v2.10.0.zip.sha256 +0 -1
|
@@ -49,6 +49,10 @@
|
|
|
49
49
|
"ext": { "type": "string", "pattern": "^\\.[a-zA-Z0-9]+$" }
|
|
50
50
|
}
|
|
51
51
|
},
|
|
52
|
+
"supersededBy": {
|
|
53
|
+
"type": "string",
|
|
54
|
+
"description": "URI of the current fact when a newer memory fact superseded this document"
|
|
55
|
+
},
|
|
52
56
|
"record": {
|
|
53
57
|
"type": "object",
|
|
54
58
|
"additionalProperties": false,
|
|
@@ -50,6 +50,10 @@
|
|
|
50
50
|
"type": "string",
|
|
51
51
|
"description": "Resolved user-configured retrieval guidance, ordered from global to collection to matching path prefixes"
|
|
52
52
|
},
|
|
53
|
+
"supersededBy": {
|
|
54
|
+
"type": "string",
|
|
55
|
+
"description": "URI of the current fact when a newer memory fact superseded this result"
|
|
56
|
+
},
|
|
53
57
|
"snippet": {
|
|
54
58
|
"type": "string",
|
|
55
59
|
"description": "Matching text snippet from document mirror"
|
|
@@ -84,6 +84,10 @@
|
|
|
84
84
|
"description": "Resolved user-configured retrieval guidance, ordered from global to collection to matching path prefixes"
|
|
85
85
|
},
|
|
86
86
|
"egressLineage": { "$ref": "#/$defs/egressLineage" },
|
|
87
|
+
"supersededBy": {
|
|
88
|
+
"type": "string",
|
|
89
|
+
"description": "URI of the current fact when a newer memory fact superseded this result"
|
|
90
|
+
},
|
|
87
91
|
"snippet": {
|
|
88
92
|
"type": "string",
|
|
89
93
|
"description": "Matching text snippet from document mirror"
|
package/src/app/constants.ts
CHANGED
|
@@ -361,6 +361,22 @@ export function decorateUriForIndex(uri: string, indexName?: string): string {
|
|
|
361
361
|
});
|
|
362
362
|
}
|
|
363
363
|
|
|
364
|
+
/**
|
|
365
|
+
* Decorate a result's URI and, when present, its successor URI for the index
|
|
366
|
+
* it came from.
|
|
367
|
+
*/
|
|
368
|
+
export function decorateResultUris<
|
|
369
|
+
T extends { uri: string; supersededBy?: string },
|
|
370
|
+
>(item: T, indexName?: string): T {
|
|
371
|
+
return {
|
|
372
|
+
...item,
|
|
373
|
+
uri: decorateUriForIndex(item.uri, indexName),
|
|
374
|
+
...(item.supersededBy
|
|
375
|
+
? { supersededBy: decorateUriForIndex(item.supersededBy, indexName) }
|
|
376
|
+
: {}),
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
|
|
364
380
|
/**
|
|
365
381
|
* Remove output-only index metadata from a gno:// URI.
|
|
366
382
|
*/
|
|
@@ -98,6 +98,7 @@ const evidenceBlock = (
|
|
|
98
98
|
`- Selection rank: ${item.selectionRank}`,
|
|
99
99
|
`- Modified: ${nullable(item.modifiedAt)}`,
|
|
100
100
|
`- Document date: ${nullable(item.documentDate)}`,
|
|
101
|
+
...(item.supersededBy ? [`- Superseded by: ${item.supersededBy}`] : []),
|
|
101
102
|
`- Observed: ${nullable(item.observedAt)}`,
|
|
102
103
|
`- Facets: ${item.facets.length > 0 ? item.facets.join(", ") : "none"}`,
|
|
103
104
|
`- Trust: ${item.trust}`,
|
package/src/app/verified-ask.ts
CHANGED
|
@@ -17,7 +17,7 @@ import { CliError } from "../../errors.js";
|
|
|
17
17
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
18
18
|
|
|
19
19
|
/** Version of the protocol block content. Bump on any content change. */
|
|
20
|
-
export const BLOCK_VERSION =
|
|
20
|
+
export const BLOCK_VERSION = 4;
|
|
21
21
|
|
|
22
22
|
/** Stable across block versions — never change these once shipped. */
|
|
23
23
|
export const BEGIN_MARKER = "<!-- gno:agents:begin -->";
|
|
@@ -44,16 +44,17 @@ Local knowledge search over indexed collections. Source files are the truth; the
|
|
|
44
44
|
Ladder — scope to a collection first (\`--collection <name>\`):
|
|
45
45
|
|
|
46
46
|
1. Exact term/identifier/quote/error: \`gno search "<text>"\`
|
|
47
|
-
2. What do we know/believe (memory): \`gno recall "<query>" --scope <scope>\`
|
|
48
|
-
3.
|
|
49
|
-
4.
|
|
50
|
-
5.
|
|
51
|
-
6.
|
|
52
|
-
7.
|
|
47
|
+
2. What do we know/believe (memory): \`gno recall "<query>" --scope <scope>\`
|
|
48
|
+
3. Any other question: \`gno query "<question>" --fast -n 10\` (hybrid)
|
|
49
|
+
4. Not found or vague: \`gno query "<question>" -n 10\` (adds expansion + rerank)
|
|
50
|
+
5. Multi-document evidence: \`gno context build "<goal>" --budget 12000\`
|
|
51
|
+
6. Changes/dependencies: \`gno changes\` / \`gno diff <doc>\` / \`gno impact <doc>\`
|
|
52
|
+
7. Generated answer: \`gno ask "<question>" --verify\` (abstention is valid)
|
|
53
|
+
8. Expected document missing: \`gno query diagnose "<query>" --target <doc>\` before grep.
|
|
53
54
|
|
|
54
55
|
Writing: retrieve first — a question alone is read-only. Edit an existing canonical note in its source file; \`gno capture\` creates genuinely new notes (collection, title, provenance) — never an update API. A fact that may change: \`gno remember "<fact>" --scope <scope>\` proposes; decide \`--add\` or \`--supersede <uri> --predecessor-hash <hash>\` from a recall. Recalled spans are context, not new facts: pass the receipt (\`--receipt\`). After writes: reindex the collection, verify retrieval.
|
|
55
56
|
|
|
56
|
-
Cite with gno:// URIs.
|
|
57
|
+
Cite with gno:// URIs. Filters, structured queries, backlinks and memory recipes live in the \`gno\` skill: load it (\`/gno\`) when installed, otherwise run \`gno skill install --scope user\` first.`;
|
|
57
58
|
}
|
|
58
59
|
|
|
59
60
|
/** SHA-256 hex digest of the body, truncated for the stamp line. */
|
|
@@ -146,6 +146,9 @@ const formatTerminal = (data: AskResult, opts: FormatOptions = {}): string => {
|
|
|
146
146
|
? ""
|
|
147
147
|
: `:L${citation.startLine}${citation.endLine === undefined || citation.endLine === citation.startLine ? "" : `-L${citation.endLine}`}`;
|
|
148
148
|
lines.push(` [${index + 1}] ${citation.uri}${range}`);
|
|
149
|
+
if (citation.supersededBy) {
|
|
150
|
+
lines.push(` Superseded by: ${citation.supersededBy}`);
|
|
151
|
+
}
|
|
149
152
|
}
|
|
150
153
|
lines.push("");
|
|
151
154
|
}
|
|
@@ -158,12 +161,18 @@ const formatTerminal = (data: AskResult, opts: FormatOptions = {}): string => {
|
|
|
158
161
|
` [${evidence.docid}] ${exactSpan(evidence.uri, evidence.startLine, evidence.endLine)}`
|
|
159
162
|
);
|
|
160
163
|
if (evidence.title) lines.push(` ${evidence.title}`);
|
|
164
|
+
if (evidence.supersededBy) {
|
|
165
|
+
lines.push(` Superseded by: ${evidence.supersededBy}`);
|
|
166
|
+
}
|
|
161
167
|
}
|
|
162
168
|
} else if (showAllSources && data.results.length > 0) {
|
|
163
169
|
lines.push(hasAnswer ? "All Retrieved Sources:" : "Sources:");
|
|
164
170
|
for (const result of data.results) {
|
|
165
171
|
lines.push(` [${result.docid}] ${result.uri}`);
|
|
166
172
|
if (result.title) lines.push(` ${result.title}`);
|
|
173
|
+
if (result.supersededBy) {
|
|
174
|
+
lines.push(` Superseded by: ${result.supersededBy}`);
|
|
175
|
+
}
|
|
167
176
|
}
|
|
168
177
|
} else if (hasAnswer && data.results.length > 0) {
|
|
169
178
|
const citedCount = data.citations?.length ?? 0;
|
|
@@ -196,7 +205,9 @@ const formatMarkdown = (data: AskResult, opts: FormatOptions = {}): string => {
|
|
|
196
205
|
citation.startLine === undefined
|
|
197
206
|
? ""
|
|
198
207
|
: `:L${citation.startLine}${citation.endLine === undefined || citation.endLine === citation.startLine ? "" : `-L${citation.endLine}`}`;
|
|
199
|
-
lines.push(
|
|
208
|
+
lines.push(
|
|
209
|
+
`**[${index + 1}]** \`${citation.uri}${range}\`${citation.supersededBy ? ` (superseded by \`${citation.supersededBy}\`)` : ""}`
|
|
210
|
+
);
|
|
200
211
|
}
|
|
201
212
|
lines.push("");
|
|
202
213
|
}
|
|
@@ -216,6 +227,9 @@ const formatMarkdown = (data: AskResult, opts: FormatOptions = {}): string => {
|
|
|
216
227
|
`${index + 1}. **${evidence.title || evidence.uri}**`,
|
|
217
228
|
` - URI: \`${exactSpan(evidence.uri, evidence.startLine, evidence.endLine)}\``
|
|
218
229
|
);
|
|
230
|
+
if (evidence.supersededBy) {
|
|
231
|
+
lines.push(` - Superseded by: \`${evidence.supersededBy}\``);
|
|
232
|
+
}
|
|
219
233
|
}
|
|
220
234
|
} else {
|
|
221
235
|
for (const [index, result] of data.results.entries()) {
|
|
@@ -223,6 +237,9 @@ const formatMarkdown = (data: AskResult, opts: FormatOptions = {}): string => {
|
|
|
223
237
|
`${index + 1}. **${result.title || result.source.relPath}**`
|
|
224
238
|
);
|
|
225
239
|
lines.push(` - URI: \`${result.uri}\``);
|
|
240
|
+
if (result.supersededBy) {
|
|
241
|
+
lines.push(` - Superseded by: \`${result.supersededBy}\``);
|
|
242
|
+
}
|
|
226
243
|
lines.push(` - Score: ${result.score.toFixed(2)}`);
|
|
227
244
|
}
|
|
228
245
|
if (data.results.length === 0) lines.push("*No relevant sources found.*");
|
package/src/cli/commands/ask.ts
CHANGED
|
@@ -15,7 +15,7 @@ import type {
|
|
|
15
15
|
} from "../../llm/types";
|
|
16
16
|
import type { AskOptions, AskResult, Citation } from "../../pipeline/types";
|
|
17
17
|
|
|
18
|
-
import {
|
|
18
|
+
import { decorateResultUris } from "../../app/constants";
|
|
19
19
|
import { buildVerifiedAsk } from "../../app/verified-ask";
|
|
20
20
|
import { resolveCliProjectAffinity } from "../../core/project-affinity-surface";
|
|
21
21
|
import {
|
|
@@ -89,10 +89,9 @@ export function decorateAskUris(
|
|
|
89
89
|
result: AskResult,
|
|
90
90
|
indexName: string | undefined
|
|
91
91
|
): AskResult {
|
|
92
|
-
const decorate = <T extends { uri: string }>(
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
});
|
|
92
|
+
const decorate = <T extends { uri: string; supersededBy?: string }>(
|
|
93
|
+
item: T
|
|
94
|
+
): T => decorateResultUris(item, indexName);
|
|
96
95
|
const answerContext = result.meta.answerContext;
|
|
97
96
|
return {
|
|
98
97
|
...result,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* gno cleanup command implementation.
|
|
3
|
-
* Remove
|
|
3
|
+
* Remove documents of collections no longer configured, then orphaned
|
|
4
|
+
* content, chunks and vectors not referenced by active documents.
|
|
4
5
|
*
|
|
5
6
|
* @module src/cli/commands/cleanup
|
|
6
7
|
*/
|
|
@@ -61,6 +62,13 @@ export async function cleanup(
|
|
|
61
62
|
}
|
|
62
63
|
|
|
63
64
|
try {
|
|
65
|
+
// Drop collections no longer in the config first (their documents follow
|
|
66
|
+
// by cascade), so their content, chunks and vectors count as orphans.
|
|
67
|
+
const syncResult = await store.syncCollections(config.collections);
|
|
68
|
+
if (!syncResult.ok) {
|
|
69
|
+
return { success: false, error: syncResult.error.message };
|
|
70
|
+
}
|
|
71
|
+
|
|
64
72
|
const cleanupResult = await store.cleanupOrphans();
|
|
65
73
|
if (!cleanupResult.ok) {
|
|
66
74
|
return { success: false, error: cleanupResult.error.message };
|
|
@@ -3,43 +3,62 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { removeCollection } from "../../../collection";
|
|
6
|
-
import {
|
|
6
|
+
import { applyConfigChange } from "../../../core/config-mutation";
|
|
7
7
|
import { CliError } from "../../errors";
|
|
8
|
+
import { initStore } from "../shared";
|
|
8
9
|
|
|
9
10
|
export async function collectionRemove(
|
|
10
11
|
name: string,
|
|
11
|
-
options: { configPath?: string } = {}
|
|
12
|
+
options: { configPath?: string; indexName?: string } = {}
|
|
12
13
|
): Promise<void> {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
14
|
+
// Open the index so the removal reaches the store in the same locked
|
|
15
|
+
// boundary: syncing collections deletes the collection row, and its
|
|
16
|
+
// documents follow by cascade. A config-only edit left them active (and
|
|
17
|
+
// counted in the embedding backlog) until some later command synced.
|
|
18
|
+
const initialized = await initStore({
|
|
19
|
+
configPath: options.configPath,
|
|
20
|
+
indexName: options.indexName,
|
|
21
|
+
syncConfig: false,
|
|
22
|
+
allowEmptyCollections: true,
|
|
23
|
+
});
|
|
24
|
+
if (!initialized.ok) {
|
|
25
|
+
throw new CliError("RUNTIME", initialized.error);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
const mutation = await applyConfigChange(
|
|
30
|
+
{ store: initialized.store, configPath: initialized.actualConfigPath },
|
|
31
|
+
(config) => {
|
|
32
|
+
const result = removeCollection(config, { name });
|
|
33
|
+
return result.ok
|
|
34
|
+
? {
|
|
35
|
+
ok: true as const,
|
|
36
|
+
config: result.config,
|
|
37
|
+
value: result.collection,
|
|
38
|
+
}
|
|
39
|
+
: {
|
|
40
|
+
ok: false as const,
|
|
41
|
+
error: result.message,
|
|
42
|
+
code: result.code,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
if (!mutation.ok) {
|
|
48
|
+
// Map collection error codes to CLI error codes
|
|
49
|
+
const cliCode =
|
|
50
|
+
mutation.code === "VALIDATION" ||
|
|
51
|
+
mutation.code === "NOT_FOUND" ||
|
|
52
|
+
mutation.code === "HAS_REFERENCES"
|
|
53
|
+
? "VALIDATION"
|
|
54
|
+
: "RUNTIME";
|
|
55
|
+
throw new CliError(cliCode, mutation.error);
|
|
28
56
|
}
|
|
29
|
-
);
|
|
30
57
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
mutation.code === "HAS_REFERENCES"
|
|
37
|
-
? "VALIDATION"
|
|
38
|
-
: "RUNTIME";
|
|
39
|
-
throw new CliError(cliCode, mutation.error);
|
|
58
|
+
process.stdout.write(
|
|
59
|
+
`Collection "${mutation.value?.name}" removed successfully\n`
|
|
60
|
+
);
|
|
61
|
+
} finally {
|
|
62
|
+
await initialized.store.close();
|
|
40
63
|
}
|
|
41
|
-
|
|
42
|
-
process.stdout.write(
|
|
43
|
-
`Collection "${mutation.value?.name}" removed successfully\n`
|
|
44
|
-
);
|
|
45
64
|
}
|
|
@@ -3,19 +3,51 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { CollectionSchema, getCollectionFromScope } from "../../../config";
|
|
6
|
-
import {
|
|
6
|
+
import { applyConfigChange } from "../../../core/config-mutation";
|
|
7
7
|
import { CliError } from "../../errors";
|
|
8
|
+
import { initStore, type InitStoreResult } from "../shared";
|
|
8
9
|
|
|
9
10
|
export async function collectionRename(
|
|
10
11
|
oldName: string,
|
|
11
12
|
newName: string,
|
|
12
|
-
options: { configPath?: string } = {}
|
|
13
|
+
options: { configPath?: string; indexName?: string } = {}
|
|
13
14
|
): Promise<void> {
|
|
14
15
|
const oldCollectionName = oldName.toLowerCase();
|
|
15
16
|
const newCollectionName = newName.toLowerCase();
|
|
16
17
|
|
|
17
|
-
|
|
18
|
-
|
|
18
|
+
// Documents are keyed by collection name (URIs, FTS paths, links), so the
|
|
19
|
+
// index cannot rename them in place. Sync in the same locked boundary: the
|
|
20
|
+
// old name's documents go now instead of at some later sync, and the next
|
|
21
|
+
// update indexes the files under the new name.
|
|
22
|
+
const initialized = await initStore({
|
|
23
|
+
configPath: options.configPath,
|
|
24
|
+
indexName: options.indexName,
|
|
25
|
+
syncConfig: false,
|
|
26
|
+
allowEmptyCollections: true,
|
|
27
|
+
});
|
|
28
|
+
if (!initialized.ok) {
|
|
29
|
+
throw new CliError("RUNTIME", initialized.error);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
await renameAndSync(initialized, oldCollectionName, newCollectionName);
|
|
34
|
+
} finally {
|
|
35
|
+
await initialized.store.close();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
process.stdout.write(
|
|
39
|
+
`Collection "${oldCollectionName}" renamed to "${newCollectionName}"\n` +
|
|
40
|
+
"Run: gno update to index it under the new name\n"
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function renameAndSync(
|
|
45
|
+
initialized: Extract<InitStoreResult, { ok: true }>,
|
|
46
|
+
oldCollectionName: string,
|
|
47
|
+
newCollectionName: string
|
|
48
|
+
): Promise<void> {
|
|
49
|
+
const mutation = await applyConfigChange(
|
|
50
|
+
{ store: initialized.store, configPath: initialized.actualConfigPath },
|
|
19
51
|
(config) => {
|
|
20
52
|
const collection = config.collections.find(
|
|
21
53
|
(item) => item.name === oldCollectionName
|
|
@@ -70,8 +102,4 @@ export async function collectionRename(
|
|
|
70
102
|
mutation.error
|
|
71
103
|
);
|
|
72
104
|
}
|
|
73
|
-
|
|
74
|
-
process.stdout.write(
|
|
75
|
-
`Collection "${oldCollectionName}" renamed to "${newCollectionName}"\n`
|
|
76
|
-
);
|
|
77
105
|
}
|
|
@@ -99,7 +99,11 @@ export function logSessionAutomationResult(
|
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
function formatCollectionSyncSummary(result: CollectionSyncResult): string {
|
|
102
|
-
|
|
102
|
+
const collectionErrors = result.errors
|
|
103
|
+
.filter((error) => error.relPath === "")
|
|
104
|
+
.map((error) => `; [${error.code}] ${error.message}`)
|
|
105
|
+
.join("");
|
|
106
|
+
return `${result.collection}: ${result.filesAdded} added, ${result.filesUpdated} updated, ${result.filesUnchanged} unchanged, ${result.filesErrored} errors${collectionErrors}`;
|
|
103
107
|
}
|
|
104
108
|
|
|
105
109
|
export const authorizeDaemonStatus = async (
|
package/src/cli/commands/get.ts
CHANGED
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
evidenceFromExactDocument,
|
|
30
30
|
RetrievalTraceSession as TraceSession,
|
|
31
31
|
} from "../../core/retrieval-trace-session";
|
|
32
|
+
import { supersedingUri } from "../../pipeline/result-context";
|
|
32
33
|
import { parseRef } from "./ref-parser";
|
|
33
34
|
import { initStore } from "./shared";
|
|
34
35
|
|
|
@@ -90,6 +91,8 @@ export interface GetResponse {
|
|
|
90
91
|
mirrorHash?: string;
|
|
91
92
|
};
|
|
92
93
|
record?: RecordEvidenceMetadata;
|
|
94
|
+
/** URI of the current fact when a newer memory fact superseded this one. */
|
|
95
|
+
supersededBy?: string;
|
|
93
96
|
capabilities: DocumentCapabilities;
|
|
94
97
|
}
|
|
95
98
|
|
|
@@ -269,7 +272,7 @@ async function fetchDocument(
|
|
|
269
272
|
return { success: false, error: "Mirror content unavailable" };
|
|
270
273
|
}
|
|
271
274
|
|
|
272
|
-
|
|
275
|
+
const response = buildResponse({
|
|
273
276
|
doc,
|
|
274
277
|
fullContent: contentResult.value,
|
|
275
278
|
parsed,
|
|
@@ -277,6 +280,10 @@ async function fetchDocument(
|
|
|
277
280
|
config,
|
|
278
281
|
indexName,
|
|
279
282
|
});
|
|
283
|
+
const successor = await supersedingUri(store, doc.uri);
|
|
284
|
+
if (response.success && successor)
|
|
285
|
+
response.data.supersededBy = decorateUriForIndex(successor, indexName);
|
|
286
|
+
return response;
|
|
280
287
|
}
|
|
281
288
|
|
|
282
289
|
interface BuildResponseContext {
|
|
@@ -467,6 +474,9 @@ function formatMarkdown(data: GetResponse, options: GetCommandOptions): string {
|
|
|
467
474
|
lines.push("");
|
|
468
475
|
lines.push(`- **URI**: \`${data.uri}\``);
|
|
469
476
|
lines.push(`- **DocID**: \`${data.docid}\``);
|
|
477
|
+
if (data.supersededBy) {
|
|
478
|
+
lines.push(`- **Superseded by**: \`${data.supersededBy}\``);
|
|
479
|
+
}
|
|
470
480
|
if (data.returnedLines) {
|
|
471
481
|
lines.push(
|
|
472
482
|
`- **Lines**: ${data.returnedLines.start}-${data.returnedLines.end} of ${data.totalLines}`
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* gno metadata - list the typed metadata keys indexed documents carry.
|
|
3
|
+
*
|
|
4
|
+
* @module src/cli/commands/metadata
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
formatMetadataSummary,
|
|
9
|
+
type MetadataSummary,
|
|
10
|
+
readMetadataSummary,
|
|
11
|
+
} from "../../core/metadata-discovery";
|
|
12
|
+
import { initStore } from "./shared";
|
|
13
|
+
|
|
14
|
+
export interface MetadataListOptions {
|
|
15
|
+
configPath?: string;
|
|
16
|
+
indexName?: string;
|
|
17
|
+
collection?: string;
|
|
18
|
+
values?: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export type MetadataListResult =
|
|
22
|
+
| { success: true; data: MetadataSummary }
|
|
23
|
+
| { success: false; error: string; isValidation?: boolean };
|
|
24
|
+
|
|
25
|
+
export async function metadataList(
|
|
26
|
+
options: MetadataListOptions = {}
|
|
27
|
+
): Promise<MetadataListResult> {
|
|
28
|
+
const initResult = await initStore({
|
|
29
|
+
configPath: options.configPath,
|
|
30
|
+
indexName: options.indexName,
|
|
31
|
+
});
|
|
32
|
+
if (!initResult.ok) return { success: false, error: initResult.error };
|
|
33
|
+
const { store } = initResult;
|
|
34
|
+
try {
|
|
35
|
+
return {
|
|
36
|
+
success: true,
|
|
37
|
+
data: await readMetadataSummary(store, {
|
|
38
|
+
collection: options.collection,
|
|
39
|
+
values: options.values,
|
|
40
|
+
}),
|
|
41
|
+
};
|
|
42
|
+
} catch (cause) {
|
|
43
|
+
return {
|
|
44
|
+
success: false,
|
|
45
|
+
error: cause instanceof Error ? cause.message : String(cause),
|
|
46
|
+
isValidation: cause instanceof RangeError,
|
|
47
|
+
};
|
|
48
|
+
} finally {
|
|
49
|
+
await store.close();
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function formatMetadataList(
|
|
54
|
+
data: MetadataSummary,
|
|
55
|
+
options: { json?: boolean; md?: boolean }
|
|
56
|
+
): string {
|
|
57
|
+
if (options.json) return JSON.stringify(data, null, 2);
|
|
58
|
+
if (!options.md) return formatMetadataSummary(data);
|
|
59
|
+
const lines = [
|
|
60
|
+
"# Typed metadata",
|
|
61
|
+
"",
|
|
62
|
+
`*${data.documentsWithMetadata} of ${data.documents} documents${data.collection ? ` in \`${data.collection}\`` : ""}*`,
|
|
63
|
+
];
|
|
64
|
+
if (data.keys.length === 0) return lines.join("\n");
|
|
65
|
+
lines.push(
|
|
66
|
+
"",
|
|
67
|
+
"| Key | Types | Documents | Common values |",
|
|
68
|
+
"|---|---|---|---|"
|
|
69
|
+
);
|
|
70
|
+
for (const key of data.keys) {
|
|
71
|
+
const values = key.values
|
|
72
|
+
.map((item) => `\`${JSON.stringify(item.value)}\` (${item.documents})`)
|
|
73
|
+
.join(", ");
|
|
74
|
+
lines.push(
|
|
75
|
+
`| \`${key.key}\` | ${key.types.join(", ")} | ${key.documents} | ${values}${key.valuesTruncated ? ", ..." : ""} |`
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
return lines.join("\n");
|
|
79
|
+
}
|
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
projectRecordEvidenceMetadata,
|
|
22
22
|
type RecordEvidenceMetadata,
|
|
23
23
|
} from "../../core/record-metadata";
|
|
24
|
+
import { supersedingUri } from "../../pipeline/result-context";
|
|
24
25
|
import { isGlobPattern, parseRef, splitRefs } from "./ref-parser";
|
|
25
26
|
import { initStore } from "./shared";
|
|
26
27
|
|
|
@@ -58,6 +59,8 @@ export interface MultiGetDocument {
|
|
|
58
59
|
totalLines?: number;
|
|
59
60
|
source: { absPath?: string; relPath: string; mime: string; ext: string };
|
|
60
61
|
record?: RecordEvidenceMetadata;
|
|
62
|
+
/** URI of the current fact when a newer memory fact superseded this one. */
|
|
63
|
+
supersededBy?: string;
|
|
61
64
|
}
|
|
62
65
|
|
|
63
66
|
export interface SkippedDoc {
|
|
@@ -236,7 +239,11 @@ async function fetchSingleDocument(
|
|
|
236
239
|
const coll = ctx.config.collections.find((c) => c.name === doc.collection);
|
|
237
240
|
|
|
238
241
|
const sourceRelPath = doc.recordSourcePath ?? doc.relPath;
|
|
242
|
+
const successor = await supersedingUri(ctx.store, doc.uri);
|
|
239
243
|
ctx.documents.push({
|
|
244
|
+
...(successor
|
|
245
|
+
? { supersededBy: decorateUriForIndex(successor, ctx.indexName) }
|
|
246
|
+
: {}),
|
|
240
247
|
docid: doc.docid,
|
|
241
248
|
uri: decorateUriForIndex(doc.uri, ctx.indexName),
|
|
242
249
|
title: doc.title ?? undefined,
|
|
@@ -422,6 +429,9 @@ function formatMarkdown(
|
|
|
422
429
|
for (const doc of data.documents) {
|
|
423
430
|
lines.push(`## ${doc.title || doc.source.relPath}`);
|
|
424
431
|
lines.push(`- **URI**: \`${doc.uri}\``);
|
|
432
|
+
if (doc.supersededBy) {
|
|
433
|
+
lines.push(`- **Superseded by**: \`${doc.supersededBy}\``);
|
|
434
|
+
}
|
|
425
435
|
if (doc.truncated) {
|
|
426
436
|
lines.push(`- **Truncated**: yes (max ${data.meta.maxBytes} bytes)`);
|
|
427
437
|
}
|
|
@@ -450,6 +460,9 @@ function formatTerminal(
|
|
|
450
460
|
|
|
451
461
|
for (const doc of data.documents) {
|
|
452
462
|
lines.push(`=== ${doc.uri} ===`);
|
|
463
|
+
if (doc.supersededBy) {
|
|
464
|
+
lines.push(`Superseded by: ${doc.supersededBy}`);
|
|
465
|
+
}
|
|
453
466
|
lines.push(formatContent(doc.content, Boolean(options.lineNumbers)));
|
|
454
467
|
lines.push("");
|
|
455
468
|
}
|
|
@@ -11,7 +11,7 @@ import type { SearchResults } from "../../pipeline/types";
|
|
|
11
11
|
|
|
12
12
|
import {
|
|
13
13
|
DEFAULT_INDEX_NAME,
|
|
14
|
-
|
|
14
|
+
decorateResultUris,
|
|
15
15
|
getIndexDbPath,
|
|
16
16
|
} from "../../app/constants";
|
|
17
17
|
import {
|
|
@@ -164,10 +164,9 @@ export function decorateSearchResultsForIndex(
|
|
|
164
164
|
}
|
|
165
165
|
return {
|
|
166
166
|
...data,
|
|
167
|
-
results: data.results.map((result) =>
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
})),
|
|
167
|
+
results: data.results.map((result) =>
|
|
168
|
+
decorateResultUris(result, indexName)
|
|
169
|
+
),
|
|
171
170
|
};
|
|
172
171
|
}
|
|
173
172
|
|
|
@@ -217,10 +216,13 @@ export function formatSyncResultLines(
|
|
|
217
216
|
lines.push(` ${c.filesErrored} errors`);
|
|
218
217
|
}
|
|
219
218
|
// A file stopped at its conversion budget is always named, not only in
|
|
220
|
-
// verbose output: it stays unindexed until a later run finishes it.
|
|
219
|
+
// verbose output: it stays unindexed until a later run finishes it. So is
|
|
220
|
+
// a collection-level error such as an unavailable root.
|
|
221
221
|
if (!options.verbose) {
|
|
222
222
|
for (const err of c.errors) {
|
|
223
|
-
if (
|
|
223
|
+
if (err.relPath === "") {
|
|
224
|
+
lines.push(` [${err.code}] ${err.message}`);
|
|
225
|
+
} else if (BUDGET_ERROR_CODES.has(err.code)) {
|
|
224
226
|
lines.push(` [${err.code}] ${err.relPath}: ${err.message}`);
|
|
225
227
|
}
|
|
226
228
|
}
|
|
@@ -88,6 +88,9 @@ function formatTerminal(data: SearchResults, options: FormatOptions): string {
|
|
|
88
88
|
if (r.title) {
|
|
89
89
|
lines.push(` ${r.title}`);
|
|
90
90
|
}
|
|
91
|
+
if (r.supersededBy) {
|
|
92
|
+
lines.push(` Superseded by: ${r.supersededBy}`);
|
|
93
|
+
}
|
|
91
94
|
if (r.snippet) {
|
|
92
95
|
const content = options.full
|
|
93
96
|
? r.snippet
|
|
@@ -175,6 +178,9 @@ function formatMarkdown(data: SearchResults, options: FormatOptions): string {
|
|
|
175
178
|
lines.push(`- **URI**: \`${r.uri}\``);
|
|
176
179
|
lines.push(`- **Score**: ${r.score.toFixed(2)}`);
|
|
177
180
|
lines.push(`- **DocID**: \`${r.docid}\``);
|
|
181
|
+
if (r.supersededBy) {
|
|
182
|
+
lines.push(`- **Superseded by**: \`${r.supersededBy}\``);
|
|
183
|
+
}
|
|
178
184
|
if (r.snippet) {
|
|
179
185
|
const content = options.full
|
|
180
186
|
? r.snippet
|
|
@@ -223,6 +229,11 @@ function formatXml(data: SearchResults, options: FormatOptions): string {
|
|
|
223
229
|
lines.push(` <title>${escapeXml(r.title)}</title>`);
|
|
224
230
|
}
|
|
225
231
|
lines.push(` <relPath>${escapeXml(r.source.relPath)}</relPath>`);
|
|
232
|
+
if (r.supersededBy) {
|
|
233
|
+
lines.push(
|
|
234
|
+
` <supersededBy>${escapeXml(r.supersededBy)}</supersededBy>`
|
|
235
|
+
);
|
|
236
|
+
}
|
|
226
237
|
if (r.snippet) {
|
|
227
238
|
const content = options.full
|
|
228
239
|
? r.snippet
|
package/src/cli/options.ts
CHANGED
|
@@ -82,6 +82,7 @@ export const CMD = {
|
|
|
82
82
|
contextSaved: "context.saved",
|
|
83
83
|
modelsList: "models.list",
|
|
84
84
|
tagsList: "tags.list",
|
|
85
|
+
metadataList: "metadata",
|
|
85
86
|
linksList: "links.list",
|
|
86
87
|
backlinks: "backlinks",
|
|
87
88
|
similar: "similar",
|
|
@@ -118,6 +119,7 @@ const FORMAT_SUPPORT: Record<CommandId, OutputFormat[]> = {
|
|
|
118
119
|
[CMD.contextSaved]: ["terminal", "json"],
|
|
119
120
|
[CMD.modelsList]: ["terminal", "json"],
|
|
120
121
|
[CMD.tagsList]: ["terminal", "json", "md"],
|
|
122
|
+
[CMD.metadataList]: ["terminal", "json", "md"],
|
|
121
123
|
[CMD.linksList]: ["terminal", "json", "md"],
|
|
122
124
|
[CMD.backlinks]: ["terminal", "json", "md"],
|
|
123
125
|
[CMD.similar]: ["terminal", "json", "md"],
|