eyeprolog 1.5.71 → 1.5.73

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/README.md CHANGED
@@ -84,7 +84,7 @@ The checked [Symbiotic Knowledge Graphs example](examples/symbiotic-knowledge-gr
84
84
  The same RDF → Prolog → RDF boundary is exercised by five additional checked scenarios: [cross-organization data sharing](https://eyereasoner.github.io/eyeprolog/examples/deck/cross-organization-data-sharing), [explainable EV-depot configuration](https://eyereasoner.github.io/eyeprolog/examples/deck/explainable-ev-depot-configuration), [operational incident response](https://eyereasoner.github.io/eyeprolog/examples/deck/operational-incident-response), [software supply-chain vulnerability response](https://eyereasoner.github.io/eyeprolog/examples/deck/sbom-vulnerability-response), and a [scientific evidence graph](https://eyereasoner.github.io/eyeprolog/examples/deck/scientific-evidence-graph). Together they cover policy decisions, reversible configuration reasoning, dependency-graph diagnosis, transitive SBOM exposure, and evidence aggregation with explicit disagreement.
85
85
 
86
86
  ## Benchmarks
87
- EyeProlog has 19 checksum-protected wall-clock benchmarks spanning recursion/indexing, constraints, tabling/WFS, DCGs, Eyelet, search, term I/O, attributes, rewriting, and the classic Prolog naive-reverse workload. Short workloads are adaptively batched before timing so millisecond-scale noise is not mistaken for a regression. Run `npm run benchmark`; create a machine-local comparison point with `npm run benchmark:baseline`; use `npm run test:benchmark` for harness checks. The checked [`examples/bench.pl`](examples/bench.pl) preserves the classic Quintus 1984 `nrev/2` workload on a 30-element list. For a comparable LIPS number, run `npm run benchmark:lips`: it executes the classic failure-driven `dobench/1` and `dodummy/1` loops in Prolog, subtracts dummy-loop CPU time, and applies the historical 496 procedure calls per reversal. The generic benchmark table still shows a quick wall-clock LIPS estimate for `classic-nrev`, but `benchmark:lips` is the canonical engine-speed measurement. LIPS is a historical basic-engine-speed indicator, not a whole-system performance score. Details are in [*The Art of EyeProlog*](the-art-of-eyeprolog.md).
87
+ EyeProlog has 19 checksum-protected wall-clock benchmarks spanning recursion/indexing, constraints, tabling/WFS, DCGs, Eyelet, search, term I/O, attributes, rewriting, and the classic Prolog naive-reverse workload. Short workloads are adaptively batched before timing so millisecond-scale noise is not mistaken for a regression. Run `npm run benchmark`; create a machine-local comparison point with `npm run benchmark -- --save .benchmarks/baseline.json`; use `node test/run-benchmark-tests.mjs` for harness checks. The checked [`examples/bench.pl`](examples/bench.pl) preserves the classic Quintus 1984 `nrev/2` workload on a 30-element list. For a comparable LIPS number, run `node test/lips-benchmark.mjs`: it executes the classic failure-driven `dobench/1` and `dodummy/1` loops in Prolog, subtracts dummy-loop CPU time, and applies the historical 496 procedure calls per reversal. The generic benchmark table still shows a quick wall-clock LIPS estimate for `classic-nrev`, but `node test/lips-benchmark.mjs` is the canonical engine-speed measurement. LIPS is a historical basic-engine-speed indicator, not a whole-system performance score. Details are in [*The Art of EyeProlog*](the-art-of-eyeprolog.md).
88
88
  For the project policy on post-ISO-standard and WG17 compatibility features such as digit separators, see [ISO/WG17 compatibility extensions](test/conformance/ISO-WG17-EXTENSIONS.md).
89
89
  ## Development
90
90
  ```sh
@@ -93,5 +93,13 @@ cd eyeprolog
93
93
  npm install
94
94
  npm test
95
95
  ```
96
- `npm test` is the release gate and fetches the latest seven Neumerkel conformity sources before the local gates; use `npm run test:offline` for a network-free pass, `npm run test:conformance` for all conformance layers, `npm run test:neumerkel` for live upstream only, and `npm run test:neumerkel:cached` only to reproduce the last fetch. Upstream counts are discovered dynamically. A stale tracked [latest Neumerkel report](test/conformance/NEUMERKEL-LATEST.md) produces a warning during normal tests. `npm run conformance:update:neumerkel` refreshes it from live upstream; `npm run conformance:sync:neumerkel` refreshes it from the exact successful snapshot already fetched by `npm test`; and `npm run conformance:check:neumerkel` verifies that snapshot without a second live fetch. npm's version lifecycle uses the sync path and stages the generated reports into the release commit. Exact bytes/hashes stay under Git-ignored `.cache/neumerkel/`. Benchmarks remain `npm run benchmark` and `npm run benchmark:lips`.
96
+ The npm command list is deliberately small:
97
+
98
+ - `npm test` (or `npm run test`): run the release gate, including live upstream conformity checks.
99
+ - `npm run conformance:update:wg17`: update the vendored WG17 syntax cases and their inventory documentation.
100
+ - `npm run generate`: rebuild generated library and book files.
101
+ - `npm run benchmark`: run the wall-clock benchmarks.
102
+
103
+ Use `npm test -- --offline` for a network-free local pass. Focused checks remain available directly, for example `node test/run-regression.mjs docs` or `node test/run-wg17.mjs`; see [test runners](test/README.md). The automatic version hooks still run the release gate, refresh and stage conformance reports, and push the release. Detailed upstream report maintenance is documented in the [conformance guide](test/conformance/README.md).
104
+
97
105
  EyeProlog is released under the [MIT License](LICENSE.md).
@@ -15,9 +15,9 @@ cached source bytes, avoiding a second live fetch and its race window.
15
15
 
16
16
  | Gate | Passed | Total | Status |
17
17
  |---|---:|---:|---|
18
- | WG17 syntax | 366 | 366 | pass |
18
+ | WG17 syntax | 379 | 379 | pass |
19
19
 
20
- The WG17 syntax row executes the vendored 366-case conformity-testing matrix
20
+ The WG17 syntax row executes the vendored 379-case conformity-testing matrix
21
21
  against EyeProlog's strict ISO reader/writer. A behavior fix such as operator-token
22
22
  spelling therefore changes this report even when no corpus file is added or removed.
23
23
 
@@ -291,7 +291,7 @@ rdf-to-prolog \
291
291
  Run the checked EyeProlog example:
292
292
 
293
293
  ```sh
294
- npm run test:examples
294
+ node test/run-examples.mjs
295
295
  ```
296
296
 
297
297
  Materialize only the RDF-shaped results:
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.5.71",
6
+ "version": "1.5.73",
7
7
  "description": "EyeProlog turns facts and rules into answers and proofs.",
8
8
  "type": "module",
9
9
  "main": "./index.js",
@@ -46,36 +46,10 @@
46
46
  },
47
47
  "scripts": {
48
48
  "test": "node test/run-all.mjs",
49
- "test:offline": "node test/run-all.mjs --offline",
50
- "test:conformance": "node test/run-conformance-all.mjs",
51
- "test:conformance:offline": "node test/run-conformance-all.mjs --offline",
52
- "test:neumerkel": "node test/run-neumerkel.mjs",
53
- "test:neumerkel:cached": "node test/run-neumerkel.mjs --cached",
54
- "test:neumerkel:harness": "node test/run-neumerkel-tests.mjs",
55
- "test:iso": "node test/run-iso-strict.mjs",
56
- "test:wg17": "node test/run-wg17.mjs",
57
- "test:regression": "node test/run-regression.mjs",
58
- "test:examples": "node test/run-examples.mjs",
59
- "test:interop": "node test/run-interop.mjs",
60
- "test:playground": "node test/run-playground.mjs",
61
- "test:architecture": "node test/run-architecture.mjs",
62
- "test:openrulebench": "node test/run-openrulebench.mjs",
63
- "test:benchmark": "node test/run-benchmark-tests.mjs",
49
+ "conformance:update:wg17": "node tools/upgrade-wg17.mjs",
64
50
  "benchmark": "node test/benchmark.mjs",
65
- "benchmark:lips": "node test/lips-benchmark.mjs",
66
- "benchmark:baseline": "node test/benchmark.mjs --save .benchmarks/baseline.json",
67
- "conformance:update": "npm run conformance:update:wg17 && npm run conformance:update:neumerkel",
68
- "conformance:report": "node test/run-conformance-report.mjs conformance-report.md",
69
51
  "generate": "node tools/generate-library-autoload-index.mjs && node tools/generate-predicate-reference.mjs && node tools/extract-book-examples.mjs",
70
- "generate:autoload": "node tools/generate-library-autoload-index.mjs",
71
- "generate:reference": "node tools/generate-predicate-reference.mjs",
72
- "preversion": "npm test && npm run conformance:sync:neumerkel && npm run conformance:report && git add test/conformance/NEUMERKEL-LATEST.md conformance-report.md",
73
- "postversion": "git push origin HEAD --follow-tags",
74
- "conformance:update:wg17": "node tools/upgrade-wg17.mjs",
75
- "conformance:update:neumerkel": "node test/run-neumerkel.mjs --update-report",
76
- "conformance:check:neumerkel": "node test/run-neumerkel.mjs --cached --verify-report",
77
- "conformance:sync:neumerkel": "node test/run-neumerkel.mjs --cached --update-report",
78
- "test:http-json": "node test/run-http-json.mjs",
79
- "test:iso-part2-amendment": "node test/run-iso-part2-amendment.mjs"
52
+ "preversion": "npm test && node test/run-neumerkel.mjs --cached --update-report && node test/run-conformance-report.mjs conformance-report.md && git add test/conformance/NEUMERKEL-LATEST.md conformance-report.md",
53
+ "postversion": "git push origin HEAD --follow-tags"
80
54
  }
81
55
  }
package/test/README.md ADDED
@@ -0,0 +1,33 @@
1
+ # Test runners
2
+
3
+ Normally, run `npm test`. Use `npm test -- --offline` to skip live upstream fetching.
4
+ `npm run conformance:update:wg17` refreshes the vendored WG17 snapshot.
5
+
6
+ For development, run a focused check directly from the repository root:
7
+
8
+ ```sh
9
+ node test/run-regression.mjs # all regression sections
10
+ node test/run-regression.mjs docs # documentation checks only
11
+ node test/run-conformance-all.mjs # conformance layers; accepts --offline
12
+ node test/run-wg17.mjs # vendored WG17 syntax
13
+ node test/run-iso-strict.mjs # strict ISO core
14
+ node test/run-iso-part2-amendment.mjs
15
+ node test/run-neumerkel.mjs # live upstream; --cached reproduces last fetch
16
+ node test/run-neumerkel-tests.mjs # upstream-fetch harness
17
+ node test/run-examples.mjs
18
+ node test/run-playground.mjs
19
+ node test/run-architecture.mjs
20
+ node test/run-openrulebench.mjs
21
+ node test/run-http-json.mjs
22
+ node test/run-interop.mjs # requires the comparison engines
23
+ node test/run-benchmark-tests.mjs # benchmark harness
24
+ ```
25
+
26
+ These runners retain their existing options; there is no separate npm alias for
27
+ each one. Focused checks do not replace the full release gate.
28
+
29
+ For performance measurements, use `npm run benchmark`. Save a local baseline with
30
+ `npm run benchmark -- --save .benchmarks/baseline.json`, or run
31
+ `node test/lips-benchmark.mjs` for the classic LIPS measurement.
32
+
33
+ See the [conformance guide](conformance/README.md) for report maintenance.
@@ -102,7 +102,7 @@ if (baselinePath != null) {
102
102
  const loaded = JSON.parse(await fs.readFile(baselinePath, 'utf8'));
103
103
  if (loaded.format === 2) baseline = loaded;
104
104
  else {
105
- baselineWarning = `Ignoring legacy timing baseline format ${loaded.format ?? 'unknown'}; regenerate it with npm run benchmark:baseline.`;
105
+ baselineWarning = `Ignoring legacy timing baseline format ${loaded.format ?? 'unknown'}; regenerate it with npm run benchmark -- --save .benchmarks/baseline.json.`;
106
106
  baselinePath = null;
107
107
  }
108
108
  }
@@ -296,10 +296,10 @@ if (options.json) {
296
296
 
297
297
  process.stdout.write(`\n${results.length} benchmarks; ${options.runs} measured batch${options.runs === 1 ? '' : 'es'} each after ${options.warmup} warm-up batch${options.warmup === 1 ? '' : 'es'}, calibrated after one priming execution toward ${options.targetMs} ms per batch.\n`);
298
298
  if (baselineWarning) process.stdout.write(`${baselineWarning}\n`);
299
- if (baselinePath == null && !baselineWarning) process.stdout.write('No timing baseline found; run npm run benchmark:baseline to create .benchmarks/baseline.json.\n');
299
+ if (baselinePath == null && !baselineWarning) process.stdout.write('No timing baseline found; run npm run benchmark -- --save .benchmarks/baseline.json to create .benchmarks/baseline.json.\n');
300
300
  if (options.save != null) process.stdout.write(`Saved timing baseline: ${path.relative(root, options.save)}\n`);
301
301
  process.stdout.write('Change compares the current median/op directly with the saved baseline median/op; the measured range is shown separately.\n');
302
- process.stdout.write('The classic-nrev LIPS column is a quick wall-clock estimate. Use npm run benchmark:lips for the Quintus-style dummy-subtracted CPU measurement.\n');
302
+ process.stdout.write('The classic-nrev LIPS column is a quick wall-clock estimate. Use node test/lips-benchmark.mjs for the Quintus-style dummy-subtracted CPU measurement.\n');
303
303
  if (summary.comparable > 0) {
304
304
  process.stdout.write('Suite score is the geometric mean of current/baseline ratios, so every benchmark has equal relative weight; Time-weighted total compares summed medians and is dominated by longer workloads.\n');
305
305
  }
@@ -23,7 +23,7 @@ relevant implementation-dependent overlap choice to an explicit review outcome.
23
23
 
24
24
  | Requirement | Status | EyeProlog evidence / remaining work |
25
25
  | --- | --- | --- |
26
- | 5.1(a) prepare conforming Prolog text | covered | Clause 6 token/term production and rejection families are mapped by the strict production gate and the complete 366-case vendored WG17 syntax matrix; 7.4 preparation/directive behavior is separately closed. |
26
+ | 5.1(a) prepare conforming Prolog text | covered | Clause 6 token/term production and rejection families are mapped by the strict production gate and the complete 379-case vendored WG17 syntax matrix; 7.4 preparation/directive behavior is separately closed. |
27
27
  | 5.1(b) execute conforming Prolog goals | covered | Clause 7 term/control/execution semantics, the higher-level 7.10 stream model, 7.11 flags, the 7.12 error envelope, all 8.2-8.17 built-in rows, and Clause 9 arithmetic have explicit executable dispositions. |
28
28
  | 5.1(c) reject nonconforming text/read-terms | covered | WG17 negative syntax cases, focused malformed-production/escape/comment/operator cases, read-term syntax errors, and strict rejection of implementation-specific syntax provide explicit rejection evidence across the Clause 6 families. |
29
29
  | 5.1(d) document permitted variations | covered | The clause-by-clause [ISO 5.4 decision index](ISO-IMPLEMENTATION-DEFINED.md) records every explicit implementation-defined decision found in the Part 1 + Corrigenda baseline and separately inventories implementation-specific extension families. The index retains an `gap` category for future discoveries; no current release-facing row depends on one. |
@@ -35,7 +35,7 @@ relevant implementation-dependent overlap choice to an explicit review outcome.
35
35
 
36
36
  | Standard area | Status | Current evidence |
37
37
  | --- | --- | --- |
38
- | Clause 6 — tokens, terms, lists, operators, quoted text | covered | The strict Clause 6 gate maps atomic/variable/compound/operator/list/curly/double-quoted term forms, layout/comments, quoted escapes, integer bases/character codes, floating tokens, and solo/meta token boundaries, with malformed counterparts rejected. The complete 366-case WG17 syntax matrix provides externally sourced production-level expectations, and a cross-profile gate verifies that normal syntax extensions preserve every strict-accepted standard observation. The implementation-defined 6.5/6.6 PCS/collation choices are documented separately. |
38
+ | Clause 6 — tokens, terms, lists, operators, quoted text | covered | The strict Clause 6 gate maps atomic/variable/compound/operator/list/curly/double-quoted term forms, layout/comments, quoted escapes, integer bases/character codes, floating tokens, and solo/meta token boundaries, with malformed counterparts rejected. The complete 379-case WG17 syntax matrix provides externally sourced production-level expectations, and a cross-profile gate verifies that normal syntax extensions preserve every strict-accepted standard observation. The implementation-defined 6.5/6.6 PCS/collation choices are documented separately. |
39
39
  | 7.1-7.3 — term types, term order, unification | covered | [ISO-TERM-SEMANTICS-MATRIX.md](ISO-TERM-SEMANTICS-MATRIX.md) records the five mutually exclusive strict term types, derived variable/compound/list notions, the complete standard-order classes and implementation-dependent variable-order choice, NSTO MGU/failure behavior, and EyeProlog's permitted consistent occurs-check failure for Part 1's undefined STO ordinary-unification cases. The normal JavaScript API string term is documented as a 5.5.4 extension and rejected at strict program/goal entry. |
40
40
  | 7.4 — Prolog text and directives | covered | [ISO-PROLOG-TEXT-EXECUTION-MATRIX.md](ISO-PROLOG-TEXT-EXECUTION-MATRIX.md) closes the preparation/directive rows: declaration semantics, source clauses, cross-text operators/character conversion/flags, initialization order/lifetime, `include/1`, and one-time `ensure_loaded/1`, with implementation-defined cross-text choices indexed under 5.4. |
41
41
  | 7.5-7.6 — database and term/clause conversion | covered | [ISO-PROLOG-TEXT-EXECUTION-MATRIX.md](ISO-PROLOG-TEXT-EXECUTION-MATRIX.md) closes static/dynamic and private/public procedure semantics, clause order, logical-update visibility, empty/unknown lifetime, and term/body/clause conversion. Source and runtime assertion conversion recurse through `,/2`, `;/2`, and `->/2`, preserve variable identity, and protect standardized static/control procedures. |
@@ -286,14 +286,14 @@ architecture gates. The upstream case counts are discovered dynamically.
286
286
  Useful focused commands are:
287
287
 
288
288
  ```sh
289
- npm run test:conformance
290
- npm run test:neumerkel
291
- npm run test:iso
292
- npm run test:wg17
293
- npm run test:offline
289
+ node test/run-conformance-all.mjs
290
+ node test/run-neumerkel.mjs
291
+ node test/run-iso-strict.mjs
292
+ node test/run-wg17.mjs
293
+ npm test -- --offline
294
294
  ```
295
295
 
296
- `test:offline` is a development/reproduction aid; it is not sufficient for a
296
+ `npm test -- --offline` is a development/reproduction aid; it is not sufficient for a
297
297
  release claim that says EyeProlog passes the latest Neumerkel suites. Expected
298
298
  conformance outputs are never auto-accepted.
299
299
 
@@ -307,7 +307,7 @@ post-N289 STC drafts remain review input until standardized.
307
307
  | Criterion | Status | Evidence |
308
308
  | --- | --- | --- |
309
309
  | Clause 5 processor obligations have explicit dispositions | covered | `ISO-PROCESSOR-REQUIREMENTS.md` gives explicit outcomes for 5.1-5.5, including the strict/normal extension boundary. |
310
- | Clause 6 lexical/syntactic requirements have explicit dispositions | covered | the strict Clause 6 production/rejection gate and the 366-case vendored WG17 matrix cover the standard families; cross-profile preservation verifies that normal extensions do not reinterpret strict-accepted text. |
310
+ | Clause 6 lexical/syntactic requirements have explicit dispositions | covered | the strict Clause 6 production/rejection gate and the 379-case vendored WG17 matrix cover the standard families; cross-profile preservation verifies that normal extensions do not reinterpret strict-accepted text. |
311
311
  | Clause 7 semantic requirements have explicit dispositions | covered | 7.1-7.12 now have explicit semantic, implementation-defined, or error-envelope dispositions backed by strict tests and the specialized matrices. |
312
312
  | Clause 8 built-in modes/errors have explicit dispositions | covered | `ISO-BUILTIN-MODE-ERROR-MATRIX.md` accounts for 8.2-8.17 condition by condition, using grouped rows only where every grouped condition and its evidence are named |
313
313
  | Clause 9 evaluable-functor requirements have explicit dispositions | covered | `ISO-EVALUABLE-FUNCTOR-MATRIX.md` plus the strict arithmetic regression closes the published Part 1 + Corrigenda arithmetic rows |
@@ -31,8 +31,8 @@ Counts are output from upstream, not hard-coded test constants.
31
31
  Exact fetched bytes, SHA-256 hashes, fetch timestamps, and HTTP validators remain under
32
32
  Git-ignored `.cache/neumerkel/` for local inspection/reproduction and are intentionally not committed.
33
33
  A normal test run warns when this tracked report is stale. Refresh directly from live
34
- upstream with `npm run conformance:update:neumerkel`, or sync the exact successful
35
- snapshot already fetched by `npm test` with `npm run conformance:sync:neumerkel`.
36
- `npm run conformance:check:neumerkel` verifies the tracked report against that last
34
+ upstream with `node test/run-neumerkel.mjs --update-report`, or sync the exact successful
35
+ snapshot already fetched by `npm test` with `node test/run-neumerkel.mjs --cached --update-report`.
36
+ `node test/run-neumerkel.mjs --cached --verify-report` verifies the tracked report against that last
37
37
  successful live snapshot without fetching upstream a second time.
38
38
 
@@ -4,7 +4,7 @@ EyeProlog treats Ulrich Neumerkel's current ISO/WG17 conformity material as a
4
4
  moving upstream release gate, not as a frozen snapshot with permanent case
5
5
  counts.
6
6
 
7
- `npm run test:neumerkel` fetches these seven TU Wien sources on every live run:
7
+ `node test/run-neumerkel.mjs` fetches these seven TU Wien sources on every live run:
8
8
 
9
9
  1. `conformity_testing` — Part 1 syntax/reader/writer matrix;
10
10
  2. `number_chars_cont_quad.pl` — `number_chars/2` continuation corpus;
@@ -32,13 +32,13 @@ Markdown no longer matches, the test still reflects engine conformance and print
32
32
  warning with the refresh command:
33
33
 
34
34
  ```sh
35
- npm run conformance:update:neumerkel
35
+ node test/run-neumerkel.mjs --update-report
36
36
  ```
37
37
 
38
38
  Commit the resulting `test/conformance/NEUMERKEL-LATEST.md` after reviewing the
39
- change. After a successful `npm test`, `npm run conformance:sync:neumerkel` writes
39
+ change. After a successful `npm test`, `node test/run-neumerkel.mjs --cached --update-report` writes
40
40
  the tracked report from the exact cached source bytes that just passed, avoiding a
41
- second network fetch. `npm run conformance:check:neumerkel` verifies the tracked
41
+ second network fetch. `node test/run-neumerkel.mjs --cached --verify-report` verifies the tracked
42
42
  report against that same last successful snapshot. The npm version lifecycle
43
43
  uses this race-free sync path and stages the generated reports into the release
44
44
  commit. The tracked report intentionally omits fetch timestamps and HTTP validators,
@@ -53,14 +53,14 @@ Git-ignored: it is an inspection/reproduction cache, not published project evide
53
53
  Use:
54
54
 
55
55
  ```sh
56
- npm run test:neumerkel
56
+ node test/run-neumerkel.mjs
57
57
  ```
58
58
 
59
59
  for the canonical live check. For offline reproduction of the exact last live
60
60
  fetch, use:
61
61
 
62
62
  ```sh
63
- npm run test:neumerkel:cached
63
+ node test/run-neumerkel.mjs --cached
64
64
  ```
65
65
 
66
66
  The cached command never claims to check the latest upstream suites by itself.
@@ -83,39 +83,39 @@ npm test
83
83
  For an offline-only development pass:
84
84
 
85
85
  ```sh
86
- npm run test:offline
86
+ npm test -- --offline
87
87
  ```
88
88
 
89
89
  The conformance commands are:
90
90
 
91
91
  ```sh
92
- npm run test:conformance # live Neumerkel + local ISO/conformance layers
93
- npm run test:conformance:offline # same local layers, no network
94
- npm run test:neumerkel # the seven live upstream suites only
95
- npm run test:neumerkel:cached # exact last fetched bytes; reproduction only
96
- npm run conformance:check:neumerkel # verify tracked report against last successful live snapshot
97
- npm run test:iso # Part 1 + Corrigenda strict-core processor gate
98
- npm run test:iso-part2-amendment # 2013 Part 2 amendment module requirements
99
- npm run test:wg17 # vendored reviewed WG17 syntax regression
92
+ node test/run-conformance-all.mjs # live Neumerkel + local ISO/conformance layers
93
+ node test/run-conformance-all.mjs --offline # same local layers, no network
94
+ node test/run-neumerkel.mjs # the seven live upstream suites only
95
+ node test/run-neumerkel.mjs --cached # exact last fetched bytes; reproduction only
96
+ node test/run-neumerkel.mjs --cached --verify-report # verify tracked report against last successful live snapshot
97
+ node test/run-iso-strict.mjs # Part 1 + Corrigenda strict-core processor gate
98
+ node test/run-iso-part2-amendment.mjs # 2013 Part 2 amendment module requirements
99
+ node test/run-wg17.mjs # vendored reviewed WG17 syntax regression
100
100
  ```
101
101
 
102
- `test:neumerkel` always fetches the current TU Wien sources. It does not skip a
103
- fetch because a cache exists. The runner discovers the number of active tests from those sources and fails on any newly introduced case EyeProlog does not pass. If the stable, tracked [NEUMERKEL-LATEST.md](NEUMERKEL-LATEST.md) is stale, normal tests warn rather than turning a passing engine run into a failure. `npm run conformance:update:neumerkel` performs a fresh live run and refreshes the report; after `npm test`, `npm run conformance:sync:neumerkel` refreshes it from the exact successful cached snapshot; and `npm run conformance:check:neumerkel` verifies that snapshot without a second network fetch. Exact bytes, SHA-256 hashes, timestamps, and HTTP validators stay under Git-ignored `.cache/neumerkel/` for inspection/reproduction only. See [NEUMERKEL-LIVE.md](NEUMERKEL-LIVE.md).
102
+ `node test/run-neumerkel.mjs` always fetches the current TU Wien sources. It does not skip a
103
+ fetch because a cache exists. The runner discovers the number of active tests from those sources and fails on any newly introduced case EyeProlog does not pass. If the stable, tracked [NEUMERKEL-LATEST.md](NEUMERKEL-LATEST.md) is stale, normal tests warn rather than turning a passing engine run into a failure. `node test/run-neumerkel.mjs --update-report` performs a fresh live run and refreshes the report; after `npm test`, `node test/run-neumerkel.mjs --cached --update-report` refreshes it from the exact successful cached snapshot; and `node test/run-neumerkel.mjs --cached --verify-report` verifies that snapshot without a second network fetch. Exact bytes, SHA-256 hashes, timestamps, and HTTP validators stay under Git-ignored `.cache/neumerkel/` for inspection/reproduction only. See [NEUMERKEL-LIVE.md](NEUMERKEL-LIVE.md).
104
104
 
105
- The vendored WG17 syntax snapshot is intentionally secondary. Update all upstream conformance evidence with `npm run conformance:update`, or use the focused commands:
105
+ The vendored WG17 syntax snapshot is intentionally secondary. Update it with `npm run conformance:update:wg17`. For focused upstream report maintenance and verification:
106
106
 
107
107
  ```sh
108
108
  npm run conformance:update:wg17
109
- npm run conformance:update:neumerkel
110
- npm run conformance:sync:neumerkel
111
- npm run test:wg17
109
+ node test/run-neumerkel.mjs --update-report
110
+ node test/run-neumerkel.mjs --cached --update-report
111
+ node test/run-wg17.mjs
112
112
  ```
113
113
 
114
114
  Every vendored syntax case is still checked against its upstream Codex
115
115
  expectation; reviewed exact outcomes are an additional regression lock rather
116
116
  than an alternative acceptance rule.
117
117
 
118
- Regenerate the top-level local-corpus report with `npm run conformance:report`. It links to [NEUMERKEL-LATEST.md](NEUMERKEL-LATEST.md) rather than duplicating live evidence. Counts in the tracked Neumerkel report are generated evidence, not permanent constants in project policy.
118
+ Regenerate the top-level local-corpus report with `node test/run-conformance-report.mjs conformance-report.md`. It links to [NEUMERKEL-LATEST.md](NEUMERKEL-LATEST.md) rather than duplicating live evidence. Counts in the tracked Neumerkel report are generated evidence, not permanent constants in project policy.
119
119
 
120
120
  Run a matching local file-based conformance subset directly with:
121
121
 
@@ -1,7 +1,7 @@
1
1
  # WG17 syntax traceability status
2
2
 
3
3
  Source: [Conformity Testing I: Syntax](https://www.complang.tuwien.ac.at/ulrich/iso-prolog/conformity_testing)
4
- Upstream inventory checked: 2026-09-01
4
+ Upstream inventory checked: 2026-09-10
5
5
 
6
6
  This ledger counts an upstream case when its WG17 identifier, query, and
7
7
  expected ISO disposition are stored in the offline executable matrix. Every
@@ -20,25 +20,25 @@ A represented case still has to pass the executable upstream assertion in
20
20
 
21
21
  | Measure | Count |
22
22
  | --- | ---: |
23
- | Active upstream cases | 366 |
24
- | Upstream cases represented in the executable matrix | 366 (100.0%) |
23
+ | Active upstream cases | 379 |
24
+ | Upstream cases represented in the executable matrix | 379 (100.0%) |
25
25
  | Not yet traced one-by-one | 0 |
26
26
  | Deleted upstream identifiers | #20, #273 |
27
27
 
28
28
  The matrix runs in strict ISO stream-reader mode as part of `npm test`. The
29
29
  3 upstream `waits` cases are checked through EyeProlog's interactive input
30
- hook. All 366 executable cases are independently checked against the
30
+ hook. All 379 executable cases are independently checked against the
31
31
  upstream Codex expectation. 365 cases additionally retain exact reviewed
32
- outcomes for stronger regression checking; 1 case currently relies on the upstream assertion alone.
32
+ outcomes for stronger regression checking; 14 cases currently rely on the upstream assertion alone.
33
33
 
34
34
  ## Traceable evidence
35
35
 
36
36
  | Executable evidence | Referenced IDs | WG17 cases |
37
37
  | --- | ---: | --- |
38
- | [complete offline executable matrix](../run-wg17.mjs) | 366 | #1–#19, #21–#272, #274–#368 |
38
+ | [complete offline executable matrix](../run-wg17.mjs) | 379 | #1–#19, #21–#272, #274–#381 |
39
39
 
40
- The evidence groups overlap. Their union is **366** active cases:
41
- #1–#19, #21–#272, #274–#368.
40
+ The evidence groups overlap. Their union is **379** active cases:
41
+ #1–#19, #21–#272, #274–#381.
42
42
 
43
43
  ## Untraced upstream identifiers
44
44
 
@@ -46,6 +46,6 @@ None.
46
46
 
47
47
  ## Maintenance
48
48
 
49
- 1. Run `npm run conformance:update` to reconcile the dated fixture with upstream.
49
+ 1. Run `npm run conformance:update:wg17` to reconcile the dated fixture with upstream.
50
50
  2. Review every new or changed ISO expectation before adding its expected outcome.
51
- 3. Run `npm run test:wg17` and keep this generated status page synchronized.
51
+ 3. Run `node test/run-wg17.mjs` and keep this generated status page synchronized.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "source": "https://www.complang.tuwien.ac.at/ulrich/iso-prolog/conformity_testing",
3
- "checkedOn": "2026-09-01",
3
+ "checkedOn": "2026-09-10",
4
4
  "protocol": "Each query is read and executed in strict ISO mode; /**/ rows reuse the preceding setup.",
5
5
  "cases": [
6
6
  {
@@ -5557,8 +5557,112 @@
5557
5557
  }
5558
5558
  ]
5559
5559
  }
5560
+ },
5561
+ {
5562
+ "id": 369,
5563
+ "query": "writeq(?-).",
5564
+ "input": "writeq(?-).",
5565
+ "readCount": 1,
5566
+ "expected": "?-",
5567
+ "assertion": "upstream"
5568
+ },
5569
+ {
5570
+ "id": 370,
5571
+ "query": "writeq(./*).",
5572
+ "input": "writeq(./*).",
5573
+ "readCount": 1,
5574
+ "expected": "./*",
5575
+ "assertion": "upstream"
5576
+ },
5577
+ {
5578
+ "id": 371,
5579
+ "query": "writeq({*}).",
5580
+ "input": "writeq({*}).",
5581
+ "readCount": 1,
5582
+ "expected": "{*}",
5583
+ "assertion": "upstream"
5584
+ },
5585
+ {
5586
+ "id": 372,
5587
+ "query": "writeq(.*).",
5588
+ "input": "writeq(.*).",
5589
+ "readCount": 1,
5590
+ "expected": ".*",
5591
+ "assertion": "upstream"
5592
+ },
5593
+ {
5594
+ "id": 373,
5595
+ "query": "writeq(...*).",
5596
+ "input": "writeq(...*).",
5597
+ "readCount": 1,
5598
+ "expected": "...*",
5599
+ "assertion": "upstream"
5600
+ },
5601
+ {
5602
+ "id": 374,
5603
+ "query": "[.",
5604
+ "input": "[.",
5605
+ "readCount": 1,
5606
+ "expected": "syntax err.",
5607
+ "assertion": "upstream"
5608
+ },
5609
+ {
5610
+ "id": 375,
5611
+ "query": "{.",
5612
+ "input": "{.",
5613
+ "readCount": 1,
5614
+ "expected": "syntax err.",
5615
+ "assertion": "upstream"
5616
+ },
5617
+ {
5618
+ "id": 376,
5619
+ "query": "writeq(E).",
5620
+ "input": "writeq(E).",
5621
+ "readCount": 1,
5622
+ "expected": "e.g. _1",
5623
+ "assertion": "upstream"
5624
+ },
5625
+ {
5626
+ "id": 377,
5627
+ "query": "write_term(., [ignore_ops(true)]).",
5628
+ "input": "write_term(., [ignore_ops(true)]).",
5629
+ "readCount": 1,
5630
+ "expected": ".",
5631
+ "assertion": "upstream"
5632
+ },
5633
+ {
5634
+ "id": 380,
5635
+ "query": "write_term([' '], [ignore_ops(true)]).",
5636
+ "input": "write_term([' '], [ignore_ops(true)]).",
5637
+ "readCount": 1,
5638
+ "expected": ".( ,[])",
5639
+ "assertion": "upstream"
5640
+ },
5641
+ {
5642
+ "id": 378,
5643
+ "query": "writeq(--> /2).",
5644
+ "input": "writeq(--> /2).",
5645
+ "readCount": 1,
5646
+ "expected": "syntax err.",
5647
+ "assertion": "upstream"
5648
+ },
5649
+ {
5650
+ "id": 379,
5651
+ "query": "writeq((-->)/2).",
5652
+ "input": "writeq((-->)/2).",
5653
+ "readCount": 1,
5654
+ "expected": "(-->)/2",
5655
+ "assertion": "upstream"
5656
+ },
5657
+ {
5658
+ "id": 381,
5659
+ "query": "Finis ().",
5660
+ "input": "Finis ().",
5661
+ "readCount": 1,
5662
+ "expected": "syntax err.",
5663
+ "assertion": "upstream"
5560
5664
  }
5561
5665
  ],
5562
5666
  "sourceRevision": "1.300",
5563
- "sourceSha256": "b6590c1c5269e8e62a1ec72a8f04f21f123a77709854a8a66c436df25f3ff418"
5667
+ "sourceSha256": "db1edfc48e065b90169be55cd93a2a62b32f9dacb2fd0707667590c1a7536e69"
5564
5668
  }
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "source": "https://www.complang.tuwien.ac.at/ulrich/iso-prolog/conformity_testing",
3
- "checkedOn": "2026-09-01",
3
+ "checkedOn": "2026-09-10",
4
4
  "upstream": {
5
5
  "firstId": 1,
6
- "lastId": 368,
6
+ "lastId": 381,
7
7
  "deletedIds": [
8
8
  20,
9
9
  273
10
10
  ],
11
- "activeCases": 366
11
+ "activeCases": 379
12
12
  },
13
13
  "evidence": [
14
14
  {
@@ -59,8 +59,8 @@ async function fetchOne(source) {
59
59
  } catch (error) {
60
60
  throw new Error(
61
61
  `Neumerkel live fetch failed for ${source.key} (${source.url}). ` +
62
- 'The canonical conformance gate requires network access; use npm run test:offline ' +
63
- 'for a network-free local run or npm run test:neumerkel:cached to reproduce the last live fetch.',
62
+ 'The canonical conformance gate requires network access; use npm test -- --offline ' +
63
+ 'for a network-free local run or node test/run-neumerkel.mjs --cached to reproduce the last live fetch.',
64
64
  { cause: error },
65
65
  );
66
66
  }
@@ -88,7 +88,7 @@ async function fetchOne(source) {
88
88
  function readCachedOne(source, cacheDir) {
89
89
  const filename = path.join(cacheDir, source.filename);
90
90
  if (!fs.existsSync(filename)) {
91
- throw new Error(`Neumerkel cache is incomplete: missing ${path.relative(packageRoot, filename)}; run npm run test:neumerkel online first`);
91
+ throw new Error(`Neumerkel cache is incomplete: missing ${path.relative(packageRoot, filename)}; run node test/run-neumerkel.mjs online first`);
92
92
  }
93
93
  const bytes = new Uint8Array(fs.readFileSync(filename));
94
94
  return {
@@ -476,9 +476,9 @@ export function formatNeumerkelMarkdown({ summary }) {
476
476
  'Exact fetched bytes, SHA-256 hashes, fetch timestamps, and HTTP validators remain under',
477
477
  'Git-ignored `.cache/neumerkel/` for local inspection/reproduction and are intentionally not committed.',
478
478
  'A normal test run warns when this tracked report is stale. Refresh directly from live',
479
- 'upstream with `npm run conformance:update:neumerkel`, or sync the exact successful',
480
- 'snapshot already fetched by `npm test` with `npm run conformance:sync:neumerkel`.',
481
- '`npm run conformance:check:neumerkel` verifies the tracked report against that last',
479
+ 'upstream with `node test/run-neumerkel.mjs --update-report`, or sync the exact successful',
480
+ 'snapshot already fetched by `npm test` with `node test/run-neumerkel.mjs --cached --update-report`.',
481
+ '`node test/run-neumerkel.mjs --cached --verify-report` verifies the tracked report against that last',
482
482
  'successful live snapshot without fetching upstream a second time.',
483
483
  '',
484
484
  );
@@ -8,7 +8,7 @@ import { createDefaultRegistry, eyePrologLibraryIndicators, eyePrologNativeLibra
8
8
  import { assertEqual, assertIncludes, assertNotIncludes } from '../test-style.mjs';
9
9
  import { buildConformanceReport, formatConformanceReport } from '../run-conformance-report.mjs';
10
10
  import { renderWg17SyntaxStatus } from '../../tools/report-wg17-syntax-coverage.mjs';
11
- import { parseWg17SyntaxTable } from '../../tools/upgrade-wg17.mjs';
11
+ import { parseWg17SyntaxTable, updateWg17InventoryReferences } from '../../tools/upgrade-wg17.mjs';
12
12
  import { executeWg17Item, matchesUpstreamExpectation, readWg17SyntaxFixture } from '../run-wg17.mjs';
13
13
  import {
14
14
  assertArrayEqual,
@@ -169,6 +169,18 @@ ${profile}`;
169
169
  assertEqual(fs.readFileSync(filename, 'utf8'), renderWg17SyntaxStatus(), 'WG17 syntax status');
170
170
  },
171
171
  },
172
+ {
173
+ name: 'WG17 inventory documentation accepts omitted counts and repairs partial updates',
174
+ run: () => {
175
+ const countFree = 'The vendored WG17 syntax snapshot is intentionally secondary.';
176
+ assertEqual(updateWg17InventoryReferences(countFree, 379), countFree, 'no count required');
177
+ const stale = 'The 366-case vendored WG17 matrix and 365-case WG17 syntax matrix; WG17 matrix has 366 executable cases. An unrelated 366-case suite stays unchanged.';
178
+ const expected = 'The 379-case vendored WG17 matrix and 379-case WG17 syntax matrix; WG17 matrix has 379 executable cases. An unrelated 366-case suite stays unchanged.';
179
+ const updated = updateWg17InventoryReferences(stale, 379);
180
+ assertEqual(updated, expected, 'all stale WG17 counts repaired independently of fixture state');
181
+ assertEqual(updateWg17InventoryReferences(updated, 379), updated, 'retry is idempotent');
182
+ },
183
+ },
172
184
  {
173
185
  name: 'WG17 upgrader accepts omitted HTML table end tags',
174
186
  run: () => {
@@ -637,7 +649,10 @@ ${profile}`;
637
649
  const publishIndex = publishWorkflow.indexOf('run: npm publish');
638
650
  assertEqual(testIndex >= 0 && testIndex < publishIndex, true, 'publish workflow test gate');
639
651
  assertEqual(packIndex >= 0 && packIndex < publishIndex, true, 'publish workflow package gate');
640
- assertEqual(pkg.scripts?.['test:openrulebench'], 'node test/run-openrulebench.mjs', 'OpenRuleBench test script');
652
+ assertArrayEqual(Object.keys(pkg.scripts).sort(), ['benchmark', 'conformance:update:wg17', 'generate', 'postversion', 'preversion', 'test'], 'small npm command surface');
653
+ assertEqual(pkg.scripts.test, 'node test/run-all.mjs', 'full release gate');
654
+ const runner = fs.readFileSync(path.join(packageRoot, 'test', 'run-all.mjs'), 'utf8');
655
+ assertIncludes(runner, 'runOpenRuleBenchChecks(reporter)', 'OpenRuleBench remains in release gate');
641
656
  },
642
657
  },
643
658
  {
@@ -123,17 +123,13 @@ export function runNeumerkelHarnessTests(reporter = new TestReporter()) {
123
123
  reporter.test('release workflow reuses the successful live snapshot instead of refetching', () => {
124
124
  const pkg = JSON.parse(fs.readFileSync(path.join(packageRoot, 'package.json'), 'utf8'));
125
125
  const scripts = pkg.scripts ?? {};
126
- if (scripts['conformance:sync:neumerkel'] !== 'node test/run-neumerkel.mjs --cached --update-report') {
127
- throw new Error('Neumerkel sync must update from the cached successful live snapshot');
128
- }
129
- if (scripts['conformance:check:neumerkel'] !== 'node test/run-neumerkel.mjs --cached --verify-report') {
130
- throw new Error('Neumerkel report check must not refetch live upstream');
131
- }
132
- if (!String(scripts.preversion ?? '').includes('conformance:sync:neumerkel')) {
126
+ const releaseSteps = String(scripts.preversion ?? '').split(' && ');
127
+ if (releaseSteps[0] !== 'npm test' || releaseSteps[1] !== 'node test/run-neumerkel.mjs --cached --update-report') {
133
128
  throw new Error('preversion must synchronize the tracked report from the successful npm test snapshot');
134
129
  }
135
- if (String(scripts.preversion ?? '').includes('conformance:check:neumerkel')) {
136
- throw new Error('preversion must not perform a second report check/fetch cycle after synchronization');
130
+ if (releaseSteps[2] !== 'node test/run-conformance-report.mjs conformance-report.md' ||
131
+ releaseSteps[3] !== 'git add test/conformance/NEUMERKEL-LATEST.md conformance-report.md' || releaseSteps.length !== 4) {
132
+ throw new Error('preversion must generate and stage both reports without another fetch');
137
133
  }
138
134
  });
139
135
 
@@ -29,7 +29,7 @@ function parseArgs(argv) {
29
29
 
30
30
  function printHelp() {
31
31
  process.stdout.write(
32
- 'Usage: npm run test:neumerkel -- [--cached] [--source-dir DIR] [--verify-report] [--update-report]\n\n' +
32
+ 'Usage: node test/run-neumerkel.mjs [--cached] [--source-dir DIR] [--verify-report] [--update-report]\n\n' +
33
33
  'Default: fetch all seven current Neumerkel conformity sources live and run\n' +
34
34
  'every discovered case. A stale tracked report is reported as a warning, not\n' +
35
35
  'an engine-test failure. --verify-report makes report freshness mandatory;\n' +
@@ -54,7 +54,7 @@ export async function runNeumerkel(reporter, options = {}) {
54
54
  if (committed !== result.reportText) {
55
55
  const message =
56
56
  `tracked Neumerkel report is stale: ${relativeReportPath}\n` +
57
- 'Run npm run conformance:update:neumerkel and commit the updated report.';
57
+ 'Run node test/run-neumerkel.mjs --update-report and commit the updated report.';
58
58
  if (effective.verifyReport) throw new Error(message);
59
59
  reporter.stdout.write(`WARN ${message}\n`);
60
60
  } else {
@@ -7034,7 +7034,7 @@ library dependency should be explicit. `--iso-strict` always disables EyeProlog
7034
7034
  library autoloading, so strict ISO execution never gains procedures from this
7035
7035
  implementation convenience.
7036
7036
 
7037
- `-w` / `--warnings` reports explicit dependencies on non-profile libraries and calls to non-profile predicates from otherwise common modules. `--portable` turns those diagnostics into a failing run, making the conservative profile suitable for continuous integration. Cross-engine portability can be exercised with `npm run test:interop` when EyeProlog, Trealla, and Scryer are installed.
7037
+ `-w` / `--warnings` reports explicit dependencies on non-profile libraries and calls to non-profile predicates from otherwise common modules. `--portable` turns those diagnostics into a failing run, making the conservative profile suitable for continuous integration. Cross-engine portability can be exercised with `node test/run-interop.mjs` when EyeProlog, Trealla, and Scryer are installed.
7038
7038
 
7039
7039
  ### Specialized library implementation notes
7040
7040
 
@@ -10339,7 +10339,7 @@ hand.
10339
10339
  Run all 210 normal answer goldens and the 61 selected proof goldens with:
10340
10340
 
10341
10341
  ```sh
10342
- npm run test:examples
10342
+ node test/run-examples.mjs
10343
10343
  ```
10344
10344
 
10345
10345
  Run the complete conformance, regression, example, and proof corpus
@@ -10354,8 +10354,8 @@ and fast:
10354
10354
 
10355
10355
  ```sh
10356
10356
  npm run benchmark
10357
- npm run benchmark:baseline
10358
- npm run benchmark:lips
10357
+ npm run benchmark -- --save .benchmarks/baseline.json
10358
+ node test/lips-benchmark.mjs
10359
10359
  ```
10360
10360
 
10361
10361
  The benchmark suite contains 19 representative workloads and stores their
@@ -10368,7 +10368,7 @@ warm-up batch, five measured batches are reported as milliseconds per workload
10368
10368
  execution. Naturally long workloads keep a batch size of one.
10369
10369
 
10370
10370
  The classic `examples/bench.pl` workload also has a dedicated LIPS harness.
10371
- `npm run benchmark:lips` follows the 1984 Quintus method more closely than the
10371
+ `node test/lips-benchmark.mjs` follows the 1984 Quintus method more closely than the
10372
10372
  generic runner: `dobench/1` and `dodummy/1` execute failure-driven loops inside
10373
10373
  Prolog, the dummy CPU time is subtracted, and the remaining time is converted
10374
10374
  using 496 procedure calls for one reversal of the 30-element list. Node's
@@ -10409,9 +10409,9 @@ lists, terms, atoms, variables, negation, queries, rules, and
10409
10409
  syntax. Separate corpora cover expected errors, warnings, and proofs:
10410
10410
 
10411
10411
  ```sh
10412
- npm run test:conformance
10413
- npm run test:iso
10414
- npm run test:wg17
10412
+ node test/run-conformance-all.mjs
10413
+ node test/run-iso-strict.mjs
10414
+ node test/run-wg17.mjs
10415
10415
  node test/run-conformance-report.mjs
10416
10416
  ```
10417
10417
 
@@ -10452,7 +10452,7 @@ The repository exposes several forms of executable evidence:
10452
10452
  Run the browser contract independently with:
10453
10453
 
10454
10454
  ```sh
10455
- npm run test:playground
10455
+ node test/run-playground.mjs
10456
10456
  ```
10457
10457
 
10458
10458
  ### Supported ISO Prolog implementation
@@ -97,7 +97,7 @@ const text = `// Generated by tools/generate-library-autoload-index.mjs.\n` +
97
97
  if (process.argv.includes('--check')) {
98
98
  const current = fs.existsSync(outputFile) ? fs.readFileSync(outputFile, 'utf8') : '';
99
99
  if (current !== text) {
100
- console.error(`${path.relative(root, outputFile)} is stale; run npm run generate:autoload`);
100
+ console.error(`${path.relative(root, outputFile)} is stale; run npm run generate`);
101
101
  process.exit(1);
102
102
  }
103
103
  console.log(`${path.relative(root, outputFile)} is up to date`);
@@ -209,7 +209,7 @@ const wanted = expectedBook(book, generated);
209
209
 
210
210
  if (process.argv.includes('--check')) {
211
211
  if (wanted !== book) {
212
- console.error(`${path.relative(root, bookFile)} predicate reference is stale; run npm run generate:reference`);
212
+ console.error(`${path.relative(root, bookFile)} predicate reference is stale; run npm run generate`);
213
213
  process.exit(1);
214
214
  }
215
215
  console.log(`${path.relative(root, bookFile)} predicate reference is up to date (${surface.indicators.length} predicates)`);
@@ -134,9 +134,9 @@ ${untracedIds.length === 0 ? 'None.' : `${formatRanges(untracedIds)}.`}
134
134
 
135
135
  ## Maintenance
136
136
 
137
- 1. Run \`npm run conformance:update\` to reconcile the dated fixture with upstream.
137
+ 1. Run \`npm run conformance:update:wg17\` to reconcile the dated fixture with upstream.
138
138
  2. Review every new or changed ISO expectation before adding its expected outcome.
139
- 3. Run \`npm run test:wg17\` and keep this generated status page synchronized.
139
+ 3. Run \`node test/run-wg17.mjs\` and keep this generated status page synchronized.
140
140
  `;
141
141
  }
142
142
 
@@ -158,7 +158,7 @@ if (process.argv[1] != null && path.resolve(process.argv[1]) === fileURLToPath(i
158
158
  if (process.argv.includes('--check')) {
159
159
  const current = fs.readFileSync(statusPath, 'utf8');
160
160
  if (current !== rendered) {
161
- process.stderr.write('WG17 syntax status is stale; run npm run conformance:update and update the file.\n');
161
+ process.stderr.write('WG17 syntax status is stale; run npm run conformance:update:wg17 and update the file.\n');
162
162
  process.exitCode = 1;
163
163
  }
164
164
  } else {
@@ -396,17 +396,20 @@ function formatIdList(ids) {
396
396
  return ids.length === 0 ? 'none' : ids.map((id) => `#${id}`).join(', ');
397
397
  }
398
398
 
399
- function updateDocumentedInventoryCount(previousCount, nextCount) {
400
- if (previousCount === nextCount) return;
399
+ export function updateWg17InventoryReferences(text, nextCount) {
400
+ // Some documents deliberately omit a count. Update only explicit WG17
401
+ // inventory references, independently of the previous fixture count: a
402
+ // retry may follow a partial update that already wrote the new fixture.
403
+ return text
404
+ .replace(/\b\d+-case(?=\s+(?:vendored\s+)?WG17\b)/g, `${nextCount}-case`)
405
+ .replace(/\bWG17 matrix has \d+ executable/g, `WG17 matrix has ${nextCount} executable`);
406
+ }
407
+
408
+ function updateDocumentedInventoryCount(nextCount) {
401
409
  for (const filename of countDocumentationPaths) {
402
410
  const original = fs.readFileSync(filename, 'utf8');
403
- const updated = original
404
- .replaceAll(`${previousCount}-case`, `${nextCount}-case`)
405
- .replaceAll(`WG17 matrix has ${previousCount} executable`, `WG17 matrix has ${nextCount} executable`);
406
- if (updated === original) {
407
- throw new Error(`WG17 inventory count was not found in ${path.relative(packageRoot, filename)}`);
408
- }
409
- fs.writeFileSync(filename, updated);
411
+ const updated = updateWg17InventoryReferences(original, nextCount);
412
+ if (updated !== original) fs.writeFileSync(filename, updated);
410
413
  }
411
414
  }
412
415
 
@@ -454,7 +457,7 @@ function parseArgs(argv) {
454
457
  }
455
458
 
456
459
  function printHelp() {
457
- process.stdout.write(`Usage: npm run conformance:update -- [--check] [--source URL_OR_FILE]\n\n` +
460
+ process.stdout.write(`Usage: npm run conformance:update:wg17 -- [--check] [--source URL_OR_FILE]\n\n` +
458
461
  `Refreshes the vendored WG17 conformity tests from the TU Wien table.\n` +
459
462
  `New or changed rows are executable immediately against the upstream\n` +
460
463
  `Codex expectation; existing reviewed exact outcomes remain pinned only as additional regression checks.\n`);
@@ -474,7 +477,7 @@ export async function upgradeWg17({ check = false, source = syntaxSource } = {})
474
477
 
475
478
  if (check) {
476
479
  if (semanticChanges > 0) {
477
- process.stderr.write('WG17 snapshot is stale; run npm run conformance:update.\n');
480
+ process.stderr.write('WG17 snapshot is stale; run npm run conformance:update:wg17.\n');
478
481
  process.exitCode = 1;
479
482
  return { changed: true, ...reconciliation };
480
483
  }
@@ -507,7 +510,7 @@ export async function upgradeWg17({ check = false, source = syntaxSource } = {})
507
510
  // Generate status after the fixture/manifest are synchronized.
508
511
  const { renderWg17SyntaxStatus } = await import('./report-wg17-syntax-coverage.mjs');
509
512
  fs.writeFileSync(syntaxStatusPath, renderWg17SyntaxStatus());
510
- updateDocumentedInventoryCount(previous.cases.length, fixture.cases.length);
513
+ updateDocumentedInventoryCount(fixture.cases.length);
511
514
 
512
515
  // The public report executes the refreshed fixture, so keep its measured row
513
516
  // synchronized as part of the same explicit upgrade operation.
@@ -521,7 +524,7 @@ export async function upgradeWg17({ check = false, source = syntaxSource } = {})
521
524
  process.stdout.write(`Updated WG17 snapshot (${fixture.cases.length} cases).\n`);
522
525
  if (upstreamAssertions.length > 0) {
523
526
  process.stdout.write(
524
- `Direct upstream assertions used by test:wg17: ${formatIdList(upstreamAssertions)}\n`,
527
+ `Direct upstream assertions used by the WG17 runner: ${formatIdList(upstreamAssertions)}\n`,
525
528
  );
526
529
  }
527
530
  return { changed: semanticChanges > 0, upstreamAssertions, ...reconciliation };