intentumdiff-diff-expectations
The behavioral oracle for the IntentumDiff engine — a scenario catalogue that says, per diff scenario, exactly what the engine SHOULD produce (which changes, with which labels, and which change_groups
SKILL.mdskills/intentumdiff-diff-expectations/SKILL.md
GitHubIntentumDiff — diff expectations (the behavioral oracle)
Why this exists. IntentumDiff's engine is moving from Python into the Rust core; the Python
analysis/* implementation is legacy being retired, so it can no longer be the reference for
"correct." The reference is now explicit expectations: for a given input, the exact review
output the engine should produce. This catalogue is that reference. It is enforced at two layers
that must agree:
- Python acceptance — a test in
tests/unit/callsSemanticDiffer().diff_strings(...)(or a playground fixture) and asserts the expectedchanges/change_groups. - Rust unit — a
#[cfg(test)]case incrates/rust-core-hostasserts the same expectation on the Rust engine directly.
A scenario is "certified" only when both layers pass and the language is on the
RUST_CERTIFIED_LANGUAGES allowlist (see intentumdiff-dev-loop / the migration plan). When they
disagree, the catalogue wins — fix the code, not the expectation (unless the expectation itself is
wrong, in which case change it here first, then both tests).
What a scenario row contains
| Field | Meaning |
|---|---|
| language | The language id (drives parser + profile family). |
| input | old → new source (or a named playground fixture: _playground_diff(lang) / _diff(lang, "Moved Code"), from tests/fixtures/semanticdiff_examples.json). |
| expected changes | The change_type + node label/type that MUST appear (and MUST NOT — "no leakage" rows). |
| expected groups | change_groups kind + rule_id, surfaced vs suppressed, key metadata (reason, node_ids). |
| why | The intent — what human-meaningful outcome this protects. |
| enforced by | The Python test + the Rust test id. |
The general principles the scenarios encode
These are the recurring intents behind the per-language rows — a change is "right" when it matches the principle, whatever the language:
- Entity-anchored review. An added/changed function/class/form/sub/resource surfaces as one
change keyed on its name (e.g.
multiply,cube,GREET,Multiply-Numbers), not as a spray of body/scaffold add/deletes and not as(anonymous). - Scaffold/expansion churn is suppressed. Body structure (
function_body,block,return_statement,additive_expression, bashexpansion/word/string, delphiexprBinary/ barestatement, module headers likeDemo) must NOT appear as its own add/delete. - Parameter renames are renames, scoped to their routine.
a→x,b→yareREFACTORINGrenames (one each), not signature churn ((function)/(anonymous)CHANGE_SIGNATURE) and not parameter identifiers leaking asADDITIONs. - No false moves. A stationary entity that only shifted because something was added above it is
NOT a
MOVE/REORDER; named subroutines/functions are never "anonymous moves". Genuine moves are oneMOVEwith the entity name and no add/delete leakage. - A changed entity is a MODIFICATION, not suppressed noise. A form/procedure whose body
changed is a
MODIFICATION(or demoted stationary-entity-move), never dropped as "stable noise". - Identity by semantic key for data/resource/query languages. Keyed/resource/query languages match on key/title/field identity (puppet resource title + attribute, SQL field/clause, JSON key), not position — so shifts aren't spurious modifications.
- Style/format equivalence is IGNORED_STYLE with an explainable reason, keyed on node ids,
under a language rule (e.g.
javascript.formatting.call_argument_wrapping_equivalence), never a genericpresentation.ignored_style.*. - Core invariances (style-only shortcut, integer/string/hex canonical equivalence) and guardrails behave as their own certified scenarios — see references/core-behaviors.md.
The catalogue
- references/failing-scenarios.md — the 10 scenarios that are
the current migration targets (RC blockers), grouped by profile family: statement (bash, delphi),
entity/function (abap, dart, elixir, perl), resource (puppet), matcher/presentation (the JS move
- style pair). Each row is the spec a port must satisfy through Rust.
- references/core-behaviors.md — cross-cutting behaviors already green (style-only, invariances, reorder suppression, moved-code, refactor rename, guardrails), captured so the oracle is a coherent whole, not just the failures. "If you see X → expect Y" tables.
- references/text-and-content.md — text / prose / markdown /
config expectations: a changed line is ONE clean line change (char detail in
text_diff, never per-character churn); markdown heading renames de-duplicate to one section change; non-code files surface as Content, not "Behavior". "If you see X → expect Y" tables.
The standard scenario matrix (every language gets the SAME set)
Every language / major file type must have the same canonical scenarios, each asserting the
clean expected output. Structure: an abstract suite tests/unit/scenario_suite.py (Case,
check_case, and the family bases LineScenarioSuite / MarkdownScenarioSuite /
CodeScenarioSuite that define the standard test_* methods), and one implementation file per
language tests/unit/test_scenarios_<lang>.py (class Test<Lang>(…Suite) supplying language,
filename, and a CASES dict). Adding a language = one small file. A scenario a language omits is
absent from CASES and its inherited test skips; a known gap sets xfail=<reason> (the assertion
still runs and fails → visible; flips to XPASS when fixed). Cross-file scenarios (moves/refactors
spanning files) live in tests/unit/test_scenarios_crossfile.py (index-level, via
detect_cross_file_changes).
Line-oriented file types (plain text, markdown, config — generic parser):
add_at_end, add_in_middle, add_at_start, delete_at_end, delete_in_middle,
delete_at_start, modify_line, modify_two_lines, reorder_lines, whitespace_only,
identical; markdown adds add_section_at_end, add_bullet, edit_paragraph_word,
rename_heading, delete_section_body_line, move_section.
Code file types (per language, entity-oriented — same scenario names, entity-level
expectations): add_fn_at_end, add_fn_in_middle, delete_fn_at_end, delete_fn_in_middle,
modify_fn_body, rename_fn, rename_param, move_fn (reorder), add_param (signature),
style_only (reformat). Expected: one entity change keyed on the name; no scaffold/body/param
leakage; renames scoped; no false moves — see the general principles above and
failing-scenarios.md for the per-language node vocabularies.
Rollout: text + markdown are in place (the template); code languages plug into the same harness with per-language snippets, added in batches until every supported language has the full set.
Using this skill
- Porting a language to Rust: read its rows here first; they are the acceptance spec. Implement
in Rust until both the Python acceptance test and the new Rust
#[cfg(test)]case match every row, then add the language to the allowlist and retire its Python profile. - Fixing a diff bug: find/add the scenario row; if the current output disagrees with the row, the code is wrong; if the row is wrong, fix the row (with rationale) then both tests.
- Adding a new language/behavior: add its rows here first (expectations-first), then the two test layers, then the implementation.