@pennyfarthing/core 10.1.0 → 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 +13 -18
- package/package.json +3 -1
- 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 +101 -15
- 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/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.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/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/handoff.md +18 -3
- 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 -5
- package/pennyfarthing-dist/agents/tandem-backseat.md +119 -0
- package/pennyfarthing-dist/commands/setup.md +4 -0
- package/pennyfarthing-dist/guides/agent-behavior.md +62 -6
- package/pennyfarthing-dist/guides/bikelane.md +3 -2
- package/pennyfarthing-dist/guides/scale-levels.md +4 -6
- 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/hooks/bell-mode-hook.sh +131 -54
- package/pennyfarthing-dist/scripts/hooks/post-merge.sh +20 -10
- package/pennyfarthing-dist/scripts/misc/statusline.sh +50 -8
- package/pennyfarthing-dist/scripts/workflow/README.md +2 -2
- package/pennyfarthing-dist/scripts/workflow/finish-story.sh +10 -189
- package/pennyfarthing-dist/skills/skill-registry.schema.json +8 -0
- package/pennyfarthing-dist/skills/skill-registry.yaml +1 -1
- package/pennyfarthing-dist/skills/sprint/skill.md +25 -2
- package/pennyfarthing-dist/skills/workflow/skill.md +24 -1
- package/pennyfarthing-dist/workflows/architecture/workflow.yaml +65 -0
- package/pennyfarthing-dist/workflows/bdd-tandem.yaml +70 -0
- 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 +11 -6
- package/pennyfarthing_scripts/codemarkers/__init__.py +5 -1
- 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 +177 -2
- package/pennyfarthing_scripts/codemarkers/cli.py +50 -0
- package/pennyfarthing_scripts/codemarkers/formatters.py +0 -1
- package/pennyfarthing_scripts/codemarkers/models.py +15 -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 +1 -1
- 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 +1 -1
- package/pennyfarthing_scripts/complexity/cli.py +5 -1
- package/pennyfarthing_scripts/complexity/formatters.py +1 -1
- package/pennyfarthing_scripts/context.py +14 -15
- 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 +3 -4
- package/pennyfarthing_scripts/deadcode/cli.py +2 -2
- package/pennyfarthing_scripts/dependencies/__init__.py +2 -2
- 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 +1 -1
- package/pennyfarthing_scripts/dependencies/cli.py +8 -4
- package/pennyfarthing_scripts/dependencies/formatters.py +1 -1
- 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 +2 -2
- package/pennyfarthing_scripts/healthscore/__main__.py +8 -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 +451 -21
- package/pennyfarthing_scripts/healthscore/cli.py +5 -1
- package/pennyfarthing_scripts/healthscore/models.py +0 -1
- 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 +128 -14
- package/pennyfarthing_scripts/hotspots/cli.py +2 -2
- 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 +2 -3
- package/pennyfarthing_scripts/jira/claim.py +21 -0
- package/pennyfarthing_scripts/jira/cli.py +2 -2
- package/pennyfarthing_scripts/jira/client.py +4 -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 +1 -4
- package/pennyfarthing_scripts/sprint/cli.py +34 -28
- 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 +2 -2
- package/pennyfarthing_scripts/sprint/story_finish.py +3 -5
- 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 +1 -4
- 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 +13 -8
- 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 +17 -25
- 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/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/theme/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/theme/cli.py +3 -2
- 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 +3 -15
- 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
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Core hotspot analysis engine.
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
Uses PyDriller to mine git history, computes per-file metrics, and produces
|
|
5
|
+
scored hotspot results. Falls back to raw git log parsing if PyDriller is unavailable.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
from __future__ import annotations
|
|
9
9
|
|
|
10
10
|
import asyncio
|
|
11
11
|
import fnmatch
|
|
12
|
+
import logging
|
|
12
13
|
import re
|
|
13
14
|
from collections import defaultdict
|
|
14
|
-
from datetime import datetime,
|
|
15
|
+
from datetime import UTC, datetime, timedelta
|
|
15
16
|
from pathlib import Path
|
|
16
17
|
|
|
17
18
|
from pennyfarthing_scripts.hotspots.models import (
|
|
@@ -21,6 +22,8 @@ from pennyfarthing_scripts.hotspots.models import (
|
|
|
21
22
|
MultiRepoHotspotResult,
|
|
22
23
|
)
|
|
23
24
|
|
|
25
|
+
logger = logging.getLogger("hotspots")
|
|
26
|
+
|
|
24
27
|
# Scoring weights (must sum to 1.0)
|
|
25
28
|
WEIGHT_BUG_FIXES = 0.35
|
|
26
29
|
WEIGHT_CHANGES = 0.30
|
|
@@ -59,6 +62,30 @@ DEFAULT_EXCLUDES = [
|
|
|
59
62
|
"*.d.ts.map",
|
|
60
63
|
# CI config
|
|
61
64
|
".github/*",
|
|
65
|
+
# Sprint/session operational files (not code quality signals)
|
|
66
|
+
"sprint/*",
|
|
67
|
+
".session/*",
|
|
68
|
+
# Config/manifest files — high churn but not code quality signals
|
|
69
|
+
"package.json",
|
|
70
|
+
"*/package.json",
|
|
71
|
+
"tsconfig.json",
|
|
72
|
+
"*/tsconfig.json",
|
|
73
|
+
"tsconfig.*.json",
|
|
74
|
+
"*/tsconfig.*.json",
|
|
75
|
+
# Documentation — frequently edited but not code hotspots
|
|
76
|
+
"*.md",
|
|
77
|
+
"CLAUDE.md",
|
|
78
|
+
"*/CLAUDE.md",
|
|
79
|
+
"CLAUDE-*.md",
|
|
80
|
+
"*/CLAUDE-*.md",
|
|
81
|
+
"README.md",
|
|
82
|
+
"*/README.md",
|
|
83
|
+
"CHANGELOG.md",
|
|
84
|
+
"*/CHANGELOG.md",
|
|
85
|
+
"docs/*",
|
|
86
|
+
# YAML config (sprint, workflow, etc.)
|
|
87
|
+
"*.yaml",
|
|
88
|
+
"*.yml",
|
|
62
89
|
]
|
|
63
90
|
|
|
64
91
|
# Regex for identifying bug-fix commits
|
|
@@ -286,6 +313,8 @@ async def analyze_repo(
|
|
|
286
313
|
) -> HotspotResult:
|
|
287
314
|
"""Analyze a single repository for code hotspots.
|
|
288
315
|
|
|
316
|
+
Uses PyDriller for git mining when available, falls back to raw git log parsing.
|
|
317
|
+
|
|
289
318
|
Args:
|
|
290
319
|
name: Display name for the repository
|
|
291
320
|
path: Path to the git repository
|
|
@@ -308,6 +337,87 @@ async def analyze_repo(
|
|
|
308
337
|
error=f"Path not found: {resolved}",
|
|
309
338
|
)
|
|
310
339
|
|
|
340
|
+
try:
|
|
341
|
+
return await _analyze_repo_pydriller(name, resolved, days, all_excludes)
|
|
342
|
+
except ImportError:
|
|
343
|
+
logger.info("[hotspots] PyDriller not available, using git log fallback")
|
|
344
|
+
return await _analyze_repo_gitlog(name, resolved, days, all_excludes, branch)
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
async def _analyze_repo_pydriller(
|
|
348
|
+
name: str,
|
|
349
|
+
resolved: Path,
|
|
350
|
+
days: int,
|
|
351
|
+
all_excludes: list[str],
|
|
352
|
+
) -> HotspotResult:
|
|
353
|
+
"""PyDriller-backed hotspot analysis."""
|
|
354
|
+
from pydriller import Repository
|
|
355
|
+
|
|
356
|
+
since = datetime.now(UTC) - timedelta(days=days)
|
|
357
|
+
now = datetime.now(UTC)
|
|
358
|
+
|
|
359
|
+
file_metrics: dict[str, dict] = defaultdict(
|
|
360
|
+
lambda: {
|
|
361
|
+
"change_count": 0,
|
|
362
|
+
"bug_fix_count": 0,
|
|
363
|
+
"authors": set(),
|
|
364
|
+
"lines_added": 0,
|
|
365
|
+
"lines_deleted": 0,
|
|
366
|
+
"last_changed": "",
|
|
367
|
+
}
|
|
368
|
+
)
|
|
369
|
+
|
|
370
|
+
def _collect() -> int:
|
|
371
|
+
commit_count = 0
|
|
372
|
+
repo = Repository(str(resolved), since=since)
|
|
373
|
+
for commit in repo.traverse_commits():
|
|
374
|
+
commit_count += 1
|
|
375
|
+
is_fix = is_bug_fix_commit(commit.msg)
|
|
376
|
+
commit_date = commit.committer_date.isoformat()
|
|
377
|
+
|
|
378
|
+
for mod in commit.modified_files:
|
|
379
|
+
fpath = mod.new_path or mod.old_path
|
|
380
|
+
if not fpath:
|
|
381
|
+
continue
|
|
382
|
+
if _should_exclude(fpath, all_excludes):
|
|
383
|
+
continue
|
|
384
|
+
|
|
385
|
+
m = file_metrics[fpath]
|
|
386
|
+
m["change_count"] += 1
|
|
387
|
+
if is_fix:
|
|
388
|
+
m["bug_fix_count"] += 1
|
|
389
|
+
m["authors"].add(commit.author.name)
|
|
390
|
+
m["lines_added"] += mod.added_lines
|
|
391
|
+
m["lines_deleted"] += mod.deleted_lines
|
|
392
|
+
if not m["last_changed"] or commit_date > m["last_changed"]:
|
|
393
|
+
m["last_changed"] = commit_date
|
|
394
|
+
return commit_count
|
|
395
|
+
|
|
396
|
+
loop = asyncio.get_event_loop()
|
|
397
|
+
commit_count = await loop.run_in_executor(None, _collect)
|
|
398
|
+
logger.info("[hotspots] PyDriller: %d commits, %d files after filtering",
|
|
399
|
+
commit_count, len(file_metrics))
|
|
400
|
+
|
|
401
|
+
if not file_metrics:
|
|
402
|
+
return HotspotResult(
|
|
403
|
+
success=True,
|
|
404
|
+
repo_name=name,
|
|
405
|
+
repo_path=str(resolved),
|
|
406
|
+
time_window_days=days,
|
|
407
|
+
commit_count=commit_count,
|
|
408
|
+
)
|
|
409
|
+
|
|
410
|
+
return _build_hotspot_result(name, resolved, days, commit_count, file_metrics, now)
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
async def _analyze_repo_gitlog(
|
|
414
|
+
name: str,
|
|
415
|
+
resolved: Path,
|
|
416
|
+
days: int,
|
|
417
|
+
all_excludes: list[str],
|
|
418
|
+
branch: str,
|
|
419
|
+
) -> HotspotResult:
|
|
420
|
+
"""Fallback: raw git log parsing."""
|
|
311
421
|
stdout, stderr, rc = await _run_git_log(resolved, days, branch)
|
|
312
422
|
|
|
313
423
|
if rc != 0:
|
|
@@ -330,7 +440,6 @@ async def analyze_repo(
|
|
|
330
440
|
commit_count=0,
|
|
331
441
|
)
|
|
332
442
|
|
|
333
|
-
# Aggregate per-file metrics
|
|
334
443
|
file_metrics: dict[str, dict] = defaultdict(
|
|
335
444
|
lambda: {
|
|
336
445
|
"change_count": 0,
|
|
@@ -342,7 +451,7 @@ async def analyze_repo(
|
|
|
342
451
|
}
|
|
343
452
|
)
|
|
344
453
|
|
|
345
|
-
now = datetime.now(
|
|
454
|
+
now = datetime.now(UTC)
|
|
346
455
|
|
|
347
456
|
for commit in commits:
|
|
348
457
|
is_fix = is_bug_fix_commit(commit["message"])
|
|
@@ -358,7 +467,6 @@ async def analyze_repo(
|
|
|
358
467
|
m["authors"].add(commit["author"])
|
|
359
468
|
m["lines_added"] += file_info["added"]
|
|
360
469
|
m["lines_deleted"] += file_info["deleted"]
|
|
361
|
-
# Track most recent change date
|
|
362
470
|
if not m["last_changed"] or commit["date"] > m["last_changed"]:
|
|
363
471
|
m["last_changed"] = commit["date"]
|
|
364
472
|
|
|
@@ -371,7 +479,18 @@ async def analyze_repo(
|
|
|
371
479
|
commit_count=len(commits),
|
|
372
480
|
)
|
|
373
481
|
|
|
374
|
-
|
|
482
|
+
return _build_hotspot_result(name, resolved, days, len(commits), file_metrics, now)
|
|
483
|
+
|
|
484
|
+
|
|
485
|
+
def _build_hotspot_result(
|
|
486
|
+
name: str,
|
|
487
|
+
resolved: Path,
|
|
488
|
+
days: int,
|
|
489
|
+
commit_count: int,
|
|
490
|
+
file_metrics: dict[str, dict],
|
|
491
|
+
now: datetime,
|
|
492
|
+
) -> HotspotResult:
|
|
493
|
+
"""Build scored HotspotResult from aggregated file metrics."""
|
|
375
494
|
max_changes = max(m["change_count"] for m in file_metrics.values())
|
|
376
495
|
max_bugs = max(m["bug_fix_count"] for m in file_metrics.values()) or 1
|
|
377
496
|
max_authors = max(len(m["authors"]) for m in file_metrics.values())
|
|
@@ -379,13 +498,11 @@ async def analyze_repo(
|
|
|
379
498
|
m["lines_added"] + m["lines_deleted"] for m in file_metrics.values()
|
|
380
499
|
) or 1
|
|
381
500
|
|
|
382
|
-
# Build FileHotspot list with scores
|
|
383
501
|
file_hotspots = []
|
|
384
502
|
for fpath, m in file_metrics.items():
|
|
385
503
|
churn = m["lines_added"] + m["lines_deleted"]
|
|
386
504
|
|
|
387
|
-
|
|
388
|
-
age_days = days # default to full window
|
|
505
|
+
age_days = days
|
|
389
506
|
if m["last_changed"]:
|
|
390
507
|
try:
|
|
391
508
|
last_dt = datetime.fromisoformat(m["last_changed"])
|
|
@@ -420,10 +537,7 @@ async def analyze_repo(
|
|
|
420
537
|
)
|
|
421
538
|
)
|
|
422
539
|
|
|
423
|
-
# Sort by score descending
|
|
424
540
|
file_hotspots.sort(key=lambda h: h.hotspot_score, reverse=True)
|
|
425
|
-
|
|
426
|
-
# Aggregate directories
|
|
427
541
|
directory_hotspots = _aggregate_by_directory(file_hotspots)
|
|
428
542
|
|
|
429
543
|
return HotspotResult(
|
|
@@ -431,7 +545,7 @@ async def analyze_repo(
|
|
|
431
545
|
repo_name=name,
|
|
432
546
|
repo_path=str(resolved),
|
|
433
547
|
time_window_days=days,
|
|
434
|
-
commit_count=
|
|
548
|
+
commit_count=commit_count,
|
|
435
549
|
file_hotspots=file_hotspots,
|
|
436
550
|
directory_hotspots=directory_hotspots,
|
|
437
551
|
)
|
|
@@ -44,8 +44,8 @@ def _common_options(fn):
|
|
|
44
44
|
|
|
45
45
|
def _run_analysis(repo: str | None, repo_path: str | None, days: int, exclude: tuple, branch: str, skip_type: tuple = ()):
|
|
46
46
|
"""Run analysis and return result."""
|
|
47
|
-
from pennyfarthing_scripts.hotspots.analyze import analyze_all_repos, analyze_repo
|
|
48
47
|
from pennyfarthing_scripts.common.config import get_project_root
|
|
48
|
+
from pennyfarthing_scripts.hotspots.analyze import analyze_all_repos, analyze_repo
|
|
49
49
|
|
|
50
50
|
excludes = list(exclude) if exclude else None
|
|
51
51
|
skip_types = list(skip_type) if skip_type else None
|
|
@@ -90,7 +90,7 @@ def _output_result(result, fmt: str, output_file: str | None, top: int, mode: st
|
|
|
90
90
|
format_file_table,
|
|
91
91
|
format_summary,
|
|
92
92
|
)
|
|
93
|
-
from pennyfarthing_scripts.hotspots.models import
|
|
93
|
+
from pennyfarthing_scripts.hotspots.models import MultiRepoHotspotResult
|
|
94
94
|
|
|
95
95
|
# Collect all repo results
|
|
96
96
|
if isinstance(result, MultiRepoHotspotResult):
|
|
@@ -20,6 +20,21 @@ Usage:
|
|
|
20
20
|
"""
|
|
21
21
|
|
|
22
22
|
# Re-export from client for backwards compatibility
|
|
23
|
+
# Import submodules to make them accessible
|
|
24
|
+
# CLI entry point - import module, not function, so "from jira import cli" gets the module
|
|
25
|
+
from pennyfarthing_scripts.jira import (
|
|
26
|
+
bidirectional,
|
|
27
|
+
claim,
|
|
28
|
+
cli,
|
|
29
|
+
client,
|
|
30
|
+
create,
|
|
31
|
+
epic,
|
|
32
|
+
operations,
|
|
33
|
+
reconcile,
|
|
34
|
+
story,
|
|
35
|
+
sync,
|
|
36
|
+
)
|
|
37
|
+
from pennyfarthing_scripts.jira.cli import main
|
|
23
38
|
from pennyfarthing_scripts.jira.client import (
|
|
24
39
|
# Constants
|
|
25
40
|
GITHUB_TO_JIRA_MAP,
|
|
@@ -44,23 +59,6 @@ from pennyfarthing_scripts.jira.client import (
|
|
|
44
59
|
update_issue_status,
|
|
45
60
|
)
|
|
46
61
|
|
|
47
|
-
# Import submodules to make them accessible
|
|
48
|
-
from pennyfarthing_scripts.jira import (
|
|
49
|
-
bidirectional,
|
|
50
|
-
claim,
|
|
51
|
-
client,
|
|
52
|
-
create,
|
|
53
|
-
epic,
|
|
54
|
-
operations,
|
|
55
|
-
reconcile,
|
|
56
|
-
story,
|
|
57
|
-
sync,
|
|
58
|
-
)
|
|
59
|
-
|
|
60
|
-
# CLI entry point - import module, not function, so "from jira import cli" gets the module
|
|
61
|
-
from pennyfarthing_scripts.jira import cli
|
|
62
|
-
from pennyfarthing_scripts.jira.cli import main
|
|
63
|
-
|
|
64
62
|
__all__ = [
|
|
65
63
|
# Constants
|
|
66
64
|
"GITHUB_TO_JIRA_MAP",
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -28,11 +28,10 @@ from dataclasses import dataclass, field
|
|
|
28
28
|
from pathlib import Path
|
|
29
29
|
from typing import Any, Literal
|
|
30
30
|
|
|
31
|
-
from pennyfarthing_scripts.jira.client import JiraClient, map_jira_to_status, map_status_to_jira
|
|
32
31
|
from pennyfarthing_scripts.common.output import error, info, success, warn
|
|
32
|
+
from pennyfarthing_scripts.jira.client import JiraClient, map_jira_to_status, map_status_to_jira
|
|
33
33
|
from pennyfarthing_scripts.sprint.loader import get_all_stories, load_sprint
|
|
34
34
|
|
|
35
|
-
|
|
36
35
|
# =============================================================================
|
|
37
36
|
# Data Classes
|
|
38
37
|
# =============================================================================
|
|
@@ -520,7 +519,7 @@ async def async_main(args: argparse.Namespace) -> int:
|
|
|
520
519
|
tasks = [client.get_issue_async(key) for key in jira_keys]
|
|
521
520
|
results = await asyncio.gather(*tasks, return_exceptions=True)
|
|
522
521
|
|
|
523
|
-
for key, result in zip(jira_keys, results):
|
|
522
|
+
for key, result in zip(jira_keys, results, strict=False):
|
|
524
523
|
if isinstance(result, Exception):
|
|
525
524
|
warn(f"Failed to fetch {key}: {result}")
|
|
526
525
|
elif result:
|
|
@@ -162,6 +162,27 @@ def claim_story(issue_key: str) -> dict[str, Any]:
|
|
|
162
162
|
"exit_code": 3,
|
|
163
163
|
}
|
|
164
164
|
|
|
165
|
+
# Update sprint YAML assigned_to field
|
|
166
|
+
try:
|
|
167
|
+
from pennyfarthing_scripts.common.config import get_project_root
|
|
168
|
+
from pennyfarthing_scripts.sprint.yaml_io import read_sprint, write_sprint
|
|
169
|
+
|
|
170
|
+
root = get_project_root()
|
|
171
|
+
sprint_path = root / "sprint" / "current-sprint.yaml"
|
|
172
|
+
if sprint_path.exists():
|
|
173
|
+
data = read_sprint(sprint_path)
|
|
174
|
+
for epic in data.get("epics", []):
|
|
175
|
+
if not isinstance(epic, dict):
|
|
176
|
+
continue
|
|
177
|
+
for story in epic.get("stories", []):
|
|
178
|
+
if story.get("jira") == issue_key:
|
|
179
|
+
story["assigned_to"] = current_user
|
|
180
|
+
write_sprint(sprint_path, data)
|
|
181
|
+
actions.append("Sprint YAML assigned_to set")
|
|
182
|
+
break
|
|
183
|
+
except Exception:
|
|
184
|
+
pass # Best-effort — Jira claim already succeeded
|
|
185
|
+
|
|
165
186
|
return {
|
|
166
187
|
"success": True,
|
|
167
188
|
"actions": actions,
|
|
@@ -206,7 +206,7 @@ def create_standalone(title, points, description, dry_run):
|
|
|
206
206
|
click.echo(f"[DRY-RUN] Would create: {title}")
|
|
207
207
|
click.echo(f" Points: {points}")
|
|
208
208
|
click.echo(f" Sprint: {sprint_id}")
|
|
209
|
-
click.echo(
|
|
209
|
+
click.echo(" Actions: create -> add to sprint -> transition to Done")
|
|
210
210
|
return
|
|
211
211
|
|
|
212
212
|
client = get_client()
|
|
@@ -241,7 +241,7 @@ def create_standalone(title, points, description, dry_run):
|
|
|
241
241
|
# 4. Transition to Done
|
|
242
242
|
result = client.transition_sync(jira_key, "Done")
|
|
243
243
|
if result.get("success"):
|
|
244
|
-
click.echo(
|
|
244
|
+
click.echo("Transitioned to Done")
|
|
245
245
|
else:
|
|
246
246
|
click.echo(f"Warning: could not transition to Done: {result.get('reason')}")
|
|
247
247
|
|
|
@@ -515,7 +515,7 @@ class JiraClient:
|
|
|
515
515
|
f"Available: {available}",
|
|
516
516
|
}
|
|
517
517
|
|
|
518
|
-
|
|
518
|
+
self._call_api_sync(
|
|
519
519
|
"POST",
|
|
520
520
|
f"/rest/api/3/issue/{issue_key}/transitions",
|
|
521
521
|
{"transition": {"id": transition_id}},
|
|
@@ -553,7 +553,7 @@ class JiraClient:
|
|
|
553
553
|
account_id = None
|
|
554
554
|
|
|
555
555
|
payload = {"accountId": account_id}
|
|
556
|
-
|
|
556
|
+
self._call_api_sync(
|
|
557
557
|
"PUT", f"/rest/api/3/issue/{issue_key}/assignee", payload
|
|
558
558
|
)
|
|
559
559
|
# Assign PUT returns empty body on success (204)
|
|
@@ -569,7 +569,7 @@ class JiraClient:
|
|
|
569
569
|
Returns:
|
|
570
570
|
Result dict with success status
|
|
571
571
|
"""
|
|
572
|
-
|
|
572
|
+
self._call_api_sync(
|
|
573
573
|
"POST",
|
|
574
574
|
f"/rest/agile/1.0/sprint/{sprint_id}/issue",
|
|
575
575
|
{"issues": [issue_key]},
|
|
@@ -619,7 +619,7 @@ class JiraClient:
|
|
|
619
619
|
Returns:
|
|
620
620
|
Result dict with success status
|
|
621
621
|
"""
|
|
622
|
-
|
|
622
|
+
self._call_api_sync(
|
|
623
623
|
"POST",
|
|
624
624
|
"/rest/api/3/issueLink",
|
|
625
625
|
{
|
|
@@ -16,7 +16,7 @@ import sys
|
|
|
16
16
|
from pathlib import Path
|
|
17
17
|
from typing import Any
|
|
18
18
|
|
|
19
|
-
from pennyfarthing_scripts.jira.client import JIRA_PROJECT,
|
|
19
|
+
from pennyfarthing_scripts.jira.client import JIRA_PROJECT, get_client
|
|
20
20
|
from pennyfarthing_scripts.sprint.loader import find_epic, find_story, load_sprint
|
|
21
21
|
from pennyfarthing_scripts.sprint.yaml_io import read_sprint, write_sprint
|
|
22
22
|
|
|
@@ -150,6 +150,7 @@ def create_epic_in_jira(
|
|
|
150
150
|
*,
|
|
151
151
|
sprint_path: Path | None = None,
|
|
152
152
|
dry_run: bool = False,
|
|
153
|
+
force: bool = False,
|
|
153
154
|
) -> dict[str, Any]:
|
|
154
155
|
"""Create a Jira epic and its child stories from sprint YAML.
|
|
155
156
|
|
|
@@ -157,6 +158,7 @@ def create_epic_in_jira(
|
|
|
157
158
|
epic_id: Epic ID from sprint YAML (e.g., "epic-63" or "63")
|
|
158
159
|
sprint_path: Path to sprint YAML (defaults to auto-detect)
|
|
159
160
|
dry_run: If True, preview without creating
|
|
161
|
+
force: If True, create even if duplicate title detected
|
|
160
162
|
|
|
161
163
|
Returns:
|
|
162
164
|
{success, epic_key?, stories: [{id, jira_key}], error?}
|
|
@@ -184,6 +186,13 @@ def create_epic_in_jira(
|
|
|
184
186
|
if not epic_ruamel:
|
|
185
187
|
return {"success": False, "error": f"Epic '{epic_id}' not found in ruamel data"}
|
|
186
188
|
|
|
189
|
+
# Validate epic shard before any Jira operations (ADR-0022)
|
|
190
|
+
from pennyfarthing_scripts.sprint.validator import validate_epic_shard
|
|
191
|
+
validation = validate_epic_shard(dict(epic_ruamel))
|
|
192
|
+
if not validation.valid:
|
|
193
|
+
error_msgs = "; ".join(e.message for e in validation.errors)
|
|
194
|
+
return {"success": False, "error": f"Epic validation failed: {error_msgs}"}
|
|
195
|
+
|
|
187
196
|
title = epic.get("title", f"Epic {epic_id}")
|
|
188
197
|
description = epic.get("description", "")
|
|
189
198
|
epic_jira_key = epic.get("jira")
|
|
@@ -200,6 +209,41 @@ def create_epic_in_jira(
|
|
|
200
209
|
epic_jira_key = "MSSCI-XXXXX"
|
|
201
210
|
else:
|
|
202
211
|
client = get_client()
|
|
212
|
+
|
|
213
|
+
# Idempotency check: search for existing epic with same title (ADR-0022)
|
|
214
|
+
try:
|
|
215
|
+
existing = client.search_issues_sync(
|
|
216
|
+
f'project = {JIRA_PROJECT} AND issuetype = Epic AND summary ~ "{title}"'
|
|
217
|
+
)
|
|
218
|
+
if existing and not force:
|
|
219
|
+
existing_key = existing[0]["key"]
|
|
220
|
+
print(f"Found existing epic with same title: {existing_key}")
|
|
221
|
+
epic_jira_key = existing_key
|
|
222
|
+
|
|
223
|
+
# Update YAML with existing key
|
|
224
|
+
epic_ruamel["jira"] = epic_jira_key
|
|
225
|
+
write_sprint(path, data)
|
|
226
|
+
|
|
227
|
+
return {
|
|
228
|
+
"success": True,
|
|
229
|
+
"epic_key": epic_jira_key,
|
|
230
|
+
"duplicate_detected": True,
|
|
231
|
+
"stories": [],
|
|
232
|
+
}
|
|
233
|
+
elif existing and force:
|
|
234
|
+
import warnings
|
|
235
|
+
warnings.warn(
|
|
236
|
+
f"Duplicate epic title detected ({existing[0]['key']}), "
|
|
237
|
+
"proceeding with --force",
|
|
238
|
+
stacklevel=2,
|
|
239
|
+
)
|
|
240
|
+
except Exception as exc:
|
|
241
|
+
import warnings
|
|
242
|
+
warnings.warn(
|
|
243
|
+
f"Jira search for duplicate titles failed: {exc}",
|
|
244
|
+
stacklevel=2,
|
|
245
|
+
)
|
|
246
|
+
|
|
203
247
|
payload = {
|
|
204
248
|
"fields": {
|
|
205
249
|
"project": {"key": JIRA_PROJECT},
|
|
@@ -17,8 +17,9 @@ import json
|
|
|
17
17
|
import sys
|
|
18
18
|
from typing import Any
|
|
19
19
|
|
|
20
|
-
from pennyfarthing_scripts.jira.client import
|
|
21
|
-
from pennyfarthing_scripts.sprint.loader import find_epic
|
|
20
|
+
from pennyfarthing_scripts.jira.client import JIRA_PROJECT, JiraClient
|
|
21
|
+
from pennyfarthing_scripts.sprint.loader import find_epic
|
|
22
|
+
from pennyfarthing_scripts.sprint.loader import load_sprint as load_current_sprint
|
|
22
23
|
|
|
23
24
|
|
|
24
25
|
def parse_args(args: list[str] | None = None) -> argparse.Namespace:
|
|
@@ -17,13 +17,13 @@ from dataclasses import dataclass, field
|
|
|
17
17
|
from typing import Any
|
|
18
18
|
|
|
19
19
|
from pennyfarthing_scripts.common.config import get_project_root
|
|
20
|
+
from pennyfarthing_scripts.common.output import error, info, success, warn
|
|
20
21
|
from pennyfarthing_scripts.jira.client import (
|
|
21
22
|
JiraClient,
|
|
22
23
|
extract_jira_key,
|
|
23
24
|
get_jira_field,
|
|
24
25
|
map_status_to_jira,
|
|
25
26
|
)
|
|
26
|
-
from pennyfarthing_scripts.common.output import error, info, success, warn
|
|
27
27
|
from pennyfarthing_scripts.sprint.loader import find_epic, load_sprint
|
|
28
28
|
|
|
29
29
|
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -10,7 +10,6 @@ import re
|
|
|
10
10
|
from dataclasses import dataclass, field
|
|
11
11
|
from pathlib import Path
|
|
12
12
|
|
|
13
|
-
|
|
14
13
|
# Tag requirements
|
|
15
14
|
REQUIRED_TAGS = ["step-meta", "purpose", "instructions", "output"]
|
|
16
15
|
RECOMMENDED_TAGS = ["prerequisites", "actions", "collaboration-menu", "next-step"]
|
|
@@ -20,12 +20,20 @@ from pennyfarthing_scripts.migration.session import (
|
|
|
20
20
|
)
|
|
21
21
|
from pennyfarthing_scripts.migration.skill import (
|
|
22
22
|
RECOMMENDED_TAGS as SKILL_RECOMMENDED,
|
|
23
|
+
)
|
|
24
|
+
from pennyfarthing_scripts.migration.skill import (
|
|
23
25
|
REQUIRED_TAGS as SKILL_REQUIRED,
|
|
26
|
+
)
|
|
27
|
+
from pennyfarthing_scripts.migration.skill import (
|
|
24
28
|
find_skill_files,
|
|
25
29
|
)
|
|
26
30
|
from pennyfarthing_scripts.migration.step import (
|
|
27
31
|
RECOMMENDED_TAGS as STEP_RECOMMENDED,
|
|
32
|
+
)
|
|
33
|
+
from pennyfarthing_scripts.migration.step import (
|
|
28
34
|
REQUIRED_TAGS as STEP_REQUIRED,
|
|
35
|
+
)
|
|
36
|
+
from pennyfarthing_scripts.migration.step import (
|
|
29
37
|
STEP_META_FIELDS,
|
|
30
38
|
find_step_files,
|
|
31
39
|
)
|
|
@@ -119,7 +127,6 @@ def validate_skill_file(file_path: Path) -> ValidationResult:
|
|
|
119
127
|
Returns:
|
|
120
128
|
ValidationResult with errors/warnings
|
|
121
129
|
"""
|
|
122
|
-
skill_name = file_path.parent.name
|
|
123
130
|
result = ValidationResult(file_path=file_path, file_type="skill")
|
|
124
131
|
content = file_path.read_text()
|
|
125
132
|
|
|
@@ -157,10 +164,6 @@ def validate_step_file(file_path: Path) -> ValidationResult:
|
|
|
157
164
|
result = ValidationResult(file_path=file_path, file_type="step")
|
|
158
165
|
content = file_path.read_text()
|
|
159
166
|
|
|
160
|
-
# Determine workflow/step names from path
|
|
161
|
-
workflow_name = file_path.parent.parent.name
|
|
162
|
-
step_name = file_path.stem
|
|
163
|
-
|
|
164
167
|
# Check required tags
|
|
165
168
|
for tag in STEP_REQUIRED:
|
|
166
169
|
if not _has_tag(content, tag):
|
|
@@ -13,7 +13,7 @@ from __future__ import annotations
|
|
|
13
13
|
import re
|
|
14
14
|
import subprocess
|
|
15
15
|
import time
|
|
16
|
-
from dataclasses import
|
|
16
|
+
from dataclasses import asdict, dataclass
|
|
17
17
|
from datetime import datetime
|
|
18
18
|
from pathlib import Path
|
|
19
19
|
from typing import Any
|
|
@@ -38,7 +38,7 @@ class PatchState:
|
|
|
38
38
|
return yaml.dump(asdict(self), default_flow_style=False)
|
|
39
39
|
|
|
40
40
|
@classmethod
|
|
41
|
-
def from_yaml(cls, yaml_str: str) ->
|
|
41
|
+
def from_yaml(cls, yaml_str: str) -> PatchState:
|
|
42
42
|
"""Deserialize state from YAML string."""
|
|
43
43
|
data = yaml.safe_load(yaml_str)
|
|
44
44
|
return cls(**data)
|