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:
@@ -192,21 +192,25 @@ If gate passes (ambiguity ≤ 0.20 AND all minimums met):
|
||||
Run AFTER the ambiguity gate passes (you probe edges of clear requirements, not vague
|
||||
ones). Reference: @~/.claude/gsd-core/references/edge-probe.md.
|
||||
|
||||
**Non-English projects — the probe input is translated, the SPEC is not.** The shape cues the
|
||||
classifier matches are **English** word-boundary patterns, so requirement prose written in
|
||||
another language matches nothing, classifies to zero shapes, and lands every row in
|
||||
`unclassified` (#1110) — the taxonomy contributes nothing and `--auto` leaves it all
|
||||
`unresolved`. The `$REQS_JSON` payload below is **engine input, never user-facing output**, so
|
||||
the `response_language` rule at the top of this workflow does not govern it: write each
|
||||
requirement's `text` as a faithful **English** translation of the SPEC requirement.
|
||||
The SPEC keeps the original language — only the probe input is translated, and requirement
|
||||
**Non-English projects — `text_en` carries the classifier-facing translation; the SPEC is
|
||||
not.** The shape cues the classifier matches are **English** word-boundary patterns, so
|
||||
requirement prose written in another language matches nothing, classifies to zero shapes, and
|
||||
lands every row in `unclassified` (#1110) — the taxonomy contributes nothing and `--auto`
|
||||
leaves it all `unresolved`. When this project has `response_language` set, add an optional
|
||||
`text_en` key to each `$REQS_JSON` entry: a faithful **English** translation of that
|
||||
requirement's `text`. `text_en` is **engine input, never user-facing output**, so the
|
||||
`response_language` rule at the top of this workflow does not govern it — but `text` itself is
|
||||
NOT translated: write it as the requirement's own text, exactly as it appears in the SPEC.
|
||||
The SPEC keeps the original language — only `text_en` is translated, and requirement
|
||||
`id`s are never translated or renumbered (coverage rows join back on `id`, and any Acceptance
|
||||
Criteria you write from the resolved edges go into the SPEC in `response_language`).
|
||||
Translate **every** requirement, not only the ones that look edge-relevant: the
|
||||
Criteria you write from the resolved edges go into the SPEC in `response_language`). Populate
|
||||
`text_en` for **every** requirement, not only the ones that look edge-relevant: the
|
||||
`$APPLICABLE = 0` warning below fires only when *all* requirements are unclassified, so a
|
||||
partly-classified spec slips through with no signal at all.
|
||||
If a requirement still classifies to zero shapes after translation, it carries no cue in any
|
||||
language (the recorded recall gap — ADR-857 §98 / ADR-550 D7b, not a translation failure);
|
||||
partly-classified spec slips through with no signal at all. When `response_language` is unset
|
||||
(an English-language project), omit `text_en` — `text` is already English and the engine
|
||||
falls back to it automatically (`text_en ?? text`).
|
||||
If a requirement still classifies to zero shapes with `text_en` populated, it carries no cue in
|
||||
any language (the recorded recall gap — ADR-857 §98 / ADR-550 D7b, not a translation failure);
|
||||
author an explicit `shapes` array on that requirement instead of relying on the prose
|
||||
classifier.
|
||||
|
||||
@@ -251,11 +255,12 @@ fi
|
||||
|
||||
# Write the Requirements gathered in THIS spec session to a temp JSON, then invoke the
|
||||
# canonical coverage compute. Populate the heredoc from the SPEC's Requirements — one object
|
||||
# per requirement: {"id","text","shapes"?}. This is the load-bearing step: an empty file makes
|
||||
# the probe a no-op, so the guard below fails loud rather than silently skipping (RR-04).
|
||||
# When `response_language` is set, `text` carries a faithful ENGLISH translation (see above) —
|
||||
# the shape cues are English-only, so original-language prose classifies to zero shapes and the
|
||||
# probe becomes a silent no-op. Keep every `id` exactly as it appears in the SPEC.
|
||||
# per requirement: {"id","text","text_en"?,"shapes"?}. This is the load-bearing step: an empty
|
||||
# file makes the probe a no-op, so the guard below fails loud rather than silently skipping
|
||||
# (RR-04). When `response_language` is set, ALSO add `text_en` — a faithful ENGLISH
|
||||
# translation of `text` (see above) — the shape cues are English-only, so original-language
|
||||
# `text` alone classifies to zero shapes; `text` itself stays the SPEC's own requirement text
|
||||
# and is never translated. Keep every `id` exactly as it appears in the SPEC.
|
||||
# BSD/macOS mktemp only randomizes XXXXXX when it is the final path component, so make a
|
||||
# suffixless temp then append the extension — portable across BSD + GNU (#1520).
|
||||
REQS_JSON=$(mktemp "${TMPDIR:-/tmp}/edge-probe-reqs-XXXXXX") && mv "$REQS_JSON" "${REQS_JSON}.json" && REQS_JSON="${REQS_JSON}.json" || exit 1
|
||||
|
||||
Reference in New Issue
Block a user