diff --git a/README.md b/README.md index a11183bc2c..274c07bd8c 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,7 @@ specify init my-project --integration codex --ignore-agent-tools | 自动扩展 | `agent-context` | `extensions/agent-context` | 维护 AGENTS、CLAUDE、Copilot 等 agent context 文件里的 Spec Kit 受管段。 | | 默认扩展 | `arch` | `extensions/arch` | 生成或反向生成项目级 4+1 架构视图,形成架构 SSOT。 | | 默认扩展 | `discovery` | `extensions/discovery` | 在正式计划前做可行性、技术选型、旧代码评估、接口理解、PoC 和场景化技术决策。 | -| 默认扩展 | `intake` | `extensions/intake` | 把 PRD、设计稿、Figma、HTML SSOT、结构化 IR、测试用例等来源归一化为 SDD 可消费的证据包。 | +| 默认扩展 | `intake` | `extensions/intake` | 把 PRD、设计稿、Figma、视觉规格资产包、preview 覆盖证据、测试用例等来源归一化为 SDD 可消费的证据包。 | | 默认扩展 | `preview` | `extensions/preview` | 从规格和计划生成低、中、高保真 Markdown 或自包含 HTML 预览。 | | 默认扩展 | `repository-governance` | `extensions/repository-governance` | 生成仓库治理 SSOT,帮助 agent 明确目录责任、读取顺序和事实证据。 | | 默认预设 | `workflow-preset` | `presets/workflow-preset` | 强化 BDD、NFR、UIF、设计产物、任务验证策略和 implement handoff 编排。 | @@ -147,8 +147,6 @@ specify init my-project --integration codex --ignore-agent-tools ```text /speckit.intake.prd /speckit.intake.visual-design -/speckit.intake.figma2htmlssot -/speckit.intake.ir /speckit.intake.test-cases ``` @@ -156,7 +154,7 @@ specify init my-project --integration codex --ignore-agent-tools - PRD、产品说明、Markdown、PDF、导出的文档。 - 图片、线框图、设计 PDF、Figma 文件、Figma 页面或节点。 -- Figma 派生的 HTML visual SSOT 和结构化 UI acceptance IR。 +- 视觉规格结构化资产包,以及 Figma 派生的组件矩阵 preview 覆盖证据。 - 既有测试、Gherkin、手工测试用例、QA 导出、测试管理表格。 主要产物: @@ -164,8 +162,8 @@ specify init my-project --integration codex --ignore-agent-tools ```text specs//intake/prd/ specs//intake/visual-design/ -specs//intake/visual-design/figma2htmlssot/ -specs//intake/visual-design/structured-ir/ +specs//intake/visual-design/visual-spec-package/ +specs//intake/visual-design/previews/ specs//intake/test-cases/ ``` @@ -484,8 +482,6 @@ specify bundle build --path ./my-bundle ```text /speckit.intake.prd /speckit.intake.visual-design -/speckit.intake.figma2htmlssot -/speckit.intake.ir /speckit.intake.test-cases /speckit.specify /speckit.clarify @@ -496,8 +492,6 @@ specify bundle build --path ./my-bundle ```text /speckit.intake.visual-design -/speckit.intake.figma2htmlssot -/speckit.intake.ir /speckit.specify /speckit.preview.low-md /speckit.plan diff --git a/docs/community/extensions.md b/docs/community/extensions.md index 15d68c9c57..5ef6d67f4c 100644 --- a/docs/community/extensions.md +++ b/docs/community/extensions.md @@ -57,7 +57,7 @@ The following community-contributed extensions are available in [`catalog.commun | GitHub Issues Integration 2 | Creates and syncs local specs from an existing GitHub issue | `integration` | Read+Write | [spec-kit-issue](https://github.com/aaronrsun/spec-kit-issue) | | Golden Demo | Extracts acceptance criteria from specs, builds test vectors, and produces a behavioral drift report — complementary to Architecture Guard and CDD | `docs` | Read+Write | [spec-kit-golden-demo](https://github.com/jasstt/spec-kit-golden-demo) | | Improve Extension | Audits any codebase as a senior advisor and writes prioritized, self-contained spec prompts under specs/ that the spec-kit lifecycle can process | `process` | Read+Write | [spec-kit-improve](https://github.com/d0whc3r/spec-kit-improve) | -| Intake | Normalize PRD, design, HTML SSOT, structured IR, and test-case evidence into SDD-ready intake artifacts | `docs` | Read+Write | [spec-kit-intake](https://github.com/bigsmartben/spec-kit-intake) | +| Intake | Normalize PRD, visual design/spec packages, preview evidence, and test-case evidence into SDD-ready intake artifacts | `docs` | Read+Write | [spec-kit-intake](https://github.com/bigsmartben/spec-kit-intake) | | Intelligent Agent Orchestrator | Cross-catalog agent discovery and intelligent prompt-to-command routing | `process` | Read+Write | [spec-kit-orchestrator](https://github.com/pragya247/spec-kit-orchestrator) | | Iterate | Iterate on spec documents with a two-phase define-and-apply workflow — refine specs mid-implementation and go straight back to building | `docs` | Read+Write | [spec-kit-iterate](https://github.com/imviancagrace/spec-kit-iterate) | | Jira Integration | Create Jira Epics, Stories, and Issues from spec-kit specifications and task breakdowns with configurable hierarchy and custom field support | `integration` | Read+Write | [spec-kit-jira](https://github.com/mbachorik/spec-kit-jira) | diff --git a/extensions/catalog.community.json b/extensions/catalog.community.json index 25625f07d5..5a10be2d46 100644 --- a/extensions/catalog.community.json +++ b/extensions/catalog.community.json @@ -1404,7 +1404,7 @@ "intake": { "name": "Intake", "id": "intake", - "description": "Normalize PRD, design, HTML SSOT, structured IR, and test-case evidence into SDD-ready intake artifacts", + "description": "Normalize PRD, visual design/spec packages, preview evidence, and test-case evidence into SDD-ready intake artifacts", "author": "bigsmartben", "version": "0.1.4", "download_url": "https://github.com/bigsmartben/spec-kit-intake/archive/refs/tags/v0.1.4.zip", diff --git a/extensions/catalog.json b/extensions/catalog.json index 7f2cbc0e92..390e25dcf7 100644 --- a/extensions/catalog.json +++ b/extensions/catalog.json @@ -73,7 +73,7 @@ "name": "Intake", "id": "intake", "version": "0.1.4", - "description": "Normalize PRD, design, HTML SSOT, structured IR, and test-case evidence into SDD-ready intake artifacts", + "description": "Normalize PRD, visual design/spec packages, preview evidence, and test-case evidence into SDD-ready intake artifacts", "author": "bigsmartben", "repository": "https://github.com/bigsmartben/spec-kit-intake", "license": "MIT", diff --git a/extensions/intake/CHANGELOG.md b/extensions/intake/CHANGELOG.md index dd830f0601..4b7210d545 100644 --- a/extensions/intake/CHANGELOG.md +++ b/extensions/intake/CHANGELOG.md @@ -1,4 +1,4 @@ -# Changelog +# Changelog ## Unreleased @@ -6,15 +6,15 @@ ### Added -- Added `/speckit.intake.ir` for structured UI acceptance IR with CI-friendly DOM, ARIA, token, state, locator, relation, and assertion evidence. -- Added structured IR schemas and `validate_structured_ir_intake.py` for source readiness, cross-reference, provider-evidence, product-ambiguity, locator, downstream-ownership, and CI assertion checks. +- Added visual-design visual spec package artifacts for CI-friendly DOM, ARIA, token, state, locator, relation, and assertion evidence. +- Added visual spec package schemas and `validate_visual_spec_package.py` for source readiness, cross-reference, provider-evidence, product-ambiguity, locator, downstream-ownership, and CI assertion checks. +- Added `visual-design/previews/` with `component-matrix-preview.html` for human review and `component-coverage.yaml` / `viewport-coverage.yaml` for machine-checkable coverage evidence under `/speckit.intake.visual-design`. ## [0.1.3] - 2026-06-29 ### Added -- Added `/speckit.intake.figma2htmlssot` for Figma-derived HTML visual SSOT bundles with component-state, page, asset, viewport, screenshot, and coverage evidence. -- Added HTML SSOT schemas and `validate_html_ssot.py` for bundle artifact, Figma node, component-state, page, asset, viewport, visual-diff, and known-gap readiness checks. +- Added Figma-derived visual preview coverage evidence with component-state, page, asset, viewport, screenshot, and known-gap readiness checks. - Added bounded visual inference statuses for irregular Figma and visual-design sources, including `candidate` and `unsupported` claim handling. - Added readiness blocking for unbounded visual inference and unsupported visual claims. diff --git a/extensions/intake/README.md b/extensions/intake/README.md index d601b44712..428005b188 100644 --- a/extensions/intake/README.md +++ b/extensions/intake/README.md @@ -1,6 +1,6 @@ -# Spec Kit Intake Extension +# Spec Kit Intake Extension -Extract, normalize, and validate SDD-ready intake artifacts from PRDs, visual designs, Figma-derived HTML visual SSOT bundles, structured UI acceptance IR, test cases, and other software sources before downstream Spec Kit workflows project them into requirements. +Extract, normalize, and validate SDD-ready intake artifacts from PRDs, visual designs, visual requirements/spec structured asset packages, optional preview evidence, test cases, and other software sources before downstream Spec Kit workflows project them into requirements. The first goal of intake is not to generate requirements. It is to preserve as much input information as possible and turn it into structured material that SDD `specify` can consume accurately. @@ -15,8 +15,8 @@ Intake artifacts are validated in two layers: JSON Schema checks enforce the req - Static images such as PNG, JPG, WebP, and exported screens - PDF design packs and annotated review documents - Figma files, pages, frames, nodes, components, variables, and exported screenshots -- Figma-derived HTML visual SSOT bundles with traceable component-state and page coverage -- Structured UI acceptance IR with CI-friendly DOM, ARIA, token, state, locator, relation, and assertion facts +- Optional Figma-derived component matrix preview and coverage review helpers with traceable component, state, variant, viewport, and screenshot coverage +- Visual requirements/spec structured asset packages with CI-friendly DOM, ARIA, token, state, locator, relation, and assertion facts - Existing test cases, Gherkin files, QA exports, and test management spreadsheets ## Intake Scenario Coverage @@ -26,18 +26,15 @@ Intake commands are organized by vertical source domain. Each domain uses the sa | Domain | Vertical scenarios | Normalized artifact | Primary readiness focus | | --- | --- | --- | --- | | PRD | product briefs, Markdown PRDs, exported docs, PDFs, issue or epic links, mixed stakeholder notes | `prd-intake.yaml` | source identity, product intent traceability, scope boundaries, acceptance evidence, clarification gaps | -| Visual design | static images, wireframes, PDF design packs, Markdown design briefs, Figma files or selected nodes | `visual-requirements.yaml` | source integrity, fidelity rules, visual requirement traceability, parity planning, Figma metadata completeness when relevant | -| Figma to HTML SSOT | Figma files or selected nodes projected into runnable HTML visual acceptance surfaces | `visual-spec.html` | Figma node coverage, component-state coverage, page coverage, asset traceability, viewport screenshots, known gaps | -| Structured UI acceptance IR | visual design evidence and optional HTML SSOT enhancement refs projected into deterministic UI acceptance facts | `structured-ir.yaml` and `ir-assertions.yaml` | source traceability, provider/product gap separation, provider-neutral locators, DOM/ARIA/token/state/relation assertions, CI-low-cost readiness | +| Visual design | static images, wireframes, PDF design packs, Markdown design briefs, Figma files or selected nodes | `visual-requirements.yaml` and `visual-spec-package/` | source integrity, Figma-backed resource traceability, fidelity rules, visual requirement traceability, structured visual spec readiness, CI-low-cost assertion readiness | +| Figma preview helper | Figma files or selected nodes projected into component matrix review surfaces | `previews/component-matrix-preview.html` plus coverage YAML | Figma component coverage, component-state coverage, variant coverage, viewport screenshots, known gaps | | Test cases | automated tests, Gherkin files, manual QA cases, spreadsheets, test management exports, bug or issue repro steps | `test-case-intake.yaml` | scenario traceability, assertion extraction, fixture evidence, coverage gaps, flaky or skipped case reporting | Vertical instructions should never convert source evidence directly into downstream-owned requirement IDs, implementation tasks, or code component names. They produce provider-neutral intake facts that downstream workflows can consume with source refs intact. ## Commands -- `/speckit.intake.visual-design` captures or validates visual design evidence, source manifests, Figma metadata when available, inventories, and readiness for the active feature. -- `/speckit.intake.figma2htmlssot` creates or validates a Figma-derived HTML visual SSOT bundle with node, component-state, page, asset, viewport, and screenshot coverage. -- `/speckit.intake.ir` creates or validates structured UI acceptance IR for CI-friendly DOM, ARIA, token, state, locator, relation, and assertion checks. +- `/speckit.intake.visual-design` captures or validates visual design evidence, Figma-backed resources, visual requirements, preview coverage evidence, and the visual spec package for the active feature. - `/speckit.intake.prd` captures or validates PRD evidence and normalizes product intent, scope, business rules, acceptance criteria, and clarification items. - `/speckit.intake.test-cases` captures or validates test case evidence and normalizes scenarios, assertions, fixtures, and coverage gaps. @@ -58,17 +55,16 @@ specs//intake/ │ ├── figma-metadata.index.yaml │ ├── figma-node-inventory.yaml │ ├── visual-evidence-packet.md -│ └── figma2htmlssot/ -│ ├── visual-spec.html -│ ├── figma-map.json -│ ├── assets-manifest.json -│ ├── coverage-report.md -│ ├── known-gaps.md -│ └── screenshots/ -│ └── structured-ir/ -│ ├── structured-ir.yaml -│ ├── ir-assertions.yaml -│ └── ir-evidence-packet.md +│ ├── previews/ +│ │ ├── component-matrix-preview.html +│ │ ├── component-coverage.yaml +│ │ ├── viewport-coverage.yaml +│ │ ├── known-gaps.md +│ │ └── screenshots/ +│ └── visual-spec-package/ +│ ├── visual-spec.yaml +│ ├── visual-spec-assertions.yaml +│ └── visual-spec-evidence-packet.md └── test-cases/ ├── source-manifest.yaml ├── source-files/ @@ -78,11 +74,11 @@ specs//intake/ Figma metadata artifacts are required for Figma visual-design sources. Image, PDF, and Markdown visual-design sources use `design-source-manifest.yaml`, source-file checksums, extracted visual requirements, and visual parity evidence instead. PRD and test-case domains use their own source manifests and normalized intake files. -Machine-readable JSON Schemas live under `templates/schemas/` and are used by the validators before readiness rules run. HTML SSOT bundles use `figma-map.schema.json`, `assets-manifest.schema.json`, and `html-ssot-coverage.schema.json`. Structured IR uses `structured-ir.schema.json` and `ir-assertions.schema.json`. +Machine-readable JSON Schemas live under `templates/schemas/` and are used by the validators before readiness rules run. Preview helpers are defined by `templates/intake-visual-previews-contract.md` and use `component-coverage.schema.json` and `viewport-coverage.schema.json`. Visual spec packages use `visual-spec-package.schema.json` and `visual-spec-assertions.schema.json`. All intake commands provide capture instructions, evidence contracts, and readiness validation. Visual design validation additionally checks visual fidelity and Figma metadata parity. -HTML SSOT validation is owned by `scripts/python/validate_html_ssot.py`, including cross-file checks for selectors, assets, screenshots, coverage, and known gaps. -Structured IR validation is owned by `scripts/python/validate_structured_ir_intake.py`, including source readiness, schema, cross-reference, locator, downstream-ownership, provider-evidence, product-ambiguity, and CI assertion checks. +Component matrix preview validation is owned by `scripts/python/validate_visual_previews.py`, including cross-file checks for preview refs, visual spec refs, screenshots, component coverage, viewport coverage, and known gaps. +Visual spec package validation is owned by `scripts/python/validate_visual_spec_package.py`, including source readiness, schema, cross-reference, locator, downstream-ownership, provider-evidence, product-ambiguity, design-source resource traceability, and CI assertion checks. ## Requirements @@ -119,10 +115,10 @@ Then run: ```text /speckit.intake.visual-design capture /speckit.intake.visual-design validate -/speckit.intake.figma2htmlssot build -/speckit.intake.figma2htmlssot validate -/speckit.intake.ir build -/speckit.intake.ir validate +/speckit.intake.visual-design build-spec-package +/speckit.intake.visual-design validate-spec-package +/speckit.intake.visual-design build-previews +/speckit.intake.visual-design validate-previews /speckit.intake.prd capture /speckit.intake.prd validate /speckit.intake.test-cases capture @@ -152,33 +148,35 @@ Visual design claims use these evidence types: For irregular Figma sources, intake may generate bounded candidate completions, but it must not infer business rules, permissions, form validation, error copy, loading or disabled states, data sources, analytics, security, or compliance behavior from visual appearance alone. -## Figma to HTML SSOT Readiness Gate +## Figma Preview Helper Readiness Gate -Figma-derived HTML SSOT passes only when: +Figma-derived component matrix preview evidence passes only when: - upstream Figma visual-design intake is ready -- every required Figma node is mapped to HTML or recorded as an accepted exclusion +- every required component set, component instance, variant prop, state, size, density, theme, content sample, resource, and viewport is covered or recorded as a blocking missing record - the minimum acceptance grain is covered: component instance, state, content sample, container constraint, and viewport -- required sections and pages have runnable HTML selectors and screenshots -- all referenced assets are recorded in `assets-manifest.json` +- required preview cells link back to Figma refs, `visual-spec.yaml` items, `component-coverage.yaml` records, and screenshot or diff evidence when available +- component coverage is expressed in `previews/component-coverage.yaml` +- viewport coverage is expressed in `previews/viewport-coverage.yaml` - screenshot coverage and visual-diff status are recorded - known gaps are explicit and no blocking gap remains unresolved -The HTML SSOT validator emits blocker codes such as `HTML_SSOT_SOURCE_INTAKE_BLOCKED`, `HTML_SSOT_REQUIRED_ARTIFACT_MISSING`, `HTML_SSOT_SCHEMA_INVALID`, `HTML_SSOT_FIGMA_NODE_COVERAGE_INCOMPLETE`, `HTML_SSOT_COMPONENT_STATE_COVERAGE_INCOMPLETE`, `HTML_SSOT_PAGE_COVERAGE_INCOMPLETE`, `HTML_SSOT_ASSET_TRACEABILITY_INCOMPLETE`, `HTML_SSOT_VIEWPORT_CAPTURE_INCOMPLETE`, `HTML_SSOT_VISUAL_DIFF_BLOCKED`, and `HTML_SSOT_KNOWN_GAP_UNRESOLVED`. +The preview validator emits blocker codes such as `VISUAL_PREVIEW_SOURCE_INTAKE_BLOCKED`, `VISUAL_PREVIEW_REQUIRED_ARTIFACT_MISSING`, `VISUAL_PREVIEW_SCHEMA_INVALID`, `VISUAL_PREVIEW_FIGMA_NODE_COVERAGE_INCOMPLETE`, `VISUAL_PREVIEW_COMPONENT_STATE_COVERAGE_INCOMPLETE`, `VISUAL_PREVIEW_PAGE_COVERAGE_INCOMPLETE`, `VISUAL_PREVIEW_ASSET_TRACEABILITY_INCOMPLETE`, `VISUAL_PREVIEW_VIEWPORT_CAPTURE_INCOMPLETE`, `VISUAL_PREVIEW_VISUAL_DIFF_BLOCKED`, and `VISUAL_PREVIEW_KNOWN_GAP_UNRESOLVED`. -## Structured IR Readiness Gate +## Visual Spec Package Readiness Gate -Structured UI acceptance IR passes only when: +The visual requirements/spec structured asset package passes only when: - upstream visual-design intake is ready -- `structured-ir.yaml`, `ir-assertions.yaml`, and `ir-evidence-packet.md` exist -- IR items preserve source refs, page, region, role, state, viewport, provider-neutral locator strategy, expectations, acceptance intent, confidence, status, and blockers -- assertions reference existing IR items and include ready `ci_low_cost` checks +- `visual-spec.yaml`, `visual-spec-assertions.yaml`, and `visual-spec-evidence-packet.md` exist +- visual spec items preserve source refs, page, region, role, state, viewport, provider-neutral locator strategy, expectations, acceptance intent, confidence, status, and blockers +- implementation resources, images, and token refs are traceable to the design source; for Figma sources, they must trace back to Figma refs +- assertions reference existing visual spec items and include ready `ci_low_cost` checks - missing provider evidence and product ambiguity are represented with distinct blocker paths - locator strategies avoid implementation-owned CSS selectors, XPath, generated class names, downstream test IDs, code component names, tasks, or requirement IDs -- HTML SSOT, screenshots, and visual diffs remain enhancement evidence rather than the primary acceptance substrate +- component matrix previews, screenshots, and visual diffs remain helper evidence rather than the target deliverable -The structured IR validator emits blocker codes such as `IR_SOURCE_INTAKE_BLOCKED`, `IR_REQUIRED_ARTIFACT_MISSING`, `IR_SCHEMA_INVALID`, `IR_INTAKE_INCOMPLETE`, `IR_PROVIDER_EVIDENCE_MISSING`, `IR_PRODUCT_AMBIGUITY_UNRESOLVED`, `IR_ASSERTION_COVERAGE_INCOMPLETE`, `IR_LOCATOR_STRATEGY_INVALID`, `IR_DOWNSTREAM_OWNERSHIP_LEAK`, and `IR_READY_WITHOUT_EVIDENCE`. +The visual spec package validator emits blocker codes such as `VISUAL_SPEC_SOURCE_INTAKE_BLOCKED`, `VISUAL_SPEC_REQUIRED_ARTIFACT_MISSING`, `VISUAL_SPEC_SCHEMA_INVALID`, `VISUAL_SPEC_INTAKE_INCOMPLETE`, `VISUAL_SPEC_PROVIDER_EVIDENCE_MISSING`, `VISUAL_SPEC_PRODUCT_AMBIGUITY_UNRESOLVED`, `VISUAL_SPEC_ASSERTION_COVERAGE_INCOMPLETE`, `VISUAL_SPEC_LOCATOR_STRATEGY_INVALID`, `VISUAL_SPEC_DOWNSTREAM_OWNERSHIP_LEAK`, and `VISUAL_SPEC_READY_WITHOUT_EVIDENCE`. ## Development @@ -194,16 +192,16 @@ Validate visual-design artifacts: python scripts/python/validate_visual_design_intake.py specs//intake/visual-design ``` -Validate HTML SSOT bundles: +Validate component matrix preview helpers: ```bash -python scripts/python/validate_html_ssot.py specs//intake/visual-design/figma2htmlssot +python scripts/python/validate_visual_previews.py specs//intake/visual-design/previews ``` -Validate structured IR artifacts: +Validate visual spec package artifacts: ```bash -python scripts/python/validate_structured_ir_intake.py specs//intake/visual-design/structured-ir +python scripts/python/validate_visual_spec_package.py specs//intake/visual-design/visual-spec-package ``` Validate PRD artifacts: diff --git a/extensions/intake/commands/speckit.intake.figma2htmlssot.md b/extensions/intake/commands/speckit.intake.figma2htmlssot.md deleted file mode 100644 index ba83928002..0000000000 --- a/extensions/intake/commands/speckit.intake.figma2htmlssot.md +++ /dev/null @@ -1,162 +0,0 @@ ---- -description: Create or validate a Figma-derived HTML visual SSOT bundle for acceptance. ---- - -## User Input - -```text -$ARGUMENTS -``` - -Classify the input before proceeding: - -- `source`: Figma URL, file, page, frame, node, component set, or existing visual-design intake directory -- `output_dir`: existing or target figma2htmlssot artifact directory -- `validation_request`: validate, check, gate, coverage, diff, or readiness request -- `review_guidance`: target platform, viewport set, required states, capture scope, visual-diff threshold, or reviewer instructions - -## Goal - -Create, update, or validate a Figma-to-HTML visual single source of truth (SSOT) bundle for the active Spec Kit feature. The HTML bundle defines visual requirements and acceptance surfaces by preserving Figma traceability, source coverage, component-state granularity, page-level composition, assets, responsive behavior, and known gaps. - -Default output directory: - -```text -specs//intake/visual-design/figma2htmlssot/ -``` - -Normative authority: - -- `templates/intake-visual-design-contract.md` defines visual evidence semantics, fidelity policy, and provider evidence policy. -- `templates/schemas/figma-metadata-index.schema.json` and `templates/schemas/figma-node-inventory.schema.json` define required Figma metadata evidence when the source is Figma. -- `scripts/python/validate_visual_design_intake.py` defines source-side visual-design intake readiness before HTML SSOT projection. -- `templates/schemas/figma-map.schema.json`, `templates/schemas/assets-manifest.schema.json`, and `templates/schemas/html-ssot-coverage.schema.json` define machine-readable HTML SSOT bundle structure. -- `scripts/python/validate_html_ssot.py` defines HTML SSOT readiness, cross-file traceability checks, and blocker codes. -- This command owns HTML SSOT routing, capture orchestration, validation invocation, and report shape. - -## Operating Boundaries - -- Treat HTML as the visual requirements SSOT artifact only after Figma source evidence and coverage proof are recorded. -- Preserve the original Figma source refs, raw metadata refs, screenshots, asset refs, and node inventory refs. -- Do not convert visual facts into product behavior, API rules, implementation plans, implementation tasks, downstream-owned requirement IDs, or code component names. -- Do not mark the HTML SSOT ready when required Figma nodes, required component-instance states, required page surfaces, assets, or viewport captures are missing. -- Do not modify application source, tests, package manifests, feature implementation files, or existing Spec Kit core templates. -- If required Figma, screenshot, browser, or diff tooling is unavailable, create a blocked evidence report that records the missing tool and stop before claiming readiness. - -## Completeness Units - -The minimum visual acceptance unit is: - -```text -component instance + state + content sample + container constraint + viewport -``` - -Use this hierarchy: - -- Component-state: minimum runnable screenshot and comparison unit for tokens, local layout, states, overflow, and local interaction surfaces. -- Section: composition unit for spacing, ordering, alignment, and local responsive behavior across multiple component instances. -- Page: final release gate for information completeness, cross-section layout, first-viewport experience, scrolling, and target runtime acceptance. - -Component-level acceptance cannot replace page-level acceptance. Page-level acceptance cannot replace required component-state coverage. - -## Context Loading - -1. Verify the current directory is a Spec Kit project by checking for `.specify/`, unless `$ARGUMENTS` points to a standalone artifact directory for extension development. -2. Identify the active feature: - - Prefer `SPECIFY_FEATURE` when set. - - Otherwise use the current Git branch name when it matches a directory under `specs/`. - - Otherwise inspect `specs/` and choose the most recent feature directory only if there is a single clear candidate. - - If the feature cannot be identified and no standalone artifact directory was provided, stop and ask the user to set `SPECIFY_FEATURE` or run from the feature branch. -3. Read `.specify/extensions/intake/intake-config.yml` when present. -4. Read `templates/intake-visual-design-contract.md`, Figma metadata schemas, and existing visual-design intake artifacts before creating or validating HTML SSOT artifacts. -5. For Figma sources, require existing or newly captured `figma-metadata.index.yaml`, `figma-node-inventory.yaml`, and `figma-metadata.part-*.xml` before deriving the HTML SSOT. -6. Read any existing figma2htmlssot artifacts and preserve valid evidence unless the user explicitly asks to recapture or regenerate it. - -## Mode Routing - -- Build mode: use when `$ARGUMENTS` names a Figma source, node scope, viewport set, state matrix, or asks to build, generate, convert, capture, update, or recapture the HTML SSOT. -- Validate mode: use when `$ARGUMENTS` includes `validate`, `check`, `gate`, `coverage`, `diff`, `readiness`, or only names an existing figma2htmlssot output directory. -- Build then validate: use when both a source and validation intent are present, or after build artifacts are updated. - -## Build Procedure - -1. Resolve the Figma source, selected page/frame/node scope, target viewport set, required component-instance states, and target output directory. -2. Ensure the upstream visual-design intake for the Figma source passes readiness: - -```bash -python .specify/extensions/intake/scripts/python/validate_visual_design_intake.py -``` - -3. Create or update these HTML SSOT bundle artifacts: - - `visual-spec.html`: runnable HTML visual SSOT with required surfaces and states. - - `figma-map.json`: mapping from Figma node IDs to HTML selectors and acceptance units. - - `assets-manifest.json`: fonts, images, icons, media, and exported asset refs with source refs. - - `coverage-report.md`: required node, state, section, page, asset, and viewport coverage. - - `known-gaps.md`: accepted exceptions, missing evidence, blocked captures, and owner or next action. - - `screenshots/`: Figma source screenshots, HTML screenshots, and diff outputs when tooling is available. -4. Add traceability attributes to required HTML elements: - -```html -data-figma-node-id="" -data-spec-role="" -data-acceptance-unit="" -data-required="true" -``` - -5. Preserve design tokens and visual facts in CSS custom properties or documented token mappings when available. Record unmapped tokens as gaps instead of silently flattening them. -6. Export or reference assets through `assets-manifest.json`; do not embed untraceable base64 assets unless the source ref and checksum are recorded. -7. Capture target runtime screenshots for every required component-state, section, and page surface across the declared viewport set. -8. Compare Figma and HTML screenshots when tooling is available. Record thresholds, accepted exceptions, and blocking difference categories in `coverage-report.md`. -9. Validate the HTML SSOT bundle before reporting readiness: - -```bash -python .specify/extensions/intake/scripts/python/validate_html_ssot.py -``` - -10. For unavailable required evidence, write a structured blocker in `known-gaps.md` and report `ready_gate: BLOCKED`. - -## Validation Procedure - -1. Resolve the figma2htmlssot output directory from `$ARGUMENTS` or the active feature. -2. Run: - -```bash -python .specify/extensions/intake/scripts/python/validate_html_ssot.py -``` - -3. The validator confirms required bundle artifacts exist and are internally consistent: - - every required Figma node is covered by `figma-map.json` or recorded as an accepted exclusion - - every required component-state has a runnable HTML selector, viewport, content sample, container constraint, and screenshot - - every required section and page has a runnable HTML selector, viewport coverage, and screenshot - - every referenced asset appears in `assets-manifest.json` with source refs and checksum - - every required HTML element has a stable Figma source ref -4. Report blocker codes exactly as emitted by `scripts/python/validate_html_ssot.py`; do not infer or rewrite the validator output. -5. Mark readiness: - - `PASS` only when required artifacts exist, required coverage is complete, screenshots are captured, blockers are empty, and accepted exceptions are explicit. - - `BLOCKED` when any blocker code is present or any required coverage item lacks traceable evidence. - -## Readiness Authority - -Use this precedence when sources disagree: - -1. Upstream Figma metadata and visual-design intake readiness are canonical for source evidence. -2. `figma-map.json`, `assets-manifest.json`, and screenshot captures are canonical for HTML SSOT traceability and runtime coverage. -3. `coverage-report.md` and `known-gaps.md` explain readiness, accepted exceptions, and blockers for human review. - -Do not promote HTML as the visual SSOT when upstream Figma evidence is incomplete or when coverage cannot prove the minimum component-state and page-level gates. - -## Report - -Return: - -- mode executed: build, validate, or build_then_validate -- output or validated directory -- Figma source scope and upstream visual-design intake readiness -- required viewport set -- component-state, section, and page coverage counts -- asset count and unresolved asset gaps -- screenshot and visual-diff status -- readiness result -- blocker codes -- known accepted exceptions -- next corrective action when blocked diff --git a/extensions/intake/commands/speckit.intake.ir.md b/extensions/intake/commands/speckit.intake.ir.md deleted file mode 100644 index 0625b03889..0000000000 --- a/extensions/intake/commands/speckit.intake.ir.md +++ /dev/null @@ -1,165 +0,0 @@ ---- -description: Create or validate structured UI acceptance IR for CI-friendly checks. ---- - -## User Input - -```text -$ARGUMENTS -``` - -Classify the input before proceeding: - -- `source`: existing visual-design intake directory, visual requirement refs, Figma metadata refs, or HTML SSOT enhancement refs -- `output_dir`: existing or target structured IR artifact directory -- `validation_request`: validate, check, gate, assertions, CI, or readiness request -- `review_guidance`: target platform, viewport set, required states, semantic regions, ARIA expectations, token coverage, or assertion scope - -## Goal - -Create, update, or validate an Intake-owned structured UI acceptance IR layer for the active Spec Kit feature. The IR is the CI-friendly validation substrate: it carries deterministic DOM, ARIA, design-token, state, locator-strategy, relation, and assertion facts without forcing downstream commands to parse provider-native files or run full-page screenshot diffs as the primary gate. - -Default output directory: - -```text -specs//intake/visual-design/structured-ir/ -``` - -Normative authority: - -- `templates/intake-visual-design-contract.md` defines visual source evidence semantics. -- `templates/intake-structured-ir-contract.md` defines structured IR semantics, boundary, readiness, and blocker codes. -- `templates/schemas/structured-ir.schema.json` defines the machine-readable IR records. -- `templates/schemas/ir-assertions.schema.json` defines CI-friendly assertion records. -- `scripts/python/validate_structured_ir_intake.py` defines readiness validation. -- This command owns structured IR routing, capture orchestration, validation invocation, and report shape. - -## Operating Boundaries - -- Treat structured IR as the primary low-cost UI acceptance substrate. -- Treat HTML SSOT, screenshots, and visual diffs as enhancement evidence only; do not make them the primary acceptance contract. -- Preserve original source refs, visual requirement refs, DES refs, optional HTML SSOT refs, and explicit blocker refs. -- Do not generate downstream-owned requirement IDs, tasks, code component names, implementation-owned selectors, final test framework files, or product semantics. -- Do not infer business rules, permissions, validation behavior, analytics, data sources, security, or compliance behavior from visual appearance alone. -- Distinguish missing provider evidence from product ambiguity with separate blocker codes and evidence records. -- If required source evidence is unavailable, create a blocked `ir-evidence-packet.md` and stop before claiming readiness. - -## Completeness Units - -The minimum structured acceptance unit is: - -```text -IR item + source ref + semantic locator strategy + expectation + assertion + viewport/state -``` - -Use this hierarchy: - -- IR item: provider-neutral DOM, ARIA, token, state, content, relation, and locator-strategy fact. -- Assertion: low-cost check over one or more IR items. -- CI substrate: the set of ready assertions with `ci_suitability: ci_low_cost`. - -## Context Loading - -1. Verify the current directory is a Spec Kit project by checking for `.specify/`, unless `$ARGUMENTS` points to a standalone artifact directory for extension development. -2. Identify the active feature: - - Prefer `SPECIFY_FEATURE` when set. - - Otherwise use the current Git branch name when it matches a directory under `specs/`. - - Otherwise inspect `specs/` and choose the most recent feature directory only if there is a single clear candidate. - - If the feature cannot be identified and no standalone artifact directory was provided, stop and ask the user to set `SPECIFY_FEATURE` or run from the feature branch. -3. Read `.specify/extensions/intake/intake-config.yml` when present. -4. Read `templates/intake-visual-design-contract.md`, `templates/intake-structured-ir-contract.md`, and existing visual-design intake artifacts before creating or validating IR artifacts. -5. Read optional `figma2htmlssot/` artifacts only as enhancement evidence. -6. Read any existing structured IR artifacts and preserve stable valid IR IDs unless the source ref or normalized fact changes. - -## Mode Routing - -- Build mode: use when `$ARGUMENTS` names source refs, visual requirement refs, target viewports, required states, semantic regions, DOM/ARIA/token expectations, or asks to build, generate, derive, update, or recapture IR. -- Validate mode: use when `$ARGUMENTS` includes `validate`, `check`, `gate`, `assertions`, `CI`, `readiness`, or only names an existing structured IR output directory. -- Build then validate: use when both source and validation intent are present, or after build artifacts are updated. - -## Build Procedure - -1. Resolve the upstream visual-design intake directory and target structured IR output directory. -2. Ensure upstream visual-design intake passes readiness: - -```bash -python .specify/extensions/intake/scripts/python/validate_visual_design_intake.py -``` - -3. Create or update: - - `structured-ir.yaml`: source-backed UI acceptance facts. - - `ir-assertions.yaml`: low-cost assertions over IR items. - - `ir-evidence-packet.md`: readiness summary, blocker separation, and next corrective action. -4. For each IR item, record stable `IR-*` ID, source refs, optional visual requirement refs, page, region, role, state, viewport, provider-neutral locator strategy, expectations, acceptance intent, confidence, status, and blockers. -5. For each assertion, record stable `IRA-*` ID, `IR-*` refs, assertion type, expected value, evidence refs, CI suitability, status, and blockers. -6. Record missing provider evidence as `IR_PROVIDER_EVIDENCE_MISSING`. -7. Record unresolved product ambiguity as `IR_PRODUCT_AMBIGUITY_UNRESOLVED`. -8. Record locator or ownership violations as `IR_LOCATOR_STRATEGY_INVALID` or `IR_DOWNSTREAM_OWNERSHIP_LEAK`. -9. Validate before reporting readiness: - -```bash -python .specify/extensions/intake/scripts/python/validate_structured_ir_intake.py -``` - -## Validation Procedure - -1. Resolve the structured IR output directory from `$ARGUMENTS` or the active feature. -2. Run: - -```bash -python .specify/extensions/intake/scripts/python/validate_structured_ir_intake.py -``` - -3. The validator confirms: - - upstream visual-design intake is ready - - required artifacts exist - - schemas validate - - item and assertion counts match actual arrays - - assertions reference existing IR items - - source refs and provider evidence are complete - - product ambiguities are explicit and not collapsed into missing provider evidence - - locator strategies are provider-neutral - - no downstream-owned requirement IDs, tasks, code component names, implementation-owned selectors, or product semantics leak into the IR - - at least one ready `ci_low_cost` assertion exists - - `ir-evidence-packet.md` readiness is PASS only when no blocking issue remains - -4. Apply these blocker codes when validation fails: - - `IR_SOURCE_INTAKE_BLOCKED` - - `IR_REQUIRED_ARTIFACT_MISSING` - - `IR_SCHEMA_INVALID` - - `IR_INTAKE_INCOMPLETE` - - `IR_PROVIDER_EVIDENCE_MISSING` - - `IR_PRODUCT_AMBIGUITY_UNRESOLVED` - - `IR_ASSERTION_COVERAGE_INCOMPLETE` - - `IR_LOCATOR_STRATEGY_INVALID` - - `IR_DOWNSTREAM_OWNERSHIP_LEAK` - - `IR_READY_WITHOUT_EVIDENCE` - -## Readiness Authority - -Use this precedence when sources disagree: - -1. Upstream visual-design intake is canonical for source evidence and limitations. -2. `structured-ir.yaml` is canonical for provider-neutral UI acceptance facts. -3. `ir-assertions.yaml` is canonical for low-cost CI assertion records. -4. HTML SSOT, screenshots, and visual diffs are enhancement evidence only. -5. `ir-evidence-packet.md` explains readiness, accepted exceptions, and blockers for human review. - -Do not promote structured IR as ready when provider evidence is missing, product ambiguity is unresolved, assertions are not CI-suitable, or downstream implementation ownership has leaked into the intake layer. - -## Report - -Return: - -- mode executed: build, validate, or build_then_validate -- output or validated directory -- upstream visual-design intake readiness -- IR item count -- assertion count -- ready CI-low-cost assertion count -- provider evidence gaps -- product ambiguity gaps -- locator or ownership violations -- readiness result -- blocker codes -- next corrective action when blocked diff --git a/extensions/intake/commands/speckit.intake.visual-design.md b/extensions/intake/commands/speckit.intake.visual-design.md index aa7ec1f401..712d299811 100644 --- a/extensions/intake/commands/speckit.intake.visual-design.md +++ b/extensions/intake/commands/speckit.intake.visual-design.md @@ -1,4 +1,4 @@ ---- +--- description: Capture or validate visual design intake for the active Spec Kit feature. --- @@ -12,12 +12,12 @@ Classify the input before proceeding: - `source`: image, PDF, Markdown design brief, Figma URL, file, page, frame, node, or exported design asset - `intake_dir`: existing visual-design intake artifact directory -- `validation_request`: validate, check, gate, or readiness request -- `review_guidance`: target platform, required fidelity, capture scope, source precedence, or reviewer instructions +- `validation_request`: validate, check, gate, readiness, build-spec-package, validate-spec-package, build-previews, validate-previews, or CI-friendly assertion request +- `review_guidance`: target platform, required fidelity, capture scope, source precedence, Figma-backed resource requirements, or reviewer instructions ## Goal -Create, update, or validate provider-neutral visual design intake artifacts for the active Spec Kit feature. Intake preserves reachable design sources, raw provider evidence, stable source refs, checksums or retrieval metadata, and schema-required visual facts so downstream SDD workflows can project requirements and define their own visual verification criteria with traceability. +Create, update, or validate provider-neutral visual design intake artifacts for the active Spec Kit feature. Intake preserves reachable design sources, raw provider evidence, stable source refs, checksums or retrieval metadata, schema-required visual facts, and the visual requirements/spec structured asset package so downstream SDD workflows can implement high-fidelity UI with traceability. Default artifact directory: @@ -29,13 +29,21 @@ Normative authority: - `templates/schemas/*.json` defines machine-readable structure, required fields, types, and enums. - `scripts/python/validate_visual_design_intake.py` defines readiness evaluation and blocker emission. +- `scripts/python/validate_visual_spec_package.py` defines structured visual spec package readiness. +- `scripts/python/validate_visual_previews.py` defines component matrix preview and coverage readiness. - `templates/intake-visual-design-contract.md` defines semantic extraction policy, fidelity policy, and provider evidence policy. +- `templates/intake-visual-spec-package-contract.md` defines the visual requirements/spec structured asset package. +- `templates/intake-visual-previews-contract.md` defines preview coverage helper artifact structure, boundaries, and blocker semantics. - This command only performs input routing, context loading, capture orchestration, validation invocation, and reporting. ## Operating Boundaries - Preserve original design sources and record checksums before extraction. +- For Figma sources, implementation resources, images, exported assets, and token refs must trace back to Figma source refs. - Extract visual requirements as traceable engineering input, not as unsupported prose summaries or downstream-specific schema projections. +- Treat `visual-spec-package/` as the target structured visual requirements/spec asset package for downstream delivery and CI-friendly checks. +- Treat `previews/component-matrix-preview.html`, screenshots, and visual diffs as optional human-review helper evidence only, not the target deliverable. +- Treat `previews/component-coverage.yaml` and `previews/viewport-coverage.yaml` as structured coverage evidence for reviewer completeness checks; they may support readiness but do not replace `visual-spec-package/`. - Use bounded inference for dirty or incomplete design sources: observed claims are source-backed facts; inferred claims require explicit rules and high confidence; candidate claims are reference-only; unsupported claims must remain blocked. - Mark low, medium, or high fidelity explicitly and apply the matching extraction rules. - Use stable provider-neutral evidence IDs and source refs. Do not invent downstream-owned item IDs, requirement IDs, schema fields, code component names, or product semantics. @@ -59,6 +67,10 @@ Normative authority: ## Mode Routing - Capture mode: use when `$ARGUMENTS` names an image, PDF, Markdown design brief, Figma URL, frame, node, platform, fidelity level, or asks to capture, ingest, update, or recapture visual evidence. +- Build spec package mode: use when `$ARGUMENTS` includes `build-spec-package`, `with spec package`, `structured visual spec`, `CI assertions`, or asks for downstream delivery/acceptance assets. +- Validate spec package mode: use when `$ARGUMENTS` includes `validate-spec-package`, `check spec package`, `visual spec readiness`, or only names an existing `visual-spec-package` directory. +- Build previews mode: use when `$ARGUMENTS` includes `build-previews`, `component matrix`, `preview coverage`, `coverage review`, `component-coverage`, or `viewport-coverage`. +- Validate previews mode: use when `$ARGUMENTS` includes `validate-previews`, `check previews`, `preview readiness`, or only names an existing `previews` directory. - Validate mode: use when `$ARGUMENTS` includes `validate`, `check`, `gate`, `readiness`, or only names an existing visual-design intake directory. - Capture then validate: use when both a source and validation intent are present, or after capture artifacts are updated. @@ -92,6 +104,55 @@ Normative authority: 10. Add an intake parity plan that records source-side comparison targets, methods, thresholds, accepted exceptions, and blocking difference categories without defining implementation capture artifacts or downstream delivery approval. 11. Run validation before reporting readiness. +## Visual Spec Package Procedure + +1. Resolve the upstream visual-design intake directory and target `visual-spec-package/` directory. +2. Ensure visual-design intake passes readiness before building or validating the package: + +```bash +python .specify/extensions/intake/scripts/python/validate_visual_design_intake.py +``` + +3. Create or update: + - `visual-spec.yaml`: structured visual requirements/spec facts for pages, regions, roles, states, viewports, locators, expectations, resources, tokens, and blockers. + - `visual-spec-assertions.yaml`: low-cost assertions over visual spec items. + - `visual-spec-evidence-packet.md`: readiness summary, blocker separation, resource traceability, and next corrective action. +4. For Figma sources, every implementation resource, image, exported asset, icon, font, color token, spacing token, radius token, typography token, and component-state ref must trace to Figma metadata, node, variable, style, component, or exported asset refs. +5. When preview artifacts exist, use them only as helper evidence: + - `previews/component-matrix-preview.html` is a human review mirror for component sets, instances, variant props, states, sizes, density, theme, content samples, and viewports. + - `previews/component-coverage.yaml` is the machine-readable component coverage record. + - `previews/viewport-coverage.yaml` is the machine-readable viewport coverage record. +6. Do not use `component-matrix-preview.html` or preview rendering output as the source of truth for assets, tokens, product behavior, or requirements. Use Figma/source refs and visual-design intake evidence as authority. +7. Validate before reporting readiness: + +```bash +python .specify/extensions/intake/scripts/python/validate_visual_spec_package.py +``` + +## Preview Coverage Procedure + +1. Resolve the upstream visual-design intake directory and target `previews/` directory. +2. Ensure visual-design intake passes readiness before building or validating preview coverage: + +```bash +python .specify/extensions/intake/scripts/python/validate_visual_design_intake.py +``` + +3. Create or update according to `templates/intake-visual-previews-contract.md`: + - `component-matrix-preview.html`: human-review panel that exhaustively displays component sets, component instances, variant props, states, sizes, density, theme, content samples, and viewports. + - `component-coverage.yaml`: machine-readable coverage records for each required component dimension, covered cell, missing cell, blocker, visual spec ref, preview ref, and Figma source ref. + - `viewport-coverage.yaml`: machine-readable viewport coverage records with source refs, visual spec refs, page refs, screenshots, and visual diff status. + - `known-gaps.md`: accepted exceptions, missing evidence, blocked captures, and owner or next action. + - `screenshots/`: Figma source screenshots, preview screenshots, and diff outputs when tooling is available. +4. Link every preview cell back to its Figma node, component, variable, or style ref, its `visual-spec.yaml` item, its `component-coverage.yaml` record, and screenshot or diff evidence when available. +5. Do not use preview HTML as a requirements source, implementation HTML, product semantic source, token source, or replacement for `visual-spec.yaml`. +6. Do not silently complete missing Figma states, variants, viewports, resources, or images. Record them in `component-coverage.yaml`, `viewport-coverage.yaml`, or `known-gaps.md`. +7. Validate before reporting readiness: + +```bash +python .specify/extensions/intake/scripts/python/validate_visual_previews.py +``` + ## Validation Procedure 1. Resolve the visual-design intake directory from `$ARGUMENTS` or the active feature. @@ -109,9 +170,12 @@ python .specify/extensions/intake/scripts/python/validate_visual_design_intake.p Use this precedence when sources disagree: -1. JSON Schemas are canonical for structural validity. -2. `validate_visual_design_intake.py` is canonical for readiness status and blocker codes. -3. `templates/intake-visual-design-contract.md` is canonical for semantic extraction, fidelity, and provider evidence policy. +1. JSON Schemas are canonical for structural validity in all modes. +2. `validate_visual_design_intake.py` is canonical for visual-design intake readiness status and blocker codes. +3. `validate_visual_spec_package.py` is canonical for visual spec package readiness status and blocker codes. +4. `validate_visual_previews.py` is canonical for preview coverage readiness status and blocker codes. +5. `templates/intake-visual-design-contract.md` is canonical for semantic extraction, fidelity, and provider evidence policy. +6. `templates/intake-visual-spec-package-contract.md` and `templates/intake-visual-previews-contract.md` are canonical for their artifact families. Do not restate, reinterpret, or override blocker codes in this command. @@ -119,12 +183,16 @@ Do not restate, reinterpret, or override blocker codes in this command. Return: -- mode executed: capture, validate, or capture_then_validate +- mode executed: capture, validate, capture_then_validate, build_spec_package, validate_spec_package, build_previews, or validate_previews - output or validated directory - source type and source refs captured, or the recorded gap/blocker - required fidelity, or the recorded gap/blocker - source file count and processed count, or the recorded gap/blocker - visual requirement count +- visual spec package item count when built or validated +- visual spec package assertion count and CI-low-cost assertion count when built or validated +- preview component coverage count and viewport coverage count when built or validated +- Figma-backed resource traceability result when source is Figma - readiness result - blocker lint errors - next corrective action when blocked diff --git a/extensions/intake/config-template.yml b/extensions/intake/config-template.yml index 3e350cdd1c..a2cff98ab1 100644 --- a/extensions/intake/config-template.yml +++ b/extensions/intake/config-template.yml @@ -1,10 +1,10 @@ -# Intake Configuration +# Intake Configuration artifacts: base_dir: "specs/{feature}/intake" visual_design_dir: "visual-design" - figma2htmlssot_dir: "figma2htmlssot" - structured_ir_dir: "structured-ir" + previews_dir: "previews" + visual_spec_package_dir: "visual-spec-package" prd_dir: "prd" test_cases_dir: "test-cases" prd_source_manifest: "source-manifest.yaml" @@ -14,23 +14,23 @@ artifacts: prd_intake: "prd-intake.yaml" visual_requirements: "visual-requirements.yaml" test_case_intake: "test-case-intake.yaml" - html_ssot: "visual-spec.html" - html_ssot_figma_map: "figma-map.json" - html_ssot_assets_manifest: "assets-manifest.json" - html_ssot_coverage_report: "coverage-report.md" - html_ssot_known_gaps: "known-gaps.md" - html_ssot_screenshots_dir: "screenshots" - html_ssot_validator: "scripts/python/validate_html_ssot.py" - structured_ir: "structured-ir.yaml" - structured_ir_assertions: "ir-assertions.yaml" - structured_ir_evidence_packet: "ir-evidence-packet.md" - structured_ir_validator: "scripts/python/validate_structured_ir_intake.py" + component_matrix_preview: "component-matrix-preview.html" + component_coverage: "component-coverage.yaml" + viewport_coverage: "viewport-coverage.yaml" + visual_previews_known_gaps: "known-gaps.md" + visual_previews_screenshots_dir: "screenshots" + visual_previews_validator: "scripts/python/validate_visual_previews.py" + visual_spec_package: "visual-spec.yaml" + visual_spec_assertions: "visual-spec-assertions.yaml" + visual_spec_package_evidence_packet: "visual-spec-evidence-packet.md" + visual_spec_package_validator: "scripts/python/validate_visual_spec_package.py" prd_contract: "templates/intake-prd-contract.md" prd_evidence_packet_template: "templates/intake-prd-evidence-packet-template.md" visual_design_contract: "templates/intake-visual-design-contract.md" visual_design_evidence_packet_template: "templates/intake-visual-design-evidence-packet-template.md" - structured_ir_contract: "templates/intake-structured-ir-contract.md" - structured_ir_evidence_packet_template: "templates/intake-structured-ir-evidence-packet-template.md" + visual_previews_contract: "templates/intake-visual-previews-contract.md" + visual_spec_package_contract: "templates/intake-visual-spec-package-contract.md" + visual_spec_package_evidence_packet_template: "templates/intake-visual-spec-package-evidence-packet-template.md" test_cases_contract: "templates/intake-test-cases-contract.md" test_cases_evidence_packet_template: "templates/intake-test-cases-evidence-packet-template.md" schemas_dir: "templates/schemas" @@ -42,11 +42,10 @@ artifacts: test_case_intake_schema: "templates/schemas/test-case-intake.schema.json" figma_metadata_index_schema: "templates/schemas/figma-metadata-index.schema.json" figma_node_inventory_schema: "templates/schemas/figma-node-inventory.schema.json" - html_ssot_figma_map_schema: "templates/schemas/figma-map.schema.json" - html_ssot_assets_manifest_schema: "templates/schemas/assets-manifest.schema.json" - html_ssot_coverage_schema: "templates/schemas/html-ssot-coverage.schema.json" - structured_ir_schema: "templates/schemas/structured-ir.schema.json" - structured_ir_assertions_schema: "templates/schemas/ir-assertions.schema.json" + component_coverage_schema: "templates/schemas/component-coverage.schema.json" + viewport_coverage_schema: "templates/schemas/viewport-coverage.schema.json" + visual_spec_package_schema: "templates/schemas/visual-spec-package.schema.json" + visual_spec_assertions_schema: "templates/schemas/visual-spec-assertions.schema.json" metadata_glob: "figma-metadata.part-*.xml" metadata_index: "figma-metadata.index.yaml" node_inventory: "figma-node-inventory.yaml" @@ -74,11 +73,11 @@ readiness: - "high" require_source_integrity: true require_visual_requirements: true - require_structured_ir: true - require_structured_ir_source_refs: true - require_structured_ir_provider_evidence: true - require_structured_ir_ci_assertions: true - require_structured_ir_provider_product_gap_separation: true + require_visual_spec_package: true + require_visual_spec_package_source_refs: true + require_visual_spec_package_provider_evidence: true + require_visual_spec_package_ci_assertions: true + require_visual_spec_package_provider_product_gap_separation: true require_visual_source_refs: true require_fidelity_rules_applied: true require_visual_parity_plan: true diff --git a/extensions/intake/extension.yml b/extensions/intake/extension.yml index 41c850efd6..645d0d436a 100644 --- a/extensions/intake/extension.yml +++ b/extensions/intake/extension.yml @@ -4,7 +4,7 @@ extension: id: intake name: "Intake" version: "0.1.4" - description: "Normalize PRD, design, HTML SSOT, structured IR, and test-case evidence into SDD-ready intake artifacts" + description: "Normalize PRD, visual design/spec packages, preview evidence, and test-case evidence into SDD-ready intake artifacts" author: "bigsmartben" repository: "https://github.com/bigsmartben/spec-kit-intake" homepage: "https://github.com/bigsmartben/spec-kit-intake" @@ -22,13 +22,7 @@ provides: commands: - name: "speckit.intake.visual-design" file: "commands/speckit.intake.visual-design.md" - description: "Capture or validate visual design evidence, source manifests, Figma metadata, inventories, and readiness for the active feature" - - name: "speckit.intake.figma2htmlssot" - file: "commands/speckit.intake.figma2htmlssot.md" - description: "Create or validate a Figma-derived HTML visual SSOT bundle with node, component-state, page, asset, viewport, and screenshot coverage" - - name: "speckit.intake.ir" - file: "commands/speckit.intake.ir.md" - description: "Create or validate structured UI acceptance IR for CI-friendly DOM, ARIA, token, state, locator, relation, and assertion checks" + description: "Capture or validate visual design evidence, Figma-backed resources, visual requirements, and structured visual spec packages for the active feature" - name: "speckit.intake.prd" file: "commands/speckit.intake.prd.md" description: "Capture or validate PRD evidence and normalize product intent, scope, rules, and acceptance inputs for SDD workflows" @@ -60,8 +54,8 @@ defaults: artifacts: base_dir: "specs/{feature}/intake" visual_design_dir: "visual-design" - figma2htmlssot_dir: "figma2htmlssot" - structured_ir_dir: "structured-ir" + previews_dir: "previews" + visual_spec_package_dir: "visual-spec-package" prd_dir: "prd" test_cases_dir: "test-cases" prd_source_manifest: "source-manifest.yaml" @@ -71,23 +65,23 @@ defaults: prd_intake: "prd-intake.yaml" visual_requirements: "visual-requirements.yaml" test_case_intake: "test-case-intake.yaml" - html_ssot: "visual-spec.html" - html_ssot_figma_map: "figma-map.json" - html_ssot_assets_manifest: "assets-manifest.json" - html_ssot_coverage_report: "coverage-report.md" - html_ssot_known_gaps: "known-gaps.md" - html_ssot_screenshots_dir: "screenshots" - html_ssot_validator: "scripts/python/validate_html_ssot.py" - structured_ir: "structured-ir.yaml" - structured_ir_assertions: "ir-assertions.yaml" - structured_ir_evidence_packet: "ir-evidence-packet.md" - structured_ir_validator: "scripts/python/validate_structured_ir_intake.py" + component_matrix_preview: "component-matrix-preview.html" + component_coverage: "component-coverage.yaml" + viewport_coverage: "viewport-coverage.yaml" + visual_previews_known_gaps: "known-gaps.md" + visual_previews_screenshots_dir: "screenshots" + visual_previews_validator: "scripts/python/validate_visual_previews.py" + visual_spec_package: "visual-spec.yaml" + visual_spec_assertions: "visual-spec-assertions.yaml" + visual_spec_package_evidence_packet: "visual-spec-evidence-packet.md" + visual_spec_package_validator: "scripts/python/validate_visual_spec_package.py" prd_contract: "templates/intake-prd-contract.md" prd_evidence_packet_template: "templates/intake-prd-evidence-packet-template.md" visual_design_contract: "templates/intake-visual-design-contract.md" visual_design_evidence_packet_template: "templates/intake-visual-design-evidence-packet-template.md" - structured_ir_contract: "templates/intake-structured-ir-contract.md" - structured_ir_evidence_packet_template: "templates/intake-structured-ir-evidence-packet-template.md" + visual_previews_contract: "templates/intake-visual-previews-contract.md" + visual_spec_package_contract: "templates/intake-visual-spec-package-contract.md" + visual_spec_package_evidence_packet_template: "templates/intake-visual-spec-package-evidence-packet-template.md" test_cases_contract: "templates/intake-test-cases-contract.md" test_cases_evidence_packet_template: "templates/intake-test-cases-evidence-packet-template.md" schemas_dir: "templates/schemas" @@ -99,11 +93,10 @@ defaults: test_case_intake_schema: "templates/schemas/test-case-intake.schema.json" figma_metadata_index_schema: "templates/schemas/figma-metadata-index.schema.json" figma_node_inventory_schema: "templates/schemas/figma-node-inventory.schema.json" - html_ssot_figma_map_schema: "templates/schemas/figma-map.schema.json" - html_ssot_assets_manifest_schema: "templates/schemas/assets-manifest.schema.json" - html_ssot_coverage_schema: "templates/schemas/html-ssot-coverage.schema.json" - structured_ir_schema: "templates/schemas/structured-ir.schema.json" - structured_ir_assertions_schema: "templates/schemas/ir-assertions.schema.json" + component_coverage_schema: "templates/schemas/component-coverage.schema.json" + viewport_coverage_schema: "templates/schemas/viewport-coverage.schema.json" + visual_spec_package_schema: "templates/schemas/visual-spec-package.schema.json" + visual_spec_assertions_schema: "templates/schemas/visual-spec-assertions.schema.json" metadata_glob: "figma-metadata.part-*.xml" metadata_index: "figma-metadata.index.yaml" node_inventory: "figma-node-inventory.yaml" @@ -130,11 +123,11 @@ defaults: - "high" require_source_integrity: true require_visual_requirements: true - require_structured_ir: true - require_structured_ir_source_refs: true - require_structured_ir_provider_evidence: true - require_structured_ir_ci_assertions: true - require_structured_ir_provider_product_gap_separation: true + require_visual_spec_package: true + require_visual_spec_package_source_refs: true + require_visual_spec_package_provider_evidence: true + require_visual_spec_package_ci_assertions: true + require_visual_spec_package_provider_product_gap_separation: true require_visual_source_refs: true require_fidelity_rules_applied: true require_visual_parity_plan: true diff --git a/extensions/intake/scripts/python/validate_html_ssot.py b/extensions/intake/scripts/python/validate_html_ssot.py deleted file mode 100644 index e7b78bbdde..0000000000 --- a/extensions/intake/scripts/python/validate_html_ssot.py +++ /dev/null @@ -1,344 +0,0 @@ -#!/usr/bin/env python3 -"""Validate Spec Kit Figma-derived HTML visual SSOT bundles.""" - -from __future__ import annotations - -import argparse -import json -import re -import sys -from pathlib import Path -from typing import Any - -try: - import yaml -except ImportError: # pragma: no cover - exercised in user environments - yaml = None - -from intake_validator_common import validate_json_schema - - -BLOCKERS = { - "SOURCE_INTAKE_BLOCKED": "HTML_SSOT_SOURCE_INTAKE_BLOCKED", - "REQUIRED_ARTIFACT_MISSING": "HTML_SSOT_REQUIRED_ARTIFACT_MISSING", - "FIGMA_NODE_COVERAGE_INCOMPLETE": "HTML_SSOT_FIGMA_NODE_COVERAGE_INCOMPLETE", - "COMPONENT_STATE_COVERAGE_INCOMPLETE": "HTML_SSOT_COMPONENT_STATE_COVERAGE_INCOMPLETE", - "PAGE_COVERAGE_INCOMPLETE": "HTML_SSOT_PAGE_COVERAGE_INCOMPLETE", - "ASSET_TRACEABILITY_INCOMPLETE": "HTML_SSOT_ASSET_TRACEABILITY_INCOMPLETE", - "VIEWPORT_CAPTURE_INCOMPLETE": "HTML_SSOT_VIEWPORT_CAPTURE_INCOMPLETE", - "VISUAL_DIFF_BLOCKED": "HTML_SSOT_VISUAL_DIFF_BLOCKED", - "KNOWN_GAP_UNRESOLVED": "HTML_SSOT_KNOWN_GAP_UNRESOLVED", - "SCHEMA_INVALID": "HTML_SSOT_SCHEMA_INVALID", -} - - -def main() -> int: - parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument("html_ssot_dir", help="Directory containing figma2htmlssot artifacts") - parser.add_argument("--json", action="store_true", help="Emit machine-readable JSON") - args = parser.parse_args() - - html_dir = Path(args.html_ssot_dir) - blocker_codes: list[str] = [] - warnings: list[str] = [] - details: dict[str, Any] = {"html_ssot_dir": str(html_dir)} - - if not html_dir.exists() or not html_dir.is_dir(): - blocker_codes.append(BLOCKERS["REQUIRED_ARTIFACT_MISSING"]) - return emit(args.json, details, sorted(set(blocker_codes)), warnings) - - validate_source_intake(html_dir, details, blocker_codes) - - required_files = { - "visual_spec": html_dir / "visual-spec.html", - "figma_map": html_dir / "figma-map.json", - "assets_manifest": html_dir / "assets-manifest.json", - "coverage_report": html_dir / "coverage-report.md", - "known_gaps": html_dir / "known-gaps.md", - } - missing = [name for name, path in required_files.items() if not path.exists()] - screenshots_dir = html_dir / "screenshots" - if not screenshots_dir.exists() or not screenshots_dir.is_dir(): - missing.append("screenshots") - if missing: - blocker_codes.append(BLOCKERS["REQUIRED_ARTIFACT_MISSING"]) - details["required_artifacts"] = {"missing": missing} - - figma_map = load_json_artifact( - required_files["figma_map"], - "figma-map.schema.json", - "figma_map", - details, - blocker_codes, - ) - assets_manifest = load_json_artifact( - required_files["assets_manifest"], - "assets-manifest.schema.json", - "assets_manifest", - details, - blocker_codes, - ) - coverage = load_coverage_report( - required_files["coverage_report"], - details, - blocker_codes, - ) - - html_text = "" - if required_files["visual_spec"].exists(): - html_text = required_files["visual_spec"].read_text(encoding="utf-8", errors="replace") - - validate_map_coverage(figma_map, html_text, details, blocker_codes) - validate_asset_traceability(html_dir, assets_manifest, details, blocker_codes) - validate_screenshot_coverage(screenshots_dir, details, blocker_codes) - validate_coverage_readiness(coverage, details, blocker_codes) - validate_known_gaps(required_files["known_gaps"], details, blocker_codes) - - return emit(args.json, details, sorted(set(blocker_codes)), warnings) - - -def validate_source_intake( - html_dir: Path, - details: dict[str, Any], - blocker_codes: list[str], -) -> None: - upstream = html_dir.parent - packet = upstream / "visual-evidence-packet.md" - if not packet.exists(): - details["source_intake"] = {"missing": True} - blocker_codes.append(BLOCKERS["SOURCE_INTAKE_BLOCKED"]) - return - - text = packet.read_text(encoding="utf-8", errors="replace").lstrip("\ufeff") - match = re.match(r"\A---\s*\r?\n(.*?)\r?\n---", text, re.DOTALL) - metadata: dict[str, Any] = {} - if match and yaml is not None: - loaded = yaml.safe_load(match.group(1)) or {} - metadata = loaded if isinstance(loaded, dict) else {} - ready_gate = str(metadata.get("ready_gate") or "").strip().upper() - blockers = metadata.get("blockers") - details["source_intake"] = { - "path": str(packet), - "ready_gate": ready_gate, - "blockers": blockers, - } - if ready_gate != "PASS" or (isinstance(blockers, list) and blockers): - blocker_codes.append(BLOCKERS["SOURCE_INTAKE_BLOCKED"]) - - -def load_json_artifact( - path: Path, - schema_name: str, - details_key: str, - details: dict[str, Any], - blocker_codes: list[str], -) -> dict[str, Any]: - if not path.exists(): - return {} - validate_json_schema( - instance_path=path, - schema_name=schema_name, - details_key=details_key, - details=details, - blocker_codes=blocker_codes, - schema_error_code=BLOCKERS["SCHEMA_INVALID"], - ) - try: - data = json.loads(path.read_text(encoding="utf-8")) - except json.JSONDecodeError: - blocker_codes.append(BLOCKERS["SCHEMA_INVALID"]) - return {} - return data if isinstance(data, dict) else {} - - -def load_coverage_report( - path: Path, - details: dict[str, Any], - blocker_codes: list[str], -) -> dict[str, Any]: - if not path.exists(): - return {} - text = path.read_text(encoding="utf-8", errors="replace").lstrip("\ufeff") - match = re.match(r"\A---\s*\r?\n(.*?)\r?\n---", text, re.DOTALL) - if not match or yaml is None: - blocker_codes.append(BLOCKERS["SCHEMA_INVALID"]) - details["coverage_report"] = {"front_matter_missing": True} - return {} - loaded = yaml.safe_load(match.group(1)) or {} - coverage = loaded if isinstance(loaded, dict) else {} - temp_path = path.with_suffix(".frontmatter.tmp.yaml") - try: - temp_path.write_text(yaml.safe_dump(coverage), encoding="utf-8") - validate_json_schema( - instance_path=temp_path, - schema_name="html-ssot-coverage.schema.json", - details_key="coverage_report", - details=details, - blocker_codes=blocker_codes, - schema_error_code=BLOCKERS["SCHEMA_INVALID"], - ) - finally: - if temp_path.exists(): - temp_path.unlink() - return coverage - - -def selector_in_html(selector: str, html_text: str) -> bool: - if selector.startswith("[") and selector.endswith("]"): - body = selector[1:-1].strip() - if "=" in body: - attr, value = body.split("=", 1) - attr = attr.strip() - value = value.strip().strip("\"'") - return f'{attr}="{value}"' in html_text or f"{attr}='{value}'" in html_text - return body in html_text - if selector.startswith("#"): - value = selector[1:] - return f'id="{value}"' in html_text or f"id='{value}'" in html_text - if selector.startswith("."): - value = selector[1:] - return re.search(rf"class=['\"][^'\"]*\b{re.escape(value)}\b", html_text) is not None - return selector in html_text - - -def validate_map_coverage( - figma_map: dict[str, Any], - html_text: str, - details: dict[str, Any], - blocker_codes: list[str], -) -> None: - mappings = figma_map.get("mappings", []) - required = [item for item in mappings if isinstance(item, dict) and item.get("required") is True] - missing_selectors: list[str] = [] - incomplete_units: list[str] = [] - page_units = 0 - for item in required: - selector = str(item.get("selector") or "") - if selector and not selector_in_html(selector, html_text): - missing_selectors.append(selector) - if item.get("acceptance_unit") == "component-state": - if not all(item.get(field) for field in ("states", "viewports", "content_sample", "container_constraint")): - incomplete_units.append(str(item.get("figma_node_id") or selector)) - if item.get("acceptance_unit") == "page": - page_units += 1 - - details["figma_map_coverage"] = { - "required_mapping_count": len(required), - "missing_selectors": missing_selectors, - "incomplete_component_state_units": incomplete_units, - "page_unit_count": page_units, - } - if not required or missing_selectors: - blocker_codes.append(BLOCKERS["FIGMA_NODE_COVERAGE_INCOMPLETE"]) - if incomplete_units: - blocker_codes.append(BLOCKERS["COMPONENT_STATE_COVERAGE_INCOMPLETE"]) - if page_units == 0: - blocker_codes.append(BLOCKERS["PAGE_COVERAGE_INCOMPLETE"]) - - -def validate_asset_traceability( - html_dir: Path, - assets_manifest: dict[str, Any], - details: dict[str, Any], - blocker_codes: list[str], -) -> None: - assets = assets_manifest.get("assets", []) - untraceable: list[str] = [] - for asset in assets: - if not isinstance(asset, dict): - untraceable.append("") - continue - path = str(asset.get("path") or "") - source_refs = asset.get("source_refs") - checksum = str(asset.get("sha256") or "") - if path.startswith("data:") or not source_refs or not checksum: - untraceable.append(str(asset.get("id") or path or "")) - continue - if path and not path.startswith(("http://", "https://")) and not (html_dir / path).exists(): - untraceable.append(str(asset.get("id") or path)) - details["asset_traceability"] = {"asset_count": len(assets), "untraceable": untraceable} - if untraceable: - blocker_codes.append(BLOCKERS["ASSET_TRACEABILITY_INCOMPLETE"]) - - -def validate_screenshot_coverage( - screenshots_dir: Path, - details: dict[str, Any], - blocker_codes: list[str], -) -> None: - screenshots = [] - if screenshots_dir.exists() and screenshots_dir.is_dir(): - screenshots = [path.name for path in screenshots_dir.iterdir() if path.is_file()] - details["screenshots"] = {"count": len(screenshots), "files": screenshots} - if not screenshots: - blocker_codes.append(BLOCKERS["VIEWPORT_CAPTURE_INCOMPLETE"]) - - -def validate_coverage_readiness( - coverage: dict[str, Any], - details: dict[str, Any], - blocker_codes: list[str], -) -> None: - if not coverage: - return - details["coverage_readiness"] = { - "ready_gate": coverage.get("ready_gate"), - "blockers": coverage.get("blockers"), - "required_nodes_total": coverage.get("required_nodes_total"), - "required_nodes_covered": coverage.get("required_nodes_covered"), - } - if coverage.get("required_nodes_covered") != coverage.get("required_nodes_total"): - blocker_codes.append(BLOCKERS["FIGMA_NODE_COVERAGE_INCOMPLETE"]) - if coverage.get("component_state_coverage_complete") is not True: - blocker_codes.append(BLOCKERS["COMPONENT_STATE_COVERAGE_INCOMPLETE"]) - if coverage.get("page_coverage_complete") is not True: - blocker_codes.append(BLOCKERS["PAGE_COVERAGE_INCOMPLETE"]) - if coverage.get("asset_traceability_complete") is not True: - blocker_codes.append(BLOCKERS["ASSET_TRACEABILITY_INCOMPLETE"]) - if coverage.get("viewport_capture_complete") is not True: - blocker_codes.append(BLOCKERS["VIEWPORT_CAPTURE_INCOMPLETE"]) - if coverage.get("visual_diff_status") == "blocked": - blocker_codes.append(BLOCKERS["VISUAL_DIFF_BLOCKED"]) - blockers = coverage.get("blockers") - if coverage.get("ready_gate") != "PASS" or (isinstance(blockers, list) and blockers): - blocker_codes.append(BLOCKERS["KNOWN_GAP_UNRESOLVED"]) - - -def validate_known_gaps( - known_gaps_path: Path, - details: dict[str, Any], - blocker_codes: list[str], -) -> None: - if not known_gaps_path.exists(): - return - text = known_gaps_path.read_text(encoding="utf-8", errors="replace") - unresolved = bool(re.search(r"\b(UNRESOLVED|BLOCKED|TODO)\b", text, re.IGNORECASE)) - details["known_gaps"] = {"unresolved": unresolved} - if unresolved: - blocker_codes.append(BLOCKERS["KNOWN_GAP_UNRESOLVED"]) - - -def emit(json_mode: bool, details: dict[str, Any], blockers: list[str], warnings: list[str]) -> int: - result = { - "status": "BLOCKED" if blockers else "PASS", - "blockers": blockers, - "warnings": warnings, - "details": details, - } - if json_mode: - print(json.dumps(result, indent=2, sort_keys=True)) - else: - print(f"HTML SSOT readiness: {result['status']}") - if blockers: - print("Blockers:") - for blocker in blockers: - print(f"- {blocker}") - if warnings: - print("Warnings:") - for warning in warnings: - print(f"- {warning}") - return 1 if blockers else 0 - - -if __name__ == "__main__": - sys.exit(main()) diff --git a/extensions/intake/scripts/python/validate_visual_previews.py b/extensions/intake/scripts/python/validate_visual_previews.py new file mode 100644 index 0000000000..8cd4f26d9b --- /dev/null +++ b/extensions/intake/scripts/python/validate_visual_previews.py @@ -0,0 +1,313 @@ +#!/usr/bin/env python3 +"""Validate Spec Kit Figma-derived component matrix preview bundles.""" + +from __future__ import annotations + +import argparse +import json +import re +import sys +from pathlib import Path +from typing import Any + +try: + import yaml +except ImportError: # pragma: no cover - exercised in user environments + yaml = None + +from intake_validator_common import validate_json_schema + + +BLOCKERS = { + "SOURCE_INTAKE_BLOCKED": "VISUAL_PREVIEW_SOURCE_INTAKE_BLOCKED", + "REQUIRED_ARTIFACT_MISSING": "VISUAL_PREVIEW_REQUIRED_ARTIFACT_MISSING", + "FIGMA_NODE_COVERAGE_INCOMPLETE": "VISUAL_PREVIEW_FIGMA_NODE_COVERAGE_INCOMPLETE", + "COMPONENT_STATE_COVERAGE_INCOMPLETE": "VISUAL_PREVIEW_COMPONENT_STATE_COVERAGE_INCOMPLETE", + "PAGE_COVERAGE_INCOMPLETE": "VISUAL_PREVIEW_PAGE_COVERAGE_INCOMPLETE", + "ASSET_TRACEABILITY_INCOMPLETE": "VISUAL_PREVIEW_ASSET_TRACEABILITY_INCOMPLETE", + "VIEWPORT_CAPTURE_INCOMPLETE": "VISUAL_PREVIEW_VIEWPORT_CAPTURE_INCOMPLETE", + "VISUAL_DIFF_BLOCKED": "VISUAL_PREVIEW_VISUAL_DIFF_BLOCKED", + "KNOWN_GAP_UNRESOLVED": "VISUAL_PREVIEW_KNOWN_GAP_UNRESOLVED", + "SCHEMA_INVALID": "VISUAL_PREVIEW_SCHEMA_INVALID", +} + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("preview_dir", help="Directory containing visual-design preview artifacts") + parser.add_argument("--json", action="store_true", help="Emit machine-readable JSON") + args = parser.parse_args() + + html_dir = Path(args.preview_dir) + blocker_codes: list[str] = [] + warnings: list[str] = [] + details: dict[str, Any] = {"preview_dir": str(html_dir)} + + if not html_dir.exists() or not html_dir.is_dir(): + blocker_codes.append(BLOCKERS["REQUIRED_ARTIFACT_MISSING"]) + return emit(args.json, details, sorted(set(blocker_codes)), warnings) + + validate_source_intake(html_dir, details, blocker_codes) + + required_files = { + "component_matrix_preview": html_dir / "component-matrix-preview.html", + "component_coverage": html_dir / "component-coverage.yaml", + "viewport_coverage": html_dir / "viewport-coverage.yaml", + "known_gaps": html_dir / "known-gaps.md", + } + missing = [name for name, path in required_files.items() if not path.exists()] + screenshots_dir = html_dir / "screenshots" + if not screenshots_dir.exists() or not screenshots_dir.is_dir(): + missing.append("screenshots") + if missing: + blocker_codes.append(BLOCKERS["REQUIRED_ARTIFACT_MISSING"]) + details["required_artifacts"] = {"missing": missing} + + component_coverage = load_yaml_artifact( + required_files["component_coverage"], + "component-coverage.schema.json", + "component_coverage", + details, + blocker_codes, + ) + viewport_coverage = load_yaml_artifact( + required_files["viewport_coverage"], + "viewport-coverage.schema.json", + "viewport_coverage", + details, + blocker_codes, + ) + + html_text = "" + if required_files["component_matrix_preview"].exists(): + html_text = required_files["component_matrix_preview"].read_text(encoding="utf-8", errors="replace") + + validate_component_coverage(component_coverage, html_text, details, blocker_codes) + validate_viewport_coverage(html_dir, viewport_coverage, details, blocker_codes) + validate_known_gaps(required_files["known_gaps"], details, blocker_codes) + + return emit(args.json, details, sorted(set(blocker_codes)), warnings) + + +def validate_source_intake( + html_dir: Path, + details: dict[str, Any], + blocker_codes: list[str], +) -> None: + upstream = html_dir.parent + packet = upstream / "visual-evidence-packet.md" + if not packet.exists(): + details["source_intake"] = {"missing": True} + blocker_codes.append(BLOCKERS["SOURCE_INTAKE_BLOCKED"]) + return + + text = packet.read_text(encoding="utf-8", errors="replace").lstrip("\ufeff") + match = re.match(r"\A---\s*\r?\n(.*?)\r?\n---", text, re.DOTALL) + metadata: dict[str, Any] = {} + if match and yaml is not None: + loaded = yaml.safe_load(match.group(1)) or {} + metadata = loaded if isinstance(loaded, dict) else {} + ready_gate = str(metadata.get("ready_gate") or "").strip().upper() + blockers = metadata.get("blockers") + details["source_intake"] = { + "path": str(packet), + "ready_gate": ready_gate, + "blockers": blockers, + } + if ready_gate != "PASS" or (isinstance(blockers, list) and blockers): + blocker_codes.append(BLOCKERS["SOURCE_INTAKE_BLOCKED"]) + + +def load_yaml_artifact( + path: Path, + schema_name: str, + details_key: str, + details: dict[str, Any], + blocker_codes: list[str], +) -> dict[str, Any]: + if not path.exists(): + return {} + validate_json_schema( + instance_path=path, + schema_name=schema_name, + details_key=details_key, + details=details, + blocker_codes=blocker_codes, + schema_error_code=BLOCKERS["SCHEMA_INVALID"], + ) + if yaml is None: + blocker_codes.append(BLOCKERS["SCHEMA_INVALID"]) + return {} + try: + data = yaml.safe_load(path.read_text(encoding="utf-8")) or {} + except yaml.YAMLError: + blocker_codes.append(BLOCKERS["SCHEMA_INVALID"]) + return {} + return data if isinstance(data, dict) else {} + + +def preview_ref_in_html(preview_ref: str, html_text: str) -> bool: + fragment = preview_ref.rsplit("#", 1)[-1] if "#" in preview_ref else preview_ref + fragment = fragment.strip() + if not fragment: + return False + if fragment.startswith("[") and fragment.endswith("]"): + return fragment in html_text or fragment[1:-1] in html_text + return ( + f'id="{fragment}"' in html_text + or f"id='{fragment}'" in html_text + or f'data-preview-id="{fragment}"' in html_text + or f"data-preview-id='{fragment}'" in html_text + ) + + +def validate_component_coverage( + component_coverage: dict[str, Any], + html_text: str, + details: dict[str, Any], + blocker_codes: list[str], +) -> None: + components = component_coverage.get("components", []) + missing_preview_refs: list[str] = [] + missing_visual_spec_refs: list[str] = [] + missing_records: list[str] = [] + asset_or_resource_gaps: list[str] = [] + covered_count = 0 + missing_count = 0 + + for component in components if isinstance(components, list) else []: + if not isinstance(component, dict): + continue + component_id = str(component.get("id") or "") + covered = component.get("covered", []) + missing = component.get("missing", []) + if isinstance(covered, list): + covered_count += len(covered) + for record in covered: + if not isinstance(record, dict): + continue + preview_ref = str(record.get("preview_ref") or "") + visual_spec_ref = str(record.get("visual_spec_ref") or "") + if not visual_spec_ref: + missing_visual_spec_refs.append(component_id) + if not preview_ref or not preview_ref_in_html(preview_ref, html_text): + missing_preview_refs.append(preview_ref or component_id) + if isinstance(missing, list): + missing_count += len(missing) + for record in missing: + if not isinstance(record, dict): + continue + missing_records.append(component_id) + missing_type = str(record.get("missing_type") or "") + if missing_type in {"asset", "resource", "token"}: + asset_or_resource_gaps.append(component_id) + + blockers = component_coverage.get("blockers") + details["component_coverage"] = { + "component_count": len(components) if isinstance(components, list) else 0, + "covered_count": covered_count, + "missing_count": missing_count, + "missing_preview_refs": missing_preview_refs, + "missing_visual_spec_refs": missing_visual_spec_refs, + "missing_records": missing_records, + "asset_or_resource_gaps": asset_or_resource_gaps, + "ready_gate": component_coverage.get("ready_gate"), + "blockers": blockers, + } + if not components or missing_preview_refs: + blocker_codes.append(BLOCKERS["FIGMA_NODE_COVERAGE_INCOMPLETE"]) + if missing_records or missing_visual_spec_refs: + blocker_codes.append(BLOCKERS["COMPONENT_STATE_COVERAGE_INCOMPLETE"]) + if asset_or_resource_gaps: + blocker_codes.append(BLOCKERS["ASSET_TRACEABILITY_INCOMPLETE"]) + if component_coverage.get("ready_gate") != "PASS" or (isinstance(blockers, list) and blockers): + blocker_codes.append(BLOCKERS["KNOWN_GAP_UNRESOLVED"]) + + +def validate_viewport_coverage( + html_dir: Path, + viewport_coverage: dict[str, Any], + details: dict[str, Any], + blocker_codes: list[str], +) -> None: + viewports = viewport_coverage.get("viewports", []) + missing_screenshots: list[str] = [] + uncovered_viewports: list[str] = [] + visual_diff_blocked: list[str] = [] + page_refs = 0 + + for viewport in viewports if isinstance(viewports, list) else []: + if not isinstance(viewport, dict): + continue + viewport_id = str(viewport.get("id") or "") + if viewport.get("covered") is not True: + uncovered_viewports.append(viewport_id) + refs = viewport.get("screenshot_refs", []) + if not refs: + missing_screenshots.append(viewport_id) + for ref in refs if isinstance(refs, list) else []: + ref_path = str(ref) + if ref_path and not (html_dir / ref_path).exists(): + missing_screenshots.append(ref_path) + page_refs += len(viewport.get("page_refs", []) or []) + if viewport.get("visual_diff_status") == "blocked": + visual_diff_blocked.append(viewport_id) + + blockers = viewport_coverage.get("blockers") + details["viewport_coverage"] = { + "viewport_count": len(viewports) if isinstance(viewports, list) else 0, + "missing_screenshots": missing_screenshots, + "uncovered_viewports": uncovered_viewports, + "visual_diff_blocked": visual_diff_blocked, + "page_ref_count": page_refs, + "ready_gate": viewport_coverage.get("ready_gate"), + "blockers": blockers, + } + if not viewports or uncovered_viewports or missing_screenshots: + blocker_codes.append(BLOCKERS["VIEWPORT_CAPTURE_INCOMPLETE"]) + if page_refs == 0: + blocker_codes.append(BLOCKERS["PAGE_COVERAGE_INCOMPLETE"]) + if visual_diff_blocked: + blocker_codes.append(BLOCKERS["VISUAL_DIFF_BLOCKED"]) + if viewport_coverage.get("ready_gate") != "PASS" or (isinstance(blockers, list) and blockers): + blocker_codes.append(BLOCKERS["KNOWN_GAP_UNRESOLVED"]) + + +def validate_known_gaps( + known_gaps_path: Path, + details: dict[str, Any], + blocker_codes: list[str], +) -> None: + if not known_gaps_path.exists(): + return + text = known_gaps_path.read_text(encoding="utf-8", errors="replace") + unresolved = bool(re.search(r"\b(UNRESOLVED|BLOCKED|TODO)\b", text, re.IGNORECASE)) + details["known_gaps"] = {"unresolved": unresolved} + if unresolved: + blocker_codes.append(BLOCKERS["KNOWN_GAP_UNRESOLVED"]) + + +def emit(json_mode: bool, details: dict[str, Any], blockers: list[str], warnings: list[str]) -> int: + result = { + "status": "BLOCKED" if blockers else "PASS", + "blockers": blockers, + "warnings": warnings, + "details": details, + } + if json_mode: + print(json.dumps(result, indent=2, sort_keys=True)) + else: + print(f"Visual preview readiness: {result['status']}") + if blockers: + print("Blockers:") + for blocker in blockers: + print(f"- {blocker}") + if warnings: + print("Warnings:") + for warning in warnings: + print(f"- {warning}") + return 1 if blockers else 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/extensions/intake/scripts/python/validate_structured_ir_intake.py b/extensions/intake/scripts/python/validate_visual_spec_package.py similarity index 70% rename from extensions/intake/scripts/python/validate_structured_ir_intake.py rename to extensions/intake/scripts/python/validate_visual_spec_package.py index d35c39e27f..84f70d6c37 100644 --- a/extensions/intake/scripts/python/validate_structured_ir_intake.py +++ b/extensions/intake/scripts/python/validate_visual_spec_package.py @@ -1,5 +1,5 @@ -#!/usr/bin/env python3 -"""Validate Spec Kit structured UI acceptance IR intake artifacts.""" +#!/usr/bin/env python3 +"""Validate Spec Kit visual requirements/spec structured asset package intake artifacts.""" from __future__ import annotations @@ -20,16 +20,16 @@ BLOCKERS = { - "SOURCE_INTAKE_BLOCKED": "IR_SOURCE_INTAKE_BLOCKED", - "REQUIRED_ARTIFACT_MISSING": "IR_REQUIRED_ARTIFACT_MISSING", - "SCHEMA_INVALID": "IR_SCHEMA_INVALID", - "INTAKE_INCOMPLETE": "IR_INTAKE_INCOMPLETE", - "PROVIDER_EVIDENCE_MISSING": "IR_PROVIDER_EVIDENCE_MISSING", - "PRODUCT_AMBIGUITY_UNRESOLVED": "IR_PRODUCT_AMBIGUITY_UNRESOLVED", - "ASSERTION_COVERAGE_INCOMPLETE": "IR_ASSERTION_COVERAGE_INCOMPLETE", - "LOCATOR_STRATEGY_INVALID": "IR_LOCATOR_STRATEGY_INVALID", - "DOWNSTREAM_OWNERSHIP_LEAK": "IR_DOWNSTREAM_OWNERSHIP_LEAK", - "READY_WITHOUT_EVIDENCE": "IR_READY_WITHOUT_EVIDENCE", + "SOURCE_INTAKE_BLOCKED": "VISUAL_SPEC_SOURCE_INTAKE_BLOCKED", + "REQUIRED_ARTIFACT_MISSING": "VISUAL_SPEC_REQUIRED_ARTIFACT_MISSING", + "SCHEMA_INVALID": "VISUAL_SPEC_SCHEMA_INVALID", + "INTAKE_INCOMPLETE": "VISUAL_SPEC_INTAKE_INCOMPLETE", + "PROVIDER_EVIDENCE_MISSING": "VISUAL_SPEC_PROVIDER_EVIDENCE_MISSING", + "PRODUCT_AMBIGUITY_UNRESOLVED": "VISUAL_SPEC_PRODUCT_AMBIGUITY_UNRESOLVED", + "ASSERTION_COVERAGE_INCOMPLETE": "VISUAL_SPEC_ASSERTION_COVERAGE_INCOMPLETE", + "LOCATOR_STRATEGY_INVALID": "VISUAL_SPEC_LOCATOR_STRATEGY_INVALID", + "DOWNSTREAM_OWNERSHIP_LEAK": "VISUAL_SPEC_DOWNSTREAM_OWNERSHIP_LEAK", + "READY_WITHOUT_EVIDENCE": "VISUAL_SPEC_READY_WITHOUT_EVIDENCE", } FORBIDDEN_FIELD_NAMES = { @@ -56,29 +56,29 @@ PRODUCT_AMBIGUITY_MARKERS = { "PRODUCT_AMBIGUITY", "PRODUCT_AMBIGUITY_UNRESOLVED", - "IR_PRODUCT_AMBIGUITY_UNRESOLVED", + "VISUAL_SPEC_PRODUCT_AMBIGUITY_UNRESOLVED", } PROVIDER_EVIDENCE_MARKERS = { "MISSING_PROVIDER_EVIDENCE", "PROVIDER_EVIDENCE_MISSING", - "IR_PROVIDER_EVIDENCE_MISSING", + "VISUAL_SPEC_PROVIDER_EVIDENCE_MISSING", } -LOCATOR_MARKERS = {"LOCATOR_STRATEGY_INVALID", "IR_LOCATOR_STRATEGY_INVALID"} -OWNERSHIP_MARKERS = {"DOWNSTREAM_OWNERSHIP_LEAK", "IR_DOWNSTREAM_OWNERSHIP_LEAK"} +LOCATOR_MARKERS = {"LOCATOR_STRATEGY_INVALID", "VISUAL_SPEC_LOCATOR_STRATEGY_INVALID"} +OWNERSHIP_MARKERS = {"DOWNSTREAM_OWNERSHIP_LEAK", "VISUAL_SPEC_DOWNSTREAM_OWNERSHIP_LEAK"} def main() -> int: parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument("ir_dir", help="Directory containing structured IR artifacts") + parser.add_argument("package_dir", help="Directory containing visual spec package artifacts") parser.add_argument("--json", action="store_true", help="Emit machine-readable JSON") args = parser.parse_args() - ir_dir = Path(args.ir_dir) + package_dir = Path(args.package_dir) blocker_codes: list[str] = [] warnings: list[str] = [] - details: dict[str, Any] = {"ir_dir": str(ir_dir)} + details: dict[str, Any] = {"package_dir": str(package_dir)} - if not ir_dir.exists() or not ir_dir.is_dir(): + if not package_dir.exists() or not package_dir.is_dir(): blocker_codes.extend( [ BLOCKERS["SOURCE_INTAKE_BLOCKED"], @@ -87,21 +87,21 @@ def main() -> int: ] ) return emit( - label="Structured IR", + label="Visual Spec Package", json_mode=args.json, details=details, blockers=blocker_codes, warnings=warnings, ) - validate_source_intake(ir_dir, details, blocker_codes) - ir_doc = validate_structured_ir(ir_dir, details, blocker_codes) - assertions_doc = validate_ir_assertions(ir_dir, details, blocker_codes) - validate_cross_references(ir_doc, assertions_doc, details, blocker_codes) - validate_ir_evidence_packet(ir_dir, details, blocker_codes, warnings) + validate_source_intake(package_dir, details, blocker_codes) + package_doc = validate_visual_spec_package(package_dir, details, blocker_codes) + assertions_doc = validate_visual_spec_assertions(package_dir, details, blocker_codes) + validate_cross_references(package_doc, assertions_doc, details, blocker_codes) + validate_visual_spec_evidence_packet(package_dir, details, blocker_codes, warnings) return emit( - label="Structured IR", + label="Visual Spec Package", json_mode=args.json, details=details, blockers=blocker_codes, @@ -110,11 +110,11 @@ def main() -> int: def validate_source_intake( - ir_dir: Path, + package_dir: Path, details: dict[str, Any], blocker_codes: list[str], ) -> None: - upstream = ir_dir.parent + upstream = package_dir.parent packet = upstream / "visual-evidence-packet.md" if not packet.exists(): details["source_intake"] = {"missing": True} @@ -138,32 +138,32 @@ def validate_source_intake( blocker_codes.append(BLOCKERS["SOURCE_INTAKE_BLOCKED"]) -def validate_structured_ir( - ir_dir: Path, +def validate_visual_spec_package( + package_dir: Path, details: dict[str, Any], blocker_codes: list[str], ) -> dict[str, Any]: - ir_path = ir_dir / "structured-ir.yaml" - if not ir_path.exists(): + package_path = package_dir / "visual-spec.yaml" + if not package_path.exists(): blocker_codes.append(BLOCKERS["REQUIRED_ARTIFACT_MISSING"]) - details["structured_ir"] = {"missing": True} + details["visual_spec_package"] = {"missing": True} return {} validate_json_schema( - instance_path=ir_path, - schema_name="structured-ir.schema.json", - details_key="structured_ir", + instance_path=package_path, + schema_name="visual-spec-package.schema.json", + details_key="visual_spec_package", details=details, blocker_codes=blocker_codes, schema_error_code=BLOCKERS["SCHEMA_INVALID"], ) - ir_doc = load_yaml(ir_path) - items = ir_doc.get("items") + package_doc = load_yaml(package_path) + items = package_doc.get("items") if not isinstance(items, list): items = [] - declared_count = parse_count(ir_doc.get("ir_item_count"), len(items)) + declared_count = parse_count(package_doc.get("visual_spec_item_count"), len(items)) item_errors: list[dict[str, Any]] = [] invalid_locators: list[str] = [] ownership_leaks: list[str] = [] @@ -172,32 +172,35 @@ def validate_structured_ir( blocker_lint_items: list[str] = [] has_ready_item = False - details["structured_ir"] = { - "ir_complete": ir_doc.get("ir_complete"), - "source_refs_complete": ir_doc.get("source_refs_complete"), - "provider_evidence_complete": ir_doc.get("provider_evidence_complete"), - "product_ambiguities_recorded": ir_doc.get("product_ambiguities_recorded"), - "downstream_ownership_free": ir_doc.get("downstream_ownership_free"), - "ir_item_count": declared_count, + details["visual_spec_package"] = { + "visual_spec_package_complete": package_doc.get("visual_spec_package_complete"), + "source_refs_complete": package_doc.get("source_refs_complete"), + "provider_evidence_complete": package_doc.get("provider_evidence_complete"), + "product_ambiguities_recorded": package_doc.get("product_ambiguities_recorded"), + "resources_traceable_to_design_source": package_doc.get("resources_traceable_to_design_source"), + "downstream_ownership_free": package_doc.get("downstream_ownership_free"), + "visual_spec_item_count": declared_count, "actual_item_count": len(items), - "blocker_lint_errors": ir_doc.get("blocker_lint_errors"), + "blocker_lint_errors": package_doc.get("blocker_lint_errors"), } - if not is_truthy(ir_doc.get("ir_complete")) or declared_count <= 0: + if not is_truthy(package_doc.get("visual_spec_package_complete")) or declared_count <= 0: blocker_codes.append(BLOCKERS["INTAKE_INCOMPLETE"]) if declared_count != len(items): blocker_codes.append(BLOCKERS["INTAKE_INCOMPLETE"]) - if not is_truthy(ir_doc.get("source_refs_complete")): + if not is_truthy(package_doc.get("source_refs_complete")): blocker_codes.append(BLOCKERS["PROVIDER_EVIDENCE_MISSING"]) - if not is_truthy(ir_doc.get("provider_evidence_complete")): + if not is_truthy(package_doc.get("provider_evidence_complete")): blocker_codes.append(BLOCKERS["PROVIDER_EVIDENCE_MISSING"]) - if not is_truthy(ir_doc.get("product_ambiguities_recorded")): + if not is_truthy(package_doc.get("resources_traceable_to_design_source")): + blocker_codes.append(BLOCKERS["PROVIDER_EVIDENCE_MISSING"]) + if not is_truthy(package_doc.get("product_ambiguities_recorded")): blocker_codes.append(BLOCKERS["PRODUCT_AMBIGUITY_UNRESOLVED"]) - if not is_truthy(ir_doc.get("downstream_ownership_free")): + if not is_truthy(package_doc.get("downstream_ownership_free")): blocker_codes.append(BLOCKERS["DOWNSTREAM_OWNERSHIP_LEAK"]) - if non_empty(ir_doc.get("product_ambiguities")): + if non_empty(package_doc.get("product_ambiguities")): blocker_codes.append(BLOCKERS["PRODUCT_AMBIGUITY_UNRESOLVED"]) - if non_empty(ir_doc.get("blocker_lint_errors")): + if non_empty(package_doc.get("blocker_lint_errors")): blocker_codes.append(BLOCKERS["INTAKE_INCOMPLETE"]) for index, item in enumerate(items): @@ -259,12 +262,12 @@ def validate_structured_ir( if has_downstream_ownership_leak(item): ownership_leaks.append(item_id) - details["structured_ir"]["item_errors"] = item_errors - details["structured_ir"]["provider_evidence_gaps"] = sorted(set(provider_evidence_gaps)) - details["structured_ir"]["product_ambiguity_gaps"] = sorted(set(product_ambiguity_gaps)) - details["structured_ir"]["invalid_locators"] = sorted(set(invalid_locators)) - details["structured_ir"]["ownership_leaks"] = sorted(set(ownership_leaks)) - details["structured_ir"]["blocker_lint_items"] = sorted(set(blocker_lint_items)) + details["visual_spec_package"]["item_errors"] = item_errors + details["visual_spec_package"]["provider_evidence_gaps"] = sorted(set(provider_evidence_gaps)) + details["visual_spec_package"]["product_ambiguity_gaps"] = sorted(set(product_ambiguity_gaps)) + details["visual_spec_package"]["invalid_locators"] = sorted(set(invalid_locators)) + details["visual_spec_package"]["ownership_leaks"] = sorted(set(ownership_leaks)) + details["visual_spec_package"]["blocker_lint_items"] = sorted(set(blocker_lint_items)) if item_errors or blocker_lint_items or not has_ready_item: blocker_codes.append(BLOCKERS["INTAKE_INCOMPLETE"]) @@ -277,24 +280,24 @@ def validate_structured_ir( if ownership_leaks: blocker_codes.append(BLOCKERS["DOWNSTREAM_OWNERSHIP_LEAK"]) - return ir_doc + return package_doc -def validate_ir_assertions( - ir_dir: Path, +def validate_visual_spec_assertions( + package_dir: Path, details: dict[str, Any], blocker_codes: list[str], ) -> dict[str, Any]: - assertions_path = ir_dir / "ir-assertions.yaml" + assertions_path = package_dir / "visual-spec-assertions.yaml" if not assertions_path.exists(): blocker_codes.append(BLOCKERS["REQUIRED_ARTIFACT_MISSING"]) - details["ir_assertions"] = {"missing": True} + details["visual_spec_assertions"] = {"missing": True} return {} validate_json_schema( instance_path=assertions_path, - schema_name="ir-assertions.schema.json", - details_key="ir_assertions", + schema_name="visual-spec-assertions.schema.json", + details_key="visual_spec_assertions", details=details, blocker_codes=blocker_codes, schema_error_code=BLOCKERS["SCHEMA_INVALID"], @@ -313,7 +316,7 @@ def validate_ir_assertions( blocker_lint_assertions: list[str] = [] ready_ci_count = 0 - details["ir_assertions"] = { + details["visual_spec_assertions"] = { "assertions_complete": assertions_doc.get("assertions_complete"), "ci_assertions_complete": assertions_doc.get("ci_assertions_complete"), "assertion_count": declared_count, @@ -338,7 +341,7 @@ def validate_ir_assertions( assertion_id = str(assertion.get("id") or f"assertion-{index}") required_non_empty = [ "id", - "ir_refs", + "visual_spec_refs", "assertion_type", "acceptance_intent", "expected", @@ -380,12 +383,12 @@ def validate_ir_assertions( if has_downstream_ownership_leak(assertion): blocker_codes.append(BLOCKERS["DOWNSTREAM_OWNERSHIP_LEAK"]) - details["ir_assertions"]["assertion_errors"] = assertion_errors - details["ir_assertions"]["ready_ci_assertion_count"] = ready_ci_count - details["ir_assertions"]["non_ci_ready_assertions"] = non_ci_assertions - details["ir_assertions"]["provider_evidence_gaps"] = sorted(set(provider_evidence_gaps)) - details["ir_assertions"]["product_ambiguity_gaps"] = sorted(set(product_ambiguity_gaps)) - details["ir_assertions"]["blocker_lint_assertions"] = sorted(set(blocker_lint_assertions)) + details["visual_spec_assertions"]["assertion_errors"] = assertion_errors + details["visual_spec_assertions"]["ready_ci_assertion_count"] = ready_ci_count + details["visual_spec_assertions"]["non_ci_ready_assertions"] = non_ci_assertions + details["visual_spec_assertions"]["provider_evidence_gaps"] = sorted(set(provider_evidence_gaps)) + details["visual_spec_assertions"]["product_ambiguity_gaps"] = sorted(set(product_ambiguity_gaps)) + details["visual_spec_assertions"]["blocker_lint_assertions"] = sorted(set(blocker_lint_assertions)) if assertion_errors or blocker_lint_assertions or non_ci_assertions or ready_ci_count == 0: blocker_codes.append(BLOCKERS["ASSERTION_COVERAGE_INCOMPLETE"]) @@ -398,49 +401,49 @@ def validate_ir_assertions( def validate_cross_references( - ir_doc: dict[str, Any], + package_doc: dict[str, Any], assertions_doc: dict[str, Any], details: dict[str, Any], blocker_codes: list[str], ) -> None: - items = ir_doc.get("items") + items = package_doc.get("items") assertions = assertions_doc.get("assertions") if not isinstance(items, list) or not isinstance(assertions, list): return - ir_ids = {item.get("id") for item in items if isinstance(item, dict)} + visual_spec_ids = {item.get("id") for item in items if isinstance(item, dict)} missing_refs: list[str] = [] for assertion in assertions: if not isinstance(assertion, dict): continue - for ir_ref in assertion.get("ir_refs") or []: - if ir_ref not in ir_ids: - missing_refs.append(str(ir_ref)) + for visual_spec_ref in assertion.get("visual_spec_refs") or []: + if visual_spec_ref not in visual_spec_ids: + missing_refs.append(str(visual_spec_ref)) - details["ir_cross_refs"] = { - "ir_item_ids": sorted(str(item_id) for item_id in ir_ids if item_id), - "missing_ir_refs": sorted(set(missing_refs)), + details["visual_spec_cross_refs"] = { + "visual_spec_item_ids": sorted(str(item_id) for item_id in visual_spec_ids if item_id), + "missing_visual_spec_refs": sorted(set(missing_refs)), } if missing_refs: blocker_codes.append(BLOCKERS["ASSERTION_COVERAGE_INCOMPLETE"]) -def validate_ir_evidence_packet( - ir_dir: Path, +def validate_visual_spec_evidence_packet( + package_dir: Path, details: dict[str, Any], blocker_codes: list[str], warnings: list[str], ) -> None: - evidence_path = ir_dir / "ir-evidence-packet.md" + evidence_path = package_dir / "visual-spec-evidence-packet.md" if not evidence_path.exists(): blocker_codes.append(BLOCKERS["REQUIRED_ARTIFACT_MISSING"]) return - details["ir_evidence_packet"] = evidence_path.name + details["visual_spec_evidence_packet"] = evidence_path.name packet_status = parse_evidence_packet_status( evidence_path.read_text(encoding="utf-8", errors="replace") ) - details["ir_evidence_packet_metadata"] = packet_status["metadata"] + details["visual_spec_evidence_packet_metadata"] = packet_status["metadata"] if packet_status["warnings"]: warnings.extend(packet_status["warnings"]) if packet_status["errors"]: diff --git a/extensions/intake/templates/intake-structured-ir-contract.md b/extensions/intake/templates/intake-structured-ir-contract.md deleted file mode 100644 index 0b40adcb49..0000000000 --- a/extensions/intake/templates/intake-structured-ir-contract.md +++ /dev/null @@ -1,120 +0,0 @@ -# Structured IR Intake Contract - -Required structured UI acceptance IR artifacts and readiness gates. Runtime agents or external intake tools derive CI-friendly UI acceptance facts from traceable visual/design evidence before downstream workflows choose their own test runner, selectors, requirement IDs, or implementation ownership. - -Structured IR does not generate requirements, tasks, code component names, implementation-owned selectors, or product semantics. It preserves source-backed DOM, ARIA, token, state, relation, locator-strategy, and assertion facts that can be consumed by low-cost CI checks. - -## Artifact Family - -Default directory: - -```text -specs//intake/visual-design/structured-ir/ -``` - -Required files: - -- `structured-ir.yaml` -- `ir-assertions.yaml` -- `ir-evidence-packet.md` - -Optional enhancement refs may point to `figma2htmlssot/visual-spec.html`, `figma-map.json`, screenshots, or visual diff reports, but HTML SSOT and screenshots are not the primary acceptance substrate. - -## Source Boundary - -Structured IR is downstream of source evidence and upstream of implementation tests: - -1. Visual/design intake records source-backed facts, limitations, Figma metadata, and visual requirements. -2. HTML SSOT records runnable visual preview and screenshot comparison surfaces when available. -3. Structured IR records deterministic acceptance facts suitable for CI. - -If source evidence is missing, truncated, contradictory, or blocked, structured IR must record `IR_PROVIDER_EVIDENCE_MISSING`. If the source is clear but product behavior is ambiguous, it must record `IR_PRODUCT_AMBIGUITY_UNRESOLVED`. Do not collapse these into one generic gap. - -## `structured-ir.yaml` - -The file must normalize UI acceptance facts into provider-neutral records. - -Top-level fields: - -- ir_complete: true|false -- ir_item_count: integer -- source_refs_complete: true|false -- provider_evidence_complete: true|false -- product_ambiguities_recorded: true|false -- downstream_ownership_free: true|false -- product_ambiguities: array -- blocker_lint_errors: array -- items: array - -Each item must include: - -- id: stable `IR-*` identifier owned by intake -- source_refs: preserved source evidence refs -- des_refs: optional design evidence source refs -- visual_requirement_refs: optional refs to `visual-requirements.yaml` -- html_ssot_refs: optional enhancement refs only -- page, region, role, state, viewport -- locator: provider-neutral strategy, value, and `implementation_owned: false` -- expectations: DOM, ARIA, design token, state, content, or relation facts -- acceptance_intent: what low-cost check this fact supports -- evidence_type: observed|inferred|candidate|unsupported|missing|out_of_scope -- confidence: low|medium|high -- status: ready|blocked|reference_only|out_of_scope -- blockers: array - -Locator strategies must not be implementation-owned CSS selectors, XPath, generated class names, or downstream test IDs. Candidate test IDs may be recorded only as `test-id-candidate` and must remain intake-owned guidance, not implementation ownership. - -## `ir-assertions.yaml` - -The file must describe low-cost assertions over IR items. - -Top-level fields: - -- assertions_complete: true|false -- assertion_count: integer -- ci_assertions_complete: true|false -- blocker_lint_errors: array -- assertions: array - -Each assertion must include: - -- id: stable `IRA-*` identifier owned by intake -- ir_refs: one or more `IR-*` refs -- assertion_type: visible|hidden|enabled|disabled|contains_text|role|aria|token|relation|state|viewport -- acceptance_intent -- expected -- evidence_refs -- ci_suitability: ci_low_cost|manual_review|blocked -- status: ready|blocked|reference_only|out_of_scope -- blockers - -Ready assertions should use `ci_suitability: ci_low_cost`. Manual review and blocked assertions are allowed as explicit evidence, but they cannot satisfy CI readiness. - -## Readiness - -Structured IR intake is ready only when: - -- upstream visual-design intake readiness is PASS -- required IR artifacts exist -- both schemas validate -- item and assertion counts match their arrays -- every ready assertion references an existing IR item -- source refs and provider evidence are complete -- product ambiguities are explicitly recorded and unresolved ambiguity does not masquerade as missing provider evidence -- locators are provider-neutral and not implementation-owned -- no downstream requirement IDs, tasks, code component names, implementation-owned selectors, or product semantics leak into the IR -- at least one ready `ci_low_cost` assertion exists -- `ir-evidence-packet.md` front matter reports `ready_gate: PASS` with no blockers - -## Blocker Codes - -- `IR_SOURCE_INTAKE_BLOCKED` -- `IR_REQUIRED_ARTIFACT_MISSING` -- `IR_SCHEMA_INVALID` -- `IR_INTAKE_INCOMPLETE` -- `IR_PROVIDER_EVIDENCE_MISSING` -- `IR_PRODUCT_AMBIGUITY_UNRESOLVED` -- `IR_ASSERTION_COVERAGE_INCOMPLETE` -- `IR_LOCATOR_STRATEGY_INVALID` -- `IR_DOWNSTREAM_OWNERSHIP_LEAK` -- `IR_READY_WITHOUT_EVIDENCE` diff --git a/extensions/intake/templates/intake-structured-ir-evidence-packet-template.md b/extensions/intake/templates/intake-structured-ir-evidence-packet-template.md deleted file mode 100644 index a4eb669973..0000000000 --- a/extensions/intake/templates/intake-structured-ir-evidence-packet-template.md +++ /dev/null @@ -1,41 +0,0 @@ ---- -ready_gate: BLOCKED -blockers: [] -source_ref_count: 0 -extracted_item_count: 0 -generated_at: "" ---- - -# Structured IR Evidence Packet - -Purpose: summarize structured UI acceptance IR readiness while preserving enough traceability for downstream CI workflows to consume deterministic DOM, ARIA, token, state, relation, locator-strategy, and assertion facts. - -This packet is a human-readable readiness summary. Machine-readable UI acceptance facts are recorded in `structured-ir.yaml` and `ir-assertions.yaml` and validated by `templates/schemas/structured-ir.schema.json` and `templates/schemas/ir-assertions.schema.json`. This packet does not define downstream requirement IDs, implementation tasks, code component names, implementation-owned selectors, or final product behavior. - -## Source Boundary - -- Visual/design intake directory: -- Visual/design readiness: -- HTML SSOT enhancement refs, if used: -- Screenshot or visual diff refs, if used: - -## IR Summary - -- structured-ir.yaml: -- ir-assertions.yaml: -- structured IR item count: -- assertion count: -- CI-low-cost assertion count: - -## Evidence Separation - -- Missing provider evidence: -- Product ambiguities: -- Accepted out-of-scope surfaces: -- Manual-review-only assertions: - -## Readiness - -- ready_gate: -- blockers: -- next corrective action: diff --git a/extensions/intake/templates/intake-visual-previews-contract.md b/extensions/intake/templates/intake-visual-previews-contract.md new file mode 100644 index 0000000000..d8641b7f51 --- /dev/null +++ b/extensions/intake/templates/intake-visual-previews-contract.md @@ -0,0 +1,126 @@ +# Visual Preview Coverage Contract + +Required component matrix preview helper artifacts and readiness gates. Preview coverage artifacts help reviewers inspect whether design-source components, states, variants, resources, content samples, and viewports were enumerated before downstream implementation, but they are not the target visual requirements/spec asset package. + +Preview coverage does not generate requirements, implementation HTML, product semantics, downstream-owned selectors, tasks, code component names, or design tokens. It preserves source-backed coverage evidence that points back to design-source refs and forward to `visual-spec-package/` records. + +## Artifact Family + +Default directory: + +```text +specs//intake/visual-design/previews/ +``` + +Required files: + +- `component-matrix-preview.html` +- `component-coverage.yaml` +- `viewport-coverage.yaml` +- `known-gaps.md` +- `screenshots/` + +## Source Boundary + +Preview coverage is downstream of visual-design intake and adjacent to the visual spec package: + +1. Visual-design intake records source-backed facts, limitations, Figma metadata, node inventory, and visual requirements. +2. Visual Spec Package records the target structured visual requirements/spec facts. +3. Preview coverage records reviewer-oriented matrix surfaces and machine-readable coverage evidence. + +If Figma or design-source evidence is missing, truncated, contradictory, or blocked, preview coverage must record a `VISUAL_PREVIEW_*` blocker and keep the affected coverage cell missing. Do not silently complete a missing state, variant, resource, or viewport in preview HTML. + +## `component-matrix-preview.html` + +The file is a human-review panel only. Each preview cell should expose stable anchors such as `id` or `data-preview-id` so `component-coverage.yaml` can reference the cell. + +The preview panel may display: + +- component sets and component instances +- variant props +- states +- size, density, and theme dimensions +- content samples, including long copy, empty, overflow, and error-like visual states when source-backed +- viewport-specific snapshots or links +- missing, blocked, and out-of-scope labels + +The preview panel must not define product semantics, downstream component names, implementation selectors, design tokens, or source-backed facts that are absent from the design source. + +## `component-coverage.yaml` + +The file is the machine-readable component coverage evidence. + +Top-level fields: + +- ready_gate: PASS|BLOCKED +- blockers: array of `VISUAL_PREVIEW_*` or allowed upstream `VISUAL_SPEC_*` blocker codes +- components: array + +Each component must include: + +- id +- source_ref +- name +- required_dimensions +- covered +- missing + +Each covered record must include: + +- visual_spec_ref +- preview_ref +- optional source_ref +- optional screenshot_refs +- dimension values matching the component's required dimensions when applicable + +Each missing record must include: + +- missing_type: state|variant|viewport|resource|asset|token|screenshot|visual_diff|source_evidence|visual_spec_ref|preview_ref +- reason +- blocker + +## `viewport-coverage.yaml` + +The file is the machine-readable viewport coverage evidence. + +Each viewport record must include: + +- id +- width +- height +- covered +- source_refs +- visual_spec_refs +- page_refs +- screenshot_refs +- visual_diff_status: pass|blocked|not_applicable + +Missing viewport evidence must stay explicit in `missing` records or top-level blockers. + +## Readiness + +Preview coverage is ready only when: + +- upstream visual-design intake readiness is PASS +- required preview artifacts exist +- `component-coverage.yaml` validates against `component-coverage.schema.json` +- `viewport-coverage.yaml` validates against `viewport-coverage.schema.json` +- every covered component record has a `visual_spec_ref` +- every covered component record has a `preview_ref` that resolves inside `component-matrix-preview.html` +- no missing record remains for required component states, variants, resources, assets, tokens, screenshots, visual diffs, source evidence, visual spec refs, or preview refs +- every viewport record is covered and has existing screenshot refs +- at least one viewport has page refs +- `known-gaps.md` has no unresolved `BLOCKED`, `UNRESOLVED`, or `TODO` marker + +## Blocker Codes + +- `VISUAL_PREVIEW_SOURCE_INTAKE_BLOCKED` +- `VISUAL_PREVIEW_REQUIRED_ARTIFACT_MISSING` +- `VISUAL_PREVIEW_SCHEMA_INVALID` +- `VISUAL_PREVIEW_FIGMA_NODE_COVERAGE_INCOMPLETE` +- `VISUAL_PREVIEW_COMPONENT_STATE_COVERAGE_INCOMPLETE` +- `VISUAL_PREVIEW_PAGE_COVERAGE_INCOMPLETE` +- `VISUAL_PREVIEW_ASSET_TRACEABILITY_INCOMPLETE` +- `VISUAL_PREVIEW_VIEWPORT_CAPTURE_INCOMPLETE` +- `VISUAL_PREVIEW_VISUAL_DIFF_BLOCKED` +- `VISUAL_PREVIEW_KNOWN_GAP_UNRESOLVED` diff --git a/extensions/intake/templates/intake-visual-spec-package-contract.md b/extensions/intake/templates/intake-visual-spec-package-contract.md new file mode 100644 index 0000000000..c329723d9e --- /dev/null +++ b/extensions/intake/templates/intake-visual-spec-package-contract.md @@ -0,0 +1,122 @@ +# Visual Spec Package Contract + +Required visual requirements/spec structured asset package artifacts and readiness gates. Runtime agents or external intake tools derive high-fidelity UI delivery facts from traceable visual/design evidence before downstream workflows choose their own test runner, selectors, requirement IDs, or implementation ownership. + +Visual Spec Package does not generate requirements, tasks, code component names, implementation-owned selectors, or product semantics. It preserves source-backed DOM, ARIA, token, state, relation, locator-strategy, and assertion facts that can be consumed by low-cost CI checks. + +## Artifact Family + +Default directory: + +```text +specs//intake/visual-design/visual-spec-package/ +``` + +Required files: + +- `visual-spec.yaml` +- `visual-spec-assertions.yaml` +- `visual-spec-evidence-packet.md` + +Optional helper refs may point to `previews/component-matrix-preview.html`, `previews/component-coverage.yaml`, `previews/viewport-coverage.yaml`, screenshots, or visual diff reports, but preview panels and screenshots are not the target deliverable. + +## Source Boundary + +Visual Spec Package is downstream of source evidence and upstream of implementation tests: + +1. Visual/design intake records source-backed facts, limitations, Figma metadata, and visual requirements. +2. Preview helpers record component matrix review, component coverage, viewport coverage, and screenshot comparison evidence when useful. +3. Visual Spec Package records deterministic visual requirements/spec facts suitable for downstream implementation and CI. + +If source evidence is missing, truncated, contradictory, or blocked, visual spec package must record `VISUAL_SPEC_PROVIDER_EVIDENCE_MISSING`. If the source is clear but product behavior is ambiguous, it must record `VISUAL_SPEC_PRODUCT_AMBIGUITY_UNRESOLVED`. Do not collapse these into one generic gap. + +## `visual-spec.yaml` + +The file must normalize UI acceptance facts into provider-neutral records. + +Top-level fields: + +- visual_spec_package_complete: true|false +- visual_spec_item_count: integer +- source_refs_complete: true|false +- provider_evidence_complete: true|false +- resources_traceable_to_design_source: true|false +- product_ambiguities_recorded: true|false +- downstream_ownership_free: true|false +- product_ambiguities: array +- blocker_lint_errors: array +- items: array + +Each item must include: + +- id: stable `VS-*` identifier owned by intake +- source_refs: preserved source evidence refs +- des_refs: optional design evidence source refs +- visual_requirement_refs: optional refs to `visual-requirements.yaml` +- preview_refs: optional refs to `previews/component-matrix-preview.html`, `previews/component-coverage.yaml`, `previews/viewport-coverage.yaml`, screenshots, or diff evidence +- page, region, role, state, viewport +- locator: provider-neutral strategy, value, and `implementation_owned: false` +- expectations: DOM, ARIA, design token, state, content, or relation facts +- acceptance_intent: what low-cost check this fact supports +- evidence_type: observed|inferred|candidate|unsupported|missing|out_of_scope +- confidence: low|medium|high +- status: ready|blocked|reference_only|out_of_scope +- blockers: array + +Locator strategies must not be implementation-owned CSS selectors, XPath, generated class names, or downstream test IDs. Candidate test IDs may be recorded only as `test-id-candidate` and must remain intake-owned guidance, not implementation ownership. + +## `visual-spec-assertions.yaml` + +The file must describe low-cost assertions over visual spec items. + +Top-level fields: + +- assertions_complete: true|false +- assertion_count: integer +- ci_assertions_complete: true|false +- blocker_lint_errors: array +- assertions: array + +Each assertion must include: + +- id: stable `VSA-*` identifier owned by intake +- visual_spec_refs: one or more `VS-*` refs +- assertion_type: visible|hidden|enabled|disabled|contains_text|role|aria|token|relation|state|viewport +- acceptance_intent +- expected +- evidence_refs +- ci_suitability: ci_low_cost|manual_review|blocked +- status: ready|blocked|reference_only|out_of_scope +- blockers + +Ready assertions should use `ci_suitability: ci_low_cost`. Manual review and blocked assertions are allowed as explicit evidence, but they cannot satisfy CI readiness. + +## Readiness + +Visual Spec Package intake is ready only when: + +- upstream visual-design intake readiness is PASS +- required visual spec package artifacts exist +- both schemas validate +- item and assertion counts match their arrays +- every ready assertion references an existing visual spec item +- source refs and provider evidence are complete +- implementation resources, images, and token refs are traceable to the design source; for Figma sources, they must trace back to Figma refs +- product ambiguities are explicitly recorded and unresolved ambiguity does not masquerade as missing provider evidence +- locators are provider-neutral and not implementation-owned +- no downstream requirement IDs, tasks, code component names, implementation-owned selectors, or product semantics leak into the visual spec package +- at least one ready `ci_low_cost` assertion exists +- `visual-spec-evidence-packet.md` front matter reports `ready_gate: PASS` with no blockers + +## Blocker Codes + +- `VISUAL_SPEC_SOURCE_INTAKE_BLOCKED` +- `VISUAL_SPEC_REQUIRED_ARTIFACT_MISSING` +- `VISUAL_SPEC_SCHEMA_INVALID` +- `VISUAL_SPEC_INTAKE_INCOMPLETE` +- `VISUAL_SPEC_PROVIDER_EVIDENCE_MISSING` +- `VISUAL_SPEC_PRODUCT_AMBIGUITY_UNRESOLVED` +- `VISUAL_SPEC_ASSERTION_COVERAGE_INCOMPLETE` +- `VISUAL_SPEC_LOCATOR_STRATEGY_INVALID` +- `VISUAL_SPEC_DOWNSTREAM_OWNERSHIP_LEAK` +- `VISUAL_SPEC_READY_WITHOUT_EVIDENCE` diff --git a/extensions/intake/templates/intake-visual-spec-package-evidence-packet-template.md b/extensions/intake/templates/intake-visual-spec-package-evidence-packet-template.md new file mode 100644 index 0000000000..7412986caf --- /dev/null +++ b/extensions/intake/templates/intake-visual-spec-package-evidence-packet-template.md @@ -0,0 +1,42 @@ +--- +ready_gate: BLOCKED +blockers: [] +source_ref_count: 0 +extracted_item_count: 0 +generated_at: "" +--- + +# Visual Spec Package Evidence Packet + +Purpose: summarize visual requirements/spec structured asset package readiness while preserving enough traceability for downstream CI workflows to consume deterministic DOM, ARIA, token, state, relation, locator-strategy, and assertion facts. + +This packet is a human-readable readiness summary. Machine-readable UI acceptance facts are recorded in `visual-spec.yaml` and `visual-spec-assertions.yaml` and validated by `templates/schemas/visual-spec-package.schema.json` and `templates/schemas/visual-spec-assertions.schema.json`. This packet does not define downstream requirement IDs, implementation tasks, code component names, implementation-owned selectors, or final product behavior. + +## Source Boundary + +- Visual/design intake directory: +- Visual/design readiness: +- HTML preview/helper refs, if used: +- Screenshot or visual diff refs, if used: + +## Visual Spec Package Summary + +- visual-spec.yaml: +- visual-spec-assertions.yaml: +- visual spec package item count: +- assertion count: +- CI-low-cost assertion count: + +## Evidence Separation + +- Missing provider evidence: +- Resource refs not traceable to design source: +- Product ambiguities: +- Accepted out-of-scope surfaces: +- Manual-review-only assertions: + +## Readiness + +- ready_gate: +- blockers: +- next corrective action: diff --git a/extensions/intake/templates/schemas/assets-manifest.schema.json b/extensions/intake/templates/schemas/assets-manifest.schema.json deleted file mode 100644 index c5803e4c56..0000000000 --- a/extensions/intake/templates/schemas/assets-manifest.schema.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://spec-kit-intake.local/schemas/assets-manifest.schema.json", - "title": "HTML SSOT Assets Manifest", - "type": "object", - "required": ["assets"], - "properties": { - "assets": { - "type": "array", - "items": { - "type": "object", - "required": ["id", "path", "role", "source_refs", "sha256"], - "properties": { - "id": { "type": "string", "minLength": 1 }, - "path": { "type": "string", "minLength": 1 }, - "role": { "type": "string", "minLength": 1 }, - "source_refs": { - "type": "array", - "minItems": 1, - "items": { "type": "string", "minLength": 1 } - }, - "sha256": { "type": "string", "minLength": 1 } - }, - "additionalProperties": true - } - } - }, - "additionalProperties": true -} diff --git a/extensions/intake/templates/schemas/component-coverage.schema.json b/extensions/intake/templates/schemas/component-coverage.schema.json new file mode 100644 index 0000000000..236fe44e53 --- /dev/null +++ b/extensions/intake/templates/schemas/component-coverage.schema.json @@ -0,0 +1,100 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://spec-kit-intake.local/schemas/component-coverage.schema.json", + "title": "Visual Design Component Coverage", + "type": "object", + "required": ["ready_gate", "blockers", "components"], + "properties": { + "ready_gate": { "enum": ["PASS", "BLOCKED"] }, + "blockers": { + "type": "array", + "items": { "$ref": "#/$defs/blocker_code" } + }, + "components": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "required": ["id", "source_ref", "name", "required_dimensions", "covered", "missing"], + "properties": { + "id": { "type": "string", "minLength": 1 }, + "source_ref": { "type": "string", "minLength": 1 }, + "name": { "type": "string", "minLength": 1 }, + "required_dimensions": { + "type": "object", + "minProperties": 1, + "additionalProperties": { + "type": "array", + "minItems": 1, + "items": { "type": "string", "minLength": 1 } + } + }, + "covered": { + "type": "array", + "items": { + "type": "object", + "required": ["visual_spec_ref", "preview_ref"], + "properties": { + "visual_spec_ref": { "type": "string", "minLength": 1 }, + "preview_ref": { "type": "string", "minLength": 1 }, + "source_ref": { "type": "string", "minLength": 1 }, + "screenshot_refs": { + "type": "array", + "items": { "type": "string", "minLength": 1 } + } + }, + "additionalProperties": true + } + }, + "missing": { + "type": "array", + "items": { + "type": "object", + "required": ["missing_type", "reason", "blocker"], + "properties": { + "missing_type": { + "enum": [ + "state", + "variant", + "viewport", + "resource", + "asset", + "token", + "screenshot", + "visual_diff", + "source_evidence", + "visual_spec_ref", + "preview_ref" + ] + }, + "reason": { "type": "string", "minLength": 1 }, + "blocker": { "$ref": "#/$defs/blocker_code" } + }, + "additionalProperties": true + } + } + }, + "additionalProperties": true + } + } + }, + "$defs": { + "blocker_code": { + "enum": [ + "VISUAL_PREVIEW_SOURCE_INTAKE_BLOCKED", + "VISUAL_PREVIEW_REQUIRED_ARTIFACT_MISSING", + "VISUAL_PREVIEW_FIGMA_NODE_COVERAGE_INCOMPLETE", + "VISUAL_PREVIEW_COMPONENT_STATE_COVERAGE_INCOMPLETE", + "VISUAL_PREVIEW_PAGE_COVERAGE_INCOMPLETE", + "VISUAL_PREVIEW_ASSET_TRACEABILITY_INCOMPLETE", + "VISUAL_PREVIEW_VIEWPORT_CAPTURE_INCOMPLETE", + "VISUAL_PREVIEW_VISUAL_DIFF_BLOCKED", + "VISUAL_PREVIEW_KNOWN_GAP_UNRESOLVED", + "VISUAL_PREVIEW_SCHEMA_INVALID", + "VISUAL_SPEC_PROVIDER_EVIDENCE_MISSING", + "VISUAL_SPEC_ASSERTION_COVERAGE_INCOMPLETE" + ] + } + }, + "additionalProperties": true +} diff --git a/extensions/intake/templates/schemas/figma-map.schema.json b/extensions/intake/templates/schemas/figma-map.schema.json deleted file mode 100644 index c92b35f1b3..0000000000 --- a/extensions/intake/templates/schemas/figma-map.schema.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://spec-kit-intake.local/schemas/figma-map.schema.json", - "title": "HTML SSOT Figma Map", - "type": "object", - "required": ["source_intake_refs", "mappings"], - "properties": { - "source_intake_refs": { - "type": "array", - "minItems": 1, - "items": { "type": "string", "minLength": 1 } - }, - "mappings": { - "type": "array", - "minItems": 1, - "items": { - "type": "object", - "required": [ - "figma_node_id", - "selector", - "acceptance_unit", - "required", - "states", - "viewports", - "content_sample", - "container_constraint" - ], - "properties": { - "figma_node_id": { "type": "string", "minLength": 1 }, - "selector": { "type": "string", "minLength": 1 }, - "acceptance_unit": { "enum": ["component-state", "section", "page"] }, - "required": { "type": "boolean" }, - "states": { - "type": "array", - "minItems": 1, - "items": { "type": "string", "minLength": 1 } - }, - "viewports": { - "type": "array", - "minItems": 1, - "items": { "type": "string", "minLength": 1 } - }, - "content_sample": { "type": "string", "minLength": 1 }, - "container_constraint": { "type": "string", "minLength": 1 }, - "accepted_exclusion": { "type": "boolean" }, - "exclusion_reason": { "type": "string" } - }, - "additionalProperties": true - } - } - }, - "additionalProperties": true -} diff --git a/extensions/intake/templates/schemas/html-ssot-coverage.schema.json b/extensions/intake/templates/schemas/html-ssot-coverage.schema.json deleted file mode 100644 index c7bdc62118..0000000000 --- a/extensions/intake/templates/schemas/html-ssot-coverage.schema.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://spec-kit-intake.local/schemas/html-ssot-coverage.schema.json", - "title": "HTML SSOT Coverage", - "type": "object", - "required": [ - "ready_gate", - "blockers", - "required_nodes_total", - "required_nodes_covered", - "component_state_coverage_complete", - "page_coverage_complete", - "asset_traceability_complete", - "viewport_capture_complete", - "visual_diff_status", - "accepted_exceptions" - ], - "properties": { - "ready_gate": { "enum": ["PASS", "BLOCKED"] }, - "blockers": { - "type": "array", - "items": { "$ref": "#/$defs/blocker_code" } - }, - "required_nodes_total": { "type": "integer", "minimum": 0 }, - "required_nodes_covered": { "type": "integer", "minimum": 0 }, - "component_state_coverage_complete": { "type": "boolean" }, - "page_coverage_complete": { "type": "boolean" }, - "asset_traceability_complete": { "type": "boolean" }, - "viewport_capture_complete": { "type": "boolean" }, - "visual_diff_status": { "enum": ["pass", "blocked", "not_applicable"] }, - "accepted_exceptions": { "type": "array" } - }, - "$defs": { - "blocker_code": { - "enum": [ - "HTML_SSOT_SOURCE_INTAKE_BLOCKED", - "HTML_SSOT_REQUIRED_ARTIFACT_MISSING", - "HTML_SSOT_FIGMA_NODE_COVERAGE_INCOMPLETE", - "HTML_SSOT_COMPONENT_STATE_COVERAGE_INCOMPLETE", - "HTML_SSOT_PAGE_COVERAGE_INCOMPLETE", - "HTML_SSOT_ASSET_TRACEABILITY_INCOMPLETE", - "HTML_SSOT_VIEWPORT_CAPTURE_INCOMPLETE", - "HTML_SSOT_VISUAL_DIFF_BLOCKED", - "HTML_SSOT_KNOWN_GAP_UNRESOLVED", - "HTML_SSOT_SCHEMA_INVALID" - ] - } - }, - "additionalProperties": true -} diff --git a/extensions/intake/templates/schemas/viewport-coverage.schema.json b/extensions/intake/templates/schemas/viewport-coverage.schema.json new file mode 100644 index 0000000000..b366c74ef8 --- /dev/null +++ b/extensions/intake/templates/schemas/viewport-coverage.schema.json @@ -0,0 +1,91 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://spec-kit-intake.local/schemas/viewport-coverage.schema.json", + "title": "Visual Design Viewport Coverage", + "type": "object", + "required": ["ready_gate", "blockers", "viewports"], + "properties": { + "ready_gate": { "enum": ["PASS", "BLOCKED"] }, + "blockers": { + "type": "array", + "items": { "$ref": "#/$defs/blocker_code" } + }, + "viewports": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "required": [ + "id", + "width", + "height", + "covered", + "source_refs", + "visual_spec_refs", + "page_refs", + "screenshot_refs", + "visual_diff_status" + ], + "properties": { + "id": { "type": "string", "minLength": 1 }, + "width": { "type": "integer", "minimum": 1 }, + "height": { "type": "integer", "minimum": 1 }, + "covered": { "type": "boolean" }, + "source_refs": { + "type": "array", + "minItems": 1, + "items": { "type": "string", "minLength": 1 } + }, + "visual_spec_refs": { + "type": "array", + "minItems": 1, + "items": { "type": "string", "minLength": 1 } + }, + "page_refs": { + "type": "array", + "minItems": 1, + "items": { "type": "string", "minLength": 1 } + }, + "screenshot_refs": { + "type": "array", + "minItems": 1, + "items": { "type": "string", "minLength": 1 } + }, + "visual_diff_status": { "enum": ["pass", "blocked", "not_applicable"] }, + "missing": { + "type": "array", + "items": { + "type": "object", + "required": ["reason", "blocker"], + "properties": { + "reason": { "type": "string", "minLength": 1 }, + "blocker": { "$ref": "#/$defs/blocker_code" } + }, + "additionalProperties": true + } + } + }, + "additionalProperties": true + } + } + }, + "$defs": { + "blocker_code": { + "enum": [ + "VISUAL_PREVIEW_SOURCE_INTAKE_BLOCKED", + "VISUAL_PREVIEW_REQUIRED_ARTIFACT_MISSING", + "VISUAL_PREVIEW_FIGMA_NODE_COVERAGE_INCOMPLETE", + "VISUAL_PREVIEW_COMPONENT_STATE_COVERAGE_INCOMPLETE", + "VISUAL_PREVIEW_PAGE_COVERAGE_INCOMPLETE", + "VISUAL_PREVIEW_ASSET_TRACEABILITY_INCOMPLETE", + "VISUAL_PREVIEW_VIEWPORT_CAPTURE_INCOMPLETE", + "VISUAL_PREVIEW_VISUAL_DIFF_BLOCKED", + "VISUAL_PREVIEW_KNOWN_GAP_UNRESOLVED", + "VISUAL_PREVIEW_SCHEMA_INVALID", + "VISUAL_SPEC_PROVIDER_EVIDENCE_MISSING", + "VISUAL_SPEC_ASSERTION_COVERAGE_INCOMPLETE" + ] + } + }, + "additionalProperties": true +} diff --git a/extensions/intake/templates/schemas/ir-assertions.schema.json b/extensions/intake/templates/schemas/visual-spec-assertions.schema.json similarity index 75% rename from extensions/intake/templates/schemas/ir-assertions.schema.json rename to extensions/intake/templates/schemas/visual-spec-assertions.schema.json index 2c83888542..9fc962bb78 100644 --- a/extensions/intake/templates/schemas/ir-assertions.schema.json +++ b/extensions/intake/templates/schemas/visual-spec-assertions.schema.json @@ -1,7 +1,7 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://spec-kit-intake.local/schemas/ir-assertions.schema.json", - "title": "Structured UI Acceptance IR Assertions", + "$id": "https://spec-kit-intake.local/schemas/visual-spec-assertions.schema.json", + "title": "Visual Spec Package Assertions", "type": "object", "required": [ "assertions_complete", @@ -21,7 +21,7 @@ "type": "object", "required": [ "id", - "ir_refs", + "visual_spec_refs", "assertion_type", "acceptance_intent", "expected", @@ -33,9 +33,9 @@ "properties": { "id": { "type": "string", - "pattern": "^IRA-[A-Za-z0-9._:-]+$" + "pattern": "^VSA-[A-Za-z0-9._:-]+$" }, - "ir_refs": { + "visual_spec_refs": { "type": "array", "minItems": 1, "items": { "type": "string", "minLength": 1 } @@ -79,16 +79,16 @@ "$defs": { "blocker_code": { "enum": [ - "IR_SOURCE_INTAKE_BLOCKED", - "IR_REQUIRED_ARTIFACT_MISSING", - "IR_SCHEMA_INVALID", - "IR_INTAKE_INCOMPLETE", - "IR_PROVIDER_EVIDENCE_MISSING", - "IR_PRODUCT_AMBIGUITY_UNRESOLVED", - "IR_ASSERTION_COVERAGE_INCOMPLETE", - "IR_LOCATOR_STRATEGY_INVALID", - "IR_DOWNSTREAM_OWNERSHIP_LEAK", - "IR_READY_WITHOUT_EVIDENCE" + "VISUAL_SPEC_SOURCE_INTAKE_BLOCKED", + "VISUAL_SPEC_REQUIRED_ARTIFACT_MISSING", + "VISUAL_SPEC_SCHEMA_INVALID", + "VISUAL_SPEC_INTAKE_INCOMPLETE", + "VISUAL_SPEC_PROVIDER_EVIDENCE_MISSING", + "VISUAL_SPEC_PRODUCT_AMBIGUITY_UNRESOLVED", + "VISUAL_SPEC_ASSERTION_COVERAGE_INCOMPLETE", + "VISUAL_SPEC_LOCATOR_STRATEGY_INVALID", + "VISUAL_SPEC_DOWNSTREAM_OWNERSHIP_LEAK", + "VISUAL_SPEC_READY_WITHOUT_EVIDENCE" ] } }, diff --git a/extensions/intake/templates/schemas/structured-ir.schema.json b/extensions/intake/templates/schemas/visual-spec-package.schema.json similarity index 81% rename from extensions/intake/templates/schemas/structured-ir.schema.json rename to extensions/intake/templates/schemas/visual-spec-package.schema.json index 1d276312fa..71238be831 100644 --- a/extensions/intake/templates/schemas/structured-ir.schema.json +++ b/extensions/intake/templates/schemas/visual-spec-package.schema.json @@ -1,22 +1,24 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://spec-kit-intake.local/schemas/structured-ir.schema.json", - "title": "Structured UI Acceptance IR", + "$id": "https://spec-kit-intake.local/schemas/visual-spec-package.schema.json", + "title": "Visual Spec Package", "type": "object", "required": [ - "ir_complete", - "ir_item_count", + "visual_spec_package_complete", + "visual_spec_item_count", "source_refs_complete", "provider_evidence_complete", + "resources_traceable_to_design_source", "product_ambiguities_recorded", "downstream_ownership_free", "items" ], "properties": { - "ir_complete": { "type": "boolean" }, - "ir_item_count": { "type": "integer", "minimum": 0 }, + "visual_spec_package_complete": { "type": "boolean" }, + "visual_spec_item_count": { "type": "integer", "minimum": 0 }, "source_refs_complete": { "type": "boolean" }, "provider_evidence_complete": { "type": "boolean" }, + "resources_traceable_to_design_source": { "type": "boolean" }, "product_ambiguities_recorded": { "type": "boolean" }, "downstream_ownership_free": { "type": "boolean" }, "product_ambiguities": { "type": "array" }, @@ -45,7 +47,7 @@ "properties": { "id": { "type": "string", - "pattern": "^IR-[A-Za-z0-9._:-]+$" + "pattern": "^VS-[A-Za-z0-9._:-]+$" }, "source_refs": { "type": "array", @@ -60,7 +62,7 @@ "type": "array", "items": { "type": "string", "minLength": 1 } }, - "html_ssot_refs": { + "preview_refs": { "type": "array", "items": { "type": "string", "minLength": 1 } }, @@ -129,16 +131,16 @@ "$defs": { "blocker_code": { "enum": [ - "IR_SOURCE_INTAKE_BLOCKED", - "IR_REQUIRED_ARTIFACT_MISSING", - "IR_SCHEMA_INVALID", - "IR_INTAKE_INCOMPLETE", - "IR_PROVIDER_EVIDENCE_MISSING", - "IR_PRODUCT_AMBIGUITY_UNRESOLVED", - "IR_ASSERTION_COVERAGE_INCOMPLETE", - "IR_LOCATOR_STRATEGY_INVALID", - "IR_DOWNSTREAM_OWNERSHIP_LEAK", - "IR_READY_WITHOUT_EVIDENCE" + "VISUAL_SPEC_SOURCE_INTAKE_BLOCKED", + "VISUAL_SPEC_REQUIRED_ARTIFACT_MISSING", + "VISUAL_SPEC_SCHEMA_INVALID", + "VISUAL_SPEC_INTAKE_INCOMPLETE", + "VISUAL_SPEC_PROVIDER_EVIDENCE_MISSING", + "VISUAL_SPEC_PRODUCT_AMBIGUITY_UNRESOLVED", + "VISUAL_SPEC_ASSERTION_COVERAGE_INCOMPLETE", + "VISUAL_SPEC_LOCATOR_STRATEGY_INVALID", + "VISUAL_SPEC_DOWNSTREAM_OWNERSHIP_LEAK", + "VISUAL_SPEC_READY_WITHOUT_EVIDENCE" ] } }, diff --git a/extensions/intake/tests/test_extension_contract.py b/extensions/intake/tests/test_extension_contract.py index bf844b8ca4..1749b497d7 100644 --- a/extensions/intake/tests/test_extension_contract.py +++ b/extensions/intake/tests/test_extension_contract.py @@ -1,4 +1,4 @@ -import os +import os import json import shutil import subprocess @@ -13,8 +13,8 @@ VALIDATOR = ROOT / "scripts" / "python" / "validate_visual_design_intake.py" PRD_VALIDATOR = ROOT / "scripts" / "python" / "validate_prd_intake.py" TEST_CASE_VALIDATOR = ROOT / "scripts" / "python" / "validate_test_cases_intake.py" -HTML_SSOT_VALIDATOR = ROOT / "scripts" / "python" / "validate_html_ssot.py" -STRUCTURED_IR_VALIDATOR = ROOT / "scripts" / "python" / "validate_structured_ir_intake.py" +VISUAL_PREVIEWS_VALIDATOR = ROOT / "scripts" / "python" / "validate_visual_previews.py" +VISUAL_SPEC_PACKAGE_VALIDATOR = ROOT / "scripts" / "python" / "validate_visual_spec_package.py" def write_visual_intake_fixture(intake: Path, source_type: str, fidelity: str, file_name: str): @@ -308,114 +308,99 @@ def write_image_visual_intake_fixture(intake: Path): write_visual_intake_fixture(intake, "image", "low", "wireframe.png") -def write_html_ssot_fixture(html_dir: Path): +def write_visual_previews_fixture(html_dir: Path): visual_intake = html_dir.parent write_visual_intake_fixture(visual_intake, "figma", "high", "figma-source.txt") html_dir.mkdir(parents=True, exist_ok=True) screenshots = html_dir / "screenshots" screenshots.mkdir(exist_ok=True) (screenshots / "home-desktop.png").write_bytes(b"fake-png") - asset = html_dir / "logo.svg" - asset.write_text("", encoding="utf-8") - import hashlib - - digest = hashlib.sha256(asset.read_bytes()).hexdigest() - (html_dir / "visual-spec.html").write_text( - '
' - '' + (html_dir / "component-matrix-preview.html").write_text( + '
' + '' "
", encoding="utf-8", ) - (html_dir / "figma-map.json").write_text( - json.dumps( - { - "source_intake_refs": ["../visual-requirements.yaml#VR-001"], - "mappings": [ - { - "figma_node_id": "1", - "selector": '[data-figma-node-id="1"]', - "acceptance_unit": "page", - "required": True, - "states": ["default"], - "viewports": ["desktop"], - "content_sample": "Home", - "container_constraint": "page", - }, - { - "figma_node_id": "2", - "selector": '[data-figma-node-id="2"]', - "acceptance_unit": "component-state", - "required": True, - "states": ["default"], - "viewports": ["desktop"], - "content_sample": "Save", - "container_constraint": "header", - }, - ], - } - ), - encoding="utf-8", - ) - (html_dir / "assets-manifest.json").write_text( - json.dumps( - { - "assets": [ - { - "id": "asset-logo", - "path": "logo.svg", - "role": "brand", - "source_refs": ["figma://node/logo"], - "sha256": digest, - } - ] - } + (html_dir / "component-coverage.yaml").write_text( + "\n".join( + [ + "ready_gate: PASS", + "blockers: []", + "components:", + " - id: component-button", + " source_ref: figma://node/button-set", + " name: Button", + " required_dimensions:", + " size: [md]", + " tone: [primary]", + " state: [default]", + " icon: [none]", + " covered:", + " - size: md", + " tone: primary", + " state: default", + " icon: none", + " source_ref: figma://node/2", + " visual_spec_ref: ../visual-spec-package/visual-spec.yaml#VS-home-save-default", + " preview_ref: component-matrix-preview.html#button-md-primary-default", + " screenshot_refs:", + " - screenshots/home-desktop.png", + " missing: []", + "", + ] ), encoding="utf-8", ) - (html_dir / "coverage-report.md").write_text( - "---\n" + (html_dir / "viewport-coverage.yaml").write_text( "ready_gate: PASS\n" "blockers: []\n" - "required_nodes_total: 2\n" - "required_nodes_covered: 2\n" - "component_state_coverage_complete: true\n" - "page_coverage_complete: true\n" - "asset_traceability_complete: true\n" - "viewport_capture_complete: true\n" - "visual_diff_status: pass\n" - "accepted_exceptions: []\n" - "---\n" - "# Coverage Report\n", + "viewports:\n" + " - id: desktop\n" + " width: 1440\n" + " height: 900\n" + " covered: true\n" + " source_refs:\n" + " - figma://node/1\n" + " visual_spec_refs:\n" + " - ../visual-spec-package/visual-spec.yaml#VS-home-save-default\n" + " page_refs:\n" + " - component-matrix-preview.html#home-page\n" + " screenshot_refs:\n" + " - screenshots/home-desktop.png\n" + " visual_diff_status: pass\n", encoding="utf-8", ) (html_dir / "known-gaps.md").write_text("# Known Gaps\n\nNone.\n", encoding="utf-8") -def write_structured_ir_fixture(ir_dir: Path): - visual_intake = ir_dir.parent +def write_visual_spec_package_fixture(package_dir: Path): + visual_intake = package_dir.parent write_visual_intake_fixture(visual_intake, "figma", "high", "figma-source.txt") - ir_dir.mkdir(parents=True, exist_ok=True) + package_dir.mkdir(parents=True, exist_ok=True) - (ir_dir / "structured-ir.yaml").write_text( + (package_dir / "visual-spec.yaml").write_text( "\n".join( [ - "ir_complete: true", - "ir_item_count: 1", + "visual_spec_package_complete: true", + "visual_spec_item_count: 1", "source_refs_complete: true", "provider_evidence_complete: true", + "resources_traceable_to_design_source: true", "product_ambiguities_recorded: true", "downstream_ownership_free: true", "product_ambiguities: []", "blocker_lint_errors: []", "items:", - " - id: IR-home-save-default", + " - id: VS-home-save-default", " source_refs:", " - figma://node/2", " visual_requirement_refs:", " - ../visual-requirements.yaml#VR-001", - " html_ssot_refs:", - " - ../figma2htmlssot/visual-spec.html#[data-figma-node-id='2']", + " preview_refs:", + " - ../previews/component-matrix-preview.html#button-md-primary-default", + " - ../previews/component-coverage.yaml#component-button", " page: home", " region: header", " role: button", @@ -447,7 +432,7 @@ def write_structured_ir_fixture(ir_dir: Path): encoding="utf-8", ) - (ir_dir / "ir-assertions.yaml").write_text( + (package_dir / "visual-spec-assertions.yaml").write_text( "\n".join( [ "assertions_complete: true", @@ -455,14 +440,14 @@ def write_structured_ir_fixture(ir_dir: Path): "ci_assertions_complete: true", "blocker_lint_errors: []", "assertions:", - " - id: IRA-home-save-visible", - " ir_refs:", - " - IR-home-save-default", + " - id: VSA-home-save-visible", + " visual_spec_refs:", + " - VS-home-save-default", " assertion_type: visible", " acceptance_intent: Save control is visible and discoverable", " expected: true", " evidence_refs:", - " - structured-ir.yaml#IR-home-save-default", + " - visual-spec.yaml#VS-home-save-default", " ci_suitability: ci_low_cost", " status: ready", " blockers: []", @@ -472,7 +457,7 @@ def write_structured_ir_fixture(ir_dir: Path): encoding="utf-8", ) - (ir_dir / "ir-evidence-packet.md").write_text( + (package_dir / "visual-spec-evidence-packet.md").write_text( "---\n" "ready_gate: PASS\n" "blockers: []\n" @@ -480,7 +465,7 @@ def write_structured_ir_fixture(ir_dir: Path): "extracted_item_count: 1\n" "generated_at: '2026-07-01T00:00:00Z'\n" "---\n" - "# Structured IR Evidence Packet\n", + "# Visual Spec Package Evidence Packet\n", encoding="utf-8", ) @@ -498,8 +483,8 @@ def test_manifest_loads_with_spec_kit_checkout(): "from specify_cli.extensions import ExtensionManifest; " "m=ExtensionManifest(Path('extension.yml')); " "assert m.id == 'intake'; " - "assert len(m.commands) == 5; " - "assert {c['name'] for c in m.commands} == {'speckit.intake.visual-design', 'speckit.intake.figma2htmlssot', 'speckit.intake.ir', 'speckit.intake.prd', 'speckit.intake.test-cases'}; " + "assert len(m.commands) == 3; " + "assert {c['name'] for c in m.commands} == {'speckit.intake.visual-design', 'speckit.intake.prd', 'speckit.intake.test-cases'}; " "assert m.hooks" ) @@ -561,38 +546,36 @@ def test_readme_release_url_matches_extension_version(): assert f"archive/refs/tags/v{version}.zip" in readme -def test_html_ssot_schema_and_validator_paths_are_declared(): +def test_visual_previews_schema_and_validator_paths_are_declared(): extension = ROOT / "extension.yml" config = ROOT / "config-template.yml" for document in (extension.read_text(encoding="utf-8-sig"), config.read_text(encoding="utf-8")): - assert "scripts/python/validate_html_ssot.py" in document - assert "templates/schemas/figma-map.schema.json" in document - assert "templates/schemas/assets-manifest.schema.json" in document - assert "templates/schemas/html-ssot-coverage.schema.json" in document + assert "scripts/python/validate_visual_previews.py" in document + assert "templates/intake-visual-previews-contract.md" in document + assert "templates/schemas/component-coverage.schema.json" in document + assert "templates/schemas/viewport-coverage.schema.json" in document - assert HTML_SSOT_VALIDATOR.exists() - assert (ROOT / "templates" / "schemas" / "figma-map.schema.json").exists() - assert (ROOT / "templates" / "schemas" / "assets-manifest.schema.json").exists() - assert (ROOT / "templates" / "schemas" / "html-ssot-coverage.schema.json").exists() + assert VISUAL_PREVIEWS_VALIDATOR.exists() + assert (ROOT / "templates" / "intake-visual-previews-contract.md").exists() + assert (ROOT / "templates" / "schemas" / "component-coverage.schema.json").exists() + assert (ROOT / "templates" / "schemas" / "viewport-coverage.schema.json").exists() -def test_structured_ir_schema_and_validator_paths_are_declared(): +def test_visual_spec_package_schema_and_validator_paths_are_declared(): extension = ROOT / "extension.yml" config = ROOT / "config-template.yml" - assert "commands/speckit.intake.ir.md" in extension.read_text(encoding="utf-8-sig") for document in (extension.read_text(encoding="utf-8-sig"), config.read_text(encoding="utf-8")): - assert "scripts/python/validate_structured_ir_intake.py" in document - assert "templates/intake-structured-ir-contract.md" in document - assert "templates/intake-structured-ir-evidence-packet-template.md" in document - assert "templates/schemas/structured-ir.schema.json" in document - assert "templates/schemas/ir-assertions.schema.json" in document + assert "scripts/python/validate_visual_spec_package.py" in document + assert "templates/intake-visual-spec-package-contract.md" in document + assert "templates/intake-visual-spec-package-evidence-packet-template.md" in document + assert "templates/schemas/visual-spec-package.schema.json" in document + assert "templates/schemas/visual-spec-assertions.schema.json" in document - assert STRUCTURED_IR_VALIDATOR.exists() - assert (ROOT / "commands" / "speckit.intake.ir.md").exists() - assert (ROOT / "templates" / "intake-structured-ir-contract.md").exists() - assert (ROOT / "templates" / "intake-structured-ir-evidence-packet-template.md").exists() - assert (ROOT / "templates" / "schemas" / "structured-ir.schema.json").exists() - assert (ROOT / "templates" / "schemas" / "ir-assertions.schema.json").exists() + assert VISUAL_SPEC_PACKAGE_VALIDATOR.exists() + assert (ROOT / "templates" / "intake-visual-spec-package-contract.md").exists() + assert (ROOT / "templates" / "intake-visual-spec-package-evidence-packet-template.md").exists() + assert (ROOT / "templates" / "schemas" / "visual-spec-package.schema.json").exists() + assert (ROOT / "templates" / "schemas" / "visual-spec-assertions.schema.json").exists() def test_validator_blocks_missing_directory(): @@ -637,18 +620,18 @@ def test_test_case_validator_blocks_missing_directory(): assert "TEST_EVIDENCE_PACKET_MISSING" in result.stdout -def test_structured_ir_validator_blocks_missing_directory(): +def test_visual_spec_package_validator_blocks_missing_directory(): result = subprocess.run( - [sys.executable, str(STRUCTURED_IR_VALIDATOR), "missing-dir"], + [sys.executable, str(VISUAL_SPEC_PACKAGE_VALIDATOR), "missing-dir"], cwd=ROOT, text=True, capture_output=True, ) assert result.returncode == 1 - assert "IR_SOURCE_INTAKE_BLOCKED" in result.stdout - assert "IR_REQUIRED_ARTIFACT_MISSING" in result.stdout - assert "IR_READY_WITHOUT_EVIDENCE" in result.stdout + assert "VISUAL_SPEC_SOURCE_INTAKE_BLOCKED" in result.stdout + assert "VISUAL_SPEC_REQUIRED_ARTIFACT_MISSING" in result.stdout + assert "VISUAL_SPEC_READY_WITHOUT_EVIDENCE" in result.stdout @pytest.mark.parametrize( @@ -678,7 +661,6 @@ def test_validator_passes_visual_source_matrix(source_type, fidelity, file_name) shutil.rmtree(work_dir) - def test_visual_validator_allows_remote_source_gap_but_blocks_integrity(): work_dir = ROOT / ".tmp" / "test-validator-remote-source-gap" if work_dir.exists(): @@ -1746,44 +1728,44 @@ def test_validator_blocks_legacy_figma_only_without_manifest(): shutil.rmtree(work_dir) -def test_html_ssot_validator_passes_complete_minimal_bundle(): - work_dir = ROOT / ".tmp" / "test-html-ssot-validator-pass" +def test_visual_previews_validator_passes_complete_minimal_bundle(): + work_dir = ROOT / ".tmp" / "test-visual-previews-validator-pass" if work_dir.exists(): shutil.rmtree(work_dir) - html_dir = work_dir / "visual-design" / "figma2htmlssot" - write_html_ssot_fixture(html_dir) + html_dir = work_dir / "visual-design" / "previews" + write_visual_previews_fixture(html_dir) result = subprocess.run( - [sys.executable, str(HTML_SSOT_VALIDATOR), str(html_dir)], + [sys.executable, str(VISUAL_PREVIEWS_VALIDATOR), str(html_dir)], cwd=ROOT, text=True, capture_output=True, ) assert result.returncode == 0, result.stdout + result.stderr - assert "HTML SSOT readiness: PASS" in result.stdout + assert "Visual preview readiness: PASS" in result.stdout shutil.rmtree(work_dir) -def test_html_ssot_validator_blocks_missing_directory(): +def test_visual_previews_validator_blocks_missing_directory(): result = subprocess.run( - [sys.executable, str(HTML_SSOT_VALIDATOR), "missing-dir"], + [sys.executable, str(VISUAL_PREVIEWS_VALIDATOR), "missing-dir"], cwd=ROOT, text=True, capture_output=True, ) assert result.returncode == 1 - assert "HTML_SSOT_REQUIRED_ARTIFACT_MISSING" in result.stdout + assert "VISUAL_PREVIEW_REQUIRED_ARTIFACT_MISSING" in result.stdout -def test_html_ssot_validator_blocks_source_intake_blocked(): - work_dir = ROOT / ".tmp" / "test-html-ssot-source-blocked" +def test_visual_previews_validator_blocks_source_intake_blocked(): + work_dir = ROOT / ".tmp" / "test-visual-previews-source-blocked" if work_dir.exists(): shutil.rmtree(work_dir) - html_dir = work_dir / "visual-design" / "figma2htmlssot" - write_html_ssot_fixture(html_dir) + html_dir = work_dir / "visual-design" / "previews" + write_visual_previews_fixture(html_dir) packet = html_dir.parent / "visual-evidence-packet.md" packet.write_text( "---\n" @@ -1797,7 +1779,7 @@ def test_html_ssot_validator_blocks_source_intake_blocked(): ) result = subprocess.run( - [sys.executable, str(HTML_SSOT_VALIDATOR), "--json", str(html_dir)], + [sys.executable, str(VISUAL_PREVIEWS_VALIDATOR), "--json", str(html_dir)], cwd=ROOT, text=True, capture_output=True, @@ -1805,23 +1787,23 @@ def test_html_ssot_validator_blocks_source_intake_blocked(): payload = json.loads(result.stdout) assert result.returncode == 1 - assert "HTML_SSOT_SOURCE_INTAKE_BLOCKED" in payload["blockers"] + assert "VISUAL_PREVIEW_SOURCE_INTAKE_BLOCKED" in payload["blockers"] shutil.rmtree(work_dir) -def test_html_ssot_validator_reports_schema_errors_in_json(): - work_dir = ROOT / ".tmp" / "test-html-ssot-schema-error" +def test_visual_previews_validator_reports_schema_errors_in_json(): + work_dir = ROOT / ".tmp" / "test-visual-previews-schema-error" if work_dir.exists(): shutil.rmtree(work_dir) - html_dir = work_dir / "visual-design" / "figma2htmlssot" - write_html_ssot_fixture(html_dir) - figma_map = json.loads((html_dir / "figma-map.json").read_text(encoding="utf-8")) - figma_map["mappings"][0].pop("selector") - (html_dir / "figma-map.json").write_text(json.dumps(figma_map), encoding="utf-8") + html_dir = work_dir / "visual-design" / "previews" + write_visual_previews_fixture(html_dir) + component_coverage = yaml.safe_load((html_dir / "component-coverage.yaml").read_text(encoding="utf-8")) + component_coverage["components"][0]["covered"][0].pop("preview_ref") + (html_dir / "component-coverage.yaml").write_text(yaml.safe_dump(component_coverage), encoding="utf-8") result = subprocess.run( - [sys.executable, str(HTML_SSOT_VALIDATOR), "--json", str(html_dir)], + [sys.executable, str(VISUAL_PREVIEWS_VALIDATOR), "--json", str(html_dir)], cwd=ROOT, text=True, capture_output=True, @@ -1829,8 +1811,8 @@ def test_html_ssot_validator_reports_schema_errors_in_json(): payload = json.loads(result.stdout) assert result.returncode == 1 - assert "HTML_SSOT_SCHEMA_INVALID" in payload["blockers"] - assert payload["details"]["schema_validation"]["figma_map"]["valid"] is False + assert "VISUAL_PREVIEW_SCHEMA_INVALID" in payload["blockers"] + assert payload["details"]["schema_validation"]["component_coverage"]["valid"] is False shutil.rmtree(work_dir) @@ -1838,53 +1820,68 @@ def test_html_ssot_validator_reports_schema_errors_in_json(): @pytest.mark.parametrize( ("edit_kind", "expected_blocker"), [ - ("missing_selector", "HTML_SSOT_FIGMA_NODE_COVERAGE_INCOMPLETE"), - ("component_state", "HTML_SSOT_COMPONENT_STATE_COVERAGE_INCOMPLETE"), - ("page", "HTML_SSOT_PAGE_COVERAGE_INCOMPLETE"), - ("asset", "HTML_SSOT_ASSET_TRACEABILITY_INCOMPLETE"), - ("viewport", "HTML_SSOT_VIEWPORT_CAPTURE_INCOMPLETE"), - ("visual_diff", "HTML_SSOT_VISUAL_DIFF_BLOCKED"), - ("known_gap", "HTML_SSOT_KNOWN_GAP_UNRESOLVED"), + ("missing_selector", "VISUAL_PREVIEW_FIGMA_NODE_COVERAGE_INCOMPLETE"), + ("component_state", "VISUAL_PREVIEW_COMPONENT_STATE_COVERAGE_INCOMPLETE"), + ("page", "VISUAL_PREVIEW_PAGE_COVERAGE_INCOMPLETE"), + ("asset", "VISUAL_PREVIEW_ASSET_TRACEABILITY_INCOMPLETE"), + ("viewport", "VISUAL_PREVIEW_VIEWPORT_CAPTURE_INCOMPLETE"), + ("visual_diff", "VISUAL_PREVIEW_VISUAL_DIFF_BLOCKED"), + ("known_gap", "VISUAL_PREVIEW_KNOWN_GAP_UNRESOLVED"), ], ) -def test_html_ssot_validator_blocks_incomplete_coverage(edit_kind, expected_blocker): - work_dir = ROOT / ".tmp" / f"test-html-ssot-{edit_kind}" +def test_visual_previews_validator_blocks_incomplete_coverage(edit_kind, expected_blocker): + work_dir = ROOT / ".tmp" / f"test-visual-previews-{edit_kind}" if work_dir.exists(): shutil.rmtree(work_dir) - html_dir = work_dir / "visual-design" / "figma2htmlssot" - write_html_ssot_fixture(html_dir) + html_dir = work_dir / "visual-design" / "previews" + write_visual_previews_fixture(html_dir) if edit_kind == "missing_selector": - html = (html_dir / "visual-spec.html").read_text(encoding="utf-8") - (html_dir / "visual-spec.html").write_text(html.replace('data-figma-node-id="2"', ""), encoding="utf-8") - elif edit_kind == "component_state": - coverage = (html_dir / "coverage-report.md").read_text(encoding="utf-8") - (html_dir / "coverage-report.md").write_text( - coverage.replace("component_state_coverage_complete: true", "component_state_coverage_complete: false"), + html = (html_dir / "component-matrix-preview.html").read_text(encoding="utf-8") + (html_dir / "component-matrix-preview.html").write_text( + html.replace('id="button-md-primary-default"', "").replace( + 'data-preview-id="button-md-primary-default"', "" + ), encoding="utf-8", ) + elif edit_kind == "component_state": + component_coverage = yaml.safe_load((html_dir / "component-coverage.yaml").read_text(encoding="utf-8")) + component_coverage["components"][0]["missing"].append( + { + "missing_type": "state", + "state": "loading", + "reason": "Missing Figma source state", + "blocker": "VISUAL_SPEC_PROVIDER_EVIDENCE_MISSING", + } + ) + (html_dir / "component-coverage.yaml").write_text(yaml.safe_dump(component_coverage), encoding="utf-8") elif edit_kind == "page": - figma_map = json.loads((html_dir / "figma-map.json").read_text(encoding="utf-8")) - figma_map["mappings"] = [item for item in figma_map["mappings"] if item["acceptance_unit"] != "page"] - (html_dir / "figma-map.json").write_text(json.dumps(figma_map), encoding="utf-8") + viewport_coverage = yaml.safe_load((html_dir / "viewport-coverage.yaml").read_text(encoding="utf-8")) + viewport_coverage["viewports"][0]["page_refs"] = [] + (html_dir / "viewport-coverage.yaml").write_text(yaml.safe_dump(viewport_coverage), encoding="utf-8") elif edit_kind == "asset": - assets = json.loads((html_dir / "assets-manifest.json").read_text(encoding="utf-8")) - assets["assets"][0]["source_refs"] = [] - (html_dir / "assets-manifest.json").write_text(json.dumps(assets), encoding="utf-8") + component_coverage = yaml.safe_load((html_dir / "component-coverage.yaml").read_text(encoding="utf-8")) + component_coverage["components"][0]["missing"].append( + { + "missing_type": "resource", + "resource": "logo", + "reason": "Missing Figma image resource", + "blocker": "VISUAL_SPEC_PROVIDER_EVIDENCE_MISSING", + } + ) + (html_dir / "component-coverage.yaml").write_text(yaml.safe_dump(component_coverage), encoding="utf-8") elif edit_kind == "viewport": shutil.rmtree(html_dir / "screenshots") (html_dir / "screenshots").mkdir() elif edit_kind == "visual_diff": - coverage = (html_dir / "coverage-report.md").read_text(encoding="utf-8") - (html_dir / "coverage-report.md").write_text( - coverage.replace("visual_diff_status: pass", "visual_diff_status: blocked"), - encoding="utf-8", - ) + viewport_coverage = yaml.safe_load((html_dir / "viewport-coverage.yaml").read_text(encoding="utf-8")) + viewport_coverage["viewports"][0]["visual_diff_status"] = "blocked" + (html_dir / "viewport-coverage.yaml").write_text(yaml.safe_dump(viewport_coverage), encoding="utf-8") elif edit_kind == "known_gap": (html_dir / "known-gaps.md").write_text("# Known Gaps\n\nBLOCKED: missing mobile state.\n", encoding="utf-8") result = subprocess.run( - [sys.executable, str(HTML_SSOT_VALIDATOR), "--json", str(html_dir)], + [sys.executable, str(VISUAL_PREVIEWS_VALIDATOR), "--json", str(html_dir)], cwd=ROOT, text=True, capture_output=True, @@ -1897,33 +1894,33 @@ def test_html_ssot_validator_blocks_incomplete_coverage(edit_kind, expected_bloc shutil.rmtree(work_dir) -def test_structured_ir_validator_passes_complete_minimal_bundle(): - work_dir = ROOT / ".tmp" / "test-structured-ir-validator-pass" +def test_visual_spec_package_validator_passes_complete_minimal_bundle(): + work_dir = ROOT / ".tmp" / "test-visual-spec-package-validator-pass" if work_dir.exists(): shutil.rmtree(work_dir) - ir_dir = work_dir / "visual-design" / "structured-ir" - write_structured_ir_fixture(ir_dir) + package_dir = work_dir / "visual-design" / "visual-spec-package" + write_visual_spec_package_fixture(package_dir) result = subprocess.run( - [sys.executable, str(STRUCTURED_IR_VALIDATOR), str(ir_dir)], + [sys.executable, str(VISUAL_SPEC_PACKAGE_VALIDATOR), str(package_dir)], cwd=ROOT, text=True, capture_output=True, ) assert result.returncode == 0, result.stdout + result.stderr - assert "Structured IR intake readiness: PASS" in result.stdout + assert "Visual Spec Package intake readiness: PASS" in result.stdout shutil.rmtree(work_dir) -def test_structured_ir_validator_blocks_source_intake_blocked(): - work_dir = ROOT / ".tmp" / "test-structured-ir-source-blocked" +def test_visual_spec_package_validator_blocks_source_intake_blocked(): + work_dir = ROOT / ".tmp" / "test-visual-spec-package-source-blocked" if work_dir.exists(): shutil.rmtree(work_dir) - ir_dir = work_dir / "visual-design" / "structured-ir" - write_structured_ir_fixture(ir_dir) - packet = ir_dir.parent / "visual-evidence-packet.md" + package_dir = work_dir / "visual-design" / "visual-spec-package" + write_visual_spec_package_fixture(package_dir) + packet = package_dir.parent / "visual-evidence-packet.md" packet.write_text( "---\n" "ready_gate: BLOCKED\n" @@ -1936,7 +1933,7 @@ def test_structured_ir_validator_blocks_source_intake_blocked(): ) result = subprocess.run( - [sys.executable, str(STRUCTURED_IR_VALIDATOR), "--json", str(ir_dir)], + [sys.executable, str(VISUAL_SPEC_PACKAGE_VALIDATOR), "--json", str(package_dir)], cwd=ROOT, text=True, capture_output=True, @@ -1944,26 +1941,26 @@ def test_structured_ir_validator_blocks_source_intake_blocked(): payload = json.loads(result.stdout) assert result.returncode == 1 - assert "IR_SOURCE_INTAKE_BLOCKED" in payload["blockers"] - assert "IR_READY_WITHOUT_EVIDENCE" in payload["blockers"] + assert "VISUAL_SPEC_SOURCE_INTAKE_BLOCKED" in payload["blockers"] + assert "VISUAL_SPEC_READY_WITHOUT_EVIDENCE" in payload["blockers"] shutil.rmtree(work_dir) -def test_structured_ir_validator_reports_schema_errors_in_json(): - work_dir = ROOT / ".tmp" / "test-structured-ir-schema-error" +def test_visual_spec_package_validator_reports_schema_errors_in_json(): + work_dir = ROOT / ".tmp" / "test-visual-spec-package-schema-error" if work_dir.exists(): shutil.rmtree(work_dir) - ir_dir = work_dir / "visual-design" / "structured-ir" - write_structured_ir_fixture(ir_dir) - text = (ir_dir / "structured-ir.yaml").read_text(encoding="utf-8") - (ir_dir / "structured-ir.yaml").write_text( + package_dir = work_dir / "visual-design" / "visual-spec-package" + write_visual_spec_package_fixture(package_dir) + text = (package_dir / "visual-spec.yaml").read_text(encoding="utf-8") + (package_dir / "visual-spec.yaml").write_text( text.replace(" role: button\n", ""), encoding="utf-8", ) result = subprocess.run( - [sys.executable, str(STRUCTURED_IR_VALIDATOR), "--json", str(ir_dir)], + [sys.executable, str(VISUAL_SPEC_PACKAGE_VALIDATOR), "--json", str(package_dir)], cwd=ROOT, text=True, capture_output=True, @@ -1971,8 +1968,8 @@ def test_structured_ir_validator_reports_schema_errors_in_json(): payload = json.loads(result.stdout) assert result.returncode == 1 - assert "IR_SCHEMA_INVALID" in payload["blockers"] - assert payload["details"]["schema_validation"]["structured_ir"]["valid"] is False + assert "VISUAL_SPEC_SCHEMA_INVALID" in payload["blockers"] + assert payload["details"]["schema_validation"]["visual_spec_package"]["valid"] is False shutil.rmtree(work_dir) @@ -1980,23 +1977,23 @@ def test_structured_ir_validator_reports_schema_errors_in_json(): @pytest.mark.parametrize( ("artifact", "detail_key"), [ - ("structured-ir.yaml", "structured_ir"), - ("ir-assertions.yaml", "ir_assertions"), + ("visual-spec.yaml", "visual_spec_package"), + ("visual-spec-assertions.yaml", "visual_spec_assertions"), ], ) -def test_structured_ir_validator_rejects_unknown_blocker_codes(artifact, detail_key): - work_dir = ROOT / ".tmp" / f"test-structured-ir-unknown-blocker-{detail_key}" +def test_visual_spec_package_validator_rejects_unknown_blocker_codes(artifact, detail_key): + work_dir = ROOT / ".tmp" / f"test-visual-spec-package-unknown-blocker-{detail_key}" if work_dir.exists(): shutil.rmtree(work_dir) - ir_dir = work_dir / "visual-design" / "structured-ir" - write_structured_ir_fixture(ir_dir) + package_dir = work_dir / "visual-design" / "visual-spec-package" + write_visual_spec_package_fixture(package_dir) - path = ir_dir / artifact + path = package_dir / artifact text = path.read_text(encoding="utf-8") path.write_text(text.replace(" blockers: []", " blockers: [NOT_A_BLOCKER]", 1), encoding="utf-8") result = subprocess.run( - [sys.executable, str(STRUCTURED_IR_VALIDATOR), "--json", str(ir_dir)], + [sys.executable, str(VISUAL_SPEC_PACKAGE_VALIDATOR), "--json", str(package_dir)], cwd=ROOT, text=True, capture_output=True, @@ -2004,7 +2001,7 @@ def test_structured_ir_validator_rejects_unknown_blocker_codes(artifact, detail_ payload = json.loads(result.stdout) assert result.returncode == 1 - assert "IR_SCHEMA_INVALID" in payload["blockers"] + assert "VISUAL_SPEC_SCHEMA_INVALID" in payload["blockers"] assert payload["details"]["schema_validation"][detail_key]["valid"] is False shutil.rmtree(work_dir) @@ -2013,77 +2010,77 @@ def test_structured_ir_validator_rejects_unknown_blocker_codes(artifact, detail_ @pytest.mark.parametrize( ("edit_kind", "expected_blocker"), [ - ("provider_evidence", "IR_PROVIDER_EVIDENCE_MISSING"), - ("provider_evidence_blocker", "IR_PROVIDER_EVIDENCE_MISSING"), - ("product_ambiguity", "IR_PRODUCT_AMBIGUITY_UNRESOLVED"), - ("locator", "IR_LOCATOR_STRATEGY_INVALID"), - ("ownership", "IR_DOWNSTREAM_OWNERSHIP_LEAK"), - ("assertion_coverage", "IR_ASSERTION_COVERAGE_INCOMPLETE"), - ("assertion_blocker", "IR_ASSERTION_COVERAGE_INCOMPLETE"), - ("cross_ref", "IR_ASSERTION_COVERAGE_INCOMPLETE"), - ("evidence_packet", "IR_READY_WITHOUT_EVIDENCE"), + ("provider_evidence", "VISUAL_SPEC_PROVIDER_EVIDENCE_MISSING"), + ("provider_evidence_blocker", "VISUAL_SPEC_PROVIDER_EVIDENCE_MISSING"), + ("product_ambiguity", "VISUAL_SPEC_PRODUCT_AMBIGUITY_UNRESOLVED"), + ("locator", "VISUAL_SPEC_LOCATOR_STRATEGY_INVALID"), + ("ownership", "VISUAL_SPEC_DOWNSTREAM_OWNERSHIP_LEAK"), + ("assertion_coverage", "VISUAL_SPEC_ASSERTION_COVERAGE_INCOMPLETE"), + ("assertion_blocker", "VISUAL_SPEC_ASSERTION_COVERAGE_INCOMPLETE"), + ("cross_ref", "VISUAL_SPEC_ASSERTION_COVERAGE_INCOMPLETE"), + ("evidence_packet", "VISUAL_SPEC_READY_WITHOUT_EVIDENCE"), ], ) -def test_structured_ir_validator_blocks_readiness_failures(edit_kind, expected_blocker): - work_dir = ROOT / ".tmp" / f"test-structured-ir-{edit_kind}" +def test_visual_spec_package_validator_blocks_readiness_failures(edit_kind, expected_blocker): + work_dir = ROOT / ".tmp" / f"test-visual-spec-package-{edit_kind}" if work_dir.exists(): shutil.rmtree(work_dir) - ir_dir = work_dir / "visual-design" / "structured-ir" - write_structured_ir_fixture(ir_dir) + package_dir = work_dir / "visual-design" / "visual-spec-package" + write_visual_spec_package_fixture(package_dir) if edit_kind == "provider_evidence": - text = (ir_dir / "structured-ir.yaml").read_text(encoding="utf-8") - (ir_dir / "structured-ir.yaml").write_text( + text = (package_dir / "visual-spec.yaml").read_text(encoding="utf-8") + (package_dir / "visual-spec.yaml").write_text( text.replace("provider_evidence_complete: true", "provider_evidence_complete: false"), encoding="utf-8", ) elif edit_kind == "provider_evidence_blocker": - text = (ir_dir / "structured-ir.yaml").read_text(encoding="utf-8") - (ir_dir / "structured-ir.yaml").write_text( - text.replace(" blockers: []", " blockers: [IR_PROVIDER_EVIDENCE_MISSING]", 1), + text = (package_dir / "visual-spec.yaml").read_text(encoding="utf-8") + (package_dir / "visual-spec.yaml").write_text( + text.replace(" blockers: []", " blockers: [VISUAL_SPEC_PROVIDER_EVIDENCE_MISSING]", 1), encoding="utf-8", ) elif edit_kind == "product_ambiguity": - text = (ir_dir / "structured-ir.yaml").read_text(encoding="utf-8") - (ir_dir / "structured-ir.yaml").write_text( + text = (package_dir / "visual-spec.yaml").read_text(encoding="utf-8") + (package_dir / "visual-spec.yaml").write_text( text.replace("product_ambiguities: []", "product_ambiguities:\n - Save disabled conditions are not specified."), encoding="utf-8", ) elif edit_kind == "locator": - text = (ir_dir / "structured-ir.yaml").read_text(encoding="utf-8") - (ir_dir / "structured-ir.yaml").write_text( + text = (package_dir / "visual-spec.yaml").read_text(encoding="utf-8") + (package_dir / "visual-spec.yaml").write_text( text.replace(" value: button[name='Save']", " value: '#save-button'"), encoding="utf-8", ) elif edit_kind == "ownership": - text = (ir_dir / "structured-ir.yaml").read_text(encoding="utf-8") - (ir_dir / "structured-ir.yaml").write_text( + text = (package_dir / "visual-spec.yaml").read_text(encoding="utf-8") + (package_dir / "visual-spec.yaml").write_text( text.replace(" blockers: []", " blockers: []\n code_component: SaveButton", 1), encoding="utf-8", ) elif edit_kind == "assertion_coverage": - text = (ir_dir / "ir-assertions.yaml").read_text(encoding="utf-8") - (ir_dir / "ir-assertions.yaml").write_text( + text = (package_dir / "visual-spec-assertions.yaml").read_text(encoding="utf-8") + (package_dir / "visual-spec-assertions.yaml").write_text( text.replace(" ci_suitability: ci_low_cost", " ci_suitability: manual_review"), encoding="utf-8", ) elif edit_kind == "assertion_blocker": - text = (ir_dir / "ir-assertions.yaml").read_text(encoding="utf-8") - (ir_dir / "ir-assertions.yaml").write_text( - text.replace(" blockers: []", " blockers: [IR_PROVIDER_EVIDENCE_MISSING]", 1), + text = (package_dir / "visual-spec-assertions.yaml").read_text(encoding="utf-8") + (package_dir / "visual-spec-assertions.yaml").write_text( + text.replace(" blockers: []", " blockers: [VISUAL_SPEC_PROVIDER_EVIDENCE_MISSING]", 1), encoding="utf-8", ) elif edit_kind == "cross_ref": - text = (ir_dir / "ir-assertions.yaml").read_text(encoding="utf-8") - (ir_dir / "ir-assertions.yaml").write_text( - text.replace(" - IR-home-save-default", " - IR-missing"), + text = (package_dir / "visual-spec-assertions.yaml").read_text(encoding="utf-8") + (package_dir / "visual-spec-assertions.yaml").write_text( + text.replace(" - VS-home-save-default", " - VS-missing"), encoding="utf-8", ) elif edit_kind == "evidence_packet": - (ir_dir / "ir-evidence-packet.md").write_text( + (package_dir / "visual-spec-evidence-packet.md").write_text( "---\n" "ready_gate: BLOCKED\n" - "blockers: [IR_PROVIDER_EVIDENCE_MISSING]\n" + "blockers: [VISUAL_SPEC_PROVIDER_EVIDENCE_MISSING]\n" "source_ref_count: 1\n" "extracted_item_count: 1\n" "generated_at: '2026-07-01T00:00:00Z'\n" @@ -2092,7 +2089,7 @@ def test_structured_ir_validator_blocks_readiness_failures(edit_kind, expected_b ) result = subprocess.run( - [sys.executable, str(STRUCTURED_IR_VALIDATOR), "--json", str(ir_dir)], + [sys.executable, str(VISUAL_SPEC_PACKAGE_VALIDATOR), "--json", str(package_dir)], cwd=ROOT, text=True, capture_output=True, diff --git a/tests/integrations/community_defaults.py b/tests/integrations/community_defaults.py index 5944d14b53..8b49da1e1b 100644 --- a/tests/integrations/community_defaults.py +++ b/tests/integrations/community_defaults.py @@ -30,8 +30,6 @@ "speckit.discovery.poc", "speckit.discovery.decision", "speckit.intake.visual-design", - "speckit.intake.figma2htmlssot", - "speckit.intake.ir", "speckit.intake.prd", "speckit.intake.test-cases", "speckit.preview.low-md",