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.
- package/.claude/commands/subtask-planning/prp-base-create.md +120 -0
- package/.claude/commands/subtask-planning/prp-base-execute.md +65 -0
- package/.claude/commands/task-breakdown.md +94 -0
- package/.claude/system_prompts/task-breakdown.md +1 -0
- package/CHANGELOG.md +188 -0
- package/PRD.md +543 -0
- package/README.md +99 -5
- package/examples/README.md +15 -1
- package/examples/examples/11-reparenting-workflows.ts +269 -0
- package/examples/index.ts +4 -0
- package/package-lock.json +2398 -0
- package/package.json +3 -1
- package/plan/001_d3bb02af4886/TEST_RESULTS.md +259 -0
- package/plan/001_d3bb02af4886/bug_fix_tasks.json +484 -0
- package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/P1M1T1S1/PRP.md +488 -0
- package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/P1M1T1S2/PRP.md +581 -0
- package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/P1M1T1S3/PRP.md +687 -0
- package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/P1M2T1S1/PRP.md +492 -0
- package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/P1M2T1S3/PRP.md +932 -0
- package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/P1M2T1S3/research/concurrent_error_testing_patterns.md +1109 -0
- package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/P1M2T1S3/research/vitest_concurrent_testing.md +802 -0
- package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/P1M2T1S3/research/workflow_engine_test_references.md +603 -0
- package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/P1M2T2S1/PRP.md +564 -0
- package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/P1M2T2S3/PRP.md +518 -0
- package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/P1M2T2S4/PRP.md +1252 -0
- package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/P1M2T3S1/PRP.md +364 -0
- package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/P1M2T3S1/research/CODEBASE_INVENTORY.md +114 -0
- package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/P1M2T3S1/research/DECORATOR_DOCUMENTATION_PATTERNS.md +205 -0
- package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/P1M2T3S1/research/PRD_LOCATION_ANALYSIS.md +199 -0
- package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/P1M2T3S1/research/ULTRATHINK_PRP_PLAN.md +134 -0
- package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/P1M3T1S1/PRP.md +495 -0
- package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/P1M3T1S1/research/console_error_inventory.md +435 -0
- package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/P1M3T1S2/PRP.md +506 -0
- package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/P1M3T1S3/PRP.md +612 -0
- package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/P1M3T2S2/PRP.md +558 -0
- package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/P1M3T2S2/research/external_research.md +788 -0
- package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/P1M3T3S2/PRP.md +460 -0
- package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/P1M3T3S3/PRP.md +454 -0
- package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/P1M3T4S1/PRP.md +520 -0
- package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/P1M3T4S1/RECOMMENDATION.md +417 -0
- package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/P1M3T4S1/research/external_workflow_engines_research.md +760 -0
- package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/P1M3T4S1/research/security_implications_analysis.md +245 -0
- package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/P1M3T4S2/PRP.md +792 -0
- package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/P1M4T1S1/PRP.md +535 -0
- package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/P1M4T1S1/TEST_EXECUTION_REPORT.md +190 -0
- package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/P1M4T1S2/PRP.md +654 -0
- package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/P1M4T1S2/TEST_FIX_REPORT.md +227 -0
- package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/P1M4T1S2/research/KEY_FINDINGS.md +345 -0
- package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/P1M4T1S2/research/QUICK_REFERENCE.md +193 -0
- package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/P1M4T1S2/research/test_maintenance_research.md +1323 -0
- package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/P1M4T3S1/BREAKING_CHANGES_AUDIT.md +1011 -0
- package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/P1M4T3S1/PRP.md +927 -0
- package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/P1M4T3S2/PRP.md +505 -0
- package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/architecture/logger_child_signature_analysis.md +401 -0
- package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/docs/P1M1T1S3/child_implementation_research.md +142 -0
- package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/docs/P1M1T1S3/test_patterns_research.md +112 -0
- package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/docs/P1M1T1S3/vitest_patterns_research.md +159 -0
- package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/docs/P1M1T1S4/PRP.md +549 -0
- package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/docs/P1M1T1S4/VERIFICATION_REPORT.md +368 -0
- package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/docs/P1M1T1S4/edge_case_analysis.md +172 -0
- package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/docs/P1M1T1S4/usage_inventory.md +175 -0
- package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/docs/P1M2T1S2/PRP.md +696 -0
- package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/docs/P1M2T1S4/PRP.md +860 -0
- package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/docs/P1M2T2S2/PRP.md +1066 -0
- package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/docs/P1M2T2S2/research/01-testing-aggregated-errors.md +1103 -0
- package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/docs/P1M2T2S2/research/01_typescript_error_aggregation_patterns.md +789 -0
- package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/docs/P1M2T2S2/research/02-error-merge-strategy-testing-guide.md +1098 -0
- package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/docs/P1M2T2S2/research/02_aggregate_error_patterns.md +1037 -0
- package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/docs/P1M2T2S2/research/03-promise-allsettled-testing-patterns.md +916 -0
- package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/docs/P1M2T2S2/research/03_error_merging_strategies.md +1045 -0
- package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/docs/P1M2T2S2/research/04_github_stackoverflow_examples.md +890 -0
- package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/docs/P1M2T2S2/research/05_comprehensive_summary.md +822 -0
- package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/docs/P1M2T2S2/research/INDEX.md +668 -0
- package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/docs/P1M2T2S2/research/QUICK_REFERENCE.md +706 -0
- package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/docs/P1M2T2S2/research/README.md +265 -0
- package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/docs/P1M2T2S2/research/RESEARCH_REPORT.md +655 -0
- package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/docs/P1M2T2S4/research/vitest_testing_patterns.md +1103 -0
- package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/docs/P1M2T3S2/PRP.md +426 -0
- package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/docs/P1M3T1S2/PRP.md +506 -0
- package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/docs/P1M3T1S2/research/QUICK_REFERENCE.md +114 -0
- package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/docs/P1M3T1S2/research/RESEARCH_SUMMARY.md +316 -0
- package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/docs/P1M3T1S2/research/vitest_observer_error_logging_best_practices.md +754 -0
- package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/docs/P1M3T1S3/PRP.md +612 -0
- package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/docs/P1M3T2S1/PRP.md +719 -0
- package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/docs/P1M3T2S1/README.md +215 -0
- package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/docs/P1M3T2S1/analysis.md +765 -0
- package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/docs/P1M3T2S3/PRP.md +718 -0
- package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/docs/P1M3T3S1/DECISION.md +149 -0
- package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/docs/P1M3T3S1/PRP.md +470 -0
- package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/docs/P1M3T3S1/research/ULTRATHINK_PLAN.md +332 -0
- package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/docs/P1M3T3S1/research/codebase_workflow_name_analysis.md +167 -0
- package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/docs/P1M3T3S1/research/external_best_practices.md +265 -0
- package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/docs/P1M3T3S1/research/validation_patterns.md +273 -0
- package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/docs/P1M3T4S1/workflow_engine_ancestry_api_research.md +760 -0
- package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/docs/P1M3T4S3-PRP.md +434 -0
- package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/docs/P1M4T2S1/PRP.md +717 -0
- package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/docs/P1M4T2S2/PRP.md +472 -0
- package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/docs/P1M4T2S2/VALIDATION_REPORT.md +125 -0
- package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/docs/P1M4T2S2/research/ULTRATHINK_PRP_PLAN.md +301 -0
- package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/docs/error-logging-best-practices.md +1170 -0
- package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/docs/research_typescript_partial_and_overloads.md +940 -0
- package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/docs/vitest-quick-reference.md +151 -0
- package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/docs/vitest-research.md +650 -0
- package/plan/001_d3bb02af4886/bugfix/001_e8e04329daf3/prd_snapshot.md +259 -0
- package/plan/001_d3bb02af4886/bugfix/P1M1T1S1/PRP.md +457 -0
- package/plan/001_d3bb02af4886/bugfix/RESEARCH_SUMMARY.md +346 -0
- package/plan/001_d3bb02af4886/bugfix/architecture/codebase_structure.md +311 -0
- package/plan/001_d3bb02af4886/bugfix/architecture/concurrent_execution_best_practices.md +1565 -0
- package/plan/001_d3bb02af4886/bugfix/architecture/error_handling_patterns.md +288 -0
- package/plan/001_d3bb02af4886/bugfix/architecture/promise_all_analysis.md +741 -0
- package/plan/001_d3bb02af4886/docs/PRP/P1M1T1S4-functional-workflow-error-state-capture-test.md +652 -0
- package/plan/001_d3bb02af4886/docs/PRP/PRP.md +527 -0
- package/plan/001_d3bb02af4886/docs/PRP/bugfix/P1M1T2S1-PRP.md +415 -0
- package/plan/001_d3bb02af4886/docs/PRP/bugfix/P1M1T2S2-PRP.md +378 -0
- package/plan/001_d3bb02af4886/docs/PRP/bugfix/P1M1T2S4-PRP.md +713 -0
- package/plan/001_d3bb02af4886/docs/PRP/bugfix/P1M2T1S4-PRP.md +370 -0
- package/plan/001_d3bb02af4886/docs/PRP_P1M3T1S3.md +499 -0
- package/plan/001_d3bb02af4886/docs/TEST_RESULTS.md +230 -0
- package/plan/001_d3bb02af4886/docs/bugfix/ANALYSIS_PRD_VS_IMPLEMENTATION.md +1134 -0
- package/plan/001_d3bb02af4886/docs/bugfix/GAP_ANALYSIS_SUMMARY.md +179 -0
- package/plan/001_d3bb02af4886/docs/bugfix/P1M4T2S1/PRP.md +629 -0
- package/plan/001_d3bb02af4886/docs/bugfix/P1M4T2S1/validation-report.md +214 -0
- package/plan/001_d3bb02af4886/docs/bugfix/PRP_P1M4T2S3.md +629 -0
- package/plan/001_d3bb02af4886/docs/bugfix/bugfix_PRP.md +529 -0
- package/plan/001_d3bb02af4886/docs/bugfix/bugfix_QUICK_REFERENCE.md +142 -0
- package/plan/001_d3bb02af4886/docs/bugfix/bugfix_README.md +304 -0
- package/plan/001_d3bb02af4886/docs/bugfix/bugfix_TEST_RESULTS.md +558 -0
- package/plan/001_d3bb02af4886/docs/bugfix/bugfix_VALIDATION_SUMMARY.md +256 -0
- package/plan/001_d3bb02af4886/docs/bugfix/system_context.md +346 -0
- package/plan/001_d3bb02af4886/docs/bugfix-architecture/bug_analysis.md +415 -0
- package/plan/001_d3bb02af4886/docs/bugfix-architecture/implementation_patterns.md +489 -0
- package/plan/001_d3bb02af4886/docs/bugfix-architecture/system_context.md +218 -0
- package/plan/001_d3bb02af4886/docs/bugfix_INITIATION_SUMMARY.md +380 -0
- package/plan/001_d3bb02af4886/docs/research/CYCLE_DETECTION_PATTERNS.md +1923 -0
- package/plan/001_d3bb02af4886/docs/research/CYCLE_DETECTION_QUICK_REF.md +319 -0
- package/plan/001_d3bb02af4886/docs/research/P1M1T2S1/codebase-context.md +115 -0
- package/plan/001_d3bb02af4886/docs/research/P1M1T2S1/cycle-detection-algorithms.md +134 -0
- package/plan/001_d3bb02af4886/docs/research/P1M1T2S1/test-patterns.md +153 -0
- package/plan/001_d3bb02af4886/docs/research/P1M1T2S1/workflow-class.md +132 -0
- package/plan/001_d3bb02af4886/docs/research/P1M2T1S4/DECORATOR_DOCUMENTATION_BEST_PRACTICES.md +716 -0
- package/plan/001_d3bb02af4886/docs/research/P1M2T1S4/DECORATOR_DOCUMENTATION_QUICK_REF.md +186 -0
- package/plan/001_d3bb02af4886/docs/research/P1M2T1S4/GROUNDSWELL_DECORATOR_EXAMPLES.md +604 -0
- package/plan/001_d3bb02af4886/docs/research/P1M2T1S4/INDEX.md +213 -0
- package/plan/001_d3bb02af4886/docs/research/P1M2T1S4/codebase_structure.md +30 -0
- package/plan/001_d3bb02af4886/docs/research/P1M2T1S4/existing_test_pattern.md +56 -0
- package/plan/001_d3bb02af4886/docs/research/P1M2T1S4/getRootObservers_implementation.md +53 -0
- package/plan/001_d3bb02af4886/docs/research/P1M2T1S4/test_conventions.md +49 -0
- package/plan/001_d3bb02af4886/docs/research/P1M3T1S4/PRP.md +958 -0
- package/plan/001_d3bb02af4886/docs/research/P1M3T1S4/QUICK_REFERENCE.md +339 -0
- package/plan/001_d3bb02af4886/docs/research/P1M3T1S4/README.md +305 -0
- package/plan/001_d3bb02af4886/docs/research/P1M3T1S4/SUMMARY.md +433 -0
- package/plan/001_d3bb02af4886/docs/research/P1M3T1S4/bidirectional-tree-consistency-testing.md +1574 -0
- package/plan/001_d3bb02af4886/docs/research/P1M3T1S4/test-pattern-examples.md +1014 -0
- package/plan/001_d3bb02af4886/docs/research/PROMISE_ALLSETTLED_QUICK_REF.md +376 -0
- package/plan/001_d3bb02af4886/docs/research/PROMISE_ALLSETTLED_RESEARCH.md +1507 -0
- package/plan/001_d3bb02af4886/docs/research/bugfix_typescript_patterns.md +949 -0
- package/plan/001_d3bb02af4886/docs/research/error-testing-research.md +619 -0
- package/plan/001_d3bb02af4886/docs/research/error_handling_patterns.md +723 -0
- package/plan/{research → 001_d3bb02af4886/docs/research/general}/introspection-security-guide.md +56 -0
- package/plan/001_d3bb02af4886/docs/research/incremental-tree-map-updates/PRP_TEMPLATE.md +460 -0
- package/plan/001_d3bb02af4886/docs/research/incremental-tree-map-updates/QUICK_REFERENCE.md +324 -0
- package/plan/001_d3bb02af4886/docs/research/incremental-tree-map-updates/README.md +175 -0
- package/plan/001_d3bb02af4886/docs/research/incremental-tree-map-updates/RESEARCH_REPORT.md +499 -0
- package/plan/001_d3bb02af4886/docs/research/incremental-tree-map-updates/SUMMARY.md +163 -0
- package/plan/bugfix/BUG_FIX_SUMMARY.md +961 -0
- package/src/__tests__/adversarial/attachChild-performance.test.ts +216 -0
- package/src/__tests__/adversarial/circular-reference.test.ts +101 -0
- package/src/__tests__/adversarial/complex-circular-reference.test.ts +139 -0
- package/src/__tests__/adversarial/concurrent-task-failures.test.ts +571 -0
- package/src/__tests__/adversarial/deep-analysis.test.ts +729 -0
- package/src/__tests__/adversarial/deep-hierarchy-stress.test.ts +213 -0
- package/src/__tests__/adversarial/e2e-prd-validation.test.ts +448 -0
- package/src/__tests__/adversarial/edge-case.test.ts +703 -0
- package/src/__tests__/adversarial/error-merge-strategy.test.ts +760 -0
- package/src/__tests__/adversarial/incremental-performance.test.ts +140 -0
- package/src/__tests__/adversarial/node-map-update-benchmarks.test.ts +457 -0
- package/src/__tests__/adversarial/observer-propagation.test.ts +487 -0
- package/src/__tests__/adversarial/parent-validation.test.ts +143 -0
- package/src/__tests__/adversarial/prd-12-2-compliance.test.ts +611 -0
- package/src/__tests__/adversarial/prd-compliance.test.ts +731 -0
- package/src/__tests__/compatibility/backward-compatibility.test.ts +1572 -0
- package/src/__tests__/helpers/index.ts +18 -0
- package/src/__tests__/helpers/tree-verification.ts +257 -0
- package/src/__tests__/integration/bidirectional-consistency.test.ts +847 -0
- package/src/__tests__/integration/observer-logging.test.ts +643 -0
- package/src/__tests__/integration/tree-mirroring.test.ts +37 -0
- package/src/__tests__/integration/workflow-reparenting.test.ts +303 -0
- package/src/__tests__/unit/context.test.ts +79 -0
- package/src/__tests__/unit/logger.test.ts +293 -0
- package/src/__tests__/unit/observable.test.ts +321 -0
- package/src/__tests__/unit/tree-debugger-incremental.test.ts +170 -0
- package/src/__tests__/unit/utils/workflow-error-utils.test.ts +209 -0
- package/src/__tests__/unit/workflow-detachChild.test.ts +100 -0
- package/src/__tests__/unit/workflow-emitEvent-childDetached.test.ts +153 -0
- package/src/__tests__/unit/workflow-isDescendantOf.test.ts +180 -0
- package/src/__tests__/unit/workflow.test.ts +277 -1
- package/src/core/agent.ts +21 -1
- package/src/core/logger.ts +27 -2
- package/src/core/workflow-context.ts +6 -4
- package/src/core/workflow.ts +252 -14
- package/src/debugger/tree-debugger.ts +52 -7
- package/src/decorators/task.ts +65 -2
- package/src/index.ts +4 -2
- package/src/types/decorators.ts +8 -1
- package/src/types/events.ts +1 -0
- package/src/utils/index.ts +1 -0
- package/src/utils/observable.ts +32 -3
- package/src/utils/workflow-error-utils.ts +56 -0
- package/tsconfig.json +1 -1
- package/llms_full.txt +0 -5890
- package/tasks.json +0 -0
- /package/plan/{backlog.json → 001_d3bb02af4886/backlog.json} +0 -0
- /package/plan/{P1P2/PRP.md → 001_d3bb02af4886/docs/PRP/P1P2-PRP.md} +0 -0
- /package/plan/{P3P4/PRP.md → 001_d3bb02af4886/docs/PRP/P3P4-PRP.md} +0 -0
- /package/plan/{P4P5/PRP.md → 001_d3bb02af4886/docs/PRP/P4P5-PRP.md} +0 -0
- /package/plan/{architecture → 001_d3bb02af4886/docs/architecture}/external_deps.md +0 -0
- /package/plan/{architecture → 001_d3bb02af4886/docs/architecture}/system_context.md +0 -0
- /package/plan/{P1P2/research → 001_d3bb02af4886/docs/research/P1P2}/LRU_CACHE_BEST_PRACTICES.md +0 -0
- /package/plan/{P1P2/research → 001_d3bb02af4886/docs/research/P1P2}/LRU_CACHE_CODE_PATTERNS.md +0 -0
- /package/plan/{P1P2/research → 001_d3bb02af4886/docs/research/P1P2}/LRU_CACHE_INTEGRATION_GUIDE.md +0 -0
- /package/plan/{P1P2/research → 001_d3bb02af4886/docs/research/P1P2}/LRU_CACHE_RESEARCH_INDEX.md +0 -0
- /package/plan/{P1P2/research → 001_d3bb02af4886/docs/research/P1P2}/REFLECTION_INDEX.md +0 -0
- /package/plan/{P1P2/research → 001_d3bb02af4886/docs/research/P1P2}/REFLECTION_RESEARCH_REPORT.md +0 -0
- /package/plan/{P1P2/research → 001_d3bb02af4886/docs/research/P1P2}/RESEARCH_SUMMARY.md +0 -0
- /package/plan/{P1P2/research → 001_d3bb02af4886/docs/research/P1P2}/anthropic-sdk.md +0 -0
- /package/plan/{P1P2/research → 001_d3bb02af4886/docs/research/P1P2}/async-local-storage.md +0 -0
- /package/plan/{P1P2/research → 001_d3bb02af4886/docs/research/P1P2}/reflection-code-patterns.md +0 -0
- /package/plan/{P1P2/research → 001_d3bb02af4886/docs/research/P1P2}/reflection-decision-matrix.md +0 -0
- /package/plan/{P1P2/research → 001_d3bb02af4886/docs/research/P1P2}/reflection-implementation-guide.md +0 -0
- /package/plan/{P1P2/research → 001_d3bb02af4886/docs/research/P1P2}/reflection-integration-guide.md +0 -0
- /package/plan/{P1P2/research → 001_d3bb02af4886/docs/research/P1P2}/reflection-patterns.md +0 -0
- /package/plan/{P1P2/research → 001_d3bb02af4886/docs/research/P1P2}/reflection-quick-reference.md +0 -0
- /package/plan/{P1P2/research → 001_d3bb02af4886/docs/research/P1P2}/zod-schema.md +0 -0
- /package/plan/{P3P4/research → 001_d3bb02af4886/docs/research/P3P4}/caching-lru.md +0 -0
- /package/plan/{P3P4/research → 001_d3bb02af4886/docs/research/P3P4}/introspection-tools.md +0 -0
- /package/plan/{P3P4/research → 001_d3bb02af4886/docs/research/P3P4}/reflection-patterns.md +0 -0
- /package/plan/{P4P5/research → 001_d3bb02af4886/docs/research/P4P5}/RESEARCH_SUMMARY.md +0 -0
- /package/plan/{research → 001_d3bb02af4886/docs/research/general}/INTROSPECTION_RESEARCH_SUMMARY.md +0 -0
- /package/plan/{research → 001_d3bb02af4886/docs/research/general}/README-INTROSPECTION.md +0 -0
- /package/plan/{research → 001_d3bb02af4886/docs/research/general}/agent-introspection-patterns.md +0 -0
- /package/plan/{research → 001_d3bb02af4886/docs/research/general}/introspection-tool-examples.md +0 -0
- /package/{PRPs/PRDs/001-hierarchical-workflow-engine.md → plan/001_d3bb02af4886/prd_snapshot.md} +0 -0
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import { Observable, ObservableLogger } from '../../utils/observable';
|
|
3
|
+
|
|
4
|
+
describe('Observable', () => {
|
|
5
|
+
describe('with logger injection', () => {
|
|
6
|
+
let mockLogger: ObservableLogger;
|
|
7
|
+
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
mockLogger = {
|
|
10
|
+
error: vi.fn(),
|
|
11
|
+
};
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('should log subscriber next() errors via logger', () => {
|
|
15
|
+
const observable = new Observable<number>(mockLogger);
|
|
16
|
+
const testError = new Error('Next error');
|
|
17
|
+
|
|
18
|
+
const throwingSubscriber = {
|
|
19
|
+
next: () => {
|
|
20
|
+
throw testError;
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
observable.subscribe(throwingSubscriber);
|
|
25
|
+
observable.next(42);
|
|
26
|
+
|
|
27
|
+
expect(mockLogger.error).toHaveBeenCalledWith('Observable subscriber error', {
|
|
28
|
+
error: testError,
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('should log subscriber error() errors via logger', () => {
|
|
33
|
+
const observable = new Observable<number>(mockLogger);
|
|
34
|
+
const testError = new Error('Error handler failed');
|
|
35
|
+
const handlerError = new Error('Handler threw');
|
|
36
|
+
|
|
37
|
+
const throwingSubscriber = {
|
|
38
|
+
error: () => {
|
|
39
|
+
throw handlerError;
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
observable.subscribe(throwingSubscriber);
|
|
44
|
+
observable.error(testError);
|
|
45
|
+
|
|
46
|
+
expect(mockLogger.error).toHaveBeenCalledWith('Observable error handler failed', {
|
|
47
|
+
error: handlerError,
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('should log subscriber complete() errors via logger', () => {
|
|
52
|
+
const observable = new Observable<number>(mockLogger);
|
|
53
|
+
const completeError = new Error('Complete handler failed');
|
|
54
|
+
|
|
55
|
+
const throwingSubscriber = {
|
|
56
|
+
complete: () => {
|
|
57
|
+
throw completeError;
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
observable.subscribe(throwingSubscriber);
|
|
62
|
+
observable.complete();
|
|
63
|
+
|
|
64
|
+
expect(mockLogger.error).toHaveBeenCalledWith('Observable complete handler failed', {
|
|
65
|
+
error: completeError,
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('should continue notifying other subscribers after one throws', () => {
|
|
70
|
+
const observable = new Observable<number>(mockLogger);
|
|
71
|
+
const testError = new Error('First subscriber error');
|
|
72
|
+
|
|
73
|
+
const results: number[] = [];
|
|
74
|
+
|
|
75
|
+
const throwingSubscriber = {
|
|
76
|
+
next: () => {
|
|
77
|
+
throw testError;
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const workingSubscriber = {
|
|
82
|
+
next: (value: number) => {
|
|
83
|
+
results.push(value);
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
observable.subscribe(throwingSubscriber);
|
|
88
|
+
observable.subscribe(workingSubscriber);
|
|
89
|
+
observable.next(42);
|
|
90
|
+
|
|
91
|
+
expect(results).toEqual([42]);
|
|
92
|
+
expect(mockLogger.error).toHaveBeenCalled();
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('should handle multiple throwing subscribers', () => {
|
|
96
|
+
const observable = new Observable<number>(mockLogger);
|
|
97
|
+
const error1 = new Error('Error 1');
|
|
98
|
+
const error2 = new Error('Error 2');
|
|
99
|
+
|
|
100
|
+
const throwingSubscriber1 = {
|
|
101
|
+
next: () => {
|
|
102
|
+
throw error1;
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const throwingSubscriber2 = {
|
|
107
|
+
next: () => {
|
|
108
|
+
throw error2;
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
observable.subscribe(throwingSubscriber1);
|
|
113
|
+
observable.subscribe(throwingSubscriber2);
|
|
114
|
+
observable.next(42);
|
|
115
|
+
|
|
116
|
+
expect(mockLogger.error).toHaveBeenCalledTimes(2);
|
|
117
|
+
expect(mockLogger.error).toHaveBeenNthCalledWith(1, 'Observable subscriber error', {
|
|
118
|
+
error: error1,
|
|
119
|
+
});
|
|
120
|
+
expect(mockLogger.error).toHaveBeenNthCalledWith(2, 'Observable subscriber error', {
|
|
121
|
+
error: error2,
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
describe('without logger (fallback)', () => {
|
|
127
|
+
it('should fall back to console.error when no logger provided', () => {
|
|
128
|
+
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
129
|
+
const observable = new Observable<number>();
|
|
130
|
+
const testError = new Error('Next error');
|
|
131
|
+
|
|
132
|
+
const throwingSubscriber = {
|
|
133
|
+
next: () => {
|
|
134
|
+
throw testError;
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
observable.subscribe(throwingSubscriber);
|
|
139
|
+
observable.next(42);
|
|
140
|
+
|
|
141
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith('Observable subscriber error', testError);
|
|
142
|
+
consoleErrorSpy.mockRestore();
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('should fall back to console.error for error() method', () => {
|
|
146
|
+
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
147
|
+
const observable = new Observable<number>();
|
|
148
|
+
const handlerError = new Error('Handler threw');
|
|
149
|
+
|
|
150
|
+
const throwingSubscriber = {
|
|
151
|
+
error: () => {
|
|
152
|
+
throw handlerError;
|
|
153
|
+
},
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
observable.subscribe(throwingSubscriber);
|
|
157
|
+
observable.error(new Error('Original error'));
|
|
158
|
+
|
|
159
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith('Observable error handler failed', handlerError);
|
|
160
|
+
consoleErrorSpy.mockRestore();
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('should fall back to console.error for complete() method', () => {
|
|
164
|
+
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
165
|
+
const observable = new Observable<number>();
|
|
166
|
+
const completeError = new Error('Complete threw');
|
|
167
|
+
|
|
168
|
+
const throwingSubscriber = {
|
|
169
|
+
complete: () => {
|
|
170
|
+
throw completeError;
|
|
171
|
+
},
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
observable.subscribe(throwingSubscriber);
|
|
175
|
+
observable.complete();
|
|
176
|
+
|
|
177
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith('Observable complete handler failed', completeError);
|
|
178
|
+
consoleErrorSpy.mockRestore();
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
describe('backward compatibility', () => {
|
|
183
|
+
it('should work without any constructor arguments', () => {
|
|
184
|
+
const observable = new Observable<number>();
|
|
185
|
+
const results: number[] = [];
|
|
186
|
+
|
|
187
|
+
const subscriber = {
|
|
188
|
+
next: (value: number) => {
|
|
189
|
+
results.push(value);
|
|
190
|
+
},
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
observable.subscribe(subscriber);
|
|
194
|
+
observable.next(1);
|
|
195
|
+
observable.next(2);
|
|
196
|
+
observable.next(3);
|
|
197
|
+
|
|
198
|
+
expect(results).toEqual([1, 2, 3]);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it('should support subscribe/unsubscribe cycle', () => {
|
|
202
|
+
const observable = new Observable<number>();
|
|
203
|
+
const results: number[] = [];
|
|
204
|
+
|
|
205
|
+
const subscriber = {
|
|
206
|
+
next: (value: number) => {
|
|
207
|
+
results.push(value);
|
|
208
|
+
},
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
const subscription = observable.subscribe(subscriber);
|
|
212
|
+
observable.next(1);
|
|
213
|
+
subscription.unsubscribe();
|
|
214
|
+
observable.next(2);
|
|
215
|
+
|
|
216
|
+
expect(results).toEqual([1]);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it('should report subscriber count correctly', () => {
|
|
220
|
+
const observable = new Observable<number>();
|
|
221
|
+
|
|
222
|
+
const subscriber1 = { next: () => {} };
|
|
223
|
+
const subscriber2 = { next: () => {} };
|
|
224
|
+
|
|
225
|
+
expect(observable.subscriberCount).toBe(0);
|
|
226
|
+
|
|
227
|
+
const sub1 = observable.subscribe(subscriber1);
|
|
228
|
+
expect(observable.subscriberCount).toBe(1);
|
|
229
|
+
|
|
230
|
+
const sub2 = observable.subscribe(subscriber2);
|
|
231
|
+
expect(observable.subscriberCount).toBe(2);
|
|
232
|
+
|
|
233
|
+
sub1.unsubscribe();
|
|
234
|
+
expect(observable.subscriberCount).toBe(1);
|
|
235
|
+
|
|
236
|
+
sub2.unsubscribe();
|
|
237
|
+
expect(observable.subscriberCount).toBe(0);
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it('should clear subscribers on complete', () => {
|
|
241
|
+
const observable = new Observable<number>();
|
|
242
|
+
const subscriber = { next: () => {}, complete: () => {} };
|
|
243
|
+
|
|
244
|
+
observable.subscribe(subscriber);
|
|
245
|
+
expect(observable.subscriberCount).toBe(1);
|
|
246
|
+
|
|
247
|
+
observable.complete();
|
|
248
|
+
expect(observable.subscriberCount).toBe(0);
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
describe('error isolation', () => {
|
|
253
|
+
let mockLogger: ObservableLogger;
|
|
254
|
+
|
|
255
|
+
beforeEach(() => {
|
|
256
|
+
mockLogger = {
|
|
257
|
+
error: vi.fn(),
|
|
258
|
+
};
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
it('should not propagate errors outside try-catch', () => {
|
|
262
|
+
const observable = new Observable<number>(mockLogger);
|
|
263
|
+
const testError = new Error('Subscriber error');
|
|
264
|
+
|
|
265
|
+
const throwingSubscriber = {
|
|
266
|
+
next: () => {
|
|
267
|
+
throw testError;
|
|
268
|
+
},
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
observable.subscribe(throwingSubscriber);
|
|
272
|
+
|
|
273
|
+
// This should not throw
|
|
274
|
+
expect(() => {
|
|
275
|
+
observable.next(42);
|
|
276
|
+
}).not.toThrow();
|
|
277
|
+
|
|
278
|
+
expect(mockLogger.error).toHaveBeenCalled();
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
it('should handle undefined optional callbacks', () => {
|
|
282
|
+
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
283
|
+
const observable = new Observable<number>();
|
|
284
|
+
|
|
285
|
+
const subscriberWithMissingCallbacks = {
|
|
286
|
+
// next is undefined
|
|
287
|
+
// error is undefined
|
|
288
|
+
// complete is undefined
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
observable.subscribe(subscriberWithMissingCallbacks);
|
|
292
|
+
|
|
293
|
+
// Should not throw when callbacks are undefined
|
|
294
|
+
expect(() => {
|
|
295
|
+
observable.next(42);
|
|
296
|
+
observable.error(new Error('test'));
|
|
297
|
+
observable.complete();
|
|
298
|
+
}).not.toThrow();
|
|
299
|
+
|
|
300
|
+
consoleErrorSpy.mockRestore();
|
|
301
|
+
});
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
describe('ObservableLogger interface', () => {
|
|
305
|
+
it('should match WorkflowLogger.error() signature', () => {
|
|
306
|
+
const mockLogger: ObservableLogger = {
|
|
307
|
+
error: vi.fn(),
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
const observable = new Observable<number>(mockLogger);
|
|
311
|
+
|
|
312
|
+
// Test the signature matches: error(message: string, data?: unknown): void
|
|
313
|
+
mockLogger.error('test message');
|
|
314
|
+
mockLogger.error('test message', { key: 'value' });
|
|
315
|
+
mockLogger.error('test message', { error: new Error('test') });
|
|
316
|
+
mockLogger.error('test message', undefined);
|
|
317
|
+
|
|
318
|
+
expect(mockLogger.error).toHaveBeenCalledTimes(4);
|
|
319
|
+
});
|
|
320
|
+
});
|
|
321
|
+
});
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { Workflow, WorkflowTreeDebugger } from '../../index.js';
|
|
3
|
+
|
|
4
|
+
class IncrementalTestWorkflow extends Workflow {
|
|
5
|
+
async run(): Promise<void> {
|
|
6
|
+
this.setStatus('completed');
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
describe('Incremental Node Map Updates', () => {
|
|
11
|
+
it('childDetached removes entire subtree (node + descendants)', () => {
|
|
12
|
+
const root = new IncrementalTestWorkflow('Root');
|
|
13
|
+
const child1 = new IncrementalTestWorkflow('Child1', root);
|
|
14
|
+
const grandchild = new IncrementalTestWorkflow('Grandchild', child1);
|
|
15
|
+
const child2 = new IncrementalTestWorkflow('Child2', root);
|
|
16
|
+
|
|
17
|
+
const debugger_ = new WorkflowTreeDebugger(root);
|
|
18
|
+
|
|
19
|
+
// Verify all nodes are initially in the map
|
|
20
|
+
expect(debugger_.getNode(root.id)).toBe(root.getNode());
|
|
21
|
+
expect(debugger_.getNode(child1.id)).toBe(child1.getNode());
|
|
22
|
+
expect(debugger_.getNode(grandchild.id)).toBe(grandchild.getNode());
|
|
23
|
+
expect(debugger_.getNode(child2.id)).toBe(child2.getNode());
|
|
24
|
+
expect(debugger_.getStats().totalNodes).toBe(4);
|
|
25
|
+
|
|
26
|
+
// Detach child1 (should remove child1 + grandchild)
|
|
27
|
+
root.detachChild(child1);
|
|
28
|
+
|
|
29
|
+
// Verify child1 and grandchild are removed
|
|
30
|
+
expect(debugger_.getNode(child1.id)).toBeUndefined();
|
|
31
|
+
expect(debugger_.getNode(grandchild.id)).toBeUndefined();
|
|
32
|
+
|
|
33
|
+
// Verify root and child2 are still present
|
|
34
|
+
expect(debugger_.getNode(root.id)).toBe(root.getNode());
|
|
35
|
+
expect(debugger_.getNode(child2.id)).toBe(child2.getNode());
|
|
36
|
+
|
|
37
|
+
// Verify total node count decreased by 2
|
|
38
|
+
expect(debugger_.getStats().totalNodes).toBe(2);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('childDetached on already-removed node is no-op', () => {
|
|
42
|
+
const root = new IncrementalTestWorkflow('Root');
|
|
43
|
+
const child1 = new IncrementalTestWorkflow('Child1', root);
|
|
44
|
+
const grandchild = new IncrementalTestWorkflow('Grandchild', child1);
|
|
45
|
+
|
|
46
|
+
const debugger_ = new WorkflowTreeDebugger(root);
|
|
47
|
+
expect(debugger_.getStats().totalNodes).toBe(3);
|
|
48
|
+
|
|
49
|
+
// Detach child1
|
|
50
|
+
root.detachChild(child1);
|
|
51
|
+
expect(debugger_.getStats().totalNodes).toBe(1);
|
|
52
|
+
expect(debugger_.getNode(child1.id)).toBeUndefined();
|
|
53
|
+
expect(debugger_.getNode(grandchild.id)).toBeUndefined();
|
|
54
|
+
|
|
55
|
+
// Simulate the edge case where nodeMap still has orphaned nodes
|
|
56
|
+
// by directly removing a node that was never properly attached
|
|
57
|
+
// This tests the removeSubtreeNodes() no-op behavior
|
|
58
|
+
const orphanId = 'nonexistent-node';
|
|
59
|
+
expect(debugger_.getNode(orphanId)).toBeUndefined();
|
|
60
|
+
|
|
61
|
+
// Calling removeSubtreeNodes on a non-existent node should be safe
|
|
62
|
+
// We can't call detachChild twice because it throws, but we can
|
|
63
|
+
// verify the nodeMap state is consistent
|
|
64
|
+
expect(debugger_.getStats().totalNodes).toBe(1);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('childAttached adds subtree (verify existing behavior)', () => {
|
|
68
|
+
const root = new IncrementalTestWorkflow('Root');
|
|
69
|
+
|
|
70
|
+
const debugger_ = new WorkflowTreeDebugger(root);
|
|
71
|
+
expect(debugger_.getStats().totalNodes).toBe(1);
|
|
72
|
+
|
|
73
|
+
// Attach a child with its own subtree
|
|
74
|
+
const child1 = new IncrementalTestWorkflow('Child1', root);
|
|
75
|
+
const grandchild = new IncrementalTestWorkflow('Grandchild', child1);
|
|
76
|
+
|
|
77
|
+
// Verify all nodes are in the map
|
|
78
|
+
expect(debugger_.getNode(root.id)).toBe(root.getNode());
|
|
79
|
+
expect(debugger_.getNode(child1.id)).toBe(child1.getNode());
|
|
80
|
+
expect(debugger_.getNode(grandchild.id)).toBe(grandchild.getNode());
|
|
81
|
+
expect(debugger_.getStats().totalNodes).toBe(3);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('onTreeChanged does not rebuild map', () => {
|
|
85
|
+
const root = new IncrementalTestWorkflow('Root');
|
|
86
|
+
const child1 = new IncrementalTestWorkflow('Child1', root);
|
|
87
|
+
const grandchild = new IncrementalTestWorkflow('Grandchild', child1);
|
|
88
|
+
const child2 = new IncrementalTestWorkflow('Child2', root);
|
|
89
|
+
|
|
90
|
+
const debugger_ = new WorkflowTreeDebugger(root);
|
|
91
|
+
|
|
92
|
+
// Get reference to the nodeMap (we'll check it's the same object)
|
|
93
|
+
const nodeMapBefore = debugger_.getStats();
|
|
94
|
+
|
|
95
|
+
// Detach child1
|
|
96
|
+
root.detachChild(child1);
|
|
97
|
+
|
|
98
|
+
// Verify map was updated incrementally (nodes removed)
|
|
99
|
+
expect(debugger_.getStats().totalNodes).toBe(2);
|
|
100
|
+
expect(debugger_.getNode(child1.id)).toBeUndefined();
|
|
101
|
+
expect(debugger_.getNode(grandchild.id)).toBeUndefined();
|
|
102
|
+
|
|
103
|
+
// Verify remaining nodes are still accessible
|
|
104
|
+
expect(debugger_.getNode(root.id)).toBe(root.getNode());
|
|
105
|
+
expect(debugger_.getNode(child2.id)).toBe(child2.getNode());
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('multiple rapid attach/detach operations work correctly', () => {
|
|
109
|
+
const root = new IncrementalTestWorkflow('Root');
|
|
110
|
+
const child1 = new IncrementalTestWorkflow('Child1', root);
|
|
111
|
+
const child2 = new IncrementalTestWorkflow('Child2', root);
|
|
112
|
+
const grandchild1 = new IncrementalTestWorkflow('Grandchild1', child1);
|
|
113
|
+
const grandchild2 = new IncrementalTestWorkflow('Grandchild2', child1);
|
|
114
|
+
|
|
115
|
+
const debugger_ = new WorkflowTreeDebugger(root);
|
|
116
|
+
expect(debugger_.getStats().totalNodes).toBe(5);
|
|
117
|
+
|
|
118
|
+
// Detach child1 (removes child1 + 2 grandchildren)
|
|
119
|
+
root.detachChild(child1);
|
|
120
|
+
expect(debugger_.getStats().totalNodes).toBe(2);
|
|
121
|
+
|
|
122
|
+
// Attach a new child
|
|
123
|
+
const child3 = new IncrementalTestWorkflow('Child3', root);
|
|
124
|
+
expect(debugger_.getStats().totalNodes).toBe(3);
|
|
125
|
+
expect(debugger_.getNode(child3.id)).toBe(child3.getNode());
|
|
126
|
+
|
|
127
|
+
// Detach child2
|
|
128
|
+
root.detachChild(child2);
|
|
129
|
+
expect(debugger_.getStats().totalNodes).toBe(2);
|
|
130
|
+
|
|
131
|
+
// Verify final state
|
|
132
|
+
expect(debugger_.getNode(root.id)).toBe(root.getNode());
|
|
133
|
+
expect(debugger_.getNode(child3.id)).toBe(child3.getNode());
|
|
134
|
+
expect(debugger_.getNode(child1.id)).toBeUndefined();
|
|
135
|
+
expect(debugger_.getNode(child2.id)).toBeUndefined();
|
|
136
|
+
expect(debugger_.getNode(grandchild1.id)).toBeUndefined();
|
|
137
|
+
expect(debugger_.getNode(grandchild2.id)).toBeUndefined();
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('detaching node with many descendants removes all', () => {
|
|
141
|
+
const root = new IncrementalTestWorkflow('Root');
|
|
142
|
+
|
|
143
|
+
// Build a deep subtree
|
|
144
|
+
const child1 = new IncrementalTestWorkflow('Child1', root);
|
|
145
|
+
let current = child1;
|
|
146
|
+
const descendants: IncrementalTestWorkflow[] = [];
|
|
147
|
+
for (let i = 0; i < 10; i++) {
|
|
148
|
+
const descendant = new IncrementalTestWorkflow(`Descendant${i}`, current);
|
|
149
|
+
descendants.push(descendant);
|
|
150
|
+
current = descendant;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const debugger_ = new WorkflowTreeDebugger(root);
|
|
154
|
+
// Total: 1 root + 1 child1 + 10 descendants = 12 nodes
|
|
155
|
+
expect(debugger_.getStats().totalNodes).toBe(12);
|
|
156
|
+
|
|
157
|
+
// Detach child1 (should remove child1 + all 10 descendants)
|
|
158
|
+
root.detachChild(child1);
|
|
159
|
+
|
|
160
|
+
// Verify all were removed
|
|
161
|
+
expect(debugger_.getStats().totalNodes).toBe(1);
|
|
162
|
+
expect(debugger_.getNode(child1.id)).toBeUndefined();
|
|
163
|
+
for (const descendant of descendants) {
|
|
164
|
+
expect(debugger_.getNode(descendant.id)).toBeUndefined();
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Verify root is still there
|
|
168
|
+
expect(debugger_.getNode(root.id)).toBe(root.getNode());
|
|
169
|
+
});
|
|
170
|
+
});
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { mergeWorkflowErrors } from '../../../utils/workflow-error-utils.js';
|
|
3
|
+
import type { WorkflowError } from '../../../types/error.js';
|
|
4
|
+
|
|
5
|
+
describe('mergeWorkflowErrors', () => {
|
|
6
|
+
// Helper function to create a mock WorkflowError
|
|
7
|
+
function createMockWorkflowError(overrides?: Partial<WorkflowError>): WorkflowError {
|
|
8
|
+
return {
|
|
9
|
+
message: 'Test error',
|
|
10
|
+
original: new Error('Original error'),
|
|
11
|
+
workflowId: 'wf-test-123',
|
|
12
|
+
stack: 'Error: Test error\n at test.ts:10:15',
|
|
13
|
+
state: { key: 'value' },
|
|
14
|
+
logs: [
|
|
15
|
+
{
|
|
16
|
+
id: 'log-1',
|
|
17
|
+
workflowId: 'wf-test-123',
|
|
18
|
+
timestamp: Date.now(),
|
|
19
|
+
level: 'error',
|
|
20
|
+
message: 'Test log message',
|
|
21
|
+
},
|
|
22
|
+
],
|
|
23
|
+
...overrides,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
it('should return a single error when merging one error', () => {
|
|
28
|
+
const error = createMockWorkflowError({ workflowId: 'wf-1' });
|
|
29
|
+
const result = mergeWorkflowErrors([error], 'testTask', 'parent-wf', 1);
|
|
30
|
+
|
|
31
|
+
expect(result.message).toBe("1 of 1 concurrent child workflows failed in task 'testTask'");
|
|
32
|
+
expect(result.workflowId).toBe('parent-wf');
|
|
33
|
+
expect(result.logs).toEqual(error.logs);
|
|
34
|
+
expect(result.stack).toBe(error.stack);
|
|
35
|
+
expect(result.state).toEqual(error.state);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('should aggregate multiple errors with unique workflow IDs', () => {
|
|
39
|
+
const error1 = createMockWorkflowError({
|
|
40
|
+
message: 'Error 1',
|
|
41
|
+
workflowId: 'wf-1',
|
|
42
|
+
stack: 'stack 1',
|
|
43
|
+
state: { key1: 'value1' },
|
|
44
|
+
logs: [{ id: 'log-1', workflowId: 'wf-1', timestamp: 1000, level: 'error', message: 'Log 1' }],
|
|
45
|
+
});
|
|
46
|
+
const error2 = createMockWorkflowError({
|
|
47
|
+
message: 'Error 2',
|
|
48
|
+
workflowId: 'wf-2',
|
|
49
|
+
stack: 'stack 2',
|
|
50
|
+
state: { key2: 'value2' },
|
|
51
|
+
logs: [{ id: 'log-2', workflowId: 'wf-2', timestamp: 2000, level: 'error', message: 'Log 2' }],
|
|
52
|
+
});
|
|
53
|
+
const error3 = createMockWorkflowError({
|
|
54
|
+
message: 'Error 3',
|
|
55
|
+
workflowId: 'wf-3',
|
|
56
|
+
stack: 'stack 3',
|
|
57
|
+
state: { key3: 'value3' },
|
|
58
|
+
logs: [{ id: 'log-3', workflowId: 'wf-3', timestamp: 3000, level: 'error', message: 'Log 3' }],
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const result = mergeWorkflowErrors([error1, error2, error3], 'concurrentTask', 'parent-wf', 5);
|
|
62
|
+
|
|
63
|
+
expect(result.message).toBe("3 of 5 concurrent child workflows failed in task 'concurrentTask'");
|
|
64
|
+
expect(result.workflowId).toBe('parent-wf');
|
|
65
|
+
expect(result.stack).toBe('stack 1'); // First error's stack
|
|
66
|
+
expect(result.state).toEqual({ key1: 'value1' }); // First error's state
|
|
67
|
+
expect(result.logs).toHaveLength(3); // All logs aggregated
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('should deduplicate workflow IDs when errors have duplicate IDs', () => {
|
|
71
|
+
const error1 = createMockWorkflowError({ workflowId: 'wf-dup' });
|
|
72
|
+
const error2 = createMockWorkflowError({ workflowId: 'wf-dup' });
|
|
73
|
+
const error3 = createMockWorkflowError({ workflowId: 'wf-unique' });
|
|
74
|
+
|
|
75
|
+
const result = mergeWorkflowErrors([error1, error2, error3], 'testTask', 'parent-wf', 4);
|
|
76
|
+
|
|
77
|
+
// Access the metadata from original field
|
|
78
|
+
const metadata = result.original as {
|
|
79
|
+
name: string;
|
|
80
|
+
message: string;
|
|
81
|
+
errors: WorkflowError[];
|
|
82
|
+
totalChildren: number;
|
|
83
|
+
failedChildren: number;
|
|
84
|
+
failedWorkflowIds: string[];
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
expect(metadata.failedWorkflowIds).toEqual(['wf-dup', 'wf-unique']);
|
|
88
|
+
expect(metadata.failedWorkflowIds).toHaveLength(2); // Deduplicated
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('should flatten logs arrays from all errors using flatMap', () => {
|
|
92
|
+
const error1 = createMockWorkflowError({
|
|
93
|
+
workflowId: 'wf-1',
|
|
94
|
+
logs: [
|
|
95
|
+
{ id: 'log-1', workflowId: 'wf-1', timestamp: 1000, level: 'info', message: 'Log 1.1' },
|
|
96
|
+
{ id: 'log-2', workflowId: 'wf-1', timestamp: 2000, level: 'info', message: 'Log 1.2' },
|
|
97
|
+
],
|
|
98
|
+
});
|
|
99
|
+
const error2 = createMockWorkflowError({
|
|
100
|
+
workflowId: 'wf-2',
|
|
101
|
+
logs: [
|
|
102
|
+
{ id: 'log-3', workflowId: 'wf-2', timestamp: 3000, level: 'error', message: 'Log 2.1' },
|
|
103
|
+
{ id: 'log-4', workflowId: 'wf-2', timestamp: 4000, level: 'error', message: 'Log 2.2' },
|
|
104
|
+
],
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
const result = mergeWorkflowErrors([error1, error2], 'testTask', 'parent-wf', 2);
|
|
108
|
+
|
|
109
|
+
expect(result.logs).toHaveLength(4);
|
|
110
|
+
expect(result.logs[0].message).toBe('Log 1.1');
|
|
111
|
+
expect(result.logs[1].message).toBe('Log 1.2');
|
|
112
|
+
expect(result.logs[2].message).toBe('Log 2.1');
|
|
113
|
+
expect(result.logs[3].message).toBe('Log 2.2');
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('should use first error stack trace', () => {
|
|
117
|
+
const error1 = createMockWorkflowError({ stack: 'First stack trace' });
|
|
118
|
+
const error2 = createMockWorkflowError({ stack: 'Second stack trace' });
|
|
119
|
+
const error3 = createMockWorkflowError({ stack: 'Third stack trace' });
|
|
120
|
+
|
|
121
|
+
const result = mergeWorkflowErrors([error1, error2, error3], 'testTask', 'parent-wf', 3);
|
|
122
|
+
|
|
123
|
+
expect(result.stack).toBe('First stack trace');
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('should use first error state', () => {
|
|
127
|
+
const error1 = createMockWorkflowError({ state: { first: 'state1' } });
|
|
128
|
+
const error2 = createMockWorkflowError({ state: { second: 'state2' } });
|
|
129
|
+
|
|
130
|
+
const result = mergeWorkflowErrors([error1, error2], 'testTask', 'parent-wf', 2);
|
|
131
|
+
|
|
132
|
+
expect(result.state).toEqual({ first: 'state1' });
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('should use empty object when first error has no state', () => {
|
|
136
|
+
const error1 = createMockWorkflowError({ state: undefined as any });
|
|
137
|
+
const error2 = createMockWorkflowError({ state: { hasState: 'yes' } });
|
|
138
|
+
|
|
139
|
+
const result = mergeWorkflowErrors([error1, error2], 'testTask', 'parent-wf', 2);
|
|
140
|
+
|
|
141
|
+
expect(result.state).toEqual({});
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('should handle undefined stack trace gracefully', () => {
|
|
145
|
+
const error1 = createMockWorkflowError({ stack: undefined });
|
|
146
|
+
const error2 = createMockWorkflowError({ stack: 'Has stack' });
|
|
147
|
+
|
|
148
|
+
const result = mergeWorkflowErrors([error1, error2], 'testTask', 'parent-wf', 2);
|
|
149
|
+
|
|
150
|
+
expect(result.stack).toBeUndefined();
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it('should include metadata in original field', () => {
|
|
154
|
+
const error1 = createMockWorkflowError({ workflowId: 'wf-1' });
|
|
155
|
+
const error2 = createMockWorkflowError({ workflowId: 'wf-2' });
|
|
156
|
+
const error3 = createMockWorkflowError({ workflowId: 'wf-3' });
|
|
157
|
+
|
|
158
|
+
const result = mergeWorkflowErrors([error1, error2, error3], 'concurrentTask', 'parent-wf', 5);
|
|
159
|
+
|
|
160
|
+
const metadata = result.original as {
|
|
161
|
+
name: string;
|
|
162
|
+
message: string;
|
|
163
|
+
errors: WorkflowError[];
|
|
164
|
+
totalChildren: number;
|
|
165
|
+
failedChildren: number;
|
|
166
|
+
failedWorkflowIds: string[];
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
expect(metadata.name).toBe('WorkflowAggregateError');
|
|
170
|
+
expect(metadata.message).toBe("3 of 5 concurrent child workflows failed in task 'concurrentTask'");
|
|
171
|
+
expect(metadata.errors).toEqual([error1, error2, error3]);
|
|
172
|
+
expect(metadata.totalChildren).toBe(5);
|
|
173
|
+
expect(metadata.failedChildren).toBe(3);
|
|
174
|
+
expect(metadata.failedWorkflowIds).toEqual(['wf-1', 'wf-2', 'wf-3']);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it('should handle empty logs array', () => {
|
|
178
|
+
const error1 = createMockWorkflowError({ logs: [] });
|
|
179
|
+
const error2 = createMockWorkflowError({ logs: [] });
|
|
180
|
+
|
|
181
|
+
const result = mergeWorkflowErrors([error1, error2], 'testTask', 'parent-wf', 2);
|
|
182
|
+
|
|
183
|
+
expect(result.logs).toEqual([]);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it('should include task name in message', () => {
|
|
187
|
+
const error = createMockWorkflowError();
|
|
188
|
+
const result = mergeWorkflowErrors([error], 'myCustomTask', 'parent-wf', 1);
|
|
189
|
+
|
|
190
|
+
expect(result.message).toContain("task 'myCustomTask'");
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it('should include correct counts in message', () => {
|
|
194
|
+
const errors = Array.from({ length: 3 }, (_, i) =>
|
|
195
|
+
createMockWorkflowError({ workflowId: `wf-${i}` })
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
const result = mergeWorkflowErrors(errors, 'testTask', 'parent-wf', 10);
|
|
199
|
+
|
|
200
|
+
expect(result.message).toBe("3 of 10 concurrent child workflows failed in task 'testTask'");
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it('should preserve parent workflow ID', () => {
|
|
204
|
+
const error = createMockWorkflowError({ workflowId: 'child-123' });
|
|
205
|
+
const result = mergeWorkflowErrors([error], 'testTask', 'parent-abc', 1);
|
|
206
|
+
|
|
207
|
+
expect(result.workflowId).toBe('parent-abc');
|
|
208
|
+
});
|
|
209
|
+
});
|