@pennyfarthing/core 11.0.0 → 11.1.1
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 +81 -23
- package/package.json +1 -1
- package/packages/core/dist/cli/utils/010-detect-remove-old-packages.test.d.ts +20 -0
- package/packages/core/dist/cli/utils/010-detect-remove-old-packages.test.d.ts.map +1 -0
- package/packages/core/dist/cli/utils/010-detect-remove-old-packages.test.js +278 -0
- package/packages/core/dist/cli/utils/010-detect-remove-old-packages.test.js.map +1 -0
- package/packages/core/dist/cli/utils/constants.d.ts +8 -2
- package/packages/core/dist/cli/utils/constants.d.ts.map +1 -1
- package/packages/core/dist/cli/utils/constants.js +4 -1
- package/packages/core/dist/cli/utils/constants.js.map +1 -1
- package/packages/core/dist/cli/utils/constants.test.d.ts +10 -0
- package/packages/core/dist/cli/utils/constants.test.d.ts.map +1 -0
- package/packages/core/dist/cli/utils/constants.test.js +38 -0
- package/packages/core/dist/cli/utils/constants.test.js.map +1 -0
- package/packages/core/dist/consultation/consultation-protocol.d.ts +139 -0
- package/packages/core/dist/consultation/consultation-protocol.d.ts.map +1 -0
- package/packages/core/dist/consultation/consultation-protocol.js +178 -0
- package/packages/core/dist/consultation/consultation-protocol.js.map +1 -0
- package/packages/core/dist/consultation/consultation-protocol.test.d.ts +20 -0
- package/packages/core/dist/consultation/consultation-protocol.test.d.ts.map +1 -0
- package/packages/core/dist/consultation/consultation-protocol.test.js +474 -0
- package/packages/core/dist/consultation/consultation-protocol.test.js.map +1 -0
- package/packages/core/dist/consultation/dialogue-manager.d.ts +75 -0
- package/packages/core/dist/consultation/dialogue-manager.d.ts.map +1 -0
- package/packages/core/dist/consultation/dialogue-manager.js +334 -0
- package/packages/core/dist/consultation/dialogue-manager.js.map +1 -0
- package/packages/core/dist/consultation/dialogue-manager.test.d.ts +19 -0
- package/packages/core/dist/consultation/dialogue-manager.test.d.ts.map +1 -0
- package/packages/core/dist/consultation/dialogue-manager.test.js +444 -0
- package/packages/core/dist/consultation/dialogue-manager.test.js.map +1 -0
- package/packages/core/dist/public/js/react/react.js +3 -3
- package/packages/core/dist/scripts/theme-detail.test.d.ts +10 -0
- package/packages/core/dist/scripts/theme-detail.test.js +199 -0
- package/packages/core/dist/server/api/git.d.ts +13 -1
- package/packages/core/dist/server/api/git.d.ts.map +1 -1
- package/packages/core/dist/server/api/git.js +53 -34
- package/packages/core/dist/server/api/git.js.map +1 -1
- package/packages/core/dist/server/api/health-score.d.ts.map +1 -1
- package/packages/core/dist/server/api/health-score.js +25 -1
- package/packages/core/dist/server/api/health-score.js.map +1 -1
- package/packages/core/dist/server/api/settings.d.ts.map +1 -1
- package/packages/core/dist/server/api/settings.js +63 -1
- package/packages/core/dist/server/api/settings.js.map +1 -1
- package/packages/core/dist/server/api/theme-agents.d.ts.map +1 -1
- package/packages/core/dist/server/api/theme-agents.js +61 -0
- package/packages/core/dist/server/api/theme-agents.js.map +1 -1
- package/packages/core/dist/server/server.d.ts.map +1 -1
- package/packages/core/dist/server/server.js +17 -12
- package/packages/core/dist/server/server.js.map +1 -1
- package/packages/core/dist/shared/skill-search.test.js +2 -2
- package/packages/core/dist/workflow/gate-file-validation.d.ts +49 -0
- package/packages/core/dist/workflow/gate-file-validation.d.ts.map +1 -0
- package/packages/core/dist/workflow/gate-file-validation.js +157 -0
- package/packages/core/dist/workflow/gate-file-validation.js.map +1 -0
- package/packages/core/dist/workflow/gate-file-validation.test.d.ts +19 -0
- package/packages/core/dist/workflow/gate-file-validation.test.d.ts.map +1 -0
- package/packages/core/dist/workflow/gate-file-validation.test.js +536 -0
- package/packages/core/dist/workflow/gate-file-validation.test.js.map +1 -0
- package/packages/core/dist/workflow/gate-schema-validation.test.d.ts +14 -0
- package/packages/core/dist/workflow/gate-schema-validation.test.d.ts.map +1 -0
- package/packages/core/dist/workflow/gate-schema-validation.test.js +339 -0
- package/packages/core/dist/workflow/gate-schema-validation.test.js.map +1 -0
- package/packages/core/dist/workflow/handoff.js +2 -2
- package/packages/core/dist/workflow/handoff.js.map +1 -1
- package/packages/core/dist/workflow/handoff.test.js +16 -0
- package/packages/core/dist/workflow/handoff.test.js.map +1 -1
- package/packages/core/dist/workflow/workflow-schema.d.ts +4 -2
- package/packages/core/dist/workflow/workflow-schema.d.ts.map +1 -1
- package/packages/core/dist/workflow/workflow-schema.js +43 -8
- package/packages/core/dist/workflow/workflow-schema.js.map +1 -1
- package/pennyfarthing-dist/agents/README.md +6 -14
- package/pennyfarthing-dist/agents/architect.md +43 -29
- package/pennyfarthing-dist/agents/ba.md +30 -29
- package/pennyfarthing-dist/agents/dev.md +32 -43
- package/pennyfarthing-dist/agents/devops.md +57 -21
- package/pennyfarthing-dist/agents/orchestrator.md +3 -10
- package/pennyfarthing-dist/agents/pm.md +45 -31
- package/pennyfarthing-dist/agents/reviewer.md +20 -66
- package/pennyfarthing-dist/agents/sm-setup.md +2 -2
- package/pennyfarthing-dist/agents/sm.md +8 -30
- package/pennyfarthing-dist/agents/tea.md +25 -41
- package/pennyfarthing-dist/agents/tech-writer.md +33 -90
- package/pennyfarthing-dist/agents/ux-designer.md +39 -39
- package/pennyfarthing-dist/commands/benchmark-control.md +8 -64
- package/pennyfarthing-dist/commands/benchmark.md +8 -480
- package/pennyfarthing-dist/commands/job-fair.md +8 -97
- package/pennyfarthing-dist/commands/pf-benchmark-control.md +70 -0
- package/pennyfarthing-dist/commands/pf-benchmark.md +486 -0
- package/pennyfarthing-dist/commands/pf-chore.md +4 -4
- package/pennyfarthing-dist/commands/pf-ci.md +40 -0
- package/pennyfarthing-dist/commands/pf-close-epic.md +9 -27
- package/pennyfarthing-dist/commands/pf-continue-session.md +9 -213
- package/pennyfarthing-dist/commands/pf-create-branches-from-story.md +11 -353
- package/pennyfarthing-dist/commands/pf-docs.md +28 -0
- package/pennyfarthing-dist/commands/pf-epic.md +67 -0
- package/pennyfarthing-dist/commands/pf-git-cleanup.md +11 -52
- package/pennyfarthing-dist/commands/pf-git.md +75 -0
- package/pennyfarthing-dist/commands/pf-help.md +110 -128
- package/pennyfarthing-dist/commands/pf-job-fair.md +102 -0
- package/pennyfarthing-dist/commands/pf-new-work.md +9 -18
- package/pennyfarthing-dist/commands/pf-parallel-work.md +6 -66
- package/pennyfarthing-dist/commands/pf-release.md +11 -76
- package/pennyfarthing-dist/commands/pf-repo-status.md +11 -44
- package/pennyfarthing-dist/commands/pf-run-ci.md +8 -111
- package/pennyfarthing-dist/commands/pf-session.md +51 -0
- package/pennyfarthing-dist/commands/pf-solo.md +447 -0
- package/pennyfarthing-dist/commands/pf-sprint-planning.md +8 -104
- package/pennyfarthing-dist/commands/pf-standalone.md +1 -1
- package/pennyfarthing-dist/commands/pf-start-epic.md +9 -163
- package/pennyfarthing-dist/commands/pf-sync-epic-to-jira.md +8 -179
- package/pennyfarthing-dist/commands/pf-sync-work-with-sprint.md +8 -368
- package/pennyfarthing-dist/commands/pf-update-domain-docs.md +8 -78
- package/pennyfarthing-dist/commands/solo.md +8 -442
- package/pennyfarthing-dist/guides/agent-behavior.md +13 -13
- package/pennyfarthing-dist/guides/agent-coordination.md +7 -7
- package/pennyfarthing-dist/guides/agent-tag-taxonomy.md +6 -5
- package/pennyfarthing-dist/guides/bikerack.md +128 -0
- package/pennyfarthing-dist/guides/brownfield-tools.md +133 -0
- package/pennyfarthing-dist/guides/command-tag-taxonomy.md +2 -2
- package/pennyfarthing-dist/guides/gate-schema.md +227 -0
- package/pennyfarthing-dist/guides/gates.md +120 -0
- package/pennyfarthing-dist/guides/handoff-cli.md +116 -0
- package/pennyfarthing-dist/guides/hooks.md +86 -4
- package/pennyfarthing-dist/guides/output-styles.md +65 -0
- package/pennyfarthing-dist/guides/patterns/approval-gates-pattern.md +5 -5
- package/pennyfarthing-dist/guides/patterns/tdd-flow-pattern.md +4 -4
- package/pennyfarthing-dist/guides/prompt-patterns.md +5 -5
- package/pennyfarthing-dist/guides/reflector.md +4 -4
- package/pennyfarthing-dist/guides/session-artifacts.md +1 -1
- package/pennyfarthing-dist/guides/skill-schema.md +1 -1
- package/pennyfarthing-dist/guides/tandem-protocol.md +13 -1
- package/pennyfarthing-dist/guides/worktree-mode.md +3 -3
- package/pennyfarthing-dist/guides/xml-tags.md +5 -4
- package/pennyfarthing-dist/personas/themes/hogans-heroes.yaml +11 -22
- package/pennyfarthing-dist/personas/themes/stephen-king.yaml +13 -24
- package/pennyfarthing-dist/scripts/core/dialogue-manager.sh +322 -0
- package/pennyfarthing-dist/scripts/core/phase-check-start.sh +1 -1
- package/pennyfarthing-dist/scripts/hooks/otel-auto-config.sh +19 -14
- package/pennyfarthing-dist/scripts/portraits/generate-portraits.py +191 -57
- package/pennyfarthing-dist/scripts/portraits/generate-portraits.sh +26 -10
- package/pennyfarthing-dist/skills/pf-changelog/SKILL.md +4 -4
- package/pennyfarthing-dist/skills/pf-sprint/skill.md +1 -1
- package/pennyfarthing-dist/skills/skill-registry.schema.json +4 -0
- package/pennyfarthing-dist/skills/skill-registry.yaml +5 -0
- package/pennyfarthing-dist/workflows/2party-tdd.yaml +11 -0
- package/pennyfarthing-dist/workflows/agent-docs.yaml +2 -0
- package/pennyfarthing-dist/workflows/bdd-tandem.yaml +4 -0
- package/pennyfarthing-dist/workflows/bdd.yaml +4 -0
- package/pennyfarthing-dist/workflows/git-cleanup/steps/step-05-complete.md +1 -1
- package/pennyfarthing-dist/workflows/tdd-tandem.yaml +3 -0
- package/pennyfarthing-dist/workflows/tdd.yaml +3 -0
- package/pennyfarthing-dist/workflows/trivial.yaml +2 -0
- 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/bikerack/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bikerack/__pycache__/__main__.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__/debug_panel.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bikerack/__pycache__/diffs_panel.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__/sprint_panel.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/bikerack/cli.py +10 -11
- package/pennyfarthing_scripts/bikerack/debug_panel.py +218 -0
- package/pennyfarthing_scripts/bikerack/diffs_panel.py +203 -27
- 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 +114 -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/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/__init__.py +0 -0
- package/pennyfarthing_scripts/epic/cli.py +64 -0
- package/pennyfarthing_scripts/gate/__init__.py +1 -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/gate/cli.py +56 -0
- package/pennyfarthing_scripts/gate/validate.py +266 -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__/status_all.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/git_group/__init__.py +0 -0
- package/pennyfarthing_scripts/git_group/cli.py +100 -0
- package/pennyfarthing_scripts/handoff/__init__.py +1 -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__/resolve_gate.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/handoff/cli.py +120 -0
- package/pennyfarthing_scripts/handoff/complete_phase.py +155 -0
- package/pennyfarthing_scripts/handoff/gate_file.py +105 -0
- package/pennyfarthing_scripts/handoff/gate_runner.py +152 -0
- package/pennyfarthing_scripts/handoff/marker.py +109 -0
- package/pennyfarthing_scripts/handoff/resolve_gate.py +152 -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/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/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__/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/prime/workflow.py +39 -0
- package/pennyfarthing_scripts/session/__init__.py +0 -0
- package/pennyfarthing_scripts/session/cli.py +87 -0
- 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/sprint/story_finish.py +14 -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_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_yaml_io.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/test_108_1_gate_migration.py +540 -0
- package/pennyfarthing_scripts/tests/test_108_2_remove_handoff_fallback.py +339 -0
- package/pennyfarthing_scripts/tests/test_confidence_sm_evaluation.py +253 -0
- package/pennyfarthing_scripts/tests/test_confidence_sm_gate.py +315 -0
- package/pennyfarthing_scripts/tests/test_gate_file_resolution.py +341 -0
- package/pennyfarthing_scripts/tests/test_gate_runner.py +620 -0
- package/pennyfarthing_scripts/tests/test_handoff_cli.py +929 -0
- package/pennyfarthing_scripts/tests/test_handoff_e2e.py +454 -0
- package/pennyfarthing_scripts/tests/test_resolve_gate_file_field.py +464 -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__/workflow.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/validate/adapters/skill_command.py +200 -0
- package/pennyfarthing_scripts/validate/adapters/workflow.py +64 -0
- package/pennyfarthing_scripts/validate/cli.py +15 -4
- 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/pennyfarthing-dist/agents/handoff.md +0 -250
- package/pennyfarthing-dist/agents/sm-handoff.md +0 -152
- package/pennyfarthing-dist/scripts/core/handoff-marker.sh +0 -112
- 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__/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/jira/__pycache__/compat.cpython-314.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/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/tests/__pycache__/test_workflow_cli.cpython-314-pytest-9.0.2.pyc +0 -0
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
"""Tests for removing handoff subagents and inline fallback — Story 108-2.
|
|
2
|
+
|
|
3
|
+
Epic: 108 (Full Migration & Cleanup)
|
|
4
|
+
Story: 108-2 — Remove handoff subagents and inline fallback
|
|
5
|
+
|
|
6
|
+
Tests the removal of deprecated handoff subagents and the gate.type
|
|
7
|
+
fallback code path, leaving only gate.file-based resolution.
|
|
8
|
+
|
|
9
|
+
Acceptance Criteria:
|
|
10
|
+
- [AC1] pennyfarthing-dist/agents/handoff.md deleted
|
|
11
|
+
- [AC2] pennyfarthing-dist/agents/sm-handoff.md deleted
|
|
12
|
+
- [AC3] gate.type fallback removed from resolve-gate — file-only gates resolved
|
|
13
|
+
- [AC4] checkGate() in Cyclist updated or removed (see TS test)
|
|
14
|
+
- [AC5] No remaining references to handoff/sm-handoff subagents in agent files
|
|
15
|
+
- [AC6] All existing tests pass
|
|
16
|
+
- [AC7] Single code path: all gated phases in phased workflows have gate.file
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import re
|
|
22
|
+
import textwrap
|
|
23
|
+
from pathlib import Path
|
|
24
|
+
|
|
25
|
+
import pytest
|
|
26
|
+
import yaml
|
|
27
|
+
|
|
28
|
+
from pennyfarthing_scripts.handoff.resolve_gate import resolve_gate
|
|
29
|
+
|
|
30
|
+
# ---------------------------------------------------------------------------
|
|
31
|
+
# Paths
|
|
32
|
+
# ---------------------------------------------------------------------------
|
|
33
|
+
|
|
34
|
+
DIST_DIR = Path(__file__).resolve().parents[2] / "pennyfarthing-dist"
|
|
35
|
+
AGENTS_DIR = DIST_DIR / "agents"
|
|
36
|
+
WORKFLOWS_DIR = DIST_DIR / "workflows"
|
|
37
|
+
GATES_DIR = DIST_DIR / "gates"
|
|
38
|
+
|
|
39
|
+
# ---------------------------------------------------------------------------
|
|
40
|
+
# Fixtures
|
|
41
|
+
# ---------------------------------------------------------------------------
|
|
42
|
+
|
|
43
|
+
SESSION_WITH_ASSESSMENT = textwrap.dedent("""\
|
|
44
|
+
# Story 108-2: Remove handoff subagents and inline fallback
|
|
45
|
+
|
|
46
|
+
**Story ID:** 108-2
|
|
47
|
+
**Workflow:** tdd
|
|
48
|
+
**Phase:** green
|
|
49
|
+
**Phase Started:** 2026-02-15T10:00:00Z
|
|
50
|
+
|
|
51
|
+
## TEA Assessment
|
|
52
|
+
|
|
53
|
+
**Tests Written:** 5 tests
|
|
54
|
+
**Status:** RED confirmed
|
|
55
|
+
""")
|
|
56
|
+
|
|
57
|
+
SESSION_WITHOUT_ASSESSMENT = textwrap.dedent("""\
|
|
58
|
+
# Story 108-2: Remove handoff subagents and inline fallback
|
|
59
|
+
|
|
60
|
+
**Story ID:** 108-2
|
|
61
|
+
**Workflow:** tdd
|
|
62
|
+
**Phase:** green
|
|
63
|
+
**Phase Started:** 2026-02-15T10:00:00Z
|
|
64
|
+
""")
|
|
65
|
+
|
|
66
|
+
WORKFLOW_FILE_ONLY_GATE = {
|
|
67
|
+
"workflow": {
|
|
68
|
+
"name": "file-only-test",
|
|
69
|
+
"phases": [
|
|
70
|
+
{"name": "setup", "agent": "sm"},
|
|
71
|
+
{
|
|
72
|
+
"name": "green",
|
|
73
|
+
"agent": "dev",
|
|
74
|
+
"gate": {
|
|
75
|
+
"file": "gates/tests-pass",
|
|
76
|
+
"condition": "All tests passing",
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
{"name": "finish", "agent": "sm"},
|
|
80
|
+
],
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@pytest.fixture
|
|
86
|
+
def project(tmp_path: Path) -> Path:
|
|
87
|
+
"""Create a minimal project with workflow YAMLs and session."""
|
|
88
|
+
workflows_dir = tmp_path / ".pennyfarthing" / "workflows"
|
|
89
|
+
workflows_dir.mkdir(parents=True)
|
|
90
|
+
(tmp_path / ".session").mkdir()
|
|
91
|
+
|
|
92
|
+
# Write the file-only-gate workflow
|
|
93
|
+
(workflows_dir / "file-only-test.yaml").write_text(
|
|
94
|
+
yaml.dump(WORKFLOW_FILE_ONLY_GATE, default_flow_style=False)
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
# Copy real tdd.yaml for integration tests
|
|
98
|
+
tdd_src = WORKFLOWS_DIR / "tdd.yaml"
|
|
99
|
+
if tdd_src.exists():
|
|
100
|
+
(workflows_dir / "tdd.yaml").write_text(tdd_src.read_text())
|
|
101
|
+
|
|
102
|
+
return tmp_path
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
# ===========================================================================
|
|
106
|
+
# AC1: pennyfarthing-dist/agents/handoff.md deleted
|
|
107
|
+
# ===========================================================================
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class TestHandoffMdDeleted:
|
|
111
|
+
"""AC1: handoff.md must not exist in pennyfarthing-dist/agents/."""
|
|
112
|
+
|
|
113
|
+
def test_handoff_md_does_not_exist(self) -> None:
|
|
114
|
+
"""AC1: handoff.md should be deleted from agents directory."""
|
|
115
|
+
assert not (AGENTS_DIR / "handoff.md").exists(), (
|
|
116
|
+
"handoff.md still exists — should be deleted in 108-2"
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
# ===========================================================================
|
|
121
|
+
# AC2: pennyfarthing-dist/agents/sm-handoff.md deleted
|
|
122
|
+
# ===========================================================================
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
class TestSmHandoffMdDeleted:
|
|
126
|
+
"""AC2: sm-handoff.md must not exist in pennyfarthing-dist/agents/."""
|
|
127
|
+
|
|
128
|
+
def test_sm_handoff_md_does_not_exist(self) -> None:
|
|
129
|
+
"""AC2: sm-handoff.md should be deleted from agents directory."""
|
|
130
|
+
assert not (AGENTS_DIR / "sm-handoff.md").exists(), (
|
|
131
|
+
"sm-handoff.md still exists — should be deleted in 108-2"
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
# ===========================================================================
|
|
136
|
+
# AC3: gate.type fallback removed — file-only gates resolve properly
|
|
137
|
+
# ===========================================================================
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
class TestGateTypeFallbackRemoved:
|
|
141
|
+
"""AC3: gate with file but no type should resolve via assessment, not skip."""
|
|
142
|
+
|
|
143
|
+
def test_no_gate_at_all_still_returns_skip(
|
|
144
|
+
self, project: Path
|
|
145
|
+
) -> None:
|
|
146
|
+
"""AC3: Phase with no gate (setup/finish) should still return 'skip'."""
|
|
147
|
+
session = project / ".session" / "108-2-session.md"
|
|
148
|
+
session.write_text(SESSION_WITH_ASSESSMENT)
|
|
149
|
+
|
|
150
|
+
result = resolve_gate(
|
|
151
|
+
"108-2", "tdd", "setup", project_root=project
|
|
152
|
+
)
|
|
153
|
+
assert result["status"] == "skip", (
|
|
154
|
+
f"No-gate phase should skip, got: {result['status']}"
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
def test_gate_file_only_returns_ready_with_assessment(
|
|
158
|
+
self, project: Path
|
|
159
|
+
) -> None:
|
|
160
|
+
"""AC3: gate with file but no type + assessment → 'ready' (not 'skip').
|
|
161
|
+
|
|
162
|
+
Before 108-2: gate_type is None → skip (fallback).
|
|
163
|
+
After 108-2: gate.file present → check assessment → ready.
|
|
164
|
+
"""
|
|
165
|
+
session = project / ".session" / "108-2-session.md"
|
|
166
|
+
session.write_text(SESSION_WITH_ASSESSMENT)
|
|
167
|
+
|
|
168
|
+
result = resolve_gate(
|
|
169
|
+
"108-2", "file-only-test", "green", project_root=project
|
|
170
|
+
)
|
|
171
|
+
assert result["status"] == "ready", (
|
|
172
|
+
f"File-only gate with assessment should be 'ready', got: "
|
|
173
|
+
f"'{result['status']}' — gate.type fallback still active?"
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
def test_gate_file_only_returns_blocked_without_assessment(
|
|
177
|
+
self, project: Path
|
|
178
|
+
) -> None:
|
|
179
|
+
"""AC3: gate with file but no type + NO assessment → 'blocked'.
|
|
180
|
+
|
|
181
|
+
Before 108-2: gate_type is None → skip (ignores missing assessment).
|
|
182
|
+
After 108-2: gate.file present → check assessment → blocked.
|
|
183
|
+
"""
|
|
184
|
+
session = project / ".session" / "108-2-session.md"
|
|
185
|
+
session.write_text(SESSION_WITHOUT_ASSESSMENT)
|
|
186
|
+
|
|
187
|
+
result = resolve_gate(
|
|
188
|
+
"108-2", "file-only-test", "green", project_root=project
|
|
189
|
+
)
|
|
190
|
+
assert result["status"] == "blocked", (
|
|
191
|
+
f"File-only gate without assessment should be 'blocked', got: "
|
|
192
|
+
f"'{result['status']}' — gate.type fallback still active?"
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
def test_gate_file_only_populates_gate_file_field(
|
|
196
|
+
self, project: Path
|
|
197
|
+
) -> None:
|
|
198
|
+
"""AC3: gate_file field should be populated for file-only gates."""
|
|
199
|
+
session = project / ".session" / "108-2-session.md"
|
|
200
|
+
session.write_text(SESSION_WITH_ASSESSMENT)
|
|
201
|
+
|
|
202
|
+
result = resolve_gate(
|
|
203
|
+
"108-2", "file-only-test", "green", project_root=project
|
|
204
|
+
)
|
|
205
|
+
assert result["gate_file"] == "gates/tests-pass", (
|
|
206
|
+
f"Expected gate_file='gates/tests-pass', got: {result['gate_file']}"
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
def test_gate_with_both_file_and_type_still_ready(
|
|
210
|
+
self, project: Path
|
|
211
|
+
) -> None:
|
|
212
|
+
"""AC3: gate with both file and type should still return 'ready'."""
|
|
213
|
+
session = project / ".session" / "108-2-session.md"
|
|
214
|
+
session.write_text(SESSION_WITH_ASSESSMENT)
|
|
215
|
+
|
|
216
|
+
result = resolve_gate(
|
|
217
|
+
"108-2", "tdd", "green", project_root=project
|
|
218
|
+
)
|
|
219
|
+
assert result["status"] == "ready"
|
|
220
|
+
assert result["gate_file"] == "gates/tests-pass"
|
|
221
|
+
assert result["gate_type"] == "tests_pass"
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
# ===========================================================================
|
|
225
|
+
# AC5: No remaining references to handoff/sm-handoff subagents
|
|
226
|
+
# ===========================================================================
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
class TestNoSubagentReferences:
|
|
230
|
+
"""AC5: No agent files should reference the deleted subagents."""
|
|
231
|
+
|
|
232
|
+
def _scan_agent_files(self, pattern: str) -> list[tuple[str, str]]:
|
|
233
|
+
"""Scan all agent .md files for a regex pattern. Returns (file, line) pairs."""
|
|
234
|
+
hits = []
|
|
235
|
+
for md_file in AGENTS_DIR.glob("*.md"):
|
|
236
|
+
# Skip the files being deleted (they'll be gone)
|
|
237
|
+
if md_file.name in ("handoff.md", "sm-handoff.md"):
|
|
238
|
+
continue
|
|
239
|
+
content = md_file.read_text()
|
|
240
|
+
for line in content.splitlines():
|
|
241
|
+
if re.search(pattern, line, re.IGNORECASE):
|
|
242
|
+
hits.append((md_file.name, line.strip()))
|
|
243
|
+
return hits
|
|
244
|
+
|
|
245
|
+
def test_no_agent_references_handoff_subagent(self) -> None:
|
|
246
|
+
"""AC5: No agent .md files should spawn or reference 'handoff' subagent.
|
|
247
|
+
|
|
248
|
+
Matches patterns like:
|
|
249
|
+
- subagent_type: handoff
|
|
250
|
+
- spawn handoff
|
|
251
|
+
- sm-handoff subagent
|
|
252
|
+
But NOT: 'pf handoff' (the CLI) or 'handoff.ts' (the module)
|
|
253
|
+
"""
|
|
254
|
+
# Look for subagent spawn references to the deprecated handoff subagent
|
|
255
|
+
hits = self._scan_agent_files(
|
|
256
|
+
r'(?:subagent.*handoff\.md|sm-handoff\.md|"sm-handoff"|"handoff"'
|
|
257
|
+
r"|handoff subagent|sm-handoff subagent)"
|
|
258
|
+
)
|
|
259
|
+
assert len(hits) == 0, (
|
|
260
|
+
f"Found {len(hits)} references to deprecated handoff subagents:\n"
|
|
261
|
+
+ "\n".join(f" {f}: {line}" for f, line in hits)
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
def test_agents_readme_no_deprecated_entries(self) -> None:
|
|
265
|
+
"""AC5: README.md should not list handoff.md or sm-handoff.md."""
|
|
266
|
+
readme = AGENTS_DIR / "README.md"
|
|
267
|
+
if not readme.exists():
|
|
268
|
+
pytest.skip("No agents README.md")
|
|
269
|
+
|
|
270
|
+
content = readme.read_text()
|
|
271
|
+
# Should not have the deprecated entries
|
|
272
|
+
assert "handoff.md" not in content, (
|
|
273
|
+
"README.md still references handoff.md"
|
|
274
|
+
)
|
|
275
|
+
assert "sm-handoff.md" not in content, (
|
|
276
|
+
"README.md still references sm-handoff.md"
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
# ===========================================================================
|
|
281
|
+
# AC7: Single code path — all gated phases have gate.file
|
|
282
|
+
# ===========================================================================
|
|
283
|
+
|
|
284
|
+
# Phased workflows that should have gate.file on every gated phase
|
|
285
|
+
PHASED_WORKFLOWS = ["tdd", "trivial", "bdd", "bdd-tandem", "tdd-tandem",
|
|
286
|
+
"2party-tdd", "agent-docs", "patch"]
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
class TestAllGatedPhasesHaveGateFile:
|
|
290
|
+
"""AC7: Every gated phase in phased workflows must have gate.file."""
|
|
291
|
+
|
|
292
|
+
def _load_workflow(self, name: str) -> dict | None:
|
|
293
|
+
"""Load a workflow YAML, return None if not found."""
|
|
294
|
+
path = WORKFLOWS_DIR / f"{name}.yaml"
|
|
295
|
+
if not path.exists():
|
|
296
|
+
return None
|
|
297
|
+
return yaml.safe_load(path.read_text())
|
|
298
|
+
|
|
299
|
+
def _gated_phases_missing_file(
|
|
300
|
+
self, workflow_data: dict
|
|
301
|
+
) -> list[tuple[str, str]]:
|
|
302
|
+
"""Find phases that have a gate but no gate.file.
|
|
303
|
+
|
|
304
|
+
Returns list of (phase_name, gate_type) tuples.
|
|
305
|
+
Manual gates are excluded — they skip resolution entirely.
|
|
306
|
+
"""
|
|
307
|
+
missing = []
|
|
308
|
+
for phase in workflow_data["workflow"]["phases"]:
|
|
309
|
+
gate = phase.get("gate")
|
|
310
|
+
if not gate:
|
|
311
|
+
continue # No gate at all — fine (setup, finish)
|
|
312
|
+
gate_type = gate.get("type")
|
|
313
|
+
if gate_type == "manual":
|
|
314
|
+
continue # Manual gates always skip
|
|
315
|
+
if not gate.get("file"):
|
|
316
|
+
missing.append((phase["name"], gate_type or "(no type)"))
|
|
317
|
+
return missing
|
|
318
|
+
|
|
319
|
+
@pytest.mark.parametrize("workflow_name", PHASED_WORKFLOWS)
|
|
320
|
+
def test_workflow_gated_phases_have_gate_file(
|
|
321
|
+
self, workflow_name: str
|
|
322
|
+
) -> None:
|
|
323
|
+
"""AC7: Every non-manual gated phase must have gate.file defined.
|
|
324
|
+
|
|
325
|
+
This ensures the single code path — resolve-gate only needs
|
|
326
|
+
gate.file, not gate.type, for resolution.
|
|
327
|
+
"""
|
|
328
|
+
wf = self._load_workflow(workflow_name)
|
|
329
|
+
if wf is None:
|
|
330
|
+
pytest.skip(f"Workflow {workflow_name} not found")
|
|
331
|
+
|
|
332
|
+
missing = self._gated_phases_missing_file(wf)
|
|
333
|
+
assert len(missing) == 0, (
|
|
334
|
+
f"Workflow '{workflow_name}' has gated phases without gate.file:\n"
|
|
335
|
+
+ "\n".join(
|
|
336
|
+
f" - {name} (type={gtype})" for name, gtype in missing
|
|
337
|
+
)
|
|
338
|
+
+ "\nAll gated phases must have gate.file for single code path."
|
|
339
|
+
)
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
"""Tests for SM confidence gate evaluation results — Story 90-3.
|
|
2
|
+
|
|
3
|
+
Epic: 90 (Confidence Circuit Breaker via Gate)
|
|
4
|
+
Story: 90-3 — Evaluate and document results
|
|
5
|
+
|
|
6
|
+
Tests the evaluation document that assesses the SM confidence gate's
|
|
7
|
+
effectiveness after deployment. The gate (confidence-sm.md) was shipped
|
|
8
|
+
in story 90-2. This story evaluates trigger frequency, wrong-approach
|
|
9
|
+
reduction, user experience, and makes a rollout recommendation.
|
|
10
|
+
|
|
11
|
+
Acceptance Criteria:
|
|
12
|
+
- [AC1] Evaluate trigger frequency after 1 sprint
|
|
13
|
+
- [AC2] Document whether it reduced wrong-approach incidents
|
|
14
|
+
- [AC3] Assess if the gate was annoying (user overrode or dismissed)
|
|
15
|
+
- [AC4] Document findings in a results file
|
|
16
|
+
- [AC5] Decide on rollout recommendation to other agents
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import re
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
|
|
24
|
+
import pytest
|
|
25
|
+
|
|
26
|
+
# ---------------------------------------------------------------------------
|
|
27
|
+
# Path resolution
|
|
28
|
+
# ---------------------------------------------------------------------------
|
|
29
|
+
|
|
30
|
+
_THIS_DIR = Path(__file__).resolve().parent
|
|
31
|
+
_SCRIPTS_DIR = _THIS_DIR.parent # pennyfarthing_scripts/
|
|
32
|
+
_FRAMEWORK_ROOT = _SCRIPTS_DIR.parent # pennyfarthing/
|
|
33
|
+
_EVAL_FILE = (
|
|
34
|
+
_FRAMEWORK_ROOT
|
|
35
|
+
/ "pennyfarthing-dist"
|
|
36
|
+
/ "gates"
|
|
37
|
+
/ "evaluations"
|
|
38
|
+
/ "confidence-sm.md"
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
# ---------------------------------------------------------------------------
|
|
42
|
+
# Fixtures
|
|
43
|
+
# ---------------------------------------------------------------------------
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@pytest.fixture
|
|
47
|
+
def eval_path() -> Path:
|
|
48
|
+
"""Return the expected path to the evaluation results file."""
|
|
49
|
+
return _EVAL_FILE
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@pytest.fixture
|
|
53
|
+
def eval_content(eval_path: Path) -> str:
|
|
54
|
+
"""Read and return the evaluation file content. Fails if file missing."""
|
|
55
|
+
assert eval_path.is_file(), f"Evaluation file not found: {eval_path}"
|
|
56
|
+
return eval_path.read_text()
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
# ===========================================================================
|
|
60
|
+
# AC1: Evaluate trigger frequency after 1 sprint
|
|
61
|
+
# ===========================================================================
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class TestTriggerFrequency:
|
|
65
|
+
"""AC1: Evaluation documents how often the gate triggered."""
|
|
66
|
+
|
|
67
|
+
def test_eval_has_trigger_section(self, eval_content: str) -> None:
|
|
68
|
+
"""AC1: Evaluation has a section about trigger frequency."""
|
|
69
|
+
assert re.search(
|
|
70
|
+
r"(?i)trigger.*frequen|frequen.*trigger|how often|activation.*rate",
|
|
71
|
+
eval_content,
|
|
72
|
+
), "Missing trigger frequency section"
|
|
73
|
+
|
|
74
|
+
def test_trigger_section_has_data(self, eval_content: str) -> None:
|
|
75
|
+
"""AC1: Trigger section includes specific data or baseline note."""
|
|
76
|
+
content_lower = eval_content.lower()
|
|
77
|
+
assert any(
|
|
78
|
+
term in content_lower
|
|
79
|
+
for term in [
|
|
80
|
+
"triggered",
|
|
81
|
+
"fired",
|
|
82
|
+
"activated",
|
|
83
|
+
"invoked",
|
|
84
|
+
"n/a",
|
|
85
|
+
"baseline",
|
|
86
|
+
"times",
|
|
87
|
+
"occurrences",
|
|
88
|
+
]
|
|
89
|
+
), "Trigger section lacks frequency data or baseline acknowledgment"
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
# ===========================================================================
|
|
93
|
+
# AC2: Document whether it reduced wrong-approach incidents
|
|
94
|
+
# ===========================================================================
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class TestWrongApproachReduction:
|
|
98
|
+
"""AC2: Evaluation documents impact on wrong-approach incidents."""
|
|
99
|
+
|
|
100
|
+
def test_eval_has_wrong_approach_section(self, eval_content: str) -> None:
|
|
101
|
+
"""AC2: Evaluation has a section about wrong-approach incidents."""
|
|
102
|
+
content_lower = eval_content.lower()
|
|
103
|
+
assert any(
|
|
104
|
+
term in content_lower
|
|
105
|
+
for term in [
|
|
106
|
+
"wrong-approach",
|
|
107
|
+
"wrong approach",
|
|
108
|
+
"incident",
|
|
109
|
+
"misroute",
|
|
110
|
+
"incorrect action",
|
|
111
|
+
"ambiguous",
|
|
112
|
+
]
|
|
113
|
+
), "Missing wrong-approach reduction section"
|
|
114
|
+
|
|
115
|
+
def test_wrong_approach_has_comparison(self, eval_content: str) -> None:
|
|
116
|
+
"""AC2: Wrong-approach section includes comparison or baseline note."""
|
|
117
|
+
content_lower = eval_content.lower()
|
|
118
|
+
assert any(
|
|
119
|
+
term in content_lower
|
|
120
|
+
for term in [
|
|
121
|
+
"before",
|
|
122
|
+
"after",
|
|
123
|
+
"baseline",
|
|
124
|
+
"reduced",
|
|
125
|
+
"comparison",
|
|
126
|
+
"prior",
|
|
127
|
+
"improvement",
|
|
128
|
+
"no data yet",
|
|
129
|
+
]
|
|
130
|
+
), "Wrong-approach section lacks before/after comparison or baseline"
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
# ===========================================================================
|
|
134
|
+
# AC3: Assess if the gate was annoying
|
|
135
|
+
# ===========================================================================
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
class TestUserExperience:
|
|
139
|
+
"""AC3: Evaluation assesses user experience with the gate."""
|
|
140
|
+
|
|
141
|
+
def test_eval_has_ux_section(self, eval_content: str) -> None:
|
|
142
|
+
"""AC3: Evaluation has a section about user experience."""
|
|
143
|
+
content_lower = eval_content.lower()
|
|
144
|
+
assert any(
|
|
145
|
+
term in content_lower
|
|
146
|
+
for term in [
|
|
147
|
+
"user experience",
|
|
148
|
+
"annoying",
|
|
149
|
+
"friction",
|
|
150
|
+
"override",
|
|
151
|
+
"dismiss",
|
|
152
|
+
"usability",
|
|
153
|
+
]
|
|
154
|
+
), "Missing user experience assessment section"
|
|
155
|
+
|
|
156
|
+
def test_ux_mentions_override_behavior(self, eval_content: str) -> None:
|
|
157
|
+
"""AC3: UX section addresses override/dismissal behavior."""
|
|
158
|
+
content_lower = eval_content.lower()
|
|
159
|
+
assert any(
|
|
160
|
+
term in content_lower
|
|
161
|
+
for term in ["override", "dismiss", "bypass", "skip", "ignored"]
|
|
162
|
+
), "UX section doesn't address override/dismissal behavior"
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
# ===========================================================================
|
|
166
|
+
# AC4: Document findings in a results file
|
|
167
|
+
# ===========================================================================
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
class TestResultsFile:
|
|
171
|
+
"""AC4: Evaluation results file exists with proper structure."""
|
|
172
|
+
|
|
173
|
+
def test_eval_file_exists(self, eval_path: Path) -> None:
|
|
174
|
+
"""AC4: Evaluation results file exists at expected location."""
|
|
175
|
+
assert eval_path.is_file(), f"Evaluation file not found: {eval_path}"
|
|
176
|
+
|
|
177
|
+
def test_eval_file_not_empty(self, eval_path: Path) -> None:
|
|
178
|
+
"""AC4: Evaluation file is not empty."""
|
|
179
|
+
assert eval_path.is_file(), f"File not found: {eval_path}"
|
|
180
|
+
content = eval_path.read_text()
|
|
181
|
+
assert len(content.strip()) > 0, "Evaluation file is empty"
|
|
182
|
+
|
|
183
|
+
def test_eval_has_title(self, eval_content: str) -> None:
|
|
184
|
+
"""AC4: Evaluation has a title heading."""
|
|
185
|
+
assert re.search(r"^#\s+", eval_content, re.MULTILINE), (
|
|
186
|
+
"Evaluation file missing title heading"
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
def test_eval_has_structured_sections(self, eval_content: str) -> None:
|
|
190
|
+
"""AC4: Evaluation has multiple sections (at least 3 headings)."""
|
|
191
|
+
headings = re.findall(r"^##\s+", eval_content, re.MULTILINE)
|
|
192
|
+
assert len(headings) >= 3, (
|
|
193
|
+
f"Expected at least 3 sections, found {len(headings)}"
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
def test_eval_references_gate(self, eval_content: str) -> None:
|
|
197
|
+
"""AC4: Evaluation references the confidence-sm gate."""
|
|
198
|
+
assert "confidence-sm" in eval_content, (
|
|
199
|
+
"Evaluation doesn't reference the confidence-sm gate"
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
# ===========================================================================
|
|
204
|
+
# AC5: Decide on rollout recommendation to other agents
|
|
205
|
+
# ===========================================================================
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
class TestRolloutRecommendation:
|
|
209
|
+
"""AC5: Evaluation includes a rollout recommendation."""
|
|
210
|
+
|
|
211
|
+
def test_eval_has_recommendation_section(self, eval_content: str) -> None:
|
|
212
|
+
"""AC5: Evaluation has a rollout recommendation section."""
|
|
213
|
+
content_lower = eval_content.lower()
|
|
214
|
+
assert any(
|
|
215
|
+
term in content_lower
|
|
216
|
+
for term in ["recommendation", "rollout", "next steps", "decision"]
|
|
217
|
+
), "Missing rollout recommendation section"
|
|
218
|
+
|
|
219
|
+
def test_recommendation_is_actionable(self, eval_content: str) -> None:
|
|
220
|
+
"""AC5: Recommendation states a clear actionable decision."""
|
|
221
|
+
content_lower = eval_content.lower()
|
|
222
|
+
assert any(
|
|
223
|
+
term in content_lower
|
|
224
|
+
for term in [
|
|
225
|
+
"expand",
|
|
226
|
+
"hold",
|
|
227
|
+
"remove",
|
|
228
|
+
"proceed",
|
|
229
|
+
"defer",
|
|
230
|
+
"recommend",
|
|
231
|
+
"adopt",
|
|
232
|
+
"extend",
|
|
233
|
+
]
|
|
234
|
+
), "Recommendation lacks clear actionable decision"
|
|
235
|
+
|
|
236
|
+
def test_recommendation_mentions_other_agents(
|
|
237
|
+
self, eval_content: str
|
|
238
|
+
) -> None:
|
|
239
|
+
"""AC5: Recommendation addresses rollout to other agents."""
|
|
240
|
+
content_lower = eval_content.lower()
|
|
241
|
+
assert any(
|
|
242
|
+
term in content_lower
|
|
243
|
+
for term in [
|
|
244
|
+
"other agent",
|
|
245
|
+
"additional agent",
|
|
246
|
+
"tea",
|
|
247
|
+
"dev",
|
|
248
|
+
"reviewer",
|
|
249
|
+
"all agents",
|
|
250
|
+
"agent-specific",
|
|
251
|
+
"per-agent",
|
|
252
|
+
]
|
|
253
|
+
), "Recommendation doesn't address rollout to other agents"
|