@outcomeeng/spx 0.6.1 → 0.6.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +1110 -629
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -49,39 +49,45 @@ async function findSettingsFiles(root, visited = /* @__PURE__ */ new Set()) {
|
|
|
49
49
|
}
|
|
50
50
|
visited.add(normalizedRoot);
|
|
51
51
|
try {
|
|
52
|
-
|
|
53
|
-
if (!stats.isDirectory()) {
|
|
54
|
-
throw new Error(`Path is not a directory: ${normalizedRoot}`);
|
|
55
|
-
}
|
|
56
|
-
const entries = await fs.readdir(normalizedRoot, { withFileTypes: true });
|
|
57
|
-
const results = [];
|
|
58
|
-
for (const entry of entries) {
|
|
59
|
-
const fullPath = path.join(normalizedRoot, entry.name);
|
|
60
|
-
if (entry.isDirectory() && entry.name === ".claude") {
|
|
61
|
-
const settingsPath = path.join(fullPath, "settings.local.json");
|
|
62
|
-
if (await isValidSettingsFile(settingsPath)) {
|
|
63
|
-
results.push(settingsPath);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
if (entry.isDirectory() && entry.name !== ".claude") {
|
|
67
|
-
const subFiles = await findSettingsFiles(fullPath, visited);
|
|
68
|
-
results.push(...subFiles);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
return results;
|
|
52
|
+
return await findSettingsFilesInDirectory(normalizedRoot, visited);
|
|
72
53
|
} catch (error) {
|
|
73
|
-
|
|
74
|
-
if (error.message.includes("ENOENT")) {
|
|
75
|
-
throw new Error(`Directory not found: ${normalizedRoot}`);
|
|
76
|
-
}
|
|
77
|
-
if (error.message.includes("EACCES")) {
|
|
78
|
-
throw new Error(`Permission denied: ${normalizedRoot}`);
|
|
79
|
-
}
|
|
80
|
-
throw new Error(`Failed to search directory "${normalizedRoot}": ${error.message}`);
|
|
81
|
-
}
|
|
82
|
-
throw error;
|
|
54
|
+
throw discoveryError(normalizedRoot, error);
|
|
83
55
|
}
|
|
84
56
|
}
|
|
57
|
+
async function findSettingsFilesInDirectory(normalizedRoot, visited) {
|
|
58
|
+
const stats = await fs.stat(normalizedRoot);
|
|
59
|
+
if (!stats.isDirectory()) {
|
|
60
|
+
throw new Error(`Path is not a directory: ${normalizedRoot}`);
|
|
61
|
+
}
|
|
62
|
+
const entries = await fs.readdir(normalizedRoot, { withFileTypes: true });
|
|
63
|
+
const results = [];
|
|
64
|
+
for (const entry of entries) {
|
|
65
|
+
results.push(...await settingsFilesForEntry(normalizedRoot, entry, visited));
|
|
66
|
+
}
|
|
67
|
+
return results;
|
|
68
|
+
}
|
|
69
|
+
async function settingsFilesForEntry(normalizedRoot, entry, visited) {
|
|
70
|
+
if (!entry.isDirectory()) return [];
|
|
71
|
+
const fullPath = path.join(normalizedRoot, entry.name);
|
|
72
|
+
if (entry.name === ".claude") {
|
|
73
|
+
const settingsPath = path.join(fullPath, "settings.local.json");
|
|
74
|
+
return await isValidSettingsFile(settingsPath) ? [settingsPath] : [];
|
|
75
|
+
}
|
|
76
|
+
return findSettingsFiles(fullPath, visited);
|
|
77
|
+
}
|
|
78
|
+
function discoveryError(normalizedRoot, error) {
|
|
79
|
+
if (!(error instanceof Error)) return new Error(unknownErrorMessage(error));
|
|
80
|
+
if (error.message.includes("ENOENT")) return new Error(`Directory not found: ${normalizedRoot}`);
|
|
81
|
+
if (error.message.includes("EACCES")) return new Error(`Permission denied: ${normalizedRoot}`);
|
|
82
|
+
return new Error(`Failed to search directory "${normalizedRoot}": ${error.message}`);
|
|
83
|
+
}
|
|
84
|
+
function unknownErrorMessage(error) {
|
|
85
|
+
if (typeof error === "string") return error;
|
|
86
|
+
if (typeof error === "number" || typeof error === "boolean" || typeof error === "bigint") return error.toString();
|
|
87
|
+
if (typeof error === "symbol") return error.description ?? "Symbol()";
|
|
88
|
+
if (error === void 0) return "undefined";
|
|
89
|
+
return Object.prototype.toString.call(error);
|
|
90
|
+
}
|
|
85
91
|
async function isValidSettingsFile(filePath) {
|
|
86
92
|
try {
|
|
87
93
|
await fs.access(filePath, fs.constants.R_OK);
|
|
@@ -801,6 +807,7 @@ function toErrorMessage(error) {
|
|
|
801
807
|
|
|
802
808
|
// src/lib/claude/permissions/parser.ts
|
|
803
809
|
import fs2 from "fs/promises";
|
|
810
|
+
var PERMISSION_PATTERN = /^([^(]+)\((.+)\)$/;
|
|
804
811
|
async function parseSettingsFile(filePath) {
|
|
805
812
|
try {
|
|
806
813
|
const content = await fs2.readFile(filePath, "utf-8");
|
|
@@ -814,7 +821,7 @@ async function parseSettingsFile(filePath) {
|
|
|
814
821
|
}
|
|
815
822
|
}
|
|
816
823
|
function parsePermission(raw, category) {
|
|
817
|
-
const match =
|
|
824
|
+
const match = PERMISSION_PATTERN.exec(raw);
|
|
818
825
|
if (!match) {
|
|
819
826
|
throw new Error(`Malformed permission string: "${raw}"`);
|
|
820
827
|
}
|
|
@@ -1095,88 +1102,91 @@ async function createBackup(settingsPath) {
|
|
|
1095
1102
|
|
|
1096
1103
|
// src/lib/claude/settings/reporter.ts
|
|
1097
1104
|
function formatReport(result, previewOnly, globalSettingsPath, outputFile) {
|
|
1098
|
-
const lines = [];
|
|
1099
|
-
lines.push("Scanning for Claude Code settings files...");
|
|
1100
|
-
lines.push("");
|
|
1101
|
-
lines.push(`Found ${result.filesScanned} settings files`);
|
|
1102
|
-
lines.push(` Processed: ${result.filesProcessed}`);
|
|
1103
|
-
if (result.filesSkipped > 0) {
|
|
1104
|
-
lines.push(` Skipped: ${result.filesSkipped} (no permissions)`);
|
|
1105
|
-
}
|
|
1106
|
-
lines.push("");
|
|
1107
1105
|
const totalAdded = result.added.allow.length + result.added.deny.length + result.added.ask.length;
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
}
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1106
|
+
return [
|
|
1107
|
+
...headerLines(),
|
|
1108
|
+
...filesSummaryLines(result),
|
|
1109
|
+
...permissionLines(result, totalAdded),
|
|
1110
|
+
...subsumptionLines(result),
|
|
1111
|
+
...conflictLines(result),
|
|
1112
|
+
...backupLines(result),
|
|
1113
|
+
...summaryLines(result),
|
|
1114
|
+
...finalStatusLines(result, previewOnly, globalSettingsPath, outputFile)
|
|
1115
|
+
].join("\n");
|
|
1116
|
+
}
|
|
1117
|
+
function headerLines() {
|
|
1118
|
+
return ["Scanning for Claude Code settings files...", ""];
|
|
1119
|
+
}
|
|
1120
|
+
function filesSummaryLines(result) {
|
|
1121
|
+
const lines = [`Found ${result.filesScanned} settings files`, ` Processed: ${result.filesProcessed}`];
|
|
1122
|
+
if (result.filesSkipped > 0) lines.push(` Skipped: ${result.filesSkipped} (no permissions)`);
|
|
1123
|
+
return [...lines, ""];
|
|
1124
|
+
}
|
|
1125
|
+
function permissionLines(result, totalAdded) {
|
|
1126
|
+
if (totalAdded === 0) return ["No new permissions to add (all permissions already in global settings)", ""];
|
|
1127
|
+
return [
|
|
1128
|
+
`Permissions to add: ${totalAdded}`,
|
|
1129
|
+
...permissionCategoryLines("allow", result.added.allow),
|
|
1130
|
+
...permissionCategoryLines("deny", result.added.deny),
|
|
1131
|
+
...permissionCategoryLines("ask", result.added.ask),
|
|
1132
|
+
""
|
|
1133
|
+
];
|
|
1134
|
+
}
|
|
1135
|
+
function permissionCategoryLines(label, permissions) {
|
|
1136
|
+
if (permissions.length === 0) return [];
|
|
1137
|
+
return ["", ` ${label}:`, ...permissions.map((permission) => ` + ${permission}`)];
|
|
1138
|
+
}
|
|
1139
|
+
function subsumptionLines(result) {
|
|
1140
|
+
if (result.subsumed.length === 0) return [];
|
|
1141
|
+
return [
|
|
1142
|
+
`Subsumed permissions removed: ${result.subsumed.length}`,
|
|
1143
|
+
" (narrower permissions replaced by broader ones)",
|
|
1144
|
+
...result.subsumed.map((permission) => ` - ${permission}`),
|
|
1145
|
+
""
|
|
1146
|
+
];
|
|
1147
|
+
}
|
|
1148
|
+
function conflictLines(result) {
|
|
1149
|
+
if (result.conflictsResolved === 0) return [];
|
|
1150
|
+
return [`Conflicts resolved: ${result.conflictsResolved}`, " (permissions moved from allow to deny)", ""];
|
|
1151
|
+
}
|
|
1152
|
+
function backupLines(result) {
|
|
1153
|
+
return result.backupPath ? [`Backup created: ${result.backupPath}`, ""] : [];
|
|
1154
|
+
}
|
|
1155
|
+
function summaryLines(result) {
|
|
1156
|
+
return [
|
|
1157
|
+
"Summary:",
|
|
1158
|
+
` Files scanned: ${result.filesScanned}`,
|
|
1159
|
+
` Permissions added: ${result.added.allow.length} allow, ${result.added.deny.length} deny, ${result.added.ask.length} ask`,
|
|
1160
|
+
...optionalSummaryLines(result),
|
|
1161
|
+
""
|
|
1162
|
+
];
|
|
1163
|
+
}
|
|
1164
|
+
function optionalSummaryLines(result) {
|
|
1165
|
+
return [
|
|
1166
|
+
...result.subsumed.length > 0 ? [` Subsumed removed: ${result.subsumed.length}`] : [],
|
|
1167
|
+
...result.conflictsResolved > 0 ? [` Conflicts resolved: ${result.conflictsResolved}`] : []
|
|
1168
|
+
];
|
|
1169
|
+
}
|
|
1170
|
+
function finalStatusLines(result, previewOnly, globalSettingsPath, outputFile) {
|
|
1164
1171
|
if (previewOnly) {
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
lines.push("");
|
|
1173
|
-
lines.push("To apply to your global settings:");
|
|
1174
|
-
lines.push(` \u2022 Review the file, then copy to: ${globalSettingsPath || "~/.claude/settings.json"}`);
|
|
1175
|
-
lines.push(` \u2022 Or run: spx claude settings consolidate --write`);
|
|
1176
|
-
} else {
|
|
1177
|
-
lines.push(`\u2713 Global settings updated: ${globalSettingsPath || "~/.claude/settings.json"}`);
|
|
1172
|
+
return [
|
|
1173
|
+
"\u2139\uFE0F Preview mode: No changes written",
|
|
1174
|
+
"",
|
|
1175
|
+
"To apply changes:",
|
|
1176
|
+
" \u2022 Modify global settings: spx claude settings consolidate --write",
|
|
1177
|
+
" \u2022 Write to file: spx claude settings consolidate --output-file /path/to/file"
|
|
1178
|
+
];
|
|
1178
1179
|
}
|
|
1179
|
-
|
|
1180
|
+
if (outputFile) {
|
|
1181
|
+
return [
|
|
1182
|
+
`\u2713 Settings written to: ${result.outputPath || outputFile}`,
|
|
1183
|
+
"",
|
|
1184
|
+
"To apply to your global settings:",
|
|
1185
|
+
` \u2022 Review the file, then copy to: ${globalSettingsPath || "~/.claude/settings.json"}`,
|
|
1186
|
+
" \u2022 Or run: spx claude settings consolidate --write"
|
|
1187
|
+
];
|
|
1188
|
+
}
|
|
1189
|
+
return [`\u2713 Global settings updated: ${globalSettingsPath || "~/.claude/settings.json"}`];
|
|
1180
1190
|
}
|
|
1181
1191
|
|
|
1182
1192
|
// src/lib/claude/settings/writer.ts
|
|
@@ -1403,7 +1413,7 @@ var COMPACT_MARKER = {
|
|
|
1403
1413
|
FOUNDATION: "SPEC_TREE_FOUNDATION",
|
|
1404
1414
|
CONTEXT: "SPEC_TREE_CONTEXT",
|
|
1405
1415
|
TARGET_ATTRIBUTE: "target",
|
|
1406
|
-
ESCAPED_TARGET_QUOTE:
|
|
1416
|
+
ESCAPED_TARGET_QUOTE: String.raw`\"`,
|
|
1407
1417
|
UNESCAPED_TARGET_QUOTE: '"'
|
|
1408
1418
|
};
|
|
1409
1419
|
var COMPACT_ERROR = {
|
|
@@ -1510,8 +1520,8 @@ function readTargetPath(value, startIndex) {
|
|
|
1510
1520
|
return value.slice(startIndex, endIndex);
|
|
1511
1521
|
}
|
|
1512
1522
|
function isTargetPathCharacter(character) {
|
|
1513
|
-
const code = character.
|
|
1514
|
-
return code >= 48 && code <= 57 || code >= 65 && code <= 90 || code >= 97 && code <= 122 || character === "." || character === "_" || character === "-" || character === "/";
|
|
1523
|
+
const code = character.codePointAt(0);
|
|
1524
|
+
return code !== void 0 && (code >= 48 && code <= 57 || code >= 65 && code <= 90 || code >= 97 && code <= 122 || character === "." || character === "_" || character === "-" || character === "/");
|
|
1515
1525
|
}
|
|
1516
1526
|
|
|
1517
1527
|
// src/commands/compact/retrieve.ts
|
|
@@ -1598,6 +1608,18 @@ import { join as join5 } from "path";
|
|
|
1598
1608
|
import { parse as parseToml, stringify as stringifyToml } from "smol-toml";
|
|
1599
1609
|
import { parse as parseYaml, parseDocument as parseYamlDocument, stringify as stringifyYaml } from "yaml";
|
|
1600
1610
|
|
|
1611
|
+
// src/lib/error-message.ts
|
|
1612
|
+
function toMessage(error) {
|
|
1613
|
+
if (error instanceof Error) return error.message;
|
|
1614
|
+
if (typeof error === "string") return error;
|
|
1615
|
+
const fallback = `[${typeof error}]`;
|
|
1616
|
+
try {
|
|
1617
|
+
return JSON.stringify(error) ?? fallback;
|
|
1618
|
+
} catch {
|
|
1619
|
+
return fallback;
|
|
1620
|
+
}
|
|
1621
|
+
}
|
|
1622
|
+
|
|
1601
1623
|
// src/domains/agent-environment/config.ts
|
|
1602
1624
|
var AGENT_ENVIRONMENT_SECTION = "agentEnvironment";
|
|
1603
1625
|
var AGENT_RUNTIME = {
|
|
@@ -1794,15 +1816,15 @@ function validateRuntimes(raw) {
|
|
|
1794
1816
|
}
|
|
1795
1817
|
return { ok: true, value: runtimes };
|
|
1796
1818
|
}
|
|
1797
|
-
function validateRuntimeConfig(path7, raw,
|
|
1819
|
+
function validateRuntimeConfig(path7, raw, defaults5) {
|
|
1798
1820
|
if (!isRecord(raw)) return { ok: false, error: `${path7} must be an object` };
|
|
1799
1821
|
const unknown = rejectUnknownFields(path7, raw, AGENT_ENVIRONMENT_RUNTIME_CONFIG_ALLOWED_FIELDS);
|
|
1800
1822
|
if (!unknown.ok) return unknown;
|
|
1801
1823
|
const enabledRaw = raw[AGENT_ENVIRONMENT_CONFIG_FIELDS.ENABLED];
|
|
1802
|
-
if (enabledRaw === void 0) return { ok: true, value:
|
|
1824
|
+
if (enabledRaw === void 0) return { ok: true, value: defaults5 };
|
|
1803
1825
|
const enabled = validateBoolean(`${path7}.${AGENT_ENVIRONMENT_CONFIG_FIELDS.ENABLED}`, enabledRaw);
|
|
1804
1826
|
if (!enabled.ok) return enabled;
|
|
1805
|
-
return { ok: true, value: { ...
|
|
1827
|
+
return { ok: true, value: { ...defaults5, enabled: enabled.value } };
|
|
1806
1828
|
}
|
|
1807
1829
|
function validatePluginBootstrap(raw) {
|
|
1808
1830
|
const sectionPath = `${AGENT_ENVIRONMENT_SECTION}.${AGENT_ENVIRONMENT_CONFIG_FIELDS.PLUGIN_BOOTSTRAP}`;
|
|
@@ -1866,8 +1888,8 @@ function validatePluginBootstrap(raw) {
|
|
|
1866
1888
|
}
|
|
1867
1889
|
};
|
|
1868
1890
|
}
|
|
1869
|
-
function validateEntryArray(path7, raw, validateEntry2,
|
|
1870
|
-
if (raw === void 0) return { ok: true, value:
|
|
1891
|
+
function validateEntryArray(path7, raw, validateEntry2, defaults5) {
|
|
1892
|
+
if (raw === void 0) return { ok: true, value: defaults5 };
|
|
1871
1893
|
if (!Array.isArray(raw)) return { ok: false, error: `${path7} must be an array` };
|
|
1872
1894
|
const entries = [];
|
|
1873
1895
|
for (const [index, entry] of raw.entries()) {
|
|
@@ -2050,6 +2072,82 @@ var agentEnvironmentConfigDescriptor = {
|
|
|
2050
2072
|
validate
|
|
2051
2073
|
};
|
|
2052
2074
|
|
|
2075
|
+
// src/domains/diagnose/facts.ts
|
|
2076
|
+
function isRecord2(value) {
|
|
2077
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2078
|
+
}
|
|
2079
|
+
function isNonEmptyString(value) {
|
|
2080
|
+
return typeof value === "string" && value.length > 0;
|
|
2081
|
+
}
|
|
2082
|
+
function isNonEmptyStringArray(value) {
|
|
2083
|
+
return Array.isArray(value) && value.length > 0 && value.every(isNonEmptyString);
|
|
2084
|
+
}
|
|
2085
|
+
function validateMarketplaceIdentity(value, field) {
|
|
2086
|
+
if (!isRecord2(value) || !isNonEmptyString(value.name) || !isNonEmptyString(value.source)) {
|
|
2087
|
+
return { ok: false, error: `${field} must carry a non-empty \`name\` and \`source\`` };
|
|
2088
|
+
}
|
|
2089
|
+
return { ok: true, value: { name: value.name, source: value.source } };
|
|
2090
|
+
}
|
|
2091
|
+
|
|
2092
|
+
// src/domains/diagnose/config.ts
|
|
2093
|
+
var DIAGNOSE_SECTION = "diagnose";
|
|
2094
|
+
var DIAGNOSE_CONFIG_FIELDS = {
|
|
2095
|
+
SPX_FLOOR: "spxFloor",
|
|
2096
|
+
MARKETPLACE: "marketplace",
|
|
2097
|
+
EXPECTED_PLUGINS: "expectedPlugins",
|
|
2098
|
+
CHECKS: "checks"
|
|
2099
|
+
};
|
|
2100
|
+
var defaults = {};
|
|
2101
|
+
function validate2(value) {
|
|
2102
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
2103
|
+
return { ok: false, error: `${DIAGNOSE_SECTION} section must be an object` };
|
|
2104
|
+
}
|
|
2105
|
+
const candidate = value;
|
|
2106
|
+
const resolved = {};
|
|
2107
|
+
const spxFloor = candidate[DIAGNOSE_CONFIG_FIELDS.SPX_FLOOR];
|
|
2108
|
+
if (spxFloor !== void 0) {
|
|
2109
|
+
if (!isNonEmptyString(spxFloor)) {
|
|
2110
|
+
return { ok: false, error: `${DIAGNOSE_SECTION}.${DIAGNOSE_CONFIG_FIELDS.SPX_FLOOR} must be a non-empty string` };
|
|
2111
|
+
}
|
|
2112
|
+
resolved.spxFloor = spxFloor;
|
|
2113
|
+
}
|
|
2114
|
+
const marketplace = candidate[DIAGNOSE_CONFIG_FIELDS.MARKETPLACE];
|
|
2115
|
+
if (marketplace !== void 0) {
|
|
2116
|
+
const result = validateMarketplaceIdentity(
|
|
2117
|
+
marketplace,
|
|
2118
|
+
`${DIAGNOSE_SECTION}.${DIAGNOSE_CONFIG_FIELDS.MARKETPLACE}`
|
|
2119
|
+
);
|
|
2120
|
+
if (!result.ok) return result;
|
|
2121
|
+
resolved.marketplace = result.value;
|
|
2122
|
+
}
|
|
2123
|
+
const expectedPlugins = candidate[DIAGNOSE_CONFIG_FIELDS.EXPECTED_PLUGINS];
|
|
2124
|
+
if (expectedPlugins !== void 0) {
|
|
2125
|
+
if (!isNonEmptyStringArray(expectedPlugins)) {
|
|
2126
|
+
return {
|
|
2127
|
+
ok: false,
|
|
2128
|
+
error: `${DIAGNOSE_SECTION}.${DIAGNOSE_CONFIG_FIELDS.EXPECTED_PLUGINS} must be a non-empty array of non-empty strings`
|
|
2129
|
+
};
|
|
2130
|
+
}
|
|
2131
|
+
resolved.expectedPlugins = expectedPlugins;
|
|
2132
|
+
}
|
|
2133
|
+
const checks = candidate[DIAGNOSE_CONFIG_FIELDS.CHECKS];
|
|
2134
|
+
if (checks !== void 0) {
|
|
2135
|
+
if (!isNonEmptyStringArray(checks)) {
|
|
2136
|
+
return {
|
|
2137
|
+
ok: false,
|
|
2138
|
+
error: `${DIAGNOSE_SECTION}.${DIAGNOSE_CONFIG_FIELDS.CHECKS} must be a non-empty array of non-empty strings`
|
|
2139
|
+
};
|
|
2140
|
+
}
|
|
2141
|
+
resolved.checks = checks;
|
|
2142
|
+
}
|
|
2143
|
+
return { ok: true, value: resolved };
|
|
2144
|
+
}
|
|
2145
|
+
var diagnoseConfigDescriptor = {
|
|
2146
|
+
section: DIAGNOSE_SECTION,
|
|
2147
|
+
defaults,
|
|
2148
|
+
validate: validate2
|
|
2149
|
+
};
|
|
2150
|
+
|
|
2053
2151
|
// src/lib/spec-tree/config.ts
|
|
2054
2152
|
var SPEC_TREE_KIND_CATEGORY_VALUES = {
|
|
2055
2153
|
NODE: "node",
|
|
@@ -2229,7 +2327,7 @@ var SPEC_TREE_NODE_STATE = {
|
|
|
2229
2327
|
};
|
|
2230
2328
|
var SPEC_TREE_SECTION = SPEC_TREE_CONFIG.SECTION;
|
|
2231
2329
|
function isKind(value) {
|
|
2232
|
-
return Object.
|
|
2330
|
+
return Object.hasOwn(KIND_REGISTRY, value);
|
|
2233
2331
|
}
|
|
2234
2332
|
function buildDefaults() {
|
|
2235
2333
|
return { kinds: { ...KIND_REGISTRY } };
|
|
@@ -2238,7 +2336,7 @@ function buildConfigFromKindNames(kindNames) {
|
|
|
2238
2336
|
const entries = kindNames.map((kind) => [kind, KIND_REGISTRY[kind]]);
|
|
2239
2337
|
return { kinds: Object.fromEntries(entries) };
|
|
2240
2338
|
}
|
|
2241
|
-
function
|
|
2339
|
+
function validate3(value) {
|
|
2242
2340
|
if (typeof value !== "object" || value === null) {
|
|
2243
2341
|
return { ok: false, error: `${SPEC_TREE_SECTION} section must be an object` };
|
|
2244
2342
|
}
|
|
@@ -2337,7 +2435,7 @@ function validateKindDefinitionMap(kindEntries) {
|
|
|
2337
2435
|
var specTreeConfigDescriptor = {
|
|
2338
2436
|
section: SPEC_TREE_SECTION,
|
|
2339
2437
|
defaults: buildDefaults(),
|
|
2340
|
-
validate:
|
|
2438
|
+
validate: validate3
|
|
2341
2439
|
};
|
|
2342
2440
|
|
|
2343
2441
|
// src/lib/file-inclusion/adapters/eslint.ts
|
|
@@ -2513,11 +2611,11 @@ var DEFAULT_SCOPE_CONFIG = {
|
|
|
2513
2611
|
var DEFAULT_TOOLS_CONFIG = Object.fromEntries(
|
|
2514
2612
|
Object.entries(TOOL_DEFAULT_FLAGS).map(([name, flag]) => [name, { ignoreFlag: flag }])
|
|
2515
2613
|
);
|
|
2516
|
-
var
|
|
2614
|
+
var defaults2 = {
|
|
2517
2615
|
scope: DEFAULT_SCOPE_CONFIG,
|
|
2518
2616
|
tools: DEFAULT_TOOLS_CONFIG
|
|
2519
2617
|
};
|
|
2520
|
-
function
|
|
2618
|
+
function isRecord3(value) {
|
|
2521
2619
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2522
2620
|
}
|
|
2523
2621
|
function validateStringField(section, field, value) {
|
|
@@ -2540,7 +2638,7 @@ function rejectUnknownFields2(section, value, allowed) {
|
|
|
2540
2638
|
return { ok: true, value: void 0 };
|
|
2541
2639
|
}
|
|
2542
2640
|
function validateScope(raw) {
|
|
2543
|
-
if (!
|
|
2641
|
+
if (!isRecord3(raw)) {
|
|
2544
2642
|
return {
|
|
2545
2643
|
ok: false,
|
|
2546
2644
|
error: `${FILE_INCLUSION_SECTION}.${FILE_INCLUSION_CONFIG_FIELDS.SCOPE} must be an object`
|
|
@@ -2597,7 +2695,7 @@ function validateScope(raw) {
|
|
|
2597
2695
|
};
|
|
2598
2696
|
}
|
|
2599
2697
|
function validateTools(raw) {
|
|
2600
|
-
if (!
|
|
2698
|
+
if (!isRecord3(raw)) {
|
|
2601
2699
|
return {
|
|
2602
2700
|
ok: false,
|
|
2603
2701
|
error: `${FILE_INCLUSION_SECTION}.${FILE_INCLUSION_CONFIG_FIELDS.TOOLS} must be an object`
|
|
@@ -2612,7 +2710,7 @@ function validateTools(raw) {
|
|
|
2612
2710
|
error: `${FILE_INCLUSION_SECTION}.${FILE_INCLUSION_CONFIG_FIELDS.TOOLS}.${toolName} is not a recognized tool`
|
|
2613
2711
|
};
|
|
2614
2712
|
}
|
|
2615
|
-
if (!
|
|
2713
|
+
if (!isRecord3(adapterRaw)) {
|
|
2616
2714
|
return {
|
|
2617
2715
|
ok: false,
|
|
2618
2716
|
error: `${FILE_INCLUSION_SECTION}.${FILE_INCLUSION_CONFIG_FIELDS.TOOLS}.${toolName} must be an object`
|
|
@@ -2632,7 +2730,7 @@ function validateTools(raw) {
|
|
|
2632
2730
|
}
|
|
2633
2731
|
return { ok: true, value: tools };
|
|
2634
2732
|
}
|
|
2635
|
-
function
|
|
2733
|
+
function validate4(value) {
|
|
2636
2734
|
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
2637
2735
|
return { ok: false, error: `${FILE_INCLUSION_SECTION} section must be an object` };
|
|
2638
2736
|
}
|
|
@@ -2659,8 +2757,8 @@ function validate3(value) {
|
|
|
2659
2757
|
}
|
|
2660
2758
|
var fileInclusionConfigDescriptor = {
|
|
2661
2759
|
section: FILE_INCLUSION_SECTION,
|
|
2662
|
-
defaults,
|
|
2663
|
-
validate:
|
|
2760
|
+
defaults: defaults2,
|
|
2761
|
+
validate: validate4
|
|
2664
2762
|
};
|
|
2665
2763
|
|
|
2666
2764
|
// src/lib/precommit/config.ts
|
|
@@ -2669,7 +2767,7 @@ var PRECOMMIT_DEFAULTS = {
|
|
|
2669
2767
|
sourceDirs: ["src/"],
|
|
2670
2768
|
testPattern: ".test.ts"
|
|
2671
2769
|
};
|
|
2672
|
-
function
|
|
2770
|
+
function validate5(value) {
|
|
2673
2771
|
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
2674
2772
|
return { ok: false, error: `${PRECOMMIT_SECTION} section must be an object` };
|
|
2675
2773
|
}
|
|
@@ -2699,7 +2797,7 @@ function validate4(value) {
|
|
|
2699
2797
|
var precommitConfigDescriptor = {
|
|
2700
2798
|
section: PRECOMMIT_SECTION,
|
|
2701
2799
|
defaults: PRECOMMIT_DEFAULTS,
|
|
2702
|
-
validate:
|
|
2800
|
+
validate: validate5
|
|
2703
2801
|
};
|
|
2704
2802
|
|
|
2705
2803
|
// src/config/primitives/path-filter.ts
|
|
@@ -2736,7 +2834,11 @@ function validatePathFilterConfig(raw, path7) {
|
|
|
2736
2834
|
}
|
|
2737
2835
|
var PATH_SEGMENT_SEPARATOR = "/";
|
|
2738
2836
|
function normalizePathPrefix(value) {
|
|
2739
|
-
|
|
2837
|
+
let end = value.length;
|
|
2838
|
+
while (end > 0 && value[end - 1] === PATH_SEGMENT_SEPARATOR) {
|
|
2839
|
+
end -= 1;
|
|
2840
|
+
}
|
|
2841
|
+
return value.slice(0, end);
|
|
2740
2842
|
}
|
|
2741
2843
|
function pathMatchesPrefix(path7, prefix) {
|
|
2742
2844
|
const normalizedPath = normalizePathPrefix(path7);
|
|
@@ -2760,7 +2862,7 @@ var TESTING_CONFIG_FIELDS = {
|
|
|
2760
2862
|
PASSING_SCOPE: "passingScope"
|
|
2761
2863
|
};
|
|
2762
2864
|
var DEFAULT_PASSING_SCOPE = resolveDefaultPassingScope();
|
|
2763
|
-
var
|
|
2865
|
+
var defaults3 = {
|
|
2764
2866
|
passingScope: DEFAULT_PASSING_SCOPE
|
|
2765
2867
|
};
|
|
2766
2868
|
function resolveDefaultPassingScope() {
|
|
@@ -2773,14 +2875,14 @@ function resolveDefaultPassingScope() {
|
|
|
2773
2875
|
}
|
|
2774
2876
|
return result.value;
|
|
2775
2877
|
}
|
|
2776
|
-
function
|
|
2878
|
+
function validate6(value) {
|
|
2777
2879
|
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
2778
2880
|
return { ok: false, error: `${TESTING_SECTION} section must be an object` };
|
|
2779
2881
|
}
|
|
2780
2882
|
const candidate = value;
|
|
2781
2883
|
const passingScopeRaw = candidate[TESTING_CONFIG_FIELDS.PASSING_SCOPE];
|
|
2782
2884
|
if (passingScopeRaw === void 0) {
|
|
2783
|
-
return { ok: true, value:
|
|
2885
|
+
return { ok: true, value: defaults3 };
|
|
2784
2886
|
}
|
|
2785
2887
|
const passingScopeResult = validatePathFilterConfig(
|
|
2786
2888
|
passingScopeRaw,
|
|
@@ -2796,8 +2898,8 @@ function validate5(value) {
|
|
|
2796
2898
|
}
|
|
2797
2899
|
var testingConfigDescriptor = {
|
|
2798
2900
|
section: TESTING_SECTION,
|
|
2799
|
-
defaults:
|
|
2800
|
-
validate:
|
|
2901
|
+
defaults: defaults3,
|
|
2902
|
+
validate: validate6
|
|
2801
2903
|
};
|
|
2802
2904
|
|
|
2803
2905
|
// src/validation/literal/config.ts
|
|
@@ -2806,6 +2908,10 @@ var DEFAULT_MIN_STRING_LENGTH = 4;
|
|
|
2806
2908
|
var DEFAULT_MIN_NUMBER_DIGITS = 4;
|
|
2807
2909
|
var LEGACY_LITERAL_ALLOWLIST_FIELD = "allowlist";
|
|
2808
2910
|
var LEGACY_LITERAL_ALLOWLIST_ERROR = "validation.literal.values.allowlist is no longer valid; move its contents up one level to validation.literal.values.{presets,include,exclude}";
|
|
2911
|
+
var LITERAL_STRING_LIST_FIELDS = {
|
|
2912
|
+
INCLUDE: "include",
|
|
2913
|
+
EXCLUDE: "exclude"
|
|
2914
|
+
};
|
|
2809
2915
|
var PRESET_NAMES = {
|
|
2810
2916
|
WEB: "web"
|
|
2811
2917
|
};
|
|
@@ -2854,7 +2960,10 @@ var LITERAL_DEFAULTS = {
|
|
|
2854
2960
|
minStringLength: DEFAULT_MIN_STRING_LENGTH,
|
|
2855
2961
|
minNumberDigits: DEFAULT_MIN_NUMBER_DIGITS
|
|
2856
2962
|
};
|
|
2857
|
-
function
|
|
2963
|
+
function isNonNegativeInteger(value) {
|
|
2964
|
+
return typeof value === "number" && Number.isInteger(value) && value >= 0;
|
|
2965
|
+
}
|
|
2966
|
+
function validate7(value) {
|
|
2858
2967
|
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
2859
2968
|
return { ok: false, error: `${LITERAL_SECTION} section must be an object` };
|
|
2860
2969
|
}
|
|
@@ -2862,34 +2971,21 @@ function validate6(value) {
|
|
|
2862
2971
|
if (candidate[LEGACY_LITERAL_ALLOWLIST_FIELD] !== void 0) {
|
|
2863
2972
|
return { ok: false, error: LEGACY_LITERAL_ALLOWLIST_ERROR };
|
|
2864
2973
|
}
|
|
2865
|
-
const presets = candidate
|
|
2866
|
-
if (presets
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
if (!PRESET_REGISTRY.has(id)) {
|
|
2872
|
-
return { ok: false, error: `${LITERAL_SECTION}.presets: unrecognized preset "${id}"` };
|
|
2873
|
-
}
|
|
2874
|
-
}
|
|
2875
|
-
}
|
|
2876
|
-
const include = candidate["include"];
|
|
2877
|
-
if (include !== void 0 && (!Array.isArray(include) || !include.every((x) => typeof x === "string"))) {
|
|
2878
|
-
return { ok: false, error: `${LITERAL_SECTION}.include must be an array of strings` };
|
|
2879
|
-
}
|
|
2880
|
-
const exclude = candidate["exclude"];
|
|
2881
|
-
if (exclude !== void 0 && (!Array.isArray(exclude) || !exclude.every((x) => typeof x === "string"))) {
|
|
2882
|
-
return { ok: false, error: `${LITERAL_SECTION}.exclude must be an array of strings` };
|
|
2883
|
-
}
|
|
2974
|
+
const presets = readPresetList(candidate);
|
|
2975
|
+
if (!presets.ok) return presets;
|
|
2976
|
+
const include = readStringList(candidate, LITERAL_STRING_LIST_FIELDS.INCLUDE);
|
|
2977
|
+
if (!include.ok) return include;
|
|
2978
|
+
const exclude = readStringList(candidate, LITERAL_STRING_LIST_FIELDS.EXCLUDE);
|
|
2979
|
+
if (!exclude.ok) return exclude;
|
|
2884
2980
|
const minStringLength = candidate["minStringLength"] ?? LITERAL_DEFAULTS.minStringLength;
|
|
2885
|
-
if (
|
|
2981
|
+
if (!isNonNegativeInteger(minStringLength)) {
|
|
2886
2982
|
return {
|
|
2887
2983
|
ok: false,
|
|
2888
2984
|
error: `${LITERAL_SECTION}.minStringLength must be a non-negative integer`
|
|
2889
2985
|
};
|
|
2890
2986
|
}
|
|
2891
2987
|
const minNumberDigits = candidate["minNumberDigits"] ?? LITERAL_DEFAULTS.minNumberDigits;
|
|
2892
|
-
if (
|
|
2988
|
+
if (!isNonNegativeInteger(minNumberDigits)) {
|
|
2893
2989
|
return {
|
|
2894
2990
|
ok: false,
|
|
2895
2991
|
error: `${LITERAL_SECTION}.minNumberDigits must be a non-negative integer`
|
|
@@ -2898,18 +2994,40 @@ function validate6(value) {
|
|
|
2898
2994
|
return {
|
|
2899
2995
|
ok: true,
|
|
2900
2996
|
value: {
|
|
2901
|
-
presets,
|
|
2902
|
-
include,
|
|
2903
|
-
exclude,
|
|
2997
|
+
presets: presets.value,
|
|
2998
|
+
include: include.value,
|
|
2999
|
+
exclude: exclude.value,
|
|
2904
3000
|
minStringLength,
|
|
2905
3001
|
minNumberDigits
|
|
2906
3002
|
}
|
|
2907
3003
|
};
|
|
2908
3004
|
}
|
|
3005
|
+
function readPresetList(candidate) {
|
|
3006
|
+
const presets = candidate["presets"];
|
|
3007
|
+
if (presets === void 0) return { ok: true, value: void 0 };
|
|
3008
|
+
if (!isStringArray(presets)) {
|
|
3009
|
+
return { ok: false, error: `${LITERAL_SECTION}.presets must be an array of strings` };
|
|
3010
|
+
}
|
|
3011
|
+
for (const id of presets) {
|
|
3012
|
+
if (!PRESET_REGISTRY.has(id)) {
|
|
3013
|
+
return { ok: false, error: `${LITERAL_SECTION}.presets: unrecognized preset "${id}"` };
|
|
3014
|
+
}
|
|
3015
|
+
}
|
|
3016
|
+
return { ok: true, value: presets };
|
|
3017
|
+
}
|
|
3018
|
+
function readStringList(candidate, field) {
|
|
3019
|
+
const value = candidate[field];
|
|
3020
|
+
if (value === void 0) return { ok: true, value: void 0 };
|
|
3021
|
+
if (!isStringArray(value)) return { ok: false, error: `${LITERAL_SECTION}.${field} must be an array of strings` };
|
|
3022
|
+
return { ok: true, value };
|
|
3023
|
+
}
|
|
3024
|
+
function isStringArray(value) {
|
|
3025
|
+
return Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
3026
|
+
}
|
|
2909
3027
|
var literalConfigDescriptor = {
|
|
2910
3028
|
section: LITERAL_SECTION,
|
|
2911
3029
|
defaults: LITERAL_DEFAULTS,
|
|
2912
|
-
validate:
|
|
3030
|
+
validate: validate7
|
|
2913
3031
|
};
|
|
2914
3032
|
|
|
2915
3033
|
// src/validation/config/descriptor.ts
|
|
@@ -2925,9 +3043,10 @@ var VALIDATION_PATH_TOOL_SUBSECTIONS = {
|
|
|
2925
3043
|
CIRCULAR: "circular",
|
|
2926
3044
|
KNIP: "knip",
|
|
2927
3045
|
MARKDOWN: "markdown",
|
|
2928
|
-
LITERAL: "literal"
|
|
3046
|
+
LITERAL: "literal",
|
|
3047
|
+
FORMATTING: "formatting"
|
|
2929
3048
|
};
|
|
2930
|
-
var
|
|
3049
|
+
var defaults4 = {
|
|
2931
3050
|
paths: {},
|
|
2932
3051
|
literal: {
|
|
2933
3052
|
enabled: true,
|
|
@@ -2967,7 +3086,7 @@ function validateLiteral(raw) {
|
|
|
2967
3086
|
};
|
|
2968
3087
|
}
|
|
2969
3088
|
const candidate = raw;
|
|
2970
|
-
const enabledRaw = candidate[VALIDATION_ENABLED_FIELD] ??
|
|
3089
|
+
const enabledRaw = candidate[VALIDATION_ENABLED_FIELD] ?? defaults4.literal.enabled;
|
|
2971
3090
|
if (typeof enabledRaw !== "boolean") {
|
|
2972
3091
|
return {
|
|
2973
3092
|
ok: false,
|
|
@@ -2992,7 +3111,7 @@ function validateKnip(raw) {
|
|
|
2992
3111
|
};
|
|
2993
3112
|
}
|
|
2994
3113
|
const candidate = raw;
|
|
2995
|
-
const enabledRaw = candidate[VALIDATION_ENABLED_FIELD] ??
|
|
3114
|
+
const enabledRaw = candidate[VALIDATION_ENABLED_FIELD] ?? defaults4.knip.enabled;
|
|
2996
3115
|
if (typeof enabledRaw !== "boolean") {
|
|
2997
3116
|
return {
|
|
2998
3117
|
ok: false,
|
|
@@ -3001,7 +3120,7 @@ function validateKnip(raw) {
|
|
|
3001
3120
|
}
|
|
3002
3121
|
return { ok: true, value: { enabled: enabledRaw } };
|
|
3003
3122
|
}
|
|
3004
|
-
function
|
|
3123
|
+
function validate8(value) {
|
|
3005
3124
|
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
3006
3125
|
return { ok: false, error: `${VALIDATION_SECTION} section must be an object` };
|
|
3007
3126
|
}
|
|
@@ -3019,8 +3138,8 @@ function validate7(value) {
|
|
|
3019
3138
|
}
|
|
3020
3139
|
var validationConfigDescriptor = {
|
|
3021
3140
|
section: VALIDATION_SECTION,
|
|
3022
|
-
defaults:
|
|
3023
|
-
validate:
|
|
3141
|
+
defaults: defaults4,
|
|
3142
|
+
validate: validate8
|
|
3024
3143
|
};
|
|
3025
3144
|
|
|
3026
3145
|
// src/config/registry.ts
|
|
@@ -3030,7 +3149,8 @@ var productionRegistry = [
|
|
|
3030
3149
|
testingConfigDescriptor,
|
|
3031
3150
|
fileInclusionConfigDescriptor,
|
|
3032
3151
|
precommitConfigDescriptor,
|
|
3033
|
-
agentEnvironmentConfigDescriptor
|
|
3152
|
+
agentEnvironmentConfigDescriptor,
|
|
3153
|
+
diagnoseConfigDescriptor
|
|
3034
3154
|
];
|
|
3035
3155
|
|
|
3036
3156
|
// src/config/descriptor-digest.ts
|
|
@@ -3328,7 +3448,7 @@ function setNested(target, path7, value) {
|
|
|
3328
3448
|
cursor = fresh;
|
|
3329
3449
|
}
|
|
3330
3450
|
}
|
|
3331
|
-
cursor[path7
|
|
3451
|
+
cursor[path7.at(-1)] = value;
|
|
3332
3452
|
}
|
|
3333
3453
|
function validateParsedSections(filename, parsed) {
|
|
3334
3454
|
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
@@ -3339,9 +3459,6 @@ function validateParsedSections(filename, parsed) {
|
|
|
3339
3459
|
function isFileNotFound(error) {
|
|
3340
3460
|
return error instanceof Error && "code" in error && error.code === "ENOENT";
|
|
3341
3461
|
}
|
|
3342
|
-
function toMessage(error) {
|
|
3343
|
-
return error instanceof Error ? error.message : String(error);
|
|
3344
|
-
}
|
|
3345
3462
|
|
|
3346
3463
|
// src/commands/config/defaults.ts
|
|
3347
3464
|
function defaultsCommand(options, deps) {
|
|
@@ -3576,12 +3693,6 @@ var CHECK_NAME = {
|
|
|
3576
3693
|
SESSION_STORE: "session-store",
|
|
3577
3694
|
MARKETPLACE_INSTALL: "marketplace-install"
|
|
3578
3695
|
};
|
|
3579
|
-
function isRecord3(value) {
|
|
3580
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3581
|
-
}
|
|
3582
|
-
function isNonEmptyString(value) {
|
|
3583
|
-
return typeof value === "string" && value.length > 0;
|
|
3584
|
-
}
|
|
3585
3696
|
function validateChecks(raw, available) {
|
|
3586
3697
|
if (!Array.isArray(raw) || raw.length === 0) {
|
|
3587
3698
|
return { ok: false, error: "manifest `checks` must be a non-empty array of check names" };
|
|
@@ -3595,12 +3706,6 @@ function validateChecks(raw, available) {
|
|
|
3595
3706
|
}
|
|
3596
3707
|
return { ok: true, value: raw };
|
|
3597
3708
|
}
|
|
3598
|
-
function validateMarketplace2(raw) {
|
|
3599
|
-
if (!isRecord3(raw) || !isNonEmptyString(raw.name) || !isNonEmptyString(raw.source)) {
|
|
3600
|
-
return { ok: false, error: "manifest `marketplace` must carry a non-empty `name` and `source`" };
|
|
3601
|
-
}
|
|
3602
|
-
return { ok: true, value: { name: raw.name, source: raw.source } };
|
|
3603
|
-
}
|
|
3604
3709
|
function parseManifest(rawJson, availableChecks) {
|
|
3605
3710
|
let parsed;
|
|
3606
3711
|
try {
|
|
@@ -3608,7 +3713,7 @@ function parseManifest(rawJson, availableChecks) {
|
|
|
3608
3713
|
} catch (error) {
|
|
3609
3714
|
return { ok: false, error: `manifest is not valid JSON: ${error.message}` };
|
|
3610
3715
|
}
|
|
3611
|
-
if (!
|
|
3716
|
+
if (!isRecord2(parsed)) {
|
|
3612
3717
|
return { ok: false, error: "manifest must be a JSON object" };
|
|
3613
3718
|
}
|
|
3614
3719
|
const checks = validateChecks(parsed.checks, new Set(availableChecks));
|
|
@@ -3621,9 +3726,9 @@ function parseManifest(rawJson, availableChecks) {
|
|
|
3621
3726
|
manifest.spxFloor = parsed.spx_floor;
|
|
3622
3727
|
}
|
|
3623
3728
|
if (checks.value.includes(CHECK_NAME.MARKETPLACE_INSTALL)) {
|
|
3624
|
-
const marketplace =
|
|
3729
|
+
const marketplace = validateMarketplaceIdentity(parsed.marketplace, "manifest `marketplace`");
|
|
3625
3730
|
if (!marketplace.ok) return marketplace;
|
|
3626
|
-
if (!
|
|
3731
|
+
if (!isNonEmptyStringArray(parsed.expected_plugins)) {
|
|
3627
3732
|
return { ok: false, error: "manifest selects `marketplace-install` but carries no `expected_plugins`" };
|
|
3628
3733
|
}
|
|
3629
3734
|
manifest.marketplace = marketplace.value;
|
|
@@ -3738,21 +3843,70 @@ function renderReport(report2, format2, options) {
|
|
|
3738
3843
|
return format2 === DIAGNOSE_FORMAT.JSON ? renderReportJson(report2) : renderReportText(report2, options);
|
|
3739
3844
|
}
|
|
3740
3845
|
|
|
3846
|
+
// src/domains/diagnose/resolve.ts
|
|
3847
|
+
var CHECK_NAMES = new Set(Object.values(CHECK_NAME));
|
|
3848
|
+
function isCheckName(value) {
|
|
3849
|
+
return CHECK_NAMES.has(value);
|
|
3850
|
+
}
|
|
3851
|
+
function resolveDiagnoseFacts(options) {
|
|
3852
|
+
if (options.manifest !== void 0) {
|
|
3853
|
+
return { ok: true, value: options.manifest };
|
|
3854
|
+
}
|
|
3855
|
+
const { config, availableChecks } = options;
|
|
3856
|
+
const configuredChecks = config.checks;
|
|
3857
|
+
let checks;
|
|
3858
|
+
if (configuredChecks === void 0) {
|
|
3859
|
+
checks = availableChecks;
|
|
3860
|
+
} else {
|
|
3861
|
+
const available = new Set(availableChecks);
|
|
3862
|
+
const unavailable = configuredChecks.filter((name) => !isCheckName(name) || !available.has(name));
|
|
3863
|
+
if (unavailable.length > 0) {
|
|
3864
|
+
return {
|
|
3865
|
+
ok: false,
|
|
3866
|
+
error: `diagnose config \`checks\` names checks not available in this build: ${unavailable.join(", ")}`
|
|
3867
|
+
};
|
|
3868
|
+
}
|
|
3869
|
+
checks = configuredChecks.filter(isCheckName);
|
|
3870
|
+
}
|
|
3871
|
+
return {
|
|
3872
|
+
ok: true,
|
|
3873
|
+
value: {
|
|
3874
|
+
checks,
|
|
3875
|
+
spxFloor: config.spxFloor,
|
|
3876
|
+
marketplace: config.marketplace,
|
|
3877
|
+
expectedPlugins: config.expectedPlugins
|
|
3878
|
+
}
|
|
3879
|
+
};
|
|
3880
|
+
}
|
|
3881
|
+
|
|
3741
3882
|
// src/commands/diagnose/index.ts
|
|
3742
|
-
async function
|
|
3883
|
+
async function resolveDiagnoseConfig(productDir) {
|
|
3884
|
+
const loaded = await resolveConfig(productDir, [diagnoseConfigDescriptor]);
|
|
3885
|
+
if (!loaded.ok) return loaded;
|
|
3886
|
+
return { ok: true, value: loaded.value[diagnoseConfigDescriptor.section] };
|
|
3887
|
+
}
|
|
3888
|
+
async function readManifest(fs8, manifestPath, availableChecks) {
|
|
3743
3889
|
let raw;
|
|
3744
3890
|
try {
|
|
3745
|
-
raw = await
|
|
3891
|
+
raw = await fs8.readFile(manifestPath);
|
|
3746
3892
|
} catch (error) {
|
|
3747
|
-
return {
|
|
3748
|
-
ok: false,
|
|
3749
|
-
error: `cannot read diagnose manifest at ${options.manifestPath}: ${error.message}`
|
|
3750
|
-
};
|
|
3893
|
+
return { ok: false, error: `cannot read diagnose manifest at ${manifestPath}: ${error.message}` };
|
|
3751
3894
|
}
|
|
3895
|
+
return parseManifest(raw, availableChecks);
|
|
3896
|
+
}
|
|
3897
|
+
async function diagnoseCommand(options) {
|
|
3752
3898
|
const availableChecks = Object.keys(options.registry);
|
|
3753
|
-
const manifest =
|
|
3754
|
-
if (!manifest.ok) return manifest;
|
|
3755
|
-
const
|
|
3899
|
+
const manifest = options.manifestPath === void 0 ? void 0 : await readManifest(options.fs, options.manifestPath, availableChecks);
|
|
3900
|
+
if (manifest !== void 0 && !manifest.ok) return manifest;
|
|
3901
|
+
const config = manifest === void 0 ? await resolveDiagnoseConfig(options.productDir) : { ok: true, value: {} };
|
|
3902
|
+
if (!config.ok) return config;
|
|
3903
|
+
const resolved = resolveDiagnoseFacts({
|
|
3904
|
+
manifest: manifest?.value,
|
|
3905
|
+
config: config.value,
|
|
3906
|
+
availableChecks
|
|
3907
|
+
});
|
|
3908
|
+
if (!resolved.ok) return resolved;
|
|
3909
|
+
const report2 = await runDiagnose(resolved.value, options.registry);
|
|
3756
3910
|
if (!report2.ok) return report2;
|
|
3757
3911
|
return {
|
|
3758
3912
|
ok: true,
|
|
@@ -3857,9 +4011,8 @@ function nonEmptyString(value) {
|
|
|
3857
4011
|
// src/domains/worktree/occupancy-store.ts
|
|
3858
4012
|
import { join as join6 } from "path";
|
|
3859
4013
|
var OCCUPANCY_STATUS = {
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
STALE: "stale"
|
|
4014
|
+
FREE: "free",
|
|
4015
|
+
RUNNING: "running"
|
|
3863
4016
|
};
|
|
3864
4017
|
var OCCUPANCY_CLAIM = {
|
|
3865
4018
|
FILE_EXTENSION: ".claim",
|
|
@@ -3889,13 +4042,13 @@ function claimTempFilePath(claimPath, writeToken) {
|
|
|
3889
4042
|
return { ok: true, value: `${claimPath}.${validated.value}${OCCUPANCY_CLAIM.TEMP_EXTENSION}` };
|
|
3890
4043
|
}
|
|
3891
4044
|
function classifyOccupancy(claim, probe) {
|
|
3892
|
-
if (claim === void 0) return OCCUPANCY_STATUS.
|
|
3893
|
-
if (claim.host !== probe.currentHost()) return OCCUPANCY_STATUS.
|
|
3894
|
-
if (!probe.isAlive(claim.pid)) return OCCUPANCY_STATUS.
|
|
4045
|
+
if (claim === void 0) return OCCUPANCY_STATUS.FREE;
|
|
4046
|
+
if (claim.host !== probe.currentHost()) return OCCUPANCY_STATUS.FREE;
|
|
4047
|
+
if (!probe.isAlive(claim.pid)) return OCCUPANCY_STATUS.FREE;
|
|
4048
|
+
if (claim.startedAt === unreadableStartedAt(claim.pid)) return OCCUPANCY_STATUS.RUNNING;
|
|
3895
4049
|
const liveStartTime = probe.startTimeOf(claim.pid);
|
|
3896
|
-
if (
|
|
3897
|
-
|
|
3898
|
-
return OCCUPANCY_STATUS.OCCUPIED;
|
|
4050
|
+
if (liveStartTime !== void 0 && liveStartTime !== claim.startedAt) return OCCUPANCY_STATUS.FREE;
|
|
4051
|
+
return OCCUPANCY_STATUS.RUNNING;
|
|
3899
4052
|
}
|
|
3900
4053
|
function unreadableStartedAt(pid) {
|
|
3901
4054
|
return `${OCCUPANCY_CLAIM.UNREADABLE_STARTED_AT_PREFIX}${pid}`;
|
|
@@ -3937,11 +4090,6 @@ async function removeClaim(worktreesDir, name, options) {
|
|
|
3937
4090
|
return { ok: false, error: formatOccupancyError(OCCUPANCY_ERROR.CLAIM_REMOVE_FAILED, toErrorMessage2(error)) };
|
|
3938
4091
|
}
|
|
3939
4092
|
}
|
|
3940
|
-
async function readOccupancy(worktreesDir, name, probe, options) {
|
|
3941
|
-
const claimResult = await readClaim(worktreesDir, name, options);
|
|
3942
|
-
if (!claimResult.ok) return claimResult;
|
|
3943
|
-
return { ok: true, value: classifyOccupancy(claimResult.value, probe) };
|
|
3944
|
-
}
|
|
3945
4093
|
function serializeClaim(record6) {
|
|
3946
4094
|
return JSON.stringify({
|
|
3947
4095
|
sessionId: record6.sessionId,
|
|
@@ -3969,7 +4117,10 @@ function formatOccupancyError(code, detail) {
|
|
|
3969
4117
|
return `${code}${ERROR_DETAIL_SEPARATOR2}${detail}`;
|
|
3970
4118
|
}
|
|
3971
4119
|
function toErrorMessage2(error) {
|
|
3972
|
-
|
|
4120
|
+
if (error instanceof Error) return error.message;
|
|
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";
|
|
3973
4124
|
}
|
|
3974
4125
|
|
|
3975
4126
|
// src/domains/worktree/worktree-name.ts
|
|
@@ -4119,27 +4270,25 @@ var defaultWorktreePoolProbe = {
|
|
|
4119
4270
|
errored: true,
|
|
4120
4271
|
bareRepository: false,
|
|
4121
4272
|
linkedWorktrees: false,
|
|
4122
|
-
|
|
4273
|
+
running: 0,
|
|
4274
|
+
free: 0
|
|
4123
4275
|
};
|
|
4276
|
+
const root = await detectGitCommonDirProductRoot();
|
|
4124
4277
|
const facts = await gatherGitFacts();
|
|
4125
|
-
if (!facts?.worktreeListRead) return errored;
|
|
4278
|
+
if (!root.isGitRepo || !facts?.worktreeListRead) return errored;
|
|
4126
4279
|
const bareRepository = facts.commonDirIsBare;
|
|
4127
4280
|
const paths = facts.worktreeRoots;
|
|
4128
4281
|
const linkedWorktrees = !bareRepository && paths.length > 1;
|
|
4129
|
-
const
|
|
4130
|
-
|
|
4131
|
-
let
|
|
4282
|
+
const worktreesDir = worktreesScopeDir(root.productDir);
|
|
4283
|
+
let running = 0;
|
|
4284
|
+
let free = 0;
|
|
4132
4285
|
for (const path7 of paths) {
|
|
4133
|
-
const
|
|
4134
|
-
if (!
|
|
4135
|
-
|
|
4136
|
-
|
|
4137
|
-
if (occupancy === OCCUPANCY_STATUS.STALE) {
|
|
4138
|
-
staleClaim = true;
|
|
4139
|
-
break;
|
|
4140
|
-
}
|
|
4286
|
+
const claim = await readClaim(worktreesDir, worktreeClaimName(path7), { fs: defaultOccupancyFileSystem });
|
|
4287
|
+
if (!claim.ok) return errored;
|
|
4288
|
+
if (classifyOccupancy(claim.value, defaultProcessTable) === OCCUPANCY_STATUS.RUNNING) running += 1;
|
|
4289
|
+
else free += 1;
|
|
4141
4290
|
}
|
|
4142
|
-
return { errored: false, bareRepository, linkedWorktrees,
|
|
4291
|
+
return { errored: false, bareRepository, linkedWorktrees, running, free };
|
|
4143
4292
|
}
|
|
4144
4293
|
};
|
|
4145
4294
|
var defaultSessionEnvironmentProbe = {
|
|
@@ -4148,22 +4297,21 @@ var defaultSessionEnvironmentProbe = {
|
|
|
4148
4297
|
const sessionIdentity = resolveAgentSessionId(process.env) !== void 0;
|
|
4149
4298
|
const spx = resolveSpx();
|
|
4150
4299
|
if (spx === null) {
|
|
4151
|
-
return { errored: true, hookPresent, sessionIdentity, worktreeClaimed: false
|
|
4300
|
+
return { errored: true, hookPresent, sessionIdentity, worktreeClaimed: false };
|
|
4152
4301
|
}
|
|
4153
4302
|
const status = await runCapture(spx, ["worktree", "status", "--format", "json"]);
|
|
4154
4303
|
if (!status.ok) {
|
|
4155
|
-
return { errored: true, hookPresent, sessionIdentity, worktreeClaimed: false
|
|
4304
|
+
return { errored: true, hookPresent, sessionIdentity, worktreeClaimed: false };
|
|
4156
4305
|
}
|
|
4157
4306
|
const occupancy = worktreeStatusOf(status.stdout);
|
|
4158
4307
|
if (occupancy === null) {
|
|
4159
|
-
return { errored: true, hookPresent, sessionIdentity, worktreeClaimed: false
|
|
4308
|
+
return { errored: true, hookPresent, sessionIdentity, worktreeClaimed: false };
|
|
4160
4309
|
}
|
|
4161
4310
|
return {
|
|
4162
4311
|
errored: false,
|
|
4163
4312
|
hookPresent,
|
|
4164
4313
|
sessionIdentity,
|
|
4165
|
-
worktreeClaimed: occupancy === OCCUPANCY_STATUS.
|
|
4166
|
-
roundTripStale: occupancy === OCCUPANCY_STATUS.STALE
|
|
4314
|
+
worktreeClaimed: occupancy === OCCUPANCY_STATUS.RUNNING
|
|
4167
4315
|
};
|
|
4168
4316
|
}
|
|
4169
4317
|
};
|
|
@@ -4177,7 +4325,7 @@ async function claimedSessionIds() {
|
|
|
4177
4325
|
for (const path7 of facts.worktreeRoots) {
|
|
4178
4326
|
const claim = await readClaim(worktreesDir, worktreeClaimName(path7), { fs: defaultOccupancyFileSystem });
|
|
4179
4327
|
if (!claim.ok || claim.value === void 0) continue;
|
|
4180
|
-
if (classifyOccupancy(claim.value, defaultProcessTable) === OCCUPANCY_STATUS.
|
|
4328
|
+
if (classifyOccupancy(claim.value, defaultProcessTable) === OCCUPANCY_STATUS.RUNNING) {
|
|
4181
4329
|
sessionIds.add(normalizeAgentSessionToken(claim.value.sessionId));
|
|
4182
4330
|
}
|
|
4183
4331
|
}
|
|
@@ -4392,8 +4540,7 @@ function record2(verdict, bucket, reading) {
|
|
|
4392
4540
|
readings: {
|
|
4393
4541
|
hook: String(reading.hookPresent),
|
|
4394
4542
|
identity: String(reading.sessionIdentity),
|
|
4395
|
-
claimed: String(reading.worktreeClaimed)
|
|
4396
|
-
stale: String(reading.roundTripStale)
|
|
4543
|
+
claimed: String(reading.worktreeClaimed)
|
|
4397
4544
|
},
|
|
4398
4545
|
remediation: REMEDIATION2[verdict]
|
|
4399
4546
|
};
|
|
@@ -4402,7 +4549,7 @@ function classifySessionEnvironment(reading) {
|
|
|
4402
4549
|
if (!reading.hookPresent) {
|
|
4403
4550
|
return record2(SESSION_ENVIRONMENT_VERDICT.NOT_APPLICABLE, VERDICT_BUCKET.NOT_APPLICABLE, reading);
|
|
4404
4551
|
}
|
|
4405
|
-
if (reading.errored
|
|
4552
|
+
if (reading.errored) {
|
|
4406
4553
|
return record2(SESSION_ENVIRONMENT_VERDICT.UNKNOWN, VERDICT_BUCKET.UNKNOWN, reading);
|
|
4407
4554
|
}
|
|
4408
4555
|
if (reading.sessionIdentity && reading.worktreeClaimed) {
|
|
@@ -4428,7 +4575,7 @@ var SESSION_STORE_VERDICT = {
|
|
|
4428
4575
|
};
|
|
4429
4576
|
var REMEDIATION3 = {
|
|
4430
4577
|
[SESSION_STORE_VERDICT.CONSISTENT]: "Session store is consistent; no action needed.",
|
|
4431
|
-
[SESSION_STORE_VERDICT.ORPHANED_CLAIMS]: "Release or reclaim doing sessions whose backing worktree
|
|
4578
|
+
[SESSION_STORE_VERDICT.ORPHANED_CLAIMS]: "Release or reclaim doing sessions whose backing worktree reads free or is absent (spx session release).",
|
|
4432
4579
|
[SESSION_STORE_VERDICT.UNKNOWN]: "Re-run diagnose; if it persists, inspect spx session list and spx worktree status."
|
|
4433
4580
|
};
|
|
4434
4581
|
function record3(verdict, bucket, reading) {
|
|
@@ -4458,6 +4605,7 @@ function sessionStoreRunner(probe) {
|
|
|
4458
4605
|
// src/domains/diagnose/checks/spx-reachability.ts
|
|
4459
4606
|
var SPX_REACHABILITY_VERDICT = {
|
|
4460
4607
|
REACHABLE: "reachable",
|
|
4608
|
+
PRESENT: "present",
|
|
4461
4609
|
BELOW_FLOOR: "below-floor",
|
|
4462
4610
|
UNREACHABLE: "unreachable",
|
|
4463
4611
|
UNKNOWN: "unknown"
|
|
@@ -4508,9 +4656,10 @@ function meetsFloor(version2, floor) {
|
|
|
4508
4656
|
}
|
|
4509
4657
|
var REMEDIATION4 = {
|
|
4510
4658
|
[SPX_REACHABILITY_VERDICT.REACHABLE]: "spx is on PATH at or above the required floor; no action needed.",
|
|
4659
|
+
[SPX_REACHABILITY_VERDICT.PRESENT]: "spx is on PATH; no version floor is configured to compare against, so only presence is reported.",
|
|
4511
4660
|
[SPX_REACHABILITY_VERDICT.BELOW_FLOOR]: "Update spx to at least the required floor (pnpm add -g @outcomeeng/spx).",
|
|
4512
4661
|
[SPX_REACHABILITY_VERDICT.UNREACHABLE]: "Install spx and ensure it resolves on PATH (pnpm add -g @outcomeeng/spx).",
|
|
4513
|
-
[SPX_REACHABILITY_VERDICT.UNKNOWN]: "Re-run diagnose; if it persists, verify the manifest
|
|
4662
|
+
[SPX_REACHABILITY_VERDICT.UNKNOWN]: "Re-run diagnose; if it persists, verify the configured or manifest-supplied spx_floor is a valid semver, that spx is on PATH, and that spx --version reports a semver version."
|
|
4514
4663
|
};
|
|
4515
4664
|
function record4(verdict, bucket, reading, floor) {
|
|
4516
4665
|
return {
|
|
@@ -4526,12 +4675,15 @@ function record4(verdict, bucket, reading, floor) {
|
|
|
4526
4675
|
};
|
|
4527
4676
|
}
|
|
4528
4677
|
function classifySpxReachability(reading, floor) {
|
|
4529
|
-
if (reading.errored
|
|
4678
|
+
if (reading.errored) {
|
|
4530
4679
|
return record4(SPX_REACHABILITY_VERDICT.UNKNOWN, VERDICT_BUCKET.UNKNOWN, reading, floor);
|
|
4531
4680
|
}
|
|
4532
4681
|
if (reading.resolvedPath === null) {
|
|
4533
4682
|
return record4(SPX_REACHABILITY_VERDICT.UNREACHABLE, VERDICT_BUCKET.BROKEN, reading, floor);
|
|
4534
4683
|
}
|
|
4684
|
+
if (floor === void 0) {
|
|
4685
|
+
return record4(SPX_REACHABILITY_VERDICT.PRESENT, VERDICT_BUCKET.HEALTHY, reading, floor);
|
|
4686
|
+
}
|
|
4535
4687
|
if (reading.version === null) {
|
|
4536
4688
|
return record4(SPX_REACHABILITY_VERDICT.UNKNOWN, VERDICT_BUCKET.UNKNOWN, reading, floor);
|
|
4537
4689
|
}
|
|
@@ -4548,13 +4700,11 @@ function spxReachabilityRunner(probe) {
|
|
|
4548
4700
|
// src/domains/diagnose/checks/worktree-pool.ts
|
|
4549
4701
|
var WORKTREE_POOL_VERDICT = {
|
|
4550
4702
|
COMPLIANT: "compliant",
|
|
4551
|
-
STALE_CLAIMS: "stale-claims",
|
|
4552
4703
|
NON_COMPLIANT: "non-compliant",
|
|
4553
4704
|
UNKNOWN: "unknown"
|
|
4554
4705
|
};
|
|
4555
4706
|
var REMEDIATION5 = {
|
|
4556
4707
|
[WORKTREE_POOL_VERDICT.COMPLIANT]: "Worktree layout is compliant; no action needed.",
|
|
4557
|
-
[WORKTREE_POOL_VERDICT.STALE_CLAIMS]: "Release stale worktree claims (spx worktree release) or remove dead worktrees.",
|
|
4558
4708
|
[WORKTREE_POOL_VERDICT.NON_COMPLIANT]: "Linked worktrees require a bare-repository pool; convert the layout or remove the linked worktrees.",
|
|
4559
4709
|
[WORKTREE_POOL_VERDICT.UNKNOWN]: "Re-run diagnose; if it persists, inspect git worktree list and spx worktree status."
|
|
4560
4710
|
};
|
|
@@ -4566,7 +4716,8 @@ function record5(verdict, bucket, reading) {
|
|
|
4566
4716
|
readings: {
|
|
4567
4717
|
bare: String(reading.bareRepository),
|
|
4568
4718
|
linked: String(reading.linkedWorktrees),
|
|
4569
|
-
|
|
4719
|
+
running: String(reading.running),
|
|
4720
|
+
free: String(reading.free)
|
|
4570
4721
|
},
|
|
4571
4722
|
remediation: REMEDIATION5[verdict]
|
|
4572
4723
|
};
|
|
@@ -4578,9 +4729,6 @@ function classifyWorktreePool(reading) {
|
|
|
4578
4729
|
if (!reading.bareRepository && reading.linkedWorktrees) {
|
|
4579
4730
|
return record5(WORKTREE_POOL_VERDICT.NON_COMPLIANT, VERDICT_BUCKET.BROKEN, reading);
|
|
4580
4731
|
}
|
|
4581
|
-
if (reading.staleClaim) {
|
|
4582
|
-
return record5(WORKTREE_POOL_VERDICT.STALE_CLAIMS, VERDICT_BUCKET.DEGRADED, reading);
|
|
4583
|
-
}
|
|
4584
4732
|
return record5(WORKTREE_POOL_VERDICT.COMPLIANT, VERDICT_BUCKET.HEALTHY, reading);
|
|
4585
4733
|
}
|
|
4586
4734
|
function worktreePoolRunner(probe) {
|
|
@@ -4598,7 +4746,7 @@ var DEL_CHAR_CODE = 127;
|
|
|
4598
4746
|
var HEX_RADIX = 16;
|
|
4599
4747
|
var HEX_PAD = 2;
|
|
4600
4748
|
function formatHexEscape(code) {
|
|
4601
|
-
return
|
|
4749
|
+
return String.raw`\x${code.toString(HEX_RADIX).padStart(HEX_PAD, "0")}`;
|
|
4602
4750
|
}
|
|
4603
4751
|
function nonStringSentinel(type) {
|
|
4604
4752
|
return `<non-string:${type}>`;
|
|
@@ -4639,7 +4787,7 @@ var DIAGNOSE_CLI = {
|
|
|
4639
4787
|
COLOR_FLAG: "--color",
|
|
4640
4788
|
NO_COLOR_FLAG: "--no-color"
|
|
4641
4789
|
};
|
|
4642
|
-
var DIAGNOSE_DOMAIN_DESCRIPTION = "Run deterministic environment-diagnostics checks from a
|
|
4790
|
+
var DIAGNOSE_DOMAIN_DESCRIPTION = "Run deterministic environment-diagnostics checks, resolving facts from spx.config or a --manifest";
|
|
4643
4791
|
var DEFAULT_REGISTRY = {
|
|
4644
4792
|
[CHECK_NAME.SPX_REACHABILITY]: spxReachabilityRunner(defaultSpxReachabilityProbe),
|
|
4645
4793
|
[CHECK_NAME.SESSION_ENVIRONMENT]: sessionEnvironmentRunner(defaultSessionEnvironmentProbe),
|
|
@@ -4655,11 +4803,15 @@ var diagnoseDomain = {
|
|
|
4655
4803
|
name: DIAGNOSE_CLI.COMMAND,
|
|
4656
4804
|
description: DIAGNOSE_DOMAIN_DESCRIPTION,
|
|
4657
4805
|
register: (program2) => {
|
|
4658
|
-
program2.command(DIAGNOSE_CLI.COMMAND).description(DIAGNOSE_DOMAIN_DESCRIPTION).
|
|
4806
|
+
program2.command(DIAGNOSE_CLI.COMMAND).description(DIAGNOSE_DOMAIN_DESCRIPTION).option(
|
|
4807
|
+
`${DIAGNOSE_CLI.MANIFEST_FLAG} <path>`,
|
|
4808
|
+
"Path to a declarative diagnose manifest that fully instruments the diagnosis"
|
|
4809
|
+
).addOption(
|
|
4659
4810
|
new Option(`${DIAGNOSE_CLI.FORMAT_FLAG} <format>`, "Output format").choices([DIAGNOSE_FORMAT.TEXT, DIAGNOSE_FORMAT.JSON]).default(DIAGNOSE_FORMAT.TEXT)
|
|
4660
4811
|
).addOption(new Option(`${DIAGNOSE_CLI.COLOR_FLAG}`, "Force colored output")).addOption(new Option(`${DIAGNOSE_CLI.NO_COLOR_FLAG}`, "Disable colored output")).action(async (options) => {
|
|
4661
4812
|
const result = await diagnoseCommand({
|
|
4662
4813
|
manifestPath: options.manifest,
|
|
4814
|
+
productDir: process.cwd(),
|
|
4663
4815
|
format: options.format,
|
|
4664
4816
|
color: resolveColorChoice({
|
|
4665
4817
|
flag: options.color,
|
|
@@ -4682,7 +4834,7 @@ import { appendFile as nodeAppendFile } from "fs/promises";
|
|
|
4682
4834
|
// src/domains/worktree/controlling-process.ts
|
|
4683
4835
|
var CONTROLLING_PID_ENV = "SPX_WORKTREE_CONTROLLING_PID";
|
|
4684
4836
|
var AGENT_RUNTIME_NAMES = ["claude", "codex"];
|
|
4685
|
-
var AGENT_COMMAND_PATTERN = new RegExp(
|
|
4837
|
+
var AGENT_COMMAND_PATTERN = new RegExp(String.raw`\b(?:${AGENT_RUNTIME_NAMES.join("|")})\b`, "i");
|
|
4686
4838
|
var CONTROLLING_PROCESS_ERROR = {
|
|
4687
4839
|
UNRESOLVED: "worktree controlling process could not be resolved"
|
|
4688
4840
|
};
|
|
@@ -5159,6 +5311,84 @@ function createJournal(backend, identity) {
|
|
|
5159
5311
|
};
|
|
5160
5312
|
}
|
|
5161
5313
|
|
|
5314
|
+
// src/lib/github-snapshot-sink/gh-api-runner.ts
|
|
5315
|
+
import { execa as execa5 } from "execa";
|
|
5316
|
+
var GH_API = {
|
|
5317
|
+
executable: "gh"
|
|
5318
|
+
};
|
|
5319
|
+
var runGhApi = async (args, options) => {
|
|
5320
|
+
const result = await execa5(GH_API.executable, [...args], {
|
|
5321
|
+
...options?.input === void 0 ? {} : { input: options.input }
|
|
5322
|
+
});
|
|
5323
|
+
return { stdout: typeof result.stdout === "string" ? result.stdout : "" };
|
|
5324
|
+
};
|
|
5325
|
+
|
|
5326
|
+
// src/lib/github-snapshot-sink/pull-request-comment-client.ts
|
|
5327
|
+
var GITHUB_API_ERROR = {
|
|
5328
|
+
SURFACE_UNSUPPORTED: "the pull-request comment client supports only the pull-request comment surface"
|
|
5329
|
+
};
|
|
5330
|
+
var GITHUB_API = {
|
|
5331
|
+
apiCommand: "api",
|
|
5332
|
+
fieldFlag: "-f",
|
|
5333
|
+
methodFlag: "-X",
|
|
5334
|
+
paginateFlag: "--paginate",
|
|
5335
|
+
slurpFlag: "--slurp",
|
|
5336
|
+
postMethod: "POST",
|
|
5337
|
+
patchMethod: "PATCH",
|
|
5338
|
+
bodyField: "body"
|
|
5339
|
+
};
|
|
5340
|
+
function githubCommentMarkerTag(marker) {
|
|
5341
|
+
return `<!-- ${marker} -->`;
|
|
5342
|
+
}
|
|
5343
|
+
function issueCommentsPath(repository, pullNumber) {
|
|
5344
|
+
return `repos/${repository}/issues/${pullNumber}/comments`;
|
|
5345
|
+
}
|
|
5346
|
+
function issueCommentPath(repository, commentId) {
|
|
5347
|
+
return `repos/${repository}/issues/comments/${commentId}`;
|
|
5348
|
+
}
|
|
5349
|
+
function createGithubPullRequestCommentClient(options) {
|
|
5350
|
+
const { run } = options;
|
|
5351
|
+
return {
|
|
5352
|
+
async upsertPullRequestComment({ pullNumber, marker, body }) {
|
|
5353
|
+
const markedBody = `${body}
|
|
5354
|
+
${githubCommentMarkerTag(marker)}`;
|
|
5355
|
+
const listed = await run([
|
|
5356
|
+
GITHUB_API.apiCommand,
|
|
5357
|
+
issueCommentsPath(options.repository, pullNumber),
|
|
5358
|
+
GITHUB_API.paginateFlag,
|
|
5359
|
+
GITHUB_API.slurpFlag
|
|
5360
|
+
]);
|
|
5361
|
+
const comments = JSON.parse(listed.stdout).flat();
|
|
5362
|
+
const existing = comments.find((comment) => comment.body.includes(githubCommentMarkerTag(marker)));
|
|
5363
|
+
if (existing === void 0) {
|
|
5364
|
+
await run([
|
|
5365
|
+
GITHUB_API.apiCommand,
|
|
5366
|
+
issueCommentsPath(options.repository, pullNumber),
|
|
5367
|
+
GITHUB_API.methodFlag,
|
|
5368
|
+
GITHUB_API.postMethod,
|
|
5369
|
+
GITHUB_API.fieldFlag,
|
|
5370
|
+
`${GITHUB_API.bodyField}=${markedBody}`
|
|
5371
|
+
]);
|
|
5372
|
+
return;
|
|
5373
|
+
}
|
|
5374
|
+
await run([
|
|
5375
|
+
GITHUB_API.apiCommand,
|
|
5376
|
+
issueCommentPath(options.repository, existing.id),
|
|
5377
|
+
GITHUB_API.methodFlag,
|
|
5378
|
+
GITHUB_API.patchMethod,
|
|
5379
|
+
GITHUB_API.fieldFlag,
|
|
5380
|
+
`${GITHUB_API.bodyField}=${markedBody}`
|
|
5381
|
+
]);
|
|
5382
|
+
},
|
|
5383
|
+
uploadActionsArtifact() {
|
|
5384
|
+
return Promise.reject(new Error(GITHUB_API_ERROR.SURFACE_UNSUPPORTED));
|
|
5385
|
+
},
|
|
5386
|
+
saveActionsCache() {
|
|
5387
|
+
return Promise.reject(new Error(GITHUB_API_ERROR.SURFACE_UNSUPPORTED));
|
|
5388
|
+
}
|
|
5389
|
+
};
|
|
5390
|
+
}
|
|
5391
|
+
|
|
5162
5392
|
// src/lib/github-snapshot-sink/index.ts
|
|
5163
5393
|
var SNAPSHOT_SURFACE_KIND = {
|
|
5164
5394
|
PULL_REQUEST_COMMENT: "pull-request-comment",
|
|
@@ -5369,7 +5599,7 @@ async function appendJournalEvent(ref, input, sink, options = {}) {
|
|
|
5369
5599
|
try {
|
|
5370
5600
|
event = await bound.value.journal.append(input);
|
|
5371
5601
|
} catch (error) {
|
|
5372
|
-
return { ok: false, error: `${JOURNAL_RUNTIME_ERROR.APPEND_FAILED}: ${
|
|
5602
|
+
return { ok: false, error: `${JOURNAL_RUNTIME_ERROR.APPEND_FAILED}: ${toMessage(error)}` };
|
|
5373
5603
|
}
|
|
5374
5604
|
try {
|
|
5375
5605
|
await sink.emit(event);
|
|
@@ -5383,7 +5613,7 @@ async function readJournalEvents(ref, fromCursor, options = {}) {
|
|
|
5383
5613
|
try {
|
|
5384
5614
|
return { ok: true, value: await bound.value.journal.read(fromCursor) };
|
|
5385
5615
|
} catch (error) {
|
|
5386
|
-
return { ok: false, error: `${JOURNAL_RUNTIME_ERROR.READ_FAILED}: ${
|
|
5616
|
+
return { ok: false, error: `${JOURNAL_RUNTIME_ERROR.READ_FAILED}: ${toMessage(error)}` };
|
|
5387
5617
|
}
|
|
5388
5618
|
}
|
|
5389
5619
|
async function sealJournalRun(ref, options = {}) {
|
|
@@ -5393,7 +5623,7 @@ async function sealJournalRun(ref, options = {}) {
|
|
|
5393
5623
|
await bound.value.journal.seal();
|
|
5394
5624
|
return { ok: true, value: void 0 };
|
|
5395
5625
|
} catch (error) {
|
|
5396
|
-
return { ok: false, error: `${JOURNAL_RUNTIME_ERROR.SEAL_FAILED}: ${
|
|
5626
|
+
return { ok: false, error: `${JOURNAL_RUNTIME_ERROR.SEAL_FAILED}: ${toMessage(error)}` };
|
|
5397
5627
|
}
|
|
5398
5628
|
}
|
|
5399
5629
|
async function renderJournalRun(ref, projection, options = {}) {
|
|
@@ -5402,12 +5632,9 @@ async function renderJournalRun(ref, projection, options = {}) {
|
|
|
5402
5632
|
try {
|
|
5403
5633
|
return { ok: true, value: await bound.value.journal.render(projection) };
|
|
5404
5634
|
} catch (error) {
|
|
5405
|
-
return { ok: false, error: `${JOURNAL_RUNTIME_ERROR.RENDER_FAILED}: ${
|
|
5635
|
+
return { ok: false, error: `${JOURNAL_RUNTIME_ERROR.RENDER_FAILED}: ${toMessage(error)}` };
|
|
5406
5636
|
}
|
|
5407
5637
|
}
|
|
5408
|
-
function toMessage2(error) {
|
|
5409
|
-
return error instanceof Error ? error.message : String(error);
|
|
5410
|
-
}
|
|
5411
5638
|
|
|
5412
5639
|
// src/commands/journal/cli.ts
|
|
5413
5640
|
var JOURNAL_CLI_EXIT_CODE = {
|
|
@@ -5600,80 +5827,6 @@ async function journalRenderCommand(scope2, deps = {}) {
|
|
|
5600
5827
|
return okResult(JSON.stringify(rendered.value));
|
|
5601
5828
|
}
|
|
5602
5829
|
|
|
5603
|
-
// src/commands/journal/github-client.ts
|
|
5604
|
-
import { execa as execa5 } from "execa";
|
|
5605
|
-
var GITHUB_CLI_ERROR = {
|
|
5606
|
-
SURFACE_UNSUPPORTED: "the pull-request comment client supports only the pull-request comment surface"
|
|
5607
|
-
};
|
|
5608
|
-
var GITHUB_CLI = {
|
|
5609
|
-
executable: "gh",
|
|
5610
|
-
apiCommand: "api",
|
|
5611
|
-
fieldFlag: "-f",
|
|
5612
|
-
methodFlag: "-X",
|
|
5613
|
-
paginateFlag: "--paginate",
|
|
5614
|
-
slurpFlag: "--slurp",
|
|
5615
|
-
postMethod: "POST",
|
|
5616
|
-
patchMethod: "PATCH",
|
|
5617
|
-
bodyField: "body"
|
|
5618
|
-
};
|
|
5619
|
-
var defaultGhRunner = async (args, options) => {
|
|
5620
|
-
const result = await execa5(GITHUB_CLI.executable, [...args], {
|
|
5621
|
-
...options?.input === void 0 ? {} : { input: options.input }
|
|
5622
|
-
});
|
|
5623
|
-
return { stdout: typeof result.stdout === "string" ? result.stdout : "" };
|
|
5624
|
-
};
|
|
5625
|
-
function githubCommentMarkerTag(marker) {
|
|
5626
|
-
return `<!-- ${marker} -->`;
|
|
5627
|
-
}
|
|
5628
|
-
function issueCommentsPath(repository, pullNumber) {
|
|
5629
|
-
return `repos/${repository}/issues/${pullNumber}/comments`;
|
|
5630
|
-
}
|
|
5631
|
-
function issueCommentPath(repository, commentId) {
|
|
5632
|
-
return `repos/${repository}/issues/comments/${commentId}`;
|
|
5633
|
-
}
|
|
5634
|
-
function createGithubPrCommentClient(options) {
|
|
5635
|
-
const run = options.run ?? defaultGhRunner;
|
|
5636
|
-
return {
|
|
5637
|
-
async upsertPullRequestComment({ pullNumber, marker, body }) {
|
|
5638
|
-
const markedBody = `${body}
|
|
5639
|
-
${githubCommentMarkerTag(marker)}`;
|
|
5640
|
-
const listed = await run([
|
|
5641
|
-
GITHUB_CLI.apiCommand,
|
|
5642
|
-
issueCommentsPath(options.repository, pullNumber),
|
|
5643
|
-
GITHUB_CLI.paginateFlag,
|
|
5644
|
-
GITHUB_CLI.slurpFlag
|
|
5645
|
-
]);
|
|
5646
|
-
const comments = JSON.parse(listed.stdout).flat();
|
|
5647
|
-
const existing = comments.find((comment) => comment.body.includes(githubCommentMarkerTag(marker)));
|
|
5648
|
-
if (existing === void 0) {
|
|
5649
|
-
await run([
|
|
5650
|
-
GITHUB_CLI.apiCommand,
|
|
5651
|
-
issueCommentsPath(options.repository, pullNumber),
|
|
5652
|
-
GITHUB_CLI.methodFlag,
|
|
5653
|
-
GITHUB_CLI.postMethod,
|
|
5654
|
-
GITHUB_CLI.fieldFlag,
|
|
5655
|
-
`${GITHUB_CLI.bodyField}=${markedBody}`
|
|
5656
|
-
]);
|
|
5657
|
-
return;
|
|
5658
|
-
}
|
|
5659
|
-
await run([
|
|
5660
|
-
GITHUB_CLI.apiCommand,
|
|
5661
|
-
issueCommentPath(options.repository, existing.id),
|
|
5662
|
-
GITHUB_CLI.methodFlag,
|
|
5663
|
-
GITHUB_CLI.patchMethod,
|
|
5664
|
-
GITHUB_CLI.fieldFlag,
|
|
5665
|
-
`${GITHUB_CLI.bodyField}=${markedBody}`
|
|
5666
|
-
]);
|
|
5667
|
-
},
|
|
5668
|
-
uploadActionsArtifact() {
|
|
5669
|
-
return Promise.reject(new Error(GITHUB_CLI_ERROR.SURFACE_UNSUPPORTED));
|
|
5670
|
-
},
|
|
5671
|
-
saveActionsCache() {
|
|
5672
|
-
return Promise.reject(new Error(GITHUB_CLI_ERROR.SURFACE_UNSUPPORTED));
|
|
5673
|
-
}
|
|
5674
|
-
};
|
|
5675
|
-
}
|
|
5676
|
-
|
|
5677
5830
|
// src/lib/process-lifecycle/exit-codes.ts
|
|
5678
5831
|
var SIGINT_EXIT_CODE = 130;
|
|
5679
5832
|
var SIGTERM_EXIT_CODE = 143;
|
|
@@ -5913,7 +6066,7 @@ function streamBinding() {
|
|
|
5913
6066
|
const repository = process.env[JOURNAL_CLI_ENV.GITHUB_REPOSITORY] ?? "";
|
|
5914
6067
|
return {
|
|
5915
6068
|
localSink: stdoutStreamSink(),
|
|
5916
|
-
githubClient:
|
|
6069
|
+
githubClient: createGithubPullRequestCommentClient({ repository, run: runGhApi }),
|
|
5917
6070
|
githubRepository: repository
|
|
5918
6071
|
};
|
|
5919
6072
|
}
|
|
@@ -6157,9 +6310,7 @@ var NoSessionsAvailableError = class extends SessionError {
|
|
|
6157
6310
|
var SessionLegacyFrontmatterInputError = class extends SessionError {
|
|
6158
6311
|
constructor() {
|
|
6159
6312
|
super(
|
|
6160
|
-
|
|
6161
|
-
|
|
6162
|
-
printf '%s\\n' '{"priority":"high","goal":"...","next_step":"..."}' '# Body' | spx session handoff`
|
|
6313
|
+
"Invalid handoff input: stdin opens with the YAML-frontmatter delimiter `---`. Use the JSON-prefix wire format: a single JSON object holding caller-supplied fields followed by the body bytes verbatim. Example:\n\n" + String.raw` printf '%s\n' '{"priority":"high","goal":"...","next_step":"..."}' '# Body' ` + "| spx session handoff"
|
|
6163
6314
|
);
|
|
6164
6315
|
this.name = "SessionLegacyFrontmatterInputError";
|
|
6165
6316
|
}
|
|
@@ -6582,7 +6733,7 @@ function resolveSessionPaths(id, config = DEFAULT_SESSION_CONFIG) {
|
|
|
6582
6733
|
}
|
|
6583
6734
|
function formatShowOutput(content, options) {
|
|
6584
6735
|
const metadata = parseSessionMetadata(content);
|
|
6585
|
-
const
|
|
6736
|
+
const headerLines2 = [
|
|
6586
6737
|
`${SESSION_SHOW_LABEL.STATUS}: ${options.status}`,
|
|
6587
6738
|
`${SESSION_SHOW_LABEL.PRIORITY}: ${metadata.priority}`,
|
|
6588
6739
|
`${SESSION_SHOW_LABEL.GIT_REF}: ${metadata.git_ref}`,
|
|
@@ -6591,7 +6742,7 @@ function formatShowOutput(content, options) {
|
|
|
6591
6742
|
`${SESSION_SHOW_LABEL.CREATED}: ${metadata.created_at ?? ""}`,
|
|
6592
6743
|
`${SESSION_SHOW_LABEL.AGENT_SESSION}: ${metadata.agent_session_id ?? ""}`
|
|
6593
6744
|
];
|
|
6594
|
-
const header =
|
|
6745
|
+
const header = headerLines2.join("\n");
|
|
6595
6746
|
const separator = "\n" + SESSION_SHOW_SEPARATOR_CHAR.repeat(SESSION_SHOW_SEPARATOR_WIDTH) + "\n\n";
|
|
6596
6747
|
return header + separator + content;
|
|
6597
6748
|
}
|
|
@@ -7351,13 +7502,13 @@ Workflow:
|
|
|
7351
7502
|
4. archive - Move session to archive
|
|
7352
7503
|
5. delete - Remove session permanently
|
|
7353
7504
|
`;
|
|
7354
|
-
var HANDOFF_FRONTMATTER_HELP = `
|
|
7505
|
+
var HANDOFF_FRONTMATTER_HELP = String.raw`
|
|
7355
7506
|
Usage:
|
|
7356
7507
|
Pipe a JSON header followed by the body bytes to stdin.
|
|
7357
7508
|
|
|
7358
7509
|
The header is a single JSON object holding caller-supplied structured fields.
|
|
7359
7510
|
A single LF or CRLF after the header is consumed as a separator. The body
|
|
7360
|
-
is the remaining bytes verbatim
|
|
7511
|
+
is the remaining bytes verbatim — no YAML, no escape rules, no ambiguity
|
|
7361
7512
|
from leading characters like '#' or '---'.
|
|
7362
7513
|
|
|
7363
7514
|
JSON Header Fields:
|
|
@@ -7380,11 +7531,11 @@ Output Tags (for automation):
|
|
|
7380
7531
|
<SESSION_FILE>/path/to/file</SESSION_FILE> Absolute path to created file
|
|
7381
7532
|
|
|
7382
7533
|
Canonical Invocation:
|
|
7383
|
-
printf '%s
|
|
7384
|
-
'{"priority":"high","goal":"Fix login","next_step":"Run validation","specs":[],"files":[]}'
|
|
7385
|
-
'# Fix login'
|
|
7386
|
-
''
|
|
7387
|
-
'Body text
|
|
7534
|
+
printf '%s\n' \
|
|
7535
|
+
'{"priority":"high","goal":"Fix login","next_step":"Run validation","specs":[],"files":[]}' \
|
|
7536
|
+
'# Fix login' \
|
|
7537
|
+
'' \
|
|
7538
|
+
'Body text — # symbols, --- delimiters, and code fences are literal.' \
|
|
7388
7539
|
| spx session handoff
|
|
7389
7540
|
`;
|
|
7390
7541
|
var PICKUP_SELECTION_HELP = `
|
|
@@ -9081,92 +9232,111 @@ async function runNodeCommand(options, deps) {
|
|
|
9081
9232
|
return { dispatch, runFile, recorded };
|
|
9082
9233
|
}
|
|
9083
9234
|
|
|
9084
|
-
// src/
|
|
9085
|
-
|
|
9086
|
-
function createNodeOutcomeResolver(deps) {
|
|
9087
|
-
let evidence;
|
|
9088
|
-
const currentInputsByCoveredPaths = /* @__PURE__ */ new Map();
|
|
9089
|
-
const sharedEvidence = () => evidence ??= loadResolverEvidence(deps);
|
|
9090
|
-
const refreshEvidence = () => {
|
|
9091
|
-
evidence = loadResolverEvidence(deps);
|
|
9092
|
-
};
|
|
9093
|
-
const currentInputsFor = (coveredPaths) => {
|
|
9094
|
-
const cacheKey = coveredPathCollectionKey(coveredPaths);
|
|
9095
|
-
const cached = currentInputsByCoveredPaths.get(cacheKey);
|
|
9096
|
-
if (cached !== void 0) {
|
|
9097
|
-
return cached;
|
|
9098
|
-
}
|
|
9099
|
-
const current = currentStalenessInputs(deps.productDir, coveredPaths, deps);
|
|
9100
|
-
currentInputsByCoveredPaths.set(cacheKey, current);
|
|
9101
|
-
return current;
|
|
9102
|
-
};
|
|
9103
|
-
return async (nodeId) => {
|
|
9104
|
-
const { discoveredTestPaths, terminalRuns } = await sharedEvidence();
|
|
9105
|
-
const nodePath = `${SPEC_TREE_CONFIG.ROOT_DIRECTORY}${PATH_SEPARATOR}${nodeId}`;
|
|
9106
|
-
const nodeTestPaths = filterNodeTestPaths(discoveredTestPaths, nodePath);
|
|
9107
|
-
const usable = await usableRecordedOutcome(discoveredTestPaths, terminalRuns, nodeTestPaths, currentInputsFor);
|
|
9108
|
-
if (usable !== void 0) {
|
|
9109
|
-
return usable;
|
|
9110
|
-
}
|
|
9111
|
-
const { dispatch, recorded } = await runNodeCommand({ productDir: deps.productDir, nodePath }, deps);
|
|
9112
|
-
refreshEvidence();
|
|
9113
|
-
return outcomesCoverPaths(dispatch.outcomes, nodeTestPaths) && recorded.status === TEST_RUN_STATE_STATUS.PASSED;
|
|
9114
|
-
};
|
|
9115
|
-
}
|
|
9116
|
-
function coveredPathCollectionKey(coveredPaths) {
|
|
9117
|
-
return JSON.stringify([...coveredPaths].sort(compareAsciiStrings));
|
|
9118
|
-
}
|
|
9119
|
-
async function loadResolverEvidence(deps) {
|
|
9120
|
-
const discoveredTestPaths = await discoverTestFiles(deps.productDir);
|
|
9121
|
-
const runs = await readTestingRuns(deps.productDir, deps);
|
|
9122
|
-
return { discoveredTestPaths, terminalRuns: runs.ok ? runs.value.terminalRuns : [] };
|
|
9123
|
-
}
|
|
9124
|
-
function filterNodeTestPaths(discoveredTestPaths, nodePath) {
|
|
9125
|
-
const prefix = `${nodePath}${PATH_SEPARATOR}`;
|
|
9126
|
-
return discoveredTestPaths.filter((path7) => path7.startsWith(prefix));
|
|
9127
|
-
}
|
|
9128
|
-
async function usableRecordedOutcome(discoveredTestPaths, terminalRuns, nodeTestPaths, currentInputsFor) {
|
|
9129
|
-
const latest = selectLatestTerminalTestRunForNode(terminalRuns, nodeTestPaths);
|
|
9130
|
-
if (latest === void 0) {
|
|
9131
|
-
return void 0;
|
|
9132
|
-
}
|
|
9133
|
-
const runCoveredPaths = latest.state.runnerOutcomes.flatMap((outcome) => outcome.testPaths);
|
|
9134
|
-
const presentTestPaths = new Set(discoveredTestPaths);
|
|
9135
|
-
if (!runCoveredPaths.every((path7) => presentTestPaths.has(path7))) {
|
|
9136
|
-
return void 0;
|
|
9137
|
-
}
|
|
9138
|
-
const current = await currentInputsFor(runCoveredPaths);
|
|
9139
|
-
const fresh = isStalenessMatch(extractStalenessInputs(latest.state), current);
|
|
9140
|
-
return fresh && latest.state.status === TEST_RUN_STATE_STATUS.PASSED ? true : void 0;
|
|
9141
|
-
}
|
|
9235
|
+
// src/lib/node-status/ci-projection.ts
|
|
9236
|
+
import { parse as parseYaml3 } from "yaml";
|
|
9142
9237
|
|
|
9143
9238
|
// src/lib/node-status/classify.ts
|
|
9144
|
-
var
|
|
9239
|
+
var NODE_STATUS_SCHEMA_VERSION = 1;
|
|
9145
9240
|
var NODE_STATUS_JSON_INDENT = 2;
|
|
9241
|
+
var NODE_STATUS_FIELD = {
|
|
9242
|
+
SCHEMA_VERSION: "schemaVersion",
|
|
9243
|
+
VERIFICATION: "verification",
|
|
9244
|
+
OVERALL: "overall"
|
|
9245
|
+
};
|
|
9246
|
+
var NODE_STATUS_VERIFICATION_MECHANISM = {
|
|
9247
|
+
TEST: "test",
|
|
9248
|
+
EVAL: "eval",
|
|
9249
|
+
AUDIT: "audit"
|
|
9250
|
+
};
|
|
9251
|
+
var NODE_STATUS_EVIDENCE_OUTCOME = {
|
|
9252
|
+
PASSED: "passed",
|
|
9253
|
+
FAILED: "failed",
|
|
9254
|
+
NOT_RUN: "not-run"
|
|
9255
|
+
};
|
|
9256
|
+
var NODE_STATUS_MECHANISM_OVERALL = {
|
|
9257
|
+
PASSED: NODE_STATUS_EVIDENCE_OUTCOME.PASSED,
|
|
9258
|
+
FAILED: NODE_STATUS_EVIDENCE_OUTCOME.FAILED,
|
|
9259
|
+
PARTIAL: "partial",
|
|
9260
|
+
NOT_RUN: NODE_STATUS_EVIDENCE_OUTCOME.NOT_RUN
|
|
9261
|
+
};
|
|
9146
9262
|
function classifyNodeStatus(facts) {
|
|
9147
|
-
if (!facts.
|
|
9263
|
+
if (!facts.hasVerificationReferences) return SPEC_TREE_NODE_STATE.DECLARED;
|
|
9148
9264
|
if (facts.isExcluded) return SPEC_TREE_NODE_STATE.SPECIFIED;
|
|
9149
|
-
if (facts.
|
|
9265
|
+
if (verificationPassed(facts.verification)) return SPEC_TREE_NODE_STATE.PASSING;
|
|
9150
9266
|
return SPEC_TREE_NODE_STATE.FAILING;
|
|
9151
9267
|
}
|
|
9152
|
-
function
|
|
9153
|
-
return
|
|
9268
|
+
function createNodeStatusFile(verification) {
|
|
9269
|
+
return {
|
|
9270
|
+
[NODE_STATUS_FIELD.SCHEMA_VERSION]: NODE_STATUS_SCHEMA_VERSION,
|
|
9271
|
+
[NODE_STATUS_FIELD.VERIFICATION]: verification
|
|
9272
|
+
};
|
|
9273
|
+
}
|
|
9274
|
+
function hasNodeStatusVerificationReferences(verification) {
|
|
9275
|
+
return Object.values(verification).some(
|
|
9276
|
+
(mechanism) => Object.keys(mechanism).some((reference) => reference !== NODE_STATUS_FIELD.OVERALL)
|
|
9277
|
+
);
|
|
9278
|
+
}
|
|
9279
|
+
function createNodeStatusMechanismRecord(outcomes) {
|
|
9280
|
+
return {
|
|
9281
|
+
[NODE_STATUS_FIELD.OVERALL]: rollupNodeStatusMechanism(outcomes),
|
|
9282
|
+
...outcomes
|
|
9283
|
+
};
|
|
9284
|
+
}
|
|
9285
|
+
function rollupNodeStatusMechanism(outcomes) {
|
|
9286
|
+
const values = Object.values(outcomes);
|
|
9287
|
+
if (values.length === 0) return NODE_STATUS_MECHANISM_OVERALL.NOT_RUN;
|
|
9288
|
+
if (values.includes(NODE_STATUS_EVIDENCE_OUTCOME.FAILED)) {
|
|
9289
|
+
return NODE_STATUS_MECHANISM_OVERALL.FAILED;
|
|
9290
|
+
}
|
|
9291
|
+
if (values.every((outcome) => outcome === NODE_STATUS_EVIDENCE_OUTCOME.PASSED)) {
|
|
9292
|
+
return NODE_STATUS_MECHANISM_OVERALL.PASSED;
|
|
9293
|
+
}
|
|
9294
|
+
if (values.every((outcome) => outcome === NODE_STATUS_EVIDENCE_OUTCOME.NOT_RUN)) {
|
|
9295
|
+
return NODE_STATUS_MECHANISM_OVERALL.NOT_RUN;
|
|
9296
|
+
}
|
|
9297
|
+
return NODE_STATUS_MECHANISM_OVERALL.PARTIAL;
|
|
9298
|
+
}
|
|
9299
|
+
function serializeNodeStatus(status) {
|
|
9300
|
+
return `${JSON.stringify(status, null, NODE_STATUS_JSON_INDENT)}
|
|
9154
9301
|
`;
|
|
9155
9302
|
}
|
|
9303
|
+
function verificationPassed(verification) {
|
|
9304
|
+
if (verification === void 0) return false;
|
|
9305
|
+
const mechanisms = Object.values(verification);
|
|
9306
|
+
return mechanisms.length > 0 && mechanisms.every((mechanism) => mechanism[NODE_STATUS_FIELD.OVERALL] === NODE_STATUS_MECHANISM_OVERALL.PASSED);
|
|
9307
|
+
}
|
|
9156
9308
|
|
|
9157
9309
|
// src/lib/node-status/provider.ts
|
|
9158
9310
|
import { join as join20 } from "path";
|
|
9159
9311
|
|
|
9312
|
+
// src/lib/node-status/exclude.ts
|
|
9313
|
+
function isNodeStatusEntryExcluded(ignoreReader, node) {
|
|
9314
|
+
const reference = node.ref?.path;
|
|
9315
|
+
if (reference === void 0) return false;
|
|
9316
|
+
return ignoreReader.isUnderIgnoreSource(reference);
|
|
9317
|
+
}
|
|
9318
|
+
|
|
9160
9319
|
// src/lib/node-status/read.ts
|
|
9161
9320
|
import { readFileSync as readFileSync2 } from "fs";
|
|
9162
9321
|
import { join as join19 } from "path";
|
|
9163
9322
|
var NODE_STATUS_FILENAME = "spx.status.json";
|
|
9164
|
-
var
|
|
9323
|
+
var NODE_STATUS_MECHANISMS = new Set(Object.values(NODE_STATUS_VERIFICATION_MECHANISM));
|
|
9324
|
+
var NODE_STATUS_EVIDENCE_OUTCOMES = new Set(Object.values(NODE_STATUS_EVIDENCE_OUTCOME));
|
|
9325
|
+
var NODE_STATUS_OVERALL_VALUES = new Set(Object.values(NODE_STATUS_MECHANISM_OVERALL));
|
|
9165
9326
|
function isNodeError2(error) {
|
|
9166
9327
|
return error instanceof Error && "code" in error;
|
|
9167
9328
|
}
|
|
9168
|
-
function
|
|
9169
|
-
return typeof value === "
|
|
9329
|
+
function isObject(value) {
|
|
9330
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
9331
|
+
}
|
|
9332
|
+
function isNodeStatusMechanism(value) {
|
|
9333
|
+
return NODE_STATUS_MECHANISMS.has(value);
|
|
9334
|
+
}
|
|
9335
|
+
function isNodeStatusEvidenceOutcome(value) {
|
|
9336
|
+
return typeof value === "string" && NODE_STATUS_EVIDENCE_OUTCOMES.has(value);
|
|
9337
|
+
}
|
|
9338
|
+
function isNodeStatusMechanismOverall(value) {
|
|
9339
|
+
return typeof value === "string" && NODE_STATUS_OVERALL_VALUES.has(value);
|
|
9170
9340
|
}
|
|
9171
9341
|
function readNodeStatus(nodeDir) {
|
|
9172
9342
|
const filePath = join19(nodeDir, NODE_STATUS_FILENAME);
|
|
@@ -9179,14 +9349,67 @@ function readNodeStatus(nodeDir) {
|
|
|
9179
9349
|
}
|
|
9180
9350
|
throw error;
|
|
9181
9351
|
}
|
|
9182
|
-
|
|
9183
|
-
|
|
9184
|
-
|
|
9352
|
+
return parseNodeStatusFile(JSON.parse(content), filePath);
|
|
9353
|
+
}
|
|
9354
|
+
function parseNodeStatusFile(candidate, source) {
|
|
9355
|
+
if (!isObject(candidate)) {
|
|
9356
|
+
throw new Error(`Invalid ${NODE_STATUS_FILENAME} at ${source}: expected a JSON object`);
|
|
9357
|
+
}
|
|
9358
|
+
if (candidate[NODE_STATUS_FIELD.SCHEMA_VERSION] !== NODE_STATUS_SCHEMA_VERSION) {
|
|
9359
|
+
throw new Error(
|
|
9360
|
+
`Invalid ${NODE_STATUS_FILENAME} at ${source}: schemaVersion must be ${NODE_STATUS_SCHEMA_VERSION}`
|
|
9361
|
+
);
|
|
9362
|
+
}
|
|
9363
|
+
const verification = parseVerification(candidate[NODE_STATUS_FIELD.VERIFICATION], source);
|
|
9364
|
+
return {
|
|
9365
|
+
[NODE_STATUS_FIELD.SCHEMA_VERSION]: NODE_STATUS_SCHEMA_VERSION,
|
|
9366
|
+
[NODE_STATUS_FIELD.VERIFICATION]: verification
|
|
9367
|
+
};
|
|
9368
|
+
}
|
|
9369
|
+
function parseVerification(candidate, source) {
|
|
9370
|
+
if (!isObject(candidate)) {
|
|
9371
|
+
throw new Error(`Invalid ${NODE_STATUS_FILENAME} at ${source}: verification must be an object`);
|
|
9372
|
+
}
|
|
9373
|
+
const verification = {};
|
|
9374
|
+
for (const [mechanism, rawRecord] of Object.entries(candidate)) {
|
|
9375
|
+
if (!isNodeStatusMechanism(mechanism)) {
|
|
9376
|
+
throw new Error(`Invalid ${NODE_STATUS_FILENAME} at ${source}: unknown verification mechanism "${mechanism}"`);
|
|
9377
|
+
}
|
|
9378
|
+
verification[mechanism] = parseMechanismRecord(rawRecord, source, mechanism);
|
|
9379
|
+
}
|
|
9380
|
+
return verification;
|
|
9381
|
+
}
|
|
9382
|
+
function parseMechanismRecord(candidate, source, mechanism) {
|
|
9383
|
+
if (!isObject(candidate)) {
|
|
9384
|
+
throw new Error(`Invalid ${NODE_STATUS_FILENAME} at ${source}: verification.${mechanism} must be an object`);
|
|
9385
|
+
}
|
|
9386
|
+
const overall = candidate[NODE_STATUS_FIELD.OVERALL];
|
|
9387
|
+
if (!isNodeStatusMechanismOverall(overall)) {
|
|
9388
|
+
throw new Error(
|
|
9389
|
+
`Invalid ${NODE_STATUS_FILENAME} at ${source}: verification.${mechanism}.overall is invalid`
|
|
9390
|
+
);
|
|
9391
|
+
}
|
|
9392
|
+
const parsed = {
|
|
9393
|
+
[NODE_STATUS_FIELD.OVERALL]: overall
|
|
9394
|
+
};
|
|
9395
|
+
const outcomes = {};
|
|
9396
|
+
for (const [reference, outcome] of Object.entries(candidate)) {
|
|
9397
|
+
if (reference === NODE_STATUS_FIELD.OVERALL) continue;
|
|
9398
|
+
if (!isNodeStatusEvidenceOutcome(outcome)) {
|
|
9399
|
+
throw new Error(
|
|
9400
|
+
`Invalid ${NODE_STATUS_FILENAME} at ${source}: verification.${mechanism}.${reference} is invalid`
|
|
9401
|
+
);
|
|
9402
|
+
}
|
|
9403
|
+
outcomes[reference] = outcome;
|
|
9404
|
+
parsed[reference] = outcome;
|
|
9405
|
+
}
|
|
9406
|
+
const expectedOverall = rollupNodeStatusMechanism(outcomes);
|
|
9407
|
+
if (overall !== expectedOverall) {
|
|
9185
9408
|
throw new Error(
|
|
9186
|
-
`Invalid ${NODE_STATUS_FILENAME} at ${
|
|
9409
|
+
`Invalid ${NODE_STATUS_FILENAME} at ${source}: verification.${mechanism}.overall does not match evidence outcomes`
|
|
9187
9410
|
);
|
|
9188
9411
|
}
|
|
9189
|
-
return
|
|
9412
|
+
return parsed;
|
|
9190
9413
|
}
|
|
9191
9414
|
|
|
9192
9415
|
// src/lib/node-status/provider.ts
|
|
@@ -9194,56 +9417,194 @@ function nodeDirectory(productDir, node) {
|
|
|
9194
9417
|
return join20(productDir, SPEC_TREE_CONFIG.ROOT_DIRECTORY, node.id);
|
|
9195
9418
|
}
|
|
9196
9419
|
function createNodeStatusProvider(productDir) {
|
|
9420
|
+
const ignoreReader = createIgnoreSourceReader(productDir, {
|
|
9421
|
+
ignoreSourceFilename: IGNORE_SOURCE_FILENAME_DEFAULT,
|
|
9422
|
+
specTreeRootSegment: SPEC_TREE_CONFIG.ROOT_DIRECTORY
|
|
9423
|
+
});
|
|
9197
9424
|
return {
|
|
9198
9425
|
stateForNode(node) {
|
|
9199
|
-
|
|
9426
|
+
const status = readNodeStatus(nodeDirectory(productDir, node));
|
|
9427
|
+
if (status === void 0) return void 0;
|
|
9428
|
+
return classifyNodeStatus({
|
|
9429
|
+
hasVerificationReferences: hasNodeStatusVerificationReferences(status.verification),
|
|
9430
|
+
isExcluded: isNodeStatusEntryExcluded(ignoreReader, node),
|
|
9431
|
+
verification: status.verification
|
|
9432
|
+
});
|
|
9433
|
+
}
|
|
9434
|
+
};
|
|
9435
|
+
}
|
|
9436
|
+
|
|
9437
|
+
// src/lib/node-status/update.ts
|
|
9438
|
+
import { mkdir as mkdir4, readdir as readdir7, rm, writeFile as writeFile2 } from "fs/promises";
|
|
9439
|
+
import { dirname as dirname6, join as join21 } from "path";
|
|
9440
|
+
var NODE_STATUS_TEXT_ENCODING = "utf8";
|
|
9441
|
+
async function updateNodeStatus(options) {
|
|
9442
|
+
const { productDir, resolveOutcome } = options;
|
|
9443
|
+
const snapshot = await readSpecTree({ source: createFilesystemSpecTreeSource({ productDir }) });
|
|
9444
|
+
const ignoreReader = createIgnoreSourceReader(productDir, {
|
|
9445
|
+
ignoreSourceFilename: IGNORE_SOURCE_FILENAME_DEFAULT,
|
|
9446
|
+
specTreeRootSegment: SPEC_TREE_CONFIG.ROOT_DIRECTORY
|
|
9447
|
+
});
|
|
9448
|
+
const evidenceByNode = collectEvidenceByNode(snapshot);
|
|
9449
|
+
const liveStatusPaths = /* @__PURE__ */ new Set();
|
|
9450
|
+
for (const node of snapshot.allNodes) {
|
|
9451
|
+
const evidence = evidenceByNode.get(node.id) ?? [];
|
|
9452
|
+
const verification = await resolveVerification(node, {
|
|
9453
|
+
evidence,
|
|
9454
|
+
isExcluded: isNodeStatusEntryExcluded(ignoreReader, node),
|
|
9455
|
+
resolveOutcome
|
|
9456
|
+
});
|
|
9457
|
+
const statusPath = nodeStatusPath(productDir, node.id);
|
|
9458
|
+
liveStatusPaths.add(statusPath);
|
|
9459
|
+
await writeNodeStatus(statusPath, verification);
|
|
9460
|
+
}
|
|
9461
|
+
await removeStaleNodeStatusFiles(productDir, liveStatusPaths);
|
|
9462
|
+
}
|
|
9463
|
+
async function resolveVerification(node, input) {
|
|
9464
|
+
if (input.evidence.length === 0) return {};
|
|
9465
|
+
if (input.isExcluded) return createTestVerification(input.evidence, NODE_STATUS_EVIDENCE_OUTCOME.NOT_RUN);
|
|
9466
|
+
return createTestVerificationFromOutcomes(
|
|
9467
|
+
input.evidence,
|
|
9468
|
+
await input.resolveOutcome(node.id, evidencePaths(input.evidence))
|
|
9469
|
+
);
|
|
9470
|
+
}
|
|
9471
|
+
function collectEvidenceByNode(snapshot) {
|
|
9472
|
+
const evidenceByNode = /* @__PURE__ */ new Map();
|
|
9473
|
+
for (const entry of snapshot.entries) {
|
|
9474
|
+
if (entry.type === SPEC_TREE_ENTRY_TYPE.EVIDENCE) {
|
|
9475
|
+
const entries = evidenceByNode.get(entry.parentId) ?? [];
|
|
9476
|
+
entries.push(entry);
|
|
9477
|
+
evidenceByNode.set(entry.parentId, entries);
|
|
9478
|
+
}
|
|
9479
|
+
}
|
|
9480
|
+
return evidenceByNode;
|
|
9481
|
+
}
|
|
9482
|
+
function createTestVerification(evidence, outcome) {
|
|
9483
|
+
const outcomes = Object.fromEntries(evidence.map((entry) => [evidencePath(entry), outcome]));
|
|
9484
|
+
return createTestVerificationFromOutcomes(evidence, outcomes);
|
|
9485
|
+
}
|
|
9486
|
+
function createTestVerificationFromOutcomes(evidence, resolvedOutcomes) {
|
|
9487
|
+
const outcomes = {};
|
|
9488
|
+
for (const entry of evidence) {
|
|
9489
|
+
const path7 = evidencePath(entry);
|
|
9490
|
+
outcomes[path7] = resolvedOutcomes[path7] ?? NODE_STATUS_EVIDENCE_OUTCOME.NOT_RUN;
|
|
9491
|
+
}
|
|
9492
|
+
return { [NODE_STATUS_VERIFICATION_MECHANISM.TEST]: createNodeStatusMechanismRecord(outcomes) };
|
|
9493
|
+
}
|
|
9494
|
+
function evidencePaths(evidence) {
|
|
9495
|
+
return evidence.map(evidencePath);
|
|
9496
|
+
}
|
|
9497
|
+
function evidencePath(entry) {
|
|
9498
|
+
return entry.ref?.path ?? entry.id;
|
|
9499
|
+
}
|
|
9500
|
+
async function writeNodeStatus(filePath, verification) {
|
|
9501
|
+
await mkdir4(dirname6(filePath), { recursive: true });
|
|
9502
|
+
await writeFile2(filePath, serializeNodeStatus(createNodeStatusFile(verification)), NODE_STATUS_TEXT_ENCODING);
|
|
9503
|
+
}
|
|
9504
|
+
function nodeStatusPath(productDir, nodeId) {
|
|
9505
|
+
return join21(productDir, SPEC_TREE_CONFIG.ROOT_DIRECTORY, nodeId, NODE_STATUS_FILENAME);
|
|
9506
|
+
}
|
|
9507
|
+
async function removeStaleNodeStatusFiles(productDir, liveStatusPaths) {
|
|
9508
|
+
const statusPaths = await collectNodeStatusFiles(join21(productDir, SPEC_TREE_CONFIG.ROOT_DIRECTORY));
|
|
9509
|
+
await Promise.all(statusPaths.filter((path7) => !liveStatusPaths.has(path7)).map((path7) => rm(path7, { force: true })));
|
|
9510
|
+
}
|
|
9511
|
+
async function collectNodeStatusFiles(directory) {
|
|
9512
|
+
const entries = await readDirectoryEntries(directory);
|
|
9513
|
+
const statusPaths = [];
|
|
9514
|
+
for (const entry of entries) {
|
|
9515
|
+
const entryPath = join21(directory, entry.name);
|
|
9516
|
+
if (entry.isDirectory()) {
|
|
9517
|
+
statusPaths.push(...await collectNodeStatusFiles(entryPath));
|
|
9518
|
+
} else if (entry.isFile() && entry.name === NODE_STATUS_FILENAME) {
|
|
9519
|
+
statusPaths.push(entryPath);
|
|
9520
|
+
}
|
|
9521
|
+
}
|
|
9522
|
+
return statusPaths;
|
|
9523
|
+
}
|
|
9524
|
+
async function readDirectoryEntries(directory) {
|
|
9525
|
+
try {
|
|
9526
|
+
return await readdir7(directory, { withFileTypes: true });
|
|
9527
|
+
} catch (error) {
|
|
9528
|
+
if (isNodeError3(error) && error.code === "ENOENT") return [];
|
|
9529
|
+
throw error;
|
|
9530
|
+
}
|
|
9531
|
+
}
|
|
9532
|
+
function isNodeError3(error) {
|
|
9533
|
+
return error instanceof Error && "code" in error;
|
|
9534
|
+
}
|
|
9535
|
+
|
|
9536
|
+
// src/commands/spec/node-outcome-resolver.ts
|
|
9537
|
+
var PATH_SEPARATOR = "/";
|
|
9538
|
+
var SUCCESS_EXIT_CODE2 = 0;
|
|
9539
|
+
function createNodeOutcomeResolver(deps) {
|
|
9540
|
+
let evidence;
|
|
9541
|
+
const currentInputsByCoveredPaths = /* @__PURE__ */ new Map();
|
|
9542
|
+
const sharedEvidence = () => evidence ??= loadResolverEvidence(deps);
|
|
9543
|
+
const refreshEvidence = () => {
|
|
9544
|
+
evidence = loadResolverEvidence(deps);
|
|
9545
|
+
};
|
|
9546
|
+
const currentInputsFor = (coveredPaths) => {
|
|
9547
|
+
const cacheKey = coveredPathCollectionKey(coveredPaths);
|
|
9548
|
+
const cached = currentInputsByCoveredPaths.get(cacheKey);
|
|
9549
|
+
if (cached !== void 0) {
|
|
9550
|
+
return cached;
|
|
9200
9551
|
}
|
|
9552
|
+
const current = currentStalenessInputs(deps.productDir, coveredPaths, deps);
|
|
9553
|
+
currentInputsByCoveredPaths.set(cacheKey, current);
|
|
9554
|
+
return current;
|
|
9201
9555
|
};
|
|
9556
|
+
return async (nodeId, evidencePaths2) => {
|
|
9557
|
+
const { discoveredTestPaths, terminalRuns } = await sharedEvidence();
|
|
9558
|
+
const nodePath = `${SPEC_TREE_CONFIG.ROOT_DIRECTORY}${PATH_SEPARATOR}${nodeId}`;
|
|
9559
|
+
const nodeTestPaths = filterNodeTestPaths(discoveredTestPaths, nodePath, evidencePaths2);
|
|
9560
|
+
const usable = await usableRecordedOutcome(discoveredTestPaths, terminalRuns, nodeTestPaths, currentInputsFor);
|
|
9561
|
+
if (usable !== void 0) {
|
|
9562
|
+
return usable;
|
|
9563
|
+
}
|
|
9564
|
+
const { dispatch } = await runNodeCommand({ productDir: deps.productDir, nodePath }, deps);
|
|
9565
|
+
refreshEvidence();
|
|
9566
|
+
return outcomesForPaths(dispatch.outcomes, nodeTestPaths);
|
|
9567
|
+
};
|
|
9568
|
+
}
|
|
9569
|
+
function coveredPathCollectionKey(coveredPaths) {
|
|
9570
|
+
return JSON.stringify([...coveredPaths].sort(compareAsciiStrings));
|
|
9202
9571
|
}
|
|
9203
|
-
|
|
9204
|
-
|
|
9205
|
-
|
|
9206
|
-
|
|
9207
|
-
var NODE_STATUS_TEXT_ENCODING = "utf8";
|
|
9208
|
-
async function updateNodeStatus(options) {
|
|
9209
|
-
const { productDir, resolveOutcome } = options;
|
|
9210
|
-
const snapshot = await readSpecTree({ source: createFilesystemSpecTreeSource({ productDir }) });
|
|
9211
|
-
const ignoreReader = createIgnoreSourceReader(productDir, {
|
|
9212
|
-
ignoreSourceFilename: IGNORE_SOURCE_FILENAME_DEFAULT,
|
|
9213
|
-
specTreeRootSegment: SPEC_TREE_CONFIG.ROOT_DIRECTORY
|
|
9214
|
-
});
|
|
9215
|
-
const nodesWithTests = collectNodesWithTests(snapshot);
|
|
9216
|
-
for (const node of snapshot.allNodes) {
|
|
9217
|
-
const facts = await classifyFacts(node, {
|
|
9218
|
-
hasTests: nodesWithTests.has(node.id),
|
|
9219
|
-
isExcluded: isNodeExcluded(ignoreReader, node),
|
|
9220
|
-
resolveOutcome
|
|
9221
|
-
});
|
|
9222
|
-
await writeNodeStatus(productDir, node.id, classifyNodeStatus(facts));
|
|
9223
|
-
}
|
|
9572
|
+
async function loadResolverEvidence(deps) {
|
|
9573
|
+
const discoveredTestPaths = await discoverTestFiles(deps.productDir);
|
|
9574
|
+
const runs = await readTestingRuns(deps.productDir, deps);
|
|
9575
|
+
return { discoveredTestPaths, terminalRuns: runs.ok ? runs.value.terminalRuns : [] };
|
|
9224
9576
|
}
|
|
9225
|
-
|
|
9226
|
-
const
|
|
9227
|
-
|
|
9577
|
+
function filterNodeTestPaths(discoveredTestPaths, nodePath, evidencePaths2) {
|
|
9578
|
+
const prefix = `${nodePath}${PATH_SEPARATOR}`;
|
|
9579
|
+
const discovered = new Set(discoveredTestPaths.filter((path7) => path7.startsWith(prefix)));
|
|
9580
|
+
return evidencePaths2.filter((path7) => discovered.has(path7));
|
|
9228
9581
|
}
|
|
9229
|
-
function
|
|
9230
|
-
const
|
|
9231
|
-
|
|
9232
|
-
|
|
9233
|
-
|
|
9234
|
-
|
|
9582
|
+
async function usableRecordedOutcome(discoveredTestPaths, terminalRuns, nodeTestPaths, currentInputsFor) {
|
|
9583
|
+
const latest = selectLatestTerminalTestRunForNode(terminalRuns, nodeTestPaths);
|
|
9584
|
+
if (latest === void 0) {
|
|
9585
|
+
return void 0;
|
|
9586
|
+
}
|
|
9587
|
+
const runCoveredPaths = latest.state.runnerOutcomes.flatMap((outcome) => outcome.testPaths);
|
|
9588
|
+
const presentTestPaths = new Set(discoveredTestPaths);
|
|
9589
|
+
if (!runCoveredPaths.every((path7) => presentTestPaths.has(path7))) {
|
|
9590
|
+
return void 0;
|
|
9235
9591
|
}
|
|
9236
|
-
|
|
9592
|
+
const current = await currentInputsFor(runCoveredPaths);
|
|
9593
|
+
const fresh = isStalenessMatch(extractStalenessInputs(latest.state), current);
|
|
9594
|
+
return fresh && latest.state.status === TEST_RUN_STATE_STATUS.PASSED ? outcomesForPaths(latest.state.runnerOutcomes, nodeTestPaths) : void 0;
|
|
9237
9595
|
}
|
|
9238
|
-
function
|
|
9239
|
-
|
|
9240
|
-
|
|
9241
|
-
|
|
9596
|
+
function outcomesForPaths(outcomes, nodeTestPaths) {
|
|
9597
|
+
return Object.fromEntries(
|
|
9598
|
+
nodeTestPaths.map((path7) => [path7, outcomeForPath(outcomes, path7)])
|
|
9599
|
+
);
|
|
9242
9600
|
}
|
|
9243
|
-
|
|
9244
|
-
const
|
|
9245
|
-
|
|
9246
|
-
|
|
9601
|
+
function outcomeForPath(outcomes, path7) {
|
|
9602
|
+
const covering = outcomes.filter((outcome) => outcome.testPaths.includes(path7));
|
|
9603
|
+
if (covering.length === 0) return NODE_STATUS_EVIDENCE_OUTCOME.NOT_RUN;
|
|
9604
|
+
if (covering.every((outcome) => outcome.exitCode === SUCCESS_EXIT_CODE2)) {
|
|
9605
|
+
return NODE_STATUS_EVIDENCE_OUTCOME.PASSED;
|
|
9606
|
+
}
|
|
9607
|
+
return NODE_STATUS_EVIDENCE_OUTCOME.FAILED;
|
|
9247
9608
|
}
|
|
9248
9609
|
|
|
9249
9610
|
// src/commands/spec/status.ts
|
|
@@ -9954,8 +10315,9 @@ function createTestingDomain(deps = defaultTestingCliDependencies) {
|
|
|
9954
10315
|
}
|
|
9955
10316
|
var testingDomain = createTestingDomain();
|
|
9956
10317
|
|
|
9957
|
-
// src/commands/validation/
|
|
9958
|
-
import {
|
|
10318
|
+
// src/commands/validation/formatting.ts
|
|
10319
|
+
import { existsSync } from "fs";
|
|
10320
|
+
import { isAbsolute as isAbsolute3, join as join24, relative as relative3 } from "path";
|
|
9959
10321
|
|
|
9960
10322
|
// src/validation/config/path-filter.ts
|
|
9961
10323
|
import { isAbsolute as isAbsolute2, relative as relative2 } from "path";
|
|
@@ -10052,9 +10414,180 @@ function applyValidationPathFilterToScope(scopeConfig, filter) {
|
|
|
10052
10414
|
};
|
|
10053
10415
|
}
|
|
10054
10416
|
|
|
10417
|
+
// src/validation/steps/subprocess-output.ts
|
|
10418
|
+
var VALIDATION_SUBPROCESS_EVENTS = {
|
|
10419
|
+
CLOSE: "close",
|
|
10420
|
+
DATA: "data",
|
|
10421
|
+
DRAIN: "drain",
|
|
10422
|
+
ERROR: "error"
|
|
10423
|
+
};
|
|
10424
|
+
var defaultValidationSubprocessOutputStreams = {
|
|
10425
|
+
stdout: process.stdout,
|
|
10426
|
+
stderr: process.stderr
|
|
10427
|
+
};
|
|
10428
|
+
function forwardValidationSubprocessOutput(child, streams = defaultValidationSubprocessOutputStreams) {
|
|
10429
|
+
child.stdout?.on(VALIDATION_SUBPROCESS_EVENTS.DATA, (chunk) => {
|
|
10430
|
+
forwardChunkWithBackpressure(child.stdout, streams.stdout, chunk);
|
|
10431
|
+
});
|
|
10432
|
+
child.stderr?.on(VALIDATION_SUBPROCESS_EVENTS.DATA, (chunk) => {
|
|
10433
|
+
forwardChunkWithBackpressure(child.stderr, streams.stderr, chunk);
|
|
10434
|
+
});
|
|
10435
|
+
}
|
|
10436
|
+
function forwardChunkWithBackpressure(source, stream, chunk) {
|
|
10437
|
+
const ready = stream.write(chunk);
|
|
10438
|
+
if (ready || stream.once === void 0 || source === null || source === void 0) {
|
|
10439
|
+
return;
|
|
10440
|
+
}
|
|
10441
|
+
source.pause();
|
|
10442
|
+
stream.once(VALIDATION_SUBPROCESS_EVENTS.DRAIN, () => {
|
|
10443
|
+
source.resume();
|
|
10444
|
+
});
|
|
10445
|
+
}
|
|
10446
|
+
|
|
10447
|
+
// src/validation/steps/formatting.ts
|
|
10448
|
+
var DPRINT_COMMAND = "dprint";
|
|
10449
|
+
var DPRINT_CHECK_SUBCOMMAND = "check";
|
|
10450
|
+
var DPRINT_CONFIG_FILENAME = "dprint.jsonc";
|
|
10451
|
+
var defaultFormattingProcessRunner = lifecycleProcessRunner;
|
|
10452
|
+
function buildDprintCheckArgs(options) {
|
|
10453
|
+
return [DPRINT_CHECK_SUBCOMMAND, ...options.files ?? []];
|
|
10454
|
+
}
|
|
10455
|
+
async function validateFormatting(context, runner = defaultFormattingProcessRunner) {
|
|
10456
|
+
const args = buildDprintCheckArgs({ files: context.files });
|
|
10457
|
+
return new Promise((resolve8) => {
|
|
10458
|
+
const child = spawnManagedSubprocess(runner, DPRINT_COMMAND, args, { cwd: context.projectRoot });
|
|
10459
|
+
const chunks = [];
|
|
10460
|
+
const capture = (chunk) => {
|
|
10461
|
+
chunks.push(typeof chunk === "string" ? chunk : Buffer.from(chunk).toString());
|
|
10462
|
+
};
|
|
10463
|
+
child.stdout?.on(VALIDATION_SUBPROCESS_EVENTS.DATA, capture);
|
|
10464
|
+
child.stderr?.on(VALIDATION_SUBPROCESS_EVENTS.DATA, capture);
|
|
10465
|
+
child.on(VALIDATION_SUBPROCESS_EVENTS.CLOSE, (code) => {
|
|
10466
|
+
resolve8({ success: code === 0, output: chunks.join("") });
|
|
10467
|
+
});
|
|
10468
|
+
child.on(VALIDATION_SUBPROCESS_EVENTS.ERROR, (error) => {
|
|
10469
|
+
resolve8({ success: false, output: chunks.join(""), error: error.message });
|
|
10470
|
+
});
|
|
10471
|
+
});
|
|
10472
|
+
}
|
|
10473
|
+
|
|
10474
|
+
// src/commands/validation/messages.ts
|
|
10475
|
+
var VALIDATION_STAGE_DISPLAY_NAMES = {
|
|
10476
|
+
CIRCULAR: "dependency-cruiser",
|
|
10477
|
+
KNIP: "Knip",
|
|
10478
|
+
ESLINT: "ESLint",
|
|
10479
|
+
TYPESCRIPT: "TypeScript",
|
|
10480
|
+
MARKDOWN: "Markdown",
|
|
10481
|
+
LITERAL: "Literal",
|
|
10482
|
+
FORMATTING: "Formatting"
|
|
10483
|
+
};
|
|
10484
|
+
var VALIDATION_SKIP_LABELS = {
|
|
10485
|
+
VERB: "Skipping",
|
|
10486
|
+
CIRCULAR_REASON: "skip-circular",
|
|
10487
|
+
LITERAL_REASON: "skip-literal",
|
|
10488
|
+
DISABLED_BY_PREFIX: "disabled by",
|
|
10489
|
+
TYPESCRIPT_ABSENT_REASON: "TypeScript not detected in project",
|
|
10490
|
+
VALIDATION_PATHS_NO_TARGETS_REASON: "validation paths matched no files",
|
|
10491
|
+
MARKDOWN_NO_SCOPE_REASON: "no markdown files in --files scope",
|
|
10492
|
+
MARKDOWN_NO_DEFAULT_DIRECTORIES_REASON: "no spx/ or docs/ directories found"
|
|
10493
|
+
};
|
|
10494
|
+
var VALIDATION_COMMAND_OUTPUT = {
|
|
10495
|
+
CIRCULAR_FOUND: `${VALIDATION_STAGE_DISPLAY_NAMES.CIRCULAR}: circular dependencies found`,
|
|
10496
|
+
CIRCULAR_NONE_FOUND: `${VALIDATION_STAGE_DISPLAY_NAMES.CIRCULAR}: \u2713 No circular dependencies found`,
|
|
10497
|
+
KNIP_CONFIG_ERROR: `${VALIDATION_STAGE_DISPLAY_NAMES.KNIP}: \u2717 config error`,
|
|
10498
|
+
KNIP_DISABLED: `${VALIDATION_STAGE_DISPLAY_NAMES.KNIP}: skipped (${VALIDATION_SKIP_LABELS.DISABLED_BY_PREFIX} validation.knip.enabled)`,
|
|
10499
|
+
KNIP_SUCCESS: `${VALIDATION_STAGE_DISPLAY_NAMES.KNIP}: \u2713 No unused code found`,
|
|
10500
|
+
KNIP_FAILURE: "Unused code found",
|
|
10501
|
+
ESLINT_SUCCESS: `${VALIDATION_STAGE_DISPLAY_NAMES.ESLINT}: \u2713 No errors found`,
|
|
10502
|
+
ESLINT_FAILURE: `${VALIDATION_STAGE_DISPLAY_NAMES.ESLINT} validation failed`,
|
|
10503
|
+
ESLINT_MISSING_CONFIG: "ESLint config not found: project has tsconfig.json but no eslint.config.{ts,js,mjs,cjs}",
|
|
10504
|
+
TYPESCRIPT_SUCCESS: `${VALIDATION_STAGE_DISPLAY_NAMES.TYPESCRIPT}: \u2713 No type errors`,
|
|
10505
|
+
TYPESCRIPT_FAILURE: `${VALIDATION_STAGE_DISPLAY_NAMES.TYPESCRIPT} validation failed`,
|
|
10506
|
+
MARKDOWN_NO_ISSUES: `${VALIDATION_STAGE_DISPLAY_NAMES.MARKDOWN}: No issues found`,
|
|
10507
|
+
MARKDOWN_ERROR_SUMMARY_SUFFIX: "error(s) found",
|
|
10508
|
+
FORMATTING_NO_ISSUES: `${VALIDATION_STAGE_DISPLAY_NAMES.FORMATTING}: \u2713 No formatting issues found`,
|
|
10509
|
+
FORMATTING_FAILURE_SUMMARY: `${VALIDATION_STAGE_DISPLAY_NAMES.FORMATTING}: unformatted files found`
|
|
10510
|
+
};
|
|
10511
|
+
var CIRCULAR_SKIP_OUTPUT = `${VALIDATION_STAGE_DISPLAY_NAMES.CIRCULAR}: skipped (--${VALIDATION_SKIP_LABELS.CIRCULAR_REASON})`;
|
|
10512
|
+
var CIRCULAR_SKIP_JSON_OUTPUT = JSON.stringify({
|
|
10513
|
+
skipped: true,
|
|
10514
|
+
reason: VALIDATION_SKIP_LABELS.CIRCULAR_REASON
|
|
10515
|
+
});
|
|
10516
|
+
var LITERAL_SKIP_OUTPUT = `${VALIDATION_STAGE_DISPLAY_NAMES.LITERAL}: skipped (--${VALIDATION_SKIP_LABELS.LITERAL_REASON})`;
|
|
10517
|
+
var LITERAL_SKIP_JSON_OUTPUT = JSON.stringify({
|
|
10518
|
+
skipped: true,
|
|
10519
|
+
reason: VALIDATION_SKIP_LABELS.LITERAL_REASON
|
|
10520
|
+
});
|
|
10521
|
+
function formatTypeScriptAbsentSkipMessage(stageName) {
|
|
10522
|
+
return `\u23ED ${VALIDATION_SKIP_LABELS.VERB} ${stageName} (${VALIDATION_SKIP_LABELS.TYPESCRIPT_ABSENT_REASON})`;
|
|
10523
|
+
}
|
|
10524
|
+
function formatValidationPathsNoTargetsSkipMessage(stageName) {
|
|
10525
|
+
return `\u23ED ${VALIDATION_SKIP_LABELS.VERB} ${stageName} (${VALIDATION_SKIP_LABELS.VALIDATION_PATHS_NO_TARGETS_REASON})`;
|
|
10526
|
+
}
|
|
10527
|
+
|
|
10528
|
+
// src/commands/validation/formatting.ts
|
|
10529
|
+
var FORMATTING_COMMAND_OUTPUT = {
|
|
10530
|
+
NO_ISSUES: VALIDATION_COMMAND_OUTPUT.FORMATTING_NO_ISSUES,
|
|
10531
|
+
FAILURE_SUMMARY: VALIDATION_COMMAND_OUTPUT.FORMATTING_FAILURE_SUMMARY,
|
|
10532
|
+
EMPTY_SCOPE_REASON: "no files in scope",
|
|
10533
|
+
NO_CONFIG_SKIP_REASON: `no ${DPRINT_CONFIG_FILENAME} at product root`
|
|
10534
|
+
};
|
|
10535
|
+
var FORMATTING_CONFIG_ERROR_MESSAGE = `${VALIDATION_STAGE_DISPLAY_NAMES.FORMATTING}: \u2717 config error`;
|
|
10536
|
+
async function formattingCommand(options) {
|
|
10537
|
+
const { cwd, files, quiet } = options;
|
|
10538
|
+
const startTime = Date.now();
|
|
10539
|
+
const loaded = await resolveConfig(cwd, [validationConfigDescriptor]);
|
|
10540
|
+
if (!loaded.ok) {
|
|
10541
|
+
return {
|
|
10542
|
+
exitCode: 1,
|
|
10543
|
+
output: `${FORMATTING_CONFIG_ERROR_MESSAGE} \u2014 ${loaded.error}`,
|
|
10544
|
+
durationMs: Date.now() - startTime
|
|
10545
|
+
};
|
|
10546
|
+
}
|
|
10547
|
+
const validationConfig = loaded.value[validationConfigDescriptor.section];
|
|
10548
|
+
if (!existsSync(join24(cwd, DPRINT_CONFIG_FILENAME))) {
|
|
10549
|
+
const output2 = quiet ? "" : `${VALIDATION_STAGE_DISPLAY_NAMES.FORMATTING}: skipped (${FORMATTING_COMMAND_OUTPUT.NO_CONFIG_SKIP_REASON})`;
|
|
10550
|
+
return { exitCode: 0, output: output2, durationMs: Date.now() - startTime };
|
|
10551
|
+
}
|
|
10552
|
+
const pathFilter = validationPathFilterForTool(
|
|
10553
|
+
validationConfig.paths,
|
|
10554
|
+
VALIDATION_PATH_TOOL_SUBSECTIONS.FORMATTING
|
|
10555
|
+
);
|
|
10556
|
+
const hasExplicitScope = files !== void 0 && files.length > 0;
|
|
10557
|
+
const scopedFiles = hasExplicitScope ? files.map((filePath) => isAbsolute3(filePath) ? relative3(cwd, filePath) : filePath).filter((relativePath) => pathPassesValidationFilter(relativePath, pathFilter)) : void 0;
|
|
10558
|
+
if (hasExplicitScope && (scopedFiles === void 0 || scopedFiles.length === 0)) {
|
|
10559
|
+
const output2 = quiet ? "" : `${VALIDATION_STAGE_DISPLAY_NAMES.FORMATTING}: skipped (${FORMATTING_COMMAND_OUTPUT.EMPTY_SCOPE_REASON})`;
|
|
10560
|
+
return { exitCode: 0, output: output2, durationMs: Date.now() - startTime };
|
|
10561
|
+
}
|
|
10562
|
+
const result = await validateFormatting({ projectRoot: cwd, files: scopedFiles });
|
|
10563
|
+
const durationMs = Date.now() - startTime;
|
|
10564
|
+
if (result.success) {
|
|
10565
|
+
return { exitCode: 0, output: quiet ? "" : FORMATTING_COMMAND_OUTPUT.NO_ISSUES, durationMs };
|
|
10566
|
+
}
|
|
10567
|
+
const detail = result.error ?? result.output;
|
|
10568
|
+
const output = [FORMATTING_COMMAND_OUTPUT.FAILURE_SUMMARY, detail].filter((line) => line.length > 0).join("\n");
|
|
10569
|
+
return { exitCode: 1, output, durationMs };
|
|
10570
|
+
}
|
|
10571
|
+
|
|
10572
|
+
// src/validation/languages/formatting.ts
|
|
10573
|
+
var FORMATTING_LANGUAGE_NAME = "formatting";
|
|
10574
|
+
var formattingValidationLanguage = {
|
|
10575
|
+
name: FORMATTING_LANGUAGE_NAME,
|
|
10576
|
+
stages: [
|
|
10577
|
+
{
|
|
10578
|
+
name: VALIDATION_STAGE_DISPLAY_NAMES.FORMATTING,
|
|
10579
|
+
failsPipeline: true,
|
|
10580
|
+
run: (context) => formattingCommand({ cwd: context.cwd, files: context.files, quiet: context.quiet })
|
|
10581
|
+
}
|
|
10582
|
+
]
|
|
10583
|
+
};
|
|
10584
|
+
|
|
10585
|
+
// src/commands/validation/markdown.ts
|
|
10586
|
+
import { relative as relative4 } from "path";
|
|
10587
|
+
|
|
10055
10588
|
// src/validation/steps/markdown.ts
|
|
10056
|
-
import { existsSync, statSync } from "fs";
|
|
10057
|
-
import { basename as basename4, dirname as dirname8, join as
|
|
10589
|
+
import { existsSync as existsSync2, statSync } from "fs";
|
|
10590
|
+
import { basename as basename4, dirname as dirname8, join as join25, relative as pathRelative } from "path";
|
|
10058
10591
|
import { main as markdownlintMain } from "markdownlint-cli2";
|
|
10059
10592
|
import relativeLinksRule from "markdownlint-rule-relative-links";
|
|
10060
10593
|
var MARKDOWN_DEFAULT_DIRECTORY_NAMES = ["spx", "docs"];
|
|
@@ -10093,7 +10626,7 @@ function buildMarkdownlintConfig(directoryName) {
|
|
|
10093
10626
|
};
|
|
10094
10627
|
}
|
|
10095
10628
|
function getDefaultDirectories(projectRoot) {
|
|
10096
|
-
return MARKDOWN_DEFAULT_DIRECTORY_NAMES.map((name) =>
|
|
10629
|
+
return MARKDOWN_DEFAULT_DIRECTORY_NAMES.map((name) => join25(projectRoot, name)).filter((dir) => existsSync2(dir));
|
|
10097
10630
|
}
|
|
10098
10631
|
function resolveMarkdownValidationTarget(path7, deps = defaultMarkdownValidationTargetDeps) {
|
|
10099
10632
|
if (isExistingDirectory(path7, deps)) {
|
|
@@ -10176,7 +10709,7 @@ async function validateTarget(target, config, projectRoot, ignoreGlobs = []) {
|
|
|
10176
10709
|
if (parsed) {
|
|
10177
10710
|
errors.push({
|
|
10178
10711
|
...parsed,
|
|
10179
|
-
file:
|
|
10712
|
+
file: join25(directory, parsed.file)
|
|
10180
10713
|
});
|
|
10181
10714
|
}
|
|
10182
10715
|
}
|
|
@@ -10215,57 +10748,6 @@ function markdownlintConfigDirectoryName(directory, projectRoot) {
|
|
|
10215
10748
|
return basename4(directory);
|
|
10216
10749
|
}
|
|
10217
10750
|
|
|
10218
|
-
// src/commands/validation/messages.ts
|
|
10219
|
-
var VALIDATION_STAGE_DISPLAY_NAMES = {
|
|
10220
|
-
CIRCULAR: "dependency-cruiser",
|
|
10221
|
-
KNIP: "Knip",
|
|
10222
|
-
ESLINT: "ESLint",
|
|
10223
|
-
TYPESCRIPT: "TypeScript",
|
|
10224
|
-
MARKDOWN: "Markdown",
|
|
10225
|
-
LITERAL: "Literal"
|
|
10226
|
-
};
|
|
10227
|
-
var VALIDATION_SKIP_LABELS = {
|
|
10228
|
-
VERB: "Skipping",
|
|
10229
|
-
CIRCULAR_REASON: "skip-circular",
|
|
10230
|
-
LITERAL_REASON: "skip-literal",
|
|
10231
|
-
DISABLED_BY_PREFIX: "disabled by",
|
|
10232
|
-
TYPESCRIPT_ABSENT_REASON: "TypeScript not detected in project",
|
|
10233
|
-
VALIDATION_PATHS_NO_TARGETS_REASON: "validation paths matched no files",
|
|
10234
|
-
MARKDOWN_NO_SCOPE_REASON: "no markdown files in --files scope",
|
|
10235
|
-
MARKDOWN_NO_DEFAULT_DIRECTORIES_REASON: "no spx/ or docs/ directories found"
|
|
10236
|
-
};
|
|
10237
|
-
var VALIDATION_COMMAND_OUTPUT = {
|
|
10238
|
-
CIRCULAR_FOUND: `${VALIDATION_STAGE_DISPLAY_NAMES.CIRCULAR}: circular dependencies found`,
|
|
10239
|
-
CIRCULAR_NONE_FOUND: `${VALIDATION_STAGE_DISPLAY_NAMES.CIRCULAR}: \u2713 No circular dependencies found`,
|
|
10240
|
-
KNIP_CONFIG_ERROR: `${VALIDATION_STAGE_DISPLAY_NAMES.KNIP}: \u2717 config error`,
|
|
10241
|
-
KNIP_DISABLED: `${VALIDATION_STAGE_DISPLAY_NAMES.KNIP}: skipped (${VALIDATION_SKIP_LABELS.DISABLED_BY_PREFIX} validation.knip.enabled)`,
|
|
10242
|
-
KNIP_SUCCESS: `${VALIDATION_STAGE_DISPLAY_NAMES.KNIP}: \u2713 No unused code found`,
|
|
10243
|
-
KNIP_FAILURE: "Unused code found",
|
|
10244
|
-
ESLINT_SUCCESS: `${VALIDATION_STAGE_DISPLAY_NAMES.ESLINT}: \u2713 No errors found`,
|
|
10245
|
-
ESLINT_FAILURE: `${VALIDATION_STAGE_DISPLAY_NAMES.ESLINT} validation failed`,
|
|
10246
|
-
ESLINT_MISSING_CONFIG: "ESLint config not found: project has tsconfig.json but no eslint.config.{ts,js,mjs,cjs}",
|
|
10247
|
-
TYPESCRIPT_SUCCESS: `${VALIDATION_STAGE_DISPLAY_NAMES.TYPESCRIPT}: \u2713 No type errors`,
|
|
10248
|
-
TYPESCRIPT_FAILURE: `${VALIDATION_STAGE_DISPLAY_NAMES.TYPESCRIPT} validation failed`,
|
|
10249
|
-
MARKDOWN_NO_ISSUES: `${VALIDATION_STAGE_DISPLAY_NAMES.MARKDOWN}: No issues found`,
|
|
10250
|
-
MARKDOWN_ERROR_SUMMARY_SUFFIX: "error(s) found"
|
|
10251
|
-
};
|
|
10252
|
-
var CIRCULAR_SKIP_OUTPUT = `${VALIDATION_STAGE_DISPLAY_NAMES.CIRCULAR}: skipped (--${VALIDATION_SKIP_LABELS.CIRCULAR_REASON})`;
|
|
10253
|
-
var CIRCULAR_SKIP_JSON_OUTPUT = JSON.stringify({
|
|
10254
|
-
skipped: true,
|
|
10255
|
-
reason: VALIDATION_SKIP_LABELS.CIRCULAR_REASON
|
|
10256
|
-
});
|
|
10257
|
-
var LITERAL_SKIP_OUTPUT = `${VALIDATION_STAGE_DISPLAY_NAMES.LITERAL}: skipped (--${VALIDATION_SKIP_LABELS.LITERAL_REASON})`;
|
|
10258
|
-
var LITERAL_SKIP_JSON_OUTPUT = JSON.stringify({
|
|
10259
|
-
skipped: true,
|
|
10260
|
-
reason: VALIDATION_SKIP_LABELS.LITERAL_REASON
|
|
10261
|
-
});
|
|
10262
|
-
function formatTypeScriptAbsentSkipMessage(stageName) {
|
|
10263
|
-
return `\u23ED ${VALIDATION_SKIP_LABELS.VERB} ${stageName} (${VALIDATION_SKIP_LABELS.TYPESCRIPT_ABSENT_REASON})`;
|
|
10264
|
-
}
|
|
10265
|
-
function formatValidationPathsNoTargetsSkipMessage(stageName) {
|
|
10266
|
-
return `\u23ED ${VALIDATION_SKIP_LABELS.VERB} ${stageName} (${VALIDATION_SKIP_LABELS.VALIDATION_PATHS_NO_TARGETS_REASON})`;
|
|
10267
|
-
}
|
|
10268
|
-
|
|
10269
10751
|
// src/commands/validation/markdown.ts
|
|
10270
10752
|
var MARKDOWN_COMMAND_OUTPUT = {
|
|
10271
10753
|
ERROR_SUMMARY_SUFFIX: VALIDATION_COMMAND_OUTPUT.MARKDOWN_ERROR_SUMMARY_SUFFIX,
|
|
@@ -10295,7 +10777,7 @@ async function markdownCommand(options) {
|
|
|
10295
10777
|
path: path7
|
|
10296
10778
|
}));
|
|
10297
10779
|
const targets = unfilteredTargets.filter(
|
|
10298
|
-
(target) => pathPassesValidationFilter(
|
|
10780
|
+
(target) => pathPassesValidationFilter(relative4(cwd, target.path), pathFilter)
|
|
10299
10781
|
);
|
|
10300
10782
|
const skippedTargets = targetResolutions === void 0 ? [] : targetResolutions.map((resolution) => resolution.skipped).filter((skipped) => skipped !== void 0);
|
|
10301
10783
|
const skippedOutput = quiet ? [] : skippedTargets.map(formatSkippedFileScope);
|
|
@@ -11204,8 +11686,8 @@ var ParseErrorCode;
|
|
|
11204
11686
|
})(ParseErrorCode || (ParseErrorCode = {}));
|
|
11205
11687
|
|
|
11206
11688
|
// src/validation/config/scope.ts
|
|
11207
|
-
import { existsSync as
|
|
11208
|
-
import { isAbsolute as
|
|
11689
|
+
import { existsSync as existsSync3, readdirSync, readFileSync as readFileSync3 } from "fs";
|
|
11690
|
+
import { isAbsolute as isAbsolute4, join as join26, relative as relative5, resolve as resolve6 } from "path";
|
|
11209
11691
|
var TSCONFIG_FILES = {
|
|
11210
11692
|
full: "tsconfig.json",
|
|
11211
11693
|
production: "tsconfig.production.json"
|
|
@@ -11234,7 +11716,7 @@ var TYPESCRIPT_FALLBACK_INCLUDE_PATTERNS = [
|
|
|
11234
11716
|
var TEMPORARY_TSCONFIG_PARENT_SEGMENTS = ["node_modules", ".cache", "spx"];
|
|
11235
11717
|
var defaultScopeDeps = {
|
|
11236
11718
|
readFileSync: readFileSync3,
|
|
11237
|
-
existsSync:
|
|
11719
|
+
existsSync: existsSync3,
|
|
11238
11720
|
readdirSync
|
|
11239
11721
|
};
|
|
11240
11722
|
var EXPLICIT_TYPESCRIPT_SCOPE_TARGET_KIND = {
|
|
@@ -11242,7 +11724,7 @@ var EXPLICIT_TYPESCRIPT_SCOPE_TARGET_KIND = {
|
|
|
11242
11724
|
FILE: "file"
|
|
11243
11725
|
};
|
|
11244
11726
|
function resolveProjectPath(projectRoot, path7) {
|
|
11245
|
-
return
|
|
11727
|
+
return isAbsolute4(path7) ? path7 : join26(projectRoot, path7);
|
|
11246
11728
|
}
|
|
11247
11729
|
function parseTypeScriptConfig(configPath, deps = defaultScopeDeps) {
|
|
11248
11730
|
try {
|
|
@@ -11281,12 +11763,12 @@ function hasTypeScriptFilesRecursive(dirPath, maxDepth = 2, deps = defaultScopeD
|
|
|
11281
11763
|
try {
|
|
11282
11764
|
const items = deps.readdirSync(dirPath, { withFileTypes: true });
|
|
11283
11765
|
const hasDirectTsFiles = items.some(
|
|
11284
|
-
(item) => item.isFile() && pathHasTypeScriptSourceExtension(item.name) && pathPassesTypeScriptFileDiscoveryExcludes(
|
|
11766
|
+
(item) => item.isFile() && pathHasTypeScriptSourceExtension(item.name) && pathPassesTypeScriptFileDiscoveryExcludes(join26(dirPath, item.name), options)
|
|
11285
11767
|
);
|
|
11286
11768
|
if (hasDirectTsFiles) return true;
|
|
11287
11769
|
const subdirs = items.filter((item) => item.isDirectory() && !item.name.startsWith("."));
|
|
11288
11770
|
for (const subdir of subdirs.slice(0, 5)) {
|
|
11289
|
-
if (hasTypeScriptFilesRecursive(
|
|
11771
|
+
if (hasTypeScriptFilesRecursive(join26(dirPath, subdir.name), maxDepth - 1, deps, options)) {
|
|
11290
11772
|
return true;
|
|
11291
11773
|
}
|
|
11292
11774
|
}
|
|
@@ -11323,7 +11805,7 @@ function directoryContributesTypeScriptScope(dir, config, projectRoot, deps) {
|
|
|
11323
11805
|
return false;
|
|
11324
11806
|
}
|
|
11325
11807
|
try {
|
|
11326
|
-
return hasTypeScriptFilesRecursive(
|
|
11808
|
+
return hasTypeScriptFilesRecursive(join26(projectRoot, dir), 2, deps, {
|
|
11327
11809
|
excludePatterns: config.exclude,
|
|
11328
11810
|
projectRoot
|
|
11329
11811
|
});
|
|
@@ -11552,13 +12034,13 @@ function normalizeActiveIncludePattern(pattern, projectRoot, deps) {
|
|
|
11552
12034
|
function filterActiveIncludePatterns(patterns, excludePatterns, projectRoot, deps) {
|
|
11553
12035
|
return patterns.map((pattern) => normalizeActiveIncludePattern(pattern, projectRoot, deps)).filter((pattern) => typeScriptScopePatternTargetsTypeScriptSource(pattern)).filter((pattern) => {
|
|
11554
12036
|
const topLevelDir = getLiteralTopLevelPatternDirectory(pattern);
|
|
11555
|
-
return topLevelDir === null || deps.existsSync(
|
|
12037
|
+
return topLevelDir === null || deps.existsSync(join26(projectRoot, topLevelDir));
|
|
11556
12038
|
}).filter((pattern) => pathPassesValidationFilter(pattern, { exclude: excludePatterns }));
|
|
11557
12039
|
}
|
|
11558
12040
|
function getValidationDirectories(scope2, projectRoot, deps = defaultScopeDeps) {
|
|
11559
12041
|
const config = resolveTypeScriptConfig(scope2, projectRoot, deps);
|
|
11560
12042
|
const configDirectories = getTopLevelDirectoriesWithTypeScript(config, projectRoot, deps);
|
|
11561
|
-
const existingDirectories = configDirectories.filter((dir) => deps.existsSync(
|
|
12043
|
+
const existingDirectories = configDirectories.filter((dir) => deps.existsSync(join26(projectRoot, dir)));
|
|
11562
12044
|
return existingDirectories;
|
|
11563
12045
|
}
|
|
11564
12046
|
function getTypeScriptScope(scope2, projectRoot, deps = defaultScopeDeps) {
|
|
@@ -11579,14 +12061,14 @@ function pathPassesTypeScriptScope(path7, scopeConfig) {
|
|
|
11579
12061
|
return included && !excluded;
|
|
11580
12062
|
}
|
|
11581
12063
|
function pathStaysInsideTypeScriptScopeRoot(projectRoot, path7) {
|
|
11582
|
-
const resolvedPath =
|
|
11583
|
-
const relativePath =
|
|
12064
|
+
const resolvedPath = isAbsolute4(path7) ? resolve6(path7) : resolve6(projectRoot, path7);
|
|
12065
|
+
const relativePath = relative5(projectRoot, resolvedPath);
|
|
11584
12066
|
const segments = normalizeTypeScriptScopePath(relativePath).split(PATH_SEGMENT_SEPARATOR3);
|
|
11585
|
-
return relativePath.length === 0 || !segments.includes("..") && !
|
|
12067
|
+
return relativePath.length === 0 || !segments.includes("..") && !isAbsolute4(relativePath);
|
|
11586
12068
|
}
|
|
11587
12069
|
function toProjectRelativeTypeScriptScopePath(projectRoot, path7) {
|
|
11588
|
-
const resolvedPath =
|
|
11589
|
-
const relativePath =
|
|
12070
|
+
const resolvedPath = isAbsolute4(path7) ? resolve6(path7) : resolve6(projectRoot, path7);
|
|
12071
|
+
const relativePath = relative5(projectRoot, resolvedPath);
|
|
11590
12072
|
return relativePath.length === 0 ? TYPESCRIPT_SCOPE_PROJECT_ROOT : normalizeTypeScriptScopePath(relativePath);
|
|
11591
12073
|
}
|
|
11592
12074
|
function toExplicitTypeScriptScopeTarget(projectRoot, originalPath, deps = defaultScopeDeps) {
|
|
@@ -11625,10 +12107,10 @@ function explicitTypeScriptScopeTargetPassesScope(target, scopeConfig) {
|
|
|
11625
12107
|
if (typeScriptSourcePatterns.length > 0) {
|
|
11626
12108
|
return typeScriptSourcePatterns.some((pattern) => typeScriptScopePatternIntersectsDirectory(pattern, target.path));
|
|
11627
12109
|
}
|
|
11628
|
-
return pathPassesTypeScriptScope(
|
|
12110
|
+
return pathPassesTypeScriptScope(join26(target.path, TYPESCRIPT_SCOPE_DIRECTORY_PROBE_FILENAME), scopeConfig);
|
|
11629
12111
|
}
|
|
11630
12112
|
function directoryPassesTypeScriptExcludes(directory, scopeConfig) {
|
|
11631
|
-
const probePath =
|
|
12113
|
+
const probePath = join26(directory, TYPESCRIPT_SCOPE_DIRECTORY_PROBE_FILENAME);
|
|
11632
12114
|
return !scopeConfig.excludePatterns.some(
|
|
11633
12115
|
(pattern) => typeScriptScopePatternCoversDirectorySourceSet(pattern, directory) || pathMatchesTypeScriptPattern(probePath, pattern)
|
|
11634
12116
|
);
|
|
@@ -11819,10 +12301,10 @@ function resolvedModulePath(resolvedPath) {
|
|
|
11819
12301
|
return resolvedPath;
|
|
11820
12302
|
}
|
|
11821
12303
|
}
|
|
11822
|
-
function nearestPackageRoot(filePath,
|
|
12304
|
+
function nearestPackageRoot(filePath, existsSync8) {
|
|
11823
12305
|
let currentDirectory = path6.dirname(filePath);
|
|
11824
12306
|
while (true) {
|
|
11825
|
-
if (
|
|
12307
|
+
if (existsSync8(path6.join(currentDirectory, PACKAGE_MANIFEST_FILENAME))) {
|
|
11826
12308
|
return currentDirectory;
|
|
11827
12309
|
}
|
|
11828
12310
|
const parentDirectory = path6.dirname(currentDirectory);
|
|
@@ -11832,9 +12314,9 @@ function nearestPackageRoot(filePath, existsSync7) {
|
|
|
11832
12314
|
currentDirectory = parentDirectory;
|
|
11833
12315
|
}
|
|
11834
12316
|
}
|
|
11835
|
-
function bundledToolPath(resolvedPath,
|
|
12317
|
+
function bundledToolPath(resolvedPath, existsSync8) {
|
|
11836
12318
|
const bundledFilePath = resolvedModulePath(resolvedPath);
|
|
11837
|
-
return nearestPackageRoot(bundledFilePath,
|
|
12319
|
+
return nearestPackageRoot(bundledFilePath, existsSync8) ?? path6.dirname(bundledFilePath);
|
|
11838
12320
|
}
|
|
11839
12321
|
async function discoverTool(tool, options = {}) {
|
|
11840
12322
|
const { projectRoot = process.cwd(), deps = defaultToolDiscoveryDeps } = options;
|
|
@@ -11891,7 +12373,7 @@ import {
|
|
|
11891
12373
|
cruise as dependencyCruiser
|
|
11892
12374
|
} from "dependency-cruiser";
|
|
11893
12375
|
import extractTypeScriptConfig from "dependency-cruiser/config-utl/extract-ts-config";
|
|
11894
|
-
import { join as
|
|
12376
|
+
import { join as join27 } from "path";
|
|
11895
12377
|
var DEPENDENCY_CRUISER_MODULE_SYSTEMS = ["es6", "cjs"];
|
|
11896
12378
|
var DEPENDENCY_CRUISER_TYPESCRIPT_SOURCE_GLOB_SUFFIXES = [...TYPESCRIPT_FALLBACK_INCLUDE_PATTERNS];
|
|
11897
12379
|
var DEPENDENCY_CRUISER_TYPESCRIPT_SOURCE_PATTERN = String.raw`(?<!\.d)\.(?:[cm]?ts|tsx)$`;
|
|
@@ -12131,7 +12613,7 @@ async function validateCircularDependencies(scope2, typescriptScope, projectRoot
|
|
|
12131
12613
|
if (analyzeSourcePatterns.length === 0) {
|
|
12132
12614
|
return { success: true };
|
|
12133
12615
|
}
|
|
12134
|
-
const tsConfigFile =
|
|
12616
|
+
const tsConfigFile = join27(projectRoot, TSCONFIG_FILES[scope2]);
|
|
12135
12617
|
const result = await deps.dependencyCruiser(
|
|
12136
12618
|
analyzeSourcePatterns,
|
|
12137
12619
|
buildDependencyCruiserOptions(typescriptScope, projectRoot, tsConfigFile),
|
|
@@ -12251,9 +12733,9 @@ async function circularCommand(options, deps = defaultCircularCommandDeps) {
|
|
|
12251
12733
|
}
|
|
12252
12734
|
|
|
12253
12735
|
// src/validation/steps/knip.ts
|
|
12254
|
-
import { existsSync as
|
|
12255
|
-
import { mkdir as mkdir5, mkdtemp as mkdtemp2, rm, writeFile as writeFile3 } from "fs/promises";
|
|
12256
|
-
import { isAbsolute as
|
|
12736
|
+
import { existsSync as existsSync4 } from "fs";
|
|
12737
|
+
import { mkdir as mkdir5, mkdtemp as mkdtemp2, rm as rm2, writeFile as writeFile3 } from "fs/promises";
|
|
12738
|
+
import { isAbsolute as isAbsolute5, join as join28 } from "path";
|
|
12257
12739
|
var defaultKnipProcessRunner = lifecycleProcessRunner;
|
|
12258
12740
|
var KNIP_COMMAND_TOKENS = {
|
|
12259
12741
|
COMMAND: "knip",
|
|
@@ -12262,10 +12744,10 @@ var KNIP_COMMAND_TOKENS = {
|
|
|
12262
12744
|
USE_TSCONFIG_FILES_FLAG: "--use-tsconfig-files"
|
|
12263
12745
|
};
|
|
12264
12746
|
var defaultKnipDeps = {
|
|
12265
|
-
existsSync:
|
|
12747
|
+
existsSync: existsSync4,
|
|
12266
12748
|
mkdir: mkdir5,
|
|
12267
12749
|
mkdtemp: mkdtemp2,
|
|
12268
|
-
rm,
|
|
12750
|
+
rm: rm2,
|
|
12269
12751
|
writeFile: writeFile3
|
|
12270
12752
|
};
|
|
12271
12753
|
async function validateKnip2(context, runner = defaultKnipProcessRunner, deps = defaultKnipDeps) {
|
|
@@ -12286,7 +12768,7 @@ async function validateKnip2(context, runner = defaultKnipProcessRunner, deps =
|
|
|
12286
12768
|
}
|
|
12287
12769
|
async function runKnipSubprocess(projectRoot, typescriptScope, runner, deps) {
|
|
12288
12770
|
const scopedTsconfig = typescriptScope.filteredByValidationPaths ? await createScopedKnipTsconfig(projectRoot, typescriptScope, deps) : void 0;
|
|
12289
|
-
const localBin =
|
|
12771
|
+
const localBin = join28(projectRoot, "node_modules", ".bin", "knip");
|
|
12290
12772
|
const binary = deps.existsSync(localBin) ? localBin : KNIP_COMMAND_TOKENS.NPX_COMMAND;
|
|
12291
12773
|
const baseArgs = scopedTsconfig === void 0 ? [] : [
|
|
12292
12774
|
KNIP_COMMAND_TOKENS.USE_TSCONFIG_FILES_FLAG,
|
|
@@ -12341,11 +12823,11 @@ async function runKnipSubprocess(projectRoot, typescriptScope, runner, deps) {
|
|
|
12341
12823
|
});
|
|
12342
12824
|
}
|
|
12343
12825
|
async function createScopedKnipTsconfig(projectRoot, typescriptScope, deps) {
|
|
12344
|
-
const tempParentDir =
|
|
12826
|
+
const tempParentDir = join28(projectRoot, ...TEMPORARY_TSCONFIG_PARENT_SEGMENTS);
|
|
12345
12827
|
await deps.mkdir(tempParentDir, { recursive: true });
|
|
12346
|
-
const tempDir = await deps.mkdtemp(
|
|
12347
|
-
const configPath =
|
|
12348
|
-
const toProjectPathPattern = (pattern) =>
|
|
12828
|
+
const tempDir = await deps.mkdtemp(join28(tempParentDir, "validate-knip-"));
|
|
12829
|
+
const configPath = join28(tempDir, TSCONFIG_FILES.full);
|
|
12830
|
+
const toProjectPathPattern = (pattern) => isAbsolute5(pattern) ? pattern : join28(projectRoot, pattern);
|
|
12349
12831
|
const project = [
|
|
12350
12832
|
...typescriptScope.directories.flatMap(
|
|
12351
12833
|
(directory) => TYPESCRIPT_FALLBACK_INCLUDE_PATTERNS.map((pattern) => `${directory}/${pattern}`)
|
|
@@ -12353,7 +12835,7 @@ async function createScopedKnipTsconfig(projectRoot, typescriptScope, deps) {
|
|
|
12353
12835
|
...typescriptScope.filePatterns
|
|
12354
12836
|
];
|
|
12355
12837
|
const config = {
|
|
12356
|
-
extends:
|
|
12838
|
+
extends: join28(projectRoot, TSCONFIG_FILES.full),
|
|
12357
12839
|
include: project.map(toProjectPathPattern),
|
|
12358
12840
|
exclude: typescriptScope.excludePatterns.map(toProjectPathPattern)
|
|
12359
12841
|
};
|
|
@@ -12419,13 +12901,13 @@ async function knipCommand(options, deps = defaultKnipCommandDeps) {
|
|
|
12419
12901
|
}
|
|
12420
12902
|
|
|
12421
12903
|
// src/validation/steps/eslint.ts
|
|
12422
|
-
import { existsSync as
|
|
12423
|
-
import { join as
|
|
12904
|
+
import { existsSync as existsSync6 } from "fs";
|
|
12905
|
+
import { join as join30 } from "path";
|
|
12424
12906
|
|
|
12425
12907
|
// src/validation/lint-policy.ts
|
|
12426
12908
|
import { execFileSync } from "child_process";
|
|
12427
|
-
import { existsSync as
|
|
12428
|
-
import { join as
|
|
12909
|
+
import { existsSync as existsSync5, readdirSync as readdirSync2, readFileSync as readFileSync4, statSync as statSync2 } from "fs";
|
|
12910
|
+
import { join as join29 } from "path";
|
|
12429
12911
|
|
|
12430
12912
|
// src/validation/lint-policy-constants.ts
|
|
12431
12913
|
var LINT_POLICY_MANIFESTS = {
|
|
@@ -12471,19 +12953,19 @@ var BASE_BRANCH_REFS = [LINT_POLICY_BASE_REFS.REMOTE_MAIN, LINT_POLICY_BASE_REFS
|
|
|
12471
12953
|
function isSpecTreeNodePath(entry) {
|
|
12472
12954
|
return NODE_SUFFIXES.some((suffix) => entry.endsWith(suffix));
|
|
12473
12955
|
}
|
|
12474
|
-
function
|
|
12956
|
+
function readManifest2(productDir, file, key) {
|
|
12475
12957
|
return parseLintPolicyManifest(
|
|
12476
|
-
readFileSync4(
|
|
12958
|
+
readFileSync4(join29(productDir, file), "utf-8"),
|
|
12477
12959
|
file,
|
|
12478
12960
|
key
|
|
12479
12961
|
);
|
|
12480
12962
|
}
|
|
12481
12963
|
function manifestExists(productDir, file) {
|
|
12482
|
-
return
|
|
12964
|
+
return existsSync5(join29(productDir, file));
|
|
12483
12965
|
}
|
|
12484
12966
|
function findDeprecatedSpecNodePath(productDir) {
|
|
12485
12967
|
function visit2(relativeDirectory) {
|
|
12486
|
-
const absoluteDirectory =
|
|
12968
|
+
const absoluteDirectory = join29(productDir, relativeDirectory);
|
|
12487
12969
|
for (const entry of readdirSync2(absoluteDirectory, { withFileTypes: true })) {
|
|
12488
12970
|
if (!entry.isDirectory()) {
|
|
12489
12971
|
continue;
|
|
@@ -12499,8 +12981,8 @@ function findDeprecatedSpecNodePath(productDir) {
|
|
|
12499
12981
|
}
|
|
12500
12982
|
return void 0;
|
|
12501
12983
|
}
|
|
12502
|
-
const specTreeRootPath =
|
|
12503
|
-
if (!
|
|
12984
|
+
const specTreeRootPath = join29(productDir, SPEC_TREE_ROOT);
|
|
12985
|
+
if (!existsSync5(specTreeRootPath)) {
|
|
12504
12986
|
return void 0;
|
|
12505
12987
|
}
|
|
12506
12988
|
return visit2(SPEC_TREE_ROOT);
|
|
@@ -12525,8 +13007,8 @@ function assertManifestEntries(productDir, file, entries, suffixPredicate, suffi
|
|
|
12525
13007
|
if (!suffixPredicate(entry)) {
|
|
12526
13008
|
throw new Error(`${file} entry must ${suffixDescription}: ${entry}`);
|
|
12527
13009
|
}
|
|
12528
|
-
const absoluteEntry =
|
|
12529
|
-
if (!
|
|
13010
|
+
const absoluteEntry = join29(productDir, entry);
|
|
13011
|
+
if (!existsSync5(absoluteEntry) || !statSync2(absoluteEntry).isDirectory()) {
|
|
12530
13012
|
throw new Error(`${file} entry does not exist as a directory: ${entry}`);
|
|
12531
13013
|
}
|
|
12532
13014
|
}
|
|
@@ -12658,11 +13140,11 @@ function validateLintPolicy(productDir) {
|
|
|
12658
13140
|
}
|
|
12659
13141
|
validateTestLintDebtNodeManifest(
|
|
12660
13142
|
productDir,
|
|
12661
|
-
|
|
13143
|
+
readManifest2(productDir, TEST_LINT_DEBT_NODE_MANIFEST_FILE, TEST_LINT_DEBT_NODE_MANIFEST_KEY)
|
|
12662
13144
|
);
|
|
12663
13145
|
validateTestOwnedConstantDebtNodeManifest(
|
|
12664
13146
|
productDir,
|
|
12665
|
-
|
|
13147
|
+
readManifest2(
|
|
12666
13148
|
productDir,
|
|
12667
13149
|
TEST_OWNED_CONSTANT_DEBT_NODE_MANIFEST_FILE,
|
|
12668
13150
|
TEST_OWNED_CONSTANT_DEBT_NODE_MANIFEST_KEY
|
|
@@ -12674,36 +13156,6 @@ function validateLintPolicy(productDir) {
|
|
|
12674
13156
|
}
|
|
12675
13157
|
}
|
|
12676
13158
|
|
|
12677
|
-
// src/validation/steps/subprocess-output.ts
|
|
12678
|
-
var VALIDATION_SUBPROCESS_EVENTS = {
|
|
12679
|
-
CLOSE: "close",
|
|
12680
|
-
DATA: "data",
|
|
12681
|
-
DRAIN: "drain",
|
|
12682
|
-
ERROR: "error"
|
|
12683
|
-
};
|
|
12684
|
-
var defaultValidationSubprocessOutputStreams = {
|
|
12685
|
-
stdout: process.stdout,
|
|
12686
|
-
stderr: process.stderr
|
|
12687
|
-
};
|
|
12688
|
-
function forwardValidationSubprocessOutput(child, streams = defaultValidationSubprocessOutputStreams) {
|
|
12689
|
-
child.stdout?.on(VALIDATION_SUBPROCESS_EVENTS.DATA, (chunk) => {
|
|
12690
|
-
forwardChunkWithBackpressure(child.stdout, streams.stdout, chunk);
|
|
12691
|
-
});
|
|
12692
|
-
child.stderr?.on(VALIDATION_SUBPROCESS_EVENTS.DATA, (chunk) => {
|
|
12693
|
-
forwardChunkWithBackpressure(child.stderr, streams.stderr, chunk);
|
|
12694
|
-
});
|
|
12695
|
-
}
|
|
12696
|
-
function forwardChunkWithBackpressure(source, stream, chunk) {
|
|
12697
|
-
const ready = stream.write(chunk);
|
|
12698
|
-
if (ready || stream.once === void 0 || source === null || source === void 0) {
|
|
12699
|
-
return;
|
|
12700
|
-
}
|
|
12701
|
-
source.pause();
|
|
12702
|
-
stream.once(VALIDATION_SUBPROCESS_EVENTS.DRAIN, () => {
|
|
12703
|
-
source.resume();
|
|
12704
|
-
});
|
|
12705
|
-
}
|
|
12706
|
-
|
|
12707
13159
|
// src/validation/steps/eslint.ts
|
|
12708
13160
|
var defaultEslintProcessRunner = lifecycleProcessRunner;
|
|
12709
13161
|
var DEFAULT_ESLINT_CONFIG_FILE = "eslint.config.ts";
|
|
@@ -12771,8 +13223,8 @@ async function validateESLint(context, runner = defaultEslintProcessRunner, outp
|
|
|
12771
13223
|
scopeConfig: context.scopeConfig
|
|
12772
13224
|
});
|
|
12773
13225
|
return new Promise((resolve8) => {
|
|
12774
|
-
const localBin =
|
|
12775
|
-
const binary =
|
|
13226
|
+
const localBin = join30(projectRoot, ...ESLINT_LOCAL_BIN_SEGMENTS);
|
|
13227
|
+
const binary = existsSync6(localBin) ? localBin : "npx";
|
|
12776
13228
|
const spawnArgs = binary === "npx" ? eslintArgs : eslintArgs.slice(1);
|
|
12777
13229
|
const eslintProcess = spawnManagedSubprocess(runner, binary, spawnArgs, {
|
|
12778
13230
|
cwd: projectRoot
|
|
@@ -12873,7 +13325,7 @@ async function lintCommand(options) {
|
|
|
12873
13325
|
|
|
12874
13326
|
// src/validation/literal/index.ts
|
|
12875
13327
|
import { readFile as readFile9 } from "fs/promises";
|
|
12876
|
-
import { isAbsolute as
|
|
13328
|
+
import { isAbsolute as isAbsolute6, relative as relative7, resolve as resolve7 } from "path";
|
|
12877
13329
|
|
|
12878
13330
|
// src/lib/file-inclusion/predicates/ignore-source.ts
|
|
12879
13331
|
var IGNORE_SOURCE_LAYER = "ignore-source";
|
|
@@ -12907,18 +13359,18 @@ var ignoreSourceLayer = makeLayer(
|
|
|
12907
13359
|
);
|
|
12908
13360
|
|
|
12909
13361
|
// src/lib/file-inclusion/pipeline.ts
|
|
12910
|
-
import { readdir as
|
|
12911
|
-
import { join as
|
|
13362
|
+
import { readdir as readdir8 } from "fs/promises";
|
|
13363
|
+
import { join as join31, relative as relative6, sep as sep2 } from "path";
|
|
12912
13364
|
var EXPLICIT_OVERRIDE_LAYER = "explicit-override";
|
|
12913
|
-
function
|
|
13365
|
+
function isNodeError4(err) {
|
|
12914
13366
|
return err instanceof Error && "code" in err;
|
|
12915
13367
|
}
|
|
12916
13368
|
async function collectPaths(absoluteDir, projectRoot, result, artifactDirs) {
|
|
12917
13369
|
let dirEntries;
|
|
12918
13370
|
try {
|
|
12919
|
-
dirEntries = await
|
|
13371
|
+
dirEntries = await readdir8(absoluteDir, { withFileTypes: true });
|
|
12920
13372
|
} catch (err) {
|
|
12921
|
-
if (
|
|
13373
|
+
if (isNodeError4(err) && (err.code === "ENOENT" || err.code === "ENOTDIR")) {
|
|
12922
13374
|
return;
|
|
12923
13375
|
}
|
|
12924
13376
|
throw err;
|
|
@@ -12926,11 +13378,11 @@ async function collectPaths(absoluteDir, projectRoot, result, artifactDirs) {
|
|
|
12926
13378
|
for (const entry of dirEntries) {
|
|
12927
13379
|
if (entry.isDirectory()) {
|
|
12928
13380
|
if (artifactDirs.has(entry.name)) continue;
|
|
12929
|
-
const absolutePath =
|
|
13381
|
+
const absolutePath = join31(absoluteDir, entry.name);
|
|
12930
13382
|
await collectPaths(absolutePath, projectRoot, result, artifactDirs);
|
|
12931
13383
|
} else if (entry.isFile()) {
|
|
12932
|
-
const absolutePath =
|
|
12933
|
-
const rel =
|
|
13384
|
+
const absolutePath = join31(absoluteDir, entry.name);
|
|
13385
|
+
const rel = relative6(projectRoot, absolutePath);
|
|
12934
13386
|
result.push(sep2 === "/" ? rel : rel.split(sep2).join("/"));
|
|
12935
13387
|
}
|
|
12936
13388
|
}
|
|
@@ -13336,8 +13788,8 @@ async function validateLiteralReuse(input) {
|
|
|
13336
13788
|
const config = input.config ?? literalConfigDescriptor.defaults;
|
|
13337
13789
|
const request = input.files ? {
|
|
13338
13790
|
explicit: input.files.map((f) => {
|
|
13339
|
-
const abs =
|
|
13340
|
-
return
|
|
13791
|
+
const abs = isAbsolute6(f) ? f : resolve7(input.productDir, f);
|
|
13792
|
+
return relative7(input.productDir, abs).split(/[\\/]/g).join("/");
|
|
13341
13793
|
})
|
|
13342
13794
|
} : { walkRoot: input.productDir };
|
|
13343
13795
|
const scope2 = await runPipeline(
|
|
@@ -13358,7 +13810,7 @@ async function validateLiteralReuse(input) {
|
|
|
13358
13810
|
const testOccurrencesByFile = /* @__PURE__ */ new Map();
|
|
13359
13811
|
const indexedOccurrencesByFile = /* @__PURE__ */ new Map();
|
|
13360
13812
|
for (const abs of candidateFiles) {
|
|
13361
|
-
const rel =
|
|
13813
|
+
const rel = relative7(input.productDir, abs).split(/[\\/]/g).join("/");
|
|
13362
13814
|
const content = await readSafe(abs);
|
|
13363
13815
|
if (content === null) continue;
|
|
13364
13816
|
const occurrences = collectLiterals(content, rel, collectOptions);
|
|
@@ -13596,23 +14048,23 @@ function formatLoc(loc) {
|
|
|
13596
14048
|
}
|
|
13597
14049
|
|
|
13598
14050
|
// src/validation/steps/typescript.ts
|
|
13599
|
-
import { existsSync as
|
|
14051
|
+
import { existsSync as existsSync7, mkdirSync, rmSync, writeFileSync } from "fs";
|
|
13600
14052
|
import { mkdtemp as mkdtemp3 } from "fs/promises";
|
|
13601
|
-
import { isAbsolute as
|
|
14053
|
+
import { isAbsolute as isAbsolute7, join as join32 } from "path";
|
|
13602
14054
|
var defaultTypeScriptProcessRunner = lifecycleProcessRunner;
|
|
13603
14055
|
var defaultTypeScriptDeps = {
|
|
13604
14056
|
mkdtemp: mkdtemp3,
|
|
13605
14057
|
mkdirSync,
|
|
13606
14058
|
writeFileSync,
|
|
13607
14059
|
rmSync,
|
|
13608
|
-
existsSync:
|
|
14060
|
+
existsSync: existsSync7
|
|
13609
14061
|
};
|
|
13610
14062
|
var TEMPORARY_TSCONFIG_COMPILER_OPTIONS = { noEmit: true };
|
|
13611
14063
|
var TEMPORARY_TSCONFIG_DIR_PREFIX = "validate-ts-";
|
|
13612
14064
|
async function createTemporaryTsconfigDir(projectRoot, deps) {
|
|
13613
|
-
const parent =
|
|
14065
|
+
const parent = join32(projectRoot, ...TEMPORARY_TSCONFIG_PARENT_SEGMENTS);
|
|
13614
14066
|
deps.mkdirSync(parent, { recursive: true });
|
|
13615
|
-
return deps.mkdtemp(
|
|
14067
|
+
return deps.mkdtemp(join32(parent, TEMPORARY_TSCONFIG_DIR_PREFIX));
|
|
13616
14068
|
}
|
|
13617
14069
|
function buildTypeScriptArgs(context) {
|
|
13618
14070
|
const { scope: scope2, configFile } = context;
|
|
@@ -13620,11 +14072,11 @@ function buildTypeScriptArgs(context) {
|
|
|
13620
14072
|
}
|
|
13621
14073
|
async function createFileSpecificTsconfig(scope2, files, projectRoot, deps = defaultTypeScriptDeps) {
|
|
13622
14074
|
const tempDir = await createTemporaryTsconfigDir(projectRoot, deps);
|
|
13623
|
-
const configPath =
|
|
14075
|
+
const configPath = join32(tempDir, "tsconfig.json");
|
|
13624
14076
|
const baseConfigFile = TSCONFIG_FILES[scope2];
|
|
13625
|
-
const absoluteFiles = files.map((file) =>
|
|
14077
|
+
const absoluteFiles = files.map((file) => isAbsolute7(file) ? file : join32(projectRoot, file));
|
|
13626
14078
|
const tempConfig = {
|
|
13627
|
-
extends:
|
|
14079
|
+
extends: join32(projectRoot, baseConfigFile),
|
|
13628
14080
|
files: absoluteFiles,
|
|
13629
14081
|
include: [],
|
|
13630
14082
|
exclude: [],
|
|
@@ -13641,11 +14093,11 @@ async function createFileSpecificTsconfig(scope2, files, projectRoot, deps = def
|
|
|
13641
14093
|
}
|
|
13642
14094
|
async function createScopeFilteredTsconfig(scope2, projectRoot, scopeConfig, deps = defaultTypeScriptDeps) {
|
|
13643
14095
|
const tempDir = await createTemporaryTsconfigDir(projectRoot, deps);
|
|
13644
|
-
const configPath =
|
|
14096
|
+
const configPath = join32(tempDir, "tsconfig.json");
|
|
13645
14097
|
const baseConfigFile = TSCONFIG_FILES[scope2];
|
|
13646
|
-
const toProjectPathPattern = (pattern) =>
|
|
14098
|
+
const toProjectPathPattern = (pattern) => isAbsolute7(pattern) ? pattern : join32(projectRoot, pattern);
|
|
13647
14099
|
const tempConfig = {
|
|
13648
|
-
extends:
|
|
14100
|
+
extends: join32(projectRoot, baseConfigFile),
|
|
13649
14101
|
include: scopeConfig.filePatterns.map(toProjectPathPattern),
|
|
13650
14102
|
exclude: scopeConfig.excludePatterns.map(toProjectPathPattern),
|
|
13651
14103
|
compilerOptions: TEMPORARY_TSCONFIG_COMPILER_OPTIONS
|
|
@@ -13673,7 +14125,7 @@ async function validateTypeScript(context, options = {}) {
|
|
|
13673
14125
|
const { configPath, cleanup } = await createFileSpecificTsconfig(scope2, files, projectRoot, deps);
|
|
13674
14126
|
try {
|
|
13675
14127
|
return await new Promise((resolve8) => {
|
|
13676
|
-
const tscBin =
|
|
14128
|
+
const tscBin = join32(projectRoot, "node_modules", ".bin", "tsc");
|
|
13677
14129
|
const tscBinary = deps.existsSync(tscBin) ? tscBin : "npx";
|
|
13678
14130
|
const tscArgs2 = tscBinary === "npx" ? ["tsc", "--project", configPath] : ["--project", configPath];
|
|
13679
14131
|
const tscProcess = spawnManagedSubprocess(runner, tscBinary, tscArgs2, {
|
|
@@ -13706,7 +14158,7 @@ async function validateTypeScript(context, options = {}) {
|
|
|
13706
14158
|
return { success: true, skipped: true };
|
|
13707
14159
|
}
|
|
13708
14160
|
const { configPath, cleanup } = await createScopeFilteredTsconfig(scope2, projectRoot, scopeConfig, deps);
|
|
13709
|
-
const tscBin =
|
|
14161
|
+
const tscBin = join32(projectRoot, "node_modules", ".bin", "tsc");
|
|
13710
14162
|
tool = deps.existsSync(tscBin) ? tscBin : "npx";
|
|
13711
14163
|
tscArgs = tool === "npx" ? ["tsc", "--project", configPath] : ["--project", configPath];
|
|
13712
14164
|
return new Promise((resolve8) => {
|
|
@@ -13728,7 +14180,7 @@ async function validateTypeScript(context, options = {}) {
|
|
|
13728
14180
|
});
|
|
13729
14181
|
});
|
|
13730
14182
|
} else {
|
|
13731
|
-
const tscBin =
|
|
14183
|
+
const tscBin = join32(projectRoot, "node_modules", ".bin", "tsc");
|
|
13732
14184
|
tool = deps.existsSync(tscBin) ? tscBin : "npx";
|
|
13733
14185
|
const rawArgs = buildTypeScriptArgs({ scope: scope2, configFile });
|
|
13734
14186
|
tscArgs = tool === "npx" ? rawArgs : rawArgs.slice(1);
|
|
@@ -13905,11 +14357,12 @@ var typescriptValidationLanguage = {
|
|
|
13905
14357
|
|
|
13906
14358
|
// src/validation/registry.ts
|
|
13907
14359
|
var validationRegistry = {
|
|
13908
|
-
languages: [typescriptValidationLanguage, markdownValidationLanguage]
|
|
14360
|
+
languages: [typescriptValidationLanguage, markdownValidationLanguage, formattingValidationLanguage]
|
|
13909
14361
|
};
|
|
13910
|
-
|
|
13911
|
-
(language) => language.stages
|
|
13912
|
-
|
|
14362
|
+
function composeValidationPipelineStages(languages) {
|
|
14363
|
+
return languages.flatMap((language) => language.stages);
|
|
14364
|
+
}
|
|
14365
|
+
var validationPipelineStages = composeValidationPipelineStages(validationRegistry.languages);
|
|
13913
14366
|
var VALIDATION_PIPELINE_TOTAL_STEPS = validationPipelineStages.length;
|
|
13914
14367
|
|
|
13915
14368
|
// src/commands/validation/format.ts
|
|
@@ -13972,7 +14425,7 @@ async function allCommand(options) {
|
|
|
13972
14425
|
// src/validation/literal/allowlist-existing.ts
|
|
13973
14426
|
import { randomBytes } from "crypto";
|
|
13974
14427
|
import { rename as rename4, writeFile as writeFile4 } from "fs/promises";
|
|
13975
|
-
import { dirname as dirname9, join as
|
|
14428
|
+
import { dirname as dirname9, join as join33 } from "path";
|
|
13976
14429
|
var EXIT_OK = 0;
|
|
13977
14430
|
var EXIT_ERROR = 1;
|
|
13978
14431
|
var INCLUDE_FIELD = "include";
|
|
@@ -13992,7 +14445,7 @@ var productionWriter = {
|
|
|
13992
14445
|
async write(filePath, content) {
|
|
13993
14446
|
const dir = dirname9(filePath);
|
|
13994
14447
|
const random = randomBytes(RANDOM_TOKEN_BYTES).toString("hex");
|
|
13995
|
-
const tmpPath =
|
|
14448
|
+
const tmpPath = join33(dir, `${TEMP_FILE_PREFIX}${random}${TEMP_FILE_SUFFIX}`);
|
|
13996
14449
|
await writeFile4(tmpPath, content, "utf8");
|
|
13997
14450
|
await rename4(tmpPath, filePath);
|
|
13998
14451
|
}
|
|
@@ -14098,6 +14551,10 @@ var validationCliDefinition = {
|
|
|
14098
14551
|
alias: "md",
|
|
14099
14552
|
description: "Validate markdown link integrity and structure"
|
|
14100
14553
|
},
|
|
14554
|
+
format: {
|
|
14555
|
+
commandName: "format",
|
|
14556
|
+
description: "Check code formatting with dprint"
|
|
14557
|
+
},
|
|
14101
14558
|
all: {
|
|
14102
14559
|
commandName: "all",
|
|
14103
14560
|
description: "Run all validations"
|
|
@@ -14278,6 +14735,16 @@ function registerValidationCommands(validationCmd) {
|
|
|
14278
14735
|
process.exit(result.exitCode);
|
|
14279
14736
|
});
|
|
14280
14737
|
addCommonOptions(markdownCmd);
|
|
14738
|
+
const formatCmd = addValidationSubcommand(validationCmd, subcommands.format).action(async (options) => {
|
|
14739
|
+
const result = await formattingCommand({
|
|
14740
|
+
cwd: process.cwd(),
|
|
14741
|
+
files: options.files,
|
|
14742
|
+
quiet: options.quiet
|
|
14743
|
+
});
|
|
14744
|
+
if (result.output) console.log(result.output);
|
|
14745
|
+
process.exit(result.exitCode);
|
|
14746
|
+
});
|
|
14747
|
+
addCommonOptions(formatCmd);
|
|
14281
14748
|
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) => {
|
|
14282
14749
|
const result = await allCommand({
|
|
14283
14750
|
cwd: process.cwd(),
|
|
@@ -14339,6 +14806,10 @@ var WORKTREE_STATUS_FORMAT = {
|
|
|
14339
14806
|
JSON: "json",
|
|
14340
14807
|
TEXT: "text"
|
|
14341
14808
|
};
|
|
14809
|
+
var WORKTREE_STATUS_RENDER = {
|
|
14810
|
+
FREE: "-",
|
|
14811
|
+
RUNNING_PID_PREFIX: "PID "
|
|
14812
|
+
};
|
|
14342
14813
|
async function statusCommand2(options) {
|
|
14343
14814
|
const multiTargetRequest = options.worktrees !== void 0 && options.worktrees.length > 1;
|
|
14344
14815
|
const targets = await resolveStatusTargets(options);
|
|
@@ -14346,9 +14817,13 @@ async function statusCommand2(options) {
|
|
|
14346
14817
|
const records = [];
|
|
14347
14818
|
for (const target of targets.value) {
|
|
14348
14819
|
const worktreesDir = await resolveWorktreesDir({ ...options, cwd: target.worktreeRoot });
|
|
14349
|
-
const
|
|
14350
|
-
if (!
|
|
14351
|
-
|
|
14820
|
+
const claimResult = await readClaim(worktreesDir, target.name, { fs: options.fs });
|
|
14821
|
+
if (!claimResult.ok) return claimResult;
|
|
14822
|
+
const claim = claimResult.value;
|
|
14823
|
+
const status = classifyOccupancy(claim, options.processTable);
|
|
14824
|
+
records.push(
|
|
14825
|
+
status === OCCUPANCY_STATUS.RUNNING && claim !== void 0 ? { worktree: target.name, status, pid: claim.pid, session: claim.sessionId, host: claim.host } : { worktree: target.name, status }
|
|
14826
|
+
);
|
|
14352
14827
|
}
|
|
14353
14828
|
return { ok: true, value: renderStatus(records, options.format, multiTargetRequest) };
|
|
14354
14829
|
}
|
|
@@ -14360,10 +14835,13 @@ async function resolveStatusTargets(options) {
|
|
|
14360
14835
|
return { ok: true, value: [target.value] };
|
|
14361
14836
|
}
|
|
14362
14837
|
const targets = [];
|
|
14838
|
+
const seenRoots = /* @__PURE__ */ new Set();
|
|
14363
14839
|
let firstError;
|
|
14364
14840
|
for (const worktree of requested) {
|
|
14365
14841
|
const target = await resolveTargetWorktree({ ...options, worktree });
|
|
14366
14842
|
if (target.ok) {
|
|
14843
|
+
if (seenRoots.has(target.value.worktreeRoot)) continue;
|
|
14844
|
+
seenRoots.add(target.value.worktreeRoot);
|
|
14367
14845
|
targets.push(target.value);
|
|
14368
14846
|
} else {
|
|
14369
14847
|
firstError ??= target.error;
|
|
@@ -14381,7 +14859,10 @@ function renderStatus(records, format2, multiTargetRequest) {
|
|
|
14381
14859
|
return records.map(renderTextStatus).join("\n");
|
|
14382
14860
|
}
|
|
14383
14861
|
function renderTextStatus(record6) {
|
|
14384
|
-
|
|
14862
|
+
if (record6.status === OCCUPANCY_STATUS.RUNNING) {
|
|
14863
|
+
return `${record6.worktree} ${WORKTREE_STATUS_RENDER.RUNNING_PID_PREFIX}${record6.pid} (${record6.session} @ ${record6.host})`;
|
|
14864
|
+
}
|
|
14865
|
+
return `${record6.worktree} ${WORKTREE_STATUS_RENDER.FREE}`;
|
|
14385
14866
|
}
|
|
14386
14867
|
|
|
14387
14868
|
// src/lib/worktree-path-info.ts
|
|
@@ -14429,7 +14910,7 @@ function registerWorktreeCommands(worktreeCmd) {
|
|
|
14429
14910
|
});
|
|
14430
14911
|
if (!result.ok) handleError3(result.error);
|
|
14431
14912
|
});
|
|
14432
|
-
worktreeCmd.command(`${WORKTREE_CLI.STATUS} ${WORKTREE_CLI.WORKTREE_ARGUMENT}`).description("Report a worktree's occupancy (
|
|
14913
|
+
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) => {
|
|
14433
14914
|
const result = await statusCommand2({
|
|
14434
14915
|
cwd: process.cwd(),
|
|
14435
14916
|
fs: defaultOccupancyFileSystem,
|