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,303 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Integration Test: Reparenting Observer Propagation
|
|
3
|
+
*
|
|
4
|
+
* Validates that observer propagation correctly updates when a child
|
|
5
|
+
* workflow is reparented from one parent to another.
|
|
6
|
+
*
|
|
7
|
+
* Pattern 7 (from bug_fix_tasks.json):
|
|
8
|
+
* "Observer propagation should update after reparenting.
|
|
9
|
+
* Events from child should only reach new parent's observers."
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { describe, it, expect } from 'vitest';
|
|
13
|
+
import {
|
|
14
|
+
Workflow,
|
|
15
|
+
WorkflowObserver,
|
|
16
|
+
WorkflowEvent,
|
|
17
|
+
WorkflowTreeDebugger,
|
|
18
|
+
} from '../../index.js';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* SimpleWorkflow class for testing
|
|
22
|
+
* Pattern from: src/__tests__/unit/workflow.test.ts:4-11
|
|
23
|
+
*/
|
|
24
|
+
class SimpleWorkflow extends Workflow {
|
|
25
|
+
async run(): Promise<string> {
|
|
26
|
+
this.setStatus('running');
|
|
27
|
+
this.setStatus('completed');
|
|
28
|
+
return 'done';
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
describe('Integration: Reparenting Observer Propagation', () => {
|
|
33
|
+
it('should update observer propagation after reparenting', () => {
|
|
34
|
+
// ============================================================
|
|
35
|
+
// PHASE 1: Setup - Create parent1, parent2, and child
|
|
36
|
+
// ============================================================
|
|
37
|
+
// ARRANGE: Create two root workflows (both have parent = null)
|
|
38
|
+
const parent1 = new SimpleWorkflow('Parent1');
|
|
39
|
+
const parent2 = new SimpleWorkflow('Parent2');
|
|
40
|
+
|
|
41
|
+
// ARRANGE: Create child attached to parent1 (via constructor)
|
|
42
|
+
const child = new SimpleWorkflow('Child', parent1);
|
|
43
|
+
|
|
44
|
+
// ASSERT: Verify initial state
|
|
45
|
+
expect(child.parent).toBe(parent1);
|
|
46
|
+
expect(parent1.children).toContain(child);
|
|
47
|
+
expect(parent2.children).not.toContain(child);
|
|
48
|
+
|
|
49
|
+
// ============================================================
|
|
50
|
+
// PHASE 2: Verify parent1 observer receives events
|
|
51
|
+
// ============================================================
|
|
52
|
+
// ARRANGE: Create observer for parent1
|
|
53
|
+
const parent1Events: WorkflowEvent[] = [];
|
|
54
|
+
|
|
55
|
+
const parent1Observer: WorkflowObserver = {
|
|
56
|
+
onLog: () => {}, // Empty - not testing logs
|
|
57
|
+
onEvent: (event) => parent1Events.push(event), // Capture events
|
|
58
|
+
onStateUpdated: () => {}, // Empty - not testing state updates
|
|
59
|
+
onTreeChanged: () => {}, // Empty - not testing tree changes
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
// ACT: Attach observer to parent1 (must be root workflow)
|
|
63
|
+
parent1.addObserver(parent1Observer);
|
|
64
|
+
|
|
65
|
+
// ACT: Child emits event (triggers treeUpdated via setStatus)
|
|
66
|
+
// PATTERN: setStatus() emits treeUpdated event
|
|
67
|
+
parent1Events.length = 0; // Clear any construction events
|
|
68
|
+
child.setStatus('running');
|
|
69
|
+
|
|
70
|
+
// ASSERT: Verify parent1 observer received the event
|
|
71
|
+
const parent1ReceivedEvent = parent1Events.find(
|
|
72
|
+
(e) => e.type === 'treeUpdated'
|
|
73
|
+
);
|
|
74
|
+
expect(parent1ReceivedEvent).toBeDefined();
|
|
75
|
+
|
|
76
|
+
// ============================================================
|
|
77
|
+
// PHASE 3: Reparent child from parent1 to parent2
|
|
78
|
+
// ============================================================
|
|
79
|
+
// ARRANGE: Create observer for parent2
|
|
80
|
+
const parent2Events: WorkflowEvent[] = [];
|
|
81
|
+
|
|
82
|
+
const parent2Observer: WorkflowObserver = {
|
|
83
|
+
onLog: () => {},
|
|
84
|
+
onEvent: (event) => parent2Events.push(event),
|
|
85
|
+
onStateUpdated: () => {},
|
|
86
|
+
onTreeChanged: () => {},
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
// ACT: Reparent using detach + attach pattern
|
|
90
|
+
// CRITICAL: Must detach before attaching to new parent
|
|
91
|
+
parent1.detachChild(child);
|
|
92
|
+
parent2.attachChild(child);
|
|
93
|
+
parent2.addObserver(parent2Observer);
|
|
94
|
+
|
|
95
|
+
// ASSERT: Verify reparenting succeeded
|
|
96
|
+
expect(child.parent).toBe(parent2);
|
|
97
|
+
expect(parent2.children).toContain(child);
|
|
98
|
+
expect(parent1.children).not.toContain(child);
|
|
99
|
+
|
|
100
|
+
// ============================================================
|
|
101
|
+
// PHASE 4: Verify parent2 observer receives events after reparenting
|
|
102
|
+
// ============================================================
|
|
103
|
+
// ACT: Clear events to isolate post-reparenting behavior
|
|
104
|
+
parent1Events.length = 0;
|
|
105
|
+
parent2Events.length = 0;
|
|
106
|
+
|
|
107
|
+
// ACT: Child emits event again
|
|
108
|
+
child.setStatus('completed');
|
|
109
|
+
|
|
110
|
+
// ASSERT: Verify parent2 observer received the event
|
|
111
|
+
const parent2ReceivedEvent = parent2Events.find(
|
|
112
|
+
(e) => e.type === 'treeUpdated'
|
|
113
|
+
);
|
|
114
|
+
expect(parent2ReceivedEvent).toBeDefined();
|
|
115
|
+
|
|
116
|
+
// ============================================================
|
|
117
|
+
// PHASE 5: CRITICAL VALIDATION - parent1 observer does NOT receive events
|
|
118
|
+
// ============================================================
|
|
119
|
+
// ASSERT: Verify parent1 observer did NOT receive the post-reparenting event
|
|
120
|
+
const parent1DidNotReceive = parent1Events.find(
|
|
121
|
+
(e) => e.type === 'treeUpdated'
|
|
122
|
+
);
|
|
123
|
+
expect(parent1DidNotReceive).toBeUndefined();
|
|
124
|
+
|
|
125
|
+
// ASSERT: Verify event count for parent1 is zero
|
|
126
|
+
expect(parent1Events.length).toBe(0);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('should handle multiple reparenting cycles correctly', () => {
|
|
130
|
+
// ARRANGE: Create three potential parents
|
|
131
|
+
const parentA = new SimpleWorkflow('ParentA');
|
|
132
|
+
const parentB = new SimpleWorkflow('ParentB');
|
|
133
|
+
const parentC = new SimpleWorkflow('ParentC');
|
|
134
|
+
|
|
135
|
+
// ARRANGE: Create child and observers for each parent
|
|
136
|
+
const child = new SimpleWorkflow('Child', parentA);
|
|
137
|
+
|
|
138
|
+
const eventsA: WorkflowEvent[] = [];
|
|
139
|
+
const eventsB: WorkflowEvent[] = [];
|
|
140
|
+
const eventsC: WorkflowEvent[] = [];
|
|
141
|
+
|
|
142
|
+
const createObserver = (eventsArray: WorkflowEvent[]): WorkflowObserver => ({
|
|
143
|
+
onLog: () => {},
|
|
144
|
+
onEvent: (e) => eventsArray.push(e),
|
|
145
|
+
onStateUpdated: () => {},
|
|
146
|
+
onTreeChanged: () => {},
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
parentA.addObserver(createObserver(eventsA));
|
|
150
|
+
parentB.addObserver(createObserver(eventsB));
|
|
151
|
+
parentC.addObserver(createObserver(eventsC));
|
|
152
|
+
|
|
153
|
+
// ACT & ASSERT: Cycle 1 - A -> B
|
|
154
|
+
eventsA.length = 0;
|
|
155
|
+
child.setStatus('running');
|
|
156
|
+
expect(eventsA.some((e) => e.type === 'treeUpdated')).toBe(true);
|
|
157
|
+
|
|
158
|
+
parentA.detachChild(child);
|
|
159
|
+
parentB.attachChild(child);
|
|
160
|
+
|
|
161
|
+
eventsA.length = 0;
|
|
162
|
+
eventsB.length = 0;
|
|
163
|
+
child.setStatus('completed');
|
|
164
|
+
expect(eventsB.some((e) => e.type === 'treeUpdated')).toBe(true);
|
|
165
|
+
expect(eventsA.some((e) => e.type === 'treeUpdated')).toBe(false);
|
|
166
|
+
|
|
167
|
+
// ACT & ASSERT: Cycle 2 - B -> C
|
|
168
|
+
parentB.detachChild(child);
|
|
169
|
+
parentC.attachChild(child);
|
|
170
|
+
|
|
171
|
+
eventsB.length = 0;
|
|
172
|
+
eventsC.length = 0;
|
|
173
|
+
child.setStatus('running');
|
|
174
|
+
expect(eventsC.some((e) => e.type === 'treeUpdated')).toBe(true);
|
|
175
|
+
expect(eventsB.some((e) => e.type === 'treeUpdated')).toBe(false);
|
|
176
|
+
|
|
177
|
+
// ACT & ASSERT: Cycle 3 - C -> A (return to original)
|
|
178
|
+
parentC.detachChild(child);
|
|
179
|
+
parentA.attachChild(child);
|
|
180
|
+
|
|
181
|
+
eventsC.length = 0;
|
|
182
|
+
eventsA.length = 0;
|
|
183
|
+
child.setStatus('completed');
|
|
184
|
+
expect(eventsA.some((e) => e.type === 'treeUpdated')).toBe(true);
|
|
185
|
+
expect(eventsC.some((e) => e.type === 'treeUpdated')).toBe(false);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it('should verify tree consistency after reparenting using debugger', () => {
|
|
189
|
+
// ============================================================
|
|
190
|
+
// PHASE 1: Setup - Create parent1, parent2, and child
|
|
191
|
+
// ============================================================
|
|
192
|
+
// ARRANGE: Create two root workflows
|
|
193
|
+
const parent1 = new SimpleWorkflow('Parent1');
|
|
194
|
+
const parent2 = new SimpleWorkflow('Parent2');
|
|
195
|
+
|
|
196
|
+
// ARRANGE: Create child attached to parent1 (via constructor)
|
|
197
|
+
const child = new SimpleWorkflow('Child', parent1);
|
|
198
|
+
|
|
199
|
+
// ARRANGE: Create debugger attached to parent1
|
|
200
|
+
const parent1Debugger = new WorkflowTreeDebugger(parent1);
|
|
201
|
+
|
|
202
|
+
// ASSERT: Verify initial tree structure
|
|
203
|
+
expect(child.parent).toBe(parent1);
|
|
204
|
+
expect(parent1.children).toContain(child);
|
|
205
|
+
expect(parent2.children).not.toContain(child);
|
|
206
|
+
|
|
207
|
+
// ASSERT: Verify debugger sees initial structure
|
|
208
|
+
const initialTree = parent1Debugger.getTree();
|
|
209
|
+
expect(initialTree.name).toBe('Parent1');
|
|
210
|
+
expect(initialTree.children.length).toBe(1);
|
|
211
|
+
expect(initialTree.children[0].name).toBe('Child');
|
|
212
|
+
|
|
213
|
+
// ============================================================
|
|
214
|
+
// PHASE 2: Reparenting operation
|
|
215
|
+
// ============================================================
|
|
216
|
+
// ACT: Reparent child from parent1 to parent2
|
|
217
|
+
parent1.detachChild(child);
|
|
218
|
+
parent2.attachChild(child);
|
|
219
|
+
|
|
220
|
+
// ASSERT: Verify basic reparenting succeeded
|
|
221
|
+
expect(child.parent).toBe(parent2);
|
|
222
|
+
expect(parent2.children).toContain(child);
|
|
223
|
+
expect(parent1.children).not.toContain(child);
|
|
224
|
+
|
|
225
|
+
// ============================================================
|
|
226
|
+
// PHASE 3: Verify workflow tree structure
|
|
227
|
+
// ============================================================
|
|
228
|
+
// ASSERT: Verify child.parent points to new parent
|
|
229
|
+
expect(child.parent).toBe(parent2);
|
|
230
|
+
|
|
231
|
+
// ASSERT: Verify new parent.children includes child
|
|
232
|
+
expect(parent2.children).toContain(child);
|
|
233
|
+
|
|
234
|
+
// ASSERT: Verify old parent.children does NOT include child
|
|
235
|
+
expect(parent1.children).not.toContain(child);
|
|
236
|
+
|
|
237
|
+
// ============================================================
|
|
238
|
+
// PHASE 4: Verify node tree structure using debugger
|
|
239
|
+
// ============================================================
|
|
240
|
+
// NOTE: parent1Debugger is still attached to parent1, so it shows parent1's tree
|
|
241
|
+
// Create debugger for parent2 to see the new tree structure
|
|
242
|
+
const parent2Debugger = new WorkflowTreeDebugger(parent2);
|
|
243
|
+
|
|
244
|
+
// ASSERT: Verify debugger can find child node
|
|
245
|
+
const childNode = parent2Debugger.getNode(child.id);
|
|
246
|
+
expect(childNode).toBeDefined();
|
|
247
|
+
expect(childNode?.name).toBe('Child');
|
|
248
|
+
|
|
249
|
+
// ASSERT: Verify child's node parent is parent2's node (node tree mirrors workflow tree)
|
|
250
|
+
const parent2NodeDirect = parent2.getNode();
|
|
251
|
+
expect(childNode?.parent).toBe(parent2NodeDirect);
|
|
252
|
+
|
|
253
|
+
// ASSERT: Verify parent2's node children includes child's node
|
|
254
|
+
const parent2Node = parent2Debugger.getNode(parent2.id);
|
|
255
|
+
const childNodeDirect = child.getNode();
|
|
256
|
+
expect(parent2Node?.children).toContain(childNodeDirect);
|
|
257
|
+
|
|
258
|
+
// ASSERT: Verify parent1's node children does NOT include child's node
|
|
259
|
+
const parent1Node = parent1Debugger.getNode(parent1.id);
|
|
260
|
+
expect(parent1Node?.children).not.toContain(childNodeDirect);
|
|
261
|
+
|
|
262
|
+
// ============================================================
|
|
263
|
+
// PHASE 5: Visual validation using toTreeString()
|
|
264
|
+
// ============================================================
|
|
265
|
+
// ACT: Get tree string for visual debugging
|
|
266
|
+
const parent2TreeString = parent2Debugger.toTreeString();
|
|
267
|
+
|
|
268
|
+
// ASSERT: Verify tree structure in ASCII representation
|
|
269
|
+
expect(parent2TreeString).toContain('Parent2');
|
|
270
|
+
expect(parent2TreeString).toContain('Child');
|
|
271
|
+
expect(parent2TreeString).toContain('└──'); // Tree connector symbol
|
|
272
|
+
|
|
273
|
+
// ============================================================
|
|
274
|
+
// PHASE 6: Statistical validation
|
|
275
|
+
// ============================================================
|
|
276
|
+
const parent2Stats = parent2Debugger.getStats();
|
|
277
|
+
expect(parent2Stats.totalNodes).toBe(2); // parent2 + child = 2
|
|
278
|
+
|
|
279
|
+
// ============================================================
|
|
280
|
+
// CRITICAL VALIDATION: 1:1 Tree Mirror Invariant
|
|
281
|
+
// ============================================================
|
|
282
|
+
// Verify workflow tree and node tree are perfectly synchronized
|
|
283
|
+
// This is the core invariant that must be maintained
|
|
284
|
+
|
|
285
|
+
// Workflow tree state:
|
|
286
|
+
expect(child.parent).toBe(parent2);
|
|
287
|
+
expect(parent2.children).toEqual([child]);
|
|
288
|
+
expect(parent1.children).toEqual([]);
|
|
289
|
+
|
|
290
|
+
// Node tree state (via getNode() method):
|
|
291
|
+
const childNodeFinal = child.getNode();
|
|
292
|
+
const parent2NodeFinal = parent2.getNode();
|
|
293
|
+
const parent1NodeFinal = parent1.getNode();
|
|
294
|
+
|
|
295
|
+
expect(childNodeFinal.parent).toBe(parent2NodeFinal);
|
|
296
|
+
expect(parent2NodeFinal.children).toEqual([childNodeFinal]);
|
|
297
|
+
expect(parent1NodeFinal.children).toEqual([]);
|
|
298
|
+
|
|
299
|
+
// Cross-verification: debugger lookup matches direct access
|
|
300
|
+
expect(parent2Debugger.getNode(child.id)).toBe(childNodeFinal);
|
|
301
|
+
expect(parent2Debugger.getNode(parent2.id)).toBe(parent2NodeFinal);
|
|
302
|
+
});
|
|
303
|
+
});
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
type AgentExecutionContext,
|
|
10
10
|
} from '../../core/context.js';
|
|
11
11
|
import type { WorkflowNode, WorkflowEvent } from '../../types/index.js';
|
|
12
|
+
import { Workflow, WorkflowObserver, ObservedState } from '../../index.js';
|
|
12
13
|
|
|
13
14
|
describe('AgentExecutionContext', () => {
|
|
14
15
|
const createMockNode = (name: string): WorkflowNode => ({
|
|
@@ -136,3 +137,81 @@ describe('AgentExecutionContext', () => {
|
|
|
136
137
|
expect(events[0].type).toBe('stepStart');
|
|
137
138
|
});
|
|
138
139
|
});
|
|
140
|
+
|
|
141
|
+
describe('WorkflowContext', () => {
|
|
142
|
+
// Test workflow class with @ObservedState decorated fields
|
|
143
|
+
class StatefulTestWorkflow extends Workflow {
|
|
144
|
+
@ObservedState()
|
|
145
|
+
stepCount: number = 0;
|
|
146
|
+
|
|
147
|
+
@ObservedState({ redact: true })
|
|
148
|
+
apiKey: string = 'secret-key-123';
|
|
149
|
+
|
|
150
|
+
@ObservedState({ hidden: true })
|
|
151
|
+
internalCounter: number = 42;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
it('should capture state and logs in step() error handler', async () => {
|
|
155
|
+
// Arrange: Create observer to capture error events
|
|
156
|
+
const events: WorkflowEvent[] = [];
|
|
157
|
+
|
|
158
|
+
const observer: WorkflowObserver = {
|
|
159
|
+
onLog: () => {},
|
|
160
|
+
onEvent: (event) => events.push(event),
|
|
161
|
+
onStateUpdated: () => {},
|
|
162
|
+
onTreeChanged: () => {},
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
// Arrange: Create workflow with @ObservedState fields using functional executor
|
|
166
|
+
const workflow = new StatefulTestWorkflow(
|
|
167
|
+
{ name: 'StepErrorTest' },
|
|
168
|
+
async (ctx) => {
|
|
169
|
+
// Modify @ObservedState fields on the workflow instance
|
|
170
|
+
(workflow as any).stepCount = 5;
|
|
171
|
+
(workflow as any).apiKey = 'updated-key';
|
|
172
|
+
(workflow as any).internalCounter = 99;
|
|
173
|
+
|
|
174
|
+
// Execute a step that will fail - THIS TRIGGERS WorkflowContext.step() ERROR HANDLER
|
|
175
|
+
await ctx.step('failing-step', async () => {
|
|
176
|
+
throw new Error('Test error from step');
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
// Act: Attach observer and trigger error
|
|
182
|
+
workflow.addObserver(observer);
|
|
183
|
+
await expect(workflow.run()).rejects.toThrow('Test error from step');
|
|
184
|
+
|
|
185
|
+
// Assert: Verify error event was emitted
|
|
186
|
+
const errorEvents = events.filter((e) => e.type === 'error');
|
|
187
|
+
expect(errorEvents.length).toBeGreaterThanOrEqual(1);
|
|
188
|
+
|
|
189
|
+
// Assert: Verify error structure
|
|
190
|
+
const errorEvent = errorEvents[0];
|
|
191
|
+
expect(errorEvent.error).toBeDefined();
|
|
192
|
+
expect(errorEvent.error.message).toBe('Test error from step');
|
|
193
|
+
|
|
194
|
+
// Assert: Verify @ObservedState fields were captured in state
|
|
195
|
+
expect(errorEvent.error.state).toBeDefined();
|
|
196
|
+
expect(typeof errorEvent.error.state).toBe('object');
|
|
197
|
+
|
|
198
|
+
// Assert: Verify public field value is captured
|
|
199
|
+
expect(errorEvent.error.state.stepCount).toBe(5);
|
|
200
|
+
|
|
201
|
+
// Assert: Verify redacted field shows '***'
|
|
202
|
+
expect(errorEvent.error.state.apiKey).toBe('***');
|
|
203
|
+
|
|
204
|
+
// Assert: Verify hidden field is NOT in state
|
|
205
|
+
expect('internalCounter' in errorEvent.error.state).toBe(false);
|
|
206
|
+
|
|
207
|
+
// Assert: Verify logs array is present (may be empty)
|
|
208
|
+
expect(errorEvent.error.logs).toBeDefined();
|
|
209
|
+
expect(Array.isArray(errorEvent.error.logs)).toBe(true);
|
|
210
|
+
|
|
211
|
+
// Assert: Verify workflow status
|
|
212
|
+
expect(workflow.status).toBe('failed');
|
|
213
|
+
|
|
214
|
+
// Assert: Verify workflowId is captured
|
|
215
|
+
expect(errorEvent.error.workflowId).toBe(workflow.id);
|
|
216
|
+
});
|
|
217
|
+
});
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { Workflow } from '../../core/workflow';
|
|
3
|
+
import type { LogEntry } from '../../types/logging';
|
|
4
|
+
|
|
5
|
+
describe('WorkflowLogger.child()', () => {
|
|
6
|
+
describe('with Partial<LogEntry> containing parentLogId', () => {
|
|
7
|
+
it('should create child logger with parentLogId from Partial<LogEntry>', async () => {
|
|
8
|
+
class TestWorkflow extends Workflow {
|
|
9
|
+
async run() {
|
|
10
|
+
const childLogger = this.logger.child({ parentLogId: 'parent-123' });
|
|
11
|
+
childLogger.info('Child message');
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const workflow = new TestWorkflow();
|
|
16
|
+
await workflow.run();
|
|
17
|
+
|
|
18
|
+
expect(workflow.node.logs.length).toBe(1);
|
|
19
|
+
expect(workflow.node.logs[0].parentLogId).toBe('parent-123');
|
|
20
|
+
expect(workflow.node.logs[0].message).toBe('Child message');
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('should handle parentLogId with special characters', async () => {
|
|
24
|
+
class TestWorkflow extends Workflow {
|
|
25
|
+
async run() {
|
|
26
|
+
const childLogger = this.logger.child({ parentLogId: 'parent-with-dashes-and_underscores' });
|
|
27
|
+
childLogger.info('Test');
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const workflow = new TestWorkflow();
|
|
32
|
+
await workflow.run();
|
|
33
|
+
|
|
34
|
+
expect(workflow.node.logs[0].parentLogId).toBe('parent-with-dashes-and_underscores');
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
describe('with Partial<LogEntry> containing id field', () => {
|
|
39
|
+
it('should not use id field as parentLogId', async () => {
|
|
40
|
+
class TestWorkflow extends Workflow {
|
|
41
|
+
async run() {
|
|
42
|
+
// id field should NOT be used as parentLogId
|
|
43
|
+
const childLogger = this.logger.child({ id: 'custom-id' });
|
|
44
|
+
childLogger.info('Test message');
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const workflow = new TestWorkflow();
|
|
49
|
+
await workflow.run();
|
|
50
|
+
|
|
51
|
+
// parentLogId is undefined because implementation only checks input.parentLogId
|
|
52
|
+
expect(workflow.node.logs[0].parentLogId).toBeUndefined();
|
|
53
|
+
expect(workflow.node.logs[0].message).toBe('Test message');
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('should handle both id and parentLogId fields', async () => {
|
|
57
|
+
class TestWorkflow extends Workflow {
|
|
58
|
+
async run() {
|
|
59
|
+
// parentLogId should be used, not id
|
|
60
|
+
const childLogger = this.logger.child({ id: 'custom-id', parentLogId: 'correct-parent' });
|
|
61
|
+
childLogger.info('Test');
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const workflow = new TestWorkflow();
|
|
66
|
+
await workflow.run();
|
|
67
|
+
|
|
68
|
+
expect(workflow.node.logs[0].parentLogId).toBe('correct-parent');
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
describe('with empty Partial<LogEntry>', () => {
|
|
73
|
+
it('should create child logger with undefined parentLogId from empty object', async () => {
|
|
74
|
+
class TestWorkflow extends Workflow {
|
|
75
|
+
async run() {
|
|
76
|
+
const childLogger = this.logger.child({});
|
|
77
|
+
childLogger.info('Child log with empty parent metadata');
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const workflow = new TestWorkflow();
|
|
82
|
+
await workflow.run();
|
|
83
|
+
|
|
84
|
+
expect(workflow.node.logs.length).toBe(1);
|
|
85
|
+
expect(workflow.node.logs[0].parentLogId).toBeUndefined();
|
|
86
|
+
expect(workflow.node.logs[0].message).toBe('Child log with empty parent metadata');
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
describe('with string parameter (backward compatibility)', () => {
|
|
91
|
+
it('should create child logger with parentLogId from string', async () => {
|
|
92
|
+
class TestWorkflow extends Workflow {
|
|
93
|
+
async run() {
|
|
94
|
+
const childLogger = this.logger.child('parent-id-123');
|
|
95
|
+
childLogger.info('Child message');
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const workflow = new TestWorkflow();
|
|
100
|
+
await workflow.run();
|
|
101
|
+
|
|
102
|
+
expect(workflow.node.logs.length).toBe(1);
|
|
103
|
+
expect(workflow.node.logs[0].parentLogId).toBe('parent-id-123');
|
|
104
|
+
expect(workflow.node.logs[0].message).toBe('Child message');
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('should create child logger with parentLogId from string containing parentLogId', async () => {
|
|
108
|
+
class TestWorkflow extends Workflow {
|
|
109
|
+
async run() {
|
|
110
|
+
// String value is used directly as parentLogId
|
|
111
|
+
const childLogger = this.logger.child('log-abc-123');
|
|
112
|
+
childLogger.warn('Warning message');
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const workflow = new TestWorkflow();
|
|
117
|
+
await workflow.run();
|
|
118
|
+
|
|
119
|
+
expect(workflow.node.logs[0].parentLogId).toBe('log-abc-123');
|
|
120
|
+
expect(workflow.node.logs[0].level).toBe('warn');
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
describe('with empty string', () => {
|
|
125
|
+
it('should create child logger with undefined parentLogId from empty string', async () => {
|
|
126
|
+
class TestWorkflow extends Workflow {
|
|
127
|
+
async run() {
|
|
128
|
+
const childLogger = this.logger.child('');
|
|
129
|
+
childLogger.info('Child log with empty parent');
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const workflow = new TestWorkflow();
|
|
134
|
+
await workflow.run();
|
|
135
|
+
|
|
136
|
+
expect(workflow.node.logs.length).toBe(1);
|
|
137
|
+
// Empty string is falsy, so parentLogId becomes undefined in log entry
|
|
138
|
+
expect(workflow.node.logs[0].parentLogId).toBeUndefined();
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
describe('child logger with different log levels', () => {
|
|
143
|
+
it.each([
|
|
144
|
+
{ level: 'debug', method: 'debug' as const },
|
|
145
|
+
{ level: 'info', method: 'info' as const },
|
|
146
|
+
{ level: 'warn', method: 'warn' as const },
|
|
147
|
+
{ level: 'error', method: 'error' as const },
|
|
148
|
+
])('should log at $level level with child logger', async ({ level, method }) => {
|
|
149
|
+
class TestWorkflow extends Workflow {
|
|
150
|
+
async run() {
|
|
151
|
+
const childLogger = this.logger.child({ parentLogId: 'parent-123' });
|
|
152
|
+
childLogger[method](`Test ${level} message`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const workflow = new TestWorkflow();
|
|
157
|
+
await workflow.run();
|
|
158
|
+
|
|
159
|
+
expect(workflow.node.logs[0].level).toBe(level);
|
|
160
|
+
expect(workflow.node.logs[0].parentLogId).toBe('parent-123');
|
|
161
|
+
expect(workflow.node.logs[0].message).toBe(`Test ${level} message`);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it('should support logging with data parameter at all levels', async () => {
|
|
165
|
+
class TestWorkflow extends Workflow {
|
|
166
|
+
async run() {
|
|
167
|
+
const childLogger = this.logger.child({ parentLogId: 'parent-data' });
|
|
168
|
+
|
|
169
|
+
childLogger.debug('Debug message', { debugData: true });
|
|
170
|
+
childLogger.info('Info message', { infoData: 123 });
|
|
171
|
+
childLogger.warn('Warn message', { warnData: 'warning' });
|
|
172
|
+
childLogger.error('Error message', { errorData: { code: 500 } });
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const workflow = new TestWorkflow();
|
|
177
|
+
await workflow.run();
|
|
178
|
+
|
|
179
|
+
expect(workflow.node.logs.length).toBe(4);
|
|
180
|
+
expect(workflow.node.logs[0].data).toEqual({ debugData: true });
|
|
181
|
+
expect(workflow.node.logs[1].data).toEqual({ infoData: 123 });
|
|
182
|
+
expect(workflow.node.logs[2].data).toEqual({ warnData: 'warning' });
|
|
183
|
+
expect(workflow.node.logs[3].data).toEqual({ errorData: { code: 500 } });
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
describe('parent-child log hierarchy', () => {
|
|
188
|
+
it('should maintain parent-child relationship in log entries', async () => {
|
|
189
|
+
class TestWorkflow extends Workflow {
|
|
190
|
+
async run() {
|
|
191
|
+
// Log from parent logger
|
|
192
|
+
this.logger.info('Parent message');
|
|
193
|
+
|
|
194
|
+
// Get the parent log entry ID
|
|
195
|
+
const parentLogId = this.node.logs[0].id;
|
|
196
|
+
|
|
197
|
+
// Create child logger with that ID
|
|
198
|
+
const childLogger = this.logger.child({ parentLogId });
|
|
199
|
+
childLogger.info('Child message');
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const workflow = new TestWorkflow();
|
|
204
|
+
await workflow.run();
|
|
205
|
+
|
|
206
|
+
// Verify hierarchy
|
|
207
|
+
expect(workflow.node.logs.length).toBe(2);
|
|
208
|
+
expect(workflow.node.logs[0].parentLogId).toBeUndefined(); // Root log
|
|
209
|
+
expect(workflow.node.logs[1].parentLogId).toBe(workflow.node.logs[0].id); // Child log
|
|
210
|
+
expect(workflow.node.logs[0].message).toBe('Parent message');
|
|
211
|
+
expect(workflow.node.logs[1].message).toBe('Child message');
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it('should support multi-level nesting with child loggers', async () => {
|
|
215
|
+
class TestWorkflow extends Workflow {
|
|
216
|
+
async run() {
|
|
217
|
+
// Root log
|
|
218
|
+
this.logger.info('Root message');
|
|
219
|
+
const rootLogId = this.node.logs[0].id;
|
|
220
|
+
|
|
221
|
+
// First level child
|
|
222
|
+
const child1 = this.logger.child({ parentLogId: rootLogId });
|
|
223
|
+
child1.info('Level 1 child');
|
|
224
|
+
const level1LogId = this.node.logs[1].id;
|
|
225
|
+
|
|
226
|
+
// Second level child
|
|
227
|
+
const child2 = this.logger.child({ parentLogId: level1LogId });
|
|
228
|
+
child2.info('Level 2 child');
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const workflow = new TestWorkflow();
|
|
233
|
+
await workflow.run();
|
|
234
|
+
|
|
235
|
+
expect(workflow.node.logs.length).toBe(3);
|
|
236
|
+
expect(workflow.node.logs[0].parentLogId).toBeUndefined();
|
|
237
|
+
expect(workflow.node.logs[1].parentLogId).toBe(workflow.node.logs[0].id);
|
|
238
|
+
expect(workflow.node.logs[2].parentLogId).toBe(workflow.node.logs[1].id);
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
it('should support string-based parent-child hierarchy', async () => {
|
|
242
|
+
class TestWorkflow extends Workflow {
|
|
243
|
+
async run() {
|
|
244
|
+
// Log from parent
|
|
245
|
+
this.logger.info('Parent log');
|
|
246
|
+
const parentLogId = this.node.logs[0].id;
|
|
247
|
+
|
|
248
|
+
// Create child using string parentLogId (backward compatibility)
|
|
249
|
+
const childLogger = this.logger.child(parentLogId);
|
|
250
|
+
childLogger.info('Child log');
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const workflow = new TestWorkflow();
|
|
255
|
+
await workflow.run();
|
|
256
|
+
|
|
257
|
+
expect(workflow.node.logs.length).toBe(2);
|
|
258
|
+
expect(workflow.node.logs[0].parentLogId).toBeUndefined();
|
|
259
|
+
expect(workflow.node.logs[1].parentLogId).toBe(workflow.node.logs[0].id);
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it('should allow multiple child loggers from same parent', async () => {
|
|
263
|
+
class TestWorkflow extends Workflow {
|
|
264
|
+
async run() {
|
|
265
|
+
// Parent log
|
|
266
|
+
this.logger.info('Parent');
|
|
267
|
+
const parentLogId = this.node.logs[0].id;
|
|
268
|
+
|
|
269
|
+
// Multiple children from same parent
|
|
270
|
+
const child1 = this.logger.child({ parentLogId });
|
|
271
|
+
child1.info('First child');
|
|
272
|
+
|
|
273
|
+
const child2 = this.logger.child({ parentLogId });
|
|
274
|
+
child2.info('Second child');
|
|
275
|
+
|
|
276
|
+
const child3 = this.logger.child({ parentLogId });
|
|
277
|
+
child3.info('Third child');
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const workflow = new TestWorkflow();
|
|
282
|
+
await workflow.run();
|
|
283
|
+
|
|
284
|
+
expect(workflow.node.logs.length).toBe(4);
|
|
285
|
+
expect(workflow.node.logs[0].parentLogId).toBeUndefined();
|
|
286
|
+
|
|
287
|
+
// All children should have the same parent
|
|
288
|
+
expect(workflow.node.logs[1].parentLogId).toBe(workflow.node.logs[0].id);
|
|
289
|
+
expect(workflow.node.logs[2].parentLogId).toBe(workflow.node.logs[0].id);
|
|
290
|
+
expect(workflow.node.logs[3].parentLogId).toBe(workflow.node.logs[0].id);
|
|
291
|
+
});
|
|
292
|
+
});
|
|
293
|
+
});
|