ultra11y 2.25.0 → 2.27.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/.claude-plugin/marketplace.json +22 -0
- package/.claude-plugin/plugin.json +9 -0
- package/README.md +45 -0
- package/hooks/hooks.json +16 -0
- package/hooks/pre-tool-use.mjs +47 -0
- package/package.json +4 -2
- package/scripts/sync-version.mjs +11 -1
- package/scripts/ultra11y.mjs +845 -653
- package/scripts/vendor/wcag-2.2-fr.json +1 -1
- package/scripts/vendor/wcag-2.2-sc-universe.json +1 -1
- package/scripts/verify-skill-bundle.mjs +104 -7
- package/skills/review-a11y/SKILL.md +9 -3
- package/skills/review-a11y/scripts/ultra11y.mjs +845 -653
- package/skills/ultra11y/SKILL.md +5 -2
- package/skills/ultra11y/references/automation.md +37 -0
- package/skills/ultra11y/scripts/ultra11y.mjs +845 -653
- package/src/cli.ts +31 -0
- package/src/config.ts +4 -0
- package/src/data/wcag-universe.json +1 -1
- package/src/hook.ts +0 -0
- package/src/types.ts +1 -1
- package/src/vendor/codeindex-engine.d.mts +3 -3
- package/src/vendor/codeindex-engine.mjs +23 -3
- package/src/vendor/engine.meta.json +5 -5
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 realpathSync4, writeFileSync as
|
|
5
|
-
import { join as
|
|
4
|
+
import { realpathSync as realpathSync4, writeFileSync as writeFileSync15, mkdirSync as mkdirSync12, existsSync as existsSync24, readFileSync as readFileSync20, appendFileSync } from "fs";
|
|
5
|
+
import { join as join40, relative as relative3, sep as sep6, dirname as dirname11 } from "path";
|
|
6
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.27.0";
|
|
10
10
|
var SCHEMA_VERSION = 2;
|
|
11
11
|
|
|
12
12
|
// src/audit.ts
|
|
@@ -1197,7 +1197,7 @@ var wcag_universe_default = {
|
|
|
1197
1197
|
wcagVersion: "2.2",
|
|
1198
1198
|
source: "https://www.w3.org/TR/WCAG22/",
|
|
1199
1199
|
criteriaSource: "https://github.com/w3c/wcag",
|
|
1200
|
-
provenance: "Full WCAG 2.x SC universe (all levels incl. AAA, and the removed 4.1.1 Parsing) fetched from raw.githubusercontent.com/w3c/wcag@main on 2026-
|
|
1200
|
+
provenance: "Full WCAG 2.x SC universe (all levels incl. AAA, and the removed 4.1.1 Parsing) fetched from raw.githubusercontent.com/w3c/wcag@main on 2026-08-01 via `node scripts/build-standards.mjs --refresh-universe`. Classification: core-AA = ships in src/data/wcag.json (the shipped WCAG 2.2 AA core); out-of-core = WCAG AAA; removed = obsolete (4.1.1).",
|
|
1201
1201
|
criteria: [
|
|
1202
1202
|
{
|
|
1203
1203
|
id: "1.1.1",
|
|
@@ -18887,9 +18887,9 @@ var EXTRACTOR_VERSION;
|
|
|
18887
18887
|
var init_types = __esm({
|
|
18888
18888
|
"src/types.ts"() {
|
|
18889
18889
|
"use strict";
|
|
18890
|
-
ENGINE_VERSION = "2.
|
|
18890
|
+
ENGINE_VERSION = "2.24.2";
|
|
18891
18891
|
SCHEMA_VERSION2 = 5;
|
|
18892
|
-
EXTRACTOR_VERSION =
|
|
18892
|
+
EXTRACTOR_VERSION = 12;
|
|
18893
18893
|
}
|
|
18894
18894
|
});
|
|
18895
18895
|
function sh(cmd, args2, opts = {}) {
|
|
@@ -19861,6 +19861,18 @@ var init_js_ts = __esm({
|
|
|
19861
19861
|
{ re: /^\s*module\.exports\.(?<name>[\w$]+)\s*=/, kind: "const", exported: true },
|
|
19862
19862
|
// top-level const arrow function (not exported)
|
|
19863
19863
|
{ re: /^\s*(?:const|let)\s+(?<name>[\w$]+)\s*=\s*(?:async\s*)?\([^)]*\)\s*(?::[^=]+)?=>/, kind: "const", exported: false },
|
|
19864
|
+
// A module constant bound to a VALUE, not exported: `const RE = /href=/g`.
|
|
19865
|
+
// The AST tier indexes these (a top-level binding is a declaration, only an
|
|
19866
|
+
// in-function one is a local), and a fallback tier that answers "no such
|
|
19867
|
+
// symbol" for every module constant diverges from the engine it stands in for.
|
|
19868
|
+
//
|
|
19869
|
+
// Anchored at column 0, unlike every rule above it: a line scanner cannot see
|
|
19870
|
+
// scope, so `^\s*` here would sweep in each `const x = 1` inside every function
|
|
19871
|
+
// body — the locals-flood the AST tier's inFunctionBody filter exists to
|
|
19872
|
+
// prevent, and the surplus this project criticises in a flat tags file. Column
|
|
19873
|
+
// 0 is the one module-scope proxy a line can carry. Must stay AFTER the arrow
|
|
19874
|
+
// and export rules: `scan()` keeps the first rule that matches a line.
|
|
19875
|
+
{ re: /^(?:const|let|var)\s+(?<name>[\w$]+)\s*[:=]/, kind: "const", exported: false },
|
|
19864
19876
|
// `export default Foo;` — a class/const declared above and exported by reference.
|
|
19865
19877
|
{ re: /^\s*export\s+default\s+(?<name>[A-Za-z_$][\w$]*)\s*;?\s*$/, kind: "default", exported: true }
|
|
19866
19878
|
];
|
|
@@ -24974,7 +24986,15 @@ var init_specs = __esm({
|
|
|
24974
24986
|
"arguments",
|
|
24975
24987
|
"arrow_function",
|
|
24976
24988
|
"function_expression",
|
|
24977
|
-
"function"
|
|
24989
|
+
"function",
|
|
24990
|
+
// The IIFE's own link: `(function () { … })()` parses as
|
|
24991
|
+
// expression_statement → call_expression → parenthesized_expression →
|
|
24992
|
+
// function_expression, and without the parens listed the descent stopped one
|
|
24993
|
+
// node short — the same break the statement containers above were added for.
|
|
24994
|
+
// Whole categories of file are written inside one (browser scripts, UMD and
|
|
24995
|
+
// loader bundles), so for those the index held no functions at all. Named by
|
|
24996
|
+
// the ctags differential on a real loader script.
|
|
24997
|
+
"parenthesized_expression"
|
|
24978
24998
|
]),
|
|
24979
24999
|
exported: neverExport,
|
|
24980
25000
|
// export is tracked structurally; see LangSpec.exportMarkers
|
|
@@ -44894,13 +44914,60 @@ function runAudit(opts) {
|
|
|
44894
44914
|
return result;
|
|
44895
44915
|
}
|
|
44896
44916
|
|
|
44897
|
-
// src/
|
|
44898
|
-
import {
|
|
44917
|
+
// src/hook.ts
|
|
44918
|
+
import { execFileSync as execFileSync2 } from "child_process";
|
|
44919
|
+
import { createHash as createHash5 } from "crypto";
|
|
44920
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync5, writeFileSync as writeFileSync6 } from "fs";
|
|
44921
|
+
import { tmpdir } from "os";
|
|
44899
44922
|
import { join as join27 } from "path";
|
|
44900
44923
|
|
|
44901
|
-
// src/
|
|
44902
|
-
|
|
44903
|
-
|
|
44924
|
+
// src/baseline.ts
|
|
44925
|
+
var RANK = { bloquant: 0, majeur: 1, mineur: 2 };
|
|
44926
|
+
function findingId(f) {
|
|
44927
|
+
const pos = f.sourceStart !== void 0 ? `b${f.sourceStart}-${f.sourceEnd}` : `l${f.line}:${f.col}:${f.selectorHint}`;
|
|
44928
|
+
return `${f.ruleId}|${f.criteriaId}|${f.file}|${pos}`;
|
|
44929
|
+
}
|
|
44930
|
+
function parseFailOn(v) {
|
|
44931
|
+
if (v === void 0 || v === true) return "bloquant";
|
|
44932
|
+
if (v === "bloquant" || v === "blocking") return "bloquant";
|
|
44933
|
+
if (v === "majeur" || v === "major") return "majeur";
|
|
44934
|
+
if (v === "mineur" || v === "minor") return "mineur";
|
|
44935
|
+
return null;
|
|
44936
|
+
}
|
|
44937
|
+
function findingsAtOrAbove(findings, failOn) {
|
|
44938
|
+
return findings.filter((f) => RANK[f.severity] <= RANK[failOn]);
|
|
44939
|
+
}
|
|
44940
|
+
function diffAgainstBaseline(current, baseline, failOn = "bloquant") {
|
|
44941
|
+
const baseIds = new Set((baseline?.findings ?? []).map(findingId));
|
|
44942
|
+
const curIds = new Set(current.findings.map(findingId));
|
|
44943
|
+
const newFindings = current.findings.filter((f) => !baseIds.has(findingId(f)));
|
|
44944
|
+
const auditedFiles = new Set(current.findings.map((f) => f.file));
|
|
44945
|
+
const fixedFindings = (baseline?.findings ?? []).filter((f) => auditedFiles.has(f.file) && !curIds.has(findingId(f)));
|
|
44946
|
+
const ok = !newFindings.some((f) => RANK[f.severity] <= RANK[failOn]);
|
|
44947
|
+
return { newFindings, fixedFindings, ok, failOn };
|
|
44948
|
+
}
|
|
44949
|
+
function baselineSummary(diff, lang = "fr") {
|
|
44950
|
+
const out2 = [];
|
|
44951
|
+
const blocking = diff.newFindings.filter((f) => RANK[f.severity] <= RANK[diff.failOn]);
|
|
44952
|
+
if (diff.ok) {
|
|
44953
|
+
out2.push(
|
|
44954
|
+
lang === "fr" ? `\u2713 Aucune nouvelle non-conformit\xE9 \u2265 ${diff.failOn} (${diff.newFindings.length} nouvelle(s) au total, ${diff.fixedFindings.length} corrig\xE9e(s)).` : `\u2713 No new non-conformity \u2265 ${diff.failOn} (${diff.newFindings.length} new total, ${diff.fixedFindings.length} fixed).`
|
|
44955
|
+
);
|
|
44956
|
+
} else {
|
|
44957
|
+
out2.push(
|
|
44958
|
+
lang === "fr" ? `\u2717 ${blocking.length} nouvelle(s) non-conformit\xE9(s) \u2265 ${diff.failOn} introduite(s) :` : `\u2717 ${blocking.length} new non-conformity(ies) \u2265 ${diff.failOn} introduced:`
|
|
44959
|
+
);
|
|
44960
|
+
for (const f of blocking) out2.push(` [${f.severity}] ${f.ruleId} (WCAG ${f.criteriaId}) \u2014 ${f.file}:${f.line} (${f.selectorHint})`);
|
|
44961
|
+
}
|
|
44962
|
+
return out2.join("\n");
|
|
44963
|
+
}
|
|
44964
|
+
|
|
44965
|
+
// src/config.ts
|
|
44966
|
+
import { existsSync as existsSync14, statSync as statSync7 } from "fs";
|
|
44967
|
+
import { join as join26, isAbsolute as isAbsolute2 } from "path";
|
|
44968
|
+
|
|
44969
|
+
// src/pack.ts
|
|
44970
|
+
import { existsSync as existsSync13 } from "fs";
|
|
44904
44971
|
|
|
44905
44972
|
// src/standards/pack.ts
|
|
44906
44973
|
function localize(pack, s, lang) {
|
|
@@ -44946,167 +45013,150 @@ function idCaptureSource(pack) {
|
|
|
44946
45013
|
return neutralizeCaptureGroups(pack.idPattern.replace(/^\^/, "").replace(/\$$/, ""));
|
|
44947
45014
|
}
|
|
44948
45015
|
|
|
44949
|
-
// src/
|
|
44950
|
-
function
|
|
44951
|
-
|
|
44952
|
-
|
|
44953
|
-
|
|
44954
|
-
|
|
44955
|
-
}
|
|
44956
|
-
|
|
44957
|
-
|
|
44958
|
-
|
|
44959
|
-
|
|
44960
|
-
|
|
44961
|
-
}
|
|
45016
|
+
// src/pack.ts
|
|
45017
|
+
function exampleParses(ex) {
|
|
45018
|
+
for (const code2 of [ex.bad, ex.good]) {
|
|
45019
|
+
if (!code2) continue;
|
|
45020
|
+
if (ex.lang === "jsx") {
|
|
45021
|
+
if (!parseJsxAst(code2)) return `unparseable JSX example: ${code2.slice(0, 48)}\u2026`;
|
|
45022
|
+
} else if (ex.lang === "html") {
|
|
45023
|
+
try {
|
|
45024
|
+
parseHtml(code2, "<guidance>.html", false);
|
|
45025
|
+
} catch (e) {
|
|
45026
|
+
return `unparseable HTML example (${e instanceof Error ? e.message : String(e)})`;
|
|
45027
|
+
}
|
|
45028
|
+
}
|
|
44962
45029
|
}
|
|
44963
|
-
return
|
|
44964
|
-
}
|
|
44965
|
-
function packCriteriaForFinding(pack, finding) {
|
|
44966
|
-
return pack.criteria.filter((pc) => pc.wcag.includes(finding.criteriaId) && (!pc.appliesTo || ruleMatches(finding.ruleId, pc.appliesTo.ruleIds))).map((pc) => pc.id);
|
|
44967
|
-
}
|
|
44968
|
-
function packConformancePct(derived) {
|
|
44969
|
-
const c2 = derived.filter((d) => d.status === "C").length;
|
|
44970
|
-
const nc = derived.filter((d) => d.status === "NC").length;
|
|
44971
|
-
return c2 + nc === 0 ? 100 : Math.round(c2 / (c2 + nc) * 100);
|
|
44972
|
-
}
|
|
44973
|
-
function overrideFinding(f, overrides) {
|
|
44974
|
-
const o = overrides?.[f.ruleId];
|
|
44975
|
-
if (!o) return f;
|
|
44976
|
-
const patched = { ...f };
|
|
44977
|
-
if (o.advisory !== void 0) patched.advisory = o.advisory;
|
|
44978
|
-
if (o.severity) patched.severity = o.severity;
|
|
44979
|
-
return patched;
|
|
44980
|
-
}
|
|
44981
|
-
function pickLocale(ls, preferred) {
|
|
44982
|
-
if (!ls) return void 0;
|
|
44983
|
-
return ls[preferred] ?? ls.en ?? ls.fr ?? Object.values(ls).find((v) => typeof v === "string");
|
|
44984
|
-
}
|
|
44985
|
-
function tagSecondary(f, note) {
|
|
44986
|
-
return { ...f, secondary: note ? { note } : {} };
|
|
44987
|
-
}
|
|
44988
|
-
function applySecondaryMappings(base, pc, enabled, sources, defaultLocale) {
|
|
44989
|
-
const active = enabled.filter((m) => m.criterion === pc.id);
|
|
44990
|
-
if (!active.length) return base;
|
|
44991
|
-
const added = [];
|
|
44992
|
-
for (const m of active) for (const f of sources) if (f.ruleId === m.ruleId) added.push(tagSecondary(f, pickLocale(m.note, defaultLocale)));
|
|
44993
|
-
if (!added.length) return base;
|
|
44994
|
-
const findings = [...base.findings, ...added];
|
|
44995
|
-
if (added.some((f) => !f.advisory)) return { id: pc.id, theme: pc.theme, status: aggregate2([base.status, "NC"]), findings, scs: base.scs };
|
|
44996
|
-
return { ...base, findings };
|
|
45030
|
+
return null;
|
|
44997
45031
|
}
|
|
44998
|
-
function
|
|
44999
|
-
const
|
|
45000
|
-
const
|
|
45001
|
-
|
|
45002
|
-
|
|
45003
|
-
|
|
45004
|
-
|
|
45005
|
-
|
|
45006
|
-
|
|
45007
|
-
|
|
45008
|
-
|
|
45009
|
-
|
|
45010
|
-
|
|
45011
|
-
|
|
45012
|
-
|
|
45013
|
-
|
|
45014
|
-
if (!pc.appliesTo) {
|
|
45015
|
-
const status2 = scResults.length ? aggregate2(scResults.map((r) => r.status)) : "NA";
|
|
45016
|
-
return { id: pc.id, theme: pc.theme, status: status2, findings: allFindings, scs: pc.wcag };
|
|
45017
|
-
}
|
|
45018
|
-
const findings = allFindings.filter((f) => ruleMatches(f.ruleId, pc.appliesTo.ruleIds));
|
|
45019
|
-
const normativeFindings = findings.filter((f) => !f.advisory);
|
|
45020
|
-
if (normativeFindings.length) {
|
|
45021
|
-
return { id: pc.id, theme: pc.theme, status: "NC", findings, scs: pc.wcag };
|
|
45032
|
+
function checkGuidance(ds, pack) {
|
|
45033
|
+
const errors = [];
|
|
45034
|
+
const warnings = [];
|
|
45035
|
+
if (typeof ds !== "object" || ds === null || !Array.isArray(ds.entries)) {
|
|
45036
|
+
errors.push("guidance: must be an object with an entries[] array");
|
|
45037
|
+
return { errors, warnings };
|
|
45038
|
+
}
|
|
45039
|
+
const dataset = ds;
|
|
45040
|
+
if (dataset.pack && dataset.pack !== pack.key) warnings.push(`guidance: dataset.pack "${dataset.pack}" \u2260 pack key "${pack.key}"`);
|
|
45041
|
+
dataset.entries.forEach((e, i2) => {
|
|
45042
|
+
const at = `guidance.entries[${i2}]${e.id ? ` (${e.id})` : ""}`;
|
|
45043
|
+
const crit = e.criterionId ? getCriterion(pack, e.criterionId) : void 0;
|
|
45044
|
+
if (!e.criterionId) {
|
|
45045
|
+
errors.push(`${at}: criterionId is unresolved \u2014 resolve it to a real ${pack.key} criterion (no fabrication)`);
|
|
45046
|
+
} else if (!crit) {
|
|
45047
|
+
errors.push(`${at}: criterionId "${e.criterionId}" does not exist in ${pack.key}`);
|
|
45022
45048
|
}
|
|
45023
|
-
if (
|
|
45024
|
-
|
|
45049
|
+
if (!Array.isArray(e.wcag) || e.wcag.length === 0) {
|
|
45050
|
+
errors.push(`${at}: must map to at least one WCAG SC`);
|
|
45051
|
+
} else {
|
|
45052
|
+
for (const sc of e.wcag) {
|
|
45053
|
+
switch (classifySc(sc)) {
|
|
45054
|
+
case "malformed":
|
|
45055
|
+
errors.push(`${at}: malformed SC "${sc}"`);
|
|
45056
|
+
break;
|
|
45057
|
+
case "unknown":
|
|
45058
|
+
errors.push(`${at}: SC "${sc}" is not a recognized WCAG success criterion \u2014 fabricated?`);
|
|
45059
|
+
break;
|
|
45060
|
+
case "out-of-core":
|
|
45061
|
+
warnings.push(`${at}: SC "${sc}" is a real WCAG AAA success criterion, outside the WCAG 2.2 AA core`);
|
|
45062
|
+
break;
|
|
45063
|
+
case "removed":
|
|
45064
|
+
warnings.push(`${at}: SC "${sc}" is a real but removed WCAG success criterion (obsolete)`);
|
|
45065
|
+
break;
|
|
45066
|
+
}
|
|
45067
|
+
}
|
|
45068
|
+
if (crit) {
|
|
45069
|
+
const extra = e.wcag.filter((sc) => !crit.wcag.includes(sc));
|
|
45070
|
+
if (extra.length) warnings.push(`${at}: SC(s) ${extra.join(", ")} not in criterion ${crit.id} mapping (${crit.wcag.join(", ")})`);
|
|
45071
|
+
}
|
|
45025
45072
|
}
|
|
45026
|
-
|
|
45027
|
-
|
|
45028
|
-
|
|
45029
|
-
|
|
45030
|
-
|
|
45031
|
-
|
|
45032
|
-
|
|
45033
|
-
const decided = adjudicated?.get(pc.id);
|
|
45034
|
-
if (decided) {
|
|
45035
|
-
return {
|
|
45036
|
-
id: pc.id,
|
|
45037
|
-
theme: pc.theme,
|
|
45038
|
-
status: decided.status,
|
|
45039
|
-
findings: decided.findings,
|
|
45040
|
-
scs: pc.wcag,
|
|
45041
|
-
...decided.justification ? { justification: decided.justification } : {},
|
|
45042
|
-
decidedBy: "agent"
|
|
45043
|
-
};
|
|
45073
|
+
if (!Array.isArray(e.examples) || e.examples.length === 0) {
|
|
45074
|
+
warnings.push(`${at}: no code examples`);
|
|
45075
|
+
} else {
|
|
45076
|
+
e.examples.forEach((ex, j) => {
|
|
45077
|
+
const bad = exampleParses(ex);
|
|
45078
|
+
if (bad) errors.push(`${at}.examples[${j}]: ${bad}`);
|
|
45079
|
+
});
|
|
45044
45080
|
}
|
|
45045
|
-
const base = deriveBase(pc);
|
|
45046
|
-
return enabledSecondary.length ? applySecondaryMappings(base, pc, enabledSecondary, secondarySources, pack.defaultLocale) : base;
|
|
45047
45081
|
});
|
|
45082
|
+
return { errors, warnings };
|
|
45048
45083
|
}
|
|
45049
|
-
function
|
|
45050
|
-
|
|
45051
|
-
const
|
|
45052
|
-
return
|
|
45053
|
-
|
|
45054
|
-
|
|
45055
|
-
|
|
45056
|
-
|
|
45057
|
-
|
|
45058
|
-
criterion: { en: "Criterion", fr: "Crit\xE8re" },
|
|
45059
|
-
test: { en: "Test", fr: "Test" },
|
|
45060
|
-
conformant: { en: "Conformant", fr: "Conforme" },
|
|
45061
|
-
nonConformant: { en: "Non-conformant", fr: "Non conforme" },
|
|
45062
|
-
notApplicable: { en: "Not applicable", fr: "Non applicable" },
|
|
45063
|
-
auditorHeading: { en: "Accessibility criterion", fr: "Crit\xE8re d'accessibilit\xE9" }
|
|
45064
|
-
};
|
|
45065
|
-
var CORE = {
|
|
45066
|
-
theme: { en: "Principle \xB7 Guideline", fr: "Principe \xB7 R\xE8gle" },
|
|
45067
|
-
criterion: { en: "Success criterion", fr: "Crit\xE8re de succ\xE8s" },
|
|
45068
|
-
test: { en: "Technique", fr: "Technique" },
|
|
45069
|
-
conformant: { en: "Pass", fr: "Conforme" },
|
|
45070
|
-
nonConformant: { en: "Fail", fr: "Non conforme" },
|
|
45071
|
-
notApplicable: { en: "Not applicable", fr: "Non applicable" },
|
|
45072
|
-
auditorHeading: { en: "Accessibility criterion", fr: "Crit\xE8re d'accessibilit\xE9" }
|
|
45073
|
-
};
|
|
45074
|
-
function pick(s, lang, fallbackLang) {
|
|
45075
|
-
if (!s) return void 0;
|
|
45076
|
-
return s[lang] ?? s[fallbackLang] ?? s.en ?? Object.values(s)[0];
|
|
45077
|
-
}
|
|
45078
|
-
function vocabularyFor(standard, lang) {
|
|
45079
|
-
const base = isCore(standard) ? CORE : DEFAULT;
|
|
45080
|
-
const pack = isCore(standard) ? void 0 : getPack(standard);
|
|
45081
|
-
const voc = pack?.vocabulary;
|
|
45082
|
-
const packDefault = pack?.defaultLocale ?? "en";
|
|
45083
|
-
const term = (k) => pick(voc?.[k], lang, packDefault) ?? base[k][lang] ?? base[k].en;
|
|
45084
|
-
return {
|
|
45085
|
-
theme: term("theme"),
|
|
45086
|
-
criterion: term("criterion"),
|
|
45087
|
-
test: term("test"),
|
|
45088
|
-
conformant: term("conformant"),
|
|
45089
|
-
nonConformant: term("nonConformant"),
|
|
45090
|
-
notApplicable: term("notApplicable"),
|
|
45091
|
-
auditorHeading: term("auditorHeading"),
|
|
45092
|
-
normativeNote: pick(voc?.normativeNote, lang, packDefault)
|
|
45093
|
-
};
|
|
45094
|
-
}
|
|
45095
|
-
|
|
45096
|
-
// src/standards/index.ts
|
|
45097
|
-
var CORE2 = CORE_KEY;
|
|
45098
|
-
function resolveStandard(flag) {
|
|
45099
|
-
if (flag === void 0 || flag === true || flag === "") return CORE_KEY;
|
|
45100
|
-
const key = String(flag).toLowerCase();
|
|
45101
|
-
if (!hasStandard(key)) {
|
|
45102
|
-
throw new Error(`unknown standard "${key}" (known: ${listStandards().join(", ")})`);
|
|
45084
|
+
function runPackCheck(packPath, guidancePath) {
|
|
45085
|
+
const errors = [];
|
|
45086
|
+
const warnings = [];
|
|
45087
|
+
if (!existsSync13(packPath)) return { ok: false, errors: [`pack file not found: ${packPath}`], warnings };
|
|
45088
|
+
let raw;
|
|
45089
|
+
try {
|
|
45090
|
+
raw = JSON.parse(readText(packPath));
|
|
45091
|
+
} catch {
|
|
45092
|
+
return { ok: false, errors: [`${packPath}: not valid JSON`], warnings };
|
|
45103
45093
|
}
|
|
45104
|
-
|
|
45094
|
+
const v = validatePack(raw);
|
|
45095
|
+
for (const issue of v.issues) (issue.severity === "error" ? errors : warnings).push(`${issue.path ? `${issue.path}: ` : ""}${issue.message}`);
|
|
45096
|
+
if (v.ok && v.pack) {
|
|
45097
|
+
const known = new Set(ruleIds());
|
|
45098
|
+
const isNamespaced = (r) => /^(axe:|dyn-|agent:)/.test(r);
|
|
45099
|
+
const ownRuleIds = new Set((v.pack.rules ?? []).map((r) => `pack:${v.pack.key}:${r.id}`));
|
|
45100
|
+
const resolvable = (r) => isNamespaced(r) || known.has(r) || r.startsWith(`pack:${v.pack.key}:`) || ownRuleIds.has(r);
|
|
45101
|
+
for (const c2 of v.pack.criteria) {
|
|
45102
|
+
for (const r of c2.appliesTo?.ruleIds ?? []) {
|
|
45103
|
+
if (!resolvable(r)) warnings.push(`criteria "${c2.id}": appliesTo ruleId "${r}" is not a known engine rule (renamed/future?)`);
|
|
45104
|
+
}
|
|
45105
|
+
}
|
|
45106
|
+
for (const key of Object.keys(v.pack.overrides ?? {})) {
|
|
45107
|
+
if (!resolvable(key)) warnings.push(`overrides: ruleId "${key}" is not a known engine rule or a rule of this pack (renamed/future?)`);
|
|
45108
|
+
}
|
|
45109
|
+
}
|
|
45110
|
+
if (v.ok && v.pack && guidancePath) {
|
|
45111
|
+
if (!existsSync13(guidancePath)) {
|
|
45112
|
+
errors.push(`guidance file not found: ${guidancePath}`);
|
|
45113
|
+
} else {
|
|
45114
|
+
let gRaw;
|
|
45115
|
+
try {
|
|
45116
|
+
gRaw = JSON.parse(readText(guidancePath));
|
|
45117
|
+
} catch {
|
|
45118
|
+
errors.push(`${guidancePath}: not valid JSON`);
|
|
45119
|
+
}
|
|
45120
|
+
if (gRaw !== void 0) {
|
|
45121
|
+
const g = checkGuidance(gRaw, v.pack);
|
|
45122
|
+
errors.push(...g.errors);
|
|
45123
|
+
warnings.push(...g.warnings);
|
|
45124
|
+
}
|
|
45125
|
+
}
|
|
45126
|
+
}
|
|
45127
|
+
return { ok: errors.length === 0, errors, warnings };
|
|
45105
45128
|
}
|
|
45106
|
-
function
|
|
45107
|
-
|
|
45108
|
-
|
|
45109
|
-
|
|
45129
|
+
function packScaffold() {
|
|
45130
|
+
return JSON.stringify(
|
|
45131
|
+
{
|
|
45132
|
+
key: "mypack",
|
|
45133
|
+
name: "MyPack",
|
|
45134
|
+
fullName: "My national accessibility standard",
|
|
45135
|
+
org: "Org",
|
|
45136
|
+
country: "XX",
|
|
45137
|
+
baseVersion: "1.0",
|
|
45138
|
+
wcagVersion: "2.2",
|
|
45139
|
+
locales: ["en"],
|
|
45140
|
+
defaultLocale: "en",
|
|
45141
|
+
license: "\u2026",
|
|
45142
|
+
source: "https://\u2026",
|
|
45143
|
+
attribution: "\u2026 \xA9 \u2026",
|
|
45144
|
+
idPattern: "^\\d+\\.\\d+$",
|
|
45145
|
+
vocabulary: {
|
|
45146
|
+
theme: { en: "Theme" },
|
|
45147
|
+
criterion: { en: "Criterion" },
|
|
45148
|
+
test: { en: "Test" },
|
|
45149
|
+
conformant: { en: "Conformant" },
|
|
45150
|
+
nonConformant: { en: "Non-conformant" },
|
|
45151
|
+
notApplicable: { en: "Not applicable" },
|
|
45152
|
+
auditorHeading: { en: "Accessibility criterion" }
|
|
45153
|
+
},
|
|
45154
|
+
themes: [{ number: 1, name: { en: "Theme one" }, count: 1 }],
|
|
45155
|
+
criteria: [{ id: "1.1", theme: 1, title: { en: "Criterion one" }, titlePlain: { en: "Criterion one" }, wcag: ["1.1.1"] }]
|
|
45156
|
+
},
|
|
45157
|
+
null,
|
|
45158
|
+
2
|
|
45159
|
+
);
|
|
45110
45160
|
}
|
|
45111
45161
|
|
|
45112
45162
|
// src/data/guidance/rgaa.json
|
|
@@ -47573,6 +47623,441 @@ function hasGuidance(packKey) {
|
|
|
47573
47623
|
return datasets.has(packKey);
|
|
47574
47624
|
}
|
|
47575
47625
|
|
|
47626
|
+
// src/config.ts
|
|
47627
|
+
var CONFIG_FILE = ".ultra11yrc.json";
|
|
47628
|
+
function loadConfig(cwd) {
|
|
47629
|
+
const p = join26(cwd, CONFIG_FILE);
|
|
47630
|
+
if (!existsSync14(p)) return null;
|
|
47631
|
+
let parsed;
|
|
47632
|
+
try {
|
|
47633
|
+
parsed = JSON.parse(readText(p));
|
|
47634
|
+
} catch {
|
|
47635
|
+
throw new Error(`${CONFIG_FILE}: not valid JSON`);
|
|
47636
|
+
}
|
|
47637
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
47638
|
+
throw new Error(`${CONFIG_FILE}: must be a JSON object`);
|
|
47639
|
+
}
|
|
47640
|
+
return parsed;
|
|
47641
|
+
}
|
|
47642
|
+
function packPaths(path) {
|
|
47643
|
+
if (!existsSync14(path)) return null;
|
|
47644
|
+
if (statSync7(path).isDirectory()) {
|
|
47645
|
+
const pack = join26(path, "pack.json");
|
|
47646
|
+
if (!existsSync14(pack)) return null;
|
|
47647
|
+
const glossary = join26(path, "glossary.json");
|
|
47648
|
+
const guidance = join26(path, "guidance.json");
|
|
47649
|
+
return {
|
|
47650
|
+
pack,
|
|
47651
|
+
glossary: existsSync14(glossary) ? glossary : void 0,
|
|
47652
|
+
guidance: existsSync14(guidance) ? guidance : void 0
|
|
47653
|
+
};
|
|
47654
|
+
}
|
|
47655
|
+
return { pack: path };
|
|
47656
|
+
}
|
|
47657
|
+
function loadRuntimeStandards(cwd, packFlags, onWarn, override = false) {
|
|
47658
|
+
const result = { errors: [], loadedPacks: [], loadedGuidance: [] };
|
|
47659
|
+
let config = null;
|
|
47660
|
+
try {
|
|
47661
|
+
config = loadConfig(cwd);
|
|
47662
|
+
} catch (e) {
|
|
47663
|
+
result.errors.push(e instanceof Error ? e.message : String(e));
|
|
47664
|
+
return result;
|
|
47665
|
+
}
|
|
47666
|
+
result.defaultStandard = config?.standard;
|
|
47667
|
+
for (const raw of [...config?.packs ?? [], ...packFlags]) {
|
|
47668
|
+
const paths = packPaths(isAbsolute2(raw) ? raw : join26(cwd, raw));
|
|
47669
|
+
if (!paths) {
|
|
47670
|
+
result.errors.push(`--pack ${raw}: not found (expected a pack JSON file or a directory with pack.json)`);
|
|
47671
|
+
continue;
|
|
47672
|
+
}
|
|
47673
|
+
let packObj;
|
|
47674
|
+
try {
|
|
47675
|
+
packObj = JSON.parse(readText(paths.pack));
|
|
47676
|
+
} catch {
|
|
47677
|
+
result.errors.push(`--pack ${paths.pack}: not valid JSON`);
|
|
47678
|
+
continue;
|
|
47679
|
+
}
|
|
47680
|
+
let glossary = {};
|
|
47681
|
+
if (paths.glossary) {
|
|
47682
|
+
try {
|
|
47683
|
+
glossary = JSON.parse(readText(paths.glossary));
|
|
47684
|
+
} catch {
|
|
47685
|
+
onWarn(`ultra11y: ${paths.glossary} is not valid JSON \u2014 ignoring glossary.`);
|
|
47686
|
+
}
|
|
47687
|
+
}
|
|
47688
|
+
const v = registerRuntimePack(packObj, glossary, { override });
|
|
47689
|
+
for (const issue of v.issues) if (issue.severity === "warn") onWarn(`ultra11y: ${raw}: ${issue.path ? `${issue.path}: ` : ""}${issue.message}`);
|
|
47690
|
+
if (!v.ok || !v.pack) {
|
|
47691
|
+
result.errors.push(`--pack ${paths.pack}: invalid pack
|
|
47692
|
+
${formatIssues(v.issues).join("\n")}`);
|
|
47693
|
+
continue;
|
|
47694
|
+
}
|
|
47695
|
+
result.loadedPacks.push(v.pack.key);
|
|
47696
|
+
if (paths.guidance) loadGuidanceFile(paths.guidance, result, onWarn);
|
|
47697
|
+
}
|
|
47698
|
+
for (const g of config?.guidance ?? []) loadGuidanceFile(isAbsolute2(g) ? g : join26(cwd, g), result, onWarn);
|
|
47699
|
+
for (const sm of config?.secondaryMappings ?? []) {
|
|
47700
|
+
if (!sm || typeof sm.standard !== "string" || typeof sm.ruleId !== "string" || typeof sm.criterion !== "string") {
|
|
47701
|
+
result.errors.push("secondaryMappings: each entry must be { standard, ruleId, criterion, note? }");
|
|
47702
|
+
continue;
|
|
47703
|
+
}
|
|
47704
|
+
if (!getPack(sm.standard)) {
|
|
47705
|
+
result.errors.push(`secondaryMappings: unknown standard "${sm.standard}" (register its pack first; the WCAG core has no pack criteria)`);
|
|
47706
|
+
continue;
|
|
47707
|
+
}
|
|
47708
|
+
enableSecondaryMapping(sm.standard, { ruleId: sm.ruleId, criterion: sm.criterion, note: sm.note });
|
|
47709
|
+
}
|
|
47710
|
+
return result;
|
|
47711
|
+
}
|
|
47712
|
+
function loadGuidanceFile(path, result, onWarn) {
|
|
47713
|
+
if (!existsSync14(path)) {
|
|
47714
|
+
onWarn(`ultra11y: guidance ${path} not found \u2014 skipping.`);
|
|
47715
|
+
return;
|
|
47716
|
+
}
|
|
47717
|
+
let ds;
|
|
47718
|
+
try {
|
|
47719
|
+
ds = JSON.parse(readText(path));
|
|
47720
|
+
} catch {
|
|
47721
|
+
onWarn(`ultra11y: guidance ${path} is not valid JSON \u2014 skipping.`);
|
|
47722
|
+
return;
|
|
47723
|
+
}
|
|
47724
|
+
if (!ds || typeof ds.pack !== "string" || !Array.isArray(ds.entries)) {
|
|
47725
|
+
onWarn(`ultra11y: guidance ${path} is not a valid dataset (needs { pack, entries[] }) \u2014 skipping.`);
|
|
47726
|
+
return;
|
|
47727
|
+
}
|
|
47728
|
+
const pack = getPack(ds.pack);
|
|
47729
|
+
if (pack) {
|
|
47730
|
+
const g = checkGuidance(ds, pack);
|
|
47731
|
+
for (const w of g.warnings) onWarn(`ultra11y: guidance ${path}: ${w}`);
|
|
47732
|
+
if (g.errors.length) {
|
|
47733
|
+
result.errors.push(`guidance ${path}: invalid
|
|
47734
|
+
${g.errors.map((e) => ` \u2717 ${e}`).join("\n")}`);
|
|
47735
|
+
return;
|
|
47736
|
+
}
|
|
47737
|
+
} else {
|
|
47738
|
+
for (const e of ds.entries) {
|
|
47739
|
+
if (!Array.isArray(e?.wcag) || !Array.isArray(e?.examples)) {
|
|
47740
|
+
onWarn(`ultra11y: guidance ${path}: entry "${e?.id ?? "?"}" missing wcag[]/examples[] \u2014 skipping dataset.`);
|
|
47741
|
+
return;
|
|
47742
|
+
}
|
|
47743
|
+
}
|
|
47744
|
+
}
|
|
47745
|
+
if (hasGuidance(ds.pack)) onWarn(`ultra11y: guidance ${path} overrides the existing guidance for pack "${ds.pack}".`);
|
|
47746
|
+
registerRuntimeGuidance(ds);
|
|
47747
|
+
result.loadedGuidance.push(path);
|
|
47748
|
+
}
|
|
47749
|
+
|
|
47750
|
+
// src/hook.ts
|
|
47751
|
+
var AT_CMD = String.raw`(?:^|[\n;|&(]\s*)`;
|
|
47752
|
+
var OPTS = String.raw`(?:\s+(?:-[cC]\s+\S+|--?[\w-]+(?:=\S+)?))*`;
|
|
47753
|
+
var INTENTS = [
|
|
47754
|
+
// Ordered by breadth of what the command publishes: a `git commit && git push` chain is
|
|
47755
|
+
// reviewed against the base branch (push scope), not just the staged snapshot.
|
|
47756
|
+
["pr", new RegExp(`${AT_CMD}gh${OPTS}\\s+pr\\s+create(?![\\w-])`)],
|
|
47757
|
+
["push", new RegExp(`${AT_CMD}git${OPTS}\\s+push(?![\\w-])`)],
|
|
47758
|
+
["commit", new RegExp(`${AT_CMD}git${OPTS}\\s+commit(?![\\w-])`)]
|
|
47759
|
+
];
|
|
47760
|
+
var OPTED_OUT = /(?:^|\s)--(?:no-verify|dry-run)(?![\w-])/;
|
|
47761
|
+
function matchGitIntent(command) {
|
|
47762
|
+
if (OPTED_OUT.test(command)) return null;
|
|
47763
|
+
for (const [intent, re] of INTENTS) if (re.test(command)) return intent;
|
|
47764
|
+
return null;
|
|
47765
|
+
}
|
|
47766
|
+
function git2(cwd, args2) {
|
|
47767
|
+
try {
|
|
47768
|
+
return execFileSync2("git", args2, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
47769
|
+
} catch {
|
|
47770
|
+
return null;
|
|
47771
|
+
}
|
|
47772
|
+
}
|
|
47773
|
+
function resolveBase(command, cwd) {
|
|
47774
|
+
const explicit = command.match(/--base[=\s]+(\S+)/)?.[1]?.replace(/^["']|["']$/g, "");
|
|
47775
|
+
if (explicit) return explicit.includes("/") ? explicit : git2(cwd, ["rev-parse", "--verify", `origin/${explicit}`]) ? `origin/${explicit}` : explicit;
|
|
47776
|
+
const head = git2(cwd, ["symbolic-ref", "--short", "refs/remotes/origin/HEAD"]);
|
|
47777
|
+
if (head) return head;
|
|
47778
|
+
for (const ref of ["origin/main", "origin/master"]) if (git2(cwd, ["rev-parse", "--verify", ref])) return ref;
|
|
47779
|
+
return null;
|
|
47780
|
+
}
|
|
47781
|
+
function scopeFor(intent, command, cwd) {
|
|
47782
|
+
if (intent === "commit") return { staged: true };
|
|
47783
|
+
const base = resolveBase(command, cwd);
|
|
47784
|
+
return base ? { since: base } : null;
|
|
47785
|
+
}
|
|
47786
|
+
function resolveThreshold(cwd, env) {
|
|
47787
|
+
const fromEnv = env.ULTRA11Y_HOOK_FAIL_ON;
|
|
47788
|
+
if (fromEnv) return fromEnv === "off" ? null : parseFailOn(fromEnv);
|
|
47789
|
+
let configured;
|
|
47790
|
+
try {
|
|
47791
|
+
configured = loadConfig(cwd)?.hook?.failOn;
|
|
47792
|
+
} catch {
|
|
47793
|
+
}
|
|
47794
|
+
if (configured) return configured === "off" ? null : parseFailOn(configured);
|
|
47795
|
+
return "bloquant";
|
|
47796
|
+
}
|
|
47797
|
+
function loopKey(sessionId, intent, findings) {
|
|
47798
|
+
const h = createHash5("sha256");
|
|
47799
|
+
h.update(`${sessionId}\0${intent}\0`);
|
|
47800
|
+
for (const id of findings.map(findingId).sort()) h.update(`${id}\0`);
|
|
47801
|
+
return h.digest("hex").slice(0, 32);
|
|
47802
|
+
}
|
|
47803
|
+
function firstSighting(key) {
|
|
47804
|
+
try {
|
|
47805
|
+
const dir = join27(tmpdir(), "ultra11y-hook");
|
|
47806
|
+
mkdirSync5(dir, { recursive: true });
|
|
47807
|
+
const marker = join27(dir, key);
|
|
47808
|
+
if (existsSync15(marker)) return false;
|
|
47809
|
+
writeFileSync6(marker, "");
|
|
47810
|
+
return true;
|
|
47811
|
+
} catch {
|
|
47812
|
+
return false;
|
|
47813
|
+
}
|
|
47814
|
+
}
|
|
47815
|
+
var SEV_EN = { bloquant: "blocking", majeur: "major", mineur: "minor" };
|
|
47816
|
+
var MAX_DIGEST = 10;
|
|
47817
|
+
function digestOf(findings) {
|
|
47818
|
+
const shown = findings.slice(0, MAX_DIGEST).map((f) => `- ${f.file}:${f.line} \u2014 ${f.criteriaId} (${SEV_EN[f.severity]}) \u2014 ${f.message}`);
|
|
47819
|
+
if (findings.length > MAX_DIGEST) shown.push(`- \u2026and ${findings.length - MAX_DIGEST} more (the skill re-runs the engine and sees all of them).`);
|
|
47820
|
+
return shown.join("\n");
|
|
47821
|
+
}
|
|
47822
|
+
var SCOPE_HINT = {
|
|
47823
|
+
commit: "--staged --graph",
|
|
47824
|
+
push: (base) => `--since ${base} --graph`
|
|
47825
|
+
};
|
|
47826
|
+
function realAudit(scope, cwd) {
|
|
47827
|
+
const previous = process.cwd();
|
|
47828
|
+
try {
|
|
47829
|
+
process.chdir(cwd);
|
|
47830
|
+
return runAudit({
|
|
47831
|
+
inputs: ["."],
|
|
47832
|
+
changed: "since" in scope,
|
|
47833
|
+
..."since" in scope ? { since: scope.since } : { staged: true },
|
|
47834
|
+
graph: true,
|
|
47835
|
+
onWarn: () => {
|
|
47836
|
+
}
|
|
47837
|
+
// stdout/stderr belong to the hook protocol — stay quiet
|
|
47838
|
+
});
|
|
47839
|
+
} catch {
|
|
47840
|
+
return null;
|
|
47841
|
+
} finally {
|
|
47842
|
+
try {
|
|
47843
|
+
process.chdir(previous);
|
|
47844
|
+
} catch {
|
|
47845
|
+
}
|
|
47846
|
+
}
|
|
47847
|
+
}
|
|
47848
|
+
function decide(payload, deps = {}) {
|
|
47849
|
+
const env = deps.env ?? process.env;
|
|
47850
|
+
const audit2 = deps.audit ?? realAudit;
|
|
47851
|
+
const seen = deps.seen ?? firstSighting;
|
|
47852
|
+
if (payload.tool_name !== "Bash") return null;
|
|
47853
|
+
const command = payload.tool_input?.command;
|
|
47854
|
+
if (!command) return null;
|
|
47855
|
+
if (env.SKIP_A11Y || env.ULTRA11Y_HOOK === "off") return null;
|
|
47856
|
+
const intent = matchGitIntent(command);
|
|
47857
|
+
if (!intent) return null;
|
|
47858
|
+
const cwd = payload.cwd || process.cwd();
|
|
47859
|
+
if (!git2(cwd, ["rev-parse", "--git-dir"])) return null;
|
|
47860
|
+
const threshold = resolveThreshold(cwd, env);
|
|
47861
|
+
if (!threshold) return null;
|
|
47862
|
+
const scope = scopeFor(intent, command, cwd);
|
|
47863
|
+
if (!scope) return null;
|
|
47864
|
+
const result = audit2(scope, cwd);
|
|
47865
|
+
if (!result) return null;
|
|
47866
|
+
const findings = findingsAtOrAbove(result.findings, threshold);
|
|
47867
|
+
if (findings.length === 0) return null;
|
|
47868
|
+
if (!seen(loopKey(payload.session_id ?? "", intent, findings))) return null;
|
|
47869
|
+
const counts = ["bloquant", "majeur", "mineur"].map((s) => [s, findings.filter((f) => f.severity === s).length]).filter(([, n]) => n > 0).map(([s, n]) => `${n} ${SEV_EN[s]}`).join(", ");
|
|
47870
|
+
const where = intent === "commit" ? "about to be committed" : intent === "push" ? "about to be pushed" : "about to go into this pull request";
|
|
47871
|
+
const hint = "since" in scope ? SCOPE_HINT.push(scope.since) : SCOPE_HINT.commit;
|
|
47872
|
+
return {
|
|
47873
|
+
hookSpecificOutput: {
|
|
47874
|
+
hookEventName: "PreToolUse",
|
|
47875
|
+
permissionDecision: "deny",
|
|
47876
|
+
permissionDecisionReason: `ultra11y: ${findings.length} accessibility finding(s) (${counts}) in the code ${where}.`,
|
|
47877
|
+
additionalContext: [
|
|
47878
|
+
`Invoke the \`review-a11y\` skill now, scoped to \`${hint}\`, before running this command again.`,
|
|
47879
|
+
"",
|
|
47880
|
+
"The engine already found:",
|
|
47881
|
+
digestOf(findings),
|
|
47882
|
+
"",
|
|
47883
|
+
"Adjudicate each one against the cited code \u2014 refute false positives with evidence, apply the fixes the user agrees to.",
|
|
47884
|
+
"Then re-run the command: this gate does not fire twice for the same set of findings, so it will go through."
|
|
47885
|
+
].join("\n")
|
|
47886
|
+
}
|
|
47887
|
+
};
|
|
47888
|
+
}
|
|
47889
|
+
|
|
47890
|
+
// src/report.ts
|
|
47891
|
+
import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync8 } from "fs";
|
|
47892
|
+
import { join as join29 } from "path";
|
|
47893
|
+
|
|
47894
|
+
// src/prd.ts
|
|
47895
|
+
import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync7 } from "fs";
|
|
47896
|
+
import { join as join28 } from "path";
|
|
47897
|
+
|
|
47898
|
+
// src/standards/derive.ts
|
|
47899
|
+
function aggregate2(statuses) {
|
|
47900
|
+
if (statuses.includes("NC")) return "NC";
|
|
47901
|
+
if (statuses.includes("C")) return "C";
|
|
47902
|
+
if (statuses.includes("manual")) return "manual";
|
|
47903
|
+
return "NA";
|
|
47904
|
+
}
|
|
47905
|
+
function ruleMatches(ruleId, patterns) {
|
|
47906
|
+
for (const p of patterns) {
|
|
47907
|
+
if (p === "*") return true;
|
|
47908
|
+
if (p.endsWith(":*") || p.endsWith("-*")) {
|
|
47909
|
+
if (ruleId.startsWith(p.slice(0, -1))) return true;
|
|
47910
|
+
} else if (p === ruleId) return true;
|
|
47911
|
+
}
|
|
47912
|
+
return false;
|
|
47913
|
+
}
|
|
47914
|
+
function packCriteriaForFinding(pack, finding) {
|
|
47915
|
+
return pack.criteria.filter((pc) => pc.wcag.includes(finding.criteriaId) && (!pc.appliesTo || ruleMatches(finding.ruleId, pc.appliesTo.ruleIds))).map((pc) => pc.id);
|
|
47916
|
+
}
|
|
47917
|
+
function packConformancePct(derived) {
|
|
47918
|
+
const c2 = derived.filter((d) => d.status === "C").length;
|
|
47919
|
+
const nc = derived.filter((d) => d.status === "NC").length;
|
|
47920
|
+
return c2 + nc === 0 ? 100 : Math.round(c2 / (c2 + nc) * 100);
|
|
47921
|
+
}
|
|
47922
|
+
function overrideFinding(f, overrides) {
|
|
47923
|
+
const o = overrides?.[f.ruleId];
|
|
47924
|
+
if (!o) return f;
|
|
47925
|
+
const patched = { ...f };
|
|
47926
|
+
if (o.advisory !== void 0) patched.advisory = o.advisory;
|
|
47927
|
+
if (o.severity) patched.severity = o.severity;
|
|
47928
|
+
return patched;
|
|
47929
|
+
}
|
|
47930
|
+
function pickLocale(ls, preferred) {
|
|
47931
|
+
if (!ls) return void 0;
|
|
47932
|
+
return ls[preferred] ?? ls.en ?? ls.fr ?? Object.values(ls).find((v) => typeof v === "string");
|
|
47933
|
+
}
|
|
47934
|
+
function tagSecondary(f, note) {
|
|
47935
|
+
return { ...f, secondary: note ? { note } : {} };
|
|
47936
|
+
}
|
|
47937
|
+
function applySecondaryMappings(base, pc, enabled, sources, defaultLocale) {
|
|
47938
|
+
const active = enabled.filter((m) => m.criterion === pc.id);
|
|
47939
|
+
if (!active.length) return base;
|
|
47940
|
+
const added = [];
|
|
47941
|
+
for (const m of active) for (const f of sources) if (f.ruleId === m.ruleId) added.push(tagSecondary(f, pickLocale(m.note, defaultLocale)));
|
|
47942
|
+
if (!added.length) return base;
|
|
47943
|
+
const findings = [...base.findings, ...added];
|
|
47944
|
+
if (added.some((f) => !f.advisory)) return { id: pc.id, theme: pc.theme, status: aggregate2([base.status, "NC"]), findings, scs: base.scs };
|
|
47945
|
+
return { ...base, findings };
|
|
47946
|
+
}
|
|
47947
|
+
function derivePackResults(audit2, packKey) {
|
|
47948
|
+
const pack = loadPack(packKey);
|
|
47949
|
+
const byScId = new Map(audit2.criteria.map((c2) => [c2.id, c2]));
|
|
47950
|
+
const myPackFindings = (audit2.packFindings ?? []).filter((f) => f.ruleId.startsWith(`pack:${packKey}:`));
|
|
47951
|
+
const overrides = pack.overrides;
|
|
47952
|
+
const deriveBase = (pc) => {
|
|
47953
|
+
const outOfScope = pc.wcag.every((sc) => {
|
|
47954
|
+
const s = knownScStatus(sc);
|
|
47955
|
+
return s === "out-of-core" || s === "removed";
|
|
47956
|
+
});
|
|
47957
|
+
if (outOfScope) {
|
|
47958
|
+
return { id: pc.id, theme: pc.theme, status: "manual", findings: [], scs: pc.wcag, outOfScope: true };
|
|
47959
|
+
}
|
|
47960
|
+
const scResults = pc.wcag.map((sc) => byScId.get(sc)).filter((x) => !!x);
|
|
47961
|
+
const packFs = myPackFindings.filter((f) => pc.wcag.includes(f.criteriaId));
|
|
47962
|
+
const allFindings = [...scResults.flatMap((r) => r.findings), ...packFs].map((f) => overrideFinding(f, overrides));
|
|
47963
|
+
if (!pc.appliesTo) {
|
|
47964
|
+
const status2 = scResults.length ? aggregate2(scResults.map((r) => r.status)) : "NA";
|
|
47965
|
+
return { id: pc.id, theme: pc.theme, status: status2, findings: allFindings, scs: pc.wcag };
|
|
47966
|
+
}
|
|
47967
|
+
const findings = allFindings.filter((f) => ruleMatches(f.ruleId, pc.appliesTo.ruleIds));
|
|
47968
|
+
const normativeFindings = findings.filter((f) => !f.advisory);
|
|
47969
|
+
if (normativeFindings.length) {
|
|
47970
|
+
return { id: pc.id, theme: pc.theme, status: "NC", findings, scs: pc.wcag };
|
|
47971
|
+
}
|
|
47972
|
+
if (scResults.some((r) => r.status === "NC")) {
|
|
47973
|
+
return { id: pc.id, theme: pc.theme, status: "manual", findings, scs: pc.wcag, scopedOut: true };
|
|
47974
|
+
}
|
|
47975
|
+
const status = scResults.length ? aggregate2(scResults.map((r) => r.status)) : "NA";
|
|
47976
|
+
return { id: pc.id, theme: pc.theme, status, findings, scs: pc.wcag };
|
|
47977
|
+
};
|
|
47978
|
+
const enabledSecondary = (pack.secondaryMappings ?? []).filter((m) => m.enabled === true);
|
|
47979
|
+
const secondarySources = enabledSecondary.length ? [...audit2.criteria.flatMap((c2) => c2.findings), ...myPackFindings] : [];
|
|
47980
|
+
const adjudicated = audit2.packAdjudication?.standard === packKey ? new Map(audit2.packAdjudication.criteria.map((c2) => [c2.id, c2])) : void 0;
|
|
47981
|
+
return pack.criteria.map((pc) => {
|
|
47982
|
+
const decided = adjudicated?.get(pc.id);
|
|
47983
|
+
if (decided) {
|
|
47984
|
+
return {
|
|
47985
|
+
id: pc.id,
|
|
47986
|
+
theme: pc.theme,
|
|
47987
|
+
status: decided.status,
|
|
47988
|
+
findings: decided.findings,
|
|
47989
|
+
scs: pc.wcag,
|
|
47990
|
+
...decided.justification ? { justification: decided.justification } : {},
|
|
47991
|
+
decidedBy: "agent"
|
|
47992
|
+
};
|
|
47993
|
+
}
|
|
47994
|
+
const base = deriveBase(pc);
|
|
47995
|
+
return enabledSecondary.length ? applySecondaryMappings(base, pc, enabledSecondary, secondarySources, pack.defaultLocale) : base;
|
|
47996
|
+
});
|
|
47997
|
+
}
|
|
47998
|
+
function findingsForStandard(audit2, standard) {
|
|
47999
|
+
if (standard === CORE_KEY) return audit2.findings;
|
|
48000
|
+
const mine = (audit2.packFindings ?? []).filter((f) => f.ruleId.startsWith(`pack:${standard}:`));
|
|
48001
|
+
return mine.length ? [...audit2.findings, ...mine] : audit2.findings;
|
|
48002
|
+
}
|
|
48003
|
+
|
|
48004
|
+
// src/standards/vocabulary.ts
|
|
48005
|
+
var DEFAULT = {
|
|
48006
|
+
theme: { en: "Theme", fr: "Th\xE9matique" },
|
|
48007
|
+
criterion: { en: "Criterion", fr: "Crit\xE8re" },
|
|
48008
|
+
test: { en: "Test", fr: "Test" },
|
|
48009
|
+
conformant: { en: "Conformant", fr: "Conforme" },
|
|
48010
|
+
nonConformant: { en: "Non-conformant", fr: "Non conforme" },
|
|
48011
|
+
notApplicable: { en: "Not applicable", fr: "Non applicable" },
|
|
48012
|
+
auditorHeading: { en: "Accessibility criterion", fr: "Crit\xE8re d'accessibilit\xE9" }
|
|
48013
|
+
};
|
|
48014
|
+
var CORE = {
|
|
48015
|
+
theme: { en: "Principle \xB7 Guideline", fr: "Principe \xB7 R\xE8gle" },
|
|
48016
|
+
criterion: { en: "Success criterion", fr: "Crit\xE8re de succ\xE8s" },
|
|
48017
|
+
test: { en: "Technique", fr: "Technique" },
|
|
48018
|
+
conformant: { en: "Pass", fr: "Conforme" },
|
|
48019
|
+
nonConformant: { en: "Fail", fr: "Non conforme" },
|
|
48020
|
+
notApplicable: { en: "Not applicable", fr: "Non applicable" },
|
|
48021
|
+
auditorHeading: { en: "Accessibility criterion", fr: "Crit\xE8re d'accessibilit\xE9" }
|
|
48022
|
+
};
|
|
48023
|
+
function pick(s, lang, fallbackLang) {
|
|
48024
|
+
if (!s) return void 0;
|
|
48025
|
+
return s[lang] ?? s[fallbackLang] ?? s.en ?? Object.values(s)[0];
|
|
48026
|
+
}
|
|
48027
|
+
function vocabularyFor(standard, lang) {
|
|
48028
|
+
const base = isCore(standard) ? CORE : DEFAULT;
|
|
48029
|
+
const pack = isCore(standard) ? void 0 : getPack(standard);
|
|
48030
|
+
const voc = pack?.vocabulary;
|
|
48031
|
+
const packDefault = pack?.defaultLocale ?? "en";
|
|
48032
|
+
const term = (k) => pick(voc?.[k], lang, packDefault) ?? base[k][lang] ?? base[k].en;
|
|
48033
|
+
return {
|
|
48034
|
+
theme: term("theme"),
|
|
48035
|
+
criterion: term("criterion"),
|
|
48036
|
+
test: term("test"),
|
|
48037
|
+
conformant: term("conformant"),
|
|
48038
|
+
nonConformant: term("nonConformant"),
|
|
48039
|
+
notApplicable: term("notApplicable"),
|
|
48040
|
+
auditorHeading: term("auditorHeading"),
|
|
48041
|
+
normativeNote: pick(voc?.normativeNote, lang, packDefault)
|
|
48042
|
+
};
|
|
48043
|
+
}
|
|
48044
|
+
|
|
48045
|
+
// src/standards/index.ts
|
|
48046
|
+
var CORE2 = CORE_KEY;
|
|
48047
|
+
function resolveStandard(flag) {
|
|
48048
|
+
if (flag === void 0 || flag === true || flag === "") return CORE_KEY;
|
|
48049
|
+
const key = String(flag).toLowerCase();
|
|
48050
|
+
if (!hasStandard(key)) {
|
|
48051
|
+
throw new Error(`unknown standard "${key}" (known: ${listStandards().join(", ")})`);
|
|
48052
|
+
}
|
|
48053
|
+
return key;
|
|
48054
|
+
}
|
|
48055
|
+
function standardLabel(standard) {
|
|
48056
|
+
if (isCore(standard)) return "WCAG 2.2 AA";
|
|
48057
|
+
const p = loadPack(standard);
|
|
48058
|
+
return `${p.name} ${p.baseVersion}`;
|
|
48059
|
+
}
|
|
48060
|
+
|
|
47576
48061
|
// src/auditor.ts
|
|
47577
48062
|
var SEV_ORDER = ["bloquant", "majeur", "mineur"];
|
|
47578
48063
|
var ICON = { bloquant: "\u{1F534}", majeur: "\u{1F7E0}", mineur: "\u{1F7E1}" };
|
|
@@ -48149,10 +48634,10 @@ function renderPrdDoc(r, lang = "en", standard = "wcag") {
|
|
|
48149
48634
|
return out2.join("\n");
|
|
48150
48635
|
}
|
|
48151
48636
|
function writePrd(r, opts) {
|
|
48152
|
-
|
|
48637
|
+
mkdirSync6(opts.out, { recursive: true });
|
|
48153
48638
|
if (opts.format === "doc") {
|
|
48154
|
-
const p2 =
|
|
48155
|
-
|
|
48639
|
+
const p2 = join28(opts.out, `prd-doc-${r.date}.md`);
|
|
48640
|
+
writeFileSync7(p2, renderPrdDoc(r, opts.lang, opts.standard));
|
|
48156
48641
|
return [p2];
|
|
48157
48642
|
}
|
|
48158
48643
|
const remediation = opts.format === "remediation";
|
|
@@ -48161,14 +48646,14 @@ function writePrd(r, opts) {
|
|
|
48161
48646
|
const files = remediation ? renderPerCriterion(r, opts.lang, opts.standard) : renderAuditorPerCriterion(r, opts.lang, opts.standard, { technical });
|
|
48162
48647
|
const paths = [];
|
|
48163
48648
|
for (const f of files) {
|
|
48164
|
-
const p2 =
|
|
48165
|
-
|
|
48649
|
+
const p2 = join28(opts.out, f.name);
|
|
48650
|
+
writeFileSync7(p2, f.content);
|
|
48166
48651
|
paths.push(p2);
|
|
48167
48652
|
}
|
|
48168
48653
|
return paths;
|
|
48169
48654
|
}
|
|
48170
|
-
const p =
|
|
48171
|
-
|
|
48655
|
+
const p = join28(opts.out, `prd-${r.date}.md`);
|
|
48656
|
+
writeFileSync7(p, remediation ? renderBacklog(r, opts.lang, opts.standard) : renderAuditorBacklog(r, opts.lang, opts.standard, { technical }));
|
|
48172
48657
|
return [p];
|
|
48173
48658
|
}
|
|
48174
48659
|
|
|
@@ -48619,14 +49104,14 @@ function renderPackReport(r, pack, lang = "en") {
|
|
|
48619
49104
|
function writeReport(r, opts) {
|
|
48620
49105
|
const core = isCore(opts.standard);
|
|
48621
49106
|
const md = core ? renderReport(r, opts.lang) : renderPackReport(r, loadPack(opts.standard), opts.lang);
|
|
48622
|
-
|
|
48623
|
-
const path =
|
|
48624
|
-
|
|
49107
|
+
mkdirSync7(opts.out, { recursive: true });
|
|
49108
|
+
const path = join29(opts.out, `${core ? "wcag" : opts.standard}-${r.date}.md`);
|
|
49109
|
+
writeFileSync8(path, md);
|
|
48625
49110
|
return path;
|
|
48626
49111
|
}
|
|
48627
49112
|
|
|
48628
49113
|
// src/gh.ts
|
|
48629
|
-
import { execFileSync as
|
|
49114
|
+
import { execFileSync as execFileSync3 } from "child_process";
|
|
48630
49115
|
var SEV_ORDER4 = ["bloquant", "majeur", "mineur"];
|
|
48631
49116
|
var SEV_RANK2 = { bloquant: 0, majeur: 1, mineur: 2 };
|
|
48632
49117
|
var ICON4 = { bloquant: "\u{1F534}", majeur: "\u{1F7E0}", mineur: "\u{1F7E1}" };
|
|
@@ -48641,7 +49126,7 @@ function standardTag(standard) {
|
|
|
48641
49126
|
})();
|
|
48642
49127
|
}
|
|
48643
49128
|
function gh(args2, input) {
|
|
48644
|
-
return
|
|
49129
|
+
return execFileSync3("gh", args2, { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"], ...input !== void 0 ? { input } : {} });
|
|
48645
49130
|
}
|
|
48646
49131
|
function ghErrorReason(err2) {
|
|
48647
49132
|
if (!err2 || typeof err2 !== "object") return void 0;
|
|
@@ -48654,7 +49139,7 @@ function ghErrorReason(err2) {
|
|
|
48654
49139
|
}
|
|
48655
49140
|
function ghAvailable() {
|
|
48656
49141
|
try {
|
|
48657
|
-
|
|
49142
|
+
execFileSync3("gh", ["auth", "status"], { stdio: "ignore" });
|
|
48658
49143
|
return true;
|
|
48659
49144
|
} catch {
|
|
48660
49145
|
return false;
|
|
@@ -49220,12 +49705,12 @@ function renderCriteriaReference() {
|
|
|
49220
49705
|
}
|
|
49221
49706
|
|
|
49222
49707
|
// src/check.ts
|
|
49223
|
-
import { existsSync as
|
|
49224
|
-
import { dirname as dirname9, join as
|
|
49708
|
+
import { existsSync as existsSync17, readFileSync as readFileSync15 } from "fs";
|
|
49709
|
+
import { dirname as dirname9, join as join31 } from "path";
|
|
49225
49710
|
|
|
49226
49711
|
// src/verify.ts
|
|
49227
|
-
import { mkdirSync as
|
|
49228
|
-
import { join as
|
|
49712
|
+
import { mkdirSync as mkdirSync8, writeFileSync as writeFileSync9 } from "fs";
|
|
49713
|
+
import { join as join30 } from "path";
|
|
49229
49714
|
var VERIFY_MAX = 40;
|
|
49230
49715
|
var plain = (s) => s.replace(/\[([^\]]+)\]\(#[^)]*\)/g, "$1");
|
|
49231
49716
|
function auditorCriterionLine(standard) {
|
|
@@ -49399,16 +49884,16 @@ function applyVerdicts(items, expected) {
|
|
|
49399
49884
|
return { ok: failures.length === 0, total, refuted, unsupported, unadjudicated, invalid, missing, failures };
|
|
49400
49885
|
}
|
|
49401
49886
|
function writeWorklist(items, outDir, semantic, standard = "wcag", lang = "en") {
|
|
49402
|
-
|
|
49403
|
-
const todoPath =
|
|
49404
|
-
const mdPath =
|
|
49405
|
-
|
|
49406
|
-
|
|
49887
|
+
mkdirSync8(outDir, { recursive: true });
|
|
49888
|
+
const todoPath = join30(outDir, "VERIFY.todo.json");
|
|
49889
|
+
const mdPath = join30(outDir, "VERIFY.md");
|
|
49890
|
+
writeFileSync9(todoPath, JSON.stringify(items, null, 2) + "\n");
|
|
49891
|
+
writeFileSync9(mdPath, formatWorklist(items, semantic, standard, lang));
|
|
49407
49892
|
return { todoPath, mdPath, count: items.length };
|
|
49408
49893
|
}
|
|
49409
49894
|
|
|
49410
49895
|
// src/grounding.ts
|
|
49411
|
-
import { existsSync as
|
|
49896
|
+
import { existsSync as existsSync16, readFileSync as readFileSync14 } from "fs";
|
|
49412
49897
|
import { resolve as resolve4 } from "path";
|
|
49413
49898
|
var WINDOW = 10;
|
|
49414
49899
|
var norm2 = (s) => s.replace(/\s+/g, " ").trim();
|
|
@@ -49427,7 +49912,7 @@ function selectorProbes(selector) {
|
|
|
49427
49912
|
function groundFinding(g, opts = {}) {
|
|
49428
49913
|
if (isUnresolvable(g.file)) return { ok: true, moved: false };
|
|
49429
49914
|
const path = resolve4(opts.cwd ?? process.cwd(), g.file);
|
|
49430
|
-
if (!
|
|
49915
|
+
if (!existsSync16(path)) return { ok: false, moved: false, issue: `cited file not found: ${g.file}` };
|
|
49431
49916
|
let text;
|
|
49432
49917
|
try {
|
|
49433
49918
|
text = readFileSync14(path, "utf8");
|
|
@@ -49577,8 +50062,8 @@ function checkSemantic(md, opts) {
|
|
|
49577
50062
|
const standard = opts.standard ?? "wcag";
|
|
49578
50063
|
const s = M[lang];
|
|
49579
50064
|
const empty = { total: 0, grounded: 0, moved: 0, failed: 0 };
|
|
49580
|
-
const artifact = opts.verdictsPath ??
|
|
49581
|
-
if (!
|
|
50065
|
+
const artifact = opts.verdictsPath ?? join31(dirname9(opts.reportPath), "VERIFY.todo.json");
|
|
50066
|
+
if (!existsSync17(artifact)) return { ok: false, issues: [s.semanticMissing(artifact)], ...empty };
|
|
49582
50067
|
let items;
|
|
49583
50068
|
try {
|
|
49584
50069
|
const parsed = JSON.parse(readFileSync15(artifact, "utf8"));
|
|
@@ -49613,8 +50098,8 @@ function sectionBody(md, n) {
|
|
|
49613
50098
|
}
|
|
49614
50099
|
|
|
49615
50100
|
// src/adjudicate.ts
|
|
49616
|
-
import { mkdirSync as
|
|
49617
|
-
import { join as
|
|
50101
|
+
import { mkdirSync as mkdirSync9, writeFileSync as writeFileSync10 } from "fs";
|
|
50102
|
+
import { join as join32 } from "path";
|
|
49618
50103
|
|
|
49619
50104
|
// src/data/adjudication.json
|
|
49620
50105
|
var adjudication_default = {
|
|
@@ -50537,7 +51022,7 @@ function docsForAudit(audit2, cwd) {
|
|
|
50537
51022
|
const docs = [];
|
|
50538
51023
|
for (const f of files) {
|
|
50539
51024
|
try {
|
|
50540
|
-
docs.push(parseSource(readText(cwd ?
|
|
51025
|
+
docs.push(parseSource(readText(cwd ? join32(cwd, f) : f), f));
|
|
50541
51026
|
} catch {
|
|
50542
51027
|
}
|
|
50543
51028
|
}
|
|
@@ -50894,9 +51379,9 @@ function formatAdjudication(items, lang = "en", standard = CORE2) {
|
|
|
50894
51379
|
return out2.join("\n");
|
|
50895
51380
|
}
|
|
50896
51381
|
function writeAdjudication(items, outDir, opts) {
|
|
50897
|
-
|
|
50898
|
-
const todoPath =
|
|
50899
|
-
const mdPath =
|
|
51382
|
+
mkdirSync9(outDir, { recursive: true });
|
|
51383
|
+
const todoPath = join32(outDir, "ADJUDICATE.todo.json");
|
|
51384
|
+
const mdPath = join32(outDir, "ADJUDICATE.md");
|
|
50900
51385
|
const file = {
|
|
50901
51386
|
tool: "ultra11y",
|
|
50902
51387
|
kind: "adjudication",
|
|
@@ -50905,16 +51390,16 @@ function writeAdjudication(items, outDir, opts) {
|
|
|
50905
51390
|
auditDate: opts.auditDate,
|
|
50906
51391
|
items
|
|
50907
51392
|
};
|
|
50908
|
-
|
|
50909
|
-
|
|
51393
|
+
writeFileSync10(todoPath, JSON.stringify(file, null, 2) + "\n");
|
|
51394
|
+
writeFileSync10(mdPath, formatAdjudication(items, opts.lang ?? "en", opts.standard));
|
|
50910
51395
|
return { todoPath, mdPath, count: items.length };
|
|
50911
51396
|
}
|
|
50912
51397
|
|
|
50913
51398
|
// src/scan.ts
|
|
50914
|
-
import { execFileSync as
|
|
50915
|
-
import { mkdtempSync as mkdtempSync2, writeFileSync as
|
|
50916
|
-
import { tmpdir } from "os";
|
|
50917
|
-
import { join as
|
|
51399
|
+
import { execFileSync as execFileSync4 } from "child_process";
|
|
51400
|
+
import { mkdtempSync as mkdtempSync2, writeFileSync as writeFileSync11, existsSync as existsSync19, statSync as statSync8, readdirSync as readdirSync5, rmSync as rmSync3, readFileSync as readFileSync16 } from "fs";
|
|
51401
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
51402
|
+
import { join as join33, resolve as resolve5 } from "path";
|
|
50918
51403
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
50919
51404
|
|
|
50920
51405
|
// src/axe-map.ts
|
|
@@ -51156,7 +51641,7 @@ async function crawlUrls(start2, opts) {
|
|
|
51156
51641
|
}
|
|
51157
51642
|
|
|
51158
51643
|
// src/sample.ts
|
|
51159
|
-
import { existsSync as
|
|
51644
|
+
import { existsSync as existsSync18 } from "fs";
|
|
51160
51645
|
function looksLikeTarget(u) {
|
|
51161
51646
|
return /^https?:\/\//i.test(u) || /^(\.\.?[/\\]|[/\\])/.test(u) || /\.x?html?$/i.test(u);
|
|
51162
51647
|
}
|
|
@@ -51192,7 +51677,7 @@ function validateSample(raw) {
|
|
|
51192
51677
|
if (p.auth !== void 0 && typeof p.auth !== "boolean") err2(`sample.pages[${i2}].auth`, "auth must be a boolean");
|
|
51193
51678
|
if (p.storageState !== void 0 && (typeof p.storageState !== "string" || p.storageState.trim() === "")) {
|
|
51194
51679
|
err2(`sample.pages[${i2}].storageState`, "storageState must be a non-empty file path string");
|
|
51195
|
-
} else if (typeof p.storageState === "string" && p.storageState.trim() !== "" && !
|
|
51680
|
+
} else if (typeof p.storageState === "string" && p.storageState.trim() !== "" && !existsSync18(p.storageState)) {
|
|
51196
51681
|
warn(
|
|
51197
51682
|
`sample.pages[${i2}].storageState`,
|
|
51198
51683
|
`storageState path not found: "${p.storageState}" (resolved from the current directory) \u2014 the scan will not be able to load this session`
|
|
@@ -51301,7 +51786,7 @@ ENTRYPOINT ["node", "/app/runner.mjs"]
|
|
|
51301
51786
|
`;
|
|
51302
51787
|
function dockerAvailable() {
|
|
51303
51788
|
try {
|
|
51304
|
-
|
|
51789
|
+
execFileSync4("docker", ["info"], { stdio: "ignore", timeout: 1e4 });
|
|
51305
51790
|
return true;
|
|
51306
51791
|
} catch {
|
|
51307
51792
|
return false;
|
|
@@ -51309,7 +51794,7 @@ function dockerAvailable() {
|
|
|
51309
51794
|
}
|
|
51310
51795
|
function imageExists(tag) {
|
|
51311
51796
|
try {
|
|
51312
|
-
|
|
51797
|
+
execFileSync4("docker", ["image", "inspect", tag], { stdio: "ignore" });
|
|
51313
51798
|
return true;
|
|
51314
51799
|
} catch {
|
|
51315
51800
|
return false;
|
|
@@ -51317,22 +51802,22 @@ function imageExists(tag) {
|
|
|
51317
51802
|
}
|
|
51318
51803
|
var CTX_PREFIX = "ultra11y-dyn-";
|
|
51319
51804
|
function buildImage(tag = IMAGE_TAG) {
|
|
51320
|
-
const ctx = mkdtempSync2(
|
|
51805
|
+
const ctx = mkdtempSync2(join33(tmpdir2(), CTX_PREFIX));
|
|
51321
51806
|
try {
|
|
51322
|
-
|
|
51323
|
-
|
|
51324
|
-
|
|
51325
|
-
|
|
51807
|
+
writeFileSync11(join33(ctx, "runner.mjs"), RUNNER);
|
|
51808
|
+
writeFileSync11(join33(ctx, "package.json"), PKG);
|
|
51809
|
+
writeFileSync11(join33(ctx, "Dockerfile"), DOCKERFILE);
|
|
51810
|
+
execFileSync4("docker", ["build", "-t", tag, ctx], { stdio: "inherit", timeout: 9e5 });
|
|
51326
51811
|
} finally {
|
|
51327
51812
|
rmSync3(ctx, { recursive: true, force: true });
|
|
51328
51813
|
}
|
|
51329
51814
|
}
|
|
51330
51815
|
function cleanTempContexts() {
|
|
51331
51816
|
let removed = 0;
|
|
51332
|
-
const dir =
|
|
51817
|
+
const dir = tmpdir2();
|
|
51333
51818
|
for (const name2 of readdirSync5(dir)) {
|
|
51334
51819
|
if (!name2.startsWith(CTX_PREFIX)) continue;
|
|
51335
|
-
rmSync3(
|
|
51820
|
+
rmSync3(join33(dir, name2), { recursive: true, force: true });
|
|
51336
51821
|
removed++;
|
|
51337
51822
|
}
|
|
51338
51823
|
return removed;
|
|
@@ -51341,7 +51826,7 @@ function cleanDynamic(tag = IMAGE_TAG) {
|
|
|
51341
51826
|
let imageRemoved = false;
|
|
51342
51827
|
if (dockerAvailable() && imageExists(tag)) {
|
|
51343
51828
|
try {
|
|
51344
|
-
|
|
51829
|
+
execFileSync4("docker", ["rmi", "-f", tag], { stdio: "ignore" });
|
|
51345
51830
|
imageRemoved = true;
|
|
51346
51831
|
} catch {
|
|
51347
51832
|
}
|
|
@@ -51364,7 +51849,7 @@ function runRunner(target, isFile, tag) {
|
|
|
51364
51849
|
args2.push(tag, isFile ? MOUNT : target);
|
|
51365
51850
|
let stdout;
|
|
51366
51851
|
try {
|
|
51367
|
-
stdout =
|
|
51852
|
+
stdout = execFileSync4("docker", args2, { encoding: "utf8", timeout: 24e4, maxBuffer: 32 * 1024 * 1024, stdio: ["ignore", "pipe", "pipe"] });
|
|
51368
51853
|
} catch (e) {
|
|
51369
51854
|
const err2 = e;
|
|
51370
51855
|
const detail = (err2.stderr ? String(err2.stderr).trim() : "") || err2.message || String(e);
|
|
@@ -51443,7 +51928,7 @@ function toDynamicResult(out2, target, lang = "en", engine = "axe-core@playwrigh
|
|
|
51443
51928
|
var DOCKER_TESTED_SCS = ["1.4.10"];
|
|
51444
51929
|
function runScan(opts) {
|
|
51445
51930
|
const isUrl3 = /^https?:\/\//i.test(opts.target);
|
|
51446
|
-
if (!isUrl3 && !
|
|
51931
|
+
if (!isUrl3 && !existsSync19(opts.target)) {
|
|
51447
51932
|
throw new Error(`File not found: ${opts.target}. Pass an http(s):// URL or an existing HTML file.`);
|
|
51448
51933
|
}
|
|
51449
51934
|
if (!dockerAvailable()) {
|
|
@@ -51451,7 +51936,7 @@ function runScan(opts) {
|
|
|
51451
51936
|
}
|
|
51452
51937
|
const tag = opts.tag ?? IMAGE_TAG;
|
|
51453
51938
|
if (!imageExists(tag)) buildImage(tag);
|
|
51454
|
-
const isFile = !isUrl3 &&
|
|
51939
|
+
const isFile = !isUrl3 && existsSync19(opts.target) && statSync8(opts.target).isFile();
|
|
51455
51940
|
const out2 = runRunner(opts.target, isFile, tag);
|
|
51456
51941
|
return { ...toDynamicResult(out2, opts.target), testedScs: [...DOCKER_TESTED_SCS] };
|
|
51457
51942
|
}
|
|
@@ -51529,7 +52014,7 @@ async function runCrawlScan(opts) {
|
|
|
51529
52014
|
var sevRank = { bloquant: 3, majeur: 2, mineur: 1 };
|
|
51530
52015
|
function resolveHostAnchor(file, snippet2) {
|
|
51531
52016
|
const s = snippet2?.trim();
|
|
51532
|
-
if (!s || !
|
|
52017
|
+
if (!s || !existsSync19(file)) return null;
|
|
51533
52018
|
let source;
|
|
51534
52019
|
try {
|
|
51535
52020
|
source = readFileSync16(file, "utf8");
|
|
@@ -51628,7 +52113,7 @@ function mergeDynamic(audit2, dynamic, lang = "en") {
|
|
|
51628
52113
|
}
|
|
51629
52114
|
|
|
51630
52115
|
// src/scan-local.ts
|
|
51631
|
-
import { existsSync as
|
|
52116
|
+
import { existsSync as existsSync20, statSync as statSync9 } from "fs";
|
|
51632
52117
|
import { createRequire } from "module";
|
|
51633
52118
|
import { resolve as resolve6 } from "path";
|
|
51634
52119
|
var LOCAL_ENGINE = "axe-core@playwright (local)";
|
|
@@ -52184,10 +52669,10 @@ async function runOnPage(browser, AxeBuilder, target, isFile, opts) {
|
|
|
52184
52669
|
}
|
|
52185
52670
|
async function runScanLocal(opts) {
|
|
52186
52671
|
const isUrl3 = /^https?:\/\//i.test(opts.target);
|
|
52187
|
-
if (!isUrl3 && !
|
|
52672
|
+
if (!isUrl3 && !existsSync20(opts.target)) {
|
|
52188
52673
|
throw new Error(`File not found: ${opts.target}. Pass an http(s):// URL or an existing HTML file.`);
|
|
52189
52674
|
}
|
|
52190
|
-
const isFile = !isUrl3 &&
|
|
52675
|
+
const isFile = !isUrl3 && statSync9(opts.target).isFile();
|
|
52191
52676
|
const lang = opts.lang ?? "en";
|
|
52192
52677
|
const interact = opts.interact !== false;
|
|
52193
52678
|
const { chromium, AxeBuilder } = resolveLocalDeps(opts.cwd);
|
|
@@ -52265,7 +52750,7 @@ async function runCrawlScanLocal(opts) {
|
|
|
52265
52750
|
}
|
|
52266
52751
|
|
|
52267
52752
|
// src/fix.ts
|
|
52268
|
-
import { writeFileSync as
|
|
52753
|
+
import { writeFileSync as writeFileSync12 } from "fs";
|
|
52269
52754
|
|
|
52270
52755
|
// src/fix/edits.ts
|
|
52271
52756
|
function openTag(source, start2) {
|
|
@@ -52479,7 +52964,7 @@ function fixOne(file, source, opts, canWrite = true) {
|
|
|
52479
52964
|
regression = after.length > findings.length || after.some((f) => !beforeKinds.has(f.ruleId));
|
|
52480
52965
|
if (opts.write && !regression && file !== "<stdin>") {
|
|
52481
52966
|
if (canWrite) {
|
|
52482
|
-
|
|
52967
|
+
writeFileSync12(file, output);
|
|
52483
52968
|
written = true;
|
|
52484
52969
|
} else {
|
|
52485
52970
|
skippedPartialStage = true;
|
|
@@ -52560,55 +53045,14 @@ function fixSummary(r, lang = "fr", write = false) {
|
|
|
52560
53045
|
return out2.join("\n");
|
|
52561
53046
|
}
|
|
52562
53047
|
|
|
52563
|
-
// src/baseline.ts
|
|
52564
|
-
var RANK = { bloquant: 0, majeur: 1, mineur: 2 };
|
|
52565
|
-
function findingId(f) {
|
|
52566
|
-
const pos = f.sourceStart !== void 0 ? `b${f.sourceStart}-${f.sourceEnd}` : `l${f.line}:${f.col}:${f.selectorHint}`;
|
|
52567
|
-
return `${f.ruleId}|${f.criteriaId}|${f.file}|${pos}`;
|
|
52568
|
-
}
|
|
52569
|
-
function parseFailOn(v) {
|
|
52570
|
-
if (v === void 0 || v === true) return "bloquant";
|
|
52571
|
-
if (v === "bloquant" || v === "blocking") return "bloquant";
|
|
52572
|
-
if (v === "majeur" || v === "major") return "majeur";
|
|
52573
|
-
if (v === "mineur" || v === "minor") return "mineur";
|
|
52574
|
-
return null;
|
|
52575
|
-
}
|
|
52576
|
-
function findingsAtOrAbove(findings, failOn) {
|
|
52577
|
-
return findings.filter((f) => RANK[f.severity] <= RANK[failOn]);
|
|
52578
|
-
}
|
|
52579
|
-
function diffAgainstBaseline(current, baseline, failOn = "bloquant") {
|
|
52580
|
-
const baseIds = new Set((baseline?.findings ?? []).map(findingId));
|
|
52581
|
-
const curIds = new Set(current.findings.map(findingId));
|
|
52582
|
-
const newFindings = current.findings.filter((f) => !baseIds.has(findingId(f)));
|
|
52583
|
-
const auditedFiles = new Set(current.findings.map((f) => f.file));
|
|
52584
|
-
const fixedFindings = (baseline?.findings ?? []).filter((f) => auditedFiles.has(f.file) && !curIds.has(findingId(f)));
|
|
52585
|
-
const ok = !newFindings.some((f) => RANK[f.severity] <= RANK[failOn]);
|
|
52586
|
-
return { newFindings, fixedFindings, ok, failOn };
|
|
52587
|
-
}
|
|
52588
|
-
function baselineSummary(diff, lang = "fr") {
|
|
52589
|
-
const out2 = [];
|
|
52590
|
-
const blocking = diff.newFindings.filter((f) => RANK[f.severity] <= RANK[diff.failOn]);
|
|
52591
|
-
if (diff.ok) {
|
|
52592
|
-
out2.push(
|
|
52593
|
-
lang === "fr" ? `\u2713 Aucune nouvelle non-conformit\xE9 \u2265 ${diff.failOn} (${diff.newFindings.length} nouvelle(s) au total, ${diff.fixedFindings.length} corrig\xE9e(s)).` : `\u2713 No new non-conformity \u2265 ${diff.failOn} (${diff.newFindings.length} new total, ${diff.fixedFindings.length} fixed).`
|
|
52594
|
-
);
|
|
52595
|
-
} else {
|
|
52596
|
-
out2.push(
|
|
52597
|
-
lang === "fr" ? `\u2717 ${blocking.length} nouvelle(s) non-conformit\xE9(s) \u2265 ${diff.failOn} introduite(s) :` : `\u2717 ${blocking.length} new non-conformity(ies) \u2265 ${diff.failOn} introduced:`
|
|
52598
|
-
);
|
|
52599
|
-
for (const f of blocking) out2.push(` [${f.severity}] ${f.ruleId} (WCAG ${f.criteriaId}) \u2014 ${f.file}:${f.line} (${f.selectorHint})`);
|
|
52600
|
-
}
|
|
52601
|
-
return out2.join("\n");
|
|
52602
|
-
}
|
|
52603
|
-
|
|
52604
53048
|
// src/init.ts
|
|
52605
|
-
import { writeFileSync as
|
|
52606
|
-
import { execFileSync as
|
|
52607
|
-
import { join as
|
|
53049
|
+
import { writeFileSync as writeFileSync13, mkdirSync as mkdirSync10, chmodSync } from "fs";
|
|
53050
|
+
import { execFileSync as execFileSync5 } from "child_process";
|
|
53051
|
+
import { join as join34 } from "path";
|
|
52608
53052
|
var EN_SEV = { bloquant: "blocking", majeur: "major", mineur: "minor" };
|
|
52609
53053
|
function repoRoot() {
|
|
52610
53054
|
try {
|
|
52611
|
-
return
|
|
53055
|
+
return execFileSync5("git", ["rev-parse", "--show-toplevel"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
52612
53056
|
} catch {
|
|
52613
53057
|
return null;
|
|
52614
53058
|
}
|
|
@@ -52700,18 +53144,18 @@ jobs:
|
|
|
52700
53144
|
`;
|
|
52701
53145
|
}
|
|
52702
53146
|
function writeHook(root, enginePath, failOn, mode = "staged") {
|
|
52703
|
-
const dir =
|
|
52704
|
-
|
|
52705
|
-
const path =
|
|
52706
|
-
|
|
53147
|
+
const dir = join34(root, ".git", "hooks");
|
|
53148
|
+
mkdirSync10(dir, { recursive: true });
|
|
53149
|
+
const path = join34(dir, "pre-commit");
|
|
53150
|
+
writeFileSync13(path, mode === "baseline" ? hookScript(enginePath, failOn) : stagedHookScript(enginePath, failOn));
|
|
52707
53151
|
chmodSync(path, 493);
|
|
52708
53152
|
return path;
|
|
52709
53153
|
}
|
|
52710
53154
|
function writeCi(root, enginePath, failOn) {
|
|
52711
|
-
const dir =
|
|
52712
|
-
|
|
52713
|
-
const path =
|
|
52714
|
-
|
|
53155
|
+
const dir = join34(root, ".github", "workflows");
|
|
53156
|
+
mkdirSync10(dir, { recursive: true });
|
|
53157
|
+
const path = join34(dir, "a11y.yml");
|
|
53158
|
+
writeFileSync13(path, ciWorkflow(enginePath, failOn));
|
|
52715
53159
|
return path;
|
|
52716
53160
|
}
|
|
52717
53161
|
|
|
@@ -53012,7 +53456,7 @@ function stepSummary(result, opts = {}) {
|
|
|
53012
53456
|
|
|
53013
53457
|
// src/dev.ts
|
|
53014
53458
|
import { createServer } from "http";
|
|
53015
|
-
import { join as
|
|
53459
|
+
import { join as join35 } from "path";
|
|
53016
53460
|
var DEV_DEFAULT_PORT = 4111;
|
|
53017
53461
|
function criterionLabel3(f, standard) {
|
|
53018
53462
|
if (isCore(standard)) return `WCAG ${f.criteriaId}`;
|
|
@@ -53278,13 +53722,13 @@ function auditCollected(root, payload) {
|
|
|
53278
53722
|
...payload.axtree ? { axtree: payload.axtree } : {},
|
|
53279
53723
|
...payload.css ? { css: payload.css } : {}
|
|
53280
53724
|
});
|
|
53281
|
-
return { ok: true, result: runAudit({ inputs: [
|
|
53725
|
+
return { ok: true, result: runAudit({ inputs: [join35(dir, "dom.html")] }) };
|
|
53282
53726
|
}
|
|
53283
53727
|
function projectPages(root) {
|
|
53284
53728
|
const snaps = readSnapshots(root);
|
|
53285
53729
|
if (!snaps.length) return { result: null, pages: [] };
|
|
53286
53730
|
const scope = pageScopesFrom(snaps);
|
|
53287
|
-
const result = runAudit({ inputs: [
|
|
53731
|
+
const result = runAudit({ inputs: [join35(root, ".ultra11y/pages")] });
|
|
53288
53732
|
result.scope.pages = scope;
|
|
53289
53733
|
attributePages(result, scope);
|
|
53290
53734
|
return { result, pages: derivePages(result, scope) };
|
|
@@ -53608,287 +54052,12 @@ function e2eSetupPlan(runners, paths, lang = "en") {
|
|
|
53608
54052
|
return out2.join("\n");
|
|
53609
54053
|
}
|
|
53610
54054
|
|
|
53611
|
-
// src/config.ts
|
|
53612
|
-
import { existsSync as existsSync19, statSync as statSync9 } from "fs";
|
|
53613
|
-
import { join as join34, isAbsolute as isAbsolute2 } from "path";
|
|
53614
|
-
|
|
53615
|
-
// src/pack.ts
|
|
53616
|
-
import { existsSync as existsSync18 } from "fs";
|
|
53617
|
-
function exampleParses(ex) {
|
|
53618
|
-
for (const code2 of [ex.bad, ex.good]) {
|
|
53619
|
-
if (!code2) continue;
|
|
53620
|
-
if (ex.lang === "jsx") {
|
|
53621
|
-
if (!parseJsxAst(code2)) return `unparseable JSX example: ${code2.slice(0, 48)}\u2026`;
|
|
53622
|
-
} else if (ex.lang === "html") {
|
|
53623
|
-
try {
|
|
53624
|
-
parseHtml(code2, "<guidance>.html", false);
|
|
53625
|
-
} catch (e) {
|
|
53626
|
-
return `unparseable HTML example (${e instanceof Error ? e.message : String(e)})`;
|
|
53627
|
-
}
|
|
53628
|
-
}
|
|
53629
|
-
}
|
|
53630
|
-
return null;
|
|
53631
|
-
}
|
|
53632
|
-
function checkGuidance(ds, pack) {
|
|
53633
|
-
const errors = [];
|
|
53634
|
-
const warnings = [];
|
|
53635
|
-
if (typeof ds !== "object" || ds === null || !Array.isArray(ds.entries)) {
|
|
53636
|
-
errors.push("guidance: must be an object with an entries[] array");
|
|
53637
|
-
return { errors, warnings };
|
|
53638
|
-
}
|
|
53639
|
-
const dataset = ds;
|
|
53640
|
-
if (dataset.pack && dataset.pack !== pack.key) warnings.push(`guidance: dataset.pack "${dataset.pack}" \u2260 pack key "${pack.key}"`);
|
|
53641
|
-
dataset.entries.forEach((e, i2) => {
|
|
53642
|
-
const at = `guidance.entries[${i2}]${e.id ? ` (${e.id})` : ""}`;
|
|
53643
|
-
const crit = e.criterionId ? getCriterion(pack, e.criterionId) : void 0;
|
|
53644
|
-
if (!e.criterionId) {
|
|
53645
|
-
errors.push(`${at}: criterionId is unresolved \u2014 resolve it to a real ${pack.key} criterion (no fabrication)`);
|
|
53646
|
-
} else if (!crit) {
|
|
53647
|
-
errors.push(`${at}: criterionId "${e.criterionId}" does not exist in ${pack.key}`);
|
|
53648
|
-
}
|
|
53649
|
-
if (!Array.isArray(e.wcag) || e.wcag.length === 0) {
|
|
53650
|
-
errors.push(`${at}: must map to at least one WCAG SC`);
|
|
53651
|
-
} else {
|
|
53652
|
-
for (const sc of e.wcag) {
|
|
53653
|
-
switch (classifySc(sc)) {
|
|
53654
|
-
case "malformed":
|
|
53655
|
-
errors.push(`${at}: malformed SC "${sc}"`);
|
|
53656
|
-
break;
|
|
53657
|
-
case "unknown":
|
|
53658
|
-
errors.push(`${at}: SC "${sc}" is not a recognized WCAG success criterion \u2014 fabricated?`);
|
|
53659
|
-
break;
|
|
53660
|
-
case "out-of-core":
|
|
53661
|
-
warnings.push(`${at}: SC "${sc}" is a real WCAG AAA success criterion, outside the WCAG 2.2 AA core`);
|
|
53662
|
-
break;
|
|
53663
|
-
case "removed":
|
|
53664
|
-
warnings.push(`${at}: SC "${sc}" is a real but removed WCAG success criterion (obsolete)`);
|
|
53665
|
-
break;
|
|
53666
|
-
}
|
|
53667
|
-
}
|
|
53668
|
-
if (crit) {
|
|
53669
|
-
const extra = e.wcag.filter((sc) => !crit.wcag.includes(sc));
|
|
53670
|
-
if (extra.length) warnings.push(`${at}: SC(s) ${extra.join(", ")} not in criterion ${crit.id} mapping (${crit.wcag.join(", ")})`);
|
|
53671
|
-
}
|
|
53672
|
-
}
|
|
53673
|
-
if (!Array.isArray(e.examples) || e.examples.length === 0) {
|
|
53674
|
-
warnings.push(`${at}: no code examples`);
|
|
53675
|
-
} else {
|
|
53676
|
-
e.examples.forEach((ex, j) => {
|
|
53677
|
-
const bad = exampleParses(ex);
|
|
53678
|
-
if (bad) errors.push(`${at}.examples[${j}]: ${bad}`);
|
|
53679
|
-
});
|
|
53680
|
-
}
|
|
53681
|
-
});
|
|
53682
|
-
return { errors, warnings };
|
|
53683
|
-
}
|
|
53684
|
-
function runPackCheck(packPath, guidancePath) {
|
|
53685
|
-
const errors = [];
|
|
53686
|
-
const warnings = [];
|
|
53687
|
-
if (!existsSync18(packPath)) return { ok: false, errors: [`pack file not found: ${packPath}`], warnings };
|
|
53688
|
-
let raw;
|
|
53689
|
-
try {
|
|
53690
|
-
raw = JSON.parse(readText(packPath));
|
|
53691
|
-
} catch {
|
|
53692
|
-
return { ok: false, errors: [`${packPath}: not valid JSON`], warnings };
|
|
53693
|
-
}
|
|
53694
|
-
const v = validatePack(raw);
|
|
53695
|
-
for (const issue of v.issues) (issue.severity === "error" ? errors : warnings).push(`${issue.path ? `${issue.path}: ` : ""}${issue.message}`);
|
|
53696
|
-
if (v.ok && v.pack) {
|
|
53697
|
-
const known = new Set(ruleIds());
|
|
53698
|
-
const isNamespaced = (r) => /^(axe:|dyn-|agent:)/.test(r);
|
|
53699
|
-
const ownRuleIds = new Set((v.pack.rules ?? []).map((r) => `pack:${v.pack.key}:${r.id}`));
|
|
53700
|
-
const resolvable = (r) => isNamespaced(r) || known.has(r) || r.startsWith(`pack:${v.pack.key}:`) || ownRuleIds.has(r);
|
|
53701
|
-
for (const c2 of v.pack.criteria) {
|
|
53702
|
-
for (const r of c2.appliesTo?.ruleIds ?? []) {
|
|
53703
|
-
if (!resolvable(r)) warnings.push(`criteria "${c2.id}": appliesTo ruleId "${r}" is not a known engine rule (renamed/future?)`);
|
|
53704
|
-
}
|
|
53705
|
-
}
|
|
53706
|
-
for (const key of Object.keys(v.pack.overrides ?? {})) {
|
|
53707
|
-
if (!resolvable(key)) warnings.push(`overrides: ruleId "${key}" is not a known engine rule or a rule of this pack (renamed/future?)`);
|
|
53708
|
-
}
|
|
53709
|
-
}
|
|
53710
|
-
if (v.ok && v.pack && guidancePath) {
|
|
53711
|
-
if (!existsSync18(guidancePath)) {
|
|
53712
|
-
errors.push(`guidance file not found: ${guidancePath}`);
|
|
53713
|
-
} else {
|
|
53714
|
-
let gRaw;
|
|
53715
|
-
try {
|
|
53716
|
-
gRaw = JSON.parse(readText(guidancePath));
|
|
53717
|
-
} catch {
|
|
53718
|
-
errors.push(`${guidancePath}: not valid JSON`);
|
|
53719
|
-
}
|
|
53720
|
-
if (gRaw !== void 0) {
|
|
53721
|
-
const g = checkGuidance(gRaw, v.pack);
|
|
53722
|
-
errors.push(...g.errors);
|
|
53723
|
-
warnings.push(...g.warnings);
|
|
53724
|
-
}
|
|
53725
|
-
}
|
|
53726
|
-
}
|
|
53727
|
-
return { ok: errors.length === 0, errors, warnings };
|
|
53728
|
-
}
|
|
53729
|
-
function packScaffold() {
|
|
53730
|
-
return JSON.stringify(
|
|
53731
|
-
{
|
|
53732
|
-
key: "mypack",
|
|
53733
|
-
name: "MyPack",
|
|
53734
|
-
fullName: "My national accessibility standard",
|
|
53735
|
-
org: "Org",
|
|
53736
|
-
country: "XX",
|
|
53737
|
-
baseVersion: "1.0",
|
|
53738
|
-
wcagVersion: "2.2",
|
|
53739
|
-
locales: ["en"],
|
|
53740
|
-
defaultLocale: "en",
|
|
53741
|
-
license: "\u2026",
|
|
53742
|
-
source: "https://\u2026",
|
|
53743
|
-
attribution: "\u2026 \xA9 \u2026",
|
|
53744
|
-
idPattern: "^\\d+\\.\\d+$",
|
|
53745
|
-
vocabulary: {
|
|
53746
|
-
theme: { en: "Theme" },
|
|
53747
|
-
criterion: { en: "Criterion" },
|
|
53748
|
-
test: { en: "Test" },
|
|
53749
|
-
conformant: { en: "Conformant" },
|
|
53750
|
-
nonConformant: { en: "Non-conformant" },
|
|
53751
|
-
notApplicable: { en: "Not applicable" },
|
|
53752
|
-
auditorHeading: { en: "Accessibility criterion" }
|
|
53753
|
-
},
|
|
53754
|
-
themes: [{ number: 1, name: { en: "Theme one" }, count: 1 }],
|
|
53755
|
-
criteria: [{ id: "1.1", theme: 1, title: { en: "Criterion one" }, titlePlain: { en: "Criterion one" }, wcag: ["1.1.1"] }]
|
|
53756
|
-
},
|
|
53757
|
-
null,
|
|
53758
|
-
2
|
|
53759
|
-
);
|
|
53760
|
-
}
|
|
53761
|
-
|
|
53762
|
-
// src/config.ts
|
|
53763
|
-
var CONFIG_FILE = ".ultra11yrc.json";
|
|
53764
|
-
function loadConfig(cwd) {
|
|
53765
|
-
const p = join34(cwd, CONFIG_FILE);
|
|
53766
|
-
if (!existsSync19(p)) return null;
|
|
53767
|
-
let parsed;
|
|
53768
|
-
try {
|
|
53769
|
-
parsed = JSON.parse(readText(p));
|
|
53770
|
-
} catch {
|
|
53771
|
-
throw new Error(`${CONFIG_FILE}: not valid JSON`);
|
|
53772
|
-
}
|
|
53773
|
-
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
53774
|
-
throw new Error(`${CONFIG_FILE}: must be a JSON object`);
|
|
53775
|
-
}
|
|
53776
|
-
return parsed;
|
|
53777
|
-
}
|
|
53778
|
-
function packPaths(path) {
|
|
53779
|
-
if (!existsSync19(path)) return null;
|
|
53780
|
-
if (statSync9(path).isDirectory()) {
|
|
53781
|
-
const pack = join34(path, "pack.json");
|
|
53782
|
-
if (!existsSync19(pack)) return null;
|
|
53783
|
-
const glossary = join34(path, "glossary.json");
|
|
53784
|
-
const guidance = join34(path, "guidance.json");
|
|
53785
|
-
return {
|
|
53786
|
-
pack,
|
|
53787
|
-
glossary: existsSync19(glossary) ? glossary : void 0,
|
|
53788
|
-
guidance: existsSync19(guidance) ? guidance : void 0
|
|
53789
|
-
};
|
|
53790
|
-
}
|
|
53791
|
-
return { pack: path };
|
|
53792
|
-
}
|
|
53793
|
-
function loadRuntimeStandards(cwd, packFlags, onWarn, override = false) {
|
|
53794
|
-
const result = { errors: [], loadedPacks: [], loadedGuidance: [] };
|
|
53795
|
-
let config = null;
|
|
53796
|
-
try {
|
|
53797
|
-
config = loadConfig(cwd);
|
|
53798
|
-
} catch (e) {
|
|
53799
|
-
result.errors.push(e instanceof Error ? e.message : String(e));
|
|
53800
|
-
return result;
|
|
53801
|
-
}
|
|
53802
|
-
result.defaultStandard = config?.standard;
|
|
53803
|
-
for (const raw of [...config?.packs ?? [], ...packFlags]) {
|
|
53804
|
-
const paths = packPaths(isAbsolute2(raw) ? raw : join34(cwd, raw));
|
|
53805
|
-
if (!paths) {
|
|
53806
|
-
result.errors.push(`--pack ${raw}: not found (expected a pack JSON file or a directory with pack.json)`);
|
|
53807
|
-
continue;
|
|
53808
|
-
}
|
|
53809
|
-
let packObj;
|
|
53810
|
-
try {
|
|
53811
|
-
packObj = JSON.parse(readText(paths.pack));
|
|
53812
|
-
} catch {
|
|
53813
|
-
result.errors.push(`--pack ${paths.pack}: not valid JSON`);
|
|
53814
|
-
continue;
|
|
53815
|
-
}
|
|
53816
|
-
let glossary = {};
|
|
53817
|
-
if (paths.glossary) {
|
|
53818
|
-
try {
|
|
53819
|
-
glossary = JSON.parse(readText(paths.glossary));
|
|
53820
|
-
} catch {
|
|
53821
|
-
onWarn(`ultra11y: ${paths.glossary} is not valid JSON \u2014 ignoring glossary.`);
|
|
53822
|
-
}
|
|
53823
|
-
}
|
|
53824
|
-
const v = registerRuntimePack(packObj, glossary, { override });
|
|
53825
|
-
for (const issue of v.issues) if (issue.severity === "warn") onWarn(`ultra11y: ${raw}: ${issue.path ? `${issue.path}: ` : ""}${issue.message}`);
|
|
53826
|
-
if (!v.ok || !v.pack) {
|
|
53827
|
-
result.errors.push(`--pack ${paths.pack}: invalid pack
|
|
53828
|
-
${formatIssues(v.issues).join("\n")}`);
|
|
53829
|
-
continue;
|
|
53830
|
-
}
|
|
53831
|
-
result.loadedPacks.push(v.pack.key);
|
|
53832
|
-
if (paths.guidance) loadGuidanceFile(paths.guidance, result, onWarn);
|
|
53833
|
-
}
|
|
53834
|
-
for (const g of config?.guidance ?? []) loadGuidanceFile(isAbsolute2(g) ? g : join34(cwd, g), result, onWarn);
|
|
53835
|
-
for (const sm of config?.secondaryMappings ?? []) {
|
|
53836
|
-
if (!sm || typeof sm.standard !== "string" || typeof sm.ruleId !== "string" || typeof sm.criterion !== "string") {
|
|
53837
|
-
result.errors.push("secondaryMappings: each entry must be { standard, ruleId, criterion, note? }");
|
|
53838
|
-
continue;
|
|
53839
|
-
}
|
|
53840
|
-
if (!getPack(sm.standard)) {
|
|
53841
|
-
result.errors.push(`secondaryMappings: unknown standard "${sm.standard}" (register its pack first; the WCAG core has no pack criteria)`);
|
|
53842
|
-
continue;
|
|
53843
|
-
}
|
|
53844
|
-
enableSecondaryMapping(sm.standard, { ruleId: sm.ruleId, criterion: sm.criterion, note: sm.note });
|
|
53845
|
-
}
|
|
53846
|
-
return result;
|
|
53847
|
-
}
|
|
53848
|
-
function loadGuidanceFile(path, result, onWarn) {
|
|
53849
|
-
if (!existsSync19(path)) {
|
|
53850
|
-
onWarn(`ultra11y: guidance ${path} not found \u2014 skipping.`);
|
|
53851
|
-
return;
|
|
53852
|
-
}
|
|
53853
|
-
let ds;
|
|
53854
|
-
try {
|
|
53855
|
-
ds = JSON.parse(readText(path));
|
|
53856
|
-
} catch {
|
|
53857
|
-
onWarn(`ultra11y: guidance ${path} is not valid JSON \u2014 skipping.`);
|
|
53858
|
-
return;
|
|
53859
|
-
}
|
|
53860
|
-
if (!ds || typeof ds.pack !== "string" || !Array.isArray(ds.entries)) {
|
|
53861
|
-
onWarn(`ultra11y: guidance ${path} is not a valid dataset (needs { pack, entries[] }) \u2014 skipping.`);
|
|
53862
|
-
return;
|
|
53863
|
-
}
|
|
53864
|
-
const pack = getPack(ds.pack);
|
|
53865
|
-
if (pack) {
|
|
53866
|
-
const g = checkGuidance(ds, pack);
|
|
53867
|
-
for (const w of g.warnings) onWarn(`ultra11y: guidance ${path}: ${w}`);
|
|
53868
|
-
if (g.errors.length) {
|
|
53869
|
-
result.errors.push(`guidance ${path}: invalid
|
|
53870
|
-
${g.errors.map((e) => ` \u2717 ${e}`).join("\n")}`);
|
|
53871
|
-
return;
|
|
53872
|
-
}
|
|
53873
|
-
} else {
|
|
53874
|
-
for (const e of ds.entries) {
|
|
53875
|
-
if (!Array.isArray(e?.wcag) || !Array.isArray(e?.examples)) {
|
|
53876
|
-
onWarn(`ultra11y: guidance ${path}: entry "${e?.id ?? "?"}" missing wcag[]/examples[] \u2014 skipping dataset.`);
|
|
53877
|
-
return;
|
|
53878
|
-
}
|
|
53879
|
-
}
|
|
53880
|
-
}
|
|
53881
|
-
if (hasGuidance(ds.pack)) onWarn(`ultra11y: guidance ${path} overrides the existing guidance for pack "${ds.pack}".`);
|
|
53882
|
-
registerRuntimeGuidance(ds);
|
|
53883
|
-
result.loadedGuidance.push(path);
|
|
53884
|
-
}
|
|
53885
|
-
|
|
53886
54055
|
// src/orchestrate.ts
|
|
53887
|
-
import { existsSync as
|
|
53888
|
-
import { join as
|
|
54056
|
+
import { existsSync as existsSync21, mkdirSync as mkdirSync11, readFileSync as readFileSync17, rmSync as rmSync4, writeFileSync as writeFileSync14 } from "fs";
|
|
54057
|
+
import { join as join37, resolve as resolve7 } from "path";
|
|
53889
54058
|
|
|
53890
54059
|
// src/orchestrate-templates.ts
|
|
53891
|
-
import { join as
|
|
54060
|
+
import { join as join36 } from "path";
|
|
53892
54061
|
var ONE_WRITER_FOOTER = `
|
|
53893
54062
|
## Return, don't write
|
|
53894
54063
|
|
|
@@ -53960,7 +54129,7 @@ var PHASE_SPECS = {
|
|
|
53960
54129
|
title: "Adjudicate",
|
|
53961
54130
|
schema: ADJUDICATE_SCHEMA,
|
|
53962
54131
|
description: (n) => `Adjudicate the ${n} residual judgment criterion(ia) of an ultra11y audit (fan-out, fail-closed fold)`,
|
|
53963
|
-
applyHint: (engine, worklist, run2) => `node ${engine} verify --apply ${worklist} --in ${
|
|
54132
|
+
applyHint: (engine, worklist, run2) => `node ${engine} verify --apply ${worklist} --in ${join36(run2, "audit-latest.json")} --out ${run2}`
|
|
53964
54133
|
},
|
|
53965
54134
|
"verify-report": {
|
|
53966
54135
|
role: "refuter",
|
|
@@ -53982,7 +54151,7 @@ function toBatches(ids, batchSize) {
|
|
|
53982
54151
|
}
|
|
53983
54152
|
function phaseWorkflowScript(ph, runAbs, engineAbs, batchSize) {
|
|
53984
54153
|
const spec = phaseSpec(ph.name);
|
|
53985
|
-
const scriptPath =
|
|
54154
|
+
const scriptPath = join36(runAbs, "orchestration", `${ph.name}.workflow.mjs`);
|
|
53986
54155
|
const meta2 = { name: `ultra11y-${ph.name}`, description: spec.description(ph.items), phases: [{ title: spec.title }] };
|
|
53987
54156
|
return [
|
|
53988
54157
|
`export const meta = ${JSON.stringify(meta2)}`,
|
|
@@ -54026,7 +54195,7 @@ function agentContracts(runAbs, engineAbs) {
|
|
|
54026
54195
|
|
|
54027
54196
|
You adjudicate the residual judgment criteria of an ultra11y audit \u2014 the ones the deterministic engine could not decide (alt-text relevance, link purpose in context, reading order\u2026). The ACTIVE STANDARD is recorded in the worklist's \`standard\` field: under a country standard (e.g. \`rgaa\`) the items are that standard's OWN criteria, each carrying its numbered tests \u2014 not WCAG success criteria.
|
|
54028
54197
|
|
|
54029
|
-
Worklist: \`${
|
|
54198
|
+
Worklist: \`${join36(runAbs, "ADJUDICATE.todo.json")}\` (an object with \`kind: "adjudication"\` and \`items[]\`). Handle ONLY the criteria whose \`criteriaId\` is named in your prompt (\`ITEMS=<id,\u2026>\`).
|
|
54030
54199
|
|
|
54031
54200
|
For EACH of your criteria:
|
|
54032
54201
|
|
|
@@ -54044,7 +54213,7 @@ ${footer}`,
|
|
|
54044
54213
|
|
|
54045
54214
|
You are an adversarial skeptic verifying the non-conformities of an ultra11y report. Your job is to try to REFUTE each claim: assume it is wrong until the source proves it.
|
|
54046
54215
|
|
|
54047
|
-
Worklist: \`${
|
|
54216
|
+
Worklist: \`${join36(runAbs, "VERIFY.todo.json")}\` (a JSON array; each entry has \`n\`, \`criteriaId\`, \`file\`, \`line\`, \`selector\`, \`claim\`). Handle ONLY the entries whose \`n\` is named in your prompt (\`ITEMS=<n,\u2026>\`).
|
|
54048
54217
|
|
|
54049
54218
|
For EACH of your entries:
|
|
54050
54219
|
|
|
@@ -54080,14 +54249,14 @@ ${status}
|
|
|
54080
54249
|
|
|
54081
54250
|
## The loop (play every role yourself, one item at a time)
|
|
54082
54251
|
|
|
54083
|
-
1. **Audit** (if not done): \`${engine} audit "<globs>" --graph --out ${runAbs}\` \u2192 \`${
|
|
54084
|
-
2. **Adjudicate the residual criteria** \u2014 \`${engine} verify --manual --in ${
|
|
54085
|
-
3. **Report**: \`${engine} report --in ${
|
|
54086
|
-
4. **Verify the report's claims** \u2014 \`${engine} verify --report <the report .md> --out ${runAbs}\` writes \`${
|
|
54252
|
+
1. **Audit** (if not done): \`${engine} audit "<globs>" --graph --out ${runAbs}\` \u2192 \`${join36(runAbs, "audit-latest.json")}\`.
|
|
54253
|
+
2. **Adjudicate the residual criteria** \u2014 \`${engine} verify --manual --in ${join36(runAbs, "audit-latest.json")} --out ${runAbs}\` writes \`${join36(runAbs, "ADJUDICATE.todo.json")}\`. For EVERY item, apply \`${join36(runAbs, "orchestration", "agents", "adjudicator.md")}\` yourself (read the evidence, rule C/NC/NA/manual, fill the required justification/findings/reason IN the todo file). Then fold, fail-closed: \`${engine} verify --apply ${join36(runAbs, "ADJUDICATE.todo.json")} --in ${join36(runAbs, "audit-latest.json")} --out ${runAbs}\`.
|
|
54254
|
+
3. **Report**: \`${engine} report --in ${join36(runAbs, "audit-latest.json")} --out ${runAbs}\`.
|
|
54255
|
+
4. **Verify the report's claims** \u2014 \`${engine} verify --report <the report .md> --out ${runAbs}\` writes \`${join36(runAbs, "VERIFY.todo.json")}\`. For EVERY entry, apply \`${join36(runAbs, "orchestration", "agents", "refuter.md")}\` yourself (open file:line, verdict supported/partial/refuted/unsupported + note IN the todo file). Then: \`${engine} verify --apply ${join36(runAbs, "VERIFY.todo.json")} --report <the report .md>\`.
|
|
54087
54256
|
5. **Gate**: \`${engine} check --report <the report .md> --semantic\` must exit 0 before presenting anything.
|
|
54088
54257
|
6. **Fix & re-audit**: \`${engine} fix <globs> --write --iterate\`, hand-apply the judgment fixes, then loop from step 1 until the gate stays green.
|
|
54089
54258
|
|
|
54090
|
-
With subagents available, prefer the emitted workflows instead: \`orchestrate --run ${runAbs} --phase <p>\` then \`Workflow({ scriptPath: "${
|
|
54259
|
+
With subagents available, prefer the emitted workflows instead: \`orchestrate --run ${runAbs} --phase <p>\` then \`Workflow({ scriptPath: "${join36(runAbs, "orchestration", "<p>.workflow.mjs")}" })\` \u2014 you stay the sole writer either way.
|
|
54091
54260
|
`;
|
|
54092
54261
|
}
|
|
54093
54262
|
|
|
@@ -54097,10 +54266,10 @@ var SMALL_WORKLIST = 3;
|
|
|
54097
54266
|
var BATCH_SIZE = 8;
|
|
54098
54267
|
function listPhases(runDir, engineAbs) {
|
|
54099
54268
|
const run2 = resolve7(runDir);
|
|
54100
|
-
const adjPath =
|
|
54269
|
+
const adjPath = join37(run2, "ADJUDICATE.todo.json");
|
|
54101
54270
|
let adjIds = [];
|
|
54102
54271
|
let adjReady = false;
|
|
54103
|
-
if (
|
|
54272
|
+
if (existsSync21(adjPath)) {
|
|
54104
54273
|
try {
|
|
54105
54274
|
const f = JSON.parse(readFileSync17(adjPath, "utf8"));
|
|
54106
54275
|
if (f && f.kind === "adjudication" && Array.isArray(f.items)) {
|
|
@@ -54110,10 +54279,10 @@ function listPhases(runDir, engineAbs) {
|
|
|
54110
54279
|
} catch {
|
|
54111
54280
|
}
|
|
54112
54281
|
}
|
|
54113
|
-
const verPath =
|
|
54282
|
+
const verPath = join37(run2, "VERIFY.todo.json");
|
|
54114
54283
|
let verIds = [];
|
|
54115
54284
|
let verReady = false;
|
|
54116
|
-
if (
|
|
54285
|
+
if (existsSync21(verPath)) {
|
|
54117
54286
|
try {
|
|
54118
54287
|
const items = JSON.parse(readFileSync17(verPath, "utf8"));
|
|
54119
54288
|
if (Array.isArray(items)) {
|
|
@@ -54130,7 +54299,7 @@ function listPhases(runDir, engineAbs) {
|
|
|
54130
54299
|
worklist: adjPath,
|
|
54131
54300
|
items: adjIds.length,
|
|
54132
54301
|
ids: adjIds,
|
|
54133
|
-
prerequisite: `node ${engineAbs} verify --manual --in ${
|
|
54302
|
+
prerequisite: `node ${engineAbs} verify --manual --in ${join37(run2, "audit-latest.json")} --out ${run2}`
|
|
54134
54303
|
},
|
|
54135
54304
|
{
|
|
54136
54305
|
name: "verify-report",
|
|
@@ -54144,7 +54313,7 @@ function listPhases(runDir, engineAbs) {
|
|
|
54144
54313
|
}
|
|
54145
54314
|
function orchestrateRun(runDir, engineAbs, opts = {}) {
|
|
54146
54315
|
const run2 = resolve7(runDir);
|
|
54147
|
-
if (!
|
|
54316
|
+
if (!existsSync21(run2)) {
|
|
54148
54317
|
return { exitCode: 2, written: [], notices: [], errors: [`run dir not found: ${run2}`], phases: [] };
|
|
54149
54318
|
}
|
|
54150
54319
|
const phases = listPhases(run2, engineAbs);
|
|
@@ -54171,22 +54340,22 @@ function orchestrateRun(runDir, engineAbs, opts = {}) {
|
|
|
54171
54340
|
}
|
|
54172
54341
|
selected = [ph];
|
|
54173
54342
|
}
|
|
54174
|
-
const orchDir =
|
|
54175
|
-
const agentsDir =
|
|
54176
|
-
|
|
54177
|
-
|
|
54343
|
+
const orchDir = join37(run2, "orchestration");
|
|
54344
|
+
const agentsDir = join37(orchDir, "agents");
|
|
54345
|
+
mkdirSync11(join37(orchDir, "out"), { recursive: true });
|
|
54346
|
+
mkdirSync11(agentsDir, { recursive: true });
|
|
54178
54347
|
const written = [];
|
|
54179
54348
|
const notices = [];
|
|
54180
54349
|
for (const [name2, content] of Object.entries(agentContracts(run2, engineAbs))) {
|
|
54181
|
-
const p =
|
|
54182
|
-
|
|
54350
|
+
const p = join37(agentsDir, `${name2}.md`);
|
|
54351
|
+
writeFileSync14(p, content);
|
|
54183
54352
|
written.push(p);
|
|
54184
54353
|
}
|
|
54185
54354
|
const emitted = new Set(opts.eco ? [] : selected.filter((p) => p.items > 0).map((p) => p.name));
|
|
54186
54355
|
for (const ph of phases) {
|
|
54187
54356
|
if (emitted.has(ph.name)) continue;
|
|
54188
|
-
const stale =
|
|
54189
|
-
if (
|
|
54357
|
+
const stale = join37(orchDir, `${ph.name}.workflow.mjs`);
|
|
54358
|
+
if (existsSync21(stale)) {
|
|
54190
54359
|
rmSync4(stale, { force: true });
|
|
54191
54360
|
notices.push(`phase "${ph.name}": stale workflow removed \u2014 its worklist ${ph.ready ? "is now empty" : "no longer exists"}.`);
|
|
54192
54361
|
}
|
|
@@ -54200,13 +54369,13 @@ function orchestrateRun(runDir, engineAbs, opts = {}) {
|
|
|
54200
54369
|
if (ph.items <= SMALL_WORKLIST) {
|
|
54201
54370
|
notices.push(`phase "${ph.name}": only ${ph.items} item(s) \u2014 the sequential --eco path is equivalent and cheaper.`);
|
|
54202
54371
|
}
|
|
54203
|
-
const p =
|
|
54204
|
-
|
|
54372
|
+
const p = join37(orchDir, `${ph.name}.workflow.mjs`);
|
|
54373
|
+
writeFileSync14(p, phaseWorkflowScript(ph, run2, engineAbs, BATCH_SIZE));
|
|
54205
54374
|
written.push(p);
|
|
54206
54375
|
}
|
|
54207
54376
|
}
|
|
54208
|
-
const rb =
|
|
54209
|
-
|
|
54377
|
+
const rb = join37(orchDir, "RUNBOOK.md");
|
|
54378
|
+
writeFileSync14(rb, runbookMd(phases, run2, engineAbs));
|
|
54210
54379
|
written.push(rb);
|
|
54211
54380
|
return { exitCode: 0, written, notices, errors: [], phases };
|
|
54212
54381
|
}
|
|
@@ -54215,8 +54384,8 @@ function orchestrateRun(runDir, engineAbs, opts = {}) {
|
|
|
54215
54384
|
import { createInterface as createInterface2 } from "readline";
|
|
54216
54385
|
|
|
54217
54386
|
// src/mcp/handlers.ts
|
|
54218
|
-
import { existsSync as
|
|
54219
|
-
import { isAbsolute as isAbsolute3, join as
|
|
54387
|
+
import { existsSync as existsSync22, readFileSync as readFileSync18, realpathSync as realpathSync2, statSync as statSync10 } from "fs";
|
|
54388
|
+
import { isAbsolute as isAbsolute3, join as join38, resolve as resolve8, sep as sep4 } from "path";
|
|
54220
54389
|
|
|
54221
54390
|
// src/project-lock.ts
|
|
54222
54391
|
var chains = /* @__PURE__ */ new Map();
|
|
@@ -54264,7 +54433,7 @@ function requiredCwd(args2, defaults) {
|
|
|
54264
54433
|
const cwd = str2(args2.cwd) ?? defaults.defaultCwd;
|
|
54265
54434
|
if (!cwd) throw new ToolError("`cwd` is required: an absolute path to the project root.");
|
|
54266
54435
|
const abs = resolve8(cwd);
|
|
54267
|
-
if (!
|
|
54436
|
+
if (!existsSync22(abs)) throw new ToolError(`project root not found: ${abs}`);
|
|
54268
54437
|
if (!statSync10(abs).isDirectory()) throw new ToolError(`\`cwd\` is not a directory: ${abs}`);
|
|
54269
54438
|
return abs;
|
|
54270
54439
|
}
|
|
@@ -54409,8 +54578,8 @@ function handlePackCheck(args2, cwd) {
|
|
|
54409
54578
|
return { cwd, ...res };
|
|
54410
54579
|
}
|
|
54411
54580
|
function handleSampleCheck(cwd) {
|
|
54412
|
-
const file =
|
|
54413
|
-
if (!
|
|
54581
|
+
const file = join38(cwd, ".ultra11yrc.json");
|
|
54582
|
+
if (!existsSync22(file)) {
|
|
54414
54583
|
throw new ToolError(`no .ultra11yrc.json at ${cwd} \u2014 a page sample must be declared before it can be linted.`);
|
|
54415
54584
|
}
|
|
54416
54585
|
let raw;
|
|
@@ -54453,7 +54622,7 @@ function handleInit(args2, cwd) {
|
|
|
54453
54622
|
const enginePath = resolve8("scripts/ultra11y.mjs");
|
|
54454
54623
|
const written = [];
|
|
54455
54624
|
if (bool(args2.hook)) written.push(writeHook(cwd, enginePath, failOn));
|
|
54456
|
-
if (bool(args2.ci)) written.push(
|
|
54625
|
+
if (bool(args2.ci)) written.push(join38(cwd, ".github/workflows/a11y.yml"));
|
|
54457
54626
|
if (!written.length) {
|
|
54458
54627
|
throw new ToolError("nothing to install \u2014 pass hook:true and/or ci:true.");
|
|
54459
54628
|
}
|
|
@@ -54473,7 +54642,7 @@ function reportText(args2, tool) {
|
|
|
54473
54642
|
if (inline) return inline;
|
|
54474
54643
|
if (!file) throw new ToolError(`\`report_text\` is required \u2014 the report markdown for ultra11y_${tool} to work on.`);
|
|
54475
54644
|
if (!isAbsolute3(file)) throw new ToolError("`report_file` must be an absolute path.");
|
|
54476
|
-
if (!
|
|
54645
|
+
if (!existsSync22(file)) throw new ToolError(`report file not found: ${file}`);
|
|
54477
54646
|
return readFileSync18(file, "utf8");
|
|
54478
54647
|
}
|
|
54479
54648
|
function handleCriteria(args2) {
|
|
@@ -54489,7 +54658,7 @@ function handleCriteria(args2) {
|
|
|
54489
54658
|
function handleRead(args2, cwd) {
|
|
54490
54659
|
const raw = str2(args2.path);
|
|
54491
54660
|
if (!raw) throw new ToolError("`path` is required \u2014 relative to the project root, or an absolute path inside it.");
|
|
54492
|
-
const target = isAbsolute3(raw) ? raw :
|
|
54661
|
+
const target = isAbsolute3(raw) ? raw : join38(cwd, raw);
|
|
54493
54662
|
let real;
|
|
54494
54663
|
try {
|
|
54495
54664
|
real = realpathSync2(target);
|
|
@@ -54998,25 +55167,25 @@ function str3(v) {
|
|
|
54998
55167
|
var DECLARED = new Set([...TOOLS2, ...WRITE_TOOLS].map((t2) => t2.name));
|
|
54999
55168
|
|
|
55000
55169
|
// src/mcp/resources.ts
|
|
55001
|
-
import { existsSync as
|
|
55002
|
-
import { basename as basename3, dirname as dirname10, join as
|
|
55170
|
+
import { existsSync as existsSync23, readdirSync as readdirSync6, readFileSync as readFileSync19, realpathSync as realpathSync3, statSync as statSync11 } from "fs";
|
|
55171
|
+
import { basename as basename3, dirname as dirname10, join as join39, resolve as resolve9, sep as sep5 } from "path";
|
|
55003
55172
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
55004
55173
|
var SKILL_NAME = "ultra11y";
|
|
55005
55174
|
var URI_SCHEME = "skill://";
|
|
55006
55175
|
function resolveSkillRoot(moduleDir) {
|
|
55007
55176
|
const here = moduleDir ?? dirname10(fileURLToPath4(import.meta.url));
|
|
55008
55177
|
const candidates2 = [resolve9(here, ".."), resolve9(here, "..", "skills", SKILL_NAME), resolve9(here, "..", "..", "skills", SKILL_NAME)];
|
|
55009
|
-
return candidates2.find((dir) =>
|
|
55178
|
+
return candidates2.find((dir) => existsSync23(join39(dir, "SKILL.md")));
|
|
55010
55179
|
}
|
|
55011
55180
|
function listResources(moduleDir) {
|
|
55012
55181
|
const root = resolveSkillRoot(moduleDir);
|
|
55013
55182
|
if (!root) return [];
|
|
55014
55183
|
const out2 = [describe(root, "SKILL.md", `${SKILL_NAME}: the skill`)];
|
|
55015
|
-
const refDir =
|
|
55016
|
-
if (!
|
|
55184
|
+
const refDir = join39(root, "references");
|
|
55185
|
+
if (!existsSync23(refDir)) return out2;
|
|
55017
55186
|
for (const file of readdirSync6(refDir).sort()) {
|
|
55018
55187
|
if (!file.endsWith(".md")) continue;
|
|
55019
|
-
out2.push(describe(root,
|
|
55188
|
+
out2.push(describe(root, join39("references", file), `${SKILL_NAME} reference: ${basename3(file, ".md")}`));
|
|
55020
55189
|
}
|
|
55021
55190
|
return out2;
|
|
55022
55191
|
}
|
|
@@ -55051,7 +55220,7 @@ function describe(root, rel2, fallbackTitle) {
|
|
|
55051
55220
|
title: fallbackTitle,
|
|
55052
55221
|
mimeType: "text/markdown"
|
|
55053
55222
|
};
|
|
55054
|
-
const summary = firstProse(
|
|
55223
|
+
const summary = firstProse(join39(root, rel2));
|
|
55055
55224
|
if (summary) decl.description = summary;
|
|
55056
55225
|
return decl;
|
|
55057
55226
|
}
|
|
@@ -55491,6 +55660,7 @@ Usage:
|
|
|
55491
55660
|
ultra11y snapshot list [--root <dir>] [--json]
|
|
55492
55661
|
ultra11y pages --in <audit.json> [--standard <pack>] [--json] [--lang auto|en|fr] (the per-page criterion grid)
|
|
55493
55662
|
ultra11y mcp [--transport stdio|http] [--cwd <dir>] [--allow-write] [--port <n>] [--bind <addr>] [--allow-remote] [--allow-origin <o>] [--max-response-bytes <n>]
|
|
55663
|
+
ultra11y hook --claude-code (internal: the plugin's PreToolUse hook; payload on stdin)
|
|
55494
55664
|
ultra11y dev [--port <n>] [--root <dir>] [--standard <pack>] [--lang auto|en|fr] (dev side-car: live overlay + per-page dashboard)
|
|
55495
55665
|
ultra11y dev --next [--port <n>] (write the Next overlay component, then wire one line into your layout)
|
|
55496
55666
|
|
|
@@ -55631,6 +55801,13 @@ Commands:
|
|
|
55631
55801
|
--transport http listens on 127.0.0.1 (--port, --bind), and stays
|
|
55632
55802
|
loopback-only unless you pass --allow-remote. Read-only by default:
|
|
55633
55803
|
--allow-write opts into the commands that touch files.
|
|
55804
|
+
hook INTERNAL \u2014 the decision half of the Claude Code plugin's PreToolUse
|
|
55805
|
+
hook. Reads the payload on stdin; when a pending 'git commit', 'git
|
|
55806
|
+
push' or 'gh pr create' carries findings >= the threshold, prints the
|
|
55807
|
+
hook JSON that gets the review-a11y skill invoked, and prints nothing
|
|
55808
|
+
otherwise. Threshold: ULTRA11Y_HOOK_FAIL_ON, else hook.failOn in
|
|
55809
|
+
.ultra11yrc.json, else blocking. Disable with ULTRA11Y_HOOK=off or
|
|
55810
|
+
SKIP_A11Y=1. Called by hooks/pre-tool-use.mjs, not by hand.
|
|
55634
55811
|
pages The per-page criterion grid \u2014 RGAA is a per-page norm, the engine's
|
|
55635
55812
|
verdict is scope-wide, and this bridges the two. One row per criterion
|
|
55636
55813
|
(the pack's own under --standard), one column per page. Rebuilt from a
|
|
@@ -55784,7 +55961,8 @@ var COMMANDS = [
|
|
|
55784
55961
|
"fix",
|
|
55785
55962
|
"init",
|
|
55786
55963
|
"pack",
|
|
55787
|
-
"orchestrate"
|
|
55964
|
+
"orchestrate",
|
|
55965
|
+
"hook"
|
|
55788
55966
|
];
|
|
55789
55967
|
function isCommand(s) {
|
|
55790
55968
|
return !!s && COMMANDS.includes(s);
|
|
@@ -56005,9 +56183,9 @@ async function cmdAudit(p) {
|
|
|
56005
56183
|
const capturesFlag = typeof p.flags.captures === "string" && p.flags.captures ? p.flags.captures : void 0;
|
|
56006
56184
|
const capturesDir = capturesFlag ?? ".ultra11y/captures";
|
|
56007
56185
|
const scopedToDiff = p.flags.changed === true || p.flags.staged === true || since !== void 0;
|
|
56008
|
-
const capturesWanted = p.flags["no-captures"] !== true && !inputs.includes("-") && (capturesFlag !== void 0 ||
|
|
56186
|
+
const capturesWanted = p.flags["no-captures"] !== true && !inputs.includes("-") && (capturesFlag !== void 0 || existsSync24(capturesDir));
|
|
56009
56187
|
const useCaptures = capturesWanted && !scopedToDiff && !inputs.includes(capturesDir);
|
|
56010
|
-
const pagesWanted = p.flags["no-captures"] !== true && !inputs.includes("-") &&
|
|
56188
|
+
const pagesWanted = p.flags["no-captures"] !== true && !inputs.includes("-") && existsSync24(PAGES_DIR);
|
|
56011
56189
|
const usePages = pagesWanted && !scopedToDiff && !inputs.includes(PAGES_DIR);
|
|
56012
56190
|
const auditInputs = [...inputs, ...useCaptures ? [capturesDir] : [], ...usePages ? [PAGES_DIR] : []];
|
|
56013
56191
|
if (useCaptures)
|
|
@@ -56048,10 +56226,10 @@ async function cmdAudit(p) {
|
|
|
56048
56226
|
if (typeof p.flags.out === "string") {
|
|
56049
56227
|
const out2 = p.flags.out;
|
|
56050
56228
|
const asFile = out2.toLowerCase().endsWith(".json");
|
|
56051
|
-
const target = asFile ? out2 :
|
|
56229
|
+
const target = asFile ? out2 : join40(out2, "audit-latest.json");
|
|
56052
56230
|
try {
|
|
56053
|
-
|
|
56054
|
-
|
|
56231
|
+
mkdirSync12(asFile ? dirname11(out2) : out2, { recursive: true });
|
|
56232
|
+
writeFileSync15(target, JSON.stringify(result, null, 2) + "\n");
|
|
56055
56233
|
console.error(lang === "fr" ? `\u2192 audit \xE9crit dans ${target}` : `\u2192 audit written to ${target}`);
|
|
56056
56234
|
} catch {
|
|
56057
56235
|
}
|
|
@@ -56070,7 +56248,7 @@ async function cmdAudit(p) {
|
|
|
56070
56248
|
const baselineFlag = p.flags.baseline;
|
|
56071
56249
|
if (typeof baselineFlag === "string" && baselineFlag) {
|
|
56072
56250
|
let baseline = null;
|
|
56073
|
-
if (
|
|
56251
|
+
if (existsSync24(baselineFlag)) {
|
|
56074
56252
|
try {
|
|
56075
56253
|
const parsed = JSON.parse(readText(baselineFlag));
|
|
56076
56254
|
if (isCurrentAudit(parsed)) baseline = parsed;
|
|
@@ -56124,11 +56302,11 @@ async function cmdDev(p) {
|
|
|
56124
56302
|
return 2;
|
|
56125
56303
|
}
|
|
56126
56304
|
if (p.flags.next === true) {
|
|
56127
|
-
const dir =
|
|
56305
|
+
const dir = join40(root, ".ultra11y", "next");
|
|
56128
56306
|
const rel2 = ".ultra11y/next/overlay.jsx";
|
|
56129
56307
|
try {
|
|
56130
|
-
|
|
56131
|
-
|
|
56308
|
+
mkdirSync12(dir, { recursive: true });
|
|
56309
|
+
writeFileSync15(join40(dir, "overlay.jsx"), nextOverlayComponent(port));
|
|
56132
56310
|
} catch (e) {
|
|
56133
56311
|
console.error(`ultra11y dev: could not write ${rel2}: ${e instanceof Error ? e.message : String(e)}`);
|
|
56134
56312
|
return 1;
|
|
@@ -56216,7 +56394,7 @@ async function cmdSnapshot(p) {
|
|
|
56216
56394
|
2
|
|
56217
56395
|
)
|
|
56218
56396
|
);
|
|
56219
|
-
else if (!snaps.length) console.log(lang === "fr" ? `Aucun instantan\xE9 dans ${
|
|
56397
|
+
else if (!snaps.length) console.log(lang === "fr" ? `Aucun instantan\xE9 dans ${join40(root, PAGES_DIR)}.` : `No snapshot in ${join40(root, PAGES_DIR)}.`);
|
|
56220
56398
|
else for (const s of snaps) console.log(`${s.meta.id} ${s.meta.name} ${s.meta.url}${s.meta.auth ? " [auth]" : ""}`);
|
|
56221
56399
|
return 0;
|
|
56222
56400
|
}
|
|
@@ -56261,12 +56439,12 @@ async function cmdSnapshot(p) {
|
|
|
56261
56439
|
}
|
|
56262
56440
|
if (typeof payload.screenshot === "string" && payload.screenshot) {
|
|
56263
56441
|
try {
|
|
56264
|
-
|
|
56442
|
+
writeFileSync15(join40(dir, "screen.png"), Buffer.from(payload.screenshot, "base64"));
|
|
56265
56443
|
} catch {
|
|
56266
56444
|
console.error("ultra11y snapshot write: the screenshot could not be written \u2014 the pixel tier will be skipped for this page.");
|
|
56267
56445
|
}
|
|
56268
56446
|
}
|
|
56269
|
-
const result = runAudit({ inputs: [
|
|
56447
|
+
const result = runAudit({ inputs: [join40(dir, "dom.html")], onWarn: (m) => console.error(m) });
|
|
56270
56448
|
const failOnRaw = p.flags["fail-on"];
|
|
56271
56449
|
const failOnParsed = parseFailOn(failOnRaw);
|
|
56272
56450
|
if (failOnRaw !== void 0 && failOnParsed === null) {
|
|
@@ -56303,9 +56481,9 @@ function cmdInit(p) {
|
|
|
56303
56481
|
if (want.baseline) {
|
|
56304
56482
|
const inputs = p.positionals.length ? p.positionals : ["."];
|
|
56305
56483
|
const result = runAudit({ inputs, onWarn: (m) => console.error(m) });
|
|
56306
|
-
|
|
56307
|
-
const bp =
|
|
56308
|
-
|
|
56484
|
+
mkdirSync12(join40(root, "audits"), { recursive: true });
|
|
56485
|
+
const bp = join40(root, "audits", "baseline.json");
|
|
56486
|
+
writeFileSync15(bp, JSON.stringify(result, null, 2) + "\n");
|
|
56309
56487
|
wrote.push(bp);
|
|
56310
56488
|
}
|
|
56311
56489
|
if (want.hook) wrote.push(writeHook(root, engineRel, failOn, legacy ? "baseline" : "staged"));
|
|
@@ -56315,6 +56493,18 @@ function cmdInit(p) {
|
|
|
56315
56493
|
else console.log(`ultra11y init: done. The pre-commit gate audits staged changes and auto-applies safe fixes (bypass once with SKIP_A11Y=1).`);
|
|
56316
56494
|
return 0;
|
|
56317
56495
|
}
|
|
56496
|
+
async function cmdHook(p) {
|
|
56497
|
+
if (p.flags["claude-code"] !== true) {
|
|
56498
|
+
console.error("ultra11y hook: expected `hook --claude-code` (the payload is read on stdin).");
|
|
56499
|
+
return 2;
|
|
56500
|
+
}
|
|
56501
|
+
try {
|
|
56502
|
+
const decision = decide(JSON.parse(await readStdin()));
|
|
56503
|
+
if (decision) process.stdout.write(JSON.stringify(decision));
|
|
56504
|
+
} catch {
|
|
56505
|
+
}
|
|
56506
|
+
return 0;
|
|
56507
|
+
}
|
|
56318
56508
|
function cmdCriteria(p) {
|
|
56319
56509
|
if (p.flags.generate === true) {
|
|
56320
56510
|
process.stdout.write(renderCriteriaReference());
|
|
@@ -56442,8 +56632,8 @@ async function cmdPrd(p) {
|
|
|
56442
56632
|
return gh2 && gh2.failed > 0 ? 1 : 0;
|
|
56443
56633
|
}
|
|
56444
56634
|
function depsAt(root) {
|
|
56445
|
-
const pkgPath =
|
|
56446
|
-
if (!
|
|
56635
|
+
const pkgPath = join40(root, "package.json");
|
|
56636
|
+
if (!existsSync24(pkgPath)) return {};
|
|
56447
56637
|
try {
|
|
56448
56638
|
const pkg = JSON.parse(readText(pkgPath));
|
|
56449
56639
|
return { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
@@ -56469,24 +56659,24 @@ function cmdRender(p) {
|
|
|
56469
56659
|
console.error(`ultra11y render: --runner must be playwright|cypress|auto (got "${forced}").`);
|
|
56470
56660
|
return 2;
|
|
56471
56661
|
}
|
|
56472
|
-
const detected = detectE2eRunner(depsAt(root), (f) =>
|
|
56662
|
+
const detected = detectE2eRunner(depsAt(root), (f) => existsSync24(join40(root, f)));
|
|
56473
56663
|
const runners = forced && forced !== "auto" ? [forced] : detected;
|
|
56474
56664
|
if (!runners.length) {
|
|
56475
56665
|
console.error(e2eSetupPlan([], {}, lang));
|
|
56476
56666
|
return 1;
|
|
56477
56667
|
}
|
|
56478
56668
|
const engineRef = engineRefFor(root);
|
|
56479
|
-
const dir =
|
|
56669
|
+
const dir = join40(root, ".ultra11y", "e2e");
|
|
56480
56670
|
const paths = {};
|
|
56481
56671
|
try {
|
|
56482
|
-
|
|
56672
|
+
mkdirSync12(dir, { recursive: true });
|
|
56483
56673
|
if (runners.includes("playwright")) {
|
|
56484
|
-
|
|
56674
|
+
writeFileSync15(join40(dir, "playwright.mjs"), playwrightFixture(engineRef));
|
|
56485
56675
|
paths.playwright = ".ultra11y/e2e/playwright.mjs";
|
|
56486
56676
|
}
|
|
56487
56677
|
if (runners.includes("cypress")) {
|
|
56488
|
-
|
|
56489
|
-
|
|
56678
|
+
writeFileSync15(join40(dir, "cypress-plugin.mjs"), cypressPlugin(engineRef));
|
|
56679
|
+
writeFileSync15(join40(dir, "cypress-commands.mjs"), cypressCommands());
|
|
56490
56680
|
paths.cypressPlugin = ".ultra11y/e2e/cypress-plugin.mjs";
|
|
56491
56681
|
paths.cypressCommands = ".ultra11y/e2e/cypress-commands.mjs";
|
|
56492
56682
|
}
|
|
@@ -56501,7 +56691,7 @@ function cmdRender(p) {
|
|
|
56501
56691
|
if (p.flags.scaffold === true) {
|
|
56502
56692
|
const out2 = typeof p.flags.out === "string" && p.flags.out ? p.flags.out : "ultra11y-render.tsx";
|
|
56503
56693
|
try {
|
|
56504
|
-
|
|
56694
|
+
writeFileSync15(out2, ssrHarness());
|
|
56505
56695
|
} catch (e) {
|
|
56506
56696
|
console.error(`ultra11y render: could not write ${out2}: ${e instanceof Error ? e.message : String(e)}`);
|
|
56507
56697
|
return 1;
|
|
@@ -56515,29 +56705,29 @@ Fill in COMPONENTS, run it (e.g. npx tsx ${out2}), then: node scripts/ultra11y.m
|
|
|
56515
56705
|
}
|
|
56516
56706
|
if (p.flags.setup === true) {
|
|
56517
56707
|
const rel2 = ".ultra11y/capture-setup.mjs";
|
|
56518
|
-
const out2 =
|
|
56708
|
+
const out2 = join40(root, rel2);
|
|
56519
56709
|
try {
|
|
56520
|
-
|
|
56521
|
-
|
|
56710
|
+
mkdirSync12(dirname11(out2), { recursive: true });
|
|
56711
|
+
writeFileSync15(out2, captureSetup());
|
|
56522
56712
|
} catch (e) {
|
|
56523
56713
|
console.error(`ultra11y render: could not write ${out2}: ${e instanceof Error ? e.message : String(e)}`);
|
|
56524
56714
|
return 1;
|
|
56525
56715
|
}
|
|
56526
56716
|
let setupDeps = {};
|
|
56527
|
-
const setupPkg =
|
|
56528
|
-
if (
|
|
56717
|
+
const setupPkg = join40(root, "package.json");
|
|
56718
|
+
if (existsSync24(setupPkg)) {
|
|
56529
56719
|
try {
|
|
56530
56720
|
const pkg = JSON.parse(readText(setupPkg));
|
|
56531
56721
|
setupDeps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
56532
56722
|
} catch {
|
|
56533
56723
|
}
|
|
56534
56724
|
}
|
|
56535
|
-
const tr = detectTestRunner(setupDeps, (f) =>
|
|
56725
|
+
const tr = detectTestRunner(setupDeps, (f) => existsSync24(join40(root, f)));
|
|
56536
56726
|
console.log(captureSetupPlan(tr, rel2, lang));
|
|
56537
56727
|
const gaLine = ".ultra11y/captures/*.html text eol=lf linguist-generated=true";
|
|
56538
|
-
const gaPath =
|
|
56728
|
+
const gaPath = join40(root, ".gitattributes");
|
|
56539
56729
|
try {
|
|
56540
|
-
const existing =
|
|
56730
|
+
const existing = existsSync24(gaPath) ? readFileSync20(gaPath, "utf8") : "";
|
|
56541
56731
|
if (!existing.includes(".ultra11y/captures/")) {
|
|
56542
56732
|
appendFileSync(gaPath, (existing && !existing.endsWith("\n") ? "\n" : "") + gaLine + "\n");
|
|
56543
56733
|
console.log(lang === "fr" ? `.gitattributes : ajout\xE9 \xAB ${gaLine} \xBB` : `.gitattributes: added "${gaLine}"`);
|
|
@@ -56545,8 +56735,8 @@ Fill in COMPONENTS, run it (e.g. npx tsx ${out2}), then: node scripts/ultra11y.m
|
|
|
56545
56735
|
} catch {
|
|
56546
56736
|
}
|
|
56547
56737
|
try {
|
|
56548
|
-
const giPath =
|
|
56549
|
-
if (
|
|
56738
|
+
const giPath = join40(root, ".gitignore");
|
|
56739
|
+
if (existsSync24(giPath) && /^\s*\/?\.ultra11y(\/\**)?\/?\s*$/m.test(readFileSync20(giPath, "utf8")))
|
|
56550
56740
|
console.error(
|
|
56551
56741
|
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/").'
|
|
56552
56742
|
);
|
|
@@ -56556,8 +56746,8 @@ Fill in COMPONENTS, run it (e.g. npx tsx ${out2}), then: node scripts/ultra11y.m
|
|
|
56556
56746
|
}
|
|
56557
56747
|
if (p.flags.storybook === true || typeof p.flags.storybook === "string") {
|
|
56558
56748
|
const sbDir = p.positionals[0] ?? "storybook-static";
|
|
56559
|
-
const indexPath =
|
|
56560
|
-
if (!
|
|
56749
|
+
const indexPath = existsSync24(join40(sbDir, "index.json")) ? join40(sbDir, "index.json") : join40(sbDir, "stories.json");
|
|
56750
|
+
if (!existsSync24(indexPath)) {
|
|
56561
56751
|
console.error(
|
|
56562
56752
|
lang === "fr" ? `ultra11y render : aucun index Storybook (index.json/stories.json) dans ${sbDir}.` : `ultra11y render: no Storybook index (index.json/stories.json) in ${sbDir}.`
|
|
56563
56753
|
);
|
|
@@ -56567,7 +56757,7 @@ Fill in COMPONENTS, run it (e.g. npx tsx ${out2}), then: node scripts/ultra11y.m
|
|
|
56567
56757
|
const provById = new Map(stories.map((s) => [s.id, storyProvenance(s)]));
|
|
56568
56758
|
const capturesFlag = typeof p.flags.captures === "string" && p.flags.captures ? p.flags.captures : void 0;
|
|
56569
56759
|
const htmlDir = capturesFlag ?? sbDir;
|
|
56570
|
-
const htmlFiles =
|
|
56760
|
+
const htmlFiles = existsSync24(htmlDir) ? discover([htmlDir]).files.filter((f) => /\.html?$/i.test(f)) : [];
|
|
56571
56761
|
const outDir = ".ultra11y/captures";
|
|
56572
56762
|
let attributed = 0;
|
|
56573
56763
|
let skipped = 0;
|
|
@@ -56589,8 +56779,8 @@ Fill in COMPONENTS, run it (e.g. npx tsx ${out2}), then: node scripts/ultra11y.m
|
|
|
56589
56779
|
continue;
|
|
56590
56780
|
}
|
|
56591
56781
|
try {
|
|
56592
|
-
|
|
56593
|
-
|
|
56782
|
+
mkdirSync12(outDir, { recursive: true });
|
|
56783
|
+
writeFileSync15(join40(outDir, `${hitId}.html`), `${formatCaptureComment(prov)}
|
|
56594
56784
|
${raw}${raw.endsWith("\n") ? "" : "\n"}`);
|
|
56595
56785
|
attributed++;
|
|
56596
56786
|
} catch {
|
|
@@ -56612,11 +56802,11 @@ ${raw}${raw.endsWith("\n") ? "" : "\n"}`);
|
|
|
56612
56802
|
}
|
|
56613
56803
|
if (p.flags.coverage === true) {
|
|
56614
56804
|
const capturesFlag = typeof p.flags.captures === "string" && p.flags.captures ? p.flags.captures : void 0;
|
|
56615
|
-
const capturesDir = capturesFlag ??
|
|
56805
|
+
const capturesDir = capturesFlag ?? join40(root, ".ultra11y/captures");
|
|
56616
56806
|
const graphExt = [...GRAPH_ONLY_EXT, ...asList(p.flags.ext) ?? []];
|
|
56617
56807
|
const sourceFiles = discover([root], { include: asList(p.flags.include), exclude: asList(p.flags.exclude), ext: graphExt }).files;
|
|
56618
56808
|
const graph = buildGraphStreaming(sourceFiles);
|
|
56619
|
-
const capFiles =
|
|
56809
|
+
const capFiles = existsSync24(capturesDir) ? discover([capturesDir]).files : [];
|
|
56620
56810
|
const entries = capFiles.map((f) => ({ file: toPosix(f), provenance: parseCaptureProvenance(readText(f)) }));
|
|
56621
56811
|
const cov = computeCaptureCoverage(graph, entries);
|
|
56622
56812
|
if (p.flags.json) console.log(JSON.stringify(cov, null, 2));
|
|
@@ -56624,15 +56814,15 @@ ${raw}${raw.endsWith("\n") ? "" : "\n"}`);
|
|
|
56624
56814
|
return 0;
|
|
56625
56815
|
}
|
|
56626
56816
|
let deps = {};
|
|
56627
|
-
const pkgPath =
|
|
56628
|
-
if (
|
|
56817
|
+
const pkgPath = join40(root, "package.json");
|
|
56818
|
+
if (existsSync24(pkgPath)) {
|
|
56629
56819
|
try {
|
|
56630
56820
|
const pkg = JSON.parse(readText(pkgPath));
|
|
56631
56821
|
deps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
56632
56822
|
} catch {
|
|
56633
56823
|
}
|
|
56634
56824
|
}
|
|
56635
|
-
const detection = detectFrameworks(deps, (f) =>
|
|
56825
|
+
const detection = detectFrameworks(deps, (f) => existsSync24(join40(root, f)));
|
|
56636
56826
|
if (p.flags.json) console.log(JSON.stringify(detection, null, 2));
|
|
56637
56827
|
else console.log(renderPlan(detection, lang));
|
|
56638
56828
|
return 0;
|
|
@@ -56831,9 +57021,9 @@ function applyAdjudicationFile(p, adj, lang) {
|
|
|
56831
57021
|
return 1;
|
|
56832
57022
|
}
|
|
56833
57023
|
const out2 = typeof p.flags.out === "string" ? p.flags.out : ".";
|
|
56834
|
-
|
|
56835
|
-
const auditPath =
|
|
56836
|
-
|
|
57024
|
+
mkdirSync12(out2, { recursive: true });
|
|
57025
|
+
const auditPath = join40(out2, "audit-latest.json");
|
|
57026
|
+
writeFileSync15(auditPath, JSON.stringify(r.audit, null, 2) + "\n");
|
|
56837
57027
|
if (p.flags.json) console.log(JSON.stringify({ ok: true, auditPath, applied: r.applied, stillManual: r.stillManual, grounding: r.grounding }, null, 2));
|
|
56838
57028
|
else
|
|
56839
57029
|
console.log(
|
|
@@ -56903,7 +57093,7 @@ async function cmdScan(p) {
|
|
|
56903
57093
|
return 0;
|
|
56904
57094
|
}
|
|
56905
57095
|
for (const target of p.positionals.filter((a) => a !== "-")) {
|
|
56906
|
-
if (/^https?:\/\//i.test(target) ||
|
|
57096
|
+
if (/^https?:\/\//i.test(target) || existsSync24(target)) continue;
|
|
56907
57097
|
console.error(
|
|
56908
57098
|
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.`
|
|
56909
57099
|
);
|
|
@@ -57028,12 +57218,12 @@ async function cmdScan(p) {
|
|
|
57028
57218
|
audit2 = parsed;
|
|
57029
57219
|
}
|
|
57030
57220
|
const merged = mergeDynamic(audit2, dynamic, lang);
|
|
57031
|
-
|
|
57032
|
-
|
|
57221
|
+
mkdirSync12(out2, { recursive: true });
|
|
57222
|
+
writeFileSync15(join40(out2, "audit-latest.json"), JSON.stringify(merged, null, 2) + "\n");
|
|
57033
57223
|
if (p.flags.json) console.log(JSON.stringify(merged, null, 2));
|
|
57034
57224
|
else
|
|
57035
57225
|
console.log(
|
|
57036
|
-
lang === "fr" ? `Audit statique + dynamique fusionn\xE9 \u2192 ${
|
|
57226
|
+
lang === "fr" ? `Audit statique + dynamique fusionn\xE9 \u2192 ${join40(out2, "audit-latest.json")} (${merged.conformancePct}% r\xE9ussite, ${merged.findings.length} findings).` : `Static + dynamic audit merged \u2192 ${join40(out2, "audit-latest.json")} (${merged.conformancePct}% pass rate, ${merged.findings.length} findings).`
|
|
57037
57227
|
);
|
|
57038
57228
|
return 0;
|
|
57039
57229
|
}
|
|
@@ -57213,7 +57403,7 @@ function cmdOrchestrate(p) {
|
|
|
57213
57403
|
}
|
|
57214
57404
|
const engineAbs = realpathSync4(fileURLToPath5(import.meta.url));
|
|
57215
57405
|
if (p.flags.list === true) {
|
|
57216
|
-
if (!
|
|
57406
|
+
if (!existsSync24(runFlag)) {
|
|
57217
57407
|
console.error(`ultra11y orchestrate: run dir not found: ${runFlag}.`);
|
|
57218
57408
|
return 2;
|
|
57219
57409
|
}
|
|
@@ -57240,7 +57430,7 @@ function cmdOrchestrate(p) {
|
|
|
57240
57430
|
);
|
|
57241
57431
|
} else {
|
|
57242
57432
|
console.log(
|
|
57243
|
-
lang === "fr" ? `Suivez ${
|
|
57433
|
+
lang === "fr" ? `Suivez ${join40(runFlag, "orchestration", "RUNBOOK.md")} s\xE9quentiellement (chemin \xE9co).` : `Follow ${join40(runFlag, "orchestration", "RUNBOOK.md")} sequentially (the eco path).`
|
|
57244
57434
|
);
|
|
57245
57435
|
}
|
|
57246
57436
|
if (p.flags.phase === void 0 && workflows.length === 0 && p.flags.eco !== true) {
|
|
@@ -57324,6 +57514,8 @@ async function main(argv) {
|
|
|
57324
57514
|
return cmdOrchestrate(p);
|
|
57325
57515
|
case "mcp":
|
|
57326
57516
|
return cmdMcp(p);
|
|
57517
|
+
case "hook":
|
|
57518
|
+
return cmdHook(p);
|
|
57327
57519
|
default:
|
|
57328
57520
|
console.error(`ultra11y: "${p.command}" is not implemented yet`);
|
|
57329
57521
|
return 1;
|