cuke-dedup 0.12.2

Static analysis for duplicate and reusable Cucumber step definitions
Documentation
# Ruby parity corpus

`manifest.json` maps every case in the behavior and recall manifests to a Ruby fixture or an explicit remaining requirement. `unit-cases.json` additionally inventories the existing Rust tests outside the Ruby-specific suites, including shared analysis, CLI and reporting contracts. This is a superset for classification, not a count of distinct missing capabilities. Several tests may map to one behavioral fixture; a language-specific exception needs a rationale. Inventory coverage alone does not establish implementation parity.

Build the current analyzer with `cargo build --locked`, then run `npm run corpus:ruby -- target/debug/cuke-dedup`. The checker runs each group in a temporary directory without executing Ruby. It checks extraction counts, strict completeness and exit status, required and forbidden findings, exact ownership of every active finding, and declared threshold results. It exits nonzero while any desired outcome fails or any required mapping remains open. A supported uncertainty control can intentionally require incomplete analysis; that differs from an unexpected incomplete result in a supported positive case.

For inventory maintenance only, run `node scripts/check/check-ruby-parity.mjs --inventory-only`. This validates references, source checksums, test-file enumeration and oracle structure without running the analyzer; success is not Ruby acceptance. Source case hashes use UTF-8 `JSON.stringify` with recursively sorted object keys, preserving array order. Unit inventory hashes cover source text with CRLF normalized to LF. A stale hash requires reassessing the mapping, not merely regenerating it.

The root manifest and its `groupManifests` contain desired outcomes. Set `CUKE_DEDUP_RUBY_PARITY_REPORT` to a local output path to save observations, the binary checksum and remaining blockers separately. Never rewrite desired outcomes from current analyzer output to make the command pass. A `covered` mapping means its fixture contains the intended contract; it is validated only when that fixture passes. `partial`, `missing-fixture`, `unmapped` and `contract-design-required` remain completion work.

The initial executable groups cover registration positives and conflicts, uncertainty and shadowing, matcher and polarity controls, indirect usage, source loading, project discovery, ignore rules, thresholds, dialects, shared Gherkin constructs, ambiguity identity and cluster boundaries. Similarity positives deliberately remain required even while Ruby comparison support is incomplete. Undeclared assertion-like methods do not gain assertion trust; trusted providers require their own provenance matrix. The helper-dispatch fixture (`load-helper-dispatch-contained`) pins that dynamic dispatch inside an instance method of a suite class keeps registrations when the suite's load-time code is only registrations, literal loads and plain class definitions. These fixtures are original synthetic inputs and contain no copied third-party suite code.

Unit census entries classify every Rust test through `behaviorClass` (`ruby-behavior`, `shared-engine-invariant`, `language-specific-api`); the class decides which statuses may close the entry, and every closing status names its evidence so one entry can be objected to without the others:

| requiredStatus | allowed for | evidence |
| --- | --- | --- |
| `covered` | every class | `fixtureGroup` plus an `oracleClause` that resolves in that group's oracle: `requiredFindings[i]`, `requiredAbsent[i]`, `expectedCandidateSources.<rule>`, or a non-empty `duplication`. Scalar oracle fields (`complete`, `expectedExit`, counts) are not clauses: every oracle has them and many share their values |
| `rust-evidence` | ruby-behavior, shared-engine-invariant | `evidenceTest` {file, name}: a test whose body text names a `.rb` path, `SourceLanguage::Ruby`, `CucumberRuby`, a `ruby_`-prefixed helper or `cucumber.yml` (the scan is textual: string literals and comments count, the test's own name does not), or any test of a Ruby-only suite (`tests/ruby.rs`, `tests/ruby_assertion_provenance.rs`, `src/ruby/**`; neither integration suite is inventoried, and a test there may carry further attributes after `#[test]`). The target must be settled Ruby evidence: a `ruby-behavior` test that is its own evidence (a test may cite itself), or a `covered` Ruby test; a `partial` or `unmapped` target, or a chain to a different test, is rejected. Files must be addressable by cargo (`tests/<suite>.rs` or a `src/` module, not `src/main.rs`). Completion executes the referenced tests last, once per cargo binary with `--exact`, building into `target/evidence` so the analyzed binary is never rewritten, with a 30-minute timeout per batch, and requires exactly the requested passes; a failure names the dependent entries of each test that failed or did not run, with the `test result` line; every batch, passing or not, is checked per requested test against libtest's `test <name> ... <outcome>` lines (including the `- should panic` and `ignored, <reason>` variants) printed before the `failures:` section, and the last `test result` summary; a run whose `test result` summary is missing, disagrees with its per-test lines, or disagrees with cargo's exit status (timeout, crash, signal, nonzero exit after `ok`) blames every test in the batch |
| `engine-invariant` | shared-engine-invariant | `stage`, one of the adapter-independent stages whose inputs carry no adapter-produced data: `sha256`, `bounded-io`, `bounded-analysis`, `regex-compiler`, `gherkin-parser`, `cli-parsing`, `config-decoding`, `model-codec`, `lcs-math`, `matcher-text`, `api-shape`, `session-plumbing` |
| `language-inapplicable` | language-specific-api | a rationale starting `JS/TS-only: <API>.` and ending with `Semantic residue:` followed by `none` or the Ruby group (or `group-*` family), Ruby-only suite or non-excluded census entry that carries the shared behavior; the checker rejects any other rationale |
| `partial`, `unmapped` | every class | block completion; `partial` must carry a rationale naming the missing Ruby evidence. Fields of another status (`fixtureGroup`, `oracleClause`, `stage`, `evidenceTest`) are rejected as stale |

A language-specific API label does not exempt its underlying behavior from Ruby coverage: a test whose behavior is shared is reclassified and mapped, not excluded. Ruby and shared-engine tests can never be `language-inapplicable`. Consumers of adapter-produced data (candidate budgets, baselines and their fingerprints, changed mode and its path decoding, diagnostics routing, reporters) are never `engine-invariant`; they need Ruby evidence. The one exception is a property no source text can reach: a check on values the engine computes or the caller constructs, never on adapter output (re-validation of a mutated `Config` with empty inputs, fingerprint framing and member ordering over analyzer-computed hashes). Its rationale names why source cannot reach it and the Ruby test that pins the end-to-end path. Provider-context cases are executed through `provider-context-cases.json`; the local-receiver dispatch outcome is covered by `provider-context-receiver-local-send`.

## Source-corpus equivalents

The 87 source cases now have 86 executable Ruby counterparts and one framework exclusion: Jest-Cucumber scenario-local positional registrations do not have Cucumber Ruby's global registration semantics. Exact JavaScript syntax and framework APIs are excluded at the dimension level; shared binding, provenance, handler, matcher and finding behavior remains covered by Ruby-native controls. The `equivalent-*-cases.json` manifests record those translations and desired outcomes independently of the current analyzer.

Run `node scripts/check/check-ruby-parity.mjs --corpus-coverage-only` to validate this source-corpus coverage gate. It checks exhaustive source references and checksums, counterpart references, exclusions and oracle structure without executing analysis. This gate does not certify implementation parity or classify the Rust unit/integration census. Use the normal Ruby checker for implementation deficits; it intentionally remains nonzero while desired findings, completeness or broader completion requirements fail.

The expanded corpus preserves source configuration and comparison budgets, including required candidate counts. Assertion providers are original synthetic modules with explicit configuration where trust is required; an assertion-like method name alone never grants trust. Fixture files are analyzed statically and never run as project code.

## Regression CI

`node scripts/check/check-ruby-parity.mjs --regression target/release/cuke-dedup` checks every executable group against `regressions.json`, including source-corpus coverage and inventory validation. Each disposition pins the normalized outcome and independent-oracle deficits. Known gaps require reasons; changed outcomes, stale dispositions, missing cases and execution errors fail. Reports retain actual observations and their digests for diagnosis. Update dispositions only after reviewing the changed outcomes; do not change the desired oracles to match implementation gaps.

A green regression gate means the recorded behavior is stable, not complete. The normal command still requires every desired outcome and completion mapping to pass. Pending unit-census/design items remain separate. Census freshness is a required CI merge check: changing a Rust test file requires reassessing its mappings and refreshing its recorded hash, even when analyzer outcomes stay stable. Unmapped census items alone do not fail regression. Completion mode also executes every `rust-evidence` test, so it needs a Rust toolchain and compiles the test harness into `target/evidence` on first use. Evidence runs only once every other blocker and group deficit is gone; `--evidence` forces it on a blocked run and is rejected with `--regression`, `--inventory-only` and `--corpus-coverage-only`. The report records `evidenceExecuted` and `evidenceTargetDir`.

The completion command remains the acceptance gate for full Ruby completion. Experimental releases use the regression gate and ordinary release checks, with documented limitations and explicit assessment of serious regressions and security exposure; they do not require the completion census to reach zero.