@pennyfarthing/core 11.3.1 → 11.3.2

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 (408) hide show
  1. package/README.md +1 -1
  2. package/package.json +1 -1
  3. package/packages/core/dist/cli/commands/doctor.d.ts +9 -1
  4. package/packages/core/dist/cli/commands/doctor.d.ts.map +1 -1
  5. package/packages/core/dist/cli/commands/doctor.js +107 -51
  6. package/packages/core/dist/cli/commands/doctor.js.map +1 -1
  7. package/packages/core/dist/cli/commands/update.d.ts.map +1 -1
  8. package/packages/core/dist/cli/commands/update.js +1 -26
  9. package/packages/core/dist/cli/commands/update.js.map +1 -1
  10. package/packages/core/dist/cli/utils/python.d.ts +1 -0
  11. package/packages/core/dist/cli/utils/python.d.ts.map +1 -1
  12. package/packages/core/dist/cli/utils/python.js +11 -1
  13. package/packages/core/dist/cli/utils/python.js.map +1 -1
  14. package/packages/core/dist/cli/utils/settings-pf-wrapper.test.d.ts +16 -0
  15. package/packages/core/dist/cli/utils/settings-pf-wrapper.test.d.ts.map +1 -0
  16. package/packages/core/dist/cli/utils/settings-pf-wrapper.test.js +377 -0
  17. package/packages/core/dist/cli/utils/settings-pf-wrapper.test.js.map +1 -0
  18. package/packages/core/dist/server/paths.d.ts.map +1 -1
  19. package/packages/core/dist/server/paths.js +6 -0
  20. package/packages/core/dist/server/paths.js.map +1 -1
  21. package/packages/core/dist/workflow/team-lifecycle.d.ts +169 -0
  22. package/packages/core/dist/workflow/team-lifecycle.d.ts.map +1 -0
  23. package/packages/core/dist/workflow/team-lifecycle.js +217 -0
  24. package/packages/core/dist/workflow/team-lifecycle.js.map +1 -0
  25. package/packages/core/dist/workflow/team-lifecycle.test.d.ts +20 -0
  26. package/packages/core/dist/workflow/team-lifecycle.test.d.ts.map +1 -0
  27. package/packages/core/dist/workflow/team-lifecycle.test.js +966 -0
  28. package/packages/core/dist/workflow/team-lifecycle.test.js.map +1 -0
  29. package/packages/core/dist/workflow/workflow-graph-validation.d.ts +65 -0
  30. package/packages/core/dist/workflow/workflow-graph-validation.d.ts.map +1 -0
  31. package/packages/core/dist/workflow/workflow-graph-validation.js +190 -0
  32. package/packages/core/dist/workflow/workflow-graph-validation.js.map +1 -0
  33. package/packages/core/dist/workflow/workflow-graph-validation.test.d.ts +18 -0
  34. package/packages/core/dist/workflow/workflow-graph-validation.test.d.ts.map +1 -0
  35. package/packages/core/dist/workflow/workflow-graph-validation.test.js +706 -0
  36. package/packages/core/dist/workflow/workflow-graph-validation.test.js.map +1 -0
  37. package/pennyfarthing-dist/scripts/lib/run-pf.sh +3 -0
  38. package/pennyfarthing_scripts/__pycache__/__init__.cpython-314.pyc +0 -0
  39. package/pennyfarthing_scripts/__pycache__/bellmode_hook.cpython-314.pyc +0 -0
  40. package/pennyfarthing_scripts/__pycache__/cli.cpython-314.pyc +0 -0
  41. package/pennyfarthing_scripts/__pycache__/config.cpython-314.pyc +0 -0
  42. package/pennyfarthing_scripts/__pycache__/context.cpython-314.pyc +0 -0
  43. package/pennyfarthing_scripts/__pycache__/hooks.cpython-314.pyc +0 -0
  44. package/pennyfarthing_scripts/__pycache__/jira_bidirectional_sync.cpython-314.pyc +0 -0
  45. package/pennyfarthing_scripts/__pycache__/jira_epic_creation.cpython-314.pyc +0 -0
  46. package/pennyfarthing_scripts/__pycache__/jira_sync.cpython-314.pyc +0 -0
  47. package/pennyfarthing_scripts/__pycache__/jira_sync_story.cpython-314.pyc +0 -0
  48. package/pennyfarthing_scripts/__pycache__/output.cpython-314.pyc +0 -0
  49. package/pennyfarthing_scripts/__pycache__/patch_mode.cpython-314.pyc +0 -0
  50. package/pennyfarthing_scripts/__pycache__/pretooluse_hook.cpython-314.pyc +0 -0
  51. package/pennyfarthing_scripts/__pycache__/schema_validation_hook.cpython-314.pyc +0 -0
  52. package/pennyfarthing_scripts/__pycache__/session_start_hook.cpython-314.pyc +0 -0
  53. package/pennyfarthing_scripts/__pycache__/workflow.cpython-314.pyc +0 -0
  54. package/pennyfarthing_scripts/bc/__pycache__/__init__.cpython-314.pyc +0 -0
  55. package/pennyfarthing_scripts/bc/__pycache__/cli.cpython-314.pyc +0 -0
  56. package/pennyfarthing_scripts/bc/__pycache__/focus.cpython-314.pyc +0 -0
  57. package/pennyfarthing_scripts/bc/__pycache__/split.cpython-314.pyc +0 -0
  58. package/pennyfarthing_scripts/bikerack/__pycache__/__init__.cpython-314.pyc +0 -0
  59. package/pennyfarthing_scripts/bikerack/__pycache__/__main__.cpython-314.pyc +0 -0
  60. package/pennyfarthing_scripts/bikerack/__pycache__/audit_log_panel.cpython-314.pyc +0 -0
  61. package/pennyfarthing_scripts/bikerack/__pycache__/background_panel.cpython-314.pyc +0 -0
  62. package/pennyfarthing_scripts/bikerack/__pycache__/base_panel.cpython-314.pyc +0 -0
  63. package/pennyfarthing_scripts/bikerack/__pycache__/changed_panel.cpython-314.pyc +0 -0
  64. package/pennyfarthing_scripts/bikerack/__pycache__/cli.cpython-314.pyc +0 -0
  65. package/pennyfarthing_scripts/bikerack/__pycache__/context_meter_footer.cpython-314.pyc +0 -0
  66. package/pennyfarthing_scripts/bikerack/__pycache__/debug_panel.cpython-314.pyc +0 -0
  67. package/pennyfarthing_scripts/bikerack/__pycache__/diffs_panel.cpython-314.pyc +0 -0
  68. package/pennyfarthing_scripts/bikerack/__pycache__/events.cpython-314.pyc +0 -0
  69. package/pennyfarthing_scripts/bikerack/__pycache__/git_panel.cpython-314.pyc +0 -0
  70. package/pennyfarthing_scripts/bikerack/__pycache__/launcher.cpython-314.pyc +0 -0
  71. package/pennyfarthing_scripts/bikerack/__pycache__/portrait.cpython-314.pyc +0 -0
  72. package/pennyfarthing_scripts/bikerack/__pycache__/portrait_resolver.cpython-314.pyc +0 -0
  73. package/pennyfarthing_scripts/bikerack/__pycache__/progress_panel.cpython-314.pyc +0 -0
  74. package/pennyfarthing_scripts/bikerack/__pycache__/sprint_panel.cpython-314.pyc +0 -0
  75. package/pennyfarthing_scripts/bikerack/__pycache__/story_detail_data.cpython-314.pyc +0 -0
  76. package/pennyfarthing_scripts/bikerack/__pycache__/story_detail_screen.cpython-314.pyc +0 -0
  77. package/pennyfarthing_scripts/bikerack/__pycache__/tui.cpython-314.pyc +0 -0
  78. package/pennyfarthing_scripts/bikerack/__pycache__/ws_client.cpython-314.pyc +0 -0
  79. package/pennyfarthing_scripts/bmad/__init__.py +1 -0
  80. package/pennyfarthing_scripts/bmad/__pycache__/__init__.cpython-314.pyc +0 -0
  81. package/pennyfarthing_scripts/bmad/__pycache__/cli.cpython-314.pyc +0 -0
  82. package/pennyfarthing_scripts/bmad/__pycache__/parser.cpython-314.pyc +0 -0
  83. package/pennyfarthing_scripts/bmad/__pycache__/sync.cpython-314.pyc +0 -0
  84. package/pennyfarthing_scripts/bmad/__pycache__/test_parser.cpython-314-pytest-9.0.2.pyc +0 -0
  85. package/pennyfarthing_scripts/bmad/__pycache__/test_sync.cpython-314-pytest-9.0.2.pyc +0 -0
  86. package/pennyfarthing_scripts/bmad/cli.py +197 -0
  87. package/pennyfarthing_scripts/bmad/importer.py +200 -0
  88. package/pennyfarthing_scripts/bmad/parser.py +233 -0
  89. package/pennyfarthing_scripts/bmad/sync.py +464 -0
  90. package/pennyfarthing_scripts/bmad/test_parser.py +253 -0
  91. package/pennyfarthing_scripts/bmad/test_sync.py +223 -0
  92. package/pennyfarthing_scripts/brownfield/__pycache__/__init__.cpython-314.pyc +0 -0
  93. package/pennyfarthing_scripts/brownfield/__pycache__/__main__.cpython-314.pyc +0 -0
  94. package/pennyfarthing_scripts/brownfield/__pycache__/cli.cpython-314.pyc +0 -0
  95. package/pennyfarthing_scripts/brownfield/__pycache__/discover.cpython-314.pyc +0 -0
  96. package/pennyfarthing_scripts/cli.py +5 -0
  97. package/pennyfarthing_scripts/codemarkers/__pycache__/__init__.cpython-314.pyc +0 -0
  98. package/pennyfarthing_scripts/codemarkers/__pycache__/__main__.cpython-314.pyc +0 -0
  99. package/pennyfarthing_scripts/codemarkers/__pycache__/analyze.cpython-314.pyc +0 -0
  100. package/pennyfarthing_scripts/codemarkers/__pycache__/cli.cpython-314.pyc +0 -0
  101. package/pennyfarthing_scripts/codemarkers/__pycache__/formatters.cpython-314.pyc +0 -0
  102. package/pennyfarthing_scripts/codemarkers/__pycache__/models.cpython-314.pyc +0 -0
  103. package/pennyfarthing_scripts/common/__pycache__/__init__.cpython-314.pyc +0 -0
  104. package/pennyfarthing_scripts/common/__pycache__/config.cpython-314.pyc +0 -0
  105. package/pennyfarthing_scripts/common/__pycache__/output.cpython-314.pyc +0 -0
  106. package/pennyfarthing_scripts/common/__pycache__/themes.cpython-314.pyc +0 -0
  107. package/pennyfarthing_scripts/complexity/__pycache__/__init__.cpython-314.pyc +0 -0
  108. package/pennyfarthing_scripts/complexity/__pycache__/__main__.cpython-314.pyc +0 -0
  109. package/pennyfarthing_scripts/complexity/__pycache__/analyze.cpython-314.pyc +0 -0
  110. package/pennyfarthing_scripts/complexity/__pycache__/cli.cpython-314.pyc +0 -0
  111. package/pennyfarthing_scripts/complexity/__pycache__/formatters.cpython-314.pyc +0 -0
  112. package/pennyfarthing_scripts/complexity/__pycache__/models.cpython-314.pyc +0 -0
  113. package/pennyfarthing_scripts/consultation/__pycache__/__init__.cpython-314.pyc +0 -0
  114. package/pennyfarthing_scripts/consultation/__pycache__/cli.cpython-314.pyc +0 -0
  115. package/pennyfarthing_scripts/deadcode/__pycache__/__init__.cpython-314.pyc +0 -0
  116. package/pennyfarthing_scripts/deadcode/__pycache__/__main__.cpython-314.pyc +0 -0
  117. package/pennyfarthing_scripts/deadcode/__pycache__/analyze.cpython-314.pyc +0 -0
  118. package/pennyfarthing_scripts/deadcode/__pycache__/cli.cpython-314.pyc +0 -0
  119. package/pennyfarthing_scripts/deadcode/__pycache__/formatters.cpython-314.pyc +0 -0
  120. package/pennyfarthing_scripts/deadcode/__pycache__/models.cpython-314.pyc +0 -0
  121. package/pennyfarthing_scripts/dependencies/__pycache__/__init__.cpython-314.pyc +0 -0
  122. package/pennyfarthing_scripts/dependencies/__pycache__/__main__.cpython-314.pyc +0 -0
  123. package/pennyfarthing_scripts/dependencies/__pycache__/analyze.cpython-314.pyc +0 -0
  124. package/pennyfarthing_scripts/dependencies/__pycache__/cli.cpython-314.pyc +0 -0
  125. package/pennyfarthing_scripts/dependencies/__pycache__/formatters.cpython-314.pyc +0 -0
  126. package/pennyfarthing_scripts/dependencies/__pycache__/models.cpython-314.pyc +0 -0
  127. package/pennyfarthing_scripts/epic/__pycache__/__init__.cpython-314.pyc +0 -0
  128. package/pennyfarthing_scripts/epic/__pycache__/cli.cpython-314.pyc +0 -0
  129. package/pennyfarthing_scripts/gate/__pycache__/__init__.cpython-314.pyc +0 -0
  130. package/pennyfarthing_scripts/gate/__pycache__/cli.cpython-314.pyc +0 -0
  131. package/pennyfarthing_scripts/gate/__pycache__/validate.cpython-314.pyc +0 -0
  132. package/pennyfarthing_scripts/git/__pycache__/__init__.cpython-314.pyc +0 -0
  133. package/pennyfarthing_scripts/git/__pycache__/create_branches.cpython-314.pyc +0 -0
  134. package/pennyfarthing_scripts/git/__pycache__/hooks_installer.cpython-314.pyc +0 -0
  135. package/pennyfarthing_scripts/git/__pycache__/repos.cpython-314.pyc +0 -0
  136. package/pennyfarthing_scripts/git/__pycache__/status_all.cpython-314.pyc +0 -0
  137. package/pennyfarthing_scripts/git/__pycache__/worktree.cpython-314.pyc +0 -0
  138. package/pennyfarthing_scripts/git_group/__pycache__/__init__.cpython-314.pyc +0 -0
  139. package/pennyfarthing_scripts/git_group/__pycache__/cli.cpython-314.pyc +0 -0
  140. package/pennyfarthing_scripts/handoff/__pycache__/__init__.cpython-314.pyc +0 -0
  141. package/pennyfarthing_scripts/handoff/__pycache__/cli.cpython-314.pyc +0 -0
  142. package/pennyfarthing_scripts/handoff/__pycache__/complete_phase.cpython-314.pyc +0 -0
  143. package/pennyfarthing_scripts/handoff/__pycache__/gate_file.cpython-314.pyc +0 -0
  144. package/pennyfarthing_scripts/handoff/__pycache__/gate_runner.cpython-314.pyc +0 -0
  145. package/pennyfarthing_scripts/handoff/__pycache__/marker.cpython-314.pyc +0 -0
  146. package/pennyfarthing_scripts/handoff/__pycache__/phase_check.cpython-314.pyc +0 -0
  147. package/pennyfarthing_scripts/handoff/__pycache__/resolve_gate.cpython-314.pyc +0 -0
  148. package/pennyfarthing_scripts/healthscore/__pycache__/__init__.cpython-314.pyc +0 -0
  149. package/pennyfarthing_scripts/healthscore/__pycache__/__main__.cpython-314.pyc +0 -0
  150. package/pennyfarthing_scripts/healthscore/__pycache__/analyze.cpython-314.pyc +0 -0
  151. package/pennyfarthing_scripts/healthscore/__pycache__/cli.cpython-314.pyc +0 -0
  152. package/pennyfarthing_scripts/healthscore/__pycache__/formatters.cpython-314.pyc +0 -0
  153. package/pennyfarthing_scripts/healthscore/__pycache__/models.cpython-314.pyc +0 -0
  154. package/pennyfarthing_scripts/hooks/__pycache__/__init__.cpython-314.pyc +0 -0
  155. package/pennyfarthing_scripts/hooks/__pycache__/bell_mode.cpython-314.pyc +0 -0
  156. package/pennyfarthing_scripts/hooks/__pycache__/cli.cpython-314.pyc +0 -0
  157. package/pennyfarthing_scripts/hooks/__pycache__/context_breaker.cpython-314.pyc +0 -0
  158. package/pennyfarthing_scripts/hooks/__pycache__/context_warning.cpython-314.pyc +0 -0
  159. package/pennyfarthing_scripts/hooks/__pycache__/cyclist_pretooluse.cpython-314.pyc +0 -0
  160. package/pennyfarthing_scripts/hooks/__pycache__/pre_edit_check.cpython-314.pyc +0 -0
  161. package/pennyfarthing_scripts/hooks/__pycache__/reflector_check.cpython-314.pyc +0 -0
  162. package/pennyfarthing_scripts/hooks/__pycache__/schema_validation.cpython-314.pyc +0 -0
  163. package/pennyfarthing_scripts/hooks/__pycache__/session_start.cpython-314.pyc +0 -0
  164. package/pennyfarthing_scripts/hooks/__pycache__/session_stop.cpython-314.pyc +0 -0
  165. package/pennyfarthing_scripts/hooks/__pycache__/sprint_yaml_validation.cpython-314.pyc +0 -0
  166. package/pennyfarthing_scripts/hooks/__pycache__/statusline.cpython-314.pyc +0 -0
  167. package/pennyfarthing_scripts/hotspots/__pycache__/__init__.cpython-314.pyc +0 -0
  168. package/pennyfarthing_scripts/hotspots/__pycache__/__main__.cpython-314.pyc +0 -0
  169. package/pennyfarthing_scripts/hotspots/__pycache__/analyze.cpython-314.pyc +0 -0
  170. package/pennyfarthing_scripts/hotspots/__pycache__/cli.cpython-314.pyc +0 -0
  171. package/pennyfarthing_scripts/hotspots/__pycache__/formatters.cpython-314.pyc +0 -0
  172. package/pennyfarthing_scripts/hotspots/__pycache__/models.cpython-314.pyc +0 -0
  173. package/pennyfarthing_scripts/jira/__pycache__/__init__.cpython-314.pyc +0 -0
  174. package/pennyfarthing_scripts/jira/__pycache__/__main__.cpython-314.pyc +0 -0
  175. package/pennyfarthing_scripts/jira/__pycache__/bidirectional.cpython-314.pyc +0 -0
  176. package/pennyfarthing_scripts/jira/__pycache__/claim.cpython-314.pyc +0 -0
  177. package/pennyfarthing_scripts/jira/__pycache__/cli.cpython-314.pyc +0 -0
  178. package/pennyfarthing_scripts/jira/__pycache__/client.cpython-314.pyc +0 -0
  179. package/pennyfarthing_scripts/jira/__pycache__/create.cpython-314.pyc +0 -0
  180. package/pennyfarthing_scripts/jira/__pycache__/epic.cpython-314.pyc +0 -0
  181. package/pennyfarthing_scripts/jira/__pycache__/operations.cpython-314.pyc +0 -0
  182. package/pennyfarthing_scripts/jira/__pycache__/reconcile.cpython-314.pyc +0 -0
  183. package/pennyfarthing_scripts/jira/__pycache__/story.cpython-314.pyc +0 -0
  184. package/pennyfarthing_scripts/jira/__pycache__/sync.cpython-314.pyc +0 -0
  185. package/pennyfarthing_scripts/launch/__pycache__/__init__.cpython-314.pyc +0 -0
  186. package/pennyfarthing_scripts/launch/__pycache__/cli.cpython-314.pyc +0 -0
  187. package/pennyfarthing_scripts/migration/__pycache__/__init__.cpython-314.pyc +0 -0
  188. package/pennyfarthing_scripts/migration/__pycache__/session.cpython-314.pyc +0 -0
  189. package/pennyfarthing_scripts/migration/__pycache__/skill.cpython-314.pyc +0 -0
  190. package/pennyfarthing_scripts/migration/__pycache__/step.cpython-314.pyc +0 -0
  191. package/pennyfarthing_scripts/migration/__pycache__/validate.cpython-314.pyc +0 -0
  192. package/pennyfarthing_scripts/preflight/__pycache__/__init__.cpython-314.pyc +0 -0
  193. package/pennyfarthing_scripts/preflight/__pycache__/__main__.cpython-314.pyc +0 -0
  194. package/pennyfarthing_scripts/preflight/__pycache__/cli.cpython-314.pyc +0 -0
  195. package/pennyfarthing_scripts/preflight/__pycache__/finish.cpython-314.pyc +0 -0
  196. package/pennyfarthing_scripts/prime/__pycache__/__init__.cpython-314.pyc +0 -0
  197. package/pennyfarthing_scripts/prime/__pycache__/cli.cpython-314.pyc +0 -0
  198. package/pennyfarthing_scripts/prime/__pycache__/heatmap.cpython-314.pyc +0 -0
  199. package/pennyfarthing_scripts/prime/__pycache__/loader.cpython-314.pyc +0 -0
  200. package/pennyfarthing_scripts/prime/__pycache__/models.cpython-314.pyc +0 -0
  201. package/pennyfarthing_scripts/prime/__pycache__/persona.cpython-314.pyc +0 -0
  202. package/pennyfarthing_scripts/prime/__pycache__/session.cpython-314.pyc +0 -0
  203. package/pennyfarthing_scripts/prime/__pycache__/tiers.cpython-314.pyc +0 -0
  204. package/pennyfarthing_scripts/prime/__pycache__/version_sentinel.cpython-314.pyc +0 -0
  205. package/pennyfarthing_scripts/prime/__pycache__/workflow.cpython-314.pyc +0 -0
  206. package/pennyfarthing_scripts/session/__pycache__/__init__.cpython-314.pyc +0 -0
  207. package/pennyfarthing_scripts/session/__pycache__/cli.cpython-314.pyc +0 -0
  208. package/pennyfarthing_scripts/settings/__pycache__/__init__.cpython-314.pyc +0 -0
  209. package/pennyfarthing_scripts/settings/__pycache__/cli.cpython-314.pyc +0 -0
  210. package/pennyfarthing_scripts/settings/__pycache__/settings.cpython-314.pyc +0 -0
  211. package/pennyfarthing_scripts/sprint/__pycache__/__init__.cpython-314.pyc +0 -0
  212. package/pennyfarthing_scripts/sprint/__pycache__/__main__.cpython-314.pyc +0 -0
  213. package/pennyfarthing_scripts/sprint/__pycache__/archive.cpython-314.pyc +0 -0
  214. package/pennyfarthing_scripts/sprint/__pycache__/archive_epic.cpython-314.pyc +0 -0
  215. package/pennyfarthing_scripts/sprint/__pycache__/cli.cpython-314.pyc +0 -0
  216. package/pennyfarthing_scripts/sprint/__pycache__/epic_add.cpython-314.pyc +0 -0
  217. package/pennyfarthing_scripts/sprint/__pycache__/epic_update.cpython-314.pyc +0 -0
  218. package/pennyfarthing_scripts/sprint/__pycache__/import_epic.cpython-314.pyc +0 -0
  219. package/pennyfarthing_scripts/sprint/__pycache__/loader.cpython-314.pyc +0 -0
  220. package/pennyfarthing_scripts/sprint/__pycache__/status.cpython-314.pyc +0 -0
  221. package/pennyfarthing_scripts/sprint/__pycache__/story_add.cpython-314.pyc +0 -0
  222. package/pennyfarthing_scripts/sprint/__pycache__/story_finish.cpython-314.pyc +0 -0
  223. package/pennyfarthing_scripts/sprint/__pycache__/story_update.cpython-314.pyc +0 -0
  224. package/pennyfarthing_scripts/sprint/__pycache__/validate_cmd.cpython-314.pyc +0 -0
  225. package/pennyfarthing_scripts/sprint/__pycache__/validator.cpython-314.pyc +0 -0
  226. package/pennyfarthing_scripts/sprint/__pycache__/work.cpython-314.pyc +0 -0
  227. package/pennyfarthing_scripts/sprint/__pycache__/yaml_io.cpython-314.pyc +0 -0
  228. package/pennyfarthing_scripts/story/__pycache__/__init__.cpython-314.pyc +0 -0
  229. package/pennyfarthing_scripts/story/__pycache__/__main__.cpython-314.pyc +0 -0
  230. package/pennyfarthing_scripts/story/__pycache__/cli.cpython-314.pyc +0 -0
  231. package/pennyfarthing_scripts/story/__pycache__/create.cpython-314.pyc +0 -0
  232. package/pennyfarthing_scripts/story/__pycache__/size.cpython-314.pyc +0 -0
  233. package/pennyfarthing_scripts/story/__pycache__/template.cpython-314.pyc +0 -0
  234. package/pennyfarthing_scripts/tests/__pycache__/__init__.cpython-314.pyc +0 -0
  235. package/pennyfarthing_scripts/tests/__pycache__/conftest.cpython-314-pytest-9.0.2.pyc +0 -0
  236. package/pennyfarthing_scripts/tests/__pycache__/test_108_2_remove_handoff_fallback.cpython-314-pytest-9.0.2.pyc +0 -0
  237. package/pennyfarthing_scripts/tests/__pycache__/test_archive_epic.cpython-314-pytest-9.0.2.pyc +0 -0
  238. package/pennyfarthing_scripts/tests/__pycache__/test_bc.cpython-314-pytest-9.0.2.pyc +0 -0
  239. package/pennyfarthing_scripts/tests/__pycache__/test_bikerack.cpython-314-pytest-9.0.2.pyc +0 -0
  240. package/pennyfarthing_scripts/tests/__pycache__/test_brownfield.cpython-314-pytest-9.0.2.pyc +0 -0
  241. package/pennyfarthing_scripts/tests/__pycache__/test_cli_modules.cpython-314-pytest-9.0.2.pyc +0 -0
  242. package/pennyfarthing_scripts/tests/__pycache__/test_cli_normalization.cpython-314-pytest-9.0.2.pyc +0 -0
  243. package/pennyfarthing_scripts/tests/__pycache__/test_codemarkers.cpython-314-pytest-9.0.2.pyc +0 -0
  244. package/pennyfarthing_scripts/tests/__pycache__/test_common.cpython-314-pytest-9.0.2.pyc +0 -0
  245. package/pennyfarthing_scripts/tests/__pycache__/test_confidence_sm_evaluation.cpython-314-pytest-9.0.2.pyc +0 -0
  246. package/pennyfarthing_scripts/tests/__pycache__/test_confidence_sm_gate.cpython-314-pytest-9.0.2.pyc +0 -0
  247. package/pennyfarthing_scripts/tests/__pycache__/test_epic_shard_validation.cpython-314-pytest-9.0.2.pyc +0 -0
  248. package/pennyfarthing_scripts/tests/__pycache__/test_gate_file_resolution.cpython-314-pytest-9.0.2.pyc +0 -0
  249. package/pennyfarthing_scripts/tests/__pycache__/test_gate_runner.cpython-314-pytest-9.0.2.pyc +0 -0
  250. package/pennyfarthing_scripts/tests/__pycache__/test_git_utils.cpython-314-pytest-9.0.2.pyc +0 -0
  251. package/pennyfarthing_scripts/tests/__pycache__/test_handoff_cli.cpython-314-pytest-9.0.2.pyc +0 -0
  252. package/pennyfarthing_scripts/tests/__pycache__/test_handoff_e2e.cpython-314-pytest-9.0.2.pyc +0 -0
  253. package/pennyfarthing_scripts/tests/__pycache__/test_healthscore.cpython-314-pytest-9.0.2.pyc +0 -0
  254. package/pennyfarthing_scripts/tests/__pycache__/test_jira_package.cpython-314-pytest-9.0.2.pyc +0 -0
  255. package/pennyfarthing_scripts/tests/__pycache__/test_package_structure.cpython-314-pytest-9.0.2.pyc +0 -0
  256. package/pennyfarthing_scripts/tests/__pycache__/test_patch_mode.cpython-314-pytest-9.0.2.pyc +0 -0
  257. package/pennyfarthing_scripts/tests/__pycache__/test_prime.cpython-314-pytest-9.0.2.pyc +0 -0
  258. package/pennyfarthing_scripts/tests/__pycache__/test_resolve_gate_file_field.cpython-314-pytest-9.0.2.pyc +0 -0
  259. package/pennyfarthing_scripts/tests/__pycache__/test_sprint_package.cpython-314-pytest-9.0.2.pyc +0 -0
  260. package/pennyfarthing_scripts/tests/__pycache__/test_sprint_panel.cpython-314-pytest-9.0.2.pyc +0 -0
  261. package/pennyfarthing_scripts/tests/__pycache__/test_sprint_validator.cpython-314-pytest-9.0.2.pyc +0 -0
  262. package/pennyfarthing_scripts/tests/__pycache__/test_story_add.cpython-314-pytest-9.0.2.pyc +0 -0
  263. package/pennyfarthing_scripts/tests/__pycache__/test_story_package.cpython-314-pytest-9.0.2.pyc +0 -0
  264. package/pennyfarthing_scripts/tests/__pycache__/test_story_update.cpython-314-pytest-9.0.2.pyc +0 -0
  265. package/pennyfarthing_scripts/tests/__pycache__/test_tiers.cpython-314-pytest-9.0.2.pyc +0 -0
  266. package/pennyfarthing_scripts/tests/__pycache__/test_token_counting.cpython-314-pytest-9.0.2.pyc +0 -0
  267. package/pennyfarthing_scripts/tests/__pycache__/test_topology_loader.cpython-314-pytest-9.0.2.pyc +0 -0
  268. package/pennyfarthing_scripts/tests/__pycache__/test_tui_focus.cpython-314-pytest-9.0.2.pyc +0 -0
  269. package/pennyfarthing_scripts/tests/__pycache__/test_tui_panel_persistence.cpython-314-pytest-9.0.2.pyc +0 -0
  270. package/pennyfarthing_scripts/tests/__pycache__/test_validate_cmd.cpython-314-pytest-9.0.2.pyc +0 -0
  271. package/pennyfarthing_scripts/tests/__pycache__/test_version_sentinel.cpython-314-pytest-9.0.2.pyc +0 -0
  272. package/pennyfarthing_scripts/tests/__pycache__/test_workflow_check.cpython-314-pytest-9.0.2.pyc +0 -0
  273. package/pennyfarthing_scripts/tests/__pycache__/test_workflow_list_team.cpython-314-pytest-9.0.2.pyc +0 -0
  274. package/pennyfarthing_scripts/tests/__pycache__/test_workflow_list_team.cpython-314.pyc +0 -0
  275. package/pennyfarthing_scripts/tests/__pycache__/test_yaml_io.cpython-314-pytest-9.0.2.pyc +0 -0
  276. package/pennyfarthing_scripts/theme/__pycache__/__init__.cpython-314.pyc +0 -0
  277. package/pennyfarthing_scripts/theme/__pycache__/cli.cpython-314.pyc +0 -0
  278. package/pennyfarthing_scripts/validate/__pycache__/__init__.cpython-314.pyc +0 -0
  279. package/pennyfarthing_scripts/validate/__pycache__/cli.cpython-314.pyc +0 -0
  280. package/pennyfarthing_scripts/validate/adapters/__pycache__/__init__.cpython-314.pyc +0 -0
  281. package/pennyfarthing_scripts/validate/adapters/__pycache__/agent.cpython-314.pyc +0 -0
  282. package/pennyfarthing_scripts/validate/adapters/__pycache__/schema.cpython-314.pyc +0 -0
  283. package/pennyfarthing_scripts/validate/adapters/__pycache__/skill_command.cpython-314.pyc +0 -0
  284. package/pennyfarthing_scripts/validate/adapters/__pycache__/sprint.cpython-314.pyc +0 -0
  285. package/pennyfarthing_scripts/validate/adapters/__pycache__/tandem_awareness.cpython-314.pyc +0 -0
  286. package/pennyfarthing_scripts/validate/adapters/__pycache__/team_mode.cpython-314.pyc +0 -0
  287. package/pennyfarthing_scripts/validate/adapters/__pycache__/workflow.cpython-314.pyc +0 -0
  288. package/pennyfarthing_scripts/workflow/__pycache__/__init__.cpython-314.pyc +0 -0
  289. package/pennyfarthing_scripts/workflow/__pycache__/cli.cpython-314.pyc +0 -0
  290. package/pennyfarthing_scripts/workflow/__pycache__/helpers.cpython-314.pyc +0 -0
  291. package/pennyfarthing_scripts/workflow/__pycache__/scale.cpython-314.pyc +0 -0
  292. package/pennyfarthing_scripts/workflow/__pycache__/state.cpython-314.pyc +0 -0
  293. package/pennyfarthing_scripts/workflow/__pycache__/team_lifecycle.cpython-314.pyc +0 -0
  294. package/packages/core/dist/cli/commands/stale-artifacts-cleanup.test.d.ts +0 -17
  295. package/packages/core/dist/cli/commands/stale-artifacts-cleanup.test.d.ts.map +0 -1
  296. package/packages/core/dist/cli/commands/stale-artifacts-cleanup.test.js +0 -470
  297. package/packages/core/dist/cli/commands/stale-artifacts-cleanup.test.js.map +0 -1
  298. package/packages/core/dist/cli/cyclist-migration.test.d.ts +0 -16
  299. package/packages/core/dist/cli/cyclist-migration.test.d.ts.map +0 -1
  300. package/packages/core/dist/cli/cyclist-migration.test.js +0 -229
  301. package/packages/core/dist/cli/cyclist-migration.test.js.map +0 -1
  302. package/packages/core/dist/cli/utils/stale-artifacts.d.ts +0 -59
  303. package/packages/core/dist/cli/utils/stale-artifacts.d.ts.map +0 -1
  304. package/packages/core/dist/cli/utils/stale-artifacts.js +0 -163
  305. package/packages/core/dist/cli/utils/stale-artifacts.js.map +0 -1
  306. package/packages/core/dist/scripts/benchmark-integration.d.ts +0 -182
  307. package/packages/core/dist/scripts/benchmark-integration.d.ts.map +0 -1
  308. package/packages/core/dist/scripts/benchmark-integration.js +0 -691
  309. package/packages/core/dist/scripts/benchmark-integration.js.map +0 -1
  310. package/packages/core/dist/scripts/job-fair-aggregator.d.ts +0 -150
  311. package/packages/core/dist/scripts/job-fair-aggregator.d.ts.map +0 -1
  312. package/packages/core/dist/scripts/job-fair-aggregator.js +0 -547
  313. package/packages/core/dist/scripts/job-fair-aggregator.js.map +0 -1
  314. package/packages/core/dist/scripts/theme-detail.test.d.ts.map +0 -1
  315. package/packages/core/dist/scripts/theme-detail.test.js.map +0 -1
  316. package/pennyfarthing-dist/scripts/hooks/__pycache__/question_reflector_check.cpython-314.pyc +0 -0
  317. package/pennyfarthing_scripts/__pycache__/__init__.cpython-311.pyc +0 -0
  318. package/pennyfarthing_scripts/__pycache__/cli.cpython-311.pyc +0 -0
  319. package/pennyfarthing_scripts/__pycache__/context.cpython-311.pyc +0 -0
  320. package/pennyfarthing_scripts/__pycache__/jira.cpython-314.pyc +0 -0
  321. package/pennyfarthing_scripts/__pycache__/sprint.cpython-314.pyc +0 -0
  322. package/pennyfarthing_scripts/__pycache__/workflow.cpython-311.pyc +0 -0
  323. package/pennyfarthing_scripts/bc/__pycache__/__init__.cpython-311.pyc +0 -0
  324. package/pennyfarthing_scripts/bc/__pycache__/cli.cpython-311.pyc +0 -0
  325. package/pennyfarthing_scripts/bc/__pycache__/focus.cpython-311.pyc +0 -0
  326. package/pennyfarthing_scripts/bikerack/__pycache__/__init__.cpython-311.pyc +0 -0
  327. package/pennyfarthing_scripts/bikerack/__pycache__/cli.cpython-311.pyc +0 -0
  328. package/pennyfarthing_scripts/bikerack/__pycache__/launcher.cpython-311.pyc +0 -0
  329. package/pennyfarthing_scripts/common/__pycache__/__init__.cpython-311.pyc +0 -0
  330. package/pennyfarthing_scripts/common/__pycache__/config.cpython-311.pyc +0 -0
  331. package/pennyfarthing_scripts/common/__pycache__/output.cpython-311.pyc +0 -0
  332. package/pennyfarthing_scripts/common/__pycache__/pr_config.cpython-314.pyc +0 -0
  333. package/pennyfarthing_scripts/consultation/__pycache__/__init__.cpython-311.pyc +0 -0
  334. package/pennyfarthing_scripts/consultation/__pycache__/cli.cpython-311.pyc +0 -0
  335. package/pennyfarthing_scripts/consultation/__pycache__/dialogue_manager.cpython-314.pyc +0 -0
  336. package/pennyfarthing_scripts/deadcode/__pycache__/__init__.cpython-311.pyc +0 -0
  337. package/pennyfarthing_scripts/deadcode/__pycache__/cli.cpython-311.pyc +0 -0
  338. package/pennyfarthing_scripts/epic/__pycache__/__init__.cpython-311.pyc +0 -0
  339. package/pennyfarthing_scripts/epic/__pycache__/cli.cpython-311.pyc +0 -0
  340. package/pennyfarthing_scripts/git_group/__pycache__/__init__.cpython-311.pyc +0 -0
  341. package/pennyfarthing_scripts/git_group/__pycache__/cli.cpython-311.pyc +0 -0
  342. package/pennyfarthing_scripts/handoff/__pycache__/__init__.cpython-311.pyc +0 -0
  343. package/pennyfarthing_scripts/handoff/__pycache__/cli.cpython-311.pyc +0 -0
  344. package/pennyfarthing_scripts/healthscore/__pycache__/__init__.cpython-311.pyc +0 -0
  345. package/pennyfarthing_scripts/healthscore/__pycache__/analyze.cpython-311.pyc +0 -0
  346. package/pennyfarthing_scripts/healthscore/__pycache__/cli.cpython-311.pyc +0 -0
  347. package/pennyfarthing_scripts/healthscore/__pycache__/models.cpython-311.pyc +0 -0
  348. package/pennyfarthing_scripts/hooks/__pycache__/__init__.cpython-311.pyc +0 -0
  349. package/pennyfarthing_scripts/hooks/__pycache__/bell_mode.cpython-311.pyc +0 -0
  350. package/pennyfarthing_scripts/hooks/__pycache__/cli.cpython-311.pyc +0 -0
  351. package/pennyfarthing_scripts/hooks/__pycache__/context_breaker.cpython-311.pyc +0 -0
  352. package/pennyfarthing_scripts/hooks/__pycache__/context_warning.cpython-311.pyc +0 -0
  353. package/pennyfarthing_scripts/hooks/__pycache__/cyclist_pretooluse.cpython-311.pyc +0 -0
  354. package/pennyfarthing_scripts/hooks/__pycache__/pre_edit_check.cpython-311.pyc +0 -0
  355. package/pennyfarthing_scripts/hooks/__pycache__/reflector_check.cpython-311.pyc +0 -0
  356. package/pennyfarthing_scripts/hooks/__pycache__/schema_validation.cpython-311.pyc +0 -0
  357. package/pennyfarthing_scripts/hooks/__pycache__/session_start.cpython-311.pyc +0 -0
  358. package/pennyfarthing_scripts/hooks/__pycache__/sprint_yaml_validation.cpython-311.pyc +0 -0
  359. package/pennyfarthing_scripts/hooks/__pycache__/statusline.cpython-311.pyc +0 -0
  360. package/pennyfarthing_scripts/hotspots/__pycache__/__init__.cpython-311.pyc +0 -0
  361. package/pennyfarthing_scripts/hotspots/__pycache__/analyze.cpython-311.pyc +0 -0
  362. package/pennyfarthing_scripts/hotspots/__pycache__/cli.cpython-311.pyc +0 -0
  363. package/pennyfarthing_scripts/hotspots/__pycache__/models.cpython-311.pyc +0 -0
  364. package/pennyfarthing_scripts/jira/__pycache__/__init__.cpython-311.pyc +0 -0
  365. package/pennyfarthing_scripts/jira/__pycache__/bidirectional.cpython-311.pyc +0 -0
  366. package/pennyfarthing_scripts/jira/__pycache__/claim.cpython-311.pyc +0 -0
  367. package/pennyfarthing_scripts/jira/__pycache__/cli.cpython-311.pyc +0 -0
  368. package/pennyfarthing_scripts/jira/__pycache__/client.cpython-311.pyc +0 -0
  369. package/pennyfarthing_scripts/jira/__pycache__/compat.cpython-314.pyc +0 -0
  370. package/pennyfarthing_scripts/jira/__pycache__/create.cpython-311.pyc +0 -0
  371. package/pennyfarthing_scripts/jira/__pycache__/epic.cpython-311.pyc +0 -0
  372. package/pennyfarthing_scripts/jira/__pycache__/mappings.cpython-314.pyc +0 -0
  373. package/pennyfarthing_scripts/jira/__pycache__/models.cpython-314.pyc +0 -0
  374. package/pennyfarthing_scripts/jira/__pycache__/operations.cpython-311.pyc +0 -0
  375. package/pennyfarthing_scripts/jira/__pycache__/reconcile.cpython-311.pyc +0 -0
  376. package/pennyfarthing_scripts/jira/__pycache__/story.cpython-311.pyc +0 -0
  377. package/pennyfarthing_scripts/jira/__pycache__/sync.cpython-311.pyc +0 -0
  378. package/pennyfarthing_scripts/launch/__pycache__/__init__.cpython-311.pyc +0 -0
  379. package/pennyfarthing_scripts/launch/__pycache__/cli.cpython-311.pyc +0 -0
  380. package/pennyfarthing_scripts/migration/__pycache__/__main__.cpython-314.pyc +0 -0
  381. package/pennyfarthing_scripts/migration/__pycache__/cli.cpython-314.pyc +0 -0
  382. package/pennyfarthing_scripts/prime/__pycache__/__main__.cpython-314.pyc +0 -0
  383. package/pennyfarthing_scripts/session/__pycache__/__init__.cpython-311.pyc +0 -0
  384. package/pennyfarthing_scripts/session/__pycache__/cli.cpython-311.pyc +0 -0
  385. package/pennyfarthing_scripts/sprint/__pycache__/__init__.cpython-311.pyc +0 -0
  386. package/pennyfarthing_scripts/sprint/__pycache__/archive.cpython-311.pyc +0 -0
  387. package/pennyfarthing_scripts/sprint/__pycache__/cli.cpython-311.pyc +0 -0
  388. package/pennyfarthing_scripts/sprint/__pycache__/epic_add.cpython-311.pyc +0 -0
  389. package/pennyfarthing_scripts/sprint/__pycache__/epic_update.cpython-311.pyc +0 -0
  390. package/pennyfarthing_scripts/sprint/__pycache__/loader.cpython-311.pyc +0 -0
  391. package/pennyfarthing_scripts/sprint/__pycache__/status.cpython-311.pyc +0 -0
  392. package/pennyfarthing_scripts/sprint/__pycache__/story_add.cpython-311.pyc +0 -0
  393. package/pennyfarthing_scripts/sprint/__pycache__/story_update.cpython-311.pyc +0 -0
  394. package/pennyfarthing_scripts/sprint/__pycache__/validate_cmd.cpython-311.pyc +0 -0
  395. package/pennyfarthing_scripts/sprint/__pycache__/validator.cpython-311.pyc +0 -0
  396. package/pennyfarthing_scripts/sprint/__pycache__/work.cpython-311.pyc +0 -0
  397. package/pennyfarthing_scripts/sprint/__pycache__/yaml_io.cpython-311.pyc +0 -0
  398. package/pennyfarthing_scripts/tests/__pycache__/test_108_1_gate_migration.cpython-314-pytest-9.0.2.pyc +0 -0
  399. package/pennyfarthing_scripts/tests/__pycache__/test_dialogue_manager.cpython-314-pytest-9.0.2.pyc +0 -0
  400. package/pennyfarthing_scripts/tests/__pycache__/test_workflow_cli.cpython-314-pytest-9.0.2.pyc +0 -0
  401. package/pennyfarthing_scripts/theme/__pycache__/__init__.cpython-311.pyc +0 -0
  402. package/pennyfarthing_scripts/theme/__pycache__/cli.cpython-311.pyc +0 -0
  403. package/pennyfarthing_scripts/validate/__pycache__/__init__.cpython-311.pyc +0 -0
  404. package/pennyfarthing_scripts/validate/__pycache__/cli.cpython-311.pyc +0 -0
  405. package/pennyfarthing_scripts/workflow/__pycache__/__init__.cpython-311.pyc +0 -0
  406. package/pennyfarthing_scripts/workflow/__pycache__/cli.cpython-311.pyc +0 -0
  407. package/pennyfarthing_scripts/workflow/__pycache__/scale.cpython-311.pyc +0 -0
  408. package/pennyfarthing_scripts/workflow/__pycache__/state.cpython-311.pyc +0 -0
@@ -0,0 +1,233 @@
1
+ """
2
+ BMAD markdown parser for Pennyfarthing sprint adapter.
3
+
4
+ Reads BMAD story and epic markdown files and returns PF-compatible dicts.
5
+ Story files: implementation-artifacts/{epic}-{story}-{slug}.md
6
+ Epic files: planning-artifacts/epics/epic-{nn}-{slug}.md
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import re
12
+ from pathlib import Path
13
+ from typing import Any
14
+
15
+ import yaml
16
+
17
+
18
+ # =============================================================================
19
+ # Status Mapping
20
+ # =============================================================================
21
+
22
+ BMAD_TO_PF_STATUS: dict[str, str] = {
23
+ "draft": "planning",
24
+ "ready-for-dev": "ready",
25
+ "in-progress": "in_progress",
26
+ "in-review": "in_progress",
27
+ "completed": "done",
28
+ "blocked": "backlog",
29
+ }
30
+
31
+ PF_TO_BMAD_STATUS: dict[str, str] = {
32
+ "planning": "draft",
33
+ "ready": "ready-for-dev",
34
+ "in_progress": "in-progress",
35
+ "done": "completed",
36
+ "backlog": "blocked",
37
+ "canceled": "completed",
38
+ }
39
+
40
+
41
+ def map_bmad_to_pf(bmad_status: str) -> str:
42
+ """Map a BMAD status string to a PF status."""
43
+ return BMAD_TO_PF_STATUS.get(bmad_status.strip().lower(), "planning")
44
+
45
+
46
+ def map_pf_to_bmad(pf_status: str) -> str:
47
+ """Map a PF status string to a BMAD status."""
48
+ return PF_TO_BMAD_STATUS.get(pf_status.strip().lower(), "draft")
49
+
50
+
51
+ # =============================================================================
52
+ # Story Parsing
53
+ # =============================================================================
54
+
55
+ # Header patterns: flat Key: value lines after the # title
56
+ _HEADER_PATTERNS: dict[str, re.Pattern[str]] = {
57
+ "status": re.compile(r"^Status:\s*(.+)$", re.MULTILINE),
58
+ "story_key": re.compile(r"^Story-Key:\s*(.+)$", re.MULTILINE),
59
+ "jira": re.compile(r"^Jira:\s*(.+)$", re.MULTILINE),
60
+ "epic_line": re.compile(r"^Epic:\s*(.+)$", re.MULTILINE),
61
+ "date": re.compile(r"^Date:\s*(.+)$", re.MULTILINE),
62
+ }
63
+
64
+ _TITLE_RE = re.compile(r"^#\s+Story\s+\d+\.\d+:\s*(.+)$", re.MULTILINE)
65
+
66
+ # AC block: everything between ## Acceptance Criteria and the next ## heading
67
+ _AC_RE = re.compile(
68
+ r"## Acceptance Criteria\s*\n(.*?)(?=\n## |\Z)", re.DOTALL
69
+ )
70
+
71
+
72
+ def parse_bmad_story(path: Path) -> dict[str, Any]:
73
+ """Parse a single BMAD story markdown file.
74
+
75
+ Args:
76
+ path: Path to the .md file in implementation-artifacts/
77
+
78
+ Returns:
79
+ PF-compatible story dict with extra bmad_key and bmad_path fields.
80
+ """
81
+ content = path.read_text()
82
+
83
+ # Extract header fields
84
+ fields: dict[str, str] = {}
85
+ for name, pattern in _HEADER_PATTERNS.items():
86
+ match = pattern.search(content)
87
+ if match:
88
+ fields[name] = match.group(1).strip()
89
+
90
+ story_key = fields.get("story_key", "")
91
+ parts = story_key.split("-", 2) # e.g. "1-5-testing-framework" → ["1","5","testing-framework"]
92
+ epic_num = parts[0] if len(parts) >= 2 else "0"
93
+ story_num = parts[1] if len(parts) >= 2 else "0"
94
+ pf_id = f"{epic_num}-{story_num}"
95
+
96
+ # Title from # heading
97
+ title_match = _TITLE_RE.search(content)
98
+ title = title_match.group(1).strip() if title_match else path.stem
99
+
100
+ # BMAD status → PF status
101
+ bmad_status = fields.get("status", "draft")
102
+ pf_status = map_bmad_to_pf(bmad_status)
103
+
104
+ # Jira references (format: "DPGD-14 / DPGD-21")
105
+ jira_raw = fields.get("jira", "")
106
+
107
+ # Acceptance criteria summary
108
+ ac_match = _AC_RE.search(content)
109
+ ac_text = ac_match.group(1).strip() if ac_match else ""
110
+
111
+ return {
112
+ "id": pf_id,
113
+ "title": title,
114
+ "status": pf_status,
115
+ "points": 3, # Default; BMAD stories don't carry points in impl artifacts
116
+ "priority": "P1",
117
+ "workflow": "tdd",
118
+ "bmad_key": story_key,
119
+ "bmad_status": bmad_status,
120
+ "bmad_path": str(path),
121
+ "jira": jira_raw,
122
+ "epic_num": epic_num,
123
+ "acceptance_criteria": ac_text,
124
+ }
125
+
126
+
127
+ # =============================================================================
128
+ # Epic Parsing
129
+ # =============================================================================
130
+
131
+
132
+ def parse_bmad_epic(path: Path) -> dict[str, Any]:
133
+ """Parse a BMAD epic markdown file with YAML frontmatter.
134
+
135
+ Args:
136
+ path: Path to epic-{nn}-{slug}.md in planning-artifacts/epics/
137
+
138
+ Returns:
139
+ Dict with epicNumber, title, phase, status, storyCount.
140
+ """
141
+ content = path.read_text()
142
+
143
+ # Extract YAML frontmatter between --- markers
144
+ fm_match = re.match(r"^---\s*\n(.*?)\n---", content, re.DOTALL)
145
+ if not fm_match:
146
+ return {
147
+ "epicNumber": 0,
148
+ "title": path.stem,
149
+ "phase": "MVP",
150
+ "status": "draft",
151
+ "storyCount": 0,
152
+ }
153
+
154
+ fm = yaml.safe_load(fm_match.group(1)) or {}
155
+ return {
156
+ "epicNumber": fm.get("epicNumber", 0),
157
+ "title": fm.get("title", path.stem),
158
+ "phase": fm.get("phase", "MVP"),
159
+ "status": fm.get("status", "draft"),
160
+ "storyCount": fm.get("storyCount", 0),
161
+ }
162
+
163
+
164
+ # =============================================================================
165
+ # Discovery
166
+ # =============================================================================
167
+
168
+
169
+ def discover_bmad_stories(
170
+ source_root: Path,
171
+ story_dir: str = "implementation-artifacts",
172
+ ) -> list[dict[str, Any]]:
173
+ """Scan BMAD implementation-artifacts/ and return parsed stories.
174
+
175
+ Args:
176
+ source_root: Path to _bmad-output/ (or equivalent)
177
+ story_dir: Subdirectory name for story files
178
+
179
+ Returns:
180
+ List of parsed story dicts, sorted by (epic_num, story_num).
181
+ """
182
+ artifacts_dir = source_root / story_dir
183
+ if not artifacts_dir.is_dir():
184
+ return []
185
+
186
+ stories: list[dict[str, Any]] = []
187
+ for md_file in sorted(artifacts_dir.glob("*.md")):
188
+ # Skip non-story files (e.g. 0-1-bmad-method-lifecycle.md is meta)
189
+ if md_file.name.startswith("0-"):
190
+ continue
191
+ # Must match {digit}-{digit}-*.md pattern
192
+ if not re.match(r"^\d+-\d+-", md_file.name):
193
+ continue
194
+ story = parse_bmad_story(md_file)
195
+ stories.append(story)
196
+
197
+ # Sort by epic number, then story number
198
+ def sort_key(s: dict) -> tuple[int, int]:
199
+ parts = s["id"].split("-")
200
+ return (int(parts[0]), int(parts[1]))
201
+
202
+ stories.sort(key=sort_key)
203
+ return stories
204
+
205
+
206
+ def discover_bmad_epics(
207
+ source_root: Path,
208
+ epic_dir: str = "planning-artifacts/epics",
209
+ ) -> list[dict[str, Any]]:
210
+ """Scan BMAD planning-artifacts/epics/ and return parsed epics.
211
+
212
+ Args:
213
+ source_root: Path to _bmad-output/ (or equivalent)
214
+ epic_dir: Subdirectory path for epic files
215
+
216
+ Returns:
217
+ List of parsed epic dicts, sorted by epicNumber.
218
+ """
219
+ epics_dir = source_root / epic_dir
220
+ if not epics_dir.is_dir():
221
+ return []
222
+
223
+ epics: list[dict[str, Any]] = []
224
+ for md_file in sorted(epics_dir.glob("epic-*.md")):
225
+ # Skip index.md or non-epic files
226
+ if md_file.name == "index.md":
227
+ continue
228
+ epic = parse_bmad_epic(md_file)
229
+ if epic["epicNumber"] > 0:
230
+ epics.append(epic)
231
+
232
+ epics.sort(key=lambda e: e["epicNumber"])
233
+ return epics
@@ -0,0 +1,464 @@
1
+ """
2
+ Bidirectional sync between BMAD markdown and PF sprint YAML.
3
+
4
+ Modeled on pennyfarthing_scripts/jira/bidirectional.py — same
5
+ SyncPlan/SyncChange/SyncResult pattern, adapted for BMAD's flat
6
+ markdown header format instead of a REST API.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import re
12
+ from dataclasses import dataclass, field
13
+ from pathlib import Path
14
+ from typing import Any, Literal
15
+
16
+ from pennyfarthing_scripts.bmad.parser import (
17
+ discover_bmad_stories,
18
+ map_bmad_to_pf,
19
+ map_pf_to_bmad,
20
+ )
21
+ from pennyfarthing_scripts.common.config import get_project_root, load_pennyfarthing_config
22
+ from pennyfarthing_scripts.common.output import error, info, success, warn
23
+
24
+
25
+ # =============================================================================
26
+ # Data Classes
27
+ # =============================================================================
28
+
29
+
30
+ @dataclass
31
+ class BmadSyncChange:
32
+ """A single sync change to apply."""
33
+
34
+ bmad_key: str
35
+ pf_id: str
36
+ field: Literal["status"]
37
+ action: Literal["update-pf", "update-bmad"]
38
+ pf_value: Any
39
+ bmad_value: Any
40
+ target_value: Any
41
+
42
+
43
+ @dataclass
44
+ class BmadSyncPlan:
45
+ """Result of comparing PF YAML and BMAD markdown."""
46
+
47
+ changes: list[BmadSyncChange] = field(default_factory=list)
48
+ pf_only: list[str] = field(default_factory=list)
49
+ bmad_only: list[str] = field(default_factory=list)
50
+ both: list[str] = field(default_factory=list)
51
+ conflicts: list[dict[str, Any]] = field(default_factory=list)
52
+
53
+
54
+ @dataclass
55
+ class BmadSyncResult:
56
+ """Result of executing a sync plan."""
57
+
58
+ dry_run: bool
59
+ changes_planned: int
60
+ changes_applied: int
61
+ pf_modified: bool
62
+ bmad_modified: bool
63
+ new_stories_imported: int = 0
64
+ errors: list[str] = field(default_factory=list)
65
+
66
+
67
+ # =============================================================================
68
+ # Sync Plan Generation
69
+ # =============================================================================
70
+
71
+
72
+ def _collect_pf_stories(sprint_path: Path) -> list[dict[str, Any]]:
73
+ """Load all PF stories that have a bmad_key field."""
74
+ from pennyfarthing_scripts.sprint.yaml_io import read_sprint
75
+
76
+ data = read_sprint(sprint_path)
77
+ stories: list[dict[str, Any]] = []
78
+ for epic in data.get("epics", []):
79
+ for story in epic.get("stories", []):
80
+ if story.get("bmad_key"):
81
+ stories.append(dict(story))
82
+ return stories
83
+
84
+
85
+ def generate_sync_plan(
86
+ pf_stories: list[dict[str, Any]],
87
+ bmad_stories: list[dict[str, Any]],
88
+ *,
89
+ direction: Literal["pull", "push", "both"] = "both",
90
+ pf_wins: bool = True,
91
+ ) -> BmadSyncPlan:
92
+ """Compare PF and BMAD stories and build a sync plan.
93
+
94
+ Args:
95
+ pf_stories: Stories from PF YAML (must have bmad_key field)
96
+ bmad_stories: Stories parsed from BMAD markdown
97
+ direction: "pull" (BMAD→PF), "push" (PF→BMAD), or "both"
98
+ pf_wins: If True, PF status wins on conflict (default for push)
99
+
100
+ Returns:
101
+ BmadSyncPlan with changes, conflicts, and set membership.
102
+ """
103
+ plan = BmadSyncPlan()
104
+
105
+ # Build lookup maps keyed on bmad_key
106
+ pf_by_key: dict[str, dict] = {}
107
+ for story in pf_stories:
108
+ key = story.get("bmad_key", "")
109
+ if key:
110
+ pf_by_key[key] = story
111
+
112
+ bmad_by_key: dict[str, dict] = {}
113
+ for story in bmad_stories:
114
+ key = story.get("bmad_key", "")
115
+ if key:
116
+ bmad_by_key[key] = story
117
+
118
+ pf_keys = set(pf_by_key.keys())
119
+ bmad_keys = set(bmad_by_key.keys())
120
+
121
+ plan.pf_only = sorted(pf_keys - bmad_keys)
122
+ plan.bmad_only = sorted(bmad_keys - pf_keys)
123
+ plan.both = sorted(pf_keys & bmad_keys)
124
+
125
+ # Compare matched stories
126
+ for key in plan.both:
127
+ pf_story = pf_by_key[key]
128
+ bmad_story = bmad_by_key[key]
129
+
130
+ pf_status = pf_story.get("status", "planning")
131
+ bmad_status_raw = bmad_story.get("bmad_status", "draft")
132
+ bmad_status_as_pf = map_bmad_to_pf(bmad_status_raw)
133
+
134
+ if pf_status == bmad_status_as_pf:
135
+ continue # In sync
136
+
137
+ pf_id = pf_story.get("id", key)
138
+
139
+ if direction == "pull":
140
+ # BMAD → PF
141
+ plan.changes.append(
142
+ BmadSyncChange(
143
+ bmad_key=key,
144
+ pf_id=pf_id,
145
+ field="status",
146
+ action="update-pf",
147
+ pf_value=pf_status,
148
+ bmad_value=bmad_status_raw,
149
+ target_value=bmad_status_as_pf,
150
+ )
151
+ )
152
+ elif direction == "push":
153
+ # PF → BMAD
154
+ target_bmad = map_pf_to_bmad(pf_status)
155
+ plan.changes.append(
156
+ BmadSyncChange(
157
+ bmad_key=key,
158
+ pf_id=pf_id,
159
+ field="status",
160
+ action="update-bmad",
161
+ pf_value=pf_status,
162
+ bmad_value=bmad_status_raw,
163
+ target_value=target_bmad,
164
+ )
165
+ )
166
+ else:
167
+ # Both directions — resolve by pf_wins flag
168
+ if pf_wins:
169
+ target_bmad = map_pf_to_bmad(pf_status)
170
+ plan.changes.append(
171
+ BmadSyncChange(
172
+ bmad_key=key,
173
+ pf_id=pf_id,
174
+ field="status",
175
+ action="update-bmad",
176
+ pf_value=pf_status,
177
+ bmad_value=bmad_status_raw,
178
+ target_value=target_bmad,
179
+ )
180
+ )
181
+ else:
182
+ plan.changes.append(
183
+ BmadSyncChange(
184
+ bmad_key=key,
185
+ pf_id=pf_id,
186
+ field="status",
187
+ action="update-pf",
188
+ pf_value=pf_status,
189
+ bmad_value=bmad_status_raw,
190
+ target_value=bmad_status_as_pf,
191
+ )
192
+ )
193
+
194
+ return plan
195
+
196
+
197
+ # =============================================================================
198
+ # Sync Plan Execution
199
+ # =============================================================================
200
+
201
+
202
+ def _update_bmad_file_status(bmad_path: str, new_status: str) -> bool:
203
+ """Rewrite the Status: line in a BMAD markdown file.
204
+
205
+ Args:
206
+ bmad_path: Absolute path to the .md file
207
+ new_status: New BMAD status string (e.g. "completed")
208
+
209
+ Returns:
210
+ True if the file was modified.
211
+ """
212
+ path = Path(bmad_path)
213
+ if not path.exists():
214
+ return False
215
+
216
+ content = path.read_text()
217
+ new_content, count = re.subn(
218
+ r"^(Status:\s*)(.+)$",
219
+ rf"\g<1>{new_status}",
220
+ content,
221
+ count=1,
222
+ flags=re.MULTILINE,
223
+ )
224
+ if count == 0:
225
+ return False
226
+
227
+ path.write_text(new_content)
228
+ return True
229
+
230
+
231
+ def execute_sync_plan(
232
+ plan: BmadSyncPlan,
233
+ *,
234
+ dry_run: bool = False,
235
+ sprint_path: Path | None = None,
236
+ bmad_root: Path | None = None,
237
+ import_new: bool = False,
238
+ repos: str = "axiathon",
239
+ ) -> BmadSyncResult:
240
+ """Execute a sync plan.
241
+
242
+ Args:
243
+ plan: The sync plan to execute
244
+ dry_run: If True, report without applying
245
+ sprint_path: Path to PF sprint YAML
246
+ bmad_root: Path to BMAD _bmad-output/ root
247
+ import_new: If True, import bmad_only stories as new PF stories
248
+ repos: Default repos for new story imports
249
+
250
+ Returns:
251
+ BmadSyncResult with counts and errors.
252
+ """
253
+ result = BmadSyncResult(
254
+ dry_run=dry_run,
255
+ changes_planned=len(plan.changes),
256
+ changes_applied=0,
257
+ pf_modified=False,
258
+ bmad_modified=False,
259
+ )
260
+
261
+ if dry_run:
262
+ return result
263
+
264
+ # Apply PF updates (YAML)
265
+ pf_updates = [c for c in plan.changes if c.action == "update-pf"]
266
+ if pf_updates and sprint_path:
267
+ from pennyfarthing_scripts.sprint.story_update import update_story
268
+
269
+ for change in pf_updates:
270
+ update_result = update_story(
271
+ sprint_path,
272
+ change.pf_id,
273
+ status=change.target_value,
274
+ )
275
+ if update_result.get("success"):
276
+ result.changes_applied += 1
277
+ result.pf_modified = True
278
+ else:
279
+ result.errors.append(
280
+ f"{change.pf_id}: PF update failed — {update_result.get('error', 'unknown')}"
281
+ )
282
+
283
+ # Apply BMAD updates (markdown files)
284
+ bmad_updates = [c for c in plan.changes if c.action == "update-bmad"]
285
+ if bmad_updates:
286
+ # Need bmad story data to find file paths
287
+ # Re-discover to get bmad_path for each key
288
+ bmad_paths: dict[str, str] = {}
289
+ if bmad_root:
290
+ config = load_pennyfarthing_config()
291
+ bmad_config = config.get("bmad", {})
292
+ story_subdir = bmad_config.get("story_dir", "implementation-artifacts")
293
+ from pennyfarthing_scripts.bmad.parser import discover_bmad_stories as _discover
294
+
295
+ all_bmad = _discover(bmad_root, story_dir=story_subdir)
296
+ bmad_paths = {s["bmad_key"]: s["bmad_path"] for s in all_bmad}
297
+
298
+ for change in bmad_updates:
299
+ file_path = bmad_paths.get(change.bmad_key)
300
+ if not file_path:
301
+ result.errors.append(f"{change.bmad_key}: BMAD file not found")
302
+ continue
303
+ if _update_bmad_file_status(file_path, change.target_value):
304
+ result.changes_applied += 1
305
+ result.bmad_modified = True
306
+ else:
307
+ result.errors.append(f"{change.bmad_key}: Failed to update Status line")
308
+
309
+ # Import new BMAD stories not yet in PF
310
+ if import_new and plan.bmad_only and sprint_path and bmad_root:
311
+ result.new_stories_imported = _import_new_stories(
312
+ plan.bmad_only, bmad_root, sprint_path, repos, result
313
+ )
314
+
315
+ return result
316
+
317
+
318
+ def _import_new_stories(
319
+ bmad_keys: list[str],
320
+ bmad_root: Path,
321
+ sprint_path: Path,
322
+ repos: str,
323
+ result: BmadSyncResult,
324
+ ) -> int:
325
+ """Import stories that exist in BMAD but not PF.
326
+
327
+ Returns count of successfully imported stories.
328
+ """
329
+ from pennyfarthing_scripts.bmad.parser import discover_bmad_stories as _discover
330
+ from pennyfarthing_scripts.sprint.yaml_io import read_sprint, write_sprint
331
+
332
+ config = load_pennyfarthing_config()
333
+ bmad_config = config.get("bmad", {})
334
+ story_subdir = bmad_config.get("story_dir", "implementation-artifacts")
335
+
336
+ all_bmad = _discover(bmad_root, story_dir=story_subdir)
337
+ bmad_by_key = {s["bmad_key"]: s for s in all_bmad}
338
+
339
+ data = read_sprint(sprint_path)
340
+ imported = 0
341
+
342
+ for key in bmad_keys:
343
+ bmad_story = bmad_by_key.get(key)
344
+ if not bmad_story:
345
+ continue
346
+
347
+ epic_num = int(bmad_story["epic_num"])
348
+
349
+ # Find or create the epic in sprint data
350
+ target_epic = None
351
+ for epic in data.get("epics", []):
352
+ epic_id = str(epic.get("id", "")).replace("epic-", "")
353
+ if epic_id == str(epic_num):
354
+ target_epic = epic
355
+ break
356
+
357
+ if target_epic is None:
358
+ # Create new epic shard
359
+ target_epic = {
360
+ "id": str(epic_num),
361
+ "title": f"Epic {epic_num}",
362
+ "status": "planning",
363
+ "priority": "P1",
364
+ "marker": "bmad",
365
+ "repos": repos,
366
+ "stories": [],
367
+ }
368
+ data.setdefault("epics", []).append(target_epic)
369
+
370
+ # Add story
371
+ pf_story = {
372
+ "id": bmad_story["id"],
373
+ "title": bmad_story["title"],
374
+ "points": bmad_story.get("points", 3),
375
+ "priority": bmad_story.get("priority", "P1"),
376
+ "status": bmad_story["status"],
377
+ "repos": repos,
378
+ "workflow": bmad_story.get("workflow", "tdd"),
379
+ "bmad_key": key,
380
+ }
381
+ target_epic.setdefault("stories", []).append(pf_story)
382
+ imported += 1
383
+
384
+ if imported > 0:
385
+ write_sprint(sprint_path, data)
386
+ result.pf_modified = True
387
+
388
+ return imported
389
+
390
+
391
+ # =============================================================================
392
+ # Formatting
393
+ # =============================================================================
394
+
395
+
396
+ def format_sync_plan(plan: BmadSyncPlan) -> str:
397
+ """Format a sync plan for human-readable display.
398
+
399
+ Args:
400
+ plan: The plan to format
401
+
402
+ Returns:
403
+ Formatted string.
404
+ """
405
+ lines: list[str] = []
406
+
407
+ lines.append(f"Matched: {len(plan.both)} | PF-only: {len(plan.pf_only)} | BMAD-only: {len(plan.bmad_only)}")
408
+ lines.append("")
409
+
410
+ if plan.changes:
411
+ lines.append(f"Changes ({len(plan.changes)}):")
412
+ for c in plan.changes:
413
+ arrow = "BMAD→PF" if c.action == "update-pf" else "PF→BMAD"
414
+ lines.append(
415
+ f" {c.pf_id} ({c.bmad_key}): {c.field} {arrow} "
416
+ f"{c.pf_value!r} / {c.bmad_value!r} → {c.target_value!r}"
417
+ )
418
+ lines.append("")
419
+
420
+ if plan.bmad_only:
421
+ lines.append(f"New in BMAD ({len(plan.bmad_only)}):")
422
+ for key in plan.bmad_only:
423
+ lines.append(f" {key}")
424
+ lines.append("")
425
+
426
+ if plan.pf_only:
427
+ lines.append(f"PF-only ({len(plan.pf_only)}):")
428
+ for key in plan.pf_only:
429
+ lines.append(f" {key}")
430
+ lines.append("")
431
+
432
+ if not plan.changes and not plan.bmad_only and not plan.pf_only:
433
+ lines.append("Everything is in sync.")
434
+
435
+ return "\n".join(lines)
436
+
437
+
438
+ # =============================================================================
439
+ # Drift Report
440
+ # =============================================================================
441
+
442
+
443
+ def drift_report(
444
+ sprint_path: Path,
445
+ bmad_root: Path,
446
+ ) -> str:
447
+ """Generate a drift report showing what's out of sync.
448
+
449
+ Args:
450
+ sprint_path: Path to PF sprint YAML
451
+ bmad_root: Path to BMAD _bmad-output/ root
452
+
453
+ Returns:
454
+ Formatted drift report string.
455
+ """
456
+ config = load_pennyfarthing_config()
457
+ bmad_config = config.get("bmad", {})
458
+ story_subdir = bmad_config.get("story_dir", "implementation-artifacts")
459
+
460
+ pf_stories = _collect_pf_stories(sprint_path)
461
+ bmad_stories = discover_bmad_stories(bmad_root, story_dir=story_subdir)
462
+
463
+ plan = generate_sync_plan(pf_stories, bmad_stories, direction="both")
464
+ return format_sync_plan(plan)