@pennyfarthing/core 11.3.1 → 11.3.3

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 (416) 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/commands/pf-setup.md +4 -2
  38. package/pennyfarthing-dist/personas/themes/discworld.yaml +16 -24
  39. package/pennyfarthing-dist/scripts/lib/run-pf.sh +3 -0
  40. package/pennyfarthing-dist/workflows/project-setup/steps/step-08-theme-packs.md +1 -1
  41. package/pennyfarthing-dist/workflows/project-setup/steps/step-09-jira.md +92 -0
  42. package/pennyfarthing-dist/workflows/project-setup/steps/{step-09-cyclist.md → step-10-cyclist.md} +2 -2
  43. package/pennyfarthing-dist/workflows/project-setup/steps/{step-10-complete.md → step-11-complete.md} +2 -1
  44. package/pennyfarthing_scripts/__pycache__/__init__.cpython-314.pyc +0 -0
  45. package/pennyfarthing_scripts/__pycache__/bellmode_hook.cpython-314.pyc +0 -0
  46. package/pennyfarthing_scripts/__pycache__/cli.cpython-314.pyc +0 -0
  47. package/pennyfarthing_scripts/__pycache__/config.cpython-314.pyc +0 -0
  48. package/pennyfarthing_scripts/__pycache__/context.cpython-314.pyc +0 -0
  49. package/pennyfarthing_scripts/__pycache__/hooks.cpython-314.pyc +0 -0
  50. package/pennyfarthing_scripts/__pycache__/jira_bidirectional_sync.cpython-314.pyc +0 -0
  51. package/pennyfarthing_scripts/__pycache__/jira_epic_creation.cpython-314.pyc +0 -0
  52. package/pennyfarthing_scripts/__pycache__/jira_sync.cpython-314.pyc +0 -0
  53. package/pennyfarthing_scripts/__pycache__/jira_sync_story.cpython-314.pyc +0 -0
  54. package/pennyfarthing_scripts/__pycache__/output.cpython-314.pyc +0 -0
  55. package/pennyfarthing_scripts/__pycache__/patch_mode.cpython-314.pyc +0 -0
  56. package/pennyfarthing_scripts/__pycache__/pretooluse_hook.cpython-314.pyc +0 -0
  57. package/pennyfarthing_scripts/__pycache__/schema_validation_hook.cpython-314.pyc +0 -0
  58. package/pennyfarthing_scripts/__pycache__/session_start_hook.cpython-314.pyc +0 -0
  59. package/pennyfarthing_scripts/__pycache__/workflow.cpython-314.pyc +0 -0
  60. package/pennyfarthing_scripts/bc/__pycache__/__init__.cpython-314.pyc +0 -0
  61. package/pennyfarthing_scripts/bc/__pycache__/cli.cpython-314.pyc +0 -0
  62. package/pennyfarthing_scripts/bc/__pycache__/focus.cpython-314.pyc +0 -0
  63. package/pennyfarthing_scripts/bc/__pycache__/split.cpython-314.pyc +0 -0
  64. package/pennyfarthing_scripts/bikerack/__pycache__/__init__.cpython-314.pyc +0 -0
  65. package/pennyfarthing_scripts/bikerack/__pycache__/__main__.cpython-314.pyc +0 -0
  66. package/pennyfarthing_scripts/bikerack/__pycache__/audit_log_panel.cpython-314.pyc +0 -0
  67. package/pennyfarthing_scripts/bikerack/__pycache__/background_panel.cpython-314.pyc +0 -0
  68. package/pennyfarthing_scripts/bikerack/__pycache__/base_panel.cpython-314.pyc +0 -0
  69. package/pennyfarthing_scripts/bikerack/__pycache__/changed_panel.cpython-314.pyc +0 -0
  70. package/pennyfarthing_scripts/bikerack/__pycache__/cli.cpython-314.pyc +0 -0
  71. package/pennyfarthing_scripts/bikerack/__pycache__/context_meter_footer.cpython-314.pyc +0 -0
  72. package/pennyfarthing_scripts/bikerack/__pycache__/debug_panel.cpython-314.pyc +0 -0
  73. package/pennyfarthing_scripts/bikerack/__pycache__/diffs_panel.cpython-314.pyc +0 -0
  74. package/pennyfarthing_scripts/bikerack/__pycache__/events.cpython-314.pyc +0 -0
  75. package/pennyfarthing_scripts/bikerack/__pycache__/git_panel.cpython-314.pyc +0 -0
  76. package/pennyfarthing_scripts/bikerack/__pycache__/launcher.cpython-314.pyc +0 -0
  77. package/pennyfarthing_scripts/bikerack/__pycache__/portrait.cpython-314.pyc +0 -0
  78. package/pennyfarthing_scripts/bikerack/__pycache__/portrait_resolver.cpython-314.pyc +0 -0
  79. package/pennyfarthing_scripts/bikerack/__pycache__/progress_panel.cpython-314.pyc +0 -0
  80. package/pennyfarthing_scripts/bikerack/__pycache__/sprint_panel.cpython-314.pyc +0 -0
  81. package/pennyfarthing_scripts/bikerack/__pycache__/story_detail_data.cpython-314.pyc +0 -0
  82. package/pennyfarthing_scripts/bikerack/__pycache__/story_detail_screen.cpython-314.pyc +0 -0
  83. package/pennyfarthing_scripts/bikerack/__pycache__/tui.cpython-314.pyc +0 -0
  84. package/pennyfarthing_scripts/bikerack/__pycache__/ws_client.cpython-314.pyc +0 -0
  85. package/pennyfarthing_scripts/bmad/__init__.py +1 -0
  86. package/pennyfarthing_scripts/bmad/__pycache__/__init__.cpython-314.pyc +0 -0
  87. package/pennyfarthing_scripts/bmad/__pycache__/cli.cpython-314.pyc +0 -0
  88. package/pennyfarthing_scripts/bmad/__pycache__/parser.cpython-314.pyc +0 -0
  89. package/pennyfarthing_scripts/bmad/__pycache__/sync.cpython-314.pyc +0 -0
  90. package/pennyfarthing_scripts/bmad/__pycache__/test_parser.cpython-314-pytest-9.0.2.pyc +0 -0
  91. package/pennyfarthing_scripts/bmad/__pycache__/test_sync.cpython-314-pytest-9.0.2.pyc +0 -0
  92. package/pennyfarthing_scripts/bmad/cli.py +197 -0
  93. package/pennyfarthing_scripts/bmad/importer.py +200 -0
  94. package/pennyfarthing_scripts/bmad/parser.py +233 -0
  95. package/pennyfarthing_scripts/bmad/sync.py +464 -0
  96. package/pennyfarthing_scripts/bmad/test_parser.py +253 -0
  97. package/pennyfarthing_scripts/bmad/test_sync.py +223 -0
  98. package/pennyfarthing_scripts/brownfield/__pycache__/__init__.cpython-314.pyc +0 -0
  99. package/pennyfarthing_scripts/brownfield/__pycache__/__main__.cpython-314.pyc +0 -0
  100. package/pennyfarthing_scripts/brownfield/__pycache__/cli.cpython-314.pyc +0 -0
  101. package/pennyfarthing_scripts/brownfield/__pycache__/discover.cpython-314.pyc +0 -0
  102. package/pennyfarthing_scripts/cli.py +5 -0
  103. package/pennyfarthing_scripts/codemarkers/__pycache__/__init__.cpython-314.pyc +0 -0
  104. package/pennyfarthing_scripts/codemarkers/__pycache__/__main__.cpython-314.pyc +0 -0
  105. package/pennyfarthing_scripts/codemarkers/__pycache__/analyze.cpython-314.pyc +0 -0
  106. package/pennyfarthing_scripts/codemarkers/__pycache__/cli.cpython-314.pyc +0 -0
  107. package/pennyfarthing_scripts/codemarkers/__pycache__/formatters.cpython-314.pyc +0 -0
  108. package/pennyfarthing_scripts/codemarkers/__pycache__/models.cpython-314.pyc +0 -0
  109. package/pennyfarthing_scripts/common/__pycache__/__init__.cpython-314.pyc +0 -0
  110. package/pennyfarthing_scripts/common/__pycache__/config.cpython-314.pyc +0 -0
  111. package/pennyfarthing_scripts/common/__pycache__/output.cpython-314.pyc +0 -0
  112. package/pennyfarthing_scripts/common/__pycache__/themes.cpython-314.pyc +0 -0
  113. package/pennyfarthing_scripts/complexity/__pycache__/__init__.cpython-314.pyc +0 -0
  114. package/pennyfarthing_scripts/complexity/__pycache__/__main__.cpython-314.pyc +0 -0
  115. package/pennyfarthing_scripts/complexity/__pycache__/analyze.cpython-314.pyc +0 -0
  116. package/pennyfarthing_scripts/complexity/__pycache__/cli.cpython-314.pyc +0 -0
  117. package/pennyfarthing_scripts/complexity/__pycache__/formatters.cpython-314.pyc +0 -0
  118. package/pennyfarthing_scripts/complexity/__pycache__/models.cpython-314.pyc +0 -0
  119. package/pennyfarthing_scripts/consultation/__pycache__/__init__.cpython-314.pyc +0 -0
  120. package/pennyfarthing_scripts/consultation/__pycache__/cli.cpython-314.pyc +0 -0
  121. package/pennyfarthing_scripts/deadcode/__pycache__/__init__.cpython-314.pyc +0 -0
  122. package/pennyfarthing_scripts/deadcode/__pycache__/__main__.cpython-314.pyc +0 -0
  123. package/pennyfarthing_scripts/deadcode/__pycache__/analyze.cpython-314.pyc +0 -0
  124. package/pennyfarthing_scripts/deadcode/__pycache__/cli.cpython-314.pyc +0 -0
  125. package/pennyfarthing_scripts/deadcode/__pycache__/formatters.cpython-314.pyc +0 -0
  126. package/pennyfarthing_scripts/deadcode/__pycache__/models.cpython-314.pyc +0 -0
  127. package/pennyfarthing_scripts/dependencies/__pycache__/__init__.cpython-314.pyc +0 -0
  128. package/pennyfarthing_scripts/dependencies/__pycache__/__main__.cpython-314.pyc +0 -0
  129. package/pennyfarthing_scripts/dependencies/__pycache__/analyze.cpython-314.pyc +0 -0
  130. package/pennyfarthing_scripts/dependencies/__pycache__/cli.cpython-314.pyc +0 -0
  131. package/pennyfarthing_scripts/dependencies/__pycache__/formatters.cpython-314.pyc +0 -0
  132. package/pennyfarthing_scripts/dependencies/__pycache__/models.cpython-314.pyc +0 -0
  133. package/pennyfarthing_scripts/epic/__pycache__/__init__.cpython-314.pyc +0 -0
  134. package/pennyfarthing_scripts/epic/__pycache__/cli.cpython-314.pyc +0 -0
  135. package/pennyfarthing_scripts/gate/__pycache__/__init__.cpython-314.pyc +0 -0
  136. package/pennyfarthing_scripts/gate/__pycache__/cli.cpython-314.pyc +0 -0
  137. package/pennyfarthing_scripts/gate/__pycache__/validate.cpython-314.pyc +0 -0
  138. package/pennyfarthing_scripts/git/__pycache__/__init__.cpython-314.pyc +0 -0
  139. package/pennyfarthing_scripts/git/__pycache__/create_branches.cpython-314.pyc +0 -0
  140. package/pennyfarthing_scripts/git/__pycache__/hooks_installer.cpython-314.pyc +0 -0
  141. package/pennyfarthing_scripts/git/__pycache__/repos.cpython-314.pyc +0 -0
  142. package/pennyfarthing_scripts/git/__pycache__/status_all.cpython-314.pyc +0 -0
  143. package/pennyfarthing_scripts/git/__pycache__/worktree.cpython-314.pyc +0 -0
  144. package/pennyfarthing_scripts/git_group/__pycache__/__init__.cpython-314.pyc +0 -0
  145. package/pennyfarthing_scripts/git_group/__pycache__/cli.cpython-314.pyc +0 -0
  146. package/pennyfarthing_scripts/handoff/__pycache__/__init__.cpython-314.pyc +0 -0
  147. package/pennyfarthing_scripts/handoff/__pycache__/cli.cpython-314.pyc +0 -0
  148. package/pennyfarthing_scripts/handoff/__pycache__/complete_phase.cpython-314.pyc +0 -0
  149. package/pennyfarthing_scripts/handoff/__pycache__/gate_file.cpython-314.pyc +0 -0
  150. package/pennyfarthing_scripts/handoff/__pycache__/gate_runner.cpython-314.pyc +0 -0
  151. package/pennyfarthing_scripts/handoff/__pycache__/marker.cpython-314.pyc +0 -0
  152. package/pennyfarthing_scripts/handoff/__pycache__/phase_check.cpython-314.pyc +0 -0
  153. package/pennyfarthing_scripts/handoff/__pycache__/resolve_gate.cpython-314.pyc +0 -0
  154. package/pennyfarthing_scripts/healthscore/__pycache__/__init__.cpython-314.pyc +0 -0
  155. package/pennyfarthing_scripts/healthscore/__pycache__/__main__.cpython-314.pyc +0 -0
  156. package/pennyfarthing_scripts/healthscore/__pycache__/analyze.cpython-314.pyc +0 -0
  157. package/pennyfarthing_scripts/healthscore/__pycache__/cli.cpython-314.pyc +0 -0
  158. package/pennyfarthing_scripts/healthscore/__pycache__/formatters.cpython-314.pyc +0 -0
  159. package/pennyfarthing_scripts/healthscore/__pycache__/models.cpython-314.pyc +0 -0
  160. package/pennyfarthing_scripts/hooks/__pycache__/__init__.cpython-314.pyc +0 -0
  161. package/pennyfarthing_scripts/hooks/__pycache__/bell_mode.cpython-314.pyc +0 -0
  162. package/pennyfarthing_scripts/hooks/__pycache__/cli.cpython-314.pyc +0 -0
  163. package/pennyfarthing_scripts/hooks/__pycache__/context_breaker.cpython-314.pyc +0 -0
  164. package/pennyfarthing_scripts/hooks/__pycache__/context_warning.cpython-314.pyc +0 -0
  165. package/pennyfarthing_scripts/hooks/__pycache__/cyclist_pretooluse.cpython-314.pyc +0 -0
  166. package/pennyfarthing_scripts/hooks/__pycache__/pre_edit_check.cpython-314.pyc +0 -0
  167. package/pennyfarthing_scripts/hooks/__pycache__/reflector_check.cpython-314.pyc +0 -0
  168. package/pennyfarthing_scripts/hooks/__pycache__/schema_validation.cpython-314.pyc +0 -0
  169. package/pennyfarthing_scripts/hooks/__pycache__/session_start.cpython-314.pyc +0 -0
  170. package/pennyfarthing_scripts/hooks/__pycache__/session_stop.cpython-314.pyc +0 -0
  171. package/pennyfarthing_scripts/hooks/__pycache__/sprint_yaml_validation.cpython-314.pyc +0 -0
  172. package/pennyfarthing_scripts/hooks/__pycache__/statusline.cpython-314.pyc +0 -0
  173. package/pennyfarthing_scripts/hotspots/__pycache__/__init__.cpython-314.pyc +0 -0
  174. package/pennyfarthing_scripts/hotspots/__pycache__/__main__.cpython-314.pyc +0 -0
  175. package/pennyfarthing_scripts/hotspots/__pycache__/analyze.cpython-314.pyc +0 -0
  176. package/pennyfarthing_scripts/hotspots/__pycache__/cli.cpython-314.pyc +0 -0
  177. package/pennyfarthing_scripts/hotspots/__pycache__/formatters.cpython-314.pyc +0 -0
  178. package/pennyfarthing_scripts/hotspots/__pycache__/models.cpython-314.pyc +0 -0
  179. package/pennyfarthing_scripts/jira/__pycache__/__init__.cpython-314.pyc +0 -0
  180. package/pennyfarthing_scripts/jira/__pycache__/__main__.cpython-314.pyc +0 -0
  181. package/pennyfarthing_scripts/jira/__pycache__/bidirectional.cpython-314.pyc +0 -0
  182. package/pennyfarthing_scripts/jira/__pycache__/claim.cpython-314.pyc +0 -0
  183. package/pennyfarthing_scripts/jira/__pycache__/cli.cpython-314.pyc +0 -0
  184. package/pennyfarthing_scripts/jira/__pycache__/client.cpython-314.pyc +0 -0
  185. package/pennyfarthing_scripts/jira/__pycache__/create.cpython-314.pyc +0 -0
  186. package/pennyfarthing_scripts/jira/__pycache__/epic.cpython-314.pyc +0 -0
  187. package/pennyfarthing_scripts/jira/__pycache__/operations.cpython-314.pyc +0 -0
  188. package/pennyfarthing_scripts/jira/__pycache__/reconcile.cpython-314.pyc +0 -0
  189. package/pennyfarthing_scripts/jira/__pycache__/story.cpython-314.pyc +0 -0
  190. package/pennyfarthing_scripts/jira/__pycache__/sync.cpython-314.pyc +0 -0
  191. package/pennyfarthing_scripts/jira/client.py +14 -2
  192. package/pennyfarthing_scripts/launch/__pycache__/__init__.cpython-314.pyc +0 -0
  193. package/pennyfarthing_scripts/launch/__pycache__/cli.cpython-314.pyc +0 -0
  194. package/pennyfarthing_scripts/migration/__pycache__/__init__.cpython-314.pyc +0 -0
  195. package/pennyfarthing_scripts/migration/__pycache__/session.cpython-314.pyc +0 -0
  196. package/pennyfarthing_scripts/migration/__pycache__/skill.cpython-314.pyc +0 -0
  197. package/pennyfarthing_scripts/migration/__pycache__/step.cpython-314.pyc +0 -0
  198. package/pennyfarthing_scripts/migration/__pycache__/validate.cpython-314.pyc +0 -0
  199. package/pennyfarthing_scripts/preflight/__pycache__/__init__.cpython-314.pyc +0 -0
  200. package/pennyfarthing_scripts/preflight/__pycache__/__main__.cpython-314.pyc +0 -0
  201. package/pennyfarthing_scripts/preflight/__pycache__/cli.cpython-314.pyc +0 -0
  202. package/pennyfarthing_scripts/preflight/__pycache__/finish.cpython-314.pyc +0 -0
  203. package/pennyfarthing_scripts/prime/__pycache__/__init__.cpython-314.pyc +0 -0
  204. package/pennyfarthing_scripts/prime/__pycache__/cli.cpython-314.pyc +0 -0
  205. package/pennyfarthing_scripts/prime/__pycache__/heatmap.cpython-314.pyc +0 -0
  206. package/pennyfarthing_scripts/prime/__pycache__/loader.cpython-314.pyc +0 -0
  207. package/pennyfarthing_scripts/prime/__pycache__/models.cpython-314.pyc +0 -0
  208. package/pennyfarthing_scripts/prime/__pycache__/persona.cpython-314.pyc +0 -0
  209. package/pennyfarthing_scripts/prime/__pycache__/session.cpython-314.pyc +0 -0
  210. package/pennyfarthing_scripts/prime/__pycache__/tiers.cpython-314.pyc +0 -0
  211. package/pennyfarthing_scripts/prime/__pycache__/version_sentinel.cpython-314.pyc +0 -0
  212. package/pennyfarthing_scripts/prime/__pycache__/workflow.cpython-314.pyc +0 -0
  213. package/pennyfarthing_scripts/session/__pycache__/__init__.cpython-314.pyc +0 -0
  214. package/pennyfarthing_scripts/session/__pycache__/cli.cpython-314.pyc +0 -0
  215. package/pennyfarthing_scripts/settings/__pycache__/__init__.cpython-314.pyc +0 -0
  216. package/pennyfarthing_scripts/settings/__pycache__/cli.cpython-314.pyc +0 -0
  217. package/pennyfarthing_scripts/settings/__pycache__/settings.cpython-314.pyc +0 -0
  218. package/pennyfarthing_scripts/settings/settings.py +1 -1
  219. package/pennyfarthing_scripts/sprint/__pycache__/__init__.cpython-314.pyc +0 -0
  220. package/pennyfarthing_scripts/sprint/__pycache__/__main__.cpython-314.pyc +0 -0
  221. package/pennyfarthing_scripts/sprint/__pycache__/archive.cpython-314.pyc +0 -0
  222. package/pennyfarthing_scripts/sprint/__pycache__/archive_epic.cpython-314.pyc +0 -0
  223. package/pennyfarthing_scripts/sprint/__pycache__/cli.cpython-314.pyc +0 -0
  224. package/pennyfarthing_scripts/sprint/__pycache__/epic_add.cpython-314.pyc +0 -0
  225. package/pennyfarthing_scripts/sprint/__pycache__/epic_update.cpython-314.pyc +0 -0
  226. package/pennyfarthing_scripts/sprint/__pycache__/import_epic.cpython-314.pyc +0 -0
  227. package/pennyfarthing_scripts/sprint/__pycache__/loader.cpython-314.pyc +0 -0
  228. package/pennyfarthing_scripts/sprint/__pycache__/status.cpython-314.pyc +0 -0
  229. package/pennyfarthing_scripts/sprint/__pycache__/story_add.cpython-314.pyc +0 -0
  230. package/pennyfarthing_scripts/sprint/__pycache__/story_finish.cpython-314.pyc +0 -0
  231. package/pennyfarthing_scripts/sprint/__pycache__/story_update.cpython-314.pyc +0 -0
  232. package/pennyfarthing_scripts/sprint/__pycache__/validate_cmd.cpython-314.pyc +0 -0
  233. package/pennyfarthing_scripts/sprint/__pycache__/validator.cpython-314.pyc +0 -0
  234. package/pennyfarthing_scripts/sprint/__pycache__/work.cpython-314.pyc +0 -0
  235. package/pennyfarthing_scripts/sprint/__pycache__/yaml_io.cpython-314.pyc +0 -0
  236. package/pennyfarthing_scripts/story/__pycache__/__init__.cpython-314.pyc +0 -0
  237. package/pennyfarthing_scripts/story/__pycache__/__main__.cpython-314.pyc +0 -0
  238. package/pennyfarthing_scripts/story/__pycache__/cli.cpython-314.pyc +0 -0
  239. package/pennyfarthing_scripts/story/__pycache__/create.cpython-314.pyc +0 -0
  240. package/pennyfarthing_scripts/story/__pycache__/size.cpython-314.pyc +0 -0
  241. package/pennyfarthing_scripts/story/__pycache__/template.cpython-314.pyc +0 -0
  242. package/pennyfarthing_scripts/tests/__pycache__/__init__.cpython-314.pyc +0 -0
  243. package/pennyfarthing_scripts/tests/__pycache__/conftest.cpython-314-pytest-9.0.2.pyc +0 -0
  244. package/pennyfarthing_scripts/tests/__pycache__/test_108_2_remove_handoff_fallback.cpython-314-pytest-9.0.2.pyc +0 -0
  245. package/pennyfarthing_scripts/tests/__pycache__/test_archive_epic.cpython-314-pytest-9.0.2.pyc +0 -0
  246. package/pennyfarthing_scripts/tests/__pycache__/test_bc.cpython-314-pytest-9.0.2.pyc +0 -0
  247. package/pennyfarthing_scripts/tests/__pycache__/test_bikerack.cpython-314-pytest-9.0.2.pyc +0 -0
  248. package/pennyfarthing_scripts/tests/__pycache__/test_brownfield.cpython-314-pytest-9.0.2.pyc +0 -0
  249. package/pennyfarthing_scripts/tests/__pycache__/test_cli_modules.cpython-314-pytest-9.0.2.pyc +0 -0
  250. package/pennyfarthing_scripts/tests/__pycache__/test_cli_normalization.cpython-314-pytest-9.0.2.pyc +0 -0
  251. package/pennyfarthing_scripts/tests/__pycache__/test_codemarkers.cpython-314-pytest-9.0.2.pyc +0 -0
  252. package/pennyfarthing_scripts/tests/__pycache__/test_common.cpython-314-pytest-9.0.2.pyc +0 -0
  253. package/pennyfarthing_scripts/tests/__pycache__/test_confidence_sm_evaluation.cpython-314-pytest-9.0.2.pyc +0 -0
  254. package/pennyfarthing_scripts/tests/__pycache__/test_confidence_sm_gate.cpython-314-pytest-9.0.2.pyc +0 -0
  255. package/pennyfarthing_scripts/tests/__pycache__/test_epic_shard_validation.cpython-314-pytest-9.0.2.pyc +0 -0
  256. package/pennyfarthing_scripts/tests/__pycache__/test_gate_file_resolution.cpython-314-pytest-9.0.2.pyc +0 -0
  257. package/pennyfarthing_scripts/tests/__pycache__/test_gate_runner.cpython-314-pytest-9.0.2.pyc +0 -0
  258. package/pennyfarthing_scripts/tests/__pycache__/test_git_utils.cpython-314-pytest-9.0.2.pyc +0 -0
  259. package/pennyfarthing_scripts/tests/__pycache__/test_handoff_cli.cpython-314-pytest-9.0.2.pyc +0 -0
  260. package/pennyfarthing_scripts/tests/__pycache__/test_handoff_e2e.cpython-314-pytest-9.0.2.pyc +0 -0
  261. package/pennyfarthing_scripts/tests/__pycache__/test_healthscore.cpython-314-pytest-9.0.2.pyc +0 -0
  262. package/pennyfarthing_scripts/tests/__pycache__/test_jira_package.cpython-314-pytest-9.0.2.pyc +0 -0
  263. package/pennyfarthing_scripts/tests/__pycache__/test_package_structure.cpython-314-pytest-9.0.2.pyc +0 -0
  264. package/pennyfarthing_scripts/tests/__pycache__/test_patch_mode.cpython-314-pytest-9.0.2.pyc +0 -0
  265. package/pennyfarthing_scripts/tests/__pycache__/test_prime.cpython-314-pytest-9.0.2.pyc +0 -0
  266. package/pennyfarthing_scripts/tests/__pycache__/test_resolve_gate_file_field.cpython-314-pytest-9.0.2.pyc +0 -0
  267. package/pennyfarthing_scripts/tests/__pycache__/test_sprint_package.cpython-314-pytest-9.0.2.pyc +0 -0
  268. package/pennyfarthing_scripts/tests/__pycache__/test_sprint_panel.cpython-314-pytest-9.0.2.pyc +0 -0
  269. package/pennyfarthing_scripts/tests/__pycache__/test_sprint_validator.cpython-314-pytest-9.0.2.pyc +0 -0
  270. package/pennyfarthing_scripts/tests/__pycache__/test_story_add.cpython-314-pytest-9.0.2.pyc +0 -0
  271. package/pennyfarthing_scripts/tests/__pycache__/test_story_package.cpython-314-pytest-9.0.2.pyc +0 -0
  272. package/pennyfarthing_scripts/tests/__pycache__/test_story_update.cpython-314-pytest-9.0.2.pyc +0 -0
  273. package/pennyfarthing_scripts/tests/__pycache__/test_tiers.cpython-314-pytest-9.0.2.pyc +0 -0
  274. package/pennyfarthing_scripts/tests/__pycache__/test_token_counting.cpython-314-pytest-9.0.2.pyc +0 -0
  275. package/pennyfarthing_scripts/tests/__pycache__/test_topology_loader.cpython-314-pytest-9.0.2.pyc +0 -0
  276. package/pennyfarthing_scripts/tests/__pycache__/test_tui_focus.cpython-314-pytest-9.0.2.pyc +0 -0
  277. package/pennyfarthing_scripts/tests/__pycache__/test_tui_panel_persistence.cpython-314-pytest-9.0.2.pyc +0 -0
  278. package/pennyfarthing_scripts/tests/__pycache__/test_validate_cmd.cpython-314-pytest-9.0.2.pyc +0 -0
  279. package/pennyfarthing_scripts/tests/__pycache__/test_version_sentinel.cpython-314-pytest-9.0.2.pyc +0 -0
  280. package/pennyfarthing_scripts/tests/__pycache__/test_workflow_check.cpython-314-pytest-9.0.2.pyc +0 -0
  281. package/pennyfarthing_scripts/tests/__pycache__/test_workflow_list_team.cpython-314-pytest-9.0.2.pyc +0 -0
  282. package/pennyfarthing_scripts/tests/__pycache__/test_workflow_list_team.cpython-314.pyc +0 -0
  283. package/pennyfarthing_scripts/tests/__pycache__/test_yaml_io.cpython-314-pytest-9.0.2.pyc +0 -0
  284. package/pennyfarthing_scripts/theme/__pycache__/__init__.cpython-314.pyc +0 -0
  285. package/pennyfarthing_scripts/theme/__pycache__/cli.cpython-314.pyc +0 -0
  286. package/pennyfarthing_scripts/validate/__pycache__/__init__.cpython-314.pyc +0 -0
  287. package/pennyfarthing_scripts/validate/__pycache__/cli.cpython-314.pyc +0 -0
  288. package/pennyfarthing_scripts/validate/adapters/__pycache__/__init__.cpython-314.pyc +0 -0
  289. package/pennyfarthing_scripts/validate/adapters/__pycache__/agent.cpython-314.pyc +0 -0
  290. package/pennyfarthing_scripts/validate/adapters/__pycache__/schema.cpython-314.pyc +0 -0
  291. package/pennyfarthing_scripts/validate/adapters/__pycache__/skill_command.cpython-314.pyc +0 -0
  292. package/pennyfarthing_scripts/validate/adapters/__pycache__/sprint.cpython-314.pyc +0 -0
  293. package/pennyfarthing_scripts/validate/adapters/__pycache__/tandem_awareness.cpython-314.pyc +0 -0
  294. package/pennyfarthing_scripts/validate/adapters/__pycache__/team_mode.cpython-314.pyc +0 -0
  295. package/pennyfarthing_scripts/validate/adapters/__pycache__/workflow.cpython-314.pyc +0 -0
  296. package/pennyfarthing_scripts/workflow/__pycache__/__init__.cpython-314.pyc +0 -0
  297. package/pennyfarthing_scripts/workflow/__pycache__/cli.cpython-314.pyc +0 -0
  298. package/pennyfarthing_scripts/workflow/__pycache__/helpers.cpython-314.pyc +0 -0
  299. package/pennyfarthing_scripts/workflow/__pycache__/scale.cpython-314.pyc +0 -0
  300. package/pennyfarthing_scripts/workflow/__pycache__/state.cpython-314.pyc +0 -0
  301. package/pennyfarthing_scripts/workflow/__pycache__/team_lifecycle.cpython-314.pyc +0 -0
  302. package/packages/core/dist/cli/commands/stale-artifacts-cleanup.test.d.ts +0 -17
  303. package/packages/core/dist/cli/commands/stale-artifacts-cleanup.test.d.ts.map +0 -1
  304. package/packages/core/dist/cli/commands/stale-artifacts-cleanup.test.js +0 -470
  305. package/packages/core/dist/cli/commands/stale-artifacts-cleanup.test.js.map +0 -1
  306. package/packages/core/dist/cli/cyclist-migration.test.d.ts +0 -16
  307. package/packages/core/dist/cli/cyclist-migration.test.d.ts.map +0 -1
  308. package/packages/core/dist/cli/cyclist-migration.test.js +0 -229
  309. package/packages/core/dist/cli/cyclist-migration.test.js.map +0 -1
  310. package/packages/core/dist/cli/utils/stale-artifacts.d.ts +0 -59
  311. package/packages/core/dist/cli/utils/stale-artifacts.d.ts.map +0 -1
  312. package/packages/core/dist/cli/utils/stale-artifacts.js +0 -163
  313. package/packages/core/dist/cli/utils/stale-artifacts.js.map +0 -1
  314. package/packages/core/dist/scripts/benchmark-integration.d.ts +0 -182
  315. package/packages/core/dist/scripts/benchmark-integration.d.ts.map +0 -1
  316. package/packages/core/dist/scripts/benchmark-integration.js +0 -691
  317. package/packages/core/dist/scripts/benchmark-integration.js.map +0 -1
  318. package/packages/core/dist/scripts/job-fair-aggregator.d.ts +0 -150
  319. package/packages/core/dist/scripts/job-fair-aggregator.d.ts.map +0 -1
  320. package/packages/core/dist/scripts/job-fair-aggregator.js +0 -547
  321. package/packages/core/dist/scripts/job-fair-aggregator.js.map +0 -1
  322. package/packages/core/dist/scripts/theme-detail.test.d.ts.map +0 -1
  323. package/packages/core/dist/scripts/theme-detail.test.js.map +0 -1
  324. package/pennyfarthing-dist/scripts/hooks/__pycache__/question_reflector_check.cpython-314.pyc +0 -0
  325. package/pennyfarthing_scripts/__pycache__/__init__.cpython-311.pyc +0 -0
  326. package/pennyfarthing_scripts/__pycache__/cli.cpython-311.pyc +0 -0
  327. package/pennyfarthing_scripts/__pycache__/context.cpython-311.pyc +0 -0
  328. package/pennyfarthing_scripts/__pycache__/jira.cpython-314.pyc +0 -0
  329. package/pennyfarthing_scripts/__pycache__/sprint.cpython-314.pyc +0 -0
  330. package/pennyfarthing_scripts/__pycache__/workflow.cpython-311.pyc +0 -0
  331. package/pennyfarthing_scripts/bc/__pycache__/__init__.cpython-311.pyc +0 -0
  332. package/pennyfarthing_scripts/bc/__pycache__/cli.cpython-311.pyc +0 -0
  333. package/pennyfarthing_scripts/bc/__pycache__/focus.cpython-311.pyc +0 -0
  334. package/pennyfarthing_scripts/bikerack/__pycache__/__init__.cpython-311.pyc +0 -0
  335. package/pennyfarthing_scripts/bikerack/__pycache__/cli.cpython-311.pyc +0 -0
  336. package/pennyfarthing_scripts/bikerack/__pycache__/launcher.cpython-311.pyc +0 -0
  337. package/pennyfarthing_scripts/common/__pycache__/__init__.cpython-311.pyc +0 -0
  338. package/pennyfarthing_scripts/common/__pycache__/config.cpython-311.pyc +0 -0
  339. package/pennyfarthing_scripts/common/__pycache__/output.cpython-311.pyc +0 -0
  340. package/pennyfarthing_scripts/common/__pycache__/pr_config.cpython-314.pyc +0 -0
  341. package/pennyfarthing_scripts/consultation/__pycache__/__init__.cpython-311.pyc +0 -0
  342. package/pennyfarthing_scripts/consultation/__pycache__/cli.cpython-311.pyc +0 -0
  343. package/pennyfarthing_scripts/consultation/__pycache__/dialogue_manager.cpython-314.pyc +0 -0
  344. package/pennyfarthing_scripts/deadcode/__pycache__/__init__.cpython-311.pyc +0 -0
  345. package/pennyfarthing_scripts/deadcode/__pycache__/cli.cpython-311.pyc +0 -0
  346. package/pennyfarthing_scripts/epic/__pycache__/__init__.cpython-311.pyc +0 -0
  347. package/pennyfarthing_scripts/epic/__pycache__/cli.cpython-311.pyc +0 -0
  348. package/pennyfarthing_scripts/git_group/__pycache__/__init__.cpython-311.pyc +0 -0
  349. package/pennyfarthing_scripts/git_group/__pycache__/cli.cpython-311.pyc +0 -0
  350. package/pennyfarthing_scripts/handoff/__pycache__/__init__.cpython-311.pyc +0 -0
  351. package/pennyfarthing_scripts/handoff/__pycache__/cli.cpython-311.pyc +0 -0
  352. package/pennyfarthing_scripts/healthscore/__pycache__/__init__.cpython-311.pyc +0 -0
  353. package/pennyfarthing_scripts/healthscore/__pycache__/analyze.cpython-311.pyc +0 -0
  354. package/pennyfarthing_scripts/healthscore/__pycache__/cli.cpython-311.pyc +0 -0
  355. package/pennyfarthing_scripts/healthscore/__pycache__/models.cpython-311.pyc +0 -0
  356. package/pennyfarthing_scripts/hooks/__pycache__/__init__.cpython-311.pyc +0 -0
  357. package/pennyfarthing_scripts/hooks/__pycache__/bell_mode.cpython-311.pyc +0 -0
  358. package/pennyfarthing_scripts/hooks/__pycache__/cli.cpython-311.pyc +0 -0
  359. package/pennyfarthing_scripts/hooks/__pycache__/context_breaker.cpython-311.pyc +0 -0
  360. package/pennyfarthing_scripts/hooks/__pycache__/context_warning.cpython-311.pyc +0 -0
  361. package/pennyfarthing_scripts/hooks/__pycache__/cyclist_pretooluse.cpython-311.pyc +0 -0
  362. package/pennyfarthing_scripts/hooks/__pycache__/pre_edit_check.cpython-311.pyc +0 -0
  363. package/pennyfarthing_scripts/hooks/__pycache__/reflector_check.cpython-311.pyc +0 -0
  364. package/pennyfarthing_scripts/hooks/__pycache__/schema_validation.cpython-311.pyc +0 -0
  365. package/pennyfarthing_scripts/hooks/__pycache__/session_start.cpython-311.pyc +0 -0
  366. package/pennyfarthing_scripts/hooks/__pycache__/sprint_yaml_validation.cpython-311.pyc +0 -0
  367. package/pennyfarthing_scripts/hooks/__pycache__/statusline.cpython-311.pyc +0 -0
  368. package/pennyfarthing_scripts/hotspots/__pycache__/__init__.cpython-311.pyc +0 -0
  369. package/pennyfarthing_scripts/hotspots/__pycache__/analyze.cpython-311.pyc +0 -0
  370. package/pennyfarthing_scripts/hotspots/__pycache__/cli.cpython-311.pyc +0 -0
  371. package/pennyfarthing_scripts/hotspots/__pycache__/models.cpython-311.pyc +0 -0
  372. package/pennyfarthing_scripts/jira/__pycache__/__init__.cpython-311.pyc +0 -0
  373. package/pennyfarthing_scripts/jira/__pycache__/bidirectional.cpython-311.pyc +0 -0
  374. package/pennyfarthing_scripts/jira/__pycache__/claim.cpython-311.pyc +0 -0
  375. package/pennyfarthing_scripts/jira/__pycache__/cli.cpython-311.pyc +0 -0
  376. package/pennyfarthing_scripts/jira/__pycache__/client.cpython-311.pyc +0 -0
  377. package/pennyfarthing_scripts/jira/__pycache__/compat.cpython-314.pyc +0 -0
  378. package/pennyfarthing_scripts/jira/__pycache__/create.cpython-311.pyc +0 -0
  379. package/pennyfarthing_scripts/jira/__pycache__/epic.cpython-311.pyc +0 -0
  380. package/pennyfarthing_scripts/jira/__pycache__/mappings.cpython-314.pyc +0 -0
  381. package/pennyfarthing_scripts/jira/__pycache__/models.cpython-314.pyc +0 -0
  382. package/pennyfarthing_scripts/jira/__pycache__/operations.cpython-311.pyc +0 -0
  383. package/pennyfarthing_scripts/jira/__pycache__/reconcile.cpython-311.pyc +0 -0
  384. package/pennyfarthing_scripts/jira/__pycache__/story.cpython-311.pyc +0 -0
  385. package/pennyfarthing_scripts/jira/__pycache__/sync.cpython-311.pyc +0 -0
  386. package/pennyfarthing_scripts/launch/__pycache__/__init__.cpython-311.pyc +0 -0
  387. package/pennyfarthing_scripts/launch/__pycache__/cli.cpython-311.pyc +0 -0
  388. package/pennyfarthing_scripts/migration/__pycache__/__main__.cpython-314.pyc +0 -0
  389. package/pennyfarthing_scripts/migration/__pycache__/cli.cpython-314.pyc +0 -0
  390. package/pennyfarthing_scripts/prime/__pycache__/__main__.cpython-314.pyc +0 -0
  391. package/pennyfarthing_scripts/session/__pycache__/__init__.cpython-311.pyc +0 -0
  392. package/pennyfarthing_scripts/session/__pycache__/cli.cpython-311.pyc +0 -0
  393. package/pennyfarthing_scripts/sprint/__pycache__/__init__.cpython-311.pyc +0 -0
  394. package/pennyfarthing_scripts/sprint/__pycache__/archive.cpython-311.pyc +0 -0
  395. package/pennyfarthing_scripts/sprint/__pycache__/cli.cpython-311.pyc +0 -0
  396. package/pennyfarthing_scripts/sprint/__pycache__/epic_add.cpython-311.pyc +0 -0
  397. package/pennyfarthing_scripts/sprint/__pycache__/epic_update.cpython-311.pyc +0 -0
  398. package/pennyfarthing_scripts/sprint/__pycache__/loader.cpython-311.pyc +0 -0
  399. package/pennyfarthing_scripts/sprint/__pycache__/status.cpython-311.pyc +0 -0
  400. package/pennyfarthing_scripts/sprint/__pycache__/story_add.cpython-311.pyc +0 -0
  401. package/pennyfarthing_scripts/sprint/__pycache__/story_update.cpython-311.pyc +0 -0
  402. package/pennyfarthing_scripts/sprint/__pycache__/validate_cmd.cpython-311.pyc +0 -0
  403. package/pennyfarthing_scripts/sprint/__pycache__/validator.cpython-311.pyc +0 -0
  404. package/pennyfarthing_scripts/sprint/__pycache__/work.cpython-311.pyc +0 -0
  405. package/pennyfarthing_scripts/sprint/__pycache__/yaml_io.cpython-311.pyc +0 -0
  406. package/pennyfarthing_scripts/tests/__pycache__/test_108_1_gate_migration.cpython-314-pytest-9.0.2.pyc +0 -0
  407. package/pennyfarthing_scripts/tests/__pycache__/test_dialogue_manager.cpython-314-pytest-9.0.2.pyc +0 -0
  408. package/pennyfarthing_scripts/tests/__pycache__/test_workflow_cli.cpython-314-pytest-9.0.2.pyc +0 -0
  409. package/pennyfarthing_scripts/theme/__pycache__/__init__.cpython-311.pyc +0 -0
  410. package/pennyfarthing_scripts/theme/__pycache__/cli.cpython-311.pyc +0 -0
  411. package/pennyfarthing_scripts/validate/__pycache__/__init__.cpython-311.pyc +0 -0
  412. package/pennyfarthing_scripts/validate/__pycache__/cli.cpython-311.pyc +0 -0
  413. package/pennyfarthing_scripts/workflow/__pycache__/__init__.cpython-311.pyc +0 -0
  414. package/pennyfarthing_scripts/workflow/__pycache__/cli.cpython-311.pyc +0 -0
  415. package/pennyfarthing_scripts/workflow/__pycache__/scale.cpython-311.pyc +0 -0
  416. package/pennyfarthing_scripts/workflow/__pycache__/state.cpython-311.pyc +0 -0
@@ -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)