@pennyfarthing/core 11.3.1 → 11.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/package.json +1 -1
- package/packages/core/dist/cli/commands/doctor.d.ts +9 -1
- package/packages/core/dist/cli/commands/doctor.d.ts.map +1 -1
- package/packages/core/dist/cli/commands/doctor.js +107 -51
- package/packages/core/dist/cli/commands/doctor.js.map +1 -1
- package/packages/core/dist/cli/commands/update.d.ts.map +1 -1
- package/packages/core/dist/cli/commands/update.js +1 -26
- package/packages/core/dist/cli/commands/update.js.map +1 -1
- package/packages/core/dist/cli/utils/python.d.ts +1 -0
- package/packages/core/dist/cli/utils/python.d.ts.map +1 -1
- package/packages/core/dist/cli/utils/python.js +11 -1
- package/packages/core/dist/cli/utils/python.js.map +1 -1
- package/packages/core/dist/cli/utils/settings-pf-wrapper.test.d.ts +16 -0
- package/packages/core/dist/cli/utils/settings-pf-wrapper.test.d.ts.map +1 -0
- package/packages/core/dist/cli/utils/settings-pf-wrapper.test.js +377 -0
- package/packages/core/dist/cli/utils/settings-pf-wrapper.test.js.map +1 -0
- package/packages/core/dist/server/paths.d.ts.map +1 -1
- package/packages/core/dist/server/paths.js +6 -0
- package/packages/core/dist/server/paths.js.map +1 -1
- package/packages/core/dist/workflow/team-lifecycle.d.ts +169 -0
- package/packages/core/dist/workflow/team-lifecycle.d.ts.map +1 -0
- package/packages/core/dist/workflow/team-lifecycle.js +217 -0
- package/packages/core/dist/workflow/team-lifecycle.js.map +1 -0
- package/packages/core/dist/workflow/team-lifecycle.test.d.ts +20 -0
- package/packages/core/dist/workflow/team-lifecycle.test.d.ts.map +1 -0
- package/packages/core/dist/workflow/team-lifecycle.test.js +966 -0
- package/packages/core/dist/workflow/team-lifecycle.test.js.map +1 -0
- package/packages/core/dist/workflow/workflow-graph-validation.d.ts +65 -0
- package/packages/core/dist/workflow/workflow-graph-validation.d.ts.map +1 -0
- package/packages/core/dist/workflow/workflow-graph-validation.js +190 -0
- package/packages/core/dist/workflow/workflow-graph-validation.js.map +1 -0
- package/packages/core/dist/workflow/workflow-graph-validation.test.d.ts +18 -0
- package/packages/core/dist/workflow/workflow-graph-validation.test.d.ts.map +1 -0
- package/packages/core/dist/workflow/workflow-graph-validation.test.js +706 -0
- package/packages/core/dist/workflow/workflow-graph-validation.test.js.map +1 -0
- package/pennyfarthing-dist/commands/pf-setup.md +4 -2
- package/pennyfarthing-dist/personas/themes/discworld.yaml +16 -24
- package/pennyfarthing-dist/scripts/lib/run-pf.sh +3 -0
- package/pennyfarthing-dist/workflows/project-setup/steps/step-08-theme-packs.md +1 -1
- package/pennyfarthing-dist/workflows/project-setup/steps/step-09-jira.md +92 -0
- package/pennyfarthing-dist/workflows/project-setup/steps/{step-09-cyclist.md → step-10-cyclist.md} +2 -2
- package/pennyfarthing-dist/workflows/project-setup/steps/{step-10-complete.md → step-11-complete.md} +2 -1
- package/pennyfarthing_scripts/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/bellmode_hook.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/config.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/context.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/hooks.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/jira_bidirectional_sync.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/jira_epic_creation.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/jira_sync.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/jira_sync_story.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/output.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/patch_mode.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/pretooluse_hook.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/schema_validation_hook.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/session_start_hook.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/workflow.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bc/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bc/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bc/__pycache__/focus.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bc/__pycache__/split.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bikerack/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bikerack/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bikerack/__pycache__/audit_log_panel.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bikerack/__pycache__/background_panel.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bikerack/__pycache__/base_panel.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bikerack/__pycache__/changed_panel.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bikerack/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bikerack/__pycache__/context_meter_footer.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bikerack/__pycache__/debug_panel.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bikerack/__pycache__/diffs_panel.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bikerack/__pycache__/events.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bikerack/__pycache__/git_panel.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bikerack/__pycache__/launcher.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bikerack/__pycache__/portrait.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bikerack/__pycache__/portrait_resolver.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bikerack/__pycache__/progress_panel.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bikerack/__pycache__/sprint_panel.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bikerack/__pycache__/story_detail_data.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bikerack/__pycache__/story_detail_screen.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bikerack/__pycache__/tui.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bikerack/__pycache__/ws_client.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bmad/__init__.py +1 -0
- package/pennyfarthing_scripts/bmad/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bmad/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bmad/__pycache__/parser.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bmad/__pycache__/sync.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bmad/__pycache__/test_parser.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/bmad/__pycache__/test_sync.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/bmad/cli.py +197 -0
- package/pennyfarthing_scripts/bmad/importer.py +200 -0
- package/pennyfarthing_scripts/bmad/parser.py +233 -0
- package/pennyfarthing_scripts/bmad/sync.py +464 -0
- package/pennyfarthing_scripts/bmad/test_parser.py +253 -0
- package/pennyfarthing_scripts/bmad/test_sync.py +223 -0
- package/pennyfarthing_scripts/brownfield/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/brownfield/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/brownfield/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/brownfield/__pycache__/discover.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/cli.py +5 -0
- package/pennyfarthing_scripts/codemarkers/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/codemarkers/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/codemarkers/__pycache__/analyze.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/codemarkers/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/codemarkers/__pycache__/formatters.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/codemarkers/__pycache__/models.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/common/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/common/__pycache__/config.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/common/__pycache__/output.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/common/__pycache__/themes.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/complexity/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/complexity/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/complexity/__pycache__/analyze.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/complexity/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/complexity/__pycache__/formatters.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/complexity/__pycache__/models.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/consultation/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/consultation/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/deadcode/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/deadcode/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/deadcode/__pycache__/analyze.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/deadcode/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/deadcode/__pycache__/formatters.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/deadcode/__pycache__/models.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/dependencies/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/dependencies/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/dependencies/__pycache__/analyze.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/dependencies/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/dependencies/__pycache__/formatters.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/dependencies/__pycache__/models.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/epic/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/epic/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/gate/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/gate/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/gate/__pycache__/validate.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/git/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/git/__pycache__/create_branches.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/git/__pycache__/hooks_installer.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/git/__pycache__/repos.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/git/__pycache__/status_all.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/git/__pycache__/worktree.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/git_group/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/git_group/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/handoff/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/handoff/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/handoff/__pycache__/complete_phase.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/handoff/__pycache__/gate_file.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/handoff/__pycache__/gate_runner.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/handoff/__pycache__/marker.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/handoff/__pycache__/phase_check.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/handoff/__pycache__/resolve_gate.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/healthscore/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/healthscore/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/healthscore/__pycache__/analyze.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/healthscore/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/healthscore/__pycache__/formatters.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/healthscore/__pycache__/models.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/hooks/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/hooks/__pycache__/bell_mode.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/hooks/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/hooks/__pycache__/context_breaker.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/hooks/__pycache__/context_warning.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/hooks/__pycache__/cyclist_pretooluse.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/hooks/__pycache__/pre_edit_check.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/hooks/__pycache__/reflector_check.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/hooks/__pycache__/schema_validation.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/hooks/__pycache__/session_start.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/hooks/__pycache__/session_stop.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/hooks/__pycache__/sprint_yaml_validation.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/hooks/__pycache__/statusline.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/hotspots/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/hotspots/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/hotspots/__pycache__/analyze.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/hotspots/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/hotspots/__pycache__/formatters.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/hotspots/__pycache__/models.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/bidirectional.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/claim.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/client.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/create.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/epic.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/operations.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/reconcile.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/story.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/sync.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/client.py +14 -2
- package/pennyfarthing_scripts/launch/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/launch/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/migration/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/migration/__pycache__/session.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/migration/__pycache__/skill.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/migration/__pycache__/step.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/migration/__pycache__/validate.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/preflight/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/preflight/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/preflight/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/preflight/__pycache__/finish.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/prime/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/prime/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/prime/__pycache__/heatmap.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/prime/__pycache__/loader.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/prime/__pycache__/models.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/prime/__pycache__/persona.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/prime/__pycache__/session.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/prime/__pycache__/tiers.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/prime/__pycache__/version_sentinel.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/prime/__pycache__/workflow.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/session/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/session/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/settings/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/settings/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/settings/__pycache__/settings.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/settings/settings.py +1 -1
- package/pennyfarthing_scripts/sprint/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/archive.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/archive_epic.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/epic_add.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/epic_update.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/import_epic.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/loader.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/status.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/story_add.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/story_finish.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/story_update.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/validate_cmd.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/validator.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/work.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/yaml_io.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/story/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/story/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/story/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/story/__pycache__/create.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/story/__pycache__/size.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/story/__pycache__/template.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/conftest.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_108_2_remove_handoff_fallback.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_archive_epic.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_bc.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_bikerack.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_brownfield.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_cli_modules.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_cli_normalization.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_codemarkers.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_common.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_confidence_sm_evaluation.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_confidence_sm_gate.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_epic_shard_validation.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_gate_file_resolution.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_gate_runner.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_git_utils.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_handoff_cli.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_handoff_e2e.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_healthscore.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_jira_package.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_package_structure.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_patch_mode.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_prime.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_resolve_gate_file_field.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_sprint_package.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_sprint_panel.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_sprint_validator.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_story_add.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_story_package.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_story_update.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_tiers.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_token_counting.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_topology_loader.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_tui_focus.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_tui_panel_persistence.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_validate_cmd.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_version_sentinel.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_workflow_check.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_workflow_list_team.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_workflow_list_team.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_yaml_io.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/theme/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/theme/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/validate/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/validate/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/validate/adapters/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/validate/adapters/__pycache__/agent.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/validate/adapters/__pycache__/schema.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/validate/adapters/__pycache__/skill_command.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/validate/adapters/__pycache__/sprint.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/validate/adapters/__pycache__/tandem_awareness.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/validate/adapters/__pycache__/team_mode.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/validate/adapters/__pycache__/workflow.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/workflow/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/workflow/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/workflow/__pycache__/helpers.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/workflow/__pycache__/scale.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/workflow/__pycache__/state.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/workflow/__pycache__/team_lifecycle.cpython-314.pyc +0 -0
- package/packages/core/dist/cli/commands/stale-artifacts-cleanup.test.d.ts +0 -17
- package/packages/core/dist/cli/commands/stale-artifacts-cleanup.test.d.ts.map +0 -1
- package/packages/core/dist/cli/commands/stale-artifacts-cleanup.test.js +0 -470
- package/packages/core/dist/cli/commands/stale-artifacts-cleanup.test.js.map +0 -1
- package/packages/core/dist/cli/cyclist-migration.test.d.ts +0 -16
- package/packages/core/dist/cli/cyclist-migration.test.d.ts.map +0 -1
- package/packages/core/dist/cli/cyclist-migration.test.js +0 -229
- package/packages/core/dist/cli/cyclist-migration.test.js.map +0 -1
- package/packages/core/dist/cli/utils/stale-artifacts.d.ts +0 -59
- package/packages/core/dist/cli/utils/stale-artifacts.d.ts.map +0 -1
- package/packages/core/dist/cli/utils/stale-artifacts.js +0 -163
- package/packages/core/dist/cli/utils/stale-artifacts.js.map +0 -1
- package/packages/core/dist/scripts/benchmark-integration.d.ts +0 -182
- package/packages/core/dist/scripts/benchmark-integration.d.ts.map +0 -1
- package/packages/core/dist/scripts/benchmark-integration.js +0 -691
- package/packages/core/dist/scripts/benchmark-integration.js.map +0 -1
- package/packages/core/dist/scripts/job-fair-aggregator.d.ts +0 -150
- package/packages/core/dist/scripts/job-fair-aggregator.d.ts.map +0 -1
- package/packages/core/dist/scripts/job-fair-aggregator.js +0 -547
- package/packages/core/dist/scripts/job-fair-aggregator.js.map +0 -1
- package/packages/core/dist/scripts/theme-detail.test.d.ts.map +0 -1
- package/packages/core/dist/scripts/theme-detail.test.js.map +0 -1
- package/pennyfarthing-dist/scripts/hooks/__pycache__/question_reflector_check.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/__init__.cpython-311.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/cli.cpython-311.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/context.cpython-311.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/jira.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/sprint.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/workflow.cpython-311.pyc +0 -0
- package/pennyfarthing_scripts/bc/__pycache__/__init__.cpython-311.pyc +0 -0
- package/pennyfarthing_scripts/bc/__pycache__/cli.cpython-311.pyc +0 -0
- package/pennyfarthing_scripts/bc/__pycache__/focus.cpython-311.pyc +0 -0
- package/pennyfarthing_scripts/bikerack/__pycache__/__init__.cpython-311.pyc +0 -0
- package/pennyfarthing_scripts/bikerack/__pycache__/cli.cpython-311.pyc +0 -0
- package/pennyfarthing_scripts/bikerack/__pycache__/launcher.cpython-311.pyc +0 -0
- package/pennyfarthing_scripts/common/__pycache__/__init__.cpython-311.pyc +0 -0
- package/pennyfarthing_scripts/common/__pycache__/config.cpython-311.pyc +0 -0
- package/pennyfarthing_scripts/common/__pycache__/output.cpython-311.pyc +0 -0
- package/pennyfarthing_scripts/common/__pycache__/pr_config.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/consultation/__pycache__/__init__.cpython-311.pyc +0 -0
- package/pennyfarthing_scripts/consultation/__pycache__/cli.cpython-311.pyc +0 -0
- package/pennyfarthing_scripts/consultation/__pycache__/dialogue_manager.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/deadcode/__pycache__/__init__.cpython-311.pyc +0 -0
- package/pennyfarthing_scripts/deadcode/__pycache__/cli.cpython-311.pyc +0 -0
- package/pennyfarthing_scripts/epic/__pycache__/__init__.cpython-311.pyc +0 -0
- package/pennyfarthing_scripts/epic/__pycache__/cli.cpython-311.pyc +0 -0
- package/pennyfarthing_scripts/git_group/__pycache__/__init__.cpython-311.pyc +0 -0
- package/pennyfarthing_scripts/git_group/__pycache__/cli.cpython-311.pyc +0 -0
- package/pennyfarthing_scripts/handoff/__pycache__/__init__.cpython-311.pyc +0 -0
- package/pennyfarthing_scripts/handoff/__pycache__/cli.cpython-311.pyc +0 -0
- package/pennyfarthing_scripts/healthscore/__pycache__/__init__.cpython-311.pyc +0 -0
- package/pennyfarthing_scripts/healthscore/__pycache__/analyze.cpython-311.pyc +0 -0
- package/pennyfarthing_scripts/healthscore/__pycache__/cli.cpython-311.pyc +0 -0
- package/pennyfarthing_scripts/healthscore/__pycache__/models.cpython-311.pyc +0 -0
- package/pennyfarthing_scripts/hooks/__pycache__/__init__.cpython-311.pyc +0 -0
- package/pennyfarthing_scripts/hooks/__pycache__/bell_mode.cpython-311.pyc +0 -0
- package/pennyfarthing_scripts/hooks/__pycache__/cli.cpython-311.pyc +0 -0
- package/pennyfarthing_scripts/hooks/__pycache__/context_breaker.cpython-311.pyc +0 -0
- package/pennyfarthing_scripts/hooks/__pycache__/context_warning.cpython-311.pyc +0 -0
- package/pennyfarthing_scripts/hooks/__pycache__/cyclist_pretooluse.cpython-311.pyc +0 -0
- package/pennyfarthing_scripts/hooks/__pycache__/pre_edit_check.cpython-311.pyc +0 -0
- package/pennyfarthing_scripts/hooks/__pycache__/reflector_check.cpython-311.pyc +0 -0
- package/pennyfarthing_scripts/hooks/__pycache__/schema_validation.cpython-311.pyc +0 -0
- package/pennyfarthing_scripts/hooks/__pycache__/session_start.cpython-311.pyc +0 -0
- package/pennyfarthing_scripts/hooks/__pycache__/sprint_yaml_validation.cpython-311.pyc +0 -0
- package/pennyfarthing_scripts/hooks/__pycache__/statusline.cpython-311.pyc +0 -0
- package/pennyfarthing_scripts/hotspots/__pycache__/__init__.cpython-311.pyc +0 -0
- package/pennyfarthing_scripts/hotspots/__pycache__/analyze.cpython-311.pyc +0 -0
- package/pennyfarthing_scripts/hotspots/__pycache__/cli.cpython-311.pyc +0 -0
- package/pennyfarthing_scripts/hotspots/__pycache__/models.cpython-311.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/__init__.cpython-311.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/bidirectional.cpython-311.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/claim.cpython-311.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/cli.cpython-311.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/client.cpython-311.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/compat.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/create.cpython-311.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/epic.cpython-311.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/mappings.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/models.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/operations.cpython-311.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/reconcile.cpython-311.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/story.cpython-311.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/sync.cpython-311.pyc +0 -0
- package/pennyfarthing_scripts/launch/__pycache__/__init__.cpython-311.pyc +0 -0
- package/pennyfarthing_scripts/launch/__pycache__/cli.cpython-311.pyc +0 -0
- package/pennyfarthing_scripts/migration/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/migration/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/prime/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/session/__pycache__/__init__.cpython-311.pyc +0 -0
- package/pennyfarthing_scripts/session/__pycache__/cli.cpython-311.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/__init__.cpython-311.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/archive.cpython-311.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/cli.cpython-311.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/epic_add.cpython-311.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/epic_update.cpython-311.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/loader.cpython-311.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/status.cpython-311.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/story_add.cpython-311.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/story_update.cpython-311.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/validate_cmd.cpython-311.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/validator.cpython-311.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/work.cpython-311.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/yaml_io.cpython-311.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_108_1_gate_migration.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_dialogue_manager.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_workflow_cli.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/theme/__pycache__/__init__.cpython-311.pyc +0 -0
- package/pennyfarthing_scripts/theme/__pycache__/cli.cpython-311.pyc +0 -0
- package/pennyfarthing_scripts/validate/__pycache__/__init__.cpython-311.pyc +0 -0
- package/pennyfarthing_scripts/validate/__pycache__/cli.cpython-311.pyc +0 -0
- package/pennyfarthing_scripts/workflow/__pycache__/__init__.cpython-311.pyc +0 -0
- package/pennyfarthing_scripts/workflow/__pycache__/cli.cpython-311.pyc +0 -0
- package/pennyfarthing_scripts/workflow/__pycache__/scale.cpython-311.pyc +0 -0
- package/pennyfarthing_scripts/workflow/__pycache__/state.cpython-311.pyc +0 -0
|
@@ -0,0 +1,966 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for Story 86-10: Phase-scoped team lifecycle + gate hooks
|
|
3
|
+
*
|
|
4
|
+
* RED state tests for native Agent Teams lifecycle in phased workflows.
|
|
5
|
+
* These tests cover all 9 acceptance criteria:
|
|
6
|
+
*
|
|
7
|
+
* AC1: Lead creates team on phase entry when workflow has team block
|
|
8
|
+
* AC2: Lead spawns teammates per workflow YAML teammates config
|
|
9
|
+
* AC3: TaskCompleted hook enforces gate checks
|
|
10
|
+
* AC4: TeammateIdle hook validates teammate work
|
|
11
|
+
* AC5: Lead shuts down all teammates before exit protocol
|
|
12
|
+
* AC6: TeamDelete runs before pf handoff (full cleanup before marker)
|
|
13
|
+
* AC7: Session file updated with teammate activity summary
|
|
14
|
+
* AC8: Sidecar file locking for concurrent teammate writes
|
|
15
|
+
* AC9: Graceful degradation when teammate crashes
|
|
16
|
+
*
|
|
17
|
+
* Run with: pnpm build && pnpm test
|
|
18
|
+
*/
|
|
19
|
+
import { describe, it, beforeEach, afterEach } from 'node:test';
|
|
20
|
+
import assert from 'node:assert';
|
|
21
|
+
import { mkdirSync, rmSync, existsSync } from 'node:fs';
|
|
22
|
+
import { join, dirname } from 'node:path';
|
|
23
|
+
import { fileURLToPath } from 'node:url';
|
|
24
|
+
import { createTeam, spawnTeammates, shutdownAllTeammates, cleanupTeam, checkGateOnTaskCompleted, checkGateOnTeammateIdle, generateTeamSummary, acquireSidecarLock, releaseSidecarLock, getActiveTeam, _resetForTesting, } from './team-lifecycle.js';
|
|
25
|
+
// Get directory for test fixtures
|
|
26
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
27
|
+
const TEST_DIR = join(__dirname, '__test_team_lifecycle__');
|
|
28
|
+
// =============================================================================
|
|
29
|
+
// Test Fixtures
|
|
30
|
+
// =============================================================================
|
|
31
|
+
/** Phase with team config (Dev + Architect) */
|
|
32
|
+
const PHASE_WITH_TEAM = {
|
|
33
|
+
name: 'green',
|
|
34
|
+
agent: 'dev',
|
|
35
|
+
input: ['failing_tests'],
|
|
36
|
+
output: ['implementation', 'passing_tests'],
|
|
37
|
+
team: {
|
|
38
|
+
teammates: [
|
|
39
|
+
{ agent: 'architect', task: 'Review implementation approach and patterns' },
|
|
40
|
+
{ agent: 'tea', task: 'Verify tests stay green, flag regressions' },
|
|
41
|
+
],
|
|
42
|
+
model: 'sonnet',
|
|
43
|
+
display: 'in-process',
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
/** Phase with team config (single teammate) */
|
|
47
|
+
const PHASE_WITH_SINGLE_TEAMMATE = {
|
|
48
|
+
name: 'review',
|
|
49
|
+
agent: 'reviewer',
|
|
50
|
+
team: {
|
|
51
|
+
teammates: [
|
|
52
|
+
{ agent: 'architect', task: 'Validate architectural patterns' },
|
|
53
|
+
],
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
/** Phase with team and gate */
|
|
57
|
+
const PHASE_WITH_TEAM_AND_GATE = {
|
|
58
|
+
name: 'green',
|
|
59
|
+
agent: 'dev',
|
|
60
|
+
team: {
|
|
61
|
+
teammates: [
|
|
62
|
+
{ agent: 'tea', task: 'Verify tests pass' },
|
|
63
|
+
],
|
|
64
|
+
},
|
|
65
|
+
gate: {
|
|
66
|
+
type: 'tests_pass',
|
|
67
|
+
condition: 'All tests must pass before phase completion',
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
/** Phase without team config */
|
|
71
|
+
const PHASE_WITHOUT_TEAM = {
|
|
72
|
+
name: 'red',
|
|
73
|
+
agent: 'tea',
|
|
74
|
+
input: ['session_file'],
|
|
75
|
+
output: ['failing_tests'],
|
|
76
|
+
};
|
|
77
|
+
/** No-op adapter for tests that don't need real process interaction */
|
|
78
|
+
function createMockAdapter(overrides) {
|
|
79
|
+
return {
|
|
80
|
+
createTeam: async (params) => ({ teamName: params.teamName }),
|
|
81
|
+
deleteTeam: async () => { },
|
|
82
|
+
spawnTeammate: async (params) => ({ agentId: `mock-${params.agent}-001` }),
|
|
83
|
+
shutdownTeammate: async () => { },
|
|
84
|
+
...overrides,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
// =============================================================================
|
|
88
|
+
// Test Suite
|
|
89
|
+
// =============================================================================
|
|
90
|
+
describe('86-10: Phase-scoped Team Lifecycle', () => {
|
|
91
|
+
beforeEach(() => {
|
|
92
|
+
_resetForTesting();
|
|
93
|
+
if (existsSync(TEST_DIR)) {
|
|
94
|
+
rmSync(TEST_DIR, { recursive: true });
|
|
95
|
+
}
|
|
96
|
+
mkdirSync(TEST_DIR, { recursive: true });
|
|
97
|
+
mkdirSync(join(TEST_DIR, '.session'), { recursive: true });
|
|
98
|
+
});
|
|
99
|
+
afterEach(() => {
|
|
100
|
+
_resetForTesting();
|
|
101
|
+
if (existsSync(TEST_DIR)) {
|
|
102
|
+
rmSync(TEST_DIR, { recursive: true });
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
// ===========================================================================
|
|
106
|
+
// AC1: Lead creates team on phase entry when workflow has team block
|
|
107
|
+
// ===========================================================================
|
|
108
|
+
describe('AC1: Create team on phase entry', () => {
|
|
109
|
+
it('should create team when phase has team config', async () => {
|
|
110
|
+
const params = {
|
|
111
|
+
phase: PHASE_WITH_TEAM,
|
|
112
|
+
storyId: '86-10',
|
|
113
|
+
sessionDir: join(TEST_DIR, '.session'),
|
|
114
|
+
adapter: createMockAdapter(),
|
|
115
|
+
};
|
|
116
|
+
const result = await createTeam(params);
|
|
117
|
+
assert.strictEqual(result.success, true, 'Team creation should succeed');
|
|
118
|
+
assert.ok(result.data, 'Should return team handle');
|
|
119
|
+
assert.ok(result.data.teamName, 'Handle should have team name');
|
|
120
|
+
assert.strictEqual(result.data.storyId, '86-10', 'Story ID should match');
|
|
121
|
+
assert.strictEqual(result.data.phase, 'green', 'Phase should match');
|
|
122
|
+
});
|
|
123
|
+
it('should generate team name from storyId and phase', async () => {
|
|
124
|
+
const result = await createTeam({
|
|
125
|
+
phase: PHASE_WITH_TEAM,
|
|
126
|
+
storyId: '86-10',
|
|
127
|
+
sessionDir: join(TEST_DIR, '.session'),
|
|
128
|
+
adapter: createMockAdapter(),
|
|
129
|
+
});
|
|
130
|
+
assert.strictEqual(result.success, true);
|
|
131
|
+
assert.ok(result.data.teamName.includes('86-10'), 'Team name should include story ID');
|
|
132
|
+
assert.ok(result.data.teamName.includes('green'), 'Team name should include phase name');
|
|
133
|
+
});
|
|
134
|
+
it('should set createdAt timestamp on team handle', async () => {
|
|
135
|
+
const result = await createTeam({
|
|
136
|
+
phase: PHASE_WITH_TEAM,
|
|
137
|
+
storyId: '86-10',
|
|
138
|
+
sessionDir: join(TEST_DIR, '.session'),
|
|
139
|
+
adapter: createMockAdapter(),
|
|
140
|
+
});
|
|
141
|
+
assert.strictEqual(result.success, true);
|
|
142
|
+
assert.ok(result.data.createdAt, 'Should have createdAt timestamp');
|
|
143
|
+
// Verify it's a valid ISO date string
|
|
144
|
+
const date = new Date(result.data.createdAt);
|
|
145
|
+
assert.ok(!isNaN(date.getTime()), 'createdAt should be valid ISO date');
|
|
146
|
+
});
|
|
147
|
+
it('should register team in active registry', async () => {
|
|
148
|
+
await createTeam({
|
|
149
|
+
phase: PHASE_WITH_TEAM,
|
|
150
|
+
storyId: '86-10',
|
|
151
|
+
sessionDir: join(TEST_DIR, '.session'),
|
|
152
|
+
adapter: createMockAdapter(),
|
|
153
|
+
});
|
|
154
|
+
const active = getActiveTeam('86-10');
|
|
155
|
+
assert.ok(active, 'Team should be in active registry');
|
|
156
|
+
assert.strictEqual(active.storyId, '86-10');
|
|
157
|
+
});
|
|
158
|
+
it('should call adapter.createTeam when adapter provided', async () => {
|
|
159
|
+
let createCalled = false;
|
|
160
|
+
let createParams = {};
|
|
161
|
+
const adapter = createMockAdapter({
|
|
162
|
+
createTeam: async (params) => {
|
|
163
|
+
createCalled = true;
|
|
164
|
+
createParams = params;
|
|
165
|
+
return { teamName: params.teamName };
|
|
166
|
+
},
|
|
167
|
+
});
|
|
168
|
+
await createTeam({
|
|
169
|
+
phase: PHASE_WITH_TEAM,
|
|
170
|
+
storyId: '86-10',
|
|
171
|
+
sessionDir: join(TEST_DIR, '.session'),
|
|
172
|
+
adapter,
|
|
173
|
+
});
|
|
174
|
+
assert.strictEqual(createCalled, true, 'Adapter createTeam must be called');
|
|
175
|
+
assert.ok(createParams.teamName, 'Should pass team name to adapter');
|
|
176
|
+
});
|
|
177
|
+
it('should return no-op for phase without team config', async () => {
|
|
178
|
+
const result = await createTeam({
|
|
179
|
+
phase: PHASE_WITHOUT_TEAM,
|
|
180
|
+
storyId: '86-10',
|
|
181
|
+
sessionDir: join(TEST_DIR, '.session'),
|
|
182
|
+
adapter: createMockAdapter(),
|
|
183
|
+
});
|
|
184
|
+
assert.strictEqual(result.success, true, 'Should succeed as no-op');
|
|
185
|
+
assert.strictEqual(result.data, undefined, 'No handle for non-team phase');
|
|
186
|
+
});
|
|
187
|
+
it('should not register anything for non-team phase', async () => {
|
|
188
|
+
await createTeam({
|
|
189
|
+
phase: PHASE_WITHOUT_TEAM,
|
|
190
|
+
storyId: '86-10',
|
|
191
|
+
sessionDir: join(TEST_DIR, '.session'),
|
|
192
|
+
});
|
|
193
|
+
const active = getActiveTeam('86-10');
|
|
194
|
+
assert.strictEqual(active, null, 'No active team for non-team phase');
|
|
195
|
+
});
|
|
196
|
+
it('should clean up existing team before creating new one for same story', async () => {
|
|
197
|
+
const deleted = [];
|
|
198
|
+
const adapter = createMockAdapter({
|
|
199
|
+
deleteTeam: async (teamName) => { deleted.push(teamName); },
|
|
200
|
+
});
|
|
201
|
+
// First team
|
|
202
|
+
await createTeam({
|
|
203
|
+
phase: PHASE_WITH_TEAM,
|
|
204
|
+
storyId: '86-10',
|
|
205
|
+
sessionDir: join(TEST_DIR, '.session'),
|
|
206
|
+
adapter,
|
|
207
|
+
});
|
|
208
|
+
// Second team for same story (different phase)
|
|
209
|
+
await createTeam({
|
|
210
|
+
phase: PHASE_WITH_SINGLE_TEAMMATE,
|
|
211
|
+
storyId: '86-10',
|
|
212
|
+
sessionDir: join(TEST_DIR, '.session'),
|
|
213
|
+
adapter,
|
|
214
|
+
});
|
|
215
|
+
assert.strictEqual(deleted.length, 1, 'First team should be deleted');
|
|
216
|
+
const active = getActiveTeam('86-10');
|
|
217
|
+
assert.strictEqual(active.phase, 'review', 'Second team should be active');
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
// ===========================================================================
|
|
221
|
+
// AC2: Lead spawns teammates per workflow YAML teammates config
|
|
222
|
+
// ===========================================================================
|
|
223
|
+
describe('AC2: Spawn teammates per YAML config', () => {
|
|
224
|
+
it('should spawn all teammates from team config', async () => {
|
|
225
|
+
const createResult = await createTeam({
|
|
226
|
+
phase: PHASE_WITH_TEAM,
|
|
227
|
+
storyId: '86-10',
|
|
228
|
+
sessionDir: join(TEST_DIR, '.session'),
|
|
229
|
+
adapter: createMockAdapter(),
|
|
230
|
+
});
|
|
231
|
+
const handle = createResult.data;
|
|
232
|
+
const result = await spawnTeammates(handle, PHASE_WITH_TEAM.team, '86-10', 'green', createMockAdapter());
|
|
233
|
+
assert.strictEqual(result.success, true, 'Spawn should succeed');
|
|
234
|
+
assert.ok(result.data, 'Should return teammate handles');
|
|
235
|
+
assert.strictEqual(result.data.length, 2, 'Should spawn 2 teammates');
|
|
236
|
+
});
|
|
237
|
+
it('should spawn teammates with correct agent names', async () => {
|
|
238
|
+
const createResult = await createTeam({
|
|
239
|
+
phase: PHASE_WITH_TEAM,
|
|
240
|
+
storyId: '86-10',
|
|
241
|
+
sessionDir: join(TEST_DIR, '.session'),
|
|
242
|
+
adapter: createMockAdapter(),
|
|
243
|
+
});
|
|
244
|
+
const result = await spawnTeammates(createResult.data, PHASE_WITH_TEAM.team, '86-10', 'green', createMockAdapter());
|
|
245
|
+
const agents = result.data.map((t) => t.agent).sort();
|
|
246
|
+
assert.deepStrictEqual(agents, ['architect', 'tea'], 'Should match YAML config');
|
|
247
|
+
});
|
|
248
|
+
it('should pass task descriptions to spawned teammates', async () => {
|
|
249
|
+
const createResult = await createTeam({
|
|
250
|
+
phase: PHASE_WITH_TEAM,
|
|
251
|
+
storyId: '86-10',
|
|
252
|
+
sessionDir: join(TEST_DIR, '.session'),
|
|
253
|
+
adapter: createMockAdapter(),
|
|
254
|
+
});
|
|
255
|
+
const result = await spawnTeammates(createResult.data, PHASE_WITH_TEAM.team, '86-10', 'green', createMockAdapter());
|
|
256
|
+
const architect = result.data.find((t) => t.agent === 'architect');
|
|
257
|
+
assert.ok(architect, 'Should have architect teammate');
|
|
258
|
+
assert.strictEqual(architect.task, 'Review implementation approach and patterns', 'Task should match YAML config');
|
|
259
|
+
});
|
|
260
|
+
it('should call adapter.spawnTeammate for each teammate', async () => {
|
|
261
|
+
const spawnedAgents = [];
|
|
262
|
+
const adapter = createMockAdapter({
|
|
263
|
+
spawnTeammate: async (params) => {
|
|
264
|
+
spawnedAgents.push(params.agent);
|
|
265
|
+
return { agentId: `mock-${params.agent}` };
|
|
266
|
+
},
|
|
267
|
+
});
|
|
268
|
+
const createResult = await createTeam({
|
|
269
|
+
phase: PHASE_WITH_TEAM,
|
|
270
|
+
storyId: '86-10',
|
|
271
|
+
sessionDir: join(TEST_DIR, '.session'),
|
|
272
|
+
adapter,
|
|
273
|
+
});
|
|
274
|
+
await spawnTeammates(createResult.data, PHASE_WITH_TEAM.team, '86-10', 'green', adapter);
|
|
275
|
+
assert.deepStrictEqual(spawnedAgents.sort(), ['architect', 'tea'], 'Should call adapter for each teammate');
|
|
276
|
+
});
|
|
277
|
+
it('should set initial teammate status to spawned', async () => {
|
|
278
|
+
const createResult = await createTeam({
|
|
279
|
+
phase: PHASE_WITH_TEAM,
|
|
280
|
+
storyId: '86-10',
|
|
281
|
+
sessionDir: join(TEST_DIR, '.session'),
|
|
282
|
+
adapter: createMockAdapter(),
|
|
283
|
+
});
|
|
284
|
+
const result = await spawnTeammates(createResult.data, PHASE_WITH_TEAM.team, '86-10', 'green', createMockAdapter());
|
|
285
|
+
for (const teammate of result.data) {
|
|
286
|
+
assert.strictEqual(teammate.status, 'spawned', `Teammate ${teammate.agent} should have status 'spawned'`);
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
it('should update team handle with spawned teammates', async () => {
|
|
290
|
+
const createResult = await createTeam({
|
|
291
|
+
phase: PHASE_WITH_TEAM,
|
|
292
|
+
storyId: '86-10',
|
|
293
|
+
sessionDir: join(TEST_DIR, '.session'),
|
|
294
|
+
adapter: createMockAdapter(),
|
|
295
|
+
});
|
|
296
|
+
await spawnTeammates(createResult.data, PHASE_WITH_TEAM.team, '86-10', 'green', createMockAdapter());
|
|
297
|
+
const active = getActiveTeam('86-10');
|
|
298
|
+
assert.ok(active, 'Team should still be in registry');
|
|
299
|
+
assert.strictEqual(active.teammates.length, 2, 'Active team should have spawned teammates');
|
|
300
|
+
});
|
|
301
|
+
it('should handle single teammate config', async () => {
|
|
302
|
+
const createResult = await createTeam({
|
|
303
|
+
phase: PHASE_WITH_SINGLE_TEAMMATE,
|
|
304
|
+
storyId: '86-10',
|
|
305
|
+
sessionDir: join(TEST_DIR, '.session'),
|
|
306
|
+
adapter: createMockAdapter(),
|
|
307
|
+
});
|
|
308
|
+
const result = await spawnTeammates(createResult.data, PHASE_WITH_SINGLE_TEAMMATE.team, '86-10', 'review', createMockAdapter());
|
|
309
|
+
assert.strictEqual(result.success, true);
|
|
310
|
+
assert.strictEqual(result.data.length, 1, 'Should spawn exactly 1 teammate');
|
|
311
|
+
assert.strictEqual(result.data[0].agent, 'architect');
|
|
312
|
+
});
|
|
313
|
+
});
|
|
314
|
+
// ===========================================================================
|
|
315
|
+
// AC3: TaskCompleted hook enforces gate checks
|
|
316
|
+
// ===========================================================================
|
|
317
|
+
describe('AC3: TaskCompleted gate enforcement', () => {
|
|
318
|
+
it('should check gate when task completed event fires', () => {
|
|
319
|
+
const handle = {
|
|
320
|
+
teamName: '86-10-green',
|
|
321
|
+
storyId: '86-10',
|
|
322
|
+
phase: 'green',
|
|
323
|
+
teammates: [
|
|
324
|
+
{ agent: 'architect', status: 'idle' },
|
|
325
|
+
{ agent: 'tea', status: 'idle' },
|
|
326
|
+
],
|
|
327
|
+
createdAt: new Date().toISOString(),
|
|
328
|
+
};
|
|
329
|
+
const result = checkGateOnTaskCompleted(handle, PHASE_WITH_TEAM_AND_GATE);
|
|
330
|
+
assert.ok('passed' in result, 'Should return gate check result');
|
|
331
|
+
assert.ok('gate' in result, 'Should include gate type');
|
|
332
|
+
assert.ok(typeof result.passed === 'boolean', 'passed must be boolean');
|
|
333
|
+
});
|
|
334
|
+
it('should return gate type from phase config', () => {
|
|
335
|
+
const handle = {
|
|
336
|
+
teamName: '86-10-green',
|
|
337
|
+
storyId: '86-10',
|
|
338
|
+
phase: 'green',
|
|
339
|
+
teammates: [{ agent: 'tea', status: 'idle' }],
|
|
340
|
+
createdAt: new Date().toISOString(),
|
|
341
|
+
};
|
|
342
|
+
const result = checkGateOnTaskCompleted(handle, PHASE_WITH_TEAM_AND_GATE);
|
|
343
|
+
assert.strictEqual(result.gate, 'tests_pass', 'Should reflect phase gate type');
|
|
344
|
+
});
|
|
345
|
+
it('should fail gate when teammates still active', () => {
|
|
346
|
+
const handle = {
|
|
347
|
+
teamName: '86-10-green',
|
|
348
|
+
storyId: '86-10',
|
|
349
|
+
phase: 'green',
|
|
350
|
+
teammates: [
|
|
351
|
+
{ agent: 'tea', status: 'active' }, // still working
|
|
352
|
+
],
|
|
353
|
+
createdAt: new Date().toISOString(),
|
|
354
|
+
};
|
|
355
|
+
const result = checkGateOnTaskCompleted(handle, PHASE_WITH_TEAM_AND_GATE);
|
|
356
|
+
assert.strictEqual(result.passed, false, 'Gate should fail when teammates still active');
|
|
357
|
+
assert.ok(result.reason, 'Should provide failure reason');
|
|
358
|
+
});
|
|
359
|
+
it('should pass gate when all teammates idle and criteria met', () => {
|
|
360
|
+
const handle = {
|
|
361
|
+
teamName: '86-10-green',
|
|
362
|
+
storyId: '86-10',
|
|
363
|
+
phase: 'green',
|
|
364
|
+
teammates: [
|
|
365
|
+
{ agent: 'tea', status: 'idle' },
|
|
366
|
+
],
|
|
367
|
+
createdAt: new Date().toISOString(),
|
|
368
|
+
};
|
|
369
|
+
const result = checkGateOnTaskCompleted(handle, PHASE_WITH_TEAM_AND_GATE);
|
|
370
|
+
assert.strictEqual(result.passed, true, 'Gate should pass when all idle');
|
|
371
|
+
});
|
|
372
|
+
it('should pass trivially when phase has no gate', () => {
|
|
373
|
+
const handle = {
|
|
374
|
+
teamName: '86-10-green',
|
|
375
|
+
storyId: '86-10',
|
|
376
|
+
phase: 'green',
|
|
377
|
+
teammates: [{ agent: 'architect', status: 'active' }],
|
|
378
|
+
createdAt: new Date().toISOString(),
|
|
379
|
+
};
|
|
380
|
+
// Phase with team but no gate
|
|
381
|
+
const phaseNoGate = {
|
|
382
|
+
name: 'green',
|
|
383
|
+
agent: 'dev',
|
|
384
|
+
team: { teammates: [{ agent: 'architect' }] },
|
|
385
|
+
};
|
|
386
|
+
const result = checkGateOnTaskCompleted(handle, phaseNoGate);
|
|
387
|
+
assert.strictEqual(result.passed, true, 'No gate = always passes');
|
|
388
|
+
});
|
|
389
|
+
});
|
|
390
|
+
// ===========================================================================
|
|
391
|
+
// AC4: TeammateIdle hook validates teammate work
|
|
392
|
+
// ===========================================================================
|
|
393
|
+
describe('AC4: TeammateIdle validation', () => {
|
|
394
|
+
it('should validate teammate when idle event fires', () => {
|
|
395
|
+
const handle = {
|
|
396
|
+
teamName: '86-10-green',
|
|
397
|
+
storyId: '86-10',
|
|
398
|
+
phase: 'green',
|
|
399
|
+
teammates: [
|
|
400
|
+
{ agent: 'tea', task: 'Verify tests pass', status: 'idle' },
|
|
401
|
+
],
|
|
402
|
+
createdAt: new Date().toISOString(),
|
|
403
|
+
};
|
|
404
|
+
const teammate = handle.teammates[0];
|
|
405
|
+
const result = checkGateOnTeammateIdle(handle, teammate, PHASE_WITH_TEAM_AND_GATE);
|
|
406
|
+
assert.ok('passed' in result, 'Should return gate check result');
|
|
407
|
+
assert.ok(typeof result.passed === 'boolean');
|
|
408
|
+
});
|
|
409
|
+
it('should include gate type in idle check result', () => {
|
|
410
|
+
const handle = {
|
|
411
|
+
teamName: '86-10-green',
|
|
412
|
+
storyId: '86-10',
|
|
413
|
+
phase: 'green',
|
|
414
|
+
teammates: [{ agent: 'tea', status: 'idle' }],
|
|
415
|
+
createdAt: new Date().toISOString(),
|
|
416
|
+
};
|
|
417
|
+
const result = checkGateOnTeammateIdle(handle, handle.teammates[0], PHASE_WITH_TEAM_AND_GATE);
|
|
418
|
+
assert.strictEqual(result.gate, 'tests_pass', 'Gate type should match phase config');
|
|
419
|
+
});
|
|
420
|
+
it('should fail if teammate task implies tests but tests not passing', () => {
|
|
421
|
+
const handle = {
|
|
422
|
+
teamName: '86-10-green',
|
|
423
|
+
storyId: '86-10',
|
|
424
|
+
phase: 'green',
|
|
425
|
+
teammates: [
|
|
426
|
+
{ agent: 'tea', task: 'Verify tests pass', status: 'crashed' },
|
|
427
|
+
],
|
|
428
|
+
createdAt: new Date().toISOString(),
|
|
429
|
+
};
|
|
430
|
+
const result = checkGateOnTeammateIdle(handle, handle.teammates[0], PHASE_WITH_TEAM_AND_GATE);
|
|
431
|
+
assert.strictEqual(result.passed, false, 'Crashed teammate should fail gate');
|
|
432
|
+
});
|
|
433
|
+
it('should pass when idle teammate completed task successfully', () => {
|
|
434
|
+
const handle = {
|
|
435
|
+
teamName: '86-10-green',
|
|
436
|
+
storyId: '86-10',
|
|
437
|
+
phase: 'green',
|
|
438
|
+
teammates: [
|
|
439
|
+
{ agent: 'tea', task: 'Verify tests pass', status: 'idle' },
|
|
440
|
+
],
|
|
441
|
+
createdAt: new Date().toISOString(),
|
|
442
|
+
};
|
|
443
|
+
const result = checkGateOnTeammateIdle(handle, handle.teammates[0], PHASE_WITH_TEAM_AND_GATE);
|
|
444
|
+
assert.strictEqual(result.passed, true, 'Idle teammate should pass');
|
|
445
|
+
});
|
|
446
|
+
it('should pass trivially when phase has no gate', () => {
|
|
447
|
+
const handle = {
|
|
448
|
+
teamName: '86-10-green',
|
|
449
|
+
storyId: '86-10',
|
|
450
|
+
phase: 'green',
|
|
451
|
+
teammates: [{ agent: 'architect', status: 'idle' }],
|
|
452
|
+
createdAt: new Date().toISOString(),
|
|
453
|
+
};
|
|
454
|
+
const phaseNoGate = {
|
|
455
|
+
name: 'green',
|
|
456
|
+
agent: 'dev',
|
|
457
|
+
team: { teammates: [{ agent: 'architect' }] },
|
|
458
|
+
};
|
|
459
|
+
const result = checkGateOnTeammateIdle(handle, handle.teammates[0], phaseNoGate);
|
|
460
|
+
assert.strictEqual(result.passed, true, 'No gate = always passes');
|
|
461
|
+
});
|
|
462
|
+
});
|
|
463
|
+
// ===========================================================================
|
|
464
|
+
// AC5: Lead shuts down all teammates before exit protocol
|
|
465
|
+
// ===========================================================================
|
|
466
|
+
describe('AC5: Shutdown all teammates', () => {
|
|
467
|
+
it('should shut down all active teammates', async () => {
|
|
468
|
+
const shutdownAgents = [];
|
|
469
|
+
const adapter = createMockAdapter({
|
|
470
|
+
shutdownTeammate: async (params) => { shutdownAgents.push(params.agent); },
|
|
471
|
+
});
|
|
472
|
+
const createResult = await createTeam({
|
|
473
|
+
phase: PHASE_WITH_TEAM,
|
|
474
|
+
storyId: '86-10',
|
|
475
|
+
sessionDir: join(TEST_DIR, '.session'),
|
|
476
|
+
adapter,
|
|
477
|
+
});
|
|
478
|
+
const handle = createResult.data;
|
|
479
|
+
// Manually add teammate handles to simulate spawned state
|
|
480
|
+
handle.teammates = [
|
|
481
|
+
{ agent: 'architect', status: 'active' },
|
|
482
|
+
{ agent: 'tea', status: 'idle' },
|
|
483
|
+
];
|
|
484
|
+
const result = await shutdownAllTeammates(handle, adapter);
|
|
485
|
+
assert.strictEqual(result.success, true, 'Shutdown should succeed');
|
|
486
|
+
assert.deepStrictEqual(shutdownAgents.sort(), ['architect', 'tea'], 'Should shut down all teammates');
|
|
487
|
+
});
|
|
488
|
+
it('should return count of teammates shut down', async () => {
|
|
489
|
+
const adapter = createMockAdapter();
|
|
490
|
+
const createResult = await createTeam({
|
|
491
|
+
phase: PHASE_WITH_TEAM,
|
|
492
|
+
storyId: '86-10',
|
|
493
|
+
sessionDir: join(TEST_DIR, '.session'),
|
|
494
|
+
adapter,
|
|
495
|
+
});
|
|
496
|
+
const handle = createResult.data;
|
|
497
|
+
handle.teammates = [
|
|
498
|
+
{ agent: 'architect', status: 'active' },
|
|
499
|
+
{ agent: 'tea', status: 'idle' },
|
|
500
|
+
];
|
|
501
|
+
const result = await shutdownAllTeammates(handle, adapter);
|
|
502
|
+
assert.strictEqual(result.success, true);
|
|
503
|
+
assert.strictEqual(result.data?.shutdownCount, 2, 'Should report 2 shutdowns');
|
|
504
|
+
});
|
|
505
|
+
it('should update teammate statuses to shutdown', async () => {
|
|
506
|
+
const adapter = createMockAdapter();
|
|
507
|
+
const createResult = await createTeam({
|
|
508
|
+
phase: PHASE_WITH_TEAM,
|
|
509
|
+
storyId: '86-10',
|
|
510
|
+
sessionDir: join(TEST_DIR, '.session'),
|
|
511
|
+
adapter,
|
|
512
|
+
});
|
|
513
|
+
const handle = createResult.data;
|
|
514
|
+
handle.teammates = [
|
|
515
|
+
{ agent: 'architect', status: 'active' },
|
|
516
|
+
{ agent: 'tea', status: 'idle' },
|
|
517
|
+
];
|
|
518
|
+
await shutdownAllTeammates(handle, adapter);
|
|
519
|
+
for (const teammate of handle.teammates) {
|
|
520
|
+
assert.strictEqual(teammate.status, 'shutdown', `${teammate.agent} should be marked shutdown`);
|
|
521
|
+
}
|
|
522
|
+
});
|
|
523
|
+
it('should skip already-shutdown teammates', async () => {
|
|
524
|
+
const shutdownAgents = [];
|
|
525
|
+
const adapter = createMockAdapter({
|
|
526
|
+
shutdownTeammate: async (params) => { shutdownAgents.push(params.agent); },
|
|
527
|
+
});
|
|
528
|
+
const createResult = await createTeam({
|
|
529
|
+
phase: PHASE_WITH_TEAM,
|
|
530
|
+
storyId: '86-10',
|
|
531
|
+
sessionDir: join(TEST_DIR, '.session'),
|
|
532
|
+
adapter,
|
|
533
|
+
});
|
|
534
|
+
const handle = createResult.data;
|
|
535
|
+
handle.teammates = [
|
|
536
|
+
{ agent: 'architect', status: 'shutdown' }, // already done
|
|
537
|
+
{ agent: 'tea', status: 'active' },
|
|
538
|
+
];
|
|
539
|
+
await shutdownAllTeammates(handle, adapter);
|
|
540
|
+
assert.deepStrictEqual(shutdownAgents, ['tea'], 'Should only shut down non-shutdown teammates');
|
|
541
|
+
});
|
|
542
|
+
it('should succeed even with empty teammates list', async () => {
|
|
543
|
+
const adapter = createMockAdapter();
|
|
544
|
+
const createResult = await createTeam({
|
|
545
|
+
phase: PHASE_WITH_TEAM,
|
|
546
|
+
storyId: '86-10',
|
|
547
|
+
sessionDir: join(TEST_DIR, '.session'),
|
|
548
|
+
adapter,
|
|
549
|
+
});
|
|
550
|
+
const handle = createResult.data;
|
|
551
|
+
handle.teammates = [];
|
|
552
|
+
const result = await shutdownAllTeammates(handle, adapter);
|
|
553
|
+
assert.strictEqual(result.success, true, 'Should succeed with no teammates');
|
|
554
|
+
assert.strictEqual(result.data?.shutdownCount, 0);
|
|
555
|
+
});
|
|
556
|
+
});
|
|
557
|
+
// ===========================================================================
|
|
558
|
+
// AC6: TeamDelete runs before pf handoff
|
|
559
|
+
// ===========================================================================
|
|
560
|
+
describe('AC6: TeamDelete before handoff', () => {
|
|
561
|
+
it('should call adapter.deleteTeam with team name', async () => {
|
|
562
|
+
let deletedTeam = '';
|
|
563
|
+
const adapter = createMockAdapter({
|
|
564
|
+
deleteTeam: async (teamName) => { deletedTeam = teamName; },
|
|
565
|
+
});
|
|
566
|
+
const createResult = await createTeam({
|
|
567
|
+
phase: PHASE_WITH_TEAM,
|
|
568
|
+
storyId: '86-10',
|
|
569
|
+
sessionDir: join(TEST_DIR, '.session'),
|
|
570
|
+
adapter,
|
|
571
|
+
});
|
|
572
|
+
const handle = createResult.data;
|
|
573
|
+
await cleanupTeam(handle, adapter);
|
|
574
|
+
assert.strictEqual(deletedTeam, handle.teamName, 'Should delete team by name');
|
|
575
|
+
});
|
|
576
|
+
it('should remove team from active registry', async () => {
|
|
577
|
+
const adapter = createMockAdapter();
|
|
578
|
+
const createResult = await createTeam({
|
|
579
|
+
phase: PHASE_WITH_TEAM,
|
|
580
|
+
storyId: '86-10',
|
|
581
|
+
sessionDir: join(TEST_DIR, '.session'),
|
|
582
|
+
adapter,
|
|
583
|
+
});
|
|
584
|
+
const handle = createResult.data;
|
|
585
|
+
await cleanupTeam(handle, adapter);
|
|
586
|
+
const active = getActiveTeam('86-10');
|
|
587
|
+
assert.strictEqual(active, null, 'Team should not be in registry after cleanup');
|
|
588
|
+
});
|
|
589
|
+
it('should return cleaned=true on success', async () => {
|
|
590
|
+
const adapter = createMockAdapter();
|
|
591
|
+
const createResult = await createTeam({
|
|
592
|
+
phase: PHASE_WITH_TEAM,
|
|
593
|
+
storyId: '86-10',
|
|
594
|
+
sessionDir: join(TEST_DIR, '.session'),
|
|
595
|
+
adapter,
|
|
596
|
+
});
|
|
597
|
+
const result = await cleanupTeam(createResult.data, adapter);
|
|
598
|
+
assert.strictEqual(result.success, true);
|
|
599
|
+
assert.strictEqual(result.data?.cleaned, true);
|
|
600
|
+
});
|
|
601
|
+
it('should succeed even when adapter.deleteTeam fails', async () => {
|
|
602
|
+
const adapter = createMockAdapter({
|
|
603
|
+
deleteTeam: async () => { throw new Error('Team not found'); },
|
|
604
|
+
});
|
|
605
|
+
const createResult = await createTeam({
|
|
606
|
+
phase: PHASE_WITH_TEAM,
|
|
607
|
+
storyId: '86-10',
|
|
608
|
+
sessionDir: join(TEST_DIR, '.session'),
|
|
609
|
+
adapter: createMockAdapter(), // working adapter for create
|
|
610
|
+
});
|
|
611
|
+
// Cleanup with broken adapter — should not throw
|
|
612
|
+
const result = await cleanupTeam(createResult.data, adapter);
|
|
613
|
+
assert.strictEqual(result.success, true, 'Cleanup should succeed even if delete fails');
|
|
614
|
+
});
|
|
615
|
+
it('should handle cleanup for non-existent team gracefully', async () => {
|
|
616
|
+
const handle = {
|
|
617
|
+
teamName: 'nonexistent-team',
|
|
618
|
+
storyId: 'no-story',
|
|
619
|
+
phase: 'green',
|
|
620
|
+
teammates: [],
|
|
621
|
+
createdAt: new Date().toISOString(),
|
|
622
|
+
};
|
|
623
|
+
const result = await cleanupTeam(handle, createMockAdapter());
|
|
624
|
+
assert.strictEqual(result.success, true, 'Should succeed for non-existent team');
|
|
625
|
+
});
|
|
626
|
+
});
|
|
627
|
+
// ===========================================================================
|
|
628
|
+
// AC7: Session file updated with teammate activity summary
|
|
629
|
+
// ===========================================================================
|
|
630
|
+
describe('AC7: Team activity summary for audit', () => {
|
|
631
|
+
it('should generate summary with team name', () => {
|
|
632
|
+
const handle = {
|
|
633
|
+
teamName: '86-10-green',
|
|
634
|
+
storyId: '86-10',
|
|
635
|
+
phase: 'green',
|
|
636
|
+
teammates: [
|
|
637
|
+
{ agent: 'architect', task: 'Review patterns', status: 'shutdown' },
|
|
638
|
+
{ agent: 'tea', task: 'Verify tests', status: 'shutdown' },
|
|
639
|
+
],
|
|
640
|
+
createdAt: new Date().toISOString(),
|
|
641
|
+
};
|
|
642
|
+
const summary = generateTeamSummary(handle);
|
|
643
|
+
assert.strictEqual(summary.teamName, '86-10-green', 'Summary should include team name');
|
|
644
|
+
});
|
|
645
|
+
it('should include all team members with status', () => {
|
|
646
|
+
const handle = {
|
|
647
|
+
teamName: '86-10-green',
|
|
648
|
+
storyId: '86-10',
|
|
649
|
+
phase: 'green',
|
|
650
|
+
teammates: [
|
|
651
|
+
{ agent: 'architect', task: 'Review patterns', status: 'shutdown' },
|
|
652
|
+
{ agent: 'tea', task: 'Verify tests', status: 'crashed' },
|
|
653
|
+
],
|
|
654
|
+
createdAt: new Date().toISOString(),
|
|
655
|
+
};
|
|
656
|
+
const summary = generateTeamSummary(handle);
|
|
657
|
+
assert.strictEqual(summary.members.length, 2, 'Should list all members');
|
|
658
|
+
const architectMember = summary.members.find((m) => m.agent === 'architect');
|
|
659
|
+
assert.ok(architectMember, 'Should include architect');
|
|
660
|
+
assert.strictEqual(architectMember.status, 'shutdown');
|
|
661
|
+
});
|
|
662
|
+
it('should include task descriptions in member summaries', () => {
|
|
663
|
+
const handle = {
|
|
664
|
+
teamName: '86-10-green',
|
|
665
|
+
storyId: '86-10',
|
|
666
|
+
phase: 'green',
|
|
667
|
+
teammates: [
|
|
668
|
+
{ agent: 'architect', task: 'Review patterns', status: 'shutdown' },
|
|
669
|
+
],
|
|
670
|
+
createdAt: new Date().toISOString(),
|
|
671
|
+
};
|
|
672
|
+
const summary = generateTeamSummary(handle);
|
|
673
|
+
assert.strictEqual(summary.members[0].task, 'Review patterns');
|
|
674
|
+
});
|
|
675
|
+
it('should report clean shutdown status', () => {
|
|
676
|
+
const handle = {
|
|
677
|
+
teamName: '86-10-green',
|
|
678
|
+
storyId: '86-10',
|
|
679
|
+
phase: 'green',
|
|
680
|
+
teammates: [
|
|
681
|
+
{ agent: 'architect', status: 'shutdown' },
|
|
682
|
+
{ agent: 'tea', status: 'shutdown' },
|
|
683
|
+
],
|
|
684
|
+
createdAt: new Date().toISOString(),
|
|
685
|
+
};
|
|
686
|
+
const summary = generateTeamSummary(handle);
|
|
687
|
+
assert.strictEqual(summary.cleanShutdown, true, 'All shutdown = clean');
|
|
688
|
+
});
|
|
689
|
+
it('should report unclean shutdown when teammate crashed', () => {
|
|
690
|
+
const handle = {
|
|
691
|
+
teamName: '86-10-green',
|
|
692
|
+
storyId: '86-10',
|
|
693
|
+
phase: 'green',
|
|
694
|
+
teammates: [
|
|
695
|
+
{ agent: 'architect', status: 'shutdown' },
|
|
696
|
+
{ agent: 'tea', status: 'crashed' },
|
|
697
|
+
],
|
|
698
|
+
createdAt: new Date().toISOString(),
|
|
699
|
+
};
|
|
700
|
+
const summary = generateTeamSummary(handle);
|
|
701
|
+
assert.strictEqual(summary.cleanShutdown, false, 'Crashed teammate = unclean');
|
|
702
|
+
});
|
|
703
|
+
it('should include story ID and phase for audit context', () => {
|
|
704
|
+
const handle = {
|
|
705
|
+
teamName: '86-10-green',
|
|
706
|
+
storyId: '86-10',
|
|
707
|
+
phase: 'green',
|
|
708
|
+
teammates: [],
|
|
709
|
+
createdAt: new Date().toISOString(),
|
|
710
|
+
};
|
|
711
|
+
const summary = generateTeamSummary(handle);
|
|
712
|
+
assert.strictEqual(summary.storyId, '86-10');
|
|
713
|
+
assert.strictEqual(summary.phase, 'green');
|
|
714
|
+
});
|
|
715
|
+
});
|
|
716
|
+
// ===========================================================================
|
|
717
|
+
// AC8: Sidecar file locking for concurrent teammate writes
|
|
718
|
+
// ===========================================================================
|
|
719
|
+
describe('AC8: Sidecar file locking', () => {
|
|
720
|
+
it('should acquire lock for sidecar file', async () => {
|
|
721
|
+
const filePath = join(TEST_DIR, '.pennyfarthing', 'sidecars', 'patterns.md');
|
|
722
|
+
const result = await acquireSidecarLock(filePath, '86-10');
|
|
723
|
+
assert.strictEqual(result.success, true, 'Lock acquisition should succeed');
|
|
724
|
+
assert.ok(result.data, 'Should return lock handle');
|
|
725
|
+
assert.ok(result.data.lockPath, 'Lock handle should have lock path');
|
|
726
|
+
});
|
|
727
|
+
it('should include story ID in lock', async () => {
|
|
728
|
+
const filePath = join(TEST_DIR, 'sidecars', 'patterns.md');
|
|
729
|
+
const result = await acquireSidecarLock(filePath, '86-10');
|
|
730
|
+
assert.strictEqual(result.success, true);
|
|
731
|
+
assert.strictEqual(result.data.storyId, '86-10', 'Lock should track story ID');
|
|
732
|
+
});
|
|
733
|
+
it('should set acquiredAt timestamp', async () => {
|
|
734
|
+
const filePath = join(TEST_DIR, 'sidecars', 'patterns.md');
|
|
735
|
+
const result = await acquireSidecarLock(filePath, '86-10');
|
|
736
|
+
assert.strictEqual(result.success, true);
|
|
737
|
+
const date = new Date(result.data.acquiredAt);
|
|
738
|
+
assert.ok(!isNaN(date.getTime()), 'acquiredAt should be valid ISO date');
|
|
739
|
+
});
|
|
740
|
+
it('should fail to acquire when already locked by different story', async () => {
|
|
741
|
+
const filePath = join(TEST_DIR, 'sidecars', 'patterns.md');
|
|
742
|
+
// First lock succeeds
|
|
743
|
+
const first = await acquireSidecarLock(filePath, '86-10');
|
|
744
|
+
assert.strictEqual(first.success, true);
|
|
745
|
+
// Second lock for different story should fail
|
|
746
|
+
const second = await acquireSidecarLock(filePath, '86-11');
|
|
747
|
+
assert.strictEqual(second.success, false, 'Should fail when already locked');
|
|
748
|
+
assert.ok(second.error, 'Should include error message');
|
|
749
|
+
});
|
|
750
|
+
it('should allow re-entrant lock from same story', async () => {
|
|
751
|
+
const filePath = join(TEST_DIR, 'sidecars', 'patterns.md');
|
|
752
|
+
const first = await acquireSidecarLock(filePath, '86-10');
|
|
753
|
+
assert.strictEqual(first.success, true);
|
|
754
|
+
// Same story re-acquiring should succeed (re-entrant)
|
|
755
|
+
const second = await acquireSidecarLock(filePath, '86-10');
|
|
756
|
+
assert.strictEqual(second.success, true, 'Re-entrant lock should succeed');
|
|
757
|
+
});
|
|
758
|
+
it('should release lock successfully', async () => {
|
|
759
|
+
const filePath = join(TEST_DIR, 'sidecars', 'patterns.md');
|
|
760
|
+
const acquired = await acquireSidecarLock(filePath, '86-10');
|
|
761
|
+
assert.strictEqual(acquired.success, true);
|
|
762
|
+
const released = releaseSidecarLock(acquired.data);
|
|
763
|
+
assert.strictEqual(released.success, true, 'Release should succeed');
|
|
764
|
+
});
|
|
765
|
+
it('should allow new lock after release', async () => {
|
|
766
|
+
const filePath = join(TEST_DIR, 'sidecars', 'patterns.md');
|
|
767
|
+
// Acquire and release
|
|
768
|
+
const first = await acquireSidecarLock(filePath, '86-10');
|
|
769
|
+
releaseSidecarLock(first.data);
|
|
770
|
+
// New lock should succeed
|
|
771
|
+
const second = await acquireSidecarLock(filePath, '86-11');
|
|
772
|
+
assert.strictEqual(second.success, true, 'Should acquire after release');
|
|
773
|
+
});
|
|
774
|
+
it('should handle release of already-released lock gracefully', () => {
|
|
775
|
+
const stalelock = {
|
|
776
|
+
lockPath: join(TEST_DIR, 'stale.lock'),
|
|
777
|
+
storyId: '86-10',
|
|
778
|
+
acquiredAt: new Date().toISOString(),
|
|
779
|
+
};
|
|
780
|
+
const result = releaseSidecarLock(stalelock);
|
|
781
|
+
assert.strictEqual(result.success, true, 'Should succeed for stale lock');
|
|
782
|
+
});
|
|
783
|
+
});
|
|
784
|
+
// ===========================================================================
|
|
785
|
+
// AC9: Graceful degradation when teammate crashes
|
|
786
|
+
// ===========================================================================
|
|
787
|
+
describe('AC9: Graceful degradation on teammate crash', () => {
|
|
788
|
+
it('should continue spawning remaining teammates when one fails', async () => {
|
|
789
|
+
let spawnCount = 0;
|
|
790
|
+
const adapter = createMockAdapter({
|
|
791
|
+
spawnTeammate: async (params) => {
|
|
792
|
+
spawnCount++;
|
|
793
|
+
if (params.agent === 'architect') {
|
|
794
|
+
throw new Error('Spawn failed: connection refused');
|
|
795
|
+
}
|
|
796
|
+
return { agentId: `mock-${params.agent}` };
|
|
797
|
+
},
|
|
798
|
+
});
|
|
799
|
+
const createResult = await createTeam({
|
|
800
|
+
phase: PHASE_WITH_TEAM,
|
|
801
|
+
storyId: '86-10',
|
|
802
|
+
sessionDir: join(TEST_DIR, '.session'),
|
|
803
|
+
adapter: createMockAdapter(), // working adapter for create
|
|
804
|
+
});
|
|
805
|
+
const result = await spawnTeammates(createResult.data, PHASE_WITH_TEAM.team, '86-10', 'green', adapter);
|
|
806
|
+
// Should still succeed overall (degraded mode)
|
|
807
|
+
assert.strictEqual(result.success, true, 'Should succeed in degraded mode');
|
|
808
|
+
assert.strictEqual(spawnCount, 2, 'Should attempt both spawns');
|
|
809
|
+
});
|
|
810
|
+
it('should mark crashed teammate with crashed status', async () => {
|
|
811
|
+
const adapter = createMockAdapter({
|
|
812
|
+
spawnTeammate: async (params) => {
|
|
813
|
+
if (params.agent === 'architect') {
|
|
814
|
+
throw new Error('Spawn failed');
|
|
815
|
+
}
|
|
816
|
+
return { agentId: `mock-${params.agent}` };
|
|
817
|
+
},
|
|
818
|
+
});
|
|
819
|
+
const createResult = await createTeam({
|
|
820
|
+
phase: PHASE_WITH_TEAM,
|
|
821
|
+
storyId: '86-10',
|
|
822
|
+
sessionDir: join(TEST_DIR, '.session'),
|
|
823
|
+
adapter: createMockAdapter(),
|
|
824
|
+
});
|
|
825
|
+
const result = await spawnTeammates(createResult.data, PHASE_WITH_TEAM.team, '86-10', 'green', adapter);
|
|
826
|
+
const crashed = result.data.find((t) => t.agent === 'architect');
|
|
827
|
+
assert.ok(crashed, 'Crashed teammate should still appear in list');
|
|
828
|
+
assert.strictEqual(crashed.status, 'crashed', 'Failed spawn should be marked crashed');
|
|
829
|
+
});
|
|
830
|
+
it('should not block shutdown when teammate already crashed', async () => {
|
|
831
|
+
const adapter = createMockAdapter({
|
|
832
|
+
shutdownTeammate: async (params) => {
|
|
833
|
+
if (params.agent === 'architect') {
|
|
834
|
+
throw new Error('Cannot shutdown: process not found');
|
|
835
|
+
}
|
|
836
|
+
},
|
|
837
|
+
});
|
|
838
|
+
const handle = {
|
|
839
|
+
teamName: '86-10-green',
|
|
840
|
+
storyId: '86-10',
|
|
841
|
+
phase: 'green',
|
|
842
|
+
teammates: [
|
|
843
|
+
{ agent: 'architect', status: 'crashed' },
|
|
844
|
+
{ agent: 'tea', status: 'active' },
|
|
845
|
+
],
|
|
846
|
+
createdAt: new Date().toISOString(),
|
|
847
|
+
};
|
|
848
|
+
const result = await shutdownAllTeammates(handle, adapter);
|
|
849
|
+
assert.strictEqual(result.success, true, 'Shutdown should succeed despite crash');
|
|
850
|
+
});
|
|
851
|
+
it('should not block cleanup when team deletion fails', async () => {
|
|
852
|
+
const adapter = createMockAdapter({
|
|
853
|
+
deleteTeam: async () => { throw new Error('Team already deleted'); },
|
|
854
|
+
});
|
|
855
|
+
const handle = {
|
|
856
|
+
teamName: '86-10-green',
|
|
857
|
+
storyId: '86-10',
|
|
858
|
+
phase: 'green',
|
|
859
|
+
teammates: [],
|
|
860
|
+
createdAt: new Date().toISOString(),
|
|
861
|
+
};
|
|
862
|
+
const result = await cleanupTeam(handle, adapter);
|
|
863
|
+
assert.strictEqual(result.success, true, 'Cleanup should not throw');
|
|
864
|
+
});
|
|
865
|
+
it('should return error info for crashed teammates in spawn result', async () => {
|
|
866
|
+
const adapter = createMockAdapter({
|
|
867
|
+
spawnTeammate: async (params) => {
|
|
868
|
+
if (params.agent === 'architect') {
|
|
869
|
+
throw new Error('Connection refused');
|
|
870
|
+
}
|
|
871
|
+
return { agentId: `mock-${params.agent}` };
|
|
872
|
+
},
|
|
873
|
+
});
|
|
874
|
+
const createResult = await createTeam({
|
|
875
|
+
phase: PHASE_WITH_TEAM,
|
|
876
|
+
storyId: '86-10',
|
|
877
|
+
sessionDir: join(TEST_DIR, '.session'),
|
|
878
|
+
adapter: createMockAdapter(),
|
|
879
|
+
});
|
|
880
|
+
const result = await spawnTeammates(createResult.data, PHASE_WITH_TEAM.team, '86-10', 'green', adapter);
|
|
881
|
+
// Successful teammates should still be in the result
|
|
882
|
+
const tea = result.data.find((t) => t.agent === 'tea');
|
|
883
|
+
assert.ok(tea, 'Successful teammate should be in result');
|
|
884
|
+
assert.strictEqual(tea.status, 'spawned');
|
|
885
|
+
});
|
|
886
|
+
});
|
|
887
|
+
// ===========================================================================
|
|
888
|
+
// Result format compliance
|
|
889
|
+
// ===========================================================================
|
|
890
|
+
describe('Result format compliance', () => {
|
|
891
|
+
it('should return {success, data?, error?} from createTeam', async () => {
|
|
892
|
+
const result = await createTeam({
|
|
893
|
+
phase: PHASE_WITH_TEAM,
|
|
894
|
+
storyId: '86-10',
|
|
895
|
+
sessionDir: join(TEST_DIR, '.session'),
|
|
896
|
+
adapter: createMockAdapter(),
|
|
897
|
+
});
|
|
898
|
+
assert.ok('success' in result, 'Must have success field');
|
|
899
|
+
assert.strictEqual(typeof result.success, 'boolean', 'success must be boolean');
|
|
900
|
+
});
|
|
901
|
+
it('should return {success, data?, error?} from spawnTeammates', async () => {
|
|
902
|
+
const createResult = await createTeam({
|
|
903
|
+
phase: PHASE_WITH_TEAM,
|
|
904
|
+
storyId: '86-10',
|
|
905
|
+
sessionDir: join(TEST_DIR, '.session'),
|
|
906
|
+
adapter: createMockAdapter(),
|
|
907
|
+
});
|
|
908
|
+
const result = await spawnTeammates(createResult.data, PHASE_WITH_TEAM.team, '86-10', 'green', createMockAdapter());
|
|
909
|
+
assert.ok('success' in result, 'Must have success field');
|
|
910
|
+
assert.strictEqual(typeof result.success, 'boolean');
|
|
911
|
+
});
|
|
912
|
+
it('should return {success, data?, error?} from shutdownAllTeammates', async () => {
|
|
913
|
+
const handle = {
|
|
914
|
+
teamName: '86-10-green',
|
|
915
|
+
storyId: '86-10',
|
|
916
|
+
phase: 'green',
|
|
917
|
+
teammates: [],
|
|
918
|
+
createdAt: new Date().toISOString(),
|
|
919
|
+
};
|
|
920
|
+
const result = await shutdownAllTeammates(handle, createMockAdapter());
|
|
921
|
+
assert.ok('success' in result, 'Must have success field');
|
|
922
|
+
assert.strictEqual(typeof result.success, 'boolean');
|
|
923
|
+
});
|
|
924
|
+
it('should return {success, data?, error?} from cleanupTeam', async () => {
|
|
925
|
+
const handle = {
|
|
926
|
+
teamName: '86-10-green',
|
|
927
|
+
storyId: '86-10',
|
|
928
|
+
phase: 'green',
|
|
929
|
+
teammates: [],
|
|
930
|
+
createdAt: new Date().toISOString(),
|
|
931
|
+
};
|
|
932
|
+
const result = await cleanupTeam(handle, createMockAdapter());
|
|
933
|
+
assert.ok('success' in result, 'Must have success field');
|
|
934
|
+
assert.strictEqual(typeof result.success, 'boolean');
|
|
935
|
+
});
|
|
936
|
+
});
|
|
937
|
+
// ===========================================================================
|
|
938
|
+
// ProcessAdapter integration
|
|
939
|
+
// ===========================================================================
|
|
940
|
+
describe('ProcessAdapter integration', () => {
|
|
941
|
+
it('should work without adapter (in-memory test mode)', async () => {
|
|
942
|
+
const result = await createTeam({
|
|
943
|
+
phase: PHASE_WITH_TEAM,
|
|
944
|
+
storyId: '86-10',
|
|
945
|
+
sessionDir: join(TEST_DIR, '.session'),
|
|
946
|
+
// No adapter — test mode
|
|
947
|
+
});
|
|
948
|
+
assert.strictEqual(result.success, true, 'Should work without adapter');
|
|
949
|
+
assert.ok(result.data, 'Should return handle even without adapter');
|
|
950
|
+
});
|
|
951
|
+
it('should propagate adapter errors as result errors, not exceptions', async () => {
|
|
952
|
+
const adapter = createMockAdapter({
|
|
953
|
+
createTeam: async () => { throw new Error('Network timeout'); },
|
|
954
|
+
});
|
|
955
|
+
const result = await createTeam({
|
|
956
|
+
phase: PHASE_WITH_TEAM,
|
|
957
|
+
storyId: '86-10',
|
|
958
|
+
sessionDir: join(TEST_DIR, '.session'),
|
|
959
|
+
adapter,
|
|
960
|
+
});
|
|
961
|
+
assert.strictEqual(result.success, false, 'Should fail, not throw');
|
|
962
|
+
assert.ok(result.error?.includes('Network timeout'), 'Should include error message');
|
|
963
|
+
});
|
|
964
|
+
});
|
|
965
|
+
});
|
|
966
|
+
//# sourceMappingURL=team-lifecycle.test.js.map
|