@pennyfarthing/core 10.0.5 → 10.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -22
- package/package.json +17 -11
- package/packages/core/dist/cli/commands/doctor-file-layout.test.js.map +1 -1
- package/packages/core/dist/cli/commands/doctor-legacy.test.js +24 -0
- package/packages/core/dist/cli/commands/doctor-legacy.test.js.map +1 -1
- package/packages/core/dist/cli/commands/doctor.d.ts.map +1 -1
- package/packages/core/dist/cli/commands/doctor.js +346 -13
- package/packages/core/dist/cli/commands/doctor.js.map +1 -1
- package/packages/core/dist/cli/commands/e2e-fresh-install.test.js +1 -1
- package/packages/core/dist/cli/commands/e2e-fresh-install.test.js.map +1 -1
- package/packages/core/dist/cli/commands/e2e-upgrade.test.js +1 -1
- package/packages/core/dist/cli/commands/e2e-upgrade.test.js.map +1 -1
- package/packages/core/dist/cli/commands/hooks-consolidation.test.js +2 -2
- package/packages/core/dist/cli/commands/hooks-consolidation.test.js.map +1 -1
- package/packages/core/dist/cli/commands/init-consolidation.test.js.map +1 -1
- package/packages/core/dist/cli/commands/init.d.ts +7 -0
- package/packages/core/dist/cli/commands/init.d.ts.map +1 -1
- package/packages/core/dist/cli/commands/init.js +41 -8
- package/packages/core/dist/cli/commands/init.js.map +1 -1
- package/packages/core/dist/cli/commands/uninstall.d.ts.map +1 -1
- package/packages/core/dist/cli/commands/uninstall.js +24 -13
- package/packages/core/dist/cli/commands/uninstall.js.map +1 -1
- package/packages/core/dist/cli/commands/update-consolidation.test.js +0 -10
- package/packages/core/dist/cli/commands/update-consolidation.test.js.map +1 -1
- package/packages/core/dist/cli/commands/update.d.ts.map +1 -1
- package/packages/core/dist/cli/commands/update.js +26 -0
- package/packages/core/dist/cli/commands/update.js.map +1 -1
- package/packages/core/dist/cli/index.js +1 -1
- package/packages/core/dist/cli/index.js.map +1 -1
- package/packages/core/dist/cli/ocean-profiles.test.js.map +1 -1
- package/packages/core/dist/cli/theme-maker.test.js +64 -115
- package/packages/core/dist/cli/theme-maker.test.js.map +1 -1
- package/packages/core/dist/cli/utils/python.d.ts +22 -0
- package/packages/core/dist/cli/utils/python.d.ts.map +1 -0
- package/packages/core/dist/cli/utils/python.js +102 -0
- package/packages/core/dist/cli/utils/python.js.map +1 -0
- package/packages/core/dist/cli/utils/settings.d.ts.map +1 -1
- package/packages/core/dist/cli/utils/settings.js +10 -0
- package/packages/core/dist/cli/utils/settings.js.map +1 -1
- package/packages/core/dist/index.d.ts +1 -1
- package/packages/core/dist/index.d.ts.map +1 -1
- package/packages/core/dist/index.js +2 -2
- package/packages/core/dist/index.js.map +1 -1
- package/packages/core/dist/plugins/plugin-discovery.d.ts +116 -0
- package/packages/core/dist/plugins/plugin-discovery.d.ts.map +1 -0
- package/packages/core/dist/plugins/plugin-discovery.js +165 -0
- package/packages/core/dist/plugins/plugin-discovery.js.map +1 -0
- package/packages/core/dist/plugins/plugin-discovery.test.d.ts +22 -0
- package/packages/core/dist/plugins/plugin-discovery.test.d.ts.map +1 -0
- package/packages/core/dist/plugins/plugin-discovery.test.js +498 -0
- package/packages/core/dist/plugins/plugin-discovery.test.js.map +1 -0
- package/packages/core/dist/scripts/generate-spider-report.js.map +1 -1
- package/packages/core/dist/workflow/context-watch.d.ts +80 -0
- package/packages/core/dist/workflow/context-watch.d.ts.map +1 -0
- package/packages/core/dist/workflow/context-watch.js +235 -0
- package/packages/core/dist/workflow/context-watch.js.map +1 -0
- package/packages/core/dist/workflow/context-watch.test.d.ts +1 -0
- package/packages/core/dist/workflow/context-watch.test.d.ts.map +1 -0
- package/packages/core/dist/workflow/context-watch.test.js +746 -0
- package/packages/core/dist/workflow/context-watch.test.js.map +1 -0
- package/packages/core/dist/workflow/file-watch.d.ts +82 -0
- package/packages/core/dist/workflow/file-watch.d.ts.map +1 -0
- package/packages/core/dist/workflow/file-watch.js +198 -0
- package/packages/core/dist/workflow/file-watch.js.map +1 -0
- package/packages/core/dist/workflow/file-watch.test.d.ts +21 -0
- package/packages/core/dist/workflow/file-watch.test.d.ts.map +1 -0
- package/packages/core/dist/workflow/file-watch.test.js +469 -0
- package/packages/core/dist/workflow/file-watch.test.js.map +1 -0
- package/packages/core/dist/workflow/observation-writer.d.ts +79 -0
- package/packages/core/dist/workflow/observation-writer.d.ts.map +1 -0
- package/packages/core/dist/workflow/observation-writer.js +97 -0
- package/packages/core/dist/workflow/observation-writer.js.map +1 -0
- package/packages/core/dist/workflow/observation-writer.test.d.ts +18 -0
- package/packages/core/dist/workflow/observation-writer.test.d.ts.map +1 -0
- package/packages/core/dist/workflow/observation-writer.test.js +424 -0
- package/packages/core/dist/workflow/observation-writer.test.js.map +1 -0
- package/packages/core/dist/workflow/story-workflow-routing.test.js +4 -2
- package/packages/core/dist/workflow/story-workflow-routing.test.js.map +1 -1
- package/packages/core/dist/workflow/tandem-lifecycle.d.ts +117 -0
- package/packages/core/dist/workflow/tandem-lifecycle.d.ts.map +1 -0
- package/packages/core/dist/workflow/tandem-lifecycle.js +186 -0
- package/packages/core/dist/workflow/tandem-lifecycle.js.map +1 -0
- package/packages/core/dist/workflow/tandem-lifecycle.test.d.ts +16 -0
- package/packages/core/dist/workflow/tandem-lifecycle.test.d.ts.map +1 -0
- package/packages/core/dist/workflow/tandem-lifecycle.test.js +531 -0
- package/packages/core/dist/workflow/tandem-lifecycle.test.js.map +1 -0
- package/packages/core/dist/workflow/tool-watch.d.ts +68 -0
- package/packages/core/dist/workflow/tool-watch.d.ts.map +1 -0
- package/packages/core/dist/workflow/tool-watch.js +166 -0
- package/packages/core/dist/workflow/tool-watch.js.map +1 -0
- package/packages/core/dist/workflow/tool-watch.test.d.ts +18 -0
- package/packages/core/dist/workflow/tool-watch.test.d.ts.map +1 -0
- package/packages/core/dist/workflow/tool-watch.test.js +718 -0
- package/packages/core/dist/workflow/tool-watch.test.js.map +1 -0
- package/packages/core/dist/workflow/workflow-migration.test.js +8 -4
- package/packages/core/dist/workflow/workflow-migration.test.js.map +1 -1
- package/packages/core/dist/workflow/workflow-schema.d.ts +7 -0
- package/packages/core/dist/workflow/workflow-schema.d.ts.map +1 -1
- package/packages/core/dist/workflow/workflow-schema.js +44 -0
- package/packages/core/dist/workflow/workflow-schema.js.map +1 -1
- package/packages/core/dist/workflow/workflow-schema.test.d.ts.map +1 -1
- package/packages/core/dist/workflow/workflow-schema.test.js +192 -0
- package/packages/core/dist/workflow/workflow-schema.test.js.map +1 -1
- package/pennyfarthing-dist/agents/README.md +1 -3
- package/pennyfarthing-dist/agents/architect.md +0 -6
- package/pennyfarthing-dist/agents/devops.md +0 -6
- package/pennyfarthing-dist/agents/handoff.md +18 -3
- package/pennyfarthing-dist/agents/orchestrator.md +0 -6
- package/pennyfarthing-dist/agents/pm.md +0 -6
- package/pennyfarthing-dist/agents/sm-finish.md +1 -1
- package/pennyfarthing-dist/agents/sm-handoff.md +27 -4
- package/pennyfarthing-dist/agents/sm.md +11 -11
- package/pennyfarthing-dist/agents/tandem-backseat.md +119 -0
- package/pennyfarthing-dist/commands/architect.md +11 -3
- package/pennyfarthing-dist/commands/close-epic.md +24 -131
- package/pennyfarthing-dist/commands/create-theme.md +14 -24
- package/pennyfarthing-dist/commands/dev.md +11 -3
- package/pennyfarthing-dist/commands/devops.md +11 -3
- package/pennyfarthing-dist/commands/health-check.md +1 -3
- package/pennyfarthing-dist/commands/help.md +8 -12
- package/pennyfarthing-dist/commands/list-themes.md +14 -16
- package/pennyfarthing-dist/commands/orchestrator.md +11 -3
- package/pennyfarthing-dist/commands/parallel-work.md +1 -3
- package/pennyfarthing-dist/commands/pm.md +11 -3
- package/pennyfarthing-dist/commands/prime.md +6 -6
- package/pennyfarthing-dist/commands/reviewer.md +11 -3
- package/pennyfarthing-dist/commands/run-ci.md +1 -1
- package/pennyfarthing-dist/commands/set-theme.md +14 -51
- package/pennyfarthing-dist/commands/setup.md +5 -1
- package/pennyfarthing-dist/commands/show-theme.md +14 -16
- package/pennyfarthing-dist/commands/sm.md +11 -3
- package/pennyfarthing-dist/commands/tea.md +11 -3
- package/pennyfarthing-dist/commands/tech-writer.md +11 -3
- package/pennyfarthing-dist/commands/theme-maker.md +14 -671
- package/pennyfarthing-dist/commands/theme.md +95 -0
- package/pennyfarthing-dist/commands/ux-designer.md +11 -3
- package/pennyfarthing-dist/commands/work.md +3 -5
- package/pennyfarthing-dist/guides/agent-behavior.md +62 -6
- package/pennyfarthing-dist/guides/agent-coordination.md +11 -13
- package/pennyfarthing-dist/guides/agent-template-tactical.md +2 -3
- package/pennyfarthing-dist/guides/bikelane.md +3 -2
- package/pennyfarthing-dist/guides/command-tag-taxonomy.md +212 -0
- package/pennyfarthing-dist/guides/hooks.md +5 -5
- package/pennyfarthing-dist/guides/patterns/fan-out-fan-in-pattern.md +3 -3
- package/pennyfarthing-dist/guides/patterns/helper-delegation-pattern.md +9 -59
- package/pennyfarthing-dist/guides/patterns/tdd-flow-pattern.md +4 -5
- package/pennyfarthing-dist/guides/prime.md +2 -2
- package/pennyfarthing-dist/guides/scale-levels.md +4 -6
- package/pennyfarthing-dist/guides/skill-schema.md +4 -4
- package/pennyfarthing-dist/guides/tandem-protocol.md +158 -0
- package/pennyfarthing-dist/personas/themes/discworld.yaml +1 -1
- package/pennyfarthing-dist/personas/themes/fifth-element.yaml +295 -0
- package/pennyfarthing-dist/scripts/README.md +1 -1
- package/pennyfarthing-dist/scripts/core/agent-session.sh +6 -2
- package/pennyfarthing-dist/scripts/core/check-context.sh +0 -0
- package/pennyfarthing-dist/scripts/core/handoff-marker.sh +0 -0
- package/pennyfarthing-dist/scripts/core/phase-check-start.sh +0 -0
- package/pennyfarthing-dist/scripts/core/prime.sh +8 -10
- package/pennyfarthing-dist/scripts/cyclist/is-cyclist.sh +0 -0
- package/pennyfarthing-dist/scripts/git/create-feature-branches.sh +0 -0
- package/pennyfarthing-dist/scripts/git/git-status-all.sh +0 -0
- package/pennyfarthing-dist/scripts/git/install-git-hooks.sh +8 -6
- package/pennyfarthing-dist/scripts/git/release.sh +0 -0
- package/pennyfarthing-dist/scripts/git/worktree-manager.sh +0 -0
- package/pennyfarthing-dist/scripts/health/drift-detection.sh +0 -0
- package/pennyfarthing-dist/scripts/hooks/bell-mode-hook.sh +131 -54
- package/pennyfarthing-dist/scripts/hooks/context-circuit-breaker.sh +0 -0
- package/pennyfarthing-dist/scripts/hooks/context-warning.sh +0 -0
- package/pennyfarthing-dist/scripts/hooks/cyclist-pretooluse-hook.sh +0 -0
- package/pennyfarthing-dist/scripts/hooks/otel-auto-config.sh +0 -0
- package/pennyfarthing-dist/scripts/hooks/post-merge.sh +32 -15
- package/pennyfarthing-dist/scripts/hooks/pre-commit.sh +4 -3
- package/pennyfarthing-dist/scripts/hooks/pre-edit-check.sh +0 -0
- package/pennyfarthing-dist/scripts/hooks/pre-push.sh +11 -5
- package/pennyfarthing-dist/scripts/hooks/question-reflector-check.sh +0 -0
- package/pennyfarthing-dist/scripts/hooks/question_reflector_check.py +0 -0
- package/pennyfarthing-dist/scripts/hooks/schema-validation.sh +0 -0
- package/pennyfarthing-dist/scripts/hooks/session-start.sh +0 -0
- package/pennyfarthing-dist/scripts/hooks/session-stop.sh +0 -0
- package/pennyfarthing-dist/scripts/hooks/sprint-yaml-validation.sh +0 -0
- package/pennyfarthing-dist/scripts/hooks/welcome-hook.sh +0 -0
- package/pennyfarthing-dist/scripts/jira/create-jira-epic.sh +0 -0
- package/pennyfarthing-dist/scripts/jira/create-jira-story.sh +0 -0
- package/pennyfarthing-dist/scripts/jira/jira-claim-story.sh +0 -0
- package/pennyfarthing-dist/scripts/jira/jira-reconcile.sh +0 -0
- package/pennyfarthing-dist/scripts/jira/jira-sync-story.sh +0 -0
- package/pennyfarthing-dist/scripts/jira/sync-epic-jira.sh +0 -0
- package/pennyfarthing-dist/scripts/lib/background-tasks.sh +0 -0
- package/pennyfarthing-dist/scripts/lib/checkpoint.sh +0 -0
- package/pennyfarthing-dist/scripts/lib/common.sh +0 -0
- package/pennyfarthing-dist/scripts/lib/file-lock.sh +0 -0
- package/pennyfarthing-dist/scripts/lib/logging.sh +0 -0
- package/pennyfarthing-dist/scripts/lib/retry.sh +0 -0
- package/pennyfarthing-dist/scripts/maintenance/migrate-theme-schema.mjs +0 -0
- package/pennyfarthing-dist/scripts/maintenance/sidecar-health.sh +0 -0
- package/pennyfarthing-dist/scripts/misc/README.md +1 -1
- package/pennyfarthing-dist/scripts/misc/add-short-names.sh +0 -0
- package/pennyfarthing-dist/scripts/misc/add_short_names.py +0 -0
- package/pennyfarthing-dist/scripts/misc/backlog.sh +0 -0
- package/pennyfarthing-dist/scripts/misc/check-status.sh +0 -0
- package/pennyfarthing-dist/scripts/misc/find-related-work.sh +0 -0
- package/pennyfarthing-dist/scripts/misc/generate-skill-docs.sh +0 -0
- package/pennyfarthing-dist/scripts/misc/log-skill-usage.sh +0 -0
- package/pennyfarthing-dist/scripts/misc/migrate-bmad-workflow.sh +0 -0
- package/pennyfarthing-dist/scripts/misc/migrate_bmad_workflow.py +0 -0
- package/pennyfarthing-dist/scripts/misc/repo-scan.sh +0 -0
- package/pennyfarthing-dist/scripts/misc/repo-utils.sh +0 -0
- package/pennyfarthing-dist/scripts/misc/run-ci.sh +0 -0
- package/pennyfarthing-dist/scripts/misc/run-timestamp.sh +0 -0
- package/pennyfarthing-dist/scripts/misc/session-cleanup.sh +0 -0
- package/pennyfarthing-dist/scripts/misc/skill-usage-report.sh +0 -0
- package/pennyfarthing-dist/scripts/misc/statusline.sh +50 -8
- package/pennyfarthing-dist/scripts/misc/uninstall.sh +0 -0
- package/pennyfarthing-dist/scripts/misc/validate-subagent-frontmatter.sh +1 -2
- package/pennyfarthing-dist/scripts/portraits/generate-portraits.sh +0 -0
- package/pennyfarthing-dist/scripts/story/create-story.sh +0 -0
- package/pennyfarthing-dist/scripts/story/size-story.sh +0 -0
- package/pennyfarthing-dist/scripts/story/story-template.sh +0 -0
- package/pennyfarthing-dist/scripts/tests/check.test.sh +0 -0
- package/pennyfarthing-dist/scripts/tests/dev-story-workflow-import.test.sh +0 -0
- package/pennyfarthing-dist/scripts/tests/epics-and-stories-workflow-import.test.sh +0 -0
- package/pennyfarthing-dist/scripts/tests/handoff-phase-update.test.sh +5 -5
- package/pennyfarthing-dist/scripts/tests/implementation-readiness-workflow-import.test.sh +0 -0
- package/pennyfarthing-dist/scripts/tests/migrate-bmad-workflow.test.sh +0 -0
- package/pennyfarthing-dist/scripts/tests/prd-workflow-import.test.sh +0 -0
- package/pennyfarthing-dist/scripts/tests/project-context-workflow-import.test.sh +0 -0
- package/pennyfarthing-dist/scripts/tests/test-character-voice.sh +0 -0
- package/pennyfarthing-dist/scripts/tests/test-drift-detection.sh +3 -79
- package/pennyfarthing-dist/scripts/tests/test-post-merge-hook.sh +0 -0
- package/pennyfarthing-dist/scripts/tests/test-session-checkpoint.sh +0 -0
- package/pennyfarthing-dist/scripts/tests/test-solo-command.sh +0 -0
- package/pennyfarthing-dist/scripts/tests/ux-design-workflow-import.test.sh +0 -0
- package/pennyfarthing-dist/scripts/theme/README.md +1 -1
- package/pennyfarthing-dist/scripts/theme/list-themes.sh +0 -0
- package/pennyfarthing-dist/scripts/validation/validate-agent-schema.sh +0 -1
- package/pennyfarthing-dist/scripts/workflow/README.md +2 -2
- package/pennyfarthing-dist/scripts/workflow/check.py +0 -0
- package/pennyfarthing-dist/scripts/workflow/check.sh +0 -0
- package/pennyfarthing-dist/scripts/workflow/complete-step.py +0 -0
- package/pennyfarthing-dist/scripts/workflow/finish-story.sh +10 -144
- package/pennyfarthing-dist/scripts/workflow/fix-session-phase.sh +0 -0
- package/pennyfarthing-dist/scripts/workflow/get-workflow-type.py +0 -0
- package/pennyfarthing-dist/scripts/workflow/get-workflow-type.sh +0 -0
- package/pennyfarthing-dist/scripts/workflow/list-workflows.sh +0 -0
- package/pennyfarthing-dist/scripts/workflow/phase-owner.sh +0 -0
- package/pennyfarthing-dist/scripts/workflow/resume-workflow.sh +0 -0
- package/pennyfarthing-dist/scripts/workflow/show-workflow.sh +0 -0
- package/pennyfarthing-dist/scripts/workflow/start-workflow.sh +0 -0
- package/pennyfarthing-dist/scripts/workflow/workflow-status.sh +0 -0
- package/pennyfarthing-dist/skills/dev-patterns/SKILL.md +2 -2
- package/pennyfarthing-dist/skills/skill-registry.schema.json +8 -0
- package/pennyfarthing-dist/skills/skill-registry.yaml +21 -17
- package/pennyfarthing-dist/skills/sprint/skill.md +25 -2
- package/pennyfarthing-dist/skills/story/scripts/create-story.sh +0 -0
- package/pennyfarthing-dist/skills/story/scripts/size-story.sh +0 -0
- package/pennyfarthing-dist/skills/story/scripts/story-template.sh +0 -0
- package/pennyfarthing-dist/skills/theme/skill.md +290 -75
- package/pennyfarthing-dist/skills/theme-creation/SKILL.md +23 -166
- package/pennyfarthing-dist/skills/workflow/scripts/list-workflows.sh +0 -0
- package/pennyfarthing-dist/skills/workflow/scripts/resume-workflow.sh +0 -0
- package/pennyfarthing-dist/skills/workflow/scripts/show-workflow.sh +0 -0
- package/pennyfarthing-dist/skills/workflow/scripts/start-workflow.sh +0 -0
- package/pennyfarthing-dist/skills/workflow/scripts/workflow-status.sh +0 -0
- package/pennyfarthing-dist/skills/workflow/skill.md +27 -4
- package/pennyfarthing-dist/templates/agent-scopes.yaml.template +0 -11
- package/pennyfarthing-dist/templates/auto-load-sm.sh.template +14 -0
- package/pennyfarthing-dist/templates/settings.local.json.template +9 -0
- package/pennyfarthing-dist/workflows/2party-tdd.yaml +399 -0
- package/pennyfarthing-dist/workflows/architecture/workflow.yaml +65 -0
- package/pennyfarthing-dist/workflows/bdd-tandem.yaml +70 -0
- package/pennyfarthing-dist/workflows/epics-and-stories/steps/step-05-import-to-future.md +41 -24
- package/pennyfarthing-dist/workflows/tdd-tandem.yaml +61 -0
- package/pennyfarthing_scripts/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/bellmode_hook.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/config.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/hooks.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/jira_bidirectional_sync.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/jira_epic_creation.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/jira_sync.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/jira_sync_story.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/output.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/patch_mode.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/schema_validation_hook.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/workflow.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bellmode_hook.py +202 -47
- package/pennyfarthing_scripts/brownfield/__init__.py +6 -6
- package/pennyfarthing_scripts/brownfield/__main__.py +1 -0
- package/pennyfarthing_scripts/brownfield/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/brownfield/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/brownfield/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/brownfield/__pycache__/discover.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/brownfield/cli.py +0 -1
- package/pennyfarthing_scripts/brownfield/discover.py +1 -2
- package/pennyfarthing_scripts/cli.py +23 -3
- package/pennyfarthing_scripts/codemarkers/__init__.py +23 -0
- package/pennyfarthing_scripts/codemarkers/__main__.py +6 -0
- package/pennyfarthing_scripts/codemarkers/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/codemarkers/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/codemarkers/__pycache__/analyze.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/codemarkers/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/codemarkers/__pycache__/formatters.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/codemarkers/__pycache__/models.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/codemarkers/analyze.py +501 -0
- package/pennyfarthing_scripts/codemarkers/cli.py +179 -0
- package/pennyfarthing_scripts/codemarkers/formatters.py +88 -0
- package/pennyfarthing_scripts/codemarkers/models.py +60 -0
- package/pennyfarthing_scripts/common/__init__.py +8 -9
- package/pennyfarthing_scripts/common/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/common/__pycache__/config.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/common/__pycache__/output.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/common/__pycache__/themes.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/common/config.py +1 -1
- package/pennyfarthing_scripts/complexity/__init__.py +15 -0
- package/pennyfarthing_scripts/complexity/__main__.py +6 -0
- package/pennyfarthing_scripts/complexity/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/complexity/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/complexity/__pycache__/analyze.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/complexity/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/complexity/__pycache__/formatters.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/complexity/__pycache__/models.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/complexity/analyze.py +207 -0
- package/pennyfarthing_scripts/complexity/cli.py +82 -0
- package/pennyfarthing_scripts/complexity/formatters.py +64 -0
- package/pennyfarthing_scripts/complexity/models.py +32 -0
- package/pennyfarthing_scripts/context.py +14 -15
- package/pennyfarthing_scripts/deadcode/__init__.py +6 -0
- package/pennyfarthing_scripts/deadcode/__main__.py +6 -0
- package/pennyfarthing_scripts/deadcode/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/deadcode/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/deadcode/__pycache__/analyze.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/deadcode/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/deadcode/__pycache__/formatters.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/deadcode/__pycache__/models.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/deadcode/analyze.py +322 -0
- package/pennyfarthing_scripts/deadcode/cli.py +163 -0
- package/pennyfarthing_scripts/deadcode/formatters.py +106 -0
- package/pennyfarthing_scripts/deadcode/models.py +54 -0
- package/pennyfarthing_scripts/dependencies/__init__.py +20 -0
- package/pennyfarthing_scripts/dependencies/__main__.py +5 -0
- package/pennyfarthing_scripts/dependencies/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/dependencies/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/dependencies/__pycache__/analyze.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/dependencies/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/dependencies/__pycache__/formatters.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/dependencies/__pycache__/models.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/dependencies/analyze.py +155 -0
- package/pennyfarthing_scripts/dependencies/cli.py +76 -0
- package/pennyfarthing_scripts/dependencies/formatters.py +63 -0
- package/pennyfarthing_scripts/dependencies/models.py +39 -0
- package/pennyfarthing_scripts/git/__init__.py +5 -5
- package/pennyfarthing_scripts/git/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/git/__pycache__/create_branches.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/git/__pycache__/status_all.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/git/create_branches.py +3 -2
- package/pennyfarthing_scripts/git/status_all.py +1 -1
- package/pennyfarthing_scripts/healthscore/__init__.py +21 -0
- package/pennyfarthing_scripts/healthscore/__main__.py +14 -0
- package/pennyfarthing_scripts/healthscore/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/healthscore/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/healthscore/__pycache__/analyze.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/healthscore/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/healthscore/__pycache__/formatters.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/healthscore/__pycache__/models.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/healthscore/analyze.py +591 -0
- package/pennyfarthing_scripts/healthscore/cli.py +80 -0
- package/pennyfarthing_scripts/healthscore/formatters.py +46 -0
- package/pennyfarthing_scripts/healthscore/models.py +43 -0
- package/pennyfarthing_scripts/hooks/cyclist-pretooluse-hook.sh +0 -0
- package/pennyfarthing_scripts/hooks.py +8 -11
- package/pennyfarthing_scripts/hotspots/__init__.py +6 -6
- package/pennyfarthing_scripts/hotspots/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/hotspots/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/hotspots/__pycache__/analyze.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/hotspots/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/hotspots/__pycache__/formatters.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/hotspots/__pycache__/models.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/hotspots/analyze.py +155 -14
- package/pennyfarthing_scripts/hotspots/cli.py +12 -10
- package/pennyfarthing_scripts/hotspots/models.py +0 -1
- package/pennyfarthing_scripts/jira/__init__.py +15 -17
- package/pennyfarthing_scripts/jira/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/bidirectional.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/claim.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/client.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/create.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/epic.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/operations.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/reconcile.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/story.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/sync.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/bidirectional.py +44 -18
- package/pennyfarthing_scripts/jira/claim.py +21 -0
- package/pennyfarthing_scripts/jira/cli.py +6 -3
- package/pennyfarthing_scripts/jira/client.py +32 -4
- package/pennyfarthing_scripts/jira/create.py +45 -1
- package/pennyfarthing_scripts/jira/epic.py +3 -2
- package/pennyfarthing_scripts/jira/reconcile.py +0 -1
- package/pennyfarthing_scripts/jira/story.py +2 -0
- package/pennyfarthing_scripts/jira/sync.py +1 -1
- package/pennyfarthing_scripts/migration/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/migration/__pycache__/session.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/migration/__pycache__/skill.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/migration/__pycache__/step.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/migration/__pycache__/validate.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/migration/skill.py +0 -1
- package/pennyfarthing_scripts/migration/step.py +0 -1
- package/pennyfarthing_scripts/migration/validate.py +8 -5
- package/pennyfarthing_scripts/patch_mode.py +2 -2
- package/pennyfarthing_scripts/preflight/__init__.py +1 -1
- package/pennyfarthing_scripts/preflight/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/preflight/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/preflight/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/preflight/__pycache__/finish.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/preflight/finish.py +0 -1
- package/pennyfarthing_scripts/pretooluse_hook.py +6 -7
- package/pennyfarthing_scripts/prime/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/prime/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/prime/__pycache__/loader.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/prime/__pycache__/models.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/prime/__pycache__/persona.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/prime/__pycache__/session.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/prime/__pycache__/tiers.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/prime/__pycache__/workflow.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/prime/cli.py +5 -1
- package/pennyfarthing_scripts/prime/loader.py +2 -3
- package/pennyfarthing_scripts/prime/persona.py +2 -1
- package/pennyfarthing_scripts/prime/tiers.py +4 -4
- package/pennyfarthing_scripts/schema_validation_hook.py +2 -3
- package/pennyfarthing_scripts/sprint/__init__.py +10 -12
- package/pennyfarthing_scripts/sprint/__main__.py +2 -2
- package/pennyfarthing_scripts/sprint/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/archive.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/archive_epic.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/epic_add.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/import_epic.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/loader.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/status.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/story_add.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/story_finish.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/story_update.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/validate_cmd.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/validator.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/work.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/yaml_io.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/archive.py +0 -1
- package/pennyfarthing_scripts/sprint/archive_epic.py +198 -97
- package/pennyfarthing_scripts/sprint/cli.py +62 -46
- package/pennyfarthing_scripts/sprint/epic_add.py +8 -1
- package/pennyfarthing_scripts/sprint/import_epic.py +42 -18
- package/pennyfarthing_scripts/sprint/loader.py +6 -0
- package/pennyfarthing_scripts/sprint/status.py +1 -2
- package/pennyfarthing_scripts/sprint/story_add.py +202 -27
- package/pennyfarthing_scripts/sprint/story_finish.py +209 -0
- package/pennyfarthing_scripts/sprint/story_update.py +11 -3
- package/pennyfarthing_scripts/sprint/validate_cmd.py +0 -1
- package/pennyfarthing_scripts/sprint/validator.py +120 -6
- package/pennyfarthing_scripts/sprint/work.py +28 -7
- package/pennyfarthing_scripts/sprint/yaml_io.py +10 -2
- package/pennyfarthing_scripts/story/__init__.py +14 -16
- package/pennyfarthing_scripts/story/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/story/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/story/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/story/__pycache__/create.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/story/__pycache__/size.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/story/__pycache__/template.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/story/size.py +0 -1
- package/pennyfarthing_scripts/story/template.py +0 -1
- package/pennyfarthing_scripts/swebench.py +1 -2
- package/pennyfarthing_scripts/tests/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/conftest.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_brownfield.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_cli_modules.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_codemarkers.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_common.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_git_utils.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_healthscore.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_jira_package.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_package_structure.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_patch_mode.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_prime.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_sprint_package.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_sprint_validator.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_story_add.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_story_package.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_story_update.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_tiers.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_token_counting.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_validate_cmd.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_workflow_check.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_yaml_io.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/conftest.py +1 -2
- package/pennyfarthing_scripts/tests/test_brownfield.py +10 -13
- package/pennyfarthing_scripts/tests/test_cli_modules.py +0 -4
- package/pennyfarthing_scripts/tests/test_codemarkers.py +687 -0
- package/pennyfarthing_scripts/tests/test_common.py +9 -4
- package/pennyfarthing_scripts/tests/test_epic_shard_validation.py +699 -0
- package/pennyfarthing_scripts/tests/test_git_utils.py +10 -13
- package/pennyfarthing_scripts/tests/test_healthscore.py +516 -0
- package/pennyfarthing_scripts/tests/test_jira_package.py +0 -3
- package/pennyfarthing_scripts/tests/test_package_structure.py +3 -16
- package/pennyfarthing_scripts/tests/test_patch_mode.py +7 -11
- package/pennyfarthing_scripts/tests/test_prime.py +39 -21
- package/pennyfarthing_scripts/tests/test_sprint_package.py +3 -8
- package/pennyfarthing_scripts/tests/test_sprint_validator.py +53 -5
- package/pennyfarthing_scripts/tests/test_story_add.py +3 -7
- package/pennyfarthing_scripts/tests/test_story_package.py +0 -3
- package/pennyfarthing_scripts/tests/test_story_update.py +5 -10
- package/pennyfarthing_scripts/tests/test_tiers.py +18 -17
- package/pennyfarthing_scripts/tests/test_token_counting.py +19 -13
- package/pennyfarthing_scripts/tests/test_validate_cmd.py +2 -7
- package/pennyfarthing_scripts/tests/test_workflow_check.py +0 -2
- package/pennyfarthing_scripts/tests/test_yaml_io.py +0 -3
- package/pennyfarthing_scripts/theme/__init__.py +5 -0
- package/pennyfarthing_scripts/theme/__main__.py +6 -0
- package/pennyfarthing_scripts/theme/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/theme/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/theme/cli.py +287 -0
- package/pennyfarthing_scripts/validate/__init__.py +21 -0
- package/pennyfarthing_scripts/validate/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/validate/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/validate/adapters/__init__.py +0 -0
- package/pennyfarthing_scripts/validate/adapters/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/validate/adapters/__pycache__/agent.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/validate/adapters/__pycache__/schema.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/validate/adapters/__pycache__/skill_command.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/validate/adapters/__pycache__/sprint.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/validate/adapters/__pycache__/workflow.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/validate/adapters/agent.py +239 -0
- package/pennyfarthing_scripts/validate/adapters/schema.py +30 -0
- package/pennyfarthing_scripts/validate/adapters/skill_command.py +292 -0
- package/pennyfarthing_scripts/validate/adapters/sprint.py +69 -0
- package/pennyfarthing_scripts/validate/adapters/workflow.py +320 -0
- package/pennyfarthing_scripts/validate/cli.py +141 -0
- package/pennyfarthing_scripts/welcome_hook.py +2 -3
- package/pennyfarthing_scripts/workflow.py +3 -3
- package/scripts/README.md +41 -0
- package/pennyfarthing-dist/agents/workflow-status-check.md +0 -96
- package/pennyfarthing-dist/commands/benchmark-control.md +0 -69
- package/pennyfarthing-dist/commands/benchmark.md +0 -485
- package/pennyfarthing-dist/commands/job-fair.md +0 -102
- package/pennyfarthing-dist/commands/solo.md +0 -447
- package/pennyfarthing-dist/guides/benchmarks.md +0 -62
- package/pennyfarthing-dist/scripts/hooks/__pycache__/question_reflector_check.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/scripts/test/ensure-swebench-data.sh +0 -59
- package/pennyfarthing-dist/scripts/test/ground-truth-judge.py +0 -220
- package/pennyfarthing-dist/scripts/test/swebench-judge.py +0 -374
- package/pennyfarthing-dist/scripts/test/test-cache.sh +0 -165
- package/pennyfarthing-dist/scripts/test/test-setup.sh +0 -337
- package/pennyfarthing-dist/scripts/theme/compute-theme-tiers.sh +0 -13
- package/pennyfarthing-dist/scripts/theme/compute_theme_tiers.py +0 -402
- package/pennyfarthing-dist/scripts/theme/update-theme-tiers.sh +0 -97
- package/pennyfarthing-dist/skills/finalize-run/SKILL.md +0 -261
- package/pennyfarthing-dist/skills/judge/SKILL.md +0 -644
- package/pennyfarthing-dist/skills/persona-benchmark/SKILL.md +0 -187
- package/pennyfarthing-dist/workflows/dev-story/checklist.md +0 -80
- package/pennyfarthing-dist/workflows/dev-story/instructions.xml +0 -410
- package/pennyfarthing-dist/workflows/dev-story/workflow.yaml +0 -50
- package/pennyfarthing-dist/workflows/quick-spec/steps/step-01-understand.md +0 -201
- package/pennyfarthing-dist/workflows/quick-spec/steps/step-02-investigate.md +0 -156
- package/pennyfarthing-dist/workflows/quick-spec/steps/step-03-generate.md +0 -140
- package/pennyfarthing-dist/workflows/quick-spec/steps/step-04-review.md +0 -203
- package/pennyfarthing-dist/workflows/quick-spec/tech-spec-template.md +0 -74
- package/pennyfarthing-dist/workflows/quick-spec/workflow.yaml +0 -27
- package/pennyfarthing_scripts/__pycache__/__init__.cpython-311.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/jira.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/pretooluse_hook.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/sprint.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/workflow.cpython-311.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/compat.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/mappings.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/models.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/migration/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/migration/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/prime/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_workflow_cli.cpython-314-pytest-9.0.2.pyc +0 -0
|
@@ -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 as err:
|
|
316
|
+
raise click.ClickException(f"POINTS must be an integer, got '{title}'") from err
|
|
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,209 @@
|
|
|
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)
|
|
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
|
+
import sys
|
|
20
|
+
from datetime import date
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
from typing import Any
|
|
23
|
+
|
|
24
|
+
from pennyfarthing_scripts.sprint.loader import find_epic, find_story
|
|
25
|
+
from pennyfarthing_scripts.sprint.yaml_io import read_sprint, write_sprint
|
|
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
|
+
write_sprint(sprint_path, data)
|
|
179
|
+
steps.append({"step": 4, "action": "yaml_update", "status": "done", "completed": today})
|
|
180
|
+
else:
|
|
181
|
+
steps.append({"step": 4, "action": "yaml_update", "warning": f"Story {story_id} not found in YAML"})
|
|
182
|
+
except Exception as exc:
|
|
183
|
+
steps.append({"step": 4, "action": "yaml_update", "error": str(exc)})
|
|
184
|
+
|
|
185
|
+
# --- Step 5: Archive completed epics ---
|
|
186
|
+
result = _run(
|
|
187
|
+
[sys.executable, "-m", "pennyfarthing_scripts.cli", "sprint", "epic", "archive"],
|
|
188
|
+
cwd=str(project_root),
|
|
189
|
+
)
|
|
190
|
+
steps.append({"step": 5, "action": "archive_epics", "ran": True})
|
|
191
|
+
|
|
192
|
+
# --- Step 6: Git cleanup ---
|
|
193
|
+
_run(["git", "checkout", "develop"], cwd=str(project_root))
|
|
194
|
+
_run(["git", "pull", "origin", "develop"], cwd=str(project_root))
|
|
195
|
+
if branch:
|
|
196
|
+
_run(["git", "branch", "-d", branch], cwd=str(project_root))
|
|
197
|
+
steps.append({"step": 6, "action": "git_cleanup", "branch": branch})
|
|
198
|
+
|
|
199
|
+
# --- Step 7: Remove session file ---
|
|
200
|
+
if session_path.exists():
|
|
201
|
+
session_path.unlink()
|
|
202
|
+
steps.append({"step": 7, "action": "remove_session"})
|
|
203
|
+
|
|
204
|
+
return {
|
|
205
|
+
"success": True,
|
|
206
|
+
"story_id": story_id,
|
|
207
|
+
"jira_key": jira_key,
|
|
208
|
+
"steps": steps,
|
|
209
|
+
}
|
|
@@ -7,6 +7,7 @@ This module provides:
|
|
|
7
7
|
- story_update_command (Click command for CLI registration)
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
|
+
import subprocess
|
|
10
11
|
from datetime import date
|
|
11
12
|
from pathlib import Path
|
|
12
13
|
from typing import Any
|
|
@@ -95,9 +96,6 @@ def update_story(
|
|
|
95
96
|
|
|
96
97
|
# Auto-cleanup rules
|
|
97
98
|
if status == "done":
|
|
98
|
-
# Remove assigned_to
|
|
99
|
-
if "assigned_to" in story:
|
|
100
|
-
del story["assigned_to"]
|
|
101
99
|
# Auto-set completed if not explicitly provided
|
|
102
100
|
if completed_date is None and "completed" not in story:
|
|
103
101
|
story["completed"] = date.today().isoformat()
|
|
@@ -108,6 +106,16 @@ def update_story(
|
|
|
108
106
|
# Auto-set started if not already present
|
|
109
107
|
if "started" not in story:
|
|
110
108
|
story["started"] = date.today().isoformat()
|
|
109
|
+
# Auto-set assignee from current Jira user if not already assigned
|
|
110
|
+
if "assigned_to" not in story and assigned_to is None:
|
|
111
|
+
try:
|
|
112
|
+
result = subprocess.run(
|
|
113
|
+
["jira", "me"], capture_output=True, text=True
|
|
114
|
+
)
|
|
115
|
+
if result.returncode == 0 and result.stdout.strip():
|
|
116
|
+
story["assigned_to"] = result.stdout.strip()
|
|
117
|
+
except Exception:
|
|
118
|
+
pass
|
|
111
119
|
|
|
112
120
|
# Validate after mutation
|
|
113
121
|
result = validate_full_sprint(data)
|
|
@@ -78,6 +78,10 @@ REQUIRED_STORY_FIELDS = {"id", "title", "status", "points"}
|
|
|
78
78
|
# Required fields for epic
|
|
79
79
|
REQUIRED_EPIC_FIELDS = {"id", "title"}
|
|
80
80
|
|
|
81
|
+
# Required fields for epic shard files (write-time validation, ADR-0022)
|
|
82
|
+
REQUIRED_EPIC_SHARD_FIELDS = {"id", "title", "status", "stories"}
|
|
83
|
+
REQUIRED_SHARD_STORY_FIELDS = {"id", "title", "points", "status"}
|
|
84
|
+
|
|
81
85
|
# Required fields for future.yaml initiative
|
|
82
86
|
REQUIRED_INITIATIVE_FIELDS = {"name", "status"}
|
|
83
87
|
|
|
@@ -235,6 +239,15 @@ def validate_epic(epic: dict[str, Any], all_story_ids: set[str], epic_index: int
|
|
|
235
239
|
f"{base_path}.{field_name}",
|
|
236
240
|
)
|
|
237
241
|
|
|
242
|
+
# Reject non-string IDs (YAML parses bare integers like `id: 87` as int,
|
|
243
|
+
# which crashes Cyclist's sprint-data.ts — epicId.match() fails on non-strings)
|
|
244
|
+
if "id" in epic and not isinstance(epic["id"], str):
|
|
245
|
+
result.add_error(
|
|
246
|
+
f"Epic ID must be a string, got {type(epic['id']).__name__} ({epic['id']!r}). "
|
|
247
|
+
"Quote it in YAML (e.g., id: \"87\" not id: 87)",
|
|
248
|
+
f"{base_path}.id",
|
|
249
|
+
)
|
|
250
|
+
|
|
238
251
|
# Validate stories if present
|
|
239
252
|
if "stories" in epic:
|
|
240
253
|
seen_in_epic: set[str] = set()
|
|
@@ -262,6 +275,95 @@ def validate_epic(epic: dict[str, Any], all_story_ids: set[str], epic_index: int
|
|
|
262
275
|
return result
|
|
263
276
|
|
|
264
277
|
|
|
278
|
+
def validate_epic_shard(epic: dict[str, Any]) -> ValidationResult:
|
|
279
|
+
"""Validate an epic shard dict before writing to disk.
|
|
280
|
+
|
|
281
|
+
Enforces stricter requirements than validate_epic() since shards
|
|
282
|
+
are standalone files that must be self-contained.
|
|
283
|
+
|
|
284
|
+
Validates:
|
|
285
|
+
- Required fields present (id, title, status, stories)
|
|
286
|
+
- stories is a list
|
|
287
|
+
- Each story has required fields (id, title, points, status)
|
|
288
|
+
- No duplicate story IDs within the epic
|
|
289
|
+
- jira key follows MSSCI-NNNNN pattern if present
|
|
290
|
+
|
|
291
|
+
Args:
|
|
292
|
+
epic: Epic shard dict to validate
|
|
293
|
+
|
|
294
|
+
Returns:
|
|
295
|
+
ValidationResult with any errors found
|
|
296
|
+
"""
|
|
297
|
+
result = ValidationResult(valid=True)
|
|
298
|
+
|
|
299
|
+
# Check required shard fields
|
|
300
|
+
for field_name in REQUIRED_EPIC_SHARD_FIELDS:
|
|
301
|
+
if field_name not in epic:
|
|
302
|
+
result.add_error(
|
|
303
|
+
f"Missing required field: {field_name}",
|
|
304
|
+
f"epic.{field_name}",
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
# Reject non-string IDs (YAML parses bare integers like `id: 87` as int,
|
|
308
|
+
# which crashes Cyclist's sprint-data.ts checkEpicContext — epicId.match() fails)
|
|
309
|
+
if "id" in epic and not isinstance(epic["id"], str):
|
|
310
|
+
result.add_error(
|
|
311
|
+
f"Epic ID must be a string, got {type(epic['id']).__name__} ({epic['id']!r}). "
|
|
312
|
+
"Quote it in YAML (e.g., id: \"87\" not id: 87)",
|
|
313
|
+
"epic.id",
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
# Reject epic- prefix in ID (ADR-0022: reference prefix should not be baked into value)
|
|
317
|
+
if "id" in epic:
|
|
318
|
+
epic_id_val = str(epic["id"])
|
|
319
|
+
if epic_id_val.startswith("epic-"):
|
|
320
|
+
result.add_error(
|
|
321
|
+
f"Epic ID '{epic_id_val}' starts with 'epic-' prefix. "
|
|
322
|
+
"Use the numeric ID (e.g., '94' not 'epic-94')",
|
|
323
|
+
"epic.id",
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
# Validate jira key format if present
|
|
327
|
+
if "jira" in epic:
|
|
328
|
+
jira_key = str(epic["jira"])
|
|
329
|
+
if not JIRA_KEY_PATTERN.match(jira_key):
|
|
330
|
+
result.add_error(
|
|
331
|
+
f"Invalid Jira key format '{jira_key}'. Expected MSSCI-NNNNN",
|
|
332
|
+
"epic.jira",
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
# Validate stories field
|
|
336
|
+
if "stories" in epic:
|
|
337
|
+
stories = epic["stories"]
|
|
338
|
+
if not isinstance(stories, list):
|
|
339
|
+
result.add_error(
|
|
340
|
+
"'stories' must be a list",
|
|
341
|
+
"epic.stories",
|
|
342
|
+
)
|
|
343
|
+
else:
|
|
344
|
+
seen_ids: set[str] = set()
|
|
345
|
+
epic_id = epic.get("id", "epic")
|
|
346
|
+
for idx, story in enumerate(stories):
|
|
347
|
+
story_id = story.get("id")
|
|
348
|
+
if story_id:
|
|
349
|
+
if story_id in seen_ids:
|
|
350
|
+
result.add_error(
|
|
351
|
+
f"Duplicate story ID '{story_id}' within epic",
|
|
352
|
+
f"epic.stories[{idx}].id",
|
|
353
|
+
)
|
|
354
|
+
seen_ids.add(story_id)
|
|
355
|
+
|
|
356
|
+
# Check required story fields
|
|
357
|
+
for field_name in REQUIRED_SHARD_STORY_FIELDS:
|
|
358
|
+
if field_name not in story:
|
|
359
|
+
result.add_error(
|
|
360
|
+
f"Missing required field: {field_name}",
|
|
361
|
+
f"{epic_id}.stories[{idx}].{field_name}",
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
return result
|
|
365
|
+
|
|
366
|
+
|
|
265
367
|
def validate_full_sprint(data: dict[str, Any]) -> ValidationResult:
|
|
266
368
|
"""Validate complete sprint YAML including all epics and stories.
|
|
267
369
|
|
|
@@ -356,7 +458,7 @@ def validate_future(data: dict[str, Any]) -> ValidationResult:
|
|
|
356
458
|
if isinstance(initiative, str):
|
|
357
459
|
continue
|
|
358
460
|
if not isinstance(initiative, dict):
|
|
359
|
-
result.add_error(
|
|
461
|
+
result.add_error("Initiative must be a mapping", f"future.initiatives[{i}]")
|
|
360
462
|
continue
|
|
361
463
|
|
|
362
464
|
base_path = f"future.initiatives[{i}]"
|
|
@@ -433,13 +535,15 @@ def validate_future(data: dict[str, Any]) -> ValidationResult:
|
|
|
433
535
|
return result
|
|
434
536
|
|
|
435
537
|
|
|
436
|
-
def validate_sprint_file(file_path: Path) -> ValidationResult:
|
|
538
|
+
def validate_sprint_file(file_path: Path, *, strict: bool = False) -> ValidationResult:
|
|
437
539
|
"""Validate a sprint YAML file from disk.
|
|
438
540
|
|
|
439
|
-
Loads the file and validates its contents.
|
|
541
|
+
Loads the file and validates its contents. In strict mode, loader
|
|
542
|
+
warnings (e.g., unresolvable shard refs) are promoted to errors.
|
|
440
543
|
|
|
441
544
|
Args:
|
|
442
545
|
file_path: Path to sprint YAML file
|
|
546
|
+
strict: If True, treat loader warnings as validation errors
|
|
443
547
|
|
|
444
548
|
Returns:
|
|
445
549
|
ValidationResult with any errors (including load errors)
|
|
@@ -509,12 +613,22 @@ def validate_sprint_file(file_path: Path) -> ValidationResult:
|
|
|
509
613
|
)
|
|
510
614
|
return result
|
|
511
615
|
|
|
512
|
-
# Merge sharded epic files if present
|
|
616
|
+
# Merge sharded epic files if present, capturing warnings in strict mode
|
|
513
617
|
from pennyfarthing_scripts.sprint.loader import _merge_epic_shards
|
|
514
|
-
|
|
618
|
+
if strict:
|
|
619
|
+
import warnings as _warnings
|
|
620
|
+
with _warnings.catch_warnings(record=True) as caught:
|
|
621
|
+
_warnings.simplefilter("always")
|
|
622
|
+
data = _merge_epic_shards(data, file_path.parent)
|
|
623
|
+
for w in caught:
|
|
624
|
+
result.add_error(str(w.message), str(file_path))
|
|
625
|
+
else:
|
|
626
|
+
data = _merge_epic_shards(data, file_path.parent)
|
|
515
627
|
|
|
516
628
|
# Validate loaded data
|
|
517
|
-
|
|
629
|
+
full_result = validate_full_sprint(data)
|
|
630
|
+
result.merge(full_result)
|
|
631
|
+
return result
|
|
518
632
|
|
|
519
633
|
|
|
520
634
|
def format_validation_errors(result: ValidationResult) -> str:
|