@pennyfarthing/core 11.4.0 → 11.5.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (511) hide show
  1. package/package.json +1 -1
  2. package/packages/core/dist/cli/commands/cyclist.js +1 -1
  3. package/packages/core/dist/cli/commands/cyclist.js.map +1 -1
  4. package/packages/core/dist/cli/commands/cyclist.test.js +3 -3
  5. package/packages/core/dist/cli/commands/cyclist.test.js.map +1 -1
  6. package/packages/core/dist/cli/commands/doctor-persona-config-false-negative.test.d.ts +22 -0
  7. package/packages/core/dist/cli/commands/doctor-persona-config-false-negative.test.d.ts.map +1 -0
  8. package/packages/core/dist/cli/commands/doctor-persona-config-false-negative.test.js +161 -0
  9. package/packages/core/dist/cli/commands/doctor-persona-config-false-negative.test.js.map +1 -0
  10. package/packages/core/dist/cli/commands/doctor.d.ts.map +1 -1
  11. package/packages/core/dist/cli/commands/doctor.js +101 -33
  12. package/packages/core/dist/cli/commands/doctor.js.map +1 -1
  13. package/packages/core/dist/cli/commands/init.d.ts.map +1 -1
  14. package/packages/core/dist/cli/commands/init.js +33 -13
  15. package/packages/core/dist/cli/commands/init.js.map +1 -1
  16. package/packages/core/dist/cli/commands/stale-artifacts-cleanup.test.d.ts +17 -0
  17. package/packages/core/dist/cli/commands/stale-artifacts-cleanup.test.d.ts.map +1 -0
  18. package/packages/core/dist/cli/commands/stale-artifacts-cleanup.test.js +470 -0
  19. package/packages/core/dist/cli/commands/stale-artifacts-cleanup.test.js.map +1 -0
  20. package/packages/core/dist/cli/commands/update.d.ts.map +1 -1
  21. package/packages/core/dist/cli/commands/update.js +68 -17
  22. package/packages/core/dist/cli/commands/update.js.map +1 -1
  23. package/packages/core/dist/cli/cyclist-migration.test.d.ts +16 -0
  24. package/packages/core/dist/cli/cyclist-migration.test.d.ts.map +1 -0
  25. package/packages/core/dist/cli/cyclist-migration.test.js +229 -0
  26. package/packages/core/dist/cli/cyclist-migration.test.js.map +1 -0
  27. package/packages/core/dist/cli/index.js +2 -21
  28. package/packages/core/dist/cli/index.js.map +1 -1
  29. package/packages/core/dist/cli/utils/node-modules.d.ts +7 -0
  30. package/packages/core/dist/cli/utils/node-modules.d.ts.map +1 -1
  31. package/packages/core/dist/cli/utils/node-modules.js +39 -0
  32. package/packages/core/dist/cli/utils/node-modules.js.map +1 -1
  33. package/packages/core/dist/cli/utils/settings.d.ts +1 -1
  34. package/packages/core/dist/cli/utils/settings.d.ts.map +1 -1
  35. package/packages/core/dist/cli/utils/settings.js +24 -21
  36. package/packages/core/dist/cli/utils/settings.js.map +1 -1
  37. package/packages/core/dist/cli/utils/stale-artifacts.d.ts +59 -0
  38. package/packages/core/dist/cli/utils/stale-artifacts.d.ts.map +1 -0
  39. package/packages/core/dist/cli/utils/stale-artifacts.js +163 -0
  40. package/packages/core/dist/cli/utils/stale-artifacts.js.map +1 -0
  41. package/packages/core/dist/public/css/react.css +1 -1
  42. package/packages/core/dist/public/js/react/react.js +39 -39
  43. package/packages/core/dist/scripts/benchmark-integration.d.ts +182 -0
  44. package/packages/core/dist/scripts/benchmark-integration.d.ts.map +1 -0
  45. package/packages/core/dist/scripts/benchmark-integration.js +691 -0
  46. package/packages/core/dist/scripts/benchmark-integration.js.map +1 -0
  47. package/packages/core/dist/scripts/job-fair-aggregator.d.ts +150 -0
  48. package/packages/core/dist/scripts/job-fair-aggregator.d.ts.map +1 -0
  49. package/packages/core/dist/scripts/job-fair-aggregator.js +547 -0
  50. package/packages/core/dist/scripts/job-fair-aggregator.js.map +1 -0
  51. package/packages/core/dist/scripts/theme-detail.test.d.ts.map +1 -0
  52. package/packages/core/dist/scripts/theme-detail.test.js.map +1 -0
  53. package/packages/core/dist/server/api/context.d.ts.map +1 -1
  54. package/packages/core/dist/server/api/context.js +23 -5
  55. package/packages/core/dist/server/api/context.js.map +1 -1
  56. package/packages/core/dist/server/api/index.d.ts +0 -1
  57. package/packages/core/dist/server/api/index.d.ts.map +1 -1
  58. package/packages/core/dist/server/api/index.js +0 -2
  59. package/packages/core/dist/server/api/index.js.map +1 -1
  60. package/packages/core/dist/server/api/settings.d.ts.map +1 -1
  61. package/packages/core/dist/server/api/settings.js +6 -0
  62. package/packages/core/dist/server/api/settings.js.map +1 -1
  63. package/packages/core/dist/server/otlp-receiver.d.ts +0 -4
  64. package/packages/core/dist/server/otlp-receiver.d.ts.map +1 -1
  65. package/packages/core/dist/server/otlp-receiver.js +0 -18
  66. package/packages/core/dist/server/otlp-receiver.js.map +1 -1
  67. package/packages/core/dist/server/otlp-receiver.test.js +0 -27
  68. package/packages/core/dist/server/otlp-receiver.test.js.map +1 -1
  69. package/packages/core/dist/server/server.d.ts +1 -1
  70. package/packages/core/dist/server/server.d.ts.map +1 -1
  71. package/packages/core/dist/server/server.js +2 -4
  72. package/packages/core/dist/server/server.js.map +1 -1
  73. package/packages/core/dist/workflow/cross-entity-validation.d.ts.map +1 -1
  74. package/packages/core/dist/workflow/cross-entity-validation.js.map +1 -1
  75. package/packages/core/src/public/App.tsx +0 -2
  76. package/packages/core/src/public/components/BikeRackWorkspace.tsx +0 -2
  77. package/packages/core/src/public/components/DockviewWorkspace.tsx +0 -4
  78. package/packages/core/src/public/components/StandalonePanel.tsx +0 -2
  79. package/packages/core/src/public/components/panels/SettingsPanel.tsx +83 -283
  80. package/packages/core/src/public/components/panels/SprintPanel.tsx +9 -1
  81. package/packages/core/src/public/components/panels/index.ts +0 -1
  82. package/packages/core/src/public/hooks/index.ts +0 -3
  83. package/packages/core/src/public/hooks/useSprint.ts +11 -2
  84. package/packages/core/src/public/images/cyclist-tandem-source.png +0 -0
  85. package/packages/core/src/public/styles/tailwind.css +0 -101
  86. package/pennyfarthing-dist/guides/bikerack.md +94 -0
  87. package/pennyfarthing-dist/personas/themes/firefly.yaml +12 -12
  88. package/pennyfarthing-dist/pf/__pycache__/__init__.cpython-311.pyc +0 -0
  89. package/pennyfarthing-dist/pf/__pycache__/__init__.cpython-314.pyc +0 -0
  90. package/pennyfarthing-dist/pf/__pycache__/bellmode_hook.cpython-314.pyc +0 -0
  91. package/pennyfarthing-dist/pf/__pycache__/cli.cpython-311.pyc +0 -0
  92. package/pennyfarthing-dist/pf/__pycache__/cli.cpython-314.pyc +0 -0
  93. package/pennyfarthing-dist/pf/__pycache__/config.cpython-314.pyc +0 -0
  94. package/pennyfarthing-dist/pf/__pycache__/context.cpython-311.pyc +0 -0
  95. package/pennyfarthing-dist/pf/__pycache__/context.cpython-314.pyc +0 -0
  96. package/pennyfarthing-dist/pf/__pycache__/hooks.cpython-314.pyc +0 -0
  97. package/pennyfarthing-dist/pf/__pycache__/jira.cpython-314.pyc +0 -0
  98. package/pennyfarthing-dist/pf/__pycache__/jira_bidirectional_sync.cpython-314.pyc +0 -0
  99. package/pennyfarthing-dist/pf/__pycache__/jira_epic_creation.cpython-314.pyc +0 -0
  100. package/pennyfarthing-dist/pf/__pycache__/jira_sync.cpython-314.pyc +0 -0
  101. package/pennyfarthing-dist/pf/__pycache__/jira_sync_story.cpython-314.pyc +0 -0
  102. package/pennyfarthing-dist/pf/__pycache__/output.cpython-314.pyc +0 -0
  103. package/pennyfarthing-dist/pf/__pycache__/patch_mode.cpython-314.pyc +0 -0
  104. package/pennyfarthing-dist/pf/__pycache__/pretooluse_hook.cpython-314.pyc +0 -0
  105. package/pennyfarthing-dist/pf/__pycache__/schema_validation_hook.cpython-314.pyc +0 -0
  106. package/pennyfarthing-dist/pf/__pycache__/session_start_hook.cpython-314.pyc +0 -0
  107. package/pennyfarthing-dist/pf/__pycache__/sprint.cpython-314.pyc +0 -0
  108. package/pennyfarthing-dist/pf/__pycache__/workflow.cpython-311.pyc +0 -0
  109. package/pennyfarthing-dist/pf/__pycache__/workflow.cpython-314.pyc +0 -0
  110. package/pennyfarthing-dist/pf/bc/__pycache__/__init__.cpython-311.pyc +0 -0
  111. package/pennyfarthing-dist/pf/bc/__pycache__/__init__.cpython-314.pyc +0 -0
  112. package/pennyfarthing-dist/pf/bc/__pycache__/cli.cpython-311.pyc +0 -0
  113. package/pennyfarthing-dist/pf/bc/__pycache__/cli.cpython-314.pyc +0 -0
  114. package/pennyfarthing-dist/pf/bc/__pycache__/focus.cpython-311.pyc +0 -0
  115. package/pennyfarthing-dist/pf/bc/__pycache__/focus.cpython-314.pyc +0 -0
  116. package/pennyfarthing-dist/pf/bc/__pycache__/split.cpython-314.pyc +0 -0
  117. package/pennyfarthing-dist/pf/bc/cli.py +0 -1
  118. package/pennyfarthing-dist/pf/bc/focus.py +0 -1
  119. package/pennyfarthing-dist/pf/bikerack/__pycache__/__init__.cpython-311.pyc +0 -0
  120. package/pennyfarthing-dist/pf/bikerack/__pycache__/__init__.cpython-314.pyc +0 -0
  121. package/pennyfarthing-dist/pf/bikerack/__pycache__/__main__.cpython-314.pyc +0 -0
  122. package/pennyfarthing-dist/pf/bikerack/__pycache__/audit_log_panel.cpython-314.pyc +0 -0
  123. package/pennyfarthing-dist/pf/bikerack/__pycache__/background_panel.cpython-314.pyc +0 -0
  124. package/pennyfarthing-dist/pf/bikerack/__pycache__/base_panel.cpython-314.pyc +0 -0
  125. package/pennyfarthing-dist/pf/bikerack/__pycache__/changed_panel.cpython-314.pyc +0 -0
  126. package/pennyfarthing-dist/pf/bikerack/__pycache__/cli.cpython-311.pyc +0 -0
  127. package/pennyfarthing-dist/pf/bikerack/__pycache__/cli.cpython-314.pyc +0 -0
  128. package/pennyfarthing-dist/pf/bikerack/__pycache__/context_meter_footer.cpython-314.pyc +0 -0
  129. package/pennyfarthing-dist/pf/bikerack/__pycache__/debug_panel.cpython-314.pyc +0 -0
  130. package/pennyfarthing-dist/pf/bikerack/__pycache__/diffs_panel.cpython-314.pyc +0 -0
  131. package/pennyfarthing-dist/pf/bikerack/__pycache__/events.cpython-314.pyc +0 -0
  132. package/pennyfarthing-dist/pf/bikerack/__pycache__/git_panel.cpython-314.pyc +0 -0
  133. package/pennyfarthing-dist/pf/bikerack/__pycache__/launcher.cpython-311.pyc +0 -0
  134. package/pennyfarthing-dist/pf/bikerack/__pycache__/launcher.cpython-314.pyc +0 -0
  135. package/pennyfarthing-dist/pf/bikerack/__pycache__/portrait_resolver.cpython-314.pyc +0 -0
  136. package/pennyfarthing-dist/pf/bikerack/__pycache__/progress_panel.cpython-314.pyc +0 -0
  137. package/pennyfarthing-dist/pf/bikerack/__pycache__/sprint_panel.cpython-314.pyc +0 -0
  138. package/pennyfarthing-dist/pf/bikerack/__pycache__/story_detail_data.cpython-314.pyc +0 -0
  139. package/pennyfarthing-dist/pf/bikerack/__pycache__/story_detail_screen.cpython-314.pyc +0 -0
  140. package/pennyfarthing-dist/pf/bikerack/__pycache__/story_detail_widget.cpython-314.pyc +0 -0
  141. package/pennyfarthing-dist/pf/bikerack/__pycache__/tui.cpython-314.pyc +0 -0
  142. package/pennyfarthing-dist/pf/bikerack/__pycache__/ws_client.cpython-314.pyc +0 -0
  143. package/pennyfarthing-dist/pf/bikerack/context_meter_footer.py +115 -35
  144. package/pennyfarthing-dist/pf/bikerack/portrait_resolver.py +14 -2
  145. package/pennyfarthing-dist/pf/bikerack/progress_panel.py +54 -0
  146. package/pennyfarthing-dist/pf/bikerack/sprint_panel.py +58 -0
  147. package/pennyfarthing-dist/pf/bikerack/story_detail_screen.py +10 -105
  148. package/pennyfarthing-dist/pf/bikerack/story_detail_widget.py +167 -0
  149. package/pennyfarthing-dist/pf/bikerack/tui.py +140 -50
  150. package/pennyfarthing-dist/pf/bmad/__pycache__/__init__.cpython-314.pyc +0 -0
  151. package/pennyfarthing-dist/pf/bmad/__pycache__/cli.cpython-314.pyc +0 -0
  152. package/pennyfarthing-dist/pf/bmad/cli.py +0 -1
  153. package/pennyfarthing-dist/pf/bmad/importer.py +0 -1
  154. package/pennyfarthing-dist/pf/bmad/parser.py +0 -1
  155. package/pennyfarthing-dist/pf/bmad/sync.py +1 -3
  156. package/pennyfarthing-dist/pf/bmad/test_parser.py +0 -4
  157. package/pennyfarthing-dist/pf/bmad/test_sync.py +0 -3
  158. package/pennyfarthing-dist/pf/brownfield/__pycache__/__init__.cpython-314.pyc +0 -0
  159. package/pennyfarthing-dist/pf/brownfield/__pycache__/__main__.cpython-314.pyc +0 -0
  160. package/pennyfarthing-dist/pf/brownfield/__pycache__/cli.cpython-314.pyc +0 -0
  161. package/pennyfarthing-dist/pf/brownfield/__pycache__/discover.cpython-314.pyc +0 -0
  162. package/pennyfarthing-dist/pf/brownfield/cli.py +1 -1
  163. package/pennyfarthing-dist/pf/cli.py +145 -111
  164. package/pennyfarthing-dist/pf/codemarkers/__pycache__/__init__.cpython-314.pyc +0 -0
  165. package/pennyfarthing-dist/pf/codemarkers/__pycache__/__main__.cpython-314.pyc +0 -0
  166. package/pennyfarthing-dist/pf/codemarkers/__pycache__/analyze.cpython-314.pyc +0 -0
  167. package/pennyfarthing-dist/pf/codemarkers/__pycache__/cli.cpython-314.pyc +0 -0
  168. package/pennyfarthing-dist/pf/codemarkers/__pycache__/formatters.cpython-314.pyc +0 -0
  169. package/pennyfarthing-dist/pf/codemarkers/__pycache__/models.cpython-314.pyc +0 -0
  170. package/pennyfarthing-dist/pf/common/__init__.py +2 -0
  171. package/pennyfarthing-dist/pf/common/__pycache__/__init__.cpython-311.pyc +0 -0
  172. package/pennyfarthing-dist/pf/common/__pycache__/__init__.cpython-314.pyc +0 -0
  173. package/pennyfarthing-dist/pf/common/__pycache__/config.cpython-311.pyc +0 -0
  174. package/pennyfarthing-dist/pf/common/__pycache__/config.cpython-314.pyc +0 -0
  175. package/pennyfarthing-dist/pf/common/__pycache__/output.cpython-311.pyc +0 -0
  176. package/pennyfarthing-dist/pf/common/__pycache__/output.cpython-314.pyc +0 -0
  177. package/pennyfarthing-dist/pf/common/__pycache__/pr_config.cpython-314.pyc +0 -0
  178. package/pennyfarthing-dist/pf/common/__pycache__/themes.cpython-314.pyc +0 -0
  179. package/pennyfarthing-dist/pf/common/config.py +43 -0
  180. package/pennyfarthing-dist/pf/common/themes.py +7 -5
  181. package/pennyfarthing-dist/pf/complexity/__pycache__/__init__.cpython-314.pyc +0 -0
  182. package/pennyfarthing-dist/pf/complexity/__pycache__/__main__.cpython-314.pyc +0 -0
  183. package/pennyfarthing-dist/pf/complexity/__pycache__/analyze.cpython-314.pyc +0 -0
  184. package/pennyfarthing-dist/pf/complexity/__pycache__/cli.cpython-314.pyc +0 -0
  185. package/pennyfarthing-dist/pf/complexity/__pycache__/formatters.cpython-314.pyc +0 -0
  186. package/pennyfarthing-dist/pf/complexity/__pycache__/models.cpython-314.pyc +0 -0
  187. package/pennyfarthing-dist/pf/consultation/__pycache__/__init__.cpython-311.pyc +0 -0
  188. package/pennyfarthing-dist/pf/consultation/__pycache__/__init__.cpython-314.pyc +0 -0
  189. package/pennyfarthing-dist/pf/consultation/__pycache__/cli.cpython-311.pyc +0 -0
  190. package/pennyfarthing-dist/pf/consultation/__pycache__/cli.cpython-314.pyc +0 -0
  191. package/pennyfarthing-dist/pf/consultation/__pycache__/dialogue_manager.cpython-314.pyc +0 -0
  192. package/pennyfarthing-dist/pf/context.py +1 -1
  193. package/pennyfarthing-dist/pf/deadcode/__pycache__/__init__.cpython-311.pyc +0 -0
  194. package/pennyfarthing-dist/pf/deadcode/__pycache__/__init__.cpython-314.pyc +0 -0
  195. package/pennyfarthing-dist/pf/deadcode/__pycache__/__main__.cpython-314.pyc +0 -0
  196. package/pennyfarthing-dist/pf/deadcode/__pycache__/analyze.cpython-314.pyc +0 -0
  197. package/pennyfarthing-dist/pf/deadcode/__pycache__/cli.cpython-311.pyc +0 -0
  198. package/pennyfarthing-dist/pf/deadcode/__pycache__/cli.cpython-314.pyc +0 -0
  199. package/pennyfarthing-dist/pf/deadcode/__pycache__/formatters.cpython-314.pyc +0 -0
  200. package/pennyfarthing-dist/pf/deadcode/__pycache__/models.cpython-314.pyc +0 -0
  201. package/pennyfarthing-dist/pf/dependencies/__pycache__/__init__.cpython-314.pyc +0 -0
  202. package/pennyfarthing-dist/pf/dependencies/__pycache__/__main__.cpython-314.pyc +0 -0
  203. package/pennyfarthing-dist/pf/dependencies/__pycache__/analyze.cpython-314.pyc +0 -0
  204. package/pennyfarthing-dist/pf/dependencies/__pycache__/cli.cpython-314.pyc +0 -0
  205. package/pennyfarthing-dist/pf/dependencies/__pycache__/formatters.cpython-314.pyc +0 -0
  206. package/pennyfarthing-dist/pf/dependencies/__pycache__/models.cpython-314.pyc +0 -0
  207. package/pennyfarthing-dist/pf/epic/__pycache__/__init__.cpython-311.pyc +0 -0
  208. package/pennyfarthing-dist/pf/epic/__pycache__/__init__.cpython-314.pyc +0 -0
  209. package/pennyfarthing-dist/pf/epic/__pycache__/cli.cpython-311.pyc +0 -0
  210. package/pennyfarthing-dist/pf/epic/__pycache__/cli.cpython-314.pyc +0 -0
  211. package/pennyfarthing-dist/pf/git/__pycache__/__init__.cpython-314.pyc +0 -0
  212. package/pennyfarthing-dist/pf/git/__pycache__/create_branches.cpython-314.pyc +0 -0
  213. package/pennyfarthing-dist/pf/git/__pycache__/status_all.cpython-314.pyc +0 -0
  214. package/pennyfarthing-dist/pf/git/hooks_installer.py +7 -6
  215. package/pennyfarthing-dist/pf/git_group/__pycache__/__init__.cpython-311.pyc +0 -0
  216. package/pennyfarthing-dist/pf/git_group/__pycache__/__init__.cpython-314.pyc +0 -0
  217. package/pennyfarthing-dist/pf/git_group/__pycache__/cli.cpython-311.pyc +0 -0
  218. package/pennyfarthing-dist/pf/git_group/__pycache__/cli.cpython-314.pyc +0 -0
  219. package/pennyfarthing-dist/pf/handoff/__pycache__/__init__.cpython-311.pyc +0 -0
  220. package/pennyfarthing-dist/pf/handoff/__pycache__/__init__.cpython-314.pyc +0 -0
  221. package/pennyfarthing-dist/pf/handoff/__pycache__/cli.cpython-311.pyc +0 -0
  222. package/pennyfarthing-dist/pf/handoff/__pycache__/cli.cpython-314.pyc +0 -0
  223. package/pennyfarthing-dist/pf/handoff/__pycache__/complete_phase.cpython-314.pyc +0 -0
  224. package/pennyfarthing-dist/pf/handoff/__pycache__/gate_file.cpython-314.pyc +0 -0
  225. package/pennyfarthing-dist/pf/handoff/__pycache__/gate_runner.cpython-314.pyc +0 -0
  226. package/pennyfarthing-dist/pf/handoff/__pycache__/marker.cpython-314.pyc +0 -0
  227. package/pennyfarthing-dist/pf/handoff/__pycache__/phase_check.cpython-314.pyc +0 -0
  228. package/pennyfarthing-dist/pf/handoff/__pycache__/resolve_gate.cpython-314.pyc +0 -0
  229. package/pennyfarthing-dist/pf/handoff/gate_file.py +5 -1
  230. package/pennyfarthing-dist/pf/healthscore/__pycache__/__init__.cpython-311.pyc +0 -0
  231. package/pennyfarthing-dist/pf/healthscore/__pycache__/__init__.cpython-314.pyc +0 -0
  232. package/pennyfarthing-dist/pf/healthscore/__pycache__/__main__.cpython-314.pyc +0 -0
  233. package/pennyfarthing-dist/pf/healthscore/__pycache__/analyze.cpython-311.pyc +0 -0
  234. package/pennyfarthing-dist/pf/healthscore/__pycache__/analyze.cpython-314.pyc +0 -0
  235. package/pennyfarthing-dist/pf/healthscore/__pycache__/cli.cpython-311.pyc +0 -0
  236. package/pennyfarthing-dist/pf/healthscore/__pycache__/cli.cpython-314.pyc +0 -0
  237. package/pennyfarthing-dist/pf/healthscore/__pycache__/formatters.cpython-314.pyc +0 -0
  238. package/pennyfarthing-dist/pf/healthscore/__pycache__/models.cpython-311.pyc +0 -0
  239. package/pennyfarthing-dist/pf/healthscore/__pycache__/models.cpython-314.pyc +0 -0
  240. package/pennyfarthing-dist/pf/hooks/__pycache__/__init__.cpython-311.pyc +0 -0
  241. package/pennyfarthing-dist/pf/hooks/__pycache__/__init__.cpython-314.pyc +0 -0
  242. package/pennyfarthing-dist/pf/hooks/__pycache__/bell_mode.cpython-311.pyc +0 -0
  243. package/pennyfarthing-dist/pf/hooks/__pycache__/bell_mode.cpython-314.pyc +0 -0
  244. package/pennyfarthing-dist/pf/hooks/__pycache__/cli.cpython-311.pyc +0 -0
  245. package/pennyfarthing-dist/pf/hooks/__pycache__/cli.cpython-314.pyc +0 -0
  246. package/pennyfarthing-dist/pf/hooks/__pycache__/context_breaker.cpython-311.pyc +0 -0
  247. package/pennyfarthing-dist/pf/hooks/__pycache__/context_breaker.cpython-314.pyc +0 -0
  248. package/pennyfarthing-dist/pf/hooks/__pycache__/context_warning.cpython-311.pyc +0 -0
  249. package/pennyfarthing-dist/pf/hooks/__pycache__/context_warning.cpython-314.pyc +0 -0
  250. package/pennyfarthing-dist/pf/hooks/__pycache__/cyclist_pretooluse.cpython-311.pyc +0 -0
  251. package/pennyfarthing-dist/pf/hooks/__pycache__/cyclist_pretooluse.cpython-314.pyc +0 -0
  252. package/pennyfarthing-dist/pf/hooks/__pycache__/pre_edit_check.cpython-311.pyc +0 -0
  253. package/pennyfarthing-dist/pf/hooks/__pycache__/pre_edit_check.cpython-314.pyc +0 -0
  254. package/pennyfarthing-dist/pf/hooks/__pycache__/reflector_check.cpython-311.pyc +0 -0
  255. package/pennyfarthing-dist/pf/hooks/__pycache__/reflector_check.cpython-314.pyc +0 -0
  256. package/pennyfarthing-dist/pf/hooks/__pycache__/schema_validation.cpython-311.pyc +0 -0
  257. package/pennyfarthing-dist/pf/hooks/__pycache__/schema_validation.cpython-314.pyc +0 -0
  258. package/pennyfarthing-dist/pf/hooks/__pycache__/session_start.cpython-311.pyc +0 -0
  259. package/pennyfarthing-dist/pf/hooks/__pycache__/session_start.cpython-314.pyc +0 -0
  260. package/pennyfarthing-dist/pf/hooks/__pycache__/session_stop.cpython-314.pyc +0 -0
  261. package/pennyfarthing-dist/pf/hooks/__pycache__/sprint_yaml_validation.cpython-311.pyc +0 -0
  262. package/pennyfarthing-dist/pf/hooks/__pycache__/sprint_yaml_validation.cpython-314.pyc +0 -0
  263. package/pennyfarthing-dist/pf/hooks/__pycache__/statusline.cpython-311.pyc +0 -0
  264. package/pennyfarthing-dist/pf/hooks/__pycache__/statusline.cpython-314.pyc +0 -0
  265. package/pennyfarthing-dist/pf/hooks/statusline.py +11 -0
  266. package/pennyfarthing-dist/pf/hotspots/__pycache__/__init__.cpython-311.pyc +0 -0
  267. package/pennyfarthing-dist/pf/hotspots/__pycache__/__init__.cpython-314.pyc +0 -0
  268. package/pennyfarthing-dist/pf/hotspots/__pycache__/__main__.cpython-314.pyc +0 -0
  269. package/pennyfarthing-dist/pf/hotspots/__pycache__/analyze.cpython-311.pyc +0 -0
  270. package/pennyfarthing-dist/pf/hotspots/__pycache__/analyze.cpython-314.pyc +0 -0
  271. package/pennyfarthing-dist/pf/hotspots/__pycache__/cli.cpython-311.pyc +0 -0
  272. package/pennyfarthing-dist/pf/hotspots/__pycache__/cli.cpython-314.pyc +0 -0
  273. package/pennyfarthing-dist/pf/hotspots/__pycache__/formatters.cpython-314.pyc +0 -0
  274. package/pennyfarthing-dist/pf/hotspots/__pycache__/models.cpython-311.pyc +0 -0
  275. package/pennyfarthing-dist/pf/hotspots/__pycache__/models.cpython-314.pyc +0 -0
  276. package/pennyfarthing-dist/pf/jira/__init__.py +0 -6
  277. package/pennyfarthing-dist/pf/jira/__pycache__/__init__.cpython-311.pyc +0 -0
  278. package/pennyfarthing-dist/pf/jira/__pycache__/__init__.cpython-314.pyc +0 -0
  279. package/pennyfarthing-dist/pf/jira/__pycache__/__main__.cpython-314.pyc +0 -0
  280. package/pennyfarthing-dist/pf/jira/__pycache__/bidirectional.cpython-311.pyc +0 -0
  281. package/pennyfarthing-dist/pf/jira/__pycache__/bidirectional.cpython-314.pyc +0 -0
  282. package/pennyfarthing-dist/pf/jira/__pycache__/claim.cpython-311.pyc +0 -0
  283. package/pennyfarthing-dist/pf/jira/__pycache__/claim.cpython-314.pyc +0 -0
  284. package/pennyfarthing-dist/pf/jira/__pycache__/cli.cpython-311.pyc +0 -0
  285. package/pennyfarthing-dist/pf/jira/__pycache__/cli.cpython-314.pyc +0 -0
  286. package/pennyfarthing-dist/pf/jira/__pycache__/client.cpython-311.pyc +0 -0
  287. package/pennyfarthing-dist/pf/jira/__pycache__/client.cpython-314.pyc +0 -0
  288. package/pennyfarthing-dist/pf/jira/__pycache__/compat.cpython-314.pyc +0 -0
  289. package/pennyfarthing-dist/pf/jira/__pycache__/create.cpython-311.pyc +0 -0
  290. package/pennyfarthing-dist/pf/jira/__pycache__/create.cpython-314.pyc +0 -0
  291. package/pennyfarthing-dist/pf/jira/__pycache__/epic.cpython-311.pyc +0 -0
  292. package/pennyfarthing-dist/pf/jira/__pycache__/epic.cpython-314.pyc +0 -0
  293. package/pennyfarthing-dist/pf/jira/__pycache__/mappings.cpython-314.pyc +0 -0
  294. package/pennyfarthing-dist/pf/jira/__pycache__/models.cpython-314.pyc +0 -0
  295. package/pennyfarthing-dist/pf/jira/__pycache__/operations.cpython-311.pyc +0 -0
  296. package/pennyfarthing-dist/pf/jira/__pycache__/operations.cpython-314.pyc +0 -0
  297. package/pennyfarthing-dist/pf/jira/__pycache__/reconcile.cpython-311.pyc +0 -0
  298. package/pennyfarthing-dist/pf/jira/__pycache__/reconcile.cpython-314.pyc +0 -0
  299. package/pennyfarthing-dist/pf/jira/__pycache__/story.cpython-311.pyc +0 -0
  300. package/pennyfarthing-dist/pf/jira/__pycache__/story.cpython-314.pyc +0 -0
  301. package/pennyfarthing-dist/pf/jira/__pycache__/sync.cpython-311.pyc +0 -0
  302. package/pennyfarthing-dist/pf/jira/__pycache__/sync.cpython-314.pyc +0 -0
  303. package/pennyfarthing-dist/pf/jira/claim.py +14 -44
  304. package/pennyfarthing-dist/pf/jira/cli.py +124 -14
  305. package/pennyfarthing-dist/pf/jira/client.py +51 -87
  306. package/pennyfarthing-dist/pf/jira/reconcile.py +1 -1
  307. package/pennyfarthing-dist/pf/jira/story.py +6 -4
  308. package/pennyfarthing-dist/pf/jira/sync.py +2 -2
  309. package/pennyfarthing-dist/pf/launch/__pycache__/__init__.cpython-311.pyc +0 -0
  310. package/pennyfarthing-dist/pf/launch/__pycache__/__init__.cpython-314.pyc +0 -0
  311. package/pennyfarthing-dist/pf/launch/__pycache__/cli.cpython-311.pyc +0 -0
  312. package/pennyfarthing-dist/pf/launch/__pycache__/cli.cpython-314.pyc +0 -0
  313. package/pennyfarthing-dist/pf/migration/__init__.py +1 -1
  314. package/pennyfarthing-dist/pf/migration/__pycache__/__init__.cpython-314.pyc +0 -0
  315. package/pennyfarthing-dist/pf/migration/__pycache__/__main__.cpython-314.pyc +0 -0
  316. package/pennyfarthing-dist/pf/migration/__pycache__/cli.cpython-314.pyc +0 -0
  317. package/pennyfarthing-dist/pf/migration/__pycache__/session.cpython-314.pyc +0 -0
  318. package/pennyfarthing-dist/pf/migration/__pycache__/skill.cpython-314.pyc +0 -0
  319. package/pennyfarthing-dist/pf/migration/__pycache__/step.cpython-314.pyc +0 -0
  320. package/pennyfarthing-dist/pf/migration/__pycache__/validate.cpython-314.pyc +0 -0
  321. package/pennyfarthing-dist/pf/migration/cli.py +0 -1
  322. package/pennyfarthing-dist/pf/package/__init__.py +0 -0
  323. package/pennyfarthing-dist/pf/package/__pycache__/__init__.cpython-314.pyc +0 -0
  324. package/pennyfarthing-dist/pf/package/__pycache__/cli.cpython-314.pyc +0 -0
  325. package/pennyfarthing-dist/pf/package/__pycache__/discovery.cpython-314.pyc +0 -0
  326. package/pennyfarthing-dist/pf/package/__pycache__/portraits.cpython-314.pyc +0 -0
  327. package/pennyfarthing-dist/pf/package/cli.py +186 -0
  328. package/pennyfarthing-dist/pf/package/discovery.py +130 -0
  329. package/pennyfarthing-dist/pf/package/portraits.py +243 -0
  330. package/pennyfarthing-dist/pf/preflight/__pycache__/__init__.cpython-314.pyc +0 -0
  331. package/pennyfarthing-dist/pf/preflight/__pycache__/__main__.cpython-314.pyc +0 -0
  332. package/pennyfarthing-dist/pf/preflight/__pycache__/cli.cpython-314.pyc +0 -0
  333. package/pennyfarthing-dist/pf/preflight/__pycache__/finish.cpython-314.pyc +0 -0
  334. package/pennyfarthing-dist/pf/prime/__pycache__/__init__.cpython-314.pyc +0 -0
  335. package/pennyfarthing-dist/pf/prime/__pycache__/__main__.cpython-314.pyc +0 -0
  336. package/pennyfarthing-dist/pf/prime/__pycache__/cli.cpython-314.pyc +0 -0
  337. package/pennyfarthing-dist/pf/prime/__pycache__/loader.cpython-314.pyc +0 -0
  338. package/pennyfarthing-dist/pf/prime/__pycache__/models.cpython-314.pyc +0 -0
  339. package/pennyfarthing-dist/pf/prime/__pycache__/persona.cpython-314.pyc +0 -0
  340. package/pennyfarthing-dist/pf/prime/__pycache__/session.cpython-314.pyc +0 -0
  341. package/pennyfarthing-dist/pf/prime/__pycache__/tiers.cpython-314.pyc +0 -0
  342. package/pennyfarthing-dist/pf/prime/__pycache__/version_sentinel.cpython-314.pyc +0 -0
  343. package/pennyfarthing-dist/pf/prime/__pycache__/workflow.cpython-314.pyc +0 -0
  344. package/pennyfarthing-dist/pf/prime/loader.py +21 -7
  345. package/pennyfarthing-dist/pf/prime/workflow.py +11 -3
  346. package/pennyfarthing-dist/pf/release/__init__.py +0 -0
  347. package/pennyfarthing-dist/pf/release/__pycache__/__init__.cpython-314.pyc +0 -0
  348. package/pennyfarthing-dist/pf/release/__pycache__/cli.cpython-314.pyc +0 -0
  349. package/pennyfarthing-dist/pf/release/__pycache__/deprecate.cpython-314.pyc +0 -0
  350. package/pennyfarthing-dist/pf/release/__pycache__/dry_run.cpython-314.pyc +0 -0
  351. package/pennyfarthing-dist/pf/release/cli.py +112 -0
  352. package/pennyfarthing-dist/pf/release/deprecate.py +187 -0
  353. package/pennyfarthing-dist/pf/release/dry_run.py +187 -0
  354. package/pennyfarthing-dist/pf/session/__pycache__/__init__.cpython-311.pyc +0 -0
  355. package/pennyfarthing-dist/pf/session/__pycache__/__init__.cpython-314.pyc +0 -0
  356. package/pennyfarthing-dist/pf/session/__pycache__/cli.cpython-311.pyc +0 -0
  357. package/pennyfarthing-dist/pf/session/__pycache__/cli.cpython-314.pyc +0 -0
  358. package/pennyfarthing-dist/pf/settings/__pycache__/__init__.cpython-314.pyc +0 -0
  359. package/pennyfarthing-dist/pf/settings/__pycache__/cli.cpython-314.pyc +0 -0
  360. package/pennyfarthing-dist/pf/settings/__pycache__/settings.cpython-314.pyc +0 -0
  361. package/pennyfarthing-dist/pf/sprint/__pycache__/__init__.cpython-311.pyc +0 -0
  362. package/pennyfarthing-dist/pf/sprint/__pycache__/__init__.cpython-314.pyc +0 -0
  363. package/pennyfarthing-dist/pf/sprint/__pycache__/__main__.cpython-314.pyc +0 -0
  364. package/pennyfarthing-dist/pf/sprint/__pycache__/archive.cpython-311.pyc +0 -0
  365. package/pennyfarthing-dist/pf/sprint/__pycache__/archive.cpython-314.pyc +0 -0
  366. package/pennyfarthing-dist/pf/sprint/__pycache__/archive_epic.cpython-314.pyc +0 -0
  367. package/pennyfarthing-dist/pf/sprint/__pycache__/cli.cpython-311.pyc +0 -0
  368. package/pennyfarthing-dist/pf/sprint/__pycache__/cli.cpython-314.pyc +0 -0
  369. package/pennyfarthing-dist/pf/sprint/__pycache__/epic_add.cpython-311.pyc +0 -0
  370. package/pennyfarthing-dist/pf/sprint/__pycache__/epic_add.cpython-314.pyc +0 -0
  371. package/pennyfarthing-dist/pf/sprint/__pycache__/epic_update.cpython-311.pyc +0 -0
  372. package/pennyfarthing-dist/pf/sprint/__pycache__/epic_update.cpython-314.pyc +0 -0
  373. package/pennyfarthing-dist/pf/sprint/__pycache__/import_epic.cpython-314.pyc +0 -0
  374. package/pennyfarthing-dist/pf/sprint/__pycache__/loader.cpython-311.pyc +0 -0
  375. package/pennyfarthing-dist/pf/sprint/__pycache__/loader.cpython-314.pyc +0 -0
  376. package/pennyfarthing-dist/pf/sprint/__pycache__/shard_merge.cpython-314.pyc +0 -0
  377. package/pennyfarthing-dist/pf/sprint/__pycache__/status.cpython-311.pyc +0 -0
  378. package/pennyfarthing-dist/pf/sprint/__pycache__/status.cpython-314.pyc +0 -0
  379. package/pennyfarthing-dist/pf/sprint/__pycache__/story_add.cpython-311.pyc +0 -0
  380. package/pennyfarthing-dist/pf/sprint/__pycache__/story_add.cpython-314.pyc +0 -0
  381. package/pennyfarthing-dist/pf/sprint/__pycache__/story_finish.cpython-314.pyc +0 -0
  382. package/pennyfarthing-dist/pf/sprint/__pycache__/story_update.cpython-311.pyc +0 -0
  383. package/pennyfarthing-dist/pf/sprint/__pycache__/story_update.cpython-314.pyc +0 -0
  384. package/pennyfarthing-dist/pf/sprint/__pycache__/validate_cmd.cpython-311.pyc +0 -0
  385. package/pennyfarthing-dist/pf/sprint/__pycache__/validate_cmd.cpython-314.pyc +0 -0
  386. package/pennyfarthing-dist/pf/sprint/__pycache__/validator.cpython-311.pyc +0 -0
  387. package/pennyfarthing-dist/pf/sprint/__pycache__/validator.cpython-314.pyc +0 -0
  388. package/pennyfarthing-dist/pf/sprint/__pycache__/work.cpython-311.pyc +0 -0
  389. package/pennyfarthing-dist/pf/sprint/__pycache__/work.cpython-314.pyc +0 -0
  390. package/pennyfarthing-dist/pf/sprint/__pycache__/yaml_io.cpython-311.pyc +0 -0
  391. package/pennyfarthing-dist/pf/sprint/__pycache__/yaml_io.cpython-314.pyc +0 -0
  392. package/pennyfarthing-dist/pf/sprint/cli.py +1 -1
  393. package/pennyfarthing-dist/pf/sprint/loader.py +6 -74
  394. package/pennyfarthing-dist/pf/sprint/shard_merge.py +126 -0
  395. package/pennyfarthing-dist/pf/sprint/story_finish.py +4 -2
  396. package/pennyfarthing-dist/pf/sprint/yaml_io.py +8 -53
  397. package/pennyfarthing-dist/pf/story/__pycache__/__init__.cpython-314.pyc +0 -0
  398. package/pennyfarthing-dist/pf/story/__pycache__/__main__.cpython-314.pyc +0 -0
  399. package/pennyfarthing-dist/pf/story/__pycache__/cli.cpython-314.pyc +0 -0
  400. package/pennyfarthing-dist/pf/story/__pycache__/create.cpython-314.pyc +0 -0
  401. package/pennyfarthing-dist/pf/story/__pycache__/size.cpython-314.pyc +0 -0
  402. package/pennyfarthing-dist/pf/story/__pycache__/template.cpython-314.pyc +0 -0
  403. package/pennyfarthing-dist/pf/tests/__pycache__/__init__.cpython-314.pyc +0 -0
  404. package/pennyfarthing-dist/pf/tests/__pycache__/conftest.cpython-314-pytest-9.0.2.pyc +0 -0
  405. package/pennyfarthing-dist/pf/tests/__pycache__/test_108_1_gate_migration.cpython-314-pytest-9.0.2.pyc +0 -0
  406. package/pennyfarthing-dist/pf/tests/__pycache__/test_archive_epic.cpython-314-pytest-9.0.2.pyc +0 -0
  407. package/pennyfarthing-dist/pf/tests/__pycache__/test_bc.cpython-314-pytest-9.0.2.pyc +0 -0
  408. package/pennyfarthing-dist/pf/tests/__pycache__/test_bikerack.cpython-314-pytest-9.0.2.pyc +0 -0
  409. package/pennyfarthing-dist/pf/tests/__pycache__/test_brownfield.cpython-314-pytest-9.0.2.pyc +0 -0
  410. package/pennyfarthing-dist/pf/tests/__pycache__/test_cli_modules.cpython-314-pytest-9.0.2.pyc +0 -0
  411. package/pennyfarthing-dist/pf/tests/__pycache__/test_cli_normalization.cpython-314-pytest-9.0.2.pyc +0 -0
  412. package/pennyfarthing-dist/pf/tests/__pycache__/test_codemarkers.cpython-314-pytest-9.0.2.pyc +0 -0
  413. package/pennyfarthing-dist/pf/tests/__pycache__/test_common.cpython-314-pytest-9.0.2.pyc +0 -0
  414. package/pennyfarthing-dist/pf/tests/__pycache__/test_confidence_sm_evaluation.cpython-314-pytest-9.0.2.pyc +0 -0
  415. package/pennyfarthing-dist/pf/tests/__pycache__/test_confidence_sm_gate.cpython-314-pytest-9.0.2.pyc +0 -0
  416. package/pennyfarthing-dist/pf/tests/__pycache__/test_dialogue_manager.cpython-314-pytest-9.0.2.pyc +0 -0
  417. package/pennyfarthing-dist/pf/tests/__pycache__/test_epic_shard_validation.cpython-314-pytest-9.0.2.pyc +0 -0
  418. package/pennyfarthing-dist/pf/tests/__pycache__/test_git_utils.cpython-314-pytest-9.0.2.pyc +0 -0
  419. package/pennyfarthing-dist/pf/tests/__pycache__/test_handoff_cli.cpython-314-pytest-9.0.2.pyc +0 -0
  420. package/pennyfarthing-dist/pf/tests/__pycache__/test_handoff_e2e.cpython-314-pytest-9.0.2.pyc +0 -0
  421. package/pennyfarthing-dist/pf/tests/__pycache__/test_healthscore.cpython-314-pytest-9.0.2.pyc +0 -0
  422. package/pennyfarthing-dist/pf/tests/__pycache__/test_jira_package.cpython-314-pytest-9.0.2.pyc +0 -0
  423. package/pennyfarthing-dist/pf/tests/__pycache__/test_package_structure.cpython-314-pytest-9.0.2.pyc +0 -0
  424. package/pennyfarthing-dist/pf/tests/__pycache__/test_patch_mode.cpython-314-pytest-9.0.2.pyc +0 -0
  425. package/pennyfarthing-dist/pf/tests/__pycache__/test_prime.cpython-314-pytest-9.0.2.pyc +0 -0
  426. package/pennyfarthing-dist/pf/tests/__pycache__/test_sprint_package.cpython-314-pytest-9.0.2.pyc +0 -0
  427. package/pennyfarthing-dist/pf/tests/__pycache__/test_sprint_panel.cpython-314-pytest-9.0.2.pyc +0 -0
  428. package/pennyfarthing-dist/pf/tests/__pycache__/test_sprint_validator.cpython-314-pytest-9.0.2.pyc +0 -0
  429. package/pennyfarthing-dist/pf/tests/__pycache__/test_story_add.cpython-314-pytest-9.0.2.pyc +0 -0
  430. package/pennyfarthing-dist/pf/tests/__pycache__/test_story_package.cpython-314-pytest-9.0.2.pyc +0 -0
  431. package/pennyfarthing-dist/pf/tests/__pycache__/test_story_update.cpython-314-pytest-9.0.2.pyc +0 -0
  432. package/pennyfarthing-dist/pf/tests/__pycache__/test_tiers.cpython-314-pytest-9.0.2.pyc +0 -0
  433. package/pennyfarthing-dist/pf/tests/__pycache__/test_token_counting.cpython-314-pytest-9.0.2.pyc +0 -0
  434. package/pennyfarthing-dist/pf/tests/__pycache__/test_topology_loader.cpython-314-pytest-9.0.2.pyc +0 -0
  435. package/pennyfarthing-dist/pf/tests/__pycache__/test_tui_focus.cpython-314-pytest-9.0.2.pyc +0 -0
  436. package/pennyfarthing-dist/pf/tests/__pycache__/test_tui_panel_persistence.cpython-314-pytest-9.0.2.pyc +0 -0
  437. package/pennyfarthing-dist/pf/tests/__pycache__/test_validate_cmd.cpython-314-pytest-9.0.2.pyc +0 -0
  438. package/pennyfarthing-dist/pf/tests/__pycache__/test_version_sentinel.cpython-314-pytest-9.0.2.pyc +0 -0
  439. package/pennyfarthing-dist/pf/tests/__pycache__/test_workflow_check.cpython-314-pytest-9.0.2.pyc +0 -0
  440. package/pennyfarthing-dist/pf/tests/__pycache__/test_workflow_cli.cpython-314-pytest-9.0.2.pyc +0 -0
  441. package/pennyfarthing-dist/pf/tests/__pycache__/test_yaml_io.cpython-314-pytest-9.0.2.pyc +0 -0
  442. package/pennyfarthing-dist/pf/tests/test_cli_modules.py +32 -137
  443. package/pennyfarthing-dist/pf/tests/test_codemarkers.py +0 -15
  444. package/pennyfarthing-dist/pf/tests/test_dist_root.py +720 -0
  445. package/pennyfarthing-dist/pf/tests/test_package_structure.py +24 -70
  446. package/pennyfarthing-dist/pf/theme/__pycache__/__init__.cpython-311.pyc +0 -0
  447. package/pennyfarthing-dist/pf/theme/__pycache__/__init__.cpython-314.pyc +0 -0
  448. package/pennyfarthing-dist/pf/theme/__pycache__/cli.cpython-311.pyc +0 -0
  449. package/pennyfarthing-dist/pf/theme/__pycache__/cli.cpython-314.pyc +0 -0
  450. package/pennyfarthing-dist/pf/validate/__pycache__/__init__.cpython-311.pyc +0 -0
  451. package/pennyfarthing-dist/pf/validate/__pycache__/__init__.cpython-314.pyc +0 -0
  452. package/pennyfarthing-dist/pf/validate/__pycache__/cli.cpython-311.pyc +0 -0
  453. package/pennyfarthing-dist/pf/validate/__pycache__/cli.cpython-314.pyc +0 -0
  454. package/pennyfarthing-dist/pf/validate/adapters/__pycache__/__init__.cpython-314.pyc +0 -0
  455. package/pennyfarthing-dist/pf/validate/adapters/__pycache__/agent.cpython-314.pyc +0 -0
  456. package/pennyfarthing-dist/pf/validate/adapters/__pycache__/schema.cpython-314.pyc +0 -0
  457. package/pennyfarthing-dist/pf/validate/adapters/__pycache__/skill_command.cpython-314.pyc +0 -0
  458. package/pennyfarthing-dist/pf/validate/adapters/__pycache__/sprint.cpython-314.pyc +0 -0
  459. package/pennyfarthing-dist/pf/validate/adapters/__pycache__/tandem_awareness.cpython-314.pyc +0 -0
  460. package/pennyfarthing-dist/pf/validate/adapters/__pycache__/workflow.cpython-314.pyc +0 -0
  461. package/pennyfarthing-dist/pf/validate/adapters/agent.py +11 -1
  462. package/pennyfarthing-dist/pf/validate/adapters/skill_command.py +15 -4
  463. package/pennyfarthing-dist/pf/validate/adapters/tandem_awareness.py +7 -1
  464. package/pennyfarthing-dist/pf/validate/adapters/team_mode.py +8 -2
  465. package/pennyfarthing-dist/pf/validate/adapters/workflow.py +12 -2
  466. package/pennyfarthing-dist/pf/workflow/__pycache__/__init__.cpython-311.pyc +0 -0
  467. package/pennyfarthing-dist/pf/workflow/__pycache__/__init__.cpython-314.pyc +0 -0
  468. package/pennyfarthing-dist/pf/workflow/__pycache__/cli.cpython-311.pyc +0 -0
  469. package/pennyfarthing-dist/pf/workflow/__pycache__/cli.cpython-314.pyc +0 -0
  470. package/pennyfarthing-dist/pf/workflow/__pycache__/helpers.cpython-314.pyc +0 -0
  471. package/pennyfarthing-dist/pf/workflow/__pycache__/scale.cpython-311.pyc +0 -0
  472. package/pennyfarthing-dist/pf/workflow/__pycache__/scale.cpython-314.pyc +0 -0
  473. package/pennyfarthing-dist/pf/workflow/__pycache__/state.cpython-311.pyc +0 -0
  474. package/pennyfarthing-dist/pf/workflow/__pycache__/state.cpython-314.pyc +0 -0
  475. package/pennyfarthing-dist/pyproject.toml +1 -1
  476. package/pennyfarthing-dist/scripts/core/check-context.sh +2 -2
  477. package/pennyfarthing-dist/scripts/git/changelog-links.sh +216 -0
  478. package/pennyfarthing-dist/scripts/hooks/__pycache__/question_reflector_check.cpython-314.pyc +0 -0
  479. package/pennyfarthing-dist/scripts/lib/README.md +0 -1
  480. package/pennyfarthing-dist/scripts/portraits/generate-portraits.py +54 -0
  481. package/pennyfarthing-dist/scripts/portraits/generate-tandem-portraits.sh +7 -3
  482. package/pennyfarthing-dist/workflows/patch.yaml +5 -6
  483. package/pennyfarthing-dist/workflows/tdd-tandem.yaml +12 -0
  484. package/packages/core/dist/workflow/team-lifecycle.d.ts +0 -169
  485. package/packages/core/dist/workflow/team-lifecycle.d.ts.map +0 -1
  486. package/packages/core/dist/workflow/team-lifecycle.js +0 -217
  487. package/packages/core/dist/workflow/team-lifecycle.js.map +0 -1
  488. package/packages/core/dist/workflow/team-lifecycle.test.d.ts +0 -20
  489. package/packages/core/dist/workflow/team-lifecycle.test.d.ts.map +0 -1
  490. package/packages/core/dist/workflow/team-lifecycle.test.js +0 -966
  491. package/packages/core/dist/workflow/team-lifecycle.test.js.map +0 -1
  492. package/packages/core/src/public/components/FontPicker/FontPicker.css +0 -276
  493. package/packages/core/src/public/components/FontPicker/index.tsx +0 -430
  494. package/packages/core/src/public/components/ThemePalette/ThemePalette.css +0 -179
  495. package/packages/core/src/public/components/ThemePalette/index.tsx +0 -326
  496. package/packages/core/src/public/components/panels/BackgroundPanel.tsx +0 -115
  497. package/packages/core/src/public/components/ui/switch.tsx +0 -27
  498. package/packages/core/src/public/hooks/useBackgroundTasks.ts +0 -131
  499. package/pennyfarthing-dist/pf/bikerack/background_panel.py +0 -162
  500. package/pennyfarthing-dist/pf/bmad/__pycache__/parser.cpython-314.pyc +0 -0
  501. package/pennyfarthing-dist/pf/brownfield/__main__.py +0 -8
  502. package/pennyfarthing-dist/pf/config.py +0 -21
  503. package/pennyfarthing-dist/pf/hooks.py +0 -32
  504. package/pennyfarthing-dist/pf/jira_bidirectional_sync.py +0 -37
  505. package/pennyfarthing-dist/pf/jira_epic_creation.py +0 -30
  506. package/pennyfarthing-dist/pf/jira_sync.py +0 -36
  507. package/pennyfarthing-dist/pf/jira_sync_story.py +0 -30
  508. package/pennyfarthing-dist/pf/migration/__main__.py +0 -10
  509. package/pennyfarthing-dist/pf/output.py +0 -37
  510. package/pennyfarthing-dist/pf/theme/__main__.py +0 -6
  511. package/pennyfarthing-dist/scripts/lib/background-tasks.sh +0 -177
@@ -122,76 +122,6 @@ def is_jira_cli_available() -> bool:
122
122
  return shutil.which("jira") is not None
123
123
 
124
124
 
125
- def get_issue(issue_key: str) -> dict[str, Any] | None:
126
- """Fetch issue details from Jira.
127
-
128
- Args:
129
- issue_key: Jira issue key (e.g., "MSSCI-12398")
130
-
131
- Returns:
132
- Issue data as dict, or None if not found
133
- """
134
- if not is_jira_cli_available():
135
- return None
136
-
137
- result = subprocess.run(
138
- ["jira", "issue", "view", issue_key, "--raw"],
139
- capture_output=True,
140
- text=True,
141
- )
142
-
143
- if result.returncode != 0:
144
- return None
145
-
146
- try:
147
- return json.loads(result.stdout)
148
- except json.JSONDecodeError:
149
- return None
150
-
151
-
152
- def update_issue_status(issue_key: str, status: str) -> bool:
153
- """Transition an issue to a new status.
154
-
155
- Args:
156
- issue_key: Jira issue key
157
- status: Target status name
158
-
159
- Returns:
160
- True if successful, False otherwise
161
- """
162
- if not is_jira_cli_available():
163
- return False
164
-
165
- result = subprocess.run(
166
- ["jira", "issue", "move", issue_key, status],
167
- capture_output=True,
168
- text=True,
169
- )
170
-
171
- return result.returncode == 0
172
-
173
-
174
- def add_comment(issue_key: str, comment: str) -> bool:
175
- """Add a comment to an issue.
176
-
177
- Args:
178
- issue_key: Jira issue key
179
- comment: Comment text
180
-
181
- Returns:
182
- True if successful, False otherwise
183
- """
184
- if not is_jira_cli_available():
185
- return False
186
-
187
- result = subprocess.run(
188
- ["jira", "issue", "comment", "add", issue_key, "--body", comment],
189
- capture_output=True,
190
- text=True,
191
- )
192
-
193
- return result.returncode == 0
194
-
195
125
 
196
126
  def get_jira_field(issue_json: dict[str, Any], field_path: str, default: Any = None) -> Any:
197
127
  """Extract field from Jira issue JSON using dot notation.
@@ -226,13 +156,13 @@ def get_story_points(issue_key: str, issue_json: dict[str, Any] | None = None) -
226
156
 
227
157
  Args:
228
158
  issue_key: Jira issue key
229
- issue_json: Optional pre-fetched issue JSON
159
+ issue_json: Optional pre-fetched issue JSON (required if no default client)
230
160
 
231
161
  Returns:
232
162
  Story points as int, or None if not set
233
163
  """
234
164
  if issue_json is None:
235
- issue_json = get_issue(issue_key)
165
+ issue_json = get_client().get_issue_sync(issue_key)
236
166
  if not issue_json:
237
167
  return None
238
168
 
@@ -349,13 +279,11 @@ def get_current_user_email() -> str:
349
279
  class JiraClient:
350
280
  """Unified Jira REST API client with sync and async support.
351
281
 
352
- Consolidates REST API access previously scattered across:
353
- - jira_sync.py (httpx async)
354
- - jira_epic_creation.py (curl subprocess)
282
+ Consolidates all Jira REST API access.
355
283
 
356
284
  Usage (sync):
357
285
  client = JiraClient()
358
- issue = client.get_issue("MSSCI-12345")
286
+ issue = client.get_issue_sync("MSSCI-12345")
359
287
 
360
288
  Usage (async):
361
289
  client = JiraClient()
@@ -510,7 +438,7 @@ class JiraClient:
510
438
  "GET", f"/rest/api/3/issue/{issue_key}/transitions"
511
439
  )
512
440
  if not transitions_data:
513
- return {"success": False, "reason": "Could not get transitions"}
441
+ return {"success": False, "error": "Could not get transitions"}
514
442
 
515
443
  transitions = transitions_data.get("transitions", [])
516
444
  transition_id = None
@@ -523,7 +451,7 @@ class JiraClient:
523
451
  available = [t.get("name") for t in transitions]
524
452
  return {
525
453
  "success": False,
526
- "reason": f"No transition to '{target_status}' available. "
454
+ "error": f"No transition to '{target_status}' available. "
527
455
  f"Available: {available}",
528
456
  }
529
457
 
@@ -558,7 +486,7 @@ class JiraClient:
558
486
  if not users or not isinstance(users, list) or len(users) == 0:
559
487
  return {
560
488
  "success": False,
561
- "reason": f"User not found: {assignee_email}",
489
+ "error": f"User not found: {assignee_email}",
562
490
  }
563
491
  account_id = users[0].get("accountId")
564
492
  else:
@@ -571,6 +499,41 @@ class JiraClient:
571
499
  # Assign PUT returns empty body on success (204)
572
500
  return {"success": True}
573
501
 
502
+ def add_comment_sync(self, issue_key: str, comment: str) -> dict[str, Any]:
503
+ """Add a comment to a Jira issue via REST API.
504
+
505
+ Args:
506
+ issue_key: Jira issue key
507
+ comment: Comment body text
508
+
509
+ Returns:
510
+ Result dict with success status
511
+ """
512
+ payload = {
513
+ "body": {
514
+ "type": "doc",
515
+ "version": 1,
516
+ "content": [
517
+ {
518
+ "type": "paragraph",
519
+ "content": [{"type": "text", "text": comment}],
520
+ }
521
+ ],
522
+ }
523
+ }
524
+ result = self._call_api_sync(
525
+ "POST",
526
+ f"/rest/api/3/issue/{issue_key}/comment",
527
+ payload,
528
+ )
529
+ if result is None:
530
+ # POST comment returns the created comment on success;
531
+ # _call_api_sync returns None on empty/failed response
532
+ # but also returns None on JSON decode failure for 201 with empty body.
533
+ # Treat None as success since the API may return 201 with no body.
534
+ pass
535
+ return {"success": True}
536
+
574
537
  def add_to_sprint_sync(self, sprint_id: int | str, issue_key: str) -> dict[str, Any]:
575
538
  """Add issue to a sprint via Agile REST API.
576
539
 
@@ -609,9 +572,10 @@ class JiraClient:
609
572
 
610
573
  encoded_jql = urllib.parse.quote(jql)
611
574
  fields_param = ",".join(fields)
575
+ # Jira Cloud deprecated /rest/api/3/search — use /rest/api/3/search/jql
612
576
  result = self._call_api_sync(
613
577
  "GET",
614
- f"/rest/api/3/search?jql={encoded_jql}&fields={fields_param}"
578
+ f"/rest/api/3/search/jql?jql={encoded_jql}&fields={fields_param}"
615
579
  f"&maxResults={max_results}",
616
580
  )
617
581
  if not result:
@@ -680,7 +644,7 @@ class JiraClient:
680
644
  target_status: Target status name (e.g., "In Progress", "Done")
681
645
 
682
646
  Returns:
683
- Result dict with success status and optional reason
647
+ Result dict with success status and optional error
684
648
  """
685
649
  import httpx
686
650
 
@@ -694,7 +658,7 @@ class JiraClient:
694
658
  if response.status_code != 200:
695
659
  return {
696
660
  "success": False,
697
- "reason": f"Could not get transitions: {response.status_code}",
661
+ "error": f"Could not get transitions: {response.status_code}",
698
662
  }
699
663
 
700
664
  transitions = response.json().get("transitions", [])
@@ -707,7 +671,7 @@ class JiraClient:
707
671
  if not transition_id:
708
672
  return {
709
673
  "success": False,
710
- "reason": f"No transition to '{target_status}' available",
674
+ "error": f"No transition to '{target_status}' available",
711
675
  }
712
676
 
713
677
  # Execute transition
@@ -722,11 +686,11 @@ class JiraClient:
722
686
  return {"success": True}
723
687
  return {
724
688
  "success": False,
725
- "reason": f"Transition failed: {response.status_code}",
689
+ "error": f"Transition failed: {response.status_code}",
726
690
  }
727
691
 
728
692
  except httpx.HTTPError as e:
729
- return {"success": False, "reason": str(e)}
693
+ return {"success": False, "error": str(e)}
730
694
 
731
695
  async def update_fields_async(
732
696
  self, issue_key: str, fields: dict[str, Any]
@@ -756,10 +720,10 @@ class JiraClient:
756
720
 
757
721
  if response.status_code in (200, 204):
758
722
  return {"success": True}
759
- return {"success": False, "reason": f"HTTP {response.status_code}"}
723
+ return {"success": False, "error": f"HTTP {response.status_code}"}
760
724
 
761
725
  except httpx.HTTPError as e:
762
- return {"success": False, "reason": str(e)}
726
+ return {"success": False, "error": str(e)}
763
727
 
764
728
  async def sync_story_points_async(
765
729
  self,
@@ -252,7 +252,7 @@ def reconcile(
252
252
  print(" Done")
253
253
  fixed.append(key)
254
254
  else:
255
- print(f" Failed: {result.get('reason', 'unknown')}")
255
+ print(f" Failed: {result.get('error', 'unknown')}")
256
256
 
257
257
  report_lines.extend([
258
258
  "",
@@ -88,7 +88,7 @@ def fetch_jira_issue(jira_key: str) -> dict[str, Any] | None:
88
88
  Returns:
89
89
  Issue JSON if found, None otherwise
90
90
  """
91
- return jira_client.get_issue(jira_key)
91
+ return jira_client.get_client().get_issue_sync(jira_key)
92
92
 
93
93
 
94
94
  def sync_story(
@@ -147,10 +147,11 @@ def sync_story(
147
147
  if dry_run:
148
148
  actions.append(f"[DRY-RUN] {action}")
149
149
  else:
150
- if jira_client.update_issue_status(jira_key, target_status):
150
+ result = jira_client.get_client().transition_sync(jira_key, target_status)
151
+ if result.get("success"):
151
152
  actions.append(action)
152
153
  else:
153
- errors.append(f"Failed to transition {jira_key}")
154
+ errors.append(f"Failed to transition {jira_key}: {result.get('error', 'unknown')}")
154
155
  else:
155
156
  actions.append(f"status already {current_jira_status}")
156
157
 
@@ -173,7 +174,8 @@ def sync_story(
173
174
  if dry_run:
174
175
  actions.append(f"[DRY-RUN] {action}")
175
176
  else:
176
- if jira_client.add_comment(jira_key, comment):
177
+ result = jira_client.get_client().add_comment_sync(jira_key, comment)
178
+ if result.get("success"):
177
179
  actions.append(action)
178
180
  else:
179
181
  errors.append(f"Failed to add comment to {jira_key}")
@@ -150,7 +150,7 @@ async def sync_story(
150
150
  if result.get("success"):
151
151
  actions.append(f"transitioned: {current_status} -> {target_status}")
152
152
  else:
153
- actions.append(f"transition failed: {result.get('reason')}")
153
+ actions.append(f"transition failed: {result.get('error')}")
154
154
 
155
155
  # Sync points if requested
156
156
  story_points = story.get("points")
@@ -167,7 +167,7 @@ async def sync_story(
167
167
  else:
168
168
  actions.append(f"synced points: {story_points}")
169
169
  else:
170
- actions.append(f"points sync failed: {result.get('reason')}")
170
+ actions.append(f"points sync failed: {result.get('error')}")
171
171
 
172
172
  return SyncResult(
173
173
  story_id=story_id,
@@ -8,7 +8,7 @@ to conform to the XML schema definitions:
8
8
  - Workflow step files -> guides/workflow-step-schema.md
9
9
 
10
10
  Usage:
11
- python -m pf.migration [command] [options]
11
+ pf migration [command] [options]
12
12
 
13
13
  Commands:
14
14
  session Migrate session files to XML format
@@ -3,7 +3,6 @@ Migration CLI - Click-based CLI for XML schema migration tools.
3
3
 
4
4
  Usage:
5
5
  pf migration [COMMAND] [ARGS]...
6
- python -m pf.migration [COMMAND] [ARGS]...
7
6
 
8
7
  Commands:
9
8
  session Migrate session files to XML format
File without changes
@@ -0,0 +1,186 @@
1
+ """Package CLI — theme package installation and portrait downloads.
2
+
3
+ Usage:
4
+ pf package list # Show all theme packages with status
5
+ pf package install comedy # Install package + download portraits
6
+ pf package install-portraits comedy # Download portraits for installed package
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import json
12
+ import sys
13
+
14
+ import click
15
+
16
+
17
+ @click.group()
18
+ def package():
19
+ """Theme package management.
20
+
21
+ \b
22
+ Commands:
23
+ list - Show all theme packages with status
24
+ install - Install a theme package (npm + portraits)
25
+ install-portraits - Download portraits for installed packages
26
+ """
27
+ pass
28
+
29
+
30
+ @package.command("list")
31
+ @click.option("--json", "output_json", is_flag=True, help="Output as JSON.")
32
+ def list_cmd(output_json: bool):
33
+ """Show all theme packages with installation status.
34
+
35
+ Displays each of the 7 theme packages with their current state:
36
+ installed, not installed, and whether portraits are downloaded.
37
+ """
38
+ from pf.package.discovery import get_package_status
39
+
40
+ statuses = get_package_status()
41
+
42
+ if output_json:
43
+ click.echo(json.dumps(statuses, indent=2))
44
+ return
45
+
46
+ click.echo("Theme Packages")
47
+ click.echo("=" * 50)
48
+ click.echo("")
49
+
50
+ for pkg in statuses:
51
+ name = pkg["name"]
52
+ if not pkg["installed"]:
53
+ status = "not installed"
54
+ elif pkg["portraits"]:
55
+ status = "installed + portraits"
56
+ else:
57
+ status = "installed (no portraits)"
58
+ click.echo(f" {name:<22} {status}")
59
+
60
+ click.echo("")
61
+ installed = sum(1 for p in statuses if p["installed"])
62
+ with_portraits = sum(1 for p in statuses if p["portraits"])
63
+ click.echo(f"{installed}/{len(statuses)} installed, {with_portraits} with portraits")
64
+
65
+
66
+ @package.command()
67
+ @click.argument("name")
68
+ @click.option("--skip-portraits", is_flag=True, help="Skip portrait download after install.")
69
+ @click.option("--all-sizes", is_flag=True, help="Download all portrait sizes (default: large only).")
70
+ @click.option("--dry-run", is_flag=True, help="Show what would be done without making changes.")
71
+ def install(name: str, skip_portraits: bool, all_sizes: bool, dry_run: bool):
72
+ """Install a theme package and download its portraits.
73
+
74
+ Runs npm install for the package, then downloads portraits from
75
+ the GitHub monorepo via the gh CLI. Portraits are saved into the
76
+ package's node_modules directory where the portrait-resolver
77
+ already looks for them.
78
+
79
+ \b
80
+ Arguments:
81
+ NAME - Theme package name (comedy, literary, mythology-fantasy,
82
+ prestige-tv, realistic, scifi, superheroes)
83
+ """
84
+ from pf.package.discovery import is_package_installed, npm_install
85
+ from pf.package.portraits import download_portraits
86
+
87
+ # Step 1: npm install
88
+ if is_package_installed(name) and not dry_run:
89
+ click.echo(f"@pennyfarthing/themes-{name} already installed, skipping npm install")
90
+ else:
91
+ if dry_run:
92
+ click.echo(f"[DRY-RUN] Would run: npm install @pennyfarthing/themes-{name}")
93
+ else:
94
+ click.echo(f"Installing @pennyfarthing/themes-{name}...")
95
+
96
+ result = npm_install(name, dry_run=dry_run)
97
+ if not result["success"]:
98
+ click.echo(f"Error: {result['error']}", err=True)
99
+ sys.exit(1)
100
+
101
+ if dry_run:
102
+ click.echo(f" Command: {result['data']['command']}")
103
+ else:
104
+ click.echo(" Installed successfully")
105
+
106
+ # Step 2: download portraits
107
+ if skip_portraits:
108
+ if not dry_run:
109
+ click.echo("Skipping portrait download (--skip-portraits)")
110
+ return
111
+
112
+ if dry_run:
113
+ click.echo(f"[DRY-RUN] Would download portraits (sizes: {'all' if all_sizes else 'large only'})")
114
+ else:
115
+ click.echo(f"Downloading portraits ({'all sizes' if all_sizes else 'large only'})...")
116
+
117
+ result = download_portraits(name, all_sizes=all_sizes, dry_run=dry_run)
118
+ if not result["success"]:
119
+ click.echo(f"Error: {result['error']}", err=True)
120
+ sys.exit(1)
121
+
122
+ data = result["data"]
123
+ if dry_run:
124
+ click.echo(f" {data['total_files']} files to download")
125
+ click.echo(f" Destination: {data['destination']}")
126
+ if data.get("files"):
127
+ for f in data["files"]:
128
+ click.echo(f" {f}")
129
+ if data.get("truncated"):
130
+ click.echo(" ...")
131
+ else:
132
+ click.echo(f" Downloaded: {data['downloaded']}, Skipped: {data['skipped']}, Total: {data['total']}")
133
+ if data.get("error_count"):
134
+ click.echo(f" Errors: {data['error_count']}")
135
+
136
+
137
+ @package.command("install-portraits")
138
+ @click.argument("name", required=False)
139
+ @click.option("--all", "install_all", is_flag=True, help="Download portraits for all installed packages.")
140
+ @click.option("--all-sizes", is_flag=True, help="Download all portrait sizes (default: large only).")
141
+ @click.option("--dry-run", is_flag=True, help="Show what would be done without making changes.")
142
+ def install_portraits(name: str | None, install_all: bool, all_sizes: bool, dry_run: bool):
143
+ """Download portraits for already-installed theme packages.
144
+
145
+ Requires either a package NAME or --all. Downloads portrait images
146
+ from the GitHub monorepo and saves them into the package's
147
+ node_modules directory.
148
+
149
+ \b
150
+ Arguments:
151
+ NAME - Theme package name (optional if --all is used)
152
+ """
153
+ from pf.package.discovery import THEME_PACKAGES, is_package_installed
154
+ from pf.package.portraits import download_portraits
155
+
156
+ if not name and not install_all:
157
+ click.echo("Error: provide a package name or use --all", err=True)
158
+ sys.exit(1)
159
+
160
+ packages = THEME_PACKAGES if install_all else [name]
161
+
162
+ for pkg_name in packages:
163
+ if not is_package_installed(pkg_name):
164
+ if install_all:
165
+ continue # silently skip uninstalled packages in --all mode
166
+ click.echo(
167
+ f"Error: @pennyfarthing/themes-{pkg_name} is not installed. "
168
+ f"Run: pf package install {pkg_name}",
169
+ err=True,
170
+ )
171
+ sys.exit(1)
172
+
173
+ click.echo(f"Downloading portraits for {pkg_name} ({'all sizes' if all_sizes else 'large only'})...")
174
+
175
+ result = download_portraits(pkg_name, all_sizes=all_sizes, dry_run=dry_run)
176
+ if not result["success"]:
177
+ click.echo(f" Error: {result['error']}", err=True)
178
+ if not install_all:
179
+ sys.exit(1)
180
+ continue
181
+
182
+ data = result["data"]
183
+ if dry_run:
184
+ click.echo(f" [DRY-RUN] {data['total_files']} files would be downloaded")
185
+ else:
186
+ click.echo(f" Downloaded: {data['downloaded']}, Skipped: {data['skipped']}, Total: {data['total']}")
@@ -0,0 +1,130 @@
1
+ """Package discovery and npm install wrapper for theme packages.
2
+
3
+ Checks installation status of @pennyfarthing/themes-* packages and
4
+ provides npm install functionality.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import json
10
+ import shutil
11
+ import subprocess
12
+ from pathlib import Path
13
+ from typing import Any
14
+
15
+ from pf.common.config import get_project_root
16
+
17
+ THEME_PACKAGES = [
18
+ "comedy",
19
+ "literary",
20
+ "mythology-fantasy",
21
+ "prestige-tv",
22
+ "realistic",
23
+ "scifi",
24
+ "superheroes",
25
+ ]
26
+
27
+ PORTRAIT_SIZES = ["small", "medium", "large", "xlarge"]
28
+
29
+
30
+ def _node_modules_dir(project_root: Path | None = None) -> Path:
31
+ """Return the node_modules/@pennyfarthing directory."""
32
+ root = project_root or get_project_root()
33
+ return root / "node_modules" / "@pennyfarthing"
34
+
35
+
36
+ def get_package_dir(name: str, project_root: Path | None = None) -> Path:
37
+ """Return the path to a theme package in node_modules."""
38
+ return _node_modules_dir(project_root) / f"themes-{name}"
39
+
40
+
41
+ def is_package_installed(name: str, project_root: Path | None = None) -> bool:
42
+ """Check if a theme package is installed in node_modules."""
43
+ pkg_dir = get_package_dir(name, project_root)
44
+ pkg_json = pkg_dir / "package.json"
45
+ if not pkg_json.is_file():
46
+ return False
47
+ try:
48
+ data = json.loads(pkg_json.read_text())
49
+ return data.get("pennyfarthing-theme-pack") is True
50
+ except Exception:
51
+ return False
52
+
53
+
54
+ def has_portraits(name: str, project_root: Path | None = None) -> bool:
55
+ """Check if a theme package has portraits downloaded."""
56
+ pkg_dir = get_package_dir(name, project_root)
57
+ portraits_dir = pkg_dir / "portraits"
58
+ if not portraits_dir.is_dir():
59
+ return False
60
+ # Check for at least one image file in any subdirectory
61
+ for child in portraits_dir.rglob("*"):
62
+ if child.is_file() and child.suffix.lower() in (".png", ".jpg", ".jpeg", ".webp"):
63
+ return True
64
+ return False
65
+
66
+
67
+ def get_package_status(
68
+ project_root: Path | None = None,
69
+ ) -> list[dict[str, Any]]:
70
+ """Get installation status for all theme packages.
71
+
72
+ Returns:
73
+ List of dicts with keys: name, installed, portraits
74
+ """
75
+ results = []
76
+ for name in THEME_PACKAGES:
77
+ installed = is_package_installed(name, project_root)
78
+ portraits = has_portraits(name, project_root) if installed else False
79
+ results.append({
80
+ "name": name,
81
+ "installed": installed,
82
+ "portraits": portraits,
83
+ })
84
+ return results
85
+
86
+
87
+ def npm_install(
88
+ name: str,
89
+ project_root: Path | None = None,
90
+ dry_run: bool = False,
91
+ ) -> dict[str, Any]:
92
+ """Install a theme package via npm.
93
+
94
+ Args:
95
+ name: Theme package name (e.g. "comedy")
96
+ project_root: Project root (auto-detected if not provided)
97
+ dry_run: If True, show what would happen without executing
98
+
99
+ Returns:
100
+ Result dict with success, data?, error?
101
+ """
102
+ if name not in THEME_PACKAGES:
103
+ return {"success": False, "error": f"Unknown theme package: {name}"}
104
+
105
+ if shutil.which("npm") is None:
106
+ return {"success": False, "error": "npm not found. Install Node.js first."}
107
+
108
+ root = project_root or get_project_root()
109
+ pkg_spec = f"@pennyfarthing/themes-{name}"
110
+
111
+ if dry_run:
112
+ return {
113
+ "success": True,
114
+ "data": {"action": "dry-run", "command": f"npm install {pkg_spec}", "cwd": str(root)},
115
+ }
116
+
117
+ result = subprocess.run(
118
+ ["npm", "install", pkg_spec],
119
+ capture_output=True,
120
+ text=True,
121
+ cwd=str(root),
122
+ )
123
+
124
+ if result.returncode != 0:
125
+ return {
126
+ "success": False,
127
+ "error": f"npm install failed: {result.stderr.strip()}",
128
+ }
129
+
130
+ return {"success": True, "data": {"package": pkg_spec, "output": result.stdout.strip()}}