@pennyfarthing/core 10.2.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 +11 -8
- package/package.json +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/theme.js +1 -1
- package/packages/core/dist/cli/commands/theme.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/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.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.test.js.map +1 -1
- 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/tool-watch.test.js +1 -2
- package/packages/core/dist/workflow/tool-watch.test.js.map +1 -1
- package/packages/core/dist/workflow/variable-resolver.js +1 -1
- package/packages/core/dist/workflow/variable-resolver.js.map +1 -1
- package/pennyfarthing-dist/agents/README.md +3 -1
- package/pennyfarthing-dist/agents/ba.md +165 -0
- package/pennyfarthing-dist/commands/ba.md +17 -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 +31 -0
- 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 +32 -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/core/agent-session.sh +1 -1
- package/pennyfarthing-dist/scripts/hooks/__pycache__/question_reflector_check.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/scripts/portraits/generate-portraits.py +2 -2
- package/pennyfarthing-dist/scripts/validation/validate-agent-schema.sh +1 -0
- package/pennyfarthing-dist/skills/theme/skill.md +1 -1
- package/pennyfarthing-dist/workflows/architecture/workflow.yaml +2 -2
- package/pennyfarthing-dist/workflows/architecture.yaml +2 -2
- 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/ux-design/workflow.yaml +2 -2
- package/pennyfarthing_scripts/__pycache__/__init__.cpython-311.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/__init__.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.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__/pretooluse_hook.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/schema_validation_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/__pycache__/workflow.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bellmode_hook.py +1 -1
- 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/__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/cli.py +5 -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/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/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/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/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/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/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 +2 -1
- 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/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__/compat.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__/mappings.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/models.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/migration/__pycache__/__init__.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/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/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/prime/__init__.py +2 -0
- package/pennyfarthing_scripts/prime/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/prime/__pycache__/__main__.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 +13 -0
- package/pennyfarthing_scripts/prime/loader.py +70 -0
- package/pennyfarthing_scripts/prime/persona.py +2 -1
- package/pennyfarthing_scripts/prime/tiers.py +13 -0
- 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/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/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_bikerack.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_epic_shard_validation.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_workflow_cli.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_yaml_io.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/test_bikerack.py +785 -0
- package/pennyfarthing_scripts/tests/test_topology_loader.py +620 -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/validate/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/validate/__pycache__/cli.cpython-314.pyc +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/skill_command.py +0 -1
- package/packages/core/dist/workflow/context-watch.d.ts +0 -80
- package/packages/core/dist/workflow/context-watch.d.ts.map +0 -1
- package/packages/core/dist/workflow/context-watch.js +0 -235
- package/packages/core/dist/workflow/context-watch.js.map +0 -1
- package/packages/core/dist/workflow/context-watch.test.d.ts +0 -1
- package/packages/core/dist/workflow/context-watch.test.d.ts.map +0 -1
- package/packages/core/dist/workflow/context-watch.test.js +0 -746
- package/packages/core/dist/workflow/context-watch.test.js.map +0 -1
- package/pennyfarthing_scripts/__pycache__/bellmode_hook.cpython-314.pyc +0 -0
|
@@ -0,0 +1,620 @@
|
|
|
1
|
+
"""Tests for repos topology loading in prime context.
|
|
2
|
+
|
|
3
|
+
Story 87-2: Wire topology into agent prime context.
|
|
4
|
+
|
|
5
|
+
Tests that repos.yaml topology data is loaded, formatted, and injected
|
|
6
|
+
into agent prime context at the correct tiers.
|
|
7
|
+
|
|
8
|
+
Acceptance Criteria:
|
|
9
|
+
- AC1: load_repos_topology() loads and formats repos.yaml topology
|
|
10
|
+
- AC2: FULL tier includes topology component
|
|
11
|
+
- AC3: JSON output includes topology in components list
|
|
12
|
+
- AC4: Topology included in FULL/REFRESH/HANDOFF, excluded from MINIMAL
|
|
13
|
+
- AC5: Tests cover happy path, errors, token counting, backwards compat
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from unittest.mock import patch
|
|
18
|
+
|
|
19
|
+
import pytest
|
|
20
|
+
import yaml
|
|
21
|
+
|
|
22
|
+
# =============================================================================
|
|
23
|
+
# Fixtures
|
|
24
|
+
# =============================================================================
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
VALID_REPOS_YAML = {
|
|
28
|
+
"repos": {
|
|
29
|
+
"orchestrator": {
|
|
30
|
+
"path": ".",
|
|
31
|
+
"type": "orchestrator",
|
|
32
|
+
"description": "Sprint management",
|
|
33
|
+
"owns": ["sprint/**", "docs/**", ".session/**"],
|
|
34
|
+
"never_edit": ["node_modules/**", ".pennyfarthing/agents/**"],
|
|
35
|
+
"symlinks": {
|
|
36
|
+
".pennyfarthing/agents": "pennyfarthing/pennyfarthing-dist/agents",
|
|
37
|
+
},
|
|
38
|
+
"ui_layer": "none",
|
|
39
|
+
},
|
|
40
|
+
"pennyfarthing": {
|
|
41
|
+
"path": "pennyfarthing",
|
|
42
|
+
"type": "framework",
|
|
43
|
+
"description": "Framework source",
|
|
44
|
+
"owns": ["pennyfarthing-dist/**", "packages/**"],
|
|
45
|
+
"never_edit": ["node_modules/**", "packages/*/dist/**"],
|
|
46
|
+
"symlinks": {},
|
|
47
|
+
"ui_layer": "react",
|
|
48
|
+
"components_path": "packages/cyclist/src/components",
|
|
49
|
+
},
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
LEGACY_REPOS_YAML = {
|
|
54
|
+
"repos": {
|
|
55
|
+
"myrepo": {
|
|
56
|
+
"path": ".",
|
|
57
|
+
"type": "monolith",
|
|
58
|
+
"description": "Legacy repo with no topology fields",
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@pytest.fixture
|
|
65
|
+
def topology_project(tmp_path: Path) -> Path:
|
|
66
|
+
"""Set up a project with valid repos.yaml containing topology fields."""
|
|
67
|
+
pf_dir = tmp_path / ".pennyfarthing"
|
|
68
|
+
pf_dir.mkdir()
|
|
69
|
+
repos_file = pf_dir / "repos.yaml"
|
|
70
|
+
repos_file.write_text(yaml.dump(VALID_REPOS_YAML, default_flow_style=False))
|
|
71
|
+
return tmp_path
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@pytest.fixture
|
|
75
|
+
def legacy_project(tmp_path: Path) -> Path:
|
|
76
|
+
"""Set up a project with repos.yaml that has no topology fields."""
|
|
77
|
+
pf_dir = tmp_path / ".pennyfarthing"
|
|
78
|
+
pf_dir.mkdir()
|
|
79
|
+
repos_file = pf_dir / "repos.yaml"
|
|
80
|
+
repos_file.write_text(yaml.dump(LEGACY_REPOS_YAML, default_flow_style=False))
|
|
81
|
+
return tmp_path
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@pytest.fixture
|
|
85
|
+
def full_project(tmp_path: Path) -> Path:
|
|
86
|
+
"""Set up a complete project structure for tier/CLI tests."""
|
|
87
|
+
pf_dir = tmp_path / ".pennyfarthing"
|
|
88
|
+
pf_dir.mkdir()
|
|
89
|
+
|
|
90
|
+
# repos.yaml with topology
|
|
91
|
+
(pf_dir / "repos.yaml").write_text(
|
|
92
|
+
yaml.dump(VALID_REPOS_YAML, default_flow_style=False)
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
# Agent definition
|
|
96
|
+
agents_dir = pf_dir / "agents"
|
|
97
|
+
agents_dir.mkdir()
|
|
98
|
+
(agents_dir / "dev.md").write_text("# Dev Agent\n\nDeveloper agent.")
|
|
99
|
+
|
|
100
|
+
# Behavior guide
|
|
101
|
+
guides_dir = pf_dir / "guides"
|
|
102
|
+
guides_dir.mkdir()
|
|
103
|
+
(guides_dir / "agent-behavior.md").write_text("# Agent Behavior Guide")
|
|
104
|
+
|
|
105
|
+
# Sidecars
|
|
106
|
+
sidecar_dir = pf_dir / "sidecars" / "dev"
|
|
107
|
+
sidecar_dir.mkdir(parents=True)
|
|
108
|
+
(sidecar_dir / "patterns.md").write_text("# Patterns")
|
|
109
|
+
|
|
110
|
+
# Theme
|
|
111
|
+
(pf_dir / "config.local.yaml").write_text(yaml.dump({"theme": "test"}))
|
|
112
|
+
themes_dir = pf_dir / "personas" / "themes"
|
|
113
|
+
themes_dir.mkdir(parents=True)
|
|
114
|
+
(themes_dir / "test.yaml").write_text(
|
|
115
|
+
yaml.dump(
|
|
116
|
+
{
|
|
117
|
+
"agents": {
|
|
118
|
+
"dev": {"character": "Dev", "style": "s", "role": "r"}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
)
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
# Sprint
|
|
125
|
+
sprint_dir = tmp_path / "sprint"
|
|
126
|
+
sprint_dir.mkdir()
|
|
127
|
+
(sprint_dir / "current-sprint.yaml").write_text(
|
|
128
|
+
yaml.dump({"sprint": {"number": 12, "goal": "Test"}, "epics": []})
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
# Session
|
|
132
|
+
session_dir = tmp_path / ".session"
|
|
133
|
+
session_dir.mkdir()
|
|
134
|
+
(session_dir / "test-session.md").write_text(
|
|
135
|
+
"# Test Session\n\n- **Phase:** green"
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
return tmp_path
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
# =============================================================================
|
|
142
|
+
# AC1: load_repos_topology() — Loading and Formatting
|
|
143
|
+
# =============================================================================
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
class TestLoadReposTopology:
|
|
147
|
+
"""Tests for load_repos_topology() function (AC1)."""
|
|
148
|
+
|
|
149
|
+
def test_loads_valid_topology(self, topology_project: Path) -> None:
|
|
150
|
+
"""Test loading repos.yaml with full topology fields returns formatted text."""
|
|
151
|
+
from pennyfarthing_scripts.prime.loader import load_repos_topology
|
|
152
|
+
|
|
153
|
+
result = load_repos_topology(topology_project)
|
|
154
|
+
|
|
155
|
+
assert result is not None
|
|
156
|
+
assert isinstance(result, str)
|
|
157
|
+
assert len(result) > 0
|
|
158
|
+
|
|
159
|
+
def test_includes_repo_names(self, topology_project: Path) -> None:
|
|
160
|
+
"""Test formatted output includes repo names."""
|
|
161
|
+
from pennyfarthing_scripts.prime.loader import load_repos_topology
|
|
162
|
+
|
|
163
|
+
result = load_repos_topology(topology_project)
|
|
164
|
+
|
|
165
|
+
assert result is not None
|
|
166
|
+
assert "orchestrator" in result
|
|
167
|
+
assert "pennyfarthing" in result
|
|
168
|
+
|
|
169
|
+
def test_includes_owns_patterns(self, topology_project: Path) -> None:
|
|
170
|
+
"""Test formatted output includes ownership glob patterns."""
|
|
171
|
+
from pennyfarthing_scripts.prime.loader import load_repos_topology
|
|
172
|
+
|
|
173
|
+
result = load_repos_topology(topology_project)
|
|
174
|
+
|
|
175
|
+
assert result is not None
|
|
176
|
+
assert "sprint/**" in result
|
|
177
|
+
assert "pennyfarthing-dist/**" in result
|
|
178
|
+
|
|
179
|
+
def test_includes_never_edit_zones(self, topology_project: Path) -> None:
|
|
180
|
+
"""Test formatted output includes never-edit paths."""
|
|
181
|
+
from pennyfarthing_scripts.prime.loader import load_repos_topology
|
|
182
|
+
|
|
183
|
+
result = load_repos_topology(topology_project)
|
|
184
|
+
|
|
185
|
+
assert result is not None
|
|
186
|
+
assert "node_modules/**" in result
|
|
187
|
+
|
|
188
|
+
def test_includes_ui_layer(self, topology_project: Path) -> None:
|
|
189
|
+
"""Test formatted output includes UI layer for each repo."""
|
|
190
|
+
from pennyfarthing_scripts.prime.loader import load_repos_topology
|
|
191
|
+
|
|
192
|
+
result = load_repos_topology(topology_project)
|
|
193
|
+
|
|
194
|
+
assert result is not None
|
|
195
|
+
assert "none" in result
|
|
196
|
+
assert "react" in result
|
|
197
|
+
|
|
198
|
+
def test_includes_symlinks(self, topology_project: Path) -> None:
|
|
199
|
+
"""Test formatted output includes symlink mappings."""
|
|
200
|
+
from pennyfarthing_scripts.prime.loader import load_repos_topology
|
|
201
|
+
|
|
202
|
+
result = load_repos_topology(topology_project)
|
|
203
|
+
|
|
204
|
+
assert result is not None
|
|
205
|
+
assert ".pennyfarthing/agents" in result
|
|
206
|
+
|
|
207
|
+
def test_includes_components_path(self, topology_project: Path) -> None:
|
|
208
|
+
"""Test formatted output includes components_path when present."""
|
|
209
|
+
from pennyfarthing_scripts.prime.loader import load_repos_topology
|
|
210
|
+
|
|
211
|
+
result = load_repos_topology(topology_project)
|
|
212
|
+
|
|
213
|
+
assert result is not None
|
|
214
|
+
assert "packages/cyclist/src/components" in result
|
|
215
|
+
|
|
216
|
+
def test_returns_none_when_no_repos_yaml(self, tmp_path: Path) -> None:
|
|
217
|
+
"""Test returns None when repos.yaml doesn't exist."""
|
|
218
|
+
from pennyfarthing_scripts.prime.loader import load_repos_topology
|
|
219
|
+
|
|
220
|
+
pf_dir = tmp_path / ".pennyfarthing"
|
|
221
|
+
pf_dir.mkdir()
|
|
222
|
+
|
|
223
|
+
result = load_repos_topology(tmp_path)
|
|
224
|
+
|
|
225
|
+
assert result is None
|
|
226
|
+
|
|
227
|
+
def test_returns_none_when_no_pennyfarthing_dir(self, tmp_path: Path) -> None:
|
|
228
|
+
"""Test returns None when .pennyfarthing/ doesn't exist."""
|
|
229
|
+
from pennyfarthing_scripts.prime.loader import load_repos_topology
|
|
230
|
+
|
|
231
|
+
result = load_repos_topology(tmp_path)
|
|
232
|
+
|
|
233
|
+
assert result is None
|
|
234
|
+
|
|
235
|
+
def test_handles_invalid_yaml(self, tmp_path: Path) -> None:
|
|
236
|
+
"""Test gracefully handles invalid YAML content."""
|
|
237
|
+
from pennyfarthing_scripts.prime.loader import load_repos_topology
|
|
238
|
+
|
|
239
|
+
pf_dir = tmp_path / ".pennyfarthing"
|
|
240
|
+
pf_dir.mkdir()
|
|
241
|
+
(pf_dir / "repos.yaml").write_text("not: [valid: yaml: {{{}}")
|
|
242
|
+
|
|
243
|
+
result = load_repos_topology(tmp_path)
|
|
244
|
+
|
|
245
|
+
assert result is None
|
|
246
|
+
|
|
247
|
+
def test_handles_yaml_without_repos_key(self, tmp_path: Path) -> None:
|
|
248
|
+
"""Test handles YAML that has no 'repos' key."""
|
|
249
|
+
from pennyfarthing_scripts.prime.loader import load_repos_topology
|
|
250
|
+
|
|
251
|
+
pf_dir = tmp_path / ".pennyfarthing"
|
|
252
|
+
pf_dir.mkdir()
|
|
253
|
+
(pf_dir / "repos.yaml").write_text(yaml.dump({"something_else": True}))
|
|
254
|
+
|
|
255
|
+
result = load_repos_topology(tmp_path)
|
|
256
|
+
|
|
257
|
+
assert result is None
|
|
258
|
+
|
|
259
|
+
def test_handles_empty_repos(self, tmp_path: Path) -> None:
|
|
260
|
+
"""Test handles repos.yaml with empty repos dict."""
|
|
261
|
+
from pennyfarthing_scripts.prime.loader import load_repos_topology
|
|
262
|
+
|
|
263
|
+
pf_dir = tmp_path / ".pennyfarthing"
|
|
264
|
+
pf_dir.mkdir()
|
|
265
|
+
(pf_dir / "repos.yaml").write_text(yaml.dump({"repos": {}}))
|
|
266
|
+
|
|
267
|
+
result = load_repos_topology(tmp_path)
|
|
268
|
+
|
|
269
|
+
assert result is None
|
|
270
|
+
|
|
271
|
+
def test_backwards_compat_legacy_repos(self, legacy_project: Path) -> None:
|
|
272
|
+
"""Test repos without topology fields still produce output."""
|
|
273
|
+
from pennyfarthing_scripts.prime.loader import load_repos_topology
|
|
274
|
+
|
|
275
|
+
result = load_repos_topology(legacy_project)
|
|
276
|
+
|
|
277
|
+
# Should still return something — at minimum the repo name and basic info
|
|
278
|
+
assert result is not None
|
|
279
|
+
assert "myrepo" in result
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
# =============================================================================
|
|
283
|
+
# AC2: FULL Tier Includes Topology
|
|
284
|
+
# =============================================================================
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
class TestFullTierTopology:
|
|
288
|
+
"""Tests that FULL tier includes repos_topology component (AC2)."""
|
|
289
|
+
|
|
290
|
+
def test_full_tier_includes_repos_topology(self, full_project: Path) -> None:
|
|
291
|
+
"""Test FULL tier load_tier_components includes repos_topology."""
|
|
292
|
+
from pennyfarthing_scripts.prime.tiers import ContextTier, load_tier_components
|
|
293
|
+
|
|
294
|
+
components = load_tier_components(
|
|
295
|
+
tier=ContextTier.FULL,
|
|
296
|
+
agent_name="dev",
|
|
297
|
+
project_root=full_project,
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
assert "repos_topology" in components
|
|
301
|
+
assert isinstance(components["repos_topology"], str)
|
|
302
|
+
assert len(components["repos_topology"]) > 0
|
|
303
|
+
|
|
304
|
+
def test_full_tier_topology_has_token_count(self, full_project: Path) -> None:
|
|
305
|
+
"""Test FULL tier topology component has a token count estimate."""
|
|
306
|
+
from pennyfarthing_scripts.prime.tiers import ContextTier, load_tier_components
|
|
307
|
+
|
|
308
|
+
components = load_tier_components(
|
|
309
|
+
tier=ContextTier.FULL,
|
|
310
|
+
agent_name="dev",
|
|
311
|
+
project_root=full_project,
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
token_counts = components.get("token_counts", {})
|
|
315
|
+
assert "repos_topology" in token_counts
|
|
316
|
+
assert token_counts["repos_topology"] > 0
|
|
317
|
+
|
|
318
|
+
def test_full_tier_text_output_includes_topology(
|
|
319
|
+
self, full_project: Path, capsys
|
|
320
|
+
) -> None:
|
|
321
|
+
"""Test FULL tier text output includes topology section."""
|
|
322
|
+
from pennyfarthing_scripts.prime.cli import prime
|
|
323
|
+
|
|
324
|
+
with patch(
|
|
325
|
+
"pennyfarthing_scripts.prime.cli.get_project_root",
|
|
326
|
+
return_value=full_project,
|
|
327
|
+
):
|
|
328
|
+
with patch(
|
|
329
|
+
"pennyfarthing_scripts.prime.loader.get_project_root",
|
|
330
|
+
return_value=full_project,
|
|
331
|
+
):
|
|
332
|
+
result = prime(
|
|
333
|
+
agent_name="dev",
|
|
334
|
+
tier="FULL",
|
|
335
|
+
no_workflow=True,
|
|
336
|
+
no_register=True,
|
|
337
|
+
project_root=full_project,
|
|
338
|
+
)
|
|
339
|
+
|
|
340
|
+
assert result == 0
|
|
341
|
+
captured = capsys.readouterr()
|
|
342
|
+
assert "Repos Topology" in captured.out
|
|
343
|
+
assert "orchestrator" in captured.out
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
# =============================================================================
|
|
347
|
+
# AC3: JSON Output Includes Topology Component
|
|
348
|
+
# =============================================================================
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
class TestJSONTopologyOutput:
|
|
352
|
+
"""Tests that JSON output includes topology in components list (AC3)."""
|
|
353
|
+
|
|
354
|
+
def test_json_output_has_topology_component(
|
|
355
|
+
self, full_project: Path, capsys
|
|
356
|
+
) -> None:
|
|
357
|
+
"""Test JSON output includes topology in components list."""
|
|
358
|
+
import json
|
|
359
|
+
|
|
360
|
+
from pennyfarthing_scripts.prime.cli import prime
|
|
361
|
+
|
|
362
|
+
with patch(
|
|
363
|
+
"pennyfarthing_scripts.prime.cli.get_project_root",
|
|
364
|
+
return_value=full_project,
|
|
365
|
+
):
|
|
366
|
+
with patch(
|
|
367
|
+
"pennyfarthing_scripts.prime.loader.get_project_root",
|
|
368
|
+
return_value=full_project,
|
|
369
|
+
):
|
|
370
|
+
result = prime(
|
|
371
|
+
agent_name="dev",
|
|
372
|
+
tier="FULL",
|
|
373
|
+
json_output=True,
|
|
374
|
+
no_workflow=True,
|
|
375
|
+
no_register=True,
|
|
376
|
+
project_root=full_project,
|
|
377
|
+
)
|
|
378
|
+
|
|
379
|
+
assert result == 0
|
|
380
|
+
captured = capsys.readouterr()
|
|
381
|
+
data = json.loads(captured.out)
|
|
382
|
+
|
|
383
|
+
# Find topology in components list
|
|
384
|
+
component_names = [c["name"] for c in data.get("components", [])]
|
|
385
|
+
assert "repos_topology" in component_names
|
|
386
|
+
|
|
387
|
+
def test_json_topology_component_has_tokens(
|
|
388
|
+
self, full_project: Path, capsys
|
|
389
|
+
) -> None:
|
|
390
|
+
"""Test JSON topology component has token count."""
|
|
391
|
+
import json
|
|
392
|
+
|
|
393
|
+
from pennyfarthing_scripts.prime.cli import prime
|
|
394
|
+
|
|
395
|
+
with patch(
|
|
396
|
+
"pennyfarthing_scripts.prime.cli.get_project_root",
|
|
397
|
+
return_value=full_project,
|
|
398
|
+
):
|
|
399
|
+
with patch(
|
|
400
|
+
"pennyfarthing_scripts.prime.loader.get_project_root",
|
|
401
|
+
return_value=full_project,
|
|
402
|
+
):
|
|
403
|
+
result = prime(
|
|
404
|
+
agent_name="dev",
|
|
405
|
+
tier="FULL",
|
|
406
|
+
json_output=True,
|
|
407
|
+
no_workflow=True,
|
|
408
|
+
no_register=True,
|
|
409
|
+
project_root=full_project,
|
|
410
|
+
)
|
|
411
|
+
|
|
412
|
+
assert result == 0
|
|
413
|
+
captured = capsys.readouterr()
|
|
414
|
+
data = json.loads(captured.out)
|
|
415
|
+
|
|
416
|
+
topology_component = next(
|
|
417
|
+
(c for c in data.get("components", []) if c["name"] == "repos_topology"),
|
|
418
|
+
None,
|
|
419
|
+
)
|
|
420
|
+
assert topology_component is not None
|
|
421
|
+
assert topology_component["tokens"] > 0
|
|
422
|
+
|
|
423
|
+
def test_json_topology_component_has_source(
|
|
424
|
+
self, full_project: Path, capsys
|
|
425
|
+
) -> None:
|
|
426
|
+
"""Test JSON topology component has source path."""
|
|
427
|
+
import json
|
|
428
|
+
|
|
429
|
+
from pennyfarthing_scripts.prime.cli import prime
|
|
430
|
+
|
|
431
|
+
with patch(
|
|
432
|
+
"pennyfarthing_scripts.prime.cli.get_project_root",
|
|
433
|
+
return_value=full_project,
|
|
434
|
+
):
|
|
435
|
+
with patch(
|
|
436
|
+
"pennyfarthing_scripts.prime.loader.get_project_root",
|
|
437
|
+
return_value=full_project,
|
|
438
|
+
):
|
|
439
|
+
result = prime(
|
|
440
|
+
agent_name="dev",
|
|
441
|
+
tier="FULL",
|
|
442
|
+
json_output=True,
|
|
443
|
+
no_workflow=True,
|
|
444
|
+
no_register=True,
|
|
445
|
+
project_root=full_project,
|
|
446
|
+
)
|
|
447
|
+
|
|
448
|
+
assert result == 0
|
|
449
|
+
captured = capsys.readouterr()
|
|
450
|
+
data = json.loads(captured.out)
|
|
451
|
+
|
|
452
|
+
topology_component = next(
|
|
453
|
+
(c for c in data.get("components", []) if c["name"] == "repos_topology"),
|
|
454
|
+
None,
|
|
455
|
+
)
|
|
456
|
+
assert topology_component is not None
|
|
457
|
+
assert topology_component.get("source") == ".pennyfarthing/repos.yaml"
|
|
458
|
+
|
|
459
|
+
|
|
460
|
+
# =============================================================================
|
|
461
|
+
# AC4: Tiered Availability (FULL/REFRESH/HANDOFF yes, MINIMAL no)
|
|
462
|
+
# =============================================================================
|
|
463
|
+
|
|
464
|
+
|
|
465
|
+
class TestTopologyTierAvailability:
|
|
466
|
+
"""Tests that topology is in correct tiers (AC4)."""
|
|
467
|
+
|
|
468
|
+
def test_full_tier_has_topology(self, full_project: Path) -> None:
|
|
469
|
+
"""Test FULL tier includes repos_topology."""
|
|
470
|
+
from pennyfarthing_scripts.prime.tiers import ContextTier, load_tier_components
|
|
471
|
+
|
|
472
|
+
components = load_tier_components(
|
|
473
|
+
tier=ContextTier.FULL,
|
|
474
|
+
agent_name="dev",
|
|
475
|
+
project_root=full_project,
|
|
476
|
+
)
|
|
477
|
+
|
|
478
|
+
assert "repos_topology" in components
|
|
479
|
+
|
|
480
|
+
def test_refresh_tier_has_topology(self, full_project: Path) -> None:
|
|
481
|
+
"""Test REFRESH tier includes repos_topology (session-independent)."""
|
|
482
|
+
from pennyfarthing_scripts.prime.tiers import ContextTier, load_tier_components
|
|
483
|
+
|
|
484
|
+
components = load_tier_components(
|
|
485
|
+
tier=ContextTier.REFRESH,
|
|
486
|
+
agent_name="dev",
|
|
487
|
+
project_root=full_project,
|
|
488
|
+
)
|
|
489
|
+
|
|
490
|
+
assert "repos_topology" in components
|
|
491
|
+
|
|
492
|
+
def test_handoff_tier_has_topology(self, full_project: Path) -> None:
|
|
493
|
+
"""Test HANDOFF tier includes repos_topology (agent needs orientation)."""
|
|
494
|
+
from pennyfarthing_scripts.prime.tiers import ContextTier, load_tier_components
|
|
495
|
+
|
|
496
|
+
components = load_tier_components(
|
|
497
|
+
tier=ContextTier.HANDOFF,
|
|
498
|
+
agent_name="dev",
|
|
499
|
+
project_root=full_project,
|
|
500
|
+
)
|
|
501
|
+
|
|
502
|
+
assert "repos_topology" in components
|
|
503
|
+
|
|
504
|
+
def test_minimal_tier_excludes_topology(self, full_project: Path) -> None:
|
|
505
|
+
"""Test MINIMAL tier does NOT include repos_topology."""
|
|
506
|
+
from pennyfarthing_scripts.prime.tiers import ContextTier, load_tier_components
|
|
507
|
+
|
|
508
|
+
components = load_tier_components(
|
|
509
|
+
tier=ContextTier.MINIMAL,
|
|
510
|
+
agent_name="dev",
|
|
511
|
+
project_root=full_project,
|
|
512
|
+
)
|
|
513
|
+
|
|
514
|
+
assert "repos_topology" not in components
|
|
515
|
+
|
|
516
|
+
|
|
517
|
+
# =============================================================================
|
|
518
|
+
# AC5: Token Counting and Backwards Compatibility
|
|
519
|
+
# =============================================================================
|
|
520
|
+
|
|
521
|
+
|
|
522
|
+
class TestTopologyTokenCounting:
|
|
523
|
+
"""Tests for topology token counting (AC5)."""
|
|
524
|
+
|
|
525
|
+
def test_topology_token_count_realistic(self, full_project: Path) -> None:
|
|
526
|
+
"""Test topology token count is in realistic range (100-500 tokens)."""
|
|
527
|
+
from pennyfarthing_scripts.prime.tiers import ContextTier, load_tier_components
|
|
528
|
+
|
|
529
|
+
components = load_tier_components(
|
|
530
|
+
tier=ContextTier.FULL,
|
|
531
|
+
agent_name="dev",
|
|
532
|
+
project_root=full_project,
|
|
533
|
+
)
|
|
534
|
+
|
|
535
|
+
token_counts = components.get("token_counts", {})
|
|
536
|
+
topology_tokens = token_counts.get("repos_topology", 0)
|
|
537
|
+
|
|
538
|
+
# Two repos with topology fields should be 100-500 tokens
|
|
539
|
+
assert topology_tokens >= 50, (
|
|
540
|
+
f"Topology too small: {topology_tokens} tokens"
|
|
541
|
+
)
|
|
542
|
+
assert topology_tokens <= 500, (
|
|
543
|
+
f"Topology too large: {topology_tokens} tokens"
|
|
544
|
+
)
|
|
545
|
+
|
|
546
|
+
def test_topology_included_in_total_tokens(self, full_project: Path) -> None:
|
|
547
|
+
"""Test topology tokens are included in total_tokens sum."""
|
|
548
|
+
from pennyfarthing_scripts.prime.tiers import ContextTier, load_tier_components
|
|
549
|
+
|
|
550
|
+
components = load_tier_components(
|
|
551
|
+
tier=ContextTier.FULL,
|
|
552
|
+
agent_name="dev",
|
|
553
|
+
project_root=full_project,
|
|
554
|
+
)
|
|
555
|
+
|
|
556
|
+
token_counts = components.get("token_counts", {})
|
|
557
|
+
total_tokens = components.get("total_tokens", 0)
|
|
558
|
+
|
|
559
|
+
assert total_tokens >= token_counts.get("repos_topology", 0)
|
|
560
|
+
|
|
561
|
+
|
|
562
|
+
class TestTopologyBackwardsCompat:
|
|
563
|
+
"""Tests for backwards compatibility (AC5)."""
|
|
564
|
+
|
|
565
|
+
def test_project_without_repos_yaml_works(self, tmp_path: Path) -> None:
|
|
566
|
+
"""Test prime works fine when repos.yaml doesn't exist."""
|
|
567
|
+
from pennyfarthing_scripts.prime.tiers import ContextTier, load_tier_components
|
|
568
|
+
|
|
569
|
+
pf_dir = tmp_path / ".pennyfarthing"
|
|
570
|
+
pf_dir.mkdir()
|
|
571
|
+
agents_dir = pf_dir / "agents"
|
|
572
|
+
agents_dir.mkdir()
|
|
573
|
+
(agents_dir / "dev.md").write_text("# Dev Agent")
|
|
574
|
+
|
|
575
|
+
# No repos.yaml — should not crash
|
|
576
|
+
components = load_tier_components(
|
|
577
|
+
tier=ContextTier.FULL,
|
|
578
|
+
agent_name="dev",
|
|
579
|
+
project_root=tmp_path,
|
|
580
|
+
)
|
|
581
|
+
|
|
582
|
+
# Should still have other components
|
|
583
|
+
assert "agent_definition" in components
|
|
584
|
+
# Topology should be absent or None, not an error
|
|
585
|
+
topology = components.get("repos_topology")
|
|
586
|
+
assert topology is None or "repos_topology" not in components
|
|
587
|
+
|
|
588
|
+
def test_legacy_repos_yaml_works(self, legacy_project: Path) -> None:
|
|
589
|
+
"""Test repos.yaml without topology fields doesn't break prime."""
|
|
590
|
+
from pennyfarthing_scripts.prime.tiers import ContextTier, load_tier_components
|
|
591
|
+
|
|
592
|
+
# Add agent for tier loading
|
|
593
|
+
agents_dir = legacy_project / ".pennyfarthing" / "agents"
|
|
594
|
+
agents_dir.mkdir()
|
|
595
|
+
(agents_dir / "dev.md").write_text("# Dev Agent")
|
|
596
|
+
|
|
597
|
+
components = load_tier_components(
|
|
598
|
+
tier=ContextTier.FULL,
|
|
599
|
+
agent_name="dev",
|
|
600
|
+
project_root=legacy_project,
|
|
601
|
+
)
|
|
602
|
+
|
|
603
|
+
# Should work without errors — topology present with basic info
|
|
604
|
+
assert "agent_definition" in components
|
|
605
|
+
|
|
606
|
+
def test_cli_component_header_for_topology(self) -> None:
|
|
607
|
+
"""Test _component_header returns correct header for repos_topology."""
|
|
608
|
+
from pennyfarthing_scripts.prime.cli import _component_header
|
|
609
|
+
|
|
610
|
+
header = _component_header("repos_topology", "dev")
|
|
611
|
+
|
|
612
|
+
assert "Repos Topology" in header
|
|
613
|
+
|
|
614
|
+
def test_cli_component_source_for_topology(self) -> None:
|
|
615
|
+
"""Test _component_source returns correct path for repos_topology."""
|
|
616
|
+
from pennyfarthing_scripts.prime.cli import _component_source
|
|
617
|
+
|
|
618
|
+
source = _component_source("repos_topology", "dev", Path("/fake"))
|
|
619
|
+
|
|
620
|
+
assert source == ".pennyfarthing/repos.yaml"
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|