@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.
Files changed (329) hide show
  1. package/ALL-test-results.json +1 -0
  2. package/CHANGELOG.md +8 -0
  3. package/LICENSE +21 -0
  4. package/ROADMAP.md +175 -0
  5. package/dist/AgentManifest-CB4xM-Ma.d.cts +704 -0
  6. package/dist/AgentManifest-CB4xM-Ma.d.ts +704 -0
  7. package/dist/BehaviorTree-BrBFECv5.d.cts +103 -0
  8. package/dist/BehaviorTree-BrBFECv5.d.ts +103 -0
  9. package/dist/InvisibleWallet-BB6tFvRA.d.cts +1732 -0
  10. package/dist/InvisibleWallet-rtRrBOA8.d.ts +1732 -0
  11. package/dist/OrchestratorAgent-BvWgf9uw.d.cts +798 -0
  12. package/dist/OrchestratorAgent-Q_CbVTmO.d.ts +798 -0
  13. package/dist/agents/index.cjs +4790 -0
  14. package/dist/agents/index.d.cts +1788 -0
  15. package/dist/agents/index.d.ts +1788 -0
  16. package/dist/agents/index.js +4695 -0
  17. package/dist/ai/index.cjs +5347 -0
  18. package/dist/ai/index.d.cts +1753 -0
  19. package/dist/ai/index.d.ts +1753 -0
  20. package/dist/ai/index.js +5244 -0
  21. package/dist/behavior.cjs +449 -0
  22. package/dist/behavior.d.cts +130 -0
  23. package/dist/behavior.d.ts +130 -0
  24. package/dist/behavior.js +407 -0
  25. package/dist/economy/index.cjs +3659 -0
  26. package/dist/economy/index.d.cts +747 -0
  27. package/dist/economy/index.d.ts +747 -0
  28. package/dist/economy/index.js +3617 -0
  29. package/dist/implementations-D9T3un9D.d.cts +236 -0
  30. package/dist/implementations-D9T3un9D.d.ts +236 -0
  31. package/dist/index.cjs +24550 -0
  32. package/dist/index.d.cts +1729 -0
  33. package/dist/index.d.ts +1729 -0
  34. package/dist/index.js +24277 -0
  35. package/dist/learning/index.cjs +219 -0
  36. package/dist/learning/index.d.cts +104 -0
  37. package/dist/learning/index.d.ts +104 -0
  38. package/dist/learning/index.js +189 -0
  39. package/dist/negotiation/index.cjs +970 -0
  40. package/dist/negotiation/index.d.cts +610 -0
  41. package/dist/negotiation/index.d.ts +610 -0
  42. package/dist/negotiation/index.js +931 -0
  43. package/dist/skills/index.cjs +1118 -0
  44. package/dist/skills/index.d.cts +289 -0
  45. package/dist/skills/index.d.ts +289 -0
  46. package/dist/skills/index.js +1079 -0
  47. package/dist/swarm/index.cjs +5268 -0
  48. package/dist/swarm/index.d.cts +2433 -0
  49. package/dist/swarm/index.d.ts +2433 -0
  50. package/dist/swarm/index.js +5221 -0
  51. package/dist/training/index.cjs +2745 -0
  52. package/dist/training/index.d.cts +1734 -0
  53. package/dist/training/index.d.ts +1734 -0
  54. package/dist/training/index.js +2687 -0
  55. package/extract-failures.js +10 -0
  56. package/package.json +82 -0
  57. package/src/__tests__/bounty-marketplace.test.ts +374 -0
  58. package/src/__tests__/delegation.test.ts +144 -0
  59. package/src/__tests__/distributed-claimer.test.ts +147 -0
  60. package/src/__tests__/done-log-audit.test.ts +342 -0
  61. package/src/__tests__/framework.test.ts +865 -0
  62. package/src/__tests__/goal-synthesizer.test.ts +236 -0
  63. package/src/__tests__/presence.test.ts +223 -0
  64. package/src/__tests__/protocol-agent.test.ts +254 -0
  65. package/src/__tests__/revenue-splitter.test.ts +114 -0
  66. package/src/__tests__/scenario-driven-todo.test.ts +197 -0
  67. package/src/__tests__/self-improve.test.ts +349 -0
  68. package/src/__tests__/service-lifecycle.test.ts +237 -0
  69. package/src/__tests__/skill-router.test.ts +121 -0
  70. package/src/agents/AgentManifest.ts +493 -0
  71. package/src/agents/AgentRegistry.ts +475 -0
  72. package/src/agents/AgentTypes.ts +585 -0
  73. package/src/agents/AgentWalletRegistry.ts +83 -0
  74. package/src/agents/AuthenticatedCRDT.ts +388 -0
  75. package/src/agents/CapabilityMatcher.ts +453 -0
  76. package/src/agents/CrossRealityHandoff.ts +305 -0
  77. package/src/agents/CulturalMemory.ts +454 -0
  78. package/src/agents/FederatedRegistryAdapter.ts +429 -0
  79. package/src/agents/NormEngine.ts +450 -0
  80. package/src/agents/OrchestratorAgent.ts +414 -0
  81. package/src/agents/SkillWorkflowEngine.ts +472 -0
  82. package/src/agents/TaskDelegationService.ts +551 -0
  83. package/src/agents/__tests__/AgentManifest.prod.test.ts +134 -0
  84. package/src/agents/__tests__/AgentManifest.test.ts +182 -0
  85. package/src/agents/__tests__/AgentModule.test.ts +864 -0
  86. package/src/agents/__tests__/AgentRegistry.prod.test.ts +125 -0
  87. package/src/agents/__tests__/AgentRegistry.test.ts +148 -0
  88. package/src/agents/__tests__/AgentTypes.test.ts +534 -0
  89. package/src/agents/__tests__/AgentWalletRegistry.test.ts +152 -0
  90. package/src/agents/__tests__/AuthenticatedCRDT.test.ts +558 -0
  91. package/src/agents/__tests__/CapabilityMatcher.prod.test.ts +117 -0
  92. package/src/agents/__tests__/CapabilityMatcher.test.ts +178 -0
  93. package/src/agents/__tests__/CrossRealityHandoff.test.ts +402 -0
  94. package/src/agents/__tests__/CulturalMemory.test.ts +200 -0
  95. package/src/agents/__tests__/FederatedRegistryAdapter.test.ts +409 -0
  96. package/src/agents/__tests__/NormEngine.test.ts +276 -0
  97. package/src/agents/__tests__/OrchestratorAgent.test.ts +182 -0
  98. package/src/agents/__tests__/SkillWorkflowEngine.test.ts +357 -0
  99. package/src/agents/__tests__/TaskDelegationService.test.ts +446 -0
  100. package/src/agents/index.ts +107 -0
  101. package/src/agents/spatial-comms/Layer1RealTime.ts +621 -0
  102. package/src/agents/spatial-comms/Layer2A2A.ts +661 -0
  103. package/src/agents/spatial-comms/Layer3MCP.ts +651 -0
  104. package/src/agents/spatial-comms/ProtocolTypes.ts +543 -0
  105. package/src/agents/spatial-comms/SpatialCommClient.ts +483 -0
  106. package/src/agents/spatial-comms/__tests__/performance-benchmark.test.ts +465 -0
  107. package/src/agents/spatial-comms/examples/multi-agent-world-creation.ts +409 -0
  108. package/src/agents/spatial-comms/index.ts +66 -0
  109. package/src/ai/AIAdapter.ts +313 -0
  110. package/src/ai/AICopilot.ts +331 -0
  111. package/src/ai/AIOutputValidator.ts +203 -0
  112. package/src/ai/BTNodes.ts +239 -0
  113. package/src/ai/BehaviorSelector.ts +135 -0
  114. package/src/ai/BehaviorTree.ts +153 -0
  115. package/src/ai/Blackboard.ts +165 -0
  116. package/src/ai/GenerationAnalytics.ts +461 -0
  117. package/src/ai/GenerationCache.ts +265 -0
  118. package/src/ai/GoalPlanner.ts +165 -0
  119. package/src/ai/HoloScriptGenerator.ts +580 -0
  120. package/src/ai/InfluenceMap.ts +180 -0
  121. package/src/ai/NavMesh.ts +168 -0
  122. package/src/ai/PerceptionSystem.ts +178 -0
  123. package/src/ai/PromptTemplates.ts +453 -0
  124. package/src/ai/SemanticSearchService.ts +80 -0
  125. package/src/ai/StateMachine.ts +196 -0
  126. package/src/ai/SteeringBehavior.ts +150 -0
  127. package/src/ai/SteeringBehaviors.ts +244 -0
  128. package/src/ai/TrainingDataGenerator.ts +1082 -0
  129. package/src/ai/UtilityAI.ts +145 -0
  130. package/src/ai/__tests__/AIAdapter.prod.test.ts +259 -0
  131. package/src/ai/__tests__/AIAdapter.test.ts +109 -0
  132. package/src/ai/__tests__/AICopilot.prod.test.ts +341 -0
  133. package/src/ai/__tests__/AICopilot.test.ts +178 -0
  134. package/src/ai/__tests__/AIOutputValidator.prod.test.ts +226 -0
  135. package/src/ai/__tests__/AIOutputValidator.test.ts +138 -0
  136. package/src/ai/__tests__/BTNodes.prod.test.ts +391 -0
  137. package/src/ai/__tests__/BTNodes.test.ts +263 -0
  138. package/src/ai/__tests__/BehaviorSelector.prod.test.ts +129 -0
  139. package/src/ai/__tests__/BehaviorSelector.test.ts +132 -0
  140. package/src/ai/__tests__/BehaviorTree.prod.test.ts +266 -0
  141. package/src/ai/__tests__/BehaviorTree.test.ts +216 -0
  142. package/src/ai/__tests__/Blackboard.prod.test.ts +339 -0
  143. package/src/ai/__tests__/Blackboard.test.ts +183 -0
  144. package/src/ai/__tests__/GenerationAnalytics.prod.test.ts +141 -0
  145. package/src/ai/__tests__/GenerationAnalytics.test.ts +165 -0
  146. package/src/ai/__tests__/GenerationCache.prod.test.ts +144 -0
  147. package/src/ai/__tests__/GenerationCache.test.ts +171 -0
  148. package/src/ai/__tests__/GoalPlanner.prod.test.ts +189 -0
  149. package/src/ai/__tests__/GoalPlanner.test.ts +137 -0
  150. package/src/ai/__tests__/GoalPlannerDepth.prod.test.ts +217 -0
  151. package/src/ai/__tests__/HoloScriptGenerator.test.ts +125 -0
  152. package/src/ai/__tests__/InfluenceMap.prod.test.ts +146 -0
  153. package/src/ai/__tests__/InfluenceMap.test.ts +149 -0
  154. package/src/ai/__tests__/NavMesh.prod.test.ts +141 -0
  155. package/src/ai/__tests__/NavMesh.test.ts +159 -0
  156. package/src/ai/__tests__/PerceptionSystem.prod.test.ts +135 -0
  157. package/src/ai/__tests__/PerceptionSystem.test.ts +250 -0
  158. package/src/ai/__tests__/PromptTemplates.prod.test.ts +313 -0
  159. package/src/ai/__tests__/PromptTemplates.test.ts +146 -0
  160. package/src/ai/__tests__/SemanticSearch.test.ts +37 -0
  161. package/src/ai/__tests__/StateMachine.prod.test.ts +162 -0
  162. package/src/ai/__tests__/StateMachine.test.ts +163 -0
  163. package/src/ai/__tests__/SteeringBehavior.prod.test.ts +251 -0
  164. package/src/ai/__tests__/SteeringBehavior.test.ts +135 -0
  165. package/src/ai/__tests__/SteeringBehaviors.prod.test.ts +133 -0
  166. package/src/ai/__tests__/SteeringBehaviors.test.ts +151 -0
  167. package/src/ai/__tests__/TrainingDataGenerator.prod.test.ts +286 -0
  168. package/src/ai/__tests__/TrainingDataGenerator.test.ts +286 -0
  169. package/src/ai/__tests__/UtilityAI.prod.test.ts +207 -0
  170. package/src/ai/__tests__/UtilityAI.test.ts +155 -0
  171. package/src/ai/__tests__/adapters.prod.test.ts +263 -0
  172. package/src/ai/__tests__/adapters.test.ts +320 -0
  173. package/src/ai/adapters.ts +1585 -0
  174. package/src/ai/index.ts +130 -0
  175. package/src/behavior/BehaviorPresets.ts +140 -0
  176. package/src/behavior/BehaviorTree.ts +236 -0
  177. package/src/behavior/StateMachine.ts +176 -0
  178. package/src/behavior/StateTrait.ts +67 -0
  179. package/src/behavior/index.ts +8 -0
  180. package/src/behavior.ts +8 -0
  181. package/src/board/audit.ts +284 -0
  182. package/src/board/board-ops.ts +336 -0
  183. package/src/board/board-types.ts +302 -0
  184. package/src/board/index.ts +69 -0
  185. package/src/define-agent.ts +46 -0
  186. package/src/define-team.ts +33 -0
  187. package/src/delegation.ts +265 -0
  188. package/src/distributed-claimer.ts +228 -0
  189. package/src/economy/AgentBudgetEnforcer.ts +464 -0
  190. package/src/economy/BountyManager.ts +185 -0
  191. package/src/economy/CreatorRevenueAggregator.ts +460 -0
  192. package/src/economy/InvisibleWallet.ts +82 -0
  193. package/src/economy/KnowledgeMarketplace.ts +193 -0
  194. package/src/economy/PaymentWebhookService.ts +512 -0
  195. package/src/economy/RevenueSplitter.ts +156 -0
  196. package/src/economy/SubscriptionManager.ts +546 -0
  197. package/src/economy/UnifiedBudgetOptimizer.ts +635 -0
  198. package/src/economy/UsageMeter.ts +440 -0
  199. package/src/economy/_core-stubs.ts +219 -0
  200. package/src/economy/index.ts +100 -0
  201. package/src/economy/x402-facilitator.ts +1978 -0
  202. package/src/index.ts +348 -0
  203. package/src/knowledge/__tests__/knowledge-consolidator.test.ts +444 -0
  204. package/src/knowledge/__tests__/knowledge-store-vector.test.ts +291 -0
  205. package/src/knowledge/brain.ts +167 -0
  206. package/src/knowledge/consolidation.ts +581 -0
  207. package/src/knowledge/knowledge-consolidator.ts +510 -0
  208. package/src/knowledge/knowledge-store.ts +616 -0
  209. package/src/learning/MemoryConsolidator.ts +102 -0
  210. package/src/learning/MemoryScorer.ts +69 -0
  211. package/src/learning/ProceduralCompiler.ts +45 -0
  212. package/src/learning/SemanticClusterer.ts +66 -0
  213. package/src/learning/index.ts +8 -0
  214. package/src/llm/llm-adapter.ts +159 -0
  215. package/src/mesh/index.ts +309 -0
  216. package/src/negotiation/NegotiationProtocol.ts +694 -0
  217. package/src/negotiation/NegotiationTypes.ts +473 -0
  218. package/src/negotiation/VotingMechanisms.ts +691 -0
  219. package/src/negotiation/index.ts +49 -0
  220. package/src/protocol/goal-synthesizer.ts +317 -0
  221. package/src/protocol/implementations.ts +474 -0
  222. package/src/protocol/micro-phase-decomposer.ts +299 -0
  223. package/src/protocol/micro-step-decomposer.test.ts +306 -0
  224. package/src/protocol-agent.test.ts +353 -0
  225. package/src/protocol-agent.ts +670 -0
  226. package/src/self-improve/absorb-scanner.ts +252 -0
  227. package/src/self-improve/evolution-engine.ts +149 -0
  228. package/src/self-improve/framework-absorber.ts +214 -0
  229. package/src/self-improve/index.ts +50 -0
  230. package/src/self-improve/prompt-optimizer.ts +212 -0
  231. package/src/self-improve/test-generator.ts +175 -0
  232. package/src/skill-router.ts +186 -0
  233. package/src/skills/index.ts +5 -0
  234. package/src/skills/skill-md-bridge.ts +1699 -0
  235. package/src/swarm/ACOEngine.ts +261 -0
  236. package/src/swarm/CollectiveIntelligence.ts +383 -0
  237. package/src/swarm/ContributionSynthesizer.ts +481 -0
  238. package/src/swarm/LeaderElection.ts +393 -0
  239. package/src/swarm/PSOEngine.ts +206 -0
  240. package/src/swarm/QuorumPolicy.ts +173 -0
  241. package/src/swarm/SwarmCoordinator.ts +335 -0
  242. package/src/swarm/SwarmManager.ts +442 -0
  243. package/src/swarm/SwarmMembership.ts +456 -0
  244. package/src/swarm/VotingRound.ts +255 -0
  245. package/src/swarm/__tests__/ACOEngine.prod.test.ts +164 -0
  246. package/src/swarm/__tests__/ACOEngine.test.ts +117 -0
  247. package/src/swarm/__tests__/CollectiveIntelligence.prod.test.ts +296 -0
  248. package/src/swarm/__tests__/CollectiveIntelligence.test.ts +457 -0
  249. package/src/swarm/__tests__/ContributionSynthesizer.prod.test.ts +269 -0
  250. package/src/swarm/__tests__/ContributionSynthesizer.test.ts +254 -0
  251. package/src/swarm/__tests__/LeaderElection.prod.test.ts +196 -0
  252. package/src/swarm/__tests__/LeaderElection.test.ts +151 -0
  253. package/src/swarm/__tests__/PSOEngine.prod.test.ts +162 -0
  254. package/src/swarm/__tests__/PSOEngine.test.ts +106 -0
  255. package/src/swarm/__tests__/QuorumPolicy.prod.test.ts +216 -0
  256. package/src/swarm/__tests__/QuorumPolicy.test.ts +177 -0
  257. package/src/swarm/__tests__/SwarmCoordinator.prod.test.ts +186 -0
  258. package/src/swarm/__tests__/SwarmCoordinator.test.ts +167 -0
  259. package/src/swarm/__tests__/SwarmManager.prod.test.ts +308 -0
  260. package/src/swarm/__tests__/SwarmManager.test.ts +373 -0
  261. package/src/swarm/__tests__/SwarmMembership.prod.test.ts +273 -0
  262. package/src/swarm/__tests__/SwarmMembership.test.ts +264 -0
  263. package/src/swarm/__tests__/VotingRound.prod.test.ts +233 -0
  264. package/src/swarm/__tests__/VotingRound.test.ts +174 -0
  265. package/src/swarm/analytics/SwarmInspector.ts +476 -0
  266. package/src/swarm/analytics/SwarmMetrics.ts +449 -0
  267. package/src/swarm/analytics/__tests__/SwarmInspector.prod.test.ts +366 -0
  268. package/src/swarm/analytics/__tests__/SwarmInspector.test.ts +454 -0
  269. package/src/swarm/analytics/__tests__/SwarmMetrics.prod.test.ts +254 -0
  270. package/src/swarm/analytics/__tests__/SwarmMetrics.test.ts +370 -0
  271. package/src/swarm/analytics/index.ts +7 -0
  272. package/src/swarm/index.ts +69 -0
  273. package/src/swarm/messaging/BroadcastChannel.ts +509 -0
  274. package/src/swarm/messaging/GossipProtocol.ts +565 -0
  275. package/src/swarm/messaging/SwarmEventBus.ts +443 -0
  276. package/src/swarm/messaging/__tests__/BroadcastChannel.prod.test.ts +331 -0
  277. package/src/swarm/messaging/__tests__/BroadcastChannel.test.ts +333 -0
  278. package/src/swarm/messaging/__tests__/GossipProtocol.prod.test.ts +356 -0
  279. package/src/swarm/messaging/__tests__/GossipProtocol.test.ts +437 -0
  280. package/src/swarm/messaging/__tests__/SwarmEventBus.prod.test.ts +191 -0
  281. package/src/swarm/messaging/__tests__/SwarmEventBus.test.ts +247 -0
  282. package/src/swarm/messaging/index.ts +8 -0
  283. package/src/swarm/spatial/FlockingBehavior.ts +462 -0
  284. package/src/swarm/spatial/FormationController.ts +500 -0
  285. package/src/swarm/spatial/Vector3.ts +170 -0
  286. package/src/swarm/spatial/ZoneClaiming.ts +509 -0
  287. package/src/swarm/spatial/__tests__/FlockingBehavior.prod.test.ts +239 -0
  288. package/src/swarm/spatial/__tests__/FlockingBehavior.test.ts +298 -0
  289. package/src/swarm/spatial/__tests__/FormationController.prod.test.ts +240 -0
  290. package/src/swarm/spatial/__tests__/FormationController.test.ts +297 -0
  291. package/src/swarm/spatial/__tests__/Vector3.prod.test.ts +283 -0
  292. package/src/swarm/spatial/__tests__/Vector3.test.ts +224 -0
  293. package/src/swarm/spatial/__tests__/ZoneClaiming.prod.test.ts +246 -0
  294. package/src/swarm/spatial/__tests__/ZoneClaiming.test.ts +374 -0
  295. package/src/swarm/spatial/index.ts +28 -0
  296. package/src/team.ts +1245 -0
  297. package/src/training/LRScheduler.ts +377 -0
  298. package/src/training/QualityScoringPipeline.ts +139 -0
  299. package/src/training/SoftDedup.ts +461 -0
  300. package/src/training/SparsityMonitor.ts +685 -0
  301. package/src/training/SparsityMonitorTypes.ts +209 -0
  302. package/src/training/SpatialTrainingDataGenerator.ts +1526 -0
  303. package/src/training/SpatialTrainingDataTypes.ts +216 -0
  304. package/src/training/TrainingPipelineConfig.ts +215 -0
  305. package/src/training/constants.ts +94 -0
  306. package/src/training/index.ts +138 -0
  307. package/src/training/schema.ts +147 -0
  308. package/src/training/scripts/generate-novel-use-cases-dataset.ts +272 -0
  309. package/src/training/scripts/generate-spatial-dataset.ts +521 -0
  310. package/src/training/training/data/novel-use-cases.jsonl +153 -0
  311. package/src/training/training/data/spatial-reasoning-10k.jsonl +9354 -0
  312. package/src/training/trainingmonkey/TrainingMonkeyIntegration.ts +477 -0
  313. package/src/training/trainingmonkey/TrainingMonkeyTypes.ts +230 -0
  314. package/src/training/trainingmonkey/index.ts +26 -0
  315. package/src/training/trait-mappings.ts +157 -0
  316. package/src/types/core-stubs.d.ts +113 -0
  317. package/src/types.ts +304 -0
  318. package/test-output.txt +0 -0
  319. package/test-result.json +1 -0
  320. package/tsc-errors.txt +4 -0
  321. package/tsc_output.txt +0 -0
  322. package/tsconfig.json +14 -0
  323. package/tsup-learning-esm.config.ts +12 -0
  324. package/tsup.config.ts +21 -0
  325. package/typescript-errors-2.txt +0 -0
  326. package/typescript-errors.txt +22 -0
  327. package/vitest-log-utf8.txt +268 -0
  328. package/vitest-log.txt +0 -0
  329. package/vitest.config.ts +8 -0
@@ -0,0 +1,67 @@
1
+ /**
2
+ * StateTrait.ts
3
+ *
4
+ * Declarative FSM attachment to HoloScript+ nodes.
5
+ * Allows nodes to define states and transitions as trait config.
6
+ *
7
+ * @trait state
8
+ */
9
+
10
+ import type { TraitHandler, HSPlusNode } from '@holoscript/core';
11
+ import { StateMachine, StateMachineConfig } from './StateMachine';
12
+
13
+ // =============================================================================
14
+ // CONFIG
15
+ // =============================================================================
16
+
17
+ export interface StateTraitConfig {
18
+ machine: StateMachineConfig;
19
+ }
20
+
21
+ // Extended properties interface for state-aware nodes
22
+ interface StateAwareProperties {
23
+ _state?: string;
24
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- extensible properties bag
25
+ [key: string]: any;
26
+ }
27
+
28
+ // Per-node state machines
29
+ const nodeStateMachines = new Map<string, StateMachine>();
30
+
31
+ export const stateTraitHandler: TraitHandler<StateTraitConfig> = {
32
+ name: 'state' as const,
33
+ defaultConfig: { machine: { initialState: 'idle', states: [], transitions: [] } },
34
+
35
+ onAttach(node: HSPlusNode, config: StateTraitConfig, _context: unknown) {
36
+ const nodeId = node.id!;
37
+ const sm = new StateMachine(config.machine);
38
+ nodeStateMachines.set(nodeId, sm);
39
+
40
+ // Surface state on node properties
41
+ if (node.properties) {
42
+ (node.properties as StateAwareProperties)._state = sm.getCurrentState();
43
+ }
44
+ },
45
+
46
+ onDetach(node: HSPlusNode, _config: StateTraitConfig, _context: unknown) {
47
+ nodeStateMachines.delete(node.id!);
48
+ },
49
+
50
+ onUpdate(node: HSPlusNode, _config: StateTraitConfig, _context: unknown, delta: number) {
51
+ const sm = nodeStateMachines.get(node.id!);
52
+ if (!sm) return;
53
+
54
+ sm.update(delta);
55
+
56
+ if (node.properties) {
57
+ (node.properties as StateAwareProperties)._state = sm.getCurrentState();
58
+ }
59
+ },
60
+ };
61
+
62
+ /**
63
+ * Get the StateMachine for a given node (for external event sending).
64
+ */
65
+ export function getNodeStateMachine(nodeId: string): StateMachine | undefined {
66
+ return nodeStateMachines.get(nodeId);
67
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Behavior module barrel
3
+ * Re-exports all behavior functionality from @holoscript/framework
4
+ */
5
+ export * from './BehaviorPresets';
6
+ export * from './BehaviorTree';
7
+ export * from './StateMachine';
8
+ export * from './StateTrait';
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Behavior Tree — Lightweight node builders for agent decision-making.
3
+ * Re-exported from @holoscript/framework/behavior barrel (A.011.02e migration)
4
+ *
5
+ * Import from '@holoscript/framework/bt' or '@holoscript/framework/behavior'
6
+ */
7
+
8
+ export * from './behavior/index';
@@ -0,0 +1,284 @@
1
+ /**
2
+ * Done-Log Audit Logic (FW-0.3)
3
+ *
4
+ * Absorbed from mcp-server/src/holomesh/http-routes.ts.
5
+ * Verifies commit proof, detects duplicates, checks monotonic timestamps,
6
+ * and provides per-agent/per-source statistics.
7
+ *
8
+ * Two APIs:
9
+ * - `auditDoneLog(entries)` — the original function (unchanged signature)
10
+ * - `DoneLogAuditor` class — richer audit with stats, violations, and Team integration
11
+ */
12
+
13
+ import type { DoneLogEntry } from './board-types';
14
+
15
+ // ── Types ──
16
+
17
+ export interface AuditResult {
18
+ total: number;
19
+ proofRequiredTotal: number;
20
+ nonProofEntries: number;
21
+ verified: number;
22
+ unverified: number;
23
+ duplicates: number;
24
+ unverifiedTasks: Array<{
25
+ taskId: string;
26
+ title: string;
27
+ completedBy: string;
28
+ summary: string;
29
+ timestamp: string;
30
+ }>;
31
+ duplicateTasks: Array<{ title: string; count: number }>;
32
+ health: {
33
+ verificationRate: number;
34
+ message: string;
35
+ };
36
+ }
37
+
38
+ /** A single violation found by the auditor. */
39
+ export interface AuditViolation {
40
+ /** Which check produced this violation. */
41
+ rule: 'missing-completedBy' | 'missing-summary' | 'missing-commit' | 'duplicate-entry' | 'non-monotonic-timestamp';
42
+ /** Human-readable description. */
43
+ message: string;
44
+ /** The entry that triggered the violation. */
45
+ entry: DoneLogEntry;
46
+ }
47
+
48
+ /** Per-agent completion statistics. */
49
+ export interface AgentStats {
50
+ agent: string;
51
+ completed: number;
52
+ verified: number;
53
+ unverified: number;
54
+ }
55
+
56
+ /** Per-source completion statistics. */
57
+ export interface SourceStats {
58
+ source: string;
59
+ completed: number;
60
+ }
61
+
62
+ /** Completion rate bucket for a time period. */
63
+ export interface CompletionBucket {
64
+ /** ISO date string (YYYY-MM-DD). */
65
+ date: string;
66
+ completed: number;
67
+ }
68
+
69
+ /** Full statistics from DoneLogAuditor.stats(). */
70
+ export interface DoneLogStats {
71
+ total: number;
72
+ byAgent: AgentStats[];
73
+ bySource: SourceStats[];
74
+ completionOverTime: CompletionBucket[];
75
+ }
76
+
77
+ /** Combined audit + stats result from DoneLogAuditor. */
78
+ export interface FullAuditResult {
79
+ /** Original AuditResult (backward compatible). */
80
+ audit: AuditResult;
81
+ /** Structural violations (missing fields, duplicates, non-monotonic). */
82
+ violations: AuditViolation[];
83
+ /** Statistics breakdown. */
84
+ stats: DoneLogStats;
85
+ }
86
+
87
+ // ── Helper Functions ──
88
+
89
+ /** Check if a done-log entry is a report/session summary (not real task work). */
90
+ export function isLikelyReportEntry(entry: { title?: string; summary?: string }): boolean {
91
+ const title = (entry.title || '').toLowerCase();
92
+ const summary = (entry.summary || '').toLowerCase();
93
+ return title.startsWith('[report]') || summary.startsWith('session end');
94
+ }
95
+
96
+ /** Check if a commit hash looks like real git proof. */
97
+ export function isCommitProof(commitHash?: string): boolean {
98
+ if (!commitHash) return false;
99
+ const hash = commitHash.trim();
100
+ if (!hash) return false;
101
+ if (['uncommit', 'local-uncommitted', 'local_uncommitted', 'none', 'n/a', 'na'].includes(hash.toLowerCase())) {
102
+ return false;
103
+ }
104
+ return /^[0-9a-f]{7,40}$/i.test(hash);
105
+ }
106
+
107
+ /** Audit a done log: verify commits, find duplicates, calculate health. */
108
+ export function auditDoneLog(doneLog: DoneLogEntry[]): AuditResult {
109
+ const proofRequired = doneLog.filter((e) => !isLikelyReportEntry(e));
110
+ const nonProofEntries = doneLog.filter((e) => isLikelyReportEntry(e));
111
+
112
+ const verified = proofRequired.filter((e) => isCommitProof(e.commitHash));
113
+ const unverified = proofRequired.filter((e) => !isCommitProof(e.commitHash));
114
+
115
+ const duplicateMap = new Map<string, number>();
116
+ for (const e of doneLog) {
117
+ duplicateMap.set(e.title, (duplicateMap.get(e.title) || 0) + 1);
118
+ }
119
+ const duped = [...duplicateMap.entries()]
120
+ .filter(([, count]) => count > 1)
121
+ .map(([title, count]) => ({ title, count }));
122
+
123
+ const denominator = proofRequired.length;
124
+ const verificationRate = denominator > 0 ? Math.round((verified.length / denominator) * 100) : 100;
125
+
126
+ return {
127
+ total: doneLog.length,
128
+ proofRequiredTotal: proofRequired.length,
129
+ nonProofEntries: nonProofEntries.length,
130
+ verified: verified.length,
131
+ unverified: unverified.length,
132
+ duplicates: duped.length,
133
+ unverifiedTasks: unverified.map((e) => ({
134
+ taskId: e.taskId,
135
+ title: e.title,
136
+ completedBy: e.completedBy,
137
+ summary: e.summary,
138
+ timestamp: e.timestamp,
139
+ })),
140
+ duplicateTasks: duped,
141
+ health: {
142
+ verificationRate,
143
+ message: unverified.length === 0
144
+ ? 'All tasks have commit proof.'
145
+ : `${unverified.length} tasks need verification — missing or invalid commit proof.`,
146
+ },
147
+ };
148
+ }
149
+
150
+ // ── DoneLogAuditor Class ──
151
+
152
+ /**
153
+ * Stateless auditor that inspects a done-log for structural violations
154
+ * and computes aggregate statistics. Designed for Team.audit() integration.
155
+ */
156
+ export class DoneLogAuditor {
157
+ constructor(private readonly entries: DoneLogEntry[]) {}
158
+
159
+ /**
160
+ * Run all audit checks and return violations.
161
+ *
162
+ * Checks:
163
+ * 1. Every done task has completedBy, summary, and commit proof
164
+ * 2. No duplicate entries (by taskId)
165
+ * 3. Timestamps are monotonically increasing
166
+ */
167
+ audit(): AuditViolation[] {
168
+ const violations: AuditViolation[] = [];
169
+
170
+ const seenIds = new Map<string, DoneLogEntry>();
171
+
172
+ let prevTimestamp = '';
173
+
174
+ for (const entry of this.entries) {
175
+ // Skip report entries — they don't need the same rigor
176
+ if (isLikelyReportEntry(entry)) continue;
177
+
178
+ // Check required fields
179
+ if (!entry.completedBy) {
180
+ violations.push({
181
+ rule: 'missing-completedBy',
182
+ message: `Task "${entry.title}" (${entry.taskId}) has no completedBy field.`,
183
+ entry,
184
+ });
185
+ }
186
+
187
+ if (!entry.summary) {
188
+ violations.push({
189
+ rule: 'missing-summary',
190
+ message: `Task "${entry.title}" (${entry.taskId}) has no summary.`,
191
+ entry,
192
+ });
193
+ }
194
+
195
+ if (!isCommitProof(entry.commitHash)) {
196
+ violations.push({
197
+ rule: 'missing-commit',
198
+ message: `Task "${entry.title}" (${entry.taskId}) has no valid commit proof.`,
199
+ entry,
200
+ });
201
+ }
202
+
203
+ // Duplicate check by taskId
204
+ if (seenIds.has(entry.taskId)) {
205
+ violations.push({
206
+ rule: 'duplicate-entry',
207
+ message: `Task "${entry.title}" (${entry.taskId}) appears more than once in the done log.`,
208
+ entry,
209
+ });
210
+ } else {
211
+ seenIds.set(entry.taskId, entry);
212
+ }
213
+
214
+ // Monotonic timestamp check
215
+ if (prevTimestamp && entry.timestamp < prevTimestamp) {
216
+ violations.push({
217
+ rule: 'non-monotonic-timestamp',
218
+ message: `Task "${entry.title}" (${entry.taskId}) timestamp ${entry.timestamp} is before previous ${prevTimestamp}.`,
219
+ entry,
220
+ });
221
+ }
222
+ prevTimestamp = entry.timestamp;
223
+ }
224
+
225
+ return violations;
226
+ }
227
+
228
+ /** Get aggregate statistics over the done log. */
229
+ stats(): DoneLogStats {
230
+ // By agent
231
+ const agentMap = new Map<string, { completed: number; verified: number; unverified: number }>();
232
+ for (const entry of this.entries) {
233
+ const agent = entry.completedBy || 'unknown';
234
+ const existing = agentMap.get(agent) ?? { completed: 0, verified: 0, unverified: 0 };
235
+ existing.completed++;
236
+ if (isCommitProof(entry.commitHash)) {
237
+ existing.verified++;
238
+ } else {
239
+ existing.unverified++;
240
+ }
241
+ agentMap.set(agent, existing);
242
+ }
243
+
244
+ // By source — extract source prefix from taskId (e.g., "task_synth_" → "synthesizer")
245
+ const sourceMap = new Map<string, number>();
246
+ for (const entry of this.entries) {
247
+ // Best-effort source detection from summary or taskId pattern
248
+ let source = 'manual';
249
+ if (entry.taskId.includes('synth')) source = 'synthesizer';
250
+ else if (entry.summary?.includes('scout')) source = 'scout';
251
+ else if (entry.summary?.includes('derive')) source = 'derive';
252
+ sourceMap.set(source, (sourceMap.get(source) ?? 0) + 1);
253
+ }
254
+
255
+ // Completion over time (by date)
256
+ const dateMap = new Map<string, number>();
257
+ for (const entry of this.entries) {
258
+ const date = entry.timestamp.slice(0, 10); // YYYY-MM-DD
259
+ dateMap.set(date, (dateMap.get(date) ?? 0) + 1);
260
+ }
261
+
262
+ return {
263
+ total: this.entries.length,
264
+ byAgent: Array.from(agentMap.entries())
265
+ .map(([agent, s]) => ({ agent, ...s }))
266
+ .sort((a, b) => b.completed - a.completed),
267
+ bySource: Array.from(sourceMap.entries())
268
+ .map(([source, completed]) => ({ source, completed }))
269
+ .sort((a, b) => b.completed - a.completed),
270
+ completionOverTime: Array.from(dateMap.entries())
271
+ .map(([date, completed]) => ({ date, completed }))
272
+ .sort((a, b) => a.date.localeCompare(b.date)),
273
+ };
274
+ }
275
+
276
+ /** Run full audit: original AuditResult + violations + stats. */
277
+ fullAudit(): FullAuditResult {
278
+ return {
279
+ audit: auditDoneLog(this.entries),
280
+ violations: this.audit(),
281
+ stats: this.stats(),
282
+ };
283
+ }
284
+ }
@@ -0,0 +1,336 @@
1
+ /**
2
+ * Board Operations — Pure state machine logic for task and suggestion lifecycle.
3
+ *
4
+ * Absorbed from mcp-server/src/holomesh/http-routes.ts.
5
+ * These are pure functions that mutate board/suggestion arrays.
6
+ * The HTTP layer calls these and handles persistence + responses.
7
+ */
8
+
9
+ import type { TeamTask, TaskAction, DoneLogEntry, TeamSuggestion, SuggestionCategory, SlotRole } from './board-types';
10
+ import { normalizeTitle, generateTaskId, generateSuggestionId } from './board-types';
11
+
12
+ // ── Task Operations ──
13
+
14
+ export interface ClaimResult {
15
+ success: boolean;
16
+ error?: string;
17
+ task?: TeamTask;
18
+ }
19
+
20
+ export interface DoneResult {
21
+ success: boolean;
22
+ error?: string;
23
+ task?: TeamTask;
24
+ doneEntry?: DoneLogEntry;
25
+ }
26
+
27
+ export interface TaskActionResult {
28
+ success: boolean;
29
+ error?: string;
30
+ task?: TeamTask;
31
+ doneEntry?: DoneLogEntry;
32
+ }
33
+
34
+ /** Claim an open task. Returns error if task isn't open or has unmet dependencies. */
35
+ export function claimTask(
36
+ board: TeamTask[],
37
+ taskId: string,
38
+ claimerId: string,
39
+ claimerName: string
40
+ ): TaskActionResult {
41
+ const task = board.find((t) => t.id === taskId);
42
+ if (!task) return { success: false, error: 'Task not found' };
43
+ if (task.status !== 'open') return { success: false, error: `Task is ${task.status}, not open` };
44
+
45
+ // Check dependencies — all must be done (not on the board)
46
+ if (task.dependsOn && task.dependsOn.length > 0) {
47
+ const pending = task.dependsOn.filter((depId) => board.some((t) => t.id === depId && t.status !== 'done'));
48
+ if (pending.length > 0) {
49
+ return { success: false, error: `Blocked by ${pending.length} unfinished dependencies: ${pending.join(', ')}` };
50
+ }
51
+ }
52
+
53
+ task.status = 'claimed';
54
+ task.claimedBy = claimerId;
55
+ task.claimedByName = claimerName;
56
+ return { success: true, task };
57
+ }
58
+
59
+ /** Mark a task as done. Removes from board, returns done log entry. */
60
+ export function completeTask(
61
+ board: TeamTask[],
62
+ taskId: string,
63
+ completedBy: string,
64
+ opts: { commit?: string; summary?: string } = {}
65
+ ): { result: TaskActionResult & { onComplete?: TaskAction[]; unblocked?: string[] }; updatedBoard: TeamTask[] } {
66
+ const task = board.find((t) => t.id === taskId);
67
+ if (!task) return { result: { success: false, error: 'Task not found' }, updatedBoard: board };
68
+
69
+ task.status = 'done';
70
+ task.completedBy = completedBy;
71
+ task.commitHash = opts.commit;
72
+ task.completedAt = new Date().toISOString();
73
+
74
+ const doneEntry: DoneLogEntry = {
75
+ taskId: task.id,
76
+ title: task.title,
77
+ completedBy,
78
+ commitHash: task.commitHash,
79
+ timestamp: task.completedAt,
80
+ summary: opts.summary || task.title,
81
+ };
82
+
83
+ // Unblock dependent tasks — move from 'blocked' to 'open' if all their deps are done
84
+ const unblocked: string[] = [];
85
+ if (task.unblocks) {
86
+ for (const depId of task.unblocks) {
87
+ const dep = board.find((t) => t.id === depId);
88
+ if (!dep) continue;
89
+ // Check if ALL of dep's dependencies are now done (off the board or status=done)
90
+ const allDepsMet = !dep.dependsOn || dep.dependsOn.every(
91
+ (id) => id === taskId || !board.some((t) => t.id === id && t.status !== 'done')
92
+ );
93
+ if (allDepsMet && dep.status === 'blocked') {
94
+ dep.status = 'open';
95
+ unblocked.push(depId);
96
+ }
97
+ }
98
+ }
99
+
100
+ const updatedBoard = board.filter((t) => t.id !== taskId);
101
+ return {
102
+ result: {
103
+ success: true,
104
+ task,
105
+ doneEntry,
106
+ onComplete: task.onComplete,
107
+ unblocked: unblocked.length > 0 ? unblocked : undefined,
108
+ },
109
+ updatedBoard,
110
+ };
111
+ }
112
+
113
+ /** Block a task. */
114
+ export function blockTask(board: TeamTask[], taskId: string): TaskActionResult {
115
+ const task = board.find((t) => t.id === taskId);
116
+ if (!task) return { success: false, error: 'Task not found' };
117
+ task.status = 'blocked';
118
+ return { success: true, task };
119
+ }
120
+
121
+ /** Reopen a task (unclaim). */
122
+ export function reopenTask(board: TeamTask[], taskId: string): TaskActionResult {
123
+ const task = board.find((t) => t.id === taskId);
124
+ if (!task) return { success: false, error: 'Task not found' };
125
+ task.status = 'open';
126
+ task.claimedBy = undefined;
127
+ task.claimedByName = undefined;
128
+ return { success: true, task };
129
+ }
130
+
131
+ /** Delegate a task from a source board to a target board. */
132
+ export function delegateTask(
133
+ sourceBoard: TeamTask[],
134
+ targetBoard: TeamTask[],
135
+ taskId: string
136
+ ): { result: TaskActionResult; updatedSource: TeamTask[]; updatedTarget: TeamTask[] } {
137
+ const task = sourceBoard.find((t) => t.id === taskId);
138
+ if (!task) return { result: { success: false, error: 'Task not found' }, updatedSource: sourceBoard, updatedTarget: targetBoard };
139
+
140
+ const updatedSource = sourceBoard.filter((t) => t.id !== taskId);
141
+
142
+ // Clone task so it's fresh for the new board (unclaimed)
143
+ const delegatedTask: TeamTask = {
144
+ ...task,
145
+ status: 'open',
146
+ claimedBy: undefined,
147
+ claimedByName: undefined,
148
+ };
149
+
150
+ targetBoard.push(delegatedTask);
151
+ return { result: { success: true, task: delegatedTask }, updatedSource, updatedTarget: targetBoard };
152
+ }
153
+
154
+ /** Add tasks to a board with dedup against existing + done log. */
155
+ export function addTasksToBoard(
156
+ board: TeamTask[],
157
+ doneLog: DoneLogEntry[],
158
+ tasks: Array<Omit<TeamTask, 'id' | 'status' | 'createdAt'>>
159
+ ): { added: TeamTask[]; updatedBoard: TeamTask[] } {
160
+ const existingNorm = new Set([
161
+ ...board.map((t) => normalizeTitle(t.title)),
162
+ ...doneLog.map((d) => normalizeTitle(d.title)),
163
+ ]);
164
+
165
+ const added: TeamTask[] = [];
166
+ for (const t of tasks) {
167
+ const title = String(t.title || '').slice(0, 200);
168
+ if (!title || existingNorm.has(normalizeTitle(title))) continue;
169
+
170
+ const task: TeamTask = {
171
+ id: generateTaskId(),
172
+ title,
173
+ description: String(t.description || '').slice(0, 1000),
174
+ status: 'open',
175
+ source: String(t.source || 'manual'),
176
+ priority: t.priority || 5,
177
+ role: t.role,
178
+ createdAt: new Date().toISOString(),
179
+ };
180
+ board.push(task);
181
+ existingNorm.add(normalizeTitle(title));
182
+ added.push(task);
183
+ }
184
+
185
+ return { added, updatedBoard: board };
186
+ }
187
+
188
+ // ── Suggestion Operations ──
189
+
190
+ export interface SuggestionActionResult {
191
+ success: boolean;
192
+ error?: string;
193
+ suggestion?: TeamSuggestion;
194
+ promotedTask?: TeamTask;
195
+ }
196
+
197
+ /** Create a suggestion with dedup against existing open suggestions. */
198
+ export function createSuggestion(
199
+ suggestions: TeamSuggestion[],
200
+ opts: {
201
+ title: string;
202
+ description?: string;
203
+ category?: SuggestionCategory;
204
+ evidence?: string;
205
+ proposedBy: string;
206
+ proposedByName: string;
207
+ }
208
+ ): SuggestionActionResult {
209
+ const title = opts.title.trim().slice(0, 200);
210
+ if (!title) return { success: false, error: 'title is required' };
211
+
212
+ const existingNorm = new Set(
213
+ suggestions.filter((s) => s.status === 'open').map((s) => normalizeTitle(s.title))
214
+ );
215
+ if (existingNorm.has(normalizeTitle(title))) {
216
+ return { success: false, error: 'A similar open suggestion already exists' };
217
+ }
218
+
219
+ const suggestion: TeamSuggestion = {
220
+ id: generateSuggestionId(),
221
+ title,
222
+ description: (opts.description || '').slice(0, 2000),
223
+ category: opts.category || 'other',
224
+ proposedBy: opts.proposedBy,
225
+ proposedByName: opts.proposedByName,
226
+ votes: [],
227
+ score: 0,
228
+ status: 'open',
229
+ evidence: opts.evidence?.slice(0, 1000),
230
+ createdAt: new Date().toISOString(),
231
+ };
232
+ suggestions.push(suggestion);
233
+ return { success: true, suggestion };
234
+ }
235
+
236
+ /** Vote on a suggestion. Auto-promotes at majority, auto-dismisses at negative majority. */
237
+ export function voteSuggestion(
238
+ suggestions: TeamSuggestion[],
239
+ board: TeamTask[],
240
+ suggestionId: string,
241
+ voterId: string,
242
+ voterName: string,
243
+ value: 1 | -1,
244
+ maxSlots: number,
245
+ reason?: string
246
+ ): SuggestionActionResult {
247
+ const suggestion = suggestions.find((s) => s.id === suggestionId);
248
+ if (!suggestion) return { success: false, error: 'Suggestion not found' };
249
+ if (suggestion.status !== 'open') return { success: false, error: `Suggestion is ${suggestion.status}, voting closed` };
250
+
251
+ // Replace previous vote from same agent
252
+ suggestion.votes = suggestion.votes.filter((v) => v.agentId !== voterId);
253
+ suggestion.votes.push({
254
+ agentId: voterId,
255
+ agentName: voterName,
256
+ value,
257
+ reason,
258
+ votedAt: new Date().toISOString(),
259
+ });
260
+ suggestion.score = suggestion.votes.reduce((sum, v) => sum + v.value, 0);
261
+
262
+ let promotedTask: TeamTask | undefined;
263
+
264
+ // Auto-promote at majority
265
+ const promoteThreshold = Math.ceil(maxSlots / 2);
266
+ if (suggestion.score >= promoteThreshold && suggestion.status === 'open') {
267
+ suggestion.status = 'promoted';
268
+ suggestion.resolvedAt = new Date().toISOString();
269
+
270
+ promotedTask = {
271
+ id: generateTaskId(),
272
+ title: suggestion.title,
273
+ description: `${suggestion.description}\n\n[Auto-promoted from suggestion by ${suggestion.proposedByName} with ${suggestion.score} votes]`,
274
+ status: 'open',
275
+ source: `suggestion:${suggestion.id}`,
276
+ priority: suggestion.category === 'architecture' ? 2 : suggestion.category === 'testing' ? 3 : 4,
277
+ createdAt: new Date().toISOString(),
278
+ };
279
+ board.push(promotedTask);
280
+ suggestion.promotedTaskId = promotedTask.id;
281
+ }
282
+
283
+ // Auto-dismiss at negative majority
284
+ const dismissThreshold = -Math.ceil(maxSlots / 2);
285
+ if (suggestion.score <= dismissThreshold && suggestion.status === 'open') {
286
+ suggestion.status = 'dismissed';
287
+ suggestion.resolvedAt = new Date().toISOString();
288
+ }
289
+
290
+ return { success: true, suggestion, promotedTask };
291
+ }
292
+
293
+ /** Promote a suggestion manually. Creates a board task. */
294
+ export function promoteSuggestion(
295
+ suggestions: TeamSuggestion[],
296
+ board: TeamTask[],
297
+ suggestionId: string,
298
+ promoterName: string,
299
+ opts: { priority?: number; role?: SlotRole } = {}
300
+ ): SuggestionActionResult {
301
+ const suggestion = suggestions.find((s) => s.id === suggestionId);
302
+ if (!suggestion) return { success: false, error: 'Suggestion not found' };
303
+ if (suggestion.status !== 'open') return { success: false, error: `Suggestion is already ${suggestion.status}` };
304
+
305
+ suggestion.status = 'promoted';
306
+ suggestion.resolvedAt = new Date().toISOString();
307
+
308
+ const task: TeamTask = {
309
+ id: generateTaskId(),
310
+ title: suggestion.title,
311
+ description: `${suggestion.description}\n\n[Promoted by ${promoterName} from suggestion by ${suggestion.proposedByName}]`,
312
+ status: 'open',
313
+ source: `suggestion:${suggestion.id}`,
314
+ priority: opts.priority || 3,
315
+ role: opts.role,
316
+ createdAt: new Date().toISOString(),
317
+ };
318
+ board.push(task);
319
+ suggestion.promotedTaskId = task.id;
320
+
321
+ return { success: true, suggestion, promotedTask: task };
322
+ }
323
+
324
+ /** Dismiss a suggestion. */
325
+ export function dismissSuggestion(
326
+ suggestions: TeamSuggestion[],
327
+ suggestionId: string
328
+ ): SuggestionActionResult {
329
+ const suggestion = suggestions.find((s) => s.id === suggestionId);
330
+ if (!suggestion) return { success: false, error: 'Suggestion not found' };
331
+ if (suggestion.status !== 'open') return { success: false, error: `Suggestion is already ${suggestion.status}` };
332
+
333
+ suggestion.status = 'dismissed';
334
+ suggestion.resolvedAt = new Date().toISOString();
335
+ return { success: true, suggestion };
336
+ }