@pennyfarthing/core 11.2.0 → 11.2.1

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 (403) hide show
  1. package/README.md +1 -1
  2. package/package.json +2 -1
  3. package/packages/core/dist/cli/commands/doctor.d.ts.map +1 -1
  4. package/packages/core/dist/cli/commands/doctor.js +381 -66
  5. package/packages/core/dist/cli/commands/doctor.js.map +1 -1
  6. package/packages/core/dist/cli/commands/init.js +4 -4
  7. package/packages/core/dist/cli/commands/init.js.map +1 -1
  8. package/packages/core/dist/cli/commands/update.d.ts.map +1 -1
  9. package/packages/core/dist/cli/commands/update.js +4 -5
  10. package/packages/core/dist/cli/commands/update.js.map +1 -1
  11. package/packages/core/dist/cli/utils/constants.d.ts +3 -8
  12. package/packages/core/dist/cli/utils/constants.d.ts.map +1 -1
  13. package/packages/core/dist/cli/utils/constants.js +3 -4
  14. package/packages/core/dist/cli/utils/constants.js.map +1 -1
  15. package/packages/core/dist/cli/utils/settings.d.ts +11 -0
  16. package/packages/core/dist/cli/utils/settings.d.ts.map +1 -1
  17. package/packages/core/dist/cli/utils/settings.js +65 -29
  18. package/packages/core/dist/cli/utils/settings.js.map +1 -1
  19. package/packages/core/dist/cli/utils/symlinks.js +16 -16
  20. package/packages/core/dist/cli/utils/symlinks.js.map +1 -1
  21. package/packages/core/dist/consultation/tandem-metrics.d.ts +91 -0
  22. package/packages/core/dist/consultation/tandem-metrics.d.ts.map +1 -0
  23. package/packages/core/dist/consultation/tandem-metrics.js +131 -0
  24. package/packages/core/dist/consultation/tandem-metrics.js.map +1 -0
  25. package/packages/core/dist/consultation/tandem-metrics.test.d.ts +18 -0
  26. package/packages/core/dist/consultation/tandem-metrics.test.d.ts.map +1 -0
  27. package/packages/core/dist/consultation/tandem-metrics.test.js +457 -0
  28. package/packages/core/dist/consultation/tandem-metrics.test.js.map +1 -0
  29. package/packages/core/dist/public/js/react/react.js +14 -14
  30. package/packages/core/dist/scripts/benchmark-integration.d.ts +182 -0
  31. package/packages/core/dist/scripts/benchmark-integration.d.ts.map +1 -0
  32. package/packages/core/dist/scripts/benchmark-integration.js +691 -0
  33. package/packages/core/dist/scripts/benchmark-integration.js.map +1 -0
  34. package/packages/core/dist/scripts/job-fair-aggregator.d.ts +150 -0
  35. package/packages/core/dist/scripts/job-fair-aggregator.d.ts.map +1 -0
  36. package/packages/core/dist/scripts/job-fair-aggregator.js +547 -0
  37. package/packages/core/dist/scripts/job-fair-aggregator.js.map +1 -0
  38. package/packages/core/dist/server/api/agent-load.js +1 -1
  39. package/packages/core/dist/server/api/agent-load.js.map +1 -1
  40. package/packages/core/dist/server/server.d.ts +0 -3
  41. package/packages/core/dist/server/server.d.ts.map +1 -1
  42. package/packages/core/dist/server/server.js +3 -37
  43. package/packages/core/dist/server/server.js.map +1 -1
  44. package/packages/core/dist/server/server.test.d.ts +1 -1
  45. package/packages/core/dist/server/server.test.js +12 -23
  46. package/packages/core/dist/server/server.test.js.map +1 -1
  47. package/packages/core/dist/shared/capabilities.d.ts +88 -0
  48. package/packages/core/dist/shared/capabilities.d.ts.map +1 -0
  49. package/packages/core/dist/shared/capabilities.js +133 -0
  50. package/packages/core/dist/shared/capabilities.js.map +1 -0
  51. package/packages/core/dist/shared/capabilities.test.d.ts +2 -0
  52. package/packages/core/dist/shared/capabilities.test.d.ts.map +1 -0
  53. package/packages/core/dist/shared/capabilities.test.js +217 -0
  54. package/packages/core/dist/shared/capabilities.test.js.map +1 -0
  55. package/packages/core/dist/shared/spawn-prompt.d.ts +47 -0
  56. package/packages/core/dist/shared/spawn-prompt.d.ts.map +1 -0
  57. package/packages/core/dist/shared/spawn-prompt.js +82 -0
  58. package/packages/core/dist/shared/spawn-prompt.js.map +1 -0
  59. package/packages/core/dist/shared/spawn-prompt.test.d.ts +2 -0
  60. package/packages/core/dist/shared/spawn-prompt.test.d.ts.map +1 -0
  61. package/packages/core/dist/shared/spawn-prompt.test.js +251 -0
  62. package/packages/core/dist/shared/spawn-prompt.test.js.map +1 -0
  63. package/packages/core/dist/workflow/tandem-workflow-templates.test.d.ts +18 -0
  64. package/packages/core/dist/workflow/tandem-workflow-templates.test.d.ts.map +1 -0
  65. package/packages/core/dist/workflow/tandem-workflow-templates.test.js +434 -0
  66. package/packages/core/dist/workflow/tandem-workflow-templates.test.js.map +1 -0
  67. package/packages/core/dist/workflow/workflow-schema.d.ts +32 -0
  68. package/packages/core/dist/workflow/workflow-schema.d.ts.map +1 -1
  69. package/packages/core/dist/workflow/workflow-schema.js +120 -0
  70. package/packages/core/dist/workflow/workflow-schema.js.map +1 -1
  71. package/packages/core/dist/workflow/workflow-schema.test.d.ts.map +1 -1
  72. package/packages/core/dist/workflow/workflow-schema.test.js +570 -1
  73. package/packages/core/dist/workflow/workflow-schema.test.js.map +1 -1
  74. package/pennyfarthing-dist/agents/dev.md +6 -10
  75. package/pennyfarthing-dist/agents/reviewer.md +8 -2
  76. package/pennyfarthing-dist/agents/sm-finish.md +18 -1
  77. package/pennyfarthing-dist/commands/pf-git.md +4 -2
  78. package/pennyfarthing-dist/gates/approval.md +63 -0
  79. package/pennyfarthing-dist/gates/confidence-sm.md +71 -0
  80. package/pennyfarthing-dist/gates/context-ok.md +56 -0
  81. package/pennyfarthing-dist/gates/evaluations/confidence-sm.md +54 -0
  82. package/pennyfarthing-dist/gates/quality-pass.md +67 -0
  83. package/pennyfarthing-dist/gates/tests-fail.md +84 -0
  84. package/pennyfarthing-dist/gates/tests-pass.md +79 -0
  85. package/pennyfarthing-dist/guides/agent-behavior.md +23 -19
  86. package/pennyfarthing-dist/guides/bell-mode.md +1 -1
  87. package/pennyfarthing-dist/guides/hooks.md +28 -28
  88. package/pennyfarthing-dist/guides/reflector.md +1 -1
  89. package/pennyfarthing-dist/guides/tandem-protocol.md +3 -3
  90. package/pennyfarthing-dist/scripts/core/check-context.sh +2 -0
  91. package/pennyfarthing-dist/scripts/core/phase-check-start.sh +5 -87
  92. package/pennyfarthing-dist/scripts/hooks/README.md +5 -5
  93. package/pennyfarthing-dist/scripts/hooks/__pycache__/question_reflector_check.cpython-314.pyc +0 -0
  94. package/pennyfarthing-dist/scripts/hooks/bell-mode-hook.sh +4 -183
  95. package/pennyfarthing-dist/scripts/hooks/context-circuit-breaker.sh +4 -95
  96. package/pennyfarthing-dist/scripts/hooks/context-warning.sh +4 -65
  97. package/pennyfarthing-dist/scripts/hooks/cyclist-pretooluse-hook.sh +3 -31
  98. package/pennyfarthing-dist/scripts/hooks/pre-commit.sh +27 -33
  99. package/pennyfarthing-dist/scripts/hooks/pre-edit-check.sh +4 -71
  100. package/pennyfarthing-dist/scripts/hooks/question-reflector-check.sh +3 -19
  101. package/pennyfarthing-dist/scripts/hooks/schema-validation.sh +4 -30
  102. package/pennyfarthing-dist/scripts/hooks/session-start.sh +3 -32
  103. package/pennyfarthing-dist/scripts/hooks/session-stop.sh +4 -65
  104. package/pennyfarthing-dist/scripts/hooks/sprint-yaml-validation.sh +4 -78
  105. package/pennyfarthing-dist/scripts/hooks/welcome-hook.sh +4 -93
  106. package/pennyfarthing-dist/scripts/misc/README.md +1 -1
  107. package/pennyfarthing-dist/scripts/misc/statusline.sh +4 -301
  108. package/pennyfarthing-dist/templates/settings.local.json.template +19 -10
  109. package/pennyfarthing-dist/workflows/tdd.yaml +11 -2
  110. package/pennyfarthing_scripts/CLAUDE.md +19 -10
  111. package/pennyfarthing_scripts/__pycache__/__init__.cpython-311.pyc +0 -0
  112. package/pennyfarthing_scripts/__pycache__/__init__.cpython-314.pyc +0 -0
  113. package/pennyfarthing_scripts/__pycache__/bellmode_hook.cpython-314.pyc +0 -0
  114. package/pennyfarthing_scripts/__pycache__/cli.cpython-314.pyc +0 -0
  115. package/pennyfarthing_scripts/__pycache__/config.cpython-314.pyc +0 -0
  116. package/pennyfarthing_scripts/__pycache__/context.cpython-314.pyc +0 -0
  117. package/pennyfarthing_scripts/__pycache__/hooks.cpython-314.pyc +0 -0
  118. package/pennyfarthing_scripts/__pycache__/jira.cpython-314.pyc +0 -0
  119. package/pennyfarthing_scripts/__pycache__/jira_bidirectional_sync.cpython-314.pyc +0 -0
  120. package/pennyfarthing_scripts/__pycache__/jira_epic_creation.cpython-314.pyc +0 -0
  121. package/pennyfarthing_scripts/__pycache__/jira_sync.cpython-314.pyc +0 -0
  122. package/pennyfarthing_scripts/__pycache__/jira_sync_story.cpython-314.pyc +0 -0
  123. package/pennyfarthing_scripts/__pycache__/output.cpython-314.pyc +0 -0
  124. package/pennyfarthing_scripts/__pycache__/patch_mode.cpython-314.pyc +0 -0
  125. package/pennyfarthing_scripts/__pycache__/pretooluse_hook.cpython-314.pyc +0 -0
  126. package/pennyfarthing_scripts/__pycache__/schema_validation_hook.cpython-314.pyc +0 -0
  127. package/pennyfarthing_scripts/__pycache__/session_start_hook.cpython-314.pyc +0 -0
  128. package/pennyfarthing_scripts/__pycache__/sprint.cpython-314.pyc +0 -0
  129. package/pennyfarthing_scripts/__pycache__/workflow.cpython-311.pyc +0 -0
  130. package/pennyfarthing_scripts/__pycache__/workflow.cpython-314.pyc +0 -0
  131. package/pennyfarthing_scripts/bc/__pycache__/__init__.cpython-314.pyc +0 -0
  132. package/pennyfarthing_scripts/bc/__pycache__/cli.cpython-314.pyc +0 -0
  133. package/pennyfarthing_scripts/bc/__pycache__/focus.cpython-314.pyc +0 -0
  134. package/pennyfarthing_scripts/bellmode_hook.py +12 -296
  135. package/pennyfarthing_scripts/bikerack/__pycache__/__init__.cpython-314.pyc +0 -0
  136. package/pennyfarthing_scripts/bikerack/__pycache__/__main__.cpython-314.pyc +0 -0
  137. package/pennyfarthing_scripts/bikerack/__pycache__/audit_log_panel.cpython-314.pyc +0 -0
  138. package/pennyfarthing_scripts/bikerack/__pycache__/background_panel.cpython-314.pyc +0 -0
  139. package/pennyfarthing_scripts/bikerack/__pycache__/base_panel.cpython-314.pyc +0 -0
  140. package/pennyfarthing_scripts/bikerack/__pycache__/changed_panel.cpython-314.pyc +0 -0
  141. package/pennyfarthing_scripts/bikerack/__pycache__/cli.cpython-314.pyc +0 -0
  142. package/pennyfarthing_scripts/bikerack/__pycache__/context_meter_footer.cpython-314.pyc +0 -0
  143. package/pennyfarthing_scripts/bikerack/__pycache__/debug_panel.cpython-314.pyc +0 -0
  144. package/pennyfarthing_scripts/bikerack/__pycache__/diffs_panel.cpython-314.pyc +0 -0
  145. package/pennyfarthing_scripts/bikerack/__pycache__/events.cpython-314.pyc +0 -0
  146. package/pennyfarthing_scripts/bikerack/__pycache__/git_panel.cpython-314.pyc +0 -0
  147. package/pennyfarthing_scripts/bikerack/__pycache__/launcher.cpython-314.pyc +0 -0
  148. package/pennyfarthing_scripts/bikerack/__pycache__/portrait_resolver.cpython-314.pyc +0 -0
  149. package/pennyfarthing_scripts/bikerack/__pycache__/progress_panel.cpython-314.pyc +0 -0
  150. package/pennyfarthing_scripts/bikerack/__pycache__/sprint_panel.cpython-314.pyc +0 -0
  151. package/pennyfarthing_scripts/bikerack/__pycache__/story_detail_data.cpython-314.pyc +0 -0
  152. package/pennyfarthing_scripts/bikerack/__pycache__/story_detail_screen.cpython-314.pyc +0 -0
  153. package/pennyfarthing_scripts/bikerack/__pycache__/tui.cpython-314.pyc +0 -0
  154. package/pennyfarthing_scripts/bikerack/__pycache__/ws_client.cpython-314.pyc +0 -0
  155. package/pennyfarthing_scripts/bikerack/audit_log_panel.py +119 -0
  156. package/pennyfarthing_scripts/bikerack/base_panel.py +27 -4
  157. package/pennyfarthing_scripts/bikerack/changed_panel.py +96 -4
  158. package/pennyfarthing_scripts/bikerack/context_meter_footer.py +88 -0
  159. package/pennyfarthing_scripts/bikerack/debug_panel.py +1 -1
  160. package/pennyfarthing_scripts/bikerack/diffs_panel.py +30 -0
  161. package/pennyfarthing_scripts/bikerack/events.py +28 -0
  162. package/pennyfarthing_scripts/bikerack/portrait_resolver.py +139 -0
  163. package/pennyfarthing_scripts/bikerack/sprint_panel.py +373 -142
  164. package/pennyfarthing_scripts/bikerack/story_detail_data.py +244 -0
  165. package/pennyfarthing_scripts/bikerack/story_detail_screen.py +176 -0
  166. package/pennyfarthing_scripts/bikerack/tui.py +293 -61
  167. package/pennyfarthing_scripts/brownfield/__pycache__/__init__.cpython-314.pyc +0 -0
  168. package/pennyfarthing_scripts/brownfield/__pycache__/__main__.cpython-314.pyc +0 -0
  169. package/pennyfarthing_scripts/brownfield/__pycache__/cli.cpython-314.pyc +0 -0
  170. package/pennyfarthing_scripts/brownfield/__pycache__/discover.cpython-314.pyc +0 -0
  171. package/pennyfarthing_scripts/cli.py +5 -0
  172. package/pennyfarthing_scripts/codemarkers/__pycache__/__init__.cpython-314.pyc +0 -0
  173. package/pennyfarthing_scripts/codemarkers/__pycache__/__main__.cpython-314.pyc +0 -0
  174. package/pennyfarthing_scripts/codemarkers/__pycache__/analyze.cpython-314.pyc +0 -0
  175. package/pennyfarthing_scripts/codemarkers/__pycache__/cli.cpython-314.pyc +0 -0
  176. package/pennyfarthing_scripts/codemarkers/__pycache__/formatters.cpython-314.pyc +0 -0
  177. package/pennyfarthing_scripts/codemarkers/__pycache__/models.cpython-314.pyc +0 -0
  178. package/pennyfarthing_scripts/common/__pycache__/__init__.cpython-314.pyc +0 -0
  179. package/pennyfarthing_scripts/common/__pycache__/config.cpython-314.pyc +0 -0
  180. package/pennyfarthing_scripts/common/__pycache__/output.cpython-314.pyc +0 -0
  181. package/pennyfarthing_scripts/common/__pycache__/themes.cpython-314.pyc +0 -0
  182. package/pennyfarthing_scripts/common/pr_config.py +38 -0
  183. package/pennyfarthing_scripts/complexity/__pycache__/__init__.cpython-314.pyc +0 -0
  184. package/pennyfarthing_scripts/complexity/__pycache__/__main__.cpython-314.pyc +0 -0
  185. package/pennyfarthing_scripts/complexity/__pycache__/analyze.cpython-314.pyc +0 -0
  186. package/pennyfarthing_scripts/complexity/__pycache__/cli.cpython-314.pyc +0 -0
  187. package/pennyfarthing_scripts/complexity/__pycache__/formatters.cpython-314.pyc +0 -0
  188. package/pennyfarthing_scripts/complexity/__pycache__/models.cpython-314.pyc +0 -0
  189. package/pennyfarthing_scripts/consultation/__pycache__/__init__.cpython-314.pyc +0 -0
  190. package/pennyfarthing_scripts/consultation/__pycache__/cli.cpython-314.pyc +0 -0
  191. package/pennyfarthing_scripts/consultation/__pycache__/dialogue_manager.cpython-314.pyc +0 -0
  192. package/pennyfarthing_scripts/deadcode/__pycache__/__init__.cpython-314.pyc +0 -0
  193. package/pennyfarthing_scripts/deadcode/__pycache__/__main__.cpython-314.pyc +0 -0
  194. package/pennyfarthing_scripts/deadcode/__pycache__/analyze.cpython-314.pyc +0 -0
  195. package/pennyfarthing_scripts/deadcode/__pycache__/cli.cpython-314.pyc +0 -0
  196. package/pennyfarthing_scripts/deadcode/__pycache__/formatters.cpython-314.pyc +0 -0
  197. package/pennyfarthing_scripts/deadcode/__pycache__/models.cpython-314.pyc +0 -0
  198. package/pennyfarthing_scripts/dependencies/__pycache__/__init__.cpython-314.pyc +0 -0
  199. package/pennyfarthing_scripts/dependencies/__pycache__/__main__.cpython-314.pyc +0 -0
  200. package/pennyfarthing_scripts/dependencies/__pycache__/analyze.cpython-314.pyc +0 -0
  201. package/pennyfarthing_scripts/dependencies/__pycache__/cli.cpython-314.pyc +0 -0
  202. package/pennyfarthing_scripts/dependencies/__pycache__/formatters.cpython-314.pyc +0 -0
  203. package/pennyfarthing_scripts/dependencies/__pycache__/models.cpython-314.pyc +0 -0
  204. package/pennyfarthing_scripts/epic/__pycache__/__init__.cpython-314.pyc +0 -0
  205. package/pennyfarthing_scripts/epic/__pycache__/cli.cpython-314.pyc +0 -0
  206. package/pennyfarthing_scripts/git/__pycache__/__init__.cpython-314.pyc +0 -0
  207. package/pennyfarthing_scripts/git/__pycache__/create_branches.cpython-314.pyc +0 -0
  208. package/pennyfarthing_scripts/git/__pycache__/status_all.cpython-314.pyc +0 -0
  209. package/pennyfarthing_scripts/git_group/__pycache__/__init__.cpython-314.pyc +0 -0
  210. package/pennyfarthing_scripts/git_group/__pycache__/cli.cpython-314.pyc +0 -0
  211. package/pennyfarthing_scripts/handoff/__pycache__/__init__.cpython-314.pyc +0 -0
  212. package/pennyfarthing_scripts/handoff/__pycache__/cli.cpython-314.pyc +0 -0
  213. package/pennyfarthing_scripts/handoff/__pycache__/complete_phase.cpython-314.pyc +0 -0
  214. package/pennyfarthing_scripts/handoff/__pycache__/gate_file.cpython-314.pyc +0 -0
  215. package/pennyfarthing_scripts/handoff/__pycache__/gate_runner.cpython-314.pyc +0 -0
  216. package/pennyfarthing_scripts/handoff/__pycache__/marker.cpython-314.pyc +0 -0
  217. package/pennyfarthing_scripts/handoff/__pycache__/resolve_gate.cpython-314.pyc +0 -0
  218. package/pennyfarthing_scripts/handoff/cli.py +33 -1
  219. package/pennyfarthing_scripts/handoff/complete_phase.py +28 -0
  220. package/pennyfarthing_scripts/handoff/marker.py +15 -15
  221. package/pennyfarthing_scripts/handoff/phase_check.py +96 -0
  222. package/pennyfarthing_scripts/handoff/resolve_gate.py +13 -1
  223. package/pennyfarthing_scripts/healthscore/__pycache__/__init__.cpython-314.pyc +0 -0
  224. package/pennyfarthing_scripts/healthscore/__pycache__/__main__.cpython-314.pyc +0 -0
  225. package/pennyfarthing_scripts/healthscore/__pycache__/analyze.cpython-314.pyc +0 -0
  226. package/pennyfarthing_scripts/healthscore/__pycache__/cli.cpython-314.pyc +0 -0
  227. package/pennyfarthing_scripts/healthscore/__pycache__/formatters.cpython-314.pyc +0 -0
  228. package/pennyfarthing_scripts/healthscore/__pycache__/models.cpython-314.pyc +0 -0
  229. package/pennyfarthing_scripts/hooks/__init__.py +437 -0
  230. package/pennyfarthing_scripts/hooks/__pycache__/__init__.cpython-314.pyc +0 -0
  231. package/pennyfarthing_scripts/hooks/__pycache__/bell_mode.cpython-314.pyc +0 -0
  232. package/pennyfarthing_scripts/hooks/__pycache__/cli.cpython-314.pyc +0 -0
  233. package/pennyfarthing_scripts/hooks/__pycache__/context_breaker.cpython-314.pyc +0 -0
  234. package/pennyfarthing_scripts/hooks/__pycache__/context_warning.cpython-314.pyc +0 -0
  235. package/pennyfarthing_scripts/hooks/__pycache__/cyclist_pretooluse.cpython-314.pyc +0 -0
  236. package/pennyfarthing_scripts/hooks/__pycache__/pre_edit_check.cpython-314.pyc +0 -0
  237. package/pennyfarthing_scripts/hooks/__pycache__/reflector_check.cpython-314.pyc +0 -0
  238. package/pennyfarthing_scripts/hooks/__pycache__/schema_validation.cpython-314.pyc +0 -0
  239. package/pennyfarthing_scripts/hooks/__pycache__/session_start.cpython-314.pyc +0 -0
  240. package/pennyfarthing_scripts/hooks/__pycache__/session_stop.cpython-314.pyc +0 -0
  241. package/pennyfarthing_scripts/hooks/__pycache__/sprint_yaml_validation.cpython-314.pyc +0 -0
  242. package/pennyfarthing_scripts/hooks/__pycache__/statusline.cpython-314.pyc +0 -0
  243. package/pennyfarthing_scripts/hooks/bell_mode.py +215 -0
  244. package/pennyfarthing_scripts/hooks/cli.py +96 -0
  245. package/pennyfarthing_scripts/hooks/context_breaker.py +104 -0
  246. package/pennyfarthing_scripts/hooks/context_warning.py +66 -0
  247. package/pennyfarthing_scripts/hooks/cyclist_pretooluse.py +129 -0
  248. package/pennyfarthing_scripts/hooks/pre_edit_check.py +78 -0
  249. package/pennyfarthing_scripts/hooks/reflector_check.py +271 -0
  250. package/pennyfarthing_scripts/hooks/schema_validation.py +203 -0
  251. package/pennyfarthing_scripts/hooks/session_start.py +296 -0
  252. package/pennyfarthing_scripts/hooks/session_stop.py +111 -0
  253. package/pennyfarthing_scripts/hooks/sprint_yaml_validation.py +97 -0
  254. package/pennyfarthing_scripts/hooks/statusline.py +420 -0
  255. package/pennyfarthing_scripts/hooks.py +27 -432
  256. package/pennyfarthing_scripts/hotspots/__pycache__/__init__.cpython-314.pyc +0 -0
  257. package/pennyfarthing_scripts/hotspots/__pycache__/__main__.cpython-314.pyc +0 -0
  258. package/pennyfarthing_scripts/hotspots/__pycache__/analyze.cpython-314.pyc +0 -0
  259. package/pennyfarthing_scripts/hotspots/__pycache__/cli.cpython-314.pyc +0 -0
  260. package/pennyfarthing_scripts/hotspots/__pycache__/formatters.cpython-314.pyc +0 -0
  261. package/pennyfarthing_scripts/hotspots/__pycache__/models.cpython-314.pyc +0 -0
  262. package/pennyfarthing_scripts/jira/__pycache__/__init__.cpython-314.pyc +0 -0
  263. package/pennyfarthing_scripts/jira/__pycache__/__main__.cpython-314.pyc +0 -0
  264. package/pennyfarthing_scripts/jira/__pycache__/bidirectional.cpython-314.pyc +0 -0
  265. package/pennyfarthing_scripts/jira/__pycache__/claim.cpython-314.pyc +0 -0
  266. package/pennyfarthing_scripts/jira/__pycache__/cli.cpython-314.pyc +0 -0
  267. package/pennyfarthing_scripts/jira/__pycache__/client.cpython-314.pyc +0 -0
  268. package/pennyfarthing_scripts/jira/__pycache__/compat.cpython-314.pyc +0 -0
  269. package/pennyfarthing_scripts/jira/__pycache__/create.cpython-314.pyc +0 -0
  270. package/pennyfarthing_scripts/jira/__pycache__/epic.cpython-314.pyc +0 -0
  271. package/pennyfarthing_scripts/jira/__pycache__/mappings.cpython-314.pyc +0 -0
  272. package/pennyfarthing_scripts/jira/__pycache__/models.cpython-314.pyc +0 -0
  273. package/pennyfarthing_scripts/jira/__pycache__/operations.cpython-314.pyc +0 -0
  274. package/pennyfarthing_scripts/jira/__pycache__/reconcile.cpython-314.pyc +0 -0
  275. package/pennyfarthing_scripts/jira/__pycache__/story.cpython-314.pyc +0 -0
  276. package/pennyfarthing_scripts/jira/__pycache__/sync.cpython-314.pyc +0 -0
  277. package/pennyfarthing_scripts/launch/__pycache__/__init__.cpython-314.pyc +0 -0
  278. package/pennyfarthing_scripts/launch/__pycache__/cli.cpython-314.pyc +0 -0
  279. package/pennyfarthing_scripts/migration/__pycache__/__init__.cpython-314.pyc +0 -0
  280. package/pennyfarthing_scripts/migration/__pycache__/__main__.cpython-314.pyc +0 -0
  281. package/pennyfarthing_scripts/migration/__pycache__/cli.cpython-314.pyc +0 -0
  282. package/pennyfarthing_scripts/migration/__pycache__/session.cpython-314.pyc +0 -0
  283. package/pennyfarthing_scripts/migration/__pycache__/skill.cpython-314.pyc +0 -0
  284. package/pennyfarthing_scripts/migration/__pycache__/step.cpython-314.pyc +0 -0
  285. package/pennyfarthing_scripts/migration/__pycache__/validate.cpython-314.pyc +0 -0
  286. package/pennyfarthing_scripts/preflight/__pycache__/__init__.cpython-314.pyc +0 -0
  287. package/pennyfarthing_scripts/preflight/__pycache__/__main__.cpython-314.pyc +0 -0
  288. package/pennyfarthing_scripts/preflight/__pycache__/cli.cpython-314.pyc +0 -0
  289. package/pennyfarthing_scripts/preflight/__pycache__/finish.cpython-314.pyc +0 -0
  290. package/pennyfarthing_scripts/pretooluse_hook.py +3 -185
  291. package/pennyfarthing_scripts/prime/__pycache__/__init__.cpython-314.pyc +0 -0
  292. package/pennyfarthing_scripts/prime/__pycache__/__main__.cpython-314.pyc +0 -0
  293. package/pennyfarthing_scripts/prime/__pycache__/cli.cpython-314.pyc +0 -0
  294. package/pennyfarthing_scripts/prime/__pycache__/loader.cpython-314.pyc +0 -0
  295. package/pennyfarthing_scripts/prime/__pycache__/models.cpython-314.pyc +0 -0
  296. package/pennyfarthing_scripts/prime/__pycache__/persona.cpython-314.pyc +0 -0
  297. package/pennyfarthing_scripts/prime/__pycache__/session.cpython-314.pyc +0 -0
  298. package/pennyfarthing_scripts/prime/__pycache__/tiers.cpython-314.pyc +0 -0
  299. package/pennyfarthing_scripts/prime/__pycache__/version_sentinel.cpython-314.pyc +0 -0
  300. package/pennyfarthing_scripts/prime/__pycache__/workflow.cpython-314.pyc +0 -0
  301. package/pennyfarthing_scripts/prime/workflow.py +2 -1
  302. package/pennyfarthing_scripts/schema_validation_hook.py +3 -298
  303. package/pennyfarthing_scripts/session/__pycache__/__init__.cpython-314.pyc +0 -0
  304. package/pennyfarthing_scripts/session/__pycache__/cli.cpython-314.pyc +0 -0
  305. package/pennyfarthing_scripts/session_start_hook.py +4 -186
  306. package/pennyfarthing_scripts/sprint/__pycache__/__init__.cpython-314.pyc +0 -0
  307. package/pennyfarthing_scripts/sprint/__pycache__/__main__.cpython-314.pyc +0 -0
  308. package/pennyfarthing_scripts/sprint/__pycache__/archive.cpython-314.pyc +0 -0
  309. package/pennyfarthing_scripts/sprint/__pycache__/archive_epic.cpython-314.pyc +0 -0
  310. package/pennyfarthing_scripts/sprint/__pycache__/cli.cpython-314.pyc +0 -0
  311. package/pennyfarthing_scripts/sprint/__pycache__/epic_add.cpython-314.pyc +0 -0
  312. package/pennyfarthing_scripts/sprint/__pycache__/epic_update.cpython-314.pyc +0 -0
  313. package/pennyfarthing_scripts/sprint/__pycache__/import_epic.cpython-314.pyc +0 -0
  314. package/pennyfarthing_scripts/sprint/__pycache__/loader.cpython-314.pyc +0 -0
  315. package/pennyfarthing_scripts/sprint/__pycache__/status.cpython-314.pyc +0 -0
  316. package/pennyfarthing_scripts/sprint/__pycache__/story_add.cpython-314.pyc +0 -0
  317. package/pennyfarthing_scripts/sprint/__pycache__/story_finish.cpython-314.pyc +0 -0
  318. package/pennyfarthing_scripts/sprint/__pycache__/story_update.cpython-314.pyc +0 -0
  319. package/pennyfarthing_scripts/sprint/__pycache__/validate_cmd.cpython-314.pyc +0 -0
  320. package/pennyfarthing_scripts/sprint/__pycache__/validator.cpython-314.pyc +0 -0
  321. package/pennyfarthing_scripts/sprint/__pycache__/work.cpython-314.pyc +0 -0
  322. package/pennyfarthing_scripts/sprint/__pycache__/yaml_io.cpython-314.pyc +0 -0
  323. package/pennyfarthing_scripts/sprint/story_update.py +19 -0
  324. package/pennyfarthing_scripts/story/__pycache__/__init__.cpython-314.pyc +0 -0
  325. package/pennyfarthing_scripts/story/__pycache__/__main__.cpython-314.pyc +0 -0
  326. package/pennyfarthing_scripts/story/__pycache__/cli.cpython-314.pyc +0 -0
  327. package/pennyfarthing_scripts/story/__pycache__/create.cpython-314.pyc +0 -0
  328. package/pennyfarthing_scripts/story/__pycache__/size.cpython-314.pyc +0 -0
  329. package/pennyfarthing_scripts/story/__pycache__/template.cpython-314.pyc +0 -0
  330. package/pennyfarthing_scripts/tests/__pycache__/__init__.cpython-314.pyc +0 -0
  331. package/pennyfarthing_scripts/tests/__pycache__/conftest.cpython-314-pytest-9.0.2.pyc +0 -0
  332. package/pennyfarthing_scripts/tests/__pycache__/test_108_1_gate_migration.cpython-314-pytest-9.0.2.pyc +0 -0
  333. package/pennyfarthing_scripts/tests/__pycache__/test_archive_epic.cpython-314-pytest-9.0.2.pyc +0 -0
  334. package/pennyfarthing_scripts/tests/__pycache__/test_bc.cpython-314-pytest-9.0.2.pyc +0 -0
  335. package/pennyfarthing_scripts/tests/__pycache__/test_bikerack.cpython-314-pytest-9.0.2.pyc +0 -0
  336. package/pennyfarthing_scripts/tests/__pycache__/test_brownfield.cpython-314-pytest-9.0.2.pyc +0 -0
  337. package/pennyfarthing_scripts/tests/__pycache__/test_cli_modules.cpython-314-pytest-9.0.2.pyc +0 -0
  338. package/pennyfarthing_scripts/tests/__pycache__/test_cli_normalization.cpython-314-pytest-9.0.2.pyc +0 -0
  339. package/pennyfarthing_scripts/tests/__pycache__/test_codemarkers.cpython-314-pytest-9.0.2.pyc +0 -0
  340. package/pennyfarthing_scripts/tests/__pycache__/test_common.cpython-314-pytest-9.0.2.pyc +0 -0
  341. package/pennyfarthing_scripts/tests/__pycache__/test_confidence_sm_evaluation.cpython-314-pytest-9.0.2.pyc +0 -0
  342. package/pennyfarthing_scripts/tests/__pycache__/test_confidence_sm_gate.cpython-314-pytest-9.0.2.pyc +0 -0
  343. package/pennyfarthing_scripts/tests/__pycache__/test_dialogue_manager.cpython-314-pytest-9.0.2.pyc +0 -0
  344. package/pennyfarthing_scripts/tests/__pycache__/test_epic_shard_validation.cpython-314-pytest-9.0.2.pyc +0 -0
  345. package/pennyfarthing_scripts/tests/__pycache__/test_git_utils.cpython-314-pytest-9.0.2.pyc +0 -0
  346. package/pennyfarthing_scripts/tests/__pycache__/test_handoff_cli.cpython-314-pytest-9.0.2.pyc +0 -0
  347. package/pennyfarthing_scripts/tests/__pycache__/test_handoff_e2e.cpython-314-pytest-9.0.2.pyc +0 -0
  348. package/pennyfarthing_scripts/tests/__pycache__/test_healthscore.cpython-314-pytest-9.0.2.pyc +0 -0
  349. package/pennyfarthing_scripts/tests/__pycache__/test_jira_package.cpython-314-pytest-9.0.2.pyc +0 -0
  350. package/pennyfarthing_scripts/tests/__pycache__/test_package_structure.cpython-314-pytest-9.0.2.pyc +0 -0
  351. package/pennyfarthing_scripts/tests/__pycache__/test_patch_mode.cpython-314-pytest-9.0.2.pyc +0 -0
  352. package/pennyfarthing_scripts/tests/__pycache__/test_prime.cpython-314-pytest-9.0.2.pyc +0 -0
  353. package/pennyfarthing_scripts/tests/__pycache__/test_sprint_package.cpython-314-pytest-9.0.2.pyc +0 -0
  354. package/pennyfarthing_scripts/tests/__pycache__/test_sprint_panel.cpython-314-pytest-9.0.2.pyc +0 -0
  355. package/pennyfarthing_scripts/tests/__pycache__/test_sprint_validator.cpython-314-pytest-9.0.2.pyc +0 -0
  356. package/pennyfarthing_scripts/tests/__pycache__/test_story_add.cpython-314-pytest-9.0.2.pyc +0 -0
  357. package/pennyfarthing_scripts/tests/__pycache__/test_story_package.cpython-314-pytest-9.0.2.pyc +0 -0
  358. package/pennyfarthing_scripts/tests/__pycache__/test_story_update.cpython-314-pytest-9.0.2.pyc +0 -0
  359. package/pennyfarthing_scripts/tests/__pycache__/test_tiers.cpython-314-pytest-9.0.2.pyc +0 -0
  360. package/pennyfarthing_scripts/tests/__pycache__/test_token_counting.cpython-314-pytest-9.0.2.pyc +0 -0
  361. package/pennyfarthing_scripts/tests/__pycache__/test_topology_loader.cpython-314-pytest-9.0.2.pyc +0 -0
  362. package/pennyfarthing_scripts/tests/__pycache__/test_tui_focus.cpython-314-pytest-9.0.2.pyc +0 -0
  363. package/pennyfarthing_scripts/tests/__pycache__/test_tui_panel_persistence.cpython-314-pytest-9.0.2.pyc +0 -0
  364. package/pennyfarthing_scripts/tests/__pycache__/test_validate_cmd.cpython-314-pytest-9.0.2.pyc +0 -0
  365. package/pennyfarthing_scripts/tests/__pycache__/test_version_sentinel.cpython-314-pytest-9.0.2.pyc +0 -0
  366. package/pennyfarthing_scripts/tests/__pycache__/test_workflow_check.cpython-314-pytest-9.0.2.pyc +0 -0
  367. package/pennyfarthing_scripts/tests/__pycache__/test_workflow_cli.cpython-314-pytest-9.0.2.pyc +0 -0
  368. package/pennyfarthing_scripts/tests/__pycache__/test_yaml_io.cpython-314-pytest-9.0.2.pyc +0 -0
  369. package/pennyfarthing_scripts/tests/test_sprint_panel.py +344 -265
  370. package/pennyfarthing_scripts/theme/__pycache__/__init__.cpython-314.pyc +0 -0
  371. package/pennyfarthing_scripts/theme/__pycache__/cli.cpython-314.pyc +0 -0
  372. package/pennyfarthing_scripts/validate/__pycache__/__init__.cpython-314.pyc +0 -0
  373. package/pennyfarthing_scripts/validate/__pycache__/cli.cpython-314.pyc +0 -0
  374. package/pennyfarthing_scripts/validate/adapters/__pycache__/__init__.cpython-314.pyc +0 -0
  375. package/pennyfarthing_scripts/validate/adapters/__pycache__/agent.cpython-314.pyc +0 -0
  376. package/pennyfarthing_scripts/validate/adapters/__pycache__/schema.cpython-314.pyc +0 -0
  377. package/pennyfarthing_scripts/validate/adapters/__pycache__/skill_command.cpython-314.pyc +0 -0
  378. package/pennyfarthing_scripts/validate/adapters/__pycache__/sprint.cpython-314.pyc +0 -0
  379. package/pennyfarthing_scripts/validate/adapters/__pycache__/tandem_awareness.cpython-314.pyc +0 -0
  380. package/pennyfarthing_scripts/validate/adapters/__pycache__/workflow.cpython-314.pyc +0 -0
  381. package/pennyfarthing_scripts/validate/adapters/workflow.py +19 -0
  382. package/pennyfarthing_scripts/welcome_hook.py +3 -149
  383. package/pennyfarthing_scripts/workflow/__pycache__/__init__.cpython-314.pyc +0 -0
  384. package/pennyfarthing_scripts/workflow/__pycache__/cli.cpython-314.pyc +0 -0
  385. package/pennyfarthing_scripts/workflow/__pycache__/helpers.cpython-314.pyc +0 -0
  386. package/pennyfarthing_scripts/workflow/__pycache__/scale.cpython-314.pyc +0 -0
  387. package/pennyfarthing_scripts/workflow/__pycache__/state.cpython-314.pyc +0 -0
  388. package/pennyfarthing_scripts/workflow/cli.py +7 -6
  389. package/pennyfarthing_scripts/workflow/team_lifecycle.py +257 -0
  390. package/packages/core/dist/scripts/theme-detail.test.d.ts +0 -10
  391. package/packages/core/dist/scripts/theme-detail.test.js +0 -199
  392. package/pennyfarthing_scripts/bikerack/__pycache__/portrait.cpython-314.pyc +0 -0
  393. package/pennyfarthing_scripts/gate/__pycache__/__init__.cpython-314.pyc +0 -0
  394. package/pennyfarthing_scripts/gate/__pycache__/cli.cpython-314.pyc +0 -0
  395. package/pennyfarthing_scripts/gate/__pycache__/validate.cpython-314.pyc +0 -0
  396. package/pennyfarthing_scripts/git/__pycache__/hooks_installer.cpython-314.pyc +0 -0
  397. package/pennyfarthing_scripts/git/__pycache__/repos.cpython-314.pyc +0 -0
  398. package/pennyfarthing_scripts/git/__pycache__/worktree.cpython-314.pyc +0 -0
  399. package/pennyfarthing_scripts/prime/__pycache__/heatmap.cpython-314.pyc +0 -0
  400. package/pennyfarthing_scripts/tests/__pycache__/test_108_2_remove_handoff_fallback.cpython-314-pytest-9.0.2.pyc +0 -0
  401. package/pennyfarthing_scripts/tests/__pycache__/test_gate_file_resolution.cpython-314-pytest-9.0.2.pyc +0 -0
  402. package/pennyfarthing_scripts/tests/__pycache__/test_gate_runner.cpython-314-pytest-9.0.2.pyc +0 -0
  403. package/pennyfarthing_scripts/tests/__pycache__/test_resolve_gate_file_field.cpython-314-pytest-9.0.2.pyc +0 -0
@@ -0,0 +1,244 @@
1
+ """Story detail data fetching for BikeRack TUI.
2
+
3
+ Story 110-2: Fetch story detail data (AC, session, workflow) via file read or API.
4
+ Returns enriched story data for StoryDetailScreen dossier layout.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import os
10
+ import re
11
+ import subprocess
12
+ from typing import Any
13
+
14
+
15
+ def _find_project_root() -> str | None:
16
+ """Walk up from CWD looking for .pennyfarthing/ directory."""
17
+ path = os.getcwd()
18
+ while True:
19
+ if os.path.isdir(os.path.join(path, ".pennyfarthing")):
20
+ return path
21
+ parent = os.path.dirname(path)
22
+ if parent == path:
23
+ return None
24
+ path = parent
25
+
26
+
27
+ def _find_session_file(story_id: str, project_root: str | None) -> str | None:
28
+ """Locate the session file for a story, trying given root then walking up."""
29
+ filename = f"{story_id}-session.md"
30
+ if project_root:
31
+ candidate = os.path.join(project_root, ".session", filename)
32
+ if os.path.isfile(candidate):
33
+ return candidate
34
+ # Walk up from CWD looking for .session/{story_id}-session.md
35
+ path = os.getcwd()
36
+ while True:
37
+ candidate = os.path.join(path, ".session", filename)
38
+ if os.path.isfile(candidate):
39
+ return candidate
40
+ parent = os.path.dirname(path)
41
+ if parent == path:
42
+ return None
43
+ path = parent
44
+
45
+
46
+ def _parse_session_file(session_path: str) -> dict[str, Any]:
47
+ """Parse a session markdown file for metadata fields."""
48
+ result: dict[str, Any] = {}
49
+ with open(session_path) as f:
50
+ content = f.read()
51
+
52
+ # Parse title from first heading: # Story X-Y: Title
53
+ title_match = re.search(r"^# Story [\w-]+:\s*(.+)", content, re.MULTILINE)
54
+ if title_match:
55
+ result["title"] = title_match.group(1).strip()
56
+
57
+ # Parse bold metadata lines: **Key:** value
58
+ for match in re.finditer(r"\*\*(\w[\w\s]*?):\*\*\s*(.*)", content):
59
+ key = match.group(1).strip().lower()
60
+ value = match.group(2).strip()
61
+ if key == "phase":
62
+ result["workflow_phase"] = value
63
+ elif key == "workflow":
64
+ result["workflow"] = value
65
+ elif key == "branch":
66
+ result["git_branch"] = value
67
+ elif key == "jira":
68
+ result["jiraKey"] = value
69
+ elif key == "points":
70
+ try:
71
+ result["points"] = int(value)
72
+ except ValueError:
73
+ result["points"] = value
74
+ elif key == "review verdict":
75
+ result["review_verdict"] = value
76
+ elif key == "review findings":
77
+ result["review_findings"] = value
78
+
79
+ # Parse ACs from ## Acceptance Criteria section
80
+ ac_match = re.search(
81
+ r"## Acceptance Criteria\n(.*?)(?=\n##|\Z)", content, re.DOTALL
82
+ )
83
+ if ac_match:
84
+ ac_text = ac_match.group(1)
85
+ acs: list[dict[str, Any]] = []
86
+ for line in ac_text.strip().split("\n"):
87
+ line = line.strip()
88
+ if not line:
89
+ continue
90
+ # Strip leading number. or - or *
91
+ text = re.sub(r"^[\d]+\.\s*", "", line).strip()
92
+ text = re.sub(r"^[-*]\s*", "", text).strip()
93
+ if text:
94
+ acs.append({"text": text, "done": False})
95
+ if acs:
96
+ result["acceptance_criteria"] = acs
97
+
98
+ # Parse session notes from ## Session Log section
99
+ log_match = re.search(
100
+ r"## Session Log\n(.*?)(?=\n##|\Z)", content, re.DOTALL
101
+ )
102
+ if log_match:
103
+ result["session_notes"] = log_match.group(1).strip()
104
+
105
+ return result
106
+
107
+
108
+ def _get_sprint_story_status(story_id: str) -> str | None:
109
+ """Try to get story status from pf sprint CLI."""
110
+ try:
111
+ out = subprocess.run(
112
+ ["pf", "sprint", "story", "field", story_id, "status"],
113
+ capture_output=True,
114
+ text=True,
115
+ timeout=10,
116
+ )
117
+ if out.returncode == 0 and out.stdout.strip():
118
+ return out.stdout.strip()
119
+ except Exception:
120
+ pass
121
+ return None
122
+
123
+
124
+ def _get_pr_url(branch: str) -> str | None:
125
+ """Try to get PR URL for a branch via gh CLI."""
126
+ if not branch:
127
+ return None
128
+ try:
129
+ out = subprocess.run(
130
+ [
131
+ "gh",
132
+ "pr",
133
+ "list",
134
+ "--head",
135
+ branch,
136
+ "--json",
137
+ "url",
138
+ "--jq",
139
+ ".[0].url",
140
+ ],
141
+ capture_output=True,
142
+ text=True,
143
+ timeout=10,
144
+ )
145
+ if out.returncode == 0 and out.stdout.strip():
146
+ return out.stdout.strip()
147
+ except Exception:
148
+ pass
149
+ return None
150
+
151
+
152
+ def _check_context_files(story_id: str, project_root: str | None) -> dict[str, Any]:
153
+ """Check for epic and story context files.
154
+
155
+ Args:
156
+ story_id: Story identifier (e.g. "110-2").
157
+ project_root: Path to project root.
158
+
159
+ Returns:
160
+ Dict with has_epic_context, has_story_context, and their paths.
161
+ """
162
+ result: dict[str, Any] = {
163
+ "has_epic_context": False,
164
+ "has_story_context": False,
165
+ "epic_context_path": "",
166
+ "story_context_path": "",
167
+ }
168
+ if not project_root:
169
+ return result
170
+
171
+ # Extract epic number from story ID (e.g. "110-2" → "110")
172
+ parts = story_id.split("-")
173
+ if parts:
174
+ epic_num = parts[0]
175
+ epic_path = os.path.join(project_root, "sprint", "context", f"context-epic-{epic_num}.md")
176
+ if os.path.isfile(epic_path):
177
+ result["has_epic_context"] = True
178
+ result["epic_context_path"] = epic_path
179
+
180
+ story_path = os.path.join(project_root, "sprint", "context", f"context-story-{story_id}.md")
181
+ if os.path.isfile(story_path):
182
+ result["has_story_context"] = True
183
+ result["story_context_path"] = story_path
184
+
185
+ return result
186
+
187
+
188
+ def fetch_story_detail(
189
+ story_id: str,
190
+ project_root: str | None = None,
191
+ ) -> dict[str, Any]:
192
+ """Fetch detailed story data including ACs, session, workflow, and git info.
193
+
194
+ Args:
195
+ story_id: Story identifier (e.g. "110-2").
196
+ project_root: Path to project root (for file reads). Auto-detected if None.
197
+
198
+ Returns:
199
+ Dict with keys: id, title, points, status, jiraKey,
200
+ acceptance_criteria, workflow, workflow_phase,
201
+ git_branch, pr_url, session_notes.
202
+ Empty dict if story not found.
203
+ """
204
+ session_path = _find_session_file(story_id, project_root)
205
+ if not session_path:
206
+ return {}
207
+
208
+ result: dict[str, Any] = {"id": story_id}
209
+
210
+ # Parse session file (primary data source)
211
+ session_data = _parse_session_file(session_path)
212
+ result.update(session_data)
213
+
214
+ # Get status from sprint YAML if not in session
215
+ if "status" not in result:
216
+ status = _get_sprint_story_status(story_id)
217
+ if status:
218
+ result["status"] = status
219
+
220
+ # Get PR URL if we have a branch
221
+ if "pr_url" not in result:
222
+ pr_url = _get_pr_url(result.get("git_branch", ""))
223
+ result["pr_url"] = pr_url
224
+
225
+ # Check for context files
226
+ root = project_root or _find_project_root()
227
+ context_info = _check_context_files(story_id, root)
228
+ result.update(context_info)
229
+
230
+ # Ensure all required keys exist with defaults
231
+ result.setdefault("title", "")
232
+ result.setdefault("points", 0)
233
+ result.setdefault("status", "unknown")
234
+ result.setdefault("jiraKey", "")
235
+ result.setdefault("acceptance_criteria", [])
236
+ result.setdefault("workflow", "")
237
+ result.setdefault("workflow_phase", "")
238
+ result.setdefault("git_branch", "")
239
+ result.setdefault("pr_url", None)
240
+ result.setdefault("session_notes", "")
241
+ result.setdefault("review_findings", "")
242
+ result.setdefault("review_verdict", "")
243
+
244
+ return result
@@ -0,0 +1,176 @@
1
+ """StoryDetailScreen — Mission dossier detail view for BikeRack TUI.
2
+
3
+ Story 110-2: Story drill-through with dossier detail screen.
4
+ Pushed via Screen.push() from SprintPanel when user presses Enter on a story.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import webbrowser
10
+ from typing import Any
11
+
12
+ from rich.text import Text
13
+ from textual.binding import Binding
14
+ from textual.screen import Screen
15
+ from textual.widgets import Static
16
+
17
+
18
+ class StoryDetailScreen(Screen):
19
+ """Detail screen showing story dossier layout.
20
+
21
+ Displays AC checklist, workflow phase dots, git branch, PR link,
22
+ and session notes in a mission-dossier layout.
23
+ """
24
+
25
+ BINDINGS = [
26
+ Binding("escape", "pop_screen", "Back"),
27
+ Binding("enter", "open_pr_link", "Open PR"),
28
+ ]
29
+
30
+ def __init__(self, story_data: dict[str, Any] | None = None, **kwargs: Any) -> None:
31
+ super().__init__(**kwargs)
32
+ ws_data: dict[str, Any] = story_data or {}
33
+ self._story_data = self._enrich(ws_data)
34
+
35
+ @staticmethod
36
+ def _enrich(ws_data: dict[str, Any]) -> dict[str, Any]:
37
+ """Merge WS data with file-based enrichment. WS wins for non-null fields."""
38
+ story_id = ws_data.get("id", "")
39
+ if not story_id:
40
+ return ws_data
41
+
42
+ try:
43
+ from pennyfarthing_scripts.bikerack.story_detail_data import fetch_story_detail
44
+
45
+ enriched = fetch_story_detail(story_id)
46
+ except Exception:
47
+ return ws_data
48
+
49
+ if not enriched:
50
+ return ws_data
51
+
52
+ # Merge: enriched is base, WS data overwrites non-null values
53
+ merged = {**enriched}
54
+ for key, val in ws_data.items():
55
+ if val is not None and val != "" and val != []:
56
+ merged[key] = val
57
+ return merged
58
+
59
+ def compose(self):
60
+ """Compose the dossier layout with header, ACs, workflow, git info.
61
+
62
+ Absent sections are simply omitted (Tufte: absence is information).
63
+ """
64
+ data = self._story_data
65
+
66
+ # Header section
67
+ title = data.get("title", "Unknown Story")
68
+ points = data.get("points", "?")
69
+ status = data.get("status", "unknown")
70
+ jira_key = data.get("jiraKey", "")
71
+ assignee = data.get("assignee", "")
72
+ priority = data.get("priority", "")
73
+
74
+ header_text = Text()
75
+ if jira_key:
76
+ header_text.append(f"{jira_key}", style="bold cyan")
77
+ header_text.append(" ")
78
+ header_text.append(f"{title}", style="bold")
79
+ # Metadata line
80
+ meta_parts = [status]
81
+ if points and points != "?":
82
+ meta_parts.append(f"{points} pts")
83
+ if priority:
84
+ meta_parts.append(priority)
85
+ if assignee:
86
+ from pennyfarthing_scripts.bikerack.sprint_panel import _format_assignee
87
+
88
+ name = _format_assignee(assignee)
89
+ if name:
90
+ meta_parts.append(name)
91
+ header_text.append(f"\n{' \u00b7 '.join(meta_parts)}", style="dim")
92
+ yield Static(header_text, id="dossier-header")
93
+
94
+ # Context section (only if context files exist)
95
+ has_epic_ctx = data.get("has_epic_context", False)
96
+ has_story_ctx = data.get("has_story_context", False)
97
+ if has_epic_ctx or has_story_ctx:
98
+ ctx_text = Text()
99
+ ctx_text.append("Context\n", style="bold underline")
100
+ if has_epic_ctx:
101
+ ctx_text.append(" \u2713 Epic context\n", style="green")
102
+ else:
103
+ ctx_text.append(" \u25cb Epic context\n", style="dim")
104
+ if has_story_ctx:
105
+ ctx_text.append(" \u2713 Story context\n", style="green")
106
+ else:
107
+ ctx_text.append(" \u25cb Story context\n", style="dim")
108
+ yield Static(ctx_text, id="dossier-context")
109
+
110
+ # Acceptance Criteria section
111
+ acs = data.get("acceptance_criteria", [])
112
+ if acs:
113
+ ac_text = Text()
114
+ done_count = sum(1 for ac in acs if ac.get("done"))
115
+ ac_text.append("Acceptance Criteria", style="bold underline")
116
+ ac_text.append(f" {done_count}/{len(acs)}\n", style="dim")
117
+ for ac in acs:
118
+ check = "\u2713" if ac.get("done") else "\u25cb"
119
+ style = "green" if ac.get("done") else ""
120
+ ac_text.append(f" {check} {ac.get('text', '')}\n", style=style)
121
+ yield Static(ac_text, id="dossier-ac")
122
+
123
+ # Workflow section
124
+ workflow = data.get("workflow", "")
125
+ phase = data.get("workflow_phase", "")
126
+ if workflow:
127
+ wf_text = Text()
128
+ wf_text.append("Workflow\n", style="bold underline")
129
+ wf_text.append(f" {workflow}", style="cyan")
130
+ if phase:
131
+ wf_text.append(f" Phase: {phase}", style="yellow")
132
+ wf_text.append("\n")
133
+ yield Static(wf_text, id="dossier-workflow")
134
+
135
+ # Git info section
136
+ branch = data.get("git_branch", "")
137
+ pr_url = data.get("pr_url")
138
+ if branch or pr_url:
139
+ git_text = Text()
140
+ git_text.append("Git\n", style="bold underline")
141
+ if branch:
142
+ git_text.append(f" Branch: {branch}\n")
143
+ if pr_url:
144
+ git_text.append(f" PR: {pr_url}\n", style="cyan")
145
+ yield Static(git_text, id="dossier-git")
146
+
147
+ # Session notes section
148
+ notes = data.get("session_notes", "")
149
+ if notes:
150
+ notes_text = Text()
151
+ notes_text.append("Session Notes\n", style="bold underline")
152
+ notes_text.append(f" {notes}\n")
153
+ yield Static(notes_text, id="dossier-notes")
154
+
155
+ # Keybinding hint
156
+ hint = Text.from_markup(
157
+ "\n[dim][Escape] Back [Enter] Open PR[/dim]"
158
+ )
159
+ yield Static(hint, id="dossier-hint")
160
+
161
+ def action_pop_screen(self) -> None:
162
+ """Pop this screen off the stack."""
163
+ self.dismiss()
164
+
165
+ def get_pr_url(self) -> str | None:
166
+ """Get the PR URL from story data."""
167
+ url = self._story_data.get("pr_url")
168
+ return url if url else None
169
+
170
+ def action_open_pr_link(self) -> bool:
171
+ """Open PR link in browser via webbrowser.open()."""
172
+ url = self.get_pr_url()
173
+ if not url:
174
+ return False
175
+ webbrowser.open(url)
176
+ return True