@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,16 +1,17 @@
1
- import { existsSync, readFileSync, readdirSync, writeFileSync, chmodSync, statSync, readlinkSync, symlinkSync, unlinkSync, mkdirSync, renameSync } from 'fs';
1
+ import { existsSync, readFileSync, readdirSync, writeFileSync, chmodSync, statSync, readlinkSync, symlinkSync, unlinkSync, mkdirSync, renameSync, copyFileSync } from 'fs';
2
2
  import { join, relative, dirname } from 'path';
3
3
  import YAML from 'yaml';
4
4
  import { spawnSync } from 'child_process';
5
5
  import fsExtra from 'fs-extra';
6
- const { removeSync, ensureDirSync } = fsExtra;
6
+ const { removeSync, ensureDirSync, copySync } = fsExtra;
7
7
  import { logger } from '../utils/logger.js';
8
8
  import { readManifest } from '../utils/manifest.js';
9
- import { pathExists, isDirectory, isSymlink, fileMatchesHash } from '../utils/files.js';
10
- import { getPackageVersion } from '../utils/version.js';
9
+ import { pathExists, isDirectory, isSymlink, fileMatchesHash, filesMatch } from '../utils/files.js';
10
+ import { getPackageVersion, getAssetsPath } from '../utils/version.js';
11
11
  import { findNodeModulesPath } from '../utils/node-modules.js';
12
12
  import { ALL_SYMLINKS, CORE_AGENTS } from '../utils/constants.js';
13
13
  import { getPfVersion, installPfCli } from '../utils/python.js';
14
+ import { LEGACY_HOOK_MIGRATIONS, migrateHookPaths } from '../utils/settings.js';
14
15
  export async function doctorCommand(options) {
15
16
  const projectRoot = process.cwd();
16
17
  // Handle dogfood mode - run checks for framework/orchestrator development
@@ -47,6 +48,7 @@ export async function doctorCommand(options) {
47
48
  // Run checks
48
49
  results.push(...checkInstallation(projectRoot, manifest));
49
50
  results.push(...checkCoreFiles(projectRoot, manifest));
51
+ results.push(...checkCommandsAndSkills(projectRoot, nodeModulesPath));
50
52
  results.push(...checkUserFiles(projectRoot));
51
53
  results.push(...checkDirectories(projectRoot));
52
54
  results.push(...checkHooks(projectRoot));
@@ -54,6 +56,7 @@ export async function doctorCommand(options) {
54
56
  results.push(...checkFileLayout(projectRoot));
55
57
  results.push(...checkLegacyFiles(projectRoot));
56
58
  results.push(checkLegacyStatuslinePath(projectRoot));
59
+ results.push(checkLegacyHookCommands(projectRoot));
57
60
  results.push(...checkCyclist(projectRoot));
58
61
  results.push(checkPfCli(nodeModulesPath));
59
62
  // Output results
@@ -64,7 +67,8 @@ export async function doctorCommand(options) {
64
67
  // Display results by category
65
68
  const categories = [
66
69
  { name: 'Installation', filter: (r) => r.name.startsWith('manifest') },
67
- { name: 'Core Files', filter: (r) => r.name.startsWith('core/') },
70
+ { name: 'Core Files', filter: (r) => r.name.startsWith('core/') && r.name !== 'core/commands' && r.name !== 'core/skills' },
71
+ { name: 'Commands & Skills', filter: (r) => r.name === 'core/commands' || r.name === 'core/skills' },
68
72
  { name: 'User Files', filter: (r) => r.name.startsWith('project/') || r.name.startsWith('persona') || r.name.startsWith('settings') },
69
73
  { name: 'Directories', filter: (r) => r.name.startsWith('dir/') },
70
74
  { name: 'Hooks', filter: (r) => r.name.startsWith('hook/') },
@@ -188,6 +192,211 @@ function checkCoreFiles(projectRoot, manifest) {
188
192
  }
189
193
  return results;
190
194
  }
195
+ /**
196
+ * Check commands and skills are properly copied (not symlinked) and up to date.
197
+ * Commands and skills are file copies since v11.3.0 to avoid node_modules drift.
198
+ */
199
+ function checkCommandsAndSkills(projectRoot, _nodeModulesPath) {
200
+ const results = [];
201
+ // Use assetsPath for source resolution (correct pf-* prefix in dogfood)
202
+ let assetsPath = null;
203
+ try {
204
+ assetsPath = getAssetsPath();
205
+ }
206
+ catch { /* no assets available */ }
207
+ if (!assetsPath) {
208
+ // Can't check freshness without assets, but check dirs exist
209
+ const commandsDir = join(projectRoot, '.claude/commands');
210
+ const skillsDir = join(projectRoot, '.claude/skills');
211
+ results.push({
212
+ name: 'core/commands',
213
+ status: pathExists(commandsDir) ? 'pass' : 'fail',
214
+ detail: pathExists(commandsDir) ? undefined : 'Missing .claude/commands/'
215
+ });
216
+ results.push({
217
+ name: 'core/skills',
218
+ status: pathExists(skillsDir) ? 'pass' : 'fail',
219
+ detail: pathExists(skillsDir) ? undefined : 'Missing .claude/skills/'
220
+ });
221
+ return results;
222
+ }
223
+ // Check commands
224
+ const commandsDir = join(projectRoot, '.claude/commands');
225
+ const builtInCommandsPath = join(assetsPath, 'commands');
226
+ if (!pathExists(commandsDir)) {
227
+ results.push({
228
+ name: 'core/commands',
229
+ status: 'fail',
230
+ detail: 'Missing .claude/commands/ — run pennyfarthing update'
231
+ });
232
+ }
233
+ else if (pathExists(builtInCommandsPath)) {
234
+ const sourceCommands = readdirSync(builtInCommandsPath).filter(f => f.endsWith('.md') && f.startsWith('pf-'));
235
+ const installedEntries = readdirSync(commandsDir).filter(f => f.startsWith('pf-'));
236
+ let staleCount = 0;
237
+ let symlinkCount = 0;
238
+ // Check for stale symlinks (legacy) or stale copies
239
+ for (const cmd of sourceCommands) {
240
+ const installedPath = join(commandsDir, cmd);
241
+ const sourcePath = join(builtInCommandsPath, cmd);
242
+ if (!pathExists(installedPath)) {
243
+ staleCount++;
244
+ }
245
+ else if (isSymlink(installedPath)) {
246
+ symlinkCount++;
247
+ }
248
+ else if (!filesMatch(installedPath, sourcePath)) {
249
+ staleCount++;
250
+ }
251
+ }
252
+ if (symlinkCount > 0) {
253
+ results.push({
254
+ name: 'core/commands',
255
+ status: 'warn',
256
+ detail: `${symlinkCount} command(s) are symlinks — should be copies. Run pennyfarthing update`,
257
+ fix: () => {
258
+ refreshCommandsCopy(projectRoot, builtInCommandsPath);
259
+ }
260
+ });
261
+ }
262
+ else if (staleCount > 0) {
263
+ results.push({
264
+ name: 'core/commands',
265
+ status: 'warn',
266
+ detail: `${staleCount} command(s) out of date — run pennyfarthing update`,
267
+ fix: () => {
268
+ refreshCommandsCopy(projectRoot, builtInCommandsPath);
269
+ }
270
+ });
271
+ }
272
+ else {
273
+ results.push({
274
+ name: 'core/commands',
275
+ status: 'pass',
276
+ detail: `${installedEntries.length} commands`
277
+ });
278
+ }
279
+ }
280
+ // Check skills
281
+ const skillsDir = join(projectRoot, '.claude/skills');
282
+ const builtInSkillsPath = join(assetsPath, 'skills');
283
+ if (!pathExists(skillsDir)) {
284
+ results.push({
285
+ name: 'core/skills',
286
+ status: 'fail',
287
+ detail: 'Missing .claude/skills/ — run pennyfarthing update'
288
+ });
289
+ }
290
+ else if (pathExists(builtInSkillsPath)) {
291
+ const sourceSkills = readdirSync(builtInSkillsPath).filter(f => {
292
+ const fullPath = join(builtInSkillsPath, f);
293
+ return isDirectory(fullPath) && f.startsWith('pf-');
294
+ });
295
+ const installedEntries = readdirSync(skillsDir).filter(f => f.startsWith('pf-'));
296
+ let missingCount = 0;
297
+ let symlinkCount = 0;
298
+ for (const skill of sourceSkills) {
299
+ const installedPath = join(skillsDir, skill);
300
+ if (!pathExists(installedPath)) {
301
+ missingCount++;
302
+ }
303
+ else if (isSymlink(installedPath)) {
304
+ symlinkCount++;
305
+ }
306
+ }
307
+ if (symlinkCount > 0) {
308
+ results.push({
309
+ name: 'core/skills',
310
+ status: 'warn',
311
+ detail: `${symlinkCount} skill(s) are symlinks — should be copies. Run pennyfarthing update`,
312
+ fix: () => {
313
+ refreshSkillsCopy(projectRoot, builtInSkillsPath);
314
+ }
315
+ });
316
+ }
317
+ else if (missingCount > 0) {
318
+ results.push({
319
+ name: 'core/skills',
320
+ status: 'warn',
321
+ detail: `${missingCount} skill(s) missing — run pennyfarthing update`,
322
+ fix: () => {
323
+ refreshSkillsCopy(projectRoot, builtInSkillsPath);
324
+ }
325
+ });
326
+ }
327
+ else {
328
+ results.push({
329
+ name: 'core/skills',
330
+ status: 'pass',
331
+ detail: `${installedEntries.length} skills`
332
+ });
333
+ }
334
+ }
335
+ return results;
336
+ }
337
+ /**
338
+ * Fix function: refresh commands by cleaning managed entries and copying fresh
339
+ */
340
+ function refreshCommandsCopy(projectRoot, builtInCommandsPath) {
341
+ const commandsDir = join(projectRoot, '.claude/commands');
342
+ ensureDirSync(commandsDir);
343
+ // Clean all pf-* entries (symlinks or files)
344
+ const entries = readdirSync(commandsDir).filter(f => f.startsWith('pf-'));
345
+ for (const entry of entries) {
346
+ const entryPath = join(commandsDir, entry);
347
+ try {
348
+ unlinkSync(entryPath);
349
+ }
350
+ catch {
351
+ // Already gone
352
+ }
353
+ }
354
+ // Copy fresh from source
355
+ const sourceCommands = readdirSync(builtInCommandsPath).filter(f => f.endsWith('.md') && f.startsWith('pf-'));
356
+ for (const cmd of sourceCommands) {
357
+ const sourcePath = join(builtInCommandsPath, cmd);
358
+ const destPath = join(commandsDir, cmd);
359
+ try {
360
+ copyFileSync(sourcePath, destPath);
361
+ }
362
+ catch (e) {
363
+ logger.warning(`Could not copy command ${cmd}: ${e}`);
364
+ }
365
+ }
366
+ }
367
+ /**
368
+ * Fix function: refresh skills by cleaning managed entries and copying fresh
369
+ */
370
+ function refreshSkillsCopy(projectRoot, builtInSkillsPath) {
371
+ const skillsDir = join(projectRoot, '.claude/skills');
372
+ ensureDirSync(skillsDir);
373
+ // Clean all pf-* entries (symlinks or directories)
374
+ const entries = readdirSync(skillsDir).filter(f => f.startsWith('pf-'));
375
+ for (const entry of entries) {
376
+ const entryPath = join(skillsDir, entry);
377
+ try {
378
+ removeSync(entryPath);
379
+ }
380
+ catch {
381
+ // Already gone
382
+ }
383
+ }
384
+ // Copy fresh from source
385
+ const sourceSkills = readdirSync(builtInSkillsPath).filter(f => {
386
+ const fullPath = join(builtInSkillsPath, f);
387
+ return isDirectory(fullPath) && f.startsWith('pf-');
388
+ });
389
+ for (const skill of sourceSkills) {
390
+ const sourcePath = join(builtInSkillsPath, skill);
391
+ const destPath = join(skillsDir, skill);
392
+ try {
393
+ copySync(sourcePath, destPath, { overwrite: true });
394
+ }
395
+ catch (e) {
396
+ logger.warning(`Could not copy skill ${skill}: ${e}`);
397
+ }
398
+ }
399
+ }
191
400
  /**
192
401
  * Check symlinks for symlink installation mode
193
402
  */
@@ -321,6 +530,9 @@ function checkUserFiles(projectRoot) {
321
530
  // Check SessionStart hooks are configured (critical for PROJECT_ROOT)
322
531
  const hookCheck = checkSessionStartHooks(projectRoot, installationType);
323
532
  results.push(hookCheck);
533
+ // Check OTEL auto-configuration (WheelHub auto-start + telemetry env vars)
534
+ const otelCheck = checkOtelAutoStart(projectRoot, installationType);
535
+ results.push(otelCheck);
324
536
  // Check auto-load-sm hook is configured (auto-invokes /sm on new sessions)
325
537
  const autoLoadSmCheck = checkAutoLoadSmHook(projectRoot);
326
538
  results.push(autoLoadSmCheck);
@@ -441,11 +653,11 @@ function checkSessionStartHooks(projectRoot, installationType) {
441
653
  }
442
654
  };
443
655
  }
444
- // Check if session-start.sh is configured
656
+ // Check if session-start hook is configured (pf hooks or legacy .sh)
445
657
  const hasSessionStartHook = settings.hooks.SessionStart.some((entry) => {
446
658
  if (typeof entry === 'object' && entry !== null) {
447
659
  const hookEntry = entry;
448
- return hookEntry.hooks?.some(h => h.command?.includes('session-start.sh'));
660
+ return hookEntry.hooks?.some(h => h.command?.includes('pf.sh hooks session-start') || h.command?.includes('pf hooks session-start') || h.command?.includes('session-start.sh'));
449
661
  }
450
662
  return false;
451
663
  });
@@ -453,7 +665,7 @@ function checkSessionStartHooks(projectRoot, installationType) {
453
665
  return {
454
666
  name: 'settings/session-start-hook',
455
667
  status: 'fail',
456
- detail: 'session-start.sh not configured - PROJECT_ROOT will be undefined',
668
+ detail: 'session-start hook not configured - PROJECT_ROOT will be undefined',
457
669
  fix: () => {
458
670
  addSessionStartHooks(projectRoot, installationType);
459
671
  }
@@ -473,6 +685,96 @@ function checkSessionStartHooks(projectRoot, installationType) {
473
685
  };
474
686
  }
475
687
  }
688
+ /**
689
+ * Check that session-start hook uses `pf hooks session-start` (Python version)
690
+ * which handles WheelHub auto-start + OTEL env var configuration.
691
+ * Legacy .sh hooks only set 2 of 5 required OTEL vars, breaking telemetry.
692
+ */
693
+ function checkOtelAutoStart(projectRoot, installationType) {
694
+ const settingsPath = join(projectRoot, '.claude/settings.local.json');
695
+ try {
696
+ const settings = JSON.parse(readFileSync(settingsPath, 'utf8'));
697
+ if (!settings.hooks?.SessionStart) {
698
+ return {
699
+ name: 'settings/otel-auto-start',
700
+ status: 'warn',
701
+ detail: 'No SessionStart hooks — WheelHub auto-start and OTEL not configured',
702
+ };
703
+ }
704
+ // Check if `pf hooks session-start` is used (Python version with full OTEL support)
705
+ const hasPfHooksSessionStart = settings.hooks.SessionStart.some((entry) => {
706
+ if (typeof entry === 'object' && entry !== null) {
707
+ const hookEntry = entry;
708
+ return hookEntry.hooks?.some(h => h.command?.includes('pf.sh hooks session-start') || h.command?.includes('pf hooks session-start'));
709
+ }
710
+ return false;
711
+ });
712
+ if (hasPfHooksSessionStart) {
713
+ return {
714
+ name: 'settings/otel-auto-start',
715
+ status: 'pass',
716
+ detail: undefined,
717
+ };
718
+ }
719
+ // Check if using legacy .sh (only sets 2 of 5 OTEL vars)
720
+ const hasLegacySh = settings.hooks.SessionStart.some((entry) => {
721
+ if (typeof entry === 'object' && entry !== null) {
722
+ const hookEntry = entry;
723
+ return hookEntry.hooks?.some(h => h.command?.includes('session-start.sh'));
724
+ }
725
+ return false;
726
+ });
727
+ if (hasLegacySh) {
728
+ return {
729
+ name: 'settings/otel-auto-start',
730
+ status: 'warn',
731
+ detail: 'Using legacy session-start.sh — missing WheelHub auto-start and 3 OTEL env vars. Migrate to `pf hooks session-start`',
732
+ fix: () => {
733
+ migrateSessionStartToPfHooks(projectRoot);
734
+ },
735
+ };
736
+ }
737
+ return {
738
+ name: 'settings/otel-auto-start',
739
+ status: 'warn',
740
+ detail: 'session-start hook not found — WheelHub auto-start and OTEL not configured',
741
+ fix: () => {
742
+ addSessionStartHooks(projectRoot, installationType);
743
+ },
744
+ };
745
+ }
746
+ catch {
747
+ return {
748
+ name: 'settings/otel-auto-start',
749
+ status: 'warn',
750
+ detail: 'Could not parse settings.local.json',
751
+ };
752
+ }
753
+ }
754
+ /**
755
+ * Migrate legacy session-start.sh hooks to `pf hooks session-start`
756
+ */
757
+ function migrateSessionStartToPfHooks(projectRoot) {
758
+ const settingsPath = join(projectRoot, '.claude/settings.local.json');
759
+ try {
760
+ const settings = JSON.parse(readFileSync(settingsPath, 'utf8'));
761
+ if (Array.isArray(settings.hooks?.SessionStart)) {
762
+ for (const entry of settings.hooks.SessionStart) {
763
+ if (typeof entry === 'object' && entry !== null && Array.isArray(entry.hooks)) {
764
+ for (const h of entry.hooks) {
765
+ if (h.command?.includes('session-start.sh')) {
766
+ h.command = '"$CLAUDE_PROJECT_DIR"/.pennyfarthing/scripts/core/pf.sh hooks session-start';
767
+ }
768
+ }
769
+ }
770
+ }
771
+ }
772
+ writeFileSync(settingsPath, JSON.stringify(settings, null, 2), 'utf8');
773
+ }
774
+ catch {
775
+ // Silent fail — doctor will re-report on next run
776
+ }
777
+ }
476
778
  /**
477
779
  * Check that auto-load-sm hook is configured in SessionStart
478
780
  * This auto-invokes /sm agent on new session start
@@ -619,11 +921,11 @@ function checkStopHook(projectRoot, installationType) {
619
921
  }
620
922
  };
621
923
  }
622
- // Check if question-reflector-check is configured
924
+ // Check if reflector-check hook is configured (pf hooks or legacy .sh)
623
925
  const hasReflectorHook = settings.hooks.Stop.some((entry) => {
624
926
  if (typeof entry === 'object' && entry !== null) {
625
927
  const hookEntry = entry;
626
- return hookEntry.hooks?.some(h => h.command?.includes('question-reflector-check'));
928
+ return hookEntry.hooks?.some(h => h.command?.includes('pf.sh hooks reflector-check') || h.command?.includes('pf hooks reflector-check') || h.command?.includes('question-reflector-check'));
627
929
  }
628
930
  return false;
629
931
  });
@@ -631,7 +933,7 @@ function checkStopHook(projectRoot, installationType) {
631
933
  return {
632
934
  name: 'settings/stop-hook',
633
935
  status: 'warn',
634
- detail: 'question-reflector-check not configured',
936
+ detail: 'reflector-check hook not configured',
635
937
  fix: () => {
636
938
  addStopHook(projectRoot, installationType);
637
939
  }
@@ -670,11 +972,11 @@ function checkContextCircuitBreaker(projectRoot, installationType) {
670
972
  }
671
973
  };
672
974
  }
673
- // Check if context-circuit-breaker is configured
975
+ // Check if context-breaker hook is configured (pf hooks or legacy .sh)
674
976
  const hasCircuitBreaker = settings.hooks.PreToolUse.some((entry) => {
675
977
  if (typeof entry === 'object' && entry !== null) {
676
978
  const hookEntry = entry;
677
- return hookEntry.hooks?.some(h => h.command?.includes('context-circuit-breaker'));
979
+ return hookEntry.hooks?.some(h => h.command?.includes('pf.sh hooks context-breaker') || h.command?.includes('pf hooks context-breaker') || h.command?.includes('context-circuit-breaker'));
678
980
  }
679
981
  return false;
680
982
  });
@@ -682,7 +984,7 @@ function checkContextCircuitBreaker(projectRoot, installationType) {
682
984
  return {
683
985
  name: 'settings/context-circuit-breaker',
684
986
  status: 'warn',
685
- detail: 'context-circuit-breaker not configured - context exhaustion protection disabled',
987
+ detail: 'context-breaker hook not configured - context exhaustion protection disabled',
686
988
  fix: () => {
687
989
  addContextCircuitBreaker(projectRoot, installationType);
688
990
  }
@@ -705,15 +1007,14 @@ function checkContextCircuitBreaker(projectRoot, installationType) {
705
1007
  /**
706
1008
  * Fix function: Add context-circuit-breaker hook to PreToolUse in settings.local.json
707
1009
  */
708
- function addContextCircuitBreaker(projectRoot, installationType) {
1010
+ function addContextCircuitBreaker(projectRoot, _installationType) {
709
1011
  const settingsPath = join(projectRoot, '.claude/settings.local.json');
710
- const scriptBase = getScriptBasePath(installationType);
711
1012
  const requiredHook = {
712
1013
  matcher: 'Edit|Write|Bash|Task',
713
1014
  hooks: [
714
1015
  {
715
1016
  type: 'command',
716
- command: `"$CLAUDE_PROJECT_DIR"/${scriptBase}/hooks/context-circuit-breaker.sh`
1017
+ command: '"$CLAUDE_PROJECT_DIR"/.pennyfarthing/scripts/core/pf.sh hooks context-breaker'
717
1018
  }
718
1019
  ]
719
1020
  };
@@ -794,15 +1095,14 @@ function checkSchemaValidationHook(projectRoot, installationType) {
794
1095
  /**
795
1096
  * Fix function: Add schema-validation hook to PreToolUse in settings.local.json
796
1097
  */
797
- function addSchemaValidationHook(projectRoot, installationType) {
1098
+ function addSchemaValidationHook(projectRoot, _installationType) {
798
1099
  const settingsPath = join(projectRoot, '.claude/settings.local.json');
799
- const scriptBase = getScriptBasePath(installationType);
800
1100
  const requiredHook = {
801
1101
  matcher: 'Write',
802
1102
  hooks: [
803
1103
  {
804
1104
  type: 'command',
805
- command: `"$CLAUDE_PROJECT_DIR"/${scriptBase}/hooks/schema-validation.sh`
1105
+ command: '"$CLAUDE_PROJECT_DIR"/.pennyfarthing/scripts/core/pf.sh hooks schema-validation'
806
1106
  }
807
1107
  ]
808
1108
  };
@@ -848,11 +1148,11 @@ function checkPostToolUseHook(projectRoot, installationType) {
848
1148
  }
849
1149
  };
850
1150
  }
851
- // Check if bell-mode-hook is configured
1151
+ // Check if bell-mode hook is configured (pf hooks or legacy .sh)
852
1152
  const hasBellModeHook = settings.hooks.PostToolUse.some((entry) => {
853
1153
  if (typeof entry === 'object' && entry !== null) {
854
1154
  const hookEntry = entry;
855
- return hookEntry.hooks?.some(h => h.command?.includes('bell-mode-hook'));
1155
+ return hookEntry.hooks?.some(h => h.command?.includes('pf.sh hooks bell-mode') || h.command?.includes('pf hooks bell-mode') || h.command?.includes('bell-mode-hook'));
856
1156
  }
857
1157
  return false;
858
1158
  });
@@ -860,7 +1160,7 @@ function checkPostToolUseHook(projectRoot, installationType) {
860
1160
  return {
861
1161
  name: 'settings/post-tool-use-hook',
862
1162
  status: 'warn',
863
- detail: 'bell-mode-hook not configured - bell mode will not work',
1163
+ detail: 'bell-mode hook not configured - bell mode will not work',
864
1164
  fix: () => {
865
1165
  addPostToolUseHook(projectRoot, installationType);
866
1166
  }
@@ -884,15 +1184,14 @@ function checkPostToolUseHook(projectRoot, installationType) {
884
1184
  * Fix function: Add PostToolUse hook to settings.local.json
885
1185
  * Required for bell mode to inject queued messages via additionalContext
886
1186
  */
887
- function addPostToolUseHook(projectRoot, installationType) {
1187
+ function addPostToolUseHook(projectRoot, _installationType) {
888
1188
  const settingsPath = join(projectRoot, '.claude/settings.local.json');
889
- const scriptBase = getScriptBasePath(installationType);
890
1189
  const requiredHook = {
891
1190
  matcher: '',
892
1191
  hooks: [
893
1192
  {
894
1193
  type: 'command',
895
- command: `"$CLAUDE_PROJECT_DIR"/${scriptBase}/hooks/bell-mode-hook.sh`
1194
+ command: '"$CLAUDE_PROJECT_DIR"/.pennyfarthing/scripts/core/pf.sh hooks bell-mode'
896
1195
  }
897
1196
  ]
898
1197
  };
@@ -938,11 +1237,11 @@ function checkSprintYamlValidationHook(projectRoot, installationType) {
938
1237
  }
939
1238
  };
940
1239
  }
941
- // Check if sprint-yaml-validation is configured
1240
+ // Check if sprint-yaml hook is configured (pf hooks or legacy .sh)
942
1241
  const hasSprintYamlValidation = settings.hooks.PostToolUse.some((entry) => {
943
1242
  if (typeof entry === 'object' && entry !== null) {
944
1243
  const hookEntry = entry;
945
- return hookEntry.hooks?.some(h => h.command?.includes('sprint-yaml-validation'));
1244
+ return hookEntry.hooks?.some(h => h.command?.includes('pf.sh hooks sprint-yaml') || h.command?.includes('pf hooks sprint-yaml') || h.command?.includes('sprint-yaml-validation'));
946
1245
  }
947
1246
  return false;
948
1247
  });
@@ -950,7 +1249,7 @@ function checkSprintYamlValidationHook(projectRoot, installationType) {
950
1249
  return {
951
1250
  name: 'settings/sprint-yaml-validation',
952
1251
  status: 'warn',
953
- detail: 'sprint-yaml-validation not configured - sprint YAML errors may break SprintPanel',
1252
+ detail: 'sprint-yaml hook not configured - sprint YAML errors may break SprintPanel',
954
1253
  fix: () => {
955
1254
  addSprintYamlValidationHook(projectRoot, installationType);
956
1255
  }
@@ -974,15 +1273,14 @@ function checkSprintYamlValidationHook(projectRoot, installationType) {
974
1273
  * Fix function: Add sprint-yaml-validation hook to PostToolUse in settings.local.json
975
1274
  * Validates sprint YAML files after Edit/Write for Cyclist SprintPanel compatibility
976
1275
  */
977
- function addSprintYamlValidationHook(projectRoot, installationType) {
1276
+ function addSprintYamlValidationHook(projectRoot, _installationType) {
978
1277
  const settingsPath = join(projectRoot, '.claude/settings.local.json');
979
- const scriptBase = getScriptBasePath(installationType);
980
1278
  const requiredHook = {
981
1279
  matcher: 'Edit|Write',
982
1280
  hooks: [
983
1281
  {
984
1282
  type: 'command',
985
- command: `"$CLAUDE_PROJECT_DIR"/${scriptBase}/hooks/sprint-yaml-validation.sh`
1283
+ command: '"$CLAUDE_PROJECT_DIR"/.pennyfarthing/scripts/core/pf.sh hooks sprint-yaml'
986
1284
  }
987
1285
  ]
988
1286
  };
@@ -1012,18 +1310,28 @@ function addSprintYamlValidationHook(projectRoot, installationType) {
1012
1310
  /**
1013
1311
  * Fix function: Add Stop hook to settings.local.json
1014
1312
  */
1015
- function addStopHook(projectRoot, installationType) {
1313
+ function addStopHook(projectRoot, _installationType) {
1016
1314
  const settingsPath = join(projectRoot, '.claude/settings.local.json');
1017
- const scriptBase = getScriptBasePath(installationType);
1018
- const requiredHook = {
1019
- matcher: '',
1020
- hooks: [
1021
- {
1022
- type: 'command',
1023
- command: `"$CLAUDE_PROJECT_DIR"/${scriptBase}/hooks/question-reflector-check.sh`
1024
- }
1025
- ]
1026
- };
1315
+ const requiredHooks = [
1316
+ {
1317
+ matcher: '',
1318
+ hooks: [
1319
+ {
1320
+ type: 'command',
1321
+ command: '"$CLAUDE_PROJECT_DIR"/.pennyfarthing/scripts/core/pf.sh hooks reflector-check'
1322
+ }
1323
+ ]
1324
+ },
1325
+ {
1326
+ matcher: '',
1327
+ hooks: [
1328
+ {
1329
+ type: 'command',
1330
+ command: '"$CLAUDE_PROJECT_DIR"/.pennyfarthing/scripts/core/pf.sh hooks session-stop'
1331
+ }
1332
+ ]
1333
+ }
1334
+ ];
1027
1335
  let settings = {};
1028
1336
  if (pathExists(settingsPath)) {
1029
1337
  try {
@@ -1039,11 +1347,11 @@ function addStopHook(projectRoot, installationType) {
1039
1347
  }
1040
1348
  const hooks = settings.hooks;
1041
1349
  if (!hooks.Stop) {
1042
- hooks.Stop = [requiredHook];
1350
+ hooks.Stop = requiredHooks;
1043
1351
  }
1044
1352
  else if (Array.isArray(hooks.Stop)) {
1045
- // Prepend the required hook
1046
- hooks.Stop = [requiredHook, ...hooks.Stop];
1353
+ // Prepend the required hooks
1354
+ hooks.Stop = [...requiredHooks, ...hooks.Stop];
1047
1355
  }
1048
1356
  writeFileSync(settingsPath, JSON.stringify(settings, null, 2), 'utf8');
1049
1357
  }
@@ -1060,15 +1368,14 @@ function getScriptBasePath(installationType) {
1060
1368
  /**
1061
1369
  * Fix function: Add SessionStart hooks to settings.local.json
1062
1370
  */
1063
- function addSessionStartHooks(projectRoot, installationType) {
1371
+ function addSessionStartHooks(projectRoot, _installationType) {
1064
1372
  const settingsPath = join(projectRoot, '.claude/settings.local.json');
1065
- const scriptBase = getScriptBasePath(installationType);
1066
1373
  const requiredHooks = [
1067
1374
  {
1068
1375
  hooks: [
1069
1376
  {
1070
1377
  type: 'command',
1071
- command: `"$CLAUDE_PROJECT_DIR"/${scriptBase}/hooks/session-start.sh`
1378
+ command: '"$CLAUDE_PROJECT_DIR"/.pennyfarthing/scripts/core/pf.sh hooks session-start'
1072
1379
  }
1073
1380
  ]
1074
1381
  },
@@ -1117,10 +1424,9 @@ function addSessionStartHooks(projectRoot, installationType) {
1117
1424
  * Create settings.local.json from template
1118
1425
  * This is the critical fix for installations that are missing this file
1119
1426
  */
1120
- function createSettingsLocalJson(projectRoot, installationType) {
1427
+ function createSettingsLocalJson(projectRoot, _installationType) {
1121
1428
  const settingsPath = join(projectRoot, '.claude/settings.local.json');
1122
- const scriptBase = getScriptBasePath(installationType);
1123
- // Create full settings structure matching the template
1429
+ // Create full settings structure matching the template — uses pf hooks commands
1124
1430
  const settings = {
1125
1431
  permissions: {
1126
1432
  allow: [
@@ -1163,7 +1469,7 @@ function createSettingsLocalJson(projectRoot, installationType) {
1163
1469
  hooks: [
1164
1470
  {
1165
1471
  type: 'command',
1166
- command: `"$CLAUDE_PROJECT_DIR"/${scriptBase}/hooks/session-start.sh`
1472
+ command: '"$CLAUDE_PROJECT_DIR"/.pennyfarthing/scripts/core/pf.sh hooks session-start'
1167
1473
  }
1168
1474
  ]
1169
1475
  },
@@ -1185,33 +1491,42 @@ function createSettingsLocalJson(projectRoot, installationType) {
1185
1491
  ]
1186
1492
  }
1187
1493
  ],
1188
- PostToolUse: [
1494
+ Stop: [
1189
1495
  {
1190
1496
  matcher: '',
1191
1497
  hooks: [
1192
1498
  {
1193
1499
  type: 'command',
1194
- command: `"$CLAUDE_PROJECT_DIR"/${scriptBase}/hooks/bell-mode-hook.sh`
1500
+ command: '"$CLAUDE_PROJECT_DIR"/.pennyfarthing/scripts/core/pf.sh hooks reflector-check'
1195
1501
  }
1196
1502
  ]
1197
1503
  },
1198
1504
  {
1199
- matcher: 'Edit|Write',
1505
+ matcher: '',
1200
1506
  hooks: [
1201
1507
  {
1202
1508
  type: 'command',
1203
- command: `"$CLAUDE_PROJECT_DIR"/${scriptBase}/hooks/sprint-yaml-validation.sh`
1509
+ command: '"$CLAUDE_PROJECT_DIR"/.pennyfarthing/scripts/core/pf.sh hooks session-stop'
1204
1510
  }
1205
1511
  ]
1206
1512
  }
1207
1513
  ],
1208
- Stop: [
1514
+ PostToolUse: [
1209
1515
  {
1210
1516
  matcher: '',
1211
1517
  hooks: [
1212
1518
  {
1213
1519
  type: 'command',
1214
- command: `"$CLAUDE_PROJECT_DIR"/${scriptBase}/hooks/question-reflector-check.sh`
1520
+ command: '"$CLAUDE_PROJECT_DIR"/.pennyfarthing/scripts/core/pf.sh hooks bell-mode'
1521
+ }
1522
+ ]
1523
+ },
1524
+ {
1525
+ matcher: 'Edit|Write',
1526
+ hooks: [
1527
+ {
1528
+ type: 'command',
1529
+ command: '"$CLAUDE_PROJECT_DIR"/.pennyfarthing/scripts/core/pf.sh hooks sprint-yaml'
1215
1530
  }
1216
1531
  ]
1217
1532
  }
@@ -1222,7 +1537,16 @@ function createSettingsLocalJson(projectRoot, installationType) {
1222
1537
  hooks: [
1223
1538
  {
1224
1539
  type: 'command',
1225
- command: `"$CLAUDE_PROJECT_DIR"/${scriptBase}/hooks/pre-edit-check.sh`
1540
+ command: '"$CLAUDE_PROJECT_DIR"/.pennyfarthing/scripts/core/pf.sh hooks pre-edit-check'
1541
+ }
1542
+ ]
1543
+ },
1544
+ {
1545
+ matcher: 'Write',
1546
+ hooks: [
1547
+ {
1548
+ type: 'command',
1549
+ command: '"$CLAUDE_PROJECT_DIR"/.pennyfarthing/scripts/core/pf.sh hooks schema-validation'
1226
1550
  }
1227
1551
  ]
1228
1552
  },
@@ -1231,7 +1555,7 @@ function createSettingsLocalJson(projectRoot, installationType) {
1231
1555
  hooks: [
1232
1556
  {
1233
1557
  type: 'command',
1234
- command: `"$CLAUDE_PROJECT_DIR"/${scriptBase}/hooks/context-warning.sh`
1558
+ command: '"$CLAUDE_PROJECT_DIR"/.pennyfarthing/scripts/core/pf.sh hooks context-warning'
1235
1559
  }
1236
1560
  ]
1237
1561
  },
@@ -1240,7 +1564,15 @@ function createSettingsLocalJson(projectRoot, installationType) {
1240
1564
  hooks: [
1241
1565
  {
1242
1566
  type: 'command',
1243
- command: `"$CLAUDE_PROJECT_DIR"/${scriptBase}/hooks/context-circuit-breaker.sh`
1567
+ command: '"$CLAUDE_PROJECT_DIR"/.pennyfarthing/scripts/core/pf.sh hooks context-breaker'
1568
+ }
1569
+ ]
1570
+ },
1571
+ {
1572
+ hooks: [
1573
+ {
1574
+ type: 'command',
1575
+ command: '"$CLAUDE_PROJECT_DIR"/.pennyfarthing/scripts/core/pf.sh hooks cyclist-pretooluse'
1244
1576
  }
1245
1577
  ]
1246
1578
  }
@@ -1248,7 +1580,7 @@ function createSettingsLocalJson(projectRoot, installationType) {
1248
1580
  },
1249
1581
  statusLine: {
1250
1582
  type: 'command',
1251
- command: `"$CLAUDE_PROJECT_DIR"/${scriptBase}/misc/statusline.sh`
1583
+ command: '"$CLAUDE_PROJECT_DIR"/.pennyfarthing/scripts/core/pf.sh hooks statusline'
1252
1584
  }
1253
1585
  };
1254
1586
  ensureDirSync(join(projectRoot, '.claude'));
@@ -1809,8 +2141,8 @@ export function checkLegacyStatuslinePath(projectRoot) {
1809
2141
  // or plain paths like .pennyfarthing/scripts/misc/statusline.sh
1810
2142
  const pathMatch = command.match(/(?:\"\$CLAUDE_PROJECT_DIR\"\/)?([^\s"]+)/);
1811
2143
  const currentPath = pathMatch ? pathMatch[1] : command;
1812
- // Check if it contains the canonical path
1813
- if (currentPath.includes('misc/statusline.sh') || command.includes('misc/statusline.sh')) {
2144
+ // Check if it's the canonical pf hooks command or the legacy .sh path
2145
+ if (command.includes('pf.sh hooks statusline') || command === 'pf hooks statusline' || currentPath.includes('misc/statusline.sh') || command.includes('misc/statusline.sh')) {
1814
2146
  return {
1815
2147
  name: 'settings/statusline-path',
1816
2148
  status: 'pass',
@@ -1831,7 +2163,7 @@ export function checkLegacyStatuslinePath(projectRoot) {
1831
2163
  const updatedSettings = { ...settings };
1832
2164
  updatedSettings.statusLine = {
1833
2165
  type: 'command',
1834
- command: `"$CLAUDE_PROJECT_DIR"/${CANONICAL_STATUSLINE_PATH}`
2166
+ command: '"$CLAUDE_PROJECT_DIR"/.pennyfarthing/scripts/core/pf.sh hooks statusline'
1835
2167
  };
1836
2168
  writeFileSync(settingsPath, JSON.stringify(updatedSettings, null, 2));
1837
2169
  }
@@ -1845,6 +2177,82 @@ export function checkLegacyStatuslinePath(projectRoot) {
1845
2177
  detail: 'Configured'
1846
2178
  };
1847
2179
  }
2180
+ /**
2181
+ * Check if settings.local.json contains legacy .sh hook commands that should
2182
+ * be migrated to `pf hooks` commands. The .sh scripts still work (they're shims)
2183
+ * but `pf hooks` is the canonical path — faster, no shell indirection.
2184
+ */
2185
+ function checkLegacyHookCommands(projectRoot) {
2186
+ const settingsPath = join(projectRoot, '.claude/settings.local.json');
2187
+ if (!pathExists(settingsPath)) {
2188
+ return { name: 'legacy/hook-commands', status: 'pass', detail: 'No settings file' };
2189
+ }
2190
+ let settings;
2191
+ try {
2192
+ settings = JSON.parse(readFileSync(settingsPath, 'utf8'));
2193
+ }
2194
+ catch {
2195
+ return { name: 'legacy/hook-commands', status: 'warn', detail: 'Cannot parse settings.local.json' };
2196
+ }
2197
+ if (!settings.hooks) {
2198
+ return { name: 'legacy/hook-commands', status: 'pass' };
2199
+ }
2200
+ // Count how many hook commands still reference .sh scripts
2201
+ const hooks = settings.hooks;
2202
+ let legacyCount = 0;
2203
+ for (const hookType of ['SessionStart', 'SessionEnd', 'PreToolUse', 'PostToolUse', 'Stop']) {
2204
+ if (!Array.isArray(hooks[hookType]))
2205
+ continue;
2206
+ for (const entry of hooks[hookType]) {
2207
+ if (!entry.hooks)
2208
+ continue;
2209
+ for (const h of entry.hooks) {
2210
+ if (!h.command)
2211
+ continue;
2212
+ for (const shName of Object.keys(LEGACY_HOOK_MIGRATIONS)) {
2213
+ if (h.command.includes(shName)) {
2214
+ legacyCount++;
2215
+ }
2216
+ }
2217
+ }
2218
+ }
2219
+ }
2220
+ // Also check statusLine
2221
+ const statusLine = settings.statusLine;
2222
+ if (statusLine?.command && !statusLine.command.includes('pf.sh hooks statusline') && statusLine.command !== 'pf hooks statusline') {
2223
+ for (const shName of Object.keys(LEGACY_HOOK_MIGRATIONS)) {
2224
+ if (statusLine.command.includes(shName)) {
2225
+ legacyCount++;
2226
+ }
2227
+ }
2228
+ }
2229
+ if (legacyCount === 0) {
2230
+ return { name: 'legacy/hook-commands', status: 'pass' };
2231
+ }
2232
+ return {
2233
+ name: 'legacy/hook-commands',
2234
+ status: 'warn',
2235
+ detail: `${legacyCount} hook(s) still use .sh scripts — should use pf hooks commands`,
2236
+ fix: () => {
2237
+ // Migrate all hook arrays
2238
+ for (const hookType of ['SessionStart', 'SessionEnd', 'PreToolUse', 'PostToolUse', 'Stop']) {
2239
+ if (Array.isArray(hooks[hookType])) {
2240
+ migrateHookPaths(hooks[hookType]);
2241
+ }
2242
+ }
2243
+ // Migrate statusLine
2244
+ if (statusLine?.command && !statusLine.command.includes('pf.sh hooks statusline') && statusLine.command !== 'pf hooks statusline') {
2245
+ for (const [shName, pfCommand] of Object.entries(LEGACY_HOOK_MIGRATIONS)) {
2246
+ if (statusLine.command.includes(shName)) {
2247
+ statusLine.command = pfCommand;
2248
+ break;
2249
+ }
2250
+ }
2251
+ }
2252
+ writeFileSync(settingsPath, JSON.stringify(settings, null, 2), 'utf8');
2253
+ }
2254
+ };
2255
+ }
1848
2256
  /**
1849
2257
  * Check file layout — validate files are at correct .pennyfarthing/ locations.
1850
2258
  * Flags old .claude/ locations with migration instructions.