@outcomeeng/spx 0.6.9 → 0.6.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -0
- package/dist/cli.js +917 -239
- package/dist/cli.js.map +1 -1
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -95,7 +95,7 @@ var DEFAULT_CLI_IO = {
|
|
|
95
95
|
import { inspect } from "util";
|
|
96
96
|
|
|
97
97
|
// src/commands/agent/resume.ts
|
|
98
|
-
import {
|
|
98
|
+
import { open, readdir, stat } from "fs/promises";
|
|
99
99
|
import { homedir } from "os";
|
|
100
100
|
|
|
101
101
|
// src/domains/agent/protocol.ts
|
|
@@ -118,17 +118,23 @@ var AGENT_SESSION_STORE = {
|
|
|
118
118
|
CODEX_SESSIONS_DIR: "sessions",
|
|
119
119
|
CLAUDE_DIR: ".claude",
|
|
120
120
|
CLAUDE_PROJECTS_DIR: "projects",
|
|
121
|
+
CLAUDE_SUBAGENTS_DIR: "subagents",
|
|
121
122
|
JSONL_EXTENSION: ".jsonl",
|
|
122
123
|
TEXT_ENCODING: "utf8"
|
|
123
124
|
};
|
|
124
125
|
var AGENT_RESUME_LIMITS = {
|
|
125
126
|
RECENT_DAYS: 7,
|
|
126
|
-
|
|
127
|
+
PER_AGENT_DISPLAYED_CANDIDATES: 5,
|
|
128
|
+
METADATA_HEAD_BYTES: 131072,
|
|
127
129
|
HOURS_PER_DAY: 24,
|
|
128
130
|
MINUTES_PER_HOUR: 60,
|
|
129
131
|
SECONDS_PER_MINUTE: 60,
|
|
130
132
|
MILLISECONDS_PER_SECOND: 1e3,
|
|
131
|
-
|
|
133
|
+
READ_CONCURRENCY: 8
|
|
134
|
+
};
|
|
135
|
+
var AGENT_RESUME_SCOPE = {
|
|
136
|
+
WORKTREE: "worktree",
|
|
137
|
+
BRANCH: "branch"
|
|
132
138
|
};
|
|
133
139
|
var AGENT_RESUME_MODE = {
|
|
134
140
|
PICK: "pick",
|
|
@@ -151,6 +157,8 @@ var AGENT_SESSION_JSON_FIELDS = {
|
|
|
151
157
|
SESSION_ID_CAMEL: "sessionId",
|
|
152
158
|
ID: "id",
|
|
153
159
|
PAYLOAD: "payload",
|
|
160
|
+
GIT: "git",
|
|
161
|
+
BRANCH: "branch",
|
|
154
162
|
GIT_BRANCH: "gitBranch",
|
|
155
163
|
THREAD_SOURCE: "thread_source"
|
|
156
164
|
};
|
|
@@ -164,10 +172,19 @@ var CODEX_SESSION_ORIGINATOR = {
|
|
|
164
172
|
VSCODE_HYPHEN: "codex-vscode",
|
|
165
173
|
EXEC: "codex-exec"
|
|
166
174
|
};
|
|
175
|
+
var CODEX_SESSION_THREAD_SOURCE = {
|
|
176
|
+
SUBAGENT: "subagent"
|
|
177
|
+
};
|
|
167
178
|
var AGENT_RESUME_RECENT_WINDOW_MS = AGENT_RESUME_LIMITS.RECENT_DAYS * AGENT_RESUME_LIMITS.HOURS_PER_DAY * AGENT_RESUME_LIMITS.MINUTES_PER_HOUR * AGENT_RESUME_LIMITS.SECONDS_PER_MINUTE * AGENT_RESUME_LIMITS.MILLISECONDS_PER_SECOND;
|
|
168
179
|
|
|
169
180
|
// src/domains/agent/resume.ts
|
|
170
|
-
import { resolve as resolve3 } from "path";
|
|
181
|
+
import { isAbsolute, relative, resolve as resolve3, sep } from "path";
|
|
182
|
+
function worktreeResumeScope() {
|
|
183
|
+
return { kind: AGENT_RESUME_SCOPE.WORKTREE };
|
|
184
|
+
}
|
|
185
|
+
function branchResumeScope(branch) {
|
|
186
|
+
return { kind: AGENT_RESUME_SCOPE.BRANCH, branch };
|
|
187
|
+
}
|
|
171
188
|
var AGENT_RESUME_PICKER_ACTION = {
|
|
172
189
|
MOVE_UP: "move-up",
|
|
173
190
|
MOVE_DOWN: "move-down",
|
|
@@ -227,34 +244,59 @@ function codexSessionStoreDir(homeDir) {
|
|
|
227
244
|
function claudeCodeSessionStoreDir(homeDir) {
|
|
228
245
|
return resolve3(homeDir, AGENT_SESSION_STORE.CLAUDE_DIR, AGENT_SESSION_STORE.CLAUDE_PROJECTS_DIR);
|
|
229
246
|
}
|
|
247
|
+
var CLAUDE_PROJECT_PATH_SEPARATORS = /[/\\]/g;
|
|
248
|
+
var CLAUDE_PROJECT_ENCODED_SEPARATOR = "-";
|
|
249
|
+
function claudeProjectDirName(cwd) {
|
|
250
|
+
return cwd.replace(CLAUDE_PROJECT_PATH_SEPARATORS, CLAUDE_PROJECT_ENCODED_SEPARATOR);
|
|
251
|
+
}
|
|
230
252
|
async function discoverAgentResumeCandidates(options) {
|
|
231
|
-
const
|
|
232
|
-
if (
|
|
253
|
+
const scope2 = await resolveAgentResumeScopeContext(options);
|
|
254
|
+
if (scope2 === null) {
|
|
233
255
|
return [];
|
|
234
256
|
}
|
|
235
|
-
const
|
|
236
|
-
|
|
237
|
-
|
|
257
|
+
const cap = AGENT_RESUME_LIMITS.PER_AGENT_DISPLAYED_CANDIDATES;
|
|
258
|
+
const [codex, claude] = await Promise.all([
|
|
259
|
+
collectAgentCandidates(
|
|
260
|
+
AGENT_SESSION_KIND.CODEX,
|
|
261
|
+
await recentStoreFiles(
|
|
262
|
+
await collectJsonlFiles(codexSessionStoreDir(options.homeDir), options.fs),
|
|
263
|
+
options.fs,
|
|
264
|
+
options.nowMs
|
|
265
|
+
),
|
|
238
266
|
options.fs,
|
|
239
|
-
|
|
267
|
+
cap,
|
|
268
|
+
scope2.match,
|
|
269
|
+
parseCodexHead
|
|
240
270
|
),
|
|
241
|
-
|
|
242
|
-
|
|
271
|
+
collectAgentCandidates(
|
|
272
|
+
AGENT_SESSION_KIND.CLAUDE_CODE,
|
|
273
|
+
await recentStoreFiles(
|
|
274
|
+
await claudeTranscriptFiles(claudeCodeSessionStoreDir(options.homeDir), options.fs, scope2.claudeDirAccepts),
|
|
275
|
+
options.fs,
|
|
276
|
+
options.nowMs
|
|
277
|
+
),
|
|
243
278
|
options.fs,
|
|
244
|
-
|
|
279
|
+
cap,
|
|
280
|
+
scope2.match,
|
|
281
|
+
parseClaudeHead
|
|
245
282
|
)
|
|
246
|
-
];
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
283
|
+
]);
|
|
284
|
+
return [...codex, ...claude].sort(compareCandidates);
|
|
285
|
+
}
|
|
286
|
+
async function resolveAgentResumeScopeContext(options) {
|
|
287
|
+
if (options.scope.kind === AGENT_RESUME_SCOPE.BRANCH) {
|
|
288
|
+
const target = options.scope.branch;
|
|
289
|
+
return { match: (core) => core.branch === target, claudeDirAccepts: () => true };
|
|
290
|
+
}
|
|
291
|
+
const invocationRoot = await options.resolveWorktreeRoot(options.invocationDir);
|
|
292
|
+
if (invocationRoot === null) {
|
|
293
|
+
return null;
|
|
294
|
+
}
|
|
295
|
+
const projectPrefix = claudeProjectDirName(invocationRoot);
|
|
296
|
+
return {
|
|
297
|
+
match: (core) => isPathInsideOrEqual(invocationRoot, core.cwd),
|
|
298
|
+
claudeDirAccepts: (dirName) => dirName === projectPrefix || dirName.startsWith(`${projectPrefix}${CLAUDE_PROJECT_ENCODED_SEPARATOR}`)
|
|
299
|
+
};
|
|
258
300
|
}
|
|
259
301
|
function buildAgentResumeLaunchCommand(candidate) {
|
|
260
302
|
if (candidate.agent === AGENT_SESSION_KIND.CODEX) {
|
|
@@ -282,28 +324,49 @@ function renderAgentResumeList(candidates) {
|
|
|
282
324
|
function renderAgentResumeJson(candidates) {
|
|
283
325
|
return JSON.stringify(candidates, null, 2);
|
|
284
326
|
}
|
|
285
|
-
async function
|
|
286
|
-
|
|
327
|
+
async function recentStoreFiles(paths, fs8, nowMs) {
|
|
328
|
+
const stats = await mapWithConcurrency(paths, AGENT_RESUME_LIMITS.READ_CONCURRENCY, async (path7) => {
|
|
329
|
+
const stat8 = await fs8.stat(path7).catch(() => null);
|
|
330
|
+
if (stat8 === null || !isRecentAgentSessionMtime(stat8.mtimeMs, nowMs)) {
|
|
331
|
+
return null;
|
|
332
|
+
}
|
|
333
|
+
return { path: path7, modifiedAtMs: stat8.mtimeMs };
|
|
334
|
+
});
|
|
335
|
+
return stats.filter((file) => file !== null).sort((left, right) => right.modifiedAtMs - left.modifiedAtMs || left.path.localeCompare(right.path));
|
|
287
336
|
}
|
|
288
|
-
async function
|
|
289
|
-
|
|
337
|
+
async function claudeTranscriptFiles(root, fs8, dirAccepts) {
|
|
338
|
+
const projectDirs = (await fs8.readDir(root).catch(() => [])).filter((entry) => entry.isDirectory && dirAccepts(entry.name)).map((entry) => resolve3(root, entry.name));
|
|
339
|
+
const perDir = await mapWithConcurrency(projectDirs, AGENT_RESUME_LIMITS.READ_CONCURRENCY, async (dir) => {
|
|
340
|
+
const entries = await fs8.readDir(dir).catch(() => []);
|
|
341
|
+
return entries.filter((entry) => entry.isFile && entry.name.endsWith(AGENT_SESSION_STORE.JSONL_EXTENSION)).map((entry) => resolve3(dir, entry.name));
|
|
342
|
+
});
|
|
343
|
+
return perDir.flat();
|
|
290
344
|
}
|
|
291
|
-
async function
|
|
292
|
-
const
|
|
345
|
+
async function collectAgentCandidates(agent, files, fs8, cap, match, parseHead) {
|
|
346
|
+
const seen = /* @__PURE__ */ new Set();
|
|
293
347
|
const candidates = [];
|
|
294
348
|
for (const file of files) {
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
continue;
|
|
349
|
+
if (candidates.length >= cap) {
|
|
350
|
+
break;
|
|
298
351
|
}
|
|
299
|
-
const
|
|
300
|
-
if (
|
|
352
|
+
const head = await fs8.readHead(file.path, AGENT_RESUME_LIMITS.METADATA_HEAD_BYTES).catch(() => null);
|
|
353
|
+
if (head === null) {
|
|
301
354
|
continue;
|
|
302
355
|
}
|
|
303
|
-
const
|
|
304
|
-
if (
|
|
305
|
-
|
|
356
|
+
const core = parseHead(head);
|
|
357
|
+
if (core === null || !core.interactive || !match(core) || seen.has(core.sessionId)) {
|
|
358
|
+
continue;
|
|
306
359
|
}
|
|
360
|
+
seen.add(core.sessionId);
|
|
361
|
+
candidates.push({
|
|
362
|
+
agent,
|
|
363
|
+
sessionId: core.sessionId,
|
|
364
|
+
cwd: core.cwd,
|
|
365
|
+
sourcePath: file.path,
|
|
366
|
+
modifiedAtMs: file.modifiedAtMs,
|
|
367
|
+
updatedAt: core.updatedAt,
|
|
368
|
+
branch: core.branch
|
|
369
|
+
});
|
|
307
370
|
}
|
|
308
371
|
return candidates;
|
|
309
372
|
}
|
|
@@ -323,58 +386,61 @@ async function collectJsonlFiles(root, fs8) {
|
|
|
323
386
|
function isRecentAgentSessionMtime(modifiedAtMs, nowMs) {
|
|
324
387
|
return modifiedAtMs <= nowMs && nowMs - modifiedAtMs <= AGENT_RESUME_RECENT_WINDOW_MS;
|
|
325
388
|
}
|
|
326
|
-
function
|
|
327
|
-
|
|
328
|
-
let cwd = null;
|
|
329
|
-
let updatedAt = null;
|
|
330
|
-
let isInteractiveSession = false;
|
|
331
|
-
for (const line of content.split("\n")) {
|
|
389
|
+
function parseCodexHead(head) {
|
|
390
|
+
for (const line of head.split("\n")) {
|
|
332
391
|
const row = parseJsonObject(line);
|
|
333
392
|
if (row === null) {
|
|
334
393
|
continue;
|
|
335
394
|
}
|
|
336
|
-
|
|
337
|
-
|
|
395
|
+
if (firstString(row, [[AGENT_SESSION_JSON_FIELDS.TYPE]]) !== AGENT_SESSION_ROW_TYPE.CODEX_SESSION_META) {
|
|
396
|
+
continue;
|
|
397
|
+
}
|
|
398
|
+
const sessionId = firstString(row, [
|
|
338
399
|
[AGENT_SESSION_JSON_FIELDS.SESSION_ID],
|
|
339
400
|
[AGENT_SESSION_JSON_FIELDS.ID],
|
|
340
401
|
[AGENT_SESSION_JSON_FIELDS.PAYLOAD, AGENT_SESSION_JSON_FIELDS.SESSION_ID],
|
|
341
402
|
[AGENT_SESSION_JSON_FIELDS.PAYLOAD, AGENT_SESSION_JSON_FIELDS.ID]
|
|
342
403
|
]);
|
|
343
|
-
cwd = firstString(row, [
|
|
404
|
+
const cwd = firstString(row, [
|
|
344
405
|
[AGENT_SESSION_JSON_FIELDS.CWD],
|
|
345
406
|
[AGENT_SESSION_JSON_FIELDS.PAYLOAD, AGENT_SESSION_JSON_FIELDS.CWD]
|
|
346
|
-
])
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
407
|
+
]);
|
|
408
|
+
if (sessionId === null || cwd === null) {
|
|
409
|
+
return null;
|
|
410
|
+
}
|
|
411
|
+
const originator = firstString(row, [
|
|
412
|
+
[AGENT_SESSION_JSON_FIELDS.ORIGINATOR],
|
|
413
|
+
[AGENT_SESSION_JSON_FIELDS.PAYLOAD, AGENT_SESSION_JSON_FIELDS.ORIGINATOR]
|
|
414
|
+
]);
|
|
415
|
+
const threadSource = firstString(row, [
|
|
416
|
+
[AGENT_SESSION_JSON_FIELDS.THREAD_SOURCE],
|
|
417
|
+
[AGENT_SESSION_JSON_FIELDS.PAYLOAD, AGENT_SESSION_JSON_FIELDS.THREAD_SOURCE]
|
|
418
|
+
]);
|
|
419
|
+
return {
|
|
420
|
+
sessionId,
|
|
421
|
+
cwd,
|
|
422
|
+
branch: firstString(row, [
|
|
423
|
+
[AGENT_SESSION_JSON_FIELDS.PAYLOAD, AGENT_SESSION_JSON_FIELDS.GIT, AGENT_SESSION_JSON_FIELDS.BRANCH],
|
|
424
|
+
[AGENT_SESSION_JSON_FIELDS.GIT, AGENT_SESSION_JSON_FIELDS.BRANCH]
|
|
425
|
+
]),
|
|
426
|
+
updatedAt: firstString(row, [[AGENT_SESSION_JSON_FIELDS.TIMESTAMP]]),
|
|
427
|
+
interactive: isCodexInteractive(originator, threadSource)
|
|
428
|
+
};
|
|
351
429
|
}
|
|
352
|
-
return
|
|
353
|
-
agent: AGENT_SESSION_KIND.CODEX,
|
|
354
|
-
sessionId,
|
|
355
|
-
cwd,
|
|
356
|
-
sourcePath,
|
|
357
|
-
modifiedAtMs,
|
|
358
|
-
updatedAt,
|
|
359
|
-
branch: null
|
|
360
|
-
};
|
|
430
|
+
return null;
|
|
361
431
|
}
|
|
362
|
-
function
|
|
363
|
-
if (
|
|
432
|
+
function isCodexInteractive(originator, threadSource) {
|
|
433
|
+
if (threadSource === CODEX_SESSION_THREAD_SOURCE.SUBAGENT) {
|
|
364
434
|
return false;
|
|
365
435
|
}
|
|
366
|
-
const originator = firstString(row, [
|
|
367
|
-
[AGENT_SESSION_JSON_FIELDS.ORIGINATOR],
|
|
368
|
-
[AGENT_SESSION_JSON_FIELDS.PAYLOAD, AGENT_SESSION_JSON_FIELDS.ORIGINATOR]
|
|
369
|
-
]);
|
|
370
436
|
return originator === CODEX_SESSION_ORIGINATOR.TUI || originator === CODEX_SESSION_ORIGINATOR.CLI || originator === CODEX_SESSION_ORIGINATOR.VSCODE || originator === CODEX_SESSION_ORIGINATOR.VSCODE_HYPHEN;
|
|
371
437
|
}
|
|
372
|
-
function
|
|
438
|
+
function parseClaudeHead(head) {
|
|
373
439
|
let sessionId = null;
|
|
374
440
|
let cwd = null;
|
|
375
|
-
let updatedAt = null;
|
|
376
441
|
let branch = null;
|
|
377
|
-
|
|
442
|
+
let updatedAt = null;
|
|
443
|
+
for (const line of head.split("\n")) {
|
|
378
444
|
const row = parseJsonObject(line);
|
|
379
445
|
if (row === null) {
|
|
380
446
|
continue;
|
|
@@ -385,25 +451,20 @@ function parseClaudeCodeCandidateFile(sourcePath, content, modifiedAtMs) {
|
|
|
385
451
|
[AGENT_SESSION_JSON_FIELDS.PAYLOAD, AGENT_SESSION_JSON_FIELDS.SESSION_ID_CAMEL],
|
|
386
452
|
[AGENT_SESSION_JSON_FIELDS.PAYLOAD, AGENT_SESSION_JSON_FIELDS.SESSION_ID]
|
|
387
453
|
]);
|
|
388
|
-
cwd
|
|
454
|
+
cwd ??= firstString(row, [
|
|
389
455
|
[AGENT_SESSION_JSON_FIELDS.CWD],
|
|
390
456
|
[AGENT_SESSION_JSON_FIELDS.PAYLOAD, AGENT_SESSION_JSON_FIELDS.CWD]
|
|
391
|
-
])
|
|
392
|
-
updatedAt
|
|
393
|
-
branch
|
|
457
|
+
]);
|
|
458
|
+
updatedAt ??= firstString(row, [[AGENT_SESSION_JSON_FIELDS.TIMESTAMP]]);
|
|
459
|
+
branch ??= firstString(row, [[AGENT_SESSION_JSON_FIELDS.GIT_BRANCH]]);
|
|
460
|
+
if (sessionId !== null && cwd !== null && branch !== null) {
|
|
461
|
+
break;
|
|
462
|
+
}
|
|
394
463
|
}
|
|
395
464
|
if (sessionId === null || cwd === null) {
|
|
396
465
|
return null;
|
|
397
466
|
}
|
|
398
|
-
return {
|
|
399
|
-
agent: AGENT_SESSION_KIND.CLAUDE_CODE,
|
|
400
|
-
sessionId,
|
|
401
|
-
cwd,
|
|
402
|
-
sourcePath,
|
|
403
|
-
modifiedAtMs,
|
|
404
|
-
updatedAt,
|
|
405
|
-
branch
|
|
406
|
-
};
|
|
467
|
+
return { sessionId, cwd, branch, updatedAt, interactive: true };
|
|
407
468
|
}
|
|
408
469
|
function parseJsonObject(line) {
|
|
409
470
|
const trimmed = line.trim();
|
|
@@ -439,15 +500,6 @@ function valueAtPath(row, path7) {
|
|
|
439
500
|
function isRecord(value) {
|
|
440
501
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
441
502
|
}
|
|
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
503
|
function compareCandidates(left, right) {
|
|
452
504
|
const modifiedDiff = right.modifiedAtMs - left.modifiedAtMs;
|
|
453
505
|
if (modifiedDiff !== 0) {
|
|
@@ -455,8 +507,9 @@ function compareCandidates(left, right) {
|
|
|
455
507
|
}
|
|
456
508
|
return `${left.agent}:${left.sessionId}`.localeCompare(`${right.agent}:${right.sessionId}`);
|
|
457
509
|
}
|
|
458
|
-
function
|
|
459
|
-
|
|
510
|
+
function isPathInsideOrEqual(parent, child) {
|
|
511
|
+
const rel = relative(resolve3(parent), resolve3(child));
|
|
512
|
+
return rel.length === 0 || !isAbsolute(rel) && rel !== ".." && !rel.startsWith(`..${sep}`);
|
|
460
513
|
}
|
|
461
514
|
async function mapWithConcurrency(items, concurrency, mapper) {
|
|
462
515
|
const results = new Array(items.length);
|
|
@@ -478,7 +531,7 @@ async function mapWithConcurrency(items, concurrency, mapper) {
|
|
|
478
531
|
|
|
479
532
|
// src/git/root.ts
|
|
480
533
|
import { execa } from "execa";
|
|
481
|
-
import { basename, dirname, isAbsolute, join, resolve as resolve4 } from "path";
|
|
534
|
+
import { basename, dirname, isAbsolute as isAbsolute2, join, resolve as resolve4 } from "path";
|
|
482
535
|
|
|
483
536
|
// src/git/environment.ts
|
|
484
537
|
function withoutGitEnvironment(env) {
|
|
@@ -659,7 +712,7 @@ async function detectGitCommonDirProductRoot(cwd = CONFIG_PROCESS_CWD.read(), de
|
|
|
659
712
|
};
|
|
660
713
|
}
|
|
661
714
|
const commonDir = extractStdout(commonDirResult.stdout);
|
|
662
|
-
const absoluteCommonDir =
|
|
715
|
+
const absoluteCommonDir = isAbsolute2(commonDir) ? commonDir : resolve4(toplevel, commonDir);
|
|
663
716
|
const gitCommonDirProductRoot = dirname(absoluteCommonDir);
|
|
664
717
|
return {
|
|
665
718
|
productDir: gitCommonDirProductRoot,
|
|
@@ -822,7 +875,7 @@ async function gatherGitFacts(cwd = CONFIG_PROCESS_CWD.read(), deps = defaultGit
|
|
|
822
875
|
};
|
|
823
876
|
}
|
|
824
877
|
const rawCommonDir = extractStdout(commonDirResult.stdout);
|
|
825
|
-
const commonDir =
|
|
878
|
+
const commonDir = isAbsolute2(rawCommonDir) ? rawCommonDir : resolve4(worktreeRoot, rawCommonDir);
|
|
826
879
|
const bareResult = await deps.execa(
|
|
827
880
|
GIT_ROOT_COMMAND.EXECUTABLE,
|
|
828
881
|
[...GIT_CORE_BARE_ARGS],
|
|
@@ -845,8 +898,15 @@ var nodeAgentSessionFileSystem = {
|
|
|
845
898
|
isFile: entry.isFile()
|
|
846
899
|
}));
|
|
847
900
|
},
|
|
848
|
-
async
|
|
849
|
-
|
|
901
|
+
async readHead(path7, maxBytes) {
|
|
902
|
+
const handle = await open(path7, "r");
|
|
903
|
+
try {
|
|
904
|
+
const buffer = Buffer.alloc(maxBytes);
|
|
905
|
+
const { bytesRead } = await handle.read(buffer, 0, maxBytes, 0);
|
|
906
|
+
return buffer.toString(AGENT_SESSION_STORE.TEXT_ENCODING, 0, bytesRead);
|
|
907
|
+
} finally {
|
|
908
|
+
await handle.close();
|
|
909
|
+
}
|
|
850
910
|
},
|
|
851
911
|
async stat(path7) {
|
|
852
912
|
const result = await stat(path7);
|
|
@@ -868,6 +928,7 @@ async function loadAgentResumeCandidates(options) {
|
|
|
868
928
|
invocationDir: options.cwd,
|
|
869
929
|
homeDir: deps.homeDir(),
|
|
870
930
|
nowMs: deps.nowMs(),
|
|
931
|
+
scope: options.scope,
|
|
871
932
|
fs: deps.fs,
|
|
872
933
|
resolveWorktreeRoot: deps.resolveWorktreeRoot
|
|
873
934
|
});
|
|
@@ -1182,7 +1243,11 @@ var AGENT_CLI = {
|
|
|
1182
1243
|
flags: {
|
|
1183
1244
|
latest: "--latest",
|
|
1184
1245
|
list: "--list",
|
|
1185
|
-
json: "--json"
|
|
1246
|
+
json: "--json",
|
|
1247
|
+
branch: "--branch"
|
|
1248
|
+
},
|
|
1249
|
+
optionArgs: {
|
|
1250
|
+
branch: "--branch <name>"
|
|
1186
1251
|
}
|
|
1187
1252
|
};
|
|
1188
1253
|
var AGENT_CLI_EXIT = {
|
|
@@ -1213,6 +1278,21 @@ function handleError(invocation, error) {
|
|
|
1213
1278
|
writeError(invocation, `Error: ${message}`);
|
|
1214
1279
|
return invocation.io.exit(AGENT_CLI_EXIT.FAILURE);
|
|
1215
1280
|
}
|
|
1281
|
+
function resumeScopeFromOptions(options) {
|
|
1282
|
+
return options.branch === void 0 ? worktreeResumeScope() : branchResumeScope(options.branch);
|
|
1283
|
+
}
|
|
1284
|
+
async function dispatchInteractiveResume(mode, commandOptions, deps, invocation) {
|
|
1285
|
+
const candidates = await loadAgentResumeCandidates(commandOptions);
|
|
1286
|
+
if (candidates.length === 0) {
|
|
1287
|
+
writeError(invocation, AGENT_RESUME_TEXT.NO_MATCHES);
|
|
1288
|
+
return AGENT_CLI_EXIT.FAILURE;
|
|
1289
|
+
}
|
|
1290
|
+
if (mode === AGENT_RESUME_MODE.LATEST) {
|
|
1291
|
+
return deps.launchCandidate(candidates[0]);
|
|
1292
|
+
}
|
|
1293
|
+
const pickerResult = await deps.pickCandidate(candidates);
|
|
1294
|
+
return pickerResult.kind === AGENT_RESUME_PICKER_RESULT.SELECTED ? deps.launchCandidate(pickerResult.candidate) : AGENT_CLI_EXIT.SUCCESS;
|
|
1295
|
+
}
|
|
1216
1296
|
function createAgentDomain(deps = {}) {
|
|
1217
1297
|
const resolvedDeps = {
|
|
1218
1298
|
...DEFAULT_AGENT_CLI_DEPENDENCIES,
|
|
@@ -1223,7 +1303,7 @@ function createAgentDomain(deps = {}) {
|
|
|
1223
1303
|
description: "Find and resume coding-agent sessions",
|
|
1224
1304
|
register: (program, invocation) => {
|
|
1225
1305
|
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) => {
|
|
1306
|
+
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
1307
|
let requestedExitCode = AGENT_CLI_EXIT.SUCCESS;
|
|
1228
1308
|
try {
|
|
1229
1309
|
const mode = resolveAgentResumeMode(options);
|
|
@@ -1233,6 +1313,7 @@ function createAgentDomain(deps = {}) {
|
|
|
1233
1313
|
} else {
|
|
1234
1314
|
const commandOptions = {
|
|
1235
1315
|
cwd: invocation.resolveEffectiveInvocationDir(),
|
|
1316
|
+
scope: resumeScopeFromOptions(options),
|
|
1236
1317
|
deps: resolvedDeps.resumeDeps
|
|
1237
1318
|
};
|
|
1238
1319
|
if (mode === AGENT_RESUME_MODE.JSON) {
|
|
@@ -1243,18 +1324,7 @@ function createAgentDomain(deps = {}) {
|
|
|
1243
1324
|
writeOutput(invocation, await listAgentResumeSessions(commandOptions));
|
|
1244
1325
|
return;
|
|
1245
1326
|
}
|
|
1246
|
-
|
|
1247
|
-
if (candidates.length === 0) {
|
|
1248
|
-
writeError(invocation, AGENT_RESUME_TEXT.NO_MATCHES);
|
|
1249
|
-
requestedExitCode = AGENT_CLI_EXIT.FAILURE;
|
|
1250
|
-
} else if (mode === AGENT_RESUME_MODE.LATEST) {
|
|
1251
|
-
requestedExitCode = await resolvedDeps.launchCandidate(candidates[0]);
|
|
1252
|
-
} else {
|
|
1253
|
-
const pickerResult = await resolvedDeps.pickCandidate(candidates);
|
|
1254
|
-
if (pickerResult.kind === AGENT_RESUME_PICKER_RESULT.SELECTED) {
|
|
1255
|
-
requestedExitCode = await resolvedDeps.launchCandidate(pickerResult.candidate);
|
|
1256
|
-
}
|
|
1257
|
-
}
|
|
1327
|
+
requestedExitCode = await dispatchInteractiveResume(mode, commandOptions, resolvedDeps, invocation);
|
|
1258
1328
|
}
|
|
1259
1329
|
} catch (error) {
|
|
1260
1330
|
handleError(invocation, error);
|
|
@@ -2475,14 +2545,14 @@ async function compactRetrieveCommand(options) {
|
|
|
2475
2545
|
}
|
|
2476
2546
|
|
|
2477
2547
|
// src/commands/compact/store.ts
|
|
2478
|
-
import { readFile
|
|
2548
|
+
import { readFile } from "fs/promises";
|
|
2479
2549
|
var UTF8_ENCODING = "utf8";
|
|
2480
2550
|
async function compactStoreCommand(options) {
|
|
2481
2551
|
const sessionToken = resolveCompactSessionToken(options.sessionId, options.env ?? process.env);
|
|
2482
2552
|
if (sessionToken === void 0) return 1;
|
|
2483
2553
|
let transcript;
|
|
2484
2554
|
try {
|
|
2485
|
-
transcript = await
|
|
2555
|
+
transcript = await readFile(options.transcript, UTF8_ENCODING);
|
|
2486
2556
|
} catch {
|
|
2487
2557
|
return 1;
|
|
2488
2558
|
}
|
|
@@ -2538,7 +2608,7 @@ var compactDomain = {
|
|
|
2538
2608
|
};
|
|
2539
2609
|
|
|
2540
2610
|
// src/config/index.ts
|
|
2541
|
-
import { readFile as
|
|
2611
|
+
import { readFile as readFile2 } from "fs/promises";
|
|
2542
2612
|
import { join as join4 } from "path";
|
|
2543
2613
|
import { parse as parseToml, stringify as stringifyToml } from "smol-toml";
|
|
2544
2614
|
import { parse as parseYaml, parseDocument as parseYamlDocument, stringify as stringifyYaml } from "yaml";
|
|
@@ -2725,7 +2795,7 @@ function validateBoolean(path7, value) {
|
|
|
2725
2795
|
}
|
|
2726
2796
|
function validateAgent(path7, value) {
|
|
2727
2797
|
if (typeof value !== "string" || !isAgent(value)) {
|
|
2728
|
-
return { ok: false, error: `${path7} must be a registered
|
|
2798
|
+
return { ok: false, error: `${path7} must be a registered agent` };
|
|
2729
2799
|
}
|
|
2730
2800
|
return { ok: true, value };
|
|
2731
2801
|
}
|
|
@@ -2734,7 +2804,7 @@ function isAgent(value) {
|
|
|
2734
2804
|
}
|
|
2735
2805
|
function validateAgentArray(path7, value) {
|
|
2736
2806
|
if (!Array.isArray(value) || value.length === 0) {
|
|
2737
|
-
return { ok: false, error: `${path7} must be a non-empty array of registered
|
|
2807
|
+
return { ok: false, error: `${path7} must be a non-empty array of registered agents` };
|
|
2738
2808
|
}
|
|
2739
2809
|
const agents = [];
|
|
2740
2810
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -2742,7 +2812,7 @@ function validateAgentArray(path7, value) {
|
|
|
2742
2812
|
const agent = validateAgent(`${path7}.${index}`, entry);
|
|
2743
2813
|
if (!agent.ok) return agent;
|
|
2744
2814
|
if (seen.has(agent.value)) {
|
|
2745
|
-
return { ok: false, error: `${path7}.${index} repeats registered
|
|
2815
|
+
return { ok: false, error: `${path7}.${index} repeats registered agent ${agent.value}` };
|
|
2746
2816
|
}
|
|
2747
2817
|
seen.add(agent.value);
|
|
2748
2818
|
agents.push(agent.value);
|
|
@@ -2896,6 +2966,13 @@ function validatePluginBootstrap(raw) {
|
|
|
2896
2966
|
skills.value
|
|
2897
2967
|
);
|
|
2898
2968
|
if (!skillUniqueness.ok) return skillUniqueness;
|
|
2969
|
+
const bootstrapNameUniqueness = validatePluginBootstrapNameUniqueness(
|
|
2970
|
+
sectionPath,
|
|
2971
|
+
marketplaces.value,
|
|
2972
|
+
plugins.value,
|
|
2973
|
+
skills.value
|
|
2974
|
+
);
|
|
2975
|
+
if (!bootstrapNameUniqueness.ok) return bootstrapNameUniqueness;
|
|
2899
2976
|
return {
|
|
2900
2977
|
ok: true,
|
|
2901
2978
|
value: {
|
|
@@ -2944,6 +3021,38 @@ function validateNamedAgentEntryUniqueness(path7, entries) {
|
|
|
2944
3021
|
}
|
|
2945
3022
|
return { ok: true, value: void 0 };
|
|
2946
3023
|
}
|
|
3024
|
+
function validatePluginBootstrapNameUniqueness(sectionPath, marketplaces, plugins, skills) {
|
|
3025
|
+
const namesByAgent = /* @__PURE__ */ new Map();
|
|
3026
|
+
const entries = [
|
|
3027
|
+
...marketplaces.map((entry, index) => ({
|
|
3028
|
+
agent: entry.agent,
|
|
3029
|
+
name: entry.name,
|
|
3030
|
+
path: `${sectionPath}.${HARNESS_ENVIRONMENT_CONFIG_FIELDS.MARKETPLACES}.${index}`
|
|
3031
|
+
})),
|
|
3032
|
+
...plugins.map((entry, index) => ({
|
|
3033
|
+
agent: entry.agent,
|
|
3034
|
+
name: entry.name,
|
|
3035
|
+
path: `${sectionPath}.${HARNESS_ENVIRONMENT_CONFIG_FIELDS.PLUGINS}.${index}`
|
|
3036
|
+
})),
|
|
3037
|
+
...skills.map((entry, index) => ({
|
|
3038
|
+
agent: entry.agent,
|
|
3039
|
+
name: entry.name,
|
|
3040
|
+
path: `${sectionPath}.${HARNESS_ENVIRONMENT_CONFIG_FIELDS.SKILLS}.${index}`
|
|
3041
|
+
}))
|
|
3042
|
+
];
|
|
3043
|
+
for (const entry of entries) {
|
|
3044
|
+
const agentNames = namesByAgent.get(entry.agent) ?? /* @__PURE__ */ new Set();
|
|
3045
|
+
if (agentNames.has(entry.name)) {
|
|
3046
|
+
return {
|
|
3047
|
+
ok: false,
|
|
3048
|
+
error: `${entry.path}.${HARNESS_ENVIRONMENT_CONFIG_FIELDS.NAME} ${JSON.stringify(entry.name)} is already used by another ${entry.agent} bootstrap entry`
|
|
3049
|
+
};
|
|
3050
|
+
}
|
|
3051
|
+
agentNames.add(entry.name);
|
|
3052
|
+
namesByAgent.set(entry.agent, agentNames);
|
|
3053
|
+
}
|
|
3054
|
+
return { ok: true, value: void 0 };
|
|
3055
|
+
}
|
|
2947
3056
|
function validatePluginMarketplaceReferences(path7, marketplaces, plugins) {
|
|
2948
3057
|
const marketplacesByAgent = /* @__PURE__ */ new Map();
|
|
2949
3058
|
for (const marketplace of marketplaces) {
|
|
@@ -4160,7 +4269,7 @@ async function readProductConfigFile(productDir) {
|
|
|
4160
4269
|
const path7 = join4(productDir, filename);
|
|
4161
4270
|
let raw;
|
|
4162
4271
|
try {
|
|
4163
|
-
raw = await
|
|
4272
|
+
raw = await readFile2(path7, "utf8");
|
|
4164
4273
|
} catch (error) {
|
|
4165
4274
|
if (isFileNotFound(error)) continue;
|
|
4166
4275
|
return { ok: false, error: `failed to read ${filename}: ${toMessage(error)}` };
|
|
@@ -4444,7 +4553,7 @@ var configDomain = {
|
|
|
4444
4553
|
};
|
|
4445
4554
|
|
|
4446
4555
|
// src/interfaces/cli/diagnose.ts
|
|
4447
|
-
import { readFile as
|
|
4556
|
+
import { readFile as readFile3 } from "fs/promises";
|
|
4448
4557
|
import { Option } from "commander";
|
|
4449
4558
|
|
|
4450
4559
|
// src/domains/diagnose/types.ts
|
|
@@ -5957,11 +6066,12 @@ function worktreePoolReadingFromSnapshot(snapshot) {
|
|
|
5957
6066
|
}
|
|
5958
6067
|
function sessionEnvironmentReadingFromSnapshot(snapshot, environment) {
|
|
5959
6068
|
const currentWorktree = snapshot.worktrees.find((worktree) => worktree.root === snapshot.currentWorktreeRoot);
|
|
6069
|
+
const worktreeClaimed = currentWorktree?.status === OCCUPANCY_STATUS.RUNNING;
|
|
5960
6070
|
return {
|
|
5961
6071
|
errored: snapshot.errored,
|
|
5962
6072
|
hookPresent: environment.hookPresent,
|
|
5963
|
-
sessionIdentity: environment.sessionIdentity,
|
|
5964
|
-
worktreeClaimed
|
|
6073
|
+
sessionIdentity: environment.sessionIdentity || worktreeClaimed && currentWorktree.sessionId !== void 0,
|
|
6074
|
+
worktreeClaimed
|
|
5965
6075
|
};
|
|
5966
6076
|
}
|
|
5967
6077
|
async function exportedClaimReadingFromEnv(env, deps) {
|
|
@@ -6231,7 +6341,7 @@ var diagnoseDomain = {
|
|
|
6231
6341
|
isTty: Boolean(process.stdout.isTTY)
|
|
6232
6342
|
}),
|
|
6233
6343
|
registry: defaultRegistry(),
|
|
6234
|
-
fs: { readFile: (path7) =>
|
|
6344
|
+
fs: { readFile: (path7) => readFile3(path7, "utf8") }
|
|
6235
6345
|
});
|
|
6236
6346
|
if (!result.ok) {
|
|
6237
6347
|
handleError2(result.error, invocation.io);
|
|
@@ -6946,9 +7056,6 @@ function compareJournalRunsOldestFirst(left, right) {
|
|
|
6946
7056
|
const createdAtOrder = left.createdAtMs - right.createdAtMs;
|
|
6947
7057
|
return createdAtOrder === 0 ? compareAsciiStrings(left.runToken, right.runToken) : createdAtOrder;
|
|
6948
7058
|
}
|
|
6949
|
-
function applyJournalRunListLimit(runs, limit) {
|
|
6950
|
-
return limit === void 0 ? runs : runs.slice(0, limit);
|
|
6951
|
-
}
|
|
6952
7059
|
function journalRunFilePath(scope2) {
|
|
6953
7060
|
const typeRunsDir = journalRunsDir(scope2);
|
|
6954
7061
|
if (!typeRunsDir.ok) return typeRunsDir;
|
|
@@ -7369,6 +7476,9 @@ var JOURNAL_RUNTIME_ERROR = {
|
|
|
7369
7476
|
RENDER_FAILED: "journal render failed",
|
|
7370
7477
|
RUN_NOT_FOUND: "journal run not found; open the run before operating on it"
|
|
7371
7478
|
};
|
|
7479
|
+
function applyJournalRunListLimit(runs, limit) {
|
|
7480
|
+
return runs.slice(0, limit);
|
|
7481
|
+
}
|
|
7372
7482
|
function bindRunFilePath(ref) {
|
|
7373
7483
|
return journalRunFilePath({
|
|
7374
7484
|
productDir: ref.productDir,
|
|
@@ -7541,7 +7651,11 @@ async function readSealedJournalRunSet(scope2, options = {}) {
|
|
|
7541
7651
|
}
|
|
7542
7652
|
return {
|
|
7543
7653
|
ok: true,
|
|
7544
|
-
value: runs.sort((left, right) =>
|
|
7654
|
+
value: runs.sort((left, right) => compareJournalRunsNewestFirst(left.metadata, right.metadata)).slice(0, scope2.limit).sort((left, right) => compareJournalRunsOldestFirst(left.metadata, right.metadata)).map((run) => ({
|
|
7655
|
+
runToken: run.metadata.runToken,
|
|
7656
|
+
metadata: run.metadata,
|
|
7657
|
+
events: run.events.slice(0, scope2.eventLimit)
|
|
7658
|
+
}))
|
|
7545
7659
|
};
|
|
7546
7660
|
}
|
|
7547
7661
|
async function appendJournalEvent(ref, input, sink, options = {}) {
|
|
@@ -7593,7 +7707,12 @@ var JOURNAL_CLI_EXIT_CODE = {
|
|
|
7593
7707
|
OK: 0,
|
|
7594
7708
|
ERROR: 1
|
|
7595
7709
|
};
|
|
7596
|
-
var
|
|
7710
|
+
var JOURNAL_CLI_RUN_LIMIT = {
|
|
7711
|
+
DEFAULT: 20,
|
|
7712
|
+
MIN: 1
|
|
7713
|
+
};
|
|
7714
|
+
var JOURNAL_CLI_READ_SET_EVENT_LIMIT = {
|
|
7715
|
+
DEFAULT: 100,
|
|
7597
7716
|
MIN: 1
|
|
7598
7717
|
};
|
|
7599
7718
|
var JOURNAL_CLI_ENV = {
|
|
@@ -7613,7 +7732,8 @@ var JOURNAL_CLI_ERROR = {
|
|
|
7613
7732
|
PULL_REQUEST_UNRESOLVED: "github pull-request number is not resolvable from the environment",
|
|
7614
7733
|
INVALID_EVENT_INPUT: "journal append event input is missing a required CloudEvents field",
|
|
7615
7734
|
INVALID_CURSOR: "journal read cursor must be a whole non-negative integer",
|
|
7616
|
-
|
|
7735
|
+
INVALID_RUN_LIMIT: "journal run limit must be a positive whole integer",
|
|
7736
|
+
INVALID_READ_SET_EVENT_LIMIT: "journal read-set event limit must be a positive whole integer",
|
|
7617
7737
|
INVALID_SEALED_FILTER: "journal list sealed filter is not registered",
|
|
7618
7738
|
INVALID_TERMINAL_STATE_FILTER: "journal list terminal-state filter is not registered",
|
|
7619
7739
|
OPEN_HYDRATION_FAILED: "journal open failed to hydrate the pull request's prior runs"
|
|
@@ -7813,13 +7933,21 @@ function parseJournalCursor(raw) {
|
|
|
7813
7933
|
const value = Number.parseInt(raw, DECIMAL_RADIX);
|
|
7814
7934
|
return Number.isSafeInteger(value) ? { ok: true, value } : { ok: false, error: JOURNAL_CLI_ERROR.INVALID_CURSOR };
|
|
7815
7935
|
}
|
|
7816
|
-
function
|
|
7817
|
-
if (raw === void 0) return { ok: true, value:
|
|
7936
|
+
function parseJournalRunLimit(raw) {
|
|
7937
|
+
if (raw === void 0) return { ok: true, value: JOURNAL_CLI_RUN_LIMIT.DEFAULT };
|
|
7818
7938
|
if (!POSITIVE_INTEGER_PATTERN.test(raw)) {
|
|
7819
|
-
return { ok: false, error: JOURNAL_CLI_ERROR.
|
|
7939
|
+
return { ok: false, error: JOURNAL_CLI_ERROR.INVALID_RUN_LIMIT };
|
|
7820
7940
|
}
|
|
7821
7941
|
const value = Number.parseInt(raw, DECIMAL_RADIX);
|
|
7822
|
-
return Number.isSafeInteger(value) && value >=
|
|
7942
|
+
return Number.isSafeInteger(value) && value >= JOURNAL_CLI_RUN_LIMIT.MIN ? { ok: true, value } : { ok: false, error: JOURNAL_CLI_ERROR.INVALID_RUN_LIMIT };
|
|
7943
|
+
}
|
|
7944
|
+
function parseJournalReadSetEventLimit(raw) {
|
|
7945
|
+
if (raw === void 0) return { ok: true, value: JOURNAL_CLI_READ_SET_EVENT_LIMIT.DEFAULT };
|
|
7946
|
+
if (!POSITIVE_INTEGER_PATTERN.test(raw)) {
|
|
7947
|
+
return { ok: false, error: JOURNAL_CLI_ERROR.INVALID_READ_SET_EVENT_LIMIT };
|
|
7948
|
+
}
|
|
7949
|
+
const value = Number.parseInt(raw, DECIMAL_RADIX);
|
|
7950
|
+
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
7951
|
}
|
|
7824
7952
|
function parseJournalSealedFilter(raw) {
|
|
7825
7953
|
if (raw === void 0) return { ok: true, value: void 0 };
|
|
@@ -7857,7 +7985,7 @@ async function journalListCommand(scope2, deps = {}) {
|
|
|
7857
7985
|
if (!sealed.ok) return errorResult(sealed.error);
|
|
7858
7986
|
const terminalState = parseJournalTerminalFilter(scope2.terminalState);
|
|
7859
7987
|
if (!terminalState.ok) return errorResult(terminalState.error);
|
|
7860
|
-
const limit =
|
|
7988
|
+
const limit = parseJournalRunLimit(scope2.limit);
|
|
7861
7989
|
if (!limit.ok) return errorResult(limit.error);
|
|
7862
7990
|
const listScope = {
|
|
7863
7991
|
productDir: await resolveJournalProductDir(deps),
|
|
@@ -7865,16 +7993,27 @@ async function journalListCommand(scope2, deps = {}) {
|
|
|
7865
7993
|
...scope2.type === void 0 ? {} : { type: scope2.type },
|
|
7866
7994
|
...sealed.value === void 0 ? {} : { sealed: sealed.value },
|
|
7867
7995
|
...terminalState.value === void 0 ? {} : { terminalState: terminalState.value },
|
|
7868
|
-
|
|
7996
|
+
limit: limit.value
|
|
7869
7997
|
};
|
|
7870
7998
|
const runs = await listJournalRuns(listScope, verbOptions(deps));
|
|
7871
7999
|
if (!runs.ok) return errorResult(runs.error);
|
|
7872
8000
|
return okResult(JSON.stringify(runs.value));
|
|
7873
8001
|
}
|
|
7874
8002
|
async function journalReadSetCommand(scope2, deps = {}) {
|
|
8003
|
+
const limit = parseJournalRunLimit(scope2.limit);
|
|
8004
|
+
if (!limit.ok) return errorResult(limit.error);
|
|
8005
|
+
const eventLimit = parseJournalReadSetEventLimit(scope2.eventLimit);
|
|
8006
|
+
if (!eventLimit.ok) return errorResult(eventLimit.error);
|
|
7875
8007
|
const context = await resolveJournalRunScope(scope2, deps);
|
|
7876
8008
|
if (!context.ok) return errorResult(context.error);
|
|
7877
|
-
const runs = await readSealedJournalRunSet(
|
|
8009
|
+
const runs = await readSealedJournalRunSet(
|
|
8010
|
+
{
|
|
8011
|
+
...context.value,
|
|
8012
|
+
eventLimit: eventLimit.value,
|
|
8013
|
+
limit: limit.value
|
|
8014
|
+
},
|
|
8015
|
+
verbOptions(deps)
|
|
8016
|
+
);
|
|
7878
8017
|
if (!runs.ok) return errorResult(runs.error);
|
|
7879
8018
|
return okResult(JSON.stringify(runs.value));
|
|
7880
8019
|
}
|
|
@@ -7897,6 +8036,41 @@ async function journalRenderCommand(scope2, deps = {}) {
|
|
|
7897
8036
|
return okResult(JSON.stringify(rendered.value));
|
|
7898
8037
|
}
|
|
7899
8038
|
|
|
8039
|
+
// src/interfaces/cli/lib/stream-report.ts
|
|
8040
|
+
var CLI_STREAM_REPORT = {
|
|
8041
|
+
LINE_SEPARATOR: "\n"
|
|
8042
|
+
};
|
|
8043
|
+
function reportCliResult(result, io) {
|
|
8044
|
+
const output = `${result.output}${CLI_STREAM_REPORT.LINE_SEPARATOR}`;
|
|
8045
|
+
if (result.exitCode === 0) io.writeStdout(output);
|
|
8046
|
+
else io.writeStderr(output);
|
|
8047
|
+
io.setExitCode(result.exitCode);
|
|
8048
|
+
}
|
|
8049
|
+
|
|
8050
|
+
// src/interfaces/cli/lib/journal-stream-binding.ts
|
|
8051
|
+
function stdoutStreamSink(io) {
|
|
8052
|
+
return {
|
|
8053
|
+
async emit(event) {
|
|
8054
|
+
io.writeStdout(`${JSON.stringify(event)}${CLI_STREAM_REPORT.LINE_SEPARATOR}`);
|
|
8055
|
+
}
|
|
8056
|
+
};
|
|
8057
|
+
}
|
|
8058
|
+
function stderrStreamSink(io) {
|
|
8059
|
+
return {
|
|
8060
|
+
async emit(event) {
|
|
8061
|
+
io.writeStderr(`${JSON.stringify(event)}${CLI_STREAM_REPORT.LINE_SEPARATOR}`);
|
|
8062
|
+
}
|
|
8063
|
+
};
|
|
8064
|
+
}
|
|
8065
|
+
function createJournalStreamBinding(io, localSink = stdoutStreamSink(io)) {
|
|
8066
|
+
const repository = process.env[JOURNAL_CLI_ENV.GITHUB_REPOSITORY] ?? "";
|
|
8067
|
+
return {
|
|
8068
|
+
localSink,
|
|
8069
|
+
githubClient: createGithubPullRequestCommentClient({ repository, run: runGhApi }),
|
|
8070
|
+
githubRepository: repository
|
|
8071
|
+
};
|
|
8072
|
+
}
|
|
8073
|
+
|
|
7900
8074
|
// src/interfaces/cli/journal.ts
|
|
7901
8075
|
var JOURNAL_CLI = {
|
|
7902
8076
|
commandName: "journal",
|
|
@@ -7914,9 +8088,14 @@ var JOURNAL_CLI = {
|
|
|
7914
8088
|
branchSlugOption: "--branch-slug <slug>",
|
|
7915
8089
|
sealedOption: "--sealed <state>",
|
|
7916
8090
|
terminalStateOption: "--terminal-state <state>",
|
|
7917
|
-
limitOption: "--limit <count>"
|
|
8091
|
+
limitOption: "--limit <count>",
|
|
8092
|
+
eventLimitOption: "--event-limit <count>"
|
|
8093
|
+
};
|
|
8094
|
+
var JOURNAL_CLI_HELP = {
|
|
8095
|
+
LIST_RUN_LIMIT: `Maximum number of runs (default: ${JOURNAL_CLI_RUN_LIMIT.DEFAULT})`,
|
|
8096
|
+
READ_SET_EVENT_LIMIT: `Maximum events returned per run (default: ${JOURNAL_CLI_READ_SET_EVENT_LIMIT.DEFAULT})`,
|
|
8097
|
+
READ_SET_RUN_LIMIT: `Maximum number of sealed runs (default: ${JOURNAL_CLI_RUN_LIMIT.DEFAULT})`
|
|
7918
8098
|
};
|
|
7919
|
-
var STREAM_LINE_SEPARATOR = "\n";
|
|
7920
8099
|
var MALFORMED_EVENT_INPUT_ERROR = "journal append event input is not valid JSON";
|
|
7921
8100
|
function scope(options) {
|
|
7922
8101
|
return { type: options.type };
|
|
@@ -7935,7 +8114,7 @@ var journalDomain = {
|
|
|
7935
8114
|
const journalDeps = () => ({
|
|
7936
8115
|
cwd: invocation.resolveEffectiveInvocationDir(),
|
|
7937
8116
|
onWarning: (warning) => {
|
|
7938
|
-
if (warning !== void 0) invocation.io.writeStderr(`${warning}${
|
|
8117
|
+
if (warning !== void 0) invocation.io.writeStderr(`${warning}${CLI_STREAM_REPORT.LINE_SEPARATOR}`);
|
|
7939
8118
|
}
|
|
7940
8119
|
});
|
|
7941
8120
|
const journalCmd = program.command(JOURNAL_CLI.commandName).description(JOURNAL_CLI.description);
|
|
@@ -7951,7 +8130,7 @@ var journalDomain = {
|
|
|
7951
8130
|
const result = await journalAppendCommand(
|
|
7952
8131
|
runScope(options),
|
|
7953
8132
|
input.value,
|
|
7954
|
-
|
|
8133
|
+
createJournalStreamBinding(invocation.io),
|
|
7955
8134
|
journalDeps()
|
|
7956
8135
|
);
|
|
7957
8136
|
if (result.exitCode === JOURNAL_CLI_EXIT_CODE.OK) invocation.io.setExitCode(JOURNAL_CLI_EXIT_CODE.OK);
|
|
@@ -7966,15 +8145,17 @@ var journalDomain = {
|
|
|
7966
8145
|
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
8146
|
report(await journalRenderCommand(runScope(options), journalDeps()), invocation.io);
|
|
7968
8147
|
});
|
|
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,
|
|
8148
|
+
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
8149
|
report(await journalListCommand(options, journalDeps()), invocation.io);
|
|
7971
8150
|
});
|
|
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) => {
|
|
8151
|
+
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
8152
|
report(
|
|
7974
8153
|
await journalReadSetCommand(
|
|
7975
8154
|
{
|
|
7976
8155
|
...scope(options),
|
|
7977
|
-
...options.branchSlug === void 0 ? {} : { branchSlug: options.branchSlug }
|
|
8156
|
+
...options.branchSlug === void 0 ? {} : { branchSlug: options.branchSlug },
|
|
8157
|
+
...options.eventLimit === void 0 ? {} : { eventLimit: options.eventLimit },
|
|
8158
|
+
...options.limit === void 0 ? {} : { limit: options.limit }
|
|
7978
8159
|
},
|
|
7979
8160
|
journalDeps()
|
|
7980
8161
|
),
|
|
@@ -7983,21 +8164,6 @@ var journalDomain = {
|
|
|
7983
8164
|
});
|
|
7984
8165
|
}
|
|
7985
8166
|
};
|
|
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
8167
|
async function readStdinEventInput() {
|
|
8002
8168
|
const chunks = [];
|
|
8003
8169
|
for await (const chunk of process.stdin) {
|
|
@@ -8010,7 +8176,7 @@ async function readStdinEventInput() {
|
|
|
8010
8176
|
}
|
|
8011
8177
|
}
|
|
8012
8178
|
function report(result, io) {
|
|
8013
|
-
const output = `${result.output}${
|
|
8179
|
+
const output = `${result.output}${CLI_STREAM_REPORT.LINE_SEPARATOR}`;
|
|
8014
8180
|
if (result.exitCode === JOURNAL_CLI_EXIT_CODE.OK) io.writeStdout(output);
|
|
8015
8181
|
else io.writeStderr(output);
|
|
8016
8182
|
io.setExitCode(result.exitCode);
|
|
@@ -9323,7 +9489,7 @@ ${formatSessionOutputMarker(SESSION_OUTPUT_MARKER.SESSION_FILE, absolutePath)}`;
|
|
|
9323
9489
|
}
|
|
9324
9490
|
|
|
9325
9491
|
// src/commands/session/list.ts
|
|
9326
|
-
import { readdir as readdir2, readFile as
|
|
9492
|
+
import { readdir as readdir2, readFile as readFile4 } from "fs/promises";
|
|
9327
9493
|
import { join as join11 } from "path";
|
|
9328
9494
|
var SESSION_LIST_FORMAT = {
|
|
9329
9495
|
TEXT: "text",
|
|
@@ -9338,7 +9504,7 @@ async function loadSessionsFromDir(dir, status) {
|
|
|
9338
9504
|
if (!file.endsWith(".md")) continue;
|
|
9339
9505
|
const id = file.replace(".md", "");
|
|
9340
9506
|
const filePath = join11(dir, file);
|
|
9341
|
-
const content = await
|
|
9507
|
+
const content = await readFile4(filePath, SESSION_FILE_ENCODING);
|
|
9342
9508
|
const metadata = parseSessionMetadata(content);
|
|
9343
9509
|
sessions.push({
|
|
9344
9510
|
id,
|
|
@@ -9401,7 +9567,7 @@ async function listCommand(options) {
|
|
|
9401
9567
|
}
|
|
9402
9568
|
|
|
9403
9569
|
// src/commands/session/pickup.ts
|
|
9404
|
-
import { mkdir as mkdir3, readdir as readdir3, readFile as
|
|
9570
|
+
import { mkdir as mkdir3, readdir as readdir3, readFile as readFile5, rename as rename2 } from "fs/promises";
|
|
9405
9571
|
import { join as join12, resolve as resolve7 } from "path";
|
|
9406
9572
|
|
|
9407
9573
|
// src/domains/session/pickup.ts
|
|
@@ -9442,7 +9608,7 @@ var PICKUP_TARGET_STATUS = SESSION_STATUSES[1];
|
|
|
9442
9608
|
var PICKUP_DEPS = {
|
|
9443
9609
|
mkdir: mkdir3,
|
|
9444
9610
|
readdir: readdir3,
|
|
9445
|
-
readFile:
|
|
9611
|
+
readFile: readFile5,
|
|
9446
9612
|
rename: rename2
|
|
9447
9613
|
};
|
|
9448
9614
|
async function loadTodoSessions(config) {
|
|
@@ -9551,7 +9717,7 @@ async function loadPickCandidates(options) {
|
|
|
9551
9717
|
}
|
|
9552
9718
|
|
|
9553
9719
|
// src/commands/session/prune.ts
|
|
9554
|
-
import { readdir as readdir4, readFile as
|
|
9720
|
+
import { readdir as readdir4, readFile as readFile6, unlink as unlink2 } from "fs/promises";
|
|
9555
9721
|
import { join as join13 } from "path";
|
|
9556
9722
|
|
|
9557
9723
|
// src/domains/session/prune.ts
|
|
@@ -9602,7 +9768,7 @@ async function loadArchiveSessions(config) {
|
|
|
9602
9768
|
if (!file.endsWith(".md")) continue;
|
|
9603
9769
|
const id = file.replace(".md", "");
|
|
9604
9770
|
const filePath = join13(config.archiveDir, file);
|
|
9605
|
-
const content = await
|
|
9771
|
+
const content = await readFile6(filePath, SESSION_FILE_ENCODING);
|
|
9606
9772
|
const metadata = parseSessionMetadata(content);
|
|
9607
9773
|
sessions.push({
|
|
9608
9774
|
id,
|
|
@@ -9719,7 +9885,7 @@ async function releaseCommand(options) {
|
|
|
9719
9885
|
}
|
|
9720
9886
|
|
|
9721
9887
|
// src/commands/session/show.ts
|
|
9722
|
-
import { readFile as
|
|
9888
|
+
import { readFile as readFile7, stat as stat5 } from "fs/promises";
|
|
9723
9889
|
async function findExistingPath(paths, _config) {
|
|
9724
9890
|
for (let i = 0; i < paths.length; i++) {
|
|
9725
9891
|
const filePath = paths[i];
|
|
@@ -9739,7 +9905,7 @@ async function resolveSession(sessionId, config) {
|
|
|
9739
9905
|
if (!found) {
|
|
9740
9906
|
throw new SessionNotFoundError(sessionId);
|
|
9741
9907
|
}
|
|
9742
|
-
const content = await
|
|
9908
|
+
const content = await readFile7(found.path, SESSION_FILE_ENCODING);
|
|
9743
9909
|
return { status: found.status, path: found.path, content };
|
|
9744
9910
|
}
|
|
9745
9911
|
async function showSingle(sessionId, config) {
|
|
@@ -10403,7 +10569,7 @@ var sessionDomain = {
|
|
|
10403
10569
|
};
|
|
10404
10570
|
|
|
10405
10571
|
// src/lib/spec-tree/index.ts
|
|
10406
|
-
import { readdir as readdir6, readFile as
|
|
10572
|
+
import { readdir as readdir6, readFile as readFile8 } from "fs/promises";
|
|
10407
10573
|
import { join as join14 } from "path";
|
|
10408
10574
|
var SPEC_TREE_FIELD_KEY = {
|
|
10409
10575
|
VERSION: "version",
|
|
@@ -10488,7 +10654,7 @@ function createFilesystemSpecTreeSource(options) {
|
|
|
10488
10654
|
if (ref.path === void 0) {
|
|
10489
10655
|
throw new Error("Filesystem source refs require a path");
|
|
10490
10656
|
}
|
|
10491
|
-
return
|
|
10657
|
+
return readFile8(join14(options.productDir, ref.path), SPEC_TREE_TEXT_ENCODING);
|
|
10492
10658
|
}
|
|
10493
10659
|
};
|
|
10494
10660
|
}
|
|
@@ -10952,7 +11118,7 @@ function formatNextNode(node) {
|
|
|
10952
11118
|
|
|
10953
11119
|
// src/commands/test/discovery.ts
|
|
10954
11120
|
import { readdir as readdir7 } from "fs/promises";
|
|
10955
|
-
import { join as join15, relative, sep } from "path";
|
|
11121
|
+
import { join as join15, relative as relative2, sep as sep2 } from "path";
|
|
10956
11122
|
var TESTS_DIRECTORY_NAME = SPEC_TREE_EVIDENCE_FILE.DIRECTORY_NAME;
|
|
10957
11123
|
var SPEC_ROOT_DIRECTORY = SPEC_TREE_CONFIG.ROOT_DIRECTORY;
|
|
10958
11124
|
var POSIX_SEPARATOR = "/";
|
|
@@ -10980,7 +11146,7 @@ async function collectTestFiles(directory, insideTestsDir, found) {
|
|
|
10980
11146
|
}
|
|
10981
11147
|
}
|
|
10982
11148
|
function toPosixRelative(productDir, absolute) {
|
|
10983
|
-
return
|
|
11149
|
+
return relative2(productDir, absolute).split(sep2).join(POSIX_SEPARATOR);
|
|
10984
11150
|
}
|
|
10985
11151
|
function compareAscii(left, right) {
|
|
10986
11152
|
if (left < right) return -1;
|
|
@@ -11511,9 +11677,15 @@ function toErrorMessage3(error) {
|
|
|
11511
11677
|
// src/lib/git/name-status.ts
|
|
11512
11678
|
var GIT_NAME_STATUS_FLAG = "--name-status";
|
|
11513
11679
|
var GIT_NULL_DELIMITED_FLAG = "-z";
|
|
11680
|
+
var GIT_DIFF_COMMAND = "diff";
|
|
11681
|
+
var GIT_RANGE_SEPARATOR = "..";
|
|
11682
|
+
function changesetNameStatusArgs(base, head) {
|
|
11683
|
+
return [GIT_DIFF_COMMAND, GIT_NAME_STATUS_FLAG, GIT_NULL_DELIMITED_FLAG, `${base}${GIT_RANGE_SEPARATOR}${head}`];
|
|
11684
|
+
}
|
|
11514
11685
|
var GIT_RENAME_STATUS_PREFIX = "R";
|
|
11515
11686
|
var GIT_COPY_STATUS_PREFIX = "C";
|
|
11516
|
-
var
|
|
11687
|
+
var GIT_NULL_RECORD_SEPARATOR = "\0";
|
|
11688
|
+
var NULL_RECORD_SEPARATOR = GIT_NULL_RECORD_SEPARATOR;
|
|
11517
11689
|
function sortedPathSet(paths) {
|
|
11518
11690
|
return [...paths].sort(compareAsciiStrings);
|
|
11519
11691
|
}
|
|
@@ -11569,7 +11741,7 @@ var CHANGED_TEST_DIFF_NAME_STATUS_FLAG = GIT_NAME_STATUS_FLAG;
|
|
|
11569
11741
|
var CHANGED_TEST_NULL_DELIMITED_FLAG = GIT_NULL_DELIMITED_FLAG;
|
|
11570
11742
|
var CHANGED_TEST_LS_FILES_OTHERS_FLAG = "--others";
|
|
11571
11743
|
var CHANGED_TEST_LS_FILES_EXCLUDE_STANDARD_FLAG = "--exclude-standard";
|
|
11572
|
-
var
|
|
11744
|
+
var CHANGED_TEST_LS_FILES_CACHED_FLAG = "--cached";
|
|
11573
11745
|
var HEAD_REF = "HEAD";
|
|
11574
11746
|
var ORIGIN_REMOTE = "origin";
|
|
11575
11747
|
var REF_SEPARATOR = "/";
|
|
@@ -11673,7 +11845,7 @@ async function stagedCandidateTestPaths(productDir, git) {
|
|
|
11673
11845
|
}
|
|
11674
11846
|
const result = await runner(
|
|
11675
11847
|
GIT_ROOT_COMMAND.EXECUTABLE,
|
|
11676
|
-
[CHANGED_TEST_LS_FILES_COMMAND,
|
|
11848
|
+
[CHANGED_TEST_LS_FILES_COMMAND, CHANGED_TEST_LS_FILES_CACHED_FLAG, CHANGED_TEST_NULL_DELIMITED_FLAG],
|
|
11677
11849
|
{ cwd: productDir, reject: false }
|
|
11678
11850
|
);
|
|
11679
11851
|
if (result.exitCode !== 0) {
|
|
@@ -11699,14 +11871,14 @@ async function readStagedFile(productDir, path7, git) {
|
|
|
11699
11871
|
}
|
|
11700
11872
|
return result.stdout;
|
|
11701
11873
|
}
|
|
11702
|
-
async function relatedTestPaths(sourceFiles, options, baseRef,
|
|
11874
|
+
async function relatedTestPaths(sourceFiles, options, baseRef, candidateTestPaths2, deps) {
|
|
11703
11875
|
const testPaths = [];
|
|
11704
11876
|
const resolved = /* @__PURE__ */ new Set();
|
|
11705
11877
|
for (const language of deps.registry.languages) {
|
|
11706
11878
|
if (language.relatedTestPaths === void 0) continue;
|
|
11707
11879
|
const relatedDeps = deps.relatedDepsFor(language.name);
|
|
11708
11880
|
const languageResolution = await language.relatedTestPaths(
|
|
11709
|
-
{ projectRoot: options.productDir, sourcePaths: sourceFiles, candidateTestPaths, baseRef },
|
|
11881
|
+
{ projectRoot: options.productDir, sourcePaths: sourceFiles, candidateTestPaths: candidateTestPaths2, baseRef },
|
|
11710
11882
|
options.staged === true ? { ...relatedDeps, readFile: (path7) => readStagedFile(options.productDir, path7, deps.git) } : relatedDeps
|
|
11711
11883
|
);
|
|
11712
11884
|
if (languageResolution.testPaths.length > 0) {
|
|
@@ -11726,6 +11898,9 @@ function changedTestProductInputPaths(registry) {
|
|
|
11726
11898
|
...registry.languages.flatMap((language) => language.productInputPaths)
|
|
11727
11899
|
].sort(compareAsciiStrings);
|
|
11728
11900
|
}
|
|
11901
|
+
async function candidateTestPaths(options, git) {
|
|
11902
|
+
return options.staged === true ? stagedCandidateTestPaths(options.productDir, git) : discoverTestFiles(options.productDir);
|
|
11903
|
+
}
|
|
11729
11904
|
async function planChangedTestSelection(options, deps) {
|
|
11730
11905
|
const baseRef = options.baseRef ?? await defaultBaseRef(options.productDir, deps.git);
|
|
11731
11906
|
const [baseSha, headSha] = await Promise.all([
|
|
@@ -11734,16 +11909,20 @@ async function planChangedTestSelection(options, deps) {
|
|
|
11734
11909
|
]);
|
|
11735
11910
|
const paths = await changedPaths(options.productDir, baseSha, options.staged === true, deps.git);
|
|
11736
11911
|
const partition = partitionChangedPaths(paths, changedTestProductInputPaths(deps.registry));
|
|
11737
|
-
|
|
11738
|
-
|
|
11739
|
-
|
|
11740
|
-
|
|
11741
|
-
const
|
|
11912
|
+
const testPaths = partition.sourceFiles.length > 0 || partition.operands.length > 0 ? await candidateTestPaths(options, deps.git) : [];
|
|
11913
|
+
const pathSelectedTests = partition.operands.length === 0 ? [] : resolveTargetedTestFiles(testPaths, { operands: partition.operands, recursive: true }).selected;
|
|
11914
|
+
const related = partition.sourceFiles.length === 0 ? { testPaths: [], unresolved: [] } : await relatedTestPaths(partition.sourceFiles, options, baseRef, testPaths, deps);
|
|
11915
|
+
const dispatchOperands = mergeChangedSetOperands(pathSelectedTests, related.testPaths);
|
|
11916
|
+
const dirtyOperands = partition.operands.length === 0 ? dispatchOperands : mergeChangedSetOperands(partition.operands, related.testPaths);
|
|
11742
11917
|
return {
|
|
11743
11918
|
targets: {
|
|
11744
|
-
operands: partition.productInputChanged ? [SPEC_ROOT_OPERAND] :
|
|
11919
|
+
operands: partition.productInputChanged ? [SPEC_ROOT_OPERAND] : dispatchOperands,
|
|
11745
11920
|
recursive: partition.productInputChanged
|
|
11746
11921
|
},
|
|
11922
|
+
dirtyTargets: {
|
|
11923
|
+
operands: partition.productInputChanged ? [SPEC_ROOT_OPERAND] : dirtyOperands,
|
|
11924
|
+
recursive: partition.productInputChanged || partition.operands.length > 0
|
|
11925
|
+
},
|
|
11747
11926
|
baseRef,
|
|
11748
11927
|
baseSha,
|
|
11749
11928
|
headSha,
|
|
@@ -12070,11 +12249,12 @@ async function runTestsCommand(options, deps) {
|
|
|
12070
12249
|
throw new Error(CHANGED_TEST_STAGED_SELECTION_MISSING_ERROR);
|
|
12071
12250
|
}
|
|
12072
12251
|
selectedTargets = mergeTargetSelections(options.targets, changedSelection2.targets) ?? changedSelection2.targets;
|
|
12252
|
+
const dirtyTargets = mergeTargetSelections(options.targets, changedSelection2.dirtyTargets) ?? changedSelection2.dirtyTargets;
|
|
12073
12253
|
await requireWorktreeMatchesIndexForStagedRun(
|
|
12074
12254
|
options.productDir,
|
|
12075
12255
|
changedGit,
|
|
12076
12256
|
changedSelection2,
|
|
12077
|
-
|
|
12257
|
+
dirtyTargets,
|
|
12078
12258
|
passingScope
|
|
12079
12259
|
);
|
|
12080
12260
|
}
|
|
@@ -13095,7 +13275,7 @@ var testingRegistry = {
|
|
|
13095
13275
|
|
|
13096
13276
|
// src/interfaces/cli/test-runner-deps.ts
|
|
13097
13277
|
import { createWriteStream } from "fs";
|
|
13098
|
-
import { mkdtemp, readFile as
|
|
13278
|
+
import { mkdtemp, readFile as readFile9 } from "fs/promises";
|
|
13099
13279
|
import { tmpdir } from "os";
|
|
13100
13280
|
import { join as join23 } from "path";
|
|
13101
13281
|
import { finished } from "stream/promises";
|
|
@@ -13145,7 +13325,7 @@ function createRunnerDepsFor(productDir, outStream = process.stdout) {
|
|
|
13145
13325
|
}
|
|
13146
13326
|
function createRelatedDepsFor(productDir) {
|
|
13147
13327
|
const runCommand = createRelatedCommandRunner(productDir);
|
|
13148
|
-
return () => ({ runCommand, readFile: (path7) =>
|
|
13328
|
+
return () => ({ runCommand, readFile: (path7) => readFile9(join23(productDir, path7), "utf8") });
|
|
13149
13329
|
}
|
|
13150
13330
|
function artifactFileName(index, suffix) {
|
|
13151
13331
|
return `${index.toString(ARTIFACT_INDEX_RADIX).padStart(ARTIFACT_INDEX_WIDTH, "0")}-${suffix}`;
|
|
@@ -13606,14 +13786,14 @@ var testingDomain = createTestingDomain();
|
|
|
13606
13786
|
|
|
13607
13787
|
// src/interfaces/cli/validation.ts
|
|
13608
13788
|
import { existsSync as existsSync9, realpathSync } from "fs";
|
|
13609
|
-
import { isAbsolute as
|
|
13789
|
+
import { isAbsolute as isAbsolute11, relative as relative9, resolve as resolve11, sep as sep4 } from "path";
|
|
13610
13790
|
|
|
13611
13791
|
// src/commands/validation/formatting.ts
|
|
13612
13792
|
import { existsSync, statSync } from "fs";
|
|
13613
|
-
import { isAbsolute as
|
|
13793
|
+
import { isAbsolute as isAbsolute4, join as join24, relative as relative4 } from "path";
|
|
13614
13794
|
|
|
13615
13795
|
// src/validation/config/path-filter.ts
|
|
13616
|
-
import { isAbsolute as
|
|
13796
|
+
import { isAbsolute as isAbsolute3, relative as relative3 } from "path";
|
|
13617
13797
|
function hasEffectiveValidationPathMetadata(filter) {
|
|
13618
13798
|
return "hasIncludeFilter" in filter && typeof filter.hasIncludeFilter === "boolean" && "noMatchingIncludes" in filter && typeof filter.noMatchingIncludes === "boolean";
|
|
13619
13799
|
}
|
|
@@ -13624,7 +13804,7 @@ function nonEmpty(values) {
|
|
|
13624
13804
|
return values?.filter((value) => value.length > 0) ?? [];
|
|
13625
13805
|
}
|
|
13626
13806
|
function toProjectRelativeValidationPath(projectRoot, path7) {
|
|
13627
|
-
return
|
|
13807
|
+
return isAbsolute3(path7) ? relative3(projectRoot, path7) : path7;
|
|
13628
13808
|
}
|
|
13629
13809
|
function intersectIncludes(baseInclude, toolInclude) {
|
|
13630
13810
|
const base = nonEmpty(baseInclude);
|
|
@@ -13882,12 +14062,12 @@ async function formattingCommand(options) {
|
|
|
13882
14062
|
const scopedFiles = hasExplicitScope ? files.flatMap(
|
|
13883
14063
|
(filePath) => formattingPathOperandsForValidationPathFilter(
|
|
13884
14064
|
cwd,
|
|
13885
|
-
|
|
14065
|
+
isAbsolute4(filePath) ? relative4(cwd, filePath) : filePath,
|
|
13886
14066
|
pathFilter
|
|
13887
14067
|
)
|
|
13888
14068
|
) : void 0;
|
|
13889
14069
|
const scopedExcludes = hasExplicitScope && files.some(
|
|
13890
|
-
(filePath) => isFormattingFileOperand(cwd,
|
|
14070
|
+
(filePath) => isFormattingFileOperand(cwd, isAbsolute4(filePath) ? relative4(cwd, filePath) : filePath)
|
|
13891
14071
|
) ? [] : validationPathFilterExcludes(pathFilter);
|
|
13892
14072
|
if (hasExplicitScope && (scopedFiles === void 0 || scopedFiles.length === 0)) {
|
|
13893
14073
|
const output2 = quiet ? "" : `${VALIDATION_STAGE_DISPLAY_NAMES.FORMATTING}: skipped (${FORMATTING_COMMAND_OUTPUT.EMPTY_SCOPE_REASON})`;
|
|
@@ -13941,7 +14121,7 @@ var formattingValidationLanguage = {
|
|
|
13941
14121
|
};
|
|
13942
14122
|
|
|
13943
14123
|
// src/commands/validation/markdown.ts
|
|
13944
|
-
import { isAbsolute as
|
|
14124
|
+
import { isAbsolute as isAbsolute5, join as join26 } from "path";
|
|
13945
14125
|
|
|
13946
14126
|
// src/validation/steps/markdown.ts
|
|
13947
14127
|
import { existsSync as existsSync2, statSync as statSync2 } from "fs";
|
|
@@ -14247,7 +14427,7 @@ async function markdownCommand(options) {
|
|
|
14247
14427
|
return formatMarkdownResult(result, skippedOutput, quiet, durationMs);
|
|
14248
14428
|
}
|
|
14249
14429
|
function markdownValidationOperandPath(productDir, filePath) {
|
|
14250
|
-
return
|
|
14430
|
+
return isAbsolute5(filePath) ? filePath : join26(productDir, filePath);
|
|
14251
14431
|
}
|
|
14252
14432
|
function defaultMarkdownTargets(productDir, pathFilter) {
|
|
14253
14433
|
return getDefaultDirectories(productDir).flatMap(
|
|
@@ -15147,7 +15327,7 @@ var ParseErrorCode;
|
|
|
15147
15327
|
|
|
15148
15328
|
// src/validation/config/scope.ts
|
|
15149
15329
|
import { existsSync as existsSync3, readdirSync, readFileSync as readFileSync3 } from "fs";
|
|
15150
|
-
import { isAbsolute as
|
|
15330
|
+
import { isAbsolute as isAbsolute6, join as join27, relative as relative5, resolve as resolve9 } from "path";
|
|
15151
15331
|
var TSCONFIG_FILES = {
|
|
15152
15332
|
full: "tsconfig.json",
|
|
15153
15333
|
production: "tsconfig.production.json"
|
|
@@ -15184,7 +15364,7 @@ var EXPLICIT_TYPESCRIPT_SCOPE_TARGET_KIND = {
|
|
|
15184
15364
|
FILE: "file"
|
|
15185
15365
|
};
|
|
15186
15366
|
function resolveProjectPath(projectRoot, path7) {
|
|
15187
|
-
return
|
|
15367
|
+
return isAbsolute6(path7) ? path7 : join27(projectRoot, path7);
|
|
15188
15368
|
}
|
|
15189
15369
|
function parseTypeScriptConfig(configPath, deps = defaultScopeDeps) {
|
|
15190
15370
|
try {
|
|
@@ -15521,14 +15701,14 @@ function pathPassesTypeScriptScope(path7, scopeConfig) {
|
|
|
15521
15701
|
return included && !excluded;
|
|
15522
15702
|
}
|
|
15523
15703
|
function pathStaysInsideTypeScriptScopeRoot(projectRoot, path7) {
|
|
15524
|
-
const resolvedPath =
|
|
15525
|
-
const relativePath =
|
|
15704
|
+
const resolvedPath = isAbsolute6(path7) ? resolve9(path7) : resolve9(projectRoot, path7);
|
|
15705
|
+
const relativePath = relative5(projectRoot, resolvedPath);
|
|
15526
15706
|
const segments = normalizeTypeScriptScopePath(relativePath).split(PATH_SEGMENT_SEPARATOR4);
|
|
15527
|
-
return relativePath.length === 0 || !segments.includes("..") && !
|
|
15707
|
+
return relativePath.length === 0 || !segments.includes("..") && !isAbsolute6(relativePath);
|
|
15528
15708
|
}
|
|
15529
15709
|
function toProjectRelativeTypeScriptScopePath(projectRoot, path7) {
|
|
15530
|
-
const resolvedPath =
|
|
15531
|
-
const relativePath =
|
|
15710
|
+
const resolvedPath = isAbsolute6(path7) ? resolve9(path7) : resolve9(projectRoot, path7);
|
|
15711
|
+
const relativePath = relative5(projectRoot, resolvedPath);
|
|
15532
15712
|
return relativePath.length === 0 ? TYPESCRIPT_SCOPE_PROJECT_ROOT : normalizeTypeScriptScopePath(relativePath);
|
|
15533
15713
|
}
|
|
15534
15714
|
function toExplicitTypeScriptScopeTarget(projectRoot, originalPath, deps = defaultScopeDeps) {
|
|
@@ -16227,7 +16407,7 @@ async function circularCommand(options, deps = defaultCircularCommandDeps) {
|
|
|
16227
16407
|
// src/validation/steps/knip.ts
|
|
16228
16408
|
import { existsSync as existsSync4 } from "fs";
|
|
16229
16409
|
import { mkdir as mkdir5, mkdtemp as mkdtemp2, rm as rm2, writeFile as writeFile3 } from "fs/promises";
|
|
16230
|
-
import { isAbsolute as
|
|
16410
|
+
import { isAbsolute as isAbsolute7, join as join29 } from "path";
|
|
16231
16411
|
var defaultKnipProcessRunner = lifecycleProcessRunner;
|
|
16232
16412
|
var KNIP_COMMAND_TOKENS = {
|
|
16233
16413
|
COMMAND: "knip",
|
|
@@ -16319,7 +16499,7 @@ async function createScopedKnipTsconfig(projectRoot, typescriptScope, deps) {
|
|
|
16319
16499
|
await deps.mkdir(tempParentDir, { recursive: true });
|
|
16320
16500
|
const tempDir = await deps.mkdtemp(join29(tempParentDir, "validate-knip-"));
|
|
16321
16501
|
const configPath = join29(tempDir, TSCONFIG_FILES.full);
|
|
16322
|
-
const toProjectPathPattern = (pattern) =>
|
|
16502
|
+
const toProjectPathPattern = (pattern) => isAbsolute7(pattern) ? pattern : join29(projectRoot, pattern);
|
|
16323
16503
|
const project = [
|
|
16324
16504
|
...typescriptScope.directories.flatMap(
|
|
16325
16505
|
(directory) => TYPESCRIPT_FALLBACK_INCLUDE_PATTERNS.map((pattern) => `${directory}/${pattern}`)
|
|
@@ -16845,17 +17025,17 @@ function formatLintResult(result, quiet, durationMs) {
|
|
|
16845
17025
|
}
|
|
16846
17026
|
|
|
16847
17027
|
// src/validation/literal/index.ts
|
|
16848
|
-
import { readFile as
|
|
16849
|
-
import { isAbsolute as
|
|
17028
|
+
import { readFile as readFile11 } from "fs/promises";
|
|
17029
|
+
import { isAbsolute as isAbsolute9, relative as relative7, resolve as resolve10 } from "path";
|
|
16850
17030
|
|
|
16851
17031
|
// src/lib/file-inclusion/pipeline.ts
|
|
16852
|
-
import { readdir as readdir9, readFile as
|
|
16853
|
-
import { join as join33, relative as
|
|
17032
|
+
import { readdir as readdir9, readFile as readFile10, stat as stat6 } from "fs/promises";
|
|
17033
|
+
import { join as join33, relative as relative6, sep as sep3 } from "path";
|
|
16854
17034
|
|
|
16855
17035
|
// src/lib/file-inclusion/ignore-source.ts
|
|
16856
17036
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
16857
17037
|
import { existsSync as existsSync7 } from "fs";
|
|
16858
|
-
import { isAbsolute as
|
|
17038
|
+
import { isAbsolute as isAbsolute8, join as join32 } from "path";
|
|
16859
17039
|
var GIT_EXECUTABLE2 = "git";
|
|
16860
17040
|
var GIT_LS_FILES_ARGS = {
|
|
16861
17041
|
LS_FILES: "ls-files",
|
|
@@ -16973,7 +17153,7 @@ function optionalExcludeFromArgs(path7) {
|
|
|
16973
17153
|
return existsSync7(path7) ? excludeFromArgs(path7) : [];
|
|
16974
17154
|
}
|
|
16975
17155
|
function resolveGitPath(productDir, path7) {
|
|
16976
|
-
return
|
|
17156
|
+
return isAbsolute8(path7) ? path7 : join32(productDir, path7);
|
|
16977
17157
|
}
|
|
16978
17158
|
function readInfoExcludePath(productDir) {
|
|
16979
17159
|
const commonDir = readOptionalGit(productDir, [
|
|
@@ -17165,7 +17345,7 @@ async function isDirectory(absolutePath) {
|
|
|
17165
17345
|
}
|
|
17166
17346
|
async function isGitdirPointerFile(absolutePath) {
|
|
17167
17347
|
try {
|
|
17168
|
-
const content = await
|
|
17348
|
+
const content = await readFile10(absolutePath, "utf8");
|
|
17169
17349
|
return content.startsWith(GITDIR_POINTER_PREFIX);
|
|
17170
17350
|
} catch (err) {
|
|
17171
17351
|
if (isNodeError4(err) && (err.code === "ENOENT" || err.code === "ENOTDIR")) {
|
|
@@ -17185,8 +17365,8 @@ async function readDirectoryEntries2(absoluteDir) {
|
|
|
17185
17365
|
}
|
|
17186
17366
|
}
|
|
17187
17367
|
function normalizeProductPath(productDir, absolutePath) {
|
|
17188
|
-
const rel =
|
|
17189
|
-
return
|
|
17368
|
+
const rel = relative6(productDir, absolutePath);
|
|
17369
|
+
return sep3 === "/" ? rel : rel.split(sep3).join("/");
|
|
17190
17370
|
}
|
|
17191
17371
|
async function isGitMetadataEntry(absolutePath, entry) {
|
|
17192
17372
|
if (entry.name !== GIT_INTERNAL_DIRECTORY) return false;
|
|
@@ -17734,8 +17914,8 @@ var DEFAULT_LITERAL_COLLECT_OPTIONS = {
|
|
|
17734
17914
|
async function validateLiteralReuse(input) {
|
|
17735
17915
|
const config = input.config ?? literalConfigDescriptor.defaults;
|
|
17736
17916
|
const explicitPaths = input.explicitFiles?.map((f) => {
|
|
17737
|
-
const abs =
|
|
17738
|
-
return
|
|
17917
|
+
const abs = isAbsolute9(f) ? f : resolve10(input.productDir, f);
|
|
17918
|
+
return relative7(input.productDir, abs).split(/[\\/]/g).join("/");
|
|
17739
17919
|
});
|
|
17740
17920
|
if (input.pathConfig !== void 0 && (explicitPaths === void 0 || explicitPaths.length === 0) && validationPathFilterHasNoMatchingIncludes(input.pathConfig)) {
|
|
17741
17921
|
return {
|
|
@@ -17764,7 +17944,7 @@ async function validateLiteralReuse(input) {
|
|
|
17764
17944
|
const testOccurrencesByFile = /* @__PURE__ */ new Map();
|
|
17765
17945
|
const indexedOccurrencesByFile = /* @__PURE__ */ new Map();
|
|
17766
17946
|
for (const abs of candidateFiles) {
|
|
17767
|
-
const rel =
|
|
17947
|
+
const rel = relative7(input.productDir, abs).split(/[\\/]/g).join("/");
|
|
17768
17948
|
const content = await readSafe(abs);
|
|
17769
17949
|
if (content === null) continue;
|
|
17770
17950
|
const occurrences = collectLiterals(content, rel, collectOptions);
|
|
@@ -17789,7 +17969,7 @@ async function validateLiteralReuse(input) {
|
|
|
17789
17969
|
}
|
|
17790
17970
|
async function readSafe(path7) {
|
|
17791
17971
|
try {
|
|
17792
|
-
return await
|
|
17972
|
+
return await readFile11(path7, "utf8");
|
|
17793
17973
|
} catch (err) {
|
|
17794
17974
|
if (typeof err === "object" && err !== null && "code" in err) {
|
|
17795
17975
|
const code = err.code;
|
|
@@ -18064,7 +18244,7 @@ function formatLoc(loc) {
|
|
|
18064
18244
|
// src/validation/steps/typescript.ts
|
|
18065
18245
|
import { existsSync as existsSync8, mkdirSync, rmSync, writeFileSync } from "fs";
|
|
18066
18246
|
import { mkdtemp as mkdtemp3 } from "fs/promises";
|
|
18067
|
-
import { isAbsolute as
|
|
18247
|
+
import { isAbsolute as isAbsolute10, join as join34, relative as relative8 } from "path";
|
|
18068
18248
|
var defaultTypeScriptProcessRunner = lifecycleProcessRunner;
|
|
18069
18249
|
var defaultTypeScriptDeps = {
|
|
18070
18250
|
mkdtemp: mkdtemp3,
|
|
@@ -18091,7 +18271,7 @@ async function createFileSpecificTsconfig(scope2, files, projectRoot, deps = def
|
|
|
18091
18271
|
const tempDir = await createTemporaryTsconfigDir(projectRoot, deps);
|
|
18092
18272
|
const configPath = join34(tempDir, "tsconfig.json");
|
|
18093
18273
|
const baseConfigFile = TSCONFIG_FILES[scope2];
|
|
18094
|
-
const absoluteFiles = files.map((file) =>
|
|
18274
|
+
const absoluteFiles = files.map((file) => isAbsolute10(file) ? file : join34(projectRoot, file));
|
|
18095
18275
|
const tempConfig = {
|
|
18096
18276
|
extends: join34(projectRoot, baseConfigFile),
|
|
18097
18277
|
files: absoluteFiles,
|
|
@@ -18108,8 +18288,8 @@ async function createScopeFilteredTsconfig(scope2, projectRoot, scopeConfig, dep
|
|
|
18108
18288
|
const configPath = join34(tempDir, "tsconfig.json");
|
|
18109
18289
|
const baseConfigFile = TSCONFIG_FILES[scope2];
|
|
18110
18290
|
const toTemporaryConfigPathPattern = (pattern) => {
|
|
18111
|
-
const absolutePattern =
|
|
18112
|
-
return
|
|
18291
|
+
const absolutePattern = isAbsolute10(pattern) ? pattern : join34(projectRoot, pattern);
|
|
18292
|
+
return relative8(tempDir, absolutePattern);
|
|
18113
18293
|
};
|
|
18114
18294
|
const tempConfig = {
|
|
18115
18295
|
extends: join34(projectRoot, baseConfigFile),
|
|
@@ -18651,8 +18831,8 @@ function normalizeProductPathOperand(productDir, effectiveInvocationDir, operand
|
|
|
18651
18831
|
const resolvedProductDir = canonicalExistingPath(resolve11(productDir));
|
|
18652
18832
|
const resolvedInvocationDir = canonicalExistingPath(resolve11(effectiveInvocationDir));
|
|
18653
18833
|
const absoluteOperand = canonicalExistingPath(resolve11(resolvedInvocationDir, operand));
|
|
18654
|
-
const relativeOperand =
|
|
18655
|
-
if (relativeOperand === ".." || relativeOperand.startsWith(`..${
|
|
18834
|
+
const relativeOperand = relative9(resolvedProductDir, absoluteOperand);
|
|
18835
|
+
if (relativeOperand === ".." || relativeOperand.startsWith(`..${sep4}`) || isAbsolute11(relativeOperand)) {
|
|
18656
18836
|
return void 0;
|
|
18657
18837
|
}
|
|
18658
18838
|
return relativeOperand.length > 0 ? relativeOperand.replaceAll("\\", "/") : ".";
|
|
@@ -18856,7 +19036,7 @@ var validationDomain = {
|
|
|
18856
19036
|
};
|
|
18857
19037
|
|
|
18858
19038
|
// src/commands/verification-context/cli.ts
|
|
18859
|
-
import { isAbsolute as
|
|
19039
|
+
import { isAbsolute as isAbsolute12, win32 } from "path";
|
|
18860
19040
|
|
|
18861
19041
|
// src/domains/verification-context/context.ts
|
|
18862
19042
|
var VERIFICATION_CONTEXT_SCHEMA_VERSION = "verification-context.v1";
|
|
@@ -19007,7 +19187,7 @@ function normalizeFileSubjectPath(path7) {
|
|
|
19007
19187
|
VERIFICATION_CONTEXT_FILE_SUBJECT_PATH.SEPARATOR.CANONICAL
|
|
19008
19188
|
);
|
|
19009
19189
|
const segments = normalized.split(VERIFICATION_CONTEXT_FILE_SUBJECT_PATH.SEPARATOR.CANONICAL);
|
|
19010
|
-
if (
|
|
19190
|
+
if (isAbsolute12(path7) || win32.isAbsolute(path7) || windowsRoot.length > 0 || segments.includes(VERIFICATION_CONTEXT_FILE_SUBJECT_PATH.PARENT_DIRECTORY.SEGMENT)) {
|
|
19011
19191
|
return void 0;
|
|
19012
19192
|
}
|
|
19013
19193
|
return normalized;
|
|
@@ -19057,17 +19237,6 @@ async function verificationContextCreateCommand(options, deps = {}) {
|
|
|
19057
19237
|
return okResult2(JSON.stringify(persisted.value));
|
|
19058
19238
|
}
|
|
19059
19239
|
|
|
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
19240
|
// src/interfaces/cli/verification-context.ts
|
|
19072
19241
|
var VERIFICATION_CONTEXT_CLI = {
|
|
19073
19242
|
commandName: "verification-context",
|
|
@@ -19094,6 +19263,514 @@ var verificationContextDomain = {
|
|
|
19094
19263
|
}
|
|
19095
19264
|
};
|
|
19096
19265
|
|
|
19266
|
+
// src/interfaces/cli/verify.ts
|
|
19267
|
+
import { readFile as readFile12 } from "fs/promises";
|
|
19268
|
+
|
|
19269
|
+
// src/commands/verify/cli.ts
|
|
19270
|
+
import { dirname as dirname12 } from "path";
|
|
19271
|
+
|
|
19272
|
+
// src/domains/verify/verify.ts
|
|
19273
|
+
import { join as join36 } from "path";
|
|
19274
|
+
var VERIFY_SCOPE_TYPE = {
|
|
19275
|
+
CHANGESET: "changeset",
|
|
19276
|
+
WORKING_TREE: "working-tree"
|
|
19277
|
+
};
|
|
19278
|
+
var VERIFY_VERB = {
|
|
19279
|
+
START: "start",
|
|
19280
|
+
INPUT: "input",
|
|
19281
|
+
APPEND_SCOPE: "append-scope",
|
|
19282
|
+
APPEND_FINDING: "append-finding"
|
|
19283
|
+
};
|
|
19284
|
+
var VERIFY_VERIFICATION_TYPE = {
|
|
19285
|
+
REVIEW: "review"
|
|
19286
|
+
};
|
|
19287
|
+
var REVIEW_FINDING_DISPOSITION = {
|
|
19288
|
+
BLOCKING: "BLOCKING",
|
|
19289
|
+
DEBT: "DEBT"
|
|
19290
|
+
};
|
|
19291
|
+
var VERIFY_APPEND_EVENT_TYPE = {
|
|
19292
|
+
SCOPE: "io.spx.verify.scope",
|
|
19293
|
+
FINDING: "io.spx.verify.finding"
|
|
19294
|
+
};
|
|
19295
|
+
var VERIFY_EVENT_SOURCE = "/spx/verify";
|
|
19296
|
+
var VERIFY_APPEND_EVENT_FIELD = {
|
|
19297
|
+
IDEMPOTENCY_KEY: "idempotencyKey",
|
|
19298
|
+
PAYLOAD: "payload"
|
|
19299
|
+
};
|
|
19300
|
+
var VERIFY_INPUT_SOURCE = {
|
|
19301
|
+
STDIN: "stdin"
|
|
19302
|
+
};
|
|
19303
|
+
var VERIFY_SCOPE_SEPARATOR = "..";
|
|
19304
|
+
var VERIFY_SCOPE_ERROR = {
|
|
19305
|
+
MALFORMED_CHANGESET: "verify changeset scope must be <base>..<head>",
|
|
19306
|
+
UNSUPPORTED_SCOPE_TYPE: "verify scope type has no verification-context substrate representation"
|
|
19307
|
+
};
|
|
19308
|
+
var VERIFY_INPUT_DIGEST_PATH = "verify run input";
|
|
19309
|
+
function parseChangesetScope(scope2) {
|
|
19310
|
+
const separatorIndex = scope2.indexOf(VERIFY_SCOPE_SEPARATOR);
|
|
19311
|
+
if (separatorIndex < 0) return { ok: false, error: VERIFY_SCOPE_ERROR.MALFORMED_CHANGESET };
|
|
19312
|
+
const base = scope2.slice(0, separatorIndex);
|
|
19313
|
+
const head = scope2.slice(separatorIndex + VERIFY_SCOPE_SEPARATOR.length);
|
|
19314
|
+
if (base.length === 0 || head.length === 0 || head.includes(VERIFY_SCOPE_SEPARATOR)) {
|
|
19315
|
+
return { ok: false, error: VERIFY_SCOPE_ERROR.MALFORMED_CHANGESET };
|
|
19316
|
+
}
|
|
19317
|
+
return { ok: true, value: { base, head } };
|
|
19318
|
+
}
|
|
19319
|
+
function buildRunLocator(parts) {
|
|
19320
|
+
return {
|
|
19321
|
+
runToken: parts.runToken,
|
|
19322
|
+
verificationType: parts.verificationType,
|
|
19323
|
+
scopeType: parts.scopeType,
|
|
19324
|
+
scopeIdentity: parts.scopeIdentity,
|
|
19325
|
+
backendIdentity: parts.backendIdentity,
|
|
19326
|
+
storageNamespace: parts.storageNamespace,
|
|
19327
|
+
runTarget: parts.runTarget
|
|
19328
|
+
};
|
|
19329
|
+
}
|
|
19330
|
+
function digestRunInput(source, content) {
|
|
19331
|
+
const digest = digestDescriptorSection({ source, content }, VERIFY_INPUT_DIGEST_PATH);
|
|
19332
|
+
if (!digest.ok) return digest;
|
|
19333
|
+
return { ok: true, value: digest.value.sha256 };
|
|
19334
|
+
}
|
|
19335
|
+
var VERIFY_INPUT_RECORD = {
|
|
19336
|
+
PREFIX: "input-",
|
|
19337
|
+
SUFFIX: ".json"
|
|
19338
|
+
};
|
|
19339
|
+
function verifyRunsDir(scope2) {
|
|
19340
|
+
const branchScope = branchScopeDir(scope2.productDir, scope2.branchSlug);
|
|
19341
|
+
if (!branchScope.ok) return branchScope;
|
|
19342
|
+
return runsDir(branchScope.value, scope2.type);
|
|
19343
|
+
}
|
|
19344
|
+
function verifyInputRecordPath(scope2) {
|
|
19345
|
+
const runs = verifyRunsDir(scope2);
|
|
19346
|
+
if (!runs.ok) return runs;
|
|
19347
|
+
const token = validateScopeToken(scope2.runToken);
|
|
19348
|
+
if (!token.ok) return token;
|
|
19349
|
+
return {
|
|
19350
|
+
ok: true,
|
|
19351
|
+
value: join36(runs.value, `${VERIFY_INPUT_RECORD.PREFIX}${token.value}${VERIFY_INPUT_RECORD.SUFFIX}`)
|
|
19352
|
+
};
|
|
19353
|
+
}
|
|
19354
|
+
var VERIFY_APPEND_ATTEMPT = 1;
|
|
19355
|
+
function parseAppendPayload(raw) {
|
|
19356
|
+
try {
|
|
19357
|
+
return JSON.parse(raw);
|
|
19358
|
+
} catch {
|
|
19359
|
+
return void 0;
|
|
19360
|
+
}
|
|
19361
|
+
}
|
|
19362
|
+
function isJsonRecord(value) {
|
|
19363
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
19364
|
+
}
|
|
19365
|
+
function isReviewFindingDisposition(value) {
|
|
19366
|
+
return Object.values(REVIEW_FINDING_DISPOSITION).includes(value);
|
|
19367
|
+
}
|
|
19368
|
+
function validateReviewFinding(payload) {
|
|
19369
|
+
if (!isJsonRecord(payload)) return void 0;
|
|
19370
|
+
const { disposition, summary } = payload;
|
|
19371
|
+
if (!isReviewFindingDisposition(disposition)) return void 0;
|
|
19372
|
+
if (typeof summary !== "string" || summary.length === 0) return void 0;
|
|
19373
|
+
return { disposition, summary };
|
|
19374
|
+
}
|
|
19375
|
+
var FINDING_VALIDATORS = {
|
|
19376
|
+
[VERIFY_VERIFICATION_TYPE.REVIEW]: validateReviewFinding
|
|
19377
|
+
};
|
|
19378
|
+
function findingValidatorFor(verificationType) {
|
|
19379
|
+
return FINDING_VALIDATORS[verificationType];
|
|
19380
|
+
}
|
|
19381
|
+
function findAppendedSequence(events, idempotencyKey, eventType) {
|
|
19382
|
+
const match = events.find(
|
|
19383
|
+
(event) => event.type === eventType && isJsonRecord(event.data) && event.data[VERIFY_APPEND_EVENT_FIELD.IDEMPOTENCY_KEY] === idempotencyKey
|
|
19384
|
+
);
|
|
19385
|
+
return match?.seq;
|
|
19386
|
+
}
|
|
19387
|
+
function buildAppendEvent(args) {
|
|
19388
|
+
return {
|
|
19389
|
+
id: args.idempotencyKey,
|
|
19390
|
+
source: VERIFY_EVENT_SOURCE,
|
|
19391
|
+
type: args.eventType,
|
|
19392
|
+
time: args.at.toISOString(),
|
|
19393
|
+
attempt: VERIFY_APPEND_ATTEMPT,
|
|
19394
|
+
data: {
|
|
19395
|
+
[VERIFY_APPEND_EVENT_FIELD.IDEMPOTENCY_KEY]: args.idempotencyKey,
|
|
19396
|
+
[VERIFY_APPEND_EVENT_FIELD.PAYLOAD]: args.payload
|
|
19397
|
+
}
|
|
19398
|
+
};
|
|
19399
|
+
}
|
|
19400
|
+
|
|
19401
|
+
// src/commands/verify/cli.ts
|
|
19402
|
+
var VERIFY_CLI_EXIT_CODE = {
|
|
19403
|
+
OK: 0,
|
|
19404
|
+
ERROR: 1
|
|
19405
|
+
};
|
|
19406
|
+
var VERIFY_CLI_ENV = {
|
|
19407
|
+
BRANCH: SPX_VERIFY_ENV.BRANCH
|
|
19408
|
+
};
|
|
19409
|
+
var VERIFY_CLI_ERROR = {
|
|
19410
|
+
INPUT_REQUIRED: "spx verify start requires --input <input-source>",
|
|
19411
|
+
RUN_REQUIRED: "spx verify existing-run verbs require an explicit --run <run-token>",
|
|
19412
|
+
RUN_NOT_FOUND: "spx verify could not locate the requested run",
|
|
19413
|
+
CHANGED_SCOPE_FAILED: "spx verify could not derive the changeset changed-file scope",
|
|
19414
|
+
INPUT_PERSIST_FAILED: "spx verify could not persist the recorded run input",
|
|
19415
|
+
INPUT_READ_FAILED: "spx verify could not read the recorded run input",
|
|
19416
|
+
PAYLOAD_REQUIRED: "spx verify append verbs require --payload <payload-source>",
|
|
19417
|
+
IDEMPOTENCY_KEY_REQUIRED: "spx verify append verbs require --idempotency-key <key>",
|
|
19418
|
+
PAYLOAD_READ_FAILED: "spx verify could not read the append payload",
|
|
19419
|
+
PAYLOAD_INVALID: "spx verify append payload is not valid JSON",
|
|
19420
|
+
FINDING_INVALID: "spx verify append-finding payload failed verification-type validation",
|
|
19421
|
+
UNSUPPORTED_VERIFICATION_TYPE: "spx verify append-finding has no finding validator for the verification type",
|
|
19422
|
+
APPEND_FAILED: "spx verify could not append the evidence event"
|
|
19423
|
+
};
|
|
19424
|
+
function okResult3(output) {
|
|
19425
|
+
return { exitCode: VERIFY_CLI_EXIT_CODE.OK, output };
|
|
19426
|
+
}
|
|
19427
|
+
function errorResult3(error) {
|
|
19428
|
+
return { exitCode: VERIFY_CLI_EXIT_CODE.ERROR, output: error };
|
|
19429
|
+
}
|
|
19430
|
+
function toMessage2(error) {
|
|
19431
|
+
return error instanceof Error ? error.message : JSON.stringify(error);
|
|
19432
|
+
}
|
|
19433
|
+
function forwardDeps(deps) {
|
|
19434
|
+
return {
|
|
19435
|
+
...deps.cwd === void 0 ? {} : { cwd: deps.cwd },
|
|
19436
|
+
...deps.git === void 0 ? {} : { git: deps.git },
|
|
19437
|
+
...deps.branch === void 0 ? {} : { branch: deps.branch },
|
|
19438
|
+
...deps.processEnv === void 0 ? {} : { processEnv: deps.processEnv },
|
|
19439
|
+
...deps.fs === void 0 ? {} : { fs: deps.fs },
|
|
19440
|
+
...deps.now === void 0 ? {} : { now: deps.now }
|
|
19441
|
+
};
|
|
19442
|
+
}
|
|
19443
|
+
async function resolveVerifyScope(deps) {
|
|
19444
|
+
const cwd = deps.cwd ?? CONFIG_PROCESS_CWD.read();
|
|
19445
|
+
const git = deps.git ?? defaultGitDependencies;
|
|
19446
|
+
const processEnv = deps.processEnv ?? process.env;
|
|
19447
|
+
const product = await detectGitCommonDirProductRoot(cwd, git);
|
|
19448
|
+
const probedBranch = product.isGitRepo ? await getCurrentBranch(cwd, git) ?? void 0 : void 0;
|
|
19449
|
+
const headSha = (product.isGitRepo ? await getHeadSha(cwd, git) : null) ?? SPX_VERIFY_HEAD_SHA.MISSING;
|
|
19450
|
+
const branchName = deps.branch ?? processEnv[VERIFY_CLI_ENV.BRANCH] ?? probedBranch;
|
|
19451
|
+
const branchIdentity = resolveBranchIdentity({ ...branchName === void 0 ? {} : { branchName }, headSha });
|
|
19452
|
+
const backend = resolveJournalBackend(readJournalCliEnvironment(processEnv).backend);
|
|
19453
|
+
if (!backend.ok) return backend;
|
|
19454
|
+
return {
|
|
19455
|
+
ok: true,
|
|
19456
|
+
value: {
|
|
19457
|
+
productDir: product.productDir,
|
|
19458
|
+
branchSlug: slugBranchIdentity(branchIdentity),
|
|
19459
|
+
backendIdentity: backend.value
|
|
19460
|
+
}
|
|
19461
|
+
};
|
|
19462
|
+
}
|
|
19463
|
+
async function resolveChangedScope(scope2, deps) {
|
|
19464
|
+
const cwd = deps.cwd ?? CONFIG_PROCESS_CWD.read();
|
|
19465
|
+
const git = deps.git ?? defaultGitDependencies;
|
|
19466
|
+
const diff = await git.execa(GIT_ROOT_COMMAND.EXECUTABLE, [...changesetNameStatusArgs(scope2.base, scope2.head)], {
|
|
19467
|
+
cwd,
|
|
19468
|
+
reject: false
|
|
19469
|
+
});
|
|
19470
|
+
if (diff.exitCode !== VERIFY_CLI_EXIT_CODE.OK) {
|
|
19471
|
+
return { ok: false, error: `${VERIFY_CLI_ERROR.CHANGED_SCOPE_FAILED}: ${diff.stderr}` };
|
|
19472
|
+
}
|
|
19473
|
+
return { ok: true, value: pathsFromNameStatus(diff.stdout) };
|
|
19474
|
+
}
|
|
19475
|
+
async function persistInputRecord(runScope2, record6, deps) {
|
|
19476
|
+
const path7 = verifyInputRecordPath(runScope2);
|
|
19477
|
+
if (!path7.ok) return path7;
|
|
19478
|
+
const fs8 = deps.fs ?? defaultFileSystem;
|
|
19479
|
+
try {
|
|
19480
|
+
await fs8.mkdir(dirname12(path7.value), { recursive: true });
|
|
19481
|
+
await fs8.writeFile(path7.value, JSON.stringify(record6));
|
|
19482
|
+
return { ok: true, value: void 0 };
|
|
19483
|
+
} catch (error) {
|
|
19484
|
+
return { ok: false, error: `${VERIFY_CLI_ERROR.INPUT_PERSIST_FAILED}: ${toMessage2(error)}` };
|
|
19485
|
+
}
|
|
19486
|
+
}
|
|
19487
|
+
async function readInputRecordAt(path7, deps) {
|
|
19488
|
+
const fs8 = deps.fs ?? defaultFileSystem;
|
|
19489
|
+
try {
|
|
19490
|
+
const content = await fs8.readFile(path7, STATE_STORE_TEXT_ENCODING);
|
|
19491
|
+
return { ok: true, value: JSON.parse(content) };
|
|
19492
|
+
} catch (error) {
|
|
19493
|
+
if (hasErrorCode(error, ERROR_CODE_NOT_FOUND)) return { ok: true, value: void 0 };
|
|
19494
|
+
return { ok: false, error: `${VERIFY_CLI_ERROR.INPUT_READ_FAILED}: ${toMessage2(error)}` };
|
|
19495
|
+
}
|
|
19496
|
+
}
|
|
19497
|
+
function verifyRunNotFoundDiagnostic(context) {
|
|
19498
|
+
return [
|
|
19499
|
+
VERIFY_CLI_ERROR.RUN_NOT_FOUND,
|
|
19500
|
+
`run=${context.runToken}`,
|
|
19501
|
+
`verification-type=${context.verificationType}`,
|
|
19502
|
+
`scope-type=${context.scopeType}`,
|
|
19503
|
+
`scope=${context.scopeIdentity}`,
|
|
19504
|
+
`backend=${context.backendIdentity}`,
|
|
19505
|
+
`namespace=${context.storageNamespace}`,
|
|
19506
|
+
`target=${context.searchedTarget}`
|
|
19507
|
+
].join(" ");
|
|
19508
|
+
}
|
|
19509
|
+
async function verifyStartCommand(options, deps) {
|
|
19510
|
+
if (options.scopeType !== VERIFY_SCOPE_TYPE.CHANGESET) return errorResult3(VERIFY_SCOPE_ERROR.UNSUPPORTED_SCOPE_TYPE);
|
|
19511
|
+
if (options.input.trim().length === 0) return errorResult3(VERIFY_CLI_ERROR.INPUT_REQUIRED);
|
|
19512
|
+
const scope2 = parseChangesetScope(options.scope);
|
|
19513
|
+
if (!scope2.ok) return errorResult3(scope2.error);
|
|
19514
|
+
const resolved = await resolveVerifyScope(deps);
|
|
19515
|
+
if (!resolved.ok) return errorResult3(resolved.error);
|
|
19516
|
+
const inputContent = await deps.readInputSource(options.input);
|
|
19517
|
+
const inputDigest = digestRunInput(options.input, inputContent);
|
|
19518
|
+
if (!inputDigest.ok) return errorResult3(inputDigest.error);
|
|
19519
|
+
const context = await verificationContextCreateCommand(
|
|
19520
|
+
{
|
|
19521
|
+
subject: VERIFICATION_CONTEXT_SUBJECT_KIND.CHANGESET,
|
|
19522
|
+
base: scope2.value.base,
|
|
19523
|
+
head: scope2.value.head,
|
|
19524
|
+
predicate: options.verificationType,
|
|
19525
|
+
workflow: options.verificationType
|
|
19526
|
+
},
|
|
19527
|
+
forwardDeps(deps)
|
|
19528
|
+
);
|
|
19529
|
+
if (context.exitCode !== VERIFY_CLI_EXIT_CODE.OK) return errorResult3(context.output);
|
|
19530
|
+
const contextDigest = JSON.parse(context.output).digest;
|
|
19531
|
+
const opened = await journalOpenCommand(
|
|
19532
|
+
{ type: options.verificationType, branchSlug: resolved.value.branchSlug },
|
|
19533
|
+
forwardDeps(deps)
|
|
19534
|
+
);
|
|
19535
|
+
if (opened.exitCode !== VERIFY_CLI_EXIT_CODE.OK) return errorResult3(opened.output);
|
|
19536
|
+
const { runToken, runFile } = JSON.parse(opened.output);
|
|
19537
|
+
const runScope2 = {
|
|
19538
|
+
productDir: resolved.value.productDir,
|
|
19539
|
+
branchSlug: resolved.value.branchSlug,
|
|
19540
|
+
type: options.verificationType,
|
|
19541
|
+
runToken
|
|
19542
|
+
};
|
|
19543
|
+
const recorded = { source: options.input, digest: inputDigest.value, content: inputContent };
|
|
19544
|
+
const persisted = await persistInputRecord(runScope2, recorded, deps);
|
|
19545
|
+
if (!persisted.ok) return errorResult3(persisted.error);
|
|
19546
|
+
const changedScope = await resolveChangedScope(scope2.value, deps);
|
|
19547
|
+
if (!changedScope.ok) return errorResult3(changedScope.error);
|
|
19548
|
+
const namespace = verifyRunsDir(runScope2);
|
|
19549
|
+
if (!namespace.ok) return errorResult3(namespace.error);
|
|
19550
|
+
const locator = buildRunLocator({
|
|
19551
|
+
runToken,
|
|
19552
|
+
verificationType: options.verificationType,
|
|
19553
|
+
scopeType: options.scopeType,
|
|
19554
|
+
scopeIdentity: options.scope,
|
|
19555
|
+
backendIdentity: resolved.value.backendIdentity,
|
|
19556
|
+
storageNamespace: namespace.value,
|
|
19557
|
+
runTarget: runFile
|
|
19558
|
+
});
|
|
19559
|
+
const report2 = {
|
|
19560
|
+
runToken,
|
|
19561
|
+
contextDigest,
|
|
19562
|
+
changedScope: changedScope.value,
|
|
19563
|
+
input: { source: options.input, digest: inputDigest.value },
|
|
19564
|
+
locator
|
|
19565
|
+
};
|
|
19566
|
+
return okResult3(JSON.stringify(report2));
|
|
19567
|
+
}
|
|
19568
|
+
async function verifyInputCommand(options, deps) {
|
|
19569
|
+
if (options.run.trim().length === 0) return errorResult3(VERIFY_CLI_ERROR.RUN_REQUIRED);
|
|
19570
|
+
const resolved = await resolveVerifyScope(deps);
|
|
19571
|
+
if (!resolved.ok) return errorResult3(resolved.error);
|
|
19572
|
+
const runScope2 = {
|
|
19573
|
+
productDir: resolved.value.productDir,
|
|
19574
|
+
branchSlug: resolved.value.branchSlug,
|
|
19575
|
+
type: options.verificationType,
|
|
19576
|
+
runToken: options.run
|
|
19577
|
+
};
|
|
19578
|
+
const path7 = verifyInputRecordPath(runScope2);
|
|
19579
|
+
if (!path7.ok) return errorResult3(path7.error);
|
|
19580
|
+
const namespace = verifyRunsDir(runScope2);
|
|
19581
|
+
if (!namespace.ok) return errorResult3(namespace.error);
|
|
19582
|
+
const record6 = await readInputRecordAt(path7.value, deps);
|
|
19583
|
+
if (!record6.ok) return errorResult3(record6.error);
|
|
19584
|
+
if (record6.value === void 0) {
|
|
19585
|
+
return errorResult3(
|
|
19586
|
+
verifyRunNotFoundDiagnostic({
|
|
19587
|
+
runToken: options.run,
|
|
19588
|
+
verificationType: options.verificationType,
|
|
19589
|
+
scopeType: options.scopeType,
|
|
19590
|
+
scopeIdentity: options.scope,
|
|
19591
|
+
backendIdentity: resolved.value.backendIdentity,
|
|
19592
|
+
storageNamespace: namespace.value,
|
|
19593
|
+
searchedTarget: path7.value
|
|
19594
|
+
})
|
|
19595
|
+
);
|
|
19596
|
+
}
|
|
19597
|
+
const report2 = {
|
|
19598
|
+
source: record6.value.source,
|
|
19599
|
+
digest: record6.value.digest,
|
|
19600
|
+
content: record6.value.content
|
|
19601
|
+
};
|
|
19602
|
+
return okResult3(JSON.stringify(report2));
|
|
19603
|
+
}
|
|
19604
|
+
async function readRunJournalEvents(scope2, deps) {
|
|
19605
|
+
const read = await journalReadCommand(scope2, String(JOURNAL_SEQ_BASE), forwardDeps(deps));
|
|
19606
|
+
if (read.exitCode !== VERIFY_CLI_EXIT_CODE.OK) return { ok: false, error: read.output };
|
|
19607
|
+
return { ok: true, value: JSON.parse(read.output) };
|
|
19608
|
+
}
|
|
19609
|
+
async function prepareAppend(options, deps) {
|
|
19610
|
+
if (options.run.trim().length === 0) return { ok: false, error: VERIFY_CLI_ERROR.RUN_REQUIRED };
|
|
19611
|
+
if (options.payload.trim().length === 0) return { ok: false, error: VERIFY_CLI_ERROR.PAYLOAD_REQUIRED };
|
|
19612
|
+
if (options.idempotencyKey.trim().length === 0) {
|
|
19613
|
+
return { ok: false, error: VERIFY_CLI_ERROR.IDEMPOTENCY_KEY_REQUIRED };
|
|
19614
|
+
}
|
|
19615
|
+
const readPayload = deps.readPayloadSource;
|
|
19616
|
+
const binding = deps.journalBinding;
|
|
19617
|
+
if (readPayload === void 0 || binding === void 0) return { ok: false, error: VERIFY_CLI_ERROR.APPEND_FAILED };
|
|
19618
|
+
const resolved = await resolveVerifyScope(deps);
|
|
19619
|
+
if (!resolved.ok) return resolved;
|
|
19620
|
+
const runScope2 = {
|
|
19621
|
+
productDir: resolved.value.productDir,
|
|
19622
|
+
branchSlug: resolved.value.branchSlug,
|
|
19623
|
+
type: options.verificationType,
|
|
19624
|
+
runToken: options.run
|
|
19625
|
+
};
|
|
19626
|
+
const namespace = verifyRunsDir(runScope2);
|
|
19627
|
+
if (!namespace.ok) return namespace;
|
|
19628
|
+
const inputPath = verifyInputRecordPath(runScope2);
|
|
19629
|
+
if (!inputPath.ok) return inputPath;
|
|
19630
|
+
const inputRecord = await readInputRecordAt(inputPath.value, deps);
|
|
19631
|
+
if (!inputRecord.ok) return inputRecord;
|
|
19632
|
+
if (inputRecord.value === void 0) {
|
|
19633
|
+
return { ok: false, error: appendRunNotFoundDiagnostic(options, resolved.value.backendIdentity, namespace.value) };
|
|
19634
|
+
}
|
|
19635
|
+
return {
|
|
19636
|
+
ok: true,
|
|
19637
|
+
value: {
|
|
19638
|
+
readPayload,
|
|
19639
|
+
binding,
|
|
19640
|
+
journalScope: { type: options.verificationType, runToken: options.run, branchSlug: resolved.value.branchSlug },
|
|
19641
|
+
namespace: namespace.value,
|
|
19642
|
+
backendIdentity: resolved.value.backendIdentity
|
|
19643
|
+
}
|
|
19644
|
+
};
|
|
19645
|
+
}
|
|
19646
|
+
function appendRunNotFoundDiagnostic(options, backendIdentity, namespace) {
|
|
19647
|
+
return verifyRunNotFoundDiagnostic({
|
|
19648
|
+
runToken: options.run,
|
|
19649
|
+
verificationType: options.verificationType,
|
|
19650
|
+
scopeType: options.scopeType,
|
|
19651
|
+
scopeIdentity: options.scope,
|
|
19652
|
+
backendIdentity,
|
|
19653
|
+
storageNamespace: namespace,
|
|
19654
|
+
searchedTarget: namespace
|
|
19655
|
+
});
|
|
19656
|
+
}
|
|
19657
|
+
function validateAppendFinding(verb, verificationType, payload) {
|
|
19658
|
+
if (verb !== VERIFY_VERB.APPEND_FINDING) return void 0;
|
|
19659
|
+
const validator = findingValidatorFor(verificationType);
|
|
19660
|
+
if (validator === void 0) return VERIFY_CLI_ERROR.UNSUPPORTED_VERIFICATION_TYPE;
|
|
19661
|
+
if (validator(payload) === void 0) return VERIFY_CLI_ERROR.FINDING_INVALID;
|
|
19662
|
+
return void 0;
|
|
19663
|
+
}
|
|
19664
|
+
function appendEventType(verb) {
|
|
19665
|
+
return verb === VERIFY_VERB.APPEND_FINDING ? VERIFY_APPEND_EVENT_TYPE.FINDING : VERIFY_APPEND_EVENT_TYPE.SCOPE;
|
|
19666
|
+
}
|
|
19667
|
+
async function verifyAppend(options, deps, verb) {
|
|
19668
|
+
const prepared = await prepareAppend(options, deps);
|
|
19669
|
+
if (!prepared.ok) return errorResult3(prepared.error);
|
|
19670
|
+
const { readPayload, binding, journalScope, namespace, backendIdentity } = prepared.value;
|
|
19671
|
+
const eventType = appendEventType(verb);
|
|
19672
|
+
const before = await readRunJournalEvents(journalScope, deps);
|
|
19673
|
+
if (!before.ok) {
|
|
19674
|
+
if (before.error === JOURNAL_RUNTIME_ERROR.RUN_NOT_FOUND) {
|
|
19675
|
+
return errorResult3(appendRunNotFoundDiagnostic(options, backendIdentity, namespace));
|
|
19676
|
+
}
|
|
19677
|
+
return errorResult3(`${VERIFY_CLI_ERROR.APPEND_FAILED}: ${before.error}`);
|
|
19678
|
+
}
|
|
19679
|
+
const existing = findAppendedSequence(before.value, options.idempotencyKey, eventType);
|
|
19680
|
+
if (existing !== void 0) {
|
|
19681
|
+
const report3 = { sequence: existing, idempotent: true };
|
|
19682
|
+
return okResult3(JSON.stringify(report3));
|
|
19683
|
+
}
|
|
19684
|
+
let rawPayload;
|
|
19685
|
+
try {
|
|
19686
|
+
rawPayload = await readPayload(options.payload);
|
|
19687
|
+
} catch (error) {
|
|
19688
|
+
return errorResult3(`${VERIFY_CLI_ERROR.PAYLOAD_READ_FAILED}: ${toMessage2(error)}`);
|
|
19689
|
+
}
|
|
19690
|
+
const parsed = parseAppendPayload(rawPayload);
|
|
19691
|
+
if (parsed === void 0) return errorResult3(VERIFY_CLI_ERROR.PAYLOAD_INVALID);
|
|
19692
|
+
const findingError = validateAppendFinding(verb, options.verificationType, parsed);
|
|
19693
|
+
if (findingError !== void 0) return errorResult3(findingError);
|
|
19694
|
+
const event = buildAppendEvent({
|
|
19695
|
+
eventType,
|
|
19696
|
+
idempotencyKey: options.idempotencyKey,
|
|
19697
|
+
payload: parsed,
|
|
19698
|
+
at: deps.now?.() ?? /* @__PURE__ */ new Date()
|
|
19699
|
+
});
|
|
19700
|
+
const appended = await journalAppendCommand(journalScope, event, binding, forwardDeps(deps));
|
|
19701
|
+
if (appended.exitCode !== VERIFY_CLI_EXIT_CODE.OK) {
|
|
19702
|
+
return errorResult3(`${VERIFY_CLI_ERROR.APPEND_FAILED}: ${appended.output}`);
|
|
19703
|
+
}
|
|
19704
|
+
const after = await readRunJournalEvents(journalScope, deps);
|
|
19705
|
+
if (!after.ok) return errorResult3(`${VERIFY_CLI_ERROR.APPEND_FAILED}: ${after.error}`);
|
|
19706
|
+
const sequence = findAppendedSequence(after.value, options.idempotencyKey, eventType);
|
|
19707
|
+
if (sequence === void 0) return errorResult3(VERIFY_CLI_ERROR.APPEND_FAILED);
|
|
19708
|
+
const report2 = { sequence, idempotent: false };
|
|
19709
|
+
return okResult3(JSON.stringify(report2));
|
|
19710
|
+
}
|
|
19711
|
+
async function verifyAppendScopeCommand(options, deps) {
|
|
19712
|
+
return verifyAppend(options, deps, VERIFY_VERB.APPEND_SCOPE);
|
|
19713
|
+
}
|
|
19714
|
+
async function verifyAppendFindingCommand(options, deps) {
|
|
19715
|
+
return verifyAppend(options, deps, VERIFY_VERB.APPEND_FINDING);
|
|
19716
|
+
}
|
|
19717
|
+
|
|
19718
|
+
// src/interfaces/cli/verify.ts
|
|
19719
|
+
var VERIFY_CLI = {
|
|
19720
|
+
commandName: "verify",
|
|
19721
|
+
description: "Record and replay a typed verification run",
|
|
19722
|
+
startCommandName: VERIFY_VERB.START,
|
|
19723
|
+
inputCommandName: VERIFY_VERB.INPUT,
|
|
19724
|
+
appendScopeCommandName: VERIFY_VERB.APPEND_SCOPE,
|
|
19725
|
+
appendFindingCommandName: VERIFY_VERB.APPEND_FINDING,
|
|
19726
|
+
verificationTypeOption: "--verification-type <type>",
|
|
19727
|
+
scopeTypeOption: "--scope-type <scope-type>",
|
|
19728
|
+
scopeOption: "--scope <base>..<head>",
|
|
19729
|
+
inputOption: "--input <input-source>",
|
|
19730
|
+
runOption: "--run <token>",
|
|
19731
|
+
payloadOption: "--payload <payload-source>",
|
|
19732
|
+
idempotencyKeyOption: "--idempotency-key <key>"
|
|
19733
|
+
};
|
|
19734
|
+
var CLI_SOURCE_ENCODING = "utf8";
|
|
19735
|
+
async function readStdinText() {
|
|
19736
|
+
const chunks = [];
|
|
19737
|
+
for await (const chunk of process.stdin) {
|
|
19738
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
19739
|
+
}
|
|
19740
|
+
return Buffer.concat(chunks).toString(CLI_SOURCE_ENCODING);
|
|
19741
|
+
}
|
|
19742
|
+
async function readCliSource(source) {
|
|
19743
|
+
if (source === VERIFY_INPUT_SOURCE.STDIN) return readStdinText();
|
|
19744
|
+
return readFile12(source, CLI_SOURCE_ENCODING);
|
|
19745
|
+
}
|
|
19746
|
+
var verifyDomain = {
|
|
19747
|
+
name: VERIFY_CLI.commandName,
|
|
19748
|
+
description: VERIFY_CLI.description,
|
|
19749
|
+
register: (program, invocation) => {
|
|
19750
|
+
const deps = () => ({
|
|
19751
|
+
cwd: invocation.resolveEffectiveInvocationDir(),
|
|
19752
|
+
readInputSource: readCliSource,
|
|
19753
|
+
readPayloadSource: readCliSource,
|
|
19754
|
+
// The append verbs write a single structured JSON result to stdout, so the run's event
|
|
19755
|
+
// stream goes to stderr under the local backend rather than sharing the result channel.
|
|
19756
|
+
journalBinding: createJournalStreamBinding(invocation.io, stderrStreamSink(invocation.io))
|
|
19757
|
+
});
|
|
19758
|
+
const command = program.command(VERIFY_CLI.commandName).description(VERIFY_CLI.description);
|
|
19759
|
+
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) => {
|
|
19760
|
+
reportCliResult(await verifyStartCommand(options, deps()), invocation.io);
|
|
19761
|
+
});
|
|
19762
|
+
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) => {
|
|
19763
|
+
reportCliResult(await verifyInputCommand(options, deps()), invocation.io);
|
|
19764
|
+
});
|
|
19765
|
+
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) => {
|
|
19766
|
+
reportCliResult(await verifyAppendScopeCommand(options, deps()), invocation.io);
|
|
19767
|
+
});
|
|
19768
|
+
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) => {
|
|
19769
|
+
reportCliResult(await verifyAppendFindingCommand(options, deps()), invocation.io);
|
|
19770
|
+
});
|
|
19771
|
+
}
|
|
19772
|
+
};
|
|
19773
|
+
|
|
19097
19774
|
// src/interfaces/cli/worktree.ts
|
|
19098
19775
|
import { randomBytes as nodeRandomBytes4 } from "crypto";
|
|
19099
19776
|
|
|
@@ -19135,7 +19812,7 @@ function resolveReleaseSessionId(explicitSessionId, env) {
|
|
|
19135
19812
|
}
|
|
19136
19813
|
|
|
19137
19814
|
// src/commands/worktree/status.ts
|
|
19138
|
-
import { basename as basename8, dirname as
|
|
19815
|
+
import { basename as basename8, dirname as dirname13, sep as sep5 } from "path";
|
|
19139
19816
|
var WORKTREE_STATUS_FORMAT = {
|
|
19140
19817
|
JSON: "json",
|
|
19141
19818
|
TEXT: "text"
|
|
@@ -19226,7 +19903,7 @@ function renderTextStatus(records) {
|
|
|
19226
19903
|
const sections = [];
|
|
19227
19904
|
const sectionByParent = /* @__PURE__ */ new Map();
|
|
19228
19905
|
for (const record6 of records) {
|
|
19229
|
-
const parent = renderParentDirectory(
|
|
19906
|
+
const parent = renderParentDirectory(dirname13(record6.worktreeRoot));
|
|
19230
19907
|
const children = sectionByParent.get(parent);
|
|
19231
19908
|
const rendered = renderTextStatusChild(record6);
|
|
19232
19909
|
if (children === void 0) {
|
|
@@ -19240,7 +19917,7 @@ function renderTextStatus(records) {
|
|
|
19240
19917
|
return renderPlainTree({ sections });
|
|
19241
19918
|
}
|
|
19242
19919
|
function renderParentDirectory(parent) {
|
|
19243
|
-
return parent.endsWith(
|
|
19920
|
+
return parent.endsWith(sep5) ? parent : `${parent}${sep5}`;
|
|
19244
19921
|
}
|
|
19245
19922
|
function renderTextStatusChild(record6) {
|
|
19246
19923
|
if (record6.status === OCCUPANCY_STATUS.RUNNING) {
|
|
@@ -19365,6 +20042,7 @@ var CLI_DOMAINS = [
|
|
|
19365
20042
|
testingDomain,
|
|
19366
20043
|
validationDomain,
|
|
19367
20044
|
verificationContextDomain,
|
|
20045
|
+
verifyDomain,
|
|
19368
20046
|
worktreeDomain
|
|
19369
20047
|
];
|
|
19370
20048
|
|