feat(#1867): wire ui-consideration probe into ui-phase (WIRE-01)
Add the live ui-phase producer path for the UI-consideration probe (Phase 2, WIRE-01). Two small exports on the Phase-1 adapter — proposeElements (the propose-then-confirm view of detected kinds + applicable categories) and autoResolve (the deterministic --auto floor that never dismisses and never auto-backstops an unclassified item, #1110) — plus a post-verification '## 9.5 UI-Consideration Probe' step in ui-phase.md mirroring spec-phase 5.5's RUNTIME_DIR shim + fatal-invoke/malformed-report/zero-applicable fail-closed guards, propose-then-confirm (the partial-cue recall mitigation), and the '## UI Considerations' write-back in the shipped plan-phase.md:921 lift format. autoResolve is the CODE floor; the covered-upgrade stays workflow prose (the two-layer --auto). Un-upgraded backstops route to insufficient_spec -> human_needed at verify, never a silent pass (#1154). Tests: +8 typed (proposeElements shape/determinism, autoResolve never-dismiss, partial-cue strict-subset) — structured-value only. ui-phase.md size baseline ratcheted 15477->24447 (under DEFAULT cap). The plan-phase.md PRE_PHASE6 ceiling stays RED pending #1852 (unchanged from Phase 1). Claude-Session: https://claude.ai/code/session_01BKt4hgNZwXSeJYJtYAQUSS
This commit is contained in:
@@ -237,6 +237,69 @@ export function analyzeCoverage(
|
||||
return coreAnalyzeCoverage(items, resolutions, UI_VALIDATORS);
|
||||
}
|
||||
|
||||
/**
|
||||
* A per-element propose-then-confirm view (WIRE-01, #1867): the detected element `kinds`, the
|
||||
* `categories` they raise, the proposed `considerations`, and an `unclassified` flag. The ui-phase
|
||||
* probe step surfaces `kinds` to the user so a human can ADD a kind the heuristic missed — the
|
||||
* classifier is a SIGNAL, not ground truth (Goodhart). A single tripped cue on a multi-kind surface
|
||||
* under-covers; the confirm step, not the heuristic, is what makes coverage sound.
|
||||
*/
|
||||
export interface ElementProposal {
|
||||
id: string;
|
||||
kinds: UIElementKind[];
|
||||
categories: string[];
|
||||
considerations: UIConsideration[];
|
||||
unclassified: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the propose-then-confirm view for every element (WIRE-01). Deterministic: a pure function
|
||||
* of the input array (no Date/random/iteration-order surprise), so re-running the probe on an
|
||||
* unchanged UI-SPEC yields byte-identical rows (the idempotency substrate WIRE-02 relies on). An
|
||||
* aggregating VIEW over the existing Phase-1 functions — it adds no new classification logic.
|
||||
*
|
||||
* `unclassified` is true ONLY when prose classified to zero cues (#1110); an explicit `elements: []`
|
||||
* opt-out stays silent (`unclassified: false`, empty considerations), matching proposeConsiderations.
|
||||
*/
|
||||
export function proposeElements(elements: Element[]): ElementProposal[] {
|
||||
return elements.map((el): ElementProposal => {
|
||||
validateRequirement(el);
|
||||
const considerations = proposeConsiderations(el);
|
||||
const kinds: UIElementKind[] = Array.isArray(el.elements)
|
||||
? el.elements // already validated inside proposeConsiderations
|
||||
: classifyElement(el.text as string);
|
||||
const unclassified = !Array.isArray(el.elements) && kinds.length === 0;
|
||||
const categories = unclassified ? [] : applicableCategories(kinds);
|
||||
return { id: el.id, kinds, categories, considerations, unclassified };
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* The deterministic `--auto` resolution FLOOR (WIRE-01, SC2). For each proposed consideration:
|
||||
* - an `unclassified` item stays `unresolved` — NEVER auto-backstopped (a missing cue is not
|
||||
* evidence a consideration applies, #1110);
|
||||
* - every applicable item auto-resolves to a conservative `backstop` (carrying the taxonomy
|
||||
* question as its `resolution` so probe-core's "backstop requires a resolution" check passes).
|
||||
* - it NEVER emits `dismissed` under any branch — a wrong auto-dismissal is the exact silent
|
||||
* failure this probe eliminates (the never-dismiss invariant, asserted on the typed return).
|
||||
*
|
||||
* This is the CODE floor only. It mirrors spec-phase.md Step 5.5's prose `--auto` rule
|
||||
* (auto-`covered` where a defensible acceptance criterion can be written, else auto-`backstop`,
|
||||
* never auto-`dismiss`) but deliberately keeps the covered-vs-backstop JUDGMENT in the ui-phase
|
||||
* workflow (an LLM MAY upgrade an item to `explicit`/covered when it can write a real acceptance
|
||||
* criterion). Encoding the never-dismiss FLOOR in code is what makes the invariant unit-testable;
|
||||
* the covered-upgrade stays prose because "a defensible criterion exists" is not a code predicate.
|
||||
* Keep the two in sync: if spec-phase's `--auto` policy changes, revisit this floor.
|
||||
*/
|
||||
export function autoResolve(items: UIConsideration[]): Resolution<UIVerification>[] {
|
||||
return items.map((item): Resolution<UIVerification> => {
|
||||
if (item.category === UNCLASSIFIED_CATEGORY) {
|
||||
return { requirement_id: item.requirement_id, category: item.category, status: 'unresolved', verification: null, resolution: null, reason: null };
|
||||
}
|
||||
return { requirement_id: item.requirement_id, category: item.category, status: 'resolved', verification: 'backstop', resolution: item.probe, reason: null };
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* CLI entry (invokable surface): `ui-consideration-probe.cjs <elements.json> [resolutions.json]`.
|
||||
* The generic I/O plumbing (parse, fail-closed exit 2, pretty-JSON out) lives in probe-core's
|
||||
|
||||
Reference in New Issue
Block a user