Compare commits
1 Commits
d8d2e1d2a1
...
181c246ea9
| Author | SHA1 | Date |
|---|---|---|
|
|
181c246ea9 |
|
|
@ -1,7 +1,7 @@
|
||||||
# Senior Developer Review - Validation Checklist
|
# Senior Developer Review - Validation Checklist
|
||||||
|
|
||||||
- [ ] Story file loaded from `{{story_path}}`
|
- [ ] Story file loaded from `{{story_path}}`
|
||||||
- [ ] Story Status verified as reviewable (review)
|
- [ ] Story Status verified as one of: {{allow_status_values}}
|
||||||
- [ ] Epic and Story IDs resolved ({{epic_num}}.{{story_num}})
|
- [ ] Epic and Story IDs resolved ({{epic_num}}.{{story_num}})
|
||||||
- [ ] Story Context located or warning recorded
|
- [ ] Story Context located or warning recorded
|
||||||
- [ ] Epic Tech Spec located or warning recorded
|
- [ ] Epic Tech Spec located or warning recorded
|
||||||
|
|
@ -17,7 +17,6 @@
|
||||||
- [ ] Review notes appended under "Senior Developer Review (AI)"
|
- [ ] Review notes appended under "Senior Developer Review (AI)"
|
||||||
- [ ] Change Log updated with review entry
|
- [ ] Change Log updated with review entry
|
||||||
- [ ] Status updated according to settings (if enabled)
|
- [ ] Status updated according to settings (if enabled)
|
||||||
- [ ] Sprint status synced (if sprint tracking enabled)
|
|
||||||
- [ ] Story saved successfully
|
- [ ] Story saved successfully
|
||||||
|
|
||||||
_Reviewer: {{user_name}} on {{date}}_
|
_Reviewer: {{user_name}} on {{date}}_
|
||||||
|
|
@ -76,7 +76,8 @@ 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)
|
- `create-prd` - Create PRD for Level 2-4 projects (creates FRs/NFRs only)
|
||||||
- `tech-spec` - Quick spec for Level 0-1 projects
|
- `tech-spec` - Quick spec for Level 0-1 projects
|
||||||
- `create-epics-and-stories` - Break PRD into implementable pieces (runs AFTER architecture)
|
- `create-epics-and-stories` - Break PRD into implementable pieces (runs AFTER architecture)
|
||||||
- `implementation-readiness` - Validate PRD + Architecture + Epics + UX (optional)
|
- `validate-prd` - Validate PRD completeness
|
||||||
|
- `validate-tech-spec` - Validate Technical Specification
|
||||||
- `correct-course` - Handle mid-project changes
|
- `correct-course` - Handle mid-project changes
|
||||||
- `workflow-init` - Initialize workflow tracking
|
- `workflow-init` - Initialize workflow tracking
|
||||||
|
|
||||||
|
|
@ -145,7 +146,7 @@ The BMad Method Module (BMM) provides a comprehensive team of specialized AI age
|
||||||
- `workflow-status` - Check what to do next
|
- `workflow-status` - Check what to do next
|
||||||
- `create-architecture` - Produce a Scale Adaptive Architecture
|
- `create-architecture` - Produce a Scale Adaptive Architecture
|
||||||
- `validate-architecture` - Validate architecture document
|
- `validate-architecture` - Validate architecture document
|
||||||
- `implementation-readiness` - Validate PRD + Architecture + Epics + UX (optional)
|
- `implementation-readiness` - Validate readiness for Phase 4
|
||||||
|
|
||||||
**Communication Style:** Comprehensive yet pragmatic. Uses architectural metaphors. Balances technical depth with accessibility. Connects decisions to business value.
|
**Communication Style:** Comprehensive yet pragmatic. Uses architectural metaphors. Balances technical depth with accessibility. Connects decisions to business value.
|
||||||
|
|
||||||
|
|
@ -641,12 +642,13 @@ Some workflows are available to multiple agents:
|
||||||
|
|
||||||
Many workflows have optional validation workflows that perform independent review:
|
Many workflows have optional validation workflows that perform independent review:
|
||||||
|
|
||||||
| Validation | Agent | Validates |
|
| Validation | Agent | Validates |
|
||||||
| -------------------------- | ----------- | ------------------------------------------ |
|
| ----------------------- | ----------- | -------------------------------- |
|
||||||
| `implementation-readiness` | Architect | PRD + Architecture + Epics + UX (optional) |
|
| `validate-prd` | PM | PRD completeness (FRs/NFRs only) |
|
||||||
| `validate-architecture` | Architect | Architecture document |
|
| `validate-tech-spec` | PM | Technical specification quality |
|
||||||
| `validate-design` | UX Designer | UX specification and artifacts |
|
| `validate-architecture` | Architect | Architecture document |
|
||||||
| `validate-create-story` | SM | Story draft |
|
| `validate-design` | UX Designer | UX specification and artifacts |
|
||||||
|
| `validate-create-story` | SM | Story draft |
|
||||||
|
|
||||||
**When to use validation:**
|
**When to use validation:**
|
||||||
|
|
||||||
|
|
@ -943,8 +945,9 @@ Agent analyzes project state → recommends next workflow
|
||||||
|
|
||||||
```
|
```
|
||||||
Each phase has validation gates:
|
Each phase has validation gates:
|
||||||
- Phase 3 to 4: implementation-readiness (validates PRD + Architecture + Epics + UX (optional))
|
- Phase 2 to 3: validate-prd, validate-tech-spec
|
||||||
Run validation before advancing to implementation
|
- Phase 3 to 4: implementation-readiness
|
||||||
|
Run validation before advancing
|
||||||
```
|
```
|
||||||
|
|
||||||
**Course correction:**
|
**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?
|
### 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 PRD + Architecture + Epics + UX (optional) are aligned before implementation. Pass the gate check = ready for 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.
|
||||||
|
|
||||||
### Q: Can I run workflows in parallel or do they have to be sequential?
|
### 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
|
### Gate Check
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
### DoD (Definition of Done)
|
### DoD (Definition of Done)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,37 +0,0 @@
|
||||||
# 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,6 +1036,10 @@
|
||||||
"type": "arrow",
|
"type": "arrow",
|
||||||
"id": "arrow-discovery-no"
|
"id": "arrow-discovery-no"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "arrow",
|
||||||
|
"id": "arrow-prd-validate"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "arrow-phase1-to-phase2",
|
"id": "arrow-phase1-to-phase2",
|
||||||
"type": "arrow"
|
"type": "arrow"
|
||||||
|
|
@ -1051,21 +1055,17 @@
|
||||||
{
|
{
|
||||||
"id": "arrow-has-ui-no",
|
"id": "arrow-has-ui-no",
|
||||||
"type": "arrow"
|
"type": "arrow"
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "arrow-prd-hasui",
|
|
||||||
"type": "arrow"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"locked": false,
|
"locked": false,
|
||||||
"version": 108,
|
"version": 107,
|
||||||
"versionNonce": 930129275,
|
"versionNonce": 930129274,
|
||||||
"index": "aN",
|
"index": "aN",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
"seed": 1,
|
"seed": 1,
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"updated": 1764952855000,
|
"updated": 1764191563350,
|
||||||
"link": null
|
"link": null
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -1107,6 +1107,197 @@
|
||||||
"autoResize": true,
|
"autoResize": true,
|
||||||
"lineHeight": 1.25
|
"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",
|
"id": "decision-has-ui",
|
||||||
"type": "diamond",
|
"type": "diamond",
|
||||||
|
|
@ -1131,7 +1322,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "arrow",
|
"type": "arrow",
|
||||||
"id": "arrow-prd-hasui"
|
"id": "arrow-validate-prd-hasui"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "arrow",
|
"type": "arrow",
|
||||||
|
|
@ -1143,15 +1334,15 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"locked": false,
|
"locked": false,
|
||||||
"version": 3,
|
"version": 2,
|
||||||
"versionNonce": 1003877916,
|
"versionNonce": 1003877915,
|
||||||
"index": "aT",
|
"index": "aT",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
"seed": 1,
|
"seed": 1,
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"updated": 1764952855000,
|
"updated": 1763522171080,
|
||||||
"link": null
|
"link": null
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -4971,57 +5162,6 @@
|
||||||
"startArrowhead": null,
|
"startArrowhead": null,
|
||||||
"endArrowhead": "arrow",
|
"endArrowhead": "arrow",
|
||||||
"elbowed": false
|
"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": {
|
"appState": {
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 87 KiB After Width: | Height: | Size: 88 KiB |
|
|
@ -16,7 +16,6 @@
|
||||||
<step n="1" goal="Load story and discover changes">
|
<step n="1" goal="Load story and discover changes">
|
||||||
<action>Use provided {{story_path}} or ask user which story file to review</action>
|
<action>Use provided {{story_path}} or ask user which story file to review</action>
|
||||||
<action>Read COMPLETE story file</action>
|
<action>Read COMPLETE story file</action>
|
||||||
<action>Set {{story_key}} = extracted key from filename (e.g., "1-2-user-authentication.md" → "1-2-user-authentication") or story metadata</action>
|
|
||||||
<action>Parse sections: Story, Acceptance Criteria, Tasks/Subtasks, Dev Agent Record → File List, Change Log</action>
|
<action>Parse sections: Story, Acceptance Criteria, Tasks/Subtasks, Dev Agent Record → File List, Change Log</action>
|
||||||
|
|
||||||
<!-- Discover actual changes via git -->
|
<!-- Discover actual changes via git -->
|
||||||
|
|
@ -107,8 +106,6 @@
|
||||||
|
|
||||||
<step n="4" goal="Present findings and fix them">
|
<step n="4" goal="Present findings and fix them">
|
||||||
<action>Categorize findings: HIGH (must fix), MEDIUM (should fix), LOW (nice to fix)</action>
|
<action>Categorize findings: HIGH (must fix), MEDIUM (should fix), LOW (nice to fix)</action>
|
||||||
<action>Set {{fixed_count}} = 0</action>
|
|
||||||
<action>Set {{action_count}} = 0</action>
|
|
||||||
|
|
||||||
<output>**🔥 CODE REVIEW FINDINGS, {user_name}!**
|
<output>**🔥 CODE REVIEW FINDINGS, {user_name}!**
|
||||||
|
|
||||||
|
|
@ -148,15 +145,11 @@
|
||||||
<action>Add/update tests as needed</action>
|
<action>Add/update tests as needed</action>
|
||||||
<action>Update File List in story if files changed</action>
|
<action>Update File List in story if files changed</action>
|
||||||
<action>Update story Dev Agent Record with fixes applied</action>
|
<action>Update story Dev Agent Record with fixes applied</action>
|
||||||
<action>Set {{fixed_count}} = number of HIGH and MEDIUM issues fixed</action>
|
|
||||||
<action>Set {{action_count}} = 0</action>
|
|
||||||
</check>
|
</check>
|
||||||
|
|
||||||
<check if="user chooses 2">
|
<check if="user chooses 2">
|
||||||
<action>Add "Review Follow-ups (AI)" subsection to Tasks/Subtasks</action>
|
<action>Add "Review Follow-ups (AI)" subsection to Tasks/Subtasks</action>
|
||||||
<action>For each issue: `- [ ] [AI-Review][Severity] Description [file:line]`</action>
|
<action>For each issue: `- [ ] [AI-Review][Severity] Description [file:line]`</action>
|
||||||
<action>Set {{action_count}} = number of action items created</action>
|
|
||||||
<action>Set {{fixed_count}} = 0</action>
|
|
||||||
</check>
|
</check>
|
||||||
|
|
||||||
<check if="user chooses 3">
|
<check if="user chooses 3">
|
||||||
|
|
@ -165,52 +158,11 @@
|
||||||
</check>
|
</check>
|
||||||
</step>
|
</step>
|
||||||
|
|
||||||
<step n="5" goal="Update story status and sync sprint tracking">
|
<step n="5" goal="Update story status">
|
||||||
<!-- Determine new status based on review outcome -->
|
<action>If all HIGH issues fixed and ACs implemented → Update story Status to "done"</action>
|
||||||
<check if="all HIGH and MEDIUM issues fixed AND all ACs implemented">
|
<action>If issues remain → Update story Status to "in-progress"</action>
|
||||||
<action>Set {{new_status}} = "done"</action>
|
|
||||||
<action>Update story Status field to "done"</action>
|
|
||||||
</check>
|
|
||||||
<check if="HIGH or MEDIUM issues remain OR ACs not fully implemented">
|
|
||||||
<action>Set {{new_status}} = "in-progress"</action>
|
|
||||||
<action>Update story Status field to "in-progress"</action>
|
|
||||||
</check>
|
|
||||||
<action>Save story file</action>
|
<action>Save story file</action>
|
||||||
|
|
||||||
<!-- Determine sprint tracking status -->
|
|
||||||
<check if="{sprint_status} file exists">
|
|
||||||
<action>Set {{current_sprint_status}} = "enabled"</action>
|
|
||||||
</check>
|
|
||||||
<check if="{sprint_status} file does NOT exist">
|
|
||||||
<action>Set {{current_sprint_status}} = "no-sprint-tracking"</action>
|
|
||||||
</check>
|
|
||||||
|
|
||||||
<!-- Sync sprint-status.yaml when story status changes (only if sprint tracking enabled) -->
|
|
||||||
<check if="{{current_sprint_status}} != 'no-sprint-tracking'">
|
|
||||||
<action>Load the FULL file: {sprint_status}</action>
|
|
||||||
<action>Find development_status key matching {{story_key}}</action>
|
|
||||||
|
|
||||||
<check if="{{new_status}} == 'done'">
|
|
||||||
<action>Update development_status[{{story_key}}] = "done"</action>
|
|
||||||
<action>Save file, preserving ALL comments and structure</action>
|
|
||||||
<output>✅ Sprint status synced: {{story_key}} → done</output>
|
|
||||||
</check>
|
|
||||||
|
|
||||||
<check if="{{new_status}} == 'in-progress'">
|
|
||||||
<action>Update development_status[{{story_key}}] = "in-progress"</action>
|
|
||||||
<action>Save file, preserving ALL comments and structure</action>
|
|
||||||
<output>🔄 Sprint status synced: {{story_key}} → in-progress</output>
|
|
||||||
</check>
|
|
||||||
|
|
||||||
<check if="story key not found in sprint status">
|
|
||||||
<output>⚠️ Story file updated, but sprint-status sync failed: {{story_key}} not found in sprint-status.yaml</output>
|
|
||||||
</check>
|
|
||||||
</check>
|
|
||||||
|
|
||||||
<check if="{{current_sprint_status}} == 'no-sprint-tracking'">
|
|
||||||
<output>ℹ️ Story status updated (no sprint tracking configured)</output>
|
|
||||||
</check>
|
|
||||||
|
|
||||||
<output>**✅ Review Complete!**
|
<output>**✅ Review Complete!**
|
||||||
|
|
||||||
**Story Status:** {{new_status}}
|
**Story Status:** {{new_status}}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
# Review Story Workflow
|
# Review Story Workflow
|
||||||
name: code-review
|
name: code-review
|
||||||
description: "Perform an ADVERSARIAL Senior Developer code review that finds 3-10 specific problems in every story. Challenges everything: code quality, test coverage, architecture compliance, security, performance. NEVER accepts `looks good` - must find minimum issues and can auto-fix with user approval."
|
description: "Perform an ADVERSARIAL Senior Developer code review that finds 3-10 specific problems in every story. Challenges everything: code quality, test coverage, architecture compliance, security, performance. NEVER accepts 'looks good' - must find minimum issues and can auto-fix with user approval."
|
||||||
author: "BMad"
|
author: "BMad"
|
||||||
|
|
||||||
# Critical variables from config
|
# Critical variables from config
|
||||||
|
|
|
||||||
|
|
@ -27,21 +27,10 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Determine Node version
|
|
||||||
id: node-version
|
|
||||||
run: |
|
|
||||||
if [ -f .nvmrc ]; then
|
|
||||||
echo "value=$(cat .nvmrc)" >> "$GITHUB_OUTPUT"
|
|
||||||
echo "Using Node from .nvmrc"
|
|
||||||
else
|
|
||||||
echo "value=24" >> "$GITHUB_OUTPUT"
|
|
||||||
echo "Using default Node 24 (current LTS)"
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: ${{ steps.node-version.outputs.value }}
|
node-version-file: ".nvmrc"
|
||||||
cache: "npm"
|
cache: "npm"
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
|
|
@ -65,21 +54,10 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Determine Node version
|
|
||||||
id: node-version
|
|
||||||
run: |
|
|
||||||
if [ -f .nvmrc ]; then
|
|
||||||
echo "value=$(cat .nvmrc)" >> "$GITHUB_OUTPUT"
|
|
||||||
echo "Using Node from .nvmrc"
|
|
||||||
else
|
|
||||||
echo "value=22" >> "$GITHUB_OUTPUT"
|
|
||||||
echo "Using default Node 22 (current LTS)"
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: ${{ steps.node-version.outputs.value }}
|
node-version-file: ".nvmrc"
|
||||||
cache: "npm"
|
cache: "npm"
|
||||||
|
|
||||||
- name: Cache Playwright browsers
|
- name: Cache Playwright browsers
|
||||||
|
|
@ -121,21 +99,10 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Determine Node version
|
|
||||||
id: node-version
|
|
||||||
run: |
|
|
||||||
if [ -f .nvmrc ]; then
|
|
||||||
echo "value=$(cat .nvmrc)" >> "$GITHUB_OUTPUT"
|
|
||||||
echo "Using Node from .nvmrc"
|
|
||||||
else
|
|
||||||
echo "value=22" >> "$GITHUB_OUTPUT"
|
|
||||||
echo "Using default Node 22 (current LTS)"
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: ${{ steps.node-version.outputs.value }}
|
node-version-file: ".nvmrc"
|
||||||
cache: "npm"
|
cache: "npm"
|
||||||
|
|
||||||
- name: Cache Playwright browsers
|
- name: Cache Playwright browsers
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,6 @@ variables:
|
||||||
npm_config_cache: "$CI_PROJECT_DIR/.npm"
|
npm_config_cache: "$CI_PROJECT_DIR/.npm"
|
||||||
# Playwright browser cache
|
# Playwright browser cache
|
||||||
PLAYWRIGHT_BROWSERS_PATH: "$CI_PROJECT_DIR/.cache/ms-playwright"
|
PLAYWRIGHT_BROWSERS_PATH: "$CI_PROJECT_DIR/.cache/ms-playwright"
|
||||||
# Default Node version when .nvmrc is missing
|
|
||||||
DEFAULT_NODE_VERSION: "24"
|
|
||||||
|
|
||||||
# Caching configuration
|
# Caching configuration
|
||||||
cache:
|
cache:
|
||||||
|
|
@ -31,32 +29,19 @@ cache:
|
||||||
# Lint stage - Code quality checks
|
# Lint stage - Code quality checks
|
||||||
lint:
|
lint:
|
||||||
stage: lint
|
stage: lint
|
||||||
image: node:$DEFAULT_NODE_VERSION
|
image: node:20
|
||||||
before_script:
|
|
||||||
- |
|
|
||||||
NODE_VERSION=$(cat .nvmrc 2>/dev/null || echo "$DEFAULT_NODE_VERSION")
|
|
||||||
echo "Using Node $NODE_VERSION"
|
|
||||||
npm install -g n
|
|
||||||
n "$NODE_VERSION"
|
|
||||||
node -v
|
|
||||||
- npm ci
|
|
||||||
script:
|
script:
|
||||||
|
- npm ci
|
||||||
- npm run lint
|
- npm run lint
|
||||||
timeout: 5 minutes
|
timeout: 5 minutes
|
||||||
|
|
||||||
# Test stage - Parallel execution with sharding
|
# Test stage - Parallel execution with sharding
|
||||||
.test-template: &test-template
|
.test-template: &test-template
|
||||||
stage: test
|
stage: test
|
||||||
image: node:$DEFAULT_NODE_VERSION
|
image: node:20
|
||||||
needs:
|
needs:
|
||||||
- lint
|
- lint
|
||||||
before_script:
|
before_script:
|
||||||
- |
|
|
||||||
NODE_VERSION=$(cat .nvmrc 2>/dev/null || echo "$DEFAULT_NODE_VERSION")
|
|
||||||
echo "Using Node $NODE_VERSION"
|
|
||||||
npm install -g n
|
|
||||||
n "$NODE_VERSION"
|
|
||||||
node -v
|
|
||||||
- npm ci
|
- npm ci
|
||||||
- npx playwright install --with-deps chromium
|
- npx playwright install --with-deps chromium
|
||||||
artifacts:
|
artifacts:
|
||||||
|
|
@ -90,7 +75,7 @@ test:shard-4:
|
||||||
# Burn-in stage - Flaky test detection
|
# Burn-in stage - Flaky test detection
|
||||||
burn-in:
|
burn-in:
|
||||||
stage: burn-in
|
stage: burn-in
|
||||||
image: node:$DEFAULT_NODE_VERSION
|
image: node:20
|
||||||
needs:
|
needs:
|
||||||
- test:shard-1
|
- test:shard-1
|
||||||
- test:shard-2
|
- test:shard-2
|
||||||
|
|
@ -101,12 +86,6 @@ burn-in:
|
||||||
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
|
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
|
||||||
- if: '$CI_PIPELINE_SOURCE == "schedule"'
|
- if: '$CI_PIPELINE_SOURCE == "schedule"'
|
||||||
before_script:
|
before_script:
|
||||||
- |
|
|
||||||
NODE_VERSION=$(cat .nvmrc 2>/dev/null || echo "$DEFAULT_NODE_VERSION")
|
|
||||||
echo "Using Node $NODE_VERSION"
|
|
||||||
npm install -g n
|
|
||||||
n "$NODE_VERSION"
|
|
||||||
node -v
|
|
||||||
- npm ci
|
- npm ci
|
||||||
- npx playwright install --with-deps chromium
|
- npx playwright install --with-deps chromium
|
||||||
script:
|
script:
|
||||||
|
|
|
||||||
|
|
@ -61,8 +61,8 @@ Scaffolds a production-ready CI/CD quality pipeline with test execution, burn-in
|
||||||
- Ask user if unable to auto-detect
|
- Ask user if unable to auto-detect
|
||||||
|
|
||||||
5. **Read Environment Configuration**
|
5. **Read Environment Configuration**
|
||||||
- Use `.nvmrc` for Node version if present
|
- Check for `.nvmrc` to determine Node version
|
||||||
- If missing, default to a current LTS (Node 24) or newer instead of a fixed old version
|
- Default to Node 20 LTS if not found
|
||||||
- Read `package.json` to identify dependencies (affects caching strategy)
|
- Read `package.json` to identify dependencies (affects caching strategy)
|
||||||
|
|
||||||
**Halt Condition:** If preflight checks fail, stop immediately and report which requirement failed.
|
**Halt Condition:** If preflight checks fail, stop immediately and report which requirement failed.
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,11 @@ phases:
|
||||||
output: "Enterprise PRD with compliance requirements"
|
output: "Enterprise PRD with compliance requirements"
|
||||||
note: "Must address existing system constraints and migration strategy"
|
note: "Must address existing system constraints and migration strategy"
|
||||||
|
|
||||||
|
- id: "validate-prd"
|
||||||
|
recommended: true
|
||||||
|
agent: "pm"
|
||||||
|
command: "validate-prd"
|
||||||
|
|
||||||
- id: "create-ux-design"
|
- id: "create-ux-design"
|
||||||
recommended: true
|
recommended: true
|
||||||
agent: "ux-designer"
|
agent: "ux-designer"
|
||||||
|
|
@ -109,7 +114,7 @@ phases:
|
||||||
required: true
|
required: true
|
||||||
agent: "architect"
|
agent: "architect"
|
||||||
command: "implementation-readiness"
|
command: "implementation-readiness"
|
||||||
note: "Validates PRD + Architecture + Epics + UX (optional)"
|
note: "Critical gate - validates all planning + Epics before touching production system"
|
||||||
|
|
||||||
- phase: 3
|
- phase: 3
|
||||||
name: "Implementation"
|
name: "Implementation"
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,11 @@ phases:
|
||||||
output: "Comprehensive Product Requirements Document"
|
output: "Comprehensive Product Requirements Document"
|
||||||
note: "Enterprise-level requirements with compliance considerations"
|
note: "Enterprise-level requirements with compliance considerations"
|
||||||
|
|
||||||
|
- id: "validate-prd"
|
||||||
|
recommended: true
|
||||||
|
agent: "pm"
|
||||||
|
command: "validate-prd"
|
||||||
|
|
||||||
- id: "create-ux-design"
|
- id: "create-ux-design"
|
||||||
recommended: true
|
recommended: true
|
||||||
agent: "ux-designer"
|
agent: "ux-designer"
|
||||||
|
|
@ -97,7 +102,7 @@ phases:
|
||||||
required: true
|
required: true
|
||||||
agent: "architect"
|
agent: "architect"
|
||||||
command: "implementation-readiness"
|
command: "implementation-readiness"
|
||||||
note: "Validates PRD + Architecture + Epics + UX (optional)"
|
note: "Validates all planning artifacts + Epics + testability align before implementation"
|
||||||
|
|
||||||
- phase: 3
|
- phase: 3
|
||||||
name: "Implementation"
|
name: "Implementation"
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,11 @@ phases:
|
||||||
output: "PRD focused on new features/changes"
|
output: "PRD focused on new features/changes"
|
||||||
note: "Must consider existing system constraints"
|
note: "Must consider existing system constraints"
|
||||||
|
|
||||||
|
- id: "validate-prd"
|
||||||
|
optional: true
|
||||||
|
agent: "pm"
|
||||||
|
command: "validate-prd"
|
||||||
|
|
||||||
- id: "create-ux-design"
|
- id: "create-ux-design"
|
||||||
conditional: "if_has_ui"
|
conditional: "if_has_ui"
|
||||||
agent: "ux-designer"
|
agent: "ux-designer"
|
||||||
|
|
@ -93,7 +98,7 @@ phases:
|
||||||
required: true
|
required: true
|
||||||
agent: "architect"
|
agent: "architect"
|
||||||
command: "implementation-readiness"
|
command: "implementation-readiness"
|
||||||
note: "Validates PRD + Architecture + Epics + UX (optional)"
|
note: "Validates PRD + UX + Architecture + Epics cohesion before implementation"
|
||||||
|
|
||||||
- phase: 3
|
- phase: 3
|
||||||
name: "Implementation"
|
name: "Implementation"
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,12 @@ phases:
|
||||||
command: "prd"
|
command: "prd"
|
||||||
output: "Product Requirements Document with FRs and NFRs"
|
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"
|
- id: "create-ux-design"
|
||||||
conditional: "if_has_ui"
|
conditional: "if_has_ui"
|
||||||
agent: "ux-designer"
|
agent: "ux-designer"
|
||||||
|
|
@ -83,7 +89,7 @@ phases:
|
||||||
required: true
|
required: true
|
||||||
agent: "architect"
|
agent: "architect"
|
||||||
command: "implementation-readiness"
|
command: "implementation-readiness"
|
||||||
note: "Validates PRD + Architecture + Epics + UX (optional)"
|
note: "Validates PRD + UX + Architecture + Epics + Testability cohesion before implementation"
|
||||||
|
|
||||||
- phase: 3
|
- phase: 3
|
||||||
name: "Implementation"
|
name: "Implementation"
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,6 @@ class Installer {
|
||||||
this.configCollector = new ConfigCollector();
|
this.configCollector = new ConfigCollector();
|
||||||
this.ideConfigManager = new IdeConfigManager();
|
this.ideConfigManager = new IdeConfigManager();
|
||||||
this.installedFiles = []; // Track all installed files
|
this.installedFiles = []; // Track all installed files
|
||||||
this.ttsInjectedFiles = []; // Track files with TTS injection applied
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -147,8 +146,8 @@ class Installer {
|
||||||
content = content.replaceAll('{*bmad_folder*}', '{bmad_folder}');
|
content = content.replaceAll('{*bmad_folder*}', '{bmad_folder}');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process AgentVibes injection points (pass targetPath for tracking)
|
// Process AgentVibes injection points
|
||||||
content = this.processTTSInjectionPoints(content, targetPath);
|
content = this.processTTSInjectionPoints(content);
|
||||||
|
|
||||||
// Write to target with replaced content
|
// Write to target with replaced content
|
||||||
await fs.ensureDir(path.dirname(targetPath));
|
await fs.ensureDir(path.dirname(targetPath));
|
||||||
|
|
@ -227,14 +226,10 @@ class Installer {
|
||||||
* - src/modules/bmm/agents/*.md (rules sections)
|
* - src/modules/bmm/agents/*.md (rules sections)
|
||||||
* - TTS Hook: .claude/hooks/bmad-speak.sh (in AgentVibes repo)
|
* - TTS Hook: .claude/hooks/bmad-speak.sh (in AgentVibes repo)
|
||||||
*/
|
*/
|
||||||
processTTSInjectionPoints(content, targetPath = null) {
|
processTTSInjectionPoints(content) {
|
||||||
// Check if AgentVibes is enabled (set during installation configuration)
|
// Check if AgentVibes is enabled (set during installation configuration)
|
||||||
const enableAgentVibes = this.enableAgentVibes || false;
|
const enableAgentVibes = this.enableAgentVibes || false;
|
||||||
|
|
||||||
// Check if content contains any TTS injection markers
|
|
||||||
const hasPartyMode = content.includes('<!-- TTS_INJECTION:party-mode -->');
|
|
||||||
const hasAgentTTS = content.includes('<!-- TTS_INJECTION:agent-tts -->');
|
|
||||||
|
|
||||||
if (enableAgentVibes) {
|
if (enableAgentVibes) {
|
||||||
// Replace party-mode injection marker with actual TTS call
|
// Replace party-mode injection marker with actual TTS call
|
||||||
// Use single quotes to prevent shell expansion of special chars like !
|
// Use single quotes to prevent shell expansion of special chars like !
|
||||||
|
|
@ -258,12 +253,6 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
||||||
IMPORTANT: Use single quotes as shown - do NOT escape special characters like ! or $ inside single quotes
|
IMPORTANT: Use single quotes as shown - do NOT escape special characters like ! or $ inside single quotes
|
||||||
Run in background (&) to avoid blocking`,
|
Run in background (&) to avoid blocking`,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Track files that had TTS injection applied
|
|
||||||
if (targetPath && (hasPartyMode || hasAgentTTS)) {
|
|
||||||
const injectionType = hasPartyMode ? 'party-mode' : 'agent-tts';
|
|
||||||
this.ttsInjectedFiles.push({ path: targetPath, type: injectionType });
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// Strip injection markers cleanly when AgentVibes is disabled
|
// Strip injection markers cleanly when AgentVibes is disabled
|
||||||
content = content.replaceAll(/<!-- TTS_INJECTION:party-mode -->\n?/g, '');
|
content = content.replaceAll(/<!-- TTS_INJECTION:party-mode -->\n?/g, '');
|
||||||
|
|
@ -1032,8 +1021,6 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
||||||
modules: config.modules,
|
modules: config.modules,
|
||||||
ides: config.ides,
|
ides: config.ides,
|
||||||
customFiles: customFiles.length > 0 ? customFiles : undefined,
|
customFiles: customFiles.length > 0 ? customFiles : undefined,
|
||||||
ttsInjectedFiles: this.enableAgentVibes && this.ttsInjectedFiles.length > 0 ? this.ttsInjectedFiles : undefined,
|
|
||||||
agentVibesEnabled: this.enableAgentVibes || false,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Offer cleanup for legacy files (only for updates, not fresh installs, and only if not skipped)
|
// Offer cleanup for legacy files (only for updates, not fresh installs, and only if not skipped)
|
||||||
|
|
@ -1539,16 +1526,13 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
||||||
|
|
||||||
// Build YAML + customize to .md
|
// Build YAML + customize to .md
|
||||||
const customizeExists = await fs.pathExists(customizePath);
|
const customizeExists = await fs.pathExists(customizePath);
|
||||||
let xmlContent = await this.xmlHandler.buildFromYaml(yamlPath, customizeExists ? customizePath : null, {
|
const xmlContent = await this.xmlHandler.buildFromYaml(yamlPath, customizeExists ? customizePath : null, {
|
||||||
includeMetadata: true,
|
includeMetadata: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
// DO NOT replace {project-root} - LLMs understand this placeholder at runtime
|
// DO NOT replace {project-root} - LLMs understand this placeholder at runtime
|
||||||
// const processedContent = xmlContent.replaceAll('{project-root}', projectDir);
|
// const processedContent = xmlContent.replaceAll('{project-root}', projectDir);
|
||||||
|
|
||||||
// Process TTS injection points (pass targetPath for tracking)
|
|
||||||
xmlContent = this.processTTSInjectionPoints(xmlContent, mdPath);
|
|
||||||
|
|
||||||
// Write the built .md file to bmad/{module}/agents/ with POSIX-compliant final newline
|
// Write the built .md file to bmad/{module}/agents/ with POSIX-compliant final newline
|
||||||
const content = xmlContent.endsWith('\n') ? xmlContent : xmlContent + '\n';
|
const content = xmlContent.endsWith('\n') ? xmlContent : xmlContent + '\n';
|
||||||
await fs.writeFile(mdPath, content, 'utf8');
|
await fs.writeFile(mdPath, content, 'utf8');
|
||||||
|
|
@ -1644,16 +1628,13 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build YAML to XML .md
|
// Build YAML to XML .md
|
||||||
let xmlContent = await this.xmlHandler.buildFromYaml(sourceYamlPath, customizeExists ? customizePath : null, {
|
const xmlContent = await this.xmlHandler.buildFromYaml(sourceYamlPath, customizeExists ? customizePath : null, {
|
||||||
includeMetadata: true,
|
includeMetadata: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
// DO NOT replace {project-root} - LLMs understand this placeholder at runtime
|
// DO NOT replace {project-root} - LLMs understand this placeholder at runtime
|
||||||
// const processedContent = xmlContent.replaceAll('{project-root}', projectDir);
|
// const processedContent = xmlContent.replaceAll('{project-root}', projectDir);
|
||||||
|
|
||||||
// Process TTS injection points (pass targetPath for tracking)
|
|
||||||
xmlContent = this.processTTSInjectionPoints(xmlContent, targetMdPath);
|
|
||||||
|
|
||||||
// Write the built .md file with POSIX-compliant final newline
|
// Write the built .md file with POSIX-compliant final newline
|
||||||
const content = xmlContent.endsWith('\n') ? xmlContent : xmlContent + '\n';
|
const content = xmlContent.endsWith('\n') ? xmlContent : xmlContent + '\n';
|
||||||
await fs.writeFile(targetMdPath, content, 'utf8');
|
await fs.writeFile(targetMdPath, content, 'utf8');
|
||||||
|
|
@ -1741,16 +1722,13 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build YAML + customize to .md
|
// Build YAML + customize to .md
|
||||||
let xmlContent = await this.xmlHandler.buildFromYaml(sourceYamlPath, customizeExists ? customizePath : null, {
|
const xmlContent = await this.xmlHandler.buildFromYaml(sourceYamlPath, customizeExists ? customizePath : null, {
|
||||||
includeMetadata: true,
|
includeMetadata: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
// DO NOT replace {project-root} - LLMs understand this placeholder at runtime
|
// DO NOT replace {project-root} - LLMs understand this placeholder at runtime
|
||||||
// const processedContent = xmlContent.replaceAll('{project-root}', projectDir);
|
// const processedContent = xmlContent.replaceAll('{project-root}', projectDir);
|
||||||
|
|
||||||
// Process TTS injection points (pass targetPath for tracking)
|
|
||||||
xmlContent = this.processTTSInjectionPoints(xmlContent, targetMdPath);
|
|
||||||
|
|
||||||
// Write the rebuilt .md file with POSIX-compliant final newline
|
// Write the rebuilt .md file with POSIX-compliant final newline
|
||||||
const content = xmlContent.endsWith('\n') ? xmlContent : xmlContent + '\n';
|
const content = xmlContent.endsWith('\n') ? xmlContent : xmlContent + '\n';
|
||||||
await fs.writeFile(targetMdPath, content, 'utf8');
|
await fs.writeFile(targetMdPath, content, 'utf8');
|
||||||
|
|
|
||||||
|
|
@ -482,39 +482,10 @@ function compileAgent(yamlContent, answers = {}, agentName = '', targetPath = ''
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Process TTS injection markers in content
|
|
||||||
* @param {string} content - Content to process
|
|
||||||
* @param {boolean} enableAgentVibes - Whether AgentVibes is enabled
|
|
||||||
* @returns {Object} { content: string, hadInjection: boolean }
|
|
||||||
*/
|
|
||||||
function processTTSInjectionPoints(content, enableAgentVibes) {
|
|
||||||
const hasAgentTTS = content.includes('<!-- TTS_INJECTION:agent-tts -->');
|
|
||||||
|
|
||||||
if (enableAgentVibes && hasAgentTTS) {
|
|
||||||
// Replace agent-tts injection marker with TTS rule
|
|
||||||
content = content.replaceAll(
|
|
||||||
'<!-- TTS_INJECTION:agent-tts -->',
|
|
||||||
`- When responding to user messages, speak your responses using TTS:
|
|
||||||
Call: \`.claude/hooks/bmad-speak.sh '{agent-id}' '{response-text}'\` after each response
|
|
||||||
Replace {agent-id} with YOUR agent ID from <agent id="..."> tag at top of this file
|
|
||||||
Replace {response-text} with the text you just output to the user
|
|
||||||
IMPORTANT: Use single quotes as shown - do NOT escape special characters like ! or $ inside single quotes
|
|
||||||
Run in background (&) to avoid blocking`,
|
|
||||||
);
|
|
||||||
return { content, hadInjection: true };
|
|
||||||
} else if (!enableAgentVibes && hasAgentTTS) {
|
|
||||||
// Strip injection markers when disabled
|
|
||||||
content = content.replaceAll(/<!-- TTS_INJECTION:agent-tts -->\n?/g, '');
|
|
||||||
}
|
|
||||||
|
|
||||||
return { content, hadInjection: false };
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compile agent file to .md
|
* Compile agent file to .md
|
||||||
* @param {string} yamlPath - Path to agent YAML file
|
* @param {string} yamlPath - Path to agent YAML file
|
||||||
* @param {Object} options - { answers: {}, outputPath: string, enableAgentVibes: boolean }
|
* @param {Object} options - { answers: {}, outputPath: string }
|
||||||
* @returns {Object} Compilation result
|
* @returns {Object} Compilation result
|
||||||
*/
|
*/
|
||||||
function compileAgentFile(yamlPath, options = {}) {
|
function compileAgentFile(yamlPath, options = {}) {
|
||||||
|
|
@ -530,24 +501,13 @@ function compileAgentFile(yamlPath, options = {}) {
|
||||||
outputPath = path.join(dir, `${basename}.md`);
|
outputPath = path.join(dir, `${basename}.md`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process TTS injection points if enableAgentVibes option is provided
|
|
||||||
let xml = result.xml;
|
|
||||||
let ttsInjected = false;
|
|
||||||
if (options.enableAgentVibes !== undefined) {
|
|
||||||
const ttsResult = processTTSInjectionPoints(xml, options.enableAgentVibes);
|
|
||||||
xml = ttsResult.content;
|
|
||||||
ttsInjected = ttsResult.hadInjection;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write compiled XML
|
// Write compiled XML
|
||||||
fs.writeFileSync(outputPath, xml, 'utf8');
|
fs.writeFileSync(outputPath, result.xml, 'utf8');
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...result,
|
...result,
|
||||||
xml,
|
|
||||||
outputPath,
|
outputPath,
|
||||||
sourcePath: yamlPath,
|
sourcePath: yamlPath,
|
||||||
ttsInjected,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -363,60 +363,11 @@ class UI {
|
||||||
`🔧 Tools Configured: ${result.ides?.length > 0 ? result.ides.join(', ') : 'none'}`,
|
`🔧 Tools Configured: ${result.ides?.length > 0 ? result.ides.join(', ') : 'none'}`,
|
||||||
];
|
];
|
||||||
|
|
||||||
// Add AgentVibes TTS info if enabled
|
|
||||||
if (result.agentVibesEnabled) {
|
|
||||||
summary.push(`🎤 AgentVibes TTS: Enabled`);
|
|
||||||
}
|
|
||||||
|
|
||||||
CLIUtils.displayBox(summary.join('\n\n'), {
|
CLIUtils.displayBox(summary.join('\n\n'), {
|
||||||
borderColor: 'green',
|
borderColor: 'green',
|
||||||
borderStyle: 'round',
|
borderStyle: 'round',
|
||||||
});
|
});
|
||||||
|
|
||||||
// Display TTS injection details if present
|
|
||||||
if (result.ttsInjectedFiles && result.ttsInjectedFiles.length > 0) {
|
|
||||||
console.log('\n' + chalk.cyan.bold('═══════════════════════════════════════════════════'));
|
|
||||||
console.log(chalk.cyan.bold(' AgentVibes TTS Injection Summary'));
|
|
||||||
console.log(chalk.cyan.bold('═══════════════════════════════════════════════════\n'));
|
|
||||||
|
|
||||||
// Explain what TTS injection is
|
|
||||||
console.log(chalk.white.bold('What is TTS Injection?\n'));
|
|
||||||
console.log(chalk.dim(' TTS (Text-to-Speech) injection adds voice instructions to BMAD agents,'));
|
|
||||||
console.log(chalk.dim(' enabling them to speak their responses aloud using AgentVibes.\n'));
|
|
||||||
console.log(chalk.dim(' Example: When you activate the PM agent, it will greet you with'));
|
|
||||||
console.log(chalk.dim(' spoken audio like "Hey! I\'m your Project Manager. How can I help?"\n'));
|
|
||||||
|
|
||||||
console.log(chalk.green(`✅ TTS injection applied to ${result.ttsInjectedFiles.length} file(s):\n`));
|
|
||||||
|
|
||||||
// Group by type
|
|
||||||
const partyModeFiles = result.ttsInjectedFiles.filter((f) => f.type === 'party-mode');
|
|
||||||
const agentTTSFiles = result.ttsInjectedFiles.filter((f) => f.type === 'agent-tts');
|
|
||||||
|
|
||||||
if (partyModeFiles.length > 0) {
|
|
||||||
console.log(chalk.yellow(' Party Mode (multi-agent conversations):'));
|
|
||||||
for (const file of partyModeFiles) {
|
|
||||||
console.log(chalk.dim(` • ${file.path}`));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (agentTTSFiles.length > 0) {
|
|
||||||
console.log(chalk.yellow(' Agent TTS (individual agent voices):'));
|
|
||||||
for (const file of agentTTSFiles) {
|
|
||||||
console.log(chalk.dim(` • ${file.path}`));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show backup info and restore command
|
|
||||||
console.log('\n' + chalk.white.bold('Backups & Recovery:\n'));
|
|
||||||
console.log(chalk.dim(' Pre-injection backups are stored in:'));
|
|
||||||
console.log(chalk.cyan(' ~/.bmad-tts-backups/\n'));
|
|
||||||
console.log(chalk.dim(' To restore original files (removes TTS instructions):'));
|
|
||||||
console.log(chalk.cyan(` bmad-tts-injector.sh --restore ${result.path}\n`));
|
|
||||||
|
|
||||||
console.log(chalk.cyan('💡 BMAD agents will now speak when activated!'));
|
|
||||||
console.log(chalk.dim(' Ensure AgentVibes is installed: https://agentvibes.org'));
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('\n' + chalk.green.bold('✨ BMAD is ready to use!'));
|
console.log('\n' + chalk.green.bold('✨ BMAD is ready to use!'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,356 +0,0 @@
|
||||||
#!/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…
Reference in New Issue