@pennyfarthing/core 11.4.0 → 12.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (1168) hide show
  1. package/README.md +9 -9
  2. package/package.json +3 -9
  3. package/packages/core/dist/cli/commands/command.js +9 -9
  4. package/packages/core/dist/cli/commands/command.js.map +1 -1
  5. package/packages/core/dist/cli/commands/cyclist.js +1 -1
  6. package/packages/core/dist/cli/commands/cyclist.js.map +1 -1
  7. package/packages/core/dist/cli/commands/cyclist.test.js +4 -4
  8. package/packages/core/dist/cli/commands/cyclist.test.js.map +1 -1
  9. package/packages/core/dist/cli/commands/doctor-legacy.test.js +2 -1
  10. package/packages/core/dist/cli/commands/doctor-legacy.test.js.map +1 -1
  11. package/packages/core/dist/cli/commands/doctor-persona-config-false-negative.test.d.ts +22 -0
  12. package/packages/core/dist/cli/commands/doctor-persona-config-false-negative.test.d.ts.map +1 -0
  13. package/packages/core/dist/cli/commands/doctor-persona-config-false-negative.test.js +161 -0
  14. package/packages/core/dist/cli/commands/doctor-persona-config-false-negative.test.js.map +1 -0
  15. package/packages/core/dist/cli/commands/doctor.d.ts.map +1 -1
  16. package/packages/core/dist/cli/commands/doctor.js +190 -34
  17. package/packages/core/dist/cli/commands/doctor.js.map +1 -1
  18. package/packages/core/dist/cli/commands/e2e-upgrade.test.js +1 -1
  19. package/packages/core/dist/cli/commands/e2e-upgrade.test.js.map +1 -1
  20. package/packages/core/dist/cli/commands/hook-chaining.test.js +1 -1
  21. package/packages/core/dist/cli/commands/hook-chaining.test.js.map +1 -1
  22. package/packages/core/dist/cli/commands/init.d.ts.map +1 -1
  23. package/packages/core/dist/cli/commands/init.js +47 -18
  24. package/packages/core/dist/cli/commands/init.js.map +1 -1
  25. package/packages/core/dist/cli/commands/pyproject-install.test.js +1 -1
  26. package/packages/core/dist/cli/commands/pyproject-install.test.js.map +1 -1
  27. package/packages/core/dist/cli/commands/skill.js +9 -9
  28. package/packages/core/dist/cli/commands/skill.js.map +1 -1
  29. package/packages/core/dist/cli/commands/stale-artifacts-cleanup.test.d.ts +17 -0
  30. package/packages/core/dist/cli/commands/stale-artifacts-cleanup.test.d.ts.map +1 -0
  31. package/packages/core/dist/cli/commands/stale-artifacts-cleanup.test.js +470 -0
  32. package/packages/core/dist/cli/commands/stale-artifacts-cleanup.test.js.map +1 -0
  33. package/packages/core/dist/cli/commands/theme.js +3 -3
  34. package/packages/core/dist/cli/commands/theme.js.map +1 -1
  35. package/packages/core/dist/cli/commands/uninstall.js +1 -1
  36. package/packages/core/dist/cli/commands/uninstall.js.map +1 -1
  37. package/packages/core/dist/cli/commands/update.d.ts.map +1 -1
  38. package/packages/core/dist/cli/commands/update.js +71 -20
  39. package/packages/core/dist/cli/commands/update.js.map +1 -1
  40. package/packages/core/dist/cli/commands/version.js +1 -1
  41. package/packages/core/dist/cli/commands/version.js.map +1 -1
  42. package/packages/core/dist/cli/index.js +2 -30
  43. package/packages/core/dist/cli/index.js.map +1 -1
  44. package/packages/core/dist/cli/utils/constants.d.ts +2 -2
  45. package/packages/core/dist/cli/utils/constants.js +1 -1
  46. package/packages/core/dist/cli/utils/constants.js.map +1 -1
  47. package/packages/core/dist/cli/utils/files.d.ts +1 -1
  48. package/packages/core/dist/cli/utils/files.js +1 -1
  49. package/packages/core/dist/cli/utils/install-helpers.d.ts +25 -0
  50. package/packages/core/dist/cli/utils/install-helpers.d.ts.map +1 -0
  51. package/packages/core/dist/cli/utils/install-helpers.js +162 -0
  52. package/packages/core/dist/cli/utils/install-helpers.js.map +1 -0
  53. package/packages/core/dist/cli/utils/node-modules.d.ts +7 -0
  54. package/packages/core/dist/cli/utils/node-modules.d.ts.map +1 -1
  55. package/packages/core/dist/cli/utils/node-modules.js +39 -0
  56. package/packages/core/dist/cli/utils/node-modules.js.map +1 -1
  57. package/packages/core/dist/cli/utils/settings.d.ts +1 -1
  58. package/packages/core/dist/cli/utils/settings.d.ts.map +1 -1
  59. package/packages/core/dist/cli/utils/settings.js +40 -21
  60. package/packages/core/dist/cli/utils/settings.js.map +1 -1
  61. package/packages/core/dist/cli/utils/stale-artifacts.d.ts +59 -0
  62. package/packages/core/dist/cli/utils/stale-artifacts.d.ts.map +1 -0
  63. package/packages/core/dist/cli/utils/stale-artifacts.js +163 -0
  64. package/packages/core/dist/cli/utils/stale-artifacts.js.map +1 -0
  65. package/packages/core/dist/cli/utils/symlinks.d.ts +4 -0
  66. package/packages/core/dist/cli/utils/symlinks.d.ts.map +1 -1
  67. package/packages/core/dist/cli/utils/symlinks.js +25 -2
  68. package/packages/core/dist/cli/utils/symlinks.js.map +1 -1
  69. package/packages/core/dist/cli/workspace.test.js +4 -37
  70. package/packages/core/dist/cli/workspace.test.js.map +1 -1
  71. package/packages/core/dist/data-source.d.ts +42 -0
  72. package/packages/core/dist/data-source.d.ts.map +1 -0
  73. package/packages/core/dist/data-source.js +12 -0
  74. package/packages/core/dist/data-source.js.map +1 -0
  75. package/packages/core/dist/data-source.test.d.ts +16 -0
  76. package/packages/core/dist/data-source.test.d.ts.map +1 -0
  77. package/packages/core/dist/data-source.test.js +206 -0
  78. package/packages/core/dist/data-source.test.js.map +1 -0
  79. package/packages/core/dist/index.d.ts +1 -0
  80. package/packages/core/dist/index.d.ts.map +1 -1
  81. package/packages/core/dist/index.js.map +1 -1
  82. package/packages/core/dist/public/css/react.css +1 -1
  83. package/packages/core/dist/public/js/react/react.js +39 -39
  84. package/packages/core/dist/scripts/benchmark-integration.d.ts +182 -0
  85. package/packages/core/dist/scripts/benchmark-integration.d.ts.map +1 -0
  86. package/packages/core/dist/scripts/benchmark-integration.js +691 -0
  87. package/packages/core/dist/scripts/benchmark-integration.js.map +1 -0
  88. package/packages/core/dist/scripts/job-fair-aggregator.d.ts +150 -0
  89. package/packages/core/dist/scripts/job-fair-aggregator.d.ts.map +1 -0
  90. package/packages/core/dist/scripts/job-fair-aggregator.js +547 -0
  91. package/packages/core/dist/scripts/job-fair-aggregator.js.map +1 -0
  92. package/packages/core/dist/scripts/theme-detail.test.d.ts.map +1 -0
  93. package/packages/core/dist/scripts/theme-detail.test.js.map +1 -0
  94. package/packages/core/dist/server/api/context.d.ts.map +1 -1
  95. package/packages/core/dist/server/api/context.js +23 -5
  96. package/packages/core/dist/server/api/context.js.map +1 -1
  97. package/packages/core/dist/server/api/index.d.ts +0 -1
  98. package/packages/core/dist/server/api/index.d.ts.map +1 -1
  99. package/packages/core/dist/server/api/index.js +0 -2
  100. package/packages/core/dist/server/api/index.js.map +1 -1
  101. package/packages/core/dist/server/api/settings.d.ts.map +1 -1
  102. package/packages/core/dist/server/api/settings.js +6 -0
  103. package/packages/core/dist/server/api/settings.js.map +1 -1
  104. package/packages/core/dist/server/otlp-receiver.d.ts +2 -4
  105. package/packages/core/dist/server/otlp-receiver.d.ts.map +1 -1
  106. package/packages/core/dist/server/otlp-receiver.js +7 -18
  107. package/packages/core/dist/server/otlp-receiver.js.map +1 -1
  108. package/packages/core/dist/server/otlp-receiver.test.js +0 -27
  109. package/packages/core/dist/server/otlp-receiver.test.js.map +1 -1
  110. package/packages/core/dist/server/server.d.ts +2 -2
  111. package/packages/core/dist/server/server.d.ts.map +1 -1
  112. package/packages/core/dist/server/server.js +20 -5
  113. package/packages/core/dist/server/server.js.map +1 -1
  114. package/packages/core/dist/server/server.test.js +1 -1
  115. package/packages/core/dist/server/server.test.js.map +1 -1
  116. package/packages/core/dist/shared/generate-skill-docs.test.js +2 -2
  117. package/packages/core/dist/shared/skill-search.test.js +2 -2
  118. package/packages/core/dist/workflow/cross-entity-validation.d.ts.map +1 -1
  119. package/packages/core/dist/workflow/cross-entity-validation.js.map +1 -1
  120. package/packages/core/dist/workflow/tandem-workflow-templates.test.js +3 -3
  121. package/packages/core/dist/workflow/tandem-workflow-templates.test.js.map +1 -1
  122. package/packages/core/dist/workflow/workflow-migration.test.js +8 -8
  123. package/packages/core/dist/workflow/workflow-migration.test.js.map +1 -1
  124. package/packages/core/dist/workflow/workflow-schema.js +1 -1
  125. package/packages/core/dist/workflow/workflow-schema.js.map +1 -1
  126. package/packages/core/src/public/App.tsx +1 -4
  127. package/packages/core/src/public/components/DockviewWorkspace.tsx +0 -4
  128. package/packages/core/src/public/components/panels/SettingsPanel.tsx +83 -283
  129. package/packages/core/src/public/components/panels/SprintPanel.tsx +11 -1
  130. package/packages/core/src/public/components/panels/index.ts +0 -1
  131. package/packages/core/src/public/data-source.ts +117 -0
  132. package/packages/core/src/public/hooks/index.ts +0 -3
  133. package/packages/core/src/public/hooks/useAgentLoad.ts +1 -0
  134. package/packages/core/src/public/hooks/useCodeMarkers.ts +1 -0
  135. package/packages/core/src/public/hooks/useComplexity.ts +1 -0
  136. package/packages/core/src/public/hooks/useDataSource.ts +273 -0
  137. package/packages/core/src/public/hooks/useDeadCode.ts +1 -0
  138. package/packages/core/src/public/hooks/useDependencies.ts +1 -0
  139. package/packages/core/src/public/hooks/useDiffs.ts +46 -91
  140. package/packages/core/src/public/hooks/useGitStatus.ts +16 -77
  141. package/packages/core/src/public/hooks/useHealthScore.ts +1 -0
  142. package/packages/core/src/public/hooks/useHotspots.ts +1 -0
  143. package/packages/core/src/public/hooks/usePersona.ts +32 -67
  144. package/packages/core/src/public/hooks/useSprint.ts +23 -75
  145. package/packages/core/src/public/hooks/useStatsStrip.ts +47 -117
  146. package/packages/core/src/public/hooks/useStory.ts +11 -60
  147. package/packages/core/src/public/hooks/useTodos.ts +8 -61
  148. package/packages/core/src/public/images/cyclist-tandem-source.png +0 -0
  149. package/packages/core/src/public/index.ts +5 -0
  150. package/packages/core/src/public/styles/tailwind.css +15 -101
  151. package/packages/core/src/public/utils/slash-commands.ts +0 -4
  152. package/pennyfarthing-dist/agents/architect.md +8 -0
  153. package/pennyfarthing-dist/agents/ba.md +8 -0
  154. package/pennyfarthing-dist/agents/dev.md +10 -2
  155. package/pennyfarthing-dist/agents/devops.md +8 -0
  156. package/pennyfarthing-dist/agents/orchestrator.md +8 -0
  157. package/pennyfarthing-dist/agents/pm.md +8 -0
  158. package/pennyfarthing-dist/agents/reviewer.md +12 -4
  159. package/pennyfarthing-dist/agents/sm-setup.md +7 -7
  160. package/pennyfarthing-dist/agents/sm.md +20 -12
  161. package/pennyfarthing-dist/agents/tea.md +10 -2
  162. package/pennyfarthing-dist/agents/tech-writer.md +8 -0
  163. package/pennyfarthing-dist/agents/ux-designer.md +8 -0
  164. package/pennyfarthing-dist/commands/pf-architect.md +1 -1
  165. package/pennyfarthing-dist/commands/pf-ba.md +1 -1
  166. package/pennyfarthing-dist/commands/pf-dev.md +1 -1
  167. package/pennyfarthing-dist/commands/pf-devops.md +1 -1
  168. package/pennyfarthing-dist/commands/pf-health-check.md +1 -1
  169. package/pennyfarthing-dist/commands/pf-help.md +3 -2
  170. package/pennyfarthing-dist/commands/pf-orchestrator.md +1 -1
  171. package/pennyfarthing-dist/commands/pf-pm.md +1 -1
  172. package/pennyfarthing-dist/commands/pf-reviewer.md +1 -1
  173. package/pennyfarthing-dist/commands/pf-setup.md +4 -4
  174. package/pennyfarthing-dist/commands/pf-sm.md +1 -1
  175. package/pennyfarthing-dist/commands/pf-standalone.md +60 -0
  176. package/pennyfarthing-dist/commands/pf-tea.md +1 -1
  177. package/pennyfarthing-dist/commands/pf-tech-writer.md +1 -1
  178. package/pennyfarthing-dist/commands/pf-ux-designer.md +1 -1
  179. package/pennyfarthing-dist/commands/pf-work.md +1 -1
  180. package/pennyfarthing-dist/guides/agent-behavior.md +11 -11
  181. package/pennyfarthing-dist/guides/agent-tag-taxonomy.md +2 -2
  182. package/pennyfarthing-dist/guides/bikelane.md +1 -8
  183. package/pennyfarthing-dist/guides/bikerack.md +94 -0
  184. package/pennyfarthing-dist/guides/hooks.md +1 -1
  185. package/pennyfarthing-dist/guides/what-is-pennyfarthing.md +91 -0
  186. package/pennyfarthing-dist/guides/xml-tags.md +2 -2
  187. package/pennyfarthing-dist/personas/themes/discworld.yaml +14 -12
  188. package/pennyfarthing-dist/personas/themes/firefly.yaml +12 -12
  189. package/pennyfarthing-dist/personas/themes/lord-of-the-rings.yaml +14 -12
  190. package/pennyfarthing-dist/pyproject.toml +6 -2
  191. package/pennyfarthing-dist/scripts/core/agent-session.sh +4 -5
  192. package/pennyfarthing-dist/scripts/core/check-context.sh +10 -5
  193. package/pennyfarthing-dist/scripts/core/phase-check-start.sh +1 -2
  194. package/pennyfarthing-dist/scripts/core/prime.sh +1 -2
  195. package/pennyfarthing-dist/scripts/git/changelog-links.sh +216 -0
  196. package/pennyfarthing-dist/scripts/git/create-feature-branches.sh +1 -2
  197. package/pennyfarthing-dist/scripts/git/git-status-all.sh +1 -2
  198. package/pennyfarthing-dist/scripts/git/install-git-hooks.sh +1 -2
  199. package/pennyfarthing-dist/scripts/git/worktree-manager.sh +1 -2
  200. package/pennyfarthing-dist/scripts/hooks/__pycache__/question_reflector_check.cpython-314.pyc +0 -0
  201. package/pennyfarthing-dist/scripts/hooks/bell-mode-hook.sh +2 -3
  202. package/pennyfarthing-dist/scripts/hooks/context-circuit-breaker.sh +2 -3
  203. package/pennyfarthing-dist/scripts/hooks/context-warning.sh +2 -3
  204. package/pennyfarthing-dist/scripts/hooks/cyclist-pretooluse-hook.sh +2 -3
  205. package/pennyfarthing-dist/scripts/hooks/post-merge.sh +31 -1
  206. package/pennyfarthing-dist/scripts/hooks/pre-commit.sh +6 -4
  207. package/pennyfarthing-dist/scripts/hooks/pre-edit-check.sh +2 -3
  208. package/pennyfarthing-dist/scripts/hooks/pre-push.sh +1 -1
  209. package/pennyfarthing-dist/scripts/hooks/question-reflector-check.sh +2 -3
  210. package/pennyfarthing-dist/scripts/hooks/schema-validation.sh +2 -3
  211. package/pennyfarthing-dist/scripts/hooks/session-start.sh +2 -3
  212. package/pennyfarthing-dist/scripts/hooks/session-stop.sh +2 -3
  213. package/pennyfarthing-dist/scripts/hooks/sprint-yaml-validation.sh +2 -3
  214. package/pennyfarthing-dist/scripts/hooks/welcome-hook.sh +2 -3
  215. package/pennyfarthing-dist/scripts/lib/README.md +0 -1
  216. package/pennyfarthing-dist/scripts/misc/statusline.sh +2 -3
  217. package/pennyfarthing-dist/scripts/misc/uninstall.sh +1 -1
  218. package/pennyfarthing-dist/scripts/portraits/generate-portraits.py +84 -8
  219. package/pennyfarthing-dist/scripts/portraits/generate-tandem-portraits.sh +7 -3
  220. package/pennyfarthing-dist/scripts/tests/test-post-merge-hook.sh +2 -2
  221. package/pennyfarthing-dist/scripts/theme/list-themes.sh +2 -18
  222. package/pennyfarthing-dist/scripts/workflow/finish-story.sh +1 -2
  223. package/pennyfarthing-dist/scripts/workflow/fix-session-phase.sh +1 -2
  224. package/pennyfarthing-dist/scripts/workflow/get-workflow-type.sh +1 -2
  225. package/pennyfarthing-dist/scripts/workflow/list-workflows.sh +1 -2
  226. package/pennyfarthing-dist/scripts/workflow/phase-owner.sh +1 -2
  227. package/pennyfarthing-dist/scripts/workflow/resume-workflow.sh +1 -2
  228. package/pennyfarthing-dist/scripts/workflow/show-workflow.sh +1 -2
  229. package/pennyfarthing-dist/scripts/workflow/start-workflow.sh +1 -2
  230. package/pennyfarthing-dist/scripts/workflow/workflow-status.sh +1 -2
  231. package/pennyfarthing-dist/skills/pf-jira/usage.md +87 -145
  232. package/pennyfarthing-dist/skills/pf-settings/skill.md +41 -3
  233. package/pennyfarthing-dist/skills/pf-settings/usage.md +25 -0
  234. package/pennyfarthing-dist/skills/pf-sprint/skill.md +4 -0
  235. package/pennyfarthing-dist/skills/pf-sprint/usage.md +167 -173
  236. package/pennyfarthing-dist/skills/pf-theme/usage.md +17 -129
  237. package/pennyfarthing-dist/skills/pf-ux-tandem/SKILL.md +194 -0
  238. package/pennyfarthing-dist/skills/pf-workflow/usage.md +51 -128
  239. package/pennyfarthing-dist/skills/skill-registry.yaml +17 -21
  240. package/pennyfarthing-dist/src/pf/CLAUDE.md +213 -0
  241. package/pennyfarthing-dist/src/pf/__init__.py +17 -0
  242. package/pennyfarthing-dist/src/pf/__pycache__/__init__.cpython-311.pyc +0 -0
  243. package/pennyfarthing-dist/src/pf/__pycache__/__init__.cpython-314.pyc +0 -0
  244. package/pennyfarthing-dist/src/pf/__pycache__/bellmode_hook.cpython-314.pyc +0 -0
  245. package/pennyfarthing-dist/src/pf/__pycache__/cli.cpython-311.pyc +0 -0
  246. package/pennyfarthing-dist/src/pf/__pycache__/cli.cpython-314.pyc +0 -0
  247. package/pennyfarthing-dist/src/pf/__pycache__/config.cpython-314.pyc +0 -0
  248. package/pennyfarthing-dist/src/pf/__pycache__/config_migration.cpython-314.pyc +0 -0
  249. package/pennyfarthing-dist/src/pf/__pycache__/context.cpython-311.pyc +0 -0
  250. package/pennyfarthing-dist/src/pf/__pycache__/context.cpython-314.pyc +0 -0
  251. package/pennyfarthing-dist/src/pf/__pycache__/hooks.cpython-314.pyc +0 -0
  252. package/pennyfarthing-dist/src/pf/__pycache__/jira.cpython-314.pyc +0 -0
  253. package/pennyfarthing-dist/src/pf/__pycache__/jira_bidirectional_sync.cpython-314.pyc +0 -0
  254. package/pennyfarthing-dist/src/pf/__pycache__/jira_epic_creation.cpython-314.pyc +0 -0
  255. package/pennyfarthing-dist/src/pf/__pycache__/jira_sync.cpython-314.pyc +0 -0
  256. package/pennyfarthing-dist/src/pf/__pycache__/jira_sync_story.cpython-314.pyc +0 -0
  257. package/pennyfarthing-dist/src/pf/__pycache__/output.cpython-314.pyc +0 -0
  258. package/pennyfarthing-dist/src/pf/__pycache__/patch_mode.cpython-314.pyc +0 -0
  259. package/pennyfarthing-dist/src/pf/__pycache__/pretooluse_hook.cpython-314.pyc +0 -0
  260. package/pennyfarthing-dist/src/pf/__pycache__/schema_validation_hook.cpython-314.pyc +0 -0
  261. package/pennyfarthing-dist/src/pf/__pycache__/session_start_hook.cpython-314.pyc +0 -0
  262. package/pennyfarthing-dist/src/pf/__pycache__/sprint.cpython-314.pyc +0 -0
  263. package/pennyfarthing-dist/src/pf/__pycache__/workflow.cpython-311.pyc +0 -0
  264. package/pennyfarthing-dist/src/pf/__pycache__/workflow.cpython-314.pyc +0 -0
  265. package/pennyfarthing-dist/src/pf/bc/__pycache__/__init__.cpython-311.pyc +0 -0
  266. package/pennyfarthing-dist/src/pf/bc/__pycache__/__init__.cpython-314.pyc +0 -0
  267. package/pennyfarthing-dist/src/pf/bc/__pycache__/cli.cpython-311.pyc +0 -0
  268. package/pennyfarthing-dist/src/pf/bc/__pycache__/cli.cpython-314.pyc +0 -0
  269. package/pennyfarthing-dist/src/pf/bc/__pycache__/focus.cpython-311.pyc +0 -0
  270. package/pennyfarthing-dist/src/pf/bc/__pycache__/focus.cpython-314.pyc +0 -0
  271. package/pennyfarthing-dist/src/pf/bc/__pycache__/split.cpython-314.pyc +0 -0
  272. package/pennyfarthing-dist/src/pf/bc/cli.py +240 -0
  273. package/pennyfarthing-dist/src/pf/bc/focus.py +332 -0
  274. package/pennyfarthing-dist/src/pf/bikerack/__pycache__/__init__.cpython-311.pyc +0 -0
  275. package/pennyfarthing-dist/src/pf/bikerack/__pycache__/__init__.cpython-314.pyc +0 -0
  276. package/pennyfarthing-dist/src/pf/bikerack/__pycache__/__main__.cpython-314.pyc +0 -0
  277. package/pennyfarthing-dist/src/pf/bikerack/__pycache__/audit_log_panel.cpython-314.pyc +0 -0
  278. package/pennyfarthing-dist/src/pf/bikerack/__pycache__/background_panel.cpython-314.pyc +0 -0
  279. package/pennyfarthing-dist/src/pf/bikerack/__pycache__/base_panel.cpython-314.pyc +0 -0
  280. package/pennyfarthing-dist/src/pf/bikerack/__pycache__/changed_panel.cpython-314.pyc +0 -0
  281. package/pennyfarthing-dist/src/pf/bikerack/__pycache__/cli.cpython-311.pyc +0 -0
  282. package/pennyfarthing-dist/src/pf/bikerack/__pycache__/cli.cpython-314.pyc +0 -0
  283. package/pennyfarthing-dist/src/pf/bikerack/__pycache__/context_meter_footer.cpython-314.pyc +0 -0
  284. package/pennyfarthing-dist/src/pf/bikerack/__pycache__/debug_panel.cpython-314.pyc +0 -0
  285. package/pennyfarthing-dist/src/pf/bikerack/__pycache__/diffs_panel.cpython-314.pyc +0 -0
  286. package/pennyfarthing-dist/src/pf/bikerack/__pycache__/events.cpython-314.pyc +0 -0
  287. package/pennyfarthing-dist/src/pf/bikerack/__pycache__/git_panel.cpython-314.pyc +0 -0
  288. package/pennyfarthing-dist/src/pf/bikerack/__pycache__/launcher.cpython-311.pyc +0 -0
  289. package/pennyfarthing-dist/src/pf/bikerack/__pycache__/launcher.cpython-314.pyc +0 -0
  290. package/pennyfarthing-dist/src/pf/bikerack/__pycache__/layout_order.cpython-314.pyc +0 -0
  291. package/pennyfarthing-dist/src/pf/bikerack/__pycache__/portrait_resolver.cpython-314.pyc +0 -0
  292. package/pennyfarthing-dist/src/pf/bikerack/__pycache__/progress_panel.cpython-314.pyc +0 -0
  293. package/pennyfarthing-dist/src/pf/bikerack/__pycache__/sprint_panel.cpython-314.pyc +0 -0
  294. package/pennyfarthing-dist/src/pf/bikerack/__pycache__/story_detail_data.cpython-314.pyc +0 -0
  295. package/pennyfarthing-dist/src/pf/bikerack/__pycache__/story_detail_screen.cpython-314.pyc +0 -0
  296. package/pennyfarthing-dist/src/pf/bikerack/__pycache__/story_detail_widget.cpython-314.pyc +0 -0
  297. package/pennyfarthing-dist/src/pf/bikerack/__pycache__/tui.cpython-314.pyc +0 -0
  298. package/pennyfarthing-dist/src/pf/bikerack/__pycache__/ws_client.cpython-314.pyc +0 -0
  299. package/pennyfarthing-dist/src/pf/bikerack/audit_log_panel.py +631 -0
  300. package/pennyfarthing-dist/src/pf/bikerack/base_panel.py +186 -0
  301. package/pennyfarthing-dist/src/pf/bikerack/context_meter_footer.py +266 -0
  302. package/pennyfarthing-dist/src/pf/bikerack/debug_panel.py +451 -0
  303. package/pennyfarthing-dist/src/pf/bikerack/launcher.py +239 -0
  304. package/pennyfarthing-dist/src/pf/bikerack/layout_order.py +84 -0
  305. package/pennyfarthing-dist/src/pf/bikerack/portrait_resolver.py +191 -0
  306. package/pennyfarthing-dist/src/pf/bikerack/progress_panel.py +564 -0
  307. package/pennyfarthing-dist/src/pf/bikerack/sprint_panel.py +645 -0
  308. package/pennyfarthing-dist/src/pf/bikerack/story_detail_data.py +329 -0
  309. package/pennyfarthing-dist/src/pf/bikerack/story_detail_screen.py +283 -0
  310. package/pennyfarthing-dist/src/pf/bikerack/story_detail_widget.py +167 -0
  311. package/pennyfarthing-dist/src/pf/bikerack/tui.py +1170 -0
  312. package/pennyfarthing-dist/src/pf/bikerack/ws_client.py +203 -0
  313. package/pennyfarthing-dist/src/pf/bmad/__pycache__/__init__.cpython-314.pyc +0 -0
  314. package/pennyfarthing-dist/src/pf/bmad/__pycache__/cli.cpython-314.pyc +0 -0
  315. package/pennyfarthing-dist/src/pf/bmad/cli.py +196 -0
  316. package/pennyfarthing-dist/src/pf/bmad/importer.py +199 -0
  317. package/pennyfarthing-dist/src/pf/bmad/parser.py +238 -0
  318. package/pennyfarthing-dist/src/pf/bmad/sync.py +462 -0
  319. package/pennyfarthing-dist/src/pf/bmad/test_parser.py +249 -0
  320. package/pennyfarthing-dist/src/pf/bmad/test_sync.py +220 -0
  321. package/pennyfarthing-dist/src/pf/brownfield/__pycache__/__init__.cpython-314.pyc +0 -0
  322. package/pennyfarthing-dist/src/pf/brownfield/__pycache__/__main__.cpython-314.pyc +0 -0
  323. package/pennyfarthing-dist/src/pf/brownfield/__pycache__/cli.cpython-314.pyc +0 -0
  324. package/pennyfarthing-dist/src/pf/brownfield/__pycache__/discover.cpython-314.pyc +0 -0
  325. package/pennyfarthing-dist/src/pf/brownfield/cli.py +130 -0
  326. package/pennyfarthing-dist/src/pf/cli.py +393 -0
  327. package/pennyfarthing-dist/src/pf/codemarkers/__pycache__/__init__.cpython-314.pyc +0 -0
  328. package/pennyfarthing-dist/src/pf/codemarkers/__pycache__/__main__.cpython-314.pyc +0 -0
  329. package/pennyfarthing-dist/src/pf/codemarkers/__pycache__/analyze.cpython-314.pyc +0 -0
  330. package/pennyfarthing-dist/src/pf/codemarkers/__pycache__/cli.cpython-314.pyc +0 -0
  331. package/pennyfarthing-dist/src/pf/codemarkers/__pycache__/formatters.cpython-314.pyc +0 -0
  332. package/pennyfarthing-dist/src/pf/codemarkers/__pycache__/models.cpython-314.pyc +0 -0
  333. package/pennyfarthing-dist/src/pf/common/__init__.py +50 -0
  334. package/pennyfarthing-dist/src/pf/common/__pycache__/__init__.cpython-311.pyc +0 -0
  335. package/pennyfarthing-dist/src/pf/common/__pycache__/__init__.cpython-314.pyc +0 -0
  336. package/pennyfarthing-dist/src/pf/common/__pycache__/config.cpython-311.pyc +0 -0
  337. package/pennyfarthing-dist/src/pf/common/__pycache__/config.cpython-314.pyc +0 -0
  338. package/pennyfarthing-dist/src/pf/common/__pycache__/hooks.cpython-314.pyc +0 -0
  339. package/pennyfarthing-dist/src/pf/common/__pycache__/output.cpython-311.pyc +0 -0
  340. package/pennyfarthing-dist/src/pf/common/__pycache__/output.cpython-314.pyc +0 -0
  341. package/pennyfarthing-dist/src/pf/common/__pycache__/pr_config.cpython-314.pyc +0 -0
  342. package/pennyfarthing-dist/src/pf/common/__pycache__/themes.cpython-314.pyc +0 -0
  343. package/pennyfarthing-dist/src/pf/common/config.py +162 -0
  344. package/pennyfarthing-dist/src/pf/common/hooks.py +31 -0
  345. package/pennyfarthing-dist/src/pf/common/themes.py +354 -0
  346. package/pennyfarthing-dist/src/pf/complexity/__pycache__/__init__.cpython-314.pyc +0 -0
  347. package/pennyfarthing-dist/src/pf/complexity/__pycache__/__main__.cpython-314.pyc +0 -0
  348. package/pennyfarthing-dist/src/pf/complexity/__pycache__/analyze.cpython-314.pyc +0 -0
  349. package/pennyfarthing-dist/src/pf/complexity/__pycache__/cli.cpython-314.pyc +0 -0
  350. package/pennyfarthing-dist/src/pf/complexity/__pycache__/formatters.cpython-314.pyc +0 -0
  351. package/pennyfarthing-dist/src/pf/complexity/__pycache__/models.cpython-314.pyc +0 -0
  352. package/pennyfarthing-dist/src/pf/config_migration.py +102 -0
  353. package/pennyfarthing-dist/src/pf/consultation/__pycache__/__init__.cpython-311.pyc +0 -0
  354. package/pennyfarthing-dist/src/pf/consultation/__pycache__/__init__.cpython-314.pyc +0 -0
  355. package/pennyfarthing-dist/src/pf/consultation/__pycache__/cli.cpython-311.pyc +0 -0
  356. package/pennyfarthing-dist/src/pf/consultation/__pycache__/cli.cpython-314.pyc +0 -0
  357. package/pennyfarthing-dist/src/pf/consultation/__pycache__/dialogue_manager.cpython-314.pyc +0 -0
  358. package/pennyfarthing-dist/src/pf/context.py +413 -0
  359. package/pennyfarthing-dist/src/pf/core/__init__.py +6 -0
  360. package/pennyfarthing-dist/src/pf/core/__pycache__/__init__.cpython-314.pyc +0 -0
  361. package/pennyfarthing-dist/src/pf/core/__pycache__/models.cpython-314.pyc +0 -0
  362. package/pennyfarthing-dist/src/pf/core/__pycache__/resolver.cpython-314.pyc +0 -0
  363. package/pennyfarthing-dist/src/pf/core/models.py +30 -0
  364. package/pennyfarthing-dist/src/pf/core/resolver.py +137 -0
  365. package/pennyfarthing-dist/src/pf/deadcode/__pycache__/__init__.cpython-311.pyc +0 -0
  366. package/pennyfarthing-dist/src/pf/deadcode/__pycache__/__init__.cpython-314.pyc +0 -0
  367. package/pennyfarthing-dist/src/pf/deadcode/__pycache__/__main__.cpython-314.pyc +0 -0
  368. package/pennyfarthing-dist/src/pf/deadcode/__pycache__/analyze.cpython-314.pyc +0 -0
  369. package/pennyfarthing-dist/src/pf/deadcode/__pycache__/cli.cpython-311.pyc +0 -0
  370. package/pennyfarthing-dist/src/pf/deadcode/__pycache__/cli.cpython-314.pyc +0 -0
  371. package/pennyfarthing-dist/src/pf/deadcode/__pycache__/formatters.cpython-314.pyc +0 -0
  372. package/pennyfarthing-dist/src/pf/deadcode/__pycache__/models.cpython-314.pyc +0 -0
  373. package/pennyfarthing-dist/src/pf/dependencies/__pycache__/__init__.cpython-314.pyc +0 -0
  374. package/pennyfarthing-dist/src/pf/dependencies/__pycache__/__main__.cpython-314.pyc +0 -0
  375. package/pennyfarthing-dist/src/pf/dependencies/__pycache__/analyze.cpython-314.pyc +0 -0
  376. package/pennyfarthing-dist/src/pf/dependencies/__pycache__/cli.cpython-314.pyc +0 -0
  377. package/pennyfarthing-dist/src/pf/dependencies/__pycache__/formatters.cpython-314.pyc +0 -0
  378. package/pennyfarthing-dist/src/pf/dependencies/__pycache__/models.cpython-314.pyc +0 -0
  379. package/pennyfarthing-dist/src/pf/docs/__init__.py +1 -0
  380. package/pennyfarthing-dist/src/pf/docs/__pycache__/__init__.cpython-314.pyc +0 -0
  381. package/pennyfarthing-dist/src/pf/docs/__pycache__/cli.cpython-314.pyc +0 -0
  382. package/pennyfarthing-dist/src/pf/docs/__pycache__/sync_skills.cpython-314.pyc +0 -0
  383. package/pennyfarthing-dist/src/pf/docs/cli.py +51 -0
  384. package/pennyfarthing-dist/src/pf/docs/sync_skills.py +489 -0
  385. package/pennyfarthing-dist/src/pf/doctor/__init__.py +6 -0
  386. package/pennyfarthing-dist/src/pf/doctor/__pycache__/__init__.cpython-314.pyc +0 -0
  387. package/pennyfarthing-dist/src/pf/doctor/__pycache__/checks.cpython-314.pyc +0 -0
  388. package/pennyfarthing-dist/src/pf/doctor/__pycache__/cli.cpython-314.pyc +0 -0
  389. package/pennyfarthing-dist/src/pf/doctor/__pycache__/core.cpython-314.pyc +0 -0
  390. package/pennyfarthing-dist/src/pf/doctor/__pycache__/models.cpython-314.pyc +0 -0
  391. package/pennyfarthing-dist/src/pf/doctor/checks.py +180 -0
  392. package/pennyfarthing-dist/src/pf/doctor/cli.py +52 -0
  393. package/pennyfarthing-dist/src/pf/doctor/core.py +63 -0
  394. package/pennyfarthing-dist/src/pf/doctor/models.py +29 -0
  395. package/pennyfarthing-dist/src/pf/epic/__pycache__/__init__.cpython-311.pyc +0 -0
  396. package/pennyfarthing-dist/src/pf/epic/__pycache__/__init__.cpython-314.pyc +0 -0
  397. package/pennyfarthing-dist/src/pf/epic/__pycache__/cli.cpython-311.pyc +0 -0
  398. package/pennyfarthing-dist/src/pf/epic/__pycache__/cli.cpython-314.pyc +0 -0
  399. package/pennyfarthing-dist/src/pf/git/__pycache__/__init__.cpython-314.pyc +0 -0
  400. package/pennyfarthing-dist/src/pf/git/__pycache__/create_branches.cpython-314.pyc +0 -0
  401. package/pennyfarthing-dist/src/pf/git/__pycache__/hooks_installer.cpython-314.pyc +0 -0
  402. package/pennyfarthing-dist/src/pf/git/__pycache__/repos.cpython-314.pyc +0 -0
  403. package/pennyfarthing-dist/src/pf/git/__pycache__/status_all.cpython-314.pyc +0 -0
  404. package/pennyfarthing-dist/src/pf/git/hooks_installer.py +152 -0
  405. package/pennyfarthing-dist/src/pf/git_group/__pycache__/__init__.cpython-311.pyc +0 -0
  406. package/pennyfarthing-dist/src/pf/git_group/__pycache__/__init__.cpython-314.pyc +0 -0
  407. package/pennyfarthing-dist/src/pf/git_group/__pycache__/cli.cpython-311.pyc +0 -0
  408. package/pennyfarthing-dist/src/pf/git_group/__pycache__/cli.cpython-314.pyc +0 -0
  409. package/pennyfarthing-dist/src/pf/handoff/__pycache__/__init__.cpython-311.pyc +0 -0
  410. package/pennyfarthing-dist/src/pf/handoff/__pycache__/__init__.cpython-314.pyc +0 -0
  411. package/pennyfarthing-dist/src/pf/handoff/__pycache__/cli.cpython-311.pyc +0 -0
  412. package/pennyfarthing-dist/src/pf/handoff/__pycache__/cli.cpython-314.pyc +0 -0
  413. package/pennyfarthing-dist/src/pf/handoff/__pycache__/complete_phase.cpython-314.pyc +0 -0
  414. package/pennyfarthing-dist/src/pf/handoff/__pycache__/gate_file.cpython-314.pyc +0 -0
  415. package/pennyfarthing-dist/src/pf/handoff/__pycache__/gate_runner.cpython-314.pyc +0 -0
  416. package/pennyfarthing-dist/src/pf/handoff/__pycache__/marker.cpython-314.pyc +0 -0
  417. package/pennyfarthing-dist/src/pf/handoff/__pycache__/phase_check.cpython-314.pyc +0 -0
  418. package/pennyfarthing-dist/src/pf/handoff/__pycache__/resolve_gate.cpython-314.pyc +0 -0
  419. package/pennyfarthing-dist/src/pf/handoff/gate_file.py +109 -0
  420. package/pennyfarthing-dist/src/pf/healthscore/__pycache__/__init__.cpython-311.pyc +0 -0
  421. package/pennyfarthing-dist/src/pf/healthscore/__pycache__/__init__.cpython-314.pyc +0 -0
  422. package/pennyfarthing-dist/src/pf/healthscore/__pycache__/__main__.cpython-314.pyc +0 -0
  423. package/pennyfarthing-dist/src/pf/healthscore/__pycache__/analyze.cpython-311.pyc +0 -0
  424. package/pennyfarthing-dist/src/pf/healthscore/__pycache__/analyze.cpython-314.pyc +0 -0
  425. package/pennyfarthing-dist/src/pf/healthscore/__pycache__/cli.cpython-311.pyc +0 -0
  426. package/pennyfarthing-dist/src/pf/healthscore/__pycache__/cli.cpython-314.pyc +0 -0
  427. package/pennyfarthing-dist/src/pf/healthscore/__pycache__/formatters.cpython-314.pyc +0 -0
  428. package/pennyfarthing-dist/src/pf/healthscore/__pycache__/models.cpython-311.pyc +0 -0
  429. package/pennyfarthing-dist/src/pf/healthscore/__pycache__/models.cpython-314.pyc +0 -0
  430. package/pennyfarthing-dist/src/pf/hooks/__init__.py +447 -0
  431. package/pennyfarthing-dist/src/pf/hooks/__pycache__/__init__.cpython-311.pyc +0 -0
  432. package/pennyfarthing-dist/src/pf/hooks/__pycache__/__init__.cpython-314.pyc +0 -0
  433. package/pennyfarthing-dist/src/pf/hooks/__pycache__/bell_mode.cpython-311.pyc +0 -0
  434. package/pennyfarthing-dist/src/pf/hooks/__pycache__/bell_mode.cpython-314.pyc +0 -0
  435. package/pennyfarthing-dist/src/pf/hooks/__pycache__/cli.cpython-311.pyc +0 -0
  436. package/pennyfarthing-dist/src/pf/hooks/__pycache__/cli.cpython-314.pyc +0 -0
  437. package/pennyfarthing-dist/src/pf/hooks/__pycache__/context_breaker.cpython-311.pyc +0 -0
  438. package/pennyfarthing-dist/src/pf/hooks/__pycache__/context_breaker.cpython-314.pyc +0 -0
  439. package/pennyfarthing-dist/src/pf/hooks/__pycache__/context_warning.cpython-311.pyc +0 -0
  440. package/pennyfarthing-dist/src/pf/hooks/__pycache__/context_warning.cpython-314.pyc +0 -0
  441. package/pennyfarthing-dist/src/pf/hooks/__pycache__/cyclist_pretooluse.cpython-311.pyc +0 -0
  442. package/pennyfarthing-dist/src/pf/hooks/__pycache__/cyclist_pretooluse.cpython-314.pyc +0 -0
  443. package/pennyfarthing-dist/src/pf/hooks/__pycache__/pre_edit_check.cpython-311.pyc +0 -0
  444. package/pennyfarthing-dist/src/pf/hooks/__pycache__/pre_edit_check.cpython-314.pyc +0 -0
  445. package/pennyfarthing-dist/src/pf/hooks/__pycache__/reflector_check.cpython-311.pyc +0 -0
  446. package/pennyfarthing-dist/src/pf/hooks/__pycache__/reflector_check.cpython-314.pyc +0 -0
  447. package/pennyfarthing-dist/src/pf/hooks/__pycache__/schema_validation.cpython-311.pyc +0 -0
  448. package/pennyfarthing-dist/src/pf/hooks/__pycache__/schema_validation.cpython-314.pyc +0 -0
  449. package/pennyfarthing-dist/src/pf/hooks/__pycache__/session_start.cpython-311.pyc +0 -0
  450. package/pennyfarthing-dist/src/pf/hooks/__pycache__/session_start.cpython-314.pyc +0 -0
  451. package/pennyfarthing-dist/src/pf/hooks/__pycache__/session_stop.cpython-314.pyc +0 -0
  452. package/pennyfarthing-dist/src/pf/hooks/__pycache__/sprint_yaml_validation.cpython-311.pyc +0 -0
  453. package/pennyfarthing-dist/src/pf/hooks/__pycache__/sprint_yaml_validation.cpython-314.pyc +0 -0
  454. package/pennyfarthing-dist/src/pf/hooks/__pycache__/statusline.cpython-311.pyc +0 -0
  455. package/pennyfarthing-dist/src/pf/hooks/__pycache__/statusline.cpython-314.pyc +0 -0
  456. package/pennyfarthing-dist/src/pf/hooks/cyclist_pretooluse.py +161 -0
  457. package/pennyfarthing-dist/src/pf/hooks/frontmatter.py +257 -0
  458. package/pennyfarthing-dist/src/pf/hooks/session_start.py +379 -0
  459. package/pennyfarthing-dist/src/pf/hooks/statusline.py +436 -0
  460. package/pennyfarthing-dist/src/pf/hotspots/__pycache__/__init__.cpython-311.pyc +0 -0
  461. package/pennyfarthing-dist/src/pf/hotspots/__pycache__/__init__.cpython-314.pyc +0 -0
  462. package/pennyfarthing-dist/src/pf/hotspots/__pycache__/__main__.cpython-314.pyc +0 -0
  463. package/pennyfarthing-dist/src/pf/hotspots/__pycache__/analyze.cpython-311.pyc +0 -0
  464. package/pennyfarthing-dist/src/pf/hotspots/__pycache__/analyze.cpython-314.pyc +0 -0
  465. package/pennyfarthing-dist/src/pf/hotspots/__pycache__/cli.cpython-311.pyc +0 -0
  466. package/pennyfarthing-dist/src/pf/hotspots/__pycache__/cli.cpython-314.pyc +0 -0
  467. package/pennyfarthing-dist/src/pf/hotspots/__pycache__/formatters.cpython-314.pyc +0 -0
  468. package/pennyfarthing-dist/src/pf/hotspots/__pycache__/models.cpython-311.pyc +0 -0
  469. package/pennyfarthing-dist/src/pf/hotspots/__pycache__/models.cpython-314.pyc +0 -0
  470. package/pennyfarthing-dist/src/pf/init/__init__.py +1 -0
  471. package/pennyfarthing-dist/src/pf/init/__pycache__/__init__.cpython-314.pyc +0 -0
  472. package/pennyfarthing-dist/src/pf/init/__pycache__/cli.cpython-314.pyc +0 -0
  473. package/pennyfarthing-dist/src/pf/init/__pycache__/core.cpython-314.pyc +0 -0
  474. package/pennyfarthing-dist/src/pf/init/__pycache__/setup.cpython-314.pyc +0 -0
  475. package/pennyfarthing-dist/src/pf/init/cli.py +63 -0
  476. package/pennyfarthing-dist/src/pf/init/core.py +281 -0
  477. package/pennyfarthing-dist/src/pf/init/setup.py +474 -0
  478. package/pennyfarthing-dist/src/pf/jira/__init__.py +91 -0
  479. package/pennyfarthing-dist/src/pf/jira/__pycache__/__init__.cpython-311.pyc +0 -0
  480. package/pennyfarthing-dist/src/pf/jira/__pycache__/__init__.cpython-314.pyc +0 -0
  481. package/pennyfarthing-dist/src/pf/jira/__pycache__/__main__.cpython-314.pyc +0 -0
  482. package/pennyfarthing-dist/src/pf/jira/__pycache__/bidirectional.cpython-311.pyc +0 -0
  483. package/pennyfarthing-dist/src/pf/jira/__pycache__/bidirectional.cpython-314.pyc +0 -0
  484. package/pennyfarthing-dist/src/pf/jira/__pycache__/claim.cpython-311.pyc +0 -0
  485. package/pennyfarthing-dist/src/pf/jira/__pycache__/claim.cpython-314.pyc +0 -0
  486. package/pennyfarthing-dist/src/pf/jira/__pycache__/cli.cpython-311.pyc +0 -0
  487. package/pennyfarthing-dist/src/pf/jira/__pycache__/cli.cpython-314.pyc +0 -0
  488. package/pennyfarthing-dist/src/pf/jira/__pycache__/client.cpython-311.pyc +0 -0
  489. package/pennyfarthing-dist/src/pf/jira/__pycache__/client.cpython-314.pyc +0 -0
  490. package/pennyfarthing-dist/src/pf/jira/__pycache__/compat.cpython-314.pyc +0 -0
  491. package/pennyfarthing-dist/src/pf/jira/__pycache__/create.cpython-311.pyc +0 -0
  492. package/pennyfarthing-dist/src/pf/jira/__pycache__/create.cpython-314.pyc +0 -0
  493. package/pennyfarthing-dist/src/pf/jira/__pycache__/epic.cpython-311.pyc +0 -0
  494. package/pennyfarthing-dist/src/pf/jira/__pycache__/epic.cpython-314.pyc +0 -0
  495. package/pennyfarthing-dist/src/pf/jira/__pycache__/mappings.cpython-314.pyc +0 -0
  496. package/pennyfarthing-dist/src/pf/jira/__pycache__/models.cpython-314.pyc +0 -0
  497. package/pennyfarthing-dist/src/pf/jira/__pycache__/operations.cpython-311.pyc +0 -0
  498. package/pennyfarthing-dist/src/pf/jira/__pycache__/operations.cpython-314.pyc +0 -0
  499. package/pennyfarthing-dist/src/pf/jira/__pycache__/reconcile.cpython-311.pyc +0 -0
  500. package/pennyfarthing-dist/src/pf/jira/__pycache__/reconcile.cpython-314.pyc +0 -0
  501. package/pennyfarthing-dist/src/pf/jira/__pycache__/story.cpython-311.pyc +0 -0
  502. package/pennyfarthing-dist/src/pf/jira/__pycache__/story.cpython-314.pyc +0 -0
  503. package/pennyfarthing-dist/src/pf/jira/__pycache__/sync.cpython-311.pyc +0 -0
  504. package/pennyfarthing-dist/src/pf/jira/__pycache__/sync.cpython-314.pyc +0 -0
  505. package/pennyfarthing-dist/src/pf/jira/claim.py +232 -0
  506. package/pennyfarthing-dist/src/pf/jira/cli.py +472 -0
  507. package/pennyfarthing-dist/src/pf/jira/client.py +766 -0
  508. package/pennyfarthing-dist/src/pf/jira/reconcile.py +260 -0
  509. package/pennyfarthing-dist/src/pf/jira/story.py +223 -0
  510. package/pennyfarthing-dist/src/pf/jira/sync.py +350 -0
  511. package/pennyfarthing-dist/src/pf/launch/__pycache__/__init__.cpython-311.pyc +0 -0
  512. package/pennyfarthing-dist/src/pf/launch/__pycache__/__init__.cpython-314.pyc +0 -0
  513. package/pennyfarthing-dist/src/pf/launch/__pycache__/cli.cpython-311.pyc +0 -0
  514. package/pennyfarthing-dist/src/pf/launch/__pycache__/cli.cpython-314.pyc +0 -0
  515. package/pennyfarthing-dist/src/pf/migration/__init__.py +39 -0
  516. package/pennyfarthing-dist/src/pf/migration/__pycache__/__init__.cpython-314.pyc +0 -0
  517. package/pennyfarthing-dist/src/pf/migration/__pycache__/__main__.cpython-314.pyc +0 -0
  518. package/pennyfarthing-dist/src/pf/migration/__pycache__/cli.cpython-314.pyc +0 -0
  519. package/pennyfarthing-dist/src/pf/migration/__pycache__/session.cpython-314.pyc +0 -0
  520. package/pennyfarthing-dist/src/pf/migration/__pycache__/skill.cpython-314.pyc +0 -0
  521. package/pennyfarthing-dist/src/pf/migration/__pycache__/step.cpython-314.pyc +0 -0
  522. package/pennyfarthing-dist/src/pf/migration/__pycache__/validate.cpython-314.pyc +0 -0
  523. package/pennyfarthing-dist/src/pf/migration/cli.py +303 -0
  524. package/pennyfarthing-dist/src/pf/package/__pycache__/__init__.cpython-314.pyc +0 -0
  525. package/pennyfarthing-dist/src/pf/package/__pycache__/cli.cpython-314.pyc +0 -0
  526. package/pennyfarthing-dist/src/pf/package/__pycache__/discovery.cpython-314.pyc +0 -0
  527. package/pennyfarthing-dist/src/pf/package/__pycache__/portraits.cpython-314.pyc +0 -0
  528. package/pennyfarthing-dist/src/pf/package/cli.py +186 -0
  529. package/pennyfarthing-dist/src/pf/package/discovery.py +130 -0
  530. package/pennyfarthing-dist/src/pf/package/portraits.py +243 -0
  531. package/pennyfarthing-dist/src/pf/preflight/__pycache__/__init__.cpython-314.pyc +0 -0
  532. package/pennyfarthing-dist/src/pf/preflight/__pycache__/__main__.cpython-314.pyc +0 -0
  533. package/pennyfarthing-dist/src/pf/preflight/__pycache__/cli.cpython-314.pyc +0 -0
  534. package/pennyfarthing-dist/src/pf/preflight/__pycache__/finish.cpython-314.pyc +0 -0
  535. package/pennyfarthing-dist/src/pf/prime/__pycache__/__init__.cpython-314.pyc +0 -0
  536. package/pennyfarthing-dist/src/pf/prime/__pycache__/__main__.cpython-314.pyc +0 -0
  537. package/pennyfarthing-dist/src/pf/prime/__pycache__/cli.cpython-314.pyc +0 -0
  538. package/pennyfarthing-dist/src/pf/prime/__pycache__/loader.cpython-314.pyc +0 -0
  539. package/pennyfarthing-dist/src/pf/prime/__pycache__/models.cpython-314.pyc +0 -0
  540. package/pennyfarthing-dist/src/pf/prime/__pycache__/persona.cpython-314.pyc +0 -0
  541. package/pennyfarthing-dist/src/pf/prime/__pycache__/session.cpython-314.pyc +0 -0
  542. package/pennyfarthing-dist/src/pf/prime/__pycache__/tiers.cpython-314.pyc +0 -0
  543. package/pennyfarthing-dist/src/pf/prime/__pycache__/version_sentinel.cpython-314.pyc +0 -0
  544. package/pennyfarthing-dist/src/pf/prime/__pycache__/workflow.cpython-314.pyc +0 -0
  545. package/pennyfarthing-dist/src/pf/prime/cli.py +721 -0
  546. package/pennyfarthing-dist/src/pf/prime/loader.py +322 -0
  547. package/pennyfarthing-dist/src/pf/prime/persona.py +305 -0
  548. package/pennyfarthing-dist/src/pf/prime/workflow.py +325 -0
  549. package/pennyfarthing-dist/src/pf/release/__pycache__/__init__.cpython-314.pyc +0 -0
  550. package/pennyfarthing-dist/src/pf/release/__pycache__/cli.cpython-314.pyc +0 -0
  551. package/pennyfarthing-dist/src/pf/release/__pycache__/deprecate.cpython-314.pyc +0 -0
  552. package/pennyfarthing-dist/src/pf/release/__pycache__/dry_run.cpython-314.pyc +0 -0
  553. package/pennyfarthing-dist/src/pf/release/cli.py +112 -0
  554. package/pennyfarthing-dist/src/pf/release/deprecate.py +187 -0
  555. package/pennyfarthing-dist/src/pf/release/dry_run.py +187 -0
  556. package/pennyfarthing-dist/src/pf/session/__pycache__/__init__.cpython-311.pyc +0 -0
  557. package/pennyfarthing-dist/src/pf/session/__pycache__/__init__.cpython-314.pyc +0 -0
  558. package/pennyfarthing-dist/src/pf/session/__pycache__/cli.cpython-311.pyc +0 -0
  559. package/pennyfarthing-dist/src/pf/session/__pycache__/cli.cpython-314.pyc +0 -0
  560. package/pennyfarthing-dist/src/pf/settings/__init__.py +0 -0
  561. package/pennyfarthing-dist/src/pf/settings/__pycache__/__init__.cpython-314.pyc +0 -0
  562. package/pennyfarthing-dist/src/pf/settings/__pycache__/cli.cpython-314.pyc +0 -0
  563. package/pennyfarthing-dist/src/pf/settings/__pycache__/settings.cpython-314.pyc +0 -0
  564. package/pennyfarthing-dist/src/pf/settings/settings.py +135 -0
  565. package/pennyfarthing-dist/src/pf/sprint/__pycache__/__init__.cpython-311.pyc +0 -0
  566. package/pennyfarthing-dist/src/pf/sprint/__pycache__/__init__.cpython-314.pyc +0 -0
  567. package/pennyfarthing-dist/src/pf/sprint/__pycache__/__main__.cpython-314.pyc +0 -0
  568. package/pennyfarthing-dist/src/pf/sprint/__pycache__/archive.cpython-311.pyc +0 -0
  569. package/pennyfarthing-dist/src/pf/sprint/__pycache__/archive.cpython-314.pyc +0 -0
  570. package/pennyfarthing-dist/src/pf/sprint/__pycache__/archive_epic.cpython-314.pyc +0 -0
  571. package/pennyfarthing-dist/src/pf/sprint/__pycache__/cli.cpython-311.pyc +0 -0
  572. package/pennyfarthing-dist/src/pf/sprint/__pycache__/cli.cpython-314.pyc +0 -0
  573. package/pennyfarthing-dist/src/pf/sprint/__pycache__/epic_add.cpython-311.pyc +0 -0
  574. package/pennyfarthing-dist/src/pf/sprint/__pycache__/epic_add.cpython-314.pyc +0 -0
  575. package/pennyfarthing-dist/src/pf/sprint/__pycache__/epic_update.cpython-311.pyc +0 -0
  576. package/pennyfarthing-dist/src/pf/sprint/__pycache__/epic_update.cpython-314.pyc +0 -0
  577. package/pennyfarthing-dist/src/pf/sprint/__pycache__/import_epic.cpython-314.pyc +0 -0
  578. package/pennyfarthing-dist/src/pf/sprint/__pycache__/loader.cpython-311.pyc +0 -0
  579. package/pennyfarthing-dist/src/pf/sprint/__pycache__/loader.cpython-314.pyc +0 -0
  580. package/pennyfarthing-dist/src/pf/sprint/__pycache__/shard_merge.cpython-314.pyc +0 -0
  581. package/pennyfarthing-dist/src/pf/sprint/__pycache__/standalone_add.cpython-314.pyc +0 -0
  582. package/pennyfarthing-dist/src/pf/sprint/__pycache__/status.cpython-311.pyc +0 -0
  583. package/pennyfarthing-dist/src/pf/sprint/__pycache__/status.cpython-314.pyc +0 -0
  584. package/pennyfarthing-dist/src/pf/sprint/__pycache__/story_add.cpython-311.pyc +0 -0
  585. package/pennyfarthing-dist/src/pf/sprint/__pycache__/story_add.cpython-314.pyc +0 -0
  586. package/pennyfarthing-dist/src/pf/sprint/__pycache__/story_finish.cpython-314.pyc +0 -0
  587. package/pennyfarthing-dist/src/pf/sprint/__pycache__/story_transition.cpython-314.pyc +0 -0
  588. package/pennyfarthing-dist/src/pf/sprint/__pycache__/story_update.cpython-311.pyc +0 -0
  589. package/pennyfarthing-dist/src/pf/sprint/__pycache__/story_update.cpython-314.pyc +0 -0
  590. package/pennyfarthing-dist/src/pf/sprint/__pycache__/validate_cmd.cpython-311.pyc +0 -0
  591. package/pennyfarthing-dist/src/pf/sprint/__pycache__/validate_cmd.cpython-314.pyc +0 -0
  592. package/pennyfarthing-dist/src/pf/sprint/__pycache__/validator.cpython-311.pyc +0 -0
  593. package/pennyfarthing-dist/src/pf/sprint/__pycache__/validator.cpython-314.pyc +0 -0
  594. package/pennyfarthing-dist/src/pf/sprint/__pycache__/work.cpython-311.pyc +0 -0
  595. package/pennyfarthing-dist/src/pf/sprint/__pycache__/work.cpython-314.pyc +0 -0
  596. package/pennyfarthing-dist/src/pf/sprint/__pycache__/yaml_io.cpython-311.pyc +0 -0
  597. package/pennyfarthing-dist/src/pf/sprint/__pycache__/yaml_io.cpython-314.pyc +0 -0
  598. package/pennyfarthing-dist/src/pf/sprint/archive.py +164 -0
  599. package/pennyfarthing-dist/src/pf/sprint/cli.py +2206 -0
  600. package/pennyfarthing-dist/src/pf/sprint/loader.py +361 -0
  601. package/pennyfarthing-dist/src/pf/sprint/shard_merge.py +205 -0
  602. package/pennyfarthing-dist/src/pf/sprint/standalone_add.py +143 -0
  603. package/pennyfarthing-dist/src/pf/sprint/status.py +187 -0
  604. package/pennyfarthing-dist/src/pf/sprint/story_finish.py +229 -0
  605. package/pennyfarthing-dist/src/pf/sprint/story_transition.py +183 -0
  606. package/pennyfarthing-dist/src/pf/sprint/story_update.py +240 -0
  607. package/pennyfarthing-dist/src/pf/sprint/yaml_io.py +407 -0
  608. package/pennyfarthing-dist/src/pf/story/__pycache__/__init__.cpython-314.pyc +0 -0
  609. package/pennyfarthing-dist/src/pf/story/__pycache__/__main__.cpython-314.pyc +0 -0
  610. package/pennyfarthing-dist/src/pf/story/__pycache__/cli.cpython-314.pyc +0 -0
  611. package/pennyfarthing-dist/src/pf/story/__pycache__/create.cpython-314.pyc +0 -0
  612. package/pennyfarthing-dist/src/pf/story/__pycache__/size.cpython-314.pyc +0 -0
  613. package/pennyfarthing-dist/src/pf/story/__pycache__/template.cpython-314.pyc +0 -0
  614. package/pennyfarthing-dist/src/pf/tests/__pycache__/__init__.cpython-314.pyc +0 -0
  615. package/pennyfarthing-dist/src/pf/tests/__pycache__/conftest.cpython-314-pytest-9.0.2.pyc +0 -0
  616. package/pennyfarthing-dist/src/pf/tests/__pycache__/test_108_1_gate_migration.cpython-314-pytest-9.0.2.pyc +0 -0
  617. package/pennyfarthing-dist/src/pf/tests/__pycache__/test_108_2_remove_handoff_fallback.cpython-314-pytest-9.0.2.pyc +0 -0
  618. package/pennyfarthing-dist/src/pf/tests/__pycache__/test_archive_epic.cpython-314-pytest-9.0.2.pyc +0 -0
  619. package/pennyfarthing-dist/src/pf/tests/__pycache__/test_bc.cpython-314-pytest-9.0.2.pyc +0 -0
  620. package/pennyfarthing-dist/src/pf/tests/__pycache__/test_bikerack.cpython-314-pytest-9.0.2.pyc +0 -0
  621. package/pennyfarthing-dist/src/pf/tests/__pycache__/test_brownfield.cpython-314-pytest-9.0.2.pyc +0 -0
  622. package/pennyfarthing-dist/src/pf/tests/__pycache__/test_cli_modules.cpython-314-pytest-9.0.2.pyc +0 -0
  623. package/pennyfarthing-dist/src/pf/tests/__pycache__/test_cli_normalization.cpython-314-pytest-9.0.2.pyc +0 -0
  624. package/pennyfarthing-dist/src/pf/tests/__pycache__/test_codemarkers.cpython-314-pytest-9.0.2.pyc +0 -0
  625. package/pennyfarthing-dist/src/pf/tests/__pycache__/test_common.cpython-314-pytest-9.0.2.pyc +0 -0
  626. package/pennyfarthing-dist/src/pf/tests/__pycache__/test_confidence_sm_evaluation.cpython-314-pytest-9.0.2.pyc +0 -0
  627. package/pennyfarthing-dist/src/pf/tests/__pycache__/test_confidence_sm_gate.cpython-314-pytest-9.0.2.pyc +0 -0
  628. package/pennyfarthing-dist/src/pf/tests/__pycache__/test_config_consolidation.cpython-314-pytest-9.0.2.pyc +0 -0
  629. package/pennyfarthing-dist/src/pf/tests/__pycache__/test_dialogue_manager.cpython-314-pytest-9.0.2.pyc +0 -0
  630. package/pennyfarthing-dist/src/pf/tests/__pycache__/test_dist_root.cpython-314-pytest-9.0.2.pyc +0 -0
  631. package/pennyfarthing-dist/src/pf/tests/__pycache__/test_doctor.cpython-314-pytest-9.0.2.pyc +0 -0
  632. package/pennyfarthing-dist/src/pf/tests/__pycache__/test_e2e_install_lifecycle.cpython-314-pytest-9.0.2.pyc +0 -0
  633. package/pennyfarthing-dist/src/pf/tests/__pycache__/test_epic_shard_validation.cpython-314-pytest-9.0.2.pyc +0 -0
  634. package/pennyfarthing-dist/src/pf/tests/__pycache__/test_event_driven_jira_sync.cpython-314-pytest-9.0.2.pyc +0 -0
  635. package/pennyfarthing-dist/src/pf/tests/__pycache__/test_gate_file_resolution.cpython-314-pytest-9.0.2.pyc +0 -0
  636. package/pennyfarthing-dist/src/pf/tests/__pycache__/test_gate_runner.cpython-314-pytest-9.0.2.pyc +0 -0
  637. package/pennyfarthing-dist/src/pf/tests/__pycache__/test_git_utils.cpython-314-pytest-9.0.2.pyc +0 -0
  638. package/pennyfarthing-dist/src/pf/tests/__pycache__/test_handoff_cli.cpython-314-pytest-9.0.2.pyc +0 -0
  639. package/pennyfarthing-dist/src/pf/tests/__pycache__/test_handoff_e2e.cpython-314-pytest-9.0.2.pyc +0 -0
  640. package/pennyfarthing-dist/src/pf/tests/__pycache__/test_healthscore.cpython-314-pytest-9.0.2.pyc +0 -0
  641. package/pennyfarthing-dist/src/pf/tests/__pycache__/test_init_auto_setup.cpython-314-pytest-9.0.2.pyc +0 -0
  642. package/pennyfarthing-dist/src/pf/tests/__pycache__/test_init_auto_setup.cpython-314.pyc +0 -0
  643. package/pennyfarthing-dist/src/pf/tests/__pycache__/test_init_command.cpython-314-pytest-9.0.2.pyc +0 -0
  644. package/pennyfarthing-dist/src/pf/tests/__pycache__/test_jira_package.cpython-314-pytest-9.0.2.pyc +0 -0
  645. package/pennyfarthing-dist/src/pf/tests/__pycache__/test_package_structure.cpython-314-pytest-9.0.2.pyc +0 -0
  646. package/pennyfarthing-dist/src/pf/tests/__pycache__/test_patch_mode.cpython-314-pytest-9.0.2.pyc +0 -0
  647. package/pennyfarthing-dist/src/pf/tests/__pycache__/test_prime.cpython-314-pytest-9.0.2.pyc +0 -0
  648. package/pennyfarthing-dist/src/pf/tests/__pycache__/test_pypi_packaging.cpython-314-pytest-9.0.2.pyc +0 -0
  649. package/pennyfarthing-dist/src/pf/tests/__pycache__/test_resolve_gate_file_field.cpython-314-pytest-9.0.2.pyc +0 -0
  650. package/pennyfarthing-dist/src/pf/tests/__pycache__/test_sprint_package.cpython-314-pytest-9.0.2.pyc +0 -0
  651. package/pennyfarthing-dist/src/pf/tests/__pycache__/test_sprint_panel.cpython-314-pytest-9.0.2.pyc +0 -0
  652. package/pennyfarthing-dist/src/pf/tests/__pycache__/test_sprint_panel_provenance.cpython-314-pytest-9.0.2.pyc +0 -0
  653. package/pennyfarthing-dist/src/pf/tests/__pycache__/test_sprint_validator.cpython-314-pytest-9.0.2.pyc +0 -0
  654. package/pennyfarthing-dist/src/pf/tests/__pycache__/test_story_add.cpython-314-pytest-9.0.2.pyc +0 -0
  655. package/pennyfarthing-dist/src/pf/tests/__pycache__/test_story_package.cpython-314-pytest-9.0.2.pyc +0 -0
  656. package/pennyfarthing-dist/src/pf/tests/__pycache__/test_story_transition.cpython-314-pytest-9.0.2.pyc +0 -0
  657. package/pennyfarthing-dist/src/pf/tests/__pycache__/test_story_update.cpython-314-pytest-9.0.2.pyc +0 -0
  658. package/pennyfarthing-dist/src/pf/tests/__pycache__/test_tiers.cpython-314-pytest-9.0.2.pyc +0 -0
  659. package/pennyfarthing-dist/src/pf/tests/__pycache__/test_token_counting.cpython-314-pytest-9.0.2.pyc +0 -0
  660. package/pennyfarthing-dist/src/pf/tests/__pycache__/test_topology_loader.cpython-314-pytest-9.0.2.pyc +0 -0
  661. package/pennyfarthing-dist/src/pf/tests/__pycache__/test_tui_focus.cpython-314-pytest-9.0.2.pyc +0 -0
  662. package/pennyfarthing-dist/src/pf/tests/__pycache__/test_tui_panel_persistence.cpython-314-pytest-9.0.2.pyc +0 -0
  663. package/pennyfarthing-dist/src/pf/tests/__pycache__/test_upgrade_cleanup.cpython-314-pytest-9.0.2.pyc +0 -0
  664. package/pennyfarthing-dist/src/pf/tests/__pycache__/test_validate_cmd.cpython-314-pytest-9.0.2.pyc +0 -0
  665. package/pennyfarthing-dist/src/pf/tests/__pycache__/test_version_sentinel.cpython-314-pytest-9.0.2.pyc +0 -0
  666. package/pennyfarthing-dist/src/pf/tests/__pycache__/test_workflow_check.cpython-314-pytest-9.0.2.pyc +0 -0
  667. package/pennyfarthing-dist/src/pf/tests/__pycache__/test_workflow_cli.cpython-314-pytest-9.0.2.pyc +0 -0
  668. package/pennyfarthing-dist/src/pf/tests/__pycache__/test_workflow_list_team.cpython-314-pytest-9.0.2.pyc +0 -0
  669. package/pennyfarthing-dist/src/pf/tests/__pycache__/test_wrapper_removal.cpython-314-pytest-9.0.2.pyc +0 -0
  670. package/pennyfarthing-dist/src/pf/tests/__pycache__/test_yaml_io.cpython-314-pytest-9.0.2.pyc +0 -0
  671. package/pennyfarthing-dist/src/pf/tests/conftest.py +109 -0
  672. package/pennyfarthing-dist/src/pf/tests/test_108_1_gate_migration.py +541 -0
  673. package/pennyfarthing-dist/src/pf/tests/test_108_2_remove_handoff_fallback.py +340 -0
  674. package/pennyfarthing-dist/src/pf/tests/test_cli_modules.py +137 -0
  675. package/pennyfarthing-dist/src/pf/tests/test_codemarkers.py +672 -0
  676. package/pennyfarthing-dist/src/pf/tests/test_confidence_sm_evaluation.py +252 -0
  677. package/pennyfarthing-dist/src/pf/tests/test_confidence_sm_gate.py +318 -0
  678. package/pennyfarthing-dist/src/pf/tests/test_config_consolidation.py +513 -0
  679. package/pennyfarthing-dist/src/pf/tests/test_dist_root.py +720 -0
  680. package/pennyfarthing-dist/src/pf/tests/test_doctor.py +457 -0
  681. package/pennyfarthing-dist/src/pf/tests/test_event_driven_jira_sync.py +588 -0
  682. package/pennyfarthing-dist/src/pf/tests/test_init_auto_setup.py +893 -0
  683. package/pennyfarthing-dist/src/pf/tests/test_init_command.py +814 -0
  684. package/pennyfarthing-dist/src/pf/tests/test_jira_package.py +331 -0
  685. package/pennyfarthing-dist/src/pf/tests/test_package_structure.py +314 -0
  686. package/pennyfarthing-dist/src/pf/tests/test_pypi_packaging.py +329 -0
  687. package/pennyfarthing-dist/src/pf/tests/test_resolve_gate_file_field.py +460 -0
  688. package/pennyfarthing-dist/src/pf/tests/test_sprint_panel.py +610 -0
  689. package/pennyfarthing-dist/src/pf/tests/test_sprint_panel_provenance.py +485 -0
  690. package/pennyfarthing-dist/src/pf/tests/test_story_transition.py +502 -0
  691. package/pennyfarthing-dist/src/pf/tests/test_upgrade_cleanup.py +251 -0
  692. package/pennyfarthing-dist/src/pf/tests/test_wrapper_removal.py +373 -0
  693. package/pennyfarthing-dist/src/pf/theme/__pycache__/__init__.cpython-311.pyc +0 -0
  694. package/pennyfarthing-dist/src/pf/theme/__pycache__/__init__.cpython-314.pyc +0 -0
  695. package/pennyfarthing-dist/src/pf/theme/__pycache__/cli.cpython-311.pyc +0 -0
  696. package/pennyfarthing-dist/src/pf/theme/__pycache__/cli.cpython-314.pyc +0 -0
  697. package/pennyfarthing-dist/src/pf/theme/cli.py +321 -0
  698. package/pennyfarthing-dist/src/pf/upgrade/__init__.py +1 -0
  699. package/pennyfarthing-dist/src/pf/upgrade/__pycache__/__init__.cpython-314.pyc +0 -0
  700. package/pennyfarthing-dist/src/pf/upgrade/__pycache__/cli.cpython-314.pyc +0 -0
  701. package/pennyfarthing-dist/src/pf/upgrade/__pycache__/core.cpython-314.pyc +0 -0
  702. package/pennyfarthing-dist/src/pf/upgrade/cli.py +62 -0
  703. package/pennyfarthing-dist/src/pf/upgrade/core.py +483 -0
  704. package/pennyfarthing-dist/src/pf/validate/__pycache__/__init__.cpython-311.pyc +0 -0
  705. package/pennyfarthing-dist/src/pf/validate/__pycache__/__init__.cpython-314.pyc +0 -0
  706. package/pennyfarthing-dist/src/pf/validate/__pycache__/cli.cpython-311.pyc +0 -0
  707. package/pennyfarthing-dist/src/pf/validate/__pycache__/cli.cpython-314.pyc +0 -0
  708. package/pennyfarthing-dist/src/pf/validate/adapters/__init__.py +0 -0
  709. package/pennyfarthing-dist/src/pf/validate/adapters/__pycache__/__init__.cpython-314.pyc +0 -0
  710. package/pennyfarthing-dist/src/pf/validate/adapters/__pycache__/agent.cpython-314.pyc +0 -0
  711. package/pennyfarthing-dist/src/pf/validate/adapters/__pycache__/schema.cpython-314.pyc +0 -0
  712. package/pennyfarthing-dist/src/pf/validate/adapters/__pycache__/skill_command.cpython-314.pyc +0 -0
  713. package/pennyfarthing-dist/src/pf/validate/adapters/__pycache__/sprint.cpython-314.pyc +0 -0
  714. package/pennyfarthing-dist/src/pf/validate/adapters/__pycache__/tandem_awareness.cpython-314.pyc +0 -0
  715. package/pennyfarthing-dist/src/pf/validate/adapters/__pycache__/team_mode.cpython-314.pyc +0 -0
  716. package/pennyfarthing-dist/src/pf/validate/adapters/__pycache__/workflow.cpython-314.pyc +0 -0
  717. package/pennyfarthing-dist/src/pf/validate/adapters/agent.py +261 -0
  718. package/pennyfarthing-dist/src/pf/validate/adapters/skill_command.py +502 -0
  719. package/pennyfarthing-dist/src/pf/validate/adapters/tandem_awareness.py +260 -0
  720. package/pennyfarthing-dist/src/pf/validate/adapters/team_mode.py +329 -0
  721. package/pennyfarthing-dist/src/pf/validate/adapters/workflow.py +413 -0
  722. package/pennyfarthing-dist/src/pf/workflow/__pycache__/__init__.cpython-311.pyc +0 -0
  723. package/pennyfarthing-dist/src/pf/workflow/__pycache__/__init__.cpython-314.pyc +0 -0
  724. package/pennyfarthing-dist/src/pf/workflow/__pycache__/cli.cpython-311.pyc +0 -0
  725. package/pennyfarthing-dist/src/pf/workflow/__pycache__/cli.cpython-314.pyc +0 -0
  726. package/pennyfarthing-dist/src/pf/workflow/__pycache__/helpers.cpython-314.pyc +0 -0
  727. package/pennyfarthing-dist/src/pf/workflow/__pycache__/scale.cpython-311.pyc +0 -0
  728. package/pennyfarthing-dist/src/pf/workflow/__pycache__/scale.cpython-314.pyc +0 -0
  729. package/pennyfarthing-dist/src/pf/workflow/__pycache__/state.cpython-311.pyc +0 -0
  730. package/pennyfarthing-dist/src/pf/workflow/__pycache__/state.cpython-314.pyc +0 -0
  731. package/pennyfarthing-dist/src/pf/workflow/__pycache__/team_lifecycle.cpython-314.pyc +0 -0
  732. package/pennyfarthing-dist/src/pf/workflow/cli.py +1109 -0
  733. package/pennyfarthing-dist/templates/justfile.template +71 -0
  734. package/pennyfarthing-dist/templates/preferences.yaml.template +5 -8
  735. package/pennyfarthing-dist/templates/pyproject.toml +9 -1
  736. package/pennyfarthing-dist/templates/settings.local.json.template +20 -11
  737. package/pennyfarthing-dist/templates/tmux-dev.template +45 -0
  738. package/pennyfarthing-dist/templates/tmux.conf.template +91 -0
  739. package/pennyfarthing-dist/workflows/installation-check/steps/step-01-foundation.md +2 -2
  740. package/pennyfarthing-dist/workflows/installation-check/steps/step-05-layout.md +1 -1
  741. package/pennyfarthing-dist/workflows/installation-check/steps/step-07-tools.md +6 -5
  742. package/pennyfarthing-dist/workflows/patch.yaml +5 -6
  743. package/pennyfarthing-dist/workflows/project-setup/steps/step-04-claude-md.md +59 -1
  744. package/pennyfarthing-dist/workflows/release/steps/step-01-preflight.md +4 -0
  745. package/pennyfarthing-dist/workflows/release/steps/step-02-bump.md +19 -9
  746. package/pennyfarthing-dist/workflows/release/steps/step-10-publish.md +67 -21
  747. package/pennyfarthing-dist/workflows/release/steps/step-11-finalize.md +41 -30
  748. package/pennyfarthing-dist/workflows/tdd-tandem.yaml +28 -8
  749. package/packages/core/bin/pennyfarthing.js +0 -14
  750. package/packages/core/dist/workflow/team-lifecycle.d.ts +0 -169
  751. package/packages/core/dist/workflow/team-lifecycle.d.ts.map +0 -1
  752. package/packages/core/dist/workflow/team-lifecycle.js +0 -217
  753. package/packages/core/dist/workflow/team-lifecycle.js.map +0 -1
  754. package/packages/core/dist/workflow/team-lifecycle.test.d.ts +0 -20
  755. package/packages/core/dist/workflow/team-lifecycle.test.d.ts.map +0 -1
  756. package/packages/core/dist/workflow/team-lifecycle.test.js +0 -966
  757. package/packages/core/dist/workflow/team-lifecycle.test.js.map +0 -1
  758. package/packages/core/src/public/components/BikeRackIndex.tsx +0 -53
  759. package/packages/core/src/public/components/BikeRackWorkspace.tsx +0 -217
  760. package/packages/core/src/public/components/FontPicker/FontPicker.css +0 -276
  761. package/packages/core/src/public/components/FontPicker/index.tsx +0 -430
  762. package/packages/core/src/public/components/StandalonePanel.tsx +0 -82
  763. package/packages/core/src/public/components/ThemePalette/ThemePalette.css +0 -179
  764. package/packages/core/src/public/components/ThemePalette/index.tsx +0 -326
  765. package/packages/core/src/public/components/panels/BackgroundPanel.tsx +0 -115
  766. package/packages/core/src/public/components/ui/switch.tsx +0 -27
  767. package/packages/core/src/public/hooks/useBackgroundTasks.ts +0 -131
  768. package/pennyfarthing-dist/commands/pf-permissions.md +0 -193
  769. package/pennyfarthing-dist/guides/permission-protocol.md +0 -188
  770. package/pennyfarthing-dist/pf/CLAUDE.md +0 -213
  771. package/pennyfarthing-dist/pf/__init__.py +0 -17
  772. package/pennyfarthing-dist/pf/__pycache__/__init__.cpython-314.pyc +0 -0
  773. package/pennyfarthing-dist/pf/__pycache__/cli.cpython-314.pyc +0 -0
  774. package/pennyfarthing-dist/pf/__pycache__/context.cpython-314.pyc +0 -0
  775. package/pennyfarthing-dist/pf/bc/__pycache__/__init__.cpython-314.pyc +0 -0
  776. package/pennyfarthing-dist/pf/bc/__pycache__/cli.cpython-314.pyc +0 -0
  777. package/pennyfarthing-dist/pf/bc/__pycache__/focus.cpython-314.pyc +0 -0
  778. package/pennyfarthing-dist/pf/bc/__pycache__/split.cpython-314.pyc +0 -0
  779. package/pennyfarthing-dist/pf/bc/cli.py +0 -241
  780. package/pennyfarthing-dist/pf/bc/focus.py +0 -333
  781. package/pennyfarthing-dist/pf/bikerack/__pycache__/__init__.cpython-314.pyc +0 -0
  782. package/pennyfarthing-dist/pf/bikerack/__pycache__/audit_log_panel.cpython-314.pyc +0 -0
  783. package/pennyfarthing-dist/pf/bikerack/__pycache__/background_panel.cpython-314.pyc +0 -0
  784. package/pennyfarthing-dist/pf/bikerack/__pycache__/base_panel.cpython-314.pyc +0 -0
  785. package/pennyfarthing-dist/pf/bikerack/__pycache__/changed_panel.cpython-314.pyc +0 -0
  786. package/pennyfarthing-dist/pf/bikerack/__pycache__/cli.cpython-314.pyc +0 -0
  787. package/pennyfarthing-dist/pf/bikerack/__pycache__/context_meter_footer.cpython-314.pyc +0 -0
  788. package/pennyfarthing-dist/pf/bikerack/__pycache__/debug_panel.cpython-314.pyc +0 -0
  789. package/pennyfarthing-dist/pf/bikerack/__pycache__/diffs_panel.cpython-314.pyc +0 -0
  790. package/pennyfarthing-dist/pf/bikerack/__pycache__/events.cpython-314.pyc +0 -0
  791. package/pennyfarthing-dist/pf/bikerack/__pycache__/git_panel.cpython-314.pyc +0 -0
  792. package/pennyfarthing-dist/pf/bikerack/__pycache__/launcher.cpython-314.pyc +0 -0
  793. package/pennyfarthing-dist/pf/bikerack/__pycache__/portrait_resolver.cpython-314.pyc +0 -0
  794. package/pennyfarthing-dist/pf/bikerack/__pycache__/progress_panel.cpython-314.pyc +0 -0
  795. package/pennyfarthing-dist/pf/bikerack/__pycache__/sprint_panel.cpython-314.pyc +0 -0
  796. package/pennyfarthing-dist/pf/bikerack/__pycache__/story_detail_data.cpython-314.pyc +0 -0
  797. package/pennyfarthing-dist/pf/bikerack/__pycache__/story_detail_screen.cpython-314.pyc +0 -0
  798. package/pennyfarthing-dist/pf/bikerack/__pycache__/tui.cpython-314.pyc +0 -0
  799. package/pennyfarthing-dist/pf/bikerack/__pycache__/ws_client.cpython-314.pyc +0 -0
  800. package/pennyfarthing-dist/pf/bikerack/audit_log_panel.py +0 -161
  801. package/pennyfarthing-dist/pf/bikerack/background_panel.py +0 -162
  802. package/pennyfarthing-dist/pf/bikerack/base_panel.py +0 -184
  803. package/pennyfarthing-dist/pf/bikerack/context_meter_footer.py +0 -138
  804. package/pennyfarthing-dist/pf/bikerack/debug_panel.py +0 -248
  805. package/pennyfarthing-dist/pf/bikerack/launcher.py +0 -240
  806. package/pennyfarthing-dist/pf/bikerack/portrait_resolver.py +0 -160
  807. package/pennyfarthing-dist/pf/bikerack/progress_panel.py +0 -314
  808. package/pennyfarthing-dist/pf/bikerack/sprint_panel.py +0 -492
  809. package/pennyfarthing-dist/pf/bikerack/story_detail_data.py +0 -247
  810. package/pennyfarthing-dist/pf/bikerack/story_detail_screen.py +0 -177
  811. package/pennyfarthing-dist/pf/bikerack/tui.py +0 -920
  812. package/pennyfarthing-dist/pf/bikerack/ws_client.py +0 -176
  813. package/pennyfarthing-dist/pf/bmad/__pycache__/__init__.cpython-314.pyc +0 -0
  814. package/pennyfarthing-dist/pf/bmad/__pycache__/cli.cpython-314.pyc +0 -0
  815. package/pennyfarthing-dist/pf/bmad/__pycache__/parser.cpython-314.pyc +0 -0
  816. package/pennyfarthing-dist/pf/bmad/cli.py +0 -197
  817. package/pennyfarthing-dist/pf/bmad/importer.py +0 -200
  818. package/pennyfarthing-dist/pf/bmad/parser.py +0 -239
  819. package/pennyfarthing-dist/pf/bmad/sync.py +0 -464
  820. package/pennyfarthing-dist/pf/bmad/test_parser.py +0 -253
  821. package/pennyfarthing-dist/pf/bmad/test_sync.py +0 -223
  822. package/pennyfarthing-dist/pf/brownfield/__main__.py +0 -8
  823. package/pennyfarthing-dist/pf/brownfield/cli.py +0 -130
  824. package/pennyfarthing-dist/pf/cli.py +0 -354
  825. package/pennyfarthing-dist/pf/codemarkers/__pycache__/__init__.cpython-314.pyc +0 -0
  826. package/pennyfarthing-dist/pf/codemarkers/__pycache__/analyze.cpython-314.pyc +0 -0
  827. package/pennyfarthing-dist/pf/codemarkers/__pycache__/models.cpython-314.pyc +0 -0
  828. package/pennyfarthing-dist/pf/common/__init__.py +0 -48
  829. package/pennyfarthing-dist/pf/common/__pycache__/__init__.cpython-314.pyc +0 -0
  830. package/pennyfarthing-dist/pf/common/__pycache__/config.cpython-314.pyc +0 -0
  831. package/pennyfarthing-dist/pf/common/__pycache__/output.cpython-314.pyc +0 -0
  832. package/pennyfarthing-dist/pf/common/__pycache__/pr_config.cpython-314.pyc +0 -0
  833. package/pennyfarthing-dist/pf/common/__pycache__/themes.cpython-314.pyc +0 -0
  834. package/pennyfarthing-dist/pf/common/config.py +0 -119
  835. package/pennyfarthing-dist/pf/common/themes.py +0 -253
  836. package/pennyfarthing-dist/pf/complexity/__pycache__/__init__.cpython-314.pyc +0 -0
  837. package/pennyfarthing-dist/pf/complexity/__pycache__/analyze.cpython-314.pyc +0 -0
  838. package/pennyfarthing-dist/pf/complexity/__pycache__/models.cpython-314.pyc +0 -0
  839. package/pennyfarthing-dist/pf/config.py +0 -21
  840. package/pennyfarthing-dist/pf/consultation/__pycache__/__init__.cpython-314.pyc +0 -0
  841. package/pennyfarthing-dist/pf/consultation/__pycache__/cli.cpython-314.pyc +0 -0
  842. package/pennyfarthing-dist/pf/context.py +0 -413
  843. package/pennyfarthing-dist/pf/deadcode/__pycache__/__init__.cpython-314.pyc +0 -0
  844. package/pennyfarthing-dist/pf/deadcode/__pycache__/analyze.cpython-314.pyc +0 -0
  845. package/pennyfarthing-dist/pf/deadcode/__pycache__/cli.cpython-314.pyc +0 -0
  846. package/pennyfarthing-dist/pf/deadcode/__pycache__/models.cpython-314.pyc +0 -0
  847. package/pennyfarthing-dist/pf/dependencies/__pycache__/__init__.cpython-314.pyc +0 -0
  848. package/pennyfarthing-dist/pf/dependencies/__pycache__/analyze.cpython-314.pyc +0 -0
  849. package/pennyfarthing-dist/pf/dependencies/__pycache__/models.cpython-314.pyc +0 -0
  850. package/pennyfarthing-dist/pf/epic/__pycache__/__init__.cpython-314.pyc +0 -0
  851. package/pennyfarthing-dist/pf/epic/__pycache__/cli.cpython-314.pyc +0 -0
  852. package/pennyfarthing-dist/pf/git/hooks_installer.py +0 -151
  853. package/pennyfarthing-dist/pf/git_group/__pycache__/__init__.cpython-314.pyc +0 -0
  854. package/pennyfarthing-dist/pf/git_group/__pycache__/cli.cpython-314.pyc +0 -0
  855. package/pennyfarthing-dist/pf/handoff/__pycache__/__init__.cpython-314.pyc +0 -0
  856. package/pennyfarthing-dist/pf/handoff/__pycache__/cli.cpython-314.pyc +0 -0
  857. package/pennyfarthing-dist/pf/handoff/__pycache__/complete_phase.cpython-314.pyc +0 -0
  858. package/pennyfarthing-dist/pf/handoff/__pycache__/marker.cpython-314.pyc +0 -0
  859. package/pennyfarthing-dist/pf/handoff/__pycache__/phase_check.cpython-314.pyc +0 -0
  860. package/pennyfarthing-dist/pf/handoff/__pycache__/resolve_gate.cpython-314.pyc +0 -0
  861. package/pennyfarthing-dist/pf/handoff/gate_file.py +0 -105
  862. package/pennyfarthing-dist/pf/healthscore/__pycache__/__init__.cpython-314.pyc +0 -0
  863. package/pennyfarthing-dist/pf/healthscore/__pycache__/__main__.cpython-314.pyc +0 -0
  864. package/pennyfarthing-dist/pf/healthscore/__pycache__/analyze.cpython-314.pyc +0 -0
  865. package/pennyfarthing-dist/pf/healthscore/__pycache__/cli.cpython-314.pyc +0 -0
  866. package/pennyfarthing-dist/pf/healthscore/__pycache__/formatters.cpython-314.pyc +0 -0
  867. package/pennyfarthing-dist/pf/healthscore/__pycache__/models.cpython-314.pyc +0 -0
  868. package/pennyfarthing-dist/pf/hooks/__init__.py +0 -442
  869. package/pennyfarthing-dist/pf/hooks/__pycache__/__init__.cpython-314.pyc +0 -0
  870. package/pennyfarthing-dist/pf/hooks/__pycache__/bell_mode.cpython-314.pyc +0 -0
  871. package/pennyfarthing-dist/pf/hooks/__pycache__/cli.cpython-314.pyc +0 -0
  872. package/pennyfarthing-dist/pf/hooks/__pycache__/context_breaker.cpython-314.pyc +0 -0
  873. package/pennyfarthing-dist/pf/hooks/__pycache__/context_warning.cpython-314.pyc +0 -0
  874. package/pennyfarthing-dist/pf/hooks/__pycache__/cyclist_pretooluse.cpython-314.pyc +0 -0
  875. package/pennyfarthing-dist/pf/hooks/__pycache__/pre_edit_check.cpython-314.pyc +0 -0
  876. package/pennyfarthing-dist/pf/hooks/__pycache__/reflector_check.cpython-314.pyc +0 -0
  877. package/pennyfarthing-dist/pf/hooks/__pycache__/schema_validation.cpython-314.pyc +0 -0
  878. package/pennyfarthing-dist/pf/hooks/__pycache__/session_start.cpython-314.pyc +0 -0
  879. package/pennyfarthing-dist/pf/hooks/__pycache__/session_stop.cpython-314.pyc +0 -0
  880. package/pennyfarthing-dist/pf/hooks/__pycache__/sprint_yaml_validation.cpython-314.pyc +0 -0
  881. package/pennyfarthing-dist/pf/hooks/__pycache__/statusline.cpython-314.pyc +0 -0
  882. package/pennyfarthing-dist/pf/hooks/cyclist_pretooluse.py +0 -129
  883. package/pennyfarthing-dist/pf/hooks/session_start.py +0 -294
  884. package/pennyfarthing-dist/pf/hooks/statusline.py +0 -429
  885. package/pennyfarthing-dist/pf/hooks.py +0 -32
  886. package/pennyfarthing-dist/pf/hotspots/__pycache__/__init__.cpython-314.pyc +0 -0
  887. package/pennyfarthing-dist/pf/hotspots/__pycache__/analyze.cpython-314.pyc +0 -0
  888. package/pennyfarthing-dist/pf/hotspots/__pycache__/cli.cpython-314.pyc +0 -0
  889. package/pennyfarthing-dist/pf/hotspots/__pycache__/models.cpython-314.pyc +0 -0
  890. package/pennyfarthing-dist/pf/jira/__init__.py +0 -97
  891. package/pennyfarthing-dist/pf/jira/__pycache__/__init__.cpython-314.pyc +0 -0
  892. package/pennyfarthing-dist/pf/jira/__pycache__/bidirectional.cpython-314.pyc +0 -0
  893. package/pennyfarthing-dist/pf/jira/__pycache__/claim.cpython-314.pyc +0 -0
  894. package/pennyfarthing-dist/pf/jira/__pycache__/cli.cpython-314.pyc +0 -0
  895. package/pennyfarthing-dist/pf/jira/__pycache__/client.cpython-314.pyc +0 -0
  896. package/pennyfarthing-dist/pf/jira/__pycache__/create.cpython-314.pyc +0 -0
  897. package/pennyfarthing-dist/pf/jira/__pycache__/epic.cpython-314.pyc +0 -0
  898. package/pennyfarthing-dist/pf/jira/__pycache__/operations.cpython-314.pyc +0 -0
  899. package/pennyfarthing-dist/pf/jira/__pycache__/reconcile.cpython-314.pyc +0 -0
  900. package/pennyfarthing-dist/pf/jira/__pycache__/story.cpython-314.pyc +0 -0
  901. package/pennyfarthing-dist/pf/jira/__pycache__/sync.cpython-314.pyc +0 -0
  902. package/pennyfarthing-dist/pf/jira/claim.py +0 -232
  903. package/pennyfarthing-dist/pf/jira/cli.py +0 -362
  904. package/pennyfarthing-dist/pf/jira/client.py +0 -802
  905. package/pennyfarthing-dist/pf/jira/reconcile.py +0 -276
  906. package/pennyfarthing-dist/pf/jira/story.py +0 -221
  907. package/pennyfarthing-dist/pf/jira/sync.py +0 -350
  908. package/pennyfarthing-dist/pf/jira_bidirectional_sync.py +0 -37
  909. package/pennyfarthing-dist/pf/jira_epic_creation.py +0 -30
  910. package/pennyfarthing-dist/pf/jira_sync.py +0 -36
  911. package/pennyfarthing-dist/pf/jira_sync_story.py +0 -30
  912. package/pennyfarthing-dist/pf/launch/__pycache__/__init__.cpython-314.pyc +0 -0
  913. package/pennyfarthing-dist/pf/launch/__pycache__/cli.cpython-314.pyc +0 -0
  914. package/pennyfarthing-dist/pf/migration/__init__.py +0 -39
  915. package/pennyfarthing-dist/pf/migration/__main__.py +0 -10
  916. package/pennyfarthing-dist/pf/migration/cli.py +0 -304
  917. package/pennyfarthing-dist/pf/output.py +0 -37
  918. package/pennyfarthing-dist/pf/prime/__pycache__/__init__.cpython-314.pyc +0 -0
  919. package/pennyfarthing-dist/pf/prime/__pycache__/cli.cpython-314.pyc +0 -0
  920. package/pennyfarthing-dist/pf/prime/__pycache__/loader.cpython-314.pyc +0 -0
  921. package/pennyfarthing-dist/pf/prime/__pycache__/models.cpython-314.pyc +0 -0
  922. package/pennyfarthing-dist/pf/prime/__pycache__/persona.cpython-314.pyc +0 -0
  923. package/pennyfarthing-dist/pf/prime/__pycache__/session.cpython-314.pyc +0 -0
  924. package/pennyfarthing-dist/pf/prime/__pycache__/tiers.cpython-314.pyc +0 -0
  925. package/pennyfarthing-dist/pf/prime/__pycache__/workflow.cpython-314.pyc +0 -0
  926. package/pennyfarthing-dist/pf/prime/cli.py +0 -662
  927. package/pennyfarthing-dist/pf/prime/loader.py +0 -308
  928. package/pennyfarthing-dist/pf/prime/persona.py +0 -311
  929. package/pennyfarthing-dist/pf/prime/workflow.py +0 -317
  930. package/pennyfarthing-dist/pf/session/__pycache__/__init__.cpython-314.pyc +0 -0
  931. package/pennyfarthing-dist/pf/session/__pycache__/cli.cpython-314.pyc +0 -0
  932. package/pennyfarthing-dist/pf/settings/__pycache__/__init__.cpython-314.pyc +0 -0
  933. package/pennyfarthing-dist/pf/settings/__pycache__/cli.cpython-314.pyc +0 -0
  934. package/pennyfarthing-dist/pf/settings/__pycache__/settings.cpython-314.pyc +0 -0
  935. package/pennyfarthing-dist/pf/settings/settings.py +0 -134
  936. package/pennyfarthing-dist/pf/sprint/__pycache__/__init__.cpython-314.pyc +0 -0
  937. package/pennyfarthing-dist/pf/sprint/__pycache__/archive.cpython-314.pyc +0 -0
  938. package/pennyfarthing-dist/pf/sprint/__pycache__/archive_epic.cpython-314.pyc +0 -0
  939. package/pennyfarthing-dist/pf/sprint/__pycache__/cli.cpython-314.pyc +0 -0
  940. package/pennyfarthing-dist/pf/sprint/__pycache__/epic_add.cpython-314.pyc +0 -0
  941. package/pennyfarthing-dist/pf/sprint/__pycache__/epic_update.cpython-314.pyc +0 -0
  942. package/pennyfarthing-dist/pf/sprint/__pycache__/loader.cpython-314.pyc +0 -0
  943. package/pennyfarthing-dist/pf/sprint/__pycache__/status.cpython-314.pyc +0 -0
  944. package/pennyfarthing-dist/pf/sprint/__pycache__/story_add.cpython-314.pyc +0 -0
  945. package/pennyfarthing-dist/pf/sprint/__pycache__/story_finish.cpython-314.pyc +0 -0
  946. package/pennyfarthing-dist/pf/sprint/__pycache__/story_update.cpython-314.pyc +0 -0
  947. package/pennyfarthing-dist/pf/sprint/__pycache__/validate_cmd.cpython-314.pyc +0 -0
  948. package/pennyfarthing-dist/pf/sprint/__pycache__/validator.cpython-314.pyc +0 -0
  949. package/pennyfarthing-dist/pf/sprint/__pycache__/work.cpython-314.pyc +0 -0
  950. package/pennyfarthing-dist/pf/sprint/__pycache__/yaml_io.cpython-314.pyc +0 -0
  951. package/pennyfarthing-dist/pf/sprint/archive.py +0 -164
  952. package/pennyfarthing-dist/pf/sprint/cli.py +0 -2021
  953. package/pennyfarthing-dist/pf/sprint/loader.py +0 -441
  954. package/pennyfarthing-dist/pf/sprint/status.py +0 -121
  955. package/pennyfarthing-dist/pf/sprint/story_finish.py +0 -239
  956. package/pennyfarthing-dist/pf/sprint/story_update.py +0 -222
  957. package/pennyfarthing-dist/pf/sprint/yaml_io.py +0 -433
  958. package/pennyfarthing-dist/pf/tests/__pycache__/__init__.cpython-314.pyc +0 -0
  959. package/pennyfarthing-dist/pf/tests/__pycache__/conftest.cpython-314-pytest-9.0.2.pyc +0 -0
  960. package/pennyfarthing-dist/pf/tests/__pycache__/test_sprint_validator.cpython-314-pytest-9.0.2.pyc +0 -0
  961. package/pennyfarthing-dist/pf/tests/conftest.py +0 -105
  962. package/pennyfarthing-dist/pf/tests/test_108_1_gate_migration.py +0 -540
  963. package/pennyfarthing-dist/pf/tests/test_108_2_remove_handoff_fallback.py +0 -339
  964. package/pennyfarthing-dist/pf/tests/test_cli_modules.py +0 -241
  965. package/pennyfarthing-dist/pf/tests/test_codemarkers.py +0 -687
  966. package/pennyfarthing-dist/pf/tests/test_confidence_sm_evaluation.py +0 -253
  967. package/pennyfarthing-dist/pf/tests/test_confidence_sm_gate.py +0 -316
  968. package/pennyfarthing-dist/pf/tests/test_jira_package.py +0 -331
  969. package/pennyfarthing-dist/pf/tests/test_package_structure.py +0 -359
  970. package/pennyfarthing-dist/pf/tests/test_resolve_gate_file_field.py +0 -460
  971. package/pennyfarthing-dist/pf/tests/test_sprint_panel.py +0 -610
  972. package/pennyfarthing-dist/pf/theme/__main__.py +0 -6
  973. package/pennyfarthing-dist/pf/theme/__pycache__/__init__.cpython-314.pyc +0 -0
  974. package/pennyfarthing-dist/pf/theme/__pycache__/cli.cpython-314.pyc +0 -0
  975. package/pennyfarthing-dist/pf/theme/cli.py +0 -310
  976. package/pennyfarthing-dist/pf/validate/__pycache__/__init__.cpython-314.pyc +0 -0
  977. package/pennyfarthing-dist/pf/validate/__pycache__/cli.cpython-314.pyc +0 -0
  978. package/pennyfarthing-dist/pf/validate/adapters/agent.py +0 -239
  979. package/pennyfarthing-dist/pf/validate/adapters/skill_command.py +0 -491
  980. package/pennyfarthing-dist/pf/validate/adapters/tandem_awareness.py +0 -254
  981. package/pennyfarthing-dist/pf/validate/adapters/team_mode.py +0 -323
  982. package/pennyfarthing-dist/pf/validate/adapters/workflow.py +0 -403
  983. package/pennyfarthing-dist/pf/workflow/__pycache__/__init__.cpython-314.pyc +0 -0
  984. package/pennyfarthing-dist/pf/workflow/__pycache__/cli.cpython-314.pyc +0 -0
  985. package/pennyfarthing-dist/pf/workflow/__pycache__/helpers.cpython-314.pyc +0 -0
  986. package/pennyfarthing-dist/pf/workflow/__pycache__/scale.cpython-314.pyc +0 -0
  987. package/pennyfarthing-dist/pf/workflow/__pycache__/state.cpython-314.pyc +0 -0
  988. package/pennyfarthing-dist/pf/workflow/cli.py +0 -1101
  989. package/pennyfarthing-dist/scripts/core/pf.sh +0 -5
  990. package/pennyfarthing-dist/scripts/lib/background-tasks.sh +0 -177
  991. package/pennyfarthing-dist/scripts/lib/run-pf.sh +0 -44
  992. package/pennyfarthing-dist/skills/pf-permissions/skill.md +0 -157
  993. package/pennyfarthing-dist/templates/persona-config.yaml.template +0 -22
  994. package/scripts/postinstall.cjs +0 -34
  995. /package/pennyfarthing-dist/{pf → src/pf}/README.md +0 -0
  996. /package/pennyfarthing-dist/{pf → src/pf}/bc/__init__.py +0 -0
  997. /package/pennyfarthing-dist/{pf → src/pf}/bc/split.py +0 -0
  998. /package/pennyfarthing-dist/{pf → src/pf}/bellmode_hook.py +0 -0
  999. /package/pennyfarthing-dist/{pf → src/pf}/bikerack/__init__.py +0 -0
  1000. /package/pennyfarthing-dist/{pf → src/pf}/bikerack/__main__.py +0 -0
  1001. /package/pennyfarthing-dist/{pf → src/pf}/bikerack/cli.py +0 -0
  1002. /package/pennyfarthing-dist/{pf → src/pf}/bikerack/diffs_panel.py +0 -0
  1003. /package/pennyfarthing-dist/{pf → src/pf}/bikerack/events.py +0 -0
  1004. /package/pennyfarthing-dist/{pf → src/pf}/bikerack/git_panel.py +0 -0
  1005. /package/pennyfarthing-dist/{pf → src/pf}/bmad/__init__.py +0 -0
  1006. /package/pennyfarthing-dist/{pf → src/pf}/brownfield/__init__.py +0 -0
  1007. /package/pennyfarthing-dist/{pf → src/pf}/brownfield/discover.py +0 -0
  1008. /package/pennyfarthing-dist/{pf → src/pf}/codemarkers/__init__.py +0 -0
  1009. /package/pennyfarthing-dist/{pf → src/pf}/codemarkers/__main__.py +0 -0
  1010. /package/pennyfarthing-dist/{pf → src/pf}/codemarkers/analyze.py +0 -0
  1011. /package/pennyfarthing-dist/{pf → src/pf}/codemarkers/cli.py +0 -0
  1012. /package/pennyfarthing-dist/{pf → src/pf}/codemarkers/formatters.py +0 -0
  1013. /package/pennyfarthing-dist/{pf → src/pf}/codemarkers/models.py +0 -0
  1014. /package/pennyfarthing-dist/{pf → src/pf}/common/output.py +0 -0
  1015. /package/pennyfarthing-dist/{pf → src/pf}/common/pr_config.py +0 -0
  1016. /package/pennyfarthing-dist/{pf → src/pf}/complexity/__init__.py +0 -0
  1017. /package/pennyfarthing-dist/{pf → src/pf}/complexity/__main__.py +0 -0
  1018. /package/pennyfarthing-dist/{pf → src/pf}/complexity/analyze.py +0 -0
  1019. /package/pennyfarthing-dist/{pf → src/pf}/complexity/cli.py +0 -0
  1020. /package/pennyfarthing-dist/{pf → src/pf}/complexity/formatters.py +0 -0
  1021. /package/pennyfarthing-dist/{pf → src/pf}/complexity/models.py +0 -0
  1022. /package/pennyfarthing-dist/{pf → src/pf}/consultation/__init__.py +0 -0
  1023. /package/pennyfarthing-dist/{pf → src/pf}/consultation/cli.py +0 -0
  1024. /package/pennyfarthing-dist/{pf → src/pf}/consultation/dialogue_manager.py +0 -0
  1025. /package/pennyfarthing-dist/{pf → src/pf}/deadcode/__init__.py +0 -0
  1026. /package/pennyfarthing-dist/{pf → src/pf}/deadcode/__main__.py +0 -0
  1027. /package/pennyfarthing-dist/{pf → src/pf}/deadcode/analyze.py +0 -0
  1028. /package/pennyfarthing-dist/{pf → src/pf}/deadcode/cli.py +0 -0
  1029. /package/pennyfarthing-dist/{pf → src/pf}/deadcode/formatters.py +0 -0
  1030. /package/pennyfarthing-dist/{pf → src/pf}/deadcode/models.py +0 -0
  1031. /package/pennyfarthing-dist/{pf → src/pf}/dependencies/__init__.py +0 -0
  1032. /package/pennyfarthing-dist/{pf → src/pf}/dependencies/__main__.py +0 -0
  1033. /package/pennyfarthing-dist/{pf → src/pf}/dependencies/analyze.py +0 -0
  1034. /package/pennyfarthing-dist/{pf → src/pf}/dependencies/cli.py +0 -0
  1035. /package/pennyfarthing-dist/{pf → src/pf}/dependencies/formatters.py +0 -0
  1036. /package/pennyfarthing-dist/{pf → src/pf}/dependencies/models.py +0 -0
  1037. /package/pennyfarthing-dist/{pf → src/pf}/epic/__init__.py +0 -0
  1038. /package/pennyfarthing-dist/{pf → src/pf}/epic/cli.py +0 -0
  1039. /package/pennyfarthing-dist/{pf → src/pf}/gate/__init__.py +0 -0
  1040. /package/pennyfarthing-dist/{pf → src/pf}/gate/cli.py +0 -0
  1041. /package/pennyfarthing-dist/{pf → src/pf}/gate/validate.py +0 -0
  1042. /package/pennyfarthing-dist/{pf → src/pf}/git/__init__.py +0 -0
  1043. /package/pennyfarthing-dist/{pf → src/pf}/git/create_branches.py +0 -0
  1044. /package/pennyfarthing-dist/{pf → src/pf}/git/repos.py +0 -0
  1045. /package/pennyfarthing-dist/{pf → src/pf}/git/status_all.py +0 -0
  1046. /package/pennyfarthing-dist/{pf → src/pf}/git/worktree.py +0 -0
  1047. /package/pennyfarthing-dist/{pf → src/pf}/git_group/__init__.py +0 -0
  1048. /package/pennyfarthing-dist/{pf → src/pf}/git_group/cli.py +0 -0
  1049. /package/pennyfarthing-dist/{pf → src/pf}/handoff/__init__.py +0 -0
  1050. /package/pennyfarthing-dist/{pf → src/pf}/handoff/cli.py +0 -0
  1051. /package/pennyfarthing-dist/{pf → src/pf}/handoff/complete_phase.py +0 -0
  1052. /package/pennyfarthing-dist/{pf → src/pf}/handoff/gate_runner.py +0 -0
  1053. /package/pennyfarthing-dist/{pf → src/pf}/handoff/marker.py +0 -0
  1054. /package/pennyfarthing-dist/{pf → src/pf}/handoff/phase_check.py +0 -0
  1055. /package/pennyfarthing-dist/{pf → src/pf}/handoff/resolve_gate.py +0 -0
  1056. /package/pennyfarthing-dist/{pf → src/pf}/healthscore/__init__.py +0 -0
  1057. /package/pennyfarthing-dist/{pf → src/pf}/healthscore/__main__.py +0 -0
  1058. /package/pennyfarthing-dist/{pf → src/pf}/healthscore/analyze.py +0 -0
  1059. /package/pennyfarthing-dist/{pf → src/pf}/healthscore/cli.py +0 -0
  1060. /package/pennyfarthing-dist/{pf → src/pf}/healthscore/formatters.py +0 -0
  1061. /package/pennyfarthing-dist/{pf → src/pf}/healthscore/models.py +0 -0
  1062. /package/pennyfarthing-dist/{pf → src/pf}/hooks/bell_mode.py +0 -0
  1063. /package/pennyfarthing-dist/{pf → src/pf}/hooks/cli.py +0 -0
  1064. /package/pennyfarthing-dist/{pf → src/pf}/hooks/context_breaker.py +0 -0
  1065. /package/pennyfarthing-dist/{pf → src/pf}/hooks/context_warning.py +0 -0
  1066. /package/pennyfarthing-dist/{pf → src/pf}/hooks/cyclist-pretooluse-hook.sh +0 -0
  1067. /package/pennyfarthing-dist/{pf → src/pf}/hooks/pre_edit_check.py +0 -0
  1068. /package/pennyfarthing-dist/{pf → src/pf}/hooks/reflector_check.py +0 -0
  1069. /package/pennyfarthing-dist/{pf → src/pf}/hooks/schema_validation.py +0 -0
  1070. /package/pennyfarthing-dist/{pf → src/pf}/hooks/session_stop.py +0 -0
  1071. /package/pennyfarthing-dist/{pf → src/pf}/hooks/sprint_yaml_validation.py +0 -0
  1072. /package/pennyfarthing-dist/{pf → src/pf}/hotspots/__init__.py +0 -0
  1073. /package/pennyfarthing-dist/{pf → src/pf}/hotspots/__main__.py +0 -0
  1074. /package/pennyfarthing-dist/{pf → src/pf}/hotspots/analyze.py +0 -0
  1075. /package/pennyfarthing-dist/{pf → src/pf}/hotspots/cli.py +0 -0
  1076. /package/pennyfarthing-dist/{pf → src/pf}/hotspots/formatters.py +0 -0
  1077. /package/pennyfarthing-dist/{pf → src/pf}/hotspots/models.py +0 -0
  1078. /package/pennyfarthing-dist/{pf → src/pf}/jira/__main__.py +0 -0
  1079. /package/pennyfarthing-dist/{pf → src/pf}/jira/bidirectional.py +0 -0
  1080. /package/pennyfarthing-dist/{pf → src/pf}/jira/create.py +0 -0
  1081. /package/pennyfarthing-dist/{pf → src/pf}/jira/epic.py +0 -0
  1082. /package/pennyfarthing-dist/{pf → src/pf}/jira/operations.py +0 -0
  1083. /package/pennyfarthing-dist/{pf → src/pf}/launch/__init__.py +0 -0
  1084. /package/pennyfarthing-dist/{pf → src/pf}/launch/cli.py +0 -0
  1085. /package/pennyfarthing-dist/{pf → src/pf}/migration/session.py +0 -0
  1086. /package/pennyfarthing-dist/{pf → src/pf}/migration/skill.py +0 -0
  1087. /package/pennyfarthing-dist/{pf → src/pf}/migration/step.py +0 -0
  1088. /package/pennyfarthing-dist/{pf → src/pf}/migration/validate.py +0 -0
  1089. /package/pennyfarthing-dist/{pf/session → src/pf/package}/__init__.py +0 -0
  1090. /package/pennyfarthing-dist/{pf → src/pf}/patch_mode.py +0 -0
  1091. /package/pennyfarthing-dist/{pf → src/pf}/preflight/__init__.py +0 -0
  1092. /package/pennyfarthing-dist/{pf → src/pf}/preflight/__main__.py +0 -0
  1093. /package/pennyfarthing-dist/{pf → src/pf}/preflight/cli.py +0 -0
  1094. /package/pennyfarthing-dist/{pf → src/pf}/preflight/finish.py +0 -0
  1095. /package/pennyfarthing-dist/{pf → src/pf}/pretooluse_hook.py +0 -0
  1096. /package/pennyfarthing-dist/{pf → src/pf}/prime/__init__.py +0 -0
  1097. /package/pennyfarthing-dist/{pf → src/pf}/prime/__main__.py +0 -0
  1098. /package/pennyfarthing-dist/{pf → src/pf}/prime/heatmap.py +0 -0
  1099. /package/pennyfarthing-dist/{pf → src/pf}/prime/models.py +0 -0
  1100. /package/pennyfarthing-dist/{pf → src/pf}/prime/session.py +0 -0
  1101. /package/pennyfarthing-dist/{pf → src/pf}/prime/tiers.py +0 -0
  1102. /package/pennyfarthing-dist/{pf → src/pf}/prime/version_sentinel.py +0 -0
  1103. /package/pennyfarthing-dist/{pf/settings → src/pf/release}/__init__.py +0 -0
  1104. /package/pennyfarthing-dist/{pf → src/pf}/schema_validation_hook.py +0 -0
  1105. /package/pennyfarthing-dist/{pf/validate/adapters → src/pf/session}/__init__.py +0 -0
  1106. /package/pennyfarthing-dist/{pf → src/pf}/session/cli.py +0 -0
  1107. /package/pennyfarthing-dist/{pf → src/pf}/session_start_hook.py +0 -0
  1108. /package/pennyfarthing-dist/{pf → src/pf}/settings/cli.py +0 -0
  1109. /package/pennyfarthing-dist/{pf → src/pf}/sprint/__init__.py +0 -0
  1110. /package/pennyfarthing-dist/{pf → src/pf}/sprint/__main__.py +0 -0
  1111. /package/pennyfarthing-dist/{pf → src/pf}/sprint/archive_epic.py +0 -0
  1112. /package/pennyfarthing-dist/{pf → src/pf}/sprint/epic_add.py +0 -0
  1113. /package/pennyfarthing-dist/{pf → src/pf}/sprint/epic_update.py +0 -0
  1114. /package/pennyfarthing-dist/{pf → src/pf}/sprint/import_epic.py +0 -0
  1115. /package/pennyfarthing-dist/{pf → src/pf}/sprint/story_add.py +0 -0
  1116. /package/pennyfarthing-dist/{pf → src/pf}/sprint/validate_cmd.py +0 -0
  1117. /package/pennyfarthing-dist/{pf → src/pf}/sprint/validator.py +0 -0
  1118. /package/pennyfarthing-dist/{pf → src/pf}/sprint/work.py +0 -0
  1119. /package/pennyfarthing-dist/{pf → src/pf}/story/__init__.py +0 -0
  1120. /package/pennyfarthing-dist/{pf → src/pf}/story/__main__.py +0 -0
  1121. /package/pennyfarthing-dist/{pf → src/pf}/story/cli.py +0 -0
  1122. /package/pennyfarthing-dist/{pf → src/pf}/story/create.py +0 -0
  1123. /package/pennyfarthing-dist/{pf → src/pf}/story/size.py +0 -0
  1124. /package/pennyfarthing-dist/{pf → src/pf}/story/template.py +0 -0
  1125. /package/pennyfarthing-dist/{pf → src/pf}/swebench.py +0 -0
  1126. /package/pennyfarthing-dist/{pf → src/pf}/tests/__init__.py +0 -0
  1127. /package/pennyfarthing-dist/{pf → src/pf}/tests/test_archive_epic.py +0 -0
  1128. /package/pennyfarthing-dist/{pf → src/pf}/tests/test_bc.py +0 -0
  1129. /package/pennyfarthing-dist/{pf → src/pf}/tests/test_bikerack.py +0 -0
  1130. /package/pennyfarthing-dist/{pf → src/pf}/tests/test_brownfield.py +0 -0
  1131. /package/pennyfarthing-dist/{pf → src/pf}/tests/test_cli_normalization.py +0 -0
  1132. /package/pennyfarthing-dist/{pf → src/pf}/tests/test_common.py +0 -0
  1133. /package/pennyfarthing-dist/{pf → src/pf}/tests/test_dialogue_manager.py +0 -0
  1134. /package/pennyfarthing-dist/{pf → src/pf}/tests/test_epic_shard_validation.py +0 -0
  1135. /package/pennyfarthing-dist/{pf → src/pf}/tests/test_gate_file_resolution.py +0 -0
  1136. /package/pennyfarthing-dist/{pf → src/pf}/tests/test_gate_runner.py +0 -0
  1137. /package/pennyfarthing-dist/{pf → src/pf}/tests/test_git_utils.py +0 -0
  1138. /package/pennyfarthing-dist/{pf → src/pf}/tests/test_handoff_cli.py +0 -0
  1139. /package/pennyfarthing-dist/{pf → src/pf}/tests/test_handoff_e2e.py +0 -0
  1140. /package/pennyfarthing-dist/{pf → src/pf}/tests/test_healthscore.py +0 -0
  1141. /package/pennyfarthing-dist/{pf → src/pf}/tests/test_patch_mode.py +0 -0
  1142. /package/pennyfarthing-dist/{pf → src/pf}/tests/test_prime.py +0 -0
  1143. /package/pennyfarthing-dist/{pf → src/pf}/tests/test_sprint_package.py +0 -0
  1144. /package/pennyfarthing-dist/{pf → src/pf}/tests/test_sprint_validator.py +0 -0
  1145. /package/pennyfarthing-dist/{pf → src/pf}/tests/test_story_add.py +0 -0
  1146. /package/pennyfarthing-dist/{pf → src/pf}/tests/test_story_package.py +0 -0
  1147. /package/pennyfarthing-dist/{pf → src/pf}/tests/test_story_update.py +0 -0
  1148. /package/pennyfarthing-dist/{pf → src/pf}/tests/test_tiers.py +0 -0
  1149. /package/pennyfarthing-dist/{pf → src/pf}/tests/test_token_counting.py +0 -0
  1150. /package/pennyfarthing-dist/{pf → src/pf}/tests/test_topology_loader.py +0 -0
  1151. /package/pennyfarthing-dist/{pf → src/pf}/tests/test_tui_focus.py +0 -0
  1152. /package/pennyfarthing-dist/{pf → src/pf}/tests/test_tui_panel_persistence.py +0 -0
  1153. /package/pennyfarthing-dist/{pf → src/pf}/tests/test_validate_cmd.py +0 -0
  1154. /package/pennyfarthing-dist/{pf → src/pf}/tests/test_version_sentinel.py +0 -0
  1155. /package/pennyfarthing-dist/{pf → src/pf}/tests/test_workflow_check.py +0 -0
  1156. /package/pennyfarthing-dist/{pf → src/pf}/tests/test_workflow_list_team.py +0 -0
  1157. /package/pennyfarthing-dist/{pf → src/pf}/tests/test_yaml_io.py +0 -0
  1158. /package/pennyfarthing-dist/{pf → src/pf}/theme/__init__.py +0 -0
  1159. /package/pennyfarthing-dist/{pf → src/pf}/validate/__init__.py +0 -0
  1160. /package/pennyfarthing-dist/{pf → src/pf}/validate/adapters/schema.py +0 -0
  1161. /package/pennyfarthing-dist/{pf → src/pf}/validate/adapters/sprint.py +0 -0
  1162. /package/pennyfarthing-dist/{pf → src/pf}/validate/cli.py +0 -0
  1163. /package/pennyfarthing-dist/{pf → src/pf}/welcome_hook.py +0 -0
  1164. /package/pennyfarthing-dist/{pf → src/pf}/workflow/__init__.py +0 -0
  1165. /package/pennyfarthing-dist/{pf → src/pf}/workflow/helpers.py +0 -0
  1166. /package/pennyfarthing-dist/{pf → src/pf}/workflow/scale.py +0 -0
  1167. /package/pennyfarthing-dist/{pf → src/pf}/workflow/state.py +0 -0
  1168. /package/pennyfarthing-dist/{pf → src/pf}/workflow/team_lifecycle.py +0 -0
@@ -1,2021 +0,0 @@
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()