ultra11y 2.25.0 → 2.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,12 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/cli.ts
4
- import { realpathSync as realpathSync4, writeFileSync as writeFileSync14, mkdirSync as mkdirSync11, existsSync as existsSync23, readFileSync as readFileSync20, appendFileSync } from "fs";
5
- import { join as join39, relative as relative3, sep as sep6, dirname as dirname11 } from "path";
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.25.0";
9
+ var VERSION = "2.26.0";
10
10
  var SCHEMA_VERSION = 2;
11
11
 
12
12
  // src/audit.ts
@@ -44894,13 +44894,60 @@ function runAudit(opts) {
44894
44894
  return result;
44895
44895
  }
44896
44896
 
44897
- // src/report.ts
44898
- import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync7 } from "fs";
44897
+ // src/hook.ts
44898
+ import { execFileSync as execFileSync2 } from "child_process";
44899
+ import { createHash as createHash5 } from "crypto";
44900
+ import { existsSync as existsSync15, mkdirSync as mkdirSync5, writeFileSync as writeFileSync6 } from "fs";
44901
+ import { tmpdir } from "os";
44899
44902
  import { join as join27 } from "path";
44900
44903
 
44901
- // src/prd.ts
44902
- import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync6 } from "fs";
44903
- import { join as join26 } from "path";
44904
+ // src/baseline.ts
44905
+ var RANK = { bloquant: 0, majeur: 1, mineur: 2 };
44906
+ function findingId(f) {
44907
+ const pos = f.sourceStart !== void 0 ? `b${f.sourceStart}-${f.sourceEnd}` : `l${f.line}:${f.col}:${f.selectorHint}`;
44908
+ return `${f.ruleId}|${f.criteriaId}|${f.file}|${pos}`;
44909
+ }
44910
+ function parseFailOn(v) {
44911
+ if (v === void 0 || v === true) return "bloquant";
44912
+ if (v === "bloquant" || v === "blocking") return "bloquant";
44913
+ if (v === "majeur" || v === "major") return "majeur";
44914
+ if (v === "mineur" || v === "minor") return "mineur";
44915
+ return null;
44916
+ }
44917
+ function findingsAtOrAbove(findings, failOn) {
44918
+ return findings.filter((f) => RANK[f.severity] <= RANK[failOn]);
44919
+ }
44920
+ function diffAgainstBaseline(current, baseline, failOn = "bloquant") {
44921
+ const baseIds = new Set((baseline?.findings ?? []).map(findingId));
44922
+ const curIds = new Set(current.findings.map(findingId));
44923
+ const newFindings = current.findings.filter((f) => !baseIds.has(findingId(f)));
44924
+ const auditedFiles = new Set(current.findings.map((f) => f.file));
44925
+ const fixedFindings = (baseline?.findings ?? []).filter((f) => auditedFiles.has(f.file) && !curIds.has(findingId(f)));
44926
+ const ok = !newFindings.some((f) => RANK[f.severity] <= RANK[failOn]);
44927
+ return { newFindings, fixedFindings, ok, failOn };
44928
+ }
44929
+ function baselineSummary(diff, lang = "fr") {
44930
+ const out2 = [];
44931
+ const blocking = diff.newFindings.filter((f) => RANK[f.severity] <= RANK[diff.failOn]);
44932
+ if (diff.ok) {
44933
+ out2.push(
44934
+ 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).`
44935
+ );
44936
+ } else {
44937
+ out2.push(
44938
+ 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:`
44939
+ );
44940
+ for (const f of blocking) out2.push(` [${f.severity}] ${f.ruleId} (WCAG ${f.criteriaId}) \u2014 ${f.file}:${f.line} (${f.selectorHint})`);
44941
+ }
44942
+ return out2.join("\n");
44943
+ }
44944
+
44945
+ // src/config.ts
44946
+ import { existsSync as existsSync14, statSync as statSync7 } from "fs";
44947
+ import { join as join26, isAbsolute as isAbsolute2 } from "path";
44948
+
44949
+ // src/pack.ts
44950
+ import { existsSync as existsSync13 } from "fs";
44904
44951
 
44905
44952
  // src/standards/pack.ts
44906
44953
  function localize(pack, s, lang) {
@@ -44946,167 +44993,150 @@ function idCaptureSource(pack) {
44946
44993
  return neutralizeCaptureGroups(pack.idPattern.replace(/^\^/, "").replace(/\$$/, ""));
44947
44994
  }
44948
44995
 
44949
- // src/standards/derive.ts
44950
- function aggregate2(statuses) {
44951
- if (statuses.includes("NC")) return "NC";
44952
- if (statuses.includes("C")) return "C";
44953
- if (statuses.includes("manual")) return "manual";
44954
- return "NA";
44955
- }
44956
- function ruleMatches(ruleId, patterns) {
44957
- for (const p of patterns) {
44958
- if (p === "*") return true;
44959
- if (p.endsWith(":*") || p.endsWith("-*")) {
44960
- if (ruleId.startsWith(p.slice(0, -1))) return true;
44961
- } else if (p === ruleId) return true;
44996
+ // src/pack.ts
44997
+ function exampleParses(ex) {
44998
+ for (const code2 of [ex.bad, ex.good]) {
44999
+ if (!code2) continue;
45000
+ if (ex.lang === "jsx") {
45001
+ if (!parseJsxAst(code2)) return `unparseable JSX example: ${code2.slice(0, 48)}\u2026`;
45002
+ } else if (ex.lang === "html") {
45003
+ try {
45004
+ parseHtml(code2, "<guidance>.html", false);
45005
+ } catch (e) {
45006
+ return `unparseable HTML example (${e instanceof Error ? e.message : String(e)})`;
45007
+ }
45008
+ }
44962
45009
  }
44963
- return false;
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 };
45010
+ return null;
44997
45011
  }
44998
- function derivePackResults(audit2, packKey) {
44999
- const pack = loadPack(packKey);
45000
- const byScId = new Map(audit2.criteria.map((c2) => [c2.id, c2]));
45001
- const myPackFindings = (audit2.packFindings ?? []).filter((f) => f.ruleId.startsWith(`pack:${packKey}:`));
45002
- const overrides = pack.overrides;
45003
- const deriveBase = (pc) => {
45004
- const outOfScope = pc.wcag.every((sc) => {
45005
- const s = knownScStatus(sc);
45006
- return s === "out-of-core" || s === "removed";
45007
- });
45008
- if (outOfScope) {
45009
- return { id: pc.id, theme: pc.theme, status: "manual", findings: [], scs: pc.wcag, outOfScope: true };
45010
- }
45011
- const scResults = pc.wcag.map((sc) => byScId.get(sc)).filter((x) => !!x);
45012
- const packFs = myPackFindings.filter((f) => pc.wcag.includes(f.criteriaId));
45013
- const allFindings = [...scResults.flatMap((r) => r.findings), ...packFs].map((f) => overrideFinding(f, overrides));
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 };
45012
+ function checkGuidance(ds, pack) {
45013
+ const errors = [];
45014
+ const warnings = [];
45015
+ if (typeof ds !== "object" || ds === null || !Array.isArray(ds.entries)) {
45016
+ errors.push("guidance: must be an object with an entries[] array");
45017
+ return { errors, warnings };
45018
+ }
45019
+ const dataset = ds;
45020
+ if (dataset.pack && dataset.pack !== pack.key) warnings.push(`guidance: dataset.pack "${dataset.pack}" \u2260 pack key "${pack.key}"`);
45021
+ dataset.entries.forEach((e, i2) => {
45022
+ const at = `guidance.entries[${i2}]${e.id ? ` (${e.id})` : ""}`;
45023
+ const crit = e.criterionId ? getCriterion(pack, e.criterionId) : void 0;
45024
+ if (!e.criterionId) {
45025
+ errors.push(`${at}: criterionId is unresolved \u2014 resolve it to a real ${pack.key} criterion (no fabrication)`);
45026
+ } else if (!crit) {
45027
+ errors.push(`${at}: criterionId "${e.criterionId}" does not exist in ${pack.key}`);
45022
45028
  }
45023
- if (scResults.some((r) => r.status === "NC")) {
45024
- return { id: pc.id, theme: pc.theme, status: "manual", findings, scs: pc.wcag, scopedOut: true };
45029
+ if (!Array.isArray(e.wcag) || e.wcag.length === 0) {
45030
+ errors.push(`${at}: must map to at least one WCAG SC`);
45031
+ } else {
45032
+ for (const sc of e.wcag) {
45033
+ switch (classifySc(sc)) {
45034
+ case "malformed":
45035
+ errors.push(`${at}: malformed SC "${sc}"`);
45036
+ break;
45037
+ case "unknown":
45038
+ errors.push(`${at}: SC "${sc}" is not a recognized WCAG success criterion \u2014 fabricated?`);
45039
+ break;
45040
+ case "out-of-core":
45041
+ warnings.push(`${at}: SC "${sc}" is a real WCAG AAA success criterion, outside the WCAG 2.2 AA core`);
45042
+ break;
45043
+ case "removed":
45044
+ warnings.push(`${at}: SC "${sc}" is a real but removed WCAG success criterion (obsolete)`);
45045
+ break;
45046
+ }
45047
+ }
45048
+ if (crit) {
45049
+ const extra = e.wcag.filter((sc) => !crit.wcag.includes(sc));
45050
+ if (extra.length) warnings.push(`${at}: SC(s) ${extra.join(", ")} not in criterion ${crit.id} mapping (${crit.wcag.join(", ")})`);
45051
+ }
45025
45052
  }
45026
- const status = scResults.length ? aggregate2(scResults.map((r) => r.status)) : "NA";
45027
- return { id: pc.id, theme: pc.theme, status, findings, scs: pc.wcag };
45028
- };
45029
- const enabledSecondary = (pack.secondaryMappings ?? []).filter((m) => m.enabled === true);
45030
- const secondarySources = enabledSecondary.length ? [...audit2.criteria.flatMap((c2) => c2.findings), ...myPackFindings] : [];
45031
- const adjudicated = audit2.packAdjudication?.standard === packKey ? new Map(audit2.packAdjudication.criteria.map((c2) => [c2.id, c2])) : void 0;
45032
- return pack.criteria.map((pc) => {
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
- };
45053
+ if (!Array.isArray(e.examples) || e.examples.length === 0) {
45054
+ warnings.push(`${at}: no code examples`);
45055
+ } else {
45056
+ e.examples.forEach((ex, j) => {
45057
+ const bad = exampleParses(ex);
45058
+ if (bad) errors.push(`${at}.examples[${j}]: ${bad}`);
45059
+ });
45044
45060
  }
45045
- const base = deriveBase(pc);
45046
- return enabledSecondary.length ? applySecondaryMappings(base, pc, enabledSecondary, secondarySources, pack.defaultLocale) : base;
45047
45061
  });
45062
+ return { errors, warnings };
45048
45063
  }
45049
- function findingsForStandard(audit2, standard) {
45050
- if (standard === CORE_KEY) return audit2.findings;
45051
- const mine = (audit2.packFindings ?? []).filter((f) => f.ruleId.startsWith(`pack:${standard}:`));
45052
- return mine.length ? [...audit2.findings, ...mine] : audit2.findings;
45053
- }
45054
-
45055
- // src/standards/vocabulary.ts
45056
- var DEFAULT = {
45057
- theme: { en: "Theme", fr: "Th\xE9matique" },
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(", ")})`);
45064
+ function runPackCheck(packPath, guidancePath) {
45065
+ const errors = [];
45066
+ const warnings = [];
45067
+ if (!existsSync13(packPath)) return { ok: false, errors: [`pack file not found: ${packPath}`], warnings };
45068
+ let raw;
45069
+ try {
45070
+ raw = JSON.parse(readText(packPath));
45071
+ } catch {
45072
+ return { ok: false, errors: [`${packPath}: not valid JSON`], warnings };
45103
45073
  }
45104
- return key;
45074
+ const v = validatePack(raw);
45075
+ for (const issue of v.issues) (issue.severity === "error" ? errors : warnings).push(`${issue.path ? `${issue.path}: ` : ""}${issue.message}`);
45076
+ if (v.ok && v.pack) {
45077
+ const known = new Set(ruleIds());
45078
+ const isNamespaced = (r) => /^(axe:|dyn-|agent:)/.test(r);
45079
+ const ownRuleIds = new Set((v.pack.rules ?? []).map((r) => `pack:${v.pack.key}:${r.id}`));
45080
+ const resolvable = (r) => isNamespaced(r) || known.has(r) || r.startsWith(`pack:${v.pack.key}:`) || ownRuleIds.has(r);
45081
+ for (const c2 of v.pack.criteria) {
45082
+ for (const r of c2.appliesTo?.ruleIds ?? []) {
45083
+ if (!resolvable(r)) warnings.push(`criteria "${c2.id}": appliesTo ruleId "${r}" is not a known engine rule (renamed/future?)`);
45084
+ }
45085
+ }
45086
+ for (const key of Object.keys(v.pack.overrides ?? {})) {
45087
+ if (!resolvable(key)) warnings.push(`overrides: ruleId "${key}" is not a known engine rule or a rule of this pack (renamed/future?)`);
45088
+ }
45089
+ }
45090
+ if (v.ok && v.pack && guidancePath) {
45091
+ if (!existsSync13(guidancePath)) {
45092
+ errors.push(`guidance file not found: ${guidancePath}`);
45093
+ } else {
45094
+ let gRaw;
45095
+ try {
45096
+ gRaw = JSON.parse(readText(guidancePath));
45097
+ } catch {
45098
+ errors.push(`${guidancePath}: not valid JSON`);
45099
+ }
45100
+ if (gRaw !== void 0) {
45101
+ const g = checkGuidance(gRaw, v.pack);
45102
+ errors.push(...g.errors);
45103
+ warnings.push(...g.warnings);
45104
+ }
45105
+ }
45106
+ }
45107
+ return { ok: errors.length === 0, errors, warnings };
45105
45108
  }
45106
- function standardLabel(standard) {
45107
- if (isCore(standard)) return "WCAG 2.2 AA";
45108
- const p = loadPack(standard);
45109
- return `${p.name} ${p.baseVersion}`;
45109
+ function packScaffold() {
45110
+ return JSON.stringify(
45111
+ {
45112
+ key: "mypack",
45113
+ name: "MyPack",
45114
+ fullName: "My national accessibility standard",
45115
+ org: "Org",
45116
+ country: "XX",
45117
+ baseVersion: "1.0",
45118
+ wcagVersion: "2.2",
45119
+ locales: ["en"],
45120
+ defaultLocale: "en",
45121
+ license: "\u2026",
45122
+ source: "https://\u2026",
45123
+ attribution: "\u2026 \xA9 \u2026",
45124
+ idPattern: "^\\d+\\.\\d+$",
45125
+ vocabulary: {
45126
+ theme: { en: "Theme" },
45127
+ criterion: { en: "Criterion" },
45128
+ test: { en: "Test" },
45129
+ conformant: { en: "Conformant" },
45130
+ nonConformant: { en: "Non-conformant" },
45131
+ notApplicable: { en: "Not applicable" },
45132
+ auditorHeading: { en: "Accessibility criterion" }
45133
+ },
45134
+ themes: [{ number: 1, name: { en: "Theme one" }, count: 1 }],
45135
+ criteria: [{ id: "1.1", theme: 1, title: { en: "Criterion one" }, titlePlain: { en: "Criterion one" }, wcag: ["1.1.1"] }]
45136
+ },
45137
+ null,
45138
+ 2
45139
+ );
45110
45140
  }
45111
45141
 
45112
45142
  // src/data/guidance/rgaa.json
@@ -47573,6 +47603,441 @@ function hasGuidance(packKey) {
47573
47603
  return datasets.has(packKey);
47574
47604
  }
47575
47605
 
47606
+ // src/config.ts
47607
+ var CONFIG_FILE = ".ultra11yrc.json";
47608
+ function loadConfig(cwd) {
47609
+ const p = join26(cwd, CONFIG_FILE);
47610
+ if (!existsSync14(p)) return null;
47611
+ let parsed;
47612
+ try {
47613
+ parsed = JSON.parse(readText(p));
47614
+ } catch {
47615
+ throw new Error(`${CONFIG_FILE}: not valid JSON`);
47616
+ }
47617
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
47618
+ throw new Error(`${CONFIG_FILE}: must be a JSON object`);
47619
+ }
47620
+ return parsed;
47621
+ }
47622
+ function packPaths(path) {
47623
+ if (!existsSync14(path)) return null;
47624
+ if (statSync7(path).isDirectory()) {
47625
+ const pack = join26(path, "pack.json");
47626
+ if (!existsSync14(pack)) return null;
47627
+ const glossary = join26(path, "glossary.json");
47628
+ const guidance = join26(path, "guidance.json");
47629
+ return {
47630
+ pack,
47631
+ glossary: existsSync14(glossary) ? glossary : void 0,
47632
+ guidance: existsSync14(guidance) ? guidance : void 0
47633
+ };
47634
+ }
47635
+ return { pack: path };
47636
+ }
47637
+ function loadRuntimeStandards(cwd, packFlags, onWarn, override = false) {
47638
+ const result = { errors: [], loadedPacks: [], loadedGuidance: [] };
47639
+ let config = null;
47640
+ try {
47641
+ config = loadConfig(cwd);
47642
+ } catch (e) {
47643
+ result.errors.push(e instanceof Error ? e.message : String(e));
47644
+ return result;
47645
+ }
47646
+ result.defaultStandard = config?.standard;
47647
+ for (const raw of [...config?.packs ?? [], ...packFlags]) {
47648
+ const paths = packPaths(isAbsolute2(raw) ? raw : join26(cwd, raw));
47649
+ if (!paths) {
47650
+ result.errors.push(`--pack ${raw}: not found (expected a pack JSON file or a directory with pack.json)`);
47651
+ continue;
47652
+ }
47653
+ let packObj;
47654
+ try {
47655
+ packObj = JSON.parse(readText(paths.pack));
47656
+ } catch {
47657
+ result.errors.push(`--pack ${paths.pack}: not valid JSON`);
47658
+ continue;
47659
+ }
47660
+ let glossary = {};
47661
+ if (paths.glossary) {
47662
+ try {
47663
+ glossary = JSON.parse(readText(paths.glossary));
47664
+ } catch {
47665
+ onWarn(`ultra11y: ${paths.glossary} is not valid JSON \u2014 ignoring glossary.`);
47666
+ }
47667
+ }
47668
+ const v = registerRuntimePack(packObj, glossary, { override });
47669
+ for (const issue of v.issues) if (issue.severity === "warn") onWarn(`ultra11y: ${raw}: ${issue.path ? `${issue.path}: ` : ""}${issue.message}`);
47670
+ if (!v.ok || !v.pack) {
47671
+ result.errors.push(`--pack ${paths.pack}: invalid pack
47672
+ ${formatIssues(v.issues).join("\n")}`);
47673
+ continue;
47674
+ }
47675
+ result.loadedPacks.push(v.pack.key);
47676
+ if (paths.guidance) loadGuidanceFile(paths.guidance, result, onWarn);
47677
+ }
47678
+ for (const g of config?.guidance ?? []) loadGuidanceFile(isAbsolute2(g) ? g : join26(cwd, g), result, onWarn);
47679
+ for (const sm of config?.secondaryMappings ?? []) {
47680
+ if (!sm || typeof sm.standard !== "string" || typeof sm.ruleId !== "string" || typeof sm.criterion !== "string") {
47681
+ result.errors.push("secondaryMappings: each entry must be { standard, ruleId, criterion, note? }");
47682
+ continue;
47683
+ }
47684
+ if (!getPack(sm.standard)) {
47685
+ result.errors.push(`secondaryMappings: unknown standard "${sm.standard}" (register its pack first; the WCAG core has no pack criteria)`);
47686
+ continue;
47687
+ }
47688
+ enableSecondaryMapping(sm.standard, { ruleId: sm.ruleId, criterion: sm.criterion, note: sm.note });
47689
+ }
47690
+ return result;
47691
+ }
47692
+ function loadGuidanceFile(path, result, onWarn) {
47693
+ if (!existsSync14(path)) {
47694
+ onWarn(`ultra11y: guidance ${path} not found \u2014 skipping.`);
47695
+ return;
47696
+ }
47697
+ let ds;
47698
+ try {
47699
+ ds = JSON.parse(readText(path));
47700
+ } catch {
47701
+ onWarn(`ultra11y: guidance ${path} is not valid JSON \u2014 skipping.`);
47702
+ return;
47703
+ }
47704
+ if (!ds || typeof ds.pack !== "string" || !Array.isArray(ds.entries)) {
47705
+ onWarn(`ultra11y: guidance ${path} is not a valid dataset (needs { pack, entries[] }) \u2014 skipping.`);
47706
+ return;
47707
+ }
47708
+ const pack = getPack(ds.pack);
47709
+ if (pack) {
47710
+ const g = checkGuidance(ds, pack);
47711
+ for (const w of g.warnings) onWarn(`ultra11y: guidance ${path}: ${w}`);
47712
+ if (g.errors.length) {
47713
+ result.errors.push(`guidance ${path}: invalid
47714
+ ${g.errors.map((e) => ` \u2717 ${e}`).join("\n")}`);
47715
+ return;
47716
+ }
47717
+ } else {
47718
+ for (const e of ds.entries) {
47719
+ if (!Array.isArray(e?.wcag) || !Array.isArray(e?.examples)) {
47720
+ onWarn(`ultra11y: guidance ${path}: entry "${e?.id ?? "?"}" missing wcag[]/examples[] \u2014 skipping dataset.`);
47721
+ return;
47722
+ }
47723
+ }
47724
+ }
47725
+ if (hasGuidance(ds.pack)) onWarn(`ultra11y: guidance ${path} overrides the existing guidance for pack "${ds.pack}".`);
47726
+ registerRuntimeGuidance(ds);
47727
+ result.loadedGuidance.push(path);
47728
+ }
47729
+
47730
+ // src/hook.ts
47731
+ var AT_CMD = String.raw`(?:^|[\n;|&(]\s*)`;
47732
+ var OPTS = String.raw`(?:\s+(?:-[cC]\s+\S+|--?[\w-]+(?:=\S+)?))*`;
47733
+ var INTENTS = [
47734
+ // Ordered by breadth of what the command publishes: a `git commit && git push` chain is
47735
+ // reviewed against the base branch (push scope), not just the staged snapshot.
47736
+ ["pr", new RegExp(`${AT_CMD}gh${OPTS}\\s+pr\\s+create(?![\\w-])`)],
47737
+ ["push", new RegExp(`${AT_CMD}git${OPTS}\\s+push(?![\\w-])`)],
47738
+ ["commit", new RegExp(`${AT_CMD}git${OPTS}\\s+commit(?![\\w-])`)]
47739
+ ];
47740
+ var OPTED_OUT = /(?:^|\s)--(?:no-verify|dry-run)(?![\w-])/;
47741
+ function matchGitIntent(command) {
47742
+ if (OPTED_OUT.test(command)) return null;
47743
+ for (const [intent, re] of INTENTS) if (re.test(command)) return intent;
47744
+ return null;
47745
+ }
47746
+ function git2(cwd, args2) {
47747
+ try {
47748
+ return execFileSync2("git", args2, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
47749
+ } catch {
47750
+ return null;
47751
+ }
47752
+ }
47753
+ function resolveBase(command, cwd) {
47754
+ const explicit = command.match(/--base[=\s]+(\S+)/)?.[1]?.replace(/^["']|["']$/g, "");
47755
+ if (explicit) return explicit.includes("/") ? explicit : git2(cwd, ["rev-parse", "--verify", `origin/${explicit}`]) ? `origin/${explicit}` : explicit;
47756
+ const head = git2(cwd, ["symbolic-ref", "--short", "refs/remotes/origin/HEAD"]);
47757
+ if (head) return head;
47758
+ for (const ref of ["origin/main", "origin/master"]) if (git2(cwd, ["rev-parse", "--verify", ref])) return ref;
47759
+ return null;
47760
+ }
47761
+ function scopeFor(intent, command, cwd) {
47762
+ if (intent === "commit") return { staged: true };
47763
+ const base = resolveBase(command, cwd);
47764
+ return base ? { since: base } : null;
47765
+ }
47766
+ function resolveThreshold(cwd, env) {
47767
+ const fromEnv = env.ULTRA11Y_HOOK_FAIL_ON;
47768
+ if (fromEnv) return fromEnv === "off" ? null : parseFailOn(fromEnv);
47769
+ let configured;
47770
+ try {
47771
+ configured = loadConfig(cwd)?.hook?.failOn;
47772
+ } catch {
47773
+ }
47774
+ if (configured) return configured === "off" ? null : parseFailOn(configured);
47775
+ return "bloquant";
47776
+ }
47777
+ function loopKey(sessionId, intent, findings) {
47778
+ const h = createHash5("sha256");
47779
+ h.update(`${sessionId}\0${intent}\0`);
47780
+ for (const id of findings.map(findingId).sort()) h.update(`${id}\0`);
47781
+ return h.digest("hex").slice(0, 32);
47782
+ }
47783
+ function firstSighting(key) {
47784
+ try {
47785
+ const dir = join27(tmpdir(), "ultra11y-hook");
47786
+ mkdirSync5(dir, { recursive: true });
47787
+ const marker = join27(dir, key);
47788
+ if (existsSync15(marker)) return false;
47789
+ writeFileSync6(marker, "");
47790
+ return true;
47791
+ } catch {
47792
+ return false;
47793
+ }
47794
+ }
47795
+ var SEV_EN = { bloquant: "blocking", majeur: "major", mineur: "minor" };
47796
+ var MAX_DIGEST = 10;
47797
+ function digestOf(findings) {
47798
+ const shown = findings.slice(0, MAX_DIGEST).map((f) => `- ${f.file}:${f.line} \u2014 ${f.criteriaId} (${SEV_EN[f.severity]}) \u2014 ${f.message}`);
47799
+ if (findings.length > MAX_DIGEST) shown.push(`- \u2026and ${findings.length - MAX_DIGEST} more (the skill re-runs the engine and sees all of them).`);
47800
+ return shown.join("\n");
47801
+ }
47802
+ var SCOPE_HINT = {
47803
+ commit: "--staged --graph",
47804
+ push: (base) => `--since ${base} --graph`
47805
+ };
47806
+ function realAudit(scope, cwd) {
47807
+ const previous = process.cwd();
47808
+ try {
47809
+ process.chdir(cwd);
47810
+ return runAudit({
47811
+ inputs: ["."],
47812
+ changed: "since" in scope,
47813
+ ..."since" in scope ? { since: scope.since } : { staged: true },
47814
+ graph: true,
47815
+ onWarn: () => {
47816
+ }
47817
+ // stdout/stderr belong to the hook protocol — stay quiet
47818
+ });
47819
+ } catch {
47820
+ return null;
47821
+ } finally {
47822
+ try {
47823
+ process.chdir(previous);
47824
+ } catch {
47825
+ }
47826
+ }
47827
+ }
47828
+ function decide(payload, deps = {}) {
47829
+ const env = deps.env ?? process.env;
47830
+ const audit2 = deps.audit ?? realAudit;
47831
+ const seen = deps.seen ?? firstSighting;
47832
+ if (payload.tool_name !== "Bash") return null;
47833
+ const command = payload.tool_input?.command;
47834
+ if (!command) return null;
47835
+ if (env.SKIP_A11Y || env.ULTRA11Y_HOOK === "off") return null;
47836
+ const intent = matchGitIntent(command);
47837
+ if (!intent) return null;
47838
+ const cwd = payload.cwd || process.cwd();
47839
+ if (!git2(cwd, ["rev-parse", "--git-dir"])) return null;
47840
+ const threshold = resolveThreshold(cwd, env);
47841
+ if (!threshold) return null;
47842
+ const scope = scopeFor(intent, command, cwd);
47843
+ if (!scope) return null;
47844
+ const result = audit2(scope, cwd);
47845
+ if (!result) return null;
47846
+ const findings = findingsAtOrAbove(result.findings, threshold);
47847
+ if (findings.length === 0) return null;
47848
+ if (!seen(loopKey(payload.session_id ?? "", intent, findings))) return null;
47849
+ 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(", ");
47850
+ const where = intent === "commit" ? "about to be committed" : intent === "push" ? "about to be pushed" : "about to go into this pull request";
47851
+ const hint = "since" in scope ? SCOPE_HINT.push(scope.since) : SCOPE_HINT.commit;
47852
+ return {
47853
+ hookSpecificOutput: {
47854
+ hookEventName: "PreToolUse",
47855
+ permissionDecision: "deny",
47856
+ permissionDecisionReason: `ultra11y: ${findings.length} accessibility finding(s) (${counts}) in the code ${where}.`,
47857
+ additionalContext: [
47858
+ `Invoke the \`review-a11y\` skill now, scoped to \`${hint}\`, before running this command again.`,
47859
+ "",
47860
+ "The engine already found:",
47861
+ digestOf(findings),
47862
+ "",
47863
+ "Adjudicate each one against the cited code \u2014 refute false positives with evidence, apply the fixes the user agrees to.",
47864
+ "Then re-run the command: this gate does not fire twice for the same set of findings, so it will go through."
47865
+ ].join("\n")
47866
+ }
47867
+ };
47868
+ }
47869
+
47870
+ // src/report.ts
47871
+ import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync8 } from "fs";
47872
+ import { join as join29 } from "path";
47873
+
47874
+ // src/prd.ts
47875
+ import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync7 } from "fs";
47876
+ import { join as join28 } from "path";
47877
+
47878
+ // src/standards/derive.ts
47879
+ function aggregate2(statuses) {
47880
+ if (statuses.includes("NC")) return "NC";
47881
+ if (statuses.includes("C")) return "C";
47882
+ if (statuses.includes("manual")) return "manual";
47883
+ return "NA";
47884
+ }
47885
+ function ruleMatches(ruleId, patterns) {
47886
+ for (const p of patterns) {
47887
+ if (p === "*") return true;
47888
+ if (p.endsWith(":*") || p.endsWith("-*")) {
47889
+ if (ruleId.startsWith(p.slice(0, -1))) return true;
47890
+ } else if (p === ruleId) return true;
47891
+ }
47892
+ return false;
47893
+ }
47894
+ function packCriteriaForFinding(pack, finding) {
47895
+ return pack.criteria.filter((pc) => pc.wcag.includes(finding.criteriaId) && (!pc.appliesTo || ruleMatches(finding.ruleId, pc.appliesTo.ruleIds))).map((pc) => pc.id);
47896
+ }
47897
+ function packConformancePct(derived) {
47898
+ const c2 = derived.filter((d) => d.status === "C").length;
47899
+ const nc = derived.filter((d) => d.status === "NC").length;
47900
+ return c2 + nc === 0 ? 100 : Math.round(c2 / (c2 + nc) * 100);
47901
+ }
47902
+ function overrideFinding(f, overrides) {
47903
+ const o = overrides?.[f.ruleId];
47904
+ if (!o) return f;
47905
+ const patched = { ...f };
47906
+ if (o.advisory !== void 0) patched.advisory = o.advisory;
47907
+ if (o.severity) patched.severity = o.severity;
47908
+ return patched;
47909
+ }
47910
+ function pickLocale(ls, preferred) {
47911
+ if (!ls) return void 0;
47912
+ return ls[preferred] ?? ls.en ?? ls.fr ?? Object.values(ls).find((v) => typeof v === "string");
47913
+ }
47914
+ function tagSecondary(f, note) {
47915
+ return { ...f, secondary: note ? { note } : {} };
47916
+ }
47917
+ function applySecondaryMappings(base, pc, enabled, sources, defaultLocale) {
47918
+ const active = enabled.filter((m) => m.criterion === pc.id);
47919
+ if (!active.length) return base;
47920
+ const added = [];
47921
+ for (const m of active) for (const f of sources) if (f.ruleId === m.ruleId) added.push(tagSecondary(f, pickLocale(m.note, defaultLocale)));
47922
+ if (!added.length) return base;
47923
+ const findings = [...base.findings, ...added];
47924
+ if (added.some((f) => !f.advisory)) return { id: pc.id, theme: pc.theme, status: aggregate2([base.status, "NC"]), findings, scs: base.scs };
47925
+ return { ...base, findings };
47926
+ }
47927
+ function derivePackResults(audit2, packKey) {
47928
+ const pack = loadPack(packKey);
47929
+ const byScId = new Map(audit2.criteria.map((c2) => [c2.id, c2]));
47930
+ const myPackFindings = (audit2.packFindings ?? []).filter((f) => f.ruleId.startsWith(`pack:${packKey}:`));
47931
+ const overrides = pack.overrides;
47932
+ const deriveBase = (pc) => {
47933
+ const outOfScope = pc.wcag.every((sc) => {
47934
+ const s = knownScStatus(sc);
47935
+ return s === "out-of-core" || s === "removed";
47936
+ });
47937
+ if (outOfScope) {
47938
+ return { id: pc.id, theme: pc.theme, status: "manual", findings: [], scs: pc.wcag, outOfScope: true };
47939
+ }
47940
+ const scResults = pc.wcag.map((sc) => byScId.get(sc)).filter((x) => !!x);
47941
+ const packFs = myPackFindings.filter((f) => pc.wcag.includes(f.criteriaId));
47942
+ const allFindings = [...scResults.flatMap((r) => r.findings), ...packFs].map((f) => overrideFinding(f, overrides));
47943
+ if (!pc.appliesTo) {
47944
+ const status2 = scResults.length ? aggregate2(scResults.map((r) => r.status)) : "NA";
47945
+ return { id: pc.id, theme: pc.theme, status: status2, findings: allFindings, scs: pc.wcag };
47946
+ }
47947
+ const findings = allFindings.filter((f) => ruleMatches(f.ruleId, pc.appliesTo.ruleIds));
47948
+ const normativeFindings = findings.filter((f) => !f.advisory);
47949
+ if (normativeFindings.length) {
47950
+ return { id: pc.id, theme: pc.theme, status: "NC", findings, scs: pc.wcag };
47951
+ }
47952
+ if (scResults.some((r) => r.status === "NC")) {
47953
+ return { id: pc.id, theme: pc.theme, status: "manual", findings, scs: pc.wcag, scopedOut: true };
47954
+ }
47955
+ const status = scResults.length ? aggregate2(scResults.map((r) => r.status)) : "NA";
47956
+ return { id: pc.id, theme: pc.theme, status, findings, scs: pc.wcag };
47957
+ };
47958
+ const enabledSecondary = (pack.secondaryMappings ?? []).filter((m) => m.enabled === true);
47959
+ const secondarySources = enabledSecondary.length ? [...audit2.criteria.flatMap((c2) => c2.findings), ...myPackFindings] : [];
47960
+ const adjudicated = audit2.packAdjudication?.standard === packKey ? new Map(audit2.packAdjudication.criteria.map((c2) => [c2.id, c2])) : void 0;
47961
+ return pack.criteria.map((pc) => {
47962
+ const decided = adjudicated?.get(pc.id);
47963
+ if (decided) {
47964
+ return {
47965
+ id: pc.id,
47966
+ theme: pc.theme,
47967
+ status: decided.status,
47968
+ findings: decided.findings,
47969
+ scs: pc.wcag,
47970
+ ...decided.justification ? { justification: decided.justification } : {},
47971
+ decidedBy: "agent"
47972
+ };
47973
+ }
47974
+ const base = deriveBase(pc);
47975
+ return enabledSecondary.length ? applySecondaryMappings(base, pc, enabledSecondary, secondarySources, pack.defaultLocale) : base;
47976
+ });
47977
+ }
47978
+ function findingsForStandard(audit2, standard) {
47979
+ if (standard === CORE_KEY) return audit2.findings;
47980
+ const mine = (audit2.packFindings ?? []).filter((f) => f.ruleId.startsWith(`pack:${standard}:`));
47981
+ return mine.length ? [...audit2.findings, ...mine] : audit2.findings;
47982
+ }
47983
+
47984
+ // src/standards/vocabulary.ts
47985
+ var DEFAULT = {
47986
+ theme: { en: "Theme", fr: "Th\xE9matique" },
47987
+ criterion: { en: "Criterion", fr: "Crit\xE8re" },
47988
+ test: { en: "Test", fr: "Test" },
47989
+ conformant: { en: "Conformant", fr: "Conforme" },
47990
+ nonConformant: { en: "Non-conformant", fr: "Non conforme" },
47991
+ notApplicable: { en: "Not applicable", fr: "Non applicable" },
47992
+ auditorHeading: { en: "Accessibility criterion", fr: "Crit\xE8re d'accessibilit\xE9" }
47993
+ };
47994
+ var CORE = {
47995
+ theme: { en: "Principle \xB7 Guideline", fr: "Principe \xB7 R\xE8gle" },
47996
+ criterion: { en: "Success criterion", fr: "Crit\xE8re de succ\xE8s" },
47997
+ test: { en: "Technique", fr: "Technique" },
47998
+ conformant: { en: "Pass", fr: "Conforme" },
47999
+ nonConformant: { en: "Fail", fr: "Non conforme" },
48000
+ notApplicable: { en: "Not applicable", fr: "Non applicable" },
48001
+ auditorHeading: { en: "Accessibility criterion", fr: "Crit\xE8re d'accessibilit\xE9" }
48002
+ };
48003
+ function pick(s, lang, fallbackLang) {
48004
+ if (!s) return void 0;
48005
+ return s[lang] ?? s[fallbackLang] ?? s.en ?? Object.values(s)[0];
48006
+ }
48007
+ function vocabularyFor(standard, lang) {
48008
+ const base = isCore(standard) ? CORE : DEFAULT;
48009
+ const pack = isCore(standard) ? void 0 : getPack(standard);
48010
+ const voc = pack?.vocabulary;
48011
+ const packDefault = pack?.defaultLocale ?? "en";
48012
+ const term = (k) => pick(voc?.[k], lang, packDefault) ?? base[k][lang] ?? base[k].en;
48013
+ return {
48014
+ theme: term("theme"),
48015
+ criterion: term("criterion"),
48016
+ test: term("test"),
48017
+ conformant: term("conformant"),
48018
+ nonConformant: term("nonConformant"),
48019
+ notApplicable: term("notApplicable"),
48020
+ auditorHeading: term("auditorHeading"),
48021
+ normativeNote: pick(voc?.normativeNote, lang, packDefault)
48022
+ };
48023
+ }
48024
+
48025
+ // src/standards/index.ts
48026
+ var CORE2 = CORE_KEY;
48027
+ function resolveStandard(flag) {
48028
+ if (flag === void 0 || flag === true || flag === "") return CORE_KEY;
48029
+ const key = String(flag).toLowerCase();
48030
+ if (!hasStandard(key)) {
48031
+ throw new Error(`unknown standard "${key}" (known: ${listStandards().join(", ")})`);
48032
+ }
48033
+ return key;
48034
+ }
48035
+ function standardLabel(standard) {
48036
+ if (isCore(standard)) return "WCAG 2.2 AA";
48037
+ const p = loadPack(standard);
48038
+ return `${p.name} ${p.baseVersion}`;
48039
+ }
48040
+
47576
48041
  // src/auditor.ts
47577
48042
  var SEV_ORDER = ["bloquant", "majeur", "mineur"];
47578
48043
  var ICON = { bloquant: "\u{1F534}", majeur: "\u{1F7E0}", mineur: "\u{1F7E1}" };
@@ -48149,10 +48614,10 @@ function renderPrdDoc(r, lang = "en", standard = "wcag") {
48149
48614
  return out2.join("\n");
48150
48615
  }
48151
48616
  function writePrd(r, opts) {
48152
- mkdirSync5(opts.out, { recursive: true });
48617
+ mkdirSync6(opts.out, { recursive: true });
48153
48618
  if (opts.format === "doc") {
48154
- const p2 = join26(opts.out, `prd-doc-${r.date}.md`);
48155
- writeFileSync6(p2, renderPrdDoc(r, opts.lang, opts.standard));
48619
+ const p2 = join28(opts.out, `prd-doc-${r.date}.md`);
48620
+ writeFileSync7(p2, renderPrdDoc(r, opts.lang, opts.standard));
48156
48621
  return [p2];
48157
48622
  }
48158
48623
  const remediation = opts.format === "remediation";
@@ -48161,14 +48626,14 @@ function writePrd(r, opts) {
48161
48626
  const files = remediation ? renderPerCriterion(r, opts.lang, opts.standard) : renderAuditorPerCriterion(r, opts.lang, opts.standard, { technical });
48162
48627
  const paths = [];
48163
48628
  for (const f of files) {
48164
- const p2 = join26(opts.out, f.name);
48165
- writeFileSync6(p2, f.content);
48629
+ const p2 = join28(opts.out, f.name);
48630
+ writeFileSync7(p2, f.content);
48166
48631
  paths.push(p2);
48167
48632
  }
48168
48633
  return paths;
48169
48634
  }
48170
- const p = join26(opts.out, `prd-${r.date}.md`);
48171
- writeFileSync6(p, remediation ? renderBacklog(r, opts.lang, opts.standard) : renderAuditorBacklog(r, opts.lang, opts.standard, { technical }));
48635
+ const p = join28(opts.out, `prd-${r.date}.md`);
48636
+ writeFileSync7(p, remediation ? renderBacklog(r, opts.lang, opts.standard) : renderAuditorBacklog(r, opts.lang, opts.standard, { technical }));
48172
48637
  return [p];
48173
48638
  }
48174
48639
 
@@ -48619,14 +49084,14 @@ function renderPackReport(r, pack, lang = "en") {
48619
49084
  function writeReport(r, opts) {
48620
49085
  const core = isCore(opts.standard);
48621
49086
  const md = core ? renderReport(r, opts.lang) : renderPackReport(r, loadPack(opts.standard), opts.lang);
48622
- mkdirSync6(opts.out, { recursive: true });
48623
- const path = join27(opts.out, `${core ? "wcag" : opts.standard}-${r.date}.md`);
48624
- writeFileSync7(path, md);
49087
+ mkdirSync7(opts.out, { recursive: true });
49088
+ const path = join29(opts.out, `${core ? "wcag" : opts.standard}-${r.date}.md`);
49089
+ writeFileSync8(path, md);
48625
49090
  return path;
48626
49091
  }
48627
49092
 
48628
49093
  // src/gh.ts
48629
- import { execFileSync as execFileSync2 } from "child_process";
49094
+ import { execFileSync as execFileSync3 } from "child_process";
48630
49095
  var SEV_ORDER4 = ["bloquant", "majeur", "mineur"];
48631
49096
  var SEV_RANK2 = { bloquant: 0, majeur: 1, mineur: 2 };
48632
49097
  var ICON4 = { bloquant: "\u{1F534}", majeur: "\u{1F7E0}", mineur: "\u{1F7E1}" };
@@ -48641,7 +49106,7 @@ function standardTag(standard) {
48641
49106
  })();
48642
49107
  }
48643
49108
  function gh(args2, input) {
48644
- return execFileSync2("gh", args2, { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"], ...input !== void 0 ? { input } : {} });
49109
+ return execFileSync3("gh", args2, { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"], ...input !== void 0 ? { input } : {} });
48645
49110
  }
48646
49111
  function ghErrorReason(err2) {
48647
49112
  if (!err2 || typeof err2 !== "object") return void 0;
@@ -48654,7 +49119,7 @@ function ghErrorReason(err2) {
48654
49119
  }
48655
49120
  function ghAvailable() {
48656
49121
  try {
48657
- execFileSync2("gh", ["auth", "status"], { stdio: "ignore" });
49122
+ execFileSync3("gh", ["auth", "status"], { stdio: "ignore" });
48658
49123
  return true;
48659
49124
  } catch {
48660
49125
  return false;
@@ -49220,12 +49685,12 @@ function renderCriteriaReference() {
49220
49685
  }
49221
49686
 
49222
49687
  // src/check.ts
49223
- import { existsSync as existsSync14, readFileSync as readFileSync15 } from "fs";
49224
- import { dirname as dirname9, join as join29 } from "path";
49688
+ import { existsSync as existsSync17, readFileSync as readFileSync15 } from "fs";
49689
+ import { dirname as dirname9, join as join31 } from "path";
49225
49690
 
49226
49691
  // src/verify.ts
49227
- import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync8 } from "fs";
49228
- import { join as join28 } from "path";
49692
+ import { mkdirSync as mkdirSync8, writeFileSync as writeFileSync9 } from "fs";
49693
+ import { join as join30 } from "path";
49229
49694
  var VERIFY_MAX = 40;
49230
49695
  var plain = (s) => s.replace(/\[([^\]]+)\]\(#[^)]*\)/g, "$1");
49231
49696
  function auditorCriterionLine(standard) {
@@ -49399,16 +49864,16 @@ function applyVerdicts(items, expected) {
49399
49864
  return { ok: failures.length === 0, total, refuted, unsupported, unadjudicated, invalid, missing, failures };
49400
49865
  }
49401
49866
  function writeWorklist(items, outDir, semantic, standard = "wcag", lang = "en") {
49402
- mkdirSync7(outDir, { recursive: true });
49403
- const todoPath = join28(outDir, "VERIFY.todo.json");
49404
- const mdPath = join28(outDir, "VERIFY.md");
49405
- writeFileSync8(todoPath, JSON.stringify(items, null, 2) + "\n");
49406
- writeFileSync8(mdPath, formatWorklist(items, semantic, standard, lang));
49867
+ mkdirSync8(outDir, { recursive: true });
49868
+ const todoPath = join30(outDir, "VERIFY.todo.json");
49869
+ const mdPath = join30(outDir, "VERIFY.md");
49870
+ writeFileSync9(todoPath, JSON.stringify(items, null, 2) + "\n");
49871
+ writeFileSync9(mdPath, formatWorklist(items, semantic, standard, lang));
49407
49872
  return { todoPath, mdPath, count: items.length };
49408
49873
  }
49409
49874
 
49410
49875
  // src/grounding.ts
49411
- import { existsSync as existsSync13, readFileSync as readFileSync14 } from "fs";
49876
+ import { existsSync as existsSync16, readFileSync as readFileSync14 } from "fs";
49412
49877
  import { resolve as resolve4 } from "path";
49413
49878
  var WINDOW = 10;
49414
49879
  var norm2 = (s) => s.replace(/\s+/g, " ").trim();
@@ -49427,7 +49892,7 @@ function selectorProbes(selector) {
49427
49892
  function groundFinding(g, opts = {}) {
49428
49893
  if (isUnresolvable(g.file)) return { ok: true, moved: false };
49429
49894
  const path = resolve4(opts.cwd ?? process.cwd(), g.file);
49430
- if (!existsSync13(path)) return { ok: false, moved: false, issue: `cited file not found: ${g.file}` };
49895
+ if (!existsSync16(path)) return { ok: false, moved: false, issue: `cited file not found: ${g.file}` };
49431
49896
  let text;
49432
49897
  try {
49433
49898
  text = readFileSync14(path, "utf8");
@@ -49577,8 +50042,8 @@ function checkSemantic(md, opts) {
49577
50042
  const standard = opts.standard ?? "wcag";
49578
50043
  const s = M[lang];
49579
50044
  const empty = { total: 0, grounded: 0, moved: 0, failed: 0 };
49580
- const artifact = opts.verdictsPath ?? join29(dirname9(opts.reportPath), "VERIFY.todo.json");
49581
- if (!existsSync14(artifact)) return { ok: false, issues: [s.semanticMissing(artifact)], ...empty };
50045
+ const artifact = opts.verdictsPath ?? join31(dirname9(opts.reportPath), "VERIFY.todo.json");
50046
+ if (!existsSync17(artifact)) return { ok: false, issues: [s.semanticMissing(artifact)], ...empty };
49582
50047
  let items;
49583
50048
  try {
49584
50049
  const parsed = JSON.parse(readFileSync15(artifact, "utf8"));
@@ -49613,8 +50078,8 @@ function sectionBody(md, n) {
49613
50078
  }
49614
50079
 
49615
50080
  // src/adjudicate.ts
49616
- import { mkdirSync as mkdirSync8, writeFileSync as writeFileSync9 } from "fs";
49617
- import { join as join30 } from "path";
50081
+ import { mkdirSync as mkdirSync9, writeFileSync as writeFileSync10 } from "fs";
50082
+ import { join as join32 } from "path";
49618
50083
 
49619
50084
  // src/data/adjudication.json
49620
50085
  var adjudication_default = {
@@ -50537,7 +51002,7 @@ function docsForAudit(audit2, cwd) {
50537
51002
  const docs = [];
50538
51003
  for (const f of files) {
50539
51004
  try {
50540
- docs.push(parseSource(readText(cwd ? join30(cwd, f) : f), f));
51005
+ docs.push(parseSource(readText(cwd ? join32(cwd, f) : f), f));
50541
51006
  } catch {
50542
51007
  }
50543
51008
  }
@@ -50894,9 +51359,9 @@ function formatAdjudication(items, lang = "en", standard = CORE2) {
50894
51359
  return out2.join("\n");
50895
51360
  }
50896
51361
  function writeAdjudication(items, outDir, opts) {
50897
- mkdirSync8(outDir, { recursive: true });
50898
- const todoPath = join30(outDir, "ADJUDICATE.todo.json");
50899
- const mdPath = join30(outDir, "ADJUDICATE.md");
51362
+ mkdirSync9(outDir, { recursive: true });
51363
+ const todoPath = join32(outDir, "ADJUDICATE.todo.json");
51364
+ const mdPath = join32(outDir, "ADJUDICATE.md");
50900
51365
  const file = {
50901
51366
  tool: "ultra11y",
50902
51367
  kind: "adjudication",
@@ -50905,16 +51370,16 @@ function writeAdjudication(items, outDir, opts) {
50905
51370
  auditDate: opts.auditDate,
50906
51371
  items
50907
51372
  };
50908
- writeFileSync9(todoPath, JSON.stringify(file, null, 2) + "\n");
50909
- writeFileSync9(mdPath, formatAdjudication(items, opts.lang ?? "en", opts.standard));
51373
+ writeFileSync10(todoPath, JSON.stringify(file, null, 2) + "\n");
51374
+ writeFileSync10(mdPath, formatAdjudication(items, opts.lang ?? "en", opts.standard));
50910
51375
  return { todoPath, mdPath, count: items.length };
50911
51376
  }
50912
51377
 
50913
51378
  // src/scan.ts
50914
- import { execFileSync as execFileSync3 } from "child_process";
50915
- import { mkdtempSync as mkdtempSync2, writeFileSync as writeFileSync10, existsSync as existsSync16, statSync as statSync7, readdirSync as readdirSync5, rmSync as rmSync3, readFileSync as readFileSync16 } from "fs";
50916
- import { tmpdir } from "os";
50917
- import { join as join31, resolve as resolve5 } from "path";
51379
+ import { execFileSync as execFileSync4 } from "child_process";
51380
+ import { mkdtempSync as mkdtempSync2, writeFileSync as writeFileSync11, existsSync as existsSync19, statSync as statSync8, readdirSync as readdirSync5, rmSync as rmSync3, readFileSync as readFileSync16 } from "fs";
51381
+ import { tmpdir as tmpdir2 } from "os";
51382
+ import { join as join33, resolve as resolve5 } from "path";
50918
51383
  import { fileURLToPath as fileURLToPath3 } from "url";
50919
51384
 
50920
51385
  // src/axe-map.ts
@@ -51156,7 +51621,7 @@ async function crawlUrls(start2, opts) {
51156
51621
  }
51157
51622
 
51158
51623
  // src/sample.ts
51159
- import { existsSync as existsSync15 } from "fs";
51624
+ import { existsSync as existsSync18 } from "fs";
51160
51625
  function looksLikeTarget(u) {
51161
51626
  return /^https?:\/\//i.test(u) || /^(\.\.?[/\\]|[/\\])/.test(u) || /\.x?html?$/i.test(u);
51162
51627
  }
@@ -51192,7 +51657,7 @@ function validateSample(raw) {
51192
51657
  if (p.auth !== void 0 && typeof p.auth !== "boolean") err2(`sample.pages[${i2}].auth`, "auth must be a boolean");
51193
51658
  if (p.storageState !== void 0 && (typeof p.storageState !== "string" || p.storageState.trim() === "")) {
51194
51659
  err2(`sample.pages[${i2}].storageState`, "storageState must be a non-empty file path string");
51195
- } else if (typeof p.storageState === "string" && p.storageState.trim() !== "" && !existsSync15(p.storageState)) {
51660
+ } else if (typeof p.storageState === "string" && p.storageState.trim() !== "" && !existsSync18(p.storageState)) {
51196
51661
  warn(
51197
51662
  `sample.pages[${i2}].storageState`,
51198
51663
  `storageState path not found: "${p.storageState}" (resolved from the current directory) \u2014 the scan will not be able to load this session`
@@ -51301,7 +51766,7 @@ ENTRYPOINT ["node", "/app/runner.mjs"]
51301
51766
  `;
51302
51767
  function dockerAvailable() {
51303
51768
  try {
51304
- execFileSync3("docker", ["info"], { stdio: "ignore", timeout: 1e4 });
51769
+ execFileSync4("docker", ["info"], { stdio: "ignore", timeout: 1e4 });
51305
51770
  return true;
51306
51771
  } catch {
51307
51772
  return false;
@@ -51309,7 +51774,7 @@ function dockerAvailable() {
51309
51774
  }
51310
51775
  function imageExists(tag) {
51311
51776
  try {
51312
- execFileSync3("docker", ["image", "inspect", tag], { stdio: "ignore" });
51777
+ execFileSync4("docker", ["image", "inspect", tag], { stdio: "ignore" });
51313
51778
  return true;
51314
51779
  } catch {
51315
51780
  return false;
@@ -51317,22 +51782,22 @@ function imageExists(tag) {
51317
51782
  }
51318
51783
  var CTX_PREFIX = "ultra11y-dyn-";
51319
51784
  function buildImage(tag = IMAGE_TAG) {
51320
- const ctx = mkdtempSync2(join31(tmpdir(), CTX_PREFIX));
51785
+ const ctx = mkdtempSync2(join33(tmpdir2(), CTX_PREFIX));
51321
51786
  try {
51322
- writeFileSync10(join31(ctx, "runner.mjs"), RUNNER);
51323
- writeFileSync10(join31(ctx, "package.json"), PKG);
51324
- writeFileSync10(join31(ctx, "Dockerfile"), DOCKERFILE);
51325
- execFileSync3("docker", ["build", "-t", tag, ctx], { stdio: "inherit", timeout: 9e5 });
51787
+ writeFileSync11(join33(ctx, "runner.mjs"), RUNNER);
51788
+ writeFileSync11(join33(ctx, "package.json"), PKG);
51789
+ writeFileSync11(join33(ctx, "Dockerfile"), DOCKERFILE);
51790
+ execFileSync4("docker", ["build", "-t", tag, ctx], { stdio: "inherit", timeout: 9e5 });
51326
51791
  } finally {
51327
51792
  rmSync3(ctx, { recursive: true, force: true });
51328
51793
  }
51329
51794
  }
51330
51795
  function cleanTempContexts() {
51331
51796
  let removed = 0;
51332
- const dir = tmpdir();
51797
+ const dir = tmpdir2();
51333
51798
  for (const name2 of readdirSync5(dir)) {
51334
51799
  if (!name2.startsWith(CTX_PREFIX)) continue;
51335
- rmSync3(join31(dir, name2), { recursive: true, force: true });
51800
+ rmSync3(join33(dir, name2), { recursive: true, force: true });
51336
51801
  removed++;
51337
51802
  }
51338
51803
  return removed;
@@ -51341,7 +51806,7 @@ function cleanDynamic(tag = IMAGE_TAG) {
51341
51806
  let imageRemoved = false;
51342
51807
  if (dockerAvailable() && imageExists(tag)) {
51343
51808
  try {
51344
- execFileSync3("docker", ["rmi", "-f", tag], { stdio: "ignore" });
51809
+ execFileSync4("docker", ["rmi", "-f", tag], { stdio: "ignore" });
51345
51810
  imageRemoved = true;
51346
51811
  } catch {
51347
51812
  }
@@ -51364,7 +51829,7 @@ function runRunner(target, isFile, tag) {
51364
51829
  args2.push(tag, isFile ? MOUNT : target);
51365
51830
  let stdout;
51366
51831
  try {
51367
- stdout = execFileSync3("docker", args2, { encoding: "utf8", timeout: 24e4, maxBuffer: 32 * 1024 * 1024, stdio: ["ignore", "pipe", "pipe"] });
51832
+ stdout = execFileSync4("docker", args2, { encoding: "utf8", timeout: 24e4, maxBuffer: 32 * 1024 * 1024, stdio: ["ignore", "pipe", "pipe"] });
51368
51833
  } catch (e) {
51369
51834
  const err2 = e;
51370
51835
  const detail = (err2.stderr ? String(err2.stderr).trim() : "") || err2.message || String(e);
@@ -51443,7 +51908,7 @@ function toDynamicResult(out2, target, lang = "en", engine = "axe-core@playwrigh
51443
51908
  var DOCKER_TESTED_SCS = ["1.4.10"];
51444
51909
  function runScan(opts) {
51445
51910
  const isUrl3 = /^https?:\/\//i.test(opts.target);
51446
- if (!isUrl3 && !existsSync16(opts.target)) {
51911
+ if (!isUrl3 && !existsSync19(opts.target)) {
51447
51912
  throw new Error(`File not found: ${opts.target}. Pass an http(s):// URL or an existing HTML file.`);
51448
51913
  }
51449
51914
  if (!dockerAvailable()) {
@@ -51451,7 +51916,7 @@ function runScan(opts) {
51451
51916
  }
51452
51917
  const tag = opts.tag ?? IMAGE_TAG;
51453
51918
  if (!imageExists(tag)) buildImage(tag);
51454
- const isFile = !isUrl3 && existsSync16(opts.target) && statSync7(opts.target).isFile();
51919
+ const isFile = !isUrl3 && existsSync19(opts.target) && statSync8(opts.target).isFile();
51455
51920
  const out2 = runRunner(opts.target, isFile, tag);
51456
51921
  return { ...toDynamicResult(out2, opts.target), testedScs: [...DOCKER_TESTED_SCS] };
51457
51922
  }
@@ -51529,7 +51994,7 @@ async function runCrawlScan(opts) {
51529
51994
  var sevRank = { bloquant: 3, majeur: 2, mineur: 1 };
51530
51995
  function resolveHostAnchor(file, snippet2) {
51531
51996
  const s = snippet2?.trim();
51532
- if (!s || !existsSync16(file)) return null;
51997
+ if (!s || !existsSync19(file)) return null;
51533
51998
  let source;
51534
51999
  try {
51535
52000
  source = readFileSync16(file, "utf8");
@@ -51628,7 +52093,7 @@ function mergeDynamic(audit2, dynamic, lang = "en") {
51628
52093
  }
51629
52094
 
51630
52095
  // src/scan-local.ts
51631
- import { existsSync as existsSync17, statSync as statSync8 } from "fs";
52096
+ import { existsSync as existsSync20, statSync as statSync9 } from "fs";
51632
52097
  import { createRequire } from "module";
51633
52098
  import { resolve as resolve6 } from "path";
51634
52099
  var LOCAL_ENGINE = "axe-core@playwright (local)";
@@ -52184,10 +52649,10 @@ async function runOnPage(browser, AxeBuilder, target, isFile, opts) {
52184
52649
  }
52185
52650
  async function runScanLocal(opts) {
52186
52651
  const isUrl3 = /^https?:\/\//i.test(opts.target);
52187
- if (!isUrl3 && !existsSync17(opts.target)) {
52652
+ if (!isUrl3 && !existsSync20(opts.target)) {
52188
52653
  throw new Error(`File not found: ${opts.target}. Pass an http(s):// URL or an existing HTML file.`);
52189
52654
  }
52190
- const isFile = !isUrl3 && statSync8(opts.target).isFile();
52655
+ const isFile = !isUrl3 && statSync9(opts.target).isFile();
52191
52656
  const lang = opts.lang ?? "en";
52192
52657
  const interact = opts.interact !== false;
52193
52658
  const { chromium, AxeBuilder } = resolveLocalDeps(opts.cwd);
@@ -52265,7 +52730,7 @@ async function runCrawlScanLocal(opts) {
52265
52730
  }
52266
52731
 
52267
52732
  // src/fix.ts
52268
- import { writeFileSync as writeFileSync11 } from "fs";
52733
+ import { writeFileSync as writeFileSync12 } from "fs";
52269
52734
 
52270
52735
  // src/fix/edits.ts
52271
52736
  function openTag(source, start2) {
@@ -52479,7 +52944,7 @@ function fixOne(file, source, opts, canWrite = true) {
52479
52944
  regression = after.length > findings.length || after.some((f) => !beforeKinds.has(f.ruleId));
52480
52945
  if (opts.write && !regression && file !== "<stdin>") {
52481
52946
  if (canWrite) {
52482
- writeFileSync11(file, output);
52947
+ writeFileSync12(file, output);
52483
52948
  written = true;
52484
52949
  } else {
52485
52950
  skippedPartialStage = true;
@@ -52560,55 +53025,14 @@ function fixSummary(r, lang = "fr", write = false) {
52560
53025
  return out2.join("\n");
52561
53026
  }
52562
53027
 
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
53028
  // src/init.ts
52605
- import { writeFileSync as writeFileSync12, mkdirSync as mkdirSync9, chmodSync } from "fs";
52606
- import { execFileSync as execFileSync4 } from "child_process";
52607
- import { join as join32 } from "path";
53029
+ import { writeFileSync as writeFileSync13, mkdirSync as mkdirSync10, chmodSync } from "fs";
53030
+ import { execFileSync as execFileSync5 } from "child_process";
53031
+ import { join as join34 } from "path";
52608
53032
  var EN_SEV = { bloquant: "blocking", majeur: "major", mineur: "minor" };
52609
53033
  function repoRoot() {
52610
53034
  try {
52611
- return execFileSync4("git", ["rev-parse", "--show-toplevel"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
53035
+ return execFileSync5("git", ["rev-parse", "--show-toplevel"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
52612
53036
  } catch {
52613
53037
  return null;
52614
53038
  }
@@ -52700,18 +53124,18 @@ jobs:
52700
53124
  `;
52701
53125
  }
52702
53126
  function writeHook(root, enginePath, failOn, mode = "staged") {
52703
- const dir = join32(root, ".git", "hooks");
52704
- mkdirSync9(dir, { recursive: true });
52705
- const path = join32(dir, "pre-commit");
52706
- writeFileSync12(path, mode === "baseline" ? hookScript(enginePath, failOn) : stagedHookScript(enginePath, failOn));
53127
+ const dir = join34(root, ".git", "hooks");
53128
+ mkdirSync10(dir, { recursive: true });
53129
+ const path = join34(dir, "pre-commit");
53130
+ writeFileSync13(path, mode === "baseline" ? hookScript(enginePath, failOn) : stagedHookScript(enginePath, failOn));
52707
53131
  chmodSync(path, 493);
52708
53132
  return path;
52709
53133
  }
52710
53134
  function writeCi(root, enginePath, failOn) {
52711
- const dir = join32(root, ".github", "workflows");
52712
- mkdirSync9(dir, { recursive: true });
52713
- const path = join32(dir, "a11y.yml");
52714
- writeFileSync12(path, ciWorkflow(enginePath, failOn));
53135
+ const dir = join34(root, ".github", "workflows");
53136
+ mkdirSync10(dir, { recursive: true });
53137
+ const path = join34(dir, "a11y.yml");
53138
+ writeFileSync13(path, ciWorkflow(enginePath, failOn));
52715
53139
  return path;
52716
53140
  }
52717
53141
 
@@ -53012,7 +53436,7 @@ function stepSummary(result, opts = {}) {
53012
53436
 
53013
53437
  // src/dev.ts
53014
53438
  import { createServer } from "http";
53015
- import { join as join33 } from "path";
53439
+ import { join as join35 } from "path";
53016
53440
  var DEV_DEFAULT_PORT = 4111;
53017
53441
  function criterionLabel3(f, standard) {
53018
53442
  if (isCore(standard)) return `WCAG ${f.criteriaId}`;
@@ -53278,13 +53702,13 @@ function auditCollected(root, payload) {
53278
53702
  ...payload.axtree ? { axtree: payload.axtree } : {},
53279
53703
  ...payload.css ? { css: payload.css } : {}
53280
53704
  });
53281
- return { ok: true, result: runAudit({ inputs: [join33(dir, "dom.html")] }) };
53705
+ return { ok: true, result: runAudit({ inputs: [join35(dir, "dom.html")] }) };
53282
53706
  }
53283
53707
  function projectPages(root) {
53284
53708
  const snaps = readSnapshots(root);
53285
53709
  if (!snaps.length) return { result: null, pages: [] };
53286
53710
  const scope = pageScopesFrom(snaps);
53287
- const result = runAudit({ inputs: [join33(root, ".ultra11y/pages")] });
53711
+ const result = runAudit({ inputs: [join35(root, ".ultra11y/pages")] });
53288
53712
  result.scope.pages = scope;
53289
53713
  attributePages(result, scope);
53290
53714
  return { result, pages: derivePages(result, scope) };
@@ -53608,287 +54032,12 @@ function e2eSetupPlan(runners, paths, lang = "en") {
53608
54032
  return out2.join("\n");
53609
54033
  }
53610
54034
 
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
54035
  // src/orchestrate.ts
53887
- import { existsSync as existsSync20, mkdirSync as mkdirSync10, readFileSync as readFileSync17, rmSync as rmSync4, writeFileSync as writeFileSync13 } from "fs";
53888
- import { join as join36, resolve as resolve7 } from "path";
54036
+ import { existsSync as existsSync21, mkdirSync as mkdirSync11, readFileSync as readFileSync17, rmSync as rmSync4, writeFileSync as writeFileSync14 } from "fs";
54037
+ import { join as join37, resolve as resolve7 } from "path";
53889
54038
 
53890
54039
  // src/orchestrate-templates.ts
53891
- import { join as join35 } from "path";
54040
+ import { join as join36 } from "path";
53892
54041
  var ONE_WRITER_FOOTER = `
53893
54042
  ## Return, don't write
53894
54043
 
@@ -53960,7 +54109,7 @@ var PHASE_SPECS = {
53960
54109
  title: "Adjudicate",
53961
54110
  schema: ADJUDICATE_SCHEMA,
53962
54111
  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 ${join35(run2, "audit-latest.json")} --out ${run2}`
54112
+ applyHint: (engine, worklist, run2) => `node ${engine} verify --apply ${worklist} --in ${join36(run2, "audit-latest.json")} --out ${run2}`
53964
54113
  },
53965
54114
  "verify-report": {
53966
54115
  role: "refuter",
@@ -53982,7 +54131,7 @@ function toBatches(ids, batchSize) {
53982
54131
  }
53983
54132
  function phaseWorkflowScript(ph, runAbs, engineAbs, batchSize) {
53984
54133
  const spec = phaseSpec(ph.name);
53985
- const scriptPath = join35(runAbs, "orchestration", `${ph.name}.workflow.mjs`);
54134
+ const scriptPath = join36(runAbs, "orchestration", `${ph.name}.workflow.mjs`);
53986
54135
  const meta2 = { name: `ultra11y-${ph.name}`, description: spec.description(ph.items), phases: [{ title: spec.title }] };
53987
54136
  return [
53988
54137
  `export const meta = ${JSON.stringify(meta2)}`,
@@ -54026,7 +54175,7 @@ function agentContracts(runAbs, engineAbs) {
54026
54175
 
54027
54176
  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
54177
 
54029
- Worklist: \`${join35(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>\`).
54178
+ 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
54179
 
54031
54180
  For EACH of your criteria:
54032
54181
 
@@ -54044,7 +54193,7 @@ ${footer}`,
54044
54193
 
54045
54194
  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
54195
 
54047
- Worklist: \`${join35(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>\`).
54196
+ 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
54197
 
54049
54198
  For EACH of your entries:
54050
54199
 
@@ -54080,14 +54229,14 @@ ${status}
54080
54229
 
54081
54230
  ## The loop (play every role yourself, one item at a time)
54082
54231
 
54083
- 1. **Audit** (if not done): \`${engine} audit "<globs>" --graph --out ${runAbs}\` \u2192 \`${join35(runAbs, "audit-latest.json")}\`.
54084
- 2. **Adjudicate the residual criteria** \u2014 \`${engine} verify --manual --in ${join35(runAbs, "audit-latest.json")} --out ${runAbs}\` writes \`${join35(runAbs, "ADJUDICATE.todo.json")}\`. For EVERY item, apply \`${join35(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 ${join35(runAbs, "ADJUDICATE.todo.json")} --in ${join35(runAbs, "audit-latest.json")} --out ${runAbs}\`.
54085
- 3. **Report**: \`${engine} report --in ${join35(runAbs, "audit-latest.json")} --out ${runAbs}\`.
54086
- 4. **Verify the report's claims** \u2014 \`${engine} verify --report <the report .md> --out ${runAbs}\` writes \`${join35(runAbs, "VERIFY.todo.json")}\`. For EVERY entry, apply \`${join35(runAbs, "orchestration", "agents", "refuter.md")}\` yourself (open file:line, verdict supported/partial/refuted/unsupported + note IN the todo file). Then: \`${engine} verify --apply ${join35(runAbs, "VERIFY.todo.json")} --report <the report .md>\`.
54232
+ 1. **Audit** (if not done): \`${engine} audit "<globs>" --graph --out ${runAbs}\` \u2192 \`${join36(runAbs, "audit-latest.json")}\`.
54233
+ 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}\`.
54234
+ 3. **Report**: \`${engine} report --in ${join36(runAbs, "audit-latest.json")} --out ${runAbs}\`.
54235
+ 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
54236
  5. **Gate**: \`${engine} check --report <the report .md> --semantic\` must exit 0 before presenting anything.
54088
54237
  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
54238
 
54090
- With subagents available, prefer the emitted workflows instead: \`orchestrate --run ${runAbs} --phase <p>\` then \`Workflow({ scriptPath: "${join35(runAbs, "orchestration", "<p>.workflow.mjs")}" })\` \u2014 you stay the sole writer either way.
54239
+ 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
54240
  `;
54092
54241
  }
54093
54242
 
@@ -54097,10 +54246,10 @@ var SMALL_WORKLIST = 3;
54097
54246
  var BATCH_SIZE = 8;
54098
54247
  function listPhases(runDir, engineAbs) {
54099
54248
  const run2 = resolve7(runDir);
54100
- const adjPath = join36(run2, "ADJUDICATE.todo.json");
54249
+ const adjPath = join37(run2, "ADJUDICATE.todo.json");
54101
54250
  let adjIds = [];
54102
54251
  let adjReady = false;
54103
- if (existsSync20(adjPath)) {
54252
+ if (existsSync21(adjPath)) {
54104
54253
  try {
54105
54254
  const f = JSON.parse(readFileSync17(adjPath, "utf8"));
54106
54255
  if (f && f.kind === "adjudication" && Array.isArray(f.items)) {
@@ -54110,10 +54259,10 @@ function listPhases(runDir, engineAbs) {
54110
54259
  } catch {
54111
54260
  }
54112
54261
  }
54113
- const verPath = join36(run2, "VERIFY.todo.json");
54262
+ const verPath = join37(run2, "VERIFY.todo.json");
54114
54263
  let verIds = [];
54115
54264
  let verReady = false;
54116
- if (existsSync20(verPath)) {
54265
+ if (existsSync21(verPath)) {
54117
54266
  try {
54118
54267
  const items = JSON.parse(readFileSync17(verPath, "utf8"));
54119
54268
  if (Array.isArray(items)) {
@@ -54130,7 +54279,7 @@ function listPhases(runDir, engineAbs) {
54130
54279
  worklist: adjPath,
54131
54280
  items: adjIds.length,
54132
54281
  ids: adjIds,
54133
- prerequisite: `node ${engineAbs} verify --manual --in ${join36(run2, "audit-latest.json")} --out ${run2}`
54282
+ prerequisite: `node ${engineAbs} verify --manual --in ${join37(run2, "audit-latest.json")} --out ${run2}`
54134
54283
  },
54135
54284
  {
54136
54285
  name: "verify-report",
@@ -54144,7 +54293,7 @@ function listPhases(runDir, engineAbs) {
54144
54293
  }
54145
54294
  function orchestrateRun(runDir, engineAbs, opts = {}) {
54146
54295
  const run2 = resolve7(runDir);
54147
- if (!existsSync20(run2)) {
54296
+ if (!existsSync21(run2)) {
54148
54297
  return { exitCode: 2, written: [], notices: [], errors: [`run dir not found: ${run2}`], phases: [] };
54149
54298
  }
54150
54299
  const phases = listPhases(run2, engineAbs);
@@ -54171,22 +54320,22 @@ function orchestrateRun(runDir, engineAbs, opts = {}) {
54171
54320
  }
54172
54321
  selected = [ph];
54173
54322
  }
54174
- const orchDir = join36(run2, "orchestration");
54175
- const agentsDir = join36(orchDir, "agents");
54176
- mkdirSync10(join36(orchDir, "out"), { recursive: true });
54177
- mkdirSync10(agentsDir, { recursive: true });
54323
+ const orchDir = join37(run2, "orchestration");
54324
+ const agentsDir = join37(orchDir, "agents");
54325
+ mkdirSync11(join37(orchDir, "out"), { recursive: true });
54326
+ mkdirSync11(agentsDir, { recursive: true });
54178
54327
  const written = [];
54179
54328
  const notices = [];
54180
54329
  for (const [name2, content] of Object.entries(agentContracts(run2, engineAbs))) {
54181
- const p = join36(agentsDir, `${name2}.md`);
54182
- writeFileSync13(p, content);
54330
+ const p = join37(agentsDir, `${name2}.md`);
54331
+ writeFileSync14(p, content);
54183
54332
  written.push(p);
54184
54333
  }
54185
54334
  const emitted = new Set(opts.eco ? [] : selected.filter((p) => p.items > 0).map((p) => p.name));
54186
54335
  for (const ph of phases) {
54187
54336
  if (emitted.has(ph.name)) continue;
54188
- const stale = join36(orchDir, `${ph.name}.workflow.mjs`);
54189
- if (existsSync20(stale)) {
54337
+ const stale = join37(orchDir, `${ph.name}.workflow.mjs`);
54338
+ if (existsSync21(stale)) {
54190
54339
  rmSync4(stale, { force: true });
54191
54340
  notices.push(`phase "${ph.name}": stale workflow removed \u2014 its worklist ${ph.ready ? "is now empty" : "no longer exists"}.`);
54192
54341
  }
@@ -54200,13 +54349,13 @@ function orchestrateRun(runDir, engineAbs, opts = {}) {
54200
54349
  if (ph.items <= SMALL_WORKLIST) {
54201
54350
  notices.push(`phase "${ph.name}": only ${ph.items} item(s) \u2014 the sequential --eco path is equivalent and cheaper.`);
54202
54351
  }
54203
- const p = join36(orchDir, `${ph.name}.workflow.mjs`);
54204
- writeFileSync13(p, phaseWorkflowScript(ph, run2, engineAbs, BATCH_SIZE));
54352
+ const p = join37(orchDir, `${ph.name}.workflow.mjs`);
54353
+ writeFileSync14(p, phaseWorkflowScript(ph, run2, engineAbs, BATCH_SIZE));
54205
54354
  written.push(p);
54206
54355
  }
54207
54356
  }
54208
- const rb = join36(orchDir, "RUNBOOK.md");
54209
- writeFileSync13(rb, runbookMd(phases, run2, engineAbs));
54357
+ const rb = join37(orchDir, "RUNBOOK.md");
54358
+ writeFileSync14(rb, runbookMd(phases, run2, engineAbs));
54210
54359
  written.push(rb);
54211
54360
  return { exitCode: 0, written, notices, errors: [], phases };
54212
54361
  }
@@ -54215,8 +54364,8 @@ function orchestrateRun(runDir, engineAbs, opts = {}) {
54215
54364
  import { createInterface as createInterface2 } from "readline";
54216
54365
 
54217
54366
  // src/mcp/handlers.ts
54218
- import { existsSync as existsSync21, readFileSync as readFileSync18, realpathSync as realpathSync2, statSync as statSync10 } from "fs";
54219
- import { isAbsolute as isAbsolute3, join as join37, resolve as resolve8, sep as sep4 } from "path";
54367
+ import { existsSync as existsSync22, readFileSync as readFileSync18, realpathSync as realpathSync2, statSync as statSync10 } from "fs";
54368
+ import { isAbsolute as isAbsolute3, join as join38, resolve as resolve8, sep as sep4 } from "path";
54220
54369
 
54221
54370
  // src/project-lock.ts
54222
54371
  var chains = /* @__PURE__ */ new Map();
@@ -54264,7 +54413,7 @@ function requiredCwd(args2, defaults) {
54264
54413
  const cwd = str2(args2.cwd) ?? defaults.defaultCwd;
54265
54414
  if (!cwd) throw new ToolError("`cwd` is required: an absolute path to the project root.");
54266
54415
  const abs = resolve8(cwd);
54267
- if (!existsSync21(abs)) throw new ToolError(`project root not found: ${abs}`);
54416
+ if (!existsSync22(abs)) throw new ToolError(`project root not found: ${abs}`);
54268
54417
  if (!statSync10(abs).isDirectory()) throw new ToolError(`\`cwd\` is not a directory: ${abs}`);
54269
54418
  return abs;
54270
54419
  }
@@ -54409,8 +54558,8 @@ function handlePackCheck(args2, cwd) {
54409
54558
  return { cwd, ...res };
54410
54559
  }
54411
54560
  function handleSampleCheck(cwd) {
54412
- const file = join37(cwd, ".ultra11yrc.json");
54413
- if (!existsSync21(file)) {
54561
+ const file = join38(cwd, ".ultra11yrc.json");
54562
+ if (!existsSync22(file)) {
54414
54563
  throw new ToolError(`no .ultra11yrc.json at ${cwd} \u2014 a page sample must be declared before it can be linted.`);
54415
54564
  }
54416
54565
  let raw;
@@ -54453,7 +54602,7 @@ function handleInit(args2, cwd) {
54453
54602
  const enginePath = resolve8("scripts/ultra11y.mjs");
54454
54603
  const written = [];
54455
54604
  if (bool(args2.hook)) written.push(writeHook(cwd, enginePath, failOn));
54456
- if (bool(args2.ci)) written.push(join37(cwd, ".github/workflows/a11y.yml"));
54605
+ if (bool(args2.ci)) written.push(join38(cwd, ".github/workflows/a11y.yml"));
54457
54606
  if (!written.length) {
54458
54607
  throw new ToolError("nothing to install \u2014 pass hook:true and/or ci:true.");
54459
54608
  }
@@ -54473,7 +54622,7 @@ function reportText(args2, tool) {
54473
54622
  if (inline) return inline;
54474
54623
  if (!file) throw new ToolError(`\`report_text\` is required \u2014 the report markdown for ultra11y_${tool} to work on.`);
54475
54624
  if (!isAbsolute3(file)) throw new ToolError("`report_file` must be an absolute path.");
54476
- if (!existsSync21(file)) throw new ToolError(`report file not found: ${file}`);
54625
+ if (!existsSync22(file)) throw new ToolError(`report file not found: ${file}`);
54477
54626
  return readFileSync18(file, "utf8");
54478
54627
  }
54479
54628
  function handleCriteria(args2) {
@@ -54489,7 +54638,7 @@ function handleCriteria(args2) {
54489
54638
  function handleRead(args2, cwd) {
54490
54639
  const raw = str2(args2.path);
54491
54640
  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 : join37(cwd, raw);
54641
+ const target = isAbsolute3(raw) ? raw : join38(cwd, raw);
54493
54642
  let real;
54494
54643
  try {
54495
54644
  real = realpathSync2(target);
@@ -54998,25 +55147,25 @@ function str3(v) {
54998
55147
  var DECLARED = new Set([...TOOLS2, ...WRITE_TOOLS].map((t2) => t2.name));
54999
55148
 
55000
55149
  // src/mcp/resources.ts
55001
- import { existsSync as existsSync22, readdirSync as readdirSync6, readFileSync as readFileSync19, realpathSync as realpathSync3, statSync as statSync11 } from "fs";
55002
- import { basename as basename3, dirname as dirname10, join as join38, resolve as resolve9, sep as sep5 } from "path";
55150
+ import { existsSync as existsSync23, readdirSync as readdirSync6, readFileSync as readFileSync19, realpathSync as realpathSync3, statSync as statSync11 } from "fs";
55151
+ import { basename as basename3, dirname as dirname10, join as join39, resolve as resolve9, sep as sep5 } from "path";
55003
55152
  import { fileURLToPath as fileURLToPath4 } from "url";
55004
55153
  var SKILL_NAME = "ultra11y";
55005
55154
  var URI_SCHEME = "skill://";
55006
55155
  function resolveSkillRoot(moduleDir) {
55007
55156
  const here = moduleDir ?? dirname10(fileURLToPath4(import.meta.url));
55008
55157
  const candidates2 = [resolve9(here, ".."), resolve9(here, "..", "skills", SKILL_NAME), resolve9(here, "..", "..", "skills", SKILL_NAME)];
55009
- return candidates2.find((dir) => existsSync22(join38(dir, "SKILL.md")));
55158
+ return candidates2.find((dir) => existsSync23(join39(dir, "SKILL.md")));
55010
55159
  }
55011
55160
  function listResources(moduleDir) {
55012
55161
  const root = resolveSkillRoot(moduleDir);
55013
55162
  if (!root) return [];
55014
55163
  const out2 = [describe(root, "SKILL.md", `${SKILL_NAME}: the skill`)];
55015
- const refDir = join38(root, "references");
55016
- if (!existsSync22(refDir)) return out2;
55164
+ const refDir = join39(root, "references");
55165
+ if (!existsSync23(refDir)) return out2;
55017
55166
  for (const file of readdirSync6(refDir).sort()) {
55018
55167
  if (!file.endsWith(".md")) continue;
55019
- out2.push(describe(root, join38("references", file), `${SKILL_NAME} reference: ${basename3(file, ".md")}`));
55168
+ out2.push(describe(root, join39("references", file), `${SKILL_NAME} reference: ${basename3(file, ".md")}`));
55020
55169
  }
55021
55170
  return out2;
55022
55171
  }
@@ -55051,7 +55200,7 @@ function describe(root, rel2, fallbackTitle) {
55051
55200
  title: fallbackTitle,
55052
55201
  mimeType: "text/markdown"
55053
55202
  };
55054
- const summary = firstProse(join38(root, rel2));
55203
+ const summary = firstProse(join39(root, rel2));
55055
55204
  if (summary) decl.description = summary;
55056
55205
  return decl;
55057
55206
  }
@@ -55491,6 +55640,7 @@ Usage:
55491
55640
  ultra11y snapshot list [--root <dir>] [--json]
55492
55641
  ultra11y pages --in <audit.json> [--standard <pack>] [--json] [--lang auto|en|fr] (the per-page criterion grid)
55493
55642
  ultra11y mcp [--transport stdio|http] [--cwd <dir>] [--allow-write] [--port <n>] [--bind <addr>] [--allow-remote] [--allow-origin <o>] [--max-response-bytes <n>]
55643
+ ultra11y hook --claude-code (internal: the plugin's PreToolUse hook; payload on stdin)
55494
55644
  ultra11y dev [--port <n>] [--root <dir>] [--standard <pack>] [--lang auto|en|fr] (dev side-car: live overlay + per-page dashboard)
55495
55645
  ultra11y dev --next [--port <n>] (write the Next overlay component, then wire one line into your layout)
55496
55646
 
@@ -55631,6 +55781,13 @@ Commands:
55631
55781
  --transport http listens on 127.0.0.1 (--port, --bind), and stays
55632
55782
  loopback-only unless you pass --allow-remote. Read-only by default:
55633
55783
  --allow-write opts into the commands that touch files.
55784
+ hook INTERNAL \u2014 the decision half of the Claude Code plugin's PreToolUse
55785
+ hook. Reads the payload on stdin; when a pending 'git commit', 'git
55786
+ push' or 'gh pr create' carries findings >= the threshold, prints the
55787
+ hook JSON that gets the review-a11y skill invoked, and prints nothing
55788
+ otherwise. Threshold: ULTRA11Y_HOOK_FAIL_ON, else hook.failOn in
55789
+ .ultra11yrc.json, else blocking. Disable with ULTRA11Y_HOOK=off or
55790
+ SKIP_A11Y=1. Called by hooks/pre-tool-use.mjs, not by hand.
55634
55791
  pages The per-page criterion grid \u2014 RGAA is a per-page norm, the engine's
55635
55792
  verdict is scope-wide, and this bridges the two. One row per criterion
55636
55793
  (the pack's own under --standard), one column per page. Rebuilt from a
@@ -55784,7 +55941,8 @@ var COMMANDS = [
55784
55941
  "fix",
55785
55942
  "init",
55786
55943
  "pack",
55787
- "orchestrate"
55944
+ "orchestrate",
55945
+ "hook"
55788
55946
  ];
55789
55947
  function isCommand(s) {
55790
55948
  return !!s && COMMANDS.includes(s);
@@ -56005,9 +56163,9 @@ async function cmdAudit(p) {
56005
56163
  const capturesFlag = typeof p.flags.captures === "string" && p.flags.captures ? p.flags.captures : void 0;
56006
56164
  const capturesDir = capturesFlag ?? ".ultra11y/captures";
56007
56165
  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 || existsSync23(capturesDir));
56166
+ const capturesWanted = p.flags["no-captures"] !== true && !inputs.includes("-") && (capturesFlag !== void 0 || existsSync24(capturesDir));
56009
56167
  const useCaptures = capturesWanted && !scopedToDiff && !inputs.includes(capturesDir);
56010
- const pagesWanted = p.flags["no-captures"] !== true && !inputs.includes("-") && existsSync23(PAGES_DIR);
56168
+ const pagesWanted = p.flags["no-captures"] !== true && !inputs.includes("-") && existsSync24(PAGES_DIR);
56011
56169
  const usePages = pagesWanted && !scopedToDiff && !inputs.includes(PAGES_DIR);
56012
56170
  const auditInputs = [...inputs, ...useCaptures ? [capturesDir] : [], ...usePages ? [PAGES_DIR] : []];
56013
56171
  if (useCaptures)
@@ -56048,10 +56206,10 @@ async function cmdAudit(p) {
56048
56206
  if (typeof p.flags.out === "string") {
56049
56207
  const out2 = p.flags.out;
56050
56208
  const asFile = out2.toLowerCase().endsWith(".json");
56051
- const target = asFile ? out2 : join39(out2, "audit-latest.json");
56209
+ const target = asFile ? out2 : join40(out2, "audit-latest.json");
56052
56210
  try {
56053
- mkdirSync11(asFile ? dirname11(out2) : out2, { recursive: true });
56054
- writeFileSync14(target, JSON.stringify(result, null, 2) + "\n");
56211
+ mkdirSync12(asFile ? dirname11(out2) : out2, { recursive: true });
56212
+ writeFileSync15(target, JSON.stringify(result, null, 2) + "\n");
56055
56213
  console.error(lang === "fr" ? `\u2192 audit \xE9crit dans ${target}` : `\u2192 audit written to ${target}`);
56056
56214
  } catch {
56057
56215
  }
@@ -56070,7 +56228,7 @@ async function cmdAudit(p) {
56070
56228
  const baselineFlag = p.flags.baseline;
56071
56229
  if (typeof baselineFlag === "string" && baselineFlag) {
56072
56230
  let baseline = null;
56073
- if (existsSync23(baselineFlag)) {
56231
+ if (existsSync24(baselineFlag)) {
56074
56232
  try {
56075
56233
  const parsed = JSON.parse(readText(baselineFlag));
56076
56234
  if (isCurrentAudit(parsed)) baseline = parsed;
@@ -56124,11 +56282,11 @@ async function cmdDev(p) {
56124
56282
  return 2;
56125
56283
  }
56126
56284
  if (p.flags.next === true) {
56127
- const dir = join39(root, ".ultra11y", "next");
56285
+ const dir = join40(root, ".ultra11y", "next");
56128
56286
  const rel2 = ".ultra11y/next/overlay.jsx";
56129
56287
  try {
56130
- mkdirSync11(dir, { recursive: true });
56131
- writeFileSync14(join39(dir, "overlay.jsx"), nextOverlayComponent(port));
56288
+ mkdirSync12(dir, { recursive: true });
56289
+ writeFileSync15(join40(dir, "overlay.jsx"), nextOverlayComponent(port));
56132
56290
  } catch (e) {
56133
56291
  console.error(`ultra11y dev: could not write ${rel2}: ${e instanceof Error ? e.message : String(e)}`);
56134
56292
  return 1;
@@ -56216,7 +56374,7 @@ async function cmdSnapshot(p) {
56216
56374
  2
56217
56375
  )
56218
56376
  );
56219
- else if (!snaps.length) console.log(lang === "fr" ? `Aucun instantan\xE9 dans ${join39(root, PAGES_DIR)}.` : `No snapshot in ${join39(root, PAGES_DIR)}.`);
56377
+ else if (!snaps.length) console.log(lang === "fr" ? `Aucun instantan\xE9 dans ${join40(root, PAGES_DIR)}.` : `No snapshot in ${join40(root, PAGES_DIR)}.`);
56220
56378
  else for (const s of snaps) console.log(`${s.meta.id} ${s.meta.name} ${s.meta.url}${s.meta.auth ? " [auth]" : ""}`);
56221
56379
  return 0;
56222
56380
  }
@@ -56261,12 +56419,12 @@ async function cmdSnapshot(p) {
56261
56419
  }
56262
56420
  if (typeof payload.screenshot === "string" && payload.screenshot) {
56263
56421
  try {
56264
- writeFileSync14(join39(dir, "screen.png"), Buffer.from(payload.screenshot, "base64"));
56422
+ writeFileSync15(join40(dir, "screen.png"), Buffer.from(payload.screenshot, "base64"));
56265
56423
  } catch {
56266
56424
  console.error("ultra11y snapshot write: the screenshot could not be written \u2014 the pixel tier will be skipped for this page.");
56267
56425
  }
56268
56426
  }
56269
- const result = runAudit({ inputs: [join39(dir, "dom.html")], onWarn: (m) => console.error(m) });
56427
+ const result = runAudit({ inputs: [join40(dir, "dom.html")], onWarn: (m) => console.error(m) });
56270
56428
  const failOnRaw = p.flags["fail-on"];
56271
56429
  const failOnParsed = parseFailOn(failOnRaw);
56272
56430
  if (failOnRaw !== void 0 && failOnParsed === null) {
@@ -56303,9 +56461,9 @@ function cmdInit(p) {
56303
56461
  if (want.baseline) {
56304
56462
  const inputs = p.positionals.length ? p.positionals : ["."];
56305
56463
  const result = runAudit({ inputs, onWarn: (m) => console.error(m) });
56306
- mkdirSync11(join39(root, "audits"), { recursive: true });
56307
- const bp = join39(root, "audits", "baseline.json");
56308
- writeFileSync14(bp, JSON.stringify(result, null, 2) + "\n");
56464
+ mkdirSync12(join40(root, "audits"), { recursive: true });
56465
+ const bp = join40(root, "audits", "baseline.json");
56466
+ writeFileSync15(bp, JSON.stringify(result, null, 2) + "\n");
56309
56467
  wrote.push(bp);
56310
56468
  }
56311
56469
  if (want.hook) wrote.push(writeHook(root, engineRel, failOn, legacy ? "baseline" : "staged"));
@@ -56315,6 +56473,18 @@ function cmdInit(p) {
56315
56473
  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
56474
  return 0;
56317
56475
  }
56476
+ async function cmdHook(p) {
56477
+ if (p.flags["claude-code"] !== true) {
56478
+ console.error("ultra11y hook: expected `hook --claude-code` (the payload is read on stdin).");
56479
+ return 2;
56480
+ }
56481
+ try {
56482
+ const decision = decide(JSON.parse(await readStdin()));
56483
+ if (decision) process.stdout.write(JSON.stringify(decision));
56484
+ } catch {
56485
+ }
56486
+ return 0;
56487
+ }
56318
56488
  function cmdCriteria(p) {
56319
56489
  if (p.flags.generate === true) {
56320
56490
  process.stdout.write(renderCriteriaReference());
@@ -56442,8 +56612,8 @@ async function cmdPrd(p) {
56442
56612
  return gh2 && gh2.failed > 0 ? 1 : 0;
56443
56613
  }
56444
56614
  function depsAt(root) {
56445
- const pkgPath = join39(root, "package.json");
56446
- if (!existsSync23(pkgPath)) return {};
56615
+ const pkgPath = join40(root, "package.json");
56616
+ if (!existsSync24(pkgPath)) return {};
56447
56617
  try {
56448
56618
  const pkg = JSON.parse(readText(pkgPath));
56449
56619
  return { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
@@ -56469,24 +56639,24 @@ function cmdRender(p) {
56469
56639
  console.error(`ultra11y render: --runner must be playwright|cypress|auto (got "${forced}").`);
56470
56640
  return 2;
56471
56641
  }
56472
- const detected = detectE2eRunner(depsAt(root), (f) => existsSync23(join39(root, f)));
56642
+ const detected = detectE2eRunner(depsAt(root), (f) => existsSync24(join40(root, f)));
56473
56643
  const runners = forced && forced !== "auto" ? [forced] : detected;
56474
56644
  if (!runners.length) {
56475
56645
  console.error(e2eSetupPlan([], {}, lang));
56476
56646
  return 1;
56477
56647
  }
56478
56648
  const engineRef = engineRefFor(root);
56479
- const dir = join39(root, ".ultra11y", "e2e");
56649
+ const dir = join40(root, ".ultra11y", "e2e");
56480
56650
  const paths = {};
56481
56651
  try {
56482
- mkdirSync11(dir, { recursive: true });
56652
+ mkdirSync12(dir, { recursive: true });
56483
56653
  if (runners.includes("playwright")) {
56484
- writeFileSync14(join39(dir, "playwright.mjs"), playwrightFixture(engineRef));
56654
+ writeFileSync15(join40(dir, "playwright.mjs"), playwrightFixture(engineRef));
56485
56655
  paths.playwright = ".ultra11y/e2e/playwright.mjs";
56486
56656
  }
56487
56657
  if (runners.includes("cypress")) {
56488
- writeFileSync14(join39(dir, "cypress-plugin.mjs"), cypressPlugin(engineRef));
56489
- writeFileSync14(join39(dir, "cypress-commands.mjs"), cypressCommands());
56658
+ writeFileSync15(join40(dir, "cypress-plugin.mjs"), cypressPlugin(engineRef));
56659
+ writeFileSync15(join40(dir, "cypress-commands.mjs"), cypressCommands());
56490
56660
  paths.cypressPlugin = ".ultra11y/e2e/cypress-plugin.mjs";
56491
56661
  paths.cypressCommands = ".ultra11y/e2e/cypress-commands.mjs";
56492
56662
  }
@@ -56501,7 +56671,7 @@ function cmdRender(p) {
56501
56671
  if (p.flags.scaffold === true) {
56502
56672
  const out2 = typeof p.flags.out === "string" && p.flags.out ? p.flags.out : "ultra11y-render.tsx";
56503
56673
  try {
56504
- writeFileSync14(out2, ssrHarness());
56674
+ writeFileSync15(out2, ssrHarness());
56505
56675
  } catch (e) {
56506
56676
  console.error(`ultra11y render: could not write ${out2}: ${e instanceof Error ? e.message : String(e)}`);
56507
56677
  return 1;
@@ -56515,29 +56685,29 @@ Fill in COMPONENTS, run it (e.g. npx tsx ${out2}), then: node scripts/ultra11y.m
56515
56685
  }
56516
56686
  if (p.flags.setup === true) {
56517
56687
  const rel2 = ".ultra11y/capture-setup.mjs";
56518
- const out2 = join39(root, rel2);
56688
+ const out2 = join40(root, rel2);
56519
56689
  try {
56520
- mkdirSync11(dirname11(out2), { recursive: true });
56521
- writeFileSync14(out2, captureSetup());
56690
+ mkdirSync12(dirname11(out2), { recursive: true });
56691
+ writeFileSync15(out2, captureSetup());
56522
56692
  } catch (e) {
56523
56693
  console.error(`ultra11y render: could not write ${out2}: ${e instanceof Error ? e.message : String(e)}`);
56524
56694
  return 1;
56525
56695
  }
56526
56696
  let setupDeps = {};
56527
- const setupPkg = join39(root, "package.json");
56528
- if (existsSync23(setupPkg)) {
56697
+ const setupPkg = join40(root, "package.json");
56698
+ if (existsSync24(setupPkg)) {
56529
56699
  try {
56530
56700
  const pkg = JSON.parse(readText(setupPkg));
56531
56701
  setupDeps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
56532
56702
  } catch {
56533
56703
  }
56534
56704
  }
56535
- const tr = detectTestRunner(setupDeps, (f) => existsSync23(join39(root, f)));
56705
+ const tr = detectTestRunner(setupDeps, (f) => existsSync24(join40(root, f)));
56536
56706
  console.log(captureSetupPlan(tr, rel2, lang));
56537
56707
  const gaLine = ".ultra11y/captures/*.html text eol=lf linguist-generated=true";
56538
- const gaPath = join39(root, ".gitattributes");
56708
+ const gaPath = join40(root, ".gitattributes");
56539
56709
  try {
56540
- const existing = existsSync23(gaPath) ? readFileSync20(gaPath, "utf8") : "";
56710
+ const existing = existsSync24(gaPath) ? readFileSync20(gaPath, "utf8") : "";
56541
56711
  if (!existing.includes(".ultra11y/captures/")) {
56542
56712
  appendFileSync(gaPath, (existing && !existing.endsWith("\n") ? "\n" : "") + gaLine + "\n");
56543
56713
  console.log(lang === "fr" ? `.gitattributes : ajout\xE9 \xAB ${gaLine} \xBB` : `.gitattributes: added "${gaLine}"`);
@@ -56545,8 +56715,8 @@ Fill in COMPONENTS, run it (e.g. npx tsx ${out2}), then: node scripts/ultra11y.m
56545
56715
  } catch {
56546
56716
  }
56547
56717
  try {
56548
- const giPath = join39(root, ".gitignore");
56549
- if (existsSync23(giPath) && /^\s*\/?\.ultra11y(\/\**)?\/?\s*$/m.test(readFileSync20(giPath, "utf8")))
56718
+ const giPath = join40(root, ".gitignore");
56719
+ if (existsSync24(giPath) && /^\s*\/?\.ultra11y(\/\**)?\/?\s*$/m.test(readFileSync20(giPath, "utf8")))
56550
56720
  console.error(
56551
56721
  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
56722
  );
@@ -56556,8 +56726,8 @@ Fill in COMPONENTS, run it (e.g. npx tsx ${out2}), then: node scripts/ultra11y.m
56556
56726
  }
56557
56727
  if (p.flags.storybook === true || typeof p.flags.storybook === "string") {
56558
56728
  const sbDir = p.positionals[0] ?? "storybook-static";
56559
- const indexPath = existsSync23(join39(sbDir, "index.json")) ? join39(sbDir, "index.json") : join39(sbDir, "stories.json");
56560
- if (!existsSync23(indexPath)) {
56729
+ const indexPath = existsSync24(join40(sbDir, "index.json")) ? join40(sbDir, "index.json") : join40(sbDir, "stories.json");
56730
+ if (!existsSync24(indexPath)) {
56561
56731
  console.error(
56562
56732
  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
56733
  );
@@ -56567,7 +56737,7 @@ Fill in COMPONENTS, run it (e.g. npx tsx ${out2}), then: node scripts/ultra11y.m
56567
56737
  const provById = new Map(stories.map((s) => [s.id, storyProvenance(s)]));
56568
56738
  const capturesFlag = typeof p.flags.captures === "string" && p.flags.captures ? p.flags.captures : void 0;
56569
56739
  const htmlDir = capturesFlag ?? sbDir;
56570
- const htmlFiles = existsSync23(htmlDir) ? discover([htmlDir]).files.filter((f) => /\.html?$/i.test(f)) : [];
56740
+ const htmlFiles = existsSync24(htmlDir) ? discover([htmlDir]).files.filter((f) => /\.html?$/i.test(f)) : [];
56571
56741
  const outDir = ".ultra11y/captures";
56572
56742
  let attributed = 0;
56573
56743
  let skipped = 0;
@@ -56589,8 +56759,8 @@ Fill in COMPONENTS, run it (e.g. npx tsx ${out2}), then: node scripts/ultra11y.m
56589
56759
  continue;
56590
56760
  }
56591
56761
  try {
56592
- mkdirSync11(outDir, { recursive: true });
56593
- writeFileSync14(join39(outDir, `${hitId}.html`), `${formatCaptureComment(prov)}
56762
+ mkdirSync12(outDir, { recursive: true });
56763
+ writeFileSync15(join40(outDir, `${hitId}.html`), `${formatCaptureComment(prov)}
56594
56764
  ${raw}${raw.endsWith("\n") ? "" : "\n"}`);
56595
56765
  attributed++;
56596
56766
  } catch {
@@ -56612,11 +56782,11 @@ ${raw}${raw.endsWith("\n") ? "" : "\n"}`);
56612
56782
  }
56613
56783
  if (p.flags.coverage === true) {
56614
56784
  const capturesFlag = typeof p.flags.captures === "string" && p.flags.captures ? p.flags.captures : void 0;
56615
- const capturesDir = capturesFlag ?? join39(root, ".ultra11y/captures");
56785
+ const capturesDir = capturesFlag ?? join40(root, ".ultra11y/captures");
56616
56786
  const graphExt = [...GRAPH_ONLY_EXT, ...asList(p.flags.ext) ?? []];
56617
56787
  const sourceFiles = discover([root], { include: asList(p.flags.include), exclude: asList(p.flags.exclude), ext: graphExt }).files;
56618
56788
  const graph = buildGraphStreaming(sourceFiles);
56619
- const capFiles = existsSync23(capturesDir) ? discover([capturesDir]).files : [];
56789
+ const capFiles = existsSync24(capturesDir) ? discover([capturesDir]).files : [];
56620
56790
  const entries = capFiles.map((f) => ({ file: toPosix(f), provenance: parseCaptureProvenance(readText(f)) }));
56621
56791
  const cov = computeCaptureCoverage(graph, entries);
56622
56792
  if (p.flags.json) console.log(JSON.stringify(cov, null, 2));
@@ -56624,15 +56794,15 @@ ${raw}${raw.endsWith("\n") ? "" : "\n"}`);
56624
56794
  return 0;
56625
56795
  }
56626
56796
  let deps = {};
56627
- const pkgPath = join39(root, "package.json");
56628
- if (existsSync23(pkgPath)) {
56797
+ const pkgPath = join40(root, "package.json");
56798
+ if (existsSync24(pkgPath)) {
56629
56799
  try {
56630
56800
  const pkg = JSON.parse(readText(pkgPath));
56631
56801
  deps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
56632
56802
  } catch {
56633
56803
  }
56634
56804
  }
56635
- const detection = detectFrameworks(deps, (f) => existsSync23(join39(root, f)));
56805
+ const detection = detectFrameworks(deps, (f) => existsSync24(join40(root, f)));
56636
56806
  if (p.flags.json) console.log(JSON.stringify(detection, null, 2));
56637
56807
  else console.log(renderPlan(detection, lang));
56638
56808
  return 0;
@@ -56831,9 +57001,9 @@ function applyAdjudicationFile(p, adj, lang) {
56831
57001
  return 1;
56832
57002
  }
56833
57003
  const out2 = typeof p.flags.out === "string" ? p.flags.out : ".";
56834
- mkdirSync11(out2, { recursive: true });
56835
- const auditPath = join39(out2, "audit-latest.json");
56836
- writeFileSync14(auditPath, JSON.stringify(r.audit, null, 2) + "\n");
57004
+ mkdirSync12(out2, { recursive: true });
57005
+ const auditPath = join40(out2, "audit-latest.json");
57006
+ writeFileSync15(auditPath, JSON.stringify(r.audit, null, 2) + "\n");
56837
57007
  if (p.flags.json) console.log(JSON.stringify({ ok: true, auditPath, applied: r.applied, stillManual: r.stillManual, grounding: r.grounding }, null, 2));
56838
57008
  else
56839
57009
  console.log(
@@ -56903,7 +57073,7 @@ async function cmdScan(p) {
56903
57073
  return 0;
56904
57074
  }
56905
57075
  for (const target of p.positionals.filter((a) => a !== "-")) {
56906
- if (/^https?:\/\//i.test(target) || existsSync23(target)) continue;
57076
+ if (/^https?:\/\//i.test(target) || existsSync24(target)) continue;
56907
57077
  console.error(
56908
57078
  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
57079
  );
@@ -57028,12 +57198,12 @@ async function cmdScan(p) {
57028
57198
  audit2 = parsed;
57029
57199
  }
57030
57200
  const merged = mergeDynamic(audit2, dynamic, lang);
57031
- mkdirSync11(out2, { recursive: true });
57032
- writeFileSync14(join39(out2, "audit-latest.json"), JSON.stringify(merged, null, 2) + "\n");
57201
+ mkdirSync12(out2, { recursive: true });
57202
+ writeFileSync15(join40(out2, "audit-latest.json"), JSON.stringify(merged, null, 2) + "\n");
57033
57203
  if (p.flags.json) console.log(JSON.stringify(merged, null, 2));
57034
57204
  else
57035
57205
  console.log(
57036
- lang === "fr" ? `Audit statique + dynamique fusionn\xE9 \u2192 ${join39(out2, "audit-latest.json")} (${merged.conformancePct}% r\xE9ussite, ${merged.findings.length} findings).` : `Static + dynamic audit merged \u2192 ${join39(out2, "audit-latest.json")} (${merged.conformancePct}% pass rate, ${merged.findings.length} findings).`
57206
+ 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
57207
  );
57038
57208
  return 0;
57039
57209
  }
@@ -57213,7 +57383,7 @@ function cmdOrchestrate(p) {
57213
57383
  }
57214
57384
  const engineAbs = realpathSync4(fileURLToPath5(import.meta.url));
57215
57385
  if (p.flags.list === true) {
57216
- if (!existsSync23(runFlag)) {
57386
+ if (!existsSync24(runFlag)) {
57217
57387
  console.error(`ultra11y orchestrate: run dir not found: ${runFlag}.`);
57218
57388
  return 2;
57219
57389
  }
@@ -57240,7 +57410,7 @@ function cmdOrchestrate(p) {
57240
57410
  );
57241
57411
  } else {
57242
57412
  console.log(
57243
- lang === "fr" ? `Suivez ${join39(runFlag, "orchestration", "RUNBOOK.md")} s\xE9quentiellement (chemin \xE9co).` : `Follow ${join39(runFlag, "orchestration", "RUNBOOK.md")} sequentially (the eco path).`
57413
+ lang === "fr" ? `Suivez ${join40(runFlag, "orchestration", "RUNBOOK.md")} s\xE9quentiellement (chemin \xE9co).` : `Follow ${join40(runFlag, "orchestration", "RUNBOOK.md")} sequentially (the eco path).`
57244
57414
  );
57245
57415
  }
57246
57416
  if (p.flags.phase === void 0 && workflows.length === 0 && p.flags.eco !== true) {
@@ -57324,6 +57494,8 @@ async function main(argv) {
57324
57494
  return cmdOrchestrate(p);
57325
57495
  case "mcp":
57326
57496
  return cmdMcp(p);
57497
+ case "hook":
57498
+ return cmdHook(p);
57327
57499
  default:
57328
57500
  console.error(`ultra11y: "${p.command}" is not implemented yet`);
57329
57501
  return 1;