@outcomeeng/spx 0.6.9 → 0.6.11
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 -0
- package/dist/cli.js +1317 -336
- package/dist/cli.js.map +1 -1
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -95,7 +95,7 @@ var DEFAULT_CLI_IO = {
|
|
|
95
95
|
import { inspect } from "util";
|
|
96
96
|
|
|
97
97
|
// src/commands/agent/resume.ts
|
|
98
|
-
import {
|
|
98
|
+
import { open, readdir, stat } from "fs/promises";
|
|
99
99
|
import { homedir } from "os";
|
|
100
100
|
|
|
101
101
|
// src/domains/agent/protocol.ts
|
|
@@ -118,17 +118,23 @@ var AGENT_SESSION_STORE = {
|
|
|
118
118
|
CODEX_SESSIONS_DIR: "sessions",
|
|
119
119
|
CLAUDE_DIR: ".claude",
|
|
120
120
|
CLAUDE_PROJECTS_DIR: "projects",
|
|
121
|
+
CLAUDE_SUBAGENTS_DIR: "subagents",
|
|
121
122
|
JSONL_EXTENSION: ".jsonl",
|
|
122
123
|
TEXT_ENCODING: "utf8"
|
|
123
124
|
};
|
|
124
125
|
var AGENT_RESUME_LIMITS = {
|
|
125
126
|
RECENT_DAYS: 7,
|
|
126
|
-
|
|
127
|
+
PER_AGENT_DISPLAYED_CANDIDATES: 5,
|
|
128
|
+
METADATA_HEAD_BYTES: 131072,
|
|
127
129
|
HOURS_PER_DAY: 24,
|
|
128
130
|
MINUTES_PER_HOUR: 60,
|
|
129
131
|
SECONDS_PER_MINUTE: 60,
|
|
130
132
|
MILLISECONDS_PER_SECOND: 1e3,
|
|
131
|
-
|
|
133
|
+
READ_CONCURRENCY: 8
|
|
134
|
+
};
|
|
135
|
+
var AGENT_RESUME_SCOPE = {
|
|
136
|
+
WORKTREE: "worktree",
|
|
137
|
+
BRANCH: "branch"
|
|
132
138
|
};
|
|
133
139
|
var AGENT_RESUME_MODE = {
|
|
134
140
|
PICK: "pick",
|
|
@@ -151,6 +157,8 @@ var AGENT_SESSION_JSON_FIELDS = {
|
|
|
151
157
|
SESSION_ID_CAMEL: "sessionId",
|
|
152
158
|
ID: "id",
|
|
153
159
|
PAYLOAD: "payload",
|
|
160
|
+
GIT: "git",
|
|
161
|
+
BRANCH: "branch",
|
|
154
162
|
GIT_BRANCH: "gitBranch",
|
|
155
163
|
THREAD_SOURCE: "thread_source"
|
|
156
164
|
};
|
|
@@ -164,10 +172,19 @@ var CODEX_SESSION_ORIGINATOR = {
|
|
|
164
172
|
VSCODE_HYPHEN: "codex-vscode",
|
|
165
173
|
EXEC: "codex-exec"
|
|
166
174
|
};
|
|
175
|
+
var CODEX_SESSION_THREAD_SOURCE = {
|
|
176
|
+
SUBAGENT: "subagent"
|
|
177
|
+
};
|
|
167
178
|
var AGENT_RESUME_RECENT_WINDOW_MS = AGENT_RESUME_LIMITS.RECENT_DAYS * AGENT_RESUME_LIMITS.HOURS_PER_DAY * AGENT_RESUME_LIMITS.MINUTES_PER_HOUR * AGENT_RESUME_LIMITS.SECONDS_PER_MINUTE * AGENT_RESUME_LIMITS.MILLISECONDS_PER_SECOND;
|
|
168
179
|
|
|
169
180
|
// src/domains/agent/resume.ts
|
|
170
|
-
import { resolve as resolve3 } from "path";
|
|
181
|
+
import { isAbsolute, relative, resolve as resolve3, sep } from "path";
|
|
182
|
+
function worktreeResumeScope() {
|
|
183
|
+
return { kind: AGENT_RESUME_SCOPE.WORKTREE };
|
|
184
|
+
}
|
|
185
|
+
function branchResumeScope(branch) {
|
|
186
|
+
return { kind: AGENT_RESUME_SCOPE.BRANCH, branch };
|
|
187
|
+
}
|
|
171
188
|
var AGENT_RESUME_PICKER_ACTION = {
|
|
172
189
|
MOVE_UP: "move-up",
|
|
173
190
|
MOVE_DOWN: "move-down",
|
|
@@ -227,34 +244,56 @@ function codexSessionStoreDir(homeDir) {
|
|
|
227
244
|
function claudeCodeSessionStoreDir(homeDir) {
|
|
228
245
|
return resolve3(homeDir, AGENT_SESSION_STORE.CLAUDE_DIR, AGENT_SESSION_STORE.CLAUDE_PROJECTS_DIR);
|
|
229
246
|
}
|
|
247
|
+
var CLAUDE_PROJECT_PATH_SEPARATORS = /[/\\]/g;
|
|
248
|
+
var CLAUDE_PROJECT_ENCODED_SEPARATOR = "-";
|
|
249
|
+
function claudeProjectDirName(cwd) {
|
|
250
|
+
return cwd.replace(CLAUDE_PROJECT_PATH_SEPARATORS, CLAUDE_PROJECT_ENCODED_SEPARATOR);
|
|
251
|
+
}
|
|
230
252
|
async function discoverAgentResumeCandidates(options) {
|
|
231
|
-
const
|
|
232
|
-
if (
|
|
253
|
+
const scope2 = await resolveAgentResumeScopeContext(options);
|
|
254
|
+
if (scope2 === null) {
|
|
233
255
|
return [];
|
|
234
256
|
}
|
|
235
|
-
const
|
|
236
|
-
|
|
237
|
-
|
|
257
|
+
const cap = AGENT_RESUME_LIMITS.PER_AGENT_DISPLAYED_CANDIDATES;
|
|
258
|
+
const [codex, claude] = await Promise.all([
|
|
259
|
+
collectAgentCandidates(
|
|
260
|
+
AGENT_SESSION_KIND.CODEX,
|
|
261
|
+
await recentStoreFiles(
|
|
262
|
+
await collectJsonlFiles(codexSessionStoreDir(options.homeDir), options.fs),
|
|
263
|
+
options.fs,
|
|
264
|
+
options.nowMs
|
|
265
|
+
),
|
|
238
266
|
options.fs,
|
|
239
|
-
|
|
267
|
+
cap,
|
|
268
|
+
scope2.match,
|
|
269
|
+
parseCodexHead
|
|
240
270
|
),
|
|
241
|
-
|
|
242
|
-
|
|
271
|
+
collectAgentCandidates(
|
|
272
|
+
AGENT_SESSION_KIND.CLAUDE_CODE,
|
|
273
|
+
await recentStoreFiles(
|
|
274
|
+
await claudeTranscriptFiles(claudeCodeSessionStoreDir(options.homeDir), options.fs, scope2.claudeDirAccepts),
|
|
275
|
+
options.fs,
|
|
276
|
+
options.nowMs
|
|
277
|
+
),
|
|
243
278
|
options.fs,
|
|
244
|
-
|
|
279
|
+
cap,
|
|
280
|
+
scope2.match,
|
|
281
|
+
parseClaudeHead
|
|
245
282
|
)
|
|
246
|
-
];
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
283
|
+
]);
|
|
284
|
+
return [...codex, ...claude].sort(compareCandidates);
|
|
285
|
+
}
|
|
286
|
+
async function resolveAgentResumeScopeContext(options) {
|
|
287
|
+
if (options.scope.kind === AGENT_RESUME_SCOPE.BRANCH) {
|
|
288
|
+
const target = options.scope.branch;
|
|
289
|
+
return { match: (core) => core.branch === target, claudeDirAccepts: () => true };
|
|
290
|
+
}
|
|
291
|
+
const invocationRoot = await options.resolveWorktreeRoot(options.invocationDir);
|
|
292
|
+
const projectPrefix = claudeProjectDirName(invocationRoot);
|
|
293
|
+
return {
|
|
294
|
+
match: (core) => isPathInsideOrEqual(invocationRoot, core.cwd),
|
|
295
|
+
claudeDirAccepts: (dirName) => dirName === projectPrefix || dirName.startsWith(`${projectPrefix}${CLAUDE_PROJECT_ENCODED_SEPARATOR}`)
|
|
296
|
+
};
|
|
258
297
|
}
|
|
259
298
|
function buildAgentResumeLaunchCommand(candidate) {
|
|
260
299
|
if (candidate.agent === AGENT_SESSION_KIND.CODEX) {
|
|
@@ -282,28 +321,49 @@ function renderAgentResumeList(candidates) {
|
|
|
282
321
|
function renderAgentResumeJson(candidates) {
|
|
283
322
|
return JSON.stringify(candidates, null, 2);
|
|
284
323
|
}
|
|
285
|
-
async function
|
|
286
|
-
|
|
324
|
+
async function recentStoreFiles(paths, fs8, nowMs) {
|
|
325
|
+
const stats = await mapWithConcurrency(paths, AGENT_RESUME_LIMITS.READ_CONCURRENCY, async (path7) => {
|
|
326
|
+
const stat9 = await fs8.stat(path7).catch(() => null);
|
|
327
|
+
if (stat9 === null || !isRecentAgentSessionMtime(stat9.mtimeMs, nowMs)) {
|
|
328
|
+
return null;
|
|
329
|
+
}
|
|
330
|
+
return { path: path7, modifiedAtMs: stat9.mtimeMs };
|
|
331
|
+
});
|
|
332
|
+
return stats.filter((file) => file !== null).sort((left, right) => right.modifiedAtMs - left.modifiedAtMs || left.path.localeCompare(right.path));
|
|
287
333
|
}
|
|
288
|
-
async function
|
|
289
|
-
|
|
334
|
+
async function claudeTranscriptFiles(root, fs8, dirAccepts) {
|
|
335
|
+
const projectDirs = (await fs8.readDir(root).catch(() => [])).filter((entry) => entry.isDirectory && dirAccepts(entry.name)).map((entry) => resolve3(root, entry.name));
|
|
336
|
+
const perDir = await mapWithConcurrency(projectDirs, AGENT_RESUME_LIMITS.READ_CONCURRENCY, async (dir) => {
|
|
337
|
+
const entries = await fs8.readDir(dir).catch(() => []);
|
|
338
|
+
return entries.filter((entry) => entry.isFile && entry.name.endsWith(AGENT_SESSION_STORE.JSONL_EXTENSION)).map((entry) => resolve3(dir, entry.name));
|
|
339
|
+
});
|
|
340
|
+
return perDir.flat();
|
|
290
341
|
}
|
|
291
|
-
async function
|
|
292
|
-
const
|
|
342
|
+
async function collectAgentCandidates(agent, files, fs8, cap, match, parseHead) {
|
|
343
|
+
const seen = /* @__PURE__ */ new Set();
|
|
293
344
|
const candidates = [];
|
|
294
345
|
for (const file of files) {
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
continue;
|
|
346
|
+
if (candidates.length >= cap) {
|
|
347
|
+
break;
|
|
298
348
|
}
|
|
299
|
-
const
|
|
300
|
-
if (
|
|
349
|
+
const head = await fs8.readHead(file.path, AGENT_RESUME_LIMITS.METADATA_HEAD_BYTES).catch(() => null);
|
|
350
|
+
if (head === null) {
|
|
301
351
|
continue;
|
|
302
352
|
}
|
|
303
|
-
const
|
|
304
|
-
if (
|
|
305
|
-
|
|
353
|
+
const core = parseHead(head);
|
|
354
|
+
if (core === null || !core.interactive || !match(core) || seen.has(core.sessionId)) {
|
|
355
|
+
continue;
|
|
306
356
|
}
|
|
357
|
+
seen.add(core.sessionId);
|
|
358
|
+
candidates.push({
|
|
359
|
+
agent,
|
|
360
|
+
sessionId: core.sessionId,
|
|
361
|
+
cwd: core.cwd,
|
|
362
|
+
sourcePath: file.path,
|
|
363
|
+
modifiedAtMs: file.modifiedAtMs,
|
|
364
|
+
updatedAt: core.updatedAt,
|
|
365
|
+
branch: core.branch
|
|
366
|
+
});
|
|
307
367
|
}
|
|
308
368
|
return candidates;
|
|
309
369
|
}
|
|
@@ -323,58 +383,61 @@ async function collectJsonlFiles(root, fs8) {
|
|
|
323
383
|
function isRecentAgentSessionMtime(modifiedAtMs, nowMs) {
|
|
324
384
|
return modifiedAtMs <= nowMs && nowMs - modifiedAtMs <= AGENT_RESUME_RECENT_WINDOW_MS;
|
|
325
385
|
}
|
|
326
|
-
function
|
|
327
|
-
|
|
328
|
-
let cwd = null;
|
|
329
|
-
let updatedAt = null;
|
|
330
|
-
let isInteractiveSession = false;
|
|
331
|
-
for (const line of content.split("\n")) {
|
|
386
|
+
function parseCodexHead(head) {
|
|
387
|
+
for (const line of head.split("\n")) {
|
|
332
388
|
const row = parseJsonObject(line);
|
|
333
389
|
if (row === null) {
|
|
334
390
|
continue;
|
|
335
391
|
}
|
|
336
|
-
|
|
337
|
-
|
|
392
|
+
if (firstString(row, [[AGENT_SESSION_JSON_FIELDS.TYPE]]) !== AGENT_SESSION_ROW_TYPE.CODEX_SESSION_META) {
|
|
393
|
+
continue;
|
|
394
|
+
}
|
|
395
|
+
const sessionId = firstString(row, [
|
|
338
396
|
[AGENT_SESSION_JSON_FIELDS.SESSION_ID],
|
|
339
397
|
[AGENT_SESSION_JSON_FIELDS.ID],
|
|
340
398
|
[AGENT_SESSION_JSON_FIELDS.PAYLOAD, AGENT_SESSION_JSON_FIELDS.SESSION_ID],
|
|
341
399
|
[AGENT_SESSION_JSON_FIELDS.PAYLOAD, AGENT_SESSION_JSON_FIELDS.ID]
|
|
342
400
|
]);
|
|
343
|
-
cwd = firstString(row, [
|
|
401
|
+
const cwd = firstString(row, [
|
|
344
402
|
[AGENT_SESSION_JSON_FIELDS.CWD],
|
|
345
403
|
[AGENT_SESSION_JSON_FIELDS.PAYLOAD, AGENT_SESSION_JSON_FIELDS.CWD]
|
|
346
|
-
])
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
404
|
+
]);
|
|
405
|
+
if (sessionId === null || cwd === null) {
|
|
406
|
+
return null;
|
|
407
|
+
}
|
|
408
|
+
const originator = firstString(row, [
|
|
409
|
+
[AGENT_SESSION_JSON_FIELDS.ORIGINATOR],
|
|
410
|
+
[AGENT_SESSION_JSON_FIELDS.PAYLOAD, AGENT_SESSION_JSON_FIELDS.ORIGINATOR]
|
|
411
|
+
]);
|
|
412
|
+
const threadSource = firstString(row, [
|
|
413
|
+
[AGENT_SESSION_JSON_FIELDS.THREAD_SOURCE],
|
|
414
|
+
[AGENT_SESSION_JSON_FIELDS.PAYLOAD, AGENT_SESSION_JSON_FIELDS.THREAD_SOURCE]
|
|
415
|
+
]);
|
|
416
|
+
return {
|
|
417
|
+
sessionId,
|
|
418
|
+
cwd,
|
|
419
|
+
branch: firstString(row, [
|
|
420
|
+
[AGENT_SESSION_JSON_FIELDS.PAYLOAD, AGENT_SESSION_JSON_FIELDS.GIT, AGENT_SESSION_JSON_FIELDS.BRANCH],
|
|
421
|
+
[AGENT_SESSION_JSON_FIELDS.GIT, AGENT_SESSION_JSON_FIELDS.BRANCH]
|
|
422
|
+
]),
|
|
423
|
+
updatedAt: firstString(row, [[AGENT_SESSION_JSON_FIELDS.TIMESTAMP]]),
|
|
424
|
+
interactive: isCodexInteractive(originator, threadSource)
|
|
425
|
+
};
|
|
351
426
|
}
|
|
352
|
-
return
|
|
353
|
-
agent: AGENT_SESSION_KIND.CODEX,
|
|
354
|
-
sessionId,
|
|
355
|
-
cwd,
|
|
356
|
-
sourcePath,
|
|
357
|
-
modifiedAtMs,
|
|
358
|
-
updatedAt,
|
|
359
|
-
branch: null
|
|
360
|
-
};
|
|
427
|
+
return null;
|
|
361
428
|
}
|
|
362
|
-
function
|
|
363
|
-
if (
|
|
429
|
+
function isCodexInteractive(originator, threadSource) {
|
|
430
|
+
if (threadSource === CODEX_SESSION_THREAD_SOURCE.SUBAGENT) {
|
|
364
431
|
return false;
|
|
365
432
|
}
|
|
366
|
-
const originator = firstString(row, [
|
|
367
|
-
[AGENT_SESSION_JSON_FIELDS.ORIGINATOR],
|
|
368
|
-
[AGENT_SESSION_JSON_FIELDS.PAYLOAD, AGENT_SESSION_JSON_FIELDS.ORIGINATOR]
|
|
369
|
-
]);
|
|
370
433
|
return originator === CODEX_SESSION_ORIGINATOR.TUI || originator === CODEX_SESSION_ORIGINATOR.CLI || originator === CODEX_SESSION_ORIGINATOR.VSCODE || originator === CODEX_SESSION_ORIGINATOR.VSCODE_HYPHEN;
|
|
371
434
|
}
|
|
372
|
-
function
|
|
435
|
+
function parseClaudeHead(head) {
|
|
373
436
|
let sessionId = null;
|
|
374
437
|
let cwd = null;
|
|
375
|
-
let updatedAt = null;
|
|
376
438
|
let branch = null;
|
|
377
|
-
|
|
439
|
+
let updatedAt = null;
|
|
440
|
+
for (const line of head.split("\n")) {
|
|
378
441
|
const row = parseJsonObject(line);
|
|
379
442
|
if (row === null) {
|
|
380
443
|
continue;
|
|
@@ -385,25 +448,20 @@ function parseClaudeCodeCandidateFile(sourcePath, content, modifiedAtMs) {
|
|
|
385
448
|
[AGENT_SESSION_JSON_FIELDS.PAYLOAD, AGENT_SESSION_JSON_FIELDS.SESSION_ID_CAMEL],
|
|
386
449
|
[AGENT_SESSION_JSON_FIELDS.PAYLOAD, AGENT_SESSION_JSON_FIELDS.SESSION_ID]
|
|
387
450
|
]);
|
|
388
|
-
cwd
|
|
451
|
+
cwd ??= firstString(row, [
|
|
389
452
|
[AGENT_SESSION_JSON_FIELDS.CWD],
|
|
390
453
|
[AGENT_SESSION_JSON_FIELDS.PAYLOAD, AGENT_SESSION_JSON_FIELDS.CWD]
|
|
391
|
-
])
|
|
392
|
-
updatedAt
|
|
393
|
-
branch
|
|
454
|
+
]);
|
|
455
|
+
updatedAt ??= firstString(row, [[AGENT_SESSION_JSON_FIELDS.TIMESTAMP]]);
|
|
456
|
+
branch ??= firstString(row, [[AGENT_SESSION_JSON_FIELDS.GIT_BRANCH]]);
|
|
457
|
+
if (sessionId !== null && cwd !== null && branch !== null) {
|
|
458
|
+
break;
|
|
459
|
+
}
|
|
394
460
|
}
|
|
395
461
|
if (sessionId === null || cwd === null) {
|
|
396
462
|
return null;
|
|
397
463
|
}
|
|
398
|
-
return {
|
|
399
|
-
agent: AGENT_SESSION_KIND.CLAUDE_CODE,
|
|
400
|
-
sessionId,
|
|
401
|
-
cwd,
|
|
402
|
-
sourcePath,
|
|
403
|
-
modifiedAtMs,
|
|
404
|
-
updatedAt,
|
|
405
|
-
branch
|
|
406
|
-
};
|
|
464
|
+
return { sessionId, cwd, branch, updatedAt, interactive: true };
|
|
407
465
|
}
|
|
408
466
|
function parseJsonObject(line) {
|
|
409
467
|
const trimmed = line.trim();
|
|
@@ -439,15 +497,6 @@ function valueAtPath(row, path7) {
|
|
|
439
497
|
function isRecord(value) {
|
|
440
498
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
441
499
|
}
|
|
442
|
-
function maxIsoTimestamp(left, right) {
|
|
443
|
-
if (right === null) {
|
|
444
|
-
return left;
|
|
445
|
-
}
|
|
446
|
-
if (left === null) {
|
|
447
|
-
return right;
|
|
448
|
-
}
|
|
449
|
-
return Date.parse(right) > Date.parse(left) ? right : left;
|
|
450
|
-
}
|
|
451
500
|
function compareCandidates(left, right) {
|
|
452
501
|
const modifiedDiff = right.modifiedAtMs - left.modifiedAtMs;
|
|
453
502
|
if (modifiedDiff !== 0) {
|
|
@@ -455,8 +504,9 @@ function compareCandidates(left, right) {
|
|
|
455
504
|
}
|
|
456
505
|
return `${left.agent}:${left.sessionId}`.localeCompare(`${right.agent}:${right.sessionId}`);
|
|
457
506
|
}
|
|
458
|
-
function
|
|
459
|
-
|
|
507
|
+
function isPathInsideOrEqual(parent, child) {
|
|
508
|
+
const rel = relative(resolve3(parent), resolve3(child));
|
|
509
|
+
return rel.length === 0 || !isAbsolute(rel) && rel !== ".." && !rel.startsWith(`..${sep}`);
|
|
460
510
|
}
|
|
461
511
|
async function mapWithConcurrency(items, concurrency, mapper) {
|
|
462
512
|
const results = new Array(items.length);
|
|
@@ -476,9 +526,198 @@ async function mapWithConcurrency(items, concurrency, mapper) {
|
|
|
476
526
|
return results;
|
|
477
527
|
}
|
|
478
528
|
|
|
529
|
+
// src/domains/session/types.ts
|
|
530
|
+
var SESSION_PRIORITY = {
|
|
531
|
+
HIGH: "high",
|
|
532
|
+
MEDIUM: "medium",
|
|
533
|
+
LOW: "low"
|
|
534
|
+
};
|
|
535
|
+
var SESSION_STATUSES = ["todo", "doing", "archive"];
|
|
536
|
+
var DEFAULT_LIST_STATUSES = ["doing", "todo"];
|
|
537
|
+
var SESSION_FILE_ENCODING = "utf-8";
|
|
538
|
+
var SESSION_FILE_ERROR_CODE = {
|
|
539
|
+
NOT_FOUND: "ENOENT"
|
|
540
|
+
};
|
|
541
|
+
var SESSION_OUTPUT_MARKER = {
|
|
542
|
+
HANDOFF_ID: "HANDOFF_ID",
|
|
543
|
+
PICKUP_ID: "PICKUP_ID",
|
|
544
|
+
SESSION_FILE: "SESSION_FILE"
|
|
545
|
+
};
|
|
546
|
+
function formatSessionOutputMarker(marker, value) {
|
|
547
|
+
return `<${marker}>${value}</${marker}>`;
|
|
548
|
+
}
|
|
549
|
+
var CLAIMABLE_STATUS = SESSION_STATUSES[0];
|
|
550
|
+
var PRIORITY_ORDER = {
|
|
551
|
+
[SESSION_PRIORITY.HIGH]: 0,
|
|
552
|
+
[SESSION_PRIORITY.MEDIUM]: 1,
|
|
553
|
+
[SESSION_PRIORITY.LOW]: 2
|
|
554
|
+
};
|
|
555
|
+
var DEFAULT_PRIORITY = SESSION_PRIORITY.MEDIUM;
|
|
556
|
+
var SESSION_FRONT_MATTER = {
|
|
557
|
+
PRIORITY: "priority",
|
|
558
|
+
GIT_REF: "git_ref",
|
|
559
|
+
GOAL: "goal",
|
|
560
|
+
NEXT_STEP: "next_step",
|
|
561
|
+
CREATED_AT: "created_at",
|
|
562
|
+
AGENT_SESSION_ID: "agent_session_id",
|
|
563
|
+
SPECS: "specs",
|
|
564
|
+
FILES: "files"
|
|
565
|
+
};
|
|
566
|
+
|
|
567
|
+
// src/domains/agent/search.ts
|
|
568
|
+
var AGENT_SEARCH_DEFAULT_LIMIT = 20;
|
|
569
|
+
var AGENT_SEARCH_MATCH_REASON = {
|
|
570
|
+
ALL: "all",
|
|
571
|
+
PICKUP_ID: "pickup-id",
|
|
572
|
+
CONTAINS: "contains",
|
|
573
|
+
SESSION_ID: "session-id",
|
|
574
|
+
AGENT: "agent",
|
|
575
|
+
BRANCH: "branch"
|
|
576
|
+
};
|
|
577
|
+
function pickupIdSearchLiteral(pickupId) {
|
|
578
|
+
return formatSessionOutputMarker(SESSION_OUTPUT_MARKER.PICKUP_ID, pickupId);
|
|
579
|
+
}
|
|
580
|
+
function agentSearchQueryFromOptions(options) {
|
|
581
|
+
const contentNeedles = [];
|
|
582
|
+
if (options.pickupId !== void 0) {
|
|
583
|
+
contentNeedles.push({
|
|
584
|
+
reason: AGENT_SEARCH_MATCH_REASON.PICKUP_ID,
|
|
585
|
+
value: pickupIdSearchLiteral(options.pickupId)
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
if (options.contains !== void 0) {
|
|
589
|
+
contentNeedles.push({ reason: AGENT_SEARCH_MATCH_REASON.CONTAINS, value: options.contains });
|
|
590
|
+
}
|
|
591
|
+
return {
|
|
592
|
+
contentNeedles,
|
|
593
|
+
sessionId: options.sessionId ?? null,
|
|
594
|
+
branch: options.branch ?? null,
|
|
595
|
+
agent: options.agent ?? null,
|
|
596
|
+
includeAll: options.all === true,
|
|
597
|
+
limit: options.limit ?? AGENT_SEARCH_DEFAULT_LIMIT
|
|
598
|
+
};
|
|
599
|
+
}
|
|
600
|
+
async function searchAgentSessions(options) {
|
|
601
|
+
const selectedAgents = options.query.agent === null ? [AGENT_SESSION_KIND.CODEX, AGENT_SESSION_KIND.CLAUDE_CODE] : [options.query.agent];
|
|
602
|
+
const perAgent = await Promise.all(
|
|
603
|
+
selectedAgents.map((agent) => searchAgentStore(agent, options))
|
|
604
|
+
);
|
|
605
|
+
return perAgent.flat().sort(compareSearchResults).slice(0, Math.max(0, options.query.limit));
|
|
606
|
+
}
|
|
607
|
+
function renderAgentSearchJson(results) {
|
|
608
|
+
return JSON.stringify(results, null, 2);
|
|
609
|
+
}
|
|
610
|
+
function renderAgentSearchList(results) {
|
|
611
|
+
if (results.length === 0) {
|
|
612
|
+
return "No matching agent sessions found.";
|
|
613
|
+
}
|
|
614
|
+
return results.map((result) => {
|
|
615
|
+
const updatedAt = result.updatedAt ?? new Date(result.modifiedAtMs).toISOString();
|
|
616
|
+
return `${updatedAt} ${AGENT_SESSION_LABEL[result.agent]} ${result.sessionId} ${result.cwd}`;
|
|
617
|
+
}).join("\n");
|
|
618
|
+
}
|
|
619
|
+
async function searchAgentStore(agent, options) {
|
|
620
|
+
const paths = agent === AGENT_SESSION_KIND.CODEX ? await collectJsonlFiles(codexSessionStoreDir(options.homeDir), options.fs) : await claudeTranscriptFiles(
|
|
621
|
+
claudeCodeSessionStoreDir(options.homeDir),
|
|
622
|
+
options.fs,
|
|
623
|
+
claudeDirAcceptsProductScope(options.productScopeRoot)
|
|
624
|
+
);
|
|
625
|
+
const files = await storeFiles(paths, options.fs, options.nowMs, options.query.includeAll);
|
|
626
|
+
const parser = agent === AGENT_SESSION_KIND.CODEX ? parseCodexHead : parseClaudeHead;
|
|
627
|
+
return collectMatchingSessions(agent, files, options, parser);
|
|
628
|
+
}
|
|
629
|
+
function claudeDirAcceptsProductScope(productScopeRoot) {
|
|
630
|
+
const projectPrefix = claudeProjectDirName(productScopeRoot);
|
|
631
|
+
return (dirName) => dirName === projectPrefix || dirName.startsWith(`${projectPrefix}${CLAUDE_PROJECT_ENCODED_SEPARATOR}`);
|
|
632
|
+
}
|
|
633
|
+
async function collectMatchingSessions(agent, files, options, parseHead) {
|
|
634
|
+
const results = [];
|
|
635
|
+
const seen = /* @__PURE__ */ new Set();
|
|
636
|
+
for (const file of files) {
|
|
637
|
+
const head = await options.fs.readHead(file.path, AGENT_RESUME_LIMITS.METADATA_HEAD_BYTES).catch(() => null);
|
|
638
|
+
if (head === null) continue;
|
|
639
|
+
const core = parseHead(head);
|
|
640
|
+
if (core === null || !core.interactive || seen.has(core.sessionId)) continue;
|
|
641
|
+
if (!coreMatchesProductScope(core, options.productScopeRoot)) continue;
|
|
642
|
+
const matches = await matchReasons(agent, core, file.path, options);
|
|
643
|
+
if (matches.length === 0) continue;
|
|
644
|
+
seen.add(core.sessionId);
|
|
645
|
+
results.push({
|
|
646
|
+
agent,
|
|
647
|
+
sessionId: core.sessionId,
|
|
648
|
+
cwd: core.cwd,
|
|
649
|
+
sourcePath: file.path,
|
|
650
|
+
modifiedAtMs: file.modifiedAtMs,
|
|
651
|
+
updatedAt: core.updatedAt,
|
|
652
|
+
branch: core.branch,
|
|
653
|
+
matches
|
|
654
|
+
});
|
|
655
|
+
}
|
|
656
|
+
return results;
|
|
657
|
+
}
|
|
658
|
+
async function matchReasons(agent, core, path7, options) {
|
|
659
|
+
if (!hasSearchSelector(options.query)) {
|
|
660
|
+
return [AGENT_SEARCH_MATCH_REASON.ALL];
|
|
661
|
+
}
|
|
662
|
+
const metadataMatches = metadataMatchReasons(agent, core, options.query);
|
|
663
|
+
const contentMatches = await contentMatchReasons(path7, options);
|
|
664
|
+
if (metadataMatches === null || contentMatches === null) {
|
|
665
|
+
return [];
|
|
666
|
+
}
|
|
667
|
+
return [...metadataMatches, ...contentMatches];
|
|
668
|
+
}
|
|
669
|
+
function hasSearchSelector(query) {
|
|
670
|
+
return query.contentNeedles.length > 0 || query.sessionId !== null || query.branch !== null || query.agent !== null;
|
|
671
|
+
}
|
|
672
|
+
function metadataMatchReasons(agent, core, query) {
|
|
673
|
+
const matches = [];
|
|
674
|
+
if (query.agent !== null) {
|
|
675
|
+
if (agent !== query.agent) return null;
|
|
676
|
+
matches.push(AGENT_SEARCH_MATCH_REASON.AGENT);
|
|
677
|
+
}
|
|
678
|
+
if (query.sessionId !== null && core.sessionId === query.sessionId) {
|
|
679
|
+
matches.push(AGENT_SEARCH_MATCH_REASON.SESSION_ID);
|
|
680
|
+
} else if (query.sessionId !== null) {
|
|
681
|
+
return null;
|
|
682
|
+
}
|
|
683
|
+
if (query.branch !== null) {
|
|
684
|
+
if (core.branch !== query.branch) return null;
|
|
685
|
+
matches.push(AGENT_SEARCH_MATCH_REASON.BRANCH);
|
|
686
|
+
}
|
|
687
|
+
return matches;
|
|
688
|
+
}
|
|
689
|
+
async function contentMatchReasons(path7, options) {
|
|
690
|
+
if (options.query.contentNeedles.length === 0) {
|
|
691
|
+
return [];
|
|
692
|
+
}
|
|
693
|
+
const content = await options.fs.readText(path7).catch(() => null);
|
|
694
|
+
return content === null ? null : matchingContentNeedles(content, options.query.contentNeedles);
|
|
695
|
+
}
|
|
696
|
+
function matchingContentNeedles(content, needles) {
|
|
697
|
+
const matches = needles.filter((needle) => content.includes(needle.value)).map((needle) => needle.reason);
|
|
698
|
+
return matches.length === needles.length ? matches : null;
|
|
699
|
+
}
|
|
700
|
+
function coreMatchesProductScope(core, productScopeRoot) {
|
|
701
|
+
return isPathInsideOrEqual(productScopeRoot, core.cwd);
|
|
702
|
+
}
|
|
703
|
+
async function storeFiles(paths, fs8, nowMs, includeAll) {
|
|
704
|
+
const files = await mapWithConcurrency(paths, AGENT_RESUME_LIMITS.READ_CONCURRENCY, async (path7) => {
|
|
705
|
+
const stat9 = await fs8.stat(path7).catch(() => null);
|
|
706
|
+
if (stat9 === null) return null;
|
|
707
|
+
if (!includeAll && !isRecentAgentSessionMtime(stat9.mtimeMs, nowMs)) return null;
|
|
708
|
+
return { path: path7, modifiedAtMs: stat9.mtimeMs };
|
|
709
|
+
});
|
|
710
|
+
return files.filter((file) => file !== null).sort((left, right) => right.modifiedAtMs - left.modifiedAtMs || left.path.localeCompare(right.path));
|
|
711
|
+
}
|
|
712
|
+
function compareSearchResults(left, right) {
|
|
713
|
+
const modifiedDiff = right.modifiedAtMs - left.modifiedAtMs;
|
|
714
|
+
if (modifiedDiff !== 0) return modifiedDiff;
|
|
715
|
+
return `${left.agent}:${left.sessionId}`.localeCompare(`${right.agent}:${right.sessionId}`);
|
|
716
|
+
}
|
|
717
|
+
|
|
479
718
|
// src/git/root.ts
|
|
480
719
|
import { execa } from "execa";
|
|
481
|
-
import { basename, dirname, isAbsolute, join, resolve as resolve4 } from "path";
|
|
720
|
+
import { basename, dirname, isAbsolute as isAbsolute2, join, resolve as resolve4 } from "path";
|
|
482
721
|
|
|
483
722
|
// src/git/environment.ts
|
|
484
723
|
function withoutGitEnvironment(env) {
|
|
@@ -659,7 +898,7 @@ async function detectGitCommonDirProductRoot(cwd = CONFIG_PROCESS_CWD.read(), de
|
|
|
659
898
|
};
|
|
660
899
|
}
|
|
661
900
|
const commonDir = extractStdout(commonDirResult.stdout);
|
|
662
|
-
const absoluteCommonDir =
|
|
901
|
+
const absoluteCommonDir = isAbsolute2(commonDir) ? commonDir : resolve4(toplevel, commonDir);
|
|
663
902
|
const gitCommonDirProductRoot = dirname(absoluteCommonDir);
|
|
664
903
|
return {
|
|
665
904
|
productDir: gitCommonDirProductRoot,
|
|
@@ -822,7 +1061,7 @@ async function gatherGitFacts(cwd = CONFIG_PROCESS_CWD.read(), deps = defaultGit
|
|
|
822
1061
|
};
|
|
823
1062
|
}
|
|
824
1063
|
const rawCommonDir = extractStdout(commonDirResult.stdout);
|
|
825
|
-
const commonDir =
|
|
1064
|
+
const commonDir = isAbsolute2(rawCommonDir) ? rawCommonDir : resolve4(worktreeRoot, rawCommonDir);
|
|
826
1065
|
const bareResult = await deps.execa(
|
|
827
1066
|
GIT_ROOT_COMMAND.EXECUTABLE,
|
|
828
1067
|
[...GIT_CORE_BARE_ARGS],
|
|
@@ -845,8 +1084,15 @@ var nodeAgentSessionFileSystem = {
|
|
|
845
1084
|
isFile: entry.isFile()
|
|
846
1085
|
}));
|
|
847
1086
|
},
|
|
848
|
-
async
|
|
849
|
-
|
|
1087
|
+
async readHead(path7, maxBytes) {
|
|
1088
|
+
const handle = await open(path7, "r");
|
|
1089
|
+
try {
|
|
1090
|
+
const buffer = Buffer.alloc(maxBytes);
|
|
1091
|
+
const { bytesRead } = await handle.read(buffer, 0, maxBytes, 0);
|
|
1092
|
+
return buffer.toString(AGENT_SESSION_STORE.TEXT_ENCODING, 0, bytesRead);
|
|
1093
|
+
} finally {
|
|
1094
|
+
await handle.close();
|
|
1095
|
+
}
|
|
850
1096
|
},
|
|
851
1097
|
async stat(path7) {
|
|
852
1098
|
const result = await stat(path7);
|
|
@@ -857,9 +1103,9 @@ var defaultAgentResumeCommandDeps = {
|
|
|
857
1103
|
fs: nodeAgentSessionFileSystem,
|
|
858
1104
|
homeDir: homedir,
|
|
859
1105
|
nowMs: Date.now,
|
|
860
|
-
resolveWorktreeRoot: async (cwd) => {
|
|
1106
|
+
resolveWorktreeRoot: async (cwd, fallbackWorktreeRoot) => {
|
|
861
1107
|
const result = await detectWorktreeProductRoot(cwd);
|
|
862
|
-
return result.isGitRepo ? result.productDir :
|
|
1108
|
+
return result.isGitRepo ? result.productDir : fallbackWorktreeRoot;
|
|
863
1109
|
}
|
|
864
1110
|
};
|
|
865
1111
|
async function loadAgentResumeCandidates(options) {
|
|
@@ -868,8 +1114,9 @@ async function loadAgentResumeCandidates(options) {
|
|
|
868
1114
|
invocationDir: options.cwd,
|
|
869
1115
|
homeDir: deps.homeDir(),
|
|
870
1116
|
nowMs: deps.nowMs(),
|
|
1117
|
+
scope: options.scope,
|
|
871
1118
|
fs: deps.fs,
|
|
872
|
-
resolveWorktreeRoot: deps.resolveWorktreeRoot
|
|
1119
|
+
resolveWorktreeRoot: (cwd) => deps.resolveWorktreeRoot(cwd, options.fallbackWorktreeRoot)
|
|
873
1120
|
});
|
|
874
1121
|
}
|
|
875
1122
|
async function listAgentResumeSessions(options) {
|
|
@@ -879,6 +1126,63 @@ async function jsonAgentResumeSessions(options) {
|
|
|
879
1126
|
return renderAgentResumeJson(await loadAgentResumeCandidates(options));
|
|
880
1127
|
}
|
|
881
1128
|
|
|
1129
|
+
// src/commands/agent/search.ts
|
|
1130
|
+
import { open as open2, readdir as readdir2, readFile, stat as stat2 } from "fs/promises";
|
|
1131
|
+
import { homedir as homedir2 } from "os";
|
|
1132
|
+
var nodeAgentSearchFileSystem = {
|
|
1133
|
+
async readDir(path7) {
|
|
1134
|
+
const entries = await readdir2(path7, { withFileTypes: true });
|
|
1135
|
+
return entries.map((entry) => ({
|
|
1136
|
+
name: entry.name,
|
|
1137
|
+
isDirectory: entry.isDirectory(),
|
|
1138
|
+
isFile: entry.isFile()
|
|
1139
|
+
}));
|
|
1140
|
+
},
|
|
1141
|
+
async readHead(path7, maxBytes) {
|
|
1142
|
+
const handle = await open2(path7, "r");
|
|
1143
|
+
try {
|
|
1144
|
+
const buffer = Buffer.alloc(maxBytes);
|
|
1145
|
+
const { bytesRead } = await handle.read(buffer, 0, maxBytes, 0);
|
|
1146
|
+
return buffer.toString(AGENT_SESSION_STORE.TEXT_ENCODING, 0, bytesRead);
|
|
1147
|
+
} finally {
|
|
1148
|
+
await handle.close();
|
|
1149
|
+
}
|
|
1150
|
+
},
|
|
1151
|
+
async readText(path7) {
|
|
1152
|
+
return readFile(path7, AGENT_SESSION_STORE.TEXT_ENCODING);
|
|
1153
|
+
},
|
|
1154
|
+
async stat(path7) {
|
|
1155
|
+
const result = await stat2(path7);
|
|
1156
|
+
return { mtimeMs: result.mtimeMs };
|
|
1157
|
+
}
|
|
1158
|
+
};
|
|
1159
|
+
var defaultAgentSearchCommandDeps = {
|
|
1160
|
+
fs: nodeAgentSearchFileSystem,
|
|
1161
|
+
homeDir: homedir2,
|
|
1162
|
+
nowMs: Date.now,
|
|
1163
|
+
resolveProductScopeRoot: resolveAgentSearchProductScopeRoot
|
|
1164
|
+
};
|
|
1165
|
+
async function resolveAgentSearchProductScopeRoot(cwd, fallbackProductScopeRoot, gitDeps = defaultGitDependencies) {
|
|
1166
|
+
const result = await detectWorktreeProductRoot(cwd, gitDeps);
|
|
1167
|
+
return result.isGitRepo ? result.productDir : fallbackProductScopeRoot;
|
|
1168
|
+
}
|
|
1169
|
+
async function loadAgentSearchResults(options) {
|
|
1170
|
+
const deps = options.deps ?? defaultAgentSearchCommandDeps;
|
|
1171
|
+
return searchAgentSessions({
|
|
1172
|
+
homeDir: deps.homeDir(),
|
|
1173
|
+
nowMs: deps.nowMs(),
|
|
1174
|
+
productScopeRoot: await deps.resolveProductScopeRoot(options.cwd, options.fallbackProductScopeRoot),
|
|
1175
|
+
fs: deps.fs,
|
|
1176
|
+
query: options.query
|
|
1177
|
+
});
|
|
1178
|
+
}
|
|
1179
|
+
async function listAgentSearchSessions(options) {
|
|
1180
|
+
return renderAgentSearchList(await loadAgentSearchResults(options));
|
|
1181
|
+
}
|
|
1182
|
+
async function jsonAgentSearchSessions(options) {
|
|
1183
|
+
return renderAgentSearchJson(await loadAgentSearchResults(options));
|
|
1184
|
+
}
|
|
1185
|
+
|
|
882
1186
|
// src/lib/process-lifecycle/exit-codes.ts
|
|
883
1187
|
var SIGINT_EXIT_CODE = 130;
|
|
884
1188
|
var SIGTERM_EXIT_CODE = 143;
|
|
@@ -1057,6 +1361,50 @@ function spawnManagedSubprocess(runner, command, args, options) {
|
|
|
1057
1361
|
});
|
|
1058
1362
|
}
|
|
1059
1363
|
|
|
1364
|
+
// src/lib/sanitize-cli-argument.ts
|
|
1365
|
+
var MAX_CLI_ARGUMENT_DISPLAY_LENGTH = 120;
|
|
1366
|
+
var ELLIPSIS_TOKEN = "...";
|
|
1367
|
+
var SENTINEL_UNDEFINED = "<undefined>";
|
|
1368
|
+
var SENTINEL_NULL = "<null>";
|
|
1369
|
+
var SENTINEL_EMPTY = "<empty>";
|
|
1370
|
+
var CONTROL_CHAR_UPPER_BOUND = 31;
|
|
1371
|
+
var DEL_CHAR_CODE = 127;
|
|
1372
|
+
var HEX_RADIX = 16;
|
|
1373
|
+
var HEX_PAD = 2;
|
|
1374
|
+
function formatHexEscape(code) {
|
|
1375
|
+
return String.raw`\x${code.toString(HEX_RADIX).padStart(HEX_PAD, "0")}`;
|
|
1376
|
+
}
|
|
1377
|
+
function nonStringSentinel(type) {
|
|
1378
|
+
return `<non-string:${type}>`;
|
|
1379
|
+
}
|
|
1380
|
+
function sanitizeCliArgument(input) {
|
|
1381
|
+
return truncate(escapeCliArgument(input));
|
|
1382
|
+
}
|
|
1383
|
+
function escapeCliArgument(input) {
|
|
1384
|
+
if (input === void 0) return SENTINEL_UNDEFINED;
|
|
1385
|
+
if (input === null) return SENTINEL_NULL;
|
|
1386
|
+
if (typeof input !== "string") return nonStringSentinel(typeof input);
|
|
1387
|
+
if (input.length === 0) return SENTINEL_EMPTY;
|
|
1388
|
+
return escapeControlCharacters(input);
|
|
1389
|
+
}
|
|
1390
|
+
function escapeControlCharacters(value) {
|
|
1391
|
+
let out = "";
|
|
1392
|
+
for (const char of value) {
|
|
1393
|
+
const code = char.codePointAt(0);
|
|
1394
|
+
if (code === void 0) continue;
|
|
1395
|
+
if (code <= CONTROL_CHAR_UPPER_BOUND || code === DEL_CHAR_CODE) {
|
|
1396
|
+
out += formatHexEscape(code);
|
|
1397
|
+
} else {
|
|
1398
|
+
out += char;
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1401
|
+
return out;
|
|
1402
|
+
}
|
|
1403
|
+
function truncate(value) {
|
|
1404
|
+
if (value.length <= MAX_CLI_ARGUMENT_DISPLAY_LENGTH) return value;
|
|
1405
|
+
return value.slice(0, MAX_CLI_ARGUMENT_DISPLAY_LENGTH - ELLIPSIS_TOKEN.length) + ELLIPSIS_TOKEN;
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1060
1408
|
// src/interfaces/cli/foreground-launch.ts
|
|
1061
1409
|
var FOREGROUND_LAUNCH_STDIO = "inherit";
|
|
1062
1410
|
var LAUNCH_FAILURE_STATUS = 1;
|
|
@@ -1179,10 +1527,26 @@ async function runAgentResumePicker(candidates, renderPicker = render) {
|
|
|
1179
1527
|
var AGENT_CLI = {
|
|
1180
1528
|
commandName: "agent",
|
|
1181
1529
|
resumeCommandName: "resume",
|
|
1530
|
+
searchCommandName: "search",
|
|
1182
1531
|
flags: {
|
|
1183
1532
|
latest: "--latest",
|
|
1184
1533
|
list: "--list",
|
|
1185
|
-
json: "--json"
|
|
1534
|
+
json: "--json",
|
|
1535
|
+
branch: "--branch",
|
|
1536
|
+
all: "--all",
|
|
1537
|
+
pickupId: "--pickup-id",
|
|
1538
|
+
contains: "--contains",
|
|
1539
|
+
sessionId: "--session-id",
|
|
1540
|
+
agent: "--agent",
|
|
1541
|
+
limit: "--limit"
|
|
1542
|
+
},
|
|
1543
|
+
optionArgs: {
|
|
1544
|
+
branch: "--branch <name>",
|
|
1545
|
+
pickupId: "--pickup-id <id>",
|
|
1546
|
+
contains: "--contains <literal>",
|
|
1547
|
+
sessionId: "--session-id <id>",
|
|
1548
|
+
agent: "--agent <kind>",
|
|
1549
|
+
limit: "--limit <count>"
|
|
1186
1550
|
}
|
|
1187
1551
|
};
|
|
1188
1552
|
var AGENT_CLI_EXIT = {
|
|
@@ -1200,6 +1564,7 @@ var DEFAULT_AGENT_CLI_DEPENDENCIES = {
|
|
|
1200
1564
|
);
|
|
1201
1565
|
}
|
|
1202
1566
|
};
|
|
1567
|
+
var POSITIVE_DECIMAL_INTEGER_PATTERN = /^[1-9][0-9]*$/;
|
|
1203
1568
|
function writeOutput(invocation, output) {
|
|
1204
1569
|
invocation.io.writeStdout(`${output}
|
|
1205
1570
|
`);
|
|
@@ -1213,6 +1578,48 @@ function handleError(invocation, error) {
|
|
|
1213
1578
|
writeError(invocation, `Error: ${message}`);
|
|
1214
1579
|
return invocation.io.exit(AGENT_CLI_EXIT.FAILURE);
|
|
1215
1580
|
}
|
|
1581
|
+
function resumeScopeFromOptions(options) {
|
|
1582
|
+
return options.branch === void 0 ? worktreeResumeScope() : branchResumeScope(options.branch);
|
|
1583
|
+
}
|
|
1584
|
+
function parseSearchLimit(value) {
|
|
1585
|
+
if (!POSITIVE_DECIMAL_INTEGER_PATTERN.test(value)) {
|
|
1586
|
+
throw new Error(`agent search limit must be a positive integer: ${sanitizeCliArgument(value)}`);
|
|
1587
|
+
}
|
|
1588
|
+
const parsed = Number.parseInt(value, 10);
|
|
1589
|
+
if (!Number.isSafeInteger(parsed)) {
|
|
1590
|
+
throw new Error(`agent search limit must be a positive integer: ${sanitizeCliArgument(value)}`);
|
|
1591
|
+
}
|
|
1592
|
+
return parsed;
|
|
1593
|
+
}
|
|
1594
|
+
function parseSearchAgentKind(value) {
|
|
1595
|
+
if (value === AGENT_SESSION_KIND.CODEX || value === AGENT_SESSION_KIND.CLAUDE_CODE) {
|
|
1596
|
+
return value;
|
|
1597
|
+
}
|
|
1598
|
+
throw new Error(`agent search kind must be codex or claude-code: ${sanitizeCliArgument(value)}`);
|
|
1599
|
+
}
|
|
1600
|
+
function searchQueryFromOptions(options) {
|
|
1601
|
+
return {
|
|
1602
|
+
pickupId: options.pickupId,
|
|
1603
|
+
contains: options.contains,
|
|
1604
|
+
sessionId: options.sessionId,
|
|
1605
|
+
branch: options.branch,
|
|
1606
|
+
agent: options.agent === void 0 ? void 0 : parseSearchAgentKind(options.agent),
|
|
1607
|
+
all: options.all,
|
|
1608
|
+
limit: options.limit === void 0 ? AGENT_SEARCH_DEFAULT_LIMIT : parseSearchLimit(options.limit)
|
|
1609
|
+
};
|
|
1610
|
+
}
|
|
1611
|
+
async function dispatchInteractiveResume(mode, commandOptions, deps, invocation) {
|
|
1612
|
+
const candidates = await loadAgentResumeCandidates(commandOptions);
|
|
1613
|
+
if (candidates.length === 0) {
|
|
1614
|
+
writeError(invocation, AGENT_RESUME_TEXT.NO_MATCHES);
|
|
1615
|
+
return AGENT_CLI_EXIT.FAILURE;
|
|
1616
|
+
}
|
|
1617
|
+
if (mode === AGENT_RESUME_MODE.LATEST) {
|
|
1618
|
+
return deps.launchCandidate(candidates[0]);
|
|
1619
|
+
}
|
|
1620
|
+
const pickerResult = await deps.pickCandidate(candidates);
|
|
1621
|
+
return pickerResult.kind === AGENT_RESUME_PICKER_RESULT.SELECTED ? deps.launchCandidate(pickerResult.candidate) : AGENT_CLI_EXIT.SUCCESS;
|
|
1622
|
+
}
|
|
1216
1623
|
function createAgentDomain(deps = {}) {
|
|
1217
1624
|
const resolvedDeps = {
|
|
1218
1625
|
...DEFAULT_AGENT_CLI_DEPENDENCIES,
|
|
@@ -1223,16 +1630,19 @@ function createAgentDomain(deps = {}) {
|
|
|
1223
1630
|
description: "Find and resume coding-agent sessions",
|
|
1224
1631
|
register: (program, invocation) => {
|
|
1225
1632
|
const agentCmd = program.command(AGENT_CLI.commandName).description("Find and resume coding-agent sessions");
|
|
1226
|
-
agentCmd.command(AGENT_CLI.resumeCommandName).description("Resume a Codex or Claude Code agent session for this worktree").option(AGENT_CLI.flags.latest, "Launch the newest matching session").option(AGENT_CLI.flags.list, "List matching sessions").option(AGENT_CLI.flags.json, "Print matching sessions as JSON").action(async (options) => {
|
|
1633
|
+
agentCmd.command(AGENT_CLI.resumeCommandName).description("Resume a Codex or Claude Code agent session for this worktree").option(AGENT_CLI.flags.latest, "Launch the newest matching session").option(AGENT_CLI.flags.list, "List matching sessions").option(AGENT_CLI.flags.json, "Print matching sessions as JSON").option(AGENT_CLI.optionArgs.branch, "Scope to sessions started on the named branch, across worktrees").action(async (options) => {
|
|
1227
1634
|
let requestedExitCode = AGENT_CLI_EXIT.SUCCESS;
|
|
1228
1635
|
try {
|
|
1229
1636
|
const mode = resolveAgentResumeMode(options);
|
|
1637
|
+
const productContext = invocation.resolveProductContext();
|
|
1230
1638
|
if (mode === AGENT_RESUME_MODE.PICK && !resolvedDeps.isInteractiveTerminal()) {
|
|
1231
1639
|
writeError(invocation, AGENT_RESUME_TEXT.INTERACTIVE_REQUIRED);
|
|
1232
1640
|
requestedExitCode = AGENT_CLI_EXIT.FAILURE;
|
|
1233
1641
|
} else {
|
|
1234
1642
|
const commandOptions = {
|
|
1235
|
-
cwd:
|
|
1643
|
+
cwd: productContext.effectiveInvocationDir,
|
|
1644
|
+
fallbackWorktreeRoot: productContext.productDir,
|
|
1645
|
+
scope: resumeScopeFromOptions(options),
|
|
1236
1646
|
deps: resolvedDeps.resumeDeps
|
|
1237
1647
|
};
|
|
1238
1648
|
if (mode === AGENT_RESUME_MODE.JSON) {
|
|
@@ -1243,24 +1653,28 @@ function createAgentDomain(deps = {}) {
|
|
|
1243
1653
|
writeOutput(invocation, await listAgentResumeSessions(commandOptions));
|
|
1244
1654
|
return;
|
|
1245
1655
|
}
|
|
1246
|
-
|
|
1247
|
-
if (candidates.length === 0) {
|
|
1248
|
-
writeError(invocation, AGENT_RESUME_TEXT.NO_MATCHES);
|
|
1249
|
-
requestedExitCode = AGENT_CLI_EXIT.FAILURE;
|
|
1250
|
-
} else if (mode === AGENT_RESUME_MODE.LATEST) {
|
|
1251
|
-
requestedExitCode = await resolvedDeps.launchCandidate(candidates[0]);
|
|
1252
|
-
} else {
|
|
1253
|
-
const pickerResult = await resolvedDeps.pickCandidate(candidates);
|
|
1254
|
-
if (pickerResult.kind === AGENT_RESUME_PICKER_RESULT.SELECTED) {
|
|
1255
|
-
requestedExitCode = await resolvedDeps.launchCandidate(pickerResult.candidate);
|
|
1256
|
-
}
|
|
1257
|
-
}
|
|
1656
|
+
requestedExitCode = await dispatchInteractiveResume(mode, commandOptions, resolvedDeps, invocation);
|
|
1258
1657
|
}
|
|
1259
1658
|
} catch (error) {
|
|
1260
1659
|
handleError(invocation, error);
|
|
1261
1660
|
}
|
|
1262
1661
|
return invocation.io.exit(requestedExitCode);
|
|
1263
1662
|
});
|
|
1663
|
+
agentCmd.command(AGENT_CLI.searchCommandName).description("Search Codex and Claude Code agent session transcripts for this product").option(AGENT_CLI.optionArgs.pickupId, "Search for an exact SPX pickup marker").option(AGENT_CLI.optionArgs.contains, "Search transcript content for a literal string").option(AGENT_CLI.optionArgs.sessionId, "Search for an agent session id").option(AGENT_CLI.optionArgs.branch, "Search for sessions started on the named branch").option(AGENT_CLI.optionArgs.agent, "Search only one agent kind").option(AGENT_CLI.flags.all, "Include sessions outside the recent-session window").option(AGENT_CLI.optionArgs.limit, "Maximum number of results").option(AGENT_CLI.flags.json, "Print matching sessions as JSON").action(async (options) => {
|
|
1664
|
+
try {
|
|
1665
|
+
const productContext = invocation.resolveProductContext();
|
|
1666
|
+
const commandOptions = {
|
|
1667
|
+
cwd: productContext.effectiveInvocationDir,
|
|
1668
|
+
fallbackProductScopeRoot: productContext.productDir,
|
|
1669
|
+
query: agentSearchQueryFromOptions(searchQueryFromOptions(options)),
|
|
1670
|
+
deps: resolvedDeps.searchDeps
|
|
1671
|
+
};
|
|
1672
|
+
const output = options.json === true ? await jsonAgentSearchSessions(commandOptions) : await listAgentSearchSessions(commandOptions);
|
|
1673
|
+
writeOutput(invocation, output);
|
|
1674
|
+
} catch (error) {
|
|
1675
|
+
handleError(invocation, error);
|
|
1676
|
+
}
|
|
1677
|
+
});
|
|
1264
1678
|
}
|
|
1265
1679
|
};
|
|
1266
1680
|
}
|
|
@@ -2725,7 +3139,7 @@ function validateBoolean(path7, value) {
|
|
|
2725
3139
|
}
|
|
2726
3140
|
function validateAgent(path7, value) {
|
|
2727
3141
|
if (typeof value !== "string" || !isAgent(value)) {
|
|
2728
|
-
return { ok: false, error: `${path7} must be a registered
|
|
3142
|
+
return { ok: false, error: `${path7} must be a registered agent` };
|
|
2729
3143
|
}
|
|
2730
3144
|
return { ok: true, value };
|
|
2731
3145
|
}
|
|
@@ -2734,7 +3148,7 @@ function isAgent(value) {
|
|
|
2734
3148
|
}
|
|
2735
3149
|
function validateAgentArray(path7, value) {
|
|
2736
3150
|
if (!Array.isArray(value) || value.length === 0) {
|
|
2737
|
-
return { ok: false, error: `${path7} must be a non-empty array of registered
|
|
3151
|
+
return { ok: false, error: `${path7} must be a non-empty array of registered agents` };
|
|
2738
3152
|
}
|
|
2739
3153
|
const agents = [];
|
|
2740
3154
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -2742,7 +3156,7 @@ function validateAgentArray(path7, value) {
|
|
|
2742
3156
|
const agent = validateAgent(`${path7}.${index}`, entry);
|
|
2743
3157
|
if (!agent.ok) return agent;
|
|
2744
3158
|
if (seen.has(agent.value)) {
|
|
2745
|
-
return { ok: false, error: `${path7}.${index} repeats registered
|
|
3159
|
+
return { ok: false, error: `${path7}.${index} repeats registered agent ${agent.value}` };
|
|
2746
3160
|
}
|
|
2747
3161
|
seen.add(agent.value);
|
|
2748
3162
|
agents.push(agent.value);
|
|
@@ -2896,6 +3310,13 @@ function validatePluginBootstrap(raw) {
|
|
|
2896
3310
|
skills.value
|
|
2897
3311
|
);
|
|
2898
3312
|
if (!skillUniqueness.ok) return skillUniqueness;
|
|
3313
|
+
const bootstrapNameUniqueness = validatePluginBootstrapNameUniqueness(
|
|
3314
|
+
sectionPath,
|
|
3315
|
+
marketplaces.value,
|
|
3316
|
+
plugins.value,
|
|
3317
|
+
skills.value
|
|
3318
|
+
);
|
|
3319
|
+
if (!bootstrapNameUniqueness.ok) return bootstrapNameUniqueness;
|
|
2899
3320
|
return {
|
|
2900
3321
|
ok: true,
|
|
2901
3322
|
value: {
|
|
@@ -2944,6 +3365,38 @@ function validateNamedAgentEntryUniqueness(path7, entries) {
|
|
|
2944
3365
|
}
|
|
2945
3366
|
return { ok: true, value: void 0 };
|
|
2946
3367
|
}
|
|
3368
|
+
function validatePluginBootstrapNameUniqueness(sectionPath, marketplaces, plugins, skills) {
|
|
3369
|
+
const namesByAgent = /* @__PURE__ */ new Map();
|
|
3370
|
+
const entries = [
|
|
3371
|
+
...marketplaces.map((entry, index) => ({
|
|
3372
|
+
agent: entry.agent,
|
|
3373
|
+
name: entry.name,
|
|
3374
|
+
path: `${sectionPath}.${HARNESS_ENVIRONMENT_CONFIG_FIELDS.MARKETPLACES}.${index}`
|
|
3375
|
+
})),
|
|
3376
|
+
...plugins.map((entry, index) => ({
|
|
3377
|
+
agent: entry.agent,
|
|
3378
|
+
name: entry.name,
|
|
3379
|
+
path: `${sectionPath}.${HARNESS_ENVIRONMENT_CONFIG_FIELDS.PLUGINS}.${index}`
|
|
3380
|
+
})),
|
|
3381
|
+
...skills.map((entry, index) => ({
|
|
3382
|
+
agent: entry.agent,
|
|
3383
|
+
name: entry.name,
|
|
3384
|
+
path: `${sectionPath}.${HARNESS_ENVIRONMENT_CONFIG_FIELDS.SKILLS}.${index}`
|
|
3385
|
+
}))
|
|
3386
|
+
];
|
|
3387
|
+
for (const entry of entries) {
|
|
3388
|
+
const agentNames = namesByAgent.get(entry.agent) ?? /* @__PURE__ */ new Set();
|
|
3389
|
+
if (agentNames.has(entry.name)) {
|
|
3390
|
+
return {
|
|
3391
|
+
ok: false,
|
|
3392
|
+
error: `${entry.path}.${HARNESS_ENVIRONMENT_CONFIG_FIELDS.NAME} ${JSON.stringify(entry.name)} is already used by another ${entry.agent} bootstrap entry`
|
|
3393
|
+
};
|
|
3394
|
+
}
|
|
3395
|
+
agentNames.add(entry.name);
|
|
3396
|
+
namesByAgent.set(entry.agent, agentNames);
|
|
3397
|
+
}
|
|
3398
|
+
return { ok: true, value: void 0 };
|
|
3399
|
+
}
|
|
2947
3400
|
function validatePluginMarketplaceReferences(path7, marketplaces, plugins) {
|
|
2948
3401
|
const marketplacesByAgent = /* @__PURE__ */ new Map();
|
|
2949
3402
|
for (const marketplace of marketplaces) {
|
|
@@ -5957,11 +6410,12 @@ function worktreePoolReadingFromSnapshot(snapshot) {
|
|
|
5957
6410
|
}
|
|
5958
6411
|
function sessionEnvironmentReadingFromSnapshot(snapshot, environment) {
|
|
5959
6412
|
const currentWorktree = snapshot.worktrees.find((worktree) => worktree.root === snapshot.currentWorktreeRoot);
|
|
6413
|
+
const worktreeClaimed = currentWorktree?.status === OCCUPANCY_STATUS.RUNNING;
|
|
5960
6414
|
return {
|
|
5961
6415
|
errored: snapshot.errored,
|
|
5962
6416
|
hookPresent: environment.hookPresent,
|
|
5963
|
-
sessionIdentity: environment.sessionIdentity,
|
|
5964
|
-
worktreeClaimed
|
|
6417
|
+
sessionIdentity: environment.sessionIdentity || worktreeClaimed && currentWorktree.sessionId !== void 0,
|
|
6418
|
+
worktreeClaimed
|
|
5965
6419
|
};
|
|
5966
6420
|
}
|
|
5967
6421
|
async function exportedClaimReadingFromEnv(env, deps) {
|
|
@@ -6141,50 +6595,6 @@ var defaultSpxReachabilityProbe = {
|
|
|
6141
6595
|
}
|
|
6142
6596
|
};
|
|
6143
6597
|
|
|
6144
|
-
// src/lib/sanitize-cli-argument.ts
|
|
6145
|
-
var MAX_CLI_ARGUMENT_DISPLAY_LENGTH = 120;
|
|
6146
|
-
var ELLIPSIS_TOKEN = "...";
|
|
6147
|
-
var SENTINEL_UNDEFINED = "<undefined>";
|
|
6148
|
-
var SENTINEL_NULL = "<null>";
|
|
6149
|
-
var SENTINEL_EMPTY = "<empty>";
|
|
6150
|
-
var CONTROL_CHAR_UPPER_BOUND = 31;
|
|
6151
|
-
var DEL_CHAR_CODE = 127;
|
|
6152
|
-
var HEX_RADIX = 16;
|
|
6153
|
-
var HEX_PAD = 2;
|
|
6154
|
-
function formatHexEscape(code) {
|
|
6155
|
-
return String.raw`\x${code.toString(HEX_RADIX).padStart(HEX_PAD, "0")}`;
|
|
6156
|
-
}
|
|
6157
|
-
function nonStringSentinel(type) {
|
|
6158
|
-
return `<non-string:${type}>`;
|
|
6159
|
-
}
|
|
6160
|
-
function sanitizeCliArgument(input) {
|
|
6161
|
-
return truncate(escapeCliArgument(input));
|
|
6162
|
-
}
|
|
6163
|
-
function escapeCliArgument(input) {
|
|
6164
|
-
if (input === void 0) return SENTINEL_UNDEFINED;
|
|
6165
|
-
if (input === null) return SENTINEL_NULL;
|
|
6166
|
-
if (typeof input !== "string") return nonStringSentinel(typeof input);
|
|
6167
|
-
if (input.length === 0) return SENTINEL_EMPTY;
|
|
6168
|
-
return escapeControlCharacters(input);
|
|
6169
|
-
}
|
|
6170
|
-
function escapeControlCharacters(value) {
|
|
6171
|
-
let out = "";
|
|
6172
|
-
for (const char of value) {
|
|
6173
|
-
const code = char.codePointAt(0);
|
|
6174
|
-
if (code === void 0) continue;
|
|
6175
|
-
if (code <= CONTROL_CHAR_UPPER_BOUND || code === DEL_CHAR_CODE) {
|
|
6176
|
-
out += formatHexEscape(code);
|
|
6177
|
-
} else {
|
|
6178
|
-
out += char;
|
|
6179
|
-
}
|
|
6180
|
-
}
|
|
6181
|
-
return out;
|
|
6182
|
-
}
|
|
6183
|
-
function truncate(value) {
|
|
6184
|
-
if (value.length <= MAX_CLI_ARGUMENT_DISPLAY_LENGTH) return value;
|
|
6185
|
-
return value.slice(0, MAX_CLI_ARGUMENT_DISPLAY_LENGTH - ELLIPSIS_TOKEN.length) + ELLIPSIS_TOKEN;
|
|
6186
|
-
}
|
|
6187
|
-
|
|
6188
6598
|
// src/interfaces/cli/diagnose.ts
|
|
6189
6599
|
var DIAGNOSE_CLI = {
|
|
6190
6600
|
COMMAND: "diagnose",
|
|
@@ -6946,9 +7356,6 @@ function compareJournalRunsOldestFirst(left, right) {
|
|
|
6946
7356
|
const createdAtOrder = left.createdAtMs - right.createdAtMs;
|
|
6947
7357
|
return createdAtOrder === 0 ? compareAsciiStrings(left.runToken, right.runToken) : createdAtOrder;
|
|
6948
7358
|
}
|
|
6949
|
-
function applyJournalRunListLimit(runs, limit) {
|
|
6950
|
-
return limit === void 0 ? runs : runs.slice(0, limit);
|
|
6951
|
-
}
|
|
6952
7359
|
function journalRunFilePath(scope2) {
|
|
6953
7360
|
const typeRunsDir = journalRunsDir(scope2);
|
|
6954
7361
|
if (!typeRunsDir.ok) return typeRunsDir;
|
|
@@ -7369,6 +7776,9 @@ var JOURNAL_RUNTIME_ERROR = {
|
|
|
7369
7776
|
RENDER_FAILED: "journal render failed",
|
|
7370
7777
|
RUN_NOT_FOUND: "journal run not found; open the run before operating on it"
|
|
7371
7778
|
};
|
|
7779
|
+
function applyJournalRunListLimit(runs, limit) {
|
|
7780
|
+
return runs.slice(0, limit);
|
|
7781
|
+
}
|
|
7372
7782
|
function bindRunFilePath(ref) {
|
|
7373
7783
|
return journalRunFilePath({
|
|
7374
7784
|
productDir: ref.productDir,
|
|
@@ -7541,9 +7951,25 @@ async function readSealedJournalRunSet(scope2, options = {}) {
|
|
|
7541
7951
|
}
|
|
7542
7952
|
return {
|
|
7543
7953
|
ok: true,
|
|
7544
|
-
value: runs.sort((left, right) =>
|
|
7954
|
+
value: runs.sort((left, right) => compareJournalRunsNewestFirst(left.metadata, right.metadata)).slice(0, scope2.limit).sort((left, right) => compareJournalRunsOldestFirst(left.metadata, right.metadata)).map((run) => ({
|
|
7955
|
+
runToken: run.metadata.runToken,
|
|
7956
|
+
metadata: run.metadata,
|
|
7957
|
+
events: run.events.slice(0, scope2.eventLimit)
|
|
7958
|
+
}))
|
|
7545
7959
|
};
|
|
7546
7960
|
}
|
|
7961
|
+
async function findJournalRunBranchSlugs(scope2, options = {}) {
|
|
7962
|
+
const fs8 = options.fs ?? defaultFileSystem;
|
|
7963
|
+
const branches = await branchSlugs(scope2, fs8);
|
|
7964
|
+
if (!branches.ok) return branches;
|
|
7965
|
+
const matches = [];
|
|
7966
|
+
for (const branchSlug of branches.value) {
|
|
7967
|
+
const runFilePath = bindRunFilePath({ ...scope2, branchSlug });
|
|
7968
|
+
if (!runFilePath.ok) return runFilePath;
|
|
7969
|
+
if (await runFileExists(fs8, runFilePath.value)) matches.push(branchSlug);
|
|
7970
|
+
}
|
|
7971
|
+
return { ok: true, value: matches };
|
|
7972
|
+
}
|
|
7547
7973
|
async function appendJournalEvent(ref, input, sink, options = {}) {
|
|
7548
7974
|
const bound = await bindJournal(ref, options.fs);
|
|
7549
7975
|
if (!bound.ok) return bound;
|
|
@@ -7593,7 +8019,12 @@ var JOURNAL_CLI_EXIT_CODE = {
|
|
|
7593
8019
|
OK: 0,
|
|
7594
8020
|
ERROR: 1
|
|
7595
8021
|
};
|
|
7596
|
-
var
|
|
8022
|
+
var JOURNAL_CLI_RUN_LIMIT = {
|
|
8023
|
+
DEFAULT: 20,
|
|
8024
|
+
MIN: 1
|
|
8025
|
+
};
|
|
8026
|
+
var JOURNAL_CLI_READ_SET_EVENT_LIMIT = {
|
|
8027
|
+
DEFAULT: 100,
|
|
7597
8028
|
MIN: 1
|
|
7598
8029
|
};
|
|
7599
8030
|
var JOURNAL_CLI_ENV = {
|
|
@@ -7613,9 +8044,11 @@ var JOURNAL_CLI_ERROR = {
|
|
|
7613
8044
|
PULL_REQUEST_UNRESOLVED: "github pull-request number is not resolvable from the environment",
|
|
7614
8045
|
INVALID_EVENT_INPUT: "journal append event input is missing a required CloudEvents field",
|
|
7615
8046
|
INVALID_CURSOR: "journal read cursor must be a whole non-negative integer",
|
|
7616
|
-
|
|
8047
|
+
INVALID_RUN_LIMIT: "journal run limit must be a positive whole integer",
|
|
8048
|
+
INVALID_READ_SET_EVENT_LIMIT: "journal read-set event limit must be a positive whole integer",
|
|
7617
8049
|
INVALID_SEALED_FILTER: "journal list sealed filter is not registered",
|
|
7618
8050
|
INVALID_TERMINAL_STATE_FILTER: "journal list terminal-state filter is not registered",
|
|
8051
|
+
RUN_TOKEN_AMBIGUOUS: "journal run token matches multiple branch scopes; rerun with --branch-slug",
|
|
7619
8052
|
OPEN_HYDRATION_FAILED: "journal open failed to hydrate the pull request's prior runs"
|
|
7620
8053
|
};
|
|
7621
8054
|
var CURSOR_PATTERN = /^\d+$/;
|
|
@@ -7738,6 +8171,34 @@ function verbOptions(deps) {
|
|
|
7738
8171
|
function runRef(context, runToken) {
|
|
7739
8172
|
return { productDir: context.productDir, branchSlug: context.branchSlug, type: context.type, runToken };
|
|
7740
8173
|
}
|
|
8174
|
+
async function inspectionRunRef(scope2, deps) {
|
|
8175
|
+
const context = await resolveJournalRunContext(scope2, deps);
|
|
8176
|
+
if (!context.ok) return context;
|
|
8177
|
+
if (scope2.branchSlug !== void 0) return { ok: true, value: runRef(context.value, scope2.runToken) };
|
|
8178
|
+
const branches = await findJournalRunBranchSlugs(
|
|
8179
|
+
{
|
|
8180
|
+
productDir: context.value.productDir,
|
|
8181
|
+
type: context.value.type,
|
|
8182
|
+
runToken: scope2.runToken
|
|
8183
|
+
},
|
|
8184
|
+
verbOptions(deps)
|
|
8185
|
+
);
|
|
8186
|
+
if (!branches.ok) return branches;
|
|
8187
|
+
if (branches.value.length === 1) {
|
|
8188
|
+
const branchSlug = branches.value[0];
|
|
8189
|
+
return {
|
|
8190
|
+
ok: true,
|
|
8191
|
+
value: {
|
|
8192
|
+
productDir: context.value.productDir,
|
|
8193
|
+
branchSlug,
|
|
8194
|
+
type: context.value.type,
|
|
8195
|
+
runToken: scope2.runToken
|
|
8196
|
+
}
|
|
8197
|
+
};
|
|
8198
|
+
}
|
|
8199
|
+
if (branches.value.length > 1) return { ok: false, error: JOURNAL_CLI_ERROR.RUN_TOKEN_AMBIGUOUS };
|
|
8200
|
+
return { ok: true, value: runRef(context.value, scope2.runToken) };
|
|
8201
|
+
}
|
|
7741
8202
|
function okResult(output) {
|
|
7742
8203
|
return { exitCode: JOURNAL_CLI_EXIT_CODE.OK, output };
|
|
7743
8204
|
}
|
|
@@ -7813,13 +8274,21 @@ function parseJournalCursor(raw) {
|
|
|
7813
8274
|
const value = Number.parseInt(raw, DECIMAL_RADIX);
|
|
7814
8275
|
return Number.isSafeInteger(value) ? { ok: true, value } : { ok: false, error: JOURNAL_CLI_ERROR.INVALID_CURSOR };
|
|
7815
8276
|
}
|
|
7816
|
-
function
|
|
7817
|
-
if (raw === void 0) return { ok: true, value:
|
|
8277
|
+
function parseJournalRunLimit(raw) {
|
|
8278
|
+
if (raw === void 0) return { ok: true, value: JOURNAL_CLI_RUN_LIMIT.DEFAULT };
|
|
8279
|
+
if (!POSITIVE_INTEGER_PATTERN.test(raw)) {
|
|
8280
|
+
return { ok: false, error: JOURNAL_CLI_ERROR.INVALID_RUN_LIMIT };
|
|
8281
|
+
}
|
|
8282
|
+
const value = Number.parseInt(raw, DECIMAL_RADIX);
|
|
8283
|
+
return Number.isSafeInteger(value) && value >= JOURNAL_CLI_RUN_LIMIT.MIN ? { ok: true, value } : { ok: false, error: JOURNAL_CLI_ERROR.INVALID_RUN_LIMIT };
|
|
8284
|
+
}
|
|
8285
|
+
function parseJournalReadSetEventLimit(raw) {
|
|
8286
|
+
if (raw === void 0) return { ok: true, value: JOURNAL_CLI_READ_SET_EVENT_LIMIT.DEFAULT };
|
|
7818
8287
|
if (!POSITIVE_INTEGER_PATTERN.test(raw)) {
|
|
7819
|
-
return { ok: false, error: JOURNAL_CLI_ERROR.
|
|
8288
|
+
return { ok: false, error: JOURNAL_CLI_ERROR.INVALID_READ_SET_EVENT_LIMIT };
|
|
7820
8289
|
}
|
|
7821
8290
|
const value = Number.parseInt(raw, DECIMAL_RADIX);
|
|
7822
|
-
return Number.isSafeInteger(value) && value >=
|
|
8291
|
+
return Number.isSafeInteger(value) && value >= JOURNAL_CLI_READ_SET_EVENT_LIMIT.MIN ? { ok: true, value } : { ok: false, error: JOURNAL_CLI_ERROR.INVALID_READ_SET_EVENT_LIMIT };
|
|
7823
8292
|
}
|
|
7824
8293
|
function parseJournalSealedFilter(raw) {
|
|
7825
8294
|
if (raw === void 0) return { ok: true, value: void 0 };
|
|
@@ -7846,9 +8315,9 @@ async function journalAppendCommand(scope2, input, binding, deps = {}) {
|
|
|
7846
8315
|
async function journalReadCommand(scope2, fromCursor, deps = {}) {
|
|
7847
8316
|
const cursor = parseJournalCursor(fromCursor);
|
|
7848
8317
|
if (!cursor.ok) return errorResult(cursor.error);
|
|
7849
|
-
const
|
|
7850
|
-
if (!
|
|
7851
|
-
const events = await readJournalEvents(
|
|
8318
|
+
const ref = await inspectionRunRef(scope2, deps);
|
|
8319
|
+
if (!ref.ok) return errorResult(ref.error);
|
|
8320
|
+
const events = await readJournalEvents(ref.value, cursor.value, verbOptions(deps));
|
|
7852
8321
|
if (!events.ok) return errorResult(events.error);
|
|
7853
8322
|
return okResult(JSON.stringify(events.value));
|
|
7854
8323
|
}
|
|
@@ -7857,7 +8326,7 @@ async function journalListCommand(scope2, deps = {}) {
|
|
|
7857
8326
|
if (!sealed.ok) return errorResult(sealed.error);
|
|
7858
8327
|
const terminalState = parseJournalTerminalFilter(scope2.terminalState);
|
|
7859
8328
|
if (!terminalState.ok) return errorResult(terminalState.error);
|
|
7860
|
-
const limit =
|
|
8329
|
+
const limit = parseJournalRunLimit(scope2.limit);
|
|
7861
8330
|
if (!limit.ok) return errorResult(limit.error);
|
|
7862
8331
|
const listScope = {
|
|
7863
8332
|
productDir: await resolveJournalProductDir(deps),
|
|
@@ -7865,16 +8334,27 @@ async function journalListCommand(scope2, deps = {}) {
|
|
|
7865
8334
|
...scope2.type === void 0 ? {} : { type: scope2.type },
|
|
7866
8335
|
...sealed.value === void 0 ? {} : { sealed: sealed.value },
|
|
7867
8336
|
...terminalState.value === void 0 ? {} : { terminalState: terminalState.value },
|
|
7868
|
-
|
|
8337
|
+
limit: limit.value
|
|
7869
8338
|
};
|
|
7870
8339
|
const runs = await listJournalRuns(listScope, verbOptions(deps));
|
|
7871
8340
|
if (!runs.ok) return errorResult(runs.error);
|
|
7872
8341
|
return okResult(JSON.stringify(runs.value));
|
|
7873
8342
|
}
|
|
7874
8343
|
async function journalReadSetCommand(scope2, deps = {}) {
|
|
8344
|
+
const limit = parseJournalRunLimit(scope2.limit);
|
|
8345
|
+
if (!limit.ok) return errorResult(limit.error);
|
|
8346
|
+
const eventLimit = parseJournalReadSetEventLimit(scope2.eventLimit);
|
|
8347
|
+
if (!eventLimit.ok) return errorResult(eventLimit.error);
|
|
7875
8348
|
const context = await resolveJournalRunScope(scope2, deps);
|
|
7876
8349
|
if (!context.ok) return errorResult(context.error);
|
|
7877
|
-
const runs = await readSealedJournalRunSet(
|
|
8350
|
+
const runs = await readSealedJournalRunSet(
|
|
8351
|
+
{
|
|
8352
|
+
...context.value,
|
|
8353
|
+
eventLimit: eventLimit.value,
|
|
8354
|
+
limit: limit.value
|
|
8355
|
+
},
|
|
8356
|
+
verbOptions(deps)
|
|
8357
|
+
);
|
|
7878
8358
|
if (!runs.ok) return errorResult(runs.error);
|
|
7879
8359
|
return okResult(JSON.stringify(runs.value));
|
|
7880
8360
|
}
|
|
@@ -7886,10 +8366,10 @@ async function journalSealCommand(scope2, deps = {}) {
|
|
|
7886
8366
|
return okResult(JSON.stringify({ sealed: true }));
|
|
7887
8367
|
}
|
|
7888
8368
|
async function journalRenderCommand(scope2, deps = {}) {
|
|
7889
|
-
const
|
|
7890
|
-
if (!
|
|
8369
|
+
const ref = await inspectionRunRef(scope2, deps);
|
|
8370
|
+
if (!ref.ok) return errorResult(ref.error);
|
|
7891
8371
|
const rendered = await renderJournalRun(
|
|
7892
|
-
|
|
8372
|
+
ref.value,
|
|
7893
8373
|
(events) => [...events],
|
|
7894
8374
|
verbOptions(deps)
|
|
7895
8375
|
);
|
|
@@ -7897,6 +8377,41 @@ async function journalRenderCommand(scope2, deps = {}) {
|
|
|
7897
8377
|
return okResult(JSON.stringify(rendered.value));
|
|
7898
8378
|
}
|
|
7899
8379
|
|
|
8380
|
+
// src/interfaces/cli/lib/stream-report.ts
|
|
8381
|
+
var CLI_STREAM_REPORT = {
|
|
8382
|
+
LINE_SEPARATOR: "\n"
|
|
8383
|
+
};
|
|
8384
|
+
function reportCliResult(result, io) {
|
|
8385
|
+
const output = `${result.output}${CLI_STREAM_REPORT.LINE_SEPARATOR}`;
|
|
8386
|
+
if (result.exitCode === 0) io.writeStdout(output);
|
|
8387
|
+
else io.writeStderr(output);
|
|
8388
|
+
io.setExitCode(result.exitCode);
|
|
8389
|
+
}
|
|
8390
|
+
|
|
8391
|
+
// src/interfaces/cli/lib/journal-stream-binding.ts
|
|
8392
|
+
function stdoutStreamSink(io) {
|
|
8393
|
+
return {
|
|
8394
|
+
async emit(event) {
|
|
8395
|
+
io.writeStdout(`${JSON.stringify(event)}${CLI_STREAM_REPORT.LINE_SEPARATOR}`);
|
|
8396
|
+
}
|
|
8397
|
+
};
|
|
8398
|
+
}
|
|
8399
|
+
function stderrStreamSink(io) {
|
|
8400
|
+
return {
|
|
8401
|
+
async emit(event) {
|
|
8402
|
+
io.writeStderr(`${JSON.stringify(event)}${CLI_STREAM_REPORT.LINE_SEPARATOR}`);
|
|
8403
|
+
}
|
|
8404
|
+
};
|
|
8405
|
+
}
|
|
8406
|
+
function createJournalStreamBinding(io, localSink = stdoutStreamSink(io)) {
|
|
8407
|
+
const repository = process.env[JOURNAL_CLI_ENV.GITHUB_REPOSITORY] ?? "";
|
|
8408
|
+
return {
|
|
8409
|
+
localSink,
|
|
8410
|
+
githubClient: createGithubPullRequestCommentClient({ repository, run: runGhApi }),
|
|
8411
|
+
githubRepository: repository
|
|
8412
|
+
};
|
|
8413
|
+
}
|
|
8414
|
+
|
|
7900
8415
|
// src/interfaces/cli/journal.ts
|
|
7901
8416
|
var JOURNAL_CLI = {
|
|
7902
8417
|
commandName: "journal",
|
|
@@ -7914,9 +8429,14 @@ var JOURNAL_CLI = {
|
|
|
7914
8429
|
branchSlugOption: "--branch-slug <slug>",
|
|
7915
8430
|
sealedOption: "--sealed <state>",
|
|
7916
8431
|
terminalStateOption: "--terminal-state <state>",
|
|
7917
|
-
limitOption: "--limit <count>"
|
|
8432
|
+
limitOption: "--limit <count>",
|
|
8433
|
+
eventLimitOption: "--event-limit <count>"
|
|
8434
|
+
};
|
|
8435
|
+
var JOURNAL_CLI_HELP = {
|
|
8436
|
+
LIST_RUN_LIMIT: `Maximum number of runs (default: ${JOURNAL_CLI_RUN_LIMIT.DEFAULT})`,
|
|
8437
|
+
READ_SET_EVENT_LIMIT: `Maximum events returned per run (default: ${JOURNAL_CLI_READ_SET_EVENT_LIMIT.DEFAULT})`,
|
|
8438
|
+
READ_SET_RUN_LIMIT: `Maximum number of sealed runs (default: ${JOURNAL_CLI_RUN_LIMIT.DEFAULT})`
|
|
7918
8439
|
};
|
|
7919
|
-
var STREAM_LINE_SEPARATOR = "\n";
|
|
7920
8440
|
var MALFORMED_EVENT_INPUT_ERROR = "journal append event input is not valid JSON";
|
|
7921
8441
|
function scope(options) {
|
|
7922
8442
|
return { type: options.type };
|
|
@@ -7935,7 +8455,7 @@ var journalDomain = {
|
|
|
7935
8455
|
const journalDeps = () => ({
|
|
7936
8456
|
cwd: invocation.resolveEffectiveInvocationDir(),
|
|
7937
8457
|
onWarning: (warning) => {
|
|
7938
|
-
if (warning !== void 0) invocation.io.writeStderr(`${warning}${
|
|
8458
|
+
if (warning !== void 0) invocation.io.writeStderr(`${warning}${CLI_STREAM_REPORT.LINE_SEPARATOR}`);
|
|
7939
8459
|
}
|
|
7940
8460
|
});
|
|
7941
8461
|
const journalCmd = program.command(JOURNAL_CLI.commandName).description(JOURNAL_CLI.description);
|
|
@@ -7951,7 +8471,7 @@ var journalDomain = {
|
|
|
7951
8471
|
const result = await journalAppendCommand(
|
|
7952
8472
|
runScope(options),
|
|
7953
8473
|
input.value,
|
|
7954
|
-
|
|
8474
|
+
createJournalStreamBinding(invocation.io),
|
|
7955
8475
|
journalDeps()
|
|
7956
8476
|
);
|
|
7957
8477
|
if (result.exitCode === JOURNAL_CLI_EXIT_CODE.OK) invocation.io.setExitCode(JOURNAL_CLI_EXIT_CODE.OK);
|
|
@@ -7966,15 +8486,17 @@ var journalDomain = {
|
|
|
7966
8486
|
journalCmd.command(JOURNAL_CLI.renderCommandName).description("Render the run's event-prefix projection").requiredOption(JOURNAL_CLI.typeOption, "Opaque verification-type scope segment").requiredOption(JOURNAL_CLI.runOption, "Run token reported by open").option(JOURNAL_CLI.branchSlugOption, "Branch slug reported by journal list").action(async (options) => {
|
|
7967
8487
|
report(await journalRenderCommand(runScope(options), journalDeps()), invocation.io);
|
|
7968
8488
|
});
|
|
7969
|
-
journalCmd.command(JOURNAL_CLI.listCommandName).description("List persisted run metadata").option(JOURNAL_CLI.typeOption, "Opaque verification-type scope segment").option(JOURNAL_CLI.branchSlugOption, "State-store branch slug").option(JOURNAL_CLI.sealedOption, "Sealed-state filter").option(JOURNAL_CLI.terminalStateOption, "Terminal-state filter").option(JOURNAL_CLI.limitOption,
|
|
8489
|
+
journalCmd.command(JOURNAL_CLI.listCommandName).description("List persisted run metadata").option(JOURNAL_CLI.typeOption, "Opaque verification-type scope segment").option(JOURNAL_CLI.branchSlugOption, "State-store branch slug").option(JOURNAL_CLI.sealedOption, "Sealed-state filter").option(JOURNAL_CLI.terminalStateOption, "Terminal-state filter").option(JOURNAL_CLI.limitOption, JOURNAL_CLI_HELP.LIST_RUN_LIMIT).action(async (options) => {
|
|
7970
8490
|
report(await journalListCommand(options, journalDeps()), invocation.io);
|
|
7971
8491
|
});
|
|
7972
|
-
journalCmd.command(JOURNAL_CLI.readSetCommandName).description("Read sealed runs in one branch and type scope").requiredOption(JOURNAL_CLI.typeOption, "Opaque verification-type scope segment").option(JOURNAL_CLI.branchSlugOption, "State-store branch slug").action(async (options) => {
|
|
8492
|
+
journalCmd.command(JOURNAL_CLI.readSetCommandName).description("Read sealed runs in one branch and type scope").requiredOption(JOURNAL_CLI.typeOption, "Opaque verification-type scope segment").option(JOURNAL_CLI.branchSlugOption, "State-store branch slug").option(JOURNAL_CLI.limitOption, JOURNAL_CLI_HELP.READ_SET_RUN_LIMIT).option(JOURNAL_CLI.eventLimitOption, JOURNAL_CLI_HELP.READ_SET_EVENT_LIMIT).action(async (options) => {
|
|
7973
8493
|
report(
|
|
7974
8494
|
await journalReadSetCommand(
|
|
7975
8495
|
{
|
|
7976
8496
|
...scope(options),
|
|
7977
|
-
...options.branchSlug === void 0 ? {} : { branchSlug: options.branchSlug }
|
|
8497
|
+
...options.branchSlug === void 0 ? {} : { branchSlug: options.branchSlug },
|
|
8498
|
+
...options.eventLimit === void 0 ? {} : { eventLimit: options.eventLimit },
|
|
8499
|
+
...options.limit === void 0 ? {} : { limit: options.limit }
|
|
7978
8500
|
},
|
|
7979
8501
|
journalDeps()
|
|
7980
8502
|
),
|
|
@@ -7983,21 +8505,6 @@ var journalDomain = {
|
|
|
7983
8505
|
});
|
|
7984
8506
|
}
|
|
7985
8507
|
};
|
|
7986
|
-
function stdoutStreamSink(io) {
|
|
7987
|
-
return {
|
|
7988
|
-
async emit(event) {
|
|
7989
|
-
io.writeStdout(`${JSON.stringify(event)}${STREAM_LINE_SEPARATOR}`);
|
|
7990
|
-
}
|
|
7991
|
-
};
|
|
7992
|
-
}
|
|
7993
|
-
function streamBinding(io) {
|
|
7994
|
-
const repository = process.env[JOURNAL_CLI_ENV.GITHUB_REPOSITORY] ?? "";
|
|
7995
|
-
return {
|
|
7996
|
-
localSink: stdoutStreamSink(io),
|
|
7997
|
-
githubClient: createGithubPullRequestCommentClient({ repository, run: runGhApi }),
|
|
7998
|
-
githubRepository: repository
|
|
7999
|
-
};
|
|
8000
|
-
}
|
|
8001
8508
|
async function readStdinEventInput() {
|
|
8002
8509
|
const chunks = [];
|
|
8003
8510
|
for await (const chunk of process.stdin) {
|
|
@@ -8010,7 +8517,7 @@ async function readStdinEventInput() {
|
|
|
8010
8517
|
}
|
|
8011
8518
|
}
|
|
8012
8519
|
function report(result, io) {
|
|
8013
|
-
const output = `${result.output}${
|
|
8520
|
+
const output = `${result.output}${CLI_STREAM_REPORT.LINE_SEPARATOR}`;
|
|
8014
8521
|
if (result.exitCode === JOURNAL_CLI_EXIT_CODE.OK) io.writeStdout(output);
|
|
8015
8522
|
else io.writeStderr(output);
|
|
8016
8523
|
io.setExitCode(result.exitCode);
|
|
@@ -8188,7 +8695,7 @@ function sessionOptionToken(option) {
|
|
|
8188
8695
|
}
|
|
8189
8696
|
|
|
8190
8697
|
// src/commands/session/archive.ts
|
|
8191
|
-
import { mkdir, rename, stat as
|
|
8698
|
+
import { mkdir, rename, stat as stat3 } from "fs/promises";
|
|
8192
8699
|
import { dirname as dirname7, join as join8 } from "path";
|
|
8193
8700
|
|
|
8194
8701
|
// src/domains/session/archive.ts
|
|
@@ -8501,7 +9008,7 @@ var SessionAlreadyArchivedError = class extends Error {
|
|
|
8501
9008
|
};
|
|
8502
9009
|
async function fileExists(path7) {
|
|
8503
9010
|
try {
|
|
8504
|
-
const stats = await
|
|
9011
|
+
const stats = await stat3(path7);
|
|
8505
9012
|
return stats.isFile();
|
|
8506
9013
|
} catch {
|
|
8507
9014
|
return false;
|
|
@@ -8543,7 +9050,7 @@ async function archiveCommand(options) {
|
|
|
8543
9050
|
}
|
|
8544
9051
|
|
|
8545
9052
|
// src/commands/session/delete.ts
|
|
8546
|
-
import { stat as
|
|
9053
|
+
import { stat as stat4, unlink } from "fs/promises";
|
|
8547
9054
|
|
|
8548
9055
|
// src/domains/session/delete.ts
|
|
8549
9056
|
function resolveDeletePath(sessionId, existingPaths) {
|
|
@@ -8716,44 +9223,6 @@ function parseSessionId(id) {
|
|
|
8716
9223
|
return date;
|
|
8717
9224
|
}
|
|
8718
9225
|
|
|
8719
|
-
// src/domains/session/types.ts
|
|
8720
|
-
var SESSION_PRIORITY = {
|
|
8721
|
-
HIGH: "high",
|
|
8722
|
-
MEDIUM: "medium",
|
|
8723
|
-
LOW: "low"
|
|
8724
|
-
};
|
|
8725
|
-
var SESSION_STATUSES = ["todo", "doing", "archive"];
|
|
8726
|
-
var DEFAULT_LIST_STATUSES = ["doing", "todo"];
|
|
8727
|
-
var SESSION_FILE_ENCODING = "utf-8";
|
|
8728
|
-
var SESSION_FILE_ERROR_CODE = {
|
|
8729
|
-
NOT_FOUND: "ENOENT"
|
|
8730
|
-
};
|
|
8731
|
-
var SESSION_OUTPUT_MARKER = {
|
|
8732
|
-
HANDOFF_ID: "HANDOFF_ID",
|
|
8733
|
-
PICKUP_ID: "PICKUP_ID",
|
|
8734
|
-
SESSION_FILE: "SESSION_FILE"
|
|
8735
|
-
};
|
|
8736
|
-
function formatSessionOutputMarker(marker, value) {
|
|
8737
|
-
return `<${marker}>${value}</${marker}>`;
|
|
8738
|
-
}
|
|
8739
|
-
var CLAIMABLE_STATUS = SESSION_STATUSES[0];
|
|
8740
|
-
var PRIORITY_ORDER = {
|
|
8741
|
-
[SESSION_PRIORITY.HIGH]: 0,
|
|
8742
|
-
[SESSION_PRIORITY.MEDIUM]: 1,
|
|
8743
|
-
[SESSION_PRIORITY.LOW]: 2
|
|
8744
|
-
};
|
|
8745
|
-
var DEFAULT_PRIORITY = SESSION_PRIORITY.MEDIUM;
|
|
8746
|
-
var SESSION_FRONT_MATTER = {
|
|
8747
|
-
PRIORITY: "priority",
|
|
8748
|
-
GIT_REF: "git_ref",
|
|
8749
|
-
GOAL: "goal",
|
|
8750
|
-
NEXT_STEP: "next_step",
|
|
8751
|
-
CREATED_AT: "created_at",
|
|
8752
|
-
AGENT_SESSION_ID: "agent_session_id",
|
|
8753
|
-
SPECS: "specs",
|
|
8754
|
-
FILES: "files"
|
|
8755
|
-
};
|
|
8756
|
-
|
|
8757
9226
|
// src/domains/session/list.ts
|
|
8758
9227
|
var FRONT_MATTER_PATTERN = /^---\r?\n([\s\S]*?)\r?\n(?:---|\.\.\.)\r?\n?/;
|
|
8759
9228
|
var SESSION_PRIORITY_VALUES = Object.values(SESSION_PRIORITY);
|
|
@@ -8992,7 +9461,7 @@ async function findExistingPaths(paths) {
|
|
|
8992
9461
|
const existing = [];
|
|
8993
9462
|
for (const path7 of paths) {
|
|
8994
9463
|
try {
|
|
8995
|
-
const stats = await
|
|
9464
|
+
const stats = await stat4(path7);
|
|
8996
9465
|
if (stats.isFile()) {
|
|
8997
9466
|
existing.push(path7);
|
|
8998
9467
|
}
|
|
@@ -9014,7 +9483,7 @@ async function deleteCommand(options) {
|
|
|
9014
9483
|
}
|
|
9015
9484
|
|
|
9016
9485
|
// src/commands/session/handoff.ts
|
|
9017
|
-
import { mkdir as mkdir2, stat as
|
|
9486
|
+
import { mkdir as mkdir2, stat as stat5, writeFile } from "fs/promises";
|
|
9018
9487
|
import { join as join10, resolve as resolve6 } from "path";
|
|
9019
9488
|
import { stringify as stringifyYaml3 } from "yaml";
|
|
9020
9489
|
|
|
@@ -9265,7 +9734,7 @@ async function rejectDirectoryInjectionEntries(entries, cwd) {
|
|
|
9265
9734
|
if (entry.length === 0) continue;
|
|
9266
9735
|
let entryIsDirectory = false;
|
|
9267
9736
|
try {
|
|
9268
|
-
entryIsDirectory = (await
|
|
9737
|
+
entryIsDirectory = (await stat5(resolve6(cwd, entry))).isDirectory();
|
|
9269
9738
|
} catch {
|
|
9270
9739
|
continue;
|
|
9271
9740
|
}
|
|
@@ -9323,7 +9792,7 @@ ${formatSessionOutputMarker(SESSION_OUTPUT_MARKER.SESSION_FILE, absolutePath)}`;
|
|
|
9323
9792
|
}
|
|
9324
9793
|
|
|
9325
9794
|
// src/commands/session/list.ts
|
|
9326
|
-
import { readdir as
|
|
9795
|
+
import { readdir as readdir3, readFile as readFile5 } from "fs/promises";
|
|
9327
9796
|
import { join as join11 } from "path";
|
|
9328
9797
|
var SESSION_LIST_FORMAT = {
|
|
9329
9798
|
TEXT: "text",
|
|
@@ -9332,7 +9801,7 @@ var SESSION_LIST_FORMAT = {
|
|
|
9332
9801
|
var SESSION_LIST_EMPTY_TEXT = "(no sessions)";
|
|
9333
9802
|
async function loadSessionsFromDir(dir, status) {
|
|
9334
9803
|
try {
|
|
9335
|
-
const files = await
|
|
9804
|
+
const files = await readdir3(dir);
|
|
9336
9805
|
const sessions = [];
|
|
9337
9806
|
for (const file of files) {
|
|
9338
9807
|
if (!file.endsWith(".md")) continue;
|
|
@@ -9401,7 +9870,7 @@ async function listCommand(options) {
|
|
|
9401
9870
|
}
|
|
9402
9871
|
|
|
9403
9872
|
// src/commands/session/pickup.ts
|
|
9404
|
-
import { mkdir as mkdir3, readdir as
|
|
9873
|
+
import { mkdir as mkdir3, readdir as readdir4, readFile as readFile6, rename as rename2 } from "fs/promises";
|
|
9405
9874
|
import { join as join12, resolve as resolve7 } from "path";
|
|
9406
9875
|
|
|
9407
9876
|
// src/domains/session/pickup.ts
|
|
@@ -9441,7 +9910,7 @@ function selectBestSession(sessions) {
|
|
|
9441
9910
|
var PICKUP_TARGET_STATUS = SESSION_STATUSES[1];
|
|
9442
9911
|
var PICKUP_DEPS = {
|
|
9443
9912
|
mkdir: mkdir3,
|
|
9444
|
-
readdir:
|
|
9913
|
+
readdir: readdir4,
|
|
9445
9914
|
readFile: readFile6,
|
|
9446
9915
|
rename: rename2
|
|
9447
9916
|
};
|
|
@@ -9551,7 +10020,7 @@ async function loadPickCandidates(options) {
|
|
|
9551
10020
|
}
|
|
9552
10021
|
|
|
9553
10022
|
// src/commands/session/prune.ts
|
|
9554
|
-
import { readdir as
|
|
10023
|
+
import { readdir as readdir5, readFile as readFile7, unlink as unlink2 } from "fs/promises";
|
|
9555
10024
|
import { join as join13 } from "path";
|
|
9556
10025
|
|
|
9557
10026
|
// src/domains/session/prune.ts
|
|
@@ -9596,7 +10065,7 @@ function validatePruneOptions(options) {
|
|
|
9596
10065
|
}
|
|
9597
10066
|
async function loadArchiveSessions(config) {
|
|
9598
10067
|
try {
|
|
9599
|
-
const files = await
|
|
10068
|
+
const files = await readdir5(config.archiveDir);
|
|
9600
10069
|
const sessions = [];
|
|
9601
10070
|
for (const file of files) {
|
|
9602
10071
|
if (!file.endsWith(".md")) continue;
|
|
@@ -9651,7 +10120,7 @@ async function pruneCommand(options) {
|
|
|
9651
10120
|
}
|
|
9652
10121
|
|
|
9653
10122
|
// src/commands/session/release.ts
|
|
9654
|
-
import { readdir as
|
|
10123
|
+
import { readdir as readdir6, rename as rename3 } from "fs/promises";
|
|
9655
10124
|
|
|
9656
10125
|
// src/domains/session/release.ts
|
|
9657
10126
|
function buildReleasePaths(sessionId, config) {
|
|
@@ -9682,7 +10151,7 @@ var SESSION_RELEASE_OUTPUT = {
|
|
|
9682
10151
|
};
|
|
9683
10152
|
async function loadDoingSessions(config) {
|
|
9684
10153
|
try {
|
|
9685
|
-
const files = await
|
|
10154
|
+
const files = await readdir6(config.doingDir);
|
|
9686
10155
|
return files.filter((file) => file.endsWith(".md")).map((file) => ({ id: file.replace(".md", "") }));
|
|
9687
10156
|
} catch (error) {
|
|
9688
10157
|
if (error instanceof Error && "code" in error && error.code === SESSION_FILE_ERROR_CODE.NOT_FOUND) {
|
|
@@ -9719,12 +10188,12 @@ async function releaseCommand(options) {
|
|
|
9719
10188
|
}
|
|
9720
10189
|
|
|
9721
10190
|
// src/commands/session/show.ts
|
|
9722
|
-
import { readFile as readFile8, stat as
|
|
10191
|
+
import { readFile as readFile8, stat as stat6 } from "fs/promises";
|
|
9723
10192
|
async function findExistingPath(paths, _config) {
|
|
9724
10193
|
for (let i = 0; i < paths.length; i++) {
|
|
9725
10194
|
const filePath = paths[i];
|
|
9726
10195
|
try {
|
|
9727
|
-
const stats = await
|
|
10196
|
+
const stats = await stat6(filePath);
|
|
9728
10197
|
if (stats.isFile()) {
|
|
9729
10198
|
return { path: filePath, status: SEARCH_ORDER[i] };
|
|
9730
10199
|
}
|
|
@@ -10403,7 +10872,7 @@ var sessionDomain = {
|
|
|
10403
10872
|
};
|
|
10404
10873
|
|
|
10405
10874
|
// src/lib/spec-tree/index.ts
|
|
10406
|
-
import { readdir as
|
|
10875
|
+
import { readdir as readdir7, readFile as readFile9 } from "fs/promises";
|
|
10407
10876
|
import { join as join14 } from "path";
|
|
10408
10877
|
var SPEC_TREE_FIELD_KEY = {
|
|
10409
10878
|
VERSION: "version",
|
|
@@ -10779,7 +11248,7 @@ async function* readFilesystemSourceEntries(productDir, registry, schemaVersions
|
|
|
10779
11248
|
async function* walkFilesystemDirectory(context) {
|
|
10780
11249
|
let entries;
|
|
10781
11250
|
try {
|
|
10782
|
-
entries = await
|
|
11251
|
+
entries = await readdir7(context.absolutePath, { withFileTypes: true });
|
|
10783
11252
|
} catch (error) {
|
|
10784
11253
|
if (isFileNotFound2(error)) return;
|
|
10785
11254
|
throw error;
|
|
@@ -10951,8 +11420,8 @@ function formatNextNode(node) {
|
|
|
10951
11420
|
}
|
|
10952
11421
|
|
|
10953
11422
|
// src/commands/test/discovery.ts
|
|
10954
|
-
import { readdir as
|
|
10955
|
-
import { join as join15, relative, sep } from "path";
|
|
11423
|
+
import { readdir as readdir8 } from "fs/promises";
|
|
11424
|
+
import { join as join15, relative as relative2, sep as sep2 } from "path";
|
|
10956
11425
|
var TESTS_DIRECTORY_NAME = SPEC_TREE_EVIDENCE_FILE.DIRECTORY_NAME;
|
|
10957
11426
|
var SPEC_ROOT_DIRECTORY = SPEC_TREE_CONFIG.ROOT_DIRECTORY;
|
|
10958
11427
|
var POSIX_SEPARATOR = "/";
|
|
@@ -10965,7 +11434,7 @@ async function discoverTestFiles(productDir) {
|
|
|
10965
11434
|
async function collectTestFiles(directory, insideTestsDir, found) {
|
|
10966
11435
|
let entries;
|
|
10967
11436
|
try {
|
|
10968
|
-
entries = await
|
|
11437
|
+
entries = await readdir8(directory, { withFileTypes: true });
|
|
10969
11438
|
} catch (error) {
|
|
10970
11439
|
if (hasErrorCode2(error, ERROR_CODE_NOT_FOUND2)) return;
|
|
10971
11440
|
throw error;
|
|
@@ -10980,7 +11449,7 @@ async function collectTestFiles(directory, insideTestsDir, found) {
|
|
|
10980
11449
|
}
|
|
10981
11450
|
}
|
|
10982
11451
|
function toPosixRelative(productDir, absolute) {
|
|
10983
|
-
return
|
|
11452
|
+
return relative2(productDir, absolute).split(sep2).join(POSIX_SEPARATOR);
|
|
10984
11453
|
}
|
|
10985
11454
|
function compareAscii(left, right) {
|
|
10986
11455
|
if (left < right) return -1;
|
|
@@ -11511,9 +11980,15 @@ function toErrorMessage3(error) {
|
|
|
11511
11980
|
// src/lib/git/name-status.ts
|
|
11512
11981
|
var GIT_NAME_STATUS_FLAG = "--name-status";
|
|
11513
11982
|
var GIT_NULL_DELIMITED_FLAG = "-z";
|
|
11983
|
+
var GIT_DIFF_COMMAND = "diff";
|
|
11984
|
+
var GIT_RANGE_SEPARATOR = "..";
|
|
11985
|
+
function changesetNameStatusArgs(base, head) {
|
|
11986
|
+
return [GIT_DIFF_COMMAND, GIT_NAME_STATUS_FLAG, GIT_NULL_DELIMITED_FLAG, `${base}${GIT_RANGE_SEPARATOR}${head}`];
|
|
11987
|
+
}
|
|
11514
11988
|
var GIT_RENAME_STATUS_PREFIX = "R";
|
|
11515
11989
|
var GIT_COPY_STATUS_PREFIX = "C";
|
|
11516
|
-
var
|
|
11990
|
+
var GIT_NULL_RECORD_SEPARATOR = "\0";
|
|
11991
|
+
var NULL_RECORD_SEPARATOR = GIT_NULL_RECORD_SEPARATOR;
|
|
11517
11992
|
function sortedPathSet(paths) {
|
|
11518
11993
|
return [...paths].sort(compareAsciiStrings);
|
|
11519
11994
|
}
|
|
@@ -11569,7 +12044,7 @@ var CHANGED_TEST_DIFF_NAME_STATUS_FLAG = GIT_NAME_STATUS_FLAG;
|
|
|
11569
12044
|
var CHANGED_TEST_NULL_DELIMITED_FLAG = GIT_NULL_DELIMITED_FLAG;
|
|
11570
12045
|
var CHANGED_TEST_LS_FILES_OTHERS_FLAG = "--others";
|
|
11571
12046
|
var CHANGED_TEST_LS_FILES_EXCLUDE_STANDARD_FLAG = "--exclude-standard";
|
|
11572
|
-
var
|
|
12047
|
+
var CHANGED_TEST_LS_FILES_CACHED_FLAG = "--cached";
|
|
11573
12048
|
var HEAD_REF = "HEAD";
|
|
11574
12049
|
var ORIGIN_REMOTE = "origin";
|
|
11575
12050
|
var REF_SEPARATOR = "/";
|
|
@@ -11673,7 +12148,7 @@ async function stagedCandidateTestPaths(productDir, git) {
|
|
|
11673
12148
|
}
|
|
11674
12149
|
const result = await runner(
|
|
11675
12150
|
GIT_ROOT_COMMAND.EXECUTABLE,
|
|
11676
|
-
[CHANGED_TEST_LS_FILES_COMMAND,
|
|
12151
|
+
[CHANGED_TEST_LS_FILES_COMMAND, CHANGED_TEST_LS_FILES_CACHED_FLAG, CHANGED_TEST_NULL_DELIMITED_FLAG],
|
|
11677
12152
|
{ cwd: productDir, reject: false }
|
|
11678
12153
|
);
|
|
11679
12154
|
if (result.exitCode !== 0) {
|
|
@@ -11699,14 +12174,14 @@ async function readStagedFile(productDir, path7, git) {
|
|
|
11699
12174
|
}
|
|
11700
12175
|
return result.stdout;
|
|
11701
12176
|
}
|
|
11702
|
-
async function relatedTestPaths(sourceFiles, options, baseRef,
|
|
12177
|
+
async function relatedTestPaths(sourceFiles, options, baseRef, candidateTestPaths2, deps) {
|
|
11703
12178
|
const testPaths = [];
|
|
11704
12179
|
const resolved = /* @__PURE__ */ new Set();
|
|
11705
12180
|
for (const language of deps.registry.languages) {
|
|
11706
12181
|
if (language.relatedTestPaths === void 0) continue;
|
|
11707
12182
|
const relatedDeps = deps.relatedDepsFor(language.name);
|
|
11708
12183
|
const languageResolution = await language.relatedTestPaths(
|
|
11709
|
-
{ projectRoot: options.productDir, sourcePaths: sourceFiles, candidateTestPaths, baseRef },
|
|
12184
|
+
{ projectRoot: options.productDir, sourcePaths: sourceFiles, candidateTestPaths: candidateTestPaths2, baseRef },
|
|
11710
12185
|
options.staged === true ? { ...relatedDeps, readFile: (path7) => readStagedFile(options.productDir, path7, deps.git) } : relatedDeps
|
|
11711
12186
|
);
|
|
11712
12187
|
if (languageResolution.testPaths.length > 0) {
|
|
@@ -11726,6 +12201,9 @@ function changedTestProductInputPaths(registry) {
|
|
|
11726
12201
|
...registry.languages.flatMap((language) => language.productInputPaths)
|
|
11727
12202
|
].sort(compareAsciiStrings);
|
|
11728
12203
|
}
|
|
12204
|
+
async function candidateTestPaths(options, git) {
|
|
12205
|
+
return options.staged === true ? stagedCandidateTestPaths(options.productDir, git) : discoverTestFiles(options.productDir);
|
|
12206
|
+
}
|
|
11729
12207
|
async function planChangedTestSelection(options, deps) {
|
|
11730
12208
|
const baseRef = options.baseRef ?? await defaultBaseRef(options.productDir, deps.git);
|
|
11731
12209
|
const [baseSha, headSha] = await Promise.all([
|
|
@@ -11734,16 +12212,20 @@ async function planChangedTestSelection(options, deps) {
|
|
|
11734
12212
|
]);
|
|
11735
12213
|
const paths = await changedPaths(options.productDir, baseSha, options.staged === true, deps.git);
|
|
11736
12214
|
const partition = partitionChangedPaths(paths, changedTestProductInputPaths(deps.registry));
|
|
11737
|
-
|
|
11738
|
-
|
|
11739
|
-
|
|
11740
|
-
|
|
11741
|
-
const
|
|
12215
|
+
const testPaths = partition.sourceFiles.length > 0 || partition.operands.length > 0 ? await candidateTestPaths(options, deps.git) : [];
|
|
12216
|
+
const pathSelectedTests = partition.operands.length === 0 ? [] : resolveTargetedTestFiles(testPaths, { operands: partition.operands, recursive: true }).selected;
|
|
12217
|
+
const related = partition.sourceFiles.length === 0 ? { testPaths: [], unresolved: [] } : await relatedTestPaths(partition.sourceFiles, options, baseRef, testPaths, deps);
|
|
12218
|
+
const dispatchOperands = mergeChangedSetOperands(pathSelectedTests, related.testPaths);
|
|
12219
|
+
const dirtyOperands = partition.operands.length === 0 ? dispatchOperands : mergeChangedSetOperands(partition.operands, related.testPaths);
|
|
11742
12220
|
return {
|
|
11743
12221
|
targets: {
|
|
11744
|
-
operands: partition.productInputChanged ? [SPEC_ROOT_OPERAND] :
|
|
12222
|
+
operands: partition.productInputChanged ? [SPEC_ROOT_OPERAND] : dispatchOperands,
|
|
11745
12223
|
recursive: partition.productInputChanged
|
|
11746
12224
|
},
|
|
12225
|
+
dirtyTargets: {
|
|
12226
|
+
operands: partition.productInputChanged ? [SPEC_ROOT_OPERAND] : dirtyOperands,
|
|
12227
|
+
recursive: partition.productInputChanged || partition.operands.length > 0
|
|
12228
|
+
},
|
|
11747
12229
|
baseRef,
|
|
11748
12230
|
baseSha,
|
|
11749
12231
|
headSha,
|
|
@@ -12070,11 +12552,12 @@ async function runTestsCommand(options, deps) {
|
|
|
12070
12552
|
throw new Error(CHANGED_TEST_STAGED_SELECTION_MISSING_ERROR);
|
|
12071
12553
|
}
|
|
12072
12554
|
selectedTargets = mergeTargetSelections(options.targets, changedSelection2.targets) ?? changedSelection2.targets;
|
|
12555
|
+
const dirtyTargets = mergeTargetSelections(options.targets, changedSelection2.dirtyTargets) ?? changedSelection2.dirtyTargets;
|
|
12073
12556
|
await requireWorktreeMatchesIndexForStagedRun(
|
|
12074
12557
|
options.productDir,
|
|
12075
12558
|
changedGit,
|
|
12076
12559
|
changedSelection2,
|
|
12077
|
-
|
|
12560
|
+
dirtyTargets,
|
|
12078
12561
|
passingScope
|
|
12079
12562
|
);
|
|
12080
12563
|
}
|
|
@@ -12362,7 +12845,7 @@ function createNodeStatusProvider(productDir) {
|
|
|
12362
12845
|
}
|
|
12363
12846
|
|
|
12364
12847
|
// src/lib/node-status/update.ts
|
|
12365
|
-
import { mkdir as mkdir4, readdir as
|
|
12848
|
+
import { mkdir as mkdir4, readdir as readdir9, rm, writeFile as writeFile2 } from "fs/promises";
|
|
12366
12849
|
import { dirname as dirname8, join as join21 } from "path";
|
|
12367
12850
|
|
|
12368
12851
|
// src/git/tracked-paths.ts
|
|
@@ -12486,7 +12969,7 @@ async function collectNodeStatusFiles(directory) {
|
|
|
12486
12969
|
}
|
|
12487
12970
|
async function readDirectoryEntries(directory) {
|
|
12488
12971
|
try {
|
|
12489
|
-
return await
|
|
12972
|
+
return await readdir9(directory, { withFileTypes: true });
|
|
12490
12973
|
} catch (error) {
|
|
12491
12974
|
if (isNodeError3(error) && error.code === "ENOENT") return [];
|
|
12492
12975
|
throw error;
|
|
@@ -13606,14 +14089,14 @@ var testingDomain = createTestingDomain();
|
|
|
13606
14089
|
|
|
13607
14090
|
// src/interfaces/cli/validation.ts
|
|
13608
14091
|
import { existsSync as existsSync9, realpathSync } from "fs";
|
|
13609
|
-
import { isAbsolute as
|
|
14092
|
+
import { isAbsolute as isAbsolute11, relative as relative9, resolve as resolve11, sep as sep4 } from "path";
|
|
13610
14093
|
|
|
13611
14094
|
// src/commands/validation/formatting.ts
|
|
13612
14095
|
import { existsSync, statSync } from "fs";
|
|
13613
|
-
import { isAbsolute as
|
|
14096
|
+
import { isAbsolute as isAbsolute4, join as join24, relative as relative4 } from "path";
|
|
13614
14097
|
|
|
13615
14098
|
// src/validation/config/path-filter.ts
|
|
13616
|
-
import { isAbsolute as
|
|
14099
|
+
import { isAbsolute as isAbsolute3, relative as relative3 } from "path";
|
|
13617
14100
|
function hasEffectiveValidationPathMetadata(filter) {
|
|
13618
14101
|
return "hasIncludeFilter" in filter && typeof filter.hasIncludeFilter === "boolean" && "noMatchingIncludes" in filter && typeof filter.noMatchingIncludes === "boolean";
|
|
13619
14102
|
}
|
|
@@ -13624,7 +14107,7 @@ function nonEmpty(values) {
|
|
|
13624
14107
|
return values?.filter((value) => value.length > 0) ?? [];
|
|
13625
14108
|
}
|
|
13626
14109
|
function toProjectRelativeValidationPath(projectRoot, path7) {
|
|
13627
|
-
return
|
|
14110
|
+
return isAbsolute3(path7) ? relative3(projectRoot, path7) : path7;
|
|
13628
14111
|
}
|
|
13629
14112
|
function intersectIncludes(baseInclude, toolInclude) {
|
|
13630
14113
|
const base = nonEmpty(baseInclude);
|
|
@@ -13882,12 +14365,12 @@ async function formattingCommand(options) {
|
|
|
13882
14365
|
const scopedFiles = hasExplicitScope ? files.flatMap(
|
|
13883
14366
|
(filePath) => formattingPathOperandsForValidationPathFilter(
|
|
13884
14367
|
cwd,
|
|
13885
|
-
|
|
14368
|
+
isAbsolute4(filePath) ? relative4(cwd, filePath) : filePath,
|
|
13886
14369
|
pathFilter
|
|
13887
14370
|
)
|
|
13888
14371
|
) : void 0;
|
|
13889
14372
|
const scopedExcludes = hasExplicitScope && files.some(
|
|
13890
|
-
(filePath) => isFormattingFileOperand(cwd,
|
|
14373
|
+
(filePath) => isFormattingFileOperand(cwd, isAbsolute4(filePath) ? relative4(cwd, filePath) : filePath)
|
|
13891
14374
|
) ? [] : validationPathFilterExcludes(pathFilter);
|
|
13892
14375
|
if (hasExplicitScope && (scopedFiles === void 0 || scopedFiles.length === 0)) {
|
|
13893
14376
|
const output2 = quiet ? "" : `${VALIDATION_STAGE_DISPLAY_NAMES.FORMATTING}: skipped (${FORMATTING_COMMAND_OUTPUT.EMPTY_SCOPE_REASON})`;
|
|
@@ -13941,7 +14424,7 @@ var formattingValidationLanguage = {
|
|
|
13941
14424
|
};
|
|
13942
14425
|
|
|
13943
14426
|
// src/commands/validation/markdown.ts
|
|
13944
|
-
import { isAbsolute as
|
|
14427
|
+
import { isAbsolute as isAbsolute5, join as join26 } from "path";
|
|
13945
14428
|
|
|
13946
14429
|
// src/validation/steps/markdown.ts
|
|
13947
14430
|
import { existsSync as existsSync2, statSync as statSync2 } from "fs";
|
|
@@ -14247,7 +14730,7 @@ async function markdownCommand(options) {
|
|
|
14247
14730
|
return formatMarkdownResult(result, skippedOutput, quiet, durationMs);
|
|
14248
14731
|
}
|
|
14249
14732
|
function markdownValidationOperandPath(productDir, filePath) {
|
|
14250
|
-
return
|
|
14733
|
+
return isAbsolute5(filePath) ? filePath : join26(productDir, filePath);
|
|
14251
14734
|
}
|
|
14252
14735
|
function defaultMarkdownTargets(productDir, pathFilter) {
|
|
14253
14736
|
return getDefaultDirectories(productDir).flatMap(
|
|
@@ -15147,7 +15630,7 @@ var ParseErrorCode;
|
|
|
15147
15630
|
|
|
15148
15631
|
// src/validation/config/scope.ts
|
|
15149
15632
|
import { existsSync as existsSync3, readdirSync, readFileSync as readFileSync3 } from "fs";
|
|
15150
|
-
import { isAbsolute as
|
|
15633
|
+
import { isAbsolute as isAbsolute6, join as join27, relative as relative5, resolve as resolve9 } from "path";
|
|
15151
15634
|
var TSCONFIG_FILES = {
|
|
15152
15635
|
full: "tsconfig.json",
|
|
15153
15636
|
production: "tsconfig.production.json"
|
|
@@ -15184,7 +15667,7 @@ var EXPLICIT_TYPESCRIPT_SCOPE_TARGET_KIND = {
|
|
|
15184
15667
|
FILE: "file"
|
|
15185
15668
|
};
|
|
15186
15669
|
function resolveProjectPath(projectRoot, path7) {
|
|
15187
|
-
return
|
|
15670
|
+
return isAbsolute6(path7) ? path7 : join27(projectRoot, path7);
|
|
15188
15671
|
}
|
|
15189
15672
|
function parseTypeScriptConfig(configPath, deps = defaultScopeDeps) {
|
|
15190
15673
|
try {
|
|
@@ -15521,14 +16004,14 @@ function pathPassesTypeScriptScope(path7, scopeConfig) {
|
|
|
15521
16004
|
return included && !excluded;
|
|
15522
16005
|
}
|
|
15523
16006
|
function pathStaysInsideTypeScriptScopeRoot(projectRoot, path7) {
|
|
15524
|
-
const resolvedPath =
|
|
15525
|
-
const relativePath =
|
|
16007
|
+
const resolvedPath = isAbsolute6(path7) ? resolve9(path7) : resolve9(projectRoot, path7);
|
|
16008
|
+
const relativePath = relative5(projectRoot, resolvedPath);
|
|
15526
16009
|
const segments = normalizeTypeScriptScopePath(relativePath).split(PATH_SEGMENT_SEPARATOR4);
|
|
15527
|
-
return relativePath.length === 0 || !segments.includes("..") && !
|
|
16010
|
+
return relativePath.length === 0 || !segments.includes("..") && !isAbsolute6(relativePath);
|
|
15528
16011
|
}
|
|
15529
16012
|
function toProjectRelativeTypeScriptScopePath(projectRoot, path7) {
|
|
15530
|
-
const resolvedPath =
|
|
15531
|
-
const relativePath =
|
|
16013
|
+
const resolvedPath = isAbsolute6(path7) ? resolve9(path7) : resolve9(projectRoot, path7);
|
|
16014
|
+
const relativePath = relative5(projectRoot, resolvedPath);
|
|
15532
16015
|
return relativePath.length === 0 ? TYPESCRIPT_SCOPE_PROJECT_ROOT : normalizeTypeScriptScopePath(relativePath);
|
|
15533
16016
|
}
|
|
15534
16017
|
function toExplicitTypeScriptScopeTarget(projectRoot, originalPath, deps = defaultScopeDeps) {
|
|
@@ -16227,7 +16710,7 @@ async function circularCommand(options, deps = defaultCircularCommandDeps) {
|
|
|
16227
16710
|
// src/validation/steps/knip.ts
|
|
16228
16711
|
import { existsSync as existsSync4 } from "fs";
|
|
16229
16712
|
import { mkdir as mkdir5, mkdtemp as mkdtemp2, rm as rm2, writeFile as writeFile3 } from "fs/promises";
|
|
16230
|
-
import { isAbsolute as
|
|
16713
|
+
import { isAbsolute as isAbsolute7, join as join29 } from "path";
|
|
16231
16714
|
var defaultKnipProcessRunner = lifecycleProcessRunner;
|
|
16232
16715
|
var KNIP_COMMAND_TOKENS = {
|
|
16233
16716
|
COMMAND: "knip",
|
|
@@ -16319,7 +16802,7 @@ async function createScopedKnipTsconfig(projectRoot, typescriptScope, deps) {
|
|
|
16319
16802
|
await deps.mkdir(tempParentDir, { recursive: true });
|
|
16320
16803
|
const tempDir = await deps.mkdtemp(join29(tempParentDir, "validate-knip-"));
|
|
16321
16804
|
const configPath = join29(tempDir, TSCONFIG_FILES.full);
|
|
16322
|
-
const toProjectPathPattern = (pattern) =>
|
|
16805
|
+
const toProjectPathPattern = (pattern) => isAbsolute7(pattern) ? pattern : join29(projectRoot, pattern);
|
|
16323
16806
|
const project = [
|
|
16324
16807
|
...typescriptScope.directories.flatMap(
|
|
16325
16808
|
(directory) => TYPESCRIPT_FALLBACK_INCLUDE_PATTERNS.map((pattern) => `${directory}/${pattern}`)
|
|
@@ -16846,16 +17329,16 @@ function formatLintResult(result, quiet, durationMs) {
|
|
|
16846
17329
|
|
|
16847
17330
|
// src/validation/literal/index.ts
|
|
16848
17331
|
import { readFile as readFile12 } from "fs/promises";
|
|
16849
|
-
import { isAbsolute as
|
|
17332
|
+
import { isAbsolute as isAbsolute9, relative as relative7, resolve as resolve10 } from "path";
|
|
16850
17333
|
|
|
16851
17334
|
// src/lib/file-inclusion/pipeline.ts
|
|
16852
|
-
import { readdir as
|
|
16853
|
-
import { join as join33, relative as
|
|
17335
|
+
import { readdir as readdir10, readFile as readFile11, stat as stat7 } from "fs/promises";
|
|
17336
|
+
import { join as join33, relative as relative6, sep as sep3 } from "path";
|
|
16854
17337
|
|
|
16855
17338
|
// src/lib/file-inclusion/ignore-source.ts
|
|
16856
17339
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
16857
17340
|
import { existsSync as existsSync7 } from "fs";
|
|
16858
|
-
import { isAbsolute as
|
|
17341
|
+
import { isAbsolute as isAbsolute8, join as join32 } from "path";
|
|
16859
17342
|
var GIT_EXECUTABLE2 = "git";
|
|
16860
17343
|
var GIT_LS_FILES_ARGS = {
|
|
16861
17344
|
LS_FILES: "ls-files",
|
|
@@ -16973,7 +17456,7 @@ function optionalExcludeFromArgs(path7) {
|
|
|
16973
17456
|
return existsSync7(path7) ? excludeFromArgs(path7) : [];
|
|
16974
17457
|
}
|
|
16975
17458
|
function resolveGitPath(productDir, path7) {
|
|
16976
|
-
return
|
|
17459
|
+
return isAbsolute8(path7) ? path7 : join32(productDir, path7);
|
|
16977
17460
|
}
|
|
16978
17461
|
function readInfoExcludePath(productDir) {
|
|
16979
17462
|
const commonDir = readOptionalGit(productDir, [
|
|
@@ -17155,7 +17638,7 @@ function isNodeError4(err) {
|
|
|
17155
17638
|
}
|
|
17156
17639
|
async function isDirectory(absolutePath) {
|
|
17157
17640
|
try {
|
|
17158
|
-
return (await
|
|
17641
|
+
return (await stat7(absolutePath)).isDirectory();
|
|
17159
17642
|
} catch (err) {
|
|
17160
17643
|
if (isNodeError4(err) && (err.code === "ENOENT" || err.code === "ENOTDIR")) {
|
|
17161
17644
|
return false;
|
|
@@ -17176,7 +17659,7 @@ async function isGitdirPointerFile(absolutePath) {
|
|
|
17176
17659
|
}
|
|
17177
17660
|
async function readDirectoryEntries2(absoluteDir) {
|
|
17178
17661
|
try {
|
|
17179
|
-
return await
|
|
17662
|
+
return await readdir10(absoluteDir, { withFileTypes: true });
|
|
17180
17663
|
} catch (err) {
|
|
17181
17664
|
if (isNodeError4(err) && (err.code === "ENOENT" || err.code === "ENOTDIR")) {
|
|
17182
17665
|
return [];
|
|
@@ -17185,8 +17668,8 @@ async function readDirectoryEntries2(absoluteDir) {
|
|
|
17185
17668
|
}
|
|
17186
17669
|
}
|
|
17187
17670
|
function normalizeProductPath(productDir, absolutePath) {
|
|
17188
|
-
const rel =
|
|
17189
|
-
return
|
|
17671
|
+
const rel = relative6(productDir, absolutePath);
|
|
17672
|
+
return sep3 === "/" ? rel : rel.split(sep3).join("/");
|
|
17190
17673
|
}
|
|
17191
17674
|
async function isGitMetadataEntry(absolutePath, entry) {
|
|
17192
17675
|
if (entry.name !== GIT_INTERNAL_DIRECTORY) return false;
|
|
@@ -17734,8 +18217,8 @@ var DEFAULT_LITERAL_COLLECT_OPTIONS = {
|
|
|
17734
18217
|
async function validateLiteralReuse(input) {
|
|
17735
18218
|
const config = input.config ?? literalConfigDescriptor.defaults;
|
|
17736
18219
|
const explicitPaths = input.explicitFiles?.map((f) => {
|
|
17737
|
-
const abs =
|
|
17738
|
-
return
|
|
18220
|
+
const abs = isAbsolute9(f) ? f : resolve10(input.productDir, f);
|
|
18221
|
+
return relative7(input.productDir, abs).split(/[\\/]/g).join("/");
|
|
17739
18222
|
});
|
|
17740
18223
|
if (input.pathConfig !== void 0 && (explicitPaths === void 0 || explicitPaths.length === 0) && validationPathFilterHasNoMatchingIncludes(input.pathConfig)) {
|
|
17741
18224
|
return {
|
|
@@ -17764,7 +18247,7 @@ async function validateLiteralReuse(input) {
|
|
|
17764
18247
|
const testOccurrencesByFile = /* @__PURE__ */ new Map();
|
|
17765
18248
|
const indexedOccurrencesByFile = /* @__PURE__ */ new Map();
|
|
17766
18249
|
for (const abs of candidateFiles) {
|
|
17767
|
-
const rel =
|
|
18250
|
+
const rel = relative7(input.productDir, abs).split(/[\\/]/g).join("/");
|
|
17768
18251
|
const content = await readSafe(abs);
|
|
17769
18252
|
if (content === null) continue;
|
|
17770
18253
|
const occurrences = collectLiterals(content, rel, collectOptions);
|
|
@@ -18064,7 +18547,7 @@ function formatLoc(loc) {
|
|
|
18064
18547
|
// src/validation/steps/typescript.ts
|
|
18065
18548
|
import { existsSync as existsSync8, mkdirSync, rmSync, writeFileSync } from "fs";
|
|
18066
18549
|
import { mkdtemp as mkdtemp3 } from "fs/promises";
|
|
18067
|
-
import { isAbsolute as
|
|
18550
|
+
import { isAbsolute as isAbsolute10, join as join34, relative as relative8 } from "path";
|
|
18068
18551
|
var defaultTypeScriptProcessRunner = lifecycleProcessRunner;
|
|
18069
18552
|
var defaultTypeScriptDeps = {
|
|
18070
18553
|
mkdtemp: mkdtemp3,
|
|
@@ -18091,7 +18574,7 @@ async function createFileSpecificTsconfig(scope2, files, projectRoot, deps = def
|
|
|
18091
18574
|
const tempDir = await createTemporaryTsconfigDir(projectRoot, deps);
|
|
18092
18575
|
const configPath = join34(tempDir, "tsconfig.json");
|
|
18093
18576
|
const baseConfigFile = TSCONFIG_FILES[scope2];
|
|
18094
|
-
const absoluteFiles = files.map((file) =>
|
|
18577
|
+
const absoluteFiles = files.map((file) => isAbsolute10(file) ? file : join34(projectRoot, file));
|
|
18095
18578
|
const tempConfig = {
|
|
18096
18579
|
extends: join34(projectRoot, baseConfigFile),
|
|
18097
18580
|
files: absoluteFiles,
|
|
@@ -18108,8 +18591,8 @@ async function createScopeFilteredTsconfig(scope2, projectRoot, scopeConfig, dep
|
|
|
18108
18591
|
const configPath = join34(tempDir, "tsconfig.json");
|
|
18109
18592
|
const baseConfigFile = TSCONFIG_FILES[scope2];
|
|
18110
18593
|
const toTemporaryConfigPathPattern = (pattern) => {
|
|
18111
|
-
const absolutePattern =
|
|
18112
|
-
return
|
|
18594
|
+
const absolutePattern = isAbsolute10(pattern) ? pattern : join34(projectRoot, pattern);
|
|
18595
|
+
return relative8(tempDir, absolutePattern);
|
|
18113
18596
|
};
|
|
18114
18597
|
const tempConfig = {
|
|
18115
18598
|
extends: join34(projectRoot, baseConfigFile),
|
|
@@ -18651,8 +19134,8 @@ function normalizeProductPathOperand(productDir, effectiveInvocationDir, operand
|
|
|
18651
19134
|
const resolvedProductDir = canonicalExistingPath(resolve11(productDir));
|
|
18652
19135
|
const resolvedInvocationDir = canonicalExistingPath(resolve11(effectiveInvocationDir));
|
|
18653
19136
|
const absoluteOperand = canonicalExistingPath(resolve11(resolvedInvocationDir, operand));
|
|
18654
|
-
const relativeOperand =
|
|
18655
|
-
if (relativeOperand === ".." || relativeOperand.startsWith(`..${
|
|
19137
|
+
const relativeOperand = relative9(resolvedProductDir, absoluteOperand);
|
|
19138
|
+
if (relativeOperand === ".." || relativeOperand.startsWith(`..${sep4}`) || isAbsolute11(relativeOperand)) {
|
|
18656
19139
|
return void 0;
|
|
18657
19140
|
}
|
|
18658
19141
|
return relativeOperand.length > 0 ? relativeOperand.replaceAll("\\", "/") : ".";
|
|
@@ -18856,7 +19339,7 @@ var validationDomain = {
|
|
|
18856
19339
|
};
|
|
18857
19340
|
|
|
18858
19341
|
// src/commands/verification-context/cli.ts
|
|
18859
|
-
import { isAbsolute as
|
|
19342
|
+
import { isAbsolute as isAbsolute12, win32 } from "path";
|
|
18860
19343
|
|
|
18861
19344
|
// src/domains/verification-context/context.ts
|
|
18862
19345
|
var VERIFICATION_CONTEXT_SCHEMA_VERSION = "verification-context.v1";
|
|
@@ -19007,7 +19490,7 @@ function normalizeFileSubjectPath(path7) {
|
|
|
19007
19490
|
VERIFICATION_CONTEXT_FILE_SUBJECT_PATH.SEPARATOR.CANONICAL
|
|
19008
19491
|
);
|
|
19009
19492
|
const segments = normalized.split(VERIFICATION_CONTEXT_FILE_SUBJECT_PATH.SEPARATOR.CANONICAL);
|
|
19010
|
-
if (
|
|
19493
|
+
if (isAbsolute12(path7) || win32.isAbsolute(path7) || windowsRoot.length > 0 || segments.includes(VERIFICATION_CONTEXT_FILE_SUBJECT_PATH.PARENT_DIRECTORY.SEGMENT)) {
|
|
19011
19494
|
return void 0;
|
|
19012
19495
|
}
|
|
19013
19496
|
return normalized;
|
|
@@ -19057,17 +19540,6 @@ async function verificationContextCreateCommand(options, deps = {}) {
|
|
|
19057
19540
|
return okResult2(JSON.stringify(persisted.value));
|
|
19058
19541
|
}
|
|
19059
19542
|
|
|
19060
|
-
// src/interfaces/cli/lib/stream-report.ts
|
|
19061
|
-
var CLI_STREAM_REPORT = {
|
|
19062
|
-
LINE_SEPARATOR: "\n"
|
|
19063
|
-
};
|
|
19064
|
-
function reportCliResult(result, io) {
|
|
19065
|
-
const output = `${result.output}${CLI_STREAM_REPORT.LINE_SEPARATOR}`;
|
|
19066
|
-
if (result.exitCode === 0) io.writeStdout(output);
|
|
19067
|
-
else io.writeStderr(output);
|
|
19068
|
-
io.setExitCode(result.exitCode);
|
|
19069
|
-
}
|
|
19070
|
-
|
|
19071
19543
|
// src/interfaces/cli/verification-context.ts
|
|
19072
19544
|
var VERIFICATION_CONTEXT_CLI = {
|
|
19073
19545
|
commandName: "verification-context",
|
|
@@ -19094,6 +19566,514 @@ var verificationContextDomain = {
|
|
|
19094
19566
|
}
|
|
19095
19567
|
};
|
|
19096
19568
|
|
|
19569
|
+
// src/interfaces/cli/verify.ts
|
|
19570
|
+
import { readFile as readFile13 } from "fs/promises";
|
|
19571
|
+
|
|
19572
|
+
// src/commands/verify/cli.ts
|
|
19573
|
+
import { dirname as dirname12 } from "path";
|
|
19574
|
+
|
|
19575
|
+
// src/domains/verify/verify.ts
|
|
19576
|
+
import { join as join36 } from "path";
|
|
19577
|
+
var VERIFY_SCOPE_TYPE = {
|
|
19578
|
+
CHANGESET: "changeset",
|
|
19579
|
+
WORKING_TREE: "working-tree"
|
|
19580
|
+
};
|
|
19581
|
+
var VERIFY_VERB = {
|
|
19582
|
+
START: "start",
|
|
19583
|
+
INPUT: "input",
|
|
19584
|
+
APPEND_SCOPE: "append-scope",
|
|
19585
|
+
APPEND_FINDING: "append-finding"
|
|
19586
|
+
};
|
|
19587
|
+
var VERIFY_VERIFICATION_TYPE = {
|
|
19588
|
+
REVIEW: "review"
|
|
19589
|
+
};
|
|
19590
|
+
var REVIEW_FINDING_DISPOSITION = {
|
|
19591
|
+
BLOCKING: "BLOCKING",
|
|
19592
|
+
DEBT: "DEBT"
|
|
19593
|
+
};
|
|
19594
|
+
var VERIFY_APPEND_EVENT_TYPE = {
|
|
19595
|
+
SCOPE: "io.spx.verify.scope",
|
|
19596
|
+
FINDING: "io.spx.verify.finding"
|
|
19597
|
+
};
|
|
19598
|
+
var VERIFY_EVENT_SOURCE = "/spx/verify";
|
|
19599
|
+
var VERIFY_APPEND_EVENT_FIELD = {
|
|
19600
|
+
IDEMPOTENCY_KEY: "idempotencyKey",
|
|
19601
|
+
PAYLOAD: "payload"
|
|
19602
|
+
};
|
|
19603
|
+
var VERIFY_INPUT_SOURCE = {
|
|
19604
|
+
STDIN: "stdin"
|
|
19605
|
+
};
|
|
19606
|
+
var VERIFY_SCOPE_SEPARATOR = "..";
|
|
19607
|
+
var VERIFY_SCOPE_ERROR = {
|
|
19608
|
+
MALFORMED_CHANGESET: "verify changeset scope must be <base>..<head>",
|
|
19609
|
+
UNSUPPORTED_SCOPE_TYPE: "verify scope type has no verification-context substrate representation"
|
|
19610
|
+
};
|
|
19611
|
+
var VERIFY_INPUT_DIGEST_PATH = "verify run input";
|
|
19612
|
+
function parseChangesetScope(scope2) {
|
|
19613
|
+
const separatorIndex = scope2.indexOf(VERIFY_SCOPE_SEPARATOR);
|
|
19614
|
+
if (separatorIndex < 0) return { ok: false, error: VERIFY_SCOPE_ERROR.MALFORMED_CHANGESET };
|
|
19615
|
+
const base = scope2.slice(0, separatorIndex);
|
|
19616
|
+
const head = scope2.slice(separatorIndex + VERIFY_SCOPE_SEPARATOR.length);
|
|
19617
|
+
if (base.length === 0 || head.length === 0 || head.includes(VERIFY_SCOPE_SEPARATOR)) {
|
|
19618
|
+
return { ok: false, error: VERIFY_SCOPE_ERROR.MALFORMED_CHANGESET };
|
|
19619
|
+
}
|
|
19620
|
+
return { ok: true, value: { base, head } };
|
|
19621
|
+
}
|
|
19622
|
+
function buildRunLocator(parts) {
|
|
19623
|
+
return {
|
|
19624
|
+
runToken: parts.runToken,
|
|
19625
|
+
verificationType: parts.verificationType,
|
|
19626
|
+
scopeType: parts.scopeType,
|
|
19627
|
+
scopeIdentity: parts.scopeIdentity,
|
|
19628
|
+
backendIdentity: parts.backendIdentity,
|
|
19629
|
+
storageNamespace: parts.storageNamespace,
|
|
19630
|
+
runTarget: parts.runTarget
|
|
19631
|
+
};
|
|
19632
|
+
}
|
|
19633
|
+
function digestRunInput(source, content) {
|
|
19634
|
+
const digest = digestDescriptorSection({ source, content }, VERIFY_INPUT_DIGEST_PATH);
|
|
19635
|
+
if (!digest.ok) return digest;
|
|
19636
|
+
return { ok: true, value: digest.value.sha256 };
|
|
19637
|
+
}
|
|
19638
|
+
var VERIFY_INPUT_RECORD = {
|
|
19639
|
+
PREFIX: "input-",
|
|
19640
|
+
SUFFIX: ".json"
|
|
19641
|
+
};
|
|
19642
|
+
function verifyRunsDir(scope2) {
|
|
19643
|
+
const branchScope = branchScopeDir(scope2.productDir, scope2.branchSlug);
|
|
19644
|
+
if (!branchScope.ok) return branchScope;
|
|
19645
|
+
return runsDir(branchScope.value, scope2.type);
|
|
19646
|
+
}
|
|
19647
|
+
function verifyInputRecordPath(scope2) {
|
|
19648
|
+
const runs = verifyRunsDir(scope2);
|
|
19649
|
+
if (!runs.ok) return runs;
|
|
19650
|
+
const token = validateScopeToken(scope2.runToken);
|
|
19651
|
+
if (!token.ok) return token;
|
|
19652
|
+
return {
|
|
19653
|
+
ok: true,
|
|
19654
|
+
value: join36(runs.value, `${VERIFY_INPUT_RECORD.PREFIX}${token.value}${VERIFY_INPUT_RECORD.SUFFIX}`)
|
|
19655
|
+
};
|
|
19656
|
+
}
|
|
19657
|
+
var VERIFY_APPEND_ATTEMPT = 1;
|
|
19658
|
+
function parseAppendPayload(raw) {
|
|
19659
|
+
try {
|
|
19660
|
+
return JSON.parse(raw);
|
|
19661
|
+
} catch {
|
|
19662
|
+
return void 0;
|
|
19663
|
+
}
|
|
19664
|
+
}
|
|
19665
|
+
function isJsonRecord(value) {
|
|
19666
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
19667
|
+
}
|
|
19668
|
+
function isReviewFindingDisposition(value) {
|
|
19669
|
+
return Object.values(REVIEW_FINDING_DISPOSITION).includes(value);
|
|
19670
|
+
}
|
|
19671
|
+
function validateReviewFinding(payload) {
|
|
19672
|
+
if (!isJsonRecord(payload)) return void 0;
|
|
19673
|
+
const { disposition, summary } = payload;
|
|
19674
|
+
if (!isReviewFindingDisposition(disposition)) return void 0;
|
|
19675
|
+
if (typeof summary !== "string" || summary.length === 0) return void 0;
|
|
19676
|
+
return { disposition, summary };
|
|
19677
|
+
}
|
|
19678
|
+
var FINDING_VALIDATORS = {
|
|
19679
|
+
[VERIFY_VERIFICATION_TYPE.REVIEW]: validateReviewFinding
|
|
19680
|
+
};
|
|
19681
|
+
function findingValidatorFor(verificationType) {
|
|
19682
|
+
return FINDING_VALIDATORS[verificationType];
|
|
19683
|
+
}
|
|
19684
|
+
function findAppendedSequence(events, idempotencyKey, eventType) {
|
|
19685
|
+
const match = events.find(
|
|
19686
|
+
(event) => event.type === eventType && isJsonRecord(event.data) && event.data[VERIFY_APPEND_EVENT_FIELD.IDEMPOTENCY_KEY] === idempotencyKey
|
|
19687
|
+
);
|
|
19688
|
+
return match?.seq;
|
|
19689
|
+
}
|
|
19690
|
+
function buildAppendEvent(args) {
|
|
19691
|
+
return {
|
|
19692
|
+
id: args.idempotencyKey,
|
|
19693
|
+
source: VERIFY_EVENT_SOURCE,
|
|
19694
|
+
type: args.eventType,
|
|
19695
|
+
time: args.at.toISOString(),
|
|
19696
|
+
attempt: VERIFY_APPEND_ATTEMPT,
|
|
19697
|
+
data: {
|
|
19698
|
+
[VERIFY_APPEND_EVENT_FIELD.IDEMPOTENCY_KEY]: args.idempotencyKey,
|
|
19699
|
+
[VERIFY_APPEND_EVENT_FIELD.PAYLOAD]: args.payload
|
|
19700
|
+
}
|
|
19701
|
+
};
|
|
19702
|
+
}
|
|
19703
|
+
|
|
19704
|
+
// src/commands/verify/cli.ts
|
|
19705
|
+
var VERIFY_CLI_EXIT_CODE = {
|
|
19706
|
+
OK: 0,
|
|
19707
|
+
ERROR: 1
|
|
19708
|
+
};
|
|
19709
|
+
var VERIFY_CLI_ENV = {
|
|
19710
|
+
BRANCH: SPX_VERIFY_ENV.BRANCH
|
|
19711
|
+
};
|
|
19712
|
+
var VERIFY_CLI_ERROR = {
|
|
19713
|
+
INPUT_REQUIRED: "spx verify start requires --input <input-source>",
|
|
19714
|
+
RUN_REQUIRED: "spx verify existing-run verbs require an explicit --run <run-token>",
|
|
19715
|
+
RUN_NOT_FOUND: "spx verify could not locate the requested run",
|
|
19716
|
+
CHANGED_SCOPE_FAILED: "spx verify could not derive the changeset changed-file scope",
|
|
19717
|
+
INPUT_PERSIST_FAILED: "spx verify could not persist the recorded run input",
|
|
19718
|
+
INPUT_READ_FAILED: "spx verify could not read the recorded run input",
|
|
19719
|
+
PAYLOAD_REQUIRED: "spx verify append verbs require --payload <payload-source>",
|
|
19720
|
+
IDEMPOTENCY_KEY_REQUIRED: "spx verify append verbs require --idempotency-key <key>",
|
|
19721
|
+
PAYLOAD_READ_FAILED: "spx verify could not read the append payload",
|
|
19722
|
+
PAYLOAD_INVALID: "spx verify append payload is not valid JSON",
|
|
19723
|
+
FINDING_INVALID: "spx verify append-finding payload failed verification-type validation",
|
|
19724
|
+
UNSUPPORTED_VERIFICATION_TYPE: "spx verify append-finding has no finding validator for the verification type",
|
|
19725
|
+
APPEND_FAILED: "spx verify could not append the evidence event"
|
|
19726
|
+
};
|
|
19727
|
+
function okResult3(output) {
|
|
19728
|
+
return { exitCode: VERIFY_CLI_EXIT_CODE.OK, output };
|
|
19729
|
+
}
|
|
19730
|
+
function errorResult3(error) {
|
|
19731
|
+
return { exitCode: VERIFY_CLI_EXIT_CODE.ERROR, output: error };
|
|
19732
|
+
}
|
|
19733
|
+
function toMessage2(error) {
|
|
19734
|
+
return error instanceof Error ? error.message : JSON.stringify(error);
|
|
19735
|
+
}
|
|
19736
|
+
function forwardDeps(deps) {
|
|
19737
|
+
return {
|
|
19738
|
+
...deps.cwd === void 0 ? {} : { cwd: deps.cwd },
|
|
19739
|
+
...deps.git === void 0 ? {} : { git: deps.git },
|
|
19740
|
+
...deps.branch === void 0 ? {} : { branch: deps.branch },
|
|
19741
|
+
...deps.processEnv === void 0 ? {} : { processEnv: deps.processEnv },
|
|
19742
|
+
...deps.fs === void 0 ? {} : { fs: deps.fs },
|
|
19743
|
+
...deps.now === void 0 ? {} : { now: deps.now }
|
|
19744
|
+
};
|
|
19745
|
+
}
|
|
19746
|
+
async function resolveVerifyScope(deps) {
|
|
19747
|
+
const cwd = deps.cwd ?? CONFIG_PROCESS_CWD.read();
|
|
19748
|
+
const git = deps.git ?? defaultGitDependencies;
|
|
19749
|
+
const processEnv = deps.processEnv ?? process.env;
|
|
19750
|
+
const product = await detectGitCommonDirProductRoot(cwd, git);
|
|
19751
|
+
const probedBranch = product.isGitRepo ? await getCurrentBranch(cwd, git) ?? void 0 : void 0;
|
|
19752
|
+
const headSha = (product.isGitRepo ? await getHeadSha(cwd, git) : null) ?? SPX_VERIFY_HEAD_SHA.MISSING;
|
|
19753
|
+
const branchName = deps.branch ?? processEnv[VERIFY_CLI_ENV.BRANCH] ?? probedBranch;
|
|
19754
|
+
const branchIdentity = resolveBranchIdentity({ ...branchName === void 0 ? {} : { branchName }, headSha });
|
|
19755
|
+
const backend = resolveJournalBackend(readJournalCliEnvironment(processEnv).backend);
|
|
19756
|
+
if (!backend.ok) return backend;
|
|
19757
|
+
return {
|
|
19758
|
+
ok: true,
|
|
19759
|
+
value: {
|
|
19760
|
+
productDir: product.productDir,
|
|
19761
|
+
branchSlug: slugBranchIdentity(branchIdentity),
|
|
19762
|
+
backendIdentity: backend.value
|
|
19763
|
+
}
|
|
19764
|
+
};
|
|
19765
|
+
}
|
|
19766
|
+
async function resolveChangedScope(scope2, deps) {
|
|
19767
|
+
const cwd = deps.cwd ?? CONFIG_PROCESS_CWD.read();
|
|
19768
|
+
const git = deps.git ?? defaultGitDependencies;
|
|
19769
|
+
const diff = await git.execa(GIT_ROOT_COMMAND.EXECUTABLE, [...changesetNameStatusArgs(scope2.base, scope2.head)], {
|
|
19770
|
+
cwd,
|
|
19771
|
+
reject: false
|
|
19772
|
+
});
|
|
19773
|
+
if (diff.exitCode !== VERIFY_CLI_EXIT_CODE.OK) {
|
|
19774
|
+
return { ok: false, error: `${VERIFY_CLI_ERROR.CHANGED_SCOPE_FAILED}: ${diff.stderr}` };
|
|
19775
|
+
}
|
|
19776
|
+
return { ok: true, value: pathsFromNameStatus(diff.stdout) };
|
|
19777
|
+
}
|
|
19778
|
+
async function persistInputRecord(runScope2, record6, deps) {
|
|
19779
|
+
const path7 = verifyInputRecordPath(runScope2);
|
|
19780
|
+
if (!path7.ok) return path7;
|
|
19781
|
+
const fs8 = deps.fs ?? defaultFileSystem;
|
|
19782
|
+
try {
|
|
19783
|
+
await fs8.mkdir(dirname12(path7.value), { recursive: true });
|
|
19784
|
+
await fs8.writeFile(path7.value, JSON.stringify(record6));
|
|
19785
|
+
return { ok: true, value: void 0 };
|
|
19786
|
+
} catch (error) {
|
|
19787
|
+
return { ok: false, error: `${VERIFY_CLI_ERROR.INPUT_PERSIST_FAILED}: ${toMessage2(error)}` };
|
|
19788
|
+
}
|
|
19789
|
+
}
|
|
19790
|
+
async function readInputRecordAt(path7, deps) {
|
|
19791
|
+
const fs8 = deps.fs ?? defaultFileSystem;
|
|
19792
|
+
try {
|
|
19793
|
+
const content = await fs8.readFile(path7, STATE_STORE_TEXT_ENCODING);
|
|
19794
|
+
return { ok: true, value: JSON.parse(content) };
|
|
19795
|
+
} catch (error) {
|
|
19796
|
+
if (hasErrorCode(error, ERROR_CODE_NOT_FOUND)) return { ok: true, value: void 0 };
|
|
19797
|
+
return { ok: false, error: `${VERIFY_CLI_ERROR.INPUT_READ_FAILED}: ${toMessage2(error)}` };
|
|
19798
|
+
}
|
|
19799
|
+
}
|
|
19800
|
+
function verifyRunNotFoundDiagnostic(context) {
|
|
19801
|
+
return [
|
|
19802
|
+
VERIFY_CLI_ERROR.RUN_NOT_FOUND,
|
|
19803
|
+
`run=${context.runToken}`,
|
|
19804
|
+
`verification-type=${context.verificationType}`,
|
|
19805
|
+
`scope-type=${context.scopeType}`,
|
|
19806
|
+
`scope=${context.scopeIdentity}`,
|
|
19807
|
+
`backend=${context.backendIdentity}`,
|
|
19808
|
+
`namespace=${context.storageNamespace}`,
|
|
19809
|
+
`target=${context.searchedTarget}`
|
|
19810
|
+
].join(" ");
|
|
19811
|
+
}
|
|
19812
|
+
async function verifyStartCommand(options, deps) {
|
|
19813
|
+
if (options.scopeType !== VERIFY_SCOPE_TYPE.CHANGESET) return errorResult3(VERIFY_SCOPE_ERROR.UNSUPPORTED_SCOPE_TYPE);
|
|
19814
|
+
if (options.input.trim().length === 0) return errorResult3(VERIFY_CLI_ERROR.INPUT_REQUIRED);
|
|
19815
|
+
const scope2 = parseChangesetScope(options.scope);
|
|
19816
|
+
if (!scope2.ok) return errorResult3(scope2.error);
|
|
19817
|
+
const resolved = await resolveVerifyScope(deps);
|
|
19818
|
+
if (!resolved.ok) return errorResult3(resolved.error);
|
|
19819
|
+
const inputContent = await deps.readInputSource(options.input);
|
|
19820
|
+
const inputDigest = digestRunInput(options.input, inputContent);
|
|
19821
|
+
if (!inputDigest.ok) return errorResult3(inputDigest.error);
|
|
19822
|
+
const context = await verificationContextCreateCommand(
|
|
19823
|
+
{
|
|
19824
|
+
subject: VERIFICATION_CONTEXT_SUBJECT_KIND.CHANGESET,
|
|
19825
|
+
base: scope2.value.base,
|
|
19826
|
+
head: scope2.value.head,
|
|
19827
|
+
predicate: options.verificationType,
|
|
19828
|
+
workflow: options.verificationType
|
|
19829
|
+
},
|
|
19830
|
+
forwardDeps(deps)
|
|
19831
|
+
);
|
|
19832
|
+
if (context.exitCode !== VERIFY_CLI_EXIT_CODE.OK) return errorResult3(context.output);
|
|
19833
|
+
const contextDigest = JSON.parse(context.output).digest;
|
|
19834
|
+
const opened = await journalOpenCommand(
|
|
19835
|
+
{ type: options.verificationType, branchSlug: resolved.value.branchSlug },
|
|
19836
|
+
forwardDeps(deps)
|
|
19837
|
+
);
|
|
19838
|
+
if (opened.exitCode !== VERIFY_CLI_EXIT_CODE.OK) return errorResult3(opened.output);
|
|
19839
|
+
const { runToken, runFile } = JSON.parse(opened.output);
|
|
19840
|
+
const runScope2 = {
|
|
19841
|
+
productDir: resolved.value.productDir,
|
|
19842
|
+
branchSlug: resolved.value.branchSlug,
|
|
19843
|
+
type: options.verificationType,
|
|
19844
|
+
runToken
|
|
19845
|
+
};
|
|
19846
|
+
const recorded = { source: options.input, digest: inputDigest.value, content: inputContent };
|
|
19847
|
+
const persisted = await persistInputRecord(runScope2, recorded, deps);
|
|
19848
|
+
if (!persisted.ok) return errorResult3(persisted.error);
|
|
19849
|
+
const changedScope = await resolveChangedScope(scope2.value, deps);
|
|
19850
|
+
if (!changedScope.ok) return errorResult3(changedScope.error);
|
|
19851
|
+
const namespace = verifyRunsDir(runScope2);
|
|
19852
|
+
if (!namespace.ok) return errorResult3(namespace.error);
|
|
19853
|
+
const locator = buildRunLocator({
|
|
19854
|
+
runToken,
|
|
19855
|
+
verificationType: options.verificationType,
|
|
19856
|
+
scopeType: options.scopeType,
|
|
19857
|
+
scopeIdentity: options.scope,
|
|
19858
|
+
backendIdentity: resolved.value.backendIdentity,
|
|
19859
|
+
storageNamespace: namespace.value,
|
|
19860
|
+
runTarget: runFile
|
|
19861
|
+
});
|
|
19862
|
+
const report2 = {
|
|
19863
|
+
runToken,
|
|
19864
|
+
contextDigest,
|
|
19865
|
+
changedScope: changedScope.value,
|
|
19866
|
+
input: { source: options.input, digest: inputDigest.value },
|
|
19867
|
+
locator
|
|
19868
|
+
};
|
|
19869
|
+
return okResult3(JSON.stringify(report2));
|
|
19870
|
+
}
|
|
19871
|
+
async function verifyInputCommand(options, deps) {
|
|
19872
|
+
if (options.run.trim().length === 0) return errorResult3(VERIFY_CLI_ERROR.RUN_REQUIRED);
|
|
19873
|
+
const resolved = await resolveVerifyScope(deps);
|
|
19874
|
+
if (!resolved.ok) return errorResult3(resolved.error);
|
|
19875
|
+
const runScope2 = {
|
|
19876
|
+
productDir: resolved.value.productDir,
|
|
19877
|
+
branchSlug: resolved.value.branchSlug,
|
|
19878
|
+
type: options.verificationType,
|
|
19879
|
+
runToken: options.run
|
|
19880
|
+
};
|
|
19881
|
+
const path7 = verifyInputRecordPath(runScope2);
|
|
19882
|
+
if (!path7.ok) return errorResult3(path7.error);
|
|
19883
|
+
const namespace = verifyRunsDir(runScope2);
|
|
19884
|
+
if (!namespace.ok) return errorResult3(namespace.error);
|
|
19885
|
+
const record6 = await readInputRecordAt(path7.value, deps);
|
|
19886
|
+
if (!record6.ok) return errorResult3(record6.error);
|
|
19887
|
+
if (record6.value === void 0) {
|
|
19888
|
+
return errorResult3(
|
|
19889
|
+
verifyRunNotFoundDiagnostic({
|
|
19890
|
+
runToken: options.run,
|
|
19891
|
+
verificationType: options.verificationType,
|
|
19892
|
+
scopeType: options.scopeType,
|
|
19893
|
+
scopeIdentity: options.scope,
|
|
19894
|
+
backendIdentity: resolved.value.backendIdentity,
|
|
19895
|
+
storageNamespace: namespace.value,
|
|
19896
|
+
searchedTarget: path7.value
|
|
19897
|
+
})
|
|
19898
|
+
);
|
|
19899
|
+
}
|
|
19900
|
+
const report2 = {
|
|
19901
|
+
source: record6.value.source,
|
|
19902
|
+
digest: record6.value.digest,
|
|
19903
|
+
content: record6.value.content
|
|
19904
|
+
};
|
|
19905
|
+
return okResult3(JSON.stringify(report2));
|
|
19906
|
+
}
|
|
19907
|
+
async function readRunJournalEvents(scope2, deps) {
|
|
19908
|
+
const read = await journalReadCommand(scope2, String(JOURNAL_SEQ_BASE), forwardDeps(deps));
|
|
19909
|
+
if (read.exitCode !== VERIFY_CLI_EXIT_CODE.OK) return { ok: false, error: read.output };
|
|
19910
|
+
return { ok: true, value: JSON.parse(read.output) };
|
|
19911
|
+
}
|
|
19912
|
+
async function prepareAppend(options, deps) {
|
|
19913
|
+
if (options.run.trim().length === 0) return { ok: false, error: VERIFY_CLI_ERROR.RUN_REQUIRED };
|
|
19914
|
+
if (options.payload.trim().length === 0) return { ok: false, error: VERIFY_CLI_ERROR.PAYLOAD_REQUIRED };
|
|
19915
|
+
if (options.idempotencyKey.trim().length === 0) {
|
|
19916
|
+
return { ok: false, error: VERIFY_CLI_ERROR.IDEMPOTENCY_KEY_REQUIRED };
|
|
19917
|
+
}
|
|
19918
|
+
const readPayload = deps.readPayloadSource;
|
|
19919
|
+
const binding = deps.journalBinding;
|
|
19920
|
+
if (readPayload === void 0 || binding === void 0) return { ok: false, error: VERIFY_CLI_ERROR.APPEND_FAILED };
|
|
19921
|
+
const resolved = await resolveVerifyScope(deps);
|
|
19922
|
+
if (!resolved.ok) return resolved;
|
|
19923
|
+
const runScope2 = {
|
|
19924
|
+
productDir: resolved.value.productDir,
|
|
19925
|
+
branchSlug: resolved.value.branchSlug,
|
|
19926
|
+
type: options.verificationType,
|
|
19927
|
+
runToken: options.run
|
|
19928
|
+
};
|
|
19929
|
+
const namespace = verifyRunsDir(runScope2);
|
|
19930
|
+
if (!namespace.ok) return namespace;
|
|
19931
|
+
const inputPath = verifyInputRecordPath(runScope2);
|
|
19932
|
+
if (!inputPath.ok) return inputPath;
|
|
19933
|
+
const inputRecord = await readInputRecordAt(inputPath.value, deps);
|
|
19934
|
+
if (!inputRecord.ok) return inputRecord;
|
|
19935
|
+
if (inputRecord.value === void 0) {
|
|
19936
|
+
return { ok: false, error: appendRunNotFoundDiagnostic(options, resolved.value.backendIdentity, namespace.value) };
|
|
19937
|
+
}
|
|
19938
|
+
return {
|
|
19939
|
+
ok: true,
|
|
19940
|
+
value: {
|
|
19941
|
+
readPayload,
|
|
19942
|
+
binding,
|
|
19943
|
+
journalScope: { type: options.verificationType, runToken: options.run, branchSlug: resolved.value.branchSlug },
|
|
19944
|
+
namespace: namespace.value,
|
|
19945
|
+
backendIdentity: resolved.value.backendIdentity
|
|
19946
|
+
}
|
|
19947
|
+
};
|
|
19948
|
+
}
|
|
19949
|
+
function appendRunNotFoundDiagnostic(options, backendIdentity, namespace) {
|
|
19950
|
+
return verifyRunNotFoundDiagnostic({
|
|
19951
|
+
runToken: options.run,
|
|
19952
|
+
verificationType: options.verificationType,
|
|
19953
|
+
scopeType: options.scopeType,
|
|
19954
|
+
scopeIdentity: options.scope,
|
|
19955
|
+
backendIdentity,
|
|
19956
|
+
storageNamespace: namespace,
|
|
19957
|
+
searchedTarget: namespace
|
|
19958
|
+
});
|
|
19959
|
+
}
|
|
19960
|
+
function validateAppendFinding(verb, verificationType, payload) {
|
|
19961
|
+
if (verb !== VERIFY_VERB.APPEND_FINDING) return void 0;
|
|
19962
|
+
const validator = findingValidatorFor(verificationType);
|
|
19963
|
+
if (validator === void 0) return VERIFY_CLI_ERROR.UNSUPPORTED_VERIFICATION_TYPE;
|
|
19964
|
+
if (validator(payload) === void 0) return VERIFY_CLI_ERROR.FINDING_INVALID;
|
|
19965
|
+
return void 0;
|
|
19966
|
+
}
|
|
19967
|
+
function appendEventType(verb) {
|
|
19968
|
+
return verb === VERIFY_VERB.APPEND_FINDING ? VERIFY_APPEND_EVENT_TYPE.FINDING : VERIFY_APPEND_EVENT_TYPE.SCOPE;
|
|
19969
|
+
}
|
|
19970
|
+
async function verifyAppend(options, deps, verb) {
|
|
19971
|
+
const prepared = await prepareAppend(options, deps);
|
|
19972
|
+
if (!prepared.ok) return errorResult3(prepared.error);
|
|
19973
|
+
const { readPayload, binding, journalScope, namespace, backendIdentity } = prepared.value;
|
|
19974
|
+
const eventType = appendEventType(verb);
|
|
19975
|
+
const before = await readRunJournalEvents(journalScope, deps);
|
|
19976
|
+
if (!before.ok) {
|
|
19977
|
+
if (before.error === JOURNAL_RUNTIME_ERROR.RUN_NOT_FOUND) {
|
|
19978
|
+
return errorResult3(appendRunNotFoundDiagnostic(options, backendIdentity, namespace));
|
|
19979
|
+
}
|
|
19980
|
+
return errorResult3(`${VERIFY_CLI_ERROR.APPEND_FAILED}: ${before.error}`);
|
|
19981
|
+
}
|
|
19982
|
+
const existing = findAppendedSequence(before.value, options.idempotencyKey, eventType);
|
|
19983
|
+
if (existing !== void 0) {
|
|
19984
|
+
const report3 = { sequence: existing, idempotent: true };
|
|
19985
|
+
return okResult3(JSON.stringify(report3));
|
|
19986
|
+
}
|
|
19987
|
+
let rawPayload;
|
|
19988
|
+
try {
|
|
19989
|
+
rawPayload = await readPayload(options.payload);
|
|
19990
|
+
} catch (error) {
|
|
19991
|
+
return errorResult3(`${VERIFY_CLI_ERROR.PAYLOAD_READ_FAILED}: ${toMessage2(error)}`);
|
|
19992
|
+
}
|
|
19993
|
+
const parsed = parseAppendPayload(rawPayload);
|
|
19994
|
+
if (parsed === void 0) return errorResult3(VERIFY_CLI_ERROR.PAYLOAD_INVALID);
|
|
19995
|
+
const findingError = validateAppendFinding(verb, options.verificationType, parsed);
|
|
19996
|
+
if (findingError !== void 0) return errorResult3(findingError);
|
|
19997
|
+
const event = buildAppendEvent({
|
|
19998
|
+
eventType,
|
|
19999
|
+
idempotencyKey: options.idempotencyKey,
|
|
20000
|
+
payload: parsed,
|
|
20001
|
+
at: deps.now?.() ?? /* @__PURE__ */ new Date()
|
|
20002
|
+
});
|
|
20003
|
+
const appended = await journalAppendCommand(journalScope, event, binding, forwardDeps(deps));
|
|
20004
|
+
if (appended.exitCode !== VERIFY_CLI_EXIT_CODE.OK) {
|
|
20005
|
+
return errorResult3(`${VERIFY_CLI_ERROR.APPEND_FAILED}: ${appended.output}`);
|
|
20006
|
+
}
|
|
20007
|
+
const after = await readRunJournalEvents(journalScope, deps);
|
|
20008
|
+
if (!after.ok) return errorResult3(`${VERIFY_CLI_ERROR.APPEND_FAILED}: ${after.error}`);
|
|
20009
|
+
const sequence = findAppendedSequence(after.value, options.idempotencyKey, eventType);
|
|
20010
|
+
if (sequence === void 0) return errorResult3(VERIFY_CLI_ERROR.APPEND_FAILED);
|
|
20011
|
+
const report2 = { sequence, idempotent: false };
|
|
20012
|
+
return okResult3(JSON.stringify(report2));
|
|
20013
|
+
}
|
|
20014
|
+
async function verifyAppendScopeCommand(options, deps) {
|
|
20015
|
+
return verifyAppend(options, deps, VERIFY_VERB.APPEND_SCOPE);
|
|
20016
|
+
}
|
|
20017
|
+
async function verifyAppendFindingCommand(options, deps) {
|
|
20018
|
+
return verifyAppend(options, deps, VERIFY_VERB.APPEND_FINDING);
|
|
20019
|
+
}
|
|
20020
|
+
|
|
20021
|
+
// src/interfaces/cli/verify.ts
|
|
20022
|
+
var VERIFY_CLI = {
|
|
20023
|
+
commandName: "verify",
|
|
20024
|
+
description: "Record and replay a typed verification run",
|
|
20025
|
+
startCommandName: VERIFY_VERB.START,
|
|
20026
|
+
inputCommandName: VERIFY_VERB.INPUT,
|
|
20027
|
+
appendScopeCommandName: VERIFY_VERB.APPEND_SCOPE,
|
|
20028
|
+
appendFindingCommandName: VERIFY_VERB.APPEND_FINDING,
|
|
20029
|
+
verificationTypeOption: "--verification-type <type>",
|
|
20030
|
+
scopeTypeOption: "--scope-type <scope-type>",
|
|
20031
|
+
scopeOption: "--scope <base>..<head>",
|
|
20032
|
+
inputOption: "--input <input-source>",
|
|
20033
|
+
runOption: "--run <token>",
|
|
20034
|
+
payloadOption: "--payload <payload-source>",
|
|
20035
|
+
idempotencyKeyOption: "--idempotency-key <key>"
|
|
20036
|
+
};
|
|
20037
|
+
var CLI_SOURCE_ENCODING = "utf8";
|
|
20038
|
+
async function readStdinText() {
|
|
20039
|
+
const chunks = [];
|
|
20040
|
+
for await (const chunk of process.stdin) {
|
|
20041
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
20042
|
+
}
|
|
20043
|
+
return Buffer.concat(chunks).toString(CLI_SOURCE_ENCODING);
|
|
20044
|
+
}
|
|
20045
|
+
async function readCliSource(source) {
|
|
20046
|
+
if (source === VERIFY_INPUT_SOURCE.STDIN) return readStdinText();
|
|
20047
|
+
return readFile13(source, CLI_SOURCE_ENCODING);
|
|
20048
|
+
}
|
|
20049
|
+
var verifyDomain = {
|
|
20050
|
+
name: VERIFY_CLI.commandName,
|
|
20051
|
+
description: VERIFY_CLI.description,
|
|
20052
|
+
register: (program, invocation) => {
|
|
20053
|
+
const deps = () => ({
|
|
20054
|
+
cwd: invocation.resolveEffectiveInvocationDir(),
|
|
20055
|
+
readInputSource: readCliSource,
|
|
20056
|
+
readPayloadSource: readCliSource,
|
|
20057
|
+
// The append verbs write a single structured JSON result to stdout, so the run's event
|
|
20058
|
+
// stream goes to stderr under the local backend rather than sharing the result channel.
|
|
20059
|
+
journalBinding: createJournalStreamBinding(invocation.io, stderrStreamSink(invocation.io))
|
|
20060
|
+
});
|
|
20061
|
+
const command = program.command(VERIFY_CLI.commandName).description(VERIFY_CLI.description);
|
|
20062
|
+
command.command(VERIFY_CLI.startCommandName).description("Start a changeset-scoped verification run and report its run locator").requiredOption(VERIFY_CLI.verificationTypeOption, "Verification type recorded for the run").requiredOption(VERIFY_CLI.scopeTypeOption, "Scope type; changeset").requiredOption(VERIFY_CLI.scopeOption, "Changeset scope as <base>..<head>").requiredOption(VERIFY_CLI.inputOption, "Verification input source; stdin or a file path").action(async (options) => {
|
|
20063
|
+
reportCliResult(await verifyStartCommand(options, deps()), invocation.io);
|
|
20064
|
+
});
|
|
20065
|
+
command.command(VERIFY_CLI.inputCommandName).description("Replay the verification input recorded at start").requiredOption(VERIFY_CLI.verificationTypeOption, "Verification type recorded for the run").requiredOption(VERIFY_CLI.scopeTypeOption, "Scope type; changeset").requiredOption(VERIFY_CLI.scopeOption, "Changeset scope as <base>..<head>").requiredOption(VERIFY_CLI.runOption, "Run token reported by start").action(async (options) => {
|
|
20066
|
+
reportCliResult(await verifyInputCommand(options, deps()), invocation.io);
|
|
20067
|
+
});
|
|
20068
|
+
command.command(VERIFY_CLI.appendScopeCommandName).description("Record the inspected scope for a started run").requiredOption(VERIFY_CLI.verificationTypeOption, "Verification type recorded for the run").requiredOption(VERIFY_CLI.scopeTypeOption, "Scope type; changeset").requiredOption(VERIFY_CLI.scopeOption, "Changeset scope as <base>..<head>").requiredOption(VERIFY_CLI.runOption, "Run token reported by start").requiredOption(VERIFY_CLI.payloadOption, "Append payload source; stdin or a file path").requiredOption(VERIFY_CLI.idempotencyKeyOption, "Caller-supplied idempotency key for the append").action(async (options) => {
|
|
20069
|
+
reportCliResult(await verifyAppendScopeCommand(options, deps()), invocation.io);
|
|
20070
|
+
});
|
|
20071
|
+
command.command(VERIFY_CLI.appendFindingCommandName).description("Record a validated verification finding for a started run").requiredOption(VERIFY_CLI.verificationTypeOption, "Verification type recorded for the run").requiredOption(VERIFY_CLI.scopeTypeOption, "Scope type; changeset").requiredOption(VERIFY_CLI.scopeOption, "Changeset scope as <base>..<head>").requiredOption(VERIFY_CLI.runOption, "Run token reported by start").requiredOption(VERIFY_CLI.payloadOption, "Append payload source; stdin or a file path").requiredOption(VERIFY_CLI.idempotencyKeyOption, "Caller-supplied idempotency key for the append").action(async (options) => {
|
|
20072
|
+
reportCliResult(await verifyAppendFindingCommand(options, deps()), invocation.io);
|
|
20073
|
+
});
|
|
20074
|
+
}
|
|
20075
|
+
};
|
|
20076
|
+
|
|
19097
20077
|
// src/interfaces/cli/worktree.ts
|
|
19098
20078
|
import { randomBytes as nodeRandomBytes4 } from "crypto";
|
|
19099
20079
|
|
|
@@ -19135,7 +20115,7 @@ function resolveReleaseSessionId(explicitSessionId, env) {
|
|
|
19135
20115
|
}
|
|
19136
20116
|
|
|
19137
20117
|
// src/commands/worktree/status.ts
|
|
19138
|
-
import { basename as basename8, dirname as
|
|
20118
|
+
import { basename as basename8, dirname as dirname13, sep as sep5 } from "path";
|
|
19139
20119
|
var WORKTREE_STATUS_FORMAT = {
|
|
19140
20120
|
JSON: "json",
|
|
19141
20121
|
TEXT: "text"
|
|
@@ -19226,7 +20206,7 @@ function renderTextStatus(records) {
|
|
|
19226
20206
|
const sections = [];
|
|
19227
20207
|
const sectionByParent = /* @__PURE__ */ new Map();
|
|
19228
20208
|
for (const record6 of records) {
|
|
19229
|
-
const parent = renderParentDirectory(
|
|
20209
|
+
const parent = renderParentDirectory(dirname13(record6.worktreeRoot));
|
|
19230
20210
|
const children = sectionByParent.get(parent);
|
|
19231
20211
|
const rendered = renderTextStatusChild(record6);
|
|
19232
20212
|
if (children === void 0) {
|
|
@@ -19240,7 +20220,7 @@ function renderTextStatus(records) {
|
|
|
19240
20220
|
return renderPlainTree({ sections });
|
|
19241
20221
|
}
|
|
19242
20222
|
function renderParentDirectory(parent) {
|
|
19243
|
-
return parent.endsWith(
|
|
20223
|
+
return parent.endsWith(sep5) ? parent : `${parent}${sep5}`;
|
|
19244
20224
|
}
|
|
19245
20225
|
function renderTextStatusChild(record6) {
|
|
19246
20226
|
if (record6.status === OCCUPANCY_STATUS.RUNNING) {
|
|
@@ -19250,11 +20230,11 @@ function renderTextStatusChild(record6) {
|
|
|
19250
20230
|
}
|
|
19251
20231
|
|
|
19252
20232
|
// src/lib/worktree-path-info.ts
|
|
19253
|
-
import { stat as
|
|
20233
|
+
import { stat as stat8 } from "fs/promises";
|
|
19254
20234
|
var defaultWorktreePathInfo = {
|
|
19255
20235
|
isExistingNonDirectory: async (path7) => {
|
|
19256
20236
|
try {
|
|
19257
|
-
const pathStats = await
|
|
20237
|
+
const pathStats = await stat8(path7);
|
|
19258
20238
|
return !pathStats.isDirectory();
|
|
19259
20239
|
} catch {
|
|
19260
20240
|
return false;
|
|
@@ -19365,6 +20345,7 @@ var CLI_DOMAINS = [
|
|
|
19365
20345
|
testingDomain,
|
|
19366
20346
|
validationDomain,
|
|
19367
20347
|
verificationContextDomain,
|
|
20348
|
+
verifyDomain,
|
|
19368
20349
|
worktreeDomain
|
|
19369
20350
|
];
|
|
19370
20351
|
|