synapsexcoder 6.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.opencode/opencode.jsonc +102 -0
- package/README.md +353 -0
- package/dist/agents/agent-config-manager.d.ts +58 -0
- package/dist/agents/agent-config-manager.d.ts.map +1 -0
- package/dist/agents/agent-config-manager.js +313 -0
- package/dist/agents/agent-config-manager.js.map +1 -0
- package/dist/agents/base-agents.d.ts +352 -0
- package/dist/agents/base-agents.d.ts.map +1 -0
- package/dist/agents/base-agents.js +3961 -0
- package/dist/agents/base-agents.js.map +1 -0
- package/dist/agents/gated-subagent.d.ts +126 -0
- package/dist/agents/gated-subagent.d.ts.map +1 -0
- package/dist/agents/gated-subagent.js +591 -0
- package/dist/agents/gated-subagent.js.map +1 -0
- package/dist/agents/gated-subagents.d.ts +130 -0
- package/dist/agents/gated-subagents.d.ts.map +1 -0
- package/dist/agents/gated-subagents.js +2014 -0
- package/dist/agents/gated-subagents.js.map +1 -0
- package/dist/agents/internal-gatekeeper.d.ts +167 -0
- package/dist/agents/internal-gatekeeper.d.ts.map +1 -0
- package/dist/agents/internal-gatekeeper.js +1130 -0
- package/dist/agents/internal-gatekeeper.js.map +1 -0
- package/dist/agents/verification-agent.d.ts +86 -0
- package/dist/agents/verification-agent.d.ts.map +1 -0
- package/dist/agents/verification-agent.js +211 -0
- package/dist/agents/verification-agent.js.map +1 -0
- package/dist/analytics/analytics-types.d.ts +113 -0
- package/dist/analytics/analytics-types.d.ts.map +1 -0
- package/dist/analytics/analytics-types.js +8 -0
- package/dist/analytics/analytics-types.js.map +1 -0
- package/dist/analytics/dashboard-generator.d.ts +35 -0
- package/dist/analytics/dashboard-generator.d.ts.map +1 -0
- package/dist/analytics/dashboard-generator.js +365 -0
- package/dist/analytics/dashboard-generator.js.map +1 -0
- package/dist/analytics/index.d.ts +12 -0
- package/dist/analytics/index.d.ts.map +1 -0
- package/dist/analytics/index.js +12 -0
- package/dist/analytics/index.js.map +1 -0
- package/dist/analytics/metrics-aggregator.d.ts +88 -0
- package/dist/analytics/metrics-aggregator.d.ts.map +1 -0
- package/dist/analytics/metrics-aggregator.js +280 -0
- package/dist/analytics/metrics-aggregator.js.map +1 -0
- package/dist/cli/index.d.ts +36 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +2677 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/normalize_patch.d.ts +3 -0
- package/dist/cli/normalize_patch.d.ts.map +1 -0
- package/dist/cli/normalize_patch.js +34 -0
- package/dist/cli/normalize_patch.js.map +1 -0
- package/dist/commands/command-processor.d.ts +58 -0
- package/dist/commands/command-processor.d.ts.map +1 -0
- package/dist/commands/command-processor.js +796 -0
- package/dist/commands/command-processor.js.map +1 -0
- package/dist/config/compliance-checker.d.ts +93 -0
- package/dist/config/compliance-checker.d.ts.map +1 -0
- package/dist/config/compliance-checker.js +424 -0
- package/dist/config/compliance-checker.js.map +1 -0
- package/dist/config/enterprise-config.d.ts +173 -0
- package/dist/config/enterprise-config.d.ts.map +1 -0
- package/dist/config/enterprise-config.js +190 -0
- package/dist/config/enterprise-config.js.map +1 -0
- package/dist/config/index.d.ts +13 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +11 -0
- package/dist/config/index.js.map +1 -0
- package/dist/context/context-system.d.ts +97 -0
- package/dist/context/context-system.d.ts.map +1 -0
- package/dist/context/context-system.js +880 -0
- package/dist/context/context-system.js.map +1 -0
- package/dist/context/store.d.ts +123 -0
- package/dist/context/store.d.ts.map +1 -0
- package/dist/context/store.js +281 -0
- package/dist/context/store.js.map +1 -0
- package/dist/dasp/dasp-controller.d.ts +83 -0
- package/dist/dasp/dasp-controller.d.ts.map +1 -0
- package/dist/dasp/dasp-controller.js +190 -0
- package/dist/dasp/dasp-controller.js.map +1 -0
- package/dist/dasp/feedback-adapter.d.ts +56 -0
- package/dist/dasp/feedback-adapter.d.ts.map +1 -0
- package/dist/dasp/feedback-adapter.js +158 -0
- package/dist/dasp/feedback-adapter.js.map +1 -0
- package/dist/dasp/index.d.ts +14 -0
- package/dist/dasp/index.d.ts.map +1 -0
- package/dist/dasp/index.js +10 -0
- package/dist/dasp/index.js.map +1 -0
- package/dist/dasp/prompt-templates.d.ts +38 -0
- package/dist/dasp/prompt-templates.d.ts.map +1 -0
- package/dist/dasp/prompt-templates.js +406 -0
- package/dist/dasp/prompt-templates.js.map +1 -0
- package/dist/dasp/vault-rag-provider.d.ts +51 -0
- package/dist/dasp/vault-rag-provider.d.ts.map +1 -0
- package/dist/dasp/vault-rag-provider.js +125 -0
- package/dist/dasp/vault-rag-provider.js.map +1 -0
- package/dist/distribution/cli-distribution.d.ts +68 -0
- package/dist/distribution/cli-distribution.d.ts.map +1 -0
- package/dist/distribution/cli-distribution.js +941 -0
- package/dist/distribution/cli-distribution.js.map +1 -0
- package/dist/docs/doc-generator.d.ts +78 -0
- package/dist/docs/doc-generator.d.ts.map +1 -0
- package/dist/docs/doc-generator.js +297 -0
- package/dist/docs/doc-generator.js.map +1 -0
- package/dist/docs/index.d.ts +13 -0
- package/dist/docs/index.d.ts.map +1 -0
- package/dist/docs/index.js +11 -0
- package/dist/docs/index.js.map +1 -0
- package/dist/docs/site-builder.d.ts +58 -0
- package/dist/docs/site-builder.d.ts.map +1 -0
- package/dist/docs/site-builder.js +229 -0
- package/dist/docs/site-builder.js.map +1 -0
- package/dist/ecosystem/adapters/claude-adapter.d.ts +29 -0
- package/dist/ecosystem/adapters/claude-adapter.d.ts.map +1 -0
- package/dist/ecosystem/adapters/claude-adapter.js +116 -0
- package/dist/ecosystem/adapters/claude-adapter.js.map +1 -0
- package/dist/ecosystem/adapters/cursor-adapter.d.ts +27 -0
- package/dist/ecosystem/adapters/cursor-adapter.d.ts.map +1 -0
- package/dist/ecosystem/adapters/cursor-adapter.js +93 -0
- package/dist/ecosystem/adapters/cursor-adapter.js.map +1 -0
- package/dist/ecosystem/adapters/v0-adapter.d.ts +30 -0
- package/dist/ecosystem/adapters/v0-adapter.d.ts.map +1 -0
- package/dist/ecosystem/adapters/v0-adapter.js +112 -0
- package/dist/ecosystem/adapters/v0-adapter.js.map +1 -0
- package/dist/ecosystem/ecosystem-router.d.ts +80 -0
- package/dist/ecosystem/ecosystem-router.d.ts.map +1 -0
- package/dist/ecosystem/ecosystem-router.js +241 -0
- package/dist/ecosystem/ecosystem-router.js.map +1 -0
- package/dist/ecosystem/ecosystem-types.d.ts +94 -0
- package/dist/ecosystem/ecosystem-types.d.ts.map +1 -0
- package/dist/ecosystem/ecosystem-types.js +27 -0
- package/dist/ecosystem/ecosystem-types.js.map +1 -0
- package/dist/ecosystem/index.d.ts +10 -0
- package/dist/ecosystem/index.d.ts.map +1 -0
- package/dist/ecosystem/index.js +9 -0
- package/dist/ecosystem/index.js.map +1 -0
- package/dist/integration/agentic-integration.d.ts +73 -0
- package/dist/integration/agentic-integration.d.ts.map +1 -0
- package/dist/integration/agentic-integration.js +253 -0
- package/dist/integration/agentic-integration.js.map +1 -0
- package/dist/integration/background-agents-integration.d.ts +54 -0
- package/dist/integration/background-agents-integration.d.ts.map +1 -0
- package/dist/integration/background-agents-integration.js +225 -0
- package/dist/integration/background-agents-integration.js.map +1 -0
- package/dist/integration/dcp-integration.d.ts +81 -0
- package/dist/integration/dcp-integration.d.ts.map +1 -0
- package/dist/integration/dcp-integration.js +189 -0
- package/dist/integration/dcp-integration.js.map +1 -0
- package/dist/integration/firecrawl-integration.d.ts +61 -0
- package/dist/integration/firecrawl-integration.d.ts.map +1 -0
- package/dist/integration/firecrawl-integration.js +246 -0
- package/dist/integration/firecrawl-integration.js.map +1 -0
- package/dist/integration/index.d.ts +40 -0
- package/dist/integration/index.d.ts.map +1 -0
- package/dist/integration/index.js +43 -0
- package/dist/integration/index.js.map +1 -0
- package/dist/integration/integration-hub.d.ts +43 -0
- package/dist/integration/integration-hub.d.ts.map +1 -0
- package/dist/integration/integration-hub.js +507 -0
- package/dist/integration/integration-hub.js.map +1 -0
- package/dist/integration/integration-loader.d.ts +42 -0
- package/dist/integration/integration-loader.d.ts.map +1 -0
- package/dist/integration/integration-loader.js +240 -0
- package/dist/integration/integration-loader.js.map +1 -0
- package/dist/integration/md-table-formatter-integration.d.ts +41 -0
- package/dist/integration/md-table-formatter-integration.d.ts.map +1 -0
- package/dist/integration/md-table-formatter-integration.js +183 -0
- package/dist/integration/md-table-formatter-integration.js.map +1 -0
- package/dist/integration/native/agentic/agentic-engine.d.ts +52 -0
- package/dist/integration/native/agentic/agentic-engine.d.ts.map +1 -0
- package/dist/integration/native/agentic/agentic-engine.js +267 -0
- package/dist/integration/native/agentic/agentic-engine.js.map +1 -0
- package/dist/integration/native/background/background-engine.d.ts +62 -0
- package/dist/integration/native/background/background-engine.d.ts.map +1 -0
- package/dist/integration/native/background/background-engine.js +167 -0
- package/dist/integration/native/background/background-engine.js.map +1 -0
- package/dist/integration/native/dcp/dcp-engine.d.ts +55 -0
- package/dist/integration/native/dcp/dcp-engine.d.ts.map +1 -0
- package/dist/integration/native/dcp/dcp-engine.js +168 -0
- package/dist/integration/native/dcp/dcp-engine.js.map +1 -0
- package/dist/integration/native/firecrawl/firecrawl-engine.d.ts +66 -0
- package/dist/integration/native/firecrawl/firecrawl-engine.d.ts.map +1 -0
- package/dist/integration/native/firecrawl/firecrawl-engine.js +221 -0
- package/dist/integration/native/firecrawl/firecrawl-engine.js.map +1 -0
- package/dist/integration/native/formatter/formatter-engine.d.ts +47 -0
- package/dist/integration/native/formatter/formatter-engine.d.ts.map +1 -0
- package/dist/integration/native/formatter/formatter-engine.js +130 -0
- package/dist/integration/native/formatter/formatter-engine.js.map +1 -0
- package/dist/integration/native/index.d.ts +41 -0
- package/dist/integration/native/index.d.ts.map +1 -0
- package/dist/integration/native/index.js +80 -0
- package/dist/integration/native/index.js.map +1 -0
- package/dist/integration/native/orchestration/orchestration-engine.d.ts +62 -0
- package/dist/integration/native/orchestration/orchestration-engine.d.ts.map +1 -0
- package/dist/integration/native/orchestration/orchestration-engine.js +177 -0
- package/dist/integration/native/orchestration/orchestration-engine.js.map +1 -0
- package/dist/integration/native/pty/pty-engine.d.ts +45 -0
- package/dist/integration/native/pty/pty-engine.d.ts.map +1 -0
- package/dist/integration/native/pty/pty-engine.js +103 -0
- package/dist/integration/native/pty/pty-engine.js.map +1 -0
- package/dist/integration/native/shell-strategy.d.ts +60 -0
- package/dist/integration/native/shell-strategy.d.ts.map +1 -0
- package/dist/integration/native/shell-strategy.js +131 -0
- package/dist/integration/native/shell-strategy.js.map +1 -0
- package/dist/integration/native/skillful/skillful-engine.d.ts +53 -0
- package/dist/integration/native/skillful/skillful-engine.d.ts.map +1 -0
- package/dist/integration/native/skillful/skillful-engine.js +127 -0
- package/dist/integration/native/skillful/skillful-engine.js.map +1 -0
- package/dist/integration/native/subtask2/subtask2-engine.d.ts +50 -0
- package/dist/integration/native/subtask2/subtask2-engine.d.ts.map +1 -0
- package/dist/integration/native/subtask2/subtask2-engine.js +158 -0
- package/dist/integration/native/subtask2/subtask2-engine.js.map +1 -0
- package/dist/integration/native/supermemory/supermemory-engine.d.ts +63 -0
- package/dist/integration/native/supermemory/supermemory-engine.d.ts.map +1 -0
- package/dist/integration/native/supermemory/supermemory-engine.js +127 -0
- package/dist/integration/native/supermemory/supermemory-engine.js.map +1 -0
- package/dist/integration/native/workspace/workspace-engine.d.ts +75 -0
- package/dist/integration/native/workspace/workspace-engine.d.ts.map +1 -0
- package/dist/integration/native/workspace/workspace-engine.js +216 -0
- package/dist/integration/native/workspace/workspace-engine.js.map +1 -0
- package/dist/integration/oh-my-opencode-integration.d.ts +59 -0
- package/dist/integration/oh-my-opencode-integration.d.ts.map +1 -0
- package/dist/integration/oh-my-opencode-integration.js +180 -0
- package/dist/integration/oh-my-opencode-integration.js.map +1 -0
- package/dist/integration/openagents-control-integration.d.ts +81 -0
- package/dist/integration/openagents-control-integration.d.ts.map +1 -0
- package/dist/integration/openagents-control-integration.js +273 -0
- package/dist/integration/openagents-control-integration.js.map +1 -0
- package/dist/integration/pty-integration.d.ts +64 -0
- package/dist/integration/pty-integration.d.ts.map +1 -0
- package/dist/integration/pty-integration.js +180 -0
- package/dist/integration/pty-integration.js.map +1 -0
- package/dist/integration/shell-strategy-integration.d.ts +26 -0
- package/dist/integration/shell-strategy-integration.d.ts.map +1 -0
- package/dist/integration/shell-strategy-integration.js +110 -0
- package/dist/integration/shell-strategy-integration.js.map +1 -0
- package/dist/integration/skillful-integration.d.ts +74 -0
- package/dist/integration/skillful-integration.d.ts.map +1 -0
- package/dist/integration/skillful-integration.js +317 -0
- package/dist/integration/skillful-integration.js.map +1 -0
- package/dist/integration/subtask2-integration.d.ts +71 -0
- package/dist/integration/subtask2-integration.d.ts.map +1 -0
- package/dist/integration/subtask2-integration.js +240 -0
- package/dist/integration/subtask2-integration.js.map +1 -0
- package/dist/integration/supermemory-integration.d.ts +82 -0
- package/dist/integration/supermemory-integration.d.ts.map +1 -0
- package/dist/integration/supermemory-integration.js +252 -0
- package/dist/integration/supermemory-integration.js.map +1 -0
- package/dist/integration/types.d.ts +218 -0
- package/dist/integration/types.d.ts.map +1 -0
- package/dist/integration/types.js +5 -0
- package/dist/integration/types.js.map +1 -0
- package/dist/integration/workspace-integration.d.ts +46 -0
- package/dist/integration/workspace-integration.d.ts.map +1 -0
- package/dist/integration/workspace-integration.js +181 -0
- package/dist/integration/workspace-integration.js.map +1 -0
- package/dist/knowledge-vault/deepwiki-sync.d.ts +99 -0
- package/dist/knowledge-vault/deepwiki-sync.d.ts.map +1 -0
- package/dist/knowledge-vault/deepwiki-sync.js +381 -0
- package/dist/knowledge-vault/deepwiki-sync.js.map +1 -0
- package/dist/knowledge-vault/index.d.ts +11 -0
- package/dist/knowledge-vault/index.d.ts.map +1 -0
- package/dist/knowledge-vault/index.js +8 -0
- package/dist/knowledge-vault/index.js.map +1 -0
- package/dist/knowledge-vault/knowledge-vault.d.ts +38 -0
- package/dist/knowledge-vault/knowledge-vault.d.ts.map +1 -0
- package/dist/knowledge-vault/knowledge-vault.js +284 -0
- package/dist/knowledge-vault/knowledge-vault.js.map +1 -0
- package/dist/knowledge-vault/opencode-doc-ingest.d.ts +23 -0
- package/dist/knowledge-vault/opencode-doc-ingest.d.ts.map +1 -0
- package/dist/knowledge-vault/opencode-doc-ingest.js +48 -0
- package/dist/knowledge-vault/opencode-doc-ingest.js.map +1 -0
- package/dist/knowledge-vault/types.d.ts +61 -0
- package/dist/knowledge-vault/types.d.ts.map +1 -0
- package/dist/knowledge-vault/types.js +5 -0
- package/dist/knowledge-vault/types.js.map +1 -0
- package/dist/knowledge-vault/vault-config.d.ts +12 -0
- package/dist/knowledge-vault/vault-config.d.ts.map +1 -0
- package/dist/knowledge-vault/vault-config.js +24 -0
- package/dist/knowledge-vault/vault-config.js.map +1 -0
- package/dist/mcp/index.d.ts +28 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +29 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/mcp-manager.d.ts +181 -0
- package/dist/mcp/mcp-manager.d.ts.map +1 -0
- package/dist/mcp/mcp-manager.js +621 -0
- package/dist/mcp/mcp-manager.js.map +1 -0
- package/dist/mcp/mcp-types.d.ts +199 -0
- package/dist/mcp/mcp-types.d.ts.map +1 -0
- package/dist/mcp/mcp-types.js +152 -0
- package/dist/mcp/mcp-types.js.map +1 -0
- package/dist/mcp/servers/mcp-server-bridge.d.ts +64 -0
- package/dist/mcp/servers/mcp-server-bridge.d.ts.map +1 -0
- package/dist/mcp/servers/mcp-server-bridge.js +189 -0
- package/dist/mcp/servers/mcp-server-bridge.js.map +1 -0
- package/dist/messages/message-bus.d.ts +148 -0
- package/dist/messages/message-bus.d.ts.map +1 -0
- package/dist/messages/message-bus.js +432 -0
- package/dist/messages/message-bus.js.map +1 -0
- package/dist/modes/custom-mode-registry.d.ts +121 -0
- package/dist/modes/custom-mode-registry.d.ts.map +1 -0
- package/dist/modes/custom-mode-registry.js +306 -0
- package/dist/modes/custom-mode-registry.js.map +1 -0
- package/dist/modes/index.d.ts +10 -0
- package/dist/modes/index.d.ts.map +1 -0
- package/dist/modes/index.js +8 -0
- package/dist/modes/index.js.map +1 -0
- package/dist/modes/mode-designer.d.ts +89 -0
- package/dist/modes/mode-designer.d.ts.map +1 -0
- package/dist/modes/mode-designer.js +485 -0
- package/dist/modes/mode-designer.js.map +1 -0
- package/dist/modes/mode-manager.d.ts +27 -0
- package/dist/modes/mode-manager.d.ts.map +1 -0
- package/dist/modes/mode-manager.js +68 -0
- package/dist/modes/mode-manager.js.map +1 -0
- package/dist/modes/synapse-modes.d.ts +26 -0
- package/dist/modes/synapse-modes.d.ts.map +1 -0
- package/dist/modes/synapse-modes.js +69 -0
- package/dist/modes/synapse-modes.js.map +1 -0
- package/dist/opencode/agent-delegate.d.ts +3 -0
- package/dist/opencode/agent-delegate.d.ts.map +1 -0
- package/dist/opencode/agent-delegate.js +3 -0
- package/dist/opencode/agent-delegate.js.map +1 -0
- package/dist/opencode/tool-bridge.d.ts +3 -0
- package/dist/opencode/tool-bridge.d.ts.map +1 -0
- package/dist/opencode/tool-bridge.js +3 -0
- package/dist/opencode/tool-bridge.js.map +1 -0
- package/dist/optimization/token-optimizer-v2.d.ts +18 -0
- package/dist/optimization/token-optimizer-v2.d.ts.map +1 -0
- package/dist/optimization/token-optimizer-v2.js +23 -0
- package/dist/optimization/token-optimizer-v2.js.map +1 -0
- package/dist/optimization/token-optimizer.d.ts +90 -0
- package/dist/optimization/token-optimizer.d.ts.map +1 -0
- package/dist/optimization/token-optimizer.js +399 -0
- package/dist/optimization/token-optimizer.js.map +1 -0
- package/dist/parallel/agent-farm.d.ts +123 -0
- package/dist/parallel/agent-farm.d.ts.map +1 -0
- package/dist/parallel/agent-farm.js +501 -0
- package/dist/parallel/agent-farm.js.map +1 -0
- package/dist/parallel/farm-scheduler.d.ts +115 -0
- package/dist/parallel/farm-scheduler.d.ts.map +1 -0
- package/dist/parallel/farm-scheduler.js +356 -0
- package/dist/parallel/farm-scheduler.js.map +1 -0
- package/dist/parallel/farm-types.d.ts +104 -0
- package/dist/parallel/farm-types.d.ts.map +1 -0
- package/dist/parallel/farm-types.js +9 -0
- package/dist/parallel/farm-types.js.map +1 -0
- package/dist/parallel/farm-worker.d.ts +62 -0
- package/dist/parallel/farm-worker.d.ts.map +1 -0
- package/dist/parallel/farm-worker.js +268 -0
- package/dist/parallel/farm-worker.js.map +1 -0
- package/dist/parallel/index.d.ts +14 -0
- package/dist/parallel/index.d.ts.map +1 -0
- package/dist/parallel/index.js +14 -0
- package/dist/parallel/index.js.map +1 -0
- package/dist/plugin/native-tools.d.ts +8 -0
- package/dist/plugin/native-tools.d.ts.map +1 -0
- package/dist/plugin/native-tools.js +147 -0
- package/dist/plugin/native-tools.js.map +1 -0
- package/dist/plugin/opencode-plugin.d.ts +32 -0
- package/dist/plugin/opencode-plugin.d.ts.map +1 -0
- package/dist/plugin/opencode-plugin.js +119 -0
- package/dist/plugin/opencode-plugin.js.map +1 -0
- package/dist/plugins/plugin-system.d.ts +108 -0
- package/dist/plugins/plugin-system.d.ts.map +1 -0
- package/dist/plugins/plugin-system.js +707 -0
- package/dist/plugins/plugin-system.js.map +1 -0
- package/dist/profiler/cpu-profiler.d.ts +53 -0
- package/dist/profiler/cpu-profiler.d.ts.map +1 -0
- package/dist/profiler/cpu-profiler.js +233 -0
- package/dist/profiler/cpu-profiler.js.map +1 -0
- package/dist/profiler/index.d.ts +36 -0
- package/dist/profiler/index.d.ts.map +1 -0
- package/dist/profiler/index.js +122 -0
- package/dist/profiler/index.js.map +1 -0
- package/dist/profiler/memory-profiler.d.ts +45 -0
- package/dist/profiler/memory-profiler.d.ts.map +1 -0
- package/dist/profiler/memory-profiler.js +211 -0
- package/dist/profiler/memory-profiler.js.map +1 -0
- package/dist/profiler/profiler-types.d.ts +234 -0
- package/dist/profiler/profiler-types.d.ts.map +1 -0
- package/dist/profiler/profiler-types.js +89 -0
- package/dist/profiler/profiler-types.js.map +1 -0
- package/dist/profiler/query-profiler.d.ts +48 -0
- package/dist/profiler/query-profiler.d.ts.map +1 -0
- package/dist/profiler/query-profiler.js +210 -0
- package/dist/profiler/query-profiler.js.map +1 -0
- package/dist/profiler/report-generator.d.ts +17 -0
- package/dist/profiler/report-generator.d.ts.map +1 -0
- package/dist/profiler/report-generator.js +329 -0
- package/dist/profiler/report-generator.js.map +1 -0
- package/dist/sdk/api.d.ts +85 -0
- package/dist/sdk/api.d.ts.map +1 -0
- package/dist/sdk/api.js +155 -0
- package/dist/sdk/api.js.map +1 -0
- package/dist/sdk/hooks.d.ts +58 -0
- package/dist/sdk/hooks.d.ts.map +1 -0
- package/dist/sdk/hooks.js +115 -0
- package/dist/sdk/hooks.js.map +1 -0
- package/dist/sdk/index.d.ts +17 -0
- package/dist/sdk/index.d.ts.map +1 -0
- package/dist/sdk/index.js +14 -0
- package/dist/sdk/index.js.map +1 -0
- package/dist/sdk/plugin-base.d.ts +198 -0
- package/dist/sdk/plugin-base.d.ts.map +1 -0
- package/dist/sdk/plugin-base.js +227 -0
- package/dist/sdk/plugin-base.js.map +1 -0
- package/dist/security/audit-rules.d.ts +12 -0
- package/dist/security/audit-rules.d.ts.map +1 -0
- package/dist/security/audit-rules.js +214 -0
- package/dist/security/audit-rules.js.map +1 -0
- package/dist/security/index.d.ts +13 -0
- package/dist/security/index.d.ts.map +1 -0
- package/dist/security/index.js +13 -0
- package/dist/security/index.js.map +1 -0
- package/dist/security/sarif-exporter.d.ts +36 -0
- package/dist/security/sarif-exporter.d.ts.map +1 -0
- package/dist/security/sarif-exporter.js +216 -0
- package/dist/security/sarif-exporter.js.map +1 -0
- package/dist/security/security-auditor.d.ts +30 -0
- package/dist/security/security-auditor.d.ts.map +1 -0
- package/dist/security/security-auditor.js +295 -0
- package/dist/security/security-auditor.js.map +1 -0
- package/dist/security/security-types.d.ts +132 -0
- package/dist/security/security-types.d.ts.map +1 -0
- package/dist/security/security-types.js +7 -0
- package/dist/security/security-types.js.map +1 -0
- package/dist/synapsexcoder/agent-delegate.d.ts +128 -0
- package/dist/synapsexcoder/agent-delegate.d.ts.map +1 -0
- package/dist/synapsexcoder/agent-delegate.js +837 -0
- package/dist/synapsexcoder/agent-delegate.js.map +1 -0
- package/dist/synapsexcoder/agent-mailbox.d.ts +26 -0
- package/dist/synapsexcoder/agent-mailbox.d.ts.map +1 -0
- package/dist/synapsexcoder/agent-mailbox.js +64 -0
- package/dist/synapsexcoder/agent-mailbox.js.map +1 -0
- package/dist/synapsexcoder/ast-grep/ast-grep-api.d.ts +62 -0
- package/dist/synapsexcoder/ast-grep/ast-grep-api.d.ts.map +1 -0
- package/dist/synapsexcoder/ast-grep/ast-grep-api.js +223 -0
- package/dist/synapsexcoder/ast-grep/ast-grep-api.js.map +1 -0
- package/dist/synapsexcoder/cited-search.d.ts +32 -0
- package/dist/synapsexcoder/cited-search.d.ts.map +1 -0
- package/dist/synapsexcoder/cited-search.js +141 -0
- package/dist/synapsexcoder/cited-search.js.map +1 -0
- package/dist/synapsexcoder/credential-store.d.ts +42 -0
- package/dist/synapsexcoder/credential-store.d.ts.map +1 -0
- package/dist/synapsexcoder/credential-store.js +165 -0
- package/dist/synapsexcoder/credential-store.js.map +1 -0
- package/dist/synapsexcoder/devcontainer-awareness.d.ts +35 -0
- package/dist/synapsexcoder/devcontainer-awareness.d.ts.map +1 -0
- package/dist/synapsexcoder/devcontainer-awareness.js +133 -0
- package/dist/synapsexcoder/devcontainer-awareness.js.map +1 -0
- package/dist/synapsexcoder/execution-loops/helix-loop.d.ts +31 -0
- package/dist/synapsexcoder/execution-loops/helix-loop.d.ts.map +1 -0
- package/dist/synapsexcoder/execution-loops/helix-loop.js +93 -0
- package/dist/synapsexcoder/execution-loops/helix-loop.js.map +1 -0
- package/dist/synapsexcoder/execution-loops/ralp-loop.d.ts +28 -0
- package/dist/synapsexcoder/execution-loops/ralp-loop.d.ts.map +1 -0
- package/dist/synapsexcoder/execution-loops/ralp-loop.js +77 -0
- package/dist/synapsexcoder/execution-loops/ralp-loop.js.map +1 -0
- package/dist/synapsexcoder/feature-names.d.ts +27 -0
- package/dist/synapsexcoder/feature-names.d.ts.map +1 -0
- package/dist/synapsexcoder/feature-names.js +40 -0
- package/dist/synapsexcoder/feature-names.js.map +1 -0
- package/dist/synapsexcoder/index.d.ts +39 -0
- package/dist/synapsexcoder/index.d.ts.map +1 -0
- package/dist/synapsexcoder/index.js +23 -0
- package/dist/synapsexcoder/index.js.map +1 -0
- package/dist/synapsexcoder/knowledge-provider.d.ts +44 -0
- package/dist/synapsexcoder/knowledge-provider.d.ts.map +1 -0
- package/dist/synapsexcoder/knowledge-provider.js +107 -0
- package/dist/synapsexcoder/knowledge-provider.js.map +1 -0
- package/dist/synapsexcoder/lsp/lsp-layer.d.ts +51 -0
- package/dist/synapsexcoder/lsp/lsp-layer.d.ts.map +1 -0
- package/dist/synapsexcoder/lsp/lsp-layer.js +302 -0
- package/dist/synapsexcoder/lsp/lsp-layer.js.map +1 -0
- package/dist/synapsexcoder/migration/opencode-config-migrator.d.ts +37 -0
- package/dist/synapsexcoder/migration/opencode-config-migrator.d.ts.map +1 -0
- package/dist/synapsexcoder/migration/opencode-config-migrator.js +100 -0
- package/dist/synapsexcoder/migration/opencode-config-migrator.js.map +1 -0
- package/dist/synapsexcoder/runtime-environment.d.ts +35 -0
- package/dist/synapsexcoder/runtime-environment.d.ts.map +1 -0
- package/dist/synapsexcoder/runtime-environment.js +108 -0
- package/dist/synapsexcoder/runtime-environment.js.map +1 -0
- package/dist/synapsexcoder/secret-guard.d.ts +18 -0
- package/dist/synapsexcoder/secret-guard.d.ts.map +1 -0
- package/dist/synapsexcoder/secret-guard.js +69 -0
- package/dist/synapsexcoder/secret-guard.js.map +1 -0
- package/dist/synapsexcoder/self-prompt-engine.d.ts +15 -0
- package/dist/synapsexcoder/self-prompt-engine.d.ts.map +1 -0
- package/dist/synapsexcoder/self-prompt-engine.js +11 -0
- package/dist/synapsexcoder/self-prompt-engine.js.map +1 -0
- package/dist/synapsexcoder/session-observability.d.ts +44 -0
- package/dist/synapsexcoder/session-observability.d.ts.map +1 -0
- package/dist/synapsexcoder/session-observability.js +115 -0
- package/dist/synapsexcoder/session-observability.js.map +1 -0
- package/dist/synapsexcoder/specialist-agents.d.ts +38 -0
- package/dist/synapsexcoder/specialist-agents.d.ts.map +1 -0
- package/dist/synapsexcoder/specialist-agents.js +192 -0
- package/dist/synapsexcoder/specialist-agents.js.map +1 -0
- package/dist/synapsexcoder/swarm/kanban-board.d.ts +34 -0
- package/dist/synapsexcoder/swarm/kanban-board.d.ts.map +1 -0
- package/dist/synapsexcoder/swarm/kanban-board.js +85 -0
- package/dist/synapsexcoder/swarm/kanban-board.js.map +1 -0
- package/dist/synapsexcoder/swarm/swarm-mailbox.d.ts +38 -0
- package/dist/synapsexcoder/swarm/swarm-mailbox.d.ts.map +1 -0
- package/dist/synapsexcoder/swarm/swarm-mailbox.js +93 -0
- package/dist/synapsexcoder/swarm/swarm-mailbox.js.map +1 -0
- package/dist/synapsexcoder/tool-bridge.d.ts +277 -0
- package/dist/synapsexcoder/tool-bridge.d.ts.map +1 -0
- package/dist/synapsexcoder/tool-bridge.js +1356 -0
- package/dist/synapsexcoder/tool-bridge.js.map +1 -0
- package/dist/synapsexcoder/tool-routing.d.ts +28 -0
- package/dist/synapsexcoder/tool-routing.d.ts.map +1 -0
- package/dist/synapsexcoder/tool-routing.js +79 -0
- package/dist/synapsexcoder/tool-routing.js.map +1 -0
- package/dist/synapsexcoder/type-injector.d.ts +26 -0
- package/dist/synapsexcoder/type-injector.d.ts.map +1 -0
- package/dist/synapsexcoder/type-injector.js +124 -0
- package/dist/synapsexcoder/type-injector.js.map +1 -0
- package/dist/synapsexcoder/utils/fuzzy-match.d.ts +25 -0
- package/dist/synapsexcoder/utils/fuzzy-match.d.ts.map +1 -0
- package/dist/synapsexcoder/utils/fuzzy-match.js +83 -0
- package/dist/synapsexcoder/utils/fuzzy-match.js.map +1 -0
- package/dist/synapsexcoder/vault-crawl.d.ts +29 -0
- package/dist/synapsexcoder/vault-crawl.d.ts.map +1 -0
- package/dist/synapsexcoder/vault-crawl.js +103 -0
- package/dist/synapsexcoder/vault-crawl.js.map +1 -0
- package/dist/synapsexcoder/verified-apply.d.ts +39 -0
- package/dist/synapsexcoder/verified-apply.d.ts.map +1 -0
- package/dist/synapsexcoder/verified-apply.js +81 -0
- package/dist/synapsexcoder/verified-apply.js.map +1 -0
- package/dist/synapsexcoder/verified-context-editing.d.ts +34 -0
- package/dist/synapsexcoder/verified-context-editing.d.ts.map +1 -0
- package/dist/synapsexcoder/verified-context-editing.js +80 -0
- package/dist/synapsexcoder/verified-context-editing.js.map +1 -0
- package/dist/synapsexcoder/worker-pool.d.ts +47 -0
- package/dist/synapsexcoder/worker-pool.d.ts.map +1 -0
- package/dist/synapsexcoder/worker-pool.js +120 -0
- package/dist/synapsexcoder/worker-pool.js.map +1 -0
- package/dist/synapsexcoder/workspace-intelligence.d.ts +35 -0
- package/dist/synapsexcoder/workspace-intelligence.d.ts.map +1 -0
- package/dist/synapsexcoder/workspace-intelligence.js +126 -0
- package/dist/synapsexcoder/workspace-intelligence.js.map +1 -0
- package/dist/synapsexcoder/worktree-manager.d.ts +31 -0
- package/dist/synapsexcoder/worktree-manager.d.ts.map +1 -0
- package/dist/synapsexcoder/worktree-manager.js +100 -0
- package/dist/synapsexcoder/worktree-manager.js.map +1 -0
- package/dist/team/index.d.ts +17 -0
- package/dist/team/index.d.ts.map +1 -0
- package/dist/team/index.js +13 -0
- package/dist/team/index.js.map +1 -0
- package/dist/team/team-audit.d.ts +120 -0
- package/dist/team/team-audit.d.ts.map +1 -0
- package/dist/team/team-audit.js +357 -0
- package/dist/team/team-audit.js.map +1 -0
- package/dist/team/team-collaboration.d.ts +150 -0
- package/dist/team/team-collaboration.d.ts.map +1 -0
- package/dist/team/team-collaboration.js +495 -0
- package/dist/team/team-collaboration.js.map +1 -0
- package/dist/team/team-rbac.d.ts +84 -0
- package/dist/team/team-rbac.d.ts.map +1 -0
- package/dist/team/team-rbac.js +259 -0
- package/dist/team/team-rbac.js.map +1 -0
- package/dist/team/team-session-manager.d.ts +100 -0
- package/dist/team/team-session-manager.d.ts.map +1 -0
- package/dist/team/team-session-manager.js +255 -0
- package/dist/team/team-session-manager.js.map +1 -0
- package/dist/thoughts/thoughts-manager.d.ts +52 -0
- package/dist/thoughts/thoughts-manager.d.ts.map +1 -0
- package/dist/thoughts/thoughts-manager.js +271 -0
- package/dist/thoughts/thoughts-manager.js.map +1 -0
- package/dist/tools/api-validator/index.d.ts +97 -0
- package/dist/tools/api-validator/index.d.ts.map +1 -0
- package/dist/tools/api-validator/index.js +312 -0
- package/dist/tools/api-validator/index.js.map +1 -0
- package/dist/tools/code-archaeology/index.d.ts +193 -0
- package/dist/tools/code-archaeology/index.d.ts.map +1 -0
- package/dist/tools/code-archaeology/index.js +468 -0
- package/dist/tools/code-archaeology/index.js.map +1 -0
- package/dist/tools/codebase-search/index.d.ts +126 -0
- package/dist/tools/codebase-search/index.d.ts.map +1 -0
- package/dist/tools/codebase-search/index.js +437 -0
- package/dist/tools/codebase-search/index.js.map +1 -0
- package/dist/tools/context/index.d.ts +162 -0
- package/dist/tools/context/index.d.ts.map +1 -0
- package/dist/tools/context/index.js +332 -0
- package/dist/tools/context/index.js.map +1 -0
- package/dist/tools/deepwiki/analyzer.d.ts +167 -0
- package/dist/tools/deepwiki/analyzer.d.ts.map +1 -0
- package/dist/tools/deepwiki/analyzer.js +925 -0
- package/dist/tools/deepwiki/analyzer.js.map +1 -0
- package/dist/tools/deepwiki/extractor.d.ts +151 -0
- package/dist/tools/deepwiki/extractor.d.ts.map +1 -0
- package/dist/tools/deepwiki/extractor.js +923 -0
- package/dist/tools/deepwiki/extractor.js.map +1 -0
- package/dist/tools/deepwiki/generator.d.ts +89 -0
- package/dist/tools/deepwiki/generator.d.ts.map +1 -0
- package/dist/tools/deepwiki/generator.js +638 -0
- package/dist/tools/deepwiki/generator.js.map +1 -0
- package/dist/tools/deepwiki/index.d.ts +96 -0
- package/dist/tools/deepwiki/index.d.ts.map +1 -0
- package/dist/tools/deepwiki/index.js +282 -0
- package/dist/tools/deepwiki/index.js.map +1 -0
- package/dist/tools/deepwiki/types.d.ts +370 -0
- package/dist/tools/deepwiki/types.d.ts.map +1 -0
- package/dist/tools/deepwiki/types.js +21 -0
- package/dist/tools/deepwiki/types.js.map +1 -0
- package/dist/tools/dependency-mapper/index.d.ts +212 -0
- package/dist/tools/dependency-mapper/index.d.ts.map +1 -0
- package/dist/tools/dependency-mapper/index.js +592 -0
- package/dist/tools/dependency-mapper/index.js.map +1 -0
- package/dist/tools/memory/index.d.ts +120 -0
- package/dist/tools/memory/index.d.ts.map +1 -0
- package/dist/tools/memory/index.js +275 -0
- package/dist/tools/memory/index.js.map +1 -0
- package/dist/tools/normalize_patch.d.ts +3 -0
- package/dist/tools/normalize_patch.d.ts.map +1 -0
- package/dist/tools/normalize_patch.js +22 -0
- package/dist/tools/normalize_patch.js.map +1 -0
- package/dist/tools/pattern-detector/index.d.ts +105 -0
- package/dist/tools/pattern-detector/index.d.ts.map +1 -0
- package/dist/tools/pattern-detector/index.js +526 -0
- package/dist/tools/pattern-detector/index.js.map +1 -0
- package/dist/tools/performance-profiler/index.d.ts +50 -0
- package/dist/tools/performance-profiler/index.d.ts.map +1 -0
- package/dist/tools/performance-profiler/index.js +164 -0
- package/dist/tools/performance-profiler/index.js.map +1 -0
- package/dist/tools/refactoring-engine/index.d.ts +63 -0
- package/dist/tools/refactoring-engine/index.d.ts.map +1 -0
- package/dist/tools/refactoring-engine/index.js +270 -0
- package/dist/tools/refactoring-engine/index.js.map +1 -0
- package/dist/tools/registry.d.ts +36 -0
- package/dist/tools/registry.d.ts.map +1 -0
- package/dist/tools/registry.js +499 -0
- package/dist/tools/registry.js.map +1 -0
- package/dist/tools/test-generator/index.d.ts +152 -0
- package/dist/tools/test-generator/index.d.ts.map +1 -0
- package/dist/tools/test-generator/index.js +448 -0
- package/dist/tools/test-generator/index.js.map +1 -0
- package/dist/types/index.d.ts +565 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +7 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/path-validator.d.ts +94 -0
- package/dist/utils/path-validator.d.ts.map +1 -0
- package/dist/utils/path-validator.js +192 -0
- package/dist/utils/path-validator.js.map +1 -0
- package/dist/utils/secure-exec.d.ts +92 -0
- package/dist/utils/secure-exec.d.ts.map +1 -0
- package/dist/utils/secure-exec.js +230 -0
- package/dist/utils/secure-exec.js.map +1 -0
- package/dist/verification/claim-parser.d.ts +37 -0
- package/dist/verification/claim-parser.d.ts.map +1 -0
- package/dist/verification/claim-parser.js +231 -0
- package/dist/verification/claim-parser.js.map +1 -0
- package/dist/verification/cross-reference-engine.d.ts +55 -0
- package/dist/verification/cross-reference-engine.d.ts.map +1 -0
- package/dist/verification/cross-reference-engine.js +149 -0
- package/dist/verification/cross-reference-engine.js.map +1 -0
- package/dist/verification/discrepancy-tracker.d.ts +73 -0
- package/dist/verification/discrepancy-tracker.d.ts.map +1 -0
- package/dist/verification/discrepancy-tracker.js +196 -0
- package/dist/verification/discrepancy-tracker.js.map +1 -0
- package/dist/verification/feature-reconciler.d.ts +90 -0
- package/dist/verification/feature-reconciler.d.ts.map +1 -0
- package/dist/verification/feature-reconciler.js +264 -0
- package/dist/verification/feature-reconciler.js.map +1 -0
- package/dist/verification/index.d.ts +16 -0
- package/dist/verification/index.d.ts.map +1 -0
- package/dist/verification/index.js +19 -0
- package/dist/verification/index.js.map +1 -0
- package/dist/verification/integration-verifier.d.ts +66 -0
- package/dist/verification/integration-verifier.d.ts.map +1 -0
- package/dist/verification/integration-verifier.js +210 -0
- package/dist/verification/integration-verifier.js.map +1 -0
- package/dist/verification/recursive-audit-loop.d.ts +56 -0
- package/dist/verification/recursive-audit-loop.d.ts.map +1 -0
- package/dist/verification/recursive-audit-loop.js +136 -0
- package/dist/verification/recursive-audit-loop.js.map +1 -0
- package/dist/verification/runtime-verifier.d.ts +37 -0
- package/dist/verification/runtime-verifier.d.ts.map +1 -0
- package/dist/verification/runtime-verifier.js +221 -0
- package/dist/verification/runtime-verifier.js.map +1 -0
- package/dist/verification/types.d.ts +88 -0
- package/dist/verification/types.d.ts.map +1 -0
- package/dist/verification/types.js +5 -0
- package/dist/verification/types.js.map +1 -0
- package/dist/verification/verification-engine.d.ts +48 -0
- package/dist/verification/verification-engine.d.ts.map +1 -0
- package/dist/verification/verification-engine.js +203 -0
- package/dist/verification/verification-engine.js.map +1 -0
- package/opencode-agents/synapse-analyzer.md +64 -0
- package/opencode-agents/synapse-debugger.md +43 -0
- package/opencode-agents/synapse-diff-validator.md +42 -0
- package/opencode-agents/synapse-gatekeeper.md +273 -0
- package/opencode-agents/synapse-master.md +167 -0
- package/opencode-agents/synapse-planner.md +55 -0
- package/opencode-agents/synapse-researcher.md +61 -0
- package/opencode-agents/synapse-reviewer.md +64 -0
- package/opencode-agents/synapse-rewriter.md +62 -0
- package/opencode-agents/synapse-strategist.md +50 -0
- package/opencode-agents/synapse-test-runner.md +43 -0
- package/opencode-agents/synapse-ui-designer.md +61 -0
- package/opencode-agents/synapse-verifier.md +544 -0
- package/package.json +108 -0
- package/src/plugin/opencode-plugin.ts +141 -0
|
@@ -0,0 +1,3961 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
/**
|
|
3
|
+
* SynapseXcoder V6 - Base Agent Class
|
|
4
|
+
*
|
|
5
|
+
* All specialized agents (Planner, Analyzer, Reviewer, etc.) extend this base class.
|
|
6
|
+
* Provides common functionality: task execution, metrics tracking, subagent spawning,
|
|
7
|
+
* and communication with the OpenCode tool bridge.
|
|
8
|
+
*/
|
|
9
|
+
import { EventEmitter } from "events";
|
|
10
|
+
import { v4 as uuidv4 } from "uuid";
|
|
11
|
+
import { ContextSystem } from "../context/context-system.js";
|
|
12
|
+
import { ThoughtsManager } from "../thoughts/thoughts-manager.js";
|
|
13
|
+
import { AgentConfigManager } from "./agent-config-manager.js";
|
|
14
|
+
import { CommandProcessor } from "../commands/command-processor.js";
|
|
15
|
+
import { TeamCollaborationSystem } from "../team/team-collaboration.js";
|
|
16
|
+
import { PluginManager } from "../plugins/plugin-system.js";
|
|
17
|
+
import { TokenOptimizer } from "../optimization/token-optimizer.js";
|
|
18
|
+
import { optimizePromptForModel } from "../optimization/token-optimizer.js";
|
|
19
|
+
import { createGatedSubAgent } from "./gated-subagents.js";
|
|
20
|
+
import { getDefaultController as getDefaultDasp } from "../dasp/index.js";
|
|
21
|
+
// ============================================================================
|
|
22
|
+
// Model Resolution Utilities
|
|
23
|
+
// ============================================================================
|
|
24
|
+
/**
|
|
25
|
+
* Resolve model with fallback candidates
|
|
26
|
+
*/
|
|
27
|
+
export function resolveModelCandidates(userModel) {
|
|
28
|
+
const candidates = [];
|
|
29
|
+
// User-specified model first
|
|
30
|
+
if (userModel) {
|
|
31
|
+
candidates.push(userModel);
|
|
32
|
+
}
|
|
33
|
+
// Environment variable
|
|
34
|
+
const envModel = process.env.DUCKYCODER_MODEL;
|
|
35
|
+
if (envModel && !candidates.includes(envModel)) {
|
|
36
|
+
candidates.push(envModel);
|
|
37
|
+
}
|
|
38
|
+
// Provider-specific aliases
|
|
39
|
+
const aliases = {
|
|
40
|
+
"claude-sonnet": [
|
|
41
|
+
"opencode/claude-sonnet-4-5",
|
|
42
|
+
"github-copilot/claude-sonnet-4.5",
|
|
43
|
+
],
|
|
44
|
+
"claude-opus": [
|
|
45
|
+
"opencode/claude-opus-4-6",
|
|
46
|
+
"github-copilot/claude-opus-4.6",
|
|
47
|
+
"opencode/claude-opus-4-5",
|
|
48
|
+
"github-copilot/claude-opus-4-5",
|
|
49
|
+
],
|
|
50
|
+
"gpt-4": ["github-copilot/gpt-4o", "opencode/gpt-5"],
|
|
51
|
+
grok: ["github-copilot/grok-code-fast-1"],
|
|
52
|
+
};
|
|
53
|
+
for (const [alias, models] of Object.entries(aliases)) {
|
|
54
|
+
if (userModel?.includes(alias) || envModel?.includes(alias)) {
|
|
55
|
+
candidates.push(...models.filter((m) => !candidates.includes(m)));
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
// If no model specified via userModel or envVar, default to claude-opus-4-6
|
|
59
|
+
if (!userModel && !envModel) {
|
|
60
|
+
candidates.push("opencode/claude-opus-4-6");
|
|
61
|
+
candidates.push("github-copilot/claude-opus-4.6");
|
|
62
|
+
}
|
|
63
|
+
// Hard fallbacks - use models that are actually available
|
|
64
|
+
// OPTIMIZATION: Limit to 3 fallbacks to reduce retry attempts
|
|
65
|
+
const fallbacks = [
|
|
66
|
+
"opencode/claude-opus-4-6",
|
|
67
|
+
"github-copilot/claude-opus-4.6",
|
|
68
|
+
"github-copilot/grok-code-fast-1",
|
|
69
|
+
];
|
|
70
|
+
candidates.push(...fallbacks.filter((m) => !candidates.includes(m)));
|
|
71
|
+
return candidates;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Check if error is ProviderModelNotFoundError
|
|
75
|
+
*/
|
|
76
|
+
export function isProviderModelNotFoundError(error) {
|
|
77
|
+
const patterns = [
|
|
78
|
+
/ProviderModelNotFoundError/i,
|
|
79
|
+
/ModelNotFound/i,
|
|
80
|
+
/model not found/i,
|
|
81
|
+
/provider\/model/i,
|
|
82
|
+
/invalid model/i,
|
|
83
|
+
/model.*not.*available/i,
|
|
84
|
+
];
|
|
85
|
+
return patterns.some((pattern) => pattern.test(error));
|
|
86
|
+
}
|
|
87
|
+
// ============================================================================
|
|
88
|
+
// Base Agent (Abstract)
|
|
89
|
+
// ============================================================================
|
|
90
|
+
export class BaseAgent extends EventEmitter {
|
|
91
|
+
id;
|
|
92
|
+
type;
|
|
93
|
+
config;
|
|
94
|
+
memory = new Map();
|
|
95
|
+
status;
|
|
96
|
+
messageBus;
|
|
97
|
+
toolBridge;
|
|
98
|
+
artifacts = [];
|
|
99
|
+
isRunning = false;
|
|
100
|
+
abortController = null;
|
|
101
|
+
constructor(type, config, messageBus, toolBridge) {
|
|
102
|
+
super();
|
|
103
|
+
this.id = uuidv4();
|
|
104
|
+
this.type = type;
|
|
105
|
+
this.config = {
|
|
106
|
+
type,
|
|
107
|
+
mode: config.mode || "subagent",
|
|
108
|
+
model: config.model || "github-copilot/grok-code-fast-1",
|
|
109
|
+
temperature: config.temperature ?? 0.2,
|
|
110
|
+
maxSteps: config.maxSteps ?? 50,
|
|
111
|
+
description: config.description,
|
|
112
|
+
color: config.color,
|
|
113
|
+
tools: config.tools,
|
|
114
|
+
hidden: config.hidden ?? false,
|
|
115
|
+
timeout: config.timeout ?? 300000,
|
|
116
|
+
};
|
|
117
|
+
this.messageBus = messageBus;
|
|
118
|
+
this.toolBridge = toolBridge;
|
|
119
|
+
this.status = this.createInitialStatus();
|
|
120
|
+
}
|
|
121
|
+
createInitialStatus() {
|
|
122
|
+
return {
|
|
123
|
+
id: this.id,
|
|
124
|
+
type: this.type,
|
|
125
|
+
status: "idle",
|
|
126
|
+
startedAt: 0,
|
|
127
|
+
lastActivity: Date.now(),
|
|
128
|
+
metrics: {
|
|
129
|
+
tasksCompleted: 0,
|
|
130
|
+
tasksFailed: 0,
|
|
131
|
+
tokensUsed: 0,
|
|
132
|
+
executionTime: 0,
|
|
133
|
+
toolsInvoked: 0,
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
async start() {
|
|
138
|
+
if (this.isRunning)
|
|
139
|
+
return;
|
|
140
|
+
this.isRunning = true;
|
|
141
|
+
this.status.status = "running";
|
|
142
|
+
this.status.startedAt = Date.now();
|
|
143
|
+
this.abortController = new AbortController();
|
|
144
|
+
this.emit("start", this.id);
|
|
145
|
+
}
|
|
146
|
+
async stop() {
|
|
147
|
+
if (!this.isRunning)
|
|
148
|
+
return;
|
|
149
|
+
this.isRunning = false;
|
|
150
|
+
this.status.status = "idle";
|
|
151
|
+
this.abortController?.abort();
|
|
152
|
+
this.emit("stop", this.id);
|
|
153
|
+
}
|
|
154
|
+
async abort(reason) {
|
|
155
|
+
this.status.status = "idle";
|
|
156
|
+
this.abortController?.abort(reason);
|
|
157
|
+
await this.sendStatusUpdate("aborted", { reason });
|
|
158
|
+
this.emit("abort", this.id, reason);
|
|
159
|
+
}
|
|
160
|
+
// ========================================================================
|
|
161
|
+
// Status & Metrics
|
|
162
|
+
// ========================================================================
|
|
163
|
+
getStatus() {
|
|
164
|
+
return {
|
|
165
|
+
...this.status,
|
|
166
|
+
lastActivity: Date.now(),
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
updateStatus(status, details) {
|
|
170
|
+
this.status.status = status;
|
|
171
|
+
this.status.lastActivity = Date.now();
|
|
172
|
+
if (details) {
|
|
173
|
+
this.status.currentTask = details;
|
|
174
|
+
}
|
|
175
|
+
this.emit("statusChange", this.status);
|
|
176
|
+
}
|
|
177
|
+
recordTaskCompletion(tokensUsed, toolsInvoked, executionTime) {
|
|
178
|
+
this.status.metrics.tasksCompleted++;
|
|
179
|
+
this.status.metrics.tokensUsed += tokensUsed;
|
|
180
|
+
this.status.metrics.toolsInvoked += toolsInvoked;
|
|
181
|
+
this.status.metrics.executionTime += executionTime;
|
|
182
|
+
}
|
|
183
|
+
recordTaskFailure() {
|
|
184
|
+
this.status.metrics.tasksFailed++;
|
|
185
|
+
}
|
|
186
|
+
// ========================================================================
|
|
187
|
+
// Communication
|
|
188
|
+
// ========================================================================
|
|
189
|
+
async sendMessage(recipient, type, payload) {
|
|
190
|
+
return this.messageBus.send({
|
|
191
|
+
sender: this.type,
|
|
192
|
+
recipient,
|
|
193
|
+
type: type,
|
|
194
|
+
payload,
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
async sendTaskResult(recipient, result, correlationId) {
|
|
198
|
+
return this.messageBus.sendTaskResult(this.type, recipient, result, correlationId);
|
|
199
|
+
}
|
|
200
|
+
async sendTaskError(recipient, taskId, error, correlationId) {
|
|
201
|
+
return this.messageBus.sendTaskError(this.type, recipient, taskId, error, correlationId);
|
|
202
|
+
}
|
|
203
|
+
async sendStatusUpdate(status, details) {
|
|
204
|
+
return this.messageBus.sendStatusUpdate(this.type, status, details);
|
|
205
|
+
}
|
|
206
|
+
async sendApprovalRequest(request) {
|
|
207
|
+
return this.messageBus.sendApprovalRequest(this.type, request);
|
|
208
|
+
}
|
|
209
|
+
// ========================================================================
|
|
210
|
+
// Approval Workflow
|
|
211
|
+
// ========================================================================
|
|
212
|
+
async requestApproval(taskId, changes, explanation, riskLevel = "medium") {
|
|
213
|
+
this.updateStatus("waiting", "Requesting approval for changes");
|
|
214
|
+
const approvalId = await this.sendApprovalRequest({
|
|
215
|
+
taskId,
|
|
216
|
+
changes,
|
|
217
|
+
riskLevel,
|
|
218
|
+
explanation,
|
|
219
|
+
});
|
|
220
|
+
// Wait for approval response via message bus
|
|
221
|
+
const approved = await this.waitForApproval(approvalId);
|
|
222
|
+
if (approved) {
|
|
223
|
+
this.updateStatus("running", "Approval granted, proceeding with changes");
|
|
224
|
+
return true;
|
|
225
|
+
}
|
|
226
|
+
else {
|
|
227
|
+
this.updateStatus("error", "Approval denied, cancelling task");
|
|
228
|
+
return false;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
async waitForApproval(_approvalId) {
|
|
232
|
+
// Listen for approval response via message bus
|
|
233
|
+
const response = await this.messageBus.receive("master", 30000); // Wait 30s for approval response
|
|
234
|
+
return (response?.type === "approval_response" &&
|
|
235
|
+
response.payload?.approved === true);
|
|
236
|
+
}
|
|
237
|
+
// ========================================================================
|
|
238
|
+
// File Operations
|
|
239
|
+
// ========================================================================
|
|
240
|
+
async readFile(path) {
|
|
241
|
+
const result = await this.toolBridge.readFileContent(path);
|
|
242
|
+
return result?.content ?? null;
|
|
243
|
+
}
|
|
244
|
+
async readFiles(paths) {
|
|
245
|
+
const results = new Map();
|
|
246
|
+
for (const path of paths) {
|
|
247
|
+
results.set(path, await this.readFile(path));
|
|
248
|
+
}
|
|
249
|
+
return results;
|
|
250
|
+
}
|
|
251
|
+
async writeFile(path, content) {
|
|
252
|
+
const result = await this.toolBridge.writeFile(path, content);
|
|
253
|
+
return result.success;
|
|
254
|
+
}
|
|
255
|
+
async editFile(path, oldString, newString) {
|
|
256
|
+
const result = await this.toolBridge.editFile(path, oldString, newString);
|
|
257
|
+
return result.success;
|
|
258
|
+
}
|
|
259
|
+
async listDir(path, recursive = false) {
|
|
260
|
+
const result = await this.toolBridge.listDir(path, { recursive });
|
|
261
|
+
return result.success ? result.output : null;
|
|
262
|
+
}
|
|
263
|
+
async searchFiles(pattern, options) {
|
|
264
|
+
const result = await this.toolBridge.searchFiles(pattern, options);
|
|
265
|
+
return result.success ? result.output : null;
|
|
266
|
+
}
|
|
267
|
+
// ========================================================================
|
|
268
|
+
// Bash Operations
|
|
269
|
+
// ========================================================================
|
|
270
|
+
async runBash(command, options) {
|
|
271
|
+
const result = await this.toolBridge.runBash(command, options);
|
|
272
|
+
return result.success ? result.output : null;
|
|
273
|
+
}
|
|
274
|
+
async runGit(args) {
|
|
275
|
+
return this.runBash(`git ${args.join(" ")}`);
|
|
276
|
+
}
|
|
277
|
+
// ========================================================================
|
|
278
|
+
// Web Operations
|
|
279
|
+
// ========================================================================
|
|
280
|
+
async webSearch(query, numResults = 5) {
|
|
281
|
+
const result = await this.toolBridge.webSearch(query, { numResults });
|
|
282
|
+
return result.success ? result.output : null;
|
|
283
|
+
}
|
|
284
|
+
async callModel(prompt, options = {}) {
|
|
285
|
+
const result = await this.toolBridge.callModel(prompt, {
|
|
286
|
+
model: this.config.model,
|
|
287
|
+
...options,
|
|
288
|
+
});
|
|
289
|
+
return result.success ? result.output : null;
|
|
290
|
+
}
|
|
291
|
+
// ========================================================================
|
|
292
|
+
// Artifact Management
|
|
293
|
+
// ========================================================================
|
|
294
|
+
createArtifact(type, name, content) {
|
|
295
|
+
const artifact = {
|
|
296
|
+
id: uuidv4(),
|
|
297
|
+
type,
|
|
298
|
+
name,
|
|
299
|
+
content,
|
|
300
|
+
createdAt: Date.now(),
|
|
301
|
+
agentId: this.id,
|
|
302
|
+
};
|
|
303
|
+
this.artifacts.push(artifact);
|
|
304
|
+
this.emit("artifactCreated", artifact);
|
|
305
|
+
return artifact;
|
|
306
|
+
}
|
|
307
|
+
setMemory(key, value) {
|
|
308
|
+
this.memory.set(key, value);
|
|
309
|
+
}
|
|
310
|
+
getMemory(key) {
|
|
311
|
+
return this.memory.get(key);
|
|
312
|
+
}
|
|
313
|
+
clearMemory() {
|
|
314
|
+
this.memory.clear();
|
|
315
|
+
}
|
|
316
|
+
getArtifacts() {
|
|
317
|
+
return [...this.artifacts];
|
|
318
|
+
}
|
|
319
|
+
clearArtifacts() {
|
|
320
|
+
this.artifacts = [];
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
// ============================================================================
|
|
324
|
+
// Master Agent
|
|
325
|
+
// ============================================================================
|
|
326
|
+
// ============================================================================
|
|
327
|
+
// Master Agent
|
|
328
|
+
// ============================================================================
|
|
329
|
+
export class MasterAgent extends BaseAgent {
|
|
330
|
+
activeSubAgents = new Map();
|
|
331
|
+
// Using more generic type to support both BaseAgent and GatedSubAgent
|
|
332
|
+
subAgentFactory = new Map();
|
|
333
|
+
listenerStops = new Map();
|
|
334
|
+
contextSystem;
|
|
335
|
+
thoughtsManager;
|
|
336
|
+
agentConfigManager;
|
|
337
|
+
commandProcessor;
|
|
338
|
+
teamSystem;
|
|
339
|
+
pluginManager;
|
|
340
|
+
tokenOptimizer;
|
|
341
|
+
gatekeeperCoordinator = null;
|
|
342
|
+
enableGatekeeper = true;
|
|
343
|
+
gatekeeperReviewLevel = "standard";
|
|
344
|
+
gatekeeperMaxRevisions = 3;
|
|
345
|
+
daspController = null;
|
|
346
|
+
daspEnabled = true;
|
|
347
|
+
constructor(messageBus, toolBridge, config, projectRoot) {
|
|
348
|
+
// Resolve model with fallbacks
|
|
349
|
+
const resolvedModel = resolveModelCandidates(config?.model)[0];
|
|
350
|
+
super("master", {
|
|
351
|
+
mode: "primary",
|
|
352
|
+
model: resolvedModel,
|
|
353
|
+
...config,
|
|
354
|
+
}, messageBus, toolBridge);
|
|
355
|
+
// Initialize context and knowledge systems
|
|
356
|
+
this.contextSystem = new ContextSystem(projectRoot);
|
|
357
|
+
this.thoughtsManager = new ThoughtsManager(projectRoot || process.cwd());
|
|
358
|
+
this.agentConfigManager = new AgentConfigManager(projectRoot || process.cwd());
|
|
359
|
+
this.teamSystem = new TeamCollaborationSystem(projectRoot || process.cwd());
|
|
360
|
+
this.pluginManager = new PluginManager(projectRoot || process.cwd(), this);
|
|
361
|
+
this.tokenOptimizer = new TokenOptimizer();
|
|
362
|
+
this.daspController = getDefaultDasp();
|
|
363
|
+
this.commandProcessor = new CommandProcessor(this);
|
|
364
|
+
// Register default sub-agent constructors
|
|
365
|
+
// IMPORTANT: Using GatedSubAgents which include internal GatekeeperAgent
|
|
366
|
+
// for final review at the very end before returning to Master
|
|
367
|
+
this.subAgentFactory.set("planner", () => createGatedSubAgent("planner", messageBus, toolBridge, {}));
|
|
368
|
+
this.subAgentFactory.set("analyzer", () => createGatedSubAgent("analyzer", messageBus, toolBridge, {}));
|
|
369
|
+
this.subAgentFactory.set("reviewer", () => createGatedSubAgent("reviewer", messageBus, toolBridge, {}));
|
|
370
|
+
this.subAgentFactory.set("rewriter", () => createGatedSubAgent("rewriter", messageBus, toolBridge, {}));
|
|
371
|
+
this.subAgentFactory.set("ui_designer", () => createGatedSubAgent("ui_designer", messageBus, toolBridge, {}));
|
|
372
|
+
this.subAgentFactory.set("researcher", () => createGatedSubAgent("researcher", messageBus, toolBridge, {}));
|
|
373
|
+
// Gatekeeper is NOT a GatedSubAgent (it reviews others, not itself)
|
|
374
|
+
this.subAgentFactory.set("gatekeeper", () => new GatekeeperAgent(messageBus, toolBridge));
|
|
375
|
+
// Verifier - autonomous verification agent
|
|
376
|
+
this.subAgentFactory.set("verifier", () => createGatedSubAgent("verifier", messageBus, toolBridge, {}));
|
|
377
|
+
// Strategist - long-horizon goal decomposition
|
|
378
|
+
this.subAgentFactory.set("strategist", () => createGatedSubAgent("strategist", messageBus, toolBridge, {}));
|
|
379
|
+
// Diff Validator - compares original vs modified code
|
|
380
|
+
this.subAgentFactory.set("diff_validator", () => createGatedSubAgent("diff_validator", messageBus, toolBridge, {}));
|
|
381
|
+
// Debugger - auto-fix agent triggered on failure
|
|
382
|
+
this.subAgentFactory.set("debugger", () => createGatedSubAgent("debugger", messageBus, toolBridge, {}));
|
|
383
|
+
// Test Runner - executes validation tests
|
|
384
|
+
this.subAgentFactory.set("test_runner", () => createGatedSubAgent("test_runner", messageBus, toolBridge, {}));
|
|
385
|
+
}
|
|
386
|
+
async execute(task) {
|
|
387
|
+
const startTime = Date.now();
|
|
388
|
+
await this.start();
|
|
389
|
+
this.updateStatus("running", "Creating execution plan");
|
|
390
|
+
// Initialize gatekeeper coordinator if enabled
|
|
391
|
+
if (this.enableGatekeeper) {
|
|
392
|
+
this.gatekeeperCoordinator = new GatekeeperCoordinator(this.messageBus, this.toolBridge, 4);
|
|
393
|
+
}
|
|
394
|
+
const sessionId = task.context.metadata["sessionId"];
|
|
395
|
+
if (sessionId) {
|
|
396
|
+
// Log session activity
|
|
397
|
+
try {
|
|
398
|
+
const session = this.teamSystem.getSession(sessionId);
|
|
399
|
+
if (session) {
|
|
400
|
+
this.teamSystem.logActivity({
|
|
401
|
+
sessionId,
|
|
402
|
+
userId: "master-agent", // System user for now
|
|
403
|
+
type: "create",
|
|
404
|
+
targetType: "task",
|
|
405
|
+
targetId: task.id,
|
|
406
|
+
description: `Started processing task: ${task.prompt.substring(0, 50)}...`,
|
|
407
|
+
timestamp: Date.now(),
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
catch (e) {
|
|
412
|
+
console.warn(`Failed to log session start activity: ${e}`);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
try {
|
|
416
|
+
// 1. Create execution plan
|
|
417
|
+
const phases = this.buildPhases(task);
|
|
418
|
+
// 2. Execute phases sequentially, passing context forward
|
|
419
|
+
// Each phase output will be reviewed by gatekeeper before proceeding
|
|
420
|
+
const phaseResults = await this.executePhasesWithGatekeeper(phases, task.context);
|
|
421
|
+
// 3. Synthesize output from all phase results
|
|
422
|
+
const output = this.synthesizeOutput(phaseResults);
|
|
423
|
+
const executionTime = Date.now() - startTime;
|
|
424
|
+
this.recordTaskCompletion(0, phases.length, executionTime);
|
|
425
|
+
this.updateStatus("completed", "All phases complete with gatekeeper review");
|
|
426
|
+
// Final gatekeeper report
|
|
427
|
+
if (this.gatekeeperCoordinator) {
|
|
428
|
+
const gatekeeperReport = this.generateGatekeeperReport(phaseResults);
|
|
429
|
+
output.gatekeeperReport = gatekeeperReport;
|
|
430
|
+
}
|
|
431
|
+
if (sessionId) {
|
|
432
|
+
try {
|
|
433
|
+
this.teamSystem.logActivity({
|
|
434
|
+
sessionId,
|
|
435
|
+
userId: "master-agent",
|
|
436
|
+
type: "approve",
|
|
437
|
+
targetType: "task",
|
|
438
|
+
targetId: task.id,
|
|
439
|
+
description: "Task completed successfully with gatekeeper review",
|
|
440
|
+
timestamp: Date.now(),
|
|
441
|
+
metadata: {
|
|
442
|
+
executionTime,
|
|
443
|
+
phases: phases.length,
|
|
444
|
+
artifacts: this.artifacts.length,
|
|
445
|
+
},
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
catch (e) {
|
|
449
|
+
console.warn(`Failed to log session success activity: ${e}`);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
return {
|
|
453
|
+
taskId: task.id,
|
|
454
|
+
success: true,
|
|
455
|
+
output,
|
|
456
|
+
metrics: { executionTime, tokensUsed: 0, toolsInvoked: phases.length },
|
|
457
|
+
artifacts: this.collectAllArtifacts(),
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
catch (error) {
|
|
461
|
+
this.recordTaskFailure();
|
|
462
|
+
this.updateStatus("error");
|
|
463
|
+
if (sessionId) {
|
|
464
|
+
try {
|
|
465
|
+
this.teamSystem.logActivity({
|
|
466
|
+
sessionId,
|
|
467
|
+
userId: "master-agent",
|
|
468
|
+
type: "reject",
|
|
469
|
+
targetType: "task",
|
|
470
|
+
targetId: task.id,
|
|
471
|
+
description: `Task failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
472
|
+
timestamp: Date.now(),
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
catch (e) {
|
|
476
|
+
console.warn(`Failed to log session failure activity: ${e}`);
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
return {
|
|
480
|
+
taskId: task.id,
|
|
481
|
+
success: false,
|
|
482
|
+
output: undefined,
|
|
483
|
+
error: error instanceof Error ? error.message : String(error),
|
|
484
|
+
metrics: {
|
|
485
|
+
executionTime: Date.now() - startTime,
|
|
486
|
+
tokensUsed: 0,
|
|
487
|
+
toolsInvoked: 0,
|
|
488
|
+
},
|
|
489
|
+
artifacts: [],
|
|
490
|
+
};
|
|
491
|
+
}
|
|
492
|
+
finally {
|
|
493
|
+
await this.stop();
|
|
494
|
+
// Cleanup gatekeeper coordinator
|
|
495
|
+
if (this.gatekeeperCoordinator) {
|
|
496
|
+
await this.gatekeeperCoordinator.shutdown();
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
// -------------------------------------------------------------------------
|
|
501
|
+
// Phase Planning
|
|
502
|
+
// -------------------------------------------------------------------------
|
|
503
|
+
buildPhases(task) {
|
|
504
|
+
const mode = task.context.metadata["mode"] || "full_pipeline";
|
|
505
|
+
const hasUI = task.context.files.some((f) => /\.(tsx?|jsx?|html|svelte|vue)$/.test(f.path) ||
|
|
506
|
+
f.content.includes("React") ||
|
|
507
|
+
f.content.includes("<div"));
|
|
508
|
+
const needsResearch = task.context.metadata["needsResearch"] === true;
|
|
509
|
+
const generateDocs = task.context.metadata["generateDocs"] === true;
|
|
510
|
+
const phases = [];
|
|
511
|
+
switch (mode) {
|
|
512
|
+
case "full_pipeline":
|
|
513
|
+
// OPTIMIZATION: Research and Plan can run in parallel (independent phases)
|
|
514
|
+
if (needsResearch) {
|
|
515
|
+
phases.push({
|
|
516
|
+
id: "research",
|
|
517
|
+
agentType: "researcher",
|
|
518
|
+
prompt: `Research background for: ${task.prompt}`,
|
|
519
|
+
parallelGroup: "pre_analysis", // Group for parallel execution
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
phases.push({
|
|
523
|
+
id: "plan",
|
|
524
|
+
agentType: "planner",
|
|
525
|
+
prompt: `Create a plan for: ${task.prompt}`,
|
|
526
|
+
parallelGroup: needsResearch ? "pre_analysis" : undefined, // Run in parallel with research if both exist
|
|
527
|
+
}, {
|
|
528
|
+
id: "analyze",
|
|
529
|
+
agentType: "analyzer",
|
|
530
|
+
prompt: `Analyze code: ${task.prompt}`,
|
|
531
|
+
}, {
|
|
532
|
+
id: "review",
|
|
533
|
+
agentType: "reviewer",
|
|
534
|
+
prompt: `Review findings from analysis`,
|
|
535
|
+
}, {
|
|
536
|
+
id: "rewrite",
|
|
537
|
+
agentType: "rewriter",
|
|
538
|
+
prompt: `Apply fixes: ${task.prompt}`,
|
|
539
|
+
});
|
|
540
|
+
if (generateDocs) {
|
|
541
|
+
phases.push({
|
|
542
|
+
id: "docs",
|
|
543
|
+
agentType: "rewriter",
|
|
544
|
+
prompt: `Generate documentation (README.md, API docs) based on the analysis and changes: ${task.prompt}`,
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
if (hasUI) {
|
|
548
|
+
phases.push({
|
|
549
|
+
id: "ui",
|
|
550
|
+
agentType: "ui_designer",
|
|
551
|
+
prompt: `Generate UI mockups for detected UI code`,
|
|
552
|
+
});
|
|
553
|
+
}
|
|
554
|
+
break;
|
|
555
|
+
case "analyze_only":
|
|
556
|
+
phases.push({
|
|
557
|
+
id: "analyze",
|
|
558
|
+
agentType: "analyzer",
|
|
559
|
+
prompt: `Analyze: ${task.prompt}`,
|
|
560
|
+
});
|
|
561
|
+
break;
|
|
562
|
+
case "review_only":
|
|
563
|
+
phases.push({
|
|
564
|
+
id: "review",
|
|
565
|
+
agentType: "reviewer",
|
|
566
|
+
prompt: `Review: ${task.prompt}`,
|
|
567
|
+
});
|
|
568
|
+
break;
|
|
569
|
+
case "rewrite_only":
|
|
570
|
+
phases.push({
|
|
571
|
+
id: "rewrite",
|
|
572
|
+
agentType: "rewriter",
|
|
573
|
+
prompt: `Rewrite: ${task.prompt}`,
|
|
574
|
+
});
|
|
575
|
+
break;
|
|
576
|
+
case "ui_design":
|
|
577
|
+
phases.push({
|
|
578
|
+
id: "ui",
|
|
579
|
+
agentType: "ui_designer",
|
|
580
|
+
prompt: `Design UI for: ${task.prompt}`,
|
|
581
|
+
});
|
|
582
|
+
break;
|
|
583
|
+
case "merge_only":
|
|
584
|
+
phases.push({
|
|
585
|
+
id: "plan",
|
|
586
|
+
agentType: "planner",
|
|
587
|
+
prompt: `Plan merge for: ${task.prompt}`,
|
|
588
|
+
});
|
|
589
|
+
break;
|
|
590
|
+
case "dry_run":
|
|
591
|
+
phases.push({
|
|
592
|
+
id: "analyze",
|
|
593
|
+
agentType: "analyzer",
|
|
594
|
+
prompt: `Dry-run analyze: ${task.prompt} (simulation only — no changes applied)`,
|
|
595
|
+
}, {
|
|
596
|
+
id: "review",
|
|
597
|
+
agentType: "reviewer",
|
|
598
|
+
prompt: `Dry-run review findings for: ${task.prompt}`,
|
|
599
|
+
});
|
|
600
|
+
break;
|
|
601
|
+
case "security_scan":
|
|
602
|
+
phases.push({
|
|
603
|
+
id: "security",
|
|
604
|
+
agentType: "gatekeeper",
|
|
605
|
+
prompt: `Security vulnerability scan for: ${task.prompt}`,
|
|
606
|
+
}, {
|
|
607
|
+
id: "analyze",
|
|
608
|
+
agentType: "analyzer",
|
|
609
|
+
prompt: `Analyze security findings for: ${task.prompt}`,
|
|
610
|
+
});
|
|
611
|
+
break;
|
|
612
|
+
case "debug_assistant":
|
|
613
|
+
phases.push({
|
|
614
|
+
id: "debug",
|
|
615
|
+
agentType: "debugger",
|
|
616
|
+
prompt: `Debug stack trace / error: ${task.prompt}`,
|
|
617
|
+
}, {
|
|
618
|
+
id: "analyze",
|
|
619
|
+
agentType: "analyzer",
|
|
620
|
+
prompt: `Analyze root cause for: ${task.prompt}`,
|
|
621
|
+
});
|
|
622
|
+
break;
|
|
623
|
+
case "api_validation":
|
|
624
|
+
phases.push({
|
|
625
|
+
id: "api",
|
|
626
|
+
agentType: "analyzer",
|
|
627
|
+
prompt: `Validate API endpoints for: ${task.prompt}`,
|
|
628
|
+
});
|
|
629
|
+
break;
|
|
630
|
+
case "doc_generator":
|
|
631
|
+
phases.push({
|
|
632
|
+
id: "docs",
|
|
633
|
+
agentType: "rewriter",
|
|
634
|
+
prompt: `Generate documentation from: ${task.prompt}`,
|
|
635
|
+
});
|
|
636
|
+
break;
|
|
637
|
+
case "create_mode":
|
|
638
|
+
phases.push({
|
|
639
|
+
id: "plan",
|
|
640
|
+
agentType: "planner",
|
|
641
|
+
prompt: `Plan scaffolding for: ${task.prompt}`,
|
|
642
|
+
}, {
|
|
643
|
+
id: "scaffold",
|
|
644
|
+
agentType: "rewriter",
|
|
645
|
+
prompt: `Create new files for: ${task.prompt}`,
|
|
646
|
+
});
|
|
647
|
+
break;
|
|
648
|
+
case "debug_mode":
|
|
649
|
+
phases.push({
|
|
650
|
+
id: "debug",
|
|
651
|
+
agentType: "debugger",
|
|
652
|
+
prompt: `Debug: ${task.prompt}`,
|
|
653
|
+
});
|
|
654
|
+
break;
|
|
655
|
+
case "refactor_mode":
|
|
656
|
+
phases.push({
|
|
657
|
+
id: "analyze",
|
|
658
|
+
agentType: "analyzer",
|
|
659
|
+
prompt: `Analyze refactoring targets: ${task.prompt}`,
|
|
660
|
+
}, {
|
|
661
|
+
id: "rewrite",
|
|
662
|
+
agentType: "rewriter",
|
|
663
|
+
prompt: `Apply refactoring: ${task.prompt}`,
|
|
664
|
+
}, {
|
|
665
|
+
id: "review",
|
|
666
|
+
agentType: "reviewer",
|
|
667
|
+
prompt: `Review refactored code for: ${task.prompt}`,
|
|
668
|
+
});
|
|
669
|
+
break;
|
|
670
|
+
case "document_mode":
|
|
671
|
+
phases.push({
|
|
672
|
+
id: "research",
|
|
673
|
+
agentType: "researcher",
|
|
674
|
+
prompt: `Research context for documentation: ${task.prompt}`,
|
|
675
|
+
}, {
|
|
676
|
+
id: "docs",
|
|
677
|
+
agentType: "rewriter",
|
|
678
|
+
prompt: `Generate comprehensive documentation: ${task.prompt}`,
|
|
679
|
+
});
|
|
680
|
+
break;
|
|
681
|
+
case "security_mode":
|
|
682
|
+
phases.push({
|
|
683
|
+
id: "security",
|
|
684
|
+
agentType: "gatekeeper",
|
|
685
|
+
prompt: `Hardened security scan: ${task.prompt}`,
|
|
686
|
+
}, {
|
|
687
|
+
id: "analyze",
|
|
688
|
+
agentType: "analyzer",
|
|
689
|
+
prompt: `Analyze security posture for: ${task.prompt}`,
|
|
690
|
+
}, {
|
|
691
|
+
id: "review",
|
|
692
|
+
agentType: "reviewer",
|
|
693
|
+
prompt: `Review security findings for: ${task.prompt}`,
|
|
694
|
+
});
|
|
695
|
+
break;
|
|
696
|
+
default:
|
|
697
|
+
phases.push({
|
|
698
|
+
id: "analyze",
|
|
699
|
+
agentType: "analyzer",
|
|
700
|
+
prompt: task.prompt,
|
|
701
|
+
});
|
|
702
|
+
}
|
|
703
|
+
return phases;
|
|
704
|
+
}
|
|
705
|
+
// -------------------------------------------------------------------------
|
|
706
|
+
// Dynamic Task Division — parallel sub-agent execution for large workloads
|
|
707
|
+
// -------------------------------------------------------------------------
|
|
708
|
+
/**
|
|
709
|
+
* Check if a phase should be parallelized based on context
|
|
710
|
+
*/
|
|
711
|
+
shouldParallelizePhase(phaseId, context) {
|
|
712
|
+
// Parallelize rewrite phase if there are many files or issues
|
|
713
|
+
if (phaseId === "rewrite") {
|
|
714
|
+
// If we have many files, parallelize
|
|
715
|
+
if (context.files.length > 3)
|
|
716
|
+
return true;
|
|
717
|
+
// Check analysis output for many issues
|
|
718
|
+
const analysis = context.metadata["analyze_output"];
|
|
719
|
+
if (analysis && typeof analysis === "object") {
|
|
720
|
+
const issues = analysis.issues || [];
|
|
721
|
+
if (issues.length > 5)
|
|
722
|
+
return true;
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
// Parallelize analysis phase if many files
|
|
726
|
+
if (phaseId === "analyze") {
|
|
727
|
+
if (context.files.length > 5)
|
|
728
|
+
return true;
|
|
729
|
+
}
|
|
730
|
+
return false;
|
|
731
|
+
}
|
|
732
|
+
/**
|
|
733
|
+
* Divide a large task into smaller batches for parallel execution
|
|
734
|
+
*/
|
|
735
|
+
divideTaskIntoBatches(originalTask, batchSize = 3) {
|
|
736
|
+
const batches = [];
|
|
737
|
+
// Get the analysis output to understand what needs to be done
|
|
738
|
+
const analysisOutput = originalTask.context.metadata?.analyze_output;
|
|
739
|
+
if (!analysisOutput && originalTask.type === "rewriter") {
|
|
740
|
+
return [originalTask]; // Can't divide rewrite without analysis
|
|
741
|
+
}
|
|
742
|
+
// For rewrite tasks, divide by files
|
|
743
|
+
if (originalTask.type === "rewriter") {
|
|
744
|
+
const files = originalTask.context.files;
|
|
745
|
+
// Create batches of files
|
|
746
|
+
for (let i = 0; i < files.length; i += batchSize) {
|
|
747
|
+
const fileBatch = files.slice(i, i + batchSize);
|
|
748
|
+
batches.push({
|
|
749
|
+
...originalTask,
|
|
750
|
+
id: `${originalTask.id}-batch-${i / batchSize + 1}`,
|
|
751
|
+
context: {
|
|
752
|
+
...originalTask.context,
|
|
753
|
+
files: fileBatch,
|
|
754
|
+
},
|
|
755
|
+
prompt: `${originalTask.prompt} (files: ${fileBatch
|
|
756
|
+
.map((f) => f.path)
|
|
757
|
+
.join(", ")})`,
|
|
758
|
+
});
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
else {
|
|
762
|
+
// For other tasks, create logical batches
|
|
763
|
+
for (let i = 0; i < Math.ceil(batchSize); i++) {
|
|
764
|
+
batches.push({
|
|
765
|
+
...originalTask,
|
|
766
|
+
id: `${originalTask.id}-batch-${i + 1}`,
|
|
767
|
+
prompt: `${originalTask.prompt} (batch ${i + 1})`,
|
|
768
|
+
});
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
return batches.length > 1 ? batches : [originalTask];
|
|
772
|
+
}
|
|
773
|
+
/**
|
|
774
|
+
* Execute multiple subagents in parallel for a single phase
|
|
775
|
+
*/
|
|
776
|
+
async executePhaseInParallel(phase, rollingContext) {
|
|
777
|
+
this.updateStatus("running", `Phase [${phase.id}] → Parallel ${phase.agentType} execution`);
|
|
778
|
+
// Create the initial task
|
|
779
|
+
const enhancedPrompt = this.enhancePromptWithContext(phase.prompt, phase.agentType, rollingContext);
|
|
780
|
+
const initialTask = {
|
|
781
|
+
id: uuidv4(),
|
|
782
|
+
type: phase.agentType,
|
|
783
|
+
prompt: enhancedPrompt,
|
|
784
|
+
context: rollingContext,
|
|
785
|
+
successCriteria: `Complete phase: ${phase.id}`,
|
|
786
|
+
};
|
|
787
|
+
// Divide into batches
|
|
788
|
+
const taskBatches = this.divideTaskIntoBatches(initialTask);
|
|
789
|
+
if (taskBatches.length === 1) {
|
|
790
|
+
// No parallelization needed
|
|
791
|
+
const result = await this.executeSingleTask(taskBatches[0]);
|
|
792
|
+
return [result];
|
|
793
|
+
}
|
|
794
|
+
// Execute batches in parallel
|
|
795
|
+
console.log(`🚀 Parallel execution: ${taskBatches.length} ${phase.agentType} agents`);
|
|
796
|
+
const results = await Promise.all(taskBatches.map((task) => this.executeSingleTask(task)));
|
|
797
|
+
return results;
|
|
798
|
+
}
|
|
799
|
+
/**
|
|
800
|
+
* Execute a single task with model fallback
|
|
801
|
+
*/
|
|
802
|
+
async executeSingleTask(task) {
|
|
803
|
+
let attempt = 0;
|
|
804
|
+
const modelCandidates = resolveModelCandidates(this.config.model);
|
|
805
|
+
while (attempt < modelCandidates.length) {
|
|
806
|
+
try {
|
|
807
|
+
const modelToUse = attempt === 0 ? this.config.model : modelCandidates[attempt];
|
|
808
|
+
const subAgent = this.getOrCreateSubAgent(task.type, modelToUse);
|
|
809
|
+
// Register sub-agent
|
|
810
|
+
this.registerSubAgent(subAgent.id, subAgent);
|
|
811
|
+
// Dispatch task
|
|
812
|
+
const result = await this.messageBus.sendTask(this.type, task.type, task);
|
|
813
|
+
// Unregister after completion
|
|
814
|
+
this.unregisterSubAgent(subAgent.id);
|
|
815
|
+
if (result.success) {
|
|
816
|
+
return result;
|
|
817
|
+
}
|
|
818
|
+
// Fallback logic for model errors
|
|
819
|
+
if (result.error && isProviderModelNotFoundError(result.error)) {
|
|
820
|
+
attempt++;
|
|
821
|
+
console.warn(`Task ${task.id}: Model ${modelCandidates[attempt - 1]} unavailable, trying ${modelCandidates[attempt]}`);
|
|
822
|
+
continue;
|
|
823
|
+
}
|
|
824
|
+
return result;
|
|
825
|
+
}
|
|
826
|
+
catch (error) {
|
|
827
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
828
|
+
if (isProviderModelNotFoundError(errorMsg)) {
|
|
829
|
+
attempt++;
|
|
830
|
+
if (attempt < modelCandidates.length) {
|
|
831
|
+
console.warn(`Task ${task.id}: Model ${modelCandidates[attempt - 1]} failed, trying ${modelCandidates[attempt]}`);
|
|
832
|
+
continue;
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
return {
|
|
836
|
+
taskId: task.id,
|
|
837
|
+
success: false,
|
|
838
|
+
output: undefined,
|
|
839
|
+
error: errorMsg,
|
|
840
|
+
metrics: { executionTime: 0, tokensUsed: 0, toolsInvoked: 0 },
|
|
841
|
+
artifacts: [],
|
|
842
|
+
};
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
return {
|
|
846
|
+
taskId: task.id,
|
|
847
|
+
success: false,
|
|
848
|
+
output: undefined,
|
|
849
|
+
error: "All model attempts failed",
|
|
850
|
+
metrics: { executionTime: 0, tokensUsed: 0, toolsInvoked: 0 },
|
|
851
|
+
artifacts: [],
|
|
852
|
+
};
|
|
853
|
+
}
|
|
854
|
+
// -------------------------------------------------------------------------
|
|
855
|
+
// Phase Execution with Gatekeeper Review
|
|
856
|
+
// -------------------------------------------------------------------------
|
|
857
|
+
/**
|
|
858
|
+
* Execute phases with gatekeeper review after each phase.
|
|
859
|
+
* Gatekeeper reviews each phase output before proceeding to the next.
|
|
860
|
+
* If gatekeeper requests revision, the affected phase is re-executed.
|
|
861
|
+
* If gatekeeper requests restart, the entire pipeline restarts.
|
|
862
|
+
*/
|
|
863
|
+
async executePhasesWithGatekeeper(phases, baseContext) {
|
|
864
|
+
const accumulated = [];
|
|
865
|
+
let rollingContext = { ...baseContext };
|
|
866
|
+
const maxRevisions = this.gatekeeperMaxRevisions;
|
|
867
|
+
// OPTIMIZATION: Group phases by parallelGroup for concurrent execution
|
|
868
|
+
const phaseGroups = [];
|
|
869
|
+
for (const phase of phases) {
|
|
870
|
+
if (phase.parallelGroup) {
|
|
871
|
+
// Check if we already have a group for this parallelGroup
|
|
872
|
+
const existingGroup = phaseGroups.find((g) => g.phases[0]?.parallelGroup === phase.parallelGroup);
|
|
873
|
+
if (existingGroup) {
|
|
874
|
+
existingGroup.phases.push(phase);
|
|
875
|
+
}
|
|
876
|
+
else {
|
|
877
|
+
phaseGroups.push({ phases: [phase], parallel: true });
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
else {
|
|
881
|
+
phaseGroups.push({ phases: [phase], parallel: false });
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
// Execute phase groups sequentially, but phases within a group in parallel
|
|
885
|
+
for (const group of phaseGroups) {
|
|
886
|
+
if (group.parallel && group.phases.length > 1) {
|
|
887
|
+
// Execute all phases in this group in parallel
|
|
888
|
+
this.updateStatus("running", `Parallel phases: ${group.phases.map((p) => p.id).join(", ")}`);
|
|
889
|
+
console.log(`🚀 Executing parallel group: ${group.phases.map((p) => p.id).join(", ")}`);
|
|
890
|
+
const parallelResults = await Promise.all(group.phases.map((phase) => this.executePhaseWithGatekeeper(phase, rollingContext, maxRevisions)));
|
|
891
|
+
// Merge all results into rolling context
|
|
892
|
+
for (const { phase, result, gatekeeperReview } of parallelResults) {
|
|
893
|
+
rollingContext = {
|
|
894
|
+
...rollingContext,
|
|
895
|
+
metadata: {
|
|
896
|
+
...rollingContext.metadata,
|
|
897
|
+
[`${phase.id}_output`]: result.output,
|
|
898
|
+
},
|
|
899
|
+
};
|
|
900
|
+
accumulated.push({ phaseId: phase.id, agentType: phase.agentType, result, gatekeeperReview });
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
else {
|
|
904
|
+
// Execute phases sequentially
|
|
905
|
+
for (const phase of group.phases) {
|
|
906
|
+
const result = await this.executePhaseWithGatekeeper(phase, rollingContext, maxRevisions);
|
|
907
|
+
rollingContext = {
|
|
908
|
+
...rollingContext,
|
|
909
|
+
metadata: {
|
|
910
|
+
...rollingContext.metadata,
|
|
911
|
+
[`${phase.id}_output`]: result.result.output,
|
|
912
|
+
},
|
|
913
|
+
};
|
|
914
|
+
accumulated.push({
|
|
915
|
+
phaseId: phase.id,
|
|
916
|
+
agentType: phase.agentType,
|
|
917
|
+
result: result.result,
|
|
918
|
+
gatekeeperReview: result.gatekeeperReview,
|
|
919
|
+
});
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
return accumulated;
|
|
924
|
+
}
|
|
925
|
+
/**
|
|
926
|
+
* Execute a single phase with gatekeeper review (extracted for reuse)
|
|
927
|
+
*/
|
|
928
|
+
async executePhaseWithGatekeeper(phase, rollingContext, maxRevisions) {
|
|
929
|
+
let phaseApproved = false;
|
|
930
|
+
let currentAttempt = 0;
|
|
931
|
+
let mergedResult = {
|
|
932
|
+
taskId: "",
|
|
933
|
+
success: false,
|
|
934
|
+
output: undefined,
|
|
935
|
+
error: undefined,
|
|
936
|
+
metrics: { executionTime: 0, tokensUsed: 0, toolsInvoked: 0 },
|
|
937
|
+
artifacts: [],
|
|
938
|
+
};
|
|
939
|
+
let gatekeeperReview;
|
|
940
|
+
while (!phaseApproved && currentAttempt <= maxRevisions) {
|
|
941
|
+
if (currentAttempt > 0) {
|
|
942
|
+
console.log(`🔄 Phase [${phase.id}] re-execution attempt ${currentAttempt}/${maxRevisions}`);
|
|
943
|
+
this.updateStatus("running", `Phase [${phase.id}] re-execution attempt ${currentAttempt}`);
|
|
944
|
+
}
|
|
945
|
+
// Execute the phase
|
|
946
|
+
const phaseResults = await this.executeSinglePhase(phase, rollingContext);
|
|
947
|
+
mergedResult = this.mergeParallelResults(phaseResults);
|
|
948
|
+
// Gatekeeper review
|
|
949
|
+
if (this.enableGatekeeper && this.gatekeeperCoordinator) {
|
|
950
|
+
gatekeeperReview = await this.performGatekeeperReview(phase.id, phase.agentType, mergedResult, rollingContext);
|
|
951
|
+
console.log(`🔍 Gatekeeper decision for [${phase.id}]: ${gatekeeperReview.decision}`);
|
|
952
|
+
console.log(` Confidence: ${(gatekeeperReview.confidence * 100).toFixed(1)}%`);
|
|
953
|
+
console.log(` Issues: ${gatekeeperReview.issues.length}`);
|
|
954
|
+
console.log(` Suggestions: ${gatekeeperReview.suggestions.length}`);
|
|
955
|
+
// Handle gatekeeper decision
|
|
956
|
+
switch (gatekeeperReview.decision) {
|
|
957
|
+
case "approve":
|
|
958
|
+
phaseApproved = true;
|
|
959
|
+
break;
|
|
960
|
+
case "request_revision":
|
|
961
|
+
if (currentAttempt < maxRevisions) {
|
|
962
|
+
// Prepare modified prompt based on issues
|
|
963
|
+
const modifiedPrompt = this.prepareRevisionPrompt(phase.prompt, gatekeeperReview.issues, gatekeeperReview.suggestions);
|
|
964
|
+
phase.prompt = modifiedPrompt;
|
|
965
|
+
currentAttempt++;
|
|
966
|
+
// Continue to re-execute
|
|
967
|
+
}
|
|
968
|
+
else {
|
|
969
|
+
console.warn(`⚠️ Max revisions reached for phase [${phase.id}], proceeding anyway`);
|
|
970
|
+
phaseApproved = true;
|
|
971
|
+
}
|
|
972
|
+
break;
|
|
973
|
+
case "restart_from_scratch":
|
|
974
|
+
console.error(`🚨 Gatekeeper requested restart_from_scratch for phase [${phase.id}]`);
|
|
975
|
+
// Clear accumulated results and restart from beginning
|
|
976
|
+
phaseApproved = true; // Continue but mark for restart
|
|
977
|
+
break;
|
|
978
|
+
case "escalate":
|
|
979
|
+
console.error(`🚨 Gatekeeper escalated phase [${phase.id}]`);
|
|
980
|
+
// Store the review and continue with warning
|
|
981
|
+
phaseApproved = true;
|
|
982
|
+
break;
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
else {
|
|
986
|
+
// No gatekeeper, approve automatically
|
|
987
|
+
phaseApproved = true;
|
|
988
|
+
}
|
|
989
|
+
// Store execution knowledge
|
|
990
|
+
await this.storeExecutionKnowledge(phase.id, phase.agentType, mergedResult, {
|
|
991
|
+
id: uuidv4(),
|
|
992
|
+
type: phase.agentType,
|
|
993
|
+
prompt: phase.prompt,
|
|
994
|
+
context: rollingContext,
|
|
995
|
+
successCriteria: `Complete phase: ${phase.id}`,
|
|
996
|
+
});
|
|
997
|
+
// Merge phase artifacts
|
|
998
|
+
this.artifacts.push(...mergedResult.artifacts);
|
|
999
|
+
}
|
|
1000
|
+
return { phase, result: mergedResult, gatekeeperReview };
|
|
1001
|
+
}
|
|
1002
|
+
/**
|
|
1003
|
+
* Perform gatekeeper review for a phase
|
|
1004
|
+
*/
|
|
1005
|
+
async performGatekeeperReview(phaseId, agentType, result, context) {
|
|
1006
|
+
this.updateStatus("running", `Gatekeeper reviewing phase [${phaseId}]`);
|
|
1007
|
+
if (!this.gatekeeperCoordinator) {
|
|
1008
|
+
throw new Error("Gatekeeper coordinator not initialized");
|
|
1009
|
+
}
|
|
1010
|
+
const task = {
|
|
1011
|
+
id: uuidv4(),
|
|
1012
|
+
type: "gatekeeper",
|
|
1013
|
+
prompt: JSON.stringify(result.output),
|
|
1014
|
+
context: {
|
|
1015
|
+
...context,
|
|
1016
|
+
metadata: {
|
|
1017
|
+
...context.metadata,
|
|
1018
|
+
phaseId,
|
|
1019
|
+
agentType,
|
|
1020
|
+
workOutput: result.output,
|
|
1021
|
+
},
|
|
1022
|
+
},
|
|
1023
|
+
successCriteria: `Complete gatekeeper review for ${phaseId}`,
|
|
1024
|
+
};
|
|
1025
|
+
return this.gatekeeperCoordinator.submitForReview(task);
|
|
1026
|
+
}
|
|
1027
|
+
/**
|
|
1028
|
+
* Prepare a revised prompt based on gatekeeper feedback
|
|
1029
|
+
*/
|
|
1030
|
+
prepareRevisionPrompt(originalPrompt, issues, suggestions) {
|
|
1031
|
+
let revisionGuidance = "\n\n## Gatekeeper Revision Requirements\n";
|
|
1032
|
+
if (issues.length > 0) {
|
|
1033
|
+
revisionGuidance += "\n### Issues to Address:\n";
|
|
1034
|
+
issues.forEach((issue, idx) => {
|
|
1035
|
+
revisionGuidance += `${idx + 1}. [${issue.severity.toUpperCase()}] ${issue.description}\n`;
|
|
1036
|
+
if (issue.remediation) {
|
|
1037
|
+
revisionGuidance += ` Remediation: ${issue.remediation}\n`;
|
|
1038
|
+
}
|
|
1039
|
+
});
|
|
1040
|
+
}
|
|
1041
|
+
if (suggestions.length > 0) {
|
|
1042
|
+
const mustHave = suggestions.filter((s) => s.priority === "must");
|
|
1043
|
+
if (mustHave.length > 0) {
|
|
1044
|
+
revisionGuidance += "\n### Must-Have Improvements:\n";
|
|
1045
|
+
mustHave.forEach((sug, idx) => {
|
|
1046
|
+
revisionGuidance += `${idx + 1}. ${sug.description}\n`;
|
|
1047
|
+
if (sug.rationale) {
|
|
1048
|
+
revisionGuidance += ` Rationale: ${sug.rationale}\n`;
|
|
1049
|
+
}
|
|
1050
|
+
});
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
return `${originalPrompt}${revisionGuidance}`;
|
|
1054
|
+
}
|
|
1055
|
+
/**
|
|
1056
|
+
* Generate final gatekeeper report
|
|
1057
|
+
*/
|
|
1058
|
+
generateGatekeeperReport(phaseResults) {
|
|
1059
|
+
const phaseReviews = phaseResults
|
|
1060
|
+
.filter((r) => r.gatekeeperReview)
|
|
1061
|
+
.map((r) => r.gatekeeperReview);
|
|
1062
|
+
const passedCount = phaseReviews.filter((r) => r.decision === "approve").length;
|
|
1063
|
+
const needsRevisionCount = phaseReviews.filter((r) => r.decision === "request_revision").length;
|
|
1064
|
+
const restartedCount = phaseReviews.filter((r) => r.decision === "restart_from_scratch").length;
|
|
1065
|
+
const escalatedCount = phaseReviews.filter((r) => r.decision === "escalate").length;
|
|
1066
|
+
const avgConfidence = phaseReviews.length > 0
|
|
1067
|
+
? phaseReviews.reduce((sum, r) => sum + r.confidence, 0) /
|
|
1068
|
+
phaseReviews.length
|
|
1069
|
+
: 1;
|
|
1070
|
+
let overallDecision = "approve";
|
|
1071
|
+
if (restartedCount > 0) {
|
|
1072
|
+
overallDecision = "restart_from_scratch";
|
|
1073
|
+
}
|
|
1074
|
+
else if (needsRevisionCount > 0) {
|
|
1075
|
+
overallDecision = "request_revision";
|
|
1076
|
+
}
|
|
1077
|
+
else if (escalatedCount > 0) {
|
|
1078
|
+
overallDecision = "escalate";
|
|
1079
|
+
}
|
|
1080
|
+
// Build master decision
|
|
1081
|
+
let masterAction = "proceed";
|
|
1082
|
+
let reasoning = "All phases passed gatekeeper review";
|
|
1083
|
+
if (restartedCount > 0) {
|
|
1084
|
+
masterAction = "restart";
|
|
1085
|
+
reasoning = `${restartedCount} phase(s) required restart`;
|
|
1086
|
+
}
|
|
1087
|
+
else if (needsRevisionCount > 0) {
|
|
1088
|
+
masterAction = "modify_plan";
|
|
1089
|
+
reasoning = `${needsRevisionCount} phase(s) had issues requiring revision`;
|
|
1090
|
+
}
|
|
1091
|
+
else if (escalatedCount > 0) {
|
|
1092
|
+
masterAction = "stop";
|
|
1093
|
+
reasoning = `${escalatedCount} phase(s) escalated for human review`;
|
|
1094
|
+
}
|
|
1095
|
+
const masterDecision = {
|
|
1096
|
+
action: masterAction,
|
|
1097
|
+
reasoning,
|
|
1098
|
+
affectedPhases: phaseResults
|
|
1099
|
+
.filter((r) => r.gatekeeperReview?.decision !== "approve")
|
|
1100
|
+
.map((r) => r.phaseId),
|
|
1101
|
+
};
|
|
1102
|
+
const nextSteps = [];
|
|
1103
|
+
if (masterAction === "proceed") {
|
|
1104
|
+
nextSteps.push("Proceed to output synthesis");
|
|
1105
|
+
}
|
|
1106
|
+
else if (masterAction === "modify_plan") {
|
|
1107
|
+
nextSteps.push("Apply revisions to affected phases");
|
|
1108
|
+
nextSteps.push("Re-run pipeline with corrections");
|
|
1109
|
+
}
|
|
1110
|
+
else if (masterAction === "restart") {
|
|
1111
|
+
nextSteps.push("Clear current state");
|
|
1112
|
+
nextSteps.push("Restart pipeline from beginning");
|
|
1113
|
+
}
|
|
1114
|
+
else {
|
|
1115
|
+
nextSteps.push("Request human intervention");
|
|
1116
|
+
nextSteps.push("Await user instructions");
|
|
1117
|
+
}
|
|
1118
|
+
return {
|
|
1119
|
+
overallDecision,
|
|
1120
|
+
confidence: avgConfidence,
|
|
1121
|
+
phaseReviews,
|
|
1122
|
+
masterDecision,
|
|
1123
|
+
summary: `Gatekeeper Report: ${passedCount}/${phaseReviews.length} phases passed. Decision: ${overallDecision}`,
|
|
1124
|
+
nextSteps,
|
|
1125
|
+
revisionHistory: [],
|
|
1126
|
+
};
|
|
1127
|
+
}
|
|
1128
|
+
/**
|
|
1129
|
+
* Execute a single phase (extracted from executePhases)
|
|
1130
|
+
*/
|
|
1131
|
+
async executeSinglePhase(phase, rollingContext) {
|
|
1132
|
+
const shouldParallelize = this.shouldParallelizePhase(phase.id, rollingContext);
|
|
1133
|
+
if (shouldParallelize) {
|
|
1134
|
+
return this.executePhaseInParallel(phase, rollingContext);
|
|
1135
|
+
}
|
|
1136
|
+
// Execute phase with single subagent
|
|
1137
|
+
let result = {
|
|
1138
|
+
taskId: uuidv4(),
|
|
1139
|
+
success: false,
|
|
1140
|
+
output: undefined,
|
|
1141
|
+
error: "Phase execution failed",
|
|
1142
|
+
metrics: { executionTime: 0, tokensUsed: 0, toolsInvoked: 0 },
|
|
1143
|
+
artifacts: [],
|
|
1144
|
+
};
|
|
1145
|
+
let attempt = 0;
|
|
1146
|
+
const modelCandidates = resolveModelCandidates(this.config.model);
|
|
1147
|
+
while (attempt < modelCandidates.length) {
|
|
1148
|
+
try {
|
|
1149
|
+
const modelToUse = attempt === 0 ? this.config.model : modelCandidates[attempt];
|
|
1150
|
+
const subAgent = this.getOrCreateSubAgent(phase.agentType, modelToUse);
|
|
1151
|
+
const enhancedPrompt = this.enhancePromptWithContext(phase.prompt, phase.agentType, rollingContext);
|
|
1152
|
+
const subTask = {
|
|
1153
|
+
id: uuidv4(),
|
|
1154
|
+
type: phase.agentType,
|
|
1155
|
+
prompt: enhancedPrompt,
|
|
1156
|
+
context: rollingContext,
|
|
1157
|
+
successCriteria: `Complete phase: ${phase.id}`,
|
|
1158
|
+
};
|
|
1159
|
+
this.registerSubAgent(subAgent.id, subAgent);
|
|
1160
|
+
result = await this.messageBus.sendTask(this.type, phase.agentType, subTask);
|
|
1161
|
+
this.unregisterSubAgent(subAgent.id);
|
|
1162
|
+
if (result.success) {
|
|
1163
|
+
return [result];
|
|
1164
|
+
}
|
|
1165
|
+
if (result.error && isProviderModelNotFoundError(result.error)) {
|
|
1166
|
+
attempt++;
|
|
1167
|
+
continue;
|
|
1168
|
+
}
|
|
1169
|
+
return [result];
|
|
1170
|
+
}
|
|
1171
|
+
catch (error) {
|
|
1172
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
1173
|
+
if (isProviderModelNotFoundError(errorMsg)) {
|
|
1174
|
+
attempt++;
|
|
1175
|
+
if (attempt < modelCandidates.length) {
|
|
1176
|
+
continue;
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
return [
|
|
1180
|
+
{
|
|
1181
|
+
taskId: uuidv4(),
|
|
1182
|
+
success: false,
|
|
1183
|
+
output: undefined,
|
|
1184
|
+
error: errorMsg,
|
|
1185
|
+
metrics: { executionTime: 0, tokensUsed: 0, toolsInvoked: 0 },
|
|
1186
|
+
artifacts: [],
|
|
1187
|
+
},
|
|
1188
|
+
];
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
return [
|
|
1192
|
+
{
|
|
1193
|
+
taskId: uuidv4(),
|
|
1194
|
+
success: false,
|
|
1195
|
+
output: undefined,
|
|
1196
|
+
error: "All model attempts failed",
|
|
1197
|
+
metrics: { executionTime: 0, tokensUsed: 0, toolsInvoked: 0 },
|
|
1198
|
+
artifacts: [],
|
|
1199
|
+
},
|
|
1200
|
+
];
|
|
1201
|
+
}
|
|
1202
|
+
// -------------------------------------------------------------------------
|
|
1203
|
+
// Parallel Execution Helpers
|
|
1204
|
+
// -------------------------------------------------------------------------
|
|
1205
|
+
/**
|
|
1206
|
+
* Merge results from multiple parallel subagents into a single result
|
|
1207
|
+
*/
|
|
1208
|
+
mergeParallelResults(results) {
|
|
1209
|
+
if (results.length === 1) {
|
|
1210
|
+
return results[0];
|
|
1211
|
+
}
|
|
1212
|
+
// Combine all outputs, artifacts, and metrics
|
|
1213
|
+
const combinedOutputs = results
|
|
1214
|
+
.filter((r) => r.success && r.output)
|
|
1215
|
+
.map((r) => r.output);
|
|
1216
|
+
const combinedArtifacts = results.flatMap((r) => r.artifacts || []);
|
|
1217
|
+
const totalExecutionTime = results.reduce((sum, r) => sum + (r.metrics?.executionTime || 0), 0);
|
|
1218
|
+
const totalTokensUsed = results.reduce((sum, r) => sum + (r.metrics?.tokensUsed || 0), 0);
|
|
1219
|
+
const totalToolsInvoked = results.reduce((sum, r) => sum + (r.metrics?.toolsInvoked || 0), 0);
|
|
1220
|
+
// Determine overall success
|
|
1221
|
+
const allSuccessful = results.every((r) => r.success);
|
|
1222
|
+
const someSuccessful = results.some((r) => r.success);
|
|
1223
|
+
return {
|
|
1224
|
+
taskId: uuidv4(),
|
|
1225
|
+
success: allSuccessful,
|
|
1226
|
+
output: combinedOutputs.length === 1
|
|
1227
|
+
? combinedOutputs[0]
|
|
1228
|
+
: {
|
|
1229
|
+
results: combinedOutputs,
|
|
1230
|
+
summary: `Parallel execution: ${results.length} agents, ${someSuccessful ? "partial" : "no"} success`,
|
|
1231
|
+
},
|
|
1232
|
+
error: allSuccessful
|
|
1233
|
+
? undefined
|
|
1234
|
+
: `Parallel execution: ${results.filter((r) => !r.success).length} of ${results.length} agents failed`,
|
|
1235
|
+
metrics: {
|
|
1236
|
+
executionTime: totalExecutionTime,
|
|
1237
|
+
tokensUsed: totalTokensUsed,
|
|
1238
|
+
toolsInvoked: totalToolsInvoked,
|
|
1239
|
+
},
|
|
1240
|
+
artifacts: combinedArtifacts,
|
|
1241
|
+
};
|
|
1242
|
+
}
|
|
1243
|
+
// -------------------------------------------------------------------------
|
|
1244
|
+
// Context Enhancement
|
|
1245
|
+
// -------------------------------------------------------------------------
|
|
1246
|
+
enhancePromptWithContext(basePrompt, _agentType, context) {
|
|
1247
|
+
// Generate context-aware prompt using the context system
|
|
1248
|
+
const codeContext = context.files
|
|
1249
|
+
.map((f) => `File: ${f.path}\n${f.content.substring(0, 500)}...`)
|
|
1250
|
+
.slice(0, 3);
|
|
1251
|
+
const contextPrompt = this.contextSystem.generateContextPrompt(basePrompt, codeContext);
|
|
1252
|
+
// Add relevant thoughts/knowledge
|
|
1253
|
+
const relevantThoughts = this.thoughtsManager.findThoughts({
|
|
1254
|
+
keywords: this.extractKeywords(basePrompt),
|
|
1255
|
+
limit: 3,
|
|
1256
|
+
});
|
|
1257
|
+
let knowledgeContext = "";
|
|
1258
|
+
if (relevantThoughts.length > 0) {
|
|
1259
|
+
knowledgeContext = "\n\n## Relevant Knowledge\n";
|
|
1260
|
+
for (const thought of relevantThoughts) {
|
|
1261
|
+
knowledgeContext += `### ${thought.title}\n${thought.content.substring(0, 300)}...\n\n`;
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1264
|
+
// Combine everything and optimize for token efficiency
|
|
1265
|
+
const fullPrompt = `${contextPrompt}\n\n## Task\n${basePrompt}${knowledgeContext}`;
|
|
1266
|
+
// Apply DASP enhancement if enabled
|
|
1267
|
+
if (this.daspEnabled && this.daspController) {
|
|
1268
|
+
const daspResult = this.daspController.enhance({
|
|
1269
|
+
basePrompt: fullPrompt,
|
|
1270
|
+
agentType: _agentType,
|
|
1271
|
+
processingMode: context.mode,
|
|
1272
|
+
executionContext: context,
|
|
1273
|
+
searchKeywords: this.extractKeywords(basePrompt),
|
|
1274
|
+
});
|
|
1275
|
+
return daspResult.enhancedPrompt;
|
|
1276
|
+
}
|
|
1277
|
+
// Use token optimizer to ensure we stay within limits
|
|
1278
|
+
const maxTokens = 6000; // Leave room for response
|
|
1279
|
+
return optimizePromptForModel(fullPrompt, this.config.model, maxTokens);
|
|
1280
|
+
}
|
|
1281
|
+
extractKeywords(text) {
|
|
1282
|
+
// Simple keyword extraction - split by spaces and filter common words
|
|
1283
|
+
const words = text.toLowerCase().split(/\W+/);
|
|
1284
|
+
const stopWords = new Set([
|
|
1285
|
+
"the",
|
|
1286
|
+
"a",
|
|
1287
|
+
"an",
|
|
1288
|
+
"and",
|
|
1289
|
+
"or",
|
|
1290
|
+
"but",
|
|
1291
|
+
"in",
|
|
1292
|
+
"on",
|
|
1293
|
+
"at",
|
|
1294
|
+
"to",
|
|
1295
|
+
"for",
|
|
1296
|
+
"of",
|
|
1297
|
+
"with",
|
|
1298
|
+
"by",
|
|
1299
|
+
"is",
|
|
1300
|
+
"are",
|
|
1301
|
+
"was",
|
|
1302
|
+
"were",
|
|
1303
|
+
"be",
|
|
1304
|
+
"been",
|
|
1305
|
+
"being",
|
|
1306
|
+
"have",
|
|
1307
|
+
"has",
|
|
1308
|
+
"had",
|
|
1309
|
+
"do",
|
|
1310
|
+
"does",
|
|
1311
|
+
"did",
|
|
1312
|
+
"will",
|
|
1313
|
+
"would",
|
|
1314
|
+
"could",
|
|
1315
|
+
"should",
|
|
1316
|
+
"may",
|
|
1317
|
+
"might",
|
|
1318
|
+
"must",
|
|
1319
|
+
"can",
|
|
1320
|
+
"this",
|
|
1321
|
+
"that",
|
|
1322
|
+
"these",
|
|
1323
|
+
"those",
|
|
1324
|
+
]);
|
|
1325
|
+
return words.filter((word) => word.length > 2 && !stopWords.has(word));
|
|
1326
|
+
}
|
|
1327
|
+
// -------------------------------------------------------------------------
|
|
1328
|
+
// Sub-Agent Lifecycle
|
|
1329
|
+
// -------------------------------------------------------------------------
|
|
1330
|
+
/** Get a cached sub-agent or create a fresh one from the factory */
|
|
1331
|
+
getOrCreateSubAgent(type, model) {
|
|
1332
|
+
// Reuse an idle registered agent of this type if available
|
|
1333
|
+
for (const [, agent] of this.activeSubAgents) {
|
|
1334
|
+
if (agent.type === type &&
|
|
1335
|
+
agent.getStatus().status === "idle" &&
|
|
1336
|
+
agent instanceof SubAgent &&
|
|
1337
|
+
(!model || agent.config.model === model)) {
|
|
1338
|
+
return agent;
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
const factory = this.subAgentFactory.get(type);
|
|
1342
|
+
if (!factory)
|
|
1343
|
+
throw new Error(`No factory registered for agent type: ${type}`);
|
|
1344
|
+
const agent = factory();
|
|
1345
|
+
if (!(agent instanceof SubAgent))
|
|
1346
|
+
throw new Error(`Factory for ${type} did not return a SubAgent`);
|
|
1347
|
+
// Set model dynamically for this execution
|
|
1348
|
+
if (model) {
|
|
1349
|
+
agent.config.model = model;
|
|
1350
|
+
}
|
|
1351
|
+
// Start the message loop so the agent can receive tasks from Master
|
|
1352
|
+
const stopFn = agent.startListening();
|
|
1353
|
+
this.listenerStops.set(agent.id, stopFn);
|
|
1354
|
+
return agent;
|
|
1355
|
+
}
|
|
1356
|
+
/** Collect artifacts from master and all registered sub-agents */
|
|
1357
|
+
collectAllArtifacts() {
|
|
1358
|
+
const all = [...this.artifacts];
|
|
1359
|
+
for (const [, agent] of this.activeSubAgents) {
|
|
1360
|
+
all.push(...agent.getArtifacts());
|
|
1361
|
+
}
|
|
1362
|
+
return all;
|
|
1363
|
+
}
|
|
1364
|
+
synthesizeOutput(phases) {
|
|
1365
|
+
const summary = {
|
|
1366
|
+
totalPhases: phases.length,
|
|
1367
|
+
successfulPhases: phases.filter((p) => p.result.success).length,
|
|
1368
|
+
failedPhases: phases.filter((p) => !p.result.success).length,
|
|
1369
|
+
phases: phases.map((p) => ({
|
|
1370
|
+
id: p.phaseId,
|
|
1371
|
+
agent: p.agentType,
|
|
1372
|
+
success: p.result.success,
|
|
1373
|
+
output: p.result.output,
|
|
1374
|
+
error: p.result.error,
|
|
1375
|
+
artifacts: p.result.artifacts.length,
|
|
1376
|
+
gatekeeperDecision: p.gatekeeperReview?.decision || "none",
|
|
1377
|
+
gatekeeperConfidence: p.gatekeeperReview?.confidence || 0,
|
|
1378
|
+
gatekeeperIssues: p.gatekeeperReview?.issues.length || 0,
|
|
1379
|
+
gatekeeperSuggestions: p.gatekeeperReview?.suggestions.length || 0,
|
|
1380
|
+
})),
|
|
1381
|
+
gatekeeper: {
|
|
1382
|
+
enabled: this.enableGatekeeper,
|
|
1383
|
+
totalReviews: phases.filter((p) => p.gatekeeperReview).length,
|
|
1384
|
+
passedReviews: phases.filter((p) => p.gatekeeperReview?.decision === "approve").length,
|
|
1385
|
+
revisionRequests: phases.filter((p) => p.gatekeeperReview?.decision === "request_revision").length,
|
|
1386
|
+
},
|
|
1387
|
+
};
|
|
1388
|
+
return summary;
|
|
1389
|
+
}
|
|
1390
|
+
// ========================================================================
|
|
1391
|
+
// Sub-Agent Registry
|
|
1392
|
+
// ========================================================================
|
|
1393
|
+
registerSubAgent(agentId, agent) {
|
|
1394
|
+
this.activeSubAgents.set(agentId, agent);
|
|
1395
|
+
agent.on("statusChange", (status) => {
|
|
1396
|
+
this.emit("subAgentStatusChange", agentId, status);
|
|
1397
|
+
});
|
|
1398
|
+
}
|
|
1399
|
+
unregisterSubAgent(agentId) {
|
|
1400
|
+
const agent = this.activeSubAgents.get(agentId);
|
|
1401
|
+
if (agent) {
|
|
1402
|
+
// Stop the message loop
|
|
1403
|
+
const stopFn = this.listenerStops.get(agentId);
|
|
1404
|
+
if (stopFn) {
|
|
1405
|
+
stopFn();
|
|
1406
|
+
this.listenerStops.delete(agentId);
|
|
1407
|
+
}
|
|
1408
|
+
agent.stop();
|
|
1409
|
+
this.activeSubAgents.delete(agentId);
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
getActiveSubAgents() {
|
|
1413
|
+
return new Map(this.activeSubAgents);
|
|
1414
|
+
}
|
|
1415
|
+
// ========================================================================
|
|
1416
|
+
// Context and Knowledge Management
|
|
1417
|
+
// ========================================================================
|
|
1418
|
+
getContextSystem() {
|
|
1419
|
+
return this.contextSystem;
|
|
1420
|
+
}
|
|
1421
|
+
getThoughtsManager() {
|
|
1422
|
+
return this.thoughtsManager;
|
|
1423
|
+
}
|
|
1424
|
+
getAgentConfigManager() {
|
|
1425
|
+
return this.agentConfigManager;
|
|
1426
|
+
}
|
|
1427
|
+
getCommandProcessor() {
|
|
1428
|
+
return this.commandProcessor;
|
|
1429
|
+
}
|
|
1430
|
+
getTeamSystem() {
|
|
1431
|
+
return this.teamSystem;
|
|
1432
|
+
}
|
|
1433
|
+
getPluginManager() {
|
|
1434
|
+
return this.pluginManager;
|
|
1435
|
+
}
|
|
1436
|
+
getTokenOptimizer() {
|
|
1437
|
+
return this.tokenOptimizer;
|
|
1438
|
+
}
|
|
1439
|
+
// ========================================================================
|
|
1440
|
+
// Gatekeeper Configuration
|
|
1441
|
+
// ========================================================================
|
|
1442
|
+
/**
|
|
1443
|
+
* Enable or disable gatekeeper review
|
|
1444
|
+
*/
|
|
1445
|
+
setGatekeeperEnabled(enabled) {
|
|
1446
|
+
this.enableGatekeeper = enabled;
|
|
1447
|
+
console.log(`Gatekeeper ${enabled ? "enabled" : "disabled"}`);
|
|
1448
|
+
}
|
|
1449
|
+
/**
|
|
1450
|
+
* Check if gatekeeper is enabled
|
|
1451
|
+
*/
|
|
1452
|
+
isGatekeeperEnabled() {
|
|
1453
|
+
return this.enableGatekeeper;
|
|
1454
|
+
}
|
|
1455
|
+
/**
|
|
1456
|
+
* Get the gatekeeper coordinator instance
|
|
1457
|
+
*/
|
|
1458
|
+
getGatekeeperCoordinator() {
|
|
1459
|
+
return this.gatekeeperCoordinator;
|
|
1460
|
+
}
|
|
1461
|
+
/**
|
|
1462
|
+
* Configure gatekeeper review level
|
|
1463
|
+
*/
|
|
1464
|
+
setGatekeeperReviewLevel(level) {
|
|
1465
|
+
this.gatekeeperReviewLevel = level;
|
|
1466
|
+
if (this.gatekeeperCoordinator) {
|
|
1467
|
+
// Update all gatekeeper agents with new level
|
|
1468
|
+
const statuses = this.gatekeeperCoordinator.getGatekeeperStatuses();
|
|
1469
|
+
console.log(`Gatekeeper review level set to: ${level} (${statuses.size} gatekeepers available)`);
|
|
1470
|
+
}
|
|
1471
|
+
}
|
|
1472
|
+
/**
|
|
1473
|
+
* Set maximum number of revision attempts per phase
|
|
1474
|
+
*/
|
|
1475
|
+
setGatekeeperMaxRevisions(max) {
|
|
1476
|
+
this.gatekeeperMaxRevisions = Math.max(1, Math.min(10, max));
|
|
1477
|
+
console.log(`Gatekeeper max revisions set to: ${this.gatekeeperMaxRevisions}`);
|
|
1478
|
+
}
|
|
1479
|
+
/**
|
|
1480
|
+
* Get current gatekeeper configuration
|
|
1481
|
+
*/
|
|
1482
|
+
getGatekeeperConfig() {
|
|
1483
|
+
return {
|
|
1484
|
+
enabled: this.enableGatekeeper,
|
|
1485
|
+
reviewLevel: this.gatekeeperReviewLevel,
|
|
1486
|
+
maxRevisions: this.gatekeeperMaxRevisions,
|
|
1487
|
+
coordinatorStatus: this.gatekeeperCoordinator?.getGatekeeperStatuses() || null,
|
|
1488
|
+
};
|
|
1489
|
+
}
|
|
1490
|
+
// ========================================================================
|
|
1491
|
+
// Command Processing
|
|
1492
|
+
// ========================================================================
|
|
1493
|
+
async processCommand(command) {
|
|
1494
|
+
return this.commandProcessor.processCommand(command);
|
|
1495
|
+
}
|
|
1496
|
+
// Store execution results as knowledge
|
|
1497
|
+
async storeExecutionKnowledge(phaseId, agentType, result, task) {
|
|
1498
|
+
if (!result.success || !result.output)
|
|
1499
|
+
return;
|
|
1500
|
+
try {
|
|
1501
|
+
// Determine thought type based on phase
|
|
1502
|
+
let thoughtType;
|
|
1503
|
+
switch (phaseId) {
|
|
1504
|
+
case "plan":
|
|
1505
|
+
thoughtType = "plan";
|
|
1506
|
+
break;
|
|
1507
|
+
case "analyze":
|
|
1508
|
+
thoughtType = "analysis";
|
|
1509
|
+
break;
|
|
1510
|
+
case "review":
|
|
1511
|
+
thoughtType = "review";
|
|
1512
|
+
break;
|
|
1513
|
+
case "research":
|
|
1514
|
+
thoughtType = "research";
|
|
1515
|
+
break;
|
|
1516
|
+
default:
|
|
1517
|
+
thoughtType = "decision";
|
|
1518
|
+
}
|
|
1519
|
+
// Create thought from execution result
|
|
1520
|
+
this.thoughtsManager.createThought({
|
|
1521
|
+
title: `${phaseId.charAt(0).toUpperCase() + phaseId.slice(1)}: ${task.prompt.substring(0, 50)}...`,
|
|
1522
|
+
type: thoughtType,
|
|
1523
|
+
content: typeof result.output === "string"
|
|
1524
|
+
? result.output
|
|
1525
|
+
: JSON.stringify(result.output, null, 2),
|
|
1526
|
+
tags: [agentType, phaseId, "execution"],
|
|
1527
|
+
relatedFiles: task.context.files.map((f) => f.path),
|
|
1528
|
+
});
|
|
1529
|
+
// Learn patterns from successful executions
|
|
1530
|
+
if (result.success && typeof result.output === "object") {
|
|
1531
|
+
this.contextSystem.addPattern({
|
|
1532
|
+
type: "workflow",
|
|
1533
|
+
name: `${agentType} ${phaseId} pattern`,
|
|
1534
|
+
description: `Successful ${phaseId} execution pattern for ${agentType}`,
|
|
1535
|
+
examples: [
|
|
1536
|
+
{
|
|
1537
|
+
input: task.prompt,
|
|
1538
|
+
output: JSON.stringify(result.output),
|
|
1539
|
+
explanation: `Generated by ${agentType} during ${phaseId} phase`,
|
|
1540
|
+
},
|
|
1541
|
+
],
|
|
1542
|
+
tags: [agentType, phaseId, "pattern"],
|
|
1543
|
+
});
|
|
1544
|
+
}
|
|
1545
|
+
}
|
|
1546
|
+
catch (error) {
|
|
1547
|
+
console.warn("Failed to store execution knowledge:", error);
|
|
1548
|
+
}
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1551
|
+
// ============================================================================
|
|
1552
|
+
// Sub Agent Base
|
|
1553
|
+
// ============================================================================
|
|
1554
|
+
export class SubAgent extends BaseAgent {
|
|
1555
|
+
masterAgent = null;
|
|
1556
|
+
constructor(type, config, messageBus, toolBridge) {
|
|
1557
|
+
super(type, { mode: "subagent", ...config }, messageBus, toolBridge);
|
|
1558
|
+
}
|
|
1559
|
+
setMasterAgent(master) {
|
|
1560
|
+
this.masterAgent = master;
|
|
1561
|
+
}
|
|
1562
|
+
/**
|
|
1563
|
+
* Start the sub-agent's message loop.
|
|
1564
|
+
* Listens for task_dispatch messages addressed to this agent type and processes them.
|
|
1565
|
+
* Returns a stop function to cancel the loop.
|
|
1566
|
+
*/
|
|
1567
|
+
startListening() {
|
|
1568
|
+
let active = true;
|
|
1569
|
+
const loop = async () => {
|
|
1570
|
+
while (active) {
|
|
1571
|
+
const msg = await this.messageBus.receive(this.type, 500);
|
|
1572
|
+
if (!msg)
|
|
1573
|
+
continue;
|
|
1574
|
+
if (msg.type === "task_dispatch") {
|
|
1575
|
+
const task = msg.payload;
|
|
1576
|
+
const result = await this.execute(task);
|
|
1577
|
+
// Reply to Master with the result, correlating to the dispatch message
|
|
1578
|
+
await this.messageBus.sendTaskResult(this.type, msg.sender, result, msg.id);
|
|
1579
|
+
}
|
|
1580
|
+
else if (msg.type === "abort_signal") {
|
|
1581
|
+
await this.abort("abort_signal received");
|
|
1582
|
+
break;
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1585
|
+
};
|
|
1586
|
+
// Run the loop in the background (fire-and-forget)
|
|
1587
|
+
loop().catch((err) => {
|
|
1588
|
+
console.error(`[${this.type}] Message loop error:`, err);
|
|
1589
|
+
});
|
|
1590
|
+
return () => {
|
|
1591
|
+
active = false;
|
|
1592
|
+
};
|
|
1593
|
+
}
|
|
1594
|
+
async execute(task) {
|
|
1595
|
+
const startTime = Date.now();
|
|
1596
|
+
await this.start();
|
|
1597
|
+
try {
|
|
1598
|
+
const result = await this.performTask(task);
|
|
1599
|
+
const executionTime = Date.now() - startTime;
|
|
1600
|
+
this.recordTaskCompletion(0, 0, executionTime);
|
|
1601
|
+
this.updateStatus("completed");
|
|
1602
|
+
return {
|
|
1603
|
+
taskId: task.id,
|
|
1604
|
+
success: true,
|
|
1605
|
+
output: result,
|
|
1606
|
+
metrics: { executionTime, tokensUsed: 0, toolsInvoked: 0 },
|
|
1607
|
+
artifacts: this.artifacts,
|
|
1608
|
+
};
|
|
1609
|
+
}
|
|
1610
|
+
catch (error) {
|
|
1611
|
+
this.recordTaskFailure();
|
|
1612
|
+
this.updateStatus("error");
|
|
1613
|
+
return {
|
|
1614
|
+
taskId: task.id,
|
|
1615
|
+
success: false,
|
|
1616
|
+
output: undefined,
|
|
1617
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1618
|
+
metrics: {
|
|
1619
|
+
executionTime: Date.now() - startTime,
|
|
1620
|
+
tokensUsed: 0,
|
|
1621
|
+
toolsInvoked: 0,
|
|
1622
|
+
},
|
|
1623
|
+
artifacts: [],
|
|
1624
|
+
};
|
|
1625
|
+
}
|
|
1626
|
+
finally {
|
|
1627
|
+
await this.stop();
|
|
1628
|
+
}
|
|
1629
|
+
}
|
|
1630
|
+
}
|
|
1631
|
+
// ============================================================================
|
|
1632
|
+
// Specialized Sub-Agents
|
|
1633
|
+
// ============================================================================
|
|
1634
|
+
export class PlannerAgent extends SubAgent {
|
|
1635
|
+
async performTask(task) {
|
|
1636
|
+
this.updateStatus("running", "Creating execution plan");
|
|
1637
|
+
const prompt = `Create a detailed execution plan for the following task:
|
|
1638
|
+
|
|
1639
|
+
Task: ${task.prompt}
|
|
1640
|
+
|
|
1641
|
+
Context: ${task.context.files.length} files provided, mode: ${task.context.metadata?.mode || "full"}
|
|
1642
|
+
|
|
1643
|
+
Please provide a structured plan with:
|
|
1644
|
+
1. Goal analysis
|
|
1645
|
+
2. Step-by-step breakdown
|
|
1646
|
+
3. Estimated complexity (low/medium/high)
|
|
1647
|
+
4. Estimated time
|
|
1648
|
+
5. Required tools/resources
|
|
1649
|
+
6. Potential risks
|
|
1650
|
+
|
|
1651
|
+
Format as structured JSON.`;
|
|
1652
|
+
const response = await this.callModel(prompt);
|
|
1653
|
+
if (!response) {
|
|
1654
|
+
return this.localPlanFallback(task);
|
|
1655
|
+
}
|
|
1656
|
+
try {
|
|
1657
|
+
return JSON.parse(response);
|
|
1658
|
+
}
|
|
1659
|
+
catch {
|
|
1660
|
+
return {
|
|
1661
|
+
goal: task.prompt,
|
|
1662
|
+
plan: response,
|
|
1663
|
+
...this.estimateComplexityFromText(task.prompt),
|
|
1664
|
+
};
|
|
1665
|
+
}
|
|
1666
|
+
}
|
|
1667
|
+
/**
|
|
1668
|
+
* Local fallback: parse task description to build a real plan without a model.
|
|
1669
|
+
*/
|
|
1670
|
+
localPlanFallback(task) {
|
|
1671
|
+
const description = task.prompt || "";
|
|
1672
|
+
const lower = description.toLowerCase();
|
|
1673
|
+
const fileCount = task.context.files.length;
|
|
1674
|
+
// --- Extract action verbs and targets ---
|
|
1675
|
+
const actionPatterns = [
|
|
1676
|
+
{
|
|
1677
|
+
verb: "create",
|
|
1678
|
+
regex: /\b(?:create|build|generate|scaffold|add|implement|write)\s+(?:a\s+)?(.+?)(?:\s+(?:and|then|,|\.)|$)/gi,
|
|
1679
|
+
},
|
|
1680
|
+
{
|
|
1681
|
+
verb: "refactor",
|
|
1682
|
+
regex: /\b(?:refactor|restructure|reorganize|redesign)\s+(?:the\s+)?(.+?)(?:\s+(?:and|then|,|\.)|$)/gi,
|
|
1683
|
+
},
|
|
1684
|
+
{
|
|
1685
|
+
verb: "fix",
|
|
1686
|
+
regex: /\b(?:fix|repair|resolve|patch|debug)\s+(?:the\s+)?(.+?)(?:\s+(?:and|then|,|\.)|$)/gi,
|
|
1687
|
+
},
|
|
1688
|
+
{
|
|
1689
|
+
verb: "update",
|
|
1690
|
+
regex: /\b(?:update|modify|change|edit|adjust|improve)\s+(?:the\s+)?(.+?)(?:\s+(?:and|then|,|\.)|$)/gi,
|
|
1691
|
+
},
|
|
1692
|
+
{
|
|
1693
|
+
verb: "test",
|
|
1694
|
+
regex: /\b(?:test|verify|validate|check)\s+(?:the\s+)?(.+?)(?:\s+(?:and|then|,|\.)|$)/gi,
|
|
1695
|
+
},
|
|
1696
|
+
{
|
|
1697
|
+
verb: "delete",
|
|
1698
|
+
regex: /\b(?:delete|remove|drop|clean\s*up)\s+(?:the\s+)?(.+?)(?:\s+(?:and|then|,|\.)|$)/gi,
|
|
1699
|
+
},
|
|
1700
|
+
{
|
|
1701
|
+
verb: "migrate",
|
|
1702
|
+
regex: /\b(?:migrate|move|transfer|port)\s+(?:the\s+)?(.+?)(?:\s+(?:and|then|,|\.)|$)/gi,
|
|
1703
|
+
},
|
|
1704
|
+
{
|
|
1705
|
+
verb: "deploy",
|
|
1706
|
+
regex: /\b(?:deploy|publish|release|ship)\s+(?:the\s+)?(.+?)(?:\s+(?:and|then|,|\.)|$)/gi,
|
|
1707
|
+
},
|
|
1708
|
+
{
|
|
1709
|
+
verb: "analyze",
|
|
1710
|
+
regex: /\b(?:analyze|inspect|examine|review|audit)\s+(?:the\s+)?(.+?)(?:\s+(?:and|then|,|\.)|$)/gi,
|
|
1711
|
+
},
|
|
1712
|
+
{
|
|
1713
|
+
verb: "configure",
|
|
1714
|
+
regex: /\b(?:configure|setup|set\s*up|initialize|install)\s+(?:the\s+)?(.+?)(?:\s+(?:and|then|,|\.)|$)/gi,
|
|
1715
|
+
},
|
|
1716
|
+
];
|
|
1717
|
+
const extractedActions = [];
|
|
1718
|
+
for (const { verb, regex } of actionPatterns) {
|
|
1719
|
+
let match;
|
|
1720
|
+
while ((match = regex.exec(description)) !== null) {
|
|
1721
|
+
const target = match[1]?.trim().substring(0, 80) || "the task";
|
|
1722
|
+
if (target.length > 2) {
|
|
1723
|
+
extractedActions.push({ verb, target });
|
|
1724
|
+
}
|
|
1725
|
+
}
|
|
1726
|
+
}
|
|
1727
|
+
// --- Build steps from extracted actions or sentence splitting ---
|
|
1728
|
+
let steps;
|
|
1729
|
+
if (extractedActions.length > 0) {
|
|
1730
|
+
steps = extractedActions.map((action, idx) => ({
|
|
1731
|
+
step: idx + 1,
|
|
1732
|
+
action: action.verb,
|
|
1733
|
+
description: `${action.verb.charAt(0).toUpperCase() + action.verb.slice(1)} ${action.target}`,
|
|
1734
|
+
dependsOn: idx > 0 ? [idx] : [],
|
|
1735
|
+
}));
|
|
1736
|
+
}
|
|
1737
|
+
else {
|
|
1738
|
+
// Split by conjunctions, commas, or sentence boundaries
|
|
1739
|
+
const segments = description
|
|
1740
|
+
.split(/(?:\s+and\s+|\s*,\s*|\.\s+|\s+then\s+)/i)
|
|
1741
|
+
.map((s) => s.trim())
|
|
1742
|
+
.filter((s) => s.length > 3);
|
|
1743
|
+
if (segments.length > 1) {
|
|
1744
|
+
steps = segments.map((seg, idx) => ({
|
|
1745
|
+
step: idx + 1,
|
|
1746
|
+
action: "execute",
|
|
1747
|
+
description: seg.charAt(0).toUpperCase() + seg.slice(1),
|
|
1748
|
+
dependsOn: idx > 0 ? [idx] : [],
|
|
1749
|
+
}));
|
|
1750
|
+
}
|
|
1751
|
+
else {
|
|
1752
|
+
// Single task — generate analysis → implementation → verification steps
|
|
1753
|
+
steps = [
|
|
1754
|
+
{
|
|
1755
|
+
step: 1,
|
|
1756
|
+
action: "analyze",
|
|
1757
|
+
description: `Analyze requirements: ${description.substring(0, 100)}`,
|
|
1758
|
+
dependsOn: [],
|
|
1759
|
+
},
|
|
1760
|
+
{
|
|
1761
|
+
step: 2,
|
|
1762
|
+
action: "implement",
|
|
1763
|
+
description: `Implement changes for: ${description.substring(0, 80)}`,
|
|
1764
|
+
dependsOn: [1],
|
|
1765
|
+
},
|
|
1766
|
+
{
|
|
1767
|
+
step: 3,
|
|
1768
|
+
action: "verify",
|
|
1769
|
+
description: "Verify implementation and check for regressions",
|
|
1770
|
+
dependsOn: [2],
|
|
1771
|
+
},
|
|
1772
|
+
];
|
|
1773
|
+
}
|
|
1774
|
+
}
|
|
1775
|
+
// Add file-scope step if files are present
|
|
1776
|
+
if (fileCount > 0 && steps.length > 0) {
|
|
1777
|
+
steps.unshift({
|
|
1778
|
+
step: 0,
|
|
1779
|
+
action: "scan",
|
|
1780
|
+
description: `Scan ${fileCount} provided file(s) for context`,
|
|
1781
|
+
dependsOn: [],
|
|
1782
|
+
});
|
|
1783
|
+
// Re-number and adjust deps
|
|
1784
|
+
steps = steps.map((s, idx) => ({
|
|
1785
|
+
...s,
|
|
1786
|
+
step: idx + 1,
|
|
1787
|
+
dependsOn: idx === 0 ? [] : s.dependsOn.map((d) => d + 1),
|
|
1788
|
+
}));
|
|
1789
|
+
}
|
|
1790
|
+
// --- Estimate complexity ---
|
|
1791
|
+
const complexity = this.estimateComplexityFromText(lower);
|
|
1792
|
+
// --- Required tools based on keywords ---
|
|
1793
|
+
const tools = [];
|
|
1794
|
+
if (/\b(file|read|write|edit|create)\b/i.test(lower))
|
|
1795
|
+
tools.push("file_operations");
|
|
1796
|
+
if (/\b(test|spec|jest|mocha|vitest)\b/i.test(lower))
|
|
1797
|
+
tools.push("testing_framework");
|
|
1798
|
+
if (/\b(git|commit|branch|merge|pr)\b/i.test(lower))
|
|
1799
|
+
tools.push("version_control");
|
|
1800
|
+
if (/\b(npm|yarn|pnpm|install|package)\b/i.test(lower))
|
|
1801
|
+
tools.push("package_manager");
|
|
1802
|
+
if (/\b(api|endpoint|http|fetch|request)\b/i.test(lower))
|
|
1803
|
+
tools.push("http_client");
|
|
1804
|
+
if (/\b(db|database|sql|query|schema|migration)\b/i.test(lower))
|
|
1805
|
+
tools.push("database");
|
|
1806
|
+
if (/\b(docker|container|deploy|ci|cd)\b/i.test(lower))
|
|
1807
|
+
tools.push("devops");
|
|
1808
|
+
if (tools.length === 0)
|
|
1809
|
+
tools.push("code_editor");
|
|
1810
|
+
// --- Risks ---
|
|
1811
|
+
const risks = [];
|
|
1812
|
+
if (/\b(refactor|migrate|restructure)\b/i.test(lower))
|
|
1813
|
+
risks.push("Breaking changes in dependent code");
|
|
1814
|
+
if (/\b(delete|remove|drop)\b/i.test(lower))
|
|
1815
|
+
risks.push("Data or code loss if not backed up");
|
|
1816
|
+
if (/\b(security|auth|password|token|secret)\b/i.test(lower))
|
|
1817
|
+
risks.push("Security-sensitive changes require careful review");
|
|
1818
|
+
if (fileCount > 10)
|
|
1819
|
+
risks.push(`Large scope: ${fileCount} files may have cascading impacts`);
|
|
1820
|
+
if (/\b(production|prod|live)\b/i.test(lower))
|
|
1821
|
+
risks.push("Changes target production environment");
|
|
1822
|
+
if (risks.length === 0)
|
|
1823
|
+
risks.push("Standard development risk — verify with tests");
|
|
1824
|
+
return {
|
|
1825
|
+
goal: task.prompt,
|
|
1826
|
+
steps,
|
|
1827
|
+
...complexity,
|
|
1828
|
+
requiredTools: tools,
|
|
1829
|
+
risks,
|
|
1830
|
+
fileScope: fileCount,
|
|
1831
|
+
note: "Plan generated via local keyword analysis (no model available)",
|
|
1832
|
+
};
|
|
1833
|
+
}
|
|
1834
|
+
/**
|
|
1835
|
+
* Estimate complexity from task description text.
|
|
1836
|
+
*/
|
|
1837
|
+
estimateComplexityFromText(text) {
|
|
1838
|
+
const lower = text.toLowerCase();
|
|
1839
|
+
const wordCount = text.split(/\s+/).length;
|
|
1840
|
+
// High complexity indicators
|
|
1841
|
+
const highKeywords = /\b(refactor|migrate|integrate|redesign|architect|overhaul|rewrite|restructure|distributed|concurrent|multi.?thread)\b/i;
|
|
1842
|
+
// Low complexity indicators
|
|
1843
|
+
const lowKeywords = /\b(rename|move|delete|remove|typo|comment|log|print|bump|update version|fix typo)\b/i;
|
|
1844
|
+
let complexity;
|
|
1845
|
+
let time;
|
|
1846
|
+
if (highKeywords.test(lower) || wordCount > 80) {
|
|
1847
|
+
complexity = "high";
|
|
1848
|
+
time = "15-30 minutes";
|
|
1849
|
+
}
|
|
1850
|
+
else if (lowKeywords.test(lower) && wordCount < 20) {
|
|
1851
|
+
complexity = "low";
|
|
1852
|
+
time = "1-3 minutes";
|
|
1853
|
+
}
|
|
1854
|
+
else {
|
|
1855
|
+
complexity = "medium";
|
|
1856
|
+
time = "5-10 minutes";
|
|
1857
|
+
}
|
|
1858
|
+
return { estimatedComplexity: complexity, estimatedTime: time };
|
|
1859
|
+
}
|
|
1860
|
+
}
|
|
1861
|
+
export class AnalyzerAgent extends SubAgent {
|
|
1862
|
+
async performTask(task) {
|
|
1863
|
+
this.updateStatus("running", "Analyzing code");
|
|
1864
|
+
const files = task.context.files;
|
|
1865
|
+
const fileContents = files
|
|
1866
|
+
.map((f) => `File: ${f.path}\n\n${f.content}`)
|
|
1867
|
+
.join("\n\n---\n\n");
|
|
1868
|
+
const prompt = `Analyze the following code files for issues, patterns, and improvements:
|
|
1869
|
+
|
|
1870
|
+
${fileContents}
|
|
1871
|
+
|
|
1872
|
+
Please provide:
|
|
1873
|
+
1. Code quality issues
|
|
1874
|
+
2. Security vulnerabilities
|
|
1875
|
+
3. Performance bottlenecks
|
|
1876
|
+
4. Best practice violations
|
|
1877
|
+
5. Complexity metrics
|
|
1878
|
+
6. Maintainability assessment
|
|
1879
|
+
|
|
1880
|
+
Format as structured JSON.`;
|
|
1881
|
+
const response = await this.callModel(prompt);
|
|
1882
|
+
if (!response) {
|
|
1883
|
+
return this.localAnalysisFallback(files);
|
|
1884
|
+
}
|
|
1885
|
+
// Parse AI response
|
|
1886
|
+
try {
|
|
1887
|
+
const analysis = JSON.parse(response);
|
|
1888
|
+
return analysis;
|
|
1889
|
+
}
|
|
1890
|
+
catch {
|
|
1891
|
+
return {
|
|
1892
|
+
file: task.context.files[0]?.path || "unknown",
|
|
1893
|
+
analysis: response,
|
|
1894
|
+
issues: [],
|
|
1895
|
+
metrics: this.computeFileMetrics(files),
|
|
1896
|
+
};
|
|
1897
|
+
}
|
|
1898
|
+
}
|
|
1899
|
+
/**
|
|
1900
|
+
* Local fallback: perform real static analysis on provided files without a model.
|
|
1901
|
+
*/
|
|
1902
|
+
localAnalysisFallback(files) {
|
|
1903
|
+
const allIssues = [];
|
|
1904
|
+
const perFileMetrics = [];
|
|
1905
|
+
for (const file of files) {
|
|
1906
|
+
const content = file.content || "";
|
|
1907
|
+
const lines = content.split("\n");
|
|
1908
|
+
const lineCount = lines.length;
|
|
1909
|
+
// --- Basic metrics ---
|
|
1910
|
+
let blankLines = 0;
|
|
1911
|
+
let commentLines = 0;
|
|
1912
|
+
let importCount = 0;
|
|
1913
|
+
let exportCount = 0;
|
|
1914
|
+
// --- Function/class detection ---
|
|
1915
|
+
const functionMatches = content.match(/\b(?:function\s+\w+|(?:const|let|var)\s+\w+\s*=\s*(?:async\s+)?(?:\([^)]*\)|[a-zA-Z_$]\w*)\s*=>|(?:async\s+)?(?:get|set|static\s+)?\w+\s*\([^)]*\)\s*\{)/g) || [];
|
|
1916
|
+
const classMatches = content.match(/\bclass\s+\w+/g) || [];
|
|
1917
|
+
// --- Cyclomatic complexity (count decision points) ---
|
|
1918
|
+
let cyclomaticComplexity = 1; // Base
|
|
1919
|
+
const decisionPatterns = /\b(?:if|else\s+if|case|for|while|do|catch|\?\?|&&|\|\||[?]:)/g;
|
|
1920
|
+
// @ts-ignore
|
|
1921
|
+
let _decMatch;
|
|
1922
|
+
while ((_decMatch = decisionPatterns.exec(content)) !== null) {
|
|
1923
|
+
cyclomaticComplexity++;
|
|
1924
|
+
}
|
|
1925
|
+
// --- Nesting depth analysis ---
|
|
1926
|
+
let maxNesting = 0;
|
|
1927
|
+
let currentNesting = 0;
|
|
1928
|
+
for (const ch of content) {
|
|
1929
|
+
if (ch === "{") {
|
|
1930
|
+
currentNesting++;
|
|
1931
|
+
if (currentNesting > maxNesting)
|
|
1932
|
+
maxNesting = currentNesting;
|
|
1933
|
+
}
|
|
1934
|
+
else if (ch === "}") {
|
|
1935
|
+
currentNesting = Math.max(0, currentNesting - 1);
|
|
1936
|
+
}
|
|
1937
|
+
}
|
|
1938
|
+
// --- Longest function length ---
|
|
1939
|
+
let longestFn = 0;
|
|
1940
|
+
let fnNesting = 0;
|
|
1941
|
+
let fnStartLine = -1;
|
|
1942
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1943
|
+
const line = lines[i];
|
|
1944
|
+
const trimmed = line.trim();
|
|
1945
|
+
// Track blank/comment/import/export lines
|
|
1946
|
+
if (trimmed === "") {
|
|
1947
|
+
blankLines++;
|
|
1948
|
+
}
|
|
1949
|
+
else if (trimmed.startsWith("//") ||
|
|
1950
|
+
trimmed.startsWith("/*") ||
|
|
1951
|
+
trimmed.startsWith("*")) {
|
|
1952
|
+
commentLines++;
|
|
1953
|
+
}
|
|
1954
|
+
if (/^import\s/.test(trimmed))
|
|
1955
|
+
importCount++;
|
|
1956
|
+
if (/^export\s/.test(trimmed))
|
|
1957
|
+
exportCount++;
|
|
1958
|
+
// Simple function length tracking
|
|
1959
|
+
if (/(?:function\s+\w+|=>\s*\{|\)\s*\{)\s*$/.test(trimmed) &&
|
|
1960
|
+
fnStartLine === -1) {
|
|
1961
|
+
fnStartLine = i;
|
|
1962
|
+
fnNesting = 0;
|
|
1963
|
+
}
|
|
1964
|
+
if (fnStartLine !== -1) {
|
|
1965
|
+
for (const ch of line) {
|
|
1966
|
+
if (ch === "{")
|
|
1967
|
+
fnNesting++;
|
|
1968
|
+
if (ch === "}")
|
|
1969
|
+
fnNesting--;
|
|
1970
|
+
}
|
|
1971
|
+
if (fnNesting <= 0 && fnStartLine !== -1) {
|
|
1972
|
+
const fnLen = i - fnStartLine + 1;
|
|
1973
|
+
if (fnLen > longestFn)
|
|
1974
|
+
longestFn = fnLen;
|
|
1975
|
+
fnStartLine = -1;
|
|
1976
|
+
}
|
|
1977
|
+
}
|
|
1978
|
+
}
|
|
1979
|
+
perFileMetrics.push({
|
|
1980
|
+
file: file.path,
|
|
1981
|
+
linesOfCode: lineCount,
|
|
1982
|
+
blankLines,
|
|
1983
|
+
commentLines,
|
|
1984
|
+
functions: functionMatches.length,
|
|
1985
|
+
classes: classMatches.length,
|
|
1986
|
+
imports: importCount,
|
|
1987
|
+
exports: exportCount,
|
|
1988
|
+
cyclomaticComplexity,
|
|
1989
|
+
maxNestingDepth: maxNesting,
|
|
1990
|
+
longestFunctionLength: longestFn,
|
|
1991
|
+
});
|
|
1992
|
+
// --- Issue detection ---
|
|
1993
|
+
// Long functions (>50 lines)
|
|
1994
|
+
if (longestFn > 50) {
|
|
1995
|
+
allIssues.push({
|
|
1996
|
+
type: "maintainability",
|
|
1997
|
+
severity: longestFn > 100 ? "high" : "medium",
|
|
1998
|
+
file: file.path,
|
|
1999
|
+
description: `Longest function is ${longestFn} lines (threshold: 50)`,
|
|
2000
|
+
recommendation: "Break large functions into smaller, focused helper functions",
|
|
2001
|
+
});
|
|
2002
|
+
}
|
|
2003
|
+
// Deep nesting (>4 levels)
|
|
2004
|
+
if (maxNesting > 4) {
|
|
2005
|
+
allIssues.push({
|
|
2006
|
+
type: "complexity",
|
|
2007
|
+
severity: maxNesting > 6 ? "high" : "medium",
|
|
2008
|
+
file: file.path,
|
|
2009
|
+
description: `Maximum nesting depth is ${maxNesting} levels (threshold: 4)`,
|
|
2010
|
+
recommendation: "Use early returns, guard clauses, or extract nested logic into functions",
|
|
2011
|
+
});
|
|
2012
|
+
}
|
|
2013
|
+
// High cyclomatic complexity
|
|
2014
|
+
if (cyclomaticComplexity > 20) {
|
|
2015
|
+
allIssues.push({
|
|
2016
|
+
type: "complexity",
|
|
2017
|
+
severity: cyclomaticComplexity > 40 ? "high" : "medium",
|
|
2018
|
+
file: file.path,
|
|
2019
|
+
description: `Cyclomatic complexity is ${cyclomaticComplexity} (threshold: 20)`,
|
|
2020
|
+
recommendation: "Simplify control flow; consider strategy pattern or lookup tables",
|
|
2021
|
+
});
|
|
2022
|
+
}
|
|
2023
|
+
// TODO/FIXME comments
|
|
2024
|
+
for (let i = 0; i < lines.length; i++) {
|
|
2025
|
+
const line = lines[i];
|
|
2026
|
+
if (/\bTODO\b/i.test(line)) {
|
|
2027
|
+
allIssues.push({
|
|
2028
|
+
type: "best_practice",
|
|
2029
|
+
severity: "low",
|
|
2030
|
+
file: file.path,
|
|
2031
|
+
line: i + 1,
|
|
2032
|
+
description: `TODO comment: ${line.trim().substring(0, 100)}`,
|
|
2033
|
+
recommendation: "Address TODO items or create tracking issues",
|
|
2034
|
+
});
|
|
2035
|
+
}
|
|
2036
|
+
if (/\bFIXME\b/i.test(line)) {
|
|
2037
|
+
allIssues.push({
|
|
2038
|
+
type: "best_practice",
|
|
2039
|
+
severity: "medium",
|
|
2040
|
+
file: file.path,
|
|
2041
|
+
line: i + 1,
|
|
2042
|
+
description: `FIXME comment: ${line.trim().substring(0, 100)}`,
|
|
2043
|
+
recommendation: "FIXME indicates known bugs — prioritize fixing",
|
|
2044
|
+
});
|
|
2045
|
+
}
|
|
2046
|
+
}
|
|
2047
|
+
// console.log statements
|
|
2048
|
+
const consoleLogLines = [];
|
|
2049
|
+
for (let i = 0; i < lines.length; i++) {
|
|
2050
|
+
if (/\bconsole\.(log|debug|info)\b/.test(lines[i]) &&
|
|
2051
|
+
!/\/\//.test(lines[i].split("console")[0])) {
|
|
2052
|
+
consoleLogLines.push(i + 1);
|
|
2053
|
+
}
|
|
2054
|
+
}
|
|
2055
|
+
if (consoleLogLines.length > 0) {
|
|
2056
|
+
allIssues.push({
|
|
2057
|
+
type: "best_practice",
|
|
2058
|
+
severity: "low",
|
|
2059
|
+
file: file.path,
|
|
2060
|
+
line: consoleLogLines[0],
|
|
2061
|
+
description: `Found ${consoleLogLines.length} console.log/debug/info statement(s) at lines: ${consoleLogLines.slice(0, 5).join(", ")}${consoleLogLines.length > 5 ? "..." : ""}`,
|
|
2062
|
+
recommendation: "Remove debug logging or replace with a proper logging framework",
|
|
2063
|
+
});
|
|
2064
|
+
}
|
|
2065
|
+
// Unused imports detection (basic: imported names not found elsewhere in file)
|
|
2066
|
+
const importLines = lines.filter((l) => /^import\s/.test(l.trim()));
|
|
2067
|
+
for (const impLine of importLines) {
|
|
2068
|
+
const namedImports = impLine.match(/\{\s*([^}]+)\s*\}/);
|
|
2069
|
+
if (namedImports) {
|
|
2070
|
+
const names = namedImports[1]
|
|
2071
|
+
.split(",")
|
|
2072
|
+
.map((n) => n
|
|
2073
|
+
.trim()
|
|
2074
|
+
.split(/\s+as\s+/)
|
|
2075
|
+
.pop()
|
|
2076
|
+
?.trim())
|
|
2077
|
+
.filter(Boolean);
|
|
2078
|
+
for (const name of names) {
|
|
2079
|
+
if (!name)
|
|
2080
|
+
continue;
|
|
2081
|
+
// Count occurrences outside import lines
|
|
2082
|
+
const restOfFile = lines
|
|
2083
|
+
.filter((l) => !/^import\s/.test(l.trim()))
|
|
2084
|
+
.join("\n");
|
|
2085
|
+
const nameRegex = new RegExp(`\\b${name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`, "g");
|
|
2086
|
+
const usageCount = (restOfFile.match(nameRegex) || []).length;
|
|
2087
|
+
if (usageCount === 0) {
|
|
2088
|
+
allIssues.push({
|
|
2089
|
+
type: "best_practice",
|
|
2090
|
+
severity: "low",
|
|
2091
|
+
file: file.path,
|
|
2092
|
+
description: `Potentially unused import: '${name}'`,
|
|
2093
|
+
recommendation: "Remove unused imports to keep the codebase clean",
|
|
2094
|
+
});
|
|
2095
|
+
}
|
|
2096
|
+
}
|
|
2097
|
+
}
|
|
2098
|
+
}
|
|
2099
|
+
// 'any' type usage in TypeScript files
|
|
2100
|
+
if (/\.tsx?$/.test(file.path)) {
|
|
2101
|
+
const anyMatches = [];
|
|
2102
|
+
for (let i = 0; i < lines.length; i++) {
|
|
2103
|
+
if (/:\s*any\b|as\s+any\b|<any>/.test(lines[i])) {
|
|
2104
|
+
anyMatches.push(i + 1);
|
|
2105
|
+
}
|
|
2106
|
+
}
|
|
2107
|
+
if (anyMatches.length > 0) {
|
|
2108
|
+
allIssues.push({
|
|
2109
|
+
type: "type_safety",
|
|
2110
|
+
severity: anyMatches.length > 5 ? "medium" : "low",
|
|
2111
|
+
file: file.path,
|
|
2112
|
+
line: anyMatches[0],
|
|
2113
|
+
description: `Found ${anyMatches.length} 'any' type usage(s) at lines: ${anyMatches.slice(0, 5).join(", ")}${anyMatches.length > 5 ? "..." : ""}`,
|
|
2114
|
+
recommendation: "Replace 'any' with specific types for better type safety",
|
|
2115
|
+
});
|
|
2116
|
+
}
|
|
2117
|
+
}
|
|
2118
|
+
// Missing error handling in async functions
|
|
2119
|
+
const asyncFnPattern = /\basync\s+(?:function\s+\w+|\w+\s*=\s*async|\([^)]*\)\s*=>)/g;
|
|
2120
|
+
const asyncCount = (content.match(asyncFnPattern) || []).length;
|
|
2121
|
+
const tryCatchCount = (content.match(/\btry\s*\{/g) || []).length;
|
|
2122
|
+
const catchCount = (content.match(/\.catch\s*\(/g) || []).length;
|
|
2123
|
+
if (asyncCount > 0 && tryCatchCount + catchCount < asyncCount * 0.5) {
|
|
2124
|
+
allIssues.push({
|
|
2125
|
+
type: "error_handling",
|
|
2126
|
+
severity: "medium",
|
|
2127
|
+
file: file.path,
|
|
2128
|
+
description: `${asyncCount} async function(s) but only ${tryCatchCount + catchCount} error handler(s) — potential unhandled rejections`,
|
|
2129
|
+
recommendation: "Wrap async operations in try/catch or add .catch() handlers",
|
|
2130
|
+
});
|
|
2131
|
+
}
|
|
2132
|
+
// Hardcoded strings that look like secrets
|
|
2133
|
+
for (let i = 0; i < lines.length; i++) {
|
|
2134
|
+
if (/(?:password|secret|apiKey|api_key|token|AUTH)\s*[:=]\s*["'][^"']{8,}["']/i.test(lines[i])) {
|
|
2135
|
+
allIssues.push({
|
|
2136
|
+
type: "security",
|
|
2137
|
+
severity: "high",
|
|
2138
|
+
file: file.path,
|
|
2139
|
+
line: i + 1,
|
|
2140
|
+
description: "Potential hardcoded secret or credential detected",
|
|
2141
|
+
recommendation: "Move secrets to environment variables or a secure vault",
|
|
2142
|
+
});
|
|
2143
|
+
}
|
|
2144
|
+
}
|
|
2145
|
+
// Very long lines (>120 chars)
|
|
2146
|
+
const longLines = lines.filter((l) => l.length > 120).length;
|
|
2147
|
+
if (longLines > 10) {
|
|
2148
|
+
allIssues.push({
|
|
2149
|
+
type: "style",
|
|
2150
|
+
severity: "low",
|
|
2151
|
+
file: file.path,
|
|
2152
|
+
description: `${longLines} lines exceed 120 characters`,
|
|
2153
|
+
recommendation: "Consider reformatting for readability (use prettier or similar)",
|
|
2154
|
+
});
|
|
2155
|
+
}
|
|
2156
|
+
}
|
|
2157
|
+
// --- Aggregate metrics ---
|
|
2158
|
+
const totalLOC = perFileMetrics.reduce((s, m) => s + m.linesOfCode, 0);
|
|
2159
|
+
const totalFunctions = perFileMetrics.reduce((s, m) => s + m.functions, 0);
|
|
2160
|
+
const totalClasses = perFileMetrics.reduce((s, m) => s + m.classes, 0);
|
|
2161
|
+
const avgComplexity = perFileMetrics.length > 0
|
|
2162
|
+
? perFileMetrics.reduce((s, m) => s + m.cyclomaticComplexity, 0) /
|
|
2163
|
+
perFileMetrics.length
|
|
2164
|
+
: 0;
|
|
2165
|
+
// Maintainability index (simplified Halstead-based formula)
|
|
2166
|
+
// Higher is better. Penalize for high complexity, deep nesting, low comment ratio.
|
|
2167
|
+
const commentRatio = totalLOC > 0
|
|
2168
|
+
? perFileMetrics.reduce((s, m) => s + m.commentLines, 0) / totalLOC
|
|
2169
|
+
: 0;
|
|
2170
|
+
const avgNesting = perFileMetrics.length > 0
|
|
2171
|
+
? perFileMetrics.reduce((s, m) => s + m.maxNestingDepth, 0) /
|
|
2172
|
+
perFileMetrics.length
|
|
2173
|
+
: 0;
|
|
2174
|
+
const maintainabilityIndex = Math.max(0, Math.min(100, 100 - avgComplexity * 0.5 - avgNesting * 3 + commentRatio * 30));
|
|
2175
|
+
// Sort issues by severity
|
|
2176
|
+
const severityOrder = {
|
|
2177
|
+
high: 0,
|
|
2178
|
+
medium: 1,
|
|
2179
|
+
low: 2,
|
|
2180
|
+
};
|
|
2181
|
+
allIssues.sort((a, b) => (severityOrder[a.severity] ?? 3) - (severityOrder[b.severity] ?? 3));
|
|
2182
|
+
return {
|
|
2183
|
+
filesAnalyzed: files.map((f) => f.path),
|
|
2184
|
+
issues: allIssues,
|
|
2185
|
+
metrics: {
|
|
2186
|
+
totalLinesOfCode: totalLOC,
|
|
2187
|
+
totalFunctions,
|
|
2188
|
+
totalClasses,
|
|
2189
|
+
averageCyclomaticComplexity: Math.round(avgComplexity * 10) / 10,
|
|
2190
|
+
maintainabilityIndex: Math.round(maintainabilityIndex * 10) / 10,
|
|
2191
|
+
},
|
|
2192
|
+
perFileMetrics,
|
|
2193
|
+
summary: {
|
|
2194
|
+
totalIssues: allIssues.length,
|
|
2195
|
+
highSeverity: allIssues.filter((i) => i.severity === "high").length,
|
|
2196
|
+
mediumSeverity: allIssues.filter((i) => i.severity === "medium").length,
|
|
2197
|
+
lowSeverity: allIssues.filter((i) => i.severity === "low").length,
|
|
2198
|
+
issueTypes: [...new Set(allIssues.map((i) => i.type))],
|
|
2199
|
+
},
|
|
2200
|
+
note: "Analysis performed via local static analysis (no model available)",
|
|
2201
|
+
};
|
|
2202
|
+
}
|
|
2203
|
+
/**
|
|
2204
|
+
* Compute basic file metrics for JSON parse fallback.
|
|
2205
|
+
*/
|
|
2206
|
+
computeFileMetrics(files) {
|
|
2207
|
+
const totalLOC = files.reduce((sum, f) => sum + (f.content || "").split("\n").length, 0);
|
|
2208
|
+
const totalFunctions = files.reduce((sum, f) => sum + ((f.content || "").match(/\bfunction\s+\w+/g) || []).length, 0);
|
|
2209
|
+
const avgLineLength = totalLOC > 0
|
|
2210
|
+
? Math.round(files.reduce((sum, f) => sum + f.content.length, 0) / totalLOC)
|
|
2211
|
+
: 0;
|
|
2212
|
+
return {
|
|
2213
|
+
linesOfCode: totalLOC,
|
|
2214
|
+
functions: totalFunctions,
|
|
2215
|
+
averageLineLength: avgLineLength,
|
|
2216
|
+
};
|
|
2217
|
+
}
|
|
2218
|
+
}
|
|
2219
|
+
export class ReviewerAgent extends SubAgent {
|
|
2220
|
+
async performTask(task) {
|
|
2221
|
+
this.updateStatus("running", "Reviewing changes");
|
|
2222
|
+
const analysisOutput = task.context.metadata?.analyze_output;
|
|
2223
|
+
const prompt = `Review the following code analysis and provide feedback:
|
|
2224
|
+
|
|
2225
|
+
Analysis: ${JSON.stringify(analysisOutput, null, 2)}
|
|
2226
|
+
|
|
2227
|
+
Task: ${task.prompt}
|
|
2228
|
+
|
|
2229
|
+
Please provide:
|
|
2230
|
+
1. Overall assessment
|
|
2231
|
+
2. Fixes reviewed
|
|
2232
|
+
3. Blocking issues (if any)
|
|
2233
|
+
4. Recommendations for improvement
|
|
2234
|
+
|
|
2235
|
+
Format as structured JSON.`;
|
|
2236
|
+
const response = await this.callModel(prompt);
|
|
2237
|
+
if (!response) {
|
|
2238
|
+
return this.localReviewFallback(task);
|
|
2239
|
+
}
|
|
2240
|
+
try {
|
|
2241
|
+
return JSON.parse(response);
|
|
2242
|
+
}
|
|
2243
|
+
catch {
|
|
2244
|
+
return {
|
|
2245
|
+
reviewId: uuidv4(),
|
|
2246
|
+
review: response,
|
|
2247
|
+
// @ts-ignore
|
|
2248
|
+
...this.localReviewFallback(task),
|
|
2249
|
+
};
|
|
2250
|
+
}
|
|
2251
|
+
}
|
|
2252
|
+
/**
|
|
2253
|
+
* Local fallback: review code quality based on analysis output and file content.
|
|
2254
|
+
*/
|
|
2255
|
+
localReviewFallback(task) {
|
|
2256
|
+
const analysisOutput = task.context.metadata?.analyze_output;
|
|
2257
|
+
const files = task.context.files;
|
|
2258
|
+
const fixesReviewed = [];
|
|
2259
|
+
const blockingIssues = [];
|
|
2260
|
+
const recommendations = [];
|
|
2261
|
+
let assessmentPoints = 0;
|
|
2262
|
+
let maxPoints = 0;
|
|
2263
|
+
// --- Review analysis output if available ---
|
|
2264
|
+
if (analysisOutput && typeof analysisOutput === "object") {
|
|
2265
|
+
const analysis = analysisOutput;
|
|
2266
|
+
const issues = analysis.issues || [];
|
|
2267
|
+
// Check for blocking issues from analysis
|
|
2268
|
+
for (const issue of issues) {
|
|
2269
|
+
const severity = issue.severity || "low";
|
|
2270
|
+
if (severity === "high" || severity === "critical") {
|
|
2271
|
+
blockingIssues.push({
|
|
2272
|
+
severity,
|
|
2273
|
+
description: issue.description ||
|
|
2274
|
+
"High severity issue from analysis",
|
|
2275
|
+
file: issue.file,
|
|
2276
|
+
});
|
|
2277
|
+
}
|
|
2278
|
+
}
|
|
2279
|
+
if (issues.length > 0) {
|
|
2280
|
+
fixesReviewed.push({
|
|
2281
|
+
file: "analysis",
|
|
2282
|
+
status: blockingIssues.length > 0 ? "needs_attention" : "reviewed",
|
|
2283
|
+
notes: `Analysis found ${issues.length} issue(s), ${blockingIssues.length} blocking`,
|
|
2284
|
+
});
|
|
2285
|
+
}
|
|
2286
|
+
}
|
|
2287
|
+
// --- Review each file for common quality issues ---
|
|
2288
|
+
for (const file of files) {
|
|
2289
|
+
const content = file.content || "";
|
|
2290
|
+
const lines = content.split("\n");
|
|
2291
|
+
const fileIssues = [];
|
|
2292
|
+
maxPoints += 5; // 5 quality checks per file
|
|
2293
|
+
// 1. Error handling presence
|
|
2294
|
+
const hasAsync = /\basync\b/.test(content);
|
|
2295
|
+
const hasTryCatch = /\btry\s*\{/.test(content);
|
|
2296
|
+
const hasCatch = /\.catch\s*\(/.test(content);
|
|
2297
|
+
if (hasAsync && !hasTryCatch && !hasCatch) {
|
|
2298
|
+
fileIssues.push("Missing error handling for async operations");
|
|
2299
|
+
}
|
|
2300
|
+
else {
|
|
2301
|
+
assessmentPoints++;
|
|
2302
|
+
}
|
|
2303
|
+
// 2. Type safety (TS files)
|
|
2304
|
+
if (/\.tsx?$/.test(file.path)) {
|
|
2305
|
+
const anyCount = (content.match(/:\s*any\b|as\s+any\b/g) || []).length;
|
|
2306
|
+
if (anyCount > 3) {
|
|
2307
|
+
fileIssues.push(`Excessive 'any' types (${anyCount} occurrences) — weakens type safety`);
|
|
2308
|
+
}
|
|
2309
|
+
else {
|
|
2310
|
+
assessmentPoints++;
|
|
2311
|
+
}
|
|
2312
|
+
}
|
|
2313
|
+
else {
|
|
2314
|
+
assessmentPoints++; // Non-TS files get a pass on type safety
|
|
2315
|
+
}
|
|
2316
|
+
// 3. Naming conventions check
|
|
2317
|
+
const badNames = content.match(/\b(?:const|let|var)\s+(?:[a-z]|_\d)\s*=/g) || [];
|
|
2318
|
+
const singleCharVars = badNames.filter((m) => /\s[a-z_]\s*=/.test(m) && !/\b(i|j|k|x|y|z|e|_)\s*=/.test(m));
|
|
2319
|
+
if (singleCharVars.length > 3) {
|
|
2320
|
+
fileIssues.push(`Multiple single-character variable names — use descriptive names`);
|
|
2321
|
+
}
|
|
2322
|
+
else {
|
|
2323
|
+
assessmentPoints++;
|
|
2324
|
+
}
|
|
2325
|
+
// 4. Null/undefined safety
|
|
2326
|
+
const nullAccess = (content.match(/\.\w+\.\w+/g) || []).length; // chain access count
|
|
2327
|
+
const optionalChaining = (content.match(/\?\./g) || []).length;
|
|
2328
|
+
const nullChecks = (content.match(/!==?\s*(?:null|undefined)|typeof\s+\w+\s*!==?\s*["']undefined["']/g) || []).length;
|
|
2329
|
+
if (nullAccess > 10 && optionalChaining === 0 && nullChecks < 2) {
|
|
2330
|
+
fileIssues.push("Multiple property chains without null checks — consider optional chaining (?.)");
|
|
2331
|
+
}
|
|
2332
|
+
else {
|
|
2333
|
+
assessmentPoints++;
|
|
2334
|
+
}
|
|
2335
|
+
// 5. Hardcoded values
|
|
2336
|
+
const hardcoded = [];
|
|
2337
|
+
for (let i = 0; i < lines.length; i++) {
|
|
2338
|
+
// Magic numbers (excluding common ones like 0, 1, 2, -1, 100)
|
|
2339
|
+
if (/(?:timeout|delay|interval|limit|max|min|size|count|threshold)\s*[:=]\s*\d{3,}/i.test(lines[i])) {
|
|
2340
|
+
hardcoded.push(`line ${i + 1}: hardcoded numeric value`);
|
|
2341
|
+
}
|
|
2342
|
+
}
|
|
2343
|
+
if (hardcoded.length > 2) {
|
|
2344
|
+
fileIssues.push(`${hardcoded.length} hardcoded magic numbers — extract as named constants`);
|
|
2345
|
+
}
|
|
2346
|
+
else {
|
|
2347
|
+
assessmentPoints++;
|
|
2348
|
+
}
|
|
2349
|
+
if (fileIssues.length > 0) {
|
|
2350
|
+
fixesReviewed.push({
|
|
2351
|
+
file: file.path,
|
|
2352
|
+
status: fileIssues.some((i) => i.includes("error handling") || i.includes("type safety"))
|
|
2353
|
+
? "needs_attention"
|
|
2354
|
+
: "minor_issues",
|
|
2355
|
+
notes: fileIssues.join("; "),
|
|
2356
|
+
});
|
|
2357
|
+
for (const issue of fileIssues) {
|
|
2358
|
+
recommendations.push(`[${file.path}] ${issue}`);
|
|
2359
|
+
}
|
|
2360
|
+
}
|
|
2361
|
+
else {
|
|
2362
|
+
fixesReviewed.push({
|
|
2363
|
+
file: file.path,
|
|
2364
|
+
status: "passed",
|
|
2365
|
+
notes: "No significant quality issues detected",
|
|
2366
|
+
});
|
|
2367
|
+
}
|
|
2368
|
+
}
|
|
2369
|
+
// --- Calculate confidence based on how much analysis was possible ---
|
|
2370
|
+
const confidence = maxPoints > 0
|
|
2371
|
+
? Math.round((assessmentPoints / maxPoints) * 100) / 100
|
|
2372
|
+
: 0.3;
|
|
2373
|
+
// --- Build overall assessment ---
|
|
2374
|
+
let overallAssessment;
|
|
2375
|
+
if (blockingIssues.length > 0) {
|
|
2376
|
+
overallAssessment = `Blocking issues found: ${blockingIssues.length} high/critical issue(s) require attention before proceeding.`;
|
|
2377
|
+
}
|
|
2378
|
+
else if (recommendations.length > 3) {
|
|
2379
|
+
overallAssessment = `Code has ${recommendations.length} quality concerns that should be addressed. Review the recommendations for improvements.`;
|
|
2380
|
+
}
|
|
2381
|
+
else if (recommendations.length > 0) {
|
|
2382
|
+
overallAssessment = `Code is generally acceptable with ${recommendations.length} minor suggestion(s). Consider addressing them in a follow-up.`;
|
|
2383
|
+
}
|
|
2384
|
+
else if (files.length === 0) {
|
|
2385
|
+
overallAssessment =
|
|
2386
|
+
"No files provided for review. Unable to perform meaningful analysis.";
|
|
2387
|
+
}
|
|
2388
|
+
else {
|
|
2389
|
+
overallAssessment = `Code passed basic quality checks across ${files.length} file(s). No significant issues detected.`;
|
|
2390
|
+
}
|
|
2391
|
+
// --- Standard recommendations ---
|
|
2392
|
+
if (files.length > 0) {
|
|
2393
|
+
const totalLOC = files.reduce((s, f) => s + (f.content || "").split("\n").length, 0);
|
|
2394
|
+
if (totalLOC > 500 && !files.some((f) => /test|spec/i.test(f.path))) {
|
|
2395
|
+
recommendations.push("Consider adding unit tests — no test files detected for this codebase");
|
|
2396
|
+
}
|
|
2397
|
+
}
|
|
2398
|
+
return {
|
|
2399
|
+
reviewId: uuidv4(),
|
|
2400
|
+
fixesReviewed,
|
|
2401
|
+
overallAssessment,
|
|
2402
|
+
blockingIssues,
|
|
2403
|
+
recommendations,
|
|
2404
|
+
confidence,
|
|
2405
|
+
qualityScore: maxPoints > 0 ? `${assessmentPoints}/${maxPoints}` : "N/A",
|
|
2406
|
+
note: "Review performed via local static analysis (no model available)",
|
|
2407
|
+
};
|
|
2408
|
+
}
|
|
2409
|
+
}
|
|
2410
|
+
export class RewriterAgent extends SubAgent {
|
|
2411
|
+
async performTask(task) {
|
|
2412
|
+
this.updateStatus("running", "Applying fixes");
|
|
2413
|
+
const analysisOutput = task.context.metadata?.analyze_output;
|
|
2414
|
+
const reviewOutput = task.context.metadata?.review_output;
|
|
2415
|
+
const files = task.context.files;
|
|
2416
|
+
const fileContents = files
|
|
2417
|
+
.map((f) => `File: ${f.path}\n\n${f.content}`)
|
|
2418
|
+
.join("\n\n---\n\n");
|
|
2419
|
+
const prompt = `Apply fixes to the following code based on the analysis and review:
|
|
2420
|
+
|
|
2421
|
+
Analysis: ${JSON.stringify(analysisOutput, null, 2)}
|
|
2422
|
+
|
|
2423
|
+
Review: ${JSON.stringify(reviewOutput, null, 2)}
|
|
2424
|
+
|
|
2425
|
+
Task: ${task.prompt}
|
|
2426
|
+
|
|
2427
|
+
Code Files:
|
|
2428
|
+
${fileContents}
|
|
2429
|
+
|
|
2430
|
+
Please provide:
|
|
2431
|
+
1. Specific code changes needed
|
|
2432
|
+
2. New files to create (if any)
|
|
2433
|
+
3. Files to delete (if any)
|
|
2434
|
+
4. Explanation of each change
|
|
2435
|
+
|
|
2436
|
+
Format as structured JSON with file paths as keys and change objects as values.`;
|
|
2437
|
+
const response = await this.callModel(prompt);
|
|
2438
|
+
if (!response) {
|
|
2439
|
+
return this.localRewriteFallback(files, analysisOutput, reviewOutput);
|
|
2440
|
+
}
|
|
2441
|
+
try {
|
|
2442
|
+
return JSON.parse(response);
|
|
2443
|
+
}
|
|
2444
|
+
catch {
|
|
2445
|
+
return {
|
|
2446
|
+
changes: [],
|
|
2447
|
+
newFiles: [],
|
|
2448
|
+
deletedFiles: [],
|
|
2449
|
+
explanation: response,
|
|
2450
|
+
};
|
|
2451
|
+
}
|
|
2452
|
+
}
|
|
2453
|
+
/**
|
|
2454
|
+
* Local fallback: apply basic code transformations without a model.
|
|
2455
|
+
*/
|
|
2456
|
+
localRewriteFallback(
|
|
2457
|
+
// @ts-ignore
|
|
2458
|
+
_files, _analysisOutput, _reviewOutput) {
|
|
2459
|
+
const changes = [];
|
|
2460
|
+
for (const file of files) {
|
|
2461
|
+
const content = file.content || "";
|
|
2462
|
+
if (!content.trim())
|
|
2463
|
+
continue;
|
|
2464
|
+
const transformations = [];
|
|
2465
|
+
let result = content;
|
|
2466
|
+
// --- 1. Remove trailing whitespace ---
|
|
2467
|
+
const beforeTrailing = result;
|
|
2468
|
+
result = result.replace(/[ \t]+$/gm, "");
|
|
2469
|
+
if (result !== beforeTrailing) {
|
|
2470
|
+
transformations.push("Removed trailing whitespace");
|
|
2471
|
+
}
|
|
2472
|
+
// --- 2. Normalize line endings to LF ---
|
|
2473
|
+
if (result.includes("\r\n")) {
|
|
2474
|
+
result = result.replace(/\r\n/g, "\n");
|
|
2475
|
+
transformations.push("Normalized line endings (CRLF → LF)");
|
|
2476
|
+
}
|
|
2477
|
+
// --- 3. Normalize indentation (detect and standardize) ---
|
|
2478
|
+
const lines = result.split("\n");
|
|
2479
|
+
const tabLines = lines.filter((l) => l.startsWith("\t")).length;
|
|
2480
|
+
const spaceLines = lines.filter((l) => /^ {2,}/.test(l)).length;
|
|
2481
|
+
// Only normalize if there's a clear mix
|
|
2482
|
+
if (tabLines > 0 && spaceLines > tabLines * 2) {
|
|
2483
|
+
// Convert tabs to 2-space indent
|
|
2484
|
+
result = result.replace(/^\t+/gm, (match) => " ".repeat(match.length));
|
|
2485
|
+
transformations.push("Converted tab indentation to 2-space");
|
|
2486
|
+
}
|
|
2487
|
+
// --- 4. Add missing semicolons for JS/TS files ---
|
|
2488
|
+
if (/\.[jt]sx?$/.test(file.path)) {
|
|
2489
|
+
const resultLines = result.split("\n");
|
|
2490
|
+
const semiFixedLines = resultLines.map((line) => {
|
|
2491
|
+
const trimmed = line.trim();
|
|
2492
|
+
// Skip lines that don't need semicolons
|
|
2493
|
+
if (!trimmed ||
|
|
2494
|
+
trimmed.endsWith(";") ||
|
|
2495
|
+
trimmed.endsWith("{") ||
|
|
2496
|
+
trimmed.endsWith("}") ||
|
|
2497
|
+
trimmed.endsWith("(") ||
|
|
2498
|
+
trimmed.endsWith(",") ||
|
|
2499
|
+
trimmed.endsWith("*/") ||
|
|
2500
|
+
trimmed.startsWith("//") ||
|
|
2501
|
+
trimmed.startsWith("/*") ||
|
|
2502
|
+
trimmed.startsWith("*") ||
|
|
2503
|
+
trimmed.startsWith("import ") ||
|
|
2504
|
+
trimmed.startsWith("export ") ||
|
|
2505
|
+
/^(?:if|else|for|while|switch|case|default|try|catch|finally|do|class|interface|type|enum)\b/.test(trimmed) ||
|
|
2506
|
+
trimmed.endsWith("=>") ||
|
|
2507
|
+
trimmed.endsWith("?") ||
|
|
2508
|
+
trimmed.endsWith(":") ||
|
|
2509
|
+
trimmed.endsWith("||") ||
|
|
2510
|
+
trimmed.endsWith("&&")) {
|
|
2511
|
+
return line;
|
|
2512
|
+
}
|
|
2513
|
+
// Add semicolons to statement-like lines ending with closing paren, string, number, identifier
|
|
2514
|
+
if (/(?:\)|\]|['"`]|\w)\s*$/.test(trimmed)) {
|
|
2515
|
+
// Check if it's a standalone statement (assignment, return, throw, const/let/var)
|
|
2516
|
+
if (/^(?:return|throw|const|let|var|yield)\b/.test(trimmed) ||
|
|
2517
|
+
/^[\w$]+\s*[+\-*/]?=\s/.test(trimmed) ||
|
|
2518
|
+
/^(?:await\s+)?[\w$]+\s*\(/.test(trimmed)) {
|
|
2519
|
+
return line.replace(/\s*$/, ";");
|
|
2520
|
+
}
|
|
2521
|
+
}
|
|
2522
|
+
return line;
|
|
2523
|
+
});
|
|
2524
|
+
const semiResult = semiFixedLines.join("\n");
|
|
2525
|
+
if (semiResult !== result) {
|
|
2526
|
+
result = semiResult;
|
|
2527
|
+
transformations.push("Added missing semicolons to statement lines");
|
|
2528
|
+
}
|
|
2529
|
+
}
|
|
2530
|
+
// --- 5. Sort imports alphabetically ---
|
|
2531
|
+
if (/\.[jt]sx?$/.test(file.path)) {
|
|
2532
|
+
const importResult = this.sortImports(result);
|
|
2533
|
+
if (importResult !== result) {
|
|
2534
|
+
result = importResult;
|
|
2535
|
+
transformations.push("Sorted import statements alphabetically");
|
|
2536
|
+
}
|
|
2537
|
+
}
|
|
2538
|
+
// --- 6. Remove consecutive blank lines (keep max 1) ---
|
|
2539
|
+
const beforeBlank = result;
|
|
2540
|
+
result = result.replace(/\n{3,}/g, "\n\n");
|
|
2541
|
+
if (result !== beforeBlank) {
|
|
2542
|
+
transformations.push("Collapsed excessive blank lines");
|
|
2543
|
+
}
|
|
2544
|
+
// --- 7. Ensure file ends with newline ---
|
|
2545
|
+
if (!result.endsWith("\n")) {
|
|
2546
|
+
result += "\n";
|
|
2547
|
+
transformations.push("Added trailing newline");
|
|
2548
|
+
}
|
|
2549
|
+
if (transformations.length > 0) {
|
|
2550
|
+
changes.push({
|
|
2551
|
+
file: file.path,
|
|
2552
|
+
original: content,
|
|
2553
|
+
rewritten: result,
|
|
2554
|
+
transformations,
|
|
2555
|
+
});
|
|
2556
|
+
}
|
|
2557
|
+
}
|
|
2558
|
+
return {
|
|
2559
|
+
changes,
|
|
2560
|
+
newFiles: [],
|
|
2561
|
+
deletedFiles: [],
|
|
2562
|
+
summary: changes.length > 0
|
|
2563
|
+
? `Applied basic formatting transformations to ${changes.length} file(s): ${changes.map((c) => c.transformations.join(", ")).join("; ")}`
|
|
2564
|
+
: "No transformations needed — files already well-formatted",
|
|
2565
|
+
note: "Only basic code formatting applied (no model available for semantic rewrites)",
|
|
2566
|
+
};
|
|
2567
|
+
}
|
|
2568
|
+
/**
|
|
2569
|
+
* Sort import statements alphabetically while preserving groups.
|
|
2570
|
+
*/
|
|
2571
|
+
sortImports(content) {
|
|
2572
|
+
const lines = content.split("\n");
|
|
2573
|
+
const importGroups = [];
|
|
2574
|
+
let groupStart = -1;
|
|
2575
|
+
let currentImports = [];
|
|
2576
|
+
for (let i = 0; i < lines.length; i++) {
|
|
2577
|
+
const trimmed = lines[i].trim();
|
|
2578
|
+
if (/^import\s/.test(trimmed)) {
|
|
2579
|
+
if (groupStart === -1)
|
|
2580
|
+
groupStart = i;
|
|
2581
|
+
currentImports.push(lines[i]);
|
|
2582
|
+
}
|
|
2583
|
+
else {
|
|
2584
|
+
if (currentImports.length > 1) {
|
|
2585
|
+
importGroups.push({
|
|
2586
|
+
start: groupStart,
|
|
2587
|
+
end: groupStart + currentImports.length - 1,
|
|
2588
|
+
imports: [...currentImports],
|
|
2589
|
+
});
|
|
2590
|
+
}
|
|
2591
|
+
groupStart = -1;
|
|
2592
|
+
currentImports = [];
|
|
2593
|
+
}
|
|
2594
|
+
}
|
|
2595
|
+
// Handle trailing import group
|
|
2596
|
+
if (currentImports.length > 1) {
|
|
2597
|
+
importGroups.push({
|
|
2598
|
+
start: groupStart,
|
|
2599
|
+
end: groupStart + currentImports.length - 1,
|
|
2600
|
+
imports: [...currentImports],
|
|
2601
|
+
});
|
|
2602
|
+
}
|
|
2603
|
+
if (importGroups.length === 0)
|
|
2604
|
+
return content;
|
|
2605
|
+
// Sort each group and rebuild
|
|
2606
|
+
const resultLines = [...lines];
|
|
2607
|
+
let changed = false;
|
|
2608
|
+
for (const group of importGroups) {
|
|
2609
|
+
const sorted = [...group.imports].sort((a, b) => {
|
|
2610
|
+
// Extract the source module for comparison
|
|
2611
|
+
const srcA = (a.match(/from\s+["']([^"']+)["']/) || [])[1] || a;
|
|
2612
|
+
const srcB = (b.match(/from\s+["']([^"']+)["']/) || [])[1] || b;
|
|
2613
|
+
return srcA.localeCompare(srcB);
|
|
2614
|
+
});
|
|
2615
|
+
const isAlreadySorted = sorted.every((line, idx) => line === group.imports[idx]);
|
|
2616
|
+
if (!isAlreadySorted) {
|
|
2617
|
+
for (let i = 0; i < sorted.length; i++) {
|
|
2618
|
+
resultLines[group.start + i] = sorted[i];
|
|
2619
|
+
}
|
|
2620
|
+
changed = true;
|
|
2621
|
+
}
|
|
2622
|
+
}
|
|
2623
|
+
return changed ? resultLines.join("\n") : content;
|
|
2624
|
+
}
|
|
2625
|
+
}
|
|
2626
|
+
export class UiDesignerAgent extends SubAgent {
|
|
2627
|
+
async performTask(task) {
|
|
2628
|
+
this.updateStatus("running", "Generating UI mockups");
|
|
2629
|
+
const files = task.context.files;
|
|
2630
|
+
const uiFiles = files.filter((f) => f.content.includes("div") ||
|
|
2631
|
+
f.content.includes("button") ||
|
|
2632
|
+
f.content.includes("<") ||
|
|
2633
|
+
/\.(tsx?|jsx?|html|svelte|vue)$/.test(f.path));
|
|
2634
|
+
if (uiFiles.length === 0) {
|
|
2635
|
+
// No UI files found - still analyze the task prompt for UI generation
|
|
2636
|
+
return this.localUiDesignFallback([], task);
|
|
2637
|
+
}
|
|
2638
|
+
const uiContent = uiFiles
|
|
2639
|
+
.map((f) => `File: ${f.path}\n\n${f.content}`)
|
|
2640
|
+
.join("\n\n---\n\n");
|
|
2641
|
+
const prompt = `Design UI improvements for the following code:
|
|
2642
|
+
|
|
2643
|
+
${uiContent}
|
|
2644
|
+
|
|
2645
|
+
Task: ${task.prompt}
|
|
2646
|
+
|
|
2647
|
+
Please provide:
|
|
2648
|
+
1. Detected UI frameworks
|
|
2649
|
+
2. ASCII mockups for new UI components
|
|
2650
|
+
3. Accessibility issues
|
|
2651
|
+
4. Design suggestions
|
|
2652
|
+
|
|
2653
|
+
Format as structured JSON.`;
|
|
2654
|
+
const response = await this.callModel(prompt);
|
|
2655
|
+
if (!response) {
|
|
2656
|
+
return this.localUiDesignFallback(uiFiles, task);
|
|
2657
|
+
}
|
|
2658
|
+
try {
|
|
2659
|
+
return JSON.parse(response);
|
|
2660
|
+
}
|
|
2661
|
+
catch {
|
|
2662
|
+
return {
|
|
2663
|
+
detectedFrameworks: this.detectFrameworks(uiFiles),
|
|
2664
|
+
mockups: [
|
|
2665
|
+
{
|
|
2666
|
+
type: "ascii",
|
|
2667
|
+
content: response,
|
|
2668
|
+
viewport: "desktop",
|
|
2669
|
+
},
|
|
2670
|
+
],
|
|
2671
|
+
accessibilityIssues: this.detectA11yIssues(uiFiles),
|
|
2672
|
+
designSuggestions: [],
|
|
2673
|
+
};
|
|
2674
|
+
}
|
|
2675
|
+
}
|
|
2676
|
+
/**
|
|
2677
|
+
* Local fallback: generate a real UI analysis and skeletal component.
|
|
2678
|
+
*/
|
|
2679
|
+
localUiDesignFallback(uiFiles, task) {
|
|
2680
|
+
const prompt = task.prompt || "";
|
|
2681
|
+
const lower = prompt.toLowerCase();
|
|
2682
|
+
// --- Detect frameworks from file content ---
|
|
2683
|
+
const frameworks = this.detectFrameworks(uiFiles);
|
|
2684
|
+
// --- Detect UI keywords to determine component type ---
|
|
2685
|
+
const uiKeywords = {
|
|
2686
|
+
form: [
|
|
2687
|
+
"form",
|
|
2688
|
+
"input",
|
|
2689
|
+
"submit",
|
|
2690
|
+
"field",
|
|
2691
|
+
"validation",
|
|
2692
|
+
"textarea",
|
|
2693
|
+
"select",
|
|
2694
|
+
],
|
|
2695
|
+
button: ["button", "btn", "click", "action", "cta"],
|
|
2696
|
+
layout: [
|
|
2697
|
+
"layout",
|
|
2698
|
+
"grid",
|
|
2699
|
+
"flex",
|
|
2700
|
+
"container",
|
|
2701
|
+
"wrapper",
|
|
2702
|
+
"sidebar",
|
|
2703
|
+
"header",
|
|
2704
|
+
"footer",
|
|
2705
|
+
],
|
|
2706
|
+
page: ["page", "view", "screen", "route", "dashboard"],
|
|
2707
|
+
modal: ["modal", "dialog", "popup", "overlay"],
|
|
2708
|
+
list: ["list", "table", "grid", "card", "item", "row"],
|
|
2709
|
+
nav: ["nav", "navigation", "menu", "breadcrumb", "tab", "sidebar"],
|
|
2710
|
+
};
|
|
2711
|
+
const detectedTypes = [];
|
|
2712
|
+
for (const [type, keywords] of Object.entries(uiKeywords)) {
|
|
2713
|
+
if (keywords.some((kw) => lower.includes(kw))) {
|
|
2714
|
+
detectedTypes.push(type);
|
|
2715
|
+
}
|
|
2716
|
+
}
|
|
2717
|
+
if (detectedTypes.length === 0)
|
|
2718
|
+
detectedTypes.push("component");
|
|
2719
|
+
// --- Detect accessibility issues in existing files ---
|
|
2720
|
+
const accessibilityIssues = this.detectA11yIssues(uiFiles);
|
|
2721
|
+
// --- Generate skeletal component based on detected type ---
|
|
2722
|
+
const primaryType = detectedTypes[0];
|
|
2723
|
+
const componentName = this.inferComponentName(prompt);
|
|
2724
|
+
const isReact = frameworks.includes("react") || frameworks.includes("next.js");
|
|
2725
|
+
let componentCode;
|
|
2726
|
+
let asciiMockup;
|
|
2727
|
+
switch (primaryType) {
|
|
2728
|
+
case "form":
|
|
2729
|
+
asciiMockup = [
|
|
2730
|
+
`+${"─".repeat(40)}+`,
|
|
2731
|
+
`│ ${componentName.padEnd(38)}│`,
|
|
2732
|
+
`│${"─".repeat(40)}│`,
|
|
2733
|
+
`│ Label: │`,
|
|
2734
|
+
`│ ┌──────────────────────────────────┐ │`,
|
|
2735
|
+
`│ │ Input field │ │`,
|
|
2736
|
+
`│ └──────────────────────────────────┘ │`,
|
|
2737
|
+
`│ Label: │`,
|
|
2738
|
+
`│ ┌──────────────────────────────────┐ │`,
|
|
2739
|
+
`│ │ Input field │ │`,
|
|
2740
|
+
`│ └──────────────────────────────────┘ │`,
|
|
2741
|
+
`│ │`,
|
|
2742
|
+
`│ ┌──────────────────────┐ │`,
|
|
2743
|
+
`│ │ Submit │ │`,
|
|
2744
|
+
`│ └──────────────────────┘ │`,
|
|
2745
|
+
`+${"─".repeat(40)}+`,
|
|
2746
|
+
].join("\n");
|
|
2747
|
+
componentCode = isReact
|
|
2748
|
+
? this.generateReactForm(componentName)
|
|
2749
|
+
: this.generateHtmlForm(componentName);
|
|
2750
|
+
break;
|
|
2751
|
+
case "modal":
|
|
2752
|
+
asciiMockup = [
|
|
2753
|
+
` ┌${"─".repeat(36)}┐`,
|
|
2754
|
+
` │ ${componentName.padEnd(30)} [X] │`,
|
|
2755
|
+
` │${"─".repeat(36)}│`,
|
|
2756
|
+
` │ │`,
|
|
2757
|
+
` │ Content area │`,
|
|
2758
|
+
` │ │`,
|
|
2759
|
+
` │${"─".repeat(36)}│`,
|
|
2760
|
+
` │ [Cancel] [Confirm] │`,
|
|
2761
|
+
` └${"─".repeat(36)}┘`,
|
|
2762
|
+
].join("\n");
|
|
2763
|
+
componentCode = isReact
|
|
2764
|
+
? this.generateReactModal(componentName)
|
|
2765
|
+
: this.generateHtmlModal(componentName);
|
|
2766
|
+
break;
|
|
2767
|
+
case "nav":
|
|
2768
|
+
asciiMockup = [
|
|
2769
|
+
`+${"─".repeat(50)}+`,
|
|
2770
|
+
`│ Logo │ Home │ About │ Contact │ ☰ │`,
|
|
2771
|
+
`+${"─".repeat(50)}+`,
|
|
2772
|
+
].join("\n");
|
|
2773
|
+
componentCode = isReact
|
|
2774
|
+
? this.generateReactNav(componentName)
|
|
2775
|
+
: `<nav role="navigation" aria-label="Main navigation">\n <ul>\n <li><a href="/">Home</a></li>\n <li><a href="/about">About</a></li>\n <li><a href="/contact">Contact</a></li>\n </ul>\n</nav>`;
|
|
2776
|
+
break;
|
|
2777
|
+
case "list":
|
|
2778
|
+
asciiMockup = [
|
|
2779
|
+
`+${"─".repeat(42)}+`,
|
|
2780
|
+
`│ ${componentName.padEnd(40)}│`,
|
|
2781
|
+
`│${"─".repeat(42)}│`,
|
|
2782
|
+
`│ ┌──────────────────────────────────┐ │`,
|
|
2783
|
+
`│ │ Item 1 [Action] │ │`,
|
|
2784
|
+
`│ ├──────────────────────────────────┤ │`,
|
|
2785
|
+
`│ │ Item 2 [Action] │ │`,
|
|
2786
|
+
`│ ├──────────────────────────────────┤ │`,
|
|
2787
|
+
`│ │ Item 3 [Action] │ │`,
|
|
2788
|
+
`│ └──────────────────────────────────┘ │`,
|
|
2789
|
+
`+${"─".repeat(42)}+`,
|
|
2790
|
+
].join("\n");
|
|
2791
|
+
componentCode = isReact
|
|
2792
|
+
? this.generateReactList(componentName)
|
|
2793
|
+
: `<section aria-label="${componentName}">\n <h2>${componentName}</h2>\n <ul role="list">\n <li role="listitem">Item 1</li>\n <li role="listitem">Item 2</li>\n </ul>\n</section>`;
|
|
2794
|
+
break;
|
|
2795
|
+
default: // generic component / page / layout / button
|
|
2796
|
+
asciiMockup = [
|
|
2797
|
+
`+${"─".repeat(44)}+`,
|
|
2798
|
+
`│ ${componentName.padEnd(42)}│`,
|
|
2799
|
+
`│${"─".repeat(44)}│`,
|
|
2800
|
+
`│ │`,
|
|
2801
|
+
`│ ┌────────────────────────────────────────┐│`,
|
|
2802
|
+
`│ │ ││`,
|
|
2803
|
+
`│ │ Main Content Area ││`,
|
|
2804
|
+
`│ │ ││`,
|
|
2805
|
+
`│ └────────────────────────────────────────┘│`,
|
|
2806
|
+
`│ │`,
|
|
2807
|
+
`│ [Primary Action] [Secondary Action] │`,
|
|
2808
|
+
`+${"─".repeat(44)}+`,
|
|
2809
|
+
].join("\n");
|
|
2810
|
+
componentCode = isReact
|
|
2811
|
+
? this.generateReactComponent(componentName)
|
|
2812
|
+
: `<section aria-label="${componentName}">\n <h2>${componentName}</h2>\n <div class="content">\n <p>Main content area</p>\n </div>\n <div class="actions">\n <button type="button">Primary Action</button>\n </div>\n</section>`;
|
|
2813
|
+
}
|
|
2814
|
+
// --- Design suggestions based on analysis ---
|
|
2815
|
+
const designSuggestions = [];
|
|
2816
|
+
if (!uiFiles.some((f) => f.content.includes("@media") || f.content.includes("responsive"))) {
|
|
2817
|
+
designSuggestions.push("Add responsive breakpoints for mobile/tablet viewports");
|
|
2818
|
+
}
|
|
2819
|
+
if (!uiFiles.some((f) => f.content.includes("dark") || f.content.includes("theme"))) {
|
|
2820
|
+
designSuggestions.push("Consider implementing dark mode / theme support");
|
|
2821
|
+
}
|
|
2822
|
+
if (!uiFiles.some((f) => /loading|spinner|skeleton/i.test(f.content))) {
|
|
2823
|
+
designSuggestions.push("Add loading states (skeleton screens or spinners) for async content");
|
|
2824
|
+
}
|
|
2825
|
+
if (!uiFiles.some((f) => /error|fallback/i.test(f.content))) {
|
|
2826
|
+
designSuggestions.push("Add error boundary and fallback UI for graceful error handling");
|
|
2827
|
+
}
|
|
2828
|
+
designSuggestions.push("Ensure consistent spacing and typography using design tokens");
|
|
2829
|
+
return {
|
|
2830
|
+
detectedFrameworks: frameworks,
|
|
2831
|
+
detectedComponentTypes: detectedTypes,
|
|
2832
|
+
mockups: [
|
|
2833
|
+
{
|
|
2834
|
+
type: "ascii",
|
|
2835
|
+
content: asciiMockup,
|
|
2836
|
+
viewport: "desktop",
|
|
2837
|
+
componentName,
|
|
2838
|
+
},
|
|
2839
|
+
],
|
|
2840
|
+
generatedComponent: {
|
|
2841
|
+
name: componentName,
|
|
2842
|
+
code: componentCode,
|
|
2843
|
+
framework: isReact ? "React/TSX" : "HTML",
|
|
2844
|
+
},
|
|
2845
|
+
accessibilityIssues,
|
|
2846
|
+
designSuggestions,
|
|
2847
|
+
note: "UI design generated via local keyword analysis (no model available)",
|
|
2848
|
+
};
|
|
2849
|
+
}
|
|
2850
|
+
/**
|
|
2851
|
+
* Detect UI frameworks from file contents.
|
|
2852
|
+
*/
|
|
2853
|
+
detectFrameworks(files) {
|
|
2854
|
+
const frameworks = new Set();
|
|
2855
|
+
for (const file of files) {
|
|
2856
|
+
const c = file.content || "";
|
|
2857
|
+
if (/\bimport\s.*from\s+["']react["']|React\.createElement|jsx/i.test(c))
|
|
2858
|
+
frameworks.add("react");
|
|
2859
|
+
if (/\bimport\s.*from\s+["']next/i.test(c) ||
|
|
2860
|
+
/\bgetServerSideProps|getStaticProps\b/.test(c))
|
|
2861
|
+
frameworks.add("next.js");
|
|
2862
|
+
if (/\bimport\s.*from\s+["']vue["']|createApp|defineComponent/.test(c))
|
|
2863
|
+
frameworks.add("vue");
|
|
2864
|
+
if (/\bimport\s.*from\s+["']svelte["']|<script\s+lang=["']ts["']/.test(c) &&
|
|
2865
|
+
file.path.endsWith(".svelte"))
|
|
2866
|
+
frameworks.add("svelte");
|
|
2867
|
+
if (/tailwind|className=["'][^"']*(?:flex|grid|p-|m-|text-)/.test(c))
|
|
2868
|
+
frameworks.add("tailwind");
|
|
2869
|
+
if (/\bimport\s.*from\s+["']@radix-ui/.test(c))
|
|
2870
|
+
frameworks.add("radix-ui");
|
|
2871
|
+
if (/\bimport\s.*from\s+["']@shadcn/.test(c) ||
|
|
2872
|
+
/\bimport\s.*from\s+["']@\/components\/ui/.test(c))
|
|
2873
|
+
frameworks.add("shadcn/ui");
|
|
2874
|
+
if (/<html|<body|<head/i.test(c))
|
|
2875
|
+
frameworks.add("html");
|
|
2876
|
+
}
|
|
2877
|
+
return [...frameworks];
|
|
2878
|
+
}
|
|
2879
|
+
/**
|
|
2880
|
+
* Detect accessibility issues in UI files.
|
|
2881
|
+
*/
|
|
2882
|
+
detectA11yIssues(files) {
|
|
2883
|
+
const issues = [];
|
|
2884
|
+
for (const file of files) {
|
|
2885
|
+
const c = file.content || "";
|
|
2886
|
+
// Images without alt
|
|
2887
|
+
if (/<img\b(?![^>]*\balt\b)/i.test(c)) {
|
|
2888
|
+
issues.push({
|
|
2889
|
+
file: file.path,
|
|
2890
|
+
issue: "Image element missing 'alt' attribute",
|
|
2891
|
+
severity: "high",
|
|
2892
|
+
});
|
|
2893
|
+
}
|
|
2894
|
+
// Buttons without text content or aria-label
|
|
2895
|
+
if (/<button[^>]*>\s*<(?:img|svg|icon)/i.test(c) &&
|
|
2896
|
+
!/<button[^>]*aria-label/i.test(c)) {
|
|
2897
|
+
issues.push({
|
|
2898
|
+
file: file.path,
|
|
2899
|
+
issue: "Icon-only button missing aria-label",
|
|
2900
|
+
severity: "high",
|
|
2901
|
+
});
|
|
2902
|
+
}
|
|
2903
|
+
// onClick on non-interactive elements
|
|
2904
|
+
if (/\bonClick\b.*<(?:div|span|p)\b/i.test(c) &&
|
|
2905
|
+
!/role=["']button["']/i.test(c)) {
|
|
2906
|
+
issues.push({
|
|
2907
|
+
file: file.path,
|
|
2908
|
+
issue: "Click handler on non-interactive element without role='button'",
|
|
2909
|
+
severity: "medium",
|
|
2910
|
+
});
|
|
2911
|
+
}
|
|
2912
|
+
// Missing form labels
|
|
2913
|
+
if (/<input\b(?![^>]*aria-label)(?![^>]*id=["'](\w+)["'])/i.test(c)) {
|
|
2914
|
+
issues.push({
|
|
2915
|
+
file: file.path,
|
|
2916
|
+
issue: "Form input potentially missing associated label",
|
|
2917
|
+
severity: "medium",
|
|
2918
|
+
});
|
|
2919
|
+
}
|
|
2920
|
+
// Missing lang attribute
|
|
2921
|
+
if (/<html\b(?![^>]*\blang\b)/i.test(c)) {
|
|
2922
|
+
issues.push({
|
|
2923
|
+
file: file.path,
|
|
2924
|
+
issue: "HTML element missing 'lang' attribute",
|
|
2925
|
+
severity: "medium",
|
|
2926
|
+
});
|
|
2927
|
+
}
|
|
2928
|
+
// Color contrast (heuristic: very light text colors)
|
|
2929
|
+
if (/color:\s*#(?:eee|ddd|ccc|fff|fafafa)/i.test(c)) {
|
|
2930
|
+
issues.push({
|
|
2931
|
+
file: file.path,
|
|
2932
|
+
issue: "Potentially low color contrast text detected",
|
|
2933
|
+
severity: "low",
|
|
2934
|
+
});
|
|
2935
|
+
}
|
|
2936
|
+
}
|
|
2937
|
+
return issues;
|
|
2938
|
+
}
|
|
2939
|
+
/**
|
|
2940
|
+
* Infer component name from task prompt.
|
|
2941
|
+
*/
|
|
2942
|
+
inferComponentName(prompt) {
|
|
2943
|
+
// Try to extract a meaningful name from the prompt
|
|
2944
|
+
const match = prompt.match(/(?:create|build|generate|design|add)\s+(?:a\s+)?(\w[\w\s]{1,30}?)(?:\s+(?:component|page|form|modal|dialog|view|section|panel|widget))?$/i);
|
|
2945
|
+
if (match) {
|
|
2946
|
+
return match[1]
|
|
2947
|
+
.trim()
|
|
2948
|
+
.split(/\s+/)
|
|
2949
|
+
.map((w) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase())
|
|
2950
|
+
.join("");
|
|
2951
|
+
}
|
|
2952
|
+
return "Component";
|
|
2953
|
+
}
|
|
2954
|
+
generateReactForm(name) {
|
|
2955
|
+
return `import React, { FormEvent, useState } from "react";
|
|
2956
|
+
|
|
2957
|
+
interface ${name}Props {
|
|
2958
|
+
onSubmit?: (data: Record<string, string>) => void;
|
|
2959
|
+
}
|
|
2960
|
+
|
|
2961
|
+
export function ${name}({ onSubmit }: ${name}Props) {
|
|
2962
|
+
const [formData, setFormData] = useState<Record<string, string>>({});
|
|
2963
|
+
|
|
2964
|
+
const handleSubmit = (e: FormEvent) => {
|
|
2965
|
+
e.preventDefault();
|
|
2966
|
+
onSubmit?.(formData);
|
|
2967
|
+
};
|
|
2968
|
+
|
|
2969
|
+
return (
|
|
2970
|
+
<form onSubmit={handleSubmit} role="form" aria-label="${name}">
|
|
2971
|
+
<fieldset>
|
|
2972
|
+
<legend>${name}</legend>
|
|
2973
|
+
<div>
|
|
2974
|
+
<label htmlFor="${name.toLowerCase()}-field-1">Field 1</label>
|
|
2975
|
+
<input
|
|
2976
|
+
id="${name.toLowerCase()}-field-1"
|
|
2977
|
+
type="text"
|
|
2978
|
+
required
|
|
2979
|
+
aria-required="true"
|
|
2980
|
+
onChange={(e) => setFormData(prev => ({ ...prev, field1: e.target.value }))}
|
|
2981
|
+
/>
|
|
2982
|
+
</div>
|
|
2983
|
+
<div>
|
|
2984
|
+
<label htmlFor="${name.toLowerCase()}-field-2">Field 2</label>
|
|
2985
|
+
<input
|
|
2986
|
+
id="${name.toLowerCase()}-field-2"
|
|
2987
|
+
type="text"
|
|
2988
|
+
onChange={(e) => setFormData(prev => ({ ...prev, field2: e.target.value }))}
|
|
2989
|
+
/>
|
|
2990
|
+
</div>
|
|
2991
|
+
<button type="submit">Submit</button>
|
|
2992
|
+
</fieldset>
|
|
2993
|
+
</form>
|
|
2994
|
+
);
|
|
2995
|
+
}`;
|
|
2996
|
+
}
|
|
2997
|
+
generateHtmlForm(name) {
|
|
2998
|
+
return `<form role="form" aria-label="${name}">
|
|
2999
|
+
<fieldset>
|
|
3000
|
+
<legend>${name}</legend>
|
|
3001
|
+
<div>
|
|
3002
|
+
<label for="${name.toLowerCase()}-field-1">Field 1</label>
|
|
3003
|
+
<input id="${name.toLowerCase()}-field-1" type="text" required aria-required="true" />
|
|
3004
|
+
</div>
|
|
3005
|
+
<div>
|
|
3006
|
+
<label for="${name.toLowerCase()}-field-2">Field 2</label>
|
|
3007
|
+
<input id="${name.toLowerCase()}-field-2" type="text" />
|
|
3008
|
+
</div>
|
|
3009
|
+
<button type="submit">Submit</button>
|
|
3010
|
+
</fieldset>
|
|
3011
|
+
</form>`;
|
|
3012
|
+
}
|
|
3013
|
+
generateReactModal(name) {
|
|
3014
|
+
return `import React, { useEffect, useRef } from "react";
|
|
3015
|
+
|
|
3016
|
+
interface ${name}Props {
|
|
3017
|
+
isOpen: boolean;
|
|
3018
|
+
onClose: () => void;
|
|
3019
|
+
title: string;
|
|
3020
|
+
children: React.ReactNode;
|
|
3021
|
+
}
|
|
3022
|
+
|
|
3023
|
+
export function ${name}({ isOpen, onClose, title, children }: ${name}Props) {
|
|
3024
|
+
const dialogRef = useRef<HTMLDialogElement>(null);
|
|
3025
|
+
|
|
3026
|
+
useEffect(() => {
|
|
3027
|
+
if (isOpen) {
|
|
3028
|
+
dialogRef.current?.showModal();
|
|
3029
|
+
} else {
|
|
3030
|
+
dialogRef.current?.close();
|
|
3031
|
+
}
|
|
3032
|
+
}, [isOpen]);
|
|
3033
|
+
|
|
3034
|
+
return (
|
|
3035
|
+
<dialog
|
|
3036
|
+
ref={dialogRef}
|
|
3037
|
+
role="dialog"
|
|
3038
|
+
aria-modal="true"
|
|
3039
|
+
aria-label={title}
|
|
3040
|
+
onClose={onClose}
|
|
3041
|
+
>
|
|
3042
|
+
<header>
|
|
3043
|
+
<h2>{title}</h2>
|
|
3044
|
+
<button type="button" onClick={onClose} aria-label="Close dialog">✕</button>
|
|
3045
|
+
</header>
|
|
3046
|
+
<div role="document">{children}</div>
|
|
3047
|
+
<footer>
|
|
3048
|
+
<button type="button" onClick={onClose}>Cancel</button>
|
|
3049
|
+
<button type="button">Confirm</button>
|
|
3050
|
+
</footer>
|
|
3051
|
+
</dialog>
|
|
3052
|
+
);
|
|
3053
|
+
}`;
|
|
3054
|
+
}
|
|
3055
|
+
generateHtmlModal(name) {
|
|
3056
|
+
return `<dialog role="dialog" aria-modal="true" aria-label="${name}">
|
|
3057
|
+
<header>
|
|
3058
|
+
<h2>${name}</h2>
|
|
3059
|
+
<button type="button" aria-label="Close dialog">✕</button>
|
|
3060
|
+
</header>
|
|
3061
|
+
<div role="document">
|
|
3062
|
+
<p>Modal content goes here</p>
|
|
3063
|
+
</div>
|
|
3064
|
+
<footer>
|
|
3065
|
+
<button type="button">Cancel</button>
|
|
3066
|
+
<button type="button">Confirm</button>
|
|
3067
|
+
</footer>
|
|
3068
|
+
</dialog>`;
|
|
3069
|
+
}
|
|
3070
|
+
generateReactNav(name) {
|
|
3071
|
+
return `import React from "react";
|
|
3072
|
+
|
|
3073
|
+
interface NavItem {
|
|
3074
|
+
label: string;
|
|
3075
|
+
href: string;
|
|
3076
|
+
active?: boolean;
|
|
3077
|
+
}
|
|
3078
|
+
|
|
3079
|
+
interface ${name}Props {
|
|
3080
|
+
items: NavItem[];
|
|
3081
|
+
logo?: React.ReactNode;
|
|
3082
|
+
}
|
|
3083
|
+
|
|
3084
|
+
export function ${name}({ items, logo }: ${name}Props) {
|
|
3085
|
+
return (
|
|
3086
|
+
<nav role="navigation" aria-label="Main navigation">
|
|
3087
|
+
{logo && <div aria-hidden="true">{logo}</div>}
|
|
3088
|
+
<ul role="menubar">
|
|
3089
|
+
{items.map((item) => (
|
|
3090
|
+
<li key={item.href} role="none">
|
|
3091
|
+
<a
|
|
3092
|
+
href={item.href}
|
|
3093
|
+
role="menuitem"
|
|
3094
|
+
aria-current={item.active ? "page" : undefined}
|
|
3095
|
+
>
|
|
3096
|
+
{item.label}
|
|
3097
|
+
</a>
|
|
3098
|
+
</li>
|
|
3099
|
+
))}
|
|
3100
|
+
</ul>
|
|
3101
|
+
</nav>
|
|
3102
|
+
);
|
|
3103
|
+
}`;
|
|
3104
|
+
}
|
|
3105
|
+
generateReactList(name) {
|
|
3106
|
+
return `import React from "react";
|
|
3107
|
+
|
|
3108
|
+
interface ListItem {
|
|
3109
|
+
id: string;
|
|
3110
|
+
title: string;
|
|
3111
|
+
description?: string;
|
|
3112
|
+
}
|
|
3113
|
+
|
|
3114
|
+
interface ${name}Props {
|
|
3115
|
+
items: ListItem[];
|
|
3116
|
+
onAction?: (id: string) => void;
|
|
3117
|
+
emptyMessage?: string;
|
|
3118
|
+
}
|
|
3119
|
+
|
|
3120
|
+
export function ${name}({ items, onAction, emptyMessage = "No items found" }: ${name}Props) {
|
|
3121
|
+
if (items.length === 0) {
|
|
3122
|
+
return (
|
|
3123
|
+
<section aria-label="${name}">
|
|
3124
|
+
<p role="status">{emptyMessage}</p>
|
|
3125
|
+
</section>
|
|
3126
|
+
);
|
|
3127
|
+
}
|
|
3128
|
+
|
|
3129
|
+
return (
|
|
3130
|
+
<section aria-label="${name}">
|
|
3131
|
+
<h2>${name}</h2>
|
|
3132
|
+
<ul role="list">
|
|
3133
|
+
{items.map((item) => (
|
|
3134
|
+
<li key={item.id} role="listitem">
|
|
3135
|
+
<div>
|
|
3136
|
+
<h3>{item.title}</h3>
|
|
3137
|
+
{item.description && <p>{item.description}</p>}
|
|
3138
|
+
</div>
|
|
3139
|
+
{onAction && (
|
|
3140
|
+
<button
|
|
3141
|
+
type="button"
|
|
3142
|
+
onClick={() => onAction(item.id)}
|
|
3143
|
+
aria-label={\`Action for \${item.title}\`}
|
|
3144
|
+
>
|
|
3145
|
+
Action
|
|
3146
|
+
</button>
|
|
3147
|
+
)}
|
|
3148
|
+
</li>
|
|
3149
|
+
))}
|
|
3150
|
+
</ul>
|
|
3151
|
+
</section>
|
|
3152
|
+
);
|
|
3153
|
+
}`;
|
|
3154
|
+
}
|
|
3155
|
+
generateReactComponent(name) {
|
|
3156
|
+
return `import React from "react";
|
|
3157
|
+
|
|
3158
|
+
interface ${name}Props {
|
|
3159
|
+
title?: string;
|
|
3160
|
+
children?: React.ReactNode;
|
|
3161
|
+
}
|
|
3162
|
+
|
|
3163
|
+
export function ${name}({ title = "${name}", children }: ${name}Props) {
|
|
3164
|
+
return (
|
|
3165
|
+
<section aria-label={title}>
|
|
3166
|
+
<header>
|
|
3167
|
+
<h2>{title}</h2>
|
|
3168
|
+
</header>
|
|
3169
|
+
<div role="region" aria-label="Content">
|
|
3170
|
+
{children || <p>Main content area</p>}
|
|
3171
|
+
</div>
|
|
3172
|
+
<footer>
|
|
3173
|
+
<button type="button">Primary Action</button>
|
|
3174
|
+
</footer>
|
|
3175
|
+
</section>
|
|
3176
|
+
);
|
|
3177
|
+
}`;
|
|
3178
|
+
}
|
|
3179
|
+
}
|
|
3180
|
+
export class ResearcherAgent extends SubAgent {
|
|
3181
|
+
async performTask(task) {
|
|
3182
|
+
this.updateStatus("running", "Researching information");
|
|
3183
|
+
const query = task.prompt;
|
|
3184
|
+
let webResult = null;
|
|
3185
|
+
try {
|
|
3186
|
+
webResult = await this.webSearch(query, 3);
|
|
3187
|
+
}
|
|
3188
|
+
catch (error) {
|
|
3189
|
+
console.warn(`Web search failed for query "${query}": ${error}`);
|
|
3190
|
+
// Continue without web results
|
|
3191
|
+
}
|
|
3192
|
+
if (webResult) {
|
|
3193
|
+
const analysisPrompt = `Analyze the following web search results for the query "${query}":
|
|
3194
|
+
|
|
3195
|
+
${webResult}
|
|
3196
|
+
|
|
3197
|
+
Please extract:
|
|
3198
|
+
1. Key findings
|
|
3199
|
+
2. Verified facts
|
|
3200
|
+
3. Relevant sources
|
|
3201
|
+
4. Summary
|
|
3202
|
+
|
|
3203
|
+
Format as structured JSON.`;
|
|
3204
|
+
const analysisResponse = await this.callModel(analysisPrompt);
|
|
3205
|
+
if (analysisResponse) {
|
|
3206
|
+
try {
|
|
3207
|
+
return JSON.parse(analysisResponse);
|
|
3208
|
+
}
|
|
3209
|
+
catch {
|
|
3210
|
+
// Fall through to local analysis with web results
|
|
3211
|
+
}
|
|
3212
|
+
}
|
|
3213
|
+
}
|
|
3214
|
+
return this.localResearchFallback(query, webResult, task);
|
|
3215
|
+
}
|
|
3216
|
+
/**
|
|
3217
|
+
* Local fallback: extract topics, search local files, and return findings.
|
|
3218
|
+
*/
|
|
3219
|
+
localResearchFallback(_query, _webResult, _task) {
|
|
3220
|
+
const lower = query.toLowerCase();
|
|
3221
|
+
// --- Extract key topics from the query ---
|
|
3222
|
+
const stopWords = new Set([
|
|
3223
|
+
"the",
|
|
3224
|
+
"a",
|
|
3225
|
+
"an",
|
|
3226
|
+
"and",
|
|
3227
|
+
"or",
|
|
3228
|
+
"but",
|
|
3229
|
+
"in",
|
|
3230
|
+
"on",
|
|
3231
|
+
"at",
|
|
3232
|
+
"to",
|
|
3233
|
+
"for",
|
|
3234
|
+
"of",
|
|
3235
|
+
"with",
|
|
3236
|
+
"by",
|
|
3237
|
+
"from",
|
|
3238
|
+
"is",
|
|
3239
|
+
"are",
|
|
3240
|
+
"was",
|
|
3241
|
+
"were",
|
|
3242
|
+
"be",
|
|
3243
|
+
"been",
|
|
3244
|
+
"being",
|
|
3245
|
+
"have",
|
|
3246
|
+
"has",
|
|
3247
|
+
"had",
|
|
3248
|
+
"do",
|
|
3249
|
+
"does",
|
|
3250
|
+
"did",
|
|
3251
|
+
"will",
|
|
3252
|
+
"would",
|
|
3253
|
+
"could",
|
|
3254
|
+
"should",
|
|
3255
|
+
"may",
|
|
3256
|
+
"might",
|
|
3257
|
+
"must",
|
|
3258
|
+
"can",
|
|
3259
|
+
"this",
|
|
3260
|
+
"that",
|
|
3261
|
+
"these",
|
|
3262
|
+
"those",
|
|
3263
|
+
"what",
|
|
3264
|
+
"how",
|
|
3265
|
+
"why",
|
|
3266
|
+
"when",
|
|
3267
|
+
"where",
|
|
3268
|
+
"which",
|
|
3269
|
+
"who",
|
|
3270
|
+
"about",
|
|
3271
|
+
"into",
|
|
3272
|
+
"through",
|
|
3273
|
+
"during",
|
|
3274
|
+
"before",
|
|
3275
|
+
"after",
|
|
3276
|
+
"it",
|
|
3277
|
+
"its",
|
|
3278
|
+
"my",
|
|
3279
|
+
"your",
|
|
3280
|
+
"we",
|
|
3281
|
+
"they",
|
|
3282
|
+
"them",
|
|
3283
|
+
"our",
|
|
3284
|
+
"not",
|
|
3285
|
+
"no",
|
|
3286
|
+
"if",
|
|
3287
|
+
]);
|
|
3288
|
+
const words = query
|
|
3289
|
+
.split(/\W+/)
|
|
3290
|
+
.filter((w) => w.length > 2 && !stopWords.has(w.toLowerCase()));
|
|
3291
|
+
const topics = [...new Set(words.map((w) => w.toLowerCase()))];
|
|
3292
|
+
// --- Identify technical concepts ---
|
|
3293
|
+
const technicalPatterns = {
|
|
3294
|
+
programming_language: /\b(typescript|javascript|python|rust|go|java|c\+\+|ruby|php|swift|kotlin)\b/i,
|
|
3295
|
+
framework: /\b(react|next\.?js|vue|angular|svelte|express|fastify|django|flask|spring)\b/i,
|
|
3296
|
+
tool: /\b(webpack|vite|esbuild|rollup|babel|eslint|prettier|jest|vitest|docker|kubernetes)\b/i,
|
|
3297
|
+
concept: /\b(api|rest|graphql|websocket|auth|oauth|jwt|database|sql|nosql|cache|queue|cicd|devops)\b/i,
|
|
3298
|
+
pattern: /\b(singleton|factory|observer|strategy|decorator|middleware|hook|context|provider|reducer)\b/i,
|
|
3299
|
+
};
|
|
3300
|
+
const identifiedConcepts = {};
|
|
3301
|
+
for (const [category, pattern] of Object.entries(technicalPatterns)) {
|
|
3302
|
+
const matches = query.match(pattern);
|
|
3303
|
+
if (matches) {
|
|
3304
|
+
identifiedConcepts[category] = [
|
|
3305
|
+
...new Set(matches.map((m) => m.toLowerCase())),
|
|
3306
|
+
];
|
|
3307
|
+
}
|
|
3308
|
+
}
|
|
3309
|
+
// --- Scan local context files for relevant content ---
|
|
3310
|
+
const localFindings = [];
|
|
3311
|
+
const contextFiles = task.context.files;
|
|
3312
|
+
for (const file of contextFiles) {
|
|
3313
|
+
const content = (file.content || "").toLowerCase();
|
|
3314
|
+
const matchedTopics = topics.filter((topic) => content.includes(topic));
|
|
3315
|
+
if (matchedTopics.length > 0) {
|
|
3316
|
+
// Determine relevance based on match density
|
|
3317
|
+
const matchRatio = matchedTopics.length / Math.max(topics.length, 1);
|
|
3318
|
+
const relevance = matchRatio > 0.5 ? "high" : matchRatio > 0.2 ? "medium" : "low";
|
|
3319
|
+
localFindings.push({
|
|
3320
|
+
file: file.path,
|
|
3321
|
+
relevance,
|
|
3322
|
+
matchedTopics,
|
|
3323
|
+
});
|
|
3324
|
+
}
|
|
3325
|
+
}
|
|
3326
|
+
// Sort by relevance
|
|
3327
|
+
const relevanceOrder = {
|
|
3328
|
+
high: 0,
|
|
3329
|
+
medium: 1,
|
|
3330
|
+
low: 2,
|
|
3331
|
+
};
|
|
3332
|
+
localFindings.sort((a, b) => (relevanceOrder[a.relevance] ?? 3) - (relevanceOrder[b.relevance] ?? 3));
|
|
3333
|
+
// --- Build results ---
|
|
3334
|
+
const results = [];
|
|
3335
|
+
// Add web results if available
|
|
3336
|
+
if (webResult) {
|
|
3337
|
+
results.push({
|
|
3338
|
+
title: "Web Search Results",
|
|
3339
|
+
url: "",
|
|
3340
|
+
snippet: webResult.substring(0, 500),
|
|
3341
|
+
relevance: 0.7,
|
|
3342
|
+
source: "web_search",
|
|
3343
|
+
});
|
|
3344
|
+
}
|
|
3345
|
+
// Add local file findings
|
|
3346
|
+
for (const finding of localFindings.slice(0, 10)) {
|
|
3347
|
+
const file = contextFiles.find((f) => f.path === finding.file);
|
|
3348
|
+
const snippet = file
|
|
3349
|
+
? this.extractRelevantSnippet(file.content, finding.matchedTopics)
|
|
3350
|
+
: "";
|
|
3351
|
+
results.push({
|
|
3352
|
+
title: `Local: ${finding.file}`,
|
|
3353
|
+
url: finding.file,
|
|
3354
|
+
snippet,
|
|
3355
|
+
relevance: finding.relevance === "high"
|
|
3356
|
+
? 0.9
|
|
3357
|
+
: finding.relevance === "medium"
|
|
3358
|
+
? 0.6
|
|
3359
|
+
: 0.3,
|
|
3360
|
+
source: "local_file",
|
|
3361
|
+
});
|
|
3362
|
+
}
|
|
3363
|
+
// --- Key findings ---
|
|
3364
|
+
const keyFindings = [];
|
|
3365
|
+
if (localFindings.length > 0) {
|
|
3366
|
+
keyFindings.push(`Found ${localFindings.length} relevant local file(s) matching query topics`);
|
|
3367
|
+
const highRelevance = localFindings.filter((f) => f.relevance === "high");
|
|
3368
|
+
if (highRelevance.length > 0) {
|
|
3369
|
+
keyFindings.push(`High relevance files: ${highRelevance.map((f) => f.file).join(", ")}`);
|
|
3370
|
+
}
|
|
3371
|
+
}
|
|
3372
|
+
if (Object.keys(identifiedConcepts).length > 0) {
|
|
3373
|
+
for (const [category, concepts] of Object.entries(identifiedConcepts)) {
|
|
3374
|
+
keyFindings.push(`Identified ${category}: ${concepts.join(", ")}`);
|
|
3375
|
+
}
|
|
3376
|
+
}
|
|
3377
|
+
if (webResult) {
|
|
3378
|
+
keyFindings.push("Web search results available but model-based analysis unavailable");
|
|
3379
|
+
}
|
|
3380
|
+
if (keyFindings.length === 0) {
|
|
3381
|
+
keyFindings.push("No matching content found in local project files");
|
|
3382
|
+
keyFindings.push("External research requires a model connection for deeper analysis");
|
|
3383
|
+
}
|
|
3384
|
+
return {
|
|
3385
|
+
query,
|
|
3386
|
+
topics,
|
|
3387
|
+
identifiedConcepts,
|
|
3388
|
+
results,
|
|
3389
|
+
localFindings,
|
|
3390
|
+
keyFindings,
|
|
3391
|
+
verifiedFacts: [],
|
|
3392
|
+
limitations: [
|
|
3393
|
+
webResult
|
|
3394
|
+
? "Web results retrieved but could not be analyzed without a model"
|
|
3395
|
+
: "Web search unavailable — results limited to local file scanning",
|
|
3396
|
+
"Deep semantic analysis requires model availability",
|
|
3397
|
+
],
|
|
3398
|
+
note: "Research performed via local keyword matching and file scanning (no model available)",
|
|
3399
|
+
};
|
|
3400
|
+
}
|
|
3401
|
+
/**
|
|
3402
|
+
* Extract a relevant snippet from file content based on matched topics.
|
|
3403
|
+
*/
|
|
3404
|
+
extractRelevantSnippet(content, topics) {
|
|
3405
|
+
const lines = content.split("\n");
|
|
3406
|
+
for (let i = 0; i < lines.length; i++) {
|
|
3407
|
+
const lower = lines[i].toLowerCase();
|
|
3408
|
+
if (topics.some((t) => lower.includes(t))) {
|
|
3409
|
+
// Return surrounding context (3 lines before and after)
|
|
3410
|
+
const start = Math.max(0, i - 2);
|
|
3411
|
+
const end = Math.min(lines.length, i + 3);
|
|
3412
|
+
return lines.slice(start, end).join("\n").substring(0, 300);
|
|
3413
|
+
}
|
|
3414
|
+
}
|
|
3415
|
+
return content.substring(0, 200);
|
|
3416
|
+
}
|
|
3417
|
+
}
|
|
3418
|
+
// ============================================================================
|
|
3419
|
+
// Gatekeeper Agent
|
|
3420
|
+
// ============================================================================
|
|
3421
|
+
export class GatekeeperAgent extends SubAgent {
|
|
3422
|
+
reviewConfig;
|
|
3423
|
+
revisionHistory = [];
|
|
3424
|
+
constructor(messageBus, toolBridge, config) {
|
|
3425
|
+
super("gatekeeper", { mode: "subagent", ...config }, messageBus, toolBridge);
|
|
3426
|
+
this.reviewConfig = {
|
|
3427
|
+
reviewLevel: config?.reviewConfig?.reviewLevel || "standard",
|
|
3428
|
+
criteria: config?.reviewConfig?.criteria || this.getDefaultCriteria(),
|
|
3429
|
+
autoApproveThreshold: config?.reviewConfig?.autoApproveThreshold || 0.8,
|
|
3430
|
+
maxRevisions: config?.reviewConfig?.maxRevisions || 3,
|
|
3431
|
+
enableParallelReview: config?.reviewConfig?.enableParallelReview || true,
|
|
3432
|
+
strictMode: config?.reviewConfig?.strictMode || false,
|
|
3433
|
+
};
|
|
3434
|
+
}
|
|
3435
|
+
getDefaultCriteria() {
|
|
3436
|
+
return {
|
|
3437
|
+
correctness: { enabled: true, weight: 0.25 },
|
|
3438
|
+
completeness: { enabled: true, weight: 0.2 },
|
|
3439
|
+
codeQuality: { enabled: true, weight: 0.15 },
|
|
3440
|
+
security: { enabled: true, weight: 0.2 },
|
|
3441
|
+
performance: { enabled: true, weight: 0.1 },
|
|
3442
|
+
documentation: { enabled: true, weight: 0.1 },
|
|
3443
|
+
};
|
|
3444
|
+
}
|
|
3445
|
+
async performTask(task) {
|
|
3446
|
+
const startTime = Date.now();
|
|
3447
|
+
this.updateStatus("running", "Reviewing agent work");
|
|
3448
|
+
const phaseId = task.context.metadata?.phaseId || "unknown";
|
|
3449
|
+
const agentType = task.context.metadata?.agentType || "analyzer";
|
|
3450
|
+
const workOutput = task.context.metadata?.workOutput || task.prompt;
|
|
3451
|
+
try {
|
|
3452
|
+
// Perform comprehensive review based on criteria
|
|
3453
|
+
const review = await this.performComprehensiveReview(workOutput, task.context);
|
|
3454
|
+
const reviewDuration = Date.now() - startTime;
|
|
3455
|
+
return {
|
|
3456
|
+
...review,
|
|
3457
|
+
reviewId: uuidv4(),
|
|
3458
|
+
phaseId,
|
|
3459
|
+
agentType,
|
|
3460
|
+
workOutput,
|
|
3461
|
+
metadata: {
|
|
3462
|
+
reviewDuration,
|
|
3463
|
+
criteriaChecked: this.getCriteriaCheckList(),
|
|
3464
|
+
warnings: this.generateWarnings(review),
|
|
3465
|
+
},
|
|
3466
|
+
};
|
|
3467
|
+
}
|
|
3468
|
+
catch (error) {
|
|
3469
|
+
this.updateStatus("error", "Gatekeeper review failed");
|
|
3470
|
+
throw error;
|
|
3471
|
+
}
|
|
3472
|
+
}
|
|
3473
|
+
async performComprehensiveReview(workOutput, context) {
|
|
3474
|
+
// Build review prompt based on criteria
|
|
3475
|
+
const prompt = this.buildReviewPrompt(workOutput, context);
|
|
3476
|
+
const response = await this.callModel(prompt);
|
|
3477
|
+
// Parse the review response
|
|
3478
|
+
const parsedReview = this.parseReviewResponse(response);
|
|
3479
|
+
// Calculate overall decision
|
|
3480
|
+
const decision = this.calculateDecision(parsedReview.issues, parsedReview.suggestions);
|
|
3481
|
+
// Calculate confidence score
|
|
3482
|
+
const confidence = this.calculateConfidence(parsedReview);
|
|
3483
|
+
return {
|
|
3484
|
+
decision,
|
|
3485
|
+
reasoning: parsedReview.reasoning ||
|
|
3486
|
+
"Review completed based on criteria evaluation",
|
|
3487
|
+
issues: parsedReview.issues || [],
|
|
3488
|
+
suggestions: parsedReview.suggestions || [],
|
|
3489
|
+
confidence,
|
|
3490
|
+
};
|
|
3491
|
+
}
|
|
3492
|
+
buildReviewPrompt(workOutput, context) {
|
|
3493
|
+
const criteria = this.reviewConfig.criteria;
|
|
3494
|
+
const outputStr = typeof workOutput === "string"
|
|
3495
|
+
? workOutput
|
|
3496
|
+
: JSON.stringify(workOutput, null, 2);
|
|
3497
|
+
let criteriaStr = "Review criteria:\n";
|
|
3498
|
+
if (criteria.correctness.enabled)
|
|
3499
|
+
criteriaStr += "- Correctness: Code functions as intended\n";
|
|
3500
|
+
if (criteria.completeness.enabled)
|
|
3501
|
+
criteriaStr += "- Completeness: All requirements addressed\n";
|
|
3502
|
+
if (criteria.codeQuality.enabled)
|
|
3503
|
+
criteriaStr += "- Code Quality: Clean, maintainable code\n";
|
|
3504
|
+
if (criteria.security.enabled)
|
|
3505
|
+
criteriaStr += "- Security: No vulnerabilities\n";
|
|
3506
|
+
if (criteria.performance.enabled)
|
|
3507
|
+
criteriaStr += "- Performance: Efficient implementation\n";
|
|
3508
|
+
if (criteria.documentation.enabled)
|
|
3509
|
+
criteriaStr += "- Documentation: Clear docs/comments\n";
|
|
3510
|
+
return `You are a senior code reviewer acting as a gatekeeper for a multi-agent coding system.
|
|
3511
|
+
|
|
3512
|
+
Agent Type: Gatekeeper Review
|
|
3513
|
+
Phase: Reviewing work output
|
|
3514
|
+
|
|
3515
|
+
Output to Review:
|
|
3516
|
+
${outputStr.substring(0, 8000)}
|
|
3517
|
+
|
|
3518
|
+
${criteriaStr}
|
|
3519
|
+
|
|
3520
|
+
Context Files: ${context.files.map((f) => f.path).join(", ")}
|
|
3521
|
+
|
|
3522
|
+
Provide your review in JSON format:
|
|
3523
|
+
{
|
|
3524
|
+
"decision": "approve|request_revision|restart_from_scratch|escalate",
|
|
3525
|
+
"reasoning": "Brief explanation of the decision",
|
|
3526
|
+
"issues": [
|
|
3527
|
+
{
|
|
3528
|
+
"id": "issue-1",
|
|
3529
|
+
"severity": "critical|high|medium|low",
|
|
3530
|
+
"category": "correctness|completeness|code_quality|security|performance|style|best_practice|consistency",
|
|
3531
|
+
"description": "Issue description",
|
|
3532
|
+
"location": { "file": "...", "line": ... },
|
|
3533
|
+
"evidence": "Why this is an issue",
|
|
3534
|
+
"remediation": "How to fix"
|
|
3535
|
+
}
|
|
3536
|
+
],
|
|
3537
|
+
"suggestions": [
|
|
3538
|
+
{
|
|
3539
|
+
"id": "suggest-1",
|
|
3540
|
+
"priority": "must|should|could",
|
|
3541
|
+
"category": "...",
|
|
3542
|
+
"description": "Suggestion description",
|
|
3543
|
+
"rationale": "Why this helps",
|
|
3544
|
+
"example": "Optional code example"
|
|
3545
|
+
}
|
|
3546
|
+
]
|
|
3547
|
+
}
|
|
3548
|
+
|
|
3549
|
+
Only include issues and suggestions that are relevant and actionable.`;
|
|
3550
|
+
}
|
|
3551
|
+
parseReviewResponse(response) {
|
|
3552
|
+
const defaultResult = {
|
|
3553
|
+
decision: "approve",
|
|
3554
|
+
reasoning: "Review completed, no critical issues found",
|
|
3555
|
+
issues: [],
|
|
3556
|
+
suggestions: [],
|
|
3557
|
+
};
|
|
3558
|
+
if (!response) {
|
|
3559
|
+
return defaultResult;
|
|
3560
|
+
}
|
|
3561
|
+
try {
|
|
3562
|
+
// Try to parse as JSON
|
|
3563
|
+
const parsed = JSON.parse(response);
|
|
3564
|
+
return {
|
|
3565
|
+
decision: parsed.decision || "approve",
|
|
3566
|
+
reasoning: parsed.reasoning || defaultResult.reasoning,
|
|
3567
|
+
issues: (parsed.issues || []).map((issue, idx) => ({
|
|
3568
|
+
id: issue.id || `issue-${idx}`,
|
|
3569
|
+
severity: issue.severity || "medium",
|
|
3570
|
+
category: issue.category || "code_quality",
|
|
3571
|
+
description: issue.description || "Issue identified",
|
|
3572
|
+
location: issue.location,
|
|
3573
|
+
evidence: issue.evidence || "",
|
|
3574
|
+
remediation: issue.remediation || "",
|
|
3575
|
+
})),
|
|
3576
|
+
suggestions: (parsed.suggestions || []).map((sug, idx) => ({
|
|
3577
|
+
id: sug.id || `suggest-${idx}`,
|
|
3578
|
+
priority: sug.priority || "should",
|
|
3579
|
+
category: sug.category || "code_quality",
|
|
3580
|
+
description: sug.description || "Suggestion",
|
|
3581
|
+
rationale: sug.rationale || "",
|
|
3582
|
+
example: sug.example,
|
|
3583
|
+
})),
|
|
3584
|
+
};
|
|
3585
|
+
}
|
|
3586
|
+
catch {
|
|
3587
|
+
// Fallback: analyze response text
|
|
3588
|
+
return this.analyzeResponseText(response);
|
|
3589
|
+
}
|
|
3590
|
+
}
|
|
3591
|
+
analyzeResponseText(response) {
|
|
3592
|
+
const lowerResponse = response.toLowerCase();
|
|
3593
|
+
// Check for negative indicators
|
|
3594
|
+
const hasCritical = lowerResponse.includes("critical") || lowerResponse.includes("error");
|
|
3595
|
+
const hasMajorIssues = lowerResponse.includes("major") || lowerResponse.includes("serious");
|
|
3596
|
+
const needsWork = lowerResponse.includes("needs work") ||
|
|
3597
|
+
lowerResponse.includes("should fix");
|
|
3598
|
+
let decision = "approve";
|
|
3599
|
+
if (hasCritical) {
|
|
3600
|
+
decision = "request_revision";
|
|
3601
|
+
}
|
|
3602
|
+
else if (hasMajorIssues) {
|
|
3603
|
+
decision = "request_revision";
|
|
3604
|
+
}
|
|
3605
|
+
else if (needsWork) {
|
|
3606
|
+
decision = "request_revision";
|
|
3607
|
+
}
|
|
3608
|
+
return {
|
|
3609
|
+
decision,
|
|
3610
|
+
reasoning: response.substring(0, 500),
|
|
3611
|
+
issues: [],
|
|
3612
|
+
suggestions: [],
|
|
3613
|
+
};
|
|
3614
|
+
}
|
|
3615
|
+
calculateDecision(issues, suggestions) {
|
|
3616
|
+
// Critical issues = restart or escalate
|
|
3617
|
+
const criticalCount = issues.filter((i) => i.severity === "critical").length;
|
|
3618
|
+
if (criticalCount > 0) {
|
|
3619
|
+
return this.reviewConfig.strictMode ? "restart_from_scratch" : "escalate";
|
|
3620
|
+
}
|
|
3621
|
+
// High severity issues = request revision
|
|
3622
|
+
const highCount = issues.filter((i) => i.severity === "high").length;
|
|
3623
|
+
if (highCount > 0) {
|
|
3624
|
+
return "request_revision";
|
|
3625
|
+
}
|
|
3626
|
+
// Must-have suggestions = request revision
|
|
3627
|
+
const mustHave = suggestions.filter((s) => s.priority === "must").length;
|
|
3628
|
+
if (mustHave > 0) {
|
|
3629
|
+
return "request_revision";
|
|
3630
|
+
}
|
|
3631
|
+
// Medium issues = request revision if strict mode
|
|
3632
|
+
const mediumCount = issues.filter((i) => i.severity === "medium").length;
|
|
3633
|
+
if (mediumCount > 2 && this.reviewConfig.strictMode) {
|
|
3634
|
+
return "request_revision";
|
|
3635
|
+
}
|
|
3636
|
+
return "approve";
|
|
3637
|
+
}
|
|
3638
|
+
calculateConfidence(parsedReview) {
|
|
3639
|
+
let confidence = 1.0;
|
|
3640
|
+
// Reduce confidence based on issue count
|
|
3641
|
+
confidence -= parsedReview.issues.length * 0.05;
|
|
3642
|
+
// Reduce confidence based on critical/high issues
|
|
3643
|
+
const criticalHighCount = parsedReview.issues.filter((i) => i.severity === "critical" || i.severity === "high").length;
|
|
3644
|
+
confidence -= criticalHighCount * 0.15;
|
|
3645
|
+
// Boost confidence for thorough reviews
|
|
3646
|
+
if (this.reviewConfig.reviewLevel === "thorough") {
|
|
3647
|
+
confidence += 0.1;
|
|
3648
|
+
}
|
|
3649
|
+
return Math.max(0, Math.min(1, confidence));
|
|
3650
|
+
}
|
|
3651
|
+
getCriteriaCheckList() {
|
|
3652
|
+
const criteria = this.reviewConfig.criteria;
|
|
3653
|
+
const checks = [];
|
|
3654
|
+
if (criteria.correctness.enabled)
|
|
3655
|
+
checks.push("correctness");
|
|
3656
|
+
if (criteria.completeness.enabled)
|
|
3657
|
+
checks.push("completeness");
|
|
3658
|
+
if (criteria.codeQuality.enabled)
|
|
3659
|
+
checks.push("codeQuality");
|
|
3660
|
+
if (criteria.security.enabled)
|
|
3661
|
+
checks.push("security");
|
|
3662
|
+
if (criteria.performance.enabled)
|
|
3663
|
+
checks.push("performance");
|
|
3664
|
+
if (criteria.documentation.enabled)
|
|
3665
|
+
checks.push("documentation");
|
|
3666
|
+
return checks;
|
|
3667
|
+
}
|
|
3668
|
+
generateWarnings(review) {
|
|
3669
|
+
const warnings = [];
|
|
3670
|
+
if (review.decision === "request_revision") {
|
|
3671
|
+
warnings.push("Review flagged for revision");
|
|
3672
|
+
}
|
|
3673
|
+
if (review.decision === "restart_from_scratch") {
|
|
3674
|
+
warnings.push("Major issues detected, restart recommended");
|
|
3675
|
+
}
|
|
3676
|
+
if (review.issues.length > 5) {
|
|
3677
|
+
warnings.push("High number of issues detected");
|
|
3678
|
+
}
|
|
3679
|
+
return warnings;
|
|
3680
|
+
}
|
|
3681
|
+
// Add revision to history
|
|
3682
|
+
addRevision(iteration, phaseId, originalIssue, revision) {
|
|
3683
|
+
this.revisionHistory.push({
|
|
3684
|
+
iteration,
|
|
3685
|
+
phaseId,
|
|
3686
|
+
originalIssue,
|
|
3687
|
+
revision,
|
|
3688
|
+
outcome: "accepted",
|
|
3689
|
+
});
|
|
3690
|
+
}
|
|
3691
|
+
// Get revision history
|
|
3692
|
+
getRevisionHistory() {
|
|
3693
|
+
return [...this.revisionHistory];
|
|
3694
|
+
}
|
|
3695
|
+
// Get current review config
|
|
3696
|
+
getConfig() {
|
|
3697
|
+
return { ...this.reviewConfig };
|
|
3698
|
+
}
|
|
3699
|
+
// Update review config
|
|
3700
|
+
updateConfig(config) {
|
|
3701
|
+
this.reviewConfig = { ...this.reviewConfig, ...config };
|
|
3702
|
+
}
|
|
3703
|
+
}
|
|
3704
|
+
// ============================================================================
|
|
3705
|
+
// Gatekeeper Coordinator - Orchestrates multiple gatekeeper reviews
|
|
3706
|
+
// ============================================================================
|
|
3707
|
+
export class GatekeeperCoordinator {
|
|
3708
|
+
gatekeepers = new Map();
|
|
3709
|
+
reviewQueue = [];
|
|
3710
|
+
isProcessing = false;
|
|
3711
|
+
constructor(messageBus, toolBridge, maxConcurrentReviews = 4) {
|
|
3712
|
+
// Initialize gatekeepers as per spec
|
|
3713
|
+
for (let i = 0; i < maxConcurrentReviews; i++) {
|
|
3714
|
+
const gatekeeper = new GatekeeperAgent(messageBus, toolBridge);
|
|
3715
|
+
this.gatekeepers.set(`gatekeeper-${i}`, gatekeeper);
|
|
3716
|
+
}
|
|
3717
|
+
}
|
|
3718
|
+
// Get an available gatekeeper
|
|
3719
|
+
getAvailableGatekeeper() {
|
|
3720
|
+
for (const [, gatekeeper] of this.gatekeepers) {
|
|
3721
|
+
const status = gatekeeper.getStatus();
|
|
3722
|
+
if (status.status === "idle" || status.status === "completed") {
|
|
3723
|
+
return gatekeeper;
|
|
3724
|
+
}
|
|
3725
|
+
}
|
|
3726
|
+
return null;
|
|
3727
|
+
}
|
|
3728
|
+
// Submit work for gatekeeper review
|
|
3729
|
+
async submitForReview(task) {
|
|
3730
|
+
const gatekeeper = this.getAvailableGatekeeper();
|
|
3731
|
+
if (!gatekeeper) {
|
|
3732
|
+
// Queue the review
|
|
3733
|
+
return new Promise((resolve, reject) => {
|
|
3734
|
+
this.reviewQueue.push({
|
|
3735
|
+
task,
|
|
3736
|
+
gatekeeper: this.gatekeepers.values().next().value,
|
|
3737
|
+
resolve,
|
|
3738
|
+
reject,
|
|
3739
|
+
});
|
|
3740
|
+
this.processQueue();
|
|
3741
|
+
});
|
|
3742
|
+
}
|
|
3743
|
+
return this.performReview(gatekeeper, task);
|
|
3744
|
+
}
|
|
3745
|
+
// Perform review with a specific gatekeeper
|
|
3746
|
+
async performReview(gatekeeper, task) {
|
|
3747
|
+
try {
|
|
3748
|
+
const result = await gatekeeper.execute(task);
|
|
3749
|
+
this.processQueue(); // Try to process next in queue
|
|
3750
|
+
return result;
|
|
3751
|
+
}
|
|
3752
|
+
catch (error) {
|
|
3753
|
+
this.processQueue();
|
|
3754
|
+
throw error;
|
|
3755
|
+
}
|
|
3756
|
+
}
|
|
3757
|
+
// Process queued reviews
|
|
3758
|
+
async processQueue() {
|
|
3759
|
+
if (this.isProcessing || this.reviewQueue.length === 0) {
|
|
3760
|
+
return;
|
|
3761
|
+
}
|
|
3762
|
+
this.isProcessing = true;
|
|
3763
|
+
while (this.reviewQueue.length > 0) {
|
|
3764
|
+
const gatekeeper = this.getAvailableGatekeeper();
|
|
3765
|
+
if (!gatekeeper) {
|
|
3766
|
+
break;
|
|
3767
|
+
}
|
|
3768
|
+
const queueItem = this.reviewQueue.shift();
|
|
3769
|
+
this.performReview(gatekeeper, queueItem.task)
|
|
3770
|
+
.then(queueItem.resolve)
|
|
3771
|
+
.catch(queueItem.reject);
|
|
3772
|
+
}
|
|
3773
|
+
this.isProcessing = false;
|
|
3774
|
+
}
|
|
3775
|
+
// Review all phase outputs
|
|
3776
|
+
async reviewAllPhases(phaseOutputs) {
|
|
3777
|
+
const phaseReviews = [];
|
|
3778
|
+
const revisionHistory = [];
|
|
3779
|
+
let totalConfidence = 0;
|
|
3780
|
+
// Review each phase in parallel (if enabled)
|
|
3781
|
+
const reviewPromises = phaseOutputs.map(async (phase) => {
|
|
3782
|
+
const gatekeeper = this.getAvailableGatekeeper();
|
|
3783
|
+
if (!gatekeeper) {
|
|
3784
|
+
throw new Error("No gatekeeper available for review");
|
|
3785
|
+
}
|
|
3786
|
+
const task = {
|
|
3787
|
+
id: uuidv4(),
|
|
3788
|
+
type: "gatekeeper",
|
|
3789
|
+
prompt: JSON.stringify(phase.output),
|
|
3790
|
+
context: {
|
|
3791
|
+
...phase.context,
|
|
3792
|
+
metadata: {
|
|
3793
|
+
...phase.context.metadata,
|
|
3794
|
+
phaseId: phase.phaseId,
|
|
3795
|
+
agentType: phase.agentType,
|
|
3796
|
+
workOutput: phase.output,
|
|
3797
|
+
},
|
|
3798
|
+
},
|
|
3799
|
+
successCriteria: "Complete gatekeeper review",
|
|
3800
|
+
};
|
|
3801
|
+
const review = await this.performReview(gatekeeper, task);
|
|
3802
|
+
phaseReviews.push(review);
|
|
3803
|
+
totalConfidence += review.confidence;
|
|
3804
|
+
return review;
|
|
3805
|
+
});
|
|
3806
|
+
await Promise.all(reviewPromises);
|
|
3807
|
+
// Calculate overall decision
|
|
3808
|
+
const avgConfidence = phaseReviews.length > 0 ? totalConfidence / phaseReviews.length : 0;
|
|
3809
|
+
const needsRevision = phaseReviews.some((r) => r.decision === "request_revision");
|
|
3810
|
+
const needsRestart = phaseReviews.some((r) => r.decision === "restart_from_scratch");
|
|
3811
|
+
const hasEscalation = phaseReviews.some((r) => r.decision === "escalate");
|
|
3812
|
+
let overallDecision = "approve";
|
|
3813
|
+
if (needsRestart) {
|
|
3814
|
+
overallDecision = "restart_from_scratch";
|
|
3815
|
+
}
|
|
3816
|
+
else if (needsRevision) {
|
|
3817
|
+
overallDecision = "request_revision";
|
|
3818
|
+
}
|
|
3819
|
+
else if (hasEscalation) {
|
|
3820
|
+
overallDecision = "escalate";
|
|
3821
|
+
}
|
|
3822
|
+
// Collect all issues and suggestions
|
|
3823
|
+
const allIssues = phaseReviews.flatMap((r) => r.issues);
|
|
3824
|
+
const allSuggestions = phaseReviews.flatMap((r) => r.suggestions);
|
|
3825
|
+
// Build master decision
|
|
3826
|
+
const masterDecision = this.buildMasterDecision(overallDecision, allIssues, allSuggestions, phaseReviews);
|
|
3827
|
+
return {
|
|
3828
|
+
overallDecision,
|
|
3829
|
+
confidence: avgConfidence,
|
|
3830
|
+
phaseReviews,
|
|
3831
|
+
masterDecision,
|
|
3832
|
+
summary: this.generateSummary(phaseReviews, overallDecision, avgConfidence),
|
|
3833
|
+
nextSteps: this.generateNextSteps(masterDecision, allIssues),
|
|
3834
|
+
revisionHistory,
|
|
3835
|
+
};
|
|
3836
|
+
}
|
|
3837
|
+
buildMasterDecision(overallDecision, allIssues, allSuggestions, phaseReviews) {
|
|
3838
|
+
switch (overallDecision) {
|
|
3839
|
+
case "approve":
|
|
3840
|
+
return {
|
|
3841
|
+
action: "proceed",
|
|
3842
|
+
reasoning: "All phases passed gatekeeper review",
|
|
3843
|
+
affectedPhases: [],
|
|
3844
|
+
};
|
|
3845
|
+
case "request_revision":
|
|
3846
|
+
return {
|
|
3847
|
+
action: "modify_plan",
|
|
3848
|
+
reasoning: `Revision needed: ${allIssues.length} issues, ${allSuggestions.length} suggestions`,
|
|
3849
|
+
affectedPhases: phaseReviews
|
|
3850
|
+
.filter((r) => r.decision === "request_revision")
|
|
3851
|
+
.map((r) => r.phaseId),
|
|
3852
|
+
};
|
|
3853
|
+
case "restart_from_scratch":
|
|
3854
|
+
return {
|
|
3855
|
+
action: "restart",
|
|
3856
|
+
reasoning: `Critical issues require restart: ${allIssues.filter((i) => i.severity === "critical").length} critical issues`,
|
|
3857
|
+
affectedPhases: phaseReviews.map((r) => r.phaseId),
|
|
3858
|
+
};
|
|
3859
|
+
case "escalate":
|
|
3860
|
+
return {
|
|
3861
|
+
action: "stop",
|
|
3862
|
+
reasoning: "Escalation required due to unresolvable issues",
|
|
3863
|
+
affectedPhases: phaseReviews
|
|
3864
|
+
.filter((r) => r.decision === "escalate")
|
|
3865
|
+
.map((r) => r.phaseId),
|
|
3866
|
+
};
|
|
3867
|
+
default:
|
|
3868
|
+
return {
|
|
3869
|
+
action: "proceed",
|
|
3870
|
+
reasoning: "Default decision: proceed",
|
|
3871
|
+
affectedPhases: [],
|
|
3872
|
+
};
|
|
3873
|
+
}
|
|
3874
|
+
}
|
|
3875
|
+
generateSummary(phaseReviews, decision, confidence) {
|
|
3876
|
+
const passed = phaseReviews.filter((r) => r.decision === "approve").length;
|
|
3877
|
+
const revised = phaseReviews.filter((r) => r.decision === "request_revision").length;
|
|
3878
|
+
const restarted = phaseReviews.filter((r) => r.decision === "restart_from_scratch").length;
|
|
3879
|
+
const escalated = phaseReviews.filter((r) => r.decision === "escalate").length;
|
|
3880
|
+
return `Gatekeeper Review Summary:
|
|
3881
|
+
- Total Phases Reviewed: ${phaseReviews.length}
|
|
3882
|
+
- Passed: ${passed}
|
|
3883
|
+
- Requested Revision: ${revised}
|
|
3884
|
+
- Restarted: ${restarted}
|
|
3885
|
+
- Escalated: ${escalated}
|
|
3886
|
+
- Overall Decision: ${decision}
|
|
3887
|
+
- Confidence: ${(confidence * 100).toFixed(1)}%
|
|
3888
|
+
- Total Issues Found: ${phaseReviews.reduce((sum, r) => sum + r.issues.length, 0)}
|
|
3889
|
+
- Total Suggestions: ${phaseReviews.reduce((sum, r) => sum + r.suggestions.length, 0)}`;
|
|
3890
|
+
}
|
|
3891
|
+
generateNextSteps(decision, issues) {
|
|
3892
|
+
const steps = [];
|
|
3893
|
+
switch (decision.action) {
|
|
3894
|
+
case "proceed":
|
|
3895
|
+
steps.push("Proceed to next pipeline phase");
|
|
3896
|
+
steps.push("Synthesize final output");
|
|
3897
|
+
break;
|
|
3898
|
+
case "modify_plan":
|
|
3899
|
+
steps.push("Collect revision requirements from gatekeeper");
|
|
3900
|
+
steps.push("Send modified plan back to affected agents");
|
|
3901
|
+
steps.push("Re-run affected phases with corrections");
|
|
3902
|
+
break;
|
|
3903
|
+
case "restart":
|
|
3904
|
+
steps.push("Clear current phase outputs");
|
|
3905
|
+
steps.push("Restart pipeline from beginning");
|
|
3906
|
+
steps.push("Apply learnings from previous attempts");
|
|
3907
|
+
break;
|
|
3908
|
+
case "stop":
|
|
3909
|
+
steps.push("Report critical issues to user");
|
|
3910
|
+
steps.push("Request human intervention");
|
|
3911
|
+
steps.push("Await further instructions");
|
|
3912
|
+
break;
|
|
3913
|
+
}
|
|
3914
|
+
// Add issue-specific steps
|
|
3915
|
+
if (issues.length > 0) {
|
|
3916
|
+
steps.push(`Address ${issues.length} identified issues`);
|
|
3917
|
+
}
|
|
3918
|
+
return steps;
|
|
3919
|
+
}
|
|
3920
|
+
// Get all gatekeepers status
|
|
3921
|
+
getGatekeeperStatuses() {
|
|
3922
|
+
const statuses = new Map();
|
|
3923
|
+
for (const [id, gatekeeper] of this.gatekeepers) {
|
|
3924
|
+
statuses.set(id, gatekeeper.getStatus());
|
|
3925
|
+
}
|
|
3926
|
+
return statuses;
|
|
3927
|
+
}
|
|
3928
|
+
// Shutdown all gatekeepers
|
|
3929
|
+
async shutdown() {
|
|
3930
|
+
for (const [, gatekeeper] of this.gatekeepers) {
|
|
3931
|
+
await gatekeeper.stop();
|
|
3932
|
+
}
|
|
3933
|
+
this.reviewQueue = [];
|
|
3934
|
+
}
|
|
3935
|
+
}
|
|
3936
|
+
// ============================================================================
|
|
3937
|
+
// Factory Function
|
|
3938
|
+
// ============================================================================
|
|
3939
|
+
export function createAgent(type, messageBus, toolBridge, config, projectRoot) {
|
|
3940
|
+
switch (type) {
|
|
3941
|
+
case "master":
|
|
3942
|
+
return new MasterAgent(messageBus, toolBridge, config, projectRoot);
|
|
3943
|
+
case "planner":
|
|
3944
|
+
return new PlannerAgent(type, config || {}, messageBus, toolBridge);
|
|
3945
|
+
case "analyzer":
|
|
3946
|
+
return new AnalyzerAgent(type, config || {}, messageBus, toolBridge);
|
|
3947
|
+
case "reviewer":
|
|
3948
|
+
return new ReviewerAgent(type, config || {}, messageBus, toolBridge);
|
|
3949
|
+
case "rewriter":
|
|
3950
|
+
return new RewriterAgent(type, config || {}, messageBus, toolBridge);
|
|
3951
|
+
case "ui_designer":
|
|
3952
|
+
return new UiDesignerAgent(type, config || {}, messageBus, toolBridge);
|
|
3953
|
+
case "researcher":
|
|
3954
|
+
return new ResearcherAgent(type, config || {}, messageBus, toolBridge);
|
|
3955
|
+
case "gatekeeper":
|
|
3956
|
+
return new GatekeeperAgent(messageBus, toolBridge, config);
|
|
3957
|
+
default:
|
|
3958
|
+
throw new Error(`Unknown agent type: ${type}`);
|
|
3959
|
+
}
|
|
3960
|
+
}
|
|
3961
|
+
//# sourceMappingURL=base-agents.js.map
|