feat: Add workspace system demonstration files and enhanced utilities
- Add demo scripts showcasing collaborative workspace capabilities - Include workspace templates and configuration examples - Add enhanced and fixed utility versions for testing - Complete workspace system implementation with sample data
This commit is contained in:
parent
dd487630ee
commit
c0af0fbdbf
|
|
@ -0,0 +1,109 @@
|
||||||
|
{
|
||||||
|
"timestamp": "2025-07-23T21:21:48.376Z",
|
||||||
|
"session": {
|
||||||
|
"id": "85db2f3a0f721c3c",
|
||||||
|
"ide": "vscode",
|
||||||
|
"created": "2025-07-23T21:21:29.435Z",
|
||||||
|
"lastHeartbeat": "2025-07-23T21:21:29.435Z",
|
||||||
|
"pid": 97230,
|
||||||
|
"user": "bryan",
|
||||||
|
"cwd": "/mnt/c/Projects/BMAD-Method",
|
||||||
|
"nodeVersion": "v20.19.2",
|
||||||
|
"platform": "linux",
|
||||||
|
"arch": "x64",
|
||||||
|
"metadata": {
|
||||||
|
"ideSpecific": {
|
||||||
|
"supportsTerminalCommands": true,
|
||||||
|
"hasIntegratedGit": true,
|
||||||
|
"supportsPanels": true,
|
||||||
|
"hasExtensionSystem": true,
|
||||||
|
"features": [
|
||||||
|
"extensions",
|
||||||
|
"integrated-terminal",
|
||||||
|
"git-integration"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"features": [
|
||||||
|
"context-sharing",
|
||||||
|
"agent-handoffs",
|
||||||
|
"quality-tracking"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"context": {
|
||||||
|
"filesFound": 0,
|
||||||
|
"syncErrors": 0,
|
||||||
|
"files": []
|
||||||
|
},
|
||||||
|
"progress": {
|
||||||
|
"recentUpdates": 0,
|
||||||
|
"updates": []
|
||||||
|
},
|
||||||
|
"handoffs": {
|
||||||
|
"pending": 1,
|
||||||
|
"recent": [
|
||||||
|
{
|
||||||
|
"id": "d3b9448d06e1",
|
||||||
|
"timestamp": "2025-07-23T21:21:44.381Z",
|
||||||
|
"fromAgent": "unknown",
|
||||||
|
"toAgent": "dev",
|
||||||
|
"currentWork": "No work description provided",
|
||||||
|
"notes": "",
|
||||||
|
"session": {
|
||||||
|
"id": "85db2f3a0f721c3c",
|
||||||
|
"ide": "vscode",
|
||||||
|
"created": "2025-07-23T21:21:29.435Z",
|
||||||
|
"lastHeartbeat": "2025-07-23T21:21:29.435Z",
|
||||||
|
"pid": 97230,
|
||||||
|
"user": "bryan",
|
||||||
|
"cwd": "/mnt/c/Projects/BMAD-Method",
|
||||||
|
"nodeVersion": "v20.19.2",
|
||||||
|
"platform": "linux",
|
||||||
|
"arch": "x64",
|
||||||
|
"metadata": {
|
||||||
|
"ideSpecific": {
|
||||||
|
"supportsTerminalCommands": true,
|
||||||
|
"hasIntegratedGit": true,
|
||||||
|
"supportsPanels": true,
|
||||||
|
"hasExtensionSystem": true,
|
||||||
|
"features": [
|
||||||
|
"extensions",
|
||||||
|
"integrated-terminal",
|
||||||
|
"git-integration"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"features": [
|
||||||
|
"context-sharing",
|
||||||
|
"agent-handoffs",
|
||||||
|
"quality-tracking"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"context": {
|
||||||
|
"availableFiles": [],
|
||||||
|
"recentProgress": [],
|
||||||
|
"workspaceHealth": {
|
||||||
|
"score": 100,
|
||||||
|
"missingDirectories": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"recommendations": [
|
||||||
|
"Update workspace context with latest findings",
|
||||||
|
"Review any TODO items or pending decisions"
|
||||||
|
],
|
||||||
|
"status": "pending"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"quality": {
|
||||||
|
"recentReports": 1,
|
||||||
|
"reports": [
|
||||||
|
{
|
||||||
|
"file": "health-report.json",
|
||||||
|
"modified": "2025-07-23T21:21:36.053Z",
|
||||||
|
"type": "report",
|
||||||
|
"preview": "{\n \"timestamp\": \"2025-07-23T21:21:35.971Z\",\n \"overallScore\": 93,\n \"status\": \"excellent\",\n \"check..."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
# Workspace Sync Summary
|
||||||
|
|
||||||
|
**Sync Time:** 7/23/2025, 5:21:48 PM
|
||||||
|
|
||||||
|
## Session Information
|
||||||
|
**Current Session:** 85db2f3a0f721c3c (vscode)
|
||||||
|
**User:** bryan
|
||||||
|
**Last Activity:** 7/23/2025, 5:21:29 PM
|
||||||
|
|
||||||
|
## Context Files (0)
|
||||||
|
No context files found
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Recent Progress (0)
|
||||||
|
No recent progress updates
|
||||||
|
|
||||||
|
## Pending Handoffs (1)
|
||||||
|
- **d3b9448d06e1:** unknown → dev
|
||||||
|
Work: No work description provided
|
||||||
|
Time: 7/23/2025, 5:21:44 PM
|
||||||
|
|
||||||
|
## Quality Reports (1)
|
||||||
|
- **health-report.json** (report, 7/23/2025, 5:21:36 PM)
|
||||||
|
{
|
||||||
|
"timestamp": "2025-07-23T21:21:35.971Z",
|
||||||
|
"overallScore": 93,
|
||||||
|
"status": "excellent",
|
||||||
|
"check...
|
||||||
|
|
||||||
|
---
|
||||||
|
*Last synced: 7/23/2025, 5:21:48 PM*
|
||||||
|
*Generated by BMAD Cross-IDE Workspace System*
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
{
|
||||||
|
"id": "d3b9448d06e1",
|
||||||
|
"timestamp": "2025-07-23T21:21:44.381Z",
|
||||||
|
"fromAgent": "unknown",
|
||||||
|
"toAgent": "dev",
|
||||||
|
"currentWork": "No work description provided",
|
||||||
|
"notes": "",
|
||||||
|
"session": {
|
||||||
|
"id": "85db2f3a0f721c3c",
|
||||||
|
"ide": "vscode",
|
||||||
|
"created": "2025-07-23T21:21:29.435Z",
|
||||||
|
"lastHeartbeat": "2025-07-23T21:21:29.435Z",
|
||||||
|
"pid": 97230,
|
||||||
|
"user": "bryan",
|
||||||
|
"cwd": "/mnt/c/Projects/BMAD-Method",
|
||||||
|
"nodeVersion": "v20.19.2",
|
||||||
|
"platform": "linux",
|
||||||
|
"arch": "x64",
|
||||||
|
"metadata": {
|
||||||
|
"ideSpecific": {
|
||||||
|
"supportsTerminalCommands": true,
|
||||||
|
"hasIntegratedGit": true,
|
||||||
|
"supportsPanels": true,
|
||||||
|
"hasExtensionSystem": true,
|
||||||
|
"features": [
|
||||||
|
"extensions",
|
||||||
|
"integrated-terminal",
|
||||||
|
"git-integration"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"features": [
|
||||||
|
"context-sharing",
|
||||||
|
"agent-handoffs",
|
||||||
|
"quality-tracking"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"context": {
|
||||||
|
"availableFiles": [],
|
||||||
|
"recentProgress": [],
|
||||||
|
"workspaceHealth": {
|
||||||
|
"score": 100,
|
||||||
|
"missingDirectories": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"recommendations": [
|
||||||
|
"Update workspace context with latest findings",
|
||||||
|
"Review any TODO items or pending decisions"
|
||||||
|
],
|
||||||
|
"status": "pending"
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
# Agent Handoff: unknown → dev
|
||||||
|
|
||||||
|
**Handoff ID:** d3b9448d06e1
|
||||||
|
**Timestamp:** 7/23/2025, 5:21:44 PM
|
||||||
|
**To Agent:** Developer (James) - Code implementation and debugging
|
||||||
|
|
||||||
|
## Current Work
|
||||||
|
No work description provided
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
No additional notes provided
|
||||||
|
|
||||||
|
## Context Summary
|
||||||
|
- **Available context files:** 0
|
||||||
|
- **Recent progress entries:** 0
|
||||||
|
- **Workspace health:** 100/100
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Recommendations
|
||||||
|
- Update workspace context with latest findings
|
||||||
|
- Review any TODO items or pending decisions
|
||||||
|
|
||||||
|
## Session Information
|
||||||
|
|
||||||
|
- **IDE:** vscode
|
||||||
|
- **User:** bryan
|
||||||
|
- **Created:** 7/23/2025, 5:21:29 PM
|
||||||
|
- **Last Activity:** 7/23/2025, 5:21:29 PM
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
*Generated by BMAD Cross-IDE Workspace System*
|
||||||
|
|
@ -0,0 +1,170 @@
|
||||||
|
{
|
||||||
|
"timestamp": "2025-07-23T21:21:35.971Z",
|
||||||
|
"overallScore": 93,
|
||||||
|
"status": "excellent",
|
||||||
|
"checks": {
|
||||||
|
"directoryStructure": {
|
||||||
|
"score": 100,
|
||||||
|
"issues": [],
|
||||||
|
"missing": [],
|
||||||
|
"present": [
|
||||||
|
{
|
||||||
|
"name": "sessions",
|
||||||
|
"critical": true,
|
||||||
|
"description": "Session management"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "context",
|
||||||
|
"critical": true,
|
||||||
|
"description": "Shared context storage"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "handoffs",
|
||||||
|
"critical": true,
|
||||||
|
"description": "Agent handoff coordination"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "decisions",
|
||||||
|
"critical": false,
|
||||||
|
"description": "Decision tracking"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "progress",
|
||||||
|
"critical": false,
|
||||||
|
"description": "Progress monitoring"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "quality",
|
||||||
|
"critical": false,
|
||||||
|
"description": "Quality reports"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "archive",
|
||||||
|
"critical": false,
|
||||||
|
"description": "Archived data"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "hooks",
|
||||||
|
"critical": false,
|
||||||
|
"description": "Integration hooks"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "templates",
|
||||||
|
"critical": false,
|
||||||
|
"description": "Workspace templates"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "logs",
|
||||||
|
"critical": true,
|
||||||
|
"description": "Activity logging"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"workspaceConfig": {
|
||||||
|
"score": 100,
|
||||||
|
"issues": [],
|
||||||
|
"valid": true,
|
||||||
|
"config": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"created": "2025-07-23T21:21:29.430Z",
|
||||||
|
"lastUpdated": "2025-07-23T21:21:29.431Z",
|
||||||
|
"features": {
|
||||||
|
"crossIDESupport": true,
|
||||||
|
"sessionManagement": true,
|
||||||
|
"contextPersistence": true,
|
||||||
|
"agentHandoffs": true,
|
||||||
|
"qualityTracking": true
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"maxSessions": 10,
|
||||||
|
"sessionTimeout": 3600000,
|
||||||
|
"autoCleanup": true,
|
||||||
|
"logLevel": "info"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"sessionHealth": {
|
||||||
|
"score": 100,
|
||||||
|
"issues": [],
|
||||||
|
"totalSessions": 1,
|
||||||
|
"activeSessions": 1,
|
||||||
|
"staleSessions": 0,
|
||||||
|
"corruptedSessions": 0,
|
||||||
|
"sessions": [
|
||||||
|
{
|
||||||
|
"id": "85db2f3a0f721c3c",
|
||||||
|
"ide": "vscode",
|
||||||
|
"created": "2025-07-23T21:21:29.435Z",
|
||||||
|
"lastHeartbeat": "2025-07-23T21:21:29.435Z",
|
||||||
|
"pid": 97230,
|
||||||
|
"user": "bryan",
|
||||||
|
"cwd": "/mnt/c/Projects/BMAD-Method",
|
||||||
|
"nodeVersion": "v20.19.2",
|
||||||
|
"platform": "linux",
|
||||||
|
"arch": "x64",
|
||||||
|
"metadata": {
|
||||||
|
"ideSpecific": {
|
||||||
|
"supportsTerminalCommands": true,
|
||||||
|
"hasIntegratedGit": true,
|
||||||
|
"supportsPanels": true,
|
||||||
|
"hasExtensionSystem": true,
|
||||||
|
"features": [
|
||||||
|
"extensions",
|
||||||
|
"integrated-terminal",
|
||||||
|
"git-integration"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"features": [
|
||||||
|
"context-sharing",
|
||||||
|
"agent-handoffs",
|
||||||
|
"quality-tracking"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"status": "active",
|
||||||
|
"timeSinceLastHeartbeat": 6550
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"fileSystemPermissions": {
|
||||||
|
"score": 100,
|
||||||
|
"issues": [],
|
||||||
|
"canRead": true,
|
||||||
|
"canWrite": true,
|
||||||
|
"canExecute": true
|
||||||
|
},
|
||||||
|
"logHealth": {
|
||||||
|
"score": 100,
|
||||||
|
"issues": [],
|
||||||
|
"exists": true,
|
||||||
|
"size": 128,
|
||||||
|
"recentEntries": 1,
|
||||||
|
"corruptedEntries": 0
|
||||||
|
},
|
||||||
|
"crossIDECompatibility": {
|
||||||
|
"score": 55,
|
||||||
|
"issues": [
|
||||||
|
"Limited IDE template support: 1 templates found",
|
||||||
|
"No integration hooks configured"
|
||||||
|
],
|
||||||
|
"ideSupport": {
|
||||||
|
"cursor": false,
|
||||||
|
"windsurf": false,
|
||||||
|
"vscode": true,
|
||||||
|
"trae": false,
|
||||||
|
"roo": false,
|
||||||
|
"cline": false,
|
||||||
|
"gemini": false,
|
||||||
|
"github-copilot": false
|
||||||
|
},
|
||||||
|
"templateCount": 1,
|
||||||
|
"hookCount": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"summary": {
|
||||||
|
"totalIssues": 2,
|
||||||
|
"criticalIssues": 0,
|
||||||
|
"recommendations": [
|
||||||
|
"Generate additional IDE-specific templates for better compatibility"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
{
|
||||||
|
"id": "85db2f3a0f721c3c",
|
||||||
|
"ide": "vscode",
|
||||||
|
"created": "2025-07-23T21:21:29.435Z",
|
||||||
|
"lastHeartbeat": "2025-07-23T21:21:48.354Z",
|
||||||
|
"pid": 97230,
|
||||||
|
"user": "bryan",
|
||||||
|
"cwd": "/mnt/c/Projects/BMAD-Method",
|
||||||
|
"nodeVersion": "v20.19.2",
|
||||||
|
"platform": "linux",
|
||||||
|
"arch": "x64",
|
||||||
|
"metadata": {
|
||||||
|
"ideSpecific": {
|
||||||
|
"supportsTerminalCommands": true,
|
||||||
|
"hasIntegratedGit": true,
|
||||||
|
"supportsPanels": true,
|
||||||
|
"hasExtensionSystem": true,
|
||||||
|
"features": [
|
||||||
|
"extensions",
|
||||||
|
"integrated-terminal",
|
||||||
|
"git-integration"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"features": [
|
||||||
|
"context-sharing",
|
||||||
|
"agent-handoffs",
|
||||||
|
"quality-tracking"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
# VSCODE Workspace Setup
|
||||||
|
|
||||||
|
## VSCODE Integration
|
||||||
|
- Use terminal commands for workspace management
|
||||||
|
- Full workspace functionality available
|
||||||
|
- Context persists across IDE sessions
|
||||||
|
|
||||||
|
## Available Commands
|
||||||
|
```bash
|
||||||
|
npm run workspace-init # Initialize workspace session
|
||||||
|
npm run workspace-status # Show workspace status
|
||||||
|
npm run workspace-cleanup # Clean and optimize workspace
|
||||||
|
npm run workspace-handoff # Manage agent handoffs
|
||||||
|
npm run workspace-sync # Synchronize context
|
||||||
|
npm run workspace-health # Check workspace health
|
||||||
|
```
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
{
|
||||||
|
"version": "1.0.0",
|
||||||
|
"created": "2025-07-23T21:21:29.430Z",
|
||||||
|
"lastUpdated": "2025-07-23T21:21:29.431Z",
|
||||||
|
"features": {
|
||||||
|
"crossIDESupport": true,
|
||||||
|
"sessionManagement": true,
|
||||||
|
"contextPersistence": true,
|
||||||
|
"agentHandoffs": true,
|
||||||
|
"qualityTracking": true
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"maxSessions": 10,
|
||||||
|
"sessionTimeout": 3600000,
|
||||||
|
"autoCleanup": true,
|
||||||
|
"logLevel": "info"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,272 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
// Demo script to test 100% Complete Context Persistence functionality
|
||||||
|
const path = require('path');
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
// Import context manager
|
||||||
|
const ContextManager = require('./installer/lib/context-manager');
|
||||||
|
|
||||||
|
async function demoContext100Percent() {
|
||||||
|
console.log('🎯 BMAD Context Persistence 100% Complete Demo');
|
||||||
|
console.log('===============================================\n');
|
||||||
|
|
||||||
|
// Create a test workspace in /tmp
|
||||||
|
const testWorkspace = '/tmp/bmad-context-100-test/.workspace';
|
||||||
|
if (fs.existsSync(testWorkspace)) {
|
||||||
|
fs.rmSync(path.dirname(testWorkspace), { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
const contextManager = new ContextManager(testWorkspace);
|
||||||
|
console.log('✅ Initialized test workspace at:', testWorkspace);
|
||||||
|
console.log(`📁 New directories: versions/, locks/`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Demo 1: Context Versioning System
|
||||||
|
console.log('\n📚 Demo 1: Context Versioning System');
|
||||||
|
console.log('=====================================');
|
||||||
|
|
||||||
|
// Create initial context
|
||||||
|
await contextManager.updateSharedContext({
|
||||||
|
currentFocus: 'Implementing advanced context versioning system',
|
||||||
|
nextSteps: ['Add version tracking', 'Implement conflict resolution', 'Test rollback functionality']
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create first version
|
||||||
|
const contextFile = path.join(testWorkspace, 'context', 'shared-context.md');
|
||||||
|
const initialContent = fs.readFileSync(contextFile, 'utf8');
|
||||||
|
const version1 = await contextManager.createContextVersion('shared-context', initialContent, 'session-001', 'dev-agent');
|
||||||
|
console.log(`✅ Created version 1: ${version1}`);
|
||||||
|
|
||||||
|
// Modify context
|
||||||
|
await contextManager.updateSharedContext({
|
||||||
|
currentFocus: 'Implementing advanced context versioning system with conflict resolution',
|
||||||
|
nextSteps: ['Add version tracking', 'Implement conflict resolution', 'Test rollback functionality', 'Add merge capabilities']
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create second version
|
||||||
|
const modifiedContent = fs.readFileSync(contextFile, 'utf8');
|
||||||
|
const version2 = await contextManager.createContextVersion('shared-context', modifiedContent, 'session-002', 'architect-agent');
|
||||||
|
console.log(`✅ Created version 2: ${version2}`);
|
||||||
|
|
||||||
|
// Test version retrieval
|
||||||
|
const recentVersions = await contextManager.getRecentVersions('shared-context', 5);
|
||||||
|
console.log(`✅ Retrieved ${recentVersions.length} recent versions`);
|
||||||
|
recentVersions.forEach((v, i) => {
|
||||||
|
console.log(` ${i + 1}. ${v.id} by ${v.agent} at ${new Date(v.timestamp).toLocaleString()}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Demo 2: Conflict Detection and Resolution
|
||||||
|
console.log('\n⚔️ Demo 2: Conflict Detection and Resolution');
|
||||||
|
console.log('=============================================');
|
||||||
|
|
||||||
|
// Simulate concurrent modification
|
||||||
|
const newContent1 = modifiedContent.replace('conflict resolution', 'advanced conflict resolution');
|
||||||
|
const newContent2 = modifiedContent.replace('versioning system', 'versioning and backup system');
|
||||||
|
|
||||||
|
// Check for conflicts
|
||||||
|
const conflict1 = await contextManager.detectContextConflicts('shared-context', newContent1, 'session-003');
|
||||||
|
console.log(`✅ Conflict detection 1: ${conflict1.hasConflict ? 'CONFLICT DETECTED' : 'No conflict'}`);
|
||||||
|
|
||||||
|
const conflict2 = await contextManager.detectContextConflicts('shared-context', newContent2, 'session-004');
|
||||||
|
console.log(`✅ Conflict detection 2: ${conflict2.hasConflict ? 'CONFLICT DETECTED' : 'No conflict'}`);
|
||||||
|
|
||||||
|
// Test merge capabilities
|
||||||
|
if (conflict1.hasConflict || conflict2.hasConflict) {
|
||||||
|
console.log('ℹ️ Conflicts detected - would normally trigger merge process');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create versions for merge testing
|
||||||
|
const change1 = { content: newContent1, timestamp: new Date().toISOString() };
|
||||||
|
const change2 = { content: newContent2, timestamp: new Date(Date.now() + 1000).toISOString() };
|
||||||
|
|
||||||
|
try {
|
||||||
|
const mergedContent = await contextManager.mergeContextChanges('shared-context', initialContent, change1, change2);
|
||||||
|
console.log('✅ Context merge successful');
|
||||||
|
console.log(` Merged content length: ${mergedContent.length} characters`);
|
||||||
|
} catch (error) {
|
||||||
|
console.log(`⚠️ Merge test: ${error.message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Demo 3: Context Locking System
|
||||||
|
console.log('\n🔒 Demo 3: Context Locking System');
|
||||||
|
console.log('=================================');
|
||||||
|
|
||||||
|
// Acquire lock for session 1
|
||||||
|
const lock1 = await contextManager.acquireContextLock('shared-context', 'session-001', 10000);
|
||||||
|
console.log(`✅ Lock acquisition (session-001): ${lock1.acquired ? 'SUCCESS' : 'FAILED'}`);
|
||||||
|
|
||||||
|
// Try to acquire same lock from session 2
|
||||||
|
const lock2 = await contextManager.acquireContextLock('shared-context', 'session-002', 5000);
|
||||||
|
console.log(`✅ Lock acquisition (session-002): ${lock2.acquired ? 'SUCCESS' : 'FAILED'}`);
|
||||||
|
if (!lock2.acquired) {
|
||||||
|
console.log(` Locked by: ${lock2.lockedBy}, expires: ${new Date(lock2.expiresAt).toLocaleString()}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Release lock
|
||||||
|
const release1 = await contextManager.releaseContextLock('shared-context', 'session-001');
|
||||||
|
console.log(`✅ Lock release (session-001): ${release1.released ? 'SUCCESS' : 'FAILED'}`);
|
||||||
|
|
||||||
|
// Now session 2 can acquire the lock
|
||||||
|
const lock3 = await contextManager.acquireContextLock('shared-context', 'session-002', 5000);
|
||||||
|
console.log(`✅ Lock acquisition (session-002) after release: ${lock3.acquired ? 'SUCCESS' : 'FAILED'}`);
|
||||||
|
|
||||||
|
// Cleanup expired locks test
|
||||||
|
const cleanup = await contextManager.cleanupExpiredLocks();
|
||||||
|
console.log(`✅ Expired locks cleaned up: ${cleanup.cleanedCount}`);
|
||||||
|
|
||||||
|
// Release remaining locks
|
||||||
|
await contextManager.releaseContextLock('shared-context', 'session-002');
|
||||||
|
|
||||||
|
// Demo 4: Rollback System
|
||||||
|
console.log('\n⏪ Demo 4: Context Rollback System');
|
||||||
|
console.log('==================================');
|
||||||
|
|
||||||
|
// Get current context
|
||||||
|
const currentContext = await contextManager.loadSharedContext();
|
||||||
|
console.log(`✅ Current context focus: "${currentContext.currentFocus.substring(0, 50)}..."`);
|
||||||
|
|
||||||
|
// Rollback to version 1
|
||||||
|
if (recentVersions.length > 0) {
|
||||||
|
const rollbackResult = await contextManager.rollbackToVersion('shared-context', recentVersions[0].id);
|
||||||
|
console.log(`✅ Rollback result: ${rollbackResult.success ? 'SUCCESS' : 'FAILED'}`);
|
||||||
|
if (rollbackResult.success) {
|
||||||
|
console.log(` Rolled back to: ${new Date(rollbackResult.rolledBackTo).toLocaleString()}`);
|
||||||
|
console.log(` Original agent: ${rollbackResult.agent}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify rollback worked
|
||||||
|
const rolledBackContext = await contextManager.loadSharedContext();
|
||||||
|
console.log(`✅ After rollback focus: "${rolledBackContext.currentFocus.substring(0, 50)}..."`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Demo 5: BMAD Agent Integration Hooks
|
||||||
|
console.log('\n🤖 Demo 5: BMAD Agent Integration Hooks');
|
||||||
|
console.log('=======================================');
|
||||||
|
|
||||||
|
// Test story start hook
|
||||||
|
await contextManager.onStoryStart('Story-3.1-Advanced-Context-System', 'dev-agent', 'session-005');
|
||||||
|
console.log('✅ Story start hook executed');
|
||||||
|
|
||||||
|
// Test decision made hook
|
||||||
|
await contextManager.onDecisionMade({
|
||||||
|
title: 'Implement context versioning with Git-like functionality',
|
||||||
|
decision: 'Use JSON-based versioning with content hashing for conflict detection',
|
||||||
|
rationale: 'Provides reliable conflict detection and merge capabilities',
|
||||||
|
impact: 'Enables safe concurrent context modifications'
|
||||||
|
}, 'architect-agent', 'session-005');
|
||||||
|
console.log('✅ Decision made hook executed');
|
||||||
|
|
||||||
|
// Test quality audit hook
|
||||||
|
await contextManager.onQualityAudit({
|
||||||
|
story: 'Story-3.1-Advanced-Context-System',
|
||||||
|
realityAuditScore: '95/100',
|
||||||
|
patternCompliance: '92/100',
|
||||||
|
technicalDebtScore: '88/100',
|
||||||
|
overallQuality: 'A- (95/100)',
|
||||||
|
details: 'Context versioning system implemented with comprehensive conflict resolution'
|
||||||
|
}, 'qa-agent', 'session-005');
|
||||||
|
console.log('✅ Quality audit hook executed');
|
||||||
|
|
||||||
|
// Test agent handoff hook
|
||||||
|
await contextManager.onAgentHandoff('dev-agent', 'qa-agent', 'session-005',
|
||||||
|
'Context versioning system complete. All features implemented and tested. Ready for comprehensive quality validation.');
|
||||||
|
console.log('✅ Agent handoff hook executed');
|
||||||
|
|
||||||
|
// Demo 6: Integration Verification
|
||||||
|
console.log('\n🔗 Demo 6: Integration Verification');
|
||||||
|
console.log('====================================');
|
||||||
|
|
||||||
|
// Verify all hook integrations worked
|
||||||
|
const finalStatus = await contextManager.getWorkspaceStatus();
|
||||||
|
console.log('✅ Integration verification:');
|
||||||
|
console.log(` - Primary Agent: ${finalStatus.context.primaryAgent}`);
|
||||||
|
console.log(` - Current Focus: ${finalStatus.context.currentFocus.substring(0, 60)}...`);
|
||||||
|
console.log(` - Quality Score: ${finalStatus.progress.qualityScore}`);
|
||||||
|
console.log(` - Recent Decisions: ${finalStatus.recentDecisions.length}`);
|
||||||
|
|
||||||
|
// Check that decision was auto-logged
|
||||||
|
const decisions = await contextManager.getDecisions();
|
||||||
|
console.log(` - Total Decisions: ${decisions.length}`);
|
||||||
|
const autoDecision = decisions.find(d => d.title.includes('context versioning'));
|
||||||
|
console.log(` - Auto-logged decision: ${autoDecision ? 'YES' : 'NO'}`);
|
||||||
|
|
||||||
|
// Demo 7: Directory Structure Verification
|
||||||
|
console.log('\n📁 Demo 7: Enhanced Directory Structure');
|
||||||
|
console.log('=======================================');
|
||||||
|
|
||||||
|
const directories = ['context', 'decisions', 'progress', 'quality', 'archive', 'versions', 'locks'];
|
||||||
|
console.log('✅ Directory verification:');
|
||||||
|
directories.forEach(dir => {
|
||||||
|
const dirPath = path.join(testWorkspace, dir);
|
||||||
|
const exists = fs.existsSync(dirPath);
|
||||||
|
const files = exists ? fs.readdirSync(dirPath).length : 0;
|
||||||
|
console.log(` - ${dir}/: ${exists ? 'EXISTS' : 'MISSING'} (${files} files)`);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Verify version files
|
||||||
|
const versionFiles = fs.readdirSync(path.join(testWorkspace, 'versions'));
|
||||||
|
console.log(`✅ Version files created: ${versionFiles.length}`);
|
||||||
|
versionFiles.forEach((file, index) => {
|
||||||
|
console.log(` ${index + 1}. ${file}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Demo 8: Performance and Concurrency Testing
|
||||||
|
console.log('\n⚡ Demo 8: Performance and Concurrency Testing');
|
||||||
|
console.log('===============================================');
|
||||||
|
|
||||||
|
// Test multiple concurrent operations
|
||||||
|
const startTime = Date.now();
|
||||||
|
|
||||||
|
const operations = [
|
||||||
|
contextManager.updateSharedContext({ currentFocus: 'Concurrent test 1' }),
|
||||||
|
contextManager.logDecision({
|
||||||
|
title: 'Concurrent decision test',
|
||||||
|
agent: 'test-agent',
|
||||||
|
decision: 'Testing concurrent operations',
|
||||||
|
rationale: 'Verify system stability under load'
|
||||||
|
}),
|
||||||
|
contextManager.updateProgress({
|
||||||
|
currentStory: 'Concurrency Test Story',
|
||||||
|
completedTasks: ['Test task 1', 'Test task 2']
|
||||||
|
}),
|
||||||
|
contextManager.updateQualityMetrics({
|
||||||
|
agent: 'test-agent',
|
||||||
|
story: 'Concurrency Test Story',
|
||||||
|
overallQuality: 'B+ (88/100)',
|
||||||
|
details: 'Concurrent operation test'
|
||||||
|
})
|
||||||
|
];
|
||||||
|
|
||||||
|
await Promise.all(operations);
|
||||||
|
const endTime = Date.now();
|
||||||
|
|
||||||
|
console.log(`✅ Concurrent operations completed in ${endTime - startTime}ms`);
|
||||||
|
console.log(` All operations completed successfully`);
|
||||||
|
|
||||||
|
console.log('\n🎉 Context Persistence 100% Complete Demo Finished!');
|
||||||
|
console.log('====================================================');
|
||||||
|
console.log(`📁 Test workspace: ${testWorkspace}`);
|
||||||
|
console.log('🎯 100% Features Demonstrated:');
|
||||||
|
console.log(' ✅ Context versioning with content hashing');
|
||||||
|
console.log(' ✅ Conflict detection and intelligent merging');
|
||||||
|
console.log(' ✅ Context locking for concurrent access safety');
|
||||||
|
console.log(' ✅ Rollback capabilities with backup protection');
|
||||||
|
console.log(' ✅ BMAD agent integration hooks for automatic capture');
|
||||||
|
console.log(' ✅ Story/Decision/Quality/Handoff event processing');
|
||||||
|
console.log(' ✅ Performance optimization for concurrent operations');
|
||||||
|
console.log(' ✅ Enterprise-grade directory structure');
|
||||||
|
console.log('🚀 Production-ready with enterprise features!');
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('\n❌ Demo failed:', error.message);
|
||||||
|
console.error(error.stack);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run the demo
|
||||||
|
if (require.main === module) {
|
||||||
|
demoContext100Percent();
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { demoContext100Percent };
|
||||||
|
|
@ -0,0 +1,187 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
// Demo script to test Context Persistence functionality
|
||||||
|
const path = require('path');
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
// Import the ContextManager
|
||||||
|
const ContextManager = require('./installer/lib/context-manager');
|
||||||
|
|
||||||
|
async function demoContextPersistence() {
|
||||||
|
console.log('🚀 BMAD Context Persistence Demo');
|
||||||
|
console.log('=================================\n');
|
||||||
|
|
||||||
|
// Create a test workspace in /tmp
|
||||||
|
const testWorkspace = '/tmp/bmad-context-test/.workspace';
|
||||||
|
if (fs.existsSync(testWorkspace)) {
|
||||||
|
fs.rmSync(path.dirname(testWorkspace), { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
const contextManager = new ContextManager(testWorkspace);
|
||||||
|
console.log('✅ Initialized test workspace at:', testWorkspace);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Demo 1: Session Start and Context Updates
|
||||||
|
console.log('\n📍 Demo 1: Session Management and Context Updates');
|
||||||
|
console.log('==================================================');
|
||||||
|
|
||||||
|
await contextManager.onSessionStart('demo-session-001', 'dev-agent');
|
||||||
|
console.log('✅ Started session: demo-session-001 with dev-agent');
|
||||||
|
|
||||||
|
await contextManager.updateSharedContext({
|
||||||
|
currentFocus: 'Implementing context persistence framework for BMAD collaborative workspace system',
|
||||||
|
nextSteps: [
|
||||||
|
'Complete ContextManager class implementation',
|
||||||
|
'Test integration with workspace utilities',
|
||||||
|
'Create demo scenarios for all major features'
|
||||||
|
]
|
||||||
|
});
|
||||||
|
console.log('✅ Updated shared context with development focus');
|
||||||
|
|
||||||
|
// Demo 2: Decision Logging
|
||||||
|
console.log('\n📋 Demo 2: Decision Logging');
|
||||||
|
console.log('============================');
|
||||||
|
|
||||||
|
await contextManager.logDecision({
|
||||||
|
title: 'Use file-based context persistence over database',
|
||||||
|
agent: 'dev-agent',
|
||||||
|
context: 'Story 1.2: Context Persistence Framework',
|
||||||
|
decision: 'Implement context persistence using structured markdown files instead of a database',
|
||||||
|
rationale: 'Files are cross-IDE compatible, human-readable, version-control friendly, and require no external dependencies',
|
||||||
|
alternatives: 'SQLite database, JSON files, in-memory store',
|
||||||
|
impact: 'Enables seamless context sharing across different development environments',
|
||||||
|
status: 'active'
|
||||||
|
});
|
||||||
|
console.log('✅ Logged architectural decision');
|
||||||
|
|
||||||
|
await contextManager.logDecision({
|
||||||
|
title: 'Context compaction at 10MB threshold',
|
||||||
|
agent: 'dev-agent',
|
||||||
|
context: 'Context performance optimization',
|
||||||
|
decision: 'Automatically compact context files when they exceed 10MB',
|
||||||
|
rationale: 'Prevents workspace degradation while preserving critical information',
|
||||||
|
alternatives: 'No compaction, smaller threshold, manual compaction',
|
||||||
|
impact: 'Maintains workspace performance over long development cycles',
|
||||||
|
status: 'active'
|
||||||
|
});
|
||||||
|
console.log('✅ Logged performance decision');
|
||||||
|
|
||||||
|
// Demo 3: Progress Tracking
|
||||||
|
console.log('\n📈 Demo 3: Progress Tracking');
|
||||||
|
console.log('=============================');
|
||||||
|
|
||||||
|
await contextManager.updateProgress({
|
||||||
|
currentStory: 'Story 1.2: Context Persistence Framework',
|
||||||
|
completedTasks: [
|
||||||
|
'Create ContextManager class with core functionality',
|
||||||
|
'Implement shared context file format and parsing',
|
||||||
|
'Add decision logging with structured format',
|
||||||
|
'Create progress tracking system'
|
||||||
|
],
|
||||||
|
pendingTasks: [
|
||||||
|
'Add quality metrics integration',
|
||||||
|
'Implement context compaction algorithm',
|
||||||
|
'Create integration hooks for BMAD agents',
|
||||||
|
'Add cross-IDE testing scenarios'
|
||||||
|
],
|
||||||
|
blockers: [],
|
||||||
|
qualityScore: 'B+ (85/100) - Core functionality complete, testing needed'
|
||||||
|
});
|
||||||
|
console.log('✅ Updated progress tracking');
|
||||||
|
|
||||||
|
// Demo 4: Quality Metrics
|
||||||
|
console.log('\n📊 Demo 4: Quality Metrics Tracking');
|
||||||
|
console.log('====================================');
|
||||||
|
|
||||||
|
await contextManager.updateQualityMetrics({
|
||||||
|
agent: 'qa-agent',
|
||||||
|
story: 'Story 1.2: Context Persistence Framework',
|
||||||
|
realityAuditScore: '85/100',
|
||||||
|
patternCompliance: '90/100',
|
||||||
|
technicalDebtScore: '80/100',
|
||||||
|
overallQuality: 'B+ (85/100)',
|
||||||
|
details: 'Core implementation solid. Needs integration testing and error handling improvements.'
|
||||||
|
});
|
||||||
|
console.log('✅ Recorded quality assessment');
|
||||||
|
|
||||||
|
// Demo 5: Context Retrieval
|
||||||
|
console.log('\n🔍 Demo 5: Context Retrieval and Status');
|
||||||
|
console.log('=======================================');
|
||||||
|
|
||||||
|
const status = await contextManager.getWorkspaceStatus();
|
||||||
|
console.log('✅ Retrieved workspace status:');
|
||||||
|
console.log(` - Primary Agent: ${status.context.primaryAgent}`);
|
||||||
|
console.log(` - Current Focus: ${status.context.currentFocus.substring(0, 60)}...`);
|
||||||
|
console.log(` - Active Sessions: ${status.context.activeSessions.length}`);
|
||||||
|
console.log(` - Key Decisions: ${status.context.keyDecisions.length}`);
|
||||||
|
console.log(` - Next Steps: ${status.context.nextSteps.length}`);
|
||||||
|
console.log(` - Quality Score: ${status.progress.qualityScore}`);
|
||||||
|
console.log(` - Recent Decisions: ${status.recentDecisions.length}`);
|
||||||
|
|
||||||
|
// Demo 6: Decision Retrieval with Filters
|
||||||
|
console.log('\n🔎 Demo 6: Decision Retrieval with Filters');
|
||||||
|
console.log('===========================================');
|
||||||
|
|
||||||
|
const allDecisions = await contextManager.getDecisions();
|
||||||
|
console.log(`✅ Retrieved ${allDecisions.length} total decisions`);
|
||||||
|
|
||||||
|
const devDecisions = await contextManager.getDecisions({ agent: 'dev-agent' });
|
||||||
|
console.log(`✅ Retrieved ${devDecisions.length} decisions by dev-agent`);
|
||||||
|
|
||||||
|
const activeDecisions = await contextManager.getDecisions({ status: 'active' });
|
||||||
|
console.log(`✅ Retrieved ${activeDecisions.length} active decisions`);
|
||||||
|
|
||||||
|
// Demo 7: Context Export
|
||||||
|
console.log('\n📤 Demo 7: Context Export');
|
||||||
|
console.log('==========================');
|
||||||
|
|
||||||
|
const contextExport = await contextManager.exportContextSummary();
|
||||||
|
console.log('✅ Generated context export summary:');
|
||||||
|
console.log(contextExport.substring(0, 300) + '...\n');
|
||||||
|
|
||||||
|
// Demo 8: Session End
|
||||||
|
console.log('📍 Demo 8: Session End');
|
||||||
|
console.log('=======================');
|
||||||
|
|
||||||
|
await contextManager.onSessionEnd('demo-session-001');
|
||||||
|
console.log('✅ Ended session: demo-session-001');
|
||||||
|
|
||||||
|
// Demo 9: File Structure Verification
|
||||||
|
console.log('\n📁 Demo 9: File Structure Verification');
|
||||||
|
console.log('=======================================');
|
||||||
|
|
||||||
|
const verifyFiles = [
|
||||||
|
'context/shared-context.md',
|
||||||
|
'decisions/decisions-log.md',
|
||||||
|
'progress/progress-summary.md',
|
||||||
|
'quality/quality-metrics.md'
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const file of verifyFiles) {
|
||||||
|
const filePath = path.join(testWorkspace, file);
|
||||||
|
if (fs.existsSync(filePath)) {
|
||||||
|
const stats = fs.statSync(filePath);
|
||||||
|
console.log(`✅ ${file} - ${stats.size} bytes`);
|
||||||
|
} else {
|
||||||
|
console.log(`❌ ${file} - Not found`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('\n🎉 Context Persistence Demo Completed Successfully!');
|
||||||
|
console.log('====================================================');
|
||||||
|
console.log(`📁 Test workspace created at: ${testWorkspace}`);
|
||||||
|
console.log('💡 All context files are human-readable markdown');
|
||||||
|
console.log('🔧 Ready for integration with BMAD agents and IDE utilities');
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('\n❌ Demo failed:', error.message);
|
||||||
|
console.error(error.stack);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run the demo
|
||||||
|
if (require.main === module) {
|
||||||
|
demoContextPersistence();
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { demoContextPersistence };
|
||||||
|
|
@ -0,0 +1,292 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
// Demo script to test Agent Handoff Automation functionality
|
||||||
|
const path = require('path');
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
// Import managers
|
||||||
|
const ContextManager = require('./installer/lib/context-manager');
|
||||||
|
const HandoffManager = require('./installer/lib/handoff-manager');
|
||||||
|
|
||||||
|
async function demoHandoffAutomation() {
|
||||||
|
console.log('🤝 BMAD Agent Handoff Automation Demo');
|
||||||
|
console.log('=====================================\n');
|
||||||
|
|
||||||
|
// Create a test workspace in /tmp
|
||||||
|
const testWorkspace = '/tmp/bmad-handoff-test/.workspace';
|
||||||
|
if (fs.existsSync(testWorkspace)) {
|
||||||
|
fs.rmSync(path.dirname(testWorkspace), { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
const contextManager = new ContextManager(testWorkspace);
|
||||||
|
const handoffManager = new HandoffManager(testWorkspace);
|
||||||
|
console.log('✅ Initialized test workspace at:', testWorkspace);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Demo 1: Setup Rich Context for Handoffs
|
||||||
|
console.log('\n📍 Demo 1: Setting Up Rich Workspace Context');
|
||||||
|
console.log('==============================================');
|
||||||
|
|
||||||
|
// Create rich context
|
||||||
|
await contextManager.onSessionStart('demo-dev-session', 'developer');
|
||||||
|
await contextManager.updateSharedContext({
|
||||||
|
currentFocus: 'Implementing user authentication system with OAuth2 integration and JWT token management',
|
||||||
|
nextSteps: [
|
||||||
|
'Complete OAuth2 provider integration with Google and GitHub',
|
||||||
|
'Implement JWT token refresh mechanism',
|
||||||
|
'Add user profile management endpoints',
|
||||||
|
'Create comprehensive test suite for authentication flows'
|
||||||
|
]
|
||||||
|
});
|
||||||
|
console.log('✅ Created shared context for authentication system development');
|
||||||
|
|
||||||
|
// Add several decisions
|
||||||
|
await contextManager.logDecision({
|
||||||
|
title: 'Use JWT for session management',
|
||||||
|
agent: 'architect',
|
||||||
|
context: 'User Authentication System Design',
|
||||||
|
decision: 'Implement JWT tokens for stateless session management',
|
||||||
|
rationale: 'JWTs provide scalable, stateless authentication suitable for microservices architecture',
|
||||||
|
alternatives: 'Session cookies, server-side sessions, OAuth tokens only',
|
||||||
|
impact: 'Enables horizontal scaling and reduces server memory usage',
|
||||||
|
status: 'active'
|
||||||
|
});
|
||||||
|
|
||||||
|
await contextManager.logDecision({
|
||||||
|
title: 'OAuth2 provider selection',
|
||||||
|
agent: 'developer',
|
||||||
|
context: 'Authentication integration requirements',
|
||||||
|
decision: 'Support Google, GitHub, and Microsoft OAuth2 providers',
|
||||||
|
rationale: 'Covers 90% of target users based on user research data',
|
||||||
|
alternatives: 'Single provider, more providers, custom authentication',
|
||||||
|
impact: 'Reduces friction for user onboarding and registration',
|
||||||
|
status: 'active'
|
||||||
|
});
|
||||||
|
|
||||||
|
await contextManager.logDecision({
|
||||||
|
title: 'Database schema for user profiles',
|
||||||
|
agent: 'developer',
|
||||||
|
context: 'User data storage requirements',
|
||||||
|
decision: 'Use PostgreSQL with normalized user profile tables',
|
||||||
|
rationale: 'ACID compliance needed for user data integrity and security',
|
||||||
|
alternatives: 'MongoDB, SQLite, denormalized schema',
|
||||||
|
impact: 'Ensures data consistency and supports complex queries',
|
||||||
|
status: 'active'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add progress tracking
|
||||||
|
await contextManager.updateProgress({
|
||||||
|
currentStory: 'Story 2.1: User Authentication System Implementation',
|
||||||
|
completedTasks: [
|
||||||
|
'Set up OAuth2 client registration with providers',
|
||||||
|
'Implement basic JWT token generation and validation',
|
||||||
|
'Create user profile database schema',
|
||||||
|
'Add password hashing with bcrypt',
|
||||||
|
'Implement basic login/logout endpoints'
|
||||||
|
],
|
||||||
|
pendingTasks: [
|
||||||
|
'Add token refresh mechanism',
|
||||||
|
'Implement OAuth2 callback handlers',
|
||||||
|
'Create user profile management API',
|
||||||
|
'Add comprehensive error handling',
|
||||||
|
'Write integration tests for all auth flows'
|
||||||
|
],
|
||||||
|
blockers: [
|
||||||
|
'Waiting for security team review of JWT implementation',
|
||||||
|
'Need clarification on user profile field requirements'
|
||||||
|
],
|
||||||
|
qualityScore: 'B+ (82/100) - Core functionality working, needs testing and security review'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add quality metrics
|
||||||
|
await contextManager.updateQualityMetrics({
|
||||||
|
agent: 'qa-agent',
|
||||||
|
story: 'Story 2.1: User Authentication System Implementation',
|
||||||
|
realityAuditScore: '82/100',
|
||||||
|
patternCompliance: '85/100',
|
||||||
|
technicalDebtScore: '80/100',
|
||||||
|
overallQuality: 'B+ (82/100)',
|
||||||
|
details: 'Authentication flows working correctly. Security review pending. Test coverage at 75%.'
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('✅ Rich context established with decisions, progress, and quality metrics');
|
||||||
|
|
||||||
|
// Demo 2: Developer to QA Handoff
|
||||||
|
console.log('\n🔄 Demo 2: Developer to QA Handoff (Agent-Specific Filtering)');
|
||||||
|
console.log('==============================================================');
|
||||||
|
|
||||||
|
const devToQaHandoff = await handoffManager.createHandoff(
|
||||||
|
'developer',
|
||||||
|
'qa-engineer',
|
||||||
|
'Authentication system core functionality complete. Ready for comprehensive testing including security validation, OAuth2 flow testing, and edge case scenarios.'
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log('✅ Created Dev → QA handoff with agent-specific filtering:');
|
||||||
|
console.log(` - Handoff ID: ${devToQaHandoff.handoffId}`);
|
||||||
|
console.log(` - Target Agent Type: qa`);
|
||||||
|
console.log(` - Context filtered for testing and validation focus`);
|
||||||
|
|
||||||
|
// Demo 3: QA to Architect Handoff
|
||||||
|
console.log('\n🔄 Demo 3: QA to Architect Handoff (Different Agent Filtering)');
|
||||||
|
console.log('==============================================================');
|
||||||
|
|
||||||
|
const qaToArchHandoff = await handoffManager.createHandoff(
|
||||||
|
'qa-engineer',
|
||||||
|
'solution-architect',
|
||||||
|
'Security testing identified potential scalability concerns with JWT token storage. Need architectural review for high-volume scenarios.'
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log('✅ Created QA → Architect handoff:');
|
||||||
|
console.log(` - Handoff ID: ${qaToArchHandoff.handoffId}`);
|
||||||
|
console.log(` - Target Agent Type: architect`);
|
||||||
|
console.log(` - Context filtered for design and architecture decisions`);
|
||||||
|
|
||||||
|
// Demo 4: Architect to PM Handoff
|
||||||
|
console.log('\n🔄 Demo 4: Architect to Project Manager Handoff');
|
||||||
|
console.log('================================================');
|
||||||
|
|
||||||
|
const archToPmHandoff = await handoffManager.createHandoff(
|
||||||
|
'solution-architect',
|
||||||
|
'project-manager',
|
||||||
|
'Architecture review complete. Recommended Redis implementation for session scaling will require additional 2 weeks and budget approval for infrastructure.'
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log('✅ Created Architect → PM handoff:');
|
||||||
|
console.log(` - Handoff ID: ${archToPmHandoff.handoffId}`);
|
||||||
|
console.log(` - Target Agent Type: pm`);
|
||||||
|
console.log(` - Context filtered for business and stakeholder concerns`);
|
||||||
|
|
||||||
|
// Demo 5: Handoff Validation and Quality
|
||||||
|
console.log('\n📊 Demo 5: Handoff Validation and Quality Assessment');
|
||||||
|
console.log('====================================================');
|
||||||
|
|
||||||
|
// Read and validate one of the handoff files
|
||||||
|
const handoffFile = fs.readFileSync(devToQaHandoff.filePath, 'utf8');
|
||||||
|
console.log('✅ Sample handoff content validation:');
|
||||||
|
console.log(` - Context Summary: ${handoffFile.includes('Context Summary') ? 'Present' : 'Missing'}`);
|
||||||
|
console.log(` - Key Decisions: ${handoffFile.includes('Key Decisions Made') ? 'Present' : 'Missing'}`);
|
||||||
|
console.log(` - Next Actions: ${handoffFile.includes('Next Actions for') ? 'Present' : 'Missing'}`);
|
||||||
|
console.log(` - Agent-Specific Filtering: ${handoffFile.includes('Target Agent Type:') ? 'Applied' : 'Missing'}`);
|
||||||
|
console.log(` - File References: ${handoffFile.includes('Files and References') ? 'Present' : 'Missing'}`);
|
||||||
|
|
||||||
|
// Demo 6: Handoff Registry and Status
|
||||||
|
console.log('\n📋 Demo 6: Handoff Registry and Status Management');
|
||||||
|
console.log('=================================================');
|
||||||
|
|
||||||
|
const pendingHandoffs = await handoffManager.getPendingHandoffs();
|
||||||
|
console.log(`✅ Total pending handoffs: ${pendingHandoffs.length}`);
|
||||||
|
|
||||||
|
const qaHandoffs = await handoffManager.getPendingHandoffs('qa-engineer');
|
||||||
|
console.log(`✅ Pending handoffs for QA: ${qaHandoffs.length}`);
|
||||||
|
|
||||||
|
const archHandoffs = await handoffManager.getPendingHandoffs('solution-architect');
|
||||||
|
console.log(`✅ Pending handoffs for Architect: ${archHandoffs.length}`);
|
||||||
|
|
||||||
|
const stats = await handoffManager.getHandoffStats();
|
||||||
|
console.log(`✅ Handoff system statistics:`);
|
||||||
|
console.log(` - Total handoffs created: ${stats.total}`);
|
||||||
|
console.log(` - Average validation score: ${stats.avgScore.toFixed(1)}/100`);
|
||||||
|
console.log(` - Grade distribution: ${Object.entries(stats.gradeDistribution).map(([grade, count]) => `${grade}:${count}`).join(', ')}`);
|
||||||
|
|
||||||
|
// Demo 7: Cross-IDE Compatibility Test
|
||||||
|
console.log('\n🌐 Demo 7: Cross-IDE Compatibility Verification');
|
||||||
|
console.log('===============================================');
|
||||||
|
|
||||||
|
// Simulate handoffs between different IDE environments
|
||||||
|
const crossIdeHandoffs = [
|
||||||
|
{ from: 'claude-code-dev', to: 'cursor-qa', context: 'Handoff from Claude Code CLI to Cursor IDE' },
|
||||||
|
{ from: 'windsurf-architect', to: 'claude-code-dev', context: 'Handoff from Windsurf to Claude Code CLI' },
|
||||||
|
{ from: 'vscode-pm', to: 'gemini-qa', context: 'Handoff from VSCode to Gemini interface' }
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const handoff of crossIdeHandoffs) {
|
||||||
|
const result = await handoffManager.createHandoff(handoff.from, handoff.to, handoff.context);
|
||||||
|
console.log(`✅ Cross-IDE handoff: ${handoff.from} → ${handoff.to}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('✅ All cross-IDE handoffs created successfully - demonstrates universal compatibility');
|
||||||
|
|
||||||
|
// Demo 8: Handoff Content Analysis
|
||||||
|
console.log('\n🔍 Demo 8: Handoff Content Analysis (Agent-Specific Filtering Verification)');
|
||||||
|
console.log('==========================================================================');
|
||||||
|
|
||||||
|
// Analyze Dev → QA handoff content
|
||||||
|
const devQaContent = fs.readFileSync(devToQaHandoff.filePath, 'utf8');
|
||||||
|
const hasTestingFocus = devQaContent.toLowerCase().includes('testing') ||
|
||||||
|
devQaContent.toLowerCase().includes('validation') ||
|
||||||
|
devQaContent.toLowerCase().includes('quality');
|
||||||
|
|
||||||
|
console.log('✅ Dev → QA handoff analysis:');
|
||||||
|
console.log(` - Testing/Quality focus: ${hasTestingFocus ? 'YES' : 'NO'}`);
|
||||||
|
console.log(` - QA-specific actions: ${devQaContent.includes('Review acceptance criteria') ? 'YES' : 'NO'}`);
|
||||||
|
console.log(` - Implementation details filtered: ${!devQaContent.includes('code specifics') ? 'YES' : 'NO'}`);
|
||||||
|
|
||||||
|
// Analyze Arch → PM handoff content
|
||||||
|
const archPmContent = fs.readFileSync(archToPmHandoff.filePath, 'utf8');
|
||||||
|
const hasBusinessFocus = archPmContent.toLowerCase().includes('business') ||
|
||||||
|
archPmContent.toLowerCase().includes('stakeholder') ||
|
||||||
|
archPmContent.toLowerCase().includes('budget');
|
||||||
|
|
||||||
|
console.log('✅ Architect → PM handoff analysis:');
|
||||||
|
console.log(` - Business/Stakeholder focus: ${hasBusinessFocus ? 'YES' : 'NO'}`);
|
||||||
|
console.log(` - PM-specific actions: ${archPmContent.includes('Review project scope') ? 'YES' : 'NO'}`);
|
||||||
|
console.log(` - Technical details filtered: ${!archPmContent.includes('implementation specifics') ? 'YES' : 'NO'}`);
|
||||||
|
|
||||||
|
// Demo 9: File Structure and Registry Verification
|
||||||
|
console.log('\n📁 Demo 9: File Structure and Registry Verification');
|
||||||
|
console.log('===================================================');
|
||||||
|
|
||||||
|
const verifyFiles = [
|
||||||
|
'handoffs/handoff-registry.json',
|
||||||
|
'handoffs/audit-trail.md'
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const file of verifyFiles) {
|
||||||
|
const filePath = path.join(testWorkspace, file);
|
||||||
|
if (fs.existsSync(filePath)) {
|
||||||
|
const stats = fs.statSync(filePath);
|
||||||
|
console.log(`✅ ${file} - ${stats.size} bytes`);
|
||||||
|
|
||||||
|
if (file.endsWith('.json')) {
|
||||||
|
const content = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
||||||
|
console.log(` - Registry entries: ${content.length}`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log(`❌ ${file} - Not found`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify handoff files
|
||||||
|
const handoffFiles = fs.readdirSync(path.join(testWorkspace, 'handoffs'))
|
||||||
|
.filter(f => f.endsWith('.md') && f !== 'audit-trail.md');
|
||||||
|
|
||||||
|
console.log(`✅ Individual handoff files created: ${handoffFiles.length}`);
|
||||||
|
handoffFiles.forEach((file, index) => {
|
||||||
|
console.log(` ${index + 1}. ${file}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('\n🎉 Agent Handoff Automation Demo Completed Successfully!');
|
||||||
|
console.log('========================================================');
|
||||||
|
console.log(`📁 Test workspace: ${testWorkspace}`);
|
||||||
|
console.log('🤝 All handoff features demonstrated:');
|
||||||
|
console.log(' ✅ Agent-specific context filtering');
|
||||||
|
console.log(' ✅ Intelligent next action generation');
|
||||||
|
console.log(' ✅ Context integration with decisions and progress');
|
||||||
|
console.log(' ✅ Handoff validation and quality scoring');
|
||||||
|
console.log(' ✅ Registry and audit trail management');
|
||||||
|
console.log(' ✅ Cross-IDE compatibility');
|
||||||
|
console.log(' ✅ Asynchronous handoff processing');
|
||||||
|
console.log('🔧 Ready for integration with BMAD agents across all IDEs');
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('\n❌ Demo failed:', error.message);
|
||||||
|
console.error(error.stack);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run the demo
|
||||||
|
if (require.main === module) {
|
||||||
|
demoHandoffAutomation();
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { demoHandoffAutomation };
|
||||||
|
|
@ -0,0 +1,263 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
// Demo script to test Multi-Role Agent Handoff functionality
|
||||||
|
const path = require('path');
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
// Import managers
|
||||||
|
const ContextManager = require('./installer/lib/context-manager');
|
||||||
|
const HandoffManager = require('./installer/lib/handoff-manager');
|
||||||
|
|
||||||
|
async function demoMultiRoleHandoffs() {
|
||||||
|
console.log('🧠 BMAD Multi-Role Agent Handoff Demo');
|
||||||
|
console.log('=====================================\n');
|
||||||
|
|
||||||
|
// Create a test workspace in /tmp
|
||||||
|
const testWorkspace = '/tmp/bmad-multi-role-test/.workspace';
|
||||||
|
if (fs.existsSync(testWorkspace)) {
|
||||||
|
fs.rmSync(path.dirname(testWorkspace), { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
const contextManager = new ContextManager(testWorkspace);
|
||||||
|
const handoffManager = new HandoffManager(testWorkspace);
|
||||||
|
console.log('✅ Initialized test workspace at:', testWorkspace);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Demo 1: Setup Complex Context Requiring Multi-Role Analysis
|
||||||
|
console.log('\n📊 Demo 1: Setting Up Complex Performance Analysis Context');
|
||||||
|
console.log('========================================================');
|
||||||
|
|
||||||
|
await contextManager.updateSharedContext({
|
||||||
|
currentFocus: 'E-commerce platform experiencing performance degradation under high load with 40% slower checkout times',
|
||||||
|
nextSteps: [
|
||||||
|
'Analyze performance bottlenecks in payment processing system',
|
||||||
|
'Research industry benchmarks for e-commerce performance',
|
||||||
|
'Investigate user behavior patterns during high-traffic periods',
|
||||||
|
'Design scalable architecture improvements',
|
||||||
|
'Implement comprehensive performance monitoring'
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add technical decisions
|
||||||
|
await contextManager.logDecision({
|
||||||
|
title: 'Database query optimization strategy',
|
||||||
|
agent: 'developer',
|
||||||
|
context: 'Performance optimization initiative',
|
||||||
|
decision: 'Implement query caching and index optimization for product catalog',
|
||||||
|
rationale: 'Analysis shows 60% of slow queries are product lookups during checkout',
|
||||||
|
impact: 'Expected 30% improvement in checkout response times',
|
||||||
|
status: 'active'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add analytical data
|
||||||
|
await contextManager.logDecision({
|
||||||
|
title: 'Performance metrics baseline establishment',
|
||||||
|
agent: 'analyst',
|
||||||
|
context: 'Performance monitoring requirements',
|
||||||
|
decision: 'Track checkout completion time, cart abandonment rate, and server response times',
|
||||||
|
rationale: 'Need quantitative data to measure improvement impact and identify patterns',
|
||||||
|
impact: 'Enables data-driven optimization decisions',
|
||||||
|
status: 'active'
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('✅ Complex performance context established');
|
||||||
|
|
||||||
|
// Demo 2: Developer-Analyst Handoff for Performance Optimization
|
||||||
|
console.log('\n🔄 Demo 2: Developer-Analyst Handoff (Performance Optimization)');
|
||||||
|
console.log('==============================================================');
|
||||||
|
|
||||||
|
const devAnalystHandoff = await handoffManager.createHandoff(
|
||||||
|
'senior-developer',
|
||||||
|
'dev-analyst-specialist',
|
||||||
|
'Performance optimization requires both implementation expertise and data analysis. Need to analyze current metrics, identify patterns, and implement data-driven improvements.'
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log('✅ Created Dev-Analyst handoff:');
|
||||||
|
console.log(` - Handoff ID: ${devAnalystHandoff.handoffId}`);
|
||||||
|
console.log(` - Detected Type: ${handoffManager.getAgentType('dev-analyst-specialist')}`);
|
||||||
|
console.log(` - Multi-role filtering applied for development + analysis`);
|
||||||
|
|
||||||
|
// Demo 3: QA-Research Handoff for Compliance Testing
|
||||||
|
console.log('\n🔍 Demo 3: QA-Research Handoff (Compliance & Standards)');
|
||||||
|
console.log('======================================================');
|
||||||
|
|
||||||
|
const qaResearchHandoff = await handoffManager.createHandoff(
|
||||||
|
'qa-engineer',
|
||||||
|
'qa-research-specialist',
|
||||||
|
'Payment processing compliance testing requires research into PCI DSS standards, industry testing practices, and regulatory requirements. Need comprehensive research-based testing strategy.'
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log('✅ Created QA-Research handoff:');
|
||||||
|
console.log(` - Handoff ID: ${qaResearchHandoff.handoffId}`);
|
||||||
|
console.log(` - Detected Type: ${handoffManager.getAgentType('qa-research-specialist')}`);
|
||||||
|
console.log(` - Multi-role filtering applied for QA + research`);
|
||||||
|
|
||||||
|
// Demo 4: Architect-Brainstorming Handoff for Innovation
|
||||||
|
console.log('\n💡 Demo 4: Architect-Brainstorming Handoff (Creative Solutions)');
|
||||||
|
console.log('===============================================================');
|
||||||
|
|
||||||
|
const archBrainstormHandoff = await handoffManager.createHandoff(
|
||||||
|
'solution-architect',
|
||||||
|
'architect-brainstorming-lead',
|
||||||
|
'Traditional scaling approaches may not be sufficient. Need creative exploration of innovative architecture patterns, microservices alternatives, and cutting-edge performance solutions.'
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log('✅ Created Architect-Brainstorming handoff:');
|
||||||
|
console.log(` - Handoff ID: ${archBrainstormHandoff.handoffId}`);
|
||||||
|
console.log(` - Detected Type: ${handoffManager.getAgentType('architect-brainstorming-lead')}`);
|
||||||
|
console.log(` - Multi-role filtering applied for architecture + brainstorming`);
|
||||||
|
|
||||||
|
// Demo 5: PM-Analyst Handoff for Business Impact Analysis
|
||||||
|
console.log('\n📈 Demo 5: PM-Analyst Handoff (Business Impact Analysis)');
|
||||||
|
console.log('========================================================');
|
||||||
|
|
||||||
|
const pmAnalystHandoff = await handoffManager.createHandoff(
|
||||||
|
'project-manager',
|
||||||
|
'pm-analyst-consultant',
|
||||||
|
'Need to analyze business impact of performance issues: revenue loss, customer satisfaction trends, competitive positioning. Require data-driven project prioritization and resource allocation.'
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log('✅ Created PM-Analyst handoff:');
|
||||||
|
console.log(` - Handoff ID: ${pmAnalystHandoff.handoffId}`);
|
||||||
|
console.log(` - Detected Type: ${handoffManager.getAgentType('pm-analyst-consultant')}`);
|
||||||
|
console.log(` - Multi-role filtering applied for PM + analysis`);
|
||||||
|
|
||||||
|
// Demo 6: UX-Research Handoff for User Experience Investigation
|
||||||
|
console.log('\n👥 Demo 6: UX-Research Handoff (User Experience Investigation)');
|
||||||
|
console.log('=============================================================');
|
||||||
|
|
||||||
|
const uxResearchHandoff = await handoffManager.createHandoff(
|
||||||
|
'ux-designer',
|
||||||
|
'ux-research-specialist',
|
||||||
|
'Performance issues affect user experience. Need research into user behavior during slow checkout, accessibility implications, and evidence-based UX improvements for high-load scenarios.'
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log('✅ Created UX-Research handoff:');
|
||||||
|
console.log(` - Handoff ID: ${uxResearchHandoff.handoffId}`);
|
||||||
|
console.log(` - Detected Type: ${handoffManager.getAgentType('ux-research-specialist')}`);
|
||||||
|
console.log(` - Multi-role filtering applied for UX + research`);
|
||||||
|
|
||||||
|
// Demo 7: Pure Role Handoffs for Comparison
|
||||||
|
console.log('\n🎯 Demo 7: Pure Role Handoffs (Single-Role Comparison)');
|
||||||
|
console.log('======================================================');
|
||||||
|
|
||||||
|
const pureAnalyst = await handoffManager.createHandoff('data-team', 'pure-analyst', 'Pure analyst role for comparison');
|
||||||
|
const pureBrainstorming = await handoffManager.createHandoff('design-team', 'creative-brainstorming', 'Pure brainstorming role for comparison');
|
||||||
|
const pureResearch = await handoffManager.createHandoff('research-team', 'research-specialist', 'Pure research role for comparison');
|
||||||
|
|
||||||
|
console.log('✅ Created pure role handoffs for comparison:');
|
||||||
|
console.log(` - Pure Analyst: ${handoffManager.getAgentType('pure-analyst')}`);
|
||||||
|
console.log(` - Pure Brainstorming: ${handoffManager.getAgentType('creative-brainstorming')}`);
|
||||||
|
console.log(` - Pure Research: ${handoffManager.getAgentType('research-specialist')}`);
|
||||||
|
|
||||||
|
// Demo 8: Handoff Content Analysis - Multi-Role vs Single-Role
|
||||||
|
console.log('\n🔍 Demo 8: Multi-Role vs Single-Role Content Analysis');
|
||||||
|
console.log('====================================================');
|
||||||
|
|
||||||
|
// Analyze Dev-Analyst handoff
|
||||||
|
const devAnalystContent = fs.readFileSync(devAnalystHandoff.filePath, 'utf8');
|
||||||
|
console.log('✅ Dev-Analyst handoff analysis:');
|
||||||
|
console.log(` - Contains development focus: ${devAnalystContent.toLowerCase().includes('technical') ? 'YES' : 'NO'}`);
|
||||||
|
console.log(` - Contains analysis focus: ${devAnalystContent.toLowerCase().includes('analysis') || devAnalystContent.toLowerCase().includes('data') ? 'YES' : 'NO'}`);
|
||||||
|
console.log(` - Multi-role description: ${devAnalystContent.includes('multi') ? 'YES' : 'NO'}`);
|
||||||
|
|
||||||
|
// Analyze QA-Research handoff
|
||||||
|
const qaResearchContent = fs.readFileSync(qaResearchHandoff.filePath, 'utf8');
|
||||||
|
console.log('✅ QA-Research handoff analysis:');
|
||||||
|
console.log(` - Contains QA focus: ${qaResearchContent.toLowerCase().includes('testing') || qaResearchContent.toLowerCase().includes('quality') ? 'YES' : 'NO'}`);
|
||||||
|
console.log(` - Contains research focus: ${qaResearchContent.toLowerCase().includes('research') || qaResearchContent.toLowerCase().includes('investigate') ? 'YES' : 'NO'}`);
|
||||||
|
console.log(` - Research methodology actions: ${qaResearchContent.includes('Research industry') ? 'YES' : 'NO'}`);
|
||||||
|
|
||||||
|
// Compare with pure role handoff
|
||||||
|
const pureAnalystContent = fs.readFileSync(pureAnalyst.filePath, 'utf8');
|
||||||
|
console.log('✅ Pure Analyst comparison:');
|
||||||
|
console.log(` - Pure analyst actions only: ${pureAnalystContent.includes('Analyze available data') ? 'YES' : 'NO'}`);
|
||||||
|
console.log(` - No development specifics: ${!pureAnalystContent.toLowerCase().includes('code implementation') ? 'YES' : 'NO'}`);
|
||||||
|
|
||||||
|
// Demo 9: Agent Type Detection Testing
|
||||||
|
console.log('\n🤖 Demo 9: Agent Type Detection Algorithm Testing');
|
||||||
|
console.log('==================================================');
|
||||||
|
|
||||||
|
const testCases = [
|
||||||
|
{ name: 'dev-analyst-expert', expected: 'dev-analyst' },
|
||||||
|
{ name: 'qa-research-lead', expected: 'qa-research' },
|
||||||
|
{ name: 'architect-brainstorming-specialist', expected: 'architect-brainstorming' },
|
||||||
|
{ name: 'pm-analyst-consultant', expected: 'pm-analyst' },
|
||||||
|
{ name: 'ux-research-designer', expected: 'ux-research' },
|
||||||
|
{ name: 'senior-developer', expected: 'dev' },
|
||||||
|
{ name: 'data-analyst', expected: 'analyst' },
|
||||||
|
{ name: 'creative-brainstorming-facilitator', expected: 'brainstorming' },
|
||||||
|
{ name: 'research-scientist', expected: 'research' }
|
||||||
|
];
|
||||||
|
|
||||||
|
console.log('✅ Agent type detection results:');
|
||||||
|
testCases.forEach(testCase => {
|
||||||
|
const detected = handoffManager.getAgentType(testCase.name);
|
||||||
|
const correct = detected === testCase.expected;
|
||||||
|
console.log(` ${correct ? '✅' : '❌'} ${testCase.name} → ${detected} (expected: ${testCase.expected})`);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Demo 10: Multi-Role Registry Analysis
|
||||||
|
console.log('\n📊 Demo 10: Multi-Role Registry and Analytics');
|
||||||
|
console.log('==============================================');
|
||||||
|
|
||||||
|
const stats = await handoffManager.getHandoffStats();
|
||||||
|
console.log('✅ Enhanced handoff system statistics:');
|
||||||
|
console.log(` - Total handoffs created: ${stats.total}`);
|
||||||
|
console.log(` - Average validation score: ${stats.avgScore.toFixed(1)}/100`);
|
||||||
|
console.log(` - Grade distribution: ${Object.entries(stats.gradeDistribution).map(([grade, count]) => `${grade}:${count}`).join(', ')}`);
|
||||||
|
|
||||||
|
// Count multi-role vs single-role handoffs
|
||||||
|
const registryFile = path.join(testWorkspace, 'handoffs', 'handoff-registry.json');
|
||||||
|
if (fs.existsSync(registryFile)) {
|
||||||
|
const registry = JSON.parse(fs.readFileSync(registryFile, 'utf8'));
|
||||||
|
const multiRoleCount = registry.filter(h =>
|
||||||
|
handoffManager.multiRoleFilters[handoffManager.getAgentType(h.targetAgent)]
|
||||||
|
).length;
|
||||||
|
|
||||||
|
console.log(` - Multi-role handoffs: ${multiRoleCount}`);
|
||||||
|
console.log(` - Single-role handoffs: ${registry.length - multiRoleCount}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Demo 11: File Structure Verification
|
||||||
|
console.log('\n📁 Demo 11: Enhanced File Structure Verification');
|
||||||
|
console.log('=================================================');
|
||||||
|
|
||||||
|
const handoffFiles = fs.readdirSync(path.join(testWorkspace, 'handoffs'))
|
||||||
|
.filter(f => f.endsWith('.md') && f !== 'audit-trail.md');
|
||||||
|
|
||||||
|
console.log(`✅ Handoff files created: ${handoffFiles.length}`);
|
||||||
|
|
||||||
|
// Categorize by role type
|
||||||
|
const multiRoleFiles = handoffFiles.filter(file => {
|
||||||
|
const targetAgent = file.split('-to-')[1]?.split('-')[0] + '-' + file.split('-to-')[1]?.split('-')[1];
|
||||||
|
return handoffManager.multiRoleFilters[handoffManager.getAgentType(targetAgent)];
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(` - Multi-role handoff files: ${multiRoleFiles.length}`);
|
||||||
|
console.log(` - Single-role handoff files: ${handoffFiles.length - multiRoleFiles.length}`);
|
||||||
|
|
||||||
|
console.log('\n🎉 Multi-Role Agent Handoff Demo Completed Successfully!');
|
||||||
|
console.log('=======================================================');
|
||||||
|
console.log(`📁 Test workspace: ${testWorkspace}`);
|
||||||
|
console.log('🧠 Enhanced multi-role capabilities demonstrated:');
|
||||||
|
console.log(' ✅ 8 agent types supported (dev, qa, architect, pm, ux-expert, analyst, brainstorming, research)');
|
||||||
|
console.log(' ✅ 5 multi-role combinations (dev-analyst, qa-research, architect-brainstorming, pm-analyst, ux-research)');
|
||||||
|
console.log(' ✅ Intelligent agent type detection with multi-role pattern matching');
|
||||||
|
console.log(' ✅ Combined context filtering for multi-role scenarios');
|
||||||
|
console.log(' ✅ Role-specific and multi-role action generation');
|
||||||
|
console.log(' ✅ Enhanced handoff validation for complex scenarios');
|
||||||
|
console.log('🚀 Ready for real-world multi-disciplinary AI agent collaboration!');
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('\n❌ Demo failed:', error.message);
|
||||||
|
console.error(error.stack);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run the demo
|
||||||
|
if (require.main === module) {
|
||||||
|
demoMultiRoleHandoffs();
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { demoMultiRoleHandoffs };
|
||||||
|
|
@ -0,0 +1,721 @@
|
||||||
|
const path = require("path");
|
||||||
|
const fs = require("fs-extra");
|
||||||
|
const chalk = require("chalk");
|
||||||
|
|
||||||
|
class WorkspaceSetup {
|
||||||
|
constructor() {
|
||||||
|
this.workspaceStructure = {
|
||||||
|
'.workspace': {
|
||||||
|
'sessions': {},
|
||||||
|
'context': {},
|
||||||
|
'handoffs': {},
|
||||||
|
'decisions': {},
|
||||||
|
'progress': {},
|
||||||
|
'quality': {},
|
||||||
|
'archive': {}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async createWorkspaceDirectory(installDir, spinner) {
|
||||||
|
try {
|
||||||
|
spinner.text = 'Creating collaborative workspace structure...';
|
||||||
|
|
||||||
|
const workspacePath = path.join(installDir, '.workspace');
|
||||||
|
|
||||||
|
// Create main workspace directory
|
||||||
|
await fs.ensureDir(workspacePath);
|
||||||
|
|
||||||
|
// Create subdirectories
|
||||||
|
const subdirs = ['sessions', 'context', 'handoffs', 'decisions', 'progress', 'quality', 'archive'];
|
||||||
|
|
||||||
|
for (const subdir of subdirs) {
|
||||||
|
await fs.ensureDir(path.join(workspacePath, subdir));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create initial workspace configuration
|
||||||
|
const workspaceConfig = {
|
||||||
|
version: "1.0",
|
||||||
|
created: new Date().toISOString(),
|
||||||
|
structure: subdirs,
|
||||||
|
settings: {
|
||||||
|
maxContextSize: "10MB",
|
||||||
|
sessionTimeout: "2h",
|
||||||
|
archiveAfter: "30d",
|
||||||
|
maxConcurrentSessions: 5
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
await fs.writeJSON(
|
||||||
|
path.join(workspacePath, 'workspace-config.json'),
|
||||||
|
workspaceConfig,
|
||||||
|
{ spaces: 2 }
|
||||||
|
);
|
||||||
|
|
||||||
|
// Create initial README
|
||||||
|
const readmeContent = `# BMAD Collaborative Workspace
|
||||||
|
|
||||||
|
This directory contains the collaborative workspace system for multi-session AI agent coordination.
|
||||||
|
|
||||||
|
## Directory Structure
|
||||||
|
|
||||||
|
- \`sessions/\` - Active session tracking
|
||||||
|
- \`context/\` - Shared context files and decisions
|
||||||
|
- \`handoffs/\` - Agent transition packages
|
||||||
|
- \`decisions/\` - Architectural and design decisions
|
||||||
|
- \`progress/\` - Story and task progress tracking
|
||||||
|
- \`quality/\` - Quality metrics and audit results
|
||||||
|
- \`archive/\` - Compressed historical context
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Claude Code CLI Users
|
||||||
|
- Use \`*workspace-init\` to initialize a collaborative session
|
||||||
|
- Use \`*workspace-status\` to see active sessions and progress
|
||||||
|
- Use \`*workspace-cleanup\` for maintenance
|
||||||
|
|
||||||
|
### Other IDE Users
|
||||||
|
- Run \`npm run workspace-init\` to initialize
|
||||||
|
- Run \`npm run workspace-status\` for status
|
||||||
|
- Run \`npm run workspace-cleanup\` for maintenance
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
Workspace settings can be modified in \`workspace-config.json\`.
|
||||||
|
`;
|
||||||
|
|
||||||
|
await fs.writeFile(path.join(workspacePath, 'README.md'), readmeContent);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(chalk.red('Failed to create workspace directory:'), error.message);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async createWorkspaceUtilities(installDir, selectedIDEs, spinner) {
|
||||||
|
try {
|
||||||
|
spinner.text = 'Installing workspace utilities...';
|
||||||
|
|
||||||
|
const utilsPath = path.join(installDir, 'workspace-utils');
|
||||||
|
await fs.ensureDir(utilsPath);
|
||||||
|
|
||||||
|
// Create utility scripts
|
||||||
|
await this.createInitScript(utilsPath);
|
||||||
|
await this.createStatusScript(utilsPath);
|
||||||
|
await this.createCleanupScript(utilsPath);
|
||||||
|
await this.createHandoffScript(utilsPath);
|
||||||
|
await this.createSyncScript(utilsPath);
|
||||||
|
|
||||||
|
// Create package.json scripts if package.json exists
|
||||||
|
await this.addPackageJsonScripts(installDir);
|
||||||
|
|
||||||
|
// Create IDE-specific documentation
|
||||||
|
await this.createIDEDocumentation(utilsPath, selectedIDEs);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(chalk.red('Failed to create workspace utilities:'), error.message);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async createInitScript(utilsPath) {
|
||||||
|
const initScript = `#!/usr/bin/env node
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const crypto = require('crypto');
|
||||||
|
|
||||||
|
async function initWorkspace() {
|
||||||
|
try {
|
||||||
|
const workspacePath = path.join(process.cwd(), '.workspace');
|
||||||
|
|
||||||
|
if (!fs.existsSync(workspacePath)) {
|
||||||
|
console.error('❌ Workspace directory not found. Run \`npx bmad-method install\` first.');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate session ID
|
||||||
|
const sessionId = crypto.randomBytes(8).toString('hex');
|
||||||
|
const timestamp = new Date().toISOString();
|
||||||
|
|
||||||
|
// Create session file
|
||||||
|
const sessionData = {
|
||||||
|
id: sessionId,
|
||||||
|
created: timestamp,
|
||||||
|
lastHeartbeat: timestamp,
|
||||||
|
ide: process.env.IDE_TYPE || 'unknown',
|
||||||
|
pid: process.pid,
|
||||||
|
user: process.env.USER || process.env.USERNAME || 'unknown'
|
||||||
|
};
|
||||||
|
|
||||||
|
const sessionsPath = path.join(workspacePath, 'sessions');
|
||||||
|
if (!fs.existsSync(sessionsPath)) {
|
||||||
|
fs.mkdirSync(sessionsPath, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
const sessionFile = path.join(sessionsPath, \`\${sessionId}.json\`);
|
||||||
|
fs.writeFileSync(sessionFile, JSON.stringify(sessionData, null, 2));
|
||||||
|
|
||||||
|
console.log('✅ Workspace initialized successfully');
|
||||||
|
console.log(\`📍 Session ID: \${sessionId}\`);
|
||||||
|
console.log(\`🕐 Created: \${timestamp}\`);
|
||||||
|
|
||||||
|
return sessionId;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Failed to initialize workspace:', error.message);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (require.main === module) {
|
||||||
|
initWorkspace();
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { initWorkspace };
|
||||||
|
`;
|
||||||
|
|
||||||
|
await fs.writeFile(path.join(utilsPath, 'init.js'), initScript);
|
||||||
|
await fs.chmod(path.join(utilsPath, 'init.js'), 0o755);
|
||||||
|
}
|
||||||
|
|
||||||
|
async createStatusScript(utilsPath) {
|
||||||
|
const statusScript = `#!/usr/bin/env node
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
async function getWorkspaceStatus() {
|
||||||
|
try {
|
||||||
|
const workspacePath = path.join(process.cwd(), '.workspace');
|
||||||
|
|
||||||
|
if (!fs.existsSync(workspacePath)) {
|
||||||
|
console.error('❌ Workspace directory not found.');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read workspace config
|
||||||
|
const configPath = path.join(workspacePath, 'workspace-config.json');
|
||||||
|
let config = {};
|
||||||
|
if (fs.existsSync(configPath)) {
|
||||||
|
const configContent = fs.readFileSync(configPath, 'utf8');
|
||||||
|
config = JSON.parse(configContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get active sessions
|
||||||
|
const sessionsPath = path.join(workspacePath, 'sessions');
|
||||||
|
let sessionFiles = [];
|
||||||
|
if (fs.existsSync(sessionsPath)) {
|
||||||
|
sessionFiles = fs.readdirSync(sessionsPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
const activeSessions = [];
|
||||||
|
for (const file of sessionFiles) {
|
||||||
|
if (file.endsWith('.json')) {
|
||||||
|
try {
|
||||||
|
const sessionPath = path.join(sessionsPath, file);
|
||||||
|
const sessionContent = fs.readFileSync(sessionPath, 'utf8');
|
||||||
|
const sessionData = JSON.parse(sessionContent);
|
||||||
|
activeSessions.push(sessionData);
|
||||||
|
} catch (e) {
|
||||||
|
// Skip corrupted session files
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display status
|
||||||
|
console.log('🤝 BMAD Collaborative Workspace Status');
|
||||||
|
console.log('=====================================');
|
||||||
|
console.log(\`📁 Workspace: \${workspacePath}\`);
|
||||||
|
console.log(\`⚙️ Version: \${config.version || 'Unknown'}\`);
|
||||||
|
console.log(\`🕐 Created: \${config.created || 'Unknown'}\`);
|
||||||
|
console.log(\`👥 Active Sessions: \${activeSessions.length}\`);
|
||||||
|
|
||||||
|
if (activeSessions.length > 0) {
|
||||||
|
console.log('\\n📍 Session Details:');
|
||||||
|
activeSessions.forEach((session, index) => {
|
||||||
|
console.log(\` \${index + 1}. \${session.id} (\${session.ide}) - \${session.user}\`);
|
||||||
|
console.log(\` Created: \${new Date(session.created).toLocaleString()}\`);
|
||||||
|
console.log(\` Last Heartbeat: \${new Date(session.lastHeartbeat).toLocaleString()}\`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check directory structure
|
||||||
|
const directories = ['context', 'handoffs', 'decisions', 'progress', 'quality', 'archive'];
|
||||||
|
const missingDirs = [];
|
||||||
|
|
||||||
|
for (const dir of directories) {
|
||||||
|
if (!fs.existsSync(path.join(workspacePath, dir))) {
|
||||||
|
missingDirs.push(dir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (missingDirs.length > 0) {
|
||||||
|
console.log(\`\\n⚠️ Missing directories: \${missingDirs.join(', ')}\`);
|
||||||
|
console.log(' Run \`npm run workspace-cleanup\` to repair.');
|
||||||
|
} else {
|
||||||
|
console.log('\\n✅ Workspace structure is healthy');
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Failed to get workspace status:', error.message);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (require.main === module) {
|
||||||
|
getWorkspaceStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { getWorkspaceStatus };
|
||||||
|
`;
|
||||||
|
|
||||||
|
await fs.writeFile(path.join(utilsPath, 'status.js'), statusScript);
|
||||||
|
await fs.chmod(path.join(utilsPath, 'status.js'), 0o755);
|
||||||
|
}
|
||||||
|
|
||||||
|
async createCleanupScript(utilsPath) {
|
||||||
|
const cleanupScript = `#!/usr/bin/env node
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
function ensureDir(dirPath) {
|
||||||
|
if (!fs.existsSync(dirPath)) {
|
||||||
|
fs.mkdirSync(dirPath, { recursive: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeFile(filePath) {
|
||||||
|
try {
|
||||||
|
fs.unlinkSync(filePath);
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function moveFile(sourcePath, targetPath) {
|
||||||
|
try {
|
||||||
|
const data = fs.readFileSync(sourcePath);
|
||||||
|
fs.writeFileSync(targetPath, data);
|
||||||
|
fs.unlinkSync(sourcePath);
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function cleanupWorkspace() {
|
||||||
|
try {
|
||||||
|
const workspacePath = path.join(process.cwd(), '.workspace');
|
||||||
|
|
||||||
|
if (!await fs.pathExists(workspacePath)) {
|
||||||
|
console.error('❌ Workspace directory not found.');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('🧹 Starting workspace cleanup...');
|
||||||
|
|
||||||
|
// Repair directory structure
|
||||||
|
const directories = ['sessions', 'context', 'handoffs', 'decisions', 'progress', 'quality', 'archive'];
|
||||||
|
let repairedDirs = 0;
|
||||||
|
|
||||||
|
for (const dir of directories) {
|
||||||
|
const dirPath = path.join(workspacePath, dir);
|
||||||
|
if (!await fs.pathExists(dirPath)) {
|
||||||
|
await fs.ensureDir(dirPath);
|
||||||
|
repairedDirs++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (repairedDirs > 0) {
|
||||||
|
console.log(\`✅ Repaired \${repairedDirs} missing directories\`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up expired sessions (older than 2 hours)
|
||||||
|
const sessionsPath = path.join(workspacePath, 'sessions');
|
||||||
|
const sessionFiles = await fs.readdir(sessionsPath).catch(() => []);
|
||||||
|
const twoHoursAgo = Date.now() - (2 * 60 * 60 * 1000);
|
||||||
|
|
||||||
|
let cleanedSessions = 0;
|
||||||
|
for (const file of sessionFiles) {
|
||||||
|
if (file.endsWith('.json')) {
|
||||||
|
try {
|
||||||
|
const sessionPath = path.join(sessionsPath, file);
|
||||||
|
const sessionData = await fs.readJSON(sessionPath);
|
||||||
|
const lastHeartbeat = new Date(sessionData.lastHeartbeat).getTime();
|
||||||
|
|
||||||
|
if (lastHeartbeat < twoHoursAgo) {
|
||||||
|
await fs.remove(sessionPath);
|
||||||
|
cleanedSessions++;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// Remove corrupted session files
|
||||||
|
await fs.remove(path.join(sessionsPath, file));
|
||||||
|
cleanedSessions++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cleanedSessions > 0) {
|
||||||
|
console.log(\`✅ Cleaned up \${cleanedSessions} expired sessions\`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Archive old context files (older than 30 days)
|
||||||
|
const contextPath = path.join(workspacePath, 'context');
|
||||||
|
const archivePath = path.join(workspacePath, 'archive');
|
||||||
|
const thirtyDaysAgo = Date.now() - (30 * 24 * 60 * 60 * 1000);
|
||||||
|
|
||||||
|
if (await fs.pathExists(contextPath)) {
|
||||||
|
const contextFiles = await fs.readdir(contextPath).catch(() => []);
|
||||||
|
let archivedFiles = 0;
|
||||||
|
|
||||||
|
for (const file of contextFiles) {
|
||||||
|
const filePath = path.join(contextPath, file);
|
||||||
|
const stats = await fs.stat(filePath).catch(() => null);
|
||||||
|
|
||||||
|
if (stats && stats.mtime.getTime() < thirtyDaysAgo) {
|
||||||
|
const archiveFile = path.join(archivePath, \`archived-\${Date.now()}-\${file}\`);
|
||||||
|
await fs.move(filePath, archiveFile);
|
||||||
|
archivedFiles++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (archivedFiles > 0) {
|
||||||
|
console.log(\`✅ Archived \${archivedFiles} old context files\`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('✅ Workspace cleanup completed successfully');
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Failed to cleanup workspace:', error.message);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (require.main === module) {
|
||||||
|
cleanupWorkspace();
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { cleanupWorkspace };
|
||||||
|
`;
|
||||||
|
|
||||||
|
await fs.writeFile(path.join(utilsPath, 'cleanup.js'), cleanupScript);
|
||||||
|
await fs.chmod(path.join(utilsPath, 'cleanup.js'), 0o755);
|
||||||
|
}
|
||||||
|
|
||||||
|
async createHandoffScript(utilsPath) {
|
||||||
|
const handoffScript = `#!/usr/bin/env node
|
||||||
|
const fs = require('fs-extra');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
async function createHandoff(fromAgent, toAgent, context = '') {
|
||||||
|
try {
|
||||||
|
const workspacePath = path.join(process.cwd(), '.workspace');
|
||||||
|
const handoffsPath = path.join(workspacePath, 'handoffs');
|
||||||
|
|
||||||
|
if (!await fs.pathExists(handoffsPath)) {
|
||||||
|
console.error('❌ Workspace handoffs directory not found.');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
||||||
|
const handoffId = \`\${fromAgent}-to-\${toAgent}-\${timestamp}\`;
|
||||||
|
const handoffFile = path.join(handoffsPath, \`\${handoffId}.md\`);
|
||||||
|
|
||||||
|
const handoffContent = \`# Agent Handoff: \${fromAgent} → \${toAgent}
|
||||||
|
|
||||||
|
**Created:** \${new Date().toISOString()}
|
||||||
|
**Handoff ID:** \${handoffId}
|
||||||
|
**Source Agent:** \${fromAgent}
|
||||||
|
**Target Agent:** \${toAgent}
|
||||||
|
|
||||||
|
## Context Summary
|
||||||
|
\${context || 'No additional context provided.'}
|
||||||
|
|
||||||
|
## Key Decisions Made
|
||||||
|
[To be filled by source agent]
|
||||||
|
|
||||||
|
## Current Progress
|
||||||
|
[To be filled by source agent]
|
||||||
|
|
||||||
|
## Next Actions for \${toAgent}
|
||||||
|
- [ ] [Action item 1]
|
||||||
|
- [ ] [Action item 2]
|
||||||
|
- [ ] [Action item 3]
|
||||||
|
|
||||||
|
## Files and References
|
||||||
|
[List of relevant files and documentation]
|
||||||
|
|
||||||
|
## Blockers and Dependencies
|
||||||
|
[Any blockers or dependencies the target agent should be aware of]
|
||||||
|
|
||||||
|
## Handoff Validation
|
||||||
|
- [ ] Context completeness verified
|
||||||
|
- [ ] Decisions documented
|
||||||
|
- [ ] Next actions clearly defined
|
||||||
|
- [ ] References included
|
||||||
|
\`;
|
||||||
|
|
||||||
|
await fs.writeFile(handoffFile, handoffContent);
|
||||||
|
|
||||||
|
console.log('✅ Handoff package created successfully');
|
||||||
|
console.log(\`📦 Handoff ID: \${handoffId}\`);
|
||||||
|
console.log(\`📁 File: \${handoffFile}\`);
|
||||||
|
|
||||||
|
return handoffId;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Failed to create handoff:', error.message);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Command line usage
|
||||||
|
if (require.main === module) {
|
||||||
|
const args = process.argv.slice(2);
|
||||||
|
if (args.length < 2) {
|
||||||
|
console.log('Usage: node handoff.js <from-agent> <to-agent> [context]');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
createHandoff(args[0], args[1], args[2] || '');
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { createHandoff };
|
||||||
|
`;
|
||||||
|
|
||||||
|
await fs.writeFile(path.join(utilsPath, 'handoff.js'), handoffScript);
|
||||||
|
await fs.chmod(path.join(utilsPath, 'handoff.js'), 0o755);
|
||||||
|
}
|
||||||
|
|
||||||
|
async createSyncScript(utilsPath) {
|
||||||
|
const syncScript = `#!/usr/bin/env node
|
||||||
|
const fs = require('fs-extra');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
async function syncWorkspace() {
|
||||||
|
try {
|
||||||
|
const workspacePath = path.join(process.cwd(), '.workspace');
|
||||||
|
|
||||||
|
if (!await fs.pathExists(workspacePath)) {
|
||||||
|
console.error('❌ Workspace directory not found.');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('🔄 Synchronizing workspace context...');
|
||||||
|
|
||||||
|
// Update session heartbeat
|
||||||
|
const sessionsPath = path.join(workspacePath, 'sessions');
|
||||||
|
const sessionFiles = await fs.readdir(sessionsPath).catch(() => []);
|
||||||
|
|
||||||
|
// For simplicity, update the most recent session
|
||||||
|
let latestSession = null;
|
||||||
|
let latestTime = 0;
|
||||||
|
|
||||||
|
for (const file of sessionFiles) {
|
||||||
|
if (file.endsWith('.json')) {
|
||||||
|
try {
|
||||||
|
const sessionPath = path.join(sessionsPath, file);
|
||||||
|
const sessionData = await fs.readJSON(sessionPath);
|
||||||
|
const created = new Date(sessionData.created).getTime();
|
||||||
|
|
||||||
|
if (created > latestTime) {
|
||||||
|
latestTime = created;
|
||||||
|
latestSession = { path: sessionPath, data: sessionData };
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// Skip corrupted files
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (latestSession) {
|
||||||
|
latestSession.data.lastHeartbeat = new Date().toISOString();
|
||||||
|
await fs.writeJSON(latestSession.path, latestSession.data, { spaces: 2 });
|
||||||
|
console.log(\`✅ Updated session heartbeat: \${latestSession.data.id}\`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load and display recent context
|
||||||
|
const contextPath = path.join(workspacePath, 'context');
|
||||||
|
const sharedContext = path.join(contextPath, 'shared-context.md');
|
||||||
|
|
||||||
|
if (await fs.pathExists(sharedContext)) {
|
||||||
|
const content = await fs.readFile(sharedContext, 'utf8');
|
||||||
|
console.log('\\n📄 Current Shared Context:');
|
||||||
|
console.log('=' .repeat(50));
|
||||||
|
console.log(content.substring(0, 500) + (content.length > 500 ? '...' : ''));
|
||||||
|
} else {
|
||||||
|
console.log('\\n📄 No shared context available yet.');
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('\\n✅ Workspace synchronization completed');
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Failed to sync workspace:', error.message);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (require.main === module) {
|
||||||
|
syncWorkspace();
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { syncWorkspace };
|
||||||
|
`;
|
||||||
|
|
||||||
|
await fs.writeFile(path.join(utilsPath, 'sync.js'), syncScript);
|
||||||
|
await fs.chmod(path.join(utilsPath, 'sync.js'), 0o755);
|
||||||
|
}
|
||||||
|
|
||||||
|
async addPackageJsonScripts(installDir) {
|
||||||
|
const packageJsonPath = path.join(installDir, 'package.json');
|
||||||
|
|
||||||
|
if (await fs.pathExists(packageJsonPath)) {
|
||||||
|
const packageJson = await fs.readJSON(packageJsonPath);
|
||||||
|
|
||||||
|
if (!packageJson.scripts) {
|
||||||
|
packageJson.scripts = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add workspace scripts
|
||||||
|
packageJson.scripts['workspace-init'] = 'node workspace-utils/init.js';
|
||||||
|
packageJson.scripts['workspace-status'] = 'node workspace-utils/status.js';
|
||||||
|
packageJson.scripts['workspace-cleanup'] = 'node workspace-utils/cleanup.js';
|
||||||
|
packageJson.scripts['workspace-handoff'] = 'node workspace-utils/handoff.js';
|
||||||
|
packageJson.scripts['workspace-sync'] = 'node workspace-utils/sync.js';
|
||||||
|
|
||||||
|
await fs.writeJSON(packageJsonPath, packageJson, { spaces: 2 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async createIDEDocumentation(utilsPath, selectedIDEs) {
|
||||||
|
const docsPath = path.join(utilsPath, 'docs');
|
||||||
|
await fs.ensureDir(docsPath);
|
||||||
|
|
||||||
|
const ideDocuments = {
|
||||||
|
'cursor': `# Workspace Usage in Cursor
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
1. Open terminal in Cursor
|
||||||
|
2. Run \`npm run workspace-init\` to start collaborative session
|
||||||
|
3. Use \`npm run workspace-status\` to see active sessions
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
- Use @dev, @qa, @architect mentions to invoke BMAD agents
|
||||||
|
- Run \`npm run workspace-sync\` before major context switches
|
||||||
|
- Check \`npm run workspace-status\` to see other team members' progress
|
||||||
|
`,
|
||||||
|
'windsurf': `# Workspace Usage in Windsurf
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
1. Open terminal in Windsurf
|
||||||
|
2. Run \`npm run workspace-init\` to start collaborative session
|
||||||
|
3. Use \`npm run workspace-status\` to see active sessions
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
- Use @agent-name to invoke BMAD agents
|
||||||
|
- Run \`npm run workspace-sync\` to stay synchronized
|
||||||
|
- Check workspace status regularly for team coordination
|
||||||
|
`,
|
||||||
|
'claude-code': `# Workspace Usage in Claude Code CLI
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
Claude Code CLI users get enhanced workspace experience with native commands:
|
||||||
|
|
||||||
|
- \`*workspace-init\` - Initialize collaborative session (automatic)
|
||||||
|
- \`*workspace-status\` - Show active sessions and progress
|
||||||
|
- \`*workspace-cleanup\` - Clean up and optimize workspace
|
||||||
|
- \`*workspace-handoff [agent]\` - Prepare handoff to another agent
|
||||||
|
- \`*workspace-sync\` - Synchronize with latest context
|
||||||
|
|
||||||
|
## Native Integration
|
||||||
|
Workspace features are automatically integrated into your Claude Code CLI session:
|
||||||
|
- Automatic session registration and heartbeat
|
||||||
|
- Context-aware agent handoffs
|
||||||
|
- Intelligent workspace suggestions
|
||||||
|
`,
|
||||||
|
'trae': `# Workspace Usage in Trae
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
1. Open terminal in Trae
|
||||||
|
2. Run \`npm run workspace-init\` to start collaborative session
|
||||||
|
3. Use \`npm run workspace-status\` to see active sessions
|
||||||
|
|
||||||
|
## Integration
|
||||||
|
- Use @agent mentions to work with BMAD agents
|
||||||
|
- Workspace context automatically persists across sessions
|
||||||
|
- Use \`npm run workspace-handoff dev qa\` for explicit handoffs
|
||||||
|
`
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const ide of selectedIDEs) {
|
||||||
|
if (ideDocuments[ide]) {
|
||||||
|
await fs.writeFile(
|
||||||
|
path.join(docsPath, `${ide}.md`),
|
||||||
|
ideDocuments[ide]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async setupClaudeCodeWorkspaceCommands(installDir, spinner) {
|
||||||
|
try {
|
||||||
|
spinner.text = 'Integrating workspace commands with Claude Code CLI agents...';
|
||||||
|
|
||||||
|
const bmadCorePath = path.join(installDir, '.bmad-core');
|
||||||
|
const agentsPath = path.join(bmadCorePath, 'agents');
|
||||||
|
|
||||||
|
if (!await fs.pathExists(agentsPath)) {
|
||||||
|
console.warn('⚠️ .bmad-core/agents directory not found. Skipping Claude Code integration.');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add workspace commands to key agents
|
||||||
|
const agentsToUpdate = ['dev.md', 'qa.md', 'sm.md'];
|
||||||
|
|
||||||
|
for (const agentFile of agentsToUpdate) {
|
||||||
|
const agentPath = path.join(agentsPath, agentFile);
|
||||||
|
|
||||||
|
if (await fs.pathExists(agentPath)) {
|
||||||
|
let content = await fs.readFile(agentPath, 'utf8');
|
||||||
|
|
||||||
|
// Check if workspace commands already exist
|
||||||
|
if (!content.includes('*workspace-init')) {
|
||||||
|
// Add workspace commands section
|
||||||
|
const workspaceCommands = `
|
||||||
|
|
||||||
|
## Workspace Commands
|
||||||
|
|
||||||
|
You have access to collaborative workspace commands for multi-session coordination:
|
||||||
|
|
||||||
|
- \`*workspace-init\` - Initialize collaborative workspace session
|
||||||
|
- \`*workspace-status\` - Show current workspace status and active sessions
|
||||||
|
- \`*workspace-cleanup\` - Clean up workspace files and optimize storage
|
||||||
|
- \`*workspace-handoff [target-agent]\` - Prepare context handoff to specified agent
|
||||||
|
- \`*workspace-sync\` - Synchronize with latest workspace context
|
||||||
|
|
||||||
|
Use these commands to coordinate with other AI agents and maintain context across sessions.
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Insert before the last section (usually before final instructions)
|
||||||
|
const insertPoint = content.lastIndexOf('\n## ');
|
||||||
|
if (insertPoint > -1) {
|
||||||
|
content = content.slice(0, insertPoint) + workspaceCommands + '\n' + content.slice(insertPoint);
|
||||||
|
} else {
|
||||||
|
content += workspaceCommands;
|
||||||
|
}
|
||||||
|
|
||||||
|
await fs.writeFile(agentPath, content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(chalk.red('Failed to integrate Claude Code workspace commands:'), error.message);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = WorkspaceSetup;
|
||||||
|
|
@ -0,0 +1,384 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
const path = require('path');
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
// Import context manager (copy functionality since we can't use external dependencies)
|
||||||
|
class ContextManager {
|
||||||
|
constructor(workspacePath = null) {
|
||||||
|
this.workspacePath = workspacePath || path.join(process.cwd(), '.workspace');
|
||||||
|
this.contextPath = path.join(this.workspacePath, 'context');
|
||||||
|
this.decisionsPath = path.join(this.workspacePath, 'decisions');
|
||||||
|
this.progressPath = path.join(this.workspacePath, 'progress');
|
||||||
|
this.qualityPath = path.join(this.workspacePath, 'quality');
|
||||||
|
this.maxContextSize = 10 * 1024 * 1024;
|
||||||
|
this.initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
initialize() {
|
||||||
|
const dirs = [this.contextPath, this.decisionsPath, this.progressPath, this.qualityPath];
|
||||||
|
for (const dir of dirs) {
|
||||||
|
if (!fs.existsSync(dir)) {
|
||||||
|
fs.mkdirSync(dir, { recursive: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async loadSharedContext() {
|
||||||
|
try {
|
||||||
|
const contextFile = path.join(this.contextPath, 'shared-context.md');
|
||||||
|
|
||||||
|
if (!fs.existsSync(contextFile)) {
|
||||||
|
return this.getDefaultSharedContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
const content = fs.readFileSync(contextFile, 'utf8');
|
||||||
|
return this.parseSharedContext(content);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to load shared context:', error.message);
|
||||||
|
return this.getDefaultSharedContext();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getDefaultSharedContext() {
|
||||||
|
return {
|
||||||
|
lastUpdated: new Date().toISOString(),
|
||||||
|
activeSessions: [],
|
||||||
|
primaryAgent: 'unknown',
|
||||||
|
currentFocus: 'No active development focus',
|
||||||
|
keyDecisions: [],
|
||||||
|
nextSteps: [],
|
||||||
|
sessionNotes: ''
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
parseSharedContext(content) {
|
||||||
|
const context = this.getDefaultSharedContext();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const lastUpdatedMatch = content.match(/\*\*Last Updated:\*\* (.+)/);
|
||||||
|
if (lastUpdatedMatch) context.lastUpdated = lastUpdatedMatch[1];
|
||||||
|
|
||||||
|
const activeSessionsMatch = content.match(/\*\*Active Sessions:\*\* (.+)/);
|
||||||
|
if (activeSessionsMatch && activeSessionsMatch[1] !== 'None') {
|
||||||
|
context.activeSessions = activeSessionsMatch[1].split(', ').map(s => s.trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
const primaryAgentMatch = content.match(/\*\*Primary Agent:\*\* (.+)/);
|
||||||
|
if (primaryAgentMatch) context.primaryAgent = primaryAgentMatch[1];
|
||||||
|
|
||||||
|
const currentFocusMatch = content.match(/## Current Focus\n([\s\S]*?)(?=\n## |$)/);
|
||||||
|
if (currentFocusMatch) context.currentFocus = currentFocusMatch[1].trim();
|
||||||
|
|
||||||
|
const keyDecisionsMatch = content.match(/## Key Decisions\n([\s\S]*?)(?=\n## |$)/);
|
||||||
|
if (keyDecisionsMatch) {
|
||||||
|
context.keyDecisions = keyDecisionsMatch[1]
|
||||||
|
.split('\n')
|
||||||
|
.filter(line => line.startsWith('- '))
|
||||||
|
.map(line => line.substring(2).trim())
|
||||||
|
.filter(decision => decision && !decision.includes('No decisions recorded'));
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextStepsMatch = content.match(/## Next Steps\n([\s\S]*?)(?=\n## |$)/);
|
||||||
|
if (nextStepsMatch) {
|
||||||
|
context.nextSteps = nextStepsMatch[1]
|
||||||
|
.split('\n')
|
||||||
|
.filter(line => line.startsWith('- '))
|
||||||
|
.map(line => line.substring(2).trim())
|
||||||
|
.filter(step => step && !step.includes('No next steps defined'));
|
||||||
|
}
|
||||||
|
|
||||||
|
const sessionNotesMatch = content.match(/## Session Notes\n([\s\S]*?)$/);
|
||||||
|
if (sessionNotesMatch) context.sessionNotes = sessionNotesMatch[1].trim();
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('Failed to parse shared context, using defaults:', error.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
async loadProgress() {
|
||||||
|
try {
|
||||||
|
const progressFile = path.join(this.progressPath, 'progress-summary.md');
|
||||||
|
|
||||||
|
if (!fs.existsSync(progressFile)) {
|
||||||
|
return {
|
||||||
|
lastUpdated: new Date().toISOString(),
|
||||||
|
currentStory: 'No active story',
|
||||||
|
completedTasks: [],
|
||||||
|
pendingTasks: [],
|
||||||
|
blockers: [],
|
||||||
|
qualityScore: 'Not assessed'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const content = fs.readFileSync(progressFile, 'utf8');
|
||||||
|
const progress = {
|
||||||
|
lastUpdated: new Date().toISOString(),
|
||||||
|
currentStory: 'No active story',
|
||||||
|
completedTasks: [],
|
||||||
|
pendingTasks: [],
|
||||||
|
blockers: [],
|
||||||
|
qualityScore: 'Not assessed'
|
||||||
|
};
|
||||||
|
|
||||||
|
const currentStoryMatch = content.match(/\*\*Current Story:\*\* (.+)/);
|
||||||
|
if (currentStoryMatch) progress.currentStory = currentStoryMatch[1];
|
||||||
|
|
||||||
|
const qualityScoreMatch = content.match(/\*\*Quality Score:\*\* (.+)/);
|
||||||
|
if (qualityScoreMatch) progress.qualityScore = qualityScoreMatch[1];
|
||||||
|
|
||||||
|
return progress;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to load progress:', error.message);
|
||||||
|
return {
|
||||||
|
lastUpdated: new Date().toISOString(),
|
||||||
|
currentStory: 'No active story',
|
||||||
|
completedTasks: [],
|
||||||
|
pendingTasks: [],
|
||||||
|
blockers: [],
|
||||||
|
qualityScore: 'Not assessed'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getLatestQualityMetrics() {
|
||||||
|
try {
|
||||||
|
const qualityFile = path.join(this.qualityPath, 'quality-metrics.md');
|
||||||
|
|
||||||
|
if (!fs.existsSync(qualityFile)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const content = fs.readFileSync(qualityFile, 'utf8');
|
||||||
|
const assessments = content.split('## Quality Assessment -');
|
||||||
|
|
||||||
|
if (assessments.length < 2) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
timestamp: assessments[1].split('\n')[0].trim(),
|
||||||
|
available: true
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to get latest quality metrics:', error.message);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Context Management CLI
|
||||||
|
async function handleContextCommand() {
|
||||||
|
try {
|
||||||
|
const workspacePath = path.join(process.cwd(), '.workspace');
|
||||||
|
|
||||||
|
if (!fs.existsSync(workspacePath)) {
|
||||||
|
console.error('❌ Workspace directory not found. Run `npx bmad-method install` first.');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const contextManager = new ContextManager();
|
||||||
|
const args = process.argv.slice(2);
|
||||||
|
const command = args[0];
|
||||||
|
|
||||||
|
switch (command) {
|
||||||
|
case 'status':
|
||||||
|
await showContextStatus(contextManager);
|
||||||
|
break;
|
||||||
|
case 'load':
|
||||||
|
await loadContext(contextManager);
|
||||||
|
break;
|
||||||
|
case 'update':
|
||||||
|
await updateContext(contextManager, args.slice(1));
|
||||||
|
break;
|
||||||
|
case 'decisions':
|
||||||
|
await showDecisions(contextManager);
|
||||||
|
break;
|
||||||
|
case 'progress':
|
||||||
|
await showProgress(contextManager);
|
||||||
|
break;
|
||||||
|
case 'export':
|
||||||
|
await exportContext(contextManager);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
showUsage();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Context command failed:', error.message);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function showContextStatus(contextManager) {
|
||||||
|
console.log('📄 BMAD Context Status');
|
||||||
|
console.log('======================');
|
||||||
|
|
||||||
|
const context = await contextManager.loadSharedContext();
|
||||||
|
const progress = await contextManager.loadProgress();
|
||||||
|
const quality = await contextManager.getLatestQualityMetrics();
|
||||||
|
|
||||||
|
console.log(`📁 Context: ${contextManager.contextPath}`);
|
||||||
|
console.log(`🕐 Last Updated: ${context.lastUpdated}`);
|
||||||
|
console.log(`👤 Primary Agent: ${context.primaryAgent}`);
|
||||||
|
console.log(`🎯 Current Focus: ${context.currentFocus}`);
|
||||||
|
console.log(`📊 Quality Score: ${progress.qualityScore}`);
|
||||||
|
|
||||||
|
if (context.activeSessions.length > 0) {
|
||||||
|
console.log(`\n👥 Active Sessions: ${context.activeSessions.length}`);
|
||||||
|
context.activeSessions.forEach((session, index) => {
|
||||||
|
console.log(` ${index + 1}. ${session}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context.keyDecisions.length > 0) {
|
||||||
|
console.log(`\n🔑 Recent Key Decisions:`);
|
||||||
|
context.keyDecisions.slice(-3).forEach((decision, index) => {
|
||||||
|
console.log(` ${index + 1}. ${decision}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context.nextSteps.length > 0) {
|
||||||
|
console.log(`\n⏭️ Next Steps:`);
|
||||||
|
context.nextSteps.forEach((step, index) => {
|
||||||
|
console.log(` ${index + 1}. ${step}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`\n📈 Quality Metrics: ${quality ? 'Available' : 'Not available'}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadContext(contextManager) {
|
||||||
|
console.log('📄 Loading workspace context...\n');
|
||||||
|
|
||||||
|
const context = await contextManager.loadSharedContext();
|
||||||
|
|
||||||
|
console.log('🎯 Current Focus:');
|
||||||
|
console.log(context.currentFocus);
|
||||||
|
|
||||||
|
if (context.keyDecisions.length > 0) {
|
||||||
|
console.log('\n🔑 Key Decisions:');
|
||||||
|
context.keyDecisions.forEach((decision, index) => {
|
||||||
|
console.log(` ${index + 1}. ${decision}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context.nextSteps.length > 0) {
|
||||||
|
console.log('\n⏭️ Next Steps:');
|
||||||
|
context.nextSteps.forEach((step, index) => {
|
||||||
|
console.log(` ${index + 1}. ${step}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context.sessionNotes) {
|
||||||
|
console.log('\n📝 Session Notes:');
|
||||||
|
console.log(context.sessionNotes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function showDecisions(contextManager) {
|
||||||
|
const decisionsFile = path.join(contextManager.decisionsPath, 'decisions-log.md');
|
||||||
|
|
||||||
|
if (!fs.existsSync(decisionsFile)) {
|
||||||
|
console.log('📋 No decisions recorded yet.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const content = fs.readFileSync(decisionsFile, 'utf8');
|
||||||
|
const decisions = content.split('## Decision ').slice(1);
|
||||||
|
|
||||||
|
console.log('📋 Architectural & Design Decisions');
|
||||||
|
console.log('===================================');
|
||||||
|
|
||||||
|
decisions.slice(-5).forEach((decision, index) => {
|
||||||
|
const lines = decision.split('\n');
|
||||||
|
const title = lines[0].replace(/^\d+:\s*/, '');
|
||||||
|
const dateMatch = decision.match(/\*\*Date:\*\* (.+)/);
|
||||||
|
const agentMatch = decision.match(/\*\*Agent:\*\* (.+)/);
|
||||||
|
|
||||||
|
console.log(`\n${decisions.length - 4 + index}. ${title}`);
|
||||||
|
if (dateMatch) console.log(` 📅 ${dateMatch[1]}`);
|
||||||
|
if (agentMatch) console.log(` 👤 ${agentMatch[1]}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function showProgress(contextManager) {
|
||||||
|
const progress = await contextManager.loadProgress();
|
||||||
|
|
||||||
|
console.log('📈 Development Progress');
|
||||||
|
console.log('======================');
|
||||||
|
console.log(`🎯 Current Story: ${progress.currentStory}`);
|
||||||
|
console.log(`📊 Quality Score: ${progress.qualityScore}`);
|
||||||
|
console.log(`🕐 Last Updated: ${progress.lastUpdated}`);
|
||||||
|
|
||||||
|
if (progress.completedTasks && progress.completedTasks.length > 0) {
|
||||||
|
console.log(`\n✅ Completed Tasks: ${progress.completedTasks.length}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (progress.pendingTasks && progress.pendingTasks.length > 0) {
|
||||||
|
console.log(`⏳ Pending Tasks: ${progress.pendingTasks.length}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (progress.blockers && progress.blockers.length > 0) {
|
||||||
|
console.log(`🚫 Blockers: ${progress.blockers.length}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function exportContext(contextManager) {
|
||||||
|
try {
|
||||||
|
const context = await contextManager.loadSharedContext();
|
||||||
|
const progress = await contextManager.loadProgress();
|
||||||
|
|
||||||
|
const exportContent = `# Workspace Context Export
|
||||||
|
**Generated:** ${new Date().toISOString()}
|
||||||
|
|
||||||
|
## Current Status
|
||||||
|
- **Primary Agent:** ${context.primaryAgent}
|
||||||
|
- **Active Sessions:** ${context.activeSessions.join(', ') || 'None'}
|
||||||
|
- **Current Focus:** ${context.currentFocus}
|
||||||
|
- **Quality Score:** ${progress.qualityScore}
|
||||||
|
|
||||||
|
## Key Decisions
|
||||||
|
${context.keyDecisions.map(d => `- ${d}`).join('\n') || '- No decisions recorded'}
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
${context.nextSteps.map(step => `- ${step}`).join('\n') || '- No next steps defined'}
|
||||||
|
|
||||||
|
## Session Notes
|
||||||
|
${context.sessionNotes || 'No session notes available'}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const exportFile = path.join(process.cwd(), `context-export-${Date.now()}.md`);
|
||||||
|
fs.writeFileSync(exportFile, exportContent);
|
||||||
|
|
||||||
|
console.log('✅ Context exported successfully');
|
||||||
|
console.log(`📁 Export file: ${exportFile}`);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Failed to export context:', error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showUsage() {
|
||||||
|
console.log('📄 BMAD Context Management');
|
||||||
|
console.log('==========================');
|
||||||
|
console.log('');
|
||||||
|
console.log('Usage: node context.js <command>');
|
||||||
|
console.log('');
|
||||||
|
console.log('Commands:');
|
||||||
|
console.log(' status - Show current workspace context status');
|
||||||
|
console.log(' load - Load and display shared context');
|
||||||
|
console.log(' decisions - Show recent architectural decisions');
|
||||||
|
console.log(' progress - Show development progress summary');
|
||||||
|
console.log(' export - Export context to markdown file');
|
||||||
|
console.log('');
|
||||||
|
console.log('Examples:');
|
||||||
|
console.log(' node context.js status');
|
||||||
|
console.log(' node context.js load');
|
||||||
|
console.log(' node context.js export');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (require.main === module) {
|
||||||
|
handleContextCommand();
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { ContextManager };
|
||||||
|
|
@ -0,0 +1,141 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
function ensureDir(dirPath) {
|
||||||
|
if (!fs.existsSync(dirPath)) {
|
||||||
|
fs.mkdirSync(dirPath, { recursive: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeFile(filePath) {
|
||||||
|
try {
|
||||||
|
fs.unlinkSync(filePath);
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function moveFile(sourcePath, targetPath) {
|
||||||
|
try {
|
||||||
|
const data = fs.readFileSync(sourcePath);
|
||||||
|
fs.writeFileSync(targetPath, data);
|
||||||
|
fs.unlinkSync(sourcePath);
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function cleanupWorkspace() {
|
||||||
|
try {
|
||||||
|
const workspacePath = path.join(process.cwd(), '.workspace');
|
||||||
|
|
||||||
|
if (!fs.existsSync(workspacePath)) {
|
||||||
|
console.error('❌ Workspace directory not found.');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('🧹 Starting workspace cleanup...');
|
||||||
|
|
||||||
|
// Repair directory structure
|
||||||
|
const directories = ['sessions', 'context', 'handoffs', 'decisions', 'progress', 'quality', 'archive'];
|
||||||
|
let repairedDirs = 0;
|
||||||
|
|
||||||
|
for (const dir of directories) {
|
||||||
|
const dirPath = path.join(workspacePath, dir);
|
||||||
|
if (!fs.existsSync(dirPath)) {
|
||||||
|
ensureDir(dirPath);
|
||||||
|
repairedDirs++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (repairedDirs > 0) {
|
||||||
|
console.log(`✅ Repaired ${repairedDirs} missing directories`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up expired sessions (older than 2 hours)
|
||||||
|
const sessionsPath = path.join(workspacePath, 'sessions');
|
||||||
|
let sessionFiles = [];
|
||||||
|
if (fs.existsSync(sessionsPath)) {
|
||||||
|
sessionFiles = fs.readdirSync(sessionsPath);
|
||||||
|
}
|
||||||
|
const twoHoursAgo = Date.now() - (2 * 60 * 60 * 1000);
|
||||||
|
|
||||||
|
let cleanedSessions = 0;
|
||||||
|
for (const file of sessionFiles) {
|
||||||
|
if (file.endsWith('.json')) {
|
||||||
|
try {
|
||||||
|
const sessionPath = path.join(sessionsPath, file);
|
||||||
|
const sessionContent = fs.readFileSync(sessionPath, 'utf8');
|
||||||
|
const sessionData = JSON.parse(sessionContent);
|
||||||
|
const lastHeartbeat = new Date(sessionData.lastHeartbeat).getTime();
|
||||||
|
|
||||||
|
if (lastHeartbeat < twoHoursAgo) {
|
||||||
|
if (removeFile(sessionPath)) {
|
||||||
|
cleanedSessions++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// Remove corrupted session files
|
||||||
|
if (removeFile(path.join(sessionsPath, file))) {
|
||||||
|
cleanedSessions++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cleanedSessions > 0) {
|
||||||
|
console.log(`✅ Cleaned up ${cleanedSessions} expired sessions`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Archive old context files (older than 30 days)
|
||||||
|
const contextPath = path.join(workspacePath, 'context');
|
||||||
|
const archivePath = path.join(workspacePath, 'archive');
|
||||||
|
const thirtyDaysAgo = Date.now() - (30 * 24 * 60 * 60 * 1000);
|
||||||
|
|
||||||
|
if (fs.existsSync(contextPath)) {
|
||||||
|
let contextFiles = [];
|
||||||
|
try {
|
||||||
|
contextFiles = fs.readdirSync(contextPath);
|
||||||
|
} catch (e) {
|
||||||
|
contextFiles = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
let archivedFiles = 0;
|
||||||
|
|
||||||
|
for (const file of contextFiles) {
|
||||||
|
const filePath = path.join(contextPath, file);
|
||||||
|
try {
|
||||||
|
const stats = fs.statSync(filePath);
|
||||||
|
|
||||||
|
if (stats.mtime.getTime() < thirtyDaysAgo) {
|
||||||
|
const archiveFile = path.join(archivePath, `archived-${Date.now()}-${file}`);
|
||||||
|
if (moveFile(filePath, archiveFile)) {
|
||||||
|
archivedFiles++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// Skip files that can't be processed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (archivedFiles > 0) {
|
||||||
|
console.log(`✅ Archived ${archivedFiles} old context files`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('✅ Workspace cleanup completed successfully');
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Failed to cleanup workspace:', error.message);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (require.main === module) {
|
||||||
|
cleanupWorkspace();
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { cleanupWorkspace };
|
||||||
|
|
@ -0,0 +1,77 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
async function createHandoff(fromAgent, toAgent, context = '') {
|
||||||
|
try {
|
||||||
|
const workspacePath = path.join(process.cwd(), '.workspace');
|
||||||
|
const handoffsPath = path.join(workspacePath, 'handoffs');
|
||||||
|
|
||||||
|
if (!fs.existsSync(handoffsPath)) {
|
||||||
|
console.error('❌ Workspace handoffs directory not found.');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
||||||
|
const handoffId = `${fromAgent}-to-${toAgent}-${timestamp}`;
|
||||||
|
const handoffFile = path.join(handoffsPath, `${handoffId}.md`);
|
||||||
|
|
||||||
|
const handoffContent = `# Agent Handoff: ${fromAgent} → ${toAgent}
|
||||||
|
|
||||||
|
**Created:** ${new Date().toISOString()}
|
||||||
|
**Handoff ID:** ${handoffId}
|
||||||
|
**Source Agent:** ${fromAgent}
|
||||||
|
**Target Agent:** ${toAgent}
|
||||||
|
|
||||||
|
## Context Summary
|
||||||
|
${context || 'No additional context provided.'}
|
||||||
|
|
||||||
|
## Key Decisions Made
|
||||||
|
[To be filled by source agent]
|
||||||
|
|
||||||
|
## Current Progress
|
||||||
|
[To be filled by source agent]
|
||||||
|
|
||||||
|
## Next Actions for ${toAgent}
|
||||||
|
- [ ] [Action item 1]
|
||||||
|
- [ ] [Action item 2]
|
||||||
|
- [ ] [Action item 3]
|
||||||
|
|
||||||
|
## Files and References
|
||||||
|
[List of relevant files and documentation]
|
||||||
|
|
||||||
|
## Blockers and Dependencies
|
||||||
|
[Any blockers or dependencies the target agent should be aware of]
|
||||||
|
|
||||||
|
## Handoff Validation
|
||||||
|
- [ ] Context completeness verified
|
||||||
|
- [ ] Decisions documented
|
||||||
|
- [ ] Next actions clearly defined
|
||||||
|
- [ ] References included
|
||||||
|
`;
|
||||||
|
|
||||||
|
fs.writeFileSync(handoffFile, handoffContent);
|
||||||
|
|
||||||
|
console.log('✅ Handoff package created successfully');
|
||||||
|
console.log(`📦 Handoff ID: ${handoffId}`);
|
||||||
|
console.log(`📁 File: ${handoffFile}`);
|
||||||
|
|
||||||
|
return handoffId;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Failed to create handoff:', error.message);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Command line usage
|
||||||
|
if (require.main === module) {
|
||||||
|
const args = process.argv.slice(2);
|
||||||
|
if (args.length < 2) {
|
||||||
|
console.log('Usage: node handoff.js <from-agent> <to-agent> [context]');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
createHandoff(args[0], args[1], args[2] || '');
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { createHandoff };
|
||||||
|
|
@ -0,0 +1,52 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const crypto = require('crypto');
|
||||||
|
|
||||||
|
async function initWorkspace() {
|
||||||
|
try {
|
||||||
|
const workspacePath = path.join(process.cwd(), '.workspace');
|
||||||
|
|
||||||
|
if (!fs.existsSync(workspacePath)) {
|
||||||
|
console.error('❌ Workspace directory not found. Run `npx bmad-method install` first.');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate session ID
|
||||||
|
const sessionId = crypto.randomBytes(8).toString('hex');
|
||||||
|
const timestamp = new Date().toISOString();
|
||||||
|
|
||||||
|
// Create session file
|
||||||
|
const sessionData = {
|
||||||
|
id: sessionId,
|
||||||
|
created: timestamp,
|
||||||
|
lastHeartbeat: timestamp,
|
||||||
|
ide: process.env.IDE_TYPE || 'unknown',
|
||||||
|
pid: process.pid,
|
||||||
|
user: process.env.USER || process.env.USERNAME || 'unknown'
|
||||||
|
};
|
||||||
|
|
||||||
|
const sessionsPath = path.join(workspacePath, 'sessions');
|
||||||
|
if (!fs.existsSync(sessionsPath)) {
|
||||||
|
fs.mkdirSync(sessionsPath, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
const sessionFile = path.join(sessionsPath, `${sessionId}.json`);
|
||||||
|
fs.writeFileSync(sessionFile, JSON.stringify(sessionData, null, 2));
|
||||||
|
|
||||||
|
console.log('✅ Workspace initialized successfully');
|
||||||
|
console.log(`📍 Session ID: ${sessionId}`);
|
||||||
|
console.log(`🕐 Created: ${timestamp}`);
|
||||||
|
|
||||||
|
return sessionId;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Failed to initialize workspace:', error.message);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (require.main === module) {
|
||||||
|
initWorkspace();
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { initWorkspace };
|
||||||
|
|
@ -0,0 +1,87 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
async function getWorkspaceStatus() {
|
||||||
|
try {
|
||||||
|
const workspacePath = path.join(process.cwd(), '.workspace');
|
||||||
|
|
||||||
|
if (!fs.existsSync(workspacePath)) {
|
||||||
|
console.error('❌ Workspace directory not found.');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read workspace config
|
||||||
|
const configPath = path.join(workspacePath, 'workspace-config.json');
|
||||||
|
let config = {};
|
||||||
|
if (fs.existsSync(configPath)) {
|
||||||
|
const configContent = fs.readFileSync(configPath, 'utf8');
|
||||||
|
config = JSON.parse(configContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get active sessions
|
||||||
|
const sessionsPath = path.join(workspacePath, 'sessions');
|
||||||
|
let sessionFiles = [];
|
||||||
|
if (fs.existsSync(sessionsPath)) {
|
||||||
|
sessionFiles = fs.readdirSync(sessionsPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
const activeSessions = [];
|
||||||
|
for (const file of sessionFiles) {
|
||||||
|
if (file.endsWith('.json')) {
|
||||||
|
try {
|
||||||
|
const sessionPath = path.join(sessionsPath, file);
|
||||||
|
const sessionContent = fs.readFileSync(sessionPath, 'utf8');
|
||||||
|
const sessionData = JSON.parse(sessionContent);
|
||||||
|
activeSessions.push(sessionData);
|
||||||
|
} catch (e) {
|
||||||
|
// Skip corrupted session files
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display status
|
||||||
|
console.log('🤝 BMAD Collaborative Workspace Status');
|
||||||
|
console.log('=====================================');
|
||||||
|
console.log(`📁 Workspace: ${workspacePath}`);
|
||||||
|
console.log(`⚙️ Version: ${config.version || 'Unknown'}`);
|
||||||
|
console.log(`🕐 Created: ${config.created || 'Unknown'}`);
|
||||||
|
console.log(`👥 Active Sessions: ${activeSessions.length}`);
|
||||||
|
|
||||||
|
if (activeSessions.length > 0) {
|
||||||
|
console.log('\n📍 Session Details:');
|
||||||
|
activeSessions.forEach((session, index) => {
|
||||||
|
console.log(` ${index + 1}. ${session.id} (${session.ide}) - ${session.user}`);
|
||||||
|
console.log(` Created: ${new Date(session.created).toLocaleString()}`);
|
||||||
|
console.log(` Last Heartbeat: ${new Date(session.lastHeartbeat).toLocaleString()}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check directory structure
|
||||||
|
const directories = ['context', 'handoffs', 'decisions', 'progress', 'quality', 'archive'];
|
||||||
|
const missingDirs = [];
|
||||||
|
|
||||||
|
for (const dir of directories) {
|
||||||
|
if (!fs.existsSync(path.join(workspacePath, dir))) {
|
||||||
|
missingDirs.push(dir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (missingDirs.length > 0) {
|
||||||
|
console.log(`\n⚠️ Missing directories: ${missingDirs.join(', ')}`);
|
||||||
|
console.log(' Run `node workspace-utils/cleanup.js` to repair.');
|
||||||
|
} else {
|
||||||
|
console.log('\n✅ Workspace structure is healthy');
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Failed to get workspace status:', error.message);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (require.main === module) {
|
||||||
|
getWorkspaceStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { getWorkspaceStatus };
|
||||||
|
|
@ -0,0 +1,84 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
async function syncWorkspace() {
|
||||||
|
try {
|
||||||
|
const workspacePath = path.join(process.cwd(), '.workspace');
|
||||||
|
|
||||||
|
if (!fs.existsSync(workspacePath)) {
|
||||||
|
console.error('❌ Workspace directory not found.');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('🔄 Synchronizing workspace context...');
|
||||||
|
|
||||||
|
// Update session heartbeat
|
||||||
|
const sessionsPath = path.join(workspacePath, 'sessions');
|
||||||
|
let sessionFiles = [];
|
||||||
|
if (fs.existsSync(sessionsPath)) {
|
||||||
|
try {
|
||||||
|
sessionFiles = fs.readdirSync(sessionsPath);
|
||||||
|
} catch (e) {
|
||||||
|
sessionFiles = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// For simplicity, update the most recent session
|
||||||
|
let latestSession = null;
|
||||||
|
let latestTime = 0;
|
||||||
|
|
||||||
|
for (const file of sessionFiles) {
|
||||||
|
if (file.endsWith('.json')) {
|
||||||
|
try {
|
||||||
|
const sessionPath = path.join(sessionsPath, file);
|
||||||
|
const sessionContent = fs.readFileSync(sessionPath, 'utf8');
|
||||||
|
const sessionData = JSON.parse(sessionContent);
|
||||||
|
const created = new Date(sessionData.created).getTime();
|
||||||
|
|
||||||
|
if (created > latestTime) {
|
||||||
|
latestTime = created;
|
||||||
|
latestSession = { path: sessionPath, data: sessionData };
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// Skip corrupted files
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (latestSession) {
|
||||||
|
latestSession.data.lastHeartbeat = new Date().toISOString();
|
||||||
|
fs.writeFileSync(latestSession.path, JSON.stringify(latestSession.data, null, 2));
|
||||||
|
console.log(`✅ Updated session heartbeat: ${latestSession.data.id}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load and display recent context
|
||||||
|
const contextPath = path.join(workspacePath, 'context');
|
||||||
|
const sharedContext = path.join(contextPath, 'shared-context.md');
|
||||||
|
|
||||||
|
if (fs.existsSync(sharedContext)) {
|
||||||
|
try {
|
||||||
|
const content = fs.readFileSync(sharedContext, 'utf8');
|
||||||
|
console.log('\n📄 Current Shared Context:');
|
||||||
|
console.log('='.repeat(50));
|
||||||
|
console.log(content.substring(0, 500) + (content.length > 500 ? '...' : ''));
|
||||||
|
} catch (e) {
|
||||||
|
console.log('\n📄 Shared context file exists but could not be read.');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log('\n📄 No shared context available yet.');
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('\n✅ Workspace synchronization completed');
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Failed to sync workspace:', error.message);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (require.main === module) {
|
||||||
|
syncWorkspace();
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { syncWorkspace };
|
||||||
Loading…
Reference in New Issue