@outcomeeng/spx 0.6.18 → 0.6.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/cli.ts
2
- import { createRequire as createRequire2 } from "module";
2
+ import { createRequire as createRequire3 } from "module";
3
3
 
4
4
  // src/interfaces/cli/program.ts
5
5
  import { Command } from "commander";
@@ -10055,12 +10055,17 @@ var AGENT_RUN_TOOLS = {
10055
10055
  WRITE: "Write",
10056
10056
  EDIT: "Edit"
10057
10057
  };
10058
+ var AGENT_FILE_TOOLS = [
10059
+ AGENT_RUN_TOOLS.READ,
10060
+ AGENT_RUN_TOOLS.WRITE,
10061
+ AGENT_RUN_TOOLS.EDIT
10062
+ ];
10058
10063
  var AGENT_TOOL_PERMISSION_BEHAVIOR = {
10059
10064
  ALLOW: "allow",
10060
10065
  DENY: "deny"
10061
10066
  };
10062
10067
  function authorizeAgentFileToolPath(workingDirectory, tool, filePath) {
10063
- return (tool === AGENT_RUN_TOOLS.WRITE || tool === AGENT_RUN_TOOLS.EDIT) && filePath.length > 0 && isPathContained(workingDirectory, filePath) ? AGENT_TOOL_PERMISSION_BEHAVIOR.ALLOW : AGENT_TOOL_PERMISSION_BEHAVIOR.DENY;
10068
+ return AGENT_FILE_TOOLS.includes(tool) && filePath.length > 0 && isPathContained(workingDirectory, filePath) ? AGENT_TOOL_PERMISSION_BEHAVIOR.ALLOW : AGENT_TOOL_PERMISSION_BEHAVIOR.DENY;
10064
10069
  }
10065
10070
  var AGENT_PERMISSION_MODES = {
10066
10071
  DONT_ASK: "dontAsk"
@@ -10068,6 +10073,7 @@ var AGENT_PERMISSION_MODES = {
10068
10073
 
10069
10074
  // src/agent/claude-agent-runner.ts
10070
10075
  var AGENT_FILE_TOOL_PATH_INPUT_FIELD = "file_path";
10076
+ var AGENT_PRE_TOOL_USE_HOOK_EVENT = "PreToolUse";
10071
10077
  var AGENT_FILE_TOOL_PERMISSION_DENIED_MESSAGE = "Agent file tool target is outside its working directory";
10072
10078
  var ClaudeAgentRunner = class {
10073
10079
  async run(request) {
@@ -10096,41 +10102,54 @@ function createAgentRunOptions(request) {
10096
10102
  cwd: request.workingDirectory,
10097
10103
  settingSources: [],
10098
10104
  tools: [...request.tools],
10099
- allowedTools: autoAllowedAgentTools(request.allowedTools),
10100
- canUseTool: createAgentToolPermission(request),
10105
+ allowedTools: [...request.allowedTools],
10106
+ hooks: {
10107
+ PreToolUse: [{ hooks: [createAgentFileToolPermissionHook(request)] }]
10108
+ },
10101
10109
  permissionMode: request.permissionMode,
10102
10110
  maxTurns: request.maxTurns
10103
10111
  };
10104
10112
  }
10105
- function autoAllowedAgentTools(allowedTools) {
10106
- return allowedTools.filter((tool) => !isAgentFileMutationTool(tool));
10107
- }
10108
- function createAgentToolPermission(request) {
10109
- return async (toolName, input) => {
10110
- if (!isAgentRunTool(toolName) || !request.allowedTools.includes(toolName)) {
10111
- return deniedAgentToolPermission();
10113
+ function createAgentFileToolPermissionHook(request) {
10114
+ return async (input) => {
10115
+ if (input.hook_event_name !== AGENT_PRE_TOOL_USE_HOOK_EVENT || !isAgentRunTool(input.tool_name) || !request.allowedTools.includes(input.tool_name) || !isRecord8(input.tool_input)) {
10116
+ return deniedAgentFileToolPermission();
10112
10117
  }
10113
- if (!isAgentFileMutationTool(toolName)) {
10114
- return { behavior: AGENT_TOOL_PERMISSION_BEHAVIOR.ALLOW, updatedInput: input };
10118
+ if (!isAgentFileTool(input.tool_name)) {
10119
+ return allowedAgentFileToolPermission();
10115
10120
  }
10116
- const filePath = input[AGENT_FILE_TOOL_PATH_INPUT_FIELD];
10117
- if (typeof filePath !== "string" || authorizeAgentFileToolPath(request.workingDirectory, toolName, filePath) === AGENT_TOOL_PERMISSION_BEHAVIOR.DENY) {
10118
- return deniedAgentToolPermission();
10121
+ const filePath = input.tool_input[AGENT_FILE_TOOL_PATH_INPUT_FIELD];
10122
+ if (typeof filePath !== "string" || authorizeAgentFileToolPath(request.workingDirectory, input.tool_name, filePath) === AGENT_TOOL_PERMISSION_BEHAVIOR.DENY) {
10123
+ return deniedAgentFileToolPermission();
10119
10124
  }
10120
- return { behavior: AGENT_TOOL_PERMISSION_BEHAVIOR.ALLOW, updatedInput: input };
10125
+ return allowedAgentFileToolPermission();
10121
10126
  };
10122
10127
  }
10123
- function deniedAgentToolPermission() {
10128
+ function allowedAgentFileToolPermission() {
10124
10129
  return {
10125
- behavior: AGENT_TOOL_PERMISSION_BEHAVIOR.DENY,
10126
- message: AGENT_FILE_TOOL_PERMISSION_DENIED_MESSAGE
10130
+ hookSpecificOutput: {
10131
+ hookEventName: AGENT_PRE_TOOL_USE_HOOK_EVENT,
10132
+ permissionDecision: AGENT_TOOL_PERMISSION_BEHAVIOR.ALLOW
10133
+ }
10134
+ };
10135
+ }
10136
+ function deniedAgentFileToolPermission() {
10137
+ return {
10138
+ hookSpecificOutput: {
10139
+ hookEventName: AGENT_PRE_TOOL_USE_HOOK_EVENT,
10140
+ permissionDecision: AGENT_TOOL_PERMISSION_BEHAVIOR.DENY,
10141
+ permissionDecisionReason: AGENT_FILE_TOOL_PERMISSION_DENIED_MESSAGE
10142
+ }
10127
10143
  };
10128
10144
  }
10145
+ function isRecord8(input) {
10146
+ return typeof input === "object" && input !== null;
10147
+ }
10129
10148
  function isAgentRunTool(toolName) {
10130
10149
  return Object.values(AGENT_RUN_TOOLS).some((tool) => tool === toolName);
10131
10150
  }
10132
- function isAgentFileMutationTool(tool) {
10133
- return tool === AGENT_RUN_TOOLS.WRITE || tool === AGENT_RUN_TOOLS.EDIT;
10151
+ function isAgentFileTool(tool) {
10152
+ return AGENT_FILE_TOOLS.includes(tool);
10134
10153
  }
10135
10154
  async function runClaudeQuery(prompt, options) {
10136
10155
  let result;
@@ -10302,7 +10321,9 @@ function toComponent(value) {
10302
10321
  }
10303
10322
 
10304
10323
  // src/domains/release/documentation-sync.ts
10305
- var DOCUMENTATION_SYNC_PROMPT_INSTRUCTION = "Update every staged documentation file so it contains the released version and its version references and behavior descriptions match the supplied release data, including when no previous release exists.";
10324
+ var DOCUMENTATION_SYNC_PROMPT_INSTRUCTION = "Edit every staged documentation file so its version references and behavior descriptions match the supplied release data.";
10325
+ var DOCUMENTATION_SYNC_RELEASE_VERSION_INSTRUCTION = "The exact released version every staged document must contain is";
10326
+ var DOCUMENTATION_SYNC_VERSIONLESS_INSTRUCTION = "Replace every standalone previous product release-version reference. When a staged document has no such reference, add a concise current-release reference using the exact released version above.";
10306
10327
  var DOCUMENTATION_SYNC_PROMPT_DATA_BLOCK_OPEN = "<documentation-sync-input>";
10307
10328
  var DOCUMENTATION_SYNC_PROMPT_DATA_BLOCK_CLOSE = "</documentation-sync-input>";
10308
10329
  var DOCUMENTATION_SYNC_AGENT_TOOLS = [
@@ -10315,11 +10336,15 @@ var DOCUMENTATION_SYNC_AGENT_MAX_TURNS = 10;
10315
10336
  var DOCUMENTATION_SYNC_AUDIT_MAX_TURNS = 3;
10316
10337
  var DOCUMENTATION_SYNC_AUDIT_APPROVED = "APPROVED";
10317
10338
  var DOCUMENTATION_SYNC_AUDIT_REJECTED = "REJECTED";
10339
+ var DOCUMENTATION_SYNC_AUDIT_VERSIONLESS_INSTRUCTION = "When an original document has no previous-release reference, adding a concise current-release reference using the exact released version is a supported release update.";
10318
10340
  var REGEXP_SPECIAL_CHARACTER_PATTERN = /[.*+?^${}()|[\]\\]/gu;
10319
10341
  var REGEXP_ESCAPE_REPLACEMENT = String.raw`\$&`;
10320
10342
  var VERSION_REFERENCE_NON_WHITESPACE_PATTERN = String.raw`\S`;
10321
10343
  function buildDocumentationSyncPrompt(input) {
10344
+ const encodedVersion = encodeReleasePromptData(input.releaseData.version).slice(1, -1);
10322
10345
  return `${DOCUMENTATION_SYNC_PROMPT_INSTRUCTION}
10346
+ ${DOCUMENTATION_SYNC_RELEASE_VERSION_INSTRUCTION} ${encodedVersion}.
10347
+ ${DOCUMENTATION_SYNC_VERSIONLESS_INSTRUCTION}
10323
10348
 
10324
10349
  ${DOCUMENTATION_SYNC_PROMPT_DATA_BLOCK_OPEN}
10325
10350
  ${encodeReleasePromptData(input)}
@@ -10407,6 +10432,7 @@ function containsReleaseVersionReference(content, version2) {
10407
10432
  function buildDocumentationFaithfulnessAuditPrompt(input) {
10408
10433
  return [
10409
10434
  "Audit whether every original-to-updated documentation transformation faithfully applies the supplied release data, including updating each previous-release reference rather than deleting it.",
10435
+ DOCUMENTATION_SYNC_AUDIT_VERSIONLESS_INSTRUCTION,
10410
10436
  `Return exactly ${DOCUMENTATION_SYNC_AUDIT_APPROVED} when every changed claim is supported and every previous-release reference remains represented by the released version.`,
10411
10437
  `Return ${DOCUMENTATION_SYNC_AUDIT_REJECTED} followed by a concise reason for any unsupported claim, deleted previous-release reference, or omitted release update.`,
10412
10438
  DOCUMENTATION_SYNC_PROMPT_DATA_BLOCK_OPEN,
@@ -14647,6 +14673,9 @@ function formatMissingCitedDecisionError(citedPath, citingPath) {
14647
14673
  function specContextBootstrap(nodeCount) {
14648
14674
  return nodeCount === 0;
14649
14675
  }
14676
+ function compareSpecContextOrdinal(left, right) {
14677
+ return left < right ? -1 : left > right ? 1 : 0;
14678
+ }
14650
14679
  var SPEC_CONTEXT_DIGEST_ALGORITHM = "sha256";
14651
14680
  function specContextDigest(rawBytes) {
14652
14681
  return `${SPEC_CONTEXT_DIGEST_ALGORITHM}:${createHash4(SPEC_CONTEXT_DIGEST_ALGORITHM).update(rawBytes).digest("hex")}`;
@@ -14661,9 +14690,6 @@ function formatUnreadableContextDocumentError(path4) {
14661
14690
  return `Spec context document unreadable: ${path4}`;
14662
14691
  }
14663
14692
 
14664
- // src/lib/node-status/ci-projection.ts
14665
- import { parse as parseYaml3 } from "yaml";
14666
-
14667
14693
  // src/lib/node-status/classify.ts
14668
14694
  var NODE_STATUS_SCHEMA_VERSION = 1;
14669
14695
  var NODE_STATUS_JSON_INDENT = 2;
@@ -14739,9 +14765,11 @@ function verificationPassed(verification) {
14739
14765
  import { readFileSync } from "fs";
14740
14766
  import { join as join19 } from "path";
14741
14767
  var NODE_STATUS_EXCLUDE_FILENAME = "EXCLUDE";
14742
- var PATH_SEGMENT_SEPARATOR2 = "/";
14743
- var CURRENT_DIRECTORY_SEGMENT = ".";
14744
- var PARENT_DIRECTORY_SEGMENT = "..";
14768
+ var NODE_STATUS_EXCLUDE_PATH_GRAMMAR = {
14769
+ SEGMENT_SEPARATOR: "/",
14770
+ CURRENT_DIRECTORY_SEGMENT: ".",
14771
+ PARENT_DIRECTORY_SEGMENT: ".."
14772
+ };
14745
14773
  function isNodeError(err) {
14746
14774
  return err instanceof Error && "code" in err;
14747
14775
  }
@@ -14752,14 +14780,17 @@ function parseExcludeEntries(content) {
14752
14780
  return content.split("\n").map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#")).map(validateExcludeEntry);
14753
14781
  }
14754
14782
  function validateExcludeEntry(entry) {
14755
- const segments = entry.split(PATH_SEGMENT_SEPARATOR2);
14756
- if (entry.startsWith(PATH_SEGMENT_SEPARATOR2) || segments.some(
14757
- (segment) => segment.length === 0 || segment === CURRENT_DIRECTORY_SEGMENT || segment === PARENT_DIRECTORY_SEGMENT
14783
+ const segments = entry.split(NODE_STATUS_EXCLUDE_PATH_GRAMMAR.SEGMENT_SEPARATOR);
14784
+ if (entry.startsWith(NODE_STATUS_EXCLUDE_PATH_GRAMMAR.SEGMENT_SEPARATOR) || segments.some(
14785
+ (segment) => segment.length === 0 || segment === NODE_STATUS_EXCLUDE_PATH_GRAMMAR.CURRENT_DIRECTORY_SEGMENT || segment === NODE_STATUS_EXCLUDE_PATH_GRAMMAR.PARENT_DIRECTORY_SEGMENT
14758
14786
  )) {
14759
- throw new Error(`Invalid ${SPEC_TREE_CONFIG.ROOT_DIRECTORY}/${NODE_STATUS_EXCLUDE_FILENAME} entry: ${entry}`);
14787
+ throw new Error(nodeStatusInvalidExcludeEntryMessage(entry));
14760
14788
  }
14761
14789
  return entry;
14762
14790
  }
14791
+ function nodeStatusInvalidExcludeEntryMessage(entry) {
14792
+ return `Invalid ${SPEC_TREE_CONFIG.ROOT_DIRECTORY}/${NODE_STATUS_EXCLUDE_FILENAME} entry: ${entry}`;
14793
+ }
14763
14794
  function createNodeStatusExcludeReader(productDir) {
14764
14795
  let content;
14765
14796
  try {
@@ -14961,12 +14992,14 @@ async function updateNodeStatus(options) {
14961
14992
  continue;
14962
14993
  }
14963
14994
  const evidence = evidenceByNode.get(node.id) ?? [];
14995
+ const statusPath = nodeStatusPath(productDir, node.id);
14996
+ const committedVerification = readNodeStatus(dirname11(statusPath))?.[NODE_STATUS_FIELD.VERIFICATION];
14964
14997
  const verification = await resolveVerification(node, {
14965
14998
  evidence,
14966
14999
  isExcluded: excludeReader.isExcluded(node),
14967
- resolveOutcome
15000
+ resolveOutcome,
15001
+ committedOutcomes: testOutcomes(committedVerification)
14968
15002
  });
14969
- const statusPath = nodeStatusPath(productDir, node.id);
14970
15003
  liveStatusPaths.add(statusPath);
14971
15004
  await writeNodeStatus(statusPath, verification);
14972
15005
  }
@@ -14977,7 +15010,8 @@ async function resolveVerification(node, input) {
14977
15010
  if (input.isExcluded) return createTestVerification(input.evidence, NODE_STATUS_EVIDENCE_OUTCOME.NOT_RUN);
14978
15011
  return createTestVerificationFromOutcomes(
14979
15012
  input.evidence,
14980
- await input.resolveOutcome(node.id, evidencePaths(input.evidence))
15013
+ await input.resolveOutcome(node.id, evidencePaths(input.evidence)),
15014
+ input.committedOutcomes
14981
15015
  );
14982
15016
  }
14983
15017
  function collectEvidenceByNode(snapshot) {
@@ -14993,16 +15027,23 @@ function collectEvidenceByNode(snapshot) {
14993
15027
  }
14994
15028
  function createTestVerification(evidence, outcome) {
14995
15029
  const outcomes = Object.fromEntries(evidence.map((entry) => [evidencePath(entry), outcome]));
14996
- return createTestVerificationFromOutcomes(evidence, outcomes);
15030
+ return createTestVerificationFromOutcomes(evidence, outcomes, {});
14997
15031
  }
14998
- function createTestVerificationFromOutcomes(evidence, resolvedOutcomes) {
15032
+ function createTestVerificationFromOutcomes(evidence, resolvedOutcomes, committedOutcomes) {
14999
15033
  const outcomes = {};
15000
15034
  for (const entry of evidence) {
15001
15035
  const path4 = evidencePath(entry);
15002
- outcomes[path4] = resolvedOutcomes[path4] ?? NODE_STATUS_EVIDENCE_OUTCOME.NOT_RUN;
15036
+ outcomes[path4] = resolvedOutcomes[path4] ?? committedOutcomes[path4] ?? NODE_STATUS_EVIDENCE_OUTCOME.NOT_RUN;
15003
15037
  }
15004
15038
  return { [NODE_STATUS_VERIFICATION_MECHANISM.TEST]: createNodeStatusMechanismRecord(outcomes) };
15005
15039
  }
15040
+ function testOutcomes(verification) {
15041
+ const record7 = verification?.[NODE_STATUS_VERIFICATION_MECHANISM.TEST];
15042
+ if (record7 === void 0) return {};
15043
+ return Object.fromEntries(
15044
+ Object.entries(record7).filter(([reference]) => reference !== NODE_STATUS_FIELD.OVERALL)
15045
+ );
15046
+ }
15006
15047
  function evidencePaths(evidence) {
15007
15048
  return evidence.map(evidencePath);
15008
15049
  }
@@ -15152,7 +15193,7 @@ function resolveSpecContextTarget(snapshot, input) {
15152
15193
  if (candidates.length > 1) {
15153
15194
  return {
15154
15195
  failure: {
15155
- candidates: candidates.map(nodeSegment).sort((left, right) => left.localeCompare(right)),
15196
+ candidates: candidates.map(nodeSegment).sort(compareSpecContextOrdinal),
15156
15197
  input,
15157
15198
  kind: SPEC_CONTEXT_TARGET_FAILURE_KIND.AMBIGUOUS_SEGMENT,
15158
15199
  segment
@@ -15201,7 +15242,7 @@ function refPath(ref) {
15201
15242
  return ref?.path;
15202
15243
  }
15203
15244
  function sortPaths(paths) {
15204
- return [...paths].sort((left, right) => left.localeCompare(right));
15245
+ return [...paths].sort(compareSpecContextOrdinal);
15205
15246
  }
15206
15247
  function fullSpecPath(path4) {
15207
15248
  return path4.startsWith(SPEC_TREE_ROOT_PREFIX) ? path4 : `${SPEC_TREE_ROOT_PREFIX}${path4}`;
@@ -15247,11 +15288,11 @@ function lowerIndexSiblingsForContextNodes(snapshot, contextNodes) {
15247
15288
  }
15248
15289
  }
15249
15290
  lowerSiblings.sort((left, right) => {
15250
- const parentComparison = (left.parentId ?? "").localeCompare(right.parentId ?? "");
15291
+ const parentComparison = compareSpecContextOrdinal(left.parentId ?? "", right.parentId ?? "");
15251
15292
  if (parentComparison !== 0) return parentComparison;
15252
15293
  const orderComparison = left.order - right.order;
15253
15294
  if (orderComparison !== 0) return orderComparison;
15254
- return left.id.localeCompare(right.id);
15295
+ return compareSpecContextOrdinal(left.id, right.id);
15255
15296
  });
15256
15297
  return lowerSiblings;
15257
15298
  }
@@ -15386,9 +15427,7 @@ async function citedDecisionDocuments(productDir, sources, knownPaths, includePa
15386
15427
  }
15387
15428
  async function localOverlayPaths(productDir, trackedPaths) {
15388
15429
  if (trackedPaths !== void 0) {
15389
- return [...trackedPaths].filter((path4) => isLocalOverlayPath(path4)).sort(
15390
- (left, right) => left.localeCompare(right)
15391
- );
15430
+ return [...trackedPaths].filter((path4) => isLocalOverlayPath(path4)).sort(compareSpecContextOrdinal);
15392
15431
  }
15393
15432
  let entries;
15394
15433
  try {
@@ -15396,7 +15435,7 @@ async function localOverlayPaths(productDir, trackedPaths) {
15396
15435
  } catch {
15397
15436
  return [];
15398
15437
  }
15399
- return entries.filter((name) => name.endsWith(SPEC_TREE_GRAMMAR.LOCAL_OVERLAYS.EXTENSION)).sort((left, right) => left.localeCompare(right)).map((name) => childSpecPath(SPEC_CONTEXT_LOCAL_OVERLAY_DIRECTORY, name));
15438
+ return entries.filter((name) => name.endsWith(SPEC_TREE_GRAMMAR.LOCAL_OVERLAYS.EXTENSION)).sort(compareSpecContextOrdinal).map((name) => childSpecPath(SPEC_CONTEXT_LOCAL_OVERLAY_DIRECTORY, name));
15400
15439
  }
15401
15440
  async function withDocumentContent(productDir, read, scannedDocuments) {
15402
15441
  const enriched = [];
@@ -15789,7 +15828,7 @@ function groupTestFiles(testFiles, languages) {
15789
15828
 
15790
15829
  // src/domains/test/targeting.ts
15791
15830
  var TESTS_DIRECTORY_NAME3 = SPEC_TREE_EVIDENCE_FILE.DIRECTORY_NAME;
15792
- var PATH_SEGMENT_SEPARATOR3 = "/";
15831
+ var PATH_SEGMENT_SEPARATOR2 = "/";
15793
15832
  function normalizeTargetOperand(operand) {
15794
15833
  return normalizePathPrefix(operand);
15795
15834
  }
@@ -15799,7 +15838,7 @@ function matchOperand(discovered, operand, recursive) {
15799
15838
  return applyPathFilter(discovered, { include: [normalized] });
15800
15839
  }
15801
15840
  return applyPathFilter(discovered, {
15802
- include: [`${normalized}${PATH_SEGMENT_SEPARATOR3}${TESTS_DIRECTORY_NAME3}`]
15841
+ include: [`${normalized}${PATH_SEGMENT_SEPARATOR2}${TESTS_DIRECTORY_NAME3}`]
15803
15842
  });
15804
15843
  }
15805
15844
  function resolveTargetedTestFiles(discovered, selection) {
@@ -15842,7 +15881,7 @@ async function runTests(options, deps) {
15842
15881
  for (const group of groups) {
15843
15882
  const invocation = await group.language.runTests(
15844
15883
  {
15845
- projectRoot: options.productDir,
15884
+ productDir: options.productDir,
15846
15885
  testPaths: group.testPaths,
15847
15886
  excludedNodePaths: runnerExcludedNodePaths
15848
15887
  },
@@ -16071,7 +16110,7 @@ async function readTestRunStatePath(runFilePath, fs4) {
16071
16110
  return { ok: true, value: validated.value };
16072
16111
  }
16073
16112
  function validateTestRunState(value) {
16074
- if (!isRecord8(value)) return { ok: false, error: "testing run state must be an object" };
16113
+ if (!isRecord9(value)) return { ok: false, error: "testing run state must be an object" };
16075
16114
  const branchName = readString2(value, TEST_RUN_STATE_FIELDS.BRANCH_NAME);
16076
16115
  if (!branchName.ok) return branchName;
16077
16116
  const headSha = readString2(value, TEST_RUN_STATE_FIELDS.HEAD_SHA);
@@ -16114,7 +16153,7 @@ function readRunnerOutcomes(raw) {
16114
16153
  }
16115
16154
  const outcomes = [];
16116
16155
  for (const entry of raw) {
16117
- if (!isRecord8(entry)) {
16156
+ if (!isRecord9(entry)) {
16118
16157
  return { ok: false, error: `${TEST_RUN_STATE_FIELDS.RUNNER_OUTCOMES} entries must be objects` };
16119
16158
  }
16120
16159
  const runnerId = readString2(entry, TEST_RUNNER_OUTCOME_FIELDS.RUNNER_ID);
@@ -16135,7 +16174,7 @@ function readProductInputDigests(raw) {
16135
16174
  }
16136
16175
  const digests = [];
16137
16176
  for (const entry of raw) {
16138
- if (!isRecord8(entry)) {
16177
+ if (!isRecord9(entry)) {
16139
16178
  return { ok: false, error: `${TEST_RUN_STATE_FIELDS.PRODUCT_INPUT_DIGESTS} entries must be objects` };
16140
16179
  }
16141
16180
  const descriptorId = readString2(entry, PRODUCT_INPUT_DIGEST_FIELDS.DESCRIPTOR_ID);
@@ -16197,7 +16236,7 @@ function testingWriteError(error) {
16197
16236
  function withDomainErrorDetail(domainError, detail) {
16198
16237
  return detail === void 0 ? domainError : `${domainError}: ${detail}`;
16199
16238
  }
16200
- function isRecord8(value) {
16239
+ function isRecord9(value) {
16201
16240
  return typeof value === "object" && value !== null && !Array.isArray(value);
16202
16241
  }
16203
16242
  function sha256Hex2(value) {
@@ -16470,7 +16509,7 @@ async function relatedTestPaths(sourceFiles, options, baseRef, candidateTestPath
16470
16509
  if (language.relatedTestPaths === void 0) continue;
16471
16510
  const relatedDeps = deps.relatedDepsFor(language.name);
16472
16511
  const languageResolution = await language.relatedTestPaths(
16473
- { projectRoot: options.productDir, sourcePaths: sourceFiles, candidateTestPaths: candidateTestPaths2, baseRef },
16512
+ { productDir: options.productDir, sourcePaths: sourceFiles, candidateTestPaths: candidateTestPaths2, baseRef },
16474
16513
  options.staged === true ? { ...relatedDeps, readFile: (path4) => readStagedFile(options.productDir, path4, deps.git) } : relatedDeps
16475
16514
  );
16476
16515
  for (const sourceFile of languageResolution.resolvedSourcePaths) resolved.add(sourceFile);
@@ -16877,16 +16916,6 @@ async function runTestsCommand(options, deps) {
16877
16916
  );
16878
16917
  return { dispatch, runFile, recorded };
16879
16918
  }
16880
- async function runNodeCommand(options, deps) {
16881
- const recording = resolveRecordingDependencies(deps);
16882
- const runFile = await reserveRunFile(options.productDir, recording);
16883
- const dispatch = await runTests(
16884
- { productDir: options.productDir, registry: deps.registry, passingScope: { include: [options.nodePath] } },
16885
- { runnerDepsFor: deps.runnerDepsFor }
16886
- );
16887
- const recorded = await recordRun(runFile, options.productDir, dispatch, recording, deps.registry);
16888
- return { dispatch, runFile, recorded };
16889
- }
16890
16919
 
16891
16920
  // src/commands/spec/node-outcome-resolver.ts
16892
16921
  var PATH_SEPARATOR3 = "/";
@@ -16895,9 +16924,6 @@ function createNodeOutcomeResolver(deps) {
16895
16924
  let evidence;
16896
16925
  const currentInputsByCoveredPaths = /* @__PURE__ */ new Map();
16897
16926
  const sharedEvidence = () => evidence ??= loadResolverEvidence(deps);
16898
- const refreshEvidence = () => {
16899
- evidence = loadResolverEvidence(deps);
16900
- };
16901
16927
  const currentInputsFor = (coveredPaths) => {
16902
16928
  const cacheKey = coveredPathCollectionKey(coveredPaths);
16903
16929
  const cached = currentInputsByCoveredPaths.get(cacheKey);
@@ -16912,13 +16938,13 @@ function createNodeOutcomeResolver(deps) {
16912
16938
  const { discoveredTestPaths, terminalRuns } = await sharedEvidence();
16913
16939
  const nodePath = `${SPEC_TREE_CONFIG.ROOT_DIRECTORY}${PATH_SEPARATOR3}${nodeId}`;
16914
16940
  const nodeTestPaths = filterNodeTestPaths(discoveredTestPaths, nodePath, evidencePaths2);
16915
- const usable = await usableRecordedOutcome(discoveredTestPaths, terminalRuns, nodeTestPaths, currentInputsFor);
16916
- if (usable !== void 0) {
16917
- return usable;
16918
- }
16919
- const { dispatch } = await runNodeCommand({ productDir: deps.productDir, nodePath }, deps);
16920
- refreshEvidence();
16921
- return outcomesForPaths(dispatch.outcomes, nodeTestPaths);
16941
+ return recordedOutcomes(
16942
+ discoveredTestPaths,
16943
+ terminalRuns,
16944
+ evidencePaths2,
16945
+ nodeTestPaths,
16946
+ currentInputsFor
16947
+ );
16922
16948
  };
16923
16949
  }
16924
16950
  function coveredPathCollectionKey(coveredPaths) {
@@ -16934,24 +16960,27 @@ function filterNodeTestPaths(discoveredTestPaths, nodePath, evidencePaths2) {
16934
16960
  const discovered = new Set(discoveredTestPaths.filter((path4) => path4.startsWith(prefix)));
16935
16961
  return evidencePaths2.filter((path4) => discovered.has(path4));
16936
16962
  }
16937
- async function usableRecordedOutcome(discoveredTestPaths, terminalRuns, nodeTestPaths, currentInputsFor) {
16938
- const latest = selectLatestTerminalTestRunForNode(terminalRuns, nodeTestPaths);
16939
- if (latest === void 0) {
16940
- return void 0;
16941
- }
16942
- const runCoveredPaths = latest.state.runnerOutcomes.flatMap((outcome) => outcome.testPaths);
16963
+ async function recordedOutcomes(discoveredTestPaths, terminalRuns, evidencePaths2, nodeTestPaths, currentInputsFor) {
16964
+ const resolved = Object.fromEntries(
16965
+ evidencePaths2.map((path4) => [path4, NODE_STATUS_EVIDENCE_OUTCOME.NOT_RUN])
16966
+ );
16943
16967
  const presentTestPaths = new Set(discoveredTestPaths);
16944
- if (!runCoveredPaths.every((path4) => presentTestPaths.has(path4))) {
16945
- return void 0;
16968
+ for (const path4 of nodeTestPaths) {
16969
+ const latest = selectLatestTerminalTestRunForNode(terminalRuns, [path4]);
16970
+ if (latest === void 0) continue;
16971
+ const runCoveredPaths = latest.state.runnerOutcomes.flatMap((outcome) => outcome.testPaths);
16972
+ if (!runCoveredPaths.every((coveredPath) => presentTestPaths.has(coveredPath))) {
16973
+ delete resolved[path4];
16974
+ continue;
16975
+ }
16976
+ const current = await currentInputsFor(runCoveredPaths);
16977
+ if (!isStalenessMatch(extractStalenessInputs(latest.state), current)) {
16978
+ delete resolved[path4];
16979
+ continue;
16980
+ }
16981
+ resolved[path4] = outcomeForPath(latest.state.runnerOutcomes, path4);
16946
16982
  }
16947
- const current = await currentInputsFor(runCoveredPaths);
16948
- const fresh = isStalenessMatch(extractStalenessInputs(latest.state), current);
16949
- return fresh && latest.state.status === TEST_RUN_STATE_STATUS.PASSED ? outcomesForPaths(latest.state.runnerOutcomes, nodeTestPaths) : void 0;
16950
- }
16951
- function outcomesForPaths(outcomes, nodeTestPaths) {
16952
- return Object.fromEntries(
16953
- nodeTestPaths.map((path4) => [path4, outcomeForPath(outcomes, path4)])
16954
- );
16983
+ return resolved;
16955
16984
  }
16956
16985
  function outcomeForPath(outcomes, path4) {
16957
16986
  const covering = outcomes.filter((outcome) => outcome.testPaths.includes(path4));
@@ -17173,11 +17202,11 @@ function coveredProductInputPaths(coveredTestPaths2) {
17173
17202
  function excludeFlag(nodePath) {
17174
17203
  return `${PYTHON_PYTEST_IGNORE_FLAG_PREFIX}${nodePath}${PYTHON_PYTEST_IGNORE_FLAG_SUFFIX}`;
17175
17204
  }
17176
- function detect(projectRoot, deps) {
17177
- return deps?.isLanguagePresent?.(projectRoot) ?? detectPython(projectRoot).present;
17205
+ function detect(productDir, deps) {
17206
+ return deps?.isLanguagePresent?.(productDir) ?? detectPython(productDir).present;
17178
17207
  }
17179
17208
  async function runTests2(request, deps) {
17180
- if (!detect(request.projectRoot, deps)) {
17209
+ if (!detect(request.productDir, deps)) {
17181
17210
  return { invoked: false };
17182
17211
  }
17183
17212
  const args = [
@@ -17248,7 +17277,7 @@ function createJournalReporter(sink) {
17248
17277
  async function runTestsStreaming(request, deps) {
17249
17278
  const reporter = createJournalReporter(deps.sink);
17250
17279
  await deps.starter.start({
17251
- projectRoot: request.projectRoot,
17280
+ productDir: request.productDir,
17252
17281
  testPaths: request.testPaths,
17253
17282
  reporters: [reporter]
17254
17283
  });
@@ -17261,7 +17290,7 @@ function createVitestRunStarter() {
17261
17290
  const priorExitCode = process.exitCode;
17262
17291
  try {
17263
17292
  const vitest = await startVitest("test", [...options.testPaths], {
17264
- root: options.projectRoot,
17293
+ root: options.productDir,
17265
17294
  watch: false,
17266
17295
  reporters: [...options.reporters]
17267
17296
  });
@@ -17310,17 +17339,17 @@ function matchesTestFile2(filePath) {
17310
17339
  function excludeFlag2(nodePath) {
17311
17340
  return `${TYPESCRIPT_VITEST_EXCLUDE_FLAG_PREFIX}${nodePath}${TYPESCRIPT_VITEST_EXCLUDE_FLAG_SUFFIX}`;
17312
17341
  }
17313
- function detect2(projectRoot, deps) {
17314
- return deps?.isLanguagePresent?.(projectRoot) ?? detectTypeScript(projectRoot).present;
17342
+ function detect2(productDir, deps) {
17343
+ return deps?.isLanguagePresent?.(productDir) ?? detectTypeScript(productDir).present;
17315
17344
  }
17316
17345
  async function runTests3(request, deps) {
17317
- if (!detect2(request.projectRoot, deps)) {
17346
+ if (!detect2(request.productDir, deps)) {
17318
17347
  return { invoked: false };
17319
17348
  }
17320
17349
  const args = [
17321
17350
  ...VITEST_INVOKE_ARGS,
17322
17351
  VITEST_ROOT_FLAG,
17323
- request.projectRoot,
17352
+ request.productDir,
17324
17353
  ...request.testPaths,
17325
17354
  ...request.excludedNodePaths.map(excludeFlag2)
17326
17355
  ];
@@ -17346,7 +17375,7 @@ function importSpecifiers(sourceText, testPath) {
17346
17375
  }
17347
17376
  return specifiers;
17348
17377
  }
17349
- function isRecord9(value) {
17378
+ function isRecord10(value) {
17350
17379
  return typeof value === "object" && value !== null && !Array.isArray(value);
17351
17380
  }
17352
17381
  function hasErrorCode3(error, code) {
@@ -17381,11 +17410,11 @@ function pathMappingFromTsconfigPath(pathAlias, targets) {
17381
17410
  };
17382
17411
  }
17383
17412
  function pathMappingsFromTsconfig(config) {
17384
- if (!isRecord9(config)) return [];
17413
+ if (!isRecord10(config)) return [];
17385
17414
  const compilerOptions = config[TSCONFIG_COMPILER_OPTIONS_KEY];
17386
- if (!isRecord9(compilerOptions)) return [];
17415
+ if (!isRecord10(compilerOptions)) return [];
17387
17416
  const paths = compilerOptions[TSCONFIG_PATHS_KEY];
17388
- if (!isRecord9(paths)) return [];
17417
+ if (!isRecord10(paths)) return [];
17389
17418
  return Object.entries(paths).flatMap(([pathAlias, targets]) => {
17390
17419
  const mapping = pathMappingFromTsconfigPath(pathAlias, targets);
17391
17420
  return mapping === null ? [] : [mapping];
@@ -17515,7 +17544,7 @@ async function readCandidateTest(path4, deps, moduleTextCache) {
17515
17544
  return loaded;
17516
17545
  }
17517
17546
  async function relatedTestPaths2(request, deps) {
17518
- if (!detect2(request.projectRoot, deps)) {
17547
+ if (!detect2(request.productDir, deps)) {
17519
17548
  return { testPaths: [], resolvedSourcePaths: [] };
17520
17549
  }
17521
17550
  const sourcePaths = new Set(request.sourcePaths);
@@ -17545,7 +17574,7 @@ async function relatedTestPaths2(request, deps) {
17545
17574
  return { testPaths, resolvedSourcePaths: [...resolvedSourcePaths] };
17546
17575
  }
17547
17576
  async function runTestsStreaming2(request, deps) {
17548
- if (!detect2(request.projectRoot, deps)) {
17577
+ if (!detect2(request.productDir, deps)) {
17549
17578
  return { invoked: false };
17550
17579
  }
17551
17580
  const terminalStatus = await runTestsStreaming(request, {
@@ -17571,141 +17600,13 @@ var testingRegistry = {
17571
17600
  languages: [typescriptTestingLanguage, pythonTestingLanguage]
17572
17601
  };
17573
17602
 
17574
- // src/interfaces/cli/test-runner-deps.ts
17575
- import { createWriteStream } from "fs";
17576
- import { mkdtemp as mkdtemp3, readFile as readFile12 } from "fs/promises";
17577
- import { tmpdir as tmpdir3 } from "os";
17578
- import { join as join28 } from "path";
17579
- import { finished } from "stream/promises";
17580
- var PROCESS_FAILURE_EXIT_CODE = 1;
17581
- var AGENT_ARTIFACT_DIR_PREFIX = "spx-test-agent-";
17582
- var STDOUT_FILE_SUFFIX = "stdout.log";
17583
- var STDERR_FILE_SUFFIX = "stderr.log";
17584
- var ARTIFACT_INDEX_WIDTH = 3;
17585
- var ARTIFACT_INDEX_RADIX = 10;
17586
- var ARTIFACT_FILE_FLAGS = "wx";
17587
- var EMPTY_RUNNER_ARGS = [];
17588
- function createCommandRunner(productDir, outStream) {
17589
- return (command, args) => new Promise((resolveResult) => {
17590
- const child = spawnManagedSubprocess(lifecycleProcessRunner, command, args, {
17591
- cwd: productDir
17592
- });
17593
- child.stdout?.pipe(outStream);
17594
- child.stderr?.pipe(process.stderr);
17595
- child.on("close", (code) => resolveResult({ exitCode: code ?? PROCESS_FAILURE_EXIT_CODE }));
17596
- child.on("error", () => resolveResult({ exitCode: PROCESS_FAILURE_EXIT_CODE }));
17597
- });
17598
- }
17599
- function createRelatedCommandRunner(productDir) {
17600
- return (command, args) => new Promise((resolveResult) => {
17601
- const stdout = [];
17602
- const stderr = [];
17603
- const child = spawnManagedSubprocess(lifecycleProcessRunner, command, args, {
17604
- cwd: productDir
17605
- });
17606
- child.stdout?.on("data", (chunk) => stdout.push(String(chunk)));
17607
- child.stderr?.on("data", (chunk) => stderr.push(String(chunk)));
17608
- child.on("close", (code) => resolveResult({
17609
- exitCode: code ?? PROCESS_FAILURE_EXIT_CODE,
17610
- stdout: stdout.join(""),
17611
- stderr: stderr.join("")
17612
- }));
17613
- child.on("error", (error) => resolveResult({
17614
- exitCode: PROCESS_FAILURE_EXIT_CODE,
17615
- stdout: stdout.join(""),
17616
- stderr: error.message
17617
- }));
17618
- });
17619
- }
17620
- function createRunnerDepsFor(productDir, outStream = process.stdout) {
17621
- const runCommand = createCommandRunner(productDir, outStream);
17622
- return () => ({ runCommand });
17623
- }
17624
- function createRelatedDepsFor(productDir) {
17625
- const runCommand = createRelatedCommandRunner(productDir);
17626
- return () => ({ runCommand, readFile: (path4) => readFile12(join28(productDir, path4), "utf8") });
17627
- }
17628
- function artifactFileName(index, suffix) {
17629
- return `${index.toString(ARTIFACT_INDEX_RADIX).padStart(ARTIFACT_INDEX_WIDTH, "0")}-${suffix}`;
17630
- }
17631
- function createArtifactWriters(root, index, createArtifactWriteStream) {
17632
- const stdoutPath = join28(root, artifactFileName(index, STDOUT_FILE_SUFFIX));
17633
- const stderrPath = join28(root, artifactFileName(index, STDERR_FILE_SUFFIX));
17634
- const stdoutFile = createArtifactWriteStream(stdoutPath);
17635
- const stderrFile = createArtifactWriteStream(stderrPath);
17636
- return {
17637
- stdoutPath,
17638
- stderrPath,
17639
- stdoutFile,
17640
- stderrFile,
17641
- completion: Promise.all([finished(stdoutFile), finished(stderrFile)])
17642
- };
17643
- }
17644
- async function finishArtifactWriters(writers) {
17645
- writers.stdoutFile.end();
17646
- writers.stderrFile.end();
17647
- await writers.completion;
17648
- return {
17649
- stdoutPath: writers.stdoutPath,
17650
- stderrPath: writers.stderrPath
17651
- };
17652
- }
17653
- function runCapturedCommand(request) {
17654
- return new Promise((resolveResult) => {
17655
- let settled = false;
17656
- const resolveOnce = (result) => {
17657
- if (settled) return;
17658
- settled = true;
17659
- resolveResult(result);
17660
- };
17661
- const child = spawnManagedSubprocess(request.processRunner, request.command, request.args, {
17662
- cwd: request.productDir,
17663
- env: request.inheritedEnv
17664
- });
17665
- child.stdout?.pipe(request.writers.stdoutFile);
17666
- child.stderr?.pipe(request.writers.stderrFile);
17667
- const resolveWithExitCode = (exitCode) => {
17668
- finishArtifactWriters(request.writers).then((output) => resolveOnce({ exitCode, output })).catch(() => resolveOnce({ exitCode: PROCESS_FAILURE_EXIT_CODE }));
17669
- };
17670
- request.writers.completion.catch(() => {
17671
- child.kill();
17672
- resolveOnce({ exitCode: PROCESS_FAILURE_EXIT_CODE });
17673
- });
17674
- child.on("close", (code) => resolveWithExitCode(code ?? PROCESS_FAILURE_EXIT_CODE));
17675
- child.on("error", () => resolveWithExitCode(PROCESS_FAILURE_EXIT_CODE));
17676
- });
17677
- }
17678
- function createAgentOutputCommandRunner(productDir, options = {}) {
17679
- let artifactRoot;
17680
- const processRunner = options.processRunner ?? lifecycleProcessRunner;
17681
- const inheritedEnv = options.env ?? process.env;
17682
- const createArtifactWriteStream = options.createArtifactWriteStream ?? ((path4) => createWriteStream(path4, { flags: ARTIFACT_FILE_FLAGS }));
17683
- let nextArtifactIndex = 0;
17684
- return async (command, args = EMPTY_RUNNER_ARGS) => {
17685
- nextArtifactIndex += 1;
17686
- artifactRoot ??= mkdtemp3(join28(options.tmpDir ?? tmpdir3(), AGENT_ARTIFACT_DIR_PREFIX));
17687
- const root = await artifactRoot;
17688
- return runCapturedCommand({
17689
- productDir,
17690
- command,
17691
- args,
17692
- processRunner,
17693
- inheritedEnv,
17694
- writers: createArtifactWriters(root, nextArtifactIndex, createArtifactWriteStream)
17695
- });
17696
- };
17697
- }
17698
- function createAgentRunnerDepsFor(productDir, options = {}) {
17699
- const runCommand = createAgentOutputCommandRunner(productDir, options);
17700
- return () => ({ runCommand });
17701
- }
17702
-
17703
17603
  // src/interfaces/cli/spec.ts
17704
17604
  var SPEC_DOMAIN_CLI = {
17705
17605
  COMMAND: "spec",
17706
17606
  STATUS_COMMAND: "status",
17707
17607
  NEXT_COMMAND: "next",
17708
17608
  CONTEXT_COMMAND: "context",
17609
+ RETIRED_APPLY_COMMAND: "apply",
17709
17610
  JSON_OPTION: "--json",
17710
17611
  CONTENT_OPTION: "--content",
17711
17612
  FORMAT_OPTION_FLAG: "--format",
@@ -17826,8 +17727,7 @@ function registerSpecCommands(specCmd, invocation) {
17826
17727
  update: true,
17827
17728
  resolveOutcomeFor: (productDir2) => createNodeOutcomeResolver({
17828
17729
  productDir: productDir2,
17829
- registry: testingRegistry,
17830
- runnerDepsFor: createRunnerDepsFor(productDir2, process.stderr)
17730
+ registry: testingRegistry
17831
17731
  })
17832
17732
  }) : await statusCommand({ cwd: productDir(), format: format2, onWarning });
17833
17733
  writeOutput3(invocation.io, output);
@@ -17951,70 +17851,199 @@ function formatAgentTestOutput(run) {
17951
17851
  return `${lines.join(NEWLINE2)}${NEWLINE2}`;
17952
17852
  }
17953
17853
 
17954
- // src/interfaces/cli/test.ts
17955
- var TESTING_CLI = {
17956
- commandName: "test",
17957
- description: "Run spec-tree tests across product languages",
17958
- agentOption: "--agent",
17959
- agentDescription: "Capture raw runner output and print a compact agent summary",
17960
- recursiveShortFlag: "-r",
17961
- recursiveLongFlag: "--recursive",
17962
- recursiveDescription: "Extend a node-path operand to its descendant nodes' tests",
17963
- changedLongFlag: "--changed",
17964
- changedDescription: "Run only tests affected by changes against the selected base",
17965
- stagedLongFlag: "--staged",
17966
- stagedDescription: "With --changed, diff the staged snapshot instead of the worktree",
17967
- baseLongFlag: "--base",
17968
- baseOperand: "<ref>",
17969
- baseDescription: "Base ref for --changed; defaults to origin of the default branch",
17970
- targetsArgument: "[targets...]",
17971
- targetsDescription: "Node paths or test-file paths to run; omit to run the full discovered suite",
17972
- passingSubcommand: "passing",
17973
- passingDescription: "Run only the tests within the configured passing scope"
17974
- };
17975
- var UNMATCHED_TEST_FILES_WARNING = "Skipped test files with no registered runner";
17976
- var GATED_TEST_RUNNERS_WARNING = "Skipped test files because their registered runner was gated out";
17977
- var UNRESOLVED_TARGETS_WARNING = "No tests matched these operands";
17978
- var UNRESOLVED_CHANGED_SOURCE_WARNING = "No related-test capability resolved these changed source files";
17979
- var TESTING_PRODUCT_DIR_WARNING = {
17980
- NOT_GIT_REPOSITORY: `Warning: Not in a git repository. Reading ${SPEC_TREE_CONFIG.ROOT_DIRECTORY} tests relative to the current working directory.`
17981
- };
17982
- async function resolveTestProductDir(cwd, writeWarning) {
17983
- const { productDir, isGitRepo } = await detectWorktreeProductRoot(cwd);
17984
- writeWarning(isGitRepo ? void 0 : TESTING_PRODUCT_DIR_WARNING.NOT_GIT_REPOSITORY);
17985
- return productDir;
17986
- }
17987
- async function runTestsThroughCommand(productDir, passing, io, targets, changed) {
17988
- try {
17989
- return await runTestsCommand(
17990
- { productDir, passing, targets, changed },
17991
- {
17992
- registry: testingRegistry,
17993
- runnerDepsFor: createRunnerDepsFor(productDir),
17994
- relatedDepsFor: createRelatedDepsFor(productDir)
17995
- }
17996
- );
17997
- } catch (error) {
17998
- io.writeStderr(`${error instanceof Error ? error.message : String(error)}
17999
- `);
18000
- io.exit(PROCESS_FAILURE_EXIT_CODE);
18001
- }
17854
+ // src/interfaces/cli/test-runner-deps.ts
17855
+ import { createWriteStream } from "fs";
17856
+ import { mkdtemp as mkdtemp3, readFile as readFile12 } from "fs/promises";
17857
+ import { tmpdir as tmpdir3 } from "os";
17858
+ import { join as join28 } from "path";
17859
+ import { finished } from "stream/promises";
17860
+ var PROCESS_FAILURE_EXIT_CODE = 1;
17861
+ var AGENT_ARTIFACT_DIR_PREFIX = "spx-test-agent-";
17862
+ var STDOUT_FILE_SUFFIX = "stdout.log";
17863
+ var STDERR_FILE_SUFFIX = "stderr.log";
17864
+ var ARTIFACT_INDEX_WIDTH = 3;
17865
+ var ARTIFACT_INDEX_RADIX = 10;
17866
+ var ARTIFACT_FILE_FLAGS = "wx";
17867
+ var EMPTY_RUNNER_ARGS = [];
17868
+ function createCommandRunner(productDir, outStream, errStream, processRunner) {
17869
+ return (command, args) => new Promise((resolveResult) => {
17870
+ const child = spawnManagedSubprocess(processRunner, command, args, {
17871
+ cwd: productDir
17872
+ });
17873
+ child.stdout?.pipe(outStream);
17874
+ child.stderr?.pipe(errStream);
17875
+ child.on("close", (code) => resolveResult({ exitCode: code ?? PROCESS_FAILURE_EXIT_CODE }));
17876
+ child.on("error", () => resolveResult({ exitCode: PROCESS_FAILURE_EXIT_CODE }));
17877
+ });
18002
17878
  }
18003
- async function runAgentTestsThroughCommand(productDir, passing, io, targets, changed) {
18004
- try {
18005
- return await runTestsCommand(
18006
- { productDir, passing, targets, changed },
18007
- {
18008
- registry: testingRegistry,
18009
- runnerDepsFor: createAgentRunnerDepsFor(productDir),
18010
- relatedDepsFor: createRelatedDepsFor(productDir)
18011
- }
18012
- );
18013
- } catch (error) {
18014
- io.writeStderr(`${error instanceof Error ? error.message : String(error)}
18015
- `);
18016
- io.exit(PROCESS_FAILURE_EXIT_CODE);
18017
- }
17879
+ function createRelatedCommandRunner(productDir, processRunner) {
17880
+ return (command, args) => new Promise((resolveResult) => {
17881
+ const stdout = [];
17882
+ const stderr = [];
17883
+ const child = spawnManagedSubprocess(processRunner, command, args, {
17884
+ cwd: productDir
17885
+ });
17886
+ child.stdout?.on("data", (chunk) => stdout.push(String(chunk)));
17887
+ child.stderr?.on("data", (chunk) => stderr.push(String(chunk)));
17888
+ child.on("close", (code) => resolveResult({
17889
+ exitCode: code ?? PROCESS_FAILURE_EXIT_CODE,
17890
+ stdout: stdout.join(""),
17891
+ stderr: stderr.join("")
17892
+ }));
17893
+ child.on("error", (error) => resolveResult({
17894
+ exitCode: PROCESS_FAILURE_EXIT_CODE,
17895
+ stdout: stdout.join(""),
17896
+ stderr: error.message
17897
+ }));
17898
+ });
17899
+ }
17900
+ function createRunnerDepsFor(productDir, outStream = process.stdout, processRunner = lifecycleProcessRunner, errStream = process.stderr) {
17901
+ const runCommand = createCommandRunner(productDir, outStream, errStream, processRunner);
17902
+ return () => ({ runCommand });
17903
+ }
17904
+ function createRelatedDepsFor(productDir, processRunner = lifecycleProcessRunner) {
17905
+ const runCommand = createRelatedCommandRunner(productDir, processRunner);
17906
+ return () => ({ runCommand, readFile: (path4) => readFile12(join28(productDir, path4), "utf8") });
17907
+ }
17908
+ function artifactFileName(index, suffix) {
17909
+ return `${index.toString(ARTIFACT_INDEX_RADIX).padStart(ARTIFACT_INDEX_WIDTH, "0")}-${suffix}`;
17910
+ }
17911
+ function createArtifactWriters(root, index, createArtifactWriteStream) {
17912
+ const stdoutPath = join28(root, artifactFileName(index, STDOUT_FILE_SUFFIX));
17913
+ const stderrPath = join28(root, artifactFileName(index, STDERR_FILE_SUFFIX));
17914
+ const stdoutFile = createArtifactWriteStream(stdoutPath);
17915
+ const stderrFile = createArtifactWriteStream(stderrPath);
17916
+ return {
17917
+ stdoutPath,
17918
+ stderrPath,
17919
+ stdoutFile,
17920
+ stderrFile,
17921
+ completion: Promise.all([finished(stdoutFile), finished(stderrFile)])
17922
+ };
17923
+ }
17924
+ async function finishArtifactWriters(writers) {
17925
+ writers.stdoutFile.end();
17926
+ writers.stderrFile.end();
17927
+ await writers.completion;
17928
+ return {
17929
+ stdoutPath: writers.stdoutPath,
17930
+ stderrPath: writers.stderrPath
17931
+ };
17932
+ }
17933
+ function runCapturedCommand(request) {
17934
+ return new Promise((resolveResult) => {
17935
+ let settled = false;
17936
+ const resolveOnce = (result) => {
17937
+ if (settled) return;
17938
+ settled = true;
17939
+ resolveResult(result);
17940
+ };
17941
+ const child = spawnManagedSubprocess(request.processRunner, request.command, request.args, {
17942
+ cwd: request.productDir,
17943
+ env: request.inheritedEnv
17944
+ });
17945
+ child.stdout?.pipe(request.writers.stdoutFile);
17946
+ child.stderr?.pipe(request.writers.stderrFile);
17947
+ const resolveWithExitCode = (exitCode) => {
17948
+ finishArtifactWriters(request.writers).then((output) => resolveOnce({ exitCode, output })).catch(() => resolveOnce({ exitCode: PROCESS_FAILURE_EXIT_CODE }));
17949
+ };
17950
+ request.writers.completion.catch(() => {
17951
+ child.kill();
17952
+ resolveOnce({ exitCode: PROCESS_FAILURE_EXIT_CODE });
17953
+ });
17954
+ child.on("close", (code) => resolveWithExitCode(code ?? PROCESS_FAILURE_EXIT_CODE));
17955
+ child.on("error", () => resolveWithExitCode(PROCESS_FAILURE_EXIT_CODE));
17956
+ });
17957
+ }
17958
+ function createAgentOutputCommandRunner(productDir, options = {}) {
17959
+ let artifactRoot;
17960
+ const processRunner = options.processRunner ?? lifecycleProcessRunner;
17961
+ const inheritedEnv = options.env ?? process.env;
17962
+ const createArtifactWriteStream = options.createArtifactWriteStream ?? ((path4) => createWriteStream(path4, { flags: ARTIFACT_FILE_FLAGS }));
17963
+ let nextArtifactIndex = 0;
17964
+ return async (command, args = EMPTY_RUNNER_ARGS) => {
17965
+ nextArtifactIndex += 1;
17966
+ artifactRoot ??= mkdtemp3(join28(options.tmpDir ?? tmpdir3(), AGENT_ARTIFACT_DIR_PREFIX));
17967
+ const root = await artifactRoot;
17968
+ return runCapturedCommand({
17969
+ productDir,
17970
+ command,
17971
+ args,
17972
+ processRunner,
17973
+ inheritedEnv,
17974
+ writers: createArtifactWriters(root, nextArtifactIndex, createArtifactWriteStream)
17975
+ });
17976
+ };
17977
+ }
17978
+ function createAgentRunnerDepsFor(productDir, options = {}) {
17979
+ const runCommand = createAgentOutputCommandRunner(productDir, options);
17980
+ return () => ({ runCommand });
17981
+ }
17982
+
17983
+ // src/interfaces/cli/test.ts
17984
+ var TESTING_CLI = {
17985
+ commandName: "test",
17986
+ description: "Run spec-tree tests across product languages",
17987
+ agentOption: "--agent",
17988
+ agentDescription: "Capture raw runner output and print a compact agent summary",
17989
+ recursiveShortFlag: "-r",
17990
+ recursiveLongFlag: "--recursive",
17991
+ recursiveDescription: "Extend a node-path operand to its descendant nodes' tests",
17992
+ changedLongFlag: "--changed",
17993
+ changedDescription: "Run only tests affected by changes against the selected base",
17994
+ stagedLongFlag: "--staged",
17995
+ stagedDescription: "With --changed, diff the staged snapshot instead of the worktree",
17996
+ baseLongFlag: "--base",
17997
+ baseOperand: "<ref>",
17998
+ baseDescription: "Base ref for --changed; defaults to origin of the default branch",
17999
+ targetsArgument: "[targets...]",
18000
+ targetsDescription: "Node paths or test-file paths to run; omit to run the full discovered suite",
18001
+ passingSubcommand: "passing",
18002
+ passingDescription: "Run only the tests within the configured passing scope"
18003
+ };
18004
+ var UNMATCHED_TEST_FILES_WARNING = "Skipped test files with no registered runner";
18005
+ var GATED_TEST_RUNNERS_WARNING = "Skipped test files because their registered runner was gated out";
18006
+ var UNRESOLVED_TARGETS_WARNING = "No tests matched these operands";
18007
+ var UNRESOLVED_CHANGED_SOURCE_WARNING = "No related-test capability resolved these changed source files";
18008
+ var TESTING_PRODUCT_DIR_WARNING = {
18009
+ NOT_GIT_REPOSITORY: `Warning: Not in a git repository. Reading ${SPEC_TREE_CONFIG.ROOT_DIRECTORY} tests relative to the current working directory.`
18010
+ };
18011
+ async function resolveTestProductDir(cwd, writeWarning) {
18012
+ const { productDir, isGitRepo } = await detectWorktreeProductRoot(cwd);
18013
+ writeWarning(isGitRepo ? void 0 : TESTING_PRODUCT_DIR_WARNING.NOT_GIT_REPOSITORY);
18014
+ return productDir;
18015
+ }
18016
+ async function runTestsThroughCommand(productDir, passing, io, targets, changed) {
18017
+ try {
18018
+ return await runTestsCommand(
18019
+ { productDir, passing, targets, changed },
18020
+ {
18021
+ registry: testingRegistry,
18022
+ runnerDepsFor: createRunnerDepsFor(productDir),
18023
+ relatedDepsFor: createRelatedDepsFor(productDir)
18024
+ }
18025
+ );
18026
+ } catch (error) {
18027
+ io.writeStderr(`${error instanceof Error ? error.message : String(error)}
18028
+ `);
18029
+ io.exit(PROCESS_FAILURE_EXIT_CODE);
18030
+ }
18031
+ }
18032
+ async function runAgentTestsThroughCommand(productDir, passing, io, targets, changed) {
18033
+ try {
18034
+ return await runTestsCommand(
18035
+ { productDir, passing, targets, changed },
18036
+ {
18037
+ registry: testingRegistry,
18038
+ runnerDepsFor: createAgentRunnerDepsFor(productDir),
18039
+ relatedDepsFor: createRelatedDepsFor(productDir)
18040
+ }
18041
+ );
18042
+ } catch (error) {
18043
+ io.writeStderr(`${error instanceof Error ? error.message : String(error)}
18044
+ `);
18045
+ io.exit(PROCESS_FAILURE_EXIT_CODE);
18046
+ }
18018
18047
  }
18019
18048
  function unreportedGroups2(result) {
18020
18049
  const reportedRunnerIds = new Set(result.reports.map((report2) => report2.runnerId));
@@ -18138,6 +18167,12 @@ var testingDomain = createTestingDomain();
18138
18167
  import { existsSync as existsSync9, realpathSync } from "fs";
18139
18168
  import { relative as relative11, resolve as resolve16 } from "path";
18140
18169
 
18170
+ // src/validation/languages/types.ts
18171
+ var VALIDATION_STAGE_PARTICIPATION = {
18172
+ RUN: "run",
18173
+ SKIP: "skip"
18174
+ };
18175
+
18141
18176
  // src/commands/validation/formatting.ts
18142
18177
  import { existsSync, statSync } from "fs";
18143
18178
  import { isAbsolute as isAbsolute6, join as join29, relative as relative6 } from "path";
@@ -18153,8 +18188,8 @@ function unique(values) {
18153
18188
  function nonEmpty(values) {
18154
18189
  return values?.filter((value) => value.length > 0) ?? [];
18155
18190
  }
18156
- function toProjectRelativeValidationPath(projectRoot, path4) {
18157
- return isAbsolute5(path4) ? relative5(projectRoot, path4) : path4;
18191
+ function toProductRelativeValidationPath(productDir, path4) {
18192
+ return isAbsolute5(path4) ? relative5(productDir, path4) : path4;
18158
18193
  }
18159
18194
  function intersectIncludes(baseInclude, toolInclude) {
18160
18195
  const base = nonEmpty(baseInclude);
@@ -18259,6 +18294,9 @@ function applyValidationPathFilterToScope(scopeConfig, filter) {
18259
18294
  };
18260
18295
  }
18261
18296
 
18297
+ // src/validation/steps/formatting.ts
18298
+ import { createRequire } from "module";
18299
+
18262
18300
  // src/validation/steps/subprocess-output.ts
18263
18301
  var VALIDATION_SUBPROCESS_EVENTS = {
18264
18302
  CLOSE: "close",
@@ -18270,6 +18308,13 @@ var defaultValidationSubprocessOutputStreams = {
18270
18308
  stdout: process.stdout,
18271
18309
  stderr: process.stderr
18272
18310
  };
18311
+ var discardValidationSubprocessOutputStream = {
18312
+ write: () => true
18313
+ };
18314
+ var discardValidationSubprocessOutputStreams = {
18315
+ stdout: discardValidationSubprocessOutputStream,
18316
+ stderr: discardValidationSubprocessOutputStream
18317
+ };
18273
18318
  function forwardValidationSubprocessOutput(child, streams = defaultValidationSubprocessOutputStreams) {
18274
18319
  child.stdout?.on(VALIDATION_SUBPROCESS_EVENTS.DATA, (chunk) => {
18275
18320
  forwardChunkWithBackpressure(child.stdout, streams.stdout, chunk);
@@ -18290,7 +18335,12 @@ function forwardChunkWithBackpressure(source, stream, chunk) {
18290
18335
  }
18291
18336
 
18292
18337
  // src/validation/steps/formatting.ts
18293
- var DPRINT_COMMAND = "dprint";
18338
+ var DPRINT_EXECUTABLE_SPECIFIER = "dprint/bin.cjs";
18339
+ var cachedDprintCommand;
18340
+ function resolveDprintCommand() {
18341
+ cachedDprintCommand ??= createRequire(import.meta.url).resolve(DPRINT_EXECUTABLE_SPECIFIER);
18342
+ return cachedDprintCommand;
18343
+ }
18294
18344
  var DPRINT_CHECK_SUBCOMMAND = "check";
18295
18345
  var DPRINT_EXCLUDES_OPTION = "--excludes";
18296
18346
  var DPRINT_OPTIONS_TERMINATOR = "--";
@@ -18306,16 +18356,17 @@ function buildDprintCheckArgs(options) {
18306
18356
  ...files
18307
18357
  ];
18308
18358
  }
18309
- async function validateFormatting(context, runner = defaultFormattingProcessRunner) {
18359
+ async function validateFormatting(context, runner = defaultFormattingProcessRunner, outputStreams) {
18310
18360
  const args = buildDprintCheckArgs({ files: context.files, excludes: context.excludes });
18311
18361
  return new Promise((resolve17) => {
18312
- const child = spawnManagedSubprocess(runner, DPRINT_COMMAND, args, { cwd: context.projectRoot });
18362
+ const child = spawnManagedSubprocess(runner, resolveDprintCommand(), args, { cwd: context.productDir });
18313
18363
  const chunks = [];
18314
18364
  const capture = (chunk) => {
18315
18365
  chunks.push(typeof chunk === "string" ? chunk : Buffer.from(chunk).toString());
18316
18366
  };
18317
18367
  child.stdout?.on(VALIDATION_SUBPROCESS_EVENTS.DATA, capture);
18318
18368
  child.stderr?.on(VALIDATION_SUBPROCESS_EVENTS.DATA, capture);
18369
+ forwardValidationSubprocessOutput(child, outputStreams);
18319
18370
  child.on(VALIDATION_SUBPROCESS_EVENTS.CLOSE, (code) => {
18320
18371
  resolve17({ success: code === 0, output: chunks.join("") });
18321
18372
  });
@@ -18337,59 +18388,140 @@ var VALIDATION_STAGE_DISPLAY_NAMES = {
18337
18388
  };
18338
18389
  var VALIDATION_SKIP_LABELS = {
18339
18390
  VERB: "Skipping",
18340
- CIRCULAR_REASON: "skip-circular",
18341
- LITERAL_REASON: "skip-literal",
18342
18391
  DISABLED_BY_PREFIX: "disabled by",
18343
- TYPESCRIPT_ABSENT_REASON: "TypeScript not detected in project",
18392
+ TYPESCRIPT_ABSENT_REASON: "TypeScript not detected in product",
18344
18393
  VALIDATION_PATHS_NO_TARGETS_REASON: "validation paths matched no files",
18394
+ EXPLICIT_PATHS_NO_TARGETS_REASON: "explicit paths matched no files in tool scope",
18345
18395
  MARKDOWN_NO_SCOPE_REASON: "no markdown files in explicit path scope",
18346
18396
  MARKDOWN_NO_DEFAULT_DIRECTORIES_REASON: "no spx/ or docs/ directories found"
18347
18397
  };
18398
+ var VALIDATION_STREAMED_STAGE_RESULT = "output streamed";
18399
+ var VALIDATION_PROBLEM_TERMS = {
18400
+ SINGULAR: "problem",
18401
+ PLURAL: "problems"
18402
+ };
18403
+ function formatValidationNoProblemsMessage(stageName) {
18404
+ return `${stageName}: \u2713 No ${VALIDATION_PROBLEM_TERMS.PLURAL}`;
18405
+ }
18406
+ function formatValidationProblemsFoundMessage(stageName, options = {}) {
18407
+ const countPrefix = options.count === void 0 ? "" : `${options.count} `;
18408
+ const term = options.count === 1 ? VALIDATION_PROBLEM_TERMS.SINGULAR : VALIDATION_PROBLEM_TERMS.PLURAL;
18409
+ const detailSuffix = options.detail === void 0 ? "" : ` (${options.detail})`;
18410
+ return `${stageName}: ${countPrefix}${term} found${detailSuffix}`;
18411
+ }
18412
+ function formatValidationConfigProblemMessage(stageName, detail) {
18413
+ return formatValidationProblemsFoundMessage(stageName, { count: 1, detail });
18414
+ }
18415
+ var VALIDATION_STAGE_PROBLEM_MESSAGES = {
18416
+ [VALIDATION_STAGE_DISPLAY_NAMES.CIRCULAR]: {
18417
+ clear: formatValidationNoProblemsMessage(VALIDATION_STAGE_DISPLAY_NAMES.CIRCULAR),
18418
+ attention: formatValidationProblemsFoundMessage(VALIDATION_STAGE_DISPLAY_NAMES.CIRCULAR, {
18419
+ detail: "circular dependencies"
18420
+ })
18421
+ },
18422
+ [VALIDATION_STAGE_DISPLAY_NAMES.KNIP]: {
18423
+ clear: formatValidationNoProblemsMessage(VALIDATION_STAGE_DISPLAY_NAMES.KNIP),
18424
+ attention: formatValidationProblemsFoundMessage(VALIDATION_STAGE_DISPLAY_NAMES.KNIP, {
18425
+ detail: "unused code"
18426
+ })
18427
+ },
18428
+ [VALIDATION_STAGE_DISPLAY_NAMES.ESLINT]: {
18429
+ clear: formatValidationNoProblemsMessage(VALIDATION_STAGE_DISPLAY_NAMES.ESLINT),
18430
+ attention: formatValidationProblemsFoundMessage(VALIDATION_STAGE_DISPLAY_NAMES.ESLINT)
18431
+ },
18432
+ [VALIDATION_STAGE_DISPLAY_NAMES.TYPESCRIPT]: {
18433
+ clear: formatValidationNoProblemsMessage(VALIDATION_STAGE_DISPLAY_NAMES.TYPESCRIPT),
18434
+ attention: formatValidationProblemsFoundMessage(VALIDATION_STAGE_DISPLAY_NAMES.TYPESCRIPT)
18435
+ },
18436
+ [VALIDATION_STAGE_DISPLAY_NAMES.MARKDOWN]: {
18437
+ clear: formatValidationNoProblemsMessage(VALIDATION_STAGE_DISPLAY_NAMES.MARKDOWN),
18438
+ attention: formatValidationProblemsFoundMessage(VALIDATION_STAGE_DISPLAY_NAMES.MARKDOWN)
18439
+ },
18440
+ [VALIDATION_STAGE_DISPLAY_NAMES.LITERAL]: {
18441
+ clear: formatValidationNoProblemsMessage(VALIDATION_STAGE_DISPLAY_NAMES.LITERAL),
18442
+ attention: formatValidationProblemsFoundMessage(VALIDATION_STAGE_DISPLAY_NAMES.LITERAL)
18443
+ },
18444
+ [VALIDATION_STAGE_DISPLAY_NAMES.FORMATTING]: {
18445
+ clear: formatValidationNoProblemsMessage(VALIDATION_STAGE_DISPLAY_NAMES.FORMATTING),
18446
+ attention: formatValidationProblemsFoundMessage(VALIDATION_STAGE_DISPLAY_NAMES.FORMATTING, {
18447
+ detail: "unformatted files"
18448
+ })
18449
+ }
18450
+ };
18348
18451
  var VALIDATION_COMMAND_OUTPUT = {
18349
- CIRCULAR_FOUND: `${VALIDATION_STAGE_DISPLAY_NAMES.CIRCULAR}: circular dependencies found`,
18350
- CIRCULAR_NONE_FOUND: `${VALIDATION_STAGE_DISPLAY_NAMES.CIRCULAR}: \u2713 No circular dependencies found`,
18351
- KNIP_CONFIG_ERROR: `${VALIDATION_STAGE_DISPLAY_NAMES.KNIP}: \u2717 config error`,
18452
+ CIRCULAR_FOUND: VALIDATION_STAGE_PROBLEM_MESSAGES[VALIDATION_STAGE_DISPLAY_NAMES.CIRCULAR].attention,
18453
+ CIRCULAR_NONE_FOUND: VALIDATION_STAGE_PROBLEM_MESSAGES[VALIDATION_STAGE_DISPLAY_NAMES.CIRCULAR].clear,
18454
+ KNIP_CONFIG_ERROR: formatValidationConfigProblemMessage(
18455
+ VALIDATION_STAGE_DISPLAY_NAMES.KNIP,
18456
+ "configuration error"
18457
+ ),
18352
18458
  KNIP_DISABLED: `${VALIDATION_STAGE_DISPLAY_NAMES.KNIP}: skipped (${VALIDATION_SKIP_LABELS.DISABLED_BY_PREFIX} validation.knip.enabled)`,
18353
- KNIP_SUCCESS: `${VALIDATION_STAGE_DISPLAY_NAMES.KNIP}: \u2713 No unused code found`,
18354
- KNIP_FAILURE: "Unused code found",
18355
- ESLINT_SUCCESS: `${VALIDATION_STAGE_DISPLAY_NAMES.ESLINT}: \u2713 No errors found`,
18356
- ESLINT_FAILURE: `${VALIDATION_STAGE_DISPLAY_NAMES.ESLINT} validation failed`,
18357
- ESLINT_MISSING_CONFIG: "ESLint config not found: project has tsconfig.json but no eslint.config.{ts,js,mjs,cjs}",
18358
- TYPESCRIPT_SUCCESS: `${VALIDATION_STAGE_DISPLAY_NAMES.TYPESCRIPT}: \u2713 No type errors`,
18359
- TYPESCRIPT_FAILURE: `${VALIDATION_STAGE_DISPLAY_NAMES.TYPESCRIPT} validation failed`,
18360
- MARKDOWN_NO_ISSUES: `${VALIDATION_STAGE_DISPLAY_NAMES.MARKDOWN}: No issues found`,
18361
- MARKDOWN_ERROR_SUMMARY_SUFFIX: "error(s) found",
18362
- FORMATTING_NO_ISSUES: `${VALIDATION_STAGE_DISPLAY_NAMES.FORMATTING}: \u2713 No formatting issues found`,
18363
- FORMATTING_FAILURE_SUMMARY: `${VALIDATION_STAGE_DISPLAY_NAMES.FORMATTING}: unformatted files found`
18364
- };
18365
- var CIRCULAR_SKIP_OUTPUT = `${VALIDATION_STAGE_DISPLAY_NAMES.CIRCULAR}: skipped (--${VALIDATION_SKIP_LABELS.CIRCULAR_REASON})`;
18366
- var CIRCULAR_SKIP_JSON_OUTPUT = JSON.stringify({
18367
- skipped: true,
18368
- reason: VALIDATION_SKIP_LABELS.CIRCULAR_REASON
18369
- });
18370
- var LITERAL_SKIP_OUTPUT = `${VALIDATION_STAGE_DISPLAY_NAMES.LITERAL}: skipped (--${VALIDATION_SKIP_LABELS.LITERAL_REASON})`;
18371
- var LITERAL_SKIP_JSON_OUTPUT = JSON.stringify({
18372
- skipped: true,
18373
- reason: VALIDATION_SKIP_LABELS.LITERAL_REASON
18374
- });
18459
+ KNIP_SUCCESS: VALIDATION_STAGE_PROBLEM_MESSAGES[VALIDATION_STAGE_DISPLAY_NAMES.KNIP].clear,
18460
+ KNIP_FAILURE: VALIDATION_STAGE_PROBLEM_MESSAGES[VALIDATION_STAGE_DISPLAY_NAMES.KNIP].attention,
18461
+ ESLINT_SUCCESS: VALIDATION_STAGE_PROBLEM_MESSAGES[VALIDATION_STAGE_DISPLAY_NAMES.ESLINT].clear,
18462
+ ESLINT_FAILURE: VALIDATION_STAGE_PROBLEM_MESSAGES[VALIDATION_STAGE_DISPLAY_NAMES.ESLINT].attention,
18463
+ ESLINT_MISSING_CONFIG: formatValidationConfigProblemMessage(
18464
+ VALIDATION_STAGE_DISPLAY_NAMES.ESLINT,
18465
+ "configuration missing: product has tsconfig.json but no eslint.config.{ts,js,mjs,cjs}"
18466
+ ),
18467
+ TYPESCRIPT_SUCCESS: VALIDATION_STAGE_PROBLEM_MESSAGES[VALIDATION_STAGE_DISPLAY_NAMES.TYPESCRIPT].clear,
18468
+ TYPESCRIPT_FAILURE: VALIDATION_STAGE_PROBLEM_MESSAGES[VALIDATION_STAGE_DISPLAY_NAMES.TYPESCRIPT].attention,
18469
+ MARKDOWN_NO_ISSUES: VALIDATION_STAGE_PROBLEM_MESSAGES[VALIDATION_STAGE_DISPLAY_NAMES.MARKDOWN].clear,
18470
+ FORMATTING_NO_ISSUES: VALIDATION_STAGE_PROBLEM_MESSAGES[VALIDATION_STAGE_DISPLAY_NAMES.FORMATTING].clear,
18471
+ FORMATTING_FAILURE_SUMMARY: VALIDATION_STAGE_PROBLEM_MESSAGES[VALIDATION_STAGE_DISPLAY_NAMES.FORMATTING].attention
18472
+ };
18473
+ function formatValidationStageSkipOutput(stageName, reason) {
18474
+ return `${stageName}: skipped (${reason})`;
18475
+ }
18476
+ function formatValidationStageSkipJsonOutput(reason, durationMs) {
18477
+ return JSON.stringify({ skipped: true, reason, durationMs });
18478
+ }
18375
18479
  function formatTypeScriptAbsentSkipMessage(stageName) {
18376
18480
  return `\u23ED ${VALIDATION_SKIP_LABELS.VERB} ${stageName} (${VALIDATION_SKIP_LABELS.TYPESCRIPT_ABSENT_REASON})`;
18377
18481
  }
18378
18482
  function formatValidationPathsNoTargetsSkipMessage(stageName) {
18379
18483
  return `\u23ED ${VALIDATION_SKIP_LABELS.VERB} ${stageName} (${VALIDATION_SKIP_LABELS.VALIDATION_PATHS_NO_TARGETS_REASON})`;
18380
18484
  }
18485
+ function formatExplicitPathsNoTargetsSkipMessage(stageName) {
18486
+ return `\u23ED ${VALIDATION_SKIP_LABELS.VERB} ${stageName} (${VALIDATION_SKIP_LABELS.EXPLICIT_PATHS_NO_TARGETS_REASON})`;
18487
+ }
18488
+ function formatValidationScopeNoTargetsSkipMessage(stageName, metadata) {
18489
+ if (metadata.explicitPathNoMatches) {
18490
+ return formatExplicitPathsNoTargetsSkipMessage(stageName);
18491
+ }
18492
+ if (metadata.filteredByValidationPathNoMatches) {
18493
+ return formatValidationPathsNoTargetsSkipMessage(stageName);
18494
+ }
18495
+ return void 0;
18496
+ }
18497
+
18498
+ // src/commands/validation/types.ts
18499
+ var VALIDATION_OUTPUT_TARGET = {
18500
+ STDOUT: "stdout",
18501
+ STDERR: "stderr"
18502
+ };
18503
+ var VALIDATION_STREAMED_TERMINAL_OUTPUT = "";
18504
+ function streamedValidationTerminalOutput(subprocessOutput, json, streamedPipelineOutput) {
18505
+ return json !== true && streamedPipelineOutput === true && subprocessOutput !== void 0 && subprocessOutput.length > 0 ? VALIDATION_STREAMED_TERMINAL_OUTPUT : void 0;
18506
+ }
18381
18507
 
18382
18508
  // src/commands/validation/formatting.ts
18509
+ var defaultFormattingCommandDependencies = {
18510
+ validateFormatting
18511
+ };
18383
18512
  var FORMATTING_COMMAND_OUTPUT = {
18384
18513
  NO_ISSUES: VALIDATION_COMMAND_OUTPUT.FORMATTING_NO_ISSUES,
18385
18514
  FAILURE_SUMMARY: VALIDATION_COMMAND_OUTPUT.FORMATTING_FAILURE_SUMMARY,
18386
18515
  EMPTY_SCOPE_REASON: "no files in scope",
18387
18516
  NO_CONFIG_SKIP_REASON: `no ${DPRINT_CONFIG_FILENAME} at product root`
18388
18517
  };
18389
- var FORMATTING_CONFIG_ERROR_MESSAGE = `${VALIDATION_STAGE_DISPLAY_NAMES.FORMATTING}: \u2717 config error`;
18518
+ var FORMATTING_CONFIG_ERROR_MESSAGE = formatValidationConfigProblemMessage(
18519
+ VALIDATION_STAGE_DISPLAY_NAMES.FORMATTING,
18520
+ "configuration error"
18521
+ );
18390
18522
  var DPRINT_RECURSIVE_DIRECTORY_GLOB_SUFFIX = "/**/*";
18391
- async function formattingCommand(options) {
18392
- const { cwd, files, quiet } = options;
18523
+ async function formattingCommand(options, dependencies = defaultFormattingCommandDependencies) {
18524
+ const { cwd, files, json, outputStreams, quiet, streamedPipelineOutput } = options;
18393
18525
  const startTime = Date.now();
18394
18526
  const loaded = await resolveConfig(cwd, [validationConfigDescriptor]);
18395
18527
  if (!loaded.ok) {
@@ -18409,32 +18541,52 @@ async function formattingCommand(options) {
18409
18541
  VALIDATION_PATH_TOOL_SUBSECTIONS.FORMATTING
18410
18542
  );
18411
18543
  const hasExplicitScope = files !== void 0 && files.length > 0;
18412
- const scopedFiles = hasExplicitScope ? files.flatMap(
18413
- (filePath) => formattingPathOperandsForValidationPathFilter(
18414
- cwd,
18415
- isAbsolute6(filePath) ? relative6(cwd, filePath) : filePath,
18416
- pathFilter
18417
- )
18418
- ) : void 0;
18419
- const scopedExcludes = hasExplicitScope && files.some(
18420
- (filePath) => isFormattingFileOperand(cwd, isAbsolute6(filePath) ? relative6(cwd, filePath) : filePath)
18421
- ) ? [] : validationPathFilterExcludes(pathFilter);
18422
- if (hasExplicitScope && (scopedFiles === void 0 || scopedFiles.length === 0)) {
18544
+ const contexts = formattingValidationContexts(cwd, files, pathFilter);
18545
+ const scopedFiles = contexts.flatMap((context) => context.files ?? []);
18546
+ if (hasExplicitScope && scopedFiles.length === 0 || contexts.length === 0) {
18423
18547
  const output2 = quiet ? "" : `${VALIDATION_STAGE_DISPLAY_NAMES.FORMATTING}: skipped (${FORMATTING_COMMAND_OUTPUT.EMPTY_SCOPE_REASON})`;
18424
18548
  return { exitCode: 0, output: output2, durationMs: Date.now() - startTime };
18425
18549
  }
18426
- const result = await validateFormatting({
18427
- projectRoot: cwd,
18428
- files: scopedFiles,
18429
- excludes: scopedExcludes
18430
- });
18550
+ const results = [];
18551
+ const subprocessOutputStreams = outputStreams ?? discardValidationSubprocessOutputStreams;
18552
+ for (const context of contexts) {
18553
+ results.push(await dependencies.validateFormatting(context, void 0, subprocessOutputStreams));
18554
+ }
18431
18555
  const durationMs = Date.now() - startTime;
18432
- if (result.success) {
18556
+ if (results.every((result) => result.success)) {
18433
18557
  return { exitCode: 0, output: quiet ? "" : FORMATTING_COMMAND_OUTPUT.NO_ISSUES, durationMs };
18434
18558
  }
18435
- const detail = result.error ?? result.output;
18559
+ const detail = results.filter((result) => !result.success).flatMap((result) => [result.output, result.error]).filter((output2) => output2 !== void 0).filter((output2) => output2.length > 0).join("\n");
18436
18560
  const output = [FORMATTING_COMMAND_OUTPUT.FAILURE_SUMMARY, detail].filter((line) => line.length > 0).join("\n");
18437
- return { exitCode: 1, output, durationMs };
18561
+ const terminalOutput = formattingTerminalOutput(results, outputStreams, json, streamedPipelineOutput);
18562
+ return { exitCode: 1, output, terminalOutput, durationMs };
18563
+ }
18564
+ function formattingValidationContexts(productDir, files, pathFilter) {
18565
+ if (files === void 0 || files.length === 0) {
18566
+ if (validationPathFilterHasNoMatchingIncludes(pathFilter)) return [];
18567
+ const automaticFiles = pathFilter.include?.map((path4) => normalizeFormattingPathOperand(productDir, path4));
18568
+ return [{
18569
+ productDir,
18570
+ files: automaticFiles !== void 0 && automaticFiles.length > 0 ? automaticFiles : void 0,
18571
+ excludes: validationPathFilterExcludes(pathFilter)
18572
+ }];
18573
+ }
18574
+ const relativeFiles = files.map((filePath) => isAbsolute6(filePath) ? relative6(productDir, filePath) : filePath);
18575
+ return [{
18576
+ productDir,
18577
+ files: relativeFiles.map((filePath) => normalizeFormattingPathOperand(productDir, filePath)),
18578
+ excludes: []
18579
+ }];
18580
+ }
18581
+ function formattingTerminalOutput(results, outputStreams, json, streamedPipelineOutput) {
18582
+ if (json === true || outputStreams === void 0) {
18583
+ return void 0;
18584
+ }
18585
+ const errors = results.flatMap((result) => result.error === void 0 ? [] : [result.error]);
18586
+ if (errors.length > 0) {
18587
+ return [FORMATTING_COMMAND_OUTPUT.FAILURE_SUMMARY, ...errors].join("\n");
18588
+ }
18589
+ return streamedPipelineOutput === true ? VALIDATION_STREAMED_TERMINAL_OUTPUT : FORMATTING_COMMAND_OUTPUT.FAILURE_SUMMARY;
18438
18590
  }
18439
18591
  function normalizeFormattingPathOperand(productDir, relativePath) {
18440
18592
  if (isFormattingFileOperand(productDir, relativePath)) {
@@ -18450,22 +18602,35 @@ function isFormattingFileOperand(productDir, relativePath) {
18450
18602
  const absolutePath = join29(productDir, relativePath);
18451
18603
  return !existsSync(absolutePath) || !statSync(absolutePath).isDirectory();
18452
18604
  }
18453
- function formattingPathOperandsForValidationPathFilter(productDir, relativePath, pathFilter) {
18454
- if (isFormattingFileOperand(productDir, relativePath)) {
18455
- return [relativePath];
18456
- }
18457
- return validationPathFilterIntersections(relativePath, pathFilter).map((path4) => normalizeFormattingPathOperand(productDir, path4));
18458
- }
18459
18605
 
18460
18606
  // src/validation/languages/formatting.ts
18461
18607
  var FORMATTING_LANGUAGE_NAME = "formatting";
18608
+ var SKIP_FORMATTING_REASON = "skip-formatting";
18609
+ var FORMATTING_VALIDATION_STAGE_PARTICIPATION = {
18610
+ [VALIDATION_STAGE_DISPLAY_NAMES.FORMATTING]: {
18611
+ default: VALIDATION_STAGE_PARTICIPATION.RUN,
18612
+ skipReason: SKIP_FORMATTING_REASON,
18613
+ override: {
18614
+ flag: "--skip-formatting",
18615
+ description: "Skip formatting validation for this validation all run"
18616
+ }
18617
+ }
18618
+ };
18462
18619
  var formattingValidationLanguage = {
18463
18620
  name: FORMATTING_LANGUAGE_NAME,
18464
18621
  stages: [
18465
18622
  {
18466
18623
  name: VALIDATION_STAGE_DISPLAY_NAMES.FORMATTING,
18467
18624
  failsPipeline: true,
18468
- run: (context) => formattingCommand({ cwd: context.cwd, files: context.files, quiet: context.quiet })
18625
+ participation: FORMATTING_VALIDATION_STAGE_PARTICIPATION[VALIDATION_STAGE_DISPLAY_NAMES.FORMATTING],
18626
+ run: (context) => formattingCommand({
18627
+ cwd: context.cwd,
18628
+ files: context.files,
18629
+ quiet: context.quiet,
18630
+ json: context.json,
18631
+ streamedPipelineOutput: true,
18632
+ outputStreams: context.outputStreams
18633
+ })
18469
18634
  }
18470
18635
  ]
18471
18636
  };
@@ -18506,6 +18671,11 @@ var MARKDOWN_VALIDATION_TARGET_DIAGNOSTICS = {
18506
18671
  var defaultMarkdownValidationTargetDeps = {
18507
18672
  statSync: statSync2
18508
18673
  };
18674
+ var defaultMarkdownValidationDeps = {
18675
+ runMarkdownlint: async (options) => {
18676
+ await markdownlintMain(options);
18677
+ }
18678
+ };
18509
18679
  function buildMarkdownlintConfig(directoryName) {
18510
18680
  const md024Disabled = MD024_DISABLED_DIRECTORIES.includes(
18511
18681
  directoryName
@@ -18517,8 +18687,8 @@ function buildMarkdownlintConfig(directoryName) {
18517
18687
  [MARKDOWN_CONFIG_CONTROL_KEYS.CUSTOM_RULES]: [relativeLinksRule]
18518
18688
  };
18519
18689
  }
18520
- function getDefaultDirectories(projectRoot) {
18521
- return MARKDOWN_DEFAULT_DIRECTORY_NAMES.map((name) => join30(projectRoot, name)).filter((dir) => existsSync2(dir));
18690
+ function getDefaultDirectories(productDir) {
18691
+ return MARKDOWN_DEFAULT_DIRECTORY_NAMES.map((name) => join30(productDir, name)).filter((dir) => existsSync2(dir));
18522
18692
  }
18523
18693
  function resolveMarkdownValidationTarget(path4, deps = defaultMarkdownValidationTargetDeps) {
18524
18694
  if (isExistingDirectory(path4, deps)) {
@@ -18534,10 +18704,10 @@ function resolveMarkdownValidationTarget(path4, deps = defaultMarkdownValidation
18534
18704
  }
18535
18705
  };
18536
18706
  }
18537
- function getExcludeGlobsForTarget(target, projectRoot, entries) {
18538
- if (projectRoot === void 0 || entries.length === 0) return [];
18707
+ function getExcludeGlobsForTarget(target, productDir, entries) {
18708
+ if (productDir === void 0 || entries.length === 0) return [];
18539
18709
  const directory = targetDirectory(target);
18540
- const specTreeRoot = join30(projectRoot, SPEC_TREE_CONFIG.ROOT_DIRECTORY);
18710
+ const specTreeRoot = join30(productDir, SPEC_TREE_CONFIG.ROOT_DIRECTORY);
18541
18711
  const targetPath = normalizePathPrefix(pathRelative(specTreeRoot, directory));
18542
18712
  return entries.flatMap((entry) => {
18543
18713
  const excludedPath = normalizePathPrefix(entry);
@@ -18615,24 +18785,24 @@ function isAsciiDigit(value) {
18615
18785
  function isAsciiWhitespace(value) {
18616
18786
  return value === " " || value === " ";
18617
18787
  }
18618
- async function validateMarkdown(options) {
18788
+ async function validateMarkdown(options, deps = defaultMarkdownValidationDeps) {
18619
18789
  const {
18620
18790
  targets,
18621
- projectRoot,
18791
+ productDir,
18622
18792
  applyNodeStatusExcludes = true,
18623
18793
  validationPathExcludes = []
18624
18794
  } = options;
18625
18795
  const errors = [];
18626
- const specTreeExcludeEntries = applyNodeStatusExcludes ? getExcludeEntries(projectRoot) : [];
18796
+ const specTreeExcludeEntries = applyNodeStatusExcludes ? getExcludeEntries(productDir) : [];
18627
18797
  for (const target of targets) {
18628
18798
  const directory = targetDirectory(target);
18629
- const dirName = markdownlintConfigDirectoryName(directory, projectRoot);
18799
+ const dirName = markdownlintConfigDirectoryName(directory, productDir);
18630
18800
  const config = buildMarkdownlintConfig(dirName);
18631
18801
  const excludeGlobs = [
18632
- ...getExcludeGlobsForTarget(target, projectRoot, specTreeExcludeEntries),
18633
- ...validationPathExcludeGlobsForTarget(target, projectRoot, validationPathExcludes)
18802
+ ...getExcludeGlobsForTarget(target, productDir, specTreeExcludeEntries),
18803
+ ...validationPathExcludeGlobsForTarget(target, productDir, validationPathExcludes)
18634
18804
  ];
18635
- const dirErrors = await validateTarget(target, config, projectRoot, excludeGlobs);
18805
+ const dirErrors = await validateTarget(target, config, deps, productDir, excludeGlobs);
18636
18806
  errors.push(...dirErrors);
18637
18807
  }
18638
18808
  return {
@@ -18640,11 +18810,11 @@ async function validateMarkdown(options) {
18640
18810
  errors
18641
18811
  };
18642
18812
  }
18643
- function getExcludeEntries(projectRoot) {
18644
- if (projectRoot === void 0) return [];
18645
- return createNodeStatusExcludeReader(projectRoot).entries();
18813
+ function getExcludeEntries(productDir) {
18814
+ if (productDir === void 0) return [];
18815
+ return createNodeStatusExcludeReader(productDir).entries();
18646
18816
  }
18647
- async function validateTarget(target, config, projectRoot, ignoreGlobs = []) {
18817
+ async function validateTarget(target, config, deps, productDir, ignoreGlobs = []) {
18648
18818
  const errors = [];
18649
18819
  const directory = targetDirectory(target);
18650
18820
  const argv = target.kind === MARKDOWN_VALIDATION_TARGET_KIND.FILE ? [basename7(target.path)] : [MARKDOWN_DIRECTORY_GLOB];
@@ -18652,14 +18822,14 @@ async function validateTarget(target, config, projectRoot, ignoreGlobs = []) {
18652
18822
  const optionsOverride = {
18653
18823
  config: {
18654
18824
  ...markdownlintConfig,
18655
- "relative-links": projectRoot ? { root_path: projectRoot } : true
18825
+ "relative-links": productDir ? { root_path: productDir } : true
18656
18826
  },
18657
18827
  customRules,
18658
18828
  noProgress: true,
18659
18829
  noBanner: true,
18660
18830
  ...ignoreGlobs.length > 0 ? { ignores: ignoreGlobs } : {}
18661
18831
  };
18662
- await markdownlintMain({
18832
+ await deps.runMarkdownlint({
18663
18833
  directory,
18664
18834
  argv,
18665
18835
  optionsOverride,
@@ -18681,10 +18851,10 @@ async function validateTarget(target, config, projectRoot, ignoreGlobs = []) {
18681
18851
  function targetDirectory(target) {
18682
18852
  return target.kind === MARKDOWN_VALIDATION_TARGET_KIND.FILE ? dirname13(target.path) : target.path;
18683
18853
  }
18684
- function validationPathExcludeGlobsForTarget(target, projectRoot, excludes) {
18685
- if (projectRoot === void 0 || excludes.length === 0 || target.kind === MARKDOWN_VALIDATION_TARGET_KIND.FILE) return [];
18854
+ function validationPathExcludeGlobsForTarget(target, productDir, excludes) {
18855
+ if (productDir === void 0 || excludes.length === 0 || target.kind === MARKDOWN_VALIDATION_TARGET_KIND.FILE) return [];
18686
18856
  const directory = targetDirectory(target);
18687
- const targetPath = normalizePathPrefix(pathRelative(projectRoot, directory));
18857
+ const targetPath = normalizePathPrefix(pathRelative(productDir, directory));
18688
18858
  return excludes.flatMap((exclude) => {
18689
18859
  const excludedPath = normalizePathPrefix(exclude);
18690
18860
  if (targetPath === excludedPath) {
@@ -18693,11 +18863,11 @@ function validationPathExcludeGlobsForTarget(target, projectRoot, excludes) {
18693
18863
  if (!pathContainsValidationPath(targetPath, excludedPath)) {
18694
18864
  return [];
18695
18865
  }
18696
- const relativeExclude = normalizePathPrefix(pathRelative(directory, join30(projectRoot, excludedPath)));
18866
+ const relativeExclude = normalizePathPrefix(pathRelative(directory, join30(productDir, excludedPath)));
18697
18867
  if (relativeExclude.length === 0) {
18698
18868
  return [MARKDOWN_DIRECTORY_GLOB];
18699
18869
  }
18700
- const absoluteExclude = join30(projectRoot, excludedPath);
18870
+ const absoluteExclude = join30(productDir, excludedPath);
18701
18871
  return [
18702
18872
  isExistingFile(absoluteExclude, defaultMarkdownValidationTargetDeps) ? relativeExclude : `${relativeExclude}/**`
18703
18873
  ];
@@ -18726,9 +18896,9 @@ function isExistingFile(path4, deps) {
18726
18896
  return false;
18727
18897
  }
18728
18898
  }
18729
- function markdownlintConfigDirectoryName(directory, projectRoot) {
18730
- if (projectRoot !== void 0) {
18731
- const [rootSegment] = pathRelative(projectRoot, directory).split(/[\\/]/);
18899
+ function markdownlintConfigDirectoryName(directory, productDir) {
18900
+ if (productDir !== void 0) {
18901
+ const [rootSegment] = pathRelative(productDir, directory).split(/[\\/]/);
18732
18902
  if (MD024_DISABLED_DIRECTORIES.includes(rootSegment)) {
18733
18903
  return rootSegment;
18734
18904
  }
@@ -18738,11 +18908,14 @@ function markdownlintConfigDirectoryName(directory, projectRoot) {
18738
18908
 
18739
18909
  // src/commands/validation/markdown.ts
18740
18910
  var MARKDOWN_COMMAND_OUTPUT = {
18741
- ERROR_SUMMARY_SUFFIX: VALIDATION_COMMAND_OUTPUT.MARKDOWN_ERROR_SUMMARY_SUFFIX,
18742
18911
  NO_ISSUES: VALIDATION_COMMAND_OUTPUT.MARKDOWN_NO_ISSUES,
18912
+ PROBLEM_TERM: VALIDATION_PROBLEM_TERMS.SINGULAR,
18743
18913
  SKIPPED_FILE_SCOPE_PREFIX: "Markdown skipped file scope"
18744
18914
  };
18745
- var MARKDOWN_CONFIG_ERROR_MESSAGE = `${VALIDATION_STAGE_DISPLAY_NAMES.MARKDOWN}: \u2717 config error`;
18915
+ var MARKDOWN_CONFIG_ERROR_MESSAGE = formatValidationConfigProblemMessage(
18916
+ VALIDATION_STAGE_DISPLAY_NAMES.MARKDOWN,
18917
+ "configuration error"
18918
+ );
18746
18919
  async function markdownCommand(options) {
18747
18920
  const { cwd, files, quiet } = options;
18748
18921
  const startTime = Date.now();
@@ -18775,8 +18948,8 @@ async function markdownCommand(options) {
18775
18948
  }
18776
18949
  const result = await validateMarkdown({
18777
18950
  targets,
18778
- projectRoot: cwd,
18779
- validationPathExcludes: validationPathFilterExcludes(pathFilter)
18951
+ productDir: cwd,
18952
+ validationPathExcludes: targetResolutions === void 0 ? validationPathFilterExcludes(pathFilter) : []
18780
18953
  });
18781
18954
  const durationMs = Date.now() - startTime;
18782
18955
  return formatMarkdownResult(result, skippedOutput, quiet, durationMs);
@@ -18786,7 +18959,7 @@ function markdownValidationOperandPath(productDir, filePath) {
18786
18959
  }
18787
18960
  function defaultMarkdownTargets(productDir, pathFilter) {
18788
18961
  return getDefaultDirectories(productDir).flatMap(
18789
- (directory) => validationPathFilterIntersections(toProjectRelativeValidationPath(productDir, directory), pathFilter).map((intersection) => resolveMarkdownValidationTarget(join31(productDir, intersection)).target).filter((target) => target !== void 0)
18962
+ (directory) => validationPathFilterIntersections(toProductRelativeValidationPath(productDir, directory), pathFilter).map((intersection) => resolveMarkdownValidationTarget(join31(productDir, intersection)).target).filter((target) => target !== void 0)
18790
18963
  );
18791
18964
  }
18792
18965
  function formatSkippedFileScope(target) {
@@ -18802,7 +18975,9 @@ function formatMarkdownResult(result, skippedOutput, quiet, durationMs) {
18802
18975
  );
18803
18976
  const output = [
18804
18977
  ...skippedOutput,
18805
- `Markdown: ${result.errors.length} ${MARKDOWN_COMMAND_OUTPUT.ERROR_SUMMARY_SUFFIX}`,
18978
+ formatValidationProblemsFoundMessage(VALIDATION_STAGE_DISPLAY_NAMES.MARKDOWN, {
18979
+ count: result.errors.length
18980
+ }),
18806
18981
  ...errorLines
18807
18982
  ].join("\n");
18808
18983
  return { exitCode: 1, output, durationMs };
@@ -18810,12 +18985,24 @@ function formatMarkdownResult(result, skippedOutput, quiet, durationMs) {
18810
18985
 
18811
18986
  // src/validation/languages/markdown.ts
18812
18987
  var MARKDOWN_LANGUAGE_NAME = "markdown";
18988
+ var SKIP_MARKDOWN_REASON = "skip-markdown";
18989
+ var MARKDOWN_VALIDATION_STAGE_PARTICIPATION = {
18990
+ [VALIDATION_STAGE_DISPLAY_NAMES.MARKDOWN]: {
18991
+ default: VALIDATION_STAGE_PARTICIPATION.RUN,
18992
+ skipReason: SKIP_MARKDOWN_REASON,
18993
+ override: {
18994
+ flag: "--skip-markdown",
18995
+ description: "Skip Markdown validation for this validation all run"
18996
+ }
18997
+ }
18998
+ };
18813
18999
  var markdownValidationLanguage = {
18814
19000
  name: MARKDOWN_LANGUAGE_NAME,
18815
19001
  stages: [
18816
19002
  {
18817
19003
  name: VALIDATION_STAGE_DISPLAY_NAMES.MARKDOWN,
18818
19004
  failsPipeline: true,
19005
+ participation: MARKDOWN_VALIDATION_STAGE_PARTICIPATION[VALIDATION_STAGE_DISPLAY_NAMES.MARKDOWN],
18819
19006
  run: (context) => markdownCommand({ cwd: context.cwd, files: context.files, quiet: context.quiet })
18820
19007
  }
18821
19008
  ]
@@ -19687,7 +19874,7 @@ var TSCONFIG_FILES = {
19687
19874
  full: "tsconfig.json",
19688
19875
  production: "tsconfig.production.json"
19689
19876
  };
19690
- var PATH_SEGMENT_SEPARATOR4 = "/";
19877
+ var PATH_SEGMENT_SEPARATOR3 = "/";
19691
19878
  var GLOB_MARKER = "*";
19692
19879
  var RECURSIVE_GLOB_SEGMENT = "**";
19693
19880
  var SINGLE_CHARACTER_GLOB_MARKER = "?";
@@ -19718,8 +19905,8 @@ var EXPLICIT_TYPESCRIPT_SCOPE_TARGET_KIND = {
19718
19905
  DIRECTORY: "directory",
19719
19906
  FILE: "file"
19720
19907
  };
19721
- function resolveProjectPath(projectRoot, path4) {
19722
- return isAbsolute8(path4) ? path4 : join32(projectRoot, path4);
19908
+ function resolveProductPath(productDir, path4) {
19909
+ return isAbsolute8(path4) ? path4 : join32(productDir, path4);
19723
19910
  }
19724
19911
  function parseTypeScriptConfig(configPath, deps = defaultScopeDeps) {
19725
19912
  try {
@@ -19733,11 +19920,11 @@ function parseTypeScriptConfig(configPath, deps = defaultScopeDeps) {
19733
19920
  };
19734
19921
  }
19735
19922
  }
19736
- function resolveTypeScriptConfig(scope2, projectRoot, deps = defaultScopeDeps) {
19923
+ function resolveTypeScriptConfig(scope2, productDir, deps = defaultScopeDeps) {
19737
19924
  const configFile = TSCONFIG_FILES[scope2];
19738
- const config = parseTypeScriptConfig(resolveProjectPath(projectRoot, configFile), deps);
19925
+ const config = parseTypeScriptConfig(resolveProductPath(productDir, configFile), deps);
19739
19926
  if (config.extends) {
19740
- const baseConfigs = normalizeExtends(config.extends).map((extendedConfig) => parseTypeScriptConfig(resolveProjectPath(projectRoot, extendedConfig), deps));
19927
+ const baseConfigs = normalizeExtends(config.extends).map((extendedConfig) => parseTypeScriptConfig(resolveProductPath(productDir, extendedConfig), deps));
19741
19928
  const inheritedInclude = [...baseConfigs].reverse().find((baseConfig) => baseConfig.include !== void 0)?.include ?? [];
19742
19929
  const inheritedExclude = [...baseConfigs].reverse().find((baseConfig) => baseConfig.exclude !== void 0)?.exclude ?? [];
19743
19930
  return {
@@ -19773,48 +19960,48 @@ function hasTypeScriptFilesRecursive(dirPath, maxDepth = 2, deps = defaultScopeD
19773
19960
  }
19774
19961
  }
19775
19962
  function pathPassesTypeScriptFileDiscoveryExcludes(path4, options) {
19776
- const { excludePatterns = [], projectRoot } = options;
19777
- if (projectRoot === void 0) {
19963
+ const { excludePatterns = [], productDir } = options;
19964
+ if (productDir === void 0) {
19778
19965
  return true;
19779
19966
  }
19780
- const projectRelativePath = toProjectRelativeTypeScriptScopePath(projectRoot, path4);
19967
+ const projectRelativePath = toProductRelativeTypeScriptScopePath(productDir, path4);
19781
19968
  return !excludePatterns.some((pattern) => pathMatchesTypeScriptPattern(projectRelativePath, pattern));
19782
19969
  }
19783
- function getTopLevelDirectoriesWithTypeScript(config, projectRoot, deps = defaultScopeDeps) {
19970
+ function getTopLevelDirectoriesWithTypeScript(config, productDir, deps = defaultScopeDeps) {
19784
19971
  const directories = /* @__PURE__ */ new Set();
19785
- for (const dir of listTopLevelDirectories(projectRoot, deps)) {
19786
- if (directoryContributesTypeScriptScope(dir, config, projectRoot, deps)) {
19972
+ for (const dir of listTopLevelDirectories(productDir, deps)) {
19973
+ if (directoryContributesTypeScriptScope(dir, config, productDir, deps)) {
19787
19974
  directories.add(dir);
19788
19975
  }
19789
19976
  }
19790
- for (const dir of explicitIncludeTopLevelDirectories(config, projectRoot, deps)) {
19977
+ for (const dir of explicitIncludeTopLevelDirectories(config, productDir, deps)) {
19791
19978
  directories.add(dir);
19792
19979
  }
19793
19980
  return Array.from(directories).sort(compareAsciiStrings);
19794
19981
  }
19795
- function listTopLevelDirectories(projectRoot, deps) {
19796
- return deps.readdirSync(projectRoot, { withFileTypes: true }).filter((item) => item.isDirectory()).map((item) => item.name).filter((name) => !name.startsWith("."));
19982
+ function listTopLevelDirectories(productDir, deps) {
19983
+ return deps.readdirSync(productDir, { withFileTypes: true }).filter((item) => item.isDirectory()).map((item) => item.name).filter((name) => !name.startsWith("."));
19797
19984
  }
19798
- function directoryContributesTypeScriptScope(dir, config, projectRoot, deps) {
19799
- if (!directoryPassesIncludePatterns(dir, config.include ?? [], projectRoot, deps)) {
19985
+ function directoryContributesTypeScriptScope(dir, config, productDir, deps) {
19986
+ if (!directoryPassesIncludePatterns(dir, config.include ?? [], productDir, deps)) {
19800
19987
  return false;
19801
19988
  }
19802
19989
  try {
19803
- return hasTypeScriptFilesRecursive(join32(projectRoot, dir), 2, deps, {
19990
+ return hasTypeScriptFilesRecursive(join32(productDir, dir), 2, deps, {
19804
19991
  excludePatterns: config.exclude,
19805
- projectRoot
19992
+ productDir
19806
19993
  });
19807
19994
  } catch {
19808
19995
  return false;
19809
19996
  }
19810
19997
  }
19811
- function explicitIncludeTopLevelDirectories(config, projectRoot, deps) {
19998
+ function explicitIncludeTopLevelDirectories(config, productDir, deps) {
19812
19999
  if (!config.include) {
19813
20000
  return [];
19814
20001
  }
19815
20002
  const directories = [];
19816
20003
  for (const pattern of config.include) {
19817
- if (includePatternTargetsTypeScriptScope(pattern, projectRoot, deps) && pattern.includes(PATH_SEGMENT_SEPARATOR4)) {
20004
+ if (includePatternTargetsTypeScriptScope(pattern, productDir, deps) && pattern.includes(PATH_SEGMENT_SEPARATOR3)) {
19818
20005
  const topLevelDir = getLiteralTopLevelPatternDirectory(pattern);
19819
20006
  if (topLevelDir) {
19820
20007
  directories.push(topLevelDir);
@@ -19824,39 +20011,39 @@ function explicitIncludeTopLevelDirectories(config, projectRoot, deps) {
19824
20011
  return directories;
19825
20012
  }
19826
20013
  function getLiteralTopLevelPatternDirectory(pattern) {
19827
- const topLevelDir = pattern.split(PATH_SEGMENT_SEPARATOR4)[0];
20014
+ const topLevelDir = pattern.split(PATH_SEGMENT_SEPARATOR3)[0];
19828
20015
  if (!topLevelDir || typeScriptScopePatternHasGlob(topLevelDir) || topLevelDir.startsWith(HIDDEN_PATH_PREFIX)) {
19829
20016
  return null;
19830
20017
  }
19831
20018
  return topLevelDir;
19832
20019
  }
19833
- function directoryPassesIncludePatterns(directory, patterns, projectRoot, deps) {
20020
+ function directoryPassesIncludePatterns(directory, patterns, productDir, deps) {
19834
20021
  return patterns.length === 0 || patterns.some(
19835
- (pattern) => includePatternTargetsTypeScriptScope(pattern, projectRoot, deps) && typeScriptScopePatternIntersectsDirectory(pattern, directory)
20022
+ (pattern) => includePatternTargetsTypeScriptScope(pattern, productDir, deps) && typeScriptScopePatternIntersectsDirectory(pattern, directory)
19836
20023
  );
19837
20024
  }
19838
20025
  function stripTrailingPathSeparators2(value) {
19839
20026
  let end = value.length;
19840
- while (end > 0 && value.charAt(end - 1) === PATH_SEGMENT_SEPARATOR4) {
20027
+ while (end > 0 && value.charAt(end - 1) === PATH_SEGMENT_SEPARATOR3) {
19841
20028
  end -= 1;
19842
20029
  }
19843
20030
  return value.slice(0, end);
19844
20031
  }
19845
20032
  function normalizeTypeScriptScopePath(path4) {
19846
- const joined = path4.split(/[\\/]/gu).join(PATH_SEGMENT_SEPARATOR4).replace(/^\.\//u, "");
20033
+ const joined = path4.split(/[\\/]/gu).join(PATH_SEGMENT_SEPARATOR3).replace(/^\.\//u, "");
19847
20034
  return stripTrailingPathSeparators2(joined);
19848
20035
  }
19849
20036
  function pathMatchesLiteralPrefix(path4, prefix) {
19850
20037
  const normalizedPath = normalizeTypeScriptScopePath(path4);
19851
20038
  const normalizedPrefix = normalizeTypeScriptScopePath(prefix);
19852
- return normalizedPath === normalizedPrefix || normalizedPath.startsWith(`${normalizedPrefix}${PATH_SEGMENT_SEPARATOR4}`);
20039
+ return normalizedPath === normalizedPrefix || normalizedPath.startsWith(`${normalizedPrefix}${PATH_SEGMENT_SEPARATOR3}`);
19853
20040
  }
19854
20041
  function splitTypeScriptScopePathSegments(path4) {
19855
20042
  const normalizedPath = normalizeTypeScriptScopePath(path4);
19856
20043
  if (normalizedPath.length === 0 || normalizedPath === ".") {
19857
20044
  return [];
19858
20045
  }
19859
- return normalizedPath.split(PATH_SEGMENT_SEPARATOR4);
20046
+ return normalizedPath.split(PATH_SEGMENT_SEPARATOR3);
19860
20047
  }
19861
20048
  function globLiteralPrefix(pattern) {
19862
20049
  const normalizedPattern = normalizeTypeScriptScopePath(pattern);
@@ -19930,16 +20117,16 @@ function typeScriptScopeGlobPatternToRegExp(pattern) {
19930
20117
  const character = normalizedPattern[index];
19931
20118
  const nextCharacter = normalizedPattern[index + 1];
19932
20119
  const followingCharacter = normalizedPattern[index + 2];
19933
- if (character === GLOB_MARKER && nextCharacter === GLOB_MARKER && followingCharacter === PATH_SEGMENT_SEPARATOR4) {
19934
- source += `(?:.*${PATH_SEGMENT_SEPARATOR4})?`;
20120
+ if (character === GLOB_MARKER && nextCharacter === GLOB_MARKER && followingCharacter === PATH_SEGMENT_SEPARATOR3) {
20121
+ source += `(?:.*${PATH_SEGMENT_SEPARATOR3})?`;
19935
20122
  index += 2;
19936
20123
  } else if (character === GLOB_MARKER && nextCharacter === GLOB_MARKER) {
19937
20124
  source += ".*";
19938
20125
  index += 1;
19939
20126
  } else if (character === GLOB_MARKER) {
19940
- source += `[^${PATH_SEGMENT_SEPARATOR4}]*`;
20127
+ source += `[^${PATH_SEGMENT_SEPARATOR3}]*`;
19941
20128
  } else if (character === SINGLE_CHARACTER_GLOB_MARKER) {
19942
- source += `[^${PATH_SEGMENT_SEPARATOR4}]`;
20129
+ source += `[^${PATH_SEGMENT_SEPARATOR3}]`;
19943
20130
  } else {
19944
20131
  source += character.replaceAll(GLOB_REGEX_SPECIAL_CHARACTER_PATTERN, REGEX_ESCAPE_REPLACEMENT);
19945
20132
  }
@@ -20007,12 +20194,12 @@ function typeScriptScopePatternTargetsTypeScriptSource(pattern) {
20007
20194
  const terminalSegment = splitTypeScriptScopePathSegments(normalizedPattern).at(-1) ?? normalizedPattern;
20008
20195
  return typeScriptScopePatternHasGlob(terminalSegment) && !TERMINAL_EXTENSION_PATTERN.test(terminalSegment);
20009
20196
  }
20010
- function includePatternTargetsTypeScriptScope(pattern, projectRoot, deps) {
20011
- return includePatternIsLiteralDirectory(pattern, projectRoot, deps) || typeScriptScopePatternTargetsTypeScriptSource(pattern);
20197
+ function includePatternTargetsTypeScriptScope(pattern, productDir, deps) {
20198
+ return includePatternIsLiteralDirectory(pattern, productDir, deps) || typeScriptScopePatternTargetsTypeScriptSource(pattern);
20012
20199
  }
20013
- function includePatternIsLiteralDirectory(pattern, projectRoot, deps) {
20200
+ function includePatternIsLiteralDirectory(pattern, productDir, deps) {
20014
20201
  const normalizedPattern = normalizeTypeScriptScopePath(pattern);
20015
- return !typeScriptScopePatternHasGlob(normalizedPattern) && pathIsDirectory(resolveProjectPath(projectRoot, normalizedPattern), deps);
20202
+ return !typeScriptScopePatternHasGlob(normalizedPattern) && pathIsDirectory(resolveProductPath(productDir, normalizedPattern), deps);
20016
20203
  }
20017
20204
  function pathIsDirectory(path4, deps) {
20018
20205
  try {
@@ -20022,28 +20209,28 @@ function pathIsDirectory(path4, deps) {
20022
20209
  return false;
20023
20210
  }
20024
20211
  }
20025
- function normalizeActiveIncludePattern(pattern, projectRoot, deps) {
20212
+ function normalizeActiveIncludePattern(pattern, productDir, deps) {
20026
20213
  const normalizedPattern = normalizeTypeScriptScopePath(pattern);
20027
- return includePatternIsLiteralDirectory(normalizedPattern, projectRoot, deps) ? `${normalizedPattern}${TYPESCRIPT_SCOPE_DIRECTORY_PATTERN_SUFFIX}` : pattern;
20214
+ return includePatternIsLiteralDirectory(normalizedPattern, productDir, deps) ? `${normalizedPattern}${TYPESCRIPT_SCOPE_DIRECTORY_PATTERN_SUFFIX}` : pattern;
20028
20215
  }
20029
- function filterActiveIncludePatterns(patterns, excludePatterns, projectRoot, deps) {
20030
- return patterns.map((pattern) => normalizeActiveIncludePattern(pattern, projectRoot, deps)).filter((pattern) => typeScriptScopePatternTargetsTypeScriptSource(pattern)).filter((pattern) => {
20216
+ function filterActiveIncludePatterns(patterns, excludePatterns, productDir, deps) {
20217
+ return patterns.map((pattern) => normalizeActiveIncludePattern(pattern, productDir, deps)).filter((pattern) => typeScriptScopePatternTargetsTypeScriptSource(pattern)).filter((pattern) => {
20031
20218
  const topLevelDir = getLiteralTopLevelPatternDirectory(pattern);
20032
- return topLevelDir === null || deps.existsSync(join32(projectRoot, topLevelDir));
20219
+ return topLevelDir === null || deps.existsSync(join32(productDir, topLevelDir));
20033
20220
  }).filter((pattern) => pathPassesValidationFilter(pattern, { exclude: excludePatterns }));
20034
20221
  }
20035
- function getValidationDirectories(scope2, projectRoot, deps = defaultScopeDeps) {
20036
- const config = resolveTypeScriptConfig(scope2, projectRoot, deps);
20037
- const configDirectories = getTopLevelDirectoriesWithTypeScript(config, projectRoot, deps);
20038
- const existingDirectories = configDirectories.filter((dir) => deps.existsSync(join32(projectRoot, dir)));
20222
+ function getValidationDirectories(scope2, productDir, deps = defaultScopeDeps) {
20223
+ const config = resolveTypeScriptConfig(scope2, productDir, deps);
20224
+ const configDirectories = getTopLevelDirectoriesWithTypeScript(config, productDir, deps);
20225
+ const existingDirectories = configDirectories.filter((dir) => deps.existsSync(join32(productDir, dir)));
20039
20226
  return existingDirectories;
20040
20227
  }
20041
- function getTypeScriptScope(scope2, projectRoot, deps = defaultScopeDeps) {
20042
- const directories = getValidationDirectories(scope2, projectRoot, deps);
20043
- const config = resolveTypeScriptConfig(scope2, projectRoot, deps);
20228
+ function getTypeScriptScope(scope2, productDir, deps = defaultScopeDeps) {
20229
+ const directories = getValidationDirectories(scope2, productDir, deps);
20230
+ const config = resolveTypeScriptConfig(scope2, productDir, deps);
20044
20231
  return {
20045
20232
  directories,
20046
- filePatterns: filterActiveIncludePatterns(config.include ?? [], config.exclude ?? [], projectRoot, deps),
20233
+ filePatterns: filterActiveIncludePatterns(config.include ?? [], config.exclude ?? [], productDir, deps),
20047
20234
  excludePatterns: config.exclude ?? []
20048
20235
  };
20049
20236
  }
@@ -20055,31 +20242,31 @@ function pathPassesTypeScriptScope(path4, scopeConfig) {
20055
20242
  const excluded = scopeConfig.excludePatterns.some((pattern) => pathMatchesTypeScriptPattern(path4, pattern));
20056
20243
  return included && !excluded;
20057
20244
  }
20058
- function pathStaysInsideTypeScriptScopeRoot(projectRoot, path4) {
20059
- const resolvedPath = isAbsolute8(path4) ? resolve14(path4) : resolve14(projectRoot, path4);
20060
- const relativePath = relative7(projectRoot, resolvedPath);
20061
- const segments = normalizeTypeScriptScopePath(relativePath).split(PATH_SEGMENT_SEPARATOR4);
20245
+ function pathStaysInsideTypeScriptScopeRoot(productDir, path4) {
20246
+ const resolvedPath = isAbsolute8(path4) ? resolve14(path4) : resolve14(productDir, path4);
20247
+ const relativePath = relative7(productDir, resolvedPath);
20248
+ const segments = normalizeTypeScriptScopePath(relativePath).split(PATH_SEGMENT_SEPARATOR3);
20062
20249
  return relativePath.length === 0 || !segments.includes("..") && !isAbsolute8(relativePath);
20063
20250
  }
20064
- function toProjectRelativeTypeScriptScopePath(projectRoot, path4) {
20065
- const resolvedPath = isAbsolute8(path4) ? resolve14(path4) : resolve14(projectRoot, path4);
20066
- const relativePath = relative7(projectRoot, resolvedPath);
20251
+ function toProductRelativeTypeScriptScopePath(productDir, path4) {
20252
+ const resolvedPath = isAbsolute8(path4) ? resolve14(path4) : resolve14(productDir, path4);
20253
+ const relativePath = relative7(productDir, resolvedPath);
20067
20254
  return relativePath.length === 0 ? TYPESCRIPT_SCOPE_PROJECT_ROOT : normalizeTypeScriptScopePath(relativePath);
20068
20255
  }
20069
- function toExplicitTypeScriptScopeTarget(projectRoot, originalPath, deps = defaultScopeDeps) {
20070
- const path4 = toProjectRelativeTypeScriptScopePath(projectRoot, originalPath);
20256
+ function toExplicitTypeScriptScopeTarget(productDir, originalPath, deps = defaultScopeDeps) {
20257
+ const path4 = toProductRelativeTypeScriptScopePath(productDir, originalPath);
20071
20258
  return {
20072
- kind: pathIsDirectory(resolveProjectPath(projectRoot, path4), deps) ? EXPLICIT_TYPESCRIPT_SCOPE_TARGET_KIND.DIRECTORY : EXPLICIT_TYPESCRIPT_SCOPE_TARGET_KIND.FILE,
20259
+ kind: pathIsDirectory(resolveProductPath(productDir, path4), deps) ? EXPLICIT_TYPESCRIPT_SCOPE_TARGET_KIND.DIRECTORY : EXPLICIT_TYPESCRIPT_SCOPE_TARGET_KIND.FILE,
20073
20260
  path: path4
20074
20261
  };
20075
20262
  }
20076
- function explicitTypeScriptScopeTargetExists(projectRoot, target, deps) {
20077
- return deps.existsSync(resolveProjectPath(projectRoot, target.path));
20263
+ function explicitTypeScriptScopeTargetExists(productDir, target, deps) {
20264
+ return deps.existsSync(resolveProductPath(productDir, target.path));
20078
20265
  }
20079
20266
  function filterExplicitTypeScriptScopeTargets(filter, deps = defaultScopeDeps) {
20080
20267
  const {
20081
20268
  paths,
20082
- projectRoot,
20269
+ productDir,
20083
20270
  requireExistingPaths = true,
20084
20271
  scopeConfig,
20085
20272
  validationPathFilter,
@@ -20088,7 +20275,7 @@ function filterExplicitTypeScriptScopeTargets(filter, deps = defaultScopeDeps) {
20088
20275
  if (paths === void 0) {
20089
20276
  return void 0;
20090
20277
  }
20091
- return paths.filter((path4) => pathStaysInsideTypeScriptScopeRoot(projectRoot, path4)).map((path4) => toExplicitTypeScriptScopeTarget(projectRoot, path4, deps)).filter((target) => !requireExistingPaths || explicitTypeScriptScopeTargetExists(projectRoot, target, deps)).filter((target) => explicitTypeScriptScopeTargetPassesSourceKind(target)).filter(
20278
+ return paths.filter((path4) => pathStaysInsideTypeScriptScopeRoot(productDir, path4)).map((path4) => toExplicitTypeScriptScopeTarget(productDir, path4, deps)).filter((target) => !requireExistingPaths || explicitTypeScriptScopeTargetExists(productDir, target, deps)).filter((target) => explicitTypeScriptScopeTargetPassesSourceKind(target)).filter(
20092
20279
  (target) => bypassValidationPathFilter || explicitTypeScriptScopeTargetIntersectsValidationPathFilter(target, validationPathFilter)
20093
20280
  ).filter((target) => explicitTypeScriptScopeTargetPassesScope(target, scopeConfig));
20094
20281
  }
@@ -20157,7 +20344,7 @@ function constrainTypeScriptScopeToExplicitTargets(scopeConfig, targets) {
20157
20344
  const retainedDirectoryOperandPatterns = scopeConfig.filePatterns.length === 0 ? retainedDirectories.map(typeScriptLiteralDirectoryPattern) : [];
20158
20345
  const explicitFileTargets = targets.filter((target) => target.kind === EXPLICIT_TYPESCRIPT_SCOPE_TARGET_KIND.FILE).map((target) => target.path).filter(
20159
20346
  (path4) => !retainedDirectories.some(
20160
- (directory) => path4 === directory || path4.startsWith(`${directory}${PATH_SEGMENT_SEPARATOR4}`)
20347
+ (directory) => path4 === directory || path4.startsWith(`${directory}${PATH_SEGMENT_SEPARATOR3}`)
20161
20348
  )
20162
20349
  );
20163
20350
  const uncoveredExplicitFileTargets = explicitFileTargets.filter(
@@ -20182,28 +20369,28 @@ function typeScriptLiteralDirectoryPattern(pattern) {
20182
20369
  return pattern === TYPESCRIPT_SCOPE_PROJECT_ROOT ? TYPESCRIPT_SCOPE_DIRECTORY_PATTERN_SUFFIX.slice(1) : `${pattern}${TYPESCRIPT_SCOPE_DIRECTORY_PATTERN_SUFFIX}`;
20183
20370
  }
20184
20371
  function resolveTypeScriptValidationScope(filter, deps = defaultScopeDeps) {
20185
- const baseScopeConfig = getTypeScriptScope(filter.scope, filter.projectRoot, deps);
20372
+ const baseScopeConfig = getTypeScriptScope(filter.scope, filter.productDir, deps);
20186
20373
  const scopeConfig = applyValidationPathFilterToScope(baseScopeConfig, filter.validationPathFilter);
20187
- const explicitTargetScopeConfig = filter.bypassExplicitPathValidationFilter === true ? baseScopeConfig : scopeConfig;
20188
20374
  const explicitTargets = filterExplicitTypeScriptScopeTargets({
20189
20375
  paths: filter.paths,
20190
- projectRoot: filter.projectRoot,
20376
+ productDir: filter.productDir,
20191
20377
  validationPathFilter: filter.validationPathFilter,
20192
- scopeConfig: explicitTargetScopeConfig,
20193
- bypassValidationPathFilter: filter.bypassExplicitPathValidationFilter
20378
+ scopeConfig: baseScopeConfig,
20379
+ bypassValidationPathFilter: true
20194
20380
  }, deps);
20195
20381
  if (filter.paths !== void 0 && filter.paths.length > 0 && explicitTargets?.length === 0) {
20196
20382
  return {
20197
20383
  ...scopeConfig,
20198
20384
  directories: [],
20199
20385
  filePatterns: [],
20200
- filteredByValidationPaths: true,
20201
- filteredByValidationPathIncludes: true,
20202
- filteredByValidationPathNoMatches: true
20386
+ explicitPathNoMatches: true,
20387
+ filteredByValidationPaths: void 0,
20388
+ filteredByValidationPathIncludes: void 0,
20389
+ filteredByValidationPathNoMatches: void 0
20203
20390
  };
20204
20391
  }
20205
20392
  if (explicitTargets !== void 0 && explicitTargets.length > 0) {
20206
- const explicitScopeConfig = constrainTypeScriptScopeToExplicitTargets(explicitTargetScopeConfig, explicitTargets);
20393
+ const explicitScopeConfig = constrainTypeScriptScopeToExplicitTargets(baseScopeConfig, explicitTargets);
20207
20394
  return filter.markExplicitPathsAsValidationFilter === true ? {
20208
20395
  ...explicitScopeConfig,
20209
20396
  filteredByValidationPaths: true,
@@ -20216,7 +20403,7 @@ function resolveTypeScriptValidationScope(filter, deps = defaultScopeDeps) {
20216
20403
  function constrainTypeScriptPatternToDirectory(pattern, directory) {
20217
20404
  const normalizedPattern = normalizeTypeScriptScopePath(pattern);
20218
20405
  const normalizedDirectory = normalizeTypeScriptScopePath(directory);
20219
- if (normalizedPattern === normalizedDirectory || normalizedPattern.startsWith(`${normalizedDirectory}${PATH_SEGMENT_SEPARATOR4}`)) {
20406
+ if (normalizedPattern === normalizedDirectory || normalizedPattern.startsWith(`${normalizedDirectory}${PATH_SEGMENT_SEPARATOR3}`)) {
20220
20407
  return normalizedPattern;
20221
20408
  }
20222
20409
  const patternSegments = splitTypeScriptScopePathSegments(normalizedPattern);
@@ -20229,7 +20416,7 @@ function constrainTypeScriptPatternToDirectory(pattern, directory) {
20229
20416
  const suffixSegments = patternSegments.slice(patternIndex);
20230
20417
  const constrainedSuffixSegments = directoryAdvance.recursiveGlobConsumedDirectory && suffixSegments.length > 0 ? [RECURSIVE_GLOB_SEGMENT, ...suffixSegments] : suffixSegments;
20231
20418
  if (constrainedSuffixSegments.length > 0) {
20232
- return [normalizedDirectory, ...constrainedSuffixSegments].join(PATH_SEGMENT_SEPARATOR4);
20419
+ return [normalizedDirectory, ...constrainedSuffixSegments].join(PATH_SEGMENT_SEPARATOR3);
20233
20420
  }
20234
20421
  return typeScriptScopePatternHasGlob(normalizedPattern) && typeScriptScopePatternCoversDirectorySourceSet(normalizedPattern, normalizedDirectory) ? `${normalizedDirectory}${TYPESCRIPT_SCOPE_DIRECTORY_PATTERN_SUFFIX}` : normalizedDirectory;
20235
20422
  }
@@ -20263,7 +20450,7 @@ function patternSegmentMatchesDirectorySegment(patternSegment, directorySegment)
20263
20450
 
20264
20451
  // src/validation/discovery/tool-finder.ts
20265
20452
  import fs3 from "fs";
20266
- import { createRequire } from "module";
20453
+ import { createRequire as createRequire2 } from "module";
20267
20454
  import path3 from "path";
20268
20455
  import { fileURLToPath } from "url";
20269
20456
 
@@ -20298,7 +20485,11 @@ var TOOL_DISCOVERY = {
20298
20485
  };
20299
20486
 
20300
20487
  // src/validation/discovery/tool-finder.ts
20301
- var require2 = createRequire(import.meta.url);
20488
+ var TOOL_DISCOVERY_PRIORITY = {
20489
+ BUNDLED_FIRST: "bundled-first",
20490
+ PRODUCT_FIRST: "product-first"
20491
+ };
20492
+ var require2 = createRequire2(import.meta.url);
20302
20493
  var FILE_URL_PROTOCOL = new URL(import.meta.url).protocol;
20303
20494
  var PACKAGE_MANIFEST_FILENAME = "package.json";
20304
20495
  var defaultToolDiscoveryDeps = {
@@ -20345,30 +20536,44 @@ function bundledToolPath(resolvedPath, existsSync10) {
20345
20536
  return nearestPackageRoot(bundledFilePath, existsSync10) ?? path3.dirname(bundledFilePath);
20346
20537
  }
20347
20538
  async function discoverTool(tool, options = {}) {
20348
- const { projectRoot = CONFIG_PROCESS_CWD.read(), deps = defaultToolDiscoveryDeps } = options;
20349
- const bundledPath = deps.resolveModule(`${tool}/package.json`) ?? deps.resolveImport?.(tool);
20539
+ const {
20540
+ productDir = CONFIG_PROCESS_CWD.read(),
20541
+ executableName = tool,
20542
+ bundledExecutable,
20543
+ includeBundled = true,
20544
+ priority = TOOL_DISCOVERY_PRIORITY.BUNDLED_FIRST,
20545
+ deps = defaultToolDiscoveryDeps
20546
+ } = options;
20547
+ const productBinPath = path3.join(productDir, "node_modules", ".bin", executableName);
20548
+ const productLocation = () => deps.existsSync(productBinPath) ? {
20549
+ found: true,
20550
+ location: {
20551
+ tool,
20552
+ path: productBinPath,
20553
+ source: TOOL_DISCOVERY.SOURCES.PROJECT
20554
+ }
20555
+ } : null;
20556
+ if (priority === TOOL_DISCOVERY_PRIORITY.PRODUCT_FIRST) {
20557
+ const productResult = productLocation();
20558
+ if (productResult !== null) return productResult;
20559
+ }
20560
+ const bundledSpecifier = bundledExecutable ?? `${tool}/package.json`;
20561
+ const bundledPath = includeBundled ? deps.resolveModule(bundledSpecifier) ?? deps.resolveImport?.(bundledExecutable ?? tool) : null;
20350
20562
  if (bundledPath) {
20351
20563
  return {
20352
20564
  found: true,
20353
20565
  location: {
20354
20566
  tool,
20355
- path: bundledToolPath(bundledPath, deps.existsSync),
20567
+ path: bundledExecutable === void 0 ? bundledToolPath(bundledPath, deps.existsSync) : resolvedModulePath(bundledPath),
20356
20568
  source: TOOL_DISCOVERY.SOURCES.BUNDLED
20357
20569
  }
20358
20570
  };
20359
20571
  }
20360
- const projectBinPath = path3.join(projectRoot, "node_modules", ".bin", tool);
20361
- if (deps.existsSync(projectBinPath)) {
20362
- return {
20363
- found: true,
20364
- location: {
20365
- tool,
20366
- path: projectBinPath,
20367
- source: TOOL_DISCOVERY.SOURCES.PROJECT
20368
- }
20369
- };
20572
+ if (priority === TOOL_DISCOVERY_PRIORITY.BUNDLED_FIRST) {
20573
+ const productResult = productLocation();
20574
+ if (productResult !== null) return productResult;
20370
20575
  }
20371
- const globalPath = deps.whichSync(tool);
20576
+ const globalPath = deps.whichSync(executableName);
20372
20577
  if (globalPath) {
20373
20578
  return {
20374
20579
  found: true,
@@ -20519,13 +20724,13 @@ function dependencyCruiserGlobSegmentToRegExpSource(segment) {
20519
20724
  }
20520
20725
  return source;
20521
20726
  }
20522
- function buildDependencyCruiserOptions(typescriptScope, projectRoot, tsConfigFile) {
20727
+ function buildDependencyCruiserOptions(typescriptScope, productDir, tsConfigFile) {
20523
20728
  const excludePatterns = [
20524
20729
  DEPENDENCY_CRUISER_PACKAGE_EXCLUDE_PATTERN,
20525
20730
  ...toDependencyCruiserExcludePatterns(typescriptScope.excludePatterns)
20526
20731
  ];
20527
20732
  return {
20528
- baseDir: projectRoot,
20733
+ baseDir: productDir,
20529
20734
  enhancedResolveOptions: { extensions: [...DEPENDENCY_CRUISER_TYPESCRIPT_RESOLVE_EXTENSIONS] },
20530
20735
  exclude: { path: excludePatterns },
20531
20736
  includeOnly: { path: DEPENDENCY_CRUISER_TYPESCRIPT_SOURCE_PATTERN },
@@ -20633,16 +20838,16 @@ function circularDependencyCycles(result) {
20633
20838
  );
20634
20839
  return uniqueCycles(cycles);
20635
20840
  }
20636
- async function validateCircularDependencies(scope2, typescriptScope, projectRoot, deps = defaultCircularDeps) {
20841
+ async function validateCircularDependencies(scope2, typescriptScope, productDir, deps = defaultCircularDeps) {
20637
20842
  try {
20638
20843
  const analyzeSourcePatterns = toDependencyCruiserSourcePatterns(typescriptScope);
20639
20844
  if (analyzeSourcePatterns.length === 0) {
20640
20845
  return { success: true };
20641
20846
  }
20642
- const tsConfigFile = join33(projectRoot, TSCONFIG_FILES[scope2]);
20847
+ const tsConfigFile = join33(productDir, TSCONFIG_FILES[scope2]);
20643
20848
  const result = await deps.dependencyCruiser(
20644
20849
  analyzeSourcePatterns,
20645
- buildDependencyCruiserOptions(typescriptScope, projectRoot, tsConfigFile),
20850
+ buildDependencyCruiserOptions(typescriptScope, productDir, tsConfigFile),
20646
20851
  void 0,
20647
20852
  { tsConfig: deps.extractTypeScriptConfig(tsConfigFile) }
20648
20853
  );
@@ -20681,9 +20886,9 @@ var EXECUTION_MODES = {
20681
20886
 
20682
20887
  // src/commands/validation/circular.ts
20683
20888
  var TYPESCRIPT_ABSENT_MESSAGE = formatTypeScriptAbsentSkipMessage(VALIDATION_STAGE_DISPLAY_NAMES.CIRCULAR);
20684
- var CIRCULAR_CONFIG_ERROR_MESSAGE = `${VALIDATION_STAGE_DISPLAY_NAMES.CIRCULAR}: \u2717 config error`;
20685
- var CIRCULAR_VALIDATION_PATHS_NO_TARGETS_MESSAGE = formatValidationPathsNoTargetsSkipMessage(
20686
- VALIDATION_STAGE_DISPLAY_NAMES.CIRCULAR
20889
+ var CIRCULAR_CONFIG_ERROR_MESSAGE = formatValidationConfigProblemMessage(
20890
+ VALIDATION_STAGE_DISPLAY_NAMES.CIRCULAR,
20891
+ "configuration error"
20687
20892
  );
20688
20893
  var CIRCULAR_DEPENDENCY_OUTPUT = {
20689
20894
  FOUND: VALIDATION_COMMAND_OUTPUT.CIRCULAR_FOUND
@@ -20691,7 +20896,6 @@ var CIRCULAR_DEPENDENCY_OUTPUT = {
20691
20896
  var defaultCircularCommandDeps = {
20692
20897
  validateCircularDependencies
20693
20898
  };
20694
- var DEPENDENCY_CRUISER_PACKAGE_NAME = "dependency-cruiser";
20695
20899
  function formatCircularValidationResult(result, quiet) {
20696
20900
  if (result.success) {
20697
20901
  return {
@@ -20723,11 +20927,6 @@ async function circularCommand(options, deps = defaultCircularCommandDeps) {
20723
20927
  durationMs: Date.now() - startTime
20724
20928
  };
20725
20929
  }
20726
- const toolResult = await discoverTool(DEPENDENCY_CRUISER_PACKAGE_NAME, { projectRoot: cwd });
20727
- if (!toolResult.found) {
20728
- const skipMessage = formatSkipMessage(VALIDATION_STAGE_DISPLAY_NAMES.CIRCULAR, toolResult);
20729
- return { exitCode: 0, output: skipMessage, durationMs: Date.now() - startTime };
20730
- }
20731
20930
  const loaded = await resolveConfig(cwd, [validationConfigDescriptor]);
20732
20931
  if (!loaded.ok) {
20733
20932
  return {
@@ -20738,19 +20937,22 @@ async function circularCommand(options, deps = defaultCircularCommandDeps) {
20738
20937
  }
20739
20938
  const validationConfig = loaded.value[validationConfigDescriptor.section];
20740
20939
  const effectiveScopeConfig = resolveTypeScriptValidationScope({
20741
- projectRoot: cwd,
20940
+ productDir: cwd,
20742
20941
  scope: scope2,
20743
20942
  paths: files,
20744
20943
  validationPathFilter: validationPathFilterForTool(
20745
20944
  validationConfig.paths,
20746
20945
  VALIDATION_PATH_TOOL_SUBSECTIONS.CIRCULAR
20747
- ),
20748
- bypassExplicitPathValidationFilter: true
20946
+ )
20749
20947
  });
20750
- if (effectiveScopeConfig.filteredByValidationPathNoMatches) {
20948
+ const noTargetsMessage = formatValidationScopeNoTargetsSkipMessage(
20949
+ VALIDATION_STAGE_DISPLAY_NAMES.CIRCULAR,
20950
+ effectiveScopeConfig
20951
+ );
20952
+ if (noTargetsMessage !== void 0) {
20751
20953
  return {
20752
20954
  exitCode: 0,
20753
- output: quiet ? "" : CIRCULAR_VALIDATION_PATHS_NO_TARGETS_MESSAGE,
20955
+ output: quiet ? "" : noTargetsMessage,
20754
20956
  durationMs: Date.now() - startTime
20755
20957
  };
20756
20958
  }
@@ -20770,6 +20972,7 @@ var KNIP_COMMAND_TOKENS = {
20770
20972
  TSCONFIG_FLAG: "--tsConfig",
20771
20973
  USE_TSCONFIG_FILES_FLAG: "--use-tsconfig-files"
20772
20974
  };
20975
+ var KNIP_LOCAL_BIN_SEGMENTS = ["node_modules", ".bin", KNIP_COMMAND_TOKENS.COMMAND];
20773
20976
  var defaultKnipDeps = {
20774
20977
  existsSync: existsSync4,
20775
20978
  mkdir: mkdir7,
@@ -20777,9 +20980,9 @@ var defaultKnipDeps = {
20777
20980
  rm: rm4,
20778
20981
  writeFile: writeFile5
20779
20982
  };
20780
- async function validateKnip2(context, runner = defaultKnipProcessRunner, deps = defaultKnipDeps) {
20983
+ async function validateKnip2(context, runner = defaultKnipProcessRunner, deps = defaultKnipDeps, outputStreams = defaultValidationSubprocessOutputStreams) {
20781
20984
  try {
20782
- const { projectRoot, typescriptScope } = context;
20985
+ const { productDir, typescriptScope, toolPath } = context;
20783
20986
  const analyzeTargets = [
20784
20987
  ...typescriptScope.directories,
20785
20988
  ...typescriptScope.filePatterns
@@ -20787,16 +20990,16 @@ async function validateKnip2(context, runner = defaultKnipProcessRunner, deps =
20787
20990
  if (analyzeTargets.length === 0) {
20788
20991
  return { success: true };
20789
20992
  }
20790
- return await runKnipSubprocess(projectRoot, typescriptScope, runner, deps);
20993
+ return await runKnipSubprocess(productDir, typescriptScope, runner, deps, outputStreams, toolPath);
20791
20994
  } catch (error) {
20792
20995
  const errorMessage2 = error instanceof Error ? error.message : String(error);
20793
20996
  return { success: false, error: errorMessage2 };
20794
20997
  }
20795
20998
  }
20796
- async function runKnipSubprocess(projectRoot, typescriptScope, runner, deps) {
20797
- const scopedTsconfig = typescriptScope.filteredByValidationPaths ? await createScopedKnipTsconfig(projectRoot, typescriptScope, deps) : void 0;
20798
- const localBin = join34(projectRoot, "node_modules", ".bin", "knip");
20799
- const binary = deps.existsSync(localBin) ? localBin : KNIP_COMMAND_TOKENS.NPX_COMMAND;
20999
+ async function runKnipSubprocess(productDir, typescriptScope, runner, deps, outputStreams, toolPath) {
21000
+ const scopedTsconfig = typescriptScope.filteredByValidationPaths ? await createScopedKnipTsconfig(productDir, typescriptScope, deps) : void 0;
21001
+ const localBin = join34(productDir, ...KNIP_LOCAL_BIN_SEGMENTS);
21002
+ const binary = toolPath ?? (deps.existsSync(localBin) ? localBin : KNIP_COMMAND_TOKENS.NPX_COMMAND);
20800
21003
  const baseArgs = scopedTsconfig === void 0 ? [] : [
20801
21004
  KNIP_COMMAND_TOKENS.USE_TSCONFIG_FILES_FLAG,
20802
21005
  KNIP_COMMAND_TOKENS.TSCONFIG_FLAG,
@@ -20804,8 +21007,9 @@ async function runKnipSubprocess(projectRoot, typescriptScope, runner, deps) {
20804
21007
  ];
20805
21008
  const args = binary === KNIP_COMMAND_TOKENS.NPX_COMMAND ? [KNIP_COMMAND_TOKENS.COMMAND, ...baseArgs] : baseArgs;
20806
21009
  const knipProcess = spawnManagedSubprocess(runner, binary, args, {
20807
- cwd: projectRoot
21010
+ cwd: productDir
20808
21011
  });
21012
+ forwardValidationSubprocessOutput(knipProcess, outputStreams);
20809
21013
  const cleanup = scopedTsconfig?.cleanup ?? (async () => {
20810
21014
  });
20811
21015
  let cleanupStarted = false;
@@ -20835,7 +21039,7 @@ async function runKnipSubprocess(projectRoot, typescriptScope, runner, deps) {
20835
21039
  };
20836
21040
  knipProcess.on("close", (code) => {
20837
21041
  if (code === 0) {
20838
- resolveAfterCleanup({ success: true });
21042
+ resolveAfterCleanup({ success: true, output: knipOutput });
20839
21043
  } else {
20840
21044
  const errorOutput = knipOutput || knipError || "Unused code detected";
20841
21045
  resolveAfterCleanup({
@@ -20849,12 +21053,12 @@ async function runKnipSubprocess(projectRoot, typescriptScope, runner, deps) {
20849
21053
  });
20850
21054
  });
20851
21055
  }
20852
- async function createScopedKnipTsconfig(projectRoot, typescriptScope, deps) {
20853
- const tempParentDir = join34(projectRoot, ...TEMPORARY_TSCONFIG_PARENT_SEGMENTS);
21056
+ async function createScopedKnipTsconfig(productDir, typescriptScope, deps) {
21057
+ const tempParentDir = join34(productDir, ...TEMPORARY_TSCONFIG_PARENT_SEGMENTS);
20854
21058
  await deps.mkdir(tempParentDir, { recursive: true });
20855
21059
  const tempDir = await deps.mkdtemp(join34(tempParentDir, "validate-knip-"));
20856
21060
  const configPath = join34(tempDir, TSCONFIG_FILES.full);
20857
- const toProjectPathPattern = (pattern) => isAbsolute9(pattern) ? pattern : join34(projectRoot, pattern);
21061
+ const toProjectPathPattern = (pattern) => isAbsolute9(pattern) ? pattern : join34(productDir, pattern);
20858
21062
  const project = [
20859
21063
  ...typescriptScope.directories.flatMap(
20860
21064
  (directory) => TYPESCRIPT_FALLBACK_INCLUDE_PATTERNS.map((pattern) => `${directory}/${pattern}`)
@@ -20862,7 +21066,7 @@ async function createScopedKnipTsconfig(projectRoot, typescriptScope, deps) {
20862
21066
  ...typescriptScope.filePatterns
20863
21067
  ];
20864
21068
  const config = {
20865
- extends: join34(projectRoot, TSCONFIG_FILES.full),
21069
+ extends: join34(productDir, TSCONFIG_FILES.full),
20866
21070
  include: project.map(toProjectPathPattern),
20867
21071
  exclude: typescriptScope.excludePatterns.map(toProjectPathPattern)
20868
21072
  };
@@ -20879,17 +21083,25 @@ var defaultKnipCommandDeps = {
20879
21083
  discoverTool,
20880
21084
  validateKnip: validateKnip2
20881
21085
  };
20882
- var TYPESCRIPT_ABSENT_MESSAGE2 = formatTypeScriptAbsentSkipMessage(VALIDATION_STAGE_DISPLAY_NAMES.KNIP);
20883
- var KNIP_VALIDATION_PATHS_NO_TARGETS_MESSAGE = formatValidationPathsNoTargetsSkipMessage(
21086
+ var KNIP_VALIDATION_STEP_NAME = "unused code detection";
21087
+ var KNIP_TYPESCRIPT_ABSENT_MESSAGE = formatTypeScriptAbsentSkipMessage(
20884
21088
  VALIDATION_STAGE_DISPLAY_NAMES.KNIP
20885
21089
  );
20886
21090
  async function knipCommand(options, deps = defaultKnipCommandDeps) {
20887
- const { cwd, files, quiet, scope: scope2 = VALIDATION_SCOPES.FULL } = options;
21091
+ const {
21092
+ cwd,
21093
+ files,
21094
+ json,
21095
+ outputStreams,
21096
+ quiet,
21097
+ scope: scope2 = VALIDATION_SCOPES.FULL,
21098
+ streamedPipelineOutput
21099
+ } = options;
20888
21100
  const startTime = Date.now();
20889
21101
  if (!deps.detectTypeScript(cwd).present) {
20890
21102
  return {
20891
21103
  exitCode: 0,
20892
- output: quiet ? "" : TYPESCRIPT_ABSENT_MESSAGE2,
21104
+ output: quiet ? "" : KNIP_TYPESCRIPT_ABSENT_MESSAGE,
20893
21105
  durationMs: Date.now() - startTime
20894
21106
  };
20895
21107
  }
@@ -20906,34 +21118,51 @@ async function knipCommand(options, deps = defaultKnipCommandDeps) {
20906
21118
  const output = quiet ? "" : VALIDATION_COMMAND_OUTPUT.KNIP_DISABLED;
20907
21119
  return { exitCode: 0, output, durationMs: Date.now() - startTime };
20908
21120
  }
20909
- const toolResult = await deps.discoverTool("knip", { projectRoot: cwd });
21121
+ const toolResult = await deps.discoverTool(KNIP_COMMAND_TOKENS.COMMAND, {
21122
+ productDir: cwd,
21123
+ includeBundled: false
21124
+ });
20910
21125
  if (!toolResult.found) {
20911
- const skipMessage = formatSkipMessage("unused code detection", toolResult);
21126
+ const skipMessage = formatSkipMessage(KNIP_VALIDATION_STEP_NAME, toolResult);
20912
21127
  return { exitCode: 0, output: skipMessage, durationMs: Date.now() - startTime };
20913
21128
  }
20914
21129
  const scopeConfig = resolveTypeScriptValidationScope({
20915
- projectRoot: cwd,
21130
+ productDir: cwd,
20916
21131
  scope: scope2,
20917
21132
  paths: files,
20918
21133
  validationPathFilter: validationPathFilterForTool(validationConfig.paths, VALIDATION_PATH_TOOL_SUBSECTIONS.KNIP),
20919
- markExplicitPathsAsValidationFilter: true,
20920
- bypassExplicitPathValidationFilter: true
21134
+ markExplicitPathsAsValidationFilter: true
20921
21135
  });
20922
- if (scopeConfig.filteredByValidationPathNoMatches) {
21136
+ const noTargetsMessage = formatValidationScopeNoTargetsSkipMessage(
21137
+ VALIDATION_STAGE_DISPLAY_NAMES.KNIP,
21138
+ scopeConfig
21139
+ );
21140
+ if (noTargetsMessage !== void 0) {
20923
21141
  return {
20924
21142
  exitCode: 0,
20925
- output: quiet ? "" : KNIP_VALIDATION_PATHS_NO_TARGETS_MESSAGE,
21143
+ output: quiet ? "" : noTargetsMessage,
20926
21144
  durationMs: Date.now() - startTime
20927
21145
  };
20928
21146
  }
20929
- const result = await deps.validateKnip({ projectRoot: cwd, typescriptScope: scopeConfig });
21147
+ const result = await deps.validateKnip(
21148
+ {
21149
+ productDir: cwd,
21150
+ typescriptScope: scopeConfig,
21151
+ toolPath: toolResult.location.path
21152
+ },
21153
+ void 0,
21154
+ void 0,
21155
+ outputStreams ?? discardValidationSubprocessOutputStreams
21156
+ );
20930
21157
  const durationMs = Date.now() - startTime;
20931
21158
  if (result.success) {
20932
- const output = quiet ? "" : VALIDATION_COMMAND_OUTPUT.KNIP_SUCCESS;
20933
- return { exitCode: 0, output, durationMs };
21159
+ const output = quiet ? "" : [VALIDATION_COMMAND_OUTPUT.KNIP_SUCCESS, result.output].filter((line) => line !== void 0 && line.length > 0).join("\n");
21160
+ const terminalOutput = streamedValidationTerminalOutput(result.output, json, streamedPipelineOutput);
21161
+ return { exitCode: 0, output, terminalOutput, durationMs };
20934
21162
  } else {
20935
21163
  const output = result.error ?? VALIDATION_COMMAND_OUTPUT.KNIP_FAILURE;
20936
- return { exitCode: 1, output, durationMs };
21164
+ const terminalOutput = streamedValidationTerminalOutput(result.error, json, streamedPipelineOutput);
21165
+ return { exitCode: 1, output, terminalOutput, durationMs };
20937
21166
  }
20938
21167
  }
20939
21168
 
@@ -21193,8 +21422,7 @@ function validateLintPolicy(productDir) {
21193
21422
  }
21194
21423
  }
21195
21424
 
21196
- // src/validation/steps/eslint.ts
21197
- var defaultEslintProcessRunner = lifecycleProcessRunner;
21425
+ // src/validation/steps/eslint-contract.ts
21198
21426
  var DEFAULT_ESLINT_CONFIG_FILE = "eslint.config.ts";
21199
21427
  var ESLINT_COMMAND_TOKENS = {
21200
21428
  COMMAND: "eslint",
@@ -21205,6 +21433,9 @@ var ESLINT_COMMAND_TOKENS = {
21205
21433
  IGNORE_PATTERN_FLAG: "--ignore-pattern"
21206
21434
  };
21207
21435
  var ESLINT_LOCAL_BIN_SEGMENTS = ["node_modules", ".bin", ESLINT_COMMAND_TOKENS.COMMAND];
21436
+
21437
+ // src/validation/steps/eslint.ts
21438
+ var defaultEslintProcessRunner = lifecycleProcessRunner;
21208
21439
  function buildEslintArgs(context) {
21209
21440
  const {
21210
21441
  validatedFiles,
@@ -21252,8 +21483,8 @@ function buildIgnorePatternArgs(patterns) {
21252
21483
  return patterns.flatMap((pattern) => [ESLINT_COMMAND_TOKENS.IGNORE_PATTERN_FLAG, pattern]);
21253
21484
  }
21254
21485
  async function validateESLint(context, runner = defaultEslintProcessRunner, outputStreams = defaultValidationSubprocessOutputStreams) {
21255
- const { projectRoot, scope: scope2, validatedFiles, mode, eslintConfigFile } = context;
21256
- const lintPolicy = validateLintPolicy(projectRoot);
21486
+ const { productDir, scope: scope2, validatedFiles, mode, eslintConfigFile, toolPath } = context;
21487
+ const lintPolicy = validateLintPolicy(productDir);
21257
21488
  if (!lintPolicy.ok) {
21258
21489
  return { success: false, error: lintPolicy.error };
21259
21490
  }
@@ -21269,47 +21500,57 @@ async function validateESLint(context, runner = defaultEslintProcessRunner, outp
21269
21500
  scopeConfig: context.scopeConfig
21270
21501
  });
21271
21502
  return new Promise((resolve17) => {
21272
- const localBin = join36(projectRoot, ...ESLINT_LOCAL_BIN_SEGMENTS);
21273
- const binary = existsSync6(localBin) ? localBin : "npx";
21503
+ const localBin = join36(productDir, ...ESLINT_LOCAL_BIN_SEGMENTS);
21504
+ const binary = toolPath ?? (existsSync6(localBin) ? localBin : "npx");
21274
21505
  const spawnArgs = binary === "npx" ? eslintArgs : eslintArgs.slice(1);
21275
21506
  const eslintProcess = spawnManagedSubprocess(runner, binary, spawnArgs, {
21276
- cwd: projectRoot
21507
+ cwd: productDir
21277
21508
  });
21509
+ const chunks = [];
21510
+ const capture = (chunk) => {
21511
+ chunks.push(typeof chunk === "string" ? chunk : Buffer.from(chunk).toString());
21512
+ };
21513
+ eslintProcess.stdout?.on(VALIDATION_SUBPROCESS_EVENTS.DATA, capture);
21514
+ eslintProcess.stderr?.on(VALIDATION_SUBPROCESS_EVENTS.DATA, capture);
21278
21515
  forwardValidationSubprocessOutput(eslintProcess, outputStreams);
21279
21516
  eslintProcess.on(VALIDATION_SUBPROCESS_EVENTS.CLOSE, (code) => {
21517
+ const output = chunks.join("");
21280
21518
  if (code === 0) {
21281
- resolve17({ success: true });
21519
+ resolve17({ success: true, output });
21282
21520
  } else {
21283
- resolve17({ success: false, error: `ESLint exited with code ${code}` });
21521
+ resolve17({ success: false, output, error: `ESLint exited with code ${code}` });
21284
21522
  }
21285
21523
  });
21286
21524
  eslintProcess.on(VALIDATION_SUBPROCESS_EVENTS.ERROR, (error) => {
21287
- resolve17({ success: false, error: error.message });
21525
+ resolve17({ success: false, output: chunks.join(""), error: error.message });
21288
21526
  });
21289
21527
  });
21290
21528
  }
21291
21529
 
21292
21530
  // src/commands/validation/lint.ts
21293
- var TYPESCRIPT_ABSENT_MESSAGE3 = formatTypeScriptAbsentSkipMessage(VALIDATION_STAGE_DISPLAY_NAMES.ESLINT);
21294
- var MISSING_CONFIG_MESSAGE = VALIDATION_COMMAND_OUTPUT.ESLINT_MISSING_CONFIG;
21295
- var ESLINT_CONFIG_ERROR_MESSAGE = `${VALIDATION_STAGE_DISPLAY_NAMES.ESLINT}: \u2717 config error`;
21296
- var VALIDATION_PATHS_NO_TARGETS_MESSAGE = formatValidationPathsNoTargetsSkipMessage(
21297
- VALIDATION_STAGE_DISPLAY_NAMES.ESLINT
21298
- );
21299
- var defaultLintCommandDependencies = {
21531
+ var defaultLintCommandDeps = {
21300
21532
  detectTypeScript,
21301
21533
  discoverTool,
21302
21534
  resolveConfig,
21303
21535
  validateESLint
21304
21536
  };
21305
- async function lintCommand(options, deps = defaultLintCommandDependencies) {
21306
- const { cwd, scope: scope2 = "full", files, fix, outputStreams, quiet } = options;
21537
+ var TYPESCRIPT_ABSENT_MESSAGE2 = formatTypeScriptAbsentSkipMessage(VALIDATION_STAGE_DISPLAY_NAMES.ESLINT);
21538
+ var MISSING_CONFIG_MESSAGE = VALIDATION_COMMAND_OUTPUT.ESLINT_MISSING_CONFIG;
21539
+ var ESLINT_CONFIG_ERROR_MESSAGE = formatValidationConfigProblemMessage(
21540
+ VALIDATION_STAGE_DISPLAY_NAMES.ESLINT,
21541
+ "configuration error"
21542
+ );
21543
+ var VALIDATION_PATHS_NO_TARGETS_MESSAGE = formatValidationPathsNoTargetsSkipMessage(
21544
+ VALIDATION_STAGE_DISPLAY_NAMES.ESLINT
21545
+ );
21546
+ async function lintCommand(options, deps = defaultLintCommandDeps) {
21547
+ const { cwd, scope: scope2 = "full", files, fix, json, outputStreams, quiet, streamedPipelineOutput } = options;
21307
21548
  const startTime = Date.now();
21308
21549
  const tsDetection = deps.detectTypeScript(cwd);
21309
21550
  if (!tsDetection.present) {
21310
21551
  return {
21311
21552
  exitCode: 0,
21312
- output: quiet ? "" : TYPESCRIPT_ABSENT_MESSAGE3,
21553
+ output: quiet ? "" : TYPESCRIPT_ABSENT_MESSAGE2,
21313
21554
  durationMs: Date.now() - startTime
21314
21555
  };
21315
21556
  }
@@ -21336,35 +21577,38 @@ async function lintCommand(options, deps = defaultLintCommandDependencies) {
21336
21577
  );
21337
21578
  const explicitMode = files !== void 0 && files.length > 0;
21338
21579
  const scopeConfig = resolveTypeScriptValidationScope({
21339
- projectRoot: cwd,
21580
+ productDir: cwd,
21340
21581
  scope: scope2,
21341
21582
  paths: files,
21342
21583
  validationPathFilter,
21343
- markExplicitPathsAsValidationFilter: explicitMode,
21344
- bypassExplicitPathValidationFilter: true
21584
+ markExplicitPathsAsValidationFilter: explicitMode
21345
21585
  });
21346
21586
  const explicitTargets = explicitMode ? filterExplicitTypeScriptScopeTargets({
21347
21587
  paths: files,
21348
- projectRoot: cwd,
21588
+ productDir: cwd,
21349
21589
  validationPathFilter,
21350
21590
  scopeConfig: getTypeScriptScope(scope2, cwd),
21351
21591
  bypassValidationPathFilter: true
21352
21592
  }) : void 0;
21353
- const validatedFiles = explicitTargets?.every((target) => target.kind === EXPLICIT_TYPESCRIPT_SCOPE_TARGET_KIND.FILE) ? explicitTargets.map((target) => formatLintValidationOperand(toProjectRelativeValidationPath(cwd, target.path))) : void 0;
21354
- if (scopeConfig.filteredByValidationPathNoMatches) {
21593
+ const validatedFiles = explicitTargets?.every((target) => target.kind === EXPLICIT_TYPESCRIPT_SCOPE_TARGET_KIND.FILE) ? explicitTargets.map((target) => formatLintValidationOperand(toProductRelativeValidationPath(cwd, target.path))) : void 0;
21594
+ const noTargetsMessage = formatValidationScopeNoTargetsSkipMessage(
21595
+ VALIDATION_STAGE_DISPLAY_NAMES.ESLINT,
21596
+ scopeConfig
21597
+ );
21598
+ if (noTargetsMessage !== void 0) {
21355
21599
  return {
21356
21600
  exitCode: 0,
21357
- output: quiet ? "" : VALIDATION_PATHS_NO_TARGETS_MESSAGE,
21601
+ output: quiet ? "" : noTargetsMessage,
21358
21602
  durationMs: Date.now() - startTime
21359
21603
  };
21360
21604
  }
21361
- const toolResult = await deps.discoverTool("eslint", { projectRoot: cwd });
21605
+ const toolResult = await deps.discoverTool("eslint", { productDir: cwd, includeBundled: false });
21362
21606
  if (!toolResult.found) {
21363
21607
  const skipMessage = formatSkipMessage(VALIDATION_STAGE_DISPLAY_NAMES.ESLINT, toolResult);
21364
21608
  return { exitCode: 0, output: skipMessage, durationMs: Date.now() - startTime };
21365
21609
  }
21366
21610
  const context = {
21367
- projectRoot: cwd,
21611
+ productDir: cwd,
21368
21612
  scope: scope2,
21369
21613
  scopeConfig,
21370
21614
  mode: fix ? "write" : "read",
@@ -21372,26 +21616,35 @@ async function lintCommand(options, deps = defaultLintCommandDependencies) {
21372
21616
  validatedFiles,
21373
21617
  validatedFileIgnorePatterns: void 0,
21374
21618
  isFileSpecificMode: Boolean(validatedFiles && validatedFiles.length > 0),
21375
- eslintConfigFile
21619
+ eslintConfigFile,
21620
+ toolPath: toolResult.location.path
21376
21621
  };
21377
- const result = await deps.validateESLint(context, void 0, outputStreams);
21622
+ const result = await deps.validateESLint(
21623
+ context,
21624
+ void 0,
21625
+ outputStreams ?? discardValidationSubprocessOutputStreams
21626
+ );
21378
21627
  const durationMs = Date.now() - startTime;
21379
- return formatLintResult(result, quiet, durationMs);
21628
+ return formatLintResult(result, quiet, durationMs, json, streamedPipelineOutput);
21380
21629
  }
21381
21630
  function formatLintValidationOperand(path4) {
21382
21631
  return path4.length === 0 ? "." : path4;
21383
21632
  }
21384
- function formatLintResult(result, quiet, durationMs) {
21633
+ function formatLintResult(result, quiet, durationMs, json, streamedPipelineOutput) {
21385
21634
  if (result.skipped) {
21386
21635
  const output2 = quiet ? "" : VALIDATION_PATHS_NO_TARGETS_MESSAGE;
21387
21636
  return { exitCode: 0, output: output2, durationMs };
21388
21637
  }
21389
21638
  if (result.success) {
21390
- const output2 = quiet ? "" : VALIDATION_COMMAND_OUTPUT.ESLINT_SUCCESS;
21391
- return { exitCode: 0, output: output2, durationMs };
21639
+ const output2 = quiet ? "" : [VALIDATION_COMMAND_OUTPUT.ESLINT_SUCCESS, result.output].filter(
21640
+ (line) => line !== void 0 && line.length > 0
21641
+ ).join("\n");
21642
+ const terminalOutput2 = streamedValidationTerminalOutput(result.output, json, streamedPipelineOutput);
21643
+ return { exitCode: 0, output: output2, terminalOutput: terminalOutput2, durationMs };
21392
21644
  }
21393
- const output = result.error ?? VALIDATION_COMMAND_OUTPUT.ESLINT_FAILURE;
21394
- return { exitCode: 1, output, durationMs };
21645
+ const output = [result.output, result.error ?? VALIDATION_COMMAND_OUTPUT.ESLINT_FAILURE].filter((line) => line !== void 0 && line.length > 0).join("\n");
21646
+ const terminalOutput = streamedValidationTerminalOutput(result.output, json, streamedPipelineOutput);
21647
+ return { exitCode: 1, output, terminalOutput, durationMs };
21395
21648
  }
21396
21649
 
21397
21650
  // src/domains/validation/literal-problem-kind.ts
@@ -21443,7 +21696,7 @@ var GIT_DEFAULT_GLOBAL_IGNORE_PATH = {
21443
21696
  GIT_DIRECTORY: "git",
21444
21697
  IGNORE_FILE: "ignore"
21445
21698
  };
21446
- var PATH_SEGMENT_SEPARATOR5 = "/";
21699
+ var PATH_SEGMENT_SEPARATOR4 = "/";
21447
21700
  var CURRENT_DIRECTORY_PREFIX2 = ".";
21448
21701
  var GIT_SCOPE_FAILURE_MESSAGE = "failed to read git scope";
21449
21702
  var GIT_MISSING_CONTEXT_MESSAGE = "missing git working tree";
@@ -21576,11 +21829,11 @@ function readGlobalExcludesPath(productDir) {
21576
21829
  }
21577
21830
  function parentPrefixes(path4) {
21578
21831
  const prefixes = [];
21579
- let index = path4.lastIndexOf(PATH_SEGMENT_SEPARATOR5);
21832
+ let index = path4.lastIndexOf(PATH_SEGMENT_SEPARATOR4);
21580
21833
  while (index > 0) {
21581
21834
  const parent = path4.slice(0, index);
21582
21835
  prefixes.push(parent);
21583
- index = parent.lastIndexOf(PATH_SEGMENT_SEPARATOR5);
21836
+ index = parent.lastIndexOf(PATH_SEGMENT_SEPARATOR4);
21584
21837
  }
21585
21838
  return prefixes;
21586
21839
  }
@@ -21635,7 +21888,7 @@ function createIgnoreSourceReader(productDir, config) {
21635
21888
  },
21636
21889
  hasIncludedDescendant(relativePath) {
21637
21890
  return descendantParents.has(
21638
- relativePath.endsWith(PATH_SEGMENT_SEPARATOR5) ? relativePath.slice(0, -1) : relativePath
21891
+ relativePath.endsWith(PATH_SEGMENT_SEPARATOR4) ? relativePath.slice(0, -1) : relativePath
21639
21892
  );
21640
21893
  },
21641
21894
  appliedOverrides() {
@@ -22340,7 +22593,8 @@ async function validateLiteralReuse(input) {
22340
22593
  return {
22341
22594
  findings,
22342
22595
  indexedOccurrencesByFile,
22343
- filteredByValidationPathNoMatches: input.scopeConfig?.filteredByValidationPathNoMatches
22596
+ filteredByValidationPathNoMatches: input.scopeConfig?.filteredByValidationPathNoMatches,
22597
+ explicitPathNoMatches: input.scopeConfig?.explicitPathNoMatches
22344
22598
  };
22345
22599
  }
22346
22600
  async function readSafe(path4) {
@@ -22388,29 +22642,29 @@ var OUTPUT_MODE_NAME = {
22388
22642
  JSON: "json"
22389
22643
  };
22390
22644
  var OUTPUT_MODE_NAMES = Object.values(OUTPUT_MODE_NAME);
22645
+ var defaultLiteralCommandDeps = {
22646
+ validateLiteralReuse
22647
+ };
22391
22648
  var LITERAL_EXIT_CODES = {
22392
22649
  OK: 0,
22393
22650
  FINDINGS: 1,
22394
22651
  CONFIG_ERROR: 2
22395
22652
  };
22396
- var TYPESCRIPT_ABSENT_MESSAGE4 = formatTypeScriptAbsentSkipMessage(
22397
- VALIDATION_STAGE_DISPLAY_NAMES.LITERAL
22398
- );
22399
- var VALIDATION_PATHS_NO_TARGETS_MESSAGE2 = formatValidationPathsNoTargetsSkipMessage(
22653
+ var TYPESCRIPT_ABSENT_MESSAGE3 = formatTypeScriptAbsentSkipMessage(
22400
22654
  VALIDATION_STAGE_DISPLAY_NAMES.LITERAL
22401
22655
  );
22402
22656
  var LITERAL_DISABLED_MESSAGE = `\u23ED ${VALIDATION_SKIP_LABELS.VERB} ${VALIDATION_STAGE_DISPLAY_NAMES.LITERAL} (${VALIDATION_SKIP_LABELS.DISABLED_BY_PREFIX} validation.literal.enabled)`;
22403
- var NO_PROBLEMS_MESSAGE = "Literal: \u2713 No problems";
22657
+ var NO_PROBLEMS_MESSAGE = formatValidationNoProblemsMessage(VALIDATION_STAGE_DISPLAY_NAMES.LITERAL);
22404
22658
  function formatNoProblemsOfKind(kind) {
22405
22659
  return `Literal: No problems of type ${kind}`;
22406
22660
  }
22407
- async function literalCommand(options) {
22661
+ async function literalCommand(options, deps = defaultLiteralCommandDeps) {
22408
22662
  const start = Date.now();
22409
22663
  const tsDetection = detectTypeScript(options.cwd);
22410
22664
  if (!tsDetection.present) {
22411
22665
  return {
22412
22666
  exitCode: LITERAL_EXIT_CODES.OK,
22413
- output: options.quiet ? "" : TYPESCRIPT_ABSENT_MESSAGE4,
22667
+ output: options.quiet ? "" : TYPESCRIPT_ABSENT_MESSAGE3,
22414
22668
  durationMs: Date.now() - start
22415
22669
  };
22416
22670
  }
@@ -22418,7 +22672,10 @@ async function literalCommand(options) {
22418
22672
  if (typeof resolved === "string") {
22419
22673
  return {
22420
22674
  exitCode: LITERAL_EXIT_CODES.CONFIG_ERROR,
22421
- output: `Literal: \u2717 config error \u2014 ${resolved}`,
22675
+ output: `${formatValidationConfigProblemMessage(
22676
+ VALIDATION_STAGE_DISPLAY_NAMES.LITERAL,
22677
+ "configuration error"
22678
+ )} \u2014 ${resolved}`,
22422
22679
  durationMs: Date.now() - start
22423
22680
  };
22424
22681
  }
@@ -22429,17 +22686,18 @@ async function literalCommand(options) {
22429
22686
  durationMs: Date.now() - start
22430
22687
  };
22431
22688
  }
22432
- const result = await validateLiteralReuse({
22689
+ const result = await deps.validateLiteralReuse({
22433
22690
  productDir: options.cwd,
22434
22691
  explicitFiles: explicitLiteralPaths(options.files),
22435
22692
  config: resolved.literalConfig,
22436
22693
  pathConfig: resolved.pathConfig,
22437
22694
  scopeConfig: resolveExplicitLiteralTypeScriptScope(options, resolved.pathConfig)
22438
22695
  });
22439
- if (options.files !== void 0 && options.files.length > 0 && result.filteredByValidationPathNoMatches) {
22696
+ const noTargetsMessage = explicitLiteralNoTargetsSkipMessage(options, result);
22697
+ if (noTargetsMessage !== void 0) {
22440
22698
  return {
22441
22699
  exitCode: LITERAL_EXIT_CODES.OK,
22442
- output: options.quiet ? "" : VALIDATION_PATHS_NO_TARGETS_MESSAGE2,
22700
+ output: options.quiet ? "" : noTargetsMessage,
22443
22701
  durationMs: Date.now() - start
22444
22702
  };
22445
22703
  }
@@ -22454,7 +22712,18 @@ async function literalCommand(options) {
22454
22712
  } else {
22455
22713
  output = formatLiteralCommandOutput(filteredFindings, options);
22456
22714
  }
22457
- return { exitCode, output, durationMs: Date.now() - start };
22715
+ return {
22716
+ exitCode,
22717
+ output,
22718
+ durationMs: Date.now() - start,
22719
+ outputTarget: VALIDATION_OUTPUT_TARGET.STDOUT
22720
+ };
22721
+ }
22722
+ function explicitLiteralNoTargetsSkipMessage(options, result) {
22723
+ if (options.files === void 0 || options.files.length === 0) {
22724
+ return void 0;
22725
+ }
22726
+ return formatValidationScopeNoTargetsSkipMessage(VALIDATION_STAGE_DISPLAY_NAMES.LITERAL, result);
22458
22727
  }
22459
22728
  async function resolveLiteralCommandConfig(options) {
22460
22729
  if (options.config !== void 0) {
@@ -22481,12 +22750,11 @@ function resolveExplicitLiteralTypeScriptScope(options, pathConfig) {
22481
22750
  return void 0;
22482
22751
  }
22483
22752
  return resolveTypeScriptValidationScope({
22484
- projectRoot: options.cwd,
22753
+ productDir: options.cwd,
22485
22754
  scope: options.scope ?? VALIDATION_SCOPES.FULL,
22486
22755
  paths: options.files,
22487
22756
  validationPathFilter: pathConfig,
22488
- markExplicitPathsAsValidationFilter: true,
22489
- bypassExplicitPathValidationFilter: true
22757
+ markExplicitPathsAsValidationFilter: true
22490
22758
  });
22491
22759
  }
22492
22760
  function explicitLiteralPaths(files) {
@@ -22630,8 +22898,8 @@ function formatTypeScriptExitCodeError(code) {
22630
22898
  }
22631
22899
  var TEMPORARY_TSCONFIG_COMPILER_OPTIONS = { noEmit: true };
22632
22900
  var TEMPORARY_TSCONFIG_DIR_PREFIX = "validate-ts-";
22633
- async function createTemporaryTsconfigDir(projectRoot, deps) {
22634
- const parent = join39(projectRoot, ...TEMPORARY_TSCONFIG_PARENT_SEGMENTS);
22901
+ async function createTemporaryTsconfigDir(productDir, deps) {
22902
+ const parent = join39(productDir, ...TEMPORARY_TSCONFIG_PARENT_SEGMENTS);
22635
22903
  deps.mkdirSync(parent, { recursive: true });
22636
22904
  return deps.mkdtemp(join39(parent, TEMPORARY_TSCONFIG_DIR_PREFIX));
22637
22905
  }
@@ -22639,13 +22907,13 @@ function buildTypeScriptArgs(context) {
22639
22907
  const { scope: scope2, configFile } = context;
22640
22908
  return scope2 === VALIDATION_SCOPES.FULL ? ["tsc", "--noEmit"] : ["tsc", "--project", configFile];
22641
22909
  }
22642
- async function createFileSpecificTsconfig(scope2, files, projectRoot, deps = defaultTypeScriptDeps) {
22643
- const tempDir = await createTemporaryTsconfigDir(projectRoot, deps);
22910
+ async function createFileSpecificTsconfig(scope2, files, productDir, deps = defaultTypeScriptDeps) {
22911
+ const tempDir = await createTemporaryTsconfigDir(productDir, deps);
22644
22912
  const configPath = join39(tempDir, "tsconfig.json");
22645
22913
  const baseConfigFile = TSCONFIG_FILES[scope2];
22646
- const absoluteFiles = files.map((file) => isAbsolute12(file) ? file : join39(projectRoot, file));
22914
+ const absoluteFiles = files.map((file) => isAbsolute12(file) ? file : join39(productDir, file));
22647
22915
  const tempConfig = {
22648
- extends: join39(projectRoot, baseConfigFile),
22916
+ extends: join39(productDir, baseConfigFile),
22649
22917
  files: absoluteFiles,
22650
22918
  include: [],
22651
22919
  exclude: [],
@@ -22655,16 +22923,16 @@ async function createFileSpecificTsconfig(scope2, files, projectRoot, deps = def
22655
22923
  const cleanup = createTemporaryTsconfigCleanup(tempDir, deps);
22656
22924
  return { configPath, tempDir, cleanup };
22657
22925
  }
22658
- async function createScopeFilteredTsconfig(scope2, projectRoot, scopeConfig, deps = defaultTypeScriptDeps) {
22659
- const tempDir = await createTemporaryTsconfigDir(projectRoot, deps);
22926
+ async function createScopeFilteredTsconfig(scope2, productDir, scopeConfig, deps = defaultTypeScriptDeps) {
22927
+ const tempDir = await createTemporaryTsconfigDir(productDir, deps);
22660
22928
  const configPath = join39(tempDir, "tsconfig.json");
22661
22929
  const baseConfigFile = TSCONFIG_FILES[scope2];
22662
22930
  const toTemporaryConfigPathPattern = (pattern) => {
22663
- const absolutePattern = isAbsolute12(pattern) ? pattern : join39(projectRoot, pattern);
22931
+ const absolutePattern = isAbsolute12(pattern) ? pattern : join39(productDir, pattern);
22664
22932
  return relative10(tempDir, absolutePattern);
22665
22933
  };
22666
22934
  const tempConfig = {
22667
- extends: join39(projectRoot, baseConfigFile),
22935
+ extends: join39(productDir, baseConfigFile),
22668
22936
  include: scopeConfigToTemporaryIncludes(scopeConfig).map(toTemporaryConfigPathPattern),
22669
22937
  exclude: scopeConfig.excludePatterns.map(toTemporaryConfigPathPattern),
22670
22938
  compilerOptions: TEMPORARY_TSCONFIG_COMPILER_OPTIONS
@@ -22693,19 +22961,20 @@ function createTemporaryTsconfigCleanup(tempDir, deps) {
22693
22961
  };
22694
22962
  }
22695
22963
  async function validateTypeScript(context, options = {}) {
22696
- const { scope: scope2, projectRoot, files, scopeConfig } = context;
22964
+ const { scope: scope2, productDir, files, scopeConfig } = context;
22697
22965
  const {
22698
22966
  runner = defaultTypeScriptProcessRunner,
22699
22967
  deps = defaultTypeScriptDeps,
22968
+ toolPath,
22700
22969
  outputStreams = defaultValidationSubprocessOutputStreams
22701
22970
  } = options;
22702
22971
  const configFile = TSCONFIG_FILES[scope2];
22703
22972
  if (files && files.length > 0) {
22704
- const { configPath, cleanup } = await createFileSpecificTsconfig(scope2, files, projectRoot, deps);
22973
+ const { configPath, cleanup } = await createFileSpecificTsconfig(scope2, files, productDir, deps);
22705
22974
  try {
22706
22975
  return await runTypeScriptInvocation(
22707
- projectRoot,
22708
- resolveProjectTscInvocation(projectRoot, deps, ["--project", configPath]),
22976
+ productDir,
22977
+ resolveTscInvocation(productDir, deps, ["--project", configPath], toolPath),
22709
22978
  runner,
22710
22979
  outputStreams,
22711
22980
  cleanup
@@ -22722,64 +22991,89 @@ async function validateTypeScript(context, options = {}) {
22722
22991
  if (scopeConfig.filePatterns.length === 0 && scopeConfig.directories.length === 0) {
22723
22992
  return { success: true, skipped: true };
22724
22993
  }
22725
- const { configPath, cleanup } = await createScopeFilteredTsconfig(scope2, projectRoot, scopeConfig, deps);
22994
+ const { configPath, cleanup } = await createScopeFilteredTsconfig(scope2, productDir, scopeConfig, deps);
22726
22995
  return runTypeScriptInvocation(
22727
- projectRoot,
22728
- resolveProjectTscInvocation(projectRoot, deps, ["--project", configPath]),
22996
+ productDir,
22997
+ resolveTscInvocation(productDir, deps, ["--project", configPath], toolPath),
22729
22998
  runner,
22730
22999
  outputStreams,
22731
23000
  cleanup
22732
23001
  );
22733
23002
  }
22734
23003
  return runTypeScriptInvocation(
22735
- projectRoot,
22736
- resolveProjectTscInvocation(projectRoot, deps, buildTypeScriptArgs({ scope: scope2, configFile }).slice(1)),
23004
+ productDir,
23005
+ resolveTscInvocation(productDir, deps, buildTypeScriptArgs({ scope: scope2, configFile }).slice(1), toolPath),
22737
23006
  runner,
22738
23007
  outputStreams
22739
23008
  );
22740
23009
  }
22741
- function resolveProjectTscInvocation(projectRoot, deps, tscArgs) {
22742
- const tscBin = join39(projectRoot, "node_modules", ".bin", "tsc");
23010
+ function resolveTscInvocation(productDir, deps, tscArgs, toolPath) {
23011
+ if (toolPath !== void 0) {
23012
+ return { tool: toolPath, args: tscArgs };
23013
+ }
23014
+ const tscBin = join39(productDir, "node_modules", ".bin", "tsc");
22743
23015
  const tool = deps.existsSync(tscBin) ? tscBin : "npx";
22744
23016
  return {
22745
23017
  tool,
22746
23018
  args: tool === "npx" ? ["tsc", ...tscArgs] : tscArgs
22747
23019
  };
22748
23020
  }
22749
- function runTypeScriptInvocation(projectRoot, invocation, runner, outputStreams, cleanup = () => {
23021
+ function runTypeScriptInvocation(productDir, invocation, runner, outputStreams, cleanup = () => {
22750
23022
  }) {
22751
23023
  return new Promise((resolve17) => {
22752
23024
  const tscProcess = spawnManagedSubprocess(runner, invocation.tool, invocation.args, {
22753
- cwd: projectRoot
23025
+ cwd: productDir
22754
23026
  });
23027
+ const chunks = [];
23028
+ const capture = (chunk) => {
23029
+ chunks.push(typeof chunk === "string" ? chunk : Buffer.from(chunk).toString());
23030
+ };
23031
+ tscProcess.stdout?.on(VALIDATION_SUBPROCESS_EVENTS.DATA, capture);
23032
+ tscProcess.stderr?.on(VALIDATION_SUBPROCESS_EVENTS.DATA, capture);
22755
23033
  forwardValidationSubprocessOutput(tscProcess, outputStreams);
22756
23034
  tscProcess.on(VALIDATION_SUBPROCESS_EVENTS.CLOSE, (code) => {
22757
23035
  cleanup();
23036
+ const output = chunks.join("");
22758
23037
  if (code === 0) {
22759
- resolve17({ success: true, skipped: false });
23038
+ resolve17({ success: true, skipped: false, output });
22760
23039
  } else {
22761
- resolve17({ success: false, error: formatTypeScriptExitCodeError(code) });
23040
+ resolve17({ success: false, output, error: formatTypeScriptExitCodeError(code) });
22762
23041
  }
22763
23042
  });
22764
23043
  tscProcess.on(VALIDATION_SUBPROCESS_EVENTS.ERROR, (error) => {
22765
23044
  cleanup();
22766
- resolve17({ success: false, error: error.message });
23045
+ resolve17({ success: false, output: chunks.join(""), error: error.message });
22767
23046
  });
22768
23047
  });
22769
23048
  }
22770
23049
 
22771
23050
  // src/commands/validation/typescript.ts
23051
+ var defaultTypeScriptCommandDeps = {
23052
+ detectTypeScript,
23053
+ discoverTool,
23054
+ validateTypeScript
23055
+ };
22772
23056
  var TYPESCRIPT_VALIDATION_MESSAGES = {
22773
23057
  ABSENT: formatTypeScriptAbsentSkipMessage(VALIDATION_STAGE_DISPLAY_NAMES.TYPESCRIPT),
22774
- CONFIG_ERROR: `${VALIDATION_STAGE_DISPLAY_NAMES.TYPESCRIPT}: \u2717 config error`,
23058
+ CONFIG_ERROR: formatValidationConfigProblemMessage(
23059
+ VALIDATION_STAGE_DISPLAY_NAMES.TYPESCRIPT,
23060
+ "configuration error"
23061
+ ),
22775
23062
  NO_VALIDATION_PATH_TARGETS: formatValidationPathsNoTargetsSkipMessage(VALIDATION_STAGE_DISPLAY_NAMES.TYPESCRIPT),
23063
+ NO_EXPLICIT_PATH_TARGETS: formatExplicitPathsNoTargetsSkipMessage(VALIDATION_STAGE_DISPLAY_NAMES.TYPESCRIPT),
22776
23064
  SUCCESS: VALIDATION_COMMAND_OUTPUT.TYPESCRIPT_SUCCESS,
22777
23065
  TOOL_LABEL: VALIDATION_STAGE_DISPLAY_NAMES.TYPESCRIPT
22778
23066
  };
22779
- async function typescriptCommand(options) {
22780
- const { cwd, scope: scope2 = "full", files, outputStreams, quiet } = options;
23067
+ var TYPESCRIPT_TOOL_DISCOVERY = {
23068
+ TOOL: "typescript",
23069
+ EXECUTABLE_NAME: "tsc",
23070
+ BUNDLED_EXECUTABLE: "typescript/bin/tsc",
23071
+ PRODUCT_EXECUTABLE_SEGMENTS: ["node_modules", ".bin", "tsc"]
23072
+ };
23073
+ async function typescriptCommand(options, deps = defaultTypeScriptCommandDeps) {
23074
+ const { cwd, scope: scope2 = "full", files, json, outputStreams, quiet, streamedPipelineOutput } = options;
22781
23075
  const startTime = Date.now();
22782
- const tsDetection = detectTypeScript(cwd);
23076
+ const tsDetection = deps.detectTypeScript(cwd);
22783
23077
  if (!tsDetection.present) {
22784
23078
  return {
22785
23079
  exitCode: 0,
@@ -22797,53 +23091,69 @@ async function typescriptCommand(options) {
22797
23091
  }
22798
23092
  const validationConfig = loaded.value[validationConfigDescriptor.section];
22799
23093
  const scopeConfig = resolveTypeScriptValidationScope({
22800
- projectRoot: cwd,
23094
+ productDir: cwd,
22801
23095
  scope: scope2,
22802
23096
  paths: files,
22803
23097
  validationPathFilter: validationPathFilterForTool(
22804
23098
  validationConfig.paths,
22805
23099
  VALIDATION_PATH_TOOL_SUBSECTIONS.TYPESCRIPT
22806
23100
  ),
22807
- markExplicitPathsAsValidationFilter: true,
22808
- bypassExplicitPathValidationFilter: true
23101
+ markExplicitPathsAsValidationFilter: true
22809
23102
  });
22810
- if (scopeConfig.filteredByValidationPathNoMatches) {
23103
+ const noTargetsMessage = formatValidationScopeNoTargetsSkipMessage(
23104
+ VALIDATION_STAGE_DISPLAY_NAMES.TYPESCRIPT,
23105
+ scopeConfig
23106
+ );
23107
+ if (noTargetsMessage !== void 0) {
22811
23108
  return {
22812
23109
  exitCode: 0,
22813
- output: quiet ? "" : TYPESCRIPT_VALIDATION_MESSAGES.NO_VALIDATION_PATH_TARGETS,
23110
+ output: quiet ? "" : noTargetsMessage,
22814
23111
  durationMs: Date.now() - startTime
22815
23112
  };
22816
23113
  }
22817
- const toolResult = await discoverTool("typescript", { projectRoot: cwd });
23114
+ const toolResult = await deps.discoverTool(TYPESCRIPT_TOOL_DISCOVERY.TOOL, {
23115
+ productDir: cwd,
23116
+ executableName: TYPESCRIPT_TOOL_DISCOVERY.EXECUTABLE_NAME,
23117
+ bundledExecutable: TYPESCRIPT_TOOL_DISCOVERY.BUNDLED_EXECUTABLE,
23118
+ priority: TOOL_DISCOVERY_PRIORITY.PRODUCT_FIRST
23119
+ });
22818
23120
  if (!toolResult.found) {
22819
23121
  const skipMessage = formatSkipMessage(VALIDATION_STAGE_DISPLAY_NAMES.TYPESCRIPT, toolResult);
22820
23122
  return { exitCode: 0, output: skipMessage, durationMs: Date.now() - startTime };
22821
23123
  }
22822
- const result = await validateTypeScript({
23124
+ const result = await deps.validateTypeScript({
22823
23125
  scope: scope2,
22824
- projectRoot: cwd,
23126
+ productDir: cwd,
22825
23127
  scopeConfig
22826
23128
  }, {
22827
- outputStreams
23129
+ toolPath: toolResult.location.path,
23130
+ outputStreams: outputStreams ?? discardValidationSubprocessOutputStreams
22828
23131
  });
22829
23132
  const durationMs = Date.now() - startTime;
22830
- return formatTypeScriptResult(result, quiet, durationMs);
23133
+ return formatTypeScriptResult(result, quiet, durationMs, json, streamedPipelineOutput);
22831
23134
  }
22832
- function formatTypeScriptResult(result, quiet, durationMs) {
23135
+ function formatTypeScriptResult(result, quiet, durationMs, json, streamedPipelineOutput) {
22833
23136
  if (result.skipped) {
22834
23137
  const output2 = quiet ? "" : TYPESCRIPT_VALIDATION_MESSAGES.NO_VALIDATION_PATH_TARGETS;
22835
23138
  return { exitCode: 0, output: output2, durationMs };
22836
23139
  }
22837
23140
  if (result.success) {
22838
- const output2 = quiet ? "" : TYPESCRIPT_VALIDATION_MESSAGES.SUCCESS;
22839
- return { exitCode: 0, output: output2, durationMs };
23141
+ const output2 = quiet ? "" : [TYPESCRIPT_VALIDATION_MESSAGES.SUCCESS, result.output].filter((line) => line !== void 0 && line.length > 0).join("\n");
23142
+ const terminalOutput2 = streamedValidationTerminalOutput(result.output, json, streamedPipelineOutput);
23143
+ return { exitCode: 0, output: output2, terminalOutput: terminalOutput2, durationMs };
22840
23144
  }
22841
- const output = result.error ?? VALIDATION_COMMAND_OUTPUT.TYPESCRIPT_FAILURE;
22842
- return { exitCode: 1, output, durationMs };
23145
+ const output = [result.output, result.error ?? VALIDATION_COMMAND_OUTPUT.TYPESCRIPT_FAILURE].filter((line) => line !== void 0 && line.length > 0).join("\n");
23146
+ const terminalOutput = streamedValidationTerminalOutput(result.output, json, streamedPipelineOutput);
23147
+ return { exitCode: 1, output, terminalOutput, durationMs };
22843
23148
  }
22844
23149
 
22845
23150
  // src/validation/languages/typescript.ts
22846
23151
  var TYPESCRIPT_LANGUAGE_NAME = "typescript";
23152
+ var SKIP_CIRCULAR_REASON = "skip-circular";
23153
+ var SKIP_KNIP_REASON = "skip-knip";
23154
+ var SKIP_LINT_REASON = "skip-lint";
23155
+ var SKIP_TYPESCRIPT_REASON = "skip-typescript";
23156
+ var SKIP_LITERAL_REASON = "skip-literal";
22847
23157
  var TYPESCRIPT_VALIDATION_CONCERN = {
22848
23158
  LINT: "lint",
22849
23159
  TYPE_CHECK: "type-check",
@@ -22852,14 +23162,60 @@ var TYPESCRIPT_VALIDATION_CONCERN = {
22852
23162
  LITERAL_REUSE: "literal-reuse",
22853
23163
  UNUSED_CODE: "unused-code"
22854
23164
  };
23165
+ var TYPESCRIPT_VALIDATION_STAGE_BY_CONCERN = {
23166
+ [TYPESCRIPT_VALIDATION_CONCERN.LINT]: VALIDATION_STAGE_DISPLAY_NAMES.ESLINT,
23167
+ [TYPESCRIPT_VALIDATION_CONCERN.TYPE_CHECK]: VALIDATION_STAGE_DISPLAY_NAMES.TYPESCRIPT,
23168
+ [TYPESCRIPT_VALIDATION_CONCERN.AST_ENFORCEMENT]: VALIDATION_STAGE_DISPLAY_NAMES.ESLINT,
23169
+ [TYPESCRIPT_VALIDATION_CONCERN.CIRCULAR_DEPS]: VALIDATION_STAGE_DISPLAY_NAMES.CIRCULAR,
23170
+ [TYPESCRIPT_VALIDATION_CONCERN.LITERAL_REUSE]: VALIDATION_STAGE_DISPLAY_NAMES.LITERAL,
23171
+ [TYPESCRIPT_VALIDATION_CONCERN.UNUSED_CODE]: VALIDATION_STAGE_DISPLAY_NAMES.KNIP
23172
+ };
23173
+ var TYPESCRIPT_VALIDATION_STAGE_PARTICIPATION = {
23174
+ [VALIDATION_STAGE_DISPLAY_NAMES.CIRCULAR]: {
23175
+ default: VALIDATION_STAGE_PARTICIPATION.RUN,
23176
+ skipReason: SKIP_CIRCULAR_REASON,
23177
+ override: {
23178
+ flag: "--skip-circular",
23179
+ description: "Skip circular dependency detection for this validation all run"
23180
+ }
23181
+ },
23182
+ [VALIDATION_STAGE_DISPLAY_NAMES.KNIP]: {
23183
+ default: VALIDATION_STAGE_PARTICIPATION.RUN,
23184
+ skipReason: SKIP_KNIP_REASON,
23185
+ override: {
23186
+ flag: "--skip-knip",
23187
+ description: "Skip unused-code detection for this validation all run"
23188
+ }
23189
+ },
23190
+ [VALIDATION_STAGE_DISPLAY_NAMES.ESLINT]: {
23191
+ default: VALIDATION_STAGE_PARTICIPATION.RUN,
23192
+ skipReason: SKIP_LINT_REASON,
23193
+ override: {
23194
+ flag: "--skip-lint",
23195
+ description: "Skip ESLint validation for this validation all run"
23196
+ }
23197
+ },
23198
+ [VALIDATION_STAGE_DISPLAY_NAMES.TYPESCRIPT]: {
23199
+ default: VALIDATION_STAGE_PARTICIPATION.RUN,
23200
+ skipReason: SKIP_TYPESCRIPT_REASON,
23201
+ override: {
23202
+ flag: "--skip-typescript",
23203
+ description: "Skip TypeScript validation for this validation all run"
23204
+ }
23205
+ },
23206
+ [VALIDATION_STAGE_DISPLAY_NAMES.LITERAL]: {
23207
+ default: VALIDATION_STAGE_PARTICIPATION.RUN,
23208
+ skipReason: SKIP_LITERAL_REASON,
23209
+ override: {
23210
+ flag: "--skip-literal",
23211
+ description: "Skip literal reuse detection for this validation all run"
23212
+ }
23213
+ }
23214
+ };
22855
23215
  var defaultKnipStageDeps = {
22856
23216
  knipCommand
22857
23217
  };
22858
23218
  async function runCircularStage(context) {
22859
- if (context.skipCircular) {
22860
- const skipOutput = context.json ? CIRCULAR_SKIP_JSON_OUTPUT : CIRCULAR_SKIP_OUTPUT;
22861
- return { exitCode: 0, output: context.quiet ? "" : skipOutput };
22862
- }
22863
23219
  return circularCommand({
22864
23220
  cwd: context.cwd,
22865
23221
  scope: context.scope,
@@ -22869,10 +23225,6 @@ async function runCircularStage(context) {
22869
23225
  });
22870
23226
  }
22871
23227
  async function runLiteralStage(context) {
22872
- if (context.skipLiteral) {
22873
- const skipOutput = context.json ? LITERAL_SKIP_JSON_OUTPUT : LITERAL_SKIP_OUTPUT;
22874
- return { exitCode: 0, output: context.quiet ? "" : skipOutput };
22875
- }
22876
23228
  return literalCommand({
22877
23229
  cwd: context.cwd,
22878
23230
  scope: context.scope,
@@ -22887,27 +23239,32 @@ async function runKnipStage(context, deps = defaultKnipStageDeps) {
22887
23239
  scope: context.scope,
22888
23240
  files: context.files,
22889
23241
  quiet: context.quiet,
22890
- json: context.json
23242
+ json: context.json,
23243
+ streamedPipelineOutput: true,
23244
+ outputStreams: context.outputStreams
22891
23245
  });
22892
23246
  }
22893
23247
  var typescriptValidationLanguage = {
22894
23248
  name: TYPESCRIPT_LANGUAGE_NAME,
22895
23249
  concerns: Object.values(TYPESCRIPT_VALIDATION_CONCERN),
23250
+ stageByConcern: TYPESCRIPT_VALIDATION_STAGE_BY_CONCERN,
22896
23251
  stages: [
22897
23252
  {
22898
23253
  name: VALIDATION_STAGE_DISPLAY_NAMES.CIRCULAR,
22899
23254
  failsPipeline: true,
23255
+ participation: TYPESCRIPT_VALIDATION_STAGE_PARTICIPATION[VALIDATION_STAGE_DISPLAY_NAMES.CIRCULAR],
22900
23256
  run: runCircularStage
22901
23257
  },
22902
23258
  {
22903
23259
  name: VALIDATION_STAGE_DISPLAY_NAMES.KNIP,
22904
- // Knip is informational: unused-code findings never fail the pipeline.
22905
- failsPipeline: false,
23260
+ failsPipeline: true,
23261
+ participation: TYPESCRIPT_VALIDATION_STAGE_PARTICIPATION[VALIDATION_STAGE_DISPLAY_NAMES.KNIP],
22906
23262
  run: runKnipStage
22907
23263
  },
22908
23264
  {
22909
23265
  name: VALIDATION_STAGE_DISPLAY_NAMES.ESLINT,
22910
23266
  failsPipeline: true,
23267
+ participation: TYPESCRIPT_VALIDATION_STAGE_PARTICIPATION[VALIDATION_STAGE_DISPLAY_NAMES.ESLINT],
22911
23268
  run: (context) => lintCommand({
22912
23269
  cwd: context.cwd,
22913
23270
  scope: context.scope,
@@ -22915,32 +23272,46 @@ var typescriptValidationLanguage = {
22915
23272
  fix: context.fix,
22916
23273
  quiet: context.quiet,
22917
23274
  json: context.json,
23275
+ streamedPipelineOutput: true,
22918
23276
  outputStreams: context.outputStreams
22919
23277
  })
22920
23278
  },
22921
23279
  {
22922
23280
  name: VALIDATION_STAGE_DISPLAY_NAMES.TYPESCRIPT,
22923
23281
  failsPipeline: true,
23282
+ participation: TYPESCRIPT_VALIDATION_STAGE_PARTICIPATION[VALIDATION_STAGE_DISPLAY_NAMES.TYPESCRIPT],
22924
23283
  run: (context) => typescriptCommand({
22925
23284
  cwd: context.cwd,
22926
23285
  scope: context.scope,
22927
23286
  files: context.files,
22928
23287
  quiet: context.quiet,
22929
23288
  json: context.json,
23289
+ streamedPipelineOutput: true,
22930
23290
  outputStreams: context.outputStreams
22931
23291
  })
22932
23292
  },
22933
23293
  {
22934
23294
  name: VALIDATION_STAGE_DISPLAY_NAMES.LITERAL,
22935
23295
  failsPipeline: true,
23296
+ participation: TYPESCRIPT_VALIDATION_STAGE_PARTICIPATION[VALIDATION_STAGE_DISPLAY_NAMES.LITERAL],
22936
23297
  run: runLiteralStage
22937
23298
  }
22938
23299
  ]
22939
23300
  };
22940
23301
 
22941
23302
  // src/validation/registry.ts
23303
+ var VALIDATION_STAGE_PARTICIPATION_POLICIES = {
23304
+ ...TYPESCRIPT_VALIDATION_STAGE_PARTICIPATION,
23305
+ ...MARKDOWN_VALIDATION_STAGE_PARTICIPATION,
23306
+ ...FORMATTING_VALIDATION_STAGE_PARTICIPATION
23307
+ };
23308
+ var VALIDATION_REGISTRY_LANGUAGES = [
23309
+ typescriptValidationLanguage,
23310
+ markdownValidationLanguage,
23311
+ formattingValidationLanguage
23312
+ ];
22942
23313
  var validationRegistry = {
22943
- languages: [typescriptValidationLanguage, markdownValidationLanguage, formattingValidationLanguage]
23314
+ languages: VALIDATION_REGISTRY_LANGUAGES
22944
23315
  };
22945
23316
  function composeValidationPipelineStages(languages) {
22946
23317
  return languages.flatMap((language) => language.stages);
@@ -22974,10 +23345,17 @@ function formatSummary(options) {
22974
23345
  }
22975
23346
 
22976
23347
  // src/commands/validation/all.ts
22977
- function formatStepWithTiming(stepNumber, totalSteps, result, quiet) {
22978
- if (quiet || !result.output) return "";
23348
+ function formatStepWithTiming(stepNumber, totalSteps, stageName, result, quiet) {
23349
+ const output = result.terminalOutput ?? result.output;
23350
+ if (quiet) return "";
23351
+ if (result.terminalOutput === VALIDATION_STREAMED_TERMINAL_OUTPUT) {
23352
+ const verdict = result.exitCode === 0 ? VALIDATION_SYMBOLS.SUCCESS : VALIDATION_SYMBOLS.FAILURE;
23353
+ const timing2 = result.durationMs === void 0 ? "" : ` (${formatDuration(result.durationMs)})`;
23354
+ return `[${stepNumber}/${totalSteps}] ${stageName}: ${verdict} ${VALIDATION_STREAMED_STAGE_RESULT}${timing2}`;
23355
+ }
23356
+ if (!output) return "";
22979
23357
  const timing = result.durationMs === void 0 ? "" : ` (${formatDuration(result.durationMs)})`;
22980
- return `[${stepNumber}/${totalSteps}] ${result.output}${timing}`;
23358
+ return `[${stepNumber}/${totalSteps}] ${output}${timing}`;
22981
23359
  }
22982
23360
  function parseStageOutput(output) {
22983
23361
  if (output.length === 0) return null;
@@ -23011,12 +23389,39 @@ function recordStageResult(options) {
23011
23389
  });
23012
23390
  return;
23013
23391
  }
23014
- const stepOutput = formatStepWithTiming(stepNumber, totalSteps, result, quiet);
23392
+ const stepOutput = formatStepWithTiming(stepNumber, totalSteps, stage.name, result, quiet);
23015
23393
  if (stepOutput) {
23016
23394
  outputs.push(stepOutput);
23017
23395
  writeOutput5?.(stepOutput);
23018
23396
  }
23019
23397
  }
23398
+ function resolveStageParticipation(stage, participationOverrides) {
23399
+ const override = stage.participation.override;
23400
+ if (participationOverrides.has(override.flag)) {
23401
+ const participation = stage.participation.default === VALIDATION_STAGE_PARTICIPATION.RUN ? VALIDATION_STAGE_PARTICIPATION.SKIP : VALIDATION_STAGE_PARTICIPATION.RUN;
23402
+ return {
23403
+ participation,
23404
+ reason: participation === VALIDATION_STAGE_PARTICIPATION.SKIP ? stage.participation.skipReason : void 0,
23405
+ flag: override.flag
23406
+ };
23407
+ }
23408
+ return {
23409
+ participation: stage.participation.default,
23410
+ reason: stage.participation.default === VALIDATION_STAGE_PARTICIPATION.SKIP ? stage.participation.skipReason : void 0
23411
+ };
23412
+ }
23413
+ function skippedStageResult(stage, participation, json, durationMs) {
23414
+ const reason = participation.reason;
23415
+ if (reason === void 0) {
23416
+ throw new Error(`validation stage ${stage.name} skipped without a configured reason`);
23417
+ }
23418
+ return {
23419
+ exitCode: 0,
23420
+ output: json ? formatValidationStageSkipJsonOutput(reason, durationMs) : formatValidationStageSkipOutput(stage.name, participation.flag ?? reason),
23421
+ structuredOutput: json,
23422
+ durationMs
23423
+ };
23424
+ }
23020
23425
  function createCapturedSubprocessOutput() {
23021
23426
  const stdout = [];
23022
23427
  const stderr = [];
@@ -23037,14 +23442,72 @@ function createCaptureStream(chunks) {
23037
23442
  }
23038
23443
  };
23039
23444
  }
23445
+ function resolveFullPipelineStages(validationStages, dependencyStages) {
23446
+ return validationStages ?? dependencyStages ?? validationPipelineStages;
23447
+ }
23448
+ async function executeValidationStages(options) {
23449
+ let hasFailure = false;
23450
+ for (const [index, stage] of options.stages.entries()) {
23451
+ const stepNumber = index + 1;
23452
+ const execution = await executeValidationStage(stage, options);
23453
+ recordStageResult({
23454
+ json: options.json,
23455
+ stepNumber,
23456
+ totalSteps: options.stages.length,
23457
+ stage,
23458
+ result: execution.result,
23459
+ quiet: options.quiet,
23460
+ outputs: options.outputs,
23461
+ jsonSteps: options.jsonSteps,
23462
+ subprocessOutput: execution.subprocessOutput,
23463
+ writeOutput: options.writeOutput
23464
+ });
23465
+ notifyStageCompletion(stage, execution.result, stepNumber, options);
23466
+ hasFailure ||= stage.failsPipeline && execution.result.exitCode !== 0;
23467
+ }
23468
+ return hasFailure;
23469
+ }
23470
+ async function executeValidationStage(stage, options) {
23471
+ const subprocessOutput = options.json ? createCapturedSubprocessOutput() : void 0;
23472
+ const stageStartTime = options.now();
23473
+ const participation = resolveStageParticipation(stage, options.participationOverrides);
23474
+ const stageResult = participation.participation === VALIDATION_STAGE_PARTICIPATION.RUN ? await stage.run({
23475
+ ...options.context,
23476
+ outputStreams: subprocessOutput?.streams ?? options.outputStreams
23477
+ }) : skippedStageResult(stage, participation, options.json, options.now() - stageStartTime);
23478
+ const result = stageResult.durationMs === void 0 ? { ...stageResult, durationMs: options.now() - stageStartTime } : stageResult;
23479
+ return { result, subprocessOutput };
23480
+ }
23481
+ function notifyStageCompletion(stage, result, stepNumber, options) {
23482
+ if (options.json || options.onStageComplete === void 0) return;
23483
+ const output = formatStepWithTiming(stepNumber, options.stages.length, stage.name, result, options.quiet);
23484
+ if (output.length === 0) return;
23485
+ options.onStageComplete({
23486
+ stepNumber,
23487
+ totalSteps: options.stages.length,
23488
+ stageName: stage.name,
23489
+ result,
23490
+ output
23491
+ });
23492
+ }
23040
23493
  async function allCommand(options, deps = {}) {
23041
- const { cwd, scope: scope2, files, fix, quiet = false, json, skipCircular = false, skipLiteral = false } = options;
23042
- const stages = deps.stages ?? validationPipelineStages;
23494
+ const {
23495
+ cwd,
23496
+ scope: scope2,
23497
+ files,
23498
+ fix,
23499
+ quiet = false,
23500
+ json,
23501
+ validationStages,
23502
+ participationOverrides = [],
23503
+ onStageComplete,
23504
+ outputStreams
23505
+ } = options;
23506
+ const stages = resolveFullPipelineStages(validationStages, deps.stages);
23043
23507
  const now = deps.now ?? Date.now;
23044
23508
  const startTime = now();
23045
23509
  const outputs = [];
23046
23510
  const jsonSteps = [];
23047
- let hasFailure = false;
23048
23511
  const context = {
23049
23512
  cwd,
23050
23513
  scope: scope2,
@@ -23052,28 +23515,21 @@ async function allCommand(options, deps = {}) {
23052
23515
  fix,
23053
23516
  quiet: json === true ? false : quiet,
23054
23517
  json,
23055
- skipCircular,
23056
- skipLiteral
23518
+ outputStreams
23057
23519
  };
23058
- let stepNumber = 0;
23059
- for (const stage of stages) {
23060
- stepNumber += 1;
23061
- const subprocessOutput = json === true ? createCapturedSubprocessOutput() : void 0;
23062
- const result = await stage.run({ ...context, outputStreams: subprocessOutput?.streams });
23063
- recordStageResult({
23064
- json: json === true,
23065
- stepNumber,
23066
- totalSteps: stages.length,
23067
- stage,
23068
- result,
23069
- quiet,
23070
- outputs,
23071
- jsonSteps,
23072
- subprocessOutput,
23073
- writeOutput: deps.writeOutput
23074
- });
23075
- if (stage.failsPipeline && result.exitCode !== 0) hasFailure = true;
23076
- }
23520
+ const hasFailure = await executeValidationStages({
23521
+ stages,
23522
+ context,
23523
+ participationOverrides: new Set(participationOverrides),
23524
+ json: json === true,
23525
+ quiet,
23526
+ outputs,
23527
+ jsonSteps,
23528
+ onStageComplete,
23529
+ outputStreams,
23530
+ writeOutput: deps.writeOutput,
23531
+ now
23532
+ });
23077
23533
  const totalDurationMs = now() - startTime;
23078
23534
  if (json === true) {
23079
23535
  const jsonOutput = {
@@ -23086,18 +23542,26 @@ async function allCommand(options, deps = {}) {
23086
23542
  return {
23087
23543
  exitCode: hasFailure ? 1 : 0,
23088
23544
  output,
23545
+ outputTarget: VALIDATION_OUTPUT_TARGET.STDOUT,
23089
23546
  durationMs: totalDurationMs
23090
23547
  };
23091
23548
  }
23549
+ let terminalOutput;
23092
23550
  if (!quiet) {
23093
23551
  const summary = formatSummary({ success: !hasFailure, totalDurationMs });
23094
- outputs.push("", summary);
23095
23552
  deps.writeOutput?.(`
23096
23553
  ${summary}`);
23554
+ if (onStageComplete !== void 0) {
23555
+ terminalOutput = `
23556
+ ${summary}`;
23557
+ } else {
23558
+ outputs.push("", summary);
23559
+ }
23097
23560
  }
23098
23561
  return {
23099
23562
  exitCode: hasFailure ? 1 : 0,
23100
23563
  output: outputs.join("\n"),
23564
+ terminalOutput,
23101
23565
  durationMs: totalDurationMs
23102
23566
  };
23103
23567
  }
@@ -23117,36 +23581,44 @@ var validationCliDefinition = {
23117
23581
  typescript: {
23118
23582
  commandName: "typescript",
23119
23583
  alias: "ts",
23120
- description: "Run TypeScript type checking"
23584
+ description: "Run TypeScript type checking",
23585
+ options: { scope: true, json: false }
23121
23586
  },
23122
23587
  lint: {
23123
23588
  commandName: "lint",
23124
- description: "Run ESLint"
23589
+ description: "Run ESLint",
23590
+ options: { scope: true, json: false }
23125
23591
  },
23126
23592
  circular: {
23127
23593
  commandName: "circular",
23128
- description: "Check for circular dependencies"
23594
+ description: "Check for circular dependencies",
23595
+ options: { scope: true, json: false }
23129
23596
  },
23130
23597
  knip: {
23131
23598
  commandName: "knip",
23132
- description: "Detect unused code"
23599
+ description: "Detect unused code",
23600
+ options: { scope: true, json: false }
23133
23601
  },
23134
23602
  literal: {
23135
23603
  commandName: "literal",
23136
- description: "Detect cross-file literal reuse between source and tests"
23604
+ description: "Detect cross-file literal reuse between source and tests",
23605
+ options: { scope: true, json: true }
23137
23606
  },
23138
23607
  markdown: {
23139
23608
  commandName: "markdown",
23140
23609
  alias: "md",
23141
- description: "Validate markdown link integrity and structure"
23610
+ description: "Validate markdown link integrity and structure",
23611
+ options: { scope: false, json: false }
23142
23612
  },
23143
23613
  format: {
23144
23614
  commandName: "format",
23145
- description: "Check code formatting with dprint"
23615
+ description: "Check code formatting with dprint",
23616
+ options: { scope: false, json: false }
23146
23617
  },
23147
23618
  all: {
23148
23619
  commandName: "all",
23149
- description: "Run all validations"
23620
+ description: "Run all validations",
23621
+ options: { scope: true, json: true }
23150
23622
  }
23151
23623
  },
23152
23624
  commanderHelpOperands: {
@@ -23196,16 +23668,25 @@ var literalValidationCliOptions = {
23196
23668
  description: "Print grouped problem details"
23197
23669
  }
23198
23670
  };
23199
- var allValidationCliOptions = {
23200
- skipCircular: {
23201
- flag: "--skip-circular",
23202
- description: "Skip circular dependency detection for this validation all run"
23671
+ var validationCommonCliOptions = {
23672
+ scope: {
23673
+ flag: "--scope"
23203
23674
  },
23204
- skipLiteral: {
23205
- flag: "--skip-literal",
23206
- description: "Skip literal reuse detection for this validation all run"
23675
+ quiet: {
23676
+ flag: "--quiet"
23677
+ },
23678
+ json: {
23679
+ flag: "--json"
23680
+ }
23681
+ };
23682
+ var validationAllBuiltInCliOptions = {
23683
+ fix: {
23684
+ flag: "--fix"
23207
23685
  }
23208
23686
  };
23687
+ function isValidationLiteralProblemKind(value) {
23688
+ return validationLiteralProblemKinds.some((kind) => kind === value);
23689
+ }
23209
23690
  var validationSubcommandOperands = Object.values(validationCliDefinition.subcommands).flatMap(
23210
23691
  (subcommand) => {
23211
23692
  const operands = [subcommand.commandName];
@@ -23218,6 +23699,7 @@ var validationKnownOperands = /* @__PURE__ */ new Set([
23218
23699
  ...Object.values(validationCliDefinition.commanderHelpOperands)
23219
23700
  ]);
23220
23701
  var validationOptionPrefix = validationCliDefinition.commanderHelpOperands.longFlag.slice(0, 2);
23702
+ var validationShortOptionPrefix = validationCliDefinition.commanderHelpOperands.shortFlag.slice(0, 1);
23221
23703
 
23222
23704
  // src/validation/literal/allowlist-existing.ts
23223
23705
  import { randomBytes as randomBytes3 } from "crypto";
@@ -23304,27 +23786,86 @@ function serializeWithUpdatedInclude(target, include) {
23304
23786
  }
23305
23787
 
23306
23788
  // src/interfaces/cli/validation.ts
23307
- var defaultValidationCliDependencies = {
23308
- allCommand,
23309
- allowlistExisting,
23310
- circularCommand,
23311
- formattingCommand,
23312
- knipCommand,
23313
- lintCommand,
23314
- literalCommand,
23315
- markdownCommand,
23316
- typescriptCommand
23789
+ var LONG_OPTION_PREFIX = "--";
23790
+ var OPTION_PROPERTY_WORD_SEPARATOR_PATTERN = /-([a-z0-9])/g;
23791
+ var VALIDATION_ALL_OVERRIDE_FLAG_PATTERN = /^--(?!no-)[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/u;
23792
+ var validationAllReservedOverrideFlags = /* @__PURE__ */ new Set([
23793
+ ...Object.values(validationCommonCliOptions).map((option) => option.flag),
23794
+ ...Object.values(validationAllBuiltInCliOptions).map((option) => option.flag),
23795
+ validationCliDefinition.commanderHelpOperands.longFlag
23796
+ ]);
23797
+ function validationOptionPropertyName(flag) {
23798
+ return flag.slice(LONG_OPTION_PREFIX.length).replace(OPTION_PROPERTY_WORD_SEPARATOR_PATTERN, (_match, character) => character.toUpperCase());
23799
+ }
23800
+ function deriveValidationAllOverrideCliOptions(stages) {
23801
+ const optionPropertyNames = /* @__PURE__ */ new Set();
23802
+ return stages.flatMap((stage) => {
23803
+ validateStageParticipationMetadata(stage);
23804
+ const override = stage.participation.override;
23805
+ const optionPropertyName = validationOptionPropertyName(override.flag);
23806
+ if (optionPropertyNames.has(optionPropertyName)) {
23807
+ throw new Error(`duplicate validation all override option property: ${optionPropertyName}`);
23808
+ }
23809
+ optionPropertyNames.add(optionPropertyName);
23810
+ return [{
23811
+ stageName: stage.name,
23812
+ flag: override.flag,
23813
+ description: override.description,
23814
+ reason: stage.participation.skipReason,
23815
+ optionPropertyName
23816
+ }];
23817
+ });
23818
+ }
23819
+ function validateStageParticipationMetadata(stage) {
23820
+ if (stage.participation.skipReason.length === 0) {
23821
+ throw new Error(`validation stage ${stage.name} skip participation requires a reason`);
23822
+ }
23823
+ const override = stage.participation.override;
23824
+ if (!VALIDATION_ALL_OVERRIDE_FLAG_PATTERN.test(override.flag)) {
23825
+ throw new Error(`validation stage ${stage.name} override flag must be a bare long kebab-case boolean flag`);
23826
+ }
23827
+ if (validationAllReservedOverrideFlags.has(override.flag)) {
23828
+ throw new Error(`validation stage ${stage.name} override flag collides with a validation all built-in option`);
23829
+ }
23830
+ if (override.description.length === 0) {
23831
+ throw new Error(`validation stage ${stage.name} override flag requires a description`);
23832
+ }
23833
+ }
23834
+ var validationAllOverrideCliOptions = deriveValidationAllOverrideCliOptions(validationPipelineStages);
23835
+ var defaultValidationCommandHandlers = {
23836
+ typescript: typescriptCommand,
23837
+ lint: lintCommand,
23838
+ circular: circularCommand,
23839
+ knip: knipCommand,
23840
+ literal: literalCommand,
23841
+ markdown: markdownCommand,
23842
+ format: formattingCommand,
23843
+ all: allCommand
23317
23844
  };
23318
23845
  function emitValidationResult(result, io) {
23319
- if (result.output.length > 0) {
23320
- io.writeStdout(`${result.output}
23846
+ const output = result.terminalOutput ?? result.output;
23847
+ if (output.length > 0) {
23848
+ const outputTarget = result.outputTarget ?? (result.exitCode === 0 ? VALIDATION_OUTPUT_TARGET.STDOUT : VALIDATION_OUTPUT_TARGET.STDERR);
23849
+ const writeOutput5 = outputTarget === VALIDATION_OUTPUT_TARGET.STDOUT ? io.writeStdout : io.writeStderr;
23850
+ writeOutput5(`${output}
23321
23851
  `);
23322
23852
  }
23323
23853
  return io.exit(result.exitCode);
23324
23854
  }
23325
- function addCommonOptions(cmd) {
23855
+ function addCommonOptions(cmd, definition) {
23326
23856
  const { pathOperands } = validationCliDefinition;
23327
- return cmd.argument(pathOperands.optionalVariadic, pathOperands.description).option("--scope <scope>", "Validation scope (full|production)", "full").option("--quiet", "Suppress progress output").option("--json", "Output results as JSON");
23857
+ let configured = cmd.argument(pathOperands.optionalVariadic, pathOperands.description).option(validationCommonCliOptions.quiet.flag, "Suppress progress output");
23858
+ if (definition.options.scope) {
23859
+ configured = configured.option(
23860
+ `${validationCommonCliOptions.scope.flag} <scope>`,
23861
+ "Validation scope (full|production)",
23862
+ "full"
23863
+ );
23864
+ }
23865
+ if (definition.options.json) {
23866
+ configured = configured.option(validationCommonCliOptions.json.flag, "Output results as JSON");
23867
+ }
23868
+ return configured;
23328
23869
  }
23329
23870
  async function normalizeProductPathOperand(productDir, effectiveInvocationDir, operand) {
23330
23871
  const resolvedProductDir = await canonicalPathThroughExistingAncestor(resolve16(productDir));
@@ -23384,57 +23925,65 @@ function addValidationSubcommand(validationCmd, definition) {
23384
23925
  }
23385
23926
  return subcommand;
23386
23927
  }
23387
- function registerValidationCommands(validationCmd, invocation, deps) {
23928
+ function selectedValidationAllOverrides(options, allOverrideCliOptions = validationAllOverrideCliOptions) {
23929
+ return allOverrideCliOptions.filter((option) => options[option.optionPropertyName] === true).map((option) => option.flag);
23930
+ }
23931
+ function registerValidationCommands(validationCmd, invocation, options = {}) {
23388
23932
  const { subcommands } = validationCliDefinition;
23389
- const tsCmd = addValidationSubcommand(validationCmd, subcommands.typescript).action(async (pathOperands, options) => {
23933
+ const commandHandlers = {
23934
+ ...defaultValidationCommandHandlers,
23935
+ ...options.commandHandlers
23936
+ };
23937
+ const runAllowlistExisting = options.allowlistExisting ?? allowlistExisting;
23938
+ const validationStages = options.validationStages ?? validationPipelineStages;
23939
+ const allOverrideCliOptions = deriveValidationAllOverrideCliOptions(
23940
+ validationStages
23941
+ );
23942
+ const tsCmd = addValidationSubcommand(validationCmd, subcommands.typescript).action(async (pathOperands, options2) => {
23390
23943
  const paths = await resolveValidationPaths(invocation, pathOperands);
23391
- const result = await deps.typescriptCommand({
23944
+ const result = await commandHandlers.typescript({
23392
23945
  cwd: paths.productDir,
23393
- scope: options.scope,
23946
+ scope: options2.scope,
23394
23947
  files: paths.files,
23395
- quiet: options.quiet,
23396
- json: options.json
23948
+ quiet: options2.quiet
23397
23949
  });
23398
23950
  emitValidationResult(result, invocation.io);
23399
23951
  });
23400
- addCommonOptions(tsCmd);
23401
- const lintCmd = addValidationSubcommand(validationCmd, subcommands.lint).option("--fix", "Auto-fix issues").action(async (pathOperands, options) => {
23952
+ addCommonOptions(tsCmd, subcommands.typescript);
23953
+ const lintCmd = addValidationSubcommand(validationCmd, subcommands.lint).option("--fix", "Auto-fix issues").action(async (pathOperands, options2) => {
23402
23954
  const paths = await resolveValidationPaths(invocation, pathOperands);
23403
- const result = await deps.lintCommand({
23955
+ const result = await commandHandlers.lint({
23404
23956
  cwd: paths.productDir,
23405
- scope: options.scope,
23957
+ scope: options2.scope,
23406
23958
  files: paths.files,
23407
- fix: options.fix,
23408
- quiet: options.quiet,
23409
- json: options.json
23959
+ fix: options2.fix,
23960
+ quiet: options2.quiet
23410
23961
  });
23411
23962
  emitValidationResult(result, invocation.io);
23412
23963
  });
23413
- addCommonOptions(lintCmd);
23414
- const circularCmd = addValidationSubcommand(validationCmd, subcommands.circular).action(async (pathOperands, options) => {
23964
+ addCommonOptions(lintCmd, subcommands.lint);
23965
+ const circularCmd = addValidationSubcommand(validationCmd, subcommands.circular).action(async (pathOperands, options2) => {
23415
23966
  const paths = await resolveValidationPaths(invocation, pathOperands);
23416
- const result = await deps.circularCommand({
23967
+ const result = await commandHandlers.circular({
23417
23968
  cwd: paths.productDir,
23418
- scope: options.scope,
23969
+ scope: options2.scope,
23419
23970
  files: paths.files,
23420
- quiet: options.quiet,
23421
- json: options.json
23971
+ quiet: options2.quiet
23422
23972
  });
23423
23973
  emitValidationResult(result, invocation.io);
23424
23974
  });
23425
- addCommonOptions(circularCmd);
23426
- const knipCmd = addValidationSubcommand(validationCmd, subcommands.knip).action(async (pathOperands, options) => {
23975
+ addCommonOptions(circularCmd, subcommands.circular);
23976
+ const knipCmd = addValidationSubcommand(validationCmd, subcommands.knip).action(async (pathOperands, options2) => {
23427
23977
  const paths = await resolveValidationPaths(invocation, pathOperands);
23428
- const result = await deps.knipCommand({
23978
+ const result = await commandHandlers.knip({
23429
23979
  cwd: paths.productDir,
23430
- scope: options.scope,
23980
+ scope: options2.scope,
23431
23981
  files: paths.files,
23432
- quiet: options.quiet,
23433
- json: options.json
23982
+ quiet: options2.quiet
23434
23983
  });
23435
23984
  emitValidationResult(result, invocation.io);
23436
23985
  });
23437
- addCommonOptions(knipCmd);
23986
+ addCommonOptions(knipCmd, subcommands.knip);
23438
23987
  const literalCmd = addValidationSubcommand(validationCmd, subcommands.literal).option(
23439
23988
  literalValidationCliOptions.allowlistExisting.flag,
23440
23989
  literalValidationCliOptions.allowlistExisting.description
@@ -23444,106 +23993,127 @@ function registerValidationCommands(validationCmd, invocation, deps) {
23444
23993
  ).option(literalValidationCliOptions.literals.flag, literalValidationCliOptions.literals.description).option(literalValidationCliOptions.verbose.flag, literalValidationCliOptions.verbose.description).addHelpText(
23445
23994
  "after",
23446
23995
  "\nEnabled for TypeScript projects by default. Set validation.literal.enabled=false\nin spx.config.* to skip during migration."
23447
- ).action(async (pathOperands, options) => {
23996
+ ).action(async (pathOperands, options2) => {
23448
23997
  const paths = await resolveValidationPaths(invocation, pathOperands);
23449
- if (options.allowlistExisting) {
23450
- const result2 = await deps.allowlistExisting({ productDir: paths.productDir });
23998
+ if (options2.allowlistExisting) {
23999
+ const result2 = await runAllowlistExisting({ productDir: paths.productDir });
23451
24000
  emitValidationResult(result2, invocation.io);
23452
24001
  }
23453
24002
  let kind;
23454
- if (options.kind !== void 0) {
23455
- kind = parseLiteralProblemKind(options.kind);
24003
+ if (options2.kind !== void 0) {
24004
+ kind = parseLiteralProblemKind(options2.kind);
23456
24005
  if (kind === void 0) {
23457
24006
  const { unknownLiteralProblemKind } = validationCliDefinition.diagnostics;
23458
24007
  invocation.io.writeStderr(
23459
- `spx validation literal: ${unknownLiteralProblemKind.messageLabel}: ${sanitizeCliArgument(options.kind)}
24008
+ `spx validation literal: ${unknownLiteralProblemKind.messageLabel}: ${sanitizeCliArgument(options2.kind)}
23460
24009
  `
23461
24010
  );
23462
24011
  invocation.io.exit(unknownLiteralProblemKind.exitCode);
23463
24012
  }
23464
24013
  }
23465
- const result = await deps.literalCommand({
24014
+ const result = await commandHandlers.literal({
23466
24015
  cwd: paths.productDir,
23467
- scope: options.scope,
24016
+ scope: options2.scope,
23468
24017
  files: paths.files,
23469
24018
  kind,
23470
- filesWithProblems: options.filesWithProblems,
23471
- literals: options.literals,
23472
- verbose: options.verbose,
23473
- quiet: options.quiet,
23474
- json: options.json
24019
+ filesWithProblems: options2.filesWithProblems,
24020
+ literals: options2.literals,
24021
+ verbose: options2.verbose,
24022
+ quiet: options2.quiet,
24023
+ json: options2.json
23475
24024
  });
23476
24025
  emitValidationResult(result, invocation.io);
23477
24026
  });
23478
- addCommonOptions(literalCmd);
24027
+ addCommonOptions(literalCmd, subcommands.literal);
23479
24028
  const markdownCmd = addValidationSubcommand(validationCmd, subcommands.markdown).addHelpText(
23480
24029
  "after",
23481
24030
  "\nValidates spx/ and docs/ by default. For nodes listed in spx/EXCLUDE,\nonly direct markdown files in that node directory are skipped;\nchild-node markdown remains in scope."
23482
- ).action(async (pathOperands, options) => {
24031
+ ).action(async (pathOperands, options2) => {
23483
24032
  const paths = await resolveValidationPaths(invocation, pathOperands);
23484
- const result = await deps.markdownCommand({
24033
+ const result = await commandHandlers.markdown({
23485
24034
  cwd: paths.productDir,
23486
24035
  files: paths.files,
23487
- quiet: options.quiet
24036
+ quiet: options2.quiet
23488
24037
  });
23489
24038
  emitValidationResult(result, invocation.io);
23490
24039
  });
23491
- addCommonOptions(markdownCmd);
23492
- const formatCmd = addValidationSubcommand(validationCmd, subcommands.format).action(async (pathOperands, options) => {
24040
+ addCommonOptions(markdownCmd, subcommands.markdown);
24041
+ const formatCmd = addValidationSubcommand(validationCmd, subcommands.format).action(async (pathOperands, options2) => {
23493
24042
  const paths = await resolveValidationPaths(invocation, pathOperands);
23494
- const result = await deps.formattingCommand({
24043
+ const result = await commandHandlers.format({
23495
24044
  cwd: paths.productDir,
23496
24045
  files: paths.files,
23497
- quiet: options.quiet
24046
+ quiet: options2.quiet
23498
24047
  });
23499
24048
  emitValidationResult(result, invocation.io);
23500
24049
  });
23501
- addCommonOptions(formatCmd);
23502
- 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 (pathOperands, options) => {
24050
+ addCommonOptions(formatCmd, subcommands.format);
24051
+ let allCmd = addValidationSubcommand(validationCmd, subcommands.all).option(validationAllBuiltInCliOptions.fix.flag, "Auto-fix ESLint issues");
24052
+ for (const option of allOverrideCliOptions) {
24053
+ allCmd = allCmd.option(option.flag, option.description);
24054
+ }
24055
+ allCmd = allCmd.action(async (pathOperands, options2) => {
23503
24056
  const paths = await resolveValidationPaths(invocation, pathOperands);
23504
- const result = await deps.allCommand({
24057
+ const result = await commandHandlers.all({
23505
24058
  cwd: paths.productDir,
23506
- scope: options.scope,
24059
+ scope: options2.scope,
23507
24060
  files: paths.files,
23508
- fix: options.fix,
23509
- skipCircular: options.skipCircular,
23510
- skipLiteral: options.skipLiteral,
23511
- quiet: options.quiet,
23512
- json: options.json
23513
- }, {
23514
- writeOutput: (output) => invocation.io.writeStdout(`${output}
23515
- `)
24061
+ fix: options2.fix,
24062
+ validationStages,
24063
+ participationOverrides: selectedValidationAllOverrides(options2, allOverrideCliOptions),
24064
+ quiet: options2.quiet,
24065
+ json: options2.json,
24066
+ onStageComplete: ({ output }) => invocation.io.writeStdout(`${output}
24067
+ `),
24068
+ outputStreams: validationSubprocessOutputStreams(invocation.io, options2.json)
23516
24069
  });
23517
- return invocation.io.exit(result.exitCode);
24070
+ emitValidationResult(result, invocation.io);
23518
24071
  });
23519
- addCommonOptions(allCmd);
24072
+ addCommonOptions(allCmd, subcommands.all);
24073
+ }
24074
+ function validationSubprocessOutputStreams(io, json) {
24075
+ if (json === true) return discardValidationSubprocessOutputStreams;
24076
+ return {
24077
+ stdout: {
24078
+ write: (chunk) => {
24079
+ io.writeStdout(Buffer.from(chunk).toString());
24080
+ return true;
24081
+ }
24082
+ },
24083
+ stderr: {
24084
+ write: (chunk) => {
24085
+ io.writeStderr(Buffer.from(chunk).toString());
24086
+ return true;
24087
+ }
24088
+ }
24089
+ };
23520
24090
  }
23521
24091
  function parseLiteralProblemKind(value) {
23522
- return validationLiteralProblemKinds.find((problemKind) => problemKind === value);
24092
+ if (isValidationLiteralProblemKind(value)) {
24093
+ return value;
24094
+ }
24095
+ return void 0;
23523
24096
  }
23524
24097
  function handleUnknownSubcommand(operands, io) {
23525
24098
  const [first] = operands;
23526
24099
  const sanitized = sanitizeCliArgument(first);
23527
24100
  const { domain, diagnostics } = validationCliDefinition;
23528
24101
  const { unknownSubcommand } = diagnostics;
23529
- io.writeStderr(
23530
- `${SPX_PROGRAM_NAME} ${domain.commandName}: ${unknownSubcommand.messageLabel}: ${sanitized}
23531
- `
23532
- );
24102
+ io.writeStderr(`${SPX_PROGRAM_NAME} ${domain.commandName}: ${unknownSubcommand.messageLabel}: ${sanitized}
24103
+ `);
23533
24104
  return io.exit(unknownSubcommand.exitCode);
23534
24105
  }
23535
- function createValidationDomain(overrides = {}) {
23536
- const deps = { ...defaultValidationCliDependencies, ...overrides };
24106
+ function createValidationDomain(options = {}) {
23537
24107
  return {
23538
24108
  name: validationCliDefinition.domain.commandName,
23539
24109
  description: validationCliDefinition.domain.description,
23540
24110
  register: (program, invocation) => {
23541
24111
  const { domain } = validationCliDefinition;
23542
- const validationCmd = program.command(domain.commandName).alias(domain.alias).description(domain.description);
24112
+ const validationCmd = program.command(domain.commandName).alias(domain.alias).description(domain.description).addHelpCommand(false);
23543
24113
  validationCmd.on("command:*", (operands) => {
23544
24114
  handleUnknownSubcommand(operands, invocation.io);
23545
24115
  });
23546
- registerValidationCommands(validationCmd, invocation, deps);
24116
+ registerValidationCommands(validationCmd, invocation, options);
23547
24117
  }
23548
24118
  };
23549
24119
  }
@@ -23850,6 +24420,10 @@ var REVIEW_TERMINAL_STATE = {
23850
24420
  CHANGES_REQUESTED: "changes_requested",
23851
24421
  COMMENTED: "commented"
23852
24422
  };
24423
+ var REVIEW_TERMINAL_STATUSES = /* @__PURE__ */ new Set([
24424
+ JOURNAL_RUN_STATE_STATUS.APPROVED,
24425
+ JOURNAL_RUN_STATE_STATUS.REJECTED
24426
+ ]);
23853
24427
  var AUDIT_CLASS = {
23854
24428
  IMPLEMENTATION: "implementation",
23855
24429
  INSTRUCTIONS: "instructions",
@@ -24154,6 +24728,9 @@ function validateReviewTerminal(input) {
24154
24728
  if (input.metadata !== void 0 && validated === void 0) {
24155
24729
  return { ok: false, error: TERMINAL_METADATA_VALIDATION_ERROR.METADATA_INVALID };
24156
24730
  }
24731
+ if (!REVIEW_TERMINAL_STATUSES.has(input.terminalStatus)) {
24732
+ return { ok: false, error: TERMINAL_METADATA_VALIDATION_ERROR.STATUS_CONFLICT };
24733
+ }
24157
24734
  const evidenceStatus = expectedReviewEvidenceTerminalStatus(input.events);
24158
24735
  const metadataStatus = expectedReviewMetadataTerminalStatus(validated);
24159
24736
  if (evidenceStatus !== void 0 && metadataStatus !== void 0 && evidenceStatus !== metadataStatus) {
@@ -25685,7 +26262,7 @@ function createCliProgram(options = {}) {
25685
26262
 
25686
26263
  // src/cli.ts
25687
26264
  installLifecycle();
25688
- var require3 = createRequire2(import.meta.url);
26265
+ var require3 = createRequire3(import.meta.url);
25689
26266
  var { version } = require3("../package.json");
25690
26267
  createCliProgram({ version }).parse();
25691
26268
  //# sourceMappingURL=cli.js.map