diff --git a/.aiox-core/core/config/config-loader.js b/.aiox-core/core/config/config-loader.js index 0f454520c..e0a40e31f 100644 --- a/.aiox-core/core/config/config-loader.js +++ b/.aiox-core/core/config/config-loader.js @@ -77,7 +77,9 @@ const performanceMetrics = { }; /** - * Checks if cache is still valid + * Checks if the configuration cache is still valid based on TTL + * + * @returns {boolean} True if cache exists and has not expired */ function isCacheValid() { if (!configCache.lastLoad) return false; @@ -89,7 +91,13 @@ function isCacheValid() { } /** - * Loads full config file (used for initial load or cache refresh) + * Loads the full core-config.yaml file from disk + * + * Used for initial load or cache refresh. The result is cached internally + * and subsequent calls within the TTL window return the cached version. + * + * @returns {Promise} Parsed configuration object + * @throws {Error} If the config file cannot be read or parsed */ async function loadFullConfig() { const configPath = path.join('.aiox-core', 'core-config.yaml'); @@ -121,8 +129,13 @@ async function loadFullConfig() { /** * Loads specific config sections on demand * + * If the cache is valid, returns sections from cache. Otherwise performs + * a full config reload and extracts the requested sections. + * Sections that do not exist in the config are silently omitted. + * * @param {string[]} sections - Array of section names to load - * @returns {Promise} Config object with requested sections + * @returns {Promise} Config object containing only the requested sections + * @throws {Error} If config file cannot be read or parsed (on cache miss) */ async function loadConfigSections(sections) { const startTime = Date.now(); @@ -160,10 +173,18 @@ async function loadConfigSections(sections) { } /** - * Loads config for specific agent with lazy loading + * Loads config for a specific agent with lazy loading + * + * Only loads the configuration sections required by the given agent, + * as defined in the agentRequirements mapping. Falls back to + * ALWAYS_LOADED sections for unknown agent IDs. * - * @param {string} agentId - Agent ID (e.g., 'dev', 'qa', 'po') - * @returns {Promise} Config object with sections needed by agent + * @param {string} agentId - Agent ID (e.g., 'dev', 'qa', 'po', 'architect') + * @returns {Promise} Config object with only the sections needed by the agent + * @throws {Error} If config file cannot be read or parsed + * @example + * const config = await loadAgentConfig('dev'); + * // Returns { frameworkDocsLocation, projectDocsLocation, toolConfigurations, ... } */ async function loadAgentConfig(agentId) { const startTime = Date.now(); @@ -186,16 +207,25 @@ async function loadAgentConfig(agentId) { } /** - * Loads always-loaded sections (minimal config) + * Loads only the always-loaded (minimal) configuration sections + * + * Returns the lightweight subset of config needed by all agents: + * frameworkDocsLocation, projectDocsLocation, devLoadAlwaysFiles, and lazyLoading. * * @returns {Promise} Minimal config with always-loaded sections + * @throws {Error} If config file cannot be read or parsed */ async function loadMinimalConfig() { return await loadConfigSections(ALWAYS_LOADED); } /** - * Preloads config into cache (useful for startup optimization) + * Preloads the full configuration into cache for startup optimization + * + * Call this during application initialization to avoid cold cache + * misses on the first config access. + * + * @returns {Promise} */ async function preloadConfig() { console.log('🔄 Preloading config into cache...'); @@ -204,7 +234,12 @@ async function preloadConfig() { } /** - * Clears config cache (useful for testing or forcing reload) + * Clears the entire config cache, forcing a full reload on next access + * + * Useful for testing scenarios or when the config file has been + * modified externally and a fresh load is required. + * + * @returns {void} */ function clearCache() { configCache.full = null; @@ -229,10 +264,15 @@ function getPerformanceMetrics() { } /** - * Validates that required sections exist in config + * Validates that all required config sections exist for a given agent + * + * Loads the full config and checks that every section listed in the + * agent's requirements is present. Returns a detailed validation result + * including any missing sections. * - * @param {string} agentId - Agent ID to validate - * @returns {Promise} Validation result + * @param {string} agentId - Agent ID to validate (e.g., 'dev', 'qa') + * @returns {Promise<{valid: boolean, agentId: string, requiredSections: string[], missingSections: string[], availableSections: string[]}>} Validation result + * @throws {Error} If config file cannot be read or parsed */ async function validateAgentConfig(agentId) { const requiredSections = agentRequirements[agentId] || ALWAYS_LOADED; @@ -253,10 +293,15 @@ async function validateAgentConfig(agentId) { } /** - * Gets config section on demand (async lazy load) + * Gets a single config section on demand via async lazy load + * + * Leverages the caching layer so repeated calls for the same section + * within the TTL window are served from cache. * - * @param {string} sectionName - Section to load - * @returns {Promise} Section content + * @param {string} sectionName - Section name to load (e.g., 'toolConfigurations') + * @returns {Promise<*>} Section content, or undefined if the section does not exist + * @example + * const tools = await getConfigSection('toolConfigurations'); */ async function getConfigSection(sectionName) { const config = await loadConfigSections([sectionName]); diff --git a/.aiox-core/core/events/dashboard-emitter.js b/.aiox-core/core/events/dashboard-emitter.js index c752a0cae..f557125f0 100644 --- a/.aiox-core/core/events/dashboard-emitter.js +++ b/.aiox-core/core/events/dashboard-emitter.js @@ -23,10 +23,29 @@ const EMIT_TIMEOUT_MS = 500; /** * DashboardEmitter - Singleton for emitting events to monitor-server + * + * Sends high-level CLI events (agent activation, command execution, + * story status changes, session lifecycle) to the monitor-server via + * non-blocking HTTP POST requests with 500ms timeout. + * + * Falls back to writing events to a JSONL file when the monitor-server + * is unreachable. Automatically disabled in test environments. + * + * @example + * const emitter = DashboardEmitter.getInstance(); + * emitter.setAgent('dev'); + * await emitter.emitCommandStart('*develop', ['--story', '3.1']); */ class DashboardEmitter { static instance = null; + /** + * Create a new DashboardEmitter instance + * + * Uses the CLAUDE_CODE_SESSION_ID environment variable if available, + * otherwise generates a random UUID for session tracking. + * Automatically disabled when NODE_ENV is 'test'. + */ constructor() { this.sessionId = process.env.CLAUDE_CODE_SESSION_ID || randomUUID(); this.projectRoot = process.cwd(); diff --git a/.aiox-core/core/orchestration/gate-evaluator.js b/.aiox-core/core/orchestration/gate-evaluator.js index 12f28a021..774054bac 100644 --- a/.aiox-core/core/orchestration/gate-evaluator.js +++ b/.aiox-core/core/orchestration/gate-evaluator.js @@ -459,7 +459,12 @@ class GateEvaluator { } /** - * Clear all results + * Clear all stored gate results and logs + * + * Resets the evaluator to its initial state, removing all + * previously recorded gate evaluations and log entries. + * + * @returns {void} */ clear() { this.results = []; @@ -469,6 +474,8 @@ class GateEvaluator { /** * Log message * @private + * @param {string} message - Log message + * @param {string} [level='info'] - Log level (info, warn, error) */ _log(message, level = 'info') { const timestamp = new Date().toISOString(); @@ -476,7 +483,9 @@ class GateEvaluator { } /** - * Get logs + * Get all evaluation logs + * + * @returns {Array<{timestamp: string, level: string, message: string}>} Copy of all log entries */ getLogs() { return [...this.logs]; diff --git a/.aiox-core/core/orchestration/master-orchestrator.js b/.aiox-core/core/orchestration/master-orchestrator.js index c64489a22..43da4d03b 100644 --- a/.aiox-core/core/orchestration/master-orchestrator.js +++ b/.aiox-core/core/orchestration/master-orchestrator.js @@ -934,6 +934,11 @@ class MasterOrchestrator extends EventEmitter { /** * Start dashboard monitoring (Story 0.8 - AC1) + * + * Begins periodic state updates to the dashboard integration layer. + * Must be called after initialization to enable real-time observability. + * + * @returns {Promise} */ async startDashboard() { await this.dashboardIntegration.start(); @@ -941,6 +946,11 @@ class MasterOrchestrator extends EventEmitter { /** * Stop dashboard monitoring (Story 0.8) + * + * Stops periodic dashboard updates and cleans up any pending timers. + * Safe to call multiple times. + * + * @returns {void} */ stopDashboard() { this.dashboardIntegration.stop(); @@ -1060,8 +1070,12 @@ class MasterOrchestrator extends EventEmitter { /** * Save current state to disk (AC1, AC3) - * Called after each epic completion and state transition - * @returns {Promise} Success status + * + * Persists the full orchestration state including epic progress, + * timestamps, tech stack profile, recovery tracking, errors, and insights. + * Called automatically after each epic completion and state transition. + * + * @returns {Promise} True if state was saved successfully, false on failure */ async saveState() { try { @@ -1287,8 +1301,12 @@ class MasterOrchestrator extends EventEmitter { } /** - * Clear saved state for current story - * @returns {Promise} Success status + * Clear saved state for the current story + * + * Removes the persisted state file from disk, allowing a fresh + * orchestration run without resuming from previous progress. + * + * @returns {Promise} True if state file was deleted, false if not found or on error */ async clearState() { try { @@ -1304,8 +1322,12 @@ class MasterOrchestrator extends EventEmitter { } /** - * List all saved states - * @returns {Promise} List of state summaries + * List all saved orchestration states + * + * Scans the master-orchestrator state directory and returns a summary + * for each state file, sorted by most recently updated first. + * + * @returns {Promise>} List of state summaries */ async listSavedStates() { const stateDir = path.join(this.projectRoot, '.aiox', 'master-orchestrator'); @@ -1369,8 +1391,17 @@ class MasterOrchestrator extends EventEmitter { /** * Finalize pipeline execution and generate summary - * @param {Object} pipelineResult - Raw pipeline result - * @returns {Object} Finalized result + * + * Produces a comprehensive result object with human-readable duration, + * tech stack summary, epic execution details, and accumulated insights. + * + * @param {Object} [pipelineResult={}] - Raw pipeline result from executeFullPipeline + * @param {boolean} [pipelineResult.success] - Whether the pipeline succeeded + * @param {number[]} [pipelineResult.epicsExecuted] - Epic numbers that executed + * @param {number[]} [pipelineResult.epicsFailed] - Epic numbers that failed + * @param {number[]} [pipelineResult.epicsSkipped] - Epic numbers that were skipped + * @param {number} [pipelineResult.duration] - Duration in milliseconds + * @returns {Object} Finalized result with workflowId, status, duration, and epic breakdown */ finalize(pipelineResult = {}) { const duration = @@ -1508,15 +1539,30 @@ class MasterOrchestrator extends EventEmitter { /** * Stub Epic Executor - placeholder for Story 0.3 - * Real executors will be implemented in Story 0.3 + * + * Provides a no-op executor for epics that do not yet have a real + * implementation. Returns a minimal success result so the pipeline + * can continue execution during development. */ class StubEpicExecutor { + /** + * Create a new StubEpicExecutor + * + * @param {MasterOrchestrator} orchestrator - Parent orchestrator instance + * @param {number} epicNum - Epic number this executor handles + */ constructor(orchestrator, epicNum) { this.orchestrator = orchestrator; this.epicNum = epicNum; this.config = EPIC_CONFIG[epicNum]; } + /** + * Execute the stub epic (no-op placeholder) + * + * @param {Object} _context - Execution context (ignored by stub) + * @returns {Promise} Minimal result with status 'stub' + */ async execute(_context) { console.log(chalk.yellow(` ⚠️ Using stub executor for Epic ${this.epicNum}`)); console.log(chalk.gray(` Real executor (${this.config.executor}) not yet implemented`)); diff --git a/.aiox-core/core/orchestration/recovery-handler.js b/.aiox-core/core/orchestration/recovery-handler.js index a159541cd..5e30ef821 100644 --- a/.aiox-core/core/orchestration/recovery-handler.js +++ b/.aiox-core/core/orchestration/recovery-handler.js @@ -656,14 +656,24 @@ class RecoveryHandler extends EventEmitter { } /** - * Get all logs (AC7) + * Get all recovery logs (AC7) + * + * @returns {Array<{timestamp: string, level: string, message: string}>} Array of log entries + * @example + * const logs = recoveryHandler.getLogs(); + * logs.forEach(log => console.log(`[${log.level}] ${log.message}`)); */ getLogs() { return [...this.logs]; } /** - * Get logs for specific epic + * Get logs filtered by a specific epic number + * + * @param {number} epicNum - Epic number to filter logs for + * @returns {Array<{timestamp: string, level: string, message: string}>} Filtered log entries + * @example + * const epic4Logs = recoveryHandler.getEpicLogs(4); */ getEpicLogs(epicNum) { return this.logs.filter( @@ -673,27 +683,50 @@ class RecoveryHandler extends EventEmitter { /** * Get attempt history for all epics + * + * Returns a shallow copy of the internal attempts map, keyed by epic number, + * with each value being an array of attempt records. + * + * @returns {Object} Map of epic numbers to their attempt records + * @example + * const history = recoveryHandler.getAttemptHistory(); + * // { 3: [{ number: 1, error: '...', ... }], 4: [...] } */ getAttemptHistory() { return { ...this.attempts }; } /** - * Get attempt count for specific epic (AC5) + * Get the number of recovery attempts for a specific epic (AC5) + * + * @param {number} epicNum - Epic number to check + * @returns {number} Number of attempts made for this epic */ getAttemptCount(epicNum) { return (this.attempts[epicNum] || []).length; } /** - * Check if can retry (under max retries) (AC5) + * Check if an epic can still be retried (under max retries limit) (AC5) + * + * @param {number} epicNum - Epic number to check + * @returns {boolean} True if the attempt count is below maxRetries + * @example + * if (recoveryHandler.canRetry(4)) { + * await orchestrator.executeEpic(4); + * } */ canRetry(epicNum) { return this.getAttemptCount(epicNum) < this.maxRetries; } /** - * Reset attempts for an epic + * Reset all recovery attempts for a specific epic + * + * Clears the attempt history, allowing the epic to be retried + * from scratch up to maxRetries times. + * + * @param {number} epicNum - Epic number to reset */ resetAttempts(epicNum) { this.attempts[epicNum] = []; @@ -701,7 +734,10 @@ class RecoveryHandler extends EventEmitter { } /** - * Clear all state + * Clear all internal state (attempts and logs) + * + * Resets the recovery handler to its initial state. + * Useful for starting a fresh orchestration run. */ clear() { this.attempts = {}; diff --git a/.aiox-core/install-manifest.yaml b/.aiox-core/install-manifest.yaml index 05816fa4f..3c53c8fa1 100644 --- a/.aiox-core/install-manifest.yaml +++ b/.aiox-core/install-manifest.yaml @@ -8,7 +8,7 @@ # - File types for categorization # version: 5.0.3 -generated_at: "2026-03-11T15:04:09.395Z" +generated_at: "2026-03-12T18:55:53.522Z" generator: scripts/generate-install-manifest.js file_count: 1090 files: @@ -241,9 +241,9 @@ files: type: core size: 4928 - path: core/config/config-loader.js - hash: sha256:bbc6a9e57e9db7a74ae63c901b199f8b8a79eb093f23a280b6420d1aa5f7f813 + hash: sha256:8e52cf14477564a23ebaa76e78e41f709b2b6163282e691e87ac739a550bd24c type: core - size: 8534 + size: 10799 - path: core/config/config-resolver.js hash: sha256:3b29df6954cec440debef87cb4e4e7610c94dc74e562d32c74b8ec57d893213b type: core @@ -385,9 +385,9 @@ files: type: elicitation size: 10935 - path: core/events/dashboard-emitter.js - hash: sha256:20f3d2297c29a64bae34ac8097cc0b54f4f706b19f0dbd6c752a20d7c4ad9eb1 + hash: sha256:a1eb251317184d342027caa60401378a5a5c82964a14890ec30d26efb8d085c1 type: core - size: 9165 + size: 9909 - path: core/events/index.js hash: sha256:c5a3a1ba660f1a0d7963e4e8a29ee536a301621c941d962c00f67ade17ba7db3 type: core @@ -881,9 +881,9 @@ files: type: core size: 1945 - path: core/orchestration/gate-evaluator.js - hash: sha256:66a6ff6afefcdbf3e5149100b8e10aad95feaa5a84a0a993255ddb939b1c0eb4 + hash: sha256:c9db73ce4a8b762ee00857f482afe317fbe8d750935fe39617a7908e8939bbeb type: core - size: 15814 + size: 16216 - path: core/orchestration/gemini-model-selector.js hash: sha256:2fe54c401ae60c0b5dc07f74c7a464992a0558b10c0b61f183c06943441d0d57 type: core @@ -901,9 +901,9 @@ files: type: core size: 8663 - path: core/orchestration/master-orchestrator.js - hash: sha256:61b874d74fae62e9307861b02b7505538f1c94362fe638fc3941b0665dcbbdf6 + hash: sha256:c33bb1aa5da0ab068ec69f40965b952bd73ecbf05f3ee9173aedb06e8d6d7c54 type: core - size: 54417 + size: 56588 - path: core/orchestration/message-formatter.js hash: sha256:b7413c04fa22db1c5fc2f5c2aa47bb8ca0374e079894a44df21b733da6c258ae type: core @@ -913,9 +913,9 @@ files: type: core size: 5820 - path: core/orchestration/recovery-handler.js - hash: sha256:6a9ef025f95885849aa893188299aca698cea2ea428cc302012833032c9e106e + hash: sha256:3b82b483070ada051ae07f5976bc0ff5f6931adea8b9ddf37508f60fcb4dd830 type: core - size: 24355 + size: 25930 - path: core/orchestration/session-state.js hash: sha256:72aa24d7a7a256a56973d7b4e7b03c968eabeb0d22be23af75f65f2e45ac88b3 type: core