ultra11y 2.26.0 → 2.28.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/.agents/plugins/marketplace.json +12 -0
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +21 -0
- package/.opencode/plugins/ultra11y.js +102 -0
- package/README.md +34 -16
- package/hooks/hooks.json +1 -1
- package/hooks/pre-tool-use.mjs +19 -8
- package/package.json +6 -2
- package/scripts/sync-version.mjs +12 -4
- package/scripts/ultra11y.mjs +831 -150
- package/scripts/vendor/wcag-2.2-fr.json +1 -1
- package/scripts/vendor/wcag-2.2-sc-universe.json +1 -1
- package/scripts/verify-skill-bundle.mjs +134 -1
- package/skills/review-a11y/SKILL.md +5 -1
- package/skills/review-a11y/scripts/ultra11y.mjs +831 -150
- package/skills/ultra11y/SKILL.md +2 -1
- package/skills/ultra11y/references/automation.md +9 -4
- package/skills/ultra11y/references/harnesses.md +117 -0
- package/skills/ultra11y/scripts/ultra11y.mjs +831 -150
- package/src/cli.ts +116 -17
- package/src/data/wcag-universe.json +1 -1
- package/src/hook.ts +0 -0
- package/src/init.ts +26 -2
- package/src/install/agents-md.ts +78 -0
- package/src/install/claude-code.ts +61 -0
- package/src/install/codex.ts +166 -0
- package/src/install/index.ts +204 -0
- package/src/install/json-edit.ts +97 -0
- package/src/install/opencode.ts +90 -0
- package/src/install/paths.ts +132 -0
- package/src/install/text-edit.ts +54 -0
- package/src/types.ts +1 -1
- package/src/vendor/codeindex-engine.d.mts +3 -3
- package/src/vendor/codeindex-engine.mjs +23 -3
- package/src/vendor/engine.meta.json +5 -5
package/scripts/ultra11y.mjs
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
|
-
import { realpathSync as
|
|
5
|
-
import { join as
|
|
4
|
+
import { realpathSync as realpathSync6, writeFileSync as writeFileSync16, mkdirSync as mkdirSync14, existsSync as existsSync30, readFileSync as readFileSync26, appendFileSync } from "fs";
|
|
5
|
+
import { join as join45, relative as relative4, sep as sep7, dirname as dirname13 } 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.28.0";
|
|
10
10
|
var SCHEMA_VERSION = 2;
|
|
11
11
|
|
|
12
12
|
// src/audit.ts
|
|
@@ -1197,7 +1197,7 @@ var wcag_universe_default = {
|
|
|
1197
1197
|
wcagVersion: "2.2",
|
|
1198
1198
|
source: "https://www.w3.org/TR/WCAG22/",
|
|
1199
1199
|
criteriaSource: "https://github.com/w3c/wcag",
|
|
1200
|
-
provenance: "Full WCAG 2.x SC universe (all levels incl. AAA, and the removed 4.1.1 Parsing) fetched from raw.githubusercontent.com/w3c/wcag@main on 2026-
|
|
1200
|
+
provenance: "Full WCAG 2.x SC universe (all levels incl. AAA, and the removed 4.1.1 Parsing) fetched from raw.githubusercontent.com/w3c/wcag@main on 2026-08-01 via `node scripts/build-standards.mjs --refresh-universe`. Classification: core-AA = ships in src/data/wcag.json (the shipped WCAG 2.2 AA core); out-of-core = WCAG AAA; removed = obsolete (4.1.1).",
|
|
1201
1201
|
criteria: [
|
|
1202
1202
|
{
|
|
1203
1203
|
id: "1.1.1",
|
|
@@ -18887,9 +18887,9 @@ var EXTRACTOR_VERSION;
|
|
|
18887
18887
|
var init_types = __esm({
|
|
18888
18888
|
"src/types.ts"() {
|
|
18889
18889
|
"use strict";
|
|
18890
|
-
ENGINE_VERSION = "2.
|
|
18890
|
+
ENGINE_VERSION = "2.24.2";
|
|
18891
18891
|
SCHEMA_VERSION2 = 5;
|
|
18892
|
-
EXTRACTOR_VERSION =
|
|
18892
|
+
EXTRACTOR_VERSION = 12;
|
|
18893
18893
|
}
|
|
18894
18894
|
});
|
|
18895
18895
|
function sh(cmd, args2, opts = {}) {
|
|
@@ -19861,6 +19861,18 @@ var init_js_ts = __esm({
|
|
|
19861
19861
|
{ re: /^\s*module\.exports\.(?<name>[\w$]+)\s*=/, kind: "const", exported: true },
|
|
19862
19862
|
// top-level const arrow function (not exported)
|
|
19863
19863
|
{ re: /^\s*(?:const|let)\s+(?<name>[\w$]+)\s*=\s*(?:async\s*)?\([^)]*\)\s*(?::[^=]+)?=>/, kind: "const", exported: false },
|
|
19864
|
+
// A module constant bound to a VALUE, not exported: `const RE = /href=/g`.
|
|
19865
|
+
// The AST tier indexes these (a top-level binding is a declaration, only an
|
|
19866
|
+
// in-function one is a local), and a fallback tier that answers "no such
|
|
19867
|
+
// symbol" for every module constant diverges from the engine it stands in for.
|
|
19868
|
+
//
|
|
19869
|
+
// Anchored at column 0, unlike every rule above it: a line scanner cannot see
|
|
19870
|
+
// scope, so `^\s*` here would sweep in each `const x = 1` inside every function
|
|
19871
|
+
// body — the locals-flood the AST tier's inFunctionBody filter exists to
|
|
19872
|
+
// prevent, and the surplus this project criticises in a flat tags file. Column
|
|
19873
|
+
// 0 is the one module-scope proxy a line can carry. Must stay AFTER the arrow
|
|
19874
|
+
// and export rules: `scan()` keeps the first rule that matches a line.
|
|
19875
|
+
{ re: /^(?:const|let|var)\s+(?<name>[\w$]+)\s*[:=]/, kind: "const", exported: false },
|
|
19864
19876
|
// `export default Foo;` — a class/const declared above and exported by reference.
|
|
19865
19877
|
{ re: /^\s*export\s+default\s+(?<name>[A-Za-z_$][\w$]*)\s*;?\s*$/, kind: "default", exported: true }
|
|
19866
19878
|
];
|
|
@@ -24974,7 +24986,15 @@ var init_specs = __esm({
|
|
|
24974
24986
|
"arguments",
|
|
24975
24987
|
"arrow_function",
|
|
24976
24988
|
"function_expression",
|
|
24977
|
-
"function"
|
|
24989
|
+
"function",
|
|
24990
|
+
// The IIFE's own link: `(function () { … })()` parses as
|
|
24991
|
+
// expression_statement → call_expression → parenthesized_expression →
|
|
24992
|
+
// function_expression, and without the parens listed the descent stopped one
|
|
24993
|
+
// node short — the same break the statement containers above were added for.
|
|
24994
|
+
// Whole categories of file are written inside one (browser scripts, UMD and
|
|
24995
|
+
// loader bundles), so for those the index held no functions at all. Named by
|
|
24996
|
+
// the ctags differential on a real loader script.
|
|
24997
|
+
"parenthesized_expression"
|
|
24978
24998
|
]),
|
|
24979
24999
|
exported: neverExport,
|
|
24980
25000
|
// export is tracked structurally; see LangSpec.exportMarkers
|
|
@@ -33122,7 +33142,7 @@ function findWord(line, name2) {
|
|
|
33122
33142
|
}
|
|
33123
33143
|
}
|
|
33124
33144
|
function renderScip(scan2, opts = {}) {
|
|
33125
|
-
const
|
|
33145
|
+
const projectRoot2 = opts.projectRoot ?? "file://" + scan2.root.replace(/\\/g, "/");
|
|
33126
33146
|
const toolVersion = opts.toolVersion ?? ENGINE_VERSION;
|
|
33127
33147
|
const docs = scan2.files.filter((f) => f.kind === "code" && f.symbols.length > 0);
|
|
33128
33148
|
const docDefs = /* @__PURE__ */ new Map();
|
|
@@ -33209,7 +33229,7 @@ function renderScip(scan2, opts = {}) {
|
|
|
33209
33229
|
pushString(toolInfo, F_TOOL_VERSION, toolVersion);
|
|
33210
33230
|
const metadata2 = new Bytes();
|
|
33211
33231
|
pushMessage(metadata2, F_META_TOOL_INFO, toolInfo);
|
|
33212
|
-
pushString(metadata2, F_META_PROJECT_ROOT,
|
|
33232
|
+
pushString(metadata2, F_META_PROJECT_ROOT, projectRoot2);
|
|
33213
33233
|
pushVarintField(metadata2, F_META_TEXT_ENCODING, TEXT_ENCODING_UTF8);
|
|
33214
33234
|
let total = 0;
|
|
33215
33235
|
for (const d of documents) total += d.length;
|
|
@@ -47743,6 +47763,24 @@ function matchGitIntent(command) {
|
|
|
47743
47763
|
for (const [intent, re] of INTENTS) if (re.test(command)) return intent;
|
|
47744
47764
|
return null;
|
|
47745
47765
|
}
|
|
47766
|
+
var SHELL_TOOLS = /* @__PURE__ */ new Set(["bash", "shell", "exec", "exec_command", "local_shell", "localshell", "run_command"]);
|
|
47767
|
+
function isShellTool(name2) {
|
|
47768
|
+
return !!name2 && SHELL_TOOLS.has(name2.toLowerCase());
|
|
47769
|
+
}
|
|
47770
|
+
var SHELL_BIN = /(?:^|\/)(?:ba|z|k|da)?sh$/;
|
|
47771
|
+
var DASH_C = /^-[a-z]*c$/;
|
|
47772
|
+
function commandOf(toolInput) {
|
|
47773
|
+
const raw = toolInput?.command;
|
|
47774
|
+
if (typeof raw === "string") return raw || null;
|
|
47775
|
+
if (!Array.isArray(raw)) return null;
|
|
47776
|
+
const parts2 = raw.filter((p) => typeof p === "string");
|
|
47777
|
+
if (parts2.length === 0) return null;
|
|
47778
|
+
if (SHELL_BIN.test(parts2[0])) {
|
|
47779
|
+
const i2 = parts2.findIndex((p, n) => n > 0 && DASH_C.test(p));
|
|
47780
|
+
if (i2 !== -1 && parts2[i2 + 1]) return parts2[i2 + 1];
|
|
47781
|
+
}
|
|
47782
|
+
return parts2.join(" ") || null;
|
|
47783
|
+
}
|
|
47746
47784
|
function git2(cwd, args2) {
|
|
47747
47785
|
try {
|
|
47748
47786
|
return execFileSync2("git", args2, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
@@ -47829,8 +47867,8 @@ function decide(payload, deps = {}) {
|
|
|
47829
47867
|
const env = deps.env ?? process.env;
|
|
47830
47868
|
const audit2 = deps.audit ?? realAudit;
|
|
47831
47869
|
const seen = deps.seen ?? firstSighting;
|
|
47832
|
-
if (payload.tool_name
|
|
47833
|
-
const command = payload.tool_input
|
|
47870
|
+
if (!isShellTool(payload.tool_name)) return null;
|
|
47871
|
+
const command = commandOf(payload.tool_input);
|
|
47834
47872
|
if (!command) return null;
|
|
47835
47873
|
if (env.SKIP_A11Y || env.ULTRA11Y_HOOK === "off") return null;
|
|
47836
47874
|
const intent = matchGitIntent(command);
|
|
@@ -53026,9 +53064,9 @@ function fixSummary(r, lang = "fr", write = false) {
|
|
|
53026
53064
|
}
|
|
53027
53065
|
|
|
53028
53066
|
// src/init.ts
|
|
53029
|
-
import { writeFileSync as writeFileSync13, mkdirSync as mkdirSync10, chmodSync } from "fs";
|
|
53067
|
+
import { writeFileSync as writeFileSync13, mkdirSync as mkdirSync10, chmodSync, realpathSync as realpathSync2 } from "fs";
|
|
53030
53068
|
import { execFileSync as execFileSync5 } from "child_process";
|
|
53031
|
-
import { join as join34 } from "path";
|
|
53069
|
+
import { join as join34, relative as relative3, sep as sep4 } from "path";
|
|
53032
53070
|
var EN_SEV = { bloquant: "blocking", majeur: "major", mineur: "minor" };
|
|
53033
53071
|
function repoRoot() {
|
|
53034
53072
|
try {
|
|
@@ -53037,6 +53075,19 @@ function repoRoot() {
|
|
|
53037
53075
|
return null;
|
|
53038
53076
|
}
|
|
53039
53077
|
}
|
|
53078
|
+
function resolveEnginePath(root) {
|
|
53079
|
+
const argv1 = process.argv[1] ?? "scripts/ultra11y.mjs";
|
|
53080
|
+
try {
|
|
53081
|
+
const abs = realpathSync2(argv1);
|
|
53082
|
+
return abs.startsWith(root + sep4) ? relative3(root, abs) : abs;
|
|
53083
|
+
} catch {
|
|
53084
|
+
return argv1;
|
|
53085
|
+
}
|
|
53086
|
+
}
|
|
53087
|
+
function engineInvocation(root) {
|
|
53088
|
+
const p = resolveEnginePath(root);
|
|
53089
|
+
return p.startsWith("/") || p.startsWith("..") ? "npx -y ultra11y" : `node ${p}`;
|
|
53090
|
+
}
|
|
53040
53091
|
function stagedHookScript(enginePath, failOn) {
|
|
53041
53092
|
const sev = EN_SEV[failOn];
|
|
53042
53093
|
return `#!/bin/sh
|
|
@@ -53139,6 +53190,563 @@ function writeCi(root, enginePath, failOn) {
|
|
|
53139
53190
|
return path;
|
|
53140
53191
|
}
|
|
53141
53192
|
|
|
53193
|
+
// src/install/json-edit.ts
|
|
53194
|
+
import { copyFileSync, existsSync as existsSync21, mkdirSync as mkdirSync11, readFileSync as readFileSync17, renameSync as renameSync2, rmSync as rmSync4, writeFileSync as writeFileSync14 } from "fs";
|
|
53195
|
+
import { dirname as dirname10 } from "path";
|
|
53196
|
+
var SettingsParseError = class extends Error {
|
|
53197
|
+
constructor(path, cause) {
|
|
53198
|
+
super(`${path} is not valid JSON (${cause}) \u2014 fix or move it, then run install again. It has NOT been modified.`);
|
|
53199
|
+
this.path = path;
|
|
53200
|
+
this.name = "SettingsParseError";
|
|
53201
|
+
}
|
|
53202
|
+
path;
|
|
53203
|
+
};
|
|
53204
|
+
function stamp() {
|
|
53205
|
+
return (/* @__PURE__ */ new Date()).toISOString().replaceAll(/[:.]/g, "-");
|
|
53206
|
+
}
|
|
53207
|
+
function writeTextWithBackup(path, content, marker = "ultra11y") {
|
|
53208
|
+
if (existsSync21(path) && readFileSync17(path, "utf8") === content) return { path, changed: false };
|
|
53209
|
+
mkdirSync11(dirname10(path), { recursive: true });
|
|
53210
|
+
let backup;
|
|
53211
|
+
if (existsSync21(path)) {
|
|
53212
|
+
backup = `${path}.${marker}-backup-${stamp()}`;
|
|
53213
|
+
copyFileSync(path, backup);
|
|
53214
|
+
}
|
|
53215
|
+
const tmp = `${path}.${process.pid}.tmp`;
|
|
53216
|
+
try {
|
|
53217
|
+
writeFileSync14(tmp, content, { mode: 420 });
|
|
53218
|
+
renameSync2(tmp, path);
|
|
53219
|
+
} catch (e) {
|
|
53220
|
+
rmSync4(tmp, { force: true });
|
|
53221
|
+
throw e;
|
|
53222
|
+
}
|
|
53223
|
+
return { path, changed: true, backup };
|
|
53224
|
+
}
|
|
53225
|
+
function editJsonFile(path, mutate) {
|
|
53226
|
+
let root = {};
|
|
53227
|
+
if (existsSync21(path)) {
|
|
53228
|
+
const raw = readFileSync17(path, "utf8");
|
|
53229
|
+
if (raw.trim()) {
|
|
53230
|
+
let parsed;
|
|
53231
|
+
try {
|
|
53232
|
+
parsed = JSON.parse(raw);
|
|
53233
|
+
} catch (e) {
|
|
53234
|
+
throw new SettingsParseError(path, e.message);
|
|
53235
|
+
}
|
|
53236
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
53237
|
+
throw new SettingsParseError(path, "the top level is not an object");
|
|
53238
|
+
}
|
|
53239
|
+
root = parsed;
|
|
53240
|
+
}
|
|
53241
|
+
}
|
|
53242
|
+
const before = JSON.stringify(root);
|
|
53243
|
+
mutate(root);
|
|
53244
|
+
if (JSON.stringify(root) === before && existsSync21(path)) return { path, changed: false };
|
|
53245
|
+
return writeTextWithBackup(path, `${JSON.stringify(root, null, 2)}
|
|
53246
|
+
`);
|
|
53247
|
+
}
|
|
53248
|
+
function readJsonSafe(path) {
|
|
53249
|
+
try {
|
|
53250
|
+
const parsed = JSON.parse(readFileSync17(path, "utf8"));
|
|
53251
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
53252
|
+
} catch {
|
|
53253
|
+
return null;
|
|
53254
|
+
}
|
|
53255
|
+
}
|
|
53256
|
+
|
|
53257
|
+
// src/install/claude-code.ts
|
|
53258
|
+
var CLAUDE_MARKER = "hook --claude-code";
|
|
53259
|
+
var MATCHER = "Bash|bash|shell|local_shell|exec_command|run_command|exec";
|
|
53260
|
+
function withoutOurs(groups) {
|
|
53261
|
+
if (!Array.isArray(groups)) return [];
|
|
53262
|
+
return groups.map((g) => ({ ...g, hooks: (g.hooks ?? []).filter((h) => !String(h.command ?? "").includes(CLAUDE_MARKER)) })).filter((g) => (g.hooks ?? []).length > 0);
|
|
53263
|
+
}
|
|
53264
|
+
function installClaudeCode({ settingsPath, command }) {
|
|
53265
|
+
return editJsonFile(settingsPath, (root) => {
|
|
53266
|
+
const hooks = root.hooks ??= {};
|
|
53267
|
+
hooks.PreToolUse = [
|
|
53268
|
+
...withoutOurs(hooks.PreToolUse),
|
|
53269
|
+
{ matcher: MATCHER, hooks: [{ type: "command", command: `${command} ${CLAUDE_MARKER}`, timeout: 30 }] }
|
|
53270
|
+
];
|
|
53271
|
+
});
|
|
53272
|
+
}
|
|
53273
|
+
function uninstallClaudeCode({ settingsPath }) {
|
|
53274
|
+
return editJsonFile(settingsPath, (root) => {
|
|
53275
|
+
const hooks = root.hooks;
|
|
53276
|
+
if (!hooks || typeof hooks !== "object") return;
|
|
53277
|
+
const kept = withoutOurs(hooks.PreToolUse);
|
|
53278
|
+
if (kept.length > 0) hooks.PreToolUse = kept;
|
|
53279
|
+
else delete hooks.PreToolUse;
|
|
53280
|
+
if (Object.keys(hooks).length === 0) delete root.hooks;
|
|
53281
|
+
});
|
|
53282
|
+
}
|
|
53283
|
+
function claudeCodeWired(settingsPath) {
|
|
53284
|
+
const root = readJsonSafe(settingsPath);
|
|
53285
|
+
const groups = root?.hooks?.PreToolUse;
|
|
53286
|
+
if (!Array.isArray(groups)) return 0;
|
|
53287
|
+
return groups.reduce((n, g) => n + (g.hooks ?? []).filter((h) => String(h.command ?? "").includes(CLAUDE_MARKER)).length, 0);
|
|
53288
|
+
}
|
|
53289
|
+
|
|
53290
|
+
// src/install/text-edit.ts
|
|
53291
|
+
import { existsSync as existsSync22, readFileSync as readFileSync18, rmSync as rmSync5 } from "fs";
|
|
53292
|
+
var BLOCK_BEGIN = "<!-- BEGIN ultra11y (managed by `ultra11y install --agents-md`; edit outside this block) -->";
|
|
53293
|
+
var BLOCK_END = "<!-- END ultra11y -->";
|
|
53294
|
+
var blockRe = () => new RegExp(`${escapeRe2(BLOCK_BEGIN)}[\\s\\S]*?${escapeRe2(BLOCK_END)}\\n?`);
|
|
53295
|
+
function escapeRe2(s) {
|
|
53296
|
+
return s.replaceAll(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
53297
|
+
}
|
|
53298
|
+
function upsertManagedBlock(path, body2) {
|
|
53299
|
+
const block = `${BLOCK_BEGIN}
|
|
53300
|
+
${body2.trim()}
|
|
53301
|
+
${BLOCK_END}
|
|
53302
|
+
`;
|
|
53303
|
+
if (!existsSync22(path)) return writeTextWithBackup(path, block);
|
|
53304
|
+
const current = readFileSync18(path, "utf8");
|
|
53305
|
+
if (blockRe().test(current)) return writeTextWithBackup(path, current.replace(blockRe(), block));
|
|
53306
|
+
const sep8 = current.endsWith("\n\n") ? "" : current.endsWith("\n") ? "\n" : "\n\n";
|
|
53307
|
+
return writeTextWithBackup(path, `${current}${sep8}${block}`);
|
|
53308
|
+
}
|
|
53309
|
+
function removeManagedBlock(path) {
|
|
53310
|
+
if (!existsSync22(path)) return { path, changed: false };
|
|
53311
|
+
const current = readFileSync18(path, "utf8");
|
|
53312
|
+
if (!blockRe().test(current)) return { path, changed: false };
|
|
53313
|
+
const rest = current.replace(blockRe(), "");
|
|
53314
|
+
if (rest.trim() === "") {
|
|
53315
|
+
rmSync5(path, { force: true });
|
|
53316
|
+
return { path, changed: true };
|
|
53317
|
+
}
|
|
53318
|
+
return writeTextWithBackup(path, rest.replace(/\n+$/, "\n"));
|
|
53319
|
+
}
|
|
53320
|
+
function hasManagedBlock(path) {
|
|
53321
|
+
return existsSync22(path) && blockRe().test(readFileSync18(path, "utf8"));
|
|
53322
|
+
}
|
|
53323
|
+
|
|
53324
|
+
// src/install/agents-md.ts
|
|
53325
|
+
import { join as join35 } from "path";
|
|
53326
|
+
function agentsMdPath(root) {
|
|
53327
|
+
return join35(root, "AGENTS.md");
|
|
53328
|
+
}
|
|
53329
|
+
function agentsMdBlock(root) {
|
|
53330
|
+
const e = engineInvocation(root);
|
|
53331
|
+
return `## Accessibility (ultra11y)
|
|
53332
|
+
|
|
53333
|
+
This repository is checked against **WCAG 2.2 AA** with \`ultra11y\`: a zero-dependency
|
|
53334
|
+
static engine \u2014 no install, no API key, no network.
|
|
53335
|
+
|
|
53336
|
+
**Before you commit, push, or open a pull request**, whenever you changed HTML, CSS,
|
|
53337
|
+
JSX/TSX, Vue, Svelte or Astro:
|
|
53338
|
+
|
|
53339
|
+
\`\`\`sh
|
|
53340
|
+
${e} audit --staged --graph # exactly what the commit would record
|
|
53341
|
+
${e} audit --since origin/main --graph # the whole branch, for a pull request
|
|
53342
|
+
\`\`\`
|
|
53343
|
+
|
|
53344
|
+
\`--graph\` is not optional on JSX/TSX: it turns on the real cross-file analysis (an
|
|
53345
|
+
icon-only component used without a name, a label defined in another file) and it suppresses
|
|
53346
|
+
a whole class of single-file false positives.
|
|
53347
|
+
|
|
53348
|
+
Then **adjudicate**. Do not paste the output at the user.
|
|
53349
|
+
|
|
53350
|
+
1. Read every finding at its \`file:line\` in the real code. Refute the ones the code
|
|
53351
|
+
disproves, and say what disproves them. Findings inside library sources or single-file
|
|
53352
|
+
components are **preliminary**: the audited artifact is the RENDERED HTML.
|
|
53353
|
+
2. Rule on the judgment criteria yourself, from the code \u2014 is this \`alt\` relevant, is this
|
|
53354
|
+
link's purpose clear in context, is the reading order right.
|
|
53355
|
+
\`${e} verify --report <md> --in <audit.json> --manual\` emits the worklist and the
|
|
53356
|
+
per-criterion decision protocol.
|
|
53357
|
+
3. Name what source cannot decide \u2014 computed contrast, visible focus, zoom and reflow,
|
|
53358
|
+
content on hover \u2014 as **residual risks**. Never call them conforming.
|
|
53359
|
+
4. **Never invent a non-conformity.** \`${e} check --report <file>\` fails the report when a
|
|
53360
|
+
cited element does not resolve. Run it before claiming anything.
|
|
53361
|
+
5. Apply the safe deterministic fixes with \`${e} fix --staged --write --safe\` and re-stage.
|
|
53362
|
+
What remains needs judgment and the user's agreement.
|
|
53363
|
+
|
|
53364
|
+
Severity vocabulary: \`blocking\` > \`major\` > \`minor\`. Block the work on \`blocking\`.
|
|
53365
|
+
|
|
53366
|
+
To make this automatic without relying on an agent, wire the repo itself \u2014 \`${e} init --hook\`
|
|
53367
|
+
(git pre-commit) and \`${e} init --ci\` (GitHub Actions) are harness-independent and are the
|
|
53368
|
+
real safety net here. Full audits, dated conformance reports, RGAA packs and remediation
|
|
53369
|
+
backlogs: \`${e} --help\`.`;
|
|
53370
|
+
}
|
|
53371
|
+
function installAgentsMd(root) {
|
|
53372
|
+
return upsertManagedBlock(agentsMdPath(root), agentsMdBlock(root));
|
|
53373
|
+
}
|
|
53374
|
+
function uninstallAgentsMd(root) {
|
|
53375
|
+
return removeManagedBlock(agentsMdPath(root));
|
|
53376
|
+
}
|
|
53377
|
+
function agentsMdWired(root) {
|
|
53378
|
+
return hasManagedBlock(agentsMdPath(root));
|
|
53379
|
+
}
|
|
53380
|
+
|
|
53381
|
+
// src/install/codex.ts
|
|
53382
|
+
import { existsSync as existsSync24, readFileSync as readFileSync20 } from "fs";
|
|
53383
|
+
import { join as join37 } from "path";
|
|
53384
|
+
|
|
53385
|
+
// src/install/paths.ts
|
|
53386
|
+
import { copyFileSync as copyFileSync2, cpSync, existsSync as existsSync23, mkdirSync as mkdirSync12, readFileSync as readFileSync19, realpathSync as realpathSync3, statSync as statSync10 } from "fs";
|
|
53387
|
+
import { homedir as homedir2 } from "os";
|
|
53388
|
+
import { dirname as dirname11, join as join36 } from "path";
|
|
53389
|
+
function codexHome() {
|
|
53390
|
+
return process.env.CODEX_HOME || join36(homedir2(), ".codex");
|
|
53391
|
+
}
|
|
53392
|
+
function opencodeConfigDir() {
|
|
53393
|
+
const xdg = process.env.XDG_CONFIG_HOME;
|
|
53394
|
+
return join36(xdg && xdg !== "" ? xdg : join36(homedir2(), ".config"), "opencode");
|
|
53395
|
+
}
|
|
53396
|
+
function claudeSettingsPath(project, cwd = process.cwd()) {
|
|
53397
|
+
return project ? join36(cwd, ".claude", "settings.json") : join36(homedir2(), ".claude", "settings.json");
|
|
53398
|
+
}
|
|
53399
|
+
function pinnedEnginePath() {
|
|
53400
|
+
return join36(homedir2(), ".ultra11y", "bin", "ultra11y.mjs");
|
|
53401
|
+
}
|
|
53402
|
+
function installedCliCommand() {
|
|
53403
|
+
const override = process.env.ULTRA11Y_BIN;
|
|
53404
|
+
if (override) return `node ${JSON.stringify(override)}`;
|
|
53405
|
+
const argv1 = process.argv[1];
|
|
53406
|
+
let source = null;
|
|
53407
|
+
try {
|
|
53408
|
+
if (argv1) source = realpathSync3(argv1);
|
|
53409
|
+
} catch {
|
|
53410
|
+
}
|
|
53411
|
+
const ephemeral = !source || /[/\\](?:_npx|\.npm[/\\]_npx|npm-cache|Temp|tmp)[/\\]/.test(source);
|
|
53412
|
+
if (source && !ephemeral) return `node ${JSON.stringify(source)}`;
|
|
53413
|
+
const pin = pinnedEnginePath();
|
|
53414
|
+
if (source) {
|
|
53415
|
+
mkdirSync12(join36(pin, ".."), { recursive: true });
|
|
53416
|
+
copyFileSync2(source, pin);
|
|
53417
|
+
}
|
|
53418
|
+
return `node ${JSON.stringify(pin)}`;
|
|
53419
|
+
}
|
|
53420
|
+
function packageRoot() {
|
|
53421
|
+
let dir;
|
|
53422
|
+
try {
|
|
53423
|
+
dir = dirname11(realpathSync3(process.argv[1] ?? ""));
|
|
53424
|
+
} catch {
|
|
53425
|
+
return null;
|
|
53426
|
+
}
|
|
53427
|
+
for (let i2 = 0; i2 < 6 && dir && dir !== "/"; i2++) {
|
|
53428
|
+
if (existsSync23(join36(dir, "skills", "review-a11y", "SKILL.md"))) return dir;
|
|
53429
|
+
dir = dirname11(dir);
|
|
53430
|
+
}
|
|
53431
|
+
return null;
|
|
53432
|
+
}
|
|
53433
|
+
function bundledSkillsDir() {
|
|
53434
|
+
const root = packageRoot();
|
|
53435
|
+
const p = root ? join36(root, "skills") : null;
|
|
53436
|
+
return p && existsSync23(p) ? p : null;
|
|
53437
|
+
}
|
|
53438
|
+
function bundledOpencodePlugin() {
|
|
53439
|
+
const root = packageRoot();
|
|
53440
|
+
const p = root ? join36(root, ".opencode", "plugins", "ultra11y.js") : null;
|
|
53441
|
+
return p && existsSync23(p) ? p : null;
|
|
53442
|
+
}
|
|
53443
|
+
function copySkillsInto(source, dest) {
|
|
53444
|
+
const out2 = [];
|
|
53445
|
+
for (const name2 of ["ultra11y", "review-a11y"]) {
|
|
53446
|
+
const from = join36(source, name2);
|
|
53447
|
+
if (!existsSync23(from)) continue;
|
|
53448
|
+
const to = join36(dest, name2);
|
|
53449
|
+
const same = ["SKILL.md", join36("scripts", "ultra11y.mjs")].every((f) => {
|
|
53450
|
+
const a = join36(from, f);
|
|
53451
|
+
const b = join36(to, f);
|
|
53452
|
+
try {
|
|
53453
|
+
return existsSync23(b) && statSync10(a).size === statSync10(b).size && readFileSync19(a).equals(readFileSync19(b));
|
|
53454
|
+
} catch {
|
|
53455
|
+
return false;
|
|
53456
|
+
}
|
|
53457
|
+
});
|
|
53458
|
+
if (same) {
|
|
53459
|
+
out2.push({ path: to, changed: false });
|
|
53460
|
+
continue;
|
|
53461
|
+
}
|
|
53462
|
+
cpSync(from, to, { recursive: true });
|
|
53463
|
+
out2.push({ path: to, changed: true });
|
|
53464
|
+
}
|
|
53465
|
+
return out2;
|
|
53466
|
+
}
|
|
53467
|
+
|
|
53468
|
+
// src/install/codex.ts
|
|
53469
|
+
var CODEX_MARKER = "hook --codex";
|
|
53470
|
+
var MATCHER2 = "Bash|bash|shell|local_shell|exec_command|run_command|exec";
|
|
53471
|
+
function withoutOurs2(groups) {
|
|
53472
|
+
if (!Array.isArray(groups)) return [];
|
|
53473
|
+
return groups.map((g) => ({ ...g, hooks: (g.hooks ?? []).filter((h) => !String(h.command ?? "").includes(CODEX_MARKER)) })).filter((g) => (g.hooks ?? []).length > 0);
|
|
53474
|
+
}
|
|
53475
|
+
function enableHooksFeature(content) {
|
|
53476
|
+
if (HOOKS_ON.test(featuresBody(content))) return { content, changed: false };
|
|
53477
|
+
const body2 = featuresBody(content);
|
|
53478
|
+
if (HOOKS_ANY.test(body2)) {
|
|
53479
|
+
return { content: content.replace(body2, body2.replace(HOOKS_ANY, "hooks = true")), changed: true };
|
|
53480
|
+
}
|
|
53481
|
+
if (FEATURES_HEADER.test(content)) {
|
|
53482
|
+
return { content: content.replace(FEATURES_HEADER, "$&\nhooks = true"), changed: true };
|
|
53483
|
+
}
|
|
53484
|
+
const prefix2 = content.trim() === "" ? "" : "\n";
|
|
53485
|
+
return { content: `[features]
|
|
53486
|
+
hooks = true
|
|
53487
|
+
${prefix2}${content}`, changed: true };
|
|
53488
|
+
}
|
|
53489
|
+
var HOOKS_ON = /^[ \t]*hooks[ \t]*=[ \t]*true[ \t]*$/m;
|
|
53490
|
+
var HOOKS_ANY = /^[ \t]*hooks[ \t]*=[ \t]*(?:false|true)[ \t]*$/m;
|
|
53491
|
+
var FEATURES_HEADER = /^[ \t]*\[features\][ \t]*$/m;
|
|
53492
|
+
var ANY_TABLE_HEADER = /^[ \t]*\[/m;
|
|
53493
|
+
function featuresBody(content) {
|
|
53494
|
+
const m = FEATURES_HEADER.exec(content);
|
|
53495
|
+
if (!m) return "";
|
|
53496
|
+
const rest = content.slice(m.index + m[0].length);
|
|
53497
|
+
const next = ANY_TABLE_HEADER.exec(rest);
|
|
53498
|
+
return rest.slice(0, next ? next.index : void 0);
|
|
53499
|
+
}
|
|
53500
|
+
function installCodex({ codexDir, command, skillsSource }) {
|
|
53501
|
+
const reports = [];
|
|
53502
|
+
reports.push(
|
|
53503
|
+
editJsonFile(join37(codexDir, "hooks.json"), (root) => {
|
|
53504
|
+
const hooks = root.hooks ??= {};
|
|
53505
|
+
hooks.PreToolUse = [
|
|
53506
|
+
...withoutOurs2(hooks.PreToolUse),
|
|
53507
|
+
{ matcher: MATCHER2, hooks: [{ type: "command", command: `${command} ${CODEX_MARKER}`, timeout: 30, statusMessage: "ultra11y" }] }
|
|
53508
|
+
];
|
|
53509
|
+
})
|
|
53510
|
+
);
|
|
53511
|
+
const configPath = join37(codexDir, "config.toml");
|
|
53512
|
+
const current = existsSync24(configPath) ? readFileSync20(configPath, "utf8") : "";
|
|
53513
|
+
const edit = enableHooksFeature(current);
|
|
53514
|
+
if (edit.changed) reports.push(writeTextWithBackup(configPath, edit.content));
|
|
53515
|
+
if (skillsSource) reports.push(...copySkillsInto(skillsSource, join37(codexDir, "skills")));
|
|
53516
|
+
return {
|
|
53517
|
+
reports,
|
|
53518
|
+
guidance: [
|
|
53519
|
+
// Deliberately NOT forging the trusted_hash. Reproducing Codex's canonicalisation
|
|
53520
|
+
// exactly is a bet that breaks silently, in the user's home directory, the next time
|
|
53521
|
+
// Codex changes it — and the failure mode of a wrong hash is a gate that never fires.
|
|
53522
|
+
"codex: Codex will ask you to trust this hook the first time it fires \u2014 accept it, or review it with /hooks.",
|
|
53523
|
+
"codex: restart the session \u2014 hooks load at startup.",
|
|
53524
|
+
"codex: `codex plugin marketplace add maxgfr/ultra11y` then `codex plugin add ultra11y@ultra11y` is the native route; it ships the skills and the hook together."
|
|
53525
|
+
]
|
|
53526
|
+
};
|
|
53527
|
+
}
|
|
53528
|
+
function uninstallCodex({ codexDir }) {
|
|
53529
|
+
const reports = [];
|
|
53530
|
+
const hooksPath = join37(codexDir, "hooks.json");
|
|
53531
|
+
if (existsSync24(hooksPath)) {
|
|
53532
|
+
reports.push(
|
|
53533
|
+
editJsonFile(hooksPath, (root) => {
|
|
53534
|
+
const hooks = root.hooks;
|
|
53535
|
+
if (!hooks || typeof hooks !== "object") return;
|
|
53536
|
+
const kept = withoutOurs2(hooks.PreToolUse);
|
|
53537
|
+
if (kept.length > 0) hooks.PreToolUse = kept;
|
|
53538
|
+
else delete hooks.PreToolUse;
|
|
53539
|
+
if (Object.keys(hooks).length === 0) delete root.hooks;
|
|
53540
|
+
})
|
|
53541
|
+
);
|
|
53542
|
+
}
|
|
53543
|
+
return { reports, guidance: ["codex: `[features] hooks = true` was left enabled \u2014 other hooks may depend on it."] };
|
|
53544
|
+
}
|
|
53545
|
+
function codexWired(codexDir) {
|
|
53546
|
+
const root = readJsonSafe(join37(codexDir, "hooks.json"));
|
|
53547
|
+
const groups = root?.hooks?.PreToolUse;
|
|
53548
|
+
if (!Array.isArray(groups)) return 0;
|
|
53549
|
+
return groups.reduce((n, g) => n + (g.hooks ?? []).filter((h) => String(h.command ?? "").includes(CODEX_MARKER)).length, 0);
|
|
53550
|
+
}
|
|
53551
|
+
function codexHooksEnabled(codexDir) {
|
|
53552
|
+
const p = join37(codexDir, "config.toml");
|
|
53553
|
+
return existsSync24(p) && HOOKS_ON.test(featuresBody(readFileSync20(p, "utf8")));
|
|
53554
|
+
}
|
|
53555
|
+
|
|
53556
|
+
// src/install/opencode.ts
|
|
53557
|
+
import { existsSync as existsSync25, readFileSync as readFileSync21, rmSync as rmSync6 } from "fs";
|
|
53558
|
+
import { join as join38 } from "path";
|
|
53559
|
+
var OPENCODE_MARKER = "ULTRA11Y_OPENCODE_PLUGIN";
|
|
53560
|
+
function pluginTarget(configDir) {
|
|
53561
|
+
return join38(configDir, "plugin", "ultra11y.js");
|
|
53562
|
+
}
|
|
53563
|
+
function installOpencode({ configDir, pluginSource, skillsSource, enginePath }) {
|
|
53564
|
+
const target = pluginTarget(configDir);
|
|
53565
|
+
if (existsSync25(target) && !readFileSync21(target, "utf8").includes(OPENCODE_MARKER)) {
|
|
53566
|
+
throw new Error(`refusing to overwrite ${target}: it is not a file ultra11y wrote. Remove it manually if you want the plugin here.`);
|
|
53567
|
+
}
|
|
53568
|
+
const reports = [writeTextWithBackup(target, readFileSync21(pluginSource, "utf8"))];
|
|
53569
|
+
if (enginePath && existsSync25(enginePath)) {
|
|
53570
|
+
reports.push(writeTextWithBackup(join38(configDir, "plugin", "ultra11y.mjs"), readFileSync21(enginePath, "utf8")));
|
|
53571
|
+
}
|
|
53572
|
+
if (skillsSource) reports.push(...copySkillsInto(skillsSource, join38(configDir, "skills")));
|
|
53573
|
+
return {
|
|
53574
|
+
reports,
|
|
53575
|
+
guidance: [
|
|
53576
|
+
"opencode: restart the session \u2014 plugins load at startup.",
|
|
53577
|
+
'opencode: the alternative is an npm pin \u2014 add "plugin": ["ultra11y@latest"] to ~/.config/opencode/opencode.json and drop this file.',
|
|
53578
|
+
"opencode: the gate surfaces as a blocked tool call whose error message carries the findings (OpenCode has no permission-decision channel)."
|
|
53579
|
+
]
|
|
53580
|
+
};
|
|
53581
|
+
}
|
|
53582
|
+
function uninstallOpencode({ configDir }) {
|
|
53583
|
+
const reports = [];
|
|
53584
|
+
const target = pluginTarget(configDir);
|
|
53585
|
+
if (existsSync25(target) && readFileSync21(target, "utf8").includes(OPENCODE_MARKER)) {
|
|
53586
|
+
rmSync6(target, { force: true });
|
|
53587
|
+
rmSync6(join38(configDir, "plugin", "ultra11y.mjs"), { force: true });
|
|
53588
|
+
reports.push({ path: target, changed: true });
|
|
53589
|
+
}
|
|
53590
|
+
const configPath = join38(configDir, "opencode.json");
|
|
53591
|
+
if (existsSync25(configPath)) {
|
|
53592
|
+
reports.push(
|
|
53593
|
+
editJsonFile(configPath, (root) => {
|
|
53594
|
+
if (!Array.isArray(root.plugin)) return;
|
|
53595
|
+
const kept = root.plugin.filter((p) => !/^ultra11y(@|$)/.test(String(p)));
|
|
53596
|
+
if (kept.length > 0) root.plugin = kept;
|
|
53597
|
+
else delete root.plugin;
|
|
53598
|
+
})
|
|
53599
|
+
);
|
|
53600
|
+
}
|
|
53601
|
+
return { reports, guidance: [] };
|
|
53602
|
+
}
|
|
53603
|
+
function opencodeWired(configDir) {
|
|
53604
|
+
const target = pluginTarget(configDir);
|
|
53605
|
+
if (existsSync25(target) && readFileSync21(target, "utf8").includes(OPENCODE_MARKER)) return true;
|
|
53606
|
+
try {
|
|
53607
|
+
const cfg = JSON.parse(readFileSync21(join38(configDir, "opencode.json"), "utf8"));
|
|
53608
|
+
return Array.isArray(cfg.plugin) && cfg.plugin.some((p) => /^ultra11y(@|$)/.test(String(p)));
|
|
53609
|
+
} catch {
|
|
53610
|
+
return false;
|
|
53611
|
+
}
|
|
53612
|
+
}
|
|
53613
|
+
|
|
53614
|
+
// src/install/index.ts
|
|
53615
|
+
import { existsSync as existsSync26 } from "fs";
|
|
53616
|
+
import { join as join39 } from "path";
|
|
53617
|
+
var ALL_TARGETS = ["claude-code", "codex", "opencode"];
|
|
53618
|
+
function parseTargets(flags2) {
|
|
53619
|
+
const picked = /* @__PURE__ */ new Set();
|
|
53620
|
+
if (flags2.all === true) for (const t2 of ALL_TARGETS) picked.add(t2);
|
|
53621
|
+
if (flags2["claude-code"] === true) picked.add("claude-code");
|
|
53622
|
+
if (flags2.codex === true) picked.add("codex");
|
|
53623
|
+
if (flags2.opencode === true) picked.add("opencode");
|
|
53624
|
+
if (flags2["agents-md"] === true) picked.add("agents-md");
|
|
53625
|
+
return picked.size ? [...picked] : null;
|
|
53626
|
+
}
|
|
53627
|
+
function projectRoot(cwd) {
|
|
53628
|
+
return repoRoot() ?? cwd;
|
|
53629
|
+
}
|
|
53630
|
+
function installForTargets(opts) {
|
|
53631
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
53632
|
+
const out2 = [];
|
|
53633
|
+
let command = null;
|
|
53634
|
+
const cmd = () => command ??= installedCliCommand();
|
|
53635
|
+
for (const target of opts.targets) {
|
|
53636
|
+
const r = { target, reports: [], guidance: [] };
|
|
53637
|
+
try {
|
|
53638
|
+
switch (target) {
|
|
53639
|
+
case "claude-code": {
|
|
53640
|
+
const settingsPath = claudeSettingsPath(opts.project === true, cwd);
|
|
53641
|
+
if (!opts.dryRun) r.reports.push(installClaudeCode({ settingsPath, command: cmd() }));
|
|
53642
|
+
else r.reports.push({ path: settingsPath, changed: false });
|
|
53643
|
+
r.guidance.push(
|
|
53644
|
+
"claude-code: the plugin route (`/plugin marketplace add maxgfr/ultra11y` then `/plugin install ultra11y@ultra11y`) ships the skills too \u2014 this settings hook only wires the gate.",
|
|
53645
|
+
"claude-code: restart the session \u2014 hooks load at startup."
|
|
53646
|
+
);
|
|
53647
|
+
break;
|
|
53648
|
+
}
|
|
53649
|
+
case "codex": {
|
|
53650
|
+
if (opts.project === true) r.guidance.push("codex: --project is not supported (Codex reads hooks from its home only) \u2014 wiring the user scope.");
|
|
53651
|
+
if (!opts.dryRun) {
|
|
53652
|
+
const out3 = installCodex({ codexDir: codexHome(), command: cmd(), skillsSource: opts.skills === false ? null : bundledSkillsDir() });
|
|
53653
|
+
r.reports.push(...out3.reports);
|
|
53654
|
+
r.guidance.push(...out3.guidance);
|
|
53655
|
+
} else r.reports.push({ path: join39(codexHome(), "hooks.json"), changed: false });
|
|
53656
|
+
break;
|
|
53657
|
+
}
|
|
53658
|
+
case "opencode": {
|
|
53659
|
+
const source = bundledOpencodePlugin();
|
|
53660
|
+
if (!source) {
|
|
53661
|
+
r.error = "opencode: this engine was not run from a package carrying .opencode/plugins/ultra11y.js \u2014 install ultra11y from npm and retry.";
|
|
53662
|
+
break;
|
|
53663
|
+
}
|
|
53664
|
+
const configDir = opts.project === true ? join39(cwd, ".opencode") : opencodeConfigDir();
|
|
53665
|
+
if (!opts.dryRun) {
|
|
53666
|
+
const engine = existsSync26(pinnedEnginePath()) ? pinnedEnginePath() : join39(packageRoot() ?? "", "scripts", "ultra11y.mjs");
|
|
53667
|
+
const out3 = installOpencode({
|
|
53668
|
+
configDir,
|
|
53669
|
+
pluginSource: source,
|
|
53670
|
+
skillsSource: opts.skills === false ? null : bundledSkillsDir(),
|
|
53671
|
+
enginePath: existsSync26(engine) ? engine : null
|
|
53672
|
+
});
|
|
53673
|
+
r.reports.push(...out3.reports);
|
|
53674
|
+
r.guidance.push(...out3.guidance);
|
|
53675
|
+
} else r.reports.push({ path: join39(configDir, "plugin", "ultra11y.js"), changed: false });
|
|
53676
|
+
break;
|
|
53677
|
+
}
|
|
53678
|
+
case "agents-md": {
|
|
53679
|
+
const root = projectRoot(cwd);
|
|
53680
|
+
if (!opts.dryRun) r.reports.push(installAgentsMd(root));
|
|
53681
|
+
else r.reports.push({ path: agentsMdPath(root), changed: false });
|
|
53682
|
+
r.guidance.push(
|
|
53683
|
+
"agents-md: this is a tracked file in your repository \u2014 review it and commit it.",
|
|
53684
|
+
"agents-md: it cannot make anything automatic (no hook API); `ultra11y init --hook` is what actually enforces the gate there."
|
|
53685
|
+
);
|
|
53686
|
+
break;
|
|
53687
|
+
}
|
|
53688
|
+
}
|
|
53689
|
+
} catch (e) {
|
|
53690
|
+
r.error = e instanceof SettingsParseError ? e.message : `${target}: ${e.message}`;
|
|
53691
|
+
}
|
|
53692
|
+
out2.push(r);
|
|
53693
|
+
}
|
|
53694
|
+
return out2;
|
|
53695
|
+
}
|
|
53696
|
+
function uninstallForTargets(opts) {
|
|
53697
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
53698
|
+
const out2 = [];
|
|
53699
|
+
for (const target of opts.targets) {
|
|
53700
|
+
const r = { target, reports: [], guidance: [] };
|
|
53701
|
+
try {
|
|
53702
|
+
switch (target) {
|
|
53703
|
+
case "claude-code":
|
|
53704
|
+
r.reports.push(uninstallClaudeCode({ settingsPath: claudeSettingsPath(opts.project === true, cwd) }));
|
|
53705
|
+
break;
|
|
53706
|
+
case "codex": {
|
|
53707
|
+
const out3 = uninstallCodex({ codexDir: codexHome() });
|
|
53708
|
+
r.reports.push(...out3.reports);
|
|
53709
|
+
r.guidance.push(...out3.guidance);
|
|
53710
|
+
break;
|
|
53711
|
+
}
|
|
53712
|
+
case "opencode": {
|
|
53713
|
+
const out3 = uninstallOpencode({ configDir: opts.project === true ? join39(cwd, ".opencode") : opencodeConfigDir() });
|
|
53714
|
+
r.reports.push(...out3.reports);
|
|
53715
|
+
r.guidance.push(...out3.guidance);
|
|
53716
|
+
break;
|
|
53717
|
+
}
|
|
53718
|
+
case "agents-md":
|
|
53719
|
+
r.reports.push(uninstallAgentsMd(projectRoot(cwd)));
|
|
53720
|
+
break;
|
|
53721
|
+
}
|
|
53722
|
+
} catch (e) {
|
|
53723
|
+
r.error = e instanceof SettingsParseError ? e.message : `${target}: ${e.message}`;
|
|
53724
|
+
}
|
|
53725
|
+
out2.push(r);
|
|
53726
|
+
}
|
|
53727
|
+
return out2;
|
|
53728
|
+
}
|
|
53729
|
+
function statusReport(opts = {}) {
|
|
53730
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
53731
|
+
const claudePath = claudeSettingsPath(opts.project === true, cwd);
|
|
53732
|
+
const root = projectRoot(cwd);
|
|
53733
|
+
const codexDir = codexHome();
|
|
53734
|
+
const ocDir = opts.project === true ? join39(cwd, ".opencode") : opencodeConfigDir();
|
|
53735
|
+
const codexOn = codexWired(codexDir) > 0;
|
|
53736
|
+
return [
|
|
53737
|
+
{ target: "claude-code", wired: claudeCodeWired(claudePath) > 0, path: claudePath, note: "the plugin route wires this separately and does not show here" },
|
|
53738
|
+
{
|
|
53739
|
+
target: "codex",
|
|
53740
|
+
wired: codexOn,
|
|
53741
|
+
path: join39(codexDir, "hooks.json"),
|
|
53742
|
+
// A wired hook behind a disabled feature flag never fires and looks fine. Say so.
|
|
53743
|
+
note: codexOn && !codexHooksEnabled(codexDir) ? "WIRED BUT INERT: `[features] hooks = true` is not set in config.toml" : void 0
|
|
53744
|
+
},
|
|
53745
|
+
{ target: "opencode", wired: opencodeWired(ocDir), path: join39(ocDir, "plugin", "ultra11y.js") },
|
|
53746
|
+
{ target: "agents-md", wired: agentsMdWired(root), path: agentsMdPath(root) }
|
|
53747
|
+
];
|
|
53748
|
+
}
|
|
53749
|
+
|
|
53142
53750
|
// src/output.ts
|
|
53143
53751
|
var STR = {
|
|
53144
53752
|
fr: {
|
|
@@ -53436,7 +54044,7 @@ function stepSummary(result, opts = {}) {
|
|
|
53436
54044
|
|
|
53437
54045
|
// src/dev.ts
|
|
53438
54046
|
import { createServer } from "http";
|
|
53439
|
-
import { join as
|
|
54047
|
+
import { join as join40 } from "path";
|
|
53440
54048
|
var DEV_DEFAULT_PORT = 4111;
|
|
53441
54049
|
function criterionLabel3(f, standard) {
|
|
53442
54050
|
if (isCore(standard)) return `WCAG ${f.criteriaId}`;
|
|
@@ -53702,13 +54310,13 @@ function auditCollected(root, payload) {
|
|
|
53702
54310
|
...payload.axtree ? { axtree: payload.axtree } : {},
|
|
53703
54311
|
...payload.css ? { css: payload.css } : {}
|
|
53704
54312
|
});
|
|
53705
|
-
return { ok: true, result: runAudit({ inputs: [
|
|
54313
|
+
return { ok: true, result: runAudit({ inputs: [join40(dir, "dom.html")] }) };
|
|
53706
54314
|
}
|
|
53707
54315
|
function projectPages(root) {
|
|
53708
54316
|
const snaps = readSnapshots(root);
|
|
53709
54317
|
if (!snaps.length) return { result: null, pages: [] };
|
|
53710
54318
|
const scope = pageScopesFrom(snaps);
|
|
53711
|
-
const result = runAudit({ inputs: [
|
|
54319
|
+
const result = runAudit({ inputs: [join40(root, ".ultra11y/pages")] });
|
|
53712
54320
|
result.scope.pages = scope;
|
|
53713
54321
|
attributePages(result, scope);
|
|
53714
54322
|
return { result, pages: derivePages(result, scope) };
|
|
@@ -54033,11 +54641,11 @@ function e2eSetupPlan(runners, paths, lang = "en") {
|
|
|
54033
54641
|
}
|
|
54034
54642
|
|
|
54035
54643
|
// src/orchestrate.ts
|
|
54036
|
-
import { existsSync as
|
|
54037
|
-
import { join as
|
|
54644
|
+
import { existsSync as existsSync27, mkdirSync as mkdirSync13, readFileSync as readFileSync23, rmSync as rmSync7, writeFileSync as writeFileSync15 } from "fs";
|
|
54645
|
+
import { join as join42, resolve as resolve7 } from "path";
|
|
54038
54646
|
|
|
54039
54647
|
// src/orchestrate-templates.ts
|
|
54040
|
-
import { join as
|
|
54648
|
+
import { join as join41 } from "path";
|
|
54041
54649
|
var ONE_WRITER_FOOTER = `
|
|
54042
54650
|
## Return, don't write
|
|
54043
54651
|
|
|
@@ -54109,7 +54717,7 @@ var PHASE_SPECS = {
|
|
|
54109
54717
|
title: "Adjudicate",
|
|
54110
54718
|
schema: ADJUDICATE_SCHEMA,
|
|
54111
54719
|
description: (n) => `Adjudicate the ${n} residual judgment criterion(ia) of an ultra11y audit (fan-out, fail-closed fold)`,
|
|
54112
|
-
applyHint: (engine, worklist, run2) => `node ${engine} verify --apply ${worklist} --in ${
|
|
54720
|
+
applyHint: (engine, worklist, run2) => `node ${engine} verify --apply ${worklist} --in ${join41(run2, "audit-latest.json")} --out ${run2}`
|
|
54113
54721
|
},
|
|
54114
54722
|
"verify-report": {
|
|
54115
54723
|
role: "refuter",
|
|
@@ -54131,7 +54739,7 @@ function toBatches(ids, batchSize) {
|
|
|
54131
54739
|
}
|
|
54132
54740
|
function phaseWorkflowScript(ph, runAbs, engineAbs, batchSize) {
|
|
54133
54741
|
const spec = phaseSpec(ph.name);
|
|
54134
|
-
const scriptPath =
|
|
54742
|
+
const scriptPath = join41(runAbs, "orchestration", `${ph.name}.workflow.mjs`);
|
|
54135
54743
|
const meta2 = { name: `ultra11y-${ph.name}`, description: spec.description(ph.items), phases: [{ title: spec.title }] };
|
|
54136
54744
|
return [
|
|
54137
54745
|
`export const meta = ${JSON.stringify(meta2)}`,
|
|
@@ -54175,7 +54783,7 @@ function agentContracts(runAbs, engineAbs) {
|
|
|
54175
54783
|
|
|
54176
54784
|
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.
|
|
54177
54785
|
|
|
54178
|
-
Worklist: \`${
|
|
54786
|
+
Worklist: \`${join41(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>\`).
|
|
54179
54787
|
|
|
54180
54788
|
For EACH of your criteria:
|
|
54181
54789
|
|
|
@@ -54193,7 +54801,7 @@ ${footer}`,
|
|
|
54193
54801
|
|
|
54194
54802
|
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.
|
|
54195
54803
|
|
|
54196
|
-
Worklist: \`${
|
|
54804
|
+
Worklist: \`${join41(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>\`).
|
|
54197
54805
|
|
|
54198
54806
|
For EACH of your entries:
|
|
54199
54807
|
|
|
@@ -54229,14 +54837,14 @@ ${status}
|
|
|
54229
54837
|
|
|
54230
54838
|
## The loop (play every role yourself, one item at a time)
|
|
54231
54839
|
|
|
54232
|
-
1. **Audit** (if not done): \`${engine} audit "<globs>" --graph --out ${runAbs}\` \u2192 \`${
|
|
54233
|
-
2. **Adjudicate the residual criteria** \u2014 \`${engine} verify --manual --in ${
|
|
54234
|
-
3. **Report**: \`${engine} report --in ${
|
|
54235
|
-
4. **Verify the report's claims** \u2014 \`${engine} verify --report <the report .md> --out ${runAbs}\` writes \`${
|
|
54840
|
+
1. **Audit** (if not done): \`${engine} audit "<globs>" --graph --out ${runAbs}\` \u2192 \`${join41(runAbs, "audit-latest.json")}\`.
|
|
54841
|
+
2. **Adjudicate the residual criteria** \u2014 \`${engine} verify --manual --in ${join41(runAbs, "audit-latest.json")} --out ${runAbs}\` writes \`${join41(runAbs, "ADJUDICATE.todo.json")}\`. For EVERY item, apply \`${join41(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 ${join41(runAbs, "ADJUDICATE.todo.json")} --in ${join41(runAbs, "audit-latest.json")} --out ${runAbs}\`.
|
|
54842
|
+
3. **Report**: \`${engine} report --in ${join41(runAbs, "audit-latest.json")} --out ${runAbs}\`.
|
|
54843
|
+
4. **Verify the report's claims** \u2014 \`${engine} verify --report <the report .md> --out ${runAbs}\` writes \`${join41(runAbs, "VERIFY.todo.json")}\`. For EVERY entry, apply \`${join41(runAbs, "orchestration", "agents", "refuter.md")}\` yourself (open file:line, verdict supported/partial/refuted/unsupported + note IN the todo file). Then: \`${engine} verify --apply ${join41(runAbs, "VERIFY.todo.json")} --report <the report .md>\`.
|
|
54236
54844
|
5. **Gate**: \`${engine} check --report <the report .md> --semantic\` must exit 0 before presenting anything.
|
|
54237
54845
|
6. **Fix & re-audit**: \`${engine} fix <globs> --write --iterate\`, hand-apply the judgment fixes, then loop from step 1 until the gate stays green.
|
|
54238
54846
|
|
|
54239
|
-
With subagents available, prefer the emitted workflows instead: \`orchestrate --run ${runAbs} --phase <p>\` then \`Workflow({ scriptPath: "${
|
|
54847
|
+
With subagents available, prefer the emitted workflows instead: \`orchestrate --run ${runAbs} --phase <p>\` then \`Workflow({ scriptPath: "${join41(runAbs, "orchestration", "<p>.workflow.mjs")}" })\` \u2014 you stay the sole writer either way.
|
|
54240
54848
|
`;
|
|
54241
54849
|
}
|
|
54242
54850
|
|
|
@@ -54246,12 +54854,12 @@ var SMALL_WORKLIST = 3;
|
|
|
54246
54854
|
var BATCH_SIZE = 8;
|
|
54247
54855
|
function listPhases(runDir, engineAbs) {
|
|
54248
54856
|
const run2 = resolve7(runDir);
|
|
54249
|
-
const adjPath =
|
|
54857
|
+
const adjPath = join42(run2, "ADJUDICATE.todo.json");
|
|
54250
54858
|
let adjIds = [];
|
|
54251
54859
|
let adjReady = false;
|
|
54252
|
-
if (
|
|
54860
|
+
if (existsSync27(adjPath)) {
|
|
54253
54861
|
try {
|
|
54254
|
-
const f = JSON.parse(
|
|
54862
|
+
const f = JSON.parse(readFileSync23(adjPath, "utf8"));
|
|
54255
54863
|
if (f && f.kind === "adjudication" && Array.isArray(f.items)) {
|
|
54256
54864
|
adjReady = true;
|
|
54257
54865
|
adjIds = f.items.map((i2) => i2.criteriaId);
|
|
@@ -54259,12 +54867,12 @@ function listPhases(runDir, engineAbs) {
|
|
|
54259
54867
|
} catch {
|
|
54260
54868
|
}
|
|
54261
54869
|
}
|
|
54262
|
-
const verPath =
|
|
54870
|
+
const verPath = join42(run2, "VERIFY.todo.json");
|
|
54263
54871
|
let verIds = [];
|
|
54264
54872
|
let verReady = false;
|
|
54265
|
-
if (
|
|
54873
|
+
if (existsSync27(verPath)) {
|
|
54266
54874
|
try {
|
|
54267
|
-
const items = JSON.parse(
|
|
54875
|
+
const items = JSON.parse(readFileSync23(verPath, "utf8"));
|
|
54268
54876
|
if (Array.isArray(items)) {
|
|
54269
54877
|
verReady = true;
|
|
54270
54878
|
verIds = items.map((i2) => String(i2.n));
|
|
@@ -54279,7 +54887,7 @@ function listPhases(runDir, engineAbs) {
|
|
|
54279
54887
|
worklist: adjPath,
|
|
54280
54888
|
items: adjIds.length,
|
|
54281
54889
|
ids: adjIds,
|
|
54282
|
-
prerequisite: `node ${engineAbs} verify --manual --in ${
|
|
54890
|
+
prerequisite: `node ${engineAbs} verify --manual --in ${join42(run2, "audit-latest.json")} --out ${run2}`
|
|
54283
54891
|
},
|
|
54284
54892
|
{
|
|
54285
54893
|
name: "verify-report",
|
|
@@ -54293,7 +54901,7 @@ function listPhases(runDir, engineAbs) {
|
|
|
54293
54901
|
}
|
|
54294
54902
|
function orchestrateRun(runDir, engineAbs, opts = {}) {
|
|
54295
54903
|
const run2 = resolve7(runDir);
|
|
54296
|
-
if (!
|
|
54904
|
+
if (!existsSync27(run2)) {
|
|
54297
54905
|
return { exitCode: 2, written: [], notices: [], errors: [`run dir not found: ${run2}`], phases: [] };
|
|
54298
54906
|
}
|
|
54299
54907
|
const phases = listPhases(run2, engineAbs);
|
|
@@ -54320,23 +54928,23 @@ function orchestrateRun(runDir, engineAbs, opts = {}) {
|
|
|
54320
54928
|
}
|
|
54321
54929
|
selected = [ph];
|
|
54322
54930
|
}
|
|
54323
|
-
const orchDir =
|
|
54324
|
-
const agentsDir =
|
|
54325
|
-
|
|
54326
|
-
|
|
54931
|
+
const orchDir = join42(run2, "orchestration");
|
|
54932
|
+
const agentsDir = join42(orchDir, "agents");
|
|
54933
|
+
mkdirSync13(join42(orchDir, "out"), { recursive: true });
|
|
54934
|
+
mkdirSync13(agentsDir, { recursive: true });
|
|
54327
54935
|
const written = [];
|
|
54328
54936
|
const notices = [];
|
|
54329
54937
|
for (const [name2, content] of Object.entries(agentContracts(run2, engineAbs))) {
|
|
54330
|
-
const p =
|
|
54331
|
-
|
|
54938
|
+
const p = join42(agentsDir, `${name2}.md`);
|
|
54939
|
+
writeFileSync15(p, content);
|
|
54332
54940
|
written.push(p);
|
|
54333
54941
|
}
|
|
54334
54942
|
const emitted = new Set(opts.eco ? [] : selected.filter((p) => p.items > 0).map((p) => p.name));
|
|
54335
54943
|
for (const ph of phases) {
|
|
54336
54944
|
if (emitted.has(ph.name)) continue;
|
|
54337
|
-
const stale =
|
|
54338
|
-
if (
|
|
54339
|
-
|
|
54945
|
+
const stale = join42(orchDir, `${ph.name}.workflow.mjs`);
|
|
54946
|
+
if (existsSync27(stale)) {
|
|
54947
|
+
rmSync7(stale, { force: true });
|
|
54340
54948
|
notices.push(`phase "${ph.name}": stale workflow removed \u2014 its worklist ${ph.ready ? "is now empty" : "no longer exists"}.`);
|
|
54341
54949
|
}
|
|
54342
54950
|
}
|
|
@@ -54349,13 +54957,13 @@ function orchestrateRun(runDir, engineAbs, opts = {}) {
|
|
|
54349
54957
|
if (ph.items <= SMALL_WORKLIST) {
|
|
54350
54958
|
notices.push(`phase "${ph.name}": only ${ph.items} item(s) \u2014 the sequential --eco path is equivalent and cheaper.`);
|
|
54351
54959
|
}
|
|
54352
|
-
const p =
|
|
54353
|
-
|
|
54960
|
+
const p = join42(orchDir, `${ph.name}.workflow.mjs`);
|
|
54961
|
+
writeFileSync15(p, phaseWorkflowScript(ph, run2, engineAbs, BATCH_SIZE));
|
|
54354
54962
|
written.push(p);
|
|
54355
54963
|
}
|
|
54356
54964
|
}
|
|
54357
|
-
const rb =
|
|
54358
|
-
|
|
54965
|
+
const rb = join42(orchDir, "RUNBOOK.md");
|
|
54966
|
+
writeFileSync15(rb, runbookMd(phases, run2, engineAbs));
|
|
54359
54967
|
written.push(rb);
|
|
54360
54968
|
return { exitCode: 0, written, notices, errors: [], phases };
|
|
54361
54969
|
}
|
|
@@ -54364,8 +54972,8 @@ function orchestrateRun(runDir, engineAbs, opts = {}) {
|
|
|
54364
54972
|
import { createInterface as createInterface2 } from "readline";
|
|
54365
54973
|
|
|
54366
54974
|
// src/mcp/handlers.ts
|
|
54367
|
-
import { existsSync as
|
|
54368
|
-
import { isAbsolute as isAbsolute3, join as
|
|
54975
|
+
import { existsSync as existsSync28, readFileSync as readFileSync24, realpathSync as realpathSync4, statSync as statSync11 } from "fs";
|
|
54976
|
+
import { isAbsolute as isAbsolute3, join as join43, resolve as resolve8, sep as sep5 } from "path";
|
|
54369
54977
|
|
|
54370
54978
|
// src/project-lock.ts
|
|
54371
54979
|
var chains = /* @__PURE__ */ new Map();
|
|
@@ -54413,8 +55021,8 @@ function requiredCwd(args2, defaults) {
|
|
|
54413
55021
|
const cwd = str2(args2.cwd) ?? defaults.defaultCwd;
|
|
54414
55022
|
if (!cwd) throw new ToolError("`cwd` is required: an absolute path to the project root.");
|
|
54415
55023
|
const abs = resolve8(cwd);
|
|
54416
|
-
if (!
|
|
54417
|
-
if (!
|
|
55024
|
+
if (!existsSync28(abs)) throw new ToolError(`project root not found: ${abs}`);
|
|
55025
|
+
if (!statSync11(abs).isDirectory()) throw new ToolError(`\`cwd\` is not a directory: ${abs}`);
|
|
54418
55026
|
return abs;
|
|
54419
55027
|
}
|
|
54420
55028
|
function standardOf(args2) {
|
|
@@ -54558,13 +55166,13 @@ function handlePackCheck(args2, cwd) {
|
|
|
54558
55166
|
return { cwd, ...res };
|
|
54559
55167
|
}
|
|
54560
55168
|
function handleSampleCheck(cwd) {
|
|
54561
|
-
const file =
|
|
54562
|
-
if (!
|
|
55169
|
+
const file = join43(cwd, ".ultra11yrc.json");
|
|
55170
|
+
if (!existsSync28(file)) {
|
|
54563
55171
|
throw new ToolError(`no .ultra11yrc.json at ${cwd} \u2014 a page sample must be declared before it can be linted.`);
|
|
54564
55172
|
}
|
|
54565
55173
|
let raw;
|
|
54566
55174
|
try {
|
|
54567
|
-
raw = JSON.parse(
|
|
55175
|
+
raw = JSON.parse(readFileSync24(file, "utf8"));
|
|
54568
55176
|
} catch (e) {
|
|
54569
55177
|
throw new ToolError(`.ultra11yrc.json is not valid JSON: ${e.message}`);
|
|
54570
55178
|
}
|
|
@@ -54602,7 +55210,7 @@ function handleInit(args2, cwd) {
|
|
|
54602
55210
|
const enginePath = resolve8("scripts/ultra11y.mjs");
|
|
54603
55211
|
const written = [];
|
|
54604
55212
|
if (bool(args2.hook)) written.push(writeHook(cwd, enginePath, failOn));
|
|
54605
|
-
if (bool(args2.ci)) written.push(
|
|
55213
|
+
if (bool(args2.ci)) written.push(join43(cwd, ".github/workflows/a11y.yml"));
|
|
54606
55214
|
if (!written.length) {
|
|
54607
55215
|
throw new ToolError("nothing to install \u2014 pass hook:true and/or ci:true.");
|
|
54608
55216
|
}
|
|
@@ -54622,8 +55230,8 @@ function reportText(args2, tool) {
|
|
|
54622
55230
|
if (inline) return inline;
|
|
54623
55231
|
if (!file) throw new ToolError(`\`report_text\` is required \u2014 the report markdown for ultra11y_${tool} to work on.`);
|
|
54624
55232
|
if (!isAbsolute3(file)) throw new ToolError("`report_file` must be an absolute path.");
|
|
54625
|
-
if (!
|
|
54626
|
-
return
|
|
55233
|
+
if (!existsSync28(file)) throw new ToolError(`report file not found: ${file}`);
|
|
55234
|
+
return readFileSync24(file, "utf8");
|
|
54627
55235
|
}
|
|
54628
55236
|
function handleCriteria(args2) {
|
|
54629
55237
|
const lang = langOf(args2);
|
|
@@ -54638,21 +55246,21 @@ function handleCriteria(args2) {
|
|
|
54638
55246
|
function handleRead(args2, cwd) {
|
|
54639
55247
|
const raw = str2(args2.path);
|
|
54640
55248
|
if (!raw) throw new ToolError("`path` is required \u2014 relative to the project root, or an absolute path inside it.");
|
|
54641
|
-
const target = isAbsolute3(raw) ? raw :
|
|
55249
|
+
const target = isAbsolute3(raw) ? raw : join43(cwd, raw);
|
|
54642
55250
|
let real;
|
|
54643
55251
|
try {
|
|
54644
|
-
real =
|
|
55252
|
+
real = realpathSync4(target);
|
|
54645
55253
|
} catch {
|
|
54646
55254
|
throw new ToolError(`no such file: ${raw}`);
|
|
54647
55255
|
}
|
|
54648
|
-
const root =
|
|
54649
|
-
if (real !== root && !real.startsWith(root +
|
|
55256
|
+
const root = realpathSync4(cwd);
|
|
55257
|
+
if (real !== root && !real.startsWith(root + sep5)) {
|
|
54650
55258
|
throw new ToolError(`path is outside the project: ${raw}. Use your own file tool for anything else.`);
|
|
54651
55259
|
}
|
|
54652
|
-
const st =
|
|
55260
|
+
const st = statSync11(real);
|
|
54653
55261
|
if (!st.isFile()) throw new ToolError(`not a file: ${raw}`);
|
|
54654
55262
|
if (st.size > MAX_READ_BYTES) throw new ToolError(`file is too large to read (${st.size} bytes): ${raw}`);
|
|
54655
|
-
const lines =
|
|
55263
|
+
const lines = readFileSync24(real, "utf8").split("\n");
|
|
54656
55264
|
const total = lines.length;
|
|
54657
55265
|
const start2 = Math.max(1, Math.floor(num2(args2.start_line) ?? 1));
|
|
54658
55266
|
if (start2 > total) throw new ToolError(`start_line ${start2} is past the end of the file (${total} lines).`);
|
|
@@ -55147,25 +55755,25 @@ function str3(v) {
|
|
|
55147
55755
|
var DECLARED = new Set([...TOOLS2, ...WRITE_TOOLS].map((t2) => t2.name));
|
|
55148
55756
|
|
|
55149
55757
|
// src/mcp/resources.ts
|
|
55150
|
-
import { existsSync as
|
|
55151
|
-
import { basename as basename3, dirname as
|
|
55758
|
+
import { existsSync as existsSync29, readdirSync as readdirSync6, readFileSync as readFileSync25, realpathSync as realpathSync5, statSync as statSync12 } from "fs";
|
|
55759
|
+
import { basename as basename3, dirname as dirname12, join as join44, resolve as resolve9, sep as sep6 } from "path";
|
|
55152
55760
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
55153
55761
|
var SKILL_NAME = "ultra11y";
|
|
55154
55762
|
var URI_SCHEME = "skill://";
|
|
55155
55763
|
function resolveSkillRoot(moduleDir) {
|
|
55156
|
-
const here = moduleDir ??
|
|
55764
|
+
const here = moduleDir ?? dirname12(fileURLToPath4(import.meta.url));
|
|
55157
55765
|
const candidates2 = [resolve9(here, ".."), resolve9(here, "..", "skills", SKILL_NAME), resolve9(here, "..", "..", "skills", SKILL_NAME)];
|
|
55158
|
-
return candidates2.find((dir) =>
|
|
55766
|
+
return candidates2.find((dir) => existsSync29(join44(dir, "SKILL.md")));
|
|
55159
55767
|
}
|
|
55160
55768
|
function listResources(moduleDir) {
|
|
55161
55769
|
const root = resolveSkillRoot(moduleDir);
|
|
55162
55770
|
if (!root) return [];
|
|
55163
55771
|
const out2 = [describe(root, "SKILL.md", `${SKILL_NAME}: the skill`)];
|
|
55164
|
-
const refDir =
|
|
55165
|
-
if (!
|
|
55772
|
+
const refDir = join44(root, "references");
|
|
55773
|
+
if (!existsSync29(refDir)) return out2;
|
|
55166
55774
|
for (const file of readdirSync6(refDir).sort()) {
|
|
55167
55775
|
if (!file.endsWith(".md")) continue;
|
|
55168
|
-
out2.push(describe(root,
|
|
55776
|
+
out2.push(describe(root, join44("references", file), `${SKILL_NAME} reference: ${basename3(file, ".md")}`));
|
|
55169
55777
|
}
|
|
55170
55778
|
return out2;
|
|
55171
55779
|
}
|
|
@@ -55178,36 +55786,36 @@ function readResource(uri, moduleDir) {
|
|
|
55178
55786
|
const rel2 = uri.slice(URI_SCHEME.length);
|
|
55179
55787
|
if (!rel2) throw new ResourceError("empty resource path");
|
|
55180
55788
|
const target = resolve9(root, rel2);
|
|
55181
|
-
const rootReal =
|
|
55789
|
+
const rootReal = realpathSync5(root);
|
|
55182
55790
|
let targetReal;
|
|
55183
55791
|
try {
|
|
55184
|
-
targetReal =
|
|
55792
|
+
targetReal = realpathSync5(target);
|
|
55185
55793
|
} catch {
|
|
55186
55794
|
throw new ResourceError(`no such resource: ${uri}`);
|
|
55187
55795
|
}
|
|
55188
|
-
if (targetReal !== rootReal && !targetReal.startsWith(rootReal +
|
|
55796
|
+
if (targetReal !== rootReal && !targetReal.startsWith(rootReal + sep6)) {
|
|
55189
55797
|
throw new ResourceError(`resource path escapes the skill root: ${uri}`);
|
|
55190
55798
|
}
|
|
55191
|
-
if (!
|
|
55192
|
-
return { uri, mimeType: "text/markdown", text:
|
|
55799
|
+
if (!statSync12(targetReal).isFile()) throw new ResourceError(`not a file: ${uri}`);
|
|
55800
|
+
return { uri, mimeType: "text/markdown", text: readFileSync25(targetReal, "utf8") };
|
|
55193
55801
|
}
|
|
55194
55802
|
var ResourceError = class extends Error {
|
|
55195
55803
|
};
|
|
55196
55804
|
function describe(root, rel2, fallbackTitle) {
|
|
55197
55805
|
const decl = {
|
|
55198
|
-
uri: `${URI_SCHEME}${rel2.split(
|
|
55199
|
-
name: rel2.split(
|
|
55806
|
+
uri: `${URI_SCHEME}${rel2.split(sep6).join("/")}`,
|
|
55807
|
+
name: rel2.split(sep6).join("/"),
|
|
55200
55808
|
title: fallbackTitle,
|
|
55201
55809
|
mimeType: "text/markdown"
|
|
55202
55810
|
};
|
|
55203
|
-
const summary = firstProse(
|
|
55811
|
+
const summary = firstProse(join44(root, rel2));
|
|
55204
55812
|
if (summary) decl.description = summary;
|
|
55205
55813
|
return decl;
|
|
55206
55814
|
}
|
|
55207
55815
|
function firstProse(file) {
|
|
55208
55816
|
let text;
|
|
55209
55817
|
try {
|
|
55210
|
-
text =
|
|
55818
|
+
text = readFileSync25(file, "utf8");
|
|
55211
55819
|
} catch {
|
|
55212
55820
|
return void 0;
|
|
55213
55821
|
}
|
|
@@ -55640,7 +56248,10 @@ Usage:
|
|
|
55640
56248
|
ultra11y snapshot list [--root <dir>] [--json]
|
|
55641
56249
|
ultra11y pages --in <audit.json> [--standard <pack>] [--json] [--lang auto|en|fr] (the per-page criterion grid)
|
|
55642
56250
|
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
|
|
56251
|
+
ultra11y hook --claude-code|--codex|--opencode (internal: the PreToolUse hook; payload on stdin)
|
|
56252
|
+
ultra11y install --claude-code | --codex | --opencode | --agents-md | --all [--project] [--dry-run] [--no-skills]
|
|
56253
|
+
ultra11y uninstall --claude-code | --codex | --opencode | --agents-md | --all [--project]
|
|
56254
|
+
ultra11y status [--json] [--project] (doctor: which agents will run the review by themselves)
|
|
55644
56255
|
ultra11y dev [--port <n>] [--root <dir>] [--standard <pack>] [--lang auto|en|fr] (dev side-car: live overlay + per-page dashboard)
|
|
55645
56256
|
ultra11y dev --next [--port <n>] (write the Next overlay component, then wire one line into your layout)
|
|
55646
56257
|
|
|
@@ -55942,7 +56553,10 @@ var COMMANDS = [
|
|
|
55942
56553
|
"init",
|
|
55943
56554
|
"pack",
|
|
55944
56555
|
"orchestrate",
|
|
55945
|
-
"hook"
|
|
56556
|
+
"hook",
|
|
56557
|
+
"install",
|
|
56558
|
+
"uninstall",
|
|
56559
|
+
"status"
|
|
55946
56560
|
];
|
|
55947
56561
|
function isCommand(s) {
|
|
55948
56562
|
return !!s && COMMANDS.includes(s);
|
|
@@ -56038,7 +56652,16 @@ var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
|
|
|
56038
56652
|
"help",
|
|
56039
56653
|
"version",
|
|
56040
56654
|
"allow-remote",
|
|
56041
|
-
"allow-write"
|
|
56655
|
+
"allow-write",
|
|
56656
|
+
// Harness selectors, shared by `hook` and `install`/`uninstall`/`status`. Without these
|
|
56657
|
+
// the parser warned "unknown flag --claude-code" on stderr on EVERY guarded shell call.
|
|
56658
|
+
"claude-code",
|
|
56659
|
+
"codex",
|
|
56660
|
+
"opencode",
|
|
56661
|
+
"agents-md",
|
|
56662
|
+
"all",
|
|
56663
|
+
"project",
|
|
56664
|
+
"no-skills"
|
|
56042
56665
|
]);
|
|
56043
56666
|
var KNOWN_FLAGS = /* @__PURE__ */ new Set([...VALUE_FLAGS2, ...BOOLEAN_FLAGS]);
|
|
56044
56667
|
var LIST_FLAGS = /* @__PURE__ */ new Set(["include", "exclude", "ext"]);
|
|
@@ -56163,9 +56786,9 @@ async function cmdAudit(p) {
|
|
|
56163
56786
|
const capturesFlag = typeof p.flags.captures === "string" && p.flags.captures ? p.flags.captures : void 0;
|
|
56164
56787
|
const capturesDir = capturesFlag ?? ".ultra11y/captures";
|
|
56165
56788
|
const scopedToDiff = p.flags.changed === true || p.flags.staged === true || since !== void 0;
|
|
56166
|
-
const capturesWanted = p.flags["no-captures"] !== true && !inputs.includes("-") && (capturesFlag !== void 0 ||
|
|
56789
|
+
const capturesWanted = p.flags["no-captures"] !== true && !inputs.includes("-") && (capturesFlag !== void 0 || existsSync30(capturesDir));
|
|
56167
56790
|
const useCaptures = capturesWanted && !scopedToDiff && !inputs.includes(capturesDir);
|
|
56168
|
-
const pagesWanted = p.flags["no-captures"] !== true && !inputs.includes("-") &&
|
|
56791
|
+
const pagesWanted = p.flags["no-captures"] !== true && !inputs.includes("-") && existsSync30(PAGES_DIR);
|
|
56169
56792
|
const usePages = pagesWanted && !scopedToDiff && !inputs.includes(PAGES_DIR);
|
|
56170
56793
|
const auditInputs = [...inputs, ...useCaptures ? [capturesDir] : [], ...usePages ? [PAGES_DIR] : []];
|
|
56171
56794
|
if (useCaptures)
|
|
@@ -56206,10 +56829,10 @@ async function cmdAudit(p) {
|
|
|
56206
56829
|
if (typeof p.flags.out === "string") {
|
|
56207
56830
|
const out2 = p.flags.out;
|
|
56208
56831
|
const asFile = out2.toLowerCase().endsWith(".json");
|
|
56209
|
-
const target = asFile ? out2 :
|
|
56832
|
+
const target = asFile ? out2 : join45(out2, "audit-latest.json");
|
|
56210
56833
|
try {
|
|
56211
|
-
|
|
56212
|
-
|
|
56834
|
+
mkdirSync14(asFile ? dirname13(out2) : out2, { recursive: true });
|
|
56835
|
+
writeFileSync16(target, JSON.stringify(result, null, 2) + "\n");
|
|
56213
56836
|
console.error(lang === "fr" ? `\u2192 audit \xE9crit dans ${target}` : `\u2192 audit written to ${target}`);
|
|
56214
56837
|
} catch {
|
|
56215
56838
|
}
|
|
@@ -56228,7 +56851,7 @@ async function cmdAudit(p) {
|
|
|
56228
56851
|
const baselineFlag = p.flags.baseline;
|
|
56229
56852
|
if (typeof baselineFlag === "string" && baselineFlag) {
|
|
56230
56853
|
let baseline = null;
|
|
56231
|
-
if (
|
|
56854
|
+
if (existsSync30(baselineFlag)) {
|
|
56232
56855
|
try {
|
|
56233
56856
|
const parsed = JSON.parse(readText(baselineFlag));
|
|
56234
56857
|
if (isCurrentAudit(parsed)) baseline = parsed;
|
|
@@ -56282,11 +56905,11 @@ async function cmdDev(p) {
|
|
|
56282
56905
|
return 2;
|
|
56283
56906
|
}
|
|
56284
56907
|
if (p.flags.next === true) {
|
|
56285
|
-
const dir =
|
|
56908
|
+
const dir = join45(root, ".ultra11y", "next");
|
|
56286
56909
|
const rel2 = ".ultra11y/next/overlay.jsx";
|
|
56287
56910
|
try {
|
|
56288
|
-
|
|
56289
|
-
|
|
56911
|
+
mkdirSync14(dir, { recursive: true });
|
|
56912
|
+
writeFileSync16(join45(dir, "overlay.jsx"), nextOverlayComponent(port));
|
|
56290
56913
|
} catch (e) {
|
|
56291
56914
|
console.error(`ultra11y dev: could not write ${rel2}: ${e instanceof Error ? e.message : String(e)}`);
|
|
56292
56915
|
return 1;
|
|
@@ -56374,7 +56997,7 @@ async function cmdSnapshot(p) {
|
|
|
56374
56997
|
2
|
|
56375
56998
|
)
|
|
56376
56999
|
);
|
|
56377
|
-
else if (!snaps.length) console.log(lang === "fr" ? `Aucun instantan\xE9 dans ${
|
|
57000
|
+
else if (!snaps.length) console.log(lang === "fr" ? `Aucun instantan\xE9 dans ${join45(root, PAGES_DIR)}.` : `No snapshot in ${join45(root, PAGES_DIR)}.`);
|
|
56378
57001
|
else for (const s of snaps) console.log(`${s.meta.id} ${s.meta.name} ${s.meta.url}${s.meta.auth ? " [auth]" : ""}`);
|
|
56379
57002
|
return 0;
|
|
56380
57003
|
}
|
|
@@ -56419,12 +57042,12 @@ async function cmdSnapshot(p) {
|
|
|
56419
57042
|
}
|
|
56420
57043
|
if (typeof payload.screenshot === "string" && payload.screenshot) {
|
|
56421
57044
|
try {
|
|
56422
|
-
|
|
57045
|
+
writeFileSync16(join45(dir, "screen.png"), Buffer.from(payload.screenshot, "base64"));
|
|
56423
57046
|
} catch {
|
|
56424
57047
|
console.error("ultra11y snapshot write: the screenshot could not be written \u2014 the pixel tier will be skipped for this page.");
|
|
56425
57048
|
}
|
|
56426
57049
|
}
|
|
56427
|
-
const result = runAudit({ inputs: [
|
|
57050
|
+
const result = runAudit({ inputs: [join45(dir, "dom.html")], onWarn: (m) => console.error(m) });
|
|
56428
57051
|
const failOnRaw = p.flags["fail-on"];
|
|
56429
57052
|
const failOnParsed = parseFailOn(failOnRaw);
|
|
56430
57053
|
if (failOnRaw !== void 0 && failOnParsed === null) {
|
|
@@ -56441,12 +57064,7 @@ async function cmdSnapshot(p) {
|
|
|
56441
57064
|
}
|
|
56442
57065
|
function cmdInit(p) {
|
|
56443
57066
|
const root = repoRoot() ?? process.cwd();
|
|
56444
|
-
|
|
56445
|
-
try {
|
|
56446
|
-
const abs = realpathSync4(engineRel);
|
|
56447
|
-
engineRel = abs.startsWith(root + sep6) ? relative3(root, abs) : abs;
|
|
56448
|
-
} catch {
|
|
56449
|
-
}
|
|
57067
|
+
const engineRel = resolveEnginePath(root);
|
|
56450
57068
|
const failOnParsed = parseFailOn(p.flags["fail-on"]);
|
|
56451
57069
|
if (p.flags["fail-on"] !== void 0 && failOnParsed === null) {
|
|
56452
57070
|
console.error(`ultra11y init: --fail-on must be blocking|major|minor (got "${String(p.flags["fail-on"])}").`);
|
|
@@ -56461,9 +57079,9 @@ function cmdInit(p) {
|
|
|
56461
57079
|
if (want.baseline) {
|
|
56462
57080
|
const inputs = p.positionals.length ? p.positionals : ["."];
|
|
56463
57081
|
const result = runAudit({ inputs, onWarn: (m) => console.error(m) });
|
|
56464
|
-
|
|
56465
|
-
const bp =
|
|
56466
|
-
|
|
57082
|
+
mkdirSync14(join45(root, "audits"), { recursive: true });
|
|
57083
|
+
const bp = join45(root, "audits", "baseline.json");
|
|
57084
|
+
writeFileSync16(bp, JSON.stringify(result, null, 2) + "\n");
|
|
56467
57085
|
wrote.push(bp);
|
|
56468
57086
|
}
|
|
56469
57087
|
if (want.hook) wrote.push(writeHook(root, engineRel, failOn, legacy ? "baseline" : "staged"));
|
|
@@ -56474,17 +57092,74 @@ function cmdInit(p) {
|
|
|
56474
57092
|
return 0;
|
|
56475
57093
|
}
|
|
56476
57094
|
async function cmdHook(p) {
|
|
56477
|
-
|
|
56478
|
-
|
|
57095
|
+
const opencode = p.flags.opencode === true;
|
|
57096
|
+
if (!opencode && p.flags["claude-code"] !== true && p.flags.codex !== true) {
|
|
57097
|
+
console.error("ultra11y hook: expected `hook --claude-code`, `--codex` or `--opencode` (the payload is read on stdin).");
|
|
56479
57098
|
return 2;
|
|
56480
57099
|
}
|
|
56481
57100
|
try {
|
|
56482
57101
|
const decision = decide(JSON.parse(await readStdin()));
|
|
56483
|
-
if (decision)
|
|
57102
|
+
if (!decision) return 0;
|
|
57103
|
+
const out2 = decision.hookSpecificOutput;
|
|
57104
|
+
process.stdout.write(opencode ? `${out2.permissionDecisionReason}
|
|
57105
|
+
|
|
57106
|
+
${out2.additionalContext}` : JSON.stringify(decision));
|
|
56484
57107
|
} catch {
|
|
56485
57108
|
}
|
|
56486
57109
|
return 0;
|
|
56487
57110
|
}
|
|
57111
|
+
var TARGET_FLAGS = "--claude-code | --codex | --opencode | --agents-md | --all";
|
|
57112
|
+
function cmdInstall(p) {
|
|
57113
|
+
const targets = parseTargets(p.flags);
|
|
57114
|
+
if (!targets) {
|
|
57115
|
+
console.error(`ultra11y install: pick at least one target: ${TARGET_FLAGS}`);
|
|
57116
|
+
console.error(" --all covers the agent harnesses; --agents-md is separate because it writes a tracked file into your repository.");
|
|
57117
|
+
return 2;
|
|
57118
|
+
}
|
|
57119
|
+
const dryRun = p.flags["dry-run"] === true;
|
|
57120
|
+
if (dryRun && targets.includes("agents-md")) {
|
|
57121
|
+
console.log(agentsMdBlock(repoRoot() ?? process.cwd()));
|
|
57122
|
+
if (targets.length === 1) return 0;
|
|
57123
|
+
}
|
|
57124
|
+
const results = installForTargets({ targets, project: p.flags.project === true, dryRun, skills: p.flags["no-skills"] !== true });
|
|
57125
|
+
return reportInstall(results, dryRun ? "would wire" : "wired");
|
|
57126
|
+
}
|
|
57127
|
+
function cmdUninstall(p) {
|
|
57128
|
+
const targets = parseTargets(p.flags);
|
|
57129
|
+
if (!targets) {
|
|
57130
|
+
console.error(`ultra11y uninstall: pick at least one target: ${TARGET_FLAGS}`);
|
|
57131
|
+
return 2;
|
|
57132
|
+
}
|
|
57133
|
+
return reportInstall(uninstallForTargets({ targets, project: p.flags.project === true }), "removed");
|
|
57134
|
+
}
|
|
57135
|
+
function reportInstall(results, verb) {
|
|
57136
|
+
let failed2 = 0;
|
|
57137
|
+
for (const r of results) {
|
|
57138
|
+
if (r.error) {
|
|
57139
|
+
failed2++;
|
|
57140
|
+
console.error(`ultra11y: ${r.error}`);
|
|
57141
|
+
continue;
|
|
57142
|
+
}
|
|
57143
|
+
const changed = r.reports.filter((x) => x.changed);
|
|
57144
|
+
if (changed.length === 0) console.log(`ultra11y ${r.target}: already ${verb} (nothing to do)`);
|
|
57145
|
+
for (const x of changed) console.log(`ultra11y ${r.target}: ${verb} ${x.path}${x.backup ? ` (backed up to ${x.backup})` : ""}`);
|
|
57146
|
+
for (const g of r.guidance) console.log(` ${g}`);
|
|
57147
|
+
}
|
|
57148
|
+
return failed2 ? 1 : 0;
|
|
57149
|
+
}
|
|
57150
|
+
function cmdStatus(p) {
|
|
57151
|
+
const rows = statusReport({ project: p.flags.project === true });
|
|
57152
|
+
if (p.flags.json === true) {
|
|
57153
|
+
console.log(JSON.stringify({ version: VERSION, targets: rows }, null, 2));
|
|
57154
|
+
return 0;
|
|
57155
|
+
}
|
|
57156
|
+
console.log(`ultra11y ${VERSION}`);
|
|
57157
|
+
for (const r of rows) {
|
|
57158
|
+
console.log(` ${r.target.padEnd(12)} ${r.wired ? "wired " : "not wired"} ${r.path}`);
|
|
57159
|
+
if (r.note) console.log(` ${"".padEnd(12)} ${r.note}`);
|
|
57160
|
+
}
|
|
57161
|
+
return 0;
|
|
57162
|
+
}
|
|
56488
57163
|
function cmdCriteria(p) {
|
|
56489
57164
|
if (p.flags.generate === true) {
|
|
56490
57165
|
process.stdout.write(renderCriteriaReference());
|
|
@@ -56612,8 +57287,8 @@ async function cmdPrd(p) {
|
|
|
56612
57287
|
return gh2 && gh2.failed > 0 ? 1 : 0;
|
|
56613
57288
|
}
|
|
56614
57289
|
function depsAt(root) {
|
|
56615
|
-
const pkgPath =
|
|
56616
|
-
if (!
|
|
57290
|
+
const pkgPath = join45(root, "package.json");
|
|
57291
|
+
if (!existsSync30(pkgPath)) return {};
|
|
56617
57292
|
try {
|
|
56618
57293
|
const pkg = JSON.parse(readText(pkgPath));
|
|
56619
57294
|
return { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
@@ -56624,8 +57299,8 @@ function depsAt(root) {
|
|
|
56624
57299
|
function engineRefFor(root) {
|
|
56625
57300
|
let ref = process.argv[1] ?? "scripts/ultra11y.mjs";
|
|
56626
57301
|
try {
|
|
56627
|
-
const abs =
|
|
56628
|
-
ref = abs.startsWith(root +
|
|
57302
|
+
const abs = realpathSync6(ref);
|
|
57303
|
+
ref = abs.startsWith(root + sep7) ? relative4(root, abs) : abs;
|
|
56629
57304
|
} catch {
|
|
56630
57305
|
}
|
|
56631
57306
|
return ref;
|
|
@@ -56639,24 +57314,24 @@ function cmdRender(p) {
|
|
|
56639
57314
|
console.error(`ultra11y render: --runner must be playwright|cypress|auto (got "${forced}").`);
|
|
56640
57315
|
return 2;
|
|
56641
57316
|
}
|
|
56642
|
-
const detected = detectE2eRunner(depsAt(root), (f) =>
|
|
57317
|
+
const detected = detectE2eRunner(depsAt(root), (f) => existsSync30(join45(root, f)));
|
|
56643
57318
|
const runners = forced && forced !== "auto" ? [forced] : detected;
|
|
56644
57319
|
if (!runners.length) {
|
|
56645
57320
|
console.error(e2eSetupPlan([], {}, lang));
|
|
56646
57321
|
return 1;
|
|
56647
57322
|
}
|
|
56648
57323
|
const engineRef = engineRefFor(root);
|
|
56649
|
-
const dir =
|
|
57324
|
+
const dir = join45(root, ".ultra11y", "e2e");
|
|
56650
57325
|
const paths = {};
|
|
56651
57326
|
try {
|
|
56652
|
-
|
|
57327
|
+
mkdirSync14(dir, { recursive: true });
|
|
56653
57328
|
if (runners.includes("playwright")) {
|
|
56654
|
-
|
|
57329
|
+
writeFileSync16(join45(dir, "playwright.mjs"), playwrightFixture(engineRef));
|
|
56655
57330
|
paths.playwright = ".ultra11y/e2e/playwright.mjs";
|
|
56656
57331
|
}
|
|
56657
57332
|
if (runners.includes("cypress")) {
|
|
56658
|
-
|
|
56659
|
-
|
|
57333
|
+
writeFileSync16(join45(dir, "cypress-plugin.mjs"), cypressPlugin(engineRef));
|
|
57334
|
+
writeFileSync16(join45(dir, "cypress-commands.mjs"), cypressCommands());
|
|
56660
57335
|
paths.cypressPlugin = ".ultra11y/e2e/cypress-plugin.mjs";
|
|
56661
57336
|
paths.cypressCommands = ".ultra11y/e2e/cypress-commands.mjs";
|
|
56662
57337
|
}
|
|
@@ -56671,7 +57346,7 @@ function cmdRender(p) {
|
|
|
56671
57346
|
if (p.flags.scaffold === true) {
|
|
56672
57347
|
const out2 = typeof p.flags.out === "string" && p.flags.out ? p.flags.out : "ultra11y-render.tsx";
|
|
56673
57348
|
try {
|
|
56674
|
-
|
|
57349
|
+
writeFileSync16(out2, ssrHarness());
|
|
56675
57350
|
} catch (e) {
|
|
56676
57351
|
console.error(`ultra11y render: could not write ${out2}: ${e instanceof Error ? e.message : String(e)}`);
|
|
56677
57352
|
return 1;
|
|
@@ -56685,29 +57360,29 @@ Fill in COMPONENTS, run it (e.g. npx tsx ${out2}), then: node scripts/ultra11y.m
|
|
|
56685
57360
|
}
|
|
56686
57361
|
if (p.flags.setup === true) {
|
|
56687
57362
|
const rel2 = ".ultra11y/capture-setup.mjs";
|
|
56688
|
-
const out2 =
|
|
57363
|
+
const out2 = join45(root, rel2);
|
|
56689
57364
|
try {
|
|
56690
|
-
|
|
56691
|
-
|
|
57365
|
+
mkdirSync14(dirname13(out2), { recursive: true });
|
|
57366
|
+
writeFileSync16(out2, captureSetup());
|
|
56692
57367
|
} catch (e) {
|
|
56693
57368
|
console.error(`ultra11y render: could not write ${out2}: ${e instanceof Error ? e.message : String(e)}`);
|
|
56694
57369
|
return 1;
|
|
56695
57370
|
}
|
|
56696
57371
|
let setupDeps = {};
|
|
56697
|
-
const setupPkg =
|
|
56698
|
-
if (
|
|
57372
|
+
const setupPkg = join45(root, "package.json");
|
|
57373
|
+
if (existsSync30(setupPkg)) {
|
|
56699
57374
|
try {
|
|
56700
57375
|
const pkg = JSON.parse(readText(setupPkg));
|
|
56701
57376
|
setupDeps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
56702
57377
|
} catch {
|
|
56703
57378
|
}
|
|
56704
57379
|
}
|
|
56705
|
-
const tr = detectTestRunner(setupDeps, (f) =>
|
|
57380
|
+
const tr = detectTestRunner(setupDeps, (f) => existsSync30(join45(root, f)));
|
|
56706
57381
|
console.log(captureSetupPlan(tr, rel2, lang));
|
|
56707
57382
|
const gaLine = ".ultra11y/captures/*.html text eol=lf linguist-generated=true";
|
|
56708
|
-
const gaPath =
|
|
57383
|
+
const gaPath = join45(root, ".gitattributes");
|
|
56709
57384
|
try {
|
|
56710
|
-
const existing =
|
|
57385
|
+
const existing = existsSync30(gaPath) ? readFileSync26(gaPath, "utf8") : "";
|
|
56711
57386
|
if (!existing.includes(".ultra11y/captures/")) {
|
|
56712
57387
|
appendFileSync(gaPath, (existing && !existing.endsWith("\n") ? "\n" : "") + gaLine + "\n");
|
|
56713
57388
|
console.log(lang === "fr" ? `.gitattributes : ajout\xE9 \xAB ${gaLine} \xBB` : `.gitattributes: added "${gaLine}"`);
|
|
@@ -56715,8 +57390,8 @@ Fill in COMPONENTS, run it (e.g. npx tsx ${out2}), then: node scripts/ultra11y.m
|
|
|
56715
57390
|
} catch {
|
|
56716
57391
|
}
|
|
56717
57392
|
try {
|
|
56718
|
-
const giPath =
|
|
56719
|
-
if (
|
|
57393
|
+
const giPath = join45(root, ".gitignore");
|
|
57394
|
+
if (existsSync30(giPath) && /^\s*\/?\.ultra11y(\/\**)?\/?\s*$/m.test(readFileSync26(giPath, "utf8")))
|
|
56720
57395
|
console.error(
|
|
56721
57396
|
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/").'
|
|
56722
57397
|
);
|
|
@@ -56726,8 +57401,8 @@ Fill in COMPONENTS, run it (e.g. npx tsx ${out2}), then: node scripts/ultra11y.m
|
|
|
56726
57401
|
}
|
|
56727
57402
|
if (p.flags.storybook === true || typeof p.flags.storybook === "string") {
|
|
56728
57403
|
const sbDir = p.positionals[0] ?? "storybook-static";
|
|
56729
|
-
const indexPath =
|
|
56730
|
-
if (!
|
|
57404
|
+
const indexPath = existsSync30(join45(sbDir, "index.json")) ? join45(sbDir, "index.json") : join45(sbDir, "stories.json");
|
|
57405
|
+
if (!existsSync30(indexPath)) {
|
|
56731
57406
|
console.error(
|
|
56732
57407
|
lang === "fr" ? `ultra11y render : aucun index Storybook (index.json/stories.json) dans ${sbDir}.` : `ultra11y render: no Storybook index (index.json/stories.json) in ${sbDir}.`
|
|
56733
57408
|
);
|
|
@@ -56737,7 +57412,7 @@ Fill in COMPONENTS, run it (e.g. npx tsx ${out2}), then: node scripts/ultra11y.m
|
|
|
56737
57412
|
const provById = new Map(stories.map((s) => [s.id, storyProvenance(s)]));
|
|
56738
57413
|
const capturesFlag = typeof p.flags.captures === "string" && p.flags.captures ? p.flags.captures : void 0;
|
|
56739
57414
|
const htmlDir = capturesFlag ?? sbDir;
|
|
56740
|
-
const htmlFiles =
|
|
57415
|
+
const htmlFiles = existsSync30(htmlDir) ? discover([htmlDir]).files.filter((f) => /\.html?$/i.test(f)) : [];
|
|
56741
57416
|
const outDir = ".ultra11y/captures";
|
|
56742
57417
|
let attributed = 0;
|
|
56743
57418
|
let skipped = 0;
|
|
@@ -56759,8 +57434,8 @@ Fill in COMPONENTS, run it (e.g. npx tsx ${out2}), then: node scripts/ultra11y.m
|
|
|
56759
57434
|
continue;
|
|
56760
57435
|
}
|
|
56761
57436
|
try {
|
|
56762
|
-
|
|
56763
|
-
|
|
57437
|
+
mkdirSync14(outDir, { recursive: true });
|
|
57438
|
+
writeFileSync16(join45(outDir, `${hitId}.html`), `${formatCaptureComment(prov)}
|
|
56764
57439
|
${raw}${raw.endsWith("\n") ? "" : "\n"}`);
|
|
56765
57440
|
attributed++;
|
|
56766
57441
|
} catch {
|
|
@@ -56782,11 +57457,11 @@ ${raw}${raw.endsWith("\n") ? "" : "\n"}`);
|
|
|
56782
57457
|
}
|
|
56783
57458
|
if (p.flags.coverage === true) {
|
|
56784
57459
|
const capturesFlag = typeof p.flags.captures === "string" && p.flags.captures ? p.flags.captures : void 0;
|
|
56785
|
-
const capturesDir = capturesFlag ??
|
|
57460
|
+
const capturesDir = capturesFlag ?? join45(root, ".ultra11y/captures");
|
|
56786
57461
|
const graphExt = [...GRAPH_ONLY_EXT, ...asList(p.flags.ext) ?? []];
|
|
56787
57462
|
const sourceFiles = discover([root], { include: asList(p.flags.include), exclude: asList(p.flags.exclude), ext: graphExt }).files;
|
|
56788
57463
|
const graph = buildGraphStreaming(sourceFiles);
|
|
56789
|
-
const capFiles =
|
|
57464
|
+
const capFiles = existsSync30(capturesDir) ? discover([capturesDir]).files : [];
|
|
56790
57465
|
const entries = capFiles.map((f) => ({ file: toPosix(f), provenance: parseCaptureProvenance(readText(f)) }));
|
|
56791
57466
|
const cov = computeCaptureCoverage(graph, entries);
|
|
56792
57467
|
if (p.flags.json) console.log(JSON.stringify(cov, null, 2));
|
|
@@ -56794,15 +57469,15 @@ ${raw}${raw.endsWith("\n") ? "" : "\n"}`);
|
|
|
56794
57469
|
return 0;
|
|
56795
57470
|
}
|
|
56796
57471
|
let deps = {};
|
|
56797
|
-
const pkgPath =
|
|
56798
|
-
if (
|
|
57472
|
+
const pkgPath = join45(root, "package.json");
|
|
57473
|
+
if (existsSync30(pkgPath)) {
|
|
56799
57474
|
try {
|
|
56800
57475
|
const pkg = JSON.parse(readText(pkgPath));
|
|
56801
57476
|
deps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
56802
57477
|
} catch {
|
|
56803
57478
|
}
|
|
56804
57479
|
}
|
|
56805
|
-
const detection = detectFrameworks(deps, (f) =>
|
|
57480
|
+
const detection = detectFrameworks(deps, (f) => existsSync30(join45(root, f)));
|
|
56806
57481
|
if (p.flags.json) console.log(JSON.stringify(detection, null, 2));
|
|
56807
57482
|
else console.log(renderPlan(detection, lang));
|
|
56808
57483
|
return 0;
|
|
@@ -57001,9 +57676,9 @@ function applyAdjudicationFile(p, adj, lang) {
|
|
|
57001
57676
|
return 1;
|
|
57002
57677
|
}
|
|
57003
57678
|
const out2 = typeof p.flags.out === "string" ? p.flags.out : ".";
|
|
57004
|
-
|
|
57005
|
-
const auditPath =
|
|
57006
|
-
|
|
57679
|
+
mkdirSync14(out2, { recursive: true });
|
|
57680
|
+
const auditPath = join45(out2, "audit-latest.json");
|
|
57681
|
+
writeFileSync16(auditPath, JSON.stringify(r.audit, null, 2) + "\n");
|
|
57007
57682
|
if (p.flags.json) console.log(JSON.stringify({ ok: true, auditPath, applied: r.applied, stillManual: r.stillManual, grounding: r.grounding }, null, 2));
|
|
57008
57683
|
else
|
|
57009
57684
|
console.log(
|
|
@@ -57073,7 +57748,7 @@ async function cmdScan(p) {
|
|
|
57073
57748
|
return 0;
|
|
57074
57749
|
}
|
|
57075
57750
|
for (const target of p.positionals.filter((a) => a !== "-")) {
|
|
57076
|
-
if (/^https?:\/\//i.test(target) ||
|
|
57751
|
+
if (/^https?:\/\//i.test(target) || existsSync30(target)) continue;
|
|
57077
57752
|
console.error(
|
|
57078
57753
|
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.`
|
|
57079
57754
|
);
|
|
@@ -57198,12 +57873,12 @@ async function cmdScan(p) {
|
|
|
57198
57873
|
audit2 = parsed;
|
|
57199
57874
|
}
|
|
57200
57875
|
const merged = mergeDynamic(audit2, dynamic, lang);
|
|
57201
|
-
|
|
57202
|
-
|
|
57876
|
+
mkdirSync14(out2, { recursive: true });
|
|
57877
|
+
writeFileSync16(join45(out2, "audit-latest.json"), JSON.stringify(merged, null, 2) + "\n");
|
|
57203
57878
|
if (p.flags.json) console.log(JSON.stringify(merged, null, 2));
|
|
57204
57879
|
else
|
|
57205
57880
|
console.log(
|
|
57206
|
-
lang === "fr" ? `Audit statique + dynamique fusionn\xE9 \u2192 ${
|
|
57881
|
+
lang === "fr" ? `Audit statique + dynamique fusionn\xE9 \u2192 ${join45(out2, "audit-latest.json")} (${merged.conformancePct}% r\xE9ussite, ${merged.findings.length} findings).` : `Static + dynamic audit merged \u2192 ${join45(out2, "audit-latest.json")} (${merged.conformancePct}% pass rate, ${merged.findings.length} findings).`
|
|
57207
57882
|
);
|
|
57208
57883
|
return 0;
|
|
57209
57884
|
}
|
|
@@ -57381,9 +58056,9 @@ function cmdOrchestrate(p) {
|
|
|
57381
58056
|
);
|
|
57382
58057
|
return 2;
|
|
57383
58058
|
}
|
|
57384
|
-
const engineAbs =
|
|
58059
|
+
const engineAbs = realpathSync6(fileURLToPath5(import.meta.url));
|
|
57385
58060
|
if (p.flags.list === true) {
|
|
57386
|
-
if (!
|
|
58061
|
+
if (!existsSync30(runFlag)) {
|
|
57387
58062
|
console.error(`ultra11y orchestrate: run dir not found: ${runFlag}.`);
|
|
57388
58063
|
return 2;
|
|
57389
58064
|
}
|
|
@@ -57410,7 +58085,7 @@ function cmdOrchestrate(p) {
|
|
|
57410
58085
|
);
|
|
57411
58086
|
} else {
|
|
57412
58087
|
console.log(
|
|
57413
|
-
lang === "fr" ? `Suivez ${
|
|
58088
|
+
lang === "fr" ? `Suivez ${join45(runFlag, "orchestration", "RUNBOOK.md")} s\xE9quentiellement (chemin \xE9co).` : `Follow ${join45(runFlag, "orchestration", "RUNBOOK.md")} sequentially (the eco path).`
|
|
57414
58089
|
);
|
|
57415
58090
|
}
|
|
57416
58091
|
if (p.flags.phase === void 0 && workflows.length === 0 && p.flags.eco !== true) {
|
|
@@ -57496,6 +58171,12 @@ async function main(argv) {
|
|
|
57496
58171
|
return cmdMcp(p);
|
|
57497
58172
|
case "hook":
|
|
57498
58173
|
return cmdHook(p);
|
|
58174
|
+
case "install":
|
|
58175
|
+
return cmdInstall(p);
|
|
58176
|
+
case "uninstall":
|
|
58177
|
+
return cmdUninstall(p);
|
|
58178
|
+
case "status":
|
|
58179
|
+
return cmdStatus(p);
|
|
57499
58180
|
default:
|
|
57500
58181
|
console.error(`ultra11y: "${p.command}" is not implemented yet`);
|
|
57501
58182
|
return 1;
|
|
@@ -57506,7 +58187,7 @@ function isInvokedDirectly() {
|
|
|
57506
58187
|
if (argv1 === void 0) return false;
|
|
57507
58188
|
const modulePath = fileURLToPath5(import.meta.url);
|
|
57508
58189
|
try {
|
|
57509
|
-
if (
|
|
58190
|
+
if (realpathSync6(argv1) === realpathSync6(modulePath)) return true;
|
|
57510
58191
|
} catch {
|
|
57511
58192
|
}
|
|
57512
58193
|
return import.meta.url === pathToFileURL3(argv1).href;
|