@gmickel/gno 2.10.1 → 2.12.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 +27 -15
- package/assets/skill/cli-reference.md +6 -5
- package/assets/skill/examples.md +16 -12
- package/assets/skill/mcp-reference.md +2 -1
- package/assets/spa-production.json.gz +0 -0
- package/browser-extension/artifacts/{gno-browser-clipper-v2.10.1.zip → gno-browser-clipper-v2.12.0.zip} +0 -0
- package/browser-extension/artifacts/gno-browser-clipper-v2.12.0.zip.sha256 +1 -0
- package/browser-extension/dist/manifest.json +1 -1
- package/package.json +10 -5
- package/spec/cli.md +127 -35
- package/spec/evals-agentic.md +3 -1
- package/spec/mcp.md +36 -6
- 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/spec/output-schemas/status.schema.json +28 -0
- package/src/app/constants.ts +16 -0
- package/src/app/context-agent-projection.ts +10 -1
- package/src/app/context-format.ts +1 -0
- package/src/app/verified-ask.ts +2 -0
- package/src/cli/commands/agents/block.ts +10 -9
- package/src/cli/commands/agents/commands.ts +6 -5
- package/src/cli/commands/agents/engine.ts +15 -2
- package/src/cli/commands/agents/harnesses.ts +21 -14
- package/src/cli/commands/ask-format.ts +18 -1
- package/src/cli/commands/ask.ts +4 -5
- package/src/cli/commands/cleanup.ts +46 -6
- package/src/cli/commands/collection/remove.ts +1 -0
- package/src/cli/commands/collection/rename.ts +1 -0
- package/src/cli/commands/daemon.ts +5 -1
- package/src/cli/commands/doctor.ts +37 -0
- package/src/cli/commands/get.ts +11 -1
- package/src/cli/commands/mcp/paths.ts +2 -1
- package/src/cli/commands/metadata.ts +79 -0
- package/src/cli/commands/multi-get.ts +13 -0
- package/src/cli/commands/setup-semantic.ts +6 -1
- package/src/cli/commands/shared.ts +35 -8
- package/src/cli/commands/skill/paths.ts +20 -2
- package/src/cli/commands/status.ts +89 -15
- package/src/cli/format/search-results.ts +11 -0
- package/src/cli/options.ts +2 -0
- package/src/cli/program.ts +64 -3
- package/src/core/activation-probe-plan.ts +0 -1
- package/src/core/config-mutation.ts +72 -55
- 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/memory-remember.ts +0 -1
- package/src/core/metadata-discovery.ts +169 -0
- package/src/core/module-path.ts +33 -0
- package/src/core/runtime-entrypoint.ts +3 -1
- package/src/ingestion/file-processor.ts +6 -3
- package/src/ingestion/prepare-file.ts +14 -4
- package/src/ingestion/source-availability/root.ts +138 -0
- package/src/ingestion/sync.ts +35 -1
- package/src/ingestion/walker.ts +11 -3
- package/src/llm/native-worker/client.ts +8 -4
- package/src/llm/native-worker/errors.ts +26 -3
- package/src/mcp/http-egress.ts +1 -0
- package/src/mcp/http-transport.ts +7 -2
- package/src/mcp/server.ts +3 -0
- package/src/mcp/tool-descriptions-core.ts +3 -3
- package/src/mcp/tool-profile.ts +16 -1
- package/src/mcp/tools/get.ts +10 -0
- package/src/mcp/tools/index.ts +68 -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/status.ts +17 -2
- 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/query-snippet.ts +266 -0
- package/src/pipeline/result-context.ts +29 -0
- package/src/pipeline/search.ts +154 -20
- package/src/pipeline/types.ts +4 -0
- package/src/pipeline/vsearch.ts +5 -1
- package/src/sdk/client.ts +46 -6
- package/src/sdk/documents.ts +5 -0
- package/src/sdk/types.ts +11 -1
- package/src/serve/public/components/ThoroughnessSelector.tsx +1 -1
- package/src/serve/public/globals.built.css +1 -1
- package/src/serve/public/pages/Collections.tsx +8 -0
- package/src/serve/public/pages/Search.tsx +9 -1
- package/src/serve/resident-request.ts +8 -0
- package/src/serve/resident-runtime.ts +25 -3
- package/src/serve/routes/api.ts +29 -1
- package/src/serve/server.ts +12 -0
- package/src/serve/spa-production-build.ts +7 -3
- package/src/serve/status-model.ts +2 -0
- package/src/serve/status.ts +29 -0
- package/src/sessions/config-refresh.ts +6 -0
- package/src/sessions/import-child.ts +4 -1
- package/src/store/sqlite/adapter.ts +241 -20
- package/src/store/types.ts +61 -7
- package/src/store/vector/variant-search.ts +112 -74
- package/browser-extension/artifacts/gno-browser-clipper-v2.10.1.zip.sha256 +0 -1
|
@@ -9,7 +9,7 @@ import type { ContentTypeBoostStatus } from "../../config/content-types";
|
|
|
9
9
|
import type { ActivationStatus } from "../../core/activation-status";
|
|
10
10
|
import type { MemoryStatus } from "../../core/memory-diagnostics";
|
|
11
11
|
import type { BackgroundIssue } from "../../serve/status-model";
|
|
12
|
-
import type { IndexStatus } from "../../store/types";
|
|
12
|
+
import type { IndexStatus, StaleCollection } from "../../store/types";
|
|
13
13
|
|
|
14
14
|
import { getIndexDbPath, getModelsCachePath } from "../../app/constants";
|
|
15
15
|
import {
|
|
@@ -33,6 +33,10 @@ import {
|
|
|
33
33
|
} from "../../core/memory-diagnostics";
|
|
34
34
|
import { formatVectorPartitionLines } from "../../core/vector-partition-status";
|
|
35
35
|
import { BUDGET_ERROR_CODES } from "../../ingestion/file-processor";
|
|
36
|
+
import {
|
|
37
|
+
checkCollectionRoots,
|
|
38
|
+
type CollectionRootError,
|
|
39
|
+
} from "../../ingestion/source-availability/root";
|
|
36
40
|
import { ModelCache } from "../../llm/cache";
|
|
37
41
|
import { getActivePreset, resolveModelUri } from "../../llm/registry";
|
|
38
42
|
import { getConnectorVerificationTargets } from "../../serve/connectors";
|
|
@@ -80,6 +84,10 @@ export type StatusResult =
|
|
|
80
84
|
runtimeIssues?: RuntimeIssue[];
|
|
81
85
|
/** Files whose latest conversion stopped at the per-file budget. */
|
|
82
86
|
budgetStops: BudgetStop[];
|
|
87
|
+
/** Indexed collections this config does not list (kept until cleanup). */
|
|
88
|
+
staleCollections?: StaleCollection[];
|
|
89
|
+
/** Configured collections whose root is missing or unreadable now. */
|
|
90
|
+
rootErrors?: Record<string, CollectionRootError>;
|
|
83
91
|
}
|
|
84
92
|
| { success: false; error: string };
|
|
85
93
|
|
|
@@ -91,6 +99,18 @@ export interface BudgetStop {
|
|
|
91
99
|
message: string;
|
|
92
100
|
}
|
|
93
101
|
|
|
102
|
+
/** Indexed collections the config omits; an unreadable list is not fatal. */
|
|
103
|
+
async function collectStaleCollections(
|
|
104
|
+
store: SqliteAdapter,
|
|
105
|
+
configured: string[]
|
|
106
|
+
): Promise<StaleCollection[]> {
|
|
107
|
+
const stale = await store.listStaleCollections(configured);
|
|
108
|
+
return stale.ok ? stale.value.filter((c) => c.documents > 0) : [];
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const STALE_COLLECTIONS_HINT =
|
|
112
|
+
'not in this config; add them back, or run "gno cleanup" to delete their index data';
|
|
113
|
+
|
|
94
114
|
/** Most recent ingest errors inspected for budget stops. */
|
|
95
115
|
const BUDGET_STOP_SCAN_LIMIT = 100;
|
|
96
116
|
/** Most budget stops listed in status output. */
|
|
@@ -164,12 +184,14 @@ function connectorProjectionLine(activation: ActivationStatus): string | null {
|
|
|
164
184
|
|
|
165
185
|
function isStatusHealthy(
|
|
166
186
|
indexStatus: IndexStatus,
|
|
167
|
-
activation: ActivationStatus
|
|
187
|
+
activation: ActivationStatus,
|
|
188
|
+
rootErrors: Record<string, CollectionRootError> = {}
|
|
168
189
|
): boolean {
|
|
169
190
|
return (
|
|
170
191
|
indexStatus.healthy &&
|
|
171
192
|
activation.healthy &&
|
|
172
|
-
isConnectorActivationComplete(activation)
|
|
193
|
+
isConnectorActivationComplete(activation) &&
|
|
194
|
+
Object.keys(rootErrors).length === 0
|
|
173
195
|
);
|
|
174
196
|
}
|
|
175
197
|
|
|
@@ -180,7 +202,8 @@ function formatTerminal(
|
|
|
180
202
|
indexStatus: IndexStatus,
|
|
181
203
|
activation: ActivationStatus,
|
|
182
204
|
contentTypeBoost: ContentTypeBoostStatus,
|
|
183
|
-
memory: MemoryStatus
|
|
205
|
+
memory: MemoryStatus,
|
|
206
|
+
rootErrors: Record<string, CollectionRootError> = {}
|
|
184
207
|
): string {
|
|
185
208
|
const lines: string[] = [];
|
|
186
209
|
|
|
@@ -203,6 +226,11 @@ function formatTerminal(
|
|
|
203
226
|
` ${c.name}: ${c.activeDocuments} docs, ${c.totalChunks} chunks` +
|
|
204
227
|
(c.embeddedChunks > 0 ? `, ${c.embeddedChunks} embedded` : "")
|
|
205
228
|
);
|
|
229
|
+
const rootError = rootErrors[c.name];
|
|
230
|
+
if (rootError) {
|
|
231
|
+
lines.push(` Root unavailable: ${c.path}`);
|
|
232
|
+
lines.push(` [${rootError.code}] ${rootError.message}`);
|
|
233
|
+
}
|
|
206
234
|
const workspace = formatLinkWorkspace(c);
|
|
207
235
|
if (workspace) lines.push(` Link workspace: ${workspace}`);
|
|
208
236
|
}
|
|
@@ -240,7 +268,7 @@ function formatTerminal(
|
|
|
240
268
|
lines.push(`Ranking fingerprint: ${contentTypeBoost.rulesFingerprint}`);
|
|
241
269
|
|
|
242
270
|
lines.push(
|
|
243
|
-
`Health: ${isStatusHealthy(indexStatus, activation) ? "OK" : "DEGRADED"}`
|
|
271
|
+
`Health: ${isStatusHealthy(indexStatus, activation, rootErrors) ? "OK" : "DEGRADED"}`
|
|
244
272
|
);
|
|
245
273
|
lines.push("");
|
|
246
274
|
const activationState = activation.healthy
|
|
@@ -290,7 +318,8 @@ function formatMarkdown(
|
|
|
290
318
|
indexStatus: IndexStatus,
|
|
291
319
|
activation: ActivationStatus,
|
|
292
320
|
contentTypeBoost: ContentTypeBoostStatus,
|
|
293
|
-
memory: MemoryStatus
|
|
321
|
+
memory: MemoryStatus,
|
|
322
|
+
rootErrors: Record<string, CollectionRootError> = {}
|
|
294
323
|
): string {
|
|
295
324
|
const lines: string[] = [];
|
|
296
325
|
|
|
@@ -299,7 +328,7 @@ function formatMarkdown(
|
|
|
299
328
|
lines.push(`- **Config**: ${indexStatus.configPath}`);
|
|
300
329
|
lines.push(`- **Database**: ${indexStatus.dbPath}`);
|
|
301
330
|
lines.push(
|
|
302
|
-
`- **Health**: ${isStatusHealthy(indexStatus, activation) ? "✓ OK" : "⚠ DEGRADED"}`
|
|
331
|
+
`- **Health**: ${isStatusHealthy(indexStatus, activation, rootErrors) ? "✓ OK" : "⚠ DEGRADED"}`
|
|
303
332
|
);
|
|
304
333
|
lines.push("");
|
|
305
334
|
|
|
@@ -314,6 +343,15 @@ function formatMarkdown(
|
|
|
314
343
|
);
|
|
315
344
|
}
|
|
316
345
|
lines.push("");
|
|
346
|
+
for (const c of indexStatus.collections) {
|
|
347
|
+
const rootError = rootErrors[c.name];
|
|
348
|
+
if (rootError) {
|
|
349
|
+
lines.push(
|
|
350
|
+
`- **Root unavailable**: ${c.name} (${c.path}) [${rootError.code}] ${rootError.message}`
|
|
351
|
+
);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
if (Object.keys(rootErrors).length > 0) lines.push("");
|
|
317
355
|
}
|
|
318
356
|
|
|
319
357
|
lines.push("## Summary");
|
|
@@ -390,6 +428,8 @@ export async function status(
|
|
|
390
428
|
return { success: false, error: configResult.error.message };
|
|
391
429
|
}
|
|
392
430
|
const config = configResult.value;
|
|
431
|
+
// Read-time root check, overlapping the index reads below; it never rejects.
|
|
432
|
+
const rootErrors = checkCollectionRoots(config.collections);
|
|
393
433
|
|
|
394
434
|
// Open database
|
|
395
435
|
const store = new SqliteAdapter();
|
|
@@ -446,6 +486,11 @@ export async function status(
|
|
|
446
486
|
...(await checkResidentRuntimes()),
|
|
447
487
|
],
|
|
448
488
|
budgetStops: await collectBudgetStops(store),
|
|
489
|
+
staleCollections: await collectStaleCollections(
|
|
490
|
+
store,
|
|
491
|
+
config.collections.map(({ name }) => name)
|
|
492
|
+
),
|
|
493
|
+
rootErrors: await rootErrors,
|
|
449
494
|
};
|
|
450
495
|
} finally {
|
|
451
496
|
await store.close();
|
|
@@ -482,6 +527,9 @@ export function formatStatus(
|
|
|
482
527
|
documentCount: c.activeDocuments,
|
|
483
528
|
chunkCount: c.totalChunks,
|
|
484
529
|
embeddedCount: c.embeddedChunks,
|
|
530
|
+
...(result.rootErrors?.[c.name]
|
|
531
|
+
? { rootError: result.rootErrors[c.name] }
|
|
532
|
+
: {}),
|
|
485
533
|
})),
|
|
486
534
|
totalDocuments: s.activeDocuments,
|
|
487
535
|
totalChunks: s.totalChunks,
|
|
@@ -491,7 +539,7 @@ export function formatStatus(
|
|
|
491
539
|
typedMetadata: s.typedMetadata,
|
|
492
540
|
chunking: s.chunking,
|
|
493
541
|
lastUpdated: s.lastUpdatedAt,
|
|
494
|
-
healthy: isStatusHealthy(s, result.activation),
|
|
542
|
+
healthy: isStatusHealthy(s, result.activation, result.rootErrors),
|
|
495
543
|
contentTypeBoost: result.contentTypeBoost,
|
|
496
544
|
activation: result.activation,
|
|
497
545
|
memory: result.memory,
|
|
@@ -501,19 +549,37 @@ export function formatStatus(
|
|
|
501
549
|
...(result.runtimeIssues?.length
|
|
502
550
|
? { runtimeIssues: result.runtimeIssues }
|
|
503
551
|
: {}),
|
|
552
|
+
...(result.staleCollections?.length
|
|
553
|
+
? { staleCollections: result.staleCollections }
|
|
554
|
+
: {}),
|
|
504
555
|
},
|
|
505
556
|
null,
|
|
506
557
|
2
|
|
507
558
|
);
|
|
508
559
|
}
|
|
509
560
|
|
|
561
|
+
const staleLines = (result.staleCollections ?? []).map(
|
|
562
|
+
(c) =>
|
|
563
|
+
`${c.name}: ${c.documents} ${c.documents === 1 ? "document" : "documents"}`
|
|
564
|
+
);
|
|
510
565
|
if (options.md) {
|
|
511
|
-
return
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
566
|
+
return [
|
|
567
|
+
formatMarkdown(
|
|
568
|
+
result.status,
|
|
569
|
+
result.activation,
|
|
570
|
+
result.contentTypeBoost,
|
|
571
|
+
result.memory,
|
|
572
|
+
result.rootErrors
|
|
573
|
+
),
|
|
574
|
+
...(staleLines.length
|
|
575
|
+
? [
|
|
576
|
+
"",
|
|
577
|
+
`## Collections ${STALE_COLLECTIONS_HINT}`,
|
|
578
|
+
"",
|
|
579
|
+
...staleLines.map((line) => `- ${line}`),
|
|
580
|
+
]
|
|
581
|
+
: []),
|
|
582
|
+
].join("\n");
|
|
517
583
|
}
|
|
518
584
|
|
|
519
585
|
const issueLines = result.backgroundIssues.map(
|
|
@@ -529,8 +595,16 @@ export function formatStatus(
|
|
|
529
595
|
result.status,
|
|
530
596
|
result.activation,
|
|
531
597
|
result.contentTypeBoost,
|
|
532
|
-
result.memory
|
|
598
|
+
result.memory,
|
|
599
|
+
result.rootErrors
|
|
533
600
|
),
|
|
601
|
+
...(staleLines.length
|
|
602
|
+
? [
|
|
603
|
+
"",
|
|
604
|
+
`Collections ${STALE_COLLECTIONS_HINT}:`,
|
|
605
|
+
...staleLines.map((line) => ` ${line}`),
|
|
606
|
+
]
|
|
607
|
+
: []),
|
|
534
608
|
...(stopLines.length
|
|
535
609
|
? [
|
|
536
610
|
"",
|
|
@@ -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),
|
|
@@ -3646,9 +3651,14 @@ function wireManagementCommands(program: Command): void {
|
|
|
3646
3651
|
|
|
3647
3652
|
// cleanup - Clean stale data
|
|
3648
3653
|
addWriteLeaseFlags(
|
|
3649
|
-
program
|
|
3654
|
+
program
|
|
3655
|
+
.command("cleanup")
|
|
3656
|
+
.description("Clean orphaned data from index")
|
|
3657
|
+
.option("--json", "JSON output")
|
|
3650
3658
|
).action(async (cmdOpts: Record<string, unknown>) => {
|
|
3651
|
-
const { cleanup, formatCleanup } =
|
|
3659
|
+
const { cleanup, formatCleanup, formatDroppingCollections } =
|
|
3660
|
+
await import("./commands/cleanup");
|
|
3661
|
+
const json = Boolean(cmdOpts.json) || getGlobals().json;
|
|
3652
3662
|
const lease = parseWriteLeaseFlags(cmdOpts);
|
|
3653
3663
|
const result = await withCliWriteLease(
|
|
3654
3664
|
{
|
|
@@ -3660,6 +3670,13 @@ function wireManagementCommands(program: Command): void {
|
|
|
3660
3670
|
cleanup({
|
|
3661
3671
|
configPath: getGlobals().config,
|
|
3662
3672
|
indexName: getGlobals().index,
|
|
3673
|
+
// Name what is about to be deleted before deleting it.
|
|
3674
|
+
onDropping: (collections) => {
|
|
3675
|
+
const plan = json
|
|
3676
|
+
? JSON.stringify({ deleting: collections })
|
|
3677
|
+
: formatDroppingCollections(collections);
|
|
3678
|
+
process.stderr.write(`${plan}\n`);
|
|
3679
|
+
},
|
|
3663
3680
|
})
|
|
3664
3681
|
);
|
|
3665
3682
|
throwIfWriteLeaseBusy(result, false);
|
|
@@ -3667,7 +3684,11 @@ function wireManagementCommands(program: Command): void {
|
|
|
3667
3684
|
if (!result.success) {
|
|
3668
3685
|
throw new CliError("RUNTIME", result.error ?? "Cleanup failed");
|
|
3669
3686
|
}
|
|
3670
|
-
process.stdout.write(
|
|
3687
|
+
process.stdout.write(
|
|
3688
|
+
json
|
|
3689
|
+
? `${JSON.stringify({ droppedCollections: result.droppedCollections, ...result.stats }, null, 2)}\n`
|
|
3690
|
+
: `${formatCleanup(result)}\n`
|
|
3691
|
+
);
|
|
3671
3692
|
});
|
|
3672
3693
|
|
|
3673
3694
|
// reset - Reset GNO to fresh state
|
|
@@ -4184,6 +4205,46 @@ function wireAgentsCommands(program: Command): void {
|
|
|
4184
4205
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
4185
4206
|
|
|
4186
4207
|
function wireTagsCommands(program: Command): void {
|
|
4208
|
+
program
|
|
4209
|
+
.command("metadata")
|
|
4210
|
+
.description(
|
|
4211
|
+
"List typed metadata keys with their types, document counts and common values"
|
|
4212
|
+
)
|
|
4213
|
+
.option("-c, --collection <name>", "only documents in this collection")
|
|
4214
|
+
.option(
|
|
4215
|
+
"--values <n>",
|
|
4216
|
+
"common values listed per key (1-100, default 10)",
|
|
4217
|
+
(value) => Number(value)
|
|
4218
|
+
)
|
|
4219
|
+
.option("--json", "JSON output")
|
|
4220
|
+
.option("--md", "Markdown output")
|
|
4221
|
+
.action(async (cmdOpts: Record<string, unknown>) => {
|
|
4222
|
+
const format = getFormat(cmdOpts);
|
|
4223
|
+
assertFormatSupported(CMD.metadataList, format);
|
|
4224
|
+
const globals = getGlobals();
|
|
4225
|
+
const { metadataList, formatMetadataList } =
|
|
4226
|
+
await import("./commands/metadata");
|
|
4227
|
+
const result = await metadataList({
|
|
4228
|
+
configPath: globals.config,
|
|
4229
|
+
indexName: globals.index,
|
|
4230
|
+
collection: cmdOpts.collection as string | undefined,
|
|
4231
|
+
values: cmdOpts.values as number | undefined,
|
|
4232
|
+
});
|
|
4233
|
+
if (!result.success) {
|
|
4234
|
+
throw new CliError(
|
|
4235
|
+
result.isValidation ? "VALIDATION" : "RUNTIME",
|
|
4236
|
+
result.error
|
|
4237
|
+
);
|
|
4238
|
+
}
|
|
4239
|
+
await writeOutput(
|
|
4240
|
+
formatMetadataList(result.data, {
|
|
4241
|
+
json: format === "json",
|
|
4242
|
+
md: format === "md",
|
|
4243
|
+
}),
|
|
4244
|
+
format
|
|
4245
|
+
);
|
|
4246
|
+
});
|
|
4247
|
+
|
|
4187
4248
|
const tagsCmd = program.command("tags").description("Manage document tags");
|
|
4188
4249
|
|
|
4189
4250
|
// tags list
|
|
@@ -281,7 +281,6 @@ export async function findEphemeralActivationProbeMatch(
|
|
|
281
281
|
const search = await store.searchFts(candidate.term, {
|
|
282
282
|
collection: plan.collection,
|
|
283
283
|
limit: PROBE_RESULT_LIMIT,
|
|
284
|
-
snippet: false,
|
|
285
284
|
});
|
|
286
285
|
if (!search.ok) {
|
|
287
286
|
return err(search.error.code, search.error.message, search.error.cause);
|
|
@@ -60,7 +60,9 @@ export interface ConfigMutationState {
|
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
type PersistedConfigHook = (
|
|
63
|
-
config: Config
|
|
63
|
+
config: Config,
|
|
64
|
+
/** Collection names before the mutation (mutators may edit in place). */
|
|
65
|
+
previousCollections: readonly string[]
|
|
64
66
|
) => Promise<{ ok: true } | { ok: false; error: string; code: string }>;
|
|
65
67
|
|
|
66
68
|
async function applySerializedConfigChange<T>(
|
|
@@ -102,6 +104,9 @@ async function applySerializedConfigChange<T>(
|
|
|
102
104
|
code: "LOAD_ERROR",
|
|
103
105
|
};
|
|
104
106
|
}
|
|
107
|
+
const previousCollections = currentConfig.collections.map(
|
|
108
|
+
({ name }) => name
|
|
109
|
+
);
|
|
105
110
|
const mutationResult = await mutate(currentConfig, {
|
|
106
111
|
created: !loadResult.ok,
|
|
107
112
|
});
|
|
@@ -129,7 +134,7 @@ async function applySerializedConfigChange<T>(
|
|
|
129
134
|
}
|
|
130
135
|
}
|
|
131
136
|
|
|
132
|
-
const persisted = await afterPersist?.(newConfig);
|
|
137
|
+
const persisted = await afterPersist?.(newConfig, previousCollections);
|
|
133
138
|
if (persisted && !persisted.ok) return persisted;
|
|
134
139
|
ctx.onConfigUpdated?.(newConfig);
|
|
135
140
|
|
|
@@ -165,62 +170,74 @@ export async function applyConfigChange<T = void>(
|
|
|
165
170
|
state: ConfigMutationState
|
|
166
171
|
) => Promise<MutationResult<T>> | MutationResult<T>
|
|
167
172
|
): Promise<ApplyConfigResult<T>> {
|
|
168
|
-
return applySerializedConfigChange(
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
const syncCollResult = await ctx.store.syncCollections(
|
|
184
|
-
newConfig.collections
|
|
185
|
-
);
|
|
186
|
-
if (!syncCollResult.ok) {
|
|
187
|
-
throw new Error(
|
|
188
|
-
`Collection sync failed: ${syncCollResult.error.message}`
|
|
189
|
-
);
|
|
173
|
+
return applySerializedConfigChange(
|
|
174
|
+
ctx,
|
|
175
|
+
mutate,
|
|
176
|
+
async (newConfig, previousCollections) => {
|
|
177
|
+
await ctx.afterConfigSaved?.(newConfig);
|
|
178
|
+
|
|
179
|
+
if (ctx.projectStore) {
|
|
180
|
+
const projection = await ctx.projectStore(ctx.store, newConfig);
|
|
181
|
+
if (!projection.ok) {
|
|
182
|
+
console.warn(`Config saved but DB sync failed: ${projection.error}`);
|
|
183
|
+
return {
|
|
184
|
+
ok: false,
|
|
185
|
+
error: `DB sync failed: ${projection.error}`,
|
|
186
|
+
code: "SYNC_ERROR",
|
|
187
|
+
};
|
|
190
188
|
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
189
|
+
} else {
|
|
190
|
+
const syncProjection = async (): Promise<void> => {
|
|
191
|
+
// Collections this change took out of the config are explicitly
|
|
192
|
+
// removed; other indexed collections the config omits stay as they
|
|
193
|
+
// are (the next ordinary open reports them).
|
|
194
|
+
const kept = new Set(newConfig.collections.map(({ name }) => name));
|
|
195
|
+
const syncCollResult = await ctx.store.syncCollections(
|
|
196
|
+
newConfig.collections,
|
|
197
|
+
{
|
|
198
|
+
remove: previousCollections.filter((name) => !kept.has(name)),
|
|
199
|
+
keepStale: true,
|
|
200
|
+
}
|
|
197
201
|
);
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
.
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
if (!transaction.ok) {
|
|
212
|
-
console.warn(
|
|
213
|
-
`Config saved but DB sync failed: ${transaction.error.message}`
|
|
214
|
-
);
|
|
215
|
-
return {
|
|
216
|
-
ok: false,
|
|
217
|
-
error: `DB sync failed: ${transaction.error.message}`,
|
|
218
|
-
code: "SYNC_ERROR",
|
|
202
|
+
if (!syncCollResult.ok) {
|
|
203
|
+
throw new Error(
|
|
204
|
+
`Collection sync failed: ${syncCollResult.error.message}`
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
const syncCtxResult = await ctx.store.syncContexts(
|
|
208
|
+
newConfig.contexts ?? []
|
|
209
|
+
);
|
|
210
|
+
if (!syncCtxResult.ok) {
|
|
211
|
+
throw new Error(
|
|
212
|
+
`Context sync failed: ${syncCtxResult.error.message}`
|
|
213
|
+
);
|
|
214
|
+
}
|
|
219
215
|
};
|
|
216
|
+
const transaction = ctx.store.withTransaction
|
|
217
|
+
? await ctx.store.withTransaction(syncProjection)
|
|
218
|
+
: await syncProjection()
|
|
219
|
+
.then(() => ({ ok: true as const, value: undefined }))
|
|
220
|
+
.catch((cause: unknown) => ({
|
|
221
|
+
ok: false as const,
|
|
222
|
+
error: {
|
|
223
|
+
message:
|
|
224
|
+
cause instanceof Error ? cause.message : "DB sync failed",
|
|
225
|
+
},
|
|
226
|
+
}));
|
|
227
|
+
if (!transaction.ok) {
|
|
228
|
+
console.warn(
|
|
229
|
+
`Config saved but DB sync failed: ${transaction.error.message}`
|
|
230
|
+
);
|
|
231
|
+
return {
|
|
232
|
+
ok: false,
|
|
233
|
+
error: `DB sync failed: ${transaction.error.message}`,
|
|
234
|
+
code: "SYNC_ERROR",
|
|
235
|
+
};
|
|
236
|
+
}
|
|
220
237
|
}
|
|
221
|
-
}
|
|
222
238
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
239
|
+
await ctx.afterStoreSynced?.(newConfig);
|
|
240
|
+
return { ok: true };
|
|
241
|
+
}
|
|
242
|
+
);
|
|
226
243
|
}
|
|
@@ -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",
|
|
@@ -90,7 +90,6 @@ async function loadCandidatePool(
|
|
|
90
90
|
memoryScopesAny: input.scopes,
|
|
91
91
|
excludeSuperseded: true,
|
|
92
92
|
anyTerm: true,
|
|
93
|
-
snippet: false,
|
|
94
93
|
});
|
|
95
94
|
// INVALID_INPUT means the text has no searchable terms: an empty pool.
|
|
96
95
|
if (!ftsResult.ok && ftsResult.error.code !== "INVALID_INPUT") {
|