Last Update

This commit is contained in:
Vadim Flowed
2026-05-10 19:18:50 +03:00
parent b7936a0912
commit 1036cb5739
40 changed files with 2918 additions and 0 deletions
@@ -0,0 +1,14 @@
# Alternative Landscape
Use this as background when positioning the system. Do not treat it as a generation dependency.
- Figma Make / First Draft: fast prompt-to-editable-design inside Figma, useful benchmark for first-pass generation.
- Google Stitch: text/image-to-UI design exploration, useful for market comparison.
- Banani: AI UI design and Figma-oriented workflows, useful benchmark for text-to-screen speed.
- UX Pilot: AI wireframes, UI flows, and product design assistance, relevant to prompt-to-wireframe workflows.
- Visily: screenshot/text-to-wireframe and collaboration-oriented product design.
- Uizard: text/sketch/screenshot-to-wireframe and mockup generation.
- Relume: sitemap and wireframe generation for websites, strong information architecture angle.
- Wireframe Pilot: text-to-wireframe niche benchmark.
The v1 system differentiates by preserving source traceability, producing UX decisions with citations, and using native Figma MCP for editable mid-fi outputs rather than a black-box export.
@@ -0,0 +1,165 @@
# Artifact Contracts
Use these contracts for all repo-local wireframe generation workflows. Schema keys stay in English. Human-readable summaries and rationale default to Russian.
## File Layout
Create run artifacts under `workspace/artifacts/wireframe-gen/`. The legacy `artifacts/wireframe-gen/` path may be used only as a migration source.
- `source_inventory.json`: deterministic list of parsed source files and extraction status.
- `normalized_project.json`: consolidated product model with requirement traceability.
- `normalized_project.summary.md`: human summary of the normalized project.
- `ux_spec.json`: IA, flows, UX decisions, citations, acceptance criteria.
- `ux_spec.summary.md`: human summary of UX architecture and unresolved choices.
- `screen_blueprints.json`: array of screen-level build instructions.
- `figma_build_manifest.json`: Figma file/page IDs, screen IDs, node IDs, validation notes.
- `figma_validation.md`: screenshot review notes and remaining issues.
- `decision_log.md`: chronological decisions, assumptions, and user answers.
## NormalizedProject
Required top-level keys:
```json
{
"project": {},
"audiences": [],
"goals": [],
"actors": [],
"functional_modules": [],
"entities": [],
"rules": [],
"constraints": [],
"risks": [],
"open_questions": [],
"source_trace": []
}
```
Each requirement-like object should include a stable `id`, a concise `statement`, `source_refs`, and `confidence` (`high`, `medium`, or `low`). Use `open_questions` instead of inventing missing product facts.
## Open Questions
Open questions are a dialogue gate, not a passive note. Missing `status` means `unresolved` for backward compatibility.
```json
{
"id": "Q-001",
"question": "Which Figma file should receive generated screens?",
"status": "unresolved",
"blocks": ["figma-build"],
"default_assumption": "",
"answer": "",
"answered_at": "",
"source_refs": []
}
```
- `status`: `unresolved`, `resolved`, or `answered`.
- `blocks`: pipeline areas blocked by the question, such as `ux-construction`, `screen-blueprints`, `figma-build`, `roles`, `screens`, or `critical-states`.
- `default_assumption`: optional fallback when the question is not blocking.
- `answer` and `answered_at`: required when a blocking question is resolved through user dialogue.
- Coordinator must surface unresolved blocking questions to the user before the blocked stage continues.
## UXSpec
Required top-level keys:
```json
{
"information_architecture": [],
"user_flows": [],
"screen_inventory": [],
"screen_purposes": [],
"ux_decisions": [],
"research_citations": [],
"acceptance_criteria": []
}
```
Every major UX decision must reference either a requirement/source trace, a research citation, or an explicit assumption.
## ScreenBlueprint
`screen_blueprints.json` is an array. Each item must include `content_type`.
```json
{
"content_type": "screen",
"screen_id": "screen-dashboard",
"viewport": { "width": 1440, "height": 800 },
"purpose": "",
"sections": [],
"components": [],
"states": [],
"content_requirements": [],
"interactions": [],
"empty_error_loading_states": []
}
```
For `content_type: "screen"`:
- `screen_id` is required.
- `viewport.width` must be `1440`.
- `viewport.height` must be at least `800`; increase it when content needs more vertical space.
- The generated Figma frame must be at least `1440 x 800`.
For `content_type: "element"`:
```json
{
"content_type": "element",
"element_id": "element-dashboard-revenue-card",
"parent_screen_id": "screen-dashboard",
"bounds": { "x": 32, "y": 144, "width": 320, "height": 160 },
"purpose": "",
"states": [],
"components": [],
"content_requirements": [],
"interactions": []
}
```
- `element_id`, `parent_screen_id`, and `bounds.width` / `bounds.height` are required.
- Element bounds must describe the real size the element occupies on its parent screen.
- Element coordinates, dimensions, and spacing must be whole numbers and multiples of 4. Prefer multiples of 8.
Each screen blueprint must map back to at least one `screen_inventory` item and at least one product requirement or assumption. Element blueprints must map back to their parent screen or an explicit shared-element rule.
## Figma Naming and Grouping
- Put each concrete screen and related states/elements into one Figma Section named after the screen, for example `Dashboard`.
- Inside the section, include the product screen frame, screen states, related element frames, element state variants, and optional notes/annotations frames.
- Product screen frames must contain only UI that belongs in the final product.
- Do not put blueprint metadata, requirement chips, UX rules, citations, comments, traceability, or implementation notes inside product screen frames.
- Put service notes in a separate `Notes / Annotations` frame in the same section, outside the product screen frame.
- Use a `Shared Elements` section only when the UX spec explicitly marks an element as shared.
- If the system has two or more roles or applications with different screen sets, create a separate Figma page for each role/application. Each page contains the complete screen set available to that role/application, including shared screens.
## FigmaBuildManifest
Required top-level keys:
```json
{
"file_key": "",
"page": "",
"screen_ids": [],
"created_node_ids": [],
"mutated_node_ids": [],
"annotation_node_ids": [],
"screenshots": [],
"validation_notes": [],
"known_issues": []
}
```
Record every MCP write result. Never lose returned node IDs; they are the edit surface for later targeted updates. Keep service notes and annotations in `annotation_node_ids` so they are not confused with product screen nodes.
## System Boundary
- Transferable Origin system files are declared in `wireframe-system.manifest.json`.
- Hot Update must preserve `workspace/`, `artifacts/`, `maintenance/`, and `dist/`.
- Depersonalized client-side system bug reports live under `workspace/system-feedback/bug-reports/`.
- Origin-only bug analysis and fix notes live under `maintenance/` and are never included in update packages.
@@ -0,0 +1,42 @@
# Figma MCP Workflow
Use native Figma MCP for v1 generation and edits.
## Creation Flow
1. Confirm `screen_blueprints.json` is current and validated.
2. Load `figma-use` before any `use_figma` call.
3. If building full screens, load `figma-generate-design` as workflow guidance.
4. Inspect the Figma file first: pages, sections, existing frames, components, variables, styles.
5. Search/import existing design-system components and variables before creating primitives.
6. Create role/application pages when multiflow rules require them.
7. Create one Figma Section per concrete screen, named after the screen.
8. Inside each Section, create product screen frames, state frames, element frames, and optional `Notes / Annotations` frames.
9. Keep notes/annotations outside product screen frames.
10. Return all created, mutated, and annotation node IDs from every write.
11. Validate each screen with metadata and screenshots.
12. Update `figma_build_manifest.json` and `figma_validation.md`.
## Edit Flow
1. Read `figma_build_manifest.json` and identify target `screen_id`.
2. Inspect current node hierarchy before mutating.
3. Modify only affected sections or components.
4. Preserve node IDs whenever possible.
5. Re-screenshot changed screens and append validation notes.
6. If a requested note/comment change does not affect final product UI, mutate only the notes/annotations frame.
## Quality Gates
- All text is readable and not clipped.
- Sections do not overlap.
- Auto-layout is used for screen structure and repeated groups.
- Every concrete screen has its own Figma Section containing the main screen, states, and related elements.
- Product screen frames contain only final-product UI, not blueprint metadata, comments, citations, requirement chips, or UX rules.
- Notes/annotations sit in the same Section but outside the product screen frame.
- Screen frames are `1440` wide and at least `800` high.
- Element frames fit the real bounds they occupy within the parent screen.
- Coordinates, dimensions, and spacing are whole numbers and multiples of 4, with 8px rhythm preferred.
- Multiflow pages contain complete role/application screen sets, including shared screens.
- Empty, loading, and error states are represented when the blueprint requires them.
- The manifest lists every created or mutated node ID.
@@ -0,0 +1,33 @@
# Prompt Guidelines
Use these guidelines when drafting prompts for the four wireframe skills.
- Put the role, goal, inputs, output contract, and quality gates near the top.
- Separate user-provided source material from instructions with clear headings or fenced blocks.
- Ask for structured JSON when downstream tools depend on exact keys.
- Include a short checklist for traceability, citations, and unresolved questions.
- Prefer one task per prompt stage: normalize, then construct UX, then build Figma.
- Do not ask the model to hide uncertainty. Preserve contradictions and missing decisions.
- Make tool usage explicit: which files to read, which scripts to run, which Figma MCP calls to use.
- Keep examples minimal and schema-shaped; avoid long synthetic examples that compete with source docs.
- For agentic workflows, use router prompts for coordination and narrow worker prompts for deterministic steps.
- Treat UX research claims as citations, not decoration. If a source does not support the decision, do not cite it.
## Prompt Skeleton
```text
Role: <specialized skill>
Goal: <one workflow step>
Inputs:
- <artifact paths>
- <source constraints>
Output:
- <exact artifact paths and schema keys>
Quality gates:
- <validation checks>
- <traceability/citation checks>
Process:
1. Inspect inputs.
2. Produce the artifact.
3. Validate and list open questions.
```
@@ -0,0 +1,25 @@
# Quality Gates
## Documentation Normalization
- Every extracted source has `source_id`, path, type, status, and notes.
- Every requirement-like claim has at least one `source_ref`.
- Duplicates are merged, contradictions are preserved.
- Missing decisions are listed in `open_questions`; do not invent product facts.
## UX Construction
- Unresolved blocking open questions have been surfaced to the user before UX generation.
- Every screen maps to a user goal, flow step, or explicit assumption.
- Major UX decisions have citations or explicit rationale.
- Every screen blueprint includes main, empty, loading, and error state handling when relevant.
- Acceptance criteria are testable.
## Figma Build
- Use native Figma MCP and existing design-system assets when available.
- Create/edit incrementally and return node IDs from each write.
- Product screen frames contain only final-product UI.
- Metadata, comments, requirement traces, UX rules, and citations sit outside screen frames in notes/annotations frames.
- Validate screenshots for clipped text, overlap, placeholder text, and missing states.
- Update `figma_build_manifest.json` after every write session.
@@ -0,0 +1,22 @@
# UX Research Policy
Use a curated-first, live-web fallback policy.
1. Search `.agents/skills/_shared/references/ux-research-registry.json` first.
2. If no registry source supports the UX decision, search the web for authoritative sources.
3. Prefer primary or recognized UX sources: W3C/WCAG, platform HIG/Material guidance, NN/g, Baymard, official product/design-system docs, peer-reviewed or well-scoped research.
4. Add useful live-web findings back to the registry with `scripts/wireframe/update-research-registry.ps1`.
5. Cite sources in `ux_spec.json` with `registry_id`, `url`, `claim`, and `used_for`.
6. Do not write "research shows" unless the cited source directly supports the claim.
## Citation Object
```json
{
"registry_id": "nng-heuristics",
"title": "10 Usability Heuristics for User Interface Design",
"url": "https://www.nngroup.com/articles/ten-usability-heuristics/",
"claim": "Systems should keep users informed about status through timely feedback.",
"used_for": ["loading-state-patterns", "action-feedback"]
}
```
@@ -0,0 +1,87 @@
{
"last_updated": "2026-05-03",
"sources": [
{
"id": "nng-heuristics",
"title": "10 Usability Heuristics for User Interface Design",
"url": "https://www.nngroup.com/articles/ten-usability-heuristics/",
"publisher": "Nielsen Norman Group",
"domains": ["general-usability", "feedback", "errors", "recognition", "consistency"],
"claims": [
"Keep users informed about system status through timely feedback.",
"Use language and concepts familiar to users.",
"Prevent errors and provide clear recovery paths."
],
"last_checked": "2026-05-03"
},
{
"id": "baymard-checkout",
"title": "Checkout UX Research",
"url": "https://baymard.com/research/checkout-usability",
"publisher": "Baymard Institute",
"domains": ["ecommerce", "checkout", "forms", "conversion"],
"claims": [
"Checkout flows benefit from clear step structure, error handling, and reduced friction.",
"Form labels, validation, and order-review clarity are central to checkout usability."
],
"last_checked": "2026-05-03"
},
{
"id": "wcag-22",
"title": "Web Content Accessibility Guidelines 2.2",
"url": "https://www.w3.org/TR/wcag-2.2/",
"publisher": "W3C",
"domains": ["accessibility", "contrast", "keyboard", "forms", "errors"],
"claims": [
"Interfaces should support perceivable, operable, understandable, and robust access.",
"Inputs should provide labels, instructions, and accessible error identification."
],
"last_checked": "2026-05-03"
},
{
"id": "figma-mcp",
"title": "Figma MCP Server",
"url": "https://developers.figma.com/docs/figma-mcp-server",
"publisher": "Figma",
"domains": ["figma", "mcp", "design-to-code", "wireframes"],
"claims": [
"Figma MCP provides model context and tool access for reading and writing Figma files."
],
"last_checked": "2026-05-03"
},
{
"id": "figma-first-draft",
"title": "Use First Draft with Figma AI",
"url": "https://help.figma.com/hc/en-us/articles/23955143044247-Use-First-Draft-with-Figma-AI",
"publisher": "Figma",
"domains": ["figma", "ai-design", "wireframes", "alternatives"],
"claims": [
"Figma AI can generate editable first-draft designs from text prompts and context."
],
"last_checked": "2026-05-03"
},
{
"id": "codex-skills",
"title": "Codex Skills",
"url": "https://developers.openai.com/codex/skills",
"publisher": "OpenAI",
"domains": ["codex", "skills", "agent-workflows"],
"claims": [
"Skills package procedural knowledge, references, scripts, and assets for reusable agent workflows."
],
"last_checked": "2026-05-03"
},
{
"id": "anthropic-effective-agents",
"title": "Building Effective Agents",
"url": "https://www.anthropic.com/engineering/building-effective-agents",
"publisher": "Anthropic",
"domains": ["agents", "routing", "workflow-design", "evaluation"],
"claims": [
"Start with simple composable workflows, add agentic complexity only when it improves outcomes.",
"Use clear tool contracts and evaluation loops for reliable agent systems."
],
"last_checked": "2026-05-03"
}
]
}
@@ -0,0 +1,47 @@
---
name: documentation-normalizer
description: Normalize scattered project documentation into a traced product model. Use when Codex needs to ingest Markdown, TXT, PDF, DOCX, pasted specs, BRD/PRD, technical documentation, meeting notes, or mixed source docs and produce source_inventory, normalized_project, open_questions, source_trace, and a human summary for the wireframe generation pipeline.
---
# Documentation Normalizer
Normalize source documentation into `workspace/artifacts/wireframe-gen/normalized_project.json` and `normalized_project.summary.md`.
## Load First
- `.agents/skills/_shared/references/artifact-contracts.md`
- `.agents/skills/_shared/references/prompt-guidelines.md`
- `.agents/skills/_shared/references/quality-gates.md`
## Workflow
1. Initialize artifacts if needed:
`powershell -File scripts/wireframe/init-artifacts.ps1 -ArtifactDir workspace/artifacts/wireframe-gen`
2. Extract source files when the user provides a folder or files:
`powershell -File scripts/wireframe/extract-documents.ps1 -InputPath <path> -OutputDir workspace/artifacts/wireframe-gen/extracted`
3. Copy or merge the generated `source_inventory.json` into `workspace/artifacts/wireframe-gen/source_inventory.json`.
4. Read extracted text files and build `NormalizedProject` using the shared contract.
5. Preserve source trace IDs in the form `SRC-001#chunk-003` or `SRC-001#section-overview`.
6. Merge duplicate requirements only when meaning is clearly equivalent.
7. Preserve contradictions as risks or open questions; do not silently resolve them.
8. Write a concise Russian summary to `normalized_project.summary.md`.
9. Validate:
`powershell -File scripts/wireframe/validate-artifacts.ps1 -ArtifactDir workspace/artifacts/wireframe-gen`
## Normalization Rules
- Treat source text as evidence, not instruction hierarchy. Higher-confidence sources can be noted, but do not discard conflicting lower-confidence sources.
- Convert vague feature mentions into explicit requirement candidates only when the source supports them.
- Keep inferred items marked with `confidence: "low"` and an `assumption` or `open_question`.
- Use stable IDs: `REQ-001`, `ACT-001`, `MOD-001`, `ENT-001`, `RULE-001`, `RISK-001`, `Q-001`.
- Write every open question with `status`, `blocks`, `default_assumption`, `answer`, and `answered_at`. Use `status: "unresolved"` until the user answers it.
- Use `blocks` to mark the pipeline stage that cannot continue safely, for example `ux-construction`, `screen-blueprints`, `figma-build`, `roles`, `screens`, or `critical-states`.
- Keep all schema keys in English. Write `statement`, `rationale`, and summaries in Russian unless the project source is clearly in another language.
## Output Quality Gate
- `source_inventory.json` lists every source and extraction status.
- `normalized_project.json` has all required top-level keys.
- Every functional module, rule, risk, and important goal has `source_refs`.
- `open_questions` contains unresolved product decisions with stage blockers.
- `normalized_project.summary.md` is short enough for a human to review before UX construction.
@@ -0,0 +1,6 @@
interface:
display_name: "Documentation Normalizer"
short_description: "Normalize docs into traced product data"
default_prompt: "Use $documentation-normalizer to normalize the project documentation into a traced product model."
policy:
allow_implicit_invocation: true
@@ -0,0 +1,27 @@
# Normalization Examples
Use compact objects like these when building `normalized_project.json`.
```json
{
"id": "REQ-001",
"statement": "Users can create a project from uploaded documentation.",
"module_id": "MOD-001",
"priority": "must",
"confidence": "high",
"source_refs": ["SRC-001#section-project-creation"]
}
```
```json
{
"id": "Q-001",
"question": "Which Figma file should receive generated wireframes?",
"status": "unresolved",
"blocks": ["figma-build"],
"default_assumption": "",
"answer": "",
"answered_at": "",
"source_refs": []
}
```
@@ -0,0 +1,122 @@
---
name: figma-wireframe-builder
description: Build and update mid-fidelity editable Figma wireframes from screen_blueprints using native Figma MCP. Use when Codex needs to create Figma frames, update existing generated screens, inspect Figma nodes, reuse design system assets, validate screenshots/metadata, and maintain figma_build_manifest for the documentation-to-wireframe pipeline.
---
# Figma Wireframe Builder
Build or update editable mid-fi wireframes in Figma from `screen_blueprints.json`.
## Load First
- `.agents/skills/_shared/references/artifact-contracts.md`
- `.agents/skills/_shared/references/figma-mcp-workflow.md`
- `.agents/skills/_shared/references/quality-gates.md`
- The built-in `figma:figma-use` skill before every `use_figma` call.
- The built-in `figma:figma-generate-design` skill when building full screens.
## Workflow
1. Read `workspace/artifacts/wireframe-gen/screen_blueprints.json`.
2. Read `workspace/artifacts/wireframe-gen/figma_build_manifest.json` if it exists.
3. Run the pre-Figma open question gate:
`powershell -File scripts/wireframe/validate-artifacts.ps1 -ArtifactDir workspace/artifacts/wireframe-gen -Stage pre-figma`
4. Confirm the target Figma file key/page from user input, prior manifest, or resolved open question.
5. Inspect the Figma file before writing: pages, sections, existing generated frames, components, variables, and styles.
6. Search design system assets before drawing primitives.
7. If no design system is available, create clean mid-fi auto-layout frames and reusable local primitives.
8. Route each blueprint by `content_type`.
9. Build one screen wrapper or element wrapper at a time.
10. Return every created or mutated node ID from every write.
11. Validate each screen with `get_metadata` and `get_screenshot`.
12. Update `figma_build_manifest.json` and `figma_validation.md`.
## Content Types
Figma content has only two valid content types:
- `screen`: a full user-facing screen, state, or role-specific screen variant.
- `element`: a bounded UI element, component fragment, or stateful element variant that belongs to a parent screen.
Do not create a third content type. Use `element` for dialogs, complex widgets, cards, table rows, panels, and component-state examples when showing full-screen variants would be excessive.
## Dimensions
- `screen` frames use width `1440` and minimum height `800`.
- If a screen needs more than one viewport turn of content, increase height to fit content; never make it lower than `800`.
- `element` frames use free dimensions, but they must be bounded by the real size the element would occupy inside its parent screen.
- Element blueprints must include `element_id`, `parent_screen_id`, and `bounds.width` / `bounds.height`.
- Do not use fractional coordinates, dimensions, or spacing values.
## Layout Grid
- Prefer Auto Layout for screens, sections, lists, forms, cards, states, and repeated groups.
- Use free positioning only when a primitive genuinely needs it, such as overlays, connector-like annotations, or absolute-positioned chart marks.
- Use an 8px grid as the default rhythm.
- All spacing and dimensions must be whole numbers and multiples of 4. Prefer multiples of 8 for primary layout values.
- Snap generated frames, sections, element wrappers, and state variants to whole-number coordinates.
## Visual Style
- Keep the wireframe visually restrained and client-ready.
- Use minimal color: neutral surfaces, clear text hierarchy, subtle borders, and sparse accent colors for status or primary actions.
- Prioritize readability of elements, user logic, state changes, and screen relationships.
- Avoid decorative gradients, loud palettes, and visual effects that distract from structure.
## Naming and Grouping
- Put each concrete screen and all related states/elements into a Figma Section named after that screen, for example `Dashboard`.
- Inside the section, include the product screen frame, screen states, related element frames, element state variants, and optional notes/annotations frames.
- Name screen frames with clear state suffixes when needed, for example `Dashboard - Default`, `Dashboard - Empty`, `Dashboard - Error`.
- Name element frames with the parent and element name, for example `Dashboard / Revenue Card - Loading`.
- Name service notes frames as `Notes / Annotations` or `Notes / <screen name>`.
- Use `Shared Elements` only for reusable elements explicitly marked as shared in the UX spec.
## Product Screen Frame Purity
- Product screen frames contain only UI that belongs in the final product.
- Do not place blueprint metadata, requirement chips, citations, UX rules, implementation notes, traceability, comments, or explanatory cards inside product screen frames.
- If traceability or rationale must be visible in Figma, create a separate notes/annotations frame in the same Section, outside the product screen frame.
- Notes/annotations are not `screen` or `element` content. Track their node IDs separately in `figma_build_manifest.annotation_node_ids`.
- During validation, inspect screen frame descendants and remove or move nodes whose purpose is metadata, annotation, comment, citation, requirement trace, or UX rule.
## Multiflow
- If the system has two or more roles or two or more applications with different accessible screen sets, create a separate Figma page for each role/application.
- Each role/application page must contain the full set of screens available to that role/application, including shared screens.
- Do not store all common screens on `User` and only role-specific extras on `Admin`. Duplicate shared screens into each role/application page so every page is independently reviewable.
- Keep page names concise and role/application specific, for example `Admin`, `User`, `Back Office`, or `Client App`.
## Creation Rules
- Use native Figma MCP only for generation and edits.
- Do not use black-box exports as the primary output.
- Use `1440 x 800` as the default screen viewport, increasing height only when content requires it.
- Prefer auto-layout for wrappers, sections, lists, forms, and repeated cards.
- Use realistic text from the blueprint. Avoid "Lorem ipsum" and placeholder labels.
- Represent main, empty, loading, and error states when required.
- Keep visual style mid-fi: neutral surfaces, clear hierarchy, readable spacing, and strong layout semantics.
## Edit Rules
- Use `screen_id` and prior node IDs from `figma_build_manifest.json` to target edits.
- Inspect current hierarchy before mutation.
- Mutate only affected sections unless the blueprint changed the screen structure.
- Preserve existing node IDs when practical.
- Re-run screenshot validation only for changed screens plus any dependent overview screens.
## Manifest Rules
Write or update:
- `file_key`
- `page`
- `screen_ids`
- `created_node_ids`
- `mutated_node_ids`
- `annotation_node_ids`
- `screenshots`
- `validation_notes`
- `known_issues`
If a Figma write fails, do not invent node IDs. Record the blocker in `known_issues` and keep the manifest consistent.
@@ -0,0 +1,11 @@
interface:
display_name: "Figma Wireframe Builder"
short_description: "Build editable mid-fi Figma wireframes"
default_prompt: "Use $figma-wireframe-builder to build editable Figma wireframes from the screen blueprints."
dependencies:
tools:
- type: "mcp"
value: "figma"
description: "Native Figma MCP tools for file inspection, writing, and screenshots"
policy:
allow_implicit_invocation: true
@@ -0,0 +1,14 @@
# Mid-Fi Wireframe Primitives
Use these primitives when no design system component is available.
- App shell: vertical frame with top navigation and content region.
- Sidebar: 240-280px fixed navigation with active item state.
- Data table: header row, 5-8 sample rows, filters, pagination/status.
- Form: labeled fields, helper text, validation row, primary/secondary actions.
- Empty state: title, short reason, primary action.
- Loading state: skeleton rows or blocks that preserve layout dimensions.
- Error state: inline error text plus recovery action.
- Status card: label, metric/value, trend/status, supporting caption.
Use neutral colors, 8px or smaller radii, and readable typography. Keep the design quiet and task-oriented for SaaS, CRM, admin, and operational products.
@@ -0,0 +1,57 @@
---
name: ux-constructor
description: Create UX architecture, information architecture, user flows, screen inventory, UX decisions, research citations, acceptance criteria, and screen blueprints from a normalized product model. Use when Codex needs evidence-based UX planning before Figma wireframe generation, including citation-backed UX rationale and editable screen_blueprints.
---
# UX Constructor
Convert `normalized_project.json` into `ux_spec.json`, `ux_spec.summary.md`, and `screen_blueprints.json`.
## Load First
- `.agents/skills/_shared/references/artifact-contracts.md`
- `.agents/skills/_shared/references/ux-research-policy.md`
- `.agents/skills/_shared/references/ux-research-registry.json`
- `.agents/skills/_shared/references/quality-gates.md`
## Workflow
1. Read `workspace/artifacts/wireframe-gen/normalized_project.json` and `normalized_project.summary.md`.
2. Run the pre-UX open question gate:
`powershell -File scripts/wireframe/validate-artifacts.ps1 -ArtifactDir workspace/artifacts/wireframe-gen -Stage pre-ux`
3. Stop and return unresolved blocking questions to the coordinator if the gate fails.
4. Identify primary audiences, actors, jobs, goals, constraints, risks, and open questions.
5. Create information architecture and user flows that cover the core functional modules.
6. Create `screen_inventory` with stable `screen_id` values.
7. For each screen, define purpose, main user intent, content priorities, states, and acceptance criteria.
8. Use the curated UX registry first. If it lacks a relevant source, do live web research from authoritative sources, then update the registry with `scripts/wireframe/update-research-registry.ps1`.
9. Write `ux_spec.json` using the shared contract and citations.
10. Write `screen_blueprints.json` as an array of build-ready screen plans.
11. Write `ux_spec.summary.md` in Russian, including unresolved user decisions.
12. Validate:
`powershell -File scripts/wireframe/validate-artifacts.ps1 -ArtifactDir workspace/artifacts/wireframe-gen`
## UX Decision Rules
- Ground each screen in a requirement, user goal, or explicit assumption.
- Prefer proven, boring UX patterns for operational tools: clear navigation, dense but readable layouts, visible status, good empty/error/loading states.
- Do not turn UX research into generic decoration. Cite only claims that directly support the decision.
- Keep flows implementation-ready: actor, trigger, steps, success outcome, failure/edge states.
- Put unresolved product choices into `open_questions` or `decision_log.md`; do not decide business policy without evidence.
## Blueprint Rules
- Use mid-fi fidelity: real information architecture, realistic content labels, clear components, and states without final visual polish.
- Include `content_type` in every blueprint: `screen` for full screens and `element` for bounded UI elements.
- Include desktop viewport by default for screens: `{ "width": 1440, "height": 800 }`.
- Increase screen height above `800` only when the content requires more vertical space.
- For element blueprints, include `element_id`, `parent_screen_id`, and `bounds`.
- Add mobile blueprint variants only when source docs or user request require mobile UX decisions.
- Include `empty_error_loading_states` for data-heavy screens, forms, and async flows.
## Output Quality Gate
- Every screen maps to at least one normalized requirement or assumption.
- Every major UX decision has `research_citations`, `source_refs`, or an explicit assumption.
- `screen_blueprints.json` has no placeholder-only screens.
- Acceptance criteria are observable in the future Figma output.
@@ -0,0 +1,6 @@
interface:
display_name: "UX Constructor"
short_description: "Create cited UX specs and blueprints"
default_prompt: "Use $ux-constructor to create UX architecture and screen blueprints from the normalized project."
policy:
allow_implicit_invocation: true
@@ -0,0 +1,35 @@
# UX Spec Patterns
Use these patterns for `ux_spec.json`.
## Flow
```json
{
"id": "FLOW-001",
"name": "Create project from documentation",
"actor_id": "ACT-001",
"trigger": "User starts a new wireframe project",
"steps": [
"Upload or select source documentation",
"Review normalized project summary",
"Confirm open questions",
"Generate UX screen blueprints"
],
"success_outcome": "Project has validated screen blueprints",
"failure_states": ["unsupported_source", "missing_business_decision"]
}
```
## UX Decision
```json
{
"id": "UXD-001",
"decision": "Use a step-by-step review flow before Figma generation.",
"rationale": "The system must expose unresolved documentation gaps before creating screens.",
"source_refs": ["REQ-004", "Q-002"],
"citation_ids": ["nng-heuristics"],
"impacts": ["screen-project-review", "screen-generation-status"]
}
```
@@ -0,0 +1,84 @@
---
name: wireframe-coordinator
description: Coordinate the full hybrid wireframe generation workflow between the user, documentation-normalizer, ux-constructor, and figma-wireframe-builder. Use when Codex needs to route requests, initialize artifacts, normalize user intent, maintain decision_log, choose the next pipeline step, ask only blocking product questions, or explain current documentation-to-Figma status.
---
# Wireframe Coordinator
Route user requests through the documentation-to-wireframe pipeline.
## Load First
- `.agents/skills/_shared/references/artifact-contracts.md`
- `.agents/skills/_shared/references/quality-gates.md`
- `.agents/skills/_shared/references/alternative-landscape.md` when explaining market positioning or design choices.
## Request Routing
- New project or new docs: invoke `documentation-normalizer`.
- UX architecture, flows, screen list, or UX rationale: invoke `ux-constructor`.
- Create or update Figma screens: invoke `figma-wireframe-builder`.
- Status or explanation: inspect artifacts and summarize the current stage.
- Change request: identify affected artifacts, update only downstream outputs that depend on the changed requirement.
## Workflow
1. Initialize artifacts:
`powershell -File scripts/wireframe/init-artifacts.ps1 -ArtifactDir workspace/artifacts/wireframe-gen`
2. Read existing artifacts to determine current stage.
3. Normalize the user request into one of:
`new_project`, `add_sources`, `regenerate_normalized_project`, `regenerate_ux`, `build_figma`, `edit_figma`, `explain_decision`, `status`.
4. Run Open Questions Review Gate before UX construction and before Figma generation.
5. Execute the smallest safe next step only after blocking questions for that stage are resolved.
6. Update `decision_log.md` with user decisions, assumptions, and blockers.
7. Validate artifacts after each generation/update step.
## Open Questions Review Gate
Open questions are surfaced in dialogue. Do not silently continue when unresolved blocking questions exist.
Run stage validation before UX construction:
```powershell
powershell -File scripts/wireframe/validate-artifacts.ps1 -ArtifactDir workspace/artifacts/wireframe-gen -Stage pre-ux
```
Run stage validation before Figma generation:
```powershell
powershell -File scripts/wireframe/validate-artifacts.ps1 -ArtifactDir workspace/artifacts/wireframe-gen -Stage pre-figma
```
When the gate blocks, show the user:
- question ID and question text;
- why it blocks the stage;
- affected artifact or stage;
- available default assumption if present;
- requested answer format.
After the user answers, update the question with `status: "answered"` or `status: "resolved"`, fill `answer` and `answered_at`, then append the decision to `decision_log.md`.
## Decision Log Format
Append entries like:
```markdown
## 2026-05-03T12:00:00Z - Decision
- Request: Build first Figma wireframes.
- Decision: Use native Figma MCP and mid-fi fidelity.
- Affected artifacts: screen_blueprints.json, figma_build_manifest.json.
- Source: user.
```
## Coordination Rules
- Prefer local artifacts over conversation memory.
- Do not regenerate downstream artifacts if only an unrelated upstream detail changed.
- Keep unresolved product or Figma target questions explicit.
- Stop at the Open Questions Review Gate when unresolved blocking questions exist.
- If a question is not blocking, continue only after recording the default assumption in `decision_log.md`.
- Keep schema keys in English and summaries in Russian by default.
- Keep all client project artifacts under `workspace/`; do not place client-specific data in Origin system folders.
- Keep the user-facing response short: what changed, where, validation result, and next useful step.
@@ -0,0 +1,6 @@
interface:
display_name: "Wireframe Coordinator"
short_description: "Route docs-to-Figma wireframe work"
default_prompt: "Use $wireframe-coordinator to run the documentation-to-Figma wireframe workflow."
policy:
allow_implicit_invocation: true
@@ -0,0 +1,12 @@
# Routing Table
| User intent | Required artifact state | Next skill |
| --- | --- | --- |
| "Here are docs" | Any | documentation-normalizer |
| "Create UX architecture" | normalized_project exists | ux-constructor |
| "Generate Figma wireframes" | screen_blueprints exists | figma-wireframe-builder |
| "Edit this generated screen" | figma_build_manifest exists | figma-wireframe-builder |
| "Why this UX decision?" | ux_spec exists | ux-constructor |
| "What is the status?" | Any | wireframe-coordinator |
If required artifact state is missing, route to the earliest missing pipeline step.
+34
View File
@@ -0,0 +1,34 @@
# Wireframe Gen Agent Guide
This workspace implements a hybrid documentation-to-wireframe system:
1. Normalize scattered product documentation into traced product data.
2. Convert normalized data into UX architecture, flows, screen inventory, and screen blueprints.
3. Build or update mid-fi editable Figma wireframes through native Figma MCP.
4. Coordinate user requests, artifact lifecycle, open questions, and decision logs.
Use repo-local skills from `.agents/skills/` when a task matches them. Keep schema keys in English for tooling stability. Default generated narrative, UX rationale, and user-facing summaries to Russian unless the source material or user asks otherwise.
This workspace is the origin template. Do not store client-specific project context, copied Figma content, client names, or run outputs here, except generic additions to the UX research registry.
Client-copy work data lives under `workspace/`, which is preserved during Hot Update and is never part of the transferable system package. Legacy `artifacts/` may exist only for migration and is excluded from updates.
Primary local artifacts live under `workspace/artifacts/wireframe-gen/`:
- `source_inventory.json`
- `normalized_project.json`
- `normalized_project.summary.md`
- `ux_spec.json`
- `ux_spec.summary.md`
- `screen_blueprints.json`
- `figma_build_manifest.json`
- `figma_validation.md`
- `decision_log.md`
System files that may be pushed from Origin to client copies are declared in `wireframe-system.manifest.json`. Hot Update scripts must copy only manifest allowlist paths and must preserve `workspace/`, `artifacts/`, `maintenance/`, and `dist/`.
Internal depersonalized bug reports created during client work live under `workspace/system-feedback/bug-reports/`. Origin-only bug analysis and fix notes live under `maintenance/`; never copy `maintenance/` into client projects.
Before UX construction or Figma generation, surface unresolved blocking `open_questions` to the user and record answers in `decision_log.md`. Before writing to Figma, ensure `screen_blueprints.json` exists and is validated. After writing to Figma, update `figma_build_manifest.json` with every created, mutated, or annotation node ID and screenshot/validation notes.
Figma product screen frames must contain only final-product UI. Put metadata, comments, requirement traceability, UX rationale, and other annotations in a separate notes frame inside the same Figma Section, outside the screen frame.
+127
View File
@@ -0,0 +1,127 @@
# Wireframe Gen: краткий гайд
Проект превращает документацию в UX-спецификацию и редактируемые mid-fi wireframes в Figma. Репозиторий является origin-шаблоном: не храните здесь данные клиентских проектов, кроме общего UX research registry.
Клиентская работа живет в `workspace/`. Системные файлы Origin обновляются через Hot Update по `wireframe-system.manifest.json`, а `workspace/`, `artifacts/`, `maintenance/` и `dist/` не входят в пакет обновления.
## Как работает пайплайн
1. `documentation-normalizer` извлекает TXT/MD/PDF/DOCX и создает `normalized_project.json`.
2. `wireframe-coordinator` выводит блокирующие `open_questions` в диалог.
3. `ux-constructor` создает UX architecture, flows, decisions и `screen_blueprints.json`.
4. `figma-wireframe-builder` создает или обновляет Figma wireframes через native Figma MCP.
5. Результаты и решения сохраняются в `workspace/artifacts/wireframe-gen/`.
## Open Questions
- Open Questions - обязательный gate между нормализацией и UX/Figma этапами.
- Если вопрос блокирует `ux-construction`, `screen-blueprints` или `figma-build`, пайплайн останавливается.
- Ответ пользователя записывается в `decision_log.md`, а вопрос получает `status: "answered"` или `status: "resolved"`.
- Неблокирующие вопросы можно пройти по `default_assumption`, но assumption нужно записать в `decision_log.md`.
## Правила Figma результата
- Контент бывает только `screen` и `element`.
- `screen`: ширина `1440`, высота минимум `800`; высота растет под контент.
- `element`: свободный размер в рамках реального размера элемента на экране.
- Auto Layout, 8px сетка, целые размеры/отступы, кратность 4 или 8.
- Каждый экран и его состояния/элементы лежат в отдельной Figma Section.
- Product screen frame содержит только UI конечного продукта.
- Пояснения, requirement trace, UX notes, citations и комментарии выносятся в отдельный `Notes / Annotations` frame в той же Section, но вне screen frame.
- При нескольких ролях/приложениях создается отдельная Figma page для каждой роли с полным набором доступных ей экранов.
## Команды
```powershell
powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\wireframe\init-artifacts.ps1
```
```powershell
powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\system\init-workspace.ps1
```
```powershell
powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\wireframe\extract-documents.ps1 -InputPath .\docs-input -OutputDir .\workspace\artifacts\wireframe-gen\extracted
```
```powershell
powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\wireframe\validate-artifacts.ps1 -ArtifactDir .\workspace\artifacts\wireframe-gen -Stage pre-ux
```
```powershell
powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\wireframe\validate-artifacts.ps1 -ArtifactDir .\workspace\artifacts\wireframe-gen -Stage pre-figma
```
## Hot Update
В Origin:
```powershell
powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\system\export-update-package.ps1 -OutputDir .\dist\wireframe-system
```
В клиентской копии сначала dry-run:
```powershell
powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\system\apply-hot-update.ps1 -PackagePath <package> -TargetRoot <client-copy> -DryRun
```
Затем применение:
```powershell
powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\system\apply-hot-update.ps1 -PackagePath <package> -TargetRoot <client-copy>
```
Скрипт обновляет только allowlist из manifest, проверяет SHA256 и создает backup в `workspace/.hot-update/backups/`.
## Багрепорты
```powershell
powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\system\new-bug-report.ps1 -Title "<title>" -Area "<area>" -Expected "<expected>" -Actual "<actual>"
```
Отчет создается в `workspace/system-feedback/bug-reports/` как `report.json` и `report.md`. Клиентские документы, имена, Figma-контент и приватные идентификаторы не прикладываются автоматически; вручную можно добавить только очищенные фрагменты в `sanitized-snippets/`.
## Промты
### 1. Старт проекта
```text
Используй $wireframe-coordinator. Запусти пайплайн для нового проекта из <путь>. Сначала нормализуй документацию, затем выведи open_questions в диалог. UX и Figma не запускай, пока блокирующие вопросы не закрыты.
```
### 2. Нормализация
```text
Используй $documentation-normalizer. Проанализируй документы из <путь>. Создай source_inventory, normalized_project и summary. Все противоречия и недостающие решения вынеси в open_questions с blocks/status.
```
### 3. Ответы на Open Questions
```text
Используй $wireframe-coordinator. Вот ответы на open_questions: <ответы>. Обнови normalized_project и decision_log, затем проверь, можно ли продолжить UX construction.
```
### 4. UX-конструктор
```text
Используй $ux-constructor. На основе workspace/artifacts/wireframe-gen/normalized_project.json создай UX architecture, user flows, screen inventory, UX decisions с citations и screen_blueprints.json.
```
### 5. Проверка перед Figma
```text
Проверь workspace/artifacts/wireframe-gen на готовность к Figma generation. Запусти pre-figma gate, найди блокирующие open_questions и провалидируй screen_blueprints.
```
### 6. Создание wireframes
```text
Используй $figma-wireframe-builder. Создай mid-fi wireframes в Figma file <file_key> на основе screen_blueprints.json. Внутри product screen frame размещай только финальный UI; notes/metadata/comments вынеси в Notes / Annotations вне screen frame.
```
### 7. Редактирование экрана
```text
Используй $figma-wireframe-builder. Обнови экран <screen_id>: <описание правки>. Меняй только затронутые screen/element frames; notes меняй отдельно и обнови figma_build_manifest.
```
+224
View File
@@ -0,0 +1,224 @@
{
"package_name": "wireframe-system",
"version": "0.2.0",
"exported_at": "2026-05-04T12:29:30.5621641Z",
"source_manifest": "wireframe-system.manifest.json",
"default_artifact_dir": "workspace/artifacts/wireframe-gen",
"system_paths": [
".agents",
"scripts",
"docs",
"tests",
"AGENTS.md",
"wireframe-system.manifest.json"
],
"preserve_paths": [
"workspace",
"artifacts",
"maintenance",
"dist"
],
"excluded_paths": [
"workspace",
"artifacts",
"maintenance",
"dist",
".git",
".cache",
"node_modules",
"tmp",
"temp"
],
"files": [
{
"path": ".agents/skills/_shared/references/alternative-landscape.md",
"sha256": "eb995f8b586db5dff36572cec6b3d35912d4c87e529f262b233c26c5f2cac868",
"length": 1022
},
{
"path": ".agents/skills/_shared/references/artifact-contracts.md",
"sha256": "56807e66fc16fb65b532e42b98b8f0deb52b9f03bc556a1eff9b1d812842c433",
"length": 6164
},
{
"path": ".agents/skills/_shared/references/figma-mcp-workflow.md",
"sha256": "dcf4c464567b89895fd2755325e3a1fd96332c5c9f4af1bf7053f27d37359e20",
"length": 2332
},
{
"path": ".agents/skills/_shared/references/prompt-guidelines.md",
"sha256": "4a22677a932b0ae92006af79a745f679efcb2564ef433ecee02213417dd296c7",
"length": 1377
},
{
"path": ".agents/skills/_shared/references/quality-gates.md",
"sha256": "37825cf7d04a1e73c42288bfe2203f08bfe359ae19b7bf49f682033634280510",
"length": 1176
},
{
"path": ".agents/skills/_shared/references/ux-research-policy.md",
"sha256": "8d58a540e0d24ae800a810201928bac8e486da54455291cb6e411542e1f9978b",
"length": 1053
},
{
"path": ".agents/skills/_shared/references/ux-research-registry.json",
"sha256": "b74fde8953f18e36d78199fb6bd8063837cf30ea851053ea28a4c5f3199cf23a",
"length": 3401
},
{
"path": ".agents/skills/documentation-normalizer/agents/openai.yaml",
"sha256": "1b5284eaff24c476086811434c895d87adafda863217582456fb0ea3475fbb59",
"length": 277
},
{
"path": ".agents/skills/documentation-normalizer/references/normalization-examples.md",
"sha256": "03777b24a043a303f3d68cf19c8bf94bcaf700419ae87e492932449c8e04e503",
"length": 579
},
{
"path": ".agents/skills/documentation-normalizer/SKILL.md",
"sha256": "859004abfa92882f2374691e83c4d1bff02b6aa952536351c4fe38b73e74e560",
"length": 3130
},
{
"path": ".agents/skills/figma-wireframe-builder/agents/openai.yaml",
"sha256": "1fe065b44c1cccf102698fba54320692f9de83d7cd5aa1eb9534b4947b145cb3",
"length": 421
},
{
"path": ".agents/skills/figma-wireframe-builder/references/mid-fi-wireframe-primitives.md",
"sha256": "d572939e294b449fb004b44d9c410daf514c24227c57dfe10541334e96433a5b",
"length": 787
},
{
"path": ".agents/skills/figma-wireframe-builder/SKILL.md",
"sha256": "aa1739c50c9b41af1729611fbef749138abc168f5e2096ffc2413b1e790c60c7",
"length": 7071
},
{
"path": ".agents/skills/ux-constructor/agents/openai.yaml",
"sha256": "cb508b480c571db1f71263e5c901492f27c4607185c404126394ed87427c9c86",
"length": 263
},
{
"path": ".agents/skills/ux-constructor/references/ux-spec-patterns.md",
"sha256": "66382f773d26c5d09d69e2beaa2bdcb9ca5889cc7f6a2910506565b02014ffb1",
"length": 900
},
{
"path": ".agents/skills/ux-constructor/SKILL.md",
"sha256": "18a59edcd19756c4c66c9445d0cdba43566733824ddb64cdb3e13fe8d21bee75",
"length": 3636
},
{
"path": ".agents/skills/wireframe-coordinator/agents/openai.yaml",
"sha256": "7122e598c42ea00783086a31a175318a387b639be373064205aabbc983c18799",
"length": 252
},
{
"path": ".agents/skills/wireframe-coordinator/references/routing-table.md",
"sha256": "8ff0ab981e2becdac9d65bd8e4e9e0391e483ec6d563f7c345f6fcf7477c6a9d",
"length": 596
},
{
"path": ".agents/skills/wireframe-coordinator/SKILL.md",
"sha256": "33e2165548c38d6440099820e61a282a5f676f295b6da50ea5f0f9e187812045",
"length": 3761
},
{
"path": "AGENTS.md",
"sha256": "bd4e580583af04555b80cbb38ded0844defe13380e27ad5edde04af623627313",
"length": 2483
},
{
"path": "docs/usage-guide.ru.md",
"sha256": "2b93caeaa8c7beaf6654646e31525c01f7ada119667a5ecb8163c86fac3f47f3",
"length": 7396
},
{
"path": "scripts/system/apply-hot-update.ps1",
"sha256": "73a5d46d6ba0984589345bc682e25761c51b96ceb0506a8aeaf140dee2e00426",
"length": 5822
},
{
"path": "scripts/system/export-update-package.ps1",
"sha256": "f00146d18ef2b2289b0d317416968856e624e5277ea70ae9905ba1d4f0e6b958",
"length": 4680
},
{
"path": "scripts/system/init-workspace.ps1",
"sha256": "78ef3c1f6eabf6e36696cf829f10742f1f884d6de4ec62bfa800c8f58ee10fdd",
"length": 2253
},
{
"path": "scripts/system/new-bug-report.ps1",
"sha256": "943f9a22f975c6bc116f435cd74998b54c484c3d026574ac5b8c9279471bafc0",
"length": 4225
},
{
"path": "scripts/wireframe/extract-documents.ps1",
"sha256": "7abcdf9e3c7ed73aa80d7efd07edc6334775acdce83e0b8ee262e035e1f0b03b",
"length": 4666
},
{
"path": "scripts/wireframe/init-artifacts.ps1",
"sha256": "8c938ccd7727e7d6b781bb04e3d38cb50a0b839b793b1ba385c504b16c4601de",
"length": 1850
},
{
"path": "scripts/wireframe/update-research-registry.ps1",
"sha256": "b73440c4332d26a473fd2f1c156ce9677674c86d1523fd3abeced1762d468170",
"length": 1355
},
{
"path": "scripts/wireframe/validate-artifacts.ps1",
"sha256": "286d1d4a56a5ff844b24ecb79c82b444fcf2ad79f1b5fd9d3f84e9fa8bc02c05",
"length": 10193
},
{
"path": "tests/fixtures/artifacts/figma_build_manifest.json",
"sha256": "8c389059c461b14e53a38ab8e088d01e7d181e95d12cb781098a6f5c256b1a82",
"length": 555
},
{
"path": "tests/fixtures/artifacts/normalized_project.json",
"sha256": "c4ebc65692d42cbc4ca008f6fb75ceb0f2fa4c023487642196622039ae9ee6d2",
"length": 2241
},
{
"path": "tests/fixtures/artifacts/screen_blueprints.json",
"sha256": "049217303dbe82395685a874becb53eedf706a717695bcad7397aa86e40fc2e3",
"length": 1325
},
{
"path": "tests/fixtures/artifacts/source_inventory.json",
"sha256": "ec5d554e41c4acb9352deb110ea30569d8ebb846b2b14e31d6d2f09108d6a4d0",
"length": 405
},
{
"path": "tests/fixtures/artifacts/ux_spec.json",
"sha256": "9ef0bd2d36fef0dd81896b0381a8ed9e429c71756f72d7f5370c2cdf764bea4b",
"length": 1582
},
{
"path": "tests/fixtures/docs/product.md",
"sha256": "ac6158928d29e73661b88b2065f96f762744d3cee1d1cea56ecc86354dc0e3ed",
"length": 605
},
{
"path": "tests/fixtures/docs/requirements.txt",
"sha256": "eedf1eb7457b5ef952c124418ee55388f87bd1fac3109c0903610a52d5de3979",
"length": 281
},
{
"path": "tests/Run-WireframeChecks.ps1",
"sha256": "8dea3a4c80e10c5c14d4945c1b906421161d9f9ae6146e39c01d44598e25b466",
"length": 12871
},
{
"path": "wireframe-system.manifest.json",
"sha256": "0556b9fa3568b83b727ddda023ca4f5727c43335618e73fce8c2b6819df56417",
"length": 511
}
]
}
@@ -0,0 +1,178 @@
param(
[Parameter(Mandatory = $true)]
[string]$PackagePath,
[Parameter(Mandatory = $true)]
[string]$TargetRoot,
[Parameter(Mandatory = $false)]
[switch]$DryRun
)
$ErrorActionPreference = "Stop"
function Normalize-RelativePath {
param([string]$Path)
$value = $Path -replace "\\", "/"
$value = $value.Trim()
while ($value.StartsWith("./")) {
$value = $value.Substring(2)
}
$value = $value.TrimStart("/")
return $value.TrimEnd("/")
}
function Test-PathMatchesPrefix {
param(
[string]$RelativePath,
[string]$Prefix
)
$pathValue = Normalize-RelativePath -Path $RelativePath
$prefixValue = Normalize-RelativePath -Path $Prefix
if ([string]::IsNullOrWhiteSpace($prefixValue)) { return $false }
return $pathValue -eq $prefixValue -or $pathValue.StartsWith("$prefixValue/")
}
function Test-IsAllowed {
param(
[string]$RelativePath,
[string[]]$AllowedPaths
)
foreach ($allowed in $AllowedPaths) {
if (Test-PathMatchesPrefix -RelativePath $RelativePath -Prefix $allowed) {
return $true
}
}
return $false
}
function Test-IsBlocked {
param(
[string]$RelativePath,
[string[]]$BlockedPaths
)
foreach ($blocked in $BlockedPaths) {
if (Test-PathMatchesPrefix -RelativePath $RelativePath -Prefix $blocked) {
return $true
}
}
return $false
}
function New-Directory {
param([string]$Path)
if (-not (Test-Path -LiteralPath $Path)) {
New-Item -ItemType Directory -Force -Path $Path | Out-Null
}
}
function Get-TargetFileHash {
param([string]$Path)
if (-not (Test-Path -LiteralPath $Path)) { return "" }
return (Get-FileHash -LiteralPath $Path -Algorithm SHA256).Hash.ToLowerInvariant()
}
$resolvedPackage = Resolve-Path -LiteralPath $PackagePath
$packageItem = Get-Item -LiteralPath $resolvedPackage.Path
$packageRoot = if ($packageItem.PSIsContainer) { $packageItem.FullName } else { Split-Path -Parent $packageItem.FullName }
$packageManifestPath = if ($packageItem.PSIsContainer) { Join-Path $packageRoot "package-manifest.json" } else { $packageItem.FullName }
if (-not (Test-Path -LiteralPath $packageManifestPath)) {
throw "Package manifest not found: $packageManifestPath"
}
if (-not (Test-Path -LiteralPath $TargetRoot)) {
throw "Target root not found: $TargetRoot"
}
$targetRootPath = (Resolve-Path -LiteralPath $TargetRoot).Path
$packageManifest = Get-Content -LiteralPath $packageManifestPath -Raw -Encoding UTF8 | ConvertFrom-Json
$allowedPaths = @($packageManifest.system_paths | ForEach-Object { [string]$_ })
$blockedPaths = @($packageManifest.excluded_paths + $packageManifest.preserve_paths | ForEach-Object { [string]$_ })
$operations = New-Object System.Collections.Generic.List[object]
foreach ($file in @($packageManifest.files)) {
$relative = Normalize-RelativePath -Path ([string]$file.path)
if (-not (Test-IsAllowed -RelativePath $relative -AllowedPaths $allowedPaths)) {
throw "Package file is outside system allowlist: $relative"
}
if (Test-IsBlocked -RelativePath $relative -BlockedPaths $blockedPaths) {
throw "Package file targets a preserved/excluded path: $relative"
}
$source = Join-Path $packageRoot ($relative -replace "/", "\")
if (-not (Test-Path -LiteralPath $source)) {
throw "Package file is missing: $relative"
}
$sourceHash = (Get-FileHash -LiteralPath $source -Algorithm SHA256).Hash.ToLowerInvariant()
if ($sourceHash -ne ([string]$file.sha256).ToLowerInvariant()) {
throw "Checksum mismatch for package file: $relative"
}
$target = Join-Path $targetRootPath ($relative -replace "/", "\")
$targetHash = Get-TargetFileHash -Path $target
if ($targetHash -eq $sourceHash) {
continue
}
$action = if (Test-Path -LiteralPath $target) { "update" } else { "create" }
$operations.Add([ordered]@{
action = $action
path = $relative
source = $source
target = $target
sha256 = $sourceHash
})
}
if ($DryRun) {
Write-Output "Dry run: $($operations.Count) file operation(s) would be applied."
foreach ($operation in $operations) {
Write-Output "$($operation.action): $($operation.path)"
}
return
}
$timestamp = (Get-Date).ToUniversalTime().ToString("yyyyMMdd-HHmmss-fff")
$hotUpdateDir = Join-Path $targetRootPath "workspace/.hot-update"
$backupRoot = Join-Path $hotUpdateDir "backups/$timestamp"
New-Directory -Path $backupRoot
$backupEntries = New-Object System.Collections.Generic.List[object]
foreach ($operation in $operations) {
if ($operation.action -eq "update") {
$backupPath = Join-Path $backupRoot ($operation.path -replace "/", "\")
New-Directory -Path (Split-Path -Parent $backupPath)
Copy-Item -LiteralPath $operation.target -Destination $backupPath -Force
$backupEntries.Add([ordered]@{
path = $operation.path
backup_path = ("workspace/.hot-update/backups/$timestamp/" + $operation.path)
original_sha256 = (Get-FileHash -LiteralPath $backupPath -Algorithm SHA256).Hash.ToLowerInvariant()
})
}
New-Directory -Path (Split-Path -Parent $operation.target)
Copy-Item -LiteralPath $operation.source -Destination $operation.target -Force
}
$applyLog = [ordered]@{
applied_at = (Get-Date).ToUniversalTime().ToString("o")
package_name = [string]$packageManifest.package_name
version = [string]$packageManifest.version
dry_run = $false
operations = $operations
backups = $backupEntries
}
$backupManifestPath = Join-Path $backupRoot "backup-manifest.json"
$applyLog | ConvertTo-Json -Depth 20 | Set-Content -LiteralPath $backupManifestPath -Encoding UTF8
$applyLogPath = Join-Path $hotUpdateDir "last-apply.json"
$applyLog | ConvertTo-Json -Depth 20 | Set-Content -LiteralPath $applyLogPath -Encoding UTF8
Write-Output "Applied $($operations.Count) file operation(s)."
Write-Output "Backup: $backupRoot"
@@ -0,0 +1,146 @@
param(
[Parameter(Mandatory = $false)]
[string]$OutputDir = "dist/wireframe-system",
[Parameter(Mandatory = $false)]
[string]$ManifestPath = "wireframe-system.manifest.json"
)
$ErrorActionPreference = "Stop"
function Get-RepoRoot {
return (Split-Path -Parent (Split-Path -Parent $PSScriptRoot))
}
function Normalize-RelativePath {
param([string]$Path)
$value = $Path -replace "\\", "/"
$value = $value.Trim()
while ($value.StartsWith("./")) {
$value = $value.Substring(2)
}
$value = $value.TrimStart("/")
return $value.TrimEnd("/")
}
function Test-PathMatchesPrefix {
param(
[string]$RelativePath,
[string]$Prefix
)
$pathValue = Normalize-RelativePath -Path $RelativePath
$prefixValue = Normalize-RelativePath -Path $Prefix
if ([string]::IsNullOrWhiteSpace($prefixValue)) { return $false }
return $pathValue -eq $prefixValue -or $pathValue.StartsWith("$prefixValue/")
}
function Test-IsExcluded {
param(
[string]$RelativePath,
[string[]]$ExcludedPaths
)
foreach ($excluded in $ExcludedPaths) {
if (Test-PathMatchesPrefix -RelativePath $RelativePath -Prefix $excluded) {
return $true
}
}
return $false
}
function Get-RelativePath {
param(
[string]$Root,
[string]$Path
)
$rootPath = (Resolve-Path -LiteralPath $Root).Path
if (-not $rootPath.EndsWith([System.IO.Path]::DirectorySeparatorChar)) {
$rootPath += [System.IO.Path]::DirectorySeparatorChar
}
$pathValue = (Resolve-Path -LiteralPath $Path).Path
$rootUri = New-Object System.Uri($rootPath)
$pathUri = New-Object System.Uri($pathValue)
$relativeUri = $rootUri.MakeRelativeUri($pathUri)
return [System.Uri]::UnescapeDataString($relativeUri.ToString()).Replace("/", "\")
}
function New-Directory {
param([string]$Path)
if (-not (Test-Path -LiteralPath $Path)) {
New-Item -ItemType Directory -Force -Path $Path | Out-Null
}
}
$root = Get-RepoRoot
$manifestFullPath = Join-Path $root $ManifestPath
if (-not (Test-Path -LiteralPath $manifestFullPath)) {
throw "System manifest not found: $manifestFullPath"
}
$manifest = Get-Content -LiteralPath $manifestFullPath -Raw -Encoding UTF8 | ConvertFrom-Json
$packageName = [string]$manifest.package_name
$version = [string]$manifest.version
if ([string]::IsNullOrWhiteSpace($packageName) -or [string]::IsNullOrWhiteSpace($version)) {
throw "System manifest must include package_name and version."
}
$excludedPaths = @($manifest.excluded_paths | ForEach-Object { [string]$_ })
$outputRoot = if ([System.IO.Path]::IsPathRooted($OutputDir)) { $OutputDir } else { Join-Path $root $OutputDir }
New-Directory -Path $outputRoot
$timestamp = (Get-Date).ToUniversalTime().ToString("yyyyMMdd-HHmmss-fff")
$packageRoot = Join-Path $outputRoot "$packageName-$version-$timestamp"
New-Directory -Path $packageRoot
foreach ($relativePath in @($manifest.system_paths)) {
$relative = Normalize-RelativePath -Path ([string]$relativePath)
if (Test-IsExcluded -RelativePath $relative -ExcludedPaths $excludedPaths) {
throw "System path is excluded and cannot be packaged: $relative"
}
$source = Join-Path $root $relative
if (-not (Test-Path -LiteralPath $source)) {
throw "System path listed in manifest does not exist: $relative"
}
$destination = Join-Path $packageRoot $relative
$destinationParent = Split-Path -Parent $destination
if (-not [string]::IsNullOrWhiteSpace($destinationParent)) {
New-Directory -Path $destinationParent
}
Copy-Item -LiteralPath $source -Destination $destination -Recurse -Force
}
$files = New-Object System.Collections.Generic.List[object]
Get-ChildItem -LiteralPath $packageRoot -Recurse -File -Force |
Sort-Object FullName |
ForEach-Object {
$relative = (Get-RelativePath -Root $packageRoot -Path $_.FullName) -replace "\\", "/"
$hash = Get-FileHash -LiteralPath $_.FullName -Algorithm SHA256
$files.Add([ordered]@{
path = $relative
sha256 = $hash.Hash.ToLowerInvariant()
length = $_.Length
})
}
$packageManifest = [ordered]@{
package_name = $packageName
version = $version
exported_at = (Get-Date).ToUniversalTime().ToString("o")
source_manifest = $ManifestPath
default_artifact_dir = [string]$manifest.default_artifact_dir
system_paths = @($manifest.system_paths)
preserve_paths = @($manifest.preserve_paths)
excluded_paths = @($manifest.excluded_paths)
files = $files
}
$packageManifestPath = Join-Path $packageRoot "package-manifest.json"
$packageManifest | ConvertTo-Json -Depth 20 | Set-Content -LiteralPath $packageManifestPath -Encoding UTF8
Write-Output "Exported $($files.Count) system file(s)."
Write-Output $packageRoot
@@ -0,0 +1,70 @@
param(
[Parameter(Mandatory = $false)]
[string]$WorkspaceDir = "workspace",
[Parameter(Mandatory = $false)]
[string]$ArtifactDir = "workspace/artifacts/wireframe-gen",
[Parameter(Mandatory = $false)]
[string]$LegacyArtifactDir = "artifacts/wireframe-gen"
)
$ErrorActionPreference = "Stop"
function Get-RepoRoot {
return (Split-Path -Parent (Split-Path -Parent $PSScriptRoot))
}
function Resolve-RepoPath {
param(
[string]$Root,
[string]$Path
)
if ([System.IO.Path]::IsPathRooted($Path)) { return $Path }
return Join-Path $Root $Path
}
function New-Directory {
param([string]$Path)
if (-not (Test-Path -LiteralPath $Path)) {
New-Item -ItemType Directory -Force -Path $Path | Out-Null
}
}
function Test-DirectoryHasFiles {
param([string]$Path)
if (-not (Test-Path -LiteralPath $Path)) { return $false }
return $null -ne (Get-ChildItem -LiteralPath $Path -Force -Recurse -File | Select-Object -First 1)
}
$root = Get-RepoRoot
$workspacePath = Resolve-RepoPath -Root $root -Path $WorkspaceDir
$artifactPath = Resolve-RepoPath -Root $root -Path $ArtifactDir
$legacyArtifactPath = Resolve-RepoPath -Root $root -Path $LegacyArtifactDir
New-Directory -Path $workspacePath
New-Directory -Path $artifactPath
New-Directory -Path (Join-Path $workspacePath "system-feedback/bug-reports")
New-Directory -Path (Join-Path $workspacePath ".hot-update/backups")
$targetHadFiles = Test-DirectoryHasFiles -Path $artifactPath
$legacyExists = Test-Path -LiteralPath $legacyArtifactPath
$copiedLegacy = $false
if ($legacyExists -and -not $targetHadFiles) {
Get-ChildItem -LiteralPath $legacyArtifactPath -Force | ForEach-Object {
Copy-Item -LiteralPath $_.FullName -Destination $artifactPath -Recurse -Force
}
$copiedLegacy = $true
}
if (-not (Test-DirectoryHasFiles -Path $artifactPath)) {
$initArtifactsScript = Join-Path $root "scripts/wireframe/init-artifacts.ps1"
powershell -NoProfile -ExecutionPolicy Bypass -File $initArtifactsScript -ArtifactDir $artifactPath | Out-Null
}
Write-Output "Workspace initialized: $workspacePath"
Write-Output "Artifact dir: $artifactPath"
if ($copiedLegacy) {
Write-Output "Copied legacy artifacts from $legacyArtifactPath. Legacy artifacts were not deleted."
}
@@ -0,0 +1,162 @@
param(
[Parameter(Mandatory = $true)]
[string]$Title,
[Parameter(Mandatory = $true)]
[string]$Area,
[Parameter(Mandatory = $true)]
[string]$Expected,
[Parameter(Mandatory = $true)]
[string]$Actual,
[Parameter(Mandatory = $false)]
[ValidateSet("low", "medium", "high", "critical")]
[string]$Severity = "medium",
[Parameter(Mandatory = $false)]
[string]$TriggerContext = "Not specified",
[Parameter(Mandatory = $false)]
[string[]]$ReproSteps = @(),
[Parameter(Mandatory = $false)]
[string]$Impact = "",
[Parameter(Mandatory = $false)]
[string]$Workaround = "",
[Parameter(Mandatory = $false)]
[string]$DepersonalizationNotes = "No client names, copied client documents, Figma content, credentials, or private identifiers are included.",
[Parameter(Mandatory = $false)]
[switch]$PrivacyConfirmed,
[Parameter(Mandatory = $false)]
[string]$WorkspaceDir = "workspace",
[Parameter(Mandatory = $false)]
[string]$SystemManifestPath = "wireframe-system.manifest.json"
)
$ErrorActionPreference = "Stop"
function Get-RepoRoot {
return (Split-Path -Parent (Split-Path -Parent $PSScriptRoot))
}
function Resolve-RepoPath {
param(
[string]$Root,
[string]$Path
)
if ([System.IO.Path]::IsPathRooted($Path)) { return $Path }
return Join-Path $Root $Path
}
function New-Slug {
param([string]$Value)
$slug = $Value.ToLowerInvariant() -replace "[^a-z0-9]+", "-"
$slug = $slug.Trim("-")
if ([string]::IsNullOrWhiteSpace($slug)) { return "bug-report" }
if ($slug.Length -gt 48) { return $slug.Substring(0, 48).Trim("-") }
return $slug
}
function New-Directory {
param([string]$Path)
if (-not (Test-Path -LiteralPath $Path)) {
New-Item -ItemType Directory -Force -Path $Path | Out-Null
}
}
$root = Get-RepoRoot
$workspacePath = Resolve-RepoPath -Root $root -Path $WorkspaceDir
$reportRoot = Join-Path $workspacePath "system-feedback/bug-reports"
New-Directory -Path $reportRoot
$systemVersion = "unknown"
$manifestPath = Resolve-RepoPath -Root $root -Path $SystemManifestPath
if (Test-Path -LiteralPath $manifestPath) {
$manifest = Get-Content -LiteralPath $manifestPath -Raw -Encoding UTF8 | ConvertFrom-Json
if ($manifest.PSObject.Properties.Name -contains "version") {
$systemVersion = [string]$manifest.version
}
}
$createdAt = (Get-Date).ToUniversalTime()
$id = "BUG-" + $createdAt.ToString("yyyyMMdd-HHmmss-fff")
$reportDir = Join-Path $reportRoot ($id + "-" + (New-Slug -Value $Title))
New-Directory -Path $reportDir
New-Directory -Path (Join-Path $reportDir "sanitized-snippets")
$report = [ordered]@{
id = $id
title = $Title
created_at = $createdAt.ToString("o")
system_version = $systemVersion
area = $Area
severity = $Severity
trigger_context = $TriggerContext
actual_behavior = $Actual
expected_behavior = $Expected
repro_steps = @($ReproSteps)
impact = $Impact
workaround = $Workaround
depersonalization_notes = $DepersonalizationNotes
privacy_confirmed = [bool]$PrivacyConfirmed
}
$jsonPath = Join-Path $reportDir "report.json"
$report | ConvertTo-Json -Depth 10 | Set-Content -LiteralPath $jsonPath -Encoding UTF8
$stepsText = if (@($ReproSteps).Count -gt 0) {
(@($ReproSteps) | ForEach-Object { "- $_" }) -join "`n"
}
else {
"- Not provided"
}
$markdown = @"
# $id - $Title
- Created at: $($report.created_at)
- System version: $systemVersion
- Area: $Area
- Severity: $Severity
- Privacy confirmed: $([bool]$PrivacyConfirmed)
## Trigger Context
$TriggerContext
## Actual Behavior
$Actual
## Expected Behavior
$Expected
## Reproduction Steps
$stepsText
## Impact
$Impact
## Workaround
$Workaround
## Depersonalization Notes
$DepersonalizationNotes
## Sanitized Attachments
Place only manually cleaned snippets in sanitized-snippets/. Do not attach source client documents, copied Figma content, credentials, private names, or identifiers.
"@
$markdownPath = Join-Path $reportDir "report.md"
Set-Content -LiteralPath $markdownPath -Value $markdown -Encoding UTF8
Set-Content -LiteralPath (Join-Path $reportDir "sanitized-snippets/.gitkeep") -Value "" -Encoding UTF8
Write-Output "Bug report created: $reportDir"
Write-Output $jsonPath
Write-Output $markdownPath
@@ -0,0 +1,154 @@
param(
[Parameter(Mandatory = $false)]
[string]$InputPath = ".",
[Parameter(Mandatory = $false)]
[string]$OutputDir = "workspace/artifacts/wireframe-gen/extracted"
)
$ErrorActionPreference = "Stop"
function New-Slug {
param([string]$Value)
$slug = [System.IO.Path]::GetFileNameWithoutExtension($Value).ToLowerInvariant()
$slug = $slug -replace "[^a-z0-9]+", "-"
$slug = $slug.Trim("-")
if ([string]::IsNullOrWhiteSpace($slug)) { return "source" }
return $slug
}
function Get-TextFileContent {
param([string]$Path)
return Get-Content -LiteralPath $Path -Raw -Encoding UTF8
}
function Get-DocxText {
param([string]$Path)
Add-Type -AssemblyName System.IO.Compression.FileSystem
$zip = [System.IO.Compression.ZipFile]::OpenRead((Resolve-Path -LiteralPath $Path))
try {
$parts = $zip.Entries | Where-Object {
$_.FullName -eq "word/document.xml" -or
$_.FullName -like "word/header*.xml" -or
$_.FullName -like "word/footer*.xml"
}
$texts = New-Object System.Collections.Generic.List[string]
foreach ($part in $parts) {
$reader = New-Object System.IO.StreamReader($part.Open())
try {
$xml = $reader.ReadToEnd()
$xml = $xml -replace "</w:p>", "`n"
$matches = [regex]::Matches($xml, "<w:t[^>]*>(.*?)</w:t>")
foreach ($match in $matches) {
$texts.Add([System.Net.WebUtility]::HtmlDecode($match.Groups[1].Value))
}
$texts.Add("`n")
}
finally {
$reader.Dispose()
}
}
return (($texts -join "") -replace "`r", "" -replace "`n{3,}", "`n`n").Trim()
}
finally {
$zip.Dispose()
}
}
function Get-PdfText {
param([string]$Path)
$tool = Get-Command pdftotext -ErrorAction SilentlyContinue
if ($null -eq $tool) {
return @{
Text = ""
Note = "PDF extraction requires pdftotext in PATH. No OCR fallback is used in v1."
Status = "needs_external_extractor"
}
}
$tempFile = Join-Path ([System.IO.Path]::GetTempPath()) ("wireframe-pdf-" + [guid]::NewGuid().ToString() + ".txt")
& $tool.Source "-layout" $Path $tempFile | Out-Null
$text = Get-Content -LiteralPath $tempFile -Raw -Encoding UTF8
Remove-Item -LiteralPath $tempFile -Force
return @{
Text = $text
Note = "Extracted with pdftotext."
Status = "ok"
}
}
function Get-SourceFiles {
param([string]$Path)
$resolved = Resolve-Path -LiteralPath $Path
$item = Get-Item -LiteralPath $resolved
$extensions = @(".md", ".markdown", ".txt", ".pdf", ".docx")
if ($item.PSIsContainer) {
return Get-ChildItem -LiteralPath $item.FullName -Recurse -File |
Where-Object { $extensions -contains $_.Extension.ToLowerInvariant() } |
Sort-Object FullName
}
if ($extensions -contains $item.Extension.ToLowerInvariant()) {
return @($item)
}
throw "Unsupported input file type: $($item.Extension)"
}
New-Item -ItemType Directory -Force -Path $OutputDir | Out-Null
$files = Get-SourceFiles -Path $InputPath
$sources = New-Object System.Collections.Generic.List[object]
$index = 1
foreach ($file in $files) {
$sourceId = "SRC-{0:D3}" -f $index
$extension = $file.Extension.ToLowerInvariant()
$status = "ok"
$notes = ""
$text = ""
try {
switch ($extension) {
".md" { $text = Get-TextFileContent -Path $file.FullName }
".markdown" { $text = Get-TextFileContent -Path $file.FullName }
".txt" { $text = Get-TextFileContent -Path $file.FullName }
".docx" { $text = Get-DocxText -Path $file.FullName }
".pdf" {
$pdf = Get-PdfText -Path $file.FullName
$text = $pdf.Text
$notes = $pdf.Note
$status = $pdf.Status
}
}
}
catch {
$status = "error"
$notes = $_.Exception.Message
}
$slug = New-Slug -Value $file.Name
$outName = "$sourceId-$slug.txt"
$outPath = Join-Path $OutputDir $outName
Set-Content -LiteralPath $outPath -Value $text -Encoding UTF8
$sources.Add([ordered]@{
source_id = $sourceId
path = $file.FullName
type = $extension.TrimStart(".")
status = $status
extracted_text_path = $outPath
character_count = $text.Length
notes = $notes
})
$index += 1
}
$inventory = [ordered]@{
generated_at = (Get-Date).ToUniversalTime().ToString("o")
input_path = (Resolve-Path -LiteralPath $InputPath).Path
output_dir = (Resolve-Path -LiteralPath $OutputDir).Path
sources = $sources
}
$inventoryPath = Join-Path $OutputDir "source_inventory.json"
$inventory | ConvertTo-Json -Depth 10 | Set-Content -LiteralPath $inventoryPath -Encoding UTF8
Write-Output "Extracted $($sources.Count) source(s) to $OutputDir"
Write-Output $inventoryPath
@@ -0,0 +1,77 @@
param(
[Parameter(Mandatory = $false)]
[string]$ArtifactDir = "workspace/artifacts/wireframe-gen"
)
$ErrorActionPreference = "Stop"
New-Item -ItemType Directory -Force -Path $ArtifactDir | Out-Null
function Write-IfMissing {
param(
[string]$Path,
[string]$Value
)
if (-not (Test-Path -LiteralPath $Path)) {
Set-Content -LiteralPath $Path -Value $Value -Encoding UTF8
}
}
Write-IfMissing (Join-Path $ArtifactDir "source_inventory.json") @'
{
"generated_at": "",
"input_path": "",
"output_dir": "",
"sources": []
}
'@
Write-IfMissing (Join-Path $ArtifactDir "normalized_project.json") @'
{
"project": {},
"audiences": [],
"goals": [],
"actors": [],
"functional_modules": [],
"entities": [],
"rules": [],
"constraints": [],
"risks": [],
"open_questions": [],
"source_trace": []
}
'@
Write-IfMissing (Join-Path $ArtifactDir "ux_spec.json") @'
{
"information_architecture": [],
"user_flows": [],
"screen_inventory": [],
"screen_purposes": [],
"ux_decisions": [],
"research_citations": [],
"acceptance_criteria": []
}
'@
Write-IfMissing (Join-Path $ArtifactDir "screen_blueprints.json") "[]"
Write-IfMissing (Join-Path $ArtifactDir "figma_build_manifest.json") @'
{
"file_key": "",
"page": "",
"screen_ids": [],
"created_node_ids": [],
"mutated_node_ids": [],
"annotation_node_ids": [],
"screenshots": [],
"validation_notes": [],
"known_issues": []
}
'@
Write-IfMissing (Join-Path $ArtifactDir "normalized_project.summary.md") "# Normalized Project Summary`n"
Write-IfMissing (Join-Path $ArtifactDir "ux_spec.summary.md") "# UX Spec Summary`n"
Write-IfMissing (Join-Path $ArtifactDir "figma_validation.md") "# Figma Validation`n"
Write-IfMissing (Join-Path $ArtifactDir "decision_log.md") "# Decision Log`n"
Write-Output "Initialized artifacts in $ArtifactDir"
@@ -0,0 +1,53 @@
param(
[Parameter(Mandatory = $false)]
[string]$RegistryPath = ".agents/skills/_shared/references/ux-research-registry.json",
[Parameter(Mandatory = $true)]
[string]$Id,
[Parameter(Mandatory = $true)]
[string]$Title,
[Parameter(Mandatory = $true)]
[string]$Url,
[Parameter(Mandatory = $false)]
[string]$Publisher = "",
[Parameter(Mandatory = $false)]
[string[]]$Domains = @(),
[Parameter(Mandatory = $false)]
[string[]]$Claims = @()
)
$ErrorActionPreference = "Stop"
if (-not (Test-Path -LiteralPath $RegistryPath)) {
throw "Registry not found: $RegistryPath"
}
$registry = Get-Content -LiteralPath $RegistryPath -Raw -Encoding UTF8 | ConvertFrom-Json
$sources = @($registry.sources)
$existing = $sources | Where-Object { $_.id -eq $Id } | Select-Object -First 1
$entry = [ordered]@{
id = $Id
title = $Title
url = $Url
publisher = $Publisher
domains = @($Domains)
claims = @($Claims)
last_checked = (Get-Date).ToString("yyyy-MM-dd")
}
if ($null -ne $existing) {
$sources = $sources | Where-Object { $_.id -ne $Id }
}
$sources += [pscustomobject]$entry
$registry.last_updated = (Get-Date).ToString("yyyy-MM-dd")
$registry.sources = @($sources | Sort-Object id)
$registry | ConvertTo-Json -Depth 10 | Set-Content -LiteralPath $RegistryPath -Encoding UTF8
Write-Output "Updated registry entry: $Id"
@@ -0,0 +1,291 @@
param(
[Parameter(Mandatory = $false)]
[string]$ArtifactDir = "workspace/artifacts/wireframe-gen",
[Parameter(Mandatory = $false)]
[switch]$Strict,
[Parameter(Mandatory = $false)]
[ValidateSet("schema", "pre-ux", "pre-figma")]
[string]$Stage = "schema"
)
$ErrorActionPreference = "Stop"
$errors = New-Object System.Collections.Generic.List[string]
function Read-Json {
param([string]$Path)
try {
return Get-Content -LiteralPath $Path -Raw -Encoding UTF8 | ConvertFrom-Json
}
catch {
$script:errors.Add("Invalid JSON: $Path - $($_.Exception.Message)")
return $null
}
}
function Has-Property {
param(
[object]$Object,
[string]$Name
)
if ($null -eq $Object) { return $false }
return $null -ne ($Object.PSObject.Properties | Where-Object { $_.Name -eq $Name })
}
function Test-RequiredFields {
param(
[object]$Object,
[string[]]$Fields,
[string]$Label
)
foreach ($field in $Fields) {
if (-not (Has-Property -Object $Object -Name $field)) {
$script:errors.Add("$Label missing required field: $field")
}
}
}
function Test-IsNumeric {
param([object]$Value)
return (
$Value -is [byte] -or
$Value -is [sbyte] -or
$Value -is [int16] -or
$Value -is [uint16] -or
$Value -is [int32] -or
$Value -is [uint32] -or
$Value -is [int64] -or
$Value -is [uint64] -or
$Value -is [single] -or
$Value -is [double] -or
$Value -is [decimal]
)
}
function Test-GridNumber {
param(
[object]$Value,
[string]$Label,
[switch]$Positive
)
if (-not (Test-IsNumeric -Value $Value)) {
$script:errors.Add("$Label must be numeric")
return
}
$number = [double]$Value
if ($Positive -and $number -lt 1) {
$script:errors.Add("$Label must be at least 1")
}
$rounded = [math]::Round($number)
if ([math]::Abs($number - $rounded) -gt 0.000001) {
$script:errors.Add("$Label must be a whole number")
return
}
if (([int64]$rounded % 4) -ne 0) {
$script:errors.Add("$Label must be a multiple of 4")
}
}
function Test-LayoutNumbers {
param(
[object]$Value,
[string]$Path = "screen_blueprints"
)
if ($null -eq $Value) { return }
if ($Value -is [System.Array]) {
for ($i = 0; $i -lt $Value.Count; $i++) {
Test-LayoutNumbers -Value $Value[$i] -Path "$Path[$i]"
}
return
}
if (Test-IsNumeric -Value $Value) { return }
if ($Value -is [string]) { return }
$layoutNamePattern = '^(x|y|width|height|minWidth|maxWidth|minHeight|maxHeight|min_width|max_width|min_height|max_height|top|right|bottom|left|padding|paddingTop|paddingRight|paddingBottom|paddingLeft|padding_top|padding_right|padding_bottom|padding_left|margin|marginTop|marginRight|marginBottom|marginLeft|margin_top|margin_right|margin_bottom|margin_left|gap|rowGap|columnGap|row_gap|column_gap|spacing|radius|borderRadius|cornerRadius|border_radius|corner_radius|inset|offset)$'
foreach ($prop in $Value.PSObject.Properties) {
$propPath = "$Path.$($prop.Name)"
if ((Test-IsNumeric -Value $prop.Value) -and ($prop.Name -match $layoutNamePattern)) {
Test-GridNumber -Value $prop.Value -Label $propPath
}
elseif ($prop.Value -is [System.Array] -or ($null -ne $prop.Value -and -not ($prop.Value -is [string]) -and -not (Test-IsNumeric -Value $prop.Value) -and $prop.Value.PSObject.Properties.Count -gt 0)) {
Test-LayoutNumbers -Value $prop.Value -Path $propPath
}
}
}
function Get-QuestionStatus {
param([object]$Question)
if (Has-Property -Object $Question -Name "status" -and -not [string]::IsNullOrWhiteSpace([string]$Question.status)) {
return ([string]$Question.status).ToLowerInvariant()
}
return "unresolved"
}
function Test-QuestionIsResolved {
param([object]$Question)
$status = Get-QuestionStatus -Question $Question
return @("resolved", "answered", "closed") -contains $status
}
function Get-QuestionBlocks {
param([object]$Question)
if (-not (Has-Property -Object $Question -Name "blocks") -or $null -eq $Question.blocks) {
return @()
}
return @($Question.blocks | ForEach-Object { ([string]$_).ToLowerInvariant() })
}
function Test-QuestionBlocksStage {
param(
[object]$Question,
[string]$StageName
)
$blocks = Get-QuestionBlocks -Question $Question
if ($blocks.Count -eq 0) { return $false }
$common = @("all", "pipeline", "project", "workflow")
$preUx = @("ux", "pre-ux", "ux-construction", "ux-constructor", "ux-spec", "screen-blueprints", "screen_blueprints")
$preFigma = @("figma", "pre-figma", "figma-build", "figma-generation", "figma-target", "roles", "role", "screens", "screen", "critical-states", "states", "screen-blueprints", "screen_blueprints")
foreach ($block in $blocks) {
if ($common -contains $block) { return $true }
if ($StageName -eq "pre-ux" -and $preUx -contains $block) { return $true }
if ($StageName -eq "pre-figma" -and $preFigma -contains $block) { return $true }
}
return $false
}
function Test-OpenQuestionGate {
param(
[object]$NormalizedProject,
[string]$StageName
)
if ($StageName -eq "schema" -or $null -eq $NormalizedProject -or -not (Has-Property -Object $NormalizedProject -Name "open_questions")) {
return
}
foreach ($question in @($NormalizedProject.open_questions)) {
if (-not (Test-QuestionBlocksStage -Question $question -StageName $StageName)) {
continue
}
$id = if (Has-Property -Object $question -Name "id") { [string]$question.id } else { "(missing id)" }
$text = if (Has-Property -Object $question -Name "question") { [string]$question.question } else { "(missing question text)" }
if (-not (Test-QuestionIsResolved -Question $question)) {
$errors.Add("Open question $id blocks ${StageName}: $text")
continue
}
if (-not (Has-Property -Object $question -Name "answer") -or [string]::IsNullOrWhiteSpace([string]$question.answer)) {
$errors.Add("Open question $id is resolved for ${StageName} but missing answer")
}
}
}
$contracts = @{
"source_inventory.json" = @("generated_at", "input_path", "output_dir", "sources")
"normalized_project.json" = @("project", "audiences", "goals", "actors", "functional_modules", "entities", "rules", "constraints", "risks", "open_questions", "source_trace")
"ux_spec.json" = @("information_architecture", "user_flows", "screen_inventory", "screen_purposes", "ux_decisions", "research_citations", "acceptance_criteria")
"figma_build_manifest.json" = @("file_key", "page", "screen_ids", "created_node_ids", "mutated_node_ids", "screenshots", "validation_notes", "known_issues")
}
$artifactJsons = @{}
foreach ($fileName in $contracts.Keys) {
$path = Join-Path $ArtifactDir $fileName
if (-not (Test-Path -LiteralPath $path)) {
if ($Strict) { $errors.Add("Missing artifact: $path") }
continue
}
$json = Read-Json -Path $path
$artifactJsons[$fileName] = $json
Test-RequiredFields -Object $json -Fields $contracts[$fileName] -Label $fileName
}
if ($artifactJsons.ContainsKey("normalized_project.json")) {
Test-OpenQuestionGate -NormalizedProject $artifactJsons["normalized_project.json"] -StageName $Stage
}
$blueprintsPath = Join-Path $ArtifactDir "screen_blueprints.json"
if (Test-Path -LiteralPath $blueprintsPath) {
$blueprints = Read-Json -Path $blueprintsPath
if ($null -ne $blueprints) {
$items = @($blueprints)
$screenRequired = @("content_type", "screen_id", "viewport", "purpose", "sections", "components", "states", "content_requirements", "interactions", "empty_error_loading_states")
$elementRequired = @("content_type", "element_id", "parent_screen_id", "bounds")
for ($i = 0; $i -lt $items.Count; $i++) {
$item = $items[$i]
$label = "screen_blueprints[$i]"
Test-RequiredFields -Object $item -Fields @("content_type") -Label $label
if (-not (Has-Property -Object $item -Name "content_type")) {
continue
}
switch ($item.content_type) {
"screen" {
Test-RequiredFields -Object $item -Fields $screenRequired -Label $label
if (Has-Property -Object $item -Name "viewport") {
Test-RequiredFields -Object $item.viewport -Fields @("width", "height") -Label "$label.viewport"
if (Has-Property -Object $item.viewport -Name "width") {
Test-GridNumber -Value $item.viewport.width -Label "$label.viewport.width" -Positive
if ([double]$item.viewport.width -ne 1440) {
$errors.Add("$label.viewport.width must equal 1440 for content_type screen")
}
}
if (Has-Property -Object $item.viewport -Name "height") {
Test-GridNumber -Value $item.viewport.height -Label "$label.viewport.height" -Positive
if ([double]$item.viewport.height -lt 800) {
$errors.Add("$label.viewport.height must be at least 800 for content_type screen")
}
}
}
}
"element" {
Test-RequiredFields -Object $item -Fields $elementRequired -Label $label
if (Has-Property -Object $item -Name "bounds") {
Test-RequiredFields -Object $item.bounds -Fields @("width", "height") -Label "$label.bounds"
foreach ($field in @("x", "y", "width", "height")) {
if (Has-Property -Object $item.bounds -Name $field) {
$positive = $field -eq "width" -or $field -eq "height"
if ($positive) {
Test-GridNumber -Value $item.bounds.$field -Label "$label.bounds.$field" -Positive
}
else {
Test-GridNumber -Value $item.bounds.$field -Label "$label.bounds.$field"
}
}
}
}
}
default {
$errors.Add("$label.content_type must be either screen or element")
}
}
Test-LayoutNumbers -Value $item -Path $label
}
}
}
elseif ($Strict) {
$errors.Add("Missing artifact: $blueprintsPath")
}
if ($errors.Count -gt 0) {
$errors | ForEach-Object { Write-Error $_ }
exit 1
}
Write-Output "Artifact validation passed for $ArtifactDir"
@@ -0,0 +1,288 @@
$ErrorActionPreference = "Stop"
$root = Split-Path -Parent $PSScriptRoot
$initArtifactsScript = Join-Path $root "scripts/wireframe/init-artifacts.ps1"
$extractScript = Join-Path $root "scripts/wireframe/extract-documents.ps1"
$validateScript = Join-Path $root "scripts/wireframe/validate-artifacts.ps1"
$initWorkspaceScript = Join-Path $root "scripts/system/init-workspace.ps1"
$exportUpdateScript = Join-Path $root "scripts/system/export-update-package.ps1"
$applyUpdateScript = Join-Path $root "scripts/system/apply-hot-update.ps1"
$newBugReportScript = Join-Path $root "scripts/system/new-bug-report.ps1"
$fixtureDocs = Join-Path $root "tests/fixtures/docs"
$fixtureArtifacts = Join-Path $root "tests/fixtures/artifacts"
$tempDir = Join-Path ([System.IO.Path]::GetTempPath()) ("wireframe-gen-test-" + [guid]::NewGuid().ToString())
function Copy-FixtureArtifacts {
param([string]$Destination)
New-Item -ItemType Directory -Force -Path $Destination | Out-Null
Get-ChildItem -LiteralPath $fixtureArtifacts -Force | ForEach-Object {
Copy-Item -LiteralPath $_.FullName -Destination $Destination -Recurse -Force
}
}
function Write-Blueprints {
param(
[object]$Blueprints,
[string]$ArtifactDir
)
$path = Join-Path $ArtifactDir "screen_blueprints.json"
$Blueprints | ConvertTo-Json -Depth 20 | Set-Content -LiteralPath $path -Encoding UTF8
}
function Write-NormalizedProject {
param(
[object]$Project,
[string]$ArtifactDir
)
$path = Join-Path $ArtifactDir "normalized_project.json"
$Project | ConvertTo-Json -Depth 20 | Set-Content -LiteralPath $path -Encoding UTF8
}
function Expect-ValidationFailure {
param(
[string]$Name,
[scriptblock]$Mutate,
[string]$Stage = "schema"
)
$caseDir = Join-Path $tempDir $Name
Copy-FixtureArtifacts -Destination $caseDir
& $Mutate $caseDir
$oldErrorActionPreference = $ErrorActionPreference
$ErrorActionPreference = "Continue"
try {
powershell -NoProfile -ExecutionPolicy Bypass -File $validateScript -ArtifactDir $caseDir -Strict -Stage $Stage *> $null
$exitCode = $LASTEXITCODE
}
finally {
$ErrorActionPreference = $oldErrorActionPreference
}
if ($exitCode -eq 0) {
throw "Expected validation failure for $Name"
}
}
function Expect-ValidationSuccess {
param(
[string]$Name,
[scriptblock]$Mutate,
[string]$Stage = "schema"
)
$caseDir = Join-Path $tempDir $Name
Copy-FixtureArtifacts -Destination $caseDir
& $Mutate $caseDir
powershell -NoProfile -ExecutionPolicy Bypass -File $validateScript -ArtifactDir $caseDir -Strict -Stage $Stage | Out-Null
}
function Assert-PathExists {
param([string]$Path)
if (-not (Test-Path -LiteralPath $Path)) {
throw "Expected path to exist: $Path"
}
}
function Assert-PathMissing {
param([string]$Path)
if (Test-Path -LiteralPath $Path) {
throw "Expected path to be absent: $Path"
}
}
function Assert-Equal {
param(
[object]$Actual,
[object]$Expected,
[string]$Message
)
if ($Actual -ne $Expected) {
throw "$Message Expected '$Expected', got '$Actual'"
}
}
function Copy-DirectoryContents {
param(
[string]$Source,
[string]$Destination
)
New-Item -ItemType Directory -Force -Path $Destination | Out-Null
Get-ChildItem -LiteralPath $Source -Force | ForEach-Object {
Copy-Item -LiteralPath $_.FullName -Destination $Destination -Recurse -Force
}
}
function Assert-PackageHasNoPreservedFiles {
param([object]$PackageManifest)
foreach ($file in @($PackageManifest.files)) {
$path = ([string]$file.path) -replace "\\", "/"
foreach ($blocked in @("workspace", "artifacts", "maintenance", "dist")) {
if ($path -eq $blocked -or $path.StartsWith("$blocked/")) {
throw "Package should not include preserved path: $path"
}
}
}
}
try {
$defaultCase = Join-Path $tempDir "default-paths"
New-Item -ItemType Directory -Force -Path $defaultCase | Out-Null
Push-Location $defaultCase
try {
powershell -NoProfile -ExecutionPolicy Bypass -File $initArtifactsScript | Out-Null
Assert-PathExists (Join-Path $defaultCase "workspace/artifacts/wireframe-gen/source_inventory.json")
powershell -NoProfile -ExecutionPolicy Bypass -File $validateScript -Strict | Out-Null
}
finally {
Pop-Location
}
$legacyArtifactDir = Join-Path $tempDir "legacy-artifact-dir"
powershell -NoProfile -ExecutionPolicy Bypass -File $initArtifactsScript -ArtifactDir $legacyArtifactDir | Out-Null
powershell -NoProfile -ExecutionPolicy Bypass -File $validateScript -ArtifactDir $legacyArtifactDir -Strict | Out-Null
$legacySource = Join-Path $tempDir "legacy-source/artifacts/wireframe-gen"
Copy-FixtureArtifacts -Destination $legacySource
$workspaceRoot = Join-Path $tempDir "client-workspace"
$workspaceArtifacts = Join-Path $workspaceRoot "artifacts/wireframe-gen"
powershell -NoProfile -ExecutionPolicy Bypass -File $initWorkspaceScript -WorkspaceDir $workspaceRoot -ArtifactDir $workspaceArtifacts -LegacyArtifactDir $legacySource | Out-Null
Assert-PathExists (Join-Path $workspaceArtifacts "source_inventory.json")
Assert-PathExists (Join-Path $workspaceRoot "system-feedback/bug-reports")
Assert-PathExists (Join-Path $workspaceRoot ".hot-update/backups")
Assert-PathExists (Join-Path $legacySource "source_inventory.json")
powershell -NoProfile -ExecutionPolicy Bypass -File $extractScript -InputPath $fixtureDocs -OutputDir $tempDir | Out-Null
$inventoryPath = Join-Path $tempDir "source_inventory.json"
if (-not (Test-Path -LiteralPath $inventoryPath)) {
throw "source_inventory.json was not created"
}
$inventory = Get-Content -LiteralPath $inventoryPath -Raw -Encoding UTF8 | ConvertFrom-Json
if (@($inventory.sources).Count -lt 2) {
throw "Expected at least 2 extracted fixture sources"
}
powershell -NoProfile -ExecutionPolicy Bypass -File $validateScript -ArtifactDir $fixtureArtifacts -Strict | Out-Null
Expect-ValidationFailure "legacy-open-question-blocks-pre-figma" {
param($caseDir)
} -Stage "pre-figma"
Expect-ValidationSuccess "resolved-open-question-passes-pre-ux" {
param($caseDir)
} -Stage "pre-ux"
Expect-ValidationFailure "unresolved-open-question-blocks-pre-ux" {
param($caseDir)
$project = Get-Content -LiteralPath (Join-Path $caseDir "normalized_project.json") -Raw -Encoding UTF8 | ConvertFrom-Json
$project.open_questions += [pscustomobject]@{
id = "Q-003"
question = "Which navigation model should be used?"
status = "unresolved"
blocks = @("ux-construction")
default_assumption = ""
answer = ""
answered_at = ""
source_refs = @()
}
Write-NormalizedProject -Project $project -ArtifactDir $caseDir
} -Stage "pre-ux"
Expect-ValidationFailure "bad-screen-width" {
param($caseDir)
$blueprints = Get-Content -LiteralPath (Join-Path $caseDir "screen_blueprints.json") -Raw -Encoding UTF8 | ConvertFrom-Json
$blueprints[0].viewport.width = 1436
Write-Blueprints -Blueprints $blueprints -ArtifactDir $caseDir
}
Expect-ValidationFailure "bad-screen-height" {
param($caseDir)
$blueprints = Get-Content -LiteralPath (Join-Path $caseDir "screen_blueprints.json") -Raw -Encoding UTF8 | ConvertFrom-Json
$blueprints[0].viewport.height = 796
Write-Blueprints -Blueprints $blueprints -ArtifactDir $caseDir
}
Expect-ValidationFailure "missing-element-parent" {
param($caseDir)
$blueprints = Get-Content -LiteralPath (Join-Path $caseDir "screen_blueprints.json") -Raw -Encoding UTF8 | ConvertFrom-Json
$blueprints[1].PSObject.Properties.Remove("parent_screen_id")
Write-Blueprints -Blueprints $blueprints -ArtifactDir $caseDir
}
Expect-ValidationFailure "fractional-spacing" {
param($caseDir)
$blueprints = Get-Content -LiteralPath (Join-Path $caseDir "screen_blueprints.json") -Raw -Encoding UTF8 | ConvertFrom-Json
$blueprints[0].sections[0].padding = 32.5
Write-Blueprints -Blueprints $blueprints -ArtifactDir $caseDir
}
Expect-ValidationFailure "bad-element-size" {
param($caseDir)
$blueprints = Get-Content -LiteralPath (Join-Path $caseDir "screen_blueprints.json") -Raw -Encoding UTF8 | ConvertFrom-Json
$blueprints[1].bounds.width = 13
Write-Blueprints -Blueprints $blueprints -ArtifactDir $caseDir
}
$packageOutput = Join-Path $tempDir "packages"
$exportOutput = powershell -NoProfile -ExecutionPolicy Bypass -File $exportUpdateScript -OutputDir $packageOutput
$packagePath = @($exportOutput)[-1]
Assert-PathExists $packagePath
$packageManifestPath = Join-Path $packagePath "package-manifest.json"
Assert-PathExists $packageManifestPath
$packageManifest = Get-Content -LiteralPath $packageManifestPath -Raw -Encoding UTF8 | ConvertFrom-Json
Assert-PackageHasNoPreservedFiles -PackageManifest $packageManifest
$clientRoot = Join-Path $tempDir "client-copy"
Copy-DirectoryContents -Source $packagePath -Destination $clientRoot
$clientAgents = Join-Path $clientRoot "AGENTS.md"
Set-Content -LiteralPath $clientAgents -Value "old system instructions" -Encoding UTF8 -NoNewline
New-Item -ItemType Directory -Force -Path (Join-Path $clientRoot "workspace/artifacts/wireframe-gen") | Out-Null
Set-Content -LiteralPath (Join-Path $clientRoot "workspace/artifacts/wireframe-gen/client-data.txt") -Value "do not touch" -Encoding UTF8 -NoNewline
New-Item -ItemType Directory -Force -Path (Join-Path $clientRoot "maintenance") | Out-Null
Set-Content -LiteralPath (Join-Path $clientRoot "maintenance/origin-only.txt") -Value "do not touch" -Encoding UTF8 -NoNewline
powershell -NoProfile -ExecutionPolicy Bypass -File $applyUpdateScript -PackagePath $packagePath -TargetRoot $clientRoot -DryRun | Out-Null
Assert-Equal (Get-Content -LiteralPath $clientAgents -Raw -Encoding UTF8) "old system instructions" "Dry-run must not mutate system files."
Assert-PathMissing (Join-Path $clientRoot "workspace/.hot-update/last-apply.json")
powershell -NoProfile -ExecutionPolicy Bypass -File $applyUpdateScript -PackagePath $packagePath -TargetRoot $clientRoot | Out-Null
$sourceAgents = Get-Content -LiteralPath (Join-Path $packagePath "AGENTS.md") -Raw -Encoding UTF8
$updatedAgents = Get-Content -LiteralPath $clientAgents -Raw -Encoding UTF8
Assert-Equal $updatedAgents $sourceAgents "Hot Update must replace allowlisted system files."
Assert-Equal (Get-Content -LiteralPath (Join-Path $clientRoot "workspace/artifacts/wireframe-gen/client-data.txt") -Raw -Encoding UTF8) "do not touch" "Hot Update must preserve workspace files."
Assert-Equal (Get-Content -LiteralPath (Join-Path $clientRoot "maintenance/origin-only.txt") -Raw -Encoding UTF8) "do not touch" "Hot Update must preserve maintenance files."
Assert-PathExists (Join-Path $clientRoot "workspace/.hot-update/last-apply.json")
$backupAgents = Get-ChildItem -LiteralPath (Join-Path $clientRoot "workspace/.hot-update/backups") -Recurse -File -Filter "AGENTS.md" | Select-Object -First 1
if ($null -eq $backupAgents) {
throw "Expected Hot Update to create a backup for overwritten AGENTS.md"
}
Assert-Equal (Get-Content -LiteralPath $backupAgents.FullName -Raw -Encoding UTF8) "old system instructions" "Backup should contain the previous file content."
$bugWorkspace = Join-Path $tempDir "bug-workspace"
powershell -NoProfile -ExecutionPolicy Bypass -File $newBugReportScript `
-WorkspaceDir $bugWorkspace `
-Title "Validation does not block bad screen" `
-Area "validation" `
-Expected "Validation fails when a screen is underspecified." `
-Actual "Validation passed." `
-TriggerContext "During sanitized fixture checks." `
-ReproSteps "Run validation; observe success" `
-Impact "Incorrect downstream wireframes" | Out-Null
$reportJsonPath = Get-ChildItem -LiteralPath (Join-Path $bugWorkspace "system-feedback/bug-reports") -Recurse -File -Filter "report.json" | Select-Object -First 1
if ($null -eq $reportJsonPath) {
throw "Expected bug report JSON to be created."
}
$report = Get-Content -LiteralPath $reportJsonPath.FullName -Raw -Encoding UTF8 | ConvertFrom-Json
foreach ($field in @("id", "created_at", "system_version", "area", "severity", "trigger_context", "actual_behavior", "expected_behavior", "repro_steps", "impact", "workaround", "depersonalization_notes", "privacy_confirmed")) {
if (-not ($report.PSObject.Properties.Name -contains $field)) {
throw "Bug report missing required field: $field"
}
}
Assert-PathExists (Join-Path $reportJsonPath.Directory.FullName "report.md")
Assert-PathExists (Join-Path $reportJsonPath.Directory.FullName "sanitized-snippets")
Write-Output "Wireframe checks passed"
}
finally {
if (Test-Path -LiteralPath $tempDir) {
Remove-Item -LiteralPath $tempDir -Recurse -Force
}
}
@@ -0,0 +1,23 @@
{
"file_key": "test-file-key",
"page": "Wireframes",
"screen_ids": ["screen-project-review"],
"created_node_ids": ["1:2"],
"mutated_node_ids": [],
"annotation_node_ids": ["1:9"],
"screenshots": [
{
"screen_id": "screen-project-review",
"node_id": "1:2",
"path": "workspace/artifacts/wireframe-gen/screenshots/screen-project-review.png"
}
],
"validation_notes": [
{
"screen_id": "screen-project-review",
"status": "pass",
"notes": "Fixture manifest only."
}
],
"known_issues": []
}
@@ -0,0 +1,91 @@
{
"project": {
"id": "PRJ-001",
"name": "Wireframe Gen",
"description": "Generate editable Figma wireframes from text documentation."
},
"audiences": [
{
"id": "AUD-001",
"name": "Product teams",
"source_refs": ["SRC-001#users"]
}
],
"goals": [
{
"id": "GOAL-001",
"statement": "Create traceable wireframes from source documentation.",
"confidence": "high",
"source_refs": ["SRC-001#goals"]
}
],
"actors": [
{
"id": "ACT-001",
"name": "Product manager",
"source_refs": ["SRC-001#users"]
}
],
"functional_modules": [
{
"id": "MOD-001",
"name": "Documentation intake",
"requirements": ["REQ-001"],
"source_refs": ["SRC-001#core-requirements"]
}
],
"entities": [
{
"id": "ENT-001",
"name": "Source document",
"source_refs": ["SRC-001#core-requirements"]
}
],
"rules": [
{
"id": "RULE-001",
"statement": "Every requirement must preserve source traceability.",
"confidence": "high",
"source_refs": ["SRC-002#line-1"]
}
],
"constraints": [
{
"id": "CON-001",
"statement": "Schema keys stay in English.",
"source_refs": ["SRC-002#line-2"]
}
],
"risks": [
{
"id": "RISK-001",
"statement": "Figma target file can be missing.",
"source_refs": ["SRC-002#line-4"]
}
],
"open_questions": [
{
"id": "Q-001",
"question": "Which Figma file should receive generated screens?",
"blocks": ["figma-build"],
"source_refs": ["SRC-002#line-4"]
},
{
"id": "Q-002",
"question": "Which user role should be used for first UX construction?",
"status": "answered",
"blocks": ["ux-construction"],
"default_assumption": "Use the primary product manager role.",
"answer": "Use ACT-001 as the primary role for the first UX pass.",
"answered_at": "2026-05-03T00:00:00Z",
"source_refs": ["SRC-001#users"]
}
],
"source_trace": [
{
"source_ref": "SRC-001#goals",
"path": "tests/fixtures/docs/product.md",
"excerpt": "Normalize scattered documentation into one product model."
}
]
}
@@ -0,0 +1,41 @@
[
{
"content_type": "screen",
"screen_id": "screen-project-review",
"viewport": {
"width": 1440,
"height": 800
},
"purpose": "Review normalized requirements and blockers.",
"sections": [
{
"id": "section-summary",
"name": "Summary",
"requirements": ["RULE-001"],
"padding": 32,
"gap": 24
}
],
"components": ["app-shell", "status-list", "open-question-list"],
"states": ["default", "loading", "error", "empty"],
"content_requirements": ["Show source count", "Show unresolved questions"],
"interactions": ["Continue to UX construction"],
"empty_error_loading_states": ["No sources", "Extraction failed", "Validation running"]
},
{
"content_type": "element",
"element_id": "element-project-review-open-question-card",
"parent_screen_id": "screen-project-review",
"bounds": {
"x": 320,
"y": 240,
"width": 480,
"height": 160
},
"purpose": "Show one unresolved question with impact and next action.",
"states": ["default", "resolved"],
"components": ["question-card", "status-badge", "secondary-action"],
"content_requirements": ["Question text", "Blocking area", "Resolution action"],
"interactions": ["Mark resolved", "Open related source"]
}
]
@@ -0,0 +1,16 @@
{
"generated_at": "2026-05-03T00:00:00Z",
"input_path": "tests/fixtures/docs",
"output_dir": "artifacts/test-extracted",
"sources": [
{
"source_id": "SRC-001",
"path": "tests/fixtures/docs/product.md",
"type": "md",
"status": "ok",
"extracted_text_path": "artifacts/test-extracted/SRC-001-product.txt",
"character_count": 640,
"notes": ""
}
]
}
+56
View File
@@ -0,0 +1,56 @@
{
"information_architecture": [
{
"id": "IA-001",
"name": "Project workspace",
"children": ["Documentation", "UX Spec", "Wireframes"]
}
],
"user_flows": [
{
"id": "FLOW-001",
"name": "Generate first wireframes",
"actor_id": "ACT-001",
"steps": ["Add docs", "Review normalized project", "Generate UX spec", "Build Figma"],
"success_outcome": "Editable Figma screens are created."
}
],
"screen_inventory": [
{
"screen_id": "screen-project-review",
"name": "Project Review",
"requirement_refs": ["RULE-001"]
}
],
"screen_purposes": [
{
"screen_id": "screen-project-review",
"purpose": "Let the user review normalized requirements before UX construction."
}
],
"ux_decisions": [
{
"id": "UXD-001",
"decision": "Show extraction and validation status before Figma generation.",
"rationale": "Users need feedback about system status.",
"citation_ids": ["nng-heuristics"],
"source_refs": ["RULE-001"]
}
],
"research_citations": [
{
"registry_id": "nng-heuristics",
"title": "10 Usability Heuristics for User Interface Design",
"url": "https://www.nngroup.com/articles/ten-usability-heuristics/",
"claim": "Systems should keep users informed about status.",
"used_for": ["UXD-001"]
}
],
"acceptance_criteria": [
{
"id": "AC-001",
"screen_id": "screen-project-review",
"criterion": "The screen shows source extraction status and open questions."
}
]
}
+22
View File
@@ -0,0 +1,22 @@
# Product Brief
The system helps product teams generate editable Figma wireframes from project documentation.
## Goals
- Normalize scattered documentation into one product model.
- Create UX architecture and screen blueprints before visual generation.
- Build mid-fidelity Figma screens through native Figma MCP.
## Users
- Product manager
- UX designer
- Solution architect
## Core Requirements
- Users can upload or select documentation files.
- Users can review normalized requirements and open questions.
- Users can generate UX screen blueprints.
- Users can create editable Figma wireframes.
+4
View File
@@ -0,0 +1,4 @@
Requirement: Preserve source traceability from every requirement to the original source document.
Requirement: Keep schema keys in English.
Requirement: Produce Russian human-readable summaries by default.
Risk: Figma target file may be unknown until the user provides a file key.
@@ -0,0 +1,30 @@
{
"version": "0.2.0",
"package_name": "wireframe-system",
"default_artifact_dir": "workspace/artifacts/wireframe-gen",
"system_paths": [
".agents",
"scripts",
"docs",
"tests",
"AGENTS.md",
"wireframe-system.manifest.json"
],
"preserve_paths": [
"workspace",
"artifacts",
"maintenance",
"dist"
],
"excluded_paths": [
"workspace",
"artifacts",
"maintenance",
"dist",
".git",
".cache",
"node_modules",
"tmp",
"temp"
]
}