@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
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* @module src/cli/commands/cleanup
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import type { CleanupStats } from "../../store/types";
|
|
9
|
+
import type { CleanupStats, StaleCollection } from "../../store/types";
|
|
10
10
|
|
|
11
11
|
import { getIndexDbPath } from "../../app/constants";
|
|
12
12
|
import { isInitialized, loadConfig } from "../../config";
|
|
@@ -20,14 +20,26 @@ export interface CleanupOptions {
|
|
|
20
20
|
configPath?: string;
|
|
21
21
|
/** Index name */
|
|
22
22
|
indexName?: string;
|
|
23
|
+
/** Called with the collections about to be deleted, before deleting them. */
|
|
24
|
+
onDropping?: (collections: StaleCollection[]) => void;
|
|
23
25
|
}
|
|
24
26
|
|
|
25
27
|
/**
|
|
26
28
|
* Result of cleanup command.
|
|
27
29
|
*/
|
|
28
30
|
export type CleanupResult =
|
|
29
|
-
| {
|
|
30
|
-
|
|
31
|
+
| {
|
|
32
|
+
success: true;
|
|
33
|
+
stats: CleanupStats;
|
|
34
|
+
/** Indexed collections the config omits, deleted by this cleanup. */
|
|
35
|
+
droppedCollections: StaleCollection[];
|
|
36
|
+
}
|
|
37
|
+
| {
|
|
38
|
+
success: false;
|
|
39
|
+
error: string;
|
|
40
|
+
/** Collections already deleted when a later step failed. */
|
|
41
|
+
droppedCollections?: StaleCollection[];
|
|
42
|
+
};
|
|
31
43
|
|
|
32
44
|
/**
|
|
33
45
|
* Execute gno cleanup command.
|
|
@@ -64,17 +76,32 @@ export async function cleanup(
|
|
|
64
76
|
try {
|
|
65
77
|
// Drop collections no longer in the config first (their documents follow
|
|
66
78
|
// by cascade), so their content, chunks and vectors count as orphans.
|
|
67
|
-
|
|
79
|
+
// This is the explicit removal path; it names what it deletes.
|
|
80
|
+
const stale = await store.listStaleCollections(
|
|
81
|
+
config.collections.map(({ name }) => name)
|
|
82
|
+
);
|
|
83
|
+
if (!stale.ok) {
|
|
84
|
+
return { success: false, error: stale.error.message };
|
|
85
|
+
}
|
|
86
|
+
const droppedCollections = stale.value;
|
|
87
|
+
if (droppedCollections.length > 0) options.onDropping?.(droppedCollections);
|
|
88
|
+
const syncResult = await store.syncCollections(config.collections, {
|
|
89
|
+
remove: "stale",
|
|
90
|
+
});
|
|
68
91
|
if (!syncResult.ok) {
|
|
69
92
|
return { success: false, error: syncResult.error.message };
|
|
70
93
|
}
|
|
71
94
|
|
|
72
95
|
const cleanupResult = await store.cleanupOrphans();
|
|
73
96
|
if (!cleanupResult.ok) {
|
|
74
|
-
return {
|
|
97
|
+
return {
|
|
98
|
+
success: false,
|
|
99
|
+
error: cleanupResult.error.message,
|
|
100
|
+
droppedCollections,
|
|
101
|
+
};
|
|
75
102
|
}
|
|
76
103
|
|
|
77
|
-
return { success: true, stats: cleanupResult.value };
|
|
104
|
+
return { success: true, stats: cleanupResult.value, droppedCollections };
|
|
78
105
|
} finally {
|
|
79
106
|
await store.close();
|
|
80
107
|
}
|
|
@@ -118,3 +145,16 @@ export function formatCleanup(result: CleanupResult): string {
|
|
|
118
145
|
|
|
119
146
|
return lines.join("\n");
|
|
120
147
|
}
|
|
148
|
+
|
|
149
|
+
/** Lines naming the collections cleanup is about to delete. */
|
|
150
|
+
export function formatDroppingCollections(
|
|
151
|
+
collections: readonly StaleCollection[]
|
|
152
|
+
): string {
|
|
153
|
+
return [
|
|
154
|
+
"Deleting collections not in this config:",
|
|
155
|
+
...collections.map(
|
|
156
|
+
(c) =>
|
|
157
|
+
` ${c.name}: ${c.documents} ${c.documents === 1 ? "document" : "documents"}`
|
|
158
|
+
),
|
|
159
|
+
].join("\n");
|
|
160
|
+
}
|
|
@@ -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 (
|
|
@@ -33,6 +33,7 @@ import {
|
|
|
33
33
|
import { formatVectorPartitionLines } from "../../core/vector-partition-status";
|
|
34
34
|
import { getEmbeddingFingerprint } from "../../embed/fingerprint";
|
|
35
35
|
import { getCodeChunkingStatus } from "../../ingestion/chunker";
|
|
36
|
+
import { checkCollectionRoots } from "../../ingestion/source-availability/root";
|
|
36
37
|
import { ModelCache } from "../../llm/cache";
|
|
37
38
|
import { getActivePreset, resolveModelUri } from "../../llm/registry";
|
|
38
39
|
import { SqliteAdapter } from "../../store/sqlite/adapter";
|
|
@@ -739,6 +740,37 @@ export async function checkFindingsPass(
|
|
|
739
740
|
};
|
|
740
741
|
}
|
|
741
742
|
|
|
743
|
+
/** Configured collection roots that are missing or unreadable right now. */
|
|
744
|
+
export async function checkCollectionRootAccess(
|
|
745
|
+
config: Config
|
|
746
|
+
): Promise<DoctorCheck> {
|
|
747
|
+
const name = "collection-roots";
|
|
748
|
+
const errors = await checkCollectionRoots(config.collections);
|
|
749
|
+
const unavailable = config.collections.flatMap((collection) => {
|
|
750
|
+
const error = errors[collection.name];
|
|
751
|
+
return error ? [{ collection, error }] : [];
|
|
752
|
+
});
|
|
753
|
+
if (unavailable.length === 0) {
|
|
754
|
+
return {
|
|
755
|
+
name,
|
|
756
|
+
status: "ok",
|
|
757
|
+
message: `${config.collections.length} collection root(s) available`,
|
|
758
|
+
};
|
|
759
|
+
}
|
|
760
|
+
return {
|
|
761
|
+
name,
|
|
762
|
+
status: "warn",
|
|
763
|
+
message: `${unavailable.length} collection root(s) missing or unreadable: ${unavailable.map(({ collection }) => collection.name).join(", ")}`,
|
|
764
|
+
details: [
|
|
765
|
+
...unavailable.map(
|
|
766
|
+
({ collection, error }) =>
|
|
767
|
+
`${collection.name}: ${collection.path} [${error.code}] ${error.message}`
|
|
768
|
+
),
|
|
769
|
+
"Reconnect the drive or restore the folder; if it moved, change the collection's path in the config, then run: gno update",
|
|
770
|
+
],
|
|
771
|
+
};
|
|
772
|
+
}
|
|
773
|
+
|
|
742
774
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
743
775
|
// Implementation
|
|
744
776
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -779,6 +811,10 @@ export async function doctor(
|
|
|
779
811
|
// Code chunking capability
|
|
780
812
|
checks.push(checkCodeChunking());
|
|
781
813
|
|
|
814
|
+
// Collection roots, checked now (documents stay indexed while one is away)
|
|
815
|
+
const rootsCheck = await checkCollectionRootAccess(config);
|
|
816
|
+
checks.push(rootsCheck);
|
|
817
|
+
|
|
782
818
|
// Embedding fingerprint freshness
|
|
783
819
|
checks.push(...(await checkEmbeddingFingerprints(config, options.indexName)));
|
|
784
820
|
|
|
@@ -801,6 +837,7 @@ export async function doctor(
|
|
|
801
837
|
return {
|
|
802
838
|
healthy:
|
|
803
839
|
!hasErrors &&
|
|
840
|
+
rootsCheck.status === "ok" &&
|
|
804
841
|
activation.healthy &&
|
|
805
842
|
isConnectorActivationComplete(activation),
|
|
806
843
|
checks,
|
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}`
|
|
@@ -12,6 +12,7 @@ import type { ConnectorWorkspaceEnvironment } from "../../../core/connector-envi
|
|
|
12
12
|
|
|
13
13
|
import { resolveDirs } from "../../../app/constants";
|
|
14
14
|
import { assertValidIndexName } from "../../../app/index-name";
|
|
15
|
+
import { modulePath } from "../../../core/module-path";
|
|
15
16
|
import { getCurrentGnoEntrypoint } from "../../../core/runtime-entrypoint";
|
|
16
17
|
import {
|
|
17
18
|
DEFAULT_MCP_TOOL_PROFILE,
|
|
@@ -459,7 +460,7 @@ export function resolveLaunchPaths(): {
|
|
|
459
460
|
entrypoint: LaunchPath;
|
|
460
461
|
} {
|
|
461
462
|
const entrypoint = getCurrentGnoEntrypoint();
|
|
462
|
-
if (isBunfsPath(import.meta
|
|
463
|
+
if (isBunfsPath(modulePath(import.meta))) {
|
|
463
464
|
return {
|
|
464
465
|
bun: { path: process.execPath, pinned: false },
|
|
465
466
|
entrypoint: { path: entrypoint, pinned: false },
|
|
@@ -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
|
}
|
|
@@ -21,6 +21,7 @@ import type { FolderSetupReceipt } from "../../core/setup-receipt";
|
|
|
21
21
|
import { VERSION } from "../../app/constants";
|
|
22
22
|
import { canonicalizeIndexName } from "../../app/index-name";
|
|
23
23
|
import { withWriteLock } from "../../core/file-lock";
|
|
24
|
+
import { moduleDir } from "../../core/module-path";
|
|
24
25
|
import {
|
|
25
26
|
setupFingerprint,
|
|
26
27
|
setupRootFingerprint,
|
|
@@ -427,7 +428,11 @@ function createSemanticReceipt(
|
|
|
427
428
|
async function defaultSpawnWorker(
|
|
428
429
|
receipt: SetupSemanticReceipt
|
|
429
430
|
): Promise<SpawnedSemanticWorker> {
|
|
430
|
-
const workerPath = join(
|
|
431
|
+
const workerPath = join(
|
|
432
|
+
moduleDir(import.meta),
|
|
433
|
+
"..",
|
|
434
|
+
"setup-semantic-worker.ts"
|
|
435
|
+
);
|
|
431
436
|
const descriptor = openSync(receipt.logPath, "a", 0o600);
|
|
432
437
|
try {
|
|
433
438
|
const child = Bun.spawn({
|
|
@@ -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 {
|
|
@@ -22,7 +22,10 @@ import {
|
|
|
22
22
|
} from "../../config";
|
|
23
23
|
import { tracePhase } from "../../core/phase-trace";
|
|
24
24
|
import { BUDGET_ERROR_CODES } from "../../ingestion/file-processor";
|
|
25
|
-
import {
|
|
25
|
+
import {
|
|
26
|
+
SqliteAdapter,
|
|
27
|
+
staleCollectionsMessage,
|
|
28
|
+
} from "../../store/sqlite/adapter";
|
|
26
29
|
import { assertCliSessionBinding } from "../session-binding";
|
|
27
30
|
|
|
28
31
|
/**
|
|
@@ -53,6 +56,10 @@ export interface InitStoreOptions {
|
|
|
53
56
|
syncConfig?: boolean;
|
|
54
57
|
/** Permit management commands that only need an existing index database */
|
|
55
58
|
allowEmptyCollections?: boolean;
|
|
59
|
+
/** Open without syncing even when the index holds collections the config
|
|
60
|
+
* omits: config mutations report them through their own sync, and a
|
|
61
|
+
* connector proof reads one collection through a subset config. */
|
|
62
|
+
allowStaleCollections?: boolean;
|
|
56
63
|
}
|
|
57
64
|
|
|
58
65
|
/**
|
|
@@ -132,7 +139,25 @@ export async function initStore(
|
|
|
132
139
|
return { ok: false, error: openResult.error.message };
|
|
133
140
|
}
|
|
134
141
|
|
|
142
|
+
if (options.syncConfig === false && options.allowStaleCollections) {
|
|
143
|
+
return { ok: true, store, config, collections, actualConfigPath };
|
|
144
|
+
}
|
|
135
145
|
if (options.syncConfig === false) {
|
|
146
|
+
// Reads skip the sync but must not search collections another config
|
|
147
|
+
// indexed; they fail the same way a sync would.
|
|
148
|
+
const stale = await store.listStaleCollections(
|
|
149
|
+
config.collections.map(({ name }) => name)
|
|
150
|
+
);
|
|
151
|
+
const indexed = stale.ok ? stale.value.filter((c) => c.documents > 0) : [];
|
|
152
|
+
if (!stale.ok || indexed.length > 0) {
|
|
153
|
+
await store.close();
|
|
154
|
+
return {
|
|
155
|
+
ok: false,
|
|
156
|
+
error: stale.ok
|
|
157
|
+
? staleCollectionsMessage(indexed)
|
|
158
|
+
: stale.error.message,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
136
161
|
return { ok: true, store, config, collections, actualConfigPath };
|
|
137
162
|
}
|
|
138
163
|
|
|
@@ -164,10 +189,9 @@ export function decorateSearchResultsForIndex(
|
|
|
164
189
|
}
|
|
165
190
|
return {
|
|
166
191
|
...data,
|
|
167
|
-
results: data.results.map((result) =>
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
})),
|
|
192
|
+
results: data.results.map((result) =>
|
|
193
|
+
decorateResultUris(result, indexName)
|
|
194
|
+
),
|
|
171
195
|
};
|
|
172
196
|
}
|
|
173
197
|
|
|
@@ -217,10 +241,13 @@ export function formatSyncResultLines(
|
|
|
217
241
|
lines.push(` ${c.filesErrored} errors`);
|
|
218
242
|
}
|
|
219
243
|
// 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.
|
|
244
|
+
// verbose output: it stays unindexed until a later run finishes it. So is
|
|
245
|
+
// a collection-level error such as an unavailable root.
|
|
221
246
|
if (!options.verbose) {
|
|
222
247
|
for (const err of c.errors) {
|
|
223
|
-
if (
|
|
248
|
+
if (err.relPath === "") {
|
|
249
|
+
lines.push(` [${err.code}] ${err.message}`);
|
|
250
|
+
} else if (BUDGET_ERROR_CODES.has(err.code)) {
|
|
224
251
|
lines.push(` [${err.code}] ${err.relPath}: ${err.message}`);
|
|
225
252
|
}
|
|
226
253
|
}
|
|
@@ -11,6 +11,11 @@ import { existsSync, realpathSync } from "node:fs";
|
|
|
11
11
|
import { homedir } from "node:os";
|
|
12
12
|
import { dirname, isAbsolute, join, normalize, relative, sep } from "node:path";
|
|
13
13
|
|
|
14
|
+
import {
|
|
15
|
+
harnessConfigDirFromEnv,
|
|
16
|
+
type HarnessId,
|
|
17
|
+
} from "../agents/harnesses.js";
|
|
18
|
+
|
|
14
19
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
15
20
|
// Environment Variables
|
|
16
21
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -84,6 +89,11 @@ interface TargetPathConfig {
|
|
|
84
89
|
userBase: string; // e.g., ".claude" (joined with homedir) or ".config/opencode"
|
|
85
90
|
skillsSubdir: string; // e.g., "skills" or "skill"
|
|
86
91
|
envVar: string;
|
|
92
|
+
/**
|
|
93
|
+
* Harness whose config-dir env var (CLAUDE_CONFIG_DIR, CODEX_HOME) replaces
|
|
94
|
+
* the user base, resolved exactly as `gno agents` does.
|
|
95
|
+
*/
|
|
96
|
+
configDirHarness?: HarnessId;
|
|
87
97
|
}
|
|
88
98
|
|
|
89
99
|
const TARGET_CONFIGS: Record<SkillTarget, TargetPathConfig> = {
|
|
@@ -92,12 +102,14 @@ const TARGET_CONFIGS: Record<SkillTarget, TargetPathConfig> = {
|
|
|
92
102
|
userBase: ".claude",
|
|
93
103
|
skillsSubdir: "skills",
|
|
94
104
|
envVar: ENV_CLAUDE_SKILLS_DIR,
|
|
105
|
+
configDirHarness: "claude",
|
|
95
106
|
},
|
|
96
107
|
codex: {
|
|
97
108
|
projectBase: ".codex",
|
|
98
109
|
userBase: ".codex",
|
|
99
110
|
skillsSubdir: "skills",
|
|
100
111
|
envVar: ENV_CODEX_SKILLS_DIR,
|
|
112
|
+
configDirHarness: "codex",
|
|
101
113
|
},
|
|
102
114
|
opencode: {
|
|
103
115
|
projectBase: ".opencode",
|
|
@@ -150,8 +162,14 @@ export function resolveSkillPaths(opts: SkillPathOptions): SkillPaths {
|
|
|
150
162
|
let base: string;
|
|
151
163
|
|
|
152
164
|
if (scope === "user") {
|
|
153
|
-
|
|
154
|
-
|
|
165
|
+
// Any home override (option or env) is an isolation request and
|
|
166
|
+
// suppresses the harness config-dir env vars, as in `gno agents`.
|
|
167
|
+
const explicitHome = homeDir ?? process.env[ENV_SKILLS_HOME_OVERRIDE];
|
|
168
|
+
const configDir =
|
|
169
|
+
explicitHome === undefined && config.configDirHarness
|
|
170
|
+
? harnessConfigDirFromEnv(config.configDirHarness)
|
|
171
|
+
: undefined;
|
|
172
|
+
base = configDir ?? join(explicitHome ?? homedir(), config.userBase);
|
|
155
173
|
} else {
|
|
156
174
|
const projectRoot = cwd ?? process.cwd();
|
|
157
175
|
base = join(projectRoot, config.projectBase);
|