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:
@@ -3212,7 +3212,7 @@ explicit reviewer flags -> --all -> review.default_reviewers -> all detected rev
|
||||
|
||||
When a requirement's prose matches **no** shape cue, the probe does not silently drop it (#1110): it emits a single `unclassified — review manually` candidate so the zero-cue requirement is surfaced for the author to resolve like any other (specify / dismiss-with-reason / defer) — a manual-review nudge, not a hard block.
|
||||
|
||||
**Non-English projects: the probe reads English, the SPEC does not have to (#2773).** The shape cues are English word-boundary patterns, so a project running with [`response_language`](CONFIGURATION.md) set would otherwise have *every* requirement match nothing, classify to zero shapes, and land in `unclassified` — the taxonomy silently contributing nothing to exactly the kind of spec it exists to harden. `spec-phase` Step 5.5 therefore feeds the probe a faithful **English translation** of each requirement's `text`: that payload is engine input, never user-facing output, so it is translated while the SPEC itself stays in the original language, requirement ids are left untouched, and any acceptance criteria written back from the resolved edges return to `response_language`. Translation makes the classifier *applicable*; it does not make it omniscient. A requirement carrying no shape cue in **any** language still classifies to zero — that is the classifier's recorded recall gap (ADR-857 §98), not a translation failure — and the remedy there is the same one an English project uses: author an explicit `shapes` array on the requirement instead of relying on prose classification.
|
||||
**Non-English projects: the probe reads English via `text_en`, the SPEC does not have to (#2773, durable fix #3717).** The shape cues are English word-boundary patterns, so a project running with [`response_language`](CONFIGURATION.md) set would otherwise have *every* requirement match nothing, classify to zero shapes, and land in `unclassified` — the taxonomy silently contributing nothing to exactly the kind of spec it exists to harden. `spec-phase` Step 5.5 therefore populates an optional `text_en` field alongside each requirement's `text` with a faithful **English translation**: `text_en` is engine input, never user-facing output, so it is translated while `text` keeps the requirement's own wording (the SPEC stays in the original language), requirement ids are left untouched, and any acceptance criteria written back from the resolved edges return to `response_language`. Translation makes the classifier *applicable*; it does not make it omniscient. A requirement carrying no shape cue in **any** language still classifies to zero — that is the classifier's recorded recall gap (ADR-857 §98), not a translation failure — and the remedy there is the same one an English project uses: author an explicit `shapes` array on the requirement instead of relying on prose classification.
|
||||
|
||||
The resolved edges populate a `## Edge Coverage` section in `SPEC.md`. Unresolved *applicable* edges trigger a soft gate (Resolve / Write-anyway-flagged / Keep-probing) rather than a hard block. Under `--auto`, the probe **never auto-dismisses** — it auto-covers where a defensible criterion exists, otherwise auto-backstops, and logs `[auto] edge coverage: C covered, B backstop, U unresolved`. The one exception is an `unclassified` candidate: `--auto` leaves it **`unresolved`** (surfaced as a flagged assumption), never auto-`backstop` — a missing shape is not evidence an edge exists, so minting a held-out edge obligation would be a false claim.
|
||||
|
||||
|
||||
@@ -197,3 +197,42 @@ prohibition-enforcement verify-time seam.)
|
||||
this addendum adds to 550's decision record; the other two are cross-references to existing
|
||||
decisions, collected so the probe family's full "alternatives considered" set is readable in
|
||||
one place.
|
||||
|
||||
## Amendment (2026-09-01, #3717): `text_en` field — durable fix for the #2773 language stopgap
|
||||
|
||||
**What changed.** `Requirement` (the Edge Probe Module's locked input shape) gains an optional
|
||||
`text_en: string` field. `classifyShape`'s own signature is unchanged (still `(text: string) =>
|
||||
Shape[]`, a directly-tested export); the `text_en ?? text` selection happens once, at
|
||||
`proposeEdges`' single call site. `validateRequirement` fail-closes on a present-but-empty or
|
||||
whitespace-only `text_en` (an unvalidated `''` would otherwise win `??` silently, since nullish
|
||||
coalescing does not treat `''` as nullish).
|
||||
|
||||
**Why this is a locked-surface change, not a silent one.** `text` previously carried a dual,
|
||||
undocumented meaning under the #2773 stopgap: the requirement's own text for English projects,
|
||||
but silently the *translation* for `response_language` projects (spec-phase Step 5.5 wrote the
|
||||
English rendering into `text`, never the SPEC's own words). `text_en` removes that overload —
|
||||
`text` always means the requirement's own text, in whatever language the SPEC uses; `text_en`,
|
||||
when present, is the engine-only English rendering the classifier prefers.
|
||||
|
||||
**Not a re-open of the #652 rejection (see the Addendum above, `docs/adr/550-spec-phase-probe-contract.md:172-177`).** That addendum rejects a *standalone, model-dependent* LLM
|
||||
classifier surface. `text_en` is a plain optional string field read by the existing
|
||||
deterministic regex classifier — no new model-dependent surface, no change to the taxonomy or
|
||||
`SHAPE_CUES`'s cue-matching mechanism. The maintainer's approval on #3717 confirmed this reading
|
||||
explicitly and scoped the change to Form 1 only (the `text_en` field); per-language `SHAPE_CUES`
|
||||
tables (a form that WAS considered — a `lang` hint + a cue-table-per-language) were evaluated and
|
||||
declined as an unwarranted maintenance burden for a solo-maintainer project. `en` remains the
|
||||
only `SHAPE_CUES` vocabulary.
|
||||
|
||||
**Acceptance bar carried into `tests/edge-probe.test.cjs` / `tests/edge-probe-spec-phase-contract.test.cjs`:** a `SHAPE_CUES`/`VALID_SHAPES` parity assertion (`RULESET.GENERATIVE-FIX`), a fixture
|
||||
pair proving a non-English requirement with `text_en` classifies identically to its English
|
||||
equivalent, boundary coverage for `text_en`'s absence/presence/empty-string cases, and a
|
||||
workflow-prose contract test — in the same style as the existing #2773 Step 5.5 assertions —
|
||||
confirming `spec-phase.md` documents populating `text_en` for `response_language` projects.
|
||||
The actual machine check the #2773 doc-only stopgap lacked is engine-level: `text_en` is now
|
||||
a real, validated `Requirement` field (`validateRequirement` fail-closes on an empty value)
|
||||
that `classifyShape`/`proposeEdges` demonstrably prefer over `text` — a property #2773's
|
||||
prose-only convention had no way to enforce.
|
||||
|
||||
**Explicitly out of scope (unchanged from #2773).** The ADR-857 §98 / Decision 7b recall gap — a
|
||||
requirement carrying no shape cue in *any* language, including English, still classifies to
|
||||
zero shapes — is untouched. The authored `shapes` override remains the remedy there.
|
||||
|
||||
@@ -19,7 +19,7 @@ group: v1.42.1 Features
|
||||
|
||||
When a requirement's prose matches **no** shape cue, the probe does not silently drop it (#1110): it emits a single `unclassified — review manually` candidate so the zero-cue requirement is surfaced for the author to resolve like any other (specify / dismiss-with-reason / defer) — a manual-review nudge, not a hard block.
|
||||
|
||||
**Non-English projects: the probe reads English, the SPEC does not have to (#2773).** The shape cues are English word-boundary patterns, so a project running with [`response_language`](CONFIGURATION.md) set would otherwise have *every* requirement match nothing, classify to zero shapes, and land in `unclassified` — the taxonomy silently contributing nothing to exactly the kind of spec it exists to harden. `spec-phase` Step 5.5 therefore feeds the probe a faithful **English translation** of each requirement's `text`: that payload is engine input, never user-facing output, so it is translated while the SPEC itself stays in the original language, requirement ids are left untouched, and any acceptance criteria written back from the resolved edges return to `response_language`. Translation makes the classifier *applicable*; it does not make it omniscient. A requirement carrying no shape cue in **any** language still classifies to zero — that is the classifier's recorded recall gap (ADR-857 §98), not a translation failure — and the remedy there is the same one an English project uses: author an explicit `shapes` array on the requirement instead of relying on prose classification.
|
||||
**Non-English projects: the probe reads English via `text_en`, the SPEC does not have to (#2773, durable fix #3717).** The shape cues are English word-boundary patterns, so a project running with [`response_language`](CONFIGURATION.md) set would otherwise have *every* requirement match nothing, classify to zero shapes, and land in `unclassified` — the taxonomy silently contributing nothing to exactly the kind of spec it exists to harden. `spec-phase` Step 5.5 therefore populates an optional `text_en` field alongside each requirement's `text` with a faithful **English translation**: `text_en` is engine input, never user-facing output, so it is translated while `text` keeps the requirement's own wording (the SPEC stays in the original language), requirement ids are left untouched, and any acceptance criteria written back from the resolved edges return to `response_language`. Translation makes the classifier *applicable*; it does not make it omniscient. A requirement carrying no shape cue in **any** language still classifies to zero — that is the classifier's recorded recall gap (ADR-857 §98), not a translation failure — and the remedy there is the same one an English project uses: author an explicit `shapes` array on the requirement instead of relying on prose classification.
|
||||
|
||||
The resolved edges populate a `## Edge Coverage` section in `SPEC.md`. Unresolved *applicable* edges trigger a soft gate (Resolve / Write-anyway-flagged / Keep-probing) rather than a hard block. Under `--auto`, the probe **never auto-dismisses** — it auto-covers where a defensible criterion exists, otherwise auto-backstops, and logs `[auto] edge coverage: C covered, B backstop, U unresolved`. The one exception is an `unclassified` candidate: `--auto` leaves it **`unresolved`** (surfaced as a flagged assumption), never auto-`backstop` — a missing shape is not evidence an edge exists, so minting a held-out edge obligation would be a false claim.
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user