diff --git a/.codex-synaptic/instructions.db b/.codex-synaptic/instructions.db new file mode 100644 index 0000000..9668183 Binary files /dev/null and b/.codex-synaptic/instructions.db differ diff --git a/config/routing/policies.json b/config/routing/policies.json new file mode 100644 index 0000000..cdc2ef6 --- /dev/null +++ b/config/routing/policies.json @@ -0,0 +1,96 @@ +[ + { + "id": "code-implementation", + "name": "Code Implementation", + "description": "Route code implementation requests to code workers", + "precedence": 100, + "conditions": { + "keywords": [ + "implement", + "code", + "function", + "class", + "method", + "develop", + "write" + ], + "patterns": [ + "implement.*function", + "create.*class", + "write.*code" + ] + }, + "target": "code_worker", + "confidence": 0.9, + "fallback": "validation_worker", + "metadata": { + "created": "2025-09-27T03:32:47.817Z", + "lastModified": "2025-09-27T03:32:47.817Z", + "creator": "system", + "version": "1.0.0", + "enabled": true + } + }, + { + "id": "data-processing", + "name": "Data Processing", + "description": "Route data processing and analysis requests", + "precedence": 90, + "conditions": { + "keywords": [ + "data", + "analyze", + "process", + "transform", + "etl", + "pipeline" + ], + "patterns": [ + "analyze.*data", + "process.*dataset", + "transform.*data" + ] + }, + "target": "data_worker", + "confidence": 0.85, + "fallback": "code_worker", + "metadata": { + "created": "2025-09-27T03:32:47.817Z", + "lastModified": "2025-09-27T03:32:47.817Z", + "creator": "system", + "version": "1.0.0", + "enabled": true + } + }, + { + "id": "validation-testing", + "name": "Validation and Testing", + "description": "Route validation and testing requests", + "precedence": 80, + "conditions": { + "keywords": [ + "test", + "validate", + "verify", + "check", + "audit", + "quality" + ], + "patterns": [ + "test.*code", + "validate.*input", + "verify.*output" + ] + }, + "target": "validation_worker", + "confidence": 0.85, + "fallback": "code_worker", + "metadata": { + "created": "2025-09-27T03:32:47.817Z", + "lastModified": "2025-09-27T03:32:47.817Z", + "creator": "system", + "version": "1.0.0", + "enabled": true + } + } +] \ No newline at end of file diff --git a/memory/routing/2025-09-27.json b/memory/routing/2025-09-27.json new file mode 100644 index 0000000..cfb6f42 --- /dev/null +++ b/memory/routing/2025-09-27.json @@ -0,0 +1,969 @@ +[ + { + "agentType": "code_worker", + "confidence": 0.9, + "reasoning": "Matched rule: Code Implementation - Route code implementation requests to code workers", + "alternatives": [], + "metadata": { + "evaluationId": "1b9558aafcfde662cc0a58335767a0ff", + "timestamp": "2025-09-27T03:32:47.821Z", + "processingTimeMs": 1, + "rulesApplied": [ + "code-implementation" + ] + } + }, + { + "agentType": "data_worker", + "confidence": 0.85, + "reasoning": "Matched rule: Data Processing - Route data processing and analysis requests", + "alternatives": [], + "metadata": { + "evaluationId": "58c84be27341351c425c29f34f874458", + "timestamp": "2025-09-27T03:33:51.949Z", + "processingTimeMs": 1, + "rulesApplied": [ + "data-processing" + ] + } + }, + { + "agentType": "code_worker", + "confidence": 0.9, + "reasoning": "Matched rule: Code Implementation - Route code implementation requests to code workers", + "alternatives": [], + "metadata": { + "evaluationId": "97b82569955f38dd033ecc292a70a156", + "timestamp": "2025-09-27T03:35:54.341Z", + "processingTimeMs": 1, + "rulesApplied": [ + "code-implementation" + ] + } + }, + { + "agentType": "data_worker", + "confidence": 0.85, + "reasoning": "Matched rule: Data Processing - Route data processing and analysis requests", + "alternatives": [], + "metadata": { + "evaluationId": "0bdb8d147585f3b422f00b165a6f10ef", + "timestamp": "2025-09-27T03:35:54.343Z", + "processingTimeMs": 0, + "rulesApplied": [ + "data-processing" + ] + } + }, + { + "agentType": "validation_worker", + "confidence": 0.85, + "reasoning": "Matched rule: Validation and Testing - Route validation and testing requests", + "alternatives": [], + "metadata": { + "evaluationId": "709b0d0ff96f14ed78dd3b9f655fa8c7", + "timestamp": "2025-09-27T03:35:54.344Z", + "processingTimeMs": 0, + "rulesApplied": [ + "validation-testing" + ] + } + }, + { + "agentType": "code_worker", + "confidence": 0.6, + "reasoning": "Default routing based on prompt analysis", + "alternatives": [ + { + "agentType": "code_worker", + "confidence": 0, + "reasoning": "General-purpose fallback" + } + ], + "metadata": { + "evaluationId": "b829d63d9327a3bedb9700cd2d025f1c", + "timestamp": "2025-09-27T03:35:54.345Z", + "processingTimeMs": 0, + "rulesApplied": [] + } + }, + { + "agentType": "validation_worker", + "confidence": 0.95, + "reasoning": "Matched rule: Custom Code Rule - Higher precedence code rule", + "alternatives": [ + { + "agentType": "code_worker", + "confidence": 0.7200000000000001, + "reasoning": "Alternative from rule: Code Implementation" + }, + { + "agentType": "validation_worker", + "confidence": 0.68, + "reasoning": "Alternative from rule: Validation and Testing" + } + ], + "metadata": { + "evaluationId": "773d3764968dc2621b205de70fce96ff", + "timestamp": "2025-09-27T03:35:54.347Z", + "processingTimeMs": 1, + "rulesApplied": [ + "custom-code-rule", + "code-implementation", + "validation-testing" + ] + } + }, + { + "agentType": "swarm_coordinator", + "confidence": 0.88, + "reasoning": "Matched rule: Pattern Rule - Rule that matches regex patterns", + "alternatives": [], + "metadata": { + "evaluationId": "8cafe2a1552631079af17f88c0267e3a", + "timestamp": "2025-09-27T03:35:54.351Z", + "processingTimeMs": 0, + "rulesApplied": [ + "pattern-rule" + ] + } + }, + { + "agentType": "consensus_coordinator", + "confidence": 0.9, + "reasoning": "Matched rule: Short Prompt Rule - Rule for short prompts only", + "alternatives": [], + "metadata": { + "evaluationId": "4747a544c21ad21899d1bb2856ddc719", + "timestamp": "2025-09-27T03:35:54.352Z", + "processingTimeMs": 0, + "rulesApplied": [ + "short-prompt-rule" + ] + } + }, + { + "agentType": "code_worker", + "confidence": 0.8, + "reasoning": "Code-related keywords detected", + "alternatives": [ + { + "agentType": "code_worker", + "confidence": 0, + "reasoning": "General-purpose fallback" + } + ], + "metadata": { + "evaluationId": "febe69cd6c6c5a6111d2c026e42f1dda", + "timestamp": "2025-09-27T03:35:54.353Z", + "processingTimeMs": 0, + "rulesApplied": [] + } + }, + { + "agentType": "code_worker", + "confidence": 0.9, + "reasoning": "Matched rule: Code Implementation - Route code implementation requests to code workers", + "alternatives": [], + "metadata": { + "evaluationId": "1b3160eeddd1373851bd3017b04bf5f7", + "timestamp": "2025-09-27T03:35:54.354Z", + "processingTimeMs": 0, + "rulesApplied": [ + "code-implementation" + ] + } + }, + { + "agentType": "code_worker", + "confidence": 0.9, + "reasoning": "Matched rule: Code Implementation - Route code implementation requests to code workers", + "alternatives": [], + "metadata": { + "evaluationId": "3f6897a558ffd251d617fc33d1799f33", + "timestamp": "2025-09-27T03:35:54.362Z", + "processingTimeMs": 0, + "rulesApplied": [ + "code-implementation" + ] + } + }, + { + "agentType": "data_worker", + "confidence": 0.85, + "reasoning": "Matched rule: Data Processing - Route data processing and analysis requests", + "alternatives": [], + "metadata": { + "evaluationId": "86611775131ed82b95d73246ecd46dc4", + "timestamp": "2025-09-27T03:35:54.362Z", + "processingTimeMs": 0, + "rulesApplied": [ + "data-processing" + ] + } + }, + { + "agentType": "code_worker", + "confidence": 0.9, + "reasoning": "Matched rule: Code Implementation - Route code implementation requests to code workers", + "alternatives": [], + "metadata": { + "evaluationId": "d6078a8c04e3bc21856c16f4b2c3fd1f", + "timestamp": "2025-09-27T03:36:19.190Z", + "processingTimeMs": 0, + "rulesApplied": [ + "code-implementation" + ] + } + }, + { + "agentType": "code_worker", + "confidence": 0.9, + "reasoning": "Matched rule: Code Implementation - Route code implementation requests to code workers", + "alternatives": [], + "metadata": { + "evaluationId": "2e1584f7372d781ea7f9487f841e01d8", + "timestamp": "2025-09-27T03:37:00.237Z", + "processingTimeMs": 1, + "rulesApplied": [ + "code-implementation" + ] + } + }, + { + "agentType": "code_worker", + "confidence": 0.9, + "reasoning": "Matched rule: Code Implementation - Route code implementation requests to code workers", + "alternatives": [], + "metadata": { + "evaluationId": "cbc6dfab7b7b84005fc9e53347d4b9c2", + "timestamp": "2025-09-27T03:37:53.521Z", + "processingTimeMs": 1, + "rulesApplied": [ + "code-implementation" + ] + } + }, + { + "agentType": "data_worker", + "confidence": 0.85, + "reasoning": "Matched rule: Data Processing - Route data processing and analysis requests", + "alternatives": [], + "metadata": { + "evaluationId": "40a6dbf8abdc310182ca39d45debb8dd", + "timestamp": "2025-09-27T03:37:53.522Z", + "processingTimeMs": 0, + "rulesApplied": [ + "data-processing" + ] + } + }, + { + "agentType": "validation_worker", + "confidence": 0.85, + "reasoning": "Matched rule: Validation and Testing - Route validation and testing requests", + "alternatives": [], + "metadata": { + "evaluationId": "5473ae303719f0e51b701cf2678d4879", + "timestamp": "2025-09-27T03:37:53.524Z", + "processingTimeMs": 1, + "rulesApplied": [ + "validation-testing" + ] + } + }, + { + "agentType": "code_worker", + "confidence": 0.6, + "reasoning": "Default routing based on prompt analysis", + "alternatives": [ + { + "agentType": "code_worker", + "confidence": 0, + "reasoning": "General-purpose fallback" + } + ], + "metadata": { + "evaluationId": "2b42e9a5427955f07f3aa5e4e5c2885e", + "timestamp": "2025-09-27T03:37:53.525Z", + "processingTimeMs": 0, + "rulesApplied": [] + } + }, + { + "agentType": "validation_worker", + "confidence": 0.95, + "reasoning": "Matched rule: Custom Code Rule - Higher precedence code rule", + "alternatives": [ + { + "agentType": "code_worker", + "confidence": 0.7200000000000001, + "reasoning": "Alternative from rule: Code Implementation" + }, + { + "agentType": "validation_worker", + "confidence": 0.68, + "reasoning": "Alternative from rule: Validation and Testing" + } + ], + "metadata": { + "evaluationId": "e5a45406176d9afbc7207d3ec9e6cfe6", + "timestamp": "2025-09-27T03:37:53.527Z", + "processingTimeMs": 0, + "rulesApplied": [ + "custom-code-rule", + "code-implementation", + "validation-testing" + ] + } + }, + { + "agentType": "swarm_coordinator", + "confidence": 0.88, + "reasoning": "Matched rule: Pattern Rule - Rule that matches regex patterns", + "alternatives": [], + "metadata": { + "evaluationId": "f381f3344e967d1bf581921fd80e16ea", + "timestamp": "2025-09-27T03:37:53.537Z", + "processingTimeMs": 1, + "rulesApplied": [ + "pattern-rule" + ] + } + }, + { + "agentType": "consensus_coordinator", + "confidence": 0.9, + "reasoning": "Matched rule: Short Prompt Rule - Rule for short prompts only", + "alternatives": [], + "metadata": { + "evaluationId": "4ad287ba0a1ec7c3ccc57c3041181e6a", + "timestamp": "2025-09-27T03:37:53.538Z", + "processingTimeMs": 0, + "rulesApplied": [ + "short-prompt-rule" + ] + } + }, + { + "agentType": "code_worker", + "confidence": 0.8, + "reasoning": "Code-related keywords detected", + "alternatives": [ + { + "agentType": "code_worker", + "confidence": 0, + "reasoning": "General-purpose fallback" + } + ], + "metadata": { + "evaluationId": "9cc7b83047926095c43dc21e4a130167", + "timestamp": "2025-09-27T03:37:53.538Z", + "processingTimeMs": 0, + "rulesApplied": [] + } + }, + { + "agentType": "code_worker", + "confidence": 0.9, + "reasoning": "Matched rule: Code Implementation - Route code implementation requests to code workers", + "alternatives": [], + "metadata": { + "evaluationId": "7b8065b2fce9363edd7c90f57e60df2f", + "timestamp": "2025-09-27T03:37:53.540Z", + "processingTimeMs": 0, + "rulesApplied": [ + "code-implementation" + ] + } + }, + { + "agentType": "code_worker", + "confidence": 0.9, + "reasoning": "Matched rule: Code Implementation - Route code implementation requests to code workers", + "alternatives": [], + "metadata": { + "evaluationId": "cdebd4755aa49db6e66f6eebbbf73975", + "timestamp": "2025-09-27T03:37:53.544Z", + "processingTimeMs": 0, + "rulesApplied": [ + "code-implementation" + ] + } + }, + { + "agentType": "data_worker", + "confidence": 0.85, + "reasoning": "Matched rule: Data Processing - Route data processing and analysis requests", + "alternatives": [], + "metadata": { + "evaluationId": "cb4a8e84a7d4dddb506f2eccbf042940", + "timestamp": "2025-09-27T03:37:53.544Z", + "processingTimeMs": 0, + "rulesApplied": [ + "data-processing" + ] + } + }, + { + "agentType": "code_worker", + "confidence": 0.9, + "reasoning": "Matched rule: Code Implementation - Route code implementation requests to code workers", + "alternatives": [], + "metadata": { + "evaluationId": "ff78ebb81ab1fb3614b709447a075767", + "timestamp": "2025-09-27T03:38:57.080Z", + "processingTimeMs": 1, + "rulesApplied": [ + "code-implementation" + ] + } + }, + { + "agentType": "data_worker", + "confidence": 0.85, + "reasoning": "Matched rule: Data Processing - Route data processing and analysis requests", + "alternatives": [], + "metadata": { + "evaluationId": "a313d3af4cf7636d363c6a7bae3828e9", + "timestamp": "2025-09-27T03:38:57.081Z", + "processingTimeMs": 0, + "rulesApplied": [ + "data-processing" + ] + } + }, + { + "agentType": "validation_worker", + "confidence": 0.85, + "reasoning": "Matched rule: Validation and Testing - Route validation and testing requests", + "alternatives": [], + "metadata": { + "evaluationId": "164d574b39590edae0cf6f55df6c7099", + "timestamp": "2025-09-27T03:38:57.083Z", + "processingTimeMs": 1, + "rulesApplied": [ + "validation-testing" + ] + } + }, + { + "agentType": "code_worker", + "confidence": 0.6, + "reasoning": "Default routing based on prompt analysis", + "alternatives": [ + { + "agentType": "code_worker", + "confidence": 0, + "reasoning": "General-purpose fallback" + } + ], + "metadata": { + "evaluationId": "673726e839e1769dcdc44b0d3e01c41a", + "timestamp": "2025-09-27T03:38:57.084Z", + "processingTimeMs": 0, + "rulesApplied": [] + } + }, + { + "agentType": "validation_worker", + "confidence": 0.95, + "reasoning": "Matched rule: Custom Code Rule - Higher precedence code rule", + "alternatives": [ + { + "agentType": "code_worker", + "confidence": 0.7200000000000001, + "reasoning": "Alternative from rule: Code Implementation" + }, + { + "agentType": "validation_worker", + "confidence": 0.68, + "reasoning": "Alternative from rule: Validation and Testing" + } + ], + "metadata": { + "evaluationId": "7934a44b22dae6aceaf5c8bc70999f09", + "timestamp": "2025-09-27T03:38:57.086Z", + "processingTimeMs": 1, + "rulesApplied": [ + "custom-code-rule", + "code-implementation", + "validation-testing" + ] + } + }, + { + "agentType": "swarm_coordinator", + "confidence": 0.88, + "reasoning": "Matched rule: Pattern Rule - Rule that matches regex patterns", + "alternatives": [], + "metadata": { + "evaluationId": "02d79f8d796b4a47118fbfb5cb301a7a", + "timestamp": "2025-09-27T03:38:57.101Z", + "processingTimeMs": 0, + "rulesApplied": [ + "pattern-rule" + ] + } + }, + { + "agentType": "consensus_coordinator", + "confidence": 0.9, + "reasoning": "Matched rule: Short Prompt Rule - Rule for short prompts only", + "alternatives": [], + "metadata": { + "evaluationId": "8a307949d4b2f4d6e0fde068d98ad6b4", + "timestamp": "2025-09-27T03:38:57.102Z", + "processingTimeMs": 0, + "rulesApplied": [ + "short-prompt-rule" + ] + } + }, + { + "agentType": "code_worker", + "confidence": 0.8, + "reasoning": "Code-related keywords detected", + "alternatives": [ + { + "agentType": "code_worker", + "confidence": 0, + "reasoning": "General-purpose fallback" + } + ], + "metadata": { + "evaluationId": "0f2245085252af4bf125b1f7954856a9", + "timestamp": "2025-09-27T03:38:57.103Z", + "processingTimeMs": 0, + "rulesApplied": [] + } + }, + { + "agentType": "code_worker", + "confidence": 0.9, + "reasoning": "Matched rule: Code Implementation - Route code implementation requests to code workers", + "alternatives": [], + "metadata": { + "evaluationId": "d81c7ddba2f48cec0cd323ccfa2eb41d", + "timestamp": "2025-09-27T03:38:57.104Z", + "processingTimeMs": 0, + "rulesApplied": [ + "code-implementation" + ] + } + }, + { + "agentType": "code_worker", + "confidence": 0.9, + "reasoning": "Matched rule: Code Implementation - Route code implementation requests to code workers", + "alternatives": [], + "metadata": { + "evaluationId": "a327a6df7cb8360bd192bbf7b23fa231", + "timestamp": "2025-09-27T03:38:57.112Z", + "processingTimeMs": 0, + "rulesApplied": [ + "code-implementation" + ] + } + }, + { + "agentType": "data_worker", + "confidence": 0.85, + "reasoning": "Matched rule: Data Processing - Route data processing and analysis requests", + "alternatives": [], + "metadata": { + "evaluationId": "bb6d77d59d1ec800ff2e94c304145bb1", + "timestamp": "2025-09-27T03:38:57.112Z", + "processingTimeMs": 0, + "rulesApplied": [ + "data-processing" + ] + } + }, + { + "agentType": "code_worker", + "confidence": 0.9, + "reasoning": "Matched rule: Code Implementation - Route code implementation requests to code workers", + "alternatives": [], + "metadata": { + "evaluationId": "5d0c47ae9ec6ff936d3e92ef1762c9a0", + "timestamp": "2025-09-27T03:39:24.925Z", + "processingTimeMs": 1, + "rulesApplied": [ + "code-implementation" + ] + } + }, + { + "agentType": "data_worker", + "confidence": 0.85, + "reasoning": "Matched rule: Data Processing - Route data processing and analysis requests", + "alternatives": [], + "metadata": { + "evaluationId": "bedfcf85a3424391e6436bbee637b363", + "timestamp": "2025-09-27T03:39:24.926Z", + "processingTimeMs": 0, + "rulesApplied": [ + "data-processing" + ] + } + }, + { + "agentType": "validation_worker", + "confidence": 0.85, + "reasoning": "Matched rule: Validation and Testing - Route validation and testing requests", + "alternatives": [], + "metadata": { + "evaluationId": "ecbc0922c84dd8ef3787ed2e019855d2", + "timestamp": "2025-09-27T03:39:24.927Z", + "processingTimeMs": 0, + "rulesApplied": [ + "validation-testing" + ] + } + }, + { + "agentType": "code_worker", + "confidence": 0.6, + "reasoning": "Default routing based on prompt analysis", + "alternatives": [ + { + "agentType": "code_worker", + "confidence": 0, + "reasoning": "General-purpose fallback" + } + ], + "metadata": { + "evaluationId": "530f9a71b8039efe39470d29d768e995", + "timestamp": "2025-09-27T03:39:24.929Z", + "processingTimeMs": 0, + "rulesApplied": [] + } + }, + { + "agentType": "validation_worker", + "confidence": 0.95, + "reasoning": "Matched rule: Custom Code Rule - Higher precedence code rule", + "alternatives": [ + { + "agentType": "code_worker", + "confidence": 0.7200000000000001, + "reasoning": "Alternative from rule: Code Implementation" + }, + { + "agentType": "validation_worker", + "confidence": 0.68, + "reasoning": "Alternative from rule: Validation and Testing" + } + ], + "metadata": { + "evaluationId": "7f931d5477b88871bc9d615c47f4f26a", + "timestamp": "2025-09-27T03:39:24.930Z", + "processingTimeMs": 0, + "rulesApplied": [ + "custom-code-rule", + "code-implementation", + "validation-testing" + ] + } + }, + { + "agentType": "swarm_coordinator", + "confidence": 0.88, + "reasoning": "Matched rule: Pattern Rule - Rule that matches regex patterns", + "alternatives": [], + "metadata": { + "evaluationId": "3176b6bafcd2826e5859605bce4c846b", + "timestamp": "2025-09-27T03:39:24.946Z", + "processingTimeMs": 0, + "rulesApplied": [ + "pattern-rule" + ] + } + }, + { + "agentType": "consensus_coordinator", + "confidence": 0.9, + "reasoning": "Matched rule: Short Prompt Rule - Rule for short prompts only", + "alternatives": [], + "metadata": { + "evaluationId": "c136948bcc875dd9b02712b35f59ef36", + "timestamp": "2025-09-27T03:39:24.948Z", + "processingTimeMs": 0, + "rulesApplied": [ + "short-prompt-rule" + ] + } + }, + { + "agentType": "code_worker", + "confidence": 0.8, + "reasoning": "Code-related keywords detected", + "alternatives": [ + { + "agentType": "code_worker", + "confidence": 0, + "reasoning": "General-purpose fallback" + } + ], + "metadata": { + "evaluationId": "5b1239b0fc3844a7b18bb204b87285f5", + "timestamp": "2025-09-27T03:39:24.948Z", + "processingTimeMs": 0, + "rulesApplied": [] + } + }, + { + "agentType": "code_worker", + "confidence": 0.9, + "reasoning": "Matched rule: Code Implementation - Route code implementation requests to code workers", + "alternatives": [], + "metadata": { + "evaluationId": "5c257d7d078f62bf89004dfb06539336", + "timestamp": "2025-09-27T03:39:24.949Z", + "processingTimeMs": 0, + "rulesApplied": [ + "code-implementation" + ] + } + }, + { + "agentType": "code_worker", + "confidence": 0.8, + "reasoning": "Code-related keywords detected", + "alternatives": [ + { + "agentType": "code_worker", + "confidence": 0, + "reasoning": "General-purpose fallback" + } + ], + "metadata": { + "evaluationId": "1801fbf9bbdb16386ae9515b32e78c81", + "timestamp": "2025-09-27T03:39:24.950Z", + "processingTimeMs": 0, + "rulesApplied": [] + } + }, + { + "agentType": "code_worker", + "confidence": 0.9, + "reasoning": "Matched rule: Code Implementation - Route code implementation requests to code workers", + "alternatives": [], + "metadata": { + "evaluationId": "0f2156448c09b633991d44bbf8fe9c15", + "timestamp": "2025-09-27T03:39:24.952Z", + "processingTimeMs": 0, + "rulesApplied": [ + "code-implementation" + ] + } + }, + { + "agentType": "data_worker", + "confidence": 0.85, + "reasoning": "Matched rule: Data Processing - Route data processing and analysis requests", + "alternatives": [], + "metadata": { + "evaluationId": "0b14a0a434c03b1904f18fc4690a753e", + "timestamp": "2025-09-27T03:39:24.953Z", + "processingTimeMs": 0, + "rulesApplied": [ + "data-processing" + ] + } + }, + { + "agentType": "code_worker", + "confidence": 0.9, + "reasoning": "Matched rule: Code Implementation - Route code implementation requests to code workers", + "alternatives": [], + "metadata": { + "evaluationId": "d03d0700f3fd91949d77cb55c33ff31d", + "timestamp": "2025-09-27T03:39:36.791Z", + "processingTimeMs": 1, + "rulesApplied": [ + "code-implementation" + ] + } + }, + { + "agentType": "data_worker", + "confidence": 0.85, + "reasoning": "Matched rule: Data Processing - Route data processing and analysis requests", + "alternatives": [], + "metadata": { + "evaluationId": "645de4d99f16cad307d82e9fd1ddd3ab", + "timestamp": "2025-09-27T03:39:36.794Z", + "processingTimeMs": 1, + "rulesApplied": [ + "data-processing" + ] + } + }, + { + "agentType": "validation_worker", + "confidence": 0.85, + "reasoning": "Matched rule: Validation and Testing - Route validation and testing requests", + "alternatives": [], + "metadata": { + "evaluationId": "6893e8c2bc40dfdf587d0de6d4e6147e", + "timestamp": "2025-09-27T03:39:36.796Z", + "processingTimeMs": 0, + "rulesApplied": [ + "validation-testing" + ] + } + }, + { + "agentType": "code_worker", + "confidence": 0.6, + "reasoning": "Default routing based on prompt analysis", + "alternatives": [ + { + "agentType": "code_worker", + "confidence": 0, + "reasoning": "General-purpose fallback" + } + ], + "metadata": { + "evaluationId": "77bc88361b92f3aa19a1e27c70ba1ea0", + "timestamp": "2025-09-27T03:39:36.798Z", + "processingTimeMs": 0, + "rulesApplied": [] + } + }, + { + "agentType": "validation_worker", + "confidence": 0.95, + "reasoning": "Matched rule: Custom Code Rule - Higher precedence code rule", + "alternatives": [ + { + "agentType": "code_worker", + "confidence": 0.7200000000000001, + "reasoning": "Alternative from rule: Code Implementation" + }, + { + "agentType": "validation_worker", + "confidence": 0.68, + "reasoning": "Alternative from rule: Validation and Testing" + } + ], + "metadata": { + "evaluationId": "4b67b95258533b7cbfc187eca1a7aeec", + "timestamp": "2025-09-27T03:39:36.801Z", + "processingTimeMs": 0, + "rulesApplied": [ + "custom-code-rule", + "code-implementation", + "validation-testing" + ] + } + }, + { + "agentType": "swarm_coordinator", + "confidence": 0.88, + "reasoning": "Matched rule: Pattern Rule - Rule that matches regex patterns", + "alternatives": [], + "metadata": { + "evaluationId": "f780345758e545dd2b46c65a5c43ef3f", + "timestamp": "2025-09-27T03:39:36.821Z", + "processingTimeMs": 1, + "rulesApplied": [ + "pattern-rule" + ] + } + }, + { + "agentType": "consensus_coordinator", + "confidence": 0.9, + "reasoning": "Matched rule: Short Prompt Rule - Rule for short prompts only", + "alternatives": [], + "metadata": { + "evaluationId": "5483de466fa575cef37df3640a1610fb", + "timestamp": "2025-09-27T03:39:36.823Z", + "processingTimeMs": 0, + "rulesApplied": [ + "short-prompt-rule" + ] + } + }, + { + "agentType": "code_worker", + "confidence": 0.8, + "reasoning": "Code-related keywords detected", + "alternatives": [ + { + "agentType": "code_worker", + "confidence": 0, + "reasoning": "General-purpose fallback" + } + ], + "metadata": { + "evaluationId": "3f9d67a64b877334912feced881dea7d", + "timestamp": "2025-09-27T03:39:36.824Z", + "processingTimeMs": 0, + "rulesApplied": [] + } + }, + { + "agentType": "code_worker", + "confidence": 0.9, + "reasoning": "Matched rule: Code Implementation - Route code implementation requests to code workers", + "alternatives": [], + "metadata": { + "evaluationId": "5477dacd993599a33cfa1708ae7842d7", + "timestamp": "2025-09-27T03:39:36.825Z", + "processingTimeMs": 0, + "rulesApplied": [ + "code-implementation" + ] + } + }, + { + "agentType": "code_worker", + "confidence": 0.8, + "reasoning": "Code-related keywords detected", + "alternatives": [ + { + "agentType": "code_worker", + "confidence": 0, + "reasoning": "General-purpose fallback" + } + ], + "metadata": { + "evaluationId": "78e8ac2a4f27d16c6325c152a298fae0", + "timestamp": "2025-09-27T03:39:36.826Z", + "processingTimeMs": 0, + "rulesApplied": [] + } + }, + { + "agentType": "code_worker", + "confidence": 0.9, + "reasoning": "Matched rule: Code Implementation - Route code implementation requests to code workers", + "alternatives": [], + "metadata": { + "evaluationId": "60198d189877bc760028febba05db85f", + "timestamp": "2025-09-27T03:39:36.829Z", + "processingTimeMs": 0, + "rulesApplied": [ + "code-implementation" + ] + } + }, + { + "agentType": "data_worker", + "confidence": 0.85, + "reasoning": "Matched rule: Data Processing - Route data processing and analysis requests", + "alternatives": [], + "metadata": { + "evaluationId": "9e188681d6db4c763683e1816908439e", + "timestamp": "2025-09-27T03:39:36.830Z", + "processingTimeMs": 0, + "rulesApplied": [ + "data-processing" + ] + } + } +] \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 83d05c2..d9574f8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,6 +21,8 @@ "codex-synaptic": "dist/cli/index.js" }, "devDependencies": { + "@types/estree": "^1.0.8", + "@types/json-schema": "^7.0.15", "@types/node": "^24.3.1", "@types/sqlite3": "^3.1.11", "@types/uuid": "^10.0.0", diff --git a/package.json b/package.json index 0a8bc9e..8ff63fc 100644 --- a/package.json +++ b/package.json @@ -32,17 +32,19 @@ "author": "Parallax Analytics ", "license": "MIT", "devDependencies": { + "@types/estree": "^1.0.8", + "@types/json-schema": "^7.0.15", "@types/node": "^24.3.1", - "@types/uuid": "^10.0.0", "@types/sqlite3": "^3.1.11", + "@types/uuid": "^10.0.0", "@typescript-eslint/eslint-plugin": "^8.43.0", "@typescript-eslint/parser": "^8.43.0", "eslint": "^9.35.0", + "globals": "^15.12.0", "nodemon": "^3.1.10", "prettier": "^3.6.2", "ts-node": "^10.9.2", "typescript": "^5.9.2", - "globals": "^15.12.0", "vitest": "^1.6.1" }, "dependencies": { diff --git a/src/cli/codex-context.ts b/src/cli/codex-context.ts index 671f348..0333de5 100644 --- a/src/cli/codex-context.ts +++ b/src/cli/codex-context.ts @@ -2,6 +2,7 @@ import * as fs from 'node:fs'; import * as path from 'node:path'; import { createHash } from 'node:crypto'; import { scanRepository, type AgentsGuide } from '../core/scanner.js'; +import { InstructionParser, InstructionPrecedence } from '../instructions/index.js'; import type { CodexContext, CodexContextAggregationMetadata, @@ -59,6 +60,7 @@ interface ArtifactScanResult { export interface CodexContextBuilderOptions { useCache?: boolean; + useEnhancedInstructionParser?: boolean; } export interface CodexContextBuildResult { @@ -70,6 +72,7 @@ export interface CodexContextBuildResult { export class CodexContextBuilder { private readonly root: string; private readonly useCache: boolean; + private readonly useEnhancedParser: boolean; private logs: ContextLogEntry[] = []; private partial: Partial = {}; private watches: Record = {}; @@ -84,6 +87,7 @@ export class CodexContextBuilder { constructor(rootDir: string, options: CodexContextBuilderOptions = {}) { this.root = path.resolve(rootDir); this.useCache = options.useCache !== false; + this.useEnhancedParser = options.useEnhancedInstructionParser || false; } async withAgentDirectives(): Promise { @@ -91,6 +95,81 @@ export class CodexContextBuilder { return this; } + if (this.useEnhancedParser) { + return this.withEnhancedAgentDirectives(); + } else { + return this.withLegacyAgentDirectives(); + } + } + + private async withEnhancedAgentDirectives(): Promise { + const parser = new InstructionParser(); + try { + const context = await parser.parseInstructions(this.root, this.useCache); + + this.partial.agentDirectives = context.agentDirectives; + this.metadata.agentGuideCount = context.metadata.length; + + // Convert instruction parser logs to context logs + if (context.metadata.length > 0) { + this.logs.push({ + level: 'info', + message: 'Enhanced instruction parser loaded directives with precedence handling.', + details: { + filesProcessed: context.metadata.length, + precedenceChain: context.precedenceChain.join(' → '), + contextHash: context.contextHash.slice(0, 12), + totalSize: context.aggregatedSize + } + }); + + // Log individual files with precedence levels + context.metadata.forEach(meta => { + this.logs.push({ + level: 'info', + message: 'Loaded instruction file with precedence.', + details: { + path: meta.path, + precedence: InstructionPrecedence[meta.precedence], + bytes: meta.size, + valid: meta.isValid + } + }); + + if (!meta.isValid && meta.validationErrors) { + this.logs.push({ + level: 'warn', + message: 'Instruction file has validation issues.', + details: { + path: meta.path, + errors: meta.validationErrors + } + }); + } + + // Track file for cache invalidation + const fullPath = path.join(this.root, meta.path); + this.trackWatch(fullPath); + }); + } else { + this.logs.push({ level: 'warn', message: 'No AGENTS.md files found – using default enhanced directives.' }); + } + + } catch (error) { + this.logs.push({ + level: 'error', + message: 'Enhanced instruction parser failed, falling back to legacy parser.', + details: { error: String(error) } + }); + return this.withLegacyAgentDirectives(); + } finally { + await parser.close(); + } + + return this; + } + + private async withLegacyAgentDirectives(): Promise { const guides = await this.loadAgentGuides(); this.metadata.agentGuideCount = guides.length; if (!guides.length) { diff --git a/src/cli/index.ts b/src/cli/index.ts index c0ce97c..d4e1128 100644 --- a/src/cli/index.ts +++ b/src/cli/index.ts @@ -28,6 +28,8 @@ import type { ContextLogEntry } from '../types/codex-context.js'; import { RetryManager } from '../core/errors.js'; +import { InstructionParser } from '../instructions/index.js'; +import { RoutingPolicyService } from '../router/index.js'; const program = new Command(); const session = CliSession.getInstance(); @@ -382,6 +384,281 @@ backgroundCmd } })); +// Instructions commands +const instructionsCmd = program.command('instructions').description('Instruction processing and cache management'); + +instructionsCmd + .command('sync') + .description('Synchronize and cache AGENTS.md instructions from repository') + .option('-r, --root ', 'Repository root path', process.cwd()) + .option('--no-cache', 'Skip cache and force fresh scan') + .option('-v, --verbose', 'Show detailed processing information') + .action(handleCommand('instructions.sync', async (options) => { + const parser = new InstructionParser(); + try { + console.log(chalk.cyan('šŸ”„ Synchronizing instruction files...')); + + const startTime = Date.now(); + const context = await parser.parseInstructions(options.root, options.cache); + const duration = Date.now() - startTime; + + if (options.verbose) { + console.log(chalk.gray(`Processed ${context.metadata.length} instruction files`)); + console.log(chalk.gray(`Precedence chain: ${context.precedenceChain.join(' → ')}`)); + console.log(chalk.gray(`Context hash: ${context.contextHash}`)); + console.log(chalk.gray(`Total size: ${context.aggregatedSize} bytes`)); + console.log(chalk.gray(`Processing time: ${duration}ms`)); + } + + console.log(chalk.green(`āœ… Instructions synchronized successfully`)); + console.log(`šŸ“ Files processed: ${context.metadata.length}`); + console.log(`šŸ”— Context hash: ${context.contextHash.slice(0, 12)}...`); + + } catch (error) { + console.error(chalk.red(`āŒ Failed to sync instructions: ${error}`)); + process.exitCode = 1; + } finally { + await parser.close(); + } + })); + +instructionsCmd + .command('validate') + .argument('[file]', 'Specific AGENTS.md file to validate (optional)') + .description('Validate AGENTS.md file syntax and structure') + .option('-r, --root ', 'Repository root path', process.cwd()) + .action(handleCommand('instructions.validate', async (file, options) => { + const parser = new InstructionParser(); + try { + if (file) { + // Validate specific file + console.log(chalk.cyan(`šŸ” Validating ${file}...`)); + const result = await parser.validateInstructionSyntax(file); + + if (result.isValid) { + console.log(chalk.green(`āœ… ${file} is valid`)); + } else { + console.log(chalk.red(`āŒ ${file} has validation errors:`)); + result.errors.forEach(error => console.log(chalk.red(` • ${error}`))); + process.exitCode = 1; + } + } else { + // Validate all files in repository + console.log(chalk.cyan('šŸ” Validating all instruction files...')); + const context = await parser.parseInstructions(options.root, false); + + const invalidFiles = context.metadata.filter(m => !m.isValid); + if (invalidFiles.length === 0) { + console.log(chalk.green(`āœ… All ${context.metadata.length} instruction files are valid`)); + } else { + console.log(chalk.red(`āŒ Found ${invalidFiles.length} invalid files:`)); + invalidFiles.forEach(file => { + console.log(chalk.red(` • ${file.path}:`)); + file.validationErrors?.forEach(error => console.log(chalk.red(` - ${error}`))); + }); + process.exitCode = 1; + } + } + } catch (error) { + console.error(chalk.red(`āŒ Validation failed: ${error}`)); + process.exitCode = 1; + } finally { + await parser.close(); + } + })); + +instructionsCmd + .command('cache') + .description('Manage instruction cache') + .option('--clear [path]', 'Clear cache for specific path or all if no path given') + .option('--status', 'Show cache status') + .action(handleCommand('instructions.cache', async (options) => { + const parser = new InstructionParser(); + try { + if (options.clear !== undefined) { + const pathToClear = options.clear || undefined; + await parser.clearCache(pathToClear); + console.log(chalk.green(`āœ… Cache cleared${pathToClear ? ` for ${pathToClear}` : ''}`)); + } else if (options.status) { + console.log(chalk.cyan('šŸ“Š Cache status:')); + // TODO: Implement cache status display + console.log(chalk.gray('Cache status display coming soon...')); + } else { + console.log(chalk.yellow('āš ļø Please specify --clear or --status')); + } + } catch (error) { + console.error(chalk.red(`āŒ Cache operation failed: ${error}`)); + process.exitCode = 1; + } finally { + await parser.close(); + } + })); + +// Router commands +const routerCmd = program.command('router').description('Routing policy management and evaluation'); + +routerCmd + .command('evaluate') + .argument('', 'Prompt to evaluate for routing') + .description('Evaluate routing for a given prompt') + .option('-c, --context ', 'Context file to include') + .option('-e, --exclude ', 'Comma-separated list of agents to exclude') + .option('-p, --prefer ', 'Comma-separated list of preferred agents') + .option('-v, --verbose', 'Show detailed evaluation information') + .action(handleCommand('router.evaluate', async (prompt, options) => { + const router = new RoutingPolicyService(); + try { + console.log(chalk.cyan('šŸ”„ Evaluating routing for prompt...')); + + const request = { + prompt, + context: options.context ? { + fileContext: require('fs').readFileSync(options.context, 'utf8') + } : undefined, + constraints: { + excludeAgents: options.exclude ? options.exclude.split(',').map((s: string) => s.trim()) : undefined, + preferredAgents: options.prefer ? options.prefer.split(',').map((s: string) => s.trim()) : undefined + } + }; + + const evaluation = await router.evaluateRouting(request); + + console.log(chalk.green(`āœ… Routing evaluation completed`)); + console.log(`šŸŽÆ Recommended agent: ${chalk.bold(evaluation.agentType)}`); + console.log(`šŸ“Š Confidence: ${(evaluation.confidence * 100).toFixed(1)}%`); + console.log(`šŸ’­ Reasoning: ${evaluation.reasoning}`); + + if (options.verbose) { + console.log(`\nšŸ“‹ Evaluation Details:`); + console.log(` • Evaluation ID: ${evaluation.metadata.evaluationId.slice(0, 12)}...`); + console.log(` • Processing time: ${evaluation.metadata.processingTimeMs}ms`); + console.log(` • Rules applied: ${evaluation.metadata.rulesApplied.length}`); + + if (evaluation.alternatives.length > 0) { + console.log(`\nšŸ”„ Alternatives:`); + evaluation.alternatives.forEach((alt, i) => { + console.log(` ${i + 1}. ${alt.agentType} (${(alt.confidence * 100).toFixed(1)}%) - ${alt.reasoning}`); + }); + } + } + + } catch (error) { + console.error(chalk.red(`āŒ Routing evaluation failed: ${error}`)); + process.exitCode = 1; + } + })); + +routerCmd + .command('rules') + .description('Manage routing rules') + .option('-l, --list', 'List all routing rules') + .option('-a, --add ', 'Add rule from JSON file') + .option('-d, --delete ', 'Delete rule by ID') + .option('-e, --enable ', 'Enable rule by ID') + .option('-x, --disable ', 'Disable rule by ID') + .option('-v, --verbose', 'Show detailed rule information') + .action(handleCommand('router.rules', async (options) => { + const router = new RoutingPolicyService(); + try { + if (options.list) { + const rules = router.getAllRules(); + console.log(chalk.cyan(`šŸ“‹ Routing Rules (${rules.length} total):`)); + + if (rules.length === 0) { + console.log(chalk.gray(' No rules configured')); + return; + } + + rules.forEach(rule => { + const status = rule.metadata.enabled ? chalk.green('āœ“') : chalk.red('āœ—'); + console.log(` ${status} ${chalk.bold(rule.name)} (${rule.id})`); + console.log(` Precedence: ${rule.precedence}, Confidence: ${(rule.confidence * 100).toFixed(1)}%`); + console.log(` Target: ${rule.target}, Description: ${rule.description}`); + + if (options.verbose) { + console.log(` Keywords: ${rule.conditions.keywords?.join(', ') || 'none'}`); + console.log(` Patterns: ${rule.conditions.patterns?.join(', ') || 'none'}`); + console.log(` Created: ${rule.metadata.created.toISOString()}`); + } + console.log(''); + }); + } else if (options.add) { + const ruleData = JSON.parse(require('fs').readFileSync(options.add, 'utf8')); + const rule = await router.addRule(ruleData); + console.log(chalk.green(`āœ… Rule added: ${rule.name} (${rule.id})`)); + } else if (options.delete) { + const deleted = await router.deleteRule(options.delete); + if (deleted) { + console.log(chalk.green(`āœ… Rule deleted: ${options.delete}`)); + } else { + console.log(chalk.yellow(`āš ļø Rule not found: ${options.delete}`)); + } + } else if (options.enable) { + const rule = await router.updateRule(options.enable, { + metadata: { enabled: true } as any + }); + if (rule) { + console.log(chalk.green(`āœ… Rule enabled: ${rule.name}`)); + } else { + console.log(chalk.yellow(`āš ļø Rule not found: ${options.enable}`)); + } + } else if (options.disable) { + const rule = await router.updateRule(options.disable, { + metadata: { enabled: false } as any + }); + if (rule) { + console.log(chalk.yellow(`āš ļø Rule disabled: ${rule.name}`)); + } else { + console.log(chalk.yellow(`āš ļø Rule not found: ${options.disable}`)); + } + } else { + console.log(chalk.yellow('āš ļø Please specify an action: --list, --add, --delete, --enable, or --disable')); + } + } catch (error) { + console.error(chalk.red(`āŒ Router operation failed: ${error}`)); + process.exitCode = 1; + } + })); + +routerCmd + .command('history') + .description('Show routing evaluation history') + .option('-l, --limit ', 'Number of entries to show', '10') + .option('-v, --verbose', 'Show detailed evaluation information') + .action(handleCommand('router.history', async (options) => { + const router = new RoutingPolicyService(); + try { + const limit = parseInt(options.limit); + const history = router.getEvaluationHistory(limit); + + console.log(chalk.cyan(`šŸ“Š Routing History (last ${history.length} evaluations):`)); + + if (history.length === 0) { + console.log(chalk.gray(' No evaluation history found')); + return; + } + + history.forEach((evaluation, i) => { + const timestamp = evaluation.metadata.timestamp.toLocaleString(); + console.log(`\n${i + 1}. ${chalk.bold(evaluation.agentType)} (${timestamp})`); + console.log(` Confidence: ${(evaluation.confidence * 100).toFixed(1)}%`); + console.log(` Reasoning: ${evaluation.reasoning}`); + + if (options.verbose) { + console.log(` Evaluation ID: ${evaluation.metadata.evaluationId.slice(0, 12)}...`); + console.log(` Processing time: ${evaluation.metadata.processingTimeMs}ms`); + console.log(` Rules applied: ${evaluation.metadata.rulesApplied.join(', ') || 'none'}`); + if (evaluation.alternatives.length > 0) { + console.log(` Alternatives: ${evaluation.alternatives.map(a => a.agentType).join(', ')}`); + } + } + }); + } catch (error) { + console.error(chalk.red(`āŒ Failed to retrieve history: ${error}`)); + process.exitCode = 1; + } + })); + // Agent commands const agentCmd = program.command('agent').description('Agent management commands'); diff --git a/src/instructions/index.ts b/src/instructions/index.ts new file mode 100644 index 0000000..23997e5 --- /dev/null +++ b/src/instructions/index.ts @@ -0,0 +1,7 @@ +export { + InstructionParser, + InstructionPrecedence, + type InstructionMetadata, + type InstructionContext, + type InstructionCacheEntry +} from './parser.js'; \ No newline at end of file diff --git a/src/instructions/parser.ts b/src/instructions/parser.ts new file mode 100644 index 0000000..f265dbe --- /dev/null +++ b/src/instructions/parser.ts @@ -0,0 +1,523 @@ +import * as fs from 'node:fs'; +import * as path from 'node:path'; +import sqlite3 from 'sqlite3'; +import { createHash } from 'node:crypto'; +import { scanRepository, type AgentsGuide } from '../core/scanner.js'; +import { Logger } from '../core/logger.js'; + +/** + * Instruction precedence levels from lowest to highest priority + */ +export enum InstructionPrecedence { + GLOBAL = 1, + PROJECT = 2, + LOCAL = 3, + OVERRIDE = 4 +} + +export interface InstructionMetadata { + id: string; + path: string; + scope: string; + precedence: InstructionPrecedence; + contentHash: string; + size: number; + lastModified: Date; + isValid: boolean; + validationErrors?: string[]; +} + +export interface InstructionContext { + agentDirectives: string; + metadata: InstructionMetadata[]; + contextHash: string; + aggregatedSize: number; + precedenceChain: string[]; +} + +export interface InstructionCacheEntry { + context: InstructionContext; + timestamp: Date; + ttl: number; +} + +/** + * Enhanced instruction parser with precedence handling and SQLite caching + */ +export class InstructionParser { + private db: any; + private logger = Logger.getInstance(); + private readonly cacheDir: string; + private readonly dbPath: string; + + constructor(cacheDir?: string) { + this.cacheDir = cacheDir || path.join(process.cwd(), '.codex-synaptic'); + this.dbPath = path.join(this.cacheDir, 'instructions.db'); + + // Ensure cache directory exists + if (!fs.existsSync(this.cacheDir)) { + fs.mkdirSync(this.cacheDir, { recursive: true }); + } + + this.db = new sqlite3.Database(this.dbPath); + this.initializeDatabase(); + } + + private initializeDatabase(): void { + this.db.exec(` + CREATE TABLE IF NOT EXISTS instruction_cache ( + id TEXT PRIMARY KEY, + root_path TEXT NOT NULL, + context_data TEXT NOT NULL, + context_hash TEXT NOT NULL, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + ttl INTEGER DEFAULT 3600, + UNIQUE(root_path, context_hash) + ); + + CREATE TABLE IF NOT EXISTS instruction_metadata ( + id TEXT PRIMARY KEY, + file_path TEXT NOT NULL, + scope TEXT NOT NULL, + precedence INTEGER NOT NULL, + content_hash TEXT NOT NULL, + size INTEGER NOT NULL, + last_modified DATETIME NOT NULL, + is_valid BOOLEAN DEFAULT 1, + validation_errors TEXT, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP + ); + + CREATE INDEX IF NOT EXISTS idx_cache_root_path ON instruction_cache(root_path); + CREATE INDEX IF NOT EXISTS idx_cache_hash ON instruction_cache(context_hash); + CREATE INDEX IF NOT EXISTS idx_metadata_path ON instruction_metadata(file_path); + CREATE INDEX IF NOT EXISTS idx_metadata_precedence ON instruction_metadata(precedence); + `); + } + + /** + * Parse instructions from a repository with precedence handling and caching + */ + async parseInstructions(rootPath: string, useCache: boolean = true): Promise { + const absRoot = path.resolve(rootPath); + + // Check cache first if enabled + if (useCache) { + const cached = await this.getCachedInstructions(absRoot); + if (cached) { + this.logger.info('instructions', 'Instruction cache hit', { rootPath: absRoot, hash: cached.context.contextHash }); + return cached.context; + } + } + + // Scan repository for AGENTS.md files + const scanReport = await scanRepository(absRoot); + const agentsGuides = scanReport.agentsGuides; + + if (agentsGuides.length === 0) { + this.logger.warn('instructions', 'No AGENTS.md files found', { rootPath: absRoot }); + return this.createEmptyContext(absRoot); + } + + // Process guides with precedence + const processedGuides = await this.processGuidesWithPrecedence(agentsGuides, absRoot); + + // Validate instruction files + const validatedGuides = await this.validateInstructions(processedGuides, absRoot); + + // Build final context + const context = await this.buildInstructionContext(validatedGuides, absRoot); + + // Cache the result + if (useCache) { + await this.cacheInstructions(absRoot, context); + } + + this.logger.info('instructions', 'Instructions parsed successfully', { + rootPath: absRoot, + guidesCount: agentsGuides.length, + contextHash: context.contextHash, + totalSize: context.aggregatedSize + }); + + return context; + } + + /** + * Determine precedence based on file path relative to root + */ + private determinePrecedence(guidePath: string, rootPath: string): InstructionPrecedence { + const relativePath = path.relative(rootPath, guidePath); + const depth = relativePath.split(path.sep).length - 1; + + // Global: In root directory + if (depth === 0) { + return InstructionPrecedence.GLOBAL; + } + + // Project: In project directories (1-2 levels deep) + if (depth <= 2) { + return InstructionPrecedence.PROJECT; + } + + // Local: Deeper nesting + if (depth <= 4) { + return InstructionPrecedence.LOCAL; + } + + // Override: Very deep nesting or specific override patterns + return InstructionPrecedence.OVERRIDE; + } + + /** + * Process guides with precedence ordering + */ + private async processGuidesWithPrecedence( + guides: AgentsGuide[], + rootPath: string + ): Promise { + const processed: InstructionMetadata[] = []; + + for (const guide of guides) { + const fullPath = path.join(rootPath, guide.path); + const precedence = this.determinePrecedence(fullPath, rootPath); + const contentHash = createHash('sha256').update(guide.content).digest('hex'); + + try { + const stats = fs.statSync(fullPath); + + const metadata: InstructionMetadata = { + id: createHash('md5').update(fullPath).digest('hex'), + path: guide.path, + scope: guide.scope, + precedence, + contentHash, + size: guide.size, + lastModified: stats.mtime, + isValid: true, + validationErrors: [] + }; + + processed.push(metadata); + } catch (error) { + this.logger.error('instructions', 'Failed to process instruction file', { path: guide.path, error }); + continue; + } + } + + // Sort by precedence (ascending) then by path for deterministic ordering + return processed.sort((a, b) => { + if (a.precedence !== b.precedence) { + return a.precedence - b.precedence; + } + return a.path.localeCompare(b.path); + }); + } + + /** + * Validate instruction files for syntax and content issues + */ + private async validateInstructions( + metadata: InstructionMetadata[], + rootPath: string + ): Promise { + const validated: InstructionMetadata[] = []; + + for (const meta of metadata) { + const fullPath = path.join(rootPath, meta.path); + const validationErrors: string[] = []; + + try { + const content = fs.readFileSync(fullPath, 'utf8'); + + // Basic markdown validation + if (!content.trim()) { + validationErrors.push('File is empty'); + } + + // Check for malformed sections + const lines = content.split('\n'); + let inCodeBlock = false; + let headerCount = 0; + + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + + // Track code blocks + if (line.trim().startsWith('```')) { + inCodeBlock = !inCodeBlock; + } + + // Check headers + if (line.startsWith('#') && !inCodeBlock) { + headerCount++; + if (!/^#{1,6}\s+.+/.test(line)) { + validationErrors.push(`Malformed header at line ${i + 1}: "${line}"`); + } + } + } + + if (inCodeBlock) { + validationErrors.push('Unclosed code block detected'); + } + + if (headerCount === 0) { + validationErrors.push('No headers found - file may not be properly structured'); + } + + meta.isValid = validationErrors.length === 0; + meta.validationErrors = validationErrors.length > 0 ? validationErrors : undefined; + + if (!meta.isValid) { + this.logger.warn('instructions', 'Instruction file validation failed', { + path: meta.path, + errors: validationErrors + }); + } + + } catch (error) { + meta.isValid = false; + meta.validationErrors = [`Failed to read file: ${error}`]; + this.logger.error('instructions', 'Instruction file validation error', { path: meta.path, error }); + } + + validated.push(meta); + } + + return validated; + } + + /** + * Build final instruction context from validated metadata + */ + private async buildInstructionContext( + metadata: InstructionMetadata[], + rootPath: string + ): Promise { + const validMetadata = metadata.filter(m => m.isValid); + const segments: string[] = []; + const precedenceChain: string[] = []; + let aggregatedSize = 0; + + // Process in precedence order, with higher precedence overriding lower + // Only use VALID files for content, but include ALL metadata + for (const meta of validMetadata) { + try { + const fullPath = path.join(rootPath, meta.path); + const content = fs.readFileSync(fullPath, 'utf8'); + + segments.push(`\n## ${meta.scope} (${InstructionPrecedence[meta.precedence]})\n${content}`); + precedenceChain.push(`${meta.scope}:${InstructionPrecedence[meta.precedence]}`); + aggregatedSize += meta.size; + + } catch (error) { + this.logger.error('instructions', 'Failed to read instruction file during context building', { + path: meta.path, + error + }); + } + } + + const agentDirectives = segments.length > 0 + ? segments.join('\n\n---\n\n') + : this.getDefaultDirectives(); + + // Include ALL metadata (valid and invalid), not just valid ones + const contextData = JSON.stringify({ agentDirectives, metadata, precedenceChain }); + const contextHash = createHash('sha256').update(contextData).digest('hex'); + + return { + agentDirectives, + metadata, // Return ALL metadata, not just validMetadata + contextHash, + aggregatedSize, + precedenceChain + }; + } + + /** + * Create empty context for repositories without AGENTS.md files + */ + private createEmptyContext(rootPath: string): InstructionContext { + const agentDirectives = this.getDefaultDirectives(); + const contextData = JSON.stringify({ agentDirectives, metadata: [], precedenceChain: [] }); + const contextHash = createHash('sha256').update(contextData).digest('hex'); + + return { + agentDirectives, + metadata: [], + contextHash, + aggregatedSize: agentDirectives.length, + precedenceChain: ['default'] + }; + } + + /** + * Get default agent directives when no AGENTS.md files are found + */ + private getDefaultDirectives(): string { + return `# Codex-Synaptic Default Directives + +- Preserve sandbox integrity and follow security best practices +- Honor all AGENTS.md directives by scope precedence +- Maintain transparent logging for Codex orchestration +- Defer to Codex operator input on ambiguities +- Follow OpenAI usage policies and ethical guidelines +- Ensure deterministic and reproducible outputs where possible`; + } + + /** + * Get cached instructions if available and not expired + */ + private async getCachedInstructions(rootPath: string): Promise { + return new Promise((resolve, reject) => { + this.db.get( + 'SELECT * FROM instruction_cache WHERE root_path = ? ORDER BY created_at DESC LIMIT 1', + [rootPath], + (err: Error | null, row: any) => { + if (err) { + reject(err); + return; + } + + if (!row) { + resolve(null); + return; + } + + const created = new Date(row.created_at); + const now = new Date(); + const ageSeconds = (now.getTime() - created.getTime()) / 1000; + + if (ageSeconds > row.ttl) { + // Cache expired + resolve(null); + return; + } + + try { + const context: InstructionContext = JSON.parse(row.context_data); + resolve({ + context, + timestamp: created, + ttl: row.ttl + }); + } catch (parseError) { + this.logger.error('instructions', 'Failed to parse cached instruction context', { error: parseError }); + resolve(null); + } + } + ); + }); + } + + /** + * Cache instruction context for future use + */ + private async cacheInstructions(rootPath: string, context: InstructionContext): Promise { + return new Promise((resolve, reject) => { + const contextData = JSON.stringify(context); + const id = createHash('md5').update(`${rootPath}:${context.contextHash}`).digest('hex'); + + this.db.run( + `INSERT OR REPLACE INTO instruction_cache + (id, root_path, context_data, context_hash, ttl) + VALUES (?, ?, ?, ?, ?)`, + [id, rootPath, contextData, context.contextHash, 3600], // 1 hour TTL + (err: Error | null) => { + if (err) { + this.logger.error('instructions', 'Failed to cache instruction context', { error: err }); + reject(err); + } else { + this.logger.debug('instructions', 'Instruction context cached', { rootPath, hash: context.contextHash }); + resolve(); + } + } + ); + }); + } + + /** + * Clear instruction cache for a specific root path or all entries + */ + async clearCache(rootPath?: string): Promise { + return new Promise((resolve, reject) => { + const query = rootPath + ? 'DELETE FROM instruction_cache WHERE root_path = ?' + : 'DELETE FROM instruction_cache'; + + const params = rootPath ? [rootPath] : []; + + this.db.run(query, params, (err: Error | null) => { + if (err) { + reject(err); + } else { + this.logger.info('instructions', 'Instruction cache cleared', { rootPath }); + resolve(); + } + }); + }); + } + + /** + * Validate instruction syntax without full parsing + */ + async validateInstructionSyntax(filePath: string): Promise<{ isValid: boolean; errors: string[] }> { + const errors: string[] = []; + + try { + if (!fs.existsSync(filePath)) { + errors.push('File does not exist'); + return { isValid: false, errors }; + } + + const content = fs.readFileSync(filePath, 'utf8'); + + if (!content.trim()) { + errors.push('File is empty'); + } + + // Basic markdown validation + const lines = content.split('\n'); + let inCodeBlock = false; + + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + + if (line.trim().startsWith('```')) { + inCodeBlock = !inCodeBlock; + } + + if (line.startsWith('#') && !inCodeBlock) { + if (!/^#{1,6}\s+.+/.test(line)) { + errors.push(`Malformed header at line ${i + 1}: "${line}"`); + } + } + } + + if (inCodeBlock) { + errors.push('Unclosed code block detected'); + } + + } catch (error) { + errors.push(`Failed to read file: ${error}`); + } + + return { isValid: errors.length === 0, errors }; + } + + /** + * Close database connection + */ + async close(): Promise { + return new Promise((resolve, reject) => { + this.db.close((err: Error | null) => { + if (err) { + reject(err); + } else { + resolve(); + } + }); + }); + } +} \ No newline at end of file diff --git a/src/router/index.ts b/src/router/index.ts new file mode 100644 index 0000000..245499a --- /dev/null +++ b/src/router/index.ts @@ -0,0 +1,6 @@ +export { + RoutingPolicyService, + type RoutingEvaluation, + type RoutingRule, + type RoutingRequest +} from './router.js'; \ No newline at end of file diff --git a/src/router/router.ts b/src/router/router.ts new file mode 100644 index 0000000..7cddf64 --- /dev/null +++ b/src/router/router.ts @@ -0,0 +1,544 @@ +import * as fs from 'node:fs'; +import * as path from 'node:path'; +import { createHash } from 'node:crypto'; +import { Logger } from '../core/logger.js'; +import { AgentType } from '../core/types.js'; + +/** + * Routing evaluation result with confidence scoring + */ +export interface RoutingEvaluation { + agentType: AgentType; + confidence: number; + reasoning: string; + alternatives: Array<{ + agentType: AgentType; + confidence: number; + reasoning: string; + }>; + metadata: { + evaluationId: string; + timestamp: Date; + processingTimeMs: number; + rulesApplied: string[]; + }; +} + +/** + * Routing policy rule with precedence and conditions + */ +export interface RoutingRule { + id: string; + name: string; + description: string; + precedence: number; + conditions: { + keywords?: string[]; + patterns?: string[]; + contextLength?: { min?: number; max?: number }; + agentCapabilities?: string[]; + excludeAgents?: AgentType[]; + }; + target: AgentType; + confidence: number; + fallback?: AgentType; + metadata: { + created: Date; + lastModified: Date; + creator: string; + version: string; + enabled: boolean; + }; +} + +/** + * Request for routing evaluation + */ +export interface RoutingRequest { + prompt: string; + context?: { + agentDirectives?: string; + fileContext?: string; + userPreferences?: Record; + }; + constraints?: { + excludeAgents?: AgentType[]; + preferredAgents?: AgentType[]; + maxResponseTime?: number; + }; + metadata?: { + sessionId?: string; + requestId?: string; + timestamp?: Date; + }; +} + +/** + * Persona-aligned routing policy service + */ +export class RoutingPolicyService { + private logger = Logger.getInstance(); + private rules: Map = new Map(); + private configPath: string; + private historyPath: string; + private evaluationHistory: Map = new Map(); + + constructor(configDir?: string) { + const baseDir = configDir || path.join(process.cwd(), 'config', 'routing'); + this.configPath = path.join(baseDir, 'policies.json'); + this.historyPath = path.join(process.cwd(), 'memory', 'routing'); + + // Ensure directories exist + fs.mkdirSync(path.dirname(this.configPath), { recursive: true }); + fs.mkdirSync(this.historyPath, { recursive: true }); + + this.loadRoutingRules(); + } + + /** + * Evaluate routing for a given request using persona-aligned decision making + */ + async evaluateRouting(request: RoutingRequest): Promise { + const startTime = Date.now(); + const evaluationId = createHash('md5').update(`${Date.now()}-${request.prompt}`).digest('hex'); + + this.logger.info('router', 'Starting routing evaluation', { + evaluationId, + promptLength: request.prompt.length + }); + + try { + // Apply routing rules in precedence order + const applicableRules = this.findApplicableRules(request); + const rulesApplied = applicableRules.map(r => r.id); + + // If no rules match, use default routing logic + let evaluation: RoutingEvaluation; + if (applicableRules.length === 0) { + evaluation = this.performDefaultRouting(request, evaluationId); + } else { + evaluation = this.performRuleBasedRouting(request, applicableRules, evaluationId); + } + + // Add metadata + evaluation.metadata = { + evaluationId, + timestamp: new Date(), + processingTimeMs: Date.now() - startTime, + rulesApplied + }; + + // Store evaluation history + this.evaluationHistory.set(evaluationId, evaluation); + await this.persistEvaluationHistory(evaluation); + + this.logger.info('router', 'Routing evaluation completed', { + evaluationId, + agentType: evaluation.agentType, + confidence: evaluation.confidence, + processingTimeMs: evaluation.metadata.processingTimeMs + }); + + return evaluation; + + } catch (error) { + this.logger.error('router', 'Routing evaluation failed', { evaluationId, error }); + + // Return fallback routing + return { + agentType: 'code_worker' as AgentType, + confidence: 0.1, + reasoning: `Routing evaluation failed: ${error}. Using fallback agent.`, + alternatives: [], + metadata: { + evaluationId, + timestamp: new Date(), + processingTimeMs: Date.now() - startTime, + rulesApplied: [] + } + }; + } + } + + /** + * Add or update a routing rule + */ + async addRule(rule: Omit): Promise { + const fullRule: RoutingRule = { + ...rule, + metadata: { + created: new Date(), + lastModified: new Date(), + creator: 'system', + version: '1.0.0', + enabled: true + } + }; + + this.rules.set(rule.id, fullRule); + await this.saveRoutingRules(); + + this.logger.info('router', 'Routing rule added', { ruleId: rule.id, name: rule.name }); + return fullRule; + } + + /** + * Update an existing routing rule + */ + async updateRule(ruleId: string, updates: Partial): Promise { + const existingRule = this.rules.get(ruleId); + if (!existingRule) { + return null; + } + + const updatedRule: RoutingRule = { + ...existingRule, + ...updates, + metadata: { + ...existingRule.metadata, + ...(updates.metadata || {}), + lastModified: new Date() + } + }; + + this.rules.set(ruleId, updatedRule); + await this.saveRoutingRules(); + + this.logger.info('router', 'Routing rule updated', { ruleId, name: updatedRule.name }); + return updatedRule; + } + + /** + * Delete a routing rule + */ + async deleteRule(ruleId: string): Promise { + const deleted = this.rules.delete(ruleId); + if (deleted) { + await this.saveRoutingRules(); + this.logger.info('router', 'Routing rule deleted', { ruleId }); + } + return deleted; + } + + /** + * Get all routing rules + */ + getAllRules(): RoutingRule[] { + return Array.from(this.rules.values()) + .sort((a, b) => b.precedence - a.precedence); // Higher precedence first + } + + /** + * Get a specific routing rule + */ + getRule(ruleId: string): RoutingRule | null { + return this.rules.get(ruleId) || null; + } + + /** + * Get evaluation history + */ + getEvaluationHistory(limit: number = 100): RoutingEvaluation[] { + return Array.from(this.evaluationHistory.values()) + .sort((a, b) => b.metadata.timestamp.getTime() - a.metadata.timestamp.getTime()) + .slice(0, limit); + } + + /** + * Find applicable rules for a request + */ + private findApplicableRules(request: RoutingRequest): RoutingRule[] { + const applicableRules: RoutingRule[] = []; + + for (const rule of this.rules.values()) { + if (!rule.metadata.enabled) continue; + + if (this.ruleMatches(rule, request)) { + applicableRules.push(rule); + } + } + + // Sort by precedence (higher first) + return applicableRules.sort((a, b) => b.precedence - a.precedence); + } + + /** + * Check if a rule matches a request + */ + private ruleMatches(rule: RoutingRule, request: RoutingRequest): boolean { + const conditions = rule.conditions; + + // Check keyword matches + if (conditions.keywords && conditions.keywords.length > 0) { + const hasKeyword = conditions.keywords.some(keyword => + request.prompt.toLowerCase().includes(keyword.toLowerCase()) + ); + if (!hasKeyword) return false; + } + + // Check pattern matches (simple regex patterns) + if (conditions.patterns && conditions.patterns.length > 0) { + const hasPattern = conditions.patterns.some(pattern => { + try { + const regex = new RegExp(pattern, 'i'); + return regex.test(request.prompt); + } catch { + return false; + } + }); + if (!hasPattern) return false; + } + + // Check context length constraints + if (conditions.contextLength) { + const length = request.prompt.length; + if (conditions.contextLength.min && length < conditions.contextLength.min) return false; + if (conditions.contextLength.max && length > conditions.contextLength.max) return false; + } + + // Check excluded agents + if (conditions.excludeAgents && request.constraints?.excludeAgents) { + const hasExcluded = conditions.excludeAgents.some(agent => + request.constraints!.excludeAgents!.includes(agent) + ); + if (hasExcluded) return false; + } + + return true; + } + + /** + * Perform default routing when no rules match + */ + private performDefaultRouting(request: RoutingRequest, evaluationId: string): RoutingEvaluation { + const prompt = request.prompt.toLowerCase(); + let agentType: AgentType = AgentType.CODE_WORKER; + let confidence = 0.6; + let reasoning = 'Default routing based on prompt analysis'; + + // Simple heuristics for default routing + if (prompt.includes('code') || prompt.includes('implement') || prompt.includes('debug')) { + agentType = AgentType.CODE_WORKER; + confidence = 0.8; + reasoning = 'Code-related keywords detected'; + } else if (prompt.includes('data') || prompt.includes('analyze') || prompt.includes('process')) { + agentType = AgentType.DATA_WORKER; + confidence = 0.7; + reasoning = 'Data processing keywords detected'; + } else if (prompt.includes('test') || prompt.includes('validate') || prompt.includes('check')) { + agentType = AgentType.VALIDATION_WORKER; + confidence = 0.75; + reasoning = 'Validation keywords detected'; + } else if (prompt.includes('coordinate') || prompt.includes('manage') || prompt.includes('orchestrate')) { + agentType = AgentType.SWARM_COORDINATOR; + confidence = 0.7; + reasoning = 'Coordination keywords detected'; + } + + return { + agentType, + confidence, + reasoning, + alternatives: [ + { + agentType: AgentType.CODE_WORKER, + confidence: agentType === AgentType.CODE_WORKER ? 0 : 0.4, + reasoning: 'General-purpose fallback' + } + ], + metadata: { + evaluationId, + timestamp: new Date(), + processingTimeMs: 0, + rulesApplied: [] + } + }; + } + + /** + * Perform rule-based routing using the highest precedence matching rule + */ + private performRuleBasedRouting( + request: RoutingRequest, + applicableRules: RoutingRule[], + evaluationId: string + ): RoutingEvaluation { + const primaryRule = applicableRules[0]; // Highest precedence + + // Build alternatives from other matching rules + const alternatives = applicableRules.slice(1, 4).map(rule => ({ + agentType: rule.target, + confidence: rule.confidence * 0.8, // Reduce confidence for alternatives + reasoning: `Alternative from rule: ${rule.name}` + })); + + return { + agentType: primaryRule.target, + confidence: primaryRule.confidence, + reasoning: `Matched rule: ${primaryRule.name} - ${primaryRule.description}`, + alternatives, + metadata: { + evaluationId, + timestamp: new Date(), + processingTimeMs: 0, + rulesApplied: applicableRules.map(r => r.id) + } + }; + } + + /** + * Load routing rules from configuration file + */ + private loadRoutingRules(): void { + try { + if (fs.existsSync(this.configPath)) { + const content = fs.readFileSync(this.configPath, 'utf8'); + const rawRules: any[] = JSON.parse(content); + + this.rules.clear(); + rawRules.forEach(rawRule => { + // Parse dates that were serialized as strings + const rule: RoutingRule = { + ...rawRule, + metadata: { + ...rawRule.metadata, + created: new Date(rawRule.metadata.created), + lastModified: new Date(rawRule.metadata.lastModified) + } + }; + this.rules.set(rule.id, rule); + }); + + this.logger.info('router', 'Routing rules loaded', { + count: rawRules.length, + path: this.configPath + }); + } else { + this.initializeDefaultRules(); + } + } catch (error) { + this.logger.error('router', 'Failed to load routing rules', { error, path: this.configPath }); + this.initializeDefaultRules(); + } + } + + /** + * Save routing rules to configuration file + */ + private async saveRoutingRules(): Promise { + try { + const rules = Array.from(this.rules.values()); + const content = JSON.stringify(rules, null, 2); + fs.writeFileSync(this.configPath, content, 'utf8'); + + this.logger.info('router', 'Routing rules saved', { + count: rules.length, + path: this.configPath + }); + } catch (error) { + this.logger.error('router', 'Failed to save routing rules', { error, path: this.configPath }); + } + } + + /** + * Initialize default routing rules + */ + private initializeDefaultRules(): void { + const defaultRules: RoutingRule[] = [ + { + id: 'code-implementation', + name: 'Code Implementation', + description: 'Route code implementation requests to code workers', + precedence: 100, + conditions: { + keywords: ['implement', 'code', 'function', 'class', 'method', 'develop', 'write'], + patterns: ['implement.*function', 'create.*class', 'write.*code'] + }, + target: AgentType.CODE_WORKER, + confidence: 0.9, + fallback: AgentType.VALIDATION_WORKER, + metadata: { + created: new Date(), + lastModified: new Date(), + creator: 'system', + version: '1.0.0', + enabled: true + } + }, + { + id: 'data-processing', + name: 'Data Processing', + description: 'Route data processing and analysis requests', + precedence: 90, + conditions: { + keywords: ['data', 'analyze', 'process', 'transform', 'etl', 'pipeline'], + patterns: ['analyze.*data', 'process.*dataset', 'transform.*data'] + }, + target: AgentType.DATA_WORKER, + confidence: 0.85, + fallback: AgentType.CODE_WORKER, + metadata: { + created: new Date(), + lastModified: new Date(), + creator: 'system', + version: '1.0.0', + enabled: true + } + }, + { + id: 'validation-testing', + name: 'Validation and Testing', + description: 'Route validation and testing requests', + precedence: 80, + conditions: { + keywords: ['test', 'validate', 'verify', 'check', 'audit', 'quality'], + patterns: ['test.*code', 'validate.*input', 'verify.*output'] + }, + target: AgentType.VALIDATION_WORKER, + confidence: 0.85, + fallback: AgentType.CODE_WORKER, + metadata: { + created: new Date(), + lastModified: new Date(), + creator: 'system', + version: '1.0.0', + enabled: true + } + } + ]; + + defaultRules.forEach(rule => this.rules.set(rule.id, rule)); + this.saveRoutingRules(); + + this.logger.info('router', 'Default routing rules initialized', { count: defaultRules.length }); + } + + /** + * Persist evaluation history to storage + */ + private async persistEvaluationHistory(evaluation: RoutingEvaluation): Promise { + try { + const historyFile = path.join(this.historyPath, `${new Date().toISOString().split('T')[0]}.json`); + + let history: RoutingEvaluation[] = []; + if (fs.existsSync(historyFile)) { + const content = fs.readFileSync(historyFile, 'utf8'); + history = JSON.parse(content); + } + + history.push(evaluation); + + // Keep only the last 1000 evaluations per day + if (history.length > 1000) { + history = history.slice(-1000); + } + + fs.writeFileSync(historyFile, JSON.stringify(history, null, 2), 'utf8'); + } catch (error) { + this.logger.error('router', 'Failed to persist evaluation history', { + evaluationId: evaluation.metadata.evaluationId, + error + }); + } + } +} \ No newline at end of file diff --git a/tests/instructions/parser.test.ts b/tests/instructions/parser.test.ts new file mode 100644 index 0000000..281bfaa --- /dev/null +++ b/tests/instructions/parser.test.ts @@ -0,0 +1,248 @@ +import * as fs from 'node:fs'; +import { tmpdir } from 'node:os'; +import { dirname, join } from 'node:path'; +import { describe, expect, it, beforeEach, afterEach } from 'vitest'; +import { + InstructionParser, + InstructionPrecedence, + type InstructionContext +} from '../../src/instructions/index.js'; + +const writeFile = (filePath: string, content: string) => { + fs.mkdirSync(dirname(filePath), { recursive: true }); + fs.writeFileSync(filePath, content, 'utf8'); +}; + +describe('InstructionParser', () => { + let tempRoot: string; + let parser: InstructionParser; + + beforeEach(() => { + tempRoot = fs.mkdtempSync(join(tmpdir(), 'instruction-parser-')); + }); + + afterEach(async () => { + if (parser) { + await parser.close(); + } + fs.rmSync(tempRoot, { recursive: true, force: true }); + }); + + it('handles repositories with no AGENTS.md files', async () => { + parser = new InstructionParser(join(tempRoot, '.cache')); + + const context = await parser.parseInstructions(tempRoot, false); + + expect(context.metadata).toHaveLength(0); + expect(context.agentDirectives).toContain('Codex-Synaptic Default Directives'); + expect(context.precedenceChain).toEqual(['default']); + expect(context.contextHash).toBeDefined(); + }); + + it('processes single AGENTS.md file with global precedence', async () => { + writeFile(join(tempRoot, 'AGENTS.md'), '# Test Instructions\n\n- Must follow test protocols\n- Should validate inputs'); + + parser = new InstructionParser(join(tempRoot, '.cache')); + const context = await parser.parseInstructions(tempRoot, false); + + expect(context.metadata).toHaveLength(1); + expect(context.metadata[0].precedence).toBe(InstructionPrecedence.GLOBAL); + expect(context.metadata[0].scope).toBe('.'); + expect(context.agentDirectives).toContain('Test Instructions'); + expect(context.agentDirectives).toContain('Must follow test protocols'); + expect(context.precedenceChain).toEqual(['.:GLOBAL']); + }); + + it('handles multiple AGENTS.md files with correct precedence ordering', async () => { + // Global level + writeFile(join(tempRoot, 'AGENTS.md'), '# Global Instructions\n\n- Global rule 1'); + + // Project level + writeFile(join(tempRoot, 'backend/AGENTS.md'), '# Backend Instructions\n\n- Backend rule 1'); + + // Local level + writeFile(join(tempRoot, 'backend/auth/service/AGENTS.md'), '# Auth Service Instructions\n\n- Auth rule 1'); + + parser = new InstructionParser(join(tempRoot, '.cache')); + const context = await parser.parseInstructions(tempRoot, false); + + expect(context.metadata).toHaveLength(3); + + // Check precedence ordering (should be sorted by precedence) + expect(context.metadata[0].precedence).toBe(InstructionPrecedence.GLOBAL); + expect(context.metadata[1].precedence).toBe(InstructionPrecedence.PROJECT); + expect(context.metadata[2].precedence).toBe(InstructionPrecedence.LOCAL); + + // Check scopes + expect(context.metadata[0].scope).toBe('.'); + expect(context.metadata[1].scope).toBe('backend'); + expect(context.metadata[2].scope).toBe('backend/auth/service'); + + // Check content includes all sections + expect(context.agentDirectives).toContain('Global Instructions'); + expect(context.agentDirectives).toContain('Backend Instructions'); + expect(context.agentDirectives).toContain('Auth Service Instructions'); + + // Check precedence chain + expect(context.precedenceChain).toEqual(['.:GLOBAL', 'backend:PROJECT', 'backend/auth/service:LOCAL']); + }); + + it('validates instruction files and reports errors', async () => { + // Valid file + writeFile(join(tempRoot, 'AGENTS.md'), '# Valid Instructions\n\n- Rule 1\n- Rule 2'); + + // Invalid file with malformed header + writeFile(join(tempRoot, 'invalid/AGENTS.md'), '#Missing space\n\n- Rule 1\n\n```unclosed code block\ncode here'); + + parser = new InstructionParser(join(tempRoot, '.cache')); + const context = await parser.parseInstructions(tempRoot, false); + + expect(context.metadata).toHaveLength(2); + + const validFile = context.metadata.find(m => m.path === 'AGENTS.md'); + const invalidFile = context.metadata.find(m => m.path === 'invalid/AGENTS.md'); + + expect(validFile?.isValid).toBe(true); + expect(validFile?.validationErrors).toBeUndefined(); + + expect(invalidFile?.isValid).toBe(false); + expect(invalidFile?.validationErrors).toBeDefined(); + expect(invalidFile?.validationErrors).toContain('Malformed header at line 1: "#Missing space"'); + expect(invalidFile?.validationErrors).toContain('Unclosed code block detected'); + }); + + it('uses cache when enabled and cache is valid', async () => { + writeFile(join(tempRoot, 'AGENTS.md'), '# Cached Instructions\n\n- Cache test'); + + parser = new InstructionParser(join(tempRoot, '.cache')); + + // First call should parse and cache + const context1 = await parser.parseInstructions(tempRoot, true); + expect(context1.metadata).toHaveLength(1); + + // Second call should use cache + const context2 = await parser.parseInstructions(tempRoot, true); + expect(context2.metadata).toHaveLength(1); + expect(context2.contextHash).toBe(context1.contextHash); + }); + + it('invalidates cache when files are modified', async () => { + const agentsPath = join(tempRoot, 'AGENTS.md'); + writeFile(agentsPath, '# Original Instructions\n\n- Original rule'); + + parser = new InstructionParser(join(tempRoot, '.cache')); + + // First call + const context1 = await parser.parseInstructions(tempRoot, true); + const hash1 = context1.contextHash; + + // Clear cache to simulate file change invalidation + await parser.clearCache(tempRoot); + + // Modify file + writeFile(agentsPath, '# Modified Instructions\n\n- Modified rule'); + + // Second call should reparse due to cache clearing + const context2 = await parser.parseInstructions(tempRoot, true); + expect(context2.contextHash).not.toBe(hash1); + expect(context2.agentDirectives).toContain('Modified Instructions'); + }); + + it('validates individual file syntax correctly', async () => { + const validFile = join(tempRoot, 'valid.md'); + const invalidFile = join(tempRoot, 'invalid.md'); + + writeFile(validFile, '# Valid File\n\n## Section\n\n- Item 1\n- Item 2'); + writeFile(invalidFile, '# Valid Header\n\n```\nunclosed code block'); + + parser = new InstructionParser(join(tempRoot, '.cache')); + + const validResult = await parser.validateInstructionSyntax(validFile); + expect(validResult.isValid).toBe(true); + expect(validResult.errors).toHaveLength(0); + + const invalidResult = await parser.validateInstructionSyntax(invalidFile); + expect(invalidResult.isValid).toBe(false); + expect(invalidResult.errors).toContain('Unclosed code block detected'); + }); + + it('handles empty instruction files', async () => { + writeFile(join(tempRoot, 'AGENTS.md'), ''); + + parser = new InstructionParser(join(tempRoot, '.cache')); + const context = await parser.parseInstructions(tempRoot, false); + + expect(context.metadata).toHaveLength(1); + expect(context.metadata[0].isValid).toBe(false); + expect(context.metadata[0].validationErrors).toContain('File is empty'); + }); + + it('calculates correct precedence levels based on directory depth', async () => { + // Root level (depth 0) = GLOBAL + writeFile(join(tempRoot, 'AGENTS.md'), '# Global'); + + // 1 level deep = PROJECT + writeFile(join(tempRoot, 'api/AGENTS.md'), '# API Project'); + + // 2 levels deep = PROJECT + writeFile(join(tempRoot, 'api/v1/AGENTS.md'), '# API V1 Project'); + + // 3 levels deep = LOCAL + writeFile(join(tempRoot, 'api/v1/auth/AGENTS.md'), '# Auth Local'); + + // 5 levels deep = OVERRIDE + writeFile(join(tempRoot, 'api/v1/auth/service/impl/AGENTS.md'), '# Override'); + + parser = new InstructionParser(join(tempRoot, '.cache')); + const context = await parser.parseInstructions(tempRoot, false); + + expect(context.metadata).toHaveLength(5); + + const global = context.metadata.find(m => m.scope === '.'); + const apiProject = context.metadata.find(m => m.scope === 'api'); + const v1Project = context.metadata.find(m => m.scope === 'api/v1'); + const authLocal = context.metadata.find(m => m.scope === 'api/v1/auth'); + const override = context.metadata.find(m => m.scope === 'api/v1/auth/service/impl'); + + expect(global?.precedence).toBe(InstructionPrecedence.GLOBAL); + expect(apiProject?.precedence).toBe(InstructionPrecedence.PROJECT); + expect(v1Project?.precedence).toBe(InstructionPrecedence.PROJECT); + expect(authLocal?.precedence).toBe(InstructionPrecedence.LOCAL); + expect(override?.precedence).toBe(InstructionPrecedence.OVERRIDE); + }); + + it('handles nonexistent file validation gracefully', async () => { + parser = new InstructionParser(join(tempRoot, '.cache')); + + const result = await parser.validateInstructionSyntax(join(tempRoot, 'nonexistent.md')); + expect(result.isValid).toBe(false); + expect(result.errors).toContain('File does not exist'); + }); + + it('clears cache correctly', async () => { + writeFile(join(tempRoot, 'AGENTS.md'), '# Cache Test'); + + parser = new InstructionParser(join(tempRoot, '.cache')); + + // Parse and cache + await parser.parseInstructions(tempRoot, true); + + // Clear cache + await parser.clearCache(tempRoot); + + // Should work without errors (cache cleared) + const context = await parser.parseInstructions(tempRoot, true); + expect(context.metadata).toHaveLength(1); + }); + + it('generates deterministic context hashes', async () => { + writeFile(join(tempRoot, 'AGENTS.md'), '# Deterministic Test\n\n- Rule 1\n- Rule 2'); + + parser = new InstructionParser(join(tempRoot, '.cache')); + + const context1 = await parser.parseInstructions(tempRoot, false); + const context2 = await parser.parseInstructions(tempRoot, false); + + expect(context1.contextHash).toBe(context2.contextHash); + }); +}); \ No newline at end of file diff --git a/tests/router/router.test.ts b/tests/router/router.test.ts new file mode 100644 index 0000000..c31542c --- /dev/null +++ b/tests/router/router.test.ts @@ -0,0 +1,362 @@ +import * as fs from 'node:fs'; +import { tmpdir } from 'node:os'; +import { dirname, join } from 'node:path'; +import { describe, expect, it, beforeEach, afterEach } from 'vitest'; +import { + RoutingPolicyService, + type RoutingRequest, + type RoutingRule +} from '../../src/router/index.js'; +import { AgentType } from '../../src/core/types.js'; + +describe('RoutingPolicyService', () => { + let tempDir: string; + let router: RoutingPolicyService; + + beforeEach(() => { + tempDir = fs.mkdtempSync(join(tmpdir(), 'router-test-')); + }); + + afterEach(() => { + fs.rmSync(tempDir, { recursive: true, force: true }); + }); + + it('initializes with default routing rules', () => { + router = new RoutingPolicyService(tempDir); + const rules = router.getAllRules(); + + expect(rules).toHaveLength(3); + expect(rules.map(r => r.name)).toContain('Code Implementation'); + expect(rules.map(r => r.name)).toContain('Data Processing'); + expect(rules.map(r => r.name)).toContain('Validation and Testing'); + }); + + it('evaluates code implementation requests correctly', async () => { + router = new RoutingPolicyService(tempDir); + + const request: RoutingRequest = { + prompt: 'implement a new authentication function for user login' + }; + + const evaluation = await router.evaluateRouting(request); + + expect(evaluation.agentType).toBe(AgentType.CODE_WORKER); + expect(evaluation.confidence).toBe(0.9); + expect(evaluation.reasoning).toContain('Code Implementation'); + expect(evaluation.metadata.evaluationId).toBeDefined(); + expect(evaluation.metadata.processingTimeMs).toBeGreaterThanOrEqual(0); + }); + + it('evaluates data processing requests correctly', async () => { + router = new RoutingPolicyService(tempDir); + + const request: RoutingRequest = { + prompt: 'analyze the customer data and process it for insights' + }; + + const evaluation = await router.evaluateRouting(request); + + expect(evaluation.agentType).toBe(AgentType.DATA_WORKER); + expect(evaluation.confidence).toBe(0.85); + expect(evaluation.reasoning).toContain('Data Processing'); + }); + + it('evaluates validation requests correctly', async () => { + router = new RoutingPolicyService(tempDir); + + const request: RoutingRequest = { + prompt: 'validate the input parameters and test the API endpoints' + }; + + const evaluation = await router.evaluateRouting(request); + + expect(evaluation.agentType).toBe(AgentType.VALIDATION_WORKER); + expect(evaluation.confidence).toBe(0.85); + expect(evaluation.reasoning).toContain('Validation and Testing'); + }); + + it('falls back to default routing for unmatched requests', async () => { + router = new RoutingPolicyService(tempDir); + + const request: RoutingRequest = { + prompt: 'write a haiku about distributed systems' + }; + + const evaluation = await router.evaluateRouting(request); + + expect(evaluation.agentType).toBe(AgentType.CODE_WORKER); + expect(evaluation.confidence).toBe(0.6); // Default confidence + expect(evaluation.reasoning).toContain('Default routing'); + }); + + it('handles multiple matching rules by precedence', async () => { + router = new RoutingPolicyService(tempDir); + + // Add a higher precedence rule that also matches code keywords + const customRule: Omit = { + id: 'custom-code-rule', + name: 'Custom Code Rule', + description: 'Higher precedence code rule', + precedence: 150, + conditions: { + keywords: ['implement', 'function'] + }, + target: AgentType.VALIDATION_WORKER, + confidence: 0.95 + }; + + await router.addRule(customRule); + + const request: RoutingRequest = { + prompt: 'implement a function to validate user input' + }; + + const evaluation = await router.evaluateRouting(request); + + // Should use the higher precedence rule + expect(evaluation.agentType).toBe(AgentType.VALIDATION_WORKER); + expect(evaluation.confidence).toBe(0.95); + expect(evaluation.reasoning).toContain('Custom Code Rule'); + }); + + it('adds and manages routing rules', async () => { + router = new RoutingPolicyService(tempDir); + + const newRule: Omit = { + id: 'test-rule', + name: 'Test Rule', + description: 'A test routing rule', + precedence: 50, + conditions: { + keywords: ['test-keyword'], + patterns: ['test.*pattern'] + }, + target: AgentType.SWARM_COORDINATOR, + confidence: 0.7 + }; + + const addedRule = await router.addRule(newRule); + expect(addedRule.id).toBe('test-rule'); + expect(addedRule.metadata.created).toBeInstanceOf(Date); + expect(addedRule.metadata.enabled).toBe(true); + + const retrievedRule = router.getRule('test-rule'); + expect(retrievedRule).toBeDefined(); + expect(retrievedRule!.name).toBe('Test Rule'); + + const allRules = router.getAllRules(); + expect(allRules).toHaveLength(4); // 3 default + 1 new + }); + + it('updates existing routing rules', async () => { + router = new RoutingPolicyService(tempDir); + + const rule = router.getRule('code-implementation'); + expect(rule).toBeDefined(); + + // Add a small delay to ensure timestamp difference + await new Promise(resolve => setTimeout(resolve, 10)); + + const updatedRule = await router.updateRule('code-implementation', { + confidence: 0.95, + description: 'Updated description' + }); + + expect(updatedRule).toBeDefined(); + expect(updatedRule!.confidence).toBe(0.95); + expect(updatedRule!.description).toBe('Updated description'); + expect(updatedRule!.metadata.lastModified.getTime()).toBeGreaterThanOrEqual( + updatedRule!.metadata.created.getTime() + ); + }); + + it('deletes routing rules', async () => { + router = new RoutingPolicyService(tempDir); + + const deleted = await router.deleteRule('code-implementation'); + expect(deleted).toBe(true); + + const rule = router.getRule('code-implementation'); + expect(rule).toBeNull(); + + const allRules = router.getAllRules(); + expect(allRules).toHaveLength(2); // 2 remaining default rules + }); + + it('handles pattern matching in routing rules', async () => { + router = new RoutingPolicyService(tempDir); + + // Add rule with regex pattern + const patternRule: Omit = { + id: 'pattern-rule', + name: 'Pattern Rule', + description: 'Rule that matches regex patterns', + precedence: 120, + conditions: { + patterns: ['create\\s+.*\\s+database', 'setup\\s+.*\\s+server'] + }, + target: AgentType.SWARM_COORDINATOR, + confidence: 0.88 + }; + + await router.addRule(patternRule); + + const request: RoutingRequest = { + prompt: 'create a new database schema for the application' + }; + + const evaluation = await router.evaluateRouting(request); + + expect(evaluation.agentType).toBe(AgentType.SWARM_COORDINATOR); + expect(evaluation.confidence).toBe(0.88); + expect(evaluation.reasoning).toContain('Pattern Rule'); + }); + + it('respects context length constraints', async () => { + router = new RoutingPolicyService(tempDir); + + // Add rule that only matches short prompts + const shortRule: Omit = { + id: 'short-prompt-rule', + name: 'Short Prompt Rule', + description: 'Rule for short prompts only', + precedence: 110, + conditions: { + keywords: ['code'], + contextLength: { max: 20 } + }, + target: AgentType.CONSENSUS_COORDINATOR, + confidence: 0.9 + }; + + await router.addRule(shortRule); + + // Short prompt should match the new rule + const shortRequest: RoutingRequest = { + prompt: 'code fix' + }; + + const shortEvaluation = await router.evaluateRouting(shortRequest); + expect(shortEvaluation.agentType).toBe(AgentType.CONSENSUS_COORDINATOR); + + // Long prompt should not match the short rule and fall back to default code rule + const longRequest: RoutingRequest = { + prompt: 'implement a comprehensive code solution for the complex authentication system' + }; + + const longEvaluation = await router.evaluateRouting(longRequest); + expect(longEvaluation.agentType).toBe(AgentType.CODE_WORKER); // Default code rule + }); + + it('handles disabled rules correctly', async () => { + router = new RoutingPolicyService(tempDir); + + // First, let's verify the rule is working normally + const testRequest: RoutingRequest = { + prompt: 'implement a new authentication function' + }; + + const beforeDisabling = await router.evaluateRouting(testRequest); + expect(beforeDisabling.reasoning).toContain('Code Implementation'); + + // Now disable the code implementation rule + const rule = router.getRule('code-implementation'); + expect(rule).toBeDefined(); + + await router.updateRule('code-implementation', { + metadata: { + ...rule!.metadata, + enabled: false + } + }); + + // Verify the rule is disabled + const disabledRule = router.getRule('code-implementation'); + expect(disabledRule!.metadata.enabled).toBe(false); + + const evaluation = await router.evaluateRouting(testRequest); + + // Should not match the disabled rule, fall back to default routing + expect(evaluation.agentType).toBe(AgentType.CODE_WORKER); + expect(evaluation.reasoning).toContain('Code-related keywords detected'); + }); + + it('persists and loads rules correctly', async () => { + // Create first router instance + router = new RoutingPolicyService(tempDir); + + const customRule: Omit = { + id: 'persistent-rule', + name: 'Persistent Rule', + description: 'Rule that should persist', + precedence: 60, + conditions: { + keywords: ['persist'] + }, + target: AgentType.TOPOLOGY_COORDINATOR, + confidence: 0.75 + }; + + await router.addRule(customRule); + + // Create second router instance (should load from file) + const router2 = new RoutingPolicyService(tempDir); + const loadedRule = router2.getRule('persistent-rule'); + + expect(loadedRule).toBeDefined(); + expect(loadedRule!.name).toBe('Persistent Rule'); + expect(loadedRule!.target).toBe(AgentType.TOPOLOGY_COORDINATOR); + expect(loadedRule!.metadata.created).toBeInstanceOf(Date); + }); + + it('tracks evaluation history', async () => { + router = new RoutingPolicyService(tempDir); + + const request1: RoutingRequest = { + prompt: 'implement function A' + }; + + const request2: RoutingRequest = { + prompt: 'analyze data B' + }; + + const eval1 = await router.evaluateRouting(request1); + const eval2 = await router.evaluateRouting(request2); + + const history = router.getEvaluationHistory(10); + expect(history).toHaveLength(2); + + // Check that both evaluations are in the history + const historyIds = history.map(h => h.metadata.evaluationId); + expect(historyIds).toContain(eval1.metadata.evaluationId); + expect(historyIds).toContain(eval2.metadata.evaluationId); + + // Check that we have the right agent types + const agentTypes = history.map(h => h.agentType); + expect(agentTypes).toContain(AgentType.CODE_WORKER); + expect(agentTypes).toContain(AgentType.DATA_WORKER); + }); + + it('handles routing errors gracefully', async () => { + router = new RoutingPolicyService(tempDir); + + // Create a request that would cause an error in rule processing + const request: RoutingRequest = { + prompt: 'normal request' + }; + + // Mock an error by corrupting the rules + (router as any).rules.set('bad-rule', { + // Incomplete rule that might cause errors + id: 'bad-rule', + conditions: null + }); + + const evaluation = await router.evaluateRouting(request); + + // Should still return a valid evaluation (fallback) + expect(evaluation.agentType).toBeDefined(); + expect(evaluation.confidence).toBeGreaterThan(0); + expect(evaluation.metadata.evaluationId).toBeDefined(); + }); +}); \ No newline at end of file