@pennyfarthing/core 10.0.5 → 10.1.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 +9 -7
- package/package.json +15 -11
- package/packages/core/dist/cli/commands/doctor.d.ts.map +1 -1
- package/packages/core/dist/cli/commands/doctor.js +251 -4
- package/packages/core/dist/cli/commands/doctor.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/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/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/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/orchestrator.md +0 -6
- package/pennyfarthing-dist/agents/pm.md +0 -6
- package/pennyfarthing-dist/agents/sm.md +0 -6
- 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 +1 -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-coordination.md +11 -13
- package/pennyfarthing-dist/guides/agent-template-tactical.md +2 -3
- 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/skill-schema.md +4 -4
- 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 +0 -0
- 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 +12 -5
- 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 +0 -0
- 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/test/ensure-swebench-data.sh +0 -0
- package/pennyfarthing-dist/scripts/test/swebench-judge.py +0 -0
- package/pennyfarthing-dist/scripts/test/test-cache.sh +0 -0
- package/pennyfarthing-dist/scripts/test/test-setup.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/compute-theme-tiers.sh +0 -0
- package/pennyfarthing-dist/scripts/theme/compute_theme_tiers.py +0 -0
- package/pennyfarthing-dist/scripts/theme/list-themes.sh +0 -0
- package/pennyfarthing-dist/scripts/theme/update-theme-tiers.sh +0 -0
- package/pennyfarthing-dist/scripts/validation/validate-agent-schema.sh +0 -1
- 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 +62 -17
- 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.yaml +20 -16
- 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 +4 -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/epics-and-stories/steps/step-05-import-to-future.md +41 -24
- package/pennyfarthing_scripts/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/hooks.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/cli.py +15 -0
- package/pennyfarthing_scripts/codemarkers/__init__.py +19 -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 +326 -0
- package/pennyfarthing_scripts/codemarkers/cli.py +129 -0
- package/pennyfarthing_scripts/codemarkers/formatters.py +89 -0
- package/pennyfarthing_scripts/codemarkers/models.py +45 -0
- package/pennyfarthing_scripts/common/__pycache__/config.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/common/__pycache__/themes.cpython-314.pyc +0 -0
- 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 +78 -0
- package/pennyfarthing_scripts/complexity/formatters.py +64 -0
- package/pennyfarthing_scripts/complexity/models.py +32 -0
- 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 +323 -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 +72 -0
- package/pennyfarthing_scripts/dependencies/formatters.py +63 -0
- package/pennyfarthing_scripts/dependencies/models.py +39 -0
- package/pennyfarthing_scripts/healthscore/__init__.py +21 -0
- package/pennyfarthing_scripts/healthscore/__main__.py +6 -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 +161 -0
- package/pennyfarthing_scripts/healthscore/cli.py +76 -0
- package/pennyfarthing_scripts/healthscore/formatters.py +46 -0
- package/pennyfarthing_scripts/healthscore/models.py +44 -0
- package/pennyfarthing_scripts/hooks/cyclist-pretooluse-hook.sh +0 -0
- package/pennyfarthing_scripts/hotspots/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/hotspots/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/hotspots/__pycache__/analyze.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/hotspots/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/hotspots/__pycache__/formatters.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/hotspots/__pycache__/models.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/hotspots/analyze.py +27 -0
- package/pennyfarthing_scripts/hotspots/cli.py +10 -8
- package/pennyfarthing_scripts/jira/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/bidirectional.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__/operations.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/reconcile.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/bidirectional.py +42 -15
- package/pennyfarthing_scripts/jira/cli.py +4 -1
- package/pennyfarthing_scripts/jira/client.py +28 -0
- package/pennyfarthing_scripts/prime/__pycache__/cli.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__/tiers.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/prime/__pycache__/workflow.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__/loader.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_epic.py +198 -94
- package/pennyfarthing_scripts/sprint/cli.py +29 -19
- package/pennyfarthing_scripts/sprint/story_add.py +202 -27
- package/pennyfarthing_scripts/sprint/story_finish.py +211 -0
- package/pennyfarthing_scripts/sprint/work.py +27 -3
- package/pennyfarthing_scripts/tests/__pycache__/test_codemarkers.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_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_validate_cmd.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_yaml_io.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/test_codemarkers.py +682 -0
- package/pennyfarthing_scripts/tests/test_healthscore.py +524 -0
- 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 +286 -0
- package/scripts/README.md +53 -0
- package/pennyfarthing-dist/agents/workflow-status-check.md +0 -96
|
@@ -5,6 +5,7 @@ Story: MSSCI-14256 - Sprint story add command
|
|
|
5
5
|
This module provides:
|
|
6
6
|
- generate_story_id(sprint_data, epic) -> str
|
|
7
7
|
- add_story(sprint_path, epic_id, title, points, ...) -> dict
|
|
8
|
+
- add_initiative_story(initiative_slug, title, points, ...) -> dict
|
|
8
9
|
- story_add_command (Click command for CLI registration)
|
|
9
10
|
"""
|
|
10
11
|
|
|
@@ -144,44 +145,218 @@ def add_story(
|
|
|
144
145
|
}
|
|
145
146
|
|
|
146
147
|
|
|
148
|
+
def _generate_initiative_story_id(init_data: dict[str, Any], slug: str) -> str:
|
|
149
|
+
"""Generate the next standalone story ID for an initiative.
|
|
150
|
+
|
|
151
|
+
Uses the pattern {slug-prefix}-{N} where slug-prefix is derived from
|
|
152
|
+
the initiative slug (e.g., "technical-debt" -> "td", "quality-scale" -> "qs").
|
|
153
|
+
Only counts existing stories that share the same prefix.
|
|
154
|
+
|
|
155
|
+
Args:
|
|
156
|
+
init_data: Initiative YAML data
|
|
157
|
+
slug: Initiative slug (e.g., "technical-debt")
|
|
158
|
+
|
|
159
|
+
Returns:
|
|
160
|
+
Next story ID string (e.g., "td-2")
|
|
161
|
+
"""
|
|
162
|
+
# Build prefix from initiative slug initials
|
|
163
|
+
parts = slug.split("-")
|
|
164
|
+
prefix = "".join(p[0] for p in parts if p)
|
|
165
|
+
|
|
166
|
+
stories = init_data.get("standalone_stories", [])
|
|
167
|
+
max_seq = 0
|
|
168
|
+
for story in stories:
|
|
169
|
+
story_id = str(story.get("id", ""))
|
|
170
|
+
# Only count stories with matching prefix
|
|
171
|
+
if not story_id.startswith(f"{prefix}-"):
|
|
172
|
+
continue
|
|
173
|
+
suffix = story_id[len(prefix) + 1:]
|
|
174
|
+
try:
|
|
175
|
+
seq = int(suffix)
|
|
176
|
+
if seq > max_seq:
|
|
177
|
+
max_seq = seq
|
|
178
|
+
except ValueError:
|
|
179
|
+
pass
|
|
180
|
+
|
|
181
|
+
return f"{prefix}-{max_seq + 1}"
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def add_initiative_story(
|
|
185
|
+
initiative_slug: str,
|
|
186
|
+
title: str,
|
|
187
|
+
points: int,
|
|
188
|
+
*,
|
|
189
|
+
story_type: str | None = None,
|
|
190
|
+
priority: str = "P1",
|
|
191
|
+
workflow: str = "tdd",
|
|
192
|
+
jira: str | None = None,
|
|
193
|
+
repos: str = "pennyfarthing",
|
|
194
|
+
) -> dict[str, Any]:
|
|
195
|
+
"""Add a standalone story to an initiative YAML file.
|
|
196
|
+
|
|
197
|
+
Args:
|
|
198
|
+
initiative_slug: Initiative slug (e.g., "technical-debt")
|
|
199
|
+
title: Story title
|
|
200
|
+
points: Story points
|
|
201
|
+
story_type: Optional story type (feature, bug, chore, refactor)
|
|
202
|
+
priority: Priority (default: P1)
|
|
203
|
+
workflow: Workflow (default: tdd)
|
|
204
|
+
jira: Optional Jira key
|
|
205
|
+
repos: Repos (default: pennyfarthing)
|
|
206
|
+
|
|
207
|
+
Returns:
|
|
208
|
+
Dict with success status and story_id or error
|
|
209
|
+
"""
|
|
210
|
+
from pennyfarthing_scripts.common.config import get_project_root
|
|
211
|
+
|
|
212
|
+
root = get_project_root()
|
|
213
|
+
init_path = root / "sprint" / f"initiative-{initiative_slug}.yaml"
|
|
214
|
+
|
|
215
|
+
if not init_path.exists():
|
|
216
|
+
available = [
|
|
217
|
+
f.stem.replace("initiative-", "")
|
|
218
|
+
for f in (root / "sprint").glob("initiative-*.yaml")
|
|
219
|
+
]
|
|
220
|
+
return {
|
|
221
|
+
"success": False,
|
|
222
|
+
"error": f"Initiative '{initiative_slug}' not found. Available: {', '.join(sorted(available))}",
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
# Use ruamel.yaml to preserve block scalars and formatting
|
|
226
|
+
from ruamel.yaml import YAML as RuamelYAML
|
|
227
|
+
|
|
228
|
+
ryml = RuamelYAML()
|
|
229
|
+
ryml.preserve_quotes = True
|
|
230
|
+
ryml.default_flow_style = False
|
|
231
|
+
ryml.indent(mapping=2, sequence=4, offset=2)
|
|
232
|
+
ryml.width = 4096
|
|
233
|
+
|
|
234
|
+
with open(init_path) as f:
|
|
235
|
+
init_data = ryml.load(f)
|
|
236
|
+
|
|
237
|
+
if not init_data:
|
|
238
|
+
return {"success": False, "error": f"Empty initiative file: {init_path}"}
|
|
239
|
+
|
|
240
|
+
story_id = _generate_initiative_story_id(init_data, initiative_slug)
|
|
241
|
+
|
|
242
|
+
story: dict[str, Any] = {
|
|
243
|
+
"id": story_id,
|
|
244
|
+
"title": title,
|
|
245
|
+
"points": points,
|
|
246
|
+
"priority": priority,
|
|
247
|
+
"status": "backlog",
|
|
248
|
+
"repos": repos,
|
|
249
|
+
"workflow": workflow,
|
|
250
|
+
}
|
|
251
|
+
if jira is not None:
|
|
252
|
+
story["jira"] = jira
|
|
253
|
+
if story_type is not None:
|
|
254
|
+
story["type"] = story_type
|
|
255
|
+
|
|
256
|
+
if "standalone_stories" not in init_data:
|
|
257
|
+
init_data["standalone_stories"] = []
|
|
258
|
+
init_data["standalone_stories"].append(story)
|
|
259
|
+
|
|
260
|
+
# Update total_points
|
|
261
|
+
current_total = init_data.get("total_points", 0) or 0
|
|
262
|
+
init_data["total_points"] = current_total + points
|
|
263
|
+
|
|
264
|
+
with open(init_path, "w") as f:
|
|
265
|
+
ryml.dump(init_data, f)
|
|
266
|
+
|
|
267
|
+
return {
|
|
268
|
+
"success": True,
|
|
269
|
+
"story_id": story_id,
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
|
|
147
273
|
@click.command("add")
|
|
148
|
-
@click.argument("epic_id", type=str)
|
|
149
|
-
@click.argument("title", type=str)
|
|
150
|
-
@click.argument("points", type=int)
|
|
274
|
+
@click.argument("epic_id", type=str, required=False)
|
|
275
|
+
@click.argument("title", type=str, required=False)
|
|
276
|
+
@click.argument("points", type=int, required=False)
|
|
151
277
|
@click.option("--type", "story_type", type=click.Choice(["feature", "bug", "chore", "refactor"]), default="feature")
|
|
152
278
|
@click.option("--priority", type=click.Choice(["P0", "P1", "P2", "P3"]), default="P1")
|
|
153
279
|
@click.option("--workflow", type=click.Choice(["tdd", "trivial", "bdd"]), default="tdd")
|
|
154
280
|
@click.option("--jira", "jira_id", type=str, default=None)
|
|
155
281
|
@click.option("--sprint-file", type=click.Path(), default=None, help="Path to sprint YAML file")
|
|
282
|
+
@click.option("--initiative", type=str, default=None, help="Add as standalone story to initiative (e.g., technical-debt)")
|
|
283
|
+
@click.option("--repos", type=str, default="pennyfarthing", help="Repos (default: pennyfarthing)")
|
|
156
284
|
def story_add_command(
|
|
157
|
-
epic_id: str,
|
|
158
|
-
title: str,
|
|
159
|
-
points: int,
|
|
285
|
+
epic_id: str | None,
|
|
286
|
+
title: str | None,
|
|
287
|
+
points: int | None,
|
|
160
288
|
story_type: str,
|
|
161
289
|
priority: str,
|
|
162
290
|
workflow: str,
|
|
163
291
|
jira_id: str | None,
|
|
164
292
|
sprint_file: str | None,
|
|
293
|
+
initiative: str | None,
|
|
294
|
+
repos: str,
|
|
165
295
|
) -> None:
|
|
166
|
-
"""Add a new story to an epic.
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
title
|
|
177
|
-
points
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
296
|
+
"""Add a new story to an epic or initiative.
|
|
297
|
+
|
|
298
|
+
\b
|
|
299
|
+
Epic mode (default):
|
|
300
|
+
pf sprint story add <EPIC_ID> <TITLE> <POINTS>
|
|
301
|
+
|
|
302
|
+
Initiative mode (--initiative):
|
|
303
|
+
pf sprint story add --initiative <SLUG> <TITLE> <POINTS>
|
|
304
|
+
"""
|
|
305
|
+
if initiative:
|
|
306
|
+
# Initiative mode: first positional arg is title, second is points
|
|
307
|
+
# epic_id absorbs the title, title absorbs points (as str)
|
|
308
|
+
if epic_id is None:
|
|
309
|
+
raise click.ClickException("TITLE is required")
|
|
310
|
+
init_title = epic_id
|
|
311
|
+
if title is None:
|
|
312
|
+
raise click.ClickException("POINTS is required")
|
|
313
|
+
try:
|
|
314
|
+
init_points = int(title)
|
|
315
|
+
except ValueError:
|
|
316
|
+
raise click.ClickException(f"POINTS must be an integer, got '{title}'")
|
|
317
|
+
|
|
318
|
+
result = add_initiative_story(
|
|
319
|
+
initiative_slug=initiative,
|
|
320
|
+
title=init_title,
|
|
321
|
+
points=init_points,
|
|
322
|
+
story_type=story_type if story_type != "feature" else None,
|
|
323
|
+
priority=priority,
|
|
324
|
+
workflow=workflow,
|
|
325
|
+
jira=jira_id,
|
|
326
|
+
repos=repos,
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
if result["success"]:
|
|
330
|
+
click.echo(f"Added story {result['story_id']}: {init_title} [{init_points}pts] to initiative {initiative}")
|
|
331
|
+
else:
|
|
332
|
+
raise click.ClickException(result["error"])
|
|
186
333
|
else:
|
|
187
|
-
|
|
334
|
+
# Epic mode: all three positional args required
|
|
335
|
+
if epic_id is None:
|
|
336
|
+
raise click.ClickException("EPIC_ID is required")
|
|
337
|
+
if title is None:
|
|
338
|
+
raise click.ClickException("TITLE is required")
|
|
339
|
+
if points is None:
|
|
340
|
+
raise click.ClickException("POINTS is required")
|
|
341
|
+
|
|
342
|
+
if sprint_file is None:
|
|
343
|
+
from pennyfarthing_scripts.common.config import get_project_root
|
|
344
|
+
path = get_project_root() / "sprint" / "current-sprint.yaml"
|
|
345
|
+
else:
|
|
346
|
+
path = Path(sprint_file)
|
|
347
|
+
|
|
348
|
+
result = add_story(
|
|
349
|
+
sprint_path=path,
|
|
350
|
+
epic_id=epic_id,
|
|
351
|
+
title=title,
|
|
352
|
+
points=points,
|
|
353
|
+
story_type=story_type if story_type != "feature" else None,
|
|
354
|
+
priority=priority,
|
|
355
|
+
workflow=workflow,
|
|
356
|
+
jira=jira_id,
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
if result["success"]:
|
|
360
|
+
click.echo(f"Added story {result['story_id']}: {title} [{points}pts]")
|
|
361
|
+
else:
|
|
362
|
+
raise click.ClickException(result["error"])
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
"""Finish a completed story: archive, merge PR, update Jira, update YAML.
|
|
2
|
+
|
|
3
|
+
Replaces finish-story.sh with native Python that correctly handles
|
|
4
|
+
sharded epic YAML files via read_sprint/write_sprint.
|
|
5
|
+
|
|
6
|
+
Steps:
|
|
7
|
+
1. Archive session file to sprint/archive/{jira-key}-session.md
|
|
8
|
+
2. Squash merge PR via gh (handle already-merged)
|
|
9
|
+
3. Transition Jira to Done
|
|
10
|
+
4. Update sprint YAML (status: done, completed date, remove assigned_to)
|
|
11
|
+
5. Archive completed epics
|
|
12
|
+
6. Git cleanup (checkout develop, pull, delete local branch)
|
|
13
|
+
7. Remove session file
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import re
|
|
17
|
+
import shutil
|
|
18
|
+
import subprocess
|
|
19
|
+
from datetime import date
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
from typing import Any
|
|
22
|
+
|
|
23
|
+
from pennyfarthing_scripts.sprint.loader import find_epic, find_story
|
|
24
|
+
from pennyfarthing_scripts.sprint.yaml_io import read_sprint, write_sprint
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
SESSION_FIELD_RE = re.compile(r"\*\*(\w[\w\s]*):\*\*\s*(.*)")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _parse_session(session_path: Path) -> dict[str, str]:
|
|
31
|
+
"""Extract metadata fields from a session markdown file.
|
|
32
|
+
|
|
33
|
+
Parses lines like ``**Jira:** MSSCI-14467`` and
|
|
34
|
+
``**PR:** #748 - title`` into a dict.
|
|
35
|
+
"""
|
|
36
|
+
fields: dict[str, str] = {}
|
|
37
|
+
if not session_path.exists():
|
|
38
|
+
return fields
|
|
39
|
+
for line in session_path.read_text().splitlines():
|
|
40
|
+
m = SESSION_FIELD_RE.search(line)
|
|
41
|
+
if m:
|
|
42
|
+
key = m.group(1).strip().lower()
|
|
43
|
+
value = m.group(2).strip()
|
|
44
|
+
fields[key] = value
|
|
45
|
+
return fields
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _extract_jira_key(fields: dict[str, str]) -> str | None:
|
|
49
|
+
"""Get Jira key from session fields, handling markdown link format."""
|
|
50
|
+
raw = fields.get("jira", "")
|
|
51
|
+
# Strip markdown link: [MSSCI-14467](https://...)
|
|
52
|
+
raw = re.sub(r"\[([^\]]+)\].*", r"\1", raw).strip()
|
|
53
|
+
if re.match(r"^MSSCI-\d+$", raw):
|
|
54
|
+
return raw
|
|
55
|
+
return None
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _extract_pr_number(fields: dict[str, str]) -> str | None:
|
|
59
|
+
"""Get PR number from session fields like ``#748 - title``."""
|
|
60
|
+
raw = fields.get("pr", "")
|
|
61
|
+
m = re.search(r"#(\d+)", raw)
|
|
62
|
+
return m.group(1) if m else None
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _extract_branch(fields: dict[str, str]) -> str | None:
|
|
66
|
+
"""Get branch name, stripping trailing annotations like ``(pushed)``."""
|
|
67
|
+
raw = fields.get("branch", "")
|
|
68
|
+
return re.sub(r"\s*\(.*\)\s*$", "", raw).strip() or None
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _run(cmd: list[str], **kwargs: Any) -> subprocess.CompletedProcess[str]:
|
|
72
|
+
"""Run a subprocess with sane defaults."""
|
|
73
|
+
return subprocess.run(cmd, capture_output=True, text=True, **kwargs)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def finish_story(
|
|
77
|
+
project_root: Path,
|
|
78
|
+
story_id: str,
|
|
79
|
+
*,
|
|
80
|
+
dry_run: bool = False,
|
|
81
|
+
) -> dict[str, Any]:
|
|
82
|
+
"""Finish a story: archive, merge, update Jira, update YAML, clean up.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
project_root: Project root directory.
|
|
86
|
+
story_id: Story ID (e.g., "83-2").
|
|
87
|
+
dry_run: If True, report what would happen without side-effects.
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
Result dict ``{success, data?, error?, steps?}``.
|
|
91
|
+
"""
|
|
92
|
+
session_path = project_root / ".session" / f"{story_id}-session.md"
|
|
93
|
+
sprint_path = project_root / "sprint" / "current-sprint.yaml"
|
|
94
|
+
archive_dir = project_root / "sprint" / "archive"
|
|
95
|
+
archive_dir.mkdir(parents=True, exist_ok=True)
|
|
96
|
+
|
|
97
|
+
# --- Validate session ---
|
|
98
|
+
if not session_path.exists():
|
|
99
|
+
return {"success": False, "error": f"Session file not found: {session_path}"}
|
|
100
|
+
|
|
101
|
+
fields = _parse_session(session_path)
|
|
102
|
+
jira_key = _extract_jira_key(fields)
|
|
103
|
+
branch = _extract_branch(fields)
|
|
104
|
+
pr_number = _extract_pr_number(fields)
|
|
105
|
+
|
|
106
|
+
# Fallback: resolve Jira key from sprint YAML
|
|
107
|
+
if not jira_key:
|
|
108
|
+
try:
|
|
109
|
+
data = read_sprint(sprint_path)
|
|
110
|
+
parts = story_id.split("-")
|
|
111
|
+
if len(parts) >= 2:
|
|
112
|
+
epic = find_epic(data, parts[0])
|
|
113
|
+
story = find_story(epic, story_id) if epic else None
|
|
114
|
+
if story:
|
|
115
|
+
jira_key = story.get("jira")
|
|
116
|
+
except Exception:
|
|
117
|
+
pass
|
|
118
|
+
|
|
119
|
+
if not jira_key:
|
|
120
|
+
return {"success": False, "error": f"Could not determine Jira key for {story_id}"}
|
|
121
|
+
|
|
122
|
+
# Fallback: resolve PR from GitHub if not in session
|
|
123
|
+
if not pr_number and branch:
|
|
124
|
+
result = _run(["gh", "pr", "list", "--head", branch, "--json", "number", "--jq", ".[0].number"])
|
|
125
|
+
if result.returncode == 0 and result.stdout.strip():
|
|
126
|
+
pr_number = result.stdout.strip()
|
|
127
|
+
|
|
128
|
+
today = date.today().isoformat()
|
|
129
|
+
steps: list[dict[str, Any]] = []
|
|
130
|
+
|
|
131
|
+
if dry_run:
|
|
132
|
+
steps.append({"step": 1, "action": f"Archive session → {archive_dir / f'{jira_key}-session.md'}"})
|
|
133
|
+
if pr_number:
|
|
134
|
+
steps.append({"step": 2, "action": f"Merge PR #{pr_number} (squash, delete branch)"})
|
|
135
|
+
else:
|
|
136
|
+
steps.append({"step": 2, "action": "No PR to merge"})
|
|
137
|
+
steps.append({"step": 3, "action": f"Transition {jira_key} to Done"})
|
|
138
|
+
steps.append({"step": 4, "action": f"Update sprint YAML (status: done, completed: {today})"})
|
|
139
|
+
steps.append({"step": 5, "action": "Archive completed epics"})
|
|
140
|
+
steps.append({"step": 6, "action": f"Delete local branch: {branch}"})
|
|
141
|
+
steps.append({"step": 7, "action": "Remove session file"})
|
|
142
|
+
return {"success": True, "dry_run": True, "jira_key": jira_key, "steps": steps}
|
|
143
|
+
|
|
144
|
+
# --- Step 1: Archive session ---
|
|
145
|
+
archive_dest = archive_dir / f"{jira_key}-session.md"
|
|
146
|
+
shutil.copy2(session_path, archive_dest)
|
|
147
|
+
steps.append({"step": 1, "action": "archive_session", "dest": str(archive_dest)})
|
|
148
|
+
|
|
149
|
+
# --- Step 2: Merge PR ---
|
|
150
|
+
if pr_number:
|
|
151
|
+
result = _run(["gh", "pr", "merge", pr_number, "--squash", "--delete-branch"])
|
|
152
|
+
if result.returncode == 0:
|
|
153
|
+
steps.append({"step": 2, "action": "merge_pr", "pr": pr_number})
|
|
154
|
+
else:
|
|
155
|
+
steps.append({"step": 2, "action": "merge_pr", "pr": pr_number, "warning": "Already merged or failed"})
|
|
156
|
+
else:
|
|
157
|
+
steps.append({"step": 2, "action": "merge_pr", "skipped": True})
|
|
158
|
+
|
|
159
|
+
# --- Step 3: Transition Jira ---
|
|
160
|
+
result = _run(["jira", "issue", "move", jira_key, "Done"])
|
|
161
|
+
if result.returncode == 0:
|
|
162
|
+
steps.append({"step": 3, "action": "jira_done", "key": jira_key})
|
|
163
|
+
else:
|
|
164
|
+
steps.append({"step": 3, "action": "jira_done", "key": jira_key, "warning": "Already Done or failed"})
|
|
165
|
+
|
|
166
|
+
# --- Step 4: Update sprint YAML ---
|
|
167
|
+
try:
|
|
168
|
+
data = read_sprint(sprint_path)
|
|
169
|
+
parts = story_id.split("-")
|
|
170
|
+
if len(parts) < 2:
|
|
171
|
+
steps.append({"step": 4, "action": "yaml_update", "error": f"Invalid story ID: {story_id}"})
|
|
172
|
+
else:
|
|
173
|
+
epic = find_epic(data, parts[0])
|
|
174
|
+
story = find_story(epic, story_id) if epic else None
|
|
175
|
+
if story:
|
|
176
|
+
story["status"] = "done"
|
|
177
|
+
story["completed"] = today
|
|
178
|
+
if "assigned_to" in story:
|
|
179
|
+
del story["assigned_to"]
|
|
180
|
+
write_sprint(sprint_path, data)
|
|
181
|
+
steps.append({"step": 4, "action": "yaml_update", "status": "done", "completed": today})
|
|
182
|
+
else:
|
|
183
|
+
steps.append({"step": 4, "action": "yaml_update", "warning": f"Story {story_id} not found in YAML"})
|
|
184
|
+
except Exception as exc:
|
|
185
|
+
steps.append({"step": 4, "action": "yaml_update", "error": str(exc)})
|
|
186
|
+
|
|
187
|
+
# --- Step 5: Archive completed epics ---
|
|
188
|
+
result = _run(
|
|
189
|
+
["python", "-m", "pennyfarthing_scripts.cli", "sprint", "epic", "archive"],
|
|
190
|
+
cwd=str(project_root),
|
|
191
|
+
)
|
|
192
|
+
steps.append({"step": 5, "action": "archive_epics", "ran": True})
|
|
193
|
+
|
|
194
|
+
# --- Step 6: Git cleanup ---
|
|
195
|
+
_run(["git", "checkout", "develop"], cwd=str(project_root))
|
|
196
|
+
_run(["git", "pull", "origin", "develop"], cwd=str(project_root))
|
|
197
|
+
if branch:
|
|
198
|
+
_run(["git", "branch", "-d", branch], cwd=str(project_root))
|
|
199
|
+
steps.append({"step": 6, "action": "git_cleanup", "branch": branch})
|
|
200
|
+
|
|
201
|
+
# --- Step 7: Remove session file ---
|
|
202
|
+
if session_path.exists():
|
|
203
|
+
session_path.unlink()
|
|
204
|
+
steps.append({"step": 7, "action": "remove_session"})
|
|
205
|
+
|
|
206
|
+
return {
|
|
207
|
+
"success": True,
|
|
208
|
+
"story_id": story_id,
|
|
209
|
+
"jira_key": jira_key,
|
|
210
|
+
"steps": steps,
|
|
211
|
+
}
|
|
@@ -35,6 +35,20 @@ def check_story(story_id: str) -> dict[str, Any]:
|
|
|
35
35
|
status = story.get("status", "backlog")
|
|
36
36
|
assigned = story.get("assigned_to")
|
|
37
37
|
|
|
38
|
+
# Check if assigned to someone else
|
|
39
|
+
if assigned:
|
|
40
|
+
from pennyfarthing_scripts.jira.client import get_current_user_email
|
|
41
|
+
|
|
42
|
+
current_user = get_current_user_email()
|
|
43
|
+
if assigned != current_user:
|
|
44
|
+
return {
|
|
45
|
+
"available": False,
|
|
46
|
+
"type": "story",
|
|
47
|
+
"story": story,
|
|
48
|
+
"reason": f"Assigned to {assigned}",
|
|
49
|
+
"assigned_to": assigned,
|
|
50
|
+
}
|
|
51
|
+
|
|
38
52
|
# Check if already in progress
|
|
39
53
|
if status == "in_progress":
|
|
40
54
|
return {
|
|
@@ -78,15 +92,22 @@ def get_next_story() -> dict[str, Any]:
|
|
|
78
92
|
"""Get the highest priority available story.
|
|
79
93
|
|
|
80
94
|
Considers stories with backlog, ready, or planning status.
|
|
95
|
+
Excludes stories assigned to other users.
|
|
81
96
|
|
|
82
97
|
Returns:
|
|
83
98
|
Dict with next story details or error
|
|
84
99
|
"""
|
|
100
|
+
from pennyfarthing_scripts.jira.client import get_current_user_email
|
|
85
101
|
from pennyfarthing_scripts.sprint.loader import get_all_stories
|
|
86
102
|
|
|
103
|
+
current_user = get_current_user_email()
|
|
87
104
|
all_stories = get_all_stories()
|
|
88
105
|
available_statuses = {"backlog", "ready", "planning"}
|
|
89
|
-
backlog = [
|
|
106
|
+
backlog = [
|
|
107
|
+
s for s in all_stories
|
|
108
|
+
if s.get("status") in available_statuses
|
|
109
|
+
and (not s.get("assigned_to") or s.get("assigned_to") == current_user)
|
|
110
|
+
]
|
|
90
111
|
|
|
91
112
|
if not backlog:
|
|
92
113
|
return {
|
|
@@ -94,11 +115,14 @@ def get_next_story() -> dict[str, Any]:
|
|
|
94
115
|
"error": "No stories in backlog",
|
|
95
116
|
}
|
|
96
117
|
|
|
97
|
-
# Sort by priority (P0 > P1 > P2 > P3)
|
|
118
|
+
# Sort by priority (P0 > P1 > P2 > P3), preferring own assignments
|
|
98
119
|
priority_order = {"P0": 0, "P1": 1, "P2": 2, "P3": 3}
|
|
99
120
|
sorted_stories = sorted(
|
|
100
121
|
backlog,
|
|
101
|
-
key=lambda s:
|
|
122
|
+
key=lambda s: (
|
|
123
|
+
0 if s.get("assigned_to") == current_user else 1,
|
|
124
|
+
priority_order.get(s.get("priority", "P2"), 2),
|
|
125
|
+
),
|
|
102
126
|
)
|
|
103
127
|
|
|
104
128
|
next_story = sorted_stories[0]
|
|
Binary file
|
|
Binary file
|
package/pennyfarthing_scripts/tests/__pycache__/test_sprint_package.cpython-314-pytest-9.0.2.pyc
CHANGED
|
Binary file
|
package/pennyfarthing_scripts/tests/__pycache__/test_sprint_validator.cpython-314-pytest-9.0.2.pyc
CHANGED
|
Binary file
|