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.
- package/README.md +108 -6
- package/package.json +1 -1
- package/scripts/ultra11y.mjs +1490 -168
- package/skills/review-a11y/SKILL.md +1 -1
- package/skills/review-a11y/scripts/ultra11y.mjs +1490 -168
- package/skills/ultra11y/SKILL.md +1 -1
- package/skills/ultra11y/scripts/ultra11y.mjs +1490 -168
- package/src/cli.ts +95 -1
- package/src/mcp/handlers.ts +388 -0
- package/src/mcp/http.ts +260 -0
- package/src/mcp/prompts.ts +183 -0
- package/src/mcp/protocol.ts +187 -0
- package/src/mcp/resources.ts +136 -0
- package/src/mcp/server.ts +228 -0
- package/src/mcp/stdio.ts +119 -0
- package/src/mcp/tools.ts +347 -0
- package/src/project-lock.ts +42 -0
- package/src/types.ts +1 -1
package/scripts/ultra11y.mjs
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
|
-
import { realpathSync as
|
|
5
|
-
import { join as
|
|
6
|
-
import { fileURLToPath as
|
|
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.
|
|
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
|
|
10486
|
+
const str4 = this.input.slice(this.state.pos, this.state.pos + size);
|
|
10487
10487
|
this.state.pos += size;
|
|
10488
|
-
this.finishToken(type,
|
|
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
|
|
10581
|
-
this.finishToken(132,
|
|
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
|
|
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,
|
|
10637
|
+
this.finishToken(132, str4);
|
|
10638
10638
|
return;
|
|
10639
10639
|
}
|
|
10640
|
-
const val = isOctal ? parseInt(
|
|
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:
|
|
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,
|
|
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:
|
|
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 +
|
|
10686
|
+
this.finishToken(20, firstInvalidLoc ? null : opening2 + str4 + "`");
|
|
10687
10687
|
} else {
|
|
10688
10688
|
this.state.pos++;
|
|
10689
|
-
this.finishToken(21, firstInvalidLoc ? null : opening2 +
|
|
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
|
|
19474
|
-
if (
|
|
19475
|
-
const toks =
|
|
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
|
|
20907
|
+
var str22 = "";
|
|
20908
20908
|
while (idx < endPtr) {
|
|
20909
20909
|
var u0 = heapOrArray[idx++];
|
|
20910
20910
|
if (!(u0 & 128)) {
|
|
20911
|
-
|
|
20911
|
+
str22 += String.fromCharCode(u0);
|
|
20912
20912
|
continue;
|
|
20913
20913
|
}
|
|
20914
20914
|
var u1 = heapOrArray[idx++] & 63;
|
|
20915
20915
|
if ((u0 & 224) == 192) {
|
|
20916
|
-
|
|
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
|
-
|
|
20926
|
+
str22 += String.fromCharCode(u0);
|
|
20927
20927
|
} else {
|
|
20928
20928
|
var ch = u0 - 65536;
|
|
20929
|
-
|
|
20929
|
+
str22 += String.fromCharCode(55296 | ch >> 10, 56320 | ch & 1023);
|
|
20930
20930
|
}
|
|
20931
20931
|
}
|
|
20932
|
-
return
|
|
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((
|
|
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
|
|
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
|
-
|
|
21724
|
+
num22 += len;
|
|
21725
21725
|
}
|
|
21726
|
-
LE_HEAP_STORE_U32((pnum >> 2) * 4,
|
|
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((
|
|
21791
|
+
var lengthBytesUTF8 = /* @__PURE__ */ __name((str22) => {
|
|
21792
21792
|
var len = 0;
|
|
21793
|
-
for (var i2 = 0; i2 <
|
|
21794
|
-
var c2 =
|
|
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((
|
|
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 <
|
|
21813
|
-
var u =
|
|
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((
|
|
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((
|
|
21841
|
-
var size = lengthBytesUTF8(
|
|
21840
|
+
var stringToUTF8OnStack = /* @__PURE__ */ __name((str22) => {
|
|
21841
|
+
var size = lengthBytesUTF8(str22) + 1;
|
|
21842
21842
|
var ret = stackAlloc(size);
|
|
21843
|
-
stringToUTF8(
|
|
21843
|
+
stringToUTF8(str22, ret, size);
|
|
21844
21844
|
return ret;
|
|
21845
21845
|
}, "stringToUTF8OnStack");
|
|
21846
21846
|
var AsciiToString = /* @__PURE__ */ __name((ptr) => {
|
|
21847
|
-
var
|
|
21847
|
+
var str22 = "";
|
|
21848
21848
|
while (1) {
|
|
21849
21849
|
var ch = HEAPU8[ptr++];
|
|
21850
|
-
if (!ch) return
|
|
21851
|
-
|
|
21850
|
+
if (!ch) return str22;
|
|
21851
|
+
str22 += String.fromCharCode(ch);
|
|
21852
21852
|
}
|
|
21853
21853
|
}, "AsciiToString");
|
|
21854
|
-
var stringToUTF16 = /* @__PURE__ */ __name((
|
|
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 <
|
|
21859
|
+
var numCharsToWrite = maxBytesToWrite < str22.length * 2 ? maxBytesToWrite / 2 : str22.length;
|
|
21860
21860
|
for (var i2 = 0; i2 < numCharsToWrite; ++i2) {
|
|
21861
|
-
var codeUnit =
|
|
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,
|
|
22204
|
-
return
|
|
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,
|
|
22249
|
-
return
|
|
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
|
|
24662
|
-
if (
|
|
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
|
|
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(
|
|
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
|
|
28794
|
+
const header3 = JSON.parse(buf.toString("utf8", 8, 8 + headerLen));
|
|
28795
28795
|
const bodyOff = 8 + headerLen;
|
|
28796
|
-
const { dim } =
|
|
28797
|
-
const records =
|
|
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:
|
|
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
|
|
29191
|
+
const header3 = `# repo map \u2014 ${graph.fileCount} files
|
|
29192
29192
|
`;
|
|
29193
|
-
let out2 =
|
|
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
|
|
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 =
|
|
31828
|
-
else if (a === "--max-bytes") flags2.maxBytes =
|
|
31829
|
-
else if (a === "--max-calls") flags2.maxCalls =
|
|
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 =
|
|
31832
|
-
else if (a === "--budget-tokens") flags2.budgetTokens =
|
|
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 =
|
|
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 =
|
|
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
|
|
42580
|
+
const header3 = buf.toString("utf8", pos, nl);
|
|
42581
42581
|
pos = nl + 1;
|
|
42582
|
-
const m = /^[0-9a-f]{40,64} \w+ (\d+)$/.exec(
|
|
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(
|
|
42980
|
+
function derivePackResults(audit2, packKey) {
|
|
42981
42981
|
const pack = loadPack(packKey);
|
|
42982
|
-
const byScId = new Map(
|
|
42983
|
-
const myPackFindings = (
|
|
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 ? [...
|
|
43013
|
-
const adjudicated =
|
|
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(
|
|
43032
|
-
if (standard === CORE_KEY) return
|
|
43033
|
-
const mine = (
|
|
43034
|
-
return mine.length ? [...
|
|
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
|
|
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 =
|
|
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(
|
|
48516
|
-
const inputs =
|
|
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(
|
|
48558
|
-
const docs = docsForAudit(
|
|
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(
|
|
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
|
|
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(
|
|
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(
|
|
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
|
|
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(
|
|
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(
|
|
49547
|
-
const merged = JSON.parse(JSON.stringify(
|
|
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
|
|
50375
|
+
const header3 = `--- a/${file}
|
|
50376
50376
|
+++ b/${file}`;
|
|
50377
|
-
if (a.length > 4e3 || b.length > 4e3) return `${
|
|
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 = [
|
|
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((
|
|
51321
|
+
return new Promise((resolve10, reject) => {
|
|
51322
51322
|
server.once("error", reject);
|
|
51323
51323
|
server.listen(opts.port, "127.0.0.1", () => {
|
|
51324
|
-
|
|
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 ||
|
|
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("-") &&
|
|
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 :
|
|
54033
|
+
const target = asFile ? out2 : join38(out2, "audit-latest.json");
|
|
52766
54034
|
try {
|
|
52767
|
-
mkdirSync11(asFile ?
|
|
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 (
|
|
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 =
|
|
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(
|
|
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((
|
|
54144
|
+
await new Promise((resolve10) => {
|
|
52877
54145
|
const stop2 = () => {
|
|
52878
|
-
void server.close().then(
|
|
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 ${
|
|
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(
|
|
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: [
|
|
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 =
|
|
53003
|
-
engineRel = abs.startsWith(root +
|
|
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(
|
|
53021
|
-
const bp =
|
|
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 =
|
|
53160
|
-
if (!
|
|
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 =
|
|
53172
|
-
ref = abs.startsWith(root +
|
|
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) =>
|
|
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 =
|
|
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(
|
|
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(
|
|
53203
|
-
writeFileSync14(
|
|
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 =
|
|
54500
|
+
const out2 = join38(root, rel);
|
|
53233
54501
|
try {
|
|
53234
|
-
mkdirSync11(
|
|
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 =
|
|
53242
|
-
if (
|
|
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) =>
|
|
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 =
|
|
54520
|
+
const gaPath = join38(root, ".gitattributes");
|
|
53253
54521
|
try {
|
|
53254
|
-
const existing =
|
|
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 =
|
|
53263
|
-
if (
|
|
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 =
|
|
53274
|
-
if (!
|
|
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 =
|
|
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(
|
|
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 ??
|
|
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 =
|
|
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 =
|
|
53342
|
-
if (
|
|
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) =>
|
|
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
|
|
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
|
-
|
|
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
|
|
54744
|
+
let audit2;
|
|
53477
54745
|
try {
|
|
53478
|
-
|
|
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(
|
|
53484
|
-
const w = writeAdjudication(adjItems, out2, { standard, auditDate:
|
|
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 (${
|
|
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
|
|
54799
|
+
let audit2;
|
|
53532
54800
|
try {
|
|
53533
|
-
|
|
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(
|
|
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 =
|
|
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) ||
|
|
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
|
|
54995
|
+
let audit2;
|
|
53728
54996
|
if (mergeAudit) {
|
|
53729
|
-
|
|
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
|
-
|
|
55010
|
+
audit2 = parsed;
|
|
53743
55011
|
}
|
|
53744
|
-
const merged = mergeDynamic(
|
|
55012
|
+
const merged = mergeDynamic(audit2, dynamic, lang);
|
|
53745
55013
|
mkdirSync11(out2, { recursive: true });
|
|
53746
|
-
writeFileSync14(
|
|
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 ${
|
|
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 =
|
|
55196
|
+
const engineAbs = realpathSync4(fileURLToPath5(import.meta.url));
|
|
53877
55197
|
if (p.flags.list === true) {
|
|
53878
|
-
if (!
|
|
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 ${
|
|
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 =
|
|
55317
|
+
const modulePath = fileURLToPath5(import.meta.url);
|
|
53996
55318
|
try {
|
|
53997
|
-
if (
|
|
55319
|
+
if (realpathSync4(argv1) === realpathSync4(modulePath)) return true;
|
|
53998
55320
|
} catch {
|
|
53999
55321
|
}
|
|
54000
55322
|
return import.meta.url === pathToFileURL3(argv1).href;
|