@outcomeeng/spx 0.6.3 → 0.6.4
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 +16 -6
- package/dist/cli.js +1866 -944
- package/dist/cli.js.map +1 -1
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -1,11 +1,100 @@
|
|
|
1
1
|
// src/cli.ts
|
|
2
|
-
import { Command } from "commander";
|
|
3
2
|
import { createRequire as createRequire2 } from "module";
|
|
4
3
|
|
|
4
|
+
// src/interfaces/cli/program.ts
|
|
5
|
+
import { Command } from "commander";
|
|
6
|
+
|
|
7
|
+
// src/domains/config/cwd.ts
|
|
8
|
+
var CONFIG_PROCESS_CWD = {
|
|
9
|
+
read: () => process.cwd()
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
// src/domains/config/root.ts
|
|
13
|
+
import { execFileSync } from "child_process";
|
|
14
|
+
import { resolve } from "path";
|
|
15
|
+
var GIT_EXECUTABLE = "git";
|
|
16
|
+
var GIT_TOPLEVEL_ARGS = ["rev-parse", "--show-toplevel"];
|
|
17
|
+
var GIT_NOT_REPO_MARKER = "not a git repository";
|
|
18
|
+
function resolveProductDir(cwd, deps = DEFAULT_PRODUCT_DIR_RESOLVER_DEPS) {
|
|
19
|
+
const resolvedCwd = resolve(cwd);
|
|
20
|
+
const toplevel = deps.readGitToplevel(resolvedCwd);
|
|
21
|
+
if (toplevel !== void 0 && toplevel.length > 0) {
|
|
22
|
+
return { productDir: resolve(toplevel) };
|
|
23
|
+
}
|
|
24
|
+
return {
|
|
25
|
+
productDir: resolvedCwd,
|
|
26
|
+
warning: `warning: ${resolvedCwd} is not inside a git worktree \u2014 falling back to the current working directory. ${GIT_NOT_REPO_MARKER}.`
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
var DEFAULT_PRODUCT_DIR_RESOLVER_DEPS = {
|
|
30
|
+
readGitToplevel: (cwd) => {
|
|
31
|
+
try {
|
|
32
|
+
return execFileSync(GIT_EXECUTABLE, [...GIT_TOPLEVEL_ARGS], {
|
|
33
|
+
// NOSONAR - spx intentionally uses the caller's git executable.
|
|
34
|
+
cwd,
|
|
35
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
36
|
+
encoding: "utf8"
|
|
37
|
+
}).trim();
|
|
38
|
+
} catch {
|
|
39
|
+
return void 0;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
// src/interfaces/cli/product-context.ts
|
|
45
|
+
import { resolve as resolve2 } from "path";
|
|
46
|
+
var SPX_GLOBAL_OPTIONS = {
|
|
47
|
+
directory: {
|
|
48
|
+
short: "-C",
|
|
49
|
+
long: "--directory",
|
|
50
|
+
operand: "<path>",
|
|
51
|
+
flags: "-C, --directory <path>",
|
|
52
|
+
description: "Run as if spx was started in <path>"
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
function createCliInvocation(options) {
|
|
56
|
+
let cachedEffectiveInvocationDir;
|
|
57
|
+
let cachedContext;
|
|
58
|
+
const resolveEffectiveInvocationDir = () => {
|
|
59
|
+
cachedEffectiveInvocationDir ??= resolve2(options.processCwd(), options.readDirectoryOption() ?? "");
|
|
60
|
+
return cachedEffectiveInvocationDir;
|
|
61
|
+
};
|
|
62
|
+
return {
|
|
63
|
+
io: options.io,
|
|
64
|
+
resolveEffectiveInvocationDir,
|
|
65
|
+
resolveProductContext: () => {
|
|
66
|
+
if (cachedContext !== void 0) {
|
|
67
|
+
return cachedContext;
|
|
68
|
+
}
|
|
69
|
+
const effectiveInvocationDir = resolveEffectiveInvocationDir();
|
|
70
|
+
const resolved = (options.resolveProductDir ?? resolveProductDir)(effectiveInvocationDir);
|
|
71
|
+
cachedContext = {
|
|
72
|
+
effectiveInvocationDir,
|
|
73
|
+
productDir: resolved.productDir,
|
|
74
|
+
...resolved.warning === void 0 ? {} : { warning: resolved.warning }
|
|
75
|
+
};
|
|
76
|
+
options.writeWarning(cachedContext.warning);
|
|
77
|
+
return cachedContext;
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
var DEFAULT_CLI_IO = {
|
|
82
|
+
writeStdout: (output) => {
|
|
83
|
+
process.stdout.write(output);
|
|
84
|
+
},
|
|
85
|
+
writeStderr: (output) => {
|
|
86
|
+
process.stderr.write(output);
|
|
87
|
+
},
|
|
88
|
+
setExitCode: (exitCode) => {
|
|
89
|
+
process.exitCode = exitCode;
|
|
90
|
+
},
|
|
91
|
+
exit: (exitCode) => process.exit(exitCode)
|
|
92
|
+
};
|
|
93
|
+
|
|
5
94
|
// src/commands/claude/init.ts
|
|
6
95
|
import { execa } from "execa";
|
|
7
96
|
async function initCommand(options = {}) {
|
|
8
|
-
const cwd = options.cwd ||
|
|
97
|
+
const cwd = options.cwd || CONFIG_PROCESS_CWD.read();
|
|
9
98
|
try {
|
|
10
99
|
const { stdout: listOutput } = await execa(
|
|
11
100
|
"claude",
|
|
@@ -115,7 +204,7 @@ import { dirname as dirname2, join as join2 } from "path";
|
|
|
115
204
|
|
|
116
205
|
// src/git/root.ts
|
|
117
206
|
import { execa as execa2 } from "execa";
|
|
118
|
-
import { basename, dirname, isAbsolute, join, resolve } from "path";
|
|
207
|
+
import { basename, dirname, isAbsolute, join, resolve as resolve3 } from "path";
|
|
119
208
|
|
|
120
209
|
// src/git/environment.ts
|
|
121
210
|
function withoutGitEnvironment(env) {
|
|
@@ -223,7 +312,7 @@ var GIT_WORKTREE_PORCELAIN_BARE_LINE = "bare";
|
|
|
223
312
|
var GIT_WORKTREE_PORCELAIN_PRUNABLE_LINE = "prunable";
|
|
224
313
|
var GIT_WORKTREE_PORCELAIN_PRUNABLE_PREFIX = `${GIT_WORKTREE_PORCELAIN_PRUNABLE_LINE} `;
|
|
225
314
|
var TRAILING_PATH_SEPARATORS_PATTERN = /[\\/]+$/;
|
|
226
|
-
async function detectWorktreeProductRoot(cwd =
|
|
315
|
+
async function detectWorktreeProductRoot(cwd = CONFIG_PROCESS_CWD.read(), deps = defaultGitDependencies) {
|
|
227
316
|
try {
|
|
228
317
|
const result = await deps.execa(
|
|
229
318
|
GIT_ROOT_COMMAND.EXECUTABLE,
|
|
@@ -259,7 +348,7 @@ function trimStdout(stdout) {
|
|
|
259
348
|
const str = typeof stdout === "string" ? stdout : String(stdout);
|
|
260
349
|
return str.trim();
|
|
261
350
|
}
|
|
262
|
-
async function detectGitCommonDirProductRoot(cwd =
|
|
351
|
+
async function detectGitCommonDirProductRoot(cwd = CONFIG_PROCESS_CWD.read(), deps = defaultGitDependencies) {
|
|
263
352
|
try {
|
|
264
353
|
const toplevelResult = await deps.execa(
|
|
265
354
|
GIT_ROOT_COMMAND.EXECUTABLE,
|
|
@@ -288,7 +377,7 @@ async function detectGitCommonDirProductRoot(cwd = process.cwd(), deps = default
|
|
|
288
377
|
};
|
|
289
378
|
}
|
|
290
379
|
const commonDir = extractStdout(commonDirResult.stdout);
|
|
291
|
-
const absoluteCommonDir = isAbsolute(commonDir) ? commonDir :
|
|
380
|
+
const absoluteCommonDir = isAbsolute(commonDir) ? commonDir : resolve3(toplevel, commonDir);
|
|
292
381
|
const gitCommonDirProductRoot = dirname(absoluteCommonDir);
|
|
293
382
|
return {
|
|
294
383
|
productDir: gitCommonDirProductRoot,
|
|
@@ -304,7 +393,7 @@ async function detectGitCommonDirProductRoot(cwd = process.cwd(), deps = default
|
|
|
304
393
|
};
|
|
305
394
|
}
|
|
306
395
|
}
|
|
307
|
-
async function resolveDefaultBranch(cwd =
|
|
396
|
+
async function resolveDefaultBranch(cwd = CONFIG_PROCESS_CWD.read(), deps = defaultGitDependencies) {
|
|
308
397
|
const result = await deps.execa(
|
|
309
398
|
GIT_ROOT_COMMAND.EXECUTABLE,
|
|
310
399
|
[...GIT_ORIGIN_HEAD_REF_ARGS],
|
|
@@ -316,7 +405,7 @@ async function resolveDefaultBranch(cwd = process.cwd(), deps = defaultGitDepend
|
|
|
316
405
|
const branch = ref.slice(ORIGIN_REF_PREFIX.length);
|
|
317
406
|
return branch.length === 0 ? null : branch;
|
|
318
407
|
}
|
|
319
|
-
async function getCurrentBranch(cwd =
|
|
408
|
+
async function getCurrentBranch(cwd = CONFIG_PROCESS_CWD.read(), deps = defaultGitDependencies) {
|
|
320
409
|
const result = await deps.execa(
|
|
321
410
|
GIT_ROOT_COMMAND.EXECUTABLE,
|
|
322
411
|
[...GIT_CURRENT_BRANCH_ARGS],
|
|
@@ -327,7 +416,7 @@ async function getCurrentBranch(cwd = process.cwd(), deps = defaultGitDependenci
|
|
|
327
416
|
if (branch.length === 0 || branch === GIT_ROOT_COMMAND.HEAD) return null;
|
|
328
417
|
return branch;
|
|
329
418
|
}
|
|
330
|
-
async function getHeadSha(cwd =
|
|
419
|
+
async function getHeadSha(cwd = CONFIG_PROCESS_CWD.read(), deps = defaultGitDependencies) {
|
|
331
420
|
const result = await deps.execa(
|
|
332
421
|
GIT_ROOT_COMMAND.EXECUTABLE,
|
|
333
422
|
[...GIT_HEAD_SHA_ARGS],
|
|
@@ -337,7 +426,7 @@ async function getHeadSha(cwd = process.cwd(), deps = defaultGitDependencies) {
|
|
|
337
426
|
const sha = extractStdout(result.stdout);
|
|
338
427
|
return sha.length === 0 ? null : sha;
|
|
339
428
|
}
|
|
340
|
-
async function resolveRefSha(ref, cwd =
|
|
429
|
+
async function resolveRefSha(ref, cwd = CONFIG_PROCESS_CWD.read(), deps = defaultGitDependencies) {
|
|
341
430
|
const result = await deps.execa(
|
|
342
431
|
GIT_ROOT_COMMAND.EXECUTABLE,
|
|
343
432
|
[GIT_ROOT_COMMAND.REV_PARSE, ref],
|
|
@@ -347,7 +436,7 @@ async function resolveRefSha(ref, cwd = process.cwd(), deps = defaultGitDependen
|
|
|
347
436
|
const sha = extractStdout(result.stdout);
|
|
348
437
|
return sha.length === 0 ? null : sha;
|
|
349
438
|
}
|
|
350
|
-
async function originBranchExists(branch, cwd =
|
|
439
|
+
async function originBranchExists(branch, cwd = CONFIG_PROCESS_CWD.read(), deps = defaultGitDependencies) {
|
|
351
440
|
if (branch === GIT_ROOT_COMMAND.HEAD) return false;
|
|
352
441
|
const result = await deps.execa(
|
|
353
442
|
GIT_ROOT_COMMAND.EXECUTABLE,
|
|
@@ -361,7 +450,7 @@ async function originBranchExists(branch, cwd = process.cwd(), deps = defaultGit
|
|
|
361
450
|
);
|
|
362
451
|
return result.exitCode === 0;
|
|
363
452
|
}
|
|
364
|
-
async function isWorkingTreeClean(cwd =
|
|
453
|
+
async function isWorkingTreeClean(cwd = CONFIG_PROCESS_CWD.read(), deps = defaultGitDependencies) {
|
|
365
454
|
const result = await deps.execa(
|
|
366
455
|
GIT_ROOT_COMMAND.EXECUTABLE,
|
|
367
456
|
[...GIT_STATUS_PORCELAIN_ARGS],
|
|
@@ -427,7 +516,7 @@ function mainCheckoutPath(facts) {
|
|
|
427
516
|
const candidate = join(commonDirParent, name);
|
|
428
517
|
return isObservedWorktreeRoot(facts.worktreeRoots, candidate) ? candidate : null;
|
|
429
518
|
}
|
|
430
|
-
async function gatherGitFacts(cwd =
|
|
519
|
+
async function gatherGitFacts(cwd = CONFIG_PROCESS_CWD.read(), deps = defaultGitDependencies) {
|
|
431
520
|
try {
|
|
432
521
|
const [toplevelResult, commonDirResult, originResult, worktreeListResult] = await Promise.all([
|
|
433
522
|
deps.execa(GIT_ROOT_COMMAND.EXECUTABLE, [...GIT_SHOW_TOPLEVEL_ARGS], { cwd, reject: false }),
|
|
@@ -451,7 +540,7 @@ async function gatherGitFacts(cwd = process.cwd(), deps = defaultGitDependencies
|
|
|
451
540
|
};
|
|
452
541
|
}
|
|
453
542
|
const rawCommonDir = extractStdout(commonDirResult.stdout);
|
|
454
|
-
const commonDir = isAbsolute(rawCommonDir) ? rawCommonDir :
|
|
543
|
+
const commonDir = isAbsolute(rawCommonDir) ? rawCommonDir : resolve3(worktreeRoot, rawCommonDir);
|
|
455
544
|
const bareResult = await deps.execa(
|
|
456
545
|
GIT_ROOT_COMMAND.EXECUTABLE,
|
|
457
546
|
[...GIT_CORE_BARE_ARGS],
|
|
@@ -1307,17 +1396,35 @@ Searched for: **/.claude/settings.local.json`;
|
|
|
1307
1396
|
}
|
|
1308
1397
|
|
|
1309
1398
|
// src/interfaces/cli/claude.ts
|
|
1310
|
-
function
|
|
1399
|
+
function formatError(error) {
|
|
1400
|
+
if (error instanceof Error) {
|
|
1401
|
+
return error.message;
|
|
1402
|
+
}
|
|
1403
|
+
if (typeof error === "string") {
|
|
1404
|
+
return error;
|
|
1405
|
+
}
|
|
1406
|
+
return "Unknown error";
|
|
1407
|
+
}
|
|
1408
|
+
function writeOutput(io, output) {
|
|
1409
|
+
io.writeStdout(`${output}
|
|
1410
|
+
`);
|
|
1411
|
+
}
|
|
1412
|
+
function writeError(io, output) {
|
|
1413
|
+
io.writeStderr(`${output}
|
|
1414
|
+
`);
|
|
1415
|
+
}
|
|
1416
|
+
function exitWithError(io, error) {
|
|
1417
|
+
writeError(io, `Error: ${formatError(error)}`);
|
|
1418
|
+
return io.exit(1);
|
|
1419
|
+
}
|
|
1420
|
+
function registerClaudeCommands(claudeCmd, invocation) {
|
|
1421
|
+
const productDir = () => invocation.resolveProductContext().productDir;
|
|
1311
1422
|
claudeCmd.command("init").description("Initialize or update outcomeeng marketplace plugin").action(async () => {
|
|
1312
1423
|
try {
|
|
1313
|
-
const output = await initCommand({ cwd:
|
|
1314
|
-
|
|
1424
|
+
const output = await initCommand({ cwd: productDir() });
|
|
1425
|
+
writeOutput(invocation.io, output);
|
|
1315
1426
|
} catch (error) {
|
|
1316
|
-
|
|
1317
|
-
"Error:",
|
|
1318
|
-
error instanceof Error ? error.message : String(error)
|
|
1319
|
-
);
|
|
1320
|
-
process.exit(1);
|
|
1427
|
+
exitWithError(invocation.io, error);
|
|
1321
1428
|
}
|
|
1322
1429
|
});
|
|
1323
1430
|
const settingsCmd = claudeCmd.command("settings").description("Manage Claude Code settings");
|
|
@@ -1336,10 +1443,11 @@ function registerClaudeCommands(claudeCmd) {
|
|
|
1336
1443
|
async (options) => {
|
|
1337
1444
|
try {
|
|
1338
1445
|
if (options.write && options.outputFile) {
|
|
1339
|
-
|
|
1446
|
+
writeError(
|
|
1447
|
+
invocation.io,
|
|
1340
1448
|
"Error: --write and --output-file are mutually exclusive\nUse --write to modify global settings, or --output-file to write to a different location"
|
|
1341
1449
|
);
|
|
1342
|
-
|
|
1450
|
+
return invocation.io.exit(1);
|
|
1343
1451
|
}
|
|
1344
1452
|
const output = await consolidateCommand({
|
|
1345
1453
|
write: options.write,
|
|
@@ -1347,13 +1455,9 @@ function registerClaudeCommands(claudeCmd) {
|
|
|
1347
1455
|
root: options.root,
|
|
1348
1456
|
globalSettings: options.globalSettings
|
|
1349
1457
|
});
|
|
1350
|
-
|
|
1458
|
+
writeOutput(invocation.io, output);
|
|
1351
1459
|
} catch (error) {
|
|
1352
|
-
|
|
1353
|
-
"Error:",
|
|
1354
|
-
error instanceof Error ? error.message : String(error)
|
|
1355
|
-
);
|
|
1356
|
-
process.exit(1);
|
|
1460
|
+
exitWithError(invocation.io, error);
|
|
1357
1461
|
}
|
|
1358
1462
|
}
|
|
1359
1463
|
);
|
|
@@ -1361,9 +1465,9 @@ function registerClaudeCommands(claudeCmd) {
|
|
|
1361
1465
|
var claudeDomain = {
|
|
1362
1466
|
name: "claude",
|
|
1363
1467
|
description: "Manage Claude Code settings and plugins",
|
|
1364
|
-
register: (
|
|
1365
|
-
const claudeCmd =
|
|
1366
|
-
registerClaudeCommands(claudeCmd);
|
|
1468
|
+
register: (program, invocation) => {
|
|
1469
|
+
const claudeCmd = program.command("claude").description("Manage Claude Code settings and plugins");
|
|
1470
|
+
registerClaudeCommands(claudeCmd, invocation);
|
|
1367
1471
|
}
|
|
1368
1472
|
};
|
|
1369
1473
|
|
|
@@ -1520,8 +1624,8 @@ function readTargetPath(value, startIndex) {
|
|
|
1520
1624
|
return value.slice(startIndex, endIndex);
|
|
1521
1625
|
}
|
|
1522
1626
|
function isTargetPathCharacter(character) {
|
|
1523
|
-
const code = character.codePointAt(0);
|
|
1524
|
-
return code
|
|
1627
|
+
const code = character.codePointAt(0) ?? 0;
|
|
1628
|
+
return code >= 48 && code <= 57 || code >= 65 && code <= 90 || code >= 97 && code <= 122 || character === "." || character === "_" || character === "-" || character === "/";
|
|
1525
1629
|
}
|
|
1526
1630
|
|
|
1527
1631
|
// src/commands/compact/retrieve.ts
|
|
@@ -1576,14 +1680,15 @@ var COMPACT_CLI = {
|
|
|
1576
1680
|
var compactDomain = {
|
|
1577
1681
|
name: COMPACT_CLI.commandName,
|
|
1578
1682
|
description: COMPACT_CLI.description,
|
|
1579
|
-
register: (
|
|
1580
|
-
const
|
|
1683
|
+
register: (program, invocation) => {
|
|
1684
|
+
const effectiveInvocationDir = () => invocation.resolveEffectiveInvocationDir();
|
|
1685
|
+
const compactCmd = program.command(COMPACT_CLI.commandName).description(COMPACT_CLI.description);
|
|
1581
1686
|
compactCmd.command(COMPACT_CLI.storeCommandName).description("Store compact resume state").requiredOption(COMPACT_CLI.transcriptOption, "Transcript JSONL path").option(COMPACT_CLI.sessionIdOption, COMPACT_CLI.sessionIdDescription).action(async (options) => {
|
|
1582
|
-
|
|
1687
|
+
invocation.io.exit(
|
|
1583
1688
|
await compactStoreCommand({
|
|
1584
1689
|
transcript: options.transcript,
|
|
1585
1690
|
sessionId: options.sessionId,
|
|
1586
|
-
cwd:
|
|
1691
|
+
cwd: effectiveInvocationDir(),
|
|
1587
1692
|
env: process.env
|
|
1588
1693
|
})
|
|
1589
1694
|
);
|
|
@@ -1591,13 +1696,13 @@ var compactDomain = {
|
|
|
1591
1696
|
compactCmd.command(COMPACT_CLI.retrieveCommandName).description("Retrieve compact resume state").option(COMPACT_CLI.sessionIdOption, COMPACT_CLI.sessionIdDescription).action(async (options) => {
|
|
1592
1697
|
const result = await compactRetrieveCommand({
|
|
1593
1698
|
sessionId: options.sessionId,
|
|
1594
|
-
cwd:
|
|
1699
|
+
cwd: effectiveInvocationDir(),
|
|
1595
1700
|
env: process.env
|
|
1596
1701
|
});
|
|
1597
1702
|
if (result.output.length > 0) {
|
|
1598
|
-
|
|
1703
|
+
invocation.io.writeStdout(result.output);
|
|
1599
1704
|
}
|
|
1600
|
-
|
|
1705
|
+
invocation.io.setExitCode(result.exitCode);
|
|
1601
1706
|
});
|
|
1602
1707
|
}
|
|
1603
1708
|
};
|
|
@@ -1614,7 +1719,8 @@ function toMessage(error) {
|
|
|
1614
1719
|
if (typeof error === "string") return error;
|
|
1615
1720
|
const fallback = `[${typeof error}]`;
|
|
1616
1721
|
try {
|
|
1617
|
-
|
|
1722
|
+
const serialized = JSON.stringify(error);
|
|
1723
|
+
return typeof serialized === "string" ? serialized : fallback;
|
|
1618
1724
|
} catch {
|
|
1619
1725
|
return fallback;
|
|
1620
1726
|
}
|
|
@@ -2290,11 +2396,11 @@ function compareNamingSchemaVersions(left, right) {
|
|
|
2290
2396
|
return compareNumericVersionIdentifiers(left.version, right.version);
|
|
2291
2397
|
}
|
|
2292
2398
|
function canonicalNamingSchemaVersion(versions) {
|
|
2293
|
-
const
|
|
2399
|
+
const first = versions.at(0);
|
|
2294
2400
|
if (first === void 0) {
|
|
2295
2401
|
throw new Error("Naming-schema version tuple must declare at least one version");
|
|
2296
2402
|
}
|
|
2297
|
-
return
|
|
2403
|
+
return versions.slice(1).reduce(
|
|
2298
2404
|
(max, version2) => compareNamingSchemaVersions(version2, max) > VERSION_ORDER_EQUAL ? version2 : max,
|
|
2299
2405
|
first
|
|
2300
2406
|
);
|
|
@@ -3475,9 +3581,6 @@ function defaultsCommand(options, deps) {
|
|
|
3475
3581
|
function formatConfig(config, asJson) {
|
|
3476
3582
|
const format2 = asJson ? CONFIG_FILE_FORMAT.JSON : DEFAULT_CONFIG_FILE_FORMAT;
|
|
3477
3583
|
const serialized = serializeConfigFileSections(format2, config);
|
|
3478
|
-
if (!serialized.ok) {
|
|
3479
|
-
throw new Error(serialized.error);
|
|
3480
|
-
}
|
|
3481
3584
|
return serialized.value;
|
|
3482
3585
|
}
|
|
3483
3586
|
|
|
@@ -3503,9 +3606,6 @@ async function showCommand(options, deps) {
|
|
|
3503
3606
|
function formatConfig2(config, asJson) {
|
|
3504
3607
|
const format2 = asJson ? CONFIG_FILE_FORMAT.JSON : DEFAULT_CONFIG_FILE_FORMAT;
|
|
3505
3608
|
const serialized = serializeConfigFileSections(format2, config);
|
|
3506
|
-
if (!serialized.ok) {
|
|
3507
|
-
throw new Error(serialized.error);
|
|
3508
|
-
}
|
|
3509
3609
|
return serialized.value;
|
|
3510
3610
|
}
|
|
3511
3611
|
|
|
@@ -3549,82 +3649,55 @@ async function validateCommand(_options, deps) {
|
|
|
3549
3649
|
return { stdout, stderr: "", exitCode: 0 };
|
|
3550
3650
|
}
|
|
3551
3651
|
|
|
3552
|
-
// src/domains/config/root.ts
|
|
3553
|
-
import { execSync } from "child_process";
|
|
3554
|
-
import { resolve as resolve2 } from "path";
|
|
3555
|
-
var GIT_TOPLEVEL_CMD = "git rev-parse --show-toplevel";
|
|
3556
|
-
var GIT_NOT_REPO_MARKER = "not a git repository";
|
|
3557
|
-
function resolveProductDir(cwd = process.cwd()) {
|
|
3558
|
-
const resolvedCwd = resolve2(cwd);
|
|
3559
|
-
try {
|
|
3560
|
-
const stdout = execSync(GIT_TOPLEVEL_CMD, {
|
|
3561
|
-
cwd: resolvedCwd,
|
|
3562
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
3563
|
-
encoding: "utf8"
|
|
3564
|
-
});
|
|
3565
|
-
const toplevel = stdout.trim();
|
|
3566
|
-
if (toplevel.length > 0) {
|
|
3567
|
-
return { productDir: resolve2(toplevel) };
|
|
3568
|
-
}
|
|
3569
|
-
} catch {
|
|
3570
|
-
}
|
|
3571
|
-
return {
|
|
3572
|
-
productDir: resolvedCwd,
|
|
3573
|
-
warning: `warning: ${resolvedCwd} is not inside a git worktree \u2014 falling back to the current working directory. ${GIT_NOT_REPO_MARKER}.`
|
|
3574
|
-
};
|
|
3575
|
-
}
|
|
3576
|
-
|
|
3577
|
-
// src/interfaces/cli/write-warning.ts
|
|
3578
|
-
function writeWarning(warning) {
|
|
3579
|
-
if (warning === void 0) {
|
|
3580
|
-
return;
|
|
3581
|
-
}
|
|
3582
|
-
process.stderr.write(`${warning}
|
|
3583
|
-
`);
|
|
3584
|
-
}
|
|
3585
|
-
|
|
3586
3652
|
// src/interfaces/cli/config.ts
|
|
3587
|
-
|
|
3653
|
+
var CONFIG_CLI = {
|
|
3654
|
+
commandName: "config",
|
|
3655
|
+
commands: {
|
|
3656
|
+
defaults: "defaults",
|
|
3657
|
+
show: "show",
|
|
3658
|
+
validate: "validate"
|
|
3659
|
+
},
|
|
3660
|
+
flags: {
|
|
3661
|
+
json: "--json"
|
|
3662
|
+
}
|
|
3663
|
+
};
|
|
3664
|
+
function buildDefaultDeps(invocation) {
|
|
3588
3665
|
return {
|
|
3589
3666
|
resolveConfig,
|
|
3590
3667
|
readProductConfigFile,
|
|
3591
3668
|
resolveConfigFromReadResult,
|
|
3592
|
-
resolveProductDir: () =>
|
|
3593
|
-
const resolved = resolveProductDir();
|
|
3594
|
-
writeWarning(resolved.warning);
|
|
3595
|
-
return resolved.productDir;
|
|
3596
|
-
},
|
|
3669
|
+
resolveProductDir: () => invocation.resolveProductContext().productDir,
|
|
3597
3670
|
descriptors: productionRegistry
|
|
3598
3671
|
};
|
|
3599
3672
|
}
|
|
3600
|
-
async function emit(result) {
|
|
3673
|
+
async function emit(result, io) {
|
|
3601
3674
|
if (result.stdout.length > 0) {
|
|
3602
|
-
|
|
3675
|
+
io.writeStdout(result.stdout);
|
|
3603
3676
|
}
|
|
3604
3677
|
if (result.stderr.length > 0) {
|
|
3605
|
-
|
|
3678
|
+
io.writeStderr(result.stderr);
|
|
3606
3679
|
}
|
|
3607
|
-
return
|
|
3680
|
+
return io.exit(result.exitCode);
|
|
3608
3681
|
}
|
|
3609
|
-
function registerConfigCommands(configCmd) {
|
|
3610
|
-
configCmd.command(
|
|
3611
|
-
`Print the resolved configuration as ${DEFAULT_CONFIG_FILE_FORMAT.toUpperCase()} (or ${CONFIG_FILE_FORMAT.JSON.toUpperCase()} with
|
|
3612
|
-
).option(
|
|
3613
|
-
await emit(await showCommand(options, buildDefaultDeps()));
|
|
3682
|
+
function registerConfigCommands(configCmd, invocation) {
|
|
3683
|
+
configCmd.command(CONFIG_CLI.commands.show).description(
|
|
3684
|
+
`Print the resolved configuration as ${DEFAULT_CONFIG_FILE_FORMAT.toUpperCase()} (or ${CONFIG_FILE_FORMAT.JSON.toUpperCase()} with ${CONFIG_CLI.flags.json})`
|
|
3685
|
+
).option(CONFIG_CLI.flags.json, `Output as ${CONFIG_FILE_FORMAT.JSON.toUpperCase()}`).action(async (options) => {
|
|
3686
|
+
await emit(await showCommand(options, buildDefaultDeps(invocation)), invocation.io);
|
|
3614
3687
|
});
|
|
3615
|
-
configCmd.command(
|
|
3616
|
-
await emit(await validateCommand(options, buildDefaultDeps()));
|
|
3688
|
+
configCmd.command(CONFIG_CLI.commands.validate).description(`Verify that ${DEFAULT_CONFIG_FILENAME} passes every registered descriptor's validator`).action(async (options) => {
|
|
3689
|
+
await emit(await validateCommand(options, buildDefaultDeps(invocation)), invocation.io);
|
|
3617
3690
|
});
|
|
3618
|
-
configCmd.command(
|
|
3619
|
-
await emit(await defaultsCommand(options, buildDefaultDeps()));
|
|
3691
|
+
configCmd.command(CONFIG_CLI.commands.defaults).description(`Print each registered descriptor's defaults; ignores ${DEFAULT_CONFIG_FILENAME}`).option(CONFIG_CLI.flags.json, `Output as ${CONFIG_FILE_FORMAT.JSON.toUpperCase()}`).action(async (options) => {
|
|
3692
|
+
await emit(await defaultsCommand(options, buildDefaultDeps(invocation)), invocation.io);
|
|
3620
3693
|
});
|
|
3621
3694
|
}
|
|
3622
3695
|
var configDomain = {
|
|
3623
3696
|
name: "config",
|
|
3624
3697
|
description: "Inspect and validate the resolved spx configuration",
|
|
3625
|
-
register: (
|
|
3626
|
-
const configCmd =
|
|
3627
|
-
registerConfigCommands(configCmd);
|
|
3698
|
+
register: (program, invocation) => {
|
|
3699
|
+
const configCmd = program.command(CONFIG_CLI.commandName).description("Inspect and validate the resolved spx configuration");
|
|
3700
|
+
registerConfigCommands(configCmd, invocation);
|
|
3628
3701
|
}
|
|
3629
3702
|
};
|
|
3630
3703
|
|
|
@@ -3918,8 +3991,48 @@ async function diagnoseCommand(options) {
|
|
|
3918
3991
|
}
|
|
3919
3992
|
|
|
3920
3993
|
// src/commands/diagnose/probes.ts
|
|
3994
|
+
import { dirname as dirname3 } from "path";
|
|
3921
3995
|
import { execa as execa3 } from "execa";
|
|
3922
3996
|
|
|
3997
|
+
// src/domains/diagnose/checks/session-store.ts
|
|
3998
|
+
var SESSION_STORE_VERDICT = {
|
|
3999
|
+
CONSISTENT: "consistent",
|
|
4000
|
+
ORPHANED_CLAIMS: "orphaned-claims",
|
|
4001
|
+
UNKNOWN: "unknown"
|
|
4002
|
+
};
|
|
4003
|
+
function doingSessionBackedByClaim(session, claimedSessionIds2) {
|
|
4004
|
+
if (claimedSessionIds2.has(normalizeAgentSessionToken(session.id))) return true;
|
|
4005
|
+
return session.agent_session_id !== void 0 && claimedSessionIds2.has(normalizeAgentSessionToken(session.agent_session_id));
|
|
4006
|
+
}
|
|
4007
|
+
var REMEDIATION = {
|
|
4008
|
+
[SESSION_STORE_VERDICT.CONSISTENT]: "Session store is consistent; no action needed.",
|
|
4009
|
+
[SESSION_STORE_VERDICT.ORPHANED_CLAIMS]: "Release or reclaim doing sessions whose backing worktree reads free or is absent (spx session release).",
|
|
4010
|
+
[SESSION_STORE_VERDICT.UNKNOWN]: "Re-run diagnose; if it persists, inspect spx session list and spx worktree status."
|
|
4011
|
+
};
|
|
4012
|
+
function record(verdict, bucket, reading) {
|
|
4013
|
+
return {
|
|
4014
|
+
name: CHECK_NAME.SESSION_STORE,
|
|
4015
|
+
verdict,
|
|
4016
|
+
bucket,
|
|
4017
|
+
readings: {
|
|
4018
|
+
orphaned: String(reading.orphanedClaims)
|
|
4019
|
+
},
|
|
4020
|
+
remediation: REMEDIATION[verdict]
|
|
4021
|
+
};
|
|
4022
|
+
}
|
|
4023
|
+
function classifySessionStore(reading) {
|
|
4024
|
+
if (reading.errored) {
|
|
4025
|
+
return record(SESSION_STORE_VERDICT.UNKNOWN, VERDICT_BUCKET.UNKNOWN, reading);
|
|
4026
|
+
}
|
|
4027
|
+
if (reading.orphanedClaims > 0) {
|
|
4028
|
+
return record(SESSION_STORE_VERDICT.ORPHANED_CLAIMS, VERDICT_BUCKET.DEGRADED, reading);
|
|
4029
|
+
}
|
|
4030
|
+
return record(SESSION_STORE_VERDICT.CONSISTENT, VERDICT_BUCKET.HEALTHY, reading);
|
|
4031
|
+
}
|
|
4032
|
+
function sessionStoreRunner(probe) {
|
|
4033
|
+
return async () => classifySessionStore(await probe.probe());
|
|
4034
|
+
}
|
|
4035
|
+
|
|
3923
4036
|
// src/domains/hooks/session-start.ts
|
|
3924
4037
|
var HOOK_SESSION_START_PAYLOAD = {
|
|
3925
4038
|
CWD: "cwd",
|
|
@@ -4117,10 +4230,7 @@ function formatOccupancyError(code, detail) {
|
|
|
4117
4230
|
return `${code}${ERROR_DETAIL_SEPARATOR2}${detail}`;
|
|
4118
4231
|
}
|
|
4119
4232
|
function toErrorMessage2(error) {
|
|
4120
|
-
|
|
4121
|
-
if (typeof error === "string") return error;
|
|
4122
|
-
if (typeof error === "number" || typeof error === "bigint" || typeof error === "boolean") return error.toString();
|
|
4123
|
-
return JSON.stringify(error) ?? "unknown error";
|
|
4233
|
+
return toMessage(error);
|
|
4124
4234
|
}
|
|
4125
4235
|
|
|
4126
4236
|
// src/domains/worktree/worktree-name.ts
|
|
@@ -4273,13 +4383,12 @@ var defaultWorktreePoolProbe = {
|
|
|
4273
4383
|
running: 0,
|
|
4274
4384
|
free: 0
|
|
4275
4385
|
};
|
|
4276
|
-
const root = await detectGitCommonDirProductRoot();
|
|
4277
4386
|
const facts = await gatherGitFacts();
|
|
4278
|
-
if (!
|
|
4387
|
+
if (!facts?.worktreeListRead) return errored;
|
|
4279
4388
|
const bareRepository = facts.commonDirIsBare;
|
|
4280
4389
|
const paths = facts.worktreeRoots;
|
|
4281
4390
|
const linkedWorktrees = !bareRepository && paths.length > 1;
|
|
4282
|
-
const worktreesDir = worktreesScopeDir(
|
|
4391
|
+
const worktreesDir = worktreesScopeDir(dirname3(facts.commonDir));
|
|
4283
4392
|
let running = 0;
|
|
4284
4393
|
let free = 0;
|
|
4285
4394
|
for (const path7 of paths) {
|
|
@@ -4316,11 +4425,9 @@ var defaultSessionEnvironmentProbe = {
|
|
|
4316
4425
|
}
|
|
4317
4426
|
};
|
|
4318
4427
|
async function claimedSessionIds() {
|
|
4319
|
-
const root = await detectGitCommonDirProductRoot();
|
|
4320
|
-
if (!root.isGitRepo) return null;
|
|
4321
|
-
const worktreesDir = worktreesScopeDir(root.productDir);
|
|
4322
4428
|
const facts = await gatherGitFacts();
|
|
4323
4429
|
if (!facts?.worktreeListRead) return null;
|
|
4430
|
+
const worktreesDir = worktreesScopeDir(dirname3(facts.commonDir));
|
|
4324
4431
|
const sessionIds = /* @__PURE__ */ new Set();
|
|
4325
4432
|
for (const path7 of facts.worktreeRoots) {
|
|
4326
4433
|
const claim = await readClaim(worktreesDir, worktreeClaimName(path7), { fs: defaultOccupancyFileSystem });
|
|
@@ -4347,8 +4454,7 @@ var defaultSessionStoreProbe = {
|
|
|
4347
4454
|
}
|
|
4348
4455
|
let orphanedClaims = 0;
|
|
4349
4456
|
for (const session of doing) {
|
|
4350
|
-
|
|
4351
|
-
if (sessionId !== void 0 && !claimed.has(sessionId)) orphanedClaims += 1;
|
|
4457
|
+
if (!doingSessionBackedByClaim(session, claimed)) orphanedClaims += 1;
|
|
4352
4458
|
}
|
|
4353
4459
|
return { errored: false, orphanedClaims };
|
|
4354
4460
|
}
|
|
@@ -4473,14 +4579,14 @@ var MARKETPLACE_INSTALL_VERDICT = {
|
|
|
4473
4579
|
NOT_APPLICABLE: "not-applicable",
|
|
4474
4580
|
UNKNOWN: "unknown"
|
|
4475
4581
|
};
|
|
4476
|
-
var
|
|
4582
|
+
var REMEDIATION2 = {
|
|
4477
4583
|
[MARKETPLACE_INSTALL_VERDICT.INSTALLED]: "Marketplace and expected plugins are installed and enabled; no action needed.",
|
|
4478
4584
|
[MARKETPLACE_INSTALL_VERDICT.DRIFTED]: "Install or enable the expected plugins on the drifted surface.",
|
|
4479
4585
|
[MARKETPLACE_INSTALL_VERDICT.UNREGISTERED]: "Register the methodology marketplace on the present plugin surface.",
|
|
4480
4586
|
[MARKETPLACE_INSTALL_VERDICT.NOT_APPLICABLE]: "No plugin CLI surface present; no action needed.",
|
|
4481
4587
|
[MARKETPLACE_INSTALL_VERDICT.UNKNOWN]: "Re-run diagnose; if it persists, inspect the claude/codex plugin CLI output."
|
|
4482
4588
|
};
|
|
4483
|
-
function
|
|
4589
|
+
function record2(verdict, bucket, reading) {
|
|
4484
4590
|
return {
|
|
4485
4591
|
name: CHECK_NAME.MARKETPLACE_INSTALL,
|
|
4486
4592
|
verdict,
|
|
@@ -4490,23 +4596,23 @@ function record(verdict, bucket, reading) {
|
|
|
4490
4596
|
unregistered: String(reading.unregistered),
|
|
4491
4597
|
drifted: String(reading.drifted)
|
|
4492
4598
|
},
|
|
4493
|
-
remediation:
|
|
4599
|
+
remediation: REMEDIATION2[verdict]
|
|
4494
4600
|
};
|
|
4495
4601
|
}
|
|
4496
4602
|
function classifyMarketplaceInstall(reading) {
|
|
4497
4603
|
if (reading.errored) {
|
|
4498
|
-
return
|
|
4604
|
+
return record2(MARKETPLACE_INSTALL_VERDICT.UNKNOWN, VERDICT_BUCKET.UNKNOWN, reading);
|
|
4499
4605
|
}
|
|
4500
4606
|
if (!reading.surfacePresent) {
|
|
4501
|
-
return
|
|
4607
|
+
return record2(MARKETPLACE_INSTALL_VERDICT.NOT_APPLICABLE, VERDICT_BUCKET.NOT_APPLICABLE, reading);
|
|
4502
4608
|
}
|
|
4503
4609
|
if (reading.unregistered) {
|
|
4504
|
-
return
|
|
4610
|
+
return record2(MARKETPLACE_INSTALL_VERDICT.UNREGISTERED, VERDICT_BUCKET.BROKEN, reading);
|
|
4505
4611
|
}
|
|
4506
4612
|
if (reading.drifted) {
|
|
4507
|
-
return
|
|
4613
|
+
return record2(MARKETPLACE_INSTALL_VERDICT.DRIFTED, VERDICT_BUCKET.DEGRADED, reading);
|
|
4508
4614
|
}
|
|
4509
|
-
return
|
|
4615
|
+
return record2(MARKETPLACE_INSTALL_VERDICT.INSTALLED, VERDICT_BUCKET.HEALTHY, reading);
|
|
4510
4616
|
}
|
|
4511
4617
|
function marketplaceInstallRunner(probe) {
|
|
4512
4618
|
return async (manifest) => {
|
|
@@ -4525,14 +4631,14 @@ var SESSION_ENVIRONMENT_VERDICT = {
|
|
|
4525
4631
|
NOT_APPLICABLE: "not-applicable",
|
|
4526
4632
|
UNKNOWN: "unknown"
|
|
4527
4633
|
};
|
|
4528
|
-
var
|
|
4634
|
+
var REMEDIATION3 = {
|
|
4529
4635
|
[SESSION_ENVIRONMENT_VERDICT.WORKING]: "Session environment is established; no action needed.",
|
|
4530
4636
|
[SESSION_ENVIRONMENT_VERDICT.IDENTITY_ONLY]: "The SessionStart hook set the session identity but did not claim the worktree; check the worktree-claim step.",
|
|
4531
4637
|
[SESSION_ENVIRONMENT_VERDICT.SILENT_NO_OP]: "The SessionStart hook ran without effect; verify the hook resolves spx and the agent session id.",
|
|
4532
|
-
[SESSION_ENVIRONMENT_VERDICT.NOT_APPLICABLE]: "No spec-tree SessionStart hook
|
|
4638
|
+
[SESSION_ENVIRONMENT_VERDICT.NOT_APPLICABLE]: "No SessionStart hook signal or agent session identity was observed; confirm a spec-tree SessionStart hook is configured and enabled.",
|
|
4533
4639
|
[SESSION_ENVIRONMENT_VERDICT.UNKNOWN]: "Re-run diagnose; if it persists, inspect the agent session id and spx worktree status."
|
|
4534
4640
|
};
|
|
4535
|
-
function
|
|
4641
|
+
function record3(verdict, bucket, reading) {
|
|
4536
4642
|
return {
|
|
4537
4643
|
name: CHECK_NAME.SESSION_ENVIRONMENT,
|
|
4538
4644
|
verdict,
|
|
@@ -4542,66 +4648,31 @@ function record2(verdict, bucket, reading) {
|
|
|
4542
4648
|
identity: String(reading.sessionIdentity),
|
|
4543
4649
|
claimed: String(reading.worktreeClaimed)
|
|
4544
4650
|
},
|
|
4545
|
-
remediation:
|
|
4651
|
+
remediation: REMEDIATION3[verdict]
|
|
4546
4652
|
};
|
|
4547
4653
|
}
|
|
4548
4654
|
function classifySessionEnvironment(reading) {
|
|
4549
|
-
if (!reading.hookPresent) {
|
|
4550
|
-
return record2(SESSION_ENVIRONMENT_VERDICT.NOT_APPLICABLE, VERDICT_BUCKET.NOT_APPLICABLE, reading);
|
|
4551
|
-
}
|
|
4552
4655
|
if (reading.errored) {
|
|
4553
|
-
return
|
|
4656
|
+
return record3(SESSION_ENVIRONMENT_VERDICT.UNKNOWN, VERDICT_BUCKET.UNKNOWN, reading);
|
|
4554
4657
|
}
|
|
4555
4658
|
if (reading.sessionIdentity && reading.worktreeClaimed) {
|
|
4556
|
-
return
|
|
4659
|
+
return record3(SESSION_ENVIRONMENT_VERDICT.WORKING, VERDICT_BUCKET.HEALTHY, reading);
|
|
4557
4660
|
}
|
|
4558
4661
|
if (reading.sessionIdentity) {
|
|
4559
|
-
return
|
|
4662
|
+
return record3(SESSION_ENVIRONMENT_VERDICT.IDENTITY_ONLY, VERDICT_BUCKET.DEGRADED, reading);
|
|
4560
4663
|
}
|
|
4561
|
-
if (!reading.worktreeClaimed) {
|
|
4562
|
-
return
|
|
4664
|
+
if (reading.hookPresent && !reading.worktreeClaimed) {
|
|
4665
|
+
return record3(SESSION_ENVIRONMENT_VERDICT.SILENT_NO_OP, VERDICT_BUCKET.BROKEN, reading);
|
|
4563
4666
|
}
|
|
4564
|
-
|
|
4667
|
+
if (!reading.hookPresent && !reading.worktreeClaimed) {
|
|
4668
|
+
return record3(SESSION_ENVIRONMENT_VERDICT.NOT_APPLICABLE, VERDICT_BUCKET.NOT_APPLICABLE, reading);
|
|
4669
|
+
}
|
|
4670
|
+
return record3(SESSION_ENVIRONMENT_VERDICT.UNKNOWN, VERDICT_BUCKET.UNKNOWN, reading);
|
|
4565
4671
|
}
|
|
4566
4672
|
function sessionEnvironmentRunner(probe) {
|
|
4567
4673
|
return async () => classifySessionEnvironment(await probe.probe());
|
|
4568
4674
|
}
|
|
4569
4675
|
|
|
4570
|
-
// src/domains/diagnose/checks/session-store.ts
|
|
4571
|
-
var SESSION_STORE_VERDICT = {
|
|
4572
|
-
CONSISTENT: "consistent",
|
|
4573
|
-
ORPHANED_CLAIMS: "orphaned-claims",
|
|
4574
|
-
UNKNOWN: "unknown"
|
|
4575
|
-
};
|
|
4576
|
-
var REMEDIATION3 = {
|
|
4577
|
-
[SESSION_STORE_VERDICT.CONSISTENT]: "Session store is consistent; no action needed.",
|
|
4578
|
-
[SESSION_STORE_VERDICT.ORPHANED_CLAIMS]: "Release or reclaim doing sessions whose backing worktree reads free or is absent (spx session release).",
|
|
4579
|
-
[SESSION_STORE_VERDICT.UNKNOWN]: "Re-run diagnose; if it persists, inspect spx session list and spx worktree status."
|
|
4580
|
-
};
|
|
4581
|
-
function record3(verdict, bucket, reading) {
|
|
4582
|
-
return {
|
|
4583
|
-
name: CHECK_NAME.SESSION_STORE,
|
|
4584
|
-
verdict,
|
|
4585
|
-
bucket,
|
|
4586
|
-
readings: {
|
|
4587
|
-
orphaned: String(reading.orphanedClaims)
|
|
4588
|
-
},
|
|
4589
|
-
remediation: REMEDIATION3[verdict]
|
|
4590
|
-
};
|
|
4591
|
-
}
|
|
4592
|
-
function classifySessionStore(reading) {
|
|
4593
|
-
if (reading.errored) {
|
|
4594
|
-
return record3(SESSION_STORE_VERDICT.UNKNOWN, VERDICT_BUCKET.UNKNOWN, reading);
|
|
4595
|
-
}
|
|
4596
|
-
if (reading.orphanedClaims > 0) {
|
|
4597
|
-
return record3(SESSION_STORE_VERDICT.ORPHANED_CLAIMS, VERDICT_BUCKET.DEGRADED, reading);
|
|
4598
|
-
}
|
|
4599
|
-
return record3(SESSION_STORE_VERDICT.CONSISTENT, VERDICT_BUCKET.HEALTHY, reading);
|
|
4600
|
-
}
|
|
4601
|
-
function sessionStoreRunner(probe) {
|
|
4602
|
-
return async () => classifySessionStore(await probe.probe());
|
|
4603
|
-
}
|
|
4604
|
-
|
|
4605
4676
|
// src/domains/diagnose/checks/spx-reachability.ts
|
|
4606
4677
|
var SPX_REACHABILITY_VERDICT = {
|
|
4607
4678
|
REACHABLE: "reachable",
|
|
@@ -4617,7 +4688,7 @@ function parseSemver(value) {
|
|
|
4617
4688
|
major: Number(match[1]),
|
|
4618
4689
|
minor: Number(match[2]),
|
|
4619
4690
|
patch: Number(match[3]),
|
|
4620
|
-
prerelease: match
|
|
4691
|
+
prerelease: match.at(4) === void 0 ? null : match.at(4)?.slice(1) ?? null
|
|
4621
4692
|
};
|
|
4622
4693
|
}
|
|
4623
4694
|
function isNumericIdentifier(identifier) {
|
|
@@ -4795,15 +4866,16 @@ var DEFAULT_REGISTRY = {
|
|
|
4795
4866
|
[CHECK_NAME.SESSION_STORE]: sessionStoreRunner(defaultSessionStoreProbe),
|
|
4796
4867
|
[CHECK_NAME.MARKETPLACE_INSTALL]: marketplaceInstallRunner(defaultMarketplaceInstallProbe)
|
|
4797
4868
|
};
|
|
4798
|
-
function handleError(error) {
|
|
4799
|
-
|
|
4800
|
-
|
|
4869
|
+
function handleError(error, io) {
|
|
4870
|
+
io.writeStderr(`Error: ${sanitizeCliArgument(error)}
|
|
4871
|
+
`);
|
|
4872
|
+
return io.exit(1);
|
|
4801
4873
|
}
|
|
4802
4874
|
var diagnoseDomain = {
|
|
4803
4875
|
name: DIAGNOSE_CLI.COMMAND,
|
|
4804
4876
|
description: DIAGNOSE_DOMAIN_DESCRIPTION,
|
|
4805
|
-
register: (
|
|
4806
|
-
|
|
4877
|
+
register: (program, invocation) => {
|
|
4878
|
+
program.command(DIAGNOSE_CLI.COMMAND).description(DIAGNOSE_DOMAIN_DESCRIPTION).option(
|
|
4807
4879
|
`${DIAGNOSE_CLI.MANIFEST_FLAG} <path>`,
|
|
4808
4880
|
"Path to a declarative diagnose manifest that fully instruments the diagnosis"
|
|
4809
4881
|
).addOption(
|
|
@@ -4811,7 +4883,7 @@ var diagnoseDomain = {
|
|
|
4811
4883
|
).addOption(new Option(`${DIAGNOSE_CLI.COLOR_FLAG}`, "Force colored output")).addOption(new Option(`${DIAGNOSE_CLI.NO_COLOR_FLAG}`, "Disable colored output")).action(async (options) => {
|
|
4812
4884
|
const result = await diagnoseCommand({
|
|
4813
4885
|
manifestPath: options.manifest,
|
|
4814
|
-
productDir:
|
|
4886
|
+
productDir: invocation.resolveProductContext().productDir,
|
|
4815
4887
|
format: options.format,
|
|
4816
4888
|
color: resolveColorChoice({
|
|
4817
4889
|
flag: options.color,
|
|
@@ -4821,9 +4893,12 @@ var diagnoseDomain = {
|
|
|
4821
4893
|
registry: DEFAULT_REGISTRY,
|
|
4822
4894
|
fs: { readFile: (path7) => readFile3(path7, "utf8") }
|
|
4823
4895
|
});
|
|
4824
|
-
if (!result.ok)
|
|
4825
|
-
|
|
4826
|
-
|
|
4896
|
+
if (!result.ok) {
|
|
4897
|
+
handleError(result.error, invocation.io);
|
|
4898
|
+
}
|
|
4899
|
+
invocation.io.writeStdout(`${result.value.output}
|
|
4900
|
+
`);
|
|
4901
|
+
invocation.io.setExitCode(result.value.exitCode);
|
|
4827
4902
|
});
|
|
4828
4903
|
}
|
|
4829
4904
|
};
|
|
@@ -4877,7 +4952,7 @@ function isValidPid(pid) {
|
|
|
4877
4952
|
}
|
|
4878
4953
|
|
|
4879
4954
|
// src/domains/worktree/resolve.ts
|
|
4880
|
-
import { dirname as
|
|
4955
|
+
import { dirname as dirname4, resolve as resolve4 } from "path";
|
|
4881
4956
|
var WORKTREE_RESOLVE_ERROR = {
|
|
4882
4957
|
NOT_A_WORKTREE: "path resolves to no worktree"
|
|
4883
4958
|
};
|
|
@@ -4893,8 +4968,8 @@ async function resolveCurrentWorktreeName(options) {
|
|
|
4893
4968
|
}
|
|
4894
4969
|
async function resolveTargetWorktree(options) {
|
|
4895
4970
|
const base = options.cwd;
|
|
4896
|
-
const targetPath = options.worktree === void 0 ? base :
|
|
4897
|
-
const targetGitPath = await options.pathInfo.isExistingNonDirectory(targetPath) ?
|
|
4971
|
+
const targetPath = options.worktree === void 0 ? base : resolve4(base, options.worktree);
|
|
4972
|
+
const targetGitPath = await options.pathInfo.isExistingNonDirectory(targetPath) ? dirname4(targetPath) : targetPath;
|
|
4898
4973
|
const worktree = await detectWorktreeProductRoot(targetGitPath, options.gitDeps);
|
|
4899
4974
|
if (!worktree.isGitRepo) {
|
|
4900
4975
|
return { ok: false, error: `${WORKTREE_RESOLVE_ERROR.NOT_A_WORKTREE}: ${options.worktree ?? base}` };
|
|
@@ -5060,17 +5135,17 @@ function createProcessHookIo(streams) {
|
|
|
5060
5135
|
return {
|
|
5061
5136
|
readStdin: async () => {
|
|
5062
5137
|
if (streams.stdin.isTTY) return { ok: true, value: void 0 };
|
|
5063
|
-
return new Promise((
|
|
5138
|
+
return new Promise((resolve10) => {
|
|
5064
5139
|
let data = "";
|
|
5065
5140
|
streams.stdin.setEncoding("utf-8");
|
|
5066
5141
|
streams.stdin.on(HOOK_PROCESS_IO_EVENT.DATA, (chunk) => {
|
|
5067
5142
|
data += chunk;
|
|
5068
5143
|
});
|
|
5069
5144
|
streams.stdin.on(HOOK_PROCESS_IO_EVENT.END, () => {
|
|
5070
|
-
|
|
5145
|
+
resolve10({ ok: true, value: data.length === 0 ? void 0 : data });
|
|
5071
5146
|
});
|
|
5072
5147
|
streams.stdin.on(HOOK_PROCESS_IO_EVENT.ERROR, (error) => {
|
|
5073
|
-
|
|
5148
|
+
resolve10({ ok: false, error: formatStdinReadError(error) });
|
|
5074
5149
|
});
|
|
5075
5150
|
});
|
|
5076
5151
|
},
|
|
@@ -5089,7 +5164,8 @@ function describeStdinReadError(error) {
|
|
|
5089
5164
|
if (error instanceof Error) return error.message;
|
|
5090
5165
|
if (typeof error === "string") return error;
|
|
5091
5166
|
try {
|
|
5092
|
-
|
|
5167
|
+
const serialized = JSON.stringify(error);
|
|
5168
|
+
return typeof serialized === "string" ? serialized : `${typeof error}`;
|
|
5093
5169
|
} catch {
|
|
5094
5170
|
return `${typeof error}`;
|
|
5095
5171
|
}
|
|
@@ -5116,31 +5192,41 @@ var HOOK_CLI = {
|
|
|
5116
5192
|
WORKTREES_DIR_FLAG: "--worktrees-dir"
|
|
5117
5193
|
};
|
|
5118
5194
|
var HOOK_DOMAIN_DESCRIPTION = "Run host lifecycle hook events";
|
|
5119
|
-
function
|
|
5195
|
+
function writeError2(invocation, output) {
|
|
5196
|
+
invocation.io.writeStderr(`${output}
|
|
5197
|
+
`);
|
|
5198
|
+
}
|
|
5199
|
+
function writeInvocationWarning(invocation, warning) {
|
|
5200
|
+
if (warning !== void 0) {
|
|
5201
|
+
writeError2(invocation, warning);
|
|
5202
|
+
}
|
|
5203
|
+
}
|
|
5204
|
+
function registerHookCommands(hookCmd, invocation) {
|
|
5205
|
+
const effectiveInvocationDir = () => invocation.resolveEffectiveInvocationDir();
|
|
5120
5206
|
hookCmd.command(`${HOOK_CLI.RUN} ${HOOK_CLI.EVENT_ARGUMENT}`).description("Run a hook lifecycle event").option(`${HOOK_CLI.ENV_FILE_FLAG} <path>`, "Hook env file to append; defaults to $CLAUDE_ENV_FILE").option(`${HOOK_CLI.WORKTREES_DIR_FLAG} <path>`, "Explicit .spx/worktrees directory").action(async (event, options) => {
|
|
5121
5207
|
const result = await runHookCli({
|
|
5122
5208
|
claimWriteToken: createClaimWriteToken(),
|
|
5123
|
-
cwd:
|
|
5209
|
+
cwd: effectiveInvocationDir(),
|
|
5124
5210
|
env: process.env,
|
|
5125
5211
|
envFile: options.hookEnvFile,
|
|
5126
5212
|
event,
|
|
5127
5213
|
fs: defaultOccupancyFileSystem,
|
|
5128
5214
|
gitDeps: defaultGitDependencies,
|
|
5129
5215
|
io: processHookIo,
|
|
5130
|
-
onWarning:
|
|
5216
|
+
onWarning: (warning) => writeInvocationWarning(invocation, warning),
|
|
5131
5217
|
processTable: defaultProcessTable,
|
|
5132
5218
|
selfPid: process.pid,
|
|
5133
5219
|
worktreesDir: options.worktreesDir
|
|
5134
5220
|
});
|
|
5135
|
-
if (!result.ok)
|
|
5221
|
+
if (!result.ok) invocation.io.exit(1);
|
|
5136
5222
|
});
|
|
5137
5223
|
}
|
|
5138
5224
|
var hookDomain = {
|
|
5139
5225
|
name: HOOK_CLI.COMMAND,
|
|
5140
5226
|
description: HOOK_DOMAIN_DESCRIPTION,
|
|
5141
|
-
register: (
|
|
5142
|
-
const hookCmd =
|
|
5143
|
-
registerHookCommands(hookCmd);
|
|
5227
|
+
register: (program, invocation) => {
|
|
5228
|
+
const hookCmd = program.command(HOOK_CLI.COMMAND).description(HOOK_DOMAIN_DESCRIPTION);
|
|
5229
|
+
registerHookCommands(hookCmd, invocation);
|
|
5144
5230
|
}
|
|
5145
5231
|
};
|
|
5146
5232
|
|
|
@@ -5175,6 +5261,14 @@ function resolveJournalBackend(env) {
|
|
|
5175
5261
|
return { ok: true, value: JOURNAL_BACKEND.LOCAL };
|
|
5176
5262
|
}
|
|
5177
5263
|
|
|
5264
|
+
// src/lib/verification-env.ts
|
|
5265
|
+
var SPX_VERIFY_ENV = {
|
|
5266
|
+
BRANCH: "SPX_VERIFY_BRANCH"
|
|
5267
|
+
};
|
|
5268
|
+
var SPX_VERIFY_HEAD_SHA = {
|
|
5269
|
+
MISSING: "unknown"
|
|
5270
|
+
};
|
|
5271
|
+
|
|
5178
5272
|
// src/lib/agent-run-journal/index.ts
|
|
5179
5273
|
var CLOUDEVENTS_SPECVERSION = "1.0";
|
|
5180
5274
|
var JOURNAL_SEQ_BASE = 1;
|
|
@@ -5462,7 +5556,7 @@ function journalRunFilePath(scope2) {
|
|
|
5462
5556
|
}
|
|
5463
5557
|
|
|
5464
5558
|
// src/lib/appendable-journal-store/index.ts
|
|
5465
|
-
import { dirname as
|
|
5559
|
+
import { dirname as dirname5 } from "path";
|
|
5466
5560
|
var SEAL_MARKER_SUFFIX = ".sealed";
|
|
5467
5561
|
var LINE_SEPARATOR3 = "\n";
|
|
5468
5562
|
function appendableJournalSealMarkerPath(runFilePath) {
|
|
@@ -5502,7 +5596,7 @@ function createAppendableJournalStore(options) {
|
|
|
5502
5596
|
},
|
|
5503
5597
|
readAll,
|
|
5504
5598
|
async seal() {
|
|
5505
|
-
await fs8.mkdir(
|
|
5599
|
+
await fs8.mkdir(dirname5(sealMarkerPath), { recursive: true });
|
|
5506
5600
|
await fs8.writeFile(sealMarkerPath, "");
|
|
5507
5601
|
},
|
|
5508
5602
|
async isSealed() {
|
|
@@ -5643,7 +5737,7 @@ var JOURNAL_CLI_EXIT_CODE = {
|
|
|
5643
5737
|
};
|
|
5644
5738
|
var JOURNAL_CLI_ENV = {
|
|
5645
5739
|
BACKEND: "SPX_VERIFY_BACKEND",
|
|
5646
|
-
BRANCH:
|
|
5740
|
+
BRANCH: SPX_VERIFY_ENV.BRANCH,
|
|
5647
5741
|
CONTINUOUS_INTEGRATION: "CI",
|
|
5648
5742
|
GITHUB_EVENT_NAME: "GITHUB_EVENT_NAME",
|
|
5649
5743
|
GITHUB_REF: "GITHUB_REF",
|
|
@@ -5656,7 +5750,6 @@ var JOURNAL_CLI_ERROR = {
|
|
|
5656
5750
|
INVALID_EVENT_INPUT: "journal append event input is missing a required CloudEvents field",
|
|
5657
5751
|
INVALID_CURSOR: "journal read cursor must be a whole non-negative integer"
|
|
5658
5752
|
};
|
|
5659
|
-
var MISSING_HEAD_SHA_FALLBACK = "unknown";
|
|
5660
5753
|
var CURSOR_PATTERN = /^\d+$/;
|
|
5661
5754
|
var JOURNAL_EVENT_INPUT_STRING_FIELDS = ["id", "source", "type", "time"];
|
|
5662
5755
|
var JOURNAL_COMMENT_MARKER_PREFIX = "spx-journal-run:";
|
|
@@ -5684,7 +5777,7 @@ function readJournalCliEnvironment(processEnv) {
|
|
|
5684
5777
|
};
|
|
5685
5778
|
}
|
|
5686
5779
|
async function resolveJournalRunContext(scope2, deps) {
|
|
5687
|
-
const cwd = deps.cwd ??
|
|
5780
|
+
const cwd = deps.cwd ?? CONFIG_PROCESS_CWD.read();
|
|
5688
5781
|
const git = deps.git ?? defaultGitDependencies;
|
|
5689
5782
|
const cliEnvironment = readJournalCliEnvironment(deps.processEnv ?? process.env);
|
|
5690
5783
|
const environment = deps.env ?? cliEnvironment.backend;
|
|
@@ -5693,7 +5786,7 @@ async function resolveJournalRunContext(scope2, deps) {
|
|
|
5693
5786
|
const product = await detectGitCommonDirProductRoot(cwd, git);
|
|
5694
5787
|
const probedBranch = product.isGitRepo ? await getCurrentBranch(cwd, git) ?? void 0 : void 0;
|
|
5695
5788
|
const branchName = deps.branch ?? cliEnvironment.branch ?? probedBranch;
|
|
5696
|
-
const headSha = (product.isGitRepo ? await getHeadSha(cwd, git) : null) ??
|
|
5789
|
+
const headSha = (product.isGitRepo ? await getHeadSha(cwd, git) : null) ?? SPX_VERIFY_HEAD_SHA.MISSING;
|
|
5697
5790
|
const branchIdentity = resolveBranchIdentity({ ...branchName === void 0 ? {} : { branchName }, headSha });
|
|
5698
5791
|
return {
|
|
5699
5792
|
ok: true,
|
|
@@ -5827,284 +5920,269 @@ async function journalRenderCommand(scope2, deps = {}) {
|
|
|
5827
5920
|
return okResult(JSON.stringify(rendered.value));
|
|
5828
5921
|
}
|
|
5829
5922
|
|
|
5830
|
-
// src/
|
|
5831
|
-
var
|
|
5832
|
-
|
|
5833
|
-
|
|
5834
|
-
|
|
5835
|
-
|
|
5836
|
-
|
|
5837
|
-
|
|
5838
|
-
|
|
5923
|
+
// src/interfaces/cli/journal.ts
|
|
5924
|
+
var JOURNAL_CLI = {
|
|
5925
|
+
commandName: "journal",
|
|
5926
|
+
description: "Record and stream an agentic verification run journal",
|
|
5927
|
+
openCommandName: "open",
|
|
5928
|
+
appendCommandName: "append",
|
|
5929
|
+
readCommandName: "read",
|
|
5930
|
+
sealCommandName: "seal",
|
|
5931
|
+
renderCommandName: "render",
|
|
5932
|
+
typeOption: "--type <type>",
|
|
5933
|
+
runOption: "--run <token>",
|
|
5934
|
+
fromOption: "--from <cursor>"
|
|
5935
|
+
};
|
|
5936
|
+
var STREAM_LINE_SEPARATOR = "\n";
|
|
5937
|
+
var MALFORMED_EVENT_INPUT_ERROR = "journal append event input is not valid JSON";
|
|
5938
|
+
function scope(options) {
|
|
5939
|
+
return { type: options.type };
|
|
5940
|
+
}
|
|
5941
|
+
function runScope(options) {
|
|
5942
|
+
return { ...scope(options), runToken: options.run };
|
|
5943
|
+
}
|
|
5944
|
+
var journalDomain = {
|
|
5945
|
+
name: JOURNAL_CLI.commandName,
|
|
5946
|
+
description: JOURNAL_CLI.description,
|
|
5947
|
+
register: (program, invocation) => {
|
|
5948
|
+
const journalDeps = () => ({ cwd: invocation.resolveEffectiveInvocationDir() });
|
|
5949
|
+
const journalCmd = program.command(JOURNAL_CLI.commandName).description(JOURNAL_CLI.description);
|
|
5950
|
+
journalCmd.command(JOURNAL_CLI.openCommandName).description("Open a new run journal and report its run token").requiredOption(JOURNAL_CLI.typeOption, "Opaque verification-type scope segment").action(async (options) => {
|
|
5951
|
+
report(await journalOpenCommand(scope(options), journalDeps()), invocation.io);
|
|
5952
|
+
});
|
|
5953
|
+
journalCmd.command(JOURNAL_CLI.appendCommandName).description("Append a JSON event read from standard input and stream it").requiredOption(JOURNAL_CLI.typeOption, "Opaque verification-type scope segment").requiredOption(JOURNAL_CLI.runOption, "Run token reported by open").action(async (options) => {
|
|
5954
|
+
const input = await readStdinEventInput();
|
|
5955
|
+
if (!input.ok) {
|
|
5956
|
+
report({ exitCode: JOURNAL_CLI_EXIT_CODE.ERROR, output: input.error }, invocation.io);
|
|
5957
|
+
return;
|
|
5958
|
+
}
|
|
5959
|
+
const result = await journalAppendCommand(
|
|
5960
|
+
runScope(options),
|
|
5961
|
+
input.value,
|
|
5962
|
+
streamBinding(invocation.io),
|
|
5963
|
+
journalDeps()
|
|
5964
|
+
);
|
|
5965
|
+
if (result.exitCode === JOURNAL_CLI_EXIT_CODE.OK) invocation.io.setExitCode(JOURNAL_CLI_EXIT_CODE.OK);
|
|
5966
|
+
else report(result, invocation.io);
|
|
5967
|
+
});
|
|
5968
|
+
journalCmd.command(JOURNAL_CLI.readCommandName).description("Read the run's events at or after a cursor").requiredOption(JOURNAL_CLI.typeOption, "Opaque verification-type scope segment").requiredOption(JOURNAL_CLI.runOption, "Run token reported by open").requiredOption(JOURNAL_CLI.fromOption, "Sequence cursor; events at or after it are returned").action(async (options) => {
|
|
5969
|
+
report(await journalReadCommand(runScope(options), options.from, journalDeps()), invocation.io);
|
|
5970
|
+
});
|
|
5971
|
+
journalCmd.command(JOURNAL_CLI.sealCommandName).description("Seal the run journal so further appends are rejected").requiredOption(JOURNAL_CLI.typeOption, "Opaque verification-type scope segment").requiredOption(JOURNAL_CLI.runOption, "Run token reported by open").action(async (options) => {
|
|
5972
|
+
report(await journalSealCommand(runScope(options), journalDeps()), invocation.io);
|
|
5973
|
+
});
|
|
5974
|
+
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").action(async (options) => {
|
|
5975
|
+
report(await journalRenderCommand(runScope(options), journalDeps()), invocation.io);
|
|
5976
|
+
});
|
|
5977
|
+
}
|
|
5978
|
+
};
|
|
5979
|
+
function stdoutStreamSink(io) {
|
|
5839
5980
|
return {
|
|
5840
|
-
|
|
5841
|
-
|
|
5842
|
-
};
|
|
5843
|
-
const restorers = FOREGROUND_SIGNALS.map((signal) => {
|
|
5844
|
-
const originals = target.listeners(signal);
|
|
5845
|
-
for (const listener of originals) target.removeListener(signal, listener);
|
|
5846
|
-
target.on(signal, ignore);
|
|
5847
|
-
return () => {
|
|
5848
|
-
target.removeListener(signal, ignore);
|
|
5849
|
-
for (const listener of originals) target.on(signal, listener);
|
|
5850
|
-
};
|
|
5851
|
-
});
|
|
5852
|
-
return () => {
|
|
5853
|
-
for (const restore of restorers) restore();
|
|
5854
|
-
};
|
|
5981
|
+
async emit(event) {
|
|
5982
|
+
io.writeStdout(`${JSON.stringify(event)}${STREAM_LINE_SEPARATOR}`);
|
|
5855
5983
|
}
|
|
5856
5984
|
};
|
|
5857
5985
|
}
|
|
5986
|
+
function streamBinding(io) {
|
|
5987
|
+
const repository = process.env[JOURNAL_CLI_ENV.GITHUB_REPOSITORY] ?? "";
|
|
5988
|
+
return {
|
|
5989
|
+
localSink: stdoutStreamSink(io),
|
|
5990
|
+
githubClient: createGithubPullRequestCommentClient({ repository, run: runGhApi }),
|
|
5991
|
+
githubRepository: repository
|
|
5992
|
+
};
|
|
5993
|
+
}
|
|
5994
|
+
async function readStdinEventInput() {
|
|
5995
|
+
const chunks = [];
|
|
5996
|
+
for await (const chunk of process.stdin) {
|
|
5997
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
5998
|
+
}
|
|
5999
|
+
try {
|
|
6000
|
+
return { ok: true, value: JSON.parse(Buffer.concat(chunks).toString("utf8")) };
|
|
6001
|
+
} catch {
|
|
6002
|
+
return { ok: false, error: MALFORMED_EVENT_INPUT_ERROR };
|
|
6003
|
+
}
|
|
6004
|
+
}
|
|
6005
|
+
function report(result, io) {
|
|
6006
|
+
const output = `${result.output}${STREAM_LINE_SEPARATOR}`;
|
|
6007
|
+
if (result.exitCode === JOURNAL_CLI_EXIT_CODE.OK) io.writeStdout(output);
|
|
6008
|
+
else io.writeStderr(output);
|
|
6009
|
+
io.setExitCode(result.exitCode);
|
|
6010
|
+
}
|
|
5858
6011
|
|
|
5859
|
-
// src/
|
|
5860
|
-
var
|
|
5861
|
-
|
|
5862
|
-
|
|
5863
|
-
|
|
5864
|
-
|
|
5865
|
-
|
|
5866
|
-
if (!child.killed) child.kill(signal);
|
|
5867
|
-
});
|
|
5868
|
-
}
|
|
5869
|
-
function exitOnce(code, killSignal) {
|
|
5870
|
-
if (cleanupOnce) {
|
|
5871
|
-
deps.exitController.exit(code);
|
|
5872
|
-
return;
|
|
5873
|
-
}
|
|
5874
|
-
cleanupOnce = true;
|
|
5875
|
-
if (killSignal !== void 0) killEachChild(killSignal);
|
|
5876
|
-
deps.exitController.exit(code);
|
|
5877
|
-
}
|
|
5878
|
-
return {
|
|
5879
|
-
onSigint() {
|
|
5880
|
-
exitOnce(SIGINT_EXIT_CODE, SIGINT_NAME);
|
|
6012
|
+
// src/interfaces/cli/session/definition.ts
|
|
6013
|
+
var sessionCliDefinition = {
|
|
6014
|
+
domain: { commandName: "session", description: "Manage session workflow" },
|
|
6015
|
+
subcommands: {
|
|
6016
|
+
list: {
|
|
6017
|
+
commandName: "list",
|
|
6018
|
+
description: "List active sessions (doing + todo by default)"
|
|
5881
6019
|
},
|
|
5882
|
-
|
|
5883
|
-
|
|
6020
|
+
pick: {
|
|
6021
|
+
commandName: "pick",
|
|
6022
|
+
description: "Interactively pick a session and launch claude or codex to resume it"
|
|
5884
6023
|
},
|
|
5885
|
-
|
|
5886
|
-
|
|
6024
|
+
todo: {
|
|
6025
|
+
commandName: "todo",
|
|
6026
|
+
description: "List todo sessions"
|
|
5887
6027
|
},
|
|
5888
|
-
|
|
5889
|
-
|
|
5890
|
-
|
|
5891
|
-
|
|
5892
|
-
}
|
|
5893
|
-
|
|
5894
|
-
// src/lib/process-lifecycle/install.ts
|
|
5895
|
-
import { spawn } from "child_process";
|
|
5896
|
-
|
|
5897
|
-
// src/lib/process-lifecycle/registry.ts
|
|
5898
|
-
function createRegistry() {
|
|
5899
|
-
const tracked = /* @__PURE__ */ new Set();
|
|
5900
|
-
return {
|
|
5901
|
-
add(child) {
|
|
5902
|
-
tracked.add(child);
|
|
6028
|
+
show: {
|
|
6029
|
+
commandName: "show",
|
|
6030
|
+
operand: "<id...>",
|
|
6031
|
+
description: "Show session content"
|
|
5903
6032
|
},
|
|
5904
|
-
|
|
5905
|
-
|
|
6033
|
+
pickup: {
|
|
6034
|
+
commandName: "pickup",
|
|
6035
|
+
operand: "[ids...]",
|
|
6036
|
+
description: "Claim one or more sessions (move from todo to doing)"
|
|
5906
6037
|
},
|
|
5907
|
-
|
|
5908
|
-
|
|
6038
|
+
release: {
|
|
6039
|
+
commandName: "release",
|
|
6040
|
+
operand: "[ids...]",
|
|
6041
|
+
description: "Release one or more sessions (move from doing to todo)"
|
|
5909
6042
|
},
|
|
5910
|
-
|
|
5911
|
-
|
|
5912
|
-
|
|
5913
|
-
|
|
5914
|
-
|
|
5915
|
-
|
|
5916
|
-
|
|
5917
|
-
|
|
5918
|
-
|
|
5919
|
-
|
|
5920
|
-
|
|
5921
|
-
|
|
5922
|
-
|
|
5923
|
-
|
|
6043
|
+
handoff: {
|
|
6044
|
+
commandName: "handoff",
|
|
6045
|
+
description: "Create a handoff session (reads JSON header + body from stdin)"
|
|
6046
|
+
},
|
|
6047
|
+
delete: {
|
|
6048
|
+
commandName: "delete",
|
|
6049
|
+
operand: "<id...>",
|
|
6050
|
+
description: "Delete one or more sessions"
|
|
6051
|
+
},
|
|
6052
|
+
prune: {
|
|
6053
|
+
commandName: "prune",
|
|
6054
|
+
description: "Remove old todo sessions, keeping the most recent N"
|
|
6055
|
+
},
|
|
6056
|
+
archive: {
|
|
6057
|
+
commandName: "archive",
|
|
6058
|
+
operand: "<id...>",
|
|
6059
|
+
description: "Move one or more sessions to the archive directory"
|
|
5924
6060
|
}
|
|
5925
|
-
};
|
|
5926
|
-
}
|
|
5927
|
-
|
|
5928
|
-
// src/lib/process-lifecycle/install.ts
|
|
5929
|
-
var moduleRegistry = createRegistry();
|
|
5930
|
-
var moduleExitController = {
|
|
5931
|
-
exit(code) {
|
|
5932
|
-
process.exit(code);
|
|
5933
|
-
}
|
|
5934
|
-
};
|
|
5935
|
-
var installed = false;
|
|
5936
|
-
var lifecycleProcessRunner = createLifecycleRunner({
|
|
5937
|
-
registry: moduleRegistry,
|
|
5938
|
-
spawn
|
|
5939
|
-
});
|
|
5940
|
-
var foregroundProcessRunner = { spawn };
|
|
5941
|
-
var processSignalTarget = {
|
|
5942
|
-
listeners: (signal) => process.listeners(signal),
|
|
5943
|
-
on: (signal, listener) => {
|
|
5944
|
-
process.on(signal, listener);
|
|
5945
6061
|
},
|
|
5946
|
-
|
|
5947
|
-
|
|
5948
|
-
|
|
5949
|
-
|
|
5950
|
-
|
|
5951
|
-
|
|
5952
|
-
|
|
5953
|
-
|
|
5954
|
-
|
|
5955
|
-
|
|
5956
|
-
|
|
5957
|
-
|
|
5958
|
-
|
|
5959
|
-
|
|
5960
|
-
|
|
5961
|
-
|
|
5962
|
-
|
|
5963
|
-
|
|
5964
|
-
|
|
5965
|
-
|
|
5966
|
-
|
|
5967
|
-
|
|
5968
|
-
|
|
5969
|
-
|
|
5970
|
-
|
|
5971
|
-
|
|
5972
|
-
|
|
5973
|
-
|
|
5974
|
-
|
|
5975
|
-
|
|
5976
|
-
|
|
5977
|
-
|
|
5978
|
-
|
|
5979
|
-
|
|
5980
|
-
|
|
5981
|
-
|
|
5982
|
-
|
|
5983
|
-
|
|
5984
|
-
|
|
5985
|
-
|
|
5986
|
-
|
|
5987
|
-
}
|
|
5988
|
-
|
|
5989
|
-
|
|
5990
|
-
|
|
5991
|
-
|
|
5992
|
-
|
|
5993
|
-
|
|
5994
|
-
|
|
5995
|
-
|
|
5996
|
-
|
|
5997
|
-
|
|
5998
|
-
|
|
5999
|
-
|
|
6000
|
-
|
|
6001
|
-
|
|
6002
|
-
|
|
6003
|
-
|
|
6004
|
-
|
|
6005
|
-
}
|
|
6006
|
-
|
|
6007
|
-
|
|
6008
|
-
|
|
6009
|
-
|
|
6010
|
-
|
|
6011
|
-
|
|
6012
|
-
|
|
6013
|
-
|
|
6014
|
-
|
|
6015
|
-
|
|
6016
|
-
|
|
6017
|
-
|
|
6018
|
-
|
|
6019
|
-
|
|
6020
|
-
|
|
6021
|
-
|
|
6022
|
-
|
|
6023
|
-
|
|
6024
|
-
|
|
6025
|
-
|
|
6026
|
-
|
|
6027
|
-
|
|
6028
|
-
|
|
6029
|
-
|
|
6030
|
-
|
|
6031
|
-
|
|
6032
|
-
|
|
6033
|
-
|
|
6034
|
-
|
|
6035
|
-
|
|
6036
|
-
|
|
6037
|
-
|
|
6038
|
-
|
|
6039
|
-
|
|
6040
|
-
|
|
6041
|
-
|
|
6042
|
-
|
|
6043
|
-
|
|
6044
|
-
|
|
6045
|
-
|
|
6046
|
-
|
|
6047
|
-
|
|
6048
|
-
|
|
6049
|
-
|
|
6050
|
-
|
|
6051
|
-
|
|
6052
|
-
|
|
6053
|
-
|
|
6054
|
-
|
|
6055
|
-
});
|
|
6056
|
-
}
|
|
6057
|
-
};
|
|
6058
|
-
function stdoutStreamSink() {
|
|
6059
|
-
return {
|
|
6060
|
-
async emit(event) {
|
|
6061
|
-
await writeOutput(process.stdout, `${JSON.stringify(event)}${STREAM_LINE_SEPARATOR}`);
|
|
6062
|
-
}
|
|
6063
|
-
};
|
|
6064
|
-
}
|
|
6065
|
-
function streamBinding() {
|
|
6066
|
-
const repository = process.env[JOURNAL_CLI_ENV.GITHUB_REPOSITORY] ?? "";
|
|
6067
|
-
return {
|
|
6068
|
-
localSink: stdoutStreamSink(),
|
|
6069
|
-
githubClient: createGithubPullRequestCommentClient({ repository, run: runGhApi }),
|
|
6070
|
-
githubRepository: repository
|
|
6071
|
-
};
|
|
6072
|
-
}
|
|
6073
|
-
async function readStdinEventInput() {
|
|
6074
|
-
const chunks = [];
|
|
6075
|
-
for await (const chunk of process.stdin) {
|
|
6076
|
-
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
6077
|
-
}
|
|
6078
|
-
try {
|
|
6079
|
-
return { ok: true, value: JSON.parse(Buffer.concat(chunks).toString("utf8")) };
|
|
6080
|
-
} catch {
|
|
6081
|
-
return { ok: false, error: MALFORMED_EVENT_INPUT_ERROR };
|
|
6062
|
+
options: {
|
|
6063
|
+
status: {
|
|
6064
|
+
flag: "--status",
|
|
6065
|
+
placeholder: "<status>",
|
|
6066
|
+
description: "Filter by status (todo|doing|archive); defaults to doing + todo"
|
|
6067
|
+
},
|
|
6068
|
+
json: {
|
|
6069
|
+
flag: "--json",
|
|
6070
|
+
description: "Output as JSON"
|
|
6071
|
+
},
|
|
6072
|
+
fields: {
|
|
6073
|
+
flag: "--fields",
|
|
6074
|
+
placeholder: "<fields>",
|
|
6075
|
+
description: "Comma-separated fields to emit as JSON (implies --json)"
|
|
6076
|
+
},
|
|
6077
|
+
color: {
|
|
6078
|
+
flag: "--color",
|
|
6079
|
+
description: "Force colored text output"
|
|
6080
|
+
},
|
|
6081
|
+
noColor: {
|
|
6082
|
+
flag: "--no-color",
|
|
6083
|
+
description: "Disable colored text output"
|
|
6084
|
+
},
|
|
6085
|
+
sessionsDir: {
|
|
6086
|
+
flag: "--sessions-dir",
|
|
6087
|
+
placeholder: "<path>",
|
|
6088
|
+
description: "Custom sessions directory"
|
|
6089
|
+
},
|
|
6090
|
+
auto: {
|
|
6091
|
+
flag: "--auto",
|
|
6092
|
+
description: "Auto-select highest priority session"
|
|
6093
|
+
},
|
|
6094
|
+
noInject: {
|
|
6095
|
+
flag: "--no-inject",
|
|
6096
|
+
description: "Skip printing files listed in session specs/files metadata"
|
|
6097
|
+
},
|
|
6098
|
+
keep: {
|
|
6099
|
+
flag: "--keep",
|
|
6100
|
+
placeholder: "<count>",
|
|
6101
|
+
description: "Number of sessions to keep (default: 5)",
|
|
6102
|
+
defaultValue: "5"
|
|
6103
|
+
},
|
|
6104
|
+
dryRun: {
|
|
6105
|
+
flag: "--dry-run",
|
|
6106
|
+
description: "Show what would be deleted without deleting"
|
|
6107
|
+
}
|
|
6108
|
+
}
|
|
6109
|
+
};
|
|
6110
|
+
var sessionSubcommandOptions = [
|
|
6111
|
+
{
|
|
6112
|
+
subcommand: sessionCliDefinition.subcommands.list,
|
|
6113
|
+
options: [
|
|
6114
|
+
sessionCliDefinition.options.status,
|
|
6115
|
+
sessionCliDefinition.options.json,
|
|
6116
|
+
sessionCliDefinition.options.fields,
|
|
6117
|
+
sessionCliDefinition.options.color,
|
|
6118
|
+
sessionCliDefinition.options.noColor,
|
|
6119
|
+
sessionCliDefinition.options.sessionsDir
|
|
6120
|
+
]
|
|
6121
|
+
},
|
|
6122
|
+
{
|
|
6123
|
+
subcommand: sessionCliDefinition.subcommands.pick,
|
|
6124
|
+
options: [sessionCliDefinition.options.sessionsDir]
|
|
6125
|
+
},
|
|
6126
|
+
{
|
|
6127
|
+
subcommand: sessionCliDefinition.subcommands.todo,
|
|
6128
|
+
options: [
|
|
6129
|
+
sessionCliDefinition.options.json,
|
|
6130
|
+
sessionCliDefinition.options.fields,
|
|
6131
|
+
sessionCliDefinition.options.color,
|
|
6132
|
+
sessionCliDefinition.options.noColor,
|
|
6133
|
+
sessionCliDefinition.options.sessionsDir
|
|
6134
|
+
]
|
|
6135
|
+
},
|
|
6136
|
+
{
|
|
6137
|
+
subcommand: sessionCliDefinition.subcommands.show,
|
|
6138
|
+
options: [sessionCliDefinition.options.json, sessionCliDefinition.options.sessionsDir]
|
|
6139
|
+
},
|
|
6140
|
+
{
|
|
6141
|
+
subcommand: sessionCliDefinition.subcommands.pickup,
|
|
6142
|
+
options: [
|
|
6143
|
+
sessionCliDefinition.options.auto,
|
|
6144
|
+
sessionCliDefinition.options.noInject,
|
|
6145
|
+
sessionCliDefinition.options.sessionsDir
|
|
6146
|
+
]
|
|
6147
|
+
},
|
|
6148
|
+
{
|
|
6149
|
+
subcommand: sessionCliDefinition.subcommands.release,
|
|
6150
|
+
options: [sessionCliDefinition.options.sessionsDir]
|
|
6151
|
+
},
|
|
6152
|
+
{
|
|
6153
|
+
subcommand: sessionCliDefinition.subcommands.handoff,
|
|
6154
|
+
options: [sessionCliDefinition.options.sessionsDir]
|
|
6155
|
+
},
|
|
6156
|
+
{
|
|
6157
|
+
subcommand: sessionCliDefinition.subcommands.delete,
|
|
6158
|
+
options: [sessionCliDefinition.options.sessionsDir]
|
|
6159
|
+
},
|
|
6160
|
+
{
|
|
6161
|
+
subcommand: sessionCliDefinition.subcommands.prune,
|
|
6162
|
+
options: [
|
|
6163
|
+
sessionCliDefinition.options.keep,
|
|
6164
|
+
sessionCliDefinition.options.dryRun,
|
|
6165
|
+
sessionCliDefinition.options.sessionsDir
|
|
6166
|
+
]
|
|
6167
|
+
},
|
|
6168
|
+
{
|
|
6169
|
+
subcommand: sessionCliDefinition.subcommands.archive,
|
|
6170
|
+
options: [sessionCliDefinition.options.sessionsDir]
|
|
6082
6171
|
}
|
|
6172
|
+
];
|
|
6173
|
+
function sessionOptionsForSubcommand(subcommand) {
|
|
6174
|
+
return sessionSubcommandOptions.find((entry) => entry.subcommand === subcommand)?.options ?? [];
|
|
6083
6175
|
}
|
|
6084
|
-
|
|
6085
|
-
|
|
6086
|
-
const completed = await writeOutput(stream, `${result.output}${STREAM_LINE_SEPARATOR}`);
|
|
6087
|
-
if (completed) process.exit(result.exitCode);
|
|
6176
|
+
function sessionCommandToken(subcommand) {
|
|
6177
|
+
return subcommand.operand === void 0 ? subcommand.commandName : `${subcommand.commandName} ${subcommand.operand}`;
|
|
6088
6178
|
}
|
|
6089
|
-
function
|
|
6090
|
-
return
|
|
6091
|
-
stream.write(output, (error) => {
|
|
6092
|
-
if (error === void 0 || error === null) {
|
|
6093
|
-
resolve8(true);
|
|
6094
|
-
return;
|
|
6095
|
-
}
|
|
6096
|
-
if (error.code === EPIPE_CODE) {
|
|
6097
|
-
resolve8(false);
|
|
6098
|
-
return;
|
|
6099
|
-
}
|
|
6100
|
-
reject(error);
|
|
6101
|
-
});
|
|
6102
|
-
});
|
|
6179
|
+
function sessionOptionToken(option) {
|
|
6180
|
+
return option.placeholder === void 0 ? option.flag : `${option.flag} ${option.placeholder}`;
|
|
6103
6181
|
}
|
|
6104
6182
|
|
|
6105
6183
|
// src/commands/session/archive.ts
|
|
6106
6184
|
import { mkdir, rename, stat } from "fs/promises";
|
|
6107
|
-
import { dirname as
|
|
6185
|
+
import { dirname as dirname6, join as join9 } from "path";
|
|
6108
6186
|
|
|
6109
6187
|
// src/domains/session/archive.ts
|
|
6110
6188
|
var SESSION_FILE_EXTENSION = ".md";
|
|
@@ -6370,8 +6448,8 @@ async function resolveSessionConfig(options = {}) {
|
|
|
6370
6448
|
warning
|
|
6371
6449
|
};
|
|
6372
6450
|
}
|
|
6373
|
-
async function resolveSessionConfigSurfacingWarning(sessionsDir, onWarning) {
|
|
6374
|
-
const { config, warning } = await resolveSessionConfig({ sessionsDir });
|
|
6451
|
+
async function resolveSessionConfigSurfacingWarning(sessionsDir, onWarning, cwd) {
|
|
6452
|
+
const { config, warning } = await resolveSessionConfig({ sessionsDir, cwd });
|
|
6375
6453
|
if (warning !== void 0) {
|
|
6376
6454
|
onWarning?.(warning);
|
|
6377
6455
|
}
|
|
@@ -6425,13 +6503,13 @@ async function resolveArchivePaths(sessionId, config) {
|
|
|
6425
6503
|
}
|
|
6426
6504
|
async function archiveSingle(sessionId, config) {
|
|
6427
6505
|
const { source, target } = await resolveArchivePaths(sessionId, config);
|
|
6428
|
-
await mkdir(
|
|
6506
|
+
await mkdir(dirname6(target), { recursive: true });
|
|
6429
6507
|
await rename(source, target);
|
|
6430
6508
|
return `${SESSION_ARCHIVE_OUTPUT.ARCHIVED}: ${sessionId}
|
|
6431
6509
|
${SESSION_ARCHIVE_OUTPUT.ARCHIVE_LOCATION}: ${target}`;
|
|
6432
6510
|
}
|
|
6433
6511
|
async function archiveCommand(options) {
|
|
6434
|
-
const config = await resolveSessionConfigSurfacingWarning(options.sessionsDir, options.onWarning);
|
|
6512
|
+
const config = await resolveSessionConfigSurfacingWarning(options.sessionsDir, options.onWarning, options.cwd);
|
|
6435
6513
|
return processBatch(options.sessionIds, (id) => archiveSingle(id, config));
|
|
6436
6514
|
}
|
|
6437
6515
|
|
|
@@ -6454,6 +6532,123 @@ import { join as join10 } from "path";
|
|
|
6454
6532
|
import { Chalk as Chalk2 } from "chalk";
|
|
6455
6533
|
import { parse as parseYaml2 } from "yaml";
|
|
6456
6534
|
|
|
6535
|
+
// src/domains/session/display-width.ts
|
|
6536
|
+
var CONTROL_CODE_MAX = 31;
|
|
6537
|
+
var DELETE_CODE = 127;
|
|
6538
|
+
var C1_CONTROL_MIN = 128;
|
|
6539
|
+
var C1_CONTROL_MAX = 159;
|
|
6540
|
+
var ZERO_WIDTH_JOINER = 8205;
|
|
6541
|
+
var ZERO_WIDTH_RANGES = [
|
|
6542
|
+
{ start: 768, end: 879 },
|
|
6543
|
+
{ start: 6832, end: 6911 },
|
|
6544
|
+
{ start: 7616, end: 7679 },
|
|
6545
|
+
{ start: 8400, end: 8447 },
|
|
6546
|
+
{ start: 65024, end: 65039 },
|
|
6547
|
+
{ start: 65056, end: 65071 },
|
|
6548
|
+
{ start: 917760, end: 917999 }
|
|
6549
|
+
];
|
|
6550
|
+
var WIDE_RANGES = [
|
|
6551
|
+
{ start: 4352, end: 4447 },
|
|
6552
|
+
{ start: 8986, end: 8987 },
|
|
6553
|
+
{ start: 9001, end: 9002 },
|
|
6554
|
+
{ start: 9193, end: 9196 },
|
|
6555
|
+
{ start: 9200, end: 9200 },
|
|
6556
|
+
{ start: 9203, end: 9203 },
|
|
6557
|
+
{ start: 9725, end: 9726 },
|
|
6558
|
+
{ start: 9748, end: 9749 },
|
|
6559
|
+
{ start: 9800, end: 9811 },
|
|
6560
|
+
{ start: 9855, end: 9855 },
|
|
6561
|
+
{ start: 9875, end: 9875 },
|
|
6562
|
+
{ start: 9889, end: 9889 },
|
|
6563
|
+
{ start: 9898, end: 9899 },
|
|
6564
|
+
{ start: 9917, end: 9918 },
|
|
6565
|
+
{ start: 9924, end: 9925 },
|
|
6566
|
+
{ start: 9934, end: 9934 },
|
|
6567
|
+
{ start: 9940, end: 9940 },
|
|
6568
|
+
{ start: 9962, end: 9962 },
|
|
6569
|
+
{ start: 9970, end: 9971 },
|
|
6570
|
+
{ start: 9973, end: 9973 },
|
|
6571
|
+
{ start: 9978, end: 9978 },
|
|
6572
|
+
{ start: 9981, end: 9981 },
|
|
6573
|
+
{ start: 9989, end: 9989 },
|
|
6574
|
+
{ start: 9994, end: 9995 },
|
|
6575
|
+
{ start: 10024, end: 10024 },
|
|
6576
|
+
{ start: 10060, end: 10060 },
|
|
6577
|
+
{ start: 10062, end: 10062 },
|
|
6578
|
+
{ start: 10067, end: 10069 },
|
|
6579
|
+
{ start: 10071, end: 10071 },
|
|
6580
|
+
{ start: 10133, end: 10135 },
|
|
6581
|
+
{ start: 10160, end: 10160 },
|
|
6582
|
+
{ start: 10175, end: 10175 },
|
|
6583
|
+
{ start: 11035, end: 11036 },
|
|
6584
|
+
{ start: 11088, end: 11088 },
|
|
6585
|
+
{ start: 11093, end: 11093 },
|
|
6586
|
+
{ start: 11904, end: 12350 },
|
|
6587
|
+
{ start: 12352, end: 42191 },
|
|
6588
|
+
{ start: 44032, end: 55203 },
|
|
6589
|
+
{ start: 63744, end: 64255 },
|
|
6590
|
+
{ start: 65040, end: 65049 },
|
|
6591
|
+
{ start: 65072, end: 65135 },
|
|
6592
|
+
{ start: 65280, end: 65376 },
|
|
6593
|
+
{ start: 65504, end: 65510 },
|
|
6594
|
+
{ start: 126980, end: 126980 },
|
|
6595
|
+
{ start: 127183, end: 127183 },
|
|
6596
|
+
{ start: 127374, end: 127374 },
|
|
6597
|
+
{ start: 127377, end: 127386 },
|
|
6598
|
+
{ start: 127488, end: 127490 },
|
|
6599
|
+
{ start: 127504, end: 127547 },
|
|
6600
|
+
{ start: 127552, end: 127560 },
|
|
6601
|
+
{ start: 127568, end: 127569 },
|
|
6602
|
+
{ start: 127744, end: 128591 },
|
|
6603
|
+
{ start: 128640, end: 128767 },
|
|
6604
|
+
{ start: 129280, end: 129535 },
|
|
6605
|
+
{ start: 131072, end: 262141 }
|
|
6606
|
+
];
|
|
6607
|
+
var ZERO_WIDTH_COLUMNS = 0;
|
|
6608
|
+
var NARROW_COLUMNS = 1;
|
|
6609
|
+
var WIDE_COLUMNS = 2;
|
|
6610
|
+
function isInRange(codePoint, range) {
|
|
6611
|
+
return codePoint >= range.start && codePoint <= range.end;
|
|
6612
|
+
}
|
|
6613
|
+
function isControlCode(codePoint) {
|
|
6614
|
+
return codePoint <= CONTROL_CODE_MAX || codePoint === DELETE_CODE || codePoint >= C1_CONTROL_MIN && codePoint <= C1_CONTROL_MAX;
|
|
6615
|
+
}
|
|
6616
|
+
function codePointWidth(codePoint) {
|
|
6617
|
+
if (isControlCode(codePoint) || codePoint === ZERO_WIDTH_JOINER) {
|
|
6618
|
+
return ZERO_WIDTH_COLUMNS;
|
|
6619
|
+
}
|
|
6620
|
+
if (ZERO_WIDTH_RANGES.some((range) => isInRange(codePoint, range))) {
|
|
6621
|
+
return ZERO_WIDTH_COLUMNS;
|
|
6622
|
+
}
|
|
6623
|
+
if (WIDE_RANGES.some((range) => isInRange(codePoint, range))) {
|
|
6624
|
+
return WIDE_COLUMNS;
|
|
6625
|
+
}
|
|
6626
|
+
return NARROW_COLUMNS;
|
|
6627
|
+
}
|
|
6628
|
+
function visibleWidth(text) {
|
|
6629
|
+
let width = ZERO_WIDTH_COLUMNS;
|
|
6630
|
+
for (const symbol of Array.from(text)) {
|
|
6631
|
+
width += codePointWidth(symbol.codePointAt(0) ?? ZERO_WIDTH_COLUMNS);
|
|
6632
|
+
}
|
|
6633
|
+
return width;
|
|
6634
|
+
}
|
|
6635
|
+
function takeVisibleColumns(text, maxColumns) {
|
|
6636
|
+
if (maxColumns <= ZERO_WIDTH_COLUMNS) {
|
|
6637
|
+
return "";
|
|
6638
|
+
}
|
|
6639
|
+
let width = ZERO_WIDTH_COLUMNS;
|
|
6640
|
+
let result = "";
|
|
6641
|
+
for (const symbol of Array.from(text)) {
|
|
6642
|
+
const nextWidth = codePointWidth(symbol.codePointAt(0) ?? ZERO_WIDTH_COLUMNS);
|
|
6643
|
+
if (width + nextWidth > maxColumns) {
|
|
6644
|
+
break;
|
|
6645
|
+
}
|
|
6646
|
+
result += symbol;
|
|
6647
|
+
width += nextWidth;
|
|
6648
|
+
}
|
|
6649
|
+
return result;
|
|
6650
|
+
}
|
|
6651
|
+
|
|
6457
6652
|
// src/domains/session/timestamp.ts
|
|
6458
6653
|
var SESSION_ID_PATTERN = /^(\d{4})-(\d{2})-(\d{2})_(\d{2})-(\d{2})-(\d{2})$/;
|
|
6459
6654
|
var SESSION_ID_SEPARATOR = "_";
|
|
@@ -6474,12 +6669,12 @@ function parseSessionId(id) {
|
|
|
6474
6669
|
return null;
|
|
6475
6670
|
}
|
|
6476
6671
|
const [, yearStr, monthStr, dayStr, hoursStr, minutesStr, secondsStr] = match;
|
|
6477
|
-
const year = parseInt(yearStr, 10);
|
|
6478
|
-
const month = parseInt(monthStr, 10) - 1;
|
|
6479
|
-
const day = parseInt(dayStr, 10);
|
|
6480
|
-
const hours = parseInt(hoursStr, 10);
|
|
6481
|
-
const minutes = parseInt(minutesStr, 10);
|
|
6482
|
-
const seconds = parseInt(secondsStr, 10);
|
|
6672
|
+
const year = Number.parseInt(yearStr, 10);
|
|
6673
|
+
const month = Number.parseInt(monthStr, 10) - 1;
|
|
6674
|
+
const day = Number.parseInt(dayStr, 10);
|
|
6675
|
+
const hours = Number.parseInt(hoursStr, 10);
|
|
6676
|
+
const minutes = Number.parseInt(minutesStr, 10);
|
|
6677
|
+
const seconds = Number.parseInt(secondsStr, 10);
|
|
6483
6678
|
if (month < 0 || month > 11) return null;
|
|
6484
6679
|
if (day < 1 || day > 31) return null;
|
|
6485
6680
|
if (hours < 0 || hours > 23) return null;
|
|
@@ -6500,6 +6695,18 @@ var SESSION_PRIORITY = {
|
|
|
6500
6695
|
};
|
|
6501
6696
|
var SESSION_STATUSES = ["todo", "doing", "archive"];
|
|
6502
6697
|
var DEFAULT_LIST_STATUSES = ["doing", "todo"];
|
|
6698
|
+
var SESSION_FILE_ENCODING = "utf-8";
|
|
6699
|
+
var SESSION_FILE_ERROR_CODE = {
|
|
6700
|
+
NOT_FOUND: "ENOENT"
|
|
6701
|
+
};
|
|
6702
|
+
var SESSION_OUTPUT_MARKER = {
|
|
6703
|
+
HANDOFF_ID: "HANDOFF_ID",
|
|
6704
|
+
PICKUP_ID: "PICKUP_ID",
|
|
6705
|
+
SESSION_FILE: "SESSION_FILE"
|
|
6706
|
+
};
|
|
6707
|
+
function formatSessionOutputMarker(marker, value) {
|
|
6708
|
+
return `<${marker}>${value}</${marker}>`;
|
|
6709
|
+
}
|
|
6503
6710
|
var CLAIMABLE_STATUS = SESSION_STATUSES[0];
|
|
6504
6711
|
var PRIORITY_ORDER = {
|
|
6505
6712
|
[SESSION_PRIORITY.HIGH]: 0,
|
|
@@ -6537,7 +6744,7 @@ function defaultSessionMetadata() {
|
|
|
6537
6744
|
};
|
|
6538
6745
|
}
|
|
6539
6746
|
function isValidPriority(value) {
|
|
6540
|
-
return typeof value === "string" && SESSION_PRIORITY_VALUES.
|
|
6747
|
+
return typeof value === "string" && SESSION_PRIORITY_VALUES.includes(value);
|
|
6541
6748
|
}
|
|
6542
6749
|
function parseSessionMetadata(content) {
|
|
6543
6750
|
const match = FRONT_MATTER_PATTERN.exec(content);
|
|
@@ -6545,10 +6752,11 @@ function parseSessionMetadata(content) {
|
|
|
6545
6752
|
return defaultSessionMetadata();
|
|
6546
6753
|
}
|
|
6547
6754
|
try {
|
|
6548
|
-
const
|
|
6549
|
-
if (!
|
|
6755
|
+
const parsedDocument = parseYaml2(match[1]);
|
|
6756
|
+
if (!parsedDocument || typeof parsedDocument !== "object") {
|
|
6550
6757
|
return defaultSessionMetadata();
|
|
6551
6758
|
}
|
|
6759
|
+
const parsed = parsedDocument;
|
|
6552
6760
|
const rawPriority = parsed[SESSION_FRONT_MATTER.PRIORITY];
|
|
6553
6761
|
const priority = isValidPriority(rawPriority) ? rawPriority : DEFAULT_PRIORITY;
|
|
6554
6762
|
const metadata = {
|
|
@@ -6685,13 +6893,13 @@ function formatSessionLine(session, width, chalk) {
|
|
|
6685
6893
|
const { id, metadata } = session;
|
|
6686
6894
|
const badge = metadata.priority === DEFAULT_PRIORITY ? "" : ` [${metadata.priority}]`;
|
|
6687
6895
|
const summary = metadata.goal.length > 0 && metadata.next_step.length > 0 ? ` ${metadata.goal}${LIST_SUMMARY_SEPARATOR}${metadata.next_step}` : "";
|
|
6688
|
-
let remaining = Math.max(0, width - LIST_INDENT
|
|
6689
|
-
const idShown = id
|
|
6690
|
-
remaining -= idShown
|
|
6691
|
-
const badgeShown = badge
|
|
6692
|
-
remaining -= badgeShown
|
|
6693
|
-
const summaryShown = summary
|
|
6694
|
-
const styledBadge = badgeShown
|
|
6896
|
+
let remaining = Math.max(0, width - visibleWidth(LIST_INDENT));
|
|
6897
|
+
const idShown = takeVisibleColumns(id, remaining);
|
|
6898
|
+
remaining -= visibleWidth(idShown);
|
|
6899
|
+
const badgeShown = takeVisibleColumns(badge, remaining);
|
|
6900
|
+
remaining -= visibleWidth(badgeShown);
|
|
6901
|
+
const summaryShown = takeVisibleColumns(summary, remaining);
|
|
6902
|
+
const styledBadge = visibleWidth(badgeShown) > 0 ? PRIORITY_STYLE[metadata.priority](chalk, badgeShown) : "";
|
|
6695
6903
|
return `${LIST_INDENT}${chalk.dim(idShown)}${styledBadge}${chalk.dim(summaryShown)}`;
|
|
6696
6904
|
}
|
|
6697
6905
|
function formatSessionListText(sessions, opts) {
|
|
@@ -6772,13 +6980,13 @@ async function deleteSingle(sessionId, config) {
|
|
|
6772
6980
|
return `${SESSION_DELETE_OUTPUT.DELETED}: ${sessionId}`;
|
|
6773
6981
|
}
|
|
6774
6982
|
async function deleteCommand(options) {
|
|
6775
|
-
const config = await resolveSessionConfigSurfacingWarning(options.sessionsDir, options.onWarning);
|
|
6983
|
+
const config = await resolveSessionConfigSurfacingWarning(options.sessionsDir, options.onWarning, options.cwd);
|
|
6776
6984
|
return processBatch(options.sessionIds, (id) => deleteSingle(id, config));
|
|
6777
6985
|
}
|
|
6778
6986
|
|
|
6779
6987
|
// src/commands/session/handoff.ts
|
|
6780
6988
|
import { mkdir as mkdir2, writeFile } from "fs/promises";
|
|
6781
|
-
import { join as join11, resolve as
|
|
6989
|
+
import { join as join11, resolve as resolve5 } from "path";
|
|
6782
6990
|
import { stringify as stringifyYaml3 } from "yaml";
|
|
6783
6991
|
|
|
6784
6992
|
// src/domains/session/create.ts
|
|
@@ -6849,12 +7057,12 @@ function resolveWorkBranchGitRef(workBranch, existsOnOrigin) {
|
|
|
6849
7057
|
// src/domains/session/parse-handoff-input.ts
|
|
6850
7058
|
var LEGACY_FRONTMATTER_PREFIX = /^---\r?\n/;
|
|
6851
7059
|
var JSON_OBJECT_OPEN_CHAR = "{";
|
|
6852
|
-
var CARRIAGE_RETURN_CHAR_CODE = "\r".
|
|
6853
|
-
var NEWLINE_CHAR_CODE = "\n".
|
|
6854
|
-
var BACKSLASH_CHAR_CODE = "\\".
|
|
6855
|
-
var DOUBLE_QUOTE_CHAR_CODE = '"'.
|
|
6856
|
-
var OPEN_BRACE_CHAR_CODE = "{".
|
|
6857
|
-
var CLOSE_BRACE_CHAR_CODE = "}".
|
|
7060
|
+
var CARRIAGE_RETURN_CHAR_CODE = "\r".codePointAt(0) ?? 0;
|
|
7061
|
+
var NEWLINE_CHAR_CODE = "\n".codePointAt(0) ?? 0;
|
|
7062
|
+
var BACKSLASH_CHAR_CODE = "\\".codePointAt(0) ?? 0;
|
|
7063
|
+
var DOUBLE_QUOTE_CHAR_CODE = '"'.codePointAt(0) ?? 0;
|
|
7064
|
+
var OPEN_BRACE_CHAR_CODE = "{".codePointAt(0) ?? 0;
|
|
7065
|
+
var CLOSE_BRACE_CHAR_CODE = "}".codePointAt(0) ?? 0;
|
|
6858
7066
|
var UNBALANCED_HEADER_END = -1;
|
|
6859
7067
|
var LF_SEPARATOR_LENGTH = 1;
|
|
6860
7068
|
var CRLF_SEPARATOR_LENGTH = 2;
|
|
@@ -6887,10 +7095,10 @@ function parseHandoffInput(stdin) {
|
|
|
6887
7095
|
return { header, body: stdin.slice(bodyStart) };
|
|
6888
7096
|
}
|
|
6889
7097
|
function consumeOptionalBodySeparator(stdin, bodyStart) {
|
|
6890
|
-
if (stdin.
|
|
7098
|
+
if (stdin.codePointAt(bodyStart) === CARRIAGE_RETURN_CHAR_CODE && stdin.codePointAt(bodyStart + LF_SEPARATOR_LENGTH) === NEWLINE_CHAR_CODE) {
|
|
6891
7099
|
return bodyStart + CRLF_SEPARATOR_LENGTH;
|
|
6892
7100
|
}
|
|
6893
|
-
if (stdin.
|
|
7101
|
+
if (stdin.codePointAt(bodyStart) === NEWLINE_CHAR_CODE) {
|
|
6894
7102
|
return bodyStart + LF_SEPARATOR_LENGTH;
|
|
6895
7103
|
}
|
|
6896
7104
|
return bodyStart;
|
|
@@ -6899,32 +7107,33 @@ function findJsonObjectEnd(stdin) {
|
|
|
6899
7107
|
let depth = 0;
|
|
6900
7108
|
let inString = false;
|
|
6901
7109
|
for (let i = 0; i < stdin.length; i++) {
|
|
6902
|
-
const code = stdin.
|
|
7110
|
+
const code = stdin.codePointAt(i);
|
|
6903
7111
|
if (inString) {
|
|
6904
|
-
|
|
6905
|
-
|
|
6906
|
-
|
|
6907
|
-
}
|
|
6908
|
-
if (code === DOUBLE_QUOTE_CHAR_CODE) {
|
|
6909
|
-
inString = false;
|
|
6910
|
-
}
|
|
7112
|
+
const stringState = scanJsonStringCharacter(code);
|
|
7113
|
+
inString = stringState.inString;
|
|
7114
|
+
if (stringState.skipNext) i += 1;
|
|
6911
7115
|
continue;
|
|
6912
7116
|
}
|
|
6913
7117
|
if (code === DOUBLE_QUOTE_CHAR_CODE) {
|
|
6914
7118
|
inString = true;
|
|
6915
7119
|
continue;
|
|
6916
7120
|
}
|
|
6917
|
-
|
|
6918
|
-
|
|
6919
|
-
continue;
|
|
6920
|
-
}
|
|
6921
|
-
if (code === CLOSE_BRACE_CHAR_CODE) {
|
|
6922
|
-
depth -= 1;
|
|
6923
|
-
if (depth === 0) return i;
|
|
6924
|
-
}
|
|
7121
|
+
depth = nextJsonObjectDepth(depth, code);
|
|
7122
|
+
if (depth === 0 && code === CLOSE_BRACE_CHAR_CODE) return i;
|
|
6925
7123
|
}
|
|
6926
7124
|
return UNBALANCED_HEADER_END;
|
|
6927
7125
|
}
|
|
7126
|
+
function scanJsonStringCharacter(code) {
|
|
7127
|
+
if (code === BACKSLASH_CHAR_CODE) {
|
|
7128
|
+
return { inString: true, skipNext: true };
|
|
7129
|
+
}
|
|
7130
|
+
return { inString: code !== DOUBLE_QUOTE_CHAR_CODE, skipNext: false };
|
|
7131
|
+
}
|
|
7132
|
+
function nextJsonObjectDepth(depth, code) {
|
|
7133
|
+
if (code === OPEN_BRACE_CHAR_CODE) return depth + 1;
|
|
7134
|
+
if (code === CLOSE_BRACE_CHAR_CODE) return depth - 1;
|
|
7135
|
+
return depth;
|
|
7136
|
+
}
|
|
6928
7137
|
function validateHandoffHeader(parsed) {
|
|
6929
7138
|
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
6930
7139
|
throw new SessionInvalidJsonHeaderError("header must be a JSON object");
|
|
@@ -6989,13 +7198,16 @@ async function resolveSessionGitRef(cwd, deps) {
|
|
|
6989
7198
|
gatherGitFacts(cwd, deps)
|
|
6990
7199
|
]);
|
|
6991
7200
|
const isGitRepo = facts !== null;
|
|
6992
|
-
const currentWorktreePath = facts?.worktreeRoot ?? cwd ??
|
|
7201
|
+
const currentWorktreePath = facts?.worktreeRoot ?? cwd ?? CONFIG_PROCESS_CWD.read();
|
|
6993
7202
|
const isMain = facts !== null && isMainCheckout(facts);
|
|
6994
|
-
|
|
7203
|
+
let designatedMainCheckout = null;
|
|
7204
|
+
if (facts !== null) {
|
|
7205
|
+
designatedMainCheckout = mainCheckoutPath(facts);
|
|
7206
|
+
}
|
|
6995
7207
|
let isClean = false;
|
|
6996
7208
|
let defaultBranch = null;
|
|
6997
7209
|
let defaultTipSha = null;
|
|
6998
|
-
if (isGitRepo &&
|
|
7210
|
+
if (isGitRepo && isMain === false) {
|
|
6999
7211
|
[isClean, defaultBranch] = await Promise.all([
|
|
7000
7212
|
isWorkingTreeClean(cwd, deps),
|
|
7001
7213
|
resolveDefaultBranch(cwd, deps)
|
|
@@ -7057,11 +7269,11 @@ async function handoffCommand(options) {
|
|
|
7057
7269
|
const fullContent = `${SESSION_FRONT_MATTER_OPEN}${yaml}${SESSION_FRONT_MATTER_CLOSE}${body}`;
|
|
7058
7270
|
const filename = `${sessionId}.md`;
|
|
7059
7271
|
const sessionPath = join11(config.todoDir, filename);
|
|
7060
|
-
const absolutePath =
|
|
7272
|
+
const absolutePath = resolve5(sessionPath);
|
|
7061
7273
|
await mkdir2(config.todoDir, { recursive: true });
|
|
7062
|
-
await writeFile(sessionPath, fullContent,
|
|
7063
|
-
const output = `Created handoff session
|
|
7064
|
-
|
|
7274
|
+
await writeFile(sessionPath, fullContent, SESSION_FILE_ENCODING);
|
|
7275
|
+
const output = `Created handoff session ${formatSessionOutputMarker(SESSION_OUTPUT_MARKER.HANDOFF_ID, sessionId)}
|
|
7276
|
+
${formatSessionOutputMarker(SESSION_OUTPUT_MARKER.SESSION_FILE, absolutePath)}`;
|
|
7065
7277
|
return { output };
|
|
7066
7278
|
}
|
|
7067
7279
|
|
|
@@ -7081,7 +7293,7 @@ async function loadSessionsFromDir(dir, status) {
|
|
|
7081
7293
|
if (!file.endsWith(".md")) continue;
|
|
7082
7294
|
const id = file.replace(".md", "");
|
|
7083
7295
|
const filePath = join12(dir, file);
|
|
7084
|
-
const content = await readFile4(filePath,
|
|
7296
|
+
const content = await readFile4(filePath, SESSION_FILE_ENCODING);
|
|
7085
7297
|
const metadata = parseSessionMetadata(content);
|
|
7086
7298
|
sessions.push({
|
|
7087
7299
|
id,
|
|
@@ -7092,7 +7304,7 @@ async function loadSessionsFromDir(dir, status) {
|
|
|
7092
7304
|
}
|
|
7093
7305
|
return sessions;
|
|
7094
7306
|
} catch (error) {
|
|
7095
|
-
if (error instanceof Error && "code" in error && error.code ===
|
|
7307
|
+
if (error instanceof Error && "code" in error && error.code === SESSION_FILE_ERROR_CODE.NOT_FOUND) {
|
|
7096
7308
|
return [];
|
|
7097
7309
|
}
|
|
7098
7310
|
throw error;
|
|
@@ -7112,9 +7324,9 @@ function validateStatus(input) {
|
|
|
7112
7324
|
);
|
|
7113
7325
|
}
|
|
7114
7326
|
async function listCommand(options) {
|
|
7115
|
-
const fieldSelection = options.fields
|
|
7116
|
-
const config = await resolveSessionConfigSurfacingWarning(options.sessionsDir, options.onWarning);
|
|
7117
|
-
const statuses = options.status
|
|
7327
|
+
const fieldSelection = options.fields === void 0 ? void 0 : parseFieldSelection(options.fields);
|
|
7328
|
+
const config = await resolveSessionConfigSurfacingWarning(options.sessionsDir, options.onWarning, options.cwd);
|
|
7329
|
+
const statuses = options.status === void 0 ? DEFAULT_LIST_STATUSES : [validateStatus(options.status)];
|
|
7118
7330
|
const sessionsByStatus = {};
|
|
7119
7331
|
for (const status of statuses) {
|
|
7120
7332
|
const dirKey = STATUS_DIR_KEY[status];
|
|
@@ -7127,7 +7339,7 @@ async function listCommand(options) {
|
|
|
7127
7339
|
for (const status of statuses) {
|
|
7128
7340
|
recordsByStatus[status] = (sessionsByStatus[status] ?? []).map((session) => {
|
|
7129
7341
|
const record6 = toSessionRecord(session);
|
|
7130
|
-
return fieldSelection
|
|
7342
|
+
return fieldSelection === void 0 ? record6 : projectSessionRecord(record6, fieldSelection);
|
|
7131
7343
|
});
|
|
7132
7344
|
}
|
|
7133
7345
|
return JSON.stringify(recordsByStatus, null, 2);
|
|
@@ -7145,7 +7357,7 @@ async function listCommand(options) {
|
|
|
7145
7357
|
|
|
7146
7358
|
// src/commands/session/pickup.ts
|
|
7147
7359
|
import { mkdir as mkdir3, readdir as readdir2, readFile as readFile5, rename as rename2 } from "fs/promises";
|
|
7148
|
-
import { join as join13 } from "path";
|
|
7360
|
+
import { join as join13, resolve as resolve6 } from "path";
|
|
7149
7361
|
|
|
7150
7362
|
// src/domains/session/pickup.ts
|
|
7151
7363
|
function buildClaimPaths(sessionId, config) {
|
|
@@ -7155,7 +7367,7 @@ function buildClaimPaths(sessionId, config) {
|
|
|
7155
7367
|
};
|
|
7156
7368
|
}
|
|
7157
7369
|
function classifyClaimError(error, sessionId) {
|
|
7158
|
-
if (error instanceof Error && "code" in error && error.code ===
|
|
7370
|
+
if (error instanceof Error && "code" in error && error.code === SESSION_FILE_ERROR_CODE.NOT_FOUND) {
|
|
7159
7371
|
return new SessionNotAvailableError(sessionId);
|
|
7160
7372
|
}
|
|
7161
7373
|
throw error;
|
|
@@ -7182,15 +7394,24 @@ function selectBestSession(sessions) {
|
|
|
7182
7394
|
|
|
7183
7395
|
// src/commands/session/pickup.ts
|
|
7184
7396
|
var PICKUP_TARGET_STATUS = SESSION_STATUSES[1];
|
|
7397
|
+
var PICKUP_DEPS = {
|
|
7398
|
+
mkdir: mkdir3,
|
|
7399
|
+
readdir: readdir2,
|
|
7400
|
+
readFile: readFile5,
|
|
7401
|
+
rename: rename2
|
|
7402
|
+
};
|
|
7185
7403
|
async function loadTodoSessions(config) {
|
|
7404
|
+
return loadTodoSessionsWithDeps(config, PICKUP_DEPS);
|
|
7405
|
+
}
|
|
7406
|
+
async function loadTodoSessionsWithDeps(config, deps) {
|
|
7186
7407
|
try {
|
|
7187
|
-
const files = await
|
|
7408
|
+
const files = await deps.readdir(config.todoDir);
|
|
7188
7409
|
const sessions = [];
|
|
7189
7410
|
for (const file of files) {
|
|
7190
7411
|
if (!file.endsWith(".md")) continue;
|
|
7191
7412
|
const id = file.replace(".md", "");
|
|
7192
7413
|
const filePath = join13(config.todoDir, file);
|
|
7193
|
-
const content = await
|
|
7414
|
+
const content = await deps.readFile(filePath, SESSION_FILE_ENCODING);
|
|
7194
7415
|
const metadata = parseSessionMetadata(content);
|
|
7195
7416
|
sessions.push({
|
|
7196
7417
|
id,
|
|
@@ -7201,51 +7422,84 @@ async function loadTodoSessions(config) {
|
|
|
7201
7422
|
}
|
|
7202
7423
|
return sessions;
|
|
7203
7424
|
} catch (error) {
|
|
7204
|
-
if (error instanceof Error && "code" in error && error.code ===
|
|
7425
|
+
if (error instanceof Error && "code" in error && error.code === SESSION_FILE_ERROR_CODE.NOT_FOUND) {
|
|
7205
7426
|
return [];
|
|
7206
7427
|
}
|
|
7207
7428
|
throw error;
|
|
7208
7429
|
}
|
|
7209
7430
|
}
|
|
7210
|
-
|
|
7431
|
+
var SESSION_INJECTION_SECTION_PREFIX = "Injected file";
|
|
7432
|
+
var SESSION_INJECTION_MISSING_WARNING_PREFIX = "Warning: missing session injection file";
|
|
7433
|
+
function injectionPath(cwd, filePath) {
|
|
7434
|
+
return resolve6(cwd, filePath);
|
|
7435
|
+
}
|
|
7436
|
+
function formatInjectedFile(listedPath, content) {
|
|
7437
|
+
return `${SESSION_INJECTION_SECTION_PREFIX}: ${listedPath}
|
|
7438
|
+
${content}`;
|
|
7439
|
+
}
|
|
7440
|
+
async function readInjectedFiles(metadata, cwd, deps, onWarning) {
|
|
7441
|
+
const sections = [];
|
|
7442
|
+
for (const listedPath of [...metadata.specs, ...metadata.files]) {
|
|
7443
|
+
try {
|
|
7444
|
+
const content = await deps.readFile(injectionPath(cwd, listedPath), SESSION_FILE_ENCODING);
|
|
7445
|
+
sections.push(formatInjectedFile(listedPath, content));
|
|
7446
|
+
} catch (error) {
|
|
7447
|
+
if (error instanceof Error && "code" in error && error.code === SESSION_FILE_ERROR_CODE.NOT_FOUND) {
|
|
7448
|
+
onWarning?.(`${SESSION_INJECTION_MISSING_WARNING_PREFIX}: ${listedPath}`);
|
|
7449
|
+
continue;
|
|
7450
|
+
}
|
|
7451
|
+
throw error;
|
|
7452
|
+
}
|
|
7453
|
+
}
|
|
7454
|
+
return sections;
|
|
7455
|
+
}
|
|
7456
|
+
async function pickupSingle(sessionId, config, cwd, deps, noInject, onWarning) {
|
|
7211
7457
|
const paths = buildClaimPaths(sessionId, config);
|
|
7212
|
-
await
|
|
7458
|
+
await deps.mkdir(config.doingDir, { recursive: true });
|
|
7213
7459
|
try {
|
|
7214
|
-
await
|
|
7460
|
+
await deps.rename(paths.source, paths.target);
|
|
7215
7461
|
} catch (error) {
|
|
7216
7462
|
throw classifyClaimError(error, sessionId);
|
|
7217
7463
|
}
|
|
7218
|
-
const content = await
|
|
7464
|
+
const content = await deps.readFile(paths.target, SESSION_FILE_ENCODING);
|
|
7465
|
+
const metadata = parseSessionMetadata(content);
|
|
7219
7466
|
const output = formatShowOutput(content, { status: PICKUP_TARGET_STATUS });
|
|
7220
|
-
|
|
7467
|
+
const injected = noInject ? [] : await readInjectedFiles(metadata, cwd, deps, onWarning);
|
|
7468
|
+
const injectionOutput = injected.length === 0 ? "" : `
|
|
7221
7469
|
|
|
7222
|
-
${
|
|
7470
|
+
${injected.join("\n\n")}`;
|
|
7471
|
+
return `Claimed session ${formatSessionOutputMarker(SESSION_OUTPUT_MARKER.PICKUP_ID, sessionId)}
|
|
7472
|
+
|
|
7473
|
+
${output}${injectionOutput}`;
|
|
7223
7474
|
}
|
|
7224
7475
|
async function pickupCommand(options) {
|
|
7225
|
-
const
|
|
7476
|
+
const deps = options.deps ?? PICKUP_DEPS;
|
|
7477
|
+
const cwd = options.cwd ?? CONFIG_PROCESS_CWD.read();
|
|
7478
|
+
const config = await resolveSessionConfigSurfacingWarning(options.sessionsDir, options.onWarning, cwd);
|
|
7479
|
+
const noInject = options.noInject === true;
|
|
7226
7480
|
if (options.auto) {
|
|
7227
7481
|
if (options.sessionIds.length > 0) {
|
|
7228
7482
|
throw new Error("Session IDs cannot be combined with --auto");
|
|
7229
7483
|
}
|
|
7230
|
-
const sessions = await
|
|
7484
|
+
const sessions = await loadTodoSessionsWithDeps(config, deps);
|
|
7231
7485
|
const selected = selectBestSession(sessions);
|
|
7232
7486
|
if (!selected) {
|
|
7233
7487
|
throw new NoSessionsAvailableError();
|
|
7234
7488
|
}
|
|
7235
|
-
return pickupSingle(selected.id, config);
|
|
7489
|
+
return pickupSingle(selected.id, config, cwd, deps, noInject, options.onWarning);
|
|
7236
7490
|
}
|
|
7237
7491
|
if (options.sessionIds.length === 0) {
|
|
7238
7492
|
throw new Error("Either session ID or --auto flag is required");
|
|
7239
7493
|
}
|
|
7240
7494
|
if (options.sessionIds.length === 1) {
|
|
7241
|
-
return pickupSingle(options.sessionIds[0], config);
|
|
7495
|
+
return pickupSingle(options.sessionIds[0], config, cwd, deps, noInject, options.onWarning);
|
|
7242
7496
|
}
|
|
7243
|
-
return processBatch(options.sessionIds, (id) => pickupSingle(id, config));
|
|
7497
|
+
return processBatch(options.sessionIds, (id) => pickupSingle(id, config, cwd, deps, noInject, options.onWarning));
|
|
7244
7498
|
}
|
|
7245
7499
|
|
|
7246
7500
|
// src/commands/session/pick.ts
|
|
7247
7501
|
async function loadPickCandidates(options) {
|
|
7248
|
-
const config = await resolveSessionConfigSurfacingWarning(options.sessionsDir, options.onWarning);
|
|
7502
|
+
const config = await resolveSessionConfigSurfacingWarning(options.sessionsDir, options.onWarning, options.cwd);
|
|
7249
7503
|
return loadTodoSessions(config);
|
|
7250
7504
|
}
|
|
7251
7505
|
|
|
@@ -7301,7 +7555,7 @@ async function loadArchiveSessions(config) {
|
|
|
7301
7555
|
if (!file.endsWith(".md")) continue;
|
|
7302
7556
|
const id = file.replace(".md", "");
|
|
7303
7557
|
const filePath = join14(config.archiveDir, file);
|
|
7304
|
-
const content = await readFile6(filePath,
|
|
7558
|
+
const content = await readFile6(filePath, SESSION_FILE_ENCODING);
|
|
7305
7559
|
const metadata = parseSessionMetadata(content);
|
|
7306
7560
|
sessions.push({
|
|
7307
7561
|
id,
|
|
@@ -7312,7 +7566,7 @@ async function loadArchiveSessions(config) {
|
|
|
7312
7566
|
}
|
|
7313
7567
|
return sessions;
|
|
7314
7568
|
} catch (error) {
|
|
7315
|
-
if (error instanceof Error && "code" in error && error.code ===
|
|
7569
|
+
if (error instanceof Error && "code" in error && error.code === SESSION_FILE_ERROR_CODE.NOT_FOUND) {
|
|
7316
7570
|
return [];
|
|
7317
7571
|
}
|
|
7318
7572
|
throw error;
|
|
@@ -7322,7 +7576,7 @@ async function pruneCommand(options) {
|
|
|
7322
7576
|
validatePruneOptions(options);
|
|
7323
7577
|
const keep = options.keep ?? DEFAULT_KEEP_COUNT;
|
|
7324
7578
|
const dryRun = options.dryRun ?? false;
|
|
7325
|
-
const config = await resolveSessionConfigSurfacingWarning(options.sessionsDir, options.onWarning);
|
|
7579
|
+
const config = await resolveSessionConfigSurfacingWarning(options.sessionsDir, options.onWarning, options.cwd);
|
|
7326
7580
|
const sessions = await loadArchiveSessions(config);
|
|
7327
7581
|
const toPrune = selectSessionsToDelete(sessions, { keep });
|
|
7328
7582
|
if (toPrune.length === 0) {
|
|
@@ -7384,7 +7638,7 @@ async function loadDoingSessions(config) {
|
|
|
7384
7638
|
const files = await readdir4(config.doingDir);
|
|
7385
7639
|
return files.filter((file) => file.endsWith(".md")).map((file) => ({ id: file.replace(".md", "") }));
|
|
7386
7640
|
} catch (error) {
|
|
7387
|
-
if (error instanceof Error && "code" in error && error.code ===
|
|
7641
|
+
if (error instanceof Error && "code" in error && error.code === SESSION_FILE_ERROR_CODE.NOT_FOUND) {
|
|
7388
7642
|
return [];
|
|
7389
7643
|
}
|
|
7390
7644
|
throw error;
|
|
@@ -7395,7 +7649,7 @@ async function releaseSingle(sessionId, config) {
|
|
|
7395
7649
|
try {
|
|
7396
7650
|
await rename3(paths.source, paths.target);
|
|
7397
7651
|
} catch (error) {
|
|
7398
|
-
if (error instanceof Error && "code" in error && error.code ===
|
|
7652
|
+
if (error instanceof Error && "code" in error && error.code === SESSION_FILE_ERROR_CODE.NOT_FOUND) {
|
|
7399
7653
|
throw new SessionNotClaimedError(sessionId);
|
|
7400
7654
|
}
|
|
7401
7655
|
throw error;
|
|
@@ -7404,7 +7658,7 @@ async function releaseSingle(sessionId, config) {
|
|
|
7404
7658
|
${SESSION_RELEASE_OUTPUT.RETURNED_TO_TODO}`;
|
|
7405
7659
|
}
|
|
7406
7660
|
async function releaseCommand(options) {
|
|
7407
|
-
const config = await resolveSessionConfigSurfacingWarning(options.sessionsDir, options.onWarning);
|
|
7661
|
+
const config = await resolveSessionConfigSurfacingWarning(options.sessionsDir, options.onWarning, options.cwd);
|
|
7408
7662
|
let ids = options.sessionIds;
|
|
7409
7663
|
if (ids.length === 0) {
|
|
7410
7664
|
const sessions = await loadDoingSessions(config);
|
|
@@ -7438,7 +7692,7 @@ async function resolveSession(sessionId, config) {
|
|
|
7438
7692
|
if (!found) {
|
|
7439
7693
|
throw new SessionNotFoundError(sessionId);
|
|
7440
7694
|
}
|
|
7441
|
-
const content = await readFile7(found.path,
|
|
7695
|
+
const content = await readFile7(found.path, SESSION_FILE_ENCODING);
|
|
7442
7696
|
return { status: found.status, path: found.path, content };
|
|
7443
7697
|
}
|
|
7444
7698
|
async function showSingle(sessionId, config) {
|
|
@@ -7471,7 +7725,7 @@ ${failureLines}`;
|
|
|
7471
7725
|
return JSON.stringify(payload, null, 2);
|
|
7472
7726
|
}
|
|
7473
7727
|
async function showCommand2(options) {
|
|
7474
|
-
const config = await resolveSessionConfigSurfacingWarning(options.sessionsDir, options.onWarning);
|
|
7728
|
+
const config = await resolveSessionConfigSurfacingWarning(options.sessionsDir, options.onWarning, options.cwd);
|
|
7475
7729
|
if (options.format === SESSION_LIST_FORMAT.JSON) {
|
|
7476
7730
|
return showJson(options.sessionIds, config);
|
|
7477
7731
|
}
|
|
@@ -7551,7 +7805,7 @@ Output:
|
|
|
7551
7805
|
`;
|
|
7552
7806
|
|
|
7553
7807
|
// src/domains/session/pick-model.ts
|
|
7554
|
-
import { resolve as
|
|
7808
|
+
import { resolve as resolve7 } from "path";
|
|
7555
7809
|
var ELLIPSIS = "\u2026";
|
|
7556
7810
|
var PICKER_RUNTIME = {
|
|
7557
7811
|
CLAUDE: "claude",
|
|
@@ -7561,21 +7815,24 @@ var RUNTIME_SKILL_PREFIX = {
|
|
|
7561
7815
|
[PICKER_RUNTIME.CLAUDE]: "/",
|
|
7562
7816
|
[PICKER_RUNTIME.CODEX]: "$"
|
|
7563
7817
|
};
|
|
7818
|
+
var PICKER_PICKUP_COMMAND_NAME = "pickup";
|
|
7819
|
+
var PICKER_AUTO_CONTINUE_FLAG = "--auto-continue";
|
|
7564
7820
|
function buildPickupCommand(runtime, autoContinue, reference) {
|
|
7565
|
-
const prompt = `${RUNTIME_SKILL_PREFIX[runtime]}
|
|
7821
|
+
const prompt = `${RUNTIME_SKILL_PREFIX[runtime]}${PICKER_PICKUP_COMMAND_NAME} ${reference}${autoContinue ? ` ${PICKER_AUTO_CONTINUE_FLAG}` : ""}`;
|
|
7566
7822
|
return { command: runtime, args: [prompt] };
|
|
7567
7823
|
}
|
|
7568
7824
|
function pickupReference(session, sessionsDir, cwd) {
|
|
7569
|
-
return sessionsDir === void 0 ? session.id :
|
|
7825
|
+
return sessionsDir === void 0 ? session.id : resolve7(cwd, session.path);
|
|
7570
7826
|
}
|
|
7571
7827
|
function toSingleLine(text) {
|
|
7572
|
-
return text.
|
|
7828
|
+
return text.replaceAll(/\s+/g, " ").trim();
|
|
7573
7829
|
}
|
|
7574
7830
|
function truncateToWidth(text, max) {
|
|
7575
7831
|
if (max <= 0) return "";
|
|
7576
|
-
if (text
|
|
7577
|
-
|
|
7578
|
-
|
|
7832
|
+
if (visibleWidth(text) <= max) return text;
|
|
7833
|
+
const ellipsisWidth = visibleWidth(ELLIPSIS);
|
|
7834
|
+
if (max <= ellipsisWidth) return takeVisibleColumns(ELLIPSIS, max);
|
|
7835
|
+
return `${takeVisibleColumns(text, max - ellipsisWidth)}${ELLIPSIS}`;
|
|
7579
7836
|
}
|
|
7580
7837
|
var PICKER_MODE = {
|
|
7581
7838
|
BROWSE: "browse",
|
|
@@ -7680,17 +7937,195 @@ function reducePicker(state, action) {
|
|
|
7680
7937
|
}
|
|
7681
7938
|
}
|
|
7682
7939
|
|
|
7940
|
+
// src/lib/process-lifecycle/exit-codes.ts
|
|
7941
|
+
var SIGINT_EXIT_CODE = 130;
|
|
7942
|
+
var SIGTERM_EXIT_CODE = 143;
|
|
7943
|
+
var EPIPE_EXIT_CODE = 0;
|
|
7944
|
+
var UNCAUGHT_EXIT_CODE = 1;
|
|
7945
|
+
|
|
7946
|
+
// src/lib/process-lifecycle/foreground-handoff.ts
|
|
7947
|
+
var FOREGROUND_SIGNALS = ["SIGINT", "SIGTERM"];
|
|
7948
|
+
function createSignalSuspender(target) {
|
|
7949
|
+
return {
|
|
7950
|
+
suspend() {
|
|
7951
|
+
const ignore = () => {
|
|
7952
|
+
};
|
|
7953
|
+
const restorers = FOREGROUND_SIGNALS.map((signal) => {
|
|
7954
|
+
const originals = target.listeners(signal);
|
|
7955
|
+
for (const listener of originals) target.removeListener(signal, listener);
|
|
7956
|
+
target.on(signal, ignore);
|
|
7957
|
+
return () => {
|
|
7958
|
+
target.removeListener(signal, ignore);
|
|
7959
|
+
for (const listener of originals) target.on(signal, listener);
|
|
7960
|
+
};
|
|
7961
|
+
});
|
|
7962
|
+
return () => {
|
|
7963
|
+
for (const restore of restorers) restore();
|
|
7964
|
+
};
|
|
7965
|
+
}
|
|
7966
|
+
};
|
|
7967
|
+
}
|
|
7968
|
+
|
|
7969
|
+
// src/lib/process-lifecycle/handlers.ts
|
|
7970
|
+
var SIGINT_NAME = "SIGINT";
|
|
7971
|
+
var SIGTERM_NAME = "SIGTERM";
|
|
7972
|
+
function createHandlers(deps) {
|
|
7973
|
+
let cleanupOnce = false;
|
|
7974
|
+
function killEachChild(signal) {
|
|
7975
|
+
deps.registry.forEach((child) => {
|
|
7976
|
+
if (!child.killed) child.kill(signal);
|
|
7977
|
+
});
|
|
7978
|
+
}
|
|
7979
|
+
function exitOnce(code, killSignal) {
|
|
7980
|
+
if (cleanupOnce) {
|
|
7981
|
+
deps.exitController.exit(code);
|
|
7982
|
+
return;
|
|
7983
|
+
}
|
|
7984
|
+
cleanupOnce = true;
|
|
7985
|
+
if (killSignal !== void 0) killEachChild(killSignal);
|
|
7986
|
+
deps.exitController.exit(code);
|
|
7987
|
+
}
|
|
7988
|
+
return {
|
|
7989
|
+
onSigint() {
|
|
7990
|
+
exitOnce(SIGINT_EXIT_CODE, SIGINT_NAME);
|
|
7991
|
+
},
|
|
7992
|
+
onSigterm() {
|
|
7993
|
+
exitOnce(SIGTERM_EXIT_CODE, SIGTERM_NAME);
|
|
7994
|
+
},
|
|
7995
|
+
onEpipe() {
|
|
7996
|
+
exitOnce(EPIPE_EXIT_CODE, SIGTERM_NAME);
|
|
7997
|
+
},
|
|
7998
|
+
onUncaught(_error) {
|
|
7999
|
+
exitOnce(UNCAUGHT_EXIT_CODE, SIGTERM_NAME);
|
|
8000
|
+
}
|
|
8001
|
+
};
|
|
8002
|
+
}
|
|
8003
|
+
|
|
8004
|
+
// src/lib/process-lifecycle/install.ts
|
|
8005
|
+
import { spawn } from "child_process";
|
|
8006
|
+
|
|
8007
|
+
// src/lib/process-lifecycle/registry.ts
|
|
8008
|
+
function createRegistry() {
|
|
8009
|
+
const tracked = /* @__PURE__ */ new Set();
|
|
8010
|
+
return {
|
|
8011
|
+
add(child) {
|
|
8012
|
+
tracked.add(child);
|
|
8013
|
+
},
|
|
8014
|
+
remove(child) {
|
|
8015
|
+
tracked.delete(child);
|
|
8016
|
+
},
|
|
8017
|
+
forEach(fn) {
|
|
8018
|
+
for (const child of tracked) fn(child);
|
|
8019
|
+
},
|
|
8020
|
+
get size() {
|
|
8021
|
+
return tracked.size;
|
|
8022
|
+
}
|
|
8023
|
+
};
|
|
8024
|
+
}
|
|
8025
|
+
|
|
8026
|
+
// src/lib/process-lifecycle/runner.ts
|
|
8027
|
+
function createLifecycleRunner(deps) {
|
|
8028
|
+
return {
|
|
8029
|
+
spawn(command, args, options) {
|
|
8030
|
+
const child = deps.spawn(command, args, options);
|
|
8031
|
+
deps.registry.add(child);
|
|
8032
|
+
child.on("exit", () => deps.registry.remove(child));
|
|
8033
|
+
return child;
|
|
8034
|
+
}
|
|
8035
|
+
};
|
|
8036
|
+
}
|
|
8037
|
+
|
|
8038
|
+
// src/lib/process-lifecycle/install.ts
|
|
8039
|
+
var moduleRegistry = createRegistry();
|
|
8040
|
+
var moduleExitController = {
|
|
8041
|
+
exit(code) {
|
|
8042
|
+
process.exit(code);
|
|
8043
|
+
}
|
|
8044
|
+
};
|
|
8045
|
+
var installed = false;
|
|
8046
|
+
var lifecycleProcessRunner = createLifecycleRunner({
|
|
8047
|
+
registry: moduleRegistry,
|
|
8048
|
+
spawn
|
|
8049
|
+
});
|
|
8050
|
+
var foregroundProcessRunner = { spawn };
|
|
8051
|
+
var processSignalTarget = {
|
|
8052
|
+
listeners: (signal) => process.listeners(signal),
|
|
8053
|
+
on: (signal, listener) => {
|
|
8054
|
+
process.on(signal, listener);
|
|
8055
|
+
},
|
|
8056
|
+
removeListener: (signal, listener) => {
|
|
8057
|
+
process.removeListener(signal, listener);
|
|
8058
|
+
}
|
|
8059
|
+
};
|
|
8060
|
+
var lifecycleSignalSuspender = createSignalSuspender(processSignalTarget);
|
|
8061
|
+
var EPIPE_CODE = "EPIPE";
|
|
8062
|
+
var UNCAUGHT_PREFIX = "Uncaught: ";
|
|
8063
|
+
var NEWLINE = "\n";
|
|
8064
|
+
function formatUncaught(error) {
|
|
8065
|
+
if (error instanceof Error && error.stack !== void 0) {
|
|
8066
|
+
return UNCAUGHT_PREFIX + error.stack + NEWLINE;
|
|
8067
|
+
}
|
|
8068
|
+
return UNCAUGHT_PREFIX + String(error) + NEWLINE;
|
|
8069
|
+
}
|
|
8070
|
+
function logUncaughtToStderr(error) {
|
|
8071
|
+
try {
|
|
8072
|
+
process.stderr.write(formatUncaught(error));
|
|
8073
|
+
} catch {
|
|
8074
|
+
}
|
|
8075
|
+
}
|
|
8076
|
+
function installLifecycle() {
|
|
8077
|
+
if (installed) return;
|
|
8078
|
+
installed = true;
|
|
8079
|
+
const handlers = createHandlers({
|
|
8080
|
+
registry: moduleRegistry,
|
|
8081
|
+
exitController: moduleExitController
|
|
8082
|
+
});
|
|
8083
|
+
process.on("uncaughtException", (error) => {
|
|
8084
|
+
logUncaughtToStderr(error);
|
|
8085
|
+
handlers.onUncaught(error);
|
|
8086
|
+
});
|
|
8087
|
+
process.on("unhandledRejection", (reason) => {
|
|
8088
|
+
logUncaughtToStderr(reason);
|
|
8089
|
+
handlers.onUncaught(reason);
|
|
8090
|
+
});
|
|
8091
|
+
process.on("SIGTERM", () => handlers.onSigterm());
|
|
8092
|
+
process.on("SIGINT", () => handlers.onSigint());
|
|
8093
|
+
process.stdout.on("error", (error) => {
|
|
8094
|
+
if (error.code === EPIPE_CODE) {
|
|
8095
|
+
handlers.onEpipe();
|
|
8096
|
+
return;
|
|
8097
|
+
}
|
|
8098
|
+
handlers.onUncaught(error);
|
|
8099
|
+
});
|
|
8100
|
+
process.stderr.on("error", (error) => {
|
|
8101
|
+
if (error.code === EPIPE_CODE) {
|
|
8102
|
+
handlers.onEpipe();
|
|
8103
|
+
return;
|
|
8104
|
+
}
|
|
8105
|
+
handlers.onUncaught(error);
|
|
8106
|
+
});
|
|
8107
|
+
}
|
|
8108
|
+
|
|
8109
|
+
// src/lib/process-lifecycle/managed-subprocess.ts
|
|
8110
|
+
var MANAGED_SUBPROCESS_STDIO = "pipe";
|
|
8111
|
+
function spawnManagedSubprocess(runner, command, args, options) {
|
|
8112
|
+
return runner.spawn(command, args, {
|
|
8113
|
+
...options,
|
|
8114
|
+
stdio: MANAGED_SUBPROCESS_STDIO
|
|
8115
|
+
});
|
|
8116
|
+
}
|
|
8117
|
+
|
|
7683
8118
|
// src/interfaces/cli/session/pick/launch-agent.ts
|
|
7684
8119
|
var LAUNCH_FAILURE_STATUS = 1;
|
|
7685
8120
|
function launchAgent(runner, suspender, command) {
|
|
7686
8121
|
const restoreSignals = suspender.suspend();
|
|
7687
|
-
return new Promise((
|
|
8122
|
+
return new Promise((resolve10) => {
|
|
7688
8123
|
let settled = false;
|
|
7689
8124
|
const settle = (status) => {
|
|
7690
8125
|
if (settled) return;
|
|
7691
8126
|
settled = true;
|
|
7692
8127
|
restoreSignals();
|
|
7693
|
-
|
|
8128
|
+
resolve10(status);
|
|
7694
8129
|
};
|
|
7695
8130
|
const child = runner.spawn(command.command, command.args, { stdio: "inherit" });
|
|
7696
8131
|
child.once("exit", (code) => settle(code ?? LAUNCH_FAILURE_STATUS));
|
|
@@ -7735,7 +8170,7 @@ function SessionRow({ session, selected, columns }) {
|
|
|
7735
8170
|
const priority = session.metadata.priority;
|
|
7736
8171
|
const marker = selected ? SESSION_PICKER_SELECTED_MARKER : " ";
|
|
7737
8172
|
const badge = priority === DEFAULT_PRIORITY ? "" : ` [${priority}]`;
|
|
7738
|
-
const reserved = marker
|
|
8173
|
+
const reserved = visibleWidth(marker) + visibleWidth(" ") + visibleWidth(session.id) + visibleWidth(badge) + visibleWidth(" ");
|
|
7739
8174
|
const goal = truncateToWidth(toSingleLine(session.metadata.goal), Math.max(MIN_GOAL_WIDTH, columns - reserved));
|
|
7740
8175
|
return /* @__PURE__ */ jsxs(Text, { wrap: "truncate", color: selected ? "cyan" : void 0, children: [
|
|
7741
8176
|
marker,
|
|
@@ -7760,7 +8195,8 @@ function PreviewPane({ session }) {
|
|
|
7760
8195
|
}
|
|
7761
8196
|
function SessionPicker({ sessions, onLaunch, onQuit, columns: columnsProp }) {
|
|
7762
8197
|
const { stdout } = useStdout();
|
|
7763
|
-
const
|
|
8198
|
+
const stdoutColumns = Reflect.get(stdout, "columns");
|
|
8199
|
+
const columns = columnsProp ?? (typeof stdoutColumns === "number" ? stdoutColumns : FALLBACK_COLUMNS);
|
|
7764
8200
|
const [state, setState] = useState(() => initialPickerState(sessions));
|
|
7765
8201
|
useInput((input, key) => {
|
|
7766
8202
|
const action = keyToAction(toPickerKey(input, key), state.mode);
|
|
@@ -7824,23 +8260,42 @@ async function readStdin() {
|
|
|
7824
8260
|
if (process.stdin.isTTY) {
|
|
7825
8261
|
return void 0;
|
|
7826
8262
|
}
|
|
7827
|
-
return new Promise((
|
|
8263
|
+
return new Promise((resolve10) => {
|
|
7828
8264
|
let data = "";
|
|
7829
|
-
process.stdin.setEncoding(
|
|
8265
|
+
process.stdin.setEncoding(SESSION_FILE_ENCODING);
|
|
7830
8266
|
process.stdin.on("data", (chunk) => {
|
|
7831
8267
|
data += chunk;
|
|
7832
8268
|
});
|
|
7833
8269
|
process.stdin.on("end", () => {
|
|
7834
|
-
|
|
8270
|
+
resolve10(data.length === 0 ? void 0 : data);
|
|
7835
8271
|
});
|
|
7836
8272
|
process.stdin.on("error", () => {
|
|
7837
|
-
|
|
8273
|
+
resolve10(void 0);
|
|
7838
8274
|
});
|
|
7839
8275
|
});
|
|
7840
8276
|
}
|
|
7841
|
-
function
|
|
7842
|
-
|
|
7843
|
-
|
|
8277
|
+
function writeOutput2(invocation, output) {
|
|
8278
|
+
invocation.io.writeStdout(`${output}
|
|
8279
|
+
`);
|
|
8280
|
+
}
|
|
8281
|
+
function writeError3(invocation, output) {
|
|
8282
|
+
invocation.io.writeStderr(`${output}
|
|
8283
|
+
`);
|
|
8284
|
+
}
|
|
8285
|
+
function writeInvocationWarning2(invocation, warning) {
|
|
8286
|
+
if (warning !== void 0) {
|
|
8287
|
+
writeError3(invocation, warning);
|
|
8288
|
+
}
|
|
8289
|
+
}
|
|
8290
|
+
function formatError2(error) {
|
|
8291
|
+
if (error instanceof Error) {
|
|
8292
|
+
return `${error.name}: ${error.message}`;
|
|
8293
|
+
}
|
|
8294
|
+
return toMessage(error);
|
|
8295
|
+
}
|
|
8296
|
+
function handleError2(invocation, error) {
|
|
8297
|
+
writeError3(invocation, `Error: ${formatError2(error)}`);
|
|
8298
|
+
return invocation.io.exit(1);
|
|
7844
8299
|
}
|
|
7845
8300
|
function colorFlagFromOption(colorOption) {
|
|
7846
8301
|
if (colorOption === true) {
|
|
@@ -7860,10 +8315,26 @@ function resolveListColorDecision(colorOption) {
|
|
|
7860
8315
|
});
|
|
7861
8316
|
}
|
|
7862
8317
|
function resolveListWidth() {
|
|
7863
|
-
|
|
8318
|
+
const columns = Reflect.get(process.stdout, "columns");
|
|
8319
|
+
const resolvedColumns = typeof columns === "number" ? columns : DEFAULT_LIST_WIDTH;
|
|
8320
|
+
return Math.max(LIST_TEXT_MIN_WIDTH, resolvedColumns);
|
|
8321
|
+
}
|
|
8322
|
+
function addSessionOptions(command, options) {
|
|
8323
|
+
for (const option of options) {
|
|
8324
|
+
if (option.defaultValue === void 0) {
|
|
8325
|
+
command.option(sessionOptionToken(option), option.description);
|
|
8326
|
+
} else {
|
|
8327
|
+
command.option(sessionOptionToken(option), option.description, option.defaultValue);
|
|
8328
|
+
}
|
|
8329
|
+
}
|
|
8330
|
+
return command;
|
|
7864
8331
|
}
|
|
7865
|
-
function registerSessionCommands(sessionCmd) {
|
|
7866
|
-
|
|
8332
|
+
function registerSessionCommands(sessionCmd, invocation) {
|
|
8333
|
+
const effectiveInvocationDir = () => invocation.resolveEffectiveInvocationDir();
|
|
8334
|
+
addSessionOptions(
|
|
8335
|
+
sessionCmd.command(sessionCommandToken(sessionCliDefinition.subcommands.list)).description(sessionCliDefinition.subcommands.list.description),
|
|
8336
|
+
sessionOptionsForSubcommand(sessionCliDefinition.subcommands.list)
|
|
8337
|
+
).action(
|
|
7867
8338
|
async (options) => {
|
|
7868
8339
|
try {
|
|
7869
8340
|
const output = await listCommand({
|
|
@@ -7873,36 +8344,44 @@ function registerSessionCommands(sessionCmd) {
|
|
|
7873
8344
|
color: resolveListColorDecision(options.color),
|
|
7874
8345
|
width: resolveListWidth(),
|
|
7875
8346
|
sessionsDir: options.sessionsDir,
|
|
7876
|
-
|
|
8347
|
+
cwd: effectiveInvocationDir(),
|
|
8348
|
+
onWarning: (warning) => writeInvocationWarning2(invocation, warning)
|
|
7877
8349
|
});
|
|
7878
|
-
|
|
8350
|
+
writeOutput2(invocation, output);
|
|
7879
8351
|
} catch (error) {
|
|
7880
|
-
handleError2(error);
|
|
8352
|
+
handleError2(invocation, error);
|
|
7881
8353
|
}
|
|
7882
8354
|
}
|
|
7883
8355
|
);
|
|
7884
|
-
|
|
8356
|
+
addSessionOptions(
|
|
8357
|
+
sessionCmd.command(sessionCommandToken(sessionCliDefinition.subcommands.pick)).description(sessionCliDefinition.subcommands.pick.description),
|
|
8358
|
+
sessionOptionsForSubcommand(sessionCliDefinition.subcommands.pick)
|
|
8359
|
+
).action(async (options) => {
|
|
7885
8360
|
try {
|
|
7886
8361
|
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
7887
|
-
|
|
7888
|
-
|
|
8362
|
+
writeError3(invocation, PICK_NON_TTY_MESSAGE);
|
|
8363
|
+
invocation.io.exit(1);
|
|
7889
8364
|
}
|
|
7890
8365
|
const sessions = await loadPickCandidates({
|
|
7891
8366
|
sessionsDir: options.sessionsDir,
|
|
7892
|
-
|
|
8367
|
+
cwd: effectiveInvocationDir(),
|
|
8368
|
+
onWarning: (warning) => writeInvocationWarning2(invocation, warning)
|
|
7893
8369
|
});
|
|
7894
8370
|
const choice = await runPicker(sessions);
|
|
7895
8371
|
if (choice !== null) {
|
|
7896
|
-
const reference = pickupReference(choice.session, options.sessionsDir,
|
|
8372
|
+
const reference = pickupReference(choice.session, options.sessionsDir, effectiveInvocationDir());
|
|
7897
8373
|
const command = buildPickupCommand(choice.runtime, choice.autoContinue, reference);
|
|
7898
8374
|
const code = await launchAgent(foregroundProcessRunner, lifecycleSignalSuspender, command);
|
|
7899
|
-
|
|
8375
|
+
invocation.io.exit(code);
|
|
7900
8376
|
}
|
|
7901
8377
|
} catch (error) {
|
|
7902
|
-
handleError2(error);
|
|
8378
|
+
handleError2(invocation, error);
|
|
7903
8379
|
}
|
|
7904
8380
|
});
|
|
7905
|
-
|
|
8381
|
+
addSessionOptions(
|
|
8382
|
+
sessionCmd.command(sessionCommandToken(sessionCliDefinition.subcommands.todo)).description(sessionCliDefinition.subcommands.todo.description),
|
|
8383
|
+
sessionOptionsForSubcommand(sessionCliDefinition.subcommands.todo)
|
|
8384
|
+
).action(async (options) => {
|
|
7906
8385
|
try {
|
|
7907
8386
|
const output = await listCommand({
|
|
7908
8387
|
status: SESSION_STATUSES[0],
|
|
@@ -7911,129 +8390,159 @@ function registerSessionCommands(sessionCmd) {
|
|
|
7911
8390
|
color: resolveListColorDecision(options.color),
|
|
7912
8391
|
width: resolveListWidth(),
|
|
7913
8392
|
sessionsDir: options.sessionsDir,
|
|
7914
|
-
|
|
8393
|
+
cwd: effectiveInvocationDir(),
|
|
8394
|
+
onWarning: (warning) => writeInvocationWarning2(invocation, warning)
|
|
7915
8395
|
});
|
|
7916
|
-
|
|
8396
|
+
writeOutput2(invocation, output);
|
|
7917
8397
|
} catch (error) {
|
|
7918
|
-
handleError2(error);
|
|
8398
|
+
handleError2(invocation, error);
|
|
7919
8399
|
}
|
|
7920
8400
|
});
|
|
7921
|
-
|
|
8401
|
+
addSessionOptions(
|
|
8402
|
+
sessionCmd.command(sessionCommandToken(sessionCliDefinition.subcommands.show)).description(sessionCliDefinition.subcommands.show.description),
|
|
8403
|
+
sessionOptionsForSubcommand(sessionCliDefinition.subcommands.show)
|
|
8404
|
+
).action(async (ids, options) => {
|
|
7922
8405
|
try {
|
|
7923
8406
|
const output = await showCommand2({
|
|
7924
8407
|
sessionIds: ids,
|
|
7925
8408
|
format: options.json ? SESSION_LIST_FORMAT.JSON : SESSION_LIST_FORMAT.TEXT,
|
|
7926
8409
|
sessionsDir: options.sessionsDir,
|
|
7927
|
-
|
|
8410
|
+
cwd: effectiveInvocationDir(),
|
|
8411
|
+
onWarning: (warning) => writeInvocationWarning2(invocation, warning)
|
|
7928
8412
|
});
|
|
7929
|
-
|
|
8413
|
+
writeOutput2(invocation, output);
|
|
7930
8414
|
} catch (error) {
|
|
7931
|
-
handleError2(error);
|
|
8415
|
+
handleError2(invocation, error);
|
|
7932
8416
|
}
|
|
7933
8417
|
});
|
|
7934
|
-
|
|
8418
|
+
addSessionOptions(
|
|
8419
|
+
sessionCmd.command(sessionCommandToken(sessionCliDefinition.subcommands.pickup)).description(sessionCliDefinition.subcommands.pickup.description),
|
|
8420
|
+
sessionOptionsForSubcommand(sessionCliDefinition.subcommands.pickup)
|
|
8421
|
+
).addHelpText("after", PICKUP_SELECTION_HELP).action(async (ids, options) => {
|
|
7935
8422
|
try {
|
|
7936
8423
|
if (ids.length === 0 && !options.auto) {
|
|
7937
|
-
|
|
7938
|
-
|
|
8424
|
+
writeError3(invocation, "Error: Either session ID or --auto flag is required");
|
|
8425
|
+
invocation.io.exit(1);
|
|
7939
8426
|
}
|
|
7940
8427
|
const output = await pickupCommand({
|
|
7941
8428
|
sessionIds: ids,
|
|
7942
8429
|
auto: options.auto,
|
|
8430
|
+
noInject: options.inject === false,
|
|
7943
8431
|
sessionsDir: options.sessionsDir,
|
|
7944
|
-
|
|
8432
|
+
cwd: effectiveInvocationDir(),
|
|
8433
|
+
onWarning: (warning) => writeInvocationWarning2(invocation, warning)
|
|
7945
8434
|
});
|
|
7946
|
-
|
|
8435
|
+
writeOutput2(invocation, output);
|
|
7947
8436
|
} catch (error) {
|
|
7948
|
-
handleError2(error);
|
|
8437
|
+
handleError2(invocation, error);
|
|
7949
8438
|
}
|
|
7950
8439
|
});
|
|
7951
|
-
|
|
8440
|
+
addSessionOptions(
|
|
8441
|
+
sessionCmd.command(sessionCommandToken(sessionCliDefinition.subcommands.release)).description(sessionCliDefinition.subcommands.release.description),
|
|
8442
|
+
sessionOptionsForSubcommand(sessionCliDefinition.subcommands.release)
|
|
8443
|
+
).action(async (ids, options) => {
|
|
7952
8444
|
try {
|
|
7953
8445
|
const output = await releaseCommand({
|
|
7954
8446
|
sessionIds: ids,
|
|
7955
8447
|
sessionsDir: options.sessionsDir,
|
|
7956
|
-
|
|
8448
|
+
cwd: effectiveInvocationDir(),
|
|
8449
|
+
onWarning: (warning) => writeInvocationWarning2(invocation, warning)
|
|
7957
8450
|
});
|
|
7958
|
-
|
|
8451
|
+
writeOutput2(invocation, output);
|
|
7959
8452
|
} catch (error) {
|
|
7960
|
-
handleError2(error);
|
|
8453
|
+
handleError2(invocation, error);
|
|
7961
8454
|
}
|
|
7962
8455
|
});
|
|
7963
|
-
|
|
8456
|
+
addSessionOptions(
|
|
8457
|
+
sessionCmd.command(sessionCommandToken(sessionCliDefinition.subcommands.handoff)).description(sessionCliDefinition.subcommands.handoff.description),
|
|
8458
|
+
sessionOptionsForSubcommand(sessionCliDefinition.subcommands.handoff)
|
|
8459
|
+
).addHelpText("after", HANDOFF_FRONTMATTER_HELP).action(async (options) => {
|
|
7964
8460
|
try {
|
|
7965
8461
|
const content = await readStdin();
|
|
7966
8462
|
const result = await handoffCommand({
|
|
7967
8463
|
content,
|
|
7968
8464
|
sessionsDir: options.sessionsDir,
|
|
8465
|
+
cwd: effectiveInvocationDir(),
|
|
7969
8466
|
env: process.env
|
|
7970
8467
|
});
|
|
7971
|
-
|
|
8468
|
+
writeOutput2(invocation, result.output);
|
|
7972
8469
|
} catch (error) {
|
|
7973
8470
|
if (error instanceof SessionHandoffBaseError) {
|
|
7974
8471
|
if (error.checklist !== null) {
|
|
7975
|
-
|
|
8472
|
+
writeError3(invocation, renderHandoffBaseChecklist(error.checklist));
|
|
7976
8473
|
} else if (!error.silent) {
|
|
7977
|
-
|
|
8474
|
+
writeError3(invocation, `Error: ${error.name}: ${error.message}`);
|
|
7978
8475
|
}
|
|
7979
|
-
|
|
8476
|
+
invocation.io.exit(1);
|
|
7980
8477
|
}
|
|
7981
|
-
handleError2(error);
|
|
8478
|
+
handleError2(invocation, error);
|
|
7982
8479
|
}
|
|
7983
8480
|
});
|
|
7984
|
-
|
|
8481
|
+
addSessionOptions(
|
|
8482
|
+
sessionCmd.command(sessionCommandToken(sessionCliDefinition.subcommands.delete)).description(sessionCliDefinition.subcommands.delete.description),
|
|
8483
|
+
sessionOptionsForSubcommand(sessionCliDefinition.subcommands.delete)
|
|
8484
|
+
).action(async (ids, options) => {
|
|
7985
8485
|
try {
|
|
7986
8486
|
const output = await deleteCommand({
|
|
7987
8487
|
sessionIds: ids,
|
|
7988
8488
|
sessionsDir: options.sessionsDir,
|
|
7989
|
-
|
|
8489
|
+
cwd: effectiveInvocationDir(),
|
|
8490
|
+
onWarning: (warning) => writeInvocationWarning2(invocation, warning)
|
|
7990
8491
|
});
|
|
7991
|
-
|
|
8492
|
+
writeOutput2(invocation, output);
|
|
7992
8493
|
} catch (error) {
|
|
7993
|
-
handleError2(error);
|
|
8494
|
+
handleError2(invocation, error);
|
|
7994
8495
|
}
|
|
7995
8496
|
});
|
|
7996
|
-
|
|
8497
|
+
addSessionOptions(
|
|
8498
|
+
sessionCmd.command(sessionCommandToken(sessionCliDefinition.subcommands.prune)).description(sessionCliDefinition.subcommands.prune.description),
|
|
8499
|
+
sessionOptionsForSubcommand(sessionCliDefinition.subcommands.prune)
|
|
8500
|
+
).action(async (options) => {
|
|
7997
8501
|
try {
|
|
7998
8502
|
const keep = options.keep ? Number.parseInt(options.keep, 10) : void 0;
|
|
7999
8503
|
const output = await pruneCommand({
|
|
8000
8504
|
keep,
|
|
8001
8505
|
dryRun: options.dryRun,
|
|
8002
8506
|
sessionsDir: options.sessionsDir,
|
|
8003
|
-
|
|
8507
|
+
cwd: effectiveInvocationDir(),
|
|
8508
|
+
onWarning: (warning) => writeInvocationWarning2(invocation, warning)
|
|
8004
8509
|
});
|
|
8005
|
-
|
|
8510
|
+
writeOutput2(invocation, output);
|
|
8006
8511
|
} catch (error) {
|
|
8007
8512
|
if (error instanceof PruneValidationError) {
|
|
8008
|
-
|
|
8009
|
-
|
|
8513
|
+
writeError3(invocation, `Error: ${error.message}`);
|
|
8514
|
+
invocation.io.exit(1);
|
|
8010
8515
|
}
|
|
8011
|
-
handleError2(error);
|
|
8516
|
+
handleError2(invocation, error);
|
|
8012
8517
|
}
|
|
8013
8518
|
});
|
|
8014
|
-
|
|
8519
|
+
addSessionOptions(
|
|
8520
|
+
sessionCmd.command(sessionCommandToken(sessionCliDefinition.subcommands.archive)).description(sessionCliDefinition.subcommands.archive.description),
|
|
8521
|
+
sessionOptionsForSubcommand(sessionCliDefinition.subcommands.archive)
|
|
8522
|
+
).action(async (ids, options) => {
|
|
8015
8523
|
try {
|
|
8016
8524
|
const output = await archiveCommand({
|
|
8017
8525
|
sessionIds: ids,
|
|
8018
8526
|
sessionsDir: options.sessionsDir,
|
|
8019
|
-
|
|
8527
|
+
cwd: effectiveInvocationDir(),
|
|
8528
|
+
onWarning: (warning) => writeInvocationWarning2(invocation, warning)
|
|
8020
8529
|
});
|
|
8021
|
-
|
|
8530
|
+
writeOutput2(invocation, output);
|
|
8022
8531
|
} catch (error) {
|
|
8023
8532
|
if (error instanceof SessionAlreadyArchivedError) {
|
|
8024
|
-
|
|
8025
|
-
|
|
8533
|
+
writeError3(invocation, `Error: ${error.message}`);
|
|
8534
|
+
invocation.io.exit(1);
|
|
8026
8535
|
}
|
|
8027
|
-
handleError2(error);
|
|
8536
|
+
handleError2(invocation, error);
|
|
8028
8537
|
}
|
|
8029
8538
|
});
|
|
8030
8539
|
}
|
|
8031
8540
|
var sessionDomain = {
|
|
8032
|
-
name:
|
|
8033
|
-
description:
|
|
8034
|
-
register: (
|
|
8035
|
-
const sessionCmd =
|
|
8036
|
-
registerSessionCommands(sessionCmd);
|
|
8541
|
+
name: sessionCliDefinition.domain.commandName,
|
|
8542
|
+
description: sessionCliDefinition.domain.description,
|
|
8543
|
+
register: (program, invocation) => {
|
|
8544
|
+
const sessionCmd = program.command(sessionCliDefinition.domain.commandName).description(sessionCliDefinition.domain.description).addHelpText("after", SESSION_FORMAT_HELP);
|
|
8545
|
+
registerSessionCommands(sessionCmd, invocation);
|
|
8037
8546
|
}
|
|
8038
8547
|
};
|
|
8039
8548
|
|
|
@@ -8142,7 +8651,7 @@ function recognizeSpecTreeFilesystemEntry(record6, options = {}) {
|
|
|
8142
8651
|
if (record6.type === SPEC_TREE_FILESYSTEM_RECORD_TYPE.DIRECTORY) {
|
|
8143
8652
|
return recognizeDirectoryRecord(record6, name, registry, schemaVersions);
|
|
8144
8653
|
}
|
|
8145
|
-
if (record6.
|
|
8654
|
+
if (record6.parentId !== void 0 && isEvidenceFile(record6.relativePath)) {
|
|
8146
8655
|
return {
|
|
8147
8656
|
type: SPEC_TREE_ENTRY_TYPE.EVIDENCE,
|
|
8148
8657
|
id: record6.relativePath,
|
|
@@ -8151,22 +8660,19 @@ function recognizeSpecTreeFilesystemEntry(record6, options = {}) {
|
|
|
8151
8660
|
ref: sourceRefForRelativePath(record6.relativePath)
|
|
8152
8661
|
};
|
|
8153
8662
|
}
|
|
8154
|
-
|
|
8155
|
-
|
|
8156
|
-
|
|
8157
|
-
|
|
8158
|
-
|
|
8159
|
-
|
|
8160
|
-
|
|
8161
|
-
|
|
8162
|
-
|
|
8163
|
-
|
|
8164
|
-
|
|
8165
|
-
|
|
8166
|
-
|
|
8167
|
-
};
|
|
8168
|
-
}
|
|
8169
|
-
return null;
|
|
8663
|
+
const decisionMatch = matchKindSuffix(name, registry, SPEC_TREE_KIND_CATEGORY.DECISION);
|
|
8664
|
+
if (decisionMatch === null) return null;
|
|
8665
|
+
const parsed = parseOrderedSlug(stripSuffix(name, decisionMatch.definition.suffix));
|
|
8666
|
+
if (parsed === null) return null;
|
|
8667
|
+
return {
|
|
8668
|
+
type: SPEC_TREE_ENTRY_TYPE.DECISION,
|
|
8669
|
+
kind: decisionMatch.kind,
|
|
8670
|
+
id: record6.relativePath,
|
|
8671
|
+
order: parsed.order,
|
|
8672
|
+
slug: parsed.slug,
|
|
8673
|
+
parentId: record6.parentId,
|
|
8674
|
+
ref: sourceRefForRelativePath(record6.relativePath)
|
|
8675
|
+
};
|
|
8170
8676
|
}
|
|
8171
8677
|
function recognizeDirectoryRecord(record6, name, registry, schemaVersions) {
|
|
8172
8678
|
const canonical = canonicalNamingSchemaVersion(schemaVersions);
|
|
@@ -8195,6 +8701,7 @@ function recognizeDirectoryRecord(record6, name, registry, schemaVersions) {
|
|
|
8195
8701
|
ref: sourceRefForRelativePath(record6.relativePath)
|
|
8196
8702
|
};
|
|
8197
8703
|
}
|
|
8704
|
+
if (matchKindSuffix(name, registry, SPEC_TREE_KIND_CATEGORY.DECISION) !== null) return null;
|
|
8198
8705
|
if (parseOrderedSlug(name) !== null) {
|
|
8199
8706
|
return {
|
|
8200
8707
|
type: SPEC_TREE_ENTRY_TYPE.INVALID,
|
|
@@ -8355,7 +8862,8 @@ function groupDecisions(entries) {
|
|
|
8355
8862
|
if (entry.parentId === void 0) continue;
|
|
8356
8863
|
const group = grouped.get(entry.parentId) ?? [];
|
|
8357
8864
|
group.push(entry);
|
|
8358
|
-
|
|
8865
|
+
group.sort(compareOrderedEntries);
|
|
8866
|
+
grouped.set(entry.parentId, group);
|
|
8359
8867
|
}
|
|
8360
8868
|
return grouped;
|
|
8361
8869
|
}
|
|
@@ -8425,7 +8933,7 @@ async function* walkFilesystemDirectory(context) {
|
|
|
8425
8933
|
const relativePath = joinSpecTreePath(context.relativePath, entry.name);
|
|
8426
8934
|
const refPath = joinSpecTreePath(SPEC_TREE_CONFIG.ROOT_DIRECTORY, relativePath);
|
|
8427
8935
|
if (!await context.includePath(refPath)) continue;
|
|
8428
|
-
const recordType =
|
|
8936
|
+
const recordType = filesystemRecordType(entry);
|
|
8429
8937
|
if (recordType === void 0) continue;
|
|
8430
8938
|
const sourceEntry = recognizeSpecTreeFilesystemEntry(
|
|
8431
8939
|
{ type: recordType, relativePath, parentId: context.parentId },
|
|
@@ -8439,11 +8947,19 @@ async function* walkFilesystemDirectory(context) {
|
|
|
8439
8947
|
registry: context.registry,
|
|
8440
8948
|
schemaVersions: context.schemaVersions,
|
|
8441
8949
|
includePath: context.includePath,
|
|
8442
|
-
parentId:
|
|
8950
|
+
parentId: childParentId(context, sourceEntry)
|
|
8443
8951
|
});
|
|
8444
8952
|
}
|
|
8445
8953
|
}
|
|
8446
8954
|
}
|
|
8955
|
+
function filesystemRecordType(entry) {
|
|
8956
|
+
if (entry.isDirectory()) return SPEC_TREE_FILESYSTEM_RECORD_TYPE.DIRECTORY;
|
|
8957
|
+
if (entry.isFile()) return SPEC_TREE_FILESYSTEM_RECORD_TYPE.FILE;
|
|
8958
|
+
return void 0;
|
|
8959
|
+
}
|
|
8960
|
+
function childParentId(context, sourceEntry) {
|
|
8961
|
+
return sourceEntry?.type === SPEC_TREE_ENTRY_TYPE.NODE ? sourceEntry.id : context.parentId;
|
|
8962
|
+
}
|
|
8447
8963
|
function matchKindSuffix(name, registry, category) {
|
|
8448
8964
|
for (const [kind, definition] of Object.entries(registry)) {
|
|
8449
8965
|
if (definition.category === category && name.endsWith(definition.suffix)) {
|
|
@@ -8470,8 +8986,8 @@ function isProductFile(relativePath) {
|
|
|
8470
8986
|
function isEvidenceFile(relativePath) {
|
|
8471
8987
|
const segments = relativePath.split(SPEC_TREE_PATH_SEPARATOR);
|
|
8472
8988
|
if (segments.length < SPEC_TREE_MIN_EVIDENCE_PATH_SEGMENTS) return false;
|
|
8473
|
-
const filename = segments
|
|
8474
|
-
const directoryName = segments
|
|
8989
|
+
const filename = segments.at(-1) ?? "";
|
|
8990
|
+
const directoryName = segments.at(-SPEC_TREE_PARENT_SEGMENT_OFFSET);
|
|
8475
8991
|
const filenameSegments = filename.split(SPEC_TREE_EVIDENCE_FILE.SEGMENT_SEPARATOR);
|
|
8476
8992
|
return directoryName === SPEC_TREE_EVIDENCE_FILE.DIRECTORY_NAME && SPEC_TREE_EVIDENCE_FILE_TAILS.some(
|
|
8477
8993
|
(tail) => SPEC_TREE_EVIDENCE_FILE.MODES.some(
|
|
@@ -8510,7 +9026,7 @@ function stripSuffix(value, suffix) {
|
|
|
8510
9026
|
}
|
|
8511
9027
|
function readLastPathSegment(relativePath) {
|
|
8512
9028
|
const segments = relativePath.split(SPEC_TREE_PATH_SEPARATOR);
|
|
8513
|
-
return segments
|
|
9029
|
+
return segments.at(-1) ?? relativePath;
|
|
8514
9030
|
}
|
|
8515
9031
|
function joinSpecTreePath(...segments) {
|
|
8516
9032
|
return segments.filter((segment) => segment.length > 0).join(SPEC_TREE_PATH_SEPARATOR);
|
|
@@ -8550,7 +9066,7 @@ async function nextCommand(options = {}) {
|
|
|
8550
9066
|
return formatNextSpecTreeNode(snapshot2);
|
|
8551
9067
|
}
|
|
8552
9068
|
const productDir = await resolveSpecProductDir(
|
|
8553
|
-
options.cwd ??
|
|
9069
|
+
options.cwd ?? CONFIG_PROCESS_CWD.read(),
|
|
8554
9070
|
options.gitDependencies,
|
|
8555
9071
|
options.onWarning
|
|
8556
9072
|
);
|
|
@@ -9436,7 +9952,7 @@ function createNodeStatusProvider(productDir) {
|
|
|
9436
9952
|
|
|
9437
9953
|
// src/lib/node-status/update.ts
|
|
9438
9954
|
import { mkdir as mkdir4, readdir as readdir7, rm, writeFile as writeFile2 } from "fs/promises";
|
|
9439
|
-
import { dirname as
|
|
9955
|
+
import { dirname as dirname7, join as join21 } from "path";
|
|
9440
9956
|
var NODE_STATUS_TEXT_ENCODING = "utf8";
|
|
9441
9957
|
async function updateNodeStatus(options) {
|
|
9442
9958
|
const { productDir, resolveOutcome } = options;
|
|
@@ -9498,7 +10014,7 @@ function evidencePath(entry) {
|
|
|
9498
10014
|
return entry.ref?.path ?? entry.id;
|
|
9499
10015
|
}
|
|
9500
10016
|
async function writeNodeStatus(filePath, verification) {
|
|
9501
|
-
await mkdir4(
|
|
10017
|
+
await mkdir4(dirname7(filePath), { recursive: true });
|
|
9502
10018
|
await writeFile2(filePath, serializeNodeStatus(createNodeStatusFile(verification)), NODE_STATUS_TEXT_ENCODING);
|
|
9503
10019
|
}
|
|
9504
10020
|
function nodeStatusPath(productDir, nodeId) {
|
|
@@ -9634,12 +10150,21 @@ var SPEC_STATUS_TABLE_HEADER = formatTableRow([
|
|
|
9634
10150
|
TABLE_HEADER.PATH,
|
|
9635
10151
|
TABLE_HEADER.STATE
|
|
9636
10152
|
]);
|
|
10153
|
+
var SpecStatusUpdateRequiresProductDirError = class extends Error {
|
|
10154
|
+
constructor() {
|
|
10155
|
+
super("Cannot update spec status for an injected in-memory source");
|
|
10156
|
+
this.name = "SpecStatusUpdateRequiresProductDirError";
|
|
10157
|
+
}
|
|
10158
|
+
};
|
|
9637
10159
|
async function statusCommand(options = {}) {
|
|
9638
10160
|
if (options.source !== void 0) {
|
|
10161
|
+
if (options.update === true) {
|
|
10162
|
+
throw new SpecStatusUpdateRequiresProductDirError();
|
|
10163
|
+
}
|
|
9639
10164
|
return renderSpecStatus(projectSpecTree(await readSpecTree({ source: options.source })), options.format);
|
|
9640
10165
|
}
|
|
9641
10166
|
const productDir = await resolveSpecProductDir(
|
|
9642
|
-
options.cwd ??
|
|
10167
|
+
options.cwd ?? CONFIG_PROCESS_CWD.read(),
|
|
9643
10168
|
options.gitDependencies,
|
|
9644
10169
|
options.onWarning
|
|
9645
10170
|
);
|
|
@@ -9706,7 +10231,8 @@ function flattenProjectionNodes(nodes) {
|
|
|
9706
10231
|
return nodes.flatMap((node) => [node, ...flattenProjectionNodes(node.children)]);
|
|
9707
10232
|
}
|
|
9708
10233
|
function formatTableRow(values) {
|
|
9709
|
-
|
|
10234
|
+
const separator = ` ${TABLE_SEPARATOR} `;
|
|
10235
|
+
return `${TABLE_SEPARATOR} ${values.join(separator)} ${TABLE_SEPARATOR}`;
|
|
9710
10236
|
}
|
|
9711
10237
|
function formatNodeLabel(node) {
|
|
9712
10238
|
return [
|
|
@@ -9717,7 +10243,7 @@ function formatNodeLabel(node) {
|
|
|
9717
10243
|
}
|
|
9718
10244
|
|
|
9719
10245
|
// src/test/languages/python.ts
|
|
9720
|
-
import { basename as basename3, dirname as
|
|
10246
|
+
import { basename as basename3, dirname as dirname8, join as join22 } from "path/posix";
|
|
9721
10247
|
|
|
9722
10248
|
// src/validation/discovery/language-finder.ts
|
|
9723
10249
|
import fs6 from "fs";
|
|
@@ -9788,10 +10314,10 @@ function coveredProductInputPaths(coveredTestPaths2) {
|
|
|
9788
10314
|
const paths = /* @__PURE__ */ new Set();
|
|
9789
10315
|
for (const testPath of coveredTestPaths2) {
|
|
9790
10316
|
if (!matchesTestFile(testPath)) continue;
|
|
9791
|
-
let directory =
|
|
10317
|
+
let directory = dirname8(testPath);
|
|
9792
10318
|
while (directory !== "." && directory.length > 0) {
|
|
9793
10319
|
paths.add(join22(directory, PYTHON_PRODUCT_INPUT_PATH.CONFTEST));
|
|
9794
|
-
const parent =
|
|
10320
|
+
const parent = dirname8(directory);
|
|
9795
10321
|
if (parent === directory) break;
|
|
9796
10322
|
directory = parent;
|
|
9797
10323
|
}
|
|
@@ -10015,7 +10541,17 @@ var VALID_STATUS_FORMATS = [
|
|
|
10015
10541
|
OUTPUT_FORMAT.TABLE
|
|
10016
10542
|
];
|
|
10017
10543
|
var UNPRINTABLE_ERROR_MESSAGE = "unprintable error";
|
|
10018
|
-
function
|
|
10544
|
+
function writeOutput3(io, output) {
|
|
10545
|
+
io.writeStdout(`${output}
|
|
10546
|
+
`);
|
|
10547
|
+
}
|
|
10548
|
+
function writeInvocationWarning3(io, warning) {
|
|
10549
|
+
if (warning !== void 0) {
|
|
10550
|
+
io.writeStderr(`${warning}
|
|
10551
|
+
`);
|
|
10552
|
+
}
|
|
10553
|
+
}
|
|
10554
|
+
function handleCommandError(io, error) {
|
|
10019
10555
|
let message;
|
|
10020
10556
|
if (error instanceof Error) {
|
|
10021
10557
|
message = error.message;
|
|
@@ -10028,8 +10564,9 @@ function handleCommandError(error) {
|
|
|
10028
10564
|
message = UNPRINTABLE_ERROR_MESSAGE;
|
|
10029
10565
|
}
|
|
10030
10566
|
}
|
|
10031
|
-
|
|
10032
|
-
|
|
10567
|
+
io.writeStderr(`Error: ${message}
|
|
10568
|
+
`);
|
|
10569
|
+
return io.exit(1);
|
|
10033
10570
|
}
|
|
10034
10571
|
function resolveStatusFormat(options) {
|
|
10035
10572
|
if (options.json === true) {
|
|
@@ -10045,41 +10582,43 @@ function resolveStatusFormat(options) {
|
|
|
10045
10582
|
`${SPEC_STATUS_FORMAT_MESSAGE.INVALID_PREFIX} "${options.format}". Must be one of: ${VALID_STATUS_FORMATS.join(", ")}`
|
|
10046
10583
|
);
|
|
10047
10584
|
}
|
|
10048
|
-
function registerSpecCommands(specCmd) {
|
|
10585
|
+
function registerSpecCommands(specCmd, invocation) {
|
|
10586
|
+
const productDir = () => invocation.resolveProductContext().productDir;
|
|
10587
|
+
const onWarning = (warning) => writeInvocationWarning3(invocation.io, warning);
|
|
10049
10588
|
specCmd.command(SPEC_DOMAIN_CLI.STATUS_COMMAND).description("Get product status").option(SPEC_DOMAIN_CLI.JSON_OPTION, "Output as JSON").option(SPEC_DOMAIN_CLI.FORMAT_OPTION_DEFINITION, "Output format (text|json|markdown|table)").option(SPEC_DOMAIN_CLI.UPDATE_OPTION, "Refresh each node's spx.status.json before reporting").action(async (options) => {
|
|
10050
10589
|
try {
|
|
10051
10590
|
const format2 = resolveStatusFormat(options);
|
|
10052
10591
|
const output = options.update === true ? await statusCommand({
|
|
10053
|
-
cwd:
|
|
10592
|
+
cwd: productDir(),
|
|
10054
10593
|
format: format2,
|
|
10055
|
-
onWarning
|
|
10594
|
+
onWarning,
|
|
10056
10595
|
update: true,
|
|
10057
|
-
resolveOutcomeFor: (
|
|
10058
|
-
productDir,
|
|
10596
|
+
resolveOutcomeFor: (productDir2) => createNodeOutcomeResolver({
|
|
10597
|
+
productDir: productDir2,
|
|
10059
10598
|
registry: testingRegistry,
|
|
10060
|
-
runnerDepsFor: createRunnerDepsFor(
|
|
10599
|
+
runnerDepsFor: createRunnerDepsFor(productDir2, process.stderr)
|
|
10061
10600
|
})
|
|
10062
|
-
}) : await statusCommand({ cwd:
|
|
10063
|
-
|
|
10601
|
+
}) : await statusCommand({ cwd: productDir(), format: format2, onWarning });
|
|
10602
|
+
writeOutput3(invocation.io, output);
|
|
10064
10603
|
} catch (error) {
|
|
10065
|
-
handleCommandError(error);
|
|
10604
|
+
handleCommandError(invocation.io, error);
|
|
10066
10605
|
}
|
|
10067
10606
|
});
|
|
10068
10607
|
specCmd.command(SPEC_DOMAIN_CLI.NEXT_COMMAND).description("Find next spec-tree node to work on").action(async () => {
|
|
10069
10608
|
try {
|
|
10070
|
-
const output = await nextCommand({ cwd:
|
|
10071
|
-
|
|
10609
|
+
const output = await nextCommand({ cwd: productDir(), onWarning });
|
|
10610
|
+
writeOutput3(invocation.io, output);
|
|
10072
10611
|
} catch (error) {
|
|
10073
|
-
handleCommandError(error);
|
|
10612
|
+
handleCommandError(invocation.io, error);
|
|
10074
10613
|
}
|
|
10075
10614
|
});
|
|
10076
10615
|
}
|
|
10077
10616
|
var specDomain = {
|
|
10078
10617
|
name: "spec",
|
|
10079
10618
|
description: "Manage spec workflow",
|
|
10080
|
-
register: (
|
|
10081
|
-
const specCmd =
|
|
10082
|
-
registerSpecCommands(specCmd);
|
|
10619
|
+
register: (program, invocation) => {
|
|
10620
|
+
const specCmd = program.command(SPEC_DOMAIN_CLI.COMMAND).description("Manage spec workflow");
|
|
10621
|
+
registerSpecCommands(specCmd, invocation);
|
|
10083
10622
|
}
|
|
10084
10623
|
};
|
|
10085
10624
|
|
|
@@ -10193,33 +10732,33 @@ var UNRESOLVED_TARGETS_WARNING = "No tests matched these operands";
|
|
|
10193
10732
|
var TESTING_PRODUCT_DIR_WARNING = {
|
|
10194
10733
|
NOT_GIT_REPOSITORY: `Warning: Not in a git repository. Reading ${SPEC_TREE_CONFIG.ROOT_DIRECTORY} tests relative to the current working directory.`
|
|
10195
10734
|
};
|
|
10196
|
-
async function resolveTestProductDir() {
|
|
10197
|
-
const { productDir, isGitRepo } = await detectWorktreeProductRoot(
|
|
10735
|
+
async function resolveTestProductDir(cwd, writeWarning) {
|
|
10736
|
+
const { productDir, isGitRepo } = await detectWorktreeProductRoot(cwd);
|
|
10198
10737
|
writeWarning(isGitRepo ? void 0 : TESTING_PRODUCT_DIR_WARNING.NOT_GIT_REPOSITORY);
|
|
10199
10738
|
return productDir;
|
|
10200
10739
|
}
|
|
10201
|
-
async function runTestsThroughCommand(productDir, passing, targets) {
|
|
10740
|
+
async function runTestsThroughCommand(productDir, passing, io, targets) {
|
|
10202
10741
|
try {
|
|
10203
10742
|
return await runTestsCommand(
|
|
10204
10743
|
{ productDir, passing, targets },
|
|
10205
10744
|
{ registry: testingRegistry, runnerDepsFor: createRunnerDepsFor(productDir) }
|
|
10206
10745
|
);
|
|
10207
10746
|
} catch (error) {
|
|
10208
|
-
|
|
10747
|
+
io.writeStderr(`${error instanceof Error ? error.message : String(error)}
|
|
10209
10748
|
`);
|
|
10210
|
-
|
|
10749
|
+
io.exit(PROCESS_FAILURE_EXIT_CODE);
|
|
10211
10750
|
}
|
|
10212
10751
|
}
|
|
10213
|
-
async function runAgentTestsThroughCommand(productDir, passing, targets) {
|
|
10752
|
+
async function runAgentTestsThroughCommand(productDir, passing, io, targets) {
|
|
10214
10753
|
try {
|
|
10215
10754
|
return await runTestsCommand(
|
|
10216
10755
|
{ productDir, passing, targets },
|
|
10217
10756
|
{ registry: testingRegistry, runnerDepsFor: createAgentRunnerDepsFor(productDir) }
|
|
10218
10757
|
);
|
|
10219
10758
|
} catch (error) {
|
|
10220
|
-
|
|
10759
|
+
io.writeStderr(`${error instanceof Error ? error.message : String(error)}
|
|
10221
10760
|
`);
|
|
10222
|
-
|
|
10761
|
+
io.exit(PROCESS_FAILURE_EXIT_CODE);
|
|
10223
10762
|
}
|
|
10224
10763
|
}
|
|
10225
10764
|
function unreportedGroups2(result) {
|
|
@@ -10258,19 +10797,23 @@ function targetSelection(targets, options, command) {
|
|
|
10258
10797
|
const parentOptions = command.parent?.opts() ?? {};
|
|
10259
10798
|
return { operands: targets, recursive: options.recursive === true || parentOptions.recursive === true };
|
|
10260
10799
|
}
|
|
10261
|
-
|
|
10262
|
-
|
|
10263
|
-
|
|
10264
|
-
|
|
10265
|
-
|
|
10266
|
-
|
|
10267
|
-
}
|
|
10268
|
-
|
|
10269
|
-
|
|
10270
|
-
|
|
10271
|
-
|
|
10272
|
-
|
|
10273
|
-
|
|
10800
|
+
function defaultTestingCliDependencies(invocation) {
|
|
10801
|
+
const writeWarning = (warning) => {
|
|
10802
|
+
if (warning !== void 0) {
|
|
10803
|
+
invocation.io.writeStderr(`${warning}
|
|
10804
|
+
`);
|
|
10805
|
+
}
|
|
10806
|
+
};
|
|
10807
|
+
return {
|
|
10808
|
+
resolveProductDir: () => resolveTestProductDir(invocation.resolveEffectiveInvocationDir(), writeWarning),
|
|
10809
|
+
runTests: (productDir, passing, targets) => runTestsThroughCommand(productDir, passing, invocation.io, targets),
|
|
10810
|
+
runAgentTests: (productDir, passing, targets) => runAgentTestsThroughCommand(productDir, passing, invocation.io, targets),
|
|
10811
|
+
writeStdout: (output) => invocation.io.writeStdout(output),
|
|
10812
|
+
writeWarning,
|
|
10813
|
+
setExitCode: (exitCode) => invocation.io.setExitCode(exitCode),
|
|
10814
|
+
exit: (exitCode) => invocation.io.exit(exitCode)
|
|
10815
|
+
};
|
|
10816
|
+
}
|
|
10274
10817
|
async function runTestingAction(deps, passing, options, targets) {
|
|
10275
10818
|
const productDir = await deps.resolveProductDir();
|
|
10276
10819
|
if (options.agent === true) {
|
|
@@ -10282,12 +10825,13 @@ async function runTestingAction(deps, passing, options, targets) {
|
|
|
10282
10825
|
const result = await deps.runTests(productDir, passing, targets);
|
|
10283
10826
|
reportAndExit(result.dispatch, deps);
|
|
10284
10827
|
}
|
|
10285
|
-
function createTestingDomain(deps
|
|
10828
|
+
function createTestingDomain(deps) {
|
|
10286
10829
|
return {
|
|
10287
10830
|
name: TESTING_CLI.commandName,
|
|
10288
10831
|
description: TESTING_CLI.description,
|
|
10289
|
-
register: (
|
|
10290
|
-
const
|
|
10832
|
+
register: (program, invocation) => {
|
|
10833
|
+
const actionDeps = deps ?? defaultTestingCliDependencies(invocation);
|
|
10834
|
+
const testCmd = program.command(TESTING_CLI.commandName).description(TESTING_CLI.description);
|
|
10291
10835
|
testCmd.option(TESTING_CLI.agentOption, TESTING_CLI.agentDescription);
|
|
10292
10836
|
testCmd.option(
|
|
10293
10837
|
`${TESTING_CLI.recursiveShortFlag}, ${TESTING_CLI.recursiveLongFlag}`,
|
|
@@ -10296,7 +10840,7 @@ function createTestingDomain(deps = defaultTestingCliDependencies) {
|
|
|
10296
10840
|
testCmd.argument(TESTING_CLI.targetsArgument, TESTING_CLI.targetsDescription);
|
|
10297
10841
|
testCmd.action(async (targets, options, command) => {
|
|
10298
10842
|
await runTestingAction(
|
|
10299
|
-
|
|
10843
|
+
actionDeps,
|
|
10300
10844
|
false,
|
|
10301
10845
|
{ agent: requestsAgentMode(options, command) },
|
|
10302
10846
|
targetSelection(targets, options, command)
|
|
@@ -10304,7 +10848,7 @@ function createTestingDomain(deps = defaultTestingCliDependencies) {
|
|
|
10304
10848
|
});
|
|
10305
10849
|
testCmd.command(TESTING_CLI.passingSubcommand).description(TESTING_CLI.passingDescription).option(TESTING_CLI.agentOption, TESTING_CLI.agentDescription).option(`${TESTING_CLI.recursiveShortFlag}, ${TESTING_CLI.recursiveLongFlag}`, TESTING_CLI.recursiveDescription).argument(TESTING_CLI.targetsArgument, TESTING_CLI.targetsDescription).action(async (targets, options, command) => {
|
|
10306
10850
|
await runTestingAction(
|
|
10307
|
-
|
|
10851
|
+
actionDeps,
|
|
10308
10852
|
true,
|
|
10309
10853
|
{ agent: requestsAgentMode(options, command) },
|
|
10310
10854
|
targetSelection(targets, options, command)
|
|
@@ -10454,7 +10998,7 @@ function buildDprintCheckArgs(options) {
|
|
|
10454
10998
|
}
|
|
10455
10999
|
async function validateFormatting(context, runner = defaultFormattingProcessRunner) {
|
|
10456
11000
|
const args = buildDprintCheckArgs({ files: context.files });
|
|
10457
|
-
return new Promise((
|
|
11001
|
+
return new Promise((resolve10) => {
|
|
10458
11002
|
const child = spawnManagedSubprocess(runner, DPRINT_COMMAND, args, { cwd: context.projectRoot });
|
|
10459
11003
|
const chunks = [];
|
|
10460
11004
|
const capture = (chunk) => {
|
|
@@ -10463,10 +11007,10 @@ async function validateFormatting(context, runner = defaultFormattingProcessRunn
|
|
|
10463
11007
|
child.stdout?.on(VALIDATION_SUBPROCESS_EVENTS.DATA, capture);
|
|
10464
11008
|
child.stderr?.on(VALIDATION_SUBPROCESS_EVENTS.DATA, capture);
|
|
10465
11009
|
child.on(VALIDATION_SUBPROCESS_EVENTS.CLOSE, (code) => {
|
|
10466
|
-
|
|
11010
|
+
resolve10({ success: code === 0, output: chunks.join("") });
|
|
10467
11011
|
});
|
|
10468
11012
|
child.on(VALIDATION_SUBPROCESS_EVENTS.ERROR, (error) => {
|
|
10469
|
-
|
|
11013
|
+
resolve10({ success: false, output: chunks.join(""), error: error.message });
|
|
10470
11014
|
});
|
|
10471
11015
|
});
|
|
10472
11016
|
}
|
|
@@ -10587,7 +11131,7 @@ import { relative as relative4 } from "path";
|
|
|
10587
11131
|
|
|
10588
11132
|
// src/validation/steps/markdown.ts
|
|
10589
11133
|
import { existsSync as existsSync2, statSync } from "fs";
|
|
10590
|
-
import { basename as basename4, dirname as
|
|
11134
|
+
import { basename as basename4, dirname as dirname9, join as join25, relative as pathRelative } from "path";
|
|
10591
11135
|
import { main as markdownlintMain } from "markdownlint-cli2";
|
|
10592
11136
|
import relativeLinksRule from "markdownlint-rule-relative-links";
|
|
10593
11137
|
var MARKDOWN_DEFAULT_DIRECTORY_NAMES = ["spx", "docs"];
|
|
@@ -10717,7 +11261,7 @@ async function validateTarget(target, config, projectRoot, ignoreGlobs = []) {
|
|
|
10717
11261
|
return errors;
|
|
10718
11262
|
}
|
|
10719
11263
|
function targetDirectory(target) {
|
|
10720
|
-
return target.kind === MARKDOWN_VALIDATION_TARGET_KIND.FILE ?
|
|
11264
|
+
return target.kind === MARKDOWN_VALIDATION_TARGET_KIND.FILE ? dirname9(target.path) : target.path;
|
|
10721
11265
|
}
|
|
10722
11266
|
function hasMarkdownExtension(path7) {
|
|
10723
11267
|
const lastDot = path7.lastIndexOf(".");
|
|
@@ -10772,10 +11316,10 @@ async function markdownCommand(options) {
|
|
|
10772
11316
|
VALIDATION_PATH_TOOL_SUBSECTIONS.MARKDOWN
|
|
10773
11317
|
);
|
|
10774
11318
|
const targetResolutions = files && files.length > 0 ? files.map((filePath) => resolveMarkdownValidationTarget(filePath)) : void 0;
|
|
10775
|
-
const unfilteredTargets = targetResolutions
|
|
11319
|
+
const unfilteredTargets = targetResolutions === void 0 ? getDefaultDirectories(cwd).map((path7) => ({
|
|
10776
11320
|
kind: MARKDOWN_VALIDATION_TARGET_KIND.DIRECTORY,
|
|
10777
11321
|
path: path7
|
|
10778
|
-
}));
|
|
11322
|
+
})) : targetResolutions.map((resolution) => resolution.target).filter((target) => target !== void 0);
|
|
10779
11323
|
const targets = unfilteredTargets.filter(
|
|
10780
11324
|
(target) => pathPassesValidationFilter(relative4(cwd, target.path), pathFilter)
|
|
10781
11325
|
);
|
|
@@ -10794,24 +11338,26 @@ async function markdownCommand(options) {
|
|
|
10794
11338
|
projectRoot: cwd
|
|
10795
11339
|
});
|
|
10796
11340
|
const durationMs = Date.now() - startTime;
|
|
10797
|
-
|
|
10798
|
-
const output = quiet ? "" : [...skippedOutput, MARKDOWN_COMMAND_OUTPUT.NO_ISSUES].join("\n");
|
|
10799
|
-
return { exitCode: 0, output, durationMs };
|
|
10800
|
-
} else {
|
|
10801
|
-
const errorLines = result.errors.map(
|
|
10802
|
-
(error) => ` ${error.file}:${error.line} ${error.detail}`
|
|
10803
|
-
);
|
|
10804
|
-
const output = [
|
|
10805
|
-
...skippedOutput,
|
|
10806
|
-
`Markdown: ${result.errors.length} ${MARKDOWN_COMMAND_OUTPUT.ERROR_SUMMARY_SUFFIX}`,
|
|
10807
|
-
...errorLines
|
|
10808
|
-
].join("\n");
|
|
10809
|
-
return { exitCode: 1, output, durationMs };
|
|
10810
|
-
}
|
|
11341
|
+
return formatMarkdownResult(result, skippedOutput, quiet, durationMs);
|
|
10811
11342
|
}
|
|
10812
11343
|
function formatSkippedFileScope(target) {
|
|
10813
11344
|
return `${MARKDOWN_COMMAND_OUTPUT.SKIPPED_FILE_SCOPE_PREFIX}: ${target.path} (${target.reason})`;
|
|
10814
11345
|
}
|
|
11346
|
+
function formatMarkdownResult(result, skippedOutput, quiet, durationMs) {
|
|
11347
|
+
if (result.success) {
|
|
11348
|
+
const output2 = quiet ? "" : [...skippedOutput, MARKDOWN_COMMAND_OUTPUT.NO_ISSUES].join("\n");
|
|
11349
|
+
return { exitCode: 0, output: output2, durationMs };
|
|
11350
|
+
}
|
|
11351
|
+
const errorLines = result.errors.map(
|
|
11352
|
+
(error) => ` ${error.file}:${error.line} ${error.detail}`
|
|
11353
|
+
);
|
|
11354
|
+
const output = [
|
|
11355
|
+
...skippedOutput,
|
|
11356
|
+
`Markdown: ${result.errors.length} ${MARKDOWN_COMMAND_OUTPUT.ERROR_SUMMARY_SUFFIX}`,
|
|
11357
|
+
...errorLines
|
|
11358
|
+
].join("\n");
|
|
11359
|
+
return { exitCode: 1, output, durationMs };
|
|
11360
|
+
}
|
|
10815
11361
|
|
|
10816
11362
|
// src/validation/languages/markdown.ts
|
|
10817
11363
|
var MARKDOWN_LANGUAGE_NAME = "markdown";
|
|
@@ -11687,7 +12233,7 @@ var ParseErrorCode;
|
|
|
11687
12233
|
|
|
11688
12234
|
// src/validation/config/scope.ts
|
|
11689
12235
|
import { existsSync as existsSync3, readdirSync, readFileSync as readFileSync3 } from "fs";
|
|
11690
|
-
import { isAbsolute as isAbsolute4, join as join26, relative as relative5, resolve as
|
|
12236
|
+
import { isAbsolute as isAbsolute4, join as join26, relative as relative5, resolve as resolve8 } from "path";
|
|
11691
12237
|
var TSCONFIG_FILES = {
|
|
11692
12238
|
full: "tsconfig.json",
|
|
11693
12239
|
production: "tsconfig.production.json"
|
|
@@ -11913,7 +12459,7 @@ function globPatternCanMatchInsideDirectory(patternSegments, directorySegments,
|
|
|
11913
12459
|
);
|
|
11914
12460
|
} else {
|
|
11915
12461
|
const directorySegment = directorySegments[directoryIndex];
|
|
11916
|
-
result =
|
|
12462
|
+
result = globSegmentMatchesPathSegment(patternSegment, directorySegment) && globPatternCanMatchInsideDirectory(
|
|
11917
12463
|
patternSegments,
|
|
11918
12464
|
directorySegments,
|
|
11919
12465
|
patternIndex + 1,
|
|
@@ -12061,13 +12607,13 @@ function pathPassesTypeScriptScope(path7, scopeConfig) {
|
|
|
12061
12607
|
return included && !excluded;
|
|
12062
12608
|
}
|
|
12063
12609
|
function pathStaysInsideTypeScriptScopeRoot(projectRoot, path7) {
|
|
12064
|
-
const resolvedPath = isAbsolute4(path7) ?
|
|
12610
|
+
const resolvedPath = isAbsolute4(path7) ? resolve8(path7) : resolve8(projectRoot, path7);
|
|
12065
12611
|
const relativePath = relative5(projectRoot, resolvedPath);
|
|
12066
12612
|
const segments = normalizeTypeScriptScopePath(relativePath).split(PATH_SEGMENT_SEPARATOR3);
|
|
12067
12613
|
return relativePath.length === 0 || !segments.includes("..") && !isAbsolute4(relativePath);
|
|
12068
12614
|
}
|
|
12069
12615
|
function toProjectRelativeTypeScriptScopePath(projectRoot, path7) {
|
|
12070
|
-
const resolvedPath = isAbsolute4(path7) ?
|
|
12616
|
+
const resolvedPath = isAbsolute4(path7) ? resolve8(path7) : resolve8(projectRoot, path7);
|
|
12071
12617
|
const relativePath = relative5(projectRoot, resolvedPath);
|
|
12072
12618
|
return relativePath.length === 0 ? TYPESCRIPT_SCOPE_PROJECT_ROOT : normalizeTypeScriptScopePath(relativePath);
|
|
12073
12619
|
}
|
|
@@ -12303,7 +12849,7 @@ function resolvedModulePath(resolvedPath) {
|
|
|
12303
12849
|
}
|
|
12304
12850
|
function nearestPackageRoot(filePath, existsSync8) {
|
|
12305
12851
|
let currentDirectory = path6.dirname(filePath);
|
|
12306
|
-
|
|
12852
|
+
for (; ; ) {
|
|
12307
12853
|
if (existsSync8(path6.join(currentDirectory, PACKAGE_MANIFEST_FILENAME))) {
|
|
12308
12854
|
return currentDirectory;
|
|
12309
12855
|
}
|
|
@@ -12319,7 +12865,7 @@ function bundledToolPath(resolvedPath, existsSync8) {
|
|
|
12319
12865
|
return nearestPackageRoot(bundledFilePath, existsSync8) ?? path6.dirname(bundledFilePath);
|
|
12320
12866
|
}
|
|
12321
12867
|
async function discoverTool(tool, options = {}) {
|
|
12322
|
-
const { projectRoot =
|
|
12868
|
+
const { projectRoot = CONFIG_PROCESS_CWD.read(), deps = defaultToolDiscoveryDeps } = options;
|
|
12323
12869
|
const bundledPath = deps.resolveModule(`${tool}/package.json`) ?? deps.resolveImport?.(tool);
|
|
12324
12870
|
if (bundledPath) {
|
|
12325
12871
|
return {
|
|
@@ -12488,7 +13034,7 @@ function dependencyCruiserGlobSegmentToRegExpSource(segment) {
|
|
|
12488
13034
|
} else if (character === SINGLE_CHARACTER_GLOB_MARKER) {
|
|
12489
13035
|
source += `[^${DEPENDENCY_CRUISER_PATH_SEGMENT_SEPARATOR}]`;
|
|
12490
13036
|
} else {
|
|
12491
|
-
source += character
|
|
13037
|
+
source += character.replaceAll(LITERAL_REGEX_SPECIAL_CHARACTER_PATTERN, REGEX_ESCAPE_REPLACEMENT2);
|
|
12492
13038
|
}
|
|
12493
13039
|
}
|
|
12494
13040
|
return source;
|
|
@@ -12534,10 +13080,10 @@ function patternIsCoveredByDirectory(pattern, directories) {
|
|
|
12534
13080
|
});
|
|
12535
13081
|
}
|
|
12536
13082
|
function isCruiseResult(output) {
|
|
12537
|
-
return typeof output === "object" &&
|
|
13083
|
+
return output !== null && typeof output === "object" && "modules" in output && "summary" in output;
|
|
12538
13084
|
}
|
|
12539
13085
|
function closeCycle(cycle) {
|
|
12540
|
-
const first = cycle
|
|
13086
|
+
const first = cycle.at(0);
|
|
12541
13087
|
const last = cycle.at(-1);
|
|
12542
13088
|
if (first === void 0) {
|
|
12543
13089
|
return [];
|
|
@@ -12798,13 +13344,13 @@ async function runKnipSubprocess(projectRoot, typescriptScope, runner, deps) {
|
|
|
12798
13344
|
knipProcess.stderr?.on("data", (data) => {
|
|
12799
13345
|
knipError += data.toString();
|
|
12800
13346
|
});
|
|
12801
|
-
return new Promise((
|
|
13347
|
+
return new Promise((resolve10) => {
|
|
12802
13348
|
const resolveAfterCleanup = (result) => {
|
|
12803
13349
|
if (resultResolved) {
|
|
12804
13350
|
return;
|
|
12805
13351
|
}
|
|
12806
13352
|
resultResolved = true;
|
|
12807
|
-
void cleanupOnce().finally(() =>
|
|
13353
|
+
void cleanupOnce().finally(() => resolve10(result));
|
|
12808
13354
|
};
|
|
12809
13355
|
knipProcess.on("close", (code) => {
|
|
12810
13356
|
if (code === 0) {
|
|
@@ -12905,7 +13451,7 @@ import { existsSync as existsSync6 } from "fs";
|
|
|
12905
13451
|
import { join as join30 } from "path";
|
|
12906
13452
|
|
|
12907
13453
|
// src/validation/lint-policy.ts
|
|
12908
|
-
import { execFileSync } from "child_process";
|
|
13454
|
+
import { execFileSync as execFileSync2 } from "child_process";
|
|
12909
13455
|
import { existsSync as existsSync5, readdirSync as readdirSync2, readFileSync as readFileSync4, statSync as statSync2 } from "fs";
|
|
12910
13456
|
import { join as join29 } from "path";
|
|
12911
13457
|
|
|
@@ -13020,7 +13566,7 @@ function readBaselineManifest(productDir, file, key) {
|
|
|
13020
13566
|
}
|
|
13021
13567
|
let content;
|
|
13022
13568
|
try {
|
|
13023
|
-
content =
|
|
13569
|
+
content = execFileSync2("git", ["show", `${baselineRef}:${file}`], {
|
|
13024
13570
|
cwd: productDir,
|
|
13025
13571
|
encoding: "utf-8",
|
|
13026
13572
|
env: withoutGitEnvironment(process.env),
|
|
@@ -13060,7 +13606,7 @@ function readMergeBase(productDir, baseBranchRef) {
|
|
|
13060
13606
|
}
|
|
13061
13607
|
function readGitRef(productDir, args) {
|
|
13062
13608
|
try {
|
|
13063
|
-
const output =
|
|
13609
|
+
const output = execFileSync2("git", [...args], {
|
|
13064
13610
|
cwd: productDir,
|
|
13065
13611
|
encoding: "utf-8",
|
|
13066
13612
|
env: withoutGitEnvironment(process.env),
|
|
@@ -13222,7 +13768,7 @@ async function validateESLint(context, runner = defaultEslintProcessRunner, outp
|
|
|
13222
13768
|
scope: scope2,
|
|
13223
13769
|
scopeConfig: context.scopeConfig
|
|
13224
13770
|
});
|
|
13225
|
-
return new Promise((
|
|
13771
|
+
return new Promise((resolve10) => {
|
|
13226
13772
|
const localBin = join30(projectRoot, ...ESLINT_LOCAL_BIN_SEGMENTS);
|
|
13227
13773
|
const binary = existsSync6(localBin) ? localBin : "npx";
|
|
13228
13774
|
const spawnArgs = binary === "npx" ? eslintArgs : eslintArgs.slice(1);
|
|
@@ -13232,13 +13778,13 @@ async function validateESLint(context, runner = defaultEslintProcessRunner, outp
|
|
|
13232
13778
|
forwardValidationSubprocessOutput(eslintProcess, outputStreams);
|
|
13233
13779
|
eslintProcess.on(VALIDATION_SUBPROCESS_EVENTS.CLOSE, (code) => {
|
|
13234
13780
|
if (code === 0) {
|
|
13235
|
-
|
|
13781
|
+
resolve10({ success: true });
|
|
13236
13782
|
} else {
|
|
13237
|
-
|
|
13783
|
+
resolve10({ success: false, error: `ESLint exited with code ${code}` });
|
|
13238
13784
|
}
|
|
13239
13785
|
});
|
|
13240
13786
|
eslintProcess.on(VALIDATION_SUBPROCESS_EVENTS.ERROR, (error) => {
|
|
13241
|
-
|
|
13787
|
+
resolve10({ success: false, error: error.message });
|
|
13242
13788
|
});
|
|
13243
13789
|
});
|
|
13244
13790
|
}
|
|
@@ -13311,21 +13857,24 @@ async function lintCommand(options) {
|
|
|
13311
13857
|
};
|
|
13312
13858
|
const result = await validateESLint(context, void 0, outputStreams);
|
|
13313
13859
|
const durationMs = Date.now() - startTime;
|
|
13860
|
+
return formatLintResult(result, quiet, durationMs);
|
|
13861
|
+
}
|
|
13862
|
+
function formatLintResult(result, quiet, durationMs) {
|
|
13314
13863
|
if (result.skipped) {
|
|
13315
|
-
const
|
|
13316
|
-
return { exitCode: 0, output, durationMs };
|
|
13317
|
-
} else if (result.success) {
|
|
13318
|
-
const output = quiet ? "" : VALIDATION_COMMAND_OUTPUT.ESLINT_SUCCESS;
|
|
13319
|
-
return { exitCode: 0, output, durationMs };
|
|
13320
|
-
} else {
|
|
13321
|
-
const output = result.error ?? VALIDATION_COMMAND_OUTPUT.ESLINT_FAILURE;
|
|
13322
|
-
return { exitCode: 1, output, durationMs };
|
|
13864
|
+
const output2 = quiet ? "" : VALIDATION_PATHS_NO_TARGETS_MESSAGE;
|
|
13865
|
+
return { exitCode: 0, output: output2, durationMs };
|
|
13323
13866
|
}
|
|
13867
|
+
if (result.success) {
|
|
13868
|
+
const output2 = quiet ? "" : VALIDATION_COMMAND_OUTPUT.ESLINT_SUCCESS;
|
|
13869
|
+
return { exitCode: 0, output: output2, durationMs };
|
|
13870
|
+
}
|
|
13871
|
+
const output = result.error ?? VALIDATION_COMMAND_OUTPUT.ESLINT_FAILURE;
|
|
13872
|
+
return { exitCode: 1, output, durationMs };
|
|
13324
13873
|
}
|
|
13325
13874
|
|
|
13326
13875
|
// src/validation/literal/index.ts
|
|
13327
13876
|
import { readFile as readFile9 } from "fs/promises";
|
|
13328
|
-
import { isAbsolute as isAbsolute6, relative as relative7, resolve as
|
|
13877
|
+
import { isAbsolute as isAbsolute6, relative as relative7, resolve as resolve9 } from "path";
|
|
13329
13878
|
|
|
13330
13879
|
// src/lib/file-inclusion/predicates/ignore-source.ts
|
|
13331
13880
|
var IGNORE_SOURCE_LAYER = "ignore-source";
|
|
@@ -13409,22 +13958,33 @@ async function runPipeline(sequence, projectRoot, request, config, ignoreReader)
|
|
|
13409
13958
|
await collectPaths(request.walkRoot, projectRoot, allPaths, artifactDirs);
|
|
13410
13959
|
for (const path7 of allPaths) {
|
|
13411
13960
|
if (explicitPathSet.has(path7)) continue;
|
|
13412
|
-
const
|
|
13413
|
-
|
|
13414
|
-
|
|
13415
|
-
if (decision.matched) {
|
|
13416
|
-
trail.push(decision);
|
|
13417
|
-
}
|
|
13418
|
-
}
|
|
13419
|
-
if (trail.length > 0) {
|
|
13420
|
-
excluded.push({ path: path7, decisionTrail: trail });
|
|
13961
|
+
const classified = classifyPath(path7, layerPairs);
|
|
13962
|
+
if (classified.excluded) {
|
|
13963
|
+
excluded.push(classified.entry);
|
|
13421
13964
|
} else {
|
|
13422
|
-
included.push(
|
|
13965
|
+
included.push(classified.entry);
|
|
13423
13966
|
}
|
|
13424
13967
|
}
|
|
13425
13968
|
}
|
|
13426
13969
|
return { included, excluded };
|
|
13427
13970
|
}
|
|
13971
|
+
function classifyPath(path7, layerPairs) {
|
|
13972
|
+
const trail = decisionTrailForPath(path7, layerPairs);
|
|
13973
|
+
return {
|
|
13974
|
+
excluded: trail.length > 0,
|
|
13975
|
+
entry: { path: path7, decisionTrail: trail }
|
|
13976
|
+
};
|
|
13977
|
+
}
|
|
13978
|
+
function decisionTrailForPath(path7, layerPairs) {
|
|
13979
|
+
const trail = [];
|
|
13980
|
+
for (const { entry, layerConfig } of layerPairs) {
|
|
13981
|
+
const decision = entry.predicate(path7, layerConfig);
|
|
13982
|
+
if (decision.matched) {
|
|
13983
|
+
trail.push(decision);
|
|
13984
|
+
}
|
|
13985
|
+
}
|
|
13986
|
+
return trail;
|
|
13987
|
+
}
|
|
13428
13988
|
|
|
13429
13989
|
// src/validation/literal/detector.ts
|
|
13430
13990
|
import { parse as parseTypeScript } from "@typescript-eslint/parser";
|
|
@@ -13485,7 +14045,12 @@ var FIXTURE_DATA_ROLE_SEGMENTS = /* @__PURE__ */ new Set([
|
|
|
13485
14045
|
"source"
|
|
13486
14046
|
]);
|
|
13487
14047
|
var FIXTURE_DATA_CONTEXT_SEGMENTS = /* @__PURE__ */ new Set(["path", "tree"]);
|
|
13488
|
-
var
|
|
14048
|
+
var IDENTIFIER_CHAR_KIND = {
|
|
14049
|
+
UPPER: "upper",
|
|
14050
|
+
LOWER: "lower",
|
|
14051
|
+
DIGIT: "digit",
|
|
14052
|
+
OTHER: "other"
|
|
14053
|
+
};
|
|
13489
14054
|
function collectLiterals(source, filename, options) {
|
|
13490
14055
|
const ast = parseTypeScript(source, {
|
|
13491
14056
|
loc: true,
|
|
@@ -13539,26 +14104,28 @@ function emitLiteral(node, context, ancestors, options, out) {
|
|
|
13539
14104
|
}
|
|
13540
14105
|
const line = node.loc?.start?.line ?? 0;
|
|
13541
14106
|
if (node.type === LITERAL_TYPE) {
|
|
13542
|
-
|
|
13543
|
-
if (node.value.length >= options.minStringLength) {
|
|
13544
|
-
out.push({ kind: "string", value: node.value, loc: { file: context.filename, line } });
|
|
13545
|
-
}
|
|
13546
|
-
} else if (typeof node.value === "number") {
|
|
13547
|
-
const raw = typeof node.raw === "string" ? node.raw : String(node.value);
|
|
13548
|
-
if (isMeaningfulNumber(raw, options.minNumberDigits)) {
|
|
13549
|
-
out.push({ kind: "number", value: String(node.value), loc: { file: context.filename, line } });
|
|
13550
|
-
}
|
|
13551
|
-
}
|
|
14107
|
+
emitEstreeLiteral(node, context, line, options, out);
|
|
13552
14108
|
return;
|
|
13553
14109
|
}
|
|
13554
|
-
|
|
13555
|
-
|
|
13556
|
-
|
|
13557
|
-
|
|
13558
|
-
|
|
14110
|
+
emitTemplateElementLiteral(node, context, line, options, out);
|
|
14111
|
+
}
|
|
14112
|
+
function emitEstreeLiteral(node, context, line, options, out) {
|
|
14113
|
+
if (typeof node.value === "string" && node.value.length >= options.minStringLength) {
|
|
14114
|
+
out.push({ kind: "string", value: node.value, loc: { file: context.filename, line } });
|
|
14115
|
+
} else if (typeof node.value === "number") {
|
|
14116
|
+
const raw = typeof node.raw === "string" ? node.raw : String(node.value);
|
|
14117
|
+
if (isMeaningfulNumber(raw, options.minNumberDigits)) {
|
|
14118
|
+
out.push({ kind: "number", value: String(node.value), loc: { file: context.filename, line } });
|
|
13559
14119
|
}
|
|
13560
14120
|
}
|
|
13561
14121
|
}
|
|
14122
|
+
function emitTemplateElementLiteral(node, context, line, options, out) {
|
|
14123
|
+
const value = node.value;
|
|
14124
|
+
const cooked = value?.cooked ?? "";
|
|
14125
|
+
if (cooked.length >= options.minStringLength) {
|
|
14126
|
+
out.push({ kind: "string", value: cooked, loc: { file: context.filename, line } });
|
|
14127
|
+
}
|
|
14128
|
+
}
|
|
13562
14129
|
function isTestLikeFile(filename) {
|
|
13563
14130
|
return filename.includes(TEST_PATH_SEGMENT) || filename.includes(WINDOWS_TEST_PATH_SEGMENT) || filename.includes(TEST_FILE_MARKER);
|
|
13564
14131
|
}
|
|
@@ -13620,7 +14187,7 @@ function getFixtureDataDeclaratorName(node) {
|
|
|
13620
14187
|
return getIdentifierName(node.init);
|
|
13621
14188
|
}
|
|
13622
14189
|
function isFixtureDataVariableName(variableName) {
|
|
13623
|
-
const segments =
|
|
14190
|
+
const segments = fixtureClassificationSegments(variableName);
|
|
13624
14191
|
if (segments.length === 0) {
|
|
13625
14192
|
return false;
|
|
13626
14193
|
}
|
|
@@ -13633,11 +14200,92 @@ function isFixtureDataVariableName(variableName) {
|
|
|
13633
14200
|
if (segments.every((segment) => FIXTURE_DATA_ROLE_SEGMENTS.has(segment))) {
|
|
13634
14201
|
return true;
|
|
13635
14202
|
}
|
|
13636
|
-
const finalSegment = segments
|
|
14203
|
+
const finalSegment = segments.at(-1);
|
|
13637
14204
|
return finalSegment !== void 0 && FIXTURE_DATA_CONTEXT_SEGMENTS.has(finalSegment);
|
|
13638
14205
|
}
|
|
13639
14206
|
function splitIdentifierName(variableName) {
|
|
13640
|
-
return variableName.split("_").flatMap(
|
|
14207
|
+
return variableName.split("_").flatMap(splitIdentifierPart).map((segment) => segment.toLowerCase());
|
|
14208
|
+
}
|
|
14209
|
+
function fixtureClassificationSegments(variableName) {
|
|
14210
|
+
const segments = splitIdentifierName(variableName);
|
|
14211
|
+
if (!isScreamingSnakeIdentifier(variableName)) {
|
|
14212
|
+
return segments;
|
|
14213
|
+
}
|
|
14214
|
+
let first = 0;
|
|
14215
|
+
let last = segments.length;
|
|
14216
|
+
while (first < last && isSingleLetterSegment(segments[first])) {
|
|
14217
|
+
first += 1;
|
|
14218
|
+
}
|
|
14219
|
+
while (last > first && isSingleLetterSegment(segments[last - 1])) {
|
|
14220
|
+
last -= 1;
|
|
14221
|
+
}
|
|
14222
|
+
return segments.slice(first, last);
|
|
14223
|
+
}
|
|
14224
|
+
function isScreamingSnakeIdentifier(variableName) {
|
|
14225
|
+
return variableName.includes("_") && variableName.split("_").every((part) => part !== "" && part === part.toUpperCase() && part !== part.toLowerCase());
|
|
14226
|
+
}
|
|
14227
|
+
function isSingleLetterSegment(segment) {
|
|
14228
|
+
return segment.length === 1 && classifyIdentifierCharacter(segment) === IDENTIFIER_CHAR_KIND.LOWER;
|
|
14229
|
+
}
|
|
14230
|
+
function splitIdentifierPart(identifierPart) {
|
|
14231
|
+
const segments = [];
|
|
14232
|
+
let currentSegment = "";
|
|
14233
|
+
for (const character of identifierPart) {
|
|
14234
|
+
const kind = classifyIdentifierCharacter(character);
|
|
14235
|
+
if (kind === IDENTIFIER_CHAR_KIND.OTHER) {
|
|
14236
|
+
pushIdentifierSegment(segments, currentSegment);
|
|
14237
|
+
currentSegment = "";
|
|
14238
|
+
continue;
|
|
14239
|
+
}
|
|
14240
|
+
if (currentSegment === "") {
|
|
14241
|
+
currentSegment = character;
|
|
14242
|
+
continue;
|
|
14243
|
+
}
|
|
14244
|
+
if (startsNewIdentifierSegment(currentSegment, kind)) {
|
|
14245
|
+
currentSegment = startNextIdentifierSegment(segments, currentSegment, character, kind);
|
|
14246
|
+
continue;
|
|
14247
|
+
}
|
|
14248
|
+
currentSegment = `${currentSegment}${character}`;
|
|
14249
|
+
}
|
|
14250
|
+
pushIdentifierSegment(segments, currentSegment);
|
|
14251
|
+
return segments;
|
|
14252
|
+
}
|
|
14253
|
+
function startNextIdentifierSegment(segments, currentSegment, character, kind) {
|
|
14254
|
+
if (kind === IDENTIFIER_CHAR_KIND.DIGIT && isUppercaseSegment(currentSegment)) {
|
|
14255
|
+
return character;
|
|
14256
|
+
}
|
|
14257
|
+
if (kind === IDENTIFIER_CHAR_KIND.LOWER && isUppercaseRun(currentSegment)) {
|
|
14258
|
+
const prefix = currentSegment.slice(0, -1);
|
|
14259
|
+
pushIdentifierSegment(segments, prefix);
|
|
14260
|
+
return `${currentSegment.at(-1) ?? ""}${character}`;
|
|
14261
|
+
}
|
|
14262
|
+
pushIdentifierSegment(segments, currentSegment);
|
|
14263
|
+
return character;
|
|
14264
|
+
}
|
|
14265
|
+
function classifyIdentifierCharacter(character) {
|
|
14266
|
+
if (character >= "0" && character <= "9") return IDENTIFIER_CHAR_KIND.DIGIT;
|
|
14267
|
+
const lower = character.toLowerCase();
|
|
14268
|
+
const upper = character.toUpperCase();
|
|
14269
|
+
if (lower === upper) return IDENTIFIER_CHAR_KIND.OTHER;
|
|
14270
|
+
return character === upper ? IDENTIFIER_CHAR_KIND.UPPER : IDENTIFIER_CHAR_KIND.LOWER;
|
|
14271
|
+
}
|
|
14272
|
+
function startsNewIdentifierSegment(currentSegment, nextKind) {
|
|
14273
|
+
const currentKind = classifyIdentifierCharacter(currentSegment.at(-1) ?? "");
|
|
14274
|
+
if (nextKind === IDENTIFIER_CHAR_KIND.DIGIT) return currentKind !== IDENTIFIER_CHAR_KIND.DIGIT;
|
|
14275
|
+
if (currentKind === IDENTIFIER_CHAR_KIND.DIGIT) return true;
|
|
14276
|
+
if (nextKind === IDENTIFIER_CHAR_KIND.UPPER) return currentKind === IDENTIFIER_CHAR_KIND.LOWER;
|
|
14277
|
+
return currentKind === IDENTIFIER_CHAR_KIND.UPPER && isUppercaseRun(currentSegment);
|
|
14278
|
+
}
|
|
14279
|
+
function isUppercaseRun(value) {
|
|
14280
|
+
return value.length > 1 && Array.from(value).every((character) => classifyIdentifierCharacter(character) === IDENTIFIER_CHAR_KIND.UPPER);
|
|
14281
|
+
}
|
|
14282
|
+
function isUppercaseSegment(value) {
|
|
14283
|
+
return Array.from(value).every((character) => classifyIdentifierCharacter(character) === IDENTIFIER_CHAR_KIND.UPPER);
|
|
14284
|
+
}
|
|
14285
|
+
function pushIdentifierSegment(segments, segment) {
|
|
14286
|
+
if (segment !== "") {
|
|
14287
|
+
segments.push(segment);
|
|
14288
|
+
}
|
|
13641
14289
|
}
|
|
13642
14290
|
function getCallName(node) {
|
|
13643
14291
|
const callee = node.callee;
|
|
@@ -13669,7 +14317,7 @@ function getLiteralString(value) {
|
|
|
13669
14317
|
return typeof value.value === "string" ? value.value : void 0;
|
|
13670
14318
|
}
|
|
13671
14319
|
function isMeaningfulNumber(raw, minDigits) {
|
|
13672
|
-
const digits = raw.
|
|
14320
|
+
const digits = raw.replaceAll(/\D/g, "");
|
|
13673
14321
|
return digits.length >= minDigits;
|
|
13674
14322
|
}
|
|
13675
14323
|
function buildIndex(occurrences) {
|
|
@@ -13695,51 +14343,59 @@ function splitKey(key) {
|
|
|
13695
14343
|
function detectReuse(input) {
|
|
13696
14344
|
const srcReuse = [];
|
|
13697
14345
|
const testDupe = [];
|
|
13698
|
-
const testIndex =
|
|
13699
|
-
for (const [file, occurrences] of input.testOccurrencesByFile) {
|
|
13700
|
-
for (const occ of occurrences) {
|
|
13701
|
-
if (input.allowlist.has(occ.value)) continue;
|
|
13702
|
-
const key = makeKey(occ.kind, occ.value);
|
|
13703
|
-
let byFile = testIndex.get(key);
|
|
13704
|
-
if (!byFile) {
|
|
13705
|
-
byFile = /* @__PURE__ */ new Map();
|
|
13706
|
-
testIndex.set(key, byFile);
|
|
13707
|
-
}
|
|
13708
|
-
const locsInFile = byFile.get(file);
|
|
13709
|
-
if (locsInFile) locsInFile.push(occ.loc);
|
|
13710
|
-
else byFile.set(file, [occ.loc]);
|
|
13711
|
-
}
|
|
13712
|
-
}
|
|
14346
|
+
const testIndex = buildTestOccurrenceIndex(input.testOccurrencesByFile, input.allowlist);
|
|
13713
14347
|
for (const [key, byFile] of testIndex) {
|
|
13714
14348
|
const { kind, value } = splitKey(key);
|
|
13715
14349
|
const srcLocs = input.srcIndex.get(key);
|
|
13716
14350
|
const allTestLocs = [];
|
|
13717
14351
|
for (const locs of byFile.values()) allTestLocs.push(...locs);
|
|
13718
14352
|
if (srcLocs && srcLocs.length > 0) {
|
|
13719
|
-
|
|
13720
|
-
srcReuse.push({
|
|
13721
|
-
test: testLoc,
|
|
13722
|
-
kind,
|
|
13723
|
-
value,
|
|
13724
|
-
src: srcLocs,
|
|
13725
|
-
remediation: REMEDIATION6.IMPORT_FROM_SOURCE
|
|
13726
|
-
});
|
|
13727
|
-
}
|
|
14353
|
+
srcReuse.push(...reuseFindings(kind, value, srcLocs, allTestLocs));
|
|
13728
14354
|
} else if (byFile.size >= 2) {
|
|
13729
|
-
|
|
13730
|
-
const otherTests = [...allTestLocs.slice(0, i), ...allTestLocs.slice(i + 1)];
|
|
13731
|
-
testDupe.push({
|
|
13732
|
-
test: allTestLocs[i],
|
|
13733
|
-
kind,
|
|
13734
|
-
value,
|
|
13735
|
-
otherTests,
|
|
13736
|
-
remediation: REMEDIATION6.REFACTOR_TO_SOURCE_OR_GENERATOR
|
|
13737
|
-
});
|
|
13738
|
-
}
|
|
14355
|
+
testDupe.push(...dupeFindings(kind, value, allTestLocs));
|
|
13739
14356
|
}
|
|
13740
14357
|
}
|
|
13741
14358
|
return { srcReuse, testDupe };
|
|
13742
14359
|
}
|
|
14360
|
+
function buildTestOccurrenceIndex(occurrencesByFile, allowlist) {
|
|
14361
|
+
const testIndex = /* @__PURE__ */ new Map();
|
|
14362
|
+
for (const [file, occurrences] of occurrencesByFile) {
|
|
14363
|
+
for (const occ of occurrences) {
|
|
14364
|
+
if (allowlist.has(occ.value)) continue;
|
|
14365
|
+
addTestOccurrence(testIndex, file, occ);
|
|
14366
|
+
}
|
|
14367
|
+
}
|
|
14368
|
+
return testIndex;
|
|
14369
|
+
}
|
|
14370
|
+
function addTestOccurrence(testIndex, file, occurrence) {
|
|
14371
|
+
const key = makeKey(occurrence.kind, occurrence.value);
|
|
14372
|
+
let byFile = testIndex.get(key);
|
|
14373
|
+
if (!byFile) {
|
|
14374
|
+
byFile = /* @__PURE__ */ new Map();
|
|
14375
|
+
testIndex.set(key, byFile);
|
|
14376
|
+
}
|
|
14377
|
+
const locsInFile = byFile.get(file);
|
|
14378
|
+
if (locsInFile) locsInFile.push(occurrence.loc);
|
|
14379
|
+
else byFile.set(file, [occurrence.loc]);
|
|
14380
|
+
}
|
|
14381
|
+
function reuseFindings(kind, value, srcLocs, allTestLocs) {
|
|
14382
|
+
return allTestLocs.map((testLoc) => ({
|
|
14383
|
+
test: testLoc,
|
|
14384
|
+
kind,
|
|
14385
|
+
value,
|
|
14386
|
+
src: srcLocs,
|
|
14387
|
+
remediation: REMEDIATION6.IMPORT_FROM_SOURCE
|
|
14388
|
+
}));
|
|
14389
|
+
}
|
|
14390
|
+
function dupeFindings(kind, value, allTestLocs) {
|
|
14391
|
+
return allTestLocs.map((test, index) => ({
|
|
14392
|
+
test,
|
|
14393
|
+
kind,
|
|
14394
|
+
value,
|
|
14395
|
+
otherTests: [...allTestLocs.slice(0, index), ...allTestLocs.slice(index + 1)],
|
|
14396
|
+
remediation: REMEDIATION6.REFACTOR_TO_SOURCE_OR_GENERATOR
|
|
14397
|
+
}));
|
|
14398
|
+
}
|
|
13743
14399
|
|
|
13744
14400
|
// src/validation/literal/walker.ts
|
|
13745
14401
|
var TYPESCRIPT_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx"]);
|
|
@@ -13788,7 +14444,7 @@ async function validateLiteralReuse(input) {
|
|
|
13788
14444
|
const config = input.config ?? literalConfigDescriptor.defaults;
|
|
13789
14445
|
const request = input.files ? {
|
|
13790
14446
|
explicit: input.files.map((f) => {
|
|
13791
|
-
const abs = isAbsolute6(f) ? f :
|
|
14447
|
+
const abs = isAbsolute6(f) ? f : resolve9(input.productDir, f);
|
|
13792
14448
|
return relative7(input.productDir, abs).split(/[\\/]/g).join("/");
|
|
13793
14449
|
})
|
|
13794
14450
|
} : { walkRoot: input.productDir };
|
|
@@ -13800,7 +14456,7 @@ async function validateLiteralReuse(input) {
|
|
|
13800
14456
|
EMPTY_IGNORE_READER
|
|
13801
14457
|
);
|
|
13802
14458
|
const filtered = applyPathFilter2(scope2.included, input.pathConfig);
|
|
13803
|
-
const candidateFiles = filtered.filter((entry) => isTypescriptSource(entry.path)).map((entry) =>
|
|
14459
|
+
const candidateFiles = filtered.filter((entry) => isTypescriptSource(entry.path)).map((entry) => resolve9(input.productDir, entry.path));
|
|
13804
14460
|
const collectOptions = {
|
|
13805
14461
|
visitorKeys: defaultVisitorKeys,
|
|
13806
14462
|
minStringLength: config.minStringLength,
|
|
@@ -14083,12 +14739,7 @@ async function createFileSpecificTsconfig(scope2, files, projectRoot, deps = def
|
|
|
14083
14739
|
compilerOptions: TEMPORARY_TSCONFIG_COMPILER_OPTIONS
|
|
14084
14740
|
};
|
|
14085
14741
|
deps.writeFileSync(configPath, JSON.stringify(tempConfig, null, 2));
|
|
14086
|
-
const cleanup = ()
|
|
14087
|
-
try {
|
|
14088
|
-
deps.rmSync(tempDir, { recursive: true, force: true });
|
|
14089
|
-
} catch {
|
|
14090
|
-
}
|
|
14091
|
-
};
|
|
14742
|
+
const cleanup = createTemporaryTsconfigCleanup(tempDir, deps);
|
|
14092
14743
|
return { configPath, tempDir, cleanup };
|
|
14093
14744
|
}
|
|
14094
14745
|
async function createScopeFilteredTsconfig(scope2, projectRoot, scopeConfig, deps = defaultTypeScriptDeps) {
|
|
@@ -14103,13 +14754,16 @@ async function createScopeFilteredTsconfig(scope2, projectRoot, scopeConfig, dep
|
|
|
14103
14754
|
compilerOptions: TEMPORARY_TSCONFIG_COMPILER_OPTIONS
|
|
14104
14755
|
};
|
|
14105
14756
|
deps.writeFileSync(configPath, JSON.stringify(tempConfig, null, 2));
|
|
14106
|
-
const cleanup = ()
|
|
14757
|
+
const cleanup = createTemporaryTsconfigCleanup(tempDir, deps);
|
|
14758
|
+
return { configPath, tempDir, cleanup };
|
|
14759
|
+
}
|
|
14760
|
+
function createTemporaryTsconfigCleanup(tempDir, deps) {
|
|
14761
|
+
return () => {
|
|
14107
14762
|
try {
|
|
14108
14763
|
deps.rmSync(tempDir, { recursive: true, force: true });
|
|
14109
14764
|
} catch {
|
|
14110
14765
|
}
|
|
14111
14766
|
};
|
|
14112
|
-
return { configPath, tempDir, cleanup };
|
|
14113
14767
|
}
|
|
14114
14768
|
async function validateTypeScript(context, options = {}) {
|
|
14115
14769
|
const { scope: scope2, projectRoot, files, scopeConfig } = context;
|
|
@@ -14119,32 +14773,16 @@ async function validateTypeScript(context, options = {}) {
|
|
|
14119
14773
|
outputStreams = defaultValidationSubprocessOutputStreams
|
|
14120
14774
|
} = options;
|
|
14121
14775
|
const configFile = TSCONFIG_FILES[scope2];
|
|
14122
|
-
let tool;
|
|
14123
|
-
let tscArgs;
|
|
14124
14776
|
if (files && files.length > 0) {
|
|
14125
14777
|
const { configPath, cleanup } = await createFileSpecificTsconfig(scope2, files, projectRoot, deps);
|
|
14126
14778
|
try {
|
|
14127
|
-
return await
|
|
14128
|
-
|
|
14129
|
-
|
|
14130
|
-
|
|
14131
|
-
|
|
14132
|
-
|
|
14133
|
-
|
|
14134
|
-
forwardValidationSubprocessOutput(tscProcess, outputStreams);
|
|
14135
|
-
tscProcess.on(VALIDATION_SUBPROCESS_EVENTS.CLOSE, (code) => {
|
|
14136
|
-
cleanup();
|
|
14137
|
-
if (code === 0) {
|
|
14138
|
-
resolve8({ success: true, skipped: false });
|
|
14139
|
-
} else {
|
|
14140
|
-
resolve8({ success: false, error: `TypeScript exited with code ${code}` });
|
|
14141
|
-
}
|
|
14142
|
-
});
|
|
14143
|
-
tscProcess.on(VALIDATION_SUBPROCESS_EVENTS.ERROR, (error) => {
|
|
14144
|
-
cleanup();
|
|
14145
|
-
resolve8({ success: false, error: error.message });
|
|
14146
|
-
});
|
|
14147
|
-
});
|
|
14779
|
+
return await runTypeScriptInvocation(
|
|
14780
|
+
projectRoot,
|
|
14781
|
+
resolveProjectTscInvocation(projectRoot, deps, ["--project", configPath]),
|
|
14782
|
+
runner,
|
|
14783
|
+
outputStreams,
|
|
14784
|
+
cleanup
|
|
14785
|
+
);
|
|
14148
14786
|
} catch (error) {
|
|
14149
14787
|
cleanup();
|
|
14150
14788
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
@@ -14158,47 +14796,47 @@ async function validateTypeScript(context, options = {}) {
|
|
|
14158
14796
|
return { success: true, skipped: true };
|
|
14159
14797
|
}
|
|
14160
14798
|
const { configPath, cleanup } = await createScopeFilteredTsconfig(scope2, projectRoot, scopeConfig, deps);
|
|
14161
|
-
|
|
14162
|
-
|
|
14163
|
-
|
|
14164
|
-
|
|
14165
|
-
|
|
14166
|
-
|
|
14167
|
-
|
|
14168
|
-
forwardValidationSubprocessOutput(tscProcess, outputStreams);
|
|
14169
|
-
tscProcess.on(VALIDATION_SUBPROCESS_EVENTS.CLOSE, (code) => {
|
|
14170
|
-
cleanup();
|
|
14171
|
-
if (code === 0) {
|
|
14172
|
-
resolve8({ success: true, skipped: false });
|
|
14173
|
-
} else {
|
|
14174
|
-
resolve8({ success: false, error: `TypeScript exited with code ${code}` });
|
|
14175
|
-
}
|
|
14176
|
-
});
|
|
14177
|
-
tscProcess.on(VALIDATION_SUBPROCESS_EVENTS.ERROR, (error) => {
|
|
14178
|
-
cleanup();
|
|
14179
|
-
resolve8({ success: false, error: error.message });
|
|
14180
|
-
});
|
|
14181
|
-
});
|
|
14182
|
-
} else {
|
|
14183
|
-
const tscBin = join32(projectRoot, "node_modules", ".bin", "tsc");
|
|
14184
|
-
tool = deps.existsSync(tscBin) ? tscBin : "npx";
|
|
14185
|
-
const rawArgs = buildTypeScriptArgs({ scope: scope2, configFile });
|
|
14186
|
-
tscArgs = tool === "npx" ? rawArgs : rawArgs.slice(1);
|
|
14799
|
+
return runTypeScriptInvocation(
|
|
14800
|
+
projectRoot,
|
|
14801
|
+
resolveProjectTscInvocation(projectRoot, deps, ["--project", configPath]),
|
|
14802
|
+
runner,
|
|
14803
|
+
outputStreams,
|
|
14804
|
+
cleanup
|
|
14805
|
+
);
|
|
14187
14806
|
}
|
|
14188
|
-
return
|
|
14189
|
-
|
|
14807
|
+
return runTypeScriptInvocation(
|
|
14808
|
+
projectRoot,
|
|
14809
|
+
resolveProjectTscInvocation(projectRoot, deps, buildTypeScriptArgs({ scope: scope2, configFile }).slice(1)),
|
|
14810
|
+
runner,
|
|
14811
|
+
outputStreams
|
|
14812
|
+
);
|
|
14813
|
+
}
|
|
14814
|
+
function resolveProjectTscInvocation(projectRoot, deps, tscArgs) {
|
|
14815
|
+
const tscBin = join32(projectRoot, "node_modules", ".bin", "tsc");
|
|
14816
|
+
const tool = deps.existsSync(tscBin) ? tscBin : "npx";
|
|
14817
|
+
return {
|
|
14818
|
+
tool,
|
|
14819
|
+
args: tool === "npx" ? ["tsc", ...tscArgs] : tscArgs
|
|
14820
|
+
};
|
|
14821
|
+
}
|
|
14822
|
+
function runTypeScriptInvocation(projectRoot, invocation, runner, outputStreams, cleanup = () => {
|
|
14823
|
+
}) {
|
|
14824
|
+
return new Promise((resolve10) => {
|
|
14825
|
+
const tscProcess = spawnManagedSubprocess(runner, invocation.tool, invocation.args, {
|
|
14190
14826
|
cwd: projectRoot
|
|
14191
14827
|
});
|
|
14192
14828
|
forwardValidationSubprocessOutput(tscProcess, outputStreams);
|
|
14193
14829
|
tscProcess.on(VALIDATION_SUBPROCESS_EVENTS.CLOSE, (code) => {
|
|
14830
|
+
cleanup();
|
|
14194
14831
|
if (code === 0) {
|
|
14195
|
-
|
|
14832
|
+
resolve10({ success: true, skipped: false });
|
|
14196
14833
|
} else {
|
|
14197
|
-
|
|
14834
|
+
resolve10({ success: false, error: `TypeScript exited with code ${code}` });
|
|
14198
14835
|
}
|
|
14199
14836
|
});
|
|
14200
14837
|
tscProcess.on(VALIDATION_SUBPROCESS_EVENTS.ERROR, (error) => {
|
|
14201
|
-
|
|
14838
|
+
cleanup();
|
|
14839
|
+
resolve10({ success: false, error: error.message });
|
|
14202
14840
|
});
|
|
14203
14841
|
});
|
|
14204
14842
|
}
|
|
@@ -14259,16 +14897,19 @@ async function typescriptCommand(options) {
|
|
|
14259
14897
|
scopeConfig
|
|
14260
14898
|
});
|
|
14261
14899
|
const durationMs = Date.now() - startTime;
|
|
14900
|
+
return formatTypeScriptResult(result, quiet, durationMs);
|
|
14901
|
+
}
|
|
14902
|
+
function formatTypeScriptResult(result, quiet, durationMs) {
|
|
14262
14903
|
if (result.skipped) {
|
|
14263
|
-
const
|
|
14264
|
-
return { exitCode: 0, output, durationMs };
|
|
14265
|
-
}
|
|
14266
|
-
|
|
14267
|
-
|
|
14268
|
-
|
|
14269
|
-
const output = result.error ?? VALIDATION_COMMAND_OUTPUT.TYPESCRIPT_FAILURE;
|
|
14270
|
-
return { exitCode: 1, output, durationMs };
|
|
14904
|
+
const output2 = quiet ? "" : TYPESCRIPT_VALIDATION_MESSAGES.NO_VALIDATION_PATH_TARGETS;
|
|
14905
|
+
return { exitCode: 0, output: output2, durationMs };
|
|
14906
|
+
}
|
|
14907
|
+
if (result.success) {
|
|
14908
|
+
const output2 = quiet ? "" : TYPESCRIPT_VALIDATION_MESSAGES.SUCCESS;
|
|
14909
|
+
return { exitCode: 0, output: output2, durationMs };
|
|
14271
14910
|
}
|
|
14911
|
+
const output = result.error ?? VALIDATION_COMMAND_OUTPUT.TYPESCRIPT_FAILURE;
|
|
14912
|
+
return { exitCode: 1, output, durationMs };
|
|
14272
14913
|
}
|
|
14273
14914
|
|
|
14274
14915
|
// src/validation/languages/typescript.ts
|
|
@@ -14425,7 +15066,7 @@ async function allCommand(options) {
|
|
|
14425
15066
|
// src/validation/literal/allowlist-existing.ts
|
|
14426
15067
|
import { randomBytes } from "crypto";
|
|
14427
15068
|
import { rename as rename4, writeFile as writeFile4 } from "fs/promises";
|
|
14428
|
-
import { dirname as
|
|
15069
|
+
import { dirname as dirname10, join as join33 } from "path";
|
|
14429
15070
|
var EXIT_OK = 0;
|
|
14430
15071
|
var EXIT_ERROR = 1;
|
|
14431
15072
|
var INCLUDE_FIELD = "include";
|
|
@@ -14443,7 +15084,7 @@ var productionReader = {
|
|
|
14443
15084
|
};
|
|
14444
15085
|
var productionWriter = {
|
|
14445
15086
|
async write(filePath, content) {
|
|
14446
|
-
const dir =
|
|
15087
|
+
const dir = dirname10(filePath);
|
|
14447
15088
|
const random = randomBytes(RANDOM_TOKEN_BYTES).toString("hex");
|
|
14448
15089
|
const tmpPath = join33(dir, `${TEMP_FILE_PREFIX}${random}${TEMP_FILE_SUFFIX}`);
|
|
14449
15090
|
await writeFile4(tmpPath, content, "utf8");
|
|
@@ -14620,6 +15261,13 @@ var validationKnownOperands = /* @__PURE__ */ new Set([
|
|
|
14620
15261
|
...Object.values(validationCliDefinition.commanderHelpOperands)
|
|
14621
15262
|
]);
|
|
14622
15263
|
var validationOptionPrefix = validationCliDefinition.commanderHelpOperands.longFlag.slice(0, 1);
|
|
15264
|
+
function emitValidationResult(result, io) {
|
|
15265
|
+
if (result.output.length > 0) {
|
|
15266
|
+
io.writeStdout(`${result.output}
|
|
15267
|
+
`);
|
|
15268
|
+
}
|
|
15269
|
+
return io.exit(result.exitCode);
|
|
15270
|
+
}
|
|
14623
15271
|
function addCommonOptions(cmd) {
|
|
14624
15272
|
return cmd.option("--scope <scope>", "Validation scope (full|production)", "full").option("--files <paths...>", "Specific files/directories to validate").option("--quiet", "Suppress progress output").option("--json", "Output results as JSON");
|
|
14625
15273
|
}
|
|
@@ -14630,55 +15278,52 @@ function addValidationSubcommand(validationCmd, definition) {
|
|
|
14630
15278
|
}
|
|
14631
15279
|
return subcommand;
|
|
14632
15280
|
}
|
|
14633
|
-
function registerValidationCommands(validationCmd) {
|
|
15281
|
+
function registerValidationCommands(validationCmd, invocation) {
|
|
14634
15282
|
const { subcommands } = validationCliDefinition;
|
|
15283
|
+
const productDir = () => invocation.resolveProductContext().productDir;
|
|
14635
15284
|
const tsCmd = addValidationSubcommand(validationCmd, subcommands.typescript).action(async (options) => {
|
|
14636
15285
|
const result = await typescriptCommand({
|
|
14637
|
-
cwd:
|
|
15286
|
+
cwd: productDir(),
|
|
14638
15287
|
scope: options.scope,
|
|
14639
15288
|
files: options.files,
|
|
14640
15289
|
quiet: options.quiet,
|
|
14641
15290
|
json: options.json
|
|
14642
15291
|
});
|
|
14643
|
-
|
|
14644
|
-
process.exit(result.exitCode);
|
|
15292
|
+
emitValidationResult(result, invocation.io);
|
|
14645
15293
|
});
|
|
14646
15294
|
addCommonOptions(tsCmd);
|
|
14647
15295
|
const lintCmd = addValidationSubcommand(validationCmd, subcommands.lint).option("--fix", "Auto-fix issues").action(async (options) => {
|
|
14648
15296
|
const result = await lintCommand({
|
|
14649
|
-
cwd:
|
|
15297
|
+
cwd: productDir(),
|
|
14650
15298
|
scope: options.scope,
|
|
14651
15299
|
files: options.files,
|
|
14652
15300
|
fix: options.fix,
|
|
14653
15301
|
quiet: options.quiet,
|
|
14654
15302
|
json: options.json
|
|
14655
15303
|
});
|
|
14656
|
-
|
|
14657
|
-
process.exit(result.exitCode);
|
|
15304
|
+
emitValidationResult(result, invocation.io);
|
|
14658
15305
|
});
|
|
14659
15306
|
addCommonOptions(lintCmd);
|
|
14660
15307
|
const circularCmd = addValidationSubcommand(validationCmd, subcommands.circular).action(async (options) => {
|
|
14661
15308
|
const result = await circularCommand({
|
|
14662
|
-
cwd:
|
|
15309
|
+
cwd: productDir(),
|
|
14663
15310
|
scope: options.scope,
|
|
14664
15311
|
files: options.files,
|
|
14665
15312
|
quiet: options.quiet,
|
|
14666
15313
|
json: options.json
|
|
14667
15314
|
});
|
|
14668
|
-
|
|
14669
|
-
process.exit(result.exitCode);
|
|
15315
|
+
emitValidationResult(result, invocation.io);
|
|
14670
15316
|
});
|
|
14671
15317
|
addCommonOptions(circularCmd);
|
|
14672
15318
|
const knipCmd = addValidationSubcommand(validationCmd, subcommands.knip).action(async (options) => {
|
|
14673
15319
|
const result = await knipCommand({
|
|
14674
|
-
cwd:
|
|
15320
|
+
cwd: productDir(),
|
|
14675
15321
|
scope: options.scope,
|
|
14676
15322
|
files: options.files,
|
|
14677
15323
|
quiet: options.quiet,
|
|
14678
15324
|
json: options.json
|
|
14679
15325
|
});
|
|
14680
|
-
|
|
14681
|
-
process.exit(result.exitCode);
|
|
15326
|
+
emitValidationResult(result, invocation.io);
|
|
14682
15327
|
});
|
|
14683
15328
|
addCommonOptions(knipCmd);
|
|
14684
15329
|
const literalCmd = addValidationSubcommand(validationCmd, subcommands.literal).option(
|
|
@@ -14692,24 +15337,23 @@ function registerValidationCommands(validationCmd) {
|
|
|
14692
15337
|
"\nEnabled for TypeScript projects by default. Set validation.literal.enabled=false\nin spx.config.* to skip during migration."
|
|
14693
15338
|
).action(async (options) => {
|
|
14694
15339
|
if (options.allowlistExisting) {
|
|
14695
|
-
const result2 = await allowlistExisting({ productDir:
|
|
14696
|
-
|
|
14697
|
-
process.exit(result2.exitCode);
|
|
15340
|
+
const result2 = await allowlistExisting({ productDir: productDir() });
|
|
15341
|
+
emitValidationResult(result2, invocation.io);
|
|
14698
15342
|
}
|
|
14699
15343
|
let kind;
|
|
14700
15344
|
if (options.kind !== void 0) {
|
|
14701
15345
|
kind = parseLiteralProblemKind(options.kind);
|
|
14702
15346
|
if (kind === void 0) {
|
|
14703
15347
|
const { unknownLiteralProblemKind } = validationCliDefinition.diagnostics;
|
|
14704
|
-
|
|
15348
|
+
invocation.io.writeStderr(
|
|
14705
15349
|
`spx validation literal: ${unknownLiteralProblemKind.messageLabel}: ${sanitizeCliArgument(options.kind)}
|
|
14706
15350
|
`
|
|
14707
15351
|
);
|
|
14708
|
-
|
|
15352
|
+
invocation.io.exit(unknownLiteralProblemKind.exitCode);
|
|
14709
15353
|
}
|
|
14710
15354
|
}
|
|
14711
15355
|
const result = await literalCommand({
|
|
14712
|
-
cwd:
|
|
15356
|
+
cwd: productDir(),
|
|
14713
15357
|
files: options.files,
|
|
14714
15358
|
kind,
|
|
14715
15359
|
filesWithProblems: options.filesWithProblems,
|
|
@@ -14718,8 +15362,7 @@ function registerValidationCommands(validationCmd) {
|
|
|
14718
15362
|
quiet: options.quiet,
|
|
14719
15363
|
json: options.json
|
|
14720
15364
|
});
|
|
14721
|
-
|
|
14722
|
-
process.exit(result.exitCode);
|
|
15365
|
+
emitValidationResult(result, invocation.io);
|
|
14723
15366
|
});
|
|
14724
15367
|
addCommonOptions(literalCmd);
|
|
14725
15368
|
const markdownCmd = addValidationSubcommand(validationCmd, subcommands.markdown).addHelpText(
|
|
@@ -14727,27 +15370,25 @@ function registerValidationCommands(validationCmd) {
|
|
|
14727
15370
|
"\nValidates spx/ and docs/ by default. Nodes listed in spx/EXCLUDE are\nskipped \u2014 use this for declared-state nodes whose [test] links point\nto files that do not exist yet."
|
|
14728
15371
|
).action(async (options) => {
|
|
14729
15372
|
const result = await markdownCommand({
|
|
14730
|
-
cwd:
|
|
15373
|
+
cwd: productDir(),
|
|
14731
15374
|
files: options.files,
|
|
14732
15375
|
quiet: options.quiet
|
|
14733
15376
|
});
|
|
14734
|
-
|
|
14735
|
-
process.exit(result.exitCode);
|
|
15377
|
+
emitValidationResult(result, invocation.io);
|
|
14736
15378
|
});
|
|
14737
15379
|
addCommonOptions(markdownCmd);
|
|
14738
15380
|
const formatCmd = addValidationSubcommand(validationCmd, subcommands.format).action(async (options) => {
|
|
14739
15381
|
const result = await formattingCommand({
|
|
14740
|
-
cwd:
|
|
15382
|
+
cwd: productDir(),
|
|
14741
15383
|
files: options.files,
|
|
14742
15384
|
quiet: options.quiet
|
|
14743
15385
|
});
|
|
14744
|
-
|
|
14745
|
-
process.exit(result.exitCode);
|
|
15386
|
+
emitValidationResult(result, invocation.io);
|
|
14746
15387
|
});
|
|
14747
15388
|
addCommonOptions(formatCmd);
|
|
14748
15389
|
const allCmd = addValidationSubcommand(validationCmd, subcommands.all).option("--fix", "Auto-fix ESLint issues").option(allValidationCliOptions.skipCircular.flag, allValidationCliOptions.skipCircular.description).option(allValidationCliOptions.skipLiteral.flag, allValidationCliOptions.skipLiteral.description).action(async (options) => {
|
|
14749
15390
|
const result = await allCommand({
|
|
14750
|
-
cwd:
|
|
15391
|
+
cwd: productDir(),
|
|
14751
15392
|
scope: options.scope,
|
|
14752
15393
|
files: options.files,
|
|
14753
15394
|
fix: options.fix,
|
|
@@ -14756,8 +15397,7 @@ function registerValidationCommands(validationCmd) {
|
|
|
14756
15397
|
quiet: options.quiet,
|
|
14757
15398
|
json: options.json
|
|
14758
15399
|
});
|
|
14759
|
-
|
|
14760
|
-
process.exit(result.exitCode);
|
|
15400
|
+
emitValidationResult(result, invocation.io);
|
|
14761
15401
|
});
|
|
14762
15402
|
addCommonOptions(allCmd);
|
|
14763
15403
|
}
|
|
@@ -14767,25 +15407,264 @@ function parseLiteralProblemKind(value) {
|
|
|
14767
15407
|
}
|
|
14768
15408
|
return void 0;
|
|
14769
15409
|
}
|
|
14770
|
-
function handleUnknownSubcommand(operands) {
|
|
15410
|
+
function handleUnknownSubcommand(operands, io) {
|
|
14771
15411
|
const [first] = operands;
|
|
14772
15412
|
const sanitized = sanitizeCliArgument(first);
|
|
14773
15413
|
const { domain, diagnostics } = validationCliDefinition;
|
|
14774
15414
|
const { unknownSubcommand } = diagnostics;
|
|
14775
|
-
|
|
15415
|
+
io.writeStderr(`spx ${domain.commandName}: ${unknownSubcommand.messageLabel}: ${sanitized}
|
|
14776
15416
|
`);
|
|
14777
|
-
|
|
15417
|
+
return io.exit(unknownSubcommand.exitCode);
|
|
14778
15418
|
}
|
|
14779
15419
|
var validationDomain = {
|
|
14780
15420
|
name: validationCliDefinition.domain.commandName,
|
|
14781
15421
|
description: validationCliDefinition.domain.description,
|
|
14782
|
-
register: (
|
|
15422
|
+
register: (program, invocation) => {
|
|
14783
15423
|
const { domain } = validationCliDefinition;
|
|
14784
|
-
const validationCmd =
|
|
15424
|
+
const validationCmd = program.command(domain.commandName).alias(domain.alias).description(domain.description);
|
|
14785
15425
|
validationCmd.on("command:*", (operands) => {
|
|
14786
|
-
handleUnknownSubcommand(operands);
|
|
15426
|
+
handleUnknownSubcommand(operands, invocation.io);
|
|
15427
|
+
});
|
|
15428
|
+
registerValidationCommands(validationCmd, invocation);
|
|
15429
|
+
}
|
|
15430
|
+
};
|
|
15431
|
+
|
|
15432
|
+
// src/commands/verification-context/cli.ts
|
|
15433
|
+
import { isAbsolute as isAbsolute8, win32 } from "path";
|
|
15434
|
+
|
|
15435
|
+
// src/domains/verification-context/context.ts
|
|
15436
|
+
var VERIFICATION_CONTEXT_SCHEMA_VERSION = "verification-context.v1";
|
|
15437
|
+
var VERIFICATION_CONTEXT_SUBJECT_KIND = {
|
|
15438
|
+
FILE: "file",
|
|
15439
|
+
CHANGESET: "changeset"
|
|
15440
|
+
};
|
|
15441
|
+
var VERIFICATION_CONTEXT_PERSISTENCE = {
|
|
15442
|
+
kind: "state-store",
|
|
15443
|
+
scope: "branch",
|
|
15444
|
+
domain: "verification-context",
|
|
15445
|
+
format: "canonical-json"
|
|
15446
|
+
};
|
|
15447
|
+
var VERIFICATION_CONTEXT_DIGEST_PATH = "verification context";
|
|
15448
|
+
function createVerificationContextDocument(payload) {
|
|
15449
|
+
const digest = digestDescriptorSection(payload, VERIFICATION_CONTEXT_DIGEST_PATH);
|
|
15450
|
+
if (!digest.ok) return digest;
|
|
15451
|
+
const document = {
|
|
15452
|
+
digest: digest.value.sha256,
|
|
15453
|
+
context: payload
|
|
15454
|
+
};
|
|
15455
|
+
const canonical = canonicalDescriptorJson(document, VERIFICATION_CONTEXT_DIGEST_PATH);
|
|
15456
|
+
if (!canonical.ok) return canonical;
|
|
15457
|
+
return {
|
|
15458
|
+
ok: true,
|
|
15459
|
+
value: {
|
|
15460
|
+
...document,
|
|
15461
|
+
canonicalJson: canonical.value
|
|
15462
|
+
}
|
|
15463
|
+
};
|
|
15464
|
+
}
|
|
15465
|
+
|
|
15466
|
+
// src/commands/verification-context/runtime.ts
|
|
15467
|
+
import { dirname as dirname11 } from "path";
|
|
15468
|
+
|
|
15469
|
+
// src/domains/verification-context/path.ts
|
|
15470
|
+
import { join as join34 } from "path";
|
|
15471
|
+
var VERIFICATION_CONTEXT_STATE_DOMAIN = VERIFICATION_CONTEXT_PERSISTENCE.domain;
|
|
15472
|
+
var VERIFICATION_CONTEXT_STATE_PATH = {
|
|
15473
|
+
CONTEXTS_DIR: "contexts",
|
|
15474
|
+
FILE_PREFIX: "context-",
|
|
15475
|
+
JSON_EXTENSION: ".json"
|
|
15476
|
+
};
|
|
15477
|
+
function verificationContextFileName(digest) {
|
|
15478
|
+
return `${VERIFICATION_CONTEXT_STATE_PATH.FILE_PREFIX}${digest}${VERIFICATION_CONTEXT_STATE_PATH.JSON_EXTENSION}`;
|
|
15479
|
+
}
|
|
15480
|
+
function verificationContextFilePath(scope2) {
|
|
15481
|
+
const branchScope = branchScopeDir(scope2.productDir, scope2.branchSlug);
|
|
15482
|
+
if (!branchScope.ok) return branchScope;
|
|
15483
|
+
const contextsDir = composeScopeDir(
|
|
15484
|
+
branchScope.value,
|
|
15485
|
+
VERIFICATION_CONTEXT_STATE_DOMAIN,
|
|
15486
|
+
VERIFICATION_CONTEXT_STATE_PATH.CONTEXTS_DIR
|
|
15487
|
+
);
|
|
15488
|
+
if (!contextsDir.ok) return contextsDir;
|
|
15489
|
+
const digest = validateScopeToken(scope2.digest);
|
|
15490
|
+
if (!digest.ok) return digest;
|
|
15491
|
+
return { ok: true, value: join34(contextsDir.value, verificationContextFileName(digest.value)) };
|
|
15492
|
+
}
|
|
15493
|
+
|
|
15494
|
+
// src/commands/verification-context/runtime.ts
|
|
15495
|
+
var VERIFICATION_CONTEXT_RUNTIME_ERROR = {
|
|
15496
|
+
READ_FAILED: "verification context read failed",
|
|
15497
|
+
WRITE_FAILED: "verification context write failed",
|
|
15498
|
+
CONTENT_MISMATCH: "verification context already exists with different content"
|
|
15499
|
+
};
|
|
15500
|
+
async function persistVerificationContext(scope2, document, options = {}) {
|
|
15501
|
+
const contextPath = verificationContextFilePath(scope2);
|
|
15502
|
+
if (!contextPath.ok) return contextPath;
|
|
15503
|
+
const fs8 = options.fs ?? defaultFileSystem;
|
|
15504
|
+
try {
|
|
15505
|
+
await fs8.mkdir(dirname11(contextPath.value), { recursive: true });
|
|
15506
|
+
await fs8.writeFile(contextPath.value, document.canonicalJson, { flag: EXCLUSIVE_CREATE_FLAG });
|
|
15507
|
+
} catch (error) {
|
|
15508
|
+
if (hasErrorCode(error, ERROR_CODE_FILE_EXISTS)) {
|
|
15509
|
+
const existing = await readExistingContext(fs8, contextPath.value);
|
|
15510
|
+
if (!existing.ok) return existing;
|
|
15511
|
+
if (existing.value === document.canonicalJson) {
|
|
15512
|
+
return { ok: true, value: { digest: document.digest, contextPath: contextPath.value } };
|
|
15513
|
+
}
|
|
15514
|
+
return { ok: false, error: VERIFICATION_CONTEXT_RUNTIME_ERROR.CONTENT_MISMATCH };
|
|
15515
|
+
}
|
|
15516
|
+
return { ok: false, error: `${VERIFICATION_CONTEXT_RUNTIME_ERROR.WRITE_FAILED}: ${toMessage(error)}` };
|
|
15517
|
+
}
|
|
15518
|
+
return { ok: true, value: { digest: document.digest, contextPath: contextPath.value } };
|
|
15519
|
+
}
|
|
15520
|
+
async function readExistingContext(fs8, contextPath) {
|
|
15521
|
+
try {
|
|
15522
|
+
return { ok: true, value: await fs8.readFile(contextPath, STATE_STORE_TEXT_ENCODING) };
|
|
15523
|
+
} catch (error) {
|
|
15524
|
+
return { ok: false, error: `${VERIFICATION_CONTEXT_RUNTIME_ERROR.READ_FAILED}: ${toMessage(error)}` };
|
|
15525
|
+
}
|
|
15526
|
+
}
|
|
15527
|
+
|
|
15528
|
+
// src/commands/verification-context/cli.ts
|
|
15529
|
+
var VERIFICATION_CONTEXT_CLI_EXIT_CODE = {
|
|
15530
|
+
OK: 0,
|
|
15531
|
+
ERROR: 1
|
|
15532
|
+
};
|
|
15533
|
+
var VERIFICATION_CONTEXT_CLI_ENV = {
|
|
15534
|
+
BRANCH: SPX_VERIFY_ENV.BRANCH
|
|
15535
|
+
};
|
|
15536
|
+
var VERIFICATION_CONTEXT_CLI_ERROR = {
|
|
15537
|
+
INVALID_SUBJECT: "verification context subject must be file or changeset",
|
|
15538
|
+
FILE_PATH_REQUIRED: "verification context file subject requires --path",
|
|
15539
|
+
FILE_PATH_UNSAFE: "verification context file subject path must be product-relative",
|
|
15540
|
+
CHANGESET_REFS_REQUIRED: "verification context changeset subject requires --base and --head"
|
|
15541
|
+
};
|
|
15542
|
+
var VERIFICATION_CONTEXT_FILE_SUBJECT_PATH = {
|
|
15543
|
+
PARENT_DIRECTORY: {
|
|
15544
|
+
SEGMENT: "..",
|
|
15545
|
+
PREFIX: "../"
|
|
15546
|
+
},
|
|
15547
|
+
SEPARATOR: {
|
|
15548
|
+
CANONICAL: "/",
|
|
15549
|
+
WINDOWS: "\\"
|
|
15550
|
+
}
|
|
15551
|
+
};
|
|
15552
|
+
async function resolveCommandScope(deps) {
|
|
15553
|
+
const cwd = deps.cwd ?? CONFIG_PROCESS_CWD.read();
|
|
15554
|
+
const git = deps.git ?? defaultGitDependencies;
|
|
15555
|
+
const product = await detectGitCommonDirProductRoot(cwd, git);
|
|
15556
|
+
const processEnv = deps.processEnv ?? process.env;
|
|
15557
|
+
const probedBranch = product.isGitRepo ? await getCurrentBranch(cwd, git) ?? void 0 : void 0;
|
|
15558
|
+
const headSha = (product.isGitRepo ? await getHeadSha(cwd, git) : null) ?? SPX_VERIFY_HEAD_SHA.MISSING;
|
|
15559
|
+
const branchIdentity = resolveBranchIdentity({
|
|
15560
|
+
branchName: deps.branch ?? processEnv[VERIFICATION_CONTEXT_CLI_ENV.BRANCH] ?? probedBranch,
|
|
15561
|
+
headSha
|
|
15562
|
+
});
|
|
15563
|
+
return {
|
|
15564
|
+
storageProductDir: product.productDir,
|
|
15565
|
+
launchProductDir: product.worktreeRoot,
|
|
15566
|
+
branchSlug: slugBranchIdentity(branchIdentity),
|
|
15567
|
+
branchIdentity,
|
|
15568
|
+
headSha
|
|
15569
|
+
};
|
|
15570
|
+
}
|
|
15571
|
+
function okResult2(output) {
|
|
15572
|
+
return { exitCode: VERIFICATION_CONTEXT_CLI_EXIT_CODE.OK, output };
|
|
15573
|
+
}
|
|
15574
|
+
function errorResult2(error) {
|
|
15575
|
+
return { exitCode: VERIFICATION_CONTEXT_CLI_EXIT_CODE.ERROR, output: error };
|
|
15576
|
+
}
|
|
15577
|
+
function normalizeFileSubjectPath(path7) {
|
|
15578
|
+
const windowsRoot = win32.parse(path7).root;
|
|
15579
|
+
const normalized = win32.normalize(path7).replaceAll(
|
|
15580
|
+
VERIFICATION_CONTEXT_FILE_SUBJECT_PATH.SEPARATOR.WINDOWS,
|
|
15581
|
+
VERIFICATION_CONTEXT_FILE_SUBJECT_PATH.SEPARATOR.CANONICAL
|
|
15582
|
+
);
|
|
15583
|
+
const segments = normalized.split(VERIFICATION_CONTEXT_FILE_SUBJECT_PATH.SEPARATOR.CANONICAL);
|
|
15584
|
+
if (isAbsolute8(path7) || win32.isAbsolute(path7) || windowsRoot.length > 0 || segments.includes(VERIFICATION_CONTEXT_FILE_SUBJECT_PATH.PARENT_DIRECTORY.SEGMENT)) {
|
|
15585
|
+
return void 0;
|
|
15586
|
+
}
|
|
15587
|
+
return normalized;
|
|
15588
|
+
}
|
|
15589
|
+
function resolveSubject(options) {
|
|
15590
|
+
if (options.subject === VERIFICATION_CONTEXT_SUBJECT_KIND.FILE) {
|
|
15591
|
+
if (options.path === void 0 || options.path.length === 0) {
|
|
15592
|
+
return VERIFICATION_CONTEXT_CLI_ERROR.FILE_PATH_REQUIRED;
|
|
15593
|
+
}
|
|
15594
|
+
const path7 = normalizeFileSubjectPath(options.path);
|
|
15595
|
+
if (path7 === void 0) return VERIFICATION_CONTEXT_CLI_ERROR.FILE_PATH_UNSAFE;
|
|
15596
|
+
return { kind: VERIFICATION_CONTEXT_SUBJECT_KIND.FILE, path: path7 };
|
|
15597
|
+
}
|
|
15598
|
+
if (options.subject === VERIFICATION_CONTEXT_SUBJECT_KIND.CHANGESET) {
|
|
15599
|
+
if (options.base === void 0 || options.base.length === 0 || options.head === void 0 || options.head.length === 0) {
|
|
15600
|
+
return VERIFICATION_CONTEXT_CLI_ERROR.CHANGESET_REFS_REQUIRED;
|
|
15601
|
+
}
|
|
15602
|
+
return { kind: VERIFICATION_CONTEXT_SUBJECT_KIND.CHANGESET, base: options.base, head: options.head };
|
|
15603
|
+
}
|
|
15604
|
+
return VERIFICATION_CONTEXT_CLI_ERROR.INVALID_SUBJECT;
|
|
15605
|
+
}
|
|
15606
|
+
async function verificationContextCreateCommand(options, deps = {}) {
|
|
15607
|
+
const subject = resolveSubject(options);
|
|
15608
|
+
if (typeof subject === "string") return errorResult2(subject);
|
|
15609
|
+
const scope2 = await resolveCommandScope(deps);
|
|
15610
|
+
const document = createVerificationContextDocument({
|
|
15611
|
+
schemaVersion: VERIFICATION_CONTEXT_SCHEMA_VERSION,
|
|
15612
|
+
subject,
|
|
15613
|
+
predicate: options.predicate,
|
|
15614
|
+
workflow: { name: options.workflow },
|
|
15615
|
+
launch: {
|
|
15616
|
+
productDir: scope2.launchProductDir,
|
|
15617
|
+
branchSlug: scope2.branchSlug,
|
|
15618
|
+
branchIdentity: scope2.branchIdentity,
|
|
15619
|
+
headSha: scope2.headSha,
|
|
15620
|
+
createdAt: (deps.now ?? (() => /* @__PURE__ */ new Date()))().toISOString()
|
|
15621
|
+
},
|
|
15622
|
+
persistence: VERIFICATION_CONTEXT_PERSISTENCE
|
|
15623
|
+
});
|
|
15624
|
+
if (!document.ok) return errorResult2(document.error);
|
|
15625
|
+
const persisted = await persistVerificationContext(
|
|
15626
|
+
{ productDir: scope2.storageProductDir, branchSlug: scope2.branchSlug, digest: document.value.digest },
|
|
15627
|
+
document.value,
|
|
15628
|
+
{ ...deps.fs === void 0 ? {} : { fs: deps.fs } }
|
|
15629
|
+
);
|
|
15630
|
+
if (!persisted.ok) return errorResult2(persisted.error);
|
|
15631
|
+
return okResult2(JSON.stringify(persisted.value));
|
|
15632
|
+
}
|
|
15633
|
+
|
|
15634
|
+
// src/interfaces/cli/lib/stream-report.ts
|
|
15635
|
+
var CLI_STREAM_REPORT = {
|
|
15636
|
+
LINE_SEPARATOR: "\n"
|
|
15637
|
+
};
|
|
15638
|
+
function reportCliResult(result, io) {
|
|
15639
|
+
const output = `${result.output}${CLI_STREAM_REPORT.LINE_SEPARATOR}`;
|
|
15640
|
+
if (result.exitCode === 0) io.writeStdout(output);
|
|
15641
|
+
else io.writeStderr(output);
|
|
15642
|
+
io.setExitCode(result.exitCode);
|
|
15643
|
+
}
|
|
15644
|
+
|
|
15645
|
+
// src/interfaces/cli/verification-context.ts
|
|
15646
|
+
var VERIFICATION_CONTEXT_CLI = {
|
|
15647
|
+
commandName: "verification-context",
|
|
15648
|
+
description: "Create an immutable verification context document",
|
|
15649
|
+
createCommandName: "create",
|
|
15650
|
+
subjectOption: "--subject <subject>",
|
|
15651
|
+
pathOption: "--path <path>",
|
|
15652
|
+
baseOption: "--base <ref>",
|
|
15653
|
+
headOption: "--head <ref>",
|
|
15654
|
+
predicateOption: "--predicate <predicate>",
|
|
15655
|
+
workflowOption: "--workflow <workflow>"
|
|
15656
|
+
};
|
|
15657
|
+
var verificationContextDomain = {
|
|
15658
|
+
name: VERIFICATION_CONTEXT_CLI.commandName,
|
|
15659
|
+
description: VERIFICATION_CONTEXT_CLI.description,
|
|
15660
|
+
register: (program, invocation) => {
|
|
15661
|
+
const command = program.command(VERIFICATION_CONTEXT_CLI.commandName).description(VERIFICATION_CONTEXT_CLI.description);
|
|
15662
|
+
command.command(VERIFICATION_CONTEXT_CLI.createCommandName).description("Create a canonical verification context and report its path and digest").requiredOption(VERIFICATION_CONTEXT_CLI.subjectOption, "Subject kind: file or changeset").option(VERIFICATION_CONTEXT_CLI.pathOption, "Product-relative file path for a file subject").option(VERIFICATION_CONTEXT_CLI.baseOption, "Base ref for a changeset subject").option(VERIFICATION_CONTEXT_CLI.headOption, "Head ref for a changeset subject").requiredOption(VERIFICATION_CONTEXT_CLI.predicateOption, "Caller-supplied predicate identifier").requiredOption(VERIFICATION_CONTEXT_CLI.workflowOption, "Caller-supplied workflow identifier").action(async (options) => {
|
|
15663
|
+
reportCliResult(
|
|
15664
|
+
await verificationContextCreateCommand(options, { cwd: invocation.resolveEffectiveInvocationDir() }),
|
|
15665
|
+
invocation.io
|
|
15666
|
+
);
|
|
14787
15667
|
});
|
|
14788
|
-
registerValidationCommands(validationCmd);
|
|
14789
15668
|
}
|
|
14790
15669
|
};
|
|
14791
15670
|
|
|
@@ -14890,15 +15769,29 @@ var WORKTREE_CLI = {
|
|
|
14890
15769
|
WORKTREES_DIR_FLAG: "--worktrees-dir"
|
|
14891
15770
|
};
|
|
14892
15771
|
var WORKTREE_DOMAIN_DESCRIPTION = "Coordinate worktree occupancy across a bare-repository pool";
|
|
14893
|
-
function
|
|
14894
|
-
|
|
14895
|
-
|
|
15772
|
+
function writeOutput4(invocation, output) {
|
|
15773
|
+
invocation.io.writeStdout(`${output}
|
|
15774
|
+
`);
|
|
15775
|
+
}
|
|
15776
|
+
function writeError4(invocation, output) {
|
|
15777
|
+
invocation.io.writeStderr(`${output}
|
|
15778
|
+
`);
|
|
15779
|
+
}
|
|
15780
|
+
function writeInvocationWarning4(invocation, warning) {
|
|
15781
|
+
if (warning !== void 0) {
|
|
15782
|
+
writeError4(invocation, warning);
|
|
15783
|
+
}
|
|
14896
15784
|
}
|
|
14897
|
-
function
|
|
15785
|
+
function handleError3(invocation, error) {
|
|
15786
|
+
writeError4(invocation, `Error: ${error}`);
|
|
15787
|
+
return invocation.io.exit(1);
|
|
15788
|
+
}
|
|
15789
|
+
function registerWorktreeCommands(worktreeCmd, invocation) {
|
|
15790
|
+
const effectiveInvocationDir = () => invocation.resolveEffectiveInvocationDir();
|
|
14898
15791
|
worktreeCmd.command(WORKTREE_CLI.CLAIM).description("Record a worktree-occupancy claim for the running worktree").requiredOption(`${WORKTREE_CLI.SESSION_ID_FLAG} <id>`, "Claiming agent session id").option(`${WORKTREE_CLI.WORKTREES_DIR_FLAG} <path>`, "Explicit .spx/worktrees directory").action(async (options) => {
|
|
14899
15792
|
const result = await claimCommand({
|
|
14900
15793
|
claimWriteToken: createClaimWriteToken(),
|
|
14901
|
-
cwd:
|
|
15794
|
+
cwd: effectiveInvocationDir(),
|
|
14902
15795
|
env: process.env,
|
|
14903
15796
|
fs: defaultOccupancyFileSystem,
|
|
14904
15797
|
gitDeps: defaultGitDependencies,
|
|
@@ -14906,13 +15799,13 @@ function registerWorktreeCommands(worktreeCmd) {
|
|
|
14906
15799
|
selfPid: process.pid,
|
|
14907
15800
|
sessionId: options.sessionId,
|
|
14908
15801
|
worktreesDir: options.worktreesDir,
|
|
14909
|
-
onWarning:
|
|
15802
|
+
onWarning: (warning) => writeInvocationWarning4(invocation, warning)
|
|
14910
15803
|
});
|
|
14911
|
-
if (!result.ok) handleError3(result.error);
|
|
15804
|
+
if (!result.ok) handleError3(invocation, result.error);
|
|
14912
15805
|
});
|
|
14913
15806
|
worktreeCmd.command(`${WORKTREE_CLI.STATUS} ${WORKTREE_CLI.WORKTREE_ARGUMENT}`).description("Report a worktree's occupancy (running | free)").option(`${WORKTREE_CLI.FORMAT_FLAG} <format>`, "Output format (text|json)", WORKTREE_STATUS_FORMAT.TEXT).option(`${WORKTREE_CLI.WORKTREES_DIR_FLAG} <path>`, "Explicit .spx/worktrees directory").action(async (worktrees, options) => {
|
|
14914
15807
|
const result = await statusCommand2({
|
|
14915
|
-
cwd:
|
|
15808
|
+
cwd: effectiveInvocationDir(),
|
|
14916
15809
|
fs: defaultOccupancyFileSystem,
|
|
14917
15810
|
gitDeps: defaultGitDependencies,
|
|
14918
15811
|
worktrees,
|
|
@@ -14920,28 +15813,28 @@ function registerWorktreeCommands(worktreeCmd) {
|
|
|
14920
15813
|
pathInfo: defaultWorktreePathInfo,
|
|
14921
15814
|
processTable: defaultProcessTable,
|
|
14922
15815
|
worktreesDir: options.worktreesDir,
|
|
14923
|
-
onWarning:
|
|
15816
|
+
onWarning: (warning) => writeInvocationWarning4(invocation, warning)
|
|
14924
15817
|
});
|
|
14925
|
-
if (!result.ok) handleError3(result.error);
|
|
14926
|
-
|
|
15818
|
+
if (!result.ok) handleError3(invocation, result.error);
|
|
15819
|
+
writeOutput4(invocation, result.value);
|
|
14927
15820
|
});
|
|
14928
15821
|
worktreeCmd.command(WORKTREE_CLI.RELEASE).description("Release the running worktree's occupancy claim").option(`${WORKTREE_CLI.WORKTREES_DIR_FLAG} <path>`, "Explicit .spx/worktrees directory").action(async (options) => {
|
|
14929
15822
|
const result = await releaseCommand2({
|
|
14930
|
-
cwd:
|
|
15823
|
+
cwd: effectiveInvocationDir(),
|
|
14931
15824
|
fs: defaultOccupancyFileSystem,
|
|
14932
15825
|
gitDeps: defaultGitDependencies,
|
|
14933
15826
|
worktreesDir: options.worktreesDir,
|
|
14934
|
-
onWarning:
|
|
15827
|
+
onWarning: (warning) => writeInvocationWarning4(invocation, warning)
|
|
14935
15828
|
});
|
|
14936
|
-
if (!result.ok) handleError3(result.error);
|
|
15829
|
+
if (!result.ok) handleError3(invocation, result.error);
|
|
14937
15830
|
});
|
|
14938
15831
|
}
|
|
14939
15832
|
var worktreeDomain = {
|
|
14940
15833
|
name: WORKTREE_CLI.COMMAND,
|
|
14941
15834
|
description: WORKTREE_DOMAIN_DESCRIPTION,
|
|
14942
|
-
register: (
|
|
14943
|
-
const worktreeCmd =
|
|
14944
|
-
registerWorktreeCommands(worktreeCmd);
|
|
15835
|
+
register: (program, invocation) => {
|
|
15836
|
+
const worktreeCmd = program.command(WORKTREE_CLI.COMMAND).description(WORKTREE_DOMAIN_DESCRIPTION);
|
|
15837
|
+
registerWorktreeCommands(worktreeCmd, invocation);
|
|
14945
15838
|
}
|
|
14946
15839
|
};
|
|
14947
15840
|
|
|
@@ -14957,17 +15850,46 @@ var CLI_DOMAINS = [
|
|
|
14957
15850
|
specDomain,
|
|
14958
15851
|
testingDomain,
|
|
14959
15852
|
validationDomain,
|
|
15853
|
+
verificationContextDomain,
|
|
14960
15854
|
worktreeDomain
|
|
14961
15855
|
];
|
|
14962
15856
|
|
|
15857
|
+
// src/interfaces/cli/program.ts
|
|
15858
|
+
var SPX_PROGRAM_NAME = "spx";
|
|
15859
|
+
var SPX_PROGRAM_DESCRIPTION = "Fast, deterministic CLI tool for spec workflow management";
|
|
15860
|
+
function createCliProgram(options = {}) {
|
|
15861
|
+
const program = new Command();
|
|
15862
|
+
const io = {
|
|
15863
|
+
writeStdout: options.writeStdout ?? DEFAULT_CLI_IO.writeStdout,
|
|
15864
|
+
writeStderr: options.writeStderr ?? DEFAULT_CLI_IO.writeStderr,
|
|
15865
|
+
setExitCode: options.setExitCode ?? DEFAULT_CLI_IO.setExitCode,
|
|
15866
|
+
exit: options.exit ?? DEFAULT_CLI_IO.exit
|
|
15867
|
+
};
|
|
15868
|
+
program.name(SPX_PROGRAM_NAME).description(SPX_PROGRAM_DESCRIPTION).option(SPX_GLOBAL_OPTIONS.directory.flags, SPX_GLOBAL_OPTIONS.directory.description);
|
|
15869
|
+
if (options.version !== void 0) {
|
|
15870
|
+
program.version(options.version);
|
|
15871
|
+
}
|
|
15872
|
+
const invocation = createCliInvocation({
|
|
15873
|
+
readDirectoryOption: () => program.opts().directory,
|
|
15874
|
+
processCwd: options.processCwd ?? CONFIG_PROCESS_CWD.read,
|
|
15875
|
+
resolveProductDir,
|
|
15876
|
+
writeWarning: (warning) => {
|
|
15877
|
+
if (warning !== void 0) {
|
|
15878
|
+
io.writeStderr(`${warning}
|
|
15879
|
+
`);
|
|
15880
|
+
}
|
|
15881
|
+
},
|
|
15882
|
+
io
|
|
15883
|
+
});
|
|
15884
|
+
for (const domain of options.domains ?? CLI_DOMAINS) {
|
|
15885
|
+
domain.register(program, invocation);
|
|
15886
|
+
}
|
|
15887
|
+
return program;
|
|
15888
|
+
}
|
|
15889
|
+
|
|
14963
15890
|
// src/cli.ts
|
|
14964
15891
|
installLifecycle();
|
|
14965
15892
|
var require3 = createRequire2(import.meta.url);
|
|
14966
15893
|
var { version } = require3("../package.json");
|
|
14967
|
-
|
|
14968
|
-
program.name("spx").description("Fast, deterministic CLI tool for spec workflow management").version(version);
|
|
14969
|
-
for (const domain of CLI_DOMAINS) {
|
|
14970
|
-
domain.register(program);
|
|
14971
|
-
}
|
|
14972
|
-
program.parse();
|
|
15894
|
+
createCliProgram({ version }).parse();
|
|
14973
15895
|
//# sourceMappingURL=cli.js.map
|