enhance(#3717): make the edge probe's shape cues language-aware via an optional text_en field (#4156)

* test(#3717): add failing-first coverage for text_en language-aware classification

Adds unit tests for the not-yet-implemented text_en field on Requirement
(fallback selection, empty/whitespace/non-string rejection, shapes-override
precedence), a SHAPE_CUES/VALID_SHAPES parity guard (RULESET.GENERATIVE-FIX),
and workflow-prose contract tests asserting spec-phase.md Step 5.5 documents
populating text_en for response_language projects. All new tests are RED
until src/edge-probe.cts and the workflow docs are updated.

* feat(#3717): make edge-probe shape classification read an optional text_en field

Requirement gains an optional text_en; classifyShape's own signature stays
untouched (a locked, directly-tested export), and the text_en ?? text
selection is pushed to proposeEdges' single call site instead. text_en is
validated fail-closed: an empty or whitespace-only value throws rather than
silently winning the ?? fallback and degrading classification to zero shapes.

This makes the #2773 doc-only translation convention an explicit,
validatable field instead of an invisible instruction, per the approved
Form-1 scope on #3717.

* docs(#3717): document the text_en field across spec-phase, reference and how-to docs

Updates Step 5.5's response_language instructions, the edge-probe reference
Inputs contract, the FEATURES.md fragment, and the non-English how-to guide
to describe the new text_en field: text keeps the requirement's own wording
in all cases, text_en (when populated) is the engine-only English rendering
the classifier prefers.

* docs(#3717): record the text_en locked-surface change in CONTEXT.md and ADR-550

Updates the Edge Probe Module glossary entry to describe the text_en field
and its fail-closed validation, and appends an ADR-550 amendment recording
why this is additive and does not re-open the #652 LLM-classifier rejection
(text_en is a plain field read by the existing deterministic regex
classifier, not a new model-dependent surface).

* docs(#3717): add changeset fragment and regenerate FEATURES.md

pr:0 placeholder — backfilled with the real PR number after the PR opens.

* docs(#3717): attribute the text_en machine check to engine-level validation, not prose tests

Code-review (Spec axis) finding: the workflow-prose contract tests and the
ADR-550 amendment overclaimed themselves as "the machine check the #2773
doc-only stopgap lacked." That check is actually engine-level
(validateRequirement/classifyShape, covered in tests/edge-probe.test.cjs) —
the prose tests are the same style of assertion #2773 already used. Reworded
both to attribute the claim correctly.

* fix(#3717): rewrap spec-phase.md so the id-unchanged sentence stays on one line

The #3717 rewrite of Step 5.5's response_language paragraph moved a line
break so "requirement `id`s" ended one physical line and "are never
translated" started the next. The pre-existing #2773 regression test
(tests/edge-probe-spec-phase-contract.test.cjs) asserts id + "never
translated" on the SAME line (no \n in between, matching git's own
line-oriented prose), so the reflow silently broke it. Rewrapped so the
sentence lands on one line again, verified against every #2773/#3717
regex assertion in that test file.

Emitted-Drift-Ack-Growth: spec-phase.md — #3717 adds text_en documentation to Step 5.5 (response_language paragraph + REQS_JSON heredoc comment); this growth is this PR's own diff, not incidental drift.

* chore(#3717): backfill changeset PR number

pr:0 -> pr:4156 now that the PR exists.

---------

Co-authored-by: sim <sim@local>
This commit is contained in:
Tom Boucher
2026-09-01 21:39:53 -04:00
committed by GitHub
parent 4dfc46bbe7
commit bf4485ada2
11 changed files with 286 additions and 44 deletions

View File

@@ -14,20 +14,20 @@ The probe classifies each requirement's data/behavior shape by matching **Englis
That is not a rejection you can act on — it looks identical to a genuinely edge-free requirement. When it happens to *every* requirement, the probe has contributed nothing to the spec.
So Step 5.5 sends the probe an English rendering of each requirement, while your spec stays in your language. Concretely, for the same requirement:
So Step 5.5 populates an optional `text_en` field alongside each requirement — a faithful English translation — which the engine reads in preference to `text` for classification (`text_en ?? text`). Your spec's own `text` stays in your language throughout. Concretely, for the same requirement:
| Requirement text handed to the probe | Shapes | Edges raised |
|---|---|---|
| `O sistema mescla intervalos sobrepostos em uma lista ordenada` | none | none — one `unclassified` row |
| `The system merges overlapping intervals in a sorted list` | `collection` | `adjacency`, `empty`, `ordering` |
| Requirement `text` | Requirement `text_en` | Shapes | Edges raised |
|---|---|---|---|
| `O sistema mescla intervalos sobrepostos em uma lista ordenada` | *(absent)* | none | none — one `unclassified` row |
| `O sistema mescla intervalos sobrepostos em uma lista ordenada` | `The system merges overlapping intervals in a sorted list` | `collection` | `adjacency`, `empty`, `ordering` |
## What you do
Nothing extra. The translation happens inside Step 5.5 as part of the run.
Nothing extra. The translation happens inside Step 5.5 as part of the run, populating the engine-only `text_en` field.
What you should *see* is the split: your **spec stays in `response_language`** — its requirements, its acceptance criteria, its `## Edge Coverage` section — while the probe's findings are reasoned about from an English rendering of the requirement text. Requirement ids (`R1`, `R2`, …) are never translated or renumbered, so a finding always names the same requirement you wrote.
What you should *see* is the split: your **spec stays in `response_language`** — its requirements, its acceptance criteria, its `## Edge Coverage` section — while the probe's findings are reasoned about from `text_en`, an English rendering of the requirement text that lives alongside (never in place of) your requirement's own `text`. Requirement ids (`R1`, `R2`, …) are never translated or renumbered, so a finding always names the same requirement you wrote.
If your spec comes back anglicized, that is a bug worth reporting — only the probe's transient input is translated, never the document.
If your spec comes back anglicized, that is a bug worth reporting — only the transient `text_en` field is translated, never the document.
## Tell "no edges here" apart from "the probe could not read it"