@holoscript/framework 6.0.3
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/ALL-test-results.json +1 -0
- package/CHANGELOG.md +8 -0
- package/LICENSE +21 -0
- package/ROADMAP.md +175 -0
- package/dist/AgentManifest-CB4xM-Ma.d.cts +704 -0
- package/dist/AgentManifest-CB4xM-Ma.d.ts +704 -0
- package/dist/BehaviorTree-BrBFECv5.d.cts +103 -0
- package/dist/BehaviorTree-BrBFECv5.d.ts +103 -0
- package/dist/InvisibleWallet-BB6tFvRA.d.cts +1732 -0
- package/dist/InvisibleWallet-rtRrBOA8.d.ts +1732 -0
- package/dist/OrchestratorAgent-BvWgf9uw.d.cts +798 -0
- package/dist/OrchestratorAgent-Q_CbVTmO.d.ts +798 -0
- package/dist/agents/index.cjs +4790 -0
- package/dist/agents/index.d.cts +1788 -0
- package/dist/agents/index.d.ts +1788 -0
- package/dist/agents/index.js +4695 -0
- package/dist/ai/index.cjs +5347 -0
- package/dist/ai/index.d.cts +1753 -0
- package/dist/ai/index.d.ts +1753 -0
- package/dist/ai/index.js +5244 -0
- package/dist/behavior.cjs +449 -0
- package/dist/behavior.d.cts +130 -0
- package/dist/behavior.d.ts +130 -0
- package/dist/behavior.js +407 -0
- package/dist/economy/index.cjs +3659 -0
- package/dist/economy/index.d.cts +747 -0
- package/dist/economy/index.d.ts +747 -0
- package/dist/economy/index.js +3617 -0
- package/dist/implementations-D9T3un9D.d.cts +236 -0
- package/dist/implementations-D9T3un9D.d.ts +236 -0
- package/dist/index.cjs +24550 -0
- package/dist/index.d.cts +1729 -0
- package/dist/index.d.ts +1729 -0
- package/dist/index.js +24277 -0
- package/dist/learning/index.cjs +219 -0
- package/dist/learning/index.d.cts +104 -0
- package/dist/learning/index.d.ts +104 -0
- package/dist/learning/index.js +189 -0
- package/dist/negotiation/index.cjs +970 -0
- package/dist/negotiation/index.d.cts +610 -0
- package/dist/negotiation/index.d.ts +610 -0
- package/dist/negotiation/index.js +931 -0
- package/dist/skills/index.cjs +1118 -0
- package/dist/skills/index.d.cts +289 -0
- package/dist/skills/index.d.ts +289 -0
- package/dist/skills/index.js +1079 -0
- package/dist/swarm/index.cjs +5268 -0
- package/dist/swarm/index.d.cts +2433 -0
- package/dist/swarm/index.d.ts +2433 -0
- package/dist/swarm/index.js +5221 -0
- package/dist/training/index.cjs +2745 -0
- package/dist/training/index.d.cts +1734 -0
- package/dist/training/index.d.ts +1734 -0
- package/dist/training/index.js +2687 -0
- package/extract-failures.js +10 -0
- package/package.json +82 -0
- package/src/__tests__/bounty-marketplace.test.ts +374 -0
- package/src/__tests__/delegation.test.ts +144 -0
- package/src/__tests__/distributed-claimer.test.ts +147 -0
- package/src/__tests__/done-log-audit.test.ts +342 -0
- package/src/__tests__/framework.test.ts +865 -0
- package/src/__tests__/goal-synthesizer.test.ts +236 -0
- package/src/__tests__/presence.test.ts +223 -0
- package/src/__tests__/protocol-agent.test.ts +254 -0
- package/src/__tests__/revenue-splitter.test.ts +114 -0
- package/src/__tests__/scenario-driven-todo.test.ts +197 -0
- package/src/__tests__/self-improve.test.ts +349 -0
- package/src/__tests__/service-lifecycle.test.ts +237 -0
- package/src/__tests__/skill-router.test.ts +121 -0
- package/src/agents/AgentManifest.ts +493 -0
- package/src/agents/AgentRegistry.ts +475 -0
- package/src/agents/AgentTypes.ts +585 -0
- package/src/agents/AgentWalletRegistry.ts +83 -0
- package/src/agents/AuthenticatedCRDT.ts +388 -0
- package/src/agents/CapabilityMatcher.ts +453 -0
- package/src/agents/CrossRealityHandoff.ts +305 -0
- package/src/agents/CulturalMemory.ts +454 -0
- package/src/agents/FederatedRegistryAdapter.ts +429 -0
- package/src/agents/NormEngine.ts +450 -0
- package/src/agents/OrchestratorAgent.ts +414 -0
- package/src/agents/SkillWorkflowEngine.ts +472 -0
- package/src/agents/TaskDelegationService.ts +551 -0
- package/src/agents/__tests__/AgentManifest.prod.test.ts +134 -0
- package/src/agents/__tests__/AgentManifest.test.ts +182 -0
- package/src/agents/__tests__/AgentModule.test.ts +864 -0
- package/src/agents/__tests__/AgentRegistry.prod.test.ts +125 -0
- package/src/agents/__tests__/AgentRegistry.test.ts +148 -0
- package/src/agents/__tests__/AgentTypes.test.ts +534 -0
- package/src/agents/__tests__/AgentWalletRegistry.test.ts +152 -0
- package/src/agents/__tests__/AuthenticatedCRDT.test.ts +558 -0
- package/src/agents/__tests__/CapabilityMatcher.prod.test.ts +117 -0
- package/src/agents/__tests__/CapabilityMatcher.test.ts +178 -0
- package/src/agents/__tests__/CrossRealityHandoff.test.ts +402 -0
- package/src/agents/__tests__/CulturalMemory.test.ts +200 -0
- package/src/agents/__tests__/FederatedRegistryAdapter.test.ts +409 -0
- package/src/agents/__tests__/NormEngine.test.ts +276 -0
- package/src/agents/__tests__/OrchestratorAgent.test.ts +182 -0
- package/src/agents/__tests__/SkillWorkflowEngine.test.ts +357 -0
- package/src/agents/__tests__/TaskDelegationService.test.ts +446 -0
- package/src/agents/index.ts +107 -0
- package/src/agents/spatial-comms/Layer1RealTime.ts +621 -0
- package/src/agents/spatial-comms/Layer2A2A.ts +661 -0
- package/src/agents/spatial-comms/Layer3MCP.ts +651 -0
- package/src/agents/spatial-comms/ProtocolTypes.ts +543 -0
- package/src/agents/spatial-comms/SpatialCommClient.ts +483 -0
- package/src/agents/spatial-comms/__tests__/performance-benchmark.test.ts +465 -0
- package/src/agents/spatial-comms/examples/multi-agent-world-creation.ts +409 -0
- package/src/agents/spatial-comms/index.ts +66 -0
- package/src/ai/AIAdapter.ts +313 -0
- package/src/ai/AICopilot.ts +331 -0
- package/src/ai/AIOutputValidator.ts +203 -0
- package/src/ai/BTNodes.ts +239 -0
- package/src/ai/BehaviorSelector.ts +135 -0
- package/src/ai/BehaviorTree.ts +153 -0
- package/src/ai/Blackboard.ts +165 -0
- package/src/ai/GenerationAnalytics.ts +461 -0
- package/src/ai/GenerationCache.ts +265 -0
- package/src/ai/GoalPlanner.ts +165 -0
- package/src/ai/HoloScriptGenerator.ts +580 -0
- package/src/ai/InfluenceMap.ts +180 -0
- package/src/ai/NavMesh.ts +168 -0
- package/src/ai/PerceptionSystem.ts +178 -0
- package/src/ai/PromptTemplates.ts +453 -0
- package/src/ai/SemanticSearchService.ts +80 -0
- package/src/ai/StateMachine.ts +196 -0
- package/src/ai/SteeringBehavior.ts +150 -0
- package/src/ai/SteeringBehaviors.ts +244 -0
- package/src/ai/TrainingDataGenerator.ts +1082 -0
- package/src/ai/UtilityAI.ts +145 -0
- package/src/ai/__tests__/AIAdapter.prod.test.ts +259 -0
- package/src/ai/__tests__/AIAdapter.test.ts +109 -0
- package/src/ai/__tests__/AICopilot.prod.test.ts +341 -0
- package/src/ai/__tests__/AICopilot.test.ts +178 -0
- package/src/ai/__tests__/AIOutputValidator.prod.test.ts +226 -0
- package/src/ai/__tests__/AIOutputValidator.test.ts +138 -0
- package/src/ai/__tests__/BTNodes.prod.test.ts +391 -0
- package/src/ai/__tests__/BTNodes.test.ts +263 -0
- package/src/ai/__tests__/BehaviorSelector.prod.test.ts +129 -0
- package/src/ai/__tests__/BehaviorSelector.test.ts +132 -0
- package/src/ai/__tests__/BehaviorTree.prod.test.ts +266 -0
- package/src/ai/__tests__/BehaviorTree.test.ts +216 -0
- package/src/ai/__tests__/Blackboard.prod.test.ts +339 -0
- package/src/ai/__tests__/Blackboard.test.ts +183 -0
- package/src/ai/__tests__/GenerationAnalytics.prod.test.ts +141 -0
- package/src/ai/__tests__/GenerationAnalytics.test.ts +165 -0
- package/src/ai/__tests__/GenerationCache.prod.test.ts +144 -0
- package/src/ai/__tests__/GenerationCache.test.ts +171 -0
- package/src/ai/__tests__/GoalPlanner.prod.test.ts +189 -0
- package/src/ai/__tests__/GoalPlanner.test.ts +137 -0
- package/src/ai/__tests__/GoalPlannerDepth.prod.test.ts +217 -0
- package/src/ai/__tests__/HoloScriptGenerator.test.ts +125 -0
- package/src/ai/__tests__/InfluenceMap.prod.test.ts +146 -0
- package/src/ai/__tests__/InfluenceMap.test.ts +149 -0
- package/src/ai/__tests__/NavMesh.prod.test.ts +141 -0
- package/src/ai/__tests__/NavMesh.test.ts +159 -0
- package/src/ai/__tests__/PerceptionSystem.prod.test.ts +135 -0
- package/src/ai/__tests__/PerceptionSystem.test.ts +250 -0
- package/src/ai/__tests__/PromptTemplates.prod.test.ts +313 -0
- package/src/ai/__tests__/PromptTemplates.test.ts +146 -0
- package/src/ai/__tests__/SemanticSearch.test.ts +37 -0
- package/src/ai/__tests__/StateMachine.prod.test.ts +162 -0
- package/src/ai/__tests__/StateMachine.test.ts +163 -0
- package/src/ai/__tests__/SteeringBehavior.prod.test.ts +251 -0
- package/src/ai/__tests__/SteeringBehavior.test.ts +135 -0
- package/src/ai/__tests__/SteeringBehaviors.prod.test.ts +133 -0
- package/src/ai/__tests__/SteeringBehaviors.test.ts +151 -0
- package/src/ai/__tests__/TrainingDataGenerator.prod.test.ts +286 -0
- package/src/ai/__tests__/TrainingDataGenerator.test.ts +286 -0
- package/src/ai/__tests__/UtilityAI.prod.test.ts +207 -0
- package/src/ai/__tests__/UtilityAI.test.ts +155 -0
- package/src/ai/__tests__/adapters.prod.test.ts +263 -0
- package/src/ai/__tests__/adapters.test.ts +320 -0
- package/src/ai/adapters.ts +1585 -0
- package/src/ai/index.ts +130 -0
- package/src/behavior/BehaviorPresets.ts +140 -0
- package/src/behavior/BehaviorTree.ts +236 -0
- package/src/behavior/StateMachine.ts +176 -0
- package/src/behavior/StateTrait.ts +67 -0
- package/src/behavior/index.ts +8 -0
- package/src/behavior.ts +8 -0
- package/src/board/audit.ts +284 -0
- package/src/board/board-ops.ts +336 -0
- package/src/board/board-types.ts +302 -0
- package/src/board/index.ts +69 -0
- package/src/define-agent.ts +46 -0
- package/src/define-team.ts +33 -0
- package/src/delegation.ts +265 -0
- package/src/distributed-claimer.ts +228 -0
- package/src/economy/AgentBudgetEnforcer.ts +464 -0
- package/src/economy/BountyManager.ts +185 -0
- package/src/economy/CreatorRevenueAggregator.ts +460 -0
- package/src/economy/InvisibleWallet.ts +82 -0
- package/src/economy/KnowledgeMarketplace.ts +193 -0
- package/src/economy/PaymentWebhookService.ts +512 -0
- package/src/economy/RevenueSplitter.ts +156 -0
- package/src/economy/SubscriptionManager.ts +546 -0
- package/src/economy/UnifiedBudgetOptimizer.ts +635 -0
- package/src/economy/UsageMeter.ts +440 -0
- package/src/economy/_core-stubs.ts +219 -0
- package/src/economy/index.ts +100 -0
- package/src/economy/x402-facilitator.ts +1978 -0
- package/src/index.ts +348 -0
- package/src/knowledge/__tests__/knowledge-consolidator.test.ts +444 -0
- package/src/knowledge/__tests__/knowledge-store-vector.test.ts +291 -0
- package/src/knowledge/brain.ts +167 -0
- package/src/knowledge/consolidation.ts +581 -0
- package/src/knowledge/knowledge-consolidator.ts +510 -0
- package/src/knowledge/knowledge-store.ts +616 -0
- package/src/learning/MemoryConsolidator.ts +102 -0
- package/src/learning/MemoryScorer.ts +69 -0
- package/src/learning/ProceduralCompiler.ts +45 -0
- package/src/learning/SemanticClusterer.ts +66 -0
- package/src/learning/index.ts +8 -0
- package/src/llm/llm-adapter.ts +159 -0
- package/src/mesh/index.ts +309 -0
- package/src/negotiation/NegotiationProtocol.ts +694 -0
- package/src/negotiation/NegotiationTypes.ts +473 -0
- package/src/negotiation/VotingMechanisms.ts +691 -0
- package/src/negotiation/index.ts +49 -0
- package/src/protocol/goal-synthesizer.ts +317 -0
- package/src/protocol/implementations.ts +474 -0
- package/src/protocol/micro-phase-decomposer.ts +299 -0
- package/src/protocol/micro-step-decomposer.test.ts +306 -0
- package/src/protocol-agent.test.ts +353 -0
- package/src/protocol-agent.ts +670 -0
- package/src/self-improve/absorb-scanner.ts +252 -0
- package/src/self-improve/evolution-engine.ts +149 -0
- package/src/self-improve/framework-absorber.ts +214 -0
- package/src/self-improve/index.ts +50 -0
- package/src/self-improve/prompt-optimizer.ts +212 -0
- package/src/self-improve/test-generator.ts +175 -0
- package/src/skill-router.ts +186 -0
- package/src/skills/index.ts +5 -0
- package/src/skills/skill-md-bridge.ts +1699 -0
- package/src/swarm/ACOEngine.ts +261 -0
- package/src/swarm/CollectiveIntelligence.ts +383 -0
- package/src/swarm/ContributionSynthesizer.ts +481 -0
- package/src/swarm/LeaderElection.ts +393 -0
- package/src/swarm/PSOEngine.ts +206 -0
- package/src/swarm/QuorumPolicy.ts +173 -0
- package/src/swarm/SwarmCoordinator.ts +335 -0
- package/src/swarm/SwarmManager.ts +442 -0
- package/src/swarm/SwarmMembership.ts +456 -0
- package/src/swarm/VotingRound.ts +255 -0
- package/src/swarm/__tests__/ACOEngine.prod.test.ts +164 -0
- package/src/swarm/__tests__/ACOEngine.test.ts +117 -0
- package/src/swarm/__tests__/CollectiveIntelligence.prod.test.ts +296 -0
- package/src/swarm/__tests__/CollectiveIntelligence.test.ts +457 -0
- package/src/swarm/__tests__/ContributionSynthesizer.prod.test.ts +269 -0
- package/src/swarm/__tests__/ContributionSynthesizer.test.ts +254 -0
- package/src/swarm/__tests__/LeaderElection.prod.test.ts +196 -0
- package/src/swarm/__tests__/LeaderElection.test.ts +151 -0
- package/src/swarm/__tests__/PSOEngine.prod.test.ts +162 -0
- package/src/swarm/__tests__/PSOEngine.test.ts +106 -0
- package/src/swarm/__tests__/QuorumPolicy.prod.test.ts +216 -0
- package/src/swarm/__tests__/QuorumPolicy.test.ts +177 -0
- package/src/swarm/__tests__/SwarmCoordinator.prod.test.ts +186 -0
- package/src/swarm/__tests__/SwarmCoordinator.test.ts +167 -0
- package/src/swarm/__tests__/SwarmManager.prod.test.ts +308 -0
- package/src/swarm/__tests__/SwarmManager.test.ts +373 -0
- package/src/swarm/__tests__/SwarmMembership.prod.test.ts +273 -0
- package/src/swarm/__tests__/SwarmMembership.test.ts +264 -0
- package/src/swarm/__tests__/VotingRound.prod.test.ts +233 -0
- package/src/swarm/__tests__/VotingRound.test.ts +174 -0
- package/src/swarm/analytics/SwarmInspector.ts +476 -0
- package/src/swarm/analytics/SwarmMetrics.ts +449 -0
- package/src/swarm/analytics/__tests__/SwarmInspector.prod.test.ts +366 -0
- package/src/swarm/analytics/__tests__/SwarmInspector.test.ts +454 -0
- package/src/swarm/analytics/__tests__/SwarmMetrics.prod.test.ts +254 -0
- package/src/swarm/analytics/__tests__/SwarmMetrics.test.ts +370 -0
- package/src/swarm/analytics/index.ts +7 -0
- package/src/swarm/index.ts +69 -0
- package/src/swarm/messaging/BroadcastChannel.ts +509 -0
- package/src/swarm/messaging/GossipProtocol.ts +565 -0
- package/src/swarm/messaging/SwarmEventBus.ts +443 -0
- package/src/swarm/messaging/__tests__/BroadcastChannel.prod.test.ts +331 -0
- package/src/swarm/messaging/__tests__/BroadcastChannel.test.ts +333 -0
- package/src/swarm/messaging/__tests__/GossipProtocol.prod.test.ts +356 -0
- package/src/swarm/messaging/__tests__/GossipProtocol.test.ts +437 -0
- package/src/swarm/messaging/__tests__/SwarmEventBus.prod.test.ts +191 -0
- package/src/swarm/messaging/__tests__/SwarmEventBus.test.ts +247 -0
- package/src/swarm/messaging/index.ts +8 -0
- package/src/swarm/spatial/FlockingBehavior.ts +462 -0
- package/src/swarm/spatial/FormationController.ts +500 -0
- package/src/swarm/spatial/Vector3.ts +170 -0
- package/src/swarm/spatial/ZoneClaiming.ts +509 -0
- package/src/swarm/spatial/__tests__/FlockingBehavior.prod.test.ts +239 -0
- package/src/swarm/spatial/__tests__/FlockingBehavior.test.ts +298 -0
- package/src/swarm/spatial/__tests__/FormationController.prod.test.ts +240 -0
- package/src/swarm/spatial/__tests__/FormationController.test.ts +297 -0
- package/src/swarm/spatial/__tests__/Vector3.prod.test.ts +283 -0
- package/src/swarm/spatial/__tests__/Vector3.test.ts +224 -0
- package/src/swarm/spatial/__tests__/ZoneClaiming.prod.test.ts +246 -0
- package/src/swarm/spatial/__tests__/ZoneClaiming.test.ts +374 -0
- package/src/swarm/spatial/index.ts +28 -0
- package/src/team.ts +1245 -0
- package/src/training/LRScheduler.ts +377 -0
- package/src/training/QualityScoringPipeline.ts +139 -0
- package/src/training/SoftDedup.ts +461 -0
- package/src/training/SparsityMonitor.ts +685 -0
- package/src/training/SparsityMonitorTypes.ts +209 -0
- package/src/training/SpatialTrainingDataGenerator.ts +1526 -0
- package/src/training/SpatialTrainingDataTypes.ts +216 -0
- package/src/training/TrainingPipelineConfig.ts +215 -0
- package/src/training/constants.ts +94 -0
- package/src/training/index.ts +138 -0
- package/src/training/schema.ts +147 -0
- package/src/training/scripts/generate-novel-use-cases-dataset.ts +272 -0
- package/src/training/scripts/generate-spatial-dataset.ts +521 -0
- package/src/training/training/data/novel-use-cases.jsonl +153 -0
- package/src/training/training/data/spatial-reasoning-10k.jsonl +9354 -0
- package/src/training/trainingmonkey/TrainingMonkeyIntegration.ts +477 -0
- package/src/training/trainingmonkey/TrainingMonkeyTypes.ts +230 -0
- package/src/training/trainingmonkey/index.ts +26 -0
- package/src/training/trait-mappings.ts +157 -0
- package/src/types/core-stubs.d.ts +113 -0
- package/src/types.ts +304 -0
- package/test-output.txt +0 -0
- package/test-result.json +1 -0
- package/tsc-errors.txt +4 -0
- package/tsc_output.txt +0 -0
- package/tsconfig.json +14 -0
- package/tsup-learning-esm.config.ts +12 -0
- package/tsup.config.ts +21 -0
- package/typescript-errors-2.txt +0 -0
- package/typescript-errors.txt +22 -0
- package/vitest-log-utf8.txt +268 -0
- package/vitest-log.txt +0 -0
- package/vitest.config.ts +8 -0
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MicroPhaseDecomposer — LLM-powered parallel task decomposition (FW-0.2)
|
|
3
|
+
*
|
|
4
|
+
* Takes a complex task description, uses an LLM to break it into independent
|
|
5
|
+
* micro-phases with dependency edges, then builds a wave-based execution plan
|
|
6
|
+
* where each wave runs in parallel.
|
|
7
|
+
*
|
|
8
|
+
* Two modes:
|
|
9
|
+
* 1. LLM-powered: `decompose()` calls the LLM to analyze task complexity
|
|
10
|
+
* 2. Manual: `decomposeManual()` accepts pre-defined phases (for testing / deterministic use)
|
|
11
|
+
*
|
|
12
|
+
* Wired into Team: when a task's estimated complexity exceeds a threshold,
|
|
13
|
+
* the team decomposes it before distributing sub-phases to agents.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import type { ModelConfig } from '../types';
|
|
17
|
+
import { callLLM } from '../llm/llm-adapter';
|
|
18
|
+
import type { LLMMessage } from '../llm/llm-adapter';
|
|
19
|
+
|
|
20
|
+
// ── Public Types ──
|
|
21
|
+
|
|
22
|
+
export interface TaskDescription {
|
|
23
|
+
id: string;
|
|
24
|
+
title: string;
|
|
25
|
+
description: string;
|
|
26
|
+
requiredCapabilities?: string[];
|
|
27
|
+
maxParallelism?: number;
|
|
28
|
+
complexityThreshold?: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface MicroPhase {
|
|
32
|
+
id: string;
|
|
33
|
+
description: string;
|
|
34
|
+
dependencies: string[];
|
|
35
|
+
estimatedDuration: number;
|
|
36
|
+
requiredCapabilities: string[];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface WaveExecutionPlan {
|
|
40
|
+
waves: MicroPhase[][];
|
|
41
|
+
totalEstimatedDuration: number;
|
|
42
|
+
parallelizationRatio: number;
|
|
43
|
+
phaseCount: number;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface DecompositionResult {
|
|
47
|
+
taskId: string;
|
|
48
|
+
phases: MicroPhase[];
|
|
49
|
+
plan: WaveExecutionPlan;
|
|
50
|
+
wasDecomposed: boolean;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// ── Complexity Threshold ──
|
|
54
|
+
|
|
55
|
+
/** Tasks with fewer than this many estimated sub-steps skip decomposition. */
|
|
56
|
+
const DEFAULT_COMPLEXITY_THRESHOLD = 2;
|
|
57
|
+
|
|
58
|
+
// ── LLM Adapter Interface (injectable for testing) ──
|
|
59
|
+
|
|
60
|
+
export interface LLMAdapter {
|
|
61
|
+
call(messages: LLMMessage[], options?: { maxTokens?: number; temperature?: number }): Promise<string>;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Create an LLMAdapter from a ModelConfig */
|
|
65
|
+
export function createLLMAdapter(config: ModelConfig): LLMAdapter {
|
|
66
|
+
return {
|
|
67
|
+
async call(messages, options) {
|
|
68
|
+
const response = await callLLM(config, messages, options);
|
|
69
|
+
return response.content;
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// ── Decomposer ──
|
|
75
|
+
|
|
76
|
+
export class SmartMicroPhaseDecomposer {
|
|
77
|
+
constructor(private llm: LLMAdapter) {}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Decompose a complex task into parallel micro-phases using LLM analysis.
|
|
81
|
+
* Returns phases with dependency edges and a wave-based execution plan.
|
|
82
|
+
*/
|
|
83
|
+
async decompose(task: TaskDescription): Promise<DecompositionResult> {
|
|
84
|
+
const threshold = task.complexityThreshold ?? DEFAULT_COMPLEXITY_THRESHOLD;
|
|
85
|
+
|
|
86
|
+
const messages: LLMMessage[] = [
|
|
87
|
+
{
|
|
88
|
+
role: 'system',
|
|
89
|
+
content: `You are a task decomposition engine. Break complex tasks into independent micro-phases.
|
|
90
|
+
|
|
91
|
+
Output ONLY valid JSON matching this schema:
|
|
92
|
+
{
|
|
93
|
+
"phases": [
|
|
94
|
+
{
|
|
95
|
+
"id": "phase_1",
|
|
96
|
+
"description": "what this phase does",
|
|
97
|
+
"dependencies": [],
|
|
98
|
+
"estimatedDuration": 5000,
|
|
99
|
+
"requiredCapabilities": ["coding"]
|
|
100
|
+
}
|
|
101
|
+
]
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
Rules:
|
|
105
|
+
- Each phase must have a unique id (phase_1, phase_2, etc.)
|
|
106
|
+
- dependencies is an array of phase ids this phase depends on
|
|
107
|
+
- estimatedDuration is in milliseconds
|
|
108
|
+
- requiredCapabilities: one or more of ["coding", "research", "review", "testing", "architecture"]
|
|
109
|
+
- Maximize parallelism: only add dependencies when truly required
|
|
110
|
+
- If the task is simple (1-2 steps), return a single phase
|
|
111
|
+
- Keep phases focused — each should be independently executable`,
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
role: 'user',
|
|
115
|
+
content: `Decompose this task:\n\nTitle: ${task.title}\nDescription: ${task.description}\nCapabilities available: ${(task.requiredCapabilities ?? ['coding']).join(', ')}`,
|
|
116
|
+
},
|
|
117
|
+
];
|
|
118
|
+
|
|
119
|
+
const raw = await this.llm.call(messages, { maxTokens: 2048, temperature: 0.3 });
|
|
120
|
+
const phases = this.parseLLMResponse(raw, task.id);
|
|
121
|
+
|
|
122
|
+
if (phases.length < threshold) {
|
|
123
|
+
return {
|
|
124
|
+
taskId: task.id,
|
|
125
|
+
phases,
|
|
126
|
+
plan: this.buildExecutionPlan(phases),
|
|
127
|
+
wasDecomposed: false,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Enforce maxParallelism if set
|
|
132
|
+
if (task.maxParallelism) {
|
|
133
|
+
this.enforceParallelismLimit(phases, task.maxParallelism);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const plan = this.buildExecutionPlan(phases);
|
|
137
|
+
return { taskId: task.id, phases, plan, wasDecomposed: true };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Manual decomposition — accepts pre-defined phases. Useful for testing
|
|
142
|
+
* and deterministic pipelines where LLM analysis isn't needed.
|
|
143
|
+
*/
|
|
144
|
+
decomposeManual(taskId: string, phases: MicroPhase[]): DecompositionResult {
|
|
145
|
+
this.validatePhases(phases);
|
|
146
|
+
const plan = this.buildExecutionPlan(phases);
|
|
147
|
+
return { taskId, phases, plan, wasDecomposed: phases.length > 1 };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Build an execution plan from phases — topological sort into parallel waves.
|
|
152
|
+
* Each wave contains phases that can run simultaneously.
|
|
153
|
+
*/
|
|
154
|
+
buildExecutionPlan(phases: MicroPhase[]): WaveExecutionPlan {
|
|
155
|
+
if (phases.length === 0) {
|
|
156
|
+
return { waves: [], totalEstimatedDuration: 0, parallelizationRatio: 0, phaseCount: 0 };
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
this.validatePhases(phases);
|
|
160
|
+
|
|
161
|
+
const sorted = this.topologicalSort(phases);
|
|
162
|
+
const phaseMap = new Map(phases.map(p => [p.id, p]));
|
|
163
|
+
const waveAssignment = new Map<string, number>();
|
|
164
|
+
const waves: MicroPhase[][] = [];
|
|
165
|
+
|
|
166
|
+
for (const phaseId of sorted) {
|
|
167
|
+
const phase = phaseMap.get(phaseId)!;
|
|
168
|
+
let waveIdx = 0;
|
|
169
|
+
|
|
170
|
+
for (const depId of phase.dependencies) {
|
|
171
|
+
waveIdx = Math.max(waveIdx, (waveAssignment.get(depId) ?? 0) + 1);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
while (waves.length <= waveIdx) {
|
|
175
|
+
waves.push([]);
|
|
176
|
+
}
|
|
177
|
+
waves[waveIdx].push(phase);
|
|
178
|
+
waveAssignment.set(phaseId, waveIdx);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Calculate timing
|
|
182
|
+
const sequentialTime = phases.reduce((sum, p) => sum + p.estimatedDuration, 0);
|
|
183
|
+
const parallelTime = waves.reduce((sum, wave) => {
|
|
184
|
+
return sum + Math.max(...wave.map(p => p.estimatedDuration));
|
|
185
|
+
}, 0);
|
|
186
|
+
|
|
187
|
+
return {
|
|
188
|
+
waves,
|
|
189
|
+
totalEstimatedDuration: parallelTime,
|
|
190
|
+
parallelizationRatio: sequentialTime > 0
|
|
191
|
+
? Math.round(((sequentialTime - parallelTime) / sequentialTime) * 100)
|
|
192
|
+
: 0,
|
|
193
|
+
phaseCount: phases.length,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// ── Internal ──
|
|
198
|
+
|
|
199
|
+
private parseLLMResponse(raw: string, taskId: string): MicroPhase[] {
|
|
200
|
+
try {
|
|
201
|
+
// Extract JSON from potential markdown code blocks
|
|
202
|
+
const jsonMatch = raw.match(/```(?:json)?\s*([\s\S]*?)```/) || [null, raw];
|
|
203
|
+
const cleaned = (jsonMatch[1] ?? raw).trim();
|
|
204
|
+
const parsed = JSON.parse(cleaned) as { phases?: unknown[] };
|
|
205
|
+
|
|
206
|
+
if (!parsed.phases || !Array.isArray(parsed.phases)) {
|
|
207
|
+
return this.fallbackSinglePhase(taskId);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return (parsed.phases as Record<string, unknown>[]).map((p, i) => ({
|
|
211
|
+
id: String(p.id ?? `phase_${i + 1}`),
|
|
212
|
+
description: String(p.description ?? 'Unknown phase'),
|
|
213
|
+
dependencies: Array.isArray(p.dependencies) ? p.dependencies.map(String) : [],
|
|
214
|
+
estimatedDuration: typeof p.estimatedDuration === 'number' ? p.estimatedDuration : 5000,
|
|
215
|
+
requiredCapabilities: Array.isArray(p.requiredCapabilities) ? p.requiredCapabilities.map(String) : ['coding'],
|
|
216
|
+
}));
|
|
217
|
+
} catch {
|
|
218
|
+
return this.fallbackSinglePhase(taskId);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
private fallbackSinglePhase(taskId: string): MicroPhase[] {
|
|
223
|
+
return [{
|
|
224
|
+
id: `${taskId}_single`,
|
|
225
|
+
description: 'Execute task as single unit',
|
|
226
|
+
dependencies: [],
|
|
227
|
+
estimatedDuration: 10000,
|
|
228
|
+
requiredCapabilities: ['coding'],
|
|
229
|
+
}];
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
private validatePhases(phases: MicroPhase[]): void {
|
|
233
|
+
const ids = new Set(phases.map(p => p.id));
|
|
234
|
+
|
|
235
|
+
for (const phase of phases) {
|
|
236
|
+
for (const dep of phase.dependencies) {
|
|
237
|
+
if (!ids.has(dep)) {
|
|
238
|
+
throw new Error(`Phase "${phase.id}" depends on unknown phase "${dep}"`);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Check for cycles
|
|
244
|
+
this.topologicalSort(phases); // throws on cycle
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
private topologicalSort(phases: MicroPhase[]): string[] {
|
|
248
|
+
const phaseMap = new Map(phases.map(p => [p.id, p]));
|
|
249
|
+
const visited = new Set<string>();
|
|
250
|
+
const inStack = new Set<string>();
|
|
251
|
+
const result: string[] = [];
|
|
252
|
+
|
|
253
|
+
const dfs = (id: string) => {
|
|
254
|
+
if (inStack.has(id)) {
|
|
255
|
+
throw new Error(`Circular dependency detected involving phase "${id}"`);
|
|
256
|
+
}
|
|
257
|
+
if (visited.has(id)) return;
|
|
258
|
+
|
|
259
|
+
inStack.add(id);
|
|
260
|
+
const phase = phaseMap.get(id)!;
|
|
261
|
+
for (const dep of phase.dependencies) {
|
|
262
|
+
dfs(dep);
|
|
263
|
+
}
|
|
264
|
+
inStack.delete(id);
|
|
265
|
+
visited.add(id);
|
|
266
|
+
result.push(id);
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
for (const id of phaseMap.keys()) {
|
|
270
|
+
if (!visited.has(id)) dfs(id);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return result;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* If maxParallelism is set, add synthetic dependencies to limit
|
|
278
|
+
* the number of phases running concurrently in any wave.
|
|
279
|
+
*/
|
|
280
|
+
private enforceParallelismLimit(phases: MicroPhase[], maxParallelism: number): void {
|
|
281
|
+
// Build initial plan to see wave sizes
|
|
282
|
+
const plan = this.buildExecutionPlan(phases);
|
|
283
|
+
const phaseMap = new Map(phases.map(p => [p.id, p]));
|
|
284
|
+
|
|
285
|
+
for (const wave of plan.waves) {
|
|
286
|
+
if (wave.length <= maxParallelism) continue;
|
|
287
|
+
|
|
288
|
+
// Split oversized wave: phases beyond the limit depend on earlier ones
|
|
289
|
+
const overflow = wave.slice(maxParallelism);
|
|
290
|
+
for (let i = 0; i < overflow.length; i++) {
|
|
291
|
+
const target = phaseMap.get(overflow[i].id)!;
|
|
292
|
+
const anchor = wave[i % maxParallelism];
|
|
293
|
+
if (!target.dependencies.includes(anchor.id)) {
|
|
294
|
+
target.dependencies.push(anchor.id);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
SmartMicroPhaseDecomposer,
|
|
4
|
+
type LLMAdapter,
|
|
5
|
+
type MicroPhase,
|
|
6
|
+
type TaskDescription,
|
|
7
|
+
} from './micro-phase-decomposer';
|
|
8
|
+
|
|
9
|
+
// ── Test Helpers ──
|
|
10
|
+
|
|
11
|
+
function mockLLM(response: string): LLMAdapter {
|
|
12
|
+
return { call: vi.fn().mockResolvedValue(response) };
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function phase(id: string, deps: string[] = [], duration = 5000, caps: string[] = ['coding']): MicroPhase {
|
|
16
|
+
return { id, description: `Phase ${id}`, dependencies: deps, estimatedDuration: duration, requiredCapabilities: caps };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const simpleTask: TaskDescription = {
|
|
20
|
+
id: 'task_1',
|
|
21
|
+
title: 'Build login page',
|
|
22
|
+
description: 'Create a login form with email and password fields',
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// ── Tests ──
|
|
26
|
+
|
|
27
|
+
describe('SmartMicroPhaseDecomposer', () => {
|
|
28
|
+
describe('buildExecutionPlan', () => {
|
|
29
|
+
it('groups independent phases into the same wave', () => {
|
|
30
|
+
const llm = mockLLM('');
|
|
31
|
+
const decomposer = new SmartMicroPhaseDecomposer(llm);
|
|
32
|
+
|
|
33
|
+
const phases: MicroPhase[] = [
|
|
34
|
+
phase('a'),
|
|
35
|
+
phase('b'),
|
|
36
|
+
phase('c'),
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
const plan = decomposer.buildExecutionPlan(phases);
|
|
40
|
+
|
|
41
|
+
expect(plan.waves).toHaveLength(1);
|
|
42
|
+
expect(plan.waves[0]).toHaveLength(3);
|
|
43
|
+
expect(plan.phaseCount).toBe(3);
|
|
44
|
+
expect(plan.totalEstimatedDuration).toBe(5000);
|
|
45
|
+
expect(plan.parallelizationRatio).toBe(67); // (15000-5000)/15000 * 100
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('creates sequential waves for dependent phases', () => {
|
|
49
|
+
const llm = mockLLM('');
|
|
50
|
+
const decomposer = new SmartMicroPhaseDecomposer(llm);
|
|
51
|
+
|
|
52
|
+
const phases: MicroPhase[] = [
|
|
53
|
+
phase('a'),
|
|
54
|
+
phase('b', ['a']),
|
|
55
|
+
phase('c', ['b']),
|
|
56
|
+
];
|
|
57
|
+
|
|
58
|
+
const plan = decomposer.buildExecutionPlan(phases);
|
|
59
|
+
|
|
60
|
+
expect(plan.waves).toHaveLength(3);
|
|
61
|
+
expect(plan.waves[0].map(p => p.id)).toEqual(['a']);
|
|
62
|
+
expect(plan.waves[1].map(p => p.id)).toEqual(['b']);
|
|
63
|
+
expect(plan.waves[2].map(p => p.id)).toEqual(['c']);
|
|
64
|
+
expect(plan.totalEstimatedDuration).toBe(15000);
|
|
65
|
+
expect(plan.parallelizationRatio).toBe(0);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('handles diamond dependencies correctly', () => {
|
|
69
|
+
const llm = mockLLM('');
|
|
70
|
+
const decomposer = new SmartMicroPhaseDecomposer(llm);
|
|
71
|
+
|
|
72
|
+
// a
|
|
73
|
+
// / \
|
|
74
|
+
// b c
|
|
75
|
+
// \ /
|
|
76
|
+
// d
|
|
77
|
+
const phases: MicroPhase[] = [
|
|
78
|
+
phase('a'),
|
|
79
|
+
phase('b', ['a']),
|
|
80
|
+
phase('c', ['a']),
|
|
81
|
+
phase('d', ['b', 'c']),
|
|
82
|
+
];
|
|
83
|
+
|
|
84
|
+
const plan = decomposer.buildExecutionPlan(phases);
|
|
85
|
+
|
|
86
|
+
expect(plan.waves).toHaveLength(3);
|
|
87
|
+
expect(plan.waves[0].map(p => p.id)).toEqual(['a']);
|
|
88
|
+
expect(plan.waves[1].map(p => p.id).sort()).toEqual(['b', 'c']);
|
|
89
|
+
expect(plan.waves[2].map(p => p.id)).toEqual(['d']);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('returns empty plan for no phases', () => {
|
|
93
|
+
const llm = mockLLM('');
|
|
94
|
+
const decomposer = new SmartMicroPhaseDecomposer(llm);
|
|
95
|
+
const plan = decomposer.buildExecutionPlan([]);
|
|
96
|
+
|
|
97
|
+
expect(plan.waves).toHaveLength(0);
|
|
98
|
+
expect(plan.totalEstimatedDuration).toBe(0);
|
|
99
|
+
expect(plan.phaseCount).toBe(0);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('throws on unknown dependency', () => {
|
|
103
|
+
const llm = mockLLM('');
|
|
104
|
+
const decomposer = new SmartMicroPhaseDecomposer(llm);
|
|
105
|
+
|
|
106
|
+
expect(() => {
|
|
107
|
+
decomposer.buildExecutionPlan([phase('a', ['nonexistent'])]);
|
|
108
|
+
}).toThrow('depends on unknown phase');
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('throws on circular dependency', () => {
|
|
112
|
+
const llm = mockLLM('');
|
|
113
|
+
const decomposer = new SmartMicroPhaseDecomposer(llm);
|
|
114
|
+
|
|
115
|
+
const phases: MicroPhase[] = [
|
|
116
|
+
phase('a', ['b']),
|
|
117
|
+
phase('b', ['a']),
|
|
118
|
+
];
|
|
119
|
+
|
|
120
|
+
expect(() => decomposer.buildExecutionPlan(phases)).toThrow('Circular dependency');
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
describe('decomposeManual', () => {
|
|
125
|
+
it('creates a plan from manually specified phases', () => {
|
|
126
|
+
const llm = mockLLM('');
|
|
127
|
+
const decomposer = new SmartMicroPhaseDecomposer(llm);
|
|
128
|
+
|
|
129
|
+
const phases = [phase('setup'), phase('build', ['setup']), phase('test', ['build'])];
|
|
130
|
+
const result = decomposer.decomposeManual('task_manual', phases);
|
|
131
|
+
|
|
132
|
+
expect(result.taskId).toBe('task_manual');
|
|
133
|
+
expect(result.wasDecomposed).toBe(true);
|
|
134
|
+
expect(result.plan.waves).toHaveLength(3);
|
|
135
|
+
expect(result.phases).toHaveLength(3);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('marks single-phase tasks as not decomposed', () => {
|
|
139
|
+
const llm = mockLLM('');
|
|
140
|
+
const decomposer = new SmartMicroPhaseDecomposer(llm);
|
|
141
|
+
|
|
142
|
+
const result = decomposer.decomposeManual('task_simple', [phase('only')]);
|
|
143
|
+
|
|
144
|
+
expect(result.wasDecomposed).toBe(false);
|
|
145
|
+
expect(result.plan.waves).toHaveLength(1);
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
describe('decompose (LLM-powered)', () => {
|
|
150
|
+
it('parses well-formed LLM JSON response', async () => {
|
|
151
|
+
const llmResponse = JSON.stringify({
|
|
152
|
+
phases: [
|
|
153
|
+
{ id: 'phase_1', description: 'Setup project', dependencies: [], estimatedDuration: 3000, requiredCapabilities: ['coding'] },
|
|
154
|
+
{ id: 'phase_2', description: 'Write components', dependencies: ['phase_1'], estimatedDuration: 8000, requiredCapabilities: ['coding'] },
|
|
155
|
+
{ id: 'phase_3', description: 'Write tests', dependencies: ['phase_1'], estimatedDuration: 5000, requiredCapabilities: ['testing'] },
|
|
156
|
+
{ id: 'phase_4', description: 'Integration test', dependencies: ['phase_2', 'phase_3'], estimatedDuration: 4000, requiredCapabilities: ['testing'] },
|
|
157
|
+
],
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
const llm = mockLLM(llmResponse);
|
|
161
|
+
const decomposer = new SmartMicroPhaseDecomposer(llm);
|
|
162
|
+
const result = await decomposer.decompose(simpleTask);
|
|
163
|
+
|
|
164
|
+
expect(result.wasDecomposed).toBe(true);
|
|
165
|
+
expect(result.phases).toHaveLength(4);
|
|
166
|
+
expect(result.plan.waves).toHaveLength(3);
|
|
167
|
+
// Wave 0: phase_1, Wave 1: phase_2 + phase_3, Wave 2: phase_4
|
|
168
|
+
expect(result.plan.waves[1]).toHaveLength(2);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it('extracts JSON from markdown code blocks', async () => {
|
|
172
|
+
const llmResponse = '```json\n{"phases": [{"id": "p1", "description": "Do it", "dependencies": [], "estimatedDuration": 5000, "requiredCapabilities": ["coding"]}]}\n```';
|
|
173
|
+
|
|
174
|
+
const llm = mockLLM(llmResponse);
|
|
175
|
+
const decomposer = new SmartMicroPhaseDecomposer(llm);
|
|
176
|
+
const result = await decomposer.decompose({ ...simpleTask, complexityThreshold: 1 });
|
|
177
|
+
|
|
178
|
+
expect(result.phases).toHaveLength(1);
|
|
179
|
+
expect(result.phases[0].id).toBe('p1');
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('falls back to single phase on unparseable LLM output', async () => {
|
|
183
|
+
const llm = mockLLM('I cannot decompose this task because reasons.');
|
|
184
|
+
const decomposer = new SmartMicroPhaseDecomposer(llm);
|
|
185
|
+
const result = await decomposer.decompose(simpleTask);
|
|
186
|
+
|
|
187
|
+
expect(result.phases).toHaveLength(1);
|
|
188
|
+
expect(result.wasDecomposed).toBe(false);
|
|
189
|
+
expect(result.phases[0].id).toContain('task_1');
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it('falls back on missing phases array', async () => {
|
|
193
|
+
const llm = mockLLM(JSON.stringify({ steps: ['a', 'b'] }));
|
|
194
|
+
const decomposer = new SmartMicroPhaseDecomposer(llm);
|
|
195
|
+
const result = await decomposer.decompose(simpleTask);
|
|
196
|
+
|
|
197
|
+
expect(result.phases).toHaveLength(1);
|
|
198
|
+
expect(result.wasDecomposed).toBe(false);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it('does not mark as decomposed when below complexity threshold', async () => {
|
|
202
|
+
const llmResponse = JSON.stringify({
|
|
203
|
+
phases: [{ id: 'only', description: 'Just do it', dependencies: [], estimatedDuration: 3000, requiredCapabilities: ['coding'] }],
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
const llm = mockLLM(llmResponse);
|
|
207
|
+
const decomposer = new SmartMicroPhaseDecomposer(llm);
|
|
208
|
+
// Default threshold = 2, single phase returned
|
|
209
|
+
const result = await decomposer.decompose(simpleTask);
|
|
210
|
+
|
|
211
|
+
expect(result.wasDecomposed).toBe(false);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it('respects custom complexity threshold', async () => {
|
|
215
|
+
const llmResponse = JSON.stringify({
|
|
216
|
+
phases: [
|
|
217
|
+
{ id: 'a', description: 'Step A', dependencies: [], estimatedDuration: 3000, requiredCapabilities: ['coding'] },
|
|
218
|
+
{ id: 'b', description: 'Step B', dependencies: [], estimatedDuration: 3000, requiredCapabilities: ['coding'] },
|
|
219
|
+
],
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
const llm = mockLLM(llmResponse);
|
|
223
|
+
const decomposer = new SmartMicroPhaseDecomposer(llm);
|
|
224
|
+
const result = await decomposer.decompose({ ...simpleTask, complexityThreshold: 3 });
|
|
225
|
+
|
|
226
|
+
expect(result.wasDecomposed).toBe(false);
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
it('enforces maxParallelism by adding synthetic dependencies', async () => {
|
|
230
|
+
const llmResponse = JSON.stringify({
|
|
231
|
+
phases: [
|
|
232
|
+
{ id: 'a', description: 'A', dependencies: [], estimatedDuration: 3000, requiredCapabilities: ['coding'] },
|
|
233
|
+
{ id: 'b', description: 'B', dependencies: [], estimatedDuration: 3000, requiredCapabilities: ['coding'] },
|
|
234
|
+
{ id: 'c', description: 'C', dependencies: [], estimatedDuration: 3000, requiredCapabilities: ['coding'] },
|
|
235
|
+
{ id: 'd', description: 'D', dependencies: [], estimatedDuration: 3000, requiredCapabilities: ['coding'] },
|
|
236
|
+
],
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
const llm = mockLLM(llmResponse);
|
|
240
|
+
const decomposer = new SmartMicroPhaseDecomposer(llm);
|
|
241
|
+
const result = await decomposer.decompose({ ...simpleTask, maxParallelism: 2 });
|
|
242
|
+
|
|
243
|
+
// With maxParallelism=2, no wave should have more than 2 phases
|
|
244
|
+
for (const wave of result.plan.waves) {
|
|
245
|
+
expect(wave.length).toBeLessThanOrEqual(2);
|
|
246
|
+
}
|
|
247
|
+
expect(result.wasDecomposed).toBe(true);
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
it('sends correct prompt to LLM', async () => {
|
|
251
|
+
const llm = mockLLM(JSON.stringify({ phases: [] }));
|
|
252
|
+
const decomposer = new SmartMicroPhaseDecomposer(llm);
|
|
253
|
+
|
|
254
|
+
await decomposer.decompose({
|
|
255
|
+
...simpleTask,
|
|
256
|
+
requiredCapabilities: ['coding', 'testing'],
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
expect(llm.call).toHaveBeenCalledTimes(1);
|
|
260
|
+
const [messages, options] = (llm.call as ReturnType<typeof vi.fn>).mock.calls[0];
|
|
261
|
+
expect(messages).toHaveLength(2);
|
|
262
|
+
expect(messages[0].role).toBe('system');
|
|
263
|
+
expect(messages[1].content).toContain('Build login page');
|
|
264
|
+
expect(messages[1].content).toContain('coding, testing');
|
|
265
|
+
expect(options?.temperature).toBe(0.3);
|
|
266
|
+
});
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
describe('parallelization ratio', () => {
|
|
270
|
+
it('calculates correct ratio for mixed parallel/serial', () => {
|
|
271
|
+
const llm = mockLLM('');
|
|
272
|
+
const decomposer = new SmartMicroPhaseDecomposer(llm);
|
|
273
|
+
|
|
274
|
+
// a(5s) and b(5s) in parallel, c(5s) depends on both
|
|
275
|
+
const phases = [
|
|
276
|
+
phase('a', [], 5000),
|
|
277
|
+
phase('b', [], 5000),
|
|
278
|
+
phase('c', ['a', 'b'], 5000),
|
|
279
|
+
];
|
|
280
|
+
const plan = decomposer.buildExecutionPlan(phases);
|
|
281
|
+
|
|
282
|
+
// Sequential: 15000ms, Parallel: 5000 + 5000 = 10000ms
|
|
283
|
+
// Ratio: (15000-10000)/15000 = 33%
|
|
284
|
+
expect(plan.totalEstimatedDuration).toBe(10000);
|
|
285
|
+
expect(plan.parallelizationRatio).toBe(33);
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
it('gives 100% ratio when all phases run in parallel (impossible with int rounding but approaches)', () => {
|
|
289
|
+
const llm = mockLLM('');
|
|
290
|
+
const decomposer = new SmartMicroPhaseDecomposer(llm);
|
|
291
|
+
|
|
292
|
+
// All independent, same duration
|
|
293
|
+
const phases = [
|
|
294
|
+
phase('a', [], 1000),
|
|
295
|
+
phase('b', [], 1000),
|
|
296
|
+
phase('c', [], 1000),
|
|
297
|
+
phase('d', [], 1000),
|
|
298
|
+
];
|
|
299
|
+
const plan = decomposer.buildExecutionPlan(phases);
|
|
300
|
+
|
|
301
|
+
// Sequential: 4000, Parallel: 1000
|
|
302
|
+
expect(plan.totalEstimatedDuration).toBe(1000);
|
|
303
|
+
expect(plan.parallelizationRatio).toBe(75);
|
|
304
|
+
});
|
|
305
|
+
});
|
|
306
|
+
});
|