@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.
Files changed (268) hide show
  1. package/README.md +11 -8
  2. package/package.json +1 -1
  3. package/packages/core/dist/cli/commands/e2e-fresh-install.test.js +1 -1
  4. package/packages/core/dist/cli/commands/e2e-fresh-install.test.js.map +1 -1
  5. package/packages/core/dist/cli/commands/e2e-upgrade.test.js +1 -1
  6. package/packages/core/dist/cli/commands/e2e-upgrade.test.js.map +1 -1
  7. package/packages/core/dist/cli/commands/theme.js +1 -1
  8. package/packages/core/dist/cli/commands/theme.js.map +1 -1
  9. package/packages/core/dist/cli/utils/themes.d.ts.map +1 -1
  10. package/packages/core/dist/cli/utils/themes.js +3 -2
  11. package/packages/core/dist/cli/utils/themes.js.map +1 -1
  12. package/packages/core/dist/scripts/add-ocean-profiles.js +1 -1
  13. package/packages/core/dist/scripts/add-ocean-profiles.js.map +1 -1
  14. package/packages/core/dist/scripts/generate-all-spiders.js +2 -0
  15. package/packages/core/dist/scripts/generate-all-spiders.js.map +1 -1
  16. package/packages/core/dist/scripts/generate-report.d.ts.map +1 -1
  17. package/packages/core/dist/scripts/generate-report.js +2 -0
  18. package/packages/core/dist/scripts/generate-report.js.map +1 -1
  19. package/packages/core/dist/scripts/generate-spider.d.ts.map +1 -1
  20. package/packages/core/dist/scripts/generate-spider.js +2 -0
  21. package/packages/core/dist/scripts/generate-spider.js.map +1 -1
  22. package/packages/core/dist/scripts/validate-ocean-profiles.js +1 -1
  23. package/packages/core/dist/scripts/validate-ocean-profiles.js.map +1 -1
  24. package/packages/core/dist/workflow/file-watch.test.js.map +1 -1
  25. package/packages/core/dist/workflow/output-path-normalizer.d.ts +47 -0
  26. package/packages/core/dist/workflow/output-path-normalizer.d.ts.map +1 -0
  27. package/packages/core/dist/workflow/output-path-normalizer.js +79 -0
  28. package/packages/core/dist/workflow/output-path-normalizer.js.map +1 -0
  29. package/packages/core/dist/workflow/output-path-normalizer.test.d.ts +16 -0
  30. package/packages/core/dist/workflow/output-path-normalizer.test.d.ts.map +1 -0
  31. package/packages/core/dist/workflow/output-path-normalizer.test.js +157 -0
  32. package/packages/core/dist/workflow/output-path-normalizer.test.js.map +1 -0
  33. package/packages/core/dist/workflow/tool-watch.test.js +1 -2
  34. package/packages/core/dist/workflow/tool-watch.test.js.map +1 -1
  35. package/packages/core/dist/workflow/variable-resolver.js +1 -1
  36. package/packages/core/dist/workflow/variable-resolver.js.map +1 -1
  37. package/pennyfarthing-dist/agents/README.md +3 -1
  38. package/pennyfarthing-dist/agents/ba.md +165 -0
  39. package/pennyfarthing-dist/commands/ba.md +17 -0
  40. package/pennyfarthing-dist/guides/workflow-schema.md +1 -1
  41. package/pennyfarthing-dist/personas/themes/a-team.yaml +30 -0
  42. package/pennyfarthing-dist/personas/themes/alice-in-wonderland.yaml +30 -0
  43. package/pennyfarthing-dist/personas/themes/battlestar-galactica.yaml +30 -0
  44. package/pennyfarthing-dist/personas/themes/blade-runner.yaml +30 -0
  45. package/pennyfarthing-dist/personas/themes/catch-22.yaml +30 -0
  46. package/pennyfarthing-dist/personas/themes/control.yaml +30 -0
  47. package/pennyfarthing-dist/personas/themes/cowboy-bebop.yaml +31 -0
  48. package/pennyfarthing-dist/personas/themes/discworld.yaml +31 -0
  49. package/pennyfarthing-dist/personas/themes/doctor-who.yaml +31 -0
  50. package/pennyfarthing-dist/personas/themes/dune.yaml +32 -0
  51. package/pennyfarthing-dist/personas/themes/fifth-element.yaml +32 -0
  52. package/pennyfarthing-dist/personas/themes/firefly.yaml +31 -0
  53. package/pennyfarthing-dist/personas/themes/game-of-thrones.yaml +30 -0
  54. package/pennyfarthing-dist/personas/themes/harry-potter.yaml +30 -0
  55. package/pennyfarthing-dist/personas/themes/hitchhikers-guide.yaml +30 -0
  56. package/pennyfarthing-dist/personas/themes/lord-of-the-rings.yaml +30 -0
  57. package/pennyfarthing-dist/personas/themes/mad-max.yaml +30 -0
  58. package/pennyfarthing-dist/personas/themes/mash.yaml +33 -0
  59. package/pennyfarthing-dist/personas/themes/princess-bride.yaml +34 -0
  60. package/pennyfarthing-dist/personas/themes/sandman.yaml +33 -0
  61. package/pennyfarthing-dist/personas/themes/star-trek-tng.yaml +34 -0
  62. package/pennyfarthing-dist/personas/themes/star-wars.yaml +33 -0
  63. package/pennyfarthing-dist/personas/themes/the-expanse.yaml +30 -0
  64. package/pennyfarthing-dist/personas/themes/the-matrix.yaml +30 -0
  65. package/pennyfarthing-dist/personas/themes/watchmen.yaml +30 -0
  66. package/pennyfarthing-dist/personas/themes/west-wing.yaml +30 -0
  67. package/pennyfarthing-dist/personas/themes/x-files.yaml +30 -0
  68. package/pennyfarthing-dist/scripts/core/agent-session.sh +1 -1
  69. package/pennyfarthing-dist/scripts/hooks/__pycache__/question_reflector_check.cpython-314.pyc +0 -0
  70. package/pennyfarthing-dist/scripts/portraits/generate-portraits.py +2 -2
  71. package/pennyfarthing-dist/scripts/validation/validate-agent-schema.sh +1 -0
  72. package/pennyfarthing-dist/skills/theme/skill.md +1 -1
  73. package/pennyfarthing-dist/workflows/architecture/workflow.yaml +2 -2
  74. package/pennyfarthing-dist/workflows/architecture.yaml +2 -2
  75. package/pennyfarthing-dist/workflows/epics-and-stories/workflow.yaml +2 -2
  76. package/pennyfarthing-dist/workflows/implementation-readiness/workflow.yaml +2 -2
  77. package/pennyfarthing-dist/workflows/prd/workflow.yaml +2 -2
  78. package/pennyfarthing-dist/workflows/product-brief/workflow.yaml +2 -2
  79. package/pennyfarthing-dist/workflows/project-context/workflow.yaml +2 -2
  80. package/pennyfarthing-dist/workflows/quick-dev/workflow.yaml +2 -2
  81. package/pennyfarthing-dist/workflows/research/workflow.yaml +2 -2
  82. package/pennyfarthing-dist/workflows/retrospective/workflow.yaml +1 -1
  83. package/pennyfarthing-dist/workflows/sprint-planning/workflow.yaml +3 -3
  84. package/pennyfarthing-dist/workflows/ux-design/workflow.yaml +2 -2
  85. package/pennyfarthing_scripts/__pycache__/__init__.cpython-311.pyc +0 -0
  86. package/pennyfarthing_scripts/__pycache__/__init__.cpython-314.pyc +0 -0
  87. package/pennyfarthing_scripts/__pycache__/cli.cpython-314.pyc +0 -0
  88. package/pennyfarthing_scripts/__pycache__/config.cpython-314.pyc +0 -0
  89. package/pennyfarthing_scripts/__pycache__/hooks.cpython-314.pyc +0 -0
  90. package/pennyfarthing_scripts/__pycache__/jira.cpython-314.pyc +0 -0
  91. package/pennyfarthing_scripts/__pycache__/jira_bidirectional_sync.cpython-314.pyc +0 -0
  92. package/pennyfarthing_scripts/__pycache__/jira_epic_creation.cpython-314.pyc +0 -0
  93. package/pennyfarthing_scripts/__pycache__/jira_sync.cpython-314.pyc +0 -0
  94. package/pennyfarthing_scripts/__pycache__/jira_sync_story.cpython-314.pyc +0 -0
  95. package/pennyfarthing_scripts/__pycache__/output.cpython-314.pyc +0 -0
  96. package/pennyfarthing_scripts/__pycache__/patch_mode.cpython-314.pyc +0 -0
  97. package/pennyfarthing_scripts/__pycache__/pretooluse_hook.cpython-314.pyc +0 -0
  98. package/pennyfarthing_scripts/__pycache__/schema_validation_hook.cpython-314.pyc +0 -0
  99. package/pennyfarthing_scripts/__pycache__/sprint.cpython-314.pyc +0 -0
  100. package/pennyfarthing_scripts/__pycache__/workflow.cpython-311.pyc +0 -0
  101. package/pennyfarthing_scripts/__pycache__/workflow.cpython-314.pyc +0 -0
  102. package/pennyfarthing_scripts/bellmode_hook.py +1 -1
  103. package/pennyfarthing_scripts/bikerack/__init__.py +36 -0
  104. package/pennyfarthing_scripts/bikerack/__main__.py +5 -0
  105. package/pennyfarthing_scripts/bikerack/__pycache__/__init__.cpython-314.pyc +0 -0
  106. package/pennyfarthing_scripts/bikerack/__pycache__/__main__.cpython-314.pyc +0 -0
  107. package/pennyfarthing_scripts/bikerack/__pycache__/cli.cpython-314.pyc +0 -0
  108. package/pennyfarthing_scripts/bikerack/__pycache__/launcher.cpython-314.pyc +0 -0
  109. package/pennyfarthing_scripts/bikerack/cli.py +148 -0
  110. package/pennyfarthing_scripts/bikerack/launcher.py +181 -0
  111. package/pennyfarthing_scripts/brownfield/__pycache__/__init__.cpython-314.pyc +0 -0
  112. package/pennyfarthing_scripts/brownfield/__pycache__/__main__.cpython-314.pyc +0 -0
  113. package/pennyfarthing_scripts/brownfield/__pycache__/cli.cpython-314.pyc +0 -0
  114. package/pennyfarthing_scripts/brownfield/__pycache__/discover.cpython-314.pyc +0 -0
  115. package/pennyfarthing_scripts/cli.py +5 -0
  116. package/pennyfarthing_scripts/codemarkers/__pycache__/__init__.cpython-314.pyc +0 -0
  117. package/pennyfarthing_scripts/codemarkers/__pycache__/__main__.cpython-314.pyc +0 -0
  118. package/pennyfarthing_scripts/codemarkers/__pycache__/analyze.cpython-314.pyc +0 -0
  119. package/pennyfarthing_scripts/codemarkers/__pycache__/cli.cpython-314.pyc +0 -0
  120. package/pennyfarthing_scripts/codemarkers/__pycache__/formatters.cpython-314.pyc +0 -0
  121. package/pennyfarthing_scripts/codemarkers/__pycache__/models.cpython-314.pyc +0 -0
  122. package/pennyfarthing_scripts/common/__pycache__/__init__.cpython-314.pyc +0 -0
  123. package/pennyfarthing_scripts/common/__pycache__/config.cpython-314.pyc +0 -0
  124. package/pennyfarthing_scripts/common/__pycache__/output.cpython-314.pyc +0 -0
  125. package/pennyfarthing_scripts/common/__pycache__/themes.cpython-314.pyc +0 -0
  126. package/pennyfarthing_scripts/complexity/__pycache__/__init__.cpython-314.pyc +0 -0
  127. package/pennyfarthing_scripts/complexity/__pycache__/__main__.cpython-314.pyc +0 -0
  128. package/pennyfarthing_scripts/complexity/__pycache__/analyze.cpython-314.pyc +0 -0
  129. package/pennyfarthing_scripts/complexity/__pycache__/cli.cpython-314.pyc +0 -0
  130. package/pennyfarthing_scripts/complexity/__pycache__/formatters.cpython-314.pyc +0 -0
  131. package/pennyfarthing_scripts/complexity/__pycache__/models.cpython-314.pyc +0 -0
  132. package/pennyfarthing_scripts/deadcode/__pycache__/__init__.cpython-314.pyc +0 -0
  133. package/pennyfarthing_scripts/deadcode/__pycache__/__main__.cpython-314.pyc +0 -0
  134. package/pennyfarthing_scripts/deadcode/__pycache__/analyze.cpython-314.pyc +0 -0
  135. package/pennyfarthing_scripts/deadcode/__pycache__/cli.cpython-314.pyc +0 -0
  136. package/pennyfarthing_scripts/deadcode/__pycache__/formatters.cpython-314.pyc +0 -0
  137. package/pennyfarthing_scripts/deadcode/__pycache__/models.cpython-314.pyc +0 -0
  138. package/pennyfarthing_scripts/dependencies/__pycache__/__init__.cpython-314.pyc +0 -0
  139. package/pennyfarthing_scripts/dependencies/__pycache__/__main__.cpython-314.pyc +0 -0
  140. package/pennyfarthing_scripts/dependencies/__pycache__/analyze.cpython-314.pyc +0 -0
  141. package/pennyfarthing_scripts/dependencies/__pycache__/cli.cpython-314.pyc +0 -0
  142. package/pennyfarthing_scripts/dependencies/__pycache__/formatters.cpython-314.pyc +0 -0
  143. package/pennyfarthing_scripts/dependencies/__pycache__/models.cpython-314.pyc +0 -0
  144. package/pennyfarthing_scripts/git/__pycache__/__init__.cpython-314.pyc +0 -0
  145. package/pennyfarthing_scripts/git/__pycache__/create_branches.cpython-314.pyc +0 -0
  146. package/pennyfarthing_scripts/git/__pycache__/status_all.cpython-314.pyc +0 -0
  147. package/pennyfarthing_scripts/healthscore/__pycache__/__init__.cpython-314.pyc +0 -0
  148. package/pennyfarthing_scripts/healthscore/__pycache__/__main__.cpython-314.pyc +0 -0
  149. package/pennyfarthing_scripts/healthscore/__pycache__/analyze.cpython-314.pyc +0 -0
  150. package/pennyfarthing_scripts/healthscore/__pycache__/cli.cpython-314.pyc +0 -0
  151. package/pennyfarthing_scripts/healthscore/__pycache__/formatters.cpython-314.pyc +0 -0
  152. package/pennyfarthing_scripts/healthscore/__pycache__/models.cpython-314.pyc +0 -0
  153. package/pennyfarthing_scripts/healthscore/analyze.py +2 -1
  154. package/pennyfarthing_scripts/hotspots/__pycache__/__init__.cpython-314.pyc +0 -0
  155. package/pennyfarthing_scripts/hotspots/__pycache__/__main__.cpython-314.pyc +0 -0
  156. package/pennyfarthing_scripts/hotspots/__pycache__/analyze.cpython-314.pyc +0 -0
  157. package/pennyfarthing_scripts/hotspots/__pycache__/cli.cpython-314.pyc +0 -0
  158. package/pennyfarthing_scripts/hotspots/__pycache__/formatters.cpython-314.pyc +0 -0
  159. package/pennyfarthing_scripts/hotspots/__pycache__/models.cpython-314.pyc +0 -0
  160. package/pennyfarthing_scripts/jira/__pycache__/__init__.cpython-314.pyc +0 -0
  161. package/pennyfarthing_scripts/jira/__pycache__/__main__.cpython-314.pyc +0 -0
  162. package/pennyfarthing_scripts/jira/__pycache__/bidirectional.cpython-314.pyc +0 -0
  163. package/pennyfarthing_scripts/jira/__pycache__/claim.cpython-314.pyc +0 -0
  164. package/pennyfarthing_scripts/jira/__pycache__/cli.cpython-314.pyc +0 -0
  165. package/pennyfarthing_scripts/jira/__pycache__/client.cpython-314.pyc +0 -0
  166. package/pennyfarthing_scripts/jira/__pycache__/compat.cpython-314.pyc +0 -0
  167. package/pennyfarthing_scripts/jira/__pycache__/create.cpython-314.pyc +0 -0
  168. package/pennyfarthing_scripts/jira/__pycache__/epic.cpython-314.pyc +0 -0
  169. package/pennyfarthing_scripts/jira/__pycache__/mappings.cpython-314.pyc +0 -0
  170. package/pennyfarthing_scripts/jira/__pycache__/models.cpython-314.pyc +0 -0
  171. package/pennyfarthing_scripts/jira/__pycache__/operations.cpython-314.pyc +0 -0
  172. package/pennyfarthing_scripts/jira/__pycache__/reconcile.cpython-314.pyc +0 -0
  173. package/pennyfarthing_scripts/jira/__pycache__/story.cpython-314.pyc +0 -0
  174. package/pennyfarthing_scripts/jira/__pycache__/sync.cpython-314.pyc +0 -0
  175. package/pennyfarthing_scripts/migration/__pycache__/__init__.cpython-314.pyc +0 -0
  176. package/pennyfarthing_scripts/migration/__pycache__/__main__.cpython-314.pyc +0 -0
  177. package/pennyfarthing_scripts/migration/__pycache__/cli.cpython-314.pyc +0 -0
  178. package/pennyfarthing_scripts/migration/__pycache__/session.cpython-314.pyc +0 -0
  179. package/pennyfarthing_scripts/migration/__pycache__/skill.cpython-314.pyc +0 -0
  180. package/pennyfarthing_scripts/migration/__pycache__/step.cpython-314.pyc +0 -0
  181. package/pennyfarthing_scripts/migration/__pycache__/validate.cpython-314.pyc +0 -0
  182. package/pennyfarthing_scripts/preflight/__pycache__/__init__.cpython-314.pyc +0 -0
  183. package/pennyfarthing_scripts/preflight/__pycache__/__main__.cpython-314.pyc +0 -0
  184. package/pennyfarthing_scripts/preflight/__pycache__/cli.cpython-314.pyc +0 -0
  185. package/pennyfarthing_scripts/preflight/__pycache__/finish.cpython-314.pyc +0 -0
  186. package/pennyfarthing_scripts/prime/__init__.py +2 -0
  187. package/pennyfarthing_scripts/prime/__pycache__/__init__.cpython-314.pyc +0 -0
  188. package/pennyfarthing_scripts/prime/__pycache__/__main__.cpython-314.pyc +0 -0
  189. package/pennyfarthing_scripts/prime/__pycache__/cli.cpython-314.pyc +0 -0
  190. package/pennyfarthing_scripts/prime/__pycache__/loader.cpython-314.pyc +0 -0
  191. package/pennyfarthing_scripts/prime/__pycache__/models.cpython-314.pyc +0 -0
  192. package/pennyfarthing_scripts/prime/__pycache__/persona.cpython-314.pyc +0 -0
  193. package/pennyfarthing_scripts/prime/__pycache__/session.cpython-314.pyc +0 -0
  194. package/pennyfarthing_scripts/prime/__pycache__/tiers.cpython-314.pyc +0 -0
  195. package/pennyfarthing_scripts/prime/__pycache__/workflow.cpython-314.pyc +0 -0
  196. package/pennyfarthing_scripts/prime/cli.py +13 -0
  197. package/pennyfarthing_scripts/prime/loader.py +70 -0
  198. package/pennyfarthing_scripts/prime/persona.py +2 -1
  199. package/pennyfarthing_scripts/prime/tiers.py +13 -0
  200. package/pennyfarthing_scripts/sprint/__pycache__/__init__.cpython-314.pyc +0 -0
  201. package/pennyfarthing_scripts/sprint/__pycache__/__main__.cpython-314.pyc +0 -0
  202. package/pennyfarthing_scripts/sprint/__pycache__/archive.cpython-314.pyc +0 -0
  203. package/pennyfarthing_scripts/sprint/__pycache__/archive_epic.cpython-314.pyc +0 -0
  204. package/pennyfarthing_scripts/sprint/__pycache__/cli.cpython-314.pyc +0 -0
  205. package/pennyfarthing_scripts/sprint/__pycache__/epic_add.cpython-314.pyc +0 -0
  206. package/pennyfarthing_scripts/sprint/__pycache__/import_epic.cpython-314.pyc +0 -0
  207. package/pennyfarthing_scripts/sprint/__pycache__/loader.cpython-314.pyc +0 -0
  208. package/pennyfarthing_scripts/sprint/__pycache__/status.cpython-314.pyc +0 -0
  209. package/pennyfarthing_scripts/sprint/__pycache__/story_add.cpython-314.pyc +0 -0
  210. package/pennyfarthing_scripts/sprint/__pycache__/story_finish.cpython-314.pyc +0 -0
  211. package/pennyfarthing_scripts/sprint/__pycache__/story_update.cpython-314.pyc +0 -0
  212. package/pennyfarthing_scripts/sprint/__pycache__/validate_cmd.cpython-314.pyc +0 -0
  213. package/pennyfarthing_scripts/sprint/__pycache__/validator.cpython-314.pyc +0 -0
  214. package/pennyfarthing_scripts/sprint/__pycache__/work.cpython-314.pyc +0 -0
  215. package/pennyfarthing_scripts/sprint/__pycache__/yaml_io.cpython-314.pyc +0 -0
  216. package/pennyfarthing_scripts/story/__pycache__/__init__.cpython-314.pyc +0 -0
  217. package/pennyfarthing_scripts/story/__pycache__/__main__.cpython-314.pyc +0 -0
  218. package/pennyfarthing_scripts/story/__pycache__/cli.cpython-314.pyc +0 -0
  219. package/pennyfarthing_scripts/story/__pycache__/create.cpython-314.pyc +0 -0
  220. package/pennyfarthing_scripts/story/__pycache__/size.cpython-314.pyc +0 -0
  221. package/pennyfarthing_scripts/story/__pycache__/template.cpython-314.pyc +0 -0
  222. package/pennyfarthing_scripts/tests/__pycache__/__init__.cpython-314.pyc +0 -0
  223. package/pennyfarthing_scripts/tests/__pycache__/conftest.cpython-314-pytest-9.0.2.pyc +0 -0
  224. package/pennyfarthing_scripts/tests/__pycache__/test_bikerack.cpython-314-pytest-9.0.2.pyc +0 -0
  225. package/pennyfarthing_scripts/tests/__pycache__/test_brownfield.cpython-314-pytest-9.0.2.pyc +0 -0
  226. package/pennyfarthing_scripts/tests/__pycache__/test_cli_modules.cpython-314-pytest-9.0.2.pyc +0 -0
  227. package/pennyfarthing_scripts/tests/__pycache__/test_codemarkers.cpython-314-pytest-9.0.2.pyc +0 -0
  228. package/pennyfarthing_scripts/tests/__pycache__/test_common.cpython-314-pytest-9.0.2.pyc +0 -0
  229. package/pennyfarthing_scripts/tests/__pycache__/test_epic_shard_validation.cpython-314-pytest-9.0.2.pyc +0 -0
  230. package/pennyfarthing_scripts/tests/__pycache__/test_git_utils.cpython-314-pytest-9.0.2.pyc +0 -0
  231. package/pennyfarthing_scripts/tests/__pycache__/test_healthscore.cpython-314-pytest-9.0.2.pyc +0 -0
  232. package/pennyfarthing_scripts/tests/__pycache__/test_jira_package.cpython-314-pytest-9.0.2.pyc +0 -0
  233. package/pennyfarthing_scripts/tests/__pycache__/test_package_structure.cpython-314-pytest-9.0.2.pyc +0 -0
  234. package/pennyfarthing_scripts/tests/__pycache__/test_patch_mode.cpython-314-pytest-9.0.2.pyc +0 -0
  235. package/pennyfarthing_scripts/tests/__pycache__/test_prime.cpython-314-pytest-9.0.2.pyc +0 -0
  236. package/pennyfarthing_scripts/tests/__pycache__/test_sprint_package.cpython-314-pytest-9.0.2.pyc +0 -0
  237. package/pennyfarthing_scripts/tests/__pycache__/test_sprint_validator.cpython-314-pytest-9.0.2.pyc +0 -0
  238. package/pennyfarthing_scripts/tests/__pycache__/test_story_add.cpython-314-pytest-9.0.2.pyc +0 -0
  239. package/pennyfarthing_scripts/tests/__pycache__/test_story_package.cpython-314-pytest-9.0.2.pyc +0 -0
  240. package/pennyfarthing_scripts/tests/__pycache__/test_story_update.cpython-314-pytest-9.0.2.pyc +0 -0
  241. package/pennyfarthing_scripts/tests/__pycache__/test_tiers.cpython-314-pytest-9.0.2.pyc +0 -0
  242. package/pennyfarthing_scripts/tests/__pycache__/test_token_counting.cpython-314-pytest-9.0.2.pyc +0 -0
  243. package/pennyfarthing_scripts/tests/__pycache__/test_validate_cmd.cpython-314-pytest-9.0.2.pyc +0 -0
  244. package/pennyfarthing_scripts/tests/__pycache__/test_workflow_check.cpython-314-pytest-9.0.2.pyc +0 -0
  245. package/pennyfarthing_scripts/tests/__pycache__/test_workflow_cli.cpython-314-pytest-9.0.2.pyc +0 -0
  246. package/pennyfarthing_scripts/tests/__pycache__/test_yaml_io.cpython-314-pytest-9.0.2.pyc +0 -0
  247. package/pennyfarthing_scripts/tests/test_bikerack.py +785 -0
  248. package/pennyfarthing_scripts/tests/test_topology_loader.py +620 -0
  249. package/pennyfarthing_scripts/theme/__pycache__/__init__.cpython-314.pyc +0 -0
  250. package/pennyfarthing_scripts/theme/__pycache__/cli.cpython-314.pyc +0 -0
  251. package/pennyfarthing_scripts/validate/__pycache__/__init__.cpython-314.pyc +0 -0
  252. package/pennyfarthing_scripts/validate/__pycache__/cli.cpython-314.pyc +0 -0
  253. package/pennyfarthing_scripts/validate/adapters/__pycache__/__init__.cpython-314.pyc +0 -0
  254. package/pennyfarthing_scripts/validate/adapters/__pycache__/agent.cpython-314.pyc +0 -0
  255. package/pennyfarthing_scripts/validate/adapters/__pycache__/schema.cpython-314.pyc +0 -0
  256. package/pennyfarthing_scripts/validate/adapters/__pycache__/skill_command.cpython-314.pyc +0 -0
  257. package/pennyfarthing_scripts/validate/adapters/__pycache__/sprint.cpython-314.pyc +0 -0
  258. package/pennyfarthing_scripts/validate/adapters/__pycache__/workflow.cpython-314.pyc +0 -0
  259. package/pennyfarthing_scripts/validate/adapters/skill_command.py +0 -1
  260. package/packages/core/dist/workflow/context-watch.d.ts +0 -80
  261. package/packages/core/dist/workflow/context-watch.d.ts.map +0 -1
  262. package/packages/core/dist/workflow/context-watch.js +0 -235
  263. package/packages/core/dist/workflow/context-watch.js.map +0 -1
  264. package/packages/core/dist/workflow/context-watch.test.d.ts +0 -1
  265. package/packages/core/dist/workflow/context-watch.test.d.ts.map +0 -1
  266. package/packages/core/dist/workflow/context-watch.test.js +0 -746
  267. package/packages/core/dist/workflow/context-watch.test.js.map +0 -1
  268. 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"
@@ -69,7 +69,6 @@ def _validate_against_schema(
69
69
  List of error message strings.
70
70
  """
71
71
  errors: list[str] = []
72
- definitions = schema.get("definitions", {})
73
72
 
74
73
  def _resolve_ref(ref: str) -> dict:
75
74
  """Resolve a $ref pointer like #/definitions/skill."""