ultra11y 5.43.3 → 5.43.5

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.
@@ -10,7 +10,7 @@
10
10
  "name": "ultra11y",
11
11
  "source": "./",
12
12
  "description": "Automatic accessibility review before commit, push and pull request, plus detailed evidenced Markdown, HTML and per-page WCAG 2.2 AA / RGAA reports in Claude Code.",
13
- "version": "5.43.3",
13
+ "version": "5.43.5",
14
14
  "author": { "name": "maxgfr" },
15
15
  "homepage": "https://github.com/SocialGouv/ultra11y",
16
16
  "repository": "https://github.com/SocialGouv/ultra11y",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "ultra11y",
3
3
  "description": "Accessibility for WCAG 2.2 AA and country standards (RGAA…): automatic review before commit, push and pull request, plus detailed evidenced Markdown, HTML and per-page conformance reports in interactive agent sessions.",
4
- "version": "5.43.3",
4
+ "version": "5.43.5",
5
5
  "author": { "name": "maxgfr" },
6
6
  "homepage": "https://github.com/SocialGouv/ultra11y",
7
7
  "repository": "https://github.com/SocialGouv/ultra11y",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "ultra11y",
3
3
  "description": "Accessibility for WCAG 2.2 AA and country standards (RGAA…): automatic review before commit, push and pull request, plus detailed evidenced Markdown, HTML and per-page conformance reports in interactive agent sessions.",
4
- "version": "5.43.3",
4
+ "version": "5.43.5",
5
5
  "author": { "name": "maxgfr", "url": "https://github.com/maxgfr" },
6
6
  "homepage": "https://github.com/SocialGouv/ultra11y",
7
7
  "repository": "https://github.com/SocialGouv/ultra11y",
@@ -27,7 +27,7 @@ import { fileURLToPath } from "node:url";
27
27
  // Ownership marker: `ultra11y install --opencode` refuses to overwrite a plugin file that
28
28
  // does not carry it, so a user's own ultra11y.js is never clobbered.
29
29
  const ULTRA11Y_OPENCODE_PLUGIN = "ULTRA11Y_OPENCODE_PLUGIN";
30
- const ULTRA11Y_PLUGIN_VERSION = "5.43.3";
30
+ const ULTRA11Y_PLUGIN_VERSION = "5.43.5";
31
31
 
32
32
  const here = dirname(fileURLToPath(import.meta.url));
33
33
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ultra11y",
3
- "version": "5.43.3",
3
+ "version": "5.43.5",
4
4
  "description": "Audit HTML/CSS/JSX against WCAG 2.2 AA and produce a dated compliance report, or author/review accessible markup (native-HTML-first). A skills.sh agent skill: an install-free static engine (bundled, no npm install) plus the agent's judgment, with check/verify gates against hallucinated non-conformities, and pluggable in-repo standards packs (RGAA France first; Section 508 & EN 301 549 next).",
5
5
  "type": "module",
6
6
  "packageManager": "pnpm@10.33.0",
@@ -6,7 +6,7 @@ import { join as join52, relative as relative6, resolve as resolve17, sep as sep
6
6
  import { fileURLToPath as fileURLToPath5, pathToFileURL as pathToFileURL3 } from "url";
7
7
 
8
8
  // src/types.ts
9
- var VERSION = "5.43.3";
9
+ var VERSION = "5.43.5";
10
10
  var SCHEMA_VERSION = 2;
11
11
  var INAPPLICABLE_STATUS = "C";
12
12
 
@@ -58325,8 +58325,9 @@ function derivePackResults(audit2, packKey, pageId) {
58325
58325
  const measured = measuredRescue(derived, pc, cov, ran, pageId);
58326
58326
  const decided = adjudicated?.get(pc.id);
58327
58327
  if (decided?.status === "manual") {
58328
+ const { inapplicable: _absence, ...open } = measured;
58328
58329
  return {
58329
- ...measured,
58330
+ ...open,
58330
58331
  status: measured.status === "NC" ? "NC" : "manual",
58331
58332
  ...decided.justification ? { justification: decided.justification } : {},
58332
58333
  decidedBy: "agent"
@@ -59853,8 +59854,9 @@ function pageTally(rows) {
59853
59854
  c: rows.filter((r) => r.status === "C").length,
59854
59855
  nc: rows.filter((r) => r.status === "NC").length,
59855
59856
  // No row carries `NA` any more (INAPPLICABLE_STATUS); this counts the conformities
59856
- // reached for want of a subject, and it is a SUBSET of `c` rather than a fourth bucket.
59857
- na: rows.filter((r) => r.inapplicable).length,
59857
+ // reached for want of a subject, and it is a SUBSET of `c` rather than a fourth bucket
59858
+ // (same predicate as src/report.ts tallyRows).
59859
+ na: rows.filter((r) => r.status === "C" && r.inapplicable).length,
59858
59860
  manual: rows.filter((r) => r.status === "manual").length
59859
59861
  };
59860
59862
  }
@@ -60402,7 +60404,9 @@ function tallyRows(rows) {
60402
60404
  return {
60403
60405
  c: rows.filter((x) => x.status === "C").length,
60404
60406
  nc: rows.filter((x) => x.status === "NC").length,
60405
- na: rows.filter((x) => x.inapplicable).length,
60407
+ // NA within C, structurally: a row that carried `inapplicable` under another status (a
60408
+ // criterion that stayed open, say) must not be counted in both columns.
60409
+ na: rows.filter((x) => x.status === "C" && x.inapplicable).length,
60406
60410
  manual: rows.filter((x) => x.status === "manual").length
60407
60411
  };
60408
60412
  }
@@ -60905,7 +60909,7 @@ function render(r, lang, opts) {
60905
60909
  }
60906
60910
  }
60907
60911
  out2.push(`## ${s.naTitle}`, "");
60908
- const na = rows.filter((x) => x.inapplicable);
60912
+ const na = rows.filter((x) => x.status === "C" && x.inapplicable);
60909
60913
  if (!na.length) out2.push(s.nothing, "");
60910
60914
  else {
60911
60915
  out2.push(`> ${s.naNote}`, "");
@@ -64510,10 +64514,10 @@ function applyAdjudication(audit2, adj, opts = {}) {
64510
64514
  `criterion ${it.criteriaId}: a C verdict needs evidence to cite, and none was harvested for this criterion \u2014 record "manual" (reason "undecidable"), or "NA" if nothing in scope is concerned`
64511
64515
  );
64512
64516
  }
64513
- } else if (cites.length === 0 && v === "C") {
64517
+ } else if (cites.length === 0 && (v === "C" || v === "NA")) {
64514
64518
  blame(
64515
64519
  it.criteriaId,
64516
- `criterion ${it.criteriaId}: a C verdict must cite at least one of the ${it.evidence.length} evidence item(s) it was shown (citations: [{file, line, \u2026}])`
64520
+ v === "C" ? `criterion ${it.criteriaId}: a C verdict must cite at least one of the ${it.evidence.length} evidence item(s) it was shown (citations: [{file, line, \u2026}])` : `criterion ${it.criteriaId}: a NA verdict on a criterion with evidence must cite it`
64517
64521
  );
64518
64522
  } else if (cites.length > 0) {
64519
64523
  const anchors = it.evidence.flatMap((e) => [`${e.file}:${e.line}`, ...e.alsoAt ?? []]);
@@ -68865,7 +68869,7 @@ function packAuditDocument(input, packKey, lang) {
68865
68869
  // worse than either being wrong.
68866
68870
  c: rows.filter((r) => r.status === "C").length,
68867
68871
  nc: rows.filter((r) => r.status === "NC").length,
68868
- na: rows.filter((r) => r.inapplicable).length,
68872
+ na: rows.filter((r) => r.status === "C" && r.inapplicable).length,
68869
68873
  manual: rows.filter((r) => r.status === "manual").length
68870
68874
  };
68871
68875
  });
@@ -3,7 +3,7 @@ name: review-a11y
3
3
  description: "Use to REVIEW changed frontend code for accessibility: staged files, a working diff, branch, or PR; prompts such as 'review a11y', 'is this accessible?', or 'anything to fix before merge?'; and hook or CI findings on a change. Run the bundled install-free engine with cross-file JSX/TSX analysis, then adjudicate candidates and return a severity-ranked WCAG 2.2 AA review with file:line fixes, residual rendering risks, and a verdict. Use `ultra11y` instead for whole-repo audits, dated conformance reports, country standards, PRD backlogs, or authoring accessible markup."
4
4
  license: MIT
5
5
  metadata:
6
- version: 5.43.3
6
+ version: 5.43.5
7
7
  ---
8
8
 
9
9
  # review-a11y — accessibility review of changed code
@@ -6,7 +6,7 @@ import { join as join52, relative as relative6, resolve as resolve17, sep as sep
6
6
  import { fileURLToPath as fileURLToPath5, pathToFileURL as pathToFileURL3 } from "url";
7
7
 
8
8
  // src/types.ts
9
- var VERSION = "5.43.3";
9
+ var VERSION = "5.43.5";
10
10
  var SCHEMA_VERSION = 2;
11
11
  var INAPPLICABLE_STATUS = "C";
12
12
 
@@ -58325,8 +58325,9 @@ function derivePackResults(audit2, packKey, pageId) {
58325
58325
  const measured = measuredRescue(derived, pc, cov, ran, pageId);
58326
58326
  const decided = adjudicated?.get(pc.id);
58327
58327
  if (decided?.status === "manual") {
58328
+ const { inapplicable: _absence, ...open } = measured;
58328
58329
  return {
58329
- ...measured,
58330
+ ...open,
58330
58331
  status: measured.status === "NC" ? "NC" : "manual",
58331
58332
  ...decided.justification ? { justification: decided.justification } : {},
58332
58333
  decidedBy: "agent"
@@ -59853,8 +59854,9 @@ function pageTally(rows) {
59853
59854
  c: rows.filter((r) => r.status === "C").length,
59854
59855
  nc: rows.filter((r) => r.status === "NC").length,
59855
59856
  // No row carries `NA` any more (INAPPLICABLE_STATUS); this counts the conformities
59856
- // reached for want of a subject, and it is a SUBSET of `c` rather than a fourth bucket.
59857
- na: rows.filter((r) => r.inapplicable).length,
59857
+ // reached for want of a subject, and it is a SUBSET of `c` rather than a fourth bucket
59858
+ // (same predicate as src/report.ts tallyRows).
59859
+ na: rows.filter((r) => r.status === "C" && r.inapplicable).length,
59858
59860
  manual: rows.filter((r) => r.status === "manual").length
59859
59861
  };
59860
59862
  }
@@ -60402,7 +60404,9 @@ function tallyRows(rows) {
60402
60404
  return {
60403
60405
  c: rows.filter((x) => x.status === "C").length,
60404
60406
  nc: rows.filter((x) => x.status === "NC").length,
60405
- na: rows.filter((x) => x.inapplicable).length,
60407
+ // NA within C, structurally: a row that carried `inapplicable` under another status (a
60408
+ // criterion that stayed open, say) must not be counted in both columns.
60409
+ na: rows.filter((x) => x.status === "C" && x.inapplicable).length,
60406
60410
  manual: rows.filter((x) => x.status === "manual").length
60407
60411
  };
60408
60412
  }
@@ -60905,7 +60909,7 @@ function render(r, lang, opts) {
60905
60909
  }
60906
60910
  }
60907
60911
  out2.push(`## ${s.naTitle}`, "");
60908
- const na = rows.filter((x) => x.inapplicable);
60912
+ const na = rows.filter((x) => x.status === "C" && x.inapplicable);
60909
60913
  if (!na.length) out2.push(s.nothing, "");
60910
60914
  else {
60911
60915
  out2.push(`> ${s.naNote}`, "");
@@ -64510,10 +64514,10 @@ function applyAdjudication(audit2, adj, opts = {}) {
64510
64514
  `criterion ${it.criteriaId}: a C verdict needs evidence to cite, and none was harvested for this criterion \u2014 record "manual" (reason "undecidable"), or "NA" if nothing in scope is concerned`
64511
64515
  );
64512
64516
  }
64513
- } else if (cites.length === 0 && v === "C") {
64517
+ } else if (cites.length === 0 && (v === "C" || v === "NA")) {
64514
64518
  blame(
64515
64519
  it.criteriaId,
64516
- `criterion ${it.criteriaId}: a C verdict must cite at least one of the ${it.evidence.length} evidence item(s) it was shown (citations: [{file, line, \u2026}])`
64520
+ v === "C" ? `criterion ${it.criteriaId}: a C verdict must cite at least one of the ${it.evidence.length} evidence item(s) it was shown (citations: [{file, line, \u2026}])` : `criterion ${it.criteriaId}: a NA verdict on a criterion with evidence must cite it`
64517
64521
  );
64518
64522
  } else if (cites.length > 0) {
64519
64523
  const anchors = it.evidence.flatMap((e) => [`${e.file}:${e.line}`, ...e.alsoAt ?? []]);
@@ -68865,7 +68869,7 @@ function packAuditDocument(input, packKey, lang) {
68865
68869
  // worse than either being wrong.
68866
68870
  c: rows.filter((r) => r.status === "C").length,
68867
68871
  nc: rows.filter((r) => r.status === "NC").length,
68868
- na: rows.filter((r) => r.inapplicable).length,
68872
+ na: rows.filter((r) => r.status === "C" && r.inapplicable).length,
68869
68873
  manual: rows.filter((r) => r.status === "manual").length
68870
68874
  };
68871
68875
  });
@@ -3,7 +3,7 @@ name: ultra11y
3
3
  description: "Use to AUDIT a repository, site, or page against WCAG 2.2 AA or a country standard such as RGAA; produce a dated conformance report, criterion grid, PRD backlog, or tickets; AUTHOR accessible markup; fix accessibility; or wire a repo gate and rendered-DOM capture pipeline. The bundled install-free engine runs 93 static checks tied to success criteria, while the agent adjudicates judgment criteria and routes rendering criteria to browser scans; check/verify reject invented non-conformities. Use `review-a11y` instead for a review scoped only to a diff, branch, or PR."
4
4
  license: MIT
5
5
  metadata:
6
- version: 5.43.3
6
+ version: 5.43.5
7
7
  ---
8
8
 
9
9
  # ultra11y — audit WCAG 2.2 AA and write accessible markup
@@ -93,6 +93,9 @@ name (« not open for adjudication »), never re-applied: re-folding the same fi
93
93
  written-back audit answers `ok: false` with that criterion in `rejectedCriteria[]`. Agent `NC`s become
94
94
  real `agent:<sc>` findings that re-render in the report's §2 and re-enter the verify worklist;
95
95
  `report`/`prd` re-render with the adjudicated statuses; §5 shrinks to only still-`manual` items.
96
+ A `manual` verdict — or a coverage gap — on a criterion the engine had provisionally closed for
97
+ want of a subject stays in §5 only: it is never counted in the NA column, which remains a subset
98
+ of §3's C.
96
99
 
97
100
  ## Residual risk
98
101
 
@@ -6,7 +6,7 @@ import { join as join52, relative as relative6, resolve as resolve17, sep as sep
6
6
  import { fileURLToPath as fileURLToPath5, pathToFileURL as pathToFileURL3 } from "url";
7
7
 
8
8
  // src/types.ts
9
- var VERSION = "5.43.3";
9
+ var VERSION = "5.43.5";
10
10
  var SCHEMA_VERSION = 2;
11
11
  var INAPPLICABLE_STATUS = "C";
12
12
 
@@ -58325,8 +58325,9 @@ function derivePackResults(audit2, packKey, pageId) {
58325
58325
  const measured = measuredRescue(derived, pc, cov, ran, pageId);
58326
58326
  const decided = adjudicated?.get(pc.id);
58327
58327
  if (decided?.status === "manual") {
58328
+ const { inapplicable: _absence, ...open } = measured;
58328
58329
  return {
58329
- ...measured,
58330
+ ...open,
58330
58331
  status: measured.status === "NC" ? "NC" : "manual",
58331
58332
  ...decided.justification ? { justification: decided.justification } : {},
58332
58333
  decidedBy: "agent"
@@ -59853,8 +59854,9 @@ function pageTally(rows) {
59853
59854
  c: rows.filter((r) => r.status === "C").length,
59854
59855
  nc: rows.filter((r) => r.status === "NC").length,
59855
59856
  // No row carries `NA` any more (INAPPLICABLE_STATUS); this counts the conformities
59856
- // reached for want of a subject, and it is a SUBSET of `c` rather than a fourth bucket.
59857
- na: rows.filter((r) => r.inapplicable).length,
59857
+ // reached for want of a subject, and it is a SUBSET of `c` rather than a fourth bucket
59858
+ // (same predicate as src/report.ts tallyRows).
59859
+ na: rows.filter((r) => r.status === "C" && r.inapplicable).length,
59858
59860
  manual: rows.filter((r) => r.status === "manual").length
59859
59861
  };
59860
59862
  }
@@ -60402,7 +60404,9 @@ function tallyRows(rows) {
60402
60404
  return {
60403
60405
  c: rows.filter((x) => x.status === "C").length,
60404
60406
  nc: rows.filter((x) => x.status === "NC").length,
60405
- na: rows.filter((x) => x.inapplicable).length,
60407
+ // NA within C, structurally: a row that carried `inapplicable` under another status (a
60408
+ // criterion that stayed open, say) must not be counted in both columns.
60409
+ na: rows.filter((x) => x.status === "C" && x.inapplicable).length,
60406
60410
  manual: rows.filter((x) => x.status === "manual").length
60407
60411
  };
60408
60412
  }
@@ -60905,7 +60909,7 @@ function render(r, lang, opts) {
60905
60909
  }
60906
60910
  }
60907
60911
  out2.push(`## ${s.naTitle}`, "");
60908
- const na = rows.filter((x) => x.inapplicable);
60912
+ const na = rows.filter((x) => x.status === "C" && x.inapplicable);
60909
60913
  if (!na.length) out2.push(s.nothing, "");
60910
60914
  else {
60911
60915
  out2.push(`> ${s.naNote}`, "");
@@ -64510,10 +64514,10 @@ function applyAdjudication(audit2, adj, opts = {}) {
64510
64514
  `criterion ${it.criteriaId}: a C verdict needs evidence to cite, and none was harvested for this criterion \u2014 record "manual" (reason "undecidable"), or "NA" if nothing in scope is concerned`
64511
64515
  );
64512
64516
  }
64513
- } else if (cites.length === 0 && v === "C") {
64517
+ } else if (cites.length === 0 && (v === "C" || v === "NA")) {
64514
64518
  blame(
64515
64519
  it.criteriaId,
64516
- `criterion ${it.criteriaId}: a C verdict must cite at least one of the ${it.evidence.length} evidence item(s) it was shown (citations: [{file, line, \u2026}])`
64520
+ v === "C" ? `criterion ${it.criteriaId}: a C verdict must cite at least one of the ${it.evidence.length} evidence item(s) it was shown (citations: [{file, line, \u2026}])` : `criterion ${it.criteriaId}: a NA verdict on a criterion with evidence must cite it`
64517
64521
  );
64518
64522
  } else if (cites.length > 0) {
64519
64523
  const anchors = it.evidence.flatMap((e) => [`${e.file}:${e.line}`, ...e.alsoAt ?? []]);
@@ -68865,7 +68869,7 @@ function packAuditDocument(input, packKey, lang) {
68865
68869
  // worse than either being wrong.
68866
68870
  c: rows.filter((r) => r.status === "C").length,
68867
68871
  nc: rows.filter((r) => r.status === "NC").length,
68868
- na: rows.filter((r) => r.inapplicable).length,
68872
+ na: rows.filter((r) => r.status === "C" && r.inapplicable).length,
68869
68873
  manual: rows.filter((r) => r.status === "manual").length
68870
68874
  };
68871
68875
  });
@@ -27,7 +27,7 @@ variables:
27
27
  ULTRA11Y_LANG: en
28
28
  # Exact by default: a CI report must say which engine produced it. The release script keeps
29
29
  # this pin aligned with the package version; override only for a deliberate upgrade test.
30
- ULTRA11Y_VERSION: '5.43.3'
30
+ ULTRA11Y_VERSION: '5.43.5'
31
31
  # Batches of 8 amortise the prompt; each accepted verdict is checkpointed before the next
32
32
  # batch, and later passes receive only the residue.
33
33
  ULTRA11Y_GRAIN: batch
package/src/adjudicate.ts CHANGED
@@ -1103,19 +1103,21 @@ export function applyAdjudication(
1103
1103
  `criterion ${it.criteriaId}: a C verdict needs evidence to cite, and none was harvested for this criterion — record "manual" (reason "undecidable"), or "NA" if nothing in scope is concerned`,
1104
1104
  );
1105
1105
  }
1106
- } else if (cites.length === 0 && v === "C") {
1107
- // Only a `C` must cite. A `C` says "everything here conforms", so it has to name what
1108
- // it cleared; an `NA` says "none of this is what the criterion is about", and there is
1109
- // nothing to clear — asking it to cite the very elements it just ruled out of scope is
1110
- // a contradiction, and one the engine does not hold ITSELF to: an engine-proved NA
1111
- // (src/audit.ts subjectMatterReason) carries a justification naming what was searched
1112
- // for, and no citations at all. Measured on a real run, six criteria were refused for
1113
- // this alone, each of them an honest NA.
1106
+ } else if (cites.length === 0 && (v === "C" || v === "NA")) {
1107
+ // Either clearing verdict must cite. A `C` says "everything here conforms" and a `NA`
1108
+ // says "none of this is what the criterion is about" — both rule on the harvested
1109
+ // evidence, and the contract the filler was given (adjudicationContract, echoed by the
1110
+ // judge schema and the brief) already requires citations[] whenever evidence was
1111
+ // presented: "to say which of those items fall outside the criterion's scope". The
1112
+ // fold used to enforce only the `C` half, so a bare NA cleared shown evidence without
1113
+ // naming it — an agent could wave what it had been shown out of scope with a sentence.
1114
1114
  //
1115
1115
  // The justification is what keeps an NA falsifiable, and it is still required above.
1116
1116
  blame(
1117
1117
  it.criteriaId,
1118
- `criterion ${it.criteriaId}: a C verdict must cite at least one of the ${it.evidence.length} evidence item(s) it was shown (citations: [{file, line, …}])`,
1118
+ v === "C"
1119
+ ? `criterion ${it.criteriaId}: a C verdict must cite at least one of the ${it.evidence.length} evidence item(s) it was shown (citations: [{file, line, …}])`
1120
+ : `criterion ${it.criteriaId}: a NA verdict on a criterion with evidence must cite it`,
1119
1121
  );
1120
1122
  } else if (cites.length > 0) {
1121
1123
  // Each citation must name evidence THIS item carried. Grounding alone would only
@@ -262,8 +262,9 @@ export function pageTally(rows: PageCriterionRow[]): { c: number; nc: number; na
262
262
  c: rows.filter((r) => r.status === "C").length,
263
263
  nc: rows.filter((r) => r.status === "NC").length,
264
264
  // No row carries `NA` any more (INAPPLICABLE_STATUS); this counts the conformities
265
- // reached for want of a subject, and it is a SUBSET of `c` rather than a fourth bucket.
266
- na: rows.filter((r) => r.inapplicable).length,
265
+ // reached for want of a subject, and it is a SUBSET of `c` rather than a fourth bucket
266
+ // (same predicate as src/report.ts tallyRows).
267
+ na: rows.filter((r) => r.status === "C" && r.inapplicable).length,
267
268
  manual: rows.filter((r) => r.status === "manual").length,
268
269
  };
269
270
  }
package/src/report.ts CHANGED
@@ -627,7 +627,9 @@ export function tallyRows(rows: ReportRow[]): ReportTally {
627
627
  return {
628
628
  c: rows.filter((x) => x.status === "C").length,
629
629
  nc: rows.filter((x) => x.status === "NC").length,
630
- na: rows.filter((x) => x.inapplicable).length,
630
+ // NA within C, structurally: a row that carried `inapplicable` under another status (a
631
+ // criterion that stayed open, say) must not be counted in both columns.
632
+ na: rows.filter((x) => x.status === "C" && x.inapplicable).length,
631
633
  manual: rows.filter((x) => x.status === "manual").length,
632
634
  };
633
635
  }
@@ -1522,9 +1524,10 @@ function render(r: AuditResult, lang: Lang, opts: RenderOpts): ReportDocuments {
1522
1524
  }
1523
1525
 
1524
1526
  // 4. conforming for want of a subject — each with what was looked for, so the claim stays
1525
- // falsifiable rather than merely asserted.
1527
+ // falsifiable rather than merely asserted. Same predicate as tallyRows: the section lists
1528
+ // exactly the criteria the synthesis counts as NA.
1526
1529
  out.push(`## ${s.naTitle}`, "");
1527
- const na = rows.filter((x) => x.inapplicable);
1530
+ const na = rows.filter((x) => x.status === "C" && x.inapplicable);
1528
1531
  if (!na.length) out.push(s.nothing, "");
1529
1532
  else {
1530
1533
  out.push(`> ${s.naNote}`, "");
@@ -469,8 +469,14 @@ export function derivePackResults(audit: AuditResult, packKey: string, pageId?:
469
469
  const measured = measuredRescue(derived, pc, cov, ran, pageId);
470
470
  const decided = adjudicated?.get(pc.id);
471
471
  if (decided?.status === "manual") {
472
+ // An abstention keeps the criterion OPEN, so the absence-derived `inapplicable` must not
473
+ // ride along: the report counts NA inside C (src/report.ts tallyRows), and a row that is
474
+ // both « à évaluer » and « conforme faute de sujet » lands in both columns — NA then
475
+ // overtakes C in the synthesis and `check --report` refuses the document this engine
476
+ // itself wrote.
477
+ const { inapplicable: _absence, ...open } = measured;
472
478
  return {
473
- ...measured,
479
+ ...open,
474
480
  status: measured.status === "NC" ? "NC" : "manual",
475
481
  ...(decided.justification ? { justification: decided.justification } : {}),
476
482
  decidedBy: "agent" as const,
@@ -174,7 +174,7 @@ export function packAuditDocument(input: AuditResult | PackAuditResult, packKey:
174
174
  // worse than either being wrong.
175
175
  c: rows.filter((r) => r.status === "C").length,
176
176
  nc: rows.filter((r) => r.status === "NC").length,
177
- na: rows.filter((r) => r.inapplicable).length,
177
+ na: rows.filter((r) => r.status === "C" && r.inapplicable).length,
178
178
  manual: rows.filter((r) => r.status === "manual").length,
179
179
  };
180
180
  });
package/src/types.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  // Single source of truth for shared types + the embedded version. sync-version.mjs
2
2
  // rewrites VERSION at release time (kept in lockstep with package.json + SKILL.md).
3
- export const VERSION = "5.43.3";
3
+ export const VERSION = "5.43.5";
4
4
  export const SCHEMA_VERSION = 2;
5
5
 
6
6
  // Lang = the UI FRAME's language — the `L` localization tables each module (report,