Added to Git
This commit is contained in:
Vendored
BIN
Binary file not shown.
@@ -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
|
||||||
+27
@@ -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
|
||||||
+14
@@ -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.
|
||||||
@@ -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.
|
||||||
@@ -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.
|
||||||
|
```
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Vendored
BIN
Binary file not shown.
@@ -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"
|
||||||
Vendored
BIN
Binary file not shown.
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
Binary file not shown.
@@ -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": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -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
@@ -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.
|
||||||
@@ -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"
|
||||||
|
]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user