aios-core
Version:
Synkra AIOS: AI-Orchestrated System for Full Stack Development - Core Framework
294 lines (233 loc) • 7.73 kB
JavaScript
/**
* Decision Log Generator for AIOS Framework
*
* Generates decision logs for yolo mode execution to track autonomous
* decisions, files modified, tests run, and rollback information.
*
* @module decision-log-generator
* @version 1.0.0
* @created 2025-01-16 (Story 6.1.2.6)
*/
const fs = require('fs').promises;
const path = require('path');
/**
* Calculates duration between start and end time
*
* @param {Object} context - Execution context
* @returns {string} Formatted duration string
*/
function calculateDuration(context) {
if (!context.endTime) {
const elapsed = Date.now() - context.startTime;
return formatDuration(elapsed) + ' (in progress)';
}
const duration = context.endTime - context.startTime;
return formatDuration(duration);
}
/**
* Formats milliseconds into human-readable duration
*
* @param {number} ms - Duration in milliseconds
* @returns {string} Formatted duration
*/
function formatDuration(ms) {
const seconds = Math.floor(ms / 1000);
const minutes = Math.floor(seconds / 60);
const hours = Math.floor(minutes / 60);
if (hours > 0) {
const remainingMinutes = minutes % 60;
return `${hours}h ${remainingMinutes}m`;
} else if (minutes > 0) {
const remainingSeconds = seconds % 60;
return `${minutes}m ${remainingSeconds}s`;
} else {
return `${seconds}s`;
}
}
/**
* Generates markdown list of key decisions (ADR format - AC3, AC7)
*
* @param {Array} decisions - Array of decision objects
* @returns {string} Formatted markdown list
*/
function generateDecisionsList(decisions) {
if (!decisions || decisions.length === 0) {
return '*No autonomous decisions recorded.*';
}
let markdown = '';
decisions.forEach((decision, index) => {
markdown += `### Decision ${index + 1}: ${decision.description}\n\n`;
markdown += `**Timestamp:** ${new Date(decision.timestamp).toISOString()}\n\n`;
// Decision classification (AC7)
if (decision.type) {
markdown += `**Type:** ${decision.type}\n\n`;
}
if (decision.priority) {
markdown += `**Priority:** ${decision.priority}\n\n`;
}
markdown += `**Reason:** ${decision.reason}\n\n`;
if (decision.alternatives && decision.alternatives.length > 0) {
markdown += '**Alternatives Considered:**\n';
decision.alternatives.forEach(alt => {
markdown += `- ${alt}\n`;
});
markdown += '\n';
}
markdown += '---\n\n';
});
return markdown;
}
/**
* Generates markdown list of files modified
*
* @param {Array} filesModified - Array of file paths
* @returns {string} Formatted markdown list
*/
function generateFilesList(filesModified) {
if (!filesModified || filesModified.length === 0) {
return '*No files modified.*';
}
let markdown = '';
filesModified.forEach(file => {
const fileName = typeof file === 'string' ? file : file.path;
const action = file.action || 'modified';
markdown += `- \`${fileName}\` (${action})\n`;
});
return markdown;
}
/**
* Generates markdown list of tests run
*
* @param {Array} testsRun - Array of test objects
* @returns {string} Formatted markdown list
*/
function generateTestsList(testsRun) {
if (!testsRun || testsRun.length === 0) {
return '*No tests recorded.*';
}
let markdown = '';
testsRun.forEach(test => {
const status = test.passed ? '✅ PASS' : '❌ FAIL';
markdown += `- ${status}: \`${test.name}\``;
if (test.duration) {
markdown += ` (${test.duration}ms)`;
}
if (!test.passed && test.error) {
markdown += `\n - Error: ${test.error}`;
}
markdown += '\n';
});
return markdown;
}
/**
* Generates markdown list of files for rollback
*
* @param {Array} filesModified - Array of file paths
* @returns {string} Formatted markdown list
*/
function generateRollbackFilesList(filesModified) {
if (!filesModified || filesModified.length === 0) {
return '*No files to rollback.*';
}
let markdown = '';
filesModified.forEach(file => {
const fileName = typeof file === 'string' ? file : file.path;
markdown += `- ${fileName}\n`;
});
return markdown;
}
/**
* Generates decision log for yolo mode execution
*
* @param {string} storyId - Story ID (e.g., "6.1.2.6")
* @param {Object} context - Execution context
* @param {string} context.agentId - Agent ID executing the story
* @param {string} context.storyPath - Path to story file
* @param {number} context.startTime - Start timestamp
* @param {number} [context.endTime] - End timestamp
* @param {string} context.status - Execution status
* @param {Array} [context.decisions] - Array of decision objects
* @param {Array} [context.filesModified] - Array of file paths modified
* @param {Array} [context.testsRun] - Array of test results
* @param {Object} [context.metrics] - Performance metrics
* @param {string} [context.commitBefore] - Git commit hash before execution
* @returns {Promise<string>} Path to decision log file
*/
async function generateDecisionLog(storyId, context) {
// Ensure .ai directory exists
const aiDir = '.ai';
try {
await fs.access(aiDir);
} catch (_error) {
await fs.mkdir(aiDir, { recursive: true });
}
const logPath = path.join(aiDir, `decision-log-${storyId}.md`);
// Format timestamps
const startTime = new Date(context.startTime).toISOString();
const endTime = context.endTime ? new Date(context.endTime).toISOString() : 'In Progress';
// ADR-compliant template structure (AC3)
const template = `# Decision Log: Story ${storyId}
**Generated:** ${new Date().toISOString()}
**Agent:** ${context.agentId}
**Mode:** Yolo (Autonomous Development)
**Story:** ${context.storyPath}
**Rollback:** \`git reset --hard ${context.commitBefore || 'HEAD'}\`
---
## Context
**Story Implementation:** ${storyId}
**Execution Time:** ${calculateDuration(context)}
**Status:** ${context.status}
**Started:** ${startTime}
**Completed:** ${endTime}
**Files Modified:** ${context.filesModified?.length || 0} files
**Tests Run:** ${context.testsRun?.length || 0} tests
**Decisions Made:** ${context.decisions?.length || 0} autonomous decisions
---
## Decisions Made
${generateDecisionsList(context.decisions)}
---
## Rationale & Alternatives
The decisions above were made autonomously during yolo mode development. Each decision includes:
- The specific choice made (Decision)
- Why that choice was optimal (Reason)
- What other options were considered (Alternatives)
- Classification by type and priority (AC7)
---
## Implementation Changes
### Files Modified
${generateFilesList(context.filesModified)}
### Test Results
${generateTestsList(context.testsRun)}
---
## Consequences & Rollback
### Rollback Instructions
If you need to undo these changes:
\`\`\`bash
# Full rollback to state before execution
git reset --hard ${context.commitBefore || 'HEAD'}
# Selective file rollback
git checkout ${context.commitBefore || 'HEAD'} -- <file-path>
\`\`\`
### Affected Files
${generateRollbackFilesList(context.filesModified)}
### Performance Impact
${context.metrics ? `
- Agent Load Time: ${context.metrics.agentLoadTime || 'N/A'}ms
- Task Execution Time: ${context.metrics.taskExecutionTime || 'N/A'}ms
- Logging Overhead: Minimal (async, non-blocking)
` : '*No performance metrics recorded.*'}
---
*This is an Architecture Decision Record (ADR) auto-generated by AIOS Decision Logging System*
*Story 6.1.2.6.2 - Decision Log Automation Infrastructure*
`;
await fs.writeFile(logPath, template, 'utf8');
return logPath;
}
module.exports = {
generateDecisionLog,
calculateDuration,
generateDecisionsList,
generateFilesList,
generateTestsList,
generateRollbackFilesList,
};