mirror of
https://github.com/bmadcode/BMAD-METHOD.git
synced 2025-12-17 09:45:25 +00:00
- Remove validate-prd workflow references from all workflow path YAML files - Update Excalidraw diagram: remove Validate PRD box and zombie JSON elements - Re-export SVG at 1x scale - Standardize implementation-readiness descriptions across all docs - Add validation script (validate-svg-changes.sh) and README for SVG export process - Correct Excalidraw timestamps 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Brian <bmadcode@gmail.com>
This commit is contained in:
parent
c95b65f462
commit
5ee1551b5b
@ -76,8 +76,7 @@ The BMad Method Module (BMM) provides a comprehensive team of specialized AI age
|
||||
- `create-prd` - Create PRD for Level 2-4 projects (creates FRs/NFRs only)
|
||||
- `tech-spec` - Quick spec for Level 0-1 projects
|
||||
- `create-epics-and-stories` - Break PRD into implementable pieces (runs AFTER architecture)
|
||||
- `validate-prd` - Validate PRD completeness
|
||||
- `validate-tech-spec` - Validate Technical Specification
|
||||
- `implementation-readiness` - Validate PRD + Architecture + Epics + UX (optional)
|
||||
- `correct-course` - Handle mid-project changes
|
||||
- `workflow-init` - Initialize workflow tracking
|
||||
|
||||
@ -146,7 +145,7 @@ The BMad Method Module (BMM) provides a comprehensive team of specialized AI age
|
||||
- `workflow-status` - Check what to do next
|
||||
- `create-architecture` - Produce a Scale Adaptive Architecture
|
||||
- `validate-architecture` - Validate architecture document
|
||||
- `implementation-readiness` - Validate readiness for Phase 4
|
||||
- `implementation-readiness` - Validate PRD + Architecture + Epics + UX (optional)
|
||||
|
||||
**Communication Style:** Comprehensive yet pragmatic. Uses architectural metaphors. Balances technical depth with accessibility. Connects decisions to business value.
|
||||
|
||||
@ -642,13 +641,12 @@ Some workflows are available to multiple agents:
|
||||
|
||||
Many workflows have optional validation workflows that perform independent review:
|
||||
|
||||
| Validation | Agent | Validates |
|
||||
| ----------------------- | ----------- | -------------------------------- |
|
||||
| `validate-prd` | PM | PRD completeness (FRs/NFRs only) |
|
||||
| `validate-tech-spec` | PM | Technical specification quality |
|
||||
| `validate-architecture` | Architect | Architecture document |
|
||||
| `validate-design` | UX Designer | UX specification and artifacts |
|
||||
| `validate-create-story` | SM | Story draft |
|
||||
| Validation | Agent | Validates |
|
||||
| -------------------------- | ----------- | ------------------------------------------ |
|
||||
| `implementation-readiness` | Architect | PRD + Architecture + Epics + UX (optional) |
|
||||
| `validate-architecture` | Architect | Architecture document |
|
||||
| `validate-design` | UX Designer | UX specification and artifacts |
|
||||
| `validate-create-story` | SM | Story draft |
|
||||
|
||||
**When to use validation:**
|
||||
|
||||
@ -945,9 +943,8 @@ Agent analyzes project state → recommends next workflow
|
||||
|
||||
```
|
||||
Each phase has validation gates:
|
||||
- Phase 2 to 3: validate-prd, validate-tech-spec
|
||||
- Phase 3 to 4: implementation-readiness
|
||||
Run validation before advancing
|
||||
- Phase 3 to 4: implementation-readiness (validates PRD + Architecture + Epics + UX (optional))
|
||||
Run validation before advancing to implementation
|
||||
```
|
||||
|
||||
**Course correction:**
|
||||
|
||||
@ -147,7 +147,7 @@ If status file exists, use workflow-status. If not, use workflow-init.
|
||||
|
||||
### Q: How do I know when Phase 3 is complete and I can start Phase 4?
|
||||
|
||||
**A:** For Level 3-4, run the implementation-readiness workflow. It validates that PRD (FRs/NFRs), architecture, epics+stories, and UX (if applicable) are cohesive before implementation. Pass the gate check = ready for Phase 4.
|
||||
**A:** For Level 3-4, run the implementation-readiness workflow. It validates PRD + Architecture + Epics + UX (optional) are aligned before implementation. Pass the gate check = ready for Phase 4.
|
||||
|
||||
### Q: Can I run workflows in parallel or do they have to be sequential?
|
||||
|
||||
|
||||
@ -246,7 +246,7 @@ Workflow that initializes Phase 4 implementation by creating sprint-status.yaml,
|
||||
|
||||
### Gate Check
|
||||
|
||||
Validation workflow (implementation-readiness) run before Phase 4 to ensure PRD, architecture, and UX documents are cohesive with no gaps or contradictions. Required for BMad Method and Enterprise Method tracks.
|
||||
Validation workflow (implementation-readiness) run before Phase 4 to ensure PRD + Architecture + Epics + UX (optional) are aligned with no gaps or contradictions. Required for BMad Method and Enterprise Method tracks.
|
||||
|
||||
### DoD (Definition of Done)
|
||||
|
||||
|
||||
37
src/modules/bmm/docs/images/README.md
Normal file
37
src/modules/bmm/docs/images/README.md
Normal file
@ -0,0 +1,37 @@
|
||||
# Workflow Diagram Maintenance
|
||||
|
||||
## Regenerating SVG from Excalidraw
|
||||
|
||||
When you edit `workflow-method-greenfield.excalidraw`, regenerate the SVG:
|
||||
|
||||
1. Open https://excalidraw.com/
|
||||
2. Load the `.excalidraw` file
|
||||
3. Click menu (☰) → Export image → SVG
|
||||
4. **Set "Scale" to 1x** (default is 2x)
|
||||
5. Click "Export"
|
||||
6. Save as `workflow-method-greenfield.svg`
|
||||
7. **Validate the changes** (see below)
|
||||
8. Commit both files together
|
||||
|
||||
**Important:**
|
||||
|
||||
- Always use **1x scale** to maintain consistent dimensions
|
||||
- Automated export tools (`excalidraw-to-svg`) are broken - use manual export only
|
||||
|
||||
## Visual Validation
|
||||
|
||||
After regenerating the SVG, validate that it renders correctly:
|
||||
|
||||
```bash
|
||||
./tools/validate-svg-changes.sh src/modules/bmm/docs/images/workflow-method-greenfield.svg
|
||||
```
|
||||
|
||||
This script:
|
||||
|
||||
- Checks for required dependencies (Playwright, ImageMagick)
|
||||
- Installs Playwright locally if needed (no package.json pollution)
|
||||
- Renders old vs new SVG using browser-accurate rendering
|
||||
- Compares pixel-by-pixel and generates a diff image
|
||||
- Outputs a prompt for AI visual analysis (paste into Gemini/Claude)
|
||||
|
||||
**Threshold**: <0.01% difference is acceptable (anti-aliasing variations)
|
||||
@ -1036,10 +1036,6 @@
|
||||
"type": "arrow",
|
||||
"id": "arrow-discovery-no"
|
||||
},
|
||||
{
|
||||
"type": "arrow",
|
||||
"id": "arrow-prd-validate"
|
||||
},
|
||||
{
|
||||
"id": "arrow-phase1-to-phase2",
|
||||
"type": "arrow"
|
||||
@ -1055,17 +1051,21 @@
|
||||
{
|
||||
"id": "arrow-has-ui-no",
|
||||
"type": "arrow"
|
||||
},
|
||||
{
|
||||
"id": "arrow-prd-hasui",
|
||||
"type": "arrow"
|
||||
}
|
||||
],
|
||||
"locked": false,
|
||||
"version": 107,
|
||||
"versionNonce": 930129274,
|
||||
"version": 108,
|
||||
"versionNonce": 930129275,
|
||||
"index": "aN",
|
||||
"isDeleted": false,
|
||||
"strokeStyle": "solid",
|
||||
"seed": 1,
|
||||
"frameId": null,
|
||||
"updated": 1764191563350,
|
||||
"updated": 1764952855000,
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
@ -1107,197 +1107,6 @@
|
||||
"autoResize": true,
|
||||
"lineHeight": 1.25
|
||||
},
|
||||
{
|
||||
"id": "arrow-prd-validate",
|
||||
"type": "arrow",
|
||||
"x": 439.4640518625828,
|
||||
"y": 331.0450590268819,
|
||||
"width": 0.17283039375342923,
|
||||
"height": 28.50332681186643,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1976d2",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"roughness": 0,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"startBinding": {
|
||||
"elementId": "proc-prd",
|
||||
"focus": 0,
|
||||
"gap": 1
|
||||
},
|
||||
"endBinding": {
|
||||
"elementId": "proc-validate-prd",
|
||||
"focus": 0,
|
||||
"gap": 1
|
||||
},
|
||||
"points": [
|
||||
[
|
||||
0,
|
||||
0
|
||||
],
|
||||
[
|
||||
0.17283039375342923,
|
||||
28.50332681186643
|
||||
]
|
||||
],
|
||||
"lastCommittedPoint": null,
|
||||
"version": 102,
|
||||
"versionNonce": 1274591910,
|
||||
"index": "aP",
|
||||
"isDeleted": false,
|
||||
"strokeStyle": "solid",
|
||||
"seed": 1,
|
||||
"frameId": null,
|
||||
"roundness": null,
|
||||
"boundElements": [],
|
||||
"updated": 1764191023838,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"startArrowhead": null,
|
||||
"endArrowhead": "arrow"
|
||||
},
|
||||
{
|
||||
"id": "proc-validate-prd",
|
||||
"type": "rectangle",
|
||||
"x": 360,
|
||||
"y": 360,
|
||||
"width": 160,
|
||||
"height": 80,
|
||||
"angle": 0,
|
||||
"strokeColor": "#43a047",
|
||||
"backgroundColor": "#c8e6c9",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"roughness": 0,
|
||||
"opacity": 100,
|
||||
"roundness": {
|
||||
"type": 3,
|
||||
"value": 8
|
||||
},
|
||||
"groupIds": [
|
||||
"proc-validate-prd-group"
|
||||
],
|
||||
"boundElements": [
|
||||
{
|
||||
"type": "text",
|
||||
"id": "proc-validate-prd-text"
|
||||
},
|
||||
{
|
||||
"type": "arrow",
|
||||
"id": "arrow-prd-validate"
|
||||
},
|
||||
{
|
||||
"type": "arrow",
|
||||
"id": "arrow-validate-prd-hasui"
|
||||
},
|
||||
{
|
||||
"id": "jv0rnlK2D9JKIGTO7pUtT",
|
||||
"type": "arrow"
|
||||
}
|
||||
],
|
||||
"locked": false,
|
||||
"version": 3,
|
||||
"versionNonce": 894806650,
|
||||
"index": "aQ",
|
||||
"isDeleted": false,
|
||||
"strokeStyle": "solid",
|
||||
"seed": 1,
|
||||
"frameId": null,
|
||||
"updated": 1764191341774,
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"id": "proc-validate-prd-text",
|
||||
"type": "text",
|
||||
"x": 370,
|
||||
"y": 375,
|
||||
"width": 140,
|
||||
"height": 50,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"roughness": 0,
|
||||
"opacity": 100,
|
||||
"groupIds": [
|
||||
"proc-validate-prd-group"
|
||||
],
|
||||
"fontSize": 14,
|
||||
"fontFamily": 1,
|
||||
"text": "Validate PRD\n<<optional>>",
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle",
|
||||
"containerId": "proc-validate-prd",
|
||||
"locked": false,
|
||||
"version": 2,
|
||||
"versionNonce": 944332155,
|
||||
"index": "aR",
|
||||
"isDeleted": false,
|
||||
"strokeStyle": "solid",
|
||||
"seed": 1,
|
||||
"frameId": null,
|
||||
"roundness": null,
|
||||
"boundElements": [],
|
||||
"updated": 1763522171080,
|
||||
"link": null,
|
||||
"originalText": "Validate PRD\n<<optional>>",
|
||||
"autoResize": true,
|
||||
"lineHeight": 1.7857142857142858
|
||||
},
|
||||
{
|
||||
"id": "arrow-validate-prd-hasui",
|
||||
"type": "arrow",
|
||||
"x": 440,
|
||||
"y": 440,
|
||||
"width": 0,
|
||||
"height": 30,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1976d2",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"roughness": 0,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"startBinding": {
|
||||
"elementId": "proc-validate-prd",
|
||||
"focus": 0,
|
||||
"gap": 1
|
||||
},
|
||||
"endBinding": {
|
||||
"elementId": "decision-has-ui",
|
||||
"focus": 0,
|
||||
"gap": 1
|
||||
},
|
||||
"points": [
|
||||
[
|
||||
0,
|
||||
0
|
||||
],
|
||||
[
|
||||
0,
|
||||
30
|
||||
]
|
||||
],
|
||||
"lastCommittedPoint": null,
|
||||
"version": 2,
|
||||
"versionNonce": 1369541557,
|
||||
"index": "aS",
|
||||
"isDeleted": false,
|
||||
"strokeStyle": "solid",
|
||||
"seed": 1,
|
||||
"frameId": null,
|
||||
"roundness": null,
|
||||
"boundElements": [],
|
||||
"updated": 1763522171080,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"startArrowhead": null,
|
||||
"endArrowhead": "arrow"
|
||||
},
|
||||
{
|
||||
"id": "decision-has-ui",
|
||||
"type": "diamond",
|
||||
@ -1322,7 +1131,7 @@
|
||||
},
|
||||
{
|
||||
"type": "arrow",
|
||||
"id": "arrow-validate-prd-hasui"
|
||||
"id": "arrow-prd-hasui"
|
||||
},
|
||||
{
|
||||
"type": "arrow",
|
||||
@ -1334,15 +1143,15 @@
|
||||
}
|
||||
],
|
||||
"locked": false,
|
||||
"version": 2,
|
||||
"versionNonce": 1003877915,
|
||||
"version": 3,
|
||||
"versionNonce": 1003877916,
|
||||
"index": "aT",
|
||||
"isDeleted": false,
|
||||
"strokeStyle": "solid",
|
||||
"seed": 1,
|
||||
"frameId": null,
|
||||
"roundness": null,
|
||||
"updated": 1763522171080,
|
||||
"updated": 1764952855000,
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
@ -5162,6 +4971,57 @@
|
||||
"startArrowhead": null,
|
||||
"endArrowhead": "arrow",
|
||||
"elbowed": false
|
||||
},
|
||||
{
|
||||
"id": "arrow-prd-hasui",
|
||||
"type": "arrow",
|
||||
"x": 440,
|
||||
"y": 330,
|
||||
"width": 0,
|
||||
"height": 140,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1976d2",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"roughness": 0,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"startBinding": {
|
||||
"elementId": "proc-prd",
|
||||
"focus": 0,
|
||||
"gap": 1
|
||||
},
|
||||
"endBinding": {
|
||||
"elementId": "decision-has-ui",
|
||||
"focus": 0,
|
||||
"gap": 1
|
||||
},
|
||||
"points": [
|
||||
[
|
||||
0,
|
||||
0
|
||||
],
|
||||
[
|
||||
0,
|
||||
140
|
||||
]
|
||||
],
|
||||
"lastCommittedPoint": null,
|
||||
"version": 1,
|
||||
"versionNonce": 1,
|
||||
"index": "b1J",
|
||||
"isDeleted": false,
|
||||
"strokeStyle": "solid",
|
||||
"seed": 1,
|
||||
"frameId": null,
|
||||
"roundness": null,
|
||||
"boundElements": [],
|
||||
"updated": 1764952855000,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"startArrowhead": null,
|
||||
"endArrowhead": "arrow"
|
||||
}
|
||||
],
|
||||
"appState": {
|
||||
|
||||
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 88 KiB After Width: | Height: | Size: 87 KiB |
@ -56,11 +56,6 @@ phases:
|
||||
output: "Enterprise PRD with compliance requirements"
|
||||
note: "Must address existing system constraints and migration strategy"
|
||||
|
||||
- id: "validate-prd"
|
||||
recommended: true
|
||||
agent: "pm"
|
||||
command: "validate-prd"
|
||||
|
||||
- id: "create-ux-design"
|
||||
recommended: true
|
||||
agent: "ux-designer"
|
||||
@ -114,7 +109,7 @@ phases:
|
||||
required: true
|
||||
agent: "architect"
|
||||
command: "implementation-readiness"
|
||||
note: "Critical gate - validates all planning + Epics before touching production system"
|
||||
note: "Validates PRD + Architecture + Epics + UX (optional)"
|
||||
|
||||
- phase: 3
|
||||
name: "Implementation"
|
||||
|
||||
@ -44,11 +44,6 @@ phases:
|
||||
output: "Comprehensive Product Requirements Document"
|
||||
note: "Enterprise-level requirements with compliance considerations"
|
||||
|
||||
- id: "validate-prd"
|
||||
recommended: true
|
||||
agent: "pm"
|
||||
command: "validate-prd"
|
||||
|
||||
- id: "create-ux-design"
|
||||
recommended: true
|
||||
agent: "ux-designer"
|
||||
@ -102,7 +97,7 @@ phases:
|
||||
required: true
|
||||
agent: "architect"
|
||||
command: "implementation-readiness"
|
||||
note: "Validates all planning artifacts + Epics + testability align before implementation"
|
||||
note: "Validates PRD + Architecture + Epics + UX (optional)"
|
||||
|
||||
- phase: 3
|
||||
name: "Implementation"
|
||||
|
||||
@ -55,11 +55,6 @@ phases:
|
||||
output: "PRD focused on new features/changes"
|
||||
note: "Must consider existing system constraints"
|
||||
|
||||
- id: "validate-prd"
|
||||
optional: true
|
||||
agent: "pm"
|
||||
command: "validate-prd"
|
||||
|
||||
- id: "create-ux-design"
|
||||
conditional: "if_has_ui"
|
||||
agent: "ux-designer"
|
||||
@ -98,7 +93,7 @@ phases:
|
||||
required: true
|
||||
agent: "architect"
|
||||
command: "implementation-readiness"
|
||||
note: "Validates PRD + UX + Architecture + Epics cohesion before implementation"
|
||||
note: "Validates PRD + Architecture + Epics + UX (optional)"
|
||||
|
||||
- phase: 3
|
||||
name: "Implementation"
|
||||
|
||||
@ -43,12 +43,6 @@ phases:
|
||||
command: "prd"
|
||||
output: "Product Requirements Document with FRs and NFRs"
|
||||
|
||||
- id: "validate-prd"
|
||||
optional: true
|
||||
agent: "pm"
|
||||
command: "validate-prd"
|
||||
note: "Quality check for PRD completeness"
|
||||
|
||||
- id: "create-ux-design"
|
||||
conditional: "if_has_ui"
|
||||
agent: "ux-designer"
|
||||
@ -89,7 +83,7 @@ phases:
|
||||
required: true
|
||||
agent: "architect"
|
||||
command: "implementation-readiness"
|
||||
note: "Validates PRD + UX + Architecture + Epics + Testability cohesion before implementation"
|
||||
note: "Validates PRD + Architecture + Epics + UX (optional)"
|
||||
|
||||
- phase: 3
|
||||
name: "Implementation"
|
||||
|
||||
356
tools/validate-svg-changes.sh
Executable file
356
tools/validate-svg-changes.sh
Executable file
@ -0,0 +1,356 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Visual SVG Validation Script
|
||||
#
|
||||
# Compares old vs new SVG files using browser-accurate rendering (Playwright)
|
||||
# and pixel-level comparison (ImageMagick), then generates a prompt for AI analysis.
|
||||
#
|
||||
# Usage: ./tools/validate-svg-changes.sh <path-to-svg>
|
||||
#
|
||||
|
||||
set -e
|
||||
|
||||
SVG_FILE="${1:-src/modules/bmm/docs/images/workflow-method-greenfield.svg}"
|
||||
TMP_DIR="/tmp/svg-validation-$$"
|
||||
|
||||
echo "🎨 Visual SVG Validation"
|
||||
echo ""
|
||||
|
||||
# Check if file exists
|
||||
if [ ! -f "$SVG_FILE" ]; then
|
||||
echo "❌ Error: SVG file not found: $SVG_FILE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check for ImageMagick
|
||||
if ! command -v magick &> /dev/null; then
|
||||
echo "❌ ImageMagick not found"
|
||||
echo ""
|
||||
echo "Install with:"
|
||||
echo " brew install imagemagick"
|
||||
echo ""
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✓ ImageMagick found"
|
||||
|
||||
# Check for Node.js
|
||||
if ! command -v node &> /dev/null; then
|
||||
echo "❌ Node.js not found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✓ Node.js found ($(node -v))"
|
||||
|
||||
# Check for Playwright (local install)
|
||||
if [ ! -d "node_modules/playwright" ]; then
|
||||
echo ""
|
||||
echo "📦 Playwright not found locally"
|
||||
echo "Installing Playwright (local to this project, no package.json changes)..."
|
||||
echo ""
|
||||
npm install --no-save playwright
|
||||
echo ""
|
||||
echo "✓ Playwright installed"
|
||||
else
|
||||
echo "✓ Playwright found"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "🔄 Rendering SVGs to PNG..."
|
||||
echo ""
|
||||
|
||||
# Create temp directory
|
||||
mkdir -p "$TMP_DIR"
|
||||
|
||||
# Extract old SVG from git
|
||||
git show HEAD:"$SVG_FILE" > "$TMP_DIR/old.svg" 2>/dev/null || {
|
||||
echo "❌ Could not extract old SVG from git HEAD"
|
||||
echo " Make sure you have uncommitted changes to compare"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Copy new SVG
|
||||
cp "$SVG_FILE" "$TMP_DIR/new.svg"
|
||||
|
||||
# Create Node.js renderer script in project directory (so it can find node_modules)
|
||||
cat > "tools/render-svg-temp.js" << 'EOJS'
|
||||
const { chromium } = require('playwright');
|
||||
const fs = require('fs');
|
||||
|
||||
async function renderSVG(svgPath, pngPath) {
|
||||
const browser = await chromium.launch({ headless: true });
|
||||
const page = await browser.newPage();
|
||||
|
||||
const svgContent = fs.readFileSync(svgPath, 'utf8');
|
||||
const widthMatch = svgContent.match(/width="([^"]+)"/);
|
||||
const heightMatch = svgContent.match(/height="([^"]+)"/);
|
||||
const width = Math.ceil(parseFloat(widthMatch[1]));
|
||||
const height = Math.ceil(parseFloat(heightMatch[1]));
|
||||
|
||||
const html = `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
body { margin: 0; padding: 0; background: white; }
|
||||
svg { display: block; }
|
||||
</style>
|
||||
</head>
|
||||
<body>${svgContent}</body>
|
||||
</html>
|
||||
`;
|
||||
|
||||
await page.setContent(html);
|
||||
await page.setViewportSize({ width, height });
|
||||
await page.waitForTimeout(1000);
|
||||
await page.screenshot({ path: pngPath, fullPage: true });
|
||||
await browser.close();
|
||||
|
||||
console.log(`✓ Rendered ${pngPath}`);
|
||||
}
|
||||
|
||||
(async () => {
|
||||
await renderSVG(process.argv[2], process.argv[3]);
|
||||
await renderSVG(process.argv[4], process.argv[5]);
|
||||
})();
|
||||
EOJS
|
||||
|
||||
# Render both SVGs (run from project dir so node_modules is accessible)
|
||||
node tools/render-svg-temp.js \
|
||||
"$TMP_DIR/old.svg" "$TMP_DIR/old.png" \
|
||||
"$TMP_DIR/new.svg" "$TMP_DIR/new.png"
|
||||
|
||||
# Clean up temp script
|
||||
rm tools/render-svg-temp.js
|
||||
|
||||
echo ""
|
||||
echo "🔍 Comparing pixels..."
|
||||
echo ""
|
||||
|
||||
# Compare using ImageMagick
|
||||
DIFF_OUTPUT=$(magick compare -metric AE "$TMP_DIR/old.png" "$TMP_DIR/new.png" "$TMP_DIR/diff.png" 2>&1 || true)
|
||||
DIFF_PIXELS=$(echo "$DIFF_OUTPUT" | awk '{print $1}')
|
||||
|
||||
# Get image dimensions
|
||||
DIMENSIONS=$(magick identify -format "%wx%h" "$TMP_DIR/old.png")
|
||||
WIDTH=$(echo "$DIMENSIONS" | cut -d'x' -f1)
|
||||
HEIGHT=$(echo "$DIMENSIONS" | cut -d'x' -f2)
|
||||
TOTAL_PIXELS=$((WIDTH * HEIGHT))
|
||||
|
||||
# Calculate percentage
|
||||
DIFF_PERCENT=$(echo "scale=4; $DIFF_PIXELS / $TOTAL_PIXELS * 100" | bc)
|
||||
|
||||
echo "📊 Results:"
|
||||
echo " Dimensions: ${WIDTH} × ${HEIGHT}"
|
||||
echo " Total pixels: $(printf "%'d" $TOTAL_PIXELS)"
|
||||
echo " Different pixels: $(printf "%'d" $DIFF_PIXELS)"
|
||||
echo " Difference: ${DIFF_PERCENT}%"
|
||||
echo ""
|
||||
|
||||
if (( $(echo "$DIFF_PERCENT < 0.01" | bc -l) )); then
|
||||
echo "✅ ESSENTIALLY IDENTICAL (< 0.01% difference)"
|
||||
VERDICT="essentially identical"
|
||||
elif (( $(echo "$DIFF_PERCENT < 0.1" | bc -l) )); then
|
||||
echo "⚠️ MINOR DIFFERENCES (< 0.1%)"
|
||||
VERDICT="minor differences detected"
|
||||
else
|
||||
echo "❌ SIGNIFICANT DIFFERENCES (≥ 0.1%)"
|
||||
VERDICT="significant differences detected"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "📁 Output files:"
|
||||
echo " Old render: $TMP_DIR/old.png"
|
||||
echo " New render: $TMP_DIR/new.png"
|
||||
echo " Diff image: $TMP_DIR/diff.png"
|
||||
echo ""
|
||||
|
||||
# Generate HTML comparison page
|
||||
cat > "$TMP_DIR/comparison.html" << 'EOHTML'
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>SVG Comparison</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||||
background: #f5f5f5;
|
||||
padding: 20px;
|
||||
}
|
||||
.header {
|
||||
background: white;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 20px;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
}
|
||||
h1 { margin-bottom: 10px; color: #333; }
|
||||
.stats {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 10px;
|
||||
margin-top: 15px;
|
||||
}
|
||||
.stat {
|
||||
background: #f8f9fa;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.stat-label { font-size: 12px; color: #666; text-transform: uppercase; }
|
||||
.stat-value { font-size: 18px; font-weight: 600; color: #333; margin-top: 4px; }
|
||||
.container {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
gap: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.panel {
|
||||
background: white;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
}
|
||||
h2 {
|
||||
margin: 0 0 15px 0;
|
||||
color: #333;
|
||||
font-size: 18px;
|
||||
border-bottom: 2px solid #e0e0e0;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
.image-container {
|
||||
border: 1px solid #ddd;
|
||||
background: white;
|
||||
overflow: auto;
|
||||
max-height: 600px;
|
||||
}
|
||||
img {
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
.verdict {
|
||||
display: inline-block;
|
||||
padding: 4px 12px;
|
||||
border-radius: 12px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.verdict.good { background: #d4edda; color: #155724; }
|
||||
.verdict.warning { background: #fff3cd; color: #856404; }
|
||||
.verdict.bad { background: #f8d7da; color: #721c24; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
<h1>🎨 SVG Visual Comparison</h1>
|
||||
<p><strong>File:</strong> FILENAME_PLACEHOLDER</p>
|
||||
<div class="stats">
|
||||
<div class="stat">
|
||||
<div class="stat-label">Dimensions</div>
|
||||
<div class="stat-value">DIMENSIONS_PLACEHOLDER</div>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<div class="stat-label">Different Pixels</div>
|
||||
<div class="stat-value">DIFF_PIXELS_PLACEHOLDER</div>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<div class="stat-label">Difference</div>
|
||||
<div class="stat-value">DIFF_PERCENT_PLACEHOLDER%</div>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<div class="stat-label">Verdict</div>
|
||||
<div class="stat-value"><span class="verdict VERDICT_CLASS_PLACEHOLDER">VERDICT_PLACEHOLDER</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<div class="panel">
|
||||
<h2>📄 Old (HEAD)</h2>
|
||||
<div class="image-container">
|
||||
<img src="old.png" alt="Old SVG">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel">
|
||||
<h2>📝 New (Working)</h2>
|
||||
<div class="image-container">
|
||||
<img src="new.png" alt="New SVG">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel">
|
||||
<h2>🔍 Diff (Red = Changes)</h2>
|
||||
<div class="image-container">
|
||||
<img src="diff.png" alt="Diff">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
EOHTML
|
||||
|
||||
# Determine verdict class for styling
|
||||
if (( $(echo "$DIFF_PERCENT < 0.01" | bc -l) )); then
|
||||
VERDICT_CLASS="good"
|
||||
elif (( $(echo "$DIFF_PERCENT < 0.1" | bc -l) )); then
|
||||
VERDICT_CLASS="warning"
|
||||
else
|
||||
VERDICT_CLASS="bad"
|
||||
fi
|
||||
|
||||
# Replace placeholders in HTML
|
||||
sed -i '' "s|FILENAME_PLACEHOLDER|$SVG_FILE|g" "$TMP_DIR/comparison.html"
|
||||
sed -i '' "s|DIMENSIONS_PLACEHOLDER|${WIDTH} × ${HEIGHT}|g" "$TMP_DIR/comparison.html"
|
||||
sed -i '' "s|DIFF_PIXELS_PLACEHOLDER|$(printf "%'d" $DIFF_PIXELS) / $(printf "%'d" $TOTAL_PIXELS)|g" "$TMP_DIR/comparison.html"
|
||||
sed -i '' "s|DIFF_PERCENT_PLACEHOLDER|$DIFF_PERCENT|g" "$TMP_DIR/comparison.html"
|
||||
sed -i '' "s|VERDICT_PLACEHOLDER|$VERDICT|g" "$TMP_DIR/comparison.html"
|
||||
sed -i '' "s|VERDICT_CLASS_PLACEHOLDER|$VERDICT_CLASS|g" "$TMP_DIR/comparison.html"
|
||||
|
||||
echo "✓ Generated comparison page: $TMP_DIR/comparison.html"
|
||||
echo ""
|
||||
echo "🌐 Opening comparison in browser..."
|
||||
open "$TMP_DIR/comparison.html"
|
||||
echo ""
|
||||
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
echo "🤖 AI VISUAL ANALYSIS PROMPT"
|
||||
echo ""
|
||||
echo "Copy and paste this into Gemini/Claude with the diff image attached:"
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
cat << PROMPT
|
||||
|
||||
I've made changes to an Excalidraw diagram SVG file. Please analyze the visual differences between the old and new versions.
|
||||
|
||||
**Automated Analysis:**
|
||||
- Dimensions: ${WIDTH} × ${HEIGHT} pixels
|
||||
- Different pixels: $(printf "%'d" $DIFF_PIXELS) out of $(printf "%'d" $TOTAL_PIXELS)
|
||||
- Difference: ${DIFF_PERCENT}%
|
||||
- Verdict: ${VERDICT}
|
||||
|
||||
**Attached Image:**
|
||||
The attached image shows the pixel-level diff (red = differences).
|
||||
|
||||
**Questions:**
|
||||
1. Are the differences purely anti-aliasing/rendering artifacts, or are there actual content changes?
|
||||
2. If there are content changes, what specifically changed?
|
||||
3. Do the changes align with the intent to remove zombie Excalidraw elements (elements marked as deleted but left in the JSON)?
|
||||
4. Is this safe to commit?
|
||||
|
||||
**Context:**
|
||||
- File: $SVG_FILE
|
||||
- Changes: Removed 191 lines of zombie JSON from Excalidraw source
|
||||
- Expected: Visual output should be identical (zombie elements were already marked as deleted)
|
||||
|
||||
PROMPT
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
echo "📎 Attach this file to your AI prompt:"
|
||||
echo " $TMP_DIR/diff.png"
|
||||
echo ""
|
||||
echo "💡 To open the diff image:"
|
||||
echo " open $TMP_DIR/diff.png"
|
||||
echo ""
|
||||
Loading…
x
Reference in New Issue
Block a user