@longshot/cli 0.0.1

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/dist/store.js ADDED
@@ -0,0 +1,612 @@
1
+ import { readFile, writeFile, appendFile, mkdir, readdir, unlink, stat } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import { existsSync, renameSync } from "node:fs";
4
+ import { getPrefix, formatIdForFile, getSeqNumber, isLegacyId, getIdPrefix, setProfileDataDir } from "./profile.js";
5
+ let DATA_DIR = join(import.meta.dirname, "..", ".longshot");
6
+ let TASKS_DIR = join(DATA_DIR, "tasks");
7
+ /** Override data directory (for testing) */
8
+ export function setDataDir(dir) {
9
+ DATA_DIR = dir;
10
+ TASKS_DIR = join(dir, "tasks");
11
+ RUNS_DIR = join(dir, "runs");
12
+ QUEUE_PATH = join(dir, "queue.json");
13
+ setProfileDataDir(dir);
14
+ }
15
+ async function ensureDataDir() {
16
+ if (!existsSync(DATA_DIR)) {
17
+ // Auto-migrate from old data/ directory if it exists
18
+ const oldDir = join(DATA_DIR, "..", "data");
19
+ if (existsSync(oldDir) && (existsSync(join(oldDir, "tasks.json")) || existsSync(join(oldDir, "spec.md")))) {
20
+ renameSync(oldDir, DATA_DIR);
21
+ console.log("Migrated data/ → .longshot/");
22
+ }
23
+ else {
24
+ await mkdir(DATA_DIR, { recursive: true });
25
+ }
26
+ }
27
+ }
28
+ async function ensureTasksDir() {
29
+ await ensureDataDir();
30
+ if (!existsSync(TASKS_DIR)) {
31
+ await mkdir(TASKS_DIR, { recursive: true });
32
+ }
33
+ }
34
+ async function ensureArchivesDir() {
35
+ const dir = join(DATA_DIR, "archives");
36
+ if (!existsSync(dir)) {
37
+ await mkdir(dir, { recursive: true });
38
+ }
39
+ return dir;
40
+ }
41
+ // --- Spec ---
42
+ export async function readSpec() {
43
+ await ensureDataDir();
44
+ const path = join(DATA_DIR, "spec.md");
45
+ if (!existsSync(path))
46
+ return "";
47
+ return readFile(path, "utf-8");
48
+ }
49
+ export async function writeSpec(content) {
50
+ await ensureDataDir();
51
+ await writeFile(join(DATA_DIR, "spec.md"), content, "utf-8");
52
+ }
53
+ // --- History ---
54
+ export async function appendHistory(entry) {
55
+ await ensureDataDir();
56
+ const line = JSON.stringify(entry) + "\n";
57
+ await appendFile(join(DATA_DIR, "history.jsonl"), line, "utf-8");
58
+ }
59
+ export async function readHistory() {
60
+ await ensureDataDir();
61
+ const path = join(DATA_DIR, "history.jsonl");
62
+ if (!existsSync(path))
63
+ return [];
64
+ const content = await readFile(path, "utf-8");
65
+ return content
66
+ .trim()
67
+ .split("\n")
68
+ .filter(Boolean)
69
+ .map((line) => JSON.parse(line));
70
+ }
71
+ const HISTORY_MAX = 500;
72
+ const HISTORY_KEEP = 300;
73
+ export async function truncateHistoryIfNeeded() {
74
+ const entries = await readHistory();
75
+ if (entries.length <= HISTORY_MAX)
76
+ return;
77
+ const archiveDir = await ensureArchivesDir();
78
+ const toArchive = entries.slice(0, entries.length - HISTORY_KEEP);
79
+ const toKeep = entries.slice(entries.length - HISTORY_KEEP);
80
+ const ts = new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19);
81
+ await writeFile(join(archiveDir, `history-${ts}.jsonl`), toArchive.map((e) => JSON.stringify(e)).join("\n") + "\n", "utf-8");
82
+ await writeFile(join(DATA_DIR, "history.jsonl"), toKeep.map((e) => JSON.stringify(e)).join("\n") + "\n", "utf-8");
83
+ }
84
+ // --- Conversation (per-prefix) ---
85
+ function conversationPath() {
86
+ const prefix = getPrefix();
87
+ return join(DATA_DIR, `conversation-${prefix}.json`);
88
+ }
89
+ export async function readConversation() {
90
+ await ensureDataDir();
91
+ const path = conversationPath();
92
+ if (!existsSync(path))
93
+ return [];
94
+ const content = await readFile(path, "utf-8");
95
+ return JSON.parse(content);
96
+ }
97
+ export async function saveConversation(messages) {
98
+ await ensureDataDir();
99
+ await writeFile(conversationPath(), JSON.stringify(messages, null, 2), "utf-8");
100
+ }
101
+ export async function clearConversation() {
102
+ await ensureDataDir();
103
+ await writeFile(conversationPath(), "[]", "utf-8");
104
+ }
105
+ const CONVERSATION_MAX = 100;
106
+ const CONVERSATION_KEEP = 50;
107
+ export async function archiveConversationIfNeeded(messages) {
108
+ if (messages.length <= CONVERSATION_MAX)
109
+ return messages;
110
+ const archiveDir = await ensureArchivesDir();
111
+ const toArchive = messages.slice(0, messages.length - CONVERSATION_KEEP);
112
+ const toKeep = messages.slice(messages.length - CONVERSATION_KEEP);
113
+ const prefix = getPrefix();
114
+ const ts = new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19);
115
+ await writeFile(join(archiveDir, `conversation-${prefix}-${ts}.json`), JSON.stringify(toArchive, null, 2), "utf-8");
116
+ await saveConversation(toKeep);
117
+ return toKeep;
118
+ }
119
+ // --- Tasks ---
120
+ async function readTaskIndex() {
121
+ await ensureTasksDir();
122
+ const path = join(DATA_DIR, "tasks.json");
123
+ if (!existsSync(path))
124
+ return [];
125
+ const content = await readFile(path, "utf-8");
126
+ return JSON.parse(content);
127
+ }
128
+ async function writeTaskIndex(tasks) {
129
+ await ensureTasksDir();
130
+ await writeFile(join(DATA_DIR, "tasks.json"), JSON.stringify(tasks, null, 2), "utf-8");
131
+ }
132
+ export async function listTasks() {
133
+ return readTaskIndex();
134
+ }
135
+ export async function getTask(id) {
136
+ const tasks = await readTaskIndex();
137
+ // Support both prefixed IDs and legacy numeric IDs
138
+ const meta = tasks.find((t) => t.id === id || (isLegacyId(id) && String(t.id) === id));
139
+ if (!meta)
140
+ return null;
141
+ const specPath = join(TASKS_DIR, `${formatIdForFile(meta.id)}-${meta.slug}.md`);
142
+ const spec = existsSync(specPath) ? await readFile(specPath, "utf-8") : "";
143
+ return { meta, spec };
144
+ }
145
+ export async function createTask(title, slug) {
146
+ const tasks = await readTaskIndex();
147
+ const prefix = getPrefix();
148
+ // Find next sequence number for this prefix
149
+ const prefixTasks = tasks.filter((t) => {
150
+ const idPrefix = getIdPrefix(t.id);
151
+ return idPrefix === prefix;
152
+ });
153
+ const maxSeq = prefixTasks.length > 0
154
+ ? Math.max(...prefixTasks.map((t) => getSeqNumber(t.id)))
155
+ : 0;
156
+ const seq = maxSeq + 1;
157
+ const id = `${prefix}-${seq}`;
158
+ const now = new Date().toISOString();
159
+ const meta = {
160
+ id,
161
+ slug,
162
+ title,
163
+ status: "drafting",
164
+ created: now,
165
+ updated: now,
166
+ };
167
+ tasks.push(meta);
168
+ await writeTaskIndex(tasks);
169
+ // Create empty task spec file
170
+ const specPath = join(TASKS_DIR, `${formatIdForFile(id)}-${slug}.md`);
171
+ await writeFile(specPath, `# ${title}\n\nStatus: drafting\n\n`, "utf-8");
172
+ return meta;
173
+ }
174
+ export async function updateTaskStatus(id, status, checkpoint) {
175
+ const tasks = await readTaskIndex();
176
+ const task = tasks.find((t) => t.id === id || (isLegacyId(id) && String(t.id) === id));
177
+ if (!task)
178
+ return;
179
+ task.status = status;
180
+ task.updated = new Date().toISOString();
181
+ if (status === "complete") {
182
+ task.completed = new Date().toISOString();
183
+ }
184
+ if (checkpoint) {
185
+ task.checkpoint = checkpoint;
186
+ }
187
+ await writeTaskIndex(tasks);
188
+ }
189
+ export function hasRealSpec(spec) {
190
+ const lines = spec.split('\n').filter(line => {
191
+ const trimmed = line.trim();
192
+ return trimmed && !trimmed.startsWith('# ') && !trimmed.startsWith('Status:');
193
+ });
194
+ return lines.length > 0;
195
+ }
196
+ export async function readTaskSpec(id) {
197
+ const task = await getTask(id);
198
+ return task?.spec || "";
199
+ }
200
+ export async function writeTaskSpec(id, content) {
201
+ const tasks = await readTaskIndex();
202
+ const meta = tasks.find((t) => t.id === id || (isLegacyId(id) && String(t.id) === id));
203
+ if (!meta)
204
+ return;
205
+ const specPath = join(TASKS_DIR, `${formatIdForFile(meta.id)}-${meta.slug}.md`);
206
+ await writeFile(specPath, content, "utf-8");
207
+ }
208
+ export function getActiveTask(tasks) {
209
+ return tasks.find((t) => t.status === "in_progress" || t.status === "ready" || t.status === "queued" || t.status === "approved" || t.status === "refining" || t.status === "fixing" || t.status === "conflict");
210
+ }
211
+ // --- Runs ---
212
+ let RUNS_DIR = join(DATA_DIR, "runs");
213
+ async function ensureRunsDir() {
214
+ await ensureDataDir();
215
+ if (!existsSync(RUNS_DIR)) {
216
+ await mkdir(RUNS_DIR, { recursive: true });
217
+ }
218
+ }
219
+ export function generateRunId() {
220
+ const now = new Date();
221
+ const ts = now.toISOString().replace(/[:.]/g, "-").slice(0, 19);
222
+ const suffix = crypto.randomUUID().slice(0, 8);
223
+ return `${ts}-${suffix}`;
224
+ }
225
+ export async function appendRunEvent(runId, event) {
226
+ await ensureRunsDir();
227
+ const path = join(RUNS_DIR, `${runId}.jsonl`);
228
+ const line = JSON.stringify(event) + "\n";
229
+ await appendFile(path, line, "utf-8");
230
+ }
231
+ export async function readRunLog(runId) {
232
+ await ensureRunsDir();
233
+ const path = join(RUNS_DIR, `${runId}.jsonl`);
234
+ if (!existsSync(path))
235
+ return [];
236
+ const content = await readFile(path, "utf-8");
237
+ return content
238
+ .trim()
239
+ .split("\n")
240
+ .filter(Boolean)
241
+ .map((line) => JSON.parse(line));
242
+ }
243
+ export async function listRuns() {
244
+ await ensureRunsDir();
245
+ const files = await readdir(RUNS_DIR);
246
+ return files
247
+ .filter((f) => f.endsWith(".jsonl"))
248
+ .map((f) => f.replace(".jsonl", ""))
249
+ .sort()
250
+ .reverse();
251
+ }
252
+ const RUN_MAX_AGE_DAYS = 7;
253
+ export async function cleanOldRuns() {
254
+ await ensureRunsDir();
255
+ const files = await readdir(RUNS_DIR);
256
+ const cutoff = Date.now() - RUN_MAX_AGE_DAYS * 24 * 60 * 60 * 1000;
257
+ let deleted = 0;
258
+ for (const f of files) {
259
+ if (!f.endsWith(".jsonl"))
260
+ continue;
261
+ const filePath = join(RUNS_DIR, f);
262
+ try {
263
+ const s = await stat(filePath);
264
+ if (s.mtimeMs < cutoff) {
265
+ await unlink(filePath);
266
+ deleted++;
267
+ }
268
+ }
269
+ catch { }
270
+ }
271
+ return deleted;
272
+ }
273
+ // --- Task Conversations ---
274
+ async function taskSubDir(taskId) {
275
+ const tasksPath = join(DATA_DIR, "tasks.json");
276
+ let slug = "unknown";
277
+ if (existsSync(tasksPath)) {
278
+ const content = await readFile(tasksPath, "utf-8");
279
+ const tasks = JSON.parse(content);
280
+ const meta = tasks.find((t) => t.id === taskId || (isLegacyId(taskId) && String(t.id) === taskId));
281
+ if (meta) {
282
+ slug = meta.slug;
283
+ taskId = meta.id; // Normalize to actual stored ID
284
+ }
285
+ }
286
+ return join(TASKS_DIR, `${formatIdForFile(taskId)}-${slug}`);
287
+ }
288
+ async function ensureTaskSubDir(taskId) {
289
+ const dir = await taskSubDir(taskId);
290
+ if (!existsSync(dir)) {
291
+ await mkdir(dir, { recursive: true });
292
+ }
293
+ return dir;
294
+ }
295
+ export async function readTaskConversation(taskId) {
296
+ const dir = await taskSubDir(taskId);
297
+ const path = join(dir, "conversation.json");
298
+ if (!existsSync(path))
299
+ return [];
300
+ try {
301
+ const content = await readFile(path, "utf-8");
302
+ return JSON.parse(content);
303
+ }
304
+ catch {
305
+ return [];
306
+ }
307
+ }
308
+ export async function saveTaskConversation(taskId, messages) {
309
+ const dir = await ensureTaskSubDir(taskId);
310
+ await writeFile(join(dir, "conversation.json"), JSON.stringify(messages, null, 2), "utf-8");
311
+ }
312
+ export async function archiveTaskConversationIfNeeded(taskId, messages) {
313
+ if (messages.length <= CONVERSATION_MAX)
314
+ return messages;
315
+ const archiveDir = await ensureArchivesDir();
316
+ const toArchive = messages.slice(0, messages.length - CONVERSATION_KEEP);
317
+ const toKeep = messages.slice(messages.length - CONVERSATION_KEEP);
318
+ const ts = new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19);
319
+ await writeFile(join(archiveDir, `task-${taskId}-conversation-${ts}.json`), JSON.stringify(toArchive, null, 2), "utf-8");
320
+ await saveTaskConversation(taskId, toKeep);
321
+ return toKeep;
322
+ }
323
+ export function getTaskConversationPath(taskId, slug) {
324
+ return join(TASKS_DIR, `${formatIdForFile(taskId)}-${slug}`, "conversation.json");
325
+ }
326
+ export function getTaskSpecPath(taskId, slug) {
327
+ return join(TASKS_DIR, `${formatIdForFile(taskId)}-${slug}.md`);
328
+ }
329
+ // --- Task Reports ---
330
+ export async function getTaskReport(taskId) {
331
+ const dir = await taskSubDir(taskId);
332
+ const path = join(dir, "report.md");
333
+ if (!existsSync(path))
334
+ return null;
335
+ try {
336
+ return await readFile(path, "utf-8");
337
+ }
338
+ catch {
339
+ return null;
340
+ }
341
+ }
342
+ export async function saveTaskReport(taskId, content) {
343
+ const dir = await ensureTaskSubDir(taskId);
344
+ await writeFile(join(dir, "report.md"), content, "utf-8");
345
+ }
346
+ async function verifyDir(taskId) {
347
+ const tasksPath = join(DATA_DIR, "tasks.json");
348
+ let slug = "unknown";
349
+ let resolvedId = taskId;
350
+ if (existsSync(tasksPath)) {
351
+ const content = await readFile(tasksPath, "utf-8");
352
+ const tasks = JSON.parse(content);
353
+ const meta = tasks.find((t) => t.id === taskId || (isLegacyId(taskId) && String(t.id) === taskId));
354
+ if (meta) {
355
+ slug = meta.slug;
356
+ resolvedId = meta.id;
357
+ }
358
+ }
359
+ return join(TASKS_DIR, `${formatIdForFile(resolvedId)}-${slug}`, "verify");
360
+ }
361
+ export async function ensureVerifyDir(taskId) {
362
+ const dir = await verifyDir(taskId);
363
+ if (!existsSync(dir)) {
364
+ await mkdir(dir, { recursive: true });
365
+ }
366
+ return dir;
367
+ }
368
+ export async function nextVerifyRunNumber(taskId) {
369
+ const dir = await verifyDir(taskId);
370
+ if (!existsSync(dir))
371
+ return 1;
372
+ const files = await readdir(dir);
373
+ const nums = files
374
+ .filter((f) => f.match(/^run-\d+-result\.json$/))
375
+ .map((f) => parseInt(f.match(/^run-(\d+)/)?.[1] || "0", 10));
376
+ // Also count running ones (jsonl without result)
377
+ const runningNums = files
378
+ .filter((f) => f.match(/^run-\d+\.jsonl$/))
379
+ .map((f) => parseInt(f.match(/^run-(\d+)/)?.[1] || "0", 10));
380
+ const allNums = [...nums, ...runningNums];
381
+ return allNums.length > 0 ? Math.max(...allNums) + 1 : 1;
382
+ }
383
+ export async function appendVerifyEvent(taskId, runNumber, event) {
384
+ const dir = await ensureVerifyDir(taskId);
385
+ const path = join(dir, `run-${String(runNumber).padStart(3, "0")}.jsonl`);
386
+ const line = JSON.stringify(event) + "\n";
387
+ await appendFile(path, line, "utf-8");
388
+ }
389
+ export async function readVerifyLog(taskId, runNumber) {
390
+ const dir = await verifyDir(taskId);
391
+ const path = join(dir, `run-${String(runNumber).padStart(3, "0")}.jsonl`);
392
+ if (!existsSync(path))
393
+ return [];
394
+ const content = await readFile(path, "utf-8");
395
+ return content
396
+ .trim()
397
+ .split("\n")
398
+ .filter(Boolean)
399
+ .map((line) => JSON.parse(line));
400
+ }
401
+ export async function saveVerifyResult(taskId, result) {
402
+ const dir = await ensureVerifyDir(taskId);
403
+ const path = join(dir, `run-${String(result.runNumber).padStart(3, "0")}-result.json`);
404
+ await writeFile(path, JSON.stringify(result, null, 2), "utf-8");
405
+ }
406
+ export async function readVerifyResult(taskId, runNumber) {
407
+ const dir = await verifyDir(taskId);
408
+ const path = join(dir, `run-${String(runNumber).padStart(3, "0")}-result.json`);
409
+ if (!existsSync(path))
410
+ return null;
411
+ const content = await readFile(path, "utf-8");
412
+ return JSON.parse(content);
413
+ }
414
+ export async function listVerifyResults(taskId) {
415
+ const dir = await verifyDir(taskId);
416
+ if (!existsSync(dir))
417
+ return [];
418
+ const files = await readdir(dir);
419
+ const results = [];
420
+ for (const f of files.filter((f) => f.match(/^run-\d+-result\.json$/)).sort()) {
421
+ const content = await readFile(join(dir, f), "utf-8");
422
+ results.push(JSON.parse(content));
423
+ }
424
+ return results;
425
+ }
426
+ let QUEUE_PATH = join(DATA_DIR, "queue.json");
427
+ export async function readQueue() {
428
+ await ensureDataDir();
429
+ if (!existsSync(QUEUE_PATH))
430
+ return [];
431
+ try {
432
+ const content = await readFile(QUEUE_PATH, "utf-8");
433
+ return JSON.parse(content);
434
+ }
435
+ catch {
436
+ return [];
437
+ }
438
+ }
439
+ export async function saveQueue(items) {
440
+ await ensureDataDir();
441
+ await writeFile(QUEUE_PATH, JSON.stringify(items, null, 2), "utf-8");
442
+ }
443
+ export async function addQueueItem(item) {
444
+ const queue = await readQueue();
445
+ const newItem = {
446
+ ...item,
447
+ id: crypto.randomUUID(),
448
+ status: "pending",
449
+ createdAt: new Date().toISOString(),
450
+ };
451
+ queue.push(newItem);
452
+ await saveQueue(queue);
453
+ return newItem;
454
+ }
455
+ export async function updateQueueItem(id, update) {
456
+ const queue = await readQueue();
457
+ const item = queue.find((q) => q.id === id);
458
+ if (!item)
459
+ return;
460
+ Object.assign(item, update);
461
+ await saveQueue(queue);
462
+ }
463
+ export async function removeQueueItem(id) {
464
+ const queue = await readQueue();
465
+ const idx = queue.findIndex((q) => q.id === id);
466
+ if (idx === -1)
467
+ return false;
468
+ if (queue[idx].status !== "pending")
469
+ return false;
470
+ queue.splice(idx, 1);
471
+ await saveQueue(queue);
472
+ return true;
473
+ }
474
+ export async function cleanOldQueueItems() {
475
+ const queue = await readQueue();
476
+ const cutoff = Date.now() - 24 * 60 * 60 * 1000;
477
+ const filtered = queue.filter((item) => {
478
+ if (item.status === "pending" || item.status === "running")
479
+ return true;
480
+ const completed = item.completedAt
481
+ ? new Date(item.completedAt).getTime()
482
+ : 0;
483
+ return completed > cutoff;
484
+ });
485
+ if (filtered.length !== queue.length) {
486
+ await saveQueue(filtered);
487
+ }
488
+ }
489
+ export function getQueueCounts(queue) {
490
+ let pending = 0;
491
+ let running = 0;
492
+ for (const item of queue) {
493
+ if (item.status === "pending")
494
+ pending++;
495
+ if (item.status === "running")
496
+ running++;
497
+ }
498
+ return { pending, running };
499
+ }
500
+ export async function readServices() {
501
+ await ensureDataDir();
502
+ const path = join(DATA_DIR, "services.json");
503
+ if (!existsSync(path))
504
+ return [];
505
+ try {
506
+ const content = await readFile(path, "utf-8");
507
+ return JSON.parse(content);
508
+ }
509
+ catch {
510
+ return [];
511
+ }
512
+ }
513
+ export async function saveServices(services) {
514
+ await ensureDataDir();
515
+ await writeFile(join(DATA_DIR, "services.json"), JSON.stringify(services, null, 2), "utf-8");
516
+ }
517
+ export async function createService(name, command, cwd) {
518
+ const services = await readServices();
519
+ const service = {
520
+ id: crypto.randomUUID().slice(0, 8),
521
+ name,
522
+ command,
523
+ cwd,
524
+ createdAt: new Date().toISOString(),
525
+ };
526
+ services.push(service);
527
+ await saveServices(services);
528
+ return service;
529
+ }
530
+ export async function updateService(id, update) {
531
+ const services = await readServices();
532
+ const service = services.find((s) => s.id === id);
533
+ if (!service)
534
+ return false;
535
+ if (update.name !== undefined)
536
+ service.name = update.name;
537
+ if (update.command !== undefined)
538
+ service.command = update.command;
539
+ if (update.cwd !== undefined)
540
+ service.cwd = update.cwd;
541
+ await saveServices(services);
542
+ return true;
543
+ }
544
+ export async function deleteService(id) {
545
+ const services = await readServices();
546
+ const idx = services.findIndex((s) => s.id === id);
547
+ if (idx === -1)
548
+ return false;
549
+ services.splice(idx, 1);
550
+ await saveServices(services);
551
+ return true;
552
+ }
553
+ export async function getService(id) {
554
+ const services = await readServices();
555
+ return services.find((s) => s.id === id) || null;
556
+ }
557
+ export function getServicesLogDir() {
558
+ return join(DATA_DIR, "services");
559
+ }
560
+ export async function ensureServicesLogDir() {
561
+ const dir = getServicesLogDir();
562
+ if (!existsSync(dir)) {
563
+ await mkdir(dir, { recursive: true });
564
+ }
565
+ return dir;
566
+ }
567
+ export async function readBranches() {
568
+ await ensureDataDir();
569
+ const path = join(DATA_DIR, "branches.json");
570
+ if (!existsSync(path))
571
+ return { active: null, branches: [] };
572
+ try {
573
+ const content = await readFile(path, "utf-8");
574
+ return JSON.parse(content);
575
+ }
576
+ catch {
577
+ return { active: null, branches: [] };
578
+ }
579
+ }
580
+ export async function saveBranches(state) {
581
+ await ensureDataDir();
582
+ await writeFile(join(DATA_DIR, "branches.json"), JSON.stringify(state, null, 2), "utf-8");
583
+ }
584
+ // --- MCP Config ---
585
+ export async function readMcpConfig() {
586
+ const path = join(DATA_DIR, "mcp-config.json");
587
+ if (!existsSync(path))
588
+ return null;
589
+ try {
590
+ const content = await readFile(path, "utf-8");
591
+ return JSON.parse(content);
592
+ }
593
+ catch {
594
+ return null;
595
+ }
596
+ }
597
+ export async function listVerifyArtifacts(taskId, runNumber) {
598
+ const dir = await verifyDir(taskId);
599
+ if (!existsSync(dir))
600
+ return [];
601
+ const pfx = `run-${String(runNumber).padStart(3, "0")}-`;
602
+ const files = await readdir(dir);
603
+ return files.filter((f) => f.startsWith(pfx) && !f.endsWith(".jsonl") && !f.endsWith("-result.json"));
604
+ }
605
+ // --- Re-export profile helpers ---
606
+ export { getPrefix, getSeqNumber, isLegacyId, getIdPrefix, formatIdForFile } from "./profile.js";
607
+ /** Get display-friendly ID (sequence number only) for UI */
608
+ export function getDisplayId(id) {
609
+ if (isLegacyId(id))
610
+ return id;
611
+ return String(getSeqNumber(id));
612
+ }