diff --git a/.changeset/vivid-orcas-chatter.md b/.changeset/vivid-orcas-chatter.md new file mode 100644 index 000000000..f04fbade6 --- /dev/null +++ b/.changeset/vivid-orcas-chatter.md @@ -0,0 +1,5 @@ +--- +type: Added +pr: 1979 +--- +**`/gsd-ui-phase` now probes UI state coverage** — a new `ui-consideration-probe` (the third `probe-core` adapter) enumerates the shape-rooted UI states a UI-SPEC must resolve (empty/loading/error/populated/partial/overflow/zero-one-many/long-text). After the UI checker approves, the probe surfaces applicable considerations for each element, records a `## UI Considerations` section in the UI-SPEC, and plan-phase lifts each resolved consideration into `must_haves` — so a purely-visual state with no wired test routes to `insufficient_spec → human_needed` at verify rather than a silent pass. diff --git a/.gitignore b/.gitignore index c08717e6c..ce01a3157 100644 --- a/.gitignore +++ b/.gitignore @@ -101,6 +101,7 @@ build/ /gsd-core/bin/lib/probe-core.cjs /gsd-core/bin/lib/spec-section.cjs /gsd-core/bin/lib/prohibition-enforcement.cjs +/gsd-core/bin/lib/ui-consideration-probe.cjs /gsd-core/bin/lib/config-types.cjs /gsd-core/bin/lib/cli-exit.cjs /gsd-core/bin/lib/code-review-flags.cjs diff --git a/CONTEXT.md b/CONTEXT.md index 565add669..0d26b5f29 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -380,12 +380,15 @@ The prompt-level data/instruction isolation seam for untrusted web/document ingr > Glossary prose for these modules lives above (Probe Core / Edge Probe / Prohibition Probe / Verification Tier / Verification substrate). These are the greppable one-line predicates ADR-550's Consequences promised alongside the glossary. Research-derived numbers (N17/N18 rates) are deliberately kept out of this machine-canon and live hedged in `docs/design/verifier-reach.md`. (That design note and `docs/adr/1606` are co-delivered sibling PRs of epic #1605; predicate refs to them below resolve once the batch lands.) `PROBE.principle=verifier-reach-equals-spec-reach (a goal-backward verifier only checks assertions that exist; probes make omitted assertions exist before code) — ADR-857 verification-substrate boundary; docs/design/verifier-reach.md` -`PROBE.family=edge-probe(shape-axis)+prohibition-probe(must-NOT-axis), shared probe-core, run as spec-phase soft gates (ADR-550 D7)` +`PROBE.family=edge-probe(shape-axis)+prohibition-probe(must-NOT-axis)+ui-consideration-probe(UI-state-axis), shared probe-core, run as spec-phase/ui-phase soft gates (ADR-550 D7; #1867)` `PROBE.protocol=recall(adversarial over-generate)->precision(drop routine-engineering); dismissals require a non-empty reason` `PROBE.core.seam=analyzeCoverage(items,resolutions?,validators) ingests ALREADY-proposed items; does NOT assume deterministic propose (ADR-550 D7b)` `PROBE.item.axes=status{resolved|dismissed|unresolved} x verification{|null} — orthogonal; the lifecycle enum carries no verification fact (ADR-550 D7a)` `PROBE.edge.verification=explicit|backstop` `PROBE.prohib.verification=test|judgment` +`PROBE.ui.verification=explicit|backstop` +`PROBE.ui.axis=MIXED — closed compiled shape-rooted 8 (empty/loading/error/populated/partial/overflow/zero-one-many/long-text) via ui-consideration-probe adapter; open UX (real-time/a11y/i18n-RTL) prose-owned in references/domain-probes.md, NOT compiled (#1867)` +`PROBE.ui.seam=ui-phase Step 9.5 post-verification: element-cue classify -> propose-then-confirm (partial-cue mitigation, Goodhart) -> autoResolve --auto floor (never dismiss; unclassified stays unresolved #1110) -> ## UI Considerations write-back -> plan-phase `## UI Considerations` lift rule (#1867)` `PROBE.ci.surface=the contract (parse/validate, projection round-trip, fail-closed guards), NEVER the LLM judgment (ADR-550 D5)` `PROHIB.recall=LLM-prose; no compiled prohibition-probe recall engine (only the schema/projection layer is code, ADR-550 D7b)` `PROHIB.canon-referral=OWASP/GDPR/fairness-canon are REFERRED to /gsd:secure-phase+eslint, never minted as prohibitions (ADR-550 D6)` diff --git a/agents/gsd-ui-checker.md b/agents/gsd-ui-checker.md index 9f2962487..88f475afb 100644 --- a/agents/gsd-ui-checker.md +++ b/agents/gsd-ui-checker.md @@ -52,6 +52,8 @@ This persona is **not a standalone accuracy guarantee**. It is a stance for appl **Anti-capitulation rule (re-verification turns):** If the researcher disagrees with a BLOCK verdict or submits a revised spec, The Auditor re-examines the revised content against the criteria. Researcher disagreement alone is never grounds to downgrade a BLOCK. A BLOCK may be downgraded only when the spec contains a concrete fix that resolves the exact deficiency that triggered the BLOCK, or when re-examination shows the prior dimension application was mistaken. Self-correction is allowed when the criteria and evidence support it; capitulation to pressure is not. "We'll handle it in implementation" or "it's implied" are not concrete fixes. +@~/.claude/gsd-core/references/ui-consideration-probe.md + Before verifying, discover project context: diff --git a/agents/gsd-ui-researcher.md b/agents/gsd-ui-researcher.md index f48b1fb0c..a1b8f6bfe 100644 --- a/agents/gsd-ui-researcher.md +++ b/agents/gsd-ui-researcher.md @@ -28,6 +28,7 @@ If the prompt contains a `` block, you MUST use the `Read` too @~/.claude/gsd-core/references/untrusted-input-boundary.md +@~/.claude/gsd-core/references/ui-consideration-probe.md @~/.claude/gsd-core/references/research-documentation-lookup.md diff --git a/docs/INVENTORY-MANIFEST.json b/docs/INVENTORY-MANIFEST.json index 046d19ce2..aa12f48ed 100644 --- a/docs/INVENTORY-MANIFEST.json +++ b/docs/INVENTORY-MANIFEST.json @@ -275,6 +275,7 @@ "thinking-models-verification.md", "thinking-partner.md", "ui-brand.md", + "ui-consideration-probe.md", "universal-anti-patterns.md", "untrusted-input-boundary.md", "user-profiling.md", @@ -431,6 +432,7 @@ "template.cjs", "uat-predicate.cjs", "uat.cjs", + "ui-consideration-probe.cjs", "ui-safety-gate.cjs", "update-context.cjs", "validate-command-router.cjs", diff --git a/docs/INVENTORY.md b/docs/INVENTORY.md index 63f4d67a9..56109762e 100644 --- a/docs/INVENTORY.md +++ b/docs/INVENTORY.md @@ -312,6 +312,7 @@ Full roster at `gsd-core/references/*.md`. References are shared knowledge docum | `domain-probes.md` | Domain-specific probing questions for discuss-phase. | | `edge-probe.md` | Spec-phase edge-completeness probe — 8-category edge taxonomy, shape classification, and the `requirements → checks → verifier` resolution model (Step 5.5). | | `prohibition-probe.md` | Spec-phase prohibition-completeness probe — the two-stage adversarial-recall → precision protocol that surfaces the unwritten *must-NOT* constraints (values/safety/ethics), with status×verification (`test`/`judgment`) tiering and canon-referral breadcrumbs (Step 5.6); second adapter of the `probe-core` resolution model. | +| `ui-consideration-probe.md` | UI-phase state-completeness probe — the closed shape-rooted UI-state taxonomy (empty/loading/error/populated/partial/overflow/zero-one-many/long-text), element-cue relevance filter, and `{explicit, backstop}` tiering; third adapter of the `probe-core` model (ADR-550 D7), run at ui-phase Step 9.5; the MIXED axis routes open UX (real-time/a11y/i18n-RTL) to `domain-probes.md` (#1867). | | `honest-verifier.md` | Verify-time abstention on non-inferable (`backstop`) truths — the truth-axis mirror of the prohibition judgment-tier disposition (ADR-550 D4): a `backstop` truth the verifier can't confirm with explicit evidence abstains → `human_needed` (reason `insufficient_spec`), never a silent pass (#1154). | | `gate-prompts.md` | Gate/checkpoint prompt templates. | | `loop-hook-dispatch.md` | Generic dispatch contract for consuming `gsd_run loop render-hooks --raw` output in any host-loop workflow — envelope shape, per-kind dispatch rules (contribution/step/gate), and liveness banner. | @@ -516,6 +517,7 @@ Full listing: `gsd-core/bin/lib/*.cjs`. | `normalize-test-command.cjs` | Normalizes a resolved test command to a one-shot form so a watch-mode runner (vitest/jest) cannot hang a verification gate (#1857); shared by all four test-command gates (regression, post-merge, audit-fix, verify-phase) | | `uat.cjs` | UAT file parsing, verification debt tracking, audit-uat support | | `uat-predicate.cjs` | UAT-passed predicate — markdown-aware evaluation of HUMAN-UAT results; returns pass only when all required checks pass; ignores false-positive contexts (frontmatter, fenced code, blockquotes, HTML comments) | +| `ui-consideration-probe.cjs` | Spec-completeness UI-consideration probe (compiled from `src/ui-consideration-probe.cts`, gitignored) — the third adapter of the `probe-core` resolution model (ADR-550 Decision 7): element-kind classification, applicable-category relevance filter, consideration proposal, `proposeElements`/`autoResolve` (propose-then-confirm + the `--auto` never-dismiss floor), and the `{explicit, backstop}` validators; delegates merge/rollup/CLI to `probe-core`; exports `classifyElement`, `applicableCategories`, `proposeConsiderations`, `proposeElements`, `autoResolve`, `analyzeCoverage`, `UI_TAXONOMY` (#1867) | | `ui-safety-gate.cjs` | Shell-free word-boundary UI token detector (#3706, #3718); reads phase-section text from stdin, exits 0 (UI found) or 1 (no UI); also deployed to `gsd-core/bin/lib/` so the GSD installer ships it to `$RUNTIME_DIR` (#448) | | `update-context.cjs` | Pure install-context resolver for `/gsd:update` — runtime/scope/config-dir/version detection (LOCAL/GLOBAL/UNKNOWN) ported from update.md bash; backs `gsd-tools update-context` (#498) | | `validate-command-router.cjs` | Thin CJS subcommand router adapter for `gsd-tools validate` | diff --git a/eslint.config.mjs b/eslint.config.mjs index 142c3eb67..fe922768c 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -79,6 +79,7 @@ export default tseslint.config( 'gsd-core/bin/lib/probe-core.cjs', 'gsd-core/bin/lib/spec-section.cjs', 'gsd-core/bin/lib/prohibition-enforcement.cjs', + 'gsd-core/bin/lib/ui-consideration-probe.cjs', 'gsd-core/bin/lib/code-review-flags.cjs', 'gsd-core/bin/lib/context-utilization.cjs', 'gsd-core/bin/lib/api-coverage.cjs', diff --git a/gsd-core/references/ui-consideration-probe.md b/gsd-core/references/ui-consideration-probe.md new file mode 100644 index 000000000..489c1470b --- /dev/null +++ b/gsd-core/references/ui-consideration-probe.md @@ -0,0 +1,73 @@ +# UI-Consideration Probe — Spec-Completeness Reference + +The **third** adapter of the shared `probe-core` resolution model (ADR-550 Decision 7), on +the **UI element/state axis**. It surfaces the shape-rooted UI *state* considerations a +UI-SPEC must resolve before a dimension may PASS — the visual analog of the requirement-side +[edge-probe](./edge-probe.md), reusing its exact lifecycle, validators, and plan-phase lift +(see edge-probe.md for the shared status×verification model — this doc does not re-argue it). + +**Axis boundary (this is a MIXED axis).** This compiled taxonomy covers ONLY the finite, +project-independent shape-rooted content/robustness states. The **open**, domain/UX-dependent +considerations — real-time/offline/optimistic-UI, deep accessibility (WCAG breadth), +internationalization / RTL depth, and emerging interaction paradigms — are open-ended and are +prose-owned in the companion [domain-probes.md](./domain-probes.md) technology/UX bank, NOT +here. Forcing them into a closed compiled taxonomy is the wrong model. + +## Inputs + +A list of UI elements, each a `{ id, text, elements? }` record where `text` is the +researcher-authored description and `elements` is an optional author-supplied override of the +element classification. The six element kinds are: `form`, `list-collection`, `nav`, `media`, +`interactive-control`, `static-content`. When `elements` is absent, a heuristic classifier +proposes kinds from the prose (propose-then-confirm) — the author may correct the kind. + +## Taxonomy (8 categories) + +Closed and small by design: the finite, project-independent content/robustness states every +UI surface must account for. Growth toward open UX topics happens in `domain-probes.md`, not by +bloating this closed core. + +| id | name | applies to element kinds | consideration question | +|----|------|--------------------------|------------------------| +| empty | Empty / no data | form, list-collection, media | What is shown when there is no data — zero items, an unfilled form, or absent media? | +| loading | Loading / in-flight | form, list-collection, media, nav | What is shown while data or content is still loading (skeleton, spinner, progressive reveal)? | +| error | Error / failure | form, list-collection, media, nav | What is shown when the load or submit fails (message, retry affordance, partial fallback)? | +| populated | Populated / happy path | list-collection, media | What does the normal populated (happy-path) state look like at a typical volume of content? | +| partial | Partial / incomplete | form, list-collection | What is shown for partial or incomplete data — some fields or rows present, others missing? | +| overflow | Overflow / truncation | list-collection, nav, static-content | What happens when content exceeds its container — scroll, clip, wrap, or truncate? | +| zero-one-many | Zero / one / many | list-collection | How does the layout read at zero, one, and many items (singular vs plural copy, spacing)? | +| long-text | Long text | form, static-content, interactive-control, nav | What happens with unusually long text — truncation, wrapping, ellipsis, or reflow? | + +## Relevance filter + resolution states + +The probe reuses the edge-probe rails verbatim (ADR-550 Decision 7 — see +[edge-probe.md](./edge-probe.md#relevance-filter--resolution-states) for the full model): + +1. **Relevance filter first.** Classify each element's kind(s), then raise only the categories + whose `applies to element kinds` intersect. A static label is never asked about loading or + empty state — that is what makes an unresolved consideration meaningful. +2. **Dismissal requires a reason string.** Silence is not a resolution; the reason is the audit + trail. +3. **Zero-classification surfaces one `unclassified` candidate (#1110).** An element whose prose + matched no kind cue yields exactly one soft `unclassified — review manually` item + (`category: "unclassified"`, `status: "unresolved"`) — never a silent drop, never a guessed + kind. `unclassified` is a review signal, **not** a ninth taxonomy category; an explicit + `elements: []` opt-out stays silent. + +Each raised consideration carries the shared two orthogonal axes — `status` +(`resolved | dismissed | unresolved`) and, when resolved, a `verification` tier +(`explicit | backstop`). A `backstop` consideration lifts into `must_haves.truths` and, at +verify time, is confirmed only by explicit evidence (a wired held-out/property test) or routes +to `insufficient_spec → human_needed` — never a silent pass (the honest-verifier disposition, +#1154). See [honest-verifier.md](./honest-verifier.md). + +## Closed / open boundary + +The **8 ids above are the closed, compiled subset** — finite and project-independent, so a +compiled taxonomy is legitimate (the same property that makes edge-probe's data-shape taxonomy +closed). The **open subset is prose-owned in [domain-probes.md](./domain-probes.md)**: +real-time/offline/optimistic-UI, deep accessibility (WCAG breadth), i18n / RTL depth, and +emerging interaction paradigms (gesture/voice/reduced-motion/print) are open-ended and +cue-triggered — they do not belong in this closed taxonomy. This probe **complements** the +`gsd-ui-checker` six quality dimensions (it adds a state-coverage axis); it does not change the +BLOCK/FLAG/PASS enum or the dimensions themselves. diff --git a/gsd-core/templates/UI-SPEC.md b/gsd-core/templates/UI-SPEC.md index be2c6e142..e94990c00 100644 --- a/gsd-core/templates/UI-SPEC.md +++ b/gsd-core/templates/UI-SPEC.md @@ -79,6 +79,31 @@ Accent reserved for: {explicit list — never "all interactive elements"} --- +## UI Considerations + +> Populated by the ui-phase UI-consideration probe (Step 9.5) and lifted by plan-phase's +> `## UI Considerations` lift rule via the identical rule as SPEC `## Edge Coverage`. Shape-rooted UI *state* +> coverage (empty / loading / error / populated / partial / overflow / zero-one-many / long-text). +> Empty-state and error-state COPY live in `## Copywriting Contract` above — this section covers +> state coverage and REFERENCES those rows rather than restating the copy (de-dup). + +Applicable state considerations resolved: {N covered, M backstop, K unresolved — or "none applicable"} + +| Category | Element(s) | Status | Resolution / Reason | +|----------|------------|--------|---------------------| +| {empty} | {list-collection} | ✅ covered | {concrete truth string — e.g. "Empty results render the documented 'No results' copy"} | +| {long-text} | {static-content} | 🧪 backstop | {held-out/visual UI-state test — lifts as `{ statement, verification: backstop }`} | +| {overflow} | {list-collection} | ⚠ unresolved | {planner treats as assumption} | + + + +--- + ## Registry Safety | Registry | Blocks Used | Safety Gate | diff --git a/gsd-core/workflows/plan-phase.md b/gsd-core/workflows/plan-phase.md index 329b9eb57..28cc487dd 100644 --- a/gsd-core/workflows/plan-phase.md +++ b/gsd-core/workflows/plan-phase.md @@ -610,6 +610,10 @@ SKETCH_FINDINGS_PATH=$(ls ./.claude/skills/sketch-findings-*/SKILL.md 2>/dev/nul PHASE_DIR_FOR_SPEC=$(_gsd_field "$INIT" phase_dir) SPEC_FILE=$(ls "${PHASE_DIR_FOR_SPEC}"/*-SPEC.md 2>/dev/null | grep -Ev -- '-(AI|UI)-SPEC\.md$' | head -1) SPEC_PATH="${SPEC_FILE}" +# Resolve the phase UI-SPEC separately (the glob above excludes -UI-SPEC.md); it carries the +# ## UI Considerations section the planner lifts by the same rule as ## Edge Coverage (#1867). +UI_SPEC_FILE=$(ls "${PHASE_DIR_FOR_SPEC}"/*-UI-SPEC.md 2>/dev/null | head -1) +UI_SPEC_PATH="${UI_SPEC_FILE}" ``` ## 7.5. Verify Nyquist Artifacts @@ -808,6 +812,7 @@ Output consumed by /gsd:execute-phase. Plans need: - must_haves for goal-backward verification - If the SPEC has an `## Edge Coverage` section, lift every `covered` edge's acceptance criterion into `must_haves.truths` as a plain string, and every `backstop` edge **as a structured flat-scalar marker** — an object item `{ statement: , verification: backstop }`, NOT a prose note (the verifier branches deterministically on the `verification: backstop` field; a parenthetical is unparseable — the #1110 fragility). Use a flat scalar `verification:` continuation key, never a nested object (ADR-550 #1278). At verify time a `backstop` truth the verifier cannot confirm with explicit evidence abstains → `human_needed` (reason `insufficient_spec`), never a silent pass (#1154; see `references/honest-verifier.md`). `unresolved` edges are explicit assumptions — surface them in the plan, do not silently drop them. **Otherwise** (`EDGE_ABSENT`): apply the SAME lift to the fallback report `{COVERAGE}` (per §C of `references/specless-probe-fallback.md`); a SPEC-supplied section is never re-run. - If the SPEC has a `## Prohibitions` section, lift every resolved prohibition into the `must_haves.prohibitions:` sibling block (NOT `truths` — ADR-550 D3) with `statement`+`status`+`verification`, via the single `projectProhibitions` serializer (Hyrum — no second serializer); unresolved -> flagged assumptions, don't drop; never put a must-NOT under `truths`. **Otherwise** (`PROHIB_ABSENT`), author the recalled prohibitions into the SAME block via the SAME `projectProhibitions` contract but **descriptor-less** (no `check_*`) so each disposes flagged-unverified; never auto-dismiss. Section-level precedence + no-silent-drop equality apply (§C). +- If a `-UI-SPEC.md` exists (resolved above as `UI_SPEC_PATH`) with a `## UI Considerations` section, lift it by the **identical rule** as `## Edge Coverage` above — `covered` → `must_haves.truths` string, `backstop` → flat scalar `{ statement, verification: backstop }`, `unresolved` → explicit planner assumption (no new verb — ADR-550 #1278/#1154; #1867). Read it from `UI_SPEC_PATH` (the SPEC glob excludes `-UI-SPEC.md`). - **"Artifacts this phase produces" section (MANDATORY)** — list every symbol this phase creates: decorators, classes, functions, CLI flags, struct/dataclass fields, new file paths. The plan-review-convergence source-grounding pass reads this section to exclude newly-created symbols from drift verification; omitting it causes new symbols to be flagged for acknowledgement. @@ -854,6 +859,7 @@ Every task MUST include these fields — they are NOT optional: - [ ] must_haves derived from phase goal - [ ] Every PLAN.md includes an "Artifacts this phase produces" section listing symbols created by this phase (decorators, classes, functions, CLI flags, struct/dataclass fields, new file paths) - [ ] Every SPEC ## Edge Coverage covered/backstop edge is represented in a plan's must_haves (no silent drops) +- [ ] Every UI-SPEC ## UI Considerations covered/backstop consideration is represented in a plan's must_haves (no silent drops) - [ ] Every SPEC ## Prohibitions resolved item is represented in a plan's must_haves.prohibitions (no silent drops) ``` diff --git a/gsd-core/workflows/ui-phase.md b/gsd-core/workflows/ui-phase.md index 4244aa4b1..840a09fbe 100644 --- a/gsd-core/workflows/ui-phase.md +++ b/gsd-core/workflows/ui-phase.md @@ -223,7 +223,7 @@ Agent( ## 8. Handle Checker Return **If `## UI-SPEC VERIFIED`:** -Display dimension results. Proceed to step 10. +Display dimension results. Proceed to step 9.5. **If `## ISSUES FOUND`:** Display blocking issues. Proceed to step 9. @@ -264,6 +264,151 @@ Options: Use AskUserQuestion for the choice. +**On "Force approve":** proceed to step 9.5 (the UI-consideration probe still runs on the accepted UI-SPEC, so state coverage is recorded even when quality FLAGs were accepted), then step 10. **On "Edit manually" / "Abandon":** exit without running the probe. + +## 9.5. UI-Consideration Probe (post-verification) + +Run AFTER the checker approves the UI-SPEC (VERIFIED, or force-approved at step 9) — never inline +during authoring, so a revision-loop researcher rewrite (step 9) cannot clobber the section and the +`## UI Considerations` block is committed with the FINAL UI-SPEC. This is the visual analog of +spec-phase Step 5.5's edge probe, retargeted to the UI element/state axis. Reference: +@~/.claude/gsd-core/references/ui-consideration-probe.md. + +**Skip conditions:** if `--auto` and the UI-SPEC already carries a resolved `## UI Considerations` +section (re-run), the write-back is idempotent (it REPLACES that section, never appends). If the +runtime is non-Claude and the probe engine cannot be resolved, the shim FAILS LOUD (below) — it +never silently no-ops (a silent skip would drop the whole state-coverage axis). + +**Runtime coverage compute — resolve and invoke ui-consideration-probe.cjs:** + +```bash +# Resolve the compiled ui-consideration-probe.cjs against the GSD install dir via RUNTIME_DIR +# (#448) — NOT the consuming project's git root — falling back to git toplevel / $HOME/.claude. +# Mirrors spec-phase.md Step 5.5's edge-probe resolution idiom verbatim (same candidate paths). +_GSD_RT="${RUNTIME_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}" +UI_PROBE_JS=$(for _c in \ + "$_GSD_RT/gsd-core/bin/lib/ui-consideration-probe.cjs" \ + "$_GSD_RT/bin/lib/ui-consideration-probe.cjs" \ + "$_GSD_RT/.claude/bin/lib/ui-consideration-probe.cjs" \ + "$HOME/.claude/gsd-core/bin/lib/ui-consideration-probe.cjs" \ + "$HOME/.claude/bin/lib/ui-consideration-probe.cjs"; do + [ -f "$_c" ] && { echo "$_c"; break; } +done) + +# Graceful degradation — never a silent skip. Build ONLY when $_GSD_RT is a verified GSD source +# checkout (has tsconfig.build.json + src/ui-consideration-probe.cts), pinned with --prefix so we +# never trigger the CONSUMING project's own build during a ui-phase. Real installs ship the +# compiled .cjs via prepublishOnly, so this path only matters in a GSD dev checkout. +if [ -z "$UI_PROBE_JS" ]; then + if [ -f "$_GSD_RT/tsconfig.build.json" ] && [ -f "$_GSD_RT/src/ui-consideration-probe.cts" ]; then + npm --prefix "$_GSD_RT" run build:lib 2>/dev/null || true + UI_PROBE_JS=$(for _c in \ + "$_GSD_RT/gsd-core/bin/lib/ui-consideration-probe.cjs" \ + "$_GSD_RT/bin/lib/ui-consideration-probe.cjs" \ + "$_GSD_RT/.claude/bin/lib/ui-consideration-probe.cjs" \ + "$HOME/.claude/gsd-core/bin/lib/ui-consideration-probe.cjs" \ + "$HOME/.claude/bin/lib/ui-consideration-probe.cjs"; do + [ -f "$_c" ] && { echo "$_c"; break; } + done) + fi + if [ -z "$UI_PROBE_JS" ]; then + echo "ERROR: ui-consideration-probe.cjs not found — reinstall GSD or run \`npm run build:lib\` in your GSD checkout." >&2 + exit 1 + fi +fi + +# Element extraction (MANUAL BY DESIGN — not an oversight): the agent reads the researcher-authored +# UI-SPEC prose (the described surfaces — the Design System / Copywriting rows and any element the +# researcher named) and writes ONE object per UI element/surface: {"id","text"} where text is the +# prose describing it. This mirrors spec-phase Step 5.5's edge-probe REQS_JSON step VERBATIM — a +# hand-populated heredoc guarded by the fail-loud check below — the established, shipped +# pattern for feeding a probe from a prose spec. It is NOT mechanized on purpose: a UI-SPEC has no +# single machine-parseable "elements" column — surfaces are distributed across design-token tables +# (Design System / Typography / Color), the Copywriting section, and prose the researcher names, so a +# regex/table parse would fail-OPEN (miss a prose-named surface, or feed a design-token row as a bogus +# element). The agent-authored heredoc + fail-loud guard is the conservative choice, identical to the +# requirement-side edge-probe path (RR-04). If a future UI-SPEC gains a canonical element table, +# revisit to parse it. Populate the heredoc from the UI-SPEC; the guard below fails loud on a +# forgotten substitution (never a no-op). +ELEMENTS_JSON=$(mktemp "${TMPDIR:-/tmp}/ui-probe-elements-XXXXXX") && mv "$ELEMENTS_JSON" "${ELEMENTS_JSON}.json" && ELEMENTS_JSON="${ELEMENTS_JSON}.json" || exit 1 +cat > "$ELEMENTS_JSON" <<'JSON' +[ + { "id": "E1", "text": "" } +] +JSON +if ! node -e 'const a=require(process.argv[1]);if(!Array.isArray(a)||a.length===0)process.exit(1);if(a.some(e=>typeof e.text!=="string"||!e.text.trim()||e.text.includes("/dev/null; then + rm -f "$ELEMENTS_JSON" + echo "ERROR: ui-probe elements JSON is empty/invalid or still holds the placeholder — populate \$ELEMENTS_JSON from the UI-SPEC's described surfaces before this step runs." >&2 + exit 1 +fi +# Invoke the compiled engine and CAPTURE its report. FATAL-INVOKE GUARD: use `if ! COVERAGE=$(…)`, +# NEVER a bare `COVERAGE=$(node …)` — a bare capture swallows the engine's exit 2 (invalid shape / +# bad input) and falls through to prose re-derivation: fail-OPEN at the exact boundary the engine +# validation protects. +if ! COVERAGE=$(node "$UI_PROBE_JS" "$ELEMENTS_JSON"); then + rm -f "$ELEMENTS_JSON" + echo "ERROR: ui-consideration-probe engine failed (invalid shapes or bad input) — fix the element(s) and re-run; never proceed with empty coverage." >&2 + exit 1 +fi +rm -f "$ELEMENTS_JSON" +# Malformed-report guard: exit 0 but garbage. The report must parse as { items[], coverage{} }. +if ! printf '%s' "$COVERAGE" | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{let r;try{r=JSON.parse(s)}catch{process.exit(1)}if(!r||!Array.isArray(r.items)||typeof r.coverage!=="object"||r.coverage===null)process.exit(1)})'; then + echo "ERROR: ui-consideration-probe produced an unparseable or malformed coverage report — refusing to proceed with the resolution loop." >&2 + exit 1 +fi +# Zero-applicable guard: a report where NO category applied across ANY element is far more likely a +# classification miss (or malformed elements) than a genuinely state-free UI. Surface it loudly. +APPLICABLE=$(printf '%s' "$COVERAGE" | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{let n=0;try{n=JSON.parse(s).coverage.applicable}catch{n=0}process.stdout.write(String(n))})') +if [ "$APPLICABLE" = "0" ]; then + echo "WARNING: ui-consideration-probe proposed ZERO applicable categories across all elements — likely a classification miss or malformed elements, not a genuinely state-free UI. Do NOT silently write an empty UI Considerations section." >&2 +fi +``` + +If `$APPLICABLE` is `0`, do NOT proceed silently: ask via AskUserQuestion ("The UI probe found no +applicable state considerations — is this genuinely a state-free surface, or should we revisit the +element descriptions?"). Only write an empty section after explicit confirmation. + +**Propose-then-confirm (the partial-cue mitigation — load-bearing).** For each element, the engine +reports the DETECTED element kinds (`classifyElement` over the built `.cjs`). The prose classifier +is heuristic and LOSSY: a surface that is genuinely both a form and a list, but whose prose trips +only the form cue, under-covers — and because SOMETHING classified, no `unclassified` signal fires. +So SURFACE the detected kinds to the user (AskUserQuestion) and ask whether any real element kind +was missed. If the user ADDs a kind, re-run that element with an authored `elements` override +(the union of detected + added) so the missed categories are raised. A single tripped cue is a +SIGNAL, not proof the element is only that kind — the confirm step, not the heuristic, is what makes +coverage sound. + +**Resolution loop** (mirror spec-phase 5.5): resolve each applicable consideration via +AskUserQuestion — **Specify** (→ `covered`, write a concrete truth) / **Dismiss (reason required)** / +**Backstop** (a held-out/visual UI-state test) / **Defer** (→ `unresolved`). An `unclassified` row is +a manual-review nudge, not a hard block. Text mode (`workflow.text_mode` / `--text`) → numbered lists. + +**Kind-confirmation under `--auto`.** The propose-then-confirm step above is an AskUserQuestion, so +under `--auto` it follows the spec-phase 5.5 convention (replace AskUserQuestion with Claude's +recommended choice): Claude re-reads each element's prose and authors the `elements` override (the +union of the detected kinds + any kind it identifies as missed) instead of prompting — so `--auto` +recall rests on Claude's kind-identification, not the heuristic cue-match alone. This matters because +`autoResolve` (below) is a RESOLUTION floor only: it resolves the *detected* categories and cannot +recover a kind that was never surfaced, so recall is fixed HERE, at kind-confirmation, before +resolution runs. + +**`--auto` mode (two layers).** The adapter's `autoResolve` is the CODE floor: every applicable +consideration auto-`backstop`s (carrying the taxonomy question as its resolution) and an +`unclassified` candidate stays `unresolved` — it NEVER auto-`dismiss`es and never auto-backstops an +unclassified item (#1110). On top of that floor the workflow MAY upgrade an item to `covered` when a +defensible acceptance criterion can be written (the same judgment spec-phase 5.5 applies in prose). +An auto `--auto` run therefore leaves un-upgraded backstops as `backstop`: at verify time each one +with no wired evidence routes to `insufficient_spec → human_needed` — never a silent pass (#1154). +That surfacing is the intended honest-verifier behavior, not over-flagging. + +**Write-back.** Populate a `## UI Considerations` section in the UI-SPEC from the resolved +considerations, in the format the shipped plan-phase `## UI Considerations` lift rule reads: +`covered` → a truth string; `backstop` → a flat scalar `{ statement, verification: backstop }`; +`unresolved` → an explicit `⚠ unresolved — planner must treat as assumption` row. Empty-state and +error-state COPY stays in `## Copywriting Contract` — the considerations section covers shape-rooted +STATE coverage and REFERENCES those rows rather than restating the copy (de-dup). IDEMPOTENT: if a +`## UI Considerations` section already exists, REPLACE it — never append a duplicate. + ## 10. Present Final Status Display: diff --git a/src/ui-consideration-probe.cts b/src/ui-consideration-probe.cts new file mode 100644 index 000000000..fcb30e570 --- /dev/null +++ b/src/ui-consideration-probe.cts @@ -0,0 +1,315 @@ +/** + * UI-consideration probe — the THIRD adapter of the probe-core resolution model + * (ADR-457 build model; ADR-550 Decision 7 seam; #1867). + * + * The generic resolution lifecycle, the status×verification re-cut, `validateResolution`, + * `validateRequirement`, the `analyzeCoverage` merge/rollup/orphan-reject engine, and the + * `runProbeCli` scaffold all live in `src/probe-core.cts`. This module keeps ONLY the + * UI-specific cluster: the six element kinds, the closed 8-category shape-rooted UI state + * taxonomy, element classification, consideration proposal, and the `{ explicit, backstop }` + * verification validators — mirroring `edge-probe` on the UI element/state axis. + * + * MIXED-axis boundary (spike verdict, ADR-550 pattern): this compiled taxonomy covers ONLY the + * finite, project-independent shape-rooted *content/robustness* states (empty/loading/error/…). + * Open, domain-specific UX considerations (real-time/offline, deep a11y/WCAG breadth, i18n/RTL + * depth, emerging interaction paradigms) are prose-owned in `references/domain-probes.md`, NOT + * here — forcing them into a closed compiled taxonomy is the wrong model. + * + * Authored as strict TypeScript (`src/ui-consideration-probe.cts`) and compiled by + * `tsc -p tsconfig.build.json` to the gitignored runtime artifact + * `gsd-core/bin/lib/ui-consideration-probe.cjs`. Do NOT hand-write the `.cjs`; it is emitted. + * Tests `require()` the built artifact; `pretest` runs `build:lib` first. + */ + +import { + type Item, + type Resolution, + type CoverageReport, + type Validators, + validateRequirement as coreValidateRequirement, + validateResolution as coreValidateResolution, + analyzeCoverage as coreAnalyzeCoverage, + runProbeCli, +} from './probe-core.cjs'; + +/** The six UI element kinds a described component can be (the closed relevance axis, D-03). */ +export type UIElementKind = + | 'form' + | 'list-collection' + | 'nav' + | 'media' + | 'interactive-control' + | 'static-content'; + +/** The UI probe's verification tiers (mirrors EdgeVerification — the `verification` axis values). */ +export type UIVerification = 'explicit' | 'backstop'; + +/** A single UI-state taxonomy category. `elements` lists which kinds make it applicable. */ +export interface TaxonomyEntry { + id: string; + name: string; + elements: UIElementKind[]; + consideration: string; +} + +/** A UI element to probe; `elements` is an optional authored override of classification. */ +export interface Element { + id: string; + text?: string; + elements?: UIElementKind[]; +} + +/** A UI consideration item — a probe-core `Item` specialized to the UI verification vocabulary. */ +export type UIConsideration = Item; + +/** + * Word-boundary cues mapping element prose -> UI element kind. + * Heuristic and intentionally lossy; an authored `elements` array overrides it. Every pattern is a + * flat linear `\b(a|b|c)\b` alternation with NO nested/overlapping quantifiers (no catastrophic + * backtracking — mirrors SHAPE_CUES). + */ +export const UI_CUES: Record = { + 'form': /\b(forms?|inputs?|fields?|submit|validation|validate|password|email|checkbox|radio|textarea)\b/i, + 'list-collection': /\b(lists?|listing|tables?|grids?|collections?|rows?|items?|cards?|feed|results?)\b/i, + 'nav': /\b(nav|navigation|menus?|tabs?|breadcrumbs?|pagination|sidebars?)\b/i, + 'media': /\b(images?|img|videos?|avatars?|thumbnails?|photos?|gallery|icons?)\b/i, + 'interactive-control': /\b(buttons?|toggles?|switch|switches|dropdowns?|sliders?|controls?|pickers?)\b/i, + 'static-content': /\b(labels?|headings?|titles?|paragraphs?|copy|descriptions?|text)\b/i, +}; + +/** The locked element vocabulary — exactly the keys of UI_CUES (single source of truth). */ +export const VALID_ELEMENT_KINDS: ReadonlySet = new Set(Object.keys(UI_CUES)); + +/** Detect which element kinds a description's prose matches (heuristic). */ +export function classifyElement(text: string): UIElementKind[] { + const kinds: UIElementKind[] = []; + const subject = String(text == null ? '' : text); + for (const kind of Object.keys(UI_CUES) as UIElementKind[]) { + if (UI_CUES[kind].test(subject)) kinds.push(kind); + } + return kinds; +} + +/** + * Closed taxonomy of 8 shape-rooted UI *content/robustness* state categories. `elements` lists + * which element kinds make the category relevant. These ids are the CLOSED/compiled subset — the + * open UX subset (real-time/offline, deep a11y, i18n/RTL depth) is prose-owned in + * `references/domain-probes.md` and deliberately absent here (D-02). + */ +export const UI_TAXONOMY: TaxonomyEntry[] = [ + { id: 'empty', name: 'Empty / no data', elements: ['form', 'list-collection', 'media'], consideration: 'What is shown when there is no data — zero items, an unfilled form, or absent media?' }, + { id: 'loading', name: 'Loading / in-flight', elements: ['form', 'list-collection', 'media', 'nav'], consideration: 'What is shown while data or content is still loading (skeleton, spinner, progressive reveal)?' }, + { id: 'error', name: 'Error / failure', elements: ['form', 'list-collection', 'media', 'nav'], consideration: 'What is shown when the load or submit fails (message, retry affordance, partial fallback)?' }, + { id: 'populated', name: 'Populated / happy path', elements: ['list-collection', 'media'], consideration: 'What does the normal populated (happy-path) state look like at a typical volume of content?' }, + { id: 'partial', name: 'Partial / incomplete', elements: ['form', 'list-collection'], consideration: 'What is shown for partial or incomplete data — some fields or rows present, others missing?' }, + { id: 'overflow', name: 'Overflow / truncation', elements: ['list-collection', 'nav', 'static-content'], consideration: 'What happens when content exceeds its container — scroll, clip, wrap, or truncate?' }, + { id: 'zero-one-many', name: 'Zero / one / many', elements: ['list-collection'], consideration: 'How does the layout read at zero, one, and many items (singular vs plural copy, spacing)?' }, + { id: 'long-text', name: 'Long text', elements: ['form', 'static-content', 'interactive-control', 'nav'], consideration: 'What happens with unusually long text — truncation, wrapping, ellipsis, or reflow?' }, +]; + +/** Return taxonomy category ids whose applicable element kinds intersect the input set. */ +export function applicableCategories(kinds: UIElementKind[]): string[] { + const set = new Set(kinds); + return UI_TAXONOMY.filter((c) => c.elements.some((k) => set.has(k))).map((c) => c.id); +} + +/** + * Pseudo-category for an element whose prose matched NO element cue (#1110). It is a soft + * "review manually" signal, NOT a 9th taxonomy category: it stays out of `UI_TAXONOMY` (the closed + * eight) and only joins `UI_VALIDATORS.categories` so `analyzeCoverage` accepts the item. + */ +export const UNCLASSIFIED_CATEGORY = 'unclassified'; +const UNCLASSIFIED_PROBE = 'unclassified — review manually'; + +/** + * The UI adapter's injected runtime validators (ADR-550 #5). `categories` is the closed taxonomy + * plus the unclassified soft-signal; both verification tiers require a non-empty `resolution` so + * plan-phase has a criterion to lift. NOTE the probe-core Validators field is `verification` + * (SINGULAR); CONTEXT.md D-05's `verifications` is a paraphrase typo, not the real field name. + */ +export const UI_VALIDATORS: Validators = { + categories: [...UI_TAXONOMY.map((c) => c.id), UNCLASSIFIED_CATEGORY], + verification: ['explicit', 'backstop'], + requiredFieldsByVerification: { explicit: ['resolution'], backstop: ['resolution'] }, +}; + +/** + * Validate a single element — the generic id/text checks (probe-core) plus the UI adapter's + * `elements`-must-be-an-array check. The `text` prose is REQUIRED (it is the classification + * signal), so reject a missing/empty `text` when no authored `elements` override is present. + * Without this, a `{ id }` element classifies to zero kinds → zero considerations → it is silently + * DROPPED from coverage. An explicit `elements` array (including `[]` for "no applicable + * categories") is the legitimate way to opt out of prose classification. + */ +export function validateRequirement(element: Element): void { + coreValidateRequirement(element); + const r = element as unknown as { elements?: unknown; text?: unknown }; + if (r.elements != null && !Array.isArray(r.elements)) { + throw new Error(`element ${element.id} elements must be an array when present`); + } + if (r.elements == null && !(typeof r.text === 'string' && r.text.trim())) { + throw new Error( + `element ${element.id} text must be a non-empty string when no elements override is provided`, + ); + } +} + +/** Validate a UI-consideration resolution against the UI verification vocabulary (delegated, D-06). */ +export function validateResolution(resolution: Resolution): true { + return coreValidateResolution(resolution, UI_VALIDATORS); +} + +/** + * Propose candidate considerations for an element. Uses authored `elements` when present, else + * classifies from prose. Every proposed consideration starts unresolved (verification null); the + * taxonomy entry's `consideration` question is carried in the item's `probe` field. + */ +export function proposeConsiderations(element: Element): UIConsideration[] { + validateRequirement(element); + let kinds: UIElementKind[]; + if (Array.isArray(element.elements)) { + // Fail closed: an authored array must contain only locked element kinds. A non-empty but + // invalid array would otherwise intersect no category and silently suppress every probe — the + // gate reads green while nothing was checked. An empty array stays a valid "no applicable + // categories" override (silent opt-out). + for (const k of element.elements) { + if (typeof k !== 'string' || !VALID_ELEMENT_KINDS.has(k)) { + throw new Error( + `invalid element kind ${JSON.stringify(k)} for element ${element.id} — must be one of: ${[...VALID_ELEMENT_KINDS].join(', ')}`, + ); + } + } + kinds = element.elements; + } else { + kinds = classifyElement(element.text as string); + if (kinds.length === 0) { + // Prose present but no element cue matched. Do NOT silently drop it (#1110): a UI element + // whose phrasing missed every cue would otherwise vanish from coverage with no signal — the + // exact blind spot this probe exists to catch. Surface ONE soft, dismissible "unclassified — + // review manually" candidate. The explicit `elements: []` opt-out (above) stays silent. + return [{ + requirement_id: element.id, + category: UNCLASSIFIED_CATEGORY, + status: 'unresolved', + verification: null, + resolution: null, + reason: null, + probe: UNCLASSIFIED_PROBE, + }]; + } + } + return applicableCategories(kinds).map((catId): UIConsideration => { + const cat = UI_TAXONOMY.find((c) => c.id === catId); + return { + requirement_id: element.id, + category: catId, + status: 'unresolved', + verification: null, + resolution: null, + reason: null, + probe: cat ? cat.consideration : '', + }; + }); +} + +/** + * Propose considerations for every element (deterministic propose), then delegate the + * merge/rollup/orphan-reject to probe-core. UI-specific pre-checks: elements must be an array, + * element ids must be unique. Throws on any invalid resolution. + */ +export function analyzeCoverage( + elements: Element[], + resolutions: Resolution[] = [], +): CoverageReport { + if (!Array.isArray(elements)) { + throw new Error('elements must be an array'); + } + const items: UIConsideration[] = []; + const seenIds = new Set(); + for (const el of elements) { + validateRequirement(el); + if (seenIds.has(el.id)) { + throw new Error(`duplicate element id ${JSON.stringify(el.id)}`); + } + seenIds.add(el.id); + for (const consideration of proposeConsiderations(el)) items.push(consideration); + } + 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[] { + return items.map((item): Resolution => { + 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 [resolutions.json]`. + * The generic I/O plumbing (parse, fail-closed exit 2, pretty-JSON out) lives in probe-core's + * `runProbeCli`; this adapter supplies its `analyzeCoverage`. Guarded by `require.main === module` + * so it runs only when the compiled `.cjs` is executed directly. + */ +if (require.main === module) { + runProbeCli( + (elements, resolutions) => + analyzeCoverage(elements as Element[], resolutions as Resolution[]), + { usage: 'ui-consideration-probe.cjs [resolutions.json]' }, + ); +} diff --git a/tests/agent-size-baseline.json b/tests/agent-size-baseline.json index 08166ac12..3d68c1af2 100644 --- a/tests/agent-size-baseline.json +++ b/tests/agent-size-baseline.json @@ -29,8 +29,8 @@ "gsd-roadmapper.md": 22273, "gsd-security-auditor.md": 8981, "gsd-ui-auditor.md": 17249, - "gsd-ui-checker.md": 14060, - "gsd-ui-researcher.md": 19500, + "gsd-ui-checker.md": 14118, + "gsd-ui-researcher.md": 19557, "gsd-user-profiler.md": 8516, "gsd-verifier.md": 49147 } diff --git a/tests/fixtures/golden-install-parity/antigravity.json b/tests/fixtures/golden-install-parity/antigravity.json index a25f54429..b6bc90968 100644 --- a/tests/fixtures/golden-install-parity/antigravity.json +++ b/tests/fixtures/golden-install-parity/antigravity.json @@ -31,8 +31,8 @@ "agents/gsd-roadmapper.md": "7a8465ac6d4dd29e", "agents/gsd-security-auditor.md": "2ce13af25179dd10", "agents/gsd-ui-auditor.md": "f777f9c7bf62788c", - "agents/gsd-ui-checker.md": "dbb50fc8324471b5", - "agents/gsd-ui-researcher.md": "5d74011a96e5b67f", + "agents/gsd-ui-checker.md": "216af34b01e277aa", + "agents/gsd-ui-researcher.md": "5f86de1decbd16d2", "agents/gsd-user-profiler.md": "25d65f6458454764", "agents/gsd-verifier.md": "224e8df2d8fd1d95", "gsd-core/VERSION": "ef0deccd81a6723c", @@ -138,6 +138,7 @@ "gsd-core/references/thinking-models-verification.md": "a71a933d51ca3d8d", "gsd-core/references/thinking-partner.md": "827c1badf3e6df41", "gsd-core/references/ui-brand.md": "48717bcfcd63bd27", + "gsd-core/references/ui-consideration-probe.md": "7e019dfaae47f4c4", "gsd-core/references/universal-anti-patterns.md": "6a1245050b21df01", "gsd-core/references/untrusted-input-boundary.md": "d33b80d4d348599a", "gsd-core/references/user-profiling.md": "8e023a908d968af1", @@ -153,7 +154,7 @@ "gsd-core/templates/README.md": "28160dd8b0631652", "gsd-core/templates/SECURITY.md": "b628f7f1c6d2328f", "gsd-core/templates/UAT.md": "68d32d1fea14e184", - "gsd-core/templates/UI-SPEC.md": "20ca56a4e3e21f01", + "gsd-core/templates/UI-SPEC.md": "7dd5c7cdc7ece0ec", "gsd-core/templates/VALIDATION.md": "6144951011cdca57", "gsd-core/templates/claude-md.md": "8797c0c7da927c8e", "gsd-core/templates/codebase/architecture.md": "282db635ba093b1a", @@ -270,7 +271,7 @@ "gsd-core/workflows/onboard.md": "69d871aa53a1a859", "gsd-core/workflows/pause-work.md": "564de32981a24337", "gsd-core/workflows/plan-milestone-gaps.md": "dd6a4b3a8b05ab6e", - "gsd-core/workflows/plan-phase.md": "f3310f285ab426c6", + "gsd-core/workflows/plan-phase.md": "40eecc5a8817b9f2", "gsd-core/workflows/plan-phase/steps/closed-phase-gate.md": "4099ef6d0868de60", "gsd-core/workflows/plan-phase/steps/prd-express-path.md": "ab0b22244c3389aa", "gsd-core/workflows/plan-phase/steps/windows-troubleshooting.md": "e9de7a96bbfff261", @@ -302,7 +303,7 @@ "gsd-core/workflows/sync-skills.md": "8326a7ff0411b077", "gsd-core/workflows/thread.md": "31201839d0530e89", "gsd-core/workflows/transition.md": "3eb5edaa5c95c0de", - "gsd-core/workflows/ui-phase.md": "44f44a8aa7df3cfb", + "gsd-core/workflows/ui-phase.md": "be51b5f5229ac856", "gsd-core/workflows/ui-review.md": "ba558aaf1ad9f58d", "gsd-core/workflows/ultraplan-phase.md": "d8e92b0b7214eba6", "gsd-core/workflows/undo.md": "6ab639d1fc7e0721", diff --git a/tests/fixtures/golden-install-parity/augment.json b/tests/fixtures/golden-install-parity/augment.json index cb13ab537..c8060f895 100644 --- a/tests/fixtures/golden-install-parity/augment.json +++ b/tests/fixtures/golden-install-parity/augment.json @@ -31,8 +31,8 @@ "agents/gsd-roadmapper.md": "bb2f57695dbab32c", "agents/gsd-security-auditor.md": "f22374cc1db28dca", "agents/gsd-ui-auditor.md": "dcd5712e6b160a53", - "agents/gsd-ui-checker.md": "286b934b411079f8", - "agents/gsd-ui-researcher.md": "184d1c3edca694df", + "agents/gsd-ui-checker.md": "5c27ec0d88ef87c2", + "agents/gsd-ui-researcher.md": "bcc591f2dfebdb60", "agents/gsd-user-profiler.md": "622220df0654b6bf", "agents/gsd-verifier.md": "f5c00a9b08fc1a7e", "commands/gsd-add-tests.md": "3608d0cf4b515103", @@ -209,6 +209,7 @@ "gsd-core/references/thinking-models-verification.md": "a71a933d51ca3d8d", "gsd-core/references/thinking-partner.md": "41069529ef776e39", "gsd-core/references/ui-brand.md": "48717bcfcd63bd27", + "gsd-core/references/ui-consideration-probe.md": "7e019dfaae47f4c4", "gsd-core/references/universal-anti-patterns.md": "6a1245050b21df01", "gsd-core/references/untrusted-input-boundary.md": "d33b80d4d348599a", "gsd-core/references/user-profiling.md": "b50416fe57c1b321", @@ -224,7 +225,7 @@ "gsd-core/templates/README.md": "93d3426fc64e2c12", "gsd-core/templates/SECURITY.md": "b628f7f1c6d2328f", "gsd-core/templates/UAT.md": "9e296471b97ebcec", - "gsd-core/templates/UI-SPEC.md": "20ca56a4e3e21f01", + "gsd-core/templates/UI-SPEC.md": "7dd5c7cdc7ece0ec", "gsd-core/templates/VALIDATION.md": "f53e0ca061d3528e", "gsd-core/templates/claude-md.md": "d1d333e4b963c0d2", "gsd-core/templates/codebase/architecture.md": "6be88214162fdd89", @@ -341,7 +342,7 @@ "gsd-core/workflows/onboard.md": "6f9e6c0b484271a9", "gsd-core/workflows/pause-work.md": "f2b33bba5593d422", "gsd-core/workflows/plan-milestone-gaps.md": "852f6d7c0c4299dc", - "gsd-core/workflows/plan-phase.md": "b693bf6fd9ec6b08", + "gsd-core/workflows/plan-phase.md": "a1057033055542a3", "gsd-core/workflows/plan-phase/steps/closed-phase-gate.md": "b36f77ac7344a072", "gsd-core/workflows/plan-phase/steps/prd-express-path.md": "197c0590326371b2", "gsd-core/workflows/plan-phase/steps/windows-troubleshooting.md": "49f58c3f75be3eb5", @@ -373,7 +374,7 @@ "gsd-core/workflows/sync-skills.md": "b505e6f8331c0918", "gsd-core/workflows/thread.md": "927e7eeefd2fcf5c", "gsd-core/workflows/transition.md": "cb8ec5affb7ebba1", - "gsd-core/workflows/ui-phase.md": "db908f3bad5bc9f0", + "gsd-core/workflows/ui-phase.md": "0ae35f603071630e", "gsd-core/workflows/ui-review.md": "7acfc485526d064b", "gsd-core/workflows/ultraplan-phase.md": "0fb8291153e3937d", "gsd-core/workflows/undo.md": "96d2775f008b3a85", diff --git a/tests/fixtures/golden-install-parity/claude-local.json b/tests/fixtures/golden-install-parity/claude-local.json index a716ecf12..2a6e61fed 100644 --- a/tests/fixtures/golden-install-parity/claude-local.json +++ b/tests/fixtures/golden-install-parity/claude-local.json @@ -30,8 +30,8 @@ "agents/gsd-roadmapper.md": "453e9471ad27c7ea", "agents/gsd-security-auditor.md": "45bd98918cd3a004", "agents/gsd-ui-auditor.md": "a0b09cc8e4645956", - "agents/gsd-ui-checker.md": "3a7be21f4daa1c05", - "agents/gsd-ui-researcher.md": "a739b0ded9c3ae23", + "agents/gsd-ui-checker.md": "33ffdc73d2105a24", + "agents/gsd-ui-researcher.md": "4b36852c839f1134", "agents/gsd-user-profiler.md": "d40584599906f3b7", "agents/gsd-verifier.md": "628ef3a944a7a6eb", "commands/gsd-add-tests.md": "057e3e440989e681", @@ -208,6 +208,7 @@ "gsd-core/references/thinking-models-verification.md": "a71a933d51ca3d8d", "gsd-core/references/thinking-partner.md": "827c1badf3e6df41", "gsd-core/references/ui-brand.md": "48717bcfcd63bd27", + "gsd-core/references/ui-consideration-probe.md": "7e019dfaae47f4c4", "gsd-core/references/universal-anti-patterns.md": "6a1245050b21df01", "gsd-core/references/untrusted-input-boundary.md": "d33b80d4d348599a", "gsd-core/references/user-profiling.md": "b50416fe57c1b321", @@ -223,7 +224,7 @@ "gsd-core/templates/README.md": "90d2617778373147", "gsd-core/templates/SECURITY.md": "b628f7f1c6d2328f", "gsd-core/templates/UAT.md": "68d32d1fea14e184", - "gsd-core/templates/UI-SPEC.md": "20ca56a4e3e21f01", + "gsd-core/templates/UI-SPEC.md": "7dd5c7cdc7ece0ec", "gsd-core/templates/VALIDATION.md": "6144951011cdca57", "gsd-core/templates/claude-md.md": "d8f0fe8dba3bb28a", "gsd-core/templates/codebase/architecture.md": "6be88214162fdd89", @@ -340,7 +341,7 @@ "gsd-core/workflows/onboard.md": "b86d78eef6c77e5b", "gsd-core/workflows/pause-work.md": "da902807d2213204", "gsd-core/workflows/plan-milestone-gaps.md": "7679fac068d1009d", - "gsd-core/workflows/plan-phase.md": "8e8331ca99bc8680", + "gsd-core/workflows/plan-phase.md": "50f812bc3456819d", "gsd-core/workflows/plan-phase/steps/closed-phase-gate.md": "4099ef6d0868de60", "gsd-core/workflows/plan-phase/steps/prd-express-path.md": "b810f9f2374e23a5", "gsd-core/workflows/plan-phase/steps/windows-troubleshooting.md": "e9de7a96bbfff261", @@ -372,7 +373,7 @@ "gsd-core/workflows/sync-skills.md": "5624d529dae1ad79", "gsd-core/workflows/thread.md": "14a9d195572a198f", "gsd-core/workflows/transition.md": "78a91b0154a93cf5", - "gsd-core/workflows/ui-phase.md": "57664a12509b455d", + "gsd-core/workflows/ui-phase.md": "50b0dd962c8029b9", "gsd-core/workflows/ui-review.md": "9c6005236e2067b5", "gsd-core/workflows/ultraplan-phase.md": "b926ba7e4de0c76d", "gsd-core/workflows/undo.md": "d759702f84e308fa", diff --git a/tests/fixtures/golden-install-parity/claude.json b/tests/fixtures/golden-install-parity/claude.json index e977d6af5..ecb3b42a0 100644 --- a/tests/fixtures/golden-install-parity/claude.json +++ b/tests/fixtures/golden-install-parity/claude.json @@ -30,8 +30,8 @@ "agents/gsd-roadmapper.md": "8a7f1f1256a6aed5", "agents/gsd-security-auditor.md": "4f9fc3f654af4944", "agents/gsd-ui-auditor.md": "40c0dcc15bcfb9fb", - "agents/gsd-ui-checker.md": "dae43a7eef4f2041", - "agents/gsd-ui-researcher.md": "5e561130434efdb2", + "agents/gsd-ui-checker.md": "8126405043f99cb6", + "agents/gsd-ui-researcher.md": "9e3ac030767167e0", "agents/gsd-user-profiler.md": "003276f85792cfda", "agents/gsd-verifier.md": "2271174b5aa20e31", "gsd-core/VERSION": "ef0deccd81a6723c", @@ -137,6 +137,7 @@ "gsd-core/references/thinking-models-verification.md": "a71a933d51ca3d8d", "gsd-core/references/thinking-partner.md": "827c1badf3e6df41", "gsd-core/references/ui-brand.md": "48717bcfcd63bd27", + "gsd-core/references/ui-consideration-probe.md": "7e019dfaae47f4c4", "gsd-core/references/universal-anti-patterns.md": "6a1245050b21df01", "gsd-core/references/untrusted-input-boundary.md": "d33b80d4d348599a", "gsd-core/references/user-profiling.md": "b50416fe57c1b321", @@ -152,7 +153,7 @@ "gsd-core/templates/README.md": "90d2617778373147", "gsd-core/templates/SECURITY.md": "b628f7f1c6d2328f", "gsd-core/templates/UAT.md": "68d32d1fea14e184", - "gsd-core/templates/UI-SPEC.md": "20ca56a4e3e21f01", + "gsd-core/templates/UI-SPEC.md": "7dd5c7cdc7ece0ec", "gsd-core/templates/VALIDATION.md": "6144951011cdca57", "gsd-core/templates/claude-md.md": "d8f0fe8dba3bb28a", "gsd-core/templates/codebase/architecture.md": "6be88214162fdd89", @@ -269,7 +270,7 @@ "gsd-core/workflows/onboard.md": "3c50ed1f1fd07619", "gsd-core/workflows/pause-work.md": "5716362557f44ce4", "gsd-core/workflows/plan-milestone-gaps.md": "1b43d12812f7bc1e", - "gsd-core/workflows/plan-phase.md": "823254227fc8e369", + "gsd-core/workflows/plan-phase.md": "a62a13d6b8df73e0", "gsd-core/workflows/plan-phase/steps/closed-phase-gate.md": "4099ef6d0868de60", "gsd-core/workflows/plan-phase/steps/prd-express-path.md": "197c0590326371b2", "gsd-core/workflows/plan-phase/steps/windows-troubleshooting.md": "e9de7a96bbfff261", @@ -301,7 +302,7 @@ "gsd-core/workflows/sync-skills.md": "b505e6f8331c0918", "gsd-core/workflows/thread.md": "3b2c560d7189576d", "gsd-core/workflows/transition.md": "f6731bbb766929e7", - "gsd-core/workflows/ui-phase.md": "deaf9eff81cb6733", + "gsd-core/workflows/ui-phase.md": "05eb644af3a15516", "gsd-core/workflows/ui-review.md": "7b488a7486a6b7cf", "gsd-core/workflows/ultraplan-phase.md": "328664400a001fd5", "gsd-core/workflows/undo.md": "791e0bf96d9a057f", diff --git a/tests/fixtures/golden-install-parity/cline.json b/tests/fixtures/golden-install-parity/cline.json index 5be4ab26d..f4f2efcb1 100644 --- a/tests/fixtures/golden-install-parity/cline.json +++ b/tests/fixtures/golden-install-parity/cline.json @@ -34,8 +34,8 @@ "agents/gsd-roadmapper.md": "bbb23d3097911516", "agents/gsd-security-auditor.md": "c35c8ec2f85b331f", "agents/gsd-ui-auditor.md": "9338efa31b9b7b99", - "agents/gsd-ui-checker.md": "548c820528c95e39", - "agents/gsd-ui-researcher.md": "eea2dfddf30aec9d", + "agents/gsd-ui-checker.md": "151d12b4e007cbbf", + "agents/gsd-ui-researcher.md": "1bb303f3a3c4dfc9", "agents/gsd-user-profiler.md": "622220df0654b6bf", "agents/gsd-verifier.md": "75912e9cadd83eb3", "gsd-core/VERSION": "ef0deccd81a6723c", @@ -141,6 +141,7 @@ "gsd-core/references/thinking-models-verification.md": "a71a933d51ca3d8d", "gsd-core/references/thinking-partner.md": "41069529ef776e39", "gsd-core/references/ui-brand.md": "48717bcfcd63bd27", + "gsd-core/references/ui-consideration-probe.md": "7e019dfaae47f4c4", "gsd-core/references/universal-anti-patterns.md": "6a1245050b21df01", "gsd-core/references/untrusted-input-boundary.md": "d33b80d4d348599a", "gsd-core/references/user-profiling.md": "b50416fe57c1b321", @@ -156,7 +157,7 @@ "gsd-core/templates/README.md": "004c355d4b02b145", "gsd-core/templates/SECURITY.md": "b628f7f1c6d2328f", "gsd-core/templates/UAT.md": "9e296471b97ebcec", - "gsd-core/templates/UI-SPEC.md": "20ca56a4e3e21f01", + "gsd-core/templates/UI-SPEC.md": "7dd5c7cdc7ece0ec", "gsd-core/templates/VALIDATION.md": "f53e0ca061d3528e", "gsd-core/templates/claude-md.md": "c9fea2d8afa17d80", "gsd-core/templates/codebase/architecture.md": "6be88214162fdd89", @@ -273,7 +274,7 @@ "gsd-core/workflows/onboard.md": "6f9e6c0b484271a9", "gsd-core/workflows/pause-work.md": "3530607514b0ac00", "gsd-core/workflows/plan-milestone-gaps.md": "bafdc6945cd2bd87", - "gsd-core/workflows/plan-phase.md": "a08c5c99792f3086", + "gsd-core/workflows/plan-phase.md": "5a0e013e461375dd", "gsd-core/workflows/plan-phase/steps/closed-phase-gate.md": "b36f77ac7344a072", "gsd-core/workflows/plan-phase/steps/prd-express-path.md": "4b0a2cb0f4f28179", "gsd-core/workflows/plan-phase/steps/windows-troubleshooting.md": "090c31e22b1508fe", @@ -305,7 +306,7 @@ "gsd-core/workflows/sync-skills.md": "b505e6f8331c0918", "gsd-core/workflows/thread.md": "d71b1e815925c5cc", "gsd-core/workflows/transition.md": "b9e41eb751375faf", - "gsd-core/workflows/ui-phase.md": "dfc93789f7fa93f5", + "gsd-core/workflows/ui-phase.md": "c7e6a2ab78e74c1b", "gsd-core/workflows/ui-review.md": "8cd9605add21301e", "gsd-core/workflows/ultraplan-phase.md": "ceff456b1e9d94d8", "gsd-core/workflows/undo.md": "96d2775f008b3a85", diff --git a/tests/fixtures/golden-install-parity/codebuddy.json b/tests/fixtures/golden-install-parity/codebuddy.json index 5d5b986b8..7ddf5cd91 100644 --- a/tests/fixtures/golden-install-parity/codebuddy.json +++ b/tests/fixtures/golden-install-parity/codebuddy.json @@ -31,8 +31,8 @@ "agents/gsd-roadmapper.md": "20b69eff61a7a9fa", "agents/gsd-security-auditor.md": "d3b8f44034a76c9d", "agents/gsd-ui-auditor.md": "a26bbc733817959b", - "agents/gsd-ui-checker.md": "c114c86b5aee38ee", - "agents/gsd-ui-researcher.md": "5b2d98efecd3ca11", + "agents/gsd-ui-checker.md": "25822359044cd708", + "agents/gsd-ui-researcher.md": "e37d9f53ade25d1f", "agents/gsd-user-profiler.md": "622220df0654b6bf", "agents/gsd-verifier.md": "c8a8c73a277a512f", "commands/gsd-add-tests.md": "aa65032141557fb7", @@ -209,6 +209,7 @@ "gsd-core/references/thinking-models-verification.md": "a71a933d51ca3d8d", "gsd-core/references/thinking-partner.md": "41069529ef776e39", "gsd-core/references/ui-brand.md": "48717bcfcd63bd27", + "gsd-core/references/ui-consideration-probe.md": "7e019dfaae47f4c4", "gsd-core/references/universal-anti-patterns.md": "6a1245050b21df01", "gsd-core/references/untrusted-input-boundary.md": "d33b80d4d348599a", "gsd-core/references/user-profiling.md": "b50416fe57c1b321", @@ -224,7 +225,7 @@ "gsd-core/templates/README.md": "93d3426fc64e2c12", "gsd-core/templates/SECURITY.md": "b628f7f1c6d2328f", "gsd-core/templates/UAT.md": "9e296471b97ebcec", - "gsd-core/templates/UI-SPEC.md": "20ca56a4e3e21f01", + "gsd-core/templates/UI-SPEC.md": "7dd5c7cdc7ece0ec", "gsd-core/templates/VALIDATION.md": "f53e0ca061d3528e", "gsd-core/templates/claude-md.md": "d1d333e4b963c0d2", "gsd-core/templates/codebase/architecture.md": "6be88214162fdd89", @@ -341,7 +342,7 @@ "gsd-core/workflows/onboard.md": "6f9e6c0b484271a9", "gsd-core/workflows/pause-work.md": "f2b33bba5593d422", "gsd-core/workflows/plan-milestone-gaps.md": "852f6d7c0c4299dc", - "gsd-core/workflows/plan-phase.md": "7e7e9c1c99b83834", + "gsd-core/workflows/plan-phase.md": "27d4e6b4a084dd99", "gsd-core/workflows/plan-phase/steps/closed-phase-gate.md": "b36f77ac7344a072", "gsd-core/workflows/plan-phase/steps/prd-express-path.md": "197c0590326371b2", "gsd-core/workflows/plan-phase/steps/windows-troubleshooting.md": "49f58c3f75be3eb5", @@ -373,7 +374,7 @@ "gsd-core/workflows/sync-skills.md": "b505e6f8331c0918", "gsd-core/workflows/thread.md": "927e7eeefd2fcf5c", "gsd-core/workflows/transition.md": "cb8ec5affb7ebba1", - "gsd-core/workflows/ui-phase.md": "7313e999421db8c1", + "gsd-core/workflows/ui-phase.md": "e4bef31fd9389c63", "gsd-core/workflows/ui-review.md": "7acfc485526d064b", "gsd-core/workflows/ultraplan-phase.md": "0fb8291153e3937d", "gsd-core/workflows/undo.md": "96d2775f008b3a85", diff --git a/tests/fixtures/golden-install-parity/codex.json b/tests/fixtures/golden-install-parity/codex.json index 46950ce44..f5a0013f4 100644 --- a/tests/fixtures/golden-install-parity/codex.json +++ b/tests/fixtures/golden-install-parity/codex.json @@ -132,10 +132,10 @@ "agents/gsd-security-auditor.toml": "42f529475bd28c22", "agents/gsd-ui-auditor.md": "abbf560bc8d5069c", "agents/gsd-ui-auditor.toml": "5e9dd62a12a16a1e", - "agents/gsd-ui-checker.md": "c5afa8df782f338c", - "agents/gsd-ui-checker.toml": "b5ca67cf1a0dd9fe", - "agents/gsd-ui-researcher.md": "2befca69af55e44a", - "agents/gsd-ui-researcher.toml": "b55270666066e501", + "agents/gsd-ui-checker.md": "b65d350a4e0564e9", + "agents/gsd-ui-checker.toml": "3815693c6649f9f5", + "agents/gsd-ui-researcher.md": "e18cc90fadc5ab2e", + "agents/gsd-ui-researcher.toml": "7a38aff7b13d9e37", "agents/gsd-user-profiler.md": "1bf5033c929181c1", "agents/gsd-user-profiler.toml": "b9c244bb8fbf8140", "agents/gsd-verifier.md": "4ac4b860e2504374", @@ -244,6 +244,7 @@ "gsd-core/references/thinking-models-verification.md": "a71a933d51ca3d8d", "gsd-core/references/thinking-partner.md": "347c33b2d5646c93", "gsd-core/references/ui-brand.md": "37a2dc822a4b77c4", + "gsd-core/references/ui-consideration-probe.md": "7e019dfaae47f4c4", "gsd-core/references/universal-anti-patterns.md": "865f7ba442795487", "gsd-core/references/untrusted-input-boundary.md": "d33b80d4d348599a", "gsd-core/references/user-profiling.md": "8e023a908d968af1", @@ -259,7 +260,7 @@ "gsd-core/templates/README.md": "dc13994dabe139a2", "gsd-core/templates/SECURITY.md": "b628f7f1c6d2328f", "gsd-core/templates/UAT.md": "59a3d4f6c4afbfc9", - "gsd-core/templates/UI-SPEC.md": "20ca56a4e3e21f01", + "gsd-core/templates/UI-SPEC.md": "7dd5c7cdc7ece0ec", "gsd-core/templates/VALIDATION.md": "e3ca8ebb8d7e2cd0", "gsd-core/templates/claude-md.md": "dd1a9011684f9753", "gsd-core/templates/codebase/architecture.md": "282db635ba093b1a", @@ -376,7 +377,7 @@ "gsd-core/workflows/onboard.md": "83c40ba7055b8b24", "gsd-core/workflows/pause-work.md": "a217770ecafcb2e0", "gsd-core/workflows/plan-milestone-gaps.md": "73d46f77c50a0690", - "gsd-core/workflows/plan-phase.md": "34c4d4c7ad7fdf36", + "gsd-core/workflows/plan-phase.md": "ec989d6bc926bdf6", "gsd-core/workflows/plan-phase/steps/closed-phase-gate.md": "d838b87563feedf6", "gsd-core/workflows/plan-phase/steps/prd-express-path.md": "f10975692cbd036e", "gsd-core/workflows/plan-phase/steps/windows-troubleshooting.md": "f5edc589cab52a7b", @@ -408,7 +409,7 @@ "gsd-core/workflows/sync-skills.md": "e2b793963799f8ce", "gsd-core/workflows/thread.md": "3eef3b6baf50fbfa", "gsd-core/workflows/transition.md": "423d12e392f5cb87", - "gsd-core/workflows/ui-phase.md": "de272c1e82f49486", + "gsd-core/workflows/ui-phase.md": "989eefe25c06ae48", "gsd-core/workflows/ui-review.md": "5768b7aac1429ea0", "gsd-core/workflows/ultraplan-phase.md": "0bafc2af27be4591", "gsd-core/workflows/undo.md": "5ff7d63b0a2f46d5", diff --git a/tests/fixtures/golden-install-parity/copilot.json b/tests/fixtures/golden-install-parity/copilot.json index 491c59ae8..948c1d6cf 100644 --- a/tests/fixtures/golden-install-parity/copilot.json +++ b/tests/fixtures/golden-install-parity/copilot.json @@ -31,8 +31,8 @@ "agents/gsd-roadmapper.agent.md": "322048cf8ddcb4e5", "agents/gsd-security-auditor.agent.md": "6f6a88b35dc2a24b", "agents/gsd-ui-auditor.agent.md": "92f50549e84ef482", - "agents/gsd-ui-checker.agent.md": "a28c41265b4b01c7", - "agents/gsd-ui-researcher.agent.md": "d24301cc182be33e", + "agents/gsd-ui-checker.agent.md": "47d8cf3486009e11", + "agents/gsd-ui-researcher.agent.md": "0746daeb54f83008", "agents/gsd-user-profiler.agent.md": "ae16a248e18dd42b", "agents/gsd-verifier.agent.md": "87a8e3238e838a39", "copilot-instructions.md": "1fb04111759f1645", @@ -139,6 +139,7 @@ "gsd-core/references/thinking-models-verification.md": "a71a933d51ca3d8d", "gsd-core/references/thinking-partner.md": "827c1badf3e6df41", "gsd-core/references/ui-brand.md": "48717bcfcd63bd27", + "gsd-core/references/ui-consideration-probe.md": "7e019dfaae47f4c4", "gsd-core/references/universal-anti-patterns.md": "6a1245050b21df01", "gsd-core/references/untrusted-input-boundary.md": "d33b80d4d348599a", "gsd-core/references/user-profiling.md": "8e023a908d968af1", @@ -154,7 +155,7 @@ "gsd-core/templates/README.md": "da29c64b438065b3", "gsd-core/templates/SECURITY.md": "b628f7f1c6d2328f", "gsd-core/templates/UAT.md": "68d32d1fea14e184", - "gsd-core/templates/UI-SPEC.md": "20ca56a4e3e21f01", + "gsd-core/templates/UI-SPEC.md": "7dd5c7cdc7ece0ec", "gsd-core/templates/VALIDATION.md": "6144951011cdca57", "gsd-core/templates/claude-md.md": "f436ae75a9c8518a", "gsd-core/templates/codebase/architecture.md": "282db635ba093b1a", @@ -271,7 +272,7 @@ "gsd-core/workflows/onboard.md": "a62602c6f3fd538a", "gsd-core/workflows/pause-work.md": "9ce66367be6c40db", "gsd-core/workflows/plan-milestone-gaps.md": "5cf589802d08bdf3", - "gsd-core/workflows/plan-phase.md": "909457cfb58ce634", + "gsd-core/workflows/plan-phase.md": "5a2cd75b453aa573", "gsd-core/workflows/plan-phase/steps/closed-phase-gate.md": "4099ef6d0868de60", "gsd-core/workflows/plan-phase/steps/prd-express-path.md": "bb052483744f0a6d", "gsd-core/workflows/plan-phase/steps/windows-troubleshooting.md": "e9de7a96bbfff261", @@ -303,7 +304,7 @@ "gsd-core/workflows/sync-skills.md": "eca50ffe8320dba8", "gsd-core/workflows/thread.md": "c5cbd66223906998", "gsd-core/workflows/transition.md": "0bb671f58f9522f3", - "gsd-core/workflows/ui-phase.md": "42142cae9ce6df5e", + "gsd-core/workflows/ui-phase.md": "646b99f22a6e4762", "gsd-core/workflows/ui-review.md": "2851b3576894ee75", "gsd-core/workflows/ultraplan-phase.md": "7217c34dc9eaf7bb", "gsd-core/workflows/undo.md": "ba1ef7aa80bef6bd", diff --git a/tests/fixtures/golden-install-parity/cursor.json b/tests/fixtures/golden-install-parity/cursor.json index dbc6937ea..0bb3bb986 100644 --- a/tests/fixtures/golden-install-parity/cursor.json +++ b/tests/fixtures/golden-install-parity/cursor.json @@ -31,8 +31,8 @@ "agents/gsd-roadmapper.md": "d28e7d4bac46dde2", "agents/gsd-security-auditor.md": "5ff44ee432387d93", "agents/gsd-ui-auditor.md": "d824acc3b2a18c53", - "agents/gsd-ui-checker.md": "783daaed264d1dbc", - "agents/gsd-ui-researcher.md": "1b4d93bd032ebfde", + "agents/gsd-ui-checker.md": "c6c24e8066470830", + "agents/gsd-ui-researcher.md": "0f5be5e55501f7e8", "agents/gsd-user-profiler.md": "622220df0654b6bf", "agents/gsd-verifier.md": "dea8e61123848fb4", "commands/gsd-add-tests.md": "1f89b16ab2cca426", @@ -209,6 +209,7 @@ "gsd-core/references/thinking-models-verification.md": "a71a933d51ca3d8d", "gsd-core/references/thinking-partner.md": "827c1badf3e6df41", "gsd-core/references/ui-brand.md": "48717bcfcd63bd27", + "gsd-core/references/ui-consideration-probe.md": "7e019dfaae47f4c4", "gsd-core/references/universal-anti-patterns.md": "6a1245050b21df01", "gsd-core/references/untrusted-input-boundary.md": "d33b80d4d348599a", "gsd-core/references/user-profiling.md": "b50416fe57c1b321", @@ -224,7 +225,7 @@ "gsd-core/templates/README.md": "83a3d5b593587e83", "gsd-core/templates/SECURITY.md": "b628f7f1c6d2328f", "gsd-core/templates/UAT.md": "68d32d1fea14e184", - "gsd-core/templates/UI-SPEC.md": "20ca56a4e3e21f01", + "gsd-core/templates/UI-SPEC.md": "7dd5c7cdc7ece0ec", "gsd-core/templates/VALIDATION.md": "6144951011cdca57", "gsd-core/templates/claude-md.md": "d6d7da8b7817a04c", "gsd-core/templates/codebase/architecture.md": "6be88214162fdd89", @@ -341,7 +342,7 @@ "gsd-core/workflows/onboard.md": "20c28136423d40ac", "gsd-core/workflows/pause-work.md": "5716362557f44ce4", "gsd-core/workflows/plan-milestone-gaps.md": "1b43d12812f7bc1e", - "gsd-core/workflows/plan-phase.md": "d10fdacf2b9d850e", + "gsd-core/workflows/plan-phase.md": "c07b91bb188270d7", "gsd-core/workflows/plan-phase/steps/closed-phase-gate.md": "e06ccd4d4c0703fb", "gsd-core/workflows/plan-phase/steps/prd-express-path.md": "197c0590326371b2", "gsd-core/workflows/plan-phase/steps/windows-troubleshooting.md": "3bed01c3c906ac52", @@ -373,7 +374,7 @@ "gsd-core/workflows/sync-skills.md": "b505e6f8331c0918", "gsd-core/workflows/thread.md": "bf9a944978dad763", "gsd-core/workflows/transition.md": "f6731bbb766929e7", - "gsd-core/workflows/ui-phase.md": "81f029c1bd2431b7", + "gsd-core/workflows/ui-phase.md": "d1ae54a7ba413d69", "gsd-core/workflows/ui-review.md": "4bc0fd55fc037998", "gsd-core/workflows/ultraplan-phase.md": "466e01d65c350ef6", "gsd-core/workflows/undo.md": "18dec684fb1076f9", diff --git a/tests/fixtures/golden-install-parity/hermes.json b/tests/fixtures/golden-install-parity/hermes.json index fcebacbd0..779b4b835 100644 --- a/tests/fixtures/golden-install-parity/hermes.json +++ b/tests/fixtures/golden-install-parity/hermes.json @@ -31,8 +31,8 @@ "agents/gsd-roadmapper.md": "64dce5d5f9fa5654", "agents/gsd-security-auditor.md": "e4d35ada4ea67d7f", "agents/gsd-ui-auditor.md": "86797e85f718dfac", - "agents/gsd-ui-checker.md": "b3161cc5fbb7fd61", - "agents/gsd-ui-researcher.md": "25ffa7fa83c32a69", + "agents/gsd-ui-checker.md": "cfc8a3bac0a0ef5b", + "agents/gsd-ui-researcher.md": "8799b6013e06ae49", "agents/gsd-user-profiler.md": "ca3bf75581f211a0", "agents/gsd-verifier.md": "82d3e9015ed04017", "gsd-core/VERSION": "ef0deccd81a6723c", @@ -138,6 +138,7 @@ "gsd-core/references/thinking-models-verification.md": "a71a933d51ca3d8d", "gsd-core/references/thinking-partner.md": "827c1badf3e6df41", "gsd-core/references/ui-brand.md": "48717bcfcd63bd27", + "gsd-core/references/ui-consideration-probe.md": "7e019dfaae47f4c4", "gsd-core/references/universal-anti-patterns.md": "6a1245050b21df01", "gsd-core/references/untrusted-input-boundary.md": "d33b80d4d348599a", "gsd-core/references/user-profiling.md": "b50416fe57c1b321", @@ -153,7 +154,7 @@ "gsd-core/templates/README.md": "89560317a9097a05", "gsd-core/templates/SECURITY.md": "b628f7f1c6d2328f", "gsd-core/templates/UAT.md": "68d32d1fea14e184", - "gsd-core/templates/UI-SPEC.md": "20ca56a4e3e21f01", + "gsd-core/templates/UI-SPEC.md": "7dd5c7cdc7ece0ec", "gsd-core/templates/VALIDATION.md": "6144951011cdca57", "gsd-core/templates/claude-md.md": "7c778398f79e25a3", "gsd-core/templates/codebase/architecture.md": "6be88214162fdd89", @@ -270,7 +271,7 @@ "gsd-core/workflows/onboard.md": "3c50ed1f1fd07619", "gsd-core/workflows/pause-work.md": "ae2d5789a95f70fe", "gsd-core/workflows/plan-milestone-gaps.md": "c86cdc1964256b98", - "gsd-core/workflows/plan-phase.md": "e30dcfb9615f19da", + "gsd-core/workflows/plan-phase.md": "6e3846fb2ae1bd45", "gsd-core/workflows/plan-phase/steps/closed-phase-gate.md": "4099ef6d0868de60", "gsd-core/workflows/plan-phase/steps/prd-express-path.md": "9607e6d03e93c1c2", "gsd-core/workflows/plan-phase/steps/windows-troubleshooting.md": "62f8e4f3b475fe5f", @@ -302,7 +303,7 @@ "gsd-core/workflows/sync-skills.md": "b505e6f8331c0918", "gsd-core/workflows/thread.md": "5a6759e01763c2a6", "gsd-core/workflows/transition.md": "2f65c0e12675ea08", - "gsd-core/workflows/ui-phase.md": "082a41a0549211fe", + "gsd-core/workflows/ui-phase.md": "a45a9409c26699f4", "gsd-core/workflows/ui-review.md": "bc0ae72c0e1cab94", "gsd-core/workflows/ultraplan-phase.md": "0d103bf2622436f7", "gsd-core/workflows/undo.md": "791e0bf96d9a057f", diff --git a/tests/fixtures/golden-install-parity/kilo.json b/tests/fixtures/golden-install-parity/kilo.json index f865d0b20..dc0369c9a 100644 --- a/tests/fixtures/golden-install-parity/kilo.json +++ b/tests/fixtures/golden-install-parity/kilo.json @@ -31,8 +31,8 @@ "agents/gsd-roadmapper.md": "277e0a3252553ab7", "agents/gsd-security-auditor.md": "0113435969e869c4", "agents/gsd-ui-auditor.md": "9b988b95d28e56ed", - "agents/gsd-ui-checker.md": "35afd6eaf5588659", - "agents/gsd-ui-researcher.md": "68b046253268aa64", + "agents/gsd-ui-checker.md": "e078ea5a07313976", + "agents/gsd-ui-researcher.md": "cc9578c4f686d926", "agents/gsd-user-profiler.md": "b8cb09319c517701", "agents/gsd-verifier.md": "af609b1959cde3f3", "command/gsd-add-tests.md": "c314f9a6be312bfa", @@ -209,6 +209,7 @@ "gsd-core/references/thinking-models-verification.md": "a71a933d51ca3d8d", "gsd-core/references/thinking-partner.md": "827c1badf3e6df41", "gsd-core/references/ui-brand.md": "48717bcfcd63bd27", + "gsd-core/references/ui-consideration-probe.md": "7e019dfaae47f4c4", "gsd-core/references/universal-anti-patterns.md": "6a1245050b21df01", "gsd-core/references/untrusted-input-boundary.md": "d33b80d4d348599a", "gsd-core/references/user-profiling.md": "8e023a908d968af1", @@ -224,7 +225,7 @@ "gsd-core/templates/README.md": "73d3c9689b6efbc9", "gsd-core/templates/SECURITY.md": "b628f7f1c6d2328f", "gsd-core/templates/UAT.md": "68d32d1fea14e184", - "gsd-core/templates/UI-SPEC.md": "20ca56a4e3e21f01", + "gsd-core/templates/UI-SPEC.md": "7dd5c7cdc7ece0ec", "gsd-core/templates/VALIDATION.md": "6144951011cdca57", "gsd-core/templates/claude-md.md": "f35856254768bf7d", "gsd-core/templates/codebase/architecture.md": "282db635ba093b1a", @@ -341,7 +342,7 @@ "gsd-core/workflows/onboard.md": "78b288089305dc46", "gsd-core/workflows/pause-work.md": "a6e5336c409fdc8b", "gsd-core/workflows/plan-milestone-gaps.md": "1b43d12812f7bc1e", - "gsd-core/workflows/plan-phase.md": "6f5eacc08e5d9d2a", + "gsd-core/workflows/plan-phase.md": "65667ea11d155b97", "gsd-core/workflows/plan-phase/steps/closed-phase-gate.md": "4099ef6d0868de60", "gsd-core/workflows/plan-phase/steps/prd-express-path.md": "f10975692cbd036e", "gsd-core/workflows/plan-phase/steps/windows-troubleshooting.md": "e9de7a96bbfff261", @@ -373,7 +374,7 @@ "gsd-core/workflows/sync-skills.md": "dc7b8b015afa4b3b", "gsd-core/workflows/thread.md": "3b2c560d7189576d", "gsd-core/workflows/transition.md": "caf7616dc597ad86", - "gsd-core/workflows/ui-phase.md": "638e871b80cacc6d", + "gsd-core/workflows/ui-phase.md": "47ae6541bbfdf2b6", "gsd-core/workflows/ui-review.md": "0fb289914252d53e", "gsd-core/workflows/ultraplan-phase.md": "29245758b8497fa0", "gsd-core/workflows/undo.md": "7cd2153f8b15e30d", diff --git a/tests/fixtures/golden-install-parity/kimi.json b/tests/fixtures/golden-install-parity/kimi.json index 327225224..0bdc80b25 100644 --- a/tests/fixtures/golden-install-parity/kimi.json +++ b/tests/fixtures/golden-install-parity/kimi.json @@ -63,9 +63,9 @@ "agents/subagents/gsd-security-auditor.yaml": "fe8a4345cc13571d", "agents/subagents/gsd-ui-auditor.md": "e4a319070959ebbc", "agents/subagents/gsd-ui-auditor.yaml": "3fc98c1d9e8f10fd", - "agents/subagents/gsd-ui-checker.md": "df6ba7c57f18ce52", + "agents/subagents/gsd-ui-checker.md": "07cd4e382ca55994", "agents/subagents/gsd-ui-checker.yaml": "c42316006654fae3", - "agents/subagents/gsd-ui-researcher.md": "678f035c1fdd0603", + "agents/subagents/gsd-ui-researcher.md": "974f7ecca58c7afa", "agents/subagents/gsd-ui-researcher.yaml": "15e215874bc2c37d", "agents/subagents/gsd-user-profiler.md": "c16f94e09e433394", "agents/subagents/gsd-user-profiler.yaml": "645826a29d079159", @@ -174,6 +174,7 @@ "gsd-core/references/thinking-models-verification.md": "a71a933d51ca3d8d", "gsd-core/references/thinking-partner.md": "41069529ef776e39", "gsd-core/references/ui-brand.md": "48717bcfcd63bd27", + "gsd-core/references/ui-consideration-probe.md": "7e019dfaae47f4c4", "gsd-core/references/universal-anti-patterns.md": "6a1245050b21df01", "gsd-core/references/untrusted-input-boundary.md": "d33b80d4d348599a", "gsd-core/references/user-profiling.md": "b50416fe57c1b321", @@ -189,7 +190,7 @@ "gsd-core/templates/README.md": "93d3426fc64e2c12", "gsd-core/templates/SECURITY.md": "b628f7f1c6d2328f", "gsd-core/templates/UAT.md": "9e296471b97ebcec", - "gsd-core/templates/UI-SPEC.md": "20ca56a4e3e21f01", + "gsd-core/templates/UI-SPEC.md": "7dd5c7cdc7ece0ec", "gsd-core/templates/VALIDATION.md": "f53e0ca061d3528e", "gsd-core/templates/claude-md.md": "d1d333e4b963c0d2", "gsd-core/templates/codebase/architecture.md": "6be88214162fdd89", @@ -306,7 +307,7 @@ "gsd-core/workflows/onboard.md": "6f9e6c0b484271a9", "gsd-core/workflows/pause-work.md": "f2b33bba5593d422", "gsd-core/workflows/plan-milestone-gaps.md": "852f6d7c0c4299dc", - "gsd-core/workflows/plan-phase.md": "bfd41e5f867bd751", + "gsd-core/workflows/plan-phase.md": "f584e1d652e47f40", "gsd-core/workflows/plan-phase/steps/closed-phase-gate.md": "b36f77ac7344a072", "gsd-core/workflows/plan-phase/steps/prd-express-path.md": "197c0590326371b2", "gsd-core/workflows/plan-phase/steps/windows-troubleshooting.md": "49f58c3f75be3eb5", @@ -338,7 +339,7 @@ "gsd-core/workflows/sync-skills.md": "b505e6f8331c0918", "gsd-core/workflows/thread.md": "927e7eeefd2fcf5c", "gsd-core/workflows/transition.md": "cb8ec5affb7ebba1", - "gsd-core/workflows/ui-phase.md": "6c4d02dfda1efcae", + "gsd-core/workflows/ui-phase.md": "d02054ab9f01abb4", "gsd-core/workflows/ui-review.md": "7acfc485526d064b", "gsd-core/workflows/ultraplan-phase.md": "0fb8291153e3937d", "gsd-core/workflows/undo.md": "96d2775f008b3a85", diff --git a/tests/fixtures/golden-install-parity/opencode.json b/tests/fixtures/golden-install-parity/opencode.json index fd7fb5267..73b3e653e 100644 --- a/tests/fixtures/golden-install-parity/opencode.json +++ b/tests/fixtures/golden-install-parity/opencode.json @@ -31,8 +31,8 @@ "agents/gsd-roadmapper.md": "1658a40b20d8b575", "agents/gsd-security-auditor.md": "e36e436c0d5c26d5", "agents/gsd-ui-auditor.md": "e810012e685b2466", - "agents/gsd-ui-checker.md": "909bdad2067d5028", - "agents/gsd-ui-researcher.md": "d2e1ed5af2df4a7d", + "agents/gsd-ui-checker.md": "4f88fd4c9d4c56a3", + "agents/gsd-ui-researcher.md": "ecb617901cb7ad06", "agents/gsd-user-profiler.md": "d825b4c0a6431f8b", "agents/gsd-verifier.md": "b9f2e801f9395a35", "command/gsd-add-tests.md": "b9cd93ed01945f75", @@ -209,6 +209,7 @@ "gsd-core/references/thinking-models-verification.md": "a71a933d51ca3d8d", "gsd-core/references/thinking-partner.md": "827c1badf3e6df41", "gsd-core/references/ui-brand.md": "48717bcfcd63bd27", + "gsd-core/references/ui-consideration-probe.md": "7e019dfaae47f4c4", "gsd-core/references/universal-anti-patterns.md": "6a1245050b21df01", "gsd-core/references/untrusted-input-boundary.md": "d33b80d4d348599a", "gsd-core/references/user-profiling.md": "8e023a908d968af1", @@ -224,7 +225,7 @@ "gsd-core/templates/README.md": "73d3c9689b6efbc9", "gsd-core/templates/SECURITY.md": "b628f7f1c6d2328f", "gsd-core/templates/UAT.md": "68d32d1fea14e184", - "gsd-core/templates/UI-SPEC.md": "20ca56a4e3e21f01", + "gsd-core/templates/UI-SPEC.md": "7dd5c7cdc7ece0ec", "gsd-core/templates/VALIDATION.md": "6144951011cdca57", "gsd-core/templates/claude-md.md": "a4f5e38984001194", "gsd-core/templates/codebase/architecture.md": "282db635ba093b1a", @@ -341,7 +342,7 @@ "gsd-core/workflows/onboard.md": "102556e1715c01b9", "gsd-core/workflows/pause-work.md": "70c72beca55c080a", "gsd-core/workflows/plan-milestone-gaps.md": "0a9dacd422cd9533", - "gsd-core/workflows/plan-phase.md": "c771c9c99f4d777d", + "gsd-core/workflows/plan-phase.md": "824b0fa8d7554ae7", "gsd-core/workflows/plan-phase/steps/closed-phase-gate.md": "4099ef6d0868de60", "gsd-core/workflows/plan-phase/steps/prd-express-path.md": "3a09141de7f3dedb", "gsd-core/workflows/plan-phase/steps/windows-troubleshooting.md": "e9de7a96bbfff261", @@ -373,7 +374,7 @@ "gsd-core/workflows/sync-skills.md": "7e2c138cdbef4282", "gsd-core/workflows/thread.md": "82652edb1883af59", "gsd-core/workflows/transition.md": "a66f117fad29d65a", - "gsd-core/workflows/ui-phase.md": "3927811cceccf3fd", + "gsd-core/workflows/ui-phase.md": "4d11cc725ebfe597", "gsd-core/workflows/ui-review.md": "149f39373eee5c92", "gsd-core/workflows/ultraplan-phase.md": "f4bad8de3fdb49fa", "gsd-core/workflows/undo.md": "0bba5e7f6196c894", diff --git a/tests/fixtures/golden-install-parity/qwen.json b/tests/fixtures/golden-install-parity/qwen.json index 46350f3b8..2472db607 100644 --- a/tests/fixtures/golden-install-parity/qwen.json +++ b/tests/fixtures/golden-install-parity/qwen.json @@ -31,8 +31,8 @@ "agents/gsd-roadmapper.md": "604b25c6687811f4", "agents/gsd-security-auditor.md": "f85447e5b5300feb", "agents/gsd-ui-auditor.md": "cc46d104cbc49079", - "agents/gsd-ui-checker.md": "fa0b9c2510ee40c0", - "agents/gsd-ui-researcher.md": "623cb59aa6dbe146", + "agents/gsd-ui-checker.md": "702b224dede2618f", + "agents/gsd-ui-researcher.md": "3cab4101dd0a3e1d", "agents/gsd-user-profiler.md": "ca3bf75581f211a0", "agents/gsd-verifier.md": "45c450b6d7cc5f0d", "gsd-core/VERSION": "ef0deccd81a6723c", @@ -138,6 +138,7 @@ "gsd-core/references/thinking-models-verification.md": "a71a933d51ca3d8d", "gsd-core/references/thinking-partner.md": "827c1badf3e6df41", "gsd-core/references/ui-brand.md": "48717bcfcd63bd27", + "gsd-core/references/ui-consideration-probe.md": "7e019dfaae47f4c4", "gsd-core/references/universal-anti-patterns.md": "6a1245050b21df01", "gsd-core/references/untrusted-input-boundary.md": "d33b80d4d348599a", "gsd-core/references/user-profiling.md": "b50416fe57c1b321", @@ -153,7 +154,7 @@ "gsd-core/templates/README.md": "f4825d4fa594f2b1", "gsd-core/templates/SECURITY.md": "b628f7f1c6d2328f", "gsd-core/templates/UAT.md": "68d32d1fea14e184", - "gsd-core/templates/UI-SPEC.md": "20ca56a4e3e21f01", + "gsd-core/templates/UI-SPEC.md": "7dd5c7cdc7ece0ec", "gsd-core/templates/VALIDATION.md": "6144951011cdca57", "gsd-core/templates/claude-md.md": "ec5972ab31c0f5d0", "gsd-core/templates/codebase/architecture.md": "6be88214162fdd89", @@ -270,7 +271,7 @@ "gsd-core/workflows/onboard.md": "3c50ed1f1fd07619", "gsd-core/workflows/pause-work.md": "be33f84dc1d4822f", "gsd-core/workflows/plan-milestone-gaps.md": "d98e98486123eb97", - "gsd-core/workflows/plan-phase.md": "8ef2986df30b616e", + "gsd-core/workflows/plan-phase.md": "e33c0b28f48db48a", "gsd-core/workflows/plan-phase/steps/closed-phase-gate.md": "4099ef6d0868de60", "gsd-core/workflows/plan-phase/steps/prd-express-path.md": "c22ff5ea46de665a", "gsd-core/workflows/plan-phase/steps/windows-troubleshooting.md": "d050d8d551ed1756", @@ -302,7 +303,7 @@ "gsd-core/workflows/sync-skills.md": "b505e6f8331c0918", "gsd-core/workflows/thread.md": "f24b6454c69d064a", "gsd-core/workflows/transition.md": "9c615db5686219d0", - "gsd-core/workflows/ui-phase.md": "a1f11225433e1795", + "gsd-core/workflows/ui-phase.md": "041cb6bcb496881d", "gsd-core/workflows/ui-review.md": "8b989f372684851f", "gsd-core/workflows/ultraplan-phase.md": "49921383982474e0", "gsd-core/workflows/undo.md": "791e0bf96d9a057f", diff --git a/tests/fixtures/golden-install-parity/trae.json b/tests/fixtures/golden-install-parity/trae.json index 549b0dbfc..6b7e3a29e 100644 --- a/tests/fixtures/golden-install-parity/trae.json +++ b/tests/fixtures/golden-install-parity/trae.json @@ -31,8 +31,8 @@ "agents/gsd-roadmapper.md": "493ef92b42b12cf4", "agents/gsd-security-auditor.md": "fbd3798eec23651b", "agents/gsd-ui-auditor.md": "771ae08260534d5c", - "agents/gsd-ui-checker.md": "f97d16ad56f4b15b", - "agents/gsd-ui-researcher.md": "3e737e761bc67073", + "agents/gsd-ui-checker.md": "7ecd910efa6eb00e", + "agents/gsd-ui-researcher.md": "9e1a84a55a4cac99", "agents/gsd-user-profiler.md": "622220df0654b6bf", "agents/gsd-verifier.md": "99e95e235aacffbf", "gsd-core/VERSION": "ef0deccd81a6723c", @@ -138,6 +138,7 @@ "gsd-core/references/thinking-models-verification.md": "a71a933d51ca3d8d", "gsd-core/references/thinking-partner.md": "827c1badf3e6df41", "gsd-core/references/ui-brand.md": "48717bcfcd63bd27", + "gsd-core/references/ui-consideration-probe.md": "7e019dfaae47f4c4", "gsd-core/references/universal-anti-patterns.md": "6a1245050b21df01", "gsd-core/references/untrusted-input-boundary.md": "d33b80d4d348599a", "gsd-core/references/user-profiling.md": "b50416fe57c1b321", @@ -153,7 +154,7 @@ "gsd-core/templates/README.md": "332f65072c3f03ae", "gsd-core/templates/SECURITY.md": "b628f7f1c6d2328f", "gsd-core/templates/UAT.md": "68d32d1fea14e184", - "gsd-core/templates/UI-SPEC.md": "20ca56a4e3e21f01", + "gsd-core/templates/UI-SPEC.md": "7dd5c7cdc7ece0ec", "gsd-core/templates/VALIDATION.md": "6144951011cdca57", "gsd-core/templates/claude-md.md": "17217a07ab8a6485", "gsd-core/templates/codebase/architecture.md": "6be88214162fdd89", @@ -270,7 +271,7 @@ "gsd-core/workflows/onboard.md": "61f111302af0f404", "gsd-core/workflows/pause-work.md": "c20d267e28ce92f0", "gsd-core/workflows/plan-milestone-gaps.md": "26db7b9329b7ddc8", - "gsd-core/workflows/plan-phase.md": "dbdcfdb7aeb4a4f0", + "gsd-core/workflows/plan-phase.md": "8bc16d0a44541ac4", "gsd-core/workflows/plan-phase/steps/closed-phase-gate.md": "e06ccd4d4c0703fb", "gsd-core/workflows/plan-phase/steps/prd-express-path.md": "778b73a8db6f7c32", "gsd-core/workflows/plan-phase/steps/windows-troubleshooting.md": "619946c879f33b9d", @@ -302,7 +303,7 @@ "gsd-core/workflows/sync-skills.md": "b505e6f8331c0918", "gsd-core/workflows/thread.md": "0582c6be8a78bbd8", "gsd-core/workflows/transition.md": "163d220813b0835f", - "gsd-core/workflows/ui-phase.md": "871655025b431a55", + "gsd-core/workflows/ui-phase.md": "0c4c1a26a4297ac3", "gsd-core/workflows/ui-review.md": "4e70afbfd5601853", "gsd-core/workflows/ultraplan-phase.md": "77b58ef8af5209bd", "gsd-core/workflows/undo.md": "59b8baa54efc4110", diff --git a/tests/fixtures/golden-install-parity/windsurf.json b/tests/fixtures/golden-install-parity/windsurf.json index 3d78aa631..3342749db 100644 --- a/tests/fixtures/golden-install-parity/windsurf.json +++ b/tests/fixtures/golden-install-parity/windsurf.json @@ -31,8 +31,8 @@ "agents/gsd-roadmapper.md": "fb62e1e3de84b5f9", "agents/gsd-security-auditor.md": "13b660e2d336ed8e", "agents/gsd-ui-auditor.md": "20cc99872e9b998b", - "agents/gsd-ui-checker.md": "73412297c27be10a", - "agents/gsd-ui-researcher.md": "f830742b9493601c", + "agents/gsd-ui-checker.md": "56698909c270b130", + "agents/gsd-ui-researcher.md": "c348aa3ff8412ecb", "agents/gsd-user-profiler.md": "622220df0654b6bf", "agents/gsd-verifier.md": "a07b00b9c5b7b338", "gsd-core/VERSION": "ef0deccd81a6723c", @@ -138,6 +138,7 @@ "gsd-core/references/thinking-models-verification.md": "a71a933d51ca3d8d", "gsd-core/references/thinking-partner.md": "827c1badf3e6df41", "gsd-core/references/ui-brand.md": "48717bcfcd63bd27", + "gsd-core/references/ui-consideration-probe.md": "7e019dfaae47f4c4", "gsd-core/references/universal-anti-patterns.md": "6a1245050b21df01", "gsd-core/references/untrusted-input-boundary.md": "d33b80d4d348599a", "gsd-core/references/user-profiling.md": "b50416fe57c1b321", @@ -153,7 +154,7 @@ "gsd-core/templates/README.md": "ee35799bf08677f3", "gsd-core/templates/SECURITY.md": "b628f7f1c6d2328f", "gsd-core/templates/UAT.md": "68d32d1fea14e184", - "gsd-core/templates/UI-SPEC.md": "20ca56a4e3e21f01", + "gsd-core/templates/UI-SPEC.md": "7dd5c7cdc7ece0ec", "gsd-core/templates/VALIDATION.md": "6144951011cdca57", "gsd-core/templates/claude-md.md": "20be2a0201ab92cd", "gsd-core/templates/codebase/architecture.md": "6be88214162fdd89", @@ -270,7 +271,7 @@ "gsd-core/workflows/onboard.md": "20c28136423d40ac", "gsd-core/workflows/pause-work.md": "93fcc1c845da6396", "gsd-core/workflows/plan-milestone-gaps.md": "7880866ee1caf923", - "gsd-core/workflows/plan-phase.md": "c7c5cac6409937ac", + "gsd-core/workflows/plan-phase.md": "29567e9419151453", "gsd-core/workflows/plan-phase/steps/closed-phase-gate.md": "e06ccd4d4c0703fb", "gsd-core/workflows/plan-phase/steps/prd-express-path.md": "80b1ba493a9a967f", "gsd-core/workflows/plan-phase/steps/windows-troubleshooting.md": "3fed4740a91d0443", @@ -302,7 +303,7 @@ "gsd-core/workflows/sync-skills.md": "b505e6f8331c0918", "gsd-core/workflows/thread.md": "d83a0d0bd0f94c4c", "gsd-core/workflows/transition.md": "1bd8a772f0aac5ba", - "gsd-core/workflows/ui-phase.md": "2fcfffcfb344456d", + "gsd-core/workflows/ui-phase.md": "829285b180dd7b9e", "gsd-core/workflows/ui-review.md": "dca8100a43161ea7", "gsd-core/workflows/ultraplan-phase.md": "53b77a8edf60acd0", "gsd-core/workflows/undo.md": "18dec684fb1076f9", diff --git a/tests/fixtures/golden-install-parity/zcode.json b/tests/fixtures/golden-install-parity/zcode.json index 06726787a..aee256ede 100644 --- a/tests/fixtures/golden-install-parity/zcode.json +++ b/tests/fixtures/golden-install-parity/zcode.json @@ -31,8 +31,8 @@ "agents/gsd-roadmapper.md": "840ac933e3b094f9", "agents/gsd-security-auditor.md": "b7202c44366697dd", "agents/gsd-ui-auditor.md": "76f446c50edf81f8", - "agents/gsd-ui-checker.md": "2104071d60d917d0", - "agents/gsd-ui-researcher.md": "2a1dda374b1958aa", + "agents/gsd-ui-checker.md": "15949ccab982b71c", + "agents/gsd-ui-researcher.md": "0e8ec8509d476904", "agents/gsd-user-profiler.md": "ca3bf75581f211a0", "agents/gsd-verifier.md": "8282761ba471c022", "commands/gsd-add-tests.md": "51542808862d4aa7", @@ -209,6 +209,7 @@ "gsd-core/references/thinking-models-verification.md": "a71a933d51ca3d8d", "gsd-core/references/thinking-partner.md": "41069529ef776e39", "gsd-core/references/ui-brand.md": "48717bcfcd63bd27", + "gsd-core/references/ui-consideration-probe.md": "7e019dfaae47f4c4", "gsd-core/references/universal-anti-patterns.md": "6a1245050b21df01", "gsd-core/references/untrusted-input-boundary.md": "d33b80d4d348599a", "gsd-core/references/user-profiling.md": "b50416fe57c1b321", @@ -224,7 +225,7 @@ "gsd-core/templates/README.md": "93d3426fc64e2c12", "gsd-core/templates/SECURITY.md": "b628f7f1c6d2328f", "gsd-core/templates/UAT.md": "9e296471b97ebcec", - "gsd-core/templates/UI-SPEC.md": "20ca56a4e3e21f01", + "gsd-core/templates/UI-SPEC.md": "7dd5c7cdc7ece0ec", "gsd-core/templates/VALIDATION.md": "f53e0ca061d3528e", "gsd-core/templates/claude-md.md": "d1d333e4b963c0d2", "gsd-core/templates/codebase/architecture.md": "6be88214162fdd89", @@ -341,7 +342,7 @@ "gsd-core/workflows/onboard.md": "6f9e6c0b484271a9", "gsd-core/workflows/pause-work.md": "f2b33bba5593d422", "gsd-core/workflows/plan-milestone-gaps.md": "852f6d7c0c4299dc", - "gsd-core/workflows/plan-phase.md": "24a8ceaa8b032641", + "gsd-core/workflows/plan-phase.md": "3784a2a5e8f098cd", "gsd-core/workflows/plan-phase/steps/closed-phase-gate.md": "b36f77ac7344a072", "gsd-core/workflows/plan-phase/steps/prd-express-path.md": "197c0590326371b2", "gsd-core/workflows/plan-phase/steps/windows-troubleshooting.md": "49f58c3f75be3eb5", @@ -373,7 +374,7 @@ "gsd-core/workflows/sync-skills.md": "b505e6f8331c0918", "gsd-core/workflows/thread.md": "927e7eeefd2fcf5c", "gsd-core/workflows/transition.md": "cb8ec5affb7ebba1", - "gsd-core/workflows/ui-phase.md": "49fa69e892423486", + "gsd-core/workflows/ui-phase.md": "98d044ed4d7163d7", "gsd-core/workflows/ui-review.md": "7acfc485526d064b", "gsd-core/workflows/ultraplan-phase.md": "0fb8291153e3937d", "gsd-core/workflows/undo.md": "96d2775f008b3a85", diff --git a/tests/ui-consideration-probe-docs-fixtures.test.cjs b/tests/ui-consideration-probe-docs-fixtures.test.cjs new file mode 100644 index 000000000..c93414681 --- /dev/null +++ b/tests/ui-consideration-probe-docs-fixtures.test.cjs @@ -0,0 +1,68 @@ +// allow-test-rule: runtime-contract-is-the-product (see #1867) — the rendered reference doc's taxonomy table IS the runtime contract; this pins its bijection to the code (docs-parity, ADR-456 exception matrix) +// Asserts gsd-core/references/ui-consideration-probe.md keeps its taxonomy id column in +// sync with the source-of-truth UI_TAXONOMY (built .cjs), and that the closed compiled +// taxonomy stays DISJOINT from the open-prose domain-probes.md bank (the mixed-axis boundary, +// ADPT-02). The comparison is on PARSED table ids and PARSED `##` headings, never a raw +// full-text substring match — a reformat that preserves the data does not fail; semantic drift does. +'use strict'; +process.env.GSD_TEST_MODE = '1'; + +const { test, describe } = require('node:test'); +const assert = require('node:assert/strict'); +const fs = require('node:fs'); +const path = require('node:path'); + +const uc = require(path.join(__dirname, '..', 'gsd-core', 'bin', 'lib', 'ui-consideration-probe.cjs')); +const docPath = path.join(__dirname, '..', 'gsd-core', 'references', 'ui-consideration-probe.md'); +const domainPath = path.join(__dirname, '..', 'gsd-core', 'references', 'domain-probes.md'); +const templatePath = path.join(__dirname, '..', 'gsd-core', 'templates', 'UI-SPEC.md'); + +// Extract the first-column ids from the `## Taxonomy` markdown table (skips the `id` header +// row and the `|----|` separator; an id is a lowercase-hyphen token). +function docTaxonomyIds(md) { + const section = md.split(/^## Taxonomy/m)[1].split(/^## /m)[0]; + return section.split('\n') + .filter((l) => l.trim().startsWith('|')) + .map((l) => l.split('|')[1].trim()) + .filter((c) => /^[a-z][a-z0-9-]*$/.test(c) && c !== 'id'); +} + +// The `##` topic headings of the open-prose bank (lower-cased). +function domainTopics(md) { + return md.split('\n') + .filter((l) => /^## /.test(l)) + .map((l) => l.replace(/^## /, '').trim().toLowerCase()); +} + +describe('ui-consideration-probe doc/code parity (ADPT-02)', () => { + test('reference doc exists', () => { + assert.ok(fs.existsSync(docPath), `${docPath} must exist`); + }); + + test('doc taxonomy ids deep-equal the code UI_TAXONOMY ids, in order (doc == code)', () => { + const md = fs.readFileSync(docPath, 'utf8'); + assert.deepEqual(docTaxonomyIds(md), uc.UI_TAXONOMY.map((c) => c.id)); + }); + + test('no taxonomy id overlaps a domain-probes.md open-prose topic (closed/open disjointness)', () => { + const topics = domainTopics(fs.readFileSync(domainPath, 'utf8')); + for (const id of uc.UI_TAXONOMY.map((c) => c.id)) { + assert.ok(!topics.includes(id), `taxonomy id "${id}" must not overlap a domain-probes.md topic`); + } + }); + + test('the doc names domain-probes.md as the companion open-prose bank (links, does not duplicate)', () => { + const md = fs.readFileSync(docPath, 'utf8'); + assert.match(md, /domain-probes\.md/); + }); +}); + +describe('UI-SPEC template `## UI Considerations` section (WIRE-02 SC3 de-dup)', () => { + // PARSED `##` headings only (never a raw copy substring) — a reformat that preserves the data + // does not fail; a missing/merged section does. Reuses the domainTopics() heading parser. + test('template ## headings include BOTH `UI Considerations` and `Copywriting Contract` as distinct sections', () => { + const headings = domainTopics(fs.readFileSync(templatePath, 'utf8')); + assert.ok(headings.includes('ui considerations'), 'template must gain a ## UI Considerations section'); + assert.ok(headings.includes('copywriting contract'), 'template must retain the distinct ## Copywriting Contract section (de-dup, not a rename)'); + }); +}); diff --git a/tests/ui-consideration-probe.test.cjs b/tests/ui-consideration-probe.test.cjs new file mode 100644 index 000000000..fb15502f5 --- /dev/null +++ b/tests/ui-consideration-probe.test.cjs @@ -0,0 +1,306 @@ +/** + * UI-consideration-probe adapter unit tests (#1867). + * + * Asserts the LOCKED export surface of the THIRD probe-core adapter against the + * BUILT artifact (`gsd-core/bin/lib/ui-consideration-probe.cjs`), which + * `npm run build:lib` (run by pretest) emits from `src/ui-consideration-probe.cts`. + * + * The adapter mirrors `edge-probe` on the UI element/state axis: a closed 8-id + * shape-rooted `UI_TAXONOMY`, an element-kind relevance filter + * (`UI_CUES` → `classifyElement` → `applicableCategories`), the `unclassified` + * soft-signal (#1110), and the `{explicit, backstop}` verification validators — + * all lifecycle/merge/validation delegated to `probe-core` (ADPT-01/02/03, FILT-01). + * + * Structured-value assertions only (local/no-source-grep): every assertion is on a + * typed return of the built module, never on stdout or file-content substrings. + */ +'use strict'; +process.env.GSD_TEST_MODE = '1'; + +const { test, describe } = require('node:test'); +const assert = require('node:assert/strict'); +const path = require('node:path'); + +const BUILT_SCRIPT = path.join(__dirname, '..', 'gsd-core', 'bin', 'lib', 'ui-consideration-probe.cjs'); +const uc = require(BUILT_SCRIPT); +// The LIFT-01 primitives are probe-core's (there is no adapter-owned lift function — the lift is +// plan-phase workflow prose); LIFT-01 correctness is proven at the shared primitive level here. +const core = require(path.join(__dirname, '..', 'gsd-core', 'bin', 'lib', 'probe-core.cjs')); + +const TAXONOMY_IDS = ['empty', 'loading', 'error', 'populated', 'partial', 'overflow', 'zero-one-many', 'long-text']; + +describe('ui-consideration-probe: classifyElement (D-03/D-04 element-cue filter)', () => { + test('detects form from input/field/validation cues', () => { + assert.ok(uc.classifyElement('A signup form with input fields and validation').includes('form')); + }); + test('detects list-collection from table/rows cues', () => { + assert.ok(uc.classifyElement('A table listing all rows of results').includes('list-collection')); + }); + test('detects static-content from heading/paragraph/copy cues', () => { + assert.ok(uc.classifyElement('A heading and a paragraph of body copy').includes('static-content')); + }); + test('returns [] when no element cue matches (zero-cue prose)', () => { + assert.deepEqual(uc.classifyElement('xyzzy plugh frobnicate wibble'), []); + }); + test('null/undefined text is null-safe and returns []', () => { + assert.deepEqual(uc.classifyElement(null), []); + assert.deepEqual(uc.classifyElement(undefined), []); + }); +}); + +describe('ui-consideration-probe: UI_TAXONOMY + UI_VALIDATORS (ADPT-02/03, D-01/D-02/D-05)', () => { + test('UI_TAXONOMY has exactly the 8 shape-rooted ids in order', () => { + assert.deepEqual(uc.UI_TAXONOMY.map((c) => c.id), TAXONOMY_IDS); + }); + test('every taxonomy entry has name, elements[], and a string consideration', () => { + for (const c of uc.UI_TAXONOMY) { + assert.equal(typeof c.name, 'string'); + assert.ok(Array.isArray(c.elements) && c.elements.length >= 1); + assert.equal(typeof c.consideration, 'string'); + assert.ok(c.consideration.length > 0); + } + }); + test('UNCLASSIFIED_CATEGORY is the soft-signal, kept OUT of the taxonomy (#1110)', () => { + assert.equal(uc.UNCLASSIFIED_CATEGORY, 'unclassified'); + assert.ok(!uc.UI_TAXONOMY.map((c) => c.id).includes('unclassified')); + }); + test('UI_VALIDATORS.categories === the 8 ids plus unclassified; verification is the {explicit,backstop} tiers (singular key)', () => { + assert.deepEqual(uc.UI_VALIDATORS.categories, [...TAXONOMY_IDS, 'unclassified']); + // The probe-core Validators field is `verification` (SINGULAR) — CONTEXT.md D-05's `verifications` is a paraphrase typo. + assert.deepEqual(uc.UI_VALIDATORS.verification, ['explicit', 'backstop']); + assert.equal(uc.UI_VALIDATORS.verifications, undefined); + }); + test('VALID_ELEMENT_KINDS is derived from UI_CUES keys (single source of truth)', () => { + assert.deepEqual([...uc.VALID_ELEMENT_KINDS].sort(), Object.keys(uc.UI_CUES).sort()); + }); +}); + +describe('ui-consideration-probe: applicableCategories (FILT-01 relevance intersection, D-04)', () => { + test('static-content raises only overflow + long-text (no loading/error/empty — SPEC R2 hint)', () => { + assert.deepEqual(uc.applicableCategories(['static-content']).sort(), ['long-text', 'overflow']); + }); + test('list-collection raises the richest set (empty/loading/error/populated/partial/overflow/zero-one-many)', () => { + assert.deepEqual(uc.applicableCategories(['list-collection']).sort(), + ['empty', 'error', 'loading', 'overflow', 'partial', 'populated', 'zero-one-many']); + }); + test('no element kinds raises nothing', () => { + assert.deepEqual(uc.applicableCategories([]), []); + }); + test('result ids are a subset of the taxonomy ids', () => { + const all = uc.applicableCategories(['form', 'list-collection', 'nav', 'media', 'interactive-control', 'static-content']); + for (const id of all) assert.ok(TAXONOMY_IDS.includes(id)); + }); + test('every UIElementKind maps to >= 1 taxonomy category (a classified element never silently yields zero considerations)', () => { + for (const kind of Object.keys(uc.UI_CUES)) { + assert.ok(uc.applicableCategories([kind]).length >= 1, `element kind ${kind} must have >= 1 applicable category`); + } + }); +}); + +describe('ui-consideration-probe: proposeConsiderations (ADPT-01/FILT-01, #1110)', () => { + test('emits exactly one unresolved Item per applicable category, question carried in Item.probe', () => { + const element = { id: 'C1', text: 'A table listing all rows of results' }; + const items = uc.proposeConsiderations(element); + const expected = uc.applicableCategories(uc.classifyElement(element.text)); + assert.deepEqual(items.map((i) => i.category).sort(), [...expected].sort()); + for (const it of items) { + assert.equal(it.requirement_id, 'C1'); + assert.equal(it.status, 'unresolved'); + assert.equal(it.verification, null); + assert.equal(it.resolution, null); + assert.equal(it.reason, null); + assert.equal(typeof it.probe, 'string'); + assert.ok(it.probe.length > 0); + } + }); + test('zero-cue prose yields exactly ONE unclassified item, never a silent drop or minted category (#1110)', () => { + const items = uc.proposeConsiderations({ id: 'Z', text: 'xyzzy plugh frobnicate' }); + assert.equal(items.length, 1); + assert.equal(items[0].category, 'unclassified'); + assert.equal(items[0].status, 'unresolved'); + assert.equal(items[0].verification, null); + }); + test('an explicit `elements: []` opt-out is silent (no items, no unclassified)', () => { + assert.deepEqual(uc.proposeConsiderations({ id: 'O', text: 'anything', elements: [] }), []); + }); + test('an authored array with an invalid element kind throws (fail closed, never silently empty)', () => { + assert.throws(() => uc.proposeConsiderations({ id: 'B', text: 'x', elements: ['not-a-kind'] })); + }); + test('an authored valid element override bypasses prose classification', () => { + const items = uc.proposeConsiderations({ id: 'A', text: 'no cues here at all', elements: ['static-content'] }); + assert.deepEqual(items.map((i) => i.category).sort(), ['long-text', 'overflow']); + }); +}); + +describe('ui-consideration-probe: delegated validation (ADPT-03, D-06 — inherited from probe-core)', () => { + test('validateResolution rejects a dismissed resolution with an empty/blank reason', () => { + assert.throws(() => uc.validateResolution({ + requirement_id: 'C1', category: 'empty', status: 'dismissed', verification: null, resolution: null, reason: ' ', + })); + }); + test('analyzeCoverage rejects an orphan resolution (no matching proposed item)', () => { + const elements = [{ id: 'C1', text: 'A table listing all rows of results' }]; + const orphan = [{ + requirement_id: 'C1', category: 'nonexistent-category', status: 'resolved', + verification: 'explicit', resolution: 'x', reason: null, + }]; + assert.throws(() => uc.analyzeCoverage(elements, orphan)); + }); + test('analyzeCoverage delegates a clean merge to probe-core and reports coverage', () => { + const elements = [{ id: 'C1', text: 'A table listing all rows of results' }]; + const report = uc.analyzeCoverage(elements, []); + assert.ok(report && report.coverage && typeof report.coverage.applicable === 'number'); + assert.ok(Array.isArray(report.items) && report.items.length >= 1); + }); +}); + +// ── LIFT-01 (proven at the shared probe-core primitive level on UI-SPEC-shaped input) ────────── +// A resolved `## UI Considerations` section after resolution: a `covered` (inferable) +// consideration → a plain-string truth; a `backstop` (non-inferable, purely-visual) consideration +// → carries verification: 'backstop'. Measures DISPOSITION, not entry-count (SPEC R7, Goodhart). +const COVERED = 'Empty state for the results table renders the documented "No results" copy.'; +const BACKSTOP = { statement: 'Overflowing long labels truncate with an ellipsis without shifting layout.', verification: 'backstop' }; +const COVERED_2 = 'Loading state shows a skeleton for the results table.'; + +describe('ui-consideration-probe LIFT-01: projectTruths (covered→string, backstop→flat scalar, D-07)', () => { + test('covered consideration projects to a bare string; backstop projects to {statement, verification:backstop}', () => { + const out = core.projectTruths([COVERED, BACKSTOP]); + assert.equal(out[0], COVERED); + assert.deepEqual(out[1], { statement: BACKSTOP.statement, verification: 'backstop' }); + }); + test('projection preserves input order (deterministic lift over taxonomy id order — ordering/stability edge)', () => { + const out = core.projectTruths([COVERED, COVERED_2, BACKSTOP]); + assert.equal(out[0], COVERED); + assert.equal(out[1], COVERED_2); + assert.deepEqual(out[2], { statement: BACKSTOP.statement, verification: 'backstop' }); + }); + test('no covered/backstop consideration is silently dropped', () => { + const input = [COVERED, COVERED_2, BACKSTOP]; + assert.equal(core.projectTruths(input).length, input.length); + }); +}); + +describe('ui-consideration-probe LIFT-01: verify-time disposition (never silent pass, D-09)', () => { + test('a no-evidence backstop consideration routes to insufficient_spec — NEVER a silent green', () => { + const d = core.dispositionForUnverifiableTruth(BACKSTOP, { evidence: [] }); + assert.equal(d.status, 'unverified'); + assert.equal(d.flagged, true); + assert.equal(d.tier, 'backstop'); + assert.equal(d.reason, core.INSUFFICIENT_SPEC); + assert.equal(core.INSUFFICIENT_SPEC, 'insufficient_spec'); + assert.notEqual(d.status, 'green'); + }); + test('a backstop consideration WITH explicit evidence (a passing wired test) disposes green', () => { + const d = core.dispositionForUnverifiableTruth(BACKSTOP, { evidence: [{ kind: 'wired-test', passed: true }] }); + assert.equal(d.status, 'green'); + assert.equal(d.flagged, false); + }); + test('a covered (inferable) consideration disposes green even with no evidence (over-abstention guard)', () => { + const d = core.dispositionForUnverifiableTruth(COVERED, { evidence: [] }); + assert.equal(d.status, 'green'); + assert.equal(d.flagged, false); + }); +}); + +// ══ WIRE-01 (Phase 2, #1867) — the live ui-phase producer surface ════════════════════════════ +// Two new adapter functions the ui-phase Step 9.5 probe consumes: proposeElements (the +// propose-then-confirm view exposing detected kinds + applicable categories per element) and +// autoResolve (the deterministic `--auto` resolution FLOOR that never dismisses and never +// auto-backstops an unclassified item, #1110). Structured-value assertions only. +const LIST_ELEMENT = { id: 'C1', text: 'A table listing all rows of results' }; +const ZERO_CUE_ELEMENT = { id: 'Z', text: 'xyzzy plugh frobnicate' }; +// A surface that is genuinely BOTH a form and a list, but whose prose trips only the form cue — +// the partial-cue recall gap the confirm step exists to close. +const PARTIAL_CUE_ELEMENT = { id: 'P', text: 'A signup form with input fields and validation' }; + +describe('ui-consideration-probe: proposeElements (WIRE-01 confirm surface, SC1)', () => { + test('a classified element returns one ElementProposal with kinds, applicable categories, considerations, unclassified:false', () => { + const [p] = uc.proposeElements([LIST_ELEMENT]); + assert.equal(p.id, 'C1'); + assert.ok(p.kinds.includes('list-collection')); + assert.deepEqual([...p.categories].sort(), [...uc.applicableCategories(uc.classifyElement(LIST_ELEMENT.text))].sort()); + assert.deepEqual(p.considerations.map((c) => c.category).sort(), [...p.categories].sort()); + assert.equal(p.unclassified, false); + }); + test('a zero-cue element returns kinds:[], categories:[], unclassified:true, and exactly one unclassified consideration (#1110)', () => { + const [p] = uc.proposeElements([ZERO_CUE_ELEMENT]); + assert.deepEqual(p.kinds, []); + assert.deepEqual(p.categories, []); + assert.equal(p.unclassified, true); + assert.equal(p.considerations.length, 1); + assert.equal(p.considerations[0].category, uc.UNCLASSIFIED_CATEGORY); + }); + test('proposeElements is deterministic — two calls on the same element array deepEqual (idempotency substrate for WIRE-02)', () => { + assert.deepEqual(uc.proposeElements([LIST_ELEMENT, ZERO_CUE_ELEMENT]), uc.proposeElements([LIST_ELEMENT, ZERO_CUE_ELEMENT])); + }); + test('an authored elements[] override bypasses prose classification and drives the categories', () => { + const [p] = uc.proposeElements([{ id: 'A', text: 'no cues here at all', elements: ['static-content'] }]); + assert.deepEqual([...p.kinds].sort(), ['static-content']); + assert.deepEqual([...p.categories].sort(), ['long-text', 'overflow']); + assert.equal(p.unclassified, false); + }); +}); + +describe('ui-consideration-probe: autoResolve (WIRE-01 typed --auto never-dismiss, SC2, #1110)', () => { + test('every applicable consideration auto-resolves to a backstop with a non-empty resolution; NONE is dismissed', () => { + const items = uc.proposeConsiderations(LIST_ELEMENT); + const resolutions = uc.autoResolve(items); + assert.equal(resolutions.length, items.length); + for (const r of resolutions) { + assert.notEqual(r.status, 'dismissed'); + assert.equal(r.status, 'resolved'); + assert.equal(r.verification, 'backstop'); + assert.equal(typeof r.resolution, 'string'); + assert.ok(r.resolution.length > 0); + } + }); + test('an unclassified item stays unresolved — never auto-backstopped (a missing cue is not evidence, #1110)', () => { + const items = uc.proposeConsiderations(ZERO_CUE_ELEMENT); // one unclassified item + const [r] = uc.autoResolve(items); + assert.equal(r.status, 'unresolved'); + assert.equal(r.verification, null); + assert.equal(r.resolution, null); + assert.equal(r.reason, null); + }); + test('autoResolve output validates and merges through probe-core: zero dismissed, byVerification.backstop === applicable', () => { + const items = uc.proposeConsiderations(LIST_ELEMENT); + const report = uc.analyzeCoverage([LIST_ELEMENT], uc.autoResolve(items)); + assert.ok(report.items.every((it) => it.status !== 'dismissed')); + assert.equal(report.coverage.resolved, report.coverage.applicable); + assert.equal(report.coverage.byVerification.backstop, report.coverage.applicable); + }); +}); + +describe('ui-consideration-probe: partial-cue recall gap (confirm is load-bearing, not the heuristic — Goodhart)', () => { + test('prose that trips only the form cue under-covers: heuristic categories are a STRICT SUBSET of the confirmed form+list union', () => { + const [heuristic] = uc.proposeElements([PARTIAL_CUE_ELEMENT]); + const [confirmed] = uc.proposeElements([{ ...PARTIAL_CUE_ELEMENT, elements: ['form', 'list-collection'] }]); + assert.deepEqual(heuristic.kinds, ['form']); // prose only tripped 'form' + const hSet = new Set(heuristic.categories); + const cSet = new Set(confirmed.categories); + for (const cat of hSet) assert.ok(cSet.has(cat), `heuristic category ${cat} must be in the confirmed union`); + assert.ok(cSet.size > hSet.size, 'the confirmed union must strictly exceed the heuristic set — proving the confirm step recovers missed coverage'); + }); +}); + +// ══ WIRE-02 (Phase 2, #1867) — the UI-SPEC section round-trips the shipped lift, backward-compat, +// idempotency. Typed returns only (this file carries no allow-test-rule header). The `## UI +// Considerations` section format is LOCKED by the shipped plan-phase `## UI Considerations` lift rule + +// probe-core `projectTruths`; these guards pin that the template documents the SAME format. ═════ +describe('ui-consideration-probe WIRE-02: backward-compat + format-match + idempotency (SC4)', () => { + test('projectTruths(undefined) and projectTruths([]) both === [] — an old UI-SPEC with no section lifts nothing, never throws (Hyrum SC4)', () => { + assert.deepEqual(core.projectTruths(undefined), []); + assert.deepEqual(core.projectTruths([]), []); + }); + test('a mixed covered/backstop/unresolved considerations array projects to the exact plan-phase-lift shape, order preserved (format-match SC3)', () => { + const input = ['Empty state renders the documented "No results" copy.', { statement: 'Overflowing long labels truncate with an ellipsis.', verification: 'backstop' }, 'Loading shows a skeleton for the results table.']; + const out = core.projectTruths(input); + assert.equal(out[0], input[0]); // covered → bare string + assert.deepEqual(out[1], { statement: input[1].statement, verification: 'backstop' }); // backstop → flat scalar + assert.equal(out[2], input[2]); // order preserved + }); + test('proposeElements is deterministic — re-running the probe rewrites byte-stable rows, never duplicated (idempotency SC4)', () => { + const els = [{ id: 'C1', text: 'A table listing all rows of results' }, { id: 'Z', text: 'xyzzy plugh' }]; + assert.deepEqual(uc.proposeElements(els), uc.proposeElements(els)); + }); +}); diff --git a/tests/workflow-size-baseline.json b/tests/workflow-size-baseline.json index b85ca8b25..249eb5649 100644 --- a/tests/workflow-size-baseline.json +++ b/tests/workflow-size-baseline.json @@ -53,7 +53,7 @@ "onboard.md": 8590, "pause-work.md": 14441, "plan-milestone-gaps.md": 11809, - "plan-phase.md": 92251, + "plan-phase.md": 93113, "plan-review-convergence.md": 23512, "plant-seed.md": 11785, "pr-branch.md": 15963, @@ -82,7 +82,7 @@ "sync-skills.md": 6125, "thread.md": 12508, "transition.md": 22060, - "ui-phase.md": 15521, + "ui-phase.md": 26054, "ui-review.md": 11216, "ultraplan-phase.md": 10512, "undo.md": 10431,