@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
|
@@ -0,0 +1,687 @@
|
|
|
1
|
+
"""Tests for codemarkers module.
|
|
2
|
+
|
|
3
|
+
Story 80-1: Python codemarkers module — grep + git blame.
|
|
4
|
+
MSSCI-14454
|
|
5
|
+
|
|
6
|
+
TDD RED phase — all tests written before implementation.
|
|
7
|
+
Tests cover: models, analyze engine, CLI, formatters.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import json
|
|
13
|
+
from dataclasses import asdict
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from unittest.mock import AsyncMock, patch
|
|
16
|
+
|
|
17
|
+
import pytest
|
|
18
|
+
|
|
19
|
+
# ---------------------------------------------------------------------------
|
|
20
|
+
# Models
|
|
21
|
+
# ---------------------------------------------------------------------------
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class TestCodeMarkerModel:
|
|
25
|
+
"""CodeMarker dataclass fields and defaults."""
|
|
26
|
+
|
|
27
|
+
def test_required_fields(self) -> None:
|
|
28
|
+
"""CodeMarker requires path, line, marker_type, text."""
|
|
29
|
+
from pennyfarthing_scripts.codemarkers.models import CodeMarker
|
|
30
|
+
|
|
31
|
+
m = CodeMarker(path="src/app.ts", line=42, marker_type="TODO", text="TODO: refactor")
|
|
32
|
+
assert m.path == "src/app.ts"
|
|
33
|
+
assert m.line == 42
|
|
34
|
+
assert m.marker_type == "TODO"
|
|
35
|
+
assert m.text == "TODO: refactor"
|
|
36
|
+
|
|
37
|
+
def test_default_blame_fields(self) -> None:
|
|
38
|
+
"""Blame fields default to empty/zero."""
|
|
39
|
+
from pennyfarthing_scripts.codemarkers.models import CodeMarker
|
|
40
|
+
|
|
41
|
+
m = CodeMarker(path="a.py", line=1, marker_type="FIXME", text="FIXME: broken")
|
|
42
|
+
assert m.author == ""
|
|
43
|
+
assert m.date == ""
|
|
44
|
+
assert m.age_days == 0.0
|
|
45
|
+
assert m.is_stale is False
|
|
46
|
+
|
|
47
|
+
def test_stale_marker(self) -> None:
|
|
48
|
+
"""is_stale can be set to True."""
|
|
49
|
+
from pennyfarthing_scripts.codemarkers.models import CodeMarker
|
|
50
|
+
|
|
51
|
+
m = CodeMarker(
|
|
52
|
+
path="old.py", line=10, marker_type="TODO", text="TODO: old",
|
|
53
|
+
author="dev", date="2025-01-01T00:00:00", age_days=120.0, is_stale=True,
|
|
54
|
+
)
|
|
55
|
+
assert m.is_stale is True
|
|
56
|
+
assert m.age_days == 120.0
|
|
57
|
+
|
|
58
|
+
def test_serializable(self) -> None:
|
|
59
|
+
"""CodeMarker should be serializable via dataclasses.asdict."""
|
|
60
|
+
from pennyfarthing_scripts.codemarkers.models import CodeMarker
|
|
61
|
+
|
|
62
|
+
m = CodeMarker(path="x.py", line=5, marker_type="HACK", text="HACK: workaround")
|
|
63
|
+
d = asdict(m)
|
|
64
|
+
assert d["path"] == "x.py"
|
|
65
|
+
assert d["marker_type"] == "HACK"
|
|
66
|
+
assert isinstance(d, dict)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class TestMarkerSummaryModel:
|
|
70
|
+
"""MarkerSummary aggregates counts by type."""
|
|
71
|
+
|
|
72
|
+
def test_defaults(self) -> None:
|
|
73
|
+
"""MarkerSummary fields default to zero/empty."""
|
|
74
|
+
from pennyfarthing_scripts.codemarkers.models import MarkerSummary
|
|
75
|
+
|
|
76
|
+
s = MarkerSummary()
|
|
77
|
+
assert s.total_markers == 0
|
|
78
|
+
assert s.stale_markers == 0
|
|
79
|
+
assert s.by_type == {}
|
|
80
|
+
|
|
81
|
+
def test_populated(self) -> None:
|
|
82
|
+
"""MarkerSummary with values."""
|
|
83
|
+
from pennyfarthing_scripts.codemarkers.models import MarkerSummary
|
|
84
|
+
|
|
85
|
+
s = MarkerSummary(
|
|
86
|
+
total_markers=10, stale_markers=3,
|
|
87
|
+
by_type={"TODO": 6, "FIXME": 4},
|
|
88
|
+
)
|
|
89
|
+
assert s.total_markers == 10
|
|
90
|
+
assert s.by_type["FIXME"] == 4
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class TestCodeMarkersResultModel:
|
|
94
|
+
"""CodeMarkersResult follows ADR-0008 pattern."""
|
|
95
|
+
|
|
96
|
+
def test_success_result(self) -> None:
|
|
97
|
+
"""Successful result has success=True and markers list."""
|
|
98
|
+
from pennyfarthing_scripts.codemarkers.models import CodeMarker, CodeMarkersResult
|
|
99
|
+
|
|
100
|
+
r = CodeMarkersResult(
|
|
101
|
+
success=True, repo_name="test", repo_path="/tmp/test",
|
|
102
|
+
stale_threshold_days=90,
|
|
103
|
+
markers=[CodeMarker(path="a.py", line=1, marker_type="TODO", text="TODO: x")],
|
|
104
|
+
)
|
|
105
|
+
assert r.success is True
|
|
106
|
+
assert len(r.markers) == 1
|
|
107
|
+
assert r.error is None
|
|
108
|
+
|
|
109
|
+
def test_error_result(self) -> None:
|
|
110
|
+
"""Error result has success=False and error message."""
|
|
111
|
+
from pennyfarthing_scripts.codemarkers.models import CodeMarkersResult
|
|
112
|
+
|
|
113
|
+
r = CodeMarkersResult(
|
|
114
|
+
success=False, repo_name="bad", repo_path="/nonexistent",
|
|
115
|
+
stale_threshold_days=90, error="Path not found",
|
|
116
|
+
)
|
|
117
|
+
assert r.success is False
|
|
118
|
+
assert "not found" in r.error
|
|
119
|
+
|
|
120
|
+
def test_json_serializable(self) -> None:
|
|
121
|
+
"""Full result serializes to JSON via asdict."""
|
|
122
|
+
from pennyfarthing_scripts.codemarkers.models import (
|
|
123
|
+
CodeMarker,
|
|
124
|
+
CodeMarkersResult,
|
|
125
|
+
MarkerSummary,
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
r = CodeMarkersResult(
|
|
129
|
+
success=True, repo_name="repo", repo_path="/tmp",
|
|
130
|
+
stale_threshold_days=90,
|
|
131
|
+
markers=[CodeMarker(path="b.py", line=2, marker_type="XXX", text="XXX: bad")],
|
|
132
|
+
summary=MarkerSummary(total_markers=1, by_type={"XXX": 1}),
|
|
133
|
+
)
|
|
134
|
+
text = json.dumps(asdict(r), default=str)
|
|
135
|
+
parsed = json.loads(text)
|
|
136
|
+
assert parsed["success"] is True
|
|
137
|
+
assert parsed["markers"][0]["marker_type"] == "XXX"
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
# ---------------------------------------------------------------------------
|
|
141
|
+
# Analyze — grep and blame
|
|
142
|
+
# ---------------------------------------------------------------------------
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
class TestGrepMarkers:
|
|
146
|
+
"""_grep_markers finds TODO/FIXME/HACK/XXX in source files."""
|
|
147
|
+
|
|
148
|
+
def test_finds_todo(self, tmp_path: Path) -> None:
|
|
149
|
+
"""Detects TODO comments."""
|
|
150
|
+
from pennyfarthing_scripts.codemarkers.analyze import _grep_markers
|
|
151
|
+
|
|
152
|
+
f = tmp_path / "app.py"
|
|
153
|
+
f.write_text("x = 1\n# TODO: fix this\ny = 2\n")
|
|
154
|
+
markers = _grep_markers(tmp_path, excludes=[])
|
|
155
|
+
assert len(markers) == 1
|
|
156
|
+
assert markers[0]["marker_type"] == "TODO"
|
|
157
|
+
assert markers[0]["line"] == 2
|
|
158
|
+
|
|
159
|
+
def test_finds_fixme(self, tmp_path: Path) -> None:
|
|
160
|
+
"""Detects FIXME comments."""
|
|
161
|
+
from pennyfarthing_scripts.codemarkers.analyze import _grep_markers
|
|
162
|
+
|
|
163
|
+
f = tmp_path / "bug.ts"
|
|
164
|
+
f.write_text("// FIXME: null check needed\n")
|
|
165
|
+
markers = _grep_markers(tmp_path, excludes=[])
|
|
166
|
+
assert len(markers) == 1
|
|
167
|
+
assert markers[0]["marker_type"] == "FIXME"
|
|
168
|
+
|
|
169
|
+
def test_finds_hack(self, tmp_path: Path) -> None:
|
|
170
|
+
"""Detects HACK comments."""
|
|
171
|
+
from pennyfarthing_scripts.codemarkers.analyze import _grep_markers
|
|
172
|
+
|
|
173
|
+
f = tmp_path / "util.js"
|
|
174
|
+
f.write_text("/* HACK: temporary workaround */\n")
|
|
175
|
+
markers = _grep_markers(tmp_path, excludes=[])
|
|
176
|
+
assert len(markers) == 1
|
|
177
|
+
assert markers[0]["marker_type"] == "HACK"
|
|
178
|
+
|
|
179
|
+
def test_finds_xxx(self, tmp_path: Path) -> None:
|
|
180
|
+
"""Detects XXX comments."""
|
|
181
|
+
from pennyfarthing_scripts.codemarkers.analyze import _grep_markers
|
|
182
|
+
|
|
183
|
+
f = tmp_path / "danger.py"
|
|
184
|
+
f.write_text("# XXX: this is terrible\n")
|
|
185
|
+
markers = _grep_markers(tmp_path, excludes=[])
|
|
186
|
+
assert len(markers) == 1
|
|
187
|
+
assert markers[0]["marker_type"] == "XXX"
|
|
188
|
+
|
|
189
|
+
def test_multiple_markers_same_file(self, tmp_path: Path) -> None:
|
|
190
|
+
"""Finds multiple markers in a single file."""
|
|
191
|
+
from pennyfarthing_scripts.codemarkers.analyze import _grep_markers
|
|
192
|
+
|
|
193
|
+
f = tmp_path / "multi.py"
|
|
194
|
+
f.write_text("# TODO: first\nx = 1\n# FIXME: second\n# HACK: third\n")
|
|
195
|
+
markers = _grep_markers(tmp_path, excludes=[])
|
|
196
|
+
assert len(markers) == 3
|
|
197
|
+
types = {m["marker_type"] for m in markers}
|
|
198
|
+
assert types == {"TODO", "FIXME", "HACK"}
|
|
199
|
+
|
|
200
|
+
def test_excludes_node_modules(self, tmp_path: Path) -> None:
|
|
201
|
+
"""Files matching exclude patterns are skipped."""
|
|
202
|
+
from pennyfarthing_scripts.codemarkers.analyze import _grep_markers
|
|
203
|
+
|
|
204
|
+
nm = tmp_path / "node_modules" / "pkg"
|
|
205
|
+
nm.mkdir(parents=True)
|
|
206
|
+
(nm / "index.js").write_text("// TODO: vendor code\n")
|
|
207
|
+
(tmp_path / "src.py").write_text("# TODO: real code\n")
|
|
208
|
+
|
|
209
|
+
markers = _grep_markers(tmp_path, excludes=["node_modules/*"])
|
|
210
|
+
paths = [m["path"] for m in markers]
|
|
211
|
+
assert not any("node_modules" in p for p in paths)
|
|
212
|
+
assert len(markers) == 1
|
|
213
|
+
|
|
214
|
+
def test_case_sensitive(self, tmp_path: Path) -> None:
|
|
215
|
+
"""Markers must be uppercase to match."""
|
|
216
|
+
from pennyfarthing_scripts.codemarkers.analyze import _grep_markers
|
|
217
|
+
|
|
218
|
+
f = tmp_path / "lower.py"
|
|
219
|
+
f.write_text("# todo: lowercase should not match\n# TODO: uppercase matches\n")
|
|
220
|
+
markers = _grep_markers(tmp_path, excludes=[])
|
|
221
|
+
assert len(markers) == 1
|
|
222
|
+
assert markers[0]["marker_type"] == "TODO"
|
|
223
|
+
|
|
224
|
+
def test_nested_directories(self, tmp_path: Path) -> None:
|
|
225
|
+
"""Scans recursively into subdirectories."""
|
|
226
|
+
from pennyfarthing_scripts.codemarkers.analyze import _grep_markers
|
|
227
|
+
|
|
228
|
+
sub = tmp_path / "src" / "lib"
|
|
229
|
+
sub.mkdir(parents=True)
|
|
230
|
+
(sub / "deep.ts").write_text("// FIXME: deep bug\n")
|
|
231
|
+
markers = _grep_markers(tmp_path, excludes=[])
|
|
232
|
+
assert len(markers) == 1
|
|
233
|
+
|
|
234
|
+
def test_empty_directory(self, tmp_path: Path) -> None:
|
|
235
|
+
"""Returns empty list for directory with no markers."""
|
|
236
|
+
from pennyfarthing_scripts.codemarkers.analyze import _grep_markers
|
|
237
|
+
|
|
238
|
+
(tmp_path / "clean.py").write_text("x = 1\ny = 2\n")
|
|
239
|
+
markers = _grep_markers(tmp_path, excludes=[])
|
|
240
|
+
assert markers == []
|
|
241
|
+
|
|
242
|
+
def test_binary_files_skipped(self, tmp_path: Path) -> None:
|
|
243
|
+
"""Binary files should not cause crashes."""
|
|
244
|
+
from pennyfarthing_scripts.codemarkers.analyze import _grep_markers
|
|
245
|
+
|
|
246
|
+
(tmp_path / "image.png").write_bytes(b"\x89PNG\r\n\x1a\n" + b"\x00" * 100)
|
|
247
|
+
(tmp_path / "code.py").write_text("# TODO: real marker\n")
|
|
248
|
+
markers = _grep_markers(tmp_path, excludes=[])
|
|
249
|
+
assert len(markers) == 1
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
class TestParseBlameOutput:
|
|
253
|
+
"""_parse_blame_porcelain extracts author and date from git blame --porcelain."""
|
|
254
|
+
|
|
255
|
+
def test_parses_author_and_time(self) -> None:
|
|
256
|
+
"""Extracts author name and author-time from porcelain output."""
|
|
257
|
+
from pennyfarthing_scripts.codemarkers.analyze import _parse_blame_porcelain
|
|
258
|
+
|
|
259
|
+
porcelain = (
|
|
260
|
+
"abc123 1 1 1\n"
|
|
261
|
+
"author John Doe\n"
|
|
262
|
+
"author-mail <john@example.com>\n"
|
|
263
|
+
"author-time 1700000000\n"
|
|
264
|
+
"author-tz +0000\n"
|
|
265
|
+
"committer John Doe\n"
|
|
266
|
+
"committer-mail <john@example.com>\n"
|
|
267
|
+
"committer-time 1700000000\n"
|
|
268
|
+
"committer-tz +0000\n"
|
|
269
|
+
"summary Some commit\n"
|
|
270
|
+
"filename src/app.py\n"
|
|
271
|
+
"\t# TODO: fix this\n"
|
|
272
|
+
)
|
|
273
|
+
result = _parse_blame_porcelain(porcelain, line=1)
|
|
274
|
+
assert result["author"] == "John Doe"
|
|
275
|
+
assert result["author_time"] == 1700000000
|
|
276
|
+
|
|
277
|
+
def test_empty_output(self) -> None:
|
|
278
|
+
"""Returns empty dict for empty output."""
|
|
279
|
+
from pennyfarthing_scripts.codemarkers.analyze import _parse_blame_porcelain
|
|
280
|
+
|
|
281
|
+
result = _parse_blame_porcelain("", line=1)
|
|
282
|
+
assert result == {} or result.get("author") == ""
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
class TestBatchBlame:
|
|
286
|
+
"""_batch_blame_file calls git blame once per file, extracts multiple lines."""
|
|
287
|
+
|
|
288
|
+
@pytest.mark.asyncio
|
|
289
|
+
async def test_blames_entire_file(self) -> None:
|
|
290
|
+
"""Blames the whole file and returns data for requested lines."""
|
|
291
|
+
from pennyfarthing_scripts.codemarkers.analyze import _batch_blame_file
|
|
292
|
+
|
|
293
|
+
blame_output = (
|
|
294
|
+
"abc123 1 1 1\n"
|
|
295
|
+
"author Alice\n"
|
|
296
|
+
"author-time 1700000000\n"
|
|
297
|
+
"author-tz +0000\n"
|
|
298
|
+
"committer Alice\n"
|
|
299
|
+
"committer-time 1700000000\n"
|
|
300
|
+
"committer-tz +0000\n"
|
|
301
|
+
"summary init\n"
|
|
302
|
+
"filename test.py\n"
|
|
303
|
+
"\tline 1\n"
|
|
304
|
+
"def456 2 2 1\n"
|
|
305
|
+
"author Bob\n"
|
|
306
|
+
"author-time 1700100000\n"
|
|
307
|
+
"author-tz +0000\n"
|
|
308
|
+
"committer Bob\n"
|
|
309
|
+
"committer-time 1700100000\n"
|
|
310
|
+
"committer-tz +0000\n"
|
|
311
|
+
"summary fix\n"
|
|
312
|
+
"filename test.py\n"
|
|
313
|
+
"\tline 2\n"
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
with patch(
|
|
317
|
+
"pennyfarthing_scripts.codemarkers.analyze._run_git_command",
|
|
318
|
+
new_callable=AsyncMock,
|
|
319
|
+
return_value=(blame_output, "", 0),
|
|
320
|
+
):
|
|
321
|
+
results = await _batch_blame_file(
|
|
322
|
+
Path("/repo"), "test.py", [1, 2]
|
|
323
|
+
)
|
|
324
|
+
assert 1 in results
|
|
325
|
+
assert 2 in results
|
|
326
|
+
assert results[1]["author"] == "Alice"
|
|
327
|
+
assert results[2]["author"] == "Bob"
|
|
328
|
+
|
|
329
|
+
@pytest.mark.asyncio
|
|
330
|
+
async def test_git_blame_failure_returns_empty(self) -> None:
|
|
331
|
+
"""When git blame fails, returns empty dict for all lines."""
|
|
332
|
+
from pennyfarthing_scripts.codemarkers.analyze import _batch_blame_file
|
|
333
|
+
|
|
334
|
+
with patch(
|
|
335
|
+
"pennyfarthing_scripts.codemarkers.analyze._run_git_command",
|
|
336
|
+
new_callable=AsyncMock,
|
|
337
|
+
return_value=("", "fatal: not a git repo", 128),
|
|
338
|
+
):
|
|
339
|
+
results = await _batch_blame_file(Path("/bad"), "x.py", [1])
|
|
340
|
+
assert results == {} or results.get(1, {}).get("author", "") == ""
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
class TestAnalyzeRepo:
|
|
344
|
+
"""analyze_repo is the main entry point — grep + blame + staleness."""
|
|
345
|
+
|
|
346
|
+
@pytest.mark.asyncio
|
|
347
|
+
async def test_nonexistent_path(self) -> None:
|
|
348
|
+
"""Returns error result for nonexistent path."""
|
|
349
|
+
from pennyfarthing_scripts.codemarkers.analyze import analyze_repo
|
|
350
|
+
|
|
351
|
+
result = await analyze_repo("bad", Path("/nonexistent/repo"), days=90)
|
|
352
|
+
assert result.success is False
|
|
353
|
+
assert "not found" in result.error.lower() or "not exist" in result.error.lower()
|
|
354
|
+
|
|
355
|
+
@pytest.mark.asyncio
|
|
356
|
+
async def test_empty_repo(self, tmp_path: Path) -> None:
|
|
357
|
+
"""Returns success with empty markers for repo with no markers."""
|
|
358
|
+
from pennyfarthing_scripts.codemarkers.analyze import analyze_repo
|
|
359
|
+
|
|
360
|
+
(tmp_path / ".git").mkdir()
|
|
361
|
+
(tmp_path / "clean.py").write_text("x = 1\n")
|
|
362
|
+
|
|
363
|
+
with patch(
|
|
364
|
+
"pennyfarthing_scripts.codemarkers.analyze._batch_blame_file",
|
|
365
|
+
new_callable=AsyncMock,
|
|
366
|
+
return_value={},
|
|
367
|
+
):
|
|
368
|
+
result = await analyze_repo("test", tmp_path, days=90)
|
|
369
|
+
assert result.success is True
|
|
370
|
+
assert len(result.markers) == 0
|
|
371
|
+
|
|
372
|
+
@pytest.mark.asyncio
|
|
373
|
+
async def test_markers_enriched_with_blame(self, tmp_path: Path) -> None:
|
|
374
|
+
"""Markers get author, date, age_days from git blame."""
|
|
375
|
+
from pennyfarthing_scripts.codemarkers.analyze import analyze_repo
|
|
376
|
+
|
|
377
|
+
(tmp_path / ".git").mkdir()
|
|
378
|
+
(tmp_path / "app.py").write_text("x = 1\n# TODO: fix this\ny = 2\n")
|
|
379
|
+
|
|
380
|
+
# Mock blame: author Alice, time = 90 days ago
|
|
381
|
+
import time
|
|
382
|
+
ninety_days_ago = int(time.time()) - (90 * 86400)
|
|
383
|
+
|
|
384
|
+
async def mock_blame(repo_path, file_path, lines):
|
|
385
|
+
return {
|
|
386
|
+
2: {"author": "Alice", "author_time": ninety_days_ago},
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
with patch(
|
|
390
|
+
"pennyfarthing_scripts.codemarkers.analyze._batch_blame_file",
|
|
391
|
+
side_effect=mock_blame,
|
|
392
|
+
):
|
|
393
|
+
result = await analyze_repo("test", tmp_path, days=90)
|
|
394
|
+
assert result.success is True
|
|
395
|
+
assert len(result.markers) == 1
|
|
396
|
+
assert result.markers[0].author == "Alice"
|
|
397
|
+
assert result.markers[0].age_days >= 89 # approximately 90
|
|
398
|
+
|
|
399
|
+
@pytest.mark.asyncio
|
|
400
|
+
async def test_stale_threshold(self, tmp_path: Path) -> None:
|
|
401
|
+
"""Markers older than threshold are flagged is_stale=True."""
|
|
402
|
+
from pennyfarthing_scripts.codemarkers.analyze import analyze_repo
|
|
403
|
+
|
|
404
|
+
(tmp_path / ".git").mkdir()
|
|
405
|
+
(tmp_path / "old.py").write_text("# TODO: ancient code\n")
|
|
406
|
+
|
|
407
|
+
import time
|
|
408
|
+
old_time = int(time.time()) - (200 * 86400) # 200 days ago
|
|
409
|
+
|
|
410
|
+
async def mock_blame(repo_path, file_path, lines):
|
|
411
|
+
return {1: {"author": "OldDev", "author_time": old_time}}
|
|
412
|
+
|
|
413
|
+
with patch(
|
|
414
|
+
"pennyfarthing_scripts.codemarkers.analyze._batch_blame_file",
|
|
415
|
+
side_effect=mock_blame,
|
|
416
|
+
):
|
|
417
|
+
result = await analyze_repo("test", tmp_path, days=90)
|
|
418
|
+
assert result.markers[0].is_stale is True
|
|
419
|
+
assert result.markers[0].age_days >= 199
|
|
420
|
+
|
|
421
|
+
@pytest.mark.asyncio
|
|
422
|
+
async def test_summary_computed(self, tmp_path: Path) -> None:
|
|
423
|
+
"""Result includes summary with counts by type."""
|
|
424
|
+
from pennyfarthing_scripts.codemarkers.analyze import analyze_repo
|
|
425
|
+
|
|
426
|
+
(tmp_path / ".git").mkdir()
|
|
427
|
+
(tmp_path / "mix.py").write_text("# TODO: one\n# FIXME: two\n# TODO: three\n")
|
|
428
|
+
|
|
429
|
+
import time
|
|
430
|
+
recent = int(time.time()) - (10 * 86400)
|
|
431
|
+
|
|
432
|
+
async def mock_blame(repo_path, file_path, lines):
|
|
433
|
+
return {ln: {"author": "Dev", "author_time": recent} for ln in lines}
|
|
434
|
+
|
|
435
|
+
with patch(
|
|
436
|
+
"pennyfarthing_scripts.codemarkers.analyze._batch_blame_file",
|
|
437
|
+
side_effect=mock_blame,
|
|
438
|
+
):
|
|
439
|
+
result = await analyze_repo("test", tmp_path, days=90)
|
|
440
|
+
assert result.summary is not None
|
|
441
|
+
assert result.summary.total_markers == 3
|
|
442
|
+
assert result.summary.by_type["TODO"] == 2
|
|
443
|
+
assert result.summary.by_type["FIXME"] == 1
|
|
444
|
+
assert result.summary.stale_markers == 0
|
|
445
|
+
|
|
446
|
+
@pytest.mark.asyncio
|
|
447
|
+
async def test_default_excludes_applied(self, tmp_path: Path) -> None:
|
|
448
|
+
"""DEFAULT_EXCLUDES filters out node_modules, dist, etc."""
|
|
449
|
+
from pennyfarthing_scripts.codemarkers.analyze import analyze_repo
|
|
450
|
+
|
|
451
|
+
(tmp_path / ".git").mkdir()
|
|
452
|
+
nm = tmp_path / "node_modules" / "pkg"
|
|
453
|
+
nm.mkdir(parents=True)
|
|
454
|
+
(nm / "lib.js").write_text("// TODO: vendor code\n")
|
|
455
|
+
(tmp_path / "src.py").write_text("# TODO: real code\n")
|
|
456
|
+
|
|
457
|
+
import time
|
|
458
|
+
recent = int(time.time()) - 86400
|
|
459
|
+
|
|
460
|
+
async def mock_blame(repo_path, file_path, lines):
|
|
461
|
+
return {ln: {"author": "Dev", "author_time": recent} for ln in lines}
|
|
462
|
+
|
|
463
|
+
with patch(
|
|
464
|
+
"pennyfarthing_scripts.codemarkers.analyze._batch_blame_file",
|
|
465
|
+
side_effect=mock_blame,
|
|
466
|
+
):
|
|
467
|
+
result = await analyze_repo("test", tmp_path, days=90)
|
|
468
|
+
paths = [m.path for m in result.markers]
|
|
469
|
+
assert not any("node_modules" in p for p in paths)
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
class TestShouldExclude:
|
|
473
|
+
"""_should_exclude matches file paths against glob patterns."""
|
|
474
|
+
|
|
475
|
+
def test_matches_node_modules(self) -> None:
|
|
476
|
+
from pennyfarthing_scripts.codemarkers.analyze import _should_exclude
|
|
477
|
+
|
|
478
|
+
assert _should_exclude("node_modules/pkg/index.js", ["node_modules/*"]) is True
|
|
479
|
+
|
|
480
|
+
def test_matches_lock_file(self) -> None:
|
|
481
|
+
from pennyfarthing_scripts.codemarkers.analyze import _should_exclude
|
|
482
|
+
|
|
483
|
+
assert _should_exclude("package-lock.json", ["package-lock.json"]) is True
|
|
484
|
+
|
|
485
|
+
def test_matches_glob_extension(self) -> None:
|
|
486
|
+
from pennyfarthing_scripts.codemarkers.analyze import _should_exclude
|
|
487
|
+
|
|
488
|
+
assert _should_exclude("bundle.min.js", ["*.min.js"]) is True
|
|
489
|
+
|
|
490
|
+
def test_no_match(self) -> None:
|
|
491
|
+
from pennyfarthing_scripts.codemarkers.analyze import _should_exclude
|
|
492
|
+
|
|
493
|
+
assert _should_exclude("src/app.py", ["node_modules/*", "dist/*"]) is False
|
|
494
|
+
|
|
495
|
+
|
|
496
|
+
# ---------------------------------------------------------------------------
|
|
497
|
+
# Formatters
|
|
498
|
+
# ---------------------------------------------------------------------------
|
|
499
|
+
|
|
500
|
+
|
|
501
|
+
class TestTableFormatter:
|
|
502
|
+
"""format_marker_table produces column-aligned output."""
|
|
503
|
+
|
|
504
|
+
def test_empty_list(self) -> None:
|
|
505
|
+
"""Empty marker list produces 'no markers' message."""
|
|
506
|
+
from pennyfarthing_scripts.codemarkers.formatters import format_marker_table
|
|
507
|
+
|
|
508
|
+
out = format_marker_table([])
|
|
509
|
+
assert "no" in out.lower() or "No" in out
|
|
510
|
+
|
|
511
|
+
def test_table_has_headers(self) -> None:
|
|
512
|
+
"""Table output includes column headers."""
|
|
513
|
+
from pennyfarthing_scripts.codemarkers.formatters import format_marker_table
|
|
514
|
+
from pennyfarthing_scripts.codemarkers.models import CodeMarker
|
|
515
|
+
|
|
516
|
+
markers = [
|
|
517
|
+
CodeMarker(path="a.py", line=1, marker_type="TODO", text="TODO: test",
|
|
518
|
+
author="Dev", age_days=10.0),
|
|
519
|
+
]
|
|
520
|
+
out = format_marker_table(markers)
|
|
521
|
+
assert "Type" in out
|
|
522
|
+
assert "File" in out or "Path" in out
|
|
523
|
+
assert "Age" in out
|
|
524
|
+
|
|
525
|
+
def test_top_n_limits_output(self) -> None:
|
|
526
|
+
"""Table respects top_n limit."""
|
|
527
|
+
from pennyfarthing_scripts.codemarkers.formatters import format_marker_table
|
|
528
|
+
from pennyfarthing_scripts.codemarkers.models import CodeMarker
|
|
529
|
+
|
|
530
|
+
markers = [
|
|
531
|
+
CodeMarker(path=f"f{i}.py", line=i, marker_type="TODO", text=f"TODO: {i}")
|
|
532
|
+
for i in range(50)
|
|
533
|
+
]
|
|
534
|
+
out = format_marker_table(markers, top_n=5)
|
|
535
|
+
# Header + separator + 5 data rows = 7 lines
|
|
536
|
+
lines = [line for line in out.strip().split("\n") if line.strip()]
|
|
537
|
+
assert len(lines) <= 7
|
|
538
|
+
|
|
539
|
+
|
|
540
|
+
class TestJsonExport:
|
|
541
|
+
"""export_json serializes full result to JSON."""
|
|
542
|
+
|
|
543
|
+
def test_valid_json(self) -> None:
|
|
544
|
+
from pennyfarthing_scripts.codemarkers.formatters import export_json
|
|
545
|
+
from pennyfarthing_scripts.codemarkers.models import (
|
|
546
|
+
CodeMarker,
|
|
547
|
+
CodeMarkersResult,
|
|
548
|
+
MarkerSummary,
|
|
549
|
+
)
|
|
550
|
+
|
|
551
|
+
r = CodeMarkersResult(
|
|
552
|
+
success=True, repo_name="test", repo_path="/tmp",
|
|
553
|
+
stale_threshold_days=90,
|
|
554
|
+
markers=[CodeMarker(path="x.py", line=1, marker_type="TODO", text="TODO: x")],
|
|
555
|
+
summary=MarkerSummary(total_markers=1, by_type={"TODO": 1}),
|
|
556
|
+
)
|
|
557
|
+
text = export_json(r)
|
|
558
|
+
parsed = json.loads(text)
|
|
559
|
+
assert parsed["success"] is True
|
|
560
|
+
assert len(parsed["markers"]) == 1
|
|
561
|
+
|
|
562
|
+
|
|
563
|
+
class TestCsvExport:
|
|
564
|
+
"""export_csv outputs CSV with headers."""
|
|
565
|
+
|
|
566
|
+
def test_csv_header_row(self) -> None:
|
|
567
|
+
from pennyfarthing_scripts.codemarkers.formatters import export_csv
|
|
568
|
+
from pennyfarthing_scripts.codemarkers.models import CodeMarker
|
|
569
|
+
|
|
570
|
+
markers = [
|
|
571
|
+
CodeMarker(path="a.py", line=1, marker_type="TODO", text="TODO: test"),
|
|
572
|
+
]
|
|
573
|
+
text = export_csv(markers)
|
|
574
|
+
lines = text.strip().split("\n")
|
|
575
|
+
assert "path" in lines[0]
|
|
576
|
+
assert "marker_type" in lines[0]
|
|
577
|
+
assert len(lines) == 2 # header + 1 data row
|
|
578
|
+
|
|
579
|
+
|
|
580
|
+
# ---------------------------------------------------------------------------
|
|
581
|
+
# CLI
|
|
582
|
+
# ---------------------------------------------------------------------------
|
|
583
|
+
|
|
584
|
+
|
|
585
|
+
class TestCLI:
|
|
586
|
+
"""Click CLI commands."""
|
|
587
|
+
|
|
588
|
+
def test_cli_help(self) -> None:
|
|
589
|
+
"""codemarkers group shows help."""
|
|
590
|
+
from click.testing import CliRunner
|
|
591
|
+
|
|
592
|
+
from pennyfarthing_scripts.codemarkers.cli import codemarkers
|
|
593
|
+
|
|
594
|
+
runner = CliRunner()
|
|
595
|
+
result = runner.invoke(codemarkers, ["--help"])
|
|
596
|
+
assert result.exit_code == 0
|
|
597
|
+
assert "analyze" in result.output.lower()
|
|
598
|
+
|
|
599
|
+
def test_analyze_command_exists(self) -> None:
|
|
600
|
+
"""analyze subcommand is registered."""
|
|
601
|
+
from click.testing import CliRunner
|
|
602
|
+
|
|
603
|
+
from pennyfarthing_scripts.codemarkers.cli import codemarkers
|
|
604
|
+
|
|
605
|
+
runner = CliRunner()
|
|
606
|
+
result = runner.invoke(codemarkers, ["analyze", "--help"])
|
|
607
|
+
assert result.exit_code == 0
|
|
608
|
+
assert "--days" in result.output
|
|
609
|
+
assert "--format" in result.output
|
|
610
|
+
|
|
611
|
+
def test_stale_command_exists(self) -> None:
|
|
612
|
+
"""stale subcommand is registered."""
|
|
613
|
+
from click.testing import CliRunner
|
|
614
|
+
|
|
615
|
+
from pennyfarthing_scripts.codemarkers.cli import codemarkers
|
|
616
|
+
|
|
617
|
+
runner = CliRunner()
|
|
618
|
+
result = runner.invoke(codemarkers, ["stale", "--help"])
|
|
619
|
+
assert result.exit_code == 0
|
|
620
|
+
|
|
621
|
+
def test_summary_command_exists(self) -> None:
|
|
622
|
+
"""summary subcommand is registered."""
|
|
623
|
+
from click.testing import CliRunner
|
|
624
|
+
|
|
625
|
+
from pennyfarthing_scripts.codemarkers.cli import codemarkers
|
|
626
|
+
|
|
627
|
+
runner = CliRunner()
|
|
628
|
+
result = runner.invoke(codemarkers, ["summary", "--help"])
|
|
629
|
+
assert result.exit_code == 0
|
|
630
|
+
|
|
631
|
+
def test_json_format_option(self) -> None:
|
|
632
|
+
"""--format json produces JSON output."""
|
|
633
|
+
from click.testing import CliRunner
|
|
634
|
+
|
|
635
|
+
from pennyfarthing_scripts.codemarkers.cli import codemarkers
|
|
636
|
+
|
|
637
|
+
runner = CliRunner()
|
|
638
|
+
with patch(
|
|
639
|
+
"pennyfarthing_scripts.codemarkers.cli._run_analysis"
|
|
640
|
+
) as mock_run:
|
|
641
|
+
from pennyfarthing_scripts.codemarkers.models import CodeMarkersResult, MarkerSummary
|
|
642
|
+
mock_run.return_value = CodeMarkersResult(
|
|
643
|
+
success=True, repo_name="test", repo_path="/tmp",
|
|
644
|
+
stale_threshold_days=90, markers=[],
|
|
645
|
+
summary=MarkerSummary(),
|
|
646
|
+
)
|
|
647
|
+
result = runner.invoke(codemarkers, ["analyze", "--path", "/tmp", "--format", "json"])
|
|
648
|
+
assert result.exit_code == 0
|
|
649
|
+
parsed = json.loads(result.output)
|
|
650
|
+
assert parsed["success"] is True
|
|
651
|
+
|
|
652
|
+
|
|
653
|
+
class TestMainModule:
|
|
654
|
+
"""python -m pennyfarthing_scripts.codemarkers works."""
|
|
655
|
+
|
|
656
|
+
def test_module_runnable(self) -> None:
|
|
657
|
+
"""Module can be invoked with --help."""
|
|
658
|
+
import subprocess
|
|
659
|
+
import sys
|
|
660
|
+
|
|
661
|
+
result = subprocess.run(
|
|
662
|
+
[sys.executable, "-m", "pennyfarthing_scripts.codemarkers", "--help"],
|
|
663
|
+
capture_output=True, text=True, timeout=30,
|
|
664
|
+
)
|
|
665
|
+
assert result.returncode == 0
|
|
666
|
+
assert "analyze" in result.stdout.lower()
|
|
667
|
+
|
|
668
|
+
|
|
669
|
+
# ---------------------------------------------------------------------------
|
|
670
|
+
# __init__ re-exports
|
|
671
|
+
# ---------------------------------------------------------------------------
|
|
672
|
+
|
|
673
|
+
|
|
674
|
+
class TestModuleExports:
|
|
675
|
+
"""__init__.py re-exports key symbols."""
|
|
676
|
+
|
|
677
|
+
def test_exports_models(self) -> None:
|
|
678
|
+
from pennyfarthing_scripts.codemarkers import (
|
|
679
|
+
CodeMarker,
|
|
680
|
+
CodeMarkersResult,
|
|
681
|
+
)
|
|
682
|
+
assert CodeMarker is not None
|
|
683
|
+
assert CodeMarkersResult is not None
|
|
684
|
+
|
|
685
|
+
def test_exports_analyze(self) -> None:
|
|
686
|
+
from pennyfarthing_scripts.codemarkers import analyze_repo
|
|
687
|
+
assert callable(analyze_repo)
|