@pennyfarthing/core 10.0.5 → 10.2.0
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 +19 -22
- package/package.json +17 -11
- package/packages/core/dist/cli/commands/doctor-file-layout.test.js.map +1 -1
- package/packages/core/dist/cli/commands/doctor-legacy.test.js +24 -0
- package/packages/core/dist/cli/commands/doctor-legacy.test.js.map +1 -1
- package/packages/core/dist/cli/commands/doctor.d.ts.map +1 -1
- package/packages/core/dist/cli/commands/doctor.js +346 -13
- package/packages/core/dist/cli/commands/doctor.js.map +1 -1
- package/packages/core/dist/cli/commands/e2e-fresh-install.test.js +1 -1
- package/packages/core/dist/cli/commands/e2e-fresh-install.test.js.map +1 -1
- package/packages/core/dist/cli/commands/e2e-upgrade.test.js +1 -1
- package/packages/core/dist/cli/commands/e2e-upgrade.test.js.map +1 -1
- package/packages/core/dist/cli/commands/hooks-consolidation.test.js +2 -2
- package/packages/core/dist/cli/commands/hooks-consolidation.test.js.map +1 -1
- package/packages/core/dist/cli/commands/init-consolidation.test.js.map +1 -1
- package/packages/core/dist/cli/commands/init.d.ts +7 -0
- package/packages/core/dist/cli/commands/init.d.ts.map +1 -1
- package/packages/core/dist/cli/commands/init.js +41 -8
- package/packages/core/dist/cli/commands/init.js.map +1 -1
- package/packages/core/dist/cli/commands/uninstall.d.ts.map +1 -1
- package/packages/core/dist/cli/commands/uninstall.js +24 -13
- package/packages/core/dist/cli/commands/uninstall.js.map +1 -1
- package/packages/core/dist/cli/commands/update-consolidation.test.js +0 -10
- package/packages/core/dist/cli/commands/update-consolidation.test.js.map +1 -1
- package/packages/core/dist/cli/commands/update.d.ts.map +1 -1
- package/packages/core/dist/cli/commands/update.js +26 -0
- package/packages/core/dist/cli/commands/update.js.map +1 -1
- package/packages/core/dist/cli/index.js +1 -1
- package/packages/core/dist/cli/index.js.map +1 -1
- package/packages/core/dist/cli/ocean-profiles.test.js.map +1 -1
- package/packages/core/dist/cli/theme-maker.test.js +64 -115
- package/packages/core/dist/cli/theme-maker.test.js.map +1 -1
- package/packages/core/dist/cli/utils/python.d.ts +22 -0
- package/packages/core/dist/cli/utils/python.d.ts.map +1 -0
- package/packages/core/dist/cli/utils/python.js +102 -0
- package/packages/core/dist/cli/utils/python.js.map +1 -0
- package/packages/core/dist/cli/utils/settings.d.ts.map +1 -1
- package/packages/core/dist/cli/utils/settings.js +10 -0
- package/packages/core/dist/cli/utils/settings.js.map +1 -1
- package/packages/core/dist/index.d.ts +1 -1
- package/packages/core/dist/index.d.ts.map +1 -1
- package/packages/core/dist/index.js +2 -2
- package/packages/core/dist/index.js.map +1 -1
- package/packages/core/dist/plugins/plugin-discovery.d.ts +116 -0
- package/packages/core/dist/plugins/plugin-discovery.d.ts.map +1 -0
- package/packages/core/dist/plugins/plugin-discovery.js +165 -0
- package/packages/core/dist/plugins/plugin-discovery.js.map +1 -0
- package/packages/core/dist/plugins/plugin-discovery.test.d.ts +22 -0
- package/packages/core/dist/plugins/plugin-discovery.test.d.ts.map +1 -0
- package/packages/core/dist/plugins/plugin-discovery.test.js +498 -0
- package/packages/core/dist/plugins/plugin-discovery.test.js.map +1 -0
- package/packages/core/dist/scripts/generate-spider-report.js.map +1 -1
- package/packages/core/dist/workflow/context-watch.d.ts +80 -0
- package/packages/core/dist/workflow/context-watch.d.ts.map +1 -0
- package/packages/core/dist/workflow/context-watch.js +235 -0
- package/packages/core/dist/workflow/context-watch.js.map +1 -0
- package/packages/core/dist/workflow/context-watch.test.d.ts +1 -0
- package/packages/core/dist/workflow/context-watch.test.d.ts.map +1 -0
- package/packages/core/dist/workflow/context-watch.test.js +746 -0
- package/packages/core/dist/workflow/context-watch.test.js.map +1 -0
- package/packages/core/dist/workflow/file-watch.d.ts +82 -0
- package/packages/core/dist/workflow/file-watch.d.ts.map +1 -0
- package/packages/core/dist/workflow/file-watch.js +198 -0
- package/packages/core/dist/workflow/file-watch.js.map +1 -0
- package/packages/core/dist/workflow/file-watch.test.d.ts +21 -0
- package/packages/core/dist/workflow/file-watch.test.d.ts.map +1 -0
- package/packages/core/dist/workflow/file-watch.test.js +469 -0
- package/packages/core/dist/workflow/file-watch.test.js.map +1 -0
- package/packages/core/dist/workflow/observation-writer.d.ts +79 -0
- package/packages/core/dist/workflow/observation-writer.d.ts.map +1 -0
- package/packages/core/dist/workflow/observation-writer.js +97 -0
- package/packages/core/dist/workflow/observation-writer.js.map +1 -0
- package/packages/core/dist/workflow/observation-writer.test.d.ts +18 -0
- package/packages/core/dist/workflow/observation-writer.test.d.ts.map +1 -0
- package/packages/core/dist/workflow/observation-writer.test.js +424 -0
- package/packages/core/dist/workflow/observation-writer.test.js.map +1 -0
- package/packages/core/dist/workflow/story-workflow-routing.test.js +4 -2
- package/packages/core/dist/workflow/story-workflow-routing.test.js.map +1 -1
- package/packages/core/dist/workflow/tandem-lifecycle.d.ts +117 -0
- package/packages/core/dist/workflow/tandem-lifecycle.d.ts.map +1 -0
- package/packages/core/dist/workflow/tandem-lifecycle.js +186 -0
- package/packages/core/dist/workflow/tandem-lifecycle.js.map +1 -0
- package/packages/core/dist/workflow/tandem-lifecycle.test.d.ts +16 -0
- package/packages/core/dist/workflow/tandem-lifecycle.test.d.ts.map +1 -0
- package/packages/core/dist/workflow/tandem-lifecycle.test.js +531 -0
- package/packages/core/dist/workflow/tandem-lifecycle.test.js.map +1 -0
- package/packages/core/dist/workflow/tool-watch.d.ts +68 -0
- package/packages/core/dist/workflow/tool-watch.d.ts.map +1 -0
- package/packages/core/dist/workflow/tool-watch.js +166 -0
- package/packages/core/dist/workflow/tool-watch.js.map +1 -0
- package/packages/core/dist/workflow/tool-watch.test.d.ts +18 -0
- package/packages/core/dist/workflow/tool-watch.test.d.ts.map +1 -0
- package/packages/core/dist/workflow/tool-watch.test.js +718 -0
- package/packages/core/dist/workflow/tool-watch.test.js.map +1 -0
- package/packages/core/dist/workflow/workflow-migration.test.js +8 -4
- package/packages/core/dist/workflow/workflow-migration.test.js.map +1 -1
- package/packages/core/dist/workflow/workflow-schema.d.ts +7 -0
- package/packages/core/dist/workflow/workflow-schema.d.ts.map +1 -1
- package/packages/core/dist/workflow/workflow-schema.js +44 -0
- package/packages/core/dist/workflow/workflow-schema.js.map +1 -1
- package/packages/core/dist/workflow/workflow-schema.test.d.ts.map +1 -1
- package/packages/core/dist/workflow/workflow-schema.test.js +192 -0
- package/packages/core/dist/workflow/workflow-schema.test.js.map +1 -1
- package/pennyfarthing-dist/agents/README.md +1 -3
- package/pennyfarthing-dist/agents/architect.md +0 -6
- package/pennyfarthing-dist/agents/devops.md +0 -6
- package/pennyfarthing-dist/agents/handoff.md +18 -3
- package/pennyfarthing-dist/agents/orchestrator.md +0 -6
- package/pennyfarthing-dist/agents/pm.md +0 -6
- package/pennyfarthing-dist/agents/sm-finish.md +1 -1
- package/pennyfarthing-dist/agents/sm-handoff.md +27 -4
- package/pennyfarthing-dist/agents/sm.md +11 -11
- package/pennyfarthing-dist/agents/tandem-backseat.md +119 -0
- package/pennyfarthing-dist/commands/architect.md +11 -3
- package/pennyfarthing-dist/commands/close-epic.md +24 -131
- package/pennyfarthing-dist/commands/create-theme.md +14 -24
- package/pennyfarthing-dist/commands/dev.md +11 -3
- package/pennyfarthing-dist/commands/devops.md +11 -3
- package/pennyfarthing-dist/commands/health-check.md +1 -3
- package/pennyfarthing-dist/commands/help.md +8 -12
- package/pennyfarthing-dist/commands/list-themes.md +14 -16
- package/pennyfarthing-dist/commands/orchestrator.md +11 -3
- package/pennyfarthing-dist/commands/parallel-work.md +1 -3
- package/pennyfarthing-dist/commands/pm.md +11 -3
- package/pennyfarthing-dist/commands/prime.md +6 -6
- package/pennyfarthing-dist/commands/reviewer.md +11 -3
- package/pennyfarthing-dist/commands/run-ci.md +1 -1
- package/pennyfarthing-dist/commands/set-theme.md +14 -51
- package/pennyfarthing-dist/commands/setup.md +5 -1
- package/pennyfarthing-dist/commands/show-theme.md +14 -16
- package/pennyfarthing-dist/commands/sm.md +11 -3
- package/pennyfarthing-dist/commands/tea.md +11 -3
- package/pennyfarthing-dist/commands/tech-writer.md +11 -3
- package/pennyfarthing-dist/commands/theme-maker.md +14 -671
- package/pennyfarthing-dist/commands/theme.md +95 -0
- package/pennyfarthing-dist/commands/ux-designer.md +11 -3
- package/pennyfarthing-dist/commands/work.md +3 -5
- package/pennyfarthing-dist/guides/agent-behavior.md +62 -6
- package/pennyfarthing-dist/guides/agent-coordination.md +11 -13
- package/pennyfarthing-dist/guides/agent-template-tactical.md +2 -3
- package/pennyfarthing-dist/guides/bikelane.md +3 -2
- package/pennyfarthing-dist/guides/command-tag-taxonomy.md +212 -0
- package/pennyfarthing-dist/guides/hooks.md +5 -5
- package/pennyfarthing-dist/guides/patterns/fan-out-fan-in-pattern.md +3 -3
- package/pennyfarthing-dist/guides/patterns/helper-delegation-pattern.md +9 -59
- package/pennyfarthing-dist/guides/patterns/tdd-flow-pattern.md +4 -5
- package/pennyfarthing-dist/guides/prime.md +2 -2
- package/pennyfarthing-dist/guides/scale-levels.md +4 -6
- package/pennyfarthing-dist/guides/skill-schema.md +4 -4
- package/pennyfarthing-dist/guides/tandem-protocol.md +158 -0
- package/pennyfarthing-dist/personas/themes/discworld.yaml +1 -1
- package/pennyfarthing-dist/personas/themes/fifth-element.yaml +295 -0
- package/pennyfarthing-dist/scripts/README.md +1 -1
- package/pennyfarthing-dist/scripts/core/agent-session.sh +6 -2
- package/pennyfarthing-dist/scripts/core/check-context.sh +0 -0
- package/pennyfarthing-dist/scripts/core/handoff-marker.sh +0 -0
- package/pennyfarthing-dist/scripts/core/phase-check-start.sh +0 -0
- package/pennyfarthing-dist/scripts/core/prime.sh +8 -10
- package/pennyfarthing-dist/scripts/cyclist/is-cyclist.sh +0 -0
- package/pennyfarthing-dist/scripts/git/create-feature-branches.sh +0 -0
- package/pennyfarthing-dist/scripts/git/git-status-all.sh +0 -0
- package/pennyfarthing-dist/scripts/git/install-git-hooks.sh +8 -6
- package/pennyfarthing-dist/scripts/git/release.sh +0 -0
- package/pennyfarthing-dist/scripts/git/worktree-manager.sh +0 -0
- package/pennyfarthing-dist/scripts/health/drift-detection.sh +0 -0
- package/pennyfarthing-dist/scripts/hooks/bell-mode-hook.sh +131 -54
- package/pennyfarthing-dist/scripts/hooks/context-circuit-breaker.sh +0 -0
- package/pennyfarthing-dist/scripts/hooks/context-warning.sh +0 -0
- package/pennyfarthing-dist/scripts/hooks/cyclist-pretooluse-hook.sh +0 -0
- package/pennyfarthing-dist/scripts/hooks/otel-auto-config.sh +0 -0
- package/pennyfarthing-dist/scripts/hooks/post-merge.sh +32 -15
- package/pennyfarthing-dist/scripts/hooks/pre-commit.sh +4 -3
- package/pennyfarthing-dist/scripts/hooks/pre-edit-check.sh +0 -0
- package/pennyfarthing-dist/scripts/hooks/pre-push.sh +11 -5
- package/pennyfarthing-dist/scripts/hooks/question-reflector-check.sh +0 -0
- package/pennyfarthing-dist/scripts/hooks/question_reflector_check.py +0 -0
- package/pennyfarthing-dist/scripts/hooks/schema-validation.sh +0 -0
- package/pennyfarthing-dist/scripts/hooks/session-start.sh +0 -0
- package/pennyfarthing-dist/scripts/hooks/session-stop.sh +0 -0
- package/pennyfarthing-dist/scripts/hooks/sprint-yaml-validation.sh +0 -0
- package/pennyfarthing-dist/scripts/hooks/welcome-hook.sh +0 -0
- package/pennyfarthing-dist/scripts/jira/create-jira-epic.sh +0 -0
- package/pennyfarthing-dist/scripts/jira/create-jira-story.sh +0 -0
- package/pennyfarthing-dist/scripts/jira/jira-claim-story.sh +0 -0
- package/pennyfarthing-dist/scripts/jira/jira-reconcile.sh +0 -0
- package/pennyfarthing-dist/scripts/jira/jira-sync-story.sh +0 -0
- package/pennyfarthing-dist/scripts/jira/sync-epic-jira.sh +0 -0
- package/pennyfarthing-dist/scripts/lib/background-tasks.sh +0 -0
- package/pennyfarthing-dist/scripts/lib/checkpoint.sh +0 -0
- package/pennyfarthing-dist/scripts/lib/common.sh +0 -0
- package/pennyfarthing-dist/scripts/lib/file-lock.sh +0 -0
- package/pennyfarthing-dist/scripts/lib/logging.sh +0 -0
- package/pennyfarthing-dist/scripts/lib/retry.sh +0 -0
- package/pennyfarthing-dist/scripts/maintenance/migrate-theme-schema.mjs +0 -0
- package/pennyfarthing-dist/scripts/maintenance/sidecar-health.sh +0 -0
- package/pennyfarthing-dist/scripts/misc/README.md +1 -1
- package/pennyfarthing-dist/scripts/misc/add-short-names.sh +0 -0
- package/pennyfarthing-dist/scripts/misc/add_short_names.py +0 -0
- package/pennyfarthing-dist/scripts/misc/backlog.sh +0 -0
- package/pennyfarthing-dist/scripts/misc/check-status.sh +0 -0
- package/pennyfarthing-dist/scripts/misc/find-related-work.sh +0 -0
- package/pennyfarthing-dist/scripts/misc/generate-skill-docs.sh +0 -0
- package/pennyfarthing-dist/scripts/misc/log-skill-usage.sh +0 -0
- package/pennyfarthing-dist/scripts/misc/migrate-bmad-workflow.sh +0 -0
- package/pennyfarthing-dist/scripts/misc/migrate_bmad_workflow.py +0 -0
- package/pennyfarthing-dist/scripts/misc/repo-scan.sh +0 -0
- package/pennyfarthing-dist/scripts/misc/repo-utils.sh +0 -0
- package/pennyfarthing-dist/scripts/misc/run-ci.sh +0 -0
- package/pennyfarthing-dist/scripts/misc/run-timestamp.sh +0 -0
- package/pennyfarthing-dist/scripts/misc/session-cleanup.sh +0 -0
- package/pennyfarthing-dist/scripts/misc/skill-usage-report.sh +0 -0
- package/pennyfarthing-dist/scripts/misc/statusline.sh +50 -8
- package/pennyfarthing-dist/scripts/misc/uninstall.sh +0 -0
- package/pennyfarthing-dist/scripts/misc/validate-subagent-frontmatter.sh +1 -2
- package/pennyfarthing-dist/scripts/portraits/generate-portraits.sh +0 -0
- package/pennyfarthing-dist/scripts/story/create-story.sh +0 -0
- package/pennyfarthing-dist/scripts/story/size-story.sh +0 -0
- package/pennyfarthing-dist/scripts/story/story-template.sh +0 -0
- package/pennyfarthing-dist/scripts/tests/check.test.sh +0 -0
- package/pennyfarthing-dist/scripts/tests/dev-story-workflow-import.test.sh +0 -0
- package/pennyfarthing-dist/scripts/tests/epics-and-stories-workflow-import.test.sh +0 -0
- package/pennyfarthing-dist/scripts/tests/handoff-phase-update.test.sh +5 -5
- package/pennyfarthing-dist/scripts/tests/implementation-readiness-workflow-import.test.sh +0 -0
- package/pennyfarthing-dist/scripts/tests/migrate-bmad-workflow.test.sh +0 -0
- package/pennyfarthing-dist/scripts/tests/prd-workflow-import.test.sh +0 -0
- package/pennyfarthing-dist/scripts/tests/project-context-workflow-import.test.sh +0 -0
- package/pennyfarthing-dist/scripts/tests/test-character-voice.sh +0 -0
- package/pennyfarthing-dist/scripts/tests/test-drift-detection.sh +3 -79
- package/pennyfarthing-dist/scripts/tests/test-post-merge-hook.sh +0 -0
- package/pennyfarthing-dist/scripts/tests/test-session-checkpoint.sh +0 -0
- package/pennyfarthing-dist/scripts/tests/test-solo-command.sh +0 -0
- package/pennyfarthing-dist/scripts/tests/ux-design-workflow-import.test.sh +0 -0
- package/pennyfarthing-dist/scripts/theme/README.md +1 -1
- package/pennyfarthing-dist/scripts/theme/list-themes.sh +0 -0
- package/pennyfarthing-dist/scripts/validation/validate-agent-schema.sh +0 -1
- package/pennyfarthing-dist/scripts/workflow/README.md +2 -2
- package/pennyfarthing-dist/scripts/workflow/check.py +0 -0
- package/pennyfarthing-dist/scripts/workflow/check.sh +0 -0
- package/pennyfarthing-dist/scripts/workflow/complete-step.py +0 -0
- package/pennyfarthing-dist/scripts/workflow/finish-story.sh +10 -144
- package/pennyfarthing-dist/scripts/workflow/fix-session-phase.sh +0 -0
- package/pennyfarthing-dist/scripts/workflow/get-workflow-type.py +0 -0
- package/pennyfarthing-dist/scripts/workflow/get-workflow-type.sh +0 -0
- package/pennyfarthing-dist/scripts/workflow/list-workflows.sh +0 -0
- package/pennyfarthing-dist/scripts/workflow/phase-owner.sh +0 -0
- package/pennyfarthing-dist/scripts/workflow/resume-workflow.sh +0 -0
- package/pennyfarthing-dist/scripts/workflow/show-workflow.sh +0 -0
- package/pennyfarthing-dist/scripts/workflow/start-workflow.sh +0 -0
- package/pennyfarthing-dist/scripts/workflow/workflow-status.sh +0 -0
- package/pennyfarthing-dist/skills/dev-patterns/SKILL.md +2 -2
- package/pennyfarthing-dist/skills/skill-registry.schema.json +8 -0
- package/pennyfarthing-dist/skills/skill-registry.yaml +21 -17
- package/pennyfarthing-dist/skills/sprint/skill.md +25 -2
- package/pennyfarthing-dist/skills/story/scripts/create-story.sh +0 -0
- package/pennyfarthing-dist/skills/story/scripts/size-story.sh +0 -0
- package/pennyfarthing-dist/skills/story/scripts/story-template.sh +0 -0
- package/pennyfarthing-dist/skills/theme/skill.md +290 -75
- package/pennyfarthing-dist/skills/theme-creation/SKILL.md +23 -166
- package/pennyfarthing-dist/skills/workflow/scripts/list-workflows.sh +0 -0
- package/pennyfarthing-dist/skills/workflow/scripts/resume-workflow.sh +0 -0
- package/pennyfarthing-dist/skills/workflow/scripts/show-workflow.sh +0 -0
- package/pennyfarthing-dist/skills/workflow/scripts/start-workflow.sh +0 -0
- package/pennyfarthing-dist/skills/workflow/scripts/workflow-status.sh +0 -0
- package/pennyfarthing-dist/skills/workflow/skill.md +27 -4
- package/pennyfarthing-dist/templates/agent-scopes.yaml.template +0 -11
- package/pennyfarthing-dist/templates/auto-load-sm.sh.template +14 -0
- package/pennyfarthing-dist/templates/settings.local.json.template +9 -0
- package/pennyfarthing-dist/workflows/2party-tdd.yaml +399 -0
- package/pennyfarthing-dist/workflows/architecture/workflow.yaml +65 -0
- package/pennyfarthing-dist/workflows/bdd-tandem.yaml +70 -0
- package/pennyfarthing-dist/workflows/epics-and-stories/steps/step-05-import-to-future.md +41 -24
- package/pennyfarthing-dist/workflows/tdd-tandem.yaml +61 -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__/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__/schema_validation_hook.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/workflow.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bellmode_hook.py +202 -47
- package/pennyfarthing_scripts/brownfield/__init__.py +6 -6
- package/pennyfarthing_scripts/brownfield/__main__.py +1 -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/brownfield/cli.py +0 -1
- package/pennyfarthing_scripts/brownfield/discover.py +1 -2
- package/pennyfarthing_scripts/cli.py +23 -3
- package/pennyfarthing_scripts/codemarkers/__init__.py +23 -0
- package/pennyfarthing_scripts/codemarkers/__main__.py +6 -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/codemarkers/analyze.py +501 -0
- package/pennyfarthing_scripts/codemarkers/cli.py +179 -0
- package/pennyfarthing_scripts/codemarkers/formatters.py +88 -0
- package/pennyfarthing_scripts/codemarkers/models.py +60 -0
- package/pennyfarthing_scripts/common/__init__.py +8 -9
- 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/common/config.py +1 -1
- package/pennyfarthing_scripts/complexity/__init__.py +15 -0
- package/pennyfarthing_scripts/complexity/__main__.py +6 -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/complexity/analyze.py +207 -0
- package/pennyfarthing_scripts/complexity/cli.py +82 -0
- package/pennyfarthing_scripts/complexity/formatters.py +64 -0
- package/pennyfarthing_scripts/complexity/models.py +32 -0
- package/pennyfarthing_scripts/context.py +14 -15
- package/pennyfarthing_scripts/deadcode/__init__.py +6 -0
- package/pennyfarthing_scripts/deadcode/__main__.py +6 -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/deadcode/analyze.py +322 -0
- package/pennyfarthing_scripts/deadcode/cli.py +163 -0
- package/pennyfarthing_scripts/deadcode/formatters.py +106 -0
- package/pennyfarthing_scripts/deadcode/models.py +54 -0
- package/pennyfarthing_scripts/dependencies/__init__.py +20 -0
- package/pennyfarthing_scripts/dependencies/__main__.py +5 -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/dependencies/analyze.py +155 -0
- package/pennyfarthing_scripts/dependencies/cli.py +76 -0
- package/pennyfarthing_scripts/dependencies/formatters.py +63 -0
- package/pennyfarthing_scripts/dependencies/models.py +39 -0
- package/pennyfarthing_scripts/git/__init__.py +5 -5
- 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/create_branches.py +3 -2
- package/pennyfarthing_scripts/git/status_all.py +1 -1
- package/pennyfarthing_scripts/healthscore/__init__.py +21 -0
- package/pennyfarthing_scripts/healthscore/__main__.py +14 -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/healthscore/analyze.py +591 -0
- package/pennyfarthing_scripts/healthscore/cli.py +80 -0
- package/pennyfarthing_scripts/healthscore/formatters.py +46 -0
- package/pennyfarthing_scripts/healthscore/models.py +43 -0
- package/pennyfarthing_scripts/hooks/cyclist-pretooluse-hook.sh +0 -0
- package/pennyfarthing_scripts/hooks.py +8 -11
- package/pennyfarthing_scripts/hotspots/__init__.py +6 -6
- 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/hotspots/analyze.py +155 -14
- package/pennyfarthing_scripts/hotspots/cli.py +12 -10
- package/pennyfarthing_scripts/hotspots/models.py +0 -1
- package/pennyfarthing_scripts/jira/__init__.py +15 -17
- 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/bidirectional.py +44 -18
- package/pennyfarthing_scripts/jira/claim.py +21 -0
- package/pennyfarthing_scripts/jira/cli.py +6 -3
- package/pennyfarthing_scripts/jira/client.py +32 -4
- package/pennyfarthing_scripts/jira/create.py +45 -1
- package/pennyfarthing_scripts/jira/epic.py +3 -2
- package/pennyfarthing_scripts/jira/reconcile.py +0 -1
- package/pennyfarthing_scripts/jira/story.py +2 -0
- package/pennyfarthing_scripts/jira/sync.py +1 -1
- 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/migration/skill.py +0 -1
- package/pennyfarthing_scripts/migration/step.py +0 -1
- package/pennyfarthing_scripts/migration/validate.py +8 -5
- package/pennyfarthing_scripts/patch_mode.py +2 -2
- package/pennyfarthing_scripts/preflight/__init__.py +1 -1
- 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/preflight/finish.py +0 -1
- package/pennyfarthing_scripts/pretooluse_hook.py +6 -7
- 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__/workflow.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/prime/cli.py +5 -1
- package/pennyfarthing_scripts/prime/loader.py +2 -3
- package/pennyfarthing_scripts/prime/persona.py +2 -1
- package/pennyfarthing_scripts/prime/tiers.py +4 -4
- package/pennyfarthing_scripts/schema_validation_hook.py +2 -3
- package/pennyfarthing_scripts/sprint/__init__.py +10 -12
- package/pennyfarthing_scripts/sprint/__main__.py +2 -2
- 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__/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/archive.py +0 -1
- package/pennyfarthing_scripts/sprint/archive_epic.py +198 -97
- package/pennyfarthing_scripts/sprint/cli.py +62 -46
- package/pennyfarthing_scripts/sprint/epic_add.py +8 -1
- package/pennyfarthing_scripts/sprint/import_epic.py +42 -18
- package/pennyfarthing_scripts/sprint/loader.py +6 -0
- package/pennyfarthing_scripts/sprint/status.py +1 -2
- package/pennyfarthing_scripts/sprint/story_add.py +202 -27
- package/pennyfarthing_scripts/sprint/story_finish.py +209 -0
- package/pennyfarthing_scripts/sprint/story_update.py +11 -3
- package/pennyfarthing_scripts/sprint/validate_cmd.py +0 -1
- package/pennyfarthing_scripts/sprint/validator.py +120 -6
- package/pennyfarthing_scripts/sprint/work.py +28 -7
- package/pennyfarthing_scripts/sprint/yaml_io.py +10 -2
- package/pennyfarthing_scripts/story/__init__.py +14 -16
- 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/story/size.py +0 -1
- package/pennyfarthing_scripts/story/template.py +0 -1
- package/pennyfarthing_scripts/swebench.py +1 -2
- 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_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_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_git_utils.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_sprint_package.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_validate_cmd.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/conftest.py +1 -2
- package/pennyfarthing_scripts/tests/test_brownfield.py +10 -13
- package/pennyfarthing_scripts/tests/test_cli_modules.py +0 -4
- package/pennyfarthing_scripts/tests/test_codemarkers.py +687 -0
- package/pennyfarthing_scripts/tests/test_common.py +9 -4
- package/pennyfarthing_scripts/tests/test_epic_shard_validation.py +699 -0
- package/pennyfarthing_scripts/tests/test_git_utils.py +10 -13
- package/pennyfarthing_scripts/tests/test_healthscore.py +516 -0
- package/pennyfarthing_scripts/tests/test_jira_package.py +0 -3
- package/pennyfarthing_scripts/tests/test_package_structure.py +3 -16
- package/pennyfarthing_scripts/tests/test_patch_mode.py +7 -11
- package/pennyfarthing_scripts/tests/test_prime.py +39 -21
- package/pennyfarthing_scripts/tests/test_sprint_package.py +3 -8
- package/pennyfarthing_scripts/tests/test_sprint_validator.py +53 -5
- package/pennyfarthing_scripts/tests/test_story_add.py +3 -7
- package/pennyfarthing_scripts/tests/test_story_package.py +0 -3
- package/pennyfarthing_scripts/tests/test_story_update.py +5 -10
- package/pennyfarthing_scripts/tests/test_tiers.py +18 -17
- package/pennyfarthing_scripts/tests/test_token_counting.py +19 -13
- package/pennyfarthing_scripts/tests/test_validate_cmd.py +2 -7
- package/pennyfarthing_scripts/tests/test_workflow_check.py +0 -2
- package/pennyfarthing_scripts/tests/test_yaml_io.py +0 -3
- package/pennyfarthing_scripts/theme/__init__.py +5 -0
- package/pennyfarthing_scripts/theme/__main__.py +6 -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/theme/cli.py +287 -0
- package/pennyfarthing_scripts/validate/__init__.py +21 -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/__init__.py +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/agent.py +239 -0
- package/pennyfarthing_scripts/validate/adapters/schema.py +30 -0
- package/pennyfarthing_scripts/validate/adapters/skill_command.py +292 -0
- package/pennyfarthing_scripts/validate/adapters/sprint.py +69 -0
- package/pennyfarthing_scripts/validate/adapters/workflow.py +320 -0
- package/pennyfarthing_scripts/validate/cli.py +141 -0
- package/pennyfarthing_scripts/welcome_hook.py +2 -3
- package/pennyfarthing_scripts/workflow.py +3 -3
- package/scripts/README.md +41 -0
- package/pennyfarthing-dist/agents/workflow-status-check.md +0 -96
- package/pennyfarthing-dist/commands/benchmark-control.md +0 -69
- package/pennyfarthing-dist/commands/benchmark.md +0 -485
- package/pennyfarthing-dist/commands/job-fair.md +0 -102
- package/pennyfarthing-dist/commands/solo.md +0 -447
- package/pennyfarthing-dist/guides/benchmarks.md +0 -62
- package/pennyfarthing-dist/scripts/hooks/__pycache__/question_reflector_check.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/scripts/test/ensure-swebench-data.sh +0 -59
- package/pennyfarthing-dist/scripts/test/ground-truth-judge.py +0 -220
- package/pennyfarthing-dist/scripts/test/swebench-judge.py +0 -374
- package/pennyfarthing-dist/scripts/test/test-cache.sh +0 -165
- package/pennyfarthing-dist/scripts/test/test-setup.sh +0 -337
- package/pennyfarthing-dist/scripts/theme/compute-theme-tiers.sh +0 -13
- package/pennyfarthing-dist/scripts/theme/compute_theme_tiers.py +0 -402
- package/pennyfarthing-dist/scripts/theme/update-theme-tiers.sh +0 -97
- package/pennyfarthing-dist/skills/finalize-run/SKILL.md +0 -261
- package/pennyfarthing-dist/skills/judge/SKILL.md +0 -644
- package/pennyfarthing-dist/skills/persona-benchmark/SKILL.md +0 -187
- package/pennyfarthing-dist/workflows/dev-story/checklist.md +0 -80
- package/pennyfarthing-dist/workflows/dev-story/instructions.xml +0 -410
- package/pennyfarthing-dist/workflows/dev-story/workflow.yaml +0 -50
- package/pennyfarthing-dist/workflows/quick-spec/steps/step-01-understand.md +0 -201
- package/pennyfarthing-dist/workflows/quick-spec/steps/step-02-investigate.md +0 -156
- package/pennyfarthing-dist/workflows/quick-spec/steps/step-03-generate.md +0 -140
- package/pennyfarthing-dist/workflows/quick-spec/steps/step-04-review.md +0 -203
- package/pennyfarthing-dist/workflows/quick-spec/tech-spec-template.md +0 -74
- package/pennyfarthing-dist/workflows/quick-spec/workflow.yaml +0 -27
- package/pennyfarthing_scripts/__pycache__/__init__.cpython-311.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/jira.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/pretooluse_hook.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
|
@@ -16,21 +16,11 @@ Acceptance Criteria:
|
|
|
16
16
|
"""
|
|
17
17
|
|
|
18
18
|
import asyncio
|
|
19
|
-
import tempfile
|
|
20
19
|
from pathlib import Path
|
|
21
|
-
from
|
|
22
|
-
from unittest.mock import AsyncMock, MagicMock, patch
|
|
20
|
+
from unittest.mock import AsyncMock, patch
|
|
23
21
|
|
|
24
22
|
import pytest
|
|
25
23
|
|
|
26
|
-
from pennyfarthing_scripts.git.status_all import (
|
|
27
|
-
RepoStatus,
|
|
28
|
-
format_status_brief,
|
|
29
|
-
format_status_full,
|
|
30
|
-
format_summary,
|
|
31
|
-
get_all_repo_status,
|
|
32
|
-
get_repo_status,
|
|
33
|
-
)
|
|
34
24
|
from pennyfarthing_scripts.git.create_branches import (
|
|
35
25
|
BranchAction,
|
|
36
26
|
BranchResult,
|
|
@@ -40,7 +30,14 @@ from pennyfarthing_scripts.git.create_branches import (
|
|
|
40
30
|
filter_repos,
|
|
41
31
|
format_results,
|
|
42
32
|
)
|
|
43
|
-
|
|
33
|
+
from pennyfarthing_scripts.git.status_all import (
|
|
34
|
+
RepoStatus,
|
|
35
|
+
format_status_brief,
|
|
36
|
+
format_status_full,
|
|
37
|
+
format_summary,
|
|
38
|
+
get_all_repo_status,
|
|
39
|
+
get_repo_status,
|
|
40
|
+
)
|
|
44
41
|
|
|
45
42
|
# =============================================================================
|
|
46
43
|
# Test Fixtures
|
|
@@ -793,8 +790,8 @@ class TestCrossPlatformCompatibility:
|
|
|
793
790
|
def test_no_shell_specific_commands(self) -> None:
|
|
794
791
|
"""Implementation should not use shell-specific commands."""
|
|
795
792
|
# This is more of a code review check, but we can verify the modules exist
|
|
796
|
-
import pennyfarthing_scripts.git.status_all as status_mod
|
|
797
793
|
import pennyfarthing_scripts.git.create_branches as branch_mod
|
|
794
|
+
import pennyfarthing_scripts.git.status_all as status_mod
|
|
798
795
|
|
|
799
796
|
# Modules should exist and be importable
|
|
800
797
|
assert status_mod is not None
|
|
@@ -0,0 +1,516 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tests for the healthscore module.
|
|
3
|
+
|
|
4
|
+
Covers all acceptance criteria for MSSCI-14470:
|
|
5
|
+
AC1: Module structure (cli.py, models.py, analyze.py, formatters.py)
|
|
6
|
+
AC2: Weighted scoring algorithm with 8 configurable dimensions
|
|
7
|
+
AC3: Each dimension 0-100, composite is weighted average 0-100
|
|
8
|
+
AC4: CLI command: pf healthscore analyze [--format table|json|csv] [--path DIR]
|
|
9
|
+
AC5: Result caching within 5-minute window
|
|
10
|
+
AC6: Cache stored in .pennyfarthing/.cache/healthscore/
|
|
11
|
+
AC7: ADR-0008 result pattern
|
|
12
|
+
AC8: Registered in main CLI
|
|
13
|
+
AC9: Tests cover scoring algorithm, caching, and CLI output
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import asyncio
|
|
19
|
+
import json
|
|
20
|
+
from dataclasses import asdict
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
from unittest.mock import patch
|
|
23
|
+
|
|
24
|
+
from click.testing import CliRunner
|
|
25
|
+
|
|
26
|
+
from pennyfarthing_scripts.healthscore.analyze import (
|
|
27
|
+
analyze_healthscore,
|
|
28
|
+
compute_composite_score,
|
|
29
|
+
get_cache_path,
|
|
30
|
+
read_cached_score,
|
|
31
|
+
write_cached_score,
|
|
32
|
+
)
|
|
33
|
+
from pennyfarthing_scripts.healthscore.cli import healthscore
|
|
34
|
+
from pennyfarthing_scripts.healthscore.models import (
|
|
35
|
+
DEFAULT_WEIGHTS,
|
|
36
|
+
DimensionScore,
|
|
37
|
+
HealthscoreResult,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
# ---------------------------------------------------------------------------
|
|
41
|
+
# AC1: Module structure
|
|
42
|
+
# ---------------------------------------------------------------------------
|
|
43
|
+
|
|
44
|
+
class TestModuleStructure:
|
|
45
|
+
"""AC1: New module at pennyfarthing_scripts/healthscore/ with standard files."""
|
|
46
|
+
|
|
47
|
+
def test_package_has_init(self):
|
|
48
|
+
"""Module must be importable as a package."""
|
|
49
|
+
import pennyfarthing_scripts.healthscore
|
|
50
|
+
assert hasattr(pennyfarthing_scripts.healthscore, "HealthscoreResult")
|
|
51
|
+
assert hasattr(pennyfarthing_scripts.healthscore, "DimensionScore")
|
|
52
|
+
assert hasattr(pennyfarthing_scripts.healthscore, "DEFAULT_WEIGHTS")
|
|
53
|
+
assert hasattr(pennyfarthing_scripts.healthscore, "analyze_healthscore")
|
|
54
|
+
|
|
55
|
+
def test_models_module_exists(self):
|
|
56
|
+
"""models.py must define DimensionScore and HealthscoreResult."""
|
|
57
|
+
from pennyfarthing_scripts.healthscore import models
|
|
58
|
+
assert hasattr(models, "DimensionScore")
|
|
59
|
+
assert hasattr(models, "HealthscoreResult")
|
|
60
|
+
|
|
61
|
+
def test_analyze_module_exists(self):
|
|
62
|
+
"""analyze.py must define analyze_healthscore."""
|
|
63
|
+
from pennyfarthing_scripts.healthscore import analyze
|
|
64
|
+
assert hasattr(analyze, "analyze_healthscore")
|
|
65
|
+
|
|
66
|
+
def test_cli_module_exists(self):
|
|
67
|
+
"""cli.py must define the click group."""
|
|
68
|
+
from pennyfarthing_scripts.healthscore import cli
|
|
69
|
+
assert hasattr(cli, "healthscore")
|
|
70
|
+
|
|
71
|
+
def test_formatters_module_exists(self):
|
|
72
|
+
"""formatters.py must define table/json/csv formatters."""
|
|
73
|
+
from pennyfarthing_scripts.healthscore import formatters
|
|
74
|
+
assert hasattr(formatters, "format_table")
|
|
75
|
+
assert hasattr(formatters, "export_json")
|
|
76
|
+
assert hasattr(formatters, "export_csv")
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
# ---------------------------------------------------------------------------
|
|
80
|
+
# AC2: Weighted scoring algorithm with 8 configurable dimensions
|
|
81
|
+
# ---------------------------------------------------------------------------
|
|
82
|
+
|
|
83
|
+
class TestWeightedScoring:
|
|
84
|
+
"""AC2: 8 dimensions with configurable weights."""
|
|
85
|
+
|
|
86
|
+
def test_default_weights_has_8_dimensions(self):
|
|
87
|
+
"""Must define exactly 8 dimensions."""
|
|
88
|
+
assert len(DEFAULT_WEIGHTS) == 8
|
|
89
|
+
|
|
90
|
+
def test_default_weights_sum_to_one(self):
|
|
91
|
+
"""Default weights must sum to 1.0."""
|
|
92
|
+
total = sum(DEFAULT_WEIGHTS.values())
|
|
93
|
+
assert abs(total - 1.0) < 1e-9, f"Weights sum to {total}, expected 1.0"
|
|
94
|
+
|
|
95
|
+
def test_default_weight_keys(self):
|
|
96
|
+
"""Must include all 8 named dimensions."""
|
|
97
|
+
expected = {
|
|
98
|
+
"churn", "todo_density", "complexity", "test_gaps",
|
|
99
|
+
"dead_code", "deprecation_debt", "dependency_freshness",
|
|
100
|
+
"agent_context_efficiency",
|
|
101
|
+
}
|
|
102
|
+
assert set(DEFAULT_WEIGHTS.keys()) == expected
|
|
103
|
+
|
|
104
|
+
def test_default_weight_values(self):
|
|
105
|
+
"""Default weights must match spec."""
|
|
106
|
+
assert DEFAULT_WEIGHTS["churn"] == 0.15
|
|
107
|
+
assert DEFAULT_WEIGHTS["todo_density"] == 0.15
|
|
108
|
+
assert DEFAULT_WEIGHTS["complexity"] == 0.15
|
|
109
|
+
assert DEFAULT_WEIGHTS["test_gaps"] == 0.15
|
|
110
|
+
assert DEFAULT_WEIGHTS["dead_code"] == 0.10
|
|
111
|
+
assert DEFAULT_WEIGHTS["deprecation_debt"] == 0.10
|
|
112
|
+
assert DEFAULT_WEIGHTS["dependency_freshness"] == 0.10
|
|
113
|
+
assert DEFAULT_WEIGHTS["agent_context_efficiency"] == 0.10
|
|
114
|
+
|
|
115
|
+
def test_custom_weights_override_defaults(self):
|
|
116
|
+
"""compute_composite_score must accept custom weights."""
|
|
117
|
+
custom = dict.fromkeys(DEFAULT_WEIGHTS, 1.0 / 8)
|
|
118
|
+
scores = dict.fromkeys(DEFAULT_WEIGHTS, 80.0)
|
|
119
|
+
result = compute_composite_score(scores, custom)
|
|
120
|
+
assert abs(result - 80.0) < 1e-9
|
|
121
|
+
|
|
122
|
+
def test_unequal_custom_weights(self):
|
|
123
|
+
"""Asymmetric weights should shift composite score."""
|
|
124
|
+
weights = dict.fromkeys(DEFAULT_WEIGHTS, 0.0)
|
|
125
|
+
weights["churn"] = 1.0 # All weight on churn
|
|
126
|
+
|
|
127
|
+
scores = dict.fromkeys(DEFAULT_WEIGHTS, 50.0)
|
|
128
|
+
scores["churn"] = 100.0
|
|
129
|
+
|
|
130
|
+
result = compute_composite_score(scores, weights)
|
|
131
|
+
assert abs(result - 100.0) < 1e-9
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
# ---------------------------------------------------------------------------
|
|
135
|
+
# AC3: Each dimension 0-100, composite is weighted average 0-100
|
|
136
|
+
# ---------------------------------------------------------------------------
|
|
137
|
+
|
|
138
|
+
class TestScoreRanges:
|
|
139
|
+
"""AC3: Dimension scores 0-100, composite 0-100."""
|
|
140
|
+
|
|
141
|
+
def test_all_zeros_yields_zero(self):
|
|
142
|
+
"""All dimensions at 0 → composite 0."""
|
|
143
|
+
scores = dict.fromkeys(DEFAULT_WEIGHTS, 0.0)
|
|
144
|
+
result = compute_composite_score(scores, DEFAULT_WEIGHTS)
|
|
145
|
+
assert result == 0.0
|
|
146
|
+
|
|
147
|
+
def test_all_hundreds_yields_hundred(self):
|
|
148
|
+
"""All dimensions at 100 → composite 100."""
|
|
149
|
+
scores = dict.fromkeys(DEFAULT_WEIGHTS, 100.0)
|
|
150
|
+
result = compute_composite_score(scores, DEFAULT_WEIGHTS)
|
|
151
|
+
assert abs(result - 100.0) < 1e-9
|
|
152
|
+
|
|
153
|
+
def test_mixed_scores_weighted_average(self):
|
|
154
|
+
"""Mixed scores should produce correct weighted average."""
|
|
155
|
+
scores = {
|
|
156
|
+
"churn": 80.0,
|
|
157
|
+
"todo_density": 60.0,
|
|
158
|
+
"complexity": 70.0,
|
|
159
|
+
"test_gaps": 50.0,
|
|
160
|
+
"dead_code": 90.0,
|
|
161
|
+
"deprecation_debt": 40.0,
|
|
162
|
+
"dependency_freshness": 85.0,
|
|
163
|
+
"agent_context_efficiency": 75.0,
|
|
164
|
+
}
|
|
165
|
+
expected = sum(scores[k] * DEFAULT_WEIGHTS[k] for k in DEFAULT_WEIGHTS)
|
|
166
|
+
result = compute_composite_score(scores, DEFAULT_WEIGHTS)
|
|
167
|
+
assert abs(result - expected) < 1e-9
|
|
168
|
+
|
|
169
|
+
def test_none_dimensions_excluded_and_renormalized(self):
|
|
170
|
+
"""Unavailable dimensions (None) are excluded; remaining weights renormalize."""
|
|
171
|
+
scores = dict.fromkeys(DEFAULT_WEIGHTS)
|
|
172
|
+
scores["churn"] = 80.0
|
|
173
|
+
scores["complexity"] = 60.0
|
|
174
|
+
# Only churn (0.15) and complexity (0.15) available → renorm to 0.5 each
|
|
175
|
+
expected = (80.0 * 0.5) + (60.0 * 0.5)
|
|
176
|
+
result = compute_composite_score(scores, DEFAULT_WEIGHTS)
|
|
177
|
+
assert abs(result - expected) < 1e-9
|
|
178
|
+
|
|
179
|
+
def test_all_none_dimensions_returns_zero(self):
|
|
180
|
+
"""All dimensions unavailable → composite 0."""
|
|
181
|
+
scores = dict.fromkeys(DEFAULT_WEIGHTS)
|
|
182
|
+
result = compute_composite_score(scores, DEFAULT_WEIGHTS)
|
|
183
|
+
assert result == 0.0
|
|
184
|
+
|
|
185
|
+
def test_composite_score_in_result_object(self):
|
|
186
|
+
"""HealthscoreResult.composite_score should reflect computed value."""
|
|
187
|
+
result = HealthscoreResult(
|
|
188
|
+
success=True,
|
|
189
|
+
composite_score=73.5,
|
|
190
|
+
target_path="/tmp/project",
|
|
191
|
+
dimensions=[
|
|
192
|
+
DimensionScore(name="churn", score=80.0, weight=0.15),
|
|
193
|
+
DimensionScore(name="complexity", score=67.0, weight=0.15),
|
|
194
|
+
],
|
|
195
|
+
)
|
|
196
|
+
assert 0.0 <= result.composite_score <= 100.0
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
# ---------------------------------------------------------------------------
|
|
200
|
+
# AC4: CLI command
|
|
201
|
+
# ---------------------------------------------------------------------------
|
|
202
|
+
|
|
203
|
+
class TestCLI:
|
|
204
|
+
"""AC4: pf healthscore analyze [--format table|json|csv] [--path DIR]."""
|
|
205
|
+
|
|
206
|
+
def test_help_shows_analyze_command(self):
|
|
207
|
+
"""CLI group must expose the analyze subcommand."""
|
|
208
|
+
runner = CliRunner()
|
|
209
|
+
result = runner.invoke(healthscore, ["--help"])
|
|
210
|
+
assert result.exit_code == 0
|
|
211
|
+
assert "analyze" in result.output
|
|
212
|
+
|
|
213
|
+
def test_analyze_help_shows_options(self):
|
|
214
|
+
"""analyze command must show --format, --path, --output, --no-cache."""
|
|
215
|
+
runner = CliRunner()
|
|
216
|
+
result = runner.invoke(healthscore, ["analyze", "--help"])
|
|
217
|
+
assert result.exit_code == 0
|
|
218
|
+
assert "--format" in result.output
|
|
219
|
+
assert "--path" in result.output
|
|
220
|
+
assert "--output" in result.output
|
|
221
|
+
assert "--no-cache" in result.output
|
|
222
|
+
|
|
223
|
+
def test_analyze_format_choices(self):
|
|
224
|
+
"""--format must accept table, json, csv."""
|
|
225
|
+
runner = CliRunner()
|
|
226
|
+
result = runner.invoke(healthscore, ["analyze", "--help"])
|
|
227
|
+
assert "table" in result.output
|
|
228
|
+
assert "json" in result.output
|
|
229
|
+
assert "csv" in result.output
|
|
230
|
+
|
|
231
|
+
def test_analyze_json_output_is_valid_json(self):
|
|
232
|
+
"""--format json must produce parseable JSON."""
|
|
233
|
+
mock_result = HealthscoreResult(
|
|
234
|
+
success=True,
|
|
235
|
+
composite_score=72.5,
|
|
236
|
+
target_path="/tmp/project",
|
|
237
|
+
dimensions=[
|
|
238
|
+
DimensionScore(name="churn", score=80.0, weight=0.15),
|
|
239
|
+
],
|
|
240
|
+
)
|
|
241
|
+
with patch(
|
|
242
|
+
"pennyfarthing_scripts.healthscore.cli._run_analysis",
|
|
243
|
+
return_value=mock_result,
|
|
244
|
+
):
|
|
245
|
+
runner = CliRunner()
|
|
246
|
+
result = runner.invoke(healthscore, ["analyze", "--format", "json"])
|
|
247
|
+
assert result.exit_code == 0
|
|
248
|
+
data = json.loads(result.output)
|
|
249
|
+
assert data["success"] is True
|
|
250
|
+
assert data["composite_score"] == 72.5
|
|
251
|
+
|
|
252
|
+
def test_analyze_table_output_has_score_header(self):
|
|
253
|
+
"""Table output must include score and dimension labels."""
|
|
254
|
+
mock_result = HealthscoreResult(
|
|
255
|
+
success=True,
|
|
256
|
+
composite_score=72.5,
|
|
257
|
+
target_path="/tmp/project",
|
|
258
|
+
dimensions=[
|
|
259
|
+
DimensionScore(name="churn", score=80.0, weight=0.15),
|
|
260
|
+
DimensionScore(name="complexity", score=65.0, weight=0.15),
|
|
261
|
+
],
|
|
262
|
+
)
|
|
263
|
+
with patch(
|
|
264
|
+
"pennyfarthing_scripts.healthscore.cli._run_analysis",
|
|
265
|
+
return_value=mock_result,
|
|
266
|
+
):
|
|
267
|
+
runner = CliRunner()
|
|
268
|
+
result = runner.invoke(healthscore, ["analyze", "--format", "table"])
|
|
269
|
+
assert result.exit_code == 0
|
|
270
|
+
assert "churn" in result.output.lower() or "Churn" in result.output
|
|
271
|
+
|
|
272
|
+
def test_analyze_csv_output_has_header_row(self):
|
|
273
|
+
"""CSV output must include header row with dimension names."""
|
|
274
|
+
mock_result = HealthscoreResult(
|
|
275
|
+
success=True,
|
|
276
|
+
composite_score=72.5,
|
|
277
|
+
target_path="/tmp/project",
|
|
278
|
+
dimensions=[
|
|
279
|
+
DimensionScore(name="churn", score=80.0, weight=0.15),
|
|
280
|
+
],
|
|
281
|
+
)
|
|
282
|
+
with patch(
|
|
283
|
+
"pennyfarthing_scripts.healthscore.cli._run_analysis",
|
|
284
|
+
return_value=mock_result,
|
|
285
|
+
):
|
|
286
|
+
runner = CliRunner()
|
|
287
|
+
result = runner.invoke(healthscore, ["analyze", "--format", "csv"])
|
|
288
|
+
assert result.exit_code == 0
|
|
289
|
+
lines = result.output.strip().split("\n")
|
|
290
|
+
assert len(lines) >= 2 # header + at least one data row
|
|
291
|
+
|
|
292
|
+
def test_analyze_output_to_file(self, tmp_path):
|
|
293
|
+
"""--output must write result to file."""
|
|
294
|
+
mock_result = HealthscoreResult(
|
|
295
|
+
success=True,
|
|
296
|
+
composite_score=72.5,
|
|
297
|
+
target_path="/tmp/project",
|
|
298
|
+
dimensions=[],
|
|
299
|
+
)
|
|
300
|
+
output_file = tmp_path / "result.json"
|
|
301
|
+
with patch(
|
|
302
|
+
"pennyfarthing_scripts.healthscore.cli._run_analysis",
|
|
303
|
+
return_value=mock_result,
|
|
304
|
+
):
|
|
305
|
+
runner = CliRunner()
|
|
306
|
+
result = runner.invoke(healthscore, [
|
|
307
|
+
"analyze", "--format", "json", "--output", str(output_file)
|
|
308
|
+
])
|
|
309
|
+
assert result.exit_code == 0
|
|
310
|
+
assert output_file.exists()
|
|
311
|
+
data = json.loads(output_file.read_text())
|
|
312
|
+
assert data["success"] is True
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
# ---------------------------------------------------------------------------
|
|
316
|
+
# AC5: Result caching within 5-minute window
|
|
317
|
+
# ---------------------------------------------------------------------------
|
|
318
|
+
|
|
319
|
+
class TestCaching:
|
|
320
|
+
"""AC5: Component scores cached, reused within 5-minute window."""
|
|
321
|
+
|
|
322
|
+
def test_write_then_read_cached_score(self, tmp_path):
|
|
323
|
+
"""Written score must be readable back."""
|
|
324
|
+
write_cached_score(tmp_path, "churn", 85.0)
|
|
325
|
+
result = read_cached_score(tmp_path, "churn", ttl=300)
|
|
326
|
+
assert result == 85.0
|
|
327
|
+
|
|
328
|
+
def test_cache_miss_returns_none(self, tmp_path):
|
|
329
|
+
"""Reading a non-existent cache entry returns None."""
|
|
330
|
+
result = read_cached_score(tmp_path, "nonexistent", ttl=300)
|
|
331
|
+
assert result is None
|
|
332
|
+
|
|
333
|
+
def test_expired_cache_returns_none(self, tmp_path):
|
|
334
|
+
"""Cache entry older than TTL returns None."""
|
|
335
|
+
write_cached_score(tmp_path, "churn", 85.0)
|
|
336
|
+
# Read with 0 TTL → always expired
|
|
337
|
+
result = read_cached_score(tmp_path, "churn", ttl=0)
|
|
338
|
+
assert result is None
|
|
339
|
+
|
|
340
|
+
def test_cache_ttl_boundary(self, tmp_path):
|
|
341
|
+
"""Cache at exactly TTL boundary should still be valid."""
|
|
342
|
+
write_cached_score(tmp_path, "churn", 85.0)
|
|
343
|
+
# Read within generous TTL
|
|
344
|
+
result = read_cached_score(tmp_path, "churn", ttl=300)
|
|
345
|
+
assert result == 85.0
|
|
346
|
+
|
|
347
|
+
def test_multiple_dimensions_cached_independently(self, tmp_path):
|
|
348
|
+
"""Each dimension has its own cache entry."""
|
|
349
|
+
write_cached_score(tmp_path, "churn", 85.0)
|
|
350
|
+
write_cached_score(tmp_path, "complexity", 60.0)
|
|
351
|
+
assert read_cached_score(tmp_path, "churn", ttl=300) == 85.0
|
|
352
|
+
assert read_cached_score(tmp_path, "complexity", ttl=300) == 60.0
|
|
353
|
+
|
|
354
|
+
def test_overwrite_cached_score(self, tmp_path):
|
|
355
|
+
"""Writing a new score overwrites the previous value."""
|
|
356
|
+
write_cached_score(tmp_path, "churn", 85.0)
|
|
357
|
+
write_cached_score(tmp_path, "churn", 42.0)
|
|
358
|
+
result = read_cached_score(tmp_path, "churn", ttl=300)
|
|
359
|
+
assert result == 42.0
|
|
360
|
+
|
|
361
|
+
def test_no_cache_flag_bypasses_cache(self):
|
|
362
|
+
"""analyze_healthscore with cache_ttl=0 must not use cached results."""
|
|
363
|
+
# This tests that the analyze function respects cache_ttl=0
|
|
364
|
+
# (will fail until implementation — that's the point)
|
|
365
|
+
with patch(
|
|
366
|
+
"pennyfarthing_scripts.healthscore.analyze.read_cached_score",
|
|
367
|
+
return_value=99.0,
|
|
368
|
+
) as mock_read:
|
|
369
|
+
asyncio.run(
|
|
370
|
+
analyze_healthscore(Path("/tmp/project"), cache_ttl=0)
|
|
371
|
+
)
|
|
372
|
+
# With ttl=0, cached values should not be used
|
|
373
|
+
mock_read.assert_not_called()
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
# ---------------------------------------------------------------------------
|
|
377
|
+
# AC6: Cache stored in .pennyfarthing/.cache/healthscore/
|
|
378
|
+
# ---------------------------------------------------------------------------
|
|
379
|
+
|
|
380
|
+
class TestCacheLocation:
|
|
381
|
+
"""AC6: Cache files stored in .pennyfarthing/.cache/healthscore/."""
|
|
382
|
+
|
|
383
|
+
def test_cache_path_under_pennyfarthing(self, tmp_path):
|
|
384
|
+
"""get_cache_path must return a path under .pennyfarthing/.cache/healthscore/."""
|
|
385
|
+
cache_dir = get_cache_path(tmp_path)
|
|
386
|
+
path_str = str(cache_dir)
|
|
387
|
+
assert ".pennyfarthing" in path_str
|
|
388
|
+
assert ".cache" in path_str
|
|
389
|
+
assert "healthscore" in path_str
|
|
390
|
+
|
|
391
|
+
def test_cache_path_includes_target_directory(self, tmp_path):
|
|
392
|
+
"""Cache path should be specific to the target directory."""
|
|
393
|
+
path_a = get_cache_path(tmp_path / "project-a")
|
|
394
|
+
path_b = get_cache_path(tmp_path / "project-b")
|
|
395
|
+
# Different projects should get different cache dirs (or at least different keys)
|
|
396
|
+
assert path_a != path_b
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
# ---------------------------------------------------------------------------
|
|
400
|
+
# AC7: ADR-0008 result pattern
|
|
401
|
+
# ---------------------------------------------------------------------------
|
|
402
|
+
|
|
403
|
+
class TestADR0008Pattern:
|
|
404
|
+
"""AC7: HealthscoreResult follows ADR-0008 pattern."""
|
|
405
|
+
|
|
406
|
+
def test_result_has_success_field(self):
|
|
407
|
+
"""Result must have a success boolean."""
|
|
408
|
+
result = HealthscoreResult(success=True)
|
|
409
|
+
assert result.success is True
|
|
410
|
+
|
|
411
|
+
def test_result_has_error_field(self):
|
|
412
|
+
"""Result must have an optional error field."""
|
|
413
|
+
result = HealthscoreResult(success=False, error="something broke")
|
|
414
|
+
assert result.error == "something broke"
|
|
415
|
+
|
|
416
|
+
def test_result_serializable_with_asdict(self):
|
|
417
|
+
"""Result must be serializable via dataclasses.asdict."""
|
|
418
|
+
result = HealthscoreResult(
|
|
419
|
+
success=True,
|
|
420
|
+
composite_score=72.5,
|
|
421
|
+
target_path="/tmp/project",
|
|
422
|
+
dimensions=[
|
|
423
|
+
DimensionScore(name="churn", score=80.0, weight=0.15),
|
|
424
|
+
],
|
|
425
|
+
)
|
|
426
|
+
d = asdict(result)
|
|
427
|
+
assert d["success"] is True
|
|
428
|
+
assert d["composite_score"] == 72.5
|
|
429
|
+
assert len(d["dimensions"]) == 1
|
|
430
|
+
|
|
431
|
+
def test_result_json_roundtrip(self):
|
|
432
|
+
"""Result must survive JSON serialization."""
|
|
433
|
+
result = HealthscoreResult(
|
|
434
|
+
success=True,
|
|
435
|
+
composite_score=72.5,
|
|
436
|
+
target_path="/tmp/project",
|
|
437
|
+
dimensions=[
|
|
438
|
+
DimensionScore(name="churn", score=80.0, weight=0.15),
|
|
439
|
+
],
|
|
440
|
+
)
|
|
441
|
+
d = asdict(result)
|
|
442
|
+
text = json.dumps(d, default=str)
|
|
443
|
+
loaded = json.loads(text)
|
|
444
|
+
assert loaded["success"] is True
|
|
445
|
+
assert loaded["composite_score"] == 72.5
|
|
446
|
+
|
|
447
|
+
def test_dimension_score_has_name_score_weight(self):
|
|
448
|
+
"""DimensionScore must have name, score, weight, error fields."""
|
|
449
|
+
ds = DimensionScore(name="churn", score=80.0, weight=0.15)
|
|
450
|
+
assert ds.name == "churn"
|
|
451
|
+
assert ds.score == 80.0
|
|
452
|
+
assert ds.weight == 0.15
|
|
453
|
+
assert ds.error is None
|
|
454
|
+
|
|
455
|
+
def test_dimension_score_none_means_unavailable(self):
|
|
456
|
+
"""DimensionScore with score=None means dimension unavailable."""
|
|
457
|
+
ds = DimensionScore(name="churn", score=None, weight=0.15, error="no data")
|
|
458
|
+
assert ds.score is None
|
|
459
|
+
assert ds.error == "no data"
|
|
460
|
+
|
|
461
|
+
def test_error_result(self):
|
|
462
|
+
"""Failed result has success=False and error message."""
|
|
463
|
+
result = HealthscoreResult(success=False, error="target not found")
|
|
464
|
+
assert result.success is False
|
|
465
|
+
assert result.error == "target not found"
|
|
466
|
+
assert result.composite_score == 0.0
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
# ---------------------------------------------------------------------------
|
|
470
|
+
# AC8: Registered in main CLI
|
|
471
|
+
# ---------------------------------------------------------------------------
|
|
472
|
+
|
|
473
|
+
class TestMainCLIRegistration:
|
|
474
|
+
"""AC8: healthscore command registered in pennyfarthing_scripts/cli.py."""
|
|
475
|
+
|
|
476
|
+
def test_healthscore_registered_in_main_cli(self):
|
|
477
|
+
"""Main CLI must have a 'healthscore' command group."""
|
|
478
|
+
from pennyfarthing_scripts.cli import cli
|
|
479
|
+
command_names = list(cli.commands)
|
|
480
|
+
assert "healthscore" in command_names
|
|
481
|
+
|
|
482
|
+
|
|
483
|
+
# ---------------------------------------------------------------------------
|
|
484
|
+
# AC9: Full integration — analyze_healthscore returns real result
|
|
485
|
+
# ---------------------------------------------------------------------------
|
|
486
|
+
|
|
487
|
+
class TestAnalyzeIntegration:
|
|
488
|
+
"""AC9: End-to-end analysis returns HealthscoreResult."""
|
|
489
|
+
|
|
490
|
+
def test_analyze_returns_healthscore_result(self):
|
|
491
|
+
"""analyze_healthscore must return a HealthscoreResult."""
|
|
492
|
+
result = asyncio.run(analyze_healthscore(Path("/tmp/nonexistent")))
|
|
493
|
+
assert isinstance(result, HealthscoreResult)
|
|
494
|
+
|
|
495
|
+
def test_analyze_result_has_dimensions(self):
|
|
496
|
+
"""Result must include dimension scores list."""
|
|
497
|
+
result = asyncio.run(analyze_healthscore(Path("/tmp/nonexistent")))
|
|
498
|
+
assert isinstance(result.dimensions, list)
|
|
499
|
+
|
|
500
|
+
def test_analyze_with_custom_weights(self):
|
|
501
|
+
"""Custom weights must be accepted and applied."""
|
|
502
|
+
custom = dict.fromkeys(DEFAULT_WEIGHTS, 1.0 / 8)
|
|
503
|
+
result = asyncio.run(analyze_healthscore(Path("/tmp/nonexistent"), weights=custom))
|
|
504
|
+
assert isinstance(result, HealthscoreResult)
|
|
505
|
+
|
|
506
|
+
def test_analyze_result_cached_flag(self):
|
|
507
|
+
"""Result must indicate whether values came from cache."""
|
|
508
|
+
result = asyncio.run(analyze_healthscore(Path("/tmp/nonexistent")))
|
|
509
|
+
assert isinstance(result.cached, bool)
|
|
510
|
+
|
|
511
|
+
def test_analyze_graceful_on_missing_path(self):
|
|
512
|
+
"""Missing target path should return error result, not raise."""
|
|
513
|
+
result = asyncio.run(analyze_healthscore(Path("/tmp/definitely-not-a-real-path-xyz123")))
|
|
514
|
+
assert isinstance(result, HealthscoreResult)
|
|
515
|
+
# Should either succeed with degraded scores or fail gracefully
|
|
516
|
+
assert isinstance(result.success, bool)
|
|
@@ -10,12 +10,7 @@ These tests verify:
|
|
|
10
10
|
5. Backwards compatibility for existing imports
|
|
11
11
|
"""
|
|
12
12
|
|
|
13
|
-
import importlib
|
|
14
13
|
import sys
|
|
15
|
-
from pathlib import Path
|
|
16
|
-
from typing import Any
|
|
17
|
-
|
|
18
|
-
import pytest
|
|
19
14
|
|
|
20
15
|
|
|
21
16
|
class TestCommonPackage:
|
|
@@ -126,8 +121,6 @@ class TestJiraPackage:
|
|
|
126
121
|
from pennyfarthing_scripts.jira import (
|
|
127
122
|
JiraClient,
|
|
128
123
|
extract_jira_key,
|
|
129
|
-
map_status_to_jira,
|
|
130
|
-
map_jira_to_status,
|
|
131
124
|
)
|
|
132
125
|
|
|
133
126
|
assert JiraClient is not None
|
|
@@ -136,9 +129,7 @@ class TestJiraPackage:
|
|
|
136
129
|
def test_jira_backwards_compatibility(self) -> None:
|
|
137
130
|
"""Old-style imports should still work."""
|
|
138
131
|
# These imports should work for backwards compatibility
|
|
139
|
-
from pennyfarthing_scripts.jira import JiraClient
|
|
140
|
-
from pennyfarthing_scripts.jira import get_issue
|
|
141
|
-
from pennyfarthing_scripts.jira import STATUS_TO_JIRA
|
|
132
|
+
from pennyfarthing_scripts.jira import STATUS_TO_JIRA, JiraClient, get_issue
|
|
142
133
|
|
|
143
134
|
assert JiraClient is not None
|
|
144
135
|
assert callable(get_issue)
|
|
@@ -188,10 +179,8 @@ class TestSprintPackage:
|
|
|
188
179
|
def test_sprint_package_reexports(self) -> None:
|
|
189
180
|
"""sprint/__init__.py should re-export commonly used items."""
|
|
190
181
|
from pennyfarthing_scripts.sprint import (
|
|
191
|
-
load_sprint,
|
|
192
182
|
find_epic,
|
|
193
|
-
|
|
194
|
-
get_all_stories,
|
|
183
|
+
load_sprint,
|
|
195
184
|
)
|
|
196
185
|
|
|
197
186
|
assert callable(load_sprint)
|
|
@@ -199,9 +188,7 @@ class TestSprintPackage:
|
|
|
199
188
|
|
|
200
189
|
def test_sprint_backwards_compatibility(self) -> None:
|
|
201
190
|
"""Old-style imports should still work."""
|
|
202
|
-
from pennyfarthing_scripts.sprint import load_sprint
|
|
203
|
-
from pennyfarthing_scripts.sprint import find_epic
|
|
204
|
-
from pennyfarthing_scripts.sprint import load_current_sprint
|
|
191
|
+
from pennyfarthing_scripts.sprint import find_epic, load_current_sprint, load_sprint
|
|
205
192
|
|
|
206
193
|
assert callable(load_sprint)
|
|
207
194
|
assert callable(find_epic)
|
|
@@ -18,13 +18,9 @@ Acceptance Criteria:
|
|
|
18
18
|
These tests should FAIL until patch_mode.py is implemented.
|
|
19
19
|
"""
|
|
20
20
|
|
|
21
|
-
import json
|
|
22
|
-
import os
|
|
23
|
-
import subprocess
|
|
24
|
-
import sys
|
|
25
21
|
from pathlib import Path
|
|
26
|
-
from typing import Any
|
|
27
|
-
from unittest.mock import MagicMock, patch
|
|
22
|
+
from typing import Any
|
|
23
|
+
from unittest.mock import MagicMock, patch
|
|
28
24
|
|
|
29
25
|
import pytest
|
|
30
26
|
import yaml
|
|
@@ -32,17 +28,17 @@ import yaml
|
|
|
32
28
|
# Import will fail until module exists - this is intentional for RED state
|
|
33
29
|
try:
|
|
34
30
|
from pennyfarthing_scripts.patch_mode import (
|
|
35
|
-
PatchState,
|
|
36
31
|
PatchStack,
|
|
32
|
+
PatchState,
|
|
33
|
+
create_patch_branch,
|
|
37
34
|
enter_patch_mode,
|
|
38
35
|
exit_patch_mode,
|
|
36
|
+
generate_patch_commit_message,
|
|
39
37
|
get_patch_stack,
|
|
40
|
-
|
|
38
|
+
is_in_patch_mode,
|
|
39
|
+
log_patch_to_session,
|
|
41
40
|
merge_patch_branch,
|
|
42
41
|
restore_workflow_state,
|
|
43
|
-
log_patch_to_session,
|
|
44
|
-
generate_patch_commit_message,
|
|
45
|
-
is_in_patch_mode,
|
|
46
42
|
)
|
|
47
43
|
IMPORT_SUCCESS = True
|
|
48
44
|
except ImportError:
|