ultra11y 2.23.0 → 2.24.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 realpathSync2, writeFileSync as writeFileSync14, mkdirSync as mkdirSync11, existsSync as existsSync20, readFileSync as readFileSync17, appendFileSync } from "fs";
5
- import { join as join36, relative as relative3, sep as sep4, dirname as dirname10 } from "path";
6
- import { fileURLToPath as fileURLToPath4, pathToFileURL as pathToFileURL3 } from "url";
4
+ import { realpathSync as realpathSync4, writeFileSync as writeFileSync14, mkdirSync as mkdirSync11, existsSync as existsSync22, readFileSync as readFileSync19, appendFileSync } from "fs";
5
+ import { join as join38, relative as relative3, sep as sep6, dirname as dirname11 } from "path";
6
+ import { fileURLToPath as fileURLToPath5, pathToFileURL as pathToFileURL3 } from "url";
7
7
 
8
8
  // src/types.ts
9
- var VERSION = "2.23.0";
9
+ var VERSION = "2.24.0";
10
10
  var SCHEMA_VERSION = 2;
11
11
 
12
12
  // src/audit.ts
@@ -10483,9 +10483,9 @@ var Tokenizer2 = class extends CommentsParser {
10483
10483
  });
10484
10484
  }
10485
10485
  finishOp(type, size) {
10486
- const str2 = this.input.slice(this.state.pos, this.state.pos + size);
10486
+ const str4 = this.input.slice(this.state.pos, this.state.pos + size);
10487
10487
  this.state.pos += size;
10488
- this.finishToken(type, str2);
10488
+ this.finishToken(type, str4);
10489
10489
  }
10490
10490
  readRegexp() {
10491
10491
  const startLoc = this.state.startLoc;
@@ -10577,8 +10577,8 @@ var Tokenizer2 = class extends CommentsParser {
10577
10577
  throw this.raise(Errors.NumberIdentifier, this.state.curPosition());
10578
10578
  }
10579
10579
  if (isBigInt) {
10580
- const str2 = this.input.slice(start2, this.state.pos).replace(/[_n]/g, "");
10581
- this.finishToken(132, str2);
10580
+ const str4 = this.input.slice(start2, this.state.pos).replace(/[_n]/g, "");
10581
+ this.finishToken(132, str4);
10582
10582
  return;
10583
10583
  }
10584
10584
  this.finishToken(131, val);
@@ -10622,7 +10622,7 @@ var Tokenizer2 = class extends CommentsParser {
10622
10622
  isFloat = true;
10623
10623
  next = this.input.charCodeAt(this.state.pos);
10624
10624
  }
10625
- const str2 = this.input.slice(start2, this.state.pos).replaceAll("_", "");
10625
+ const str4 = this.input.slice(start2, this.state.pos).replaceAll("_", "");
10626
10626
  if (next === 110) {
10627
10627
  if (isFloat || hasLeadingZero) {
10628
10628
  this.raise(Errors.InvalidBigIntLiteral, startLoc);
@@ -10634,10 +10634,10 @@ var Tokenizer2 = class extends CommentsParser {
10634
10634
  throw this.raise(Errors.NumberIdentifier, this.state.curPosition());
10635
10635
  }
10636
10636
  if (isBigInt) {
10637
- this.finishToken(132, str2);
10637
+ this.finishToken(132, str4);
10638
10638
  return;
10639
10639
  }
10640
- const val = isOctal ? parseInt(str2, 8) : parseFloat(str2);
10640
+ const val = isOctal ? parseInt(str4, 8) : parseFloat(str4);
10641
10641
  this.finishToken(131, val);
10642
10642
  }
10643
10643
  readCodePoint(throwOnInvalid) {
@@ -10650,7 +10650,7 @@ var Tokenizer2 = class extends CommentsParser {
10650
10650
  }
10651
10651
  readString(quote) {
10652
10652
  const {
10653
- str: str2,
10653
+ str: str4,
10654
10654
  pos,
10655
10655
  curLine,
10656
10656
  lineStart
@@ -10658,7 +10658,7 @@ var Tokenizer2 = class extends CommentsParser {
10658
10658
  this.state.pos = pos + 1;
10659
10659
  this.state.lineStart = lineStart;
10660
10660
  this.state.curLine = curLine;
10661
- this.finishToken(130, str2);
10661
+ this.finishToken(130, str4);
10662
10662
  }
10663
10663
  readTemplateContinuation() {
10664
10664
  if (!this.match(4)) {
@@ -10670,7 +10670,7 @@ var Tokenizer2 = class extends CommentsParser {
10670
10670
  readTemplateToken() {
10671
10671
  const opening2 = this.input[this.state.pos];
10672
10672
  const {
10673
- str: str2,
10673
+ str: str4,
10674
10674
  firstInvalidLoc,
10675
10675
  pos,
10676
10676
  curLine,
@@ -10683,10 +10683,10 @@ var Tokenizer2 = class extends CommentsParser {
10683
10683
  this.state.firstInvalidTemplateEscapePos = new Position(firstInvalidLoc.curLine, firstInvalidLoc.pos - firstInvalidLoc.lineStart, this.sourceToOffsetPos(firstInvalidLoc.pos));
10684
10684
  }
10685
10685
  if (this.input.codePointAt(pos) === 96) {
10686
- this.finishToken(20, firstInvalidLoc ? null : opening2 + str2 + "`");
10686
+ this.finishToken(20, firstInvalidLoc ? null : opening2 + str4 + "`");
10687
10687
  } else {
10688
10688
  this.state.pos++;
10689
- this.finishToken(21, firstInvalidLoc ? null : opening2 + str2 + "${");
10689
+ this.finishToken(21, firstInvalidLoc ? null : opening2 + str4 + "${");
10690
10690
  }
10691
10691
  }
10692
10692
  recordStrictModeErrors(toParseError, at) {
@@ -19470,9 +19470,9 @@ function diffFiles(dir, spec) {
19470
19470
  }
19471
19471
  }
19472
19472
  const byPath = new Map(out2.map((f) => [f.path, f]));
19473
- const num2 = sh("git", [...gitArgs(dir), "diff", "-z", "-M", "--numstat", ...rangeArgs(spec)]);
19474
- if (num2.ok) {
19475
- const toks = num2.stdout.split("\0");
19473
+ const num22 = sh("git", [...gitArgs(dir), "diff", "-z", "-M", "--numstat", ...rangeArgs(spec)]);
19474
+ if (num22.ok) {
19475
+ const toks = num22.stdout.split("\0");
19476
19476
  let i2 = 0;
19477
19477
  while (i2 < toks.length) {
19478
19478
  const head = toks[i2++];
@@ -20904,16 +20904,16 @@ async function Module2(moduleArg = {}) {
20904
20904
  if (endPtr - idx > 16 && heapOrArray.buffer && UTF8Decoder) {
20905
20905
  return UTF8Decoder.decode(heapOrArray.subarray(idx, endPtr));
20906
20906
  }
20907
- var str2 = "";
20907
+ var str22 = "";
20908
20908
  while (idx < endPtr) {
20909
20909
  var u0 = heapOrArray[idx++];
20910
20910
  if (!(u0 & 128)) {
20911
- str2 += String.fromCharCode(u0);
20911
+ str22 += String.fromCharCode(u0);
20912
20912
  continue;
20913
20913
  }
20914
20914
  var u1 = heapOrArray[idx++] & 63;
20915
20915
  if ((u0 & 224) == 192) {
20916
- str2 += String.fromCharCode((u0 & 31) << 6 | u1);
20916
+ str22 += String.fromCharCode((u0 & 31) << 6 | u1);
20917
20917
  continue;
20918
20918
  }
20919
20919
  var u2 = heapOrArray[idx++] & 63;
@@ -20923,13 +20923,13 @@ async function Module2(moduleArg = {}) {
20923
20923
  u0 = (u0 & 7) << 18 | u1 << 12 | u2 << 6 | heapOrArray[idx++] & 63;
20924
20924
  }
20925
20925
  if (u0 < 65536) {
20926
- str2 += String.fromCharCode(u0);
20926
+ str22 += String.fromCharCode(u0);
20927
20927
  } else {
20928
20928
  var ch = u0 - 65536;
20929
- str2 += String.fromCharCode(55296 | ch >> 10, 56320 | ch & 1023);
20929
+ str22 += String.fromCharCode(55296 | ch >> 10, 56320 | ch & 1023);
20930
20930
  }
20931
20931
  }
20932
- return str2;
20932
+ return str22;
20933
20933
  }, "UTF8ArrayToString");
20934
20934
  var getDylinkMetadata = /* @__PURE__ */ __name((binary2) => {
20935
20935
  var offset = 0;
@@ -21695,7 +21695,7 @@ async function Module2(moduleArg = {}) {
21695
21695
  _fd_close.sig = "ii";
21696
21696
  var INT53_MAX = 9007199254740992;
21697
21697
  var INT53_MIN = -9007199254740992;
21698
- var bigintToI53Checked = /* @__PURE__ */ __name((num2) => num2 < INT53_MIN || num2 > INT53_MAX ? NaN : Number(num2), "bigintToI53Checked");
21698
+ var bigintToI53Checked = /* @__PURE__ */ __name((num22) => num22 < INT53_MIN || num22 > INT53_MAX ? NaN : Number(num22), "bigintToI53Checked");
21699
21699
  function _fd_seek(fd, offset, whence, newOffset) {
21700
21700
  offset = bigintToI53Checked(offset);
21701
21701
  return 70;
@@ -21713,7 +21713,7 @@ async function Module2(moduleArg = {}) {
21713
21713
  }
21714
21714
  }, "printChar");
21715
21715
  var _fd_write = /* @__PURE__ */ __name((fd, iov, iovcnt, pnum) => {
21716
- var num2 = 0;
21716
+ var num22 = 0;
21717
21717
  for (var i2 = 0; i2 < iovcnt; i2++) {
21718
21718
  var ptr = LE_HEAP_LOAD_U32((iov >> 2) * 4);
21719
21719
  var len = LE_HEAP_LOAD_U32((iov + 4 >> 2) * 4);
@@ -21721,9 +21721,9 @@ async function Module2(moduleArg = {}) {
21721
21721
  for (var j = 0; j < len; j++) {
21722
21722
  printChar(fd, HEAPU8[ptr + j]);
21723
21723
  }
21724
- num2 += len;
21724
+ num22 += len;
21725
21725
  }
21726
- LE_HEAP_STORE_U32((pnum >> 2) * 4, num2);
21726
+ LE_HEAP_STORE_U32((pnum >> 2) * 4, num22);
21727
21727
  return 0;
21728
21728
  }, "_fd_write");
21729
21729
  _fd_write.sig = "iippp";
@@ -21788,10 +21788,10 @@ async function Module2(moduleArg = {}) {
21788
21788
  }
21789
21789
  quit_(1, e);
21790
21790
  }, "handleException");
21791
- var lengthBytesUTF8 = /* @__PURE__ */ __name((str2) => {
21791
+ var lengthBytesUTF8 = /* @__PURE__ */ __name((str22) => {
21792
21792
  var len = 0;
21793
- for (var i2 = 0; i2 < str2.length; ++i2) {
21794
- var c2 = str2.charCodeAt(i2);
21793
+ for (var i2 = 0; i2 < str22.length; ++i2) {
21794
+ var c2 = str22.charCodeAt(i2);
21795
21795
  if (c2 <= 127) {
21796
21796
  len++;
21797
21797
  } else if (c2 <= 2047) {
@@ -21805,12 +21805,12 @@ async function Module2(moduleArg = {}) {
21805
21805
  }
21806
21806
  return len;
21807
21807
  }, "lengthBytesUTF8");
21808
- var stringToUTF8Array = /* @__PURE__ */ __name((str2, heap, outIdx, maxBytesToWrite) => {
21808
+ var stringToUTF8Array = /* @__PURE__ */ __name((str22, heap, outIdx, maxBytesToWrite) => {
21809
21809
  if (!(maxBytesToWrite > 0)) return 0;
21810
21810
  var startIdx = outIdx;
21811
21811
  var endIdx = outIdx + maxBytesToWrite - 1;
21812
- for (var i2 = 0; i2 < str2.length; ++i2) {
21813
- var u = str2.codePointAt(i2);
21812
+ for (var i2 = 0; i2 < str22.length; ++i2) {
21813
+ var u = str22.codePointAt(i2);
21814
21814
  if (u <= 127) {
21815
21815
  if (outIdx >= endIdx) break;
21816
21816
  heap[outIdx++] = u;
@@ -21835,30 +21835,30 @@ async function Module2(moduleArg = {}) {
21835
21835
  heap[outIdx] = 0;
21836
21836
  return outIdx - startIdx;
21837
21837
  }, "stringToUTF8Array");
21838
- var stringToUTF8 = /* @__PURE__ */ __name((str2, outPtr, maxBytesToWrite) => stringToUTF8Array(str2, HEAPU8, outPtr, maxBytesToWrite), "stringToUTF8");
21838
+ var stringToUTF8 = /* @__PURE__ */ __name((str22, outPtr, maxBytesToWrite) => stringToUTF8Array(str22, HEAPU8, outPtr, maxBytesToWrite), "stringToUTF8");
21839
21839
  var stackAlloc = /* @__PURE__ */ __name((sz) => __emscripten_stack_alloc(sz), "stackAlloc");
21840
- var stringToUTF8OnStack = /* @__PURE__ */ __name((str2) => {
21841
- var size = lengthBytesUTF8(str2) + 1;
21840
+ var stringToUTF8OnStack = /* @__PURE__ */ __name((str22) => {
21841
+ var size = lengthBytesUTF8(str22) + 1;
21842
21842
  var ret = stackAlloc(size);
21843
- stringToUTF8(str2, ret, size);
21843
+ stringToUTF8(str22, ret, size);
21844
21844
  return ret;
21845
21845
  }, "stringToUTF8OnStack");
21846
21846
  var AsciiToString = /* @__PURE__ */ __name((ptr) => {
21847
- var str2 = "";
21847
+ var str22 = "";
21848
21848
  while (1) {
21849
21849
  var ch = HEAPU8[ptr++];
21850
- if (!ch) return str2;
21851
- str2 += String.fromCharCode(ch);
21850
+ if (!ch) return str22;
21851
+ str22 += String.fromCharCode(ch);
21852
21852
  }
21853
21853
  }, "AsciiToString");
21854
- var stringToUTF16 = /* @__PURE__ */ __name((str2, outPtr, maxBytesToWrite) => {
21854
+ var stringToUTF16 = /* @__PURE__ */ __name((str22, outPtr, maxBytesToWrite) => {
21855
21855
  maxBytesToWrite ??= 2147483647;
21856
21856
  if (maxBytesToWrite < 2) return 0;
21857
21857
  maxBytesToWrite -= 2;
21858
21858
  var startPtr = outPtr;
21859
- var numCharsToWrite = maxBytesToWrite < str2.length * 2 ? maxBytesToWrite / 2 : str2.length;
21859
+ var numCharsToWrite = maxBytesToWrite < str22.length * 2 ? maxBytesToWrite / 2 : str22.length;
21860
21860
  for (var i2 = 0; i2 < numCharsToWrite; ++i2) {
21861
- var codeUnit = str2.charCodeAt(i2);
21861
+ var codeUnit = str22.charCodeAt(i2);
21862
21862
  LE_HEAP_STORE_I16((outPtr >> 1) * 2, codeUnit);
21863
21863
  outPtr += 2;
21864
21864
  }
@@ -22200,8 +22200,8 @@ function parseAnyPredicate(steps, index, operator, textPredicates) {
22200
22200
  if (c2.name === captureName1) nodes1.push(c2.node);
22201
22201
  if (c2.name === captureName2) nodes2.push(c2.node);
22202
22202
  }
22203
- const compare = /* @__PURE__ */ __name((n1, n2, positive) => {
22204
- return positive ? n1.text === n2.text : n1.text !== n2.text;
22203
+ const compare = /* @__PURE__ */ __name((n1, n2, positive2) => {
22204
+ return positive2 ? n1.text === n2.text : n1.text !== n2.text;
22205
22205
  }, "compare");
22206
22206
  return matchAll ? nodes1.every((n1) => nodes2.some((n2) => compare(n1, n2, isPositive))) : nodes1.some((n1) => nodes2.some((n2) => compare(n1, n2, isPositive)));
22207
22207
  });
@@ -22245,8 +22245,8 @@ function parseMatchPredicate(steps, index, operator, textPredicates) {
22245
22245
  for (const c2 of captures) {
22246
22246
  if (c2.name === captureName) nodes.push(c2.node.text);
22247
22247
  }
22248
- const test = /* @__PURE__ */ __name((text, positive) => {
22249
- return positive ? regex.test(text) : !regex.test(text);
22248
+ const test = /* @__PURE__ */ __name((text, positive2) => {
22249
+ return positive2 ? regex.test(text) : !regex.test(text);
22250
22250
  }, "test");
22251
22251
  if (nodes.length === 0) return !isPositive;
22252
22252
  return matchAll ? nodes.every((text) => test(text, isPositive)) : nodes.some((text) => test(text, isPositive));
@@ -24658,8 +24658,8 @@ function collectAll(root, spec, defNames, maxCalls, wantImports) {
24658
24658
  if (wantRefs) {
24659
24659
  const how = spec.imports[type];
24660
24660
  if (how === "string") {
24661
- const str2 = findFirst(node, (n) => /string/.test(n.type));
24662
- if (str2) addRef(str2.text.replace(/^['"]|['"]$/g, ""));
24661
+ const str22 = findFirst(node, (n) => /string/.test(n.type));
24662
+ if (str22) addRef(str22.text.replace(/^['"]|['"]$/g, ""));
24663
24663
  } else if (how === "path") {
24664
24664
  const name2 = node.childForFieldName("name") ?? node.childForFieldName("module_name");
24665
24665
  addRef((name2 ?? node).text.replace(/^(import|from)\s+/, "").split(/\s+/)[0]);
@@ -28765,14 +28765,14 @@ function buildEmbeddingIndex(scan2, model) {
28765
28765
  return { embedVersion: EMBED_VERSION, modelId: model.modelId, dim: model.dim, records };
28766
28766
  }
28767
28767
  function serializeEmbeddings(index) {
28768
- const header2 = JSON.stringify({
28768
+ const header3 = JSON.stringify({
28769
28769
  embedVersion: index.embedVersion,
28770
28770
  modelId: index.modelId,
28771
28771
  dim: index.dim,
28772
28772
  count: index.records.length,
28773
28773
  records: index.records.map((r) => ({ file: r.file, symbol: r.symbol ?? "", line: r.line ?? 0 }))
28774
28774
  });
28775
- const headerBuf = Buffer.from(header2, "utf8");
28775
+ const headerBuf = Buffer.from(header3, "utf8");
28776
28776
  const body2 = Buffer.alloc(index.records.length * index.dim);
28777
28777
  let off = 0;
28778
28778
  for (const r of index.records) {
@@ -28791,10 +28791,10 @@ function deserializeEmbeddings(bytes) {
28791
28791
  throw new Error("embeddings.bin: bad magic (not a codeindex embeddings artifact)");
28792
28792
  }
28793
28793
  const headerLen = buf.readUInt32LE(4);
28794
- const header2 = JSON.parse(buf.toString("utf8", 8, 8 + headerLen));
28794
+ const header3 = JSON.parse(buf.toString("utf8", 8, 8 + headerLen));
28795
28795
  const bodyOff = 8 + headerLen;
28796
- const { dim } = header2;
28797
- const records = header2.records.map((m, i2) => {
28796
+ const { dim } = header3;
28797
+ const records = header3.records.map((m, i2) => {
28798
28798
  const vec = new Int8Array(dim);
28799
28799
  for (let d = 0; d < dim; d++) vec[d] = buf.readInt8(bodyOff + i2 * dim + d);
28800
28800
  const rec = { file: m.file, vec };
@@ -28802,7 +28802,7 @@ function deserializeEmbeddings(bytes) {
28802
28802
  if (m.line) rec.line = m.line;
28803
28803
  return rec;
28804
28804
  });
28805
- return { embedVersion: header2.embedVersion, modelId: header2.modelId, dim, records };
28805
+ return { embedVersion: header3.embedVersion, modelId: header3.modelId, dim, records };
28806
28806
  }
28807
28807
  var MAGIC;
28808
28808
  var init_embed = __esm({
@@ -29188,9 +29188,9 @@ function renderRepoMap(scan2, graph, opts = {}) {
29188
29188
  const maxSymbols = opts.maxSymbolsPerFile ?? 8;
29189
29189
  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));
29190
29190
  const records = new Map(scan2.files.map((f) => [f.rel, f]));
29191
- const header2 = `# repo map \u2014 ${graph.fileCount} files
29191
+ const header3 = `# repo map \u2014 ${graph.fileCount} files
29192
29192
  `;
29193
- let out2 = header2;
29193
+ let out2 = header3;
29194
29194
  let files = 0;
29195
29195
  for (const node of ranked) {
29196
29196
  const rec = records.get(node.rel);
@@ -31808,7 +31808,7 @@ function parseFlags(args2) {
31808
31808
  if (v === void 0) throw new Error(`missing value for ${a}`);
31809
31809
  return v;
31810
31810
  };
31811
- const num2 = () => {
31811
+ const num22 = () => {
31812
31812
  const raw = next();
31813
31813
  const n = Number(raw);
31814
31814
  if (!Number.isFinite(n) || n <= 0) throw new Error(`${a} expects a positive number, got "${raw}"`);
@@ -31824,12 +31824,12 @@ function parseFlags(args2) {
31824
31824
  else if (a === "--scope") flags2.scope = next();
31825
31825
  else if (a === "--no-gitignore") flags2.gitignore = false;
31826
31826
  else if (a === "--ignore-dir") flags2.ignoreDirs.push(next());
31827
- else if (a === "--max-files") flags2.maxFiles = num2();
31828
- else if (a === "--max-bytes") flags2.maxBytes = num2();
31829
- else if (a === "--max-calls") flags2.maxCalls = num2();
31827
+ else if (a === "--max-files") flags2.maxFiles = num22();
31828
+ else if (a === "--max-bytes") flags2.maxBytes = num22();
31829
+ else if (a === "--max-calls") flags2.maxCalls = num22();
31830
31830
  else if (a === "--ignore-case") flags2.ignoreCase = true;
31831
- else if (a === "--max-hits") flags2.maxHits = num2();
31832
- else if (a === "--budget-tokens") flags2.budgetTokens = num2();
31831
+ else if (a === "--max-hits") flags2.maxHits = num22();
31832
+ else if (a === "--budget-tokens") flags2.budgetTokens = num22();
31833
31833
  else if (a === "--no-ast") flags2.noAst = true;
31834
31834
  else if (a === "--index") flags2.indexDir = next();
31835
31835
  else if (a === "--no-index-cache") flags2.noIndexCache = true;
@@ -31840,14 +31840,14 @@ function parseFlags(args2) {
31840
31840
  flags2.workers = n;
31841
31841
  } else if (a === "--since") flags2.since = next();
31842
31842
  else if (a === "--config") flags2.config = resolve2(next());
31843
- else if (a === "--limit") flags2.limit = num2();
31843
+ else if (a === "--limit") flags2.limit = num22();
31844
31844
  else if (a === "--no-fuzzy") flags2.fuzzy = false;
31845
31845
  else if (a === "--semantic") flags2.semantic = true;
31846
31846
  else if (a === "--recall") flags2.recall = true;
31847
31847
  else if (a === "--run") flags2.run = true;
31848
31848
  else if (a === "--base") flags2.base = next();
31849
31849
  else if (a === "--staged") flags2.staged = true;
31850
- else if (a === "--depth") flags2.depth = num2();
31850
+ else if (a === "--depth") flags2.depth = num22();
31851
31851
  else if (a === "--kind") flags2.kind = next();
31852
31852
  else if (a === "--json") flags2.json = true;
31853
31853
  else if (!a.startsWith("--") && flags2.positional === void 0) flags2.positional = a;
@@ -42577,9 +42577,9 @@ function stagedContents(files) {
42577
42577
  for (const file of files) {
42578
42578
  const nl = buf.indexOf(10, pos);
42579
42579
  if (nl === -1) break;
42580
- const header2 = buf.toString("utf8", pos, nl);
42580
+ const header3 = buf.toString("utf8", pos, nl);
42581
42581
  pos = nl + 1;
42582
- const m = /^[0-9a-f]{40,64} \w+ (\d+)$/.exec(header2);
42582
+ const m = /^[0-9a-f]{40,64} \w+ (\d+)$/.exec(header3);
42583
42583
  if (!m) continue;
42584
42584
  const size = Number(m[1]);
42585
42585
  out2.set(file, buf.toString("utf8", pos, pos + size));
@@ -42977,10 +42977,10 @@ function applySecondaryMappings(base, pc, enabled, sources, defaultLocale) {
42977
42977
  if (added.some((f) => !f.advisory)) return { id: pc.id, theme: pc.theme, status: aggregate2([base.status, "NC"]), findings, scs: base.scs };
42978
42978
  return { ...base, findings };
42979
42979
  }
42980
- function derivePackResults(audit, packKey) {
42980
+ function derivePackResults(audit2, packKey) {
42981
42981
  const pack = loadPack(packKey);
42982
- const byScId = new Map(audit.criteria.map((c2) => [c2.id, c2]));
42983
- const myPackFindings = (audit.packFindings ?? []).filter((f) => f.ruleId.startsWith(`pack:${packKey}:`));
42982
+ const byScId = new Map(audit2.criteria.map((c2) => [c2.id, c2]));
42983
+ const myPackFindings = (audit2.packFindings ?? []).filter((f) => f.ruleId.startsWith(`pack:${packKey}:`));
42984
42984
  const overrides = pack.overrides;
42985
42985
  const deriveBase = (pc) => {
42986
42986
  const outOfScope = pc.wcag.every((sc) => {
@@ -43009,8 +43009,8 @@ function derivePackResults(audit, packKey) {
43009
43009
  return { id: pc.id, theme: pc.theme, status, findings, scs: pc.wcag };
43010
43010
  };
43011
43011
  const enabledSecondary = (pack.secondaryMappings ?? []).filter((m) => m.enabled === true);
43012
- const secondarySources = enabledSecondary.length ? [...audit.criteria.flatMap((c2) => c2.findings), ...myPackFindings] : [];
43013
- const adjudicated = audit.packAdjudication?.standard === packKey ? new Map(audit.packAdjudication.criteria.map((c2) => [c2.id, c2])) : void 0;
43012
+ const secondarySources = enabledSecondary.length ? [...audit2.criteria.flatMap((c2) => c2.findings), ...myPackFindings] : [];
43013
+ const adjudicated = audit2.packAdjudication?.standard === packKey ? new Map(audit2.packAdjudication.criteria.map((c2) => [c2.id, c2])) : void 0;
43014
43014
  return pack.criteria.map((pc) => {
43015
43015
  const decided = adjudicated?.get(pc.id);
43016
43016
  if (decided) {
@@ -43028,10 +43028,10 @@ function derivePackResults(audit, packKey) {
43028
43028
  return enabledSecondary.length ? applySecondaryMappings(base, pc, enabledSecondary, secondarySources, pack.defaultLocale) : base;
43029
43029
  });
43030
43030
  }
43031
- function findingsForStandard(audit, standard) {
43032
- if (standard === CORE_KEY) return audit.findings;
43033
- const mine = (audit.packFindings ?? []).filter((f) => f.ruleId.startsWith(`pack:${standard}:`));
43034
- return mine.length ? [...audit.findings, ...mine] : audit.findings;
43031
+ function findingsForStandard(audit2, standard) {
43032
+ if (standard === CORE_KEY) return audit2.findings;
43033
+ const mine = (audit2.packFindings ?? []).filter((f) => f.ruleId.startsWith(`pack:${standard}:`));
43034
+ return mine.length ? [...audit2.findings, ...mine] : audit2.findings;
43035
43035
  }
43036
43036
 
43037
43037
  // src/standards/vocabulary.ts
@@ -47245,10 +47245,10 @@ function legacyNcHeader(standard) {
47245
47245
  }
47246
47246
  function buildWorklistLegacy(reportMd, standard, max) {
47247
47247
  const items = [];
47248
- const header2 = legacyNcHeader(standard);
47248
+ const header3 = legacyNcHeader(standard);
47249
47249
  const lines = reportMd.split("\n");
47250
47250
  for (let i2 = 0; i2 < lines.length && items.length < max; i2++) {
47251
- const h = header2.exec(lines[i2]);
47251
+ const h = header3.exec(lines[i2]);
47252
47252
  if (!h) continue;
47253
47253
  let claim = h[2] ?? "";
47254
47254
  for (let j = i2 + 1; j < Math.min(i2 + 4, lines.length); j++) {
@@ -48512,8 +48512,8 @@ var HARVESTERS = {
48512
48512
  // 3.1.2 Language of Parts — element-level lang overrides (not the root <html lang>)
48513
48513
  "3.1.2": (docs) => docs.flatMap((d) => d.elements.filter((e) => e.tag !== "html" && attr(e, "lang") !== void 0).map((e) => ev(d, e, `lang="${attr(e, "lang")}"`)))
48514
48514
  };
48515
- function docsForAudit(audit, cwd) {
48516
- const inputs = audit.scope.inputs.filter((i2) => i2 !== "-" && i2 !== "<stdin>");
48515
+ function docsForAudit(audit2, cwd) {
48516
+ const inputs = audit2.scope.inputs.filter((i2) => i2 !== "-" && i2 !== "<stdin>");
48517
48517
  if (!inputs.length) return [];
48518
48518
  const { files } = discover(inputs, {});
48519
48519
  const docs = [];
@@ -48554,12 +48554,12 @@ function packEvidence(scs, docs) {
48554
48554
  }
48555
48555
  return out2;
48556
48556
  }
48557
- function buildAdjudicationWorklist(audit, opts = {}) {
48558
- const docs = docsForAudit(audit, opts.cwd);
48557
+ function buildAdjudicationWorklist(audit2, opts = {}) {
48558
+ const docs = docsForAudit(audit2, opts.cwd);
48559
48559
  const standard = opts.standard;
48560
48560
  if (standard !== void 0 && !isCore(standard)) {
48561
48561
  const pack = loadPack(standard);
48562
- return derivePackResults(audit, standard).filter((pc) => pc.status === "manual").map((pc) => {
48562
+ return derivePackResults(audit2, standard).filter((pc) => pc.status === "manual").map((pc) => {
48563
48563
  const crit = getCriterion(pack, pc.id);
48564
48564
  const scs = crit?.wcag ?? pc.scs;
48565
48565
  const autos = scs.map((sc) => getSC(sc)?.automatability).filter((a) => !!a);
@@ -48567,7 +48567,7 @@ function buildAdjudicationWorklist(audit, opts = {}) {
48567
48567
  return blankItem(pc.id, automatability, crit ? titlePlain(pack, crit, "fr") : void 0, packEvidence(scs, docs));
48568
48568
  });
48569
48569
  }
48570
- return audit.residualRisks.map(
48570
+ return audit2.residualRisks.map(
48571
48571
  (r) => blankItem(r.criteriaId, r.automatability, scTitle(r.criteriaId) ?? void 0, HARVESTERS[r.criteriaId]?.(docs) ?? [])
48572
48572
  );
48573
48573
  }
@@ -48593,16 +48593,16 @@ function normativeRefResolves(ref, standard, itemCriterionId) {
48593
48593
  if (testKey !== void 0 && !(crit.tests && Object.hasOwn(crit.tests, testKey))) return false;
48594
48594
  return itemCriterionId === void 0 || critId === itemCriterionId;
48595
48595
  }
48596
- function applyAdjudication(audit, adj, opts = {}) {
48596
+ function applyAdjudication(audit2, adj, opts = {}) {
48597
48597
  const issues = [];
48598
48598
  const byId2 = new Map(adj.items.map((it) => [it.criteriaId, it]));
48599
48599
  const packMode = !isCore(adj.standard);
48600
48600
  if (packMode) {
48601
- for (const pc of derivePackResults(audit, adj.standard)) {
48601
+ for (const pc of derivePackResults(audit2, adj.standard)) {
48602
48602
  if (pc.status === "manual" && !byId2.has(pc.id)) issues.push(`criterion ${pc.id}: missing from the adjudication (coverage gap)`);
48603
48603
  }
48604
48604
  } else {
48605
- for (const r of audit.residualRisks) if (!byId2.has(r.criteriaId)) issues.push(`criterion ${r.criteriaId}: missing from the adjudication (coverage gap)`);
48605
+ for (const r of audit2.residualRisks) if (!byId2.has(r.criteriaId)) issues.push(`criterion ${r.criteriaId}: missing from the adjudication (coverage gap)`);
48606
48606
  }
48607
48607
  const groundInputs = [];
48608
48608
  for (const it of adj.items) {
@@ -48634,9 +48634,9 @@ function applyAdjudication(audit, adj, opts = {}) {
48634
48634
  const grounding = groundItems(groundInputs, { cwd: opts.cwd });
48635
48635
  for (const gi of grounding.issues) issues.push(gi);
48636
48636
  if (issues.length) {
48637
- return { ok: false, audit, issues, applied: 0, stillManual: 0, grounding };
48637
+ return { ok: false, audit: audit2, issues, applied: 0, stillManual: 0, grounding };
48638
48638
  }
48639
- const next = structuredClone(audit);
48639
+ const next = structuredClone(audit2);
48640
48640
  const critById = new Map(next.criteria.map((c2) => [c2.id, c2]));
48641
48641
  const newFindings = [];
48642
48642
  let applied = 0;
@@ -49543,8 +49543,8 @@ function resolveHostAnchor(file, snippet2) {
49543
49543
  }
49544
49544
  return null;
49545
49545
  }
49546
- function mergeDynamic(audit, dynamic, lang = "en") {
49547
- const merged = JSON.parse(JSON.stringify(audit));
49546
+ function mergeDynamic(audit2, dynamic, lang = "en") {
49547
+ const merged = JSON.parse(JSON.stringify(audit2));
49548
49548
  if (dynamic.sample) merged.scope.sample = dynamic.sample;
49549
49549
  if (dynamic.testedScs?.length) {
49550
49550
  const tested = /* @__PURE__ */ new Set([...merged.scope.scan?.testedScs ?? [], ...dynamic.testedScs]);
@@ -50372,9 +50372,9 @@ function unifiedDiff(file, before, after, ctx = 2) {
50372
50372
  if (before === after) return "";
50373
50373
  const a = before.split("\n");
50374
50374
  const b = after.split("\n");
50375
- const header2 = `--- a/${file}
50375
+ const header3 = `--- a/${file}
50376
50376
  +++ b/${file}`;
50377
- if (a.length > 4e3 || b.length > 4e3) return `${header2}
50377
+ if (a.length > 4e3 || b.length > 4e3) return `${header3}
50378
50378
  @@ diff omitted (large file): ${a.length} \u2192 ${b.length} lines @@`;
50379
50379
  const n = a.length;
50380
50380
  const m = b.length;
@@ -50408,7 +50408,7 @@ function unifiedDiff(file, before, after, ctx = 2) {
50408
50408
  for (let d = -ctx; d <= ctx; d++) if (k2 + d >= 0 && k2 + d < ops.length) show[k2 + d] = true;
50409
50409
  }
50410
50410
  }
50411
- const out2 = [header2];
50411
+ const out2 = [header3];
50412
50412
  let k = 0;
50413
50413
  while (k < ops.length) {
50414
50414
  if (!show[k]) {
@@ -51318,10 +51318,10 @@ function startDevServer(opts) {
51318
51318
  }
51319
51319
  res.writeHead(404, { "content-type": "text/plain" }).end("not found");
51320
51320
  });
51321
- return new Promise((resolve8, reject) => {
51321
+ return new Promise((resolve10, reject) => {
51322
51322
  server.once("error", reject);
51323
51323
  server.listen(opts.port, "127.0.0.1", () => {
51324
- resolve8({
51324
+ resolve10({
51325
51325
  port: server.address().port,
51326
51326
  close: () => new Promise((r) => server.close(() => r()))
51327
51327
  });
@@ -52193,6 +52193,1251 @@ function orchestrateRun(runDir, engineAbs, opts = {}) {
52193
52193
  return { exitCode: 0, written, notices, errors: [], phases };
52194
52194
  }
52195
52195
 
52196
+ // src/mcp/stdio.ts
52197
+ import { createInterface as createInterface2 } from "readline";
52198
+
52199
+ // src/mcp/handlers.ts
52200
+ import { existsSync as existsSync20, readFileSync as readFileSync17, realpathSync as realpathSync2, statSync as statSync10 } from "fs";
52201
+ import { isAbsolute as isAbsolute3, join as join36, resolve as resolve8, sep as sep4 } from "path";
52202
+
52203
+ // src/project-lock.ts
52204
+ var chains = /* @__PURE__ */ new Map();
52205
+ function withProjectLock(dir, fn) {
52206
+ const prev = chains.get(dir) ?? Promise.resolve();
52207
+ const next = prev.then(fn, fn);
52208
+ const tail = next.then(noop, noop);
52209
+ chains.set(dir, tail);
52210
+ tail.then(() => {
52211
+ if (chains.get(dir) === tail) chains.delete(dir);
52212
+ }, noop);
52213
+ return next;
52214
+ }
52215
+ function noop() {
52216
+ }
52217
+
52218
+ // src/mcp/handlers.ts
52219
+ var ToolError = class extends Error {
52220
+ };
52221
+ var MAX_READ_LINES = 2e3;
52222
+ var MAX_READ_BYTES = 8 * 1024 * 1024;
52223
+ var DEFAULT_GLOBS = ["**/*.html", "**/*.htm", "**/*.jsx", "**/*.tsx", "**/*.vue", "**/*.svelte"];
52224
+ var WRITE_TOOL_NAMES = /* @__PURE__ */ new Set(["ultra11y_fix", "ultra11y_scan", "ultra11y_init"]);
52225
+ function str2(v) {
52226
+ return typeof v === "string" && v.trim() !== "" ? v : void 0;
52227
+ }
52228
+ function num2(v) {
52229
+ const n = typeof v === "number" ? v : typeof v === "string" && v.trim() !== "" ? Number(v) : NaN;
52230
+ return Number.isFinite(n) ? n : void 0;
52231
+ }
52232
+ function bool(v) {
52233
+ return v === true || v === "true";
52234
+ }
52235
+ function strArray2(v) {
52236
+ const a = Array.isArray(v) && v.every((x) => typeof x === "string") ? v : void 0;
52237
+ return a && a.length ? a : void 0;
52238
+ }
52239
+ function positive(v, key) {
52240
+ const n = num2(v);
52241
+ if (n === void 0) return void 0;
52242
+ if (n <= 0) throw new ToolError(`\`${key}\` must be greater than 0.`);
52243
+ return n;
52244
+ }
52245
+ function requiredCwd(args2, defaults) {
52246
+ const cwd = str2(args2.cwd) ?? defaults.defaultCwd;
52247
+ if (!cwd) throw new ToolError("`cwd` is required: an absolute path to the project root.");
52248
+ const abs = resolve8(cwd);
52249
+ if (!existsSync20(abs)) throw new ToolError(`project root not found: ${abs}`);
52250
+ if (!statSync10(abs).isDirectory()) throw new ToolError(`\`cwd\` is not a directory: ${abs}`);
52251
+ return abs;
52252
+ }
52253
+ function standardOf(args2) {
52254
+ const s = str2(args2.standard) ?? "wcag";
52255
+ if (s !== "wcag" && s !== "rgaa") throw new ToolError(`\`standard\` must be one of: wcag, rgaa (got "${s}")`);
52256
+ return s;
52257
+ }
52258
+ function langOf(args2) {
52259
+ const l = str2(args2.lang) ?? "en";
52260
+ if (l !== "en" && l !== "fr") throw new ToolError(`\`lang\` must be one of: en, fr (got "${l}")`);
52261
+ return l;
52262
+ }
52263
+ async function callTool2(name2, args2, defaults = {}) {
52264
+ if (WRITE_TOOL_NAMES.has(name2) && !defaults.allowWrite) {
52265
+ throw new ToolError(`${name2} changes files in your project and is disabled \u2014 start the server with --allow-write to enable it.`);
52266
+ }
52267
+ if (name2 === "ultra11y_criteria") return outcome(handleCriteria(args2));
52268
+ const cwd = requiredCwd(args2, defaults);
52269
+ return await withProjectLock(cwd, async () => outcome(await dispatch(name2, args2, cwd)));
52270
+ }
52271
+ async function dispatch(name2, args2, cwd) {
52272
+ switch (name2) {
52273
+ case "ultra11y_audit":
52274
+ return handleAudit(args2, cwd);
52275
+ case "ultra11y_report":
52276
+ return handleReport(args2, cwd);
52277
+ case "ultra11y_prd":
52278
+ return handlePrd(args2, cwd);
52279
+ case "ultra11y_check":
52280
+ return handleCheck(args2, cwd);
52281
+ case "ultra11y_verify":
52282
+ return handleVerify(args2, cwd);
52283
+ case "ultra11y_adjudicate":
52284
+ return handleAdjudicate(args2, cwd);
52285
+ case "ultra11y_pack_check":
52286
+ return handlePackCheck(args2, cwd);
52287
+ case "ultra11y_sample_check":
52288
+ return handleSampleCheck(cwd);
52289
+ case "ultra11y_read":
52290
+ return handleRead(args2, cwd);
52291
+ case "ultra11y_fix":
52292
+ return handleFix(args2, cwd);
52293
+ case "ultra11y_init":
52294
+ return handleInit(args2, cwd);
52295
+ case "ultra11y_scan":
52296
+ return handleScan(args2, cwd);
52297
+ default:
52298
+ throw new ToolError(`unknown tool: ${name2}`);
52299
+ }
52300
+ }
52301
+ function outcome(result) {
52302
+ return { text: JSON.stringify(result, null, 2) + "\n" };
52303
+ }
52304
+ function audit(args2, cwd) {
52305
+ const prev = process.cwd();
52306
+ const warnings = [];
52307
+ try {
52308
+ process.chdir(cwd);
52309
+ return runAudit({
52310
+ inputs: strArray2(args2.globs) ?? DEFAULT_GLOBS,
52311
+ include: strArray2(args2.include),
52312
+ exclude: strArray2(args2.exclude),
52313
+ forceJsx: bool(args2.jsx),
52314
+ changed: bool(args2.changed),
52315
+ since: str2(args2.since),
52316
+ staged: bool(args2.staged),
52317
+ graph: bool(args2.graph),
52318
+ maxFiles: positive(args2.max_files, "max_files"),
52319
+ onWarn: (m) => warnings.push(m)
52320
+ });
52321
+ } finally {
52322
+ process.chdir(prev);
52323
+ }
52324
+ }
52325
+ function handleAudit(args2, cwd) {
52326
+ const r = audit(args2, cwd);
52327
+ return {
52328
+ cwd,
52329
+ ...r,
52330
+ next: "Static analysis decides only a few criteria. Run ultra11y_adjudicate for the judgment ones, and ultra11y_scan for the rendering ones (contrast, focus, zoom) \u2014 a criterion nobody tested is untested, not conformant."
52331
+ };
52332
+ }
52333
+ function handleReport(args2, cwd) {
52334
+ const standard = standardOf(args2);
52335
+ const lang = langOf(args2);
52336
+ const r = audit(args2, cwd);
52337
+ const md = standard === "wcag" ? renderReport(r, lang) : renderPackReport(r, loadPack(standard), lang);
52338
+ return { cwd, standard, lang, report: md, next: "Validate it with ultra11y_check before presenting it." };
52339
+ }
52340
+ function handlePrd(args2, cwd) {
52341
+ const standard = standardOf(args2);
52342
+ const lang = langOf(args2);
52343
+ const r = audit(args2, cwd);
52344
+ const units = prdUnits(r, standard, lang);
52345
+ const { nc, advisory } = partitionUnits(units);
52346
+ return {
52347
+ cwd,
52348
+ standard,
52349
+ non_conformities: nc,
52350
+ advisory,
52351
+ ...str2(args2.split) === "criterion" ? { grouped_by: "criterion" } : {}
52352
+ };
52353
+ }
52354
+ function handleCheck(args2, cwd) {
52355
+ const text = reportText(args2, "check");
52356
+ const res = checkReport(text, standardOf(args2), langOf(args2));
52357
+ return { cwd, source: str2(args2.report_text) ? "inline" : "file", ...res };
52358
+ }
52359
+ function handleVerify(args2, cwd) {
52360
+ const text = reportText(args2, "verify");
52361
+ const standard = standardOf(args2);
52362
+ const max = positive(args2.max_verify, "max_verify");
52363
+ const items = max === void 0 ? buildWorklist(text, standard) : buildWorklist(text, standard, max);
52364
+ return {
52365
+ cwd,
52366
+ standard,
52367
+ count: items.length,
52368
+ items,
52369
+ worklist: formatWorklist(items, true, standard, langOf(args2)),
52370
+ next: "For each item, read the real markup with ultra11y_read and judge whether the evidence carries the claim."
52371
+ };
52372
+ }
52373
+ function handleAdjudicate(args2, cwd) {
52374
+ const standard = standardOf(args2);
52375
+ const r = audit(args2, cwd);
52376
+ const items = buildAdjudicationWorklist(r, { cwd, standard });
52377
+ return {
52378
+ cwd,
52379
+ standard,
52380
+ count: items.length,
52381
+ items,
52382
+ worklist: formatAdjudication(items, langOf(args2)),
52383
+ next: "These are the criteria the engine cannot decide. Read each element's real context with ultra11y_read, then rule on it."
52384
+ };
52385
+ }
52386
+ function handlePackCheck(args2, cwd) {
52387
+ const pack = str2(args2.pack);
52388
+ if (!pack) throw new ToolError("`pack` is required \u2014 an absolute path to the pack JSON.");
52389
+ if (!isAbsolute3(pack)) throw new ToolError("`pack` must be an absolute path.");
52390
+ const res = runPackCheck(pack, str2(args2.guidance));
52391
+ return { cwd, ...res };
52392
+ }
52393
+ function handleSampleCheck(cwd) {
52394
+ const file = join36(cwd, ".ultra11yrc.json");
52395
+ if (!existsSync20(file)) {
52396
+ throw new ToolError(`no .ultra11yrc.json at ${cwd} \u2014 a page sample must be declared before it can be linted.`);
52397
+ }
52398
+ let raw;
52399
+ try {
52400
+ raw = JSON.parse(readFileSync17(file, "utf8"));
52401
+ } catch (e) {
52402
+ throw new ToolError(`.ultra11yrc.json is not valid JSON: ${e.message}`);
52403
+ }
52404
+ return { cwd, config: raw, next: "Check the sample covers every page kind the methodology requires, not just the convenient ones." };
52405
+ }
52406
+ function handleFix(args2, cwd) {
52407
+ const prev = process.cwd();
52408
+ try {
52409
+ process.chdir(cwd);
52410
+ const write = bool(args2.write);
52411
+ const res = runFix({
52412
+ inputs: strArray2(args2.globs) ?? DEFAULT_GLOBS,
52413
+ only: strArray2(args2.only),
52414
+ changed: bool(args2.changed),
52415
+ write,
52416
+ safe: true
52417
+ });
52418
+ return {
52419
+ cwd,
52420
+ applied: write,
52421
+ summary: fixSummary(res, langOf(args2), write),
52422
+ ...res,
52423
+ ...write ? {} : { next: "This was a dry run. Pass write:true to apply it." }
52424
+ };
52425
+ } finally {
52426
+ process.chdir(prev);
52427
+ }
52428
+ }
52429
+ function handleInit(args2, cwd) {
52430
+ const failOnRaw = str2(args2.fail_on) ?? "error";
52431
+ if (!["error", "warning", "notice"].includes(failOnRaw)) {
52432
+ throw new ToolError(`\`fail_on\` must be one of: error, warning, notice (got "${failOnRaw}")`);
52433
+ }
52434
+ const failOn = failOnRaw;
52435
+ const enginePath = resolve8("scripts/ultra11y.mjs");
52436
+ const written = [];
52437
+ if (bool(args2.hook)) written.push(writeHook(cwd, enginePath, failOn));
52438
+ if (bool(args2.ci)) written.push(join36(cwd, ".github/workflows/a11y.yml"));
52439
+ if (!written.length) {
52440
+ throw new ToolError("nothing to install \u2014 pass hook:true and/or ci:true.");
52441
+ }
52442
+ return { cwd, written, ...bool(args2.ci) ? { ci_workflow: ciWorkflow(enginePath, failOn) } : {} };
52443
+ }
52444
+ function handleScan(args2, cwd) {
52445
+ const target = str2(args2.target);
52446
+ if (!target) throw new ToolError("`target` is required \u2014 a URL or a local HTML file to render.");
52447
+ throw new ToolError(
52448
+ `ultra11y_scan is not available over MCP: it launches a headless browser (and may pull a Docker image), which this server does not manage. Run it from the CLI instead \u2014 \`ultra11y scan ${target} --merge\` in ${cwd} \u2014 then re-run ultra11y_audit to see the merged result.`
52449
+ );
52450
+ }
52451
+ function reportText(args2, tool) {
52452
+ const inline = str2(args2.report_text);
52453
+ const file = str2(args2.report_file);
52454
+ if (inline && file) throw new ToolError("pass `report_text` or `report_file`, not both.");
52455
+ if (inline) return inline;
52456
+ if (!file) throw new ToolError(`\`report_text\` is required \u2014 the report markdown for ultra11y_${tool} to work on.`);
52457
+ if (!isAbsolute3(file)) throw new ToolError("`report_file` must be an absolute path.");
52458
+ if (!existsSync20(file)) throw new ToolError(`report file not found: ${file}`);
52459
+ return readFileSync17(file, "utf8");
52460
+ }
52461
+ function handleCriteria(args2) {
52462
+ const lang = langOf(args2);
52463
+ const id = str2(args2.sc);
52464
+ if (!id) {
52465
+ return { standard: standardOf(args2), criteria: allSC().map((c2) => ({ sc: c2.sc, title: c2.title })) };
52466
+ }
52467
+ const sc = getSC(id);
52468
+ if (!sc) throw new ToolError(`no such success criterion: ${id}. List them all by omitting \`sc\`.`);
52469
+ return { standard: standardOf(args2), sc: id, text: formatSC(sc, lang) };
52470
+ }
52471
+ function handleRead(args2, cwd) {
52472
+ const raw = str2(args2.path);
52473
+ if (!raw) throw new ToolError("`path` is required \u2014 relative to the project root, or an absolute path inside it.");
52474
+ const target = isAbsolute3(raw) ? raw : join36(cwd, raw);
52475
+ let real;
52476
+ try {
52477
+ real = realpathSync2(target);
52478
+ } catch {
52479
+ throw new ToolError(`no such file: ${raw}`);
52480
+ }
52481
+ const root = realpathSync2(cwd);
52482
+ if (real !== root && !real.startsWith(root + sep4)) {
52483
+ throw new ToolError(`path is outside the project: ${raw}. Use your own file tool for anything else.`);
52484
+ }
52485
+ const st = statSync10(real);
52486
+ if (!st.isFile()) throw new ToolError(`not a file: ${raw}`);
52487
+ if (st.size > MAX_READ_BYTES) throw new ToolError(`file is too large to read (${st.size} bytes): ${raw}`);
52488
+ const lines = readFileSync17(real, "utf8").split("\n");
52489
+ const total = lines.length;
52490
+ const start2 = Math.max(1, Math.floor(num2(args2.start_line) ?? 1));
52491
+ if (start2 > total) throw new ToolError(`start_line ${start2} is past the end of the file (${total} lines).`);
52492
+ const requestedEnd = Math.floor(num2(args2.end_line) ?? total);
52493
+ const end = Math.min(total, Math.max(start2, requestedEnd), start2 + MAX_READ_LINES - 1);
52494
+ return {
52495
+ path: isAbsolute3(raw) ? real : raw,
52496
+ start_line: start2,
52497
+ end_line: end,
52498
+ total_lines: total,
52499
+ truncated: end < Math.min(total, requestedEnd),
52500
+ content: lines.slice(start2 - 1, end).join("\n")
52501
+ };
52502
+ }
52503
+
52504
+ // src/mcp/protocol.ts
52505
+ var PROTOCOL_VERSIONS2 = ["2024-11-05", "2025-03-26", "2025-06-18", "2025-11-25"];
52506
+ var LATEST_PROTOCOL2 = PROTOCOL_VERSIONS2[PROTOCOL_VERSIONS2.length - 1];
52507
+ var ASSUMED_HTTP_PROTOCOL = "2025-03-26";
52508
+ var ANNOTATIONS_SINCE2 = "2025-03-26";
52509
+ var RICH_TOOLS_SINCE2 = "2025-06-18";
52510
+ var DEFAULT_MAX_RESPONSE_BYTES2 = 1e6;
52511
+ function isProtocolVersion(v) {
52512
+ return typeof v === "string" && PROTOCOL_VERSIONS2.includes(v);
52513
+ }
52514
+ function negotiateProtocol2(requested) {
52515
+ return isProtocolVersion(requested) ? requested : LATEST_PROTOCOL2;
52516
+ }
52517
+ function validateArgs2(schema, args2) {
52518
+ for (const key of schema.required) {
52519
+ const v = args2[key];
52520
+ if (v === void 0 || v === null || v === "") return `\`${key}\` is required`;
52521
+ }
52522
+ for (const [key, value] of Object.entries(args2)) {
52523
+ if (value === void 0 || value === null) continue;
52524
+ const spec = schema.properties[key];
52525
+ if (!spec?.type) continue;
52526
+ const actual = Array.isArray(value) ? "array" : typeof value;
52527
+ if (spec.type === "number") {
52528
+ if (actual === "number") continue;
52529
+ if (actual === "string" && value.trim() !== "" && Number.isFinite(Number(value))) continue;
52530
+ return `\`${key}\` must be a number, got ${actual === "string" ? JSON.stringify(value) : actual}`;
52531
+ }
52532
+ if (spec.type === "array") {
52533
+ if (actual !== "array") return `\`${key}\` must be an array, got ${actual}`;
52534
+ const arr = value;
52535
+ if (spec.items?.type === "string" && !arr.every((x) => typeof x === "string")) {
52536
+ return `\`${key}\` must be an array of strings`;
52537
+ }
52538
+ if (spec.enum) {
52539
+ const bad = arr.find((x) => typeof x === "string" && !spec.enum.includes(x));
52540
+ if (bad !== void 0) return `\`${key}\` contains "${String(bad)}" \u2014 allowed: ${spec.enum.join(", ")}`;
52541
+ }
52542
+ continue;
52543
+ }
52544
+ if (actual !== spec.type) return `\`${key}\` must be a ${spec.type}, got ${actual}`;
52545
+ if (spec.enum && typeof value === "string" && !spec.enum.includes(value)) {
52546
+ return `\`${key}\` must be one of: ${spec.enum.join(", ")}`;
52547
+ }
52548
+ }
52549
+ return void 0;
52550
+ }
52551
+ var NARROWER2 = {
52552
+ ultra11y_audit: "narrow the globs, or pass `max_files`; `changed: true` audits only what the diff touched",
52553
+ ultra11y_report: "report one standard at a time, or read the file at the returned path",
52554
+ ultra11y_prd: 'split per criterion with `split: "criterion"`',
52555
+ ultra11y_criteria: "ask for one success criterion instead of the whole reference",
52556
+ ultra11y_verify: "lower `max_verify`",
52557
+ ultra11y_scan: "scan one page instead of a crawl",
52558
+ ultra11y_read: "pass `start_line`/`end_line` to read a window instead of the whole file"
52559
+ };
52560
+ function capResponse2(text, tool, maxBytes, artifact) {
52561
+ const bytes = Buffer.byteLength(text, "utf8");
52562
+ if (bytes <= maxBytes) return text;
52563
+ return JSON.stringify(
52564
+ {
52565
+ truncated: true,
52566
+ tool,
52567
+ bytes,
52568
+ maxBytes,
52569
+ reason: "This response exceeds the configured limit and was withheld rather than sent as an unusable partial payload.",
52570
+ narrower: NARROWER2[tool] ?? "narrow the request and call again",
52571
+ ...artifact ? { artifact, artifactNote: "The full result is on disk here \u2014 read it directly if you need all of it." } : {}
52572
+ },
52573
+ null,
52574
+ 2
52575
+ ) + "\n";
52576
+ }
52577
+ function structuredContentFor2(text, capped, hasSchema) {
52578
+ if (capped || !hasSchema) return void 0;
52579
+ let parsed;
52580
+ try {
52581
+ parsed = JSON.parse(text);
52582
+ } catch {
52583
+ return void 0;
52584
+ }
52585
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return void 0;
52586
+ return parsed;
52587
+ }
52588
+ var LOOPBACK_ORIGIN = /^https?:\/\/(localhost|127\.0\.0\.1|\[::1\])(:\d+)?$/i;
52589
+ function isOriginAllowed(origin, allowed = []) {
52590
+ if (origin === void 0) return true;
52591
+ const o = origin.trim();
52592
+ if (o === "" || o === "null") return true;
52593
+ if (LOOPBACK_ORIGIN.test(o)) return true;
52594
+ return allowed.some((a) => a === "*" || a.toLowerCase() === o.toLowerCase());
52595
+ }
52596
+
52597
+ // src/mcp/tools.ts
52598
+ var cwdProp = { type: "string", description: "Absolute path to the project root the paths are relative to." };
52599
+ var globsProp = {
52600
+ type: "array",
52601
+ items: { type: "string" },
52602
+ description: "Files or globs to audit (e.g. ['src/**/*.tsx']). Relative to the project root."
52603
+ };
52604
+ var standardProp = {
52605
+ type: "string",
52606
+ enum: ["wcag", "rgaa"],
52607
+ description: "Which standard to report against. Default: wcag (WCAG 2.2 AA)."
52608
+ };
52609
+ var langProp = { type: "string", enum: ["en", "fr"], description: "Language for the rendered prose. Default: en." };
52610
+ var COVERAGE_NOTE = "Static analysis alone decides only a few of the 55 WCAG 2.2 AA criteria \u2014 most need YOUR judgment (is this alt text actually relevant? is this link's purpose clear in context?) and some need a real browser (ultra11y_scan). A silent criterion is untested, never conformant.";
52611
+ var TOOLS2 = [
52612
+ {
52613
+ name: "ultra11y_audit",
52614
+ title: "Audit markup against WCAG 2.2 AA",
52615
+ description: "Run the static engine over HTML/CSS/JSX and return the findings keyed by WCAG success criterion. Fast and offline \u2014 no browser, no network, no keys. This is the entry point. " + COVERAGE_NOTE,
52616
+ inputSchema: {
52617
+ type: "object",
52618
+ properties: {
52619
+ cwd: cwdProp,
52620
+ globs: globsProp,
52621
+ changed: { type: "boolean", description: "Audit only the files git reports as changed." },
52622
+ since: { type: "string", description: "Audit only what changed since this git ref." },
52623
+ staged: { type: "boolean", description: "Audit exactly the staged index snapshot." },
52624
+ include: { type: "array", items: { type: "string" }, description: "Extra globs to include." },
52625
+ exclude: { type: "array", items: { type: "string" }, description: "Globs to skip." },
52626
+ jsx: { type: "boolean", description: "Force JSX parsing for files the extension does not imply." },
52627
+ graph: { type: "boolean", description: "Also run cross-file rules over the dependency graph \u2014 catches a label defined in another file." },
52628
+ max_files: { type: "number", description: "Hard cap on files audited (truncation is reported, never silent)." }
52629
+ },
52630
+ required: ["cwd"]
52631
+ }
52632
+ },
52633
+ {
52634
+ name: "ultra11y_report",
52635
+ title: "Render a dated conformance report",
52636
+ description: "Turn an audit into the auditor-facing conformance report: one section per success criterion, with the evidence and the verdict. Says explicitly which criteria were not tested, because a report silent about its own coverage reads as a clean bill of health.",
52637
+ inputSchema: {
52638
+ type: "object",
52639
+ properties: { cwd: cwdProp, globs: globsProp, standard: standardProp, lang: langProp },
52640
+ required: ["cwd"]
52641
+ }
52642
+ },
52643
+ {
52644
+ name: "ultra11y_prd",
52645
+ title: "Turn findings into a remediation backlog",
52646
+ description: "Convert an audit into actionable work: one unit per non-conformity, with the criterion it violates, the fix, and an effort estimate. Use it to hand accessibility debt to a team rather than a wall of violations.",
52647
+ inputSchema: {
52648
+ type: "object",
52649
+ properties: {
52650
+ cwd: cwdProp,
52651
+ globs: globsProp,
52652
+ standard: standardProp,
52653
+ lang: langProp,
52654
+ split: { type: "string", enum: ["criterion"], description: "Emit one unit per criterion instead of one document." }
52655
+ },
52656
+ required: ["cwd"]
52657
+ }
52658
+ },
52659
+ {
52660
+ name: "ultra11y_criteria",
52661
+ title: "The offline standards reference",
52662
+ description: "Look up what a success criterion actually requires \u2014 its exact wording, how it is tested, and what counts as a failure. Offline and authoritative; use it instead of recalling a criterion from memory, which is how invented non-conformities get written.",
52663
+ inputSchema: {
52664
+ type: "object",
52665
+ properties: {
52666
+ sc: { type: "string", description: "A success criterion number (e.g. '1.1.1'). Omit to list them all." },
52667
+ standard: standardProp,
52668
+ lang: langProp
52669
+ },
52670
+ required: []
52671
+ }
52672
+ },
52673
+ {
52674
+ name: "ultra11y_check",
52675
+ title: "The anti-hallucination gate",
52676
+ description: "Validate a report you wrote: every criterion it cites must exist, every non-conformity must trace to a real finding, and nothing may be asserted conformant that was never tested. A result with ok:false is a real verdict, not a tool failure \u2014 it means the report claims more than the evidence carries.",
52677
+ inputSchema: {
52678
+ type: "object",
52679
+ properties: {
52680
+ cwd: cwdProp,
52681
+ report_text: { type: "string", description: "The report markdown to validate. Preferred over report_file." },
52682
+ report_file: { type: "string", description: "Absolute path to a report file to validate instead." },
52683
+ standard: standardProp,
52684
+ lang: langProp
52685
+ },
52686
+ required: ["cwd"]
52687
+ }
52688
+ },
52689
+ {
52690
+ name: "ultra11y_verify",
52691
+ title: "Build a claim-evidence worklist",
52692
+ description: "Emit a claim-by-evidence worklist from a report, for you to adjudicate each pair. This is where the 38 judgment criteria get decided: read the real markup and rule on whether the alt text is relevant, the link purpose is clear, the reading order makes sense.",
52693
+ inputSchema: {
52694
+ type: "object",
52695
+ properties: {
52696
+ cwd: cwdProp,
52697
+ report_text: { type: "string", description: "The report markdown to build the worklist from." },
52698
+ report_file: { type: "string", description: "Absolute path to a report file to use instead." },
52699
+ standard: standardProp,
52700
+ max_verify: { type: "number", description: "Cap on the number of claim/evidence pairs emitted." },
52701
+ lang: langProp
52702
+ },
52703
+ required: ["cwd"]
52704
+ }
52705
+ },
52706
+ {
52707
+ name: "ultra11y_adjudicate",
52708
+ title: "Worklist for the judgment criteria",
52709
+ description: "Emit the criteria the engine CANNOT decide, with the evidence needed to decide them: alt-text relevance, link purpose in context, heading structure, reading order, label-in-name. Most of WCAG lives here \u2014 this is the tool that makes an audit real rather than a lint run.",
52710
+ inputSchema: {
52711
+ type: "object",
52712
+ properties: { cwd: cwdProp, globs: globsProp, standard: standardProp, lang: langProp },
52713
+ required: ["cwd"]
52714
+ }
52715
+ },
52716
+ {
52717
+ name: "ultra11y_pack_check",
52718
+ title: "Validate a country standards pack",
52719
+ description: "Check that a country standards pack (RGAA and friends) matches what the published standard actually ships, so a localized audit cannot be graded against invented criteria. Reads the pack; changes nothing.",
52720
+ inputSchema: {
52721
+ type: "object",
52722
+ properties: {
52723
+ cwd: cwdProp,
52724
+ pack: { type: "string", description: "Absolute path to the pack JSON." },
52725
+ guidance: { type: "string", description: "Absolute path to its guidance file." }
52726
+ },
52727
+ required: ["cwd", "pack"]
52728
+ }
52729
+ },
52730
+ {
52731
+ name: "ultra11y_sample_check",
52732
+ title: "Lint the normative page sample",
52733
+ description: "Validate a project's `.ultra11yrc.json` page sample against the methodology: a conformance claim covering only the pages that happened to be easy is not a conformance claim. Reads the config; changes nothing.",
52734
+ inputSchema: { type: "object", properties: { cwd: cwdProp }, required: ["cwd"] }
52735
+ },
52736
+ {
52737
+ name: "ultra11y_read",
52738
+ title: "Read a file from the project",
52739
+ description: "Read a file, or a line range of one, from the audited project. Use it to see the real markup behind a finding before judging it. Reads are confined to the project root; anything else is your own file tool's job.",
52740
+ inputSchema: {
52741
+ type: "object",
52742
+ properties: {
52743
+ cwd: cwdProp,
52744
+ path: { type: "string", description: "Path relative to the project root, or an absolute path inside it." },
52745
+ start_line: { type: "number", description: "First line to return, 1-based (default 1)." },
52746
+ end_line: { type: "number", description: "Last line to return, inclusive (default: end of file, capped)." }
52747
+ },
52748
+ required: ["cwd", "path"]
52749
+ },
52750
+ outputSchema: {
52751
+ type: "object",
52752
+ properties: {
52753
+ path: { type: "string" },
52754
+ start_line: { type: "number" },
52755
+ end_line: { type: "number" },
52756
+ total_lines: { type: "number" },
52757
+ truncated: { type: "boolean" },
52758
+ content: { type: "string" }
52759
+ },
52760
+ required: ["path", "start_line", "end_line", "total_lines", "truncated", "content"]
52761
+ }
52762
+ }
52763
+ ];
52764
+ var WRITE_TOOLS = [
52765
+ {
52766
+ name: "ultra11y_fix",
52767
+ title: "Apply safe accessibility codemods",
52768
+ description: "WRITES TO THE PROJECT: apply the mechanical fixes \u2014 the ones with exactly one correct answer, like a missing lang attribute or a button with no accessible name. Dry-run by default; with write:true it re-audits afterwards and refuses to keep a change that introduced a new non-conformity. It never invents alt text: that is a judgment call and it leaves a TODO for you.",
52769
+ inputSchema: {
52770
+ type: "object",
52771
+ properties: {
52772
+ cwd: cwdProp,
52773
+ globs: globsProp,
52774
+ write: { type: "boolean", description: "Actually apply the changes (default: dry-run, returns the diff)." },
52775
+ only: { type: "array", items: { type: "string" }, description: "Apply only these rule ids." },
52776
+ changed: { type: "boolean", description: "Fix only the files git reports as changed." }
52777
+ },
52778
+ required: ["cwd"]
52779
+ }
52780
+ },
52781
+ {
52782
+ name: "ultra11y_scan",
52783
+ title: "Audit the rendered page in a real browser",
52784
+ description: "SLOW: launches a headless browser (optionally via Docker) and runs axe-core against the RENDERED DOM, then merges the result into the static audit. This is the only way to decide the 14 rendering criteria \u2014 contrast, focus visibility, zoom, reflow, hover, target size \u2014 which no static analysis can reach. Expect tens of seconds per page, more on a first run that pulls an image.",
52785
+ inputSchema: {
52786
+ type: "object",
52787
+ properties: {
52788
+ cwd: cwdProp,
52789
+ target: { type: "string", description: "A URL or a local HTML file to render." },
52790
+ runtime: { type: "string", enum: ["auto", "local", "docker"], description: "Where to get the browser. Default: auto." },
52791
+ merge: { type: "boolean", description: "Merge the rendered findings into the existing static audit." }
52792
+ },
52793
+ required: ["cwd", "target"]
52794
+ }
52795
+ },
52796
+ {
52797
+ name: "ultra11y_init",
52798
+ title: "Install the accessibility gate",
52799
+ description: "WRITES TO THE PROJECT: install a git pre-commit hook, a CI job, and/or a baseline so accessibility regressions fail the build instead of accumulating. Modifies files in the repository \u2014 review the result before committing it.",
52800
+ inputSchema: {
52801
+ type: "object",
52802
+ properties: {
52803
+ cwd: cwdProp,
52804
+ hook: { type: "boolean", description: "Install the git pre-commit hook." },
52805
+ ci: { type: "boolean", description: "Install the CI workflow." },
52806
+ baseline: { type: "boolean", description: "Record the current findings as the accepted baseline." },
52807
+ fail_on: { type: "string", enum: ["error", "warning", "notice"], description: "Severity that fails the gate." }
52808
+ },
52809
+ required: ["cwd"]
52810
+ }
52811
+ }
52812
+ ];
52813
+ var TOOL_META2 = {
52814
+ ultra11y_audit: { openWorld: false },
52815
+ ultra11y_report: { openWorld: false },
52816
+ ultra11y_prd: { openWorld: false },
52817
+ ultra11y_criteria: { openWorld: false },
52818
+ ultra11y_check: { openWorld: false },
52819
+ ultra11y_verify: { openWorld: false },
52820
+ ultra11y_adjudicate: { openWorld: false },
52821
+ ultra11y_pack_check: { openWorld: false },
52822
+ ultra11y_sample_check: { openWorld: false },
52823
+ ultra11y_read: { openWorld: false },
52824
+ // Rewrites source files. Not destructive in the delete sense, and it verifies
52825
+ // its own work — but it is the user's code.
52826
+ ultra11y_fix: { write: true, destructive: false, idempotent: true, openWorld: false },
52827
+ // Fetches a URL and may pull a Docker image.
52828
+ ultra11y_scan: { write: true, destructive: false, idempotent: false, openWorld: true },
52829
+ ultra11y_init: { write: true, destructive: false, idempotent: true, openWorld: false }
52830
+ };
52831
+ function annotationsFor2(name2) {
52832
+ const meta2 = TOOL_META2[name2];
52833
+ if (!meta2) return void 0;
52834
+ return {
52835
+ readOnlyHint: !meta2.write,
52836
+ ...meta2.write ? { destructiveHint: meta2.destructive === true, idempotentHint: meta2.idempotent === true } : {},
52837
+ openWorldHint: meta2.openWorld === true
52838
+ };
52839
+ }
52840
+ function toolsFor2(protocolVersion, opts = {}) {
52841
+ const base = opts.allowWrite ? [...TOOLS2, ...WRITE_TOOLS] : TOOLS2;
52842
+ const withAnnotations = protocolVersion >= ANNOTATIONS_SINCE2;
52843
+ const withRich = protocolVersion >= RICH_TOOLS_SINCE2;
52844
+ return base.map((t2) => {
52845
+ const decl = {
52846
+ name: t2.name,
52847
+ description: t2.description,
52848
+ inputSchema: applyDefaultCwd(t2.inputSchema, opts.defaultCwd)
52849
+ };
52850
+ if (withRich && t2.title) decl.title = t2.title;
52851
+ if (withRich && t2.outputSchema) decl.outputSchema = t2.outputSchema;
52852
+ if (withAnnotations) {
52853
+ const a = annotationsFor2(t2.name);
52854
+ if (a) decl.annotations = a;
52855
+ }
52856
+ return decl;
52857
+ });
52858
+ }
52859
+ function applyDefaultCwd(schema, defaultCwd) {
52860
+ const existing = schema.properties.cwd;
52861
+ if (!defaultCwd || !existing) return schema;
52862
+ return {
52863
+ type: "object",
52864
+ properties: {
52865
+ ...schema.properties,
52866
+ cwd: { ...existing, description: `${existing.description} Optional \u2014 defaults to ${defaultCwd}.` }
52867
+ },
52868
+ required: schema.required.filter((r) => r !== "cwd")
52869
+ };
52870
+ }
52871
+
52872
+ // src/mcp/prompts.ts
52873
+ var PromptError = class extends Error {
52874
+ };
52875
+ var cwdArg = { name: "cwd", description: "Absolute path to the project root.", required: true };
52876
+ var PROMPTS = [
52877
+ {
52878
+ name: "audit_wcag",
52879
+ title: "Audit a project against WCAG 2.2 AA",
52880
+ description: "The full conformance workflow: run the static engine, adjudicate the criteria it cannot decide, name the ones only a browser can settle, and produce a dated report whose coverage is stated rather than implied.",
52881
+ arguments: [
52882
+ cwdArg,
52883
+ { name: "globs", description: "What to audit, comma-separated (default: the project's markup).", required: false },
52884
+ { name: "standard", description: "wcag (default) or rgaa.", required: false }
52885
+ ]
52886
+ },
52887
+ {
52888
+ name: "adjudicate_criteria",
52889
+ title: "Decide the criteria the engine cannot",
52890
+ description: "The judgment workflow: work the adjudication queue \u2014 alt-text relevance, link purpose in context, heading structure, reading order \u2014 by reading the real markup, not by pattern-matching the element.",
52891
+ arguments: [cwdArg, { name: "standard", description: "wcag (default) or rgaa.", required: false }]
52892
+ },
52893
+ {
52894
+ name: "review_diff_a11y",
52895
+ title: "Review a diff for accessibility",
52896
+ description: "The review workflow: audit exactly the code under change, refute the false positives, and report what this diff introduces \u2014 not the project's whole accessibility backlog.",
52897
+ arguments: [cwdArg, { name: "since", description: "The git ref to diff against (default: the staged changes).", required: false }]
52898
+ }
52899
+ ];
52900
+ function getPrompt(name2, args2 = {}) {
52901
+ const decl = PROMPTS.find((p) => p.name === name2);
52902
+ if (!decl) throw new PromptError(`unknown prompt: ${name2 || "(none given)"}`);
52903
+ for (const arg of decl.arguments) {
52904
+ if (arg.required && !str3(args2[arg.name])) throw new PromptError(`\`${arg.name}\` is required for prompt "${name2}"`);
52905
+ }
52906
+ const text = name2 === "audit_wcag" ? auditWcag(args2) : name2 === "adjudicate_criteria" ? adjudicateCriteria(args2) : reviewDiff(args2);
52907
+ return { description: decl.description, messages: [{ role: "user", content: { type: "text", text } }] };
52908
+ }
52909
+ var CORE_RULE = `A criterion nobody tested is UNTESTED, never conformant. The static engine decides only a handful of the 55 WCAG 2.2 AA criteria; fourteen need a real browser and thirty-eight need your judgment. Never write that a criterion passes because the tool did not flag it, and never invent a non-conformity the evidence does not show.`;
52910
+ var GATE = `\`ultra11y_check\` returning \`ok: false\` is a VERDICT, not a tool failure. It means the report claims more than the evidence carries \u2014 usually a criterion asserted conformant that was never actually tested. Fix the claim, not the gate.`;
52911
+ function auditWcag(args2) {
52912
+ const cwd = str3(args2.cwd);
52913
+ const globs = str3(args2.globs);
52914
+ const standard = str3(args2.standard);
52915
+ return `Audit \`${cwd}\` against ${standard === "rgaa" ? "RGAA" : "WCAG 2.2 AA"}${globs ? `, over \`${globs}\`` : ""}.
52916
+
52917
+ ${CORE_RULE}
52918
+
52919
+ **Sequence:**
52920
+
52921
+ 1. \`ultra11y_audit\`${globs ? ` with \`globs: ["${globs}"]\`` : ""} \u2014 the static pass. Add \`graph: true\` for a component codebase, where a label is routinely defined in a different file from the input it names.
52922
+ 2. \`ultra11y_adjudicate\` \u2014 the queue of criteria the engine cannot decide. This is most of WCAG, and skipping it is what turns an audit into a lint run.
52923
+ 3. For each item: \`ultra11y_read\` the real markup around it and rule on it. \`ultra11y_criteria\` when you need the criterion's exact wording \u2014 look it up rather than recalling it, which is how invented non-conformities get written.
52924
+ 4. \`ultra11y_report\`${standard ? ` with \`standard: "${standard}"\`` : ""} for the dated conformance report.
52925
+ 5. \`ultra11y_check\` on what you wrote.
52926
+
52927
+ **Say what you did not test.** The rendering criteria \u2014 contrast, focus visibility, zoom, reflow, hover, target size \u2014 cannot be decided from source. They need \`ultra11y scan\` in a real browser, from the CLI. A report that omits them silently reads as a clean bill of health for the very criteria most likely to fail.
52928
+
52929
+ **Then \`ultra11y_prd\`** to turn the non-conformities into work a team can pick up, with the fix and the effort per unit.
52930
+
52931
+ ${GATE}`;
52932
+ }
52933
+ function adjudicateCriteria(args2) {
52934
+ const cwd = str3(args2.cwd);
52935
+ const standard = str3(args2.standard);
52936
+ return `Work the adjudication queue for \`${cwd}\`${standard ? ` against ${standard}` : ""}.
52937
+
52938
+ ${CORE_RULE}
52939
+
52940
+ **Sequence:**
52941
+
52942
+ 1. \`ultra11y_adjudicate\` \u2014 the items, each naming the criterion and the element it turns on.
52943
+ 2. For each: \`ultra11y_read\` the file around that element. You are deciding what the markup MEANS in context, and context is exactly what an excerpt drops.
52944
+ 3. \`ultra11y_criteria\` for the criterion's exact test, whenever you are about to rule from memory.
52945
+ 4. Rule on each item, then \`ultra11y_check\` your write-up.
52946
+
52947
+ **What these criteria actually ask.**
52948
+
52949
+ - **Alt text** \u2014 does it convey what the image contributes HERE? A correct-looking alt on a decorative image is a failure; so is "chart" on a chart carrying the paragraph's only data.
52950
+ - **Link purpose in context** \u2014 could a user reading links out of context tell where this one goes? "Read more" fails unless its context is programmatically associated.
52951
+ - **Heading structure** \u2014 do the headings describe the real outline of the page, or the visual size someone wanted?
52952
+ - **Reading order** \u2014 does the DOM order match the meaning, independent of CSS placement?
52953
+ - **Label in name** \u2014 does the accessible name start with the visible label, so a speech user can say what they see?
52954
+
52955
+ **When the evidence does not settle it, say so.** "Needs a human with the design context" is a legitimate verdict and a useful one. A confident wrong ruling is neither.`;
52956
+ }
52957
+ function reviewDiff(args2) {
52958
+ const cwd = str3(args2.cwd);
52959
+ const since = str3(args2.since);
52960
+ return `Review the ${since ? `changes since \`${since}\`` : "staged changes"} in \`${cwd}\` for accessibility.
52961
+
52962
+ ${CORE_RULE}
52963
+
52964
+ **Sequence:**
52965
+
52966
+ 1. \`ultra11y_audit\` with ${since ? `\`since: "${since}"\`` : "`staged: true`"} and \`graph: true\` \u2014 scoped to exactly the code under change, but able to see across files.
52967
+ 2. \`ultra11y_read\` each flagged element in its real context before writing anything about it.
52968
+ 3. \`ultra11y_criteria\` for any criterion you are about to cite.
52969
+ 4. Report, ranked: blocking \u2192 major \u2192 minor.
52970
+
52971
+ **Review the diff, not the project.** A pre-existing non-conformity in a file this branch merely touched is not this branch's finding \u2014 mention it once as context, never as a blocker.
52972
+
52973
+ **Refute the false positives yourself.** Findings inside library source, generated output, or a single-file component the engine could not fully resolve are PRELIMINARY. Check each against the real markup and drop the ones that do not survive; a review that forwards the engine's output unfiltered wastes the author's time and teaches them to ignore the next one.
52974
+
52975
+ **Name the residual risks.** Contrast, focus visibility and zoom cannot be judged from source \u2014 say that they were not assessed rather than letting silence imply they passed.`;
52976
+ }
52977
+ function str3(v) {
52978
+ return typeof v === "string" && v.trim() !== "" ? v : void 0;
52979
+ }
52980
+ var DECLARED = new Set([...TOOLS2, ...WRITE_TOOLS].map((t2) => t2.name));
52981
+
52982
+ // src/mcp/resources.ts
52983
+ import { existsSync as existsSync21, readdirSync as readdirSync6, readFileSync as readFileSync18, realpathSync as realpathSync3, statSync as statSync11 } from "fs";
52984
+ import { basename as basename3, dirname as dirname10, join as join37, resolve as resolve9, sep as sep5 } from "path";
52985
+ import { fileURLToPath as fileURLToPath4 } from "url";
52986
+ var SKILL_NAME = "ultra11y";
52987
+ var URI_SCHEME = "skill://";
52988
+ function resolveSkillRoot(moduleDir) {
52989
+ const here = moduleDir ?? dirname10(fileURLToPath4(import.meta.url));
52990
+ const candidates2 = [resolve9(here, ".."), resolve9(here, "..", "skills", SKILL_NAME), resolve9(here, "..", "..", "skills", SKILL_NAME)];
52991
+ return candidates2.find((dir) => existsSync21(join37(dir, "SKILL.md")));
52992
+ }
52993
+ function listResources(moduleDir) {
52994
+ const root = resolveSkillRoot(moduleDir);
52995
+ if (!root) return [];
52996
+ const out2 = [describe(root, "SKILL.md", `${SKILL_NAME}: the skill`)];
52997
+ const refDir = join37(root, "references");
52998
+ if (!existsSync21(refDir)) return out2;
52999
+ for (const file of readdirSync6(refDir).sort()) {
53000
+ if (!file.endsWith(".md")) continue;
53001
+ out2.push(describe(root, join37("references", file), `${SKILL_NAME} reference: ${basename3(file, ".md")}`));
53002
+ }
53003
+ return out2;
53004
+ }
53005
+ function readResource(uri, moduleDir) {
53006
+ if (!uri.startsWith(URI_SCHEME)) {
53007
+ throw new ResourceError(`unknown resource scheme in "${uri}" (expected ${URI_SCHEME}\u2026)`);
53008
+ }
53009
+ const root = resolveSkillRoot(moduleDir);
53010
+ if (!root) throw new ResourceError("no skill payload found next to this build \u2014 nothing to read");
53011
+ const rel = uri.slice(URI_SCHEME.length);
53012
+ if (!rel) throw new ResourceError("empty resource path");
53013
+ const target = resolve9(root, rel);
53014
+ const rootReal = realpathSync3(root);
53015
+ let targetReal;
53016
+ try {
53017
+ targetReal = realpathSync3(target);
53018
+ } catch {
53019
+ throw new ResourceError(`no such resource: ${uri}`);
53020
+ }
53021
+ if (targetReal !== rootReal && !targetReal.startsWith(rootReal + sep5)) {
53022
+ throw new ResourceError(`resource path escapes the skill root: ${uri}`);
53023
+ }
53024
+ if (!statSync11(targetReal).isFile()) throw new ResourceError(`not a file: ${uri}`);
53025
+ return { uri, mimeType: "text/markdown", text: readFileSync18(targetReal, "utf8") };
53026
+ }
53027
+ var ResourceError = class extends Error {
53028
+ };
53029
+ function describe(root, rel, fallbackTitle) {
53030
+ const decl = {
53031
+ uri: `${URI_SCHEME}${rel.split(sep5).join("/")}`,
53032
+ name: rel.split(sep5).join("/"),
53033
+ title: fallbackTitle,
53034
+ mimeType: "text/markdown"
53035
+ };
53036
+ const summary = firstProse(join37(root, rel));
53037
+ if (summary) decl.description = summary;
53038
+ return decl;
53039
+ }
53040
+ function firstProse(file) {
53041
+ let text;
53042
+ try {
53043
+ text = readFileSync18(file, "utf8");
53044
+ } catch {
53045
+ return void 0;
53046
+ }
53047
+ const body2 = text.startsWith("---\n") ? text.slice(text.indexOf("\n---", 3) + 4) : text;
53048
+ for (const block of body2.split(/\n\s*\n/)) {
53049
+ const line = block.trim();
53050
+ if (!line || line.startsWith("#") || line.startsWith(">") || line.startsWith("|") || line.startsWith("```")) continue;
53051
+ const flat = line.replace(/\s+/g, " ").replace(/[*`]/g, "");
53052
+ return flat.length > 300 ? `${flat.slice(0, 297)}\u2026` : flat;
53053
+ }
53054
+ return void 0;
53055
+ }
53056
+
53057
+ // src/mcp/server.ts
53058
+ var ERR_INVALID_REQUEST = -32600;
53059
+ var ERR_METHOD_NOT_FOUND = -32601;
53060
+ var ERR_INVALID_PARAMS = -32602;
53061
+ var ERR_INTERNAL = -32603;
53062
+ function createServer2(opts = {}) {
53063
+ const serverInfo = { name: opts.serverName ?? "ultra11y", version: VERSION };
53064
+ const maxBytes = opts.maxResponseBytes ?? DEFAULT_MAX_RESPONSE_BYTES2;
53065
+ let protocol = LATEST_PROTOCOL2;
53066
+ const cancelled = /* @__PURE__ */ new Set();
53067
+ const CANCELLED_MAX = 1024;
53068
+ const listTools = () => toolsFor2(protocol, { defaultCwd: opts.defaultCwd, allowWrite: opts.allowWrite });
53069
+ async function handle2(msg, send) {
53070
+ if (msg === null || typeof msg !== "object" || Array.isArray(msg)) {
53071
+ send({ jsonrpc: "2.0", id: null, error: { code: ERR_INVALID_REQUEST, message: "invalid request: expected a JSON-RPC object" } });
53072
+ return;
53073
+ }
53074
+ if (msg.id === void 0 || msg.id === null) {
53075
+ if (msg.method === "notifications/cancelled") {
53076
+ const target = msg.params?.requestId;
53077
+ if (typeof target === "string" || typeof target === "number") {
53078
+ if (cancelled.size >= CANCELLED_MAX) cancelled.delete(cancelled.values().next().value);
53079
+ cancelled.add(String(target));
53080
+ }
53081
+ }
53082
+ return;
53083
+ }
53084
+ const id = msg.id;
53085
+ const reply = (out2) => {
53086
+ if (cancelled.delete(String(id))) return;
53087
+ send({ jsonrpc: "2.0", id, ...out2 });
53088
+ };
53089
+ try {
53090
+ switch (msg.method) {
53091
+ case "initialize": {
53092
+ protocol = negotiateProtocol2(msg.params?.protocolVersion);
53093
+ reply({
53094
+ result: {
53095
+ protocolVersion: protocol,
53096
+ // Three primitives, because a skill is three things: the engine
53097
+ // (tools), the method (prompts) and the documentation the method
53098
+ // refers to (resources). A client given only the first has to
53099
+ // invent the other two.
53100
+ capabilities: {
53101
+ tools: { listChanged: false },
53102
+ resources: { subscribe: false, listChanged: false },
53103
+ prompts: { listChanged: false }
53104
+ },
53105
+ serverInfo
53106
+ }
53107
+ });
53108
+ return;
53109
+ }
53110
+ case "ping":
53111
+ reply({ result: {} });
53112
+ return;
53113
+ case "tools/list":
53114
+ reply({ result: { tools: listTools() } });
53115
+ return;
53116
+ case "tools/call":
53117
+ await handleToolCall(msg, reply);
53118
+ return;
53119
+ case "resources/list":
53120
+ reply({ result: { resources: listResources(opts.skillDir) } });
53121
+ return;
53122
+ case "resources/read": {
53123
+ const uri = typeof msg.params?.uri === "string" ? msg.params.uri : "";
53124
+ if (!uri) {
53125
+ reply({ error: { code: ERR_INVALID_PARAMS, message: "`uri` is required" } });
53126
+ return;
53127
+ }
53128
+ try {
53129
+ reply({ result: { contents: [readResource(uri, opts.skillDir)] } });
53130
+ } catch (e) {
53131
+ if (e instanceof ResourceError) reply({ error: { code: ERR_INVALID_PARAMS, message: e.message } });
53132
+ else reply({ error: { code: ERR_INTERNAL, message: errMessage2(e) } });
53133
+ }
53134
+ return;
53135
+ }
53136
+ case "prompts/list":
53137
+ reply({ result: { prompts: PROMPTS } });
53138
+ return;
53139
+ case "prompts/get": {
53140
+ const name2 = typeof msg.params?.name === "string" ? msg.params.name : "";
53141
+ const args2 = msg.params?.arguments ?? {};
53142
+ try {
53143
+ reply({ result: getPrompt(name2, args2) });
53144
+ } catch (e) {
53145
+ if (e instanceof PromptError) reply({ error: { code: ERR_INVALID_PARAMS, message: e.message } });
53146
+ else reply({ error: { code: ERR_INTERNAL, message: errMessage2(e) } });
53147
+ }
53148
+ return;
53149
+ }
53150
+ default:
53151
+ reply({ error: { code: ERR_METHOD_NOT_FOUND, message: `method not found: ${String(msg.method)}` } });
53152
+ return;
53153
+ }
53154
+ } catch (e) {
53155
+ reply({ error: { code: ERR_INTERNAL, message: errMessage2(e) } });
53156
+ }
53157
+ }
53158
+ async function handleToolCall(msg, reply) {
53159
+ const params = msg.params ?? {};
53160
+ const name2 = typeof params.name === "string" ? params.name : "";
53161
+ const args2 = params.arguments ?? {};
53162
+ const decl = listTools().find((t2) => t2.name === name2);
53163
+ if (!decl) {
53164
+ reply({ error: { code: ERR_INVALID_PARAMS, message: `unknown tool: ${name2 || "(none given)"}` } });
53165
+ return;
53166
+ }
53167
+ const invalid = validateArgs2(decl.inputSchema, args2);
53168
+ if (invalid) {
53169
+ reply({ error: { code: ERR_INVALID_PARAMS, message: invalid } });
53170
+ return;
53171
+ }
53172
+ try {
53173
+ const { text: raw, artifact } = await callTool2(name2, args2, { defaultCwd: opts.defaultCwd, allowWrite: opts.allowWrite });
53174
+ const text = capResponse2(raw, name2, maxBytes, artifact);
53175
+ const capped = text !== raw;
53176
+ const structured = protocol >= RICH_TOOLS_SINCE2 ? structuredContentFor2(text, capped, decl.outputSchema !== void 0) : void 0;
53177
+ reply({ result: { content: [{ type: "text", text }], ...structured ? { structuredContent: structured } : {} } });
53178
+ } catch (e) {
53179
+ if (e instanceof ToolError) {
53180
+ reply({ result: { content: [{ type: "text", text: e.message }], isError: true } });
53181
+ return;
53182
+ }
53183
+ reply({ error: { code: ERR_INTERNAL, message: errMessage2(e) } });
53184
+ }
53185
+ }
53186
+ return {
53187
+ handle: handle2,
53188
+ protocolVersion: () => protocol,
53189
+ setProtocolVersion: (v) => {
53190
+ protocol = v;
53191
+ },
53192
+ tools: listTools
53193
+ };
53194
+ }
53195
+ function errMessage2(e) {
53196
+ return e instanceof Error ? e.message : String(e);
53197
+ }
53198
+
53199
+ // src/mcp/stdio.ts
53200
+ var MAX_IN_FLIGHT = 4;
53201
+ async function runStdioServer(opts = {}) {
53202
+ const input = opts.input ?? process.stdin;
53203
+ const output = opts.output ?? process.stdout;
53204
+ const emit2 = output.write.bind(output);
53205
+ let restore;
53206
+ if (!opts.captureStdout && output === process.stdout) {
53207
+ const original = process.stdout.write;
53208
+ process.stdout.write = ((chunk, ...rest) => process.stderr.write(chunk, ...rest));
53209
+ restore = () => {
53210
+ process.stdout.write = original;
53211
+ };
53212
+ }
53213
+ const server = createServer2(opts);
53214
+ const send = (msg) => {
53215
+ emit2(JSON.stringify(msg) + "\n");
53216
+ };
53217
+ const inFlight = /* @__PURE__ */ new Set();
53218
+ const track = (p) => {
53219
+ inFlight.add(p);
53220
+ void p.finally(() => inFlight.delete(p));
53221
+ return p;
53222
+ };
53223
+ const drainToLimit = async () => {
53224
+ while (inFlight.size >= MAX_IN_FLIGHT) await Promise.race(inFlight);
53225
+ };
53226
+ const rl = createInterface2({ input, terminal: false });
53227
+ try {
53228
+ for await (const line of rl) {
53229
+ const trimmed = line.trim();
53230
+ if (!trimmed) continue;
53231
+ let parsed;
53232
+ try {
53233
+ parsed = JSON.parse(trimmed);
53234
+ } catch {
53235
+ send({ jsonrpc: "2.0", id: null, error: { code: -32700, message: "parse error" } });
53236
+ continue;
53237
+ }
53238
+ await drainToLimit();
53239
+ if (Array.isArray(parsed)) {
53240
+ track(
53241
+ (async () => {
53242
+ const out2 = [];
53243
+ await Promise.all(parsed.map((m) => server.handle(m, (r) => void out2.push(r))));
53244
+ if (out2.length) emit2(JSON.stringify(out2) + "\n");
53245
+ })().catch(reportInternal(send))
53246
+ );
53247
+ continue;
53248
+ }
53249
+ if (parsed === null || typeof parsed !== "object") {
53250
+ send({ jsonrpc: "2.0", id: null, error: { code: ERR_INVALID_REQUEST, message: "invalid request: expected a JSON-RPC object" } });
53251
+ continue;
53252
+ }
53253
+ track(server.handle(parsed, send).catch(reportInternal(send)));
53254
+ }
53255
+ await Promise.all(inFlight);
53256
+ } finally {
53257
+ rl.close();
53258
+ restore?.();
53259
+ }
53260
+ }
53261
+ function reportInternal(send) {
53262
+ return (e) => {
53263
+ send({ jsonrpc: "2.0", id: null, error: { code: -32603, message: e instanceof Error ? e.message : String(e) } });
53264
+ };
53265
+ }
53266
+
53267
+ // src/mcp/http.ts
53268
+ import { createServer as createHttpServer } from "http";
53269
+ var MCP_PATH = "/mcp";
53270
+ var MAX_BODY_BYTES = 4 * 1024 * 1024;
53271
+ var CORS_HEADERS = "content-type, accept, mcp-protocol-version, mcp-session-id, authorization, last-event-id";
53272
+ var LOOPBACK_BIND = /* @__PURE__ */ new Set(["127.0.0.1", "::1", "localhost"]);
53273
+ function startHttpServer(opts = {}) {
53274
+ const bind = opts.bind ?? "127.0.0.1";
53275
+ if (!LOOPBACK_BIND.has(bind) && !opts.allowRemote) {
53276
+ return Promise.reject(
53277
+ new Error(
53278
+ `refusing to bind ${bind}: ultra11y's MCP server clones arbitrary git URLs and reads local files. Pass --allow-remote if that is really what you want.`
53279
+ )
53280
+ );
53281
+ }
53282
+ const server = createHttpServer((req, res) => {
53283
+ void route(req, res, opts).catch((e) => {
53284
+ if (res.headersSent) {
53285
+ res.destroy();
53286
+ return;
53287
+ }
53288
+ sendJson(res, 500, { jsonrpc: "2.0", id: null, error: { code: -32603, message: e instanceof Error ? e.message : String(e) } });
53289
+ });
53290
+ });
53291
+ server.requestTimeout = 0;
53292
+ server.headersTimeout = 6e4;
53293
+ server.keepAliveTimeout = 12e4;
53294
+ return new Promise((resolve10, reject) => {
53295
+ server.once("error", reject);
53296
+ server.listen(opts.port ?? 0, bind, () => {
53297
+ server.removeListener("error", reject);
53298
+ const addr2 = server.address();
53299
+ const port = typeof addr2 === "object" && addr2 ? addr2.port : opts.port ?? 0;
53300
+ const host = bind.includes(":") ? `[${bind}]` : bind;
53301
+ resolve10({
53302
+ server,
53303
+ port,
53304
+ url: `http://${host}:${port}${MCP_PATH}`,
53305
+ close: () => new Promise((done) => {
53306
+ server.closeAllConnections?.();
53307
+ server.close(() => done());
53308
+ })
53309
+ });
53310
+ });
53311
+ });
53312
+ }
53313
+ async function route(req, res, opts) {
53314
+ const path = (req.url ?? "").split("?")[0];
53315
+ const origin = header2(req, "origin");
53316
+ if (!isOriginAllowed(origin, opts.allowOrigin)) {
53317
+ sendJson(res, 403, { error: "origin not allowed", origin });
53318
+ return;
53319
+ }
53320
+ if (req.method === "OPTIONS") {
53321
+ res.writeHead(204, {
53322
+ ...corsHeaders(origin),
53323
+ "access-control-allow-methods": "POST, GET, DELETE, OPTIONS",
53324
+ "access-control-allow-headers": CORS_HEADERS,
53325
+ "access-control-max-age": "86400"
53326
+ });
53327
+ res.end();
53328
+ return;
53329
+ }
53330
+ if (path !== MCP_PATH) {
53331
+ sendJson(res, 404, { error: `not found: ${path} (the MCP endpoint is ${MCP_PATH})` }, origin);
53332
+ return;
53333
+ }
53334
+ if (req.method === "GET" || req.method === "DELETE") {
53335
+ res.writeHead(405, { allow: "POST, OPTIONS", ...corsHeaders(origin) });
53336
+ res.end(JSON.stringify({ error: `${req.method} is not supported: this server is stateless and offers no server-initiated stream` }));
53337
+ return;
53338
+ }
53339
+ if (req.method !== "POST") {
53340
+ res.writeHead(405, { allow: "POST, OPTIONS", ...corsHeaders(origin) });
53341
+ res.end(JSON.stringify({ error: `${req.method} is not supported` }));
53342
+ return;
53343
+ }
53344
+ const contentType = (header2(req, "content-type") ?? "").split(";")[0].trim().toLowerCase();
53345
+ if (contentType && contentType !== "application/json") {
53346
+ sendJson(res, 415, { error: `unsupported content-type "${contentType}" \u2014 send application/json` }, origin);
53347
+ return;
53348
+ }
53349
+ const accept = (header2(req, "accept") ?? "").toLowerCase();
53350
+ if (accept && !/application\/json|text\/event-stream|\*\/\*/.test(accept)) {
53351
+ sendJson(res, 406, { error: "this endpoint replies with application/json" }, origin);
53352
+ return;
53353
+ }
53354
+ const declared = header2(req, "mcp-protocol-version");
53355
+ if (declared !== void 0 && !isProtocolVersion(declared)) {
53356
+ sendJson(res, 400, { error: `unsupported MCP-Protocol-Version: ${declared}` }, origin);
53357
+ return;
53358
+ }
53359
+ const protocol = declared ?? ASSUMED_HTTP_PROTOCOL;
53360
+ let raw;
53361
+ try {
53362
+ raw = await readBody2(req);
53363
+ } catch (e) {
53364
+ if (e.message === "too large") {
53365
+ sendJson(res, 413, { error: `request body exceeds ${MAX_BODY_BYTES} bytes` }, origin);
53366
+ return;
53367
+ }
53368
+ sendJson(res, 400, { error: `could not read request body: ${e.message}` }, origin);
53369
+ return;
53370
+ }
53371
+ let parsed;
53372
+ try {
53373
+ parsed = JSON.parse(raw);
53374
+ } catch {
53375
+ sendJson(res, 200, { jsonrpc: "2.0", id: null, error: { code: -32700, message: "parse error" } }, origin);
53376
+ return;
53377
+ }
53378
+ const mcp = createServer2(opts);
53379
+ mcp.setProtocolVersion(protocol);
53380
+ const out2 = [];
53381
+ const collect2 = (m) => void out2.push(m);
53382
+ const messages = Array.isArray(parsed) ? parsed : [parsed];
53383
+ for (const m of messages) await mcp.handle(m, collect2);
53384
+ if (out2.length === 0) {
53385
+ res.writeHead(202, corsHeaders(origin));
53386
+ res.end();
53387
+ return;
53388
+ }
53389
+ sendJson(res, 200, Array.isArray(parsed) ? out2 : out2[0], origin);
53390
+ }
53391
+ function header2(req, name2) {
53392
+ const v = req.headers[name2];
53393
+ return Array.isArray(v) ? v[0] : v;
53394
+ }
53395
+ function corsHeaders(origin) {
53396
+ return origin ? { "access-control-allow-origin": origin, vary: "origin" } : {};
53397
+ }
53398
+ function sendJson(res, status, body2, origin, extra = {}) {
53399
+ const text = JSON.stringify(body2);
53400
+ res.writeHead(status, {
53401
+ "content-type": "application/json",
53402
+ "content-length": String(Buffer.byteLength(text, "utf8")),
53403
+ ...corsHeaders(origin),
53404
+ ...extra
53405
+ });
53406
+ res.end(text);
53407
+ }
53408
+ var DRAIN_LIMIT = MAX_BODY_BYTES * 8;
53409
+ function readBody2(req) {
53410
+ return new Promise((resolve10, reject) => {
53411
+ const chunks = [];
53412
+ let size = 0;
53413
+ let over = false;
53414
+ const declared = Number(req.headers["content-length"]);
53415
+ if (Number.isFinite(declared) && declared > MAX_BODY_BYTES) over = true;
53416
+ req.on("data", (c2) => {
53417
+ size += c2.length;
53418
+ if (over) {
53419
+ if (size > DRAIN_LIMIT) {
53420
+ req.destroy();
53421
+ reject(new Error("too large"));
53422
+ }
53423
+ return;
53424
+ }
53425
+ if (size > MAX_BODY_BYTES) {
53426
+ over = true;
53427
+ chunks.length = 0;
53428
+ return;
53429
+ }
53430
+ chunks.push(c2);
53431
+ });
53432
+ req.on("end", () => {
53433
+ if (over) reject(new Error("too large"));
53434
+ else resolve10(Buffer.concat(chunks).toString("utf8"));
53435
+ });
53436
+ req.on("error", reject);
53437
+ req.on("aborted", () => reject(new Error("client aborted the request")));
53438
+ });
53439
+ }
53440
+
52196
53441
  // src/cli.ts
52197
53442
  var HELP2 = `ultra11y v${VERSION}
52198
53443
  Audit HTML/CSS/JSX against WCAG 2.2 AA and produce a dated compliance report, or
@@ -52227,6 +53472,7 @@ Usage:
52227
53472
  ultra11y snapshot write [--root <dir>] [--fail-on blocking|major|minor] [--json] (payload on stdin \u2192 .ultra11y/pages/<id>/ + audit it)
52228
53473
  ultra11y snapshot list [--root <dir>] [--json]
52229
53474
  ultra11y pages --in <audit.json> [--standard <pack>] [--json] [--lang auto|en|fr] (the per-page criterion grid)
53475
+ ultra11y mcp [--transport stdio|http] [--cwd <dir>] [--allow-write] [--port <n>] [--bind <addr>] [--allow-remote] [--allow-origin <o>] [--max-response-bytes <n>]
52230
53476
  ultra11y dev [--port <n>] [--root <dir>] [--standard <pack>] [--lang auto|en|fr] (dev side-car: live overlay + per-page dashboard)
52231
53477
  ultra11y dev --next [--port <n>] (write the Next overlay component, then wire one line into your layout)
52232
53478
 
@@ -52361,6 +53607,12 @@ Commands:
52361
53607
  --next writes the overlay component to wire into your layout \u2014 it
52362
53608
  renders NOTHING outside development. LOOPBACK ONLY: the server
52363
53609
  writes files, so it never binds beyond 127.0.0.1.
53610
+ mcp Serve the engine over the Model Context Protocol, so an MCP client
53611
+ (Claude Code, an IDE) drives audit/report/criteria as tools instead of
53612
+ shelling out. --transport stdio (default) speaks over stdin/stdout;
53613
+ --transport http listens on 127.0.0.1 (--port, --bind), and stays
53614
+ loopback-only unless you pass --allow-remote. Read-only by default:
53615
+ --allow-write opts into the commands that touch files.
52364
53616
  pages The per-page criterion grid \u2014 RGAA is a per-page norm, the engine's
52365
53617
  verdict is scope-wide, and this bridges the two. One row per criterion
52366
53618
  (the pack's own under --standard), one column per page. Rebuilt from a
@@ -52417,7 +53669,13 @@ Options:
52417
53669
  --e2e render: write the Playwright/Cypress fixtures into .ultra11y/e2e/
52418
53670
  --runner <name> render --e2e: force playwright|cypress instead of auto-detecting
52419
53671
  --root <dir> snapshot: project root holding .ultra11y/pages (default: .)
52420
- --port <n> dev: port for the side-car (default: 4111)
53672
+ --port <n> dev: port for the side-car (default: 4111); mcp: HTTP transport port
53673
+ --transport <k> mcp: stdio (default) or http
53674
+ --bind <addr> mcp --transport http: address to bind (default 127.0.0.1)
53675
+ --allow-remote mcp --transport http: accept non-loopback connections (off by default)
53676
+ --allow-origin <o> mcp --transport http: an Origin allowed to call the server
53677
+ --max-response-bytes <n> mcp: cap a tool response's size
53678
+ --allow-write mcp: allow the tools that write files (read-only otherwise)
52421
53679
  --glossary [<t>] criteria: look up a term the country standard defines (or list all)
52422
53680
  --next dev: write the Next.js overlay component instead of serving
52423
53681
  --require-captures audit: gate \u2014 fail if any opaque/control component lacks a rendered capture (implies --graph)
@@ -52504,6 +53762,7 @@ var COMMANDS = [
52504
53762
  "snapshot",
52505
53763
  "pages",
52506
53764
  "dev",
53765
+ "mcp",
52507
53766
  "fix",
52508
53767
  "init",
52509
53768
  "pack",
@@ -52548,8 +53807,15 @@ var VALUE_FLAGS2 = /* @__PURE__ */ new Set([
52548
53807
  "phase",
52549
53808
  "root",
52550
53809
  "runner",
53810
+ // Shared by `dev` (side-car port) and `mcp` (HTTP transport port) — one entry, both users.
52551
53811
  "port",
52552
- "glossary"
53812
+ "glossary",
53813
+ // `mcp` only. The flag sets are global, so these are accepted (and warned
53814
+ // about, never silently ignored) on every command — like --phase already is.
53815
+ "transport",
53816
+ "bind",
53817
+ "allow-origin",
53818
+ "max-response-bytes"
52553
53819
  ]);
52554
53820
  var INIT_VALUE_FLAGS = new Set([...VALUE_FLAGS2].filter((f) => f !== "baseline"));
52555
53821
  function valueFlagsFor(command) {
@@ -52594,7 +53860,9 @@ var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
52594
53860
  "sample",
52595
53861
  "eco",
52596
53862
  "help",
52597
- "version"
53863
+ "version",
53864
+ "allow-remote",
53865
+ "allow-write"
52598
53866
  ]);
52599
53867
  var KNOWN_FLAGS = /* @__PURE__ */ new Set([...VALUE_FLAGS2, ...BOOLEAN_FLAGS]);
52600
53868
  var LIST_FLAGS = /* @__PURE__ */ new Set(["include", "exclude", "ext"]);
@@ -52719,9 +53987,9 @@ async function cmdAudit(p) {
52719
53987
  const capturesFlag = typeof p.flags.captures === "string" && p.flags.captures ? p.flags.captures : void 0;
52720
53988
  const capturesDir = capturesFlag ?? ".ultra11y/captures";
52721
53989
  const scopedToDiff = p.flags.changed === true || p.flags.staged === true || since !== void 0;
52722
- const capturesWanted = p.flags["no-captures"] !== true && !inputs.includes("-") && (capturesFlag !== void 0 || existsSync20(capturesDir));
53990
+ const capturesWanted = p.flags["no-captures"] !== true && !inputs.includes("-") && (capturesFlag !== void 0 || existsSync22(capturesDir));
52723
53991
  const useCaptures = capturesWanted && !scopedToDiff && !inputs.includes(capturesDir);
52724
- const pagesWanted = p.flags["no-captures"] !== true && !inputs.includes("-") && existsSync20(PAGES_DIR);
53992
+ const pagesWanted = p.flags["no-captures"] !== true && !inputs.includes("-") && existsSync22(PAGES_DIR);
52725
53993
  const usePages = pagesWanted && !scopedToDiff && !inputs.includes(PAGES_DIR);
52726
53994
  const auditInputs = [...inputs, ...useCaptures ? [capturesDir] : [], ...usePages ? [PAGES_DIR] : []];
52727
53995
  if (useCaptures)
@@ -52762,9 +54030,9 @@ async function cmdAudit(p) {
52762
54030
  if (typeof p.flags.out === "string") {
52763
54031
  const out2 = p.flags.out;
52764
54032
  const asFile = out2.toLowerCase().endsWith(".json");
52765
- const target = asFile ? out2 : join36(out2, "audit-latest.json");
54033
+ const target = asFile ? out2 : join38(out2, "audit-latest.json");
52766
54034
  try {
52767
- mkdirSync11(asFile ? dirname10(out2) : out2, { recursive: true });
54035
+ mkdirSync11(asFile ? dirname11(out2) : out2, { recursive: true });
52768
54036
  writeFileSync14(target, JSON.stringify(result, null, 2) + "\n");
52769
54037
  console.error(lang === "fr" ? `\u2192 audit \xE9crit dans ${target}` : `\u2192 audit written to ${target}`);
52770
54038
  } catch {
@@ -52784,7 +54052,7 @@ async function cmdAudit(p) {
52784
54052
  const baselineFlag = p.flags.baseline;
52785
54053
  if (typeof baselineFlag === "string" && baselineFlag) {
52786
54054
  let baseline = null;
52787
- if (existsSync20(baselineFlag)) {
54055
+ if (existsSync22(baselineFlag)) {
52788
54056
  try {
52789
54057
  const parsed = JSON.parse(readText(baselineFlag));
52790
54058
  if (isCurrentAudit(parsed)) baseline = parsed;
@@ -52838,11 +54106,11 @@ async function cmdDev(p) {
52838
54106
  return 2;
52839
54107
  }
52840
54108
  if (p.flags.next === true) {
52841
- const dir = join36(root, ".ultra11y", "next");
54109
+ const dir = join38(root, ".ultra11y", "next");
52842
54110
  const rel = ".ultra11y/next/overlay.jsx";
52843
54111
  try {
52844
54112
  mkdirSync11(dir, { recursive: true });
52845
- writeFileSync14(join36(dir, "overlay.jsx"), nextOverlayComponent(port));
54113
+ writeFileSync14(join38(dir, "overlay.jsx"), nextOverlayComponent(port));
52846
54114
  } catch (e) {
52847
54115
  console.error(`ultra11y dev: could not write ${rel}: ${e instanceof Error ? e.message : String(e)}`);
52848
54116
  return 1;
@@ -52873,9 +54141,9 @@ async function cmdDev(p) {
52873
54141
  }
52874
54142
  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}`);
52875
54143
  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.");
52876
- await new Promise((resolve8) => {
54144
+ await new Promise((resolve10) => {
52877
54145
  const stop2 = () => {
52878
- void server.close().then(resolve8);
54146
+ void server.close().then(resolve10);
52879
54147
  };
52880
54148
  process.once("SIGINT", stop2);
52881
54149
  process.once("SIGTERM", stop2);
@@ -52930,7 +54198,7 @@ async function cmdSnapshot(p) {
52930
54198
  2
52931
54199
  )
52932
54200
  );
52933
- else if (!snaps.length) console.log(lang === "fr" ? `Aucun instantan\xE9 dans ${join36(root, PAGES_DIR)}.` : `No snapshot in ${join36(root, PAGES_DIR)}.`);
54201
+ else if (!snaps.length) console.log(lang === "fr" ? `Aucun instantan\xE9 dans ${join38(root, PAGES_DIR)}.` : `No snapshot in ${join38(root, PAGES_DIR)}.`);
52934
54202
  else for (const s of snaps) console.log(`${s.meta.id} ${s.meta.name} ${s.meta.url}${s.meta.auth ? " [auth]" : ""}`);
52935
54203
  return 0;
52936
54204
  }
@@ -52975,12 +54243,12 @@ async function cmdSnapshot(p) {
52975
54243
  }
52976
54244
  if (typeof payload.screenshot === "string" && payload.screenshot) {
52977
54245
  try {
52978
- writeFileSync14(join36(dir, "screen.png"), Buffer.from(payload.screenshot, "base64"));
54246
+ writeFileSync14(join38(dir, "screen.png"), Buffer.from(payload.screenshot, "base64"));
52979
54247
  } catch {
52980
54248
  console.error("ultra11y snapshot write: the screenshot could not be written \u2014 the pixel tier will be skipped for this page.");
52981
54249
  }
52982
54250
  }
52983
- const result = runAudit({ inputs: [join36(dir, "dom.html")], onWarn: (m) => console.error(m) });
54251
+ const result = runAudit({ inputs: [join38(dir, "dom.html")], onWarn: (m) => console.error(m) });
52984
54252
  const failOnRaw = p.flags["fail-on"];
52985
54253
  const failOnParsed = parseFailOn(failOnRaw);
52986
54254
  if (failOnRaw !== void 0 && failOnParsed === null) {
@@ -52999,8 +54267,8 @@ function cmdInit(p) {
52999
54267
  const root = repoRoot() ?? process.cwd();
53000
54268
  let engineRel = process.argv[1] ?? "scripts/ultra11y.mjs";
53001
54269
  try {
53002
- const abs = realpathSync2(engineRel);
53003
- engineRel = abs.startsWith(root + sep4) ? relative3(root, abs) : abs;
54270
+ const abs = realpathSync4(engineRel);
54271
+ engineRel = abs.startsWith(root + sep6) ? relative3(root, abs) : abs;
53004
54272
  } catch {
53005
54273
  }
53006
54274
  const failOnParsed = parseFailOn(p.flags["fail-on"]);
@@ -53017,8 +54285,8 @@ function cmdInit(p) {
53017
54285
  if (want.baseline) {
53018
54286
  const inputs = p.positionals.length ? p.positionals : ["."];
53019
54287
  const result = runAudit({ inputs, onWarn: (m) => console.error(m) });
53020
- mkdirSync11(join36(root, "audits"), { recursive: true });
53021
- const bp = join36(root, "audits", "baseline.json");
54288
+ mkdirSync11(join38(root, "audits"), { recursive: true });
54289
+ const bp = join38(root, "audits", "baseline.json");
53022
54290
  writeFileSync14(bp, JSON.stringify(result, null, 2) + "\n");
53023
54291
  wrote.push(bp);
53024
54292
  }
@@ -53156,8 +54424,8 @@ async function cmdPrd(p) {
53156
54424
  return gh2 && gh2.failed > 0 ? 1 : 0;
53157
54425
  }
53158
54426
  function depsAt(root) {
53159
- const pkgPath = join36(root, "package.json");
53160
- if (!existsSync20(pkgPath)) return {};
54427
+ const pkgPath = join38(root, "package.json");
54428
+ if (!existsSync22(pkgPath)) return {};
53161
54429
  try {
53162
54430
  const pkg = JSON.parse(readText(pkgPath));
53163
54431
  return { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
@@ -53168,8 +54436,8 @@ function depsAt(root) {
53168
54436
  function engineRefFor(root) {
53169
54437
  let ref = process.argv[1] ?? "scripts/ultra11y.mjs";
53170
54438
  try {
53171
- const abs = realpathSync2(ref);
53172
- ref = abs.startsWith(root + sep4) ? relative3(root, abs) : abs;
54439
+ const abs = realpathSync4(ref);
54440
+ ref = abs.startsWith(root + sep6) ? relative3(root, abs) : abs;
53173
54441
  } catch {
53174
54442
  }
53175
54443
  return ref;
@@ -53183,24 +54451,24 @@ function cmdRender(p) {
53183
54451
  console.error(`ultra11y render: --runner must be playwright|cypress|auto (got "${forced}").`);
53184
54452
  return 2;
53185
54453
  }
53186
- const detected = detectE2eRunner(depsAt(root), (f) => existsSync20(join36(root, f)));
54454
+ const detected = detectE2eRunner(depsAt(root), (f) => existsSync22(join38(root, f)));
53187
54455
  const runners = forced && forced !== "auto" ? [forced] : detected;
53188
54456
  if (!runners.length) {
53189
54457
  console.error(e2eSetupPlan([], {}, lang));
53190
54458
  return 1;
53191
54459
  }
53192
54460
  const engineRef = engineRefFor(root);
53193
- const dir = join36(root, ".ultra11y", "e2e");
54461
+ const dir = join38(root, ".ultra11y", "e2e");
53194
54462
  const paths = {};
53195
54463
  try {
53196
54464
  mkdirSync11(dir, { recursive: true });
53197
54465
  if (runners.includes("playwright")) {
53198
- writeFileSync14(join36(dir, "playwright.mjs"), playwrightFixture(engineRef));
54466
+ writeFileSync14(join38(dir, "playwright.mjs"), playwrightFixture(engineRef));
53199
54467
  paths.playwright = ".ultra11y/e2e/playwright.mjs";
53200
54468
  }
53201
54469
  if (runners.includes("cypress")) {
53202
- writeFileSync14(join36(dir, "cypress-plugin.mjs"), cypressPlugin(engineRef));
53203
- writeFileSync14(join36(dir, "cypress-commands.mjs"), cypressCommands());
54470
+ writeFileSync14(join38(dir, "cypress-plugin.mjs"), cypressPlugin(engineRef));
54471
+ writeFileSync14(join38(dir, "cypress-commands.mjs"), cypressCommands());
53204
54472
  paths.cypressPlugin = ".ultra11y/e2e/cypress-plugin.mjs";
53205
54473
  paths.cypressCommands = ".ultra11y/e2e/cypress-commands.mjs";
53206
54474
  }
@@ -53229,29 +54497,29 @@ Fill in COMPONENTS, run it (e.g. npx tsx ${out2}), then: node scripts/ultra11y.m
53229
54497
  }
53230
54498
  if (p.flags.setup === true) {
53231
54499
  const rel = ".ultra11y/capture-setup.mjs";
53232
- const out2 = join36(root, rel);
54500
+ const out2 = join38(root, rel);
53233
54501
  try {
53234
- mkdirSync11(dirname10(out2), { recursive: true });
54502
+ mkdirSync11(dirname11(out2), { recursive: true });
53235
54503
  writeFileSync14(out2, captureSetup());
53236
54504
  } catch (e) {
53237
54505
  console.error(`ultra11y render: could not write ${out2}: ${e instanceof Error ? e.message : String(e)}`);
53238
54506
  return 1;
53239
54507
  }
53240
54508
  let setupDeps = {};
53241
- const setupPkg = join36(root, "package.json");
53242
- if (existsSync20(setupPkg)) {
54509
+ const setupPkg = join38(root, "package.json");
54510
+ if (existsSync22(setupPkg)) {
53243
54511
  try {
53244
54512
  const pkg = JSON.parse(readText(setupPkg));
53245
54513
  setupDeps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
53246
54514
  } catch {
53247
54515
  }
53248
54516
  }
53249
- const tr = detectTestRunner(setupDeps, (f) => existsSync20(join36(root, f)));
54517
+ const tr = detectTestRunner(setupDeps, (f) => existsSync22(join38(root, f)));
53250
54518
  console.log(captureSetupPlan(tr, rel, lang));
53251
54519
  const gaLine = ".ultra11y/captures/*.html text eol=lf linguist-generated=true";
53252
- const gaPath = join36(root, ".gitattributes");
54520
+ const gaPath = join38(root, ".gitattributes");
53253
54521
  try {
53254
- const existing = existsSync20(gaPath) ? readFileSync17(gaPath, "utf8") : "";
54522
+ const existing = existsSync22(gaPath) ? readFileSync19(gaPath, "utf8") : "";
53255
54523
  if (!existing.includes(".ultra11y/captures/")) {
53256
54524
  appendFileSync(gaPath, (existing && !existing.endsWith("\n") ? "\n" : "") + gaLine + "\n");
53257
54525
  console.log(lang === "fr" ? `.gitattributes : ajout\xE9 \xAB ${gaLine} \xBB` : `.gitattributes: added "${gaLine}"`);
@@ -53259,8 +54527,8 @@ Fill in COMPONENTS, run it (e.g. npx tsx ${out2}), then: node scripts/ultra11y.m
53259
54527
  } catch {
53260
54528
  }
53261
54529
  try {
53262
- const giPath = join36(root, ".gitignore");
53263
- if (existsSync20(giPath) && /^\s*\/?\.ultra11y(\/\**)?\/?\s*$/m.test(readFileSync17(giPath, "utf8")))
54530
+ const giPath = join38(root, ".gitignore");
54531
+ if (existsSync22(giPath) && /^\s*\/?\.ultra11y(\/\**)?\/?\s*$/m.test(readFileSync19(giPath, "utf8")))
53264
54532
  console.error(
53265
54533
  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/").'
53266
54534
  );
@@ -53270,8 +54538,8 @@ Fill in COMPONENTS, run it (e.g. npx tsx ${out2}), then: node scripts/ultra11y.m
53270
54538
  }
53271
54539
  if (p.flags.storybook === true || typeof p.flags.storybook === "string") {
53272
54540
  const sbDir = p.positionals[0] ?? "storybook-static";
53273
- const indexPath = existsSync20(join36(sbDir, "index.json")) ? join36(sbDir, "index.json") : join36(sbDir, "stories.json");
53274
- if (!existsSync20(indexPath)) {
54541
+ const indexPath = existsSync22(join38(sbDir, "index.json")) ? join38(sbDir, "index.json") : join38(sbDir, "stories.json");
54542
+ if (!existsSync22(indexPath)) {
53275
54543
  console.error(
53276
54544
  lang === "fr" ? `ultra11y render : aucun index Storybook (index.json/stories.json) dans ${sbDir}.` : `ultra11y render: no Storybook index (index.json/stories.json) in ${sbDir}.`
53277
54545
  );
@@ -53281,7 +54549,7 @@ Fill in COMPONENTS, run it (e.g. npx tsx ${out2}), then: node scripts/ultra11y.m
53281
54549
  const provById = new Map(stories.map((s) => [s.id, storyProvenance(s)]));
53282
54550
  const capturesFlag = typeof p.flags.captures === "string" && p.flags.captures ? p.flags.captures : void 0;
53283
54551
  const htmlDir = capturesFlag ?? sbDir;
53284
- const htmlFiles = existsSync20(htmlDir) ? discover([htmlDir]).files.filter((f) => /\.html?$/i.test(f)) : [];
54552
+ const htmlFiles = existsSync22(htmlDir) ? discover([htmlDir]).files.filter((f) => /\.html?$/i.test(f)) : [];
53285
54553
  const outDir = ".ultra11y/captures";
53286
54554
  let attributed = 0;
53287
54555
  let skipped = 0;
@@ -53304,7 +54572,7 @@ Fill in COMPONENTS, run it (e.g. npx tsx ${out2}), then: node scripts/ultra11y.m
53304
54572
  }
53305
54573
  try {
53306
54574
  mkdirSync11(outDir, { recursive: true });
53307
- writeFileSync14(join36(outDir, `${hitId}.html`), `${formatCaptureComment(prov)}
54575
+ writeFileSync14(join38(outDir, `${hitId}.html`), `${formatCaptureComment(prov)}
53308
54576
  ${raw}${raw.endsWith("\n") ? "" : "\n"}`);
53309
54577
  attributed++;
53310
54578
  } catch {
@@ -53326,11 +54594,11 @@ ${raw}${raw.endsWith("\n") ? "" : "\n"}`);
53326
54594
  }
53327
54595
  if (p.flags.coverage === true) {
53328
54596
  const capturesFlag = typeof p.flags.captures === "string" && p.flags.captures ? p.flags.captures : void 0;
53329
- const capturesDir = capturesFlag ?? join36(root, ".ultra11y/captures");
54597
+ const capturesDir = capturesFlag ?? join38(root, ".ultra11y/captures");
53330
54598
  const graphExt = [...GRAPH_ONLY_EXT, ...asList(p.flags.ext) ?? []];
53331
54599
  const sourceFiles = discover([root], { include: asList(p.flags.include), exclude: asList(p.flags.exclude), ext: graphExt }).files;
53332
54600
  const graph = buildGraphStreaming(sourceFiles);
53333
- const capFiles = existsSync20(capturesDir) ? discover([capturesDir]).files : [];
54601
+ const capFiles = existsSync22(capturesDir) ? discover([capturesDir]).files : [];
53334
54602
  const entries = capFiles.map((f) => ({ file: toPosix(f), provenance: parseCaptureProvenance(readText(f)) }));
53335
54603
  const cov = computeCaptureCoverage(graph, entries);
53336
54604
  if (p.flags.json) console.log(JSON.stringify(cov, null, 2));
@@ -53338,15 +54606,15 @@ ${raw}${raw.endsWith("\n") ? "" : "\n"}`);
53338
54606
  return 0;
53339
54607
  }
53340
54608
  let deps = {};
53341
- const pkgPath = join36(root, "package.json");
53342
- if (existsSync20(pkgPath)) {
54609
+ const pkgPath = join38(root, "package.json");
54610
+ if (existsSync22(pkgPath)) {
53343
54611
  try {
53344
54612
  const pkg = JSON.parse(readText(pkgPath));
53345
54613
  deps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
53346
54614
  } catch {
53347
54615
  }
53348
54616
  }
53349
- const detection = detectFrameworks(deps, (f) => existsSync20(join36(root, f)));
54617
+ const detection = detectFrameworks(deps, (f) => existsSync22(join38(root, f)));
53350
54618
  if (p.flags.json) console.log(JSON.stringify(detection, null, 2));
53351
54619
  else console.log(renderPlan(detection, lang));
53352
54620
  return 0;
@@ -53362,7 +54630,7 @@ function cmdCheck(p) {
53362
54630
  const lang = resolveLang(p.flags, { standard });
53363
54631
  const md = readInputFile(rep, "check", "--report");
53364
54632
  if (md === null) return 2;
53365
- let audit;
54633
+ let audit2;
53366
54634
  const inFlag = p.flags.in;
53367
54635
  if (typeof inFlag === "string" && inFlag) {
53368
54636
  let rawAudit;
@@ -53373,13 +54641,13 @@ function cmdCheck(p) {
53373
54641
  return 2;
53374
54642
  }
53375
54643
  try {
53376
- audit = JSON.parse(rawAudit);
54644
+ audit2 = JSON.parse(rawAudit);
53377
54645
  } catch {
53378
54646
  console.error("ultra11y check: --in file is not valid JSON.");
53379
54647
  return 2;
53380
54648
  }
53381
54649
  }
53382
- const res = checkReport(md, standard, lang, { audit });
54650
+ const res = checkReport(md, standard, lang, { audit: audit2 });
53383
54651
  const sem = p.flags.semantic === true ? checkSemantic(md, {
53384
54652
  reportPath: rep,
53385
54653
  verdictsPath: typeof p.flags.verdicts === "string" && p.flags.verdicts ? p.flags.verdicts : void 0,
@@ -53473,18 +54741,18 @@ function cmdVerify(p) {
53473
54741
  );
53474
54742
  return 2;
53475
54743
  }
53476
- let audit;
54744
+ let audit2;
53477
54745
  try {
53478
- audit = JSON.parse(readText(inFlag));
54746
+ audit2 = JSON.parse(readText(inFlag));
53479
54747
  } catch {
53480
54748
  console.error(`ultra11y verify: --in file not found or not valid JSON: ${inFlag}.`);
53481
54749
  return 2;
53482
54750
  }
53483
- const adjItems = buildAdjudicationWorklist(audit, { standard });
53484
- const w = writeAdjudication(adjItems, out2, { standard, auditDate: audit.date, lang });
54751
+ const adjItems = buildAdjudicationWorklist(audit2, { standard });
54752
+ const w = writeAdjudication(adjItems, out2, { standard, auditDate: audit2.date, lang });
53485
54753
  if (adjItems.every((it) => it.evidence.length === 0)) {
53486
54754
  console.error(
53487
- lang === "fr" ? `ultra11y verify : aucune \xE9vidence n'a pu \xEAtre extraite (${audit.scope.inputs.join(", ")} introuvable ?) \u2014 lancez --manual depuis le r\xE9pertoire de l'audit.` : `ultra11y verify: no evidence could be harvested (${audit.scope.inputs.join(", ")} not found?) \u2014 run --manual from the audit's directory.`
54755
+ lang === "fr" ? `ultra11y verify : aucune \xE9vidence n'a pu \xEAtre extraite (${audit2.scope.inputs.join(", ")} introuvable ?) \u2014 lancez --manual depuis le r\xE9pertoire de l'audit.` : `ultra11y verify: no evidence could be harvested (${audit2.scope.inputs.join(", ")} not found?) \u2014 run --manual from the audit's directory.`
53488
54756
  );
53489
54757
  }
53490
54758
  if (p.flags.json) console.log(JSON.stringify({ mdPath: w.mdPath, todoPath: w.todoPath, count: w.count, items: adjItems }, null, 2));
@@ -53528,14 +54796,14 @@ function applyAdjudicationFile(p, adj, lang) {
53528
54796
  );
53529
54797
  return 2;
53530
54798
  }
53531
- let audit;
54799
+ let audit2;
53532
54800
  try {
53533
- audit = JSON.parse(readText(inFlag));
54801
+ audit2 = JSON.parse(readText(inFlag));
53534
54802
  } catch {
53535
54803
  console.error(`ultra11y verify: --in file not found or not valid JSON: ${inFlag}.`);
53536
54804
  return 2;
53537
54805
  }
53538
- const r = applyAdjudication(audit, adj);
54806
+ const r = applyAdjudication(audit2, adj);
53539
54807
  if (!r.ok) {
53540
54808
  if (p.flags.json) console.log(JSON.stringify(r, null, 2));
53541
54809
  else {
@@ -53546,7 +54814,7 @@ function applyAdjudicationFile(p, adj, lang) {
53546
54814
  }
53547
54815
  const out2 = typeof p.flags.out === "string" ? p.flags.out : ".";
53548
54816
  mkdirSync11(out2, { recursive: true });
53549
- const auditPath = join36(out2, "audit-latest.json");
54817
+ const auditPath = join38(out2, "audit-latest.json");
53550
54818
  writeFileSync14(auditPath, JSON.stringify(r.audit, null, 2) + "\n");
53551
54819
  if (p.flags.json) console.log(JSON.stringify({ ok: true, auditPath, applied: r.applied, stillManual: r.stillManual, grounding: r.grounding }, null, 2));
53552
54820
  else
@@ -53617,7 +54885,7 @@ async function cmdScan(p) {
53617
54885
  return 0;
53618
54886
  }
53619
54887
  for (const target of p.positionals.filter((a) => a !== "-")) {
53620
- if (/^https?:\/\//i.test(target) || existsSync20(target)) continue;
54888
+ if (/^https?:\/\//i.test(target) || existsSync22(target)) continue;
53621
54889
  console.error(
53622
54890
  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.`
53623
54891
  );
@@ -53724,9 +54992,9 @@ async function cmdScan(p) {
53724
54992
  const out2 = typeof p.flags.out === "string" ? p.flags.out : "audits";
53725
54993
  const mergeIn = p.flags.merge;
53726
54994
  if (typeof mergeIn === "string" && mergeIn) {
53727
- let audit;
54995
+ let audit2;
53728
54996
  if (mergeAudit) {
53729
- audit = mergeAudit;
54997
+ audit2 = mergeAudit;
53730
54998
  } else {
53731
54999
  let parsed;
53732
55000
  try {
@@ -53739,15 +55007,15 @@ async function cmdScan(p) {
53739
55007
  console.error("ultra11y scan: --merge input is not a current ultra11y AuditResult (WCAG-keyed, schema v2). Re-run `audit`.");
53740
55008
  return 2;
53741
55009
  }
53742
- audit = parsed;
55010
+ audit2 = parsed;
53743
55011
  }
53744
- const merged = mergeDynamic(audit, dynamic, lang);
55012
+ const merged = mergeDynamic(audit2, dynamic, lang);
53745
55013
  mkdirSync11(out2, { recursive: true });
53746
- writeFileSync14(join36(out2, "audit-latest.json"), JSON.stringify(merged, null, 2) + "\n");
55014
+ writeFileSync14(join38(out2, "audit-latest.json"), JSON.stringify(merged, null, 2) + "\n");
53747
55015
  if (p.flags.json) console.log(JSON.stringify(merged, null, 2));
53748
55016
  else
53749
55017
  console.log(
53750
- lang === "fr" ? `Audit statique + dynamique fusionn\xE9 \u2192 ${join36(out2, "audit-latest.json")} (${merged.conformancePct}% r\xE9ussite, ${merged.findings.length} findings).` : `Static + dynamic audit merged \u2192 ${join36(out2, "audit-latest.json")} (${merged.conformancePct}% pass rate, ${merged.findings.length} findings).`
55018
+ lang === "fr" ? `Audit statique + dynamique fusionn\xE9 \u2192 ${join38(out2, "audit-latest.json")} (${merged.conformancePct}% r\xE9ussite, ${merged.findings.length} findings).` : `Static + dynamic audit merged \u2192 ${join38(out2, "audit-latest.json")} (${merged.conformancePct}% pass rate, ${merged.findings.length} findings).`
53751
55019
  );
53752
55020
  return 0;
53753
55021
  }
@@ -53864,6 +55132,58 @@ function cmdPack(p) {
53864
55132
  console.error("ultra11y pack: expected `pack check <pack.json> [--guidance <g.json>]` or `pack scaffold`.");
53865
55133
  return 2;
53866
55134
  }
55135
+ async function cmdMcp(p) {
55136
+ const transport = typeof p.flags.transport === "string" ? p.flags.transport : "stdio";
55137
+ if (transport !== "stdio" && transport !== "http") {
55138
+ console.error(`ultra11y: invalid --transport "${transport}" (expected: stdio, http)`);
55139
+ return 2;
55140
+ }
55141
+ const maxRaw = typeof p.flags["max-response-bytes"] === "string" ? Number(p.flags["max-response-bytes"]) : void 0;
55142
+ if (maxRaw !== void 0 && (!Number.isFinite(maxRaw) || maxRaw <= 0)) {
55143
+ console.error("ultra11y: invalid --max-response-bytes");
55144
+ return 2;
55145
+ }
55146
+ const options = {
55147
+ // A default project root makes `cwd` optional on every tool, for a server
55148
+ // dedicated to one project.
55149
+ defaultCwd: typeof p.flags.cwd === "string" ? p.flags.cwd : void 0,
55150
+ allowWrite: p.flags["allow-write"] === true,
55151
+ maxResponseBytes: maxRaw
55152
+ };
55153
+ if (transport === "stdio") {
55154
+ await runStdioServer(options);
55155
+ return 0;
55156
+ }
55157
+ const port = typeof p.flags.port === "string" ? Number(p.flags.port) : 7341;
55158
+ if (!Number.isInteger(port) || port < 0 || port > 65535) {
55159
+ console.error("ultra11y: invalid --port");
55160
+ return 2;
55161
+ }
55162
+ const originRaw = typeof p.flags["allow-origin"] === "string" ? p.flags["allow-origin"] : void 0;
55163
+ const allowOrigin = originRaw ? originRaw.split(",").map((x) => x.trim()).filter(Boolean) : void 0;
55164
+ let running;
55165
+ try {
55166
+ running = await startHttpServer({
55167
+ ...options,
55168
+ port,
55169
+ bind: typeof p.flags.bind === "string" ? p.flags.bind : void 0,
55170
+ allowOrigin,
55171
+ allowRemote: p.flags["allow-remote"] === true
55172
+ });
55173
+ } catch (e) {
55174
+ console.error(`ultra11y: ${e.message}`);
55175
+ return 2;
55176
+ }
55177
+ console.error(`ultra11y: MCP server listening on ${running.url}`);
55178
+ console.error(` client: claude mcp add --transport http ultra11y ${running.url}`);
55179
+ for (const sig of ["SIGINT", "SIGTERM"]) {
55180
+ process.once(sig, () => {
55181
+ void running.close().then(() => process.exit(0));
55182
+ });
55183
+ }
55184
+ await new Promise((res) => running.server.once("close", res));
55185
+ return 0;
55186
+ }
53867
55187
  function cmdOrchestrate(p) {
53868
55188
  const lang = resolveLang(p.flags, {});
53869
55189
  const runFlag = p.flags.run;
@@ -53873,9 +55193,9 @@ function cmdOrchestrate(p) {
53873
55193
  );
53874
55194
  return 2;
53875
55195
  }
53876
- const engineAbs = realpathSync2(fileURLToPath4(import.meta.url));
55196
+ const engineAbs = realpathSync4(fileURLToPath5(import.meta.url));
53877
55197
  if (p.flags.list === true) {
53878
- if (!existsSync20(runFlag)) {
55198
+ if (!existsSync22(runFlag)) {
53879
55199
  console.error(`ultra11y orchestrate: run dir not found: ${runFlag}.`);
53880
55200
  return 2;
53881
55201
  }
@@ -53902,7 +55222,7 @@ function cmdOrchestrate(p) {
53902
55222
  );
53903
55223
  } else {
53904
55224
  console.log(
53905
- lang === "fr" ? `Suivez ${join36(runFlag, "orchestration", "RUNBOOK.md")} s\xE9quentiellement (chemin \xE9co).` : `Follow ${join36(runFlag, "orchestration", "RUNBOOK.md")} sequentially (the eco path).`
55225
+ lang === "fr" ? `Suivez ${join38(runFlag, "orchestration", "RUNBOOK.md")} s\xE9quentiellement (chemin \xE9co).` : `Follow ${join38(runFlag, "orchestration", "RUNBOOK.md")} sequentially (the eco path).`
53906
55226
  );
53907
55227
  }
53908
55228
  if (p.flags.phase === void 0 && workflows.length === 0 && p.flags.eco !== true) {
@@ -53984,6 +55304,8 @@ async function main(argv) {
53984
55304
  return cmdPack(p);
53985
55305
  case "orchestrate":
53986
55306
  return cmdOrchestrate(p);
55307
+ case "mcp":
55308
+ return cmdMcp(p);
53987
55309
  default:
53988
55310
  console.error(`ultra11y: "${p.command}" is not implemented yet`);
53989
55311
  return 1;
@@ -53992,9 +55314,9 @@ async function main(argv) {
53992
55314
  function isInvokedDirectly() {
53993
55315
  const argv1 = process.argv[1];
53994
55316
  if (argv1 === void 0) return false;
53995
- const modulePath = fileURLToPath4(import.meta.url);
55317
+ const modulePath = fileURLToPath5(import.meta.url);
53996
55318
  try {
53997
- if (realpathSync2(argv1) === realpathSync2(modulePath)) return true;
55319
+ if (realpathSync4(argv1) === realpathSync4(modulePath)) return true;
53998
55320
  } catch {
53999
55321
  }
54000
55322
  return import.meta.url === pathToFileURL3(argv1).href;