Compare commits
9 Commits
e75f57e73d
...
954c6010dd
| Author | SHA1 | Date |
|---|---|---|
|
|
954c6010dd | |
|
|
993d02b8b3 | |
|
|
5cb5606ba3 | |
|
|
49d284179a | |
|
|
594d9854eb | |
|
|
9565bef286 | |
|
|
54ab3f13d3 | |
|
|
c3cf0c1fc6 | |
|
|
5247468d98 |
|
|
@ -79,3 +79,6 @@ bmad-custom-src/
|
|||
website/.astro/
|
||||
website/dist/
|
||||
build/
|
||||
|
||||
# Fork-specific documentation (not committed)
|
||||
BUG-TRACKING.md
|
||||
|
|
|
|||
|
|
@ -0,0 +1,85 @@
|
|||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
We release security patches for the following versions:
|
||||
|
||||
| Version | Supported |
|
||||
| ------- | ------------------ |
|
||||
| Latest | :white_check_mark: |
|
||||
| < Latest | :x: |
|
||||
|
||||
We recommend always using the latest version of BMad Method to ensure you have the most recent security updates.
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
We take security vulnerabilities seriously. If you discover a security issue, please report it responsibly.
|
||||
|
||||
### How to Report
|
||||
|
||||
**Do NOT report security vulnerabilities through public GitHub issues.**
|
||||
|
||||
Instead, please report them via one of these methods:
|
||||
|
||||
1. **GitHub Security Advisories** (Preferred): Use [GitHub's private vulnerability reporting](https://github.com/bmad-code-org/BMAD-METHOD/security/advisories/new) to submit a confidential report.
|
||||
|
||||
2. **Discord**: Contact a maintainer directly via DM on our [Discord server](https://discord.gg/gk8jAdXWmj).
|
||||
|
||||
### What to Include
|
||||
|
||||
Please include as much of the following information as possible:
|
||||
|
||||
- Type of vulnerability (e.g., prompt injection, path traversal, etc.)
|
||||
- Full paths of source file(s) related to the vulnerability
|
||||
- Step-by-step instructions to reproduce the issue
|
||||
- Proof-of-concept or exploit code (if available)
|
||||
- Impact assessment of the vulnerability
|
||||
|
||||
### Response Timeline
|
||||
|
||||
- **Initial Response**: Within 48 hours of receiving your report
|
||||
- **Status Update**: Within 7 days with our assessment
|
||||
- **Resolution Target**: Critical issues within 30 days; other issues within 90 days
|
||||
|
||||
### What to Expect
|
||||
|
||||
1. We will acknowledge receipt of your report
|
||||
2. We will investigate and validate the vulnerability
|
||||
3. We will work on a fix and coordinate disclosure timing with you
|
||||
4. We will credit you in the security advisory (unless you prefer to remain anonymous)
|
||||
|
||||
## Security Scope
|
||||
|
||||
### In Scope
|
||||
|
||||
- Vulnerabilities in BMad Method core framework code
|
||||
- Security issues in agent definitions or workflows that could lead to unintended behavior
|
||||
- Path traversal or file system access issues
|
||||
- Prompt injection vulnerabilities that bypass intended agent behavior
|
||||
- Supply chain vulnerabilities in dependencies
|
||||
|
||||
### Out of Scope
|
||||
|
||||
- Security issues in user-created custom agents or modules
|
||||
- Vulnerabilities in third-party AI providers (Claude, GPT, etc.)
|
||||
- Issues that require physical access to a user's machine
|
||||
- Social engineering attacks
|
||||
- Denial of service attacks that don't exploit a specific vulnerability
|
||||
|
||||
## Security Best Practices for Users
|
||||
|
||||
When using BMad Method:
|
||||
|
||||
1. **Review Agent Outputs**: Always review AI-generated code before executing it
|
||||
2. **Limit File Access**: Configure your AI IDE to limit file system access where possible
|
||||
3. **Keep Updated**: Regularly update to the latest version
|
||||
4. **Validate Dependencies**: Review any dependencies added by generated code
|
||||
5. **Environment Isolation**: Consider running AI-assisted development in isolated environments
|
||||
|
||||
## Acknowledgments
|
||||
|
||||
We appreciate the security research community's efforts in helping keep BMad Method secure. Contributors who report valid security issues will be acknowledged in our security advisories.
|
||||
|
||||
---
|
||||
|
||||
Thank you for helping keep BMad Method and our community safe.
|
||||
|
|
@ -0,0 +1,536 @@
|
|||
# Bug Tracking Workflow Wireframe
|
||||
|
||||
## Quick Reference
|
||||
|
||||
```
|
||||
COMMANDS:
|
||||
/triage - Triage new bugs from bugs.md
|
||||
/implement bug-NNN - Implement a bug fix
|
||||
/implement feature-N - Implement a feature
|
||||
/verify - List pending verification
|
||||
/verify bug-NNN - Verify and close specific bug
|
||||
/verify all - Batch verify all
|
||||
|
||||
FILES:
|
||||
docs/bugs.md - Human-readable bug tracking
|
||||
docs/bugs.yaml - Agent-readable metadata
|
||||
|
||||
SEVERITY → COMPLEXITY → WORKFLOW ROUTING:
|
||||
┌──────────┬─────────┬─────────┬─────────┬─────────┐
|
||||
│ │ TRIVIAL │ SMALL │ MEDIUM │ COMPLEX │
|
||||
├──────────┼─────────┼─────────┼─────────┼─────────┤
|
||||
│ CRITICAL │ correct-course (any complexity) │
|
||||
├──────────┼─────────┼─────────┼─────────┼─────────┤
|
||||
│ HIGH │direct-fx│tech-spec│corr-crs │corr-crs │
|
||||
├──────────┼─────────┼─────────┼─────────┼─────────┤
|
||||
│ MEDIUM │direct-fx│tech-spec│corr-crs │corr-crs │
|
||||
├──────────┼─────────┼─────────┼─────────┼─────────┤
|
||||
│ LOW │direct-fx│ backlog │ backlog │ backlog │
|
||||
└──────────┴─────────┴─────────┴─────────┴─────────┘
|
||||
|
||||
SEVERITY:
|
||||
critical - Core broken, crashes, data loss
|
||||
high - Major feature blocked, workaround exists
|
||||
medium - Partial breakage, minor impact
|
||||
low - Cosmetic, edge case
|
||||
|
||||
COMPLEXITY:
|
||||
trivial - One-liner, minimal change
|
||||
small - Single file change
|
||||
medium - Multi-file change
|
||||
complex - Architectural change
|
||||
|
||||
STATUS FLOW:
|
||||
reported → triaged → [routed] → in-progress → fixed/implemented → verified → closed
|
||||
|
||||
STATUS VALUES:
|
||||
triaged - Analyzed, routed, awaiting implementation
|
||||
routed - Sent to tech-spec or correct-course workflow
|
||||
in-progress - Developer actively working
|
||||
fixed - Code complete, awaiting verification (bugs)
|
||||
implemented - Code complete, awaiting verification (features)
|
||||
closed - Verified and closed
|
||||
backlog - Deferred to future sprint
|
||||
blocked - Cannot proceed until issue resolved
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Part 1: System Architecture
|
||||
|
||||
### System Overview
|
||||
|
||||
```
|
||||
INPUT SOURCES
|
||||
+-------------------+ +-------------------+ +-------------------+
|
||||
| IN-APP MODAL | | MANUAL ENTRY | | EXTERNAL ISSUE |
|
||||
| (Optional API) | | (bugs.md) | | TRACKER IMPORT |
|
||||
+--------+----------+ +--------+----------+ +--------+----------+
|
||||
| | |
|
||||
+------------+------------+-------------------------+
|
||||
|
|
||||
v
|
||||
+============================+
|
||||
| /triage (WORKFLOW) |
|
||||
+============================+
|
||||
|
|
||||
+---------------+---------------+---------------+
|
||||
| | | |
|
||||
v v v v
|
||||
direct-fix tech-spec correct-course backlog
|
||||
| | | |
|
||||
v v v v
|
||||
/implement /tech-spec /correct-course (deferred)
|
||||
| | |
|
||||
+---------------+---------------+
|
||||
|
|
||||
v
|
||||
/verify → CLOSED
|
||||
```
|
||||
|
||||
### File Architecture
|
||||
|
||||
```
|
||||
{project-root}/
|
||||
docs/
|
||||
bugs.md <-- User-facing: informal bug reports & tracking
|
||||
bugs.yaml <-- Agent-facing: structured metadata database
|
||||
epics.md <-- Context: for mapping bugs to stories
|
||||
_bmad/bmm/
|
||||
config.yaml <-- Project configuration
|
||||
workflows/
|
||||
bug-tracking/ <-- Triage workflow files
|
||||
implement/ <-- Implementation workflow
|
||||
verify/ <-- Verification workflow
|
||||
```
|
||||
|
||||
### bugs.md Structure
|
||||
|
||||
```markdown
|
||||
# Bug Tracking - {project_name}
|
||||
|
||||
# manual input
|
||||
## Bug: Login fails on iOS Safari
|
||||
Description of the bug...
|
||||
Reported by: User Name
|
||||
Date: 2025-01-15
|
||||
|
||||
- **Crash on startup (Android)**: App crashes immediately. CRITICAL.
|
||||
|
||||
1. Form validation missing - No validation on email field
|
||||
|
||||
---
|
||||
|
||||
# Tracked Bugs
|
||||
### bug-001: Login fails on iOS Safari
|
||||
Brief description...
|
||||
- **Severity:** high
|
||||
- **Complexity:** small
|
||||
- **Workflow:** tech-spec
|
||||
- **Related:** story-2-3
|
||||
**Notes:** Triage reasoning...
|
||||
|
||||
---
|
||||
|
||||
# Tracked Feature Requests
|
||||
### feature-001: Dark mode toggle
|
||||
Brief description...
|
||||
- **Priority:** medium
|
||||
- **Complexity:** medium
|
||||
- **Workflow:** tech-spec
|
||||
|
||||
---
|
||||
|
||||
# Fixed Bugs
|
||||
[IMPLEMENTED] bug-003: Header alignment [Sev: low, Fixed: 2025-01-18, Verified: pending]
|
||||
- Fix: Adjusted flexbox styling
|
||||
- File(s): src/components/Header.tsx
|
||||
|
||||
bug-002: Form submission error [Sev: medium, Fixed: 2025-01-15, Verified: 2025-01-16, CLOSED]
|
||||
- Fix: Added error boundary
|
||||
|
||||
---
|
||||
|
||||
# Implemented Features
|
||||
[IMPLEMENTED] feature-002: Export to CSV [Impl: 2025-01-20, Verified: pending]
|
||||
- Files: src/export.ts, src/utils/csv.ts
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Part 2: Workflow Operations
|
||||
|
||||
### Slash Command Reference
|
||||
|
||||
| Command | Description | When to Use |
|
||||
|---------|-------------|-------------|
|
||||
| `/triage` | Main workflow - triage user-reported bugs | When new bugs are in bugs.md |
|
||||
| `/implement bug-NNN` | Implement a specific bug fix | After triage, routed for direct-fix |
|
||||
| `/implement feature-NNN` | Implement a feature request | After feature is triaged |
|
||||
| `/verify` | List all pending verification | After implementation, before closing |
|
||||
| `/verify bug-NNN` | Verify and close specific bug | After testing confirms fix works |
|
||||
| `/verify all` | Batch verify all pending items | Bulk close multiple fixes |
|
||||
|
||||
### /triage Workflow
|
||||
|
||||
```
|
||||
USER INVOKES: /triage
|
||||
|
|
||||
v
|
||||
+---------------------------+
|
||||
| STEP 1: INITIALIZATION |
|
||||
+---------------------------+
|
||||
| - Load config.yaml |
|
||||
| - Check for bugs.yaml |
|
||||
| - Detect existing session |
|
||||
+------------+--------------+
|
||||
|
|
||||
+--------+--------+
|
||||
| |
|
||||
v v
|
||||
+-------------+ +-------------+
|
||||
| Has pending | | Fresh start |
|
||||
| triaged work| +------+------+
|
||||
+------+------+ |
|
||||
| v
|
||||
v +-------------+
|
||||
+-------------+ | Scan manual |
|
||||
| Show status | | input section|
|
||||
| [T/I/V/L/Q] | +------+------+
|
||||
+-------------+ |
|
||||
v
|
||||
+-------------+
|
||||
| [S/C/Q] |
|
||||
| Sync/Cont/Q |
|
||||
+------+------+
|
||||
|
|
||||
+---------------+---------------+
|
||||
v v v
|
||||
[S] API Sync [C] Continue [Q] Quit
|
||||
|
||||
+---------------------------+
|
||||
| STEP 2: API SYNC | (Optional - if [S] selected)
|
||||
+---------------------------+
|
||||
| GET /api/bug-reports/pending
|
||||
| - Fetch, format, insert to bugs.md
|
||||
| - POST /mark-synced
|
||||
+---------------------------+
|
||||
|
||||
+---------------------------+
|
||||
| STEP 3: PARSE |
|
||||
+---------------------------+
|
||||
| Read "# manual input" only
|
||||
| - Parse headers, bullets, numbered lists
|
||||
| - Extract: title, desc, reporter, platform
|
||||
| - Compare with bugs.yaml (NEW vs EXISTING)
|
||||
+------------+--------------+
|
||||
|
|
||||
+--------+--------+
|
||||
v v
|
||||
No new bugs {N} new bugs
|
||||
[HALT] [C] Continue
|
||||
|
|
||||
v
|
||||
+---------------------------+
|
||||
| STEP 4: TRIAGE (per bug) |
|
||||
+---------------------------+
|
||||
| FOR EACH NEW BUG:
|
||||
| 1. Generate bug-NNN ID
|
||||
| 2. Assess SEVERITY (critical|high|med|low)
|
||||
| 3. Assess COMPLEXITY (trivial|small|med|complex)
|
||||
| 4. Apply ROUTING MATRIX → workflow
|
||||
| 5. Map to story/epic if applicable
|
||||
| 6. Assess DOC IMPACT (prd|architecture|ux)
|
||||
| 7. Add triage notes
|
||||
| 8. Present: [A]ccept/[M]odify/[S]kip/[N]ext
|
||||
+---------------------------+
|
||||
|
|
||||
v (after all bugs)
|
||||
+---------------------------+
|
||||
| STEP 5: UPDATE FILES |
|
||||
+---------------------------+
|
||||
| bugs.yaml: Add entries, update stats
|
||||
| bugs.md: Remove from manual input,
|
||||
| Add to Tracked Bugs/Features
|
||||
+---------------------------+
|
||||
|
|
||||
v
|
||||
+---------------------------+
|
||||
| STEP 6: COMPLETE |
|
||||
+---------------------------+
|
||||
| Show summary + next steps:
|
||||
| /implement bug-NNN
|
||||
| /verify bug-NNN
|
||||
+---------------------------+
|
||||
```
|
||||
|
||||
### /implement Workflow
|
||||
|
||||
```
|
||||
USER INVOKES: /implement bug-NNN
|
||||
|
|
||||
v
|
||||
+-------------------------------+
|
||||
| STEP 1-2: Load Context |
|
||||
+-------------------------------+
|
||||
| - Parse ID (bug-NNN/feature-NNN)
|
||||
| - Load from bugs.yaml
|
||||
| - Check status (halt if backlog/blocked/deferred)
|
||||
+---------------+---------------+
|
||||
|
|
||||
v
|
||||
+-------------------------------+
|
||||
| STEP 3: Check Workflow Route |
|
||||
+-------------------------------+
|
||||
|
|
||||
+-----------+-----------+-----------+
|
||||
v v v v
|
||||
correct- tech-spec direct-fix ambiguous
|
||||
course |
|
||||
| | | Apply Matrix
|
||||
v v |
|
||||
[ROUTES TO [ROUTES TO |
|
||||
/correct- /tech-spec |
|
||||
course] workflow] |
|
||||
| | |
|
||||
v v v
|
||||
Creates Creates +--------+
|
||||
story spec | STEP 4:|
|
||||
| Confirm|
|
||||
+---+----+
|
||||
|
|
||||
v
|
||||
+---------------+
|
||||
| STEP 5: |
|
||||
| IMPLEMENT |
|
||||
+---------------+
|
||||
| Dev Agent: |
|
||||
| - Read files |
|
||||
| - Make changes|
|
||||
| - Minimal fix |
|
||||
+-------+-------+
|
||||
|
|
||||
v
|
||||
+---------------+
|
||||
| STEP 6: Check |
|
||||
| npm run check |
|
||||
+-------+-------+
|
||||
|
|
||||
v
|
||||
+---------------+
|
||||
| STEP 7-8: |
|
||||
| Update Files |
|
||||
+---------------+
|
||||
| bugs.yaml: |
|
||||
| status: fixed|
|
||||
| bugs.md: |
|
||||
| [IMPLEMENTED]|
|
||||
+-------+-------+
|
||||
|
|
||||
v
|
||||
+---------------+
|
||||
| STEP 9: |
|
||||
| "Run /verify" |
|
||||
+---------------+
|
||||
```
|
||||
|
||||
### /verify Workflow
|
||||
|
||||
```
|
||||
USER INVOKES: /verify [bug-NNN]
|
||||
|
|
||||
+-----------+-----------+
|
||||
v v
|
||||
+---------------+ +---------------+
|
||||
| No ID given | | ID provided |
|
||||
+-------+-------+ +-------+-------+
|
||||
| |
|
||||
v |
|
||||
+---------------+ |
|
||||
| List pending | |
|
||||
| [IMPLEMENTED] | |
|
||||
| items | |
|
||||
+-------+-------+ |
|
||||
| |
|
||||
+-------+---------------+
|
||||
|
|
||||
v
|
||||
+-------------------------------+
|
||||
| STEP 2: Load & Validate |
|
||||
+-------------------------------+
|
||||
| - Verify status: fixed/implemented
|
||||
| - Check file sync
|
||||
+---------------+---------------+
|
||||
|
|
||||
v
|
||||
+-------------------------------+
|
||||
| STEP 3: Confirm Verification |
|
||||
+-------------------------------+
|
||||
| Show: Title, type, date, files
|
||||
| "Has this been tested?"
|
||||
| [yes | no | skip]
|
||||
+---------------+---------------+
|
||||
|
|
||||
+-----------+-----------+
|
||||
v v v
|
||||
+-------+ +-------+ +-------+
|
||||
| YES | | NO | | SKIP |
|
||||
+---+---+ +---+---+ +---+---+
|
||||
| | |
|
||||
v v v
|
||||
Step 4 Add note Next item
|
||||
"rework"
|
||||
|
||||
+-------------------------------+
|
||||
| STEP 4-5: Update Files |
|
||||
+-------------------------------+
|
||||
| bugs.yaml: status: closed,
|
||||
| verified_date
|
||||
| bugs.md: Remove [IMPLEMENTED],
|
||||
| Add CLOSED tag
|
||||
+-------------------------------+
|
||||
|
|
||||
v
|
||||
+-------------------------------+
|
||||
| STEP 6: Summary |
|
||||
| "bug-NNN VERIFIED and CLOSED" |
|
||||
+-------------------------------+
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Part 3: Routing & Agent Delegation
|
||||
|
||||
### Workflow Routing by Type
|
||||
|
||||
| Workflow | Trigger Conditions | Pre-Implement Phase | Implementation Phase |
|
||||
|----------|-------------------|---------------------|---------------------|
|
||||
| **direct-fix** | high/med + trivial | None | Dev Agent in /implement Step 5 |
|
||||
| **tech-spec** | high/med + small | Architect creates spec | /dev-story per spec |
|
||||
| **correct-course** | critical (any) OR med/complex+ OR doc_impact | PM→Architect→SM create story | /dev-story per story |
|
||||
| **backlog** | low + small+ | None (deferred) | Awaits sprint promotion |
|
||||
|
||||
### Agent Responsibilities
|
||||
|
||||
```
|
||||
/triage
|
||||
|
|
||||
v
|
||||
+------------------------+
|
||||
| SM AGENT (Scrum |
|
||||
| Master Facilitator) |
|
||||
+------------------------+
|
||||
| - Runs triage workflow |
|
||||
| - Assesses severity |
|
||||
| - Routes to workflows |
|
||||
+-----------+------------+
|
||||
|
|
||||
+-------------------+-------------------+
|
||||
v v v
|
||||
+------------+ +------------+ +------------+
|
||||
| Direct-Fix | | Tech-Spec | | Correct- |
|
||||
+-----+------+ +-----+------+ | Course |
|
||||
| | +-----+------+
|
||||
v v |
|
||||
+------------+ +------------+ v
|
||||
| DEV AGENT | | ARCHITECT | +------------+
|
||||
| /implement | | /tech-spec | | PM AGENT |
|
||||
| Step 5 | +-----+------+ | + ARCHITECT|
|
||||
+------------+ | | + SM |
|
||||
v +-----+------+
|
||||
+------------+ |
|
||||
| DEV AGENT | v
|
||||
| /dev-story | +------------+
|
||||
+------------+ | DEV AGENT |
|
||||
| /dev-story |
|
||||
+------------+
|
||||
```
|
||||
|
||||
### Doc Impact Routing
|
||||
|
||||
When `doc_impact` flags are detected during /implement:
|
||||
|
||||
| Flag | Agent | Action |
|
||||
|------|-------|--------|
|
||||
| PRD | PM Agent | Update PRD.md |
|
||||
| Architecture | Architect Agent | Update architecture.md |
|
||||
| UX | UX Designer Agent | Update UX specs |
|
||||
|
||||
User is prompted: `[update-docs-first | proceed-anyway | cancel]`
|
||||
|
||||
---
|
||||
|
||||
## Part 4: State & Lifecycle
|
||||
|
||||
### File State Transitions
|
||||
|
||||
```
|
||||
═══════════════════════════════════════════════════════════════════════════════
|
||||
DIRECT-FIX TECH-SPEC CORRECT-COURSE BACKLOG
|
||||
═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
ENTRY # manual input # manual input # manual input # manual input
|
||||
(informal text) (informal text) (informal text) (informal text)
|
||||
│ │ │ │
|
||||
▼ ▼ ▼ ▼
|
||||
─────────────────────────────────────────────────────────────────────────────────
|
||||
TRIAGE # Tracked Bugs # Tracked Bugs # Tracked Bugs # Tracked Bugs
|
||||
bug-NNN bug-NNN bug-NNN bug-NNN
|
||||
wf: direct-fix wf: tech-spec wf: correct-crs wf: backlog
|
||||
│ │ │ │
|
||||
▼ ▼ ▼ │
|
||||
─────────────────────────────────────────────────────────────────────────────────
|
||||
ROUTE (skip) /tech-spec /correct-course (waiting)
|
||||
creates spec creates story │
|
||||
│ │ │ │
|
||||
▼ ▼ ▼ │
|
||||
─────────────────────────────────────────────────────────────────────────────────
|
||||
CODE /implement /dev-story /dev-story (waiting)
|
||||
Step 5 per spec per story │
|
||||
│ │ │ │
|
||||
▼ ▼ ▼ │
|
||||
─────────────────────────────────────────────────────────────────────────────────
|
||||
IMPL # Fixed Bugs # Fixed Bugs # Fixed Bugs (unchanged)
|
||||
[IMPLEMENTED] [IMPLEMENTED] [IMPLEMENTED] │
|
||||
bug-NNN bug-NNN bug-NNN │
|
||||
│ │ │ │
|
||||
▼ ▼ ▼ │
|
||||
─────────────────────────────────────────────────────────────────────────────────
|
||||
VERIFY /verify /verify /verify (waiting)
|
||||
bug-NNN bug-NNN bug-NNN │
|
||||
│ │ │ │
|
||||
▼ ▼ ▼ ▼
|
||||
─────────────────────────────────────────────────────────────────────────────────
|
||||
DONE CLOSED ✓ CLOSED ✓ CLOSED ✓ WAITING ◷
|
||||
═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
FILE STATE SUMMARY:
|
||||
┌──────────┬─────────────────────────────┬──────────────────────────────────┐
|
||||
│ STAGE │ bugs.md │ bugs.yaml │
|
||||
├──────────┼─────────────────────────────┼──────────────────────────────────┤
|
||||
│ Entry │ # manual input │ (no entry) │
|
||||
├──────────┼─────────────────────────────┼──────────────────────────────────┤
|
||||
│ Triage │ → # Tracked Bugs/Features │ status: triaged + metadata │
|
||||
├──────────┼─────────────────────────────┼──────────────────────────────────┤
|
||||
│ Implement│ → # Fixed [IMPLEMENTED] │ status: fixed/implemented │
|
||||
├──────────┼─────────────────────────────┼──────────────────────────────────┤
|
||||
│ Verify │ [IMPLEMENTED] → CLOSED │ status: closed + verified_date │
|
||||
└──────────┴─────────────────────────────┴──────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Appendix: Optional Extensions
|
||||
|
||||
### In-App Bug Reporting API
|
||||
|
||||
Optional integration for apps with built-in bug reporting UI:
|
||||
|
||||
1. **User submits** via in-app modal → `POST /api/bug-reports`
|
||||
2. **Database stores** with `status: 'new'`
|
||||
3. **During /triage Step 2** (if [S]ync selected):
|
||||
- `GET /api/bug-reports/pending` fetches new reports
|
||||
- Formats as markdown, inserts to `# manual input`
|
||||
- `POST /api/bug-reports/mark-synced` prevents re-fetch
|
||||
|
||||
This is optional - manual entry to bugs.md works without any API.
|
||||
|
|
@ -19,7 +19,6 @@
|
|||
"fs-extra": "^11.3.0",
|
||||
"glob": "^11.0.3",
|
||||
"ignore": "^7.0.5",
|
||||
"inquirer": "^9.3.8",
|
||||
"js-yaml": "^4.1.0",
|
||||
"ora": "^5.4.1",
|
||||
"semver": "^7.6.3",
|
||||
|
|
@ -34,6 +33,7 @@
|
|||
"devDependencies": {
|
||||
"@astrojs/sitemap": "^3.6.0",
|
||||
"@astrojs/starlight": "^0.37.0",
|
||||
"@clack/prompts": "^0.11.0",
|
||||
"@eslint/js": "^9.33.0",
|
||||
"archiver": "^7.0.1",
|
||||
"astro": "^5.16.0",
|
||||
|
|
@ -244,7 +244,6 @@
|
|||
"integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.27.1",
|
||||
"@babel/generator": "^7.28.5",
|
||||
|
|
@ -756,6 +755,29 @@
|
|||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@clack/core": {
|
||||
"version": "0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@clack/core/-/core-0.5.0.tgz",
|
||||
"integrity": "sha512-p3y0FIOwaYRUPRcMO7+dlmLh8PSRcrjuTndsiA0WAFbWES0mLZlrjVoBRZ9DzkPFJZG6KGkJmoEAY0ZcVWTkow==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"picocolors": "^1.0.0",
|
||||
"sisteransi": "^1.0.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@clack/prompts": {
|
||||
"version": "0.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@clack/prompts/-/prompts-0.11.0.tgz",
|
||||
"integrity": "sha512-pMN5FcrEw9hUkZA4f+zLlzivQSeQf5dRGJjSUbvVYDLvpKCdQx5OaknvKzgbtXOizhP+SJJJjqEbOe55uKKfAw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@clack/core": "0.5.0",
|
||||
"picocolors": "^1.0.0",
|
||||
"sisteransi": "^1.0.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@colors/colors": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz",
|
||||
|
|
@ -1998,36 +2020,6 @@
|
|||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@inquirer/external-editor": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-1.0.3.tgz",
|
||||
"integrity": "sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"chardet": "^2.1.1",
|
||||
"iconv-lite": "^0.7.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/node": ">=18"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/node": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@inquirer/figures": {
|
||||
"version": "1.0.15",
|
||||
"resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.15.tgz",
|
||||
"integrity": "sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@isaacs/balanced-match": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz",
|
||||
|
|
@ -3641,9 +3633,8 @@
|
|||
"version": "25.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.3.tgz",
|
||||
"integrity": "sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA==",
|
||||
"devOptional": true,
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"undici-types": "~7.16.0"
|
||||
}
|
||||
|
|
@ -3983,7 +3974,6 @@
|
|||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
},
|
||||
|
|
@ -4031,6 +4021,7 @@
|
|||
"version": "4.3.2",
|
||||
"resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz",
|
||||
"integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"type-fest": "^0.21.3"
|
||||
|
|
@ -4046,6 +4037,7 @@
|
|||
"version": "0.21.3",
|
||||
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz",
|
||||
"integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==",
|
||||
"dev": true,
|
||||
"license": "(MIT OR CC0-1.0)",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
|
|
@ -4290,7 +4282,6 @@
|
|||
"integrity": "sha512-6mF/YrvwwRxLTu+aMEa5pwzKUNl5ZetWbTyZCs9Um0F12HUmxUiF5UHiZPy4rifzU3gtpM3xP2DfdmkNX9eZRg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@astrojs/compiler": "^2.13.0",
|
||||
"@astrojs/internal-helpers": "0.7.5",
|
||||
|
|
@ -5358,7 +5349,6 @@
|
|||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"baseline-browser-mapping": "^2.9.0",
|
||||
"caniuse-lite": "^1.0.30001759",
|
||||
|
|
@ -5601,12 +5591,6 @@
|
|||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/chardet": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/chardet/-/chardet-2.1.1.tgz",
|
||||
"integrity": "sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/chokidar": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
|
||||
|
|
@ -5787,15 +5771,6 @@
|
|||
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/cli-width": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz",
|
||||
"integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">= 12"
|
||||
}
|
||||
},
|
||||
"node_modules/cliui": {
|
||||
"version": "8.0.1",
|
||||
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
|
||||
|
|
@ -6689,7 +6664,6 @@
|
|||
"integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.8.0",
|
||||
"@eslint-community/regexpp": "^4.12.1",
|
||||
|
|
@ -8269,22 +8243,6 @@
|
|||
"@babel/runtime": "^7.23.2"
|
||||
}
|
||||
},
|
||||
"node_modules/iconv-lite": {
|
||||
"version": "0.7.1",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.1.tgz",
|
||||
"integrity": "sha512-2Tth85cXwGFHfvRgZWszZSvdo+0Xsqmw8k8ZwxScfcBneNUraK+dxRxRm24nszx80Y0TVio8kKLt5sLE7ZCLlw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/express"
|
||||
}
|
||||
},
|
||||
"node_modules/ieee754": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
||||
|
|
@ -8420,43 +8378,6 @@
|
|||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/inquirer": {
|
||||
"version": "9.3.8",
|
||||
"resolved": "https://registry.npmjs.org/inquirer/-/inquirer-9.3.8.tgz",
|
||||
"integrity": "sha512-pFGGdaHrmRKMh4WoDDSowddgjT1Vkl90atobmTeSmcPGdYiwikch/m/Ef5wRaiamHejtw0cUUMMerzDUXCci2w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@inquirer/external-editor": "^1.0.2",
|
||||
"@inquirer/figures": "^1.0.3",
|
||||
"ansi-escapes": "^4.3.2",
|
||||
"cli-width": "^4.1.0",
|
||||
"mute-stream": "1.0.0",
|
||||
"ora": "^5.4.1",
|
||||
"run-async": "^3.0.0",
|
||||
"rxjs": "^7.8.1",
|
||||
"string-width": "^4.2.3",
|
||||
"strip-ansi": "^6.0.1",
|
||||
"wrap-ansi": "^6.2.0",
|
||||
"yoctocolors-cjs": "^2.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/inquirer/node_modules/wrap-ansi": {
|
||||
"version": "6.2.0",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
|
||||
"integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-styles": "^4.0.0",
|
||||
"string-width": "^4.1.0",
|
||||
"strip-ansi": "^6.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/iron-webcrypto": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/iron-webcrypto/-/iron-webcrypto-1.2.1.tgz",
|
||||
|
|
@ -10304,7 +10225,6 @@
|
|||
"integrity": "sha512-p3JTemJJbkiMjXEMiFwgm0v6ym5g8K+b2oDny+6xdl300tUKySxvilJQLSea48C6OaYNmO30kH9KxpiAg5bWJw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"globby": "15.0.0",
|
||||
"js-yaml": "4.1.1",
|
||||
|
|
@ -11576,15 +11496,6 @@
|
|||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/mute-stream": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-1.0.0.tgz",
|
||||
"integrity": "sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": "^14.17.0 || ^16.13.0 || >=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/nano-spawn": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/nano-spawn/-/nano-spawn-2.0.0.tgz",
|
||||
|
|
@ -12378,7 +12289,6 @@
|
|||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"nanoid": "^3.3.11",
|
||||
"picocolors": "^1.1.1",
|
||||
|
|
@ -12444,7 +12354,6 @@
|
|||
"integrity": "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"prettier": "bin/prettier.cjs"
|
||||
},
|
||||
|
|
@ -13273,7 +13182,6 @@
|
|||
"integrity": "sha512-3nk8Y3a9Ea8szgKhinMlGMhGMw89mqule3KWczxhIzqudyHdCIOHw8WJlj/r329fACjKLEh13ZSk7oE22kyeIw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/estree": "1.0.8"
|
||||
},
|
||||
|
|
@ -13310,15 +13218,6 @@
|
|||
"fsevents": "~2.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/run-async": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/run-async/-/run-async-3.0.0.tgz",
|
||||
"integrity": "sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/run-parallel": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
|
||||
|
|
@ -13343,15 +13242,6 @@
|
|||
"queue-microtask": "^1.2.2"
|
||||
}
|
||||
},
|
||||
"node_modules/rxjs": {
|
||||
"version": "7.8.2",
|
||||
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz",
|
||||
"integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"tslib": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/safe-buffer": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||
|
|
@ -13372,12 +13262,6 @@
|
|||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/sax": {
|
||||
"version": "1.4.3",
|
||||
"resolved": "https://registry.npmjs.org/sax/-/sax-1.4.3.tgz",
|
||||
|
|
@ -14251,6 +14135,7 @@
|
|||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||
"dev": true,
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/type-check": {
|
||||
|
|
@ -14335,7 +14220,7 @@
|
|||
"version": "7.16.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
|
||||
"integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==",
|
||||
"devOptional": true,
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/unicode-properties": {
|
||||
|
|
@ -14837,7 +14722,6 @@
|
|||
"integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"esbuild": "^0.25.0",
|
||||
"fdir": "^6.4.4",
|
||||
|
|
@ -15111,7 +14995,6 @@
|
|||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz",
|
||||
"integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==",
|
||||
"license": "ISC",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"yaml": "bin.mjs"
|
||||
},
|
||||
|
|
@ -15270,18 +15153,6 @@
|
|||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/yoctocolors-cjs": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.3.tgz",
|
||||
"integrity": "sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/zip-stream": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-6.0.1.tgz",
|
||||
|
|
@ -15303,7 +15174,6 @@
|
|||
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/colinhacks"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -67,6 +67,7 @@
|
|||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@clack/prompts": "^0.11.0",
|
||||
"@kayvan/markdown-tree-parser": "^1.6.1",
|
||||
"boxen": "^5.1.2",
|
||||
"chalk": "^4.1.2",
|
||||
|
|
@ -77,7 +78,6 @@
|
|||
"fs-extra": "^11.3.0",
|
||||
"glob": "^11.0.3",
|
||||
"ignore": "^7.0.5",
|
||||
"inquirer": "^9.3.8",
|
||||
"js-yaml": "^4.1.0",
|
||||
"ora": "^5.4.1",
|
||||
"semver": "^7.6.3",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,123 @@
|
|||
<task id="{bmad_folder}/core/tasks/sync-bug-tracking.xml" name="Sync Bug Tracking">
|
||||
<objective>Sync bugs.yaml and bugs.md when a story is marked done, updating related bugs to "fixed" and features to "implemented"</objective>
|
||||
|
||||
<description>
|
||||
This task is invoked by workflows (story-done, code-review) after a story is marked done.
|
||||
It searches bugs.yaml for bugs/features linked to the completed story and updates their status.
|
||||
For multi-story features, it only marks "implemented" when ALL linked stories are done.
|
||||
</description>
|
||||
|
||||
<inputs>
|
||||
<input name="story_key" required="true">The story key (e.g., "3-7-checkout-from-club-detail-page")</input>
|
||||
<input name="story_id" required="false">The story ID (e.g., "3.7") - used for related_story matching</input>
|
||||
<input name="bugs_yaml" required="true">Path to bugs.yaml file</input>
|
||||
<input name="bugs_md" required="true">Path to bugs.md file</input>
|
||||
<input name="sprint_status" required="true">Path to sprint-status.yaml file</input>
|
||||
<input name="date" required="true">Current date for timestamps</input>
|
||||
</inputs>
|
||||
|
||||
<outputs>
|
||||
<output name="bugs_updated">List of bug IDs marked as fixed</output>
|
||||
<output name="features_updated">List of feature IDs marked as implemented</output>
|
||||
<output name="features_pending">List of feature IDs with incomplete stories</output>
|
||||
</outputs>
|
||||
|
||||
<flow>
|
||||
<step n="1" goal="Load bugs.yaml and check for existence">
|
||||
<action>Load {bugs_yaml} if it exists</action>
|
||||
<check if="bugs.yaml does not exist">
|
||||
<action>Set bugs_updated = [], features_updated = [], features_pending = []</action>
|
||||
<action>Return early - no bug tracking to sync</action>
|
||||
</check>
|
||||
</step>
|
||||
|
||||
<step n="2" goal="Find matching bugs and features using multiple methods">
|
||||
<action>Initialize: bugs_updated = [], features_updated = [], features_pending = []</action>
|
||||
|
||||
<action>Search for entries matching this story using ALL THREE methods:</action>
|
||||
<action>1. Check sprint-status.yaml for comment "# Source: bugs.yaml/feature-XXX" or "# Source: bugs.yaml/bug-XXX" on the {story_key} line - this is the MOST RELIABLE method</action>
|
||||
<action>2. Check related_story field in bugs.yaml matching {story_id} or {story_key}</action>
|
||||
<action>3. Check sprint_stories arrays in feature_requests for entries containing {story_key}</action>
|
||||
|
||||
<critical>PRIORITY: Use sprint-status comment source if present - it's explicit and unambiguous</critical>
|
||||
</step>
|
||||
|
||||
<step n="3" goal="Update matching bugs">
|
||||
<check if="matching bugs found in bugs section">
|
||||
<action>For each matching bug:</action>
|
||||
<action>- Update status: "triaged" or "routed" or "in-progress" → "fixed"</action>
|
||||
<action>- Set fixed_date: {date}</action>
|
||||
<action>- Set assigned_to: "dev-agent" (if not already set)</action>
|
||||
<action>- Append to notes: "Auto-closed via sync-bug-tracking. Story {story_key} marked done on {date}."</action>
|
||||
<action>- Add bug ID to bugs_updated list</action>
|
||||
</check>
|
||||
</step>
|
||||
|
||||
<step n="4" goal="Update matching features (with multi-story check)">
|
||||
<check if="matching features found in feature_requests section">
|
||||
<action>For each matching feature (via related_story OR sprint_stories):</action>
|
||||
|
||||
<critical>MULTI-STORY FEATURE CHECK: If feature has sprint_stories array with multiple entries:</critical>
|
||||
<action>1. Extract all story keys from sprint_stories (format: "story-key: status")</action>
|
||||
<action>2. Load sprint-status.yaml and check development_status for EACH story</action>
|
||||
<action>3. Only proceed if ALL stories in sprint_stories have status "done" in sprint-status.yaml</action>
|
||||
<action>4. If any story is NOT done, add feature to features_pending and log: "Feature {feature_id} has incomplete stories: {incomplete_list}"</action>
|
||||
|
||||
<check if="ALL sprint_stories are done (or feature has single story that matches)">
|
||||
<action>- Update status: "backlog" or "triaged" or "routed" or "in-progress" → "implemented"</action>
|
||||
<action>- Set implemented_date: {date}</action>
|
||||
<action>- Update sprint_stories entries to reflect done status</action>
|
||||
<action>- Append to notes: "Auto-closed via sync-bug-tracking. Story {story_key} marked done on {date}."</action>
|
||||
<action>- Add feature ID to features_updated list</action>
|
||||
</check>
|
||||
</check>
|
||||
</step>
|
||||
|
||||
<step n="5" goal="Save bugs.yaml updates">
|
||||
<check if="bugs_updated is not empty OR features_updated is not empty">
|
||||
<action>Save updated bugs.yaml, preserving all structure and comments</action>
|
||||
</check>
|
||||
</step>
|
||||
|
||||
<step n="6" goal="Update bugs.md to match">
|
||||
<check if="bugs_updated is not empty OR features_updated is not empty">
|
||||
<action>Load {bugs_md}</action>
|
||||
|
||||
<check if="bugs_updated is not empty">
|
||||
<action>For each bug in bugs_updated:</action>
|
||||
<action>- Find the bug entry in "# Tracked Bugs" section</action>
|
||||
<action>- Move it to "# Fixed Bugs" section</action>
|
||||
<action>- Add [IMPLEMENTED] tag prefix with date: "[IMPLEMENTED] bug-XXX: Title [Fixed: {date}, Verified: pending]"</action>
|
||||
</check>
|
||||
|
||||
<check if="features_updated is not empty">
|
||||
<action>For each feature in features_updated:</action>
|
||||
<action>- Find the feature entry in "# Tracked Feature Requests" section</action>
|
||||
<action>- Move it to "# Implemented Features" section</action>
|
||||
<action>- Add [IMPLEMENTED] tag prefix with date: "[IMPLEMENTED] feature-XXX: Title [Implemented: {date}, Verified: pending]"</action>
|
||||
</check>
|
||||
|
||||
<action>Update statistics section if present</action>
|
||||
<action>Save updated bugs.md</action>
|
||||
</check>
|
||||
</step>
|
||||
|
||||
<step n="7" goal="Return results">
|
||||
<output>
|
||||
Bug/Feature Sync Results:
|
||||
{{#if bugs_updated}}
|
||||
- Bugs marked fixed: {{bugs_updated}}
|
||||
{{/if}}
|
||||
{{#if features_updated}}
|
||||
- Features marked implemented: {{features_updated}}
|
||||
{{/if}}
|
||||
{{#if features_pending}}
|
||||
- Features with incomplete stories (not yet implemented): {{features_pending}}
|
||||
{{/if}}
|
||||
{{#if no_matches}}
|
||||
- No related bugs/features found for story {story_key}
|
||||
{{/if}}
|
||||
</output>
|
||||
</step>
|
||||
</flow>
|
||||
</task>
|
||||
|
|
@ -214,11 +214,24 @@
|
|||
<output>ℹ️ Story status updated (no sprint tracking configured)</output>
|
||||
</check>
|
||||
|
||||
<!-- Sync bug tracking when story is marked done -->
|
||||
<check if="{{new_status}} == 'done'">
|
||||
<invoke-task path="{project-root}/.bmad/core/tasks/sync-bug-tracking.xml">
|
||||
<param name="story_key">{{story_key}}</param>
|
||||
<param name="story_id">{{story_id}}</param>
|
||||
<param name="bugs_yaml">{output_folder}/bugs.yaml</param>
|
||||
<param name="bugs_md">{output_folder}/bugs.md</param>
|
||||
<param name="sprint_status">{sprint_status}</param>
|
||||
<param name="date">{date}</param>
|
||||
</invoke-task>
|
||||
</check>
|
||||
|
||||
<output>**✅ Review Complete!**
|
||||
|
||||
**Story Status:** {{new_status}}
|
||||
**Issues Fixed:** {{fixed_count}}
|
||||
**Action Items Created:** {{action_count}}
|
||||
{{#if new_status == "done"}}**Bug/Feature Tracking:** Synced automatically{{/if}}
|
||||
|
||||
{{#if new_status == "done"}}Code review complete!{{else}}Address the action items and continue development.{{/if}}
|
||||
</output>
|
||||
|
|
|
|||
|
|
@ -1300,7 +1300,67 @@ Bob (Scrum Master): "See you all when prep work is done. Meeting adjourned!"
|
|||
|
||||
</step>
|
||||
|
||||
<step n="11" goal="Save Retrospective and Update Sprint Status">
|
||||
<step n="11" goal="Sync Epic-Linked Bugs/Features to Closed Status">
|
||||
<critical>Check bugs.yaml for bugs/features linked to this epic and close them</critical>
|
||||
|
||||
<action>Load {bugs_yaml} if it exists</action>
|
||||
|
||||
<check if="bugs.yaml exists">
|
||||
<action>Search for entries with related_epic matching {{epic_number}}</action>
|
||||
|
||||
<action>For bugs section - find bugs with related_epic == {{epic_number}} AND status in ["fixed", "triaged", "routed"]:</action>
|
||||
<check if="matching bugs found">
|
||||
<action>For each matching bug:</action>
|
||||
<action>- Move entry from "bugs" section to "closed_bugs" section</action>
|
||||
<action>- Update status: → "closed"</action>
|
||||
<action>- Set verified_by: "retrospective-workflow"</action>
|
||||
<action>- Set verified_date: {date}</action>
|
||||
<action>- Append to notes: "Auto-closed via epic retrospective. Epic {{epic_number}} completed on {date}."</action>
|
||||
</check>
|
||||
|
||||
<action>For feature_requests section - find features with related_epic == {{epic_number}} AND status in ["implemented", "backlog", "in-progress"]:</action>
|
||||
<check if="matching features found">
|
||||
<action>For each matching feature:</action>
|
||||
<action>- Move entry from "feature_requests" section to "implemented_features" section</action>
|
||||
<action>- Update status: → "complete"</action>
|
||||
<action>- Set completed_by: "retrospective-workflow"</action>
|
||||
<action>- Set completed_date: {date}</action>
|
||||
<action>- Append to notes: "Auto-closed via epic retrospective. Epic {{epic_number}} completed on {date}."</action>
|
||||
</check>
|
||||
|
||||
<action>Update statistics section with new counts</action>
|
||||
<action>Save updated bugs.yaml</action>
|
||||
|
||||
<check if="bugs/features were moved">
|
||||
<action>Also update bugs.md:</action>
|
||||
<action>- Remove [IMPLEMENTED] tag from closed items</action>
|
||||
<action>- Move bug entries to "# Fixed Bugs" section if not already there</action>
|
||||
<action>- Move feature entries to "# Implemented Features" section if not already there</action>
|
||||
<action>- Add [CLOSED] or [COMPLETE] tag to indicate final status</action>
|
||||
<action>Save updated bugs.md</action>
|
||||
</check>
|
||||
|
||||
<output>
|
||||
Bug/Feature Closure:
|
||||
{{#if bugs_closed}}
|
||||
- Bugs closed for Epic {{epic_number}}: {{bugs_closed_list}}
|
||||
{{/if}}
|
||||
{{#if features_completed}}
|
||||
- Features completed for Epic {{epic_number}}: {{features_completed_list}}
|
||||
{{/if}}
|
||||
{{#if no_matches}}
|
||||
- No outstanding bugs/features linked to Epic {{epic_number}}
|
||||
{{/if}}
|
||||
</output>
|
||||
</check>
|
||||
|
||||
<check if="bugs.yaml does not exist">
|
||||
<action>Skip bug tracking sync - no bugs.yaml file present</action>
|
||||
</check>
|
||||
|
||||
</step>
|
||||
|
||||
<step n="12" goal="Save Retrospective and Update Sprint Status">
|
||||
|
||||
<action>Ensure retrospectives folder exists: {retrospectives_folder}</action>
|
||||
<action>Create folder if it doesn't exist</action>
|
||||
|
|
@ -1356,7 +1416,7 @@ Retrospective document was saved successfully, but {sprint_status_file} may need
|
|||
|
||||
</step>
|
||||
|
||||
<step n="12" goal="Final Summary and Handoff">
|
||||
<step n="13" goal="Final Summary and Handoff">
|
||||
|
||||
<output>
|
||||
**✅ Retrospective Complete, {user_name}!**
|
||||
|
|
|
|||
|
|
@ -54,5 +54,9 @@ sprint_status_file: "{implementation_artifacts}/sprint-status.yaml"
|
|||
story_directory: "{implementation_artifacts}"
|
||||
retrospectives_folder: "{implementation_artifacts}"
|
||||
|
||||
# Bug tracking integration (optional)
|
||||
bugs_yaml: "{planning_artifacts}/bugs.yaml"
|
||||
bugs_md: "{planning_artifacts}/bugs.md"
|
||||
|
||||
standalone: true
|
||||
web_bundle: false
|
||||
|
|
|
|||
|
|
@ -48,6 +48,26 @@
|
|||
<note>After discovery, these content variables are available: {epics_content} (all epics loaded - uses FULL_LOAD strategy)</note>
|
||||
</step>
|
||||
|
||||
<step n="1.5" goal="Load bugs.yaml for bug/feature tracking (optional)">
|
||||
<action>Check if {bugs_yaml} exists in {planning_artifacts}</action>
|
||||
<check if="bugs_yaml exists">
|
||||
<action>Read bugs.yaml using grep to find all bug-NNN and feature-NNN entries</action>
|
||||
<action>For each bug/feature, extract:
|
||||
- ID (e.g., bug-001, feature-003)
|
||||
- Title
|
||||
- Status (triaged, routed, in-progress, fixed/implemented, verified, closed)
|
||||
- Recommended workflow (direct-fix, tech-spec, correct-course, backlog)
|
||||
- Related stories (sprint_stories field for features)
|
||||
</action>
|
||||
<action>Build bug/feature inventory for inclusion in sprint status</action>
|
||||
<action>Track feature-to-story mappings (feature-001 → stories 7-1, 7-2, etc.)</action>
|
||||
</check>
|
||||
<check if="bugs_yaml does not exist">
|
||||
<output>Note: No bugs.yaml found - bug tracking not enabled for this project.</output>
|
||||
<action>Continue without bug integration</action>
|
||||
</check>
|
||||
</step>
|
||||
|
||||
<step n="2" goal="Build sprint status structure">
|
||||
<action>For each epic found, create entries in this order:</action>
|
||||
|
||||
|
|
@ -65,6 +85,17 @@ development_status:
|
|||
epic-1-retrospective: optional
|
||||
```
|
||||
|
||||
<action>If bugs.yaml was loaded, add bug/feature sources header comment:</action>
|
||||
|
||||
```yaml
|
||||
# STORY SOURCES:
|
||||
# ==============
|
||||
# - epics.md: Primary source ({story_count} stories)
|
||||
# - bugs.yaml: Feature-driven stories ({feature_story_count} stories from sprint_stories)
|
||||
# - feature-001: 7-1, 7-2, 7-3 (from sprint_stories field)
|
||||
# - feature-002: 3-7
|
||||
```
|
||||
|
||||
</step>
|
||||
|
||||
<step n="3" goal="Apply intelligent status detection">
|
||||
|
|
|
|||
|
|
@ -33,6 +33,10 @@ variables:
|
|||
epics_location: "{planning_artifacts}" # Directory containing epic*.md files
|
||||
epics_pattern: "epic*.md" # Pattern to find epic files
|
||||
|
||||
# Bug tracking integration (optional)
|
||||
bugs_yaml: "{planning_artifacts}/bugs.yaml" # Structured bug/feature metadata
|
||||
bugs_md: "{planning_artifacts}/bugs.md" # Human-readable bug tracking
|
||||
|
||||
# Output configuration
|
||||
status_file: "{implementation_artifacts}/sprint-status.yaml"
|
||||
|
||||
|
|
|
|||
|
|
@ -88,15 +88,31 @@ Enter corrections (e.g., "1=in-progress, 2=backlog") or "skip" to continue witho
|
|||
- IF any epic has status in-progress but has no associated stories: warn "in-progress epic has no stories"
|
||||
</step>
|
||||
|
||||
<step n="2.5" goal="Load bug/feature tracking status (optional)">
|
||||
<action>Check if {bugs_yaml} exists</action>
|
||||
<check if="bugs_yaml exists">
|
||||
<action>Grep for bug-NNN and feature-NNN entries with status field</action>
|
||||
<action>Count items by status: triaged, fixed/implemented (pending verify), verified, closed</action>
|
||||
<action>Identify items needing action:
|
||||
- Items with [IMPLEMENTED] tag → need verification
|
||||
- Items with status "triaged" + workflow "direct-fix" → ready for implementation
|
||||
</action>
|
||||
<action>Store: bugs_pending_verify, bugs_triaged, features_pending_verify, features_triaged</action>
|
||||
</check>
|
||||
</step>
|
||||
|
||||
<step n="3" goal="Select next action recommendation">
|
||||
<action>Pick the next recommended workflow using priority:</action>
|
||||
<note>When selecting "first" story: sort by epic number, then story number (e.g., 1-1 before 1-2 before 2-1)</note>
|
||||
1. If any story status == in-progress → recommend `dev-story` for the first in-progress story
|
||||
2. Else if any story status == review → recommend `code-review` for the first review story
|
||||
3. Else if any story status == ready-for-dev → recommend `dev-story`
|
||||
4. Else if any story status == backlog → recommend `create-story`
|
||||
5. Else if any retrospective status == optional → recommend `retrospective`
|
||||
6. Else → All implementation items done; suggest `workflow-status` to plan next phase
|
||||
<note>Bug verification takes priority over new story work to close the feedback loop</note>
|
||||
1. If any bug/feature has [IMPLEMENTED] tag (pending verify) → recommend `verify` for first pending item
|
||||
2. If any story status == in-progress → recommend `dev-story` for the first in-progress story
|
||||
3. Else if any story status == review → recommend `code-review` for the first review story
|
||||
4. Else if any story status == ready-for-dev → recommend `dev-story`
|
||||
5. Else if any bug status == triaged with workflow == direct-fix → recommend `implement` for first triaged bug
|
||||
6. Else if any story status == backlog → recommend `create-story`
|
||||
7. Else if any retrospective status == optional → recommend `retrospective`
|
||||
8. Else → All implementation items done; suggest `workflow-status` to plan next phase
|
||||
<action>Store selected recommendation as: next_story_id, next_workflow_id, next_agent (SM/DEV as appropriate)</action>
|
||||
</step>
|
||||
|
||||
|
|
@ -112,6 +128,11 @@ Enter corrections (e.g., "1=in-progress, 2=backlog") or "skip" to continue witho
|
|||
|
||||
**Epics:** backlog {{epic_backlog}}, in-progress {{epic_in_progress}}, done {{epic_done}}
|
||||
|
||||
{{#if bugs_yaml_exists}}
|
||||
**Bugs:** triaged {{bugs_triaged}}, pending-verify {{bugs_pending_verify}}, closed {{bugs_closed}}
|
||||
**Features:** triaged {{features_triaged}}, pending-verify {{features_pending_verify}}, complete {{features_complete}}
|
||||
{{/if}}
|
||||
|
||||
**Next Recommendation:** /bmad:bmm:workflows:{{next_workflow_id}} ({{next_story_id}})
|
||||
|
||||
{{#if risks}}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,9 @@ instructions: "{installed_path}/instructions.md"
|
|||
variables:
|
||||
sprint_status_file: "{implementation_artifacts}/sprint-status.yaml"
|
||||
tracking_system: "file-system"
|
||||
# Bug tracking integration (optional)
|
||||
bugs_yaml: "{planning_artifacts}/bugs.yaml"
|
||||
bugs_md: "{planning_artifacts}/bugs.md"
|
||||
|
||||
# Smart input file references
|
||||
input_file_patterns:
|
||||
|
|
|
|||
|
|
@ -0,0 +1,124 @@
|
|||
# Story Approved Workflow Instructions (DEV Agent)
|
||||
|
||||
<critical>The workflow execution engine is governed by: {project-root}/.bmad/core/tasks/workflow.xml</critical>
|
||||
<critical>You MUST have already loaded and processed: {installed_path}/workflow.yaml</critical>
|
||||
<critical>Communicate all responses in {communication_language}</critical>
|
||||
|
||||
<workflow>
|
||||
|
||||
<critical>This workflow is run by DEV agent AFTER user confirms a story is approved (Definition of Done is complete)</critical>
|
||||
<critical>Workflow: Update story file status to Done</critical>
|
||||
|
||||
<step n="1" goal="Find reviewed story to mark done" tag="sprint-status">
|
||||
|
||||
<check if="{story_path} is provided">
|
||||
<action>Use {story_path} directly</action>
|
||||
<action>Read COMPLETE story file and parse sections</action>
|
||||
<action>Extract story_key from filename or story metadata</action>
|
||||
<action>Verify Status is "review" - if not, HALT with message: "Story status must be 'review' to mark as done"</action>
|
||||
</check>
|
||||
|
||||
<check if="{story_path} is NOT provided">
|
||||
<critical>MUST read COMPLETE sprint-status.yaml file from start to end to preserve order</critical>
|
||||
<action>Load the FULL file: {output_folder}/sprint-status.yaml</action>
|
||||
<action>Read ALL lines from beginning to end - do not skip any content</action>
|
||||
<action>Parse the development_status section completely</action>
|
||||
|
||||
<action>Find FIRST story (reading in order from top to bottom) where: - Key matches pattern: number-number-name (e.g., "1-2-user-auth") - NOT an epic key (epic-X) or retrospective (epic-X-retrospective) - Status value equals "review"
|
||||
</action>
|
||||
|
||||
<check if="no story with status 'review' found">
|
||||
<output>No stories with status "review" found
|
||||
|
||||
All stories are either still in development or already done.
|
||||
|
||||
**Next Steps:**
|
||||
|
||||
1. Run `dev-story` to implement stories
|
||||
2. Run `code-review` if stories need review first
|
||||
3. Check sprint-status.yaml for current story states
|
||||
</output>
|
||||
<action>HALT</action>
|
||||
</check>
|
||||
|
||||
<action>Use the first reviewed story found</action>
|
||||
<action>Find matching story file in {story_dir} using story_key pattern</action>
|
||||
<action>Read the COMPLETE story file</action>
|
||||
</check>
|
||||
|
||||
<action>Extract story_id and story_title from the story file</action>
|
||||
|
||||
<action>Find the "Status:" line (usually at the top)</action>
|
||||
<action>Update story file: Change Status to "done"</action>
|
||||
|
||||
<action>Add completion notes to Dev Agent Record section:</action>
|
||||
<action>Find "## Dev Agent Record" section and add:
|
||||
|
||||
```
|
||||
### Completion Notes
|
||||
**Completed:** {date}
|
||||
**Definition of Done:** All acceptance criteria met, code reviewed, tests passing
|
||||
```
|
||||
|
||||
</action>
|
||||
|
||||
<action>Save the story file</action>
|
||||
</step>
|
||||
|
||||
<step n="2" goal="Update sprint status to done" tag="sprint-status">
|
||||
<action>Load the FULL file: {output_folder}/sprint-status.yaml</action>
|
||||
<action>Find development_status key matching {story_key}</action>
|
||||
<action>Verify current status is "review" (expected previous state)</action>
|
||||
<action>Update development_status[{story_key}] = "done"</action>
|
||||
<action>Save file, preserving ALL comments and structure including STATUS DEFINITIONS</action>
|
||||
|
||||
<check if="story key not found in file">
|
||||
<output>Story file updated, but could not update sprint-status: {story_key} not found
|
||||
|
||||
Story is marked Done in file, but sprint-status.yaml may be out of sync.
|
||||
</output>
|
||||
</check>
|
||||
|
||||
</step>
|
||||
|
||||
<step n="3" goal="Sync related bugs/features in bug tracking">
|
||||
<critical>Invoke shared task to sync bugs.yaml and bugs.md for this completed story</critical>
|
||||
|
||||
<invoke-task path="{project-root}/.bmad/core/tasks/sync-bug-tracking.xml">
|
||||
<param name="story_key">{story_key}</param>
|
||||
<param name="story_id">{story_id}</param>
|
||||
<param name="bugs_yaml">{bugs_yaml}</param>
|
||||
<param name="bugs_md">{bugs_md}</param>
|
||||
<param name="sprint_status">{sprint_status}</param>
|
||||
<param name="date">{date}</param>
|
||||
</invoke-task>
|
||||
|
||||
</step>
|
||||
|
||||
<step n="4" goal="Confirm completion to user">
|
||||
|
||||
<output>**Story Approved and Marked Done, {user_name}!**
|
||||
|
||||
Story file updated - Status: done
|
||||
Sprint status updated: review → done
|
||||
|
||||
**Completed Story:**
|
||||
|
||||
- **ID:** {story_id}
|
||||
- **Key:** {story_key}
|
||||
- **Title:** {story_title}
|
||||
- **Completed:** {date}
|
||||
|
||||
**Next Steps:**
|
||||
|
||||
1. Continue with next story in your backlog
|
||||
- Run `create-story` for next backlog story
|
||||
- Or run `dev-story` if ready stories exist
|
||||
2. Check epic completion status
|
||||
- Run `retrospective` workflow to check if epic is complete
|
||||
- Epic retrospective will verify all stories are done
|
||||
</output>
|
||||
|
||||
</step>
|
||||
|
||||
</workflow>
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
# Story Done Workflow (DEV Agent)
|
||||
name: story-done
|
||||
description: 'Marks a story as done (DoD complete), updates sprint-status → DONE, and syncs related bugs/features in bugs.yaml/bugs.md to [IMPLEMENTED] status.'
|
||||
author: 'BMad'
|
||||
|
||||
# Critical variables from config
|
||||
config_source: '{project-root}/.bmad/bmm/config.yaml'
|
||||
output_folder: '{config_source}:output_folder'
|
||||
user_name: '{config_source}:user_name'
|
||||
communication_language: '{config_source}:communication_language'
|
||||
date: system-generated
|
||||
sprint_status: '{output_folder}/sprint-status.yaml'
|
||||
|
||||
# Workflow components
|
||||
installed_path: '{project-root}/.bmad/bmm/workflows/4-implementation/story-done'
|
||||
instructions: '{installed_path}/instructions.md'
|
||||
|
||||
# Variables and inputs
|
||||
variables:
|
||||
story_dir: '{config_source}:dev_ephemeral_location/stories' # Directory where stories are stored
|
||||
bugs_yaml: '{output_folder}/bugs.yaml' # Bug/feature tracking structured data
|
||||
bugs_md: '{output_folder}/bugs.md' # Bug/feature tracking human-readable log
|
||||
|
||||
# Output configuration - no output file, just status updates
|
||||
default_output_file: ''
|
||||
|
||||
standalone: true
|
||||
|
|
@ -0,0 +1,542 @@
|
|||
# In-App Bug Reporting - Reference Implementation
|
||||
|
||||
This document provides a reference implementation for adding **in-app bug reporting** to your project. The BMAD bug-tracking workflow works without this feature (using manual `bugs.md` input), but in-app reporting provides a better user experience.
|
||||
|
||||
## Overview
|
||||
|
||||
The in-app bug reporting feature allows users to submit bug reports directly from your application. Reports are stored in your database and then synced to `bugs.md` by the triage workflow.
|
||||
|
||||
```
|
||||
User -> UI Modal -> API -> Database -> Triage Workflow -> bugs.md/bugs.yaml
|
||||
```
|
||||
|
||||
## Components Required
|
||||
|
||||
| Component | Purpose | Stack-Specific |
|
||||
|-----------|---------|----------------|
|
||||
| Database table | Store pending bug reports | Yes |
|
||||
| API: Create report | Accept user submissions | Yes |
|
||||
| API: Get pending | Fetch unsynced reports | Yes |
|
||||
| API: Mark synced | Update status after sync | Yes |
|
||||
| UI Modal | Bug report form | Yes |
|
||||
| Validation schemas | Input validation | Partially |
|
||||
|
||||
## 1. Database Schema
|
||||
|
||||
### Drizzle ORM (PostgreSQL)
|
||||
|
||||
```typescript
|
||||
// src/lib/server/db/schema.ts
|
||||
|
||||
import { pgTable, uuid, text, timestamp, index } from 'drizzle-orm/pg-core';
|
||||
|
||||
export const bugReports = pgTable(
|
||||
'bug_reports',
|
||||
{
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
organizationId: uuid('organization_id').notNull(), // For multi-tenant apps
|
||||
reporterType: text('reporter_type').notNull(), // 'staff' | 'member' | 'user'
|
||||
reporterId: uuid('reporter_id').notNull(),
|
||||
title: text('title').notNull(),
|
||||
description: text('description').notNull(),
|
||||
userAgent: text('user_agent'),
|
||||
pageUrl: text('page_url'),
|
||||
platform: text('platform'), // 'Windows', 'macOS', 'iOS', etc.
|
||||
browser: text('browser'), // 'Chrome', 'Safari', 'Firefox'
|
||||
screenshotUrl: text('screenshot_url'), // Optional: cloud storage URL
|
||||
status: text('status').notNull().default('new'), // 'new' | 'synced' | 'dismissed'
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
|
||||
syncedAt: timestamp('synced_at', { withTimezone: true })
|
||||
},
|
||||
(table) => [
|
||||
index('bug_reports_organization_id_idx').on(table.organizationId),
|
||||
index('bug_reports_status_idx').on(table.status),
|
||||
index('bug_reports_created_at_idx').on(table.createdAt)
|
||||
]
|
||||
);
|
||||
|
||||
export const BUG_REPORT_STATUS = {
|
||||
NEW: 'new',
|
||||
SYNCED: 'synced',
|
||||
DISMISSED: 'dismissed'
|
||||
} as const;
|
||||
|
||||
export const REPORTER_TYPE = {
|
||||
STAFF: 'staff',
|
||||
MEMBER: 'member',
|
||||
USER: 'user'
|
||||
} as const;
|
||||
```
|
||||
|
||||
### Prisma Schema
|
||||
|
||||
```prisma
|
||||
model BugReport {
|
||||
id String @id @default(uuid())
|
||||
organizationId String @map("organization_id")
|
||||
reporterType String @map("reporter_type")
|
||||
reporterId String @map("reporter_id")
|
||||
title String
|
||||
description String
|
||||
userAgent String? @map("user_agent")
|
||||
pageUrl String? @map("page_url")
|
||||
platform String?
|
||||
browser String?
|
||||
screenshotUrl String? @map("screenshot_url")
|
||||
status String @default("new")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
syncedAt DateTime? @map("synced_at")
|
||||
|
||||
@@index([organizationId])
|
||||
@@index([status])
|
||||
@@index([createdAt])
|
||||
@@map("bug_reports")
|
||||
}
|
||||
```
|
||||
|
||||
## 2. Validation Schemas
|
||||
|
||||
### Zod (TypeScript)
|
||||
|
||||
```typescript
|
||||
// src/lib/schemas/bug-report.ts
|
||||
|
||||
import { z } from 'zod';
|
||||
|
||||
export const createBugReportSchema = z.object({
|
||||
title: z
|
||||
.string()
|
||||
.trim()
|
||||
.min(5, 'Title must be at least 5 characters')
|
||||
.max(200, 'Title must be 200 characters or less'),
|
||||
description: z
|
||||
.string()
|
||||
.trim()
|
||||
.min(10, 'Description must be at least 10 characters')
|
||||
.max(5000, 'Description must be 5000 characters or less'),
|
||||
pageUrl: z.string().url().optional(),
|
||||
userAgent: z.string().max(1000).optional(),
|
||||
platform: z.string().max(50).optional(),
|
||||
browser: z.string().max(50).optional()
|
||||
});
|
||||
|
||||
export const markSyncedSchema = z.object({
|
||||
ids: z.array(z.string().uuid()).min(1, 'At least one ID is required')
|
||||
});
|
||||
|
||||
export const SCREENSHOT_CONFIG = {
|
||||
maxSizeBytes: 5 * 1024 * 1024, // 5MB
|
||||
maxSizeMB: 5,
|
||||
allowedTypes: ['image/jpeg', 'image/png', 'image/webp'] as const
|
||||
} as const;
|
||||
```
|
||||
|
||||
## 3. API Endpoints
|
||||
|
||||
### POST /api/bug-reports - Create Report
|
||||
|
||||
```typescript
|
||||
// SvelteKit: src/routes/api/bug-reports/+server.ts
|
||||
|
||||
import { json } from '@sveltejs/kit';
|
||||
import type { RequestHandler } from './$types';
|
||||
import { db } from '$lib/server/db';
|
||||
import { bugReports } from '$lib/server/db/schema';
|
||||
import { createBugReportSchema } from '$lib/schemas/bug-report';
|
||||
|
||||
export const POST: RequestHandler = async ({ request, locals }) => {
|
||||
// Determine reporter from auth context
|
||||
if (!locals.user) {
|
||||
return json({ error: { code: 'UNAUTHORIZED' } }, { status: 401 });
|
||||
}
|
||||
|
||||
const body = await request.json();
|
||||
const result = createBugReportSchema.safeParse(body);
|
||||
|
||||
if (!result.success) {
|
||||
return json({
|
||||
error: { code: 'VALIDATION_ERROR', message: result.error.issues[0]?.message }
|
||||
}, { status: 400 });
|
||||
}
|
||||
|
||||
const { title, description, pageUrl, userAgent, platform, browser } = result.data;
|
||||
|
||||
const [newReport] = await db
|
||||
.insert(bugReports)
|
||||
.values({
|
||||
organizationId: locals.user.organizationId,
|
||||
reporterType: 'staff',
|
||||
reporterId: locals.user.id,
|
||||
title,
|
||||
description,
|
||||
pageUrl,
|
||||
userAgent,
|
||||
platform,
|
||||
browser
|
||||
})
|
||||
.returning();
|
||||
|
||||
return json({
|
||||
data: {
|
||||
bugReport: {
|
||||
id: newReport.id,
|
||||
title: newReport.title,
|
||||
createdAt: newReport.createdAt.toISOString()
|
||||
}
|
||||
}
|
||||
}, { status: 201 });
|
||||
};
|
||||
```
|
||||
|
||||
### GET /api/bug-reports/pending - Fetch for Triage
|
||||
|
||||
```typescript
|
||||
// SvelteKit: src/routes/api/bug-reports/pending/+server.ts
|
||||
|
||||
import { json } from '@sveltejs/kit';
|
||||
import type { RequestHandler } from './$types';
|
||||
import { db } from '$lib/server/db';
|
||||
import { bugReports, BUG_REPORT_STATUS } from '$lib/server/db/schema';
|
||||
import { eq } from 'drizzle-orm';
|
||||
|
||||
export const GET: RequestHandler = async () => {
|
||||
const reports = await db
|
||||
.select()
|
||||
.from(bugReports)
|
||||
.where(eq(bugReports.status, BUG_REPORT_STATUS.NEW))
|
||||
.orderBy(bugReports.createdAt);
|
||||
|
||||
// Map to workflow-expected format
|
||||
const formatted = reports.map((r) => ({
|
||||
id: r.id,
|
||||
title: r.title,
|
||||
description: r.description,
|
||||
reporterType: r.reporterType,
|
||||
reporterName: 'Unknown', // Join with users table for real name
|
||||
platform: r.platform,
|
||||
browser: r.browser,
|
||||
pageUrl: r.pageUrl,
|
||||
screenshotUrl: r.screenshotUrl,
|
||||
createdAt: r.createdAt.toISOString()
|
||||
}));
|
||||
|
||||
return json({
|
||||
data: {
|
||||
reports: formatted,
|
||||
count: formatted.length
|
||||
}
|
||||
});
|
||||
};
|
||||
```
|
||||
|
||||
### POST /api/bug-reports/mark-synced - Update After Sync
|
||||
|
||||
```typescript
|
||||
// SvelteKit: src/routes/api/bug-reports/mark-synced/+server.ts
|
||||
|
||||
import { json } from '@sveltejs/kit';
|
||||
import type { RequestHandler } from './$types';
|
||||
import { db } from '$lib/server/db';
|
||||
import { bugReports, BUG_REPORT_STATUS } from '$lib/server/db/schema';
|
||||
import { inArray } from 'drizzle-orm';
|
||||
import { markSyncedSchema } from '$lib/schemas/bug-report';
|
||||
|
||||
export const POST: RequestHandler = async ({ request }) => {
|
||||
const body = await request.json();
|
||||
const result = markSyncedSchema.safeParse(body);
|
||||
|
||||
if (!result.success) {
|
||||
return json({
|
||||
error: { code: 'VALIDATION_ERROR', message: result.error.issues[0]?.message }
|
||||
}, { status: 400 });
|
||||
}
|
||||
|
||||
const updated = await db
|
||||
.update(bugReports)
|
||||
.set({
|
||||
status: BUG_REPORT_STATUS.SYNCED,
|
||||
syncedAt: new Date()
|
||||
})
|
||||
.where(inArray(bugReports.id, result.data.ids))
|
||||
.returning({ id: bugReports.id });
|
||||
|
||||
return json({
|
||||
data: {
|
||||
updatedCount: updated.length,
|
||||
updatedIds: updated.map((r) => r.id)
|
||||
}
|
||||
});
|
||||
};
|
||||
```
|
||||
|
||||
## 4. UI Component
|
||||
|
||||
### Svelte 5 (with shadcn-svelte)
|
||||
|
||||
```svelte
|
||||
<!-- src/lib/components/BugReportModal.svelte -->
|
||||
<script lang="ts">
|
||||
import * as Dialog from '$lib/components/ui/dialog';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Textarea } from '$lib/components/ui/textarea';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import { Bug } from 'lucide-svelte';
|
||||
import { browser } from '$app/environment';
|
||||
|
||||
interface Props {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
let { open = $bindable(), onClose }: Props = $props();
|
||||
|
||||
let title = $state('');
|
||||
let description = $state('');
|
||||
let loading = $state(false);
|
||||
|
||||
// Auto-detect environment
|
||||
let platform = $derived(browser ? detectPlatform() : '');
|
||||
let browserName = $derived(browser ? detectBrowser() : '');
|
||||
let currentUrl = $derived(browser ? window.location.href : '');
|
||||
|
||||
function detectPlatform(): string {
|
||||
const ua = navigator.userAgent.toLowerCase();
|
||||
if (ua.includes('iphone') || ua.includes('ipad')) return 'iOS';
|
||||
if (ua.includes('android')) return 'Android';
|
||||
if (ua.includes('mac')) return 'macOS';
|
||||
if (ua.includes('win')) return 'Windows';
|
||||
return 'Unknown';
|
||||
}
|
||||
|
||||
function detectBrowser(): string {
|
||||
const ua = navigator.userAgent;
|
||||
if (ua.includes('Chrome') && !ua.includes('Edg')) return 'Chrome';
|
||||
if (ua.includes('Safari') && !ua.includes('Chrome')) return 'Safari';
|
||||
if (ua.includes('Firefox')) return 'Firefox';
|
||||
if (ua.includes('Edg')) return 'Edge';
|
||||
return 'Unknown';
|
||||
}
|
||||
|
||||
async function handleSubmit() {
|
||||
loading = true;
|
||||
try {
|
||||
const response = await fetch('/api/bug-reports', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
title,
|
||||
description,
|
||||
pageUrl: currentUrl,
|
||||
userAgent: navigator.userAgent,
|
||||
platform,
|
||||
browser: browserName
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const data = await response.json();
|
||||
toast.error(data.error?.message || 'Failed to submit');
|
||||
return;
|
||||
}
|
||||
|
||||
toast.success('Bug report submitted');
|
||||
onClose();
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Dialog.Root bind:open onOpenChange={(o) => !o && onClose()}>
|
||||
<Dialog.Content class="sm:max-w-[500px]">
|
||||
<Dialog.Header>
|
||||
<Dialog.Title class="flex items-center gap-2">
|
||||
<Bug class="h-5 w-5" />
|
||||
Report a Bug
|
||||
</Dialog.Title>
|
||||
</Dialog.Header>
|
||||
|
||||
<form onsubmit={(e) => { e.preventDefault(); handleSubmit(); }} class="space-y-4">
|
||||
<div>
|
||||
<Input bind:value={title} placeholder="Brief summary" maxlength={200} />
|
||||
</div>
|
||||
<div>
|
||||
<Textarea bind:value={description} placeholder="What happened?" rows={4} />
|
||||
</div>
|
||||
<div class="rounded-md bg-muted p-3 text-sm text-muted-foreground">
|
||||
{platform} / {browserName}
|
||||
</div>
|
||||
<Dialog.Footer>
|
||||
<Button variant="outline" onclick={onClose} disabled={loading}>Cancel</Button>
|
||||
<Button type="submit" disabled={loading}>Submit</Button>
|
||||
</Dialog.Footer>
|
||||
</form>
|
||||
</Dialog.Content>
|
||||
</Dialog.Root>
|
||||
```
|
||||
|
||||
### React (with shadcn/ui)
|
||||
|
||||
```tsx
|
||||
// src/components/BugReportModal.tsx
|
||||
|
||||
import { useState } from 'react';
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from '@/components/ui/dialog';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import { Bug } from 'lucide-react';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
interface Props {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export function BugReportModal({ open, onClose }: Props) {
|
||||
const [title, setTitle] = useState('');
|
||||
const [description, setDescription] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const detectPlatform = () => {
|
||||
const ua = navigator.userAgent.toLowerCase();
|
||||
if (ua.includes('iphone') || ua.includes('ipad')) return 'iOS';
|
||||
if (ua.includes('android')) return 'Android';
|
||||
if (ua.includes('mac')) return 'macOS';
|
||||
if (ua.includes('win')) return 'Windows';
|
||||
return 'Unknown';
|
||||
};
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setLoading(true);
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/bug-reports', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
title,
|
||||
description,
|
||||
pageUrl: window.location.href,
|
||||
userAgent: navigator.userAgent,
|
||||
platform: detectPlatform()
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) throw new Error('Failed to submit');
|
||||
toast.success('Bug report submitted');
|
||||
onClose();
|
||||
} catch {
|
||||
toast.error('Failed to submit bug report');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={(o) => !o && onClose()}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<Bug className="h-5 w-5" />
|
||||
Report a Bug
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
<Input value={title} onChange={(e) => setTitle(e.target.value)} placeholder="Brief summary" />
|
||||
<Textarea value={description} onChange={(e) => setDescription(e.target.value)} placeholder="What happened?" />
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={onClose} disabled={loading}>Cancel</Button>
|
||||
<Button type="submit" disabled={loading}>Submit</Button>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## 5. Workflow Configuration
|
||||
|
||||
Update your project's `.bmad/bmm/config.yaml` to set the `project_url`:
|
||||
|
||||
```yaml
|
||||
# .bmad/bmm/config.yaml
|
||||
|
||||
project_url: "http://localhost:5173" # Dev
|
||||
# project_url: "https://your-app.com" # Prod
|
||||
```
|
||||
|
||||
The triage workflow will use this to call your API endpoints.
|
||||
|
||||
## 6. API Response Format
|
||||
|
||||
The workflow expects these response formats:
|
||||
|
||||
### GET /api/bug-reports/pending
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"reports": [
|
||||
{
|
||||
"id": "uuid",
|
||||
"title": "Bug title",
|
||||
"description": "Bug description",
|
||||
"reporterType": "staff",
|
||||
"reporterName": "John Doe",
|
||||
"platform": "macOS",
|
||||
"browser": "Chrome",
|
||||
"pageUrl": "https://...",
|
||||
"screenshotUrl": "https://...",
|
||||
"createdAt": "2025-01-01T00:00:00Z"
|
||||
}
|
||||
],
|
||||
"count": 1
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### POST /api/bug-reports/mark-synced
|
||||
|
||||
Request:
|
||||
```json
|
||||
{ "ids": ["uuid1", "uuid2"] }
|
||||
```
|
||||
|
||||
Response:
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"updatedCount": 2,
|
||||
"updatedIds": ["uuid1", "uuid2"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 7. Optional: Screenshot Storage
|
||||
|
||||
For screenshot uploads, you'll need cloud storage (R2, S3, etc.):
|
||||
|
||||
1. Create an upload endpoint: `POST /api/bug-reports/[id]/upload-screenshot`
|
||||
2. Upload to cloud storage
|
||||
3. Update `screenshotUrl` on the bug report record
|
||||
|
||||
## 8. Security Considerations
|
||||
|
||||
- **Authentication**: Create endpoint should require auth
|
||||
- **API Key**: Consider adding API key auth for pending/mark-synced endpoints in production
|
||||
- **Rate Limiting**: Add rate limits to prevent spam
|
||||
- **Input Sanitization**: Validate all user input (handled by Zod schemas)
|
||||
|
||||
## Without In-App Reporting
|
||||
|
||||
If you don't implement in-app reporting, the workflow still works:
|
||||
|
||||
1. Users manually add bugs to `docs/bugs.md` under `# manual input`
|
||||
2. Run `/triage` to process them
|
||||
3. Workflow skips Step 0 (API sync) when no API is available
|
||||
|
||||
The workflows are designed to be flexible and work with or without the in-app feature.
|
||||
|
|
@ -0,0 +1,167 @@
|
|||
# Step 1: Bug Tracking Workflow Initialization
|
||||
|
||||
## MANDATORY EXECUTION RULES (READ FIRST):
|
||||
|
||||
- 🛑 NEVER generate content without user input
|
||||
- 📖 CRITICAL: ALWAYS read the complete step file before taking any action - partial understanding leads to incomplete triage
|
||||
- 🔄 CRITICAL: When loading next step with 'C', ensure the entire file is read and understood before proceeding
|
||||
- ✅ ALWAYS treat this as collaborative triage between peers
|
||||
- 📋 YOU ARE A FACILITATOR, not an automatic processor
|
||||
- 💬 FOCUS on initialization and setup only - don't look ahead to future steps
|
||||
- 🚪 DETECT existing workflow state and handle continuation properly
|
||||
- ✅ YOU MUST ALWAYS SPEAK OUTPUT In your Agent communication style with the config `{communication_language}`
|
||||
|
||||
## EXECUTION PROTOCOLS:
|
||||
|
||||
- 🎯 Show your analysis before taking any action
|
||||
- 💾 Initialize bugs.yaml if needed
|
||||
- 📖 Track workflow state for potential continuation
|
||||
- 🚫 FORBIDDEN to load next step until setup is complete
|
||||
|
||||
## CONTEXT BOUNDARIES:
|
||||
|
||||
- Variables from workflow.md are available in memory
|
||||
- bugs.yaml tracks all structured bug metadata
|
||||
- bugs.md is the user-facing input file
|
||||
- Don't assume knowledge from other steps
|
||||
|
||||
## YOUR TASK:
|
||||
|
||||
Initialize the Bug Tracking workflow by detecting existing state, discovering input files, and setting up for collaborative triage.
|
||||
|
||||
## INITIALIZATION SEQUENCE:
|
||||
|
||||
### 1. Check for Existing Session
|
||||
|
||||
First, check workflow state:
|
||||
|
||||
- Look for existing `{bugs_output}` (bugs.yaml)
|
||||
- If exists, grep for bugs with `status: triaged` (pending implementation)
|
||||
- Check `{bugs_input}` (bugs.md) for items in "# manual input" section
|
||||
|
||||
### 2. Handle Continuation (If Pending Work Exists)
|
||||
|
||||
If bugs.yaml exists with triaged bugs awaiting action:
|
||||
|
||||
- **STOP here** and load `./step-01b-continue.md` immediately
|
||||
- Do not proceed with fresh initialization
|
||||
- Let step-01b handle the continuation logic
|
||||
|
||||
### 3. Fresh Workflow Setup (If No Pending Work)
|
||||
|
||||
If no bugs.yaml exists OR no pending triaged bugs:
|
||||
|
||||
#### A. Input File Discovery
|
||||
|
||||
Discover and validate required files:
|
||||
|
||||
**Required Files:**
|
||||
- `{bugs_input}` (bugs.md) - User-facing bug reports
|
||||
- Must have "# manual input" section for new bugs
|
||||
- May have "# Tracked Bugs" and "# Fixed Bugs" sections
|
||||
|
||||
**Optional Context Files:**
|
||||
- `{sprint_status}` - Current sprint context (which stories are in progress)
|
||||
- `{epics_file}` - For mapping bugs to related stories/epics
|
||||
|
||||
#### B. Initialize bugs.yaml (If Not Exists)
|
||||
|
||||
If bugs.yaml doesn't exist, create it with header structure:
|
||||
|
||||
```yaml
|
||||
# Bug Tracking Database
|
||||
# Generated by bug-tracking workflow
|
||||
# Last updated: {date}
|
||||
|
||||
# Severity Definitions:
|
||||
# - critical: Prevents core functionality, crashes, data loss
|
||||
# - high: Blocks major features, significantly degrades UX
|
||||
# - medium: Affects subset of users, minor impact with workaround
|
||||
# - low: Cosmetic, edge case, or minor inconvenience
|
||||
|
||||
# Complexity Definitions:
|
||||
# - trivial: One-line fix, obvious solution
|
||||
# - small: Single file/component, solution clear
|
||||
# - medium: Multiple files OR requires investigation
|
||||
# - complex: Architectural change, affects many areas
|
||||
|
||||
# Workflow Routing Matrix:
|
||||
# - critical + any → correct-course
|
||||
# - high + trivial → direct-fix
|
||||
# - high + small → tech-spec
|
||||
# - high + medium/complex → correct-course
|
||||
# - medium + trivial → direct-fix
|
||||
# - medium + small → tech-spec
|
||||
# - medium + medium/complex → correct-course
|
||||
# - low + trivial → direct-fix
|
||||
# - low + small/medium/complex → backlog
|
||||
|
||||
bugs: []
|
||||
features: []
|
||||
closed_bugs: []
|
||||
|
||||
statistics:
|
||||
total_active: 0
|
||||
by_severity:
|
||||
critical: 0
|
||||
high: 0
|
||||
medium: 0
|
||||
low: 0
|
||||
last_updated: {date}
|
||||
```
|
||||
|
||||
#### C. Scan for New Bugs
|
||||
|
||||
Read ONLY the "# manual input" section from bugs.md:
|
||||
- Grep for "# manual input" to find starting line
|
||||
- Grep for next section header to find ending line
|
||||
- Read just that range (do NOT read entire file)
|
||||
|
||||
Count items found in manual input section.
|
||||
|
||||
#### D. Complete Initialization and Report
|
||||
|
||||
Report to user:
|
||||
|
||||
"Welcome {user_name}! I've initialized the Bug Tracking workspace for {project_name}.
|
||||
|
||||
**Files Status:**
|
||||
- bugs.md: {found/created} - {count} item(s) in manual input section
|
||||
- bugs.yaml: {found/created} - {active_count} active bugs tracked
|
||||
|
||||
**Context Files:**
|
||||
- Sprint Status: {loaded/not found}
|
||||
- Epics: {loaded/not found}
|
||||
|
||||
**Ready for Triage:**
|
||||
{count} new item(s) found in manual input section.
|
||||
|
||||
[S] Sync bug reports from API first (if app integration configured)
|
||||
[C] Continue to parse and triage bugs
|
||||
[Q] Quit - no new bugs to triage"
|
||||
|
||||
## SUCCESS METRICS:
|
||||
|
||||
✅ Existing workflow detected and handed off to step-01b correctly
|
||||
✅ Fresh workflow initialized with bugs.yaml structure
|
||||
✅ Input files discovered and validated
|
||||
✅ Manual input section scanned for new items
|
||||
✅ User informed of status and can proceed
|
||||
|
||||
## FAILURE MODES:
|
||||
|
||||
❌ Proceeding with fresh initialization when pending work exists
|
||||
❌ Not creating bugs.yaml with proper header/definitions
|
||||
❌ Reading entire bugs.md instead of just manual input section
|
||||
❌ Not reporting status to user before proceeding
|
||||
|
||||
❌ **CRITICAL**: Reading only partial step file - leads to incomplete understanding
|
||||
❌ **CRITICAL**: Proceeding with 'C' without fully reading the next step file
|
||||
|
||||
## NEXT STEP:
|
||||
|
||||
- If user selects [S], load `./step-02-sync.md` to sync from API
|
||||
- If user selects [C], load `./step-03-parse.md` to parse and identify new bugs
|
||||
- If user selects [Q], end workflow gracefully
|
||||
|
||||
Remember: Do NOT proceed until user explicitly selects an option from the menu!
|
||||
|
|
@ -0,0 +1,110 @@
|
|||
# Step 1b: Continue Existing Bug Tracking Session
|
||||
|
||||
## MANDATORY EXECUTION RULES (READ FIRST):
|
||||
|
||||
- 🛑 NEVER generate content without user input
|
||||
- 📖 CRITICAL: ALWAYS read the complete step file before taking any action
|
||||
- 🔄 CRITICAL: When loading next step with 'C', ensure the entire file is read and understood before proceeding
|
||||
- ✅ ALWAYS treat this as collaborative triage between peers
|
||||
- 📋 YOU ARE A FACILITATOR, not an automatic processor
|
||||
- 🚪 This step handles CONTINUATION of existing work
|
||||
- ✅ YOU MUST ALWAYS SPEAK OUTPUT In your Agent communication style with the config `{communication_language}`
|
||||
|
||||
## EXECUTION PROTOCOLS:
|
||||
|
||||
- 🎯 Summarize existing state before offering options
|
||||
- 💾 Preserve all existing bugs.yaml data
|
||||
- 📖 Help user understand where they left off
|
||||
- 🚫 FORBIDDEN to lose or overwrite existing triage work
|
||||
|
||||
## CONTEXT BOUNDARIES:
|
||||
|
||||
- Variables from workflow.md are available in memory
|
||||
- bugs.yaml contains existing structured data
|
||||
- User may have triaged bugs awaiting implementation
|
||||
- Don't re-triage already processed bugs
|
||||
|
||||
## YOUR TASK:
|
||||
|
||||
Welcome user back and summarize the current state of bug tracking, offering relevant continuation options.
|
||||
|
||||
## CONTINUATION SEQUENCE:
|
||||
|
||||
### 1. Load Current State
|
||||
|
||||
Read bugs.yaml and extract:
|
||||
- Total active bugs count
|
||||
- Bugs by status (triaged, implemented, verified)
|
||||
- Bugs by severity breakdown
|
||||
- Bugs by recommended workflow
|
||||
|
||||
### 2. Check for New Input
|
||||
|
||||
Scan "# manual input" section of bugs.md:
|
||||
- Count items not yet in bugs.yaml
|
||||
- These are new bugs needing triage
|
||||
|
||||
### 3. Present Continuation Summary
|
||||
|
||||
Report to user:
|
||||
|
||||
"Welcome back, {user_name}! Here's your Bug Tracking status for {project_name}.
|
||||
|
||||
**Current State:**
|
||||
- Active Bugs: {total_active}
|
||||
- Triaged (awaiting action): {triaged_count}
|
||||
- Implemented (awaiting verification): {implemented_count}
|
||||
- By Severity: Critical: {critical} | High: {high} | Medium: {medium} | Low: {low}
|
||||
|
||||
**Workflow Routing:**
|
||||
- Direct Fix: {direct_fix_count} bug(s)
|
||||
- Tech-Spec: {tech_spec_count} bug(s)
|
||||
- Correct-Course: {correct_course_count} bug(s)
|
||||
- Backlog: {backlog_count} bug(s)
|
||||
|
||||
**New Items:**
|
||||
- {new_count} new item(s) found in manual input section
|
||||
|
||||
**Options:**
|
||||
[T] Triage new bugs ({new_count} items)
|
||||
[I] Implement a bug - `/implement bug-NNN`
|
||||
[V] Verify implemented bugs - `/verify`
|
||||
[L] List bugs by status/severity
|
||||
[Q] Quit"
|
||||
|
||||
### 4. Handle User Selection
|
||||
|
||||
Based on user choice:
|
||||
|
||||
- **[T] Triage**: Load `./step-03-parse.md` to process new bugs
|
||||
- **[I] Implement**: Guide user to run `/implement bug-NNN` skill
|
||||
- **[V] Verify**: Guide user to run `/verify` skill
|
||||
- **[L] List**: Show filtered bug list, then return to menu
|
||||
- **[Q] Quit**: End workflow gracefully
|
||||
|
||||
## SUCCESS METRICS:
|
||||
|
||||
✅ Existing state accurately summarized
|
||||
✅ New items detected and counted
|
||||
✅ User given clear options based on current state
|
||||
✅ Appropriate next step loaded based on selection
|
||||
|
||||
## FAILURE MODES:
|
||||
|
||||
❌ Losing track of existing triaged bugs
|
||||
❌ Re-triaging already processed bugs
|
||||
❌ Not detecting new items in manual input
|
||||
❌ Proceeding without user selection
|
||||
|
||||
❌ **CRITICAL**: Reading only partial step file
|
||||
❌ **CRITICAL**: Proceeding without explicit user menu selection
|
||||
|
||||
## NEXT STEP:
|
||||
|
||||
Load appropriate step based on user selection:
|
||||
- [T] → `./step-03-parse.md`
|
||||
- [I], [V] → Guide to relevant skill, then return here
|
||||
- [L] → Display list, return to this menu
|
||||
- [Q] → End workflow
|
||||
|
||||
Remember: Do NOT proceed until user explicitly selects an option!
|
||||
|
|
@ -0,0 +1,145 @@
|
|||
# Step 2: Sync Bug Reports from API
|
||||
|
||||
## MANDATORY EXECUTION RULES (READ FIRST):
|
||||
|
||||
- 🛑 NEVER generate content without user input
|
||||
- 📖 CRITICAL: ALWAYS read the complete step file before taking any action
|
||||
- 🔄 CRITICAL: When loading next step with 'C', ensure the entire file is read and understood before proceeding
|
||||
- ✅ ALWAYS treat this as collaborative triage between peers
|
||||
- 📋 YOU ARE A FACILITATOR, not an automatic processor
|
||||
- 🌐 This step handles OPTIONAL API integration for in-app bug reporting
|
||||
- ✅ YOU MUST ALWAYS SPEAK OUTPUT In your Agent communication style with the config `{communication_language}`
|
||||
|
||||
## EXECUTION PROTOCOLS:
|
||||
|
||||
- 🎯 Attempt API sync only if configured
|
||||
- 💾 Preserve existing manual input entries
|
||||
- 📖 Format synced reports as markdown entries
|
||||
- 🚫 FORBIDDEN to lose manually entered bugs
|
||||
|
||||
## CONTEXT BOUNDARIES:
|
||||
|
||||
- Variables from workflow.md are available in memory
|
||||
- `project_url` may or may not be configured
|
||||
- API endpoints are optional - gracefully handle if unavailable
|
||||
- This step can be skipped if no API integration
|
||||
|
||||
## YOUR TASK:
|
||||
|
||||
Sync pending bug reports from the application's PostgreSQL database via API, formatting them as markdown entries in bugs.md.
|
||||
|
||||
## SYNC SEQUENCE:
|
||||
|
||||
### 1. Check API Configuration
|
||||
|
||||
Verify `{project_url}` is configured:
|
||||
- If not configured or user skipped this step, proceed to step-03
|
||||
- If configured, attempt API connection
|
||||
|
||||
### 2. Fetch Pending Reports
|
||||
|
||||
**API Call:**
|
||||
```
|
||||
GET {project_url}/api/bug-reports/pending
|
||||
```
|
||||
|
||||
**Expected Response:**
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"reports": [...],
|
||||
"count": number
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Report Fields:**
|
||||
- `id` - Database ID
|
||||
- `title` - Bug title
|
||||
- `description` - Bug description
|
||||
- `reporterType` - Type of reporter (user, staff, admin)
|
||||
- `reporterName` - Name of reporter
|
||||
- `platform` - Platform (iOS, Android, web)
|
||||
- `browser` - Browser if web
|
||||
- `pageUrl` - URL where bug occurred
|
||||
- `screenshotUrl` - Optional screenshot
|
||||
- `createdAt` - Timestamp
|
||||
|
||||
### 3. Handle No Reports
|
||||
|
||||
If count == 0:
|
||||
|
||||
"No new bug reports from the application API.
|
||||
|
||||
[C] Continue to triage existing manual input
|
||||
[Q] Quit - nothing to process"
|
||||
|
||||
### 4. Format Reports as Markdown
|
||||
|
||||
For each report, create markdown entry:
|
||||
|
||||
```markdown
|
||||
## Bug: {title}
|
||||
|
||||
{description}
|
||||
|
||||
Reported by: {reporterName} ({reporterType})
|
||||
Date: {createdAt formatted as YYYY-MM-DD}
|
||||
Platform: {platform} / {browser}
|
||||
Page: {pageUrl}
|
||||
{if screenshotUrl: Screenshot: {screenshotUrl}}
|
||||
```
|
||||
|
||||
### 5. Insert into bugs.md
|
||||
|
||||
- Read the "# manual input" section location from bugs.md
|
||||
- Insert new markdown entries after the "# manual input" header
|
||||
- Preserve any existing manual input entries
|
||||
- Write updated bugs.md
|
||||
|
||||
### 6. Mark Reports as Synced
|
||||
|
||||
**API Call:**
|
||||
```
|
||||
POST {project_url}/api/bug-reports/mark-synced
|
||||
Body: { "ids": [array of synced report IDs] }
|
||||
```
|
||||
|
||||
This updates status to 'synced' so reports won't be fetched again.
|
||||
|
||||
### 7. Report Sync Results
|
||||
|
||||
"**Synced {count} bug report(s) from application:**
|
||||
|
||||
{for each report:}
|
||||
- {title} (from {reporterName})
|
||||
{end for}
|
||||
|
||||
These have been added to the manual input section of bugs.md.
|
||||
|
||||
[C] Continue to parse and triage all bugs
|
||||
[Q] Quit"
|
||||
|
||||
## SUCCESS METRICS:
|
||||
|
||||
✅ API availability checked gracefully
|
||||
✅ Pending reports fetched and formatted
|
||||
✅ Existing manual entries preserved
|
||||
✅ Reports marked as synced in database
|
||||
✅ User informed of sync results
|
||||
|
||||
## FAILURE MODES:
|
||||
|
||||
❌ Crashing if API unavailable (should gracefully skip)
|
||||
❌ Overwriting existing manual input entries
|
||||
❌ Not marking reports as synced (causes duplicates)
|
||||
❌ Proceeding without user confirmation
|
||||
|
||||
❌ **CRITICAL**: Reading only partial step file
|
||||
❌ **CRITICAL**: Proceeding without explicit user selection
|
||||
|
||||
## NEXT STEP:
|
||||
|
||||
After user selects [C], load `./step-03-parse.md` to parse and identify all bugs needing triage.
|
||||
|
||||
Remember: Do NOT proceed until user explicitly selects [C] from the menu!
|
||||
|
|
@ -0,0 +1,164 @@
|
|||
# Step 3: Parse and Identify New Bugs
|
||||
|
||||
## MANDATORY EXECUTION RULES (READ FIRST):
|
||||
|
||||
- 🛑 NEVER generate content without user input
|
||||
- 📖 CRITICAL: ALWAYS read the complete step file before taking any action
|
||||
- 🔄 CRITICAL: When loading next step with 'C', ensure the entire file is read and understood before proceeding
|
||||
- ✅ ALWAYS treat this as collaborative triage between peers
|
||||
- 📋 YOU ARE A FACILITATOR, not an automatic processor
|
||||
- 🔍 This step PARSES input only - triage happens in next step
|
||||
- ✅ YOU MUST ALWAYS SPEAK OUTPUT In your Agent communication style with the config `{communication_language}`
|
||||
|
||||
## EXECUTION PROTOCOLS:
|
||||
|
||||
- 🎯 Parse manual input section thoroughly
|
||||
- 💾 Compare against existing bugs.yaml entries
|
||||
- 📖 Extract all available information from informal reports
|
||||
- 🚫 FORBIDDEN to start triage in this step - parsing only
|
||||
|
||||
## CONTEXT BOUNDARIES:
|
||||
|
||||
- Variables from workflow.md are available in memory
|
||||
- bugs.yaml contains existing triaged bugs
|
||||
- Only parse "# manual input" section of bugs.md
|
||||
- Do NOT read entire bugs.md file
|
||||
|
||||
## YOUR TASK:
|
||||
|
||||
Parse the "# manual input" section of bugs.md, extract bug information, and identify which items need triage.
|
||||
|
||||
## PARSE SEQUENCE:
|
||||
|
||||
### 1. Read Manual Input Section
|
||||
|
||||
Section-based reading of bugs.md:
|
||||
- Grep for "# manual input" to find starting line number
|
||||
- Grep for next section header ("# Tracked Bugs", "# Tracked Feature Requests", "# Fixed Bugs") to find ending line
|
||||
- Read just that range using offset/limit (do NOT read entire file)
|
||||
- If no closing section found within initial window, expand read range and retry
|
||||
|
||||
### 2. Search Existing IDs in bugs.yaml
|
||||
|
||||
Do NOT read entire bugs.yaml file:
|
||||
- Grep for `id: bug-[0-9]+` pattern to find all existing bug IDs
|
||||
- Grep for `id: feature-[0-9]+` pattern to find all existing feature IDs
|
||||
- This enables duplicate detection and next-ID generation
|
||||
|
||||
### 3. Parse Bug Reports
|
||||
|
||||
Expected formats in manual input (informal, user-written):
|
||||
|
||||
**Format A: Markdown Headers**
|
||||
```markdown
|
||||
## Bug: Title Here
|
||||
|
||||
Description text, possibly multi-paragraph.
|
||||
|
||||
Reported by: Name
|
||||
Date: YYYY-MM-DD
|
||||
Related: Story 2.7
|
||||
Platform: iOS
|
||||
```
|
||||
|
||||
**Format B: Bullet Lists**
|
||||
```markdown
|
||||
- **Title (Platform)**: Description text. CRITICAL if urgent.
|
||||
```
|
||||
|
||||
**Format C: Numbered Lists**
|
||||
```markdown
|
||||
1. Title - Description text
|
||||
2. Another bug - More description
|
||||
```
|
||||
|
||||
### 4. Extract Information
|
||||
|
||||
For each bug report, extract:
|
||||
|
||||
| Field | Required | Notes |
|
||||
|-------|----------|-------|
|
||||
| Title | Yes | First line or header |
|
||||
| Description | Yes | May be multi-paragraph |
|
||||
| Reported by | No | Extract if mentioned |
|
||||
| Date | No | Extract if mentioned |
|
||||
| Related story | No | e.g., "2-7", "Story 2.7" |
|
||||
| Platform | No | iOS, Android, web, all |
|
||||
| Reproduction steps | No | If provided |
|
||||
| Severity hints | No | "CRITICAL", "urgent", etc. |
|
||||
|
||||
### 5. Categorize Items
|
||||
|
||||
Compare extracted bugs with existing bugs.yaml:
|
||||
|
||||
- **New bugs**: Not in bugs.yaml yet (need full triage)
|
||||
- **Updated bugs**: In bugs.yaml but description changed (need re-triage)
|
||||
- **Feature requests**: Items that are enhancements, not bugs
|
||||
- **Unchanged**: Already triaged, skip
|
||||
|
||||
### 6. Handle No New Bugs
|
||||
|
||||
If NO new bugs found:
|
||||
|
||||
"No new bugs found in the manual input section.
|
||||
|
||||
All items have already been triaged and are tracked in bugs.yaml.
|
||||
|
||||
**Options:**
|
||||
1. Add new bugs to docs/bugs.md (informal format)
|
||||
2. View bugs.yaml to see structured bug tracking
|
||||
3. Route existing triaged bugs to workflows
|
||||
|
||||
[Q] Quit - nothing to triage"
|
||||
|
||||
**HALT** - Do not proceed.
|
||||
|
||||
### 7. Present Parsed Items
|
||||
|
||||
"**Parsed {total_count} item(s) from manual input:**
|
||||
|
||||
**New Bugs ({new_count}):**
|
||||
{for each new bug:}
|
||||
- {extracted_title}
|
||||
- Description: {first 100 chars}...
|
||||
- Platform: {platform or "not specified"}
|
||||
- Related: {story or "not specified"}
|
||||
{end for}
|
||||
|
||||
**Feature Requests ({feature_count}):**
|
||||
{for each feature:}
|
||||
- {title}
|
||||
{end for}
|
||||
|
||||
**Already Triaged ({unchanged_count}):**
|
||||
{list titles of skipped items}
|
||||
|
||||
Ready to triage {new_count} new bug(s) and {feature_count} feature request(s).
|
||||
|
||||
[C] Continue to triage
|
||||
[E] Edit - re-parse with corrections
|
||||
[Q] Quit"
|
||||
|
||||
## SUCCESS METRICS:
|
||||
|
||||
✅ Manual input section read efficiently (not entire file)
|
||||
✅ All formats parsed correctly (headers, bullets, numbered)
|
||||
✅ Existing bugs detected to prevent duplicates
|
||||
✅ New vs updated vs unchanged correctly categorized
|
||||
✅ User shown summary and can proceed
|
||||
|
||||
## FAILURE MODES:
|
||||
|
||||
❌ Reading entire bugs.md instead of section
|
||||
❌ Missing bugs due to format not recognized
|
||||
❌ Not detecting duplicates against bugs.yaml
|
||||
❌ Starting triage in this step (should only parse)
|
||||
|
||||
❌ **CRITICAL**: Reading only partial step file
|
||||
❌ **CRITICAL**: Proceeding without user selection
|
||||
|
||||
## NEXT STEP:
|
||||
|
||||
After user selects [C], load `./step-04-triage.md` to perform triage analysis on each new bug.
|
||||
|
||||
Remember: Do NOT proceed until user explicitly selects [C] from the menu!
|
||||
|
|
@ -0,0 +1,212 @@
|
|||
# Step 4: Triage Each Bug
|
||||
|
||||
## MANDATORY EXECUTION RULES (READ FIRST):
|
||||
|
||||
- 🛑 NEVER generate content without user input
|
||||
- 📖 CRITICAL: ALWAYS read the complete step file before taking any action
|
||||
- 🔄 CRITICAL: When loading next step with 'C', ensure the entire file is read and understood before proceeding
|
||||
- ✅ ALWAYS treat this as collaborative triage between peers
|
||||
- 📋 YOU ARE A FACILITATOR - ask clarifying questions when needed
|
||||
- 🎯 This step performs the CORE TRIAGE analysis
|
||||
- ⚠️ ABSOLUTELY NO TIME ESTIMATES - AI development speed varies widely
|
||||
- ✅ YOU MUST ALWAYS SPEAK OUTPUT In your Agent communication style with the config `{communication_language}`
|
||||
|
||||
## EXECUTION PROTOCOLS:
|
||||
|
||||
- 🎯 Triage ONE bug at a time with user confirmation
|
||||
- 💾 Track triage decisions for bugs.yaml update
|
||||
- 📖 Ask clarifying questions when severity/complexity unclear
|
||||
- 🚫 FORBIDDEN to auto-triage without user review
|
||||
|
||||
## CONTEXT BOUNDARIES:
|
||||
|
||||
- Parsed bugs from step-03 are in memory
|
||||
- Reference bugs.yaml header for severity/complexity definitions
|
||||
- Reference epics.md for story mapping
|
||||
- Each bug gets full triage analysis
|
||||
|
||||
## YOUR TASK:
|
||||
|
||||
Perform collaborative triage analysis on each parsed bug, assessing severity, complexity, effort, workflow routing, and documentation impact.
|
||||
|
||||
## TRIAGE SEQUENCE (FOR EACH BUG):
|
||||
|
||||
### 1. Generate Bug ID
|
||||
|
||||
- Find highest existing bug-NNN from step-03 grep results
|
||||
- Assign next sequential ID (e.g., bug-006)
|
||||
- Format: `bug-` + zero-padded 3-digit number
|
||||
- For features: `feature-` + zero-padded 3-digit number
|
||||
|
||||
### 2. Assess Severity
|
||||
|
||||
**Severity Levels:**
|
||||
| Level | Criteria |
|
||||
|-------|----------|
|
||||
| critical | Prevents core functionality, crashes, data loss |
|
||||
| high | Blocks major features, significantly degrades UX but has workaround |
|
||||
| medium | Affects subset of users, minor impact |
|
||||
| low | Cosmetic, edge case, minor inconvenience |
|
||||
|
||||
**Analysis Questions:**
|
||||
- Does it prevent core functionality? → critical
|
||||
- Does it cause crashes or data loss? → critical
|
||||
- Does it block major features? → high
|
||||
- Does it significantly degrade UX but have workaround? → high
|
||||
- Does it affect subset of users with minor impact? → medium
|
||||
- Is it cosmetic or edge case? → low
|
||||
|
||||
**If Unclear - ASK:**
|
||||
"**Clarification needed for: {bug_title}**
|
||||
|
||||
I need more information to assess severity:
|
||||
1. Does this bug prevent users from completing core flows?
|
||||
2. Does the bug cause crashes or data loss?
|
||||
3. How many users are affected? (all users, specific platform, edge case)
|
||||
4. Is there a workaround available?
|
||||
|
||||
Please provide additional context."
|
||||
|
||||
### 3. Assess Complexity
|
||||
|
||||
**Complexity Levels:**
|
||||
| Level | Criteria |
|
||||
|-------|----------|
|
||||
| trivial | One-line fix, obvious solution |
|
||||
| small | Single file/component, solution clear |
|
||||
| medium | Multiple files OR requires investigation |
|
||||
| complex | Architectural change, affects many areas |
|
||||
|
||||
**If Unclear - ASK:**
|
||||
"**Clarification needed for: {bug_title}**
|
||||
|
||||
To estimate complexity, I need:
|
||||
1. Have you identified the root cause, or does it need investigation?
|
||||
2. Which file(s) or component(s) are affected?
|
||||
3. Is this isolated or does it affect multiple parts of the app?
|
||||
|
||||
Please provide technical details if available."
|
||||
|
||||
### 4. Determine Workflow Routing
|
||||
|
||||
**Routing Matrix:**
|
||||
| Severity | Complexity | Workflow |
|
||||
|----------|------------|----------|
|
||||
| critical | any | correct-course |
|
||||
| high | trivial | direct-fix |
|
||||
| high | small | tech-spec |
|
||||
| high | medium/complex | correct-course |
|
||||
| medium | trivial | direct-fix |
|
||||
| medium | small | tech-spec |
|
||||
| medium | medium/complex | correct-course |
|
||||
| low | trivial | direct-fix |
|
||||
| low | small+ | backlog |
|
||||
|
||||
### 5. Map to Related Story/Epic
|
||||
|
||||
- If bug mentions story ID (e.g., "2-7"), use that
|
||||
- Otherwise, infer from description using epic keywords
|
||||
- Reference epics.md for story matching
|
||||
- Format: `{epic_number}-{story_number}` or null
|
||||
|
||||
### 6. Determine Affected Platform
|
||||
|
||||
Extract from description:
|
||||
- `all` - Default if not specified
|
||||
- `ios` - iOS only
|
||||
- `android` - Android only
|
||||
- `web` - Web only
|
||||
|
||||
### 7. Assess Documentation Impact
|
||||
|
||||
**PRD Impact** (`doc_impact.prd: true/false`)
|
||||
Set TRUE if issue:
|
||||
- Conflicts with stated product goals
|
||||
- Requires changing MVP scope
|
||||
- Adds/removes/modifies core functionality
|
||||
- Changes success metrics
|
||||
- Affects multiple epics
|
||||
|
||||
**Architecture Impact** (`doc_impact.architecture: true/false`)
|
||||
Set TRUE if issue:
|
||||
- Requires new system components
|
||||
- Changes data model (new tables, schema)
|
||||
- Affects API contracts
|
||||
- Introduces new dependencies
|
||||
- Changes auth/security model
|
||||
|
||||
**UX Impact** (`doc_impact.ux: true/false`)
|
||||
Set TRUE if issue:
|
||||
- Adds new screens or navigation
|
||||
- Changes existing user flows
|
||||
- Requires new UI components
|
||||
- Affects accessibility
|
||||
|
||||
**If any doc_impact is TRUE AND workflow != correct-course:**
|
||||
- Override workflow to `correct-course`
|
||||
- Add note: "Workflow elevated due to documentation impact"
|
||||
|
||||
### 8. Add Triage Notes
|
||||
|
||||
Document reasoning:
|
||||
- Why this severity? (business impact, user impact)
|
||||
- Why this complexity? (investigation needed, files affected)
|
||||
- Why this workflow? (routing logic applied)
|
||||
- Suggested next steps or investigation areas
|
||||
|
||||
### 9. Present Triage for Confirmation
|
||||
|
||||
"**Triage: {bug_id} - {bug_title}**
|
||||
|
||||
| Field | Value |
|
||||
|-------|-------|
|
||||
| Severity | {severity} |
|
||||
| Complexity | {complexity} |
|
||||
| Platform | {platform} |
|
||||
| Workflow | {recommended_workflow} |
|
||||
| Related | {related_story or 'None'} |
|
||||
|
||||
**Documentation Impact:**
|
||||
- PRD: {yes/no}
|
||||
- Architecture: {yes/no}
|
||||
- UX: {yes/no}
|
||||
|
||||
**Triage Notes:**
|
||||
{triage_notes}
|
||||
|
||||
[A] Accept triage
|
||||
[M] Modify - adjust severity/complexity/workflow
|
||||
[S] Skip - don't triage this item now
|
||||
[N] Next bug (after accepting)"
|
||||
|
||||
### 10. Handle Modifications
|
||||
|
||||
If user selects [M]:
|
||||
- Ask which field to modify
|
||||
- Accept new value
|
||||
- Re-present triage for confirmation
|
||||
|
||||
## SUCCESS METRICS:
|
||||
|
||||
✅ Each bug triaged with user confirmation
|
||||
✅ Unclear items prompted for clarification
|
||||
✅ Routing matrix applied correctly
|
||||
✅ Documentation impact assessed
|
||||
✅ Triage notes document reasoning
|
||||
|
||||
## FAILURE MODES:
|
||||
|
||||
❌ Auto-triaging without user review
|
||||
❌ Not asking clarifying questions when needed
|
||||
❌ Incorrect routing matrix application
|
||||
❌ Missing documentation impact assessment
|
||||
❌ Not documenting triage reasoning
|
||||
|
||||
❌ **CRITICAL**: Reading only partial step file
|
||||
❌ **CRITICAL**: Proceeding without user confirmation per bug
|
||||
|
||||
## NEXT STEP:
|
||||
|
||||
After ALL bugs triaged (user selected [A] or [N] for each), load `./step-05-update.md` to update bugs.yaml and bugs.md.
|
||||
|
||||
Remember: Triage each bug individually with user confirmation!
|
||||
|
|
@ -0,0 +1,200 @@
|
|||
# Step 5: Update Files with Triaged Metadata
|
||||
|
||||
## MANDATORY EXECUTION RULES (READ FIRST):
|
||||
|
||||
- 🛑 NEVER generate content without user input
|
||||
- 📖 CRITICAL: ALWAYS read the complete step file before taking any action
|
||||
- 🔄 CRITICAL: When loading next step with 'C', ensure the entire file is read and understood before proceeding
|
||||
- ✅ ALWAYS treat this as collaborative triage between peers
|
||||
- 📋 YOU ARE A FACILITATOR, not an automatic processor
|
||||
- 💾 This step WRITES the triage results to files
|
||||
- ✅ YOU MUST ALWAYS SPEAK OUTPUT In your Agent communication style with the config `{communication_language}`
|
||||
|
||||
## EXECUTION PROTOCOLS:
|
||||
|
||||
- 🎯 Update both bugs.yaml and bugs.md atomically
|
||||
- 💾 Preserve ALL existing data - append only
|
||||
- 📖 Move items from manual input to tracked sections
|
||||
- 🚫 FORBIDDEN to lose or corrupt existing data
|
||||
|
||||
## CONTEXT BOUNDARIES:
|
||||
|
||||
- Triage decisions from step-04 are in memory
|
||||
- bugs.yaml structure defined in step-01
|
||||
- bugs.md sections: manual input, Tracked Bugs, Tracked Feature Requests, Fixed Bugs
|
||||
- Preserve header comments and definitions
|
||||
|
||||
## YOUR TASK:
|
||||
|
||||
Write all triaged metadata to bugs.yaml and move triaged items from "# manual input" to appropriate tracked sections in bugs.md.
|
||||
|
||||
## UPDATE SEQUENCE:
|
||||
|
||||
### 1. Update bugs.yaml
|
||||
|
||||
#### A. Load Existing Structure
|
||||
|
||||
Read current bugs.yaml (if exists):
|
||||
- Preserve ALL header comments and definitions
|
||||
- Preserve existing `bugs:` array entries
|
||||
- Preserve existing `features:` array entries
|
||||
- Preserve existing `closed_bugs:` array
|
||||
|
||||
#### B. Add New Bug Entries
|
||||
|
||||
For each triaged bug, add to `bugs:` array:
|
||||
|
||||
```yaml
|
||||
- id: bug-NNN
|
||||
title: "Bug title"
|
||||
description: |
|
||||
Full description text
|
||||
Can be multi-line
|
||||
severity: critical|high|medium|low
|
||||
complexity: trivial|small|medium|complex
|
||||
affected_platform: all|ios|android|web
|
||||
recommended_workflow: direct-fix|tech-spec|correct-course|backlog
|
||||
related_story: "X-Y" or null
|
||||
status: triaged
|
||||
reported_by: "Name" or null
|
||||
reported_date: "YYYY-MM-DD" or null
|
||||
triaged_date: "{date}"
|
||||
doc_impact:
|
||||
prd: true|false
|
||||
architecture: true|false
|
||||
ux: true|false
|
||||
notes: "Impact description" or null
|
||||
triage_notes: |
|
||||
Reasoning for severity, complexity, workflow decisions
|
||||
implemented_by: null
|
||||
implemented_date: null
|
||||
verified_by: null
|
||||
verified_date: null
|
||||
```
|
||||
|
||||
#### C. Add Feature Request Entries
|
||||
|
||||
For features, add to `features:` array with similar structure.
|
||||
|
||||
#### D. Update Statistics
|
||||
|
||||
Recalculate statistics section:
|
||||
|
||||
```yaml
|
||||
statistics:
|
||||
total_active: {count of non-closed bugs}
|
||||
by_severity:
|
||||
critical: {count}
|
||||
high: {count}
|
||||
medium: {count}
|
||||
low: {count}
|
||||
by_status:
|
||||
triaged: {count}
|
||||
implemented: {count}
|
||||
verified: {count}
|
||||
by_workflow:
|
||||
direct-fix: {count}
|
||||
tech-spec: {count}
|
||||
correct-course: {count}
|
||||
backlog: {count}
|
||||
last_updated: "{date}"
|
||||
```
|
||||
|
||||
#### E. Write bugs.yaml
|
||||
|
||||
Write complete bugs.yaml file preserving all content.
|
||||
|
||||
### 2. Update bugs.md
|
||||
|
||||
#### A. Section-Based Reading
|
||||
|
||||
Use grep to locate section line numbers:
|
||||
- "# manual input"
|
||||
- "# Tracked Bugs"
|
||||
- "# Tracked Feature Requests"
|
||||
- "# Fixed Bugs"
|
||||
|
||||
Read only relevant sections with offset/limit.
|
||||
|
||||
#### B. Remove from Manual Input
|
||||
|
||||
For each triaged item:
|
||||
- Remove the original entry from "# manual input" section
|
||||
- Handle both header format and bullet format
|
||||
|
||||
#### C. Add to Tracked Bugs
|
||||
|
||||
For each triaged bug, add to "# Tracked Bugs" section:
|
||||
|
||||
```markdown
|
||||
### {bug_id}: {title}
|
||||
|
||||
{brief_description}
|
||||
|
||||
- **Severity:** {severity}
|
||||
- **Complexity:** {complexity}
|
||||
- **Platform:** {platform}
|
||||
- **Workflow:** {workflow}
|
||||
- **Related:** {story or "None"}
|
||||
{if doc_impact flagged:}
|
||||
- **Doc Impact:** {PRD|Architecture|UX as applicable}
|
||||
{end if}
|
||||
|
||||
**Notes:** {triage_notes_summary}
|
||||
|
||||
---
|
||||
```
|
||||
|
||||
Create "# Tracked Bugs" section if it doesn't exist.
|
||||
|
||||
#### D. Add to Tracked Feature Requests
|
||||
|
||||
For features, add to "# Tracked Feature Requests" section with similar format.
|
||||
|
||||
#### E. Write bugs.md
|
||||
|
||||
Write updated bugs.md preserving all sections.
|
||||
|
||||
### 3. Confirm Updates
|
||||
|
||||
"**Files Updated:**
|
||||
|
||||
**bugs.yaml:**
|
||||
- Added {bug_count} new bug(s)
|
||||
- Added {feature_count} new feature request(s)
|
||||
- Total active bugs: {total_active}
|
||||
- Statistics recalculated
|
||||
|
||||
**bugs.md:**
|
||||
- Removed {count} item(s) from manual input
|
||||
- Added {bug_count} bug(s) to Tracked Bugs section
|
||||
- Added {feature_count} feature(s) to Tracked Feature Requests section
|
||||
|
||||
[C] Continue to summary
|
||||
[R] Review changes - show diff
|
||||
[U] Undo - restore previous state"
|
||||
|
||||
## SUCCESS METRICS:
|
||||
|
||||
✅ bugs.yaml updated with all triaged metadata
|
||||
✅ bugs.md items moved from manual input to tracked sections
|
||||
✅ Statistics accurately recalculated
|
||||
✅ All existing data preserved
|
||||
✅ User confirmed updates
|
||||
|
||||
## FAILURE MODES:
|
||||
|
||||
❌ Losing existing bugs.yaml entries
|
||||
❌ Corrupting bugs.md structure
|
||||
❌ Items remaining in manual input after triage
|
||||
❌ Statistics not matching actual data
|
||||
❌ Not preserving header comments/definitions
|
||||
|
||||
❌ **CRITICAL**: Reading only partial step file
|
||||
❌ **CRITICAL**: Proceeding without user confirmation
|
||||
|
||||
## NEXT STEP:
|
||||
|
||||
After user selects [C], load `./step-06-complete.md` to present final triage summary.
|
||||
|
||||
Remember: Do NOT proceed until user explicitly selects [C] from the menu!
|
||||
|
|
@ -0,0 +1,180 @@
|
|||
# Step 6: Triage Complete - Summary and Next Steps
|
||||
|
||||
## MANDATORY EXECUTION RULES (READ FIRST):
|
||||
|
||||
- 🛑 NEVER generate content without user input
|
||||
- 📖 CRITICAL: ALWAYS read the complete step file before taking any action
|
||||
- ✅ ALWAYS treat this as collaborative triage between peers
|
||||
- 📋 YOU ARE A FACILITATOR, not an automatic processor
|
||||
- 🎉 This is the FINAL step - present comprehensive summary
|
||||
- ✅ YOU MUST ALWAYS SPEAK OUTPUT In your Agent communication style with the config `{communication_language}`
|
||||
|
||||
## EXECUTION PROTOCOLS:
|
||||
|
||||
- 🎯 Present comprehensive triage summary
|
||||
- 💾 All data already written in step-05
|
||||
- 📖 Guide user to next actions
|
||||
- 🚫 FORBIDDEN to modify files in this step
|
||||
|
||||
## CONTEXT BOUNDARIES:
|
||||
|
||||
- All triage decisions finalized in previous steps
|
||||
- bugs.yaml and bugs.md already updated
|
||||
- This step is READ-ONLY presentation
|
||||
- Focus on actionable next steps
|
||||
|
||||
## YOUR TASK:
|
||||
|
||||
Present a comprehensive summary of the triage session and guide the user to appropriate next actions based on workflow recommendations.
|
||||
|
||||
## COMPLETION SEQUENCE:
|
||||
|
||||
### 1. Present Triage Summary
|
||||
|
||||
"**Bug Triage Complete, {user_name}!**
|
||||
|
||||
---
|
||||
|
||||
## Triaged Items
|
||||
|
||||
{for each triaged bug:}
|
||||
|
||||
### {bug_id}: {bug_title}
|
||||
|
||||
| Field | Value |
|
||||
|-------|-------|
|
||||
| Severity | {severity} |
|
||||
| Complexity | {complexity} |
|
||||
| Platform | {platform} |
|
||||
| Workflow | {recommended_workflow} |
|
||||
| Related | {related_story or 'None'} |
|
||||
|
||||
{if doc_impact flagged:}
|
||||
**Documentation Impact:**
|
||||
- PRD: {yes/no}
|
||||
- Architecture: {yes/no}
|
||||
- UX: {yes/no}
|
||||
- Notes: {doc_impact_notes}
|
||||
{end if}
|
||||
|
||||
**Triage Reasoning:**
|
||||
{triage_notes}
|
||||
|
||||
---
|
||||
|
||||
{end for}
|
||||
|
||||
## Updated Files
|
||||
|
||||
- **bugs.yaml** - Structured metadata for all triaged items
|
||||
- **bugs.md** - Moved triaged items to Tracked sections
|
||||
|
||||
---
|
||||
|
||||
## Statistics Summary
|
||||
|
||||
| Metric | Count |
|
||||
|--------|-------|
|
||||
| Total Active Bugs | {total_active} |
|
||||
| Critical | {critical_count} |
|
||||
| High | {high_count} |
|
||||
| Medium | {medium_count} |
|
||||
| Low | {low_count} |
|
||||
|
||||
{if any doc_impact flagged:}
|
||||
|
||||
## Documentation Updates Required
|
||||
|
||||
Items with documentation impact have been routed to `correct-course` workflow:
|
||||
- PRD Impact: {prd_impact_count} item(s)
|
||||
- Architecture Impact: {arch_impact_count} item(s)
|
||||
- UX Impact: {ux_impact_count} item(s)
|
||||
{end if}
|
||||
|
||||
---
|
||||
|
||||
## Workflow Recommendations
|
||||
|
||||
### Direct Fix ({direct_fix_count} items)
|
||||
Quick fixes with obvious solutions. No spec needed.
|
||||
|
||||
**Command:** `/implement bug-NNN`
|
||||
|
||||
{list bug IDs for direct-fix}
|
||||
|
||||
### Tech-Spec ({tech_spec_count} items)
|
||||
Require technical specification before implementation.
|
||||
|
||||
**Process:** Create tech-spec first, then `/implement`
|
||||
|
||||
{list bug IDs for tech-spec}
|
||||
|
||||
### Correct-Course ({correct_course_count} items)
|
||||
Need impact analysis before proceeding.
|
||||
|
||||
**Process:** Run correct-course workflow for impact analysis
|
||||
|
||||
{list bug IDs for correct-course}
|
||||
|
||||
### Backlog ({backlog_count} items)
|
||||
Deferred - low priority items for future consideration.
|
||||
|
||||
{list bug IDs for backlog}
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
**To implement a bug fix:**
|
||||
```
|
||||
/implement bug-NNN
|
||||
```
|
||||
|
||||
**To verify after testing:**
|
||||
```
|
||||
/verify bug-NNN
|
||||
```
|
||||
|
||||
**To verify all implemented bugs:**
|
||||
```
|
||||
/verify
|
||||
```
|
||||
|
||||
**To list bugs by platform:**
|
||||
```
|
||||
/list-bugs android
|
||||
/list-bugs ios
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Thank you for completing the triage session!"
|
||||
|
||||
### 2. End Workflow
|
||||
|
||||
The workflow is complete. No further steps.
|
||||
|
||||
## SUCCESS METRICS:
|
||||
|
||||
✅ Comprehensive summary presented
|
||||
✅ All triaged items listed with metadata
|
||||
✅ Statistics accurately displayed
|
||||
✅ Workflow recommendations clear
|
||||
✅ Next step commands provided
|
||||
✅ User knows how to proceed
|
||||
|
||||
## FAILURE MODES:
|
||||
|
||||
❌ Incomplete summary missing items
|
||||
❌ Statistics not matching bugs.yaml
|
||||
❌ Unclear next step guidance
|
||||
❌ Modifying files in this step (should be read-only)
|
||||
|
||||
## WORKFLOW COMPLETE
|
||||
|
||||
This is the final step. The bug tracking triage workflow is complete.
|
||||
|
||||
User can now:
|
||||
- Run `/implement bug-NNN` to fix bugs
|
||||
- Run `/verify` to verify implemented bugs
|
||||
- Add new bugs to bugs.md and run triage again
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
---
|
||||
name: bug-tracking
|
||||
description: Triage user-reported bugs from bugs.md, generate structured metadata in bugs.yaml, and route to appropriate workflow
|
||||
main_config: '{project-root}/_bmad/bmm/config.yaml'
|
||||
web_bundle: true
|
||||
---
|
||||
|
||||
# Bug Tracking Workflow
|
||||
|
||||
**Goal:** Transform informal bug reports into structured, actionable metadata with severity assessment, complexity estimation, and workflow routing recommendations.
|
||||
|
||||
**Your Role:** You are a triage facilitator collaborating with a peer. This is a partnership, not a client-vendor relationship. You bring structured analysis and triage methodology, while the user brings domain expertise and context about their product. Work together to efficiently categorize and route bugs for resolution.
|
||||
|
||||
---
|
||||
|
||||
## WORKFLOW ARCHITECTURE
|
||||
|
||||
This uses **micro-file architecture** for disciplined execution:
|
||||
|
||||
- Each step is a self-contained file with embedded rules
|
||||
- Sequential progression with user control at each step
|
||||
- State tracked via bugs.yaml metadata
|
||||
- Append-only updates to bugs.md (move triaged items, never delete)
|
||||
- You NEVER proceed to a step file if the current step file indicates the user must approve and indicate continuation.
|
||||
|
||||
---
|
||||
|
||||
## INITIALIZATION
|
||||
|
||||
### Configuration Loading
|
||||
|
||||
Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve:
|
||||
|
||||
- `project_name`, `output_folder`, `user_name`
|
||||
- `communication_language`, `date` as system-generated current datetime
|
||||
- `dev_ephemeral_location` for sprint-status.yaml location
|
||||
- ✅ YOU MUST ALWAYS SPEAK OUTPUT In your Agent communication style with the config `{communication_language}`
|
||||
|
||||
### Paths
|
||||
|
||||
- `installed_path` = `{project-root}/_bmad/bmm/workflows/bug-tracking`
|
||||
- `bugs_input` = `{output_folder}/bugs.md` (user-facing bug reports)
|
||||
- `bugs_output` = `{output_folder}/bugs.yaml` (agent-facing structured metadata)
|
||||
- `sprint_status` = `{dev_ephemeral_location}/sprint-status.yaml`
|
||||
- `epics_file` = `{output_folder}/epics.md`
|
||||
|
||||
### Optional API Integration
|
||||
|
||||
- `project_url` = configurable base URL for in-app bug report sync (default: `http://localhost:5173`)
|
||||
- See `reference-implementation.md` for in-app bug reporting setup
|
||||
|
||||
---
|
||||
|
||||
## EXECUTION
|
||||
|
||||
Load and execute `steps/step-01-init.md` to begin the workflow.
|
||||
|
||||
**Note:** Input file discovery and initialization protocols are handled in step-01-init.md.
|
||||
|
|
@ -0,0 +1,523 @@
|
|||
# Implement Workflow (Bug Fix or Feature)
|
||||
|
||||
```xml
|
||||
<critical>This workflow loads bug/feature context, implements the code, and updates tracking in both bugs.yaml and bugs.md</critical>
|
||||
<critical>Communicate in {communication_language} with {user_name}</critical>
|
||||
<critical>Auto-detects type from ID format: bug-NNN = bug fix, feature-NNN = feature implementation</critical>
|
||||
|
||||
<workflow>
|
||||
|
||||
<step n="1" goal="Get item ID from user">
|
||||
<check if="item_id not provided in user input">
|
||||
<ask>Which bug or feature should I implement? (e.g., bug-026 or feature-021)</ask>
|
||||
</check>
|
||||
<action>Extract item ID from user input</action>
|
||||
<action>Detect type from ID format:</action>
|
||||
<action>- "bug-NNN" -> type = "bug", action_verb = "fix", past_verb = "Fixed"</action>
|
||||
<action>- "feature-NNN" -> type = "feature", action_verb = "implement", past_verb = "Implemented"</action>
|
||||
<check if="ID doesn't match either format">
|
||||
<output>Invalid ID format. Use bug-NNN (e.g., bug-026) or feature-NNN (e.g., feature-021)</output>
|
||||
<action>HALT</action>
|
||||
</check>
|
||||
</step>
|
||||
|
||||
<step n="2" goal="Load context from bugs.yaml">
|
||||
<action>Search for {item_id} in {bugs_yaml} using grep with 50+ lines of context after the match (do NOT read entire file - it exceeds token limits)</action>
|
||||
<check if="type == bug">
|
||||
<action>Entry will be in bugs section, grep will capture all fields</action>
|
||||
</check>
|
||||
<check if="type == feature">
|
||||
<action>Entry will be in feature_requests section, grep will capture all fields</action>
|
||||
</check>
|
||||
<check if="item not found in bugs.yaml">
|
||||
<output>{item_id} not found in bugs.yaml. Please verify the ID or run bug-tracking workflow first.</output>
|
||||
<action>HALT</action>
|
||||
</check>
|
||||
<action>Extract and store metadata:</action>
|
||||
<action>- title: Title/summary</action>
|
||||
<action>- description: Full description</action>
|
||||
<action>- severity/priority: Importance level</action>
|
||||
<action>- complexity: trivial | small | medium | complex</action>
|
||||
<action>- effort_estimate: Estimated hours</action>
|
||||
<action>- affected_platform: all | ios | android (bugs only)</action>
|
||||
<action>- related_story/related_epic: Related items if applicable</action>
|
||||
<action>- doc_impact: Documentation impact flags (prd, architecture, ux) and notes</action>
|
||||
<action>- notes: Triage notes including planned approach, files to check, implementation strategy</action>
|
||||
|
||||
<check if="recommended_workflow == 'backlog'">
|
||||
<output>**BACKLOG ITEM - NOT READY FOR IMPLEMENTATION**
|
||||
|
||||
**{item_id}: {title}**
|
||||
|
||||
This item has `recommended_workflow: backlog` which means it's deferred and not scheduled for implementation.
|
||||
|
||||
**To implement this item, first promote it to the sprint:**
|
||||
1. Run `*sprint-planning` and select this item for promotion
|
||||
2. Or manually update bugs.yaml: change `recommended_workflow` to `direct-fix`, `tech-spec`, or `correct-course`
|
||||
|
||||
**Current Status:** {status}
|
||||
**Priority:** {priority}
|
||||
**Complexity:** {complexity}
|
||||
</output>
|
||||
<action>HALT</action>
|
||||
</check>
|
||||
|
||||
<check if="status == 'deferred'">
|
||||
<output>**DEFERRED ITEM - NOT READY FOR IMPLEMENTATION**
|
||||
|
||||
**{item_id}: {title}**
|
||||
|
||||
This item is deferred (marked for future release, not current MVP).
|
||||
|
||||
**To implement this item:**
|
||||
1. Update bugs.yaml: change `status` from `deferred` to `backlog`
|
||||
2. Run `*sprint-planning` to promote to current sprint
|
||||
|
||||
**Notes:** {notes}
|
||||
</output>
|
||||
<action>HALT</action>
|
||||
</check>
|
||||
|
||||
<check if="status == 'blocked'">
|
||||
<output>**BLOCKED ITEM - CANNOT IMPLEMENT**
|
||||
|
||||
**{item_id}: {title}**
|
||||
|
||||
This item is blocked and requires clarification before implementation.
|
||||
|
||||
**Blocking reason:** {notes}
|
||||
|
||||
**To unblock:**
|
||||
1. Resolve the blocking issue
|
||||
2. Update bugs.yaml: change `status` from `blocked` to `backlog`
|
||||
3. Run `/triage {item_id}` to re-evaluate
|
||||
</output>
|
||||
<action>HALT</action>
|
||||
</check>
|
||||
</step>
|
||||
|
||||
<step n="2.5" goal="Check for documentation impact and route to appropriate agents">
|
||||
<action>Check doc_impact fields from bugs.yaml entry</action>
|
||||
<check if="doc_impact.prd OR doc_impact.architecture OR doc_impact.ux is TRUE">
|
||||
<output>**DOCUMENTATION IMPACT DETECTED**
|
||||
|
||||
**{item_id}: {title}**
|
||||
|
||||
This {type} requires documentation updates BEFORE implementation:
|
||||
|
||||
{if doc_impact.prd:}
|
||||
- **PRD Impact:** Updates needed to product requirements
|
||||
-> Route to PM Agent for PRD updates
|
||||
{end if}
|
||||
|
||||
{if doc_impact.architecture:}
|
||||
- **Architecture Impact:** Updates needed to architecture docs
|
||||
-> Route to Architect Agent for architecture updates
|
||||
{end if}
|
||||
|
||||
{if doc_impact.ux:}
|
||||
- **UX Impact:** Updates needed to UX specifications
|
||||
-> Route to UX Designer Agent for UX spec updates
|
||||
{end if}
|
||||
|
||||
**Details:** {doc_impact.notes}
|
||||
|
||||
**Options:**
|
||||
1. **update-docs-first** - Route to agents for documentation updates before implementation (recommended)
|
||||
2. **proceed-anyway** - Skip documentation updates and implement directly (not recommended)
|
||||
3. **cancel** - Return to review</output>
|
||||
<ask>How should we proceed?</ask>
|
||||
|
||||
<check if="user chooses update-docs-first">
|
||||
<output>Routing to documentation update workflow...
|
||||
|
||||
**Documentation Update Sequence:**</output>
|
||||
|
||||
<check if="doc_impact.prd">
|
||||
<output>1. **PRD Update** - Invoking PM Agent...</output>
|
||||
<action>Prepare PRD update context:</action>
|
||||
<action>- Source item: {item_id}</action>
|
||||
<action>- Change description: {description}</action>
|
||||
<action>- Specific PRD sections: {doc_impact.notes PRD sections}</action>
|
||||
<invoke-agent agent="pm">
|
||||
<task>Review and update PRD for {item_id}: {title}
|
||||
|
||||
Change context: {description}
|
||||
|
||||
Documentation notes: {doc_impact.notes}
|
||||
|
||||
Please update the relevant PRD sections to reflect this change.
|
||||
|
||||
After updates:
|
||||
1. Summarize what was changed
|
||||
2. Return to the implement workflow by running: /implement {item_id}
|
||||
|
||||
IMPORTANT: You MUST return to /implement {item_id} after completing the PRD updates so the actual code implementation can proceed.</task>
|
||||
</invoke-agent>
|
||||
</check>
|
||||
|
||||
<check if="doc_impact.architecture">
|
||||
<output>2. **Architecture Update** - Invoking Architect Agent...</output>
|
||||
<action>Prepare architecture update context:</action>
|
||||
<action>- Source item: {item_id}</action>
|
||||
<action>- Change description: {description}</action>
|
||||
<action>- Specific architecture sections: {doc_impact.notes architecture sections}</action>
|
||||
<invoke-agent agent="architect">
|
||||
<task>Review and update Architecture documentation for {item_id}: {title}
|
||||
|
||||
Change context: {description}
|
||||
|
||||
Documentation notes: {doc_impact.notes}
|
||||
|
||||
Please update the relevant architecture sections (data model, APIs, security, etc.) to reflect this change.
|
||||
|
||||
After updates:
|
||||
1. Summarize what was changed
|
||||
2. Return to the implement workflow by running: /implement {item_id}
|
||||
|
||||
IMPORTANT: You MUST return to /implement {item_id} after completing the architecture updates so the actual code implementation can proceed.</task>
|
||||
</invoke-agent>
|
||||
</check>
|
||||
|
||||
<check if="doc_impact.ux">
|
||||
<output>3. **UX Spec Update** - Invoking UX Designer Agent...</output>
|
||||
<action>Prepare UX update context:</action>
|
||||
<action>- Source item: {item_id}</action>
|
||||
<action>- Change description: {description}</action>
|
||||
<action>- Specific UX sections: {doc_impact.notes UX sections}</action>
|
||||
<invoke-agent agent="ux-designer">
|
||||
<task>Review and update UX specification for {item_id}: {title}
|
||||
|
||||
Change context: {description}
|
||||
|
||||
Documentation notes: {doc_impact.notes}
|
||||
|
||||
Please update the relevant UX spec sections (screens, flows, components, etc.) to reflect this change.
|
||||
|
||||
After updates:
|
||||
1. Summarize what was changed
|
||||
2. Return to the implement workflow by running: /implement {item_id}
|
||||
|
||||
IMPORTANT: You MUST return to /implement {item_id} after completing the UX updates so the actual code implementation can proceed.</task>
|
||||
</invoke-agent>
|
||||
</check>
|
||||
|
||||
<output>**Documentation updates complete.**
|
||||
|
||||
Proceeding with implementation...</output>
|
||||
<action>Continue to step 3</action>
|
||||
</check>
|
||||
|
||||
<check if="user chooses cancel">
|
||||
<output>Cancelled. {item_id} remains in current state.</output>
|
||||
<action>HALT</action>
|
||||
</check>
|
||||
|
||||
<action if="user chooses proceed-anyway">
|
||||
<output>Proceeding without documentation updates. Remember to update docs after implementation.</output>
|
||||
<action>Continue to step 3</action>
|
||||
</action>
|
||||
</check>
|
||||
</step>
|
||||
|
||||
<step n="3" goal="Evaluate routing and auto-route to correct-course if needed">
|
||||
<action>Check recommended_workflow field from bugs.yaml</action>
|
||||
|
||||
<check if="recommended_workflow == 'correct-course'">
|
||||
<output>**AUTO-ROUTING TO CORRECT-COURSE**
|
||||
|
||||
**{item_id}: {title}**
|
||||
**Priority:** {severity_or_priority} | **Complexity:** {complexity}
|
||||
|
||||
This {type} has `recommended_workflow: correct-course` which requires impact analysis and story creation before implementation.
|
||||
|
||||
Invoking correct-course workflow via SM agent...</output>
|
||||
|
||||
<action>Invoke the correct-course workflow skill with item context</action>
|
||||
<invoke-skill skill="bmad:bmm:workflows:correct-course">
|
||||
<args>{item_id}: {title} - {description}
|
||||
|
||||
Priority: {severity_or_priority}
|
||||
Complexity: {complexity}
|
||||
Doc Impact: {doc_impact summary}
|
||||
Notes: {notes}</args>
|
||||
</invoke-skill>
|
||||
<action>HALT - Correct Course workflow will handle story/epic creation</action>
|
||||
</check>
|
||||
|
||||
<check if="recommended_workflow == 'tech-spec'">
|
||||
<output>**AUTO-ROUTING TO TECH-SPEC**
|
||||
|
||||
**{item_id}: {title}**
|
||||
|
||||
This {type} has `recommended_workflow: tech-spec`. Invoking tech-spec workflow...</output>
|
||||
|
||||
<invoke-skill skill="bmad:bmm:workflows:tech-spec">
|
||||
<args>{item_id}: {title} - {description}</args>
|
||||
</invoke-skill>
|
||||
<action>HALT - Tech-spec workflow will create implementation spec</action>
|
||||
</check>
|
||||
|
||||
<check if="recommended_workflow == 'direct-fix'">
|
||||
<output>**DIRECT IMPLEMENTATION**
|
||||
|
||||
This {type} is routed for direct implementation. Proceeding...</output>
|
||||
<action>Continue to step 4</action>
|
||||
</check>
|
||||
|
||||
<check if="recommended_workflow is not set OR recommended_workflow is ambiguous">
|
||||
<action>Evaluate the workflow routing matrix based on severity and complexity:</action>
|
||||
<action>**Routing Matrix:**</action>
|
||||
<action>- critical + any -> correct-course</action>
|
||||
<action>- high/medium + medium/complex -> correct-course</action>
|
||||
<action>- high + trivial -> direct-fix</action>
|
||||
<action>- high/medium + small -> tech-spec</action>
|
||||
<action>- medium + trivial -> direct-fix</action>
|
||||
<action>- low + trivial -> direct-fix</action>
|
||||
<action>- low + small+ -> backlog</action>
|
||||
|
||||
<action>Apply matrix to determine routing and continue accordingly</action>
|
||||
</check>
|
||||
</step>
|
||||
|
||||
<step n="4" goal="Present context and confirm approach">
|
||||
<output>**{item_id}: {title}**
|
||||
|
||||
**Type:** {type} | **Severity/Priority:** {severity_or_priority} | **Complexity:** {complexity} | **Effort:** ~{effort_estimate}h
|
||||
|
||||
**Description:**
|
||||
{description}
|
||||
|
||||
**Planned Approach (from triage notes):**
|
||||
{notes}
|
||||
|
||||
**Related:** {related_story} / {related_epic}
|
||||
</output>
|
||||
<ask>Ready to {action_verb} this {type}? (yes/no/clarify)</ask>
|
||||
<check if="user says clarify">
|
||||
<ask>What additional context do you need?</ask>
|
||||
<action>Gather clarification, update mental model</action>
|
||||
</check>
|
||||
<check if="user says no">
|
||||
<output>Cancelled. {item_id} remains in current state.</output>
|
||||
<action>HALT</action>
|
||||
</check>
|
||||
</step>
|
||||
|
||||
<step n="5" goal="Implement the fix/feature">
|
||||
<action>Based on the notes/planned approach, identify files to modify or create</action>
|
||||
<action>Read each affected file to understand current implementation</action>
|
||||
<action>Implement following the planned approach:</action>
|
||||
<action>- Make minimal, targeted changes</action>
|
||||
<action>- Follow existing code patterns and style</action>
|
||||
<action>- Add comments only where logic is non-obvious</action>
|
||||
<action>- Do not over-engineer or add unrelated improvements</action>
|
||||
<action>- Do not add extra features or "nice to haves"</action>
|
||||
|
||||
<action>For each file modified/created, track:</action>
|
||||
<action>- File path</action>
|
||||
<action>- What was changed/added</action>
|
||||
<action>- How it addresses the bug/feature</action>
|
||||
|
||||
<check if="requires new files">
|
||||
<action>Create new files following project conventions</action>
|
||||
<action>Add appropriate imports/exports</action>
|
||||
</check>
|
||||
|
||||
<check if="planned approach is unclear or insufficient">
|
||||
<ask>The triage notes don't provide a clear approach.
|
||||
|
||||
Based on my analysis, I suggest: {proposed_approach}
|
||||
|
||||
Should I proceed with this approach?</ask>
|
||||
</check>
|
||||
</step>
|
||||
|
||||
<step n="6" goal="Verify implementation compiles">
|
||||
<action>Run TypeScript compilation check: npm run check</action>
|
||||
<check if="compilation errors in modified files">
|
||||
<action>Fix compilation errors</action>
|
||||
<action>Re-run compilation check</action>
|
||||
</check>
|
||||
<output>Compilation check passed.</output>
|
||||
</step>
|
||||
|
||||
<step n="6.5" goal="Pre-update sync check">
|
||||
<action>Search for {item_id} in both bugs.yaml and bugs.md using grep to check current status</action>
|
||||
<check if="status differs between files OR item missing from one file">
|
||||
<output>SYNC WARNING: {item_id} status mismatch detected
|
||||
- bugs.yaml: {yaml_status}
|
||||
- bugs.md: {md_status}
|
||||
Proceeding will update both files to "{new_status}".</output>
|
||||
</check>
|
||||
</step>
|
||||
|
||||
<step n="7" goal="Update bugs.yaml">
|
||||
<action>Update entry in bugs.yaml:</action>
|
||||
<check if="type == bug">
|
||||
<action>- status: "fixed"</action>
|
||||
<action>- fixed_date: {date} (YYYY-MM-DD format)</action>
|
||||
</check>
|
||||
<check if="type == feature">
|
||||
<action>- status: "implemented"</action>
|
||||
<action>- implemented_date: {date} (YYYY-MM-DD format)</action>
|
||||
</check>
|
||||
<action>- assigned_to: "dev-agent"</action>
|
||||
<action>- files_modified: {list of files changed/created during implementation}</action>
|
||||
<action>- Append to notes: "{past_verb} ({date}): {summary of changes made}"</action>
|
||||
<action>Write updated bugs.yaml</action>
|
||||
</step>
|
||||
|
||||
<step n="8" goal="Update bugs.md">
|
||||
<action>Search for {item_id} in {bugs_md} using grep with surrounding context to locate the entry</action>
|
||||
|
||||
<action>**8a. Remove from tracked section (if present)**</action>
|
||||
<check if="type == bug">
|
||||
<action>Search for "{item_id}:" in "# Tracked Bugs" section</action>
|
||||
</check>
|
||||
<check if="type == feature">
|
||||
<action>Search for "{item_id}:" in "# Tracked Feature Requests" section</action>
|
||||
</check>
|
||||
<action>If found, remove the entire entry (including any indented sub-items)</action>
|
||||
|
||||
<action>**8b. Add to completed section (INSERT AT TOP - newest first)**</action>
|
||||
<check if="type == bug">
|
||||
<action>Locate "# Fixed Bugs" section in bugs.md</action>
|
||||
<action>If section not found, create it</action>
|
||||
<action>INSERT AT TOP of section (immediately after "# Fixed Bugs" header):</action>
|
||||
<action>[IMPLEMENTED] {item_id}: {title} - {brief_description}. [Severity: {severity}, Platform: {platform}, Fixed: {date}, Verified: pending]</action>
|
||||
<action> - Fix: {description of what was fixed}</action>
|
||||
<action> - File(s): {list of modified files}</action>
|
||||
</check>
|
||||
<check if="type == feature">
|
||||
<action>Locate "# Implemented Features" section in bugs.md</action>
|
||||
<action>If section not found, create it before "# Fixed Bugs"</action>
|
||||
<action>INSERT AT TOP of section (immediately after "# Implemented Features" header):</action>
|
||||
<action>[IMPLEMENTED] {item_id}: {title} - {brief_description}. [Implemented: {date}, Platform: {platform}, Verified: pending]</action>
|
||||
<action> - Files: {list of modified/created files}</action>
|
||||
<action> - Features: {bullet list of what was implemented}</action>
|
||||
</check>
|
||||
<action>Write updated bugs.md</action>
|
||||
</step>
|
||||
|
||||
<step n="9" goal="Post-update validation">
|
||||
<action>Search for {item_id} in both bugs.yaml and bugs.md using grep to validate updates</action>
|
||||
<action>Confirm {item_id} shows status "fixed"/"implemented" in bugs.yaml</action>
|
||||
<action>Confirm {item_id} has [IMPLEMENTED] tag in bugs.md</action>
|
||||
<check if="validation fails">
|
||||
<output>SYNC ERROR: Files may be out of sync. Please verify manually:
|
||||
- bugs.yaml: Expected status "fixed"/"implemented"
|
||||
- bugs.md: Expected [IMPLEMENTED] tag in appropriate section</output>
|
||||
</check>
|
||||
</step>
|
||||
|
||||
<step n="10" goal="Present completion summary">
|
||||
<output>**{item_id} {past_verb.upper()}**
|
||||
|
||||
**Changes Made:**
|
||||
{for each modified file:}
|
||||
- {file_path}: {what was changed}
|
||||
{end for}
|
||||
|
||||
**Updated Tracking:**
|
||||
- bugs.yaml: status -> "{status}", {date_field} -> {date}, files_modified updated
|
||||
- bugs.md: Moved to "{target_section}" with [IMPLEMENTED] tag
|
||||
|
||||
**Verification Status:** pending
|
||||
|
||||
**Next Steps:**
|
||||
1. Test manually
|
||||
2. Run `/verify {item_id}` after verification to close
|
||||
</output>
|
||||
</step>
|
||||
|
||||
</workflow>
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
/implement bug-026
|
||||
/implement feature-021
|
||||
```
|
||||
|
||||
## Key Principles
|
||||
|
||||
1. **Auto-detect Type** - ID format determines bug vs feature handling
|
||||
2. **Context First** - Always read and present details before implementing
|
||||
3. **Confirm Approach** - Validate planned approach with user before coding
|
||||
4. **Minimal Changes** - Only implement what's needed, no scope creep
|
||||
5. **Dual Tracking** - ALWAYS update both bugs.yaml AND bugs.md
|
||||
6. **[IMPLEMENTED] Tag** - Indicates complete but awaiting verification
|
||||
|
||||
---
|
||||
|
||||
## Reference: Bug Tracking Definitions
|
||||
|
||||
### Severity Levels
|
||||
|
||||
| Level | Description | Action |
|
||||
|-------|-------------|--------|
|
||||
| **critical** | Blocks core functionality, prevents app use, or causes data loss (crashes, auth broken, data corruption) | Fix immediately, may require hotfix |
|
||||
| **high** | Major feature broken, significant UX degradation, workaround exists but painful (platform-specific failure, 5+ sec delays, accessibility blocker) | Fix in current/next sprint |
|
||||
| **medium** | Feature partially broken, UX degraded but usable (minor feature broken, unclear errors, 1-3 sec delays) | Fix when capacity allows |
|
||||
| **low** | Minor issue, cosmetic, edge case (typos, spacing, visual glitches) | Fix opportunistically or defer |
|
||||
|
||||
### Complexity Levels
|
||||
|
||||
| Level | Description | Effort |
|
||||
|-------|-------------|--------|
|
||||
| **trivial** | Obvious fix, single line change, no investigation needed (typo, missing semicolon, wrong color) | < 30 minutes |
|
||||
| **small** | Single file/component, clear root cause, solution known (missing validation, incorrect prop, logic error) | 30 min - 2 hours |
|
||||
| **medium** | Multiple files affected OR investigation required (spans 2-3 components, debugging needed, integration issue) | 2-8 hours |
|
||||
| **complex** | Architectural issue, affects multiple stories, requires design changes (race conditions, refactoring, profiling) | 8+ hours (1-2 days) |
|
||||
|
||||
### Workflow Routing Matrix
|
||||
|
||||
| Severity | Complexity | Workflow | Rationale |
|
||||
|----------|------------|----------|-----------|
|
||||
| critical | any | correct-course -> urgent | Need impact analysis even if small fix |
|
||||
| high | trivial | direct-fix (urgent) | Fast path for obvious important fix |
|
||||
| high | small | tech-spec (urgent) | Fast path with minimal overhead |
|
||||
| high | medium+ | correct-course -> story | Need proper analysis + testing |
|
||||
| medium | trivial | direct-fix | Too small for workflow overhead |
|
||||
| medium | small | tech-spec | Isolated fix needs spec |
|
||||
| medium | medium+ | correct-course -> story | Multi-file change needs story |
|
||||
| low | trivial | direct-fix (defer) | Fix opportunistically |
|
||||
| low | small+ | backlog (defer) | Document but don't schedule yet |
|
||||
|
||||
### Status Flow
|
||||
|
||||
```
|
||||
reported -> triaged -> routed -> in-progress -> fixed -> verified -> closed
|
||||
```
|
||||
|
||||
| Status | Description |
|
||||
|--------|-------------|
|
||||
| **reported** | Bug logged in bugs.md, not yet analyzed |
|
||||
| **triaged** | PM analyzed, assigned severity/complexity/workflow |
|
||||
| **routed** | Workflow determined, story/tech-spec created |
|
||||
| **in-progress** | Developer actively working on fix |
|
||||
| **fixed** | Code changed, tests passing, ready for verification |
|
||||
| **verified** | Bug confirmed fixed by reporter or QA |
|
||||
| **closed** | Bug resolved and verified, no further action |
|
||||
|
||||
### Metadata Fields
|
||||
|
||||
| Field | Description |
|
||||
|-------|-------------|
|
||||
| id | Unique identifier (bug-NNN or feature-NNN) |
|
||||
| title | Short description (< 80 chars) |
|
||||
| description | Detailed explanation |
|
||||
| severity | critical \| high \| medium \| low |
|
||||
| complexity | trivial \| small \| medium \| complex |
|
||||
| status | Current workflow state |
|
||||
| recommended_workflow | direct-fix \| tech-spec \| correct-course \| backlog |
|
||||
| effort_estimate | Hours (based on complexity) |
|
||||
| reported_by / reported_date | Who found it and when |
|
||||
| triaged_by / triaged_date | Who triaged and when |
|
||||
| fixed_date / verified_date | Implementation and verification dates |
|
||||
| related_story / related_epic | Context links |
|
||||
| affected_platform | all \| ios \| android |
|
||||
| doc_impact | Documentation impact: prd, architecture, ux flags + notes |
|
||||
| notes | Investigation notes, decisions, implementation details |
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
name: implement
|
||||
description: "Implement a bug fix or feature - loads context from bugs.yaml, implements the code, updates both bugs.yaml and bugs.md with [IMPLEMENTED] tag"
|
||||
author: "BMad"
|
||||
|
||||
# Critical variables from config
|
||||
config_source: "{project-root}/.bmad/bmm/config.yaml"
|
||||
output_folder: "{config_source}:output_folder"
|
||||
user_name: "{config_source}:user_name"
|
||||
communication_language: "{config_source}:communication_language"
|
||||
date: system-generated
|
||||
|
||||
# Workflow components
|
||||
installed_path: "{project-root}/.bmad/bmm/workflows/implement"
|
||||
instructions: "{installed_path}/instructions.md"
|
||||
template: false
|
||||
|
||||
# Input and output files
|
||||
variables:
|
||||
bugs_md: "{output_folder}/bugs.md"
|
||||
bugs_yaml: "{output_folder}/bugs.yaml"
|
||||
|
||||
standalone: true
|
||||
|
|
@ -0,0 +1,219 @@
|
|||
# Verify Workflow (Close Implemented Bugs/Features)
|
||||
|
||||
```xml
|
||||
<critical>This workflow verifies implemented items and closes them in both bugs.yaml and bugs.md</critical>
|
||||
<critical>Communicate in {communication_language} with {user_name}</critical>
|
||||
<critical>Removes [IMPLEMENTED] tag and updates status to CLOSED (bugs) or COMPLETE (features)</critical>
|
||||
|
||||
<workflow>
|
||||
|
||||
<step n="1" goal="Get item ID or list pending items">
|
||||
<check if="item_id provided in user input">
|
||||
<action>Extract item ID from user input</action>
|
||||
<action>Detect type from ID format:</action>
|
||||
<action>- "bug-NNN" -> type = "bug", final_status = "CLOSED"</action>
|
||||
<action>- "feature-NNN" -> type = "feature", final_status = "COMPLETE"</action>
|
||||
<action>Proceed to Step 2</action>
|
||||
</check>
|
||||
<check if="no item_id provided OR user says 'list'">
|
||||
<action>Search {bugs_yaml} for 'status: "fixed"' or 'status: "implemented"' using grep (do NOT read entire file)</action>
|
||||
<action>Search {bugs_md} for '[IMPLEMENTED]' entries using grep</action>
|
||||
<action>Find all items with:</action>
|
||||
<action>- status == "fixed" or "implemented" in bugs.yaml</action>
|
||||
<action>- [IMPLEMENTED] tag in bugs.md</action>
|
||||
<output>**Pending Verification:**
|
||||
|
||||
{for each pending item:}
|
||||
- **{item_id}**: {title} [{type}, {fixed_date or implemented_date}]
|
||||
{end for}
|
||||
|
||||
**Total:** {count} item(s) awaiting verification
|
||||
|
||||
To verify an item: `/verify bug-026`
|
||||
To verify all: Type "verify all"
|
||||
</output>
|
||||
<ask>Which item would you like to verify?</ask>
|
||||
</check>
|
||||
<check if="user says 'verify all' or 'all'">
|
||||
<action>Set batch_mode = true</action>
|
||||
<action>Collect all pending items</action>
|
||||
<action>Proceed to Step 2 with batch processing</action>
|
||||
</check>
|
||||
</step>
|
||||
|
||||
<step n="2" goal="Load item context and check sync">
|
||||
<action>Search for {item_id} in {bugs_yaml} using grep with 50+ lines of context after the match (do NOT read entire file - it exceeds token limits)</action>
|
||||
<check if="type == bug">
|
||||
<action>Entry will be in bugs section, verify status == "fixed"</action>
|
||||
</check>
|
||||
<check if="type == feature">
|
||||
<action>Entry will be in feature_requests section, verify status == "implemented"</action>
|
||||
</check>
|
||||
<check if="item not found OR status not fixed/implemented">
|
||||
<output>{item_id} is not in an implemented state. Current status: {status}
|
||||
|
||||
Only items with status "fixed" (bugs) or "implemented" (features) can be verified.</output>
|
||||
<action>HALT</action>
|
||||
</check>
|
||||
<action>Extract metadata: title, description, fixed_date/implemented_date, notes</action>
|
||||
|
||||
<action>**Sync Check:** Also read {bugs_md} to verify sync status</action>
|
||||
<check if="bugs.yaml says fixed/implemented but bugs.md missing [IMPLEMENTED] tag">
|
||||
<output>SYNC WARNING: {item_id} status mismatch detected
|
||||
- bugs.yaml: {yaml_status}
|
||||
- bugs.md: Missing [IMPLEMENTED] tag (may have been implemented outside workflow)
|
||||
|
||||
Proceeding will update both files to CLOSED/COMPLETE.</output>
|
||||
<ask>Continue with verification? (yes/no)</ask>
|
||||
<check if="user says no">
|
||||
<output>Cancelled. Please run /implement {item_id} first to sync files.</output>
|
||||
<action>HALT</action>
|
||||
</check>
|
||||
</check>
|
||||
</step>
|
||||
|
||||
<step n="3" goal="Confirm verification">
|
||||
<output>**Verify {item_id}: {title}**
|
||||
|
||||
**Type:** {type}
|
||||
**{past_verb}:** {fixed_date or implemented_date}
|
||||
|
||||
**Implementation Notes:**
|
||||
{notes - show the FIXED/IMPLEMENTED section}
|
||||
|
||||
**Files Changed:**
|
||||
{extract file list from notes}
|
||||
</output>
|
||||
<ask>Has this been tested and verified working? (yes/no/skip)</ask>
|
||||
<check if="user says no">
|
||||
<ask>What issue did you find? (I'll add it to the notes)</ask>
|
||||
<action>Append verification failure note to bugs.yaml notes field</action>
|
||||
<output>Noted. {item_id} remains in implemented state for rework.</output>
|
||||
<action>HALT or continue to next item in batch</action>
|
||||
</check>
|
||||
<check if="user says skip">
|
||||
<output>Skipped. {item_id} remains in implemented state.</output>
|
||||
<action>Continue to next item in batch or HALT</action>
|
||||
</check>
|
||||
</step>
|
||||
|
||||
<step n="4" goal="Update bugs.yaml">
|
||||
<action>Update entry in bugs.yaml:</action>
|
||||
<action>- status: "closed"</action>
|
||||
<action>- verified_by: {user_name}</action>
|
||||
<action>- verified_date: {date} (YYYY-MM-DD format)</action>
|
||||
<action>- Append to notes: "Verified ({date}) by {user_name}"</action>
|
||||
<action>Write updated bugs.yaml</action>
|
||||
</step>
|
||||
|
||||
<step n="5" goal="Update bugs.md">
|
||||
<action>Search for {item_id} in {bugs_md} using grep with surrounding context to locate the entry</action>
|
||||
|
||||
<action>**5a. Find the entry**</action>
|
||||
<check if="type == bug">
|
||||
<action>Search for "[IMPLEMENTED] {item_id}:" in "# Fixed Bugs" section</action>
|
||||
<check if="not found">
|
||||
<action>Search for "{item_id}:" in "# Tracked Bugs" section (implemented outside workflow)</action>
|
||||
</check>
|
||||
</check>
|
||||
<check if="type == feature">
|
||||
<action>Search for "[IMPLEMENTED] {item_id}:" in "# Implemented Features" section</action>
|
||||
<check if="not found">
|
||||
<action>Search for "{item_id}:" in "# Tracked Feature Requests" section (implemented outside workflow)</action>
|
||||
</check>
|
||||
</check>
|
||||
|
||||
<action>**5b. Move entry if in wrong section**</action>
|
||||
<check if="entry found in Tracked section (implemented outside workflow)">
|
||||
<action>DELETE the entry from "# Tracked Bugs" or "# Tracked Feature Requests"</action>
|
||||
<action>ADD entry to correct section:</action>
|
||||
<check if="type == bug">
|
||||
<action>Add to "# Fixed Bugs" section</action>
|
||||
</check>
|
||||
<check if="type == feature">
|
||||
<action>Add to "# Implemented Features" section (at top, before other entries)</action>
|
||||
</check>
|
||||
</check>
|
||||
|
||||
<action>**5c. Update the entry format**</action>
|
||||
<action>Remove "[IMPLEMENTED] " prefix if present</action>
|
||||
<action>Update the status tag in brackets:</action>
|
||||
<check if="type == bug">
|
||||
<action>Change from "[Severity: X, Fixed: DATE, Verified: pending]" or "[Severity: X, Complexity: Y, Workflow: Z]"</action>
|
||||
<action>To "[Severity: X, Platform: Y, Fixed: {date}, Verified: {date}, CLOSED]"</action>
|
||||
</check>
|
||||
<check if="type == feature">
|
||||
<action>Change from "[Implemented: DATE, Verified: pending]" or "[Priority: X, Complexity: Y, Workflow: Z]"</action>
|
||||
<action>To "[Implemented: {date}, Platform: Y, Verified: {date}, COMPLETE]"</action>
|
||||
</check>
|
||||
<action>Add implementation notes if available from bugs.yaml</action>
|
||||
|
||||
<action>Write updated bugs.md</action>
|
||||
</step>
|
||||
|
||||
<step n="5.5" goal="Post-update validation">
|
||||
<action>Search for {item_id} in both bugs.yaml and bugs.md using grep to validate updates</action>
|
||||
<action>Confirm bugs.yaml: status="closed", verified_by set, verified_date set</action>
|
||||
<action>Confirm bugs.md: No [IMPLEMENTED] tag, has CLOSED/COMPLETE in status tag</action>
|
||||
<check if="validation fails">
|
||||
<output>SYNC ERROR: Verification may be incomplete. Please check both files:
|
||||
- bugs.yaml: Expected status "closed", verified_by/verified_date set
|
||||
- bugs.md: Expected CLOSED/COMPLETE tag, no [IMPLEMENTED] prefix</output>
|
||||
</check>
|
||||
</step>
|
||||
|
||||
<step n="6" goal="Present completion summary">
|
||||
<check if="batch_mode">
|
||||
<output>**Verification Complete**
|
||||
|
||||
**Verified {verified_count} item(s):**
|
||||
{for each verified item:}
|
||||
- {item_id}: {title} -> {final_status}
|
||||
{end for}
|
||||
|
||||
**Skipped:** {skipped_count}
|
||||
**Failed verification:** {failed_count}
|
||||
|
||||
**Updated Files:**
|
||||
- bugs.yaml: status -> "closed", verified_by/verified_date set
|
||||
- bugs.md: [IMPLEMENTED] tag removed, status -> {final_status}
|
||||
</output>
|
||||
</check>
|
||||
<check if="not batch_mode">
|
||||
<output>**{item_id} VERIFIED and {final_status}**
|
||||
|
||||
**Updated:**
|
||||
- bugs.yaml: status -> "closed", verified_by -> {user_name}, verified_date -> {date}
|
||||
- bugs.md: Removed [IMPLEMENTED] tag, added "Verified: {date}, {final_status}"
|
||||
|
||||
This item is now fully closed.
|
||||
</output>
|
||||
</check>
|
||||
</step>
|
||||
|
||||
</workflow>
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
/verify # List all pending verification
|
||||
/verify bug-026 # Verify specific bug
|
||||
/verify feature-021 # Verify specific feature
|
||||
/verify all # Verify all pending items
|
||||
```
|
||||
|
||||
## Status Transitions
|
||||
|
||||
| Type | Before | After |
|
||||
|------|--------|-------|
|
||||
| Bug | status: "fixed", [IMPLEMENTED] | status: "closed", CLOSED |
|
||||
| Feature | status: "implemented", [IMPLEMENTED] | status: "closed", COMPLETE |
|
||||
|
||||
## Key Principles
|
||||
|
||||
1. **Verification Gate** - User must confirm item was tested and works
|
||||
2. **Failure Handling** - If verification fails, add note and keep in implemented state
|
||||
3. **Batch Support** - Can verify multiple items at once
|
||||
4. **Dual Tracking** - ALWAYS update both bugs.yaml AND bugs.md
|
||||
5. **Proper Closure** - Removes [IMPLEMENTED] tag, adds final CLOSED/COMPLETE status
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
name: verify
|
||||
description: "Verify and close implemented bugs/features - removes [IMPLEMENTED] tag, updates status to CLOSED/COMPLETE in both bugs.yaml and bugs.md"
|
||||
author: "BMad"
|
||||
|
||||
# Critical variables from config
|
||||
config_source: "{project-root}/.bmad/bmm/config.yaml"
|
||||
output_folder: "{config_source}:output_folder"
|
||||
user_name: "{config_source}:user_name"
|
||||
communication_language: "{config_source}:communication_language"
|
||||
date: system-generated
|
||||
|
||||
# Workflow components
|
||||
installed_path: "{project-root}/.bmad/bmm/workflows/verify"
|
||||
instructions: "{installed_path}/instructions.md"
|
||||
template: false
|
||||
|
||||
# Input and output files
|
||||
variables:
|
||||
bugs_md: "{output_folder}/bugs.md"
|
||||
bugs_yaml: "{output_folder}/bugs.yaml"
|
||||
|
||||
standalone: true
|
||||
|
|
@ -3,7 +3,7 @@ const path = require('node:path');
|
|||
const fs = require('node:fs');
|
||||
|
||||
// Fix for stdin issues when running through npm on Windows
|
||||
// Ensures keyboard interaction works properly with inquirer prompts
|
||||
// Ensures keyboard interaction works properly with CLI prompts
|
||||
if (process.stdin.isTTY) {
|
||||
try {
|
||||
process.stdin.resume();
|
||||
|
|
|
|||
|
|
@ -71,14 +71,10 @@ module.exports = {
|
|||
console.log(chalk.dim(' • ElevenLabs AI (150+ premium voices)'));
|
||||
console.log(chalk.dim(' • Piper TTS (50+ free voices)\n'));
|
||||
|
||||
const { default: inquirer } = await import('inquirer');
|
||||
await inquirer.prompt([
|
||||
{
|
||||
type: 'input',
|
||||
name: 'continue',
|
||||
const prompts = require('../lib/prompts');
|
||||
await prompts.text({
|
||||
message: chalk.green('Press Enter to start AgentVibes installer...'),
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
console.log('');
|
||||
|
||||
|
|
|
|||
|
|
@ -4,15 +4,7 @@ const yaml = require('yaml');
|
|||
const chalk = require('chalk');
|
||||
const { getProjectRoot, getModulePath } = require('../../../lib/project-root');
|
||||
const { CLIUtils } = require('../../../lib/cli-utils');
|
||||
|
||||
// Lazy-load inquirer (ESM module) to avoid ERR_REQUIRE_ESM
|
||||
let _inquirer = null;
|
||||
async function getInquirer() {
|
||||
if (!_inquirer) {
|
||||
_inquirer = (await import('inquirer')).default;
|
||||
}
|
||||
return _inquirer;
|
||||
}
|
||||
const prompts = require('../../../lib/prompts');
|
||||
|
||||
class ConfigCollector {
|
||||
constructor() {
|
||||
|
|
@ -183,7 +175,6 @@ class ConfigCollector {
|
|||
* @returns {boolean} True if new fields were prompted, false if all fields existed
|
||||
*/
|
||||
async collectModuleConfigQuick(moduleName, projectDir, silentMode = true) {
|
||||
const inquirer = await getInquirer();
|
||||
this.currentProjectDir = projectDir;
|
||||
|
||||
// Load existing config if not already loaded
|
||||
|
|
@ -359,7 +350,7 @@ class ConfigCollector {
|
|||
// Only show header if we actually have questions
|
||||
CLIUtils.displayModuleConfigHeader(moduleName, moduleConfig.header, moduleConfig.subheader);
|
||||
console.log(); // Line break before questions
|
||||
const promptedAnswers = await inquirer.prompt(questions);
|
||||
const promptedAnswers = await prompts.prompt(questions);
|
||||
|
||||
// Merge prompted answers with static answers
|
||||
Object.assign(allAnswers, promptedAnswers);
|
||||
|
|
@ -502,7 +493,6 @@ class ConfigCollector {
|
|||
* @param {boolean} skipCompletion - Skip showing completion message (for early core collection)
|
||||
*/
|
||||
async collectModuleConfig(moduleName, projectDir, skipLoadExisting = false, skipCompletion = false) {
|
||||
const inquirer = await getInquirer();
|
||||
this.currentProjectDir = projectDir;
|
||||
// Load existing config if needed and not already loaded
|
||||
if (!skipLoadExisting && !this.existingConfig) {
|
||||
|
|
@ -597,7 +587,7 @@ class ConfigCollector {
|
|||
console.log(chalk.cyan('?') + ' ' + chalk.magenta(moduleDisplayName));
|
||||
let customize = true;
|
||||
if (moduleName !== 'core') {
|
||||
const customizeAnswer = await inquirer.prompt([
|
||||
const customizeAnswer = await prompts.prompt([
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'customize',
|
||||
|
|
@ -614,7 +604,7 @@ class ConfigCollector {
|
|||
|
||||
if (questionsWithoutDefaults.length > 0) {
|
||||
console.log(chalk.dim(`\n Asking required questions for ${moduleName.toUpperCase()}...`));
|
||||
const promptedAnswers = await inquirer.prompt(questionsWithoutDefaults);
|
||||
const promptedAnswers = await prompts.prompt(questionsWithoutDefaults);
|
||||
Object.assign(allAnswers, promptedAnswers);
|
||||
}
|
||||
|
||||
|
|
@ -628,7 +618,7 @@ class ConfigCollector {
|
|||
allAnswers[question.name] = question.default;
|
||||
}
|
||||
} else {
|
||||
const promptedAnswers = await inquirer.prompt(questions);
|
||||
const promptedAnswers = await prompts.prompt(questions);
|
||||
Object.assign(allAnswers, promptedAnswers);
|
||||
}
|
||||
}
|
||||
|
|
@ -750,7 +740,7 @@ class ConfigCollector {
|
|||
console.log(chalk.cyan('?') + ' ' + chalk.magenta(moduleDisplayName));
|
||||
|
||||
// Ask user if they want to accept defaults or customize on the next line
|
||||
const { customize } = await inquirer.prompt([
|
||||
const { customize } = await prompts.prompt([
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'customize',
|
||||
|
|
@ -845,7 +835,7 @@ class ConfigCollector {
|
|||
}
|
||||
|
||||
/**
|
||||
* Build an inquirer question from a config item
|
||||
* Build a prompt question from a config item
|
||||
* @param {string} moduleName - Module name
|
||||
* @param {string} key - Config key
|
||||
* @param {Object} item - Config item definition
|
||||
|
|
@ -1007,7 +997,7 @@ class ConfigCollector {
|
|||
message: message,
|
||||
};
|
||||
|
||||
// Set default - if it's dynamic, use a function that inquirer will evaluate with current answers
|
||||
// Set default - if it's dynamic, use a function that the prompt will evaluate with current answers
|
||||
// But if we have an existing value, always use that instead
|
||||
if (existingValue !== null && existingValue !== undefined && questionType !== 'list') {
|
||||
question.default = existingValue;
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ const { CLIUtils } = require('../../../lib/cli-utils');
|
|||
const { ManifestGenerator } = require('./manifest-generator');
|
||||
const { IdeConfigManager } = require('./ide-config-manager');
|
||||
const { CustomHandler } = require('../custom/handler');
|
||||
const prompts = require('../../../lib/prompts');
|
||||
|
||||
// BMAD installation folder name - this is constant and should never change
|
||||
const BMAD_FOLDER_NAME = '_bmad';
|
||||
|
|
@ -758,6 +759,9 @@ class Installer {
|
|||
config.skipIde = toolSelection.skipIde;
|
||||
const ideConfigurations = toolSelection.configurations;
|
||||
|
||||
// Add spacing after prompts before installation progress
|
||||
console.log('');
|
||||
|
||||
if (spinner.isSpinning) {
|
||||
spinner.text = 'Continuing installation...';
|
||||
} else {
|
||||
|
|
@ -2139,15 +2143,11 @@ class Installer {
|
|||
* Private: Prompt for update action
|
||||
*/
|
||||
async promptUpdateAction() {
|
||||
const { default: inquirer } = await import('inquirer');
|
||||
return await inquirer.prompt([
|
||||
{
|
||||
type: 'list',
|
||||
name: 'action',
|
||||
const action = await prompts.select({
|
||||
message: 'What would you like to do?',
|
||||
choices: [{ name: 'Update existing installation', value: 'update' }],
|
||||
},
|
||||
]);
|
||||
});
|
||||
return { action };
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -2156,8 +2156,6 @@ class Installer {
|
|||
* @param {Object} _legacyV4 - Legacy V4 detection result (unused in simplified version)
|
||||
*/
|
||||
async handleLegacyV4Migration(_projectDir, _legacyV4) {
|
||||
const { default: inquirer } = await import('inquirer');
|
||||
|
||||
console.log('');
|
||||
console.log(chalk.yellow.bold('⚠️ Legacy BMAD v4 detected'));
|
||||
console.log(chalk.yellow('─'.repeat(80)));
|
||||
|
|
@ -2172,26 +2170,22 @@ class Installer {
|
|||
console.log(chalk.dim('If your v4 installation set up rules or commands, you should remove those as well.'));
|
||||
console.log('');
|
||||
|
||||
const { proceed } = await inquirer.prompt([
|
||||
{
|
||||
type: 'list',
|
||||
name: 'proceed',
|
||||
const proceed = await prompts.select({
|
||||
message: 'What would you like to do?',
|
||||
choices: [
|
||||
{
|
||||
name: 'Exit and clean up manually (recommended)',
|
||||
value: 'exit',
|
||||
short: 'Exit installation',
|
||||
hint: 'Exit installation',
|
||||
},
|
||||
{
|
||||
name: 'Continue with installation anyway',
|
||||
value: 'continue',
|
||||
short: 'Continue',
|
||||
hint: 'Continue',
|
||||
},
|
||||
],
|
||||
default: 'exit',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
if (proceed === 'exit') {
|
||||
console.log('');
|
||||
|
|
@ -2437,7 +2431,6 @@ class Installer {
|
|||
|
||||
console.log(chalk.yellow(`\n⚠️ Found ${customModulesWithMissingSources.length} custom module(s) with missing sources:`));
|
||||
|
||||
const { default: inquirer } = await import('inquirer');
|
||||
let keptCount = 0;
|
||||
let updatedCount = 0;
|
||||
let removedCount = 0;
|
||||
|
|
@ -2451,12 +2444,12 @@ class Installer {
|
|||
{
|
||||
name: 'Keep installed (will not be processed)',
|
||||
value: 'keep',
|
||||
short: 'Keep',
|
||||
hint: 'Keep',
|
||||
},
|
||||
{
|
||||
name: 'Specify new source location',
|
||||
value: 'update',
|
||||
short: 'Update',
|
||||
hint: 'Update',
|
||||
},
|
||||
];
|
||||
|
||||
|
|
@ -2465,33 +2458,27 @@ class Installer {
|
|||
choices.push({
|
||||
name: '⚠️ REMOVE module completely (destructive!)',
|
||||
value: 'remove',
|
||||
short: 'Remove',
|
||||
hint: 'Remove',
|
||||
});
|
||||
}
|
||||
|
||||
const { action } = await inquirer.prompt([
|
||||
{
|
||||
type: 'list',
|
||||
name: 'action',
|
||||
const action = await prompts.select({
|
||||
message: `How would you like to handle "${missing.name}"?`,
|
||||
choices,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
switch (action) {
|
||||
case 'update': {
|
||||
const { newSourcePath } = await inquirer.prompt([
|
||||
{
|
||||
type: 'input',
|
||||
name: 'newSourcePath',
|
||||
// Use sync validation because @clack/prompts doesn't support async validate
|
||||
const newSourcePath = await prompts.text({
|
||||
message: 'Enter the new path to the custom module:',
|
||||
default: missing.sourcePath,
|
||||
validate: async (input) => {
|
||||
validate: (input) => {
|
||||
if (!input || input.trim() === '') {
|
||||
return 'Please enter a path';
|
||||
}
|
||||
const expandedPath = path.resolve(input.trim());
|
||||
if (!(await fs.pathExists(expandedPath))) {
|
||||
if (!fs.pathExistsSync(expandedPath)) {
|
||||
return 'Path does not exist';
|
||||
}
|
||||
// Check if it looks like a valid module
|
||||
|
|
@ -2499,13 +2486,12 @@ class Installer {
|
|||
const agentsPath = path.join(expandedPath, 'agents');
|
||||
const workflowsPath = path.join(expandedPath, 'workflows');
|
||||
|
||||
if (!(await fs.pathExists(moduleYamlPath)) && !(await fs.pathExists(agentsPath)) && !(await fs.pathExists(workflowsPath))) {
|
||||
if (!fs.pathExistsSync(moduleYamlPath) && !fs.pathExistsSync(agentsPath) && !fs.pathExistsSync(workflowsPath)) {
|
||||
return 'Path does not appear to contain a valid custom module';
|
||||
}
|
||||
return true;
|
||||
return; // clack expects undefined for valid input
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
// Update the source in manifest
|
||||
const resolvedPath = path.resolve(newSourcePath.trim());
|
||||
|
|
@ -2531,46 +2517,38 @@ class Installer {
|
|||
console.log(chalk.red.bold(`\n⚠️ WARNING: This will PERMANENTLY DELETE "${missing.name}" and all its files!`));
|
||||
console.log(chalk.red(` Module location: ${path.join(bmadDir, missing.id)}`));
|
||||
|
||||
const { confirm } = await inquirer.prompt([
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'confirm',
|
||||
const confirmDelete = await prompts.confirm({
|
||||
message: chalk.red.bold('Are you absolutely sure you want to delete this module?'),
|
||||
default: false,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
if (confirm) {
|
||||
const { typedConfirm } = await inquirer.prompt([
|
||||
{
|
||||
type: 'input',
|
||||
name: 'typedConfirm',
|
||||
if (confirmDelete) {
|
||||
const typedConfirm = await prompts.text({
|
||||
message: chalk.red.bold('Type "DELETE" to confirm permanent deletion:'),
|
||||
validate: (input) => {
|
||||
if (input !== 'DELETE') {
|
||||
return chalk.red('You must type "DELETE" exactly to proceed');
|
||||
}
|
||||
return true;
|
||||
return; // clack expects undefined for valid input
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
if (typedConfirm === 'DELETE') {
|
||||
// Remove the module from filesystem and manifest
|
||||
const modulePath = path.join(bmadDir, moduleId);
|
||||
const modulePath = path.join(bmadDir, missing.id);
|
||||
if (await fs.pathExists(modulePath)) {
|
||||
const fsExtra = require('fs-extra');
|
||||
await fsExtra.remove(modulePath);
|
||||
console.log(chalk.yellow(` ✓ Deleted module directory: ${path.relative(projectRoot, modulePath)}`));
|
||||
}
|
||||
|
||||
await this.manifest.removeModule(bmadDir, moduleId);
|
||||
await this.manifest.removeCustomModule(bmadDir, moduleId);
|
||||
await this.manifest.removeModule(bmadDir, missing.id);
|
||||
await this.manifest.removeCustomModule(bmadDir, missing.id);
|
||||
console.log(chalk.yellow(` ✓ Removed from manifest`));
|
||||
|
||||
// Also remove from installedModules list
|
||||
if (installedModules && installedModules.includes(moduleId)) {
|
||||
const index = installedModules.indexOf(moduleId);
|
||||
if (installedModules && installedModules.includes(missing.id)) {
|
||||
const index = installedModules.indexOf(missing.id);
|
||||
if (index !== -1) {
|
||||
installedModules.splice(index, 1);
|
||||
}
|
||||
|
|
@ -2591,7 +2569,7 @@ class Installer {
|
|||
}
|
||||
case 'keep': {
|
||||
keptCount++;
|
||||
keptModulesWithoutSources.push(moduleId);
|
||||
keptModulesWithoutSources.push(missing.id);
|
||||
console.log(chalk.dim(` Module will be kept as-is`));
|
||||
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ const {
|
|||
resolveSubagentFiles,
|
||||
} = require('./shared/module-injections');
|
||||
const { getAgentsFromBmad, getAgentsFromDir } = require('./shared/bmad-artifacts');
|
||||
const prompts = require('../../../lib/prompts');
|
||||
|
||||
/**
|
||||
* Google Antigravity IDE setup handler
|
||||
|
|
@ -26,6 +27,21 @@ class AntigravitySetup extends BaseIdeSetup {
|
|||
this.workflowsDir = 'workflows';
|
||||
}
|
||||
|
||||
/**
|
||||
* Prompt for subagent installation location
|
||||
* @returns {Promise<string>} Selected location ('project' or 'user')
|
||||
*/
|
||||
async _promptInstallLocation() {
|
||||
return prompts.select({
|
||||
message: 'Where would you like to install Antigravity subagents?',
|
||||
choices: [
|
||||
{ name: 'Project level (.agent/agents/)', value: 'project' },
|
||||
{ name: 'User level (~/.agent/agents/)', value: 'user' },
|
||||
],
|
||||
default: 'project',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect configuration choices before installation
|
||||
* @param {Object} options - Configuration options
|
||||
|
|
@ -57,21 +73,7 @@ class AntigravitySetup extends BaseIdeSetup {
|
|||
config.subagentChoices = await this.promptSubagentInstallation(injectionConfig.subagents);
|
||||
|
||||
if (config.subagentChoices.install !== 'none') {
|
||||
// Ask for installation location
|
||||
const { default: inquirer } = await import('inquirer');
|
||||
const locationAnswer = await inquirer.prompt([
|
||||
{
|
||||
type: 'list',
|
||||
name: 'location',
|
||||
message: 'Where would you like to install Antigravity subagents?',
|
||||
choices: [
|
||||
{ name: 'Project level (.agent/agents/)', value: 'project' },
|
||||
{ name: 'User level (~/.agent/agents/)', value: 'user' },
|
||||
],
|
||||
default: 'project',
|
||||
},
|
||||
]);
|
||||
config.installLocation = locationAnswer.location;
|
||||
config.installLocation = await this._promptInstallLocation();
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
|
|
@ -297,20 +299,7 @@ class AntigravitySetup extends BaseIdeSetup {
|
|||
choices = await this.promptSubagentInstallation(config.subagents);
|
||||
|
||||
if (choices.install !== 'none') {
|
||||
const { default: inquirer } = await import('inquirer');
|
||||
const locationAnswer = await inquirer.prompt([
|
||||
{
|
||||
type: 'list',
|
||||
name: 'location',
|
||||
message: 'Where would you like to install Antigravity subagents?',
|
||||
choices: [
|
||||
{ name: 'Project level (.agent/agents/)', value: 'project' },
|
||||
{ name: 'User level (~/.agent/agents/)', value: 'user' },
|
||||
],
|
||||
default: 'project',
|
||||
},
|
||||
]);
|
||||
location = locationAnswer.location;
|
||||
location = await this._promptInstallLocation();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -334,13 +323,8 @@ class AntigravitySetup extends BaseIdeSetup {
|
|||
* Prompt user for subagent installation preferences
|
||||
*/
|
||||
async promptSubagentInstallation(subagentConfig) {
|
||||
const { default: inquirer } = await import('inquirer');
|
||||
|
||||
// First ask if they want to install subagents
|
||||
const { install } = await inquirer.prompt([
|
||||
{
|
||||
type: 'list',
|
||||
name: 'install',
|
||||
const install = await prompts.select({
|
||||
message: 'Would you like to install Antigravity subagents for enhanced functionality?',
|
||||
choices: [
|
||||
{ name: 'Yes, install all subagents', value: 'all' },
|
||||
|
|
@ -348,8 +332,7 @@ class AntigravitySetup extends BaseIdeSetup {
|
|||
{ name: 'No, skip subagent installation', value: 'none' },
|
||||
],
|
||||
default: 'all',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
if (install === 'selective') {
|
||||
// Show list of available subagents with descriptions
|
||||
|
|
@ -361,18 +344,14 @@ class AntigravitySetup extends BaseIdeSetup {
|
|||
'document-reviewer.md': 'Document quality review',
|
||||
};
|
||||
|
||||
const { selected } = await inquirer.prompt([
|
||||
{
|
||||
type: 'checkbox',
|
||||
name: 'selected',
|
||||
message: 'Select subagents to install:',
|
||||
const selected = await prompts.multiselect({
|
||||
message: `Select subagents to install ${chalk.dim('(↑/↓ navigate, SPACE select, ENTER confirm)')}:`,
|
||||
choices: subagentConfig.files.map((file) => ({
|
||||
name: `${file.replace('.md', '')} - ${subagentInfo[file] || 'Specialized assistant'}`,
|
||||
value: file,
|
||||
checked: true,
|
||||
})),
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
return { install: 'selective', selected };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ const {
|
|||
resolveSubagentFiles,
|
||||
} = require('./shared/module-injections');
|
||||
const { getAgentsFromBmad, getAgentsFromDir } = require('./shared/bmad-artifacts');
|
||||
const prompts = require('../../../lib/prompts');
|
||||
|
||||
/**
|
||||
* Claude Code IDE setup handler
|
||||
|
|
@ -25,6 +26,21 @@ class ClaudeCodeSetup extends BaseIdeSetup {
|
|||
this.agentsDir = 'agents';
|
||||
}
|
||||
|
||||
/**
|
||||
* Prompt for subagent installation location
|
||||
* @returns {Promise<string>} Selected location ('project' or 'user')
|
||||
*/
|
||||
async promptInstallLocation() {
|
||||
return prompts.select({
|
||||
message: 'Where would you like to install Claude Code subagents?',
|
||||
choices: [
|
||||
{ name: 'Project level (.claude/agents/)', value: 'project' },
|
||||
{ name: 'User level (~/.claude/agents/)', value: 'user' },
|
||||
],
|
||||
default: 'project',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect configuration choices before installation
|
||||
* @param {Object} options - Configuration options
|
||||
|
|
@ -56,21 +72,7 @@ class ClaudeCodeSetup extends BaseIdeSetup {
|
|||
config.subagentChoices = await this.promptSubagentInstallation(injectionConfig.subagents);
|
||||
|
||||
if (config.subagentChoices.install !== 'none') {
|
||||
// Ask for installation location
|
||||
const { default: inquirer } = await import('inquirer');
|
||||
const locationAnswer = await inquirer.prompt([
|
||||
{
|
||||
type: 'list',
|
||||
name: 'location',
|
||||
message: 'Where would you like to install Claude Code subagents?',
|
||||
choices: [
|
||||
{ name: 'Project level (.claude/agents/)', value: 'project' },
|
||||
{ name: 'User level (~/.claude/agents/)', value: 'user' },
|
||||
],
|
||||
default: 'project',
|
||||
},
|
||||
]);
|
||||
config.installLocation = locationAnswer.location;
|
||||
config.installLocation = await this.promptInstallLocation();
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
|
|
@ -305,20 +307,7 @@ class ClaudeCodeSetup extends BaseIdeSetup {
|
|||
choices = await this.promptSubagentInstallation(config.subagents);
|
||||
|
||||
if (choices.install !== 'none') {
|
||||
const { default: inquirer } = await import('inquirer');
|
||||
const locationAnswer = await inquirer.prompt([
|
||||
{
|
||||
type: 'list',
|
||||
name: 'location',
|
||||
message: 'Where would you like to install Claude Code subagents?',
|
||||
choices: [
|
||||
{ name: 'Project level (.claude/agents/)', value: 'project' },
|
||||
{ name: 'User level (~/.claude/agents/)', value: 'user' },
|
||||
],
|
||||
default: 'project',
|
||||
},
|
||||
]);
|
||||
location = locationAnswer.location;
|
||||
location = await this.promptInstallLocation();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -342,13 +331,8 @@ class ClaudeCodeSetup extends BaseIdeSetup {
|
|||
* Prompt user for subagent installation preferences
|
||||
*/
|
||||
async promptSubagentInstallation(subagentConfig) {
|
||||
const { default: inquirer } = await import('inquirer');
|
||||
|
||||
// First ask if they want to install subagents
|
||||
const { install } = await inquirer.prompt([
|
||||
{
|
||||
type: 'list',
|
||||
name: 'install',
|
||||
const install = await prompts.select({
|
||||
message: 'Would you like to install Claude Code subagents for enhanced functionality?',
|
||||
choices: [
|
||||
{ name: 'Yes, install all subagents', value: 'all' },
|
||||
|
|
@ -356,8 +340,7 @@ class ClaudeCodeSetup extends BaseIdeSetup {
|
|||
{ name: 'No, skip subagent installation', value: 'none' },
|
||||
],
|
||||
default: 'all',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
if (install === 'selective') {
|
||||
// Show list of available subagents with descriptions
|
||||
|
|
@ -369,18 +352,14 @@ class ClaudeCodeSetup extends BaseIdeSetup {
|
|||
'document-reviewer.md': 'Document quality review',
|
||||
};
|
||||
|
||||
const { selected } = await inquirer.prompt([
|
||||
{
|
||||
type: 'checkbox',
|
||||
name: 'selected',
|
||||
message: 'Select subagents to install:',
|
||||
choices: subagentConfig.files.map((file) => ({
|
||||
name: `${file.replace('.md', '')} - ${subagentInfo[file] || 'Specialized assistant'}`,
|
||||
const selected = await prompts.multiselect({
|
||||
message: `Select subagents to install ${chalk.dim('(↑/↓ navigate, SPACE select, ENTER confirm)')}:`,
|
||||
options: subagentConfig.files.map((file) => ({
|
||||
label: `${file.replace('.md', '')} - ${subagentInfo[file] || 'Specialized assistant'}`,
|
||||
value: file,
|
||||
checked: true,
|
||||
})),
|
||||
},
|
||||
]);
|
||||
initialValues: subagentConfig.files,
|
||||
});
|
||||
|
||||
return { install: 'selective', selected };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ const { BaseIdeSetup } = require('./_base-ide');
|
|||
const { WorkflowCommandGenerator } = require('./shared/workflow-command-generator');
|
||||
const { AgentCommandGenerator } = require('./shared/agent-command-generator');
|
||||
const { getTasksFromBmad } = require('./shared/bmad-artifacts');
|
||||
const prompts = require('../../../lib/prompts');
|
||||
|
||||
/**
|
||||
* Codex setup handler (CLI mode)
|
||||
|
|
@ -21,16 +22,11 @@ class CodexSetup extends BaseIdeSetup {
|
|||
* @returns {Object} Collected configuration
|
||||
*/
|
||||
async collectConfiguration(options = {}) {
|
||||
const { default: inquirer } = await import('inquirer');
|
||||
|
||||
let confirmed = false;
|
||||
let installLocation = 'global';
|
||||
|
||||
while (!confirmed) {
|
||||
const { location } = await inquirer.prompt([
|
||||
{
|
||||
type: 'list',
|
||||
name: 'location',
|
||||
installLocation = await prompts.select({
|
||||
message: 'Where would you like to install Codex CLI prompts?',
|
||||
choices: [
|
||||
{
|
||||
|
|
@ -43,10 +39,7 @@ class CodexSetup extends BaseIdeSetup {
|
|||
},
|
||||
],
|
||||
default: 'global',
|
||||
},
|
||||
]);
|
||||
|
||||
installLocation = location;
|
||||
});
|
||||
|
||||
// Display detailed instructions for the chosen option
|
||||
console.log('');
|
||||
|
|
@ -57,16 +50,10 @@ class CodexSetup extends BaseIdeSetup {
|
|||
}
|
||||
|
||||
// Confirm the choice
|
||||
const { proceed } = await inquirer.prompt([
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'proceed',
|
||||
confirmed = await prompts.confirm({
|
||||
message: 'Proceed with this installation option?',
|
||||
default: true,
|
||||
},
|
||||
]);
|
||||
|
||||
confirmed = proceed;
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
console.log(chalk.yellow("\n Let's choose a different installation option.\n"));
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ const path = require('node:path');
|
|||
const { BaseIdeSetup } = require('./_base-ide');
|
||||
const chalk = require('chalk');
|
||||
const { AgentCommandGenerator } = require('./shared/agent-command-generator');
|
||||
const prompts = require('../../../lib/prompts');
|
||||
|
||||
/**
|
||||
* GitHub Copilot setup handler
|
||||
|
|
@ -21,16 +22,12 @@ class GitHubCopilotSetup extends BaseIdeSetup {
|
|||
* @returns {Object} Collected configuration
|
||||
*/
|
||||
async collectConfiguration(options = {}) {
|
||||
const { default: inquirer } = await import('inquirer');
|
||||
const config = {};
|
||||
|
||||
console.log('\n' + chalk.blue(' 🔧 VS Code Settings Configuration'));
|
||||
console.log(chalk.dim(' GitHub Copilot works best with specific settings\n'));
|
||||
|
||||
const response = await inquirer.prompt([
|
||||
{
|
||||
type: 'list',
|
||||
name: 'configChoice',
|
||||
config.vsCodeConfig = await prompts.select({
|
||||
message: 'How would you like to configure VS Code settings?',
|
||||
choices: [
|
||||
{ name: 'Use recommended defaults (fastest)', value: 'defaults' },
|
||||
|
|
@ -38,12 +35,10 @@ class GitHubCopilotSetup extends BaseIdeSetup {
|
|||
{ name: 'Skip settings configuration', value: 'skip' },
|
||||
],
|
||||
default: 'defaults',
|
||||
},
|
||||
]);
|
||||
config.vsCodeConfig = response.configChoice;
|
||||
});
|
||||
|
||||
if (response.configChoice === 'manual') {
|
||||
config.manualSettings = await inquirer.prompt([
|
||||
if (config.vsCodeConfig === 'manual') {
|
||||
config.manualSettings = await prompts.prompt([
|
||||
{
|
||||
type: 'input',
|
||||
name: 'maxRequests',
|
||||
|
|
@ -52,7 +47,8 @@ class GitHubCopilotSetup extends BaseIdeSetup {
|
|||
validate: (input) => {
|
||||
const num = parseInt(input, 10);
|
||||
if (isNaN(num)) return 'Enter a valid number 1-50';
|
||||
return (num >= 1 && num <= 50) || 'Enter 1-50';
|
||||
if (num < 1 || num > 50) return 'Enter a number between 1-50';
|
||||
return true;
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,432 @@
|
|||
/**
|
||||
* @clack/prompts wrapper for BMAD CLI
|
||||
*
|
||||
* This module provides a unified interface for CLI prompts using @clack/prompts.
|
||||
* It replaces Inquirer.js to fix Windows arrow key navigation issues (libuv #852).
|
||||
*
|
||||
* @module prompts
|
||||
*/
|
||||
|
||||
let _clack = null;
|
||||
|
||||
/**
|
||||
* Lazy-load @clack/prompts (ESM module)
|
||||
* @returns {Promise<Object>} The clack prompts module
|
||||
*/
|
||||
async function getClack() {
|
||||
if (!_clack) {
|
||||
_clack = await import('@clack/prompts');
|
||||
}
|
||||
return _clack;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle user cancellation gracefully
|
||||
* @param {any} value - The value to check
|
||||
* @param {string} [message='Operation cancelled'] - Message to display
|
||||
* @returns {boolean} True if cancelled
|
||||
*/
|
||||
async function handleCancel(value, message = 'Operation cancelled') {
|
||||
const clack = await getClack();
|
||||
if (clack.isCancel(value)) {
|
||||
clack.cancel(message);
|
||||
process.exit(0);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display intro message
|
||||
* @param {string} message - The intro message
|
||||
*/
|
||||
async function intro(message) {
|
||||
const clack = await getClack();
|
||||
clack.intro(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display outro message
|
||||
* @param {string} message - The outro message
|
||||
*/
|
||||
async function outro(message) {
|
||||
const clack = await getClack();
|
||||
clack.outro(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a note/info box
|
||||
* @param {string} message - The note content
|
||||
* @param {string} [title] - Optional title
|
||||
*/
|
||||
async function note(message, title) {
|
||||
const clack = await getClack();
|
||||
clack.note(message, title);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a spinner for async operations
|
||||
* @returns {Object} Spinner controller with start, stop, message methods
|
||||
*/
|
||||
async function spinner() {
|
||||
const clack = await getClack();
|
||||
return clack.spinner();
|
||||
}
|
||||
|
||||
/**
|
||||
* Single-select prompt (replaces Inquirer 'list' type)
|
||||
* @param {Object} options - Prompt options
|
||||
* @param {string} options.message - The question to ask
|
||||
* @param {Array} options.choices - Array of choices [{name, value, hint?}]
|
||||
* @param {any} [options.default] - Default selected value
|
||||
* @returns {Promise<any>} Selected value
|
||||
*/
|
||||
async function select(options) {
|
||||
const clack = await getClack();
|
||||
|
||||
// Convert Inquirer-style choices to clack format
|
||||
// Handle both object choices {name, value, hint} and primitive choices (string/number)
|
||||
const clackOptions = options.choices
|
||||
.filter((c) => c.type !== 'separator') // Skip separators for now
|
||||
.map((choice) => {
|
||||
if (typeof choice === 'string' || typeof choice === 'number') {
|
||||
return { value: choice, label: String(choice) };
|
||||
}
|
||||
return {
|
||||
value: choice.value === undefined ? choice.name : choice.value,
|
||||
label: choice.name || choice.label || String(choice.value),
|
||||
hint: choice.hint || choice.description,
|
||||
};
|
||||
});
|
||||
|
||||
// Find initial value
|
||||
let initialValue;
|
||||
if (options.default !== undefined) {
|
||||
initialValue = options.default;
|
||||
}
|
||||
|
||||
const result = await clack.select({
|
||||
message: options.message,
|
||||
options: clackOptions,
|
||||
initialValue,
|
||||
});
|
||||
|
||||
await handleCancel(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Multi-select prompt (replaces Inquirer 'checkbox' type)
|
||||
* @param {Object} options - Prompt options
|
||||
* @param {string} options.message - The question to ask
|
||||
* @param {Array} options.choices - Array of choices [{name, value, checked?, hint?}]
|
||||
* @param {boolean} [options.required=false] - Whether at least one must be selected
|
||||
* @returns {Promise<Array>} Array of selected values
|
||||
*/
|
||||
async function multiselect(options) {
|
||||
const clack = await getClack();
|
||||
|
||||
// Support both clack-native (options) and Inquirer-style (choices) APIs
|
||||
let clackOptions;
|
||||
let initialValues;
|
||||
|
||||
if (options.options) {
|
||||
// Native clack format: options with label/value
|
||||
clackOptions = options.options;
|
||||
initialValues = options.initialValues || [];
|
||||
} else {
|
||||
// Convert Inquirer-style choices to clack format
|
||||
// Handle both object choices {name, value, hint} and primitive choices (string/number)
|
||||
clackOptions = options.choices
|
||||
.filter((c) => c.type !== 'separator') // Skip separators
|
||||
.map((choice) => {
|
||||
if (typeof choice === 'string' || typeof choice === 'number') {
|
||||
return { value: choice, label: String(choice) };
|
||||
}
|
||||
return {
|
||||
value: choice.value === undefined ? choice.name : choice.value,
|
||||
label: choice.name || choice.label || String(choice.value),
|
||||
hint: choice.hint || choice.description,
|
||||
};
|
||||
});
|
||||
|
||||
// Find initial values (pre-checked items)
|
||||
initialValues = options.choices
|
||||
.filter((c) => c.checked && c.type !== 'separator')
|
||||
.map((c) => (c.value === undefined ? c.name : c.value));
|
||||
}
|
||||
|
||||
const result = await clack.multiselect({
|
||||
message: options.message,
|
||||
options: clackOptions,
|
||||
initialValues: initialValues.length > 0 ? initialValues : undefined,
|
||||
required: options.required || false,
|
||||
});
|
||||
|
||||
await handleCancel(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Grouped multi-select prompt for categorized options
|
||||
* @param {Object} options - Prompt options
|
||||
* @param {string} options.message - The question to ask
|
||||
* @param {Object} options.options - Object mapping group names to arrays of choices
|
||||
* @param {Array} [options.initialValues] - Array of initially selected values
|
||||
* @param {boolean} [options.required=false] - Whether at least one must be selected
|
||||
* @param {boolean} [options.selectableGroups=false] - Whether groups can be selected as a whole
|
||||
* @returns {Promise<Array>} Array of selected values
|
||||
*/
|
||||
async function groupMultiselect(options) {
|
||||
const clack = await getClack();
|
||||
|
||||
const result = await clack.groupMultiselect({
|
||||
message: options.message,
|
||||
options: options.options,
|
||||
initialValues: options.initialValues,
|
||||
required: options.required || false,
|
||||
});
|
||||
|
||||
await handleCancel(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirm prompt (replaces Inquirer 'confirm' type)
|
||||
* @param {Object} options - Prompt options
|
||||
* @param {string} options.message - The question to ask
|
||||
* @param {boolean} [options.default=true] - Default value
|
||||
* @returns {Promise<boolean>} User's answer
|
||||
*/
|
||||
async function confirm(options) {
|
||||
const clack = await getClack();
|
||||
|
||||
const result = await clack.confirm({
|
||||
message: options.message,
|
||||
initialValue: options.default === undefined ? true : options.default,
|
||||
});
|
||||
|
||||
await handleCancel(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Text input prompt (replaces Inquirer 'input' type)
|
||||
* @param {Object} options - Prompt options
|
||||
* @param {string} options.message - The question to ask
|
||||
* @param {string} [options.default] - Default value
|
||||
* @param {string} [options.placeholder] - Placeholder text (defaults to options.default if not provided)
|
||||
* @param {Function} [options.validate] - Validation function
|
||||
* @returns {Promise<string>} User's input
|
||||
*/
|
||||
async function text(options) {
|
||||
const clack = await getClack();
|
||||
|
||||
// Use default as placeholder if placeholder not explicitly provided
|
||||
// This shows the default value as grayed-out hint text
|
||||
const placeholder = options.placeholder === undefined ? options.default : options.placeholder;
|
||||
|
||||
const result = await clack.text({
|
||||
message: options.message,
|
||||
defaultValue: options.default,
|
||||
placeholder: typeof placeholder === 'string' ? placeholder : undefined,
|
||||
validate: options.validate,
|
||||
});
|
||||
|
||||
await handleCancel(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Password input prompt (replaces Inquirer 'password' type)
|
||||
* @param {Object} options - Prompt options
|
||||
* @param {string} options.message - The question to ask
|
||||
* @param {Function} [options.validate] - Validation function
|
||||
* @returns {Promise<string>} User's input
|
||||
*/
|
||||
async function password(options) {
|
||||
const clack = await getClack();
|
||||
|
||||
const result = await clack.password({
|
||||
message: options.message,
|
||||
validate: options.validate,
|
||||
});
|
||||
|
||||
await handleCancel(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Group multiple prompts together
|
||||
* @param {Object} prompts - Object of prompt functions
|
||||
* @param {Object} [options] - Group options
|
||||
* @returns {Promise<Object>} Object with all answers
|
||||
*/
|
||||
async function group(prompts, options = {}) {
|
||||
const clack = await getClack();
|
||||
|
||||
const result = await clack.group(prompts, {
|
||||
onCancel: () => {
|
||||
clack.cancel('Operation cancelled');
|
||||
process.exit(0);
|
||||
},
|
||||
...options,
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run tasks with spinner feedback
|
||||
* @param {Array} tasks - Array of task objects [{title, task, enabled?}]
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function tasks(taskList) {
|
||||
const clack = await getClack();
|
||||
await clack.tasks(taskList);
|
||||
}
|
||||
|
||||
/**
|
||||
* Log messages with styling
|
||||
*/
|
||||
const log = {
|
||||
async info(message) {
|
||||
const clack = await getClack();
|
||||
clack.log.info(message);
|
||||
},
|
||||
async success(message) {
|
||||
const clack = await getClack();
|
||||
clack.log.success(message);
|
||||
},
|
||||
async warn(message) {
|
||||
const clack = await getClack();
|
||||
clack.log.warn(message);
|
||||
},
|
||||
async error(message) {
|
||||
const clack = await getClack();
|
||||
clack.log.error(message);
|
||||
},
|
||||
async message(message) {
|
||||
const clack = await getClack();
|
||||
clack.log.message(message);
|
||||
},
|
||||
async step(message) {
|
||||
const clack = await getClack();
|
||||
clack.log.step(message);
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Execute an array of Inquirer-style questions using @clack/prompts
|
||||
* This provides compatibility with dynamic question arrays
|
||||
* @param {Array} questions - Array of Inquirer-style question objects
|
||||
* @returns {Promise<Object>} Object with answers keyed by question name
|
||||
*/
|
||||
async function prompt(questions) {
|
||||
const answers = {};
|
||||
|
||||
for (const question of questions) {
|
||||
const { type, name, message, choices, default: defaultValue, validate, when } = question;
|
||||
|
||||
// Handle conditional questions via 'when' property
|
||||
if (when !== undefined) {
|
||||
const shouldAsk = typeof when === 'function' ? await when(answers) : when;
|
||||
if (!shouldAsk) continue;
|
||||
}
|
||||
|
||||
let answer;
|
||||
|
||||
switch (type) {
|
||||
case 'input': {
|
||||
// Note: @clack/prompts doesn't support async validation, so validate must be sync
|
||||
answer = await text({
|
||||
message,
|
||||
default: typeof defaultValue === 'function' ? defaultValue(answers) : defaultValue,
|
||||
validate: validate
|
||||
? (val) => {
|
||||
const result = validate(val, answers);
|
||||
if (result instanceof Promise) {
|
||||
throw new TypeError('Async validation is not supported by @clack/prompts. Please use synchronous validation.');
|
||||
}
|
||||
return result === true ? undefined : result;
|
||||
}
|
||||
: undefined,
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case 'confirm': {
|
||||
answer = await confirm({
|
||||
message,
|
||||
default: typeof defaultValue === 'function' ? defaultValue(answers) : defaultValue,
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case 'list': {
|
||||
answer = await select({
|
||||
message,
|
||||
choices: choices || [],
|
||||
default: typeof defaultValue === 'function' ? defaultValue(answers) : defaultValue,
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case 'checkbox': {
|
||||
answer = await multiselect({
|
||||
message,
|
||||
choices: choices || [],
|
||||
required: false,
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case 'password': {
|
||||
// Note: @clack/prompts doesn't support async validation, so validate must be sync
|
||||
answer = await password({
|
||||
message,
|
||||
validate: validate
|
||||
? (val) => {
|
||||
const result = validate(val, answers);
|
||||
if (result instanceof Promise) {
|
||||
throw new TypeError('Async validation is not supported by @clack/prompts. Please use synchronous validation.');
|
||||
}
|
||||
return result === true ? undefined : result;
|
||||
}
|
||||
: undefined,
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
// Default to text input for unknown types
|
||||
answer = await text({
|
||||
message,
|
||||
default: typeof defaultValue === 'function' ? defaultValue(answers) : defaultValue,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
answers[name] = answer;
|
||||
}
|
||||
|
||||
return answers;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getClack,
|
||||
handleCancel,
|
||||
intro,
|
||||
outro,
|
||||
note,
|
||||
spinner,
|
||||
select,
|
||||
multiselect,
|
||||
groupMultiselect,
|
||||
confirm,
|
||||
text,
|
||||
password,
|
||||
group,
|
||||
tasks,
|
||||
log,
|
||||
prompt,
|
||||
};
|
||||
|
|
@ -4,16 +4,21 @@ const os = require('node:os');
|
|||
const fs = require('fs-extra');
|
||||
const { CLIUtils } = require('./cli-utils');
|
||||
const { CustomHandler } = require('../installers/lib/custom/handler');
|
||||
const prompts = require('./prompts');
|
||||
|
||||
// Lazy-load inquirer (ESM module) to avoid ERR_REQUIRE_ESM
|
||||
let _inquirer = null;
|
||||
async function getInquirer() {
|
||||
if (!_inquirer) {
|
||||
_inquirer = (await import('inquirer')).default;
|
||||
// Separator class for visual grouping in select/multiselect prompts
|
||||
// Note: @clack/prompts doesn't support separators natively, they are filtered out
|
||||
class Separator {
|
||||
constructor(text = '────────') {
|
||||
this.line = text;
|
||||
this.name = text;
|
||||
}
|
||||
return _inquirer;
|
||||
type = 'separator';
|
||||
}
|
||||
|
||||
// Separator for choice lists (compatible interface)
|
||||
const choiceUtils = { Separator };
|
||||
|
||||
/**
|
||||
* UI utilities for the installer
|
||||
*/
|
||||
|
|
@ -23,7 +28,6 @@ class UI {
|
|||
* @returns {Object} Installation configuration
|
||||
*/
|
||||
async promptInstall() {
|
||||
const inquirer = await getInquirer();
|
||||
CLIUtils.displayLogo();
|
||||
|
||||
// Display version-specific start message from install-messages.yaml
|
||||
|
|
@ -113,26 +117,20 @@ class UI {
|
|||
console.log(chalk.yellow('─'.repeat(80)));
|
||||
console.log('');
|
||||
|
||||
const { proceed } = await inquirer.prompt([
|
||||
{
|
||||
type: 'list',
|
||||
name: 'proceed',
|
||||
const proceed = await prompts.select({
|
||||
message: 'What would you like to do?',
|
||||
choices: [
|
||||
{
|
||||
name: 'Cancel and do a fresh install (recommended)',
|
||||
value: 'cancel',
|
||||
short: 'Cancel installation',
|
||||
},
|
||||
{
|
||||
name: 'Proceed anyway (will attempt update, potentially may fail or have unstable behavior)',
|
||||
value: 'proceed',
|
||||
short: 'Proceed with update',
|
||||
},
|
||||
],
|
||||
default: 'cancel',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
if (proceed === 'cancel') {
|
||||
console.log('');
|
||||
|
|
@ -188,14 +186,10 @@ class UI {
|
|||
|
||||
// If Claude Code was selected, ask about TTS
|
||||
if (claudeCodeSelected) {
|
||||
const { enableTts } = await inquirer.prompt([
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'enableTts',
|
||||
const enableTts = await prompts.confirm({
|
||||
message: 'Claude Code supports TTS (Text-to-Speech). Would you like to enable it?',
|
||||
default: false,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
if (enableTts) {
|
||||
agentVibesConfig = { enabled: true, alreadyInstalled: false };
|
||||
|
|
@ -250,18 +244,11 @@ class UI {
|
|||
// Common actions
|
||||
choices.push({ name: 'Modify BMAD Installation', value: 'update' });
|
||||
|
||||
const promptResult = await inquirer.prompt([
|
||||
{
|
||||
type: 'list',
|
||||
name: 'actionType',
|
||||
actionType = await prompts.select({
|
||||
message: 'What would you like to do?',
|
||||
choices: choices,
|
||||
default: choices[0].value, // Use the first option as default
|
||||
},
|
||||
]);
|
||||
|
||||
// Extract actionType from prompt result
|
||||
actionType = promptResult.actionType;
|
||||
default: choices[0].value,
|
||||
});
|
||||
|
||||
// Handle quick update separately
|
||||
if (actionType === 'quick-update') {
|
||||
|
|
@ -290,14 +277,10 @@ class UI {
|
|||
const { installedModuleIds } = await this.getExistingInstallation(confirmedDirectory);
|
||||
|
||||
console.log(chalk.dim(` Found existing modules: ${[...installedModuleIds].join(', ')}`));
|
||||
const { changeModuleSelection } = await inquirer.prompt([
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'changeModuleSelection',
|
||||
const changeModuleSelection = await prompts.confirm({
|
||||
message: 'Modify official module selection (BMad Method, BMad Builder, Creative Innovation Suite)?',
|
||||
default: false,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
let selectedModules = [];
|
||||
if (changeModuleSelection) {
|
||||
|
|
@ -310,14 +293,10 @@ class UI {
|
|||
|
||||
// After module selection, ask about custom modules
|
||||
console.log('');
|
||||
const { changeCustomModules } = await inquirer.prompt([
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'changeCustomModules',
|
||||
const changeCustomModules = await prompts.confirm({
|
||||
message: 'Modify custom module selection (add, update, or remove custom modules/agents/workflows)?',
|
||||
default: false,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
let customModuleResult = { selectedCustomModules: [], customContentConfig: { hasCustomContent: false } };
|
||||
if (changeCustomModules) {
|
||||
|
|
@ -352,15 +331,10 @@ class UI {
|
|||
let enableTts = false;
|
||||
|
||||
if (hasClaudeCode) {
|
||||
const { enableTts: enable } = await inquirer.prompt([
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'enableTts',
|
||||
enableTts = await prompts.confirm({
|
||||
message: 'Claude Code supports TTS (Text-to-Speech). Would you like to enable it?',
|
||||
default: false,
|
||||
},
|
||||
]);
|
||||
enableTts = enable;
|
||||
});
|
||||
}
|
||||
|
||||
// Core config with existing defaults (ask after TTS)
|
||||
|
|
@ -385,14 +359,10 @@ class UI {
|
|||
const { installedModuleIds } = await this.getExistingInstallation(confirmedDirectory);
|
||||
|
||||
// Ask about official modules for new installations
|
||||
const { wantsOfficialModules } = await inquirer.prompt([
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'wantsOfficialModules',
|
||||
const wantsOfficialModules = await prompts.confirm({
|
||||
message: 'Will you be installing any official BMad modules (BMad Method, BMad Builder, Creative Innovation Suite)?',
|
||||
default: true,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
let selectedOfficialModules = [];
|
||||
if (wantsOfficialModules) {
|
||||
|
|
@ -401,14 +371,10 @@ class UI {
|
|||
}
|
||||
|
||||
// Ask about custom content
|
||||
const { wantsCustomContent } = await inquirer.prompt([
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'wantsCustomContent',
|
||||
const wantsCustomContent = await prompts.confirm({
|
||||
message: 'Would you like to install a local custom module (this includes custom agents and workflows also)?',
|
||||
default: false,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
if (wantsCustomContent) {
|
||||
customContentConfig = await this.promptCustomContentSource();
|
||||
|
|
@ -459,7 +425,6 @@ class UI {
|
|||
* @returns {Object} Tool configuration
|
||||
*/
|
||||
async promptToolSelection(projectDir, selectedModules) {
|
||||
const inquirer = await getInquirer();
|
||||
// Check for existing configured IDEs - use findBmadDir to detect custom folder names
|
||||
const { Detector } = require('../installers/lib/core/detector');
|
||||
const { Installer } = require('../installers/lib/core/installer');
|
||||
|
|
@ -477,13 +442,14 @@ class UI {
|
|||
const preferredIdes = ideManager.getPreferredIdes();
|
||||
const otherIdes = ideManager.getOtherIdes();
|
||||
|
||||
// Build IDE choices array with separators
|
||||
const ideChoices = [];
|
||||
// Build grouped options object for groupMultiselect
|
||||
const groupedOptions = {};
|
||||
const processedIdes = new Set();
|
||||
const initialValues = [];
|
||||
|
||||
// First, add previously configured IDEs at the top, marked with ✅
|
||||
if (configuredIdes.length > 0) {
|
||||
ideChoices.push(new inquirer.Separator('── Previously Configured ──'));
|
||||
const configuredGroup = [];
|
||||
for (const ideValue of configuredIdes) {
|
||||
// Skip empty or invalid IDE values
|
||||
if (!ideValue || typeof ideValue !== 'string') {
|
||||
|
|
@ -496,81 +462,71 @@ class UI {
|
|||
const ide = preferredIde || otherIde;
|
||||
|
||||
if (ide) {
|
||||
ideChoices.push({
|
||||
name: `${ide.name} ✅`,
|
||||
configuredGroup.push({
|
||||
label: `${ide.name} ✅`,
|
||||
value: ide.value,
|
||||
checked: true, // Previously configured IDEs are checked by default
|
||||
});
|
||||
processedIdes.add(ide.value);
|
||||
initialValues.push(ide.value); // Pre-select configured IDEs
|
||||
} else {
|
||||
// Warn about unrecognized IDE (but don't fail)
|
||||
console.log(chalk.yellow(`⚠️ Previously configured IDE '${ideValue}' is no longer available`));
|
||||
}
|
||||
}
|
||||
if (configuredGroup.length > 0) {
|
||||
groupedOptions['Previously Configured'] = configuredGroup;
|
||||
}
|
||||
}
|
||||
|
||||
// Add preferred tools (excluding already processed)
|
||||
const remainingPreferred = preferredIdes.filter((ide) => !processedIdes.has(ide.value));
|
||||
if (remainingPreferred.length > 0) {
|
||||
ideChoices.push(new inquirer.Separator('── Recommended Tools ──'));
|
||||
for (const ide of remainingPreferred) {
|
||||
ideChoices.push({
|
||||
name: `${ide.name} ⭐`,
|
||||
value: ide.value,
|
||||
checked: false,
|
||||
});
|
||||
groupedOptions['Recommended Tools'] = remainingPreferred.map((ide) => {
|
||||
processedIdes.add(ide.value);
|
||||
}
|
||||
return {
|
||||
label: `${ide.name} ⭐`,
|
||||
value: ide.value,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
// Add other tools (excluding already processed)
|
||||
const remainingOther = otherIdes.filter((ide) => !processedIdes.has(ide.value));
|
||||
if (remainingOther.length > 0) {
|
||||
ideChoices.push(new inquirer.Separator('── Additional Tools ──'));
|
||||
for (const ide of remainingOther) {
|
||||
ideChoices.push({
|
||||
name: ide.name,
|
||||
groupedOptions['Additional Tools'] = remainingOther.map((ide) => ({
|
||||
label: ide.name,
|
||||
value: ide.value,
|
||||
checked: false,
|
||||
});
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
let answers;
|
||||
let selectedIdes = [];
|
||||
let userConfirmedNoTools = false;
|
||||
|
||||
// Loop until user selects at least one tool OR explicitly confirms no tools
|
||||
while (!userConfirmedNoTools) {
|
||||
answers = await inquirer.prompt([
|
||||
{
|
||||
type: 'checkbox',
|
||||
name: 'ides',
|
||||
message: 'Select tools to configure:',
|
||||
choices: ideChoices,
|
||||
pageSize: 30,
|
||||
},
|
||||
]);
|
||||
selectedIdes = await prompts.groupMultiselect({
|
||||
message: `Select tools to configure ${chalk.dim('(↑/↓ navigate, SPACE select, ENTER confirm)')}:`,
|
||||
options: groupedOptions,
|
||||
initialValues: initialValues.length > 0 ? initialValues : undefined,
|
||||
required: false,
|
||||
});
|
||||
|
||||
// If tools were selected, we're done
|
||||
if (answers.ides && answers.ides.length > 0) {
|
||||
if (selectedIdes && selectedIdes.length > 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Warn that no tools were selected - users often miss the spacebar requirement
|
||||
console.log();
|
||||
console.log(chalk.red.bold('⚠️ WARNING: No tools were selected!'));
|
||||
console.log(chalk.red(' You must press SPACEBAR to select items, then ENTER to confirm.'));
|
||||
console.log(chalk.red(' You must press SPACE to select items, then ENTER to confirm.'));
|
||||
console.log(chalk.red(' Simply highlighting an item does NOT select it.'));
|
||||
console.log();
|
||||
|
||||
const { goBack } = await inquirer.prompt([
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'goBack',
|
||||
const goBack = await prompts.confirm({
|
||||
message: chalk.yellow('Would you like to go back and select at least one tool?'),
|
||||
default: true,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
if (goBack) {
|
||||
// Re-display a message before looping back
|
||||
|
|
@ -582,8 +538,8 @@ class UI {
|
|||
}
|
||||
|
||||
return {
|
||||
ides: answers.ides || [],
|
||||
skipIde: !answers.ides || answers.ides.length === 0,
|
||||
ides: selectedIdes || [],
|
||||
skipIde: !selectedIdes || selectedIdes.length === 0,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -592,23 +548,17 @@ class UI {
|
|||
* @returns {Object} Update configuration
|
||||
*/
|
||||
async promptUpdate() {
|
||||
const inquirer = await getInquirer();
|
||||
const answers = await inquirer.prompt([
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'backupFirst',
|
||||
const backupFirst = await prompts.confirm({
|
||||
message: 'Create backup before updating?',
|
||||
default: true,
|
||||
},
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'preserveCustomizations',
|
||||
});
|
||||
|
||||
const preserveCustomizations = await prompts.confirm({
|
||||
message: 'Preserve local customizations?',
|
||||
default: true,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
return answers;
|
||||
return { backupFirst, preserveCustomizations };
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -617,27 +567,17 @@ class UI {
|
|||
* @returns {Array} Selected modules
|
||||
*/
|
||||
async promptModules(modules) {
|
||||
const inquirer = await getInquirer();
|
||||
const choices = modules.map((mod) => ({
|
||||
name: `${mod.name} - ${mod.description}`,
|
||||
value: mod.id,
|
||||
checked: false,
|
||||
}));
|
||||
|
||||
const { selectedModules } = await inquirer.prompt([
|
||||
{
|
||||
type: 'checkbox',
|
||||
name: 'selectedModules',
|
||||
message: 'Select modules to add:',
|
||||
const selectedModules = await prompts.multiselect({
|
||||
message: `Select modules to add ${chalk.dim('(↑/↓ navigate, SPACE select, ENTER confirm)')}:`,
|
||||
choices,
|
||||
validate: (answer) => {
|
||||
if (answer.length === 0) {
|
||||
return 'You must choose at least one module.';
|
||||
}
|
||||
return true;
|
||||
},
|
||||
},
|
||||
]);
|
||||
required: true,
|
||||
});
|
||||
|
||||
return selectedModules;
|
||||
}
|
||||
|
|
@ -649,17 +589,10 @@ class UI {
|
|||
* @returns {boolean} User confirmation
|
||||
*/
|
||||
async confirm(message, defaultValue = false) {
|
||||
const inquirer = await getInquirer();
|
||||
const { confirmed } = await inquirer.prompt([
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'confirmed',
|
||||
return await prompts.confirm({
|
||||
message,
|
||||
default: defaultValue,
|
||||
},
|
||||
]);
|
||||
|
||||
return confirmed;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -753,10 +686,9 @@ class UI {
|
|||
* Get module choices for selection
|
||||
* @param {Set} installedModuleIds - Currently installed module IDs
|
||||
* @param {Object} customContentConfig - Custom content configuration
|
||||
* @returns {Array} Module choices for inquirer
|
||||
* @returns {Array} Module choices for prompt
|
||||
*/
|
||||
async getModuleChoices(installedModuleIds, customContentConfig = null) {
|
||||
const inquirer = await getInquirer();
|
||||
const moduleChoices = [];
|
||||
const isNewInstallation = installedModuleIds.size === 0;
|
||||
|
||||
|
|
@ -811,9 +743,9 @@ class UI {
|
|||
if (allCustomModules.length > 0) {
|
||||
// Add separator for custom content, all custom modules, and official content separator
|
||||
moduleChoices.push(
|
||||
new inquirer.Separator('── Custom Content ──'),
|
||||
new choiceUtils.Separator('── Custom Content ──'),
|
||||
...allCustomModules,
|
||||
new inquirer.Separator('── Official Content ──'),
|
||||
new choiceUtils.Separator('── Official Content ──'),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -837,44 +769,43 @@ class UI {
|
|||
* @returns {Array} Selected module IDs
|
||||
*/
|
||||
async selectModules(moduleChoices, defaultSelections = []) {
|
||||
const inquirer = await getInquirer();
|
||||
const moduleAnswer = await inquirer.prompt([
|
||||
{
|
||||
type: 'checkbox',
|
||||
name: 'modules',
|
||||
message: 'Select modules to install:',
|
||||
choices: moduleChoices,
|
||||
default: defaultSelections,
|
||||
},
|
||||
]);
|
||||
// Mark choices as checked based on defaultSelections
|
||||
const choicesWithDefaults = moduleChoices.map((choice) => ({
|
||||
...choice,
|
||||
checked: defaultSelections.includes(choice.value),
|
||||
}));
|
||||
|
||||
const selected = moduleAnswer.modules || [];
|
||||
const selected = await prompts.multiselect({
|
||||
message: `Select modules to install ${chalk.dim('(↑/↓ navigate, SPACE select, ENTER confirm)')}:`,
|
||||
choices: choicesWithDefaults,
|
||||
required: false,
|
||||
});
|
||||
|
||||
return selected;
|
||||
return selected || [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Prompt for directory selection
|
||||
* @returns {Object} Directory answer from inquirer
|
||||
* @returns {Object} Directory answer from prompt
|
||||
*/
|
||||
async promptForDirectory() {
|
||||
const inquirer = await getInquirer();
|
||||
return await inquirer.prompt([
|
||||
{
|
||||
type: 'input',
|
||||
name: 'directory',
|
||||
message: `Installation directory:`,
|
||||
// Use sync validation because @clack/prompts doesn't support async validate
|
||||
const directory = await prompts.text({
|
||||
message: 'Installation directory:',
|
||||
default: process.cwd(),
|
||||
validate: async (input) => this.validateDirectory(input),
|
||||
filter: (input) => {
|
||||
// If empty, use the default
|
||||
if (!input || input.trim() === '') {
|
||||
return process.cwd();
|
||||
placeholder: process.cwd(),
|
||||
validate: (input) => this.validateDirectorySync(input),
|
||||
});
|
||||
|
||||
// Apply filter logic
|
||||
let filteredDir = directory;
|
||||
if (!filteredDir || filteredDir.trim() === '') {
|
||||
filteredDir = process.cwd();
|
||||
} else {
|
||||
filteredDir = this.expandUserPath(filteredDir);
|
||||
}
|
||||
return this.expandUserPath(input);
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
return { directory: filteredDir };
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -915,45 +846,92 @@ class UI {
|
|||
* @returns {boolean} Whether user confirmed
|
||||
*/
|
||||
async confirmDirectory(directory) {
|
||||
const inquirer = await getInquirer();
|
||||
const dirExists = await fs.pathExists(directory);
|
||||
|
||||
if (dirExists) {
|
||||
const confirmAnswer = await inquirer.prompt([
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'proceed',
|
||||
message: `Install to this directory?`,
|
||||
const proceed = await prompts.confirm({
|
||||
message: 'Install to this directory?',
|
||||
default: true,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
if (!confirmAnswer.proceed) {
|
||||
if (!proceed) {
|
||||
console.log(chalk.yellow("\nLet's try again with a different path.\n"));
|
||||
}
|
||||
|
||||
return confirmAnswer.proceed;
|
||||
return proceed;
|
||||
} else {
|
||||
// Ask for confirmation to create the directory
|
||||
const createConfirm = await inquirer.prompt([
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'create',
|
||||
const create = await prompts.confirm({
|
||||
message: `The directory '${directory}' doesn't exist. Would you like to create it?`,
|
||||
default: false,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
if (!createConfirm.create) {
|
||||
if (!create) {
|
||||
console.log(chalk.yellow("\nLet's try again with a different path.\n"));
|
||||
}
|
||||
|
||||
return createConfirm.create;
|
||||
return create;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate directory path for installation
|
||||
* Validate directory path for installation (sync version for clack prompts)
|
||||
* @param {string} input - User input path
|
||||
* @returns {string|undefined} Error message or undefined if valid
|
||||
*/
|
||||
validateDirectorySync(input) {
|
||||
// Allow empty input to use the default
|
||||
if (!input || input.trim() === '') {
|
||||
return; // Empty means use default, undefined = valid for clack
|
||||
}
|
||||
|
||||
let expandedPath;
|
||||
try {
|
||||
expandedPath = this.expandUserPath(input.trim());
|
||||
} catch (error) {
|
||||
return error.message;
|
||||
}
|
||||
|
||||
// Check if the path exists
|
||||
const pathExists = fs.pathExistsSync(expandedPath);
|
||||
|
||||
if (!pathExists) {
|
||||
// Find the first existing parent directory
|
||||
const existingParent = this.findExistingParentSync(expandedPath);
|
||||
|
||||
if (!existingParent) {
|
||||
return 'Cannot create directory: no existing parent directory found';
|
||||
}
|
||||
|
||||
// Check if the existing parent is writable
|
||||
try {
|
||||
fs.accessSync(existingParent, fs.constants.W_OK);
|
||||
// Path doesn't exist but can be created - will prompt for confirmation later
|
||||
return;
|
||||
} catch {
|
||||
// Provide a detailed error message explaining both issues
|
||||
return `Directory '${expandedPath}' does not exist and cannot be created: parent directory '${existingParent}' is not writable`;
|
||||
}
|
||||
}
|
||||
|
||||
// If it exists, validate it's a directory and writable
|
||||
const stat = fs.statSync(expandedPath);
|
||||
if (!stat.isDirectory()) {
|
||||
return `Path exists but is not a directory: ${expandedPath}`;
|
||||
}
|
||||
|
||||
// Check write permissions
|
||||
try {
|
||||
fs.accessSync(expandedPath, fs.constants.W_OK);
|
||||
} catch {
|
||||
return `Directory is not writable: ${expandedPath}`;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate directory path for installation (async version)
|
||||
* @param {string} input - User input path
|
||||
* @returns {string|true} Error message or true if valid
|
||||
*/
|
||||
|
|
@ -1009,7 +987,28 @@ class UI {
|
|||
}
|
||||
|
||||
/**
|
||||
* Find the first existing parent directory
|
||||
* Find the first existing parent directory (sync version)
|
||||
* @param {string} targetPath - The path to check
|
||||
* @returns {string|null} The first existing parent directory, or null if none found
|
||||
*/
|
||||
findExistingParentSync(targetPath) {
|
||||
let currentPath = path.resolve(targetPath);
|
||||
|
||||
// Walk up the directory tree until we find an existing directory
|
||||
while (currentPath !== path.dirname(currentPath)) {
|
||||
// Stop at root
|
||||
const parent = path.dirname(currentPath);
|
||||
if (fs.pathExistsSync(parent)) {
|
||||
return parent;
|
||||
}
|
||||
currentPath = parent;
|
||||
}
|
||||
|
||||
return null; // No existing parent found (shouldn't happen in practice)
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the first existing parent directory (async version)
|
||||
* @param {string} targetPath - The path to check
|
||||
* @returns {string|null} The first existing parent directory, or null if none found
|
||||
*/
|
||||
|
|
@ -1071,7 +1070,7 @@ class UI {
|
|||
* @sideeffects None - pure user input collection, no files written
|
||||
* @edgecases Shows warning if user enables TTS but AgentVibes not detected
|
||||
* @calledby promptInstall() during installation flow, after core config, before IDE selection
|
||||
* @calls checkAgentVibesInstalled(), inquirer.prompt(), chalk.green/yellow/dim()
|
||||
* @calls checkAgentVibesInstalled(), prompts.select(), chalk.green/yellow/dim()
|
||||
*
|
||||
* AI NOTE: This prompt is strategically positioned in installation flow:
|
||||
* - AFTER core config (user_name, etc)
|
||||
|
|
@ -1102,7 +1101,6 @@ class UI {
|
|||
* - GitHub Issue: paulpreibisch/AgentVibes#36
|
||||
*/
|
||||
async promptAgentVibes(projectDir) {
|
||||
const inquirer = await getInquirer();
|
||||
CLIUtils.displaySection('🎤 Voice Features', 'Enable TTS for multi-agent conversations');
|
||||
|
||||
// Check if AgentVibes is already installed
|
||||
|
|
@ -1114,23 +1112,19 @@ class UI {
|
|||
console.log(chalk.dim(' AgentVibes not detected'));
|
||||
}
|
||||
|
||||
const answers = await inquirer.prompt([
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'enableTts',
|
||||
const enableTts = await prompts.confirm({
|
||||
message: 'Enable Agents to Speak Out loud (powered by Agent Vibes? Claude Code only currently)',
|
||||
default: false, // Default to yes - recommended for best experience
|
||||
},
|
||||
]);
|
||||
default: false,
|
||||
});
|
||||
|
||||
if (answers.enableTts && !agentVibesInstalled) {
|
||||
if (enableTts && !agentVibesInstalled) {
|
||||
console.log(chalk.yellow('\n ⚠️ AgentVibes not installed'));
|
||||
console.log(chalk.dim(' Install AgentVibes separately to enable TTS:'));
|
||||
console.log(chalk.dim(' https://github.com/paulpreibisch/AgentVibes\n'));
|
||||
}
|
||||
|
||||
return {
|
||||
enabled: answers.enableTts,
|
||||
enabled: enableTts,
|
||||
alreadyInstalled: agentVibesInstalled,
|
||||
};
|
||||
}
|
||||
|
|
@ -1248,30 +1242,75 @@ class UI {
|
|||
return existingInstall.ides || [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate custom content path synchronously
|
||||
* @param {string} input - User input path
|
||||
* @returns {string|undefined} Error message or undefined if valid
|
||||
*/
|
||||
validateCustomContentPathSync(input) {
|
||||
// Allow empty input to cancel
|
||||
if (!input || input.trim() === '') {
|
||||
return; // Allow empty to exit
|
||||
}
|
||||
|
||||
try {
|
||||
// Expand the path
|
||||
const expandedPath = this.expandUserPath(input.trim());
|
||||
|
||||
// Check if path exists
|
||||
if (!fs.pathExistsSync(expandedPath)) {
|
||||
return 'Path does not exist';
|
||||
}
|
||||
|
||||
// Check if it's a directory
|
||||
const stat = fs.statSync(expandedPath);
|
||||
if (!stat.isDirectory()) {
|
||||
return 'Path must be a directory';
|
||||
}
|
||||
|
||||
// Check for module.yaml in the root
|
||||
const moduleYamlPath = path.join(expandedPath, 'module.yaml');
|
||||
if (!fs.pathExistsSync(moduleYamlPath)) {
|
||||
return 'Directory must contain a module.yaml file in the root';
|
||||
}
|
||||
|
||||
// Try to parse the module.yaml to get the module ID
|
||||
try {
|
||||
const yaml = require('yaml');
|
||||
const content = fs.readFileSync(moduleYamlPath, 'utf8');
|
||||
const moduleData = yaml.parse(content);
|
||||
if (!moduleData.code) {
|
||||
return 'module.yaml must contain a "code" field for the module ID';
|
||||
}
|
||||
} catch (error) {
|
||||
return 'Invalid module.yaml file: ' + error.message;
|
||||
}
|
||||
|
||||
return; // Valid
|
||||
} catch (error) {
|
||||
return 'Error validating path: ' + error.message;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prompt user for custom content source location
|
||||
* @returns {Object} Custom content configuration
|
||||
*/
|
||||
async promptCustomContentSource() {
|
||||
const inquirer = await getInquirer();
|
||||
const customContentConfig = { hasCustomContent: true, sources: [] };
|
||||
|
||||
// Keep asking for more sources until user is done
|
||||
while (true) {
|
||||
// First ask if user wants to add another module or continue
|
||||
if (customContentConfig.sources.length > 0) {
|
||||
const { action } = await inquirer.prompt([
|
||||
{
|
||||
type: 'list',
|
||||
name: 'action',
|
||||
const action = await prompts.select({
|
||||
message: 'Would you like to:',
|
||||
choices: [
|
||||
{ name: 'Add another custom module', value: 'add' },
|
||||
{ name: 'Continue with installation', value: 'continue' },
|
||||
],
|
||||
default: 'continue',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
if (action === 'continue') {
|
||||
break;
|
||||
|
|
@ -1282,57 +1321,11 @@ class UI {
|
|||
let isValid = false;
|
||||
|
||||
while (!isValid) {
|
||||
const { path: inputPath } = await inquirer.prompt([
|
||||
{
|
||||
type: 'input',
|
||||
name: 'path',
|
||||
// Use sync validation because @clack/prompts doesn't support async validate
|
||||
const inputPath = await prompts.text({
|
||||
message: 'Enter the path to your custom content folder (or press Enter to cancel):',
|
||||
validate: async (input) => {
|
||||
// Allow empty input to cancel
|
||||
if (!input || input.trim() === '') {
|
||||
return true; // Allow empty to exit
|
||||
}
|
||||
|
||||
try {
|
||||
// Expand the path
|
||||
const expandedPath = this.expandUserPath(input.trim());
|
||||
|
||||
// Check if path exists
|
||||
if (!(await fs.pathExists(expandedPath))) {
|
||||
return 'Path does not exist';
|
||||
}
|
||||
|
||||
// Check if it's a directory
|
||||
const stat = await fs.stat(expandedPath);
|
||||
if (!stat.isDirectory()) {
|
||||
return 'Path must be a directory';
|
||||
}
|
||||
|
||||
// Check for module.yaml in the root
|
||||
const moduleYamlPath = path.join(expandedPath, 'module.yaml');
|
||||
if (!(await fs.pathExists(moduleYamlPath))) {
|
||||
return 'Directory must contain a module.yaml file in the root';
|
||||
}
|
||||
|
||||
// Try to parse the module.yaml to get the module ID
|
||||
try {
|
||||
const yaml = require('yaml');
|
||||
const content = await fs.readFile(moduleYamlPath, 'utf8');
|
||||
const moduleData = yaml.parse(content);
|
||||
if (!moduleData.code) {
|
||||
return 'module.yaml must contain a "code" field for the module ID';
|
||||
}
|
||||
} catch (error) {
|
||||
return 'Invalid module.yaml file: ' + error.message;
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
return 'Error validating path: ' + error.message;
|
||||
}
|
||||
},
|
||||
},
|
||||
]);
|
||||
validate: (input) => this.validateCustomContentPathSync(input),
|
||||
});
|
||||
|
||||
// If user pressed Enter without typing anything, exit the loop
|
||||
if (!inputPath || inputPath.trim() === '') {
|
||||
|
|
@ -1364,14 +1357,10 @@ class UI {
|
|||
}
|
||||
|
||||
// Ask if user wants to add these to the installation
|
||||
const { shouldInstall } = await inquirer.prompt([
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'shouldInstall',
|
||||
const shouldInstall = await prompts.confirm({
|
||||
message: `Install ${customContentConfig.sources.length} custom module(s) now?`,
|
||||
default: true,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
if (shouldInstall) {
|
||||
customContentConfig.selected = true;
|
||||
|
|
@ -1391,7 +1380,6 @@ class UI {
|
|||
* @returns {Object} Result with selected custom modules and custom content config
|
||||
*/
|
||||
async handleCustomModulesInModifyFlow(directory, selectedModules) {
|
||||
const inquirer = await getInquirer();
|
||||
// Get existing installation to find custom modules
|
||||
const { existingInstall } = await this.getExistingInstallation(directory);
|
||||
|
||||
|
|
@ -1451,16 +1439,11 @@ class UI {
|
|||
choices.push({ name: 'Add new custom modules', value: 'add' }, { name: 'Cancel (no custom modules)', value: 'cancel' });
|
||||
}
|
||||
|
||||
const { customAction } = await inquirer.prompt([
|
||||
{
|
||||
type: 'list',
|
||||
name: 'customAction',
|
||||
message:
|
||||
cachedCustomModules.length > 0 ? 'What would you like to do with custom modules?' : 'Would you like to add custom modules?',
|
||||
const customAction = await prompts.select({
|
||||
message: cachedCustomModules.length > 0 ? 'What would you like to do with custom modules?' : 'Would you like to add custom modules?',
|
||||
choices: choices,
|
||||
default: cachedCustomModules.length > 0 ? 'keep' : 'add',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
switch (customAction) {
|
||||
case 'keep': {
|
||||
|
|
@ -1472,21 +1455,18 @@ class UI {
|
|||
|
||||
case 'select': {
|
||||
// Let user choose which to keep
|
||||
const choices = cachedCustomModules.map((m) => ({
|
||||
const selectChoices = cachedCustomModules.map((m) => ({
|
||||
name: `${m.name} ${chalk.gray(`(${m.id})`)}`,
|
||||
value: m.id,
|
||||
checked: m.checked,
|
||||
}));
|
||||
|
||||
const { keepModules } = await inquirer.prompt([
|
||||
{
|
||||
type: 'checkbox',
|
||||
name: 'keepModules',
|
||||
message: 'Select custom modules to keep:',
|
||||
choices: choices,
|
||||
default: cachedCustomModules.filter((m) => m.checked).map((m) => m.id),
|
||||
},
|
||||
]);
|
||||
result.selectedCustomModules = keepModules;
|
||||
const keepModules = await prompts.multiselect({
|
||||
message: `Select custom modules to keep ${chalk.dim('(↑/↓ navigate, SPACE select, ENTER confirm)')}:`,
|
||||
choices: selectChoices,
|
||||
required: false,
|
||||
});
|
||||
result.selectedCustomModules = keepModules || [];
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -1586,7 +1566,6 @@ class UI {
|
|||
* @returns {Promise<boolean>} True if user wants to proceed, false if they cancel
|
||||
*/
|
||||
async showOldAlphaVersionWarning(installedVersion, currentVersion, bmadFolderName) {
|
||||
const inquirer = await getInquirer();
|
||||
const versionInfo = this.checkAlphaVersionAge(installedVersion, currentVersion);
|
||||
|
||||
// Also warn if version is unknown or can't be parsed (legacy/unsupported)
|
||||
|
|
@ -1627,26 +1606,20 @@ class UI {
|
|||
console.log(chalk.yellow('─'.repeat(80)));
|
||||
console.log('');
|
||||
|
||||
const { proceed } = await inquirer.prompt([
|
||||
{
|
||||
type: 'list',
|
||||
name: 'proceed',
|
||||
const proceed = await prompts.select({
|
||||
message: 'What would you like to do?',
|
||||
choices: [
|
||||
{
|
||||
name: 'Proceed with update anyway (may have issues)',
|
||||
value: 'proceed',
|
||||
short: 'Proceed with update',
|
||||
},
|
||||
{
|
||||
name: 'Cancel (recommended - do a fresh install instead)',
|
||||
value: 'cancel',
|
||||
short: 'Cancel installation',
|
||||
},
|
||||
],
|
||||
default: 'cancel',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
if (proceed === 'cancel') {
|
||||
console.log('');
|
||||
|
|
|
|||
Loading…
Reference in New Issue