Merge pull request #1979 from davesienkowski/feat/1867-ui-consideration-probe

feat(#1867): ui-consideration probe — 3rd probe-core adapter for UI state coverage
This commit is contained in:
Tom Boucher
2026-07-10 15:04:11 -04:00
committed by GitHub
34 changed files with 1065 additions and 93 deletions

View File

@@ -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.

1
.gitignore vendored
View File

@@ -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

View File

@@ -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{<probe-defined>|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)`

View File

@@ -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.
</objective_persona>
@~/.claude/gsd-core/references/ui-consideration-probe.md
<project_context>
Before verifying, discover project context:

View File

@@ -28,6 +28,7 @@ If the prompt contains a `<required_reading>` block, you MUST use the `Read` too
</role>
@~/.claude/gsd-core/references/untrusted-input-boundary.md
@~/.claude/gsd-core/references/ui-consideration-probe.md
<documentation_lookup>
@~/.claude/gsd-core/references/research-documentation-lookup.md

View File

@@ -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",

View File

@@ -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 <point> --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` |

View File

@@ -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',

View File

@@ -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.

View File

@@ -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} |
<!-- Status vocabulary (locked by probe-core projectTruths):
✅ covered → a plain truth string lifted into must_haves.truths
🧪 backstop → a flat scalar { statement, verification: backstop }; at verify time, no explicit
evidence → insufficient_spec → human_needed (never a silent pass, #1154)
⚠ unresolved → an explicit planner assumption (surfaced, never silently dropped)
Rows are REPLACED (not appended) on a probe re-run — idempotent. -->
---
## Registry Safety
| Registry | Blocks Used | Safety Gate |

View File

@@ -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: <the check>, 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.
</downstream_consumer>
@@ -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)
</quality_gate>
```

View File

@@ -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 <replace:> 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": "<replace: element/surface description from the UI-SPEC prose>" }
]
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("<replace:")))process.exit(1)' "$ELEMENTS_JSON" 2>/dev/null; then
rm -f "$ELEMENTS_JSON"
echo "ERROR: ui-probe elements JSON is empty/invalid or still holds the <replace: …> 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:

View File

@@ -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<UIVerification>;
/**
* 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<UIElementKind, RegExp> = {
'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<string> = 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<UIElementKind>(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<UIVerification>): 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<UIVerification>[] = [],
): CoverageReport<UIVerification> {
if (!Array.isArray(elements)) {
throw new Error('elements must be an array');
}
const items: UIConsideration[] = [];
const seenIds = new Set<string>();
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<UIVerification>[] {
return items.map((item): Resolution<UIVerification> => {
if (item.category === UNCLASSIFIED_CATEGORY) {
return { requirement_id: item.requirement_id, category: item.category, status: 'unresolved', verification: null, resolution: null, reason: null };
}
return { requirement_id: item.requirement_id, category: item.category, status: 'resolved', verification: 'backstop', resolution: item.probe, reason: null };
});
}
/*
* CLI entry (invokable surface): `ui-consideration-probe.cjs <elements.json> [resolutions.json]`.
* The generic I/O plumbing (parse, fail-closed exit 2, pretty-JSON out) lives in probe-core's
* `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<UIVerification>[]),
{ usage: 'ui-consideration-probe.cjs <elements.json> [resolutions.json]' },
);
}

View File

@@ -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
}

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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)');
});
});

View File

@@ -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));
});
});

View File

@@ -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,