@elmundi/ship-cli 0.14.1 → 0.15.3
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 +17 -16
- package/bin/shipctl.mjs +4 -80
- package/lib/commands/feedback.mjs +1 -1
- package/lib/commands/help.mjs +47 -131
- package/lib/commands/init.mjs +17 -250
- package/lib/commands/knowledge.mjs +25 -328
- package/lib/commands/preflight.mjs +213 -0
- package/lib/commands/run.mjs +277 -119
- package/lib/commands/trigger.mjs +95 -10
- package/lib/config/schema.mjs +68 -11
- package/lib/http.mjs +0 -2
- package/lib/runtime/routines.mjs +34 -0
- package/lib/templates.mjs +2 -2
- package/lib/verify/checks/agents-on-disk.mjs +5 -28
- package/lib/verify/registry.mjs +7 -8
- package/package.json +1 -1
- package/lib/artifacts/fs-index.mjs +0 -230
- package/lib/cache/store.mjs +0 -422
- package/lib/commands/bootstrap.mjs +0 -4
- package/lib/commands/callback.mjs +0 -742
- package/lib/commands/docs.mjs +0 -90
- package/lib/commands/kickoff.mjs +0 -192
- package/lib/commands/lanes.mjs +0 -566
- package/lib/commands/manifest-catalog.mjs +0 -251
- package/lib/commands/migrate.mjs +0 -204
- package/lib/commands/new.mjs +0 -452
- package/lib/commands/patterns.mjs +0 -160
- package/lib/commands/process.mjs +0 -388
- package/lib/commands/search.mjs +0 -43
- package/lib/commands/sync.mjs +0 -824
- package/lib/config/migrate.mjs +0 -223
- package/lib/find-ship-root.mjs +0 -75
- package/lib/process/specialist-prompt-contract.mjs +0 -171
- package/lib/state/lockfile.mjs +0 -180
- package/lib/vendor/run-agent.workflow.yml +0 -254
- package/lib/verify/checks/artifacts-up-to-date.mjs +0 -78
- package/lib/verify/checks/cache-integrity.mjs +0 -51
- package/lib/verify/checks/gitignore-cache.mjs +0 -51
- package/lib/verify/checks/rules-markers.mjs +0 -135
package/lib/commands/process.mjs
DELETED
|
@@ -1,388 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
|
|
4
|
-
import { readConfig } from "../config/io.mjs";
|
|
5
|
-
import { CONFIG_SCHEMA_VERSION, validateConfig } from "../config/schema.mjs";
|
|
6
|
-
import { apiGet } from "../http.mjs";
|
|
7
|
-
import {
|
|
8
|
-
buildSpecialistPromptBundle,
|
|
9
|
-
renderSpecialistPromptBundleMarkdown,
|
|
10
|
-
} from "../process/specialist-prompt-contract.mjs";
|
|
11
|
-
|
|
12
|
-
export async function processCommand(ctx, rest) {
|
|
13
|
-
const [sub, ...args] = rest;
|
|
14
|
-
if (!sub || sub === "help" || sub === "-h" || sub === "--help") {
|
|
15
|
-
printProcessHelp();
|
|
16
|
-
return;
|
|
17
|
-
}
|
|
18
|
-
if (sub === "prompt" || sub === "bundle") {
|
|
19
|
-
await processPromptCommand(ctx, args);
|
|
20
|
-
return;
|
|
21
|
-
}
|
|
22
|
-
if (sub === "tickets") {
|
|
23
|
-
await processTicketsCommand(ctx, args);
|
|
24
|
-
return;
|
|
25
|
-
}
|
|
26
|
-
console.error(
|
|
27
|
-
`Unknown 'shipctl process' subcommand: ${sub}\nRun: shipctl process --help`,
|
|
28
|
-
);
|
|
29
|
-
process.exit(1);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function printProcessHelp() {
|
|
33
|
-
console.log(`shipctl process — assemble Process/FSM specialist context
|
|
34
|
-
|
|
35
|
-
USAGE
|
|
36
|
-
shipctl process prompt --state <state-id> [--ticket-json <json>] [--ticket-file <path>]
|
|
37
|
-
[--ticket-id <id>] [--ticket-title <title>] [--ticket-url <url>]
|
|
38
|
-
[--ticket-description <text>] [--policies-file <path>]
|
|
39
|
-
[--cwd <dir>] [--json]
|
|
40
|
-
shipctl process prompt --specialist <specialist-id> [...]
|
|
41
|
-
shipctl process tickets --workspace <id> [--process <id>] [--tracker <kind>]
|
|
42
|
-
[--query <text>] [--state open|closed|all] [--limit <n>]
|
|
43
|
-
[--project-hint <hint>] [--assignee-me] [--assignee <user>]
|
|
44
|
-
[--json]
|
|
45
|
-
|
|
46
|
-
WHAT IT EMITS
|
|
47
|
-
A specialist prompt bundle assembled from .ship/config.yml:
|
|
48
|
-
- process and state identity
|
|
49
|
-
- specialist template fields from the selected state
|
|
50
|
-
- ticket context supplied by the caller / tracker picker
|
|
51
|
-
- allowed outgoing FSM transitions from process.transitions
|
|
52
|
-
- workspace policies supplied by --policies-file
|
|
53
|
-
- mandatory knowledge-first and Ship-boundary guardrails
|
|
54
|
-
|
|
55
|
-
The command does not mutate tracker tickets, execute transitions, or write to
|
|
56
|
-
the repository. It prepares context for an agent runtime; Ship remains
|
|
57
|
-
responsible for side effects, FSM validation, audit logging, and PR-only writes.`);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
async function processTicketsCommand(ctx, args) {
|
|
61
|
-
const opts = parseTicketsArgs(args);
|
|
62
|
-
const baseUrl = resolveWorkspaceBaseUrl(opts.baseUrl || ctx.baseUrl);
|
|
63
|
-
const token = process.env.SHIP_API_TOKEN || "";
|
|
64
|
-
if (!token) {
|
|
65
|
-
console.error("SHIP_API_TOKEN is required for shipctl process tickets.");
|
|
66
|
-
process.exit(1);
|
|
67
|
-
}
|
|
68
|
-
const processId = opts.processId || "development";
|
|
69
|
-
const params = new URLSearchParams();
|
|
70
|
-
for (const [key, value] of Object.entries({
|
|
71
|
-
tracker: opts.tracker,
|
|
72
|
-
project_hint: opts.projectHint,
|
|
73
|
-
state: opts.state,
|
|
74
|
-
query: opts.query,
|
|
75
|
-
assignee: opts.assignee,
|
|
76
|
-
limit: opts.limit,
|
|
77
|
-
})) {
|
|
78
|
-
if (value !== null && value !== undefined && value !== "") {
|
|
79
|
-
params.set(key, String(value));
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
if (opts.assigneeMe) params.set("assignee_me", "true");
|
|
83
|
-
const suffix = params.toString() ? `?${params}` : "";
|
|
84
|
-
const data = await apiGet(
|
|
85
|
-
baseUrl,
|
|
86
|
-
`/v1/workspaces/${encodeURIComponent(opts.workspace)}/processes/${encodeURIComponent(processId)}/tickets${suffix}`,
|
|
87
|
-
);
|
|
88
|
-
if (ctx.json || opts.json) {
|
|
89
|
-
console.log(JSON.stringify(data, null, 2));
|
|
90
|
-
return;
|
|
91
|
-
}
|
|
92
|
-
const tickets = Array.isArray(data?.tickets) ? data.tickets : [];
|
|
93
|
-
console.log(`Tracker: ${data?.tracker || opts.tracker || "(auto)"}`);
|
|
94
|
-
if (!tickets.length) {
|
|
95
|
-
console.log("No tickets matched.");
|
|
96
|
-
return;
|
|
97
|
-
}
|
|
98
|
-
for (const ticket of tickets) {
|
|
99
|
-
const id = ticket.key || ticket.display_id || ticket.id || "(no id)";
|
|
100
|
-
const title = ticket.title || "(untitled)";
|
|
101
|
-
const status = ticket.status ? ` [${ticket.status}]` : "";
|
|
102
|
-
const url = ticket.url ? `\n ${ticket.url}` : "";
|
|
103
|
-
console.log(`- ${id}${status}: ${title}${url}`);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
async function processPromptCommand(ctx, args) {
|
|
108
|
-
const opts = parsePromptArgs(args);
|
|
109
|
-
const cwd = opts.cwd || process.cwd();
|
|
110
|
-
let config;
|
|
111
|
-
try {
|
|
112
|
-
config = readConfig(cwd).config;
|
|
113
|
-
} catch (err) {
|
|
114
|
-
die(err instanceof Error ? err.message : String(err));
|
|
115
|
-
}
|
|
116
|
-
if (config.version !== CONFIG_SCHEMA_VERSION) {
|
|
117
|
-
die(
|
|
118
|
-
`.ship/config.yml is at v${config.version}; shipctl process prompt requires v${CONFIG_SCHEMA_VERSION}.\nRun 'shipctl migrate' to upgrade.`,
|
|
119
|
-
);
|
|
120
|
-
}
|
|
121
|
-
const validation = validateConfig(config);
|
|
122
|
-
if (!validation.ok) {
|
|
123
|
-
die(["config is invalid:", ...validation.errors.map((e) => ` - ${e}`)].join("\n"));
|
|
124
|
-
}
|
|
125
|
-
const processConfig = config.process;
|
|
126
|
-
if (!processConfig || typeof processConfig !== "object") {
|
|
127
|
-
die("process: missing from .ship/config.yml");
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
const state = selectState(processConfig, opts);
|
|
131
|
-
const allowedTransitions = Array.isArray(processConfig.transitions)
|
|
132
|
-
? processConfig.transitions.filter((transition) => transition.from === state.id)
|
|
133
|
-
: [];
|
|
134
|
-
const bundle = buildSpecialistPromptBundle({
|
|
135
|
-
process: processConfig,
|
|
136
|
-
state,
|
|
137
|
-
allowedTransitions,
|
|
138
|
-
ticket: resolveTicket(opts),
|
|
139
|
-
policies: readOptionalFile(opts.policiesFile, "policies"),
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
if (ctx.json || opts.json) {
|
|
143
|
-
console.log(JSON.stringify(bundle, null, 2));
|
|
144
|
-
return;
|
|
145
|
-
}
|
|
146
|
-
process.stdout.write(renderSpecialistPromptBundleMarkdown(bundle));
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
function parsePromptArgs(args) {
|
|
150
|
-
const out = {
|
|
151
|
-
state: null,
|
|
152
|
-
specialist: null,
|
|
153
|
-
cwd: null,
|
|
154
|
-
json: false,
|
|
155
|
-
ticketJson: null,
|
|
156
|
-
ticketFile: null,
|
|
157
|
-
ticket: {},
|
|
158
|
-
policiesFile: null,
|
|
159
|
-
};
|
|
160
|
-
const copy = [...args];
|
|
161
|
-
const readValue = (flag) => {
|
|
162
|
-
const current = copy.shift();
|
|
163
|
-
if (current === flag) {
|
|
164
|
-
if (copy.length === 0) die(`${flag} requires a value`);
|
|
165
|
-
return String(copy.shift());
|
|
166
|
-
}
|
|
167
|
-
const prefix = `${flag}=`;
|
|
168
|
-
if (current && current.startsWith(prefix)) {
|
|
169
|
-
return current.slice(prefix.length);
|
|
170
|
-
}
|
|
171
|
-
copy.unshift(current);
|
|
172
|
-
return null;
|
|
173
|
-
};
|
|
174
|
-
|
|
175
|
-
while (copy.length) {
|
|
176
|
-
const arg = copy[0];
|
|
177
|
-
if (arg === "--help" || arg === "-h") {
|
|
178
|
-
printProcessHelp();
|
|
179
|
-
process.exit(0);
|
|
180
|
-
}
|
|
181
|
-
if (arg === "--json") {
|
|
182
|
-
copy.shift();
|
|
183
|
-
out.json = true;
|
|
184
|
-
continue;
|
|
185
|
-
}
|
|
186
|
-
const state = readValue("--state");
|
|
187
|
-
if (state !== null) {
|
|
188
|
-
out.state = state;
|
|
189
|
-
continue;
|
|
190
|
-
}
|
|
191
|
-
const specialist = readValue("--specialist");
|
|
192
|
-
if (specialist !== null) {
|
|
193
|
-
out.specialist = specialist;
|
|
194
|
-
continue;
|
|
195
|
-
}
|
|
196
|
-
const cwd = readValue("--cwd");
|
|
197
|
-
if (cwd !== null) {
|
|
198
|
-
out.cwd = path.resolve(cwd);
|
|
199
|
-
continue;
|
|
200
|
-
}
|
|
201
|
-
const ticketJson = readValue("--ticket-json");
|
|
202
|
-
if (ticketJson !== null) {
|
|
203
|
-
out.ticketJson = ticketJson;
|
|
204
|
-
continue;
|
|
205
|
-
}
|
|
206
|
-
const ticketFile = readValue("--ticket-file");
|
|
207
|
-
if (ticketFile !== null) {
|
|
208
|
-
out.ticketFile = path.resolve(ticketFile);
|
|
209
|
-
continue;
|
|
210
|
-
}
|
|
211
|
-
const policiesFile = readValue("--policies-file");
|
|
212
|
-
if (policiesFile !== null) {
|
|
213
|
-
out.policiesFile = path.resolve(policiesFile);
|
|
214
|
-
continue;
|
|
215
|
-
}
|
|
216
|
-
for (const [flag, key] of [
|
|
217
|
-
["--ticket-id", "id"],
|
|
218
|
-
["--ticket-key", "key"],
|
|
219
|
-
["--ticket-title", "title"],
|
|
220
|
-
["--ticket-url", "url"],
|
|
221
|
-
["--ticket-status", "status"],
|
|
222
|
-
["--ticket-description", "description"],
|
|
223
|
-
]) {
|
|
224
|
-
const value = readValue(flag);
|
|
225
|
-
if (value !== null) {
|
|
226
|
-
out.ticket[key] = value;
|
|
227
|
-
break;
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
if (copy[0] === arg) {
|
|
231
|
-
die(`unknown argument: ${arg}\nRun: shipctl process prompt --help`);
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
if (!out.state && !out.specialist) {
|
|
235
|
-
die("either --state <state-id> or --specialist <specialist-id> is required");
|
|
236
|
-
}
|
|
237
|
-
if (out.state && out.specialist) {
|
|
238
|
-
die("use either --state or --specialist, not both");
|
|
239
|
-
}
|
|
240
|
-
return out;
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
function parseTicketsArgs(args) {
|
|
244
|
-
const out = {
|
|
245
|
-
workspace: null,
|
|
246
|
-
processId: "development",
|
|
247
|
-
tracker: null,
|
|
248
|
-
projectHint: null,
|
|
249
|
-
query: null,
|
|
250
|
-
state: "open",
|
|
251
|
-
limit: 10,
|
|
252
|
-
assigneeMe: false,
|
|
253
|
-
assignee: null,
|
|
254
|
-
baseUrl: null,
|
|
255
|
-
json: false,
|
|
256
|
-
};
|
|
257
|
-
const copy = [...args];
|
|
258
|
-
const readValue = (flag) => {
|
|
259
|
-
const current = copy.shift();
|
|
260
|
-
if (current === flag) {
|
|
261
|
-
if (copy.length === 0) die(`${flag} requires a value`);
|
|
262
|
-
return String(copy.shift());
|
|
263
|
-
}
|
|
264
|
-
const prefix = `${flag}=`;
|
|
265
|
-
if (current && current.startsWith(prefix)) {
|
|
266
|
-
return current.slice(prefix.length);
|
|
267
|
-
}
|
|
268
|
-
copy.unshift(current);
|
|
269
|
-
return null;
|
|
270
|
-
};
|
|
271
|
-
while (copy.length) {
|
|
272
|
-
const arg = copy[0];
|
|
273
|
-
if (arg === "--help" || arg === "-h") {
|
|
274
|
-
printProcessHelp();
|
|
275
|
-
process.exit(0);
|
|
276
|
-
}
|
|
277
|
-
if (arg === "--json") {
|
|
278
|
-
copy.shift();
|
|
279
|
-
out.json = true;
|
|
280
|
-
continue;
|
|
281
|
-
}
|
|
282
|
-
if (arg === "--assignee-me") {
|
|
283
|
-
copy.shift();
|
|
284
|
-
out.assigneeMe = true;
|
|
285
|
-
continue;
|
|
286
|
-
}
|
|
287
|
-
for (const [flag, key] of [
|
|
288
|
-
["--workspace", "workspace"],
|
|
289
|
-
["--process", "processId"],
|
|
290
|
-
["--tracker", "tracker"],
|
|
291
|
-
["--project-hint", "projectHint"],
|
|
292
|
-
["--query", "query"],
|
|
293
|
-
["--state", "state"],
|
|
294
|
-
["--limit", "limit"],
|
|
295
|
-
["--assignee", "assignee"],
|
|
296
|
-
["--base-url", "baseUrl"],
|
|
297
|
-
]) {
|
|
298
|
-
const value = readValue(flag);
|
|
299
|
-
if (value !== null) {
|
|
300
|
-
out[key] = key === "limit" ? Number(value) : value;
|
|
301
|
-
break;
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
if (copy[0] === arg) {
|
|
305
|
-
die(`unknown argument: ${arg}\nRun: shipctl process tickets --help`);
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
if (!out.workspace) die("--workspace <id> is required");
|
|
309
|
-
if (!["open", "closed", "all"].includes(out.state)) {
|
|
310
|
-
die("--state must be one of open|closed|all");
|
|
311
|
-
}
|
|
312
|
-
if (!Number.isInteger(out.limit) || out.limit < 1 || out.limit > 25) {
|
|
313
|
-
die("--limit must be an integer between 1 and 25");
|
|
314
|
-
}
|
|
315
|
-
return out;
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
function selectState(processConfig, opts) {
|
|
319
|
-
const states = Array.isArray(processConfig.states) ? processConfig.states : [];
|
|
320
|
-
if (opts.state) {
|
|
321
|
-
const state = states.find((item) => item.id === opts.state);
|
|
322
|
-
if (!state) die(`unknown process state ${JSON.stringify(opts.state)}`);
|
|
323
|
-
return state;
|
|
324
|
-
}
|
|
325
|
-
const state = states.find((item) => {
|
|
326
|
-
const specialist = item.specialist;
|
|
327
|
-
if (typeof specialist === "string") return specialist === opts.specialist;
|
|
328
|
-
if (specialist && typeof specialist === "object") {
|
|
329
|
-
return specialist.id === opts.specialist || specialist.name === opts.specialist;
|
|
330
|
-
}
|
|
331
|
-
return false;
|
|
332
|
-
});
|
|
333
|
-
if (!state) die(`unknown process specialist ${JSON.stringify(opts.specialist)}`);
|
|
334
|
-
return state;
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
function resolveTicket(opts) {
|
|
338
|
-
const parts = [];
|
|
339
|
-
if (opts.ticketFile) {
|
|
340
|
-
const body = readOptionalFile(opts.ticketFile, "ticket");
|
|
341
|
-
if (body) {
|
|
342
|
-
parts.push(parseTicketJson(body, `ticket file ${opts.ticketFile}`));
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
if (opts.ticketJson) {
|
|
346
|
-
parts.push(parseTicketJson(opts.ticketJson, "--ticket-json"));
|
|
347
|
-
}
|
|
348
|
-
if (Object.keys(opts.ticket).length) {
|
|
349
|
-
parts.push(opts.ticket);
|
|
350
|
-
}
|
|
351
|
-
if (!parts.length) return null;
|
|
352
|
-
return Object.assign({}, ...parts);
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
function parseTicketJson(value, label) {
|
|
356
|
-
try {
|
|
357
|
-
const parsed = JSON.parse(value);
|
|
358
|
-
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
359
|
-
die(`${label}: must be a JSON object`);
|
|
360
|
-
}
|
|
361
|
-
return parsed;
|
|
362
|
-
} catch (err) {
|
|
363
|
-
die(`${label}: failed to parse JSON (${err instanceof Error ? err.message : err})`);
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
function readOptionalFile(filePath, label) {
|
|
368
|
-
if (!filePath) return null;
|
|
369
|
-
try {
|
|
370
|
-
return fs.readFileSync(filePath, "utf8");
|
|
371
|
-
} catch (err) {
|
|
372
|
-
die(`failed to read ${label} file ${filePath}: ${err instanceof Error ? err.message : err}`);
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
function resolveWorkspaceBaseUrl(explicit) {
|
|
377
|
-
return (
|
|
378
|
-
explicit ||
|
|
379
|
-
process.env.SHIP_WORKSPACE_API_BASE ||
|
|
380
|
-
process.env.SHIP_API_BASE ||
|
|
381
|
-
"https://api.ship.elmundi.com"
|
|
382
|
-
);
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
function die(message) {
|
|
386
|
-
console.error(message);
|
|
387
|
-
process.exit(1);
|
|
388
|
-
}
|
package/lib/commands/search.mjs
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import { apiPost } from "../http.mjs";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Vector search over methodology corpus (documentation, prompts, README).
|
|
5
|
-
* @param {{ baseUrl: string; json: boolean }} ctx
|
|
6
|
-
* @param {string[]} args query tokens (same line as `ship search …`)
|
|
7
|
-
*/
|
|
8
|
-
export async function searchCommand(ctx, args) {
|
|
9
|
-
if (!args.length || args[0] === "help" || args[0] === "-h" || args[0] === "--help") {
|
|
10
|
-
console.log(`Usage:
|
|
11
|
-
shipctl search <query> [--top-k 8]
|
|
12
|
-
|
|
13
|
-
POST /search on the methodology API (same SHIP_API_BASE as shipctl pattern/tool/…).
|
|
14
|
-
|
|
15
|
-
Global flags: --base-url URL --json`);
|
|
16
|
-
return;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const qParts = [];
|
|
20
|
-
let topK = 8;
|
|
21
|
-
for (let i = 0; i < args.length; i++) {
|
|
22
|
-
const a = args[i];
|
|
23
|
-
if (a === "--top-k" && args[i + 1]) {
|
|
24
|
-
topK = Number(args[++i]);
|
|
25
|
-
continue;
|
|
26
|
-
}
|
|
27
|
-
qParts.push(a);
|
|
28
|
-
}
|
|
29
|
-
const query = qParts.join(" ").trim();
|
|
30
|
-
if (query.length < 3) {
|
|
31
|
-
console.error("search: query must be at least 3 characters.");
|
|
32
|
-
process.exit(1);
|
|
33
|
-
}
|
|
34
|
-
const data = await apiPost(ctx.baseUrl, "/search", { query, top_k: topK });
|
|
35
|
-
if (ctx.json) console.log(JSON.stringify(data, null, 2));
|
|
36
|
-
else {
|
|
37
|
-
console.log(`Query: ${data.query}\n`);
|
|
38
|
-
for (const r of data.results || []) {
|
|
39
|
-
console.log(`- ${r.path} (chunk ${r.chunk_index}, distance ${r.distance ?? "n/a"})`);
|
|
40
|
-
console.log(` ${r.snippet}\n`);
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
}
|