@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.
Files changed (422) hide show
  1. package/README.md +22 -24
  2. package/package.json +3 -1
  3. package/packages/core/dist/cli/commands/doctor-file-layout.test.js.map +1 -1
  4. package/packages/core/dist/cli/commands/doctor-legacy.test.js +24 -0
  5. package/packages/core/dist/cli/commands/doctor-legacy.test.js.map +1 -1
  6. package/packages/core/dist/cli/commands/doctor.d.ts.map +1 -1
  7. package/packages/core/dist/cli/commands/doctor.js +101 -15
  8. package/packages/core/dist/cli/commands/doctor.js.map +1 -1
  9. package/packages/core/dist/cli/commands/e2e-fresh-install.test.js +2 -2
  10. package/packages/core/dist/cli/commands/e2e-fresh-install.test.js.map +1 -1
  11. package/packages/core/dist/cli/commands/e2e-upgrade.test.js +2 -2
  12. package/packages/core/dist/cli/commands/e2e-upgrade.test.js.map +1 -1
  13. package/packages/core/dist/cli/commands/hooks-consolidation.test.js +2 -2
  14. package/packages/core/dist/cli/commands/hooks-consolidation.test.js.map +1 -1
  15. package/packages/core/dist/cli/commands/init-consolidation.test.js.map +1 -1
  16. package/packages/core/dist/cli/commands/theme.js +1 -1
  17. package/packages/core/dist/cli/commands/theme.js.map +1 -1
  18. package/packages/core/dist/cli/commands/uninstall.d.ts.map +1 -1
  19. package/packages/core/dist/cli/commands/uninstall.js +24 -13
  20. package/packages/core/dist/cli/commands/uninstall.js.map +1 -1
  21. package/packages/core/dist/cli/commands/update-consolidation.test.js +0 -10
  22. package/packages/core/dist/cli/commands/update-consolidation.test.js.map +1 -1
  23. package/packages/core/dist/cli/commands/update.js.map +1 -1
  24. package/packages/core/dist/cli/ocean-profiles.test.js.map +1 -1
  25. package/packages/core/dist/cli/theme-maker.test.js +64 -115
  26. package/packages/core/dist/cli/theme-maker.test.js.map +1 -1
  27. package/packages/core/dist/cli/utils/themes.d.ts.map +1 -1
  28. package/packages/core/dist/cli/utils/themes.js +3 -2
  29. package/packages/core/dist/cli/utils/themes.js.map +1 -1
  30. package/packages/core/dist/index.d.ts +1 -1
  31. package/packages/core/dist/index.d.ts.map +1 -1
  32. package/packages/core/dist/index.js +2 -2
  33. package/packages/core/dist/index.js.map +1 -1
  34. package/packages/core/dist/plugins/plugin-discovery.d.ts +116 -0
  35. package/packages/core/dist/plugins/plugin-discovery.d.ts.map +1 -0
  36. package/packages/core/dist/plugins/plugin-discovery.js +165 -0
  37. package/packages/core/dist/plugins/plugin-discovery.js.map +1 -0
  38. package/packages/core/dist/plugins/plugin-discovery.test.d.ts +22 -0
  39. package/packages/core/dist/plugins/plugin-discovery.test.d.ts.map +1 -0
  40. package/packages/core/dist/plugins/plugin-discovery.test.js +498 -0
  41. package/packages/core/dist/plugins/plugin-discovery.test.js.map +1 -0
  42. package/packages/core/dist/scripts/add-ocean-profiles.js +1 -1
  43. package/packages/core/dist/scripts/add-ocean-profiles.js.map +1 -1
  44. package/packages/core/dist/scripts/generate-all-spiders.js +2 -0
  45. package/packages/core/dist/scripts/generate-all-spiders.js.map +1 -1
  46. package/packages/core/dist/scripts/generate-report.d.ts.map +1 -1
  47. package/packages/core/dist/scripts/generate-report.js +2 -0
  48. package/packages/core/dist/scripts/generate-report.js.map +1 -1
  49. package/packages/core/dist/scripts/generate-spider-report.js.map +1 -1
  50. package/packages/core/dist/scripts/generate-spider.d.ts.map +1 -1
  51. package/packages/core/dist/scripts/generate-spider.js +2 -0
  52. package/packages/core/dist/scripts/generate-spider.js.map +1 -1
  53. package/packages/core/dist/scripts/validate-ocean-profiles.js +1 -1
  54. package/packages/core/dist/scripts/validate-ocean-profiles.js.map +1 -1
  55. package/packages/core/dist/workflow/file-watch.d.ts +82 -0
  56. package/packages/core/dist/workflow/file-watch.d.ts.map +1 -0
  57. package/packages/core/dist/workflow/file-watch.js +198 -0
  58. package/packages/core/dist/workflow/file-watch.js.map +1 -0
  59. package/packages/core/dist/workflow/file-watch.test.d.ts +21 -0
  60. package/packages/core/dist/workflow/file-watch.test.d.ts.map +1 -0
  61. package/packages/core/dist/workflow/file-watch.test.js +469 -0
  62. package/packages/core/dist/workflow/file-watch.test.js.map +1 -0
  63. package/packages/core/dist/workflow/observation-writer.d.ts +79 -0
  64. package/packages/core/dist/workflow/observation-writer.d.ts.map +1 -0
  65. package/packages/core/dist/workflow/observation-writer.js +97 -0
  66. package/packages/core/dist/workflow/observation-writer.js.map +1 -0
  67. package/packages/core/dist/workflow/observation-writer.test.d.ts +18 -0
  68. package/packages/core/dist/workflow/observation-writer.test.d.ts.map +1 -0
  69. package/packages/core/dist/workflow/observation-writer.test.js +424 -0
  70. package/packages/core/dist/workflow/observation-writer.test.js.map +1 -0
  71. package/packages/core/dist/workflow/output-path-normalizer.d.ts +47 -0
  72. package/packages/core/dist/workflow/output-path-normalizer.d.ts.map +1 -0
  73. package/packages/core/dist/workflow/output-path-normalizer.js +79 -0
  74. package/packages/core/dist/workflow/output-path-normalizer.js.map +1 -0
  75. package/packages/core/dist/workflow/output-path-normalizer.test.d.ts +16 -0
  76. package/packages/core/dist/workflow/output-path-normalizer.test.d.ts.map +1 -0
  77. package/packages/core/dist/workflow/output-path-normalizer.test.js +157 -0
  78. package/packages/core/dist/workflow/output-path-normalizer.test.js.map +1 -0
  79. package/packages/core/dist/workflow/story-workflow-routing.test.js +4 -2
  80. package/packages/core/dist/workflow/story-workflow-routing.test.js.map +1 -1
  81. package/packages/core/dist/workflow/tandem-lifecycle.d.ts +117 -0
  82. package/packages/core/dist/workflow/tandem-lifecycle.d.ts.map +1 -0
  83. package/packages/core/dist/workflow/tandem-lifecycle.js +186 -0
  84. package/packages/core/dist/workflow/tandem-lifecycle.js.map +1 -0
  85. package/packages/core/dist/workflow/tandem-lifecycle.test.d.ts +16 -0
  86. package/packages/core/dist/workflow/tandem-lifecycle.test.d.ts.map +1 -0
  87. package/packages/core/dist/workflow/tandem-lifecycle.test.js +531 -0
  88. package/packages/core/dist/workflow/tandem-lifecycle.test.js.map +1 -0
  89. package/packages/core/dist/workflow/tool-watch.d.ts +68 -0
  90. package/packages/core/dist/workflow/tool-watch.d.ts.map +1 -0
  91. package/packages/core/dist/workflow/tool-watch.js +166 -0
  92. package/packages/core/dist/workflow/tool-watch.js.map +1 -0
  93. package/packages/core/dist/workflow/tool-watch.test.d.ts +18 -0
  94. package/packages/core/dist/workflow/tool-watch.test.d.ts.map +1 -0
  95. package/packages/core/dist/workflow/tool-watch.test.js +717 -0
  96. package/packages/core/dist/workflow/tool-watch.test.js.map +1 -0
  97. package/packages/core/dist/workflow/variable-resolver.js +1 -1
  98. package/packages/core/dist/workflow/variable-resolver.js.map +1 -1
  99. package/packages/core/dist/workflow/workflow-migration.test.js +8 -4
  100. package/packages/core/dist/workflow/workflow-migration.test.js.map +1 -1
  101. package/packages/core/dist/workflow/workflow-schema.d.ts +7 -0
  102. package/packages/core/dist/workflow/workflow-schema.d.ts.map +1 -1
  103. package/packages/core/dist/workflow/workflow-schema.js +44 -0
  104. package/packages/core/dist/workflow/workflow-schema.js.map +1 -1
  105. package/packages/core/dist/workflow/workflow-schema.test.d.ts.map +1 -1
  106. package/packages/core/dist/workflow/workflow-schema.test.js +192 -0
  107. package/packages/core/dist/workflow/workflow-schema.test.js.map +1 -1
  108. package/pennyfarthing-dist/agents/README.md +3 -1
  109. package/pennyfarthing-dist/agents/ba.md +165 -0
  110. package/pennyfarthing-dist/agents/handoff.md +18 -3
  111. package/pennyfarthing-dist/agents/sm-finish.md +1 -1
  112. package/pennyfarthing-dist/agents/sm-handoff.md +27 -4
  113. package/pennyfarthing-dist/agents/sm.md +11 -5
  114. package/pennyfarthing-dist/agents/tandem-backseat.md +119 -0
  115. package/pennyfarthing-dist/commands/ba.md +17 -0
  116. package/pennyfarthing-dist/commands/setup.md +4 -0
  117. package/pennyfarthing-dist/guides/agent-behavior.md +62 -6
  118. package/pennyfarthing-dist/guides/bikelane.md +3 -2
  119. package/pennyfarthing-dist/guides/scale-levels.md +4 -6
  120. package/pennyfarthing-dist/guides/tandem-protocol.md +158 -0
  121. package/pennyfarthing-dist/guides/workflow-schema.md +1 -1
  122. package/pennyfarthing-dist/personas/themes/a-team.yaml +30 -0
  123. package/pennyfarthing-dist/personas/themes/alice-in-wonderland.yaml +30 -0
  124. package/pennyfarthing-dist/personas/themes/battlestar-galactica.yaml +30 -0
  125. package/pennyfarthing-dist/personas/themes/blade-runner.yaml +30 -0
  126. package/pennyfarthing-dist/personas/themes/catch-22.yaml +30 -0
  127. package/pennyfarthing-dist/personas/themes/control.yaml +30 -0
  128. package/pennyfarthing-dist/personas/themes/cowboy-bebop.yaml +31 -0
  129. package/pennyfarthing-dist/personas/themes/discworld.yaml +32 -1
  130. package/pennyfarthing-dist/personas/themes/doctor-who.yaml +31 -0
  131. package/pennyfarthing-dist/personas/themes/dune.yaml +32 -0
  132. package/pennyfarthing-dist/personas/themes/fifth-element.yaml +327 -0
  133. package/pennyfarthing-dist/personas/themes/firefly.yaml +31 -0
  134. package/pennyfarthing-dist/personas/themes/game-of-thrones.yaml +30 -0
  135. package/pennyfarthing-dist/personas/themes/harry-potter.yaml +30 -0
  136. package/pennyfarthing-dist/personas/themes/hitchhikers-guide.yaml +30 -0
  137. package/pennyfarthing-dist/personas/themes/lord-of-the-rings.yaml +30 -0
  138. package/pennyfarthing-dist/personas/themes/mad-max.yaml +30 -0
  139. package/pennyfarthing-dist/personas/themes/mash.yaml +33 -0
  140. package/pennyfarthing-dist/personas/themes/princess-bride.yaml +34 -0
  141. package/pennyfarthing-dist/personas/themes/sandman.yaml +33 -0
  142. package/pennyfarthing-dist/personas/themes/star-trek-tng.yaml +34 -0
  143. package/pennyfarthing-dist/personas/themes/star-wars.yaml +33 -0
  144. package/pennyfarthing-dist/personas/themes/the-expanse.yaml +30 -0
  145. package/pennyfarthing-dist/personas/themes/the-matrix.yaml +30 -0
  146. package/pennyfarthing-dist/personas/themes/watchmen.yaml +30 -0
  147. package/pennyfarthing-dist/personas/themes/west-wing.yaml +30 -0
  148. package/pennyfarthing-dist/personas/themes/x-files.yaml +30 -0
  149. package/pennyfarthing-dist/scripts/README.md +1 -1
  150. package/pennyfarthing-dist/scripts/core/agent-session.sh +1 -1
  151. package/pennyfarthing-dist/scripts/hooks/bell-mode-hook.sh +131 -54
  152. package/pennyfarthing-dist/scripts/hooks/post-merge.sh +20 -10
  153. package/pennyfarthing-dist/scripts/misc/statusline.sh +50 -8
  154. package/pennyfarthing-dist/scripts/portraits/generate-portraits.py +2 -2
  155. package/pennyfarthing-dist/scripts/validation/validate-agent-schema.sh +1 -0
  156. package/pennyfarthing-dist/scripts/workflow/README.md +2 -2
  157. package/pennyfarthing-dist/scripts/workflow/finish-story.sh +10 -189
  158. package/pennyfarthing-dist/skills/skill-registry.schema.json +8 -0
  159. package/pennyfarthing-dist/skills/skill-registry.yaml +1 -1
  160. package/pennyfarthing-dist/skills/sprint/skill.md +25 -2
  161. package/pennyfarthing-dist/skills/theme/skill.md +1 -1
  162. package/pennyfarthing-dist/skills/workflow/skill.md +24 -1
  163. package/pennyfarthing-dist/workflows/architecture/workflow.yaml +65 -0
  164. package/pennyfarthing-dist/workflows/architecture.yaml +2 -2
  165. package/pennyfarthing-dist/workflows/bdd-tandem.yaml +70 -0
  166. package/pennyfarthing-dist/workflows/epics-and-stories/workflow.yaml +2 -2
  167. package/pennyfarthing-dist/workflows/implementation-readiness/workflow.yaml +2 -2
  168. package/pennyfarthing-dist/workflows/prd/workflow.yaml +2 -2
  169. package/pennyfarthing-dist/workflows/product-brief/workflow.yaml +2 -2
  170. package/pennyfarthing-dist/workflows/project-context/workflow.yaml +2 -2
  171. package/pennyfarthing-dist/workflows/quick-dev/workflow.yaml +2 -2
  172. package/pennyfarthing-dist/workflows/research/workflow.yaml +2 -2
  173. package/pennyfarthing-dist/workflows/retrospective/workflow.yaml +1 -1
  174. package/pennyfarthing-dist/workflows/sprint-planning/workflow.yaml +3 -3
  175. package/pennyfarthing-dist/workflows/tdd-tandem.yaml +61 -0
  176. package/pennyfarthing-dist/workflows/ux-design/workflow.yaml +2 -2
  177. package/pennyfarthing_scripts/__pycache__/cli.cpython-314.pyc +0 -0
  178. package/pennyfarthing_scripts/__pycache__/hooks.cpython-314.pyc +0 -0
  179. package/pennyfarthing_scripts/__pycache__/pretooluse_hook.cpython-314.pyc +0 -0
  180. package/pennyfarthing_scripts/__pycache__/schema_validation_hook.cpython-314.pyc +0 -0
  181. package/pennyfarthing_scripts/__pycache__/workflow.cpython-314.pyc +0 -0
  182. package/pennyfarthing_scripts/bellmode_hook.py +202 -47
  183. package/pennyfarthing_scripts/bikerack/__init__.py +36 -0
  184. package/pennyfarthing_scripts/bikerack/__main__.py +5 -0
  185. package/pennyfarthing_scripts/bikerack/__pycache__/__init__.cpython-314.pyc +0 -0
  186. package/pennyfarthing_scripts/bikerack/__pycache__/__main__.cpython-314.pyc +0 -0
  187. package/pennyfarthing_scripts/bikerack/__pycache__/cli.cpython-314.pyc +0 -0
  188. package/pennyfarthing_scripts/bikerack/__pycache__/launcher.cpython-314.pyc +0 -0
  189. package/pennyfarthing_scripts/bikerack/cli.py +148 -0
  190. package/pennyfarthing_scripts/bikerack/launcher.py +181 -0
  191. package/pennyfarthing_scripts/brownfield/__init__.py +6 -6
  192. package/pennyfarthing_scripts/brownfield/__main__.py +1 -0
  193. package/pennyfarthing_scripts/brownfield/cli.py +0 -1
  194. package/pennyfarthing_scripts/brownfield/discover.py +1 -2
  195. package/pennyfarthing_scripts/cli.py +16 -6
  196. package/pennyfarthing_scripts/codemarkers/__init__.py +5 -1
  197. package/pennyfarthing_scripts/codemarkers/__pycache__/__init__.cpython-314.pyc +0 -0
  198. package/pennyfarthing_scripts/codemarkers/__pycache__/__main__.cpython-314.pyc +0 -0
  199. package/pennyfarthing_scripts/codemarkers/__pycache__/analyze.cpython-314.pyc +0 -0
  200. package/pennyfarthing_scripts/codemarkers/__pycache__/cli.cpython-314.pyc +0 -0
  201. package/pennyfarthing_scripts/codemarkers/__pycache__/formatters.cpython-314.pyc +0 -0
  202. package/pennyfarthing_scripts/codemarkers/__pycache__/models.cpython-314.pyc +0 -0
  203. package/pennyfarthing_scripts/codemarkers/analyze.py +177 -2
  204. package/pennyfarthing_scripts/codemarkers/cli.py +50 -0
  205. package/pennyfarthing_scripts/codemarkers/formatters.py +0 -1
  206. package/pennyfarthing_scripts/codemarkers/models.py +15 -0
  207. package/pennyfarthing_scripts/common/__init__.py +8 -9
  208. package/pennyfarthing_scripts/common/__pycache__/__init__.cpython-314.pyc +0 -0
  209. package/pennyfarthing_scripts/common/__pycache__/config.cpython-314.pyc +0 -0
  210. package/pennyfarthing_scripts/common/config.py +1 -1
  211. package/pennyfarthing_scripts/complexity/__init__.py +1 -1
  212. package/pennyfarthing_scripts/complexity/__pycache__/__init__.cpython-314.pyc +0 -0
  213. package/pennyfarthing_scripts/complexity/__pycache__/__main__.cpython-314.pyc +0 -0
  214. package/pennyfarthing_scripts/complexity/__pycache__/analyze.cpython-314.pyc +0 -0
  215. package/pennyfarthing_scripts/complexity/__pycache__/cli.cpython-314.pyc +0 -0
  216. package/pennyfarthing_scripts/complexity/__pycache__/formatters.cpython-314.pyc +0 -0
  217. package/pennyfarthing_scripts/complexity/__pycache__/models.cpython-314.pyc +0 -0
  218. package/pennyfarthing_scripts/complexity/analyze.py +1 -1
  219. package/pennyfarthing_scripts/complexity/cli.py +5 -1
  220. package/pennyfarthing_scripts/complexity/formatters.py +1 -1
  221. package/pennyfarthing_scripts/context.py +14 -15
  222. package/pennyfarthing_scripts/deadcode/__pycache__/__init__.cpython-314.pyc +0 -0
  223. package/pennyfarthing_scripts/deadcode/__pycache__/__main__.cpython-314.pyc +0 -0
  224. package/pennyfarthing_scripts/deadcode/__pycache__/analyze.cpython-314.pyc +0 -0
  225. package/pennyfarthing_scripts/deadcode/__pycache__/cli.cpython-314.pyc +0 -0
  226. package/pennyfarthing_scripts/deadcode/__pycache__/formatters.cpython-314.pyc +0 -0
  227. package/pennyfarthing_scripts/deadcode/__pycache__/models.cpython-314.pyc +0 -0
  228. package/pennyfarthing_scripts/deadcode/analyze.py +3 -4
  229. package/pennyfarthing_scripts/deadcode/cli.py +2 -2
  230. package/pennyfarthing_scripts/dependencies/__init__.py +2 -2
  231. package/pennyfarthing_scripts/dependencies/__pycache__/__init__.cpython-314.pyc +0 -0
  232. package/pennyfarthing_scripts/dependencies/__pycache__/__main__.cpython-314.pyc +0 -0
  233. package/pennyfarthing_scripts/dependencies/__pycache__/analyze.cpython-314.pyc +0 -0
  234. package/pennyfarthing_scripts/dependencies/__pycache__/cli.cpython-314.pyc +0 -0
  235. package/pennyfarthing_scripts/dependencies/__pycache__/formatters.cpython-314.pyc +0 -0
  236. package/pennyfarthing_scripts/dependencies/__pycache__/models.cpython-314.pyc +0 -0
  237. package/pennyfarthing_scripts/dependencies/analyze.py +1 -1
  238. package/pennyfarthing_scripts/dependencies/cli.py +8 -4
  239. package/pennyfarthing_scripts/dependencies/formatters.py +1 -1
  240. package/pennyfarthing_scripts/git/__init__.py +5 -5
  241. package/pennyfarthing_scripts/git/create_branches.py +3 -2
  242. package/pennyfarthing_scripts/git/status_all.py +1 -1
  243. package/pennyfarthing_scripts/healthscore/__init__.py +2 -2
  244. package/pennyfarthing_scripts/healthscore/__main__.py +8 -0
  245. package/pennyfarthing_scripts/healthscore/__pycache__/__init__.cpython-314.pyc +0 -0
  246. package/pennyfarthing_scripts/healthscore/__pycache__/__main__.cpython-314.pyc +0 -0
  247. package/pennyfarthing_scripts/healthscore/__pycache__/analyze.cpython-314.pyc +0 -0
  248. package/pennyfarthing_scripts/healthscore/__pycache__/cli.cpython-314.pyc +0 -0
  249. package/pennyfarthing_scripts/healthscore/__pycache__/formatters.cpython-314.pyc +0 -0
  250. package/pennyfarthing_scripts/healthscore/__pycache__/models.cpython-314.pyc +0 -0
  251. package/pennyfarthing_scripts/healthscore/analyze.py +452 -21
  252. package/pennyfarthing_scripts/healthscore/cli.py +5 -1
  253. package/pennyfarthing_scripts/healthscore/models.py +0 -1
  254. package/pennyfarthing_scripts/hooks.py +8 -11
  255. package/pennyfarthing_scripts/hotspots/__init__.py +6 -6
  256. package/pennyfarthing_scripts/hotspots/__pycache__/__init__.cpython-314.pyc +0 -0
  257. package/pennyfarthing_scripts/hotspots/__pycache__/analyze.cpython-314.pyc +0 -0
  258. package/pennyfarthing_scripts/hotspots/__pycache__/cli.cpython-314.pyc +0 -0
  259. package/pennyfarthing_scripts/hotspots/__pycache__/models.cpython-314.pyc +0 -0
  260. package/pennyfarthing_scripts/hotspots/analyze.py +128 -14
  261. package/pennyfarthing_scripts/hotspots/cli.py +2 -2
  262. package/pennyfarthing_scripts/hotspots/models.py +0 -1
  263. package/pennyfarthing_scripts/jira/__init__.py +15 -17
  264. package/pennyfarthing_scripts/jira/__pycache__/__init__.cpython-314.pyc +0 -0
  265. package/pennyfarthing_scripts/jira/__pycache__/bidirectional.cpython-314.pyc +0 -0
  266. package/pennyfarthing_scripts/jira/__pycache__/claim.cpython-314.pyc +0 -0
  267. package/pennyfarthing_scripts/jira/__pycache__/cli.cpython-314.pyc +0 -0
  268. package/pennyfarthing_scripts/jira/__pycache__/client.cpython-314.pyc +0 -0
  269. package/pennyfarthing_scripts/jira/__pycache__/create.cpython-314.pyc +0 -0
  270. package/pennyfarthing_scripts/jira/__pycache__/epic.cpython-314.pyc +0 -0
  271. package/pennyfarthing_scripts/jira/__pycache__/reconcile.cpython-314.pyc +0 -0
  272. package/pennyfarthing_scripts/jira/__pycache__/story.cpython-314.pyc +0 -0
  273. package/pennyfarthing_scripts/jira/__pycache__/sync.cpython-314.pyc +0 -0
  274. package/pennyfarthing_scripts/jira/bidirectional.py +2 -3
  275. package/pennyfarthing_scripts/jira/claim.py +21 -0
  276. package/pennyfarthing_scripts/jira/cli.py +2 -2
  277. package/pennyfarthing_scripts/jira/client.py +4 -4
  278. package/pennyfarthing_scripts/jira/create.py +45 -1
  279. package/pennyfarthing_scripts/jira/epic.py +3 -2
  280. package/pennyfarthing_scripts/jira/reconcile.py +0 -1
  281. package/pennyfarthing_scripts/jira/story.py +2 -0
  282. package/pennyfarthing_scripts/jira/sync.py +1 -1
  283. package/pennyfarthing_scripts/migration/__pycache__/__init__.cpython-314.pyc +0 -0
  284. package/pennyfarthing_scripts/migration/__pycache__/session.cpython-314.pyc +0 -0
  285. package/pennyfarthing_scripts/migration/__pycache__/skill.cpython-314.pyc +0 -0
  286. package/pennyfarthing_scripts/migration/__pycache__/step.cpython-314.pyc +0 -0
  287. package/pennyfarthing_scripts/migration/__pycache__/validate.cpython-314.pyc +0 -0
  288. package/pennyfarthing_scripts/migration/skill.py +0 -1
  289. package/pennyfarthing_scripts/migration/step.py +0 -1
  290. package/pennyfarthing_scripts/migration/validate.py +8 -5
  291. package/pennyfarthing_scripts/patch_mode.py +2 -2
  292. package/pennyfarthing_scripts/preflight/__init__.py +1 -1
  293. package/pennyfarthing_scripts/preflight/__pycache__/__init__.cpython-314.pyc +0 -0
  294. package/pennyfarthing_scripts/preflight/__pycache__/finish.cpython-314.pyc +0 -0
  295. package/pennyfarthing_scripts/preflight/finish.py +0 -1
  296. package/pennyfarthing_scripts/pretooluse_hook.py +6 -7
  297. package/pennyfarthing_scripts/prime/__init__.py +2 -0
  298. package/pennyfarthing_scripts/prime/__pycache__/__init__.cpython-314.pyc +0 -0
  299. package/pennyfarthing_scripts/prime/__pycache__/cli.cpython-314.pyc +0 -0
  300. package/pennyfarthing_scripts/prime/__pycache__/loader.cpython-314.pyc +0 -0
  301. package/pennyfarthing_scripts/prime/__pycache__/persona.cpython-314.pyc +0 -0
  302. package/pennyfarthing_scripts/prime/__pycache__/tiers.cpython-314.pyc +0 -0
  303. package/pennyfarthing_scripts/prime/cli.py +18 -1
  304. package/pennyfarthing_scripts/prime/loader.py +72 -3
  305. package/pennyfarthing_scripts/prime/persona.py +4 -2
  306. package/pennyfarthing_scripts/prime/tiers.py +17 -4
  307. package/pennyfarthing_scripts/schema_validation_hook.py +2 -3
  308. package/pennyfarthing_scripts/sprint/__init__.py +10 -12
  309. package/pennyfarthing_scripts/sprint/__main__.py +2 -2
  310. package/pennyfarthing_scripts/sprint/__pycache__/__init__.cpython-314.pyc +0 -0
  311. package/pennyfarthing_scripts/sprint/__pycache__/archive.cpython-314.pyc +0 -0
  312. package/pennyfarthing_scripts/sprint/__pycache__/archive_epic.cpython-314.pyc +0 -0
  313. package/pennyfarthing_scripts/sprint/__pycache__/cli.cpython-314.pyc +0 -0
  314. package/pennyfarthing_scripts/sprint/__pycache__/epic_add.cpython-314.pyc +0 -0
  315. package/pennyfarthing_scripts/sprint/__pycache__/import_epic.cpython-314.pyc +0 -0
  316. package/pennyfarthing_scripts/sprint/__pycache__/loader.cpython-314.pyc +0 -0
  317. package/pennyfarthing_scripts/sprint/__pycache__/status.cpython-314.pyc +0 -0
  318. package/pennyfarthing_scripts/sprint/__pycache__/story_add.cpython-314.pyc +0 -0
  319. package/pennyfarthing_scripts/sprint/__pycache__/story_finish.cpython-314.pyc +0 -0
  320. package/pennyfarthing_scripts/sprint/__pycache__/story_update.cpython-314.pyc +0 -0
  321. package/pennyfarthing_scripts/sprint/__pycache__/validate_cmd.cpython-314.pyc +0 -0
  322. package/pennyfarthing_scripts/sprint/__pycache__/validator.cpython-314.pyc +0 -0
  323. package/pennyfarthing_scripts/sprint/__pycache__/work.cpython-314.pyc +0 -0
  324. package/pennyfarthing_scripts/sprint/__pycache__/yaml_io.cpython-314.pyc +0 -0
  325. package/pennyfarthing_scripts/sprint/archive.py +0 -1
  326. package/pennyfarthing_scripts/sprint/archive_epic.py +1 -4
  327. package/pennyfarthing_scripts/sprint/cli.py +34 -28
  328. package/pennyfarthing_scripts/sprint/epic_add.py +8 -1
  329. package/pennyfarthing_scripts/sprint/import_epic.py +42 -18
  330. package/pennyfarthing_scripts/sprint/loader.py +6 -0
  331. package/pennyfarthing_scripts/sprint/status.py +1 -2
  332. package/pennyfarthing_scripts/sprint/story_add.py +2 -2
  333. package/pennyfarthing_scripts/sprint/story_finish.py +3 -5
  334. package/pennyfarthing_scripts/sprint/story_update.py +11 -3
  335. package/pennyfarthing_scripts/sprint/validate_cmd.py +0 -1
  336. package/pennyfarthing_scripts/sprint/validator.py +120 -6
  337. package/pennyfarthing_scripts/sprint/work.py +1 -4
  338. package/pennyfarthing_scripts/sprint/yaml_io.py +10 -2
  339. package/pennyfarthing_scripts/story/__init__.py +14 -16
  340. package/pennyfarthing_scripts/story/__pycache__/__init__.cpython-314.pyc +0 -0
  341. package/pennyfarthing_scripts/story/__pycache__/size.cpython-314.pyc +0 -0
  342. package/pennyfarthing_scripts/story/__pycache__/template.cpython-314.pyc +0 -0
  343. package/pennyfarthing_scripts/story/size.py +0 -1
  344. package/pennyfarthing_scripts/story/template.py +0 -1
  345. package/pennyfarthing_scripts/swebench.py +1 -2
  346. package/pennyfarthing_scripts/tests/__pycache__/conftest.cpython-314-pytest-9.0.2.pyc +0 -0
  347. package/pennyfarthing_scripts/tests/__pycache__/test_bikerack.cpython-314-pytest-9.0.2.pyc +0 -0
  348. package/pennyfarthing_scripts/tests/__pycache__/test_epic_shard_validation.cpython-314-pytest-9.0.2.pyc +0 -0
  349. package/pennyfarthing_scripts/tests/__pycache__/test_healthscore.cpython-314-pytest-9.0.2.pyc +0 -0
  350. package/pennyfarthing_scripts/tests/__pycache__/test_sprint_validator.cpython-314-pytest-9.0.2.pyc +0 -0
  351. package/pennyfarthing_scripts/tests/__pycache__/test_yaml_io.cpython-314-pytest-9.0.2.pyc +0 -0
  352. package/pennyfarthing_scripts/tests/conftest.py +1 -2
  353. package/pennyfarthing_scripts/tests/test_bikerack.py +785 -0
  354. package/pennyfarthing_scripts/tests/test_brownfield.py +10 -13
  355. package/pennyfarthing_scripts/tests/test_cli_modules.py +0 -4
  356. package/pennyfarthing_scripts/tests/test_codemarkers.py +13 -8
  357. package/pennyfarthing_scripts/tests/test_common.py +9 -4
  358. package/pennyfarthing_scripts/tests/test_epic_shard_validation.py +699 -0
  359. package/pennyfarthing_scripts/tests/test_git_utils.py +10 -13
  360. package/pennyfarthing_scripts/tests/test_healthscore.py +17 -25
  361. package/pennyfarthing_scripts/tests/test_jira_package.py +0 -3
  362. package/pennyfarthing_scripts/tests/test_package_structure.py +3 -16
  363. package/pennyfarthing_scripts/tests/test_patch_mode.py +7 -11
  364. package/pennyfarthing_scripts/tests/test_prime.py +39 -21
  365. package/pennyfarthing_scripts/tests/test_sprint_package.py +3 -8
  366. package/pennyfarthing_scripts/tests/test_sprint_validator.py +53 -5
  367. package/pennyfarthing_scripts/tests/test_story_add.py +3 -7
  368. package/pennyfarthing_scripts/tests/test_story_package.py +0 -3
  369. package/pennyfarthing_scripts/tests/test_story_update.py +5 -10
  370. package/pennyfarthing_scripts/tests/test_tiers.py +18 -17
  371. package/pennyfarthing_scripts/tests/test_token_counting.py +19 -13
  372. package/pennyfarthing_scripts/tests/test_topology_loader.py +620 -0
  373. package/pennyfarthing_scripts/tests/test_validate_cmd.py +2 -7
  374. package/pennyfarthing_scripts/tests/test_workflow_check.py +0 -2
  375. package/pennyfarthing_scripts/tests/test_yaml_io.py +0 -3
  376. package/pennyfarthing_scripts/theme/__pycache__/__init__.cpython-314.pyc +0 -0
  377. package/pennyfarthing_scripts/theme/__pycache__/cli.cpython-314.pyc +0 -0
  378. package/pennyfarthing_scripts/theme/cli.py +3 -2
  379. package/pennyfarthing_scripts/validate/__init__.py +21 -0
  380. package/pennyfarthing_scripts/validate/__pycache__/__init__.cpython-314.pyc +0 -0
  381. package/pennyfarthing_scripts/validate/__pycache__/cli.cpython-314.pyc +0 -0
  382. package/pennyfarthing_scripts/validate/adapters/__init__.py +0 -0
  383. package/pennyfarthing_scripts/validate/adapters/__pycache__/__init__.cpython-314.pyc +0 -0
  384. package/pennyfarthing_scripts/validate/adapters/__pycache__/agent.cpython-314.pyc +0 -0
  385. package/pennyfarthing_scripts/validate/adapters/__pycache__/schema.cpython-314.pyc +0 -0
  386. package/pennyfarthing_scripts/validate/adapters/__pycache__/skill_command.cpython-314.pyc +0 -0
  387. package/pennyfarthing_scripts/validate/adapters/__pycache__/sprint.cpython-314.pyc +0 -0
  388. package/pennyfarthing_scripts/validate/adapters/__pycache__/workflow.cpython-314.pyc +0 -0
  389. package/pennyfarthing_scripts/validate/adapters/agent.py +239 -0
  390. package/pennyfarthing_scripts/validate/adapters/schema.py +30 -0
  391. package/pennyfarthing_scripts/validate/adapters/skill_command.py +291 -0
  392. package/pennyfarthing_scripts/validate/adapters/sprint.py +69 -0
  393. package/pennyfarthing_scripts/validate/adapters/workflow.py +320 -0
  394. package/pennyfarthing_scripts/validate/cli.py +141 -0
  395. package/pennyfarthing_scripts/welcome_hook.py +2 -3
  396. package/pennyfarthing_scripts/workflow.py +3 -3
  397. package/scripts/README.md +3 -15
  398. package/pennyfarthing-dist/commands/benchmark-control.md +0 -69
  399. package/pennyfarthing-dist/commands/benchmark.md +0 -485
  400. package/pennyfarthing-dist/commands/job-fair.md +0 -102
  401. package/pennyfarthing-dist/commands/solo.md +0 -447
  402. package/pennyfarthing-dist/guides/benchmarks.md +0 -62
  403. package/pennyfarthing-dist/scripts/test/ensure-swebench-data.sh +0 -59
  404. package/pennyfarthing-dist/scripts/test/ground-truth-judge.py +0 -220
  405. package/pennyfarthing-dist/scripts/test/swebench-judge.py +0 -374
  406. package/pennyfarthing-dist/scripts/test/test-cache.sh +0 -165
  407. package/pennyfarthing-dist/scripts/test/test-setup.sh +0 -337
  408. package/pennyfarthing-dist/scripts/theme/compute-theme-tiers.sh +0 -13
  409. package/pennyfarthing-dist/scripts/theme/compute_theme_tiers.py +0 -402
  410. package/pennyfarthing-dist/scripts/theme/update-theme-tiers.sh +0 -97
  411. package/pennyfarthing-dist/skills/finalize-run/SKILL.md +0 -261
  412. package/pennyfarthing-dist/skills/judge/SKILL.md +0 -644
  413. package/pennyfarthing-dist/skills/persona-benchmark/SKILL.md +0 -187
  414. package/pennyfarthing-dist/workflows/dev-story/checklist.md +0 -80
  415. package/pennyfarthing-dist/workflows/dev-story/instructions.xml +0 -410
  416. package/pennyfarthing-dist/workflows/dev-story/workflow.yaml +0 -50
  417. package/pennyfarthing-dist/workflows/quick-spec/steps/step-01-understand.md +0 -201
  418. package/pennyfarthing-dist/workflows/quick-spec/steps/step-02-investigate.md +0 -156
  419. package/pennyfarthing-dist/workflows/quick-spec/steps/step-03-generate.md +0 -140
  420. package/pennyfarthing-dist/workflows/quick-spec/steps/step-04-review.md +0 -203
  421. package/pennyfarthing-dist/workflows/quick-spec/tech-spec-template.md +0 -74
  422. package/pennyfarthing-dist/workflows/quick-spec/workflow.yaml +0 -27
@@ -8,7 +8,6 @@ concurrent execution regardless of model behavior.
8
8
  import asyncio
9
9
  import json
10
10
  import re
11
- import sys
12
11
  from dataclasses import dataclass, field
13
12
  from pathlib import Path
14
13
  from typing import Any
@@ -40,15 +40,14 @@ from pathlib import Path
40
40
  sys.path.insert(0, str(Path(__file__).parent))
41
41
 
42
42
  from hooks import (
43
- find_project_root,
44
- get_cyclist_port,
45
- send_to_cyclist,
46
- read_stdin_json,
47
- output_hook_response,
48
43
  HookResponse,
49
- load_settings,
50
- is_cyclist_running,
44
+ find_project_root,
51
45
  get_context_state,
46
+ is_cyclist_running,
47
+ load_settings,
48
+ output_hook_response,
49
+ read_stdin_json,
50
+ send_to_cyclist,
52
51
  )
53
52
 
54
53
 
@@ -47,6 +47,7 @@ from pennyfarthing_scripts.prime.loader import (
47
47
  load_agent_definition,
48
48
  load_behavior_guide,
49
49
  load_domain_docs,
50
+ load_repos_topology,
50
51
  load_session_context,
51
52
  load_sidecars,
52
53
  load_sprint_context,
@@ -94,6 +95,7 @@ __all__ = [
94
95
  "load_session_context",
95
96
  "load_sidecars",
96
97
  "load_domain_docs",
98
+ "load_repos_topology",
97
99
  # Workflow detection
98
100
  "detect_workflow_state",
99
101
  "check_redirect",
@@ -22,6 +22,10 @@ import argparse
22
22
  import json
23
23
  import sys
24
24
  from pathlib import Path
25
+ from typing import TYPE_CHECKING
26
+
27
+ if TYPE_CHECKING:
28
+ from typing import Any
25
29
 
26
30
  from pennyfarthing_scripts.common.config import get_project_root
27
31
  from pennyfarthing_scripts.prime.loader import (
@@ -42,7 +46,7 @@ from pennyfarthing_scripts.prime.persona import (
42
46
  load_persona,
43
47
  )
44
48
  from pennyfarthing_scripts.prime.session import cleanup_old_sessions, register_session
45
- from pennyfarthing_scripts.prime.tiers import ContextTier, tier_from_string, load_tier_components
49
+ from pennyfarthing_scripts.prime.tiers import ContextTier, load_tier_components, tier_from_string
46
50
  from pennyfarthing_scripts.prime.workflow import check_redirect, detect_workflow_state
47
51
 
48
52
 
@@ -98,6 +102,7 @@ def _component_header(name: str, agent_name: str | None) -> str:
98
102
  "persona_compressed": f"Persona: {agent_name} (compressed)",
99
103
  "behavior_guide": "Agent Behavior Guide",
100
104
  "sprint_context": "Sprint Context",
105
+ "repos_topology": "Repos Topology",
101
106
  "session_header": "Active Session",
102
107
  "session_assessment": "Session Assessment",
103
108
  "sidecars": f"Agent Sidecar: {agent_name}",
@@ -114,6 +119,7 @@ def _component_source(name: str, agent_name: str | None, root: Path) -> str | No
114
119
  "persona_compressed": None,
115
120
  "behavior_guide": ".pennyfarthing/guides/agent-behavior.md",
116
121
  "sprint_context": "sprint/current-sprint.yaml",
122
+ "repos_topology": ".pennyfarthing/repos.yaml",
117
123
  "session_header": None,
118
124
  "session_assessment": None,
119
125
  "sidecars": f".pennyfarthing/sidecars/{agent_name}/",
@@ -470,6 +476,17 @@ def prime(
470
476
  _print_header("Sprint Context", quiet)
471
477
  print(sprint_content)
472
478
 
479
+ # ==========================================================================
480
+ # PRIORITY 5.5: Repos Topology
481
+ # ==========================================================================
482
+ if not json_output:
483
+ from pennyfarthing_scripts.prime.loader import load_repos_topology
484
+
485
+ topology_content = load_repos_topology(root)
486
+ if topology_content:
487
+ _print_header("Repos Topology", quiet)
488
+ print(topology_content)
489
+
473
490
  # ==========================================================================
474
491
  # PRIORITY 6: Session context
475
492
  # ==========================================================================
@@ -14,7 +14,6 @@ from __future__ import annotations
14
14
 
15
15
  import re
16
16
  from pathlib import Path
17
- from typing import Any
18
17
 
19
18
  from pennyfarthing_scripts.common.config import get_project_root
20
19
 
@@ -68,7 +67,7 @@ def load_sprint_context(project_root: Path | None = None) -> str | None:
68
67
  Formatted sprint summary, or None if no sprint data
69
68
  """
70
69
  # Import here to avoid circular imports
71
- from pennyfarthing_scripts.sprint.loader import load_sprint, get_sprint_info
70
+ from pennyfarthing_scripts.sprint.loader import load_sprint
72
71
  from pennyfarthing_scripts.sprint.status import get_sprint_status
73
72
 
74
73
  root = project_root or get_project_root()
@@ -136,7 +135,7 @@ def _extract_session_parts(content: str) -> tuple[str, str]:
136
135
 
137
136
  # Header: everything before first ## heading
138
137
  header_lines = []
139
- for i, line in enumerate(lines):
138
+ for line in lines:
140
139
  if line.startswith("## "):
141
140
  break
142
141
  header_lines.append(line)
@@ -237,3 +236,73 @@ def load_domain_docs(project_root: Path | None = None) -> list[tuple[str, str]]:
237
236
  docs.append((doc_file.name, doc_file.read_text()))
238
237
 
239
238
  return docs
239
+
240
+
241
+ def load_repos_topology(project_root: Path | None = None) -> str | None:
242
+ """Load repos.yaml topology as formatted context for agents.
243
+
244
+ Reads .pennyfarthing/repos.yaml and formats the topology fields
245
+ (owns, never_edit, symlinks, ui_layer, components_path) as a
246
+ readable manifest for agent spatial awareness.
247
+
248
+ Args:
249
+ project_root: Project root path (auto-detected if not provided)
250
+
251
+ Returns:
252
+ Formatted topology context string, or None if unavailable
253
+ """
254
+ import yaml
255
+
256
+ root = project_root or get_project_root()
257
+ repos_file = root / ".pennyfarthing" / "repos.yaml"
258
+
259
+ if not repos_file.exists():
260
+ return None
261
+
262
+ try:
263
+ data = yaml.safe_load(repos_file.read_text())
264
+ except Exception:
265
+ return None
266
+
267
+ if not isinstance(data, dict) or "repos" not in data:
268
+ return None
269
+
270
+ repos = data["repos"]
271
+ if not repos:
272
+ return None
273
+
274
+ lines: list[str] = []
275
+ for name, config in repos.items():
276
+ lines.append(f"## {name}")
277
+ if config.get("path"):
278
+ lines.append(f"Path: {config['path']}")
279
+ if config.get("type"):
280
+ lines.append(f"Type: {config['type']}")
281
+ if config.get("description"):
282
+ lines.append(f"Description: {config['description']}")
283
+
284
+ owns = config.get("owns", [])
285
+ if owns:
286
+ lines.append(f"Owns: {', '.join(owns)}")
287
+
288
+ never_edit = config.get("never_edit", [])
289
+ if never_edit:
290
+ lines.append(f"Never Edit: {', '.join(never_edit)}")
291
+
292
+ symlinks = config.get("symlinks", {})
293
+ if symlinks:
294
+ lines.append("Symlinks:")
295
+ for src, dest in symlinks.items():
296
+ lines.append(f" {src} → {dest}")
297
+
298
+ ui_layer = config.get("ui_layer")
299
+ if ui_layer:
300
+ lines.append(f"UI Layer: {ui_layer}")
301
+
302
+ components_path = config.get("components_path")
303
+ if components_path:
304
+ lines.append(f"Components: {components_path}")
305
+
306
+ lines.append("")
307
+
308
+ return "\n".join(lines).strip() if lines else None
@@ -14,15 +14,17 @@ import yaml
14
14
  from pennyfarthing_scripts.common.config import get_project_root, load_yaml_config
15
15
  from pennyfarthing_scripts.common.themes import (
16
16
  get_current_theme as _get_current_theme,
17
+ )
18
+ from pennyfarthing_scripts.common.themes import (
17
19
  resolve_theme_path,
18
20
  )
19
21
  from pennyfarthing_scripts.prime.models import CrewMember, Persona
20
22
 
21
-
22
23
  # Standard agent roles for crew manifest
23
24
  AGENT_ROLES = [
24
25
  "sm", "tea", "dev", "reviewer", "architect",
25
26
  "pm", "tech-writer", "ux-designer", "devops", "orchestrator",
27
+ "ba",
26
28
  ]
27
29
 
28
30
 
@@ -128,7 +130,7 @@ def get_crew_manifest(project_root: Path | None = None) -> list[CrewMember]:
128
130
  project_root: Project root path (auto-detected if not provided)
129
131
 
130
132
  Returns:
131
- List of CrewMember objects for all 10 standard roles
133
+ List of CrewMember objects for all 11 standard roles
132
134
  """
133
135
  root = project_root or get_project_root()
134
136
  theme = get_current_theme(root)
@@ -35,21 +35,22 @@ def estimate_tokens(text: str) -> int:
35
35
  return max(1, len(text) // 4)
36
36
 
37
37
 
38
- from pennyfarthing_scripts.prime.loader import (
38
+ from pennyfarthing_scripts.prime.loader import ( # noqa: E402
39
39
  load_agent_definition,
40
40
  load_behavior_guide,
41
+ load_repos_topology,
41
42
  load_session_context,
42
43
  load_sidecars,
43
44
  load_sprint_context,
44
45
  )
45
- from pennyfarthing_scripts.prime.persona import (
46
+ from pennyfarthing_scripts.prime.persona import ( # noqa: E402
46
47
  format_persona_compressed,
47
48
  get_crew_manifest,
48
49
  get_user_title,
49
50
  is_character_voice_enabled,
50
51
  load_persona,
51
52
  )
52
- from pennyfarthing_scripts.prime.workflow import detect_workflow_state
53
+ from pennyfarthing_scripts.prime.workflow import detect_workflow_state # noqa: E402
53
54
 
54
55
 
55
56
  class ContextTier(Enum):
@@ -78,7 +79,7 @@ def tier_from_string(value: str) -> ContextTier:
78
79
  return ContextTier(normalized)
79
80
  except ValueError:
80
81
  valid = ", ".join(t.value for t in ContextTier)
81
- raise ValueError(f"Invalid tier '{value}'. Must be one of: {valid}")
82
+ raise ValueError(f"Invalid tier '{value}'. Must be one of: {valid}") from None
82
83
 
83
84
 
84
85
  def load_tier_components(
@@ -134,6 +135,10 @@ def load_tier_components(
134
135
  if sprint_content:
135
136
  add_component("sprint_context", sprint_content)
136
137
 
138
+ topology_content = load_repos_topology(project_root)
139
+ if topology_content:
140
+ add_component("repos_topology", topology_content)
141
+
137
142
  session_result = load_session_context(project_root)
138
143
  if session_result:
139
144
  filename, header, _ = session_result
@@ -156,6 +161,10 @@ def load_tier_components(
156
161
  compressed = format_persona_compressed(persona, theme, agent_name)
157
162
  add_component("persona_compressed", compressed)
158
163
 
164
+ topology_content = load_repos_topology(project_root)
165
+ if topology_content:
166
+ add_component("repos_topology", topology_content)
167
+
159
168
  components["token_counts"] = token_counts
160
169
  components["total_tokens"] = sum(token_counts.values())
161
170
  return components
@@ -185,6 +194,10 @@ def load_tier_components(
185
194
  if sprint_content:
186
195
  add_component("sprint_context", sprint_content)
187
196
 
197
+ topology_content = load_repos_topology(project_root)
198
+ if topology_content:
199
+ add_component("repos_topology", topology_content)
200
+
188
201
  session_result = load_session_context(project_root)
189
202
  if session_result:
190
203
  filename, header, assessment = session_result
@@ -29,12 +29,11 @@ from pathlib import Path
29
29
  sys.path.insert(0, str(Path(__file__).parent))
30
30
 
31
31
  from hooks import (
32
- read_stdin_json,
33
- output_hook_response,
34
32
  HookResponse,
33
+ output_hook_response,
34
+ read_stdin_json,
35
35
  )
36
36
 
37
-
38
37
  # =============================================================================
39
38
  # File Type Detection
40
39
  # =============================================================================
@@ -18,6 +18,16 @@ Usage:
18
18
  """
19
19
 
20
20
  # Re-export from loader for backwards compatibility
21
+ # Import submodules to make them accessible
22
+ # CLI entry point - import module, not function, so "from sprint import cli" gets the module
23
+ from pennyfarthing_scripts.sprint import (
24
+ archive,
25
+ cli,
26
+ loader,
27
+ status,
28
+ work,
29
+ )
30
+ from pennyfarthing_scripts.sprint.cli import main
21
31
  from pennyfarthing_scripts.sprint.loader import (
22
32
  find_epic,
23
33
  find_story,
@@ -31,18 +41,6 @@ from pennyfarthing_scripts.sprint.loader import (
31
41
  load_sprint,
32
42
  )
33
43
 
34
- # Import submodules to make them accessible
35
- from pennyfarthing_scripts.sprint import (
36
- archive,
37
- loader,
38
- status,
39
- work,
40
- )
41
-
42
- # CLI entry point - import module, not function, so "from sprint import cli" gets the module
43
- from pennyfarthing_scripts.sprint import cli
44
- from pennyfarthing_scripts.sprint.cli import main
45
-
46
44
  __all__ = [
47
45
  # Loader functions
48
46
  "find_epic",
@@ -4,7 +4,7 @@ Entry point for python -m pennyfarthing_scripts.sprint
4
4
 
5
5
  import sys
6
6
 
7
- from pennyfarthing_scripts.sprint.cli import cli
7
+ from pennyfarthing_scripts.sprint.cli import sprint
8
8
 
9
9
  if __name__ == "__main__":
10
- sys.exit(cli())
10
+ sys.exit(sprint())
@@ -4,7 +4,6 @@ Sprint story archiving.
4
4
  Provides functions for archiving completed stories.
5
5
  """
6
6
 
7
- from pathlib import Path
8
7
  from typing import Any
9
8
 
10
9
  from pennyfarthing_scripts.common.config import get_project_root
@@ -18,7 +18,6 @@ from pennyfarthing_scripts.sprint.yaml_io import (
18
18
  _read_yaml_file,
19
19
  _write_yaml_file,
20
20
  read_sprint,
21
- write_sprint,
22
21
  )
23
22
 
24
23
 
@@ -252,13 +251,11 @@ def archive_epic(
252
251
 
253
252
  # Find the epic in merged data
254
253
  epic = None
255
- epic_index = None
256
- for i, e in enumerate(sprint_data["epics"]):
254
+ for e in sprint_data["epics"]:
257
255
  eid = str(e.get("id", ""))
258
256
  ejira = str(e.get("jira", ""))
259
257
  if eid == epic_id or ejira == epic_id:
260
258
  epic = e
261
- epic_index = i
262
259
  break
263
260
 
264
261
  if not epic:
@@ -150,7 +150,7 @@ def work(story_id: str | None, dry_run: bool):
150
150
  click.echo(f"Story: {story.get('id')}")
151
151
  click.echo(f"Title: {story.get('title')}")
152
152
  click.echo(f"Points: {story.get('points')}")
153
- click.echo(f"Status: Available")
153
+ click.echo("Status: Available")
154
154
  else:
155
155
  error_msg = result.get("error") or result.get("reason")
156
156
  raise click.ClickException(f"Not available: {error_msg}")
@@ -334,12 +334,12 @@ def story_claim(story_id: str, claim: bool):
334
334
 
335
335
 
336
336
  # Register story-add as story.add
337
- from pennyfarthing_scripts.sprint.story_add import story_add_command
337
+ from pennyfarthing_scripts.sprint.story_add import story_add_command # noqa: E402
338
338
 
339
339
  story.add_command(story_add_command, "add")
340
340
 
341
341
  # Register story-update as story.update
342
- from pennyfarthing_scripts.sprint.story_update import story_update_command
342
+ from pennyfarthing_scripts.sprint.story_update import story_update_command # noqa: E402
343
343
 
344
344
  story.add_command(story_update_command, "update")
345
345
 
@@ -501,7 +501,6 @@ def epic_cancel(epic_id: str, jira: bool, dry_run: bool):
501
501
  pf sprint epic cancel epic-42 --jira
502
502
  """
503
503
  from pennyfarthing_scripts.common.config import get_project_root
504
- from pennyfarthing_scripts.sprint.loader import load_sprint
505
504
  from pennyfarthing_scripts.sprint.yaml_io import read_sprint, write_sprint
506
505
 
507
506
  root = get_project_root()
@@ -583,7 +582,7 @@ def _cancel_epic_in_initiatives(epic_id: str, root, *, jira: bool, dry_run: bool
583
582
  init_name = init_data.get("name", init_file.stem)
584
583
  epics = init_data.get("epics", [])
585
584
 
586
- for i, e in enumerate(epics):
585
+ for _i, e in enumerate(epics):
587
586
  matched = False
588
587
  epic_dict = None
589
588
 
@@ -663,6 +662,8 @@ def epic_archive(epic_id: str | None, dry_run: bool, jira: bool):
663
662
  # Lazy import
664
663
  from pennyfarthing_scripts.sprint.archive_epic import (
665
664
  archive_all_completed,
665
+ )
666
+ from pennyfarthing_scripts.sprint.archive_epic import (
666
667
  archive_epic as do_archive_epic,
667
668
  )
668
669
 
@@ -757,7 +758,6 @@ def epic_remove(epic_id: str, dry_run: bool):
757
758
  pf sprint epic remove epic-41
758
759
  pf sprint epic remove epic-41 --dry-run
759
760
  """
760
- from pathlib import Path
761
761
 
762
762
  import yaml
763
763
 
@@ -886,20 +886,23 @@ def epic_promote(epic_id: str):
886
886
  new_epic_id = original_id
887
887
  existing_ids = {str(e.get("id", "")) for e in sprint_data["epics"] if isinstance(e, dict)}
888
888
 
889
+ # Normalize to numeric ID (ADR-0022: strip epic- prefix from values)
890
+ new_epic_id = new_epic_id.replace("epic-", "") if new_epic_id.startswith("epic-") else new_epic_id
891
+
889
892
  if new_epic_id in existing_ids:
890
893
  max_num = 0
891
894
  for eid in existing_ids:
892
- if eid.startswith("epic-"):
893
- try:
894
- max_num = max(max_num, int(eid.replace("epic-", "")))
895
- except ValueError:
896
- pass
897
- new_epic_id = f"epic-{max_num + 1}"
895
+ clean_eid = eid.replace("epic-", "") if eid.startswith("epic-") else eid
896
+ try:
897
+ max_num = max(max_num, int(clean_eid))
898
+ except ValueError:
899
+ pass
900
+ new_epic_id = str(max_num + 1)
898
901
  click.echo(f"Warning: Epic ID {original_id} already exists. Assigning new ID: {new_epic_id}")
899
902
 
900
903
  # Transform epic for current sprint
901
904
  old_id_num = original_id.replace("epic-", "")
902
- new_id_num = new_epic_id.replace("epic-", "")
905
+ new_id_num = new_epic_id.replace("epic-", "") if new_epic_id.startswith("epic-") else new_epic_id
903
906
 
904
907
  epic_data["id"] = new_epic_id
905
908
  epic_data["status"] = "backlog"
@@ -928,6 +931,13 @@ def epic_promote(epic_id: str):
928
931
  click.echo(f" Stories: {story_count}")
929
932
  click.echo("")
930
933
 
934
+ # Validate epic shard before writing (ADR-0022)
935
+ from pennyfarthing_scripts.sprint.validator import validate_epic_shard
936
+ validation = validate_epic_shard(dict(epic_data))
937
+ if not validation.valid:
938
+ error_msgs = "; ".join(e.message for e in validation.errors)
939
+ raise click.ClickException(f"Epic validation failed: {error_msgs}")
940
+
931
941
  # Append to sprint
932
942
  sprint_data["epics"].append(epic_data)
933
943
 
@@ -960,7 +970,6 @@ def epic_promote(epic_id: str):
960
970
  click.echo(f"Removed {original_id} from {source_init_file.name}")
961
971
  else:
962
972
  # Initiative is empty — remove shard and future.yaml reference
963
- init_name = init_data.get("name", "")
964
973
  init_slug = source_init_file.stem.replace("initiative-", "")
965
974
  source_init_file.unlink()
966
975
  click.echo(f"Removed empty initiative shard: {source_init_file.name}")
@@ -987,7 +996,7 @@ def epic_promote(epic_id: str):
987
996
 
988
997
 
989
998
  # Register epic-add as epic.add
990
- from pennyfarthing_scripts.sprint.epic_add import epic_add_command
999
+ from pennyfarthing_scripts.sprint.epic_add import epic_add_command # noqa: E402
991
1000
 
992
1001
  epic.add_command(epic_add_command, "add")
993
1002
 
@@ -1161,7 +1170,7 @@ def initiative_cancel(name: str, jira: bool, dry_run: bool):
1161
1170
  return
1162
1171
 
1163
1172
  # Cancel all epics
1164
- for i, e in enumerate(epics):
1173
+ for _i, e in enumerate(epics):
1165
1174
  if isinstance(e, str):
1166
1175
  shard = _epic_shard_path(sprint_dir, e)
1167
1176
  if shard.exists():
@@ -1216,7 +1225,6 @@ def check(id: str):
1216
1225
 
1217
1226
  from pennyfarthing_scripts.sprint.loader import (
1218
1227
  find_epic,
1219
- get_all_stories,
1220
1228
  load_sprint,
1221
1229
  )
1222
1230
  from pennyfarthing_scripts.sprint.work import check_story, get_next_story
@@ -1328,10 +1336,10 @@ def _find_epic_for_story(data: dict | None, story_id: str) -> str:
1328
1336
 
1329
1337
  @sprint.command()
1330
1338
  def info():
1331
- """Output sprint info as JSON for Cyclist sidebar.
1339
+ """Output sprint info as JSON.
1332
1340
 
1333
1341
  \b
1334
- Returns: {"remaining": N, "inProgress": N, "endDate": "YYYY-MM-DD"}
1342
+ Returns sprint header fields plus computed story point totals.
1335
1343
  """
1336
1344
  import json
1337
1345
 
@@ -1340,8 +1348,6 @@ def info():
1340
1348
  sprint_data = get_sprint_info()
1341
1349
  stories = get_all_stories()
1342
1350
 
1343
- end_date = sprint_data.get("end_date")
1344
-
1345
1351
  remaining = sum(
1346
1352
  s.get("points", 0) or 0
1347
1353
  for s in stories
@@ -1353,11 +1359,11 @@ def info():
1353
1359
  if s.get("status") == "in_progress"
1354
1360
  )
1355
1361
 
1356
- click.echo(json.dumps({
1357
- "remaining": remaining,
1358
- "inProgress": in_progress,
1359
- "endDate": str(end_date) if end_date else None,
1360
- }))
1362
+ result = {str(k): str(v) if hasattr(v, 'isoformat') else v for k, v in sprint_data.items()}
1363
+ result["remaining"] = remaining
1364
+ result["inProgress"] = in_progress
1365
+
1366
+ click.echo(json.dumps(result))
1361
1367
 
1362
1368
 
1363
1369
  # --- Metrics command (replaces sprint-metrics.sh) ---
@@ -1783,7 +1789,7 @@ completed:
1783
1789
  click.echo(f"Created {archive_file}")
1784
1790
 
1785
1791
  click.echo("")
1786
- click.echo(f"New sprint initialized:")
1792
+ click.echo("New sprint initialized:")
1787
1793
  click.echo(f" Name: TO Sprint {sprint_yyww}")
1788
1794
  click.echo(f" Jira ID: {jira_id}")
1789
1795
  click.echo(f" Dates: {start_date} to {end_date}")
@@ -1854,7 +1860,7 @@ sprint.commands["epic-add"].hidden = True
1854
1860
 
1855
1861
 
1856
1862
  # Register validate command from validate_cmd module
1857
- from pennyfarthing_scripts.sprint.validate_cmd import validate_command
1863
+ from pennyfarthing_scripts.sprint.validate_cmd import validate_command # noqa: E402
1858
1864
 
1859
1865
  sprint.add_command(validate_command)
1860
1866
 
@@ -14,11 +14,12 @@ from typing import Any
14
14
  import click
15
15
  from ruamel.yaml.comments import CommentedMap, CommentedSeq
16
16
 
17
+ from pennyfarthing_scripts.sprint import validator as _shard_validator
17
18
  from pennyfarthing_scripts.sprint.yaml_io import (
18
19
  EPIC_KEY_ORDER,
20
+ _get_epic_ref,
19
21
  _read_yaml_file,
20
22
  _write_yaml_file,
21
- _get_epic_ref,
22
23
  )
23
24
 
24
25
 
@@ -76,6 +77,12 @@ def add_epic(
76
77
  if key not in EPIC_KEY_ORDER:
77
78
  epic[key] = fields[key]
78
79
 
80
+ # Validate epic shard before writing
81
+ validation = _shard_validator.validate_epic_shard(dict(epic))
82
+ if not validation.valid:
83
+ error_msgs = "; ".join(e.message for e in validation.errors)
84
+ return {"success": False, "error": f"Epic validation failed: {error_msgs}"}
85
+
79
86
  # Determine the shard reference
80
87
  ref = _get_epic_ref(epic)
81
88
  shard_file = sprint_dir / f"epic-{ref}.yaml"