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.
Files changed (36) hide show
  1. package/.agents/plugins/marketplace.json +12 -0
  2. package/.claude-plugin/marketplace.json +1 -1
  3. package/.claude-plugin/plugin.json +1 -1
  4. package/.codex-plugin/plugin.json +21 -0
  5. package/.opencode/plugins/ultra11y.js +102 -0
  6. package/README.md +34 -16
  7. package/hooks/hooks.json +1 -1
  8. package/hooks/pre-tool-use.mjs +19 -8
  9. package/package.json +6 -2
  10. package/scripts/sync-version.mjs +12 -4
  11. package/scripts/ultra11y.mjs +831 -150
  12. package/scripts/vendor/wcag-2.2-fr.json +1 -1
  13. package/scripts/vendor/wcag-2.2-sc-universe.json +1 -1
  14. package/scripts/verify-skill-bundle.mjs +134 -1
  15. package/skills/review-a11y/SKILL.md +5 -1
  16. package/skills/review-a11y/scripts/ultra11y.mjs +831 -150
  17. package/skills/ultra11y/SKILL.md +2 -1
  18. package/skills/ultra11y/references/automation.md +9 -4
  19. package/skills/ultra11y/references/harnesses.md +117 -0
  20. package/skills/ultra11y/scripts/ultra11y.mjs +831 -150
  21. package/src/cli.ts +116 -17
  22. package/src/data/wcag-universe.json +1 -1
  23. package/src/hook.ts +0 -0
  24. package/src/init.ts +26 -2
  25. package/src/install/agents-md.ts +78 -0
  26. package/src/install/claude-code.ts +61 -0
  27. package/src/install/codex.ts +166 -0
  28. package/src/install/index.ts +204 -0
  29. package/src/install/json-edit.ts +97 -0
  30. package/src/install/opencode.ts +90 -0
  31. package/src/install/paths.ts +132 -0
  32. package/src/install/text-edit.ts +54 -0
  33. package/src/types.ts +1 -1
  34. package/src/vendor/codeindex-engine.d.mts +3 -3
  35. package/src/vendor/codeindex-engine.mjs +23 -3
  36. package/src/vendor/engine.meta.json +5 -5
@@ -1,12 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/cli.ts
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";
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.26.0";
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-07-31 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).",
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.22.0";
18890
+ ENGINE_VERSION = "2.24.2";
18891
18891
  SCHEMA_VERSION2 = 5;
18892
- EXTRACTOR_VERSION = 11;
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 projectRoot = opts.projectRoot ?? "file://" + scan2.root.replace(/\\/g, "/");
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, projectRoot);
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 !== "Bash") return null;
47833
- const command = payload.tool_input?.command;
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 join35 } from "path";
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: [join35(dir, "dom.html")] }) };
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: [join35(root, ".ultra11y/pages")] });
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 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";
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 join36 } from "path";
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 ${join36(run2, "audit-latest.json")} --out ${run2}`
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 = join36(runAbs, "orchestration", `${ph.name}.workflow.mjs`);
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: \`${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>\`).
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: \`${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>\`).
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 \`${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>\`.
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: "${join36(runAbs, "orchestration", "<p>.workflow.mjs")}" })\` \u2014 you stay the sole writer either way.
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 = join37(run2, "ADJUDICATE.todo.json");
54857
+ const adjPath = join42(run2, "ADJUDICATE.todo.json");
54250
54858
  let adjIds = [];
54251
54859
  let adjReady = false;
54252
- if (existsSync21(adjPath)) {
54860
+ if (existsSync27(adjPath)) {
54253
54861
  try {
54254
- const f = JSON.parse(readFileSync17(adjPath, "utf8"));
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 = join37(run2, "VERIFY.todo.json");
54870
+ const verPath = join42(run2, "VERIFY.todo.json");
54263
54871
  let verIds = [];
54264
54872
  let verReady = false;
54265
- if (existsSync21(verPath)) {
54873
+ if (existsSync27(verPath)) {
54266
54874
  try {
54267
- const items = JSON.parse(readFileSync17(verPath, "utf8"));
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 ${join37(run2, "audit-latest.json")} --out ${run2}`
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 (!existsSync21(run2)) {
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 = join37(run2, "orchestration");
54324
- const agentsDir = join37(orchDir, "agents");
54325
- mkdirSync11(join37(orchDir, "out"), { recursive: true });
54326
- mkdirSync11(agentsDir, { recursive: true });
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 = join37(agentsDir, `${name2}.md`);
54331
- writeFileSync14(p, content);
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 = join37(orchDir, `${ph.name}.workflow.mjs`);
54338
- if (existsSync21(stale)) {
54339
- rmSync4(stale, { force: true });
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 = join37(orchDir, `${ph.name}.workflow.mjs`);
54353
- writeFileSync14(p, phaseWorkflowScript(ph, run2, engineAbs, BATCH_SIZE));
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 = join37(orchDir, "RUNBOOK.md");
54358
- writeFileSync14(rb, runbookMd(phases, run2, engineAbs));
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 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";
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 (!existsSync22(abs)) throw new ToolError(`project root not found: ${abs}`);
54417
- if (!statSync10(abs).isDirectory()) throw new ToolError(`\`cwd\` is not a directory: ${abs}`);
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 = join38(cwd, ".ultra11yrc.json");
54562
- if (!existsSync22(file)) {
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(readFileSync18(file, "utf8"));
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(join38(cwd, ".github/workflows/a11y.yml"));
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 (!existsSync22(file)) throw new ToolError(`report file not found: ${file}`);
54626
- return readFileSync18(file, "utf8");
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 : join38(cwd, raw);
55249
+ const target = isAbsolute3(raw) ? raw : join43(cwd, raw);
54642
55250
  let real;
54643
55251
  try {
54644
- real = realpathSync2(target);
55252
+ real = realpathSync4(target);
54645
55253
  } catch {
54646
55254
  throw new ToolError(`no such file: ${raw}`);
54647
55255
  }
54648
- const root = realpathSync2(cwd);
54649
- if (real !== root && !real.startsWith(root + sep4)) {
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 = statSync10(real);
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 = readFileSync18(real, "utf8").split("\n");
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 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";
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 ?? dirname10(fileURLToPath4(import.meta.url));
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) => existsSync23(join39(dir, "SKILL.md")));
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 = join39(root, "references");
55165
- if (!existsSync23(refDir)) return out2;
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, join39("references", file), `${SKILL_NAME} reference: ${basename3(file, ".md")}`));
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 = realpathSync3(root);
55789
+ const rootReal = realpathSync5(root);
55182
55790
  let targetReal;
55183
55791
  try {
55184
- targetReal = realpathSync3(target);
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 + sep5)) {
55796
+ if (targetReal !== rootReal && !targetReal.startsWith(rootReal + sep6)) {
55189
55797
  throw new ResourceError(`resource path escapes the skill root: ${uri}`);
55190
55798
  }
55191
- if (!statSync11(targetReal).isFile()) throw new ResourceError(`not a file: ${uri}`);
55192
- return { uri, mimeType: "text/markdown", text: readFileSync19(targetReal, "utf8") };
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(sep5).join("/")}`,
55199
- name: rel2.split(sep5).join("/"),
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(join39(root, rel2));
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 = readFileSync19(file, "utf8");
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 (internal: the plugin's PreToolUse hook; payload on stdin)
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 || existsSync24(capturesDir));
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("-") && existsSync24(PAGES_DIR);
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 : join40(out2, "audit-latest.json");
56832
+ const target = asFile ? out2 : join45(out2, "audit-latest.json");
56210
56833
  try {
56211
- mkdirSync12(asFile ? dirname11(out2) : out2, { recursive: true });
56212
- writeFileSync15(target, JSON.stringify(result, null, 2) + "\n");
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 (existsSync24(baselineFlag)) {
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 = join40(root, ".ultra11y", "next");
56908
+ const dir = join45(root, ".ultra11y", "next");
56286
56909
  const rel2 = ".ultra11y/next/overlay.jsx";
56287
56910
  try {
56288
- mkdirSync12(dir, { recursive: true });
56289
- writeFileSync15(join40(dir, "overlay.jsx"), nextOverlayComponent(port));
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 ${join40(root, PAGES_DIR)}.` : `No snapshot in ${join40(root, PAGES_DIR)}.`);
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
- writeFileSync15(join40(dir, "screen.png"), Buffer.from(payload.screenshot, "base64"));
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: [join40(dir, "dom.html")], onWarn: (m) => console.error(m) });
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
- let engineRel = process.argv[1] ?? "scripts/ultra11y.mjs";
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
- mkdirSync12(join40(root, "audits"), { recursive: true });
56465
- const bp = join40(root, "audits", "baseline.json");
56466
- writeFileSync15(bp, JSON.stringify(result, null, 2) + "\n");
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
- if (p.flags["claude-code"] !== true) {
56478
- console.error("ultra11y hook: expected `hook --claude-code` (the payload is read on stdin).");
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) process.stdout.write(JSON.stringify(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 = join40(root, "package.json");
56616
- if (!existsSync24(pkgPath)) return {};
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 = realpathSync4(ref);
56628
- ref = abs.startsWith(root + sep6) ? relative3(root, abs) : abs;
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) => existsSync24(join40(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 = join40(root, ".ultra11y", "e2e");
57324
+ const dir = join45(root, ".ultra11y", "e2e");
56650
57325
  const paths = {};
56651
57326
  try {
56652
- mkdirSync12(dir, { recursive: true });
57327
+ mkdirSync14(dir, { recursive: true });
56653
57328
  if (runners.includes("playwright")) {
56654
- writeFileSync15(join40(dir, "playwright.mjs"), playwrightFixture(engineRef));
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
- writeFileSync15(join40(dir, "cypress-plugin.mjs"), cypressPlugin(engineRef));
56659
- writeFileSync15(join40(dir, "cypress-commands.mjs"), cypressCommands());
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
- writeFileSync15(out2, ssrHarness());
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 = join40(root, rel2);
57363
+ const out2 = join45(root, rel2);
56689
57364
  try {
56690
- mkdirSync12(dirname11(out2), { recursive: true });
56691
- writeFileSync15(out2, captureSetup());
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 = join40(root, "package.json");
56698
- if (existsSync24(setupPkg)) {
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) => existsSync24(join40(root, 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 = join40(root, ".gitattributes");
57383
+ const gaPath = join45(root, ".gitattributes");
56709
57384
  try {
56710
- const existing = existsSync24(gaPath) ? readFileSync20(gaPath, "utf8") : "";
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 = join40(root, ".gitignore");
56719
- if (existsSync24(giPath) && /^\s*\/?\.ultra11y(\/\**)?\/?\s*$/m.test(readFileSync20(giPath, "utf8")))
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 = existsSync24(join40(sbDir, "index.json")) ? join40(sbDir, "index.json") : join40(sbDir, "stories.json");
56730
- if (!existsSync24(indexPath)) {
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 = existsSync24(htmlDir) ? discover([htmlDir]).files.filter((f) => /\.html?$/i.test(f)) : [];
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
- mkdirSync12(outDir, { recursive: true });
56763
- writeFileSync15(join40(outDir, `${hitId}.html`), `${formatCaptureComment(prov)}
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 ?? join40(root, ".ultra11y/captures");
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 = existsSync24(capturesDir) ? discover([capturesDir]).files : [];
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 = join40(root, "package.json");
56798
- if (existsSync24(pkgPath)) {
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) => existsSync24(join40(root, 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
- mkdirSync12(out2, { recursive: true });
57005
- const auditPath = join40(out2, "audit-latest.json");
57006
- writeFileSync15(auditPath, JSON.stringify(r.audit, null, 2) + "\n");
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) || existsSync24(target)) continue;
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
- mkdirSync12(out2, { recursive: true });
57202
- writeFileSync15(join40(out2, "audit-latest.json"), JSON.stringify(merged, null, 2) + "\n");
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 ${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).`
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 = realpathSync4(fileURLToPath5(import.meta.url));
58059
+ const engineAbs = realpathSync6(fileURLToPath5(import.meta.url));
57385
58060
  if (p.flags.list === true) {
57386
- if (!existsSync24(runFlag)) {
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 ${join40(runFlag, "orchestration", "RUNBOOK.md")} s\xE9quentiellement (chemin \xE9co).` : `Follow ${join40(runFlag, "orchestration", "RUNBOOK.md")} sequentially (the eco path).`
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 (realpathSync4(argv1) === realpathSync4(modulePath)) return true;
58190
+ if (realpathSync6(argv1) === realpathSync6(modulePath)) return true;
57510
58191
  } catch {
57511
58192
  }
57512
58193
  return import.meta.url === pathToFileURL3(argv1).href;