@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/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 { readdir, readFile, stat } from "fs/promises";
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
- DISPLAYED_CANDIDATES: 20,
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
- ROOT_RESOLUTION_CONCURRENCY: 8
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 invocationRoot = await options.resolveWorktreeRoot(options.invocationDir);
232
- if (invocationRoot === null) {
253
+ const scope2 = await resolveAgentResumeScopeContext(options);
254
+ if (scope2 === null) {
233
255
  return [];
234
256
  }
235
- const drafts = [
236
- ...await discoverCodexResumeCandidates(
237
- codexSessionStoreDir(options.homeDir),
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
- options.nowMs
267
+ cap,
268
+ scope2.match,
269
+ parseCodexHead
240
270
  ),
241
- ...await discoverClaudeCodeResumeCandidates(
242
- claudeCodeSessionStoreDir(options.homeDir),
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
- options.nowMs
279
+ cap,
280
+ scope2.match,
281
+ parseClaudeHead
245
282
  )
246
- ];
247
- const sortedDrafts = [...drafts];
248
- sortedDrafts.sort(compareCandidates);
249
- const rootResults = await mapWithConcurrency(
250
- sortedDrafts,
251
- AGENT_RESUME_LIMITS.ROOT_RESOLUTION_CONCURRENCY,
252
- async (candidate) => ({
253
- candidate,
254
- root: await options.resolveWorktreeRoot(candidate.cwd)
255
- })
256
- );
257
- return rootResults.filter((result) => result.root !== null && samePath(result.root, invocationRoot)).map((result) => result.candidate).slice(0, AGENT_RESUME_LIMITS.DISPLAYED_CANDIDATES);
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 discoverCodexResumeCandidates(root, fs8, nowMs) {
286
- return discoverStoreCandidates(root, fs8, nowMs, parseCodexCandidateFile);
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 discoverClaudeCodeResumeCandidates(root, fs8, nowMs) {
289
- return discoverStoreCandidates(root, fs8, nowMs, parseClaudeCodeCandidateFile);
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 discoverStoreCandidates(root, fs8, nowMs, parseCandidate) {
292
- const files = await collectJsonlFiles(root, fs8);
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
- const stat8 = await fs8.stat(file).catch(() => null);
296
- if (stat8 === null || !isRecentAgentSessionMtime(stat8.mtimeMs, nowMs)) {
297
- continue;
346
+ if (candidates.length >= cap) {
347
+ break;
298
348
  }
299
- const content = await fs8.readFile(file).catch(() => null);
300
- if (content === null) {
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 candidate = parseCandidate(file, content, stat8.mtimeMs);
304
- if (candidate !== null) {
305
- candidates.push(candidate);
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 parseCodexCandidateFile(sourcePath, content, modifiedAtMs) {
327
- let sessionId = null;
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
- isInteractiveSession = isCodexInteractiveSessionRow(row) || isInteractiveSession;
337
- sessionId ??= firstString(row, [
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
- ]) ?? cwd;
347
- updatedAt = maxIsoTimestamp(updatedAt, firstString(row, [[AGENT_SESSION_JSON_FIELDS.TIMESTAMP]]));
348
- }
349
- if (!isInteractiveSession || sessionId === null || cwd === null) {
350
- return null;
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 isCodexInteractiveSessionRow(row) {
363
- if (firstString(row, [[AGENT_SESSION_JSON_FIELDS.TYPE]]) !== AGENT_SESSION_ROW_TYPE.CODEX_SESSION_META) {
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 parseClaudeCodeCandidateFile(sourcePath, content, modifiedAtMs) {
435
+ function parseClaudeHead(head) {
373
436
  let sessionId = null;
374
437
  let cwd = null;
375
- let updatedAt = null;
376
438
  let branch = null;
377
- for (const line of content.split("\n")) {
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 = firstString(row, [
451
+ cwd ??= firstString(row, [
389
452
  [AGENT_SESSION_JSON_FIELDS.CWD],
390
453
  [AGENT_SESSION_JSON_FIELDS.PAYLOAD, AGENT_SESSION_JSON_FIELDS.CWD]
391
- ]) ?? cwd;
392
- updatedAt = maxIsoTimestamp(updatedAt, firstString(row, [[AGENT_SESSION_JSON_FIELDS.TIMESTAMP]]));
393
- branch = firstString(row, [[AGENT_SESSION_JSON_FIELDS.GIT_BRANCH]]) ?? 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 samePath(left, right) {
459
- return resolve3(left) === resolve3(right);
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 = isAbsolute(commonDir) ? commonDir : resolve4(toplevel, commonDir);
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 = isAbsolute(rawCommonDir) ? rawCommonDir : resolve4(worktreeRoot, rawCommonDir);
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 readFile(path7) {
849
- return readFile(path7, AGENT_SESSION_STORE.TEXT_ENCODING);
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 : null;
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: invocation.resolveEffectiveInvocationDir(),
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
- const candidates = await loadAgentResumeCandidates(commandOptions);
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 configured agent` };
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 configured agents` };
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 configured agent ${agent.value}` };
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: currentWorktree?.status === OCCUPANCY_STATUS.RUNNING
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) => compareJournalRunsOldestFirst(left.metadata, right.metadata)).map((run) => ({ runToken: run.metadata.runToken, metadata: run.metadata, events: run.events }))
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 JOURNAL_CLI_LIST_LIMIT = {
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
- INVALID_LIST_LIMIT: "journal list limit must be a positive whole integer",
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 parseJournalListLimit(raw) {
7817
- if (raw === void 0) return { ok: true, value: void 0 };
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.INVALID_LIST_LIMIT };
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 >= JOURNAL_CLI_LIST_LIMIT.MIN ? { ok: true, value } : { ok: false, error: JOURNAL_CLI_ERROR.INVALID_LIST_LIMIT };
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 context = await resolveJournalRunContext(scope2, deps);
7850
- if (!context.ok) return errorResult(context.error);
7851
- const events = await readJournalEvents(runRef(context.value, scope2.runToken), cursor.value, verbOptions(deps));
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 = parseJournalListLimit(scope2.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
- ...limit.value === void 0 ? {} : { limit: limit.value }
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(context.value, verbOptions(deps));
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 context = await resolveJournalRunContext(scope2, deps);
7890
- if (!context.ok) return errorResult(context.error);
8369
+ const ref = await inspectionRunRef(scope2, deps);
8370
+ if (!ref.ok) return errorResult(ref.error);
7891
8371
  const rendered = await renderJournalRun(
7892
- runRef(context.value, scope2.runToken),
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}${STREAM_LINE_SEPARATOR}`);
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
- streamBinding(invocation.io),
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, "Maximum number of runs").action(async (options) => {
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}${STREAM_LINE_SEPARATOR}`;
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 stat2 } from "fs/promises";
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 stat2(path7);
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 stat3, unlink } from "fs/promises";
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 stat3(path7);
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 stat4, writeFile } from "fs/promises";
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 stat4(resolve6(cwd, entry))).isDirectory();
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 readdir2, readFile as readFile5 } from "fs/promises";
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 readdir2(dir);
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 readdir3, readFile as readFile6, rename as rename2 } from "fs/promises";
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: readdir3,
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 readdir4, readFile as readFile7, unlink as unlink2 } from "fs/promises";
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 readdir4(config.archiveDir);
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 readdir5, rename as rename3 } from "fs/promises";
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 readdir5(config.doingDir);
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 stat5 } from "fs/promises";
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 stat5(filePath);
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 readdir6, readFile as readFile9 } from "fs/promises";
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 readdir6(context.absolutePath, { withFileTypes: true });
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 readdir7 } from "fs/promises";
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 readdir7(directory, { withFileTypes: true });
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 relative(productDir, absolute).split(sep).join(POSIX_SEPARATOR);
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 NULL_RECORD_SEPARATOR = "\0";
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 LS_FILES_CACHED_FLAG = "--cached";
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, LS_FILES_CACHED_FLAG, CHANGED_TEST_NULL_DELIMITED_FLAG],
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, candidateTestPaths, deps) {
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
- let candidateTestPaths = [];
11738
- if (partition.sourceFiles.length > 0) {
11739
- candidateTestPaths = options.staged === true ? await stagedCandidateTestPaths(options.productDir, deps.git) : await discoverTestFiles(options.productDir);
11740
- }
11741
- const related = partition.sourceFiles.length === 0 ? { testPaths: [], unresolved: [] } : await relatedTestPaths(partition.sourceFiles, options, baseRef, candidateTestPaths, deps);
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] : mergeChangedSetOperands(partition.operands, related.testPaths),
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
- selectedTargets,
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 readdir8, rm, writeFile as writeFile2 } from "fs/promises";
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 readdir8(directory, { withFileTypes: true });
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 isAbsolute10, relative as relative8, resolve as resolve11, sep as sep3 } from "path";
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 isAbsolute3, join as join24, relative as relative3 } from "path";
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 isAbsolute2, relative as relative2 } from "path";
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 isAbsolute2(path7) ? relative2(projectRoot, path7) : path7;
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
- isAbsolute3(filePath) ? relative3(cwd, filePath) : filePath,
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, isAbsolute3(filePath) ? relative3(cwd, filePath) : filePath)
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 isAbsolute4, join as join26 } from "path";
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 isAbsolute4(filePath) ? filePath : join26(productDir, filePath);
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 isAbsolute5, join as join27, relative as relative4, resolve as resolve9 } from "path";
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 isAbsolute5(path7) ? path7 : join27(projectRoot, path7);
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 = isAbsolute5(path7) ? resolve9(path7) : resolve9(projectRoot, path7);
15525
- const relativePath = relative4(projectRoot, resolvedPath);
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("..") && !isAbsolute5(relativePath);
16010
+ return relativePath.length === 0 || !segments.includes("..") && !isAbsolute6(relativePath);
15528
16011
  }
15529
16012
  function toProjectRelativeTypeScriptScopePath(projectRoot, path7) {
15530
- const resolvedPath = isAbsolute5(path7) ? resolve9(path7) : resolve9(projectRoot, path7);
15531
- const relativePath = relative4(projectRoot, resolvedPath);
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 isAbsolute6, join as join29 } from "path";
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) => isAbsolute6(pattern) ? pattern : join29(projectRoot, 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 isAbsolute8, relative as relative6, resolve as resolve10 } from "path";
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 readdir9, readFile as readFile11, stat as stat6 } from "fs/promises";
16853
- import { join as join33, relative as relative5, sep as sep2 } from "path";
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 isAbsolute7, join as join32 } from "path";
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 isAbsolute7(path7) ? path7 : join32(productDir, path7);
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 stat6(absolutePath)).isDirectory();
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 readdir9(absoluteDir, { withFileTypes: true });
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 = relative5(productDir, absolutePath);
17189
- return sep2 === "/" ? rel : rel.split(sep2).join("/");
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 = isAbsolute8(f) ? f : resolve10(input.productDir, f);
17738
- return relative6(input.productDir, abs).split(/[\\/]/g).join("/");
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 = relative6(input.productDir, abs).split(/[\\/]/g).join("/");
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 isAbsolute9, join as join34, relative as relative7 } from "path";
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) => isAbsolute9(file) ? file : join34(projectRoot, 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 = isAbsolute9(pattern) ? pattern : join34(projectRoot, pattern);
18112
- return relative7(tempDir, absolutePattern);
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 = relative8(resolvedProductDir, absoluteOperand);
18655
- if (relativeOperand === ".." || relativeOperand.startsWith(`..${sep3}`) || isAbsolute10(relativeOperand)) {
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 isAbsolute11, win32 } from "path";
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 (isAbsolute11(path7) || win32.isAbsolute(path7) || windowsRoot.length > 0 || segments.includes(VERIFICATION_CONTEXT_FILE_SUBJECT_PATH.PARENT_DIRECTORY.SEGMENT)) {
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 dirname12, sep as sep4 } from "path";
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(dirname12(record6.worktreeRoot));
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(sep4) ? parent : `${parent}${sep4}`;
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 stat7 } from "fs/promises";
20233
+ import { stat as stat8 } from "fs/promises";
19254
20234
  var defaultWorktreePathInfo = {
19255
20235
  isExistingNonDirectory: async (path7) => {
19256
20236
  try {
19257
- const pathStats = await stat7(path7);
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