@gmickel/gno 1.22.0 → 1.24.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 +33 -12
- package/assets/skill/SKILL.md +41 -19
- package/package.json +1 -1
- package/spec/cli.md +127 -20
- package/spec/evals-agentic.md +35 -0
- package/spec/evals.md +6 -0
- package/spec/mcp.md +18 -0
- package/spec/output-schemas/query-diagnose-v1.schema.json +123 -0
- package/spec/output-schemas/query-diagnose.schema.json +89 -2
- package/spec/output-schemas/setup-activation-result.schema.json +456 -0
- package/spec/output-schemas/setup-command-result.schema.json +93 -0
- package/spec/output-schemas/setup-receipt.schema.json +258 -0
- package/spec/output-schemas/setup-semantic-receipt.schema.json +195 -0
- package/src/app/context-runtime-types.ts +3 -0
- package/src/app/context-runtime.ts +1 -0
- package/src/app/context-surface.ts +4 -2
- package/src/cli/commands/ask.ts +31 -20
- package/src/cli/commands/completion/scripts.ts +2 -0
- package/src/cli/commands/context-build.ts +17 -7
- package/src/cli/commands/embed.ts +7 -2
- package/src/cli/commands/query.ts +58 -37
- package/src/cli/commands/search.ts +29 -19
- package/src/cli/commands/setup-activation.ts +324 -0
- package/src/cli/commands/setup-semantic.ts +591 -0
- package/src/cli/commands/setup.ts +410 -0
- package/src/cli/commands/vsearch.ts +31 -22
- package/src/cli/options.ts +39 -0
- package/src/cli/program.ts +112 -0
- package/src/cli/setup-semantic-worker.ts +177 -0
- package/src/config/defaults.ts +10 -1
- package/src/config/types.ts +71 -0
- package/src/core/config-mutation.ts +94 -64
- package/src/core/file-lock.ts +70 -31
- package/src/core/folder-setup-planning.ts +453 -0
- package/src/core/folder-setup.ts +490 -0
- package/src/core/project-affinity-surface.ts +114 -0
- package/src/core/project-affinity.ts +330 -0
- package/src/core/setup-activation.ts +309 -0
- package/src/core/setup-receipt.ts +321 -0
- package/src/core/validation.ts +20 -1
- package/src/mcp/tools/ask.ts +10 -1
- package/src/mcp/tools/context.ts +18 -0
- package/src/mcp/tools/index.ts +13 -2
- package/src/mcp/tools/query.ts +12 -0
- package/src/mcp/tools/search.ts +7 -0
- package/src/mcp/tools/vsearch.ts +7 -0
- package/src/pipeline/diagnose.ts +48 -3
- package/src/pipeline/explain.ts +54 -13
- package/src/pipeline/hybrid.ts +100 -59
- package/src/pipeline/project-affinity.ts +162 -0
- package/src/pipeline/search.ts +76 -10
- package/src/pipeline/types.ts +9 -0
- package/src/pipeline/vsearch.ts +117 -91
- package/src/sdk/client.ts +80 -20
- package/src/sdk/index.ts +2 -0
- package/src/sdk/types.ts +20 -7
- package/src/serve/connectors.ts +29 -2
- package/src/serve/context-capsule.ts +18 -1
- package/src/serve/routes/api.ts +69 -0
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Direct verified-folder setup CLI composition.
|
|
3
|
+
*
|
|
4
|
+
* @module src/cli/commands/setup
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// node:readline/promises is the platform line-input API; Bun has no equivalent
|
|
8
|
+
// for one default-No terminal confirmation.
|
|
9
|
+
import { createInterface } from "node:readline/promises";
|
|
10
|
+
|
|
11
|
+
import type {
|
|
12
|
+
FolderSetupError,
|
|
13
|
+
FolderSetupOptions,
|
|
14
|
+
FolderSetupResult,
|
|
15
|
+
} from "../../core/folder-setup";
|
|
16
|
+
import type {
|
|
17
|
+
FolderSetupReceipt,
|
|
18
|
+
SetupStageName,
|
|
19
|
+
} from "../../core/setup-receipt";
|
|
20
|
+
import type { SqliteAdapter } from "../../store/sqlite/adapter";
|
|
21
|
+
|
|
22
|
+
import { getIndexDbPath } from "../../app/constants";
|
|
23
|
+
import {
|
|
24
|
+
getConfigPaths,
|
|
25
|
+
isInitialized,
|
|
26
|
+
loadConfig,
|
|
27
|
+
toAbsolutePath,
|
|
28
|
+
} from "../../config";
|
|
29
|
+
import { setupFolder } from "../../core/folder-setup";
|
|
30
|
+
import { persistSetupReceipt } from "../../core/setup-receipt";
|
|
31
|
+
import { SqliteAdapter as DefaultSqliteAdapter } from "../../store/sqlite/adapter";
|
|
32
|
+
import { init } from "./init";
|
|
33
|
+
import {
|
|
34
|
+
scheduleSetupSemantic,
|
|
35
|
+
type SetupSemanticReceipt,
|
|
36
|
+
} from "./setup-semantic";
|
|
37
|
+
|
|
38
|
+
export const SETUP_COMMAND_SCHEMA_VERSION = "1.0" as const;
|
|
39
|
+
|
|
40
|
+
export interface SetupCommandError {
|
|
41
|
+
code: string;
|
|
42
|
+
message: string;
|
|
43
|
+
remediation: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface SetupCommandResult {
|
|
47
|
+
schemaVersion: typeof SETUP_COMMAND_SCHEMA_VERSION;
|
|
48
|
+
status: "completed" | "failed";
|
|
49
|
+
lexical: {
|
|
50
|
+
receipt: FolderSetupReceipt | null;
|
|
51
|
+
error: SetupCommandError | null;
|
|
52
|
+
};
|
|
53
|
+
semantic: SetupSemanticReceipt | null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface SetupCommandOptions {
|
|
57
|
+
folder: string;
|
|
58
|
+
name?: string;
|
|
59
|
+
exclude?: string[];
|
|
60
|
+
authorizeSecretRisk?: boolean;
|
|
61
|
+
semantic?: boolean;
|
|
62
|
+
indexName?: string;
|
|
63
|
+
configPath?: string;
|
|
64
|
+
offline?: boolean;
|
|
65
|
+
yes?: boolean;
|
|
66
|
+
json?: boolean;
|
|
67
|
+
quiet?: boolean;
|
|
68
|
+
stdinIsTTY?: boolean;
|
|
69
|
+
stderrIsTTY?: boolean;
|
|
70
|
+
progress?: (stage: SetupStageName, receipt: FolderSetupReceipt) => void;
|
|
71
|
+
confirmSecretRisk?: (receipt: FolderSetupReceipt) => Promise<boolean>;
|
|
72
|
+
setupFolderFn?: (options: FolderSetupOptions) => Promise<FolderSetupResult>;
|
|
73
|
+
scheduleSemanticFn?: typeof scheduleSetupSemantic;
|
|
74
|
+
initFn?: typeof init;
|
|
75
|
+
isInitializedFn?: typeof isInitialized;
|
|
76
|
+
createStore?: () => SqliteAdapter;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export interface SetupCommandOutcome {
|
|
80
|
+
result: SetupCommandResult;
|
|
81
|
+
exitCode: 0 | 1 | 2;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const VALIDATION_ERROR_CODES = new Set([
|
|
85
|
+
"folder_not_found",
|
|
86
|
+
"folder_not_directory",
|
|
87
|
+
"folder_unreadable",
|
|
88
|
+
"dangerous_root",
|
|
89
|
+
"secret_risk",
|
|
90
|
+
"empty_folder",
|
|
91
|
+
"unsupported_only",
|
|
92
|
+
"no_indexable_lexical_corpus",
|
|
93
|
+
"invalid_collection_name",
|
|
94
|
+
"collection_name_conflict",
|
|
95
|
+
"collection_overlap",
|
|
96
|
+
"collection_filter_disagreement",
|
|
97
|
+
"store_index_mismatch",
|
|
98
|
+
"setup_path_overlap",
|
|
99
|
+
]);
|
|
100
|
+
|
|
101
|
+
function commandError(
|
|
102
|
+
code: string,
|
|
103
|
+
message: string,
|
|
104
|
+
remediation: string
|
|
105
|
+
): SetupCommandError {
|
|
106
|
+
return { code, message, remediation };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function failureOutcome(
|
|
110
|
+
error: SetupCommandError,
|
|
111
|
+
receipt: FolderSetupReceipt | null,
|
|
112
|
+
exitCode: 1 | 2
|
|
113
|
+
): SetupCommandOutcome {
|
|
114
|
+
return {
|
|
115
|
+
result: {
|
|
116
|
+
schemaVersion: SETUP_COMMAND_SCHEMA_VERSION,
|
|
117
|
+
status: "failed",
|
|
118
|
+
lexical: { receipt, error },
|
|
119
|
+
semantic: null,
|
|
120
|
+
},
|
|
121
|
+
exitCode,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function exitCodeForSetupError(error: FolderSetupError): 1 | 2 {
|
|
126
|
+
return VALIDATION_ERROR_CODES.has(error.code) ? 1 : 2;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function firstActiveStage(receipt: FolderSetupReceipt): SetupStageName {
|
|
130
|
+
const stages = Object.entries(receipt.stages);
|
|
131
|
+
const active = stages.find(([, stage]) => stage.status === "in_progress");
|
|
132
|
+
if (active) {
|
|
133
|
+
return active[0] as SetupStageName;
|
|
134
|
+
}
|
|
135
|
+
const failed = stages.find(([, stage]) => stage.status === "failed");
|
|
136
|
+
if (failed) {
|
|
137
|
+
return failed[0] as SetupStageName;
|
|
138
|
+
}
|
|
139
|
+
const lastPassed = stages
|
|
140
|
+
.reverse()
|
|
141
|
+
.find(([, stage]) => stage.status === "passed");
|
|
142
|
+
return (lastPassed?.[0] as SetupStageName | undefined) ?? "preflight";
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export async function terminalSecretConfirmation(
|
|
146
|
+
receipt: FolderSetupReceipt,
|
|
147
|
+
ask?: (question: string) => Promise<string>
|
|
148
|
+
): Promise<boolean> {
|
|
149
|
+
process.stderr.write(
|
|
150
|
+
`Potential secret files detected in ${receipt.input.folder}\n`
|
|
151
|
+
);
|
|
152
|
+
process.stderr.write(
|
|
153
|
+
`Effective exclusions: ${receipt.input.excludes.join(", ") || "(none)"}\n`
|
|
154
|
+
);
|
|
155
|
+
const prompt = ask
|
|
156
|
+
? null
|
|
157
|
+
: createInterface({
|
|
158
|
+
input: process.stdin,
|
|
159
|
+
output: process.stderr,
|
|
160
|
+
});
|
|
161
|
+
try {
|
|
162
|
+
try {
|
|
163
|
+
const answer = await (ask ?? prompt!.question.bind(prompt))(
|
|
164
|
+
"Index this folder despite the secret-file risk? [y/N] "
|
|
165
|
+
);
|
|
166
|
+
return /^(?:y|yes)$/i.test(answer.trim());
|
|
167
|
+
} catch {
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
} finally {
|
|
171
|
+
prompt?.close();
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export function lexicalSuccessIsProven(receipt: FolderSetupReceipt): boolean {
|
|
176
|
+
const resultUri = receipt.activation?.evidence.resultUri;
|
|
177
|
+
return (
|
|
178
|
+
receipt.status === "completed" &&
|
|
179
|
+
receipt.activation?.ready === true &&
|
|
180
|
+
typeof resultUri === "string" &&
|
|
181
|
+
resultUri.length > 0
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function validateSetupArguments(
|
|
186
|
+
options: SetupCommandOptions
|
|
187
|
+
): SetupCommandError | null {
|
|
188
|
+
if (!options.folder.trim()) {
|
|
189
|
+
return commandError(
|
|
190
|
+
"invalid_folder",
|
|
191
|
+
"Folder is required",
|
|
192
|
+
"Pass a readable local folder to `gno setup`."
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
if (options.exclude?.some((value) => value.length === 0)) {
|
|
196
|
+
return commandError(
|
|
197
|
+
"invalid_exclusion",
|
|
198
|
+
"--exclude requires a non-empty literal pattern",
|
|
199
|
+
"Remove the empty occurrence or pass a literal exclusion pattern."
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
return null;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Execute the standalone setup transaction. This function returns classified
|
|
207
|
+
* outcomes; the Commander surface owns stdout/stderr rendering.
|
|
208
|
+
*/
|
|
209
|
+
async function executeSetup(
|
|
210
|
+
options: SetupCommandOptions
|
|
211
|
+
): Promise<SetupCommandOutcome> {
|
|
212
|
+
const argumentError = validateSetupArguments(options);
|
|
213
|
+
if (argumentError) {
|
|
214
|
+
return failureOutcome(argumentError, null, 1);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const paths = getConfigPaths();
|
|
218
|
+
const configPath = toAbsolutePath(options.configPath ?? paths.configFile);
|
|
219
|
+
const dataDir = paths.dataDir;
|
|
220
|
+
const indexName = options.indexName ?? "default";
|
|
221
|
+
const initialized = await (options.isInitializedFn ?? isInitialized)(
|
|
222
|
+
configPath
|
|
223
|
+
);
|
|
224
|
+
if (!initialized) {
|
|
225
|
+
const initializedResult = await (options.initFn ?? init)({
|
|
226
|
+
configPath,
|
|
227
|
+
yes: true,
|
|
228
|
+
});
|
|
229
|
+
if (!initializedResult.success) {
|
|
230
|
+
return failureOutcome(
|
|
231
|
+
commandError(
|
|
232
|
+
"bootstrap_failed",
|
|
233
|
+
initializedResult.error ?? "Failed to initialize GNO",
|
|
234
|
+
"Fix config/data-directory permissions and rerun setup."
|
|
235
|
+
),
|
|
236
|
+
null,
|
|
237
|
+
2
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const configResult = await loadConfig(configPath);
|
|
243
|
+
if (!configResult.ok) {
|
|
244
|
+
return failureOutcome(
|
|
245
|
+
commandError(
|
|
246
|
+
"config_load_failed",
|
|
247
|
+
configResult.error.message,
|
|
248
|
+
"Repair the selected config and rerun setup."
|
|
249
|
+
),
|
|
250
|
+
null,
|
|
251
|
+
2
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const store =
|
|
256
|
+
options.createStore?.() ?? (new DefaultSqliteAdapter() as SqliteAdapter);
|
|
257
|
+
store.setConfigPath(configPath);
|
|
258
|
+
const opened = await store.open(
|
|
259
|
+
getIndexDbPath(indexName),
|
|
260
|
+
configResult.value.ftsTokenizer
|
|
261
|
+
);
|
|
262
|
+
if (!opened.ok) {
|
|
263
|
+
await store.close();
|
|
264
|
+
return failureOutcome(
|
|
265
|
+
commandError(
|
|
266
|
+
"store_open_failed",
|
|
267
|
+
opened.error.message,
|
|
268
|
+
"Repair the selected index database and rerun setup."
|
|
269
|
+
),
|
|
270
|
+
null,
|
|
271
|
+
2
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const setupFolderFn = options.setupFolderFn ?? setupFolder;
|
|
276
|
+
let lastProgress: string | null = null;
|
|
277
|
+
const receiptWriter = async (receipt: FolderSetupReceipt): Promise<void> => {
|
|
278
|
+
await persistSetupReceipt(receipt);
|
|
279
|
+
if (options.quiet || options.json) {
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
const stage = firstActiveStage(receipt);
|
|
283
|
+
const key = stage;
|
|
284
|
+
if (lastProgress !== key) {
|
|
285
|
+
lastProgress = key;
|
|
286
|
+
options.progress?.(stage, receipt);
|
|
287
|
+
}
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
const runCore = (authorized: boolean): Promise<FolderSetupResult> =>
|
|
291
|
+
setupFolderFn({
|
|
292
|
+
folder: options.folder,
|
|
293
|
+
store,
|
|
294
|
+
configPath,
|
|
295
|
+
dataDir,
|
|
296
|
+
indexName,
|
|
297
|
+
name: options.name,
|
|
298
|
+
exclude: options.exclude,
|
|
299
|
+
secretRiskAuthorized: authorized,
|
|
300
|
+
receiptWriter,
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
try {
|
|
304
|
+
let lexicalResult = await runCore(options.authorizeSecretRisk === true);
|
|
305
|
+
if (
|
|
306
|
+
!lexicalResult.ok &&
|
|
307
|
+
lexicalResult.error.code === "secret_risk" &&
|
|
308
|
+
lexicalResult.receipt &&
|
|
309
|
+
options.authorizeSecretRisk !== true
|
|
310
|
+
) {
|
|
311
|
+
const mayPrompt =
|
|
312
|
+
options.json !== true &&
|
|
313
|
+
options.yes !== true &&
|
|
314
|
+
(options.stdinIsTTY ?? process.stdin.isTTY ?? false) &&
|
|
315
|
+
(options.stderrIsTTY ?? process.stderr.isTTY ?? false);
|
|
316
|
+
if (mayPrompt) {
|
|
317
|
+
const confirmed = await (
|
|
318
|
+
options.confirmSecretRisk ?? terminalSecretConfirmation
|
|
319
|
+
)(lexicalResult.receipt);
|
|
320
|
+
if (confirmed) {
|
|
321
|
+
lexicalResult = await runCore(true);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
if (!lexicalResult.ok) {
|
|
327
|
+
return failureOutcome(
|
|
328
|
+
lexicalResult.error,
|
|
329
|
+
lexicalResult.receipt,
|
|
330
|
+
exitCodeForSetupError(lexicalResult.error)
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
if (!lexicalSuccessIsProven(lexicalResult.receipt)) {
|
|
334
|
+
return failureOutcome(
|
|
335
|
+
commandError(
|
|
336
|
+
"lexical_success_invariant_failed",
|
|
337
|
+
"Setup completed without an exact lexical retrieval result",
|
|
338
|
+
"Rerun setup after repairing the selected index."
|
|
339
|
+
),
|
|
340
|
+
lexicalResult.receipt,
|
|
341
|
+
2
|
|
342
|
+
);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
const semantic = await (
|
|
346
|
+
options.scheduleSemanticFn ?? scheduleSetupSemantic
|
|
347
|
+
)({
|
|
348
|
+
setupReceipt: lexicalResult.receipt,
|
|
349
|
+
dataDir,
|
|
350
|
+
configPath,
|
|
351
|
+
indexName,
|
|
352
|
+
offline: options.offline ?? false,
|
|
353
|
+
disabled: options.semantic === false,
|
|
354
|
+
});
|
|
355
|
+
return {
|
|
356
|
+
result: {
|
|
357
|
+
schemaVersion: SETUP_COMMAND_SCHEMA_VERSION,
|
|
358
|
+
status: "completed",
|
|
359
|
+
lexical: {
|
|
360
|
+
receipt: lexicalResult.receipt,
|
|
361
|
+
error: null,
|
|
362
|
+
},
|
|
363
|
+
semantic,
|
|
364
|
+
},
|
|
365
|
+
exitCode: 0,
|
|
366
|
+
};
|
|
367
|
+
} finally {
|
|
368
|
+
await store.close();
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
export async function setup(
|
|
373
|
+
options: SetupCommandOptions
|
|
374
|
+
): Promise<SetupCommandOutcome> {
|
|
375
|
+
try {
|
|
376
|
+
return await executeSetup(options);
|
|
377
|
+
} catch (error) {
|
|
378
|
+
return failureOutcome(
|
|
379
|
+
commandError(
|
|
380
|
+
"setup_runtime_failed",
|
|
381
|
+
error instanceof Error ? error.message : String(error),
|
|
382
|
+
"Fix the reported local setup error and rerun setup."
|
|
383
|
+
),
|
|
384
|
+
null,
|
|
385
|
+
2
|
|
386
|
+
);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
export function formatSetupResult(
|
|
391
|
+
result: SetupCommandResult,
|
|
392
|
+
options: { json: boolean }
|
|
393
|
+
): string {
|
|
394
|
+
if (options.json) {
|
|
395
|
+
return JSON.stringify(result, null, 2);
|
|
396
|
+
}
|
|
397
|
+
const receipt = result.lexical.receipt;
|
|
398
|
+
if (result.status === "failed") {
|
|
399
|
+
const error = result.lexical.error;
|
|
400
|
+
return `${error?.code ?? "setup_failed"}: ${error?.message ?? "Setup failed"}. ${error?.remediation ?? ""}`.trim();
|
|
401
|
+
}
|
|
402
|
+
const semantic = result.semantic;
|
|
403
|
+
return [
|
|
404
|
+
`Setup ${receipt?.collection.disposition}: ${receipt?.collection.name}`,
|
|
405
|
+
`result=${receipt?.activation?.evidence.resultUri}`,
|
|
406
|
+
`receipt=${receipt?.paths.receipt}`,
|
|
407
|
+
`semantic=${semantic?.status ?? "pending"}`,
|
|
408
|
+
`resume=${semantic?.resumeCommand ?? "gno embed"}`,
|
|
409
|
+
].join(" ");
|
|
410
|
+
}
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* @module src/cli/commands/vsearch
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
import type { CliProjectAffinityRequest } from "../../core/project-affinity-surface";
|
|
8
9
|
import type {
|
|
9
10
|
RetrievalTraceSession,
|
|
10
11
|
RetrievalTraceSurfaceMetadata,
|
|
@@ -12,6 +13,7 @@ import type {
|
|
|
12
13
|
import type { EmbeddingPort } from "../../llm/types";
|
|
13
14
|
import type { SearchOptions, SearchResults } from "../../pipeline/types";
|
|
14
15
|
|
|
16
|
+
import { resolveCliProjectAffinity } from "../../core/project-affinity-surface";
|
|
15
17
|
import {
|
|
16
18
|
finishRetrievalTraceAfterError,
|
|
17
19
|
retrievalTraceFilters,
|
|
@@ -35,26 +37,27 @@ import { decorateSearchResultsForIndex, initStore } from "./shared";
|
|
|
35
37
|
// Types
|
|
36
38
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
37
39
|
|
|
38
|
-
export type VsearchCommandOptions = SearchOptions &
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
40
|
+
export type VsearchCommandOptions = Omit<SearchOptions, "projectAffinity"> &
|
|
41
|
+
CliProjectAffinityRequest & {
|
|
42
|
+
/** Override config path */
|
|
43
|
+
configPath?: string;
|
|
44
|
+
/** Index name */
|
|
45
|
+
indexName?: string;
|
|
46
|
+
/** Override model URI */
|
|
47
|
+
model?: string;
|
|
48
|
+
/** Output as JSON */
|
|
49
|
+
json?: boolean;
|
|
50
|
+
/** Output as Markdown */
|
|
51
|
+
md?: boolean;
|
|
52
|
+
/** Output as CSV */
|
|
53
|
+
csv?: boolean;
|
|
54
|
+
/** Output as XML */
|
|
55
|
+
xml?: boolean;
|
|
56
|
+
/** Output files only */
|
|
57
|
+
files?: boolean;
|
|
58
|
+
/** Terminal hyperlink policy */
|
|
59
|
+
terminalLinks?: FormatOptions["terminalLinks"];
|
|
60
|
+
};
|
|
58
61
|
|
|
59
62
|
export type VsearchResult =
|
|
60
63
|
| {
|
|
@@ -96,6 +99,12 @@ export async function vsearch(
|
|
|
96
99
|
let traceSession: RetrievalTraceSession | undefined;
|
|
97
100
|
|
|
98
101
|
try {
|
|
102
|
+
const { projectAffinityDisabled, projectRoots, ...searchOptions } = options;
|
|
103
|
+
const projectAffinity = await resolveCliProjectAffinity(config, {
|
|
104
|
+
cwd: process.cwd(),
|
|
105
|
+
disabled: projectAffinityDisabled,
|
|
106
|
+
projectRoots,
|
|
107
|
+
});
|
|
99
108
|
// Get model URI from preset
|
|
100
109
|
const modelUri = resolveModelUri(
|
|
101
110
|
config,
|
|
@@ -107,7 +116,7 @@ export async function vsearch(
|
|
|
107
116
|
store,
|
|
108
117
|
config,
|
|
109
118
|
query,
|
|
110
|
-
filters: retrievalTraceFilters({ ...
|
|
119
|
+
filters: retrievalTraceFilters({ ...searchOptions, limit }),
|
|
111
120
|
pipeline: "vector",
|
|
112
121
|
indexName: options.indexName,
|
|
113
122
|
modelUris: [modelUri],
|
|
@@ -152,7 +161,7 @@ export async function vsearch(
|
|
|
152
161
|
deps,
|
|
153
162
|
query,
|
|
154
163
|
queryEmbedding,
|
|
155
|
-
{ ...
|
|
164
|
+
{ ...searchOptions, limit, projectAffinity, traceSession }
|
|
156
165
|
);
|
|
157
166
|
if (!result.ok) {
|
|
158
167
|
await traceSession?.finish("failed");
|
package/src/cli/options.ts
CHANGED
|
@@ -5,6 +5,10 @@
|
|
|
5
5
|
* @module src/cli/options
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
import {
|
|
9
|
+
normalizeProjectAffinityValues,
|
|
10
|
+
ProjectAffinityInputError,
|
|
11
|
+
} from "../core/project-affinity-surface";
|
|
8
12
|
import { CliError } from "./errors";
|
|
9
13
|
|
|
10
14
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -13,6 +17,41 @@ import { CliError } from "./errors";
|
|
|
13
17
|
|
|
14
18
|
export type OutputFormat = "terminal" | "json" | "files" | "csv" | "md" | "xml";
|
|
15
19
|
|
|
20
|
+
export interface CliProjectAffinityOptions {
|
|
21
|
+
projectAffinityDisabled: boolean;
|
|
22
|
+
projectRoots: string[];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const collectRepeatableValue = (
|
|
26
|
+
value: string,
|
|
27
|
+
previous: string[] = []
|
|
28
|
+
): string[] => [...previous, value];
|
|
29
|
+
|
|
30
|
+
export const parseCliProjectAffinityOptions = (
|
|
31
|
+
options: Record<string, unknown>
|
|
32
|
+
): CliProjectAffinityOptions => {
|
|
33
|
+
try {
|
|
34
|
+
const projectRoots = normalizeProjectAffinityValues(
|
|
35
|
+
Array.isArray(options.projectRoot)
|
|
36
|
+
? (options.projectRoot as string[])
|
|
37
|
+
: undefined,
|
|
38
|
+
"project roots"
|
|
39
|
+
);
|
|
40
|
+
const projectAffinityDisabled = options.projectAffinity === false;
|
|
41
|
+
if (projectAffinityDisabled && projectRoots.length > 0) {
|
|
42
|
+
throw new ProjectAffinityInputError(
|
|
43
|
+
"--no-project-affinity cannot be combined with --project-root"
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
return { projectAffinityDisabled, projectRoots };
|
|
47
|
+
} catch (error) {
|
|
48
|
+
throw new CliError(
|
|
49
|
+
"VALIDATION",
|
|
50
|
+
error instanceof Error ? error.message : "Invalid project affinity input"
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
|
|
16
55
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
17
56
|
// Format Support Matrix (per spec/cli.md)
|
|
18
57
|
// ─────────────────────────────────────────────────────────────────────────────
|