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:
5
.changeset/vivid-orcas-chatter.md
Normal file
5
.changeset/vivid-orcas-chatter.md
Normal 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
1
.gitignore
vendored
@@ -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
|
||||
|
||||
@@ -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)`
|
||||
|
||||
@@ -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:
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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` |
|
||||
|
||||
@@ -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',
|
||||
|
||||
73
gsd-core/references/ui-consideration-probe.md
Normal file
73
gsd-core/references/ui-consideration-probe.md
Normal 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.
|
||||
@@ -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 |
|
||||
|
||||
@@ -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>
|
||||
```
|
||||
|
||||
@@ -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:
|
||||
|
||||
315
src/ui-consideration-probe.cts
Normal file
315
src/ui-consideration-probe.cts
Normal 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]' },
|
||||
);
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
11
tests/fixtures/golden-install-parity/claude.json
vendored
11
tests/fixtures/golden-install-parity/claude.json
vendored
@@ -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",
|
||||
|
||||
11
tests/fixtures/golden-install-parity/cline.json
vendored
11
tests/fixtures/golden-install-parity/cline.json
vendored
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
15
tests/fixtures/golden-install-parity/codex.json
vendored
15
tests/fixtures/golden-install-parity/codex.json
vendored
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
11
tests/fixtures/golden-install-parity/cursor.json
vendored
11
tests/fixtures/golden-install-parity/cursor.json
vendored
@@ -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",
|
||||
|
||||
11
tests/fixtures/golden-install-parity/hermes.json
vendored
11
tests/fixtures/golden-install-parity/hermes.json
vendored
@@ -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",
|
||||
|
||||
11
tests/fixtures/golden-install-parity/kilo.json
vendored
11
tests/fixtures/golden-install-parity/kilo.json
vendored
@@ -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",
|
||||
|
||||
11
tests/fixtures/golden-install-parity/kimi.json
vendored
11
tests/fixtures/golden-install-parity/kimi.json
vendored
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
11
tests/fixtures/golden-install-parity/qwen.json
vendored
11
tests/fixtures/golden-install-parity/qwen.json
vendored
@@ -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",
|
||||
|
||||
11
tests/fixtures/golden-install-parity/trae.json
vendored
11
tests/fixtures/golden-install-parity/trae.json
vendored
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
11
tests/fixtures/golden-install-parity/zcode.json
vendored
11
tests/fixtures/golden-install-parity/zcode.json
vendored
@@ -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",
|
||||
|
||||
68
tests/ui-consideration-probe-docs-fixtures.test.cjs
Normal file
68
tests/ui-consideration-probe-docs-fixtures.test.cjs
Normal 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)');
|
||||
});
|
||||
});
|
||||
306
tests/ui-consideration-probe.test.cjs
Normal file
306
tests/ui-consideration-probe.test.cjs
Normal 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));
|
||||
});
|
||||
});
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user