@pennyfarthing/core 10.1.0 → 10.3.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 +22 -24
- package/package.json +3 -1
- package/packages/core/dist/cli/commands/doctor-file-layout.test.js.map +1 -1
- package/packages/core/dist/cli/commands/doctor-legacy.test.js +24 -0
- package/packages/core/dist/cli/commands/doctor-legacy.test.js.map +1 -1
- package/packages/core/dist/cli/commands/doctor.d.ts.map +1 -1
- package/packages/core/dist/cli/commands/doctor.js +101 -15
- package/packages/core/dist/cli/commands/doctor.js.map +1 -1
- package/packages/core/dist/cli/commands/e2e-fresh-install.test.js +2 -2
- package/packages/core/dist/cli/commands/e2e-fresh-install.test.js.map +1 -1
- package/packages/core/dist/cli/commands/e2e-upgrade.test.js +2 -2
- 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/theme.js +1 -1
- package/packages/core/dist/cli/commands/theme.js.map +1 -1
- package/packages/core/dist/cli/commands/uninstall.d.ts.map +1 -1
- package/packages/core/dist/cli/commands/uninstall.js +24 -13
- package/packages/core/dist/cli/commands/uninstall.js.map +1 -1
- package/packages/core/dist/cli/commands/update-consolidation.test.js +0 -10
- package/packages/core/dist/cli/commands/update-consolidation.test.js.map +1 -1
- package/packages/core/dist/cli/commands/update.js.map +1 -1
- package/packages/core/dist/cli/ocean-profiles.test.js.map +1 -1
- package/packages/core/dist/cli/theme-maker.test.js +64 -115
- package/packages/core/dist/cli/theme-maker.test.js.map +1 -1
- package/packages/core/dist/cli/utils/themes.d.ts.map +1 -1
- package/packages/core/dist/cli/utils/themes.js +3 -2
- package/packages/core/dist/cli/utils/themes.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/add-ocean-profiles.js +1 -1
- package/packages/core/dist/scripts/add-ocean-profiles.js.map +1 -1
- package/packages/core/dist/scripts/generate-all-spiders.js +2 -0
- package/packages/core/dist/scripts/generate-all-spiders.js.map +1 -1
- package/packages/core/dist/scripts/generate-report.d.ts.map +1 -1
- package/packages/core/dist/scripts/generate-report.js +2 -0
- package/packages/core/dist/scripts/generate-report.js.map +1 -1
- package/packages/core/dist/scripts/generate-spider-report.js.map +1 -1
- package/packages/core/dist/scripts/generate-spider.d.ts.map +1 -1
- package/packages/core/dist/scripts/generate-spider.js +2 -0
- package/packages/core/dist/scripts/generate-spider.js.map +1 -1
- package/packages/core/dist/scripts/validate-ocean-profiles.js +1 -1
- package/packages/core/dist/scripts/validate-ocean-profiles.js.map +1 -1
- 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/output-path-normalizer.d.ts +47 -0
- package/packages/core/dist/workflow/output-path-normalizer.d.ts.map +1 -0
- package/packages/core/dist/workflow/output-path-normalizer.js +79 -0
- package/packages/core/dist/workflow/output-path-normalizer.js.map +1 -0
- package/packages/core/dist/workflow/output-path-normalizer.test.d.ts +16 -0
- package/packages/core/dist/workflow/output-path-normalizer.test.d.ts.map +1 -0
- package/packages/core/dist/workflow/output-path-normalizer.test.js +157 -0
- package/packages/core/dist/workflow/output-path-normalizer.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 +717 -0
- package/packages/core/dist/workflow/tool-watch.test.js.map +1 -0
- package/packages/core/dist/workflow/variable-resolver.js +1 -1
- package/packages/core/dist/workflow/variable-resolver.js.map +1 -1
- 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 +3 -1
- package/pennyfarthing-dist/agents/ba.md +165 -0
- package/pennyfarthing-dist/agents/handoff.md +18 -3
- package/pennyfarthing-dist/agents/sm-finish.md +1 -1
- package/pennyfarthing-dist/agents/sm-handoff.md +27 -4
- package/pennyfarthing-dist/agents/sm.md +11 -5
- package/pennyfarthing-dist/agents/tandem-backseat.md +119 -0
- package/pennyfarthing-dist/commands/ba.md +17 -0
- package/pennyfarthing-dist/commands/setup.md +4 -0
- package/pennyfarthing-dist/guides/agent-behavior.md +62 -6
- package/pennyfarthing-dist/guides/bikelane.md +3 -2
- package/pennyfarthing-dist/guides/scale-levels.md +4 -6
- package/pennyfarthing-dist/guides/tandem-protocol.md +158 -0
- package/pennyfarthing-dist/guides/workflow-schema.md +1 -1
- package/pennyfarthing-dist/personas/themes/a-team.yaml +30 -0
- package/pennyfarthing-dist/personas/themes/alice-in-wonderland.yaml +30 -0
- package/pennyfarthing-dist/personas/themes/battlestar-galactica.yaml +30 -0
- package/pennyfarthing-dist/personas/themes/blade-runner.yaml +30 -0
- package/pennyfarthing-dist/personas/themes/catch-22.yaml +30 -0
- package/pennyfarthing-dist/personas/themes/control.yaml +30 -0
- package/pennyfarthing-dist/personas/themes/cowboy-bebop.yaml +31 -0
- package/pennyfarthing-dist/personas/themes/discworld.yaml +32 -1
- package/pennyfarthing-dist/personas/themes/doctor-who.yaml +31 -0
- package/pennyfarthing-dist/personas/themes/dune.yaml +32 -0
- package/pennyfarthing-dist/personas/themes/fifth-element.yaml +327 -0
- package/pennyfarthing-dist/personas/themes/firefly.yaml +31 -0
- package/pennyfarthing-dist/personas/themes/game-of-thrones.yaml +30 -0
- package/pennyfarthing-dist/personas/themes/harry-potter.yaml +30 -0
- package/pennyfarthing-dist/personas/themes/hitchhikers-guide.yaml +30 -0
- package/pennyfarthing-dist/personas/themes/lord-of-the-rings.yaml +30 -0
- package/pennyfarthing-dist/personas/themes/mad-max.yaml +30 -0
- package/pennyfarthing-dist/personas/themes/mash.yaml +33 -0
- package/pennyfarthing-dist/personas/themes/princess-bride.yaml +34 -0
- package/pennyfarthing-dist/personas/themes/sandman.yaml +33 -0
- package/pennyfarthing-dist/personas/themes/star-trek-tng.yaml +34 -0
- package/pennyfarthing-dist/personas/themes/star-wars.yaml +33 -0
- package/pennyfarthing-dist/personas/themes/the-expanse.yaml +30 -0
- package/pennyfarthing-dist/personas/themes/the-matrix.yaml +30 -0
- package/pennyfarthing-dist/personas/themes/watchmen.yaml +30 -0
- package/pennyfarthing-dist/personas/themes/west-wing.yaml +30 -0
- package/pennyfarthing-dist/personas/themes/x-files.yaml +30 -0
- package/pennyfarthing-dist/scripts/README.md +1 -1
- package/pennyfarthing-dist/scripts/core/agent-session.sh +1 -1
- package/pennyfarthing-dist/scripts/hooks/bell-mode-hook.sh +131 -54
- package/pennyfarthing-dist/scripts/hooks/post-merge.sh +20 -10
- package/pennyfarthing-dist/scripts/misc/statusline.sh +50 -8
- package/pennyfarthing-dist/scripts/portraits/generate-portraits.py +2 -2
- package/pennyfarthing-dist/scripts/validation/validate-agent-schema.sh +1 -0
- package/pennyfarthing-dist/scripts/workflow/README.md +2 -2
- package/pennyfarthing-dist/scripts/workflow/finish-story.sh +10 -189
- package/pennyfarthing-dist/skills/skill-registry.schema.json +8 -0
- package/pennyfarthing-dist/skills/skill-registry.yaml +1 -1
- package/pennyfarthing-dist/skills/sprint/skill.md +25 -2
- package/pennyfarthing-dist/skills/theme/skill.md +1 -1
- package/pennyfarthing-dist/skills/workflow/skill.md +24 -1
- package/pennyfarthing-dist/workflows/architecture/workflow.yaml +65 -0
- package/pennyfarthing-dist/workflows/architecture.yaml +2 -2
- package/pennyfarthing-dist/workflows/bdd-tandem.yaml +70 -0
- package/pennyfarthing-dist/workflows/epics-and-stories/workflow.yaml +2 -2
- package/pennyfarthing-dist/workflows/implementation-readiness/workflow.yaml +2 -2
- package/pennyfarthing-dist/workflows/prd/workflow.yaml +2 -2
- package/pennyfarthing-dist/workflows/product-brief/workflow.yaml +2 -2
- package/pennyfarthing-dist/workflows/project-context/workflow.yaml +2 -2
- package/pennyfarthing-dist/workflows/quick-dev/workflow.yaml +2 -2
- package/pennyfarthing-dist/workflows/research/workflow.yaml +2 -2
- package/pennyfarthing-dist/workflows/retrospective/workflow.yaml +1 -1
- package/pennyfarthing-dist/workflows/sprint-planning/workflow.yaml +3 -3
- package/pennyfarthing-dist/workflows/tdd-tandem.yaml +61 -0
- package/pennyfarthing-dist/workflows/ux-design/workflow.yaml +2 -2
- package/pennyfarthing_scripts/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/hooks.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/pretooluse_hook.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/bikerack/__init__.py +36 -0
- package/pennyfarthing_scripts/bikerack/__main__.py +5 -0
- package/pennyfarthing_scripts/bikerack/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bikerack/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bikerack/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bikerack/__pycache__/launcher.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bikerack/cli.py +148 -0
- package/pennyfarthing_scripts/bikerack/launcher.py +181 -0
- package/pennyfarthing_scripts/brownfield/__init__.py +6 -6
- package/pennyfarthing_scripts/brownfield/__main__.py +1 -0
- package/pennyfarthing_scripts/brownfield/cli.py +0 -1
- package/pennyfarthing_scripts/brownfield/discover.py +1 -2
- package/pennyfarthing_scripts/cli.py +16 -6
- package/pennyfarthing_scripts/codemarkers/__init__.py +5 -1
- package/pennyfarthing_scripts/codemarkers/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/codemarkers/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/codemarkers/__pycache__/analyze.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/codemarkers/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/codemarkers/__pycache__/formatters.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/codemarkers/__pycache__/models.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/codemarkers/analyze.py +177 -2
- package/pennyfarthing_scripts/codemarkers/cli.py +50 -0
- package/pennyfarthing_scripts/codemarkers/formatters.py +0 -1
- package/pennyfarthing_scripts/codemarkers/models.py +15 -0
- package/pennyfarthing_scripts/common/__init__.py +8 -9
- package/pennyfarthing_scripts/common/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/common/__pycache__/config.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/common/config.py +1 -1
- package/pennyfarthing_scripts/complexity/__init__.py +1 -1
- package/pennyfarthing_scripts/complexity/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/complexity/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/complexity/__pycache__/analyze.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/complexity/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/complexity/__pycache__/formatters.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/complexity/__pycache__/models.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/complexity/analyze.py +1 -1
- package/pennyfarthing_scripts/complexity/cli.py +5 -1
- package/pennyfarthing_scripts/complexity/formatters.py +1 -1
- package/pennyfarthing_scripts/context.py +14 -15
- package/pennyfarthing_scripts/deadcode/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/deadcode/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/deadcode/__pycache__/analyze.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/deadcode/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/deadcode/__pycache__/formatters.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/deadcode/__pycache__/models.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/deadcode/analyze.py +3 -4
- package/pennyfarthing_scripts/deadcode/cli.py +2 -2
- package/pennyfarthing_scripts/dependencies/__init__.py +2 -2
- package/pennyfarthing_scripts/dependencies/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/dependencies/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/dependencies/__pycache__/analyze.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/dependencies/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/dependencies/__pycache__/formatters.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/dependencies/__pycache__/models.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/dependencies/analyze.py +1 -1
- package/pennyfarthing_scripts/dependencies/cli.py +8 -4
- package/pennyfarthing_scripts/dependencies/formatters.py +1 -1
- package/pennyfarthing_scripts/git/__init__.py +5 -5
- package/pennyfarthing_scripts/git/create_branches.py +3 -2
- package/pennyfarthing_scripts/git/status_all.py +1 -1
- package/pennyfarthing_scripts/healthscore/__init__.py +2 -2
- package/pennyfarthing_scripts/healthscore/__main__.py +8 -0
- package/pennyfarthing_scripts/healthscore/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/healthscore/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/healthscore/__pycache__/analyze.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/healthscore/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/healthscore/__pycache__/formatters.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/healthscore/__pycache__/models.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/healthscore/analyze.py +452 -21
- package/pennyfarthing_scripts/healthscore/cli.py +5 -1
- package/pennyfarthing_scripts/healthscore/models.py +0 -1
- package/pennyfarthing_scripts/hooks.py +8 -11
- package/pennyfarthing_scripts/hotspots/__init__.py +6 -6
- package/pennyfarthing_scripts/hotspots/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/hotspots/__pycache__/analyze.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/hotspots/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/hotspots/__pycache__/models.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/hotspots/analyze.py +128 -14
- package/pennyfarthing_scripts/hotspots/cli.py +2 -2
- package/pennyfarthing_scripts/hotspots/models.py +0 -1
- package/pennyfarthing_scripts/jira/__init__.py +15 -17
- package/pennyfarthing_scripts/jira/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/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__/reconcile.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/story.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/sync.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/bidirectional.py +2 -3
- package/pennyfarthing_scripts/jira/claim.py +21 -0
- package/pennyfarthing_scripts/jira/cli.py +2 -2
- package/pennyfarthing_scripts/jira/client.py +4 -4
- package/pennyfarthing_scripts/jira/create.py +45 -1
- package/pennyfarthing_scripts/jira/epic.py +3 -2
- package/pennyfarthing_scripts/jira/reconcile.py +0 -1
- package/pennyfarthing_scripts/jira/story.py +2 -0
- package/pennyfarthing_scripts/jira/sync.py +1 -1
- package/pennyfarthing_scripts/migration/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/migration/__pycache__/session.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/migration/__pycache__/skill.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/migration/__pycache__/step.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/migration/__pycache__/validate.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/migration/skill.py +0 -1
- package/pennyfarthing_scripts/migration/step.py +0 -1
- package/pennyfarthing_scripts/migration/validate.py +8 -5
- package/pennyfarthing_scripts/patch_mode.py +2 -2
- package/pennyfarthing_scripts/preflight/__init__.py +1 -1
- package/pennyfarthing_scripts/preflight/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/preflight/__pycache__/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/__init__.py +2 -0
- 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__/persona.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/prime/__pycache__/tiers.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/prime/cli.py +18 -1
- package/pennyfarthing_scripts/prime/loader.py +72 -3
- package/pennyfarthing_scripts/prime/persona.py +4 -2
- package/pennyfarthing_scripts/prime/tiers.py +17 -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__/archive.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/archive_epic.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/epic_add.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/import_epic.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/loader.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/status.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/story_add.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/story_finish.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/story_update.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/validate_cmd.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/validator.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/work.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/yaml_io.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/archive.py +0 -1
- package/pennyfarthing_scripts/sprint/archive_epic.py +1 -4
- package/pennyfarthing_scripts/sprint/cli.py +34 -28
- package/pennyfarthing_scripts/sprint/epic_add.py +8 -1
- package/pennyfarthing_scripts/sprint/import_epic.py +42 -18
- package/pennyfarthing_scripts/sprint/loader.py +6 -0
- package/pennyfarthing_scripts/sprint/status.py +1 -2
- package/pennyfarthing_scripts/sprint/story_add.py +2 -2
- package/pennyfarthing_scripts/sprint/story_finish.py +3 -5
- package/pennyfarthing_scripts/sprint/story_update.py +11 -3
- package/pennyfarthing_scripts/sprint/validate_cmd.py +0 -1
- package/pennyfarthing_scripts/sprint/validator.py +120 -6
- package/pennyfarthing_scripts/sprint/work.py +1 -4
- package/pennyfarthing_scripts/sprint/yaml_io.py +10 -2
- package/pennyfarthing_scripts/story/__init__.py +14 -16
- package/pennyfarthing_scripts/story/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/story/__pycache__/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__/conftest.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_bikerack.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_epic_shard_validation.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_validator.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_bikerack.py +785 -0
- package/pennyfarthing_scripts/tests/test_brownfield.py +10 -13
- package/pennyfarthing_scripts/tests/test_cli_modules.py +0 -4
- package/pennyfarthing_scripts/tests/test_codemarkers.py +13 -8
- package/pennyfarthing_scripts/tests/test_common.py +9 -4
- package/pennyfarthing_scripts/tests/test_epic_shard_validation.py +699 -0
- package/pennyfarthing_scripts/tests/test_git_utils.py +10 -13
- package/pennyfarthing_scripts/tests/test_healthscore.py +17 -25
- package/pennyfarthing_scripts/tests/test_jira_package.py +0 -3
- package/pennyfarthing_scripts/tests/test_package_structure.py +3 -16
- package/pennyfarthing_scripts/tests/test_patch_mode.py +7 -11
- package/pennyfarthing_scripts/tests/test_prime.py +39 -21
- package/pennyfarthing_scripts/tests/test_sprint_package.py +3 -8
- package/pennyfarthing_scripts/tests/test_sprint_validator.py +53 -5
- package/pennyfarthing_scripts/tests/test_story_add.py +3 -7
- package/pennyfarthing_scripts/tests/test_story_package.py +0 -3
- package/pennyfarthing_scripts/tests/test_story_update.py +5 -10
- package/pennyfarthing_scripts/tests/test_tiers.py +18 -17
- package/pennyfarthing_scripts/tests/test_token_counting.py +19 -13
- package/pennyfarthing_scripts/tests/test_topology_loader.py +620 -0
- package/pennyfarthing_scripts/tests/test_validate_cmd.py +2 -7
- package/pennyfarthing_scripts/tests/test_workflow_check.py +0 -2
- package/pennyfarthing_scripts/tests/test_yaml_io.py +0 -3
- package/pennyfarthing_scripts/theme/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/theme/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/theme/cli.py +3 -2
- package/pennyfarthing_scripts/validate/__init__.py +21 -0
- package/pennyfarthing_scripts/validate/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/validate/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/validate/adapters/__init__.py +0 -0
- package/pennyfarthing_scripts/validate/adapters/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/validate/adapters/__pycache__/agent.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/validate/adapters/__pycache__/schema.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/validate/adapters/__pycache__/skill_command.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/validate/adapters/__pycache__/sprint.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/validate/adapters/__pycache__/workflow.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/validate/adapters/agent.py +239 -0
- package/pennyfarthing_scripts/validate/adapters/schema.py +30 -0
- package/pennyfarthing_scripts/validate/adapters/skill_command.py +291 -0
- package/pennyfarthing_scripts/validate/adapters/sprint.py +69 -0
- package/pennyfarthing_scripts/validate/adapters/workflow.py +320 -0
- package/pennyfarthing_scripts/validate/cli.py +141 -0
- package/pennyfarthing_scripts/welcome_hook.py +2 -3
- package/pennyfarthing_scripts/workflow.py +3 -3
- package/scripts/README.md +3 -15
- package/pennyfarthing-dist/commands/benchmark-control.md +0 -69
- package/pennyfarthing-dist/commands/benchmark.md +0 -485
- package/pennyfarthing-dist/commands/job-fair.md +0 -102
- package/pennyfarthing-dist/commands/solo.md +0 -447
- package/pennyfarthing-dist/guides/benchmarks.md +0 -62
- package/pennyfarthing-dist/scripts/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
|
@@ -10,9 +10,7 @@ from datetime import date
|
|
|
10
10
|
from pathlib import Path
|
|
11
11
|
from typing import Any
|
|
12
12
|
|
|
13
|
-
import
|
|
14
|
-
|
|
15
|
-
from pennyfarthing_scripts.common.config import get_project_root, load_yaml_config
|
|
13
|
+
from pennyfarthing_scripts.common.config import get_project_root
|
|
16
14
|
|
|
17
15
|
|
|
18
16
|
def parse_epics_markdown(content: str) -> dict[str, Any]:
|
|
@@ -195,13 +193,13 @@ def generate_initiative_yaml(
|
|
|
195
193
|
today = date.today().isoformat()
|
|
196
194
|
|
|
197
195
|
lines = [
|
|
198
|
-
|
|
196
|
+
" # ==========================================================================",
|
|
199
197
|
f" # {initiative_name.upper()}",
|
|
200
198
|
f" # Imported from: {source_file}",
|
|
201
199
|
f" # Date: {today}",
|
|
202
|
-
|
|
200
|
+
" # ==========================================================================",
|
|
203
201
|
f' - name: "{initiative_name}"',
|
|
204
|
-
|
|
202
|
+
" description: |",
|
|
205
203
|
]
|
|
206
204
|
|
|
207
205
|
# Add description lines with proper indentation
|
|
@@ -210,10 +208,10 @@ def generate_initiative_yaml(
|
|
|
210
208
|
|
|
211
209
|
lines.extend(
|
|
212
210
|
[
|
|
213
|
-
|
|
214
|
-
|
|
211
|
+
" status: ready",
|
|
212
|
+
" blocked_by: null",
|
|
215
213
|
f" total_points: {parsed['total_points']}",
|
|
216
|
-
|
|
214
|
+
" epics:",
|
|
217
215
|
]
|
|
218
216
|
)
|
|
219
217
|
|
|
@@ -228,14 +226,14 @@ def generate_initiative_yaml(
|
|
|
228
226
|
[
|
|
229
227
|
f" - id: epic-{current_epic_num}",
|
|
230
228
|
f' title: "{epic["title"]}"',
|
|
231
|
-
|
|
229
|
+
" description: |",
|
|
232
230
|
f" {epic.get('description', epic['title'])}",
|
|
233
231
|
f" points: {epic_points}",
|
|
234
|
-
|
|
232
|
+
" priority: P1",
|
|
235
233
|
f' marker: "{marker}"',
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
234
|
+
" repos: pennyfarthing",
|
|
235
|
+
" status: planning",
|
|
236
|
+
" stories:",
|
|
239
237
|
]
|
|
240
238
|
)
|
|
241
239
|
|
|
@@ -247,12 +245,12 @@ def generate_initiative_yaml(
|
|
|
247
245
|
[
|
|
248
246
|
f' - id: "{story_id}"',
|
|
249
247
|
f' title: "{title}"',
|
|
250
|
-
|
|
248
|
+
" description: |",
|
|
251
249
|
f" {story.get('description', title)}",
|
|
252
250
|
f" points: {story.get('points', 1)}",
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
251
|
+
" priority: P0",
|
|
252
|
+
" status: planning",
|
|
253
|
+
" repos: pennyfarthing",
|
|
256
254
|
]
|
|
257
255
|
)
|
|
258
256
|
|
|
@@ -304,6 +302,32 @@ def import_epic(
|
|
|
304
302
|
# Get next epic number
|
|
305
303
|
start_epic_num = get_next_epic_number(root)
|
|
306
304
|
|
|
305
|
+
# Validate each parsed epic before writing (ADR-0022)
|
|
306
|
+
from pennyfarthing_scripts.sprint.validator import validate_epic_shard
|
|
307
|
+
|
|
308
|
+
epic_num = start_epic_num
|
|
309
|
+
for epic in parsed["epics"]:
|
|
310
|
+
# Build a shard-like dict for validation
|
|
311
|
+
shard_dict = {
|
|
312
|
+
"id": str(epic_num),
|
|
313
|
+
"title": epic.get("title", ""),
|
|
314
|
+
"status": "planning",
|
|
315
|
+
"stories": [
|
|
316
|
+
{
|
|
317
|
+
"id": f"{epic_num}-{s['num']}",
|
|
318
|
+
"title": s.get("title", ""),
|
|
319
|
+
"points": s.get("points", 1),
|
|
320
|
+
"status": "planning",
|
|
321
|
+
}
|
|
322
|
+
for s in epic.get("stories", [])
|
|
323
|
+
],
|
|
324
|
+
}
|
|
325
|
+
validation = validate_epic_shard(shard_dict)
|
|
326
|
+
if not validation.valid:
|
|
327
|
+
error_msgs = "; ".join(e.message for e in validation.errors)
|
|
328
|
+
return {"success": False, "error": f"Epic {epic_num} validation failed: {error_msgs}"}
|
|
329
|
+
epic_num += 1
|
|
330
|
+
|
|
307
331
|
# Generate YAML
|
|
308
332
|
relative_path = str(epics_path.relative_to(root)) if epics_path.is_relative_to(root) else str(epics_path)
|
|
309
333
|
new_yaml, next_epic_num = generate_initiative_yaml(
|
|
@@ -5,6 +5,7 @@ Provides access to sprint/current-sprint.yaml data.
|
|
|
5
5
|
Supports sharded per-epic format (epic-{ref}.yaml shard files).
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
+
import warnings
|
|
8
9
|
from pathlib import Path
|
|
9
10
|
from typing import Any
|
|
10
11
|
|
|
@@ -40,6 +41,11 @@ def _merge_epic_shards(data: dict[str, Any], sprint_dir: Path) -> dict[str, Any]
|
|
|
40
41
|
epic_data = load_yaml_config(epic_file)
|
|
41
42
|
if epic_data is not None:
|
|
42
43
|
merged_epics.append(epic_data)
|
|
44
|
+
else:
|
|
45
|
+
warnings.warn(
|
|
46
|
+
f"Sprint epic ref '{ref}' not found: {epic_file}",
|
|
47
|
+
stacklevel=2,
|
|
48
|
+
)
|
|
43
49
|
|
|
44
50
|
data["epics"] = merged_epics
|
|
45
51
|
return data
|
|
@@ -6,7 +6,7 @@ Provides functions for getting and displaying sprint status.
|
|
|
6
6
|
|
|
7
7
|
from typing import Any
|
|
8
8
|
|
|
9
|
-
from pennyfarthing_scripts.sprint.loader import get_all_stories, get_sprint_info
|
|
9
|
+
from pennyfarthing_scripts.sprint.loader import get_all_stories, get_sprint_info
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
def get_sprint_status(filter_status: str | None = None) -> dict[str, Any]:
|
|
@@ -99,7 +99,6 @@ def main(args: list[str] | None = None) -> int:
|
|
|
99
99
|
Exit code
|
|
100
100
|
"""
|
|
101
101
|
import argparse
|
|
102
|
-
import sys
|
|
103
102
|
|
|
104
103
|
parser = argparse.ArgumentParser(description="Show sprint status")
|
|
105
104
|
parser.add_argument(
|
|
@@ -312,8 +312,8 @@ def story_add_command(
|
|
|
312
312
|
raise click.ClickException("POINTS is required")
|
|
313
313
|
try:
|
|
314
314
|
init_points = int(title)
|
|
315
|
-
except ValueError:
|
|
316
|
-
raise click.ClickException(f"POINTS must be an integer, got '{title}'")
|
|
315
|
+
except ValueError as err:
|
|
316
|
+
raise click.ClickException(f"POINTS must be an integer, got '{title}'") from err
|
|
317
317
|
|
|
318
318
|
result = add_initiative_story(
|
|
319
319
|
initiative_slug=initiative,
|
|
@@ -7,7 +7,7 @@ Steps:
|
|
|
7
7
|
1. Archive session file to sprint/archive/{jira-key}-session.md
|
|
8
8
|
2. Squash merge PR via gh (handle already-merged)
|
|
9
9
|
3. Transition Jira to Done
|
|
10
|
-
4. Update sprint YAML (status: done, completed date
|
|
10
|
+
4. Update sprint YAML (status: done, completed date)
|
|
11
11
|
5. Archive completed epics
|
|
12
12
|
6. Git cleanup (checkout develop, pull, delete local branch)
|
|
13
13
|
7. Remove session file
|
|
@@ -16,6 +16,7 @@ Steps:
|
|
|
16
16
|
import re
|
|
17
17
|
import shutil
|
|
18
18
|
import subprocess
|
|
19
|
+
import sys
|
|
19
20
|
from datetime import date
|
|
20
21
|
from pathlib import Path
|
|
21
22
|
from typing import Any
|
|
@@ -23,7 +24,6 @@ from typing import Any
|
|
|
23
24
|
from pennyfarthing_scripts.sprint.loader import find_epic, find_story
|
|
24
25
|
from pennyfarthing_scripts.sprint.yaml_io import read_sprint, write_sprint
|
|
25
26
|
|
|
26
|
-
|
|
27
27
|
SESSION_FIELD_RE = re.compile(r"\*\*(\w[\w\s]*):\*\*\s*(.*)")
|
|
28
28
|
|
|
29
29
|
|
|
@@ -175,8 +175,6 @@ def finish_story(
|
|
|
175
175
|
if story:
|
|
176
176
|
story["status"] = "done"
|
|
177
177
|
story["completed"] = today
|
|
178
|
-
if "assigned_to" in story:
|
|
179
|
-
del story["assigned_to"]
|
|
180
178
|
write_sprint(sprint_path, data)
|
|
181
179
|
steps.append({"step": 4, "action": "yaml_update", "status": "done", "completed": today})
|
|
182
180
|
else:
|
|
@@ -186,7 +184,7 @@ def finish_story(
|
|
|
186
184
|
|
|
187
185
|
# --- Step 5: Archive completed epics ---
|
|
188
186
|
result = _run(
|
|
189
|
-
[
|
|
187
|
+
[sys.executable, "-m", "pennyfarthing_scripts.cli", "sprint", "epic", "archive"],
|
|
190
188
|
cwd=str(project_root),
|
|
191
189
|
)
|
|
192
190
|
steps.append({"step": 5, "action": "archive_epics", "ran": True})
|
|
@@ -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:
|
|
@@ -7,11 +7,8 @@ Provides functions for starting and managing work on stories.
|
|
|
7
7
|
from typing import Any
|
|
8
8
|
|
|
9
9
|
from pennyfarthing_scripts.sprint.loader import (
|
|
10
|
-
find_epic,
|
|
11
|
-
find_story,
|
|
12
10
|
get_stories_by_status,
|
|
13
11
|
get_story_by_id,
|
|
14
|
-
load_sprint,
|
|
15
12
|
)
|
|
16
13
|
|
|
17
14
|
|
|
@@ -220,7 +217,7 @@ def main(args: list[str] | None = None) -> int:
|
|
|
220
217
|
print(f"Story: {story.get('id')}")
|
|
221
218
|
print(f"Title: {story.get('title')}")
|
|
222
219
|
print(f"Points: {story.get('points')}")
|
|
223
|
-
print(
|
|
220
|
+
print("Status: Available")
|
|
224
221
|
return 0
|
|
225
222
|
else:
|
|
226
223
|
print(f"Not available: {result.get('error') or result.get('reason')}", file=sys.stderr)
|
|
@@ -272,7 +272,9 @@ def canonical_dump(data: Any) -> str:
|
|
|
272
272
|
def _get_epic_ref(epic: Mapping) -> str:
|
|
273
273
|
"""Get the canonical reference ID for an epic shard file.
|
|
274
274
|
|
|
275
|
-
|
|
275
|
+
Priority: Jira key > numeric ID extracted from epic-N > raw ID.
|
|
276
|
+
Strips 'epic-' prefix from IDs to prevent double-prefix filenames
|
|
277
|
+
(e.g., epic-epic-94.yaml). See ADR-0022.
|
|
276
278
|
"""
|
|
277
279
|
jira = epic.get("jira")
|
|
278
280
|
epic_id = str(epic.get("id", ""))
|
|
@@ -281,7 +283,13 @@ def _get_epic_ref(epic: Mapping) -> str:
|
|
|
281
283
|
return str(jira)
|
|
282
284
|
if JIRA_PATTERN.match(epic_id):
|
|
283
285
|
return epic_id
|
|
284
|
-
|
|
286
|
+
|
|
287
|
+
# Strip epic- prefix to prevent double-prefix filenames
|
|
288
|
+
# e.g., "epic-94" -> "94" so file becomes "epic-94.yaml" not "epic-epic-94.yaml"
|
|
289
|
+
stripped = epic_id
|
|
290
|
+
while stripped.startswith("epic-"):
|
|
291
|
+
stripped = stripped[5:]
|
|
292
|
+
return stripped or epic_id
|
|
285
293
|
|
|
286
294
|
|
|
287
295
|
def _write_yaml_file(path: Path, data: Any) -> None:
|
|
@@ -15,6 +15,20 @@ Usage:
|
|
|
15
15
|
"""
|
|
16
16
|
|
|
17
17
|
# Re-export common functions
|
|
18
|
+
# Import submodules
|
|
19
|
+
# CLI entry point - import module, not function, so "from story import cli" gets the module
|
|
20
|
+
from pennyfarthing_scripts.story import (
|
|
21
|
+
cli,
|
|
22
|
+
create,
|
|
23
|
+
size,
|
|
24
|
+
template,
|
|
25
|
+
)
|
|
26
|
+
from pennyfarthing_scripts.story.cli import main
|
|
27
|
+
from pennyfarthing_scripts.story.create import (
|
|
28
|
+
create_story,
|
|
29
|
+
generate_story_yaml,
|
|
30
|
+
validate_points,
|
|
31
|
+
)
|
|
18
32
|
from pennyfarthing_scripts.story.size import (
|
|
19
33
|
SIZING_GUIDELINES,
|
|
20
34
|
format_size_info,
|
|
@@ -26,22 +40,6 @@ from pennyfarthing_scripts.story.template import (
|
|
|
26
40
|
get_all_templates,
|
|
27
41
|
get_template,
|
|
28
42
|
)
|
|
29
|
-
from pennyfarthing_scripts.story.create import (
|
|
30
|
-
create_story,
|
|
31
|
-
generate_story_yaml,
|
|
32
|
-
validate_points,
|
|
33
|
-
)
|
|
34
|
-
|
|
35
|
-
# Import submodules
|
|
36
|
-
from pennyfarthing_scripts.story import (
|
|
37
|
-
create,
|
|
38
|
-
size,
|
|
39
|
-
template,
|
|
40
|
-
)
|
|
41
|
-
|
|
42
|
-
# CLI entry point - import module, not function, so "from story import cli" gets the module
|
|
43
|
-
from pennyfarthing_scripts.story import cli
|
|
44
|
-
from pennyfarthing_scripts.story.cli import main
|
|
45
43
|
|
|
46
44
|
__all__ = [
|
|
47
45
|
# Size
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -19,7 +19,6 @@ from dataclasses import dataclass, field
|
|
|
19
19
|
from pathlib import Path
|
|
20
20
|
from typing import Any
|
|
21
21
|
|
|
22
|
-
|
|
23
22
|
# Default cache location for SWE-bench data
|
|
24
23
|
DEFAULT_CACHE_PATH = "/tmp/swebench_all.json"
|
|
25
24
|
|
|
@@ -108,7 +107,7 @@ def load_swebench_data(cache_path: str | Path = DEFAULT_CACHE_PATH) -> list[dict
|
|
|
108
107
|
FileNotFoundError: If cache file doesn't exist
|
|
109
108
|
json.JSONDecodeError: If cache file is invalid JSON
|
|
110
109
|
"""
|
|
111
|
-
with open(cache_path
|
|
110
|
+
with open(cache_path) as f:
|
|
112
111
|
return json.load(f)
|
|
113
112
|
|
|
114
113
|
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/pennyfarthing_scripts/tests/__pycache__/test_healthscore.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
|
|
Binary file
|