@gmickel/gno 2.10.1 → 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.1.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/package.json +3 -1
- package/spec/cli.md +27 -1
- package/spec/mcp.md +27 -3
- 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/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/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 +45 -0
- 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/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 +29 -1
- package/src/serve/server.ts +12 -0
- package/src/store/sqlite/adapter.ts +82 -2
- 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.1.zip.sha256 +0 -1
|
@@ -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,
|
|
@@ -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"],
|
package/src/cli/program.ts
CHANGED
|
@@ -2481,6 +2481,11 @@ function wireRetrievalCommands(program: Command): void {
|
|
|
2481
2481
|
);
|
|
2482
2482
|
}
|
|
2483
2483
|
|
|
2484
|
+
// Terminal output is the raw content; the mark goes to stderr so
|
|
2485
|
+
// piping the document stays byte-exact.
|
|
2486
|
+
if (format === "terminal" && result.data.supersededBy) {
|
|
2487
|
+
process.stderr.write(`Superseded by: ${result.data.supersededBy}\n`);
|
|
2488
|
+
}
|
|
2484
2489
|
process.stdout.write(
|
|
2485
2490
|
`${formatGet(result, {
|
|
2486
2491
|
lineNumbers: Boolean(cmdOpts.lineNumbers),
|
|
@@ -4184,6 +4189,46 @@ function wireAgentsCommands(program: Command): void {
|
|
|
4184
4189
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
4185
4190
|
|
|
4186
4191
|
function wireTagsCommands(program: Command): void {
|
|
4192
|
+
program
|
|
4193
|
+
.command("metadata")
|
|
4194
|
+
.description(
|
|
4195
|
+
"List typed metadata keys with their types, document counts and common values"
|
|
4196
|
+
)
|
|
4197
|
+
.option("-c, --collection <name>", "only documents in this collection")
|
|
4198
|
+
.option(
|
|
4199
|
+
"--values <n>",
|
|
4200
|
+
"common values listed per key (1-100, default 10)",
|
|
4201
|
+
(value) => Number(value)
|
|
4202
|
+
)
|
|
4203
|
+
.option("--json", "JSON output")
|
|
4204
|
+
.option("--md", "Markdown output")
|
|
4205
|
+
.action(async (cmdOpts: Record<string, unknown>) => {
|
|
4206
|
+
const format = getFormat(cmdOpts);
|
|
4207
|
+
assertFormatSupported(CMD.metadataList, format);
|
|
4208
|
+
const globals = getGlobals();
|
|
4209
|
+
const { metadataList, formatMetadataList } =
|
|
4210
|
+
await import("./commands/metadata");
|
|
4211
|
+
const result = await metadataList({
|
|
4212
|
+
configPath: globals.config,
|
|
4213
|
+
indexName: globals.index,
|
|
4214
|
+
collection: cmdOpts.collection as string | undefined,
|
|
4215
|
+
values: cmdOpts.values as number | undefined,
|
|
4216
|
+
});
|
|
4217
|
+
if (!result.success) {
|
|
4218
|
+
throw new CliError(
|
|
4219
|
+
result.isValidation ? "VALIDATION" : "RUNTIME",
|
|
4220
|
+
result.error
|
|
4221
|
+
);
|
|
4222
|
+
}
|
|
4223
|
+
await writeOutput(
|
|
4224
|
+
formatMetadataList(result.data, {
|
|
4225
|
+
json: format === "json",
|
|
4226
|
+
md: format === "md",
|
|
4227
|
+
}),
|
|
4228
|
+
format
|
|
4229
|
+
);
|
|
4230
|
+
});
|
|
4231
|
+
|
|
4187
4232
|
const tagsCmd = program.command("tags").description("Manage document tags");
|
|
4188
4233
|
|
|
4189
4234
|
// tags list
|
|
@@ -365,6 +365,8 @@ export const contextCapsuleEvidenceSchema = z
|
|
|
365
365
|
]),
|
|
366
366
|
egressLineage: egressLineageSchema.optional(),
|
|
367
367
|
record: recordEvidenceMetadataSchema.optional(),
|
|
368
|
+
/** Current successor when a newer memory fact superseded this one. */
|
|
369
|
+
supersededBy: contextCapsuleGnoUriSchema.optional(),
|
|
368
370
|
})
|
|
369
371
|
.strict()
|
|
370
372
|
.superRefine((value, context) => {
|
|
@@ -51,6 +51,7 @@ export const contextCapsuleVerificationEvidenceSchema = z
|
|
|
51
51
|
"chunk_corrupt",
|
|
52
52
|
"source_missing",
|
|
53
53
|
"mirror_missing",
|
|
54
|
+
"superseded",
|
|
54
55
|
]),
|
|
55
56
|
rankingStatus: z.enum(["unchanged", "reranked", "unavailable"]),
|
|
56
57
|
rankingCode: z.enum([
|
|
@@ -82,6 +83,7 @@ export const contextCapsuleVerificationEvidenceSchema = z
|
|
|
82
83
|
"passage_stale",
|
|
83
84
|
"chunk_missing",
|
|
84
85
|
"chunk_corrupt",
|
|
86
|
+
"superseded",
|
|
85
87
|
].includes(value.contentCode) &&
|
|
86
88
|
hashes.every((hash) => hash !== null)) ||
|
|
87
89
|
(value.contentStatus === "missing" &&
|
|
@@ -28,7 +28,7 @@ import type {
|
|
|
28
28
|
import type { ContextConfiguredGuidance } from "./context-guidance";
|
|
29
29
|
import type { MetadataPredicate } from "./typed-metadata";
|
|
30
30
|
|
|
31
|
-
import { decorateUriForIndex } from "../app/constants";
|
|
31
|
+
import { decorateResultUris, decorateUriForIndex } from "../app/constants";
|
|
32
32
|
import { canonicalizeIndexName } from "../app/index-name";
|
|
33
33
|
import { resolveTemporalRange } from "../pipeline/temporal";
|
|
34
34
|
import { attachSearchResultPlannerMetadata } from "../pipeline/trace-metadata";
|
|
@@ -383,10 +383,7 @@ export const planContextEvidence = async <T, P>(
|
|
|
383
383
|
const decoratedResults = responses
|
|
384
384
|
.flatMap((response) => response.results)
|
|
385
385
|
.map((result) => {
|
|
386
|
-
const decorated =
|
|
387
|
-
...result,
|
|
388
|
-
uri: decorateUriForIndex(result.uri, indexName),
|
|
389
|
-
};
|
|
386
|
+
const decorated = decorateResultUris(result, indexName);
|
|
390
387
|
const metadata = result[SEARCH_RESULT_PLANNER_METADATA];
|
|
391
388
|
return metadata
|
|
392
389
|
? attachSearchResultPlannerMetadata(decorated, metadata)
|
|
@@ -502,6 +502,7 @@ export const materializeContextEvidenceCandidates = async (
|
|
|
502
502
|
...(projectRecordEvidenceMetadata(document)
|
|
503
503
|
? { record: projectRecordEvidenceMetadata(document) }
|
|
504
504
|
: {}),
|
|
505
|
+
...(result.supersededBy ? { supersededBy: result.supersededBy } : {}),
|
|
505
506
|
},
|
|
506
507
|
},
|
|
507
508
|
};
|
|
@@ -11,6 +11,7 @@ import type { ContextCapsuleVerification } from "./context-capsule-verification"
|
|
|
11
11
|
|
|
12
12
|
import { decorateUriForIndex, deriveDocid } from "../app/constants";
|
|
13
13
|
import { chunkMatchesCanonicalContent } from "../pipeline/chunk-lookup";
|
|
14
|
+
import { ok } from "../store/types";
|
|
14
15
|
import { type ContextCapsuleCreateOptions } from "./context-capsule";
|
|
15
16
|
import { sha256Text } from "./context-capsule-validation";
|
|
16
17
|
import {
|
|
@@ -35,6 +36,7 @@ type ContextVerifierStore = Pick<
|
|
|
35
36
|
| "getCollections"
|
|
36
37
|
| "getContexts"
|
|
37
38
|
| "getDocumentsByDocids"
|
|
39
|
+
| "getSupersedingUris"
|
|
38
40
|
> &
|
|
39
41
|
Required<Pick<StorePort, "getContentBatch">>;
|
|
40
42
|
|
|
@@ -127,6 +129,8 @@ interface LoadedEvidence {
|
|
|
127
129
|
chunksByHash: Map<string, ChunkRow[]>;
|
|
128
130
|
documents: DocumentRow[];
|
|
129
131
|
snapshot: ContextEvidenceSnapshot;
|
|
132
|
+
/** Current successor URI of evidence whose memory fact was superseded. */
|
|
133
|
+
successors: ReadonlyMap<string, string>;
|
|
130
134
|
}
|
|
131
135
|
|
|
132
136
|
const missingReceipt = (evidence: Evidence): EvidenceReceipt => ({
|
|
@@ -229,6 +233,8 @@ const verifyContent = (
|
|
|
229
233
|
contentCode = "chunk_missing";
|
|
230
234
|
} else if (!chunkValid) {
|
|
231
235
|
contentCode = "chunk_corrupt";
|
|
236
|
+
} else if (loaded.successors.has(evidence.uri)) {
|
|
237
|
+
contentCode = "superseded";
|
|
232
238
|
}
|
|
233
239
|
|
|
234
240
|
return {
|
|
@@ -374,16 +380,23 @@ export const verifyContextCapsule = async (
|
|
|
374
380
|
)
|
|
375
381
|
),
|
|
376
382
|
];
|
|
377
|
-
const [documentResult, contentResult, chunkResult, rankResult] =
|
|
383
|
+
const [documentResult, contentResult, chunkResult, rankResult, successors] =
|
|
378
384
|
await Promise.all([
|
|
379
385
|
deps.store.getDocumentsByDocids(docids, { activeOnly: true }),
|
|
380
386
|
deps.store.getContentBatch(mirrorHashes),
|
|
381
387
|
deps.store.getChunksBatch(mirrorHashes),
|
|
382
388
|
deps.resolveCurrentRanks?.(structuredClone(capsule)).catch(() => null) ??
|
|
383
389
|
Promise.resolve(null),
|
|
390
|
+
deps.store.getSupersedingUris?.([...evidenceUris]) ??
|
|
391
|
+
Promise.resolve(ok(new Map<string, string>())),
|
|
384
392
|
]);
|
|
385
393
|
const loaded: LoadedEvidence = {
|
|
386
394
|
snapshot: before,
|
|
395
|
+
successors: unwrapStore(
|
|
396
|
+
successors,
|
|
397
|
+
"document_load_failed",
|
|
398
|
+
"Failed to load supersession of verification evidence"
|
|
399
|
+
),
|
|
387
400
|
documents: unwrapStore(
|
|
388
401
|
documentResult,
|
|
389
402
|
"document_load_failed",
|