ultra11y 4.1.0 → 4.3.0

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.
@@ -1,12 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/cli.ts
4
- import { realpathSync as realpathSync6, writeFileSync as writeFileSync19, mkdirSync as mkdirSync17, existsSync as existsSync32, readFileSync as readFileSync29, appendFileSync, copyFileSync as copyFileSync4 } from "fs";
5
- import { join as join47, relative as relative5, sep as sep7, dirname as dirname14 } from "path";
4
+ import { realpathSync as realpathSync6, writeFileSync as writeFileSync19, mkdirSync as mkdirSync17, existsSync as existsSync34, readFileSync as readFileSync32, appendFileSync, copyFileSync as copyFileSync4 } from "fs";
5
+ import { join as join50, relative as relative5, sep as sep7, dirname as dirname14 } from "path";
6
6
  import { fileURLToPath as fileURLToPath5, pathToFileURL as pathToFileURL3 } from "url";
7
7
 
8
8
  // src/types.ts
9
- var VERSION = "4.1.0";
9
+ var VERSION = "4.3.0";
10
10
  var SCHEMA_VERSION = 2;
11
11
 
12
12
  // src/audit.ts
@@ -1197,7 +1197,7 @@ var wcag_universe_default = {
1197
1197
  wcagVersion: "2.2",
1198
1198
  source: "https://www.w3.org/TR/WCAG22/",
1199
1199
  criteriaSource: "https://github.com/w3c/wcag",
1200
- provenance: "Full WCAG 2.x SC universe (all levels incl. AAA, and the removed 4.1.1 Parsing) fetched from raw.githubusercontent.com/w3c/wcag@main on 2026-08-12 via `node scripts/build-standards.mjs --refresh-universe`. Classification: core-AA = ships in src/data/wcag.json (the shipped WCAG 2.2 AA core); out-of-core = WCAG AAA; removed = obsolete (4.1.1).",
1200
+ provenance: "Full WCAG 2.x SC universe (all levels incl. AAA, and the removed 4.1.1 Parsing) fetched from raw.githubusercontent.com/w3c/wcag@main on 2026-08-14 via `node scripts/build-standards.mjs --refresh-universe`. Classification: core-AA = ships in src/data/wcag.json (the shipped WCAG 2.2 AA core); out-of-core = WCAG AAA; removed = obsolete (4.1.1).",
1201
1201
  criteria: [
1202
1202
  {
1203
1203
  id: "1.1.1",
@@ -18794,8 +18794,8 @@ function opaqueLibrarySpecifiers(ast, elements) {
18794
18794
  }
18795
18795
 
18796
18796
  // src/glob.ts
18797
- import { existsSync as existsSync9, statSync as statSync6 } from "fs";
18798
- import { join as join21, sep as sep3 } from "path";
18797
+ import { existsSync as existsSync11, statSync as statSync6 } from "fs";
18798
+ import { join as join24, sep as sep3 } from "path";
18799
18799
 
18800
18800
  // src/util.ts
18801
18801
  import { readFileSync } from "fs";
@@ -18852,13 +18852,20 @@ import { join as join14 } from "path";
18852
18852
  import { createHash as createHash3 } from "crypto";
18853
18853
  import { existsSync as existsSync6, readFileSync as readFileSync8 } from "fs";
18854
18854
  import { join as join16 } from "path";
18855
- import { existsSync as existsSync7 } from "fs";
18856
- import { join as join17 } from "path";
18855
+ import { existsSync as existsSync7, readFileSync as readFileSync9 } from "fs";
18856
+ import { join as join17, resolve as resolve2 } from "path";
18857
+ import { existsSync as existsSync8, readFileSync as readFileSync10 } from "fs";
18858
+ import { join as join18, resolve as resolve3 } from "path";
18859
+ import { readFileSync as readFileSync11 } from "fs";
18860
+ import { join as join19 } from "path";
18861
+ import { spawn } from "child_process";
18862
+ import { existsSync as existsSync9 } from "fs";
18863
+ import { join as join20 } from "path";
18857
18864
  import { pathToFileURL as pathToFileURL2 } from "url";
18858
18865
  import { statSync as statSync5 } from "fs";
18859
- import { join as join18 } from "path";
18860
- import { readFileSync as readFileSync9 } from "fs";
18861
- import { isAbsolute, join as join19 } from "path";
18866
+ import { join as join21 } from "path";
18867
+ import { readFileSync as readFileSync12 } from "fs";
18868
+ import { isAbsolute, join as join22 } from "path";
18862
18869
  import { createInterface } from "readline";
18863
18870
  import { existsSync as existsSync2, statSync as statSync2 } from "fs";
18864
18871
  import { availableParallelism } from "os";
@@ -18873,8 +18880,8 @@ import { existsSync as existsSync4, mkdirSync, mkdtempSync, readFileSync as read
18873
18880
  import { dirname as dirname3, join as join6, resolve, sep as sep2 } from "path";
18874
18881
  import { gunzipSync } from "zlib";
18875
18882
  import { join as join15 } from "path";
18876
- import { existsSync as existsSync8, mkdirSync as mkdirSync3, readFileSync as readFileSync10, writeFileSync as writeFileSync4 } from "fs";
18877
- import { join as join20, resolve as resolve2 } from "path";
18883
+ import { existsSync as existsSync10, mkdirSync as mkdirSync3, readFileSync as readFileSync13, writeFileSync as writeFileSync4 } from "fs";
18884
+ import { join as join23, resolve as resolve4 } from "path";
18878
18885
  var __defProp = Object.defineProperty;
18879
18886
  var __getOwnPropNames = Object.getOwnPropertyNames;
18880
18887
  var __esm = (fn, res) => function __init() {
@@ -18890,7 +18897,7 @@ var EXTRACTOR_VERSION;
18890
18897
  var init_types = __esm({
18891
18898
  "src/types.ts"() {
18892
18899
  "use strict";
18893
- ENGINE_VERSION = "2.27.1";
18900
+ ENGINE_VERSION = "2.28.0";
18894
18901
  SCHEMA_VERSION2 = 5;
18895
18902
  EXTRACTOR_VERSION = 13;
18896
18903
  }
@@ -18958,6 +18965,14 @@ function keywords2(question) {
18958
18965
  }
18959
18966
  return out2;
18960
18967
  }
18968
+ function droppedKeywords(question) {
18969
+ const out2 = [];
18970
+ for (const raw of foldText(question).split(/[^A-Za-z0-9_]+/)) {
18971
+ if (!raw) continue;
18972
+ if (raw.length < 2 || STOPWORDS.has(raw.toLowerCase())) out2.push(raw);
18973
+ }
18974
+ return out2;
18975
+ }
18961
18976
  function rankedKeywords(question) {
18962
18977
  const base = keywords2(question);
18963
18978
  const score = (raw) => {
@@ -20830,13 +20845,13 @@ async function Module2(moduleArg = {}) {
20830
20845
  }
20831
20846
  readAsync = /* @__PURE__ */ __name(async (url) => {
20832
20847
  if (isFileURI(url)) {
20833
- return new Promise((resolve32, reject) => {
20848
+ return new Promise((resolve52, reject) => {
20834
20849
  var xhr = new XMLHttpRequest();
20835
20850
  xhr.open("GET", url, true);
20836
20851
  xhr.responseType = "arraybuffer";
20837
20852
  xhr.onload = () => {
20838
20853
  if (xhr.status == 200 || xhr.status == 0 && xhr.response) {
20839
- resolve32(xhr.response);
20854
+ resolve52(xhr.response);
20840
20855
  return;
20841
20856
  }
20842
20857
  reject(xhr.status);
@@ -21032,9 +21047,9 @@ async function Module2(moduleArg = {}) {
21032
21047
  __name(receiveInstantiationResult, "receiveInstantiationResult");
21033
21048
  var info2 = getWasmImports();
21034
21049
  if (Module["instantiateWasm"]) {
21035
- return new Promise((resolve32, reject) => {
21050
+ return new Promise((resolve52, reject) => {
21036
21051
  Module["instantiateWasm"](info2, (mod, inst) => {
21037
- resolve32(receiveInstance(mod, inst));
21052
+ resolve52(receiveInstance(mod, inst));
21038
21053
  });
21039
21054
  });
21040
21055
  }
@@ -22365,8 +22380,8 @@ async function Module2(moduleArg = {}) {
22365
22380
  if (runtimeInitialized) {
22366
22381
  moduleRtn = Module;
22367
22382
  } else {
22368
- moduleRtn = new Promise((resolve32, reject) => {
22369
- readyPromiseResolve = resolve32;
22383
+ moduleRtn = new Promise((resolve52, reject) => {
22384
+ readyPromiseResolve = resolve52;
22370
22385
  readyPromiseReject = reject;
22371
22386
  });
22372
22387
  }
@@ -28772,6 +28787,27 @@ function buildTrigramIndex(docs) {
28772
28787
  return index;
28773
28788
  }
28774
28789
  function searchIndex(scan2, query, opts = {}) {
28790
+ return runSearch(scan2, query, opts).results;
28791
+ }
28792
+ function explainQuery(scan2, query, opts = {}) {
28793
+ return runSearch(scan2, query, opts);
28794
+ }
28795
+ function emptyExplanation(query, verdict, note) {
28796
+ return {
28797
+ results: [],
28798
+ explain: {
28799
+ query,
28800
+ terms: [],
28801
+ droppedStopwords: droppedKeywords(query),
28802
+ unresolvedTerms: [],
28803
+ verdict,
28804
+ note,
28805
+ bridgedOnlyResults: 0,
28806
+ resultCount: 0
28807
+ }
28808
+ };
28809
+ }
28810
+ function runSearch(scan2, query, opts = {}) {
28775
28811
  const terms = [];
28776
28812
  const seen = /* @__PURE__ */ new Set();
28777
28813
  for (const kw of keywords2(query)) {
@@ -28781,11 +28817,18 @@ function searchIndex(scan2, query, opts = {}) {
28781
28817
  terms.push(t2);
28782
28818
  }
28783
28819
  }
28784
- if (!terms.length) return [];
28820
+ if (!terms.length) {
28821
+ const dropped = droppedKeywords(query);
28822
+ return emptyExplanation(
28823
+ query,
28824
+ "none",
28825
+ dropped.length ? `Nothing was searched for: every token in this query (${dropped.join(", ")}) is a stopword or too short to index. Search with the identifiers or domain words you are actually looking for.` : "Nothing was searched for: the query carried no indexable token."
28826
+ );
28827
+ }
28785
28828
  const queryWantsTests = terms.some((t2) => TEST_INTENT.test(t2));
28786
28829
  const docs = bm25DocsFor(scan2);
28787
28830
  const n = docs.length;
28788
- if (!n) return [];
28831
+ if (!n) return emptyExplanation(query, "none", "This index contains no files.");
28789
28832
  const avgLen = {};
28790
28833
  for (const f of FIELDS) {
28791
28834
  let total = 0;
@@ -28847,7 +28890,7 @@ function searchIndex(scan2, query, opts = {}) {
28847
28890
  const matchedFields = /* @__PURE__ */ new Set();
28848
28891
  const symbolTerms = /* @__PURE__ */ new Set();
28849
28892
  const fuzzyHit = /* @__PURE__ */ new Set();
28850
- let exact = false;
28893
+ let exactNameHit = false;
28851
28894
  const weightedTf = (term) => {
28852
28895
  let wtf = 0;
28853
28896
  for (const f of FIELDS) {
@@ -28865,7 +28908,7 @@ function searchIndex(scan2, query, opts = {}) {
28865
28908
  if (wtf > 0) {
28866
28909
  matched.push(t2);
28867
28910
  symbolTerms.add(t2);
28868
- if (d.exactNames.has(t2)) exact = true;
28911
+ if (d.exactNames.has(t2)) exactNameHit = true;
28869
28912
  score += idfOf(df.get(t2)) * (wtf / (K1 + wtf));
28870
28913
  continue;
28871
28914
  }
@@ -28878,7 +28921,7 @@ function searchIndex(scan2, query, opts = {}) {
28878
28921
  }
28879
28922
  }
28880
28923
  if (!matched.length && !fuzzyHit.size) continue;
28881
- if (exact) score *= EXACT_NAME_BOOST;
28924
+ if (exactNameHit) score *= EXACT_NAME_BOOST;
28882
28925
  if (d.isTest && !queryWantsTests) score *= TEST_DEMOTION;
28883
28926
  if (prior) {
28884
28927
  score *= 1 + 0.35 * Math.log1p(prior.get(d.file) ?? 0);
@@ -28902,10 +28945,60 @@ function searchIndex(scan2, query, opts = {}) {
28902
28945
  result.line = Math.min(...hits.map((h) => h.line));
28903
28946
  }
28904
28947
  if (fuzzyHit.size) result.fuzzyTerms = [...fuzzyHit].sort(byStr);
28948
+ if (!matched.length) result.bridgedOnly = true;
28905
28949
  results.push(result);
28906
28950
  }
28907
28951
  results.sort((a, b) => b.score - a.score || byStr(a.file, b.file));
28908
- return results.slice(0, opts.limit ?? DEFAULT_LIMIT);
28952
+ const kept = (opts.exact ? results.filter((r) => !r.bridgedOnly) : results).slice(0, opts.limit ?? DEFAULT_LIMIT);
28953
+ return { results: kept, explain: explainOf(query, terms, df, fuzzyCandidates, kept) };
28954
+ }
28955
+ function explainOf(query, terms, df, fuzzyCandidates, kept) {
28956
+ const diagnostics = terms.map((term) => {
28957
+ const frequency = df.get(term) ?? 0;
28958
+ const candidates2 = frequency === 0 ? fuzzyCandidates.get(term) ?? [] : [];
28959
+ if (!candidates2.length) return { term, df: frequency };
28960
+ const via = candidates2[0].dice === STEM_WEIGHT ? "stem" : "trigram";
28961
+ return { term, df: frequency, bridge: { via, to: candidates2.map((c2) => c2.term), dice: candidates2[0].dice } };
28962
+ });
28963
+ const unresolvedTerms = diagnostics.filter((d) => d.df === 0 && !d.bridge).map((d) => d.term).sort(byStr);
28964
+ const wholeIdentifier = !/\s/.test(query.trim()) && terms.length ? { term: terms[0], df: df.get(terms[0]) ?? 0 } : void 0;
28965
+ const bridgedOnlyResults = kept.filter((r) => r.bridgedOnly).length;
28966
+ const allBridged = kept.length > 0 && bridgedOnlyResults === kept.length;
28967
+ const missingIdentifier = wholeIdentifier?.df === 0;
28968
+ const verdict = !kept.length ? "none" : missingIdentifier || allBridged ? "weak" : "match";
28969
+ const explain = {
28970
+ query,
28971
+ terms: diagnostics,
28972
+ droppedStopwords: droppedKeywords(query),
28973
+ unresolvedTerms,
28974
+ ...wholeIdentifier ? { wholeIdentifier } : {},
28975
+ verdict,
28976
+ bridgedOnlyResults,
28977
+ resultCount: kept.length
28978
+ };
28979
+ const note = noteFor(explain, missingIdentifier, allBridged);
28980
+ if (note) explain.note = note;
28981
+ return explain;
28982
+ }
28983
+ function noteFor(explain, missingIdentifier, allBridged) {
28984
+ const { verdict, wholeIdentifier, resultCount, terms } = explain;
28985
+ if (verdict === "match") return void 0;
28986
+ if (!resultCount) {
28987
+ return explain.unresolvedTerms.length ? `No file matches. ${explain.unresolvedTerms.length === 1 ? "The term" : "The terms"} ${explain.unresolvedTerms.join(", ")} appear nowhere in this index.` : "No file matches this query.";
28988
+ }
28989
+ if (missingIdentifier && wholeIdentifier) {
28990
+ const parts2 = terms.filter((t2) => t2.term !== wholeIdentifier.term && t2.df > 0).map((t2) => t2.term).join(", ");
28991
+ const near = terms.find((t2) => t2.term === wholeIdentifier.term)?.bridge?.to ?? [];
28992
+ const because = parts2 ? ` The ${resultCount} result${resultCount === 1 ? "" : "s"} below match only its parts (${parts2}).` : "";
28993
+ const suggestion = near.length ? ` Closest indexed name${near.length === 1 ? "" : "s"}: ${near.join(", ")}.` : "";
28994
+ return `No file in this index defines or mentions "${wholeIdentifier.term}".${because}${suggestion} If you expected it here, check you are indexing the right branch or commit.`;
28995
+ }
28996
+ if (allBridged) {
28997
+ const bridged = terms.filter((t2) => t2.bridge);
28998
+ const spelled = bridged.map((t2) => `"${t2.term}" \u2192 ${t2.bridge.to.join(", ")}`).join("; ");
28999
+ return `Nothing matched verbatim. Every result below came from a near match: ${spelled}. Re-run with --exact to see only literal matches.`;
29000
+ }
29001
+ return void 0;
28909
29002
  }
28910
29003
  var K1;
28911
29004
  var DEFAULT_LIMIT;
@@ -29443,6 +29536,15 @@ function findSymbol(scan2, namePath, opts = {}) {
29443
29536
  m.body = content.split("\n").slice(m.line - 1, end).join("\n");
29444
29537
  }
29445
29538
  }
29539
+ if (opts.concise) {
29540
+ return capped.map((m) => ({
29541
+ name: m.name,
29542
+ kind: m.kind,
29543
+ file: m.file,
29544
+ line: m.line,
29545
+ ...m.body !== void 0 ? { body: m.body } : {}
29546
+ }));
29547
+ }
29446
29548
  return capped;
29447
29549
  }
29448
29550
  function findReferences(scan2, name2) {
@@ -31101,6 +31203,703 @@ var init_endpoint = __esm({
31101
31203
  init_embed();
31102
31204
  }
31103
31205
  });
31206
+ function renderRepoMap(scan2, graph, opts = {}) {
31207
+ const budgetChars = (opts.budgetTokens ?? 1024) * CHARS_PER_TOKEN;
31208
+ const maxSymbols = opts.maxSymbolsPerFile ?? 8;
31209
+ const ranked = [...graph.files].filter((f) => f.fileKind === "code").sort((a, b) => (b.pagerank ?? 0) - (a.pagerank ?? 0) || b.symbols - a.symbols || byStr(a.rel, b.rel));
31210
+ const records = new Map(scan2.files.map((f) => [f.rel, f]));
31211
+ const header4 = `# repo map \u2014 ${graph.fileCount} files
31212
+ `;
31213
+ let out2 = header4;
31214
+ let files = 0;
31215
+ for (const node of ranked) {
31216
+ const rec = records.get(node.rel);
31217
+ if (!rec) continue;
31218
+ const symbols = [...rec.symbols].filter((s) => s.kind !== "reexport" && s.kind !== "reexport-all").sort((a, b) => Number(b.exported) - Number(a.exported) || a.line - b.line).slice(0, maxSymbols);
31219
+ let block = `
31220
+ ${node.rel}:
31221
+ `;
31222
+ for (const s of symbols) {
31223
+ const sig = (s.signature ?? `${s.kind} ${s.name}`).replace(/\s+/g, " ").trim().slice(0, 120);
31224
+ block += ` ${s.line}: ${sig}
31225
+ `;
31226
+ }
31227
+ if (out2.length + block.length > budgetChars) break;
31228
+ out2 += block;
31229
+ files++;
31230
+ }
31231
+ return `${out2}
31232
+ (${files} of ${ranked.length} code files shown, ~${Math.ceil(out2.length / CHARS_PER_TOKEN)} tokens)
31233
+ `;
31234
+ }
31235
+ var CHARS_PER_TOKEN;
31236
+ var init_repomap = __esm({
31237
+ "src/repomap.ts"() {
31238
+ "use strict";
31239
+ init_sort();
31240
+ CHARS_PER_TOKEN = 4;
31241
+ }
31242
+ });
31243
+ function changeCoupling(dir, opts = {}) {
31244
+ const maxCommitFiles = opts.maxCommitFiles ?? 30;
31245
+ const minTogether = opts.minTogether ?? 3;
31246
+ const maxPairs = opts.maxPairs ?? 100;
31247
+ const range = opts.since ? [`${opts.since}..HEAD`] : [];
31248
+ const res = sh("git", ["-C", dir, "-c", "core.quotePath=false", "log", ...range, "--pretty=format:%x1e", "--name-only"]);
31249
+ if (!res.ok) return { ok: false, couplings: [] };
31250
+ const totals = /* @__PURE__ */ new Map();
31251
+ const pairs = /* @__PURE__ */ new Map();
31252
+ for (const block of res.stdout.split("")) {
31253
+ const files = block.split("\n").map((l) => l.trim()).filter(Boolean);
31254
+ if (!files.length || files.length > maxCommitFiles) continue;
31255
+ const unique = [...new Set(files)].sort(byStr);
31256
+ for (const f of unique) totals.set(f, (totals.get(f) ?? 0) + 1);
31257
+ for (let i2 = 0; i2 < unique.length; i2++) {
31258
+ for (let j = i2 + 1; j < unique.length; j++) {
31259
+ const key = `${unique[i2]}${SEP4}${unique[j]}`;
31260
+ pairs.set(key, (pairs.get(key) ?? 0) + 1);
31261
+ }
31262
+ }
31263
+ }
31264
+ const out2 = [];
31265
+ for (const [key, together] of pairs) {
31266
+ if (together < minTogether) continue;
31267
+ const [a, b] = key.split(SEP4);
31268
+ const totalA = totals.get(a) ?? together;
31269
+ const totalB = totals.get(b) ?? together;
31270
+ out2.push({ a, b, together, totalA, totalB, strength: Number((together / Math.min(totalA, totalB)).toFixed(3)) });
31271
+ }
31272
+ out2.sort((x, y) => y.strength - x.strength || y.together - x.together || byStr(x.a, y.a) || byStr(x.b, y.b));
31273
+ return { ok: true, couplings: out2.slice(0, maxPairs) };
31274
+ }
31275
+ function rankHotspots(scan2, churn, top = 20) {
31276
+ const out2 = scan2.files.filter((f) => f.kind === "code").map((f) => {
31277
+ const commits = churn.get(f.rel) ?? 0;
31278
+ return { rel: f.rel, lines: f.lines, commits, score: Number((commits * Math.log2(f.lines + 1)).toFixed(2)) };
31279
+ });
31280
+ out2.sort((a, b) => b.score - a.score || b.lines - a.lines || byStr(a.rel, b.rel));
31281
+ return out2.slice(0, top);
31282
+ }
31283
+ var SEP4;
31284
+ var init_coupling = __esm({
31285
+ "src/coupling.ts"() {
31286
+ "use strict";
31287
+ init_util();
31288
+ init_sort();
31289
+ SEP4 = "\0";
31290
+ }
31291
+ });
31292
+ function tagline(root) {
31293
+ for (const name2 of README_NAMES) {
31294
+ const path = join17(root, name2);
31295
+ if (!existsSync7(path)) continue;
31296
+ let text;
31297
+ try {
31298
+ text = readFileSync9(path, "utf8");
31299
+ } catch {
31300
+ continue;
31301
+ }
31302
+ for (const raw of text.split(/\n\s*\n/)) {
31303
+ const line = raw.trim();
31304
+ if (!line || line.startsWith("#") || line.startsWith("<")) continue;
31305
+ if (/^(\[!\[|!\[|\[)/.test(line) && !/[.:]\s/.test(line)) continue;
31306
+ const cleaned = line.replace(/\[([^\]]*)\]\([^)]*\)/g, "$1").replace(/[*_`]/g, "").replace(/\s+/g, " ").trim();
31307
+ if (cleaned.length > 20) return cleaned.slice(0, 400);
31308
+ }
31309
+ break;
31310
+ }
31311
+ return void 0;
31312
+ }
31313
+ function onboardBrief(scan2, graph, opts = {}) {
31314
+ const lines = [];
31315
+ const name2 = resolve2(scan2.root).replace(/\/+$/, "").split("/").pop() || "repository";
31316
+ lines.push(`# ${name2}`, "");
31317
+ const summary = tagline(scan2.root);
31318
+ if (summary) lines.push(summary, "");
31319
+ const languages = Object.entries(graph.languages).sort((a, b) => b[1] - a[1] || byStr(a[0], b[0])).slice(0, 6).map(([lang, count]) => `${lang} ${count}`).join(", ");
31320
+ lines.push(`**${graph.fileCount} indexed files** \xB7 ${languages}`, "");
31321
+ const workspaces = detectWorkspaces(scan2.root);
31322
+ if (workspaces.packages.length > 1) {
31323
+ lines.push(`## Layout \u2014 monorepo, ${workspaces.packages.length} packages`, "");
31324
+ for (const pkg of workspaces.packages.slice(0, 20)) lines.push(`- \`${pkg.dir}\` \u2014 ${pkg.name}`);
31325
+ if (workspaces.packages.length > 20) lines.push(`- \u2026and ${workspaces.packages.length - 20} more`);
31326
+ if (workspaces.cycle?.length) lines.push("", `\u26A0 dependency cycle: ${workspaces.cycle.join(" \u2192 ")}`);
31327
+ lines.push("");
31328
+ }
31329
+ lines.push("## Key files", "", renderRepoMap(scan2, graph, { budgetTokens: opts.budgetTokens ?? 900 }).trim(), "");
31330
+ const { churn, ok: churnOk } = gitChurn(scan2.root);
31331
+ if (churnOk && churn.size) {
31332
+ const hotspots = rankHotspots(scan2, churn, 8);
31333
+ if (hotspots.length) {
31334
+ lines.push("## Where work concentrates", "", "Files ranked by commits \xD7 size \u2014 where changes and defects cluster.", "");
31335
+ for (const spot of hotspots) lines.push(`- \`${spot.rel}\` \u2014 ${spot.commits} commits, ${spot.lines} lines`);
31336
+ lines.push("");
31337
+ }
31338
+ }
31339
+ lines.push(
31340
+ "## Next",
31341
+ "",
31342
+ "- `search <query>` \u2014 BM25 over names, paths, doc comments and prose; `explain_search` says whether it really matched",
31343
+ "- `find_symbol <Name>` / `symbols_overview <file>` \u2014 read structure without reading files",
31344
+ "- `find_references <Name>` \u2014 three labelled tiers; add `lsp: true` when a language server is configured",
31345
+ ""
31346
+ );
31347
+ const brief = lines.join("\n");
31348
+ if (opts.remember === false) return { brief };
31349
+ const memoryName = opts.memoryName ?? "onboarding";
31350
+ writeMemory(scan2.root, memoryName, brief);
31351
+ return { brief, memory: memoryName };
31352
+ }
31353
+ var README_NAMES;
31354
+ var init_onboard = __esm({
31355
+ "src/onboard.ts"() {
31356
+ "use strict";
31357
+ init_repomap();
31358
+ init_workspaces();
31359
+ init_coupling();
31360
+ init_git();
31361
+ init_memory();
31362
+ init_sort();
31363
+ README_NAMES = ["README.md", "README.markdown", "README.rst", "README.txt", "README"];
31364
+ }
31365
+ });
31366
+ function encodeMessage(msg) {
31367
+ const body2 = JSON.stringify(msg);
31368
+ return `Content-Length: ${byteLength(body2)}${HEADER_END}${body2}`;
31369
+ }
31370
+ function byteLength(s) {
31371
+ return new TextEncoder().encode(s).byteLength;
31372
+ }
31373
+ function createFramer() {
31374
+ const encoder = new TextEncoder();
31375
+ const decoder = new TextDecoder();
31376
+ let buffer = new Uint8Array(0);
31377
+ const append = (chunk2) => {
31378
+ const next = new Uint8Array(buffer.length + chunk2.length);
31379
+ next.set(buffer);
31380
+ next.set(chunk2, buffer.length);
31381
+ buffer = next;
31382
+ };
31383
+ const headerEnd = () => {
31384
+ for (let i2 = 0; i2 + 3 < buffer.length; i2++) {
31385
+ if (buffer[i2] === 13 && buffer[i2 + 1] === 10 && buffer[i2 + 2] === 13 && buffer[i2 + 3] === 10) return i2;
31386
+ }
31387
+ return -1;
31388
+ };
31389
+ return {
31390
+ push(chunk2) {
31391
+ append(typeof chunk2 === "string" ? encoder.encode(chunk2) : chunk2);
31392
+ const out2 = [];
31393
+ for (; ; ) {
31394
+ const end = headerEnd();
31395
+ if (end < 0) return out2;
31396
+ const headers = decoder.decode(buffer.subarray(0, end));
31397
+ const match = /content-length:\s*(\d+)/i.exec(headers);
31398
+ if (!match) {
31399
+ buffer = buffer.subarray(end + 4);
31400
+ continue;
31401
+ }
31402
+ const length = Number(match[1]);
31403
+ if (!Number.isSafeInteger(length) || length < 0 || length > MAX_FRAME_BYTES) {
31404
+ buffer = buffer.subarray(end + 4);
31405
+ continue;
31406
+ }
31407
+ const start2 = end + 4;
31408
+ if (buffer.length < start2 + length) return out2;
31409
+ const body2 = decoder.decode(buffer.subarray(start2, start2 + length));
31410
+ buffer = buffer.subarray(start2 + length);
31411
+ try {
31412
+ out2.push(JSON.parse(body2));
31413
+ } catch {
31414
+ }
31415
+ }
31416
+ }
31417
+ };
31418
+ }
31419
+ function fileUri(root, rel2) {
31420
+ const abs = `${root.replace(/\/+$/, "")}/${rel2.replace(/^\/+/, "")}`;
31421
+ const drive = /^([A-Za-z]):/.exec(abs);
31422
+ const path = drive ? `/${abs}` : abs;
31423
+ return "file://" + path.split("/").map((segment, i2) => i2 === 0 ? segment : encodeURIComponent(segment)).join("/");
31424
+ }
31425
+ function relFromUri(root, uri) {
31426
+ if (!uri.startsWith("file://")) return void 0;
31427
+ let path = decodeURIComponent(uri.slice("file://".length));
31428
+ if (/^\/[A-Za-z]:/.test(path)) path = path.slice(1);
31429
+ const base = root.replace(/\/+$/, "");
31430
+ if (path === base) return "";
31431
+ if (!path.startsWith(`${base}/`)) return void 0;
31432
+ return path.slice(base.length + 1);
31433
+ }
31434
+ function locationsToRefs(root, raw) {
31435
+ const list = Array.isArray(raw) ? raw : raw && typeof raw === "object" ? [raw] : [];
31436
+ const seen = /* @__PURE__ */ new Set();
31437
+ const out2 = [];
31438
+ for (const item of list) {
31439
+ if (!item || typeof item !== "object") continue;
31440
+ const uri = item.uri ?? item.targetUri;
31441
+ if (!uri) continue;
31442
+ const file = relFromUri(root, uri);
31443
+ if (file === void 0 || file === "") continue;
31444
+ const start2 = (item.range ?? item.targetSelectionRange ?? item.targetRange)?.start;
31445
+ const line = typeof start2?.line === "number" ? start2.line + 1 : 1;
31446
+ const character = typeof start2?.character === "number" ? start2.character : void 0;
31447
+ const key = `${file}:${line}:${character ?? ""}`;
31448
+ if (seen.has(key)) continue;
31449
+ seen.add(key);
31450
+ out2.push(character === void 0 ? { file, line } : { file, line, character });
31451
+ }
31452
+ return out2.sort((a, b) => byStr(a.file, b.file) || a.line - b.line || (a.character ?? 0) - (b.character ?? 0));
31453
+ }
31454
+ var MAX_FRAME_BYTES;
31455
+ var HEADER_END;
31456
+ var init_protocol = __esm({
31457
+ "src/lsp/protocol.ts"() {
31458
+ "use strict";
31459
+ init_sort();
31460
+ MAX_FRAME_BYTES = 32 * 1024 * 1024;
31461
+ HEADER_END = "\r\n\r\n";
31462
+ }
31463
+ });
31464
+ async function openLspSession(transport, options) {
31465
+ const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT;
31466
+ const startupTimeoutMs = options.startupTimeoutMs ?? DEFAULT_STARTUP_TIMEOUT;
31467
+ const framer = createFramer();
31468
+ const pending = /* @__PURE__ */ new Map();
31469
+ const open = /* @__PURE__ */ new Set();
31470
+ let nextId = 1;
31471
+ let dead;
31472
+ const failAll = (error) => {
31473
+ dead = error;
31474
+ for (const [, p] of pending) {
31475
+ clearTimeout(p.timer);
31476
+ p.reject(error);
31477
+ }
31478
+ pending.clear();
31479
+ };
31480
+ transport.onData((chunk2) => {
31481
+ for (const message of framer.push(chunk2)) {
31482
+ if (typeof message.id !== "number") continue;
31483
+ const waiter = pending.get(message.id);
31484
+ if (!waiter) continue;
31485
+ pending.delete(message.id);
31486
+ clearTimeout(waiter.timer);
31487
+ if (message.error) waiter.reject(new Error(`${message.error.message} (code ${message.error.code})`));
31488
+ else waiter.resolve(message.result);
31489
+ }
31490
+ });
31491
+ transport.onExit((code2) => failAll(new Error(`language server exited (code ${code2 ?? "unknown"})`)));
31492
+ const notify = (method, params) => {
31493
+ if (dead) return;
31494
+ transport.write(encodeMessage({ jsonrpc: "2.0", method, params }));
31495
+ };
31496
+ const request = (method, params, budget = timeoutMs) => {
31497
+ if (dead) return Promise.reject(dead);
31498
+ const id = nextId++;
31499
+ return new Promise((resolve52, reject) => {
31500
+ const timer = setTimeout(() => {
31501
+ pending.delete(id);
31502
+ reject(new LspTimeout(method, budget));
31503
+ }, budget);
31504
+ timer.unref?.();
31505
+ pending.set(id, { resolve: resolve52, reject, timer });
31506
+ transport.write(encodeMessage({ jsonrpc: "2.0", id, method, params }));
31507
+ });
31508
+ };
31509
+ const initResult = await request(
31510
+ "initialize",
31511
+ {
31512
+ processId: null,
31513
+ rootUri: fileUri(options.root, ""),
31514
+ workspaceFolders: [{ uri: fileUri(options.root, ""), name: "repo" }],
31515
+ capabilities: {
31516
+ textDocument: {
31517
+ references: { dynamicRegistration: false },
31518
+ definition: { dynamicRegistration: false, linkSupport: true },
31519
+ implementation: { dynamicRegistration: false, linkSupport: true }
31520
+ }
31521
+ },
31522
+ ...options.initializationOptions !== void 0 ? { initializationOptions: options.initializationOptions } : {}
31523
+ },
31524
+ startupTimeoutMs
31525
+ );
31526
+ notify("initialized", {});
31527
+ const provides = (key) => {
31528
+ const value = initResult?.capabilities?.[key];
31529
+ return value === true || typeof value === "object" && value !== null;
31530
+ };
31531
+ const capabilities = {
31532
+ references: provides("referencesProvider"),
31533
+ definition: provides("definitionProvider"),
31534
+ implementation: provides("implementationProvider"),
31535
+ typeHierarchy: provides("typeHierarchyProvider")
31536
+ };
31537
+ const positionOf = (rel2, line, character) => ({
31538
+ textDocument: { uri: fileUri(options.root, rel2) },
31539
+ // The engine counts lines from 1; LSP counts from 0.
31540
+ position: { line: Math.max(0, line - 1), character }
31541
+ });
31542
+ return {
31543
+ capabilities,
31544
+ didOpen(rel2, text, languageId) {
31545
+ if (open.has(rel2)) return;
31546
+ open.add(rel2);
31547
+ notify("textDocument/didOpen", {
31548
+ textDocument: { uri: fileUri(options.root, rel2), languageId, version: 1, text }
31549
+ });
31550
+ },
31551
+ async references(rel2, line, character) {
31552
+ if (!capabilities.references) return [];
31553
+ const raw = await request("textDocument/references", {
31554
+ ...positionOf(rel2, line, character),
31555
+ context: { includeDeclaration: true }
31556
+ });
31557
+ return locationsToRefs(options.root, raw);
31558
+ },
31559
+ async definition(rel2, line, character) {
31560
+ if (!capabilities.definition) return [];
31561
+ return locationsToRefs(options.root, await request("textDocument/definition", positionOf(rel2, line, character)));
31562
+ },
31563
+ async shutdown() {
31564
+ try {
31565
+ for (const rel2 of open) notify("textDocument/didClose", { textDocument: { uri: fileUri(options.root, rel2) } });
31566
+ open.clear();
31567
+ await request("shutdown", null, Math.min(timeoutMs, 2e3));
31568
+ notify("exit", void 0);
31569
+ } catch {
31570
+ } finally {
31571
+ failAll(new Error("session closed"));
31572
+ transport.close();
31573
+ }
31574
+ }
31575
+ };
31576
+ }
31577
+ var LspTimeout;
31578
+ var DEFAULT_TIMEOUT;
31579
+ var DEFAULT_STARTUP_TIMEOUT;
31580
+ var init_client = __esm({
31581
+ "src/lsp/client.ts"() {
31582
+ "use strict";
31583
+ init_protocol();
31584
+ LspTimeout = class extends Error {
31585
+ constructor(method, ms) {
31586
+ super(`${method} exceeded ${ms}ms`);
31587
+ this.name = "LspTimeout";
31588
+ }
31589
+ };
31590
+ DEFAULT_TIMEOUT = 5e3;
31591
+ DEFAULT_STARTUP_TIMEOUT = 15e3;
31592
+ }
31593
+ });
31594
+ function resolveLspConfigPath(repo) {
31595
+ const env = process.env.CODEINDEX_LSP_CONFIG;
31596
+ if (env !== void 0) {
31597
+ const trimmed = env.trim();
31598
+ if (!trimmed || trimmed === "0" || trimmed.toLowerCase() === "off") return { path: void 0, source: "none" };
31599
+ return { path: resolve3(trimmed), source: "env" };
31600
+ }
31601
+ const inRepo = join18(repo, LSP_CONFIG_DIR, LSP_CONFIG_NAME);
31602
+ if (existsSync8(inRepo)) return { path: inRepo, source: "repo" };
31603
+ const inCwd = join18(process.cwd(), LSP_CONFIG_DIR, LSP_CONFIG_NAME);
31604
+ if (inCwd !== inRepo && existsSync8(inCwd)) return { path: inCwd, source: "cwd" };
31605
+ return { path: void 0, source: "none" };
31606
+ }
31607
+ function parseLspConfig(payload) {
31608
+ if (!payload || typeof payload !== "object") throw new Error("lsp.json must be a JSON object");
31609
+ const raw = payload;
31610
+ if (raw.version !== 1) throw new Error(`lsp.json: unsupported version ${JSON.stringify(raw.version)} (expected 1)`);
31611
+ if (!Array.isArray(raw.servers)) throw new Error("lsp.json: `servers` must be an array");
31612
+ const ids = /* @__PURE__ */ new Set();
31613
+ const servers = raw.servers.map((entry, i2) => {
31614
+ if (!entry || typeof entry !== "object") throw new Error(`lsp.json: servers[${i2}] must be an object`);
31615
+ const s = entry;
31616
+ const id = typeof s.id === "string" && s.id.trim() ? s.id.trim() : void 0;
31617
+ if (!id) throw new Error(`lsp.json: servers[${i2}].id must be a non-empty string`);
31618
+ if (ids.has(id)) throw new Error(`lsp.json: duplicate server id ${JSON.stringify(id)}`);
31619
+ ids.add(id);
31620
+ if (typeof s.command !== "string" || !s.command.trim()) throw new Error(`lsp.json: servers[${i2}].command must be a non-empty string`);
31621
+ if (!Array.isArray(s.languages) || !s.languages.length || s.languages.some((l) => typeof l !== "string")) {
31622
+ throw new Error(`lsp.json: servers[${i2}].languages must be a non-empty array of strings`);
31623
+ }
31624
+ if (s.args !== void 0 && (!Array.isArray(s.args) || s.args.some((a) => typeof a !== "string"))) {
31625
+ throw new Error(`lsp.json: servers[${i2}].args must be an array of strings`);
31626
+ }
31627
+ return {
31628
+ id,
31629
+ languages: s.languages,
31630
+ ...typeof s.languageId === "string" ? { languageId: s.languageId } : {},
31631
+ command: s.command,
31632
+ ...Array.isArray(s.args) ? { args: s.args } : {},
31633
+ ...s.env && typeof s.env === "object" ? { env: s.env } : {},
31634
+ ...s.initializationOptions !== void 0 ? { initializationOptions: s.initializationOptions } : {},
31635
+ ...typeof s.timeoutMs === "number" ? { timeoutMs: s.timeoutMs } : {},
31636
+ ...typeof s.startupTimeoutMs === "number" ? { startupTimeoutMs: s.startupTimeoutMs } : {}
31637
+ };
31638
+ });
31639
+ return { version: 1, servers };
31640
+ }
31641
+ function loadLspConfig(repo) {
31642
+ const { path } = resolveLspConfigPath(repo);
31643
+ if (!path || !existsSync8(path)) return void 0;
31644
+ let payload;
31645
+ try {
31646
+ payload = JSON.parse(readFileSync10(path, "utf8"));
31647
+ } catch (e) {
31648
+ throw new Error(`${path}: ${e instanceof Error ? e.message : String(e)}`);
31649
+ }
31650
+ return parseLspConfig(payload);
31651
+ }
31652
+ function serverForLang(config, lang) {
31653
+ return config.servers.find((s) => s.languages.includes(lang));
31654
+ }
31655
+ function timeoutFor(server) {
31656
+ return positiveEnv("CODEINDEX_LSP_TIMEOUT_MS") ?? server.timeoutMs ?? DEFAULT_TIMEOUT_MS;
31657
+ }
31658
+ function startupTimeoutFor(server) {
31659
+ return positiveEnv("CODEINDEX_LSP_STARTUP_TIMEOUT_MS") ?? server.startupTimeoutMs ?? DEFAULT_STARTUP_TIMEOUT_MS;
31660
+ }
31661
+ function positiveEnv(name2) {
31662
+ const raw = process.env[name2];
31663
+ if (raw === void 0) return void 0;
31664
+ const value = Number(raw);
31665
+ return Number.isFinite(value) && value > 0 ? value : void 0;
31666
+ }
31667
+ var LSP_CONFIG_NAME;
31668
+ var LSP_CONFIG_DIR;
31669
+ var DEFAULT_TIMEOUT_MS;
31670
+ var DEFAULT_STARTUP_TIMEOUT_MS;
31671
+ var init_config2 = __esm({
31672
+ "src/lsp/config.ts"() {
31673
+ "use strict";
31674
+ LSP_CONFIG_NAME = "lsp.json";
31675
+ LSP_CONFIG_DIR = ".codeindex";
31676
+ DEFAULT_TIMEOUT_MS = 5e3;
31677
+ DEFAULT_STARTUP_TIMEOUT_MS = 15e3;
31678
+ }
31679
+ });
31680
+ function lspUnavailable(server, reason) {
31681
+ return { server, ok: false, reason, refs: [], agreement: { both: [], lspOnly: [], staticOnly: [] } };
31682
+ }
31683
+ function columnOfSymbol(root, rel2, line, name2) {
31684
+ try {
31685
+ const lines = readFileSync11(join19(root, rel2), "utf8").split(/\r?\n/);
31686
+ const index = lines[line - 1]?.indexOf(name2) ?? -1;
31687
+ return index < 0 ? 0 : index;
31688
+ } catch {
31689
+ return 0;
31690
+ }
31691
+ }
31692
+ function agreementOf(refs, statik) {
31693
+ const lspFiles = new Set(refs.map((r) => r.file));
31694
+ const staticFiles = /* @__PURE__ */ new Set([
31695
+ ...statik.callSites.map((c2) => c2.file),
31696
+ ...statik.referencingFiles,
31697
+ // Declaration sites are references in the LSP sense (includeDeclaration),
31698
+ // so counting them keeps the two sides comparing the same population.
31699
+ ...statik.defs.map((d) => d.file)
31700
+ ]);
31701
+ const both = [];
31702
+ const lspOnly = [];
31703
+ const staticOnly = [];
31704
+ for (const file of lspFiles) (staticFiles.has(file) ? both : lspOnly).push(file);
31705
+ for (const file of staticFiles) if (!lspFiles.has(file)) staticOnly.push(file);
31706
+ return { both: both.sort(byStr), lspOnly: lspOnly.sort(byStr), staticOnly: staticOnly.sort(byStr) };
31707
+ }
31708
+ async function annotateWithLsp(scan2, name2, statik, session, serverId, languageId) {
31709
+ if (!session.capabilities.references) {
31710
+ return { ...statik, lsp: lspUnavailable(serverId, "server does not provide textDocument/references") };
31711
+ }
31712
+ if (!statik.defs.length) {
31713
+ return { ...statik, lsp: lspUnavailable(serverId, `no declaration of ${name2} to anchor a request on`) };
31714
+ }
31715
+ const seen = /* @__PURE__ */ new Set();
31716
+ const refs = [];
31717
+ try {
31718
+ for (const def of statik.defs) {
31719
+ const text = readTextOrEmpty(scan2.root, def.file);
31720
+ if (!text) continue;
31721
+ session.didOpen(def.file, text, languageId);
31722
+ const character = columnOfSymbol(scan2.root, def.file, def.line, name2);
31723
+ for (const ref of await session.references(def.file, def.line, character)) {
31724
+ const key = `${ref.file}:${ref.line}:${ref.character ?? ""}`;
31725
+ if (seen.has(key)) continue;
31726
+ seen.add(key);
31727
+ refs.push(ref);
31728
+ }
31729
+ }
31730
+ } catch (e) {
31731
+ return {
31732
+ ...statik,
31733
+ lsp: {
31734
+ server: serverId,
31735
+ ok: false,
31736
+ reason: e instanceof Error ? e.message : String(e),
31737
+ refs: refs.sort(refOrder),
31738
+ agreement: agreementOf(refs, statik)
31739
+ }
31740
+ };
31741
+ }
31742
+ refs.sort(refOrder);
31743
+ return { ...statik, lsp: { server: serverId, ok: true, refs, agreement: agreementOf(refs, statik) } };
31744
+ }
31745
+ function refOrder(a, b) {
31746
+ return byStr(a.file, b.file) || a.line - b.line || (a.character ?? 0) - (b.character ?? 0);
31747
+ }
31748
+ function readTextOrEmpty(root, rel2) {
31749
+ try {
31750
+ return readFileSync11(join19(root, rel2), "utf8");
31751
+ } catch {
31752
+ return "";
31753
+ }
31754
+ }
31755
+ var init_refs = __esm({
31756
+ "src/lsp/refs.ts"() {
31757
+ "use strict";
31758
+ init_sort();
31759
+ }
31760
+ });
31761
+ function spawnLspTransport(server, cwd) {
31762
+ let child;
31763
+ try {
31764
+ child = spawn(server.command, server.args ?? [], {
31765
+ cwd,
31766
+ stdio: ["pipe", "pipe", "pipe"],
31767
+ ...server.env ? { env: { ...process.env, ...server.env } } : {}
31768
+ });
31769
+ } catch {
31770
+ return void 0;
31771
+ }
31772
+ let exited = false;
31773
+ const dataListeners = [];
31774
+ const exitListeners = [];
31775
+ const fireExit = (code2) => {
31776
+ if (exited) return;
31777
+ exited = true;
31778
+ for (const listener of exitListeners) listener(code2);
31779
+ };
31780
+ child.on("error", () => fireExit(null));
31781
+ child.on("close", (code2) => fireExit(code2));
31782
+ child.stdout?.on("data", (chunk2) => {
31783
+ for (const listener of dataListeners) listener(chunk2);
31784
+ });
31785
+ child.stderr?.on("data", () => {
31786
+ });
31787
+ return {
31788
+ write(chunk2) {
31789
+ if (exited) return;
31790
+ try {
31791
+ child.stdin?.write(chunk2);
31792
+ } catch {
31793
+ fireExit(null);
31794
+ }
31795
+ },
31796
+ onData(cb) {
31797
+ dataListeners.push(cb);
31798
+ },
31799
+ onExit(cb) {
31800
+ if (exited) cb(null);
31801
+ else exitListeners.push(cb);
31802
+ },
31803
+ close() {
31804
+ try {
31805
+ child.stdin?.end();
31806
+ } catch {
31807
+ }
31808
+ const timer = setTimeout(() => {
31809
+ try {
31810
+ child.kill("SIGKILL");
31811
+ } catch {
31812
+ }
31813
+ }, 2e3);
31814
+ timer.unref?.();
31815
+ child.on("close", () => clearTimeout(timer));
31816
+ }
31817
+ };
31818
+ }
31819
+ var init_spawn = __esm({
31820
+ "src/lsp/spawn.ts"() {
31821
+ "use strict";
31822
+ }
31823
+ });
31824
+ async function lspStatus(scan2, repo, probe = false) {
31825
+ const { path, source } = resolveLspConfigPath(repo);
31826
+ const config = loadLspConfig(repo);
31827
+ if (!config) return { lspVersion: 1, mode: "none", configPath: path ?? null, source, servers: [], unmappedLanguages: [] };
31828
+ const counts = /* @__PURE__ */ new Map();
31829
+ for (const file of scan2.files) counts.set(file.lang, (counts.get(file.lang) ?? 0) + 1);
31830
+ const servers = [];
31831
+ for (const server of config.servers) {
31832
+ const status = {
31833
+ id: server.id,
31834
+ languages: server.languages,
31835
+ command: server.command,
31836
+ onPath: have(server.command),
31837
+ filesInRepo: server.languages.reduce((sum, lang) => sum + (counts.get(lang) ?? 0), 0)
31838
+ };
31839
+ if (probe) {
31840
+ const session = await tryOpen(server, scan2.root);
31841
+ if (session.ok) {
31842
+ status.reachable = true;
31843
+ status.capabilities = session.session.capabilities;
31844
+ await session.session.shutdown();
31845
+ } else {
31846
+ status.reachable = false;
31847
+ status.error = session.reason;
31848
+ }
31849
+ }
31850
+ servers.push(status);
31851
+ }
31852
+ const claimed = new Set(config.servers.flatMap((s) => s.languages));
31853
+ const unmappedLanguages = [...counts.keys()].filter((lang) => !claimed.has(lang) && lang !== "other").sort();
31854
+ return { lspVersion: 1, mode: "configured", configPath: path ?? null, source, servers, unmappedLanguages };
31855
+ }
31856
+ async function tryOpen(server, root) {
31857
+ if (!have(server.command)) return { ok: false, reason: `${server.command} is not on PATH` };
31858
+ const transport = spawnLspTransport(server, root);
31859
+ if (!transport) return { ok: false, reason: `could not start ${server.command}` };
31860
+ try {
31861
+ const session = await openLspSession(transport, {
31862
+ root,
31863
+ timeoutMs: timeoutFor(server),
31864
+ startupTimeoutMs: startupTimeoutFor(server),
31865
+ ...server.initializationOptions !== void 0 ? { initializationOptions: server.initializationOptions } : {}
31866
+ });
31867
+ return { ok: true, session };
31868
+ } catch (e) {
31869
+ transport.close();
31870
+ return { ok: false, reason: e instanceof Error ? e.message : String(e) };
31871
+ }
31872
+ }
31873
+ async function referencesWithLsp(scan2, repo, name2, statik) {
31874
+ let config;
31875
+ try {
31876
+ config = loadLspConfig(repo);
31877
+ } catch (e) {
31878
+ return { ...statik, lsp: lspUnavailable("(config)", e instanceof Error ? e.message : String(e)) };
31879
+ }
31880
+ if (!config) return statik;
31881
+ const lang = statik.defs[0]?.lang;
31882
+ if (!lang) return { ...statik, lsp: lspUnavailable("(none)", `no declaration of ${name2} to anchor a request on`) };
31883
+ const server = serverForLang(config, lang);
31884
+ if (!server) return { ...statik, lsp: lspUnavailable("(none)", `no server configured for ${lang}`) };
31885
+ const opened = await tryOpen(server, scan2.root);
31886
+ if (!opened.ok) return { ...statik, lsp: lspUnavailable(server.id, opened.reason) };
31887
+ try {
31888
+ return await annotateWithLsp(scan2, name2, statik, opened.session, server.id, server.languageId ?? server.languages[0]);
31889
+ } finally {
31890
+ await opened.session.shutdown();
31891
+ }
31892
+ }
31893
+ var init_lsp = __esm({
31894
+ "src/lsp/index.ts"() {
31895
+ "use strict";
31896
+ init_util();
31897
+ init_client();
31898
+ init_config2();
31899
+ init_refs();
31900
+ init_spawn();
31901
+ }
31902
+ });
31104
31903
  function isEntrypointLike(rel2) {
31105
31904
  const base = rel2.split("/").pop();
31106
31905
  const stem = base.split(".")[0].toLowerCase();
@@ -31305,92 +32104,6 @@ var init_rules = __esm({
31305
32104
  ]);
31306
32105
  }
31307
32106
  });
31308
- function changeCoupling(dir, opts = {}) {
31309
- const maxCommitFiles = opts.maxCommitFiles ?? 30;
31310
- const minTogether = opts.minTogether ?? 3;
31311
- const maxPairs = opts.maxPairs ?? 100;
31312
- const range = opts.since ? [`${opts.since}..HEAD`] : [];
31313
- const res = sh("git", ["-C", dir, "-c", "core.quotePath=false", "log", ...range, "--pretty=format:%x1e", "--name-only"]);
31314
- if (!res.ok) return { ok: false, couplings: [] };
31315
- const totals = /* @__PURE__ */ new Map();
31316
- const pairs = /* @__PURE__ */ new Map();
31317
- for (const block of res.stdout.split("")) {
31318
- const files = block.split("\n").map((l) => l.trim()).filter(Boolean);
31319
- if (!files.length || files.length > maxCommitFiles) continue;
31320
- const unique = [...new Set(files)].sort(byStr);
31321
- for (const f of unique) totals.set(f, (totals.get(f) ?? 0) + 1);
31322
- for (let i2 = 0; i2 < unique.length; i2++) {
31323
- for (let j = i2 + 1; j < unique.length; j++) {
31324
- const key = `${unique[i2]}${SEP4}${unique[j]}`;
31325
- pairs.set(key, (pairs.get(key) ?? 0) + 1);
31326
- }
31327
- }
31328
- }
31329
- const out2 = [];
31330
- for (const [key, together] of pairs) {
31331
- if (together < minTogether) continue;
31332
- const [a, b] = key.split(SEP4);
31333
- const totalA = totals.get(a) ?? together;
31334
- const totalB = totals.get(b) ?? together;
31335
- out2.push({ a, b, together, totalA, totalB, strength: Number((together / Math.min(totalA, totalB)).toFixed(3)) });
31336
- }
31337
- out2.sort((x, y) => y.strength - x.strength || y.together - x.together || byStr(x.a, y.a) || byStr(x.b, y.b));
31338
- return { ok: true, couplings: out2.slice(0, maxPairs) };
31339
- }
31340
- function rankHotspots(scan2, churn, top = 20) {
31341
- const out2 = scan2.files.filter((f) => f.kind === "code").map((f) => {
31342
- const commits = churn.get(f.rel) ?? 0;
31343
- return { rel: f.rel, lines: f.lines, commits, score: Number((commits * Math.log2(f.lines + 1)).toFixed(2)) };
31344
- });
31345
- out2.sort((a, b) => b.score - a.score || b.lines - a.lines || byStr(a.rel, b.rel));
31346
- return out2.slice(0, top);
31347
- }
31348
- var SEP4;
31349
- var init_coupling = __esm({
31350
- "src/coupling.ts"() {
31351
- "use strict";
31352
- init_util();
31353
- init_sort();
31354
- SEP4 = "\0";
31355
- }
31356
- });
31357
- function renderRepoMap(scan2, graph, opts = {}) {
31358
- const budgetChars = (opts.budgetTokens ?? 1024) * CHARS_PER_TOKEN;
31359
- const maxSymbols = opts.maxSymbolsPerFile ?? 8;
31360
- const ranked = [...graph.files].filter((f) => f.fileKind === "code").sort((a, b) => (b.pagerank ?? 0) - (a.pagerank ?? 0) || b.symbols - a.symbols || byStr(a.rel, b.rel));
31361
- const records = new Map(scan2.files.map((f) => [f.rel, f]));
31362
- const header4 = `# repo map \u2014 ${graph.fileCount} files
31363
- `;
31364
- let out2 = header4;
31365
- let files = 0;
31366
- for (const node of ranked) {
31367
- const rec = records.get(node.rel);
31368
- if (!rec) continue;
31369
- const symbols = [...rec.symbols].filter((s) => s.kind !== "reexport" && s.kind !== "reexport-all").sort((a, b) => Number(b.exported) - Number(a.exported) || a.line - b.line).slice(0, maxSymbols);
31370
- let block = `
31371
- ${node.rel}:
31372
- `;
31373
- for (const s of symbols) {
31374
- const sig = (s.signature ?? `${s.kind} ${s.name}`).replace(/\s+/g, " ").trim().slice(0, 120);
31375
- block += ` ${s.line}: ${sig}
31376
- `;
31377
- }
31378
- if (out2.length + block.length > budgetChars) break;
31379
- out2 += block;
31380
- files++;
31381
- }
31382
- return `${out2}
31383
- (${files} of ${ranked.length} code files shown, ~${Math.ceil(out2.length / CHARS_PER_TOKEN)} tokens)
31384
- `;
31385
- }
31386
- var CHARS_PER_TOKEN;
31387
- var init_repomap = __esm({
31388
- "src/repomap.ts"() {
31389
- "use strict";
31390
- init_sort();
31391
- CHARS_PER_TOKEN = 4;
31392
- }
31393
- });
31394
32107
  function findDeadCode(scan2) {
31395
32108
  const callers = callerIndexFor(scan2);
31396
32109
  const refs = symbolRefsFor(scan2);
@@ -31540,7 +32253,7 @@ function negotiateProtocol(requested) {
31540
32253
  function capResponse(text, tool, repo, maxBytes) {
31541
32254
  const bytes = Buffer.byteLength(text, "utf8");
31542
32255
  if (bytes <= maxBytes) return text;
31543
- const artifact = ARTIFACT_FOR[tool] ? join17(repo, INDEX_DIR, ARTIFACT_FOR[tool]) : void 0;
32256
+ const artifact = ARTIFACT_FOR[tool] ? join20(repo, INDEX_DIR, ARTIFACT_FOR[tool]) : void 0;
31544
32257
  return JSON.stringify(
31545
32258
  {
31546
32259
  truncated: true,
@@ -31549,7 +32262,7 @@ function capResponse(text, tool, repo, maxBytes) {
31549
32262
  maxBytes,
31550
32263
  reason: "This response exceeds the configured limit and was withheld rather than sent as an unusable partial payload.",
31551
32264
  narrower: NARROWER[tool] ?? "narrow the request with `scope`, `include`/`exclude`, or a `limit`",
31552
- ...artifact && existsSync7(artifact) ? { artifact, artifactNote: "The full result is already on disk here \u2014 read it directly if you need all of it." } : artifact ? { artifactNote: `Run \`codeindex index --repo ${repo} --out ${join17(repo, INDEX_DIR)}\` to get this as a file.` } : {}
32265
+ ...artifact && existsSync9(artifact) ? { artifact, artifactNote: "The full result is already on disk here \u2014 read it directly if you need all of it." } : artifact ? { artifactNote: `Run \`codeindex index --repo ${repo} --out ${join20(repo, INDEX_DIR)}\` to get this as a file.` } : {}
31553
32266
  },
31554
32267
  null,
31555
32268
  2
@@ -31580,7 +32293,7 @@ var RICH_TOOLS_SINCE;
31580
32293
  var DEFAULT_MAX_RESPONSE_BYTES;
31581
32294
  var NARROWER;
31582
32295
  var ARTIFACT_FOR;
31583
- var init_protocol = __esm({
32296
+ var init_protocol2 = __esm({
31584
32297
  "src/mcp/protocol.ts"() {
31585
32298
  "use strict";
31586
32299
  init_preload();
@@ -31609,11 +32322,27 @@ function annotationsFor(name2) {
31609
32322
  openWorldHint: meta2.openWorld === true
31610
32323
  };
31611
32324
  }
31612
- function toolsFor(defaultRepo, protocolVersion = PROTOCOL_VERSIONS[0]) {
32325
+ function profileNames() {
32326
+ return ["all", ...Object.keys(TOOL_PROFILES).sort()];
32327
+ }
32328
+ function toolsInProfiles(spec) {
32329
+ const names = spec.split(",").map((s) => s.trim()).filter(Boolean);
32330
+ const out2 = /* @__PURE__ */ new Set();
32331
+ for (const name2 of names) {
32332
+ if (name2 === "all") return new Set(TOOLS.map((t2) => t2.name));
32333
+ const profile = TOOL_PROFILES[name2];
32334
+ if (!profile) throw new Error(`unknown tool profile "${name2}" \u2014 one of: ${profileNames().join(", ")}`);
32335
+ for (const tool of profile) out2.add(tool);
32336
+ }
32337
+ return out2;
32338
+ }
32339
+ function toolsFor(defaultRepo, protocolVersion = PROTOCOL_VERSIONS[0], profile) {
31613
32340
  const withAnnotations = protocolVersion >= ANNOTATIONS_SINCE;
31614
32341
  const withRich = protocolVersion >= RICH_TOOLS_SINCE;
31615
- if (!defaultRepo && !withAnnotations && !withRich) return TOOLS;
31616
- return TOOLS.map((t2) => ({
32342
+ const allowed = profile ? toolsInProfiles(profile) : void 0;
32343
+ const TOOLS_ = allowed ? TOOLS.filter((t2) => allowed.has(t2.name)) : TOOLS;
32344
+ if (!defaultRepo && !withAnnotations && !withRich) return TOOLS_;
32345
+ return TOOLS_.map((t2) => ({
31617
32346
  ...t2,
31618
32347
  ...withRich && TOOL_META[t2.name] ? { title: TOOL_META[t2.name].title } : {},
31619
32348
  ...withRich && OUTPUT_SCHEMAS[t2.name] ? { outputSchema: OUTPUT_SCHEMAS[t2.name] } : {},
@@ -31638,10 +32367,11 @@ var strArr;
31638
32367
  var anyObj;
31639
32368
  var OUTPUT_SCHEMAS;
31640
32369
  var TOOL_META;
32370
+ var TOOL_PROFILES;
31641
32371
  var init_tools = __esm({
31642
32372
  "src/mcp/tools.ts"() {
31643
32373
  "use strict";
31644
- init_protocol();
32374
+ init_protocol2();
31645
32375
  repoProp = { repo: { type: "string", description: "Absolute path to the repository root" } };
31646
32376
  scopeProps = {
31647
32377
  scope: { type: "string", description: "Restrict to one directory (repo-relative)" },
@@ -31709,7 +32439,7 @@ var init_tools = __esm({
31709
32439
  },
31710
32440
  {
31711
32441
  name: "find_symbol",
31712
- description: "Find symbol declarations by name or name path ('Class/method' matches a method inside Class). Options: substring matching, includeBody to return the declaration's source. Exact-name matches rank first.",
32442
+ description: `Find symbol declarations by name or name path ('Class/method' matches a method inside Class). Each match carries its COMPLETE SIGNATURE (parameters and return type) by default, because "what shape is it" is the question that follows "where is it" almost every time and one round trip beats two. Options: substring matching, includeBody for the declaration's source, concise to drop everything but name/kind/file/line when you genuinely only want a location. Exact-name matches rank first.`,
31713
32443
  inputSchema: {
31714
32444
  type: "object",
31715
32445
  properties: {
@@ -31717,6 +32447,10 @@ var init_tools = __esm({
31717
32447
  namePath: { type: "string", description: "Symbol name or Parent/child path" },
31718
32448
  substring: { type: "boolean" },
31719
32449
  includeBody: { type: "boolean" },
32450
+ concise: {
32451
+ type: "boolean",
32452
+ description: "Return only name/kind/file/line \u2014 drop the signature, line span, visibility and language. Roughly 2.5x smaller; use it when you are resolving a path and nothing more (default false)."
32453
+ },
31720
32454
  maxResults: { type: "number", description: "Cap matches (default 50)" }
31721
32455
  },
31722
32456
  required: ["repo", "namePath"]
@@ -31727,10 +32461,42 @@ var init_tools = __esm({
31727
32461
  description: "Who references a symbol? Three labeled tiers: defs (declarations), callSites (line-precise, import-corroborated call bindings), referencingFiles (file-level identifier/doc mentions \u2014 may include homonyms). Confidence decreases across tiers; the labels let you decide what to trust.",
31728
32462
  inputSchema: {
31729
32463
  type: "object",
31730
- properties: { ...repoProp, name: { type: "string", description: "Symbol name" } },
32464
+ properties: {
32465
+ ...repoProp,
32466
+ name: { type: "string", description: "Symbol name" },
32467
+ lsp: {
32468
+ type: "boolean",
32469
+ description: "Also ask a configured language server (see lsp_status) and append an `lsp` block: its references plus an `agreement` matrix (both / lspOnly / staticOnly). The three static tiers are unchanged either way. `staticOnly` is where the homonyms are. No config, no binary, a crash or a timeout all degrade to the static answer with a stated reason (default false)."
32470
+ }
32471
+ },
31731
32472
  required: ["repo", "name"]
31732
32473
  }
31733
32474
  },
32475
+ {
32476
+ name: "lsp_status",
32477
+ description: "Is the optional LSP tier configured, and would it answer? Reports the config path and its source, each server with whether its command is on PATH and how many files in this repo it claims, and the languages no server covers. Opt-in by asset: the tier is active only when <repo>/.codeindex/lsp.json exists (or CODEINDEX_LSP_CONFIG points at one). `probe: true` additionally starts each server to read the capabilities it really advertises. The tier never touches graph.json/symbols.json \u2014 it annotates query answers only.",
32478
+ inputSchema: {
32479
+ type: "object",
32480
+ properties: {
32481
+ ...repoProp,
32482
+ probe: { type: "boolean", description: "Start each server and read its real capabilities (default false: no spawn)" }
32483
+ },
32484
+ required: ["repo"]
32485
+ }
32486
+ },
32487
+ {
32488
+ name: "onboard",
32489
+ description: "One call that says what this repository IS: its own tagline, size and language mix, monorepo layout when there is one, a token-budgeted map of the highest-PageRank files with their key signatures, and where git says work concentrates. Composes scan_summary + workspaces + repo_map + hotspots so the first four round trips of a session become one, and persists the result as the `onboarding` memory (read_memory) so the next session does not repeat them. Set remember:false to skip the write.",
32490
+ inputSchema: {
32491
+ type: "object",
32492
+ properties: {
32493
+ ...repoProp,
32494
+ budgetTokens: { type: "number", description: "Token budget for the key-files section (default 900)" },
32495
+ remember: { type: "boolean", description: "Persist the brief as the `onboarding` memory (default true)" }
32496
+ },
32497
+ required: ["repo"]
32498
+ }
32499
+ },
31734
32500
  {
31735
32501
  name: "repo_map",
31736
32502
  description: "Token-budgeted map of the repository: the highest-PageRank files with their key exported signatures, deterministically rendered to fit `budgetTokens` (default 1024). The densest single read to understand an unfamiliar codebase.",
@@ -31903,6 +32669,14 @@ var init_tools = __esm({
31903
32669
  type: "string",
31904
32670
  description: `Structural prior: "graph" multiplies the lexical score by the file's PageRank over the resolved import graph; "lexical" (default) scores on text alone. Unproven on the judged corpus \u2014 see SearchOptions.rank.`
31905
32671
  },
32672
+ exact: {
32673
+ type: "boolean",
32674
+ description: "Drop results that carry no verbatim query-term match \u2014 the ones the stem/trigram bridge produced (default false)."
32675
+ },
32676
+ explain: {
32677
+ type: "boolean",
32678
+ description: "Wrap the response as `{ results, explain }` with the query verdict (default false = bare array). See explain_search."
32679
+ },
31906
32680
  semantic: {
31907
32681
  type: "boolean",
31908
32682
  description: 'RRF-fuse an embedding tier with lexical (default false). Precedence: the HTTP endpoint (CODEINDEX_EMBED_ENDPOINT) if set, else a local static model. The response reports the effective tier as a top-level `tier` field ("endpoint"/"static" on success, "lexical" plus `degradedReason` when neither is available/reachable) instead of degrading silently \u2014 see embed_status.'
@@ -31911,6 +32685,22 @@ var init_tools = __esm({
31911
32685
  required: ["repo", "query"]
31912
32686
  }
31913
32687
  },
32688
+ {
32689
+ name: "explain_search",
32690
+ description: 'Search, and say whether the query actually found anything. Returns `{ results, explain }` where explain.verdict is "match" (a verbatim term matched), "weak" (results exist but rest on a near match, or the identifier you asked for has document frequency 0) or "none". Use this instead of `search` whenever an empty-feeling or surprising result matters: a query for an identifier that is NOT in the indexed tree still returns confident-looking rows built from its subtokens \u2014 searching "nullGipStep7" in a repo that only has "nullGipStep2" ranks files matching "null" and "gip" \u2014 and only the verdict distinguishes that from a real hit. Also names the terms dropped as stopwords, the terms that exist nowhere, and what each near match bridged to.',
32691
+ inputSchema: {
32692
+ type: "object",
32693
+ properties: {
32694
+ ...repoProp,
32695
+ ...scopeProps,
32696
+ query: { type: "string", description: "Natural-language or identifier query" },
32697
+ limit: { type: "number", description: "Max results (default 20)" },
32698
+ fuzzy: { type: "boolean", description: "Stem/trigram fallback for zero-document-frequency terms (default true)" },
32699
+ exact: { type: "boolean", description: "Drop results carrying no verbatim term match (default false)" }
32700
+ },
32701
+ required: ["repo", "query"]
32702
+ }
32703
+ },
31914
32704
  {
31915
32705
  name: "embed_status",
31916
32706
  description: "Report the embedding tier: the effective mode (none/static/endpoint; endpoint > static model), the resolved model (opt-in, never shipped in the package) with its modelId/dim, EMBED_VERSION, and the configured HTTP endpoint with its reachability. Use to check whether `search` with semantic:true will fuse embeddings or degrade to lexical.",
@@ -32050,6 +32840,45 @@ var init_tools = __esm({
32050
32840
  },
32051
32841
  required: ["defs", "callSites", "referencingFiles"]
32052
32842
  },
32843
+ lsp_status: {
32844
+ type: "object",
32845
+ properties: {
32846
+ lspVersion: { type: "number" },
32847
+ mode: { type: "string", enum: ["none", "configured"] },
32848
+ configPath: { type: ["string", "null"] },
32849
+ source: { type: "string", enum: ["env", "repo", "cwd", "none"] },
32850
+ servers: { type: "array", items: anyObj },
32851
+ unmappedLanguages: strArr
32852
+ },
32853
+ required: ["lspVersion", "mode", "source", "servers", "unmappedLanguages"]
32854
+ },
32855
+ onboard: {
32856
+ type: "object",
32857
+ properties: { brief: { type: "string" }, memory: { type: "string" } },
32858
+ required: ["brief"]
32859
+ },
32860
+ explain_search: {
32861
+ type: "object",
32862
+ properties: {
32863
+ results: { type: "array", items: anyObj },
32864
+ explain: {
32865
+ type: "object",
32866
+ properties: {
32867
+ query: { type: "string" },
32868
+ terms: { type: "array", items: anyObj },
32869
+ droppedStopwords: strArr,
32870
+ unresolvedTerms: strArr,
32871
+ wholeIdentifier: anyObj,
32872
+ verdict: { type: "string", enum: ["match", "weak", "none"] },
32873
+ note: { type: "string" },
32874
+ bridgedOnlyResults: { type: "number" },
32875
+ resultCount: { type: "number" }
32876
+ },
32877
+ required: ["query", "terms", "droppedStopwords", "unresolvedTerms", "verdict", "bridgedOnlyResults", "resultCount"]
32878
+ }
32879
+ },
32880
+ required: ["results", "explain"]
32881
+ },
32053
32882
  hotspots: {
32054
32883
  type: "object",
32055
32884
  properties: { churnOk: { type: "boolean" }, hotspots: { type: "array", items: anyObj } },
@@ -32113,6 +32942,7 @@ var init_tools = __esm({
32113
32942
  find_symbol: { title: "Find symbol" },
32114
32943
  find_references: { title: "Find references" },
32115
32944
  repo_map: { title: "Repository map" },
32945
+ onboard: { title: "Project brief", write: true, destructive: false, idempotent: true },
32116
32946
  hotspots: { title: "Hotspots" },
32117
32947
  coupling: { title: "Change coupling" },
32118
32948
  replace_symbol_body: { title: "Replace symbol body", write: true, destructive: true, idempotent: true },
@@ -32128,12 +32958,29 @@ var init_tools = __esm({
32128
32958
  mermaid: { title: "Mermaid module diagram" },
32129
32959
  grep: { title: "Grep file contents" },
32130
32960
  search: { title: "Lexical search", openWorld: true },
32961
+ explain_search: { title: "Search with a verdict", openWorld: true },
32962
+ // openWorld: it spawns a process the user configured, whose answer this
32963
+ // engine does not determine — the same honesty `search` and `embed_status`
32964
+ // already carry for reaching outside their own artifacts.
32965
+ lsp_status: { title: "LSP tier status", openWorld: true },
32131
32966
  embed_status: { title: "Embedding tier status", openWorld: true },
32132
32967
  type_hierarchy: { title: "Type hierarchy" },
32133
32968
  implementations: { title: "Implementations" },
32134
32969
  call_graph: { title: "Call graph neighborhood" },
32135
32970
  check_rules: { title: "Check architecture rules" }
32136
32971
  };
32972
+ TOOL_PROFILES = {
32973
+ // Land in an unfamiliar repository and get your bearings.
32974
+ orient: ["scan_summary", "repo_map", "onboard", "workspaces", "mermaid", "read_memory", "list_memories"],
32975
+ // Locate a thing.
32976
+ find: ["search", "explain_search", "grep", "find_symbol", "symbols", "symbols_overview"],
32977
+ // Decide whether changing it is safe.
32978
+ impact: ["find_references", "callers", "call_graph", "dead_code", "type_hierarchy", "implementations", "lsp_status"],
32979
+ // Change it.
32980
+ edit: ["find_symbol", "symbols_overview", "replace_symbol_body", "insert_after_symbol", "insert_before_symbol"],
32981
+ // Where the work and the risk concentrate.
32982
+ risk: ["hotspots", "churn", "coupling", "complexity", "check_rules", "duplicated_literals", "dead_code"]
32983
+ };
32137
32984
  }
32138
32985
  });
32139
32986
  function scanFingerprint(scan2) {
@@ -32149,7 +32996,7 @@ async function memoizedEmbeddingIndex(key, build) {
32149
32996
  function memoizedEmbedModel(modelDir) {
32150
32997
  let stat;
32151
32998
  try {
32152
- stat = statSync5(join18(modelDir, "model.json"));
32999
+ stat = statSync5(join21(modelDir, "model.json"));
32153
33000
  } catch {
32154
33001
  return void 0;
32155
33002
  }
@@ -32264,6 +33111,7 @@ __export(mcp_exports, {
32264
33111
  PROTOCOL_VERSIONS: () => PROTOCOL_VERSIONS,
32265
33112
  TOOLS: () => TOOLS,
32266
33113
  TOOL_META: () => TOOL_META,
33114
+ TOOL_PROFILES: () => TOOL_PROFILES,
32267
33115
  annotationsFor: () => annotationsFor,
32268
33116
  capResponse: () => capResponse,
32269
33117
  getArtifacts: () => getArtifacts,
@@ -32272,12 +33120,14 @@ __export(mcp_exports, {
32272
33120
  memoizedEmbedModel: () => memoizedEmbedModel,
32273
33121
  memoizedEmbeddingIndex: () => memoizedEmbeddingIndex,
32274
33122
  negotiateProtocol: () => negotiateProtocol,
33123
+ profileNames: () => profileNames,
32275
33124
  resourceLinkFor: () => resourceLinkFor,
32276
33125
  runMcpServer: () => runMcpServer,
32277
33126
  scanFingerprint: () => scanFingerprint,
32278
33127
  structuredContentFor: () => structuredContentFor,
32279
33128
  toCacheMap: () => toCacheMap,
32280
33129
  toolsFor: () => toolsFor,
33130
+ toolsInProfiles: () => toolsInProfiles,
32281
33131
  validateArgs: () => validateArgs,
32282
33132
  warmGrammarsForRepo: () => warmGrammarsForRepo,
32283
33133
  warmGrammarsForWalk: () => warmGrammarsForWalk
@@ -32358,6 +33208,7 @@ async function callTool(name2, args2, defaultRepo) {
32358
33208
  const matches = findSymbol(getScan(repo, scanOpts, walked), namePath, {
32359
33209
  substring: args2.substring === true,
32360
33210
  includeBody: args2.includeBody === true,
33211
+ concise: args2.concise === true,
32361
33212
  maxResults: num(args2.maxResults)
32362
33213
  });
32363
33214
  return JSON.stringify(matches, null, 2);
@@ -32365,7 +33216,13 @@ async function callTool(name2, args2, defaultRepo) {
32365
33216
  if (name2 === "find_references") {
32366
33217
  const symName = str(args2.name);
32367
33218
  if (!symName) throw new Error("`name` is required");
32368
- return JSON.stringify(findReferences(getScan(repo, scanOpts, walked), symName), null, 2);
33219
+ const scan2 = getScan(repo, scanOpts, walked);
33220
+ const statik = findReferences(scan2, symName);
33221
+ if (args2.lsp === true) return JSON.stringify(await referencesWithLsp(scan2, repo, symName, statik), null, 2);
33222
+ return JSON.stringify(statik, null, 2);
33223
+ }
33224
+ if (name2 === "lsp_status") {
33225
+ return JSON.stringify(await lspStatus(getScan(repo, scanOpts, walked), repo, args2.probe === true), null, 2);
32369
33226
  }
32370
33227
  if (name2 === "replace_symbol_body" || name2 === "insert_after_symbol" || name2 === "insert_before_symbol") {
32371
33228
  const namePath = str(args2.namePath);
@@ -32436,6 +33293,18 @@ async function callTool(name2, args2, defaultRepo) {
32436
33293
  const { graph } = getArtifacts(repo, scanOpts, walked);
32437
33294
  return renderMermaid(graph, { module: str(args2.module), maxEdges: num(args2.maxEdges) });
32438
33295
  }
33296
+ if (name2 === "onboard") {
33297
+ const scan2 = getScan(repo, scanOpts, walked);
33298
+ const { graph } = getArtifacts(repo, scanOpts, walked);
33299
+ return JSON.stringify(
33300
+ onboardBrief(scan2, graph, {
33301
+ ...typeof args2.budgetTokens === "number" ? { budgetTokens: args2.budgetTokens } : {},
33302
+ ...args2.remember === false ? { remember: false } : {}
33303
+ }),
33304
+ null,
33305
+ 2
33306
+ );
33307
+ }
32439
33308
  if (name2 === "repo_map") {
32440
33309
  const { scan: scan2, graph } = getArtifacts(repo, scanOpts, walked);
32441
33310
  return renderRepoMap(scan2, graph, { budgetTokens: typeof args2.budgetTokens === "number" ? args2.budgetTokens : void 0 });
@@ -32467,18 +33336,19 @@ async function callTool(name2, args2, defaultRepo) {
32467
33336
  const scan2 = getScan(repo, scanOpts, walked);
32468
33337
  const limit = typeof args2.limit === "number" ? args2.limit : void 0;
32469
33338
  const fuzzy = typeof args2.fuzzy === "boolean" ? args2.fuzzy : void 0;
33339
+ const exactOpt = args2.exact === true ? { exact: true } : {};
32470
33340
  if (args2.semantic === true) {
32471
33341
  const endpoint = resolveEmbedEndpoint();
32472
33342
  if (endpoint) {
32473
33343
  try {
32474
33344
  const index = await memoizedEmbeddingIndex({ mode: "endpoint", identity: endpoint, scan: scan2 }, () => buildEndpointIndex(scan2));
32475
33345
  const queryVec = await encodeQueryViaEndpoint(query);
32476
- const results2 = searchSemantic(scan2, query, index, { queryVec, limit, fuzzy });
32477
- return JSON.stringify({ results: results2, tier: "endpoint" }, null, 2);
33346
+ const results3 = searchSemantic(scan2, query, index, { queryVec, limit, fuzzy });
33347
+ return JSON.stringify({ results: results3, tier: "endpoint" }, null, 2);
32478
33348
  } catch (e) {
32479
- const results2 = searchIndex(scan2, query, { limit, fuzzy, ...rankOpt });
33349
+ const results3 = searchIndex(scan2, query, { limit, fuzzy, ...rankOpt });
32480
33350
  return JSON.stringify(
32481
- { results: results2, tier: "lexical", degradedReason: `embedding endpoint failed: ${errMessage(e)}` },
33351
+ { results: results3, tier: "lexical", degradedReason: `embedding endpoint failed: ${errMessage(e)}` },
32482
33352
  null,
32483
33353
  2
32484
33354
  );
@@ -32491,17 +33361,32 @@ async function callTool(name2, args2, defaultRepo) {
32491
33361
  { mode: "static", identity: `${modelDir}#${model.modelId}`, scan: scan2 },
32492
33362
  () => buildEmbeddingIndex(scan2, model)
32493
33363
  );
32494
- const results2 = searchSemantic(scan2, query, index, { model, limit, fuzzy });
32495
- return JSON.stringify({ results: results2, tier: "static" }, null, 2);
33364
+ const results3 = searchSemantic(scan2, query, index, { model, limit, fuzzy });
33365
+ return JSON.stringify({ results: results3, tier: "static" }, null, 2);
32496
33366
  }
32497
- const results = searchIndex(scan2, query, { limit, fuzzy, ...rankOpt });
33367
+ const results2 = searchIndex(scan2, query, { limit, fuzzy, ...rankOpt });
32498
33368
  return JSON.stringify(
32499
- { results, tier: "lexical", degradedReason: "no embedding endpoint or static model configured \u2014 see embed_status" },
33369
+ { results: results2, tier: "lexical", degradedReason: "no embedding endpoint or static model configured \u2014 see embed_status" },
32500
33370
  null,
32501
33371
  2
32502
33372
  );
32503
33373
  }
32504
- return JSON.stringify(searchIndex(scan2, query, { limit, fuzzy, ...rankOpt }), null, 2);
33374
+ const { results, explain } = explainQuery(scan2, query, { limit, fuzzy, ...exactOpt, ...rankOpt });
33375
+ return JSON.stringify(args2.explain === true ? { results, explain } : results, null, 2);
33376
+ }
33377
+ if (name2 === "explain_search") {
33378
+ const query = str(args2.query);
33379
+ if (!query) throw new Error("`query` is required");
33380
+ const scan2 = getScan(repo, scanOpts, walked);
33381
+ const limit = typeof args2.limit === "number" ? args2.limit : void 0;
33382
+ const fuzzy = typeof args2.fuzzy === "boolean" ? args2.fuzzy : void 0;
33383
+ const { results, explain } = explainQuery(scan2, query, {
33384
+ limit,
33385
+ fuzzy,
33386
+ ...args2.exact === true ? { exact: true } : {},
33387
+ ...rankOpt
33388
+ });
33389
+ return JSON.stringify({ results, explain }, null, 2);
32505
33390
  }
32506
33391
  if (name2 === "embed_status") {
32507
33392
  const modelDir = resolveEmbedModelDir(repo);
@@ -32552,9 +33437,9 @@ async function callTool(name2, args2, defaultRepo) {
32552
33437
  const configPath = str(args2.configPath);
32553
33438
  let payload = args2.rules;
32554
33439
  if (payload === void 0 && configPath) {
32555
- const abs = isAbsolute(configPath) ? configPath : join19(repo, configPath);
33440
+ const abs = isAbsolute(configPath) ? configPath : join22(repo, configPath);
32556
33441
  try {
32557
- payload = JSON.parse(readFileSync9(abs, "utf8"));
33442
+ payload = JSON.parse(readFileSync12(abs, "utf8"));
32558
33443
  } catch (e) {
32559
33444
  throw new Error(`cannot read rules from ${abs}: ${errMessage(e)}`);
32560
33445
  }
@@ -32572,7 +33457,7 @@ async function runMcpServer(opts = {}) {
32572
33457
  version: opts.serverInfo?.version ?? ENGINE_VERSION
32573
33458
  };
32574
33459
  let protocolVersion = PROTOCOL_VERSIONS[0];
32575
- let tools = toolsFor(opts.defaultRepo, protocolVersion);
33460
+ let tools = toolsFor(opts.defaultRepo, protocolVersion, opts.profile);
32576
33461
  const send = (msg) => {
32577
33462
  process.stdout.write(JSON.stringify({ jsonrpc: "2.0", ...msg }) + "\n");
32578
33463
  };
@@ -32595,7 +33480,7 @@ async function runMcpServer(opts = {}) {
32595
33480
  try {
32596
33481
  if (req.method === "initialize") {
32597
33482
  protocolVersion = negotiateProtocol(req.params?.protocolVersion);
32598
- tools = toolsFor(opts.defaultRepo, protocolVersion);
33483
+ tools = toolsFor(opts.defaultRepo, protocolVersion, opts.profile);
32599
33484
  send({
32600
33485
  id: req.id,
32601
33486
  result: {
@@ -32665,6 +33550,8 @@ var init_mcp = __esm({
32665
33550
  init_complexity();
32666
33551
  init_viz();
32667
33552
  init_query();
33553
+ init_lsp();
33554
+ init_onboard();
32668
33555
  init_edit();
32669
33556
  init_memory();
32670
33557
  init_bm25();
@@ -32675,10 +33562,10 @@ var init_mcp = __esm({
32675
33562
  init_endpoint();
32676
33563
  init_walk();
32677
33564
  init_tools();
32678
- init_protocol();
33565
+ init_protocol2();
32679
33566
  init_session();
32680
33567
  init_tools();
32681
- init_protocol();
33568
+ init_protocol2();
32682
33569
  init_session();
32683
33570
  SCANLESS_TOOLS = /* @__PURE__ */ new Set([
32684
33571
  "workspaces",
@@ -32881,7 +33768,7 @@ runExtractWorker(workerData.input, (o) => parentPort.postMessage(o)).catch((e) =
32881
33768
  try {
32882
33769
  const outputs = await Promise.all(
32883
33770
  shards.map(
32884
- (jobsForShard) => new Promise((resolve32, reject) => {
33771
+ (jobsForShard) => new Promise((resolve52, reject) => {
32885
33772
  const w = new Worker(bootstrap, {
32886
33773
  eval: true,
32887
33774
  workerData: { input: { jobs: jobsForShard, grammarKeys: wanted, maxCallsPerFile: opts.maxCallsPerFile } }
@@ -32895,7 +33782,7 @@ runExtractWorker(workerData.input, (o) => parentPort.postMessage(o)).catch((e) =
32895
33782
  fn();
32896
33783
  };
32897
33784
  w.once("message", (m) => {
32898
- settle(() => resolve32(m));
33785
+ settle(() => resolve52(m));
32899
33786
  void w.terminate();
32900
33787
  });
32901
33788
  w.once("error", (e) => settle(() => reject(e)));
@@ -33647,6 +34534,13 @@ init_encode();
33647
34534
  init_embed();
33648
34535
  init_search();
33649
34536
  init_endpoint();
34537
+ init_onboard();
34538
+ init_lsp();
34539
+ init_config2();
34540
+ init_client();
34541
+ init_spawn();
34542
+ init_protocol();
34543
+ init_refs();
33650
34544
  init_rules();
33651
34545
  init_coupling();
33652
34546
  init_repomap();
@@ -34065,6 +34959,8 @@ init_embed();
34065
34959
  init_search();
34066
34960
  init_endpoint();
34067
34961
  init_util();
34962
+ init_lsp();
34963
+ init_tools();
34068
34964
  var HELP = `codeindex engine v${ENGINE_VERSION} \u2014 deterministic repo indexing
34069
34965
 
34070
34966
  Usage: engine.mjs <command> [flags]
@@ -34098,6 +34994,15 @@ Commands:
34098
34994
  the source with CODEINDEX_EMBED_URL
34099
34995
  embed serve Print (or --run) the docker command that starts the
34100
34996
  containerized embedding server (rich tier)
34997
+ lsp Optional LSP tier (opt-in by asset \u2014 the tier is active only when
34998
+ <repo>/.codeindex/lsp.json exists, or CODEINDEX_LSP_CONFIG points
34999
+ at one). It annotates QUERY answers only and never touches
35000
+ graph.json/symbols.json:
35001
+ lsp status Config path and source, each server with whether
35002
+ its command is on PATH and how many files it
35003
+ claims, and the languages nothing covers (JSON).
35004
+ --probe also starts each server to read the
35005
+ capabilities it really advertises
34101
35006
  grammars Tree-sitter wasm grammars (optional AST tier; regex without them).
34102
35007
  Two tiers: CORE ships with the bundle; EXTENDED (kotlin, elixir,
34103
35008
  zig, solidity, hcl/terraform) arrives only via \`grammars pull\`.
@@ -34143,17 +35048,22 @@ Commands:
34143
35048
  and exits 0, or exits 1 when it has no opinion (run the original).
34144
35049
  Deliberately conservative \u2014 any shell metacharacter or unknown
34145
35050
  flag refuses the rewrite
34146
- mcp Run as an MCP server over stdio (30 tools: scan_summary, graph,
35051
+ mcp Run as an MCP server over stdio (33 tools: scan_summary, graph,
34147
35052
  symbols, callers, workspaces, churn, symbols_overview,
34148
- find_symbol, find_references, repo_map, hotspots, coupling,
34149
- dead_code, complexity, mermaid, grep, search, embed_status,
34150
- check_rules, the memory quartet and the three symbolic-edit
34151
- writes). Flags: --repo <dir> pins ONE repository so the per-tool
34152
- repo argument becomes optional (an explicit per-call repo still
34153
- wins); --server-name <name> overrides the announced serverInfo;
34154
- --max-response-bytes <n> caps a single tool response (default 1e6;
34155
- a response under the cap is byte-identical, one over it is
34156
- replaced by an actionable notice instead of an unusable blob)
35053
+ find_symbol, find_references, lsp_status, onboard, repo_map,
35054
+ hotspots, coupling, dead_code, complexity, mermaid, grep, search,
35055
+ explain_search, embed_status, check_rules, the memory quartet and
35056
+ the three symbolic-edit writes). Flags: --repo <dir> pins ONE
35057
+ repository so the per-tool repo argument becomes optional (an
35058
+ explicit per-call repo still wins); --server-name <name> overrides
35059
+ the announced serverInfo; --max-response-bytes <n> caps a single
35060
+ tool response (default 1e6; a response under the cap is
35061
+ byte-identical, one over it is replaced by an actionable notice
35062
+ instead of an unusable blob); --tools <profile[,profile]>
35063
+ advertises a named subset (all | orient | find | impact | edit |
35064
+ risk, default all) \u2014 every advertised tool's schema costs an agent
35065
+ context on EVERY turn, and a tool left out is still answerable
35066
+ when called by name
34157
35067
  version Print the engine version
34158
35068
 
34159
35069
  Flags (accepted before OR after the subcommand: '--repo X scan' and
@@ -34188,10 +35098,16 @@ Flags (accepted before OR after the subcommand: '--repo X scan' and
34188
35098
  --limit <n> Max results for \`search\` (default 20)
34189
35099
  --no-fuzzy \`search\`: disable trigram fuzzy fallback for query terms
34190
35100
  with zero document frequency (default: enabled)
35101
+ --exact \`search\`: drop results that carry no verbatim term match
35102
+ (the ones the stem/trigram bridge produced)
35103
+ --explain \`search\`: emit { results, explain } \u2014 which terms matched,
35104
+ which bridged, and whether the query really found anything
34191
35105
  --semantic \`search\`: RRF-fuse an embedding tier with lexical \u2014 the
34192
35106
  HTTP endpoint if CODEINDEX_EMBED_ENDPOINT is set, else a
34193
35107
  local static model (lexical-only when neither is available)
34194
35108
  --run \`embed serve\`: run the docker command instead of printing it
35109
+ --probe \`lsp status\`: start each server and read the capabilities
35110
+ it really advertises (default: no spawn)
34195
35111
  --recall \`callers\`: recall-oriented binding (issue #7) \u2014 relaxes
34196
35112
  the JS/TS import gate to unique repo-wide names and labels
34197
35113
  each site corroborated|unique-name
@@ -34217,10 +35133,10 @@ function parseFlags(args2) {
34217
35133
  if (!Number.isFinite(n) || n <= 0) throw new Error(`${a} expects a positive number, got "${raw}"`);
34218
35134
  return n;
34219
35135
  };
34220
- if (a === "--repo") flags2.repo = resolve2(next());
35136
+ if (a === "--repo") flags2.repo = resolve4(next());
34221
35137
  else if (a === "--out") {
34222
35138
  const v = next();
34223
- flags2.out = v === "-" ? "-" : resolve2(v);
35139
+ flags2.out = v === "-" ? "-" : resolve4(v);
34224
35140
  } else if (a === "--project-root") flags2.projectRoot = next();
34225
35141
  else if (a === "--include") flags2.include.push(next());
34226
35142
  else if (a === "--exclude") flags2.exclude.push(next());
@@ -34245,12 +35161,15 @@ function parseFlags(args2) {
34245
35161
  if (!Number.isInteger(n) || n < 0) throw new Error(`--workers expects a non-negative integer, got "${raw}"`);
34246
35162
  flags2.workers = n;
34247
35163
  } else if (a === "--since") flags2.since = next();
34248
- else if (a === "--config") flags2.config = resolve2(next());
35164
+ else if (a === "--config") flags2.config = resolve4(next());
34249
35165
  else if (a === "--limit") flags2.limit = num22();
34250
35166
  else if (a === "--no-fuzzy") flags2.fuzzy = false;
35167
+ else if (a === "--exact") flags2.exact = true;
35168
+ else if (a === "--explain") flags2.explain = true;
34251
35169
  else if (a === "--semantic") flags2.semantic = true;
34252
35170
  else if (a === "--recall") flags2.recall = true;
34253
35171
  else if (a === "--run") flags2.run = true;
35172
+ else if (a === "--probe") flags2.probe = true;
34254
35173
  else if (a === "--base") flags2.base = next();
34255
35174
  else if (a === "--staged") flags2.staged = true;
34256
35175
  else if (a === "--depth") flags2.depth = num22();
@@ -34294,12 +35213,13 @@ function parseMcpFlags(argv) {
34294
35213
  let defaultRepo;
34295
35214
  let name2;
34296
35215
  let maxResponseBytes;
35216
+ let profile;
34297
35217
  for (let i2 = 0; i2 < argv.length; i2++) {
34298
35218
  const a = argv[i2];
34299
35219
  if (a === "--repo") {
34300
35220
  const v = argv[++i2];
34301
35221
  if (!v) throw new Error("--repo requires a directory");
34302
- defaultRepo = resolve2(v);
35222
+ defaultRepo = resolve4(v);
34303
35223
  } else if (a === "--server-name") {
34304
35224
  const v = argv[++i2];
34305
35225
  if (!v) throw new Error("--server-name requires a value");
@@ -34309,12 +35229,17 @@ function parseMcpFlags(argv) {
34309
35229
  const n = Number(v);
34310
35230
  if (!v || !Number.isFinite(n) || n <= 0) throw new Error("--max-response-bytes requires a positive number");
34311
35231
  maxResponseBytes = n;
35232
+ } else if (a === "--tools") {
35233
+ const v = argv[++i2];
35234
+ if (!v) throw new Error(`--tools requires a profile: ${profileNames().join(", ")}`);
35235
+ toolsInProfiles(v);
35236
+ profile = v === "all" ? void 0 : v;
34312
35237
  } else {
34313
35238
  throw new Error(`unknown flag for \`mcp\`: ${a}`);
34314
35239
  }
34315
35240
  }
34316
- if (defaultRepo && !existsSync8(defaultRepo)) throw new Error(`--repo path does not exist: ${defaultRepo}`);
34317
- return { defaultRepo, serverInfo: name2 ? { name: name2 } : void 0, maxResponseBytes };
35241
+ if (defaultRepo && !existsSync10(defaultRepo)) throw new Error(`--repo path does not exist: ${defaultRepo}`);
35242
+ return { defaultRepo, serverInfo: name2 ? { name: name2 } : void 0, maxResponseBytes, profile };
34318
35243
  }
34319
35244
  var VALUE_FLAGS = /* @__PURE__ */ new Set([
34320
35245
  "--repo",
@@ -34335,6 +35260,7 @@ var VALUE_FLAGS = /* @__PURE__ */ new Set([
34335
35260
  "--config",
34336
35261
  "--limit",
34337
35262
  "--server-name",
35263
+ "--tools",
34338
35264
  "--workers",
34339
35265
  "--index",
34340
35266
  "--max-response-bytes"
@@ -34382,7 +35308,7 @@ async function runCli(rawArgv) {
34382
35308
  return;
34383
35309
  }
34384
35310
  const flags2 = parseFlags(rest);
34385
- if (!existsSync8(flags2.repo)) throw new Error(`--repo path does not exist: ${flags2.repo}`);
35311
+ if (!existsSync10(flags2.repo)) throw new Error(`--repo path does not exist: ${flags2.repo}`);
34386
35312
  const scans = !SCANLESS_COMMANDS.has(cmd) && !(cmd === "embed" && flags2.positional !== "build");
34387
35313
  let precomputedWalk;
34388
35314
  if (scans && !flags2.noAst) {
@@ -34416,11 +35342,11 @@ async function runCli(rawArgv) {
34416
35342
  if (!flags2.out) throw new Error("index needs --out <dir>");
34417
35343
  const outDir = flags2.out;
34418
35344
  mkdirSync3(outDir, { recursive: true });
34419
- const cachePath = join20(outDir, "cache.json");
35345
+ const cachePath = join23(outDir, "cache.json");
34420
35346
  let cache;
34421
35347
  let meta2 = {};
34422
35348
  try {
34423
- const parsed = JSON.parse(readFileSync10(cachePath, "utf8"));
35349
+ const parsed = JSON.parse(readFileSync13(cachePath, "utf8"));
34424
35350
  if (parsed.schemaVersion === SCHEMA_VERSION2 && parsed.extractorVersion === EXTRACTOR_VERSION) {
34425
35351
  cache = new Map(Object.entries(parsed.files));
34426
35352
  meta2 = {
@@ -34441,12 +35367,12 @@ async function runCli(rawArgv) {
34441
35367
  });
34442
35368
  const modelDir = resolveEmbedModelDir(flags2.repo);
34443
35369
  const model = modelDir ? loadEmbedModel(modelDir) : void 0;
34444
- const graphPath = join20(outDir, "graph.json");
34445
- const symbolsPath = join20(outDir, "symbols.json");
34446
- const embedPath = join20(outDir, "embeddings.bin");
35370
+ const graphPath = join23(outDir, "graph.json");
35371
+ const symbolsPath = join23(outDir, "symbols.json");
35372
+ const embedPath = join23(outDir, "embeddings.bin");
34447
35373
  const artifactSha = (path) => {
34448
35374
  try {
34449
- return sha1(readFileSync10(path));
35375
+ return sha1(readFileSync13(path));
34450
35376
  } catch {
34451
35377
  return void 0;
34452
35378
  }
@@ -34519,7 +35445,7 @@ async function runCli(rawArgv) {
34519
35445
  } else if (cmd === "scip") {
34520
35446
  const scan2 = readScan();
34521
35447
  const bytes = renderScip(scan2, { projectRoot: flags2.projectRoot });
34522
- const out2 = flags2.out ?? resolve2("index.scip");
35448
+ const out2 = flags2.out ?? resolve4("index.scip");
34523
35449
  if (out2 === "-") process.stdout.write(Buffer.from(bytes));
34524
35450
  else {
34525
35451
  writeFileSync4(out2, bytes);
@@ -34566,10 +35492,21 @@ async function runCli(rawArgv) {
34566
35492
  } else if (cmd === "search") {
34567
35493
  if (!flags2.positional) throw new Error('search needs a query: cli.mjs search "<query>" --repo <dir>');
34568
35494
  const scan2 = readScan();
35495
+ const searchOpts = {
35496
+ limit: flags2.limit,
35497
+ fuzzy: flags2.fuzzy,
35498
+ ...flags2.exact ? { exact: true } : {},
35499
+ ...flags2.rank ? { rank: flags2.rank } : {}
35500
+ };
35501
+ const warnIfWeak = () => {
35502
+ const { explain } = explainQuery(scan2, flags2.positional, searchOpts);
35503
+ if (explain.note) process.stderr.write(`codeindex: ${explain.note}
35504
+ `);
35505
+ };
34569
35506
  if (flags2.semantic) {
34570
35507
  const endpoint = resolveEmbedEndpoint();
34571
35508
  const lexical = () => {
34572
- const results = searchIndex(scan2, flags2.positional, { limit: flags2.limit, fuzzy: flags2.fuzzy, ...flags2.rank ? { rank: flags2.rank } : {} });
35509
+ const results = searchIndex(scan2, flags2.positional, searchOpts);
34573
35510
  emit(JSON.stringify(results, null, 2) + "\n", flags2.out);
34574
35511
  };
34575
35512
  if (endpoint) {
@@ -34599,9 +35536,12 @@ async function runCli(rawArgv) {
34599
35536
  emit(JSON.stringify(results, null, 2) + "\n", flags2.out);
34600
35537
  }
34601
35538
  }
35539
+ warnIfWeak();
34602
35540
  } else {
34603
- const results = searchIndex(scan2, flags2.positional, { limit: flags2.limit, fuzzy: flags2.fuzzy, ...flags2.rank ? { rank: flags2.rank } : {} });
34604
- emit(JSON.stringify(results, null, 2) + "\n", flags2.out);
35541
+ const { results, explain } = explainQuery(scan2, flags2.positional, searchOpts);
35542
+ emit(JSON.stringify(flags2.explain ? { results, explain } : results, null, 2) + "\n", flags2.out);
35543
+ if (explain.note) process.stderr.write(`codeindex: ${explain.note}
35544
+ `);
34605
35545
  }
34606
35546
  } else if (cmd === "embed") {
34607
35547
  const sub = flags2.positional;
@@ -34658,14 +35598,14 @@ async function runCli(rawArgv) {
34658
35598
  mkdirSync3(flags2.out, { recursive: true });
34659
35599
  const scan2 = readScan();
34660
35600
  const index = buildEmbeddingIndex(scan2, model);
34661
- writeFileSync4(join20(flags2.out, "embeddings.bin"), serializeEmbeddings(index));
35601
+ writeFileSync4(join23(flags2.out, "embeddings.bin"), serializeEmbeddings(index));
34662
35602
  process.stderr.write(`codeindex: ${index.records.length} embedding records \u2192 ${flags2.out}/embeddings.bin (model ${model.modelId})
34663
35603
  `);
34664
35604
  } else if (sub === "pull") {
34665
35605
  const { url, sha256 } = resolveEmbedPullUrl();
34666
- const destDir = process.env.CODEINDEX_EMBED_DIR ?? join20(flags2.repo, ".codeindex", "models");
35606
+ const destDir = process.env.CODEINDEX_EMBED_DIR ?? join23(flags2.repo, ".codeindex", "models");
34667
35607
  mkdirSync3(destDir, { recursive: true });
34668
- process.stderr.write(`codeindex: fetching model from ${url} \u2192 ${join20(destDir, "model.json")}
35608
+ process.stderr.write(`codeindex: fetching model from ${url} \u2192 ${join23(destDir, "model.json")}
34669
35609
  `);
34670
35610
  let body2;
34671
35611
  try {
@@ -34686,18 +35626,22 @@ async function runCli(rawArgv) {
34686
35626
  process.exitCode = 1;
34687
35627
  return;
34688
35628
  }
34689
- writeFileSync4(join20(destDir, "model.json"), body2);
34690
- process.stderr.write(`codeindex: model written to ${join20(destDir, "model.json")}
35629
+ writeFileSync4(join23(destDir, "model.json"), body2);
35630
+ process.stderr.write(`codeindex: model written to ${join23(destDir, "model.json")}
34691
35631
  `);
34692
35632
  } else {
34693
35633
  throw new Error("embed needs a subcommand: status | build | pull | serve");
34694
35634
  }
35635
+ } else if (cmd === "lsp") {
35636
+ const sub = flags2.positional;
35637
+ if (sub !== "status") throw new Error("lsp needs a subcommand: status");
35638
+ emit(JSON.stringify(await lspStatus(readScan(), flags2.repo, flags2.probe === true), null, 2) + "\n", flags2.out);
34695
35639
  } else if (cmd === "grammars") {
34696
35640
  const sub = flags2.positional;
34697
35641
  const cacheDir = sharedGrammarsCacheDir();
34698
35642
  if (sub === "status") {
34699
35643
  const info2 = resolveGrammarsTier();
34700
- const present = (name2) => info2.dirs.some((d) => existsSync8(join20(d, name2)));
35644
+ const present = (name2) => info2.dirs.some((d) => existsSync10(join23(d, name2)));
34701
35645
  const runtimePresent = present("web-tree-sitter.wasm");
34702
35646
  const target = resolveGrammarsPullTarget();
34703
35647
  const resolvedIn = (keys) => [...keys].filter((k) => present(`${k}.wasm`)).sort();
@@ -34729,7 +35673,7 @@ async function runCli(rawArgv) {
34729
35673
  }
34730
35674
  } else if (cmd === "rules") {
34731
35675
  if (!flags2.config) throw new Error("rules needs --config <codeindex.rules.json>");
34732
- const rules = parseRules(JSON.parse(readFileSync10(flags2.config, "utf8")));
35676
+ const rules = parseRules(JSON.parse(readFileSync13(flags2.config, "utf8")));
34733
35677
  const { graph } = readArtifacts();
34734
35678
  const violations = checkRules(graph, rules);
34735
35679
  const errors = violations.filter((v) => v.severity === "error").length;
@@ -34873,7 +35817,7 @@ function inScopeMatcher(inputs) {
34873
35817
  function walk2(dir, acc) {
34874
35818
  for (const f of walk(dir, { maxFiles: Number.MAX_SAFE_INTEGER }).files) {
34875
35819
  if (f.rel.split("/").some((seg) => SKIP_DIR.has(seg))) continue;
34876
- acc.push(join21(dir, f.rel));
35820
+ acc.push(join24(dir, f.rel));
34877
35821
  }
34878
35822
  }
34879
35823
  function staticBase(glob) {
@@ -34902,7 +35846,7 @@ function expandInputs(inputs, opts = {}) {
34902
35846
  const match = compileGlobs([input]);
34903
35847
  const acc = walkCached(staticBase(input), opts);
34904
35848
  for (const f of acc) if (match(toPosix(f)) && exts.has(ext(f))) files.add(f);
34905
- } else if (existsSync9(input)) {
35849
+ } else if (existsSync11(input)) {
34906
35850
  if (statSync6(input).isDirectory()) {
34907
35851
  const acc = walkCached(input, opts);
34908
35852
  for (const f of acc) if (exts.has(ext(f))) files.add(f);
@@ -35077,8 +36021,8 @@ function parseSourceWithAst(source, file, opts = {}) {
35077
36021
  }
35078
36022
 
35079
36023
  // src/snapshot.ts
35080
- import { existsSync as existsSync10, mkdirSync as mkdirSync4, readFileSync as readFileSync11, readdirSync as readdirSync4, writeFileSync as writeFileSync5 } from "fs";
35081
- import { dirname as dirname5, join as join22 } from "path";
36024
+ import { existsSync as existsSync12, mkdirSync as mkdirSync4, readFileSync as readFileSync14, readdirSync as readdirSync4, writeFileSync as writeFileSync5 } from "fs";
36025
+ import { dirname as dirname5, join as join25 } from "path";
35082
36026
 
35083
36027
  // src/collector.ts
35084
36028
  var COLLECTED_CSS = [
@@ -35248,12 +36192,12 @@ function validateSnapshotMeta(raw) {
35248
36192
  };
35249
36193
  }
35250
36194
  function snapshotDir(root, id) {
35251
- return join22(root, PAGES_DIR, id);
36195
+ return join25(root, PAGES_DIR, id);
35252
36196
  }
35253
36197
  function writeSnapshot(root, snap) {
35254
36198
  const dir = snapshotDir(root, snap.meta.id);
35255
36199
  mkdirSync4(dir, { recursive: true });
35256
- writeFileSync5(join22(dir, "meta.json"), `${JSON.stringify(snap.meta, null, 2)}
36200
+ writeFileSync5(join25(dir, "meta.json"), `${JSON.stringify(snap.meta, null, 2)}
35257
36201
  `);
35258
36202
  const comment = formatCaptureComment({
35259
36203
  v: 1,
@@ -35262,20 +36206,20 @@ function writeSnapshot(root, snap) {
35262
36206
  ...snap.meta.sources?.[0] ? { sourceFile: snap.meta.sources[0] } : {},
35263
36207
  name: snap.meta.name
35264
36208
  });
35265
- writeFileSync5(join22(dir, "dom.html"), `${comment}
36209
+ writeFileSync5(join25(dir, "dom.html"), `${comment}
35266
36210
  ${snap.dom}
35267
36211
  `);
35268
- if (snap.styles) writeFileSync5(join22(dir, "styles.json"), `${JSON.stringify(snap.styles)}
36212
+ if (snap.styles) writeFileSync5(join25(dir, "styles.json"), `${JSON.stringify(snap.styles)}
35269
36213
  `);
35270
- if (snap.boxes) writeFileSync5(join22(dir, "boxes.json"), `${JSON.stringify(snap.boxes)}
36214
+ if (snap.boxes) writeFileSync5(join25(dir, "boxes.json"), `${JSON.stringify(snap.boxes)}
35271
36215
  `);
35272
- if (snap.axtree) writeFileSync5(join22(dir, "axtree.json"), `${JSON.stringify(snap.axtree)}
36216
+ if (snap.axtree) writeFileSync5(join25(dir, "axtree.json"), `${JSON.stringify(snap.axtree)}
35273
36217
  `);
35274
- if (snap.css) writeFileSync5(join22(dir, "css.json"), `${JSON.stringify(snap.css)}
36218
+ if (snap.css) writeFileSync5(join25(dir, "css.json"), `${JSON.stringify(snap.css)}
35275
36219
  `);
35276
36220
  if (snap.screenshotBase64) {
35277
36221
  try {
35278
- writeFileSync5(join22(dir, "screen.png"), Buffer.from(snap.screenshotBase64, "base64"));
36222
+ writeFileSync5(join25(dir, "screen.png"), Buffer.from(snap.screenshotBase64, "base64"));
35279
36223
  } catch {
35280
36224
  }
35281
36225
  }
@@ -35283,27 +36227,27 @@ ${snap.dom}
35283
36227
  }
35284
36228
  function readJson2(file) {
35285
36229
  try {
35286
- return JSON.parse(readFileSync11(file, "utf8"));
36230
+ return JSON.parse(readFileSync14(file, "utf8"));
35287
36231
  } catch {
35288
36232
  return void 0;
35289
36233
  }
35290
36234
  }
35291
36235
  function readSnapshot(dir) {
35292
- const rawMeta = readJson2(join22(dir, "meta.json"));
36236
+ const rawMeta = readJson2(join25(dir, "meta.json"));
35293
36237
  if (rawMeta === void 0) return null;
35294
36238
  const v = validateSnapshotMeta(rawMeta);
35295
36239
  if (!v.ok || !v.meta) return null;
35296
36240
  let dom;
35297
36241
  try {
35298
- dom = readFileSync11(join22(dir, "dom.html"), "utf8");
36242
+ dom = readFileSync14(join25(dir, "dom.html"), "utf8");
35299
36243
  } catch {
35300
36244
  return null;
35301
36245
  }
35302
- const styles = readJson2(join22(dir, "styles.json"));
35303
- const boxes = readJson2(join22(dir, "boxes.json"));
35304
- const axtree = readJson2(join22(dir, "axtree.json"));
35305
- const css = readJson2(join22(dir, "css.json"));
35306
- const shot = join22(dir, "screen.png");
36246
+ const styles = readJson2(join25(dir, "styles.json"));
36247
+ const boxes = readJson2(join25(dir, "boxes.json"));
36248
+ const axtree = readJson2(join25(dir, "axtree.json"));
36249
+ const css = readJson2(join25(dir, "css.json"));
36250
+ const shot = join25(dir, "screen.png");
35307
36251
  return {
35308
36252
  meta: v.meta,
35309
36253
  dom,
@@ -35311,11 +36255,11 @@ function readSnapshot(dir) {
35311
36255
  ...boxes ? { boxes } : {},
35312
36256
  ...axtree ? { axtree } : {},
35313
36257
  ...css ? { css } : {},
35314
- ...existsSync10(shot) ? { screenshot: "screen.png" } : {}
36258
+ ...existsSync12(shot) ? { screenshot: "screen.png" } : {}
35315
36259
  };
35316
36260
  }
35317
36261
  function readSnapshots(root) {
35318
- const base = join22(root, PAGES_DIR);
36262
+ const base = join25(root, PAGES_DIR);
35319
36263
  let dirs;
35320
36264
  try {
35321
36265
  dirs = readdirSync4(base, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
@@ -35324,7 +36268,7 @@ function readSnapshots(root) {
35324
36268
  }
35325
36269
  const out2 = [];
35326
36270
  for (const d of dirs.sort()) {
35327
- const s = readSnapshot(join22(base, d));
36271
+ const s = readSnapshot(join25(base, d));
35328
36272
  if (s) out2.push(s);
35329
36273
  }
35330
36274
  return out2;
@@ -35366,11 +36310,11 @@ function align(doc, entries) {
35366
36310
  function attachSignals(doc) {
35367
36311
  if (!isSnapshotDom(doc.file)) return;
35368
36312
  const dir = dirname5(doc.file);
35369
- const styles = readJson2(join22(dir, "styles.json"));
35370
- const boxes = readJson2(join22(dir, "boxes.json"));
35371
- const axtree = readJson2(join22(dir, "axtree.json"));
35372
- const css = readJson2(join22(dir, "css.json"));
35373
- const shot = join22(dir, "screen.png");
36313
+ const styles = readJson2(join25(dir, "styles.json"));
36314
+ const boxes = readJson2(join25(dir, "boxes.json"));
36315
+ const axtree = readJson2(join25(dir, "axtree.json"));
36316
+ const css = readJson2(join25(dir, "css.json"));
36317
+ const shot = join25(dir, "screen.png");
35374
36318
  const alignedStyleMap = styles ? align(doc, styles.entries) : null;
35375
36319
  const alignedBoxMap = boxes ? align(doc, boxes.entries) : null;
35376
36320
  const truncated = Boolean(styles?.truncated || boxes?.truncated);
@@ -35380,7 +36324,7 @@ function attachSignals(doc) {
35380
36324
  ...axtree ? { axtree } : {},
35381
36325
  // Not element-indexed, so it needs no alignment — it is a property of the stylesheet.
35382
36326
  ...css ? { css } : {},
35383
- ...existsSync10(shot) ? { screenshot: shot } : {},
36327
+ ...existsSync12(shot) ? { screenshot: shot } : {},
35384
36328
  ...truncated ? { truncated } : {}
35385
36329
  };
35386
36330
  if (Object.keys(signals).length) doc.signals = signals;
@@ -39156,7 +40100,7 @@ var contrastLiteral = {
39156
40100
  var colorsRules = [contrastLiteral];
39157
40101
 
39158
40102
  // src/rules/rendered.ts
39159
- import { readFileSync as readFileSync12 } from "fs";
40103
+ import { readFileSync as readFileSync15 } from "fs";
39160
40104
 
39161
40105
  // src/pixel.ts
39162
40106
  import { deflateSync, inflateSync } from "zlib";
@@ -39483,7 +40427,7 @@ var renderedContrastPixel = {
39483
40427
  if (!shot || !boxes || !doc.signals?.styles) return [];
39484
40428
  let img = null;
39485
40429
  try {
39486
- img = decodePng(readFileSync12(shot));
40430
+ img = decodePng(readFileSync15(shot));
39487
40431
  } catch {
39488
40432
  return [];
39489
40433
  }
@@ -39793,7 +40737,7 @@ function crossToFinding(doc, ruleId, def, cf) {
39793
40737
  import { resolve as absPath } from "path";
39794
40738
 
39795
40739
  // src/graph/resolve.ts
39796
- import { dirname as dirname6, join as join23 } from "path";
40740
+ import { dirname as dirname6, join as join26 } from "path";
39797
40741
  var EXT_ORDER = [".tsx", ".jsx", ".ts", ".js"];
39798
40742
  function candidates(base) {
39799
40743
  const out2 = [base];
@@ -39803,7 +40747,7 @@ function candidates(base) {
39803
40747
  for (const e of EXT_ORDER) out2.push(stripped + e);
39804
40748
  }
39805
40749
  for (const e of EXT_ORDER) out2.push(base + e);
39806
- for (const e of EXT_ORDER) out2.push(join23(base, `index${e}`));
40750
+ for (const e of EXT_ORDER) out2.push(join26(base, `index${e}`));
39807
40751
  return out2;
39808
40752
  }
39809
40753
  function matchKnown(base, known) {
@@ -39815,7 +40759,7 @@ function matchKnown(base, known) {
39815
40759
  }
39816
40760
  function resolveSpecifier(fromFile, spec, known, aliases) {
39817
40761
  if (spec.startsWith(".")) {
39818
- return matchKnown(join23(dirname6(toPosix(fromFile)), spec), known);
40762
+ return matchKnown(join26(dirname6(toPosix(fromFile)), spec), known);
39819
40763
  }
39820
40764
  if (aliases?.length) {
39821
40765
  for (const rule of aliases) {
@@ -39823,7 +40767,7 @@ function resolveSpecifier(fromFile, spec, known, aliases) {
39823
40767
  if (!spec.startsWith(rule.prefix)) continue;
39824
40768
  const rest = spec.slice(rule.prefix.length);
39825
40769
  for (const base of rule.bases) {
39826
- const hit = matchKnown(join23(base, rest), known);
40770
+ const hit = matchKnown(join26(base, rest), known);
39827
40771
  if (hit) return hit;
39828
40772
  }
39829
40773
  } else if (spec === rule.prefix) {
@@ -39838,21 +40782,21 @@ function resolveSpecifier(fromFile, spec, known, aliases) {
39838
40782
  }
39839
40783
 
39840
40784
  // src/graph/tsconfig.ts
39841
- import { existsSync as existsSync11, readFileSync as readFileSync13 } from "fs";
39842
- import { dirname as dirname7, join as join24, resolve as resolve3, relative } from "path";
40785
+ import { existsSync as existsSync13, readFileSync as readFileSync16 } from "fs";
40786
+ import { dirname as dirname7, join as join27, resolve as resolve5, relative } from "path";
39843
40787
  function readJsonish(path) {
39844
40788
  try {
39845
- const raw = readFileSync13(path, "utf8").replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|[^:])\/\/[^\n]*/g, "$1").replace(/,(\s*[}\]])/g, "$1");
40789
+ const raw = readFileSync16(path, "utf8").replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|[^:])\/\/[^\n]*/g, "$1").replace(/,(\s*[}\]])/g, "$1");
39846
40790
  return JSON.parse(raw);
39847
40791
  } catch {
39848
40792
  return null;
39849
40793
  }
39850
40794
  }
39851
40795
  function findTsconfig(startDir) {
39852
- let dir = resolve3(startDir);
40796
+ let dir = resolve5(startDir);
39853
40797
  for (let i2 = 0; i2 < 30; i2++) {
39854
- const p = join24(dir, "tsconfig.json");
39855
- if (existsSync11(p)) return p;
40798
+ const p = join27(dir, "tsconfig.json");
40799
+ if (existsSync13(p)) return p;
39856
40800
  const parent = dirname7(dir);
39857
40801
  if (parent === dir) break;
39858
40802
  dir = parent;
@@ -39864,20 +40808,20 @@ function tsconfigChain(startDir) {
39864
40808
  if (!first) return [];
39865
40809
  const out2 = [];
39866
40810
  const seen = /* @__PURE__ */ new Set();
39867
- const queue = [resolve3(first)];
40811
+ const queue = [resolve5(first)];
39868
40812
  while (queue.length) {
39869
40813
  const p = queue.shift();
39870
40814
  if (seen.has(p)) continue;
39871
40815
  seen.add(p);
39872
- if (!existsSync11(p)) continue;
40816
+ if (!existsSync13(p)) continue;
39873
40817
  out2.push(p);
39874
40818
  const cfg = readJsonish(p);
39875
40819
  const exts = Array.isArray(cfg?.extends) ? cfg.extends : typeof cfg?.extends === "string" ? [cfg.extends] : [];
39876
40820
  const dir = dirname7(p);
39877
40821
  for (const e of exts) {
39878
40822
  if (typeof e !== "string") continue;
39879
- const cands = e.endsWith(".json") ? [resolve3(dir, e)] : [resolve3(dir, `${e}.json`), resolve3(dir, e, "tsconfig.json")];
39880
- const hit = cands.find((c2) => existsSync11(c2));
40823
+ const cands = e.endsWith(".json") ? [resolve5(dir, e)] : [resolve5(dir, `${e}.json`), resolve5(dir, e, "tsconfig.json")];
40824
+ const hit = cands.find((c2) => existsSync13(c2));
39881
40825
  if (hit) queue.push(hit);
39882
40826
  }
39883
40827
  }
@@ -39894,11 +40838,11 @@ function readTsAliases(startDir, cwd = process.cwd()) {
39894
40838
  for (const e of exts) {
39895
40839
  if (typeof e !== "string") continue;
39896
40840
  const ext2 = e.endsWith(".json") ? e : `${e}.json`;
39897
- const base = readJsonish(resolve3(root, ext2));
40841
+ const base = readJsonish(resolve5(root, ext2));
39898
40842
  if (base?.compilerOptions) co = { ...base.compilerOptions, ...co };
39899
40843
  }
39900
40844
  const baseUrl = typeof co.baseUrl === "string" ? co.baseUrl : ".";
39901
- const baseAbs = resolve3(root, baseUrl);
40845
+ const baseAbs = resolve5(root, baseUrl);
39902
40846
  const paths = co.paths ?? {};
39903
40847
  const out2 = [];
39904
40848
  for (const [pattern, targetsRaw] of Object.entries(paths)) {
@@ -39910,7 +40854,7 @@ function readTsAliases(startDir, cwd = process.cwd()) {
39910
40854
  const bases = targets.map((t2) => {
39911
40855
  const star = t2.indexOf("*");
39912
40856
  const tp = wildcard && star >= 0 ? t2.slice(0, star) : t2;
39913
- return toPosix(relative(cwd, join24(baseAbs, tp))) || ".";
40857
+ return toPosix(relative(cwd, join27(baseAbs, tp))) || ".";
39914
40858
  });
39915
40859
  out2.push({ prefix: prefix2, wildcard, bases });
39916
40860
  }
@@ -45151,9 +46095,9 @@ function buildGraphAndDocs(files, opts = {}) {
45151
46095
  }
45152
46096
 
45153
46097
  // src/discover.ts
45154
- import { existsSync as existsSync12 } from "fs";
46098
+ import { existsSync as existsSync14 } from "fs";
45155
46099
  import { execFileSync } from "child_process";
45156
- import { join as join25, relative as relative2 } from "path";
46100
+ import { join as join28, relative as relative2 } from "path";
45157
46101
  function git(args2, maxBuffer) {
45158
46102
  try {
45159
46103
  return execFileSync("git", args2, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], ...maxBuffer ? { maxBuffer } : {} });
@@ -45178,7 +46122,7 @@ function gitChangedFiles(ref) {
45178
46122
  add2(git(["diff", "--name-only", "--diff-filter=d", "--cached", base]));
45179
46123
  add2(git(["ls-files", "--others", "--exclude-standard"]));
45180
46124
  const cwd = process.cwd();
45181
- return [...out2].map((p) => relative2(cwd, join25(repoRoot2, p)));
46125
+ return [...out2].map((p) => relative2(cwd, join28(repoRoot2, p)));
45182
46126
  }
45183
46127
  function gitStagedFiles() {
45184
46128
  const top = git(["rev-parse", "--show-toplevel"]);
@@ -45187,7 +46131,7 @@ function gitStagedFiles() {
45187
46131
  if (out2 === null) return null;
45188
46132
  const repoRoot2 = top.trim();
45189
46133
  const cwd = process.cwd();
45190
- return out2.split("\n").map((s) => s.trim()).filter(Boolean).map((p) => relative2(cwd, join25(repoRoot2, p)));
46134
+ return out2.split("\n").map((s) => s.trim()).filter(Boolean).map((p) => relative2(cwd, join28(repoRoot2, p)));
45191
46135
  }
45192
46136
  function stagedContent(file) {
45193
46137
  return git(["show", `:./${toPosix(file)}`], 32 * 1024 * 1024);
@@ -45272,7 +46216,7 @@ function discover(inputs, opts = {}) {
45272
46216
  } else {
45273
46217
  const filter = makeFilter(opts);
45274
46218
  const inScope = inScopeMatcher(inputs);
45275
- files = changed.filter((f) => existsSync12(f) && filter(f) && (!inScope || inScope(f)));
46219
+ files = changed.filter((f) => existsSync14(f) && filter(f) && (!inScope || inScope(f)));
45276
46220
  }
45277
46221
  } else {
45278
46222
  files = expandInputs(inputs, opts);
@@ -45529,9 +46473,9 @@ function runAudit(opts) {
45529
46473
  // src/hook.ts
45530
46474
  import { execFileSync as execFileSync2 } from "child_process";
45531
46475
  import { createHash as createHash5 } from "crypto";
45532
- import { existsSync as existsSync15, mkdirSync as mkdirSync5, writeFileSync as writeFileSync6 } from "fs";
46476
+ import { existsSync as existsSync17, mkdirSync as mkdirSync5, writeFileSync as writeFileSync6 } from "fs";
45533
46477
  import { tmpdir } from "os";
45534
- import { join as join27 } from "path";
46478
+ import { join as join30 } from "path";
45535
46479
 
45536
46480
  // src/baseline.ts
45537
46481
  var RANK = { bloquant: 0, majeur: 1, mineur: 2 };
@@ -45575,11 +46519,11 @@ function baselineSummary(diff, lang = "fr") {
45575
46519
  }
45576
46520
 
45577
46521
  // src/config.ts
45578
- import { existsSync as existsSync14, statSync as statSync7 } from "fs";
45579
- import { join as join26, isAbsolute as isAbsolute2 } from "path";
46522
+ import { existsSync as existsSync16, statSync as statSync7 } from "fs";
46523
+ import { join as join29, isAbsolute as isAbsolute2 } from "path";
45580
46524
 
45581
46525
  // src/pack.ts
45582
- import { existsSync as existsSync13 } from "fs";
46526
+ import { existsSync as existsSync15 } from "fs";
45583
46527
 
45584
46528
  // src/standards/pack.ts
45585
46529
  function localize(pack, s, lang) {
@@ -45700,7 +46644,7 @@ function checkGuidance(ds, pack) {
45700
46644
  function runPackCheck(packPath, guidancePath) {
45701
46645
  const errors = [];
45702
46646
  const warnings = [];
45703
- if (!existsSync13(packPath)) return { ok: false, errors: [`pack file not found: ${packPath}`], warnings };
46647
+ if (!existsSync15(packPath)) return { ok: false, errors: [`pack file not found: ${packPath}`], warnings };
45704
46648
  let raw;
45705
46649
  try {
45706
46650
  raw = JSON.parse(readText(packPath));
@@ -45724,7 +46668,7 @@ function runPackCheck(packPath, guidancePath) {
45724
46668
  }
45725
46669
  }
45726
46670
  if (v.ok && v.pack && guidancePath) {
45727
- if (!existsSync13(guidancePath)) {
46671
+ if (!existsSync15(guidancePath)) {
45728
46672
  errors.push(`guidance file not found: ${guidancePath}`);
45729
46673
  } else {
45730
46674
  let gRaw;
@@ -48242,8 +49186,8 @@ function hasGuidance(packKey) {
48242
49186
  // src/config.ts
48243
49187
  var CONFIG_FILE = ".ultra11yrc.json";
48244
49188
  function loadConfig(cwd) {
48245
- const p = join26(cwd, CONFIG_FILE);
48246
- if (!existsSync14(p)) return null;
49189
+ const p = join29(cwd, CONFIG_FILE);
49190
+ if (!existsSync16(p)) return null;
48247
49191
  let parsed;
48248
49192
  try {
48249
49193
  parsed = JSON.parse(readText(p));
@@ -48256,16 +49200,16 @@ function loadConfig(cwd) {
48256
49200
  return parsed;
48257
49201
  }
48258
49202
  function packPaths(path) {
48259
- if (!existsSync14(path)) return null;
49203
+ if (!existsSync16(path)) return null;
48260
49204
  if (statSync7(path).isDirectory()) {
48261
- const pack = join26(path, "pack.json");
48262
- if (!existsSync14(pack)) return null;
48263
- const glossary = join26(path, "glossary.json");
48264
- const guidance = join26(path, "guidance.json");
49205
+ const pack = join29(path, "pack.json");
49206
+ if (!existsSync16(pack)) return null;
49207
+ const glossary = join29(path, "glossary.json");
49208
+ const guidance = join29(path, "guidance.json");
48265
49209
  return {
48266
49210
  pack,
48267
- glossary: existsSync14(glossary) ? glossary : void 0,
48268
- guidance: existsSync14(guidance) ? guidance : void 0
49211
+ glossary: existsSync16(glossary) ? glossary : void 0,
49212
+ guidance: existsSync16(guidance) ? guidance : void 0
48269
49213
  };
48270
49214
  }
48271
49215
  return { pack: path };
@@ -48281,7 +49225,7 @@ function loadRuntimeStandards(cwd, packFlags, onWarn, override = false) {
48281
49225
  }
48282
49226
  result.defaultStandard = config?.standard;
48283
49227
  for (const raw of [...config?.packs ?? [], ...packFlags]) {
48284
- const paths = packPaths(isAbsolute2(raw) ? raw : join26(cwd, raw));
49228
+ const paths = packPaths(isAbsolute2(raw) ? raw : join29(cwd, raw));
48285
49229
  if (!paths) {
48286
49230
  result.errors.push(`--pack ${raw}: not found (expected a pack JSON file or a directory with pack.json)`);
48287
49231
  continue;
@@ -48311,7 +49255,7 @@ ${formatIssues(v.issues).join("\n")}`);
48311
49255
  result.loadedPacks.push(v.pack.key);
48312
49256
  if (paths.guidance) loadGuidanceFile(paths.guidance, result, onWarn);
48313
49257
  }
48314
- for (const g of config?.guidance ?? []) loadGuidanceFile(isAbsolute2(g) ? g : join26(cwd, g), result, onWarn);
49258
+ for (const g of config?.guidance ?? []) loadGuidanceFile(isAbsolute2(g) ? g : join29(cwd, g), result, onWarn);
48315
49259
  for (const sm of config?.secondaryMappings ?? []) {
48316
49260
  if (!sm || typeof sm.standard !== "string" || typeof sm.ruleId !== "string" || typeof sm.criterion !== "string") {
48317
49261
  result.errors.push("secondaryMappings: each entry must be { standard, ruleId, criterion, note? }");
@@ -48326,7 +49270,7 @@ ${formatIssues(v.issues).join("\n")}`);
48326
49270
  return result;
48327
49271
  }
48328
49272
  function loadGuidanceFile(path, result, onWarn) {
48329
- if (!existsSync14(path)) {
49273
+ if (!existsSync16(path)) {
48330
49274
  onWarn(`ultra11y: guidance ${path} not found \u2014 skipping.`);
48331
49275
  return;
48332
49276
  }
@@ -48436,10 +49380,10 @@ function loopKey(sessionId, intent, findings) {
48436
49380
  }
48437
49381
  function firstSighting(key) {
48438
49382
  try {
48439
- const dir = join27(tmpdir(), "ultra11y-hook");
49383
+ const dir = join30(tmpdir(), "ultra11y-hook");
48440
49384
  mkdirSync5(dir, { recursive: true });
48441
- const marker = join27(dir, key);
48442
- if (existsSync15(marker)) return false;
49385
+ const marker = join30(dir, key);
49386
+ if (existsSync17(marker)) return false;
48443
49387
  writeFileSync6(marker, "");
48444
49388
  return true;
48445
49389
  } catch {
@@ -48522,12 +49466,12 @@ function decide(payload, deps = {}) {
48522
49466
  }
48523
49467
 
48524
49468
  // src/report.ts
48525
- import { copyFileSync, existsSync as existsSync16, mkdirSync as mkdirSync7, writeFileSync as writeFileSync8 } from "fs";
48526
- import { join as join29, relative as relative3 } from "path";
49469
+ import { copyFileSync, existsSync as existsSync18, mkdirSync as mkdirSync7, writeFileSync as writeFileSync8 } from "fs";
49470
+ import { join as join32, relative as relative3 } from "path";
48527
49471
 
48528
49472
  // src/prd.ts
48529
49473
  import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync7 } from "fs";
48530
- import { join as join28 } from "path";
49474
+ import { join as join31 } from "path";
48531
49475
 
48532
49476
  // src/standards/derive.ts
48533
49477
  function aggregate2(statuses) {
@@ -49309,7 +50253,7 @@ function renderPrdDoc(r, lang = "en", standard = "wcag") {
49309
50253
  function writePrd(r, opts) {
49310
50254
  mkdirSync6(opts.out, { recursive: true });
49311
50255
  if (opts.format === "doc") {
49312
- const p2 = join28(opts.out, `prd-doc-${r.date}.md`);
50256
+ const p2 = join31(opts.out, `prd-doc-${r.date}.md`);
49313
50257
  writeFileSync7(p2, renderPrdDoc(r, opts.lang, opts.standard));
49314
50258
  return [p2];
49315
50259
  }
@@ -49319,13 +50263,13 @@ function writePrd(r, opts) {
49319
50263
  const files = remediation ? renderPerCriterion(r, opts.lang, opts.standard) : renderAuditorPerCriterion(r, opts.lang, opts.standard, { technical });
49320
50264
  const paths = [];
49321
50265
  for (const f of files) {
49322
- const p2 = join28(opts.out, f.name);
50266
+ const p2 = join31(opts.out, f.name);
49323
50267
  writeFileSync7(p2, f.content);
49324
50268
  paths.push(p2);
49325
50269
  }
49326
50270
  return paths;
49327
50271
  }
49328
- const p = join28(opts.out, `prd-${r.date}.md`);
50272
+ const p = join31(opts.out, `prd-${r.date}.md`);
49329
50273
  writeFileSync7(p, remediation ? renderBacklog(r, opts.lang, opts.standard) : renderAuditorBacklog(r, opts.lang, opts.standard, { technical }));
49330
50274
  return [p];
49331
50275
  }
@@ -49748,12 +50692,13 @@ function render(r, lang, opts) {
49748
50692
  const group = ncUnits.filter((u) => u.severity === sev);
49749
50693
  if (!group.length) continue;
49750
50694
  out2.push(`### ${ICON3[sev]} ${s.sev[sev]} (${group.length})`, "");
49751
- for (const u of group) out2.push(...renderAuditorUnit(u, opts.standard, lang, { heading: "####" }));
50695
+ for (const u of group) out2.push(...renderAuditorUnit(u, opts.standard, lang, { heading: "####", ...opts.cropFor ? { cropFor: opts.cropFor } : {} }));
49752
50696
  }
49753
50697
  }
49754
50698
  if (advisoryUnits.length) {
49755
50699
  out2.push(`## \u{1F4A1} ${s.recTitle}`, "", `> ${s.recNote}`, "");
49756
- for (const u of advisoryUnits) out2.push(...renderAuditorUnit(u, opts.standard, lang, { heading: "###" }));
50700
+ for (const u of advisoryUnits)
50701
+ out2.push(...renderAuditorUnit(u, opts.standard, lang, { heading: "###", ...opts.cropFor ? { cropFor: opts.cropFor } : {} }));
49757
50702
  }
49758
50703
  const pageScope = pagesOf(r);
49759
50704
  if (pageScope.length) attributePages(r, pageScope);
@@ -49770,13 +50715,13 @@ function render(r, lang, opts) {
49770
50715
  out2.push(`- ${nc.length} ${s.ncCount}${adv.length ? ` \xB7 ${adv.length} ${s.advCount}` : ""}`);
49771
50716
  const notes = pageScope.find((x) => x.id === pg.id)?.notes;
49772
50717
  if (notes) out2.push(`- _${notes}_`);
49773
- const shot = join29(PAGES_DIR, pg.id, "screen.png");
49774
- if (existsSync16(shot)) {
50718
+ const shot = join32(PAGES_DIR, pg.id, "screen.png");
50719
+ if (existsSync18(shot)) {
49775
50720
  let href = relative3(opts.outDir ?? ".", shot).split("\\").join("/");
49776
50721
  if (opts.outDir) {
49777
50722
  try {
49778
- mkdirSync7(join29(opts.outDir, "assets"), { recursive: true });
49779
- copyFileSync(shot, join29(opts.outDir, "assets", `${pg.id}.png`));
50723
+ mkdirSync7(join32(opts.outDir, "assets"), { recursive: true });
50724
+ copyFileSync(shot, join32(opts.outDir, "assets", `${pg.id}.png`));
49780
50725
  href = `./assets/${pg.id}.png`;
49781
50726
  } catch {
49782
50727
  }
@@ -49837,9 +50782,9 @@ function reportGroups(r, lang = "en") {
49837
50782
  }
49838
50783
  return r.guidelines.map((g) => ({ key: g.key, title: guidelineTitle(g.key, lang) ?? g.title, rows: byGuideline.get(g.key) ?? [] }));
49839
50784
  }
49840
- function renderReport(r, lang = "en", outDir) {
50785
+ function renderReport(r, lang = "en", outDir, cropFor) {
49841
50786
  const s = L4[lang];
49842
- return render(r, lang, { std: s.wcagStd, groupHead: s.byGuideline, groups: reportGroups(r, lang), standard: CORE2, outDir });
50787
+ return render(r, lang, { std: s.wcagStd, groupHead: s.byGuideline, groups: reportGroups(r, lang), standard: CORE2, outDir, ...cropFor ? { cropFor } : {} });
49843
50788
  }
49844
50789
  function packReportGroups(r, pack, lang = "en") {
49845
50790
  const derived = derivePackResults(r, pack.key);
@@ -49862,7 +50807,7 @@ function packReportGroups(r, pack, lang = "en") {
49862
50807
  }
49863
50808
  return pack.themes.map((t2) => ({ key: `${t2.number}.`, title: themeName(pack, t2.number, lang) ?? "", rows: byTheme.get(t2.number) ?? [] }));
49864
50809
  }
49865
- function renderPackReport(r, pack, lang = "en", outDir) {
50810
+ function renderPackReport(r, pack, lang = "en", outDir, cropFor) {
49866
50811
  const derived = derivePackResults(r, pack.key);
49867
50812
  const std = `${pack.name} ${pack.baseVersion}`;
49868
50813
  return render(r, lang, {
@@ -49876,14 +50821,15 @@ function renderPackReport(r, pack, lang = "en", outDir) {
49876
50821
  // Forwarded, unlike before: without it the per-page screenshots resolved against the
49877
50822
  // CWD instead of the report's own directory, so a pack report written to `audits/`
49878
50823
  // carried links that only worked when read from the repo root.
49879
- outDir
50824
+ outDir,
50825
+ ...cropFor ? { cropFor } : {}
49880
50826
  });
49881
50827
  }
49882
50828
  function writeReport(r, opts) {
49883
50829
  const core = isCore(opts.standard);
49884
- const md = core ? renderReport(r, opts.lang, opts.out) : renderPackReport(r, loadPack(opts.standard), opts.lang, opts.out);
50830
+ const md = core ? renderReport(r, opts.lang, opts.out, opts.cropFor) : renderPackReport(r, loadPack(opts.standard), opts.lang, opts.out, opts.cropFor);
49885
50831
  mkdirSync7(opts.out, { recursive: true });
49886
- const path = join29(opts.out, `${core ? "wcag" : opts.standard}-${r.date}.md`);
50832
+ const path = join32(opts.out, `${core ? "wcag" : opts.standard}-${r.date}.md`);
49887
50833
  writeFileSync8(path, md);
49888
50834
  return path;
49889
50835
  }
@@ -50367,22 +51313,24 @@ function providerFromRemote() {
50367
51313
 
50368
51314
  // src/tickets/providers/github.ts
50369
51315
  var CAPABILITIES = { bodyLimit: 65536, labels: true };
50370
- function resolve4(opts) {
51316
+ function resolve6(opts) {
50371
51317
  const env = opts.env ?? process.env;
50372
51318
  const mode = opts.transport ?? "auto";
50373
51319
  const api = (env.GITHUB_API_URL || "https://api.github.com").replace(/\/+$/, "");
50374
51320
  const token = env.GH_TOKEN || env.GITHUB_TOKEN;
50375
- const cliOk = (opts.cliAvailable ?? ghAvailable)();
51321
+ const probe = opts.cliAvailable ?? ghAvailable;
51322
+ let cached;
51323
+ const cliOk = () => cached ??= probe();
50376
51324
  if (mode === "cli") {
50377
- return cliOk ? { transport: "cli", api } : { transport: "cli", api, reason: "`gh` is not installed or not authenticated (run `gh auth login`)" };
51325
+ return cliOk() ? { transport: "cli", api } : { transport: "cli", api, reason: "`gh` is not installed or not authenticated (run `gh auth login`)" };
50378
51326
  }
50379
- const repo = env.ULTRA11Y_GITHUB_REPO || env.GITHUB_REPOSITORY || (mode === "rest" || !cliOk ? gitRemoteSlug("github.com") : void 0);
51327
+ const repo = env.ULTRA11Y_GITHUB_REPO || env.GITHUB_REPOSITORY || (mode === "rest" || !cliOk() ? gitRemoteSlug("github.com") : void 0);
50380
51328
  if (mode === "rest") {
50381
51329
  if (!token) return { transport: "rest", api, reason: "no GitHub token \u2014 set GH_TOKEN or GITHUB_TOKEN" };
50382
51330
  if (!repo) return { transport: "rest", api, token, reason: "no repository \u2014 set GITHUB_REPOSITORY (owner/name) or ULTRA11Y_GITHUB_REPO" };
50383
51331
  return { transport: "rest", api, token, repo };
50384
51332
  }
50385
- if (cliOk) return { transport: "cli", api };
51333
+ if (cliOk()) return { transport: "cli", api };
50386
51334
  if (token && repo) return { transport: "rest", api, token, repo };
50387
51335
  const missing = [!token ? "a token (GH_TOKEN/GITHUB_TOKEN)" : "", !repo ? "a repository (GITHUB_REPOSITORY)" : ""].filter(Boolean);
50388
51336
  return {
@@ -50394,7 +51342,7 @@ function resolve4(opts) {
50394
51342
  };
50395
51343
  }
50396
51344
  function createGithubProvider(opts = {}) {
50397
- const r = resolve4(opts);
51345
+ const r = resolve6(opts);
50398
51346
  const auth = { Authorization: `Bearer ${r.token ?? ""}`, Accept: "application/vnd.github+json", "X-GitHub-Api-Version": "2022-11-28" };
50399
51347
  const http = {
50400
51348
  ...opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {},
@@ -50497,7 +51445,7 @@ function execReason(err2) {
50497
51445
  if (line) return line;
50498
51446
  return typeof e.message === "string" ? e.message.split("\n")[0]?.trim() ?? void 0 : void 0;
50499
51447
  }
50500
- function resolve5(opts) {
51448
+ function resolve7(opts) {
50501
51449
  const env = opts.env ?? process.env;
50502
51450
  const mode = opts.transport ?? "auto";
50503
51451
  const api = (env.CI_API_V4_URL || "https://gitlab.com/api/v4").replace(/\/+$/, "");
@@ -50517,7 +51465,7 @@ function resolve5(opts) {
50517
51465
  return { transport: "cli", api, jobToken };
50518
51466
  }
50519
51467
  function createGitlabProvider(opts = {}) {
50520
- const r = resolve5(opts);
51468
+ const r = resolve7(opts);
50521
51469
  const id = encodeURIComponent(r.project ?? "");
50522
51470
  const auth = { "PRIVATE-TOKEN": r.token ?? "" };
50523
51471
  const http = {
@@ -50586,7 +51534,7 @@ function createGitlabProvider(opts = {}) {
50586
51534
  var CAPABILITIES3 = { bodyLimit: 32767, labels: true };
50587
51535
  var JIRA_SCOPE_LABEL = "ultra11y";
50588
51536
  var PRIORITY = { bloquant: "Highest", majeur: "High", mineur: "Low" };
50589
- function resolve6(opts) {
51537
+ function resolve8(opts) {
50590
51538
  const env = opts.env ?? process.env;
50591
51539
  const issueType = env.ULTRA11Y_JIRA_ISSUE_TYPE || "Task";
50592
51540
  const apiVersion = env.ULTRA11Y_JIRA_API === "2" ? "2" : "3";
@@ -50605,7 +51553,7 @@ function toAdf(markdown) {
50605
51553
  return { type: "doc", version: 1, content: content.length ? content : [{ type: "paragraph", content: [] }] };
50606
51554
  }
50607
51555
  function createJiraProvider(opts = {}) {
50608
- const r = resolve6(opts);
51556
+ const r = resolve8(opts);
50609
51557
  const headers = { Authorization: r.auth ?? "" };
50610
51558
  const http = {
50611
51559
  ...opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {},
@@ -51109,12 +52057,12 @@ function renderCriteriaReference() {
51109
52057
  }
51110
52058
 
51111
52059
  // src/check.ts
51112
- import { existsSync as existsSync18, readFileSync as readFileSync15 } from "fs";
51113
- import { dirname as dirname9, join as join31 } from "path";
52060
+ import { existsSync as existsSync20, readFileSync as readFileSync18 } from "fs";
52061
+ import { dirname as dirname9, join as join34 } from "path";
51114
52062
 
51115
52063
  // src/verify.ts
51116
52064
  import { mkdirSync as mkdirSync8, writeFileSync as writeFileSync9 } from "fs";
51117
- import { join as join30 } from "path";
52065
+ import { join as join33 } from "path";
51118
52066
  var VERIFY_MAX = 40;
51119
52067
  var plain = (s) => s.replace(/\[([^\]]+)\]\(#[^)]*\)/g, "$1");
51120
52068
  function auditorCriterionLine(standard) {
@@ -51289,16 +52237,16 @@ function applyVerdicts(items, expected) {
51289
52237
  }
51290
52238
  function writeWorklist(items, outDir, semantic, standard = "wcag", lang = "en") {
51291
52239
  mkdirSync8(outDir, { recursive: true });
51292
- const todoPath = join30(outDir, "VERIFY.todo.json");
51293
- const mdPath = join30(outDir, "VERIFY.md");
52240
+ const todoPath = join33(outDir, "VERIFY.todo.json");
52241
+ const mdPath = join33(outDir, "VERIFY.md");
51294
52242
  writeFileSync9(todoPath, JSON.stringify(items, null, 2) + "\n");
51295
52243
  writeFileSync9(mdPath, formatWorklist(items, semantic, standard, lang));
51296
52244
  return { todoPath, mdPath, count: items.length };
51297
52245
  }
51298
52246
 
51299
52247
  // src/grounding.ts
51300
- import { existsSync as existsSync17, readFileSync as readFileSync14 } from "fs";
51301
- import { resolve as resolve7 } from "path";
52248
+ import { existsSync as existsSync19, readFileSync as readFileSync17 } from "fs";
52249
+ import { resolve as resolve9 } from "path";
51302
52250
  var WINDOW = 10;
51303
52251
  var norm2 = (s) => s.replace(/\s+/g, " ").trim();
51304
52252
  var isUnresolvable = (file) => !file || file === "-" || file === "<stdin>" || file === "stdin";
@@ -51315,11 +52263,11 @@ function selectorProbes(selector) {
51315
52263
  }
51316
52264
  function groundFinding(g, opts = {}) {
51317
52265
  if (isUnresolvable(g.file)) return { ok: true, moved: false };
51318
- const path = resolve7(opts.cwd ?? process.cwd(), g.file);
51319
- if (!existsSync17(path)) return { ok: false, moved: false, issue: `cited file not found: ${g.file}` };
52266
+ const path = resolve9(opts.cwd ?? process.cwd(), g.file);
52267
+ if (!existsSync19(path)) return { ok: false, moved: false, issue: `cited file not found: ${g.file}` };
51320
52268
  let text;
51321
52269
  try {
51322
- text = readFileSync14(path, "utf8");
52270
+ text = readFileSync17(path, "utf8");
51323
52271
  } catch {
51324
52272
  return { ok: false, moved: false, issue: `cited file unreadable: ${g.file}` };
51325
52273
  }
@@ -51516,7 +52464,8 @@ function renderPageReport(result, page, opts = {}) {
51516
52464
  const shot = opts.screenshots?.get(page.id);
51517
52465
  if (shot) out2.push(`![${s.screenshotAlt(page.name)}](${shot})`, "");
51518
52466
  else out2.push(`_${s.noScreenshot}_`, "");
51519
- if (opts.evidenceNotice?.length) out2.push(...opts.evidenceNotice, "");
52467
+ const refused = opts.evidenceNotice?.(page.id) ?? [];
52468
+ if (refused.length) out2.push(...refused, "");
51520
52469
  const withTests = rows.some((r) => r.tests.length);
51521
52470
  out2.push(`${h}# ${s.gridTitle}`, "", `> ${s.gridNote}`, "");
51522
52471
  out2.push(withTests ? `| ${s.criterion} | ${s.tests} | ${s.status} |` : `| ${s.criterion} | ${s.status} |`);
@@ -51705,11 +52654,11 @@ function checkSemantic(md, opts) {
51705
52654
  const standard = opts.standard ?? "wcag";
51706
52655
  const s = M[lang];
51707
52656
  const empty = { total: 0, grounded: 0, moved: 0, failed: 0 };
51708
- const artifact = opts.verdictsPath ?? join31(dirname9(opts.reportPath), "VERIFY.todo.json");
51709
- if (!existsSync18(artifact)) return { ok: false, issues: [s.semanticMissing(artifact)], ...empty };
52657
+ const artifact = opts.verdictsPath ?? join34(dirname9(opts.reportPath), "VERIFY.todo.json");
52658
+ if (!existsSync20(artifact)) return { ok: false, issues: [s.semanticMissing(artifact)], ...empty };
51710
52659
  let items;
51711
52660
  try {
51712
- const parsed = JSON.parse(readFileSync15(artifact, "utf8"));
52661
+ const parsed = JSON.parse(readFileSync18(artifact, "utf8"));
51713
52662
  if (!Array.isArray(parsed)) throw new Error("not an array");
51714
52663
  items = parsed;
51715
52664
  } catch {
@@ -51742,7 +52691,7 @@ function sectionBody(md, n) {
51742
52691
 
51743
52692
  // src/adjudicate.ts
51744
52693
  import { mkdirSync as mkdirSync9, writeFileSync as writeFileSync10 } from "fs";
51745
- import { join as join32 } from "path";
52694
+ import { join as join35 } from "path";
51746
52695
 
51747
52696
  // src/data/adjudication.json
51748
52697
  var adjudication_default = {
@@ -52677,7 +53626,7 @@ function docsForAudit(audit2, cwd) {
52677
53626
  const docs = [];
52678
53627
  for (const f of files) {
52679
53628
  try {
52680
- docs.push(parseSource(readText(cwd ? join32(cwd, f) : f), f));
53629
+ docs.push(parseSource(readText(cwd ? join35(cwd, f) : f), f));
52681
53630
  } catch {
52682
53631
  }
52683
53632
  }
@@ -53092,8 +54041,8 @@ function formatAdjudication(items, lang = "en", standard = CORE2) {
53092
54041
  }
53093
54042
  function writeAdjudication(items, outDir, opts) {
53094
54043
  mkdirSync9(outDir, { recursive: true });
53095
- const todoPath = join32(outDir, "ADJUDICATE.todo.json");
53096
- const mdPath = join32(outDir, "ADJUDICATE.md");
54044
+ const todoPath = join35(outDir, "ADJUDICATE.todo.json");
54045
+ const mdPath = join35(outDir, "ADJUDICATE.md");
53097
54046
  const file = {
53098
54047
  tool: "ultra11y",
53099
54048
  kind: "adjudication",
@@ -53298,9 +54247,9 @@ function applyRawVerdicts(items, verdicts) {
53298
54247
 
53299
54248
  // src/scan.ts
53300
54249
  import { execFileSync as execFileSync6 } from "child_process";
53301
- import { mkdtempSync as mkdtempSync2, writeFileSync as writeFileSync11, existsSync as existsSync20, statSync as statSync8, readdirSync as readdirSync5, rmSync as rmSync3, readFileSync as readFileSync16 } from "fs";
54250
+ import { mkdtempSync as mkdtempSync2, writeFileSync as writeFileSync11, existsSync as existsSync22, statSync as statSync8, readdirSync as readdirSync5, rmSync as rmSync3, readFileSync as readFileSync19 } from "fs";
53302
54251
  import { tmpdir as tmpdir2 } from "os";
53303
- import { join as join33, resolve as resolve8 } from "path";
54252
+ import { join as join36, resolve as resolve10 } from "path";
53304
54253
  import { fileURLToPath as fileURLToPath3 } from "url";
53305
54254
 
53306
54255
  // src/axe-map.ts
@@ -53560,7 +54509,7 @@ async function crawlUrls(start2, opts) {
53560
54509
  }
53561
54510
 
53562
54511
  // src/sample.ts
53563
- import { existsSync as existsSync19 } from "fs";
54512
+ import { existsSync as existsSync21 } from "fs";
53564
54513
  function looksLikeTarget(u) {
53565
54514
  return /^https?:\/\//i.test(u) || /^(\.\.?[/\\]|[/\\])/.test(u) || /\.x?html?$/i.test(u);
53566
54515
  }
@@ -53596,7 +54545,7 @@ function validateSample(raw) {
53596
54545
  if (p.auth !== void 0 && typeof p.auth !== "boolean") err2(`sample.pages[${i2}].auth`, "auth must be a boolean");
53597
54546
  if (p.storageState !== void 0 && (typeof p.storageState !== "string" || p.storageState.trim() === "")) {
53598
54547
  err2(`sample.pages[${i2}].storageState`, "storageState must be a non-empty file path string");
53599
- } else if (typeof p.storageState === "string" && p.storageState.trim() !== "" && !existsSync19(p.storageState)) {
54548
+ } else if (typeof p.storageState === "string" && p.storageState.trim() !== "" && !existsSync21(p.storageState)) {
53600
54549
  warn(
53601
54550
  `sample.pages[${i2}].storageState`,
53602
54551
  `storageState path not found: "${p.storageState}" (resolved from the current directory) \u2014 the scan will not be able to load this session`
@@ -53786,11 +54735,11 @@ function imageExists(tag) {
53786
54735
  }
53787
54736
  var CTX_PREFIX = "ultra11y-dyn-";
53788
54737
  function buildImage(tag = IMAGE_TAG) {
53789
- const ctx = mkdtempSync2(join33(tmpdir2(), CTX_PREFIX));
54738
+ const ctx = mkdtempSync2(join36(tmpdir2(), CTX_PREFIX));
53790
54739
  try {
53791
- writeFileSync11(join33(ctx, "runner.mjs"), RUNNER);
53792
- writeFileSync11(join33(ctx, "package.json"), PKG);
53793
- writeFileSync11(join33(ctx, "Dockerfile"), DOCKERFILE);
54740
+ writeFileSync11(join36(ctx, "runner.mjs"), RUNNER);
54741
+ writeFileSync11(join36(ctx, "package.json"), PKG);
54742
+ writeFileSync11(join36(ctx, "Dockerfile"), DOCKERFILE);
53794
54743
  execFileSync6("docker", ["build", "-t", tag, ctx], { stdio: "inherit", timeout: 9e5 });
53795
54744
  } finally {
53796
54745
  rmSync3(ctx, { recursive: true, force: true });
@@ -53801,7 +54750,7 @@ function cleanTempContexts() {
53801
54750
  const dir = tmpdir2();
53802
54751
  for (const name2 of readdirSync5(dir)) {
53803
54752
  if (!name2.startsWith(CTX_PREFIX)) continue;
53804
- rmSync3(join33(dir, name2), { recursive: true, force: true });
54753
+ rmSync3(join36(dir, name2), { recursive: true, force: true });
53805
54754
  removed++;
53806
54755
  }
53807
54756
  return removed;
@@ -53867,7 +54816,7 @@ var PROBE_FIELDS = [
53867
54816
  function runRunner(target, isFile, tag, snapshot = true) {
53868
54817
  const args2 = ["run", "--rm"];
53869
54818
  if (!snapshot) args2.push("-e", "ULTRA11Y_SNAPSHOT=0");
53870
- if (isFile) args2.push("-v", `${resolve8(target)}:${MOUNT}:ro`);
54819
+ if (isFile) args2.push("-v", `${resolve10(target)}:${MOUNT}:ro`);
53871
54820
  args2.push(tag, isFile ? MOUNT : target);
53872
54821
  let stdout;
53873
54822
  try {
@@ -53950,7 +54899,7 @@ function toDynamicResult(out2, target, lang = "en", engine = "axe-core@playwrigh
53950
54899
  var DOCKER_TESTED_SCS = ["1.4.10"];
53951
54900
  function runScan(opts) {
53952
54901
  const isUrl3 = /^https?:\/\//i.test(opts.target);
53953
- if (!isUrl3 && !existsSync20(opts.target)) {
54902
+ if (!isUrl3 && !existsSync22(opts.target)) {
53954
54903
  throw new Error(`File not found: ${opts.target}. Pass an http(s):// URL or an existing HTML file.`);
53955
54904
  }
53956
54905
  if (!dockerAvailable()) {
@@ -53958,7 +54907,7 @@ function runScan(opts) {
53958
54907
  }
53959
54908
  const tag = opts.tag ?? IMAGE_TAG;
53960
54909
  if (!imageExists(tag)) buildImage(tag);
53961
- const isFile = !isUrl3 && existsSync20(opts.target) && statSync8(opts.target).isFile();
54910
+ const isFile = !isUrl3 && existsSync22(opts.target) && statSync8(opts.target).isFile();
53962
54911
  const out2 = runRunner(opts.target, isFile, tag, Boolean(opts.snapshotRoot));
53963
54912
  const id = opts.snapshotRoot ? writeRunnerSnapshot(opts.snapshotRoot, out2, opts.target) : void 0;
53964
54913
  return { ...toDynamicResult(out2, opts.target), testedScs: [...DOCKER_TESTED_SCS], ...id ? { snapshots: [id] } : {} };
@@ -54045,10 +54994,10 @@ async function runCrawlScan(opts) {
54045
54994
  var sevRank = { bloquant: 3, majeur: 2, mineur: 1 };
54046
54995
  function resolveHostAnchor(file, snippet2) {
54047
54996
  const s = snippet2?.trim();
54048
- if (!s || !existsSync20(file)) return null;
54997
+ if (!s || !existsSync22(file)) return null;
54049
54998
  let source;
54050
54999
  try {
54051
- source = readFileSync16(file, "utf8");
55000
+ source = readFileSync19(file, "utf8");
54052
55001
  } catch {
54053
55002
  return null;
54054
55003
  }
@@ -54175,9 +55124,9 @@ function mergeSnapshotAudit(base, snap) {
54175
55124
  }
54176
55125
 
54177
55126
  // src/scan-local.ts
54178
- import { existsSync as existsSync21, statSync as statSync9 } from "fs";
55127
+ import { existsSync as existsSync23, statSync as statSync9 } from "fs";
54179
55128
  import { createRequire } from "module";
54180
- import { resolve as resolve9 } from "path";
55129
+ import { resolve as resolve11 } from "path";
54181
55130
  var LOCAL_ENGINE = "axe-core@playwright (local)";
54182
55131
  var LOCAL_TESTED_SCS = ["1.4.4", "1.4.10", "1.4.12", "2.4.7", "1.4.13"];
54183
55132
  function localTestedScs(interact) {
@@ -54187,7 +55136,7 @@ var PW_SPEC = "@playwright/test";
54187
55136
  var AXE_SPEC = "@axe-core/playwright";
54188
55137
  function localAvailable(cwd) {
54189
55138
  try {
54190
- const req = createRequire(resolve9(cwd, "package.json"));
55139
+ const req = createRequire(resolve11(cwd, "package.json"));
54191
55140
  req.resolve(PW_SPEC);
54192
55141
  req.resolve(AXE_SPEC);
54193
55142
  return true;
@@ -54199,7 +55148,7 @@ function resolveLocalDeps(cwd) {
54199
55148
  let chromium;
54200
55149
  let AxeBuilder;
54201
55150
  try {
54202
- const req = createRequire(resolve9(cwd, "package.json"));
55151
+ const req = createRequire(resolve11(cwd, "package.json"));
54203
55152
  const pw = req(PW_SPEC);
54204
55153
  const axeMod = req(AXE_SPEC);
54205
55154
  chromium = pw.chromium;
@@ -54678,7 +55627,7 @@ async function runOnPage(browser, AxeBuilder, target, isFile, opts) {
54678
55627
  const page = await context.newPage();
54679
55628
  const empty = [];
54680
55629
  try {
54681
- const url = isFile ? "file://" + resolve9(target) : target;
55630
+ const url = isFile ? "file://" + resolve11(target) : target;
54682
55631
  await page.goto(url, { waitUntil: "load", timeout: 45e3 });
54683
55632
  await page.waitForLoadState("networkidle", { timeout: 8e3 }).catch(() => {
54684
55633
  });
@@ -54746,7 +55695,7 @@ async function runOnPage(browser, AxeBuilder, target, isFile, opts) {
54746
55695
  }
54747
55696
  async function runScanLocal(opts) {
54748
55697
  const isUrl3 = /^https?:\/\//i.test(opts.target);
54749
- if (!isUrl3 && !existsSync21(opts.target)) {
55698
+ if (!isUrl3 && !existsSync23(opts.target)) {
54750
55699
  throw new Error(`File not found: ${opts.target}. Pass an http(s):// URL or an existing HTML file.`);
54751
55700
  }
54752
55701
  const isFile = !isUrl3 && statSync9(opts.target).isFile();
@@ -55333,7 +56282,7 @@ function fixSummary(r, lang = "fr", write = false) {
55333
56282
  // src/init.ts
55334
56283
  import { writeFileSync as writeFileSync13, mkdirSync as mkdirSync10, chmodSync, realpathSync as realpathSync2 } from "fs";
55335
56284
  import { execFileSync as execFileSync7 } from "child_process";
55336
- import { join as join34, relative as relative4, sep as sep4 } from "path";
56285
+ import { join as join37, relative as relative4, sep as sep4 } from "path";
55337
56286
  var EN_SEV = { bloquant: "blocking", majeur: "major", mineur: "minor" };
55338
56287
  function repoRoot() {
55339
56288
  try {
@@ -55451,23 +56400,23 @@ jobs:
55451
56400
  `;
55452
56401
  }
55453
56402
  function writeHook(root, enginePath, failOn, mode = "staged") {
55454
- const dir = join34(root, ".git", "hooks");
56403
+ const dir = join37(root, ".git", "hooks");
55455
56404
  mkdirSync10(dir, { recursive: true });
55456
- const path = join34(dir, "pre-commit");
56405
+ const path = join37(dir, "pre-commit");
55457
56406
  writeFileSync13(path, mode === "baseline" ? hookScript(enginePath, failOn) : stagedHookScript(enginePath, failOn));
55458
56407
  chmodSync(path, 493);
55459
56408
  return path;
55460
56409
  }
55461
56410
  function writeCi(root, enginePath, failOn) {
55462
- const dir = join34(root, ".github", "workflows");
56411
+ const dir = join37(root, ".github", "workflows");
55463
56412
  mkdirSync10(dir, { recursive: true });
55464
- const path = join34(dir, "a11y.yml");
56413
+ const path = join37(dir, "a11y.yml");
55465
56414
  writeFileSync13(path, ciWorkflow(enginePath, failOn));
55466
56415
  return path;
55467
56416
  }
55468
56417
 
55469
56418
  // src/install/json-edit.ts
55470
- import { copyFileSync as copyFileSync2, existsSync as existsSync22, mkdirSync as mkdirSync11, readFileSync as readFileSync17, renameSync as renameSync2, rmSync as rmSync4, writeFileSync as writeFileSync14 } from "fs";
56419
+ import { copyFileSync as copyFileSync2, existsSync as existsSync24, mkdirSync as mkdirSync11, readFileSync as readFileSync20, renameSync as renameSync2, rmSync as rmSync4, writeFileSync as writeFileSync14 } from "fs";
55471
56420
  import { dirname as dirname10 } from "path";
55472
56421
  var SettingsParseError = class extends Error {
55473
56422
  constructor(path, cause) {
@@ -55481,10 +56430,10 @@ function stamp() {
55481
56430
  return (/* @__PURE__ */ new Date()).toISOString().replaceAll(/[:.]/g, "-");
55482
56431
  }
55483
56432
  function writeTextWithBackup(path, content, marker = "ultra11y") {
55484
- if (existsSync22(path) && readFileSync17(path, "utf8") === content) return { path, changed: false };
56433
+ if (existsSync24(path) && readFileSync20(path, "utf8") === content) return { path, changed: false };
55485
56434
  mkdirSync11(dirname10(path), { recursive: true });
55486
56435
  let backup;
55487
- if (existsSync22(path)) {
56436
+ if (existsSync24(path)) {
55488
56437
  backup = `${path}.${marker}-backup-${stamp()}`;
55489
56438
  copyFileSync2(path, backup);
55490
56439
  }
@@ -55500,8 +56449,8 @@ function writeTextWithBackup(path, content, marker = "ultra11y") {
55500
56449
  }
55501
56450
  function editJsonFile(path, mutate) {
55502
56451
  let root = {};
55503
- if (existsSync22(path)) {
55504
- const raw = readFileSync17(path, "utf8");
56452
+ if (existsSync24(path)) {
56453
+ const raw = readFileSync20(path, "utf8");
55505
56454
  if (raw.trim()) {
55506
56455
  let parsed;
55507
56456
  try {
@@ -55517,13 +56466,13 @@ function editJsonFile(path, mutate) {
55517
56466
  }
55518
56467
  const before = JSON.stringify(root);
55519
56468
  mutate(root);
55520
- if (JSON.stringify(root) === before && existsSync22(path)) return { path, changed: false };
56469
+ if (JSON.stringify(root) === before && existsSync24(path)) return { path, changed: false };
55521
56470
  return writeTextWithBackup(path, `${JSON.stringify(root, null, 2)}
55522
56471
  `);
55523
56472
  }
55524
56473
  function readJsonSafe(path) {
55525
56474
  try {
55526
- const parsed = JSON.parse(readFileSync17(path, "utf8"));
56475
+ const parsed = JSON.parse(readFileSync20(path, "utf8"));
55527
56476
  return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
55528
56477
  } catch {
55529
56478
  return null;
@@ -55564,7 +56513,7 @@ function claudeCodeWired(settingsPath) {
55564
56513
  }
55565
56514
 
55566
56515
  // src/install/text-edit.ts
55567
- import { existsSync as existsSync23, readFileSync as readFileSync18, rmSync as rmSync5 } from "fs";
56516
+ import { existsSync as existsSync25, readFileSync as readFileSync21, rmSync as rmSync5 } from "fs";
55568
56517
  var BLOCK_BEGIN = "<!-- BEGIN ultra11y (managed by `ultra11y install --agents-md`; edit outside this block) -->";
55569
56518
  var BLOCK_END = "<!-- END ultra11y -->";
55570
56519
  var blockRe = () => new RegExp(`${escapeRe2(BLOCK_BEGIN)}[\\s\\S]*?${escapeRe2(BLOCK_END)}\\n?`);
@@ -55576,15 +56525,15 @@ function upsertManagedBlock(path, body2) {
55576
56525
  ${body2.trim()}
55577
56526
  ${BLOCK_END}
55578
56527
  `;
55579
- if (!existsSync23(path)) return writeTextWithBackup(path, block);
55580
- const current = readFileSync18(path, "utf8");
56528
+ if (!existsSync25(path)) return writeTextWithBackup(path, block);
56529
+ const current = readFileSync21(path, "utf8");
55581
56530
  if (blockRe().test(current)) return writeTextWithBackup(path, current.replace(blockRe(), block));
55582
56531
  const sep8 = current.endsWith("\n\n") ? "" : current.endsWith("\n") ? "\n" : "\n\n";
55583
56532
  return writeTextWithBackup(path, `${current}${sep8}${block}`);
55584
56533
  }
55585
56534
  function removeManagedBlock(path) {
55586
- if (!existsSync23(path)) return { path, changed: false };
55587
- const current = readFileSync18(path, "utf8");
56535
+ if (!existsSync25(path)) return { path, changed: false };
56536
+ const current = readFileSync21(path, "utf8");
55588
56537
  if (!blockRe().test(current)) return { path, changed: false };
55589
56538
  const rest = current.replace(blockRe(), "");
55590
56539
  if (rest.trim() === "") {
@@ -55594,13 +56543,13 @@ function removeManagedBlock(path) {
55594
56543
  return writeTextWithBackup(path, rest.replace(/\n+$/, "\n"));
55595
56544
  }
55596
56545
  function hasManagedBlock(path) {
55597
- return existsSync23(path) && blockRe().test(readFileSync18(path, "utf8"));
56546
+ return existsSync25(path) && blockRe().test(readFileSync21(path, "utf8"));
55598
56547
  }
55599
56548
 
55600
56549
  // src/install/agents-md.ts
55601
- import { join as join35 } from "path";
56550
+ import { join as join38 } from "path";
55602
56551
  function agentsMdPath(root) {
55603
- return join35(root, "AGENTS.md");
56552
+ return join38(root, "AGENTS.md");
55604
56553
  }
55605
56554
  function agentsMdBlock(root) {
55606
56555
  const e = engineInvocation(root);
@@ -55655,25 +56604,25 @@ function agentsMdWired(root) {
55655
56604
  }
55656
56605
 
55657
56606
  // src/install/codex.ts
55658
- import { existsSync as existsSync25, readFileSync as readFileSync20 } from "fs";
55659
- import { join as join37 } from "path";
56607
+ import { existsSync as existsSync27, readFileSync as readFileSync24 } from "fs";
56608
+ import { join as join40 } from "path";
55660
56609
 
55661
56610
  // src/install/paths.ts
55662
- import { copyFileSync as copyFileSync3, cpSync, existsSync as existsSync24, mkdirSync as mkdirSync12, readFileSync as readFileSync19, realpathSync as realpathSync3, statSync as statSync10 } from "fs";
56611
+ import { copyFileSync as copyFileSync3, cpSync, existsSync as existsSync26, mkdirSync as mkdirSync12, readFileSync as readFileSync23, realpathSync as realpathSync3, statSync as statSync10 } from "fs";
55663
56612
  import { homedir as homedir2 } from "os";
55664
- import { dirname as dirname11, join as join36 } from "path";
56613
+ import { dirname as dirname11, join as join39 } from "path";
55665
56614
  function codexHome() {
55666
- return process.env.CODEX_HOME || join36(homedir2(), ".codex");
56615
+ return process.env.CODEX_HOME || join39(homedir2(), ".codex");
55667
56616
  }
55668
56617
  function opencodeConfigDir() {
55669
56618
  const xdg = process.env.XDG_CONFIG_HOME;
55670
- return join36(xdg && xdg !== "" ? xdg : join36(homedir2(), ".config"), "opencode");
56619
+ return join39(xdg && xdg !== "" ? xdg : join39(homedir2(), ".config"), "opencode");
55671
56620
  }
55672
56621
  function claudeSettingsPath(project, cwd = process.cwd()) {
55673
- return project ? join36(cwd, ".claude", "settings.json") : join36(homedir2(), ".claude", "settings.json");
56622
+ return project ? join39(cwd, ".claude", "settings.json") : join39(homedir2(), ".claude", "settings.json");
55674
56623
  }
55675
56624
  function pinnedEnginePath() {
55676
- return join36(homedir2(), ".ultra11y", "bin", "ultra11y.mjs");
56625
+ return join39(homedir2(), ".ultra11y", "bin", "ultra11y.mjs");
55677
56626
  }
55678
56627
  function installedCliCommand() {
55679
56628
  const override = process.env.ULTRA11Y_BIN;
@@ -55688,7 +56637,7 @@ function installedCliCommand() {
55688
56637
  if (source && !ephemeral) return `node ${JSON.stringify(source)}`;
55689
56638
  const pin = pinnedEnginePath();
55690
56639
  if (source) {
55691
- mkdirSync12(join36(pin, ".."), { recursive: true });
56640
+ mkdirSync12(join39(pin, ".."), { recursive: true });
55692
56641
  copyFileSync3(source, pin);
55693
56642
  }
55694
56643
  return `node ${JSON.stringify(pin)}`;
@@ -55701,32 +56650,32 @@ function packageRoot() {
55701
56650
  return null;
55702
56651
  }
55703
56652
  for (let i2 = 0; i2 < 6 && dir && dir !== "/"; i2++) {
55704
- if (existsSync24(join36(dir, "skills", "review-a11y", "SKILL.md"))) return dir;
56653
+ if (existsSync26(join39(dir, "skills", "review-a11y", "SKILL.md"))) return dir;
55705
56654
  dir = dirname11(dir);
55706
56655
  }
55707
56656
  return null;
55708
56657
  }
55709
56658
  function bundledSkillsDir() {
55710
56659
  const root = packageRoot();
55711
- const p = root ? join36(root, "skills") : null;
55712
- return p && existsSync24(p) ? p : null;
56660
+ const p = root ? join39(root, "skills") : null;
56661
+ return p && existsSync26(p) ? p : null;
55713
56662
  }
55714
56663
  function bundledOpencodePlugin() {
55715
56664
  const root = packageRoot();
55716
- const p = root ? join36(root, ".opencode", "plugins", "ultra11y.js") : null;
55717
- return p && existsSync24(p) ? p : null;
56665
+ const p = root ? join39(root, ".opencode", "plugins", "ultra11y.js") : null;
56666
+ return p && existsSync26(p) ? p : null;
55718
56667
  }
55719
56668
  function copySkillsInto(source, dest) {
55720
56669
  const out2 = [];
55721
56670
  for (const name2 of ["ultra11y", "review-a11y"]) {
55722
- const from = join36(source, name2);
55723
- if (!existsSync24(from)) continue;
55724
- const to = join36(dest, name2);
55725
- const same = ["SKILL.md", join36("scripts", "ultra11y.mjs")].every((f) => {
55726
- const a = join36(from, f);
55727
- const b = join36(to, f);
56671
+ const from = join39(source, name2);
56672
+ if (!existsSync26(from)) continue;
56673
+ const to = join39(dest, name2);
56674
+ const same = ["SKILL.md", join39("scripts", "ultra11y.mjs")].every((f) => {
56675
+ const a = join39(from, f);
56676
+ const b = join39(to, f);
55728
56677
  try {
55729
- return existsSync24(b) && statSync10(a).size === statSync10(b).size && readFileSync19(a).equals(readFileSync19(b));
56678
+ return existsSync26(b) && statSync10(a).size === statSync10(b).size && readFileSync23(a).equals(readFileSync23(b));
55730
56679
  } catch {
55731
56680
  return false;
55732
56681
  }
@@ -55776,7 +56725,7 @@ function featuresBody(content) {
55776
56725
  function installCodex({ codexDir, command, skillsSource }) {
55777
56726
  const reports = [];
55778
56727
  reports.push(
55779
- editJsonFile(join37(codexDir, "hooks.json"), (root) => {
56728
+ editJsonFile(join40(codexDir, "hooks.json"), (root) => {
55780
56729
  const hooks = root.hooks ??= {};
55781
56730
  hooks.PreToolUse = [
55782
56731
  ...withoutOurs2(hooks.PreToolUse),
@@ -55784,11 +56733,11 @@ function installCodex({ codexDir, command, skillsSource }) {
55784
56733
  ];
55785
56734
  })
55786
56735
  );
55787
- const configPath = join37(codexDir, "config.toml");
55788
- const current = existsSync25(configPath) ? readFileSync20(configPath, "utf8") : "";
56736
+ const configPath = join40(codexDir, "config.toml");
56737
+ const current = existsSync27(configPath) ? readFileSync24(configPath, "utf8") : "";
55789
56738
  const edit = enableHooksFeature(current);
55790
56739
  if (edit.changed) reports.push(writeTextWithBackup(configPath, edit.content));
55791
- if (skillsSource) reports.push(...copySkillsInto(skillsSource, join37(codexDir, "skills")));
56740
+ if (skillsSource) reports.push(...copySkillsInto(skillsSource, join40(codexDir, "skills")));
55792
56741
  return {
55793
56742
  reports,
55794
56743
  guidance: [
@@ -55803,8 +56752,8 @@ function installCodex({ codexDir, command, skillsSource }) {
55803
56752
  }
55804
56753
  function uninstallCodex({ codexDir }) {
55805
56754
  const reports = [];
55806
- const hooksPath = join37(codexDir, "hooks.json");
55807
- if (existsSync25(hooksPath)) {
56755
+ const hooksPath = join40(codexDir, "hooks.json");
56756
+ if (existsSync27(hooksPath)) {
55808
56757
  reports.push(
55809
56758
  editJsonFile(hooksPath, (root) => {
55810
56759
  const hooks = root.hooks;
@@ -55819,33 +56768,33 @@ function uninstallCodex({ codexDir }) {
55819
56768
  return { reports, guidance: ["codex: `[features] hooks = true` was left enabled \u2014 other hooks may depend on it."] };
55820
56769
  }
55821
56770
  function codexWired(codexDir) {
55822
- const root = readJsonSafe(join37(codexDir, "hooks.json"));
56771
+ const root = readJsonSafe(join40(codexDir, "hooks.json"));
55823
56772
  const groups = root?.hooks?.PreToolUse;
55824
56773
  if (!Array.isArray(groups)) return 0;
55825
56774
  return groups.reduce((n, g) => n + (g.hooks ?? []).filter((h) => String(h.command ?? "").includes(CODEX_MARKER)).length, 0);
55826
56775
  }
55827
56776
  function codexHooksEnabled(codexDir) {
55828
- const p = join37(codexDir, "config.toml");
55829
- return existsSync25(p) && HOOKS_ON.test(featuresBody(readFileSync20(p, "utf8")));
56777
+ const p = join40(codexDir, "config.toml");
56778
+ return existsSync27(p) && HOOKS_ON.test(featuresBody(readFileSync24(p, "utf8")));
55830
56779
  }
55831
56780
 
55832
56781
  // src/install/opencode.ts
55833
- import { existsSync as existsSync26, readFileSync as readFileSync21, rmSync as rmSync6 } from "fs";
55834
- import { join as join38 } from "path";
56782
+ import { existsSync as existsSync28, readFileSync as readFileSync25, rmSync as rmSync6 } from "fs";
56783
+ import { join as join41 } from "path";
55835
56784
  var OPENCODE_MARKER = "ULTRA11Y_OPENCODE_PLUGIN";
55836
56785
  function pluginTarget(configDir) {
55837
- return join38(configDir, "plugin", "ultra11y.js");
56786
+ return join41(configDir, "plugin", "ultra11y.js");
55838
56787
  }
55839
56788
  function installOpencode({ configDir, pluginSource, skillsSource, enginePath }) {
55840
56789
  const target = pluginTarget(configDir);
55841
- if (existsSync26(target) && !readFileSync21(target, "utf8").includes(OPENCODE_MARKER)) {
56790
+ if (existsSync28(target) && !readFileSync25(target, "utf8").includes(OPENCODE_MARKER)) {
55842
56791
  throw new Error(`refusing to overwrite ${target}: it is not a file ultra11y wrote. Remove it manually if you want the plugin here.`);
55843
56792
  }
55844
- const reports = [writeTextWithBackup(target, readFileSync21(pluginSource, "utf8"))];
55845
- if (enginePath && existsSync26(enginePath)) {
55846
- reports.push(writeTextWithBackup(join38(configDir, "plugin", "ultra11y.mjs"), readFileSync21(enginePath, "utf8")));
56793
+ const reports = [writeTextWithBackup(target, readFileSync25(pluginSource, "utf8"))];
56794
+ if (enginePath && existsSync28(enginePath)) {
56795
+ reports.push(writeTextWithBackup(join41(configDir, "plugin", "ultra11y.mjs"), readFileSync25(enginePath, "utf8")));
55847
56796
  }
55848
- if (skillsSource) reports.push(...copySkillsInto(skillsSource, join38(configDir, "skills")));
56797
+ if (skillsSource) reports.push(...copySkillsInto(skillsSource, join41(configDir, "skills")));
55849
56798
  return {
55850
56799
  reports,
55851
56800
  guidance: [
@@ -55858,13 +56807,13 @@ function installOpencode({ configDir, pluginSource, skillsSource, enginePath })
55858
56807
  function uninstallOpencode({ configDir }) {
55859
56808
  const reports = [];
55860
56809
  const target = pluginTarget(configDir);
55861
- if (existsSync26(target) && readFileSync21(target, "utf8").includes(OPENCODE_MARKER)) {
56810
+ if (existsSync28(target) && readFileSync25(target, "utf8").includes(OPENCODE_MARKER)) {
55862
56811
  rmSync6(target, { force: true });
55863
- rmSync6(join38(configDir, "plugin", "ultra11y.mjs"), { force: true });
56812
+ rmSync6(join41(configDir, "plugin", "ultra11y.mjs"), { force: true });
55864
56813
  reports.push({ path: target, changed: true });
55865
56814
  }
55866
- const configPath = join38(configDir, "opencode.json");
55867
- if (existsSync26(configPath)) {
56815
+ const configPath = join41(configDir, "opencode.json");
56816
+ if (existsSync28(configPath)) {
55868
56817
  reports.push(
55869
56818
  editJsonFile(configPath, (root) => {
55870
56819
  if (!Array.isArray(root.plugin)) return;
@@ -55878,9 +56827,9 @@ function uninstallOpencode({ configDir }) {
55878
56827
  }
55879
56828
  function opencodeWired(configDir) {
55880
56829
  const target = pluginTarget(configDir);
55881
- if (existsSync26(target) && readFileSync21(target, "utf8").includes(OPENCODE_MARKER)) return true;
56830
+ if (existsSync28(target) && readFileSync25(target, "utf8").includes(OPENCODE_MARKER)) return true;
55882
56831
  try {
55883
- const cfg = JSON.parse(readFileSync21(join38(configDir, "opencode.json"), "utf8"));
56832
+ const cfg = JSON.parse(readFileSync25(join41(configDir, "opencode.json"), "utf8"));
55884
56833
  return Array.isArray(cfg.plugin) && cfg.plugin.some((p) => /^ultra11y(@|$)/.test(String(p)));
55885
56834
  } catch {
55886
56835
  return false;
@@ -55888,8 +56837,8 @@ function opencodeWired(configDir) {
55888
56837
  }
55889
56838
 
55890
56839
  // src/install/index.ts
55891
- import { existsSync as existsSync27 } from "fs";
55892
- import { join as join39 } from "path";
56840
+ import { existsSync as existsSync29 } from "fs";
56841
+ import { join as join42 } from "path";
55893
56842
  var ALL_TARGETS = ["claude-code", "codex", "opencode"];
55894
56843
  function parseTargets(flags2) {
55895
56844
  const picked = /* @__PURE__ */ new Set();
@@ -55928,7 +56877,7 @@ function installForTargets(opts) {
55928
56877
  const out3 = installCodex({ codexDir: codexHome(), command: cmd(), skillsSource: opts.skills === false ? null : bundledSkillsDir() });
55929
56878
  r.reports.push(...out3.reports);
55930
56879
  r.guidance.push(...out3.guidance);
55931
- } else r.reports.push({ path: join39(codexHome(), "hooks.json"), changed: false });
56880
+ } else r.reports.push({ path: join42(codexHome(), "hooks.json"), changed: false });
55932
56881
  break;
55933
56882
  }
55934
56883
  case "opencode": {
@@ -55937,18 +56886,18 @@ function installForTargets(opts) {
55937
56886
  r.error = "opencode: this engine was not run from a package carrying .opencode/plugins/ultra11y.js \u2014 install ultra11y from npm and retry.";
55938
56887
  break;
55939
56888
  }
55940
- const configDir = opts.project === true ? join39(cwd, ".opencode") : opencodeConfigDir();
56889
+ const configDir = opts.project === true ? join42(cwd, ".opencode") : opencodeConfigDir();
55941
56890
  if (!opts.dryRun) {
55942
- const engine = existsSync27(pinnedEnginePath()) ? pinnedEnginePath() : join39(packageRoot() ?? "", "scripts", "ultra11y.mjs");
56891
+ const engine = existsSync29(pinnedEnginePath()) ? pinnedEnginePath() : join42(packageRoot() ?? "", "scripts", "ultra11y.mjs");
55943
56892
  const out3 = installOpencode({
55944
56893
  configDir,
55945
56894
  pluginSource: source,
55946
56895
  skillsSource: opts.skills === false ? null : bundledSkillsDir(),
55947
- enginePath: existsSync27(engine) ? engine : null
56896
+ enginePath: existsSync29(engine) ? engine : null
55948
56897
  });
55949
56898
  r.reports.push(...out3.reports);
55950
56899
  r.guidance.push(...out3.guidance);
55951
- } else r.reports.push({ path: join39(configDir, "plugin", "ultra11y.js"), changed: false });
56900
+ } else r.reports.push({ path: join42(configDir, "plugin", "ultra11y.js"), changed: false });
55952
56901
  break;
55953
56902
  }
55954
56903
  case "agents-md": {
@@ -55986,7 +56935,7 @@ function uninstallForTargets(opts) {
55986
56935
  break;
55987
56936
  }
55988
56937
  case "opencode": {
55989
- const out3 = uninstallOpencode({ configDir: opts.project === true ? join39(cwd, ".opencode") : opencodeConfigDir() });
56938
+ const out3 = uninstallOpencode({ configDir: opts.project === true ? join42(cwd, ".opencode") : opencodeConfigDir() });
55990
56939
  r.reports.push(...out3.reports);
55991
56940
  r.guidance.push(...out3.guidance);
55992
56941
  break;
@@ -56007,18 +56956,18 @@ function statusReport(opts = {}) {
56007
56956
  const claudePath = claudeSettingsPath(opts.project === true, cwd);
56008
56957
  const root = projectRoot(cwd);
56009
56958
  const codexDir = codexHome();
56010
- const ocDir = opts.project === true ? join39(cwd, ".opencode") : opencodeConfigDir();
56959
+ const ocDir = opts.project === true ? join42(cwd, ".opencode") : opencodeConfigDir();
56011
56960
  const codexOn = codexWired(codexDir) > 0;
56012
56961
  return [
56013
56962
  { target: "claude-code", wired: claudeCodeWired(claudePath) > 0, path: claudePath, note: "the plugin route wires this separately and does not show here" },
56014
56963
  {
56015
56964
  target: "codex",
56016
56965
  wired: codexOn,
56017
- path: join39(codexDir, "hooks.json"),
56966
+ path: join42(codexDir, "hooks.json"),
56018
56967
  // A wired hook behind a disabled feature flag never fires and looks fine. Say so.
56019
56968
  note: codexOn && !codexHooksEnabled(codexDir) ? "WIRED BUT INERT: `[features] hooks = true` is not set in config.toml" : void 0
56020
56969
  },
56021
- { target: "opencode", wired: opencodeWired(ocDir), path: join39(ocDir, "plugin", "ultra11y.js") },
56970
+ { target: "opencode", wired: opencodeWired(ocDir), path: join42(ocDir, "plugin", "ultra11y.js") },
56022
56971
  { target: "agents-md", wired: agentsMdWired(root), path: agentsMdPath(root) }
56023
56972
  ];
56024
56973
  }
@@ -56445,8 +57394,8 @@ function perPageTable(result, standard = CORE2, lang = "en") {
56445
57394
 
56446
57395
  // src/evidence.ts
56447
57396
  import { createHash as createHash6 } from "crypto";
56448
- import { existsSync as existsSync28, mkdirSync as mkdirSync13, readFileSync as readFileSync23, writeFileSync as writeFileSync15 } from "fs";
56449
- import { join as join40 } from "path";
57397
+ import { existsSync as existsSync30, mkdirSync as mkdirSync13, readFileSync as readFileSync26, writeFileSync as writeFileSync15 } from "fs";
57398
+ import { join as join43 } from "path";
56450
57399
  var DEFAULT_GEOMETRY = { pad: 24, minWidth: 320, minHeight: 120, maxWidth: 960, maxHeight: 540 };
56451
57400
  var DEFAULT_CAPS = { perRule: 6, perPage: 12, total: 200 };
56452
57401
  var HALO = { r: 255, g: 255, b: 255, a: 0.92 };
@@ -56463,7 +57412,7 @@ function pageIdOfSnapshot(file) {
56463
57412
  }
56464
57413
  function readJson3(file) {
56465
57414
  try {
56466
- return JSON.parse(readFileSync23(file, "utf8"));
57415
+ return JSON.parse(readFileSync26(file, "utf8"));
56467
57416
  } catch {
56468
57417
  return void 0;
56469
57418
  }
@@ -56479,21 +57428,21 @@ function resolveScale(img, viewport, boxes) {
56479
57428
  }
56480
57429
  function loadPageEvidence(root, pageId) {
56481
57430
  const dir = snapshotDir(root, pageId);
56482
- const domFile = join40(dir, "dom.html");
56483
- const shotFile = join40(dir, "screen.png");
56484
- if (!existsSync28(domFile)) return { skip: "no-snapshot" };
56485
- if (!existsSync28(shotFile)) return { skip: "no-screenshot" };
57431
+ const domFile = join43(dir, "dom.html");
57432
+ const shotFile = join43(dir, "screen.png");
57433
+ if (!existsSync30(domFile)) return { skip: "no-snapshot" };
57434
+ if (!existsSync30(shotFile)) return { skip: "no-screenshot" };
56486
57435
  let dom;
56487
57436
  let shot;
56488
57437
  try {
56489
- dom = readFileSync23(domFile, "utf8");
56490
- shot = readFileSync23(shotFile);
57438
+ dom = readFileSync26(domFile, "utf8");
57439
+ shot = readFileSync26(shotFile);
56491
57440
  } catch {
56492
57441
  return { skip: "no-snapshot" };
56493
57442
  }
56494
57443
  const img = decodePng(shot);
56495
57444
  if (!img) return { skip: "unreadable-image" };
56496
- const digest = readJson3(join40(dir, "boxes.json"));
57445
+ const digest = readJson3(join43(dir, "boxes.json"));
56497
57446
  if (!digest) return { skip: "no-boxes" };
56498
57447
  const doc = parseHtml(dom, domFile);
56499
57448
  const boxes = alignedBoxes(dom, digest, doc);
@@ -56509,7 +57458,7 @@ function loadPageEvidence(root, pageId) {
56509
57458
  return { pageId, img, scale, viewport, ordinalOf, boxes, truncated: Boolean(digest.truncated) };
56510
57459
  }
56511
57460
  function metaOf(dir) {
56512
- const raw = readJson3(join40(dir, "meta.json"));
57461
+ const raw = readJson3(join43(dir, "meta.json"));
56513
57462
  if (raw === void 0) return void 0;
56514
57463
  const v = validateSnapshotMeta(raw);
56515
57464
  return v.ok && v.meta ? v.meta : void 0;
@@ -56586,7 +57535,7 @@ var S2 = {
56586
57535
  alt: (sel, page) => `Capture recadr\xE9e de l'\xE9l\xE9ment ${sel} sur la page ${page}, entour\xE9 d'un cadre`,
56587
57536
  notImaged: (n) => `${n} occurrence(s) ne sont pas illustr\xE9es :`,
56588
57537
  reasons: {
56589
- "no-snapshot": "constat lev\xE9 sur le code source, pas sur une page captur\xE9e \u2014 il n'y a pas de pixels \xE0 montrer",
57538
+ "no-snapshot": "la capture de page que ce constat d\xE9signe n'est pas sur ce disque \u2014 sa r\xE9f\xE9rence fichier:ligne reste valable",
56590
57539
  "no-screenshot": "le producteur n'a pas fourni de capture pour cette page, donc le niveau pixel est inactif ici",
56591
57540
  "unreadable-image": "la capture n'est pas d\xE9codable (le moteur lit le truecolour 8 bits uniquement) \u2014 mieux vaut aucune image qu'une fausse",
56592
57541
  "no-boxes": "les positions d'\xE9l\xE9ments ne se v\xE9rifient pas contre le DOM s\xE9rialis\xE9, elles ont donc \xE9t\xE9 refus\xE9es en bloc plut\xF4t que d'encadrer le mauvais \xE9l\xE9ment",
@@ -56597,14 +57546,15 @@ var S2 = {
56597
57546
  "zero-area": "l'\xE9l\xE9ment n'occupe aucune surface peinte",
56598
57547
  "below-the-fold": "l'\xE9l\xE9ment est hors de la capture (la capture couvre la fen\xEAtre, pas la page enti\xE8re) \u2014 sa r\xE9f\xE9rence fichier:ligne reste valable",
56599
57548
  "unknown-scale": "le rapport pixels/CSS est ind\xE9termin\xE9 pour cette page ; un rapport devin\xE9 encadrerait le mauvais endroit",
56600
- capped: "occurrences regroup\xE9es : une illustration par \xE9l\xE9ment distinct, dans la limite fix\xE9e"
57549
+ deduplicated: "m\xEAme d\xE9faut sur le m\xEAme \xE9l\xE9ment : il est montr\xE9 par l'illustration d'une autre occurrence, rien n'a \xE9t\xE9 perdu",
57550
+ capped: "d\xE9faut distinct laiss\xE9 sans image : le plafond de vignettes de ce lot est atteint \u2014 relevez `--evidence-max` pour les obtenir"
56601
57551
  }
56602
57552
  },
56603
57553
  en: {
56604
57554
  alt: (sel, page) => `Cropped screenshot of the ${sel} element on the ${page} page, outlined`,
56605
57555
  notImaged: (n) => `${n} occurrence(s) are not illustrated:`,
56606
57556
  reasons: {
56607
- "no-snapshot": "raised on source code, not on a captured page \u2014 there are no pixels to show",
57557
+ "no-snapshot": "the page snapshot this finding names is not on this disk \u2014 its file:line reference still holds",
56608
57558
  "no-screenshot": "the producer supplied no screenshot for this page, so the pixel tier is inactive here",
56609
57559
  "unreadable-image": "the screenshot does not decode (the engine reads 8-bit truecolour only) \u2014 no image beats a wrong one",
56610
57560
  "no-boxes": "the element positions do not verify against the serialized DOM, so they were refused wholesale rather than outlining the wrong element",
@@ -56615,22 +57565,26 @@ var S2 = {
56615
57565
  "zero-area": "the element paints no box",
56616
57566
  "below-the-fold": "the element sits outside the capture (the screenshot covers the viewport, not the whole page) \u2014 its file:line reference still holds",
56617
57567
  "unknown-scale": "the image-pixel to CSS-pixel ratio is indeterminate for this page; a guessed ratio would outline the wrong place",
56618
- capped: "occurrences grouped: one illustration per distinct element, up to the cap"
57568
+ deduplicated: "the same defect on the same element: it is shown by the illustration of another occurrence, nothing was lost",
57569
+ capped: "a distinct defect left undrawn: this run's crop limit was reached \u2014 raise `--evidence-max` to get them"
56619
57570
  }
56620
57571
  }
56621
57572
  };
56622
- function evidenceNotice(m, pageId, lang) {
57573
+ function evidenceRefusals(m, pageId, lang) {
56623
57574
  const t2 = pageId === null ? m.totals : m.perPage.get(pageId);
56624
- if (!t2) return [];
57575
+ if (!t2) return void 0;
56625
57576
  const missing = t2.located - t2.imaged;
56626
- if (missing <= 0) return [];
57577
+ if (missing <= 0) return void 0;
57578
+ const entries = Object.entries(t2.skipped).filter(([, n]) => n);
56627
57579
  const s = S2[lang];
56628
- const out2 = [s.notImaged(missing)];
56629
- for (const [reason, n] of Object.entries(t2.skipped).sort((a, b) => (b[1] ?? 0) - (a[1] ?? 0))) {
56630
- if (!n) continue;
56631
- out2.push(`- ${n} \u2014 ${s.reasons[reason]}`);
56632
- }
56633
- return out2;
57580
+ return {
57581
+ headline: s.notImaged(missing),
57582
+ reasons: entries.sort((a, b) => b[1] - a[1]).map(([reason, n]) => `${n} \u2014 ${s.reasons[reason]}`)
57583
+ };
57584
+ }
57585
+ function evidenceNotice(m, pageId, lang) {
57586
+ const r = evidenceRefusals(m, pageId, lang);
57587
+ return r ? [r.headline, ...r.reasons.map((x) => `- ${x}`)] : [];
56634
57588
  }
56635
57589
  function bump(t2, reason) {
56636
57590
  t2.skipped[reason] = (t2.skipped[reason] ?? 0) + 1;
@@ -56669,7 +57623,7 @@ function writeEvidence(result, opts) {
56669
57623
  }
56670
57624
  continue;
56671
57625
  }
56672
- const dir = join40(opts.outDir, "assets", pageId);
57626
+ const dir = join43(opts.outDir, "assets", pageId);
56673
57627
  let wrote = false;
56674
57628
  const perRule = /* @__PURE__ */ new Map();
56675
57629
  const seen = /* @__PURE__ */ new Set();
@@ -56678,10 +57632,12 @@ function writeEvidence(result, opts) {
56678
57632
  const id = findingId(f);
56679
57633
  const key = `${f.ruleId}\0${f.selectorHint}`;
56680
57634
  const ruleCount = perRule.get(f.ruleId) ?? 0;
56681
- if (seen.has(key) || ruleCount >= caps.perRule || onPage >= caps.perPage || manifest.crops.size >= caps.total) {
56682
- manifest.skipped.set(id, "capped");
56683
- bump(tally, "capped");
56684
- bump(manifest.totals, "capped");
57635
+ const overLimit = ruleCount >= caps.perRule || onPage >= caps.perPage || manifest.crops.size >= caps.total;
57636
+ const refusal = seen.has(key) ? "deduplicated" : overLimit ? "capped" : null;
57637
+ if (refusal) {
57638
+ manifest.skipped.set(id, refusal);
57639
+ bump(tally, refusal);
57640
+ bump(manifest.totals, refusal);
56685
57641
  continue;
56686
57642
  }
56687
57643
  const crop = cropFinding(ctx, f, opts.geometry);
@@ -56692,7 +57648,7 @@ function writeEvidence(result, opts) {
56692
57648
  continue;
56693
57649
  }
56694
57650
  const name2 = cropName(f);
56695
- const path = join40(dir, name2);
57651
+ const path = join43(dir, name2);
56696
57652
  try {
56697
57653
  if (!wrote) {
56698
57654
  mkdirSync13(dir, { recursive: true });
@@ -56730,8 +57686,8 @@ function writeEvidence(result, opts) {
56730
57686
  }
56731
57687
 
56732
57688
  // src/html-emit.ts
56733
- import { mkdirSync as mkdirSync14, readFileSync as readFileSync24, statSync as statSync11, writeFileSync as writeFileSync16 } from "fs";
56734
- import { join as join41 } from "path";
57689
+ import { mkdirSync as mkdirSync14, readFileSync as readFileSync27, statSync as statSync11, writeFileSync as writeFileSync16 } from "fs";
57690
+ import { join as join44 } from "path";
56735
57691
 
56736
57692
  // src/html.ts
56737
57693
  var MARK4 = { C: "C", NC: "NC", NA: "\u2014", manual: "?" };
@@ -56888,6 +57844,13 @@ function renderHtmlDocument(doc) {
56888
57844
  }
56889
57845
 
56890
57846
  // src/html-report.ts
57847
+ function refusalBlocks(r) {
57848
+ if (!r) return [];
57849
+ return [
57850
+ { kind: "note", tone: "warn", runs: [{ text: r.headline }] },
57851
+ { kind: "list", items: r.reasons.map((text) => [{ text }]) }
57852
+ ];
57853
+ }
56891
57854
  var T4 = {
56892
57855
  fr: {
56893
57856
  reportTitle: "Rapport de conformit\xE9",
@@ -56937,8 +57900,7 @@ var T4 = {
56937
57900
  openComposite: "Rapport complet, en un seul fichier (imprimable en PDF)",
56938
57901
  openPages: "Rapport page par page",
56939
57902
  noScreenshot: "Aucune capture d'\xE9cran pour cette page.",
56940
- screenshotAlt: (n) => `Capture d'\xE9cran de la page ${n}`,
56941
- notImaged: (n) => `${n} occurrence(s) n'ont pas pu \xEAtre illustr\xE9es.`
57903
+ screenshotAlt: (n) => `Capture d'\xE9cran de la page ${n}`
56942
57904
  },
56943
57905
  en: {
56944
57906
  reportTitle: "Conformance report",
@@ -56988,8 +57950,7 @@ var T4 = {
56988
57950
  openComposite: "Full report, in a single file (printable to PDF)",
56989
57951
  openPages: "Page-by-page report",
56990
57952
  noScreenshot: "No screenshot for this page.",
56991
- screenshotAlt: (n) => `Screenshot of the ${n} page`,
56992
- notImaged: (n) => `${n} occurrence(s) could not be illustrated.`
57953
+ screenshotAlt: (n) => `Screenshot of the ${n} page`
56993
57954
  }
56994
57955
  };
56995
57956
  function ticks(text) {
@@ -57084,10 +58045,11 @@ function criterionBlocks(unit, standard, lang, level, crops) {
57084
58045
  function resolveOccurrence(f, lang) {
57085
58046
  return f.msg ? auditorUnitModel({ criteriaId: f.criteriaId, title: "", label: "", refs: [], severity: f.severity, findings: [f] }, CORE2, lang).messages[0] ?? f.message : f.message;
57086
58047
  }
57087
- function findingsBlocks(result, standard, lang, level, crops) {
58048
+ function findingsBlocks(result, standard, lang, level, crops, refusals) {
57088
58049
  const t2 = T4[lang];
57089
58050
  const { nc, advisory } = partitionUnits(prdUnits(result, standard, lang));
57090
58051
  const out2 = [{ kind: "heading", level: 2, text: t2.ncTitle, id: "nc" }];
58052
+ out2.push(...refusalBlocks(refusals?.(null)));
57091
58053
  if (!nc.length) out2.push({ kind: "para", runs: [{ text: t2.noNc }] });
57092
58054
  for (const u of nc) out2.push(...criterionBlocks(u, standard, lang, level === 2 ? 3 : 4, crops));
57093
58055
  if (advisory.length) {
@@ -57199,7 +58161,7 @@ function compositeDoc(result, opts = {}) {
57199
58161
  const h = headline(result, standard, lang);
57200
58162
  const blocks = [...synthesisBlocks(result, standard, lang)];
57201
58163
  if (h.agentRuled) blocks.push({ kind: "note", tone: "warn", runs: ticks(agentMarkNote(lang)) });
57202
- blocks.push(...findingsBlocks(result, standard, lang, 2, opts.crops));
58164
+ blocks.push(...findingsBlocks(result, standard, lang, 2, opts.crops, opts.refusals));
57203
58165
  blocks.push(...scoreboardBlocks(result, standard, lang));
57204
58166
  const scope = pagesOf(result);
57205
58167
  if (scope.length) blocks.push(...crossGridBlocks(result, derivePages(result, scope), standard, lang));
@@ -57236,6 +58198,7 @@ function pageDoc(result, page, opts = {}) {
57236
58198
  blocks.push(
57237
58199
  opts.screenshot ? { kind: "figure", src: opts.screenshot, alt: t2.screenshotAlt(page.name) } : { kind: "para", runs: [{ text: t2.noScreenshot, em: true }] }
57238
58200
  );
58201
+ blocks.push(...refusalBlocks(opts.refusals?.(page.id)));
57239
58202
  blocks.push(...pageGridBlocks(result, page, standard, lang));
57240
58203
  const view = { ...result, criteria: page.criteria, findings: page.findings };
57241
58204
  const { nc, advisory } = partitionUnits(prdUnits(view, standard, lang));
@@ -57286,7 +58249,7 @@ function inlineSize(path) {
57286
58249
  }
57287
58250
  function dataUri(path) {
57288
58251
  try {
57289
- return `data:image/png;base64,${readFileSync24(path).toString("base64")}`;
58252
+ return `data:image/png;base64,${readFileSync27(path).toString("base64")}`;
57290
58253
  } catch {
57291
58254
  return void 0;
57292
58255
  }
@@ -57300,6 +58263,10 @@ function cropLookup(m, lang, hrefOf) {
57300
58263
  return href ? { href, alt: c2.alt[lang] } : void 0;
57301
58264
  };
57302
58265
  }
58266
+ function refusalLookup(m, lang) {
58267
+ if (!m) return void 0;
58268
+ return (pageId) => evidenceRefusals(m, pageId, lang);
58269
+ }
57303
58270
  function pickRung(cropBytes, shotBytes, budget) {
57304
58271
  const sum = (xs) => xs.reduce((a, b) => a + b, 0);
57305
58272
  if (sum(cropBytes) + sum(shotBytes) <= budget) return { steps: [], cropsPerCriterion: Number.POSITIVE_INFINITY, shots: true };
@@ -57353,7 +58320,7 @@ function writeHtml(result, opts) {
57353
58320
  return drawn;
57354
58321
  };
57355
58322
  const flat = opts.layout === "pages";
57356
- const sheetDir = flat ? opts.outDir : join41(opts.outDir, "pages");
58323
+ const sheetDir = flat ? opts.outDir : join44(opts.outDir, "pages");
57357
58324
  const up = flat ? "./" : "../";
57358
58325
  const compositeName = `ultra11y-${stdKey}-${result.date}.html`;
57359
58326
  const nav = flat ? [{ href: "./index.html", text: t2.pagesTitle }] : [
@@ -57361,17 +58328,24 @@ function writeHtml(result, opts) {
57361
58328
  { href: `./${compositeName}`, text: t2.docTitle },
57362
58329
  ...opts.pages ? [{ href: "./pages/index.html", text: t2.pagesTitle }] : []
57363
58330
  ];
58331
+ const refusals = refusalLookup(opts.evidence, lang);
57364
58332
  let compositePath;
57365
58333
  if (!flat) {
57366
- const composite = compositeDoc(result, { standard, lang, crops: budgetedCrops, nav: nav.map((n) => ({ ...n, current: n.href === `./${compositeName}` })) });
58334
+ const composite = compositeDoc(result, {
58335
+ standard,
58336
+ lang,
58337
+ crops: budgetedCrops,
58338
+ ...refusals ? { refusals } : {},
58339
+ nav: nav.map((n) => ({ ...n, current: n.href === `./${compositeName}` }))
58340
+ });
57367
58341
  if (notices.length) composite.blocks.unshift({ kind: "note", tone: "warn", runs: noticeRuns(notices) });
57368
- compositePath = join41(opts.outDir, compositeName);
58342
+ compositePath = join44(opts.outDir, compositeName);
57369
58343
  writeFileSync16(compositePath, renderHtmlDocument(composite));
57370
58344
  }
57371
58345
  const fileCrops = cropLookup(opts.evidence, lang, (_p, href) => href.replace(/^\.\//, up));
57372
58346
  const indexNav = nav.map((n) => ({ ...n, current: n.href === "./index.html" }));
57373
58347
  const index = flat ? pagesIndexDoc(result, { standard, lang, nav: indexNav, sheetHref: (id) => `./page-${id}.html` }) : indexDoc(result, { standard, lang, nav: indexNav, links: nav.filter((n) => n.href !== "./index.html") });
57374
- const indexPath = join41(opts.outDir, "index.html");
58348
+ const indexPath = join44(opts.outDir, "index.html");
57375
58349
  writeFileSync16(indexPath, renderHtmlDocument(index));
57376
58350
  const sheets = [];
57377
58351
  if (opts.pages) {
@@ -57384,7 +58358,7 @@ function writeHtml(result, opts) {
57384
58358
  ];
57385
58359
  if (!flat) {
57386
58360
  writeFileSync16(
57387
- join41(sheetDir, "index.html"),
58361
+ join44(sheetDir, "index.html"),
57388
58362
  renderHtmlDocument(
57389
58363
  pagesIndexDoc(result, {
57390
58364
  standard,
@@ -57394,19 +58368,20 @@ function writeHtml(result, opts) {
57394
58368
  })
57395
58369
  )
57396
58370
  );
57397
- sheets.push(join41(sheetDir, "index.html"));
58371
+ sheets.push(join44(sheetDir, "index.html"));
57398
58372
  }
57399
58373
  for (const p of derived) {
57400
58374
  const doc = pageDoc(result, p, {
57401
58375
  standard,
57402
58376
  lang,
57403
58377
  crops: fileCrops,
58378
+ ...refusals ? { refusals } : {},
57404
58379
  nav: sheetNav,
57405
58380
  // Where `cmdPages`' screenshot copier already put the capture: `assets/<id>.png`,
57406
58381
  // beside the entry point, seen from wherever this sheet sits.
57407
58382
  ...opts.screenshots?.has(p.id) ? { screenshot: `${up}assets/${p.id}.png` } : {}
57408
58383
  });
57409
- const path = join41(sheetDir, `page-${p.id}.html`);
58384
+ const path = join44(sheetDir, `page-${p.id}.html`);
57410
58385
  writeFileSync16(path, renderHtmlDocument(doc));
57411
58386
  sheets.push(path);
57412
58387
  }
@@ -57427,8 +58402,8 @@ function noticeRuns(notices) {
57427
58402
 
57428
58403
  // src/dev.ts
57429
58404
  import { createServer } from "http";
57430
- import { mkdirSync as mkdirSync15, readFileSync as readFileSync25, writeFileSync as writeFileSync17 } from "fs";
57431
- import { dirname as dirname12, join as join42 } from "path";
58405
+ import { mkdirSync as mkdirSync15, readFileSync as readFileSync28, writeFileSync as writeFileSync17 } from "fs";
58406
+ import { dirname as dirname12, join as join45 } from "path";
57432
58407
  var DEV_DEFAULT_PORT = 4111;
57433
58408
  function criterionLabel3(f, standard) {
57434
58409
  if (isCore(standard)) return `WCAG ${f.criteriaId}`;
@@ -57650,13 +58625,13 @@ function auditCollected(root, payload) {
57650
58625
  ...payload.css ? { css: payload.css } : {},
57651
58626
  ...shot ? { screenshotBase64: shot } : {}
57652
58627
  });
57653
- return { ok: true, result: runAudit({ inputs: [join42(dir, "dom.html")] }) };
58628
+ return { ok: true, result: runAudit({ inputs: [join45(dir, "dom.html")] }) };
57654
58629
  }
57655
58630
  function projectPages(root) {
57656
58631
  const snaps = readSnapshots(root);
57657
58632
  if (!snaps.length) return { result: null, pages: [] };
57658
58633
  const scope = pageScopesFrom(snaps);
57659
- const result = runAudit({ inputs: [join42(root, ".ultra11y/pages")] });
58634
+ const result = runAudit({ inputs: [join45(root, ".ultra11y/pages")] });
57660
58635
  result.scope.pages = scope;
57661
58636
  attributePages(result, scope);
57662
58637
  foldRecordedAdjudication(root, result);
@@ -57670,7 +58645,7 @@ function judgePages(result) {
57670
58645
  function foldRecordedAdjudication(root, fresh) {
57671
58646
  let prior;
57672
58647
  try {
57673
- prior = JSON.parse(readFileSync25(join42(root, "audits", "audit-latest.json"), "utf8"));
58648
+ prior = JSON.parse(readFileSync28(join45(root, "audits", "audit-latest.json"), "utf8"));
57674
58649
  } catch {
57675
58650
  return;
57676
58651
  }
@@ -57785,7 +58760,7 @@ function startDevServer(opts) {
57785
58760
  });
57786
58761
  let auditPath;
57787
58762
  if (applied.ok) {
57788
- auditPath = join42(opts.root, "audits", "audit-latest.json");
58763
+ auditPath = join45(opts.root, "audits", "audit-latest.json");
57789
58764
  mkdirSync15(dirname12(auditPath), { recursive: true });
57790
58765
  writeFileSync17(auditPath, `${JSON.stringify(applied.audit, null, 2)}
57791
58766
  `);
@@ -57814,10 +58789,10 @@ function startDevServer(opts) {
57814
58789
  }
57815
58790
  res.writeHead(404, { "content-type": "text/plain" }).end("not found");
57816
58791
  });
57817
- return new Promise((resolve13, reject) => {
58792
+ return new Promise((resolve15, reject) => {
57818
58793
  server.once("error", reject);
57819
58794
  server.listen(opts.port, "127.0.0.1", () => {
57820
- resolve13({
58795
+ resolve15({
57821
58796
  port: server.address().port,
57822
58797
  close: () => new Promise((r) => server.close(() => r()))
57823
58798
  });
@@ -58101,11 +59076,11 @@ function e2eSetupPlan(runners, paths, lang = "en") {
58101
59076
  }
58102
59077
 
58103
59078
  // src/orchestrate.ts
58104
- import { existsSync as existsSync29, mkdirSync as mkdirSync16, readFileSync as readFileSync26, rmSync as rmSync7, writeFileSync as writeFileSync18 } from "fs";
58105
- import { join as join44, resolve as resolve10 } from "path";
59079
+ import { existsSync as existsSync31, mkdirSync as mkdirSync16, readFileSync as readFileSync29, rmSync as rmSync7, writeFileSync as writeFileSync18 } from "fs";
59080
+ import { join as join47, resolve as resolve12 } from "path";
58106
59081
 
58107
59082
  // src/orchestrate-templates.ts
58108
- import { join as join43 } from "path";
59083
+ import { join as join46 } from "path";
58109
59084
  var ONE_WRITER_FOOTER = `
58110
59085
  ## Return, don't write
58111
59086
 
@@ -58177,7 +59152,7 @@ var PHASE_SPECS = {
58177
59152
  title: "Adjudicate",
58178
59153
  schema: ADJUDICATE_SCHEMA,
58179
59154
  description: (n) => `Adjudicate the ${n} residual judgment criterion(ia) of an ultra11y audit (fan-out, fail-closed fold)`,
58180
- applyHint: (engine, worklist, run2) => `node ${engine} verify --apply ${worklist} --in ${join43(run2, "audit-latest.json")} --out ${run2}`
59155
+ applyHint: (engine, worklist, run2) => `node ${engine} verify --apply ${worklist} --in ${join46(run2, "audit-latest.json")} --out ${run2}`
58181
59156
  },
58182
59157
  "verify-report": {
58183
59158
  role: "refuter",
@@ -58199,7 +59174,7 @@ function toBatches(ids, batchSize) {
58199
59174
  }
58200
59175
  function phaseWorkflowScript(ph, runAbs, engineAbs, batchSize) {
58201
59176
  const spec = phaseSpec(ph.name);
58202
- const scriptPath = join43(runAbs, "orchestration", `${ph.name}.workflow.mjs`);
59177
+ const scriptPath = join46(runAbs, "orchestration", `${ph.name}.workflow.mjs`);
58203
59178
  const meta2 = { name: `ultra11y-${ph.name}`, description: spec.description(ph.items), phases: [{ title: spec.title }] };
58204
59179
  return [
58205
59180
  `export const meta = ${JSON.stringify(meta2)}`,
@@ -58243,7 +59218,7 @@ function agentContracts(runAbs, engineAbs) {
58243
59218
 
58244
59219
  You adjudicate the residual judgment criteria of an ultra11y audit \u2014 the ones the deterministic engine could not decide (alt-text relevance, link purpose in context, reading order\u2026). The ACTIVE STANDARD is recorded in the worklist's \`standard\` field: under a country standard (e.g. \`rgaa\`) the items are that standard's OWN criteria, each carrying its numbered tests \u2014 not WCAG success criteria.
58245
59220
 
58246
- Worklist: \`${join43(runAbs, "ADJUDICATE.todo.json")}\` (an object with \`kind: "adjudication"\` and \`items[]\`). Handle ONLY the criteria whose \`criteriaId\` is named in your prompt (\`ITEMS=<id,\u2026>\`).
59221
+ Worklist: \`${join46(runAbs, "ADJUDICATE.todo.json")}\` (an object with \`kind: "adjudication"\` and \`items[]\`). Handle ONLY the criteria whose \`criteriaId\` is named in your prompt (\`ITEMS=<id,\u2026>\`).
58247
59222
 
58248
59223
  For EACH of your criteria:
58249
59224
 
@@ -58261,7 +59236,7 @@ ${footer}`,
58261
59236
 
58262
59237
  You are an adversarial skeptic verifying the non-conformities of an ultra11y report. Your job is to try to REFUTE each claim: assume it is wrong until the source proves it.
58263
59238
 
58264
- Worklist: \`${join43(runAbs, "VERIFY.todo.json")}\` (a JSON array; each entry has \`n\`, \`criteriaId\`, \`file\`, \`line\`, \`selector\`, \`claim\`). Handle ONLY the entries whose \`n\` is named in your prompt (\`ITEMS=<n,\u2026>\`).
59239
+ Worklist: \`${join46(runAbs, "VERIFY.todo.json")}\` (a JSON array; each entry has \`n\`, \`criteriaId\`, \`file\`, \`line\`, \`selector\`, \`claim\`). Handle ONLY the entries whose \`n\` is named in your prompt (\`ITEMS=<n,\u2026>\`).
58265
59240
 
58266
59241
  For EACH of your entries:
58267
59242
 
@@ -58297,14 +59272,14 @@ ${status}
58297
59272
 
58298
59273
  ## The loop (play every role yourself, one item at a time)
58299
59274
 
58300
- 1. **Audit** (if not done): \`${engine} audit "<globs>" --graph --out ${runAbs}\` \u2192 \`${join43(runAbs, "audit-latest.json")}\`.
58301
- 2. **Adjudicate the residual criteria** \u2014 \`${engine} verify --manual --in ${join43(runAbs, "audit-latest.json")} --out ${runAbs}\` writes \`${join43(runAbs, "ADJUDICATE.todo.json")}\`. For EVERY item, apply \`${join43(runAbs, "orchestration", "agents", "adjudicator.md")}\` yourself (read the evidence, rule C/NC/NA/manual, fill the required justification/findings/reason IN the todo file). Then fold, fail-closed: \`${engine} verify --apply ${join43(runAbs, "ADJUDICATE.todo.json")} --in ${join43(runAbs, "audit-latest.json")} --out ${runAbs}\`.
58302
- 3. **Report**: \`${engine} report --in ${join43(runAbs, "audit-latest.json")} --out ${runAbs}\`.
58303
- 4. **Verify the report's claims** \u2014 \`${engine} verify --report <the report .md> --out ${runAbs}\` writes \`${join43(runAbs, "VERIFY.todo.json")}\`. For EVERY entry, apply \`${join43(runAbs, "orchestration", "agents", "refuter.md")}\` yourself (open file:line, verdict supported/partial/refuted/unsupported + note IN the todo file). Then: \`${engine} verify --apply ${join43(runAbs, "VERIFY.todo.json")} --report <the report .md>\`.
59275
+ 1. **Audit** (if not done): \`${engine} audit "<globs>" --graph --out ${runAbs}\` \u2192 \`${join46(runAbs, "audit-latest.json")}\`.
59276
+ 2. **Adjudicate the residual criteria** \u2014 \`${engine} verify --manual --in ${join46(runAbs, "audit-latest.json")} --out ${runAbs}\` writes \`${join46(runAbs, "ADJUDICATE.todo.json")}\`. For EVERY item, apply \`${join46(runAbs, "orchestration", "agents", "adjudicator.md")}\` yourself (read the evidence, rule C/NC/NA/manual, fill the required justification/findings/reason IN the todo file). Then fold, fail-closed: \`${engine} verify --apply ${join46(runAbs, "ADJUDICATE.todo.json")} --in ${join46(runAbs, "audit-latest.json")} --out ${runAbs}\`.
59277
+ 3. **Report**: \`${engine} report --in ${join46(runAbs, "audit-latest.json")} --out ${runAbs}\`.
59278
+ 4. **Verify the report's claims** \u2014 \`${engine} verify --report <the report .md> --out ${runAbs}\` writes \`${join46(runAbs, "VERIFY.todo.json")}\`. For EVERY entry, apply \`${join46(runAbs, "orchestration", "agents", "refuter.md")}\` yourself (open file:line, verdict supported/partial/refuted/unsupported + note IN the todo file). Then: \`${engine} verify --apply ${join46(runAbs, "VERIFY.todo.json")} --report <the report .md>\`.
58304
59279
  5. **Gate**: \`${engine} check --report <the report .md> --semantic\` must exit 0 before presenting anything.
58305
59280
  6. **Fix & re-audit**: \`${engine} fix <globs> --write --iterate\`, hand-apply the judgment fixes, then loop from step 1 until the gate stays green.
58306
59281
 
58307
- With subagents available, prefer the emitted workflows instead: \`orchestrate --run ${runAbs} --phase <p>\` then \`Workflow({ scriptPath: "${join43(runAbs, "orchestration", "<p>.workflow.mjs")}" })\` \u2014 you stay the sole writer either way.
59282
+ With subagents available, prefer the emitted workflows instead: \`orchestrate --run ${runAbs} --phase <p>\` then \`Workflow({ scriptPath: "${join46(runAbs, "orchestration", "<p>.workflow.mjs")}" })\` \u2014 you stay the sole writer either way.
58308
59283
  `;
58309
59284
  }
58310
59285
 
@@ -58313,13 +59288,13 @@ var PHASES = ["adjudicate", "verify-report"];
58313
59288
  var SMALL_WORKLIST = 3;
58314
59289
  var BATCH_SIZE2 = 8;
58315
59290
  function listPhases(runDir, engineAbs) {
58316
- const run2 = resolve10(runDir);
58317
- const adjPath = join44(run2, "ADJUDICATE.todo.json");
59291
+ const run2 = resolve12(runDir);
59292
+ const adjPath = join47(run2, "ADJUDICATE.todo.json");
58318
59293
  let adjIds = [];
58319
59294
  let adjReady = false;
58320
- if (existsSync29(adjPath)) {
59295
+ if (existsSync31(adjPath)) {
58321
59296
  try {
58322
- const f = JSON.parse(readFileSync26(adjPath, "utf8"));
59297
+ const f = JSON.parse(readFileSync29(adjPath, "utf8"));
58323
59298
  if (f && f.kind === "adjudication" && Array.isArray(f.items)) {
58324
59299
  adjReady = true;
58325
59300
  adjIds = f.items.map((i2) => i2.criteriaId);
@@ -58327,12 +59302,12 @@ function listPhases(runDir, engineAbs) {
58327
59302
  } catch {
58328
59303
  }
58329
59304
  }
58330
- const verPath = join44(run2, "VERIFY.todo.json");
59305
+ const verPath = join47(run2, "VERIFY.todo.json");
58331
59306
  let verIds = [];
58332
59307
  let verReady = false;
58333
- if (existsSync29(verPath)) {
59308
+ if (existsSync31(verPath)) {
58334
59309
  try {
58335
- const items = JSON.parse(readFileSync26(verPath, "utf8"));
59310
+ const items = JSON.parse(readFileSync29(verPath, "utf8"));
58336
59311
  if (Array.isArray(items)) {
58337
59312
  verReady = true;
58338
59313
  verIds = items.map((i2) => String(i2.n));
@@ -58347,7 +59322,7 @@ function listPhases(runDir, engineAbs) {
58347
59322
  worklist: adjPath,
58348
59323
  items: adjIds.length,
58349
59324
  ids: adjIds,
58350
- prerequisite: `node ${engineAbs} verify --manual --in ${join44(run2, "audit-latest.json")} --out ${run2}`
59325
+ prerequisite: `node ${engineAbs} verify --manual --in ${join47(run2, "audit-latest.json")} --out ${run2}`
58351
59326
  },
58352
59327
  {
58353
59328
  name: "verify-report",
@@ -58360,8 +59335,8 @@ function listPhases(runDir, engineAbs) {
58360
59335
  ];
58361
59336
  }
58362
59337
  function orchestrateRun(runDir, engineAbs, opts = {}) {
58363
- const run2 = resolve10(runDir);
58364
- if (!existsSync29(run2)) {
59338
+ const run2 = resolve12(runDir);
59339
+ if (!existsSync31(run2)) {
58365
59340
  return { exitCode: 2, written: [], notices: [], errors: [`run dir not found: ${run2}`], phases: [] };
58366
59341
  }
58367
59342
  const phases = listPhases(run2, engineAbs);
@@ -58388,22 +59363,22 @@ function orchestrateRun(runDir, engineAbs, opts = {}) {
58388
59363
  }
58389
59364
  selected = [ph];
58390
59365
  }
58391
- const orchDir = join44(run2, "orchestration");
58392
- const agentsDir = join44(orchDir, "agents");
58393
- mkdirSync16(join44(orchDir, "out"), { recursive: true });
59366
+ const orchDir = join47(run2, "orchestration");
59367
+ const agentsDir = join47(orchDir, "agents");
59368
+ mkdirSync16(join47(orchDir, "out"), { recursive: true });
58394
59369
  mkdirSync16(agentsDir, { recursive: true });
58395
59370
  const written = [];
58396
59371
  const notices = [];
58397
59372
  for (const [name2, content] of Object.entries(agentContracts(run2, engineAbs))) {
58398
- const p = join44(agentsDir, `${name2}.md`);
59373
+ const p = join47(agentsDir, `${name2}.md`);
58399
59374
  writeFileSync18(p, content);
58400
59375
  written.push(p);
58401
59376
  }
58402
59377
  const emitted = new Set(opts.eco ? [] : selected.filter((p) => p.items > 0).map((p) => p.name));
58403
59378
  for (const ph of phases) {
58404
59379
  if (emitted.has(ph.name)) continue;
58405
- const stale = join44(orchDir, `${ph.name}.workflow.mjs`);
58406
- if (existsSync29(stale)) {
59380
+ const stale = join47(orchDir, `${ph.name}.workflow.mjs`);
59381
+ if (existsSync31(stale)) {
58407
59382
  rmSync7(stale, { force: true });
58408
59383
  notices.push(`phase "${ph.name}": stale workflow removed \u2014 its worklist ${ph.ready ? "is now empty" : "no longer exists"}.`);
58409
59384
  }
@@ -58417,12 +59392,12 @@ function orchestrateRun(runDir, engineAbs, opts = {}) {
58417
59392
  if (ph.items <= SMALL_WORKLIST) {
58418
59393
  notices.push(`phase "${ph.name}": only ${ph.items} item(s) \u2014 the sequential --eco path is equivalent and cheaper.`);
58419
59394
  }
58420
- const p = join44(orchDir, `${ph.name}.workflow.mjs`);
59395
+ const p = join47(orchDir, `${ph.name}.workflow.mjs`);
58421
59396
  writeFileSync18(p, phaseWorkflowScript(ph, run2, engineAbs, BATCH_SIZE2));
58422
59397
  written.push(p);
58423
59398
  }
58424
59399
  }
58425
- const rb = join44(orchDir, "RUNBOOK.md");
59400
+ const rb = join47(orchDir, "RUNBOOK.md");
58426
59401
  writeFileSync18(rb, runbookMd(phases, run2, engineAbs));
58427
59402
  written.push(rb);
58428
59403
  return { exitCode: 0, written, notices, errors: [], phases };
@@ -58432,8 +59407,8 @@ function orchestrateRun(runDir, engineAbs, opts = {}) {
58432
59407
  import { createInterface as createInterface2 } from "readline";
58433
59408
 
58434
59409
  // src/mcp/handlers.ts
58435
- import { existsSync as existsSync30, readFileSync as readFileSync27, realpathSync as realpathSync4, statSync as statSync12 } from "fs";
58436
- import { isAbsolute as isAbsolute3, join as join45, resolve as resolve11, sep as sep5 } from "path";
59410
+ import { existsSync as existsSync32, readFileSync as readFileSync30, realpathSync as realpathSync4, statSync as statSync12 } from "fs";
59411
+ import { isAbsolute as isAbsolute3, join as join48, resolve as resolve13, sep as sep5 } from "path";
58437
59412
 
58438
59413
  // src/project-lock.ts
58439
59414
  var chains = /* @__PURE__ */ new Map();
@@ -58480,8 +59455,8 @@ function positive(v, key) {
58480
59455
  function requiredCwd(args2, defaults) {
58481
59456
  const cwd = str2(args2.cwd) ?? defaults.defaultCwd;
58482
59457
  if (!cwd) throw new ToolError("`cwd` is required: an absolute path to the project root.");
58483
- const abs = resolve11(cwd);
58484
- if (!existsSync30(abs)) throw new ToolError(`project root not found: ${abs}`);
59458
+ const abs = resolve13(cwd);
59459
+ if (!existsSync32(abs)) throw new ToolError(`project root not found: ${abs}`);
58485
59460
  if (!statSync12(abs).isDirectory()) throw new ToolError(`\`cwd\` is not a directory: ${abs}`);
58486
59461
  return abs;
58487
59462
  }
@@ -58547,7 +59522,7 @@ function audit(args2, cwd) {
58547
59522
  process.chdir(cwd);
58548
59523
  const inputs = strArray2(args2.globs) ?? DEFAULT_GLOBS;
58549
59524
  const scopedToDiff = bool(args2.changed) || bool(args2.staged) || str2(args2.since) !== void 0;
58550
- const extra = [CAPTURES_DIR, PAGES_DIR].filter((d) => !scopedToDiff && existsSync30(d) && !inputs.includes(d));
59525
+ const extra = [CAPTURES_DIR, PAGES_DIR].filter((d) => !scopedToDiff && existsSync32(d) && !inputs.includes(d));
58551
59526
  const result = runAudit({
58552
59527
  inputs: [...inputs, ...extra],
58553
59528
  include: strArray2(args2.include),
@@ -58666,13 +59641,13 @@ function handlePackCheck(args2, cwd) {
58666
59641
  return { cwd, ...res };
58667
59642
  }
58668
59643
  function handleSampleCheck(cwd, standard) {
58669
- const file = join45(cwd, ".ultra11yrc.json");
58670
- if (!existsSync30(file)) {
59644
+ const file = join48(cwd, ".ultra11yrc.json");
59645
+ if (!existsSync32(file)) {
58671
59646
  throw new ToolError(`no .ultra11yrc.json at ${cwd} \u2014 a page sample must be declared before it can be linted.`);
58672
59647
  }
58673
59648
  let raw;
58674
59649
  try {
58675
- raw = JSON.parse(readFileSync27(file, "utf8"));
59650
+ raw = JSON.parse(readFileSync30(file, "utf8"));
58676
59651
  } catch (e) {
58677
59652
  throw new ToolError(`.ultra11yrc.json is not valid JSON: ${e.message}`);
58678
59653
  }
@@ -58763,10 +59738,10 @@ function handleInit(args2, cwd) {
58763
59738
  throw new ToolError(`\`fail_on\` must be one of: error, warning, notice (got "${failOnRaw}")`);
58764
59739
  }
58765
59740
  const failOn = failOnRaw;
58766
- const enginePath = resolve11("scripts/ultra11y.mjs");
59741
+ const enginePath = resolve13("scripts/ultra11y.mjs");
58767
59742
  const written = [];
58768
59743
  if (bool(args2.hook)) written.push(writeHook(cwd, enginePath, failOn));
58769
- if (bool(args2.ci)) written.push(join45(cwd, ".github/workflows/a11y.yml"));
59744
+ if (bool(args2.ci)) written.push(join48(cwd, ".github/workflows/a11y.yml"));
58770
59745
  if (!written.length) {
58771
59746
  throw new ToolError("nothing to install \u2014 pass hook:true and/or ci:true.");
58772
59747
  }
@@ -58786,8 +59761,8 @@ function reportText(args2, tool) {
58786
59761
  if (inline) return inline;
58787
59762
  if (!file) throw new ToolError(`\`report_text\` is required \u2014 the report markdown for ultra11y_${tool} to work on.`);
58788
59763
  if (!isAbsolute3(file)) throw new ToolError("`report_file` must be an absolute path.");
58789
- if (!existsSync30(file)) throw new ToolError(`report file not found: ${file}`);
58790
- return readFileSync27(file, "utf8");
59764
+ if (!existsSync32(file)) throw new ToolError(`report file not found: ${file}`);
59765
+ return readFileSync30(file, "utf8");
58791
59766
  }
58792
59767
  function handleCriteria(args2) {
58793
59768
  const lang = langOf(args2);
@@ -58802,7 +59777,7 @@ function handleCriteria(args2) {
58802
59777
  function handleRead(args2, cwd) {
58803
59778
  const raw = str2(args2.path);
58804
59779
  if (!raw) throw new ToolError("`path` is required \u2014 relative to the project root, or an absolute path inside it.");
58805
- const target = isAbsolute3(raw) ? raw : join45(cwd, raw);
59780
+ const target = isAbsolute3(raw) ? raw : join48(cwd, raw);
58806
59781
  let real;
58807
59782
  try {
58808
59783
  real = realpathSync4(target);
@@ -58816,7 +59791,7 @@ function handleRead(args2, cwd) {
58816
59791
  const st = statSync12(real);
58817
59792
  if (!st.isFile()) throw new ToolError(`not a file: ${raw}`);
58818
59793
  if (st.size > MAX_READ_BYTES) throw new ToolError(`file is too large to read (${st.size} bytes): ${raw}`);
58819
- const lines = readFileSync27(real, "utf8").split("\n");
59794
+ const lines = readFileSync30(real, "utf8").split("\n");
58820
59795
  const total = lines.length;
58821
59796
  const start2 = Math.max(1, Math.floor(num2(args2.start_line) ?? 1));
58822
59797
  if (start2 > total) throw new ToolError(`start_line ${start2} is past the end of the file (${total} lines).`);
@@ -59362,25 +60337,25 @@ function str3(v) {
59362
60337
  var DECLARED = new Set([...TOOLS2, ...WRITE_TOOLS].map((t2) => t2.name));
59363
60338
 
59364
60339
  // src/mcp/resources.ts
59365
- import { existsSync as existsSync31, readdirSync as readdirSync6, readFileSync as readFileSync28, realpathSync as realpathSync5, statSync as statSync13 } from "fs";
59366
- import { basename as basename3, dirname as dirname13, join as join46, resolve as resolve12, sep as sep6 } from "path";
60340
+ import { existsSync as existsSync33, readdirSync as readdirSync6, readFileSync as readFileSync31, realpathSync as realpathSync5, statSync as statSync13 } from "fs";
60341
+ import { basename as basename3, dirname as dirname13, join as join49, resolve as resolve14, sep as sep6 } from "path";
59367
60342
  import { fileURLToPath as fileURLToPath4 } from "url";
59368
60343
  var SKILL_NAME = "ultra11y";
59369
60344
  var URI_SCHEME = "skill://";
59370
60345
  function resolveSkillRoot(moduleDir) {
59371
60346
  const here = moduleDir ?? dirname13(fileURLToPath4(import.meta.url));
59372
- const candidates2 = [resolve12(here, ".."), resolve12(here, "..", "skills", SKILL_NAME), resolve12(here, "..", "..", "skills", SKILL_NAME)];
59373
- return candidates2.find((dir) => existsSync31(join46(dir, "SKILL.md")));
60347
+ const candidates2 = [resolve14(here, ".."), resolve14(here, "..", "skills", SKILL_NAME), resolve14(here, "..", "..", "skills", SKILL_NAME)];
60348
+ return candidates2.find((dir) => existsSync33(join49(dir, "SKILL.md")));
59374
60349
  }
59375
60350
  function listResources(moduleDir) {
59376
60351
  const root = resolveSkillRoot(moduleDir);
59377
60352
  if (!root) return [];
59378
60353
  const out2 = [describe(root, "SKILL.md", `${SKILL_NAME}: the skill`)];
59379
- const refDir = join46(root, "references");
59380
- if (!existsSync31(refDir)) return out2;
60354
+ const refDir = join49(root, "references");
60355
+ if (!existsSync33(refDir)) return out2;
59381
60356
  for (const file of readdirSync6(refDir).sort()) {
59382
60357
  if (!file.endsWith(".md")) continue;
59383
- out2.push(describe(root, join46("references", file), `${SKILL_NAME} reference: ${basename3(file, ".md")}`));
60358
+ out2.push(describe(root, join49("references", file), `${SKILL_NAME} reference: ${basename3(file, ".md")}`));
59384
60359
  }
59385
60360
  return out2;
59386
60361
  }
@@ -59392,7 +60367,7 @@ function readResource(uri, moduleDir) {
59392
60367
  if (!root) throw new ResourceError("no skill payload found next to this build \u2014 nothing to read");
59393
60368
  const rel2 = uri.slice(URI_SCHEME.length);
59394
60369
  if (!rel2) throw new ResourceError("empty resource path");
59395
- const target = resolve12(root, rel2);
60370
+ const target = resolve14(root, rel2);
59396
60371
  const rootReal = realpathSync5(root);
59397
60372
  let targetReal;
59398
60373
  try {
@@ -59404,7 +60379,7 @@ function readResource(uri, moduleDir) {
59404
60379
  throw new ResourceError(`resource path escapes the skill root: ${uri}`);
59405
60380
  }
59406
60381
  if (!statSync13(targetReal).isFile()) throw new ResourceError(`not a file: ${uri}`);
59407
- return { uri, mimeType: "text/markdown", text: readFileSync28(targetReal, "utf8") };
60382
+ return { uri, mimeType: "text/markdown", text: readFileSync31(targetReal, "utf8") };
59408
60383
  }
59409
60384
  var ResourceError = class extends Error {
59410
60385
  };
@@ -59415,14 +60390,14 @@ function describe(root, rel2, fallbackTitle) {
59415
60390
  title: fallbackTitle,
59416
60391
  mimeType: "text/markdown"
59417
60392
  };
59418
- const summary = firstProse(join46(root, rel2));
60393
+ const summary = firstProse(join49(root, rel2));
59419
60394
  if (summary) decl.description = summary;
59420
60395
  return decl;
59421
60396
  }
59422
60397
  function firstProse(file) {
59423
60398
  let text;
59424
60399
  try {
59425
- text = readFileSync28(file, "utf8");
60400
+ text = readFileSync31(file, "utf8");
59426
60401
  } catch {
59427
60402
  return void 0;
59428
60403
  }
@@ -59673,14 +60648,14 @@ function startHttpServer(opts = {}) {
59673
60648
  server.requestTimeout = 0;
59674
60649
  server.headersTimeout = 6e4;
59675
60650
  server.keepAliveTimeout = 12e4;
59676
- return new Promise((resolve13, reject) => {
60651
+ return new Promise((resolve15, reject) => {
59677
60652
  server.once("error", reject);
59678
60653
  server.listen(opts.port ?? 0, bind, () => {
59679
60654
  server.removeListener("error", reject);
59680
60655
  const addr2 = server.address();
59681
60656
  const port = typeof addr2 === "object" && addr2 ? addr2.port : opts.port ?? 0;
59682
60657
  const host = bind.includes(":") ? `[${bind}]` : bind;
59683
- resolve13({
60658
+ resolve15({
59684
60659
  server,
59685
60660
  port,
59686
60661
  url: `http://${host}:${port}${MCP_PATH}`,
@@ -59789,7 +60764,7 @@ function sendJson(res, status, body2, origin, extra = {}) {
59789
60764
  }
59790
60765
  var DRAIN_LIMIT = MAX_BODY_BYTES * 8;
59791
60766
  function readBody2(req) {
59792
- return new Promise((resolve13, reject) => {
60767
+ return new Promise((resolve15, reject) => {
59793
60768
  const chunks = [];
59794
60769
  let size = 0;
59795
60770
  let over = false;
@@ -59813,7 +60788,7 @@ function readBody2(req) {
59813
60788
  });
59814
60789
  req.on("end", () => {
59815
60790
  if (over) reject(new Error("too large"));
59816
- else resolve13(Buffer.concat(chunks).toString("utf8"));
60791
+ else resolve15(Buffer.concat(chunks).toString("utf8"));
59817
60792
  });
59818
60793
  req.on("error", reject);
59819
60794
  req.on("aborted", () => reject(new Error("client aborted the request")));
@@ -59835,7 +60810,8 @@ Usage:
59835
60810
  ultra11y audit [--format sarif|github] (CI: SARIF for code scanning, or inline annotations + job summary)
59836
60811
  ultra11y audit --in <audit.json> [--fail-on blocking|major|minor] [--format sarif|github] (re-gate an audit already computed \u2014 e.g. one carrying adjudicated verdicts \u2014 without a second detection pass)
59837
60812
  ultra11y report --in <audit.json> [--out <dir>] [--standard <pack>] [--format sarif|github] [--lang auto|en|fr]
59838
- ultra11y report --in <audit.json> --html [--evidence] [--inline-budget <bytes>] [--out <dir>] (index.html + a printable single file; --evidence adds annotated crops)
60813
+ ultra11y report --in <audit.json> [--evidence [--evidence-max <n>]] [--out <dir>] (the Markdown report, illustrated with annotated crops)
60814
+ ultra11y report --in <audit.json> --html [--evidence] [--inline-budget <bytes>] [--out <dir>] (index.html + a printable single file)
59839
60815
  ultra11y prd --in <audit.json> [--out <dir>] [--split criterion] [--format audit|doc|remediation] [--no-technical] [--standard <pack>] [--lang auto|en|fr]
59840
60816
  ultra11y tickets --in <audit.json> [--provider auto|github|gitlab|jira] [--grain criterion|page|page-criterion|single|file] [--transport auto|cli|rest]
59841
60817
  ultra11y tickets [--out <dir>] [--max-tickets <n>] [--dry-run] [--json] [--standard <pack>] [--format audit|remediation] [--lang auto|en|fr]
@@ -59860,7 +60836,7 @@ Usage:
59860
60836
  ultra11y snapshot list [--root <dir>] [--json]
59861
60837
  ultra11y pages --in <audit.json> [--standard <pack>] [--json] [--lang auto|en|fr] (the per-page criterion grid)
59862
60838
  ultra11y pages --in <audit.json> --format report [--split page] [--out <dir>] (the per-page report, with screenshots)
59863
- ultra11y pages --in <audit.json> --format report --out <dir> [--evidence] [--html] (annotated crops of each non-conformity, and the HTML site)
60839
+ ultra11y pages --in <audit.json> --format report --out <dir> [--evidence [--evidence-max <n>]] [--html] (annotated crops of each non-conformity, and the HTML site)
59864
60840
  ultra11y pages discover --sitemap <url> | --crawl <url> | --from-snapshots [--depth <n>] [--max <n>] [--write] [--json] (build the page sample)
59865
60841
  ultra11y pages --in <audit.json> --standard <pack> --diff <external.json> (hold the grid against an audit someone else performed)
59866
60842
  ultra11y import --from file <report.json> | --from ara <id> [--source <adapter>] [--out <dir>] [--json]
@@ -60259,6 +61235,8 @@ var VALUE_FLAGS2 = /* @__PURE__ */ new Set([
60259
61235
  "format",
60260
61236
  // Bytes of image data the single-file HTML report may carry inline (src/html-emit.ts).
60261
61237
  "inline-budget",
61238
+ // Hard cap on the crops one run writes (src/evidence.ts `DEFAULT_CAPS.total`).
61239
+ "evidence-max",
60262
61240
  "guidance",
60263
61241
  "runtime",
60264
61242
  "cwd",
@@ -60541,9 +61519,9 @@ async function cmdAudit(p) {
60541
61519
  const capturesFlag = typeof p.flags.captures === "string" && p.flags.captures ? p.flags.captures : void 0;
60542
61520
  const capturesDir = capturesFlag ?? CAPTURES_DIR;
60543
61521
  const scopedToDiff = p.flags.changed === true || p.flags.staged === true || since !== void 0;
60544
- const capturesWanted = p.flags["no-captures"] !== true && !inputs.includes("-") && (capturesFlag !== void 0 || existsSync32(capturesDir));
61522
+ const capturesWanted = p.flags["no-captures"] !== true && !inputs.includes("-") && (capturesFlag !== void 0 || existsSync34(capturesDir));
60545
61523
  const useCaptures = capturesWanted && !scopedToDiff && !inputs.includes(capturesDir);
60546
- const pagesWanted = p.flags["no-captures"] !== true && !inputs.includes("-") && existsSync32(PAGES_DIR);
61524
+ const pagesWanted = p.flags["no-captures"] !== true && !inputs.includes("-") && existsSync34(PAGES_DIR);
60547
61525
  const usePages = pagesWanted && !scopedToDiff && !inputs.includes(PAGES_DIR);
60548
61526
  const pagesInScope = pagesWanted && !scopedToDiff;
60549
61527
  const auditInputs = [...inputs, ...useCaptures ? [capturesDir] : [], ...usePages ? [PAGES_DIR] : []];
@@ -60585,7 +61563,7 @@ async function cmdAudit(p) {
60585
61563
  if (typeof p.flags.out === "string") {
60586
61564
  const out2 = p.flags.out;
60587
61565
  const asFile = out2.toLowerCase().endsWith(".json");
60588
- const target = asFile ? out2 : join47(out2, "audit-latest.json");
61566
+ const target = asFile ? out2 : join50(out2, "audit-latest.json");
60589
61567
  try {
60590
61568
  mkdirSync17(asFile ? dirname14(out2) : out2, { recursive: true });
60591
61569
  writeFileSync19(target, JSON.stringify(result, null, 2) + "\n");
@@ -60607,7 +61585,7 @@ async function cmdAudit(p) {
60607
61585
  const baselineFlag = p.flags.baseline;
60608
61586
  if (typeof baselineFlag === "string" && baselineFlag) {
60609
61587
  let baseline = null;
60610
- if (existsSync32(baselineFlag)) {
61588
+ if (existsSync34(baselineFlag)) {
60611
61589
  try {
60612
61590
  const parsed = JSON.parse(readText(baselineFlag));
60613
61591
  if (isCurrentAudit(parsed)) baseline = parsed;
@@ -60661,11 +61639,11 @@ async function cmdDev(p) {
60661
61639
  return 2;
60662
61640
  }
60663
61641
  if (p.flags.next === true) {
60664
- const dir = join47(root, ".ultra11y", "next");
61642
+ const dir = join50(root, ".ultra11y", "next");
60665
61643
  const rel2 = ".ultra11y/next/overlay.jsx";
60666
61644
  try {
60667
61645
  mkdirSync17(dir, { recursive: true });
60668
- writeFileSync19(join47(dir, "overlay.jsx"), nextOverlayComponent(port));
61646
+ writeFileSync19(join50(dir, "overlay.jsx"), nextOverlayComponent(port));
60669
61647
  } catch (e) {
60670
61648
  console.error(`ultra11y dev: could not write ${rel2}: ${e instanceof Error ? e.message : String(e)}`);
60671
61649
  return 1;
@@ -60696,9 +61674,9 @@ async function cmdDev(p) {
60696
61674
  }
60697
61675
  console.error(fr ? `ultra11y dev : tableau de bord sur http://127.0.0.1:${server.port}` : `ultra11y dev: dashboard on http://127.0.0.1:${server.port}`);
60698
61676
  console.error(fr ? "Boucle locale uniquement (l'outil \xE9crit des fichiers). Ctrl-C pour arr\xEAter." : "Loopback only (the tool writes files). Ctrl-C to stop.");
60699
- await new Promise((resolve13) => {
61677
+ await new Promise((resolve15) => {
60700
61678
  const stop2 = () => {
60701
- void server.close().then(resolve13);
61679
+ void server.close().then(resolve15);
60702
61680
  };
60703
61681
  process.once("SIGINT", stop2);
60704
61682
  process.once("SIGTERM", stop2);
@@ -60714,7 +61692,7 @@ async function cmdPagesDiscover(p) {
60714
61692
  const snaps = sampleFromSnapshots(readSnapshots(root));
60715
61693
  if (!snaps.length) {
60716
61694
  console.error(
60717
- lang === "fr" ? `ultra11y pages discover : aucun instantan\xE9 sous ${join47(root, PAGES_DIR)} \u2014 lancez d'abord vos tests E2E avec checkA11y, ou \`scan --sample\`.` : `ultra11y pages discover: no snapshot under ${join47(root, PAGES_DIR)} \u2014 run your E2E tests with checkA11y first, or \`scan --sample\`.`
61695
+ lang === "fr" ? `ultra11y pages discover : aucun instantan\xE9 sous ${join50(root, PAGES_DIR)} \u2014 lancez d'abord vos tests E2E avec checkA11y, ou \`scan --sample\`.` : `ultra11y pages discover: no snapshot under ${join50(root, PAGES_DIR)} \u2014 run your E2E tests with checkA11y first, or \`scan --sample\`.`
60718
61696
  );
60719
61697
  return 1;
60720
61698
  }
@@ -60794,9 +61772,9 @@ ${found} page(s) discovered, ${merged.added.length} new. Nothing was written \u2
60794
61772
  }
60795
61773
  return 0;
60796
61774
  }
60797
- const file = join47(process.cwd(), ".ultra11yrc.json");
61775
+ const file = join50(process.cwd(), ".ultra11yrc.json");
60798
61776
  let doc = {};
60799
- if (existsSync32(file)) {
61777
+ if (existsSync34(file)) {
60800
61778
  try {
60801
61779
  doc = JSON.parse(readText(file));
60802
61780
  } catch {
@@ -60865,27 +61843,27 @@ async function cmdPages(p) {
60865
61843
  const derived = derivePages(result, scope);
60866
61844
  const split = p.flags.split === "page";
60867
61845
  const outDir = typeof p.flags.out === "string" && p.flags.out ? p.flags.out : void 0;
60868
- const shotOf = (id) => join47(PAGES_DIR, id, "screen.png");
61846
+ const shotOf = (id) => join50(PAGES_DIR, id, "screen.png");
60869
61847
  const shotsRelative = (fileDir) => {
60870
61848
  const m = /* @__PURE__ */ new Map();
60871
61849
  for (const pg of derived) {
60872
- if (existsSync32(shotOf(pg.id))) m.set(pg.id, relative5(fileDir, shotOf(pg.id)).split("\\").join("/"));
61850
+ if (existsSync34(shotOf(pg.id))) m.set(pg.id, relative5(fileDir, shotOf(pg.id)).split("\\").join("/"));
60873
61851
  }
60874
61852
  return m;
60875
61853
  };
60876
61854
  const shotPaths = (pages) => {
60877
61855
  const m = /* @__PURE__ */ new Map();
60878
- for (const pg of pages) if (existsSync32(shotOf(pg.id))) m.set(pg.id, shotOf(pg.id));
61856
+ for (const pg of pages) if (existsSync34(shotOf(pg.id))) m.set(pg.id, shotOf(pg.id));
60879
61857
  return m;
60880
61858
  };
60881
61859
  const shotsCopiedInto = (dir) => {
60882
61860
  const m = /* @__PURE__ */ new Map();
60883
- const assets = join47(dir, "assets");
61861
+ const assets = join50(dir, "assets");
60884
61862
  for (const pg of derived) {
60885
61863
  const src = shotOf(pg.id);
60886
- if (!existsSync32(src)) continue;
61864
+ if (!existsSync34(src)) continue;
60887
61865
  mkdirSync17(assets, { recursive: true });
60888
- copyFileSync4(src, join47(assets, `${pg.id}.png`));
61866
+ copyFileSync4(src, join50(assets, `${pg.id}.png`));
60889
61867
  m.set(pg.id, `./assets/${pg.id}.png`);
60890
61868
  }
60891
61869
  return m;
@@ -60903,7 +61881,7 @@ async function cmdPages(p) {
60903
61881
  return 0;
60904
61882
  }
60905
61883
  mkdirSync17(outDir, { recursive: true });
60906
- const manifest = wantEvidence ? writeEvidence(result, { outDir }) : void 0;
61884
+ const manifest = wantEvidence ? writeEvidence(result, { outDir, ...evidenceCapsOf(p) }) : void 0;
60907
61885
  const cropFor = manifest ? (f) => {
60908
61886
  const c2 = manifest.crops.get(findingId(f));
60909
61887
  return c2 ? { href: c2.href, alt: c2.alt[lang] } : void 0;
@@ -60911,15 +61889,16 @@ async function cmdPages(p) {
60911
61889
  if (manifest) {
60912
61890
  const total = evidenceNotice(manifest, null, lang);
60913
61891
  console.error(
60914
- lang === "fr" ? `ultra11y : ${manifest.totals.imaged} vignette(s) \xE9crite(s) dans ${join47(outDir, "assets")}.` : `ultra11y: ${manifest.totals.imaged} crop(s) written to ${join47(outDir, "assets")}.`
61892
+ lang === "fr" ? `ultra11y : ${manifest.totals.imaged} vignette(s) \xE9crite(s) dans ${join50(outDir, "assets")}.` : `ultra11y: ${manifest.totals.imaged} crop(s) written to ${join50(outDir, "assets")}.`
60915
61893
  );
60916
61894
  for (const line of total) console.error(line);
60917
61895
  }
60918
61896
  const evidenceOpts = {
60919
- ...cropFor ? { cropFor } : {}
61897
+ ...cropFor ? { cropFor } : {},
61898
+ ...manifest ? { evidenceNotice: (id) => evidenceNotice(manifest, id, lang) } : {}
60920
61899
  };
60921
61900
  if (!split) {
60922
- const file = join47(outDir, `pages-${result.date}.md`);
61901
+ const file = join50(outDir, `pages-${result.date}.md`);
60923
61902
  writeFileSync19(file, `${renderPagesDocument(result, derived, { standard, lang, screenshots: shotsCopiedInto(outDir), ...evidenceOpts })}
60924
61903
  `);
60925
61904
  const html2 = wantHtml ? emitHtml(result, {
@@ -60939,14 +61918,10 @@ async function cmdPages(p) {
60939
61918
  const sheet = (id) => `page-${id}.md`;
60940
61919
  const hrefs = new Map(derived.map((pg) => [pg.id, `./${sheet(pg.id)}`]));
60941
61920
  for (const pg of derived) {
60942
- const notice = manifest ? evidenceNotice(manifest, pg.id, lang) : [];
60943
- writeFileSync19(
60944
- join47(outDir, sheet(pg.id)),
60945
- `${renderPageDocument(result, pg, { standard, lang, screenshots: shots, ...evidenceOpts, ...notice.length ? { evidenceNotice: notice } : {} })}
60946
- `
60947
- );
61921
+ writeFileSync19(join50(outDir, sheet(pg.id)), `${renderPageDocument(result, pg, { standard, lang, screenshots: shots, ...evidenceOpts })}
61922
+ `);
60948
61923
  }
60949
- const index = join47(outDir, "index.md");
61924
+ const index = join50(outDir, "index.md");
60950
61925
  writeFileSync19(index, `${renderPagesIndex(result, derived, { standard, lang, hrefs })}
60951
61926
  `);
60952
61927
  const html = wantHtml ? emitHtml(result, {
@@ -60962,11 +61937,20 @@ async function cmdPages(p) {
60962
61937
  console.log(index);
60963
61938
  return html?.imagesDropped ? 1 : 0;
60964
61939
  }
60965
- function budgetOf(p) {
60966
- const raw = p.flags["inline-budget"];
61940
+ function positiveIntFlag(p, name2) {
61941
+ const raw = p.flags[name2];
60967
61942
  if (typeof raw !== "string" || !raw) return void 0;
60968
61943
  const n = Number.parseInt(raw, 10);
60969
- return Number.isFinite(n) && n > 0 ? n : void 0;
61944
+ if (Number.isFinite(n) && n > 0) return n;
61945
+ console.error(`ultra11y: --${name2} expects a positive integer (got "${raw}"). Ignored \u2014 the default applies.`);
61946
+ return void 0;
61947
+ }
61948
+ function budgetOf(p) {
61949
+ return positiveIntFlag(p, "inline-budget");
61950
+ }
61951
+ function evidenceCapsOf(p) {
61952
+ const total = positiveIntFlag(p, "evidence-max");
61953
+ return total === void 0 ? {} : { caps: { total } };
60970
61954
  }
60971
61955
  function emitHtml(result, opts) {
60972
61956
  const res = writeHtml(result, opts);
@@ -60989,7 +61973,7 @@ async function cmdSnapshot(p) {
60989
61973
  2
60990
61974
  )
60991
61975
  );
60992
- else if (!snaps.length) console.log(lang === "fr" ? `Aucun instantan\xE9 dans ${join47(root, PAGES_DIR)}.` : `No snapshot in ${join47(root, PAGES_DIR)}.`);
61976
+ else if (!snaps.length) console.log(lang === "fr" ? `Aucun instantan\xE9 dans ${join50(root, PAGES_DIR)}.` : `No snapshot in ${join50(root, PAGES_DIR)}.`);
60993
61977
  else for (const s of snaps) console.log(`${s.meta.id} ${s.meta.name} ${s.meta.url}${s.meta.auth ? " [auth]" : ""}`);
60994
61978
  return 0;
60995
61979
  }
@@ -61036,7 +62020,7 @@ async function cmdSnapshot(p) {
61036
62020
  console.error(`ultra11y snapshot write: could not write the snapshot: ${e instanceof Error ? e.message : String(e)}`);
61037
62021
  return 1;
61038
62022
  }
61039
- const result = runAudit({ inputs: [join47(dir, "dom.html")], onWarn: (m) => console.error(m) });
62023
+ const result = runAudit({ inputs: [join50(dir, "dom.html")], onWarn: (m) => console.error(m) });
61040
62024
  const failOnRaw = p.flags["fail-on"];
61041
62025
  const failOnParsed = parseFailOn(failOnRaw);
61042
62026
  if (failOnRaw !== void 0 && failOnParsed === null) {
@@ -61068,8 +62052,8 @@ function cmdInit(p) {
61068
62052
  if (want.baseline) {
61069
62053
  const inputs = p.positionals.length ? p.positionals : ["."];
61070
62054
  const result = runAudit({ inputs, onWarn: (m) => console.error(m) });
61071
- mkdirSync17(join47(root, "audits"), { recursive: true });
61072
- const bp = join47(root, "audits", "baseline.json");
62055
+ mkdirSync17(join50(root, "audits"), { recursive: true });
62056
+ const bp = join50(root, "audits", "baseline.json");
61073
62057
  writeFileSync19(bp, JSON.stringify(result, null, 2) + "\n");
61074
62058
  wrote.push(bp);
61075
62059
  }
@@ -61205,17 +62189,18 @@ async function cmdReport(p) {
61205
62189
  emitCiFormat(result, ciFormat, standard, lang);
61206
62190
  return 0;
61207
62191
  }
61208
- const path = writeReport(result, { out: out2, lang, standard });
62192
+ const manifest = p.flags.evidence === true ? writeEvidence(result, { outDir: out2, ...evidenceCapsOf(p) }) : void 0;
62193
+ if (manifest) {
62194
+ for (const line of evidenceNotice(manifest, null, lang)) console.error(line);
62195
+ }
62196
+ const cropFor = manifest ? (f) => {
62197
+ const c2 = manifest.crops.get(findingId(f));
62198
+ return c2 ? { href: c2.href, alt: c2.alt[lang] } : void 0;
62199
+ } : void 0;
62200
+ const path = writeReport(result, { out: out2, lang, standard, ...cropFor ? { cropFor } : {} });
61209
62201
  let html;
61210
62202
  if (p.flags.html === true) {
61211
- const manifest = p.flags.evidence === true ? writeEvidence(result, { outDir: out2 }) : void 0;
61212
- if (manifest) for (const line of evidenceNotice(manifest, null, lang)) console.error(line);
61213
62203
  html = emitHtml(result, { outDir: out2, standard, lang, ...manifest ? { evidence: manifest } : {}, inlineBudget: budgetOf(p) });
61214
- } else if (p.flags.evidence === true) {
61215
- console.error(
61216
- lang === "fr" ? "ultra11y report : --evidence n'illustre que le rapport HTML. Ajoutez --html, ou utilisez `pages --format report --evidence` pour des fiches Markdown illustr\xE9es." : "ultra11y report: --evidence only illustrates the HTML report. Add --html, or use `pages --format report --evidence` for illustrated Markdown sheets."
61217
- );
61218
- return 2;
61219
62204
  }
61220
62205
  const untested = isCore(standard) ? [] : untestedNeedsRendering(result);
61221
62206
  const partial = untested.length > 0;
@@ -61369,7 +62354,7 @@ async function cmdTickets(p) {
61369
62354
  let setPath;
61370
62355
  if (ticketsOut) {
61371
62356
  mkdirSync17(ticketsOut, { recursive: true });
61372
- setPath = join47(ticketsOut, `issues-${result.date}.json`);
62357
+ setPath = join50(ticketsOut, `issues-${result.date}.json`);
61373
62358
  const payload = {
61374
62359
  tool: "ultra11y",
61375
62360
  kind: "issues",
@@ -61470,8 +62455,8 @@ async function cmdTickets(p) {
61470
62455
  return pushed.failed > 0 ? 1 : 0;
61471
62456
  }
61472
62457
  function depsAt(root) {
61473
- const pkgPath = join47(root, "package.json");
61474
- if (!existsSync32(pkgPath)) return {};
62458
+ const pkgPath = join50(root, "package.json");
62459
+ if (!existsSync34(pkgPath)) return {};
61475
62460
  try {
61476
62461
  const pkg = JSON.parse(readText(pkgPath));
61477
62462
  return { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
@@ -61497,24 +62482,24 @@ function cmdRender(p) {
61497
62482
  console.error(`ultra11y render: --runner must be playwright|cypress|auto (got "${forced}").`);
61498
62483
  return 2;
61499
62484
  }
61500
- const detected = detectE2eRunner(depsAt(root), (f) => existsSync32(join47(root, f)));
62485
+ const detected = detectE2eRunner(depsAt(root), (f) => existsSync34(join50(root, f)));
61501
62486
  const runners = forced && forced !== "auto" ? [forced] : detected;
61502
62487
  if (!runners.length) {
61503
62488
  console.error(e2eSetupPlan([], {}, lang));
61504
62489
  return 1;
61505
62490
  }
61506
62491
  const engineRef = engineRefFor(root);
61507
- const dir = join47(root, ".ultra11y", "e2e");
62492
+ const dir = join50(root, ".ultra11y", "e2e");
61508
62493
  const paths = {};
61509
62494
  try {
61510
62495
  mkdirSync17(dir, { recursive: true });
61511
62496
  if (runners.includes("playwright")) {
61512
- writeFileSync19(join47(dir, "playwright.mjs"), playwrightFixture(engineRef));
62497
+ writeFileSync19(join50(dir, "playwright.mjs"), playwrightFixture(engineRef));
61513
62498
  paths.playwright = ".ultra11y/e2e/playwright.mjs";
61514
62499
  }
61515
62500
  if (runners.includes("cypress")) {
61516
- writeFileSync19(join47(dir, "cypress-plugin.mjs"), cypressPlugin(engineRef));
61517
- writeFileSync19(join47(dir, "cypress-commands.mjs"), cypressCommands());
62501
+ writeFileSync19(join50(dir, "cypress-plugin.mjs"), cypressPlugin(engineRef));
62502
+ writeFileSync19(join50(dir, "cypress-commands.mjs"), cypressCommands());
61518
62503
  paths.cypressPlugin = ".ultra11y/e2e/cypress-plugin.mjs";
61519
62504
  paths.cypressCommands = ".ultra11y/e2e/cypress-commands.mjs";
61520
62505
  }
@@ -61543,7 +62528,7 @@ Fill in COMPONENTS, run it (e.g. npx tsx ${out2}), then: node scripts/ultra11y.m
61543
62528
  }
61544
62529
  if (p.flags.setup === true) {
61545
62530
  const rel2 = ".ultra11y/capture-setup.mjs";
61546
- const out2 = join47(root, rel2);
62531
+ const out2 = join50(root, rel2);
61547
62532
  try {
61548
62533
  mkdirSync17(dirname14(out2), { recursive: true });
61549
62534
  writeFileSync19(out2, captureSetup());
@@ -61552,20 +62537,20 @@ Fill in COMPONENTS, run it (e.g. npx tsx ${out2}), then: node scripts/ultra11y.m
61552
62537
  return 1;
61553
62538
  }
61554
62539
  let setupDeps = {};
61555
- const setupPkg = join47(root, "package.json");
61556
- if (existsSync32(setupPkg)) {
62540
+ const setupPkg = join50(root, "package.json");
62541
+ if (existsSync34(setupPkg)) {
61557
62542
  try {
61558
62543
  const pkg = JSON.parse(readText(setupPkg));
61559
62544
  setupDeps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
61560
62545
  } catch {
61561
62546
  }
61562
62547
  }
61563
- const tr = detectTestRunner(setupDeps, (f) => existsSync32(join47(root, f)));
62548
+ const tr = detectTestRunner(setupDeps, (f) => existsSync34(join50(root, f)));
61564
62549
  console.log(captureSetupPlan(tr, rel2, lang));
61565
62550
  const gaLine = ".ultra11y/captures/*.html text eol=lf linguist-generated=true";
61566
- const gaPath = join47(root, ".gitattributes");
62551
+ const gaPath = join50(root, ".gitattributes");
61567
62552
  try {
61568
- const existing = existsSync32(gaPath) ? readFileSync29(gaPath, "utf8") : "";
62553
+ const existing = existsSync34(gaPath) ? readFileSync32(gaPath, "utf8") : "";
61569
62554
  if (!existing.includes(".ultra11y/captures/")) {
61570
62555
  appendFileSync(gaPath, (existing && !existing.endsWith("\n") ? "\n" : "") + gaLine + "\n");
61571
62556
  console.log(lang === "fr" ? `.gitattributes : ajout\xE9 \xAB ${gaLine} \xBB` : `.gitattributes: added "${gaLine}"`);
@@ -61573,8 +62558,8 @@ Fill in COMPONENTS, run it (e.g. npx tsx ${out2}), then: node scripts/ultra11y.m
61573
62558
  } catch {
61574
62559
  }
61575
62560
  try {
61576
- const giPath = join47(root, ".gitignore");
61577
- if (existsSync32(giPath) && /^\s*\/?\.ultra11y(\/\**)?\/?\s*$/m.test(readFileSync29(giPath, "utf8")))
62561
+ const giPath = join50(root, ".gitignore");
62562
+ if (existsSync34(giPath) && /^\s*\/?\.ultra11y(\/\**)?\/?\s*$/m.test(readFileSync32(giPath, "utf8")))
61578
62563
  console.error(
61579
62564
  lang === "fr" ? "\u26A0\uFE0F .ultra11y semble ignor\xE9 par .gitignore \u2014 les captures doivent \xEAtre committ\xE9es pour le gate (ajoutez \xAB !.ultra11y/captures/ \xBB)." : '\u26A0\uFE0F .ultra11y appears gitignored \u2014 captures must be committed for the gate (add "!.ultra11y/captures/").'
61580
62565
  );
@@ -61584,8 +62569,8 @@ Fill in COMPONENTS, run it (e.g. npx tsx ${out2}), then: node scripts/ultra11y.m
61584
62569
  }
61585
62570
  if (p.flags.storybook === true || typeof p.flags.storybook === "string") {
61586
62571
  const sbDir = p.positionals[0] ?? "storybook-static";
61587
- const indexPath = existsSync32(join47(sbDir, "index.json")) ? join47(sbDir, "index.json") : join47(sbDir, "stories.json");
61588
- if (!existsSync32(indexPath)) {
62572
+ const indexPath = existsSync34(join50(sbDir, "index.json")) ? join50(sbDir, "index.json") : join50(sbDir, "stories.json");
62573
+ if (!existsSync34(indexPath)) {
61589
62574
  console.error(
61590
62575
  lang === "fr" ? `ultra11y render : aucun index Storybook (index.json/stories.json) dans ${sbDir}.` : `ultra11y render: no Storybook index (index.json/stories.json) in ${sbDir}.`
61591
62576
  );
@@ -61595,7 +62580,7 @@ Fill in COMPONENTS, run it (e.g. npx tsx ${out2}), then: node scripts/ultra11y.m
61595
62580
  const provById = new Map(stories.map((s) => [s.id, storyProvenance(s)]));
61596
62581
  const capturesFlag = typeof p.flags.captures === "string" && p.flags.captures ? p.flags.captures : void 0;
61597
62582
  const htmlDir = capturesFlag ?? sbDir;
61598
- const htmlFiles = existsSync32(htmlDir) ? discover([htmlDir]).files.filter((f) => /\.html?$/i.test(f)) : [];
62583
+ const htmlFiles = existsSync34(htmlDir) ? discover([htmlDir]).files.filter((f) => /\.html?$/i.test(f)) : [];
61599
62584
  const outDir = ".ultra11y/captures";
61600
62585
  let attributed = 0;
61601
62586
  let skipped = 0;
@@ -61618,7 +62603,7 @@ Fill in COMPONENTS, run it (e.g. npx tsx ${out2}), then: node scripts/ultra11y.m
61618
62603
  }
61619
62604
  try {
61620
62605
  mkdirSync17(outDir, { recursive: true });
61621
- writeFileSync19(join47(outDir, `${hitId}.html`), `${formatCaptureComment(prov)}
62606
+ writeFileSync19(join50(outDir, `${hitId}.html`), `${formatCaptureComment(prov)}
61622
62607
  ${raw}${raw.endsWith("\n") ? "" : "\n"}`);
61623
62608
  attributed++;
61624
62609
  } catch {
@@ -61640,11 +62625,11 @@ ${raw}${raw.endsWith("\n") ? "" : "\n"}`);
61640
62625
  }
61641
62626
  if (p.flags.coverage === true) {
61642
62627
  const capturesFlag = typeof p.flags.captures === "string" && p.flags.captures ? p.flags.captures : void 0;
61643
- const capturesDir = capturesFlag ?? join47(root, ".ultra11y/captures");
62628
+ const capturesDir = capturesFlag ?? join50(root, ".ultra11y/captures");
61644
62629
  const graphExt = [...GRAPH_ONLY_EXT, ...asList(p.flags.ext) ?? []];
61645
62630
  const sourceFiles = discover([root], { include: asList(p.flags.include), exclude: asList(p.flags.exclude), ext: graphExt }).files;
61646
62631
  const graph = buildGraphStreaming(sourceFiles);
61647
- const capFiles = existsSync32(capturesDir) ? discover([capturesDir]).files : [];
62632
+ const capFiles = existsSync34(capturesDir) ? discover([capturesDir]).files : [];
61648
62633
  const entries = capFiles.map((f) => ({ file: toPosix(f), provenance: parseCaptureProvenance(readText(f)) }));
61649
62634
  const cov = computeCaptureCoverage(graph, entries);
61650
62635
  if (p.flags.json) console.log(JSON.stringify(cov, null, 2));
@@ -61652,15 +62637,15 @@ ${raw}${raw.endsWith("\n") ? "" : "\n"}`);
61652
62637
  return 0;
61653
62638
  }
61654
62639
  let deps = {};
61655
- const pkgPath = join47(root, "package.json");
61656
- if (existsSync32(pkgPath)) {
62640
+ const pkgPath = join50(root, "package.json");
62641
+ if (existsSync34(pkgPath)) {
61657
62642
  try {
61658
62643
  const pkg = JSON.parse(readText(pkgPath));
61659
62644
  deps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
61660
62645
  } catch {
61661
62646
  }
61662
62647
  }
61663
- const detection = detectFrameworks(deps, (f) => existsSync32(join47(root, f)));
62648
+ const detection = detectFrameworks(deps, (f) => existsSync34(join50(root, f)));
61664
62649
  if (p.flags.json) console.log(JSON.stringify(detection, null, 2));
61665
62650
  else console.log(renderPlan(detection, lang));
61666
62651
  return 0;
@@ -61860,7 +62845,7 @@ function applyAdjudicationFile(p, adj, lang) {
61860
62845
  }
61861
62846
  const out2 = typeof p.flags.out === "string" ? p.flags.out : ".";
61862
62847
  mkdirSync17(out2, { recursive: true });
61863
- const auditPath = join47(out2, "audit-latest.json");
62848
+ const auditPath = join50(out2, "audit-latest.json");
61864
62849
  writeFileSync19(auditPath, JSON.stringify(r.audit, null, 2) + "\n");
61865
62850
  if (p.flags.json)
61866
62851
  console.log(
@@ -61970,7 +62955,7 @@ async function cmdJudge(p) {
61970
62955
  return 1;
61971
62956
  }
61972
62957
  mkdirSync17(out2, { recursive: true });
61973
- const auditPath = join47(out2, "audit-latest.json");
62958
+ const auditPath = join50(out2, "audit-latest.json");
61974
62959
  writeFileSync19(auditPath, `${JSON.stringify(r.audit, null, 2)}
61975
62960
  `);
61976
62961
  console.log(
@@ -62040,7 +63025,7 @@ async function cmdScan(p) {
62040
63025
  return 0;
62041
63026
  }
62042
63027
  for (const target of p.positionals.filter((a) => a !== "-")) {
62043
- if (/^https?:\/\//i.test(target) || existsSync32(target)) continue;
63028
+ if (/^https?:\/\//i.test(target) || existsSync34(target)) continue;
62044
63029
  console.error(
62045
63030
  lang === "fr" ? `ultra11y scan : fichier introuvable (file not found) : ${target}. Passez une URL http(s):// ou un fichier HTML existant.` : `ultra11y scan: File not found: ${target}. Pass an http(s):// URL or an existing HTML file.`
62046
63031
  );
@@ -62167,7 +63152,7 @@ async function cmdScan(p) {
62167
63152
  }
62168
63153
  let merged = mergeDynamic(audit2, dynamic, lang);
62169
63154
  if (dynamic.snapshots?.length) {
62170
- const doms = dynamic.snapshots.map((id) => join47(snapshotRoot ?? ".", PAGES_DIR, id, "dom.html")).filter((f) => existsSync32(f));
63155
+ const doms = dynamic.snapshots.map((id) => join50(snapshotRoot ?? ".", PAGES_DIR, id, "dom.html")).filter((f) => existsSync34(f));
62171
63156
  if (doms.length) {
62172
63157
  const snapAudit = runAudit({ inputs: doms, onWarn: (m) => console.error(m) });
62173
63158
  merged = mergeSnapshotAudit(merged, snapAudit);
@@ -62179,11 +63164,11 @@ async function cmdScan(p) {
62179
63164
  }
62180
63165
  }
62181
63166
  mkdirSync17(out2, { recursive: true });
62182
- writeFileSync19(join47(out2, "audit-latest.json"), JSON.stringify(merged, null, 2) + "\n");
63167
+ writeFileSync19(join50(out2, "audit-latest.json"), JSON.stringify(merged, null, 2) + "\n");
62183
63168
  if (p.flags.json) console.log(JSON.stringify(merged, null, 2));
62184
63169
  else {
62185
63170
  console.log(
62186
- lang === "fr" ? `Audit statique + dynamique fusionn\xE9 \u2192 ${join47(out2, "audit-latest.json")} (${merged.conformancePct}% r\xE9ussite, ${merged.findings.length} findings).` : `Static + dynamic audit merged \u2192 ${join47(out2, "audit-latest.json")} (${merged.conformancePct}% pass rate, ${merged.findings.length} findings).`
63171
+ lang === "fr" ? `Audit statique + dynamique fusionn\xE9 \u2192 ${join50(out2, "audit-latest.json")} (${merged.conformancePct}% r\xE9ussite, ${merged.findings.length} findings).` : `Static + dynamic audit merged \u2192 ${join50(out2, "audit-latest.json")} (${merged.conformancePct}% pass rate, ${merged.findings.length} findings).`
62187
63172
  );
62188
63173
  if (dynamic.snapshots?.length)
62189
63174
  console.log(
@@ -62426,7 +63411,7 @@ async function cmdImport(p) {
62426
63411
  }
62427
63412
  if (outDir) {
62428
63413
  mkdirSync17(outDir, { recursive: true });
62429
- const rawFile = join47(outDir, `external-${adapterId}-${arg}.raw.json`);
63414
+ const rawFile = join50(outDir, `external-${adapterId}-${arg}.raw.json`);
62430
63415
  writeFileSync19(rawFile, rawText.endsWith("\n") ? rawText : `${rawText}
62431
63416
  `);
62432
63417
  if (!p.flags.json) console.log(rawFile);
@@ -62462,7 +63447,7 @@ async function cmdImport(p) {
62462
63447
  }
62463
63448
  if (outDir) {
62464
63449
  mkdirSync17(outDir, { recursive: true });
62465
- const file = join47(outDir, "external-latest.json");
63450
+ const file = join50(outDir, "external-latest.json");
62466
63451
  writeFileSync19(file, `${JSON.stringify(audit2, null, 2)}
62467
63452
  `);
62468
63453
  console.log(file);
@@ -62568,7 +63553,7 @@ function cmdOrchestrate(p) {
62568
63553
  }
62569
63554
  const engineAbs = realpathSync6(fileURLToPath5(import.meta.url));
62570
63555
  if (p.flags.list === true) {
62571
- if (!existsSync32(runFlag)) {
63556
+ if (!existsSync34(runFlag)) {
62572
63557
  console.error(`ultra11y orchestrate: run dir not found: ${runFlag}.`);
62573
63558
  return 2;
62574
63559
  }
@@ -62595,7 +63580,7 @@ function cmdOrchestrate(p) {
62595
63580
  );
62596
63581
  } else {
62597
63582
  console.log(
62598
- lang === "fr" ? `Suivez ${join47(runFlag, "orchestration", "RUNBOOK.md")} s\xE9quentiellement (chemin \xE9co).` : `Follow ${join47(runFlag, "orchestration", "RUNBOOK.md")} sequentially (the eco path).`
63583
+ lang === "fr" ? `Suivez ${join50(runFlag, "orchestration", "RUNBOOK.md")} s\xE9quentiellement (chemin \xE9co).` : `Follow ${join50(runFlag, "orchestration", "RUNBOOK.md")} sequentially (the eco path).`
62599
63584
  );
62600
63585
  }
62601
63586
  if (p.flags.phase === void 0 && workflows.length === 0 && p.flags.eco !== true) {