@outcomeeng/spx 0.6.4 → 0.6.6

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
@@ -4036,7 +4036,14 @@ function sessionStoreRunner(probe) {
4036
4036
  // src/domains/hooks/session-start.ts
4037
4037
  var HOOK_SESSION_START_PAYLOAD = {
4038
4038
  CWD: "cwd",
4039
- SESSION_ID: "session_id"
4039
+ SESSION_ID: "session_id",
4040
+ SOURCE: "source"
4041
+ };
4042
+ var HOOK_SESSION_START_SOURCE = {
4043
+ CLEAR: "clear",
4044
+ COMPACT: "compact",
4045
+ RESUME: "resume",
4046
+ STARTUP: "startup"
4040
4047
  };
4041
4048
  var HOOK_SESSION_START_ENV = {
4042
4049
  CLAUDE_ENV_FILE: "CLAUDE_ENV_FILE",
@@ -4080,7 +4087,8 @@ function parseHookSessionStartPayload(content) {
4080
4087
  ok: true,
4081
4088
  value: {
4082
4089
  cwd: nonEmptyString(record6[HOOK_SESSION_START_PAYLOAD.CWD]),
4083
- sessionId: nonEmptyString(record6[HOOK_SESSION_START_PAYLOAD.SESSION_ID])
4090
+ sessionId: nonEmptyString(record6[HOOK_SESSION_START_PAYLOAD.SESSION_ID]),
4091
+ source: nonEmptyString(record6[HOOK_SESSION_START_PAYLOAD.SOURCE])
4084
4092
  }
4085
4093
  };
4086
4094
  }
@@ -4090,6 +4098,15 @@ function resolveHookSessionStartSessionId(payload, env) {
4090
4098
  function resolveHookSessionStartProductDir(payload, cwd) {
4091
4099
  return payload.cwd ?? cwd;
4092
4100
  }
4101
+ var NO_STARTUP_DIRECTIVE = "";
4102
+ var HOOK_COMPACT_FOUNDATION_DIRECTIVE = [
4103
+ "Spec-tree foundation was reset by this compaction.",
4104
+ "Before any spec-governed action, including resuming an in-flight PR, /apply, or /handoff, re-invoke /understand then /contextualize on every spec node still in scope (not just the next one) before any gh/git archaeology or reading spec-governed source.",
4105
+ "The pre-compaction skill text in this summary is a historical record, not a live tool."
4106
+ ].join("\n");
4107
+ function renderSessionStartStdout(source) {
4108
+ return source === HOOK_SESSION_START_SOURCE.COMPACT ? HOOK_COMPACT_FOUNDATION_DIRECTIVE : NO_STARTUP_DIRECTIVE;
4109
+ }
4093
4110
  function resolveHookSessionStartEnvFile(env, explicitEnvFile) {
4094
4111
  return nonEmptyString(explicitEnvFile) ?? nonEmptyString(env[HOOK_SESSION_START_ENV.CLAUDE_ENV_FILE]);
4095
4112
  }
@@ -4954,7 +4971,8 @@ function isValidPid(pid) {
4954
4971
  // src/domains/worktree/resolve.ts
4955
4972
  import { dirname as dirname4, resolve as resolve4 } from "path";
4956
4973
  var WORKTREE_RESOLVE_ERROR = {
4957
- NOT_A_WORKTREE: "path resolves to no worktree"
4974
+ NOT_A_WORKTREE: "path resolves to no worktree",
4975
+ WORKTREE_LIST_UNAVAILABLE: "git worktree list is unavailable"
4958
4976
  };
4959
4977
  async function resolveWorktreesDir(options) {
4960
4978
  if (options.worktreesDir !== void 0) return options.worktreesDir;
@@ -4966,6 +4984,22 @@ async function resolveCurrentWorktreeName(options) {
4966
4984
  const worktree = await detectWorktreeProductRoot(options.cwd, options.gitDeps);
4967
4985
  return worktreeClaimName(worktree.productDir);
4968
4986
  }
4987
+ async function resolveAllTargetWorktrees(options) {
4988
+ const facts = await gatherGitFacts(options.cwd, options.gitDeps);
4989
+ if (facts === null) {
4990
+ return { ok: false, error: `${WORKTREE_RESOLVE_ERROR.NOT_A_WORKTREE}: ${options.cwd}` };
4991
+ }
4992
+ if (!facts.worktreeListRead) {
4993
+ return { ok: false, error: WORKTREE_RESOLVE_ERROR.WORKTREE_LIST_UNAVAILABLE };
4994
+ }
4995
+ return {
4996
+ ok: true,
4997
+ value: facts.worktreeRoots.map((worktreeRoot) => ({
4998
+ name: worktreeClaimName(worktreeRoot),
4999
+ worktreeRoot
5000
+ }))
5001
+ };
5002
+ }
4969
5003
  async function resolveTargetWorktree(options) {
4970
5004
  const base = options.cwd;
4971
5005
  const targetPath = options.worktree === void 0 ? base : resolve4(base, options.worktree);
@@ -5003,7 +5037,6 @@ var defaultHookEnvFileSystem = {
5003
5037
  }
5004
5038
  };
5005
5039
  var ERROR_DETAIL_SEPARATOR3 = ": ";
5006
- var EMPTY_HOOK_STDOUT = "";
5007
5040
  async function runSessionStartHook(options) {
5008
5041
  const diagnostics = [];
5009
5042
  const payloadResult = parseHookSessionStartPayload(options.content);
@@ -5042,7 +5075,7 @@ async function runSessionStartHook(options) {
5042
5075
  diagnostics,
5043
5076
  envFileWritten,
5044
5077
  productDir,
5045
- stdout: EMPTY_HOOK_STDOUT,
5078
+ stdout: renderSessionStartStdout(payload.source),
5046
5079
  ...sessionId === void 0 ? {} : { sessionId }
5047
5080
  }
5048
5081
  };
@@ -5135,17 +5168,17 @@ function createProcessHookIo(streams) {
5135
5168
  return {
5136
5169
  readStdin: async () => {
5137
5170
  if (streams.stdin.isTTY) return { ok: true, value: void 0 };
5138
- return new Promise((resolve10) => {
5171
+ return new Promise((resolve11) => {
5139
5172
  let data = "";
5140
5173
  streams.stdin.setEncoding("utf-8");
5141
5174
  streams.stdin.on(HOOK_PROCESS_IO_EVENT.DATA, (chunk) => {
5142
5175
  data += chunk;
5143
5176
  });
5144
5177
  streams.stdin.on(HOOK_PROCESS_IO_EVENT.END, () => {
5145
- resolve10({ ok: true, value: data.length === 0 ? void 0 : data });
5178
+ resolve11({ ok: true, value: data.length === 0 ? void 0 : data });
5146
5179
  });
5147
5180
  streams.stdin.on(HOOK_PROCESS_IO_EVENT.ERROR, (error) => {
5148
- resolve10({ ok: false, error: formatStdinReadError(error) });
5181
+ resolve11({ ok: false, error: formatStdinReadError(error) });
5149
5182
  });
5150
5183
  });
5151
5184
  },
@@ -6255,16 +6288,16 @@ ${output}`;
6255
6288
  // src/domains/session/handoff-base-checklist.ts
6256
6289
  var SESSION_HANDOFF_BASE_ERROR_NAME = "SessionHandoffBaseError";
6257
6290
  var HANDOFF_BASE_MARK = {
6258
- MET: "\u2713",
6259
- UNMET: "\u2717"
6291
+ MET: "[PASS]:",
6292
+ UNMET: "[FAIL]:"
6260
6293
  };
6261
6294
  var HANDOFF_BASE_PREREQUISITE_LABEL = {
6262
6295
  CLEAN_WORKING_TREE: "working tree is clean",
6263
6296
  DETACHED_AT_DEFAULT_TIP: "HEAD is detached at the default-branch tip"
6264
6297
  };
6265
6298
  var HANDOFF_BASE_REMEDY = {
6266
- /** Unclean working tree: commit, or hand off from the main checkout. */
6267
- COMMIT_OR_MAIN_CHECKOUT: "commit the changes, or run handoff from the main checkout",
6299
+ /** Unclean working tree: commit before any handoff proceeds. */
6300
+ COMMIT_BEFORE_HANDOFF: "commit the changes before handoff. DO NOT PROCEED while this checkout is dirty",
6268
6301
  /** Off the default-branch tip: detach to it, or hand off from the main checkout. */
6269
6302
  DETACH_TO_TIP_OR_MAIN_CHECKOUT: "detach HEAD to the default-branch tip, or run handoff from the main checkout",
6270
6303
  /** Default branch unresolved: only the main checkout can anchor the base. */
@@ -6281,23 +6314,34 @@ var HANDOFF_BASE_UNRESOLVED = "unresolved";
6281
6314
  var CHECKLIST_INDENT = " ";
6282
6315
  var REMEDY_SEPARATOR = " \u2014 ";
6283
6316
  var CHECKLIST_HEADER = `${SESSION_HANDOFF_BASE_ERROR_NAME}: cannot create a handoff session from this worktree \u2014 it is not the main checkout.`;
6317
+ var HANDOFF_BASE_DIRTY_HEADER = `${SESSION_HANDOFF_BASE_ERROR_NAME}: YOUR CHECKOUT IS DIRTY. YOU CANNOT HANDOFF.`;
6318
+ var HANDOFF_BASE_FACTS_HEADER = "GIT FACTS:";
6319
+ var HANDOFF_BASE_CHECKS_HEADER = "HERE ARE THE CHECKS AND THEIR STATUS:";
6284
6320
  function renderFactLine(label, value) {
6285
6321
  return `${CHECKLIST_INDENT}${label}: ${value ?? HANDOFF_BASE_UNRESOLVED}`;
6286
6322
  }
6287
- function renderPrerequisiteLine(prerequisite) {
6323
+ function renderPrerequisiteLine(prerequisite, index) {
6288
6324
  const mark = prerequisite.met ? HANDOFF_BASE_MARK.MET : HANDOFF_BASE_MARK.UNMET;
6289
- const base = `${CHECKLIST_INDENT}${mark} ${prerequisite.label}`;
6325
+ const lineNumber = index + 1;
6326
+ const base = `${lineNumber}. ${mark} ${prerequisite.label}`;
6290
6327
  return prerequisite.met ? base : `${base}${REMEDY_SEPARATOR}${prerequisite.remedy}`;
6291
6328
  }
6329
+ function isDirtyCheckoutRefusal(checklist) {
6330
+ return checklist.prerequisites.some(
6331
+ (prerequisite) => prerequisite.label === HANDOFF_BASE_PREREQUISITE_LABEL.CLEAN_WORKING_TREE && !prerequisite.met
6332
+ );
6333
+ }
6292
6334
  function renderHandoffBaseChecklist(checklist) {
6293
6335
  return [
6294
- CHECKLIST_HEADER,
6336
+ isDirtyCheckoutRefusal(checklist) ? HANDOFF_BASE_DIRTY_HEADER : CHECKLIST_HEADER,
6337
+ HANDOFF_BASE_FACTS_HEADER,
6295
6338
  renderFactLine(HANDOFF_BASE_FACT_LABEL.DEFAULT_BRANCH, checklist.defaultBranch),
6296
6339
  renderFactLine(HANDOFF_BASE_FACT_LABEL.DEFAULT_TIP, checklist.defaultTipSha),
6297
6340
  renderFactLine(HANDOFF_BASE_FACT_LABEL.HEAD, checklist.headSha),
6298
6341
  renderFactLine(HANDOFF_BASE_FACT_LABEL.CURRENT_WORKTREE, checklist.currentWorktreePath),
6299
6342
  renderFactLine(HANDOFF_BASE_FACT_LABEL.MAIN_CHECKOUT, checklist.mainCheckoutPath),
6300
- ...checklist.prerequisites.map(renderPrerequisiteLine)
6343
+ HANDOFF_BASE_CHECKS_HEADER,
6344
+ ...checklist.prerequisites.map((prerequisite, index) => renderPrerequisiteLine(prerequisite, index))
6301
6345
  ].join("\n");
6302
6346
  }
6303
6347
 
@@ -6404,6 +6448,17 @@ var SessionWorkBranchNotOnOriginError = class extends SessionError {
6404
6448
  this.workBranch = workBranch;
6405
6449
  }
6406
6450
  };
6451
+ var SessionInjectionDirectoryError = class extends SessionError {
6452
+ /** The listed `specs`/`files` entry that resolves to an existing directory. */
6453
+ entry;
6454
+ constructor(entry) {
6455
+ super(
6456
+ `Session injection entry resolves to a directory: ${entry}. The specs and files arrays hold file paths; list the file inside the directory rather than the directory itself.`
6457
+ );
6458
+ this.name = "SessionInjectionDirectoryError";
6459
+ this.entry = entry;
6460
+ }
6461
+ };
6407
6462
  var SessionInvalidJsonHeaderError = class extends SessionError {
6408
6463
  constructor(reason) {
6409
6464
  super(`Invalid JSON header for handoff: ${reason}`);
@@ -6985,7 +7040,7 @@ async function deleteCommand(options) {
6985
7040
  }
6986
7041
 
6987
7042
  // src/commands/session/handoff.ts
6988
- import { mkdir as mkdir2, writeFile } from "fs/promises";
7043
+ import { mkdir as mkdir2, stat as stat3, writeFile } from "fs/promises";
6989
7044
  import { join as join11, resolve as resolve5 } from "path";
6990
7045
  import { stringify as stringifyYaml3 } from "yaml";
6991
7046
 
@@ -7007,7 +7062,7 @@ function cleanPrerequisite(facts) {
7007
7062
  return {
7008
7063
  label: HANDOFF_BASE_PREREQUISITE_LABEL.CLEAN_WORKING_TREE,
7009
7064
  met: facts.isClean,
7010
- remedy: facts.isClean ? "" : HANDOFF_BASE_REMEDY.COMMIT_OR_MAIN_CHECKOUT
7065
+ remedy: facts.isClean ? "" : HANDOFF_BASE_REMEDY.COMMIT_BEFORE_HANDOFF
7011
7066
  };
7012
7067
  }
7013
7068
  function detachedAtTipPrerequisite(facts, met) {
@@ -7231,6 +7286,20 @@ async function resolveRecordedGitRef(suppliedRef, gateRef, cwd, deps) {
7231
7286
  const existsOnOrigin = await originBranchExists(suppliedRef, cwd, deps);
7232
7287
  return resolveWorkBranchGitRef(suppliedRef, existsOnOrigin);
7233
7288
  }
7289
+ async function rejectDirectoryInjectionEntries(entries, cwd) {
7290
+ for (const entry of entries) {
7291
+ if (entry.length === 0) continue;
7292
+ let entryIsDirectory = false;
7293
+ try {
7294
+ entryIsDirectory = (await stat3(resolve5(cwd, entry))).isDirectory();
7295
+ } catch {
7296
+ continue;
7297
+ }
7298
+ if (entryIsDirectory) {
7299
+ throw new SessionInjectionDirectoryError(entry);
7300
+ }
7301
+ }
7302
+ }
7234
7303
  async function handoffCommand(options) {
7235
7304
  const { config } = await resolveSessionConfig({
7236
7305
  sessionsDir: options.sessionsDir,
@@ -7250,6 +7319,8 @@ async function handoffCommand(options) {
7250
7319
  }
7251
7320
  const gateRef = await resolveSessionGitRef(options.cwd, options.deps);
7252
7321
  const gitRef = await resolveRecordedGitRef(header.git_ref, gateRef, options.cwd, options.deps);
7322
+ const injectionCwd = options.cwd ?? CONFIG_PROCESS_CWD.read();
7323
+ await rejectDirectoryInjectionEntries([...header.specs, ...header.files], injectionCwd);
7253
7324
  const sessionId = generateSessionId();
7254
7325
  const agentSessionId = resolveAgentSessionId(options.env ?? process.env);
7255
7326
  const createdAt = (/* @__PURE__ */ new Date()).toISOString();
@@ -7430,6 +7501,7 @@ async function loadTodoSessionsWithDeps(config, deps) {
7430
7501
  }
7431
7502
  var SESSION_INJECTION_SECTION_PREFIX = "Injected file";
7432
7503
  var SESSION_INJECTION_MISSING_WARNING_PREFIX = "Warning: missing session injection file";
7504
+ var SESSION_INJECTION_UNREADABLE_WARNING_PREFIX = "Warning: unreadable session injection path";
7433
7505
  function injectionPath(cwd, filePath) {
7434
7506
  return resolve6(cwd, filePath);
7435
7507
  }
@@ -7437,6 +7509,11 @@ function formatInjectedFile(listedPath, content) {
7437
7509
  return `${SESSION_INJECTION_SECTION_PREFIX}: ${listedPath}
7438
7510
  ${content}`;
7439
7511
  }
7512
+ function formatInjectionWarning(error, listedPath) {
7513
+ const isAbsent = error instanceof Error && "code" in error && error.code === SESSION_FILE_ERROR_CODE.NOT_FOUND;
7514
+ const prefix = isAbsent ? SESSION_INJECTION_MISSING_WARNING_PREFIX : SESSION_INJECTION_UNREADABLE_WARNING_PREFIX;
7515
+ return `${prefix}: ${listedPath}`;
7516
+ }
7440
7517
  async function readInjectedFiles(metadata, cwd, deps, onWarning) {
7441
7518
  const sections = [];
7442
7519
  for (const listedPath of [...metadata.specs, ...metadata.files]) {
@@ -7444,11 +7521,7 @@ async function readInjectedFiles(metadata, cwd, deps, onWarning) {
7444
7521
  const content = await deps.readFile(injectionPath(cwd, listedPath), SESSION_FILE_ENCODING);
7445
7522
  sections.push(formatInjectedFile(listedPath, content));
7446
7523
  } catch (error) {
7447
- if (error instanceof Error && "code" in error && error.code === SESSION_FILE_ERROR_CODE.NOT_FOUND) {
7448
- onWarning?.(`${SESSION_INJECTION_MISSING_WARNING_PREFIX}: ${listedPath}`);
7449
- continue;
7450
- }
7451
- throw error;
7524
+ onWarning?.(formatInjectionWarning(error, listedPath));
7452
7525
  }
7453
7526
  }
7454
7527
  return sections;
@@ -7672,12 +7745,12 @@ async function releaseCommand(options) {
7672
7745
  }
7673
7746
 
7674
7747
  // src/commands/session/show.ts
7675
- import { readFile as readFile7, stat as stat3 } from "fs/promises";
7748
+ import { readFile as readFile7, stat as stat4 } from "fs/promises";
7676
7749
  async function findExistingPath(paths, _config) {
7677
7750
  for (let i = 0; i < paths.length; i++) {
7678
7751
  const filePath = paths[i];
7679
7752
  try {
7680
- const stats = await stat3(filePath);
7753
+ const stats = await stat4(filePath);
7681
7754
  if (stats.isFile()) {
7682
7755
  return { path: filePath, status: SEARCH_ORDER[i] };
7683
7756
  }
@@ -8119,13 +8192,13 @@ function spawnManagedSubprocess(runner, command, args, options) {
8119
8192
  var LAUNCH_FAILURE_STATUS = 1;
8120
8193
  function launchAgent(runner, suspender, command) {
8121
8194
  const restoreSignals = suspender.suspend();
8122
- return new Promise((resolve10) => {
8195
+ return new Promise((resolve11) => {
8123
8196
  let settled = false;
8124
8197
  const settle = (status) => {
8125
8198
  if (settled) return;
8126
8199
  settled = true;
8127
8200
  restoreSignals();
8128
- resolve10(status);
8201
+ resolve11(status);
8129
8202
  };
8130
8203
  const child = runner.spawn(command.command, command.args, { stdio: "inherit" });
8131
8204
  child.once("exit", (code) => settle(code ?? LAUNCH_FAILURE_STATUS));
@@ -8260,17 +8333,17 @@ async function readStdin() {
8260
8333
  if (process.stdin.isTTY) {
8261
8334
  return void 0;
8262
8335
  }
8263
- return new Promise((resolve10) => {
8336
+ return new Promise((resolve11) => {
8264
8337
  let data = "";
8265
8338
  process.stdin.setEncoding(SESSION_FILE_ENCODING);
8266
8339
  process.stdin.on("data", (chunk) => {
8267
8340
  data += chunk;
8268
8341
  });
8269
8342
  process.stdin.on("end", () => {
8270
- resolve10(data.length === 0 ? void 0 : data);
8343
+ resolve11(data.length === 0 ? void 0 : data);
8271
8344
  });
8272
8345
  process.stdin.on("error", () => {
8273
- resolve10(void 0);
8346
+ resolve11(void 0);
8274
8347
  });
8275
8348
  });
8276
8349
  }
@@ -9953,17 +10026,56 @@ function createNodeStatusProvider(productDir) {
9953
10026
  // src/lib/node-status/update.ts
9954
10027
  import { mkdir as mkdir4, readdir as readdir7, rm, writeFile as writeFile2 } from "fs/promises";
9955
10028
  import { dirname as dirname7, join as join21 } from "path";
10029
+
10030
+ // src/git/tracked-paths.ts
10031
+ var GIT_LS_FILES_SUBCOMMAND = "ls-files";
10032
+ var GIT_NUL_TERMINATED_FLAG = "-z";
10033
+ var TRACKED_PATH_NUL_SEPARATOR = "\0";
10034
+ var TRACKED_PATH_DIRECTORY_SEPARATOR = "/";
10035
+ var GIT_SUCCESS_EXIT_CODE = 0;
10036
+ async function listTrackedPaths(productDir, deps) {
10037
+ let result;
10038
+ try {
10039
+ result = await deps.execa(
10040
+ GIT_ROOT_COMMAND.EXECUTABLE,
10041
+ [GIT_LS_FILES_SUBCOMMAND, GIT_NUL_TERMINATED_FLAG],
10042
+ { cwd: productDir, reject: false }
10043
+ );
10044
+ } catch {
10045
+ return void 0;
10046
+ }
10047
+ if (result.exitCode !== GIT_SUCCESS_EXIT_CODE) return void 0;
10048
+ return new Set(result.stdout.split(TRACKED_PATH_NUL_SEPARATOR).filter((entry) => entry.length > 0));
10049
+ }
10050
+ function createTrackedPathInclusion(trackedPaths) {
10051
+ if (trackedPaths === void 0) return () => true;
10052
+ const admitted = new Set(trackedPaths);
10053
+ for (const trackedPath of trackedPaths) {
10054
+ let separatorIndex = trackedPath.indexOf(TRACKED_PATH_DIRECTORY_SEPARATOR);
10055
+ while (separatorIndex !== -1) {
10056
+ admitted.add(trackedPath.slice(0, separatorIndex));
10057
+ separatorIndex = trackedPath.indexOf(TRACKED_PATH_DIRECTORY_SEPARATOR, separatorIndex + 1);
10058
+ }
10059
+ }
10060
+ return (path7) => admitted.has(path7);
10061
+ }
10062
+
10063
+ // src/lib/node-status/update.ts
9956
10064
  var NODE_STATUS_TEXT_ENCODING = "utf8";
9957
10065
  async function updateNodeStatus(options) {
9958
- const { productDir, resolveOutcome } = options;
10066
+ const { productDir, resolveOutcome, gitDependencies = defaultGitDependencies } = options;
9959
10067
  const snapshot = await readSpecTree({ source: createFilesystemSpecTreeSource({ productDir }) });
9960
10068
  const ignoreReader = createIgnoreSourceReader(productDir, {
9961
10069
  ignoreSourceFilename: IGNORE_SOURCE_FILENAME_DEFAULT,
9962
10070
  specTreeRootSegment: SPEC_TREE_CONFIG.ROOT_DIRECTORY
9963
10071
  });
10072
+ const isTrackedNodeDirectory = createTrackedPathInclusion(await listTrackedPaths(productDir, gitDependencies));
9964
10073
  const evidenceByNode = collectEvidenceByNode(snapshot);
9965
10074
  const liveStatusPaths = /* @__PURE__ */ new Set();
9966
10075
  for (const node of snapshot.allNodes) {
10076
+ if (!isTrackedNodeDirectory(`${SPEC_TREE_CONFIG.ROOT_DIRECTORY}${TRACKED_PATH_DIRECTORY_SEPARATOR}${node.id}`)) {
10077
+ continue;
10078
+ }
9967
10079
  const evidence = evidenceByNode.get(node.id) ?? [];
9968
10080
  const verification = await resolveVerification(node, {
9969
10081
  evidence,
@@ -10163,13 +10275,14 @@ async function statusCommand(options = {}) {
10163
10275
  }
10164
10276
  return renderSpecStatus(projectSpecTree(await readSpecTree({ source: options.source })), options.format);
10165
10277
  }
10278
+ const gitDependencies = options.gitDependencies ?? defaultGitDependencies;
10166
10279
  const productDir = await resolveSpecProductDir(
10167
10280
  options.cwd ?? CONFIG_PROCESS_CWD.read(),
10168
- options.gitDependencies,
10281
+ gitDependencies,
10169
10282
  options.onWarning
10170
10283
  );
10171
10284
  if (options.update === true) {
10172
- await updateNodeStatus({ productDir, resolveOutcome: options.resolveOutcomeFor(productDir) });
10285
+ await updateNodeStatus({ productDir, resolveOutcome: options.resolveOutcomeFor(productDir), gitDependencies });
10173
10286
  }
10174
10287
  const snapshot = await readSpecTree({
10175
10288
  source: createFilesystemSpecTreeSource({ productDir }),
@@ -10859,8 +10972,12 @@ function createTestingDomain(deps) {
10859
10972
  }
10860
10973
  var testingDomain = createTestingDomain();
10861
10974
 
10975
+ // src/interfaces/cli/validation.ts
10976
+ import { existsSync as existsSync8, realpathSync } from "fs";
10977
+ import { isAbsolute as isAbsolute9, relative as relative9, resolve as resolve10, sep as sep3 } from "path";
10978
+
10862
10979
  // src/commands/validation/formatting.ts
10863
- import { existsSync } from "fs";
10980
+ import { existsSync, statSync } from "fs";
10864
10981
  import { isAbsolute as isAbsolute3, join as join24, relative as relative3 } from "path";
10865
10982
 
10866
10983
  // src/validation/config/path-filter.ts
@@ -10870,11 +10987,24 @@ function hasEffectiveValidationPathMetadata(filter) {
10870
10987
  return "hasIncludeFilter" in filter && typeof filter.hasIncludeFilter === "boolean" && "noMatchingIncludes" in filter && typeof filter.noMatchingIncludes === "boolean";
10871
10988
  }
10872
10989
  function normalizePathPrefix2(prefix) {
10873
- return prefix.split(/[\\/]/g).join(PATH_PREFIX_SEPARATOR).replace(/^\.\//, "").replace(/\/+$/, "");
10990
+ const normalizedSeparators = prefix.split("\\").join(PATH_PREFIX_SEPARATOR);
10991
+ const withoutLeadingDot = normalizedSeparators.startsWith(`.${PATH_PREFIX_SEPARATOR}`) ? normalizedSeparators.slice(2) : normalizedSeparators;
10992
+ const normalized = trimTrailingPathSeparators(withoutLeadingDot);
10993
+ return normalized === "." ? "" : normalized;
10994
+ }
10995
+ function trimTrailingPathSeparators(path7) {
10996
+ let end = path7.length;
10997
+ while (end > 0 && path7[end - 1] === PATH_PREFIX_SEPARATOR) {
10998
+ end -= 1;
10999
+ }
11000
+ return path7.slice(0, end);
10874
11001
  }
10875
11002
  function pathMatchesPrefix2(path7, prefix) {
10876
11003
  const normalizedPath = normalizePathPrefix2(path7);
10877
11004
  const normalizedPrefix = normalizePathPrefix2(prefix);
11005
+ if (normalizedPrefix.length === 0) {
11006
+ return true;
11007
+ }
10878
11008
  return normalizedPath === normalizedPrefix || normalizedPath.startsWith(`${normalizedPrefix}${PATH_PREFIX_SEPARATOR}`);
10879
11009
  }
10880
11010
  function unique(values) {
@@ -10930,6 +11060,27 @@ function pathPassesValidationFilter(path7, filter) {
10930
11060
  }
10931
11061
  return !exclude.some((prefix) => pathMatchesPrefix2(path7, prefix));
10932
11062
  }
11063
+ function pathIntersectsValidationFilter(path7, filter) {
11064
+ return validationPathFilterIntersections(path7, filter).length > 0;
11065
+ }
11066
+ function validationPathFilterExcludes(filter) {
11067
+ return unique(nonEmpty(filter.exclude).map(normalizePathPrefix2));
11068
+ }
11069
+ function validationPathFilterIntersections(path7, filter) {
11070
+ if (hasEffectiveValidationPathMetadata(filter) && filter.noMatchingIncludes) {
11071
+ return [];
11072
+ }
11073
+ const normalizedPath = normalizePathPrefix2(path7);
11074
+ const include = nonEmpty(filter.include);
11075
+ const exclude = nonEmpty(filter.exclude);
11076
+ const candidates = include.length > 0 ? include.flatMap((prefix) => {
11077
+ const normalizedPrefix = normalizePathPrefix2(prefix);
11078
+ if (pathMatchesPrefix2(normalizedPath, normalizedPrefix)) return [normalizedPath];
11079
+ if (pathMatchesPrefix2(normalizedPrefix, normalizedPath)) return [normalizedPrefix];
11080
+ return [];
11081
+ }) : [normalizedPath];
11082
+ return unique(candidates).filter((candidate) => !exclude.some((prefix) => pathMatchesPrefix2(candidate, prefix)));
11083
+ }
10933
11084
  function applyValidationPathFilterToScope(scopeConfig, filter) {
10934
11085
  if (!hasValidationPathFilter(filter)) {
10935
11086
  return scopeConfig;
@@ -10945,7 +11096,14 @@ function applyValidationPathFilterToScope(scopeConfig, filter) {
10945
11096
  (include) => scopeConfig.directories.some((directory) => pathMatchesPrefix2(include, directory))
10946
11097
  );
10947
11098
  const noMatchingIncludes = hasEffectiveMetadata && filter.noMatchingIncludes || hasIncludeFilter && scopedDirectories.length === 0 && scopedFilePatterns.length === 0 && includeFallbacksWithinScope.length === 0;
10948
- const directories = noMatchingIncludes ? [] : scopedDirectories.length > 0 ? scopedDirectories : includeFallbacksWithinScope;
11099
+ let directories;
11100
+ if (noMatchingIncludes) {
11101
+ directories = [];
11102
+ } else if (scopedDirectories.length > 0) {
11103
+ directories = scopedDirectories;
11104
+ } else {
11105
+ directories = includeFallbacksWithinScope;
11106
+ }
10949
11107
  const filePatterns = scopedFilePatterns.length > 0 ? scopedFilePatterns : [...directories];
10950
11108
  return {
10951
11109
  ...scopeConfig,
@@ -10991,14 +11149,23 @@ function forwardChunkWithBackpressure(source, stream, chunk) {
10991
11149
  // src/validation/steps/formatting.ts
10992
11150
  var DPRINT_COMMAND = "dprint";
10993
11151
  var DPRINT_CHECK_SUBCOMMAND = "check";
11152
+ var DPRINT_EXCLUDES_OPTION = "--excludes";
11153
+ var DPRINT_OPTIONS_TERMINATOR = "--";
10994
11154
  var DPRINT_CONFIG_FILENAME = "dprint.jsonc";
10995
11155
  var defaultFormattingProcessRunner = lifecycleProcessRunner;
10996
11156
  function buildDprintCheckArgs(options) {
10997
- return [DPRINT_CHECK_SUBCOMMAND, ...options.files ?? []];
11157
+ const excludes = options.excludes ?? [];
11158
+ const files = options.files ?? [];
11159
+ return [
11160
+ DPRINT_CHECK_SUBCOMMAND,
11161
+ ...excludes.length > 0 ? [DPRINT_EXCLUDES_OPTION, ...excludes] : [],
11162
+ ...files.length > 0 ? [DPRINT_OPTIONS_TERMINATOR] : [],
11163
+ ...files
11164
+ ];
10998
11165
  }
10999
11166
  async function validateFormatting(context, runner = defaultFormattingProcessRunner) {
11000
- const args = buildDprintCheckArgs({ files: context.files });
11001
- return new Promise((resolve10) => {
11167
+ const args = buildDprintCheckArgs({ files: context.files, excludes: context.excludes });
11168
+ return new Promise((resolve11) => {
11002
11169
  const child = spawnManagedSubprocess(runner, DPRINT_COMMAND, args, { cwd: context.projectRoot });
11003
11170
  const chunks = [];
11004
11171
  const capture = (chunk) => {
@@ -11007,10 +11174,10 @@ async function validateFormatting(context, runner = defaultFormattingProcessRunn
11007
11174
  child.stdout?.on(VALIDATION_SUBPROCESS_EVENTS.DATA, capture);
11008
11175
  child.stderr?.on(VALIDATION_SUBPROCESS_EVENTS.DATA, capture);
11009
11176
  child.on(VALIDATION_SUBPROCESS_EVENTS.CLOSE, (code) => {
11010
- resolve10({ success: code === 0, output: chunks.join("") });
11177
+ resolve11({ success: code === 0, output: chunks.join("") });
11011
11178
  });
11012
11179
  child.on(VALIDATION_SUBPROCESS_EVENTS.ERROR, (error) => {
11013
- resolve10({ success: false, output: chunks.join(""), error: error.message });
11180
+ resolve11({ success: false, output: chunks.join(""), error: error.message });
11014
11181
  });
11015
11182
  });
11016
11183
  }
@@ -11032,7 +11199,7 @@ var VALIDATION_SKIP_LABELS = {
11032
11199
  DISABLED_BY_PREFIX: "disabled by",
11033
11200
  TYPESCRIPT_ABSENT_REASON: "TypeScript not detected in project",
11034
11201
  VALIDATION_PATHS_NO_TARGETS_REASON: "validation paths matched no files",
11035
- MARKDOWN_NO_SCOPE_REASON: "no markdown files in --files scope",
11202
+ MARKDOWN_NO_SCOPE_REASON: "no markdown files in explicit path scope",
11036
11203
  MARKDOWN_NO_DEFAULT_DIRECTORIES_REASON: "no spx/ or docs/ directories found"
11037
11204
  };
11038
11205
  var VALIDATION_COMMAND_OUTPUT = {
@@ -11077,6 +11244,7 @@ var FORMATTING_COMMAND_OUTPUT = {
11077
11244
  NO_CONFIG_SKIP_REASON: `no ${DPRINT_CONFIG_FILENAME} at product root`
11078
11245
  };
11079
11246
  var FORMATTING_CONFIG_ERROR_MESSAGE = `${VALIDATION_STAGE_DISPLAY_NAMES.FORMATTING}: \u2717 config error`;
11247
+ var DPRINT_RECURSIVE_DIRECTORY_GLOB_SUFFIX = "/**/*";
11080
11248
  async function formattingCommand(options) {
11081
11249
  const { cwd, files, quiet } = options;
11082
11250
  const startTime = Date.now();
@@ -11098,12 +11266,22 @@ async function formattingCommand(options) {
11098
11266
  VALIDATION_PATH_TOOL_SUBSECTIONS.FORMATTING
11099
11267
  );
11100
11268
  const hasExplicitScope = files !== void 0 && files.length > 0;
11101
- const scopedFiles = hasExplicitScope ? files.map((filePath) => isAbsolute3(filePath) ? relative3(cwd, filePath) : filePath).filter((relativePath) => pathPassesValidationFilter(relativePath, pathFilter)) : void 0;
11269
+ const scopedFiles = hasExplicitScope ? files.flatMap(
11270
+ (filePath) => formattingPathOperandsForValidationPathFilter(
11271
+ cwd,
11272
+ isAbsolute3(filePath) ? relative3(cwd, filePath) : filePath,
11273
+ pathFilter
11274
+ )
11275
+ ) : void 0;
11102
11276
  if (hasExplicitScope && (scopedFiles === void 0 || scopedFiles.length === 0)) {
11103
11277
  const output2 = quiet ? "" : `${VALIDATION_STAGE_DISPLAY_NAMES.FORMATTING}: skipped (${FORMATTING_COMMAND_OUTPUT.EMPTY_SCOPE_REASON})`;
11104
11278
  return { exitCode: 0, output: output2, durationMs: Date.now() - startTime };
11105
11279
  }
11106
- const result = await validateFormatting({ projectRoot: cwd, files: scopedFiles });
11280
+ const result = await validateFormatting({
11281
+ projectRoot: cwd,
11282
+ files: scopedFiles,
11283
+ excludes: validationPathFilterExcludes(pathFilter)
11284
+ });
11107
11285
  const durationMs = Date.now() - startTime;
11108
11286
  if (result.success) {
11109
11287
  return { exitCode: 0, output: quiet ? "" : FORMATTING_COMMAND_OUTPUT.NO_ISSUES, durationMs };
@@ -11112,6 +11290,24 @@ async function formattingCommand(options) {
11112
11290
  const output = [FORMATTING_COMMAND_OUTPUT.FAILURE_SUMMARY, detail].filter((line) => line.length > 0).join("\n");
11113
11291
  return { exitCode: 1, output, durationMs };
11114
11292
  }
11293
+ function normalizeFormattingPathOperand(productDir, relativePath) {
11294
+ const absolutePath = join24(productDir, relativePath);
11295
+ if (!existsSync(absolutePath) || !statSync(absolutePath).isDirectory()) {
11296
+ return relativePath;
11297
+ }
11298
+ const normalizedDirectory = trimTrailingPathSeparators(relativePath);
11299
+ if (normalizedDirectory.length === 0 || normalizedDirectory === ".") {
11300
+ return DPRINT_RECURSIVE_DIRECTORY_GLOB_SUFFIX.slice(1);
11301
+ }
11302
+ return `${normalizedDirectory}${DPRINT_RECURSIVE_DIRECTORY_GLOB_SUFFIX}`;
11303
+ }
11304
+ function formattingPathOperandsForValidationPathFilter(productDir, relativePath, pathFilter) {
11305
+ const absolutePath = join24(productDir, relativePath);
11306
+ if (!existsSync(absolutePath) || !statSync(absolutePath).isDirectory()) {
11307
+ return pathPassesValidationFilter(relativePath, pathFilter) ? [relativePath] : [];
11308
+ }
11309
+ return validationPathFilterIntersections(relativePath, pathFilter).map((path7) => normalizeFormattingPathOperand(productDir, path7));
11310
+ }
11115
11311
 
11116
11312
  // src/validation/languages/formatting.ts
11117
11313
  var FORMATTING_LANGUAGE_NAME = "formatting";
@@ -11127,10 +11323,10 @@ var formattingValidationLanguage = {
11127
11323
  };
11128
11324
 
11129
11325
  // src/commands/validation/markdown.ts
11130
- import { relative as relative4 } from "path";
11326
+ import { isAbsolute as isAbsolute4, join as join26, relative as relative4 } from "path";
11131
11327
 
11132
11328
  // src/validation/steps/markdown.ts
11133
- import { existsSync as existsSync2, statSync } from "fs";
11329
+ import { existsSync as existsSync2, statSync as statSync2 } from "fs";
11134
11330
  import { basename as basename4, dirname as dirname9, join as join25, relative as pathRelative } from "path";
11135
11331
  import { main as markdownlintMain } from "markdownlint-cli2";
11136
11332
  import relativeLinksRule from "markdownlint-rule-relative-links";
@@ -11154,9 +11350,8 @@ var MARKDOWN_VALIDATION_TARGET_KIND = {
11154
11350
  var MARKDOWN_VALIDATION_TARGET_DIAGNOSTICS = {
11155
11351
  MISSING_OR_UNRELATED_SCOPE: "not an existing directory or markdown file"
11156
11352
  };
11157
- var ERROR_LINE_PATTERN = /^(.+?):(\d+)(?::\d+)?\s+(.+)$/;
11158
11353
  var defaultMarkdownValidationTargetDeps = {
11159
- statSync
11354
+ statSync: statSync2
11160
11355
  };
11161
11356
  function buildMarkdownlintConfig(directoryName) {
11162
11357
  const md024Disabled = MD024_DISABLED_DIRECTORIES.includes(
@@ -11199,25 +11394,71 @@ function parseErrorLine(line) {
11199
11394
  if (DATA_URI_PATTERN.test(line)) {
11200
11395
  return null;
11201
11396
  }
11202
- const match = ERROR_LINE_PATTERN.exec(line);
11203
- if (!match) {
11204
- return null;
11397
+ const parsed = parseMarkdownlintErrorLine(line);
11398
+ if (parsed === null) return null;
11399
+ return {
11400
+ file: parsed.file,
11401
+ line: Number.parseInt(parsed.line, 10),
11402
+ detail: parsed.detail
11403
+ };
11404
+ }
11405
+ function parseMarkdownlintErrorLine(line) {
11406
+ for (let fileSeparator = line.indexOf(":"); fileSeparator > 0; fileSeparator = line.indexOf(":", fileSeparator + 1)) {
11407
+ const parsed = parseMarkdownlintErrorLineAtSeparator(line, fileSeparator);
11408
+ if (parsed !== null) return parsed;
11205
11409
  }
11206
- const [, file, lineStr, detail] = match;
11410
+ return null;
11411
+ }
11412
+ function parseMarkdownlintErrorLineAtSeparator(line, fileSeparator) {
11413
+ const lineStart = fileSeparator + 1;
11414
+ const lineEnd = scanDigits(line, lineStart);
11415
+ if (lineEnd === lineStart) return null;
11416
+ const detailStart = scanMarkdownlintColumnAndWhitespace(line, lineEnd);
11417
+ if (detailStart === null || detailStart >= line.length) return null;
11207
11418
  return {
11208
- file,
11209
- line: parseInt(lineStr, 10),
11210
- detail
11419
+ file: line.slice(0, fileSeparator),
11420
+ line: line.slice(lineStart, lineEnd),
11421
+ detail: line.slice(detailStart)
11211
11422
  };
11212
11423
  }
11424
+ function scanDigits(value, start) {
11425
+ let index = start;
11426
+ while (index < value.length && isAsciiDigit(value[index] ?? "")) {
11427
+ index += 1;
11428
+ }
11429
+ return index;
11430
+ }
11431
+ function scanMarkdownlintColumnAndWhitespace(value, start) {
11432
+ let index = start;
11433
+ if (value[index] === ":") {
11434
+ const columnStart = index + 1;
11435
+ index = scanDigits(value, columnStart);
11436
+ if (index === columnStart) return null;
11437
+ }
11438
+ if (!isAsciiWhitespace(value[index] ?? "")) return null;
11439
+ while (index < value.length && isAsciiWhitespace(value[index] ?? "")) {
11440
+ index += 1;
11441
+ }
11442
+ return index;
11443
+ }
11444
+ function isAsciiDigit(value) {
11445
+ return value >= "0" && value <= "9";
11446
+ }
11447
+ function isAsciiWhitespace(value) {
11448
+ return value === " " || value === " ";
11449
+ }
11213
11450
  async function validateMarkdown(options) {
11214
- const { targets, projectRoot } = options;
11451
+ const { targets, projectRoot, validationPathExcludes = [] } = options;
11215
11452
  const errors = [];
11216
- const excludeGlobs = getExcludeGlobs(projectRoot);
11453
+ const specTreeExcludeGlobs = getExcludeGlobs(projectRoot);
11217
11454
  for (const target of targets) {
11218
11455
  const directory = targetDirectory(target);
11219
11456
  const dirName = markdownlintConfigDirectoryName(directory, projectRoot);
11220
11457
  const config = buildMarkdownlintConfig(dirName);
11458
+ const excludeGlobs = [
11459
+ ...specTreeExcludeGlobs,
11460
+ ...validationPathExcludeGlobsForTarget(target, projectRoot, validationPathExcludes)
11461
+ ];
11221
11462
  const dirErrors = await validateTarget(target, config, projectRoot, excludeGlobs);
11222
11463
  errors.push(...dirErrors);
11223
11464
  }
@@ -11263,6 +11504,32 @@ async function validateTarget(target, config, projectRoot, ignoreGlobs = []) {
11263
11504
  function targetDirectory(target) {
11264
11505
  return target.kind === MARKDOWN_VALIDATION_TARGET_KIND.FILE ? dirname9(target.path) : target.path;
11265
11506
  }
11507
+ function validationPathExcludeGlobsForTarget(target, projectRoot, excludes) {
11508
+ if (projectRoot === void 0 || excludes.length === 0) return [];
11509
+ const directory = targetDirectory(target);
11510
+ const targetPath = normalizePathPrefix2(pathRelative(projectRoot, directory));
11511
+ return excludes.flatMap((exclude) => {
11512
+ const excludedPath = normalizePathPrefix2(exclude);
11513
+ if (pathContainsValidationPath(excludedPath, targetPath)) {
11514
+ return [MARKDOWN_DIRECTORY_GLOB];
11515
+ }
11516
+ if (!pathContainsValidationPath(targetPath, excludedPath)) {
11517
+ return [];
11518
+ }
11519
+ const relativeExclude = normalizePathPrefix2(pathRelative(directory, join25(projectRoot, excludedPath)));
11520
+ if (relativeExclude.length === 0) {
11521
+ return [MARKDOWN_DIRECTORY_GLOB];
11522
+ }
11523
+ const absoluteExclude = join25(projectRoot, excludedPath);
11524
+ return [
11525
+ isExistingFile(absoluteExclude, defaultMarkdownValidationTargetDeps) ? relativeExclude : `${relativeExclude}/**`
11526
+ ];
11527
+ });
11528
+ }
11529
+ function pathContainsValidationPath(prefix, path7) {
11530
+ if (prefix.length === 0) return true;
11531
+ return path7 === prefix || path7.startsWith(`${prefix}/`);
11532
+ }
11266
11533
  function hasMarkdownExtension(path7) {
11267
11534
  const lastDot = path7.lastIndexOf(".");
11268
11535
  if (lastDot < 0) return false;
@@ -11315,11 +11582,12 @@ async function markdownCommand(options) {
11315
11582
  validationConfig.paths,
11316
11583
  VALIDATION_PATH_TOOL_SUBSECTIONS.MARKDOWN
11317
11584
  );
11318
- const targetResolutions = files && files.length > 0 ? files.map((filePath) => resolveMarkdownValidationTarget(filePath)) : void 0;
11585
+ const targetResolutions = files && files.length > 0 ? files.map((filePath) => resolveMarkdownValidationTarget(markdownValidationOperandPath(cwd, filePath))) : void 0;
11586
+ const explicitTargets = targetResolutions === void 0 ? void 0 : targetResolutions.map((resolution) => resolution.target).filter((target) => target !== void 0).flatMap((target) => markdownTargetsForValidationPathFilter(cwd, target, pathFilter));
11319
11587
  const unfilteredTargets = targetResolutions === void 0 ? getDefaultDirectories(cwd).map((path7) => ({
11320
11588
  kind: MARKDOWN_VALIDATION_TARGET_KIND.DIRECTORY,
11321
11589
  path: path7
11322
- })) : targetResolutions.map((resolution) => resolution.target).filter((target) => target !== void 0);
11590
+ })) : explicitTargets ?? [];
11323
11591
  const targets = unfilteredTargets.filter(
11324
11592
  (target) => pathPassesValidationFilter(relative4(cwd, target.path), pathFilter)
11325
11593
  );
@@ -11335,11 +11603,22 @@ async function markdownCommand(options) {
11335
11603
  }
11336
11604
  const result = await validateMarkdown({
11337
11605
  targets,
11338
- projectRoot: cwd
11606
+ projectRoot: cwd,
11607
+ validationPathExcludes: validationPathFilterExcludes(pathFilter)
11339
11608
  });
11340
11609
  const durationMs = Date.now() - startTime;
11341
11610
  return formatMarkdownResult(result, skippedOutput, quiet, durationMs);
11342
11611
  }
11612
+ function markdownValidationOperandPath(productDir, filePath) {
11613
+ return isAbsolute4(filePath) ? filePath : join26(productDir, filePath);
11614
+ }
11615
+ function markdownTargetsForValidationPathFilter(productDir, target, pathFilter) {
11616
+ const relativePath = relative4(productDir, target.path);
11617
+ if (target.kind === MARKDOWN_VALIDATION_TARGET_KIND.FILE) {
11618
+ return pathPassesValidationFilter(relativePath, pathFilter) ? [target] : [];
11619
+ }
11620
+ return validationPathFilterIntersections(relativePath, pathFilter).map((path7) => resolveMarkdownValidationTarget(markdownValidationOperandPath(productDir, path7)).target).filter((filteredTarget) => filteredTarget !== void 0);
11621
+ }
11343
11622
  function formatSkippedFileScope(target) {
11344
11623
  return `${MARKDOWN_COMMAND_OUTPUT.SKIPPED_FILE_SCOPE_PREFIX}: ${target.path} (${target.reason})`;
11345
11624
  }
@@ -12233,7 +12512,7 @@ var ParseErrorCode;
12233
12512
 
12234
12513
  // src/validation/config/scope.ts
12235
12514
  import { existsSync as existsSync3, readdirSync, readFileSync as readFileSync3 } from "fs";
12236
- import { isAbsolute as isAbsolute4, join as join26, relative as relative5, resolve as resolve8 } from "path";
12515
+ import { isAbsolute as isAbsolute5, join as join27, relative as relative5, resolve as resolve8 } from "path";
12237
12516
  var TSCONFIG_FILES = {
12238
12517
  full: "tsconfig.json",
12239
12518
  production: "tsconfig.production.json"
@@ -12270,7 +12549,7 @@ var EXPLICIT_TYPESCRIPT_SCOPE_TARGET_KIND = {
12270
12549
  FILE: "file"
12271
12550
  };
12272
12551
  function resolveProjectPath(projectRoot, path7) {
12273
- return isAbsolute4(path7) ? path7 : join26(projectRoot, path7);
12552
+ return isAbsolute5(path7) ? path7 : join27(projectRoot, path7);
12274
12553
  }
12275
12554
  function parseTypeScriptConfig(configPath, deps = defaultScopeDeps) {
12276
12555
  try {
@@ -12309,12 +12588,12 @@ function hasTypeScriptFilesRecursive(dirPath, maxDepth = 2, deps = defaultScopeD
12309
12588
  try {
12310
12589
  const items = deps.readdirSync(dirPath, { withFileTypes: true });
12311
12590
  const hasDirectTsFiles = items.some(
12312
- (item) => item.isFile() && pathHasTypeScriptSourceExtension(item.name) && pathPassesTypeScriptFileDiscoveryExcludes(join26(dirPath, item.name), options)
12591
+ (item) => item.isFile() && pathHasTypeScriptSourceExtension(item.name) && pathPassesTypeScriptFileDiscoveryExcludes(join27(dirPath, item.name), options)
12313
12592
  );
12314
12593
  if (hasDirectTsFiles) return true;
12315
12594
  const subdirs = items.filter((item) => item.isDirectory() && !item.name.startsWith("."));
12316
12595
  for (const subdir of subdirs.slice(0, 5)) {
12317
- if (hasTypeScriptFilesRecursive(join26(dirPath, subdir.name), maxDepth - 1, deps, options)) {
12596
+ if (hasTypeScriptFilesRecursive(join27(dirPath, subdir.name), maxDepth - 1, deps, options)) {
12318
12597
  return true;
12319
12598
  }
12320
12599
  }
@@ -12351,7 +12630,7 @@ function directoryContributesTypeScriptScope(dir, config, projectRoot, deps) {
12351
12630
  return false;
12352
12631
  }
12353
12632
  try {
12354
- return hasTypeScriptFilesRecursive(join26(projectRoot, dir), 2, deps, {
12633
+ return hasTypeScriptFilesRecursive(join27(projectRoot, dir), 2, deps, {
12355
12634
  excludePatterns: config.exclude,
12356
12635
  projectRoot
12357
12636
  });
@@ -12580,13 +12859,13 @@ function normalizeActiveIncludePattern(pattern, projectRoot, deps) {
12580
12859
  function filterActiveIncludePatterns(patterns, excludePatterns, projectRoot, deps) {
12581
12860
  return patterns.map((pattern) => normalizeActiveIncludePattern(pattern, projectRoot, deps)).filter((pattern) => typeScriptScopePatternTargetsTypeScriptSource(pattern)).filter((pattern) => {
12582
12861
  const topLevelDir = getLiteralTopLevelPatternDirectory(pattern);
12583
- return topLevelDir === null || deps.existsSync(join26(projectRoot, topLevelDir));
12862
+ return topLevelDir === null || deps.existsSync(join27(projectRoot, topLevelDir));
12584
12863
  }).filter((pattern) => pathPassesValidationFilter(pattern, { exclude: excludePatterns }));
12585
12864
  }
12586
12865
  function getValidationDirectories(scope2, projectRoot, deps = defaultScopeDeps) {
12587
12866
  const config = resolveTypeScriptConfig(scope2, projectRoot, deps);
12588
12867
  const configDirectories = getTopLevelDirectoriesWithTypeScript(config, projectRoot, deps);
12589
- const existingDirectories = configDirectories.filter((dir) => deps.existsSync(join26(projectRoot, dir)));
12868
+ const existingDirectories = configDirectories.filter((dir) => deps.existsSync(join27(projectRoot, dir)));
12590
12869
  return existingDirectories;
12591
12870
  }
12592
12871
  function getTypeScriptScope(scope2, projectRoot, deps = defaultScopeDeps) {
@@ -12607,13 +12886,13 @@ function pathPassesTypeScriptScope(path7, scopeConfig) {
12607
12886
  return included && !excluded;
12608
12887
  }
12609
12888
  function pathStaysInsideTypeScriptScopeRoot(projectRoot, path7) {
12610
- const resolvedPath = isAbsolute4(path7) ? resolve8(path7) : resolve8(projectRoot, path7);
12889
+ const resolvedPath = isAbsolute5(path7) ? resolve8(path7) : resolve8(projectRoot, path7);
12611
12890
  const relativePath = relative5(projectRoot, resolvedPath);
12612
12891
  const segments = normalizeTypeScriptScopePath(relativePath).split(PATH_SEGMENT_SEPARATOR3);
12613
- return relativePath.length === 0 || !segments.includes("..") && !isAbsolute4(relativePath);
12892
+ return relativePath.length === 0 || !segments.includes("..") && !isAbsolute5(relativePath);
12614
12893
  }
12615
12894
  function toProjectRelativeTypeScriptScopePath(projectRoot, path7) {
12616
- const resolvedPath = isAbsolute4(path7) ? resolve8(path7) : resolve8(projectRoot, path7);
12895
+ const resolvedPath = isAbsolute5(path7) ? resolve8(path7) : resolve8(projectRoot, path7);
12617
12896
  const relativePath = relative5(projectRoot, resolvedPath);
12618
12897
  return relativePath.length === 0 ? TYPESCRIPT_SCOPE_PROJECT_ROOT : normalizeTypeScriptScopePath(relativePath);
12619
12898
  }
@@ -12632,7 +12911,10 @@ function filterExplicitTypeScriptScopeTargets(filter, deps = defaultScopeDeps) {
12632
12911
  if (paths === void 0) {
12633
12912
  return void 0;
12634
12913
  }
12635
- return paths.filter((path7) => pathStaysInsideTypeScriptScopeRoot(projectRoot, path7)).map((path7) => toExplicitTypeScriptScopeTarget(projectRoot, path7, deps)).filter((target) => !requireExistingPaths || explicitTypeScriptScopeTargetExists(projectRoot, target, deps)).filter((target) => explicitTypeScriptScopeTargetPassesSourceKind(target)).filter((target) => pathPassesValidationFilter(target.path, validationPathFilter)).filter((target) => explicitTypeScriptScopeTargetPassesScope(target, scopeConfig));
12914
+ return paths.filter((path7) => pathStaysInsideTypeScriptScopeRoot(projectRoot, path7)).map((path7) => toExplicitTypeScriptScopeTarget(projectRoot, path7, deps)).filter((target) => !requireExistingPaths || explicitTypeScriptScopeTargetExists(projectRoot, target, deps)).filter((target) => explicitTypeScriptScopeTargetPassesSourceKind(target)).filter((target) => explicitTypeScriptScopeTargetIntersectsValidationPathFilter(target, validationPathFilter)).filter((target) => explicitTypeScriptScopeTargetPassesScope(target, scopeConfig));
12915
+ }
12916
+ function explicitTypeScriptScopeTargetIntersectsValidationPathFilter(target, validationPathFilter) {
12917
+ return target.kind === EXPLICIT_TYPESCRIPT_SCOPE_TARGET_KIND.DIRECTORY ? pathIntersectsValidationFilter(target.path, validationPathFilter) : pathPassesValidationFilter(target.path, validationPathFilter);
12636
12918
  }
12637
12919
  function explicitTypeScriptScopeTargetPassesSourceKind(target) {
12638
12920
  return target.kind === EXPLICIT_TYPESCRIPT_SCOPE_TARGET_KIND.DIRECTORY || pathHasTypeScriptSourceExtension(target.path);
@@ -12653,10 +12935,12 @@ function explicitTypeScriptScopeTargetPassesScope(target, scopeConfig) {
12653
12935
  if (typeScriptSourcePatterns.length > 0) {
12654
12936
  return typeScriptSourcePatterns.some((pattern) => typeScriptScopePatternIntersectsDirectory(pattern, target.path));
12655
12937
  }
12656
- return pathPassesTypeScriptScope(join26(target.path, TYPESCRIPT_SCOPE_DIRECTORY_PROBE_FILENAME), scopeConfig);
12938
+ return scopeConfig.directories.some(
12939
+ (directory) => pathMatchesLiteralPrefix(directory, target.path) || pathMatchesLiteralPrefix(target.path, directory)
12940
+ ) || pathPassesTypeScriptScope(join27(target.path, TYPESCRIPT_SCOPE_DIRECTORY_PROBE_FILENAME), scopeConfig);
12657
12941
  }
12658
12942
  function directoryPassesTypeScriptExcludes(directory, scopeConfig) {
12659
- const probePath = join26(directory, TYPESCRIPT_SCOPE_DIRECTORY_PROBE_FILENAME);
12943
+ const probePath = join27(directory, TYPESCRIPT_SCOPE_DIRECTORY_PROBE_FILENAME);
12660
12944
  return !scopeConfig.excludePatterns.some(
12661
12945
  (pattern) => typeScriptScopePatternCoversDirectorySourceSet(pattern, directory) || pathMatchesTypeScriptPattern(probePath, pattern)
12662
12946
  );
@@ -12683,6 +12967,15 @@ function constrainTypeScriptScopeToExplicitTargets(scopeConfig, targets) {
12683
12967
  const retainedDirectoryFilePatterns = scopeConfig.filePatterns.filter(
12684
12968
  (pattern) => !typeScriptScopePatternHasGlob(pattern) && pathHasTypeScriptSourceExtension(pattern) && retainedDirectories.some((directory) => pathMatchesLiteralPrefix(pattern, directory))
12685
12969
  );
12970
+ const retainedDirectoryPatterns = scopeConfig.filePatterns.filter(
12971
+ (pattern) => !typeScriptScopePatternHasGlob(pattern) && !pathHasTypeScriptSourceExtension(pattern) && retainedDirectories.some((directory) => pathMatchesLiteralPrefix(pattern, directory))
12972
+ ).map((pattern) => typeScriptLiteralDirectoryPattern(pattern));
12973
+ const retainedDirectoryScopePatterns = retainedDirectories.filter(
12974
+ (directory) => scopeConfig.filePatterns.some(
12975
+ (pattern) => !typeScriptScopePatternHasGlob(pattern) && !pathHasTypeScriptSourceExtension(pattern) && pathMatchesLiteralPrefix(directory, pattern)
12976
+ )
12977
+ ).map((directory) => typeScriptLiteralDirectoryPattern(directory));
12978
+ const retainedDirectoryOperandPatterns = scopeConfig.filePatterns.length === 0 ? retainedDirectories.map(typeScriptLiteralDirectoryPattern) : [];
12686
12979
  const explicitFileTargets = targets.filter((target) => target.kind === EXPLICIT_TYPESCRIPT_SCOPE_TARGET_KIND.FILE).map((target) => target.path).filter(
12687
12980
  (path7) => !retainedDirectories.some(
12688
12981
  (directory) => path7 === directory || path7.startsWith(`${directory}${PATH_SEGMENT_SEPARATOR3}`)
@@ -12695,12 +12988,20 @@ function constrainTypeScriptScopeToExplicitTargets(scopeConfig, targets) {
12695
12988
  ...scopeConfig,
12696
12989
  directories: retainedDirectories,
12697
12990
  filePatterns: [
12698
- ...scopedFilePatternsForDirectoryTargets,
12699
- ...retainedDirectoryFilePatterns,
12700
- ...uncoveredExplicitFileTargets
12991
+ .../* @__PURE__ */ new Set([
12992
+ ...scopedFilePatternsForDirectoryTargets,
12993
+ ...retainedDirectoryFilePatterns,
12994
+ ...retainedDirectoryPatterns,
12995
+ ...retainedDirectoryScopePatterns,
12996
+ ...retainedDirectoryOperandPatterns,
12997
+ ...uncoveredExplicitFileTargets
12998
+ ])
12701
12999
  ]
12702
13000
  };
12703
13001
  }
13002
+ function typeScriptLiteralDirectoryPattern(pattern) {
13003
+ return pattern === TYPESCRIPT_SCOPE_PROJECT_ROOT ? TYPESCRIPT_SCOPE_DIRECTORY_PATTERN_SUFFIX.slice(1) : `${pattern}${TYPESCRIPT_SCOPE_DIRECTORY_PATTERN_SUFFIX}`;
13004
+ }
12704
13005
  function resolveTypeScriptValidationScope(filter, deps = defaultScopeDeps) {
12705
13006
  const scopeConfig = applyValidationPathFilterToScope(
12706
13007
  getTypeScriptScope(filter.scope, filter.projectRoot, deps),
@@ -12847,10 +13148,10 @@ function resolvedModulePath(resolvedPath) {
12847
13148
  return resolvedPath;
12848
13149
  }
12849
13150
  }
12850
- function nearestPackageRoot(filePath, existsSync8) {
13151
+ function nearestPackageRoot(filePath, existsSync9) {
12851
13152
  let currentDirectory = path6.dirname(filePath);
12852
13153
  for (; ; ) {
12853
- if (existsSync8(path6.join(currentDirectory, PACKAGE_MANIFEST_FILENAME))) {
13154
+ if (existsSync9(path6.join(currentDirectory, PACKAGE_MANIFEST_FILENAME))) {
12854
13155
  return currentDirectory;
12855
13156
  }
12856
13157
  const parentDirectory = path6.dirname(currentDirectory);
@@ -12860,9 +13161,9 @@ function nearestPackageRoot(filePath, existsSync8) {
12860
13161
  currentDirectory = parentDirectory;
12861
13162
  }
12862
13163
  }
12863
- function bundledToolPath(resolvedPath, existsSync8) {
13164
+ function bundledToolPath(resolvedPath, existsSync9) {
12864
13165
  const bundledFilePath = resolvedModulePath(resolvedPath);
12865
- return nearestPackageRoot(bundledFilePath, existsSync8) ?? path6.dirname(bundledFilePath);
13166
+ return nearestPackageRoot(bundledFilePath, existsSync9) ?? path6.dirname(bundledFilePath);
12866
13167
  }
12867
13168
  async function discoverTool(tool, options = {}) {
12868
13169
  const { projectRoot = CONFIG_PROCESS_CWD.read(), deps = defaultToolDiscoveryDeps } = options;
@@ -12919,7 +13220,7 @@ import {
12919
13220
  cruise as dependencyCruiser
12920
13221
  } from "dependency-cruiser";
12921
13222
  import extractTypeScriptConfig from "dependency-cruiser/config-utl/extract-ts-config";
12922
- import { join as join27 } from "path";
13223
+ import { join as join28 } from "path";
12923
13224
  var DEPENDENCY_CRUISER_MODULE_SYSTEMS = ["es6", "cjs"];
12924
13225
  var DEPENDENCY_CRUISER_TYPESCRIPT_SOURCE_GLOB_SUFFIXES = [...TYPESCRIPT_FALLBACK_INCLUDE_PATTERNS];
12925
13226
  var DEPENDENCY_CRUISER_TYPESCRIPT_SOURCE_PATTERN = String.raw`(?<!\.d)\.(?:[cm]?ts|tsx)$`;
@@ -13159,7 +13460,7 @@ async function validateCircularDependencies(scope2, typescriptScope, projectRoot
13159
13460
  if (analyzeSourcePatterns.length === 0) {
13160
13461
  return { success: true };
13161
13462
  }
13162
- const tsConfigFile = join27(projectRoot, TSCONFIG_FILES[scope2]);
13463
+ const tsConfigFile = join28(projectRoot, TSCONFIG_FILES[scope2]);
13163
13464
  const result = await deps.dependencyCruiser(
13164
13465
  analyzeSourcePatterns,
13165
13466
  buildDependencyCruiserOptions(typescriptScope, projectRoot, tsConfigFile),
@@ -13281,7 +13582,7 @@ async function circularCommand(options, deps = defaultCircularCommandDeps) {
13281
13582
  // src/validation/steps/knip.ts
13282
13583
  import { existsSync as existsSync4 } from "fs";
13283
13584
  import { mkdir as mkdir5, mkdtemp as mkdtemp2, rm as rm2, writeFile as writeFile3 } from "fs/promises";
13284
- import { isAbsolute as isAbsolute5, join as join28 } from "path";
13585
+ import { isAbsolute as isAbsolute6, join as join29 } from "path";
13285
13586
  var defaultKnipProcessRunner = lifecycleProcessRunner;
13286
13587
  var KNIP_COMMAND_TOKENS = {
13287
13588
  COMMAND: "knip",
@@ -13314,7 +13615,7 @@ async function validateKnip2(context, runner = defaultKnipProcessRunner, deps =
13314
13615
  }
13315
13616
  async function runKnipSubprocess(projectRoot, typescriptScope, runner, deps) {
13316
13617
  const scopedTsconfig = typescriptScope.filteredByValidationPaths ? await createScopedKnipTsconfig(projectRoot, typescriptScope, deps) : void 0;
13317
- const localBin = join28(projectRoot, "node_modules", ".bin", "knip");
13618
+ const localBin = join29(projectRoot, "node_modules", ".bin", "knip");
13318
13619
  const binary = deps.existsSync(localBin) ? localBin : KNIP_COMMAND_TOKENS.NPX_COMMAND;
13319
13620
  const baseArgs = scopedTsconfig === void 0 ? [] : [
13320
13621
  KNIP_COMMAND_TOKENS.USE_TSCONFIG_FILES_FLAG,
@@ -13344,13 +13645,13 @@ async function runKnipSubprocess(projectRoot, typescriptScope, runner, deps) {
13344
13645
  knipProcess.stderr?.on("data", (data) => {
13345
13646
  knipError += data.toString();
13346
13647
  });
13347
- return new Promise((resolve10) => {
13648
+ return new Promise((resolve11) => {
13348
13649
  const resolveAfterCleanup = (result) => {
13349
13650
  if (resultResolved) {
13350
13651
  return;
13351
13652
  }
13352
13653
  resultResolved = true;
13353
- void cleanupOnce().finally(() => resolve10(result));
13654
+ void cleanupOnce().finally(() => resolve11(result));
13354
13655
  };
13355
13656
  knipProcess.on("close", (code) => {
13356
13657
  if (code === 0) {
@@ -13369,11 +13670,11 @@ async function runKnipSubprocess(projectRoot, typescriptScope, runner, deps) {
13369
13670
  });
13370
13671
  }
13371
13672
  async function createScopedKnipTsconfig(projectRoot, typescriptScope, deps) {
13372
- const tempParentDir = join28(projectRoot, ...TEMPORARY_TSCONFIG_PARENT_SEGMENTS);
13673
+ const tempParentDir = join29(projectRoot, ...TEMPORARY_TSCONFIG_PARENT_SEGMENTS);
13373
13674
  await deps.mkdir(tempParentDir, { recursive: true });
13374
- const tempDir = await deps.mkdtemp(join28(tempParentDir, "validate-knip-"));
13375
- const configPath = join28(tempDir, TSCONFIG_FILES.full);
13376
- const toProjectPathPattern = (pattern) => isAbsolute5(pattern) ? pattern : join28(projectRoot, pattern);
13675
+ const tempDir = await deps.mkdtemp(join29(tempParentDir, "validate-knip-"));
13676
+ const configPath = join29(tempDir, TSCONFIG_FILES.full);
13677
+ const toProjectPathPattern = (pattern) => isAbsolute6(pattern) ? pattern : join29(projectRoot, pattern);
13377
13678
  const project = [
13378
13679
  ...typescriptScope.directories.flatMap(
13379
13680
  (directory) => TYPESCRIPT_FALLBACK_INCLUDE_PATTERNS.map((pattern) => `${directory}/${pattern}`)
@@ -13381,7 +13682,7 @@ async function createScopedKnipTsconfig(projectRoot, typescriptScope, deps) {
13381
13682
  ...typescriptScope.filePatterns
13382
13683
  ];
13383
13684
  const config = {
13384
- extends: join28(projectRoot, TSCONFIG_FILES.full),
13685
+ extends: join29(projectRoot, TSCONFIG_FILES.full),
13385
13686
  include: project.map(toProjectPathPattern),
13386
13687
  exclude: typescriptScope.excludePatterns.map(toProjectPathPattern)
13387
13688
  };
@@ -13448,12 +13749,12 @@ async function knipCommand(options, deps = defaultKnipCommandDeps) {
13448
13749
 
13449
13750
  // src/validation/steps/eslint.ts
13450
13751
  import { existsSync as existsSync6 } from "fs";
13451
- import { join as join30 } from "path";
13752
+ import { join as join31 } from "path";
13452
13753
 
13453
13754
  // src/validation/lint-policy.ts
13454
13755
  import { execFileSync as execFileSync2 } from "child_process";
13455
- import { existsSync as existsSync5, readdirSync as readdirSync2, readFileSync as readFileSync4, statSync as statSync2 } from "fs";
13456
- import { join as join29 } from "path";
13756
+ import { existsSync as existsSync5, readdirSync as readdirSync2, readFileSync as readFileSync4, statSync as statSync3 } from "fs";
13757
+ import { join as join30 } from "path";
13457
13758
 
13458
13759
  // src/validation/lint-policy-constants.ts
13459
13760
  var LINT_POLICY_MANIFESTS = {
@@ -13501,17 +13802,17 @@ function isSpecTreeNodePath(entry) {
13501
13802
  }
13502
13803
  function readManifest2(productDir, file, key) {
13503
13804
  return parseLintPolicyManifest(
13504
- readFileSync4(join29(productDir, file), "utf-8"),
13805
+ readFileSync4(join30(productDir, file), "utf-8"),
13505
13806
  file,
13506
13807
  key
13507
13808
  );
13508
13809
  }
13509
13810
  function manifestExists(productDir, file) {
13510
- return existsSync5(join29(productDir, file));
13811
+ return existsSync5(join30(productDir, file));
13511
13812
  }
13512
13813
  function findDeprecatedSpecNodePath(productDir) {
13513
13814
  function visit2(relativeDirectory) {
13514
- const absoluteDirectory = join29(productDir, relativeDirectory);
13815
+ const absoluteDirectory = join30(productDir, relativeDirectory);
13515
13816
  for (const entry of readdirSync2(absoluteDirectory, { withFileTypes: true })) {
13516
13817
  if (!entry.isDirectory()) {
13517
13818
  continue;
@@ -13527,7 +13828,7 @@ function findDeprecatedSpecNodePath(productDir) {
13527
13828
  }
13528
13829
  return void 0;
13529
13830
  }
13530
- const specTreeRootPath = join29(productDir, SPEC_TREE_ROOT);
13831
+ const specTreeRootPath = join30(productDir, SPEC_TREE_ROOT);
13531
13832
  if (!existsSync5(specTreeRootPath)) {
13532
13833
  return void 0;
13533
13834
  }
@@ -13553,8 +13854,8 @@ function assertManifestEntries(productDir, file, entries, suffixPredicate, suffi
13553
13854
  if (!suffixPredicate(entry)) {
13554
13855
  throw new Error(`${file} entry must ${suffixDescription}: ${entry}`);
13555
13856
  }
13556
- const absoluteEntry = join29(productDir, entry);
13557
- if (!existsSync5(absoluteEntry) || !statSync2(absoluteEntry).isDirectory()) {
13857
+ const absoluteEntry = join30(productDir, entry);
13858
+ if (!existsSync5(absoluteEntry) || !statSync3(absoluteEntry).isDirectory()) {
13558
13859
  throw new Error(`${file} entry does not exist as a directory: ${entry}`);
13559
13860
  }
13560
13861
  }
@@ -13715,7 +14016,14 @@ var ESLINT_COMMAND_TOKENS = {
13715
14016
  };
13716
14017
  var ESLINT_LOCAL_BIN_SEGMENTS = ["node_modules", ".bin", ESLINT_COMMAND_TOKENS.COMMAND];
13717
14018
  function buildEslintArgs(context) {
13718
- const { validatedFiles, mode, configFile = DEFAULT_ESLINT_CONFIG_FILE, scope: scope2, scopeConfig } = context;
14019
+ const {
14020
+ validatedFiles,
14021
+ validatedFileIgnorePatterns = [],
14022
+ mode,
14023
+ configFile = DEFAULT_ESLINT_CONFIG_FILE,
14024
+ scope: scope2,
14025
+ scopeConfig
14026
+ } = context;
13719
14027
  const fixArg = mode === EXECUTION_MODES.WRITE ? [ESLINT_COMMAND_TOKENS.FIX_FLAG] : [];
13720
14028
  if (validatedFiles && validatedFiles.length > 0) {
13721
14029
  return [
@@ -13723,6 +14031,7 @@ function buildEslintArgs(context) {
13723
14031
  ESLINT_COMMAND_TOKENS.CONFIG_FLAG,
13724
14032
  configFile,
13725
14033
  ...fixArg,
14034
+ ...buildIgnorePatternArgs(validatedFileIgnorePatterns),
13726
14035
  ESLINT_COMMAND_TOKENS.FILE_SEPARATOR,
13727
14036
  ...validatedFiles
13728
14037
  ];
@@ -13763,13 +14072,14 @@ async function validateESLint(context, runner = defaultEslintProcessRunner, outp
13763
14072
  }
13764
14073
  const eslintArgs = buildEslintArgs({
13765
14074
  validatedFiles,
14075
+ validatedFileIgnorePatterns: context.validatedFileIgnorePatterns,
13766
14076
  mode,
13767
14077
  configFile: eslintConfigFile,
13768
14078
  scope: scope2,
13769
14079
  scopeConfig: context.scopeConfig
13770
14080
  });
13771
- return new Promise((resolve10) => {
13772
- const localBin = join30(projectRoot, ...ESLINT_LOCAL_BIN_SEGMENTS);
14081
+ return new Promise((resolve11) => {
14082
+ const localBin = join31(projectRoot, ...ESLINT_LOCAL_BIN_SEGMENTS);
13773
14083
  const binary = existsSync6(localBin) ? localBin : "npx";
13774
14084
  const spawnArgs = binary === "npx" ? eslintArgs : eslintArgs.slice(1);
13775
14085
  const eslintProcess = spawnManagedSubprocess(runner, binary, spawnArgs, {
@@ -13778,13 +14088,13 @@ async function validateESLint(context, runner = defaultEslintProcessRunner, outp
13778
14088
  forwardValidationSubprocessOutput(eslintProcess, outputStreams);
13779
14089
  eslintProcess.on(VALIDATION_SUBPROCESS_EVENTS.CLOSE, (code) => {
13780
14090
  if (code === 0) {
13781
- resolve10({ success: true });
14091
+ resolve11({ success: true });
13782
14092
  } else {
13783
- resolve10({ success: false, error: `ESLint exited with code ${code}` });
14093
+ resolve11({ success: false, error: `ESLint exited with code ${code}` });
13784
14094
  }
13785
14095
  });
13786
14096
  eslintProcess.on(VALIDATION_SUBPROCESS_EVENTS.ERROR, (error) => {
13787
- resolve10({ success: false, error: error.message });
14097
+ resolve11({ success: false, error: error.message });
13788
14098
  });
13789
14099
  });
13790
14100
  }
@@ -13832,7 +14142,12 @@ async function lintCommand(options) {
13832
14142
  getTypeScriptScope(scope2, cwd),
13833
14143
  validationPathFilter
13834
14144
  );
13835
- const validatedFiles = files?.map((file) => toProjectRelativeValidationPath(cwd, file)).filter((file) => pathPassesValidationFilter(file, validationPathFilter));
14145
+ const validatedFiles = files?.flatMap(
14146
+ (file) => validationPathFilterIntersections(
14147
+ toProjectRelativeValidationPath(cwd, file),
14148
+ validationPathFilter
14149
+ ).map(formatLintValidationOperand)
14150
+ );
13836
14151
  if (scopeConfig.filteredByValidationPathNoMatches || files !== void 0 && files.length > 0 && validatedFiles?.length === 0) {
13837
14152
  return {
13838
14153
  exitCode: 0,
@@ -13852,6 +14167,7 @@ async function lintCommand(options) {
13852
14167
  mode: fix ? "write" : "read",
13853
14168
  enabledValidations: { ESLINT: true },
13854
14169
  validatedFiles,
14170
+ validatedFileIgnorePatterns: files === void 0 ? void 0 : validationPathFilterExcludes(validationPathFilter),
13855
14171
  isFileSpecificMode: Boolean(validatedFiles && validatedFiles.length > 0),
13856
14172
  eslintConfigFile
13857
14173
  };
@@ -13859,6 +14175,9 @@ async function lintCommand(options) {
13859
14175
  const durationMs = Date.now() - startTime;
13860
14176
  return formatLintResult(result, quiet, durationMs);
13861
14177
  }
14178
+ function formatLintValidationOperand(path7) {
14179
+ return path7.length === 0 ? "." : path7;
14180
+ }
13862
14181
  function formatLintResult(result, quiet, durationMs) {
13863
14182
  if (result.skipped) {
13864
14183
  const output2 = quiet ? "" : VALIDATION_PATHS_NO_TARGETS_MESSAGE;
@@ -13874,7 +14193,7 @@ function formatLintResult(result, quiet, durationMs) {
13874
14193
 
13875
14194
  // src/validation/literal/index.ts
13876
14195
  import { readFile as readFile9 } from "fs/promises";
13877
- import { isAbsolute as isAbsolute6, relative as relative7, resolve as resolve9 } from "path";
14196
+ import { isAbsolute as isAbsolute7, relative as relative7, resolve as resolve9 } from "path";
13878
14197
 
13879
14198
  // src/lib/file-inclusion/predicates/ignore-source.ts
13880
14199
  var IGNORE_SOURCE_LAYER = "ignore-source";
@@ -13909,7 +14228,7 @@ var ignoreSourceLayer = makeLayer(
13909
14228
 
13910
14229
  // src/lib/file-inclusion/pipeline.ts
13911
14230
  import { readdir as readdir8 } from "fs/promises";
13912
- import { join as join31, relative as relative6, sep as sep2 } from "path";
14231
+ import { join as join32, relative as relative6, sep as sep2 } from "path";
13913
14232
  var EXPLICIT_OVERRIDE_LAYER = "explicit-override";
13914
14233
  function isNodeError4(err) {
13915
14234
  return err instanceof Error && "code" in err;
@@ -13927,10 +14246,10 @@ async function collectPaths(absoluteDir, projectRoot, result, artifactDirs) {
13927
14246
  for (const entry of dirEntries) {
13928
14247
  if (entry.isDirectory()) {
13929
14248
  if (artifactDirs.has(entry.name)) continue;
13930
- const absolutePath = join31(absoluteDir, entry.name);
14249
+ const absolutePath = join32(absoluteDir, entry.name);
13931
14250
  await collectPaths(absolutePath, projectRoot, result, artifactDirs);
13932
14251
  } else if (entry.isFile()) {
13933
- const absolutePath = join31(absoluteDir, entry.name);
14252
+ const absolutePath = join32(absoluteDir, entry.name);
13934
14253
  const rel = relative6(projectRoot, absolutePath);
13935
14254
  result.push(sep2 === "/" ? rel : rel.split(sep2).join("/"));
13936
14255
  }
@@ -14442,9 +14761,9 @@ function applyPathFilter2(entries, pathConfig) {
14442
14761
  }
14443
14762
  async function validateLiteralReuse(input) {
14444
14763
  const config = input.config ?? literalConfigDescriptor.defaults;
14445
- const request = input.files ? {
14764
+ const request = input.scopeConfig === void 0 && input.files ? {
14446
14765
  explicit: input.files.map((f) => {
14447
- const abs = isAbsolute6(f) ? f : resolve9(input.productDir, f);
14766
+ const abs = isAbsolute7(f) ? f : resolve9(input.productDir, f);
14448
14767
  return relative7(input.productDir, abs).split(/[\\/]/g).join("/");
14449
14768
  })
14450
14769
  } : { walkRoot: input.productDir };
@@ -14455,7 +14774,9 @@ async function validateLiteralReuse(input) {
14455
14774
  DEFAULT_SCOPE_CONFIG,
14456
14775
  EMPTY_IGNORE_READER
14457
14776
  );
14458
- const filtered = applyPathFilter2(scope2.included, input.pathConfig);
14777
+ const pathFiltered = applyPathFilter2(scope2.included, input.pathConfig);
14778
+ const literalScopeConfig = input.scopeConfig;
14779
+ const filtered = literalScopeConfig === void 0 ? pathFiltered : pathFiltered.filter((entry) => pathPassesTypeScriptScope(entry.path, literalScopeConfig));
14459
14780
  const candidateFiles = filtered.filter((entry) => isTypescriptSource(entry.path)).map((entry) => resolve9(input.productDir, entry.path));
14460
14781
  const collectOptions = {
14461
14782
  visitorKeys: defaultVisitorKeys,
@@ -14483,7 +14804,11 @@ async function validateLiteralReuse(input) {
14483
14804
  testOccurrencesByFile,
14484
14805
  allowlist: resolveAllowlist(config)
14485
14806
  });
14486
- return { findings, indexedOccurrencesByFile };
14807
+ return {
14808
+ findings,
14809
+ indexedOccurrencesByFile,
14810
+ filteredByValidationPathNoMatches: input.scopeConfig?.filteredByValidationPathNoMatches
14811
+ };
14487
14812
  }
14488
14813
  async function readSafe(path7) {
14489
14814
  try {
@@ -14518,6 +14843,9 @@ var LITERAL_EXIT_CODES = {
14518
14843
  var TYPESCRIPT_ABSENT_MESSAGE3 = formatTypeScriptAbsentSkipMessage(
14519
14844
  VALIDATION_STAGE_DISPLAY_NAMES.LITERAL
14520
14845
  );
14846
+ var VALIDATION_PATHS_NO_TARGETS_MESSAGE2 = formatValidationPathsNoTargetsSkipMessage(
14847
+ VALIDATION_STAGE_DISPLAY_NAMES.LITERAL
14848
+ );
14521
14849
  var LITERAL_DISABLED_MESSAGE = `\u23ED ${VALIDATION_SKIP_LABELS.VERB} ${VALIDATION_STAGE_DISPLAY_NAMES.LITERAL} (${VALIDATION_SKIP_LABELS.DISABLED_BY_PREFIX} validation.literal.enabled)`;
14522
14850
  var NO_PROBLEMS_MESSAGE = "Literal: \u2713 No problems";
14523
14851
  function formatNoProblemsOfKind(kind) {
@@ -14533,31 +14861,15 @@ async function literalCommand(options) {
14533
14861
  durationMs: Date.now() - start
14534
14862
  };
14535
14863
  }
14536
- let resolvedEnabled;
14537
- let resolvedLiteralConfig;
14538
- let resolvedPathConfig;
14539
- if (options.config === void 0) {
14540
- const loaded = await resolveConfig(options.cwd, [validationConfigDescriptor]);
14541
- if (!loaded.ok) {
14542
- return {
14543
- exitCode: LITERAL_EXIT_CODES.CONFIG_ERROR,
14544
- output: `Literal: \u2717 config error \u2014 ${loaded.error}`,
14545
- durationMs: Date.now() - start
14546
- };
14547
- }
14548
- const validationConfig = loaded.value[validationConfigDescriptor.section];
14549
- resolvedEnabled = validationConfig.literal.enabled;
14550
- resolvedLiteralConfig = validationConfig.literal.values;
14551
- resolvedPathConfig = validationPathFilterForTool(
14552
- validationConfig.paths,
14553
- VALIDATION_PATH_TOOL_SUBSECTIONS.LITERAL
14554
- );
14555
- } else {
14556
- resolvedEnabled = options.enabled ?? validationConfigDescriptor.defaults.literal.enabled;
14557
- resolvedLiteralConfig = options.config;
14558
- resolvedPathConfig = options.pathConfig ?? validationConfigDescriptor.defaults.paths;
14864
+ const resolved = await resolveLiteralCommandConfig(options);
14865
+ if (typeof resolved === "string") {
14866
+ return {
14867
+ exitCode: LITERAL_EXIT_CODES.CONFIG_ERROR,
14868
+ output: `Literal: \u2717 config error \u2014 ${resolved}`,
14869
+ durationMs: Date.now() - start
14870
+ };
14559
14871
  }
14560
- if (!resolvedEnabled) {
14872
+ if (!resolved.enabled) {
14561
14873
  return {
14562
14874
  exitCode: LITERAL_EXIT_CODES.OK,
14563
14875
  output: options.quiet ? "" : LITERAL_DISABLED_MESSAGE,
@@ -14566,10 +14878,17 @@ async function literalCommand(options) {
14566
14878
  }
14567
14879
  const result = await validateLiteralReuse({
14568
14880
  productDir: options.cwd,
14569
- files: options.files,
14570
- config: resolvedLiteralConfig,
14571
- pathConfig: resolvedPathConfig
14881
+ config: resolved.literalConfig,
14882
+ pathConfig: resolved.pathConfig,
14883
+ scopeConfig: resolveExplicitLiteralTypeScriptScope(options, resolved.pathConfig)
14572
14884
  });
14885
+ if (options.files !== void 0 && options.files.length > 0 && result.filteredByValidationPathNoMatches) {
14886
+ return {
14887
+ exitCode: LITERAL_EXIT_CODES.OK,
14888
+ output: options.quiet ? "" : VALIDATION_PATHS_NO_TARGETS_MESSAGE2,
14889
+ durationMs: Date.now() - start
14890
+ };
14891
+ }
14573
14892
  const filteredFindings = filterLiteralFindings(result.findings, options.kind);
14574
14893
  const totalProblems = countLiteralProblems(filteredFindings);
14575
14894
  const exitCode = totalProblems === 0 ? LITERAL_EXIT_CODES.OK : LITERAL_EXIT_CODES.FINDINGS;
@@ -14583,6 +14902,38 @@ async function literalCommand(options) {
14583
14902
  }
14584
14903
  return { exitCode, output, durationMs: Date.now() - start };
14585
14904
  }
14905
+ async function resolveLiteralCommandConfig(options) {
14906
+ if (options.config !== void 0) {
14907
+ return {
14908
+ enabled: options.enabled ?? validationConfigDescriptor.defaults.literal.enabled,
14909
+ literalConfig: options.config,
14910
+ pathConfig: options.pathConfig ?? validationConfigDescriptor.defaults.paths
14911
+ };
14912
+ }
14913
+ const loaded = await resolveConfig(options.cwd, [validationConfigDescriptor]);
14914
+ if (!loaded.ok) return loaded.error;
14915
+ const validationConfig = loaded.value[validationConfigDescriptor.section];
14916
+ return {
14917
+ enabled: validationConfig.literal.enabled,
14918
+ literalConfig: validationConfig.literal.values,
14919
+ pathConfig: validationPathFilterForTool(
14920
+ validationConfig.paths,
14921
+ VALIDATION_PATH_TOOL_SUBSECTIONS.LITERAL
14922
+ )
14923
+ };
14924
+ }
14925
+ function resolveExplicitLiteralTypeScriptScope(options, pathConfig) {
14926
+ if (options.files === void 0 || options.files.length === 0) {
14927
+ return void 0;
14928
+ }
14929
+ return resolveTypeScriptValidationScope({
14930
+ projectRoot: options.cwd,
14931
+ scope: options.scope ?? VALIDATION_SCOPES.FULL,
14932
+ paths: options.files,
14933
+ validationPathFilter: pathConfig,
14934
+ markExplicitPathsAsValidationFilter: true
14935
+ });
14936
+ }
14586
14937
  function filterLiteralFindings(findings, kind) {
14587
14938
  return {
14588
14939
  srcReuse: kind === LITERAL_PROBLEM_KIND.DUPE ? [] : sortReuseFindings(findings.srcReuse),
@@ -14706,7 +15057,7 @@ function formatLoc(loc) {
14706
15057
  // src/validation/steps/typescript.ts
14707
15058
  import { existsSync as existsSync7, mkdirSync, rmSync, writeFileSync } from "fs";
14708
15059
  import { mkdtemp as mkdtemp3 } from "fs/promises";
14709
- import { isAbsolute as isAbsolute7, join as join32 } from "path";
15060
+ import { isAbsolute as isAbsolute8, join as join33, relative as relative8 } from "path";
14710
15061
  var defaultTypeScriptProcessRunner = lifecycleProcessRunner;
14711
15062
  var defaultTypeScriptDeps = {
14712
15063
  mkdtemp: mkdtemp3,
@@ -14718,9 +15069,9 @@ var defaultTypeScriptDeps = {
14718
15069
  var TEMPORARY_TSCONFIG_COMPILER_OPTIONS = { noEmit: true };
14719
15070
  var TEMPORARY_TSCONFIG_DIR_PREFIX = "validate-ts-";
14720
15071
  async function createTemporaryTsconfigDir(projectRoot, deps) {
14721
- const parent = join32(projectRoot, ...TEMPORARY_TSCONFIG_PARENT_SEGMENTS);
15072
+ const parent = join33(projectRoot, ...TEMPORARY_TSCONFIG_PARENT_SEGMENTS);
14722
15073
  deps.mkdirSync(parent, { recursive: true });
14723
- return deps.mkdtemp(join32(parent, TEMPORARY_TSCONFIG_DIR_PREFIX));
15074
+ return deps.mkdtemp(join33(parent, TEMPORARY_TSCONFIG_DIR_PREFIX));
14724
15075
  }
14725
15076
  function buildTypeScriptArgs(context) {
14726
15077
  const { scope: scope2, configFile } = context;
@@ -14728,11 +15079,11 @@ function buildTypeScriptArgs(context) {
14728
15079
  }
14729
15080
  async function createFileSpecificTsconfig(scope2, files, projectRoot, deps = defaultTypeScriptDeps) {
14730
15081
  const tempDir = await createTemporaryTsconfigDir(projectRoot, deps);
14731
- const configPath = join32(tempDir, "tsconfig.json");
15082
+ const configPath = join33(tempDir, "tsconfig.json");
14732
15083
  const baseConfigFile = TSCONFIG_FILES[scope2];
14733
- const absoluteFiles = files.map((file) => isAbsolute7(file) ? file : join32(projectRoot, file));
15084
+ const absoluteFiles = files.map((file) => isAbsolute8(file) ? file : join33(projectRoot, file));
14734
15085
  const tempConfig = {
14735
- extends: join32(projectRoot, baseConfigFile),
15086
+ extends: join33(projectRoot, baseConfigFile),
14736
15087
  files: absoluteFiles,
14737
15088
  include: [],
14738
15089
  exclude: [],
@@ -14744,13 +15095,16 @@ async function createFileSpecificTsconfig(scope2, files, projectRoot, deps = def
14744
15095
  }
14745
15096
  async function createScopeFilteredTsconfig(scope2, projectRoot, scopeConfig, deps = defaultTypeScriptDeps) {
14746
15097
  const tempDir = await createTemporaryTsconfigDir(projectRoot, deps);
14747
- const configPath = join32(tempDir, "tsconfig.json");
15098
+ const configPath = join33(tempDir, "tsconfig.json");
14748
15099
  const baseConfigFile = TSCONFIG_FILES[scope2];
14749
- const toProjectPathPattern = (pattern) => isAbsolute7(pattern) ? pattern : join32(projectRoot, pattern);
15100
+ const toTemporaryConfigPathPattern = (pattern) => {
15101
+ const absolutePattern = isAbsolute8(pattern) ? pattern : join33(projectRoot, pattern);
15102
+ return relative8(tempDir, absolutePattern);
15103
+ };
14750
15104
  const tempConfig = {
14751
- extends: join32(projectRoot, baseConfigFile),
14752
- include: scopeConfig.filePatterns.map(toProjectPathPattern),
14753
- exclude: scopeConfig.excludePatterns.map(toProjectPathPattern),
15105
+ extends: join33(projectRoot, baseConfigFile),
15106
+ include: scopeConfig.filePatterns.map(toTemporaryConfigPathPattern),
15107
+ exclude: scopeConfig.excludePatterns.map(toTemporaryConfigPathPattern),
14754
15108
  compilerOptions: TEMPORARY_TSCONFIG_COMPILER_OPTIONS
14755
15109
  };
14756
15110
  deps.writeFileSync(configPath, JSON.stringify(tempConfig, null, 2));
@@ -14812,7 +15166,7 @@ async function validateTypeScript(context, options = {}) {
14812
15166
  );
14813
15167
  }
14814
15168
  function resolveProjectTscInvocation(projectRoot, deps, tscArgs) {
14815
- const tscBin = join32(projectRoot, "node_modules", ".bin", "tsc");
15169
+ const tscBin = join33(projectRoot, "node_modules", ".bin", "tsc");
14816
15170
  const tool = deps.existsSync(tscBin) ? tscBin : "npx";
14817
15171
  return {
14818
15172
  tool,
@@ -14821,7 +15175,7 @@ function resolveProjectTscInvocation(projectRoot, deps, tscArgs) {
14821
15175
  }
14822
15176
  function runTypeScriptInvocation(projectRoot, invocation, runner, outputStreams, cleanup = () => {
14823
15177
  }) {
14824
- return new Promise((resolve10) => {
15178
+ return new Promise((resolve11) => {
14825
15179
  const tscProcess = spawnManagedSubprocess(runner, invocation.tool, invocation.args, {
14826
15180
  cwd: projectRoot
14827
15181
  });
@@ -14829,14 +15183,14 @@ function runTypeScriptInvocation(projectRoot, invocation, runner, outputStreams,
14829
15183
  tscProcess.on(VALIDATION_SUBPROCESS_EVENTS.CLOSE, (code) => {
14830
15184
  cleanup();
14831
15185
  if (code === 0) {
14832
- resolve10({ success: true, skipped: false });
15186
+ resolve11({ success: true, skipped: false });
14833
15187
  } else {
14834
- resolve10({ success: false, error: `TypeScript exited with code ${code}` });
15188
+ resolve11({ success: false, error: `TypeScript exited with code ${code}` });
14835
15189
  }
14836
15190
  });
14837
15191
  tscProcess.on(VALIDATION_SUBPROCESS_EVENTS.ERROR, (error) => {
14838
15192
  cleanup();
14839
- resolve10({ success: false, error: error.message });
15193
+ resolve11({ success: false, error: error.message });
14840
15194
  });
14841
15195
  });
14842
15196
  }
@@ -14869,16 +15223,17 @@ async function typescriptCommand(options) {
14869
15223
  };
14870
15224
  }
14871
15225
  const validationConfig = loaded.value[validationConfigDescriptor.section];
14872
- const validationPathFilter = validationPathFilterForTool(
14873
- validationConfig.paths,
14874
- VALIDATION_PATH_TOOL_SUBSECTIONS.TYPESCRIPT
14875
- );
14876
- const scopeConfig = applyValidationPathFilterToScope(
14877
- getTypeScriptScope(scope2, cwd),
14878
- validationPathFilter
14879
- );
14880
- const filteredFiles = files?.map((file) => toProjectRelativeValidationPath(cwd, file)).filter((file) => pathPassesValidationFilter(file, validationPathFilter));
14881
- if (scopeConfig.filteredByValidationPathNoMatches || files !== void 0 && files.length > 0 && filteredFiles?.length === 0) {
15226
+ const scopeConfig = resolveTypeScriptValidationScope({
15227
+ projectRoot: cwd,
15228
+ scope: scope2,
15229
+ paths: files,
15230
+ validationPathFilter: validationPathFilterForTool(
15231
+ validationConfig.paths,
15232
+ VALIDATION_PATH_TOOL_SUBSECTIONS.TYPESCRIPT
15233
+ ),
15234
+ markExplicitPathsAsValidationFilter: true
15235
+ });
15236
+ if (scopeConfig.filteredByValidationPathNoMatches) {
14882
15237
  return {
14883
15238
  exitCode: 0,
14884
15239
  output: quiet ? "" : TYPESCRIPT_VALIDATION_MESSAGES.NO_VALIDATION_PATH_TARGETS,
@@ -14893,7 +15248,6 @@ async function typescriptCommand(options) {
14893
15248
  const result = await validateTypeScript({
14894
15249
  scope: scope2,
14895
15250
  projectRoot: cwd,
14896
- files: filteredFiles,
14897
15251
  scopeConfig
14898
15252
  });
14899
15253
  const durationMs = Date.now() - startTime;
@@ -14937,6 +15291,7 @@ async function runLiteralStage(context) {
14937
15291
  }
14938
15292
  return literalCommand({
14939
15293
  cwd: context.cwd,
15294
+ scope: context.scope,
14940
15295
  files: context.files,
14941
15296
  quiet: context.quiet,
14942
15297
  json: context.json
@@ -15066,7 +15421,7 @@ async function allCommand(options) {
15066
15421
  // src/validation/literal/allowlist-existing.ts
15067
15422
  import { randomBytes } from "crypto";
15068
15423
  import { rename as rename4, writeFile as writeFile4 } from "fs/promises";
15069
- import { dirname as dirname10, join as join33 } from "path";
15424
+ import { dirname as dirname10, join as join34 } from "path";
15070
15425
  var EXIT_OK = 0;
15071
15426
  var EXIT_ERROR = 1;
15072
15427
  var INCLUDE_FIELD = "include";
@@ -15086,7 +15441,7 @@ var productionWriter = {
15086
15441
  async write(filePath, content) {
15087
15442
  const dir = dirname10(filePath);
15088
15443
  const random = randomBytes(RANDOM_TOKEN_BYTES).toString("hex");
15089
- const tmpPath = join33(dir, `${TEMP_FILE_PREFIX}${random}${TEMP_FILE_SUFFIX}`);
15444
+ const tmpPath = join34(dir, `${TEMP_FILE_PREFIX}${random}${TEMP_FILE_SUFFIX}`);
15090
15445
  await writeFile4(tmpPath, content, "utf8");
15091
15446
  await rename4(tmpPath, filePath);
15092
15447
  }
@@ -15206,6 +15561,10 @@ var validationCliDefinition = {
15206
15561
  longFlag: "--help",
15207
15562
  shortFlag: "-h"
15208
15563
  },
15564
+ pathOperands: {
15565
+ optionalVariadic: "[paths...]",
15566
+ description: "Specific files/directories to validate"
15567
+ },
15209
15568
  diagnostics: {
15210
15569
  unknownSubcommand: {
15211
15570
  messageLabel: "unknown subcommand",
@@ -15214,6 +15573,11 @@ var validationCliDefinition = {
15214
15573
  unknownLiteralProblemKind: {
15215
15574
  messageLabel: "unknown problem kind",
15216
15575
  exitCode: 1
15576
+ },
15577
+ invalidPathOperand: {
15578
+ messageLabel: "invalid path operand",
15579
+ reason: "escapes product directory",
15580
+ exitCode: 1
15217
15581
  }
15218
15582
  }
15219
15583
  };
@@ -15269,7 +15633,50 @@ function emitValidationResult(result, io) {
15269
15633
  return io.exit(result.exitCode);
15270
15634
  }
15271
15635
  function addCommonOptions(cmd) {
15272
- return cmd.option("--scope <scope>", "Validation scope (full|production)", "full").option("--files <paths...>", "Specific files/directories to validate").option("--quiet", "Suppress progress output").option("--json", "Output results as JSON");
15636
+ const { pathOperands } = validationCliDefinition;
15637
+ 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");
15638
+ }
15639
+ function normalizeProductPathOperand(productDir, effectiveInvocationDir, operand) {
15640
+ const resolvedProductDir = canonicalExistingPath(resolve10(productDir));
15641
+ const resolvedInvocationDir = canonicalExistingPath(resolve10(effectiveInvocationDir));
15642
+ const absoluteOperand = canonicalExistingPath(resolve10(resolvedInvocationDir, operand));
15643
+ const relativeOperand = relative9(resolvedProductDir, absoluteOperand);
15644
+ if (relativeOperand === ".." || relativeOperand.startsWith(`..${sep3}`) || isAbsolute9(relativeOperand)) {
15645
+ return void 0;
15646
+ }
15647
+ return relativeOperand.length > 0 ? relativeOperand.replaceAll("\\", "/") : ".";
15648
+ }
15649
+ function canonicalExistingPath(path7) {
15650
+ return existsSync8(path7) ? realpathSync.native(path7) : path7;
15651
+ }
15652
+ function normalizePathOperands(productDir, effectiveInvocationDir, pathOperands) {
15653
+ if (pathOperands.length === 0) return void 0;
15654
+ const normalized = [];
15655
+ for (const operand of pathOperands) {
15656
+ const path7 = normalizeProductPathOperand(productDir, effectiveInvocationDir, operand);
15657
+ if (path7 === void 0) return void 0;
15658
+ normalized.push(path7);
15659
+ }
15660
+ return normalized;
15661
+ }
15662
+ function resolveValidationPaths(invocation, pathOperands) {
15663
+ const context = invocation.resolveProductContext();
15664
+ const files = normalizePathOperands(context.productDir, context.effectiveInvocationDir, pathOperands);
15665
+ if (pathOperands.length > 0 && files === void 0) {
15666
+ const { invalidPathOperand } = validationCliDefinition.diagnostics;
15667
+ const invalidOperand = pathOperands.find(
15668
+ (operand) => normalizeProductPathOperand(context.productDir, context.effectiveInvocationDir, operand) === void 0
15669
+ );
15670
+ invocation.io.writeStderr(
15671
+ `spx ${validationCliDefinition.domain.commandName}: ${invalidPathOperand.messageLabel}: ${sanitizeCliArgument(invalidOperand)} (${invalidPathOperand.reason})
15672
+ `
15673
+ );
15674
+ invocation.io.exit(invalidPathOperand.exitCode);
15675
+ }
15676
+ return {
15677
+ productDir: context.productDir,
15678
+ files
15679
+ };
15273
15680
  }
15274
15681
  function addValidationSubcommand(validationCmd, definition) {
15275
15682
  let subcommand = validationCmd.command(definition.commandName).description(definition.description);
@@ -15280,23 +15687,24 @@ function addValidationSubcommand(validationCmd, definition) {
15280
15687
  }
15281
15688
  function registerValidationCommands(validationCmd, invocation) {
15282
15689
  const { subcommands } = validationCliDefinition;
15283
- const productDir = () => invocation.resolveProductContext().productDir;
15284
- const tsCmd = addValidationSubcommand(validationCmd, subcommands.typescript).action(async (options) => {
15690
+ const tsCmd = addValidationSubcommand(validationCmd, subcommands.typescript).action(async (pathOperands, options) => {
15691
+ const paths = resolveValidationPaths(invocation, pathOperands);
15285
15692
  const result = await typescriptCommand({
15286
- cwd: productDir(),
15693
+ cwd: paths.productDir,
15287
15694
  scope: options.scope,
15288
- files: options.files,
15695
+ files: paths.files,
15289
15696
  quiet: options.quiet,
15290
15697
  json: options.json
15291
15698
  });
15292
15699
  emitValidationResult(result, invocation.io);
15293
15700
  });
15294
15701
  addCommonOptions(tsCmd);
15295
- const lintCmd = addValidationSubcommand(validationCmd, subcommands.lint).option("--fix", "Auto-fix issues").action(async (options) => {
15702
+ const lintCmd = addValidationSubcommand(validationCmd, subcommands.lint).option("--fix", "Auto-fix issues").action(async (pathOperands, options) => {
15703
+ const paths = resolveValidationPaths(invocation, pathOperands);
15296
15704
  const result = await lintCommand({
15297
- cwd: productDir(),
15705
+ cwd: paths.productDir,
15298
15706
  scope: options.scope,
15299
- files: options.files,
15707
+ files: paths.files,
15300
15708
  fix: options.fix,
15301
15709
  quiet: options.quiet,
15302
15710
  json: options.json
@@ -15304,22 +15712,24 @@ function registerValidationCommands(validationCmd, invocation) {
15304
15712
  emitValidationResult(result, invocation.io);
15305
15713
  });
15306
15714
  addCommonOptions(lintCmd);
15307
- const circularCmd = addValidationSubcommand(validationCmd, subcommands.circular).action(async (options) => {
15715
+ const circularCmd = addValidationSubcommand(validationCmd, subcommands.circular).action(async (pathOperands, options) => {
15716
+ const paths = resolveValidationPaths(invocation, pathOperands);
15308
15717
  const result = await circularCommand({
15309
- cwd: productDir(),
15718
+ cwd: paths.productDir,
15310
15719
  scope: options.scope,
15311
- files: options.files,
15720
+ files: paths.files,
15312
15721
  quiet: options.quiet,
15313
15722
  json: options.json
15314
15723
  });
15315
15724
  emitValidationResult(result, invocation.io);
15316
15725
  });
15317
15726
  addCommonOptions(circularCmd);
15318
- const knipCmd = addValidationSubcommand(validationCmd, subcommands.knip).action(async (options) => {
15727
+ const knipCmd = addValidationSubcommand(validationCmd, subcommands.knip).action(async (pathOperands, options) => {
15728
+ const paths = resolveValidationPaths(invocation, pathOperands);
15319
15729
  const result = await knipCommand({
15320
- cwd: productDir(),
15730
+ cwd: paths.productDir,
15321
15731
  scope: options.scope,
15322
- files: options.files,
15732
+ files: paths.files,
15323
15733
  quiet: options.quiet,
15324
15734
  json: options.json
15325
15735
  });
@@ -15335,9 +15745,10 @@ function registerValidationCommands(validationCmd, invocation) {
15335
15745
  ).option(literalValidationCliOptions.literals.flag, literalValidationCliOptions.literals.description).option(literalValidationCliOptions.verbose.flag, literalValidationCliOptions.verbose.description).addHelpText(
15336
15746
  "after",
15337
15747
  "\nEnabled for TypeScript projects by default. Set validation.literal.enabled=false\nin spx.config.* to skip during migration."
15338
- ).action(async (options) => {
15748
+ ).action(async (pathOperands, options) => {
15749
+ const paths = resolveValidationPaths(invocation, pathOperands);
15339
15750
  if (options.allowlistExisting) {
15340
- const result2 = await allowlistExisting({ productDir: productDir() });
15751
+ const result2 = await allowlistExisting({ productDir: paths.productDir });
15341
15752
  emitValidationResult(result2, invocation.io);
15342
15753
  }
15343
15754
  let kind;
@@ -15353,8 +15764,9 @@ function registerValidationCommands(validationCmd, invocation) {
15353
15764
  }
15354
15765
  }
15355
15766
  const result = await literalCommand({
15356
- cwd: productDir(),
15357
- files: options.files,
15767
+ cwd: paths.productDir,
15768
+ scope: options.scope,
15769
+ files: paths.files,
15358
15770
  kind,
15359
15771
  filesWithProblems: options.filesWithProblems,
15360
15772
  literals: options.literals,
@@ -15368,29 +15780,32 @@ function registerValidationCommands(validationCmd, invocation) {
15368
15780
  const markdownCmd = addValidationSubcommand(validationCmd, subcommands.markdown).addHelpText(
15369
15781
  "after",
15370
15782
  "\nValidates spx/ and docs/ by default. Nodes listed in spx/EXCLUDE are\nskipped \u2014 use this for declared-state nodes whose [test] links point\nto files that do not exist yet."
15371
- ).action(async (options) => {
15783
+ ).action(async (pathOperands, options) => {
15784
+ const paths = resolveValidationPaths(invocation, pathOperands);
15372
15785
  const result = await markdownCommand({
15373
- cwd: productDir(),
15374
- files: options.files,
15786
+ cwd: paths.productDir,
15787
+ files: paths.files,
15375
15788
  quiet: options.quiet
15376
15789
  });
15377
15790
  emitValidationResult(result, invocation.io);
15378
15791
  });
15379
15792
  addCommonOptions(markdownCmd);
15380
- const formatCmd = addValidationSubcommand(validationCmd, subcommands.format).action(async (options) => {
15793
+ const formatCmd = addValidationSubcommand(validationCmd, subcommands.format).action(async (pathOperands, options) => {
15794
+ const paths = resolveValidationPaths(invocation, pathOperands);
15381
15795
  const result = await formattingCommand({
15382
- cwd: productDir(),
15383
- files: options.files,
15796
+ cwd: paths.productDir,
15797
+ files: paths.files,
15384
15798
  quiet: options.quiet
15385
15799
  });
15386
15800
  emitValidationResult(result, invocation.io);
15387
15801
  });
15388
15802
  addCommonOptions(formatCmd);
15389
- const allCmd = addValidationSubcommand(validationCmd, subcommands.all).option("--fix", "Auto-fix ESLint issues").option(allValidationCliOptions.skipCircular.flag, allValidationCliOptions.skipCircular.description).option(allValidationCliOptions.skipLiteral.flag, allValidationCliOptions.skipLiteral.description).action(async (options) => {
15803
+ 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) => {
15804
+ const paths = resolveValidationPaths(invocation, pathOperands);
15390
15805
  const result = await allCommand({
15391
- cwd: productDir(),
15806
+ cwd: paths.productDir,
15392
15807
  scope: options.scope,
15393
- files: options.files,
15808
+ files: paths.files,
15394
15809
  fix: options.fix,
15395
15810
  skipCircular: options.skipCircular,
15396
15811
  skipLiteral: options.skipLiteral,
@@ -15430,7 +15845,7 @@ var validationDomain = {
15430
15845
  };
15431
15846
 
15432
15847
  // src/commands/verification-context/cli.ts
15433
- import { isAbsolute as isAbsolute8, win32 } from "path";
15848
+ import { isAbsolute as isAbsolute10, win32 } from "path";
15434
15849
 
15435
15850
  // src/domains/verification-context/context.ts
15436
15851
  var VERIFICATION_CONTEXT_SCHEMA_VERSION = "verification-context.v1";
@@ -15467,7 +15882,7 @@ function createVerificationContextDocument(payload) {
15467
15882
  import { dirname as dirname11 } from "path";
15468
15883
 
15469
15884
  // src/domains/verification-context/path.ts
15470
- import { join as join34 } from "path";
15885
+ import { join as join35 } from "path";
15471
15886
  var VERIFICATION_CONTEXT_STATE_DOMAIN = VERIFICATION_CONTEXT_PERSISTENCE.domain;
15472
15887
  var VERIFICATION_CONTEXT_STATE_PATH = {
15473
15888
  CONTEXTS_DIR: "contexts",
@@ -15488,7 +15903,7 @@ function verificationContextFilePath(scope2) {
15488
15903
  if (!contextsDir.ok) return contextsDir;
15489
15904
  const digest = validateScopeToken(scope2.digest);
15490
15905
  if (!digest.ok) return digest;
15491
- return { ok: true, value: join34(contextsDir.value, verificationContextFileName(digest.value)) };
15906
+ return { ok: true, value: join35(contextsDir.value, verificationContextFileName(digest.value)) };
15492
15907
  }
15493
15908
 
15494
15909
  // src/commands/verification-context/runtime.ts
@@ -15581,7 +15996,7 @@ function normalizeFileSubjectPath(path7) {
15581
15996
  VERIFICATION_CONTEXT_FILE_SUBJECT_PATH.SEPARATOR.CANONICAL
15582
15997
  );
15583
15998
  const segments = normalized.split(VERIFICATION_CONTEXT_FILE_SUBJECT_PATH.SEPARATOR.CANONICAL);
15584
- if (isAbsolute8(path7) || win32.isAbsolute(path7) || windowsRoot.length > 0 || segments.includes(VERIFICATION_CONTEXT_FILE_SUBJECT_PATH.PARENT_DIRECTORY.SEGMENT)) {
15999
+ if (isAbsolute10(path7) || win32.isAbsolute(path7) || windowsRoot.length > 0 || segments.includes(VERIFICATION_CONTEXT_FILE_SUBJECT_PATH.PARENT_DIRECTORY.SEGMENT)) {
15585
16000
  return void 0;
15586
16001
  }
15587
16002
  return normalized;
@@ -15689,8 +16104,11 @@ var WORKTREE_STATUS_RENDER = {
15689
16104
  FREE: "-",
15690
16105
  RUNNING_PID_PREFIX: "PID "
15691
16106
  };
16107
+ var WORKTREE_STATUS_ERROR = {
16108
+ ALL_WITH_EXPLICIT_TARGETS: "worktree status --all cannot be combined with explicit worktree operands"
16109
+ };
15692
16110
  async function statusCommand2(options) {
15693
- const multiTargetRequest = options.worktrees !== void 0 && options.worktrees.length > 1;
16111
+ const multiTargetRequest = options.all === true || options.worktrees !== void 0 && options.worktrees.length > 1;
15694
16112
  const targets = await resolveStatusTargets(options);
15695
16113
  if (!targets.ok) return targets;
15696
16114
  const records = [];
@@ -15708,6 +16126,12 @@ async function statusCommand2(options) {
15708
16126
  }
15709
16127
  async function resolveStatusTargets(options) {
15710
16128
  const requested = options.worktrees;
16129
+ if (options.all === true) {
16130
+ if (requested !== void 0 && requested.length > 0) {
16131
+ return { ok: false, error: WORKTREE_STATUS_ERROR.ALL_WITH_EXPLICIT_TARGETS };
16132
+ }
16133
+ return resolveAllTargetWorktrees(options);
16134
+ }
15711
16135
  if (requested === void 0 || requested.length === 0) {
15712
16136
  const target = await resolveTargetWorktree(options);
15713
16137
  if (!target.ok) return target;
@@ -15745,11 +16169,11 @@ function renderTextStatus(record6) {
15745
16169
  }
15746
16170
 
15747
16171
  // src/lib/worktree-path-info.ts
15748
- import { stat as stat4 } from "fs/promises";
16172
+ import { stat as stat5 } from "fs/promises";
15749
16173
  var defaultWorktreePathInfo = {
15750
16174
  isExistingNonDirectory: async (path7) => {
15751
16175
  try {
15752
- const pathStats = await stat4(path7);
16176
+ const pathStats = await stat5(path7);
15753
16177
  return !pathStats.isDirectory();
15754
16178
  } catch {
15755
16179
  return false;
@@ -15765,6 +16189,7 @@ var WORKTREE_CLI = {
15765
16189
  RELEASE: "release",
15766
16190
  WORKTREE_ARGUMENT: "[worktrees...]",
15767
16191
  SESSION_ID_FLAG: "--session-id",
16192
+ ALL_FLAG: "--all",
15768
16193
  FORMAT_FLAG: "--format",
15769
16194
  WORKTREES_DIR_FLAG: "--worktrees-dir"
15770
16195
  };
@@ -15803,21 +16228,24 @@ function registerWorktreeCommands(worktreeCmd, invocation) {
15803
16228
  });
15804
16229
  if (!result.ok) handleError3(invocation, result.error);
15805
16230
  });
15806
- worktreeCmd.command(`${WORKTREE_CLI.STATUS} ${WORKTREE_CLI.WORKTREE_ARGUMENT}`).description("Report a worktree's occupancy (running | free)").option(`${WORKTREE_CLI.FORMAT_FLAG} <format>`, "Output format (text|json)", WORKTREE_STATUS_FORMAT.TEXT).option(`${WORKTREE_CLI.WORKTREES_DIR_FLAG} <path>`, "Explicit .spx/worktrees directory").action(async (worktrees, options) => {
15807
- const result = await statusCommand2({
15808
- cwd: effectiveInvocationDir(),
15809
- fs: defaultOccupancyFileSystem,
15810
- gitDeps: defaultGitDependencies,
15811
- worktrees,
15812
- format: options.format,
15813
- pathInfo: defaultWorktreePathInfo,
15814
- processTable: defaultProcessTable,
15815
- worktreesDir: options.worktreesDir,
15816
- onWarning: (warning) => writeInvocationWarning4(invocation, warning)
15817
- });
15818
- if (!result.ok) handleError3(invocation, result.error);
15819
- writeOutput4(invocation, result.value);
15820
- });
16231
+ worktreeCmd.command(`${WORKTREE_CLI.STATUS} ${WORKTREE_CLI.WORKTREE_ARGUMENT}`).description("Report a worktree's occupancy (running | free)").option(WORKTREE_CLI.ALL_FLAG, "Report every git-observed worktree").option(`${WORKTREE_CLI.FORMAT_FLAG} <format>`, "Output format (text|json)", WORKTREE_STATUS_FORMAT.TEXT).option(`${WORKTREE_CLI.WORKTREES_DIR_FLAG} <path>`, "Explicit .spx/worktrees directory").action(
16232
+ async (worktrees, options) => {
16233
+ const result = await statusCommand2({
16234
+ cwd: effectiveInvocationDir(),
16235
+ fs: defaultOccupancyFileSystem,
16236
+ gitDeps: defaultGitDependencies,
16237
+ worktrees,
16238
+ all: options.all,
16239
+ format: options.format,
16240
+ pathInfo: defaultWorktreePathInfo,
16241
+ processTable: defaultProcessTable,
16242
+ worktreesDir: options.worktreesDir,
16243
+ onWarning: (warning) => writeInvocationWarning4(invocation, warning)
16244
+ });
16245
+ if (!result.ok) handleError3(invocation, result.error);
16246
+ writeOutput4(invocation, result.value);
16247
+ }
16248
+ );
15821
16249
  worktreeCmd.command(WORKTREE_CLI.RELEASE).description("Release the running worktree's occupancy claim").option(`${WORKTREE_CLI.WORKTREES_DIR_FLAG} <path>`, "Explicit .spx/worktrees directory").action(async (options) => {
15822
16250
  const result = await releaseCommand2({
15823
16251
  cwd: effectiveInvocationDir(),