@pennyfarthing/core 11.3.2 → 11.3.4

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 (1129) hide show
  1. package/README.md +1 -1
  2. package/package.json +3 -2
  3. package/packages/core/dist/cli/commands/doctor.d.ts +1 -9
  4. package/packages/core/dist/cli/commands/doctor.d.ts.map +1 -1
  5. package/packages/core/dist/cli/commands/doctor.js +51 -107
  6. package/packages/core/dist/cli/commands/doctor.js.map +1 -1
  7. package/packages/core/dist/cli/commands/stale-artifacts-cleanup.test.d.ts +17 -0
  8. package/packages/core/dist/cli/commands/stale-artifacts-cleanup.test.d.ts.map +1 -0
  9. package/packages/core/dist/cli/commands/stale-artifacts-cleanup.test.js +470 -0
  10. package/packages/core/dist/cli/commands/stale-artifacts-cleanup.test.js.map +1 -0
  11. package/packages/core/dist/cli/commands/update.d.ts.map +1 -1
  12. package/packages/core/dist/cli/commands/update.js +26 -1
  13. package/packages/core/dist/cli/commands/update.js.map +1 -1
  14. package/packages/core/dist/cli/cyclist-migration.test.d.ts +16 -0
  15. package/packages/core/dist/cli/cyclist-migration.test.d.ts.map +1 -0
  16. package/packages/core/dist/cli/cyclist-migration.test.js +229 -0
  17. package/packages/core/dist/cli/cyclist-migration.test.js.map +1 -0
  18. package/packages/core/dist/cli/utils/python.d.ts +0 -1
  19. package/packages/core/dist/cli/utils/python.d.ts.map +1 -1
  20. package/packages/core/dist/cli/utils/python.js +1 -11
  21. package/packages/core/dist/cli/utils/python.js.map +1 -1
  22. package/packages/core/dist/cli/utils/stale-artifacts.d.ts +59 -0
  23. package/packages/core/dist/cli/utils/stale-artifacts.d.ts.map +1 -0
  24. package/packages/core/dist/cli/utils/stale-artifacts.js +163 -0
  25. package/packages/core/dist/cli/utils/stale-artifacts.js.map +1 -0
  26. package/packages/core/dist/scripts/benchmark-integration.d.ts +182 -0
  27. package/packages/core/dist/scripts/benchmark-integration.d.ts.map +1 -0
  28. package/packages/core/dist/scripts/benchmark-integration.js +691 -0
  29. package/packages/core/dist/scripts/benchmark-integration.js.map +1 -0
  30. package/packages/core/dist/scripts/job-fair-aggregator.d.ts +150 -0
  31. package/packages/core/dist/scripts/job-fair-aggregator.d.ts.map +1 -0
  32. package/packages/core/dist/scripts/job-fair-aggregator.js +547 -0
  33. package/packages/core/dist/scripts/job-fair-aggregator.js.map +1 -0
  34. package/packages/core/dist/scripts/theme-detail.test.d.ts.map +1 -0
  35. package/packages/core/dist/scripts/theme-detail.test.js.map +1 -0
  36. package/packages/core/dist/server/paths.d.ts.map +1 -1
  37. package/packages/core/dist/server/paths.js +0 -6
  38. package/packages/core/dist/server/paths.js.map +1 -1
  39. package/pennyfarthing-dist/agents/sm-finish.md +2 -2
  40. package/pennyfarthing-dist/agents/testing-runner.md +1 -1
  41. package/pennyfarthing-dist/commands/pf-prime.md +1 -1
  42. package/pennyfarthing-dist/commands/pf-setup.md +4 -2
  43. package/pennyfarthing-dist/guides/agent-behavior.md +1 -1
  44. package/pennyfarthing-dist/guides/bikerack.md +1 -1
  45. package/pennyfarthing-dist/guides/brownfield-tools.md +7 -7
  46. package/pennyfarthing-dist/guides/gates.md +3 -3
  47. package/pennyfarthing-dist/guides/handoff-cli.md +6 -6
  48. package/pennyfarthing-dist/guides/prime.md +6 -6
  49. package/pennyfarthing-dist/guides/scale-levels.md +1 -1
  50. package/pennyfarthing-dist/personas/themes/discworld.yaml +16 -24
  51. package/pennyfarthing-dist/personas/themes/firefly.yaml +4 -4
  52. package/pennyfarthing-dist/pf/CLAUDE.md +213 -0
  53. package/pennyfarthing-dist/pf/README.md +66 -0
  54. package/pennyfarthing-dist/pf/__pycache__/__init__.cpython-311.pyc +0 -0
  55. package/pennyfarthing-dist/pf/__pycache__/__init__.cpython-314.pyc +0 -0
  56. package/pennyfarthing-dist/pf/__pycache__/bellmode_hook.cpython-314.pyc +0 -0
  57. package/pennyfarthing-dist/pf/__pycache__/cli.cpython-311.pyc +0 -0
  58. package/pennyfarthing-dist/pf/__pycache__/cli.cpython-314.pyc +0 -0
  59. package/pennyfarthing-dist/pf/__pycache__/config.cpython-314.pyc +0 -0
  60. package/pennyfarthing-dist/pf/__pycache__/context.cpython-311.pyc +0 -0
  61. package/pennyfarthing-dist/pf/__pycache__/context.cpython-314.pyc +0 -0
  62. package/pennyfarthing-dist/pf/__pycache__/hooks.cpython-314.pyc +0 -0
  63. package/pennyfarthing-dist/pf/__pycache__/jira.cpython-314.pyc +0 -0
  64. package/pennyfarthing-dist/pf/__pycache__/jira_bidirectional_sync.cpython-314.pyc +0 -0
  65. package/pennyfarthing-dist/pf/__pycache__/jira_epic_creation.cpython-314.pyc +0 -0
  66. package/pennyfarthing-dist/pf/__pycache__/jira_sync.cpython-314.pyc +0 -0
  67. package/pennyfarthing-dist/pf/__pycache__/jira_sync_story.cpython-314.pyc +0 -0
  68. package/pennyfarthing-dist/pf/__pycache__/output.cpython-314.pyc +0 -0
  69. package/pennyfarthing-dist/pf/__pycache__/patch_mode.cpython-314.pyc +0 -0
  70. package/pennyfarthing-dist/pf/__pycache__/pretooluse_hook.cpython-314.pyc +0 -0
  71. package/pennyfarthing-dist/pf/__pycache__/schema_validation_hook.cpython-314.pyc +0 -0
  72. package/pennyfarthing-dist/pf/__pycache__/session_start_hook.cpython-314.pyc +0 -0
  73. package/pennyfarthing-dist/pf/__pycache__/sprint.cpython-314.pyc +0 -0
  74. package/pennyfarthing-dist/pf/__pycache__/workflow.cpython-311.pyc +0 -0
  75. package/pennyfarthing-dist/pf/__pycache__/workflow.cpython-314.pyc +0 -0
  76. package/pennyfarthing-dist/pf/bc/__pycache__/__init__.cpython-311.pyc +0 -0
  77. package/pennyfarthing-dist/pf/bc/__pycache__/__init__.cpython-314.pyc +0 -0
  78. package/pennyfarthing-dist/pf/bc/__pycache__/cli.cpython-311.pyc +0 -0
  79. package/pennyfarthing-dist/pf/bc/__pycache__/cli.cpython-314.pyc +0 -0
  80. package/pennyfarthing-dist/pf/bc/__pycache__/focus.cpython-311.pyc +0 -0
  81. package/pennyfarthing-dist/pf/bc/__pycache__/focus.cpython-314.pyc +0 -0
  82. package/pennyfarthing-dist/pf/bc/__pycache__/split.cpython-314.pyc +0 -0
  83. package/pennyfarthing-dist/pf/bc/cli.py +242 -0
  84. package/pennyfarthing-dist/pf/bc/focus.py +334 -0
  85. package/pennyfarthing-dist/pf/bc/split.py +52 -0
  86. package/pennyfarthing-dist/pf/bellmode_hook.py +22 -0
  87. package/pennyfarthing-dist/pf/bikerack/__init__.py +44 -0
  88. package/pennyfarthing-dist/pf/bikerack/__main__.py +5 -0
  89. package/pennyfarthing-dist/pf/bikerack/__pycache__/__init__.cpython-311.pyc +0 -0
  90. package/pennyfarthing-dist/pf/bikerack/__pycache__/__init__.cpython-314.pyc +0 -0
  91. package/pennyfarthing-dist/pf/bikerack/__pycache__/__main__.cpython-314.pyc +0 -0
  92. package/pennyfarthing-dist/pf/bikerack/__pycache__/audit_log_panel.cpython-314.pyc +0 -0
  93. package/pennyfarthing-dist/pf/bikerack/__pycache__/background_panel.cpython-314.pyc +0 -0
  94. package/pennyfarthing-dist/pf/bikerack/__pycache__/base_panel.cpython-314.pyc +0 -0
  95. package/pennyfarthing-dist/pf/bikerack/__pycache__/changed_panel.cpython-314.pyc +0 -0
  96. package/pennyfarthing-dist/pf/bikerack/__pycache__/cli.cpython-311.pyc +0 -0
  97. package/pennyfarthing-dist/pf/bikerack/__pycache__/cli.cpython-314.pyc +0 -0
  98. package/pennyfarthing-dist/pf/bikerack/__pycache__/context_meter_footer.cpython-314.pyc +0 -0
  99. package/pennyfarthing-dist/pf/bikerack/__pycache__/debug_panel.cpython-314.pyc +0 -0
  100. package/pennyfarthing-dist/pf/bikerack/__pycache__/diffs_panel.cpython-314.pyc +0 -0
  101. package/pennyfarthing-dist/pf/bikerack/__pycache__/events.cpython-314.pyc +0 -0
  102. package/pennyfarthing-dist/pf/bikerack/__pycache__/git_panel.cpython-314.pyc +0 -0
  103. package/pennyfarthing-dist/pf/bikerack/__pycache__/launcher.cpython-311.pyc +0 -0
  104. package/pennyfarthing-dist/pf/bikerack/__pycache__/launcher.cpython-314.pyc +0 -0
  105. package/pennyfarthing-dist/pf/bikerack/__pycache__/portrait_resolver.cpython-314.pyc +0 -0
  106. package/pennyfarthing-dist/pf/bikerack/__pycache__/progress_panel.cpython-314.pyc +0 -0
  107. package/pennyfarthing-dist/pf/bikerack/__pycache__/sprint_panel.cpython-314.pyc +0 -0
  108. package/pennyfarthing-dist/pf/bikerack/__pycache__/story_detail_data.cpython-314.pyc +0 -0
  109. package/pennyfarthing-dist/pf/bikerack/__pycache__/story_detail_screen.cpython-314.pyc +0 -0
  110. package/pennyfarthing-dist/pf/bikerack/__pycache__/tui.cpython-314.pyc +0 -0
  111. package/pennyfarthing-dist/pf/bikerack/__pycache__/ws_client.cpython-314.pyc +0 -0
  112. package/pennyfarthing-dist/pf/bikerack/audit_log_panel.py +161 -0
  113. package/pennyfarthing-dist/pf/bikerack/background_panel.py +162 -0
  114. package/pennyfarthing-dist/pf/bikerack/changed_panel.py +201 -0
  115. package/pennyfarthing-dist/pf/bikerack/cli.py +145 -0
  116. package/pennyfarthing-dist/pf/bikerack/context_meter_footer.py +138 -0
  117. package/pennyfarthing-dist/pf/bikerack/debug_panel.py +248 -0
  118. package/pennyfarthing-dist/pf/bikerack/diffs_panel.py +409 -0
  119. package/pennyfarthing-dist/pf/bikerack/git_panel.py +139 -0
  120. package/pennyfarthing-dist/pf/bikerack/launcher.py +240 -0
  121. package/pennyfarthing-dist/pf/bikerack/portrait_resolver.py +139 -0
  122. package/pennyfarthing-dist/pf/bikerack/progress_panel.py +314 -0
  123. package/pennyfarthing-dist/pf/bikerack/sprint_panel.py +435 -0
  124. package/pennyfarthing-dist/pf/bikerack/story_detail_data.py +247 -0
  125. package/pennyfarthing-dist/pf/bikerack/story_detail_screen.py +177 -0
  126. package/pennyfarthing-dist/pf/bikerack/tui.py +935 -0
  127. package/pennyfarthing-dist/pf/bmad/__pycache__/__init__.cpython-314.pyc +0 -0
  128. package/pennyfarthing-dist/pf/bmad/__pycache__/cli.cpython-314.pyc +0 -0
  129. package/pennyfarthing-dist/pf/bmad/cli.py +197 -0
  130. package/pennyfarthing-dist/pf/bmad/importer.py +200 -0
  131. package/pennyfarthing-dist/pf/bmad/sync.py +464 -0
  132. package/pennyfarthing-dist/pf/bmad/test_parser.py +253 -0
  133. package/pennyfarthing-dist/pf/bmad/test_sync.py +223 -0
  134. package/pennyfarthing-dist/pf/brownfield/__init__.py +35 -0
  135. package/pennyfarthing-dist/pf/brownfield/__main__.py +8 -0
  136. package/pennyfarthing-dist/pf/brownfield/__pycache__/__init__.cpython-314.pyc +0 -0
  137. package/pennyfarthing-dist/pf/brownfield/__pycache__/__main__.cpython-314.pyc +0 -0
  138. package/pennyfarthing-dist/pf/brownfield/__pycache__/cli.cpython-314.pyc +0 -0
  139. package/pennyfarthing-dist/pf/brownfield/__pycache__/discover.cpython-314.pyc +0 -0
  140. package/pennyfarthing-dist/pf/brownfield/cli.py +130 -0
  141. package/pennyfarthing-dist/pf/cli.py +354 -0
  142. package/pennyfarthing-dist/pf/codemarkers/__init__.py +23 -0
  143. package/pennyfarthing-dist/pf/codemarkers/__main__.py +6 -0
  144. package/pennyfarthing-dist/pf/codemarkers/__pycache__/__init__.cpython-314.pyc +0 -0
  145. package/pennyfarthing-dist/pf/codemarkers/__pycache__/__main__.cpython-314.pyc +0 -0
  146. package/pennyfarthing-dist/pf/codemarkers/__pycache__/analyze.cpython-314.pyc +0 -0
  147. package/pennyfarthing-dist/pf/codemarkers/__pycache__/cli.cpython-314.pyc +0 -0
  148. package/pennyfarthing-dist/pf/codemarkers/__pycache__/formatters.cpython-314.pyc +0 -0
  149. package/pennyfarthing-dist/pf/codemarkers/__pycache__/models.cpython-314.pyc +0 -0
  150. package/pennyfarthing-dist/pf/codemarkers/analyze.py +501 -0
  151. package/pennyfarthing-dist/pf/codemarkers/cli.py +179 -0
  152. package/pennyfarthing-dist/pf/codemarkers/formatters.py +88 -0
  153. package/pennyfarthing-dist/pf/common/__init__.py +48 -0
  154. package/pennyfarthing-dist/pf/common/__pycache__/__init__.cpython-311.pyc +0 -0
  155. package/pennyfarthing-dist/pf/common/__pycache__/__init__.cpython-314.pyc +0 -0
  156. package/pennyfarthing-dist/pf/common/__pycache__/config.cpython-311.pyc +0 -0
  157. package/pennyfarthing-dist/pf/common/__pycache__/config.cpython-314.pyc +0 -0
  158. package/pennyfarthing-dist/pf/common/__pycache__/output.cpython-311.pyc +0 -0
  159. package/pennyfarthing-dist/pf/common/__pycache__/output.cpython-314.pyc +0 -0
  160. package/pennyfarthing-dist/pf/common/__pycache__/pr_config.cpython-314.pyc +0 -0
  161. package/pennyfarthing-dist/pf/common/__pycache__/themes.cpython-314.pyc +0 -0
  162. package/pennyfarthing-dist/pf/common/pr_config.py +38 -0
  163. package/pennyfarthing-dist/pf/common/themes.py +253 -0
  164. package/pennyfarthing-dist/pf/complexity/__init__.py +15 -0
  165. package/pennyfarthing-dist/pf/complexity/__main__.py +6 -0
  166. package/pennyfarthing-dist/pf/complexity/__pycache__/__init__.cpython-314.pyc +0 -0
  167. package/pennyfarthing-dist/pf/complexity/__pycache__/__main__.cpython-314.pyc +0 -0
  168. package/pennyfarthing-dist/pf/complexity/__pycache__/analyze.cpython-314.pyc +0 -0
  169. package/pennyfarthing-dist/pf/complexity/__pycache__/cli.cpython-314.pyc +0 -0
  170. package/pennyfarthing-dist/pf/complexity/__pycache__/formatters.cpython-314.pyc +0 -0
  171. package/pennyfarthing-dist/pf/complexity/__pycache__/models.cpython-314.pyc +0 -0
  172. package/pennyfarthing-dist/pf/complexity/analyze.py +207 -0
  173. package/pennyfarthing-dist/pf/complexity/cli.py +82 -0
  174. package/pennyfarthing-dist/pf/complexity/formatters.py +64 -0
  175. package/pennyfarthing-dist/pf/config.py +21 -0
  176. package/pennyfarthing-dist/pf/consultation/__pycache__/__init__.cpython-311.pyc +0 -0
  177. package/pennyfarthing-dist/pf/consultation/__pycache__/__init__.cpython-314.pyc +0 -0
  178. package/pennyfarthing-dist/pf/consultation/__pycache__/cli.cpython-311.pyc +0 -0
  179. package/pennyfarthing-dist/pf/consultation/__pycache__/cli.cpython-314.pyc +0 -0
  180. package/pennyfarthing-dist/pf/consultation/__pycache__/dialogue_manager.cpython-314.pyc +0 -0
  181. package/pennyfarthing-dist/pf/consultation/cli.py +149 -0
  182. package/pennyfarthing-dist/pf/deadcode/__main__.py +6 -0
  183. package/pennyfarthing-dist/pf/deadcode/__pycache__/__init__.cpython-311.pyc +0 -0
  184. package/pennyfarthing-dist/pf/deadcode/__pycache__/__init__.cpython-314.pyc +0 -0
  185. package/pennyfarthing-dist/pf/deadcode/__pycache__/__main__.cpython-314.pyc +0 -0
  186. package/pennyfarthing-dist/pf/deadcode/__pycache__/analyze.cpython-314.pyc +0 -0
  187. package/pennyfarthing-dist/pf/deadcode/__pycache__/cli.cpython-311.pyc +0 -0
  188. package/pennyfarthing-dist/pf/deadcode/__pycache__/cli.cpython-314.pyc +0 -0
  189. package/pennyfarthing-dist/pf/deadcode/__pycache__/formatters.cpython-314.pyc +0 -0
  190. package/pennyfarthing-dist/pf/deadcode/__pycache__/models.cpython-314.pyc +0 -0
  191. package/pennyfarthing-dist/pf/deadcode/analyze.py +322 -0
  192. package/pennyfarthing-dist/pf/deadcode/cli.py +163 -0
  193. package/pennyfarthing-dist/pf/deadcode/formatters.py +106 -0
  194. package/pennyfarthing-dist/pf/dependencies/__init__.py +20 -0
  195. package/pennyfarthing-dist/pf/dependencies/__main__.py +5 -0
  196. package/pennyfarthing-dist/pf/dependencies/__pycache__/__init__.cpython-314.pyc +0 -0
  197. package/pennyfarthing-dist/pf/dependencies/__pycache__/__main__.cpython-314.pyc +0 -0
  198. package/pennyfarthing-dist/pf/dependencies/__pycache__/analyze.cpython-314.pyc +0 -0
  199. package/pennyfarthing-dist/pf/dependencies/__pycache__/cli.cpython-314.pyc +0 -0
  200. package/pennyfarthing-dist/pf/dependencies/__pycache__/formatters.cpython-314.pyc +0 -0
  201. package/pennyfarthing-dist/pf/dependencies/__pycache__/models.cpython-314.pyc +0 -0
  202. package/pennyfarthing-dist/pf/dependencies/analyze.py +155 -0
  203. package/pennyfarthing-dist/pf/dependencies/cli.py +76 -0
  204. package/pennyfarthing-dist/pf/dependencies/formatters.py +63 -0
  205. package/pennyfarthing-dist/pf/epic/__pycache__/__init__.cpython-311.pyc +0 -0
  206. package/pennyfarthing-dist/pf/epic/__pycache__/__init__.cpython-314.pyc +0 -0
  207. package/pennyfarthing-dist/pf/epic/__pycache__/cli.cpython-311.pyc +0 -0
  208. package/pennyfarthing-dist/pf/epic/__pycache__/cli.cpython-314.pyc +0 -0
  209. package/pennyfarthing-dist/pf/gate/cli.py +56 -0
  210. package/pennyfarthing-dist/pf/git/__init__.py +40 -0
  211. package/pennyfarthing-dist/pf/git/__pycache__/__init__.cpython-314.pyc +0 -0
  212. package/pennyfarthing-dist/pf/git/__pycache__/create_branches.cpython-314.pyc +0 -0
  213. package/pennyfarthing-dist/pf/git/__pycache__/status_all.cpython-314.pyc +0 -0
  214. package/pennyfarthing-dist/pf/git/create_branches.py +439 -0
  215. package/pennyfarthing-dist/pf/git/hooks_installer.py +151 -0
  216. package/pennyfarthing-dist/pf/git/repos.py +196 -0
  217. package/pennyfarthing-dist/pf/git/status_all.py +326 -0
  218. package/pennyfarthing-dist/pf/git/worktree.py +302 -0
  219. package/pennyfarthing-dist/pf/git_group/__pycache__/__init__.cpython-311.pyc +0 -0
  220. package/pennyfarthing-dist/pf/git_group/__pycache__/__init__.cpython-314.pyc +0 -0
  221. package/pennyfarthing-dist/pf/git_group/__pycache__/cli.cpython-311.pyc +0 -0
  222. package/pennyfarthing-dist/pf/git_group/__pycache__/cli.cpython-314.pyc +0 -0
  223. package/pennyfarthing-dist/pf/git_group/cli.py +203 -0
  224. package/pennyfarthing-dist/pf/handoff/__pycache__/__init__.cpython-311.pyc +0 -0
  225. package/pennyfarthing-dist/pf/handoff/__pycache__/__init__.cpython-314.pyc +0 -0
  226. package/pennyfarthing-dist/pf/handoff/__pycache__/cli.cpython-311.pyc +0 -0
  227. package/pennyfarthing-dist/pf/handoff/__pycache__/cli.cpython-314.pyc +0 -0
  228. package/pennyfarthing-dist/pf/handoff/__pycache__/complete_phase.cpython-314.pyc +0 -0
  229. package/pennyfarthing-dist/pf/handoff/__pycache__/gate_file.cpython-314.pyc +0 -0
  230. package/pennyfarthing-dist/pf/handoff/__pycache__/gate_runner.cpython-314.pyc +0 -0
  231. package/pennyfarthing-dist/pf/handoff/__pycache__/marker.cpython-314.pyc +0 -0
  232. package/pennyfarthing-dist/pf/handoff/__pycache__/phase_check.cpython-314.pyc +0 -0
  233. package/pennyfarthing-dist/pf/handoff/__pycache__/resolve_gate.cpython-314.pyc +0 -0
  234. package/pennyfarthing-dist/pf/handoff/cli.py +152 -0
  235. package/pennyfarthing-dist/pf/handoff/marker.py +109 -0
  236. package/pennyfarthing-dist/pf/handoff/phase_check.py +96 -0
  237. package/pennyfarthing-dist/pf/healthscore/__init__.py +21 -0
  238. package/pennyfarthing-dist/pf/healthscore/__main__.py +14 -0
  239. package/pennyfarthing-dist/pf/healthscore/__pycache__/__init__.cpython-311.pyc +0 -0
  240. package/pennyfarthing-dist/pf/healthscore/__pycache__/__init__.cpython-314.pyc +0 -0
  241. package/pennyfarthing-dist/pf/healthscore/__pycache__/__main__.cpython-314.pyc +0 -0
  242. package/pennyfarthing-dist/pf/healthscore/__pycache__/analyze.cpython-311.pyc +0 -0
  243. package/pennyfarthing-dist/pf/healthscore/__pycache__/analyze.cpython-314.pyc +0 -0
  244. package/pennyfarthing-dist/pf/healthscore/__pycache__/cli.cpython-311.pyc +0 -0
  245. package/pennyfarthing-dist/pf/healthscore/__pycache__/cli.cpython-314.pyc +0 -0
  246. package/pennyfarthing-dist/pf/healthscore/__pycache__/formatters.cpython-314.pyc +0 -0
  247. package/pennyfarthing-dist/pf/healthscore/__pycache__/models.cpython-311.pyc +0 -0
  248. package/pennyfarthing-dist/pf/healthscore/__pycache__/models.cpython-314.pyc +0 -0
  249. package/pennyfarthing-dist/pf/healthscore/analyze.py +606 -0
  250. package/pennyfarthing-dist/pf/healthscore/cli.py +80 -0
  251. package/pennyfarthing-dist/pf/healthscore/formatters.py +46 -0
  252. package/pennyfarthing-dist/pf/hooks/__pycache__/__init__.cpython-311.pyc +0 -0
  253. package/pennyfarthing-dist/pf/hooks/__pycache__/__init__.cpython-314.pyc +0 -0
  254. package/pennyfarthing-dist/pf/hooks/__pycache__/bell_mode.cpython-311.pyc +0 -0
  255. package/pennyfarthing-dist/pf/hooks/__pycache__/bell_mode.cpython-314.pyc +0 -0
  256. package/pennyfarthing-dist/pf/hooks/__pycache__/cli.cpython-311.pyc +0 -0
  257. package/pennyfarthing-dist/pf/hooks/__pycache__/cli.cpython-314.pyc +0 -0
  258. package/pennyfarthing-dist/pf/hooks/__pycache__/context_breaker.cpython-311.pyc +0 -0
  259. package/pennyfarthing-dist/pf/hooks/__pycache__/context_breaker.cpython-314.pyc +0 -0
  260. package/pennyfarthing-dist/pf/hooks/__pycache__/context_warning.cpython-311.pyc +0 -0
  261. package/pennyfarthing-dist/pf/hooks/__pycache__/context_warning.cpython-314.pyc +0 -0
  262. package/pennyfarthing-dist/pf/hooks/__pycache__/cyclist_pretooluse.cpython-311.pyc +0 -0
  263. package/pennyfarthing-dist/pf/hooks/__pycache__/cyclist_pretooluse.cpython-314.pyc +0 -0
  264. package/pennyfarthing-dist/pf/hooks/__pycache__/pre_edit_check.cpython-311.pyc +0 -0
  265. package/pennyfarthing-dist/pf/hooks/__pycache__/pre_edit_check.cpython-314.pyc +0 -0
  266. package/pennyfarthing-dist/pf/hooks/__pycache__/reflector_check.cpython-311.pyc +0 -0
  267. package/pennyfarthing-dist/pf/hooks/__pycache__/reflector_check.cpython-314.pyc +0 -0
  268. package/pennyfarthing-dist/pf/hooks/__pycache__/schema_validation.cpython-311.pyc +0 -0
  269. package/pennyfarthing-dist/pf/hooks/__pycache__/schema_validation.cpython-314.pyc +0 -0
  270. package/pennyfarthing-dist/pf/hooks/__pycache__/session_start.cpython-311.pyc +0 -0
  271. package/pennyfarthing-dist/pf/hooks/__pycache__/session_start.cpython-314.pyc +0 -0
  272. package/pennyfarthing-dist/pf/hooks/__pycache__/session_stop.cpython-314.pyc +0 -0
  273. package/pennyfarthing-dist/pf/hooks/__pycache__/sprint_yaml_validation.cpython-311.pyc +0 -0
  274. package/pennyfarthing-dist/pf/hooks/__pycache__/sprint_yaml_validation.cpython-314.pyc +0 -0
  275. package/pennyfarthing-dist/pf/hooks/__pycache__/statusline.cpython-311.pyc +0 -0
  276. package/pennyfarthing-dist/pf/hooks/__pycache__/statusline.cpython-314.pyc +0 -0
  277. package/pennyfarthing-dist/pf/hooks/bell_mode.py +214 -0
  278. package/pennyfarthing-dist/pf/hooks/cli.py +96 -0
  279. package/pennyfarthing-dist/pf/hooks/context_breaker.py +104 -0
  280. package/pennyfarthing-dist/pf/hooks/context_warning.py +66 -0
  281. package/pennyfarthing-dist/pf/hooks/cyclist_pretooluse.py +129 -0
  282. package/pennyfarthing-dist/pf/hooks/schema_validation.py +202 -0
  283. package/pennyfarthing-dist/pf/hooks/session_start.py +294 -0
  284. package/pennyfarthing-dist/pf/hooks/sprint_yaml_validation.py +97 -0
  285. package/pennyfarthing-dist/pf/hooks/statusline.py +429 -0
  286. package/pennyfarthing-dist/pf/hooks.py +32 -0
  287. package/pennyfarthing-dist/pf/hotspots/__init__.py +31 -0
  288. package/pennyfarthing-dist/pf/hotspots/__main__.py +6 -0
  289. package/pennyfarthing-dist/pf/hotspots/__pycache__/__init__.cpython-311.pyc +0 -0
  290. package/pennyfarthing-dist/pf/hotspots/__pycache__/__init__.cpython-314.pyc +0 -0
  291. package/pennyfarthing-dist/pf/hotspots/__pycache__/__main__.cpython-314.pyc +0 -0
  292. package/pennyfarthing-dist/pf/hotspots/__pycache__/analyze.cpython-311.pyc +0 -0
  293. package/pennyfarthing-dist/pf/hotspots/__pycache__/analyze.cpython-314.pyc +0 -0
  294. package/pennyfarthing-dist/pf/hotspots/__pycache__/cli.cpython-311.pyc +0 -0
  295. package/pennyfarthing-dist/pf/hotspots/__pycache__/cli.cpython-314.pyc +0 -0
  296. package/pennyfarthing-dist/pf/hotspots/__pycache__/formatters.cpython-314.pyc +0 -0
  297. package/pennyfarthing-dist/pf/hotspots/__pycache__/models.cpython-311.pyc +0 -0
  298. package/pennyfarthing-dist/pf/hotspots/__pycache__/models.cpython-314.pyc +0 -0
  299. package/pennyfarthing-dist/pf/hotspots/analyze.py +613 -0
  300. package/pennyfarthing-dist/pf/hotspots/cli.py +154 -0
  301. package/pennyfarthing-dist/pf/hotspots/formatters.py +109 -0
  302. package/pennyfarthing-dist/pf/jira/__init__.py +97 -0
  303. package/pennyfarthing-dist/pf/jira/__main__.py +10 -0
  304. package/pennyfarthing-dist/pf/jira/__pycache__/__init__.cpython-311.pyc +0 -0
  305. package/pennyfarthing-dist/pf/jira/__pycache__/__init__.cpython-314.pyc +0 -0
  306. package/pennyfarthing-dist/pf/jira/__pycache__/__main__.cpython-314.pyc +0 -0
  307. package/pennyfarthing-dist/pf/jira/__pycache__/bidirectional.cpython-311.pyc +0 -0
  308. package/pennyfarthing-dist/pf/jira/__pycache__/bidirectional.cpython-314.pyc +0 -0
  309. package/pennyfarthing-dist/pf/jira/__pycache__/claim.cpython-311.pyc +0 -0
  310. package/pennyfarthing-dist/pf/jira/__pycache__/claim.cpython-314.pyc +0 -0
  311. package/pennyfarthing-dist/pf/jira/__pycache__/cli.cpython-311.pyc +0 -0
  312. package/pennyfarthing-dist/pf/jira/__pycache__/cli.cpython-314.pyc +0 -0
  313. package/pennyfarthing-dist/pf/jira/__pycache__/client.cpython-311.pyc +0 -0
  314. package/pennyfarthing-dist/pf/jira/__pycache__/client.cpython-314.pyc +0 -0
  315. package/pennyfarthing-dist/pf/jira/__pycache__/compat.cpython-314.pyc +0 -0
  316. package/pennyfarthing-dist/pf/jira/__pycache__/create.cpython-311.pyc +0 -0
  317. package/pennyfarthing-dist/pf/jira/__pycache__/create.cpython-314.pyc +0 -0
  318. package/pennyfarthing-dist/pf/jira/__pycache__/epic.cpython-311.pyc +0 -0
  319. package/pennyfarthing-dist/pf/jira/__pycache__/epic.cpython-314.pyc +0 -0
  320. package/pennyfarthing-dist/pf/jira/__pycache__/mappings.cpython-314.pyc +0 -0
  321. package/pennyfarthing-dist/pf/jira/__pycache__/models.cpython-314.pyc +0 -0
  322. package/pennyfarthing-dist/pf/jira/__pycache__/operations.cpython-311.pyc +0 -0
  323. package/pennyfarthing-dist/pf/jira/__pycache__/operations.cpython-314.pyc +0 -0
  324. package/pennyfarthing-dist/pf/jira/__pycache__/reconcile.cpython-311.pyc +0 -0
  325. package/pennyfarthing-dist/pf/jira/__pycache__/reconcile.cpython-314.pyc +0 -0
  326. package/pennyfarthing-dist/pf/jira/__pycache__/story.cpython-311.pyc +0 -0
  327. package/pennyfarthing-dist/pf/jira/__pycache__/story.cpython-314.pyc +0 -0
  328. package/pennyfarthing-dist/pf/jira/__pycache__/sync.cpython-311.pyc +0 -0
  329. package/pennyfarthing-dist/pf/jira/__pycache__/sync.cpython-314.pyc +0 -0
  330. package/pennyfarthing-dist/pf/jira/bidirectional.py +587 -0
  331. package/pennyfarthing-dist/pf/jira/claim.py +232 -0
  332. package/pennyfarthing-dist/pf/jira/cli.py +362 -0
  333. package/pennyfarthing-dist/pf/jira/client.py +802 -0
  334. package/pennyfarthing-dist/pf/jira/create.py +311 -0
  335. package/pennyfarthing-dist/pf/jira/epic.py +177 -0
  336. package/pennyfarthing-dist/pf/jira/operations.py +124 -0
  337. package/pennyfarthing-dist/pf/jira/reconcile.py +276 -0
  338. package/pennyfarthing-dist/pf/jira/story.py +221 -0
  339. package/pennyfarthing-dist/pf/jira/sync.py +350 -0
  340. package/pennyfarthing-dist/pf/jira_bidirectional_sync.py +37 -0
  341. package/pennyfarthing-dist/pf/jira_epic_creation.py +30 -0
  342. package/pennyfarthing-dist/pf/jira_sync.py +36 -0
  343. package/pennyfarthing-dist/pf/jira_sync_story.py +30 -0
  344. package/pennyfarthing-dist/pf/launch/__pycache__/__init__.cpython-311.pyc +0 -0
  345. package/pennyfarthing-dist/pf/launch/__pycache__/__init__.cpython-314.pyc +0 -0
  346. package/pennyfarthing-dist/pf/launch/__pycache__/cli.cpython-311.pyc +0 -0
  347. package/pennyfarthing-dist/pf/launch/__pycache__/cli.cpython-314.pyc +0 -0
  348. package/pennyfarthing-dist/pf/launch/cli.py +231 -0
  349. package/pennyfarthing-dist/pf/migration/__init__.py +39 -0
  350. package/pennyfarthing-dist/pf/migration/__main__.py +10 -0
  351. package/pennyfarthing-dist/pf/migration/__pycache__/__init__.cpython-314.pyc +0 -0
  352. package/pennyfarthing-dist/pf/migration/__pycache__/__main__.cpython-314.pyc +0 -0
  353. package/pennyfarthing-dist/pf/migration/__pycache__/cli.cpython-314.pyc +0 -0
  354. package/pennyfarthing-dist/pf/migration/__pycache__/session.cpython-314.pyc +0 -0
  355. package/pennyfarthing-dist/pf/migration/__pycache__/skill.cpython-314.pyc +0 -0
  356. package/pennyfarthing-dist/pf/migration/__pycache__/step.cpython-314.pyc +0 -0
  357. package/pennyfarthing-dist/pf/migration/__pycache__/validate.cpython-314.pyc +0 -0
  358. package/pennyfarthing-dist/pf/migration/cli.py +304 -0
  359. package/pennyfarthing-dist/pf/migration/validate.py +285 -0
  360. package/pennyfarthing-dist/pf/output.py +37 -0
  361. package/pennyfarthing-dist/pf/patch_mode.py +449 -0
  362. package/pennyfarthing-dist/pf/preflight/__init__.py +17 -0
  363. package/pennyfarthing-dist/pf/preflight/__main__.py +10 -0
  364. package/pennyfarthing-dist/pf/preflight/__pycache__/__init__.cpython-314.pyc +0 -0
  365. package/pennyfarthing-dist/pf/preflight/__pycache__/__main__.cpython-314.pyc +0 -0
  366. package/pennyfarthing-dist/pf/preflight/__pycache__/cli.cpython-314.pyc +0 -0
  367. package/pennyfarthing-dist/pf/preflight/__pycache__/finish.cpython-314.pyc +0 -0
  368. package/pennyfarthing-dist/pf/preflight/cli.py +141 -0
  369. package/pennyfarthing-dist/pf/pretooluse_hook.py +10 -0
  370. package/pennyfarthing-dist/pf/prime/__init__.py +127 -0
  371. package/pennyfarthing-dist/pf/prime/__main__.py +8 -0
  372. package/pennyfarthing-dist/pf/prime/__pycache__/__init__.cpython-314.pyc +0 -0
  373. package/pennyfarthing-dist/pf/prime/__pycache__/__main__.cpython-314.pyc +0 -0
  374. package/pennyfarthing-dist/pf/prime/__pycache__/cli.cpython-314.pyc +0 -0
  375. package/pennyfarthing-dist/pf/prime/__pycache__/loader.cpython-314.pyc +0 -0
  376. package/pennyfarthing-dist/pf/prime/__pycache__/models.cpython-314.pyc +0 -0
  377. package/pennyfarthing-dist/pf/prime/__pycache__/persona.cpython-314.pyc +0 -0
  378. package/pennyfarthing-dist/pf/prime/__pycache__/session.cpython-314.pyc +0 -0
  379. package/pennyfarthing-dist/pf/prime/__pycache__/tiers.cpython-314.pyc +0 -0
  380. package/pennyfarthing-dist/pf/prime/__pycache__/version_sentinel.cpython-314.pyc +0 -0
  381. package/pennyfarthing-dist/pf/prime/__pycache__/workflow.cpython-314.pyc +0 -0
  382. package/pennyfarthing-dist/pf/prime/cli.py +662 -0
  383. package/pennyfarthing-dist/pf/prime/heatmap.py +643 -0
  384. package/pennyfarthing-dist/pf/prime/loader.py +308 -0
  385. package/pennyfarthing-dist/pf/prime/persona.py +311 -0
  386. package/pennyfarthing-dist/pf/prime/session.py +183 -0
  387. package/pennyfarthing-dist/pf/prime/tiers.py +214 -0
  388. package/pennyfarthing-dist/pf/prime/workflow.py +317 -0
  389. package/pennyfarthing-dist/pf/schema_validation_hook.py +10 -0
  390. package/pennyfarthing-dist/pf/session/__pycache__/__init__.cpython-311.pyc +0 -0
  391. package/pennyfarthing-dist/pf/session/__pycache__/__init__.cpython-314.pyc +0 -0
  392. package/pennyfarthing-dist/pf/session/__pycache__/cli.cpython-311.pyc +0 -0
  393. package/pennyfarthing-dist/pf/session/__pycache__/cli.cpython-314.pyc +0 -0
  394. package/pennyfarthing-dist/pf/session/cli.py +87 -0
  395. package/pennyfarthing-dist/pf/session_start_hook.py +10 -0
  396. package/pennyfarthing-dist/pf/settings/__pycache__/__init__.cpython-314.pyc +0 -0
  397. package/pennyfarthing-dist/pf/settings/__pycache__/cli.cpython-314.pyc +0 -0
  398. package/pennyfarthing-dist/pf/settings/__pycache__/settings.cpython-314.pyc +0 -0
  399. package/pennyfarthing-dist/pf/settings/cli.py +55 -0
  400. package/pennyfarthing-dist/pf/settings/settings.py +98 -0
  401. package/pennyfarthing-dist/pf/sprint/__init__.py +64 -0
  402. package/pennyfarthing-dist/pf/sprint/__main__.py +10 -0
  403. package/pennyfarthing-dist/pf/sprint/__pycache__/__init__.cpython-311.pyc +0 -0
  404. package/pennyfarthing-dist/pf/sprint/__pycache__/__init__.cpython-314.pyc +0 -0
  405. package/pennyfarthing-dist/pf/sprint/__pycache__/__main__.cpython-314.pyc +0 -0
  406. package/pennyfarthing-dist/pf/sprint/__pycache__/archive.cpython-311.pyc +0 -0
  407. package/pennyfarthing-dist/pf/sprint/__pycache__/archive.cpython-314.pyc +0 -0
  408. package/pennyfarthing-dist/pf/sprint/__pycache__/archive_epic.cpython-314.pyc +0 -0
  409. package/pennyfarthing-dist/pf/sprint/__pycache__/cli.cpython-311.pyc +0 -0
  410. package/pennyfarthing-dist/pf/sprint/__pycache__/cli.cpython-314.pyc +0 -0
  411. package/pennyfarthing-dist/pf/sprint/__pycache__/epic_add.cpython-311.pyc +0 -0
  412. package/pennyfarthing-dist/pf/sprint/__pycache__/epic_add.cpython-314.pyc +0 -0
  413. package/pennyfarthing-dist/pf/sprint/__pycache__/epic_update.cpython-311.pyc +0 -0
  414. package/pennyfarthing-dist/pf/sprint/__pycache__/epic_update.cpython-314.pyc +0 -0
  415. package/pennyfarthing-dist/pf/sprint/__pycache__/import_epic.cpython-314.pyc +0 -0
  416. package/pennyfarthing-dist/pf/sprint/__pycache__/loader.cpython-311.pyc +0 -0
  417. package/pennyfarthing-dist/pf/sprint/__pycache__/loader.cpython-314.pyc +0 -0
  418. package/pennyfarthing-dist/pf/sprint/__pycache__/status.cpython-311.pyc +0 -0
  419. package/pennyfarthing-dist/pf/sprint/__pycache__/status.cpython-314.pyc +0 -0
  420. package/pennyfarthing-dist/pf/sprint/__pycache__/story_add.cpython-311.pyc +0 -0
  421. package/pennyfarthing-dist/pf/sprint/__pycache__/story_add.cpython-314.pyc +0 -0
  422. package/pennyfarthing-dist/pf/sprint/__pycache__/story_finish.cpython-314.pyc +0 -0
  423. package/pennyfarthing-dist/pf/sprint/__pycache__/story_update.cpython-311.pyc +0 -0
  424. package/pennyfarthing-dist/pf/sprint/__pycache__/story_update.cpython-314.pyc +0 -0
  425. package/pennyfarthing-dist/pf/sprint/__pycache__/validate_cmd.cpython-311.pyc +0 -0
  426. package/pennyfarthing-dist/pf/sprint/__pycache__/validate_cmd.cpython-314.pyc +0 -0
  427. package/pennyfarthing-dist/pf/sprint/__pycache__/validator.cpython-311.pyc +0 -0
  428. package/pennyfarthing-dist/pf/sprint/__pycache__/validator.cpython-314.pyc +0 -0
  429. package/pennyfarthing-dist/pf/sprint/__pycache__/work.cpython-311.pyc +0 -0
  430. package/pennyfarthing-dist/pf/sprint/__pycache__/work.cpython-314.pyc +0 -0
  431. package/pennyfarthing-dist/pf/sprint/__pycache__/yaml_io.cpython-311.pyc +0 -0
  432. package/pennyfarthing-dist/pf/sprint/__pycache__/yaml_io.cpython-314.pyc +0 -0
  433. package/pennyfarthing-dist/pf/sprint/archive.py +164 -0
  434. package/pennyfarthing-dist/pf/sprint/archive_epic.py +625 -0
  435. package/pennyfarthing-dist/pf/sprint/cli.py +2021 -0
  436. package/pennyfarthing-dist/pf/sprint/epic_add.py +186 -0
  437. package/pennyfarthing-dist/pf/sprint/epic_update.py +148 -0
  438. package/pennyfarthing-dist/pf/sprint/import_epic.py +455 -0
  439. package/pennyfarthing-dist/pf/sprint/loader.py +441 -0
  440. package/pennyfarthing-dist/pf/sprint/status.py +121 -0
  441. package/pennyfarthing-dist/pf/sprint/story_add.py +372 -0
  442. package/pennyfarthing-dist/pf/sprint/story_finish.py +227 -0
  443. package/pennyfarthing-dist/pf/sprint/story_update.py +222 -0
  444. package/pennyfarthing-dist/pf/sprint/validate_cmd.py +307 -0
  445. package/pennyfarthing-dist/pf/sprint/validator.py +694 -0
  446. package/pennyfarthing-dist/pf/sprint/work.py +229 -0
  447. package/pennyfarthing-dist/pf/story/__init__.py +65 -0
  448. package/pennyfarthing-dist/pf/story/__main__.py +10 -0
  449. package/pennyfarthing-dist/pf/story/__pycache__/__init__.cpython-314.pyc +0 -0
  450. package/pennyfarthing-dist/pf/story/__pycache__/__main__.cpython-314.pyc +0 -0
  451. package/pennyfarthing-dist/pf/story/__pycache__/cli.cpython-314.pyc +0 -0
  452. package/pennyfarthing-dist/pf/story/__pycache__/create.cpython-314.pyc +0 -0
  453. package/pennyfarthing-dist/pf/story/__pycache__/size.cpython-314.pyc +0 -0
  454. package/pennyfarthing-dist/pf/story/__pycache__/template.cpython-314.pyc +0 -0
  455. package/pennyfarthing-dist/pf/story/cli.py +105 -0
  456. package/pennyfarthing-dist/pf/story/create.py +167 -0
  457. package/pennyfarthing-dist/pf/tests/__init__.py +1 -0
  458. package/pennyfarthing-dist/pf/tests/__pycache__/__init__.cpython-314.pyc +0 -0
  459. package/pennyfarthing-dist/pf/tests/__pycache__/conftest.cpython-314-pytest-9.0.2.pyc +0 -0
  460. package/pennyfarthing-dist/pf/tests/__pycache__/test_108_1_gate_migration.cpython-314-pytest-9.0.2.pyc +0 -0
  461. package/pennyfarthing-dist/pf/tests/__pycache__/test_archive_epic.cpython-314-pytest-9.0.2.pyc +0 -0
  462. package/pennyfarthing-dist/pf/tests/__pycache__/test_bc.cpython-314-pytest-9.0.2.pyc +0 -0
  463. package/pennyfarthing-dist/pf/tests/__pycache__/test_bikerack.cpython-314-pytest-9.0.2.pyc +0 -0
  464. package/pennyfarthing-dist/pf/tests/__pycache__/test_brownfield.cpython-314-pytest-9.0.2.pyc +0 -0
  465. package/pennyfarthing-dist/pf/tests/__pycache__/test_cli_modules.cpython-314-pytest-9.0.2.pyc +0 -0
  466. package/pennyfarthing-dist/pf/tests/__pycache__/test_cli_normalization.cpython-314-pytest-9.0.2.pyc +0 -0
  467. package/pennyfarthing-dist/pf/tests/__pycache__/test_codemarkers.cpython-314-pytest-9.0.2.pyc +0 -0
  468. package/pennyfarthing-dist/pf/tests/__pycache__/test_common.cpython-314-pytest-9.0.2.pyc +0 -0
  469. package/pennyfarthing-dist/pf/tests/__pycache__/test_confidence_sm_evaluation.cpython-314-pytest-9.0.2.pyc +0 -0
  470. package/pennyfarthing-dist/pf/tests/__pycache__/test_confidence_sm_gate.cpython-314-pytest-9.0.2.pyc +0 -0
  471. package/pennyfarthing-dist/pf/tests/__pycache__/test_dialogue_manager.cpython-314-pytest-9.0.2.pyc +0 -0
  472. package/pennyfarthing-dist/pf/tests/__pycache__/test_epic_shard_validation.cpython-314-pytest-9.0.2.pyc +0 -0
  473. package/pennyfarthing-dist/pf/tests/__pycache__/test_git_utils.cpython-314-pytest-9.0.2.pyc +0 -0
  474. package/pennyfarthing-dist/pf/tests/__pycache__/test_handoff_cli.cpython-314-pytest-9.0.2.pyc +0 -0
  475. package/pennyfarthing-dist/pf/tests/__pycache__/test_handoff_e2e.cpython-314-pytest-9.0.2.pyc +0 -0
  476. package/pennyfarthing-dist/pf/tests/__pycache__/test_healthscore.cpython-314-pytest-9.0.2.pyc +0 -0
  477. package/pennyfarthing-dist/pf/tests/__pycache__/test_jira_package.cpython-314-pytest-9.0.2.pyc +0 -0
  478. package/pennyfarthing-dist/pf/tests/__pycache__/test_package_structure.cpython-314-pytest-9.0.2.pyc +0 -0
  479. package/pennyfarthing-dist/pf/tests/__pycache__/test_patch_mode.cpython-314-pytest-9.0.2.pyc +0 -0
  480. package/pennyfarthing-dist/pf/tests/__pycache__/test_prime.cpython-314-pytest-9.0.2.pyc +0 -0
  481. package/pennyfarthing-dist/pf/tests/__pycache__/test_sprint_package.cpython-314-pytest-9.0.2.pyc +0 -0
  482. package/pennyfarthing-dist/pf/tests/__pycache__/test_sprint_panel.cpython-314-pytest-9.0.2.pyc +0 -0
  483. package/pennyfarthing-dist/pf/tests/__pycache__/test_sprint_validator.cpython-314-pytest-9.0.2.pyc +0 -0
  484. package/pennyfarthing-dist/pf/tests/__pycache__/test_story_add.cpython-314-pytest-9.0.2.pyc +0 -0
  485. package/pennyfarthing-dist/pf/tests/__pycache__/test_story_package.cpython-314-pytest-9.0.2.pyc +0 -0
  486. package/pennyfarthing-dist/pf/tests/__pycache__/test_story_update.cpython-314-pytest-9.0.2.pyc +0 -0
  487. package/pennyfarthing-dist/pf/tests/__pycache__/test_tiers.cpython-314-pytest-9.0.2.pyc +0 -0
  488. package/pennyfarthing-dist/pf/tests/__pycache__/test_token_counting.cpython-314-pytest-9.0.2.pyc +0 -0
  489. package/pennyfarthing-dist/pf/tests/__pycache__/test_topology_loader.cpython-314-pytest-9.0.2.pyc +0 -0
  490. package/pennyfarthing-dist/pf/tests/__pycache__/test_tui_focus.cpython-314-pytest-9.0.2.pyc +0 -0
  491. package/pennyfarthing-dist/pf/tests/__pycache__/test_tui_panel_persistence.cpython-314-pytest-9.0.2.pyc +0 -0
  492. package/pennyfarthing-dist/pf/tests/__pycache__/test_validate_cmd.cpython-314-pytest-9.0.2.pyc +0 -0
  493. package/pennyfarthing-dist/pf/tests/__pycache__/test_version_sentinel.cpython-314-pytest-9.0.2.pyc +0 -0
  494. package/pennyfarthing-dist/pf/tests/__pycache__/test_workflow_check.cpython-314-pytest-9.0.2.pyc +0 -0
  495. package/pennyfarthing-dist/pf/tests/__pycache__/test_workflow_cli.cpython-314-pytest-9.0.2.pyc +0 -0
  496. package/pennyfarthing-dist/pf/tests/__pycache__/test_yaml_io.cpython-314-pytest-9.0.2.pyc +0 -0
  497. package/pennyfarthing-dist/pf/tests/conftest.py +105 -0
  498. package/pennyfarthing-dist/pf/tests/test_108_1_gate_migration.py +540 -0
  499. package/pennyfarthing-dist/pf/tests/test_108_2_remove_handoff_fallback.py +339 -0
  500. package/pennyfarthing-dist/pf/tests/test_archive_epic.py +185 -0
  501. package/pennyfarthing-dist/pf/tests/test_bc.py +490 -0
  502. package/pennyfarthing-dist/pf/tests/test_bikerack.py +785 -0
  503. package/pennyfarthing-dist/pf/tests/test_brownfield.py +839 -0
  504. package/pennyfarthing-dist/pf/tests/test_cli_modules.py +241 -0
  505. package/pennyfarthing-dist/pf/tests/test_cli_normalization.py +295 -0
  506. package/pennyfarthing-dist/pf/tests/test_codemarkers.py +687 -0
  507. package/pennyfarthing-dist/pf/tests/test_common.py +185 -0
  508. package/pennyfarthing-dist/pf/tests/test_confidence_sm_evaluation.py +253 -0
  509. package/pennyfarthing-dist/pf/tests/test_confidence_sm_gate.py +316 -0
  510. package/pennyfarthing-dist/pf/tests/test_dialogue_manager.py +810 -0
  511. package/pennyfarthing-dist/pf/tests/test_epic_shard_validation.py +699 -0
  512. package/pennyfarthing-dist/pf/tests/test_gate_file_resolution.py +341 -0
  513. package/pennyfarthing-dist/pf/tests/test_gate_runner.py +620 -0
  514. package/pennyfarthing-dist/pf/tests/test_git_utils.py +863 -0
  515. package/pennyfarthing-dist/pf/tests/test_handoff_cli.py +934 -0
  516. package/pennyfarthing-dist/pf/tests/test_handoff_e2e.py +454 -0
  517. package/pennyfarthing-dist/pf/tests/test_healthscore.py +516 -0
  518. package/pennyfarthing-dist/pf/tests/test_jira_package.py +331 -0
  519. package/pennyfarthing-dist/pf/tests/test_package_structure.py +359 -0
  520. package/pennyfarthing-dist/pf/tests/test_patch_mode.py +826 -0
  521. package/pennyfarthing-dist/pf/tests/test_prime.py +1068 -0
  522. package/pennyfarthing-dist/pf/tests/test_resolve_gate_file_field.py +460 -0
  523. package/pennyfarthing-dist/pf/tests/test_sprint_package.py +397 -0
  524. package/pennyfarthing-dist/pf/tests/test_sprint_panel.py +610 -0
  525. package/pennyfarthing-dist/pf/tests/test_sprint_validator.py +779 -0
  526. package/pennyfarthing-dist/pf/tests/test_story_add.py +917 -0
  527. package/pennyfarthing-dist/pf/tests/test_story_package.py +153 -0
  528. package/pennyfarthing-dist/pf/tests/test_story_update.py +764 -0
  529. package/pennyfarthing-dist/pf/tests/test_tiers.py +1091 -0
  530. package/pennyfarthing-dist/pf/tests/test_token_counting.py +565 -0
  531. package/pennyfarthing-dist/pf/tests/test_topology_loader.py +620 -0
  532. package/pennyfarthing-dist/pf/tests/test_tui_focus.py +411 -0
  533. package/pennyfarthing-dist/pf/tests/test_tui_panel_persistence.py +411 -0
  534. package/pennyfarthing-dist/pf/tests/test_validate_cmd.py +495 -0
  535. package/pennyfarthing-dist/pf/tests/test_version_sentinel.py +122 -0
  536. package/pennyfarthing-dist/pf/tests/test_workflow_check.py +338 -0
  537. package/pennyfarthing-dist/pf/tests/test_workflow_list_team.py +143 -0
  538. package/pennyfarthing-dist/pf/tests/test_yaml_io.py +812 -0
  539. package/pennyfarthing-dist/pf/theme/__main__.py +6 -0
  540. package/pennyfarthing-dist/pf/theme/__pycache__/__init__.cpython-311.pyc +0 -0
  541. package/pennyfarthing-dist/pf/theme/__pycache__/__init__.cpython-314.pyc +0 -0
  542. package/pennyfarthing-dist/pf/theme/__pycache__/cli.cpython-311.pyc +0 -0
  543. package/pennyfarthing-dist/pf/theme/__pycache__/cli.cpython-314.pyc +0 -0
  544. package/pennyfarthing-dist/pf/theme/cli.py +310 -0
  545. package/pennyfarthing-dist/pf/validate/__pycache__/__init__.cpython-311.pyc +0 -0
  546. package/pennyfarthing-dist/pf/validate/__pycache__/__init__.cpython-314.pyc +0 -0
  547. package/pennyfarthing-dist/pf/validate/__pycache__/cli.cpython-311.pyc +0 -0
  548. package/pennyfarthing-dist/pf/validate/__pycache__/cli.cpython-314.pyc +0 -0
  549. package/pennyfarthing-dist/pf/validate/adapters/__pycache__/__init__.cpython-314.pyc +0 -0
  550. package/pennyfarthing-dist/pf/validate/adapters/__pycache__/agent.cpython-314.pyc +0 -0
  551. package/pennyfarthing-dist/pf/validate/adapters/__pycache__/schema.cpython-314.pyc +0 -0
  552. package/pennyfarthing-dist/pf/validate/adapters/__pycache__/skill_command.cpython-314.pyc +0 -0
  553. package/pennyfarthing-dist/pf/validate/adapters/__pycache__/sprint.cpython-314.pyc +0 -0
  554. package/pennyfarthing-dist/pf/validate/adapters/__pycache__/tandem_awareness.cpython-314.pyc +0 -0
  555. package/pennyfarthing-dist/pf/validate/adapters/__pycache__/workflow.cpython-314.pyc +0 -0
  556. package/pennyfarthing-dist/pf/validate/adapters/agent.py +239 -0
  557. package/pennyfarthing-dist/pf/validate/adapters/schema.py +30 -0
  558. package/pennyfarthing-dist/pf/validate/adapters/skill_command.py +491 -0
  559. package/pennyfarthing-dist/pf/validate/adapters/sprint.py +69 -0
  560. package/pennyfarthing-dist/pf/validate/adapters/tandem_awareness.py +254 -0
  561. package/pennyfarthing-dist/pf/validate/adapters/team_mode.py +323 -0
  562. package/pennyfarthing-dist/pf/validate/adapters/workflow.py +403 -0
  563. package/pennyfarthing-dist/pf/validate/cli.py +164 -0
  564. package/pennyfarthing-dist/pf/welcome_hook.py +10 -0
  565. package/pennyfarthing-dist/pf/workflow/__init__.py +40 -0
  566. package/pennyfarthing-dist/pf/workflow/__pycache__/__init__.cpython-311.pyc +0 -0
  567. package/pennyfarthing-dist/pf/workflow/__pycache__/__init__.cpython-314.pyc +0 -0
  568. package/pennyfarthing-dist/pf/workflow/__pycache__/cli.cpython-311.pyc +0 -0
  569. package/pennyfarthing-dist/pf/workflow/__pycache__/cli.cpython-314.pyc +0 -0
  570. package/pennyfarthing-dist/pf/workflow/__pycache__/helpers.cpython-314.pyc +0 -0
  571. package/pennyfarthing-dist/pf/workflow/__pycache__/scale.cpython-311.pyc +0 -0
  572. package/pennyfarthing-dist/pf/workflow/__pycache__/scale.cpython-314.pyc +0 -0
  573. package/pennyfarthing-dist/pf/workflow/__pycache__/state.cpython-311.pyc +0 -0
  574. package/pennyfarthing-dist/pf/workflow/__pycache__/state.cpython-314.pyc +0 -0
  575. package/pennyfarthing-dist/pf/workflow/cli.py +1101 -0
  576. package/pennyfarthing-dist/pf/workflow/helpers.py +241 -0
  577. package/pennyfarthing-dist/pyproject.toml +18 -0
  578. package/pennyfarthing-dist/scripts/core/agent-session.sh +3 -3
  579. package/pennyfarthing-dist/scripts/core/check-context.sh +8 -8
  580. package/pennyfarthing-dist/scripts/hooks/__pycache__/question_reflector_check.cpython-314.pyc +0 -0
  581. package/pennyfarthing-dist/scripts/hooks/pre-commit.sh +3 -3
  582. package/pennyfarthing-dist/scripts/jira/README.md +1 -1
  583. package/pennyfarthing-dist/scripts/jira/create-jira-epic.sh +1 -1
  584. package/pennyfarthing-dist/scripts/jira/create-jira-story.sh +1 -1
  585. package/pennyfarthing-dist/scripts/jira/jira-claim-story.sh +1 -1
  586. package/pennyfarthing-dist/scripts/jira/jira-reconcile.sh +1 -1
  587. package/pennyfarthing-dist/scripts/jira/jira-sync-story.sh +1 -1
  588. package/pennyfarthing-dist/scripts/jira/sync-epic-jira.sh +1 -1
  589. package/pennyfarthing-dist/scripts/lib/common.sh +3 -3
  590. package/pennyfarthing-dist/scripts/lib/run-pf.sh +11 -13
  591. package/pennyfarthing-dist/scripts/sprint/README.md +1 -1
  592. package/pennyfarthing-dist/scripts/story/create-story.sh +1 -1
  593. package/pennyfarthing-dist/scripts/story/size-story.sh +1 -1
  594. package/pennyfarthing-dist/scripts/story/story-template.sh +1 -1
  595. package/pennyfarthing-dist/scripts/theme/list-themes.sh +3 -3
  596. package/pennyfarthing-dist/templates/pyproject.toml +3 -3
  597. package/pennyfarthing-dist/workflows/project-setup/steps/step-08-theme-packs.md +1 -1
  598. package/pennyfarthing-dist/workflows/project-setup/steps/step-09-jira.md +92 -0
  599. package/pennyfarthing-dist/workflows/project-setup/steps/step-10-cyclist.md +245 -0
  600. package/pennyfarthing-dist/workflows/project-setup/steps/step-11-complete.md +205 -0
  601. package/packages/core/dist/cli/utils/settings-pf-wrapper.test.d.ts +0 -16
  602. package/packages/core/dist/cli/utils/settings-pf-wrapper.test.d.ts.map +0 -1
  603. package/packages/core/dist/cli/utils/settings-pf-wrapper.test.js +0 -377
  604. package/packages/core/dist/cli/utils/settings-pf-wrapper.test.js.map +0 -1
  605. package/packages/core/dist/workflow/team-lifecycle.d.ts +0 -169
  606. package/packages/core/dist/workflow/team-lifecycle.d.ts.map +0 -1
  607. package/packages/core/dist/workflow/team-lifecycle.js +0 -217
  608. package/packages/core/dist/workflow/team-lifecycle.js.map +0 -1
  609. package/packages/core/dist/workflow/team-lifecycle.test.d.ts +0 -20
  610. package/packages/core/dist/workflow/team-lifecycle.test.d.ts.map +0 -1
  611. package/packages/core/dist/workflow/team-lifecycle.test.js +0 -966
  612. package/packages/core/dist/workflow/team-lifecycle.test.js.map +0 -1
  613. package/packages/core/dist/workflow/workflow-graph-validation.d.ts +0 -65
  614. package/packages/core/dist/workflow/workflow-graph-validation.d.ts.map +0 -1
  615. package/packages/core/dist/workflow/workflow-graph-validation.js +0 -190
  616. package/packages/core/dist/workflow/workflow-graph-validation.js.map +0 -1
  617. package/packages/core/dist/workflow/workflow-graph-validation.test.d.ts +0 -18
  618. package/packages/core/dist/workflow/workflow-graph-validation.test.d.ts.map +0 -1
  619. package/packages/core/dist/workflow/workflow-graph-validation.test.js +0 -706
  620. package/packages/core/dist/workflow/workflow-graph-validation.test.js.map +0 -1
  621. package/pennyfarthing-dist/workflows/project-setup/steps/step-09-cyclist.md +0 -245
  622. package/pennyfarthing-dist/workflows/project-setup/steps/step-10-complete.md +0 -204
  623. package/pennyfarthing_scripts/CLAUDE.md +0 -213
  624. package/pennyfarthing_scripts/README.md +0 -66
  625. package/pennyfarthing_scripts/__pycache__/__init__.cpython-314.pyc +0 -0
  626. package/pennyfarthing_scripts/__pycache__/bellmode_hook.cpython-314.pyc +0 -0
  627. package/pennyfarthing_scripts/__pycache__/cli.cpython-314.pyc +0 -0
  628. package/pennyfarthing_scripts/__pycache__/config.cpython-314.pyc +0 -0
  629. package/pennyfarthing_scripts/__pycache__/context.cpython-314.pyc +0 -0
  630. package/pennyfarthing_scripts/__pycache__/hooks.cpython-314.pyc +0 -0
  631. package/pennyfarthing_scripts/__pycache__/jira_bidirectional_sync.cpython-314.pyc +0 -0
  632. package/pennyfarthing_scripts/__pycache__/jira_epic_creation.cpython-314.pyc +0 -0
  633. package/pennyfarthing_scripts/__pycache__/jira_sync.cpython-314.pyc +0 -0
  634. package/pennyfarthing_scripts/__pycache__/jira_sync_story.cpython-314.pyc +0 -0
  635. package/pennyfarthing_scripts/__pycache__/output.cpython-314.pyc +0 -0
  636. package/pennyfarthing_scripts/__pycache__/patch_mode.cpython-314.pyc +0 -0
  637. package/pennyfarthing_scripts/__pycache__/pretooluse_hook.cpython-314.pyc +0 -0
  638. package/pennyfarthing_scripts/__pycache__/schema_validation_hook.cpython-314.pyc +0 -0
  639. package/pennyfarthing_scripts/__pycache__/session_start_hook.cpython-314.pyc +0 -0
  640. package/pennyfarthing_scripts/__pycache__/workflow.cpython-314.pyc +0 -0
  641. package/pennyfarthing_scripts/bc/__pycache__/__init__.cpython-314.pyc +0 -0
  642. package/pennyfarthing_scripts/bc/__pycache__/cli.cpython-314.pyc +0 -0
  643. package/pennyfarthing_scripts/bc/__pycache__/focus.cpython-314.pyc +0 -0
  644. package/pennyfarthing_scripts/bc/__pycache__/split.cpython-314.pyc +0 -0
  645. package/pennyfarthing_scripts/bc/cli.py +0 -242
  646. package/pennyfarthing_scripts/bc/focus.py +0 -334
  647. package/pennyfarthing_scripts/bc/split.py +0 -52
  648. package/pennyfarthing_scripts/bellmode_hook.py +0 -22
  649. package/pennyfarthing_scripts/bikerack/__init__.py +0 -44
  650. package/pennyfarthing_scripts/bikerack/__main__.py +0 -5
  651. package/pennyfarthing_scripts/bikerack/__pycache__/__init__.cpython-314.pyc +0 -0
  652. package/pennyfarthing_scripts/bikerack/__pycache__/__main__.cpython-314.pyc +0 -0
  653. package/pennyfarthing_scripts/bikerack/__pycache__/audit_log_panel.cpython-314.pyc +0 -0
  654. package/pennyfarthing_scripts/bikerack/__pycache__/background_panel.cpython-314.pyc +0 -0
  655. package/pennyfarthing_scripts/bikerack/__pycache__/base_panel.cpython-314.pyc +0 -0
  656. package/pennyfarthing_scripts/bikerack/__pycache__/changed_panel.cpython-314.pyc +0 -0
  657. package/pennyfarthing_scripts/bikerack/__pycache__/cli.cpython-314.pyc +0 -0
  658. package/pennyfarthing_scripts/bikerack/__pycache__/context_meter_footer.cpython-314.pyc +0 -0
  659. package/pennyfarthing_scripts/bikerack/__pycache__/debug_panel.cpython-314.pyc +0 -0
  660. package/pennyfarthing_scripts/bikerack/__pycache__/diffs_panel.cpython-314.pyc +0 -0
  661. package/pennyfarthing_scripts/bikerack/__pycache__/events.cpython-314.pyc +0 -0
  662. package/pennyfarthing_scripts/bikerack/__pycache__/git_panel.cpython-314.pyc +0 -0
  663. package/pennyfarthing_scripts/bikerack/__pycache__/launcher.cpython-314.pyc +0 -0
  664. package/pennyfarthing_scripts/bikerack/__pycache__/portrait.cpython-314.pyc +0 -0
  665. package/pennyfarthing_scripts/bikerack/__pycache__/portrait_resolver.cpython-314.pyc +0 -0
  666. package/pennyfarthing_scripts/bikerack/__pycache__/progress_panel.cpython-314.pyc +0 -0
  667. package/pennyfarthing_scripts/bikerack/__pycache__/sprint_panel.cpython-314.pyc +0 -0
  668. package/pennyfarthing_scripts/bikerack/__pycache__/story_detail_data.cpython-314.pyc +0 -0
  669. package/pennyfarthing_scripts/bikerack/__pycache__/story_detail_screen.cpython-314.pyc +0 -0
  670. package/pennyfarthing_scripts/bikerack/__pycache__/tui.cpython-314.pyc +0 -0
  671. package/pennyfarthing_scripts/bikerack/__pycache__/ws_client.cpython-314.pyc +0 -0
  672. package/pennyfarthing_scripts/bikerack/audit_log_panel.py +0 -161
  673. package/pennyfarthing_scripts/bikerack/background_panel.py +0 -162
  674. package/pennyfarthing_scripts/bikerack/changed_panel.py +0 -201
  675. package/pennyfarthing_scripts/bikerack/cli.py +0 -145
  676. package/pennyfarthing_scripts/bikerack/context_meter_footer.py +0 -138
  677. package/pennyfarthing_scripts/bikerack/debug_panel.py +0 -248
  678. package/pennyfarthing_scripts/bikerack/diffs_panel.py +0 -409
  679. package/pennyfarthing_scripts/bikerack/git_panel.py +0 -139
  680. package/pennyfarthing_scripts/bikerack/launcher.py +0 -240
  681. package/pennyfarthing_scripts/bikerack/portrait_resolver.py +0 -139
  682. package/pennyfarthing_scripts/bikerack/progress_panel.py +0 -314
  683. package/pennyfarthing_scripts/bikerack/sprint_panel.py +0 -435
  684. package/pennyfarthing_scripts/bikerack/story_detail_data.py +0 -247
  685. package/pennyfarthing_scripts/bikerack/story_detail_screen.py +0 -177
  686. package/pennyfarthing_scripts/bikerack/tui.py +0 -935
  687. package/pennyfarthing_scripts/bmad/__pycache__/__init__.cpython-314.pyc +0 -0
  688. package/pennyfarthing_scripts/bmad/__pycache__/cli.cpython-314.pyc +0 -0
  689. package/pennyfarthing_scripts/bmad/__pycache__/parser.cpython-314.pyc +0 -0
  690. package/pennyfarthing_scripts/bmad/__pycache__/sync.cpython-314.pyc +0 -0
  691. package/pennyfarthing_scripts/bmad/__pycache__/test_parser.cpython-314-pytest-9.0.2.pyc +0 -0
  692. package/pennyfarthing_scripts/bmad/__pycache__/test_sync.cpython-314-pytest-9.0.2.pyc +0 -0
  693. package/pennyfarthing_scripts/bmad/cli.py +0 -197
  694. package/pennyfarthing_scripts/bmad/importer.py +0 -200
  695. package/pennyfarthing_scripts/bmad/sync.py +0 -464
  696. package/pennyfarthing_scripts/bmad/test_parser.py +0 -253
  697. package/pennyfarthing_scripts/bmad/test_sync.py +0 -223
  698. package/pennyfarthing_scripts/brownfield/__init__.py +0 -35
  699. package/pennyfarthing_scripts/brownfield/__main__.py +0 -8
  700. package/pennyfarthing_scripts/brownfield/__pycache__/__init__.cpython-314.pyc +0 -0
  701. package/pennyfarthing_scripts/brownfield/__pycache__/__main__.cpython-314.pyc +0 -0
  702. package/pennyfarthing_scripts/brownfield/__pycache__/cli.cpython-314.pyc +0 -0
  703. package/pennyfarthing_scripts/brownfield/__pycache__/discover.cpython-314.pyc +0 -0
  704. package/pennyfarthing_scripts/brownfield/cli.py +0 -130
  705. package/pennyfarthing_scripts/cli.py +0 -354
  706. package/pennyfarthing_scripts/codemarkers/__init__.py +0 -23
  707. package/pennyfarthing_scripts/codemarkers/__main__.py +0 -6
  708. package/pennyfarthing_scripts/codemarkers/__pycache__/__init__.cpython-314.pyc +0 -0
  709. package/pennyfarthing_scripts/codemarkers/__pycache__/__main__.cpython-314.pyc +0 -0
  710. package/pennyfarthing_scripts/codemarkers/__pycache__/analyze.cpython-314.pyc +0 -0
  711. package/pennyfarthing_scripts/codemarkers/__pycache__/cli.cpython-314.pyc +0 -0
  712. package/pennyfarthing_scripts/codemarkers/__pycache__/formatters.cpython-314.pyc +0 -0
  713. package/pennyfarthing_scripts/codemarkers/__pycache__/models.cpython-314.pyc +0 -0
  714. package/pennyfarthing_scripts/codemarkers/analyze.py +0 -501
  715. package/pennyfarthing_scripts/codemarkers/cli.py +0 -179
  716. package/pennyfarthing_scripts/codemarkers/formatters.py +0 -88
  717. package/pennyfarthing_scripts/common/__init__.py +0 -48
  718. package/pennyfarthing_scripts/common/__pycache__/__init__.cpython-314.pyc +0 -0
  719. package/pennyfarthing_scripts/common/__pycache__/config.cpython-314.pyc +0 -0
  720. package/pennyfarthing_scripts/common/__pycache__/output.cpython-314.pyc +0 -0
  721. package/pennyfarthing_scripts/common/__pycache__/themes.cpython-314.pyc +0 -0
  722. package/pennyfarthing_scripts/common/pr_config.py +0 -38
  723. package/pennyfarthing_scripts/common/themes.py +0 -253
  724. package/pennyfarthing_scripts/complexity/__init__.py +0 -15
  725. package/pennyfarthing_scripts/complexity/__main__.py +0 -6
  726. package/pennyfarthing_scripts/complexity/__pycache__/__init__.cpython-314.pyc +0 -0
  727. package/pennyfarthing_scripts/complexity/__pycache__/__main__.cpython-314.pyc +0 -0
  728. package/pennyfarthing_scripts/complexity/__pycache__/analyze.cpython-314.pyc +0 -0
  729. package/pennyfarthing_scripts/complexity/__pycache__/cli.cpython-314.pyc +0 -0
  730. package/pennyfarthing_scripts/complexity/__pycache__/formatters.cpython-314.pyc +0 -0
  731. package/pennyfarthing_scripts/complexity/__pycache__/models.cpython-314.pyc +0 -0
  732. package/pennyfarthing_scripts/complexity/analyze.py +0 -207
  733. package/pennyfarthing_scripts/complexity/cli.py +0 -82
  734. package/pennyfarthing_scripts/complexity/formatters.py +0 -64
  735. package/pennyfarthing_scripts/config.py +0 -21
  736. package/pennyfarthing_scripts/consultation/__pycache__/__init__.cpython-314.pyc +0 -0
  737. package/pennyfarthing_scripts/consultation/__pycache__/cli.cpython-314.pyc +0 -0
  738. package/pennyfarthing_scripts/consultation/cli.py +0 -149
  739. package/pennyfarthing_scripts/deadcode/__main__.py +0 -6
  740. package/pennyfarthing_scripts/deadcode/__pycache__/__init__.cpython-314.pyc +0 -0
  741. package/pennyfarthing_scripts/deadcode/__pycache__/__main__.cpython-314.pyc +0 -0
  742. package/pennyfarthing_scripts/deadcode/__pycache__/analyze.cpython-314.pyc +0 -0
  743. package/pennyfarthing_scripts/deadcode/__pycache__/cli.cpython-314.pyc +0 -0
  744. package/pennyfarthing_scripts/deadcode/__pycache__/formatters.cpython-314.pyc +0 -0
  745. package/pennyfarthing_scripts/deadcode/__pycache__/models.cpython-314.pyc +0 -0
  746. package/pennyfarthing_scripts/deadcode/analyze.py +0 -322
  747. package/pennyfarthing_scripts/deadcode/cli.py +0 -163
  748. package/pennyfarthing_scripts/deadcode/formatters.py +0 -106
  749. package/pennyfarthing_scripts/dependencies/__init__.py +0 -20
  750. package/pennyfarthing_scripts/dependencies/__main__.py +0 -5
  751. package/pennyfarthing_scripts/dependencies/__pycache__/__init__.cpython-314.pyc +0 -0
  752. package/pennyfarthing_scripts/dependencies/__pycache__/__main__.cpython-314.pyc +0 -0
  753. package/pennyfarthing_scripts/dependencies/__pycache__/analyze.cpython-314.pyc +0 -0
  754. package/pennyfarthing_scripts/dependencies/__pycache__/cli.cpython-314.pyc +0 -0
  755. package/pennyfarthing_scripts/dependencies/__pycache__/formatters.cpython-314.pyc +0 -0
  756. package/pennyfarthing_scripts/dependencies/__pycache__/models.cpython-314.pyc +0 -0
  757. package/pennyfarthing_scripts/dependencies/analyze.py +0 -155
  758. package/pennyfarthing_scripts/dependencies/cli.py +0 -76
  759. package/pennyfarthing_scripts/dependencies/formatters.py +0 -63
  760. package/pennyfarthing_scripts/epic/__pycache__/__init__.cpython-314.pyc +0 -0
  761. package/pennyfarthing_scripts/epic/__pycache__/cli.cpython-314.pyc +0 -0
  762. package/pennyfarthing_scripts/gate/__pycache__/__init__.cpython-314.pyc +0 -0
  763. package/pennyfarthing_scripts/gate/__pycache__/cli.cpython-314.pyc +0 -0
  764. package/pennyfarthing_scripts/gate/__pycache__/validate.cpython-314.pyc +0 -0
  765. package/pennyfarthing_scripts/gate/cli.py +0 -56
  766. package/pennyfarthing_scripts/git/__init__.py +0 -40
  767. package/pennyfarthing_scripts/git/__pycache__/__init__.cpython-314.pyc +0 -0
  768. package/pennyfarthing_scripts/git/__pycache__/create_branches.cpython-314.pyc +0 -0
  769. package/pennyfarthing_scripts/git/__pycache__/hooks_installer.cpython-314.pyc +0 -0
  770. package/pennyfarthing_scripts/git/__pycache__/repos.cpython-314.pyc +0 -0
  771. package/pennyfarthing_scripts/git/__pycache__/status_all.cpython-314.pyc +0 -0
  772. package/pennyfarthing_scripts/git/__pycache__/worktree.cpython-314.pyc +0 -0
  773. package/pennyfarthing_scripts/git/create_branches.py +0 -439
  774. package/pennyfarthing_scripts/git/hooks_installer.py +0 -151
  775. package/pennyfarthing_scripts/git/repos.py +0 -196
  776. package/pennyfarthing_scripts/git/status_all.py +0 -326
  777. package/pennyfarthing_scripts/git/worktree.py +0 -302
  778. package/pennyfarthing_scripts/git_group/__pycache__/__init__.cpython-314.pyc +0 -0
  779. package/pennyfarthing_scripts/git_group/__pycache__/cli.cpython-314.pyc +0 -0
  780. package/pennyfarthing_scripts/git_group/cli.py +0 -203
  781. package/pennyfarthing_scripts/handoff/__pycache__/__init__.cpython-314.pyc +0 -0
  782. package/pennyfarthing_scripts/handoff/__pycache__/cli.cpython-314.pyc +0 -0
  783. package/pennyfarthing_scripts/handoff/__pycache__/complete_phase.cpython-314.pyc +0 -0
  784. package/pennyfarthing_scripts/handoff/__pycache__/gate_file.cpython-314.pyc +0 -0
  785. package/pennyfarthing_scripts/handoff/__pycache__/gate_runner.cpython-314.pyc +0 -0
  786. package/pennyfarthing_scripts/handoff/__pycache__/marker.cpython-314.pyc +0 -0
  787. package/pennyfarthing_scripts/handoff/__pycache__/phase_check.cpython-314.pyc +0 -0
  788. package/pennyfarthing_scripts/handoff/__pycache__/resolve_gate.cpython-314.pyc +0 -0
  789. package/pennyfarthing_scripts/handoff/cli.py +0 -152
  790. package/pennyfarthing_scripts/handoff/marker.py +0 -109
  791. package/pennyfarthing_scripts/handoff/phase_check.py +0 -96
  792. package/pennyfarthing_scripts/healthscore/__init__.py +0 -21
  793. package/pennyfarthing_scripts/healthscore/__main__.py +0 -14
  794. package/pennyfarthing_scripts/healthscore/__pycache__/__init__.cpython-314.pyc +0 -0
  795. package/pennyfarthing_scripts/healthscore/__pycache__/__main__.cpython-314.pyc +0 -0
  796. package/pennyfarthing_scripts/healthscore/__pycache__/analyze.cpython-314.pyc +0 -0
  797. package/pennyfarthing_scripts/healthscore/__pycache__/cli.cpython-314.pyc +0 -0
  798. package/pennyfarthing_scripts/healthscore/__pycache__/formatters.cpython-314.pyc +0 -0
  799. package/pennyfarthing_scripts/healthscore/__pycache__/models.cpython-314.pyc +0 -0
  800. package/pennyfarthing_scripts/healthscore/analyze.py +0 -606
  801. package/pennyfarthing_scripts/healthscore/cli.py +0 -80
  802. package/pennyfarthing_scripts/healthscore/formatters.py +0 -46
  803. package/pennyfarthing_scripts/hooks/__pycache__/__init__.cpython-314.pyc +0 -0
  804. package/pennyfarthing_scripts/hooks/__pycache__/bell_mode.cpython-314.pyc +0 -0
  805. package/pennyfarthing_scripts/hooks/__pycache__/cli.cpython-314.pyc +0 -0
  806. package/pennyfarthing_scripts/hooks/__pycache__/context_breaker.cpython-314.pyc +0 -0
  807. package/pennyfarthing_scripts/hooks/__pycache__/context_warning.cpython-314.pyc +0 -0
  808. package/pennyfarthing_scripts/hooks/__pycache__/cyclist_pretooluse.cpython-314.pyc +0 -0
  809. package/pennyfarthing_scripts/hooks/__pycache__/pre_edit_check.cpython-314.pyc +0 -0
  810. package/pennyfarthing_scripts/hooks/__pycache__/reflector_check.cpython-314.pyc +0 -0
  811. package/pennyfarthing_scripts/hooks/__pycache__/schema_validation.cpython-314.pyc +0 -0
  812. package/pennyfarthing_scripts/hooks/__pycache__/session_start.cpython-314.pyc +0 -0
  813. package/pennyfarthing_scripts/hooks/__pycache__/session_stop.cpython-314.pyc +0 -0
  814. package/pennyfarthing_scripts/hooks/__pycache__/sprint_yaml_validation.cpython-314.pyc +0 -0
  815. package/pennyfarthing_scripts/hooks/__pycache__/statusline.cpython-314.pyc +0 -0
  816. package/pennyfarthing_scripts/hooks/bell_mode.py +0 -214
  817. package/pennyfarthing_scripts/hooks/cli.py +0 -96
  818. package/pennyfarthing_scripts/hooks/context_breaker.py +0 -104
  819. package/pennyfarthing_scripts/hooks/context_warning.py +0 -66
  820. package/pennyfarthing_scripts/hooks/cyclist_pretooluse.py +0 -129
  821. package/pennyfarthing_scripts/hooks/schema_validation.py +0 -202
  822. package/pennyfarthing_scripts/hooks/session_start.py +0 -294
  823. package/pennyfarthing_scripts/hooks/sprint_yaml_validation.py +0 -97
  824. package/pennyfarthing_scripts/hooks/statusline.py +0 -429
  825. package/pennyfarthing_scripts/hooks.py +0 -32
  826. package/pennyfarthing_scripts/hotspots/__init__.py +0 -31
  827. package/pennyfarthing_scripts/hotspots/__main__.py +0 -6
  828. package/pennyfarthing_scripts/hotspots/__pycache__/__init__.cpython-314.pyc +0 -0
  829. package/pennyfarthing_scripts/hotspots/__pycache__/__main__.cpython-314.pyc +0 -0
  830. package/pennyfarthing_scripts/hotspots/__pycache__/analyze.cpython-314.pyc +0 -0
  831. package/pennyfarthing_scripts/hotspots/__pycache__/cli.cpython-314.pyc +0 -0
  832. package/pennyfarthing_scripts/hotspots/__pycache__/formatters.cpython-314.pyc +0 -0
  833. package/pennyfarthing_scripts/hotspots/__pycache__/models.cpython-314.pyc +0 -0
  834. package/pennyfarthing_scripts/hotspots/analyze.py +0 -613
  835. package/pennyfarthing_scripts/hotspots/cli.py +0 -154
  836. package/pennyfarthing_scripts/hotspots/formatters.py +0 -109
  837. package/pennyfarthing_scripts/jira/__init__.py +0 -97
  838. package/pennyfarthing_scripts/jira/__main__.py +0 -10
  839. package/pennyfarthing_scripts/jira/__pycache__/__init__.cpython-314.pyc +0 -0
  840. package/pennyfarthing_scripts/jira/__pycache__/__main__.cpython-314.pyc +0 -0
  841. package/pennyfarthing_scripts/jira/__pycache__/bidirectional.cpython-314.pyc +0 -0
  842. package/pennyfarthing_scripts/jira/__pycache__/claim.cpython-314.pyc +0 -0
  843. package/pennyfarthing_scripts/jira/__pycache__/cli.cpython-314.pyc +0 -0
  844. package/pennyfarthing_scripts/jira/__pycache__/client.cpython-314.pyc +0 -0
  845. package/pennyfarthing_scripts/jira/__pycache__/create.cpython-314.pyc +0 -0
  846. package/pennyfarthing_scripts/jira/__pycache__/epic.cpython-314.pyc +0 -0
  847. package/pennyfarthing_scripts/jira/__pycache__/operations.cpython-314.pyc +0 -0
  848. package/pennyfarthing_scripts/jira/__pycache__/reconcile.cpython-314.pyc +0 -0
  849. package/pennyfarthing_scripts/jira/__pycache__/story.cpython-314.pyc +0 -0
  850. package/pennyfarthing_scripts/jira/__pycache__/sync.cpython-314.pyc +0 -0
  851. package/pennyfarthing_scripts/jira/bidirectional.py +0 -587
  852. package/pennyfarthing_scripts/jira/claim.py +0 -232
  853. package/pennyfarthing_scripts/jira/cli.py +0 -362
  854. package/pennyfarthing_scripts/jira/client.py +0 -790
  855. package/pennyfarthing_scripts/jira/create.py +0 -311
  856. package/pennyfarthing_scripts/jira/epic.py +0 -177
  857. package/pennyfarthing_scripts/jira/operations.py +0 -124
  858. package/pennyfarthing_scripts/jira/reconcile.py +0 -276
  859. package/pennyfarthing_scripts/jira/story.py +0 -221
  860. package/pennyfarthing_scripts/jira/sync.py +0 -350
  861. package/pennyfarthing_scripts/jira_bidirectional_sync.py +0 -37
  862. package/pennyfarthing_scripts/jira_epic_creation.py +0 -30
  863. package/pennyfarthing_scripts/jira_sync.py +0 -36
  864. package/pennyfarthing_scripts/jira_sync_story.py +0 -30
  865. package/pennyfarthing_scripts/launch/__pycache__/__init__.cpython-314.pyc +0 -0
  866. package/pennyfarthing_scripts/launch/__pycache__/cli.cpython-314.pyc +0 -0
  867. package/pennyfarthing_scripts/launch/cli.py +0 -231
  868. package/pennyfarthing_scripts/migration/__init__.py +0 -39
  869. package/pennyfarthing_scripts/migration/__main__.py +0 -10
  870. package/pennyfarthing_scripts/migration/__pycache__/__init__.cpython-314.pyc +0 -0
  871. package/pennyfarthing_scripts/migration/__pycache__/session.cpython-314.pyc +0 -0
  872. package/pennyfarthing_scripts/migration/__pycache__/skill.cpython-314.pyc +0 -0
  873. package/pennyfarthing_scripts/migration/__pycache__/step.cpython-314.pyc +0 -0
  874. package/pennyfarthing_scripts/migration/__pycache__/validate.cpython-314.pyc +0 -0
  875. package/pennyfarthing_scripts/migration/cli.py +0 -304
  876. package/pennyfarthing_scripts/migration/validate.py +0 -285
  877. package/pennyfarthing_scripts/output.py +0 -37
  878. package/pennyfarthing_scripts/patch_mode.py +0 -449
  879. package/pennyfarthing_scripts/preflight/__init__.py +0 -17
  880. package/pennyfarthing_scripts/preflight/__main__.py +0 -10
  881. package/pennyfarthing_scripts/preflight/__pycache__/__init__.cpython-314.pyc +0 -0
  882. package/pennyfarthing_scripts/preflight/__pycache__/__main__.cpython-314.pyc +0 -0
  883. package/pennyfarthing_scripts/preflight/__pycache__/cli.cpython-314.pyc +0 -0
  884. package/pennyfarthing_scripts/preflight/__pycache__/finish.cpython-314.pyc +0 -0
  885. package/pennyfarthing_scripts/preflight/cli.py +0 -141
  886. package/pennyfarthing_scripts/pretooluse_hook.py +0 -10
  887. package/pennyfarthing_scripts/prime/__init__.py +0 -127
  888. package/pennyfarthing_scripts/prime/__main__.py +0 -8
  889. package/pennyfarthing_scripts/prime/__pycache__/__init__.cpython-314.pyc +0 -0
  890. package/pennyfarthing_scripts/prime/__pycache__/cli.cpython-314.pyc +0 -0
  891. package/pennyfarthing_scripts/prime/__pycache__/heatmap.cpython-314.pyc +0 -0
  892. package/pennyfarthing_scripts/prime/__pycache__/loader.cpython-314.pyc +0 -0
  893. package/pennyfarthing_scripts/prime/__pycache__/models.cpython-314.pyc +0 -0
  894. package/pennyfarthing_scripts/prime/__pycache__/persona.cpython-314.pyc +0 -0
  895. package/pennyfarthing_scripts/prime/__pycache__/session.cpython-314.pyc +0 -0
  896. package/pennyfarthing_scripts/prime/__pycache__/tiers.cpython-314.pyc +0 -0
  897. package/pennyfarthing_scripts/prime/__pycache__/version_sentinel.cpython-314.pyc +0 -0
  898. package/pennyfarthing_scripts/prime/__pycache__/workflow.cpython-314.pyc +0 -0
  899. package/pennyfarthing_scripts/prime/cli.py +0 -662
  900. package/pennyfarthing_scripts/prime/heatmap.py +0 -643
  901. package/pennyfarthing_scripts/prime/loader.py +0 -308
  902. package/pennyfarthing_scripts/prime/persona.py +0 -311
  903. package/pennyfarthing_scripts/prime/session.py +0 -183
  904. package/pennyfarthing_scripts/prime/tiers.py +0 -214
  905. package/pennyfarthing_scripts/prime/workflow.py +0 -317
  906. package/pennyfarthing_scripts/schema_validation_hook.py +0 -10
  907. package/pennyfarthing_scripts/session/__pycache__/__init__.cpython-314.pyc +0 -0
  908. package/pennyfarthing_scripts/session/__pycache__/cli.cpython-314.pyc +0 -0
  909. package/pennyfarthing_scripts/session/cli.py +0 -87
  910. package/pennyfarthing_scripts/session_start_hook.py +0 -10
  911. package/pennyfarthing_scripts/settings/__pycache__/__init__.cpython-314.pyc +0 -0
  912. package/pennyfarthing_scripts/settings/__pycache__/cli.cpython-314.pyc +0 -0
  913. package/pennyfarthing_scripts/settings/__pycache__/settings.cpython-314.pyc +0 -0
  914. package/pennyfarthing_scripts/settings/cli.py +0 -55
  915. package/pennyfarthing_scripts/settings/settings.py +0 -98
  916. package/pennyfarthing_scripts/sprint/__init__.py +0 -64
  917. package/pennyfarthing_scripts/sprint/__main__.py +0 -10
  918. package/pennyfarthing_scripts/sprint/__pycache__/__init__.cpython-314.pyc +0 -0
  919. package/pennyfarthing_scripts/sprint/__pycache__/__main__.cpython-314.pyc +0 -0
  920. package/pennyfarthing_scripts/sprint/__pycache__/archive.cpython-314.pyc +0 -0
  921. package/pennyfarthing_scripts/sprint/__pycache__/archive_epic.cpython-314.pyc +0 -0
  922. package/pennyfarthing_scripts/sprint/__pycache__/cli.cpython-314.pyc +0 -0
  923. package/pennyfarthing_scripts/sprint/__pycache__/epic_add.cpython-314.pyc +0 -0
  924. package/pennyfarthing_scripts/sprint/__pycache__/epic_update.cpython-314.pyc +0 -0
  925. package/pennyfarthing_scripts/sprint/__pycache__/import_epic.cpython-314.pyc +0 -0
  926. package/pennyfarthing_scripts/sprint/__pycache__/loader.cpython-314.pyc +0 -0
  927. package/pennyfarthing_scripts/sprint/__pycache__/status.cpython-314.pyc +0 -0
  928. package/pennyfarthing_scripts/sprint/__pycache__/story_add.cpython-314.pyc +0 -0
  929. package/pennyfarthing_scripts/sprint/__pycache__/story_finish.cpython-314.pyc +0 -0
  930. package/pennyfarthing_scripts/sprint/__pycache__/story_update.cpython-314.pyc +0 -0
  931. package/pennyfarthing_scripts/sprint/__pycache__/validate_cmd.cpython-314.pyc +0 -0
  932. package/pennyfarthing_scripts/sprint/__pycache__/validator.cpython-314.pyc +0 -0
  933. package/pennyfarthing_scripts/sprint/__pycache__/work.cpython-314.pyc +0 -0
  934. package/pennyfarthing_scripts/sprint/__pycache__/yaml_io.cpython-314.pyc +0 -0
  935. package/pennyfarthing_scripts/sprint/archive.py +0 -164
  936. package/pennyfarthing_scripts/sprint/archive_epic.py +0 -625
  937. package/pennyfarthing_scripts/sprint/cli.py +0 -2021
  938. package/pennyfarthing_scripts/sprint/epic_add.py +0 -186
  939. package/pennyfarthing_scripts/sprint/epic_update.py +0 -148
  940. package/pennyfarthing_scripts/sprint/import_epic.py +0 -455
  941. package/pennyfarthing_scripts/sprint/loader.py +0 -441
  942. package/pennyfarthing_scripts/sprint/status.py +0 -121
  943. package/pennyfarthing_scripts/sprint/story_add.py +0 -372
  944. package/pennyfarthing_scripts/sprint/story_finish.py +0 -227
  945. package/pennyfarthing_scripts/sprint/story_update.py +0 -222
  946. package/pennyfarthing_scripts/sprint/validate_cmd.py +0 -307
  947. package/pennyfarthing_scripts/sprint/validator.py +0 -694
  948. package/pennyfarthing_scripts/sprint/work.py +0 -229
  949. package/pennyfarthing_scripts/story/__init__.py +0 -65
  950. package/pennyfarthing_scripts/story/__main__.py +0 -10
  951. package/pennyfarthing_scripts/story/__pycache__/__init__.cpython-314.pyc +0 -0
  952. package/pennyfarthing_scripts/story/__pycache__/__main__.cpython-314.pyc +0 -0
  953. package/pennyfarthing_scripts/story/__pycache__/cli.cpython-314.pyc +0 -0
  954. package/pennyfarthing_scripts/story/__pycache__/create.cpython-314.pyc +0 -0
  955. package/pennyfarthing_scripts/story/__pycache__/size.cpython-314.pyc +0 -0
  956. package/pennyfarthing_scripts/story/__pycache__/template.cpython-314.pyc +0 -0
  957. package/pennyfarthing_scripts/story/cli.py +0 -105
  958. package/pennyfarthing_scripts/story/create.py +0 -167
  959. package/pennyfarthing_scripts/tests/__init__.py +0 -1
  960. package/pennyfarthing_scripts/tests/__pycache__/__init__.cpython-314.pyc +0 -0
  961. package/pennyfarthing_scripts/tests/__pycache__/conftest.cpython-314-pytest-9.0.2.pyc +0 -0
  962. package/pennyfarthing_scripts/tests/__pycache__/test_108_2_remove_handoff_fallback.cpython-314-pytest-9.0.2.pyc +0 -0
  963. package/pennyfarthing_scripts/tests/__pycache__/test_archive_epic.cpython-314-pytest-9.0.2.pyc +0 -0
  964. package/pennyfarthing_scripts/tests/__pycache__/test_bc.cpython-314-pytest-9.0.2.pyc +0 -0
  965. package/pennyfarthing_scripts/tests/__pycache__/test_bikerack.cpython-314-pytest-9.0.2.pyc +0 -0
  966. package/pennyfarthing_scripts/tests/__pycache__/test_brownfield.cpython-314-pytest-9.0.2.pyc +0 -0
  967. package/pennyfarthing_scripts/tests/__pycache__/test_cli_modules.cpython-314-pytest-9.0.2.pyc +0 -0
  968. package/pennyfarthing_scripts/tests/__pycache__/test_cli_normalization.cpython-314-pytest-9.0.2.pyc +0 -0
  969. package/pennyfarthing_scripts/tests/__pycache__/test_codemarkers.cpython-314-pytest-9.0.2.pyc +0 -0
  970. package/pennyfarthing_scripts/tests/__pycache__/test_common.cpython-314-pytest-9.0.2.pyc +0 -0
  971. package/pennyfarthing_scripts/tests/__pycache__/test_confidence_sm_evaluation.cpython-314-pytest-9.0.2.pyc +0 -0
  972. package/pennyfarthing_scripts/tests/__pycache__/test_confidence_sm_gate.cpython-314-pytest-9.0.2.pyc +0 -0
  973. package/pennyfarthing_scripts/tests/__pycache__/test_epic_shard_validation.cpython-314-pytest-9.0.2.pyc +0 -0
  974. package/pennyfarthing_scripts/tests/__pycache__/test_gate_file_resolution.cpython-314-pytest-9.0.2.pyc +0 -0
  975. package/pennyfarthing_scripts/tests/__pycache__/test_gate_runner.cpython-314-pytest-9.0.2.pyc +0 -0
  976. package/pennyfarthing_scripts/tests/__pycache__/test_git_utils.cpython-314-pytest-9.0.2.pyc +0 -0
  977. package/pennyfarthing_scripts/tests/__pycache__/test_handoff_cli.cpython-314-pytest-9.0.2.pyc +0 -0
  978. package/pennyfarthing_scripts/tests/__pycache__/test_handoff_e2e.cpython-314-pytest-9.0.2.pyc +0 -0
  979. package/pennyfarthing_scripts/tests/__pycache__/test_healthscore.cpython-314-pytest-9.0.2.pyc +0 -0
  980. package/pennyfarthing_scripts/tests/__pycache__/test_jira_package.cpython-314-pytest-9.0.2.pyc +0 -0
  981. package/pennyfarthing_scripts/tests/__pycache__/test_package_structure.cpython-314-pytest-9.0.2.pyc +0 -0
  982. package/pennyfarthing_scripts/tests/__pycache__/test_patch_mode.cpython-314-pytest-9.0.2.pyc +0 -0
  983. package/pennyfarthing_scripts/tests/__pycache__/test_prime.cpython-314-pytest-9.0.2.pyc +0 -0
  984. package/pennyfarthing_scripts/tests/__pycache__/test_resolve_gate_file_field.cpython-314-pytest-9.0.2.pyc +0 -0
  985. package/pennyfarthing_scripts/tests/__pycache__/test_sprint_package.cpython-314-pytest-9.0.2.pyc +0 -0
  986. package/pennyfarthing_scripts/tests/__pycache__/test_sprint_panel.cpython-314-pytest-9.0.2.pyc +0 -0
  987. package/pennyfarthing_scripts/tests/__pycache__/test_sprint_validator.cpython-314-pytest-9.0.2.pyc +0 -0
  988. package/pennyfarthing_scripts/tests/__pycache__/test_story_add.cpython-314-pytest-9.0.2.pyc +0 -0
  989. package/pennyfarthing_scripts/tests/__pycache__/test_story_package.cpython-314-pytest-9.0.2.pyc +0 -0
  990. package/pennyfarthing_scripts/tests/__pycache__/test_story_update.cpython-314-pytest-9.0.2.pyc +0 -0
  991. package/pennyfarthing_scripts/tests/__pycache__/test_tiers.cpython-314-pytest-9.0.2.pyc +0 -0
  992. package/pennyfarthing_scripts/tests/__pycache__/test_token_counting.cpython-314-pytest-9.0.2.pyc +0 -0
  993. package/pennyfarthing_scripts/tests/__pycache__/test_topology_loader.cpython-314-pytest-9.0.2.pyc +0 -0
  994. package/pennyfarthing_scripts/tests/__pycache__/test_tui_focus.cpython-314-pytest-9.0.2.pyc +0 -0
  995. package/pennyfarthing_scripts/tests/__pycache__/test_tui_panel_persistence.cpython-314-pytest-9.0.2.pyc +0 -0
  996. package/pennyfarthing_scripts/tests/__pycache__/test_validate_cmd.cpython-314-pytest-9.0.2.pyc +0 -0
  997. package/pennyfarthing_scripts/tests/__pycache__/test_version_sentinel.cpython-314-pytest-9.0.2.pyc +0 -0
  998. package/pennyfarthing_scripts/tests/__pycache__/test_workflow_check.cpython-314-pytest-9.0.2.pyc +0 -0
  999. package/pennyfarthing_scripts/tests/__pycache__/test_workflow_list_team.cpython-314-pytest-9.0.2.pyc +0 -0
  1000. package/pennyfarthing_scripts/tests/__pycache__/test_workflow_list_team.cpython-314.pyc +0 -0
  1001. package/pennyfarthing_scripts/tests/__pycache__/test_yaml_io.cpython-314-pytest-9.0.2.pyc +0 -0
  1002. package/pennyfarthing_scripts/tests/conftest.py +0 -105
  1003. package/pennyfarthing_scripts/tests/test_108_1_gate_migration.py +0 -540
  1004. package/pennyfarthing_scripts/tests/test_108_2_remove_handoff_fallback.py +0 -339
  1005. package/pennyfarthing_scripts/tests/test_archive_epic.py +0 -185
  1006. package/pennyfarthing_scripts/tests/test_bc.py +0 -490
  1007. package/pennyfarthing_scripts/tests/test_bikerack.py +0 -785
  1008. package/pennyfarthing_scripts/tests/test_brownfield.py +0 -839
  1009. package/pennyfarthing_scripts/tests/test_cli_modules.py +0 -241
  1010. package/pennyfarthing_scripts/tests/test_cli_normalization.py +0 -295
  1011. package/pennyfarthing_scripts/tests/test_codemarkers.py +0 -687
  1012. package/pennyfarthing_scripts/tests/test_common.py +0 -185
  1013. package/pennyfarthing_scripts/tests/test_confidence_sm_evaluation.py +0 -253
  1014. package/pennyfarthing_scripts/tests/test_confidence_sm_gate.py +0 -316
  1015. package/pennyfarthing_scripts/tests/test_dialogue_manager.py +0 -810
  1016. package/pennyfarthing_scripts/tests/test_epic_shard_validation.py +0 -699
  1017. package/pennyfarthing_scripts/tests/test_gate_file_resolution.py +0 -341
  1018. package/pennyfarthing_scripts/tests/test_gate_runner.py +0 -620
  1019. package/pennyfarthing_scripts/tests/test_git_utils.py +0 -863
  1020. package/pennyfarthing_scripts/tests/test_handoff_cli.py +0 -934
  1021. package/pennyfarthing_scripts/tests/test_handoff_e2e.py +0 -454
  1022. package/pennyfarthing_scripts/tests/test_healthscore.py +0 -516
  1023. package/pennyfarthing_scripts/tests/test_jira_package.py +0 -331
  1024. package/pennyfarthing_scripts/tests/test_package_structure.py +0 -359
  1025. package/pennyfarthing_scripts/tests/test_patch_mode.py +0 -826
  1026. package/pennyfarthing_scripts/tests/test_prime.py +0 -1068
  1027. package/pennyfarthing_scripts/tests/test_resolve_gate_file_field.py +0 -462
  1028. package/pennyfarthing_scripts/tests/test_sprint_package.py +0 -397
  1029. package/pennyfarthing_scripts/tests/test_sprint_panel.py +0 -610
  1030. package/pennyfarthing_scripts/tests/test_sprint_validator.py +0 -779
  1031. package/pennyfarthing_scripts/tests/test_story_add.py +0 -917
  1032. package/pennyfarthing_scripts/tests/test_story_package.py +0 -153
  1033. package/pennyfarthing_scripts/tests/test_story_update.py +0 -764
  1034. package/pennyfarthing_scripts/tests/test_tiers.py +0 -1091
  1035. package/pennyfarthing_scripts/tests/test_token_counting.py +0 -565
  1036. package/pennyfarthing_scripts/tests/test_topology_loader.py +0 -620
  1037. package/pennyfarthing_scripts/tests/test_tui_focus.py +0 -411
  1038. package/pennyfarthing_scripts/tests/test_tui_panel_persistence.py +0 -411
  1039. package/pennyfarthing_scripts/tests/test_validate_cmd.py +0 -495
  1040. package/pennyfarthing_scripts/tests/test_version_sentinel.py +0 -122
  1041. package/pennyfarthing_scripts/tests/test_workflow_check.py +0 -338
  1042. package/pennyfarthing_scripts/tests/test_workflow_list_team.py +0 -143
  1043. package/pennyfarthing_scripts/tests/test_yaml_io.py +0 -812
  1044. package/pennyfarthing_scripts/theme/__main__.py +0 -6
  1045. package/pennyfarthing_scripts/theme/__pycache__/__init__.cpython-314.pyc +0 -0
  1046. package/pennyfarthing_scripts/theme/__pycache__/cli.cpython-314.pyc +0 -0
  1047. package/pennyfarthing_scripts/theme/cli.py +0 -310
  1048. package/pennyfarthing_scripts/validate/__pycache__/__init__.cpython-314.pyc +0 -0
  1049. package/pennyfarthing_scripts/validate/__pycache__/cli.cpython-314.pyc +0 -0
  1050. package/pennyfarthing_scripts/validate/adapters/__pycache__/__init__.cpython-314.pyc +0 -0
  1051. package/pennyfarthing_scripts/validate/adapters/__pycache__/agent.cpython-314.pyc +0 -0
  1052. package/pennyfarthing_scripts/validate/adapters/__pycache__/schema.cpython-314.pyc +0 -0
  1053. package/pennyfarthing_scripts/validate/adapters/__pycache__/skill_command.cpython-314.pyc +0 -0
  1054. package/pennyfarthing_scripts/validate/adapters/__pycache__/sprint.cpython-314.pyc +0 -0
  1055. package/pennyfarthing_scripts/validate/adapters/__pycache__/tandem_awareness.cpython-314.pyc +0 -0
  1056. package/pennyfarthing_scripts/validate/adapters/__pycache__/team_mode.cpython-314.pyc +0 -0
  1057. package/pennyfarthing_scripts/validate/adapters/__pycache__/workflow.cpython-314.pyc +0 -0
  1058. package/pennyfarthing_scripts/validate/adapters/agent.py +0 -239
  1059. package/pennyfarthing_scripts/validate/adapters/schema.py +0 -30
  1060. package/pennyfarthing_scripts/validate/adapters/skill_command.py +0 -491
  1061. package/pennyfarthing_scripts/validate/adapters/sprint.py +0 -69
  1062. package/pennyfarthing_scripts/validate/adapters/tandem_awareness.py +0 -254
  1063. package/pennyfarthing_scripts/validate/adapters/team_mode.py +0 -323
  1064. package/pennyfarthing_scripts/validate/adapters/workflow.py +0 -403
  1065. package/pennyfarthing_scripts/validate/cli.py +0 -164
  1066. package/pennyfarthing_scripts/welcome_hook.py +0 -10
  1067. package/pennyfarthing_scripts/workflow/__init__.py +0 -40
  1068. package/pennyfarthing_scripts/workflow/__pycache__/__init__.cpython-314.pyc +0 -0
  1069. package/pennyfarthing_scripts/workflow/__pycache__/cli.cpython-314.pyc +0 -0
  1070. package/pennyfarthing_scripts/workflow/__pycache__/helpers.cpython-314.pyc +0 -0
  1071. package/pennyfarthing_scripts/workflow/__pycache__/scale.cpython-314.pyc +0 -0
  1072. package/pennyfarthing_scripts/workflow/__pycache__/state.cpython-314.pyc +0 -0
  1073. package/pennyfarthing_scripts/workflow/__pycache__/team_lifecycle.cpython-314.pyc +0 -0
  1074. package/pennyfarthing_scripts/workflow/cli.py +0 -1101
  1075. package/pennyfarthing_scripts/workflow/helpers.py +0 -241
  1076. /package/{pennyfarthing_scripts → pennyfarthing-dist/pf}/__init__.py +0 -0
  1077. /package/{pennyfarthing_scripts → pennyfarthing-dist/pf}/bc/__init__.py +0 -0
  1078. /package/{pennyfarthing_scripts → pennyfarthing-dist/pf}/bikerack/base_panel.py +0 -0
  1079. /package/{pennyfarthing_scripts → pennyfarthing-dist/pf}/bikerack/events.py +0 -0
  1080. /package/{pennyfarthing_scripts → pennyfarthing-dist/pf}/bikerack/ws_client.py +0 -0
  1081. /package/{pennyfarthing_scripts → pennyfarthing-dist/pf}/bmad/__init__.py +0 -0
  1082. /package/{pennyfarthing_scripts → pennyfarthing-dist/pf}/bmad/parser.py +0 -0
  1083. /package/{pennyfarthing_scripts → pennyfarthing-dist/pf}/brownfield/discover.py +0 -0
  1084. /package/{pennyfarthing_scripts → pennyfarthing-dist/pf}/codemarkers/models.py +0 -0
  1085. /package/{pennyfarthing_scripts → pennyfarthing-dist/pf}/common/config.py +0 -0
  1086. /package/{pennyfarthing_scripts → pennyfarthing-dist/pf}/common/output.py +0 -0
  1087. /package/{pennyfarthing_scripts → pennyfarthing-dist/pf}/complexity/models.py +0 -0
  1088. /package/{pennyfarthing_scripts → pennyfarthing-dist/pf}/consultation/__init__.py +0 -0
  1089. /package/{pennyfarthing_scripts → pennyfarthing-dist/pf}/consultation/dialogue_manager.py +0 -0
  1090. /package/{pennyfarthing_scripts → pennyfarthing-dist/pf}/context.py +0 -0
  1091. /package/{pennyfarthing_scripts → pennyfarthing-dist/pf}/deadcode/__init__.py +0 -0
  1092. /package/{pennyfarthing_scripts → pennyfarthing-dist/pf}/deadcode/models.py +0 -0
  1093. /package/{pennyfarthing_scripts → pennyfarthing-dist/pf}/dependencies/models.py +0 -0
  1094. /package/{pennyfarthing_scripts → pennyfarthing-dist/pf}/epic/__init__.py +0 -0
  1095. /package/{pennyfarthing_scripts → pennyfarthing-dist/pf}/epic/cli.py +0 -0
  1096. /package/{pennyfarthing_scripts → pennyfarthing-dist/pf}/gate/__init__.py +0 -0
  1097. /package/{pennyfarthing_scripts → pennyfarthing-dist/pf}/gate/validate.py +0 -0
  1098. /package/{pennyfarthing_scripts → pennyfarthing-dist/pf}/git_group/__init__.py +0 -0
  1099. /package/{pennyfarthing_scripts → pennyfarthing-dist/pf}/handoff/__init__.py +0 -0
  1100. /package/{pennyfarthing_scripts → pennyfarthing-dist/pf}/handoff/complete_phase.py +0 -0
  1101. /package/{pennyfarthing_scripts → pennyfarthing-dist/pf}/handoff/gate_file.py +0 -0
  1102. /package/{pennyfarthing_scripts → pennyfarthing-dist/pf}/handoff/gate_runner.py +0 -0
  1103. /package/{pennyfarthing_scripts → pennyfarthing-dist/pf}/handoff/resolve_gate.py +0 -0
  1104. /package/{pennyfarthing_scripts → pennyfarthing-dist/pf}/healthscore/models.py +0 -0
  1105. /package/{pennyfarthing_scripts → pennyfarthing-dist/pf}/hooks/__init__.py +0 -0
  1106. /package/{pennyfarthing_scripts → pennyfarthing-dist/pf}/hooks/cyclist-pretooluse-hook.sh +0 -0
  1107. /package/{pennyfarthing_scripts → pennyfarthing-dist/pf}/hooks/pre_edit_check.py +0 -0
  1108. /package/{pennyfarthing_scripts → pennyfarthing-dist/pf}/hooks/reflector_check.py +0 -0
  1109. /package/{pennyfarthing_scripts → pennyfarthing-dist/pf}/hooks/session_stop.py +0 -0
  1110. /package/{pennyfarthing_scripts → pennyfarthing-dist/pf}/hotspots/models.py +0 -0
  1111. /package/{pennyfarthing_scripts → pennyfarthing-dist/pf}/launch/__init__.py +0 -0
  1112. /package/{pennyfarthing_scripts → pennyfarthing-dist/pf}/migration/session.py +0 -0
  1113. /package/{pennyfarthing_scripts → pennyfarthing-dist/pf}/migration/skill.py +0 -0
  1114. /package/{pennyfarthing_scripts → pennyfarthing-dist/pf}/migration/step.py +0 -0
  1115. /package/{pennyfarthing_scripts → pennyfarthing-dist/pf}/preflight/finish.py +0 -0
  1116. /package/{pennyfarthing_scripts → pennyfarthing-dist/pf}/prime/models.py +0 -0
  1117. /package/{pennyfarthing_scripts → pennyfarthing-dist/pf}/prime/version_sentinel.py +0 -0
  1118. /package/{pennyfarthing_scripts → pennyfarthing-dist/pf}/session/__init__.py +0 -0
  1119. /package/{pennyfarthing_scripts → pennyfarthing-dist/pf}/settings/__init__.py +0 -0
  1120. /package/{pennyfarthing_scripts → pennyfarthing-dist/pf}/sprint/yaml_io.py +0 -0
  1121. /package/{pennyfarthing_scripts → pennyfarthing-dist/pf}/story/size.py +0 -0
  1122. /package/{pennyfarthing_scripts → pennyfarthing-dist/pf}/story/template.py +0 -0
  1123. /package/{pennyfarthing_scripts → pennyfarthing-dist/pf}/swebench.py +0 -0
  1124. /package/{pennyfarthing_scripts → pennyfarthing-dist/pf}/theme/__init__.py +0 -0
  1125. /package/{pennyfarthing_scripts → pennyfarthing-dist/pf}/validate/__init__.py +0 -0
  1126. /package/{pennyfarthing_scripts → pennyfarthing-dist/pf}/validate/adapters/__init__.py +0 -0
  1127. /package/{pennyfarthing_scripts → pennyfarthing-dist/pf}/workflow/scale.py +0 -0
  1128. /package/{pennyfarthing_scripts → pennyfarthing-dist/pf}/workflow/state.py +0 -0
  1129. /package/{pennyfarthing_scripts → pennyfarthing-dist/pf}/workflow/team_lifecycle.py +0 -0
@@ -0,0 +1,2021 @@
1
+ """
2
+ Sprint CLI - Click-based CLI for sprint operations.
3
+
4
+ Usage:
5
+ pf sprint [COMMAND] [ARGS]...
6
+
7
+ Commands:
8
+ status Show sprint status
9
+ backlog Show available stories
10
+ use Switch active sprint (multi-sprint projects)
11
+ list Show registered sprints from sprint/sprints.yaml
12
+ active Show which sprint is currently active
13
+ work Start work on a story
14
+ archive Archive a completed story
15
+ story Story subcommands (show, add, update, size, template, finish, claim)
16
+ epic Epic subcommands (show, add, promote, archive, cancel, import, remove)
17
+ initiative Initiative subcommands (show, cancel)
18
+ """
19
+
20
+ import click
21
+
22
+
23
+ @click.group()
24
+ def sprint():
25
+ """Sprint status and story operations.
26
+
27
+ \b
28
+ Commands:
29
+ status - Show sprint status
30
+ backlog - Show available stories
31
+ use - Switch active sprint (multi-sprint projects)
32
+ list - Show registered sprints
33
+ active - Show which sprint is active
34
+ story - Story operations (show, add, update, size, template, finish, claim)
35
+ epic - Epic operations (show, add, promote, archive, cancel, import, remove)
36
+ initiative - Initiative operations (show, cancel)
37
+ work - Start work on a story
38
+ archive - Archive a completed story
39
+ """
40
+ pass
41
+
42
+
43
+ @sprint.command()
44
+ @click.argument("filter", required=False, type=click.Choice(
45
+ ["backlog", "todo", "in-progress", "review", "done"],
46
+ case_sensitive=False,
47
+ ))
48
+ def status(filter: str | None):
49
+ """Show sprint status.
50
+
51
+ \b
52
+ Arguments:
53
+ FILTER - Optional status filter (backlog, in-progress, done, etc.)
54
+ """
55
+ # Lazy import to maintain startup performance
56
+ from pf.sprint.status import format_status, get_sprint_status
57
+
58
+ sprint_status = get_sprint_status(filter)
59
+ click.echo(format_status(sprint_status))
60
+
61
+
62
+ @sprint.command()
63
+ def backlog():
64
+ """Show available stories grouped by epic.
65
+
66
+ Shows stories with backlog, ready, or planning status.
67
+ Output is grouped by epic with a markdown table per epic.
68
+ """
69
+ from pf.sprint.loader import load_sprint
70
+
71
+ data = load_sprint()
72
+ if not data or "epics" not in data:
73
+ click.echo("No sprint data available")
74
+ return
75
+
76
+ sprint_info = data.get("sprint", {})
77
+ click.echo(f"# Available Stories - {sprint_info.get('name', 'Unknown Sprint')}")
78
+ click.echo("")
79
+
80
+ available_statuses = {"backlog", "ready", "planning"}
81
+ total_count = 0
82
+ total_points = 0
83
+
84
+ for epic in data["epics"]:
85
+ if not isinstance(epic, dict):
86
+ continue
87
+
88
+ stories = [
89
+ s for s in epic.get("stories", [])
90
+ if s.get("status") in available_statuses
91
+ ]
92
+ if not stories:
93
+ continue
94
+
95
+ click.echo(f"### {epic.get('title', 'Unknown Epic')}")
96
+ if epic.get("description"):
97
+ desc = epic["description"].strip().split("\n")[0][:200]
98
+ click.echo(f"*{desc}*")
99
+ click.echo("")
100
+ click.echo("| ID | Title | Pts | Pri | Status | Assigned | Workflow |")
101
+ click.echo("|----|-------|-----|-----|--------|----------|----------|")
102
+
103
+ for s in stories:
104
+ title = s.get("title", "?")
105
+ if len(title) > 40:
106
+ title = title[:37] + "..."
107
+ sid = s.get("id", "?")
108
+ pts = s.get("points", "?")
109
+ pri = s.get("priority", "P2")
110
+ stat = s.get("status", "backlog")
111
+ wf = s.get("workflow", "tdd")
112
+ assigned = s.get("assigned_to", "")
113
+ if assigned:
114
+ parts = assigned.split("@")[0].split(".")
115
+ if len(parts) >= 2:
116
+ assigned = f"{parts[0][0].upper()}. {parts[-1].capitalize()}"
117
+ click.echo(f"| {sid} | {title} | {pts} | {pri} | {stat} | {assigned} | {wf} |")
118
+ total_count += 1
119
+ total_points += s.get("points", 0) or 0
120
+
121
+ click.echo("")
122
+
123
+ click.echo("---")
124
+ click.echo(f"**Total available:** {total_count} stories, {total_points} points")
125
+
126
+
127
+ @sprint.command("use")
128
+ @click.argument("name")
129
+ def sprint_use(name: str):
130
+ """Switch the active sprint (per-user preference).
131
+
132
+ \b
133
+ Validates the sprint name against sprint/sprints.yaml, then saves
134
+ the preference to .pennyfarthing/config.local.yaml. Does not modify
135
+ any shared repo files. Use "default" to clear the preference.
136
+
137
+ \b
138
+ Arguments:
139
+ NAME - Sprint name from the registry, or "default" to clear
140
+
141
+ \b
142
+ Examples:
143
+ pf sprint use main # Switch to main project sprint
144
+ pf sprint use ocsf-rs1 # Switch to OCSF research spike
145
+ pf sprint use default # Clear preference, use sprint/current-sprint.yaml
146
+ """
147
+ # Lazy import
148
+ from pf.sprint.loader import switch_sprint
149
+
150
+ result = switch_sprint(name)
151
+ if result["success"]:
152
+ entry = result.get("sprint", {})
153
+ click.echo(result["message"])
154
+ if entry.get("description"):
155
+ click.echo(f" {entry['description']}")
156
+ if entry.get("type"):
157
+ click.echo(f" Type: {entry['type']}")
158
+ if entry.get("repos"):
159
+ click.echo(f" Repos: {', '.join(entry['repos'])}")
160
+ else:
161
+ raise click.ClickException(result["error"])
162
+
163
+
164
+ @sprint.command("list")
165
+ def sprint_list():
166
+ """Show all registered sprints from the sprint registry.
167
+
168
+ \b
169
+ Reads sprint/sprints.yaml and displays each registered sprint
170
+ with its type, description, and whether it is currently active
171
+ for this user (based on .pennyfarthing/config.local.yaml).
172
+ """
173
+ # Lazy import
174
+ from pf.sprint.loader import get_active_sprint_name, load_sprint_registry
175
+
176
+ registry = load_sprint_registry()
177
+ if registry is None:
178
+ click.echo("No sprint registry found (sprint/sprints.yaml)")
179
+ click.echo("This project uses a single sprint.")
180
+ return
181
+
182
+ active = get_active_sprint_name()
183
+ sprints = registry.get("sprints", {})
184
+
185
+ if not sprints:
186
+ click.echo("Sprint registry is empty.")
187
+ return
188
+
189
+ click.echo("Registered Sprints:")
190
+ click.echo("")
191
+
192
+ for name, entry in sprints.items():
193
+ marker = "*" if name == active else " "
194
+ desc = entry.get("description", "")
195
+ sprint_type = entry.get("type", "project")
196
+ repos = ", ".join(entry.get("repos", []))
197
+ click.echo(f" {marker} {name:<16} [{sprint_type}] {desc}")
198
+ if repos:
199
+ click.echo(f" Repos: {repos}")
200
+
201
+ click.echo("")
202
+ if active:
203
+ click.echo(f"Active: {active} (per-user preference)")
204
+ else:
205
+ click.echo("Active: default (sprint/current-sprint.yaml)")
206
+ click.echo("Switch: pf sprint use <name>")
207
+
208
+
209
+ @sprint.command("active")
210
+ def sprint_active():
211
+ """Show which sprint is currently active for this user.
212
+
213
+ \b
214
+ Reads the per-user preference from .pennyfarthing/config.local.yaml
215
+ and displays the active sprint name and metadata. If no preference
216
+ is set, reports the default sprint/current-sprint.yaml.
217
+ """
218
+ # Lazy import
219
+ from pf.sprint.loader import get_active_sprint_name, load_sprint_registry
220
+
221
+ active = get_active_sprint_name()
222
+ if not active:
223
+ click.echo("default (sprint/current-sprint.yaml)")
224
+ click.echo(" No per-user sprint preference set.")
225
+ click.echo(" Set one with: pf sprint use <name>")
226
+ return
227
+
228
+ registry = load_sprint_registry()
229
+ if registry:
230
+ sprints = registry.get("sprints", {})
231
+ entry = sprints.get(active, {})
232
+ sprint_type = entry.get("type", "project")
233
+ desc = entry.get("description", "")
234
+ click.echo(f"{active} ({sprint_type})")
235
+ if desc:
236
+ click.echo(f" {desc}")
237
+ else:
238
+ click.echo(f"{active}")
239
+ click.echo(" (no sprint registry found to look up metadata)")
240
+
241
+
242
+ @sprint.command()
243
+ @click.argument("story_id", required=False)
244
+ @click.option("--dry-run", is_flag=True, help="Show what would be done without making changes")
245
+ def work(story_id: str | None, dry_run: bool):
246
+ """Start work on a story.
247
+
248
+ \b
249
+ Arguments:
250
+ STORY_ID - Story ID to work on, or 'next' for highest priority
251
+ """
252
+ # Lazy import
253
+ from pf.sprint.loader import get_stories_by_status
254
+ from pf.sprint.work import check_story, get_next_story
255
+
256
+ if not story_id:
257
+ # Show backlog
258
+ stories = get_stories_by_status("backlog")
259
+ click.echo(f"Available stories: {len(stories)}")
260
+ for story in stories[:10]:
261
+ click.echo(f" {story.get('id')}: {story.get('title')} [{story.get('points', '?')}pts]")
262
+ return
263
+
264
+ if story_id == "next":
265
+ result = get_next_story()
266
+ else:
267
+ result = check_story(story_id)
268
+
269
+ if result.get("available"):
270
+ story = result.get("story", {})
271
+ click.echo(f"Story: {story.get('id')}")
272
+ click.echo(f"Title: {story.get('title')}")
273
+ click.echo(f"Points: {story.get('points')}")
274
+ click.echo("Status: Available")
275
+ else:
276
+ error_msg = result.get("error") or result.get("reason")
277
+ raise click.ClickException(f"Not available: {error_msg}")
278
+
279
+
280
+ @sprint.command()
281
+ @click.argument("story_id")
282
+ @click.argument("pr_number", required=False)
283
+ @click.option("--apply", is_flag=True, help="Also remove from current-sprint.yaml")
284
+ @click.option("--dry-run", is_flag=True, help="Show what would be done without making changes")
285
+ def archive(story_id: str, pr_number: str | None, apply: bool, dry_run: bool):
286
+ """Archive a completed story.
287
+
288
+ \b
289
+ Arguments:
290
+ STORY_ID - Story ID to archive
291
+ PR_NUMBER - Optional PR number if merged via PR
292
+ """
293
+ # Lazy import
294
+ from pf.sprint.archive import archive_story
295
+
296
+ result = archive_story(
297
+ story_id,
298
+ pr_number=pr_number,
299
+ dry_run=dry_run,
300
+ apply=apply,
301
+ )
302
+
303
+ if result.get("success"):
304
+ if result.get("dry_run"):
305
+ click.echo(f"[DRY-RUN] {result.get('message')}")
306
+ else:
307
+ click.echo(result.get("message"))
308
+ else:
309
+ raise click.ClickException(f"Failed: {result.get('error')}")
310
+
311
+
312
+ # --- Story subgroup ---
313
+
314
+ @sprint.group()
315
+ def story():
316
+ """Story operations (show, add, update, size, template, finish, claim)."""
317
+ pass
318
+
319
+
320
+ @story.command("show")
321
+ @click.argument("story_id")
322
+ @click.option("--json", "output_json", is_flag=True, help="Output as JSON")
323
+ def story_show(story_id: str, output_json: bool):
324
+ """Show details for a specific story.
325
+
326
+ \b
327
+ Arguments:
328
+ STORY_ID - Story ID (e.g., MSSCI-12664 or 67-1)
329
+ """
330
+ # Lazy import
331
+ from pf.sprint.loader import get_story_by_id
332
+
333
+ story_data = get_story_by_id(story_id)
334
+
335
+ if not story_data:
336
+ raise click.ClickException(f"Story not found: {story_id}")
337
+
338
+ if output_json:
339
+ import json
340
+
341
+ click.echo(json.dumps(story_data, indent=2))
342
+ else:
343
+ click.echo(f"Story: {story_data.get('id', story_id)}")
344
+ click.echo(f"Title: {story_data.get('title', 'N/A')}")
345
+ click.echo(f"Points: {story_data.get('points', 'N/A')}")
346
+ click.echo(f"Status: {story_data.get('status', 'N/A')}")
347
+ if story_data.get("priority"):
348
+ click.echo(f"Priority: {story_data.get('priority')}")
349
+ if story_data.get("workflow"):
350
+ click.echo(f"Workflow: {story_data.get('workflow')}")
351
+ if story_data.get("jira"):
352
+ click.echo(f"Jira: {story_data.get('jira')}")
353
+ if story_data.get("description"):
354
+ click.echo(f"Description: {story_data.get('description')}")
355
+
356
+
357
+ @story.command("size")
358
+ @click.argument("points", required=False, type=int)
359
+ def story_size(points: int | None):
360
+ """Display story sizing guidelines.
361
+
362
+ \b
363
+ Arguments:
364
+ POINTS - Optional specific point value to show guidance for
365
+ """
366
+ from pf.story.size import format_size_info, get_sizing_guidelines
367
+
368
+ guidelines = get_sizing_guidelines(points)
369
+ click.echo(format_size_info(guidelines))
370
+
371
+
372
+ @story.command("template")
373
+ @click.argument("template_type", required=False)
374
+ def story_template(template_type: str | None):
375
+ """Display story templates by type.
376
+
377
+ \b
378
+ Arguments:
379
+ TYPE - Template type (feature, bug, refactor, chore)
380
+ """
381
+ from pf.story.template import get_all_templates, get_template
382
+
383
+ if template_type:
384
+ template = get_template(template_type)
385
+ if template:
386
+ click.echo(f"Type: {template['type']}")
387
+ click.echo(f"Description: {template['description']}")
388
+ click.echo("")
389
+ click.echo("Template:")
390
+ click.echo(template["template"])
391
+ else:
392
+ raise click.ClickException(f"Unknown template type: {template_type}")
393
+ else:
394
+ click.echo("Available templates:")
395
+ for name, template in get_all_templates().items():
396
+ click.echo(f" {name}: {template['description']}")
397
+
398
+
399
+ @story.command("finish")
400
+ @click.argument("story_id")
401
+ @click.option("--dry-run", is_flag=True, help="Show what would be done without executing")
402
+ def story_finish(story_id: str, dry_run: bool):
403
+ """Complete a story: archive session, merge PR, transition Jira, update sprint YAML.
404
+
405
+ \b
406
+ Arguments:
407
+ STORY_ID - Story ID (e.g., 83-2)
408
+ """
409
+ from pf.common.config import get_project_root
410
+ from pf.sprint.story_finish import finish_story
411
+
412
+ root = get_project_root()
413
+ result = finish_story(root, story_id, dry_run=dry_run)
414
+
415
+ if not result["success"]:
416
+ raise click.ClickException(result["error"])
417
+
418
+ if result.get("dry_run"):
419
+ click.echo(f"[DRY RUN] Finish story {story_id} ({result.get('jira_key', '?')})")
420
+ for step in result.get("steps", []):
421
+ click.echo(f" {step['step']}. {step['action']}")
422
+ return
423
+
424
+ click.echo(f"=== Story {story_id} Complete ===")
425
+ jira_key = result.get("jira_key")
426
+ if jira_key:
427
+ click.echo(f"Jira: https://1898andco.atlassian.net/browse/{jira_key}")
428
+ for step in result.get("steps", []):
429
+ warning = step.get("warning", "")
430
+ error = step.get("error", "")
431
+ suffix = f" (warning: {warning})" if warning else f" (error: {error})" if error else ""
432
+ click.echo(f" {step['step']}. {step['action']}{suffix}")
433
+
434
+
435
+ @story.command("claim")
436
+ @click.argument("story_id")
437
+ @click.option("--claim/--unclaim", default=True, help="Claim or unclaim the story")
438
+ @click.option("--dry-run", is_flag=True, help="Show what would be done without making changes")
439
+ def story_claim(story_id: str, claim: bool, dry_run: bool):
440
+ """Claim or unclaim a story in Jira.
441
+
442
+ \b
443
+ Arguments:
444
+ STORY_ID - Story ID / Jira key to claim
445
+ """
446
+ if dry_run:
447
+ action = "claim" if claim else "unclaim"
448
+ click.echo(f"[DRY-RUN] Would {action} {story_id}")
449
+ return
450
+ from pf.jira.claim import claim_issue, unclaim_issue
451
+
452
+ if claim:
453
+ result = claim_issue(story_id)
454
+ else:
455
+ result = unclaim_issue(story_id)
456
+
457
+ if result.get("success"):
458
+ click.echo(result.get("message", f"{'Claimed' if claim else 'Unclaimed'} {story_id}"))
459
+ else:
460
+ raise click.ClickException(result.get("error", "Unknown error"))
461
+
462
+
463
+ # Register story-add as story.add
464
+ from pf.sprint.story_add import story_add_command # noqa: E402
465
+
466
+ story.add_command(story_add_command, "add")
467
+
468
+ # Register story-update as story.update
469
+ from pf.sprint.story_update import story_update_command # noqa: E402
470
+
471
+ story.add_command(story_update_command, "update")
472
+
473
+
474
+ # --- Epic subgroup ---
475
+
476
+ @sprint.group()
477
+ def epic():
478
+ """Epic operations (show, add, promote, archive, cancel, import, remove)."""
479
+ pass
480
+
481
+
482
+ @epic.command("show")
483
+ @click.argument("epic_id")
484
+ @click.option("--json", "output_json", is_flag=True, help="Output as JSON")
485
+ def epic_show(epic_id: str, output_json: bool):
486
+ """Show details for a specific epic.
487
+
488
+ Searches both the current sprint and future initiative shards.
489
+
490
+ \b
491
+ Arguments:
492
+ EPIC_ID - Epic ID (e.g., epic-42 or MSSCI-14298)
493
+
494
+ \b
495
+ Examples:
496
+ pf sprint epic show MSSCI-14298
497
+ pf sprint epic show epic-42
498
+ pf sprint epic show epic-42 --json
499
+ """
500
+ import json as json_mod
501
+
502
+ from pf.common.config import get_project_root
503
+ from pf.sprint.loader import load_sprint
504
+
505
+ root = get_project_root()
506
+ epic_data = None
507
+ source = None
508
+
509
+ # 1. Search current sprint
510
+ sprint_data = load_sprint(root)
511
+ if sprint_data and "epics" in sprint_data:
512
+ for e in sprint_data["epics"]:
513
+ if isinstance(e, dict):
514
+ eid = str(e.get("id", ""))
515
+ ejira = str(e.get("jira", ""))
516
+ if epic_id in (eid, ejira, eid.replace("epic-", ""), f"epic-{epic_id}"):
517
+ epic_data = e
518
+ source = "current sprint"
519
+ break
520
+
521
+ # 2. Search future initiative shards
522
+ if not epic_data:
523
+ epic_data, source = _find_epic_in_initiatives(epic_id, root)
524
+
525
+ if not epic_data:
526
+ raise click.ClickException(f"Epic not found: {epic_id}")
527
+
528
+ if output_json:
529
+ # Convert to plain dict for JSON serialization
530
+ click.echo(json_mod.dumps(dict(epic_data), indent=2, default=str))
531
+ else:
532
+ click.echo(f"Epic: {epic_data.get('id', epic_id)}")
533
+ click.echo(f"Title: {epic_data.get('title', 'N/A')}")
534
+ click.echo(f"Status: {epic_data.get('status', 'N/A')}")
535
+ click.echo(f"Points: {epic_data.get('points', 'N/A')}")
536
+ click.echo(f"Source: {source}")
537
+ if epic_data.get("priority"):
538
+ click.echo(f"Priority: {epic_data.get('priority')}")
539
+ if epic_data.get("jira"):
540
+ click.echo(f"Jira: {epic_data.get('jira')}")
541
+ if epic_data.get("repos"):
542
+ click.echo(f"Repos: {epic_data.get('repos')}")
543
+ if epic_data.get("description"):
544
+ click.echo(f"Description: {epic_data.get('description').rstrip()}")
545
+
546
+ stories = epic_data.get("stories", [])
547
+ if stories:
548
+ click.echo(f"\nStories ({len(stories)}):")
549
+ for s in stories:
550
+ sid = s.get("id", "?")
551
+ stitle = s.get("title", "?")
552
+ spts = s.get("points", "?")
553
+ sstat = s.get("status", "?")
554
+ click.echo(f" {sid}: {stitle} [{spts}pts] ({sstat})")
555
+
556
+
557
+ def _epic_shard_path(sprint_dir, ref: str):
558
+ """Resolve an epic shard file path from a ref string.
559
+
560
+ Handles both 'epic-42' and 'MSSCI-12792' style refs.
561
+ The file naming convention is epic-{ref}.yaml, but refs that
562
+ already start with 'epic-' should not be double-prefixed.
563
+ """
564
+ if ref.startswith("epic-"):
565
+ return sprint_dir / f"{ref}.yaml"
566
+ return sprint_dir / f"epic-{ref}.yaml"
567
+
568
+
569
+ def _epic_ref_matches(ref: str, epic_id: str) -> bool:
570
+ """Check if an initiative epic ref matches the requested epic_id."""
571
+ # Normalize both to compare without prefix
572
+ ref_bare = ref.replace("epic-", "") if ref.startswith("epic-") else ref
573
+ id_bare = epic_id.replace("epic-", "") if epic_id.startswith("epic-") else epic_id
574
+ return ref_bare == id_bare or ref == epic_id
575
+
576
+
577
+ def _find_epic_in_initiatives(epic_id: str, root):
578
+ """Search initiative shard files for an epic by ID.
579
+
580
+ Returns (epic_dict, source_string) or (None, None).
581
+ """
582
+ import yaml
583
+
584
+ sprint_dir = root / "sprint"
585
+ for init_file in sorted(sprint_dir.glob("initiative-*.yaml")):
586
+ with open(init_file) as f:
587
+ init_data = yaml.safe_load(f.read())
588
+ if not init_data:
589
+ continue
590
+
591
+ init_name = init_data.get("name", init_file.stem)
592
+ epics = init_data.get("epics", [])
593
+ for e in epics:
594
+ if isinstance(e, str):
595
+ if _epic_ref_matches(e, epic_id):
596
+ shard = _epic_shard_path(sprint_dir, e)
597
+ if shard.exists():
598
+ with open(shard) as sf:
599
+ epic_data = yaml.safe_load(sf.read())
600
+ if epic_data:
601
+ return epic_data, f"initiative: {init_name}"
602
+ elif isinstance(e, dict):
603
+ eid = str(e.get("id", ""))
604
+ if _epic_ref_matches(eid, epic_id):
605
+ return e, f"initiative: {init_name}"
606
+
607
+ return None, None
608
+
609
+
610
+ @epic.command("cancel")
611
+ @click.argument("epic_id")
612
+ @click.option("--jira", is_flag=True, help="Also cancel the epic in Jira")
613
+ @click.option("--dry-run", is_flag=True, help="Show what would be done without making changes")
614
+ def epic_cancel(epic_id: str, jira: bool, dry_run: bool):
615
+ """Cancel an epic and all its stories.
616
+
617
+ Sets the epic status to 'canceled' and all stories to 'canceled'.
618
+ Searches both the current sprint and future initiative shards.
619
+
620
+ \b
621
+ Arguments:
622
+ EPIC_ID - Epic ID (e.g., epic-42 or MSSCI-14298)
623
+
624
+ \b
625
+ Examples:
626
+ pf sprint epic cancel epic-42 --dry-run
627
+ pf sprint epic cancel epic-42
628
+ pf sprint epic cancel epic-42 --jira
629
+ """
630
+ from pf.common.config import get_project_root
631
+ from pf.sprint.yaml_io import read_sprint, write_sprint
632
+
633
+ root = get_project_root()
634
+ sprint_dir = root / "sprint"
635
+ sprint_path = sprint_dir / "current-sprint.yaml"
636
+
637
+ # 1. Try current sprint
638
+ sprint_data = read_sprint(sprint_path) if sprint_path.exists() else None
639
+ found_in_sprint = False
640
+ if sprint_data and "epics" in sprint_data:
641
+ for e in sprint_data["epics"]:
642
+ if not isinstance(e, dict):
643
+ continue
644
+ eid = str(e.get("id", ""))
645
+ ejira = str(e.get("jira", ""))
646
+ if epic_id in (eid, ejira, eid.replace("epic-", ""), f"epic-{epic_id}"):
647
+ found_in_sprint = True
648
+ jira_key = e.get("jira")
649
+ stories = e.get("stories", [])
650
+ story_count = len(stories)
651
+
652
+ click.echo(f"Epic: {eid}")
653
+ click.echo(f"Title: {e.get('title', 'N/A')}")
654
+ click.echo(f"Stories: {story_count}")
655
+
656
+ if jira_key and not jira:
657
+ click.echo(f"\nWarning: Epic has Jira key {jira_key} -- pass --jira to also cancel in Jira")
658
+
659
+ if dry_run:
660
+ click.echo(f"\n[DRY-RUN] Would cancel {eid} and {story_count} stories")
661
+ return
662
+
663
+ e["status"] = "canceled"
664
+ for s in stories:
665
+ s["status"] = "canceled"
666
+
667
+ write_sprint(sprint_path, sprint_data)
668
+ click.echo(f"\nCanceled {eid} and {story_count} stories in current sprint")
669
+
670
+ if jira and jira_key:
671
+ _transition_jira(jira_key, "Cancelled")
672
+ click.echo(f"Transitioned Jira {jira_key} to Cancelled")
673
+ return
674
+
675
+ # 2. Try initiative shards
676
+ if not found_in_sprint:
677
+ _cancel_epic_in_initiatives(epic_id, root, jira=jira, dry_run=dry_run)
678
+
679
+
680
+ def _transition_jira(jira_key: str, status: str) -> bool:
681
+ """Transition a Jira issue to the given status."""
682
+ import subprocess
683
+
684
+ try:
685
+ result = subprocess.run(
686
+ ["jira", "issue", "move", jira_key, status],
687
+ capture_output=True,
688
+ text=True,
689
+ timeout=30,
690
+ )
691
+ return result.returncode == 0
692
+ except Exception:
693
+ return False
694
+
695
+
696
+ def _cancel_epic_in_initiatives(epic_id: str, root, *, jira: bool, dry_run: bool):
697
+ """Find and cancel an epic in initiative shard files."""
698
+ import yaml
699
+
700
+ sprint_dir = root / "sprint"
701
+
702
+ for init_file in sorted(sprint_dir.glob("initiative-*.yaml")):
703
+ with open(init_file) as f:
704
+ raw = f.read()
705
+ init_data = yaml.safe_load(raw)
706
+ if not init_data:
707
+ continue
708
+
709
+ init_name = init_data.get("name", init_file.stem)
710
+ epics = init_data.get("epics", [])
711
+
712
+ for _i, e in enumerate(epics):
713
+ matched = False
714
+ epic_dict = None
715
+
716
+ if isinstance(e, str):
717
+ if _epic_ref_matches(e, epic_id):
718
+ shard = _epic_shard_path(sprint_dir, e)
719
+ if shard.exists():
720
+ with open(shard) as sf:
721
+ epic_dict = yaml.safe_load(sf.read())
722
+ matched = True
723
+ elif isinstance(e, dict):
724
+ eid = str(e.get("id", ""))
725
+ if _epic_ref_matches(eid, epic_id):
726
+ epic_dict = e
727
+ matched = True
728
+
729
+ if not matched or not epic_dict:
730
+ continue
731
+
732
+ jira_key = epic_dict.get("jira")
733
+ stories = epic_dict.get("stories", [])
734
+ story_count = len(stories)
735
+
736
+ click.echo(f"Epic: {epic_dict.get('id', epic_id)}")
737
+ click.echo(f"Title: {epic_dict.get('title', 'N/A')}")
738
+ click.echo(f"Initiative: {init_name}")
739
+ click.echo(f"Stories: {story_count}")
740
+
741
+ if jira_key and not jira:
742
+ click.echo(f"\nWarning: Epic has Jira key {jira_key} -- pass --jira to also cancel in Jira")
743
+
744
+ if dry_run:
745
+ click.echo(f"\n[DRY-RUN] Would cancel {epic_dict.get('id', epic_id)} and {story_count} stories")
746
+ return
747
+
748
+ epic_dict["status"] = "canceled"
749
+ for s in stories:
750
+ s["status"] = "canceled"
751
+
752
+ # Write back — either shard file or inline in initiative
753
+ if isinstance(e, str):
754
+ shard = _epic_shard_path(sprint_dir, e)
755
+ with open(shard, "w") as sf:
756
+ yaml.dump(dict(epic_dict), sf, default_flow_style=False, sort_keys=False)
757
+ else:
758
+ with open(init_file, "w") as f:
759
+ yaml.dump(init_data, f, default_flow_style=False, sort_keys=False)
760
+
761
+ click.echo(f"\nCanceled {epic_dict.get('id', epic_id)} and {story_count} stories")
762
+
763
+ if jira and jira_key:
764
+ _transition_jira(jira_key, "Cancelled")
765
+ click.echo(f"Transitioned Jira {jira_key} to Cancelled")
766
+ return
767
+
768
+ raise click.ClickException(f"Epic not found: {epic_id}")
769
+
770
+
771
+ @epic.command("archive")
772
+ @click.argument("epic_id", required=False)
773
+ @click.option("--dry-run", is_flag=True, help="Show what would be done without making changes")
774
+ @click.option("--jira", is_flag=True, help="Also update Jira epic status to Done")
775
+ def epic_archive(epic_id: str | None, dry_run: bool, jira: bool):
776
+ """Archive completed epics.
777
+
778
+ \b
779
+ Arguments:
780
+ EPIC_ID - Epic ID to archive (omit to scan all completed epics)
781
+
782
+ \b
783
+ Examples:
784
+ pf sprint epic archive # Scan and archive all completed
785
+ pf sprint epic archive --dry-run # Preview what would be archived
786
+ pf sprint epic archive epic-64 # Archive specific epic
787
+ pf sprint epic archive epic-64 --jira # Archive and update Jira
788
+ """
789
+ # Lazy import
790
+ from pf.sprint.archive_epic import (
791
+ archive_all_completed,
792
+ )
793
+ from pf.sprint.archive_epic import (
794
+ archive_epic as do_archive_epic,
795
+ )
796
+
797
+ if epic_id:
798
+ result = do_archive_epic(epic_id, dry_run=dry_run, update_jira=jira)
799
+ else:
800
+ result = archive_all_completed(dry_run=dry_run, update_jira=jira)
801
+
802
+ if result.get("success"):
803
+ if dry_run:
804
+ click.echo(f"[DRY-RUN] {result.get('message')}")
805
+ if "archived" in result:
806
+ for r in result["archived"]:
807
+ e = r.get("epic", {})
808
+ eid = e.get("id") if e else r.get("epic_id")
809
+ stories = len(e.get("stories", [])) if e else r.get("stories_archived", 0)
810
+ click.echo(f" Would archive: {eid} ({stories} stories)")
811
+ else:
812
+ click.echo(result.get("message"))
813
+ if "archived" in result:
814
+ for r in result["archived"]:
815
+ click.echo(f" ✓ {r.get('epic_id')}: {r.get('stories_archived')} stories")
816
+ if result.get("stories_archived"):
817
+ click.echo(f" ✓ {result.get('epic_id')}: {result.get('stories_archived')} stories")
818
+ else:
819
+ error_msg = result.get("error", "Unknown error")
820
+ if result.get("incomplete_stories"):
821
+ error_msg += f"\n Incomplete: {', '.join(result['incomplete_stories'])}"
822
+ raise click.ClickException(error_msg)
823
+
824
+
825
+ @epic.command("import")
826
+ @click.argument("epics_file")
827
+ @click.argument("initiative_name", required=False)
828
+ @click.option("--marker", default="imported", help="Marker tag for stories (default: imported)")
829
+ @click.option("--dry-run", is_flag=True, help="Show what would be done without making changes")
830
+ def epic_import(epics_file: str, initiative_name: str | None, marker: str, dry_run: bool):
831
+ """Import BMAD epics-and-stories output to future.yaml.
832
+
833
+ \b
834
+ Arguments:
835
+ EPICS_FILE - Path to markdown file from epics-and-stories workflow
836
+ INITIATIVE_NAME - Name for the initiative (optional, extracted from file)
837
+
838
+ \b
839
+ Examples:
840
+ pf sprint epic import docs/planning/my-feature-epics.md
841
+ pf sprint epic import docs/planning/my-feature-epics.md "My Feature" --marker my-feature
842
+ pf sprint epic import docs/planning/my-feature-epics.md --dry-run
843
+ """
844
+ # Lazy import
845
+ from pf.sprint.import_epic import import_epic as do_import
846
+
847
+ result = do_import(
848
+ epics_file,
849
+ initiative_name=initiative_name,
850
+ marker=marker,
851
+ dry_run=dry_run,
852
+ )
853
+
854
+ if result.get("success"):
855
+ if dry_run:
856
+ click.echo(f"[DRY-RUN] {result.get('message')}")
857
+ click.echo(f" Epics: {result.get('epics_count')}")
858
+ click.echo(f" Stories: {result.get('stories_count')}")
859
+ click.echo(f" Points: {result.get('total_points')}")
860
+ click.echo(f" Epic numbers: epic-{result.get('start_epic_num')} to epic-{result.get('next_epic_num') - 1}")
861
+ click.echo("")
862
+ click.echo("YAML Preview:")
863
+ click.echo("-" * 60)
864
+ click.echo(result.get("yaml_preview"))
865
+ click.echo("-" * 60)
866
+ else:
867
+ click.echo(f"✓ {result.get('message')}")
868
+ click.echo(f" Next available epic number: {result.get('next_epic_num')}")
869
+ else:
870
+ raise click.ClickException(result.get("error", "Unknown error"))
871
+
872
+
873
+ @epic.command("remove")
874
+ @click.argument("epic_id")
875
+ @click.option("--dry-run", is_flag=True, help="Show what would be removed without making changes")
876
+ def epic_remove(epic_id: str, dry_run: bool):
877
+ """Remove an epic from future.yaml (for cancelled pre-Jira epics).
878
+
879
+ \b
880
+ Arguments:
881
+ EPIC_ID - Epic ID to remove (e.g., epic-41)
882
+
883
+ \b
884
+ Examples:
885
+ pf sprint epic remove epic-41
886
+ pf sprint epic remove epic-41 --dry-run
887
+ """
888
+
889
+ import yaml
890
+
891
+ from pf.common.config import get_project_root
892
+
893
+ future_path = get_project_root() / "sprint" / "future.yaml"
894
+ if not future_path.exists():
895
+ raise click.ClickException(f"File not found: {future_path}")
896
+
897
+ with open(future_path) as f:
898
+ data = yaml.safe_load(f.read())
899
+
900
+ if not data or "future" not in data or "initiatives" not in data["future"]:
901
+ raise click.ClickException("Invalid future.yaml structure")
902
+
903
+ # Find the epic
904
+ found = False
905
+ for init in data["future"]["initiatives"]:
906
+ epics = init.get("epics", [])
907
+ for e in epics:
908
+ if e.get("id") == epic_id:
909
+ found = True
910
+ story_count = len(e.get("stories", []))
911
+ click.echo(f"Found epic in initiative '{init.get('name', 'unknown')}':")
912
+ click.echo(f" ID: {epic_id}")
913
+ click.echo(f" Title: {e.get('title', 'unknown')}")
914
+ click.echo(f" Points: {e.get('points', '?')}")
915
+ click.echo(f" Stories: {story_count}")
916
+
917
+ if dry_run:
918
+ click.echo(f"\n[DRY-RUN] Would remove {epic_id} from future.yaml")
919
+ return
920
+
921
+ # Remove using yq to preserve comments and formatting
922
+ import subprocess as sp
923
+
924
+ result = sp.run(
925
+ [
926
+ "yq", "eval", "-i",
927
+ f'del(.future.initiatives[].epics[] | select(.id == "{epic_id}"))',
928
+ str(future_path),
929
+ ],
930
+ capture_output=True,
931
+ text=True,
932
+ )
933
+ if result.returncode != 0:
934
+ raise click.ClickException(f"yq failed: {result.stderr}")
935
+
936
+ click.echo(f"\n✓ Removed {epic_id} from future.yaml")
937
+ return
938
+
939
+ if not found:
940
+ raise click.ClickException(
941
+ f"Epic {epic_id} not found in future.yaml"
942
+ )
943
+
944
+
945
+ @epic.command("promote")
946
+ @click.argument("epic_id")
947
+ @click.option("--dry-run", is_flag=True, help="Show what would be done without making changes")
948
+ def epic_promote(epic_id: str, dry_run: bool):
949
+ """Move an epic from future initiatives to current-sprint.yaml.
950
+
951
+ Detects ID collisions and assigns new IDs if needed.
952
+ Automatically removes the epic from its initiative shard after promotion.
953
+
954
+ \b
955
+ Arguments:
956
+ EPIC_ID - Epic ID (e.g., epic-41 or 41)
957
+
958
+ \b
959
+ Examples:
960
+ pf sprint epic promote epic-41
961
+ pf sprint epic promote 41
962
+ """
963
+ import copy
964
+
965
+ import yaml
966
+
967
+ from pf.common.config import get_project_root
968
+
969
+ root = get_project_root()
970
+ sprint_dir = root / "sprint"
971
+ sprint_file = sprint_dir / "current-sprint.yaml"
972
+
973
+ if not sprint_file.exists():
974
+ raise click.ClickException(f"Sprint file not found: {sprint_file}")
975
+
976
+ # Find the epic in initiative shards
977
+ epic_data = None
978
+ source_init_file = None
979
+ source_ref = None
980
+
981
+ for init_file in sorted(sprint_dir.glob("initiative-*.yaml")):
982
+ with open(init_file) as f:
983
+ init_data = yaml.safe_load(f.read())
984
+ if not init_data:
985
+ continue
986
+ for e in init_data.get("epics", []):
987
+ edata = _resolve_epic_ref(e, sprint_dir)
988
+ if not edata:
989
+ continue
990
+ eid = str(edata.get("id", ""))
991
+ if _epic_ref_matches(eid, epic_id):
992
+ epic_data = copy.deepcopy(edata)
993
+ source_init_file = init_file
994
+ source_ref = e
995
+ break
996
+ if epic_data:
997
+ break
998
+
999
+ if not epic_data:
1000
+ raise click.ClickException(f"Epic {epic_id} not found in future initiatives")
1001
+
1002
+ # Load current sprint
1003
+ with open(sprint_file) as f:
1004
+ sprint_data = yaml.safe_load(f.read())
1005
+
1006
+ if not sprint_data:
1007
+ raise click.ClickException(f"Invalid sprint file: {sprint_file}")
1008
+
1009
+ if "epics" not in sprint_data:
1010
+ sprint_data["epics"] = []
1011
+
1012
+ # Check for ID collision
1013
+ original_id = str(epic_data.get("id", epic_id))
1014
+ new_epic_id = original_id
1015
+ existing_ids = {str(e.get("id", "")) for e in sprint_data["epics"] if isinstance(e, dict)}
1016
+
1017
+ # Normalize to numeric ID (ADR-0022: strip epic- prefix from values)
1018
+ new_epic_id = new_epic_id.replace("epic-", "") if new_epic_id.startswith("epic-") else new_epic_id
1019
+
1020
+ if new_epic_id in existing_ids:
1021
+ max_num = 0
1022
+ for eid in existing_ids:
1023
+ clean_eid = eid.replace("epic-", "") if eid.startswith("epic-") else eid
1024
+ try:
1025
+ max_num = max(max_num, int(clean_eid))
1026
+ except ValueError:
1027
+ pass
1028
+ new_epic_id = str(max_num + 1)
1029
+ click.echo(f"Warning: Epic ID {original_id} already exists. Assigning new ID: {new_epic_id}")
1030
+
1031
+ # Transform epic for current sprint
1032
+ old_id_num = original_id.replace("epic-", "")
1033
+ new_id_num = new_epic_id.replace("epic-", "") if new_epic_id.startswith("epic-") else new_epic_id
1034
+
1035
+ epic_data["id"] = new_epic_id
1036
+ epic_data["status"] = "backlog"
1037
+ if not epic_data.get("title", "").startswith("Epic:"):
1038
+ epic_data["title"] = f"Epic: {epic_data.get('title', 'Unknown')}"
1039
+
1040
+ for s in epic_data.get("stories", []):
1041
+ sid = str(s.get("id", ""))
1042
+ if sid.startswith(f"{old_id_num}-"):
1043
+ s["id"] = sid.replace(f"{old_id_num}-", f"{new_id_num}-", 1)
1044
+ s["status"] = "backlog"
1045
+ s.setdefault("repos", "pennyfarthing")
1046
+ s.setdefault("workflow", "tdd")
1047
+ s.setdefault("priority", "P2")
1048
+ s.setdefault("acceptance_criteria", [])
1049
+
1050
+ story_count = len(epic_data.get("stories", []))
1051
+
1052
+ click.echo("")
1053
+ click.echo("Promoting epic to current sprint:")
1054
+ click.echo(f" Original ID: {original_id}")
1055
+ if new_epic_id != original_id:
1056
+ click.echo(f" New ID: {new_epic_id}")
1057
+ click.echo(f" Title: {epic_data.get('title')}")
1058
+ click.echo(f" Points: {epic_data.get('points', 0)}")
1059
+ click.echo(f" Stories: {story_count}")
1060
+ click.echo("")
1061
+
1062
+ if dry_run:
1063
+ click.echo(f"\n[DRY-RUN] Would promote {original_id} ({story_count} stories) to current sprint")
1064
+ return
1065
+
1066
+ # Validate epic shard before writing (ADR-0022)
1067
+ from pf.sprint.validator import validate_epic_shard
1068
+ validation = validate_epic_shard(dict(epic_data))
1069
+ if not validation.valid:
1070
+ error_msgs = "; ".join(e.message for e in validation.errors)
1071
+ raise click.ClickException(f"Epic validation failed: {error_msgs}")
1072
+
1073
+ # Append to sprint
1074
+ sprint_data["epics"].append(epic_data)
1075
+
1076
+ from pf.sprint.yaml_io import write_sprint
1077
+ write_sprint(sprint_file, sprint_data)
1078
+ click.echo(f"Added epic to {sprint_file}")
1079
+
1080
+ # Remove from initiative shard
1081
+ with open(source_init_file) as f:
1082
+ init_data = yaml.safe_load(f.read())
1083
+
1084
+ if isinstance(source_ref, str):
1085
+ # String ref — remove from initiative list (shard file is kept for current sprint)
1086
+ init_data["epics"] = [e for e in init_data.get("epics", []) if e != source_ref]
1087
+ else:
1088
+ # Inline dict — remove matching entry
1089
+ init_data["epics"] = [
1090
+ e for e in init_data.get("epics", [])
1091
+ if not (isinstance(e, dict) and _epic_ref_matches(str(e.get("id", "")), epic_id))
1092
+ ]
1093
+
1094
+ remaining_epics = init_data.get("epics", [])
1095
+ if remaining_epics:
1096
+ # Initiative still has epics — update shard in place
1097
+ with open(source_init_file, "w") as f:
1098
+ yaml.dump(init_data, f, default_flow_style=False, sort_keys=False)
1099
+ click.echo(f"Removed {original_id} from {source_init_file.name}")
1100
+ else:
1101
+ # Initiative is empty — remove shard and future.yaml reference
1102
+ init_slug = source_init_file.stem.replace("initiative-", "")
1103
+ source_init_file.unlink()
1104
+ click.echo(f"Removed empty initiative shard: {source_init_file.name}")
1105
+
1106
+ # Remove from future.yaml
1107
+ future_file = sprint_dir / "future.yaml"
1108
+ if future_file.exists():
1109
+ with open(future_file) as f:
1110
+ future_data = yaml.safe_load(f.read()) or {}
1111
+ future_inits = future_data.get("future", {}).get("initiatives", [])
1112
+ if init_slug in future_inits:
1113
+ future_inits.remove(init_slug)
1114
+ with open(future_file, "w") as f:
1115
+ yaml.dump(future_data, f, default_flow_style=False, sort_keys=False)
1116
+ click.echo(f"Removed '{init_slug}' from future.yaml")
1117
+
1118
+ click.echo("")
1119
+ click.echo("Promotion complete!")
1120
+ click.echo("")
1121
+ click.echo("Next steps:")
1122
+ click.echo(f" 1. Review the epic: pf sprint epic show {new_epic_id}")
1123
+ click.echo(f" 2. Create Jira epic: pf jira create epic {new_epic_id}")
1124
+ click.echo(f" 3. Start work: /sprint work {new_id_num}-1")
1125
+
1126
+
1127
+ # Register epic-add as epic.add
1128
+ from pf.sprint.epic_add import epic_add_command # noqa: E402
1129
+
1130
+ epic.add_command(epic_add_command, "add")
1131
+
1132
+ # Register epic-update as epic.update
1133
+ from pf.sprint.epic_update import epic_update_command # noqa: E402
1134
+
1135
+ epic.add_command(epic_update_command, "update")
1136
+
1137
+
1138
+ # --- Initiative subgroup ---
1139
+
1140
+ @sprint.group()
1141
+ def initiative():
1142
+ """Initiative operations (show, cancel)."""
1143
+ pass
1144
+
1145
+
1146
+ @initiative.command("show")
1147
+ @click.argument("name")
1148
+ @click.option("--json", "output_json", is_flag=True, help="Output as JSON")
1149
+ def initiative_show(name: str, output_json: bool):
1150
+ """Show details for a specific initiative.
1151
+
1152
+ \b
1153
+ Arguments:
1154
+ NAME - Initiative slug (e.g., benchmark-reliability, technical-debt)
1155
+
1156
+ \b
1157
+ Examples:
1158
+ pf sprint initiative show benchmark-reliability
1159
+ pf sprint initiative show technical-debt --json
1160
+ """
1161
+ import json as json_mod
1162
+
1163
+ import yaml
1164
+
1165
+ from pf.common.config import get_project_root
1166
+
1167
+ root = get_project_root()
1168
+ init_file = root / "sprint" / f"initiative-{name}.yaml"
1169
+
1170
+ if not init_file.exists():
1171
+ raise click.ClickException(f"Initiative not found: {name}\n Expected: {init_file}")
1172
+
1173
+ with open(init_file) as f:
1174
+ init_data = yaml.safe_load(f.read())
1175
+
1176
+ if not init_data:
1177
+ raise click.ClickException(f"Empty initiative file: {init_file}")
1178
+
1179
+ if output_json:
1180
+ click.echo(json_mod.dumps(init_data, indent=2, default=str))
1181
+ return
1182
+
1183
+ click.echo(f"Initiative: {init_data.get('name', name)}")
1184
+ click.echo(f"Status: {init_data.get('status', 'N/A')}")
1185
+ if init_data.get("total_points"):
1186
+ click.echo(f"Total Points: {init_data.get('total_points')}")
1187
+ if init_data.get("blocked_by"):
1188
+ click.echo(f"Blocked By: {init_data.get('blocked_by')}")
1189
+ if init_data.get("description"):
1190
+ click.echo(f"Description: {init_data.get('description').rstrip()}")
1191
+
1192
+ epics = init_data.get("epics", [])
1193
+ if epics:
1194
+ click.echo(f"\nEpics ({len(epics)}):")
1195
+ sprint_dir = root / "sprint"
1196
+ for e in epics:
1197
+ if isinstance(e, str):
1198
+ # String ref — try to load shard for details
1199
+ shard = _epic_shard_path(sprint_dir, e)
1200
+ if shard.exists():
1201
+ with open(shard) as sf:
1202
+ edata = yaml.safe_load(sf.read())
1203
+ if edata:
1204
+ etitle = edata.get("title", "?")
1205
+ epts = edata.get("points", "?")
1206
+ estat = edata.get("status", "?")
1207
+ click.echo(f" {edata.get('id', e)}: {etitle} [{epts}pts] ({estat})")
1208
+ continue
1209
+ click.echo(f" {e} (shard not found)")
1210
+ elif isinstance(e, dict):
1211
+ eid = e.get("id", "?")
1212
+ etitle = e.get("title", "?")
1213
+ epts = e.get("points", "?")
1214
+ estat = e.get("status", "?")
1215
+ click.echo(f" {eid}: {etitle} [{epts}pts] ({estat})")
1216
+
1217
+ standalone_stories = init_data.get("standalone_stories", [])
1218
+ if standalone_stories:
1219
+ click.echo(f"\nStandalone Stories ({len(standalone_stories)}):")
1220
+ for s in standalone_stories:
1221
+ sid = s.get("id", "?")
1222
+ stitle = s.get("title", "?")
1223
+ spts = s.get("points", "?")
1224
+ sstat = s.get("status", "?")
1225
+ click.echo(f" {sid}: {stitle} [{spts}pts] ({sstat})")
1226
+
1227
+
1228
+ @initiative.command("cancel")
1229
+ @click.argument("name")
1230
+ @click.option("--jira", is_flag=True, help="Also cancel epics in Jira")
1231
+ @click.option("--dry-run", is_flag=True, help="Show what would be done without making changes")
1232
+ def initiative_cancel(name: str, jira: bool, dry_run: bool):
1233
+ """Cancel an initiative and all its epics/stories.
1234
+
1235
+ Sets the initiative status to 'canceled' and cancels all epics and stories
1236
+ within it.
1237
+
1238
+ \b
1239
+ Arguments:
1240
+ NAME - Initiative slug (e.g., benchmark-reliability, technical-debt)
1241
+
1242
+ \b
1243
+ Examples:
1244
+ pf sprint initiative cancel technical-debt --dry-run
1245
+ pf sprint initiative cancel technical-debt
1246
+ pf sprint initiative cancel technical-debt --jira
1247
+ """
1248
+ import yaml
1249
+
1250
+ from pf.common.config import get_project_root
1251
+
1252
+ root = get_project_root()
1253
+ sprint_dir = root / "sprint"
1254
+ init_file = sprint_dir / f"initiative-{name}.yaml"
1255
+
1256
+ if not init_file.exists():
1257
+ raise click.ClickException(f"Initiative not found: {name}\n Expected: {init_file}")
1258
+
1259
+ with open(init_file) as f:
1260
+ init_data = yaml.safe_load(f.read())
1261
+
1262
+ if not init_data:
1263
+ raise click.ClickException(f"Empty initiative file: {init_file}")
1264
+
1265
+ init_name = init_data.get("name", name)
1266
+ epics = init_data.get("epics", [])
1267
+ standalone_stories = init_data.get("standalone_stories", [])
1268
+
1269
+ # Collect Jira keys for warning
1270
+ jira_keys = []
1271
+ epic_count = 0
1272
+ story_count = 0
1273
+
1274
+ for e in epics:
1275
+ if isinstance(e, str):
1276
+ shard = _epic_shard_path(sprint_dir, e)
1277
+ if shard.exists():
1278
+ with open(shard) as sf:
1279
+ edata = yaml.safe_load(sf.read())
1280
+ if edata:
1281
+ epic_count += 1
1282
+ if edata.get("jira"):
1283
+ jira_keys.append(edata["jira"])
1284
+ story_count += len(edata.get("stories", []))
1285
+ elif isinstance(e, dict):
1286
+ epic_count += 1
1287
+ if e.get("jira"):
1288
+ jira_keys.append(e["jira"])
1289
+ story_count += len(e.get("stories", []))
1290
+
1291
+ story_count += len(standalone_stories)
1292
+
1293
+ click.echo(f"Initiative: {init_name}")
1294
+ click.echo(f"Epics: {epic_count}")
1295
+ click.echo(f"Stories: {story_count}")
1296
+
1297
+ if jira_keys and not jira:
1298
+ click.echo(f"\nWarning: {len(jira_keys)} epic(s) have Jira keys -- pass --jira to also cancel in Jira")
1299
+ for k in jira_keys:
1300
+ click.echo(f" {k}")
1301
+
1302
+ if dry_run:
1303
+ click.echo(f"\n[DRY-RUN] Would cancel initiative '{init_name}' ({epic_count} epics, {story_count} stories)")
1304
+ return
1305
+
1306
+ # Cancel all epics
1307
+ for _i, e in enumerate(epics):
1308
+ if isinstance(e, str):
1309
+ shard = _epic_shard_path(sprint_dir, e)
1310
+ if shard.exists():
1311
+ with open(shard) as sf:
1312
+ edata = yaml.safe_load(sf.read())
1313
+ if edata:
1314
+ edata["status"] = "canceled"
1315
+ for s in edata.get("stories", []):
1316
+ s["status"] = "canceled"
1317
+ with open(shard, "w") as sf:
1318
+ yaml.dump(edata, sf, default_flow_style=False, sort_keys=False)
1319
+ if jira and edata.get("jira"):
1320
+ _transition_jira(edata["jira"], "Cancelled")
1321
+ elif isinstance(e, dict):
1322
+ e["status"] = "canceled"
1323
+ for s in e.get("stories", []):
1324
+ s["status"] = "canceled"
1325
+ if jira and e.get("jira"):
1326
+ _transition_jira(e["jira"], "Cancelled")
1327
+
1328
+ # Cancel standalone stories
1329
+ for s in standalone_stories:
1330
+ s["status"] = "canceled"
1331
+
1332
+ # Update initiative status
1333
+ init_data["status"] = "canceled"
1334
+
1335
+ with open(init_file, "w") as f:
1336
+ yaml.dump(init_data, f, default_flow_style=False, sort_keys=False)
1337
+
1338
+ click.echo(f"\nCanceled initiative '{init_name}' ({epic_count} epics, {story_count} stories)")
1339
+ if jira and jira_keys:
1340
+ click.echo(f"Transitioned {len(jira_keys)} Jira epic(s) to Cancelled")
1341
+
1342
+
1343
+ # --- Check command (replaces check-story.sh) ---
1344
+
1345
+ @sprint.command()
1346
+ @click.argument("id")
1347
+ def check(id: str):
1348
+ """Check story/epic availability. Returns JSON.
1349
+
1350
+ \b
1351
+ Arguments:
1352
+ ID - Story ID, epic ID, or 'next' for highest priority
1353
+
1354
+ \b
1355
+ Returns JSON with type, details, and availability:
1356
+ type: "story" | "epic" | "next" | "not_found"
1357
+ """
1358
+ import json
1359
+
1360
+ from pf.sprint.loader import (
1361
+ find_epic,
1362
+ load_sprint,
1363
+ )
1364
+ from pf.sprint.work import check_story, get_next_story
1365
+
1366
+ data = load_sprint()
1367
+
1368
+ if id == "next":
1369
+ result = get_next_story()
1370
+ if result.get("available"):
1371
+ story = result["story"]
1372
+ # Find parent epic
1373
+ epic_id = _find_epic_for_story(data, story.get("id", ""))
1374
+ out = {
1375
+ "type": "next",
1376
+ "story": {
1377
+ "id": story.get("id"),
1378
+ "title": story.get("title"),
1379
+ "points": story.get("points", 0),
1380
+ "priority": story.get("priority", "P2"),
1381
+ "workflow": story.get("workflow", "tdd"),
1382
+ "repos": story.get("repos", "pennyfarthing"),
1383
+ "epic_id": epic_id,
1384
+ "acceptance_criteria": story.get("acceptance_criteria", []),
1385
+ },
1386
+ }
1387
+ else:
1388
+ out = {"type": "next", "story": None, "message": "No available stories in backlog"}
1389
+ click.echo(json.dumps(out, indent=2))
1390
+ return
1391
+
1392
+ # Check if it's an epic
1393
+ if data:
1394
+ epic = find_epic(data, id)
1395
+ if epic:
1396
+ available_statuses = {"backlog", "ready", "planning"}
1397
+ available = [
1398
+ s for s in epic.get("stories", [])
1399
+ if s.get("status") in available_statuses
1400
+ ]
1401
+ # Sort by priority
1402
+ priority_order = {"P0": 0, "P1": 1, "P2": 2, "P3": 3}
1403
+ available.sort(key=lambda s: priority_order.get(s.get("priority", "P2"), 2))
1404
+
1405
+ first = available[0] if available else None
1406
+ out = {
1407
+ "type": "epic",
1408
+ "id": str(epic.get("id", id)),
1409
+ "title": epic.get("title", "Unknown"),
1410
+ "available_stories": len(available),
1411
+ }
1412
+ if first:
1413
+ out["first_story"] = {
1414
+ "id": first.get("id"),
1415
+ "title": first.get("title"),
1416
+ "points": first.get("points", 0),
1417
+ "workflow": first.get("workflow", "tdd"),
1418
+ "repos": first.get("repos", "pennyfarthing"),
1419
+ "acceptance_criteria": first.get("acceptance_criteria", []),
1420
+ }
1421
+ else:
1422
+ out["first_story"] = None
1423
+ out["message"] = "No available stories in this epic"
1424
+ click.echo(json.dumps(out, indent=2))
1425
+ return
1426
+
1427
+ # Check if it's a story
1428
+ result = check_story(id)
1429
+ story = result.get("story")
1430
+ if story:
1431
+ epic_id = _find_epic_for_story(data, story.get("id", ""))
1432
+ out = {
1433
+ "type": "story",
1434
+ "id": story.get("id", id),
1435
+ "title": story.get("title", "Unknown"),
1436
+ "points": story.get("points", 0),
1437
+ "workflow": story.get("workflow", "tdd"),
1438
+ "status": story.get("status", "backlog"),
1439
+ "assigned_to": story.get("assigned_to", ""),
1440
+ "epic_id": epic_id,
1441
+ "repos": story.get("repos", "pennyfarthing"),
1442
+ "available": result.get("available", False),
1443
+ "acceptance_criteria": story.get("acceptance_criteria", []),
1444
+ }
1445
+ click.echo(json.dumps(out, indent=2))
1446
+ return
1447
+
1448
+ # Not found
1449
+ click.echo(json.dumps({
1450
+ "type": "not_found",
1451
+ "id": id,
1452
+ "message": "Story or epic not found in current sprint",
1453
+ }, indent=2))
1454
+
1455
+
1456
+ def _find_epic_for_story(data: dict | None, story_id: str) -> str:
1457
+ """Find the parent epic ID for a story."""
1458
+ if not data or "epics" not in data:
1459
+ return ""
1460
+ for epic in data["epics"]:
1461
+ if not isinstance(epic, dict):
1462
+ continue
1463
+ for s in epic.get("stories", []):
1464
+ if s.get("id") == story_id:
1465
+ return str(epic.get("id", ""))
1466
+ return ""
1467
+
1468
+
1469
+ # --- Info command (replaces sprint-info.sh) ---
1470
+
1471
+ @sprint.command()
1472
+ def info():
1473
+ """Output sprint info as JSON.
1474
+
1475
+ \b
1476
+ Returns sprint header fields plus computed story point totals.
1477
+ """
1478
+ import json
1479
+
1480
+ from pf.sprint.loader import get_all_stories, get_sprint_info
1481
+
1482
+ sprint_data = get_sprint_info()
1483
+ stories = get_all_stories()
1484
+
1485
+ remaining = sum(
1486
+ s.get("points", 0) or 0
1487
+ for s in stories
1488
+ if s.get("status") in ("backlog", "planning", "ready", None)
1489
+ )
1490
+ in_progress = sum(
1491
+ s.get("points", 0) or 0
1492
+ for s in stories
1493
+ if s.get("status") == "in_progress"
1494
+ )
1495
+
1496
+ result = {str(k): str(v) if hasattr(v, 'isoformat') else v for k, v in sprint_data.items()}
1497
+ result["remaining"] = remaining
1498
+ result["inProgress"] = in_progress
1499
+
1500
+ click.echo(json.dumps(result))
1501
+
1502
+
1503
+ # --- Metrics command (replaces sprint-metrics.sh) ---
1504
+
1505
+ @sprint.command()
1506
+ @click.option("--json", "output_json", is_flag=True, help="Output in JSON format")
1507
+ def metrics(output_json: bool):
1508
+ """Display sprint metrics and progress.
1509
+
1510
+ Shows points, stories, timeline, and velocity tracking.
1511
+ """
1512
+ import json
1513
+ from datetime import date, datetime
1514
+
1515
+ from pf.sprint.loader import get_all_stories, get_sprint_info
1516
+
1517
+ sprint_data = get_sprint_info()
1518
+ stories = get_all_stories()
1519
+
1520
+ if not sprint_data:
1521
+ click.echo("No sprint data available")
1522
+ return
1523
+
1524
+ sprint_name = sprint_data.get("name", "Unknown")
1525
+ goal = sprint_data.get("goal", "")
1526
+ start_date_str = sprint_data.get("start_date", "")
1527
+ end_date_str = sprint_data.get("end_date", "")
1528
+
1529
+ # Count stories/points by status
1530
+ done_stories = [s for s in stories if s.get("status") in ("done", "completed")]
1531
+ wip_stories = [s for s in stories if s.get("status") == "in_progress"]
1532
+ backlog_stories = [s for s in stories if s.get("status") in ("backlog", "planning", "ready", None)]
1533
+
1534
+ done_pts = sum(s.get("points", 0) or 0 for s in done_stories)
1535
+ wip_pts = sum(s.get("points", 0) or 0 for s in wip_stories)
1536
+ backlog_pts = sum(s.get("points", 0) or 0 for s in backlog_stories)
1537
+ total_pts = done_pts + wip_pts + backlog_pts
1538
+
1539
+ # Date calculations
1540
+ today = date.today()
1541
+ try:
1542
+ start_date = datetime.strptime(str(start_date_str), "%Y-%m-%d").date()
1543
+ end_date = datetime.strptime(str(end_date_str), "%Y-%m-%d").date()
1544
+ except (ValueError, TypeError):
1545
+ start_date = today
1546
+ end_date = today
1547
+
1548
+ total_days = (end_date - start_date).days or 1
1549
+ days_elapsed = max(0, (today - start_date).days)
1550
+ days_remaining = max(0, (end_date - today).days)
1551
+
1552
+ pct_complete = (done_pts * 100 // total_pts) if total_pts > 0 else 0
1553
+ pct_time = (days_elapsed * 100 // total_days) if total_days > 0 else 0
1554
+
1555
+ velocity_target = sprint_data.get("velocity_target", total_pts)
1556
+ expected_pts = (velocity_target * days_elapsed // total_days) if total_days > 0 else 0
1557
+
1558
+ if output_json:
1559
+ click.echo(json.dumps({
1560
+ "sprint": sprint_name,
1561
+ "dates": {
1562
+ "start": str(start_date_str),
1563
+ "end": str(end_date_str),
1564
+ "today": str(today),
1565
+ },
1566
+ "points": {
1567
+ "total": total_pts,
1568
+ "completed": done_pts,
1569
+ "in_progress": wip_pts,
1570
+ "backlog": backlog_pts,
1571
+ "velocity_target": velocity_target,
1572
+ },
1573
+ "stories": {
1574
+ "total": len(stories),
1575
+ "done": len(done_stories),
1576
+ "in_progress": len(wip_stories),
1577
+ "backlog": len(backlog_stories),
1578
+ },
1579
+ "progress": {
1580
+ "percent_complete": pct_complete,
1581
+ "percent_time": pct_time,
1582
+ "days_elapsed": days_elapsed,
1583
+ "days_remaining": days_remaining,
1584
+ "total_days": total_days,
1585
+ },
1586
+ "velocity": {
1587
+ "expected_points": expected_pts,
1588
+ "actual_points": done_pts,
1589
+ "on_track": done_pts >= expected_pts,
1590
+ },
1591
+ }, indent=2))
1592
+ return
1593
+
1594
+ # Human-readable output
1595
+ click.echo("")
1596
+ click.echo(f" Sprint: {sprint_name}")
1597
+ click.echo(f" Goal: {goal}")
1598
+ click.echo("")
1599
+ click.echo(f" Timeline: {start_date_str} to {end_date_str} (Day {days_elapsed}/{total_days}, {days_remaining} remaining)")
1600
+ click.echo("")
1601
+ click.echo(f" Points: {done_pts} done / {wip_pts} WIP / {backlog_pts} backlog = {total_pts} total ({pct_complete}%)")
1602
+ click.echo(f" Stories: {len(done_stories)} done / {len(wip_stories)} WIP / {len(backlog_stories)} backlog = {len(stories)} total")
1603
+ click.echo("")
1604
+ click.echo(f" Velocity: {done_pts}/{expected_pts} expected ({velocity_target} target)")
1605
+ if done_pts >= expected_pts:
1606
+ click.echo(" Status: On track")
1607
+ else:
1608
+ click.echo(" Status: Behind schedule")
1609
+
1610
+
1611
+ # --- Story field command (replaces get-story-field.sh) ---
1612
+
1613
+ @story.command("field")
1614
+ @click.argument("story_id")
1615
+ @click.argument("field_name")
1616
+ def story_field(story_id: str, field_name: str):
1617
+ """Get a field value from a story.
1618
+
1619
+ \b
1620
+ Arguments:
1621
+ STORY_ID - Story ID (e.g., 79-1 or MSSCI-12345)
1622
+ FIELD_NAME - Field to extract (e.g., workflow, status, points)
1623
+
1624
+ Returns the field value or "null" if not found.
1625
+ """
1626
+ from pf.sprint.loader import get_story_by_id, get_story_field, load_sprint
1627
+
1628
+ # Default values for common fields
1629
+ defaults = {
1630
+ "workflow": "tdd",
1631
+ "status": "backlog",
1632
+ "repos": "pennyfarthing",
1633
+ }
1634
+
1635
+ # Try get_story_field first (works with epic-story format like "79-1")
1636
+ data = load_sprint()
1637
+ if data:
1638
+ value = get_story_field(data, story_id, field_name)
1639
+ if value is not None:
1640
+ click.echo(str(value))
1641
+ return
1642
+
1643
+ # Fallback: try direct story lookup (works with Jira keys)
1644
+ story = get_story_by_id(story_id)
1645
+ if story:
1646
+ value = story.get(field_name)
1647
+ if value is not None:
1648
+ click.echo(str(value))
1649
+ return
1650
+
1651
+ # Return default or null
1652
+ click.echo(defaults.get(field_name, "null"))
1653
+
1654
+
1655
+ # --- Epic field command (replaces get-epic-field.sh) ---
1656
+
1657
+ @epic.command("field")
1658
+ @click.argument("epic_id")
1659
+ @click.argument("field_name")
1660
+ def epic_field(epic_id: str, field_name: str):
1661
+ """Get a field value from an epic.
1662
+
1663
+ \b
1664
+ Arguments:
1665
+ EPIC_ID - Epic ID (e.g., epic-79 or 79)
1666
+ FIELD_NAME - Field to extract (e.g., jira, title, status)
1667
+
1668
+ Returns the field value or "null" if not found.
1669
+ """
1670
+ from pf.sprint.loader import find_epic, load_sprint
1671
+
1672
+ data = load_sprint()
1673
+ if not data:
1674
+ click.echo("null")
1675
+ return
1676
+
1677
+ epic = find_epic(data, epic_id)
1678
+ if not epic:
1679
+ click.echo("null")
1680
+ return
1681
+
1682
+ value = epic.get(field_name)
1683
+ if value is not None:
1684
+ click.echo(str(value).rstrip())
1685
+ else:
1686
+ click.echo("null")
1687
+
1688
+
1689
+ # --- Future command (replaces list-future.sh) ---
1690
+
1691
+ @sprint.command()
1692
+ @click.argument("epic_id", required=False)
1693
+ def future(epic_id: str | None):
1694
+ """Show future work initiatives and epics.
1695
+
1696
+ \b
1697
+ Arguments:
1698
+ EPIC_ID - Optional epic ID to show detailed stories (e.g., epic-55)
1699
+
1700
+ \b
1701
+ Examples:
1702
+ pf sprint future # Show all initiatives
1703
+ pf sprint future epic-55 # Show stories for specific epic
1704
+ """
1705
+ import yaml
1706
+
1707
+ from pf.common.config import get_project_root
1708
+
1709
+ root = get_project_root()
1710
+ sprint_dir = root / "sprint"
1711
+
1712
+ init_files = sorted(sprint_dir.glob("initiative-*.yaml"))
1713
+ if not init_files:
1714
+ click.echo("No future initiatives found.")
1715
+ return
1716
+
1717
+ # If specific epic requested, show detailed view
1718
+ if epic_id:
1719
+ _show_future_epic_detail(epic_id, init_files, sprint_dir)
1720
+ return
1721
+
1722
+ # Default: show initiative summary
1723
+ click.echo("# Future Work - Available for Promotion")
1724
+ click.echo("")
1725
+
1726
+ total_epics = 0
1727
+ total_points = 0
1728
+
1729
+ for init_file in init_files:
1730
+ with open(init_file) as f:
1731
+ init_data = yaml.safe_load(f.read())
1732
+ if not init_data:
1733
+ continue
1734
+
1735
+ init_name = init_data.get("name", init_file.stem)
1736
+ init_status = init_data.get("status", "planning")
1737
+ blocked_by = init_data.get("blocked_by")
1738
+ init_points = init_data.get("total_points", 0)
1739
+
1740
+ if init_status == "ready":
1741
+ status_tag = "[READY]"
1742
+ elif blocked_by:
1743
+ status_tag = "[BLOCKED]"
1744
+ else:
1745
+ status_tag = f"[{init_status}]"
1746
+
1747
+ click.echo(f"## {init_name} {status_tag}")
1748
+ click.echo(f"**Total:** {init_points} points")
1749
+ if blocked_by:
1750
+ click.echo(f"**Blocked:** {blocked_by}")
1751
+ click.echo("")
1752
+
1753
+ click.echo("| Epic | Title | Pts | Pri | Status |")
1754
+ click.echo("|------|-------|-----|-----|--------|")
1755
+
1756
+ epics = init_data.get("epics", [])
1757
+ for e in epics:
1758
+ edata = _resolve_epic_ref(e, sprint_dir)
1759
+ if not edata:
1760
+ continue
1761
+ eid = edata.get("id", "?")
1762
+ etitle = edata.get("title", "?")
1763
+ if len(etitle) > 40:
1764
+ etitle = etitle[:37] + "..."
1765
+ epts = edata.get("points", "?")
1766
+ epri = edata.get("priority", "P2")
1767
+ estat = edata.get("status", "planning")
1768
+ click.echo(f"| {eid} | {etitle} | {epts} | {epri} | {estat} |")
1769
+ total_epics += 1
1770
+ total_points += edata.get("points", 0) or 0
1771
+
1772
+ click.echo("")
1773
+
1774
+ click.echo("---")
1775
+ click.echo(f"**Summary:** {total_epics} epics, {total_points} points total")
1776
+ click.echo("")
1777
+ click.echo("To see epic details: `pf sprint future epic-55`")
1778
+ click.echo("To promote an epic: `pf sprint epic promote epic-55`")
1779
+
1780
+
1781
+ def _resolve_epic_ref(ref, sprint_dir) -> dict | None:
1782
+ """Resolve an epic reference (string ref or inline dict) to a dict."""
1783
+ import yaml
1784
+
1785
+ if isinstance(ref, dict):
1786
+ return ref
1787
+ if isinstance(ref, str):
1788
+ shard = _epic_shard_path(sprint_dir, ref)
1789
+ if shard.exists():
1790
+ with open(shard) as f:
1791
+ return yaml.safe_load(f.read())
1792
+ return None
1793
+
1794
+
1795
+ def _show_future_epic_detail(epic_id: str, init_files, sprint_dir):
1796
+ """Show detailed view of a specific future epic."""
1797
+ import yaml
1798
+
1799
+ for init_file in init_files:
1800
+ with open(init_file) as f:
1801
+ init_data = yaml.safe_load(f.read())
1802
+ if not init_data:
1803
+ continue
1804
+
1805
+ for e in init_data.get("epics", []):
1806
+ edata = _resolve_epic_ref(e, sprint_dir)
1807
+ if not edata:
1808
+ continue
1809
+ eid = str(edata.get("id", ""))
1810
+ if epic_id not in (eid, eid.replace("epic-", ""), f"epic-{epic_id}"):
1811
+ continue
1812
+
1813
+ click.echo(f"# Epic Details: {eid}")
1814
+ click.echo("")
1815
+ click.echo(f"**Title:** {edata.get('title', '?')}")
1816
+ click.echo(f"**Points:** {edata.get('points', '?')} | **Priority:** {edata.get('priority', 'P2')} | **Status:** {edata.get('status', 'planning')}")
1817
+ click.echo("")
1818
+ desc = edata.get("description", "No description")
1819
+ if desc:
1820
+ click.echo("**Description:**")
1821
+ for line in str(desc).strip().split("\n")[:5]:
1822
+ click.echo(line)
1823
+ click.echo("")
1824
+
1825
+ stories = edata.get("stories", [])
1826
+ if stories:
1827
+ click.echo("## Stories")
1828
+ click.echo("")
1829
+ click.echo("| ID | Title | Pts | Pri | Status |")
1830
+ click.echo("|----|-------|-----|-----|--------|")
1831
+ for s in stories:
1832
+ stitle = s.get("title", "?")
1833
+ if len(stitle) > 45:
1834
+ stitle = stitle[:42] + "..."
1835
+ click.echo(f"| {s.get('id', '?')} | {stitle} | {s.get('points', '?')} | {s.get('priority', 'P1')} | {s.get('status', 'planning')} |")
1836
+ click.echo("")
1837
+
1838
+ click.echo("---")
1839
+ click.echo(f"To promote this epic: `pf sprint epic promote {eid}`")
1840
+ return
1841
+
1842
+ raise click.ClickException(f"Epic {epic_id} not found in future initiatives")
1843
+
1844
+
1845
+ # --- New sprint command (replaces new-sprint.sh) ---
1846
+
1847
+ @sprint.command("new")
1848
+ @click.argument("sprint_yyww")
1849
+ @click.argument("jira_id", type=int)
1850
+ @click.argument("start_date")
1851
+ @click.argument("end_date")
1852
+ @click.argument("goal")
1853
+ @click.option("--dry-run", is_flag=True, help="Show what would be done without making changes")
1854
+ def new_sprint(sprint_yyww: str, jira_id: int, start_date: str, end_date: str, goal: str, dry_run: bool):
1855
+ """Initialize a new sprint.
1856
+
1857
+ \b
1858
+ Arguments:
1859
+ SPRINT_YYWW Sprint identifier in YYWW format (e.g., 2607)
1860
+ JIRA_ID Jira sprint ID number (e.g., 278)
1861
+ START_DATE Sprint start date YYYY-MM-DD
1862
+ END_DATE Sprint end date YYYY-MM-DD
1863
+ GOAL Sprint goal (quoted string)
1864
+
1865
+ \b
1866
+ Examples:
1867
+ pf sprint new 2607 278 2026-02-16 2026-03-01 "Performance and polish"
1868
+ """
1869
+ from pf.common.config import get_project_root
1870
+
1871
+ root = get_project_root()
1872
+ sprint_file = root / "sprint" / "current-sprint.yaml"
1873
+ archive_file = root / "sprint" / "archive" / f"sprint-{sprint_yyww}-completed.yaml"
1874
+
1875
+ # Warn if current sprint is active
1876
+ if sprint_file.exists():
1877
+ import yaml
1878
+
1879
+ with open(sprint_file) as f:
1880
+ existing = yaml.safe_load(f.read())
1881
+ if existing and existing.get("sprint", {}).get("status") == "active":
1882
+ click.echo("Warning: Current sprint is still active!")
1883
+ click.echo("Current sprint file will be overwritten.")
1884
+ if not click.confirm("Continue?"):
1885
+ click.echo("Aborted.")
1886
+ return
1887
+
1888
+ if dry_run:
1889
+ click.echo(f"[DRY-RUN] Would initialize sprint TO Sprint {sprint_yyww}")
1890
+ click.echo(f" Jira ID: {jira_id}")
1891
+ click.echo(f" Dates: {start_date} to {end_date}")
1892
+ click.echo(f" Goal: {goal}")
1893
+ return
1894
+
1895
+ # Create sprint file using write_sprint for consistency
1896
+ from pf.sprint.yaml_io import write_sprint
1897
+
1898
+ sprint_data = {
1899
+ "sprint": {
1900
+ "name": f"TO Sprint {sprint_yyww}",
1901
+ "jira_sprint_id": jira_id,
1902
+ "jira_sprint_name": f"TO Sprint {sprint_yyww}",
1903
+ "goal": goal,
1904
+ "start_date": start_date,
1905
+ "end_date": end_date,
1906
+ "status": "active",
1907
+ },
1908
+ "epics": [],
1909
+ }
1910
+ write_sprint(sprint_file, sprint_data)
1911
+ click.echo(f"Created {sprint_file}")
1912
+
1913
+ # Create archive file
1914
+ from datetime import date
1915
+
1916
+ archive_content = f"""# Sprint TO Sprint {sprint_yyww} - Completed Stories
1917
+ # Jira Sprint ID: {jira_id}
1918
+ # Archived: {date.today()}
1919
+
1920
+ sprint:
1921
+ name: "TO Sprint {sprint_yyww}"
1922
+ jira_sprint_id: {jira_id}
1923
+ jira_sprint_name: "TO Sprint {sprint_yyww}"
1924
+ goal: {goal}
1925
+
1926
+ completed:
1927
+ # Completed stories will be appended here by pf sprint archive
1928
+ """
1929
+ archive_file.parent.mkdir(parents=True, exist_ok=True)
1930
+ archive_file.write_text(archive_content)
1931
+ click.echo(f"Created {archive_file}")
1932
+
1933
+ click.echo("")
1934
+ click.echo("New sprint initialized:")
1935
+ click.echo(f" Name: TO Sprint {sprint_yyww}")
1936
+ click.echo(f" Jira ID: {jira_id}")
1937
+ click.echo(f" Dates: {start_date} to {end_date}")
1938
+ click.echo(f" Goal: {goal}")
1939
+ click.echo("")
1940
+ click.echo("Next steps:")
1941
+ click.echo(" 1. Add epics: pf sprint epic promote <epic-id>")
1942
+ click.echo(" 2. Check status: pf sprint status")
1943
+
1944
+
1945
+ # --- Standalone command ---
1946
+
1947
+ @sprint.command()
1948
+ @click.argument("title", required=False)
1949
+ @click.argument("points", required=False, type=int)
1950
+ def standalone(title: str | None, points: int | None):
1951
+ """Wrap current changes into a standalone Jira story, branch, PR, and merge.
1952
+
1953
+ This is an agent-executed workflow. Use /standalone to run it interactively.
1954
+ """
1955
+ click.echo("The standalone command is an agent-executed workflow.")
1956
+ click.echo("Use /standalone to run it interactively with full agent support.")
1957
+
1958
+
1959
+ # --- Backwards compatibility aliases (hidden) ---
1960
+
1961
+ # Hidden alias: sprint story-add -> sprint story add
1962
+ sprint.add_command(story_add_command, "story-add")
1963
+ sprint.commands["story-add"].hidden = True
1964
+
1965
+ # Hidden alias: sprint story-update -> sprint story update
1966
+ sprint.add_command(story_update_command, "story-update")
1967
+ sprint.commands["story-update"].hidden = True
1968
+
1969
+ # Hidden alias: sprint archive-epic -> sprint epic archive
1970
+ @sprint.command("archive-epic", hidden=True)
1971
+ @click.argument("epic_id", required=False)
1972
+ @click.option("--dry-run", is_flag=True)
1973
+ @click.option("--jira", is_flag=True)
1974
+ def archive_epic_compat(epic_id, dry_run, jira):
1975
+ """(Deprecated) Use 'sprint epic archive' instead."""
1976
+ ctx = click.get_current_context()
1977
+ ctx.invoke(epic_archive, epic_id=epic_id, dry_run=dry_run, jira=jira)
1978
+
1979
+ # Hidden alias: sprint import-epic -> sprint epic import
1980
+ @sprint.command("import-epic", hidden=True)
1981
+ @click.argument("epics_file")
1982
+ @click.argument("initiative_name", required=False)
1983
+ @click.option("--marker", default="imported")
1984
+ @click.option("--dry-run", is_flag=True)
1985
+ def import_epic_compat(epics_file, initiative_name, marker, dry_run):
1986
+ """(Deprecated) Use 'sprint epic import' instead."""
1987
+ ctx = click.get_current_context()
1988
+ ctx.invoke(epic_import, epics_file=epics_file, initiative_name=initiative_name, marker=marker, dry_run=dry_run)
1989
+
1990
+ # Hidden alias: sprint remove-epic -> sprint epic remove
1991
+ @sprint.command("remove-epic", hidden=True)
1992
+ @click.argument("epic_id")
1993
+ @click.option("--dry-run", is_flag=True)
1994
+ def remove_epic_compat(epic_id, dry_run):
1995
+ """(Deprecated) Use 'sprint epic remove' instead."""
1996
+ ctx = click.get_current_context()
1997
+ ctx.invoke(epic_remove, epic_id=epic_id, dry_run=dry_run)
1998
+
1999
+ # Hidden alias: sprint epic-add -> sprint epic add
2000
+ sprint.add_command(epic_add_command, "epic-add")
2001
+ sprint.commands["epic-add"].hidden = True
2002
+
2003
+
2004
+ # Register validate command from validate_cmd module
2005
+ from pf.sprint.validate_cmd import validate_command # noqa: E402
2006
+
2007
+ sprint.add_command(validate_command)
2008
+
2009
+
2010
+ # For backwards compatibility when running as module
2011
+ def main(args: list[str] | None = None) -> int:
2012
+ """Entry point for backwards compatibility."""
2013
+ try:
2014
+ sprint(args)
2015
+ return 0
2016
+ except SystemExit as e:
2017
+ return e.code if isinstance(e.code, int) else 0
2018
+
2019
+
2020
+ if __name__ == "__main__":
2021
+ sprint()