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.
- package/.claude-plugin/marketplace.json +22 -0
- package/.claude-plugin/plugin.json +9 -0
- package/README.md +45 -0
- package/hooks/hooks.json +16 -0
- package/hooks/pre-tool-use.mjs +47 -0
- package/package.json +4 -2
- package/scripts/sync-version.mjs +11 -1
- package/scripts/ultra11y.mjs +821 -649
- package/scripts/verify-skill-bundle.mjs +104 -7
- package/skills/review-a11y/SKILL.md +9 -3
- package/skills/review-a11y/scripts/ultra11y.mjs +821 -649
- package/skills/ultra11y/SKILL.md +5 -2
- package/skills/ultra11y/references/automation.md +37 -0
- package/skills/ultra11y/scripts/ultra11y.mjs +821 -649
- package/src/cli.ts +31 -0
- package/src/config.ts +4 -0
- package/src/hook.ts +0 -0
- package/src/types.ts +1 -1
package/scripts/ultra11y.mjs
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
|
-
import { realpathSync as realpathSync4, writeFileSync as
|
|
5
|
-
import { join as
|
|
4
|
+
import { realpathSync as realpathSync4, writeFileSync as writeFileSync15, mkdirSync as mkdirSync12, existsSync as existsSync24, readFileSync as readFileSync20, appendFileSync } from "fs";
|
|
5
|
+
import { join as join40, relative as relative3, sep as sep6, dirname as dirname11 } from "path";
|
|
6
6
|
import { fileURLToPath as fileURLToPath5, pathToFileURL as pathToFileURL3 } from "url";
|
|
7
7
|
|
|
8
8
|
// src/types.ts
|
|
9
|
-
var VERSION = "2.
|
|
9
|
+
var VERSION = "2.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/
|
|
44898
|
-
import {
|
|
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/
|
|
44902
|
-
|
|
44903
|
-
|
|
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/
|
|
44950
|
-
function
|
|
44951
|
-
|
|
44952
|
-
|
|
44953
|
-
|
|
44954
|
-
|
|
44955
|
-
}
|
|
44956
|
-
|
|
44957
|
-
|
|
44958
|
-
|
|
44959
|
-
|
|
44960
|
-
|
|
44961
|
-
}
|
|
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
|
|
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
|
|
44999
|
-
const
|
|
45000
|
-
const
|
|
45001
|
-
|
|
45002
|
-
|
|
45003
|
-
|
|
45004
|
-
|
|
45005
|
-
|
|
45006
|
-
|
|
45007
|
-
|
|
45008
|
-
|
|
45009
|
-
|
|
45010
|
-
|
|
45011
|
-
|
|
45012
|
-
|
|
45013
|
-
|
|
45014
|
-
if (!pc.appliesTo) {
|
|
45015
|
-
const status2 = scResults.length ? aggregate2(scResults.map((r) => r.status)) : "NA";
|
|
45016
|
-
return { id: pc.id, theme: pc.theme, status: status2, findings: allFindings, scs: pc.wcag };
|
|
45017
|
-
}
|
|
45018
|
-
const findings = allFindings.filter((f) => ruleMatches(f.ruleId, pc.appliesTo.ruleIds));
|
|
45019
|
-
const normativeFindings = findings.filter((f) => !f.advisory);
|
|
45020
|
-
if (normativeFindings.length) {
|
|
45021
|
-
return { id: pc.id, theme: pc.theme, status: "NC", findings, scs: pc.wcag };
|
|
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 (
|
|
45024
|
-
|
|
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
|
-
|
|
45027
|
-
|
|
45028
|
-
|
|
45029
|
-
|
|
45030
|
-
|
|
45031
|
-
|
|
45032
|
-
|
|
45033
|
-
const decided = adjudicated?.get(pc.id);
|
|
45034
|
-
if (decided) {
|
|
45035
|
-
return {
|
|
45036
|
-
id: pc.id,
|
|
45037
|
-
theme: pc.theme,
|
|
45038
|
-
status: decided.status,
|
|
45039
|
-
findings: decided.findings,
|
|
45040
|
-
scs: pc.wcag,
|
|
45041
|
-
...decided.justification ? { justification: decided.justification } : {},
|
|
45042
|
-
decidedBy: "agent"
|
|
45043
|
-
};
|
|
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
|
|
45050
|
-
|
|
45051
|
-
const
|
|
45052
|
-
return
|
|
45053
|
-
|
|
45054
|
-
|
|
45055
|
-
|
|
45056
|
-
|
|
45057
|
-
|
|
45058
|
-
criterion: { en: "Criterion", fr: "Crit\xE8re" },
|
|
45059
|
-
test: { en: "Test", fr: "Test" },
|
|
45060
|
-
conformant: { en: "Conformant", fr: "Conforme" },
|
|
45061
|
-
nonConformant: { en: "Non-conformant", fr: "Non conforme" },
|
|
45062
|
-
notApplicable: { en: "Not applicable", fr: "Non applicable" },
|
|
45063
|
-
auditorHeading: { en: "Accessibility criterion", fr: "Crit\xE8re d'accessibilit\xE9" }
|
|
45064
|
-
};
|
|
45065
|
-
var CORE = {
|
|
45066
|
-
theme: { en: "Principle \xB7 Guideline", fr: "Principe \xB7 R\xE8gle" },
|
|
45067
|
-
criterion: { en: "Success criterion", fr: "Crit\xE8re de succ\xE8s" },
|
|
45068
|
-
test: { en: "Technique", fr: "Technique" },
|
|
45069
|
-
conformant: { en: "Pass", fr: "Conforme" },
|
|
45070
|
-
nonConformant: { en: "Fail", fr: "Non conforme" },
|
|
45071
|
-
notApplicable: { en: "Not applicable", fr: "Non applicable" },
|
|
45072
|
-
auditorHeading: { en: "Accessibility criterion", fr: "Crit\xE8re d'accessibilit\xE9" }
|
|
45073
|
-
};
|
|
45074
|
-
function pick(s, lang, fallbackLang) {
|
|
45075
|
-
if (!s) return void 0;
|
|
45076
|
-
return s[lang] ?? s[fallbackLang] ?? s.en ?? Object.values(s)[0];
|
|
45077
|
-
}
|
|
45078
|
-
function vocabularyFor(standard, lang) {
|
|
45079
|
-
const base = isCore(standard) ? CORE : DEFAULT;
|
|
45080
|
-
const pack = isCore(standard) ? void 0 : getPack(standard);
|
|
45081
|
-
const voc = pack?.vocabulary;
|
|
45082
|
-
const packDefault = pack?.defaultLocale ?? "en";
|
|
45083
|
-
const term = (k) => pick(voc?.[k], lang, packDefault) ?? base[k][lang] ?? base[k].en;
|
|
45084
|
-
return {
|
|
45085
|
-
theme: term("theme"),
|
|
45086
|
-
criterion: term("criterion"),
|
|
45087
|
-
test: term("test"),
|
|
45088
|
-
conformant: term("conformant"),
|
|
45089
|
-
nonConformant: term("nonConformant"),
|
|
45090
|
-
notApplicable: term("notApplicable"),
|
|
45091
|
-
auditorHeading: term("auditorHeading"),
|
|
45092
|
-
normativeNote: pick(voc?.normativeNote, lang, packDefault)
|
|
45093
|
-
};
|
|
45094
|
-
}
|
|
45095
|
-
|
|
45096
|
-
// src/standards/index.ts
|
|
45097
|
-
var CORE2 = CORE_KEY;
|
|
45098
|
-
function resolveStandard(flag) {
|
|
45099
|
-
if (flag === void 0 || flag === true || flag === "") return CORE_KEY;
|
|
45100
|
-
const key = String(flag).toLowerCase();
|
|
45101
|
-
if (!hasStandard(key)) {
|
|
45102
|
-
throw new Error(`unknown standard "${key}" (known: ${listStandards().join(", ")})`);
|
|
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
|
-
|
|
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
|
|
45107
|
-
|
|
45108
|
-
|
|
45109
|
-
|
|
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
|
-
|
|
48617
|
+
mkdirSync6(opts.out, { recursive: true });
|
|
48153
48618
|
if (opts.format === "doc") {
|
|
48154
|
-
const p2 =
|
|
48155
|
-
|
|
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 =
|
|
48165
|
-
|
|
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 =
|
|
48171
|
-
|
|
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
|
-
|
|
48623
|
-
const path =
|
|
48624
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
49224
|
-
import { dirname as dirname9, join as
|
|
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
|
|
49228
|
-
import { join as
|
|
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
|
-
|
|
49403
|
-
const todoPath =
|
|
49404
|
-
const mdPath =
|
|
49405
|
-
|
|
49406
|
-
|
|
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
|
|
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 (!
|
|
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 ??
|
|
49581
|
-
if (!
|
|
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
|
|
49617
|
-
import { join as
|
|
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 ?
|
|
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
|
-
|
|
50898
|
-
const todoPath =
|
|
50899
|
-
const mdPath =
|
|
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
|
-
|
|
50909
|
-
|
|
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
|
|
50915
|
-
import { mkdtempSync as mkdtempSync2, writeFileSync as
|
|
50916
|
-
import { tmpdir } from "os";
|
|
50917
|
-
import { join as
|
|
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
|
|
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() !== "" && !
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
51785
|
+
const ctx = mkdtempSync2(join33(tmpdir2(), CTX_PREFIX));
|
|
51321
51786
|
try {
|
|
51322
|
-
|
|
51323
|
-
|
|
51324
|
-
|
|
51325
|
-
|
|
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 =
|
|
51797
|
+
const dir = tmpdir2();
|
|
51333
51798
|
for (const name2 of readdirSync5(dir)) {
|
|
51334
51799
|
if (!name2.startsWith(CTX_PREFIX)) continue;
|
|
51335
|
-
rmSync3(
|
|
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
|
-
|
|
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 =
|
|
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 && !
|
|
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 &&
|
|
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 || !
|
|
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
|
|
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 && !
|
|
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 &&
|
|
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
|
|
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
|
-
|
|
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
|
|
52606
|
-
import { execFileSync as
|
|
52607
|
-
import { join as
|
|
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
|
|
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 =
|
|
52704
|
-
|
|
52705
|
-
const path =
|
|
52706
|
-
|
|
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 =
|
|
52712
|
-
|
|
52713
|
-
const path =
|
|
52714
|
-
|
|
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
|
|
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: [
|
|
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: [
|
|
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
|
|
53888
|
-
import { join as
|
|
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
|
|
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 ${
|
|
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 =
|
|
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: \`${
|
|
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: \`${
|
|
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 \`${
|
|
54084
|
-
2. **Adjudicate the residual criteria** \u2014 \`${engine} verify --manual --in ${
|
|
54085
|
-
3. **Report**: \`${engine} report --in ${
|
|
54086
|
-
4. **Verify the report's claims** \u2014 \`${engine} verify --report <the report .md> --out ${runAbs}\` writes \`${
|
|
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: "${
|
|
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 =
|
|
54249
|
+
const adjPath = join37(run2, "ADJUDICATE.todo.json");
|
|
54101
54250
|
let adjIds = [];
|
|
54102
54251
|
let adjReady = false;
|
|
54103
|
-
if (
|
|
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 =
|
|
54262
|
+
const verPath = join37(run2, "VERIFY.todo.json");
|
|
54114
54263
|
let verIds = [];
|
|
54115
54264
|
let verReady = false;
|
|
54116
|
-
if (
|
|
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 ${
|
|
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 (!
|
|
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 =
|
|
54175
|
-
const agentsDir =
|
|
54176
|
-
|
|
54177
|
-
|
|
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 =
|
|
54182
|
-
|
|
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 =
|
|
54189
|
-
if (
|
|
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 =
|
|
54204
|
-
|
|
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 =
|
|
54209
|
-
|
|
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
|
|
54219
|
-
import { isAbsolute as isAbsolute3, join as
|
|
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 (!
|
|
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 =
|
|
54413
|
-
if (!
|
|
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(
|
|
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 (!
|
|
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 :
|
|
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
|
|
55002
|
-
import { basename as basename3, dirname as dirname10, join as
|
|
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) =>
|
|
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 =
|
|
55016
|
-
if (!
|
|
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,
|
|
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(
|
|
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 ||
|
|
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("-") &&
|
|
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 :
|
|
56209
|
+
const target = asFile ? out2 : join40(out2, "audit-latest.json");
|
|
56052
56210
|
try {
|
|
56053
|
-
|
|
56054
|
-
|
|
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 (
|
|
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 =
|
|
56285
|
+
const dir = join40(root, ".ultra11y", "next");
|
|
56128
56286
|
const rel2 = ".ultra11y/next/overlay.jsx";
|
|
56129
56287
|
try {
|
|
56130
|
-
|
|
56131
|
-
|
|
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 ${
|
|
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
|
-
|
|
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: [
|
|
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
|
-
|
|
56307
|
-
const bp =
|
|
56308
|
-
|
|
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 =
|
|
56446
|
-
if (!
|
|
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) =>
|
|
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 =
|
|
56649
|
+
const dir = join40(root, ".ultra11y", "e2e");
|
|
56480
56650
|
const paths = {};
|
|
56481
56651
|
try {
|
|
56482
|
-
|
|
56652
|
+
mkdirSync12(dir, { recursive: true });
|
|
56483
56653
|
if (runners.includes("playwright")) {
|
|
56484
|
-
|
|
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
|
-
|
|
56489
|
-
|
|
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
|
-
|
|
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 =
|
|
56688
|
+
const out2 = join40(root, rel2);
|
|
56519
56689
|
try {
|
|
56520
|
-
|
|
56521
|
-
|
|
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 =
|
|
56528
|
-
if (
|
|
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) =>
|
|
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 =
|
|
56708
|
+
const gaPath = join40(root, ".gitattributes");
|
|
56539
56709
|
try {
|
|
56540
|
-
const existing =
|
|
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 =
|
|
56549
|
-
if (
|
|
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 =
|
|
56560
|
-
if (!
|
|
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 =
|
|
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
|
-
|
|
56593
|
-
|
|
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 ??
|
|
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 =
|
|
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 =
|
|
56628
|
-
if (
|
|
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) =>
|
|
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
|
-
|
|
56835
|
-
const auditPath =
|
|
56836
|
-
|
|
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) ||
|
|
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
|
-
|
|
57032
|
-
|
|
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 ${
|
|
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 (!
|
|
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 ${
|
|
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;
|