@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
@@ -1,309 +1,25 @@
1
- #!/usr/bin/env python3
2
1
  """
3
- PostToolUse HookBell Mode + Tandem Injection (Python)
2
+ Backward-compatibility shimbell mode hook moved to hooks/bell_mode.py.
4
3
 
5
- Called by Claude Code after each tool execution. Handles two independent
6
- injection systems:
7
-
8
- 1. Bell queue (Cyclist only) — injects queued user messages when Cyclist
9
- is running and bell_mode is enabled. In CLI sessions this is a no-op.
10
- 2. Tandem observations (always active) — injects backseat agent observations
11
- when tandem observation files exist. No configuration required.
12
-
13
- Bell queue takes precedence: if a queued message exists, tandem is
14
- deferred to the next hook invocation.
15
-
16
- Story: MSSCI-12409 - Hook consistency and WheelHub consolidation
4
+ This file will be removed in a future version.
17
5
  """
18
6
 
19
- import json
20
- import re
21
- import sys
22
- from pathlib import Path
23
-
24
- # Add parent directory to path for imports
25
- sys.path.insert(0, str(Path(__file__).parent))
26
-
27
- from hooks import (
28
- CYCLIST_PORT_FILE,
29
- HookResponse,
30
- find_project_root,
31
- is_bell_mode_enabled,
32
- output_hook_response,
33
- read_port_file,
34
- send_to_cyclist,
7
+ # Re-export public API from new location for existing tests/imports
8
+ from pennyfarthing_scripts.hooks.bell_mode import ( # noqa: F401
9
+ _check_tandem_files as check_tandem_files,
10
+ _get_latest_observation as get_latest_observation,
11
+ _get_tandem_mtime as get_tandem_mtime,
12
+ _read_bell_queue as read_bell_queue,
13
+ _read_tandem_observations as read_tandem_observations,
14
+ _save_tandem_mtime as save_tandem_mtime,
15
+ main,
35
16
  )
36
17
 
37
18
 
38
- def read_bell_queue(project_root: Path) -> list[dict]:
39
- """Read the bell message queue.
40
-
41
- Args:
42
- project_root: Project root directory
43
-
44
- Returns:
45
- List of queued messages, or empty list if none
46
- """
47
- queue_path = project_root / ".pennyfarthing" / "bell-queue.json"
48
- if not queue_path.exists():
49
- return []
50
-
51
- try:
52
- with open(queue_path) as f:
53
- queue = json.load(f)
54
- if isinstance(queue, list):
55
- return queue
56
- except (json.JSONDecodeError, OSError):
57
- pass
58
-
59
- return []
60
-
61
-
62
- def dequeue_message(project_root: Path) -> None:
63
- """Remove the first message from the queue.
64
-
65
- Args:
66
- project_root: Project root directory
67
- """
68
- queue_path = project_root / ".pennyfarthing" / "bell-queue.json"
69
- if not queue_path.exists():
70
- return
71
-
72
- try:
73
- with open(queue_path) as f:
74
- queue = json.load(f)
75
-
76
- if isinstance(queue, list) and len(queue) > 0:
77
- queue = queue[1:] # Remove first item
78
- with open(queue_path, "w") as f:
79
- json.dump(queue, f)
80
- except (json.JSONDecodeError, OSError):
81
- pass
82
-
83
-
84
- def notify_cyclist(project_root: Path, message_text: str) -> None:
85
- """Notify Cyclist browser that a queued message was consumed.
86
-
87
- Args:
88
- project_root: Project root directory
89
- message_text: The message text that was consumed
90
- """
91
- try:
92
- send_to_cyclist(
93
- endpoint="/api/bell-consumed",
94
- data={"text": message_text},
95
- project_root=project_root,
96
- timeout=5,
97
- )
98
- except Exception:
99
- # Ignore errors - don't block hook
100
- pass
101
-
102
-
103
- # =============================================================================
104
- # Tandem Observation Injection (Story 95-7 / MSSCI-14672)
105
- # =============================================================================
106
- # Stubs for tandem observation injection. Dev will implement.
107
-
108
-
109
- def read_tandem_observations(project_root: Path) -> list[Path]:
110
- """Find tandem observation files in .session/ directory.
111
-
112
- Looks for files matching .session/*-tandem-*.md pattern.
113
-
114
- Args:
115
- project_root: Project root directory
116
-
117
- Returns:
118
- List of Path objects for tandem observation files
119
- """
120
- session_dir = project_root / ".session"
121
- if not session_dir.is_dir():
122
- return []
123
- return sorted(session_dir.glob("*-tandem-*.md"))
124
-
125
-
126
- def get_latest_observation(file_content: str) -> dict | None:
127
- """Extract the latest observation entry from a tandem observation file.
128
-
129
- Parses the markdown format to extract the most recent ## [HH:MM] Observation
130
- block, including the observer persona name from the file header.
131
-
132
- Args:
133
- file_content: Full text content of the tandem observation file
134
-
135
- Returns:
136
- Dict with 'persona' and 'text' keys, or None if no observations found
137
- """
138
- # Extract persona from header: **Observer:** agent (Persona Name)
139
- persona_match = re.search(r"\*\*Observer:\*\*\s*\w+\s*\(([^)]+)\)", file_content)
140
- persona = persona_match.group(1) if persona_match else "Unknown"
141
-
142
- # Split on observation headers: ## [HH:MM] Observation
143
- entries = re.split(r"## \[\d{1,2}:\d{2}\] Observation\n", file_content)
144
- if len(entries) < 2:
145
- return None
146
-
147
- # Last entry is the most recent observation
148
- last_entry = entries[-1].strip()
149
- # Remove trailing --- separator
150
- last_entry = re.sub(r"\n---\s*$", "", last_entry).strip()
151
- # Remove the **Trigger:** line
152
- lines = last_entry.split("\n")
153
- text_lines = [line for line in lines if not line.startswith("**Trigger:**")]
154
- text = "\n".join(text_lines).strip()
155
-
156
- if not text:
157
- return None
158
-
159
- return {"persona": persona, "text": text}
160
-
161
-
162
19
  def format_tandem_message(persona_name: str, observation_text: str) -> str:
163
- """Format a tandem observation as a bell mode injection message.
164
-
165
- Args:
166
- persona_name: The backseat agent's persona name
167
- observation_text: The observation summary text
168
-
169
- Returns:
170
- Formatted string: [Tandem] {persona_name}: {observation_text}
171
- """
20
+ """Format a tandem observation as a bell mode injection message."""
172
21
  return f"[Tandem] {persona_name}: {observation_text}"
173
22
 
174
23
 
175
- def get_tandem_mtime(project_root: Path, agent: str) -> float:
176
- """Read the last-checked mtime for a tandem agent's observation file.
177
-
178
- Args:
179
- project_root: Project root directory
180
- agent: Agent name (e.g. 'reviewer', 'tea')
181
-
182
- Returns:
183
- Last-checked mtime as float, or 0.0 if no sidecar exists
184
- """
185
- sidecar = project_root / ".session" / f".tandem-mtime-{agent}"
186
- if not sidecar.exists():
187
- return 0.0
188
- try:
189
- return float(sidecar.read_text().strip())
190
- except (ValueError, OSError):
191
- return 0.0
192
-
193
-
194
- def save_tandem_mtime(project_root: Path, agent: str, mtime: float) -> None:
195
- """Save the mtime for a tandem agent's observation file.
196
-
197
- Writes to .session/.tandem-mtime-{agent} sidecar file.
198
-
199
- Args:
200
- project_root: Project root directory
201
- agent: Agent name (e.g. 'reviewer', 'tea')
202
- mtime: The mtime value to save
203
- """
204
- sidecar = project_root / ".session" / f".tandem-mtime-{agent}"
205
- try:
206
- sidecar.write_text(str(mtime))
207
- except OSError:
208
- pass
209
-
210
-
211
- def check_tandem_files(project_root: Path) -> list[dict]:
212
- """Check for new tandem observations and return injection messages.
213
-
214
- Main entry point for tandem injection in the PostToolUse hook.
215
- Always active — no configuration required. The presence of tandem
216
- observation files in .session/ is the only signal needed.
217
-
218
- Args:
219
- project_root: Project root directory
220
-
221
- Returns:
222
- List of dicts with 'agent', 'message' keys for each new observation
223
- """
224
- tandem_files = read_tandem_observations(project_root)
225
- if not tandem_files:
226
- return []
227
-
228
- results = []
229
- for obs_file in tandem_files:
230
- # Extract agent name from filename: *-tandem-{agent}.md
231
- agent_match = re.search(r"-tandem-(\w+)\.md$", obs_file.name)
232
- if not agent_match:
233
- continue
234
- agent = agent_match.group(1)
235
-
236
- # Compare mtime
237
- try:
238
- file_mtime = obs_file.stat().st_mtime
239
- except OSError:
240
- continue
241
- saved_mtime = get_tandem_mtime(project_root, agent)
242
- if file_mtime == saved_mtime:
243
- continue
244
-
245
- # Read and parse
246
- try:
247
- content = obs_file.read_text()
248
- except OSError:
249
- continue
250
- obs = get_latest_observation(content)
251
- if not obs:
252
- # Update mtime even for unparseable files to avoid re-checking
253
- save_tandem_mtime(project_root, agent, file_mtime)
254
- continue
255
-
256
- message = format_tandem_message(obs["persona"], obs["text"])
257
- results.append({"agent": agent, "message": message})
258
-
259
- # Update mtime sidecar
260
- save_tandem_mtime(project_root, agent, file_mtime)
261
-
262
- return results
263
-
264
-
265
- def main() -> None:
266
- """Main entry point for PostToolUse hook."""
267
- try:
268
- # Read and discard stdin (required by hook protocol)
269
- sys.stdin.read()
270
-
271
- # Find project root
272
- project_root = find_project_root()
273
- if not project_root:
274
- sys.exit(0)
275
-
276
- # --- Bell queue (Cyclist only, requires bell_mode: true) ---
277
- # Takes precedence over tandem when active.
278
- is_cyclist = read_port_file(CYCLIST_PORT_FILE, project_root) is not None
279
- if is_cyclist and is_bell_mode_enabled(project_root):
280
- queue = read_bell_queue(project_root)
281
- if queue:
282
- first_message = queue[0]
283
- message_text = first_message.get("text", "")
284
- if message_text:
285
- output_hook_response(HookResponse(
286
- event_name="PostToolUse",
287
- additional_context=f"User feedback: {message_text}",
288
- ))
289
- dequeue_message(project_root)
290
- notify_cyclist(project_root, message_text)
291
- sys.exit(0)
292
-
293
- # --- Tandem observations (always active, no config required) ---
294
- tandem_results = check_tandem_files(project_root)
295
- if tandem_results:
296
- output_hook_response(HookResponse(
297
- event_name="PostToolUse",
298
- additional_context=tandem_results[0]["message"],
299
- ))
300
-
301
- sys.exit(0)
302
-
303
- except Exception as e:
304
- print(f"[bellmode-hook] Error: {e}", file=sys.stderr)
305
- sys.exit(0)
306
-
307
-
308
24
  if __name__ == "__main__":
309
25
  main()
@@ -0,0 +1,119 @@
1
+ """AuditLogPanel — Real-time tool event audit log for BikeRack TUI.
2
+
3
+ Story 110-8: Subscribes to /ws/spans, displays tool events in a native
4
+ Textual DataTable with timestamp, tool name, input excerpt, and result.
5
+
6
+ Uses native Textual widgets (DataTable) instead of Rich renderables.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from datetime import datetime, timezone
12
+ from typing import Any
13
+
14
+ from rich.console import Console
15
+ from textual._context import NoActiveAppError
16
+ from textual.widgets import DataTable
17
+
18
+ from pennyfarthing_scripts.bikerack.base_panel import PANEL_ICONS, BasePanel
19
+
20
+ MAX_INPUT_LENGTH = 80
21
+
22
+ # Shared fallback console for offline column/cell measurement
23
+ _FALLBACK_CONSOLE = Console()
24
+
25
+
26
+ class _OfflineDataTable(DataTable):
27
+ """DataTable subclass that works without an active Textual app.
28
+
29
+ Textual's DataTable.add_columns/add_row need self.app.console for
30
+ column width measurement. This subclass provides a fallback Rich Console
31
+ so the table can be constructed and populated in unit tests.
32
+ """
33
+
34
+ @property
35
+ def app(self):
36
+ try:
37
+ return super().app
38
+ except NoActiveAppError:
39
+ return type("_Stub", (), {"console": _FALLBACK_CONSOLE})()
40
+
41
+
42
+ class AuditLogPanel(BasePanel):
43
+ """Audit log panel — real-time tool event display via DataTable.
44
+
45
+ Subscribes to the 'spans' WebSocket channel and renders tool events
46
+ in a native Textual DataTable with columns: Time, Tool, Input, Result.
47
+ """
48
+
49
+ channel: str = "spans"
50
+ panel_name: str = "Audit Log"
51
+ icon: str = PANEL_ICONS["audit-log"][0]
52
+
53
+ def __init__(self, client=None, **kwargs):
54
+ super().__init__(client=client, **kwargs)
55
+ self._table = _OfflineDataTable()
56
+ self._table.add_columns("Time", "Tool", "Input", "Result")
57
+
58
+ def handle_message(self, message: dict[str, Any] | None) -> None:
59
+ """Handle incoming WebSocket messages for tool events.
60
+
61
+ Overrides BasePanel.handle_message to manipulate DataTable directly
62
+ instead of going through render_panel/post_message.
63
+ """
64
+ if not self._mounted or message is None:
65
+ return
66
+ if not isinstance(message, dict):
67
+ return
68
+
69
+ msg_type = message.get("type")
70
+
71
+ if msg_type == "init":
72
+ spans = message.get("spans")
73
+ if not isinstance(spans, list):
74
+ return
75
+ self._table.clear()
76
+ for span in spans:
77
+ if isinstance(span, dict):
78
+ self._add_row(span)
79
+
80
+ elif msg_type == "span":
81
+ span = message.get("span")
82
+ if isinstance(span, dict):
83
+ self._add_row(span)
84
+
85
+ def _add_row(self, span: dict[str, Any]) -> None:
86
+ """Add a single tool event row to the DataTable."""
87
+ timestamp = span.get("timestamp")
88
+ tool_name = span.get("toolName", "")
89
+ input_text = span.get("input", "")
90
+ success = span.get("success")
91
+
92
+ time_str = _format_timestamp(timestamp)
93
+
94
+ if len(input_text) > MAX_INPUT_LENGTH:
95
+ input_text = input_text[: MAX_INPUT_LENGTH - 1] + "\u2026"
96
+
97
+ if success is True:
98
+ result = "\u2713"
99
+ elif success is False:
100
+ result = "\u2717"
101
+ else:
102
+ result = "\u2014"
103
+
104
+ self._table.add_row(time_str, tool_name, input_text, result)
105
+
106
+ def render_panel(self, payload: dict[str, Any]) -> Any:
107
+ """Not used — handle_message manages DataTable directly."""
108
+ return self._table
109
+
110
+
111
+ def _format_timestamp(ts: Any) -> str:
112
+ """Format millisecond timestamp to HH:MM:SS."""
113
+ if ts is None:
114
+ return "\u2014"
115
+ try:
116
+ dt = datetime.fromtimestamp(float(ts) / 1000, tz=timezone.utc)
117
+ return dt.strftime("%H:%M:%S")
118
+ except (ValueError, TypeError, OSError):
119
+ return "\u2014"
@@ -10,6 +10,7 @@ from __future__ import annotations
10
10
  from typing import Any
11
11
 
12
12
  from rich.text import Text
13
+ from textual.message import Message
13
14
  from textual.widgets import Static
14
15
 
15
16
  # Nerd Font icon registry: panel_name → (nerd_font_icon, ascii_fallback)
@@ -46,7 +47,12 @@ def get_panel_icon(panel_name: str, use_nerd_font: bool = True) -> str:
46
47
  return entry[0] if use_nerd_font else entry[1]
47
48
 
48
49
 
49
- def render_progress_bar(percent: int | float, width: int = 20, warn_high: bool = False) -> Text:
50
+ def render_progress_bar(
51
+ percent: int | float,
52
+ width: int = 20,
53
+ warn_high: bool = False,
54
+ fill_style: str | None = None,
55
+ ) -> Text:
50
56
  """Render a Unicode progress bar with color based on percentage.
51
57
 
52
58
  Args:
@@ -54,6 +60,7 @@ def render_progress_bar(percent: int | float, width: int = 20, warn_high: bool =
54
60
  width: Number of bar characters (default 20).
55
61
  warn_high: If True, use red at high values (for resource usage).
56
62
  If False (default), use blue at 100% (for completion).
63
+ fill_style: Override the computed fill color (e.g. ``"dim green"``).
57
64
 
58
65
  Returns:
59
66
  Rich Text like ``[████████░░░░░░░░░░░░] 22%``
@@ -62,7 +69,9 @@ def render_progress_bar(percent: int | float, width: int = 20, warn_high: bool =
62
69
  filled = round(width * percent / 100)
63
70
  empty = width - filled
64
71
 
65
- if warn_high:
72
+ if fill_style is not None:
73
+ style = fill_style
74
+ elif warn_high:
66
75
  if percent < 50:
67
76
  style = "green"
68
77
  elif percent <= 80:
@@ -113,6 +122,13 @@ class BasePanel(Static):
113
122
  ``render_panel(payload)`` to return a Rich renderable.
114
123
  """
115
124
 
125
+ class DataReceived(Message, bubble=False):
126
+ """WebSocket data received — routed through Textual message system."""
127
+
128
+ def __init__(self, content: Any) -> None:
129
+ super().__init__()
130
+ self.content = content
131
+
116
132
  #: WebSocket channel this panel subscribes to (override in subclass)
117
133
  channel: str = ""
118
134
 
@@ -138,10 +154,17 @@ class BasePanel(Static):
138
154
  """Mark panel as unmounted — messages ignored after this."""
139
155
  self._mounted = False
140
156
 
157
+ def on_base_panel_data_received(self, event: DataReceived) -> None:
158
+ """Process DataReceived in Textual message context — triggers repaint."""
159
+ self.update(event.content)
160
+
141
161
  def handle_message(self, message: dict[str, Any] | None) -> None:
142
162
  """Handle incoming WebSocket message.
143
163
 
144
- Stores payload, calls render_panel, updates widget display.
164
+ Stores payload, calls render_panel, posts DataReceived message.
165
+ Uses post_message to route through Textual's message system,
166
+ ensuring proper repaint cycle (call_from_thread / direct update
167
+ from async WS tasks does not reliably trigger redraws).
145
168
  No-op after unmount or if message is None.
146
169
  """
147
170
  if not self._mounted or message is None:
@@ -149,7 +172,7 @@ class BasePanel(Static):
149
172
  self._last_payload = message
150
173
  rendered = self.render_panel(message)
151
174
  try:
152
- self.update(rendered)
175
+ self.post_message(self.DataReceived(rendered))
153
176
  except Exception:
154
177
  pass
155
178