groundswell 0.0.1 → 0.0.2

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 (242) hide show
  1. package/.claude/commands/subtask-planning/prp-base-create.md +120 -0
  2. package/.claude/commands/subtask-planning/prp-base-execute.md +65 -0
  3. package/.claude/commands/task-breakdown.md +94 -0
  4. package/.claude/system_prompts/task-breakdown.md +1 -0
  5. package/CHANGELOG.md +188 -0
  6. package/PRD.md +543 -0
  7. package/README.md +99 -5
  8. package/examples/README.md +15 -1
  9. package/examples/examples/11-reparenting-workflows.ts +269 -0
  10. package/examples/index.ts +4 -0
  11. package/package-lock.json +2398 -0
  12. package/package.json +3 -1
  13. package/plan/001_d3bb02af4886/TEST_RESULTS.md +259 -0
  14. package/plan/001_d3bb02af4886/bug_fix_tasks.json +484 -0
  15. package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/P1M1T1S1/PRP.md +488 -0
  16. package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/P1M1T1S2/PRP.md +581 -0
  17. package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/P1M1T1S3/PRP.md +687 -0
  18. package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/P1M2T1S1/PRP.md +492 -0
  19. package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/P1M2T1S3/PRP.md +932 -0
  20. package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/P1M2T1S3/research/concurrent_error_testing_patterns.md +1109 -0
  21. package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/P1M2T1S3/research/vitest_concurrent_testing.md +802 -0
  22. package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/P1M2T1S3/research/workflow_engine_test_references.md +603 -0
  23. package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/P1M2T2S1/PRP.md +564 -0
  24. package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/P1M2T2S3/PRP.md +518 -0
  25. package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/P1M2T2S4/PRP.md +1252 -0
  26. package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/P1M2T3S1/PRP.md +364 -0
  27. package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/P1M2T3S1/research/CODEBASE_INVENTORY.md +114 -0
  28. package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/P1M2T3S1/research/DECORATOR_DOCUMENTATION_PATTERNS.md +205 -0
  29. package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/P1M2T3S1/research/PRD_LOCATION_ANALYSIS.md +199 -0
  30. package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/P1M2T3S1/research/ULTRATHINK_PRP_PLAN.md +134 -0
  31. package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/P1M3T1S1/PRP.md +495 -0
  32. package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/P1M3T1S1/research/console_error_inventory.md +435 -0
  33. package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/P1M3T1S2/PRP.md +506 -0
  34. package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/P1M3T1S3/PRP.md +612 -0
  35. package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/P1M3T2S2/PRP.md +558 -0
  36. package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/P1M3T2S2/research/external_research.md +788 -0
  37. package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/P1M3T3S2/PRP.md +460 -0
  38. package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/P1M3T3S3/PRP.md +454 -0
  39. package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/P1M3T4S1/PRP.md +520 -0
  40. package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/P1M3T4S1/RECOMMENDATION.md +417 -0
  41. package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/P1M3T4S1/research/external_workflow_engines_research.md +760 -0
  42. package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/P1M3T4S1/research/security_implications_analysis.md +245 -0
  43. package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/P1M3T4S2/PRP.md +792 -0
  44. package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/P1M4T1S1/PRP.md +535 -0
  45. package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/P1M4T1S1/TEST_EXECUTION_REPORT.md +190 -0
  46. package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/P1M4T1S2/PRP.md +654 -0
  47. package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/P1M4T1S2/TEST_FIX_REPORT.md +227 -0
  48. package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/P1M4T1S2/research/KEY_FINDINGS.md +345 -0
  49. package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/P1M4T1S2/research/QUICK_REFERENCE.md +193 -0
  50. package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/P1M4T1S2/research/test_maintenance_research.md +1323 -0
  51. package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/P1M4T3S1/BREAKING_CHANGES_AUDIT.md +1011 -0
  52. package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/P1M4T3S1/PRP.md +927 -0
  53. package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/P1M4T3S2/PRP.md +505 -0
  54. package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/architecture/logger_child_signature_analysis.md +401 -0
  55. package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/docs/P1M1T1S3/child_implementation_research.md +142 -0
  56. package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/docs/P1M1T1S3/test_patterns_research.md +112 -0
  57. package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/docs/P1M1T1S3/vitest_patterns_research.md +159 -0
  58. package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/docs/P1M1T1S4/PRP.md +549 -0
  59. package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/docs/P1M1T1S4/VERIFICATION_REPORT.md +368 -0
  60. package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/docs/P1M1T1S4/edge_case_analysis.md +172 -0
  61. package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/docs/P1M1T1S4/usage_inventory.md +175 -0
  62. package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/docs/P1M2T1S2/PRP.md +696 -0
  63. package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/docs/P1M2T1S4/PRP.md +860 -0
  64. package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/docs/P1M2T2S2/PRP.md +1066 -0
  65. package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/docs/P1M2T2S2/research/01-testing-aggregated-errors.md +1103 -0
  66. package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/docs/P1M2T2S2/research/01_typescript_error_aggregation_patterns.md +789 -0
  67. package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/docs/P1M2T2S2/research/02-error-merge-strategy-testing-guide.md +1098 -0
  68. package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/docs/P1M2T2S2/research/02_aggregate_error_patterns.md +1037 -0
  69. package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/docs/P1M2T2S2/research/03-promise-allsettled-testing-patterns.md +916 -0
  70. package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/docs/P1M2T2S2/research/03_error_merging_strategies.md +1045 -0
  71. package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/docs/P1M2T2S2/research/04_github_stackoverflow_examples.md +890 -0
  72. package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/docs/P1M2T2S2/research/05_comprehensive_summary.md +822 -0
  73. package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/docs/P1M2T2S2/research/INDEX.md +668 -0
  74. package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/docs/P1M2T2S2/research/QUICK_REFERENCE.md +706 -0
  75. package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/docs/P1M2T2S2/research/README.md +265 -0
  76. package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/docs/P1M2T2S2/research/RESEARCH_REPORT.md +655 -0
  77. package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/docs/P1M2T2S4/research/vitest_testing_patterns.md +1103 -0
  78. package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/docs/P1M2T3S2/PRP.md +426 -0
  79. package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/docs/P1M3T1S2/PRP.md +506 -0
  80. package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/docs/P1M3T1S2/research/QUICK_REFERENCE.md +114 -0
  81. package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/docs/P1M3T1S2/research/RESEARCH_SUMMARY.md +316 -0
  82. package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/docs/P1M3T1S2/research/vitest_observer_error_logging_best_practices.md +754 -0
  83. package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/docs/P1M3T1S3/PRP.md +612 -0
  84. package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/docs/P1M3T2S1/PRP.md +719 -0
  85. package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/docs/P1M3T2S1/README.md +215 -0
  86. package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/docs/P1M3T2S1/analysis.md +765 -0
  87. package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/docs/P1M3T2S3/PRP.md +718 -0
  88. package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/docs/P1M3T3S1/DECISION.md +149 -0
  89. package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/docs/P1M3T3S1/PRP.md +470 -0
  90. package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/docs/P1M3T3S1/research/ULTRATHINK_PLAN.md +332 -0
  91. package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/docs/P1M3T3S1/research/codebase_workflow_name_analysis.md +167 -0
  92. package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/docs/P1M3T3S1/research/external_best_practices.md +265 -0
  93. package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/docs/P1M3T3S1/research/validation_patterns.md +273 -0
  94. package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/docs/P1M3T4S1/workflow_engine_ancestry_api_research.md +760 -0
  95. package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/docs/P1M3T4S3-PRP.md +434 -0
  96. package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/docs/P1M4T2S1/PRP.md +717 -0
  97. package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/docs/P1M4T2S2/PRP.md +472 -0
  98. package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/docs/P1M4T2S2/VALIDATION_REPORT.md +125 -0
  99. package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/docs/P1M4T2S2/research/ULTRATHINK_PRP_PLAN.md +301 -0
  100. package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/docs/error-logging-best-practices.md +1170 -0
  101. package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/docs/research_typescript_partial_and_overloads.md +940 -0
  102. package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/docs/vitest-quick-reference.md +151 -0
  103. package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/docs/vitest-research.md +650 -0
  104. package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/prd_snapshot.md +259 -0
  105. package/plan/001_d3bb02af4886/bugfix/P1M1T1S1/PRP.md +457 -0
  106. package/plan/001_d3bb02af4886/bugfix/RESEARCH_SUMMARY.md +346 -0
  107. package/plan/001_d3bb02af4886/bugfix/architecture/codebase_structure.md +311 -0
  108. package/plan/001_d3bb02af4886/bugfix/architecture/concurrent_execution_best_practices.md +1565 -0
  109. package/plan/001_d3bb02af4886/bugfix/architecture/error_handling_patterns.md +288 -0
  110. package/plan/001_d3bb02af4886/bugfix/architecture/promise_all_analysis.md +741 -0
  111. package/plan/001_d3bb02af4886/docs/PRP/P1M1T1S4-functional-workflow-error-state-capture-test.md +652 -0
  112. package/plan/001_d3bb02af4886/docs/PRP/PRP.md +527 -0
  113. package/plan/001_d3bb02af4886/docs/PRP/bugfix/P1M1T2S1-PRP.md +415 -0
  114. package/plan/001_d3bb02af4886/docs/PRP/bugfix/P1M1T2S2-PRP.md +378 -0
  115. package/plan/001_d3bb02af4886/docs/PRP/bugfix/P1M1T2S4-PRP.md +713 -0
  116. package/plan/001_d3bb02af4886/docs/PRP/bugfix/P1M2T1S4-PRP.md +370 -0
  117. package/plan/001_d3bb02af4886/docs/PRP_P1M3T1S3.md +499 -0
  118. package/plan/001_d3bb02af4886/docs/TEST_RESULTS.md +230 -0
  119. package/plan/001_d3bb02af4886/docs/bugfix/ANALYSIS_PRD_VS_IMPLEMENTATION.md +1134 -0
  120. package/plan/001_d3bb02af4886/docs/bugfix/GAP_ANALYSIS_SUMMARY.md +179 -0
  121. package/plan/001_d3bb02af4886/docs/bugfix/P1M4T2S1/PRP.md +629 -0
  122. package/plan/001_d3bb02af4886/docs/bugfix/P1M4T2S1/validation-report.md +214 -0
  123. package/plan/001_d3bb02af4886/docs/bugfix/PRP_P1M4T2S3.md +629 -0
  124. package/plan/001_d3bb02af4886/docs/bugfix/bugfix_PRP.md +529 -0
  125. package/plan/001_d3bb02af4886/docs/bugfix/bugfix_QUICK_REFERENCE.md +142 -0
  126. package/plan/001_d3bb02af4886/docs/bugfix/bugfix_README.md +304 -0
  127. package/plan/001_d3bb02af4886/docs/bugfix/bugfix_TEST_RESULTS.md +558 -0
  128. package/plan/001_d3bb02af4886/docs/bugfix/bugfix_VALIDATION_SUMMARY.md +256 -0
  129. package/plan/001_d3bb02af4886/docs/bugfix/system_context.md +346 -0
  130. package/plan/001_d3bb02af4886/docs/bugfix-architecture/bug_analysis.md +415 -0
  131. package/plan/001_d3bb02af4886/docs/bugfix-architecture/implementation_patterns.md +489 -0
  132. package/plan/001_d3bb02af4886/docs/bugfix-architecture/system_context.md +218 -0
  133. package/plan/001_d3bb02af4886/docs/bugfix_INITIATION_SUMMARY.md +380 -0
  134. package/plan/001_d3bb02af4886/docs/research/CYCLE_DETECTION_PATTERNS.md +1923 -0
  135. package/plan/001_d3bb02af4886/docs/research/CYCLE_DETECTION_QUICK_REF.md +319 -0
  136. package/plan/001_d3bb02af4886/docs/research/P1M1T2S1/codebase-context.md +115 -0
  137. package/plan/001_d3bb02af4886/docs/research/P1M1T2S1/cycle-detection-algorithms.md +134 -0
  138. package/plan/001_d3bb02af4886/docs/research/P1M1T2S1/test-patterns.md +153 -0
  139. package/plan/001_d3bb02af4886/docs/research/P1M1T2S1/workflow-class.md +132 -0
  140. package/plan/001_d3bb02af4886/docs/research/P1M2T1S4/DECORATOR_DOCUMENTATION_BEST_PRACTICES.md +716 -0
  141. package/plan/001_d3bb02af4886/docs/research/P1M2T1S4/DECORATOR_DOCUMENTATION_QUICK_REF.md +186 -0
  142. package/plan/001_d3bb02af4886/docs/research/P1M2T1S4/GROUNDSWELL_DECORATOR_EXAMPLES.md +604 -0
  143. package/plan/001_d3bb02af4886/docs/research/P1M2T1S4/INDEX.md +213 -0
  144. package/plan/001_d3bb02af4886/docs/research/P1M2T1S4/codebase_structure.md +30 -0
  145. package/plan/001_d3bb02af4886/docs/research/P1M2T1S4/existing_test_pattern.md +56 -0
  146. package/plan/001_d3bb02af4886/docs/research/P1M2T1S4/getRootObservers_implementation.md +53 -0
  147. package/plan/001_d3bb02af4886/docs/research/P1M2T1S4/test_conventions.md +49 -0
  148. package/plan/001_d3bb02af4886/docs/research/P1M3T1S4/PRP.md +958 -0
  149. package/plan/001_d3bb02af4886/docs/research/P1M3T1S4/QUICK_REFERENCE.md +339 -0
  150. package/plan/001_d3bb02af4886/docs/research/P1M3T1S4/README.md +305 -0
  151. package/plan/001_d3bb02af4886/docs/research/P1M3T1S4/SUMMARY.md +433 -0
  152. package/plan/001_d3bb02af4886/docs/research/P1M3T1S4/bidirectional-tree-consistency-testing.md +1574 -0
  153. package/plan/001_d3bb02af4886/docs/research/P1M3T1S4/test-pattern-examples.md +1014 -0
  154. package/plan/001_d3bb02af4886/docs/research/PROMISE_ALLSETTLED_QUICK_REF.md +376 -0
  155. package/plan/001_d3bb02af4886/docs/research/PROMISE_ALLSETTLED_RESEARCH.md +1507 -0
  156. package/plan/001_d3bb02af4886/docs/research/bugfix_typescript_patterns.md +949 -0
  157. package/plan/001_d3bb02af4886/docs/research/error-testing-research.md +619 -0
  158. package/plan/001_d3bb02af4886/docs/research/error_handling_patterns.md +723 -0
  159. package/plan/{research → 001_d3bb02af4886/docs/research/general}/introspection-security-guide.md +56 -0
  160. package/plan/001_d3bb02af4886/docs/research/incremental-tree-map-updates/PRP_TEMPLATE.md +460 -0
  161. package/plan/001_d3bb02af4886/docs/research/incremental-tree-map-updates/QUICK_REFERENCE.md +324 -0
  162. package/plan/001_d3bb02af4886/docs/research/incremental-tree-map-updates/README.md +175 -0
  163. package/plan/001_d3bb02af4886/docs/research/incremental-tree-map-updates/RESEARCH_REPORT.md +499 -0
  164. package/plan/001_d3bb02af4886/docs/research/incremental-tree-map-updates/SUMMARY.md +163 -0
  165. package/plan/bugfix/BUG_FIX_SUMMARY.md +961 -0
  166. package/src/__tests__/adversarial/attachChild-performance.test.ts +216 -0
  167. package/src/__tests__/adversarial/circular-reference.test.ts +101 -0
  168. package/src/__tests__/adversarial/complex-circular-reference.test.ts +139 -0
  169. package/src/__tests__/adversarial/concurrent-task-failures.test.ts +571 -0
  170. package/src/__tests__/adversarial/deep-analysis.test.ts +729 -0
  171. package/src/__tests__/adversarial/deep-hierarchy-stress.test.ts +213 -0
  172. package/src/__tests__/adversarial/e2e-prd-validation.test.ts +448 -0
  173. package/src/__tests__/adversarial/edge-case.test.ts +703 -0
  174. package/src/__tests__/adversarial/error-merge-strategy.test.ts +760 -0
  175. package/src/__tests__/adversarial/incremental-performance.test.ts +140 -0
  176. package/src/__tests__/adversarial/node-map-update-benchmarks.test.ts +457 -0
  177. package/src/__tests__/adversarial/observer-propagation.test.ts +487 -0
  178. package/src/__tests__/adversarial/parent-validation.test.ts +143 -0
  179. package/src/__tests__/adversarial/prd-12-2-compliance.test.ts +611 -0
  180. package/src/__tests__/adversarial/prd-compliance.test.ts +731 -0
  181. package/src/__tests__/compatibility/backward-compatibility.test.ts +1572 -0
  182. package/src/__tests__/helpers/index.ts +18 -0
  183. package/src/__tests__/helpers/tree-verification.ts +257 -0
  184. package/src/__tests__/integration/bidirectional-consistency.test.ts +847 -0
  185. package/src/__tests__/integration/observer-logging.test.ts +643 -0
  186. package/src/__tests__/integration/tree-mirroring.test.ts +37 -0
  187. package/src/__tests__/integration/workflow-reparenting.test.ts +303 -0
  188. package/src/__tests__/unit/context.test.ts +79 -0
  189. package/src/__tests__/unit/logger.test.ts +293 -0
  190. package/src/__tests__/unit/observable.test.ts +321 -0
  191. package/src/__tests__/unit/tree-debugger-incremental.test.ts +170 -0
  192. package/src/__tests__/unit/utils/workflow-error-utils.test.ts +209 -0
  193. package/src/__tests__/unit/workflow-detachChild.test.ts +100 -0
  194. package/src/__tests__/unit/workflow-emitEvent-childDetached.test.ts +153 -0
  195. package/src/__tests__/unit/workflow-isDescendantOf.test.ts +180 -0
  196. package/src/__tests__/unit/workflow.test.ts +277 -1
  197. package/src/core/agent.ts +21 -1
  198. package/src/core/logger.ts +27 -2
  199. package/src/core/workflow-context.ts +6 -4
  200. package/src/core/workflow.ts +252 -14
  201. package/src/debugger/tree-debugger.ts +52 -7
  202. package/src/decorators/task.ts +65 -2
  203. package/src/index.ts +4 -2
  204. package/src/types/decorators.ts +8 -1
  205. package/src/types/events.ts +1 -0
  206. package/src/utils/index.ts +1 -0
  207. package/src/utils/observable.ts +32 -3
  208. package/src/utils/workflow-error-utils.ts +56 -0
  209. package/tsconfig.json +1 -1
  210. package/llms_full.txt +0 -5890
  211. package/tasks.json +0 -0
  212. /package/plan/{backlog.json → 001_d3bb02af4886/backlog.json} +0 -0
  213. /package/plan/{P1P2/PRP.md → 001_d3bb02af4886/docs/PRP/P1P2-PRP.md} +0 -0
  214. /package/plan/{P3P4/PRP.md → 001_d3bb02af4886/docs/PRP/P3P4-PRP.md} +0 -0
  215. /package/plan/{P4P5/PRP.md → 001_d3bb02af4886/docs/PRP/P4P5-PRP.md} +0 -0
  216. /package/plan/{architecture → 001_d3bb02af4886/docs/architecture}/external_deps.md +0 -0
  217. /package/plan/{architecture → 001_d3bb02af4886/docs/architecture}/system_context.md +0 -0
  218. /package/plan/{P1P2/research → 001_d3bb02af4886/docs/research/P1P2}/LRU_CACHE_BEST_PRACTICES.md +0 -0
  219. /package/plan/{P1P2/research → 001_d3bb02af4886/docs/research/P1P2}/LRU_CACHE_CODE_PATTERNS.md +0 -0
  220. /package/plan/{P1P2/research → 001_d3bb02af4886/docs/research/P1P2}/LRU_CACHE_INTEGRATION_GUIDE.md +0 -0
  221. /package/plan/{P1P2/research → 001_d3bb02af4886/docs/research/P1P2}/LRU_CACHE_RESEARCH_INDEX.md +0 -0
  222. /package/plan/{P1P2/research → 001_d3bb02af4886/docs/research/P1P2}/REFLECTION_INDEX.md +0 -0
  223. /package/plan/{P1P2/research → 001_d3bb02af4886/docs/research/P1P2}/REFLECTION_RESEARCH_REPORT.md +0 -0
  224. /package/plan/{P1P2/research → 001_d3bb02af4886/docs/research/P1P2}/RESEARCH_SUMMARY.md +0 -0
  225. /package/plan/{P1P2/research → 001_d3bb02af4886/docs/research/P1P2}/anthropic-sdk.md +0 -0
  226. /package/plan/{P1P2/research → 001_d3bb02af4886/docs/research/P1P2}/async-local-storage.md +0 -0
  227. /package/plan/{P1P2/research → 001_d3bb02af4886/docs/research/P1P2}/reflection-code-patterns.md +0 -0
  228. /package/plan/{P1P2/research → 001_d3bb02af4886/docs/research/P1P2}/reflection-decision-matrix.md +0 -0
  229. /package/plan/{P1P2/research → 001_d3bb02af4886/docs/research/P1P2}/reflection-implementation-guide.md +0 -0
  230. /package/plan/{P1P2/research → 001_d3bb02af4886/docs/research/P1P2}/reflection-integration-guide.md +0 -0
  231. /package/plan/{P1P2/research → 001_d3bb02af4886/docs/research/P1P2}/reflection-patterns.md +0 -0
  232. /package/plan/{P1P2/research → 001_d3bb02af4886/docs/research/P1P2}/reflection-quick-reference.md +0 -0
  233. /package/plan/{P1P2/research → 001_d3bb02af4886/docs/research/P1P2}/zod-schema.md +0 -0
  234. /package/plan/{P3P4/research → 001_d3bb02af4886/docs/research/P3P4}/caching-lru.md +0 -0
  235. /package/plan/{P3P4/research → 001_d3bb02af4886/docs/research/P3P4}/introspection-tools.md +0 -0
  236. /package/plan/{P3P4/research → 001_d3bb02af4886/docs/research/P3P4}/reflection-patterns.md +0 -0
  237. /package/plan/{P4P5/research → 001_d3bb02af4886/docs/research/P4P5}/RESEARCH_SUMMARY.md +0 -0
  238. /package/plan/{research → 001_d3bb02af4886/docs/research/general}/INTROSPECTION_RESEARCH_SUMMARY.md +0 -0
  239. /package/plan/{research → 001_d3bb02af4886/docs/research/general}/README-INTROSPECTION.md +0 -0
  240. /package/plan/{research → 001_d3bb02af4886/docs/research/general}/agent-introspection-patterns.md +0 -0
  241. /package/plan/{research → 001_d3bb02af4886/docs/research/general}/introspection-tool-examples.md +0 -0
  242. /package/{PRPs/PRDs/001-hierarchical-workflow-engine.md → plan/001_d3bb02af4886/prd_snapshot.md} +0 -0
@@ -0,0 +1,760 @@
1
+ /**
2
+ * ErrorMergeStrategy Functionality Test Suite
3
+ *
4
+ * Tests the ErrorMergeStrategy feature for concurrent task error aggregation.
5
+ *
6
+ * Validates:
7
+ * - Default behavior (disabled) - first error wins
8
+ * - Enabled with default merge - uses mergeWorkflowErrors
9
+ * - Enabled with custom combine - user-provided merger
10
+ * - Edge cases - single failure, all failing, mixed scenarios
11
+ * - Event emission - individual and merged error events
12
+ * - Completion verification - all workflows complete
13
+ *
14
+ * Related:
15
+ * - P1.M2.T2.S2: Error aggregation logic implementation
16
+ * - P1.M2.T2.S3: Default error merger utility
17
+ * - Bug: 001_e8e04329daf3 - Concurrent task error handling
18
+ */
19
+
20
+ import { describe, it, expect, vi } from 'vitest';
21
+ import { Workflow, Task, Step } from '../../index.js';
22
+ import type { WorkflowEvent, WorkflowError } from '../../types/index.js';
23
+
24
+ describe('@Task decorator ErrorMergeStrategy', () => {
25
+ /**
26
+ * Helper to create a child workflow that may fail
27
+ * Pattern from: src/__tests__/adversarial/concurrent-task-failures.test.ts (lines 30-52)
28
+ */
29
+ function createChildWorkflow(
30
+ parent: Workflow,
31
+ name: string,
32
+ shouldFail: boolean = false
33
+ ): Workflow {
34
+ return new (class extends Workflow {
35
+ constructor(n: string, p: Workflow) {
36
+ super(n, p);
37
+ }
38
+
39
+ @Step()
40
+ async executeStep() {
41
+ if (shouldFail) {
42
+ throw new Error(`${name} failed`);
43
+ }
44
+ return `${name} succeeded`;
45
+ }
46
+
47
+ async run() {
48
+ return this.executeStep();
49
+ }
50
+ })(name, parent);
51
+ }
52
+
53
+ /**
54
+ * Helper to setup event observer for event collection
55
+ * Pattern from: src/__tests__/adversarial/concurrent-task-failures.test.ts (lines 58-67)
56
+ */
57
+ function setupEventObserver(workflow: Workflow): WorkflowEvent[] {
58
+ const events: WorkflowEvent[] = [];
59
+ workflow.addObserver({
60
+ onLog: () => {},
61
+ onEvent: (e) => events.push(e),
62
+ onStateUpdated: () => {},
63
+ onTreeChanged: () => {},
64
+ });
65
+ return events;
66
+ }
67
+
68
+ /**
69
+ * Helper to create a mock WorkflowError for custom combine() tests
70
+ * Pattern from: src/__tests__/unit/utils/workflow-error-utils.test.ts (lines 7-25)
71
+ */
72
+ function createMockWorkflowError(overrides?: Partial<WorkflowError>): WorkflowError {
73
+ return {
74
+ message: 'Test error',
75
+ original: new Error('Original error'),
76
+ workflowId: 'wf-test-123',
77
+ stack: 'Error: Test error\n at test.ts:10:15',
78
+ state: { key: 'value' },
79
+ logs: [
80
+ {
81
+ id: 'log-1',
82
+ workflowId: 'wf-test-123',
83
+ timestamp: Date.now(),
84
+ level: 'error',
85
+ message: 'Test log message',
86
+ },
87
+ ],
88
+ ...overrides,
89
+ };
90
+ }
91
+
92
+ describe('Default behavior (errorMergeStrategy disabled)', () => {
93
+ it('should throw first error when errorMergeStrategy not provided', async () => {
94
+ // ARRANGE: Create parent with concurrent tasks, no error merge strategy
95
+ class ParentWorkflow extends Workflow {
96
+ @Task({ concurrent: true }) // CRITICAL: concurrent=true required
97
+ async spawnChildren() {
98
+ return [
99
+ createChildWorkflow(this, 'Child-0', false),
100
+ createChildWorkflow(this, 'Child-1', true), // Will fail
101
+ createChildWorkflow(this, 'Child-2', false),
102
+ ];
103
+ }
104
+
105
+ async run() {
106
+ try {
107
+ await this.spawnChildren();
108
+ } catch (err) {
109
+ // Expected - capture error for validation
110
+ return err;
111
+ }
112
+ }
113
+ }
114
+
115
+ const parent = new ParentWorkflow('Parent');
116
+ const events = setupEventObserver(parent);
117
+
118
+ // ACT: Run parent workflow
119
+ const thrownError = await parent.run();
120
+
121
+ // ASSERT: All children completed (Promise.allSettled behavior)
122
+ expect(parent.children.length).toBe(3);
123
+
124
+ // ASSERT: Error was thrown (first error wins)
125
+ expect(thrownError).toBeDefined();
126
+ expect((thrownError as WorkflowError).message).toContain('Child-1 failed');
127
+
128
+ // ASSERT: No additional error event from @Task (only individual workflow errors)
129
+ const errorEvents = events.filter((e) => e.type === 'error');
130
+ expect(errorEvents.length).toBe(1); // Only Child-1's error event
131
+ });
132
+
133
+ it('should throw first error when errorMergeStrategy.enabled=false', async () => {
134
+ // ARRANGE: Create parent with explicit enabled=false
135
+ class ParentWorkflow extends Workflow {
136
+ @Task({
137
+ concurrent: true,
138
+ errorMergeStrategy: { enabled: false }, // Explicitly disabled
139
+ })
140
+ async spawnChildren() {
141
+ return [
142
+ createChildWorkflow(this, 'Alpha', false),
143
+ createChildWorkflow(this, 'Beta', true),
144
+ createChildWorkflow(this, 'Gamma', true),
145
+ ];
146
+ }
147
+
148
+ async run() {
149
+ try {
150
+ await this.spawnChildren();
151
+ } catch (err) {
152
+ return err;
153
+ }
154
+ }
155
+ }
156
+
157
+ const parent = new ParentWorkflow('Parent');
158
+ const events = setupEventObserver(parent);
159
+ const thrownError = await parent.run();
160
+
161
+ // ASSERT: All children completed
162
+ expect(parent.children.length).toBe(3);
163
+
164
+ // ASSERT: First error thrown (not aggregated)
165
+ expect(thrownError).toBeDefined();
166
+ const errorMsg = (thrownError as WorkflowError).message;
167
+ expect(errorMsg).toMatch(/Alpha failed|Beta failed|Gamma failed/);
168
+
169
+ // ASSERT: Only individual error events (no merge event)
170
+ const errorEvents = events.filter((e) => e.type === 'error');
171
+ expect(errorEvents.length).toBe(2); // Beta and Gamma errors only
172
+
173
+ // ASSERT: Error message does not contain aggregated format
174
+ expect(errorMsg).not.toContain('concurrent child workflows failed');
175
+ });
176
+ });
177
+
178
+ describe('Enabled with default error merge', () => {
179
+ it('should merge all errors when errorMergeStrategy.enabled=true', async () => {
180
+ // ARRANGE: Create parent with error merge enabled
181
+ class ParentWorkflow extends Workflow {
182
+ @Task({
183
+ concurrent: true,
184
+ errorMergeStrategy: { enabled: true }, // No combine() - use default
185
+ })
186
+ async spawnChildren() {
187
+ return [
188
+ createChildWorkflow(this, 'Alpha', false),
189
+ createChildWorkflow(this, 'Beta', true), // Will fail
190
+ createChildWorkflow(this, 'Gamma', false),
191
+ createChildWorkflow(this, 'Delta', true), // Will fail
192
+ createChildWorkflow(this, 'Epsilon', false),
193
+ ];
194
+ }
195
+
196
+ async run() {
197
+ try {
198
+ await this.spawnChildren();
199
+ } catch (err) {
200
+ return err;
201
+ }
202
+ }
203
+ }
204
+
205
+ const parent = new ParentWorkflow('Parent');
206
+ const events = setupEventObserver(parent);
207
+
208
+ // ACT
209
+ const thrownError = await parent.run();
210
+
211
+ // ASSERT: All children completed
212
+ expect(parent.children.length).toBe(5);
213
+
214
+ // ASSERT: Merged error thrown
215
+ expect(thrownError).toBeDefined();
216
+ const error = thrownError as WorkflowError;
217
+
218
+ // ASSERT: Message includes count and task name
219
+ expect(error.message).toBe("2 of 5 concurrent child workflows failed in task 'spawnChildren'");
220
+
221
+ // ASSERT: Metadata in original field
222
+ const metadata = error.original as {
223
+ name: string;
224
+ message: string;
225
+ errors: WorkflowError[];
226
+ totalChildren: number;
227
+ failedChildren: number;
228
+ failedWorkflowIds: string[];
229
+ };
230
+
231
+ expect(metadata.name).toBe('WorkflowAggregateError');
232
+ expect(metadata.totalChildren).toBe(5);
233
+ expect(metadata.failedChildren).toBe(2);
234
+ expect(metadata.failedWorkflowIds).toHaveLength(2);
235
+ expect(metadata.errors).toHaveLength(2);
236
+
237
+ // ASSERT: Logs aggregated from all errors (empty array if child workflows don't log)
238
+ expect(error.logs).toBeDefined();
239
+ expect(Array.isArray(error.logs)).toBe(true);
240
+ // Note: Logs are empty here because createChildWorkflow doesn't log
241
+ // The "should aggregate all logs from all failed workflows" test specifically tests log aggregation
242
+
243
+ // ASSERT: Error event emitted with merged error
244
+ const errorEvents = events.filter((e) => e.type === 'error');
245
+ expect(errorEvents.length).toBeGreaterThanOrEqual(3); // 2 individual + 1 merged
246
+
247
+ // Find the merged error event (has different message format)
248
+ const mergedErrorEvent = errorEvents.find((e) => {
249
+ if (e.type === 'error') {
250
+ return e.error.message.includes('2 of 5 concurrent');
251
+ }
252
+ return false;
253
+ });
254
+ expect(mergedErrorEvent).toBeDefined();
255
+ });
256
+
257
+ it('should create aggregated error message with counts and task name', async () => {
258
+ // ARRANGE: Test various counts
259
+ class ParentWorkflow extends Workflow {
260
+ @Task({
261
+ concurrent: true,
262
+ errorMergeStrategy: { enabled: true },
263
+ })
264
+ async spawnChildren() {
265
+ return [
266
+ createChildWorkflow(this, 'Success1', false),
267
+ createChildWorkflow(this, 'Fail1', true),
268
+ createChildWorkflow(this, 'Success2', false),
269
+ createChildWorkflow(this, 'Fail2', true),
270
+ createChildWorkflow(this, 'Fail3', true),
271
+ ];
272
+ }
273
+
274
+ async run() {
275
+ try {
276
+ await this.spawnChildren();
277
+ } catch (err) {
278
+ return err;
279
+ }
280
+ }
281
+ }
282
+
283
+ const parent = new ParentWorkflow('Parent');
284
+ const thrownError = await parent.run() as WorkflowError;
285
+
286
+ // ASSERT: Message format "${X} of ${Y} concurrent child workflows failed in task '${taskName}'"
287
+ expect(thrownError.message).toBe("3 of 5 concurrent child workflows failed in task 'spawnChildren'");
288
+ expect(thrownError.message).toMatch(/\d+ of \d+ concurrent child workflows failed/);
289
+ expect(thrownError.message).toContain("task 'spawnChildren'");
290
+ });
291
+
292
+ it('should aggregate all logs from all failed workflows', async () => {
293
+ // ARRANGE: Create workflows that log before failing
294
+ class LoggingWorkflow extends Workflow {
295
+ constructor(name: string, parent: Workflow, private shouldFail: boolean) {
296
+ super(name, parent);
297
+ }
298
+
299
+ @Step()
300
+ async executeStep() {
301
+ this.logger.info(`${this.node.name} starting`);
302
+ if (this.shouldFail) {
303
+ this.logger.error(`${this.node.name} failing`);
304
+ throw new Error(`${this.node.name} failed`);
305
+ }
306
+ this.logger.info(`${this.node.name} completed`);
307
+ }
308
+
309
+ async run() {
310
+ return this.executeStep();
311
+ }
312
+ }
313
+
314
+ class ParentWorkflow extends Workflow {
315
+ @Task({
316
+ concurrent: true,
317
+ errorMergeStrategy: { enabled: true },
318
+ })
319
+ async spawnChildren() {
320
+ return [
321
+ new LoggingWorkflow('Workflow-1', this, true),
322
+ new LoggingWorkflow('Workflow-2', this, true),
323
+ new LoggingWorkflow('Workflow-3', this, false),
324
+ ];
325
+ }
326
+
327
+ async run() {
328
+ try {
329
+ await this.spawnChildren();
330
+ } catch (err) {
331
+ return err;
332
+ }
333
+ }
334
+ }
335
+
336
+ const parent = new ParentWorkflow('Parent');
337
+ const thrownError = (await parent.run()) as WorkflowError;
338
+
339
+ // ASSERT: Logs from both failed workflows aggregated
340
+ expect(thrownError.logs).toBeDefined();
341
+ const logMessages = thrownError.logs.map((l) => l.message);
342
+
343
+ // Should have logs from both failing workflows
344
+ expect(logMessages.some((m) => m.includes('Workflow-1'))).toBe(true);
345
+ expect(logMessages.some((m) => m.includes('Workflow-2'))).toBe(true);
346
+ });
347
+
348
+ it('should include metadata in original field', async () => {
349
+ // ARRANGE: Test metadata structure
350
+ class ParentWorkflow extends Workflow {
351
+ @Task({
352
+ concurrent: true,
353
+ errorMergeStrategy: { enabled: true },
354
+ })
355
+ async spawnChildren() {
356
+ return [
357
+ createChildWorkflow(this, 'W1', true),
358
+ createChildWorkflow(this, 'W2', true),
359
+ createChildWorkflow(this, 'W3', true),
360
+ ];
361
+ }
362
+
363
+ async run() {
364
+ try {
365
+ await this.spawnChildren();
366
+ } catch (err) {
367
+ return err;
368
+ }
369
+ }
370
+ }
371
+
372
+ const parent = new ParentWorkflow('Parent');
373
+ const thrownError = (await parent.run()) as WorkflowError;
374
+
375
+ // ASSERT: Metadata structure is correct
376
+ const metadata = thrownError.original as {
377
+ name: string;
378
+ message: string;
379
+ errors: WorkflowError[];
380
+ totalChildren: number;
381
+ failedChildren: number;
382
+ failedWorkflowIds: string[];
383
+ };
384
+
385
+ expect(metadata.name).toBe('WorkflowAggregateError');
386
+ expect(metadata.message).toContain('concurrent child workflows failed');
387
+ expect(metadata.errors).toHaveLength(3);
388
+ expect(metadata.totalChildren).toBe(3);
389
+ expect(metadata.failedChildren).toBe(3);
390
+ expect(metadata.failedWorkflowIds).toHaveLength(3);
391
+
392
+ // ASSERT: Each error in metadata has workflowId
393
+ metadata.failedWorkflowIds.forEach((id) => {
394
+ expect(id).toBeDefined();
395
+ expect(typeof id).toBe('string');
396
+ });
397
+ });
398
+ });
399
+
400
+ describe('Enabled with custom combine function', () => {
401
+ it('should call custom combine function when provided', async () => {
402
+ // ARRANGE: Create spy for combine function
403
+ const combineSpy = vi.fn((errors: WorkflowError[]) => ({
404
+ message: `Custom merge: ${errors.length} errors`,
405
+ original: errors,
406
+ workflowId: 'custom-parent',
407
+ logs: errors.flatMap((e) => e.logs),
408
+ stack: errors[0]?.stack,
409
+ state: errors[0]?.state || {},
410
+ }));
411
+
412
+ class ParentWorkflow extends Workflow {
413
+ @Task({
414
+ concurrent: true,
415
+ errorMergeStrategy: {
416
+ enabled: true,
417
+ combine: combineSpy, // Custom combine function
418
+ },
419
+ })
420
+ async spawnChildren() {
421
+ return [
422
+ createChildWorkflow(this, 'Alpha', true),
423
+ createChildWorkflow(this, 'Beta', true),
424
+ createChildWorkflow(this, 'Gamma', false),
425
+ ];
426
+ }
427
+
428
+ async run() {
429
+ try {
430
+ await this.spawnChildren();
431
+ } catch (err) {
432
+ return err;
433
+ }
434
+ }
435
+ }
436
+
437
+ const parent = new ParentWorkflow('Parent');
438
+
439
+ // ACT
440
+ await parent.run();
441
+
442
+ // ASSERT: Custom combine function was called
443
+ expect(combineSpy).toHaveBeenCalledTimes(1);
444
+
445
+ // ASSERT: Called with array of WorkflowError objects
446
+ const calls = combineSpy.mock.calls;
447
+ expect(calls).toHaveLength(1);
448
+ const errorsArg = calls[0][0] as WorkflowError[];
449
+ expect(Array.isArray(errorsArg)).toBe(true);
450
+ expect(errorsArg).toHaveLength(2); // Alpha and Beta failed
451
+ });
452
+
453
+ it('should use custom merge result from combine function', async () => {
454
+ // ARRANGE: Custom combine that returns specific format
455
+ const customMerger = (errors: WorkflowError[]): WorkflowError => ({
456
+ message: `MERGED: ${errors.map((e) => e.message).join(' | ')}`,
457
+ original: {
458
+ customField: 'custom-value',
459
+ errors,
460
+ },
461
+ workflowId: 'merged-workflow',
462
+ logs: errors.flatMap((e) => e.logs),
463
+ stack: errors[0]?.stack,
464
+ state: errors[0]?.state || {},
465
+ });
466
+
467
+ class ParentWorkflow extends Workflow {
468
+ @Task({
469
+ concurrent: true,
470
+ errorMergeStrategy: {
471
+ enabled: true,
472
+ combine: customMerger,
473
+ },
474
+ })
475
+ async spawnChildren() {
476
+ return [
477
+ createChildWorkflow(this, 'First', true),
478
+ createChildWorkflow(this, 'Second', true),
479
+ ];
480
+ }
481
+
482
+ async run() {
483
+ try {
484
+ await this.spawnChildren();
485
+ } catch (err) {
486
+ return err;
487
+ }
488
+ }
489
+ }
490
+
491
+ const parent = new ParentWorkflow('Parent');
492
+ const thrownError = (await parent.run()) as WorkflowError;
493
+
494
+ // ASSERT: Custom merge result used
495
+ expect(thrownError.message).toBe('MERGED: First failed | Second failed');
496
+ expect(thrownError.workflowId).toBe('merged-workflow');
497
+
498
+ // ASSERT: Custom fields preserved
499
+ const customMetadata = thrownError.original as { customField: string };
500
+ expect(customMetadata.customField).toBe('custom-value');
501
+ });
502
+
503
+ it('should pass all errors to custom combine function', async () => {
504
+ // ARRANGE: Track which errors were passed
505
+ let receivedErrors: WorkflowError[] = [];
506
+
507
+ const trackingMerger = (errors: WorkflowError[]): WorkflowError => {
508
+ receivedErrors = errors;
509
+ return createMockWorkflowError({
510
+ message: `Tracked ${errors.length} errors`,
511
+ });
512
+ };
513
+
514
+ class ParentWorkflow extends Workflow {
515
+ @Task({
516
+ concurrent: true,
517
+ errorMergeStrategy: {
518
+ enabled: true,
519
+ combine: trackingMerger,
520
+ },
521
+ })
522
+ async spawnChildren() {
523
+ return [
524
+ createChildWorkflow(this, 'A', true),
525
+ createChildWorkflow(this, 'B', true),
526
+ createChildWorkflow(this, 'C', true),
527
+ createChildWorkflow(this, 'D', false),
528
+ ];
529
+ }
530
+
531
+ async run() {
532
+ try {
533
+ await this.spawnChildren();
534
+ } catch (err) {
535
+ return err;
536
+ }
537
+ }
538
+ }
539
+
540
+ const parent = new ParentWorkflow('Parent');
541
+ await parent.run();
542
+
543
+ // ASSERT: All failed errors passed to combine
544
+ expect(receivedErrors).toHaveLength(3);
545
+ const errorMessages = receivedErrors.map((e) => e.message);
546
+ expect(errorMessages).toContain('A failed');
547
+ expect(errorMessages).toContain('B failed');
548
+ expect(errorMessages).toContain('C failed');
549
+ });
550
+ });
551
+
552
+ describe('Edge cases and error scenarios', () => {
553
+ it('should handle single failure with merge enabled', async () => {
554
+ class ParentWorkflow extends Workflow {
555
+ @Task({
556
+ concurrent: true,
557
+ errorMergeStrategy: { enabled: true },
558
+ })
559
+ async spawnChildren() {
560
+ return [createChildWorkflow(this, 'OnlyChild', true)];
561
+ }
562
+
563
+ async run() {
564
+ try {
565
+ await this.spawnChildren();
566
+ } catch (err) {
567
+ return err;
568
+ }
569
+ }
570
+ }
571
+
572
+ const parent = new ParentWorkflow('Parent');
573
+ const thrownError = (await parent.run()) as WorkflowError;
574
+
575
+ // ASSERT: Message format correct for single failure
576
+ expect(thrownError.message).toBe("1 of 1 concurrent child workflows failed in task 'spawnChildren'");
577
+ });
578
+
579
+ it('should handle all workflows failing with merge enabled', async () => {
580
+ class ParentWorkflow extends Workflow {
581
+ @Task({
582
+ concurrent: true,
583
+ errorMergeStrategy: { enabled: true },
584
+ })
585
+ async spawnChildren() {
586
+ return [
587
+ createChildWorkflow(this, 'W1', true),
588
+ createChildWorkflow(this, 'W2', true),
589
+ createChildWorkflow(this, 'W3', true),
590
+ createChildWorkflow(this, 'W4', true),
591
+ ];
592
+ }
593
+
594
+ async run() {
595
+ try {
596
+ await this.spawnChildren();
597
+ } catch (err) {
598
+ return err;
599
+ }
600
+ }
601
+ }
602
+
603
+ const parent = new ParentWorkflow('Parent');
604
+ const thrownError = (await parent.run()) as WorkflowError;
605
+
606
+ // ASSERT: All failures counted
607
+ expect(thrownError.message).toBe("4 of 4 concurrent child workflows failed in task 'spawnChildren'");
608
+
609
+ // ASSERT: All workflows completed
610
+ expect(parent.children.length).toBe(4);
611
+ });
612
+
613
+ it('should handle mixed success/failure with merge enabled', async () => {
614
+ class ParentWorkflow extends Workflow {
615
+ @Task({
616
+ concurrent: true,
617
+ errorMergeStrategy: { enabled: true },
618
+ })
619
+ async spawnChildren() {
620
+ return [
621
+ createChildWorkflow(this, 'Success1', false),
622
+ createChildWorkflow(this, 'Fail1', true),
623
+ createChildWorkflow(this, 'Success2', false),
624
+ createChildWorkflow(this, 'Fail2', true),
625
+ createChildWorkflow(this, 'Success3', false),
626
+ ];
627
+ }
628
+
629
+ async run() {
630
+ try {
631
+ await this.spawnChildren();
632
+ } catch (err) {
633
+ return err;
634
+ }
635
+ }
636
+ }
637
+
638
+ const parent = new ParentWorkflow('Parent');
639
+ const thrownError = (await parent.run()) as WorkflowError;
640
+
641
+ // ASSERT: Only failed children counted in message
642
+ expect(thrownError.message).toBe("2 of 5 concurrent child workflows failed in task 'spawnChildren'");
643
+
644
+ // ASSERT: All workflows completed
645
+ expect(parent.children.length).toBe(5);
646
+
647
+ // ASSERT: Metadata correct
648
+ const metadata = thrownError.original as { failedChildren: number; totalChildren: number };
649
+ expect(metadata.failedChildren).toBe(2);
650
+ expect(metadata.totalChildren).toBe(5);
651
+ });
652
+
653
+ it('should complete all workflows even when errors occur', async () => {
654
+ const completedWorkflows = new Set<string>();
655
+
656
+ class ParentWorkflow extends Workflow {
657
+ @Task({
658
+ concurrent: true,
659
+ errorMergeStrategy: { enabled: true },
660
+ })
661
+ async spawnChildren() {
662
+ const children = [
663
+ createChildWorkflow(this, 'Success1', false),
664
+ createChildWorkflow(this, 'Fail1', true),
665
+ createChildWorkflow(this, 'Success2', false),
666
+ createChildWorkflow(this, 'Fail2', true),
667
+ ];
668
+
669
+ // Track completion
670
+ children.forEach((child) => {
671
+ child.run().then(
672
+ () => completedWorkflows.add(child.id),
673
+ () => completedWorkflows.add(child.id)
674
+ );
675
+ });
676
+
677
+ return children;
678
+ }
679
+
680
+ async run() {
681
+ try {
682
+ await this.spawnChildren();
683
+ } catch (err) {
684
+ // Expected
685
+ }
686
+ }
687
+ }
688
+
689
+ const parent = new ParentWorkflow('Parent');
690
+ await parent.run();
691
+
692
+ // ASSERT: All workflows completed (no orphans)
693
+ expect(completedWorkflows.size).toBe(4);
694
+ expect(parent.children.length).toBe(4);
695
+ });
696
+
697
+ it('should emit error event with merged error', async () => {
698
+ class ParentWorkflow extends Workflow {
699
+ @Task({
700
+ concurrent: true,
701
+ errorMergeStrategy: { enabled: true },
702
+ })
703
+ async spawnChildren() {
704
+ return [
705
+ createChildWorkflow(this, 'OK', false),
706
+ createChildWorkflow(this, 'Bad1', true),
707
+ createChildWorkflow(this, 'Bad2', true),
708
+ ];
709
+ }
710
+
711
+ async run() {
712
+ try {
713
+ await this.spawnChildren();
714
+ } catch (err) {
715
+ return err;
716
+ }
717
+ }
718
+ }
719
+
720
+ const parent = new ParentWorkflow('Parent');
721
+ const events = setupEventObserver(parent);
722
+ await parent.run();
723
+
724
+ // ASSERT: Error events emitted for individual failures
725
+ const individualErrorEvents = events.filter((e) => {
726
+ if (e.type === 'error') {
727
+ return e.error.message === 'Bad1 failed' || e.error.message === 'Bad2 failed';
728
+ }
729
+ return false;
730
+ });
731
+ expect(individualErrorEvents.length).toBeGreaterThanOrEqual(2);
732
+
733
+ // ASSERT: Additional merged error event emitted
734
+ const mergedErrorEvent = events.find((e) => {
735
+ if (e.type === 'error') {
736
+ return e.error.message.includes('concurrent child workflows failed');
737
+ }
738
+ return false;
739
+ });
740
+ expect(mergedErrorEvent).toBeDefined();
741
+
742
+ // ASSERT: Merged error has correct structure
743
+ if (mergedErrorEvent && mergedErrorEvent.type === 'error') {
744
+ expect(mergedErrorEvent.error.message).toContain('2 of 3');
745
+ expect(mergedErrorEvent.error.workflowId).toBeDefined();
746
+ expect(Array.isArray(mergedErrorEvent.error.logs)).toBe(true);
747
+ }
748
+ });
749
+ });
750
+
751
+ describe.skip('maxMergeDepth validation', () => {
752
+ // NOTE: maxMergeDepth is defined in ErrorMergeStrategy interface
753
+ // but not currently implemented in src/decorators/task.ts
754
+ // These tests should be implemented when maxMergeDepth is added
755
+
756
+ it('should respect maxMergeDepth when merging nested errors');
757
+ it('should handle maxMergeDepth=0 (no merging)');
758
+ it('should handle maxMergeDepth=1 (single level merging)');
759
+ });
760
+ });