@pennyfarthing/core 11.2.0 → 11.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (367) hide show
  1. package/README.md +100 -40
  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 +474 -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 +7 -0
  16. package/packages/core/dist/cli/utils/settings.d.ts.map +1 -1
  17. package/packages/core/dist/cli/utils/settings.js +70 -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/dialogue-manager.d.ts +1 -1
  22. package/packages/core/dist/consultation/dialogue-manager.d.ts.map +1 -1
  23. package/packages/core/dist/consultation/dialogue-manager.js +1 -1
  24. package/packages/core/dist/consultation/dialogue-manager.js.map +1 -1
  25. package/packages/core/dist/consultation/dialogue-manager.test.js.map +1 -1
  26. package/packages/core/dist/consultation/tandem-metrics.d.ts +91 -0
  27. package/packages/core/dist/consultation/tandem-metrics.d.ts.map +1 -0
  28. package/packages/core/dist/consultation/tandem-metrics.js +131 -0
  29. package/packages/core/dist/consultation/tandem-metrics.js.map +1 -0
  30. package/packages/core/dist/consultation/tandem-metrics.test.d.ts +18 -0
  31. package/packages/core/dist/consultation/tandem-metrics.test.d.ts.map +1 -0
  32. package/packages/core/dist/consultation/tandem-metrics.test.js +457 -0
  33. package/packages/core/dist/consultation/tandem-metrics.test.js.map +1 -0
  34. package/packages/core/dist/public/css/react.css +1 -1
  35. package/packages/core/dist/public/js/react/react.js +14 -14
  36. package/packages/core/dist/server/api/agent-load.js +1 -1
  37. package/packages/core/dist/server/api/agent-load.js.map +1 -1
  38. package/packages/core/dist/server/api/git.d.ts.map +1 -1
  39. package/packages/core/dist/server/api/git.js +0 -1
  40. package/packages/core/dist/server/api/git.js.map +1 -1
  41. package/packages/core/dist/server/api/index.d.ts +2 -0
  42. package/packages/core/dist/server/api/index.d.ts.map +1 -1
  43. package/packages/core/dist/server/api/index.js +2 -0
  44. package/packages/core/dist/server/api/index.js.map +1 -1
  45. package/packages/core/dist/server/api/project-info.d.ts +11 -0
  46. package/packages/core/dist/server/api/project-info.d.ts.map +1 -0
  47. package/packages/core/dist/server/api/project-info.js +18 -0
  48. package/packages/core/dist/server/api/project-info.js.map +1 -0
  49. package/packages/core/dist/server/otlp-receiver.d.ts.map +1 -1
  50. package/packages/core/dist/server/otlp-receiver.js +18 -1
  51. package/packages/core/dist/server/otlp-receiver.js.map +1 -1
  52. package/packages/core/dist/server/otlp-receiver.test.js +1 -1
  53. package/packages/core/dist/server/otlp-receiver.test.js.map +1 -1
  54. package/packages/core/dist/server/server.d.ts +0 -3
  55. package/packages/core/dist/server/server.d.ts.map +1 -1
  56. package/packages/core/dist/server/server.js +5 -38
  57. package/packages/core/dist/server/server.js.map +1 -1
  58. package/packages/core/dist/server/server.test.d.ts +1 -1
  59. package/packages/core/dist/server/server.test.js +12 -23
  60. package/packages/core/dist/server/server.test.js.map +1 -1
  61. package/packages/core/dist/server/settings.d.ts +1 -0
  62. package/packages/core/dist/server/settings.d.ts.map +1 -1
  63. package/packages/core/dist/server/settings.js +13 -0
  64. package/packages/core/dist/server/settings.js.map +1 -1
  65. package/packages/core/dist/shared/capabilities.d.ts +88 -0
  66. package/packages/core/dist/shared/capabilities.d.ts.map +1 -0
  67. package/packages/core/dist/shared/capabilities.js +133 -0
  68. package/packages/core/dist/shared/capabilities.js.map +1 -0
  69. package/packages/core/dist/shared/capabilities.test.d.ts +2 -0
  70. package/packages/core/dist/shared/capabilities.test.d.ts.map +1 -0
  71. package/packages/core/dist/shared/capabilities.test.js +217 -0
  72. package/packages/core/dist/shared/capabilities.test.js.map +1 -0
  73. package/packages/core/dist/shared/spawn-prompt.d.ts +47 -0
  74. package/packages/core/dist/shared/spawn-prompt.d.ts.map +1 -0
  75. package/packages/core/dist/shared/spawn-prompt.js +82 -0
  76. package/packages/core/dist/shared/spawn-prompt.js.map +1 -0
  77. package/packages/core/dist/shared/spawn-prompt.test.d.ts +2 -0
  78. package/packages/core/dist/shared/spawn-prompt.test.d.ts.map +1 -0
  79. package/packages/core/dist/shared/spawn-prompt.test.js +251 -0
  80. package/packages/core/dist/shared/spawn-prompt.test.js.map +1 -0
  81. package/packages/core/dist/workflow/tandem-workflow-templates.test.d.ts +18 -0
  82. package/packages/core/dist/workflow/tandem-workflow-templates.test.d.ts.map +1 -0
  83. package/packages/core/dist/workflow/tandem-workflow-templates.test.js +434 -0
  84. package/packages/core/dist/workflow/tandem-workflow-templates.test.js.map +1 -0
  85. package/packages/core/dist/workflow/team-lifecycle.d.ts +169 -0
  86. package/packages/core/dist/workflow/team-lifecycle.d.ts.map +1 -0
  87. package/packages/core/dist/workflow/team-lifecycle.js +217 -0
  88. package/packages/core/dist/workflow/team-lifecycle.js.map +1 -0
  89. package/packages/core/dist/workflow/team-lifecycle.test.d.ts +20 -0
  90. package/packages/core/dist/workflow/team-lifecycle.test.d.ts.map +1 -0
  91. package/packages/core/dist/workflow/team-lifecycle.test.js +966 -0
  92. package/packages/core/dist/workflow/team-lifecycle.test.js.map +1 -0
  93. package/packages/core/dist/workflow/workflow-schema.d.ts +32 -0
  94. package/packages/core/dist/workflow/workflow-schema.d.ts.map +1 -1
  95. package/packages/core/dist/workflow/workflow-schema.js +120 -0
  96. package/packages/core/dist/workflow/workflow-schema.js.map +1 -1
  97. package/packages/core/dist/workflow/workflow-schema.test.d.ts.map +1 -1
  98. package/packages/core/dist/workflow/workflow-schema.test.js +570 -1
  99. package/packages/core/dist/workflow/workflow-schema.test.js.map +1 -1
  100. package/packages/core/dist/workflow/workflow-team-templates.test.d.ts +17 -0
  101. package/packages/core/dist/workflow/workflow-team-templates.test.d.ts.map +1 -0
  102. package/packages/core/dist/workflow/workflow-team-templates.test.js +275 -0
  103. package/packages/core/dist/workflow/workflow-team-templates.test.js.map +1 -0
  104. package/pennyfarthing-dist/agents/dev.md +21 -12
  105. package/pennyfarthing-dist/agents/reviewer.md +23 -4
  106. package/pennyfarthing-dist/agents/sm-finish.md +19 -2
  107. package/pennyfarthing-dist/agents/sm-setup.md +7 -7
  108. package/pennyfarthing-dist/agents/sm.md +12 -12
  109. package/pennyfarthing-dist/agents/tea.md +2 -2
  110. package/pennyfarthing-dist/agents/testing-runner.md +1 -1
  111. package/pennyfarthing-dist/commands/pf-architect.md +1 -1
  112. package/pennyfarthing-dist/commands/pf-ba.md +1 -1
  113. package/pennyfarthing-dist/commands/pf-chore.md +2 -2
  114. package/pennyfarthing-dist/commands/pf-dev.md +1 -1
  115. package/pennyfarthing-dist/commands/pf-devops.md +1 -1
  116. package/pennyfarthing-dist/commands/pf-epic.md +6 -6
  117. package/pennyfarthing-dist/commands/pf-git.md +12 -10
  118. package/pennyfarthing-dist/commands/pf-health-check.md +1 -1
  119. package/pennyfarthing-dist/commands/pf-help.md +12 -12
  120. package/pennyfarthing-dist/commands/pf-orchestrator.md +1 -1
  121. package/pennyfarthing-dist/commands/pf-pm.md +1 -1
  122. package/pennyfarthing-dist/commands/pf-prime.md +8 -8
  123. package/pennyfarthing-dist/commands/pf-reviewer.md +1 -1
  124. package/pennyfarthing-dist/commands/pf-session.md +7 -7
  125. package/pennyfarthing-dist/commands/pf-sm.md +1 -1
  126. package/pennyfarthing-dist/commands/pf-sprint.md +7 -7
  127. package/pennyfarthing-dist/commands/pf-tea.md +1 -1
  128. package/pennyfarthing-dist/commands/pf-tech-writer.md +1 -1
  129. package/pennyfarthing-dist/commands/pf-theme.md +9 -9
  130. package/pennyfarthing-dist/commands/pf-ux-designer.md +1 -1
  131. package/pennyfarthing-dist/commands/pf-work.md +1 -1
  132. package/pennyfarthing-dist/gates/approval.md +63 -0
  133. package/pennyfarthing-dist/gates/confidence-sm.md +71 -0
  134. package/pennyfarthing-dist/gates/context-ok.md +56 -0
  135. package/pennyfarthing-dist/gates/evaluations/confidence-sm.md +54 -0
  136. package/pennyfarthing-dist/gates/quality-pass.md +67 -0
  137. package/pennyfarthing-dist/gates/tests-fail.md +84 -0
  138. package/pennyfarthing-dist/gates/tests-pass.md +79 -0
  139. package/pennyfarthing-dist/guides/agent-behavior.md +84 -29
  140. package/pennyfarthing-dist/guides/agent-coordination.md +10 -10
  141. package/pennyfarthing-dist/guides/agent-tag-taxonomy.md +6 -6
  142. package/pennyfarthing-dist/guides/agent-template-tactical.md +1 -1
  143. package/pennyfarthing-dist/guides/bell-mode.md +1 -1
  144. package/pennyfarthing-dist/guides/bikerack.md +10 -10
  145. package/pennyfarthing-dist/guides/brownfield-tools.md +24 -24
  146. package/pennyfarthing-dist/guides/command-tag-taxonomy.md +1 -1
  147. package/pennyfarthing-dist/guides/gate-schema.md +2 -2
  148. package/pennyfarthing-dist/guides/gates.md +3 -3
  149. package/pennyfarthing-dist/guides/handoff-cli.md +8 -8
  150. package/pennyfarthing-dist/guides/hooks.md +29 -29
  151. package/pennyfarthing-dist/guides/prime.md +2 -2
  152. package/pennyfarthing-dist/guides/reflector.md +1 -1
  153. package/pennyfarthing-dist/guides/skill-schema.md +6 -6
  154. package/pennyfarthing-dist/guides/tandem-protocol.md +3 -3
  155. package/pennyfarthing-dist/guides/workflow-schema.md +1 -1
  156. package/pennyfarthing-dist/guides/worktree-mode.md +3 -3
  157. package/pennyfarthing-dist/guides/xml-tags.md +8 -8
  158. package/pennyfarthing-dist/scripts/README.md +4 -4
  159. package/pennyfarthing-dist/scripts/core/agent-session.sh +2 -5
  160. package/pennyfarthing-dist/scripts/core/check-context.sh +3 -1
  161. package/pennyfarthing-dist/scripts/core/pf.sh +5 -0
  162. package/pennyfarthing-dist/scripts/core/phase-check-start.sh +4 -89
  163. package/pennyfarthing-dist/scripts/core/prime.sh +2 -25
  164. package/pennyfarthing-dist/scripts/git/README.md +14 -14
  165. package/pennyfarthing-dist/scripts/git/create-feature-branches.sh +2 -3
  166. package/pennyfarthing-dist/scripts/git/git-status-all.sh +2 -3
  167. package/pennyfarthing-dist/scripts/git/install-git-hooks.sh +2 -3
  168. package/pennyfarthing-dist/scripts/git/worktree-manager.sh +2 -4
  169. package/pennyfarthing-dist/scripts/hooks/README.md +6 -6
  170. package/pennyfarthing-dist/scripts/hooks/bell-mode-hook.sh +4 -183
  171. package/pennyfarthing-dist/scripts/hooks/context-circuit-breaker.sh +4 -95
  172. package/pennyfarthing-dist/scripts/hooks/context-warning.sh +4 -65
  173. package/pennyfarthing-dist/scripts/hooks/cyclist-pretooluse-hook.sh +3 -31
  174. package/pennyfarthing-dist/scripts/hooks/otel-auto-config.sh +5 -4
  175. package/pennyfarthing-dist/scripts/hooks/pre-commit.sh +29 -34
  176. package/pennyfarthing-dist/scripts/hooks/pre-edit-check.sh +4 -71
  177. package/pennyfarthing-dist/scripts/hooks/question-reflector-check.sh +3 -19
  178. package/pennyfarthing-dist/scripts/hooks/schema-validation.sh +4 -30
  179. package/pennyfarthing-dist/scripts/hooks/session-start.sh +3 -32
  180. package/pennyfarthing-dist/scripts/hooks/session-stop.sh +4 -65
  181. package/pennyfarthing-dist/scripts/hooks/sprint-yaml-validation.sh +4 -78
  182. package/pennyfarthing-dist/scripts/hooks/welcome-hook.sh +3 -93
  183. package/pennyfarthing-dist/scripts/lib/env.sh +34 -0
  184. package/pennyfarthing-dist/scripts/lib/run-pf.sh +39 -0
  185. package/pennyfarthing-dist/scripts/misc/README.md +1 -1
  186. package/pennyfarthing-dist/scripts/misc/statusline.sh +4 -301
  187. package/pennyfarthing-dist/scripts/sprint/README.md +21 -21
  188. package/pennyfarthing-dist/scripts/workflow/README.md +2 -2
  189. package/pennyfarthing-dist/scripts/workflow/finish-story.sh +2 -16
  190. package/pennyfarthing-dist/scripts/workflow/fix-session-phase.sh +3 -3
  191. package/pennyfarthing-dist/scripts/workflow/get-workflow-type.sh +3 -3
  192. package/pennyfarthing-dist/scripts/workflow/list-workflows.sh +3 -3
  193. package/pennyfarthing-dist/scripts/workflow/phase-owner.sh +3 -3
  194. package/pennyfarthing-dist/scripts/workflow/resume-workflow.sh +3 -3
  195. package/pennyfarthing-dist/scripts/workflow/show-workflow.sh +3 -3
  196. package/pennyfarthing-dist/scripts/workflow/start-workflow.sh +3 -3
  197. package/pennyfarthing-dist/scripts/workflow/workflow-status.sh +3 -3
  198. package/pennyfarthing-dist/skills/pf-bc/examples.md +23 -23
  199. package/pennyfarthing-dist/skills/pf-bc/skill.md +17 -17
  200. package/pennyfarthing-dist/skills/pf-bc/usage.md +8 -8
  201. package/pennyfarthing-dist/skills/pf-jira/SKILL.md +15 -15
  202. package/pennyfarthing-dist/skills/pf-jira/examples.md +48 -48
  203. package/pennyfarthing-dist/skills/pf-jira/usage.md +15 -15
  204. package/pennyfarthing-dist/skills/pf-sprint/examples.md +80 -80
  205. package/pennyfarthing-dist/skills/pf-sprint/skill.md +35 -35
  206. package/pennyfarthing-dist/skills/pf-sprint/usage.md +30 -30
  207. package/pennyfarthing-dist/skills/pf-theme/examples.md +15 -15
  208. package/pennyfarthing-dist/skills/pf-theme/skill.md +6 -6
  209. package/pennyfarthing-dist/skills/pf-theme/usage.md +5 -5
  210. package/pennyfarthing-dist/skills/pf-workflow/examples.md +27 -27
  211. package/pennyfarthing-dist/skills/pf-workflow/skill.md +11 -11
  212. package/pennyfarthing-dist/skills/pf-workflow/usage.md +11 -11
  213. package/pennyfarthing-dist/skills/skill-registry.yaml +19 -19
  214. package/pennyfarthing-dist/templates/settings.local.json.template +19 -10
  215. package/pennyfarthing-dist/workflows/bdd-team.yaml +89 -0
  216. package/pennyfarthing-dist/workflows/epics-and-stories/steps/step-05-import-to-future.md +1 -1
  217. package/pennyfarthing-dist/workflows/git-cleanup/steps/step-01-analyze.md +1 -1
  218. package/pennyfarthing-dist/workflows/git-cleanup/steps/step-04-verify.md +1 -1
  219. package/pennyfarthing-dist/workflows/git-cleanup/steps/step-05-complete.md +1 -1
  220. package/pennyfarthing-dist/workflows/project-setup/steps/step-01-discover.md +47 -0
  221. package/pennyfarthing-dist/workflows/tdd-team.yaml +80 -0
  222. package/pennyfarthing-dist/workflows/tdd.yaml +11 -2
  223. package/pennyfarthing_scripts/CLAUDE.md +19 -10
  224. package/pennyfarthing_scripts/__init__.py +1 -1
  225. package/pennyfarthing_scripts/__pycache__/__init__.cpython-314.pyc +0 -0
  226. package/pennyfarthing_scripts/__pycache__/bellmode_hook.cpython-314.pyc +0 -0
  227. package/pennyfarthing_scripts/__pycache__/cli.cpython-314.pyc +0 -0
  228. package/pennyfarthing_scripts/__pycache__/context.cpython-314.pyc +0 -0
  229. package/pennyfarthing_scripts/__pycache__/hooks.cpython-314.pyc +0 -0
  230. package/pennyfarthing_scripts/__pycache__/pretooluse_hook.cpython-314.pyc +0 -0
  231. package/pennyfarthing_scripts/__pycache__/schema_validation_hook.cpython-314.pyc +0 -0
  232. package/pennyfarthing_scripts/__pycache__/session_start_hook.cpython-314.pyc +0 -0
  233. package/pennyfarthing_scripts/bc/__pycache__/cli.cpython-314.pyc +0 -0
  234. package/pennyfarthing_scripts/bc/__pycache__/focus.cpython-314.pyc +0 -0
  235. package/pennyfarthing_scripts/bc/__pycache__/split.cpython-314.pyc +0 -0
  236. package/pennyfarthing_scripts/bc/cli.py +2 -2
  237. package/pennyfarthing_scripts/bellmode_hook.py +9 -296
  238. package/pennyfarthing_scripts/bikerack/__pycache__/audit_log_panel.cpython-314.pyc +0 -0
  239. package/pennyfarthing_scripts/bikerack/__pycache__/background_panel.cpython-314.pyc +0 -0
  240. package/pennyfarthing_scripts/bikerack/__pycache__/base_panel.cpython-314.pyc +0 -0
  241. package/pennyfarthing_scripts/bikerack/__pycache__/changed_panel.cpython-314.pyc +0 -0
  242. package/pennyfarthing_scripts/bikerack/__pycache__/context_meter_footer.cpython-314.pyc +0 -0
  243. package/pennyfarthing_scripts/bikerack/__pycache__/debug_panel.cpython-314.pyc +0 -0
  244. package/pennyfarthing_scripts/bikerack/__pycache__/diffs_panel.cpython-314.pyc +0 -0
  245. package/pennyfarthing_scripts/bikerack/__pycache__/events.cpython-314.pyc +0 -0
  246. package/pennyfarthing_scripts/bikerack/__pycache__/git_panel.cpython-314.pyc +0 -0
  247. package/pennyfarthing_scripts/bikerack/__pycache__/launcher.cpython-314.pyc +0 -0
  248. package/pennyfarthing_scripts/bikerack/__pycache__/portrait_resolver.cpython-314.pyc +0 -0
  249. package/pennyfarthing_scripts/bikerack/__pycache__/progress_panel.cpython-314.pyc +0 -0
  250. package/pennyfarthing_scripts/bikerack/__pycache__/sprint_panel.cpython-314.pyc +0 -0
  251. package/pennyfarthing_scripts/bikerack/__pycache__/story_detail_data.cpython-314.pyc +0 -0
  252. package/pennyfarthing_scripts/bikerack/__pycache__/story_detail_screen.cpython-314.pyc +0 -0
  253. package/pennyfarthing_scripts/bikerack/__pycache__/tui.cpython-314.pyc +0 -0
  254. package/pennyfarthing_scripts/bikerack/__pycache__/ws_client.cpython-314.pyc +0 -0
  255. package/pennyfarthing_scripts/bikerack/audit_log_panel.py +161 -0
  256. package/pennyfarthing_scripts/bikerack/base_panel.py +27 -4
  257. package/pennyfarthing_scripts/bikerack/changed_panel.py +96 -4
  258. package/pennyfarthing_scripts/bikerack/context_meter_footer.py +88 -0
  259. package/pennyfarthing_scripts/bikerack/debug_panel.py +1 -1
  260. package/pennyfarthing_scripts/bikerack/diffs_panel.py +30 -0
  261. package/pennyfarthing_scripts/bikerack/events.py +28 -0
  262. package/pennyfarthing_scripts/bikerack/launcher.py +6 -6
  263. package/pennyfarthing_scripts/bikerack/portrait_resolver.py +139 -0
  264. package/pennyfarthing_scripts/bikerack/progress_panel.py +0 -1
  265. package/pennyfarthing_scripts/bikerack/sprint_panel.py +373 -142
  266. package/pennyfarthing_scripts/bikerack/story_detail_data.py +247 -0
  267. package/pennyfarthing_scripts/bikerack/story_detail_screen.py +177 -0
  268. package/pennyfarthing_scripts/bikerack/tui.py +304 -62
  269. package/pennyfarthing_scripts/bikerack/ws_client.py +2 -2
  270. package/pennyfarthing_scripts/cli.py +5 -0
  271. package/pennyfarthing_scripts/common/__pycache__/config.cpython-314.pyc +0 -0
  272. package/pennyfarthing_scripts/common/config.py +29 -2
  273. package/pennyfarthing_scripts/common/pr_config.py +38 -0
  274. package/pennyfarthing_scripts/consultation/__pycache__/__init__.cpython-314.pyc +0 -0
  275. package/pennyfarthing_scripts/consultation/__pycache__/cli.cpython-314.pyc +0 -0
  276. package/pennyfarthing_scripts/consultation/cli.py +3 -3
  277. package/pennyfarthing_scripts/context.py +3 -3
  278. package/pennyfarthing_scripts/git/__pycache__/__init__.cpython-314.pyc +0 -0
  279. package/pennyfarthing_scripts/git/__pycache__/create_branches.cpython-314.pyc +0 -0
  280. package/pennyfarthing_scripts/git/__pycache__/repos.cpython-314.pyc +0 -0
  281. package/pennyfarthing_scripts/git/__pycache__/status_all.cpython-314.pyc +0 -0
  282. package/pennyfarthing_scripts/git/hooks_installer.py +2 -3
  283. package/pennyfarthing_scripts/git/status_all.py +1 -1
  284. package/pennyfarthing_scripts/git/worktree.py +2 -2
  285. package/pennyfarthing_scripts/git_group/__pycache__/cli.cpython-314.pyc +0 -0
  286. package/pennyfarthing_scripts/handoff/__pycache__/cli.cpython-314.pyc +0 -0
  287. package/pennyfarthing_scripts/handoff/__pycache__/complete_phase.cpython-314.pyc +0 -0
  288. package/pennyfarthing_scripts/handoff/__pycache__/marker.cpython-314.pyc +0 -0
  289. package/pennyfarthing_scripts/handoff/__pycache__/phase_check.cpython-314.pyc +0 -0
  290. package/pennyfarthing_scripts/handoff/__pycache__/resolve_gate.cpython-314.pyc +0 -0
  291. package/pennyfarthing_scripts/handoff/cli.py +33 -1
  292. package/pennyfarthing_scripts/handoff/complete_phase.py +28 -0
  293. package/pennyfarthing_scripts/handoff/marker.py +15 -15
  294. package/pennyfarthing_scripts/handoff/phase_check.py +96 -0
  295. package/pennyfarthing_scripts/handoff/resolve_gate.py +13 -1
  296. package/pennyfarthing_scripts/hooks/__init__.py +442 -0
  297. package/pennyfarthing_scripts/hooks/__pycache__/__init__.cpython-314.pyc +0 -0
  298. package/pennyfarthing_scripts/hooks/__pycache__/bell_mode.cpython-314.pyc +0 -0
  299. package/pennyfarthing_scripts/hooks/__pycache__/cli.cpython-314.pyc +0 -0
  300. package/pennyfarthing_scripts/hooks/__pycache__/context_breaker.cpython-314.pyc +0 -0
  301. package/pennyfarthing_scripts/hooks/__pycache__/context_warning.cpython-314.pyc +0 -0
  302. package/pennyfarthing_scripts/hooks/__pycache__/cyclist_pretooluse.cpython-314.pyc +0 -0
  303. package/pennyfarthing_scripts/hooks/__pycache__/pre_edit_check.cpython-314.pyc +0 -0
  304. package/pennyfarthing_scripts/hooks/__pycache__/reflector_check.cpython-314.pyc +0 -0
  305. package/pennyfarthing_scripts/hooks/__pycache__/schema_validation.cpython-314.pyc +0 -0
  306. package/pennyfarthing_scripts/hooks/__pycache__/session_start.cpython-314.pyc +0 -0
  307. package/pennyfarthing_scripts/hooks/__pycache__/session_stop.cpython-314.pyc +0 -0
  308. package/pennyfarthing_scripts/hooks/__pycache__/sprint_yaml_validation.cpython-314.pyc +0 -0
  309. package/pennyfarthing_scripts/hooks/__pycache__/statusline.cpython-314.pyc +0 -0
  310. package/pennyfarthing_scripts/hooks/bell_mode.py +214 -0
  311. package/pennyfarthing_scripts/hooks/cli.py +96 -0
  312. package/pennyfarthing_scripts/hooks/context_breaker.py +104 -0
  313. package/pennyfarthing_scripts/hooks/context_warning.py +66 -0
  314. package/pennyfarthing_scripts/hooks/cyclist_pretooluse.py +129 -0
  315. package/pennyfarthing_scripts/hooks/pre_edit_check.py +77 -0
  316. package/pennyfarthing_scripts/hooks/reflector_check.py +270 -0
  317. package/pennyfarthing_scripts/hooks/schema_validation.py +202 -0
  318. package/pennyfarthing_scripts/hooks/session_start.py +294 -0
  319. package/pennyfarthing_scripts/hooks/session_stop.py +111 -0
  320. package/pennyfarthing_scripts/hooks/sprint_yaml_validation.py +97 -0
  321. package/pennyfarthing_scripts/hooks/statusline.py +429 -0
  322. package/pennyfarthing_scripts/hooks.py +27 -432
  323. package/pennyfarthing_scripts/pretooluse_hook.py +3 -185
  324. package/pennyfarthing_scripts/prime/__pycache__/workflow.cpython-314.pyc +0 -0
  325. package/pennyfarthing_scripts/prime/heatmap.py +3 -15
  326. package/pennyfarthing_scripts/prime/workflow.py +2 -1
  327. package/pennyfarthing_scripts/schema_validation_hook.py +3 -298
  328. package/pennyfarthing_scripts/session_start_hook.py +4 -186
  329. package/pennyfarthing_scripts/sprint/__pycache__/cli.cpython-314.pyc +0 -0
  330. package/pennyfarthing_scripts/sprint/__pycache__/loader.cpython-314.pyc +0 -0
  331. package/pennyfarthing_scripts/sprint/__pycache__/story_finish.cpython-314.pyc +0 -0
  332. package/pennyfarthing_scripts/sprint/__pycache__/story_update.cpython-314.pyc +0 -0
  333. package/pennyfarthing_scripts/sprint/cli.py +121 -0
  334. package/pennyfarthing_scripts/sprint/loader.py +154 -3
  335. package/pennyfarthing_scripts/sprint/story_update.py +26 -0
  336. package/pennyfarthing_scripts/tests/__pycache__/test_bikerack.cpython-314-pytest-9.0.2.pyc +0 -0
  337. package/pennyfarthing_scripts/tests/__pycache__/test_handoff_cli.cpython-314-pytest-9.0.2.pyc +0 -0
  338. package/pennyfarthing_scripts/tests/__pycache__/test_workflow_list_team.cpython-314-pytest-9.0.2.pyc +0 -0
  339. package/pennyfarthing_scripts/tests/test_bikerack.py +26 -26
  340. package/pennyfarthing_scripts/tests/test_dialogue_manager.py +0 -1
  341. package/pennyfarthing_scripts/tests/test_sprint_panel.py +344 -265
  342. package/pennyfarthing_scripts/tests/test_workflow_list_team.py +147 -0
  343. package/pennyfarthing_scripts/validate/__pycache__/cli.cpython-314.pyc +0 -0
  344. package/pennyfarthing_scripts/validate/adapters/__pycache__/skill_command.cpython-314.pyc +0 -0
  345. package/pennyfarthing_scripts/validate/adapters/__pycache__/tandem_awareness.cpython-314.pyc +0 -0
  346. package/pennyfarthing_scripts/validate/adapters/__pycache__/team_mode.cpython-314.pyc +0 -0
  347. package/pennyfarthing_scripts/validate/adapters/__pycache__/workflow.cpython-314.pyc +0 -0
  348. package/pennyfarthing_scripts/validate/adapters/team_mode.py +323 -0
  349. package/pennyfarthing_scripts/validate/adapters/workflow.py +19 -0
  350. package/pennyfarthing_scripts/welcome_hook.py +3 -149
  351. package/pennyfarthing_scripts/workflow/__pycache__/__init__.cpython-314.pyc +0 -0
  352. package/pennyfarthing_scripts/workflow/__pycache__/cli.cpython-314.pyc +0 -0
  353. package/pennyfarthing_scripts/workflow/__pycache__/helpers.cpython-314.pyc +0 -0
  354. package/pennyfarthing_scripts/workflow/__pycache__/scale.cpython-314.pyc +0 -0
  355. package/pennyfarthing_scripts/workflow/__pycache__/state.cpython-314.pyc +0 -0
  356. package/pennyfarthing_scripts/workflow/__pycache__/team_lifecycle.cpython-314.pyc +0 -0
  357. package/pennyfarthing_scripts/workflow/cli.py +22 -20
  358. package/pennyfarthing_scripts/workflow/state.py +0 -1
  359. package/pennyfarthing_scripts/workflow/team_lifecycle.py +256 -0
  360. package/packages/core/dist/cli/cyclist-migration.test.d.ts +0 -16
  361. package/packages/core/dist/cli/cyclist-migration.test.d.ts.map +0 -1
  362. package/packages/core/dist/cli/cyclist-migration.test.js +0 -229
  363. package/packages/core/dist/cli/cyclist-migration.test.js.map +0 -1
  364. package/packages/core/dist/scripts/theme-detail.test.d.ts +0 -10
  365. package/packages/core/dist/scripts/theme-detail.test.d.ts.map +0 -1
  366. package/packages/core/dist/scripts/theme-detail.test.js +0 -199
  367. package/packages/core/dist/scripts/theme-detail.test.js.map +0 -1
@@ -1,437 +1,32 @@
1
1
  """
2
- Shared utilities for Pennyfarthing Claude Code hooks.
2
+ Backward-compatibility shim hooks utilities moved to hooks/ subpackage.
3
3
 
4
- Provides common functionality for all hooks:
5
- - Project root detection
6
- - Port file discovery
7
- - Settings loading (relay_mode, permission_mode)
8
- - Context state checking
9
- - HTTP communication with Cyclist
4
+ All exports are re-imported from pennyfarthing_scripts.hooks (the package).
5
+ Existing code that does `from hooks import ...` or
6
+ `from pennyfarthing_scripts.hooks import ...` continues to work.
10
7
 
11
- All hooks should import from this module for consistency.
12
-
13
- Story: MSSCI-12409 - Hook consistency and relay mode compatibility
8
+ This file will be removed in a future version.
14
9
  """
15
10
 
16
- import json
17
- import os
18
- import sys
19
- import urllib.error
20
- import urllib.request
21
- from dataclasses import dataclass
22
- from pathlib import Path
23
- from typing import Any
24
-
25
- import yaml
26
-
27
- # =============================================================================
28
- # Port File Constants
29
- # =============================================================================
30
-
31
- # WheelHub port file - central coordination server for all communication
32
- # Per ADR-0004: "the hub where all communication converges"
33
- CYCLIST_PORT_FILE = ".wheelhub-port"
34
-
35
- # Default port if file not found
36
- DEFAULT_CYCLIST_PORT = 7431
37
-
38
- # HTTP timeout for Cyclist communication
39
- HTTP_TIMEOUT_SECONDS = 120
40
-
41
-
42
- # =============================================================================
43
- # Project Root Detection
44
- # =============================================================================
45
-
46
-
47
- def find_project_root(start_dir: Path | None = None) -> Path | None:
48
- """Find the project root by looking for marker files.
49
-
50
- Searches for (in order):
51
- 1. .wheelhub-port (WheelHub is running)
52
- 2. .pennyfarthing directory
53
- 3. .claude directory
54
-
55
- Args:
56
- start_dir: Directory to start search from (defaults to cwd)
57
-
58
- Returns:
59
- Path to project root, or None if not found
60
- """
61
- current = Path(start_dir) if start_dir else Path.cwd()
62
- current = current.resolve()
63
-
64
- while current != current.parent:
65
- # Check for Cyclist port files first (indicates Cyclist is running)
66
- if (current / CYCLIST_PORT_FILE).exists():
67
- return current
68
- # Fall back to directory markers
69
- if (current / ".pennyfarthing").is_dir():
70
- return current
71
- if (current / ".claude").is_dir():
72
- return current
73
- current = current.parent
74
-
75
- return None
76
-
77
-
78
- # =============================================================================
79
- # Port File Reading
80
- # =============================================================================
81
-
82
-
83
- def read_port_file(file_name: str, project_root: Path | None = None) -> int | None:
84
- """Read a port number from a Cyclist port file.
85
-
86
- Args:
87
- file_name: Name of the port file (e.g. .wheelhub-port)
88
- project_root: Project root directory (auto-detected if not provided)
89
-
90
- Returns:
91
- Port number, or None if file not found or invalid
92
- """
93
- root = project_root or find_project_root()
94
- if not root:
95
- return None
96
-
97
- port_file = root / file_name
98
- if not port_file.exists():
99
- return None
100
-
101
- try:
102
- content = port_file.read_text().strip()
103
- port = int(content)
104
- if 0 < port < 65536:
105
- return port
106
- except (ValueError, OSError):
107
- pass
108
-
109
- return None
110
-
111
-
112
- def get_cyclist_port(project_root: Path | None = None) -> int:
113
- """Get the WheelHub server port.
114
-
115
- WheelHub is the central coordination server for all Cyclist communication,
116
- including hook requests, OTEL, REST APIs, and WebSocket.
117
-
118
- Args:
119
- project_root: Project root directory (auto-detected if not provided)
120
-
121
- Returns:
122
- Port number (default if file not found)
123
- """
124
- port = read_port_file(CYCLIST_PORT_FILE, project_root)
125
- if port:
126
- return port
127
-
128
- return DEFAULT_CYCLIST_PORT
129
-
130
-
131
- # =============================================================================
132
- # Settings Loading
133
- # =============================================================================
134
-
135
-
136
- @dataclass
137
- class CyclistSettings:
138
- """Cyclist workflow settings from config.local.yaml."""
139
-
140
- permission_mode: str = "manual" # plan, manual, accept
141
- relay_mode: bool = False
142
- bell_mode: bool = False
143
- theme: str | None = None
144
-
145
-
146
- def load_settings(project_root: Path | None = None) -> CyclistSettings:
147
- """Load Cyclist settings from .pennyfarthing/config.local.yaml.
148
-
149
- Handles legacy setting migrations:
150
- - permission_mode: 'turbo' -> 'accept' + relay_mode: True
151
- - handoff_mode: 'auto' -> relay_mode: True
152
- - auto_handoff: True -> relay_mode: True
153
-
154
- Args:
155
- project_root: Project root directory (auto-detected if not provided)
156
-
157
- Returns:
158
- CyclistSettings with current configuration
159
- """
160
- settings = CyclistSettings()
161
-
162
- root = project_root or find_project_root()
163
- if not root:
164
- return settings
165
-
166
- config_path = root / ".pennyfarthing" / "config.local.yaml"
167
- if not config_path.exists():
168
- return settings
169
-
170
- try:
171
- with open(config_path) as f:
172
- config = yaml.safe_load(f) or {}
173
- except (OSError, yaml.YAMLError):
174
- return settings
175
-
176
- # Extract theme
177
- settings.theme = config.get("theme")
178
-
179
- # Extract workflow settings
180
- workflow = config.get("workflow", {})
181
- if not isinstance(workflow, dict):
182
- return settings
183
-
184
- # Handle permission_mode
185
- mode = workflow.get("permission_mode", "manual")
186
- if mode == "turbo":
187
- # Migrate turbo -> accept + relay_mode
188
- settings.permission_mode = "accept"
189
- settings.relay_mode = True
190
- elif mode in ("plan", "manual", "accept"):
191
- settings.permission_mode = mode
192
- else:
193
- settings.permission_mode = "manual"
194
-
195
- # Handle explicit relay_mode (overrides migration)
196
- if "relay_mode" in workflow and isinstance(workflow["relay_mode"], bool):
197
- settings.relay_mode = workflow["relay_mode"]
198
- elif not settings.relay_mode:
199
- # Check legacy settings
200
- if workflow.get("handoff_mode") == "auto":
201
- settings.relay_mode = True
202
- elif workflow.get("auto_handoff") is True:
203
- settings.relay_mode = True
204
-
205
- # Handle bell_mode
206
- if "bell_mode" in workflow and isinstance(workflow["bell_mode"], bool):
207
- settings.bell_mode = workflow["bell_mode"]
208
-
209
- return settings
210
-
211
-
212
- def is_relay_mode_enabled(project_root: Path | None = None) -> bool:
213
- """Check if relay mode (auto-handoff) is enabled.
214
-
215
- Args:
216
- project_root: Project root directory (auto-detected if not provided)
217
-
218
- Returns:
219
- True if relay mode is enabled
220
- """
221
- return load_settings(project_root).relay_mode
222
-
223
-
224
- def is_bell_mode_enabled(project_root: Path | None = None) -> bool:
225
- """Check if bell mode is enabled.
226
-
227
- Args:
228
- project_root: Project root directory (auto-detected if not provided)
229
-
230
- Returns:
231
- True if bell mode is enabled
232
- """
233
- return load_settings(project_root).bell_mode
234
-
235
-
236
- # =============================================================================
237
- # Context State
238
- # =============================================================================
239
-
240
-
241
- @dataclass
242
- class ContextState:
243
- """Current context usage state."""
244
-
245
- used_tokens: int = 0
246
- max_tokens: int = 200000
247
- percentage: float = 0.0
248
- is_high: bool = False # > 60%
249
- is_critical: bool = False # > 80%
250
-
251
-
252
- def get_context_state(project_root: Path | None = None) -> ContextState:
253
- """Get current context usage from Cyclist API.
254
-
255
- Calls Cyclist's /api/context endpoint which runs check-context.sh.
256
-
257
- Args:
258
- project_root: Project root directory (auto-detected if not provided)
259
-
260
- Returns:
261
- ContextState with current usage (defaults if Cyclist not running)
262
- """
263
- state = ContextState()
264
-
265
- port = get_cyclist_port(project_root)
266
- url = f"http://127.0.0.1:{port}/api/context"
267
-
268
- try:
269
- with urllib.request.urlopen(url, timeout=5) as response:
270
- data = json.loads(response.read().decode())
271
- state.used_tokens = data.get("used_tokens", 0)
272
- state.max_tokens = data.get("max_tokens", 200000)
273
- if state.max_tokens > 0:
274
- state.percentage = (state.used_tokens / state.max_tokens) * 100
275
- state.is_high = state.percentage > 60
276
- state.is_critical = state.percentage > 80
277
- except (urllib.error.URLError, json.JSONDecodeError, OSError):
278
- # Cyclist not running or error - return defaults
279
- pass
280
-
281
- return state
282
-
283
-
284
- # =============================================================================
285
- # Cyclist HTTP Communication
286
- # =============================================================================
287
-
288
-
289
- def send_to_cyclist(
290
- endpoint: str,
291
- data: dict[str, Any],
292
- port: int | None = None,
293
- project_root: Path | None = None,
294
- timeout: int = HTTP_TIMEOUT_SECONDS,
295
- ) -> dict[str, Any] | None:
296
- """Send a POST request to WheelHub (Cyclist's central coordination server).
297
-
298
- All endpoints go through WheelHub per ADR-0004.
299
-
300
- Args:
301
- endpoint: API endpoint path (e.g., "/api/hook-request")
302
- data: JSON data to send
303
- port: Port to use (auto-detected if not provided)
304
- project_root: Project root for port discovery
305
- timeout: Request timeout in seconds
306
-
307
- Returns:
308
- Response JSON as dict, or None on error
309
- """
310
- if port is None:
311
- port = get_cyclist_port(project_root)
312
-
313
- url = f"http://127.0.0.1:{port}{endpoint}"
314
- json_data = json.dumps(data).encode("utf-8")
315
-
316
- request = urllib.request.Request(
317
- url,
318
- data=json_data,
319
- headers={"Content-Type": "application/json"},
320
- method="POST",
321
- )
322
-
323
- try:
324
- with urllib.request.urlopen(request, timeout=timeout) as response:
325
- return json.loads(response.read().decode())
326
- except urllib.error.URLError as e:
327
- # Connection refused means Cyclist isn't running
328
- if "Connection refused" in str(e):
329
- return None
330
- raise
331
- except (json.JSONDecodeError, OSError):
332
- return None
333
-
334
-
335
- # =============================================================================
336
- # Hook Response Formatting
337
- # =============================================================================
338
-
339
-
340
- @dataclass
341
- class HookResponse:
342
- """Standard hook response for Claude Code."""
343
-
344
- event_name: str
345
- decision: str | None = None # allow, deny, ask (for PreToolUse)
346
- reason: str | None = None
347
- updated_input: dict[str, Any] | None = None
348
- additional_context: str | None = None # For PostToolUse context injection
349
-
350
- def to_json(self) -> str:
351
- """Format as Claude Code hook JSON output."""
352
- output: dict[str, Any] = {
353
- "hookSpecificOutput": {
354
- "hookEventName": self.event_name,
355
- }
356
- }
357
-
358
- hook_output = output["hookSpecificOutput"]
359
-
360
- if self.decision:
361
- hook_output["permissionDecision"] = self.decision
362
- if self.reason:
363
- hook_output["permissionDecisionReason"] = self.reason
364
- if self.updated_input:
365
- hook_output["updatedInput"] = self.updated_input
366
- if self.additional_context:
367
- hook_output["additionalContext"] = self.additional_context
368
-
369
- return json.dumps(output)
370
-
371
-
372
- def output_hook_response(response: HookResponse) -> None:
373
- """Output hook response to stdout for Claude Code."""
374
- print(response.to_json())
375
-
376
-
377
- def read_stdin_json() -> dict[str, Any]:
378
- """Read JSON from stdin (hook input from Claude Code).
379
-
380
- Returns:
381
- Parsed JSON as dict
382
-
383
- Raises:
384
- ValueError: If input is not valid JSON
385
- """
386
- data = sys.stdin.read()
387
- try:
388
- return json.loads(data)
389
- except json.JSONDecodeError as e:
390
- raise ValueError(f"Invalid JSON input: {e}") from e
391
-
392
-
393
- # =============================================================================
394
- # Hook Execution Utilities
395
- # =============================================================================
396
-
397
-
398
- def is_cyclist_running(project_root: Path | None = None) -> bool:
399
- """Check if Cyclist server is running.
400
-
401
- Checks the CYCLIST environment variable set by ClaudeService when
402
- spawning Claude inside Cyclist. No file I/O, no HTTP, no signals —
403
- this runs on every tool invocation and must be instant.
404
-
405
- The project_root parameter is kept for backward compatibility but
406
- is no longer used.
407
-
408
- Returns:
409
- True if running inside a Cyclist-spawned Claude process
410
- """
411
- return os.environ.get("CYCLIST") == "1"
412
-
413
-
414
- def should_auto_approve(settings: CyclistSettings) -> bool:
415
- """Check if requests should be auto-approved based on settings.
416
-
417
- Auto-approve when permission_mode is 'accept' (formerly turbo).
418
-
419
- Args:
420
- settings: Current Cyclist settings
421
-
422
- Returns:
423
- True if auto-approval is enabled
424
- """
425
- return settings.permission_mode == "accept"
426
-
427
-
428
- def should_auto_handoff(settings: CyclistSettings) -> bool:
429
- """Check if handoffs should be automatic based on settings.
430
-
431
- Args:
432
- settings: Current Cyclist settings
433
-
434
- Returns:
435
- True if relay_mode is enabled
436
- """
437
- return settings.relay_mode
11
+ # Re-export everything from the hooks package
12
+ from pennyfarthing_scripts.hooks import ( # noqa: F401
13
+ CYCLIST_PORT_FILE,
14
+ DEFAULT_CYCLIST_PORT,
15
+ HTTP_TIMEOUT_SECONDS,
16
+ ContextState,
17
+ CyclistSettings,
18
+ HookResponse,
19
+ find_project_root,
20
+ get_context_state,
21
+ get_cyclist_port,
22
+ is_bell_mode_enabled,
23
+ is_cyclist_running,
24
+ is_relay_mode_enabled,
25
+ load_settings,
26
+ output_hook_response,
27
+ read_port_file,
28
+ read_stdin_json,
29
+ send_to_cyclist,
30
+ should_auto_approve,
31
+ should_auto_handoff,
32
+ )
@@ -1,192 +1,10 @@
1
- #!/usr/bin/env python3
2
1
  """
3
- Cyclist PreToolUse Hook (Python)
2
+ Backward-compatibility shim pretooluse hook moved to hooks/cyclist_pretooluse.py.
4
3
 
5
- This script is called by Claude Code before each tool execution.
6
- It communicates with WheelHub (Cyclist's central coordination server)
7
- via HTTP to get approval decisions.
8
-
9
- Flow:
10
- 1. Claude Code calls this script with tool info via stdin (JSON)
11
- 2. Script reads port from .wheelhub-port in project directory
12
- 3. Script sends request to WheelHub's /api/hook-request endpoint
13
- 4. WheelHub shows approval modal, user decides
14
- 5. Script receives response, outputs JSON decision to stdout
15
- 6. Claude Code proceeds or blocks based on decision
16
-
17
- Per ADR-0004: All communication converges through WheelHub.
18
-
19
- Story: MSSCI-12409 - Hook consistency and WheelHub consolidation
20
-
21
- Usage:
22
- Install in ~/.claude/settings.json or project .claude/settings.json:
23
- {
24
- "hooks": {
25
- "PreToolUse": [{
26
- "matcher": "Bash",
27
- "hooks": [{
28
- "type": "command",
29
- "command": "python3 /path/to/pretooluse_hook.py"
30
- }]
31
- }]
32
- }
33
- }
4
+ This file will be removed in a future version.
34
5
  """
35
6
 
36
- import sys
37
- from pathlib import Path
38
-
39
- # Add parent directory to path for imports
40
- sys.path.insert(0, str(Path(__file__).parent))
41
-
42
- from hooks import (
43
- HookResponse,
44
- find_project_root,
45
- get_context_state,
46
- is_cyclist_running,
47
- load_settings,
48
- output_hook_response,
49
- read_stdin_json,
50
- send_to_cyclist,
51
- )
52
-
53
-
54
- def _resolve_agent(session_id: str | None, project_root: Path | None) -> str | None:
55
- """Resolve agent name from session file.
56
-
57
- Looks up .session/agents/{session_id} to find the active agent name.
58
- Falls back to the most recently modified agent file if session_id
59
- doesn't match.
60
-
61
- Args:
62
- session_id: Claude Code session ID
63
- project_root: Project root directory
64
-
65
- Returns:
66
- Agent name string, or None if not found
67
- """
68
- if not project_root:
69
- return None
70
-
71
- agents_dir = project_root / ".session" / "agents"
72
- if not agents_dir.is_dir():
73
- return None
74
-
75
- # Try exact session_id match first
76
- if session_id:
77
- agent_file = agents_dir / session_id
78
- if agent_file.is_file():
79
- try:
80
- return agent_file.read_text().strip() or None
81
- except OSError:
82
- pass
83
-
84
- # Fallback: most recently modified agent file
85
- try:
86
- agent_files = sorted(
87
- (f for f in agents_dir.iterdir() if f.is_file()),
88
- key=lambda f: f.stat().st_mtime,
89
- reverse=True,
90
- )
91
- if agent_files:
92
- return agent_files[0].read_text().strip() or None
93
- except OSError:
94
- pass
95
-
96
- return None
97
-
98
-
99
- def main() -> None:
100
- """Main entry point for PreToolUse hook."""
101
- try:
102
- # Read tool data from Claude Code
103
- tool_data = read_stdin_json()
104
-
105
- # Extract relevant fields
106
- tool_name = tool_data.get("tool_name", "")
107
- tool_id = tool_data.get("tool_use_id", "")
108
- tool_input = tool_data.get("tool_input", {})
109
- session_id = tool_data.get("session_id")
110
-
111
- # Find project root
112
- project_root = find_project_root()
113
-
114
- # Resolve agent name from session file (MSSCI-14392)
115
- agent_name = _resolve_agent(session_id, project_root)
116
-
117
- # Check if Cyclist is running
118
- if not is_cyclist_running(project_root):
119
- # No Cyclist - pass through to Claude Code's built-in permissions
120
- # Using "allow" so the hook doesn't override Claude Code's own
121
- # permission system (settings.json allow lists still apply)
122
- sys.exit(0)
123
-
124
- # Load settings to check for auto-approval mode
125
- settings = load_settings(project_root)
126
- if settings.permission_mode == "accept":
127
- # Auto-accept mode - approve everything
128
- output_hook_response(HookResponse(
129
- event_name="PreToolUse",
130
- decision="allow",
131
- reason="Auto-accept mode enabled",
132
- ))
133
- sys.exit(0)
134
-
135
- # Get context state for inclusion in request
136
- context = get_context_state(project_root)
137
-
138
- # Build request data
139
- request_data = {
140
- "toolName": tool_name,
141
- "toolId": tool_id,
142
- "input": tool_input,
143
- "sessionId": session_id,
144
- "context": {
145
- "percentage": context.percentage,
146
- "isHigh": context.is_high,
147
- "isCritical": context.is_critical,
148
- },
149
- }
150
- # Include agent identity if resolved (MSSCI-14392)
151
- if agent_name:
152
- request_data["agent"] = agent_name
153
-
154
- # Send approval request to WheelHub with context info
155
- response = send_to_cyclist(
156
- endpoint="/api/hook-request",
157
- data=request_data,
158
- project_root=project_root,
159
- )
160
-
161
- if response is None:
162
- # Connection failed - defer to Claude Code
163
- output_hook_response(HookResponse(
164
- event_name="PreToolUse",
165
- decision="ask",
166
- reason="Could not connect to WheelHub",
167
- ))
168
- sys.exit(0)
169
-
170
- # Extract decision from response
171
- decision = response.get("decision", "ask")
172
- reason = response.get("reason", "")
173
- data = response.get("data")
174
-
175
- # Output decision
176
- output_hook_response(HookResponse(
177
- event_name="PreToolUse",
178
- decision=decision,
179
- reason=reason,
180
- updated_input=data,
181
- ))
182
- sys.exit(0)
183
-
184
- except Exception as e:
185
- # On error, output to stderr and exit with code 0 (allow)
186
- # We don't want hook failures to block the user
187
- print(f"[pretooluse-hook] Error: {e}", file=sys.stderr)
188
- sys.exit(0)
189
-
7
+ from pennyfarthing_scripts.hooks.cyclist_pretooluse import main # noqa: F401
190
8
 
191
9
  if __name__ == "__main__":
192
10
  main()