ultra11y 2.27.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 +807 -146
- package/scripts/verify-skill-bundle.mjs +134 -1
- package/skills/review-a11y/SKILL.md +5 -1
- package/skills/review-a11y/scripts/ultra11y.mjs +807 -146
- 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 +807 -146
- package/src/cli.ts +116 -17
- 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/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
|
|
@@ -33142,7 +33142,7 @@ function findWord(line, name2) {
|
|
|
33142
33142
|
}
|
|
33143
33143
|
}
|
|
33144
33144
|
function renderScip(scan2, opts = {}) {
|
|
33145
|
-
const
|
|
33145
|
+
const projectRoot2 = opts.projectRoot ?? "file://" + scan2.root.replace(/\\/g, "/");
|
|
33146
33146
|
const toolVersion = opts.toolVersion ?? ENGINE_VERSION;
|
|
33147
33147
|
const docs = scan2.files.filter((f) => f.kind === "code" && f.symbols.length > 0);
|
|
33148
33148
|
const docDefs = /* @__PURE__ */ new Map();
|
|
@@ -33229,7 +33229,7 @@ function renderScip(scan2, opts = {}) {
|
|
|
33229
33229
|
pushString(toolInfo, F_TOOL_VERSION, toolVersion);
|
|
33230
33230
|
const metadata2 = new Bytes();
|
|
33231
33231
|
pushMessage(metadata2, F_META_TOOL_INFO, toolInfo);
|
|
33232
|
-
pushString(metadata2, F_META_PROJECT_ROOT,
|
|
33232
|
+
pushString(metadata2, F_META_PROJECT_ROOT, projectRoot2);
|
|
33233
33233
|
pushVarintField(metadata2, F_META_TEXT_ENCODING, TEXT_ENCODING_UTF8);
|
|
33234
33234
|
let total = 0;
|
|
33235
33235
|
for (const d of documents) total += d.length;
|
|
@@ -47763,6 +47763,24 @@ function matchGitIntent(command) {
|
|
|
47763
47763
|
for (const [intent, re] of INTENTS) if (re.test(command)) return intent;
|
|
47764
47764
|
return null;
|
|
47765
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
|
+
}
|
|
47766
47784
|
function git2(cwd, args2) {
|
|
47767
47785
|
try {
|
|
47768
47786
|
return execFileSync2("git", args2, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
@@ -47849,8 +47867,8 @@ function decide(payload, deps = {}) {
|
|
|
47849
47867
|
const env = deps.env ?? process.env;
|
|
47850
47868
|
const audit2 = deps.audit ?? realAudit;
|
|
47851
47869
|
const seen = deps.seen ?? firstSighting;
|
|
47852
|
-
if (payload.tool_name
|
|
47853
|
-
const command = payload.tool_input
|
|
47870
|
+
if (!isShellTool(payload.tool_name)) return null;
|
|
47871
|
+
const command = commandOf(payload.tool_input);
|
|
47854
47872
|
if (!command) return null;
|
|
47855
47873
|
if (env.SKIP_A11Y || env.ULTRA11Y_HOOK === "off") return null;
|
|
47856
47874
|
const intent = matchGitIntent(command);
|
|
@@ -53046,9 +53064,9 @@ function fixSummary(r, lang = "fr", write = false) {
|
|
|
53046
53064
|
}
|
|
53047
53065
|
|
|
53048
53066
|
// src/init.ts
|
|
53049
|
-
import { writeFileSync as writeFileSync13, mkdirSync as mkdirSync10, chmodSync } from "fs";
|
|
53067
|
+
import { writeFileSync as writeFileSync13, mkdirSync as mkdirSync10, chmodSync, realpathSync as realpathSync2 } from "fs";
|
|
53050
53068
|
import { execFileSync as execFileSync5 } from "child_process";
|
|
53051
|
-
import { join as join34 } from "path";
|
|
53069
|
+
import { join as join34, relative as relative3, sep as sep4 } from "path";
|
|
53052
53070
|
var EN_SEV = { bloquant: "blocking", majeur: "major", mineur: "minor" };
|
|
53053
53071
|
function repoRoot() {
|
|
53054
53072
|
try {
|
|
@@ -53057,6 +53075,19 @@ function repoRoot() {
|
|
|
53057
53075
|
return null;
|
|
53058
53076
|
}
|
|
53059
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
|
+
}
|
|
53060
53091
|
function stagedHookScript(enginePath, failOn) {
|
|
53061
53092
|
const sev = EN_SEV[failOn];
|
|
53062
53093
|
return `#!/bin/sh
|
|
@@ -53159,6 +53190,563 @@ function writeCi(root, enginePath, failOn) {
|
|
|
53159
53190
|
return path;
|
|
53160
53191
|
}
|
|
53161
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
|
+
|
|
53162
53750
|
// src/output.ts
|
|
53163
53751
|
var STR = {
|
|
53164
53752
|
fr: {
|
|
@@ -53456,7 +54044,7 @@ function stepSummary(result, opts = {}) {
|
|
|
53456
54044
|
|
|
53457
54045
|
// src/dev.ts
|
|
53458
54046
|
import { createServer } from "http";
|
|
53459
|
-
import { join as
|
|
54047
|
+
import { join as join40 } from "path";
|
|
53460
54048
|
var DEV_DEFAULT_PORT = 4111;
|
|
53461
54049
|
function criterionLabel3(f, standard) {
|
|
53462
54050
|
if (isCore(standard)) return `WCAG ${f.criteriaId}`;
|
|
@@ -53722,13 +54310,13 @@ function auditCollected(root, payload) {
|
|
|
53722
54310
|
...payload.axtree ? { axtree: payload.axtree } : {},
|
|
53723
54311
|
...payload.css ? { css: payload.css } : {}
|
|
53724
54312
|
});
|
|
53725
|
-
return { ok: true, result: runAudit({ inputs: [
|
|
54313
|
+
return { ok: true, result: runAudit({ inputs: [join40(dir, "dom.html")] }) };
|
|
53726
54314
|
}
|
|
53727
54315
|
function projectPages(root) {
|
|
53728
54316
|
const snaps = readSnapshots(root);
|
|
53729
54317
|
if (!snaps.length) return { result: null, pages: [] };
|
|
53730
54318
|
const scope = pageScopesFrom(snaps);
|
|
53731
|
-
const result = runAudit({ inputs: [
|
|
54319
|
+
const result = runAudit({ inputs: [join40(root, ".ultra11y/pages")] });
|
|
53732
54320
|
result.scope.pages = scope;
|
|
53733
54321
|
attributePages(result, scope);
|
|
53734
54322
|
return { result, pages: derivePages(result, scope) };
|
|
@@ -54053,11 +54641,11 @@ function e2eSetupPlan(runners, paths, lang = "en") {
|
|
|
54053
54641
|
}
|
|
54054
54642
|
|
|
54055
54643
|
// src/orchestrate.ts
|
|
54056
|
-
import { existsSync as
|
|
54057
|
-
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";
|
|
54058
54646
|
|
|
54059
54647
|
// src/orchestrate-templates.ts
|
|
54060
|
-
import { join as
|
|
54648
|
+
import { join as join41 } from "path";
|
|
54061
54649
|
var ONE_WRITER_FOOTER = `
|
|
54062
54650
|
## Return, don't write
|
|
54063
54651
|
|
|
@@ -54129,7 +54717,7 @@ var PHASE_SPECS = {
|
|
|
54129
54717
|
title: "Adjudicate",
|
|
54130
54718
|
schema: ADJUDICATE_SCHEMA,
|
|
54131
54719
|
description: (n) => `Adjudicate the ${n} residual judgment criterion(ia) of an ultra11y audit (fan-out, fail-closed fold)`,
|
|
54132
|
-
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}`
|
|
54133
54721
|
},
|
|
54134
54722
|
"verify-report": {
|
|
54135
54723
|
role: "refuter",
|
|
@@ -54151,7 +54739,7 @@ function toBatches(ids, batchSize) {
|
|
|
54151
54739
|
}
|
|
54152
54740
|
function phaseWorkflowScript(ph, runAbs, engineAbs, batchSize) {
|
|
54153
54741
|
const spec = phaseSpec(ph.name);
|
|
54154
|
-
const scriptPath =
|
|
54742
|
+
const scriptPath = join41(runAbs, "orchestration", `${ph.name}.workflow.mjs`);
|
|
54155
54743
|
const meta2 = { name: `ultra11y-${ph.name}`, description: spec.description(ph.items), phases: [{ title: spec.title }] };
|
|
54156
54744
|
return [
|
|
54157
54745
|
`export const meta = ${JSON.stringify(meta2)}`,
|
|
@@ -54195,7 +54783,7 @@ function agentContracts(runAbs, engineAbs) {
|
|
|
54195
54783
|
|
|
54196
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.
|
|
54197
54785
|
|
|
54198
|
-
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>\`).
|
|
54199
54787
|
|
|
54200
54788
|
For EACH of your criteria:
|
|
54201
54789
|
|
|
@@ -54213,7 +54801,7 @@ ${footer}`,
|
|
|
54213
54801
|
|
|
54214
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.
|
|
54215
54803
|
|
|
54216
|
-
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>\`).
|
|
54217
54805
|
|
|
54218
54806
|
For EACH of your entries:
|
|
54219
54807
|
|
|
@@ -54249,14 +54837,14 @@ ${status}
|
|
|
54249
54837
|
|
|
54250
54838
|
## The loop (play every role yourself, one item at a time)
|
|
54251
54839
|
|
|
54252
|
-
1. **Audit** (if not done): \`${engine} audit "<globs>" --graph --out ${runAbs}\` \u2192 \`${
|
|
54253
|
-
2. **Adjudicate the residual criteria** \u2014 \`${engine} verify --manual --in ${
|
|
54254
|
-
3. **Report**: \`${engine} report --in ${
|
|
54255
|
-
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>\`.
|
|
54256
54844
|
5. **Gate**: \`${engine} check --report <the report .md> --semantic\` must exit 0 before presenting anything.
|
|
54257
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.
|
|
54258
54846
|
|
|
54259
|
-
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.
|
|
54260
54848
|
`;
|
|
54261
54849
|
}
|
|
54262
54850
|
|
|
@@ -54266,12 +54854,12 @@ var SMALL_WORKLIST = 3;
|
|
|
54266
54854
|
var BATCH_SIZE = 8;
|
|
54267
54855
|
function listPhases(runDir, engineAbs) {
|
|
54268
54856
|
const run2 = resolve7(runDir);
|
|
54269
|
-
const adjPath =
|
|
54857
|
+
const adjPath = join42(run2, "ADJUDICATE.todo.json");
|
|
54270
54858
|
let adjIds = [];
|
|
54271
54859
|
let adjReady = false;
|
|
54272
|
-
if (
|
|
54860
|
+
if (existsSync27(adjPath)) {
|
|
54273
54861
|
try {
|
|
54274
|
-
const f = JSON.parse(
|
|
54862
|
+
const f = JSON.parse(readFileSync23(adjPath, "utf8"));
|
|
54275
54863
|
if (f && f.kind === "adjudication" && Array.isArray(f.items)) {
|
|
54276
54864
|
adjReady = true;
|
|
54277
54865
|
adjIds = f.items.map((i2) => i2.criteriaId);
|
|
@@ -54279,12 +54867,12 @@ function listPhases(runDir, engineAbs) {
|
|
|
54279
54867
|
} catch {
|
|
54280
54868
|
}
|
|
54281
54869
|
}
|
|
54282
|
-
const verPath =
|
|
54870
|
+
const verPath = join42(run2, "VERIFY.todo.json");
|
|
54283
54871
|
let verIds = [];
|
|
54284
54872
|
let verReady = false;
|
|
54285
|
-
if (
|
|
54873
|
+
if (existsSync27(verPath)) {
|
|
54286
54874
|
try {
|
|
54287
|
-
const items = JSON.parse(
|
|
54875
|
+
const items = JSON.parse(readFileSync23(verPath, "utf8"));
|
|
54288
54876
|
if (Array.isArray(items)) {
|
|
54289
54877
|
verReady = true;
|
|
54290
54878
|
verIds = items.map((i2) => String(i2.n));
|
|
@@ -54299,7 +54887,7 @@ function listPhases(runDir, engineAbs) {
|
|
|
54299
54887
|
worklist: adjPath,
|
|
54300
54888
|
items: adjIds.length,
|
|
54301
54889
|
ids: adjIds,
|
|
54302
|
-
prerequisite: `node ${engineAbs} verify --manual --in ${
|
|
54890
|
+
prerequisite: `node ${engineAbs} verify --manual --in ${join42(run2, "audit-latest.json")} --out ${run2}`
|
|
54303
54891
|
},
|
|
54304
54892
|
{
|
|
54305
54893
|
name: "verify-report",
|
|
@@ -54313,7 +54901,7 @@ function listPhases(runDir, engineAbs) {
|
|
|
54313
54901
|
}
|
|
54314
54902
|
function orchestrateRun(runDir, engineAbs, opts = {}) {
|
|
54315
54903
|
const run2 = resolve7(runDir);
|
|
54316
|
-
if (!
|
|
54904
|
+
if (!existsSync27(run2)) {
|
|
54317
54905
|
return { exitCode: 2, written: [], notices: [], errors: [`run dir not found: ${run2}`], phases: [] };
|
|
54318
54906
|
}
|
|
54319
54907
|
const phases = listPhases(run2, engineAbs);
|
|
@@ -54340,23 +54928,23 @@ function orchestrateRun(runDir, engineAbs, opts = {}) {
|
|
|
54340
54928
|
}
|
|
54341
54929
|
selected = [ph];
|
|
54342
54930
|
}
|
|
54343
|
-
const orchDir =
|
|
54344
|
-
const agentsDir =
|
|
54345
|
-
|
|
54346
|
-
|
|
54931
|
+
const orchDir = join42(run2, "orchestration");
|
|
54932
|
+
const agentsDir = join42(orchDir, "agents");
|
|
54933
|
+
mkdirSync13(join42(orchDir, "out"), { recursive: true });
|
|
54934
|
+
mkdirSync13(agentsDir, { recursive: true });
|
|
54347
54935
|
const written = [];
|
|
54348
54936
|
const notices = [];
|
|
54349
54937
|
for (const [name2, content] of Object.entries(agentContracts(run2, engineAbs))) {
|
|
54350
|
-
const p =
|
|
54351
|
-
|
|
54938
|
+
const p = join42(agentsDir, `${name2}.md`);
|
|
54939
|
+
writeFileSync15(p, content);
|
|
54352
54940
|
written.push(p);
|
|
54353
54941
|
}
|
|
54354
54942
|
const emitted = new Set(opts.eco ? [] : selected.filter((p) => p.items > 0).map((p) => p.name));
|
|
54355
54943
|
for (const ph of phases) {
|
|
54356
54944
|
if (emitted.has(ph.name)) continue;
|
|
54357
|
-
const stale =
|
|
54358
|
-
if (
|
|
54359
|
-
|
|
54945
|
+
const stale = join42(orchDir, `${ph.name}.workflow.mjs`);
|
|
54946
|
+
if (existsSync27(stale)) {
|
|
54947
|
+
rmSync7(stale, { force: true });
|
|
54360
54948
|
notices.push(`phase "${ph.name}": stale workflow removed \u2014 its worklist ${ph.ready ? "is now empty" : "no longer exists"}.`);
|
|
54361
54949
|
}
|
|
54362
54950
|
}
|
|
@@ -54369,13 +54957,13 @@ function orchestrateRun(runDir, engineAbs, opts = {}) {
|
|
|
54369
54957
|
if (ph.items <= SMALL_WORKLIST) {
|
|
54370
54958
|
notices.push(`phase "${ph.name}": only ${ph.items} item(s) \u2014 the sequential --eco path is equivalent and cheaper.`);
|
|
54371
54959
|
}
|
|
54372
|
-
const p =
|
|
54373
|
-
|
|
54960
|
+
const p = join42(orchDir, `${ph.name}.workflow.mjs`);
|
|
54961
|
+
writeFileSync15(p, phaseWorkflowScript(ph, run2, engineAbs, BATCH_SIZE));
|
|
54374
54962
|
written.push(p);
|
|
54375
54963
|
}
|
|
54376
54964
|
}
|
|
54377
|
-
const rb =
|
|
54378
|
-
|
|
54965
|
+
const rb = join42(orchDir, "RUNBOOK.md");
|
|
54966
|
+
writeFileSync15(rb, runbookMd(phases, run2, engineAbs));
|
|
54379
54967
|
written.push(rb);
|
|
54380
54968
|
return { exitCode: 0, written, notices, errors: [], phases };
|
|
54381
54969
|
}
|
|
@@ -54384,8 +54972,8 @@ function orchestrateRun(runDir, engineAbs, opts = {}) {
|
|
|
54384
54972
|
import { createInterface as createInterface2 } from "readline";
|
|
54385
54973
|
|
|
54386
54974
|
// src/mcp/handlers.ts
|
|
54387
|
-
import { existsSync as
|
|
54388
|
-
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";
|
|
54389
54977
|
|
|
54390
54978
|
// src/project-lock.ts
|
|
54391
54979
|
var chains = /* @__PURE__ */ new Map();
|
|
@@ -54433,8 +55021,8 @@ function requiredCwd(args2, defaults) {
|
|
|
54433
55021
|
const cwd = str2(args2.cwd) ?? defaults.defaultCwd;
|
|
54434
55022
|
if (!cwd) throw new ToolError("`cwd` is required: an absolute path to the project root.");
|
|
54435
55023
|
const abs = resolve8(cwd);
|
|
54436
|
-
if (!
|
|
54437
|
-
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}`);
|
|
54438
55026
|
return abs;
|
|
54439
55027
|
}
|
|
54440
55028
|
function standardOf(args2) {
|
|
@@ -54578,13 +55166,13 @@ function handlePackCheck(args2, cwd) {
|
|
|
54578
55166
|
return { cwd, ...res };
|
|
54579
55167
|
}
|
|
54580
55168
|
function handleSampleCheck(cwd) {
|
|
54581
|
-
const file =
|
|
54582
|
-
if (!
|
|
55169
|
+
const file = join43(cwd, ".ultra11yrc.json");
|
|
55170
|
+
if (!existsSync28(file)) {
|
|
54583
55171
|
throw new ToolError(`no .ultra11yrc.json at ${cwd} \u2014 a page sample must be declared before it can be linted.`);
|
|
54584
55172
|
}
|
|
54585
55173
|
let raw;
|
|
54586
55174
|
try {
|
|
54587
|
-
raw = JSON.parse(
|
|
55175
|
+
raw = JSON.parse(readFileSync24(file, "utf8"));
|
|
54588
55176
|
} catch (e) {
|
|
54589
55177
|
throw new ToolError(`.ultra11yrc.json is not valid JSON: ${e.message}`);
|
|
54590
55178
|
}
|
|
@@ -54622,7 +55210,7 @@ function handleInit(args2, cwd) {
|
|
|
54622
55210
|
const enginePath = resolve8("scripts/ultra11y.mjs");
|
|
54623
55211
|
const written = [];
|
|
54624
55212
|
if (bool(args2.hook)) written.push(writeHook(cwd, enginePath, failOn));
|
|
54625
|
-
if (bool(args2.ci)) written.push(
|
|
55213
|
+
if (bool(args2.ci)) written.push(join43(cwd, ".github/workflows/a11y.yml"));
|
|
54626
55214
|
if (!written.length) {
|
|
54627
55215
|
throw new ToolError("nothing to install \u2014 pass hook:true and/or ci:true.");
|
|
54628
55216
|
}
|
|
@@ -54642,8 +55230,8 @@ function reportText(args2, tool) {
|
|
|
54642
55230
|
if (inline) return inline;
|
|
54643
55231
|
if (!file) throw new ToolError(`\`report_text\` is required \u2014 the report markdown for ultra11y_${tool} to work on.`);
|
|
54644
55232
|
if (!isAbsolute3(file)) throw new ToolError("`report_file` must be an absolute path.");
|
|
54645
|
-
if (!
|
|
54646
|
-
return
|
|
55233
|
+
if (!existsSync28(file)) throw new ToolError(`report file not found: ${file}`);
|
|
55234
|
+
return readFileSync24(file, "utf8");
|
|
54647
55235
|
}
|
|
54648
55236
|
function handleCriteria(args2) {
|
|
54649
55237
|
const lang = langOf(args2);
|
|
@@ -54658,21 +55246,21 @@ function handleCriteria(args2) {
|
|
|
54658
55246
|
function handleRead(args2, cwd) {
|
|
54659
55247
|
const raw = str2(args2.path);
|
|
54660
55248
|
if (!raw) throw new ToolError("`path` is required \u2014 relative to the project root, or an absolute path inside it.");
|
|
54661
|
-
const target = isAbsolute3(raw) ? raw :
|
|
55249
|
+
const target = isAbsolute3(raw) ? raw : join43(cwd, raw);
|
|
54662
55250
|
let real;
|
|
54663
55251
|
try {
|
|
54664
|
-
real =
|
|
55252
|
+
real = realpathSync4(target);
|
|
54665
55253
|
} catch {
|
|
54666
55254
|
throw new ToolError(`no such file: ${raw}`);
|
|
54667
55255
|
}
|
|
54668
|
-
const root =
|
|
54669
|
-
if (real !== root && !real.startsWith(root +
|
|
55256
|
+
const root = realpathSync4(cwd);
|
|
55257
|
+
if (real !== root && !real.startsWith(root + sep5)) {
|
|
54670
55258
|
throw new ToolError(`path is outside the project: ${raw}. Use your own file tool for anything else.`);
|
|
54671
55259
|
}
|
|
54672
|
-
const st =
|
|
55260
|
+
const st = statSync11(real);
|
|
54673
55261
|
if (!st.isFile()) throw new ToolError(`not a file: ${raw}`);
|
|
54674
55262
|
if (st.size > MAX_READ_BYTES) throw new ToolError(`file is too large to read (${st.size} bytes): ${raw}`);
|
|
54675
|
-
const lines =
|
|
55263
|
+
const lines = readFileSync24(real, "utf8").split("\n");
|
|
54676
55264
|
const total = lines.length;
|
|
54677
55265
|
const start2 = Math.max(1, Math.floor(num2(args2.start_line) ?? 1));
|
|
54678
55266
|
if (start2 > total) throw new ToolError(`start_line ${start2} is past the end of the file (${total} lines).`);
|
|
@@ -55167,25 +55755,25 @@ function str3(v) {
|
|
|
55167
55755
|
var DECLARED = new Set([...TOOLS2, ...WRITE_TOOLS].map((t2) => t2.name));
|
|
55168
55756
|
|
|
55169
55757
|
// src/mcp/resources.ts
|
|
55170
|
-
import { existsSync as
|
|
55171
|
-
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";
|
|
55172
55760
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
55173
55761
|
var SKILL_NAME = "ultra11y";
|
|
55174
55762
|
var URI_SCHEME = "skill://";
|
|
55175
55763
|
function resolveSkillRoot(moduleDir) {
|
|
55176
|
-
const here = moduleDir ??
|
|
55764
|
+
const here = moduleDir ?? dirname12(fileURLToPath4(import.meta.url));
|
|
55177
55765
|
const candidates2 = [resolve9(here, ".."), resolve9(here, "..", "skills", SKILL_NAME), resolve9(here, "..", "..", "skills", SKILL_NAME)];
|
|
55178
|
-
return candidates2.find((dir) =>
|
|
55766
|
+
return candidates2.find((dir) => existsSync29(join44(dir, "SKILL.md")));
|
|
55179
55767
|
}
|
|
55180
55768
|
function listResources(moduleDir) {
|
|
55181
55769
|
const root = resolveSkillRoot(moduleDir);
|
|
55182
55770
|
if (!root) return [];
|
|
55183
55771
|
const out2 = [describe(root, "SKILL.md", `${SKILL_NAME}: the skill`)];
|
|
55184
|
-
const refDir =
|
|
55185
|
-
if (!
|
|
55772
|
+
const refDir = join44(root, "references");
|
|
55773
|
+
if (!existsSync29(refDir)) return out2;
|
|
55186
55774
|
for (const file of readdirSync6(refDir).sort()) {
|
|
55187
55775
|
if (!file.endsWith(".md")) continue;
|
|
55188
|
-
out2.push(describe(root,
|
|
55776
|
+
out2.push(describe(root, join44("references", file), `${SKILL_NAME} reference: ${basename3(file, ".md")}`));
|
|
55189
55777
|
}
|
|
55190
55778
|
return out2;
|
|
55191
55779
|
}
|
|
@@ -55198,36 +55786,36 @@ function readResource(uri, moduleDir) {
|
|
|
55198
55786
|
const rel2 = uri.slice(URI_SCHEME.length);
|
|
55199
55787
|
if (!rel2) throw new ResourceError("empty resource path");
|
|
55200
55788
|
const target = resolve9(root, rel2);
|
|
55201
|
-
const rootReal =
|
|
55789
|
+
const rootReal = realpathSync5(root);
|
|
55202
55790
|
let targetReal;
|
|
55203
55791
|
try {
|
|
55204
|
-
targetReal =
|
|
55792
|
+
targetReal = realpathSync5(target);
|
|
55205
55793
|
} catch {
|
|
55206
55794
|
throw new ResourceError(`no such resource: ${uri}`);
|
|
55207
55795
|
}
|
|
55208
|
-
if (targetReal !== rootReal && !targetReal.startsWith(rootReal +
|
|
55796
|
+
if (targetReal !== rootReal && !targetReal.startsWith(rootReal + sep6)) {
|
|
55209
55797
|
throw new ResourceError(`resource path escapes the skill root: ${uri}`);
|
|
55210
55798
|
}
|
|
55211
|
-
if (!
|
|
55212
|
-
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") };
|
|
55213
55801
|
}
|
|
55214
55802
|
var ResourceError = class extends Error {
|
|
55215
55803
|
};
|
|
55216
55804
|
function describe(root, rel2, fallbackTitle) {
|
|
55217
55805
|
const decl = {
|
|
55218
|
-
uri: `${URI_SCHEME}${rel2.split(
|
|
55219
|
-
name: rel2.split(
|
|
55806
|
+
uri: `${URI_SCHEME}${rel2.split(sep6).join("/")}`,
|
|
55807
|
+
name: rel2.split(sep6).join("/"),
|
|
55220
55808
|
title: fallbackTitle,
|
|
55221
55809
|
mimeType: "text/markdown"
|
|
55222
55810
|
};
|
|
55223
|
-
const summary = firstProse(
|
|
55811
|
+
const summary = firstProse(join44(root, rel2));
|
|
55224
55812
|
if (summary) decl.description = summary;
|
|
55225
55813
|
return decl;
|
|
55226
55814
|
}
|
|
55227
55815
|
function firstProse(file) {
|
|
55228
55816
|
let text;
|
|
55229
55817
|
try {
|
|
55230
|
-
text =
|
|
55818
|
+
text = readFileSync25(file, "utf8");
|
|
55231
55819
|
} catch {
|
|
55232
55820
|
return void 0;
|
|
55233
55821
|
}
|
|
@@ -55660,7 +56248,10 @@ Usage:
|
|
|
55660
56248
|
ultra11y snapshot list [--root <dir>] [--json]
|
|
55661
56249
|
ultra11y pages --in <audit.json> [--standard <pack>] [--json] [--lang auto|en|fr] (the per-page criterion grid)
|
|
55662
56250
|
ultra11y mcp [--transport stdio|http] [--cwd <dir>] [--allow-write] [--port <n>] [--bind <addr>] [--allow-remote] [--allow-origin <o>] [--max-response-bytes <n>]
|
|
55663
|
-
ultra11y hook --claude-code
|
|
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)
|
|
55664
56255
|
ultra11y dev [--port <n>] [--root <dir>] [--standard <pack>] [--lang auto|en|fr] (dev side-car: live overlay + per-page dashboard)
|
|
55665
56256
|
ultra11y dev --next [--port <n>] (write the Next overlay component, then wire one line into your layout)
|
|
55666
56257
|
|
|
@@ -55962,7 +56553,10 @@ var COMMANDS = [
|
|
|
55962
56553
|
"init",
|
|
55963
56554
|
"pack",
|
|
55964
56555
|
"orchestrate",
|
|
55965
|
-
"hook"
|
|
56556
|
+
"hook",
|
|
56557
|
+
"install",
|
|
56558
|
+
"uninstall",
|
|
56559
|
+
"status"
|
|
55966
56560
|
];
|
|
55967
56561
|
function isCommand(s) {
|
|
55968
56562
|
return !!s && COMMANDS.includes(s);
|
|
@@ -56058,7 +56652,16 @@ var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
|
|
|
56058
56652
|
"help",
|
|
56059
56653
|
"version",
|
|
56060
56654
|
"allow-remote",
|
|
56061
|
-
"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"
|
|
56062
56665
|
]);
|
|
56063
56666
|
var KNOWN_FLAGS = /* @__PURE__ */ new Set([...VALUE_FLAGS2, ...BOOLEAN_FLAGS]);
|
|
56064
56667
|
var LIST_FLAGS = /* @__PURE__ */ new Set(["include", "exclude", "ext"]);
|
|
@@ -56183,9 +56786,9 @@ async function cmdAudit(p) {
|
|
|
56183
56786
|
const capturesFlag = typeof p.flags.captures === "string" && p.flags.captures ? p.flags.captures : void 0;
|
|
56184
56787
|
const capturesDir = capturesFlag ?? ".ultra11y/captures";
|
|
56185
56788
|
const scopedToDiff = p.flags.changed === true || p.flags.staged === true || since !== void 0;
|
|
56186
|
-
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));
|
|
56187
56790
|
const useCaptures = capturesWanted && !scopedToDiff && !inputs.includes(capturesDir);
|
|
56188
|
-
const pagesWanted = p.flags["no-captures"] !== true && !inputs.includes("-") &&
|
|
56791
|
+
const pagesWanted = p.flags["no-captures"] !== true && !inputs.includes("-") && existsSync30(PAGES_DIR);
|
|
56189
56792
|
const usePages = pagesWanted && !scopedToDiff && !inputs.includes(PAGES_DIR);
|
|
56190
56793
|
const auditInputs = [...inputs, ...useCaptures ? [capturesDir] : [], ...usePages ? [PAGES_DIR] : []];
|
|
56191
56794
|
if (useCaptures)
|
|
@@ -56226,10 +56829,10 @@ async function cmdAudit(p) {
|
|
|
56226
56829
|
if (typeof p.flags.out === "string") {
|
|
56227
56830
|
const out2 = p.flags.out;
|
|
56228
56831
|
const asFile = out2.toLowerCase().endsWith(".json");
|
|
56229
|
-
const target = asFile ? out2 :
|
|
56832
|
+
const target = asFile ? out2 : join45(out2, "audit-latest.json");
|
|
56230
56833
|
try {
|
|
56231
|
-
|
|
56232
|
-
|
|
56834
|
+
mkdirSync14(asFile ? dirname13(out2) : out2, { recursive: true });
|
|
56835
|
+
writeFileSync16(target, JSON.stringify(result, null, 2) + "\n");
|
|
56233
56836
|
console.error(lang === "fr" ? `\u2192 audit \xE9crit dans ${target}` : `\u2192 audit written to ${target}`);
|
|
56234
56837
|
} catch {
|
|
56235
56838
|
}
|
|
@@ -56248,7 +56851,7 @@ async function cmdAudit(p) {
|
|
|
56248
56851
|
const baselineFlag = p.flags.baseline;
|
|
56249
56852
|
if (typeof baselineFlag === "string" && baselineFlag) {
|
|
56250
56853
|
let baseline = null;
|
|
56251
|
-
if (
|
|
56854
|
+
if (existsSync30(baselineFlag)) {
|
|
56252
56855
|
try {
|
|
56253
56856
|
const parsed = JSON.parse(readText(baselineFlag));
|
|
56254
56857
|
if (isCurrentAudit(parsed)) baseline = parsed;
|
|
@@ -56302,11 +56905,11 @@ async function cmdDev(p) {
|
|
|
56302
56905
|
return 2;
|
|
56303
56906
|
}
|
|
56304
56907
|
if (p.flags.next === true) {
|
|
56305
|
-
const dir =
|
|
56908
|
+
const dir = join45(root, ".ultra11y", "next");
|
|
56306
56909
|
const rel2 = ".ultra11y/next/overlay.jsx";
|
|
56307
56910
|
try {
|
|
56308
|
-
|
|
56309
|
-
|
|
56911
|
+
mkdirSync14(dir, { recursive: true });
|
|
56912
|
+
writeFileSync16(join45(dir, "overlay.jsx"), nextOverlayComponent(port));
|
|
56310
56913
|
} catch (e) {
|
|
56311
56914
|
console.error(`ultra11y dev: could not write ${rel2}: ${e instanceof Error ? e.message : String(e)}`);
|
|
56312
56915
|
return 1;
|
|
@@ -56394,7 +56997,7 @@ async function cmdSnapshot(p) {
|
|
|
56394
56997
|
2
|
|
56395
56998
|
)
|
|
56396
56999
|
);
|
|
56397
|
-
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)}.`);
|
|
56398
57001
|
else for (const s of snaps) console.log(`${s.meta.id} ${s.meta.name} ${s.meta.url}${s.meta.auth ? " [auth]" : ""}`);
|
|
56399
57002
|
return 0;
|
|
56400
57003
|
}
|
|
@@ -56439,12 +57042,12 @@ async function cmdSnapshot(p) {
|
|
|
56439
57042
|
}
|
|
56440
57043
|
if (typeof payload.screenshot === "string" && payload.screenshot) {
|
|
56441
57044
|
try {
|
|
56442
|
-
|
|
57045
|
+
writeFileSync16(join45(dir, "screen.png"), Buffer.from(payload.screenshot, "base64"));
|
|
56443
57046
|
} catch {
|
|
56444
57047
|
console.error("ultra11y snapshot write: the screenshot could not be written \u2014 the pixel tier will be skipped for this page.");
|
|
56445
57048
|
}
|
|
56446
57049
|
}
|
|
56447
|
-
const result = runAudit({ inputs: [
|
|
57050
|
+
const result = runAudit({ inputs: [join45(dir, "dom.html")], onWarn: (m) => console.error(m) });
|
|
56448
57051
|
const failOnRaw = p.flags["fail-on"];
|
|
56449
57052
|
const failOnParsed = parseFailOn(failOnRaw);
|
|
56450
57053
|
if (failOnRaw !== void 0 && failOnParsed === null) {
|
|
@@ -56461,12 +57064,7 @@ async function cmdSnapshot(p) {
|
|
|
56461
57064
|
}
|
|
56462
57065
|
function cmdInit(p) {
|
|
56463
57066
|
const root = repoRoot() ?? process.cwd();
|
|
56464
|
-
|
|
56465
|
-
try {
|
|
56466
|
-
const abs = realpathSync4(engineRel);
|
|
56467
|
-
engineRel = abs.startsWith(root + sep6) ? relative3(root, abs) : abs;
|
|
56468
|
-
} catch {
|
|
56469
|
-
}
|
|
57067
|
+
const engineRel = resolveEnginePath(root);
|
|
56470
57068
|
const failOnParsed = parseFailOn(p.flags["fail-on"]);
|
|
56471
57069
|
if (p.flags["fail-on"] !== void 0 && failOnParsed === null) {
|
|
56472
57070
|
console.error(`ultra11y init: --fail-on must be blocking|major|minor (got "${String(p.flags["fail-on"])}").`);
|
|
@@ -56481,9 +57079,9 @@ function cmdInit(p) {
|
|
|
56481
57079
|
if (want.baseline) {
|
|
56482
57080
|
const inputs = p.positionals.length ? p.positionals : ["."];
|
|
56483
57081
|
const result = runAudit({ inputs, onWarn: (m) => console.error(m) });
|
|
56484
|
-
|
|
56485
|
-
const bp =
|
|
56486
|
-
|
|
57082
|
+
mkdirSync14(join45(root, "audits"), { recursive: true });
|
|
57083
|
+
const bp = join45(root, "audits", "baseline.json");
|
|
57084
|
+
writeFileSync16(bp, JSON.stringify(result, null, 2) + "\n");
|
|
56487
57085
|
wrote.push(bp);
|
|
56488
57086
|
}
|
|
56489
57087
|
if (want.hook) wrote.push(writeHook(root, engineRel, failOn, legacy ? "baseline" : "staged"));
|
|
@@ -56494,17 +57092,74 @@ function cmdInit(p) {
|
|
|
56494
57092
|
return 0;
|
|
56495
57093
|
}
|
|
56496
57094
|
async function cmdHook(p) {
|
|
56497
|
-
|
|
56498
|
-
|
|
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).");
|
|
56499
57098
|
return 2;
|
|
56500
57099
|
}
|
|
56501
57100
|
try {
|
|
56502
57101
|
const decision = decide(JSON.parse(await readStdin()));
|
|
56503
|
-
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));
|
|
56504
57107
|
} catch {
|
|
56505
57108
|
}
|
|
56506
57109
|
return 0;
|
|
56507
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
|
+
}
|
|
56508
57163
|
function cmdCriteria(p) {
|
|
56509
57164
|
if (p.flags.generate === true) {
|
|
56510
57165
|
process.stdout.write(renderCriteriaReference());
|
|
@@ -56632,8 +57287,8 @@ async function cmdPrd(p) {
|
|
|
56632
57287
|
return gh2 && gh2.failed > 0 ? 1 : 0;
|
|
56633
57288
|
}
|
|
56634
57289
|
function depsAt(root) {
|
|
56635
|
-
const pkgPath =
|
|
56636
|
-
if (!
|
|
57290
|
+
const pkgPath = join45(root, "package.json");
|
|
57291
|
+
if (!existsSync30(pkgPath)) return {};
|
|
56637
57292
|
try {
|
|
56638
57293
|
const pkg = JSON.parse(readText(pkgPath));
|
|
56639
57294
|
return { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
@@ -56644,8 +57299,8 @@ function depsAt(root) {
|
|
|
56644
57299
|
function engineRefFor(root) {
|
|
56645
57300
|
let ref = process.argv[1] ?? "scripts/ultra11y.mjs";
|
|
56646
57301
|
try {
|
|
56647
|
-
const abs =
|
|
56648
|
-
ref = abs.startsWith(root +
|
|
57302
|
+
const abs = realpathSync6(ref);
|
|
57303
|
+
ref = abs.startsWith(root + sep7) ? relative4(root, abs) : abs;
|
|
56649
57304
|
} catch {
|
|
56650
57305
|
}
|
|
56651
57306
|
return ref;
|
|
@@ -56659,24 +57314,24 @@ function cmdRender(p) {
|
|
|
56659
57314
|
console.error(`ultra11y render: --runner must be playwright|cypress|auto (got "${forced}").`);
|
|
56660
57315
|
return 2;
|
|
56661
57316
|
}
|
|
56662
|
-
const detected = detectE2eRunner(depsAt(root), (f) =>
|
|
57317
|
+
const detected = detectE2eRunner(depsAt(root), (f) => existsSync30(join45(root, f)));
|
|
56663
57318
|
const runners = forced && forced !== "auto" ? [forced] : detected;
|
|
56664
57319
|
if (!runners.length) {
|
|
56665
57320
|
console.error(e2eSetupPlan([], {}, lang));
|
|
56666
57321
|
return 1;
|
|
56667
57322
|
}
|
|
56668
57323
|
const engineRef = engineRefFor(root);
|
|
56669
|
-
const dir =
|
|
57324
|
+
const dir = join45(root, ".ultra11y", "e2e");
|
|
56670
57325
|
const paths = {};
|
|
56671
57326
|
try {
|
|
56672
|
-
|
|
57327
|
+
mkdirSync14(dir, { recursive: true });
|
|
56673
57328
|
if (runners.includes("playwright")) {
|
|
56674
|
-
|
|
57329
|
+
writeFileSync16(join45(dir, "playwright.mjs"), playwrightFixture(engineRef));
|
|
56675
57330
|
paths.playwright = ".ultra11y/e2e/playwright.mjs";
|
|
56676
57331
|
}
|
|
56677
57332
|
if (runners.includes("cypress")) {
|
|
56678
|
-
|
|
56679
|
-
|
|
57333
|
+
writeFileSync16(join45(dir, "cypress-plugin.mjs"), cypressPlugin(engineRef));
|
|
57334
|
+
writeFileSync16(join45(dir, "cypress-commands.mjs"), cypressCommands());
|
|
56680
57335
|
paths.cypressPlugin = ".ultra11y/e2e/cypress-plugin.mjs";
|
|
56681
57336
|
paths.cypressCommands = ".ultra11y/e2e/cypress-commands.mjs";
|
|
56682
57337
|
}
|
|
@@ -56691,7 +57346,7 @@ function cmdRender(p) {
|
|
|
56691
57346
|
if (p.flags.scaffold === true) {
|
|
56692
57347
|
const out2 = typeof p.flags.out === "string" && p.flags.out ? p.flags.out : "ultra11y-render.tsx";
|
|
56693
57348
|
try {
|
|
56694
|
-
|
|
57349
|
+
writeFileSync16(out2, ssrHarness());
|
|
56695
57350
|
} catch (e) {
|
|
56696
57351
|
console.error(`ultra11y render: could not write ${out2}: ${e instanceof Error ? e.message : String(e)}`);
|
|
56697
57352
|
return 1;
|
|
@@ -56705,29 +57360,29 @@ Fill in COMPONENTS, run it (e.g. npx tsx ${out2}), then: node scripts/ultra11y.m
|
|
|
56705
57360
|
}
|
|
56706
57361
|
if (p.flags.setup === true) {
|
|
56707
57362
|
const rel2 = ".ultra11y/capture-setup.mjs";
|
|
56708
|
-
const out2 =
|
|
57363
|
+
const out2 = join45(root, rel2);
|
|
56709
57364
|
try {
|
|
56710
|
-
|
|
56711
|
-
|
|
57365
|
+
mkdirSync14(dirname13(out2), { recursive: true });
|
|
57366
|
+
writeFileSync16(out2, captureSetup());
|
|
56712
57367
|
} catch (e) {
|
|
56713
57368
|
console.error(`ultra11y render: could not write ${out2}: ${e instanceof Error ? e.message : String(e)}`);
|
|
56714
57369
|
return 1;
|
|
56715
57370
|
}
|
|
56716
57371
|
let setupDeps = {};
|
|
56717
|
-
const setupPkg =
|
|
56718
|
-
if (
|
|
57372
|
+
const setupPkg = join45(root, "package.json");
|
|
57373
|
+
if (existsSync30(setupPkg)) {
|
|
56719
57374
|
try {
|
|
56720
57375
|
const pkg = JSON.parse(readText(setupPkg));
|
|
56721
57376
|
setupDeps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
56722
57377
|
} catch {
|
|
56723
57378
|
}
|
|
56724
57379
|
}
|
|
56725
|
-
const tr = detectTestRunner(setupDeps, (f) =>
|
|
57380
|
+
const tr = detectTestRunner(setupDeps, (f) => existsSync30(join45(root, f)));
|
|
56726
57381
|
console.log(captureSetupPlan(tr, rel2, lang));
|
|
56727
57382
|
const gaLine = ".ultra11y/captures/*.html text eol=lf linguist-generated=true";
|
|
56728
|
-
const gaPath =
|
|
57383
|
+
const gaPath = join45(root, ".gitattributes");
|
|
56729
57384
|
try {
|
|
56730
|
-
const existing =
|
|
57385
|
+
const existing = existsSync30(gaPath) ? readFileSync26(gaPath, "utf8") : "";
|
|
56731
57386
|
if (!existing.includes(".ultra11y/captures/")) {
|
|
56732
57387
|
appendFileSync(gaPath, (existing && !existing.endsWith("\n") ? "\n" : "") + gaLine + "\n");
|
|
56733
57388
|
console.log(lang === "fr" ? `.gitattributes : ajout\xE9 \xAB ${gaLine} \xBB` : `.gitattributes: added "${gaLine}"`);
|
|
@@ -56735,8 +57390,8 @@ Fill in COMPONENTS, run it (e.g. npx tsx ${out2}), then: node scripts/ultra11y.m
|
|
|
56735
57390
|
} catch {
|
|
56736
57391
|
}
|
|
56737
57392
|
try {
|
|
56738
|
-
const giPath =
|
|
56739
|
-
if (
|
|
57393
|
+
const giPath = join45(root, ".gitignore");
|
|
57394
|
+
if (existsSync30(giPath) && /^\s*\/?\.ultra11y(\/\**)?\/?\s*$/m.test(readFileSync26(giPath, "utf8")))
|
|
56740
57395
|
console.error(
|
|
56741
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/").'
|
|
56742
57397
|
);
|
|
@@ -56746,8 +57401,8 @@ Fill in COMPONENTS, run it (e.g. npx tsx ${out2}), then: node scripts/ultra11y.m
|
|
|
56746
57401
|
}
|
|
56747
57402
|
if (p.flags.storybook === true || typeof p.flags.storybook === "string") {
|
|
56748
57403
|
const sbDir = p.positionals[0] ?? "storybook-static";
|
|
56749
|
-
const indexPath =
|
|
56750
|
-
if (!
|
|
57404
|
+
const indexPath = existsSync30(join45(sbDir, "index.json")) ? join45(sbDir, "index.json") : join45(sbDir, "stories.json");
|
|
57405
|
+
if (!existsSync30(indexPath)) {
|
|
56751
57406
|
console.error(
|
|
56752
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}.`
|
|
56753
57408
|
);
|
|
@@ -56757,7 +57412,7 @@ Fill in COMPONENTS, run it (e.g. npx tsx ${out2}), then: node scripts/ultra11y.m
|
|
|
56757
57412
|
const provById = new Map(stories.map((s) => [s.id, storyProvenance(s)]));
|
|
56758
57413
|
const capturesFlag = typeof p.flags.captures === "string" && p.flags.captures ? p.flags.captures : void 0;
|
|
56759
57414
|
const htmlDir = capturesFlag ?? sbDir;
|
|
56760
|
-
const htmlFiles =
|
|
57415
|
+
const htmlFiles = existsSync30(htmlDir) ? discover([htmlDir]).files.filter((f) => /\.html?$/i.test(f)) : [];
|
|
56761
57416
|
const outDir = ".ultra11y/captures";
|
|
56762
57417
|
let attributed = 0;
|
|
56763
57418
|
let skipped = 0;
|
|
@@ -56779,8 +57434,8 @@ Fill in COMPONENTS, run it (e.g. npx tsx ${out2}), then: node scripts/ultra11y.m
|
|
|
56779
57434
|
continue;
|
|
56780
57435
|
}
|
|
56781
57436
|
try {
|
|
56782
|
-
|
|
56783
|
-
|
|
57437
|
+
mkdirSync14(outDir, { recursive: true });
|
|
57438
|
+
writeFileSync16(join45(outDir, `${hitId}.html`), `${formatCaptureComment(prov)}
|
|
56784
57439
|
${raw}${raw.endsWith("\n") ? "" : "\n"}`);
|
|
56785
57440
|
attributed++;
|
|
56786
57441
|
} catch {
|
|
@@ -56802,11 +57457,11 @@ ${raw}${raw.endsWith("\n") ? "" : "\n"}`);
|
|
|
56802
57457
|
}
|
|
56803
57458
|
if (p.flags.coverage === true) {
|
|
56804
57459
|
const capturesFlag = typeof p.flags.captures === "string" && p.flags.captures ? p.flags.captures : void 0;
|
|
56805
|
-
const capturesDir = capturesFlag ??
|
|
57460
|
+
const capturesDir = capturesFlag ?? join45(root, ".ultra11y/captures");
|
|
56806
57461
|
const graphExt = [...GRAPH_ONLY_EXT, ...asList(p.flags.ext) ?? []];
|
|
56807
57462
|
const sourceFiles = discover([root], { include: asList(p.flags.include), exclude: asList(p.flags.exclude), ext: graphExt }).files;
|
|
56808
57463
|
const graph = buildGraphStreaming(sourceFiles);
|
|
56809
|
-
const capFiles =
|
|
57464
|
+
const capFiles = existsSync30(capturesDir) ? discover([capturesDir]).files : [];
|
|
56810
57465
|
const entries = capFiles.map((f) => ({ file: toPosix(f), provenance: parseCaptureProvenance(readText(f)) }));
|
|
56811
57466
|
const cov = computeCaptureCoverage(graph, entries);
|
|
56812
57467
|
if (p.flags.json) console.log(JSON.stringify(cov, null, 2));
|
|
@@ -56814,15 +57469,15 @@ ${raw}${raw.endsWith("\n") ? "" : "\n"}`);
|
|
|
56814
57469
|
return 0;
|
|
56815
57470
|
}
|
|
56816
57471
|
let deps = {};
|
|
56817
|
-
const pkgPath =
|
|
56818
|
-
if (
|
|
57472
|
+
const pkgPath = join45(root, "package.json");
|
|
57473
|
+
if (existsSync30(pkgPath)) {
|
|
56819
57474
|
try {
|
|
56820
57475
|
const pkg = JSON.parse(readText(pkgPath));
|
|
56821
57476
|
deps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
56822
57477
|
} catch {
|
|
56823
57478
|
}
|
|
56824
57479
|
}
|
|
56825
|
-
const detection = detectFrameworks(deps, (f) =>
|
|
57480
|
+
const detection = detectFrameworks(deps, (f) => existsSync30(join45(root, f)));
|
|
56826
57481
|
if (p.flags.json) console.log(JSON.stringify(detection, null, 2));
|
|
56827
57482
|
else console.log(renderPlan(detection, lang));
|
|
56828
57483
|
return 0;
|
|
@@ -57021,9 +57676,9 @@ function applyAdjudicationFile(p, adj, lang) {
|
|
|
57021
57676
|
return 1;
|
|
57022
57677
|
}
|
|
57023
57678
|
const out2 = typeof p.flags.out === "string" ? p.flags.out : ".";
|
|
57024
|
-
|
|
57025
|
-
const auditPath =
|
|
57026
|
-
|
|
57679
|
+
mkdirSync14(out2, { recursive: true });
|
|
57680
|
+
const auditPath = join45(out2, "audit-latest.json");
|
|
57681
|
+
writeFileSync16(auditPath, JSON.stringify(r.audit, null, 2) + "\n");
|
|
57027
57682
|
if (p.flags.json) console.log(JSON.stringify({ ok: true, auditPath, applied: r.applied, stillManual: r.stillManual, grounding: r.grounding }, null, 2));
|
|
57028
57683
|
else
|
|
57029
57684
|
console.log(
|
|
@@ -57093,7 +57748,7 @@ async function cmdScan(p) {
|
|
|
57093
57748
|
return 0;
|
|
57094
57749
|
}
|
|
57095
57750
|
for (const target of p.positionals.filter((a) => a !== "-")) {
|
|
57096
|
-
if (/^https?:\/\//i.test(target) ||
|
|
57751
|
+
if (/^https?:\/\//i.test(target) || existsSync30(target)) continue;
|
|
57097
57752
|
console.error(
|
|
57098
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.`
|
|
57099
57754
|
);
|
|
@@ -57218,12 +57873,12 @@ async function cmdScan(p) {
|
|
|
57218
57873
|
audit2 = parsed;
|
|
57219
57874
|
}
|
|
57220
57875
|
const merged = mergeDynamic(audit2, dynamic, lang);
|
|
57221
|
-
|
|
57222
|
-
|
|
57876
|
+
mkdirSync14(out2, { recursive: true });
|
|
57877
|
+
writeFileSync16(join45(out2, "audit-latest.json"), JSON.stringify(merged, null, 2) + "\n");
|
|
57223
57878
|
if (p.flags.json) console.log(JSON.stringify(merged, null, 2));
|
|
57224
57879
|
else
|
|
57225
57880
|
console.log(
|
|
57226
|
-
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).`
|
|
57227
57882
|
);
|
|
57228
57883
|
return 0;
|
|
57229
57884
|
}
|
|
@@ -57401,9 +58056,9 @@ function cmdOrchestrate(p) {
|
|
|
57401
58056
|
);
|
|
57402
58057
|
return 2;
|
|
57403
58058
|
}
|
|
57404
|
-
const engineAbs =
|
|
58059
|
+
const engineAbs = realpathSync6(fileURLToPath5(import.meta.url));
|
|
57405
58060
|
if (p.flags.list === true) {
|
|
57406
|
-
if (!
|
|
58061
|
+
if (!existsSync30(runFlag)) {
|
|
57407
58062
|
console.error(`ultra11y orchestrate: run dir not found: ${runFlag}.`);
|
|
57408
58063
|
return 2;
|
|
57409
58064
|
}
|
|
@@ -57430,7 +58085,7 @@ function cmdOrchestrate(p) {
|
|
|
57430
58085
|
);
|
|
57431
58086
|
} else {
|
|
57432
58087
|
console.log(
|
|
57433
|
-
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).`
|
|
57434
58089
|
);
|
|
57435
58090
|
}
|
|
57436
58091
|
if (p.flags.phase === void 0 && workflows.length === 0 && p.flags.eco !== true) {
|
|
@@ -57516,6 +58171,12 @@ async function main(argv) {
|
|
|
57516
58171
|
return cmdMcp(p);
|
|
57517
58172
|
case "hook":
|
|
57518
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);
|
|
57519
58180
|
default:
|
|
57520
58181
|
console.error(`ultra11y: "${p.command}" is not implemented yet`);
|
|
57521
58182
|
return 1;
|
|
@@ -57526,7 +58187,7 @@ function isInvokedDirectly() {
|
|
|
57526
58187
|
if (argv1 === void 0) return false;
|
|
57527
58188
|
const modulePath = fileURLToPath5(import.meta.url);
|
|
57528
58189
|
try {
|
|
57529
|
-
if (
|
|
58190
|
+
if (realpathSync6(argv1) === realpathSync6(modulePath)) return true;
|
|
57530
58191
|
} catch {
|
|
57531
58192
|
}
|
|
57532
58193
|
return import.meta.url === pathToFileURL3(argv1).href;
|