@pennyfarthing/core 11.2.0 → 11.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (403) hide show
  1. package/README.md +1 -1
  2. package/package.json +2 -1
  3. package/packages/core/dist/cli/commands/doctor.d.ts.map +1 -1
  4. package/packages/core/dist/cli/commands/doctor.js +381 -66
  5. package/packages/core/dist/cli/commands/doctor.js.map +1 -1
  6. package/packages/core/dist/cli/commands/init.js +4 -4
  7. package/packages/core/dist/cli/commands/init.js.map +1 -1
  8. package/packages/core/dist/cli/commands/update.d.ts.map +1 -1
  9. package/packages/core/dist/cli/commands/update.js +4 -5
  10. package/packages/core/dist/cli/commands/update.js.map +1 -1
  11. package/packages/core/dist/cli/utils/constants.d.ts +3 -8
  12. package/packages/core/dist/cli/utils/constants.d.ts.map +1 -1
  13. package/packages/core/dist/cli/utils/constants.js +3 -4
  14. package/packages/core/dist/cli/utils/constants.js.map +1 -1
  15. package/packages/core/dist/cli/utils/settings.d.ts +11 -0
  16. package/packages/core/dist/cli/utils/settings.d.ts.map +1 -1
  17. package/packages/core/dist/cli/utils/settings.js +65 -29
  18. package/packages/core/dist/cli/utils/settings.js.map +1 -1
  19. package/packages/core/dist/cli/utils/symlinks.js +16 -16
  20. package/packages/core/dist/cli/utils/symlinks.js.map +1 -1
  21. package/packages/core/dist/consultation/tandem-metrics.d.ts +91 -0
  22. package/packages/core/dist/consultation/tandem-metrics.d.ts.map +1 -0
  23. package/packages/core/dist/consultation/tandem-metrics.js +131 -0
  24. package/packages/core/dist/consultation/tandem-metrics.js.map +1 -0
  25. package/packages/core/dist/consultation/tandem-metrics.test.d.ts +18 -0
  26. package/packages/core/dist/consultation/tandem-metrics.test.d.ts.map +1 -0
  27. package/packages/core/dist/consultation/tandem-metrics.test.js +457 -0
  28. package/packages/core/dist/consultation/tandem-metrics.test.js.map +1 -0
  29. package/packages/core/dist/public/js/react/react.js +14 -14
  30. package/packages/core/dist/scripts/benchmark-integration.d.ts +182 -0
  31. package/packages/core/dist/scripts/benchmark-integration.d.ts.map +1 -0
  32. package/packages/core/dist/scripts/benchmark-integration.js +691 -0
  33. package/packages/core/dist/scripts/benchmark-integration.js.map +1 -0
  34. package/packages/core/dist/scripts/job-fair-aggregator.d.ts +150 -0
  35. package/packages/core/dist/scripts/job-fair-aggregator.d.ts.map +1 -0
  36. package/packages/core/dist/scripts/job-fair-aggregator.js +547 -0
  37. package/packages/core/dist/scripts/job-fair-aggregator.js.map +1 -0
  38. package/packages/core/dist/server/api/agent-load.js +1 -1
  39. package/packages/core/dist/server/api/agent-load.js.map +1 -1
  40. package/packages/core/dist/server/server.d.ts +0 -3
  41. package/packages/core/dist/server/server.d.ts.map +1 -1
  42. package/packages/core/dist/server/server.js +3 -37
  43. package/packages/core/dist/server/server.js.map +1 -1
  44. package/packages/core/dist/server/server.test.d.ts +1 -1
  45. package/packages/core/dist/server/server.test.js +12 -23
  46. package/packages/core/dist/server/server.test.js.map +1 -1
  47. package/packages/core/dist/shared/capabilities.d.ts +88 -0
  48. package/packages/core/dist/shared/capabilities.d.ts.map +1 -0
  49. package/packages/core/dist/shared/capabilities.js +133 -0
  50. package/packages/core/dist/shared/capabilities.js.map +1 -0
  51. package/packages/core/dist/shared/capabilities.test.d.ts +2 -0
  52. package/packages/core/dist/shared/capabilities.test.d.ts.map +1 -0
  53. package/packages/core/dist/shared/capabilities.test.js +217 -0
  54. package/packages/core/dist/shared/capabilities.test.js.map +1 -0
  55. package/packages/core/dist/shared/spawn-prompt.d.ts +47 -0
  56. package/packages/core/dist/shared/spawn-prompt.d.ts.map +1 -0
  57. package/packages/core/dist/shared/spawn-prompt.js +82 -0
  58. package/packages/core/dist/shared/spawn-prompt.js.map +1 -0
  59. package/packages/core/dist/shared/spawn-prompt.test.d.ts +2 -0
  60. package/packages/core/dist/shared/spawn-prompt.test.d.ts.map +1 -0
  61. package/packages/core/dist/shared/spawn-prompt.test.js +251 -0
  62. package/packages/core/dist/shared/spawn-prompt.test.js.map +1 -0
  63. package/packages/core/dist/workflow/tandem-workflow-templates.test.d.ts +18 -0
  64. package/packages/core/dist/workflow/tandem-workflow-templates.test.d.ts.map +1 -0
  65. package/packages/core/dist/workflow/tandem-workflow-templates.test.js +434 -0
  66. package/packages/core/dist/workflow/tandem-workflow-templates.test.js.map +1 -0
  67. package/packages/core/dist/workflow/workflow-schema.d.ts +32 -0
  68. package/packages/core/dist/workflow/workflow-schema.d.ts.map +1 -1
  69. package/packages/core/dist/workflow/workflow-schema.js +120 -0
  70. package/packages/core/dist/workflow/workflow-schema.js.map +1 -1
  71. package/packages/core/dist/workflow/workflow-schema.test.d.ts.map +1 -1
  72. package/packages/core/dist/workflow/workflow-schema.test.js +570 -1
  73. package/packages/core/dist/workflow/workflow-schema.test.js.map +1 -1
  74. package/pennyfarthing-dist/agents/dev.md +6 -10
  75. package/pennyfarthing-dist/agents/reviewer.md +8 -2
  76. package/pennyfarthing-dist/agents/sm-finish.md +18 -1
  77. package/pennyfarthing-dist/commands/pf-git.md +4 -2
  78. package/pennyfarthing-dist/gates/approval.md +63 -0
  79. package/pennyfarthing-dist/gates/confidence-sm.md +71 -0
  80. package/pennyfarthing-dist/gates/context-ok.md +56 -0
  81. package/pennyfarthing-dist/gates/evaluations/confidence-sm.md +54 -0
  82. package/pennyfarthing-dist/gates/quality-pass.md +67 -0
  83. package/pennyfarthing-dist/gates/tests-fail.md +84 -0
  84. package/pennyfarthing-dist/gates/tests-pass.md +79 -0
  85. package/pennyfarthing-dist/guides/agent-behavior.md +23 -19
  86. package/pennyfarthing-dist/guides/bell-mode.md +1 -1
  87. package/pennyfarthing-dist/guides/hooks.md +28 -28
  88. package/pennyfarthing-dist/guides/reflector.md +1 -1
  89. package/pennyfarthing-dist/guides/tandem-protocol.md +3 -3
  90. package/pennyfarthing-dist/scripts/core/check-context.sh +2 -0
  91. package/pennyfarthing-dist/scripts/core/phase-check-start.sh +5 -87
  92. package/pennyfarthing-dist/scripts/hooks/README.md +5 -5
  93. package/pennyfarthing-dist/scripts/hooks/__pycache__/question_reflector_check.cpython-314.pyc +0 -0
  94. package/pennyfarthing-dist/scripts/hooks/bell-mode-hook.sh +4 -183
  95. package/pennyfarthing-dist/scripts/hooks/context-circuit-breaker.sh +4 -95
  96. package/pennyfarthing-dist/scripts/hooks/context-warning.sh +4 -65
  97. package/pennyfarthing-dist/scripts/hooks/cyclist-pretooluse-hook.sh +3 -31
  98. package/pennyfarthing-dist/scripts/hooks/pre-commit.sh +27 -33
  99. package/pennyfarthing-dist/scripts/hooks/pre-edit-check.sh +4 -71
  100. package/pennyfarthing-dist/scripts/hooks/question-reflector-check.sh +3 -19
  101. package/pennyfarthing-dist/scripts/hooks/schema-validation.sh +4 -30
  102. package/pennyfarthing-dist/scripts/hooks/session-start.sh +3 -32
  103. package/pennyfarthing-dist/scripts/hooks/session-stop.sh +4 -65
  104. package/pennyfarthing-dist/scripts/hooks/sprint-yaml-validation.sh +4 -78
  105. package/pennyfarthing-dist/scripts/hooks/welcome-hook.sh +4 -93
  106. package/pennyfarthing-dist/scripts/misc/README.md +1 -1
  107. package/pennyfarthing-dist/scripts/misc/statusline.sh +4 -301
  108. package/pennyfarthing-dist/templates/settings.local.json.template +19 -10
  109. package/pennyfarthing-dist/workflows/tdd.yaml +11 -2
  110. package/pennyfarthing_scripts/CLAUDE.md +19 -10
  111. package/pennyfarthing_scripts/__pycache__/__init__.cpython-311.pyc +0 -0
  112. package/pennyfarthing_scripts/__pycache__/__init__.cpython-314.pyc +0 -0
  113. package/pennyfarthing_scripts/__pycache__/bellmode_hook.cpython-314.pyc +0 -0
  114. package/pennyfarthing_scripts/__pycache__/cli.cpython-314.pyc +0 -0
  115. package/pennyfarthing_scripts/__pycache__/config.cpython-314.pyc +0 -0
  116. package/pennyfarthing_scripts/__pycache__/context.cpython-314.pyc +0 -0
  117. package/pennyfarthing_scripts/__pycache__/hooks.cpython-314.pyc +0 -0
  118. package/pennyfarthing_scripts/__pycache__/jira.cpython-314.pyc +0 -0
  119. package/pennyfarthing_scripts/__pycache__/jira_bidirectional_sync.cpython-314.pyc +0 -0
  120. package/pennyfarthing_scripts/__pycache__/jira_epic_creation.cpython-314.pyc +0 -0
  121. package/pennyfarthing_scripts/__pycache__/jira_sync.cpython-314.pyc +0 -0
  122. package/pennyfarthing_scripts/__pycache__/jira_sync_story.cpython-314.pyc +0 -0
  123. package/pennyfarthing_scripts/__pycache__/output.cpython-314.pyc +0 -0
  124. package/pennyfarthing_scripts/__pycache__/patch_mode.cpython-314.pyc +0 -0
  125. package/pennyfarthing_scripts/__pycache__/pretooluse_hook.cpython-314.pyc +0 -0
  126. package/pennyfarthing_scripts/__pycache__/schema_validation_hook.cpython-314.pyc +0 -0
  127. package/pennyfarthing_scripts/__pycache__/session_start_hook.cpython-314.pyc +0 -0
  128. package/pennyfarthing_scripts/__pycache__/sprint.cpython-314.pyc +0 -0
  129. package/pennyfarthing_scripts/__pycache__/workflow.cpython-311.pyc +0 -0
  130. package/pennyfarthing_scripts/__pycache__/workflow.cpython-314.pyc +0 -0
  131. package/pennyfarthing_scripts/bc/__pycache__/__init__.cpython-314.pyc +0 -0
  132. package/pennyfarthing_scripts/bc/__pycache__/cli.cpython-314.pyc +0 -0
  133. package/pennyfarthing_scripts/bc/__pycache__/focus.cpython-314.pyc +0 -0
  134. package/pennyfarthing_scripts/bellmode_hook.py +12 -296
  135. package/pennyfarthing_scripts/bikerack/__pycache__/__init__.cpython-314.pyc +0 -0
  136. package/pennyfarthing_scripts/bikerack/__pycache__/__main__.cpython-314.pyc +0 -0
  137. package/pennyfarthing_scripts/bikerack/__pycache__/audit_log_panel.cpython-314.pyc +0 -0
  138. package/pennyfarthing_scripts/bikerack/__pycache__/background_panel.cpython-314.pyc +0 -0
  139. package/pennyfarthing_scripts/bikerack/__pycache__/base_panel.cpython-314.pyc +0 -0
  140. package/pennyfarthing_scripts/bikerack/__pycache__/changed_panel.cpython-314.pyc +0 -0
  141. package/pennyfarthing_scripts/bikerack/__pycache__/cli.cpython-314.pyc +0 -0
  142. package/pennyfarthing_scripts/bikerack/__pycache__/context_meter_footer.cpython-314.pyc +0 -0
  143. package/pennyfarthing_scripts/bikerack/__pycache__/debug_panel.cpython-314.pyc +0 -0
  144. package/pennyfarthing_scripts/bikerack/__pycache__/diffs_panel.cpython-314.pyc +0 -0
  145. package/pennyfarthing_scripts/bikerack/__pycache__/events.cpython-314.pyc +0 -0
  146. package/pennyfarthing_scripts/bikerack/__pycache__/git_panel.cpython-314.pyc +0 -0
  147. package/pennyfarthing_scripts/bikerack/__pycache__/launcher.cpython-314.pyc +0 -0
  148. package/pennyfarthing_scripts/bikerack/__pycache__/portrait_resolver.cpython-314.pyc +0 -0
  149. package/pennyfarthing_scripts/bikerack/__pycache__/progress_panel.cpython-314.pyc +0 -0
  150. package/pennyfarthing_scripts/bikerack/__pycache__/sprint_panel.cpython-314.pyc +0 -0
  151. package/pennyfarthing_scripts/bikerack/__pycache__/story_detail_data.cpython-314.pyc +0 -0
  152. package/pennyfarthing_scripts/bikerack/__pycache__/story_detail_screen.cpython-314.pyc +0 -0
  153. package/pennyfarthing_scripts/bikerack/__pycache__/tui.cpython-314.pyc +0 -0
  154. package/pennyfarthing_scripts/bikerack/__pycache__/ws_client.cpython-314.pyc +0 -0
  155. package/pennyfarthing_scripts/bikerack/audit_log_panel.py +119 -0
  156. package/pennyfarthing_scripts/bikerack/base_panel.py +27 -4
  157. package/pennyfarthing_scripts/bikerack/changed_panel.py +96 -4
  158. package/pennyfarthing_scripts/bikerack/context_meter_footer.py +88 -0
  159. package/pennyfarthing_scripts/bikerack/debug_panel.py +1 -1
  160. package/pennyfarthing_scripts/bikerack/diffs_panel.py +30 -0
  161. package/pennyfarthing_scripts/bikerack/events.py +28 -0
  162. package/pennyfarthing_scripts/bikerack/portrait_resolver.py +139 -0
  163. package/pennyfarthing_scripts/bikerack/sprint_panel.py +373 -142
  164. package/pennyfarthing_scripts/bikerack/story_detail_data.py +244 -0
  165. package/pennyfarthing_scripts/bikerack/story_detail_screen.py +176 -0
  166. package/pennyfarthing_scripts/bikerack/tui.py +293 -61
  167. package/pennyfarthing_scripts/brownfield/__pycache__/__init__.cpython-314.pyc +0 -0
  168. package/pennyfarthing_scripts/brownfield/__pycache__/__main__.cpython-314.pyc +0 -0
  169. package/pennyfarthing_scripts/brownfield/__pycache__/cli.cpython-314.pyc +0 -0
  170. package/pennyfarthing_scripts/brownfield/__pycache__/discover.cpython-314.pyc +0 -0
  171. package/pennyfarthing_scripts/cli.py +5 -0
  172. package/pennyfarthing_scripts/codemarkers/__pycache__/__init__.cpython-314.pyc +0 -0
  173. package/pennyfarthing_scripts/codemarkers/__pycache__/__main__.cpython-314.pyc +0 -0
  174. package/pennyfarthing_scripts/codemarkers/__pycache__/analyze.cpython-314.pyc +0 -0
  175. package/pennyfarthing_scripts/codemarkers/__pycache__/cli.cpython-314.pyc +0 -0
  176. package/pennyfarthing_scripts/codemarkers/__pycache__/formatters.cpython-314.pyc +0 -0
  177. package/pennyfarthing_scripts/codemarkers/__pycache__/models.cpython-314.pyc +0 -0
  178. package/pennyfarthing_scripts/common/__pycache__/__init__.cpython-314.pyc +0 -0
  179. package/pennyfarthing_scripts/common/__pycache__/config.cpython-314.pyc +0 -0
  180. package/pennyfarthing_scripts/common/__pycache__/output.cpython-314.pyc +0 -0
  181. package/pennyfarthing_scripts/common/__pycache__/themes.cpython-314.pyc +0 -0
  182. package/pennyfarthing_scripts/common/pr_config.py +38 -0
  183. package/pennyfarthing_scripts/complexity/__pycache__/__init__.cpython-314.pyc +0 -0
  184. package/pennyfarthing_scripts/complexity/__pycache__/__main__.cpython-314.pyc +0 -0
  185. package/pennyfarthing_scripts/complexity/__pycache__/analyze.cpython-314.pyc +0 -0
  186. package/pennyfarthing_scripts/complexity/__pycache__/cli.cpython-314.pyc +0 -0
  187. package/pennyfarthing_scripts/complexity/__pycache__/formatters.cpython-314.pyc +0 -0
  188. package/pennyfarthing_scripts/complexity/__pycache__/models.cpython-314.pyc +0 -0
  189. package/pennyfarthing_scripts/consultation/__pycache__/__init__.cpython-314.pyc +0 -0
  190. package/pennyfarthing_scripts/consultation/__pycache__/cli.cpython-314.pyc +0 -0
  191. package/pennyfarthing_scripts/consultation/__pycache__/dialogue_manager.cpython-314.pyc +0 -0
  192. package/pennyfarthing_scripts/deadcode/__pycache__/__init__.cpython-314.pyc +0 -0
  193. package/pennyfarthing_scripts/deadcode/__pycache__/__main__.cpython-314.pyc +0 -0
  194. package/pennyfarthing_scripts/deadcode/__pycache__/analyze.cpython-314.pyc +0 -0
  195. package/pennyfarthing_scripts/deadcode/__pycache__/cli.cpython-314.pyc +0 -0
  196. package/pennyfarthing_scripts/deadcode/__pycache__/formatters.cpython-314.pyc +0 -0
  197. package/pennyfarthing_scripts/deadcode/__pycache__/models.cpython-314.pyc +0 -0
  198. package/pennyfarthing_scripts/dependencies/__pycache__/__init__.cpython-314.pyc +0 -0
  199. package/pennyfarthing_scripts/dependencies/__pycache__/__main__.cpython-314.pyc +0 -0
  200. package/pennyfarthing_scripts/dependencies/__pycache__/analyze.cpython-314.pyc +0 -0
  201. package/pennyfarthing_scripts/dependencies/__pycache__/cli.cpython-314.pyc +0 -0
  202. package/pennyfarthing_scripts/dependencies/__pycache__/formatters.cpython-314.pyc +0 -0
  203. package/pennyfarthing_scripts/dependencies/__pycache__/models.cpython-314.pyc +0 -0
  204. package/pennyfarthing_scripts/epic/__pycache__/__init__.cpython-314.pyc +0 -0
  205. package/pennyfarthing_scripts/epic/__pycache__/cli.cpython-314.pyc +0 -0
  206. package/pennyfarthing_scripts/git/__pycache__/__init__.cpython-314.pyc +0 -0
  207. package/pennyfarthing_scripts/git/__pycache__/create_branches.cpython-314.pyc +0 -0
  208. package/pennyfarthing_scripts/git/__pycache__/status_all.cpython-314.pyc +0 -0
  209. package/pennyfarthing_scripts/git_group/__pycache__/__init__.cpython-314.pyc +0 -0
  210. package/pennyfarthing_scripts/git_group/__pycache__/cli.cpython-314.pyc +0 -0
  211. package/pennyfarthing_scripts/handoff/__pycache__/__init__.cpython-314.pyc +0 -0
  212. package/pennyfarthing_scripts/handoff/__pycache__/cli.cpython-314.pyc +0 -0
  213. package/pennyfarthing_scripts/handoff/__pycache__/complete_phase.cpython-314.pyc +0 -0
  214. package/pennyfarthing_scripts/handoff/__pycache__/gate_file.cpython-314.pyc +0 -0
  215. package/pennyfarthing_scripts/handoff/__pycache__/gate_runner.cpython-314.pyc +0 -0
  216. package/pennyfarthing_scripts/handoff/__pycache__/marker.cpython-314.pyc +0 -0
  217. package/pennyfarthing_scripts/handoff/__pycache__/resolve_gate.cpython-314.pyc +0 -0
  218. package/pennyfarthing_scripts/handoff/cli.py +33 -1
  219. package/pennyfarthing_scripts/handoff/complete_phase.py +28 -0
  220. package/pennyfarthing_scripts/handoff/marker.py +15 -15
  221. package/pennyfarthing_scripts/handoff/phase_check.py +96 -0
  222. package/pennyfarthing_scripts/handoff/resolve_gate.py +13 -1
  223. package/pennyfarthing_scripts/healthscore/__pycache__/__init__.cpython-314.pyc +0 -0
  224. package/pennyfarthing_scripts/healthscore/__pycache__/__main__.cpython-314.pyc +0 -0
  225. package/pennyfarthing_scripts/healthscore/__pycache__/analyze.cpython-314.pyc +0 -0
  226. package/pennyfarthing_scripts/healthscore/__pycache__/cli.cpython-314.pyc +0 -0
  227. package/pennyfarthing_scripts/healthscore/__pycache__/formatters.cpython-314.pyc +0 -0
  228. package/pennyfarthing_scripts/healthscore/__pycache__/models.cpython-314.pyc +0 -0
  229. package/pennyfarthing_scripts/hooks/__init__.py +437 -0
  230. package/pennyfarthing_scripts/hooks/__pycache__/__init__.cpython-314.pyc +0 -0
  231. package/pennyfarthing_scripts/hooks/__pycache__/bell_mode.cpython-314.pyc +0 -0
  232. package/pennyfarthing_scripts/hooks/__pycache__/cli.cpython-314.pyc +0 -0
  233. package/pennyfarthing_scripts/hooks/__pycache__/context_breaker.cpython-314.pyc +0 -0
  234. package/pennyfarthing_scripts/hooks/__pycache__/context_warning.cpython-314.pyc +0 -0
  235. package/pennyfarthing_scripts/hooks/__pycache__/cyclist_pretooluse.cpython-314.pyc +0 -0
  236. package/pennyfarthing_scripts/hooks/__pycache__/pre_edit_check.cpython-314.pyc +0 -0
  237. package/pennyfarthing_scripts/hooks/__pycache__/reflector_check.cpython-314.pyc +0 -0
  238. package/pennyfarthing_scripts/hooks/__pycache__/schema_validation.cpython-314.pyc +0 -0
  239. package/pennyfarthing_scripts/hooks/__pycache__/session_start.cpython-314.pyc +0 -0
  240. package/pennyfarthing_scripts/hooks/__pycache__/session_stop.cpython-314.pyc +0 -0
  241. package/pennyfarthing_scripts/hooks/__pycache__/sprint_yaml_validation.cpython-314.pyc +0 -0
  242. package/pennyfarthing_scripts/hooks/__pycache__/statusline.cpython-314.pyc +0 -0
  243. package/pennyfarthing_scripts/hooks/bell_mode.py +215 -0
  244. package/pennyfarthing_scripts/hooks/cli.py +96 -0
  245. package/pennyfarthing_scripts/hooks/context_breaker.py +104 -0
  246. package/pennyfarthing_scripts/hooks/context_warning.py +66 -0
  247. package/pennyfarthing_scripts/hooks/cyclist_pretooluse.py +129 -0
  248. package/pennyfarthing_scripts/hooks/pre_edit_check.py +78 -0
  249. package/pennyfarthing_scripts/hooks/reflector_check.py +271 -0
  250. package/pennyfarthing_scripts/hooks/schema_validation.py +203 -0
  251. package/pennyfarthing_scripts/hooks/session_start.py +296 -0
  252. package/pennyfarthing_scripts/hooks/session_stop.py +111 -0
  253. package/pennyfarthing_scripts/hooks/sprint_yaml_validation.py +97 -0
  254. package/pennyfarthing_scripts/hooks/statusline.py +420 -0
  255. package/pennyfarthing_scripts/hooks.py +27 -432
  256. package/pennyfarthing_scripts/hotspots/__pycache__/__init__.cpython-314.pyc +0 -0
  257. package/pennyfarthing_scripts/hotspots/__pycache__/__main__.cpython-314.pyc +0 -0
  258. package/pennyfarthing_scripts/hotspots/__pycache__/analyze.cpython-314.pyc +0 -0
  259. package/pennyfarthing_scripts/hotspots/__pycache__/cli.cpython-314.pyc +0 -0
  260. package/pennyfarthing_scripts/hotspots/__pycache__/formatters.cpython-314.pyc +0 -0
  261. package/pennyfarthing_scripts/hotspots/__pycache__/models.cpython-314.pyc +0 -0
  262. package/pennyfarthing_scripts/jira/__pycache__/__init__.cpython-314.pyc +0 -0
  263. package/pennyfarthing_scripts/jira/__pycache__/__main__.cpython-314.pyc +0 -0
  264. package/pennyfarthing_scripts/jira/__pycache__/bidirectional.cpython-314.pyc +0 -0
  265. package/pennyfarthing_scripts/jira/__pycache__/claim.cpython-314.pyc +0 -0
  266. package/pennyfarthing_scripts/jira/__pycache__/cli.cpython-314.pyc +0 -0
  267. package/pennyfarthing_scripts/jira/__pycache__/client.cpython-314.pyc +0 -0
  268. package/pennyfarthing_scripts/jira/__pycache__/compat.cpython-314.pyc +0 -0
  269. package/pennyfarthing_scripts/jira/__pycache__/create.cpython-314.pyc +0 -0
  270. package/pennyfarthing_scripts/jira/__pycache__/epic.cpython-314.pyc +0 -0
  271. package/pennyfarthing_scripts/jira/__pycache__/mappings.cpython-314.pyc +0 -0
  272. package/pennyfarthing_scripts/jira/__pycache__/models.cpython-314.pyc +0 -0
  273. package/pennyfarthing_scripts/jira/__pycache__/operations.cpython-314.pyc +0 -0
  274. package/pennyfarthing_scripts/jira/__pycache__/reconcile.cpython-314.pyc +0 -0
  275. package/pennyfarthing_scripts/jira/__pycache__/story.cpython-314.pyc +0 -0
  276. package/pennyfarthing_scripts/jira/__pycache__/sync.cpython-314.pyc +0 -0
  277. package/pennyfarthing_scripts/launch/__pycache__/__init__.cpython-314.pyc +0 -0
  278. package/pennyfarthing_scripts/launch/__pycache__/cli.cpython-314.pyc +0 -0
  279. package/pennyfarthing_scripts/migration/__pycache__/__init__.cpython-314.pyc +0 -0
  280. package/pennyfarthing_scripts/migration/__pycache__/__main__.cpython-314.pyc +0 -0
  281. package/pennyfarthing_scripts/migration/__pycache__/cli.cpython-314.pyc +0 -0
  282. package/pennyfarthing_scripts/migration/__pycache__/session.cpython-314.pyc +0 -0
  283. package/pennyfarthing_scripts/migration/__pycache__/skill.cpython-314.pyc +0 -0
  284. package/pennyfarthing_scripts/migration/__pycache__/step.cpython-314.pyc +0 -0
  285. package/pennyfarthing_scripts/migration/__pycache__/validate.cpython-314.pyc +0 -0
  286. package/pennyfarthing_scripts/preflight/__pycache__/__init__.cpython-314.pyc +0 -0
  287. package/pennyfarthing_scripts/preflight/__pycache__/__main__.cpython-314.pyc +0 -0
  288. package/pennyfarthing_scripts/preflight/__pycache__/cli.cpython-314.pyc +0 -0
  289. package/pennyfarthing_scripts/preflight/__pycache__/finish.cpython-314.pyc +0 -0
  290. package/pennyfarthing_scripts/pretooluse_hook.py +3 -185
  291. package/pennyfarthing_scripts/prime/__pycache__/__init__.cpython-314.pyc +0 -0
  292. package/pennyfarthing_scripts/prime/__pycache__/__main__.cpython-314.pyc +0 -0
  293. package/pennyfarthing_scripts/prime/__pycache__/cli.cpython-314.pyc +0 -0
  294. package/pennyfarthing_scripts/prime/__pycache__/loader.cpython-314.pyc +0 -0
  295. package/pennyfarthing_scripts/prime/__pycache__/models.cpython-314.pyc +0 -0
  296. package/pennyfarthing_scripts/prime/__pycache__/persona.cpython-314.pyc +0 -0
  297. package/pennyfarthing_scripts/prime/__pycache__/session.cpython-314.pyc +0 -0
  298. package/pennyfarthing_scripts/prime/__pycache__/tiers.cpython-314.pyc +0 -0
  299. package/pennyfarthing_scripts/prime/__pycache__/version_sentinel.cpython-314.pyc +0 -0
  300. package/pennyfarthing_scripts/prime/__pycache__/workflow.cpython-314.pyc +0 -0
  301. package/pennyfarthing_scripts/prime/workflow.py +2 -1
  302. package/pennyfarthing_scripts/schema_validation_hook.py +3 -298
  303. package/pennyfarthing_scripts/session/__pycache__/__init__.cpython-314.pyc +0 -0
  304. package/pennyfarthing_scripts/session/__pycache__/cli.cpython-314.pyc +0 -0
  305. package/pennyfarthing_scripts/session_start_hook.py +4 -186
  306. package/pennyfarthing_scripts/sprint/__pycache__/__init__.cpython-314.pyc +0 -0
  307. package/pennyfarthing_scripts/sprint/__pycache__/__main__.cpython-314.pyc +0 -0
  308. package/pennyfarthing_scripts/sprint/__pycache__/archive.cpython-314.pyc +0 -0
  309. package/pennyfarthing_scripts/sprint/__pycache__/archive_epic.cpython-314.pyc +0 -0
  310. package/pennyfarthing_scripts/sprint/__pycache__/cli.cpython-314.pyc +0 -0
  311. package/pennyfarthing_scripts/sprint/__pycache__/epic_add.cpython-314.pyc +0 -0
  312. package/pennyfarthing_scripts/sprint/__pycache__/epic_update.cpython-314.pyc +0 -0
  313. package/pennyfarthing_scripts/sprint/__pycache__/import_epic.cpython-314.pyc +0 -0
  314. package/pennyfarthing_scripts/sprint/__pycache__/loader.cpython-314.pyc +0 -0
  315. package/pennyfarthing_scripts/sprint/__pycache__/status.cpython-314.pyc +0 -0
  316. package/pennyfarthing_scripts/sprint/__pycache__/story_add.cpython-314.pyc +0 -0
  317. package/pennyfarthing_scripts/sprint/__pycache__/story_finish.cpython-314.pyc +0 -0
  318. package/pennyfarthing_scripts/sprint/__pycache__/story_update.cpython-314.pyc +0 -0
  319. package/pennyfarthing_scripts/sprint/__pycache__/validate_cmd.cpython-314.pyc +0 -0
  320. package/pennyfarthing_scripts/sprint/__pycache__/validator.cpython-314.pyc +0 -0
  321. package/pennyfarthing_scripts/sprint/__pycache__/work.cpython-314.pyc +0 -0
  322. package/pennyfarthing_scripts/sprint/__pycache__/yaml_io.cpython-314.pyc +0 -0
  323. package/pennyfarthing_scripts/sprint/story_update.py +19 -0
  324. package/pennyfarthing_scripts/story/__pycache__/__init__.cpython-314.pyc +0 -0
  325. package/pennyfarthing_scripts/story/__pycache__/__main__.cpython-314.pyc +0 -0
  326. package/pennyfarthing_scripts/story/__pycache__/cli.cpython-314.pyc +0 -0
  327. package/pennyfarthing_scripts/story/__pycache__/create.cpython-314.pyc +0 -0
  328. package/pennyfarthing_scripts/story/__pycache__/size.cpython-314.pyc +0 -0
  329. package/pennyfarthing_scripts/story/__pycache__/template.cpython-314.pyc +0 -0
  330. package/pennyfarthing_scripts/tests/__pycache__/__init__.cpython-314.pyc +0 -0
  331. package/pennyfarthing_scripts/tests/__pycache__/conftest.cpython-314-pytest-9.0.2.pyc +0 -0
  332. package/pennyfarthing_scripts/tests/__pycache__/test_108_1_gate_migration.cpython-314-pytest-9.0.2.pyc +0 -0
  333. package/pennyfarthing_scripts/tests/__pycache__/test_archive_epic.cpython-314-pytest-9.0.2.pyc +0 -0
  334. package/pennyfarthing_scripts/tests/__pycache__/test_bc.cpython-314-pytest-9.0.2.pyc +0 -0
  335. package/pennyfarthing_scripts/tests/__pycache__/test_bikerack.cpython-314-pytest-9.0.2.pyc +0 -0
  336. package/pennyfarthing_scripts/tests/__pycache__/test_brownfield.cpython-314-pytest-9.0.2.pyc +0 -0
  337. package/pennyfarthing_scripts/tests/__pycache__/test_cli_modules.cpython-314-pytest-9.0.2.pyc +0 -0
  338. package/pennyfarthing_scripts/tests/__pycache__/test_cli_normalization.cpython-314-pytest-9.0.2.pyc +0 -0
  339. package/pennyfarthing_scripts/tests/__pycache__/test_codemarkers.cpython-314-pytest-9.0.2.pyc +0 -0
  340. package/pennyfarthing_scripts/tests/__pycache__/test_common.cpython-314-pytest-9.0.2.pyc +0 -0
  341. package/pennyfarthing_scripts/tests/__pycache__/test_confidence_sm_evaluation.cpython-314-pytest-9.0.2.pyc +0 -0
  342. package/pennyfarthing_scripts/tests/__pycache__/test_confidence_sm_gate.cpython-314-pytest-9.0.2.pyc +0 -0
  343. package/pennyfarthing_scripts/tests/__pycache__/test_dialogue_manager.cpython-314-pytest-9.0.2.pyc +0 -0
  344. package/pennyfarthing_scripts/tests/__pycache__/test_epic_shard_validation.cpython-314-pytest-9.0.2.pyc +0 -0
  345. package/pennyfarthing_scripts/tests/__pycache__/test_git_utils.cpython-314-pytest-9.0.2.pyc +0 -0
  346. package/pennyfarthing_scripts/tests/__pycache__/test_handoff_cli.cpython-314-pytest-9.0.2.pyc +0 -0
  347. package/pennyfarthing_scripts/tests/__pycache__/test_handoff_e2e.cpython-314-pytest-9.0.2.pyc +0 -0
  348. package/pennyfarthing_scripts/tests/__pycache__/test_healthscore.cpython-314-pytest-9.0.2.pyc +0 -0
  349. package/pennyfarthing_scripts/tests/__pycache__/test_jira_package.cpython-314-pytest-9.0.2.pyc +0 -0
  350. package/pennyfarthing_scripts/tests/__pycache__/test_package_structure.cpython-314-pytest-9.0.2.pyc +0 -0
  351. package/pennyfarthing_scripts/tests/__pycache__/test_patch_mode.cpython-314-pytest-9.0.2.pyc +0 -0
  352. package/pennyfarthing_scripts/tests/__pycache__/test_prime.cpython-314-pytest-9.0.2.pyc +0 -0
  353. package/pennyfarthing_scripts/tests/__pycache__/test_sprint_package.cpython-314-pytest-9.0.2.pyc +0 -0
  354. package/pennyfarthing_scripts/tests/__pycache__/test_sprint_panel.cpython-314-pytest-9.0.2.pyc +0 -0
  355. package/pennyfarthing_scripts/tests/__pycache__/test_sprint_validator.cpython-314-pytest-9.0.2.pyc +0 -0
  356. package/pennyfarthing_scripts/tests/__pycache__/test_story_add.cpython-314-pytest-9.0.2.pyc +0 -0
  357. package/pennyfarthing_scripts/tests/__pycache__/test_story_package.cpython-314-pytest-9.0.2.pyc +0 -0
  358. package/pennyfarthing_scripts/tests/__pycache__/test_story_update.cpython-314-pytest-9.0.2.pyc +0 -0
  359. package/pennyfarthing_scripts/tests/__pycache__/test_tiers.cpython-314-pytest-9.0.2.pyc +0 -0
  360. package/pennyfarthing_scripts/tests/__pycache__/test_token_counting.cpython-314-pytest-9.0.2.pyc +0 -0
  361. package/pennyfarthing_scripts/tests/__pycache__/test_topology_loader.cpython-314-pytest-9.0.2.pyc +0 -0
  362. package/pennyfarthing_scripts/tests/__pycache__/test_tui_focus.cpython-314-pytest-9.0.2.pyc +0 -0
  363. package/pennyfarthing_scripts/tests/__pycache__/test_tui_panel_persistence.cpython-314-pytest-9.0.2.pyc +0 -0
  364. package/pennyfarthing_scripts/tests/__pycache__/test_validate_cmd.cpython-314-pytest-9.0.2.pyc +0 -0
  365. package/pennyfarthing_scripts/tests/__pycache__/test_version_sentinel.cpython-314-pytest-9.0.2.pyc +0 -0
  366. package/pennyfarthing_scripts/tests/__pycache__/test_workflow_check.cpython-314-pytest-9.0.2.pyc +0 -0
  367. package/pennyfarthing_scripts/tests/__pycache__/test_workflow_cli.cpython-314-pytest-9.0.2.pyc +0 -0
  368. package/pennyfarthing_scripts/tests/__pycache__/test_yaml_io.cpython-314-pytest-9.0.2.pyc +0 -0
  369. package/pennyfarthing_scripts/tests/test_sprint_panel.py +344 -265
  370. package/pennyfarthing_scripts/theme/__pycache__/__init__.cpython-314.pyc +0 -0
  371. package/pennyfarthing_scripts/theme/__pycache__/cli.cpython-314.pyc +0 -0
  372. package/pennyfarthing_scripts/validate/__pycache__/__init__.cpython-314.pyc +0 -0
  373. package/pennyfarthing_scripts/validate/__pycache__/cli.cpython-314.pyc +0 -0
  374. package/pennyfarthing_scripts/validate/adapters/__pycache__/__init__.cpython-314.pyc +0 -0
  375. package/pennyfarthing_scripts/validate/adapters/__pycache__/agent.cpython-314.pyc +0 -0
  376. package/pennyfarthing_scripts/validate/adapters/__pycache__/schema.cpython-314.pyc +0 -0
  377. package/pennyfarthing_scripts/validate/adapters/__pycache__/skill_command.cpython-314.pyc +0 -0
  378. package/pennyfarthing_scripts/validate/adapters/__pycache__/sprint.cpython-314.pyc +0 -0
  379. package/pennyfarthing_scripts/validate/adapters/__pycache__/tandem_awareness.cpython-314.pyc +0 -0
  380. package/pennyfarthing_scripts/validate/adapters/__pycache__/workflow.cpython-314.pyc +0 -0
  381. package/pennyfarthing_scripts/validate/adapters/workflow.py +19 -0
  382. package/pennyfarthing_scripts/welcome_hook.py +3 -149
  383. package/pennyfarthing_scripts/workflow/__pycache__/__init__.cpython-314.pyc +0 -0
  384. package/pennyfarthing_scripts/workflow/__pycache__/cli.cpython-314.pyc +0 -0
  385. package/pennyfarthing_scripts/workflow/__pycache__/helpers.cpython-314.pyc +0 -0
  386. package/pennyfarthing_scripts/workflow/__pycache__/scale.cpython-314.pyc +0 -0
  387. package/pennyfarthing_scripts/workflow/__pycache__/state.cpython-314.pyc +0 -0
  388. package/pennyfarthing_scripts/workflow/cli.py +7 -6
  389. package/pennyfarthing_scripts/workflow/team_lifecycle.py +257 -0
  390. package/packages/core/dist/scripts/theme-detail.test.d.ts +0 -10
  391. package/packages/core/dist/scripts/theme-detail.test.js +0 -199
  392. package/pennyfarthing_scripts/bikerack/__pycache__/portrait.cpython-314.pyc +0 -0
  393. package/pennyfarthing_scripts/gate/__pycache__/__init__.cpython-314.pyc +0 -0
  394. package/pennyfarthing_scripts/gate/__pycache__/cli.cpython-314.pyc +0 -0
  395. package/pennyfarthing_scripts/gate/__pycache__/validate.cpython-314.pyc +0 -0
  396. package/pennyfarthing_scripts/git/__pycache__/hooks_installer.cpython-314.pyc +0 -0
  397. package/pennyfarthing_scripts/git/__pycache__/repos.cpython-314.pyc +0 -0
  398. package/pennyfarthing_scripts/git/__pycache__/worktree.cpython-314.pyc +0 -0
  399. package/pennyfarthing_scripts/prime/__pycache__/heatmap.cpython-314.pyc +0 -0
  400. package/pennyfarthing_scripts/tests/__pycache__/test_108_2_remove_handoff_fallback.cpython-314-pytest-9.0.2.pyc +0 -0
  401. package/pennyfarthing_scripts/tests/__pycache__/test_gate_file_resolution.cpython-314-pytest-9.0.2.pyc +0 -0
  402. package/pennyfarthing_scripts/tests/__pycache__/test_gate_runner.cpython-314-pytest-9.0.2.pyc +0 -0
  403. package/pennyfarthing_scripts/tests/__pycache__/test_resolve_gate_file_field.cpython-314-pytest-9.0.2.pyc +0 -0
@@ -1,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
  */
@@ -441,11 +650,11 @@ function checkSessionStartHooks(projectRoot, installationType) {
441
650
  }
442
651
  };
443
652
  }
444
- // Check if session-start.sh is configured
653
+ // Check if session-start hook is configured (pf hooks or legacy .sh)
445
654
  const hasSessionStartHook = settings.hooks.SessionStart.some((entry) => {
446
655
  if (typeof entry === 'object' && entry !== null) {
447
656
  const hookEntry = entry;
448
- return hookEntry.hooks?.some(h => h.command?.includes('session-start.sh'));
657
+ return hookEntry.hooks?.some(h => h.command?.includes('pf hooks session-start') || h.command?.includes('session-start.sh'));
449
658
  }
450
659
  return false;
451
660
  });
@@ -453,7 +662,7 @@ function checkSessionStartHooks(projectRoot, installationType) {
453
662
  return {
454
663
  name: 'settings/session-start-hook',
455
664
  status: 'fail',
456
- detail: 'session-start.sh not configured - PROJECT_ROOT will be undefined',
665
+ detail: 'session-start hook not configured - PROJECT_ROOT will be undefined',
457
666
  fix: () => {
458
667
  addSessionStartHooks(projectRoot, installationType);
459
668
  }
@@ -619,11 +828,11 @@ function checkStopHook(projectRoot, installationType) {
619
828
  }
620
829
  };
621
830
  }
622
- // Check if question-reflector-check is configured
831
+ // Check if reflector-check hook is configured (pf hooks or legacy .sh)
623
832
  const hasReflectorHook = settings.hooks.Stop.some((entry) => {
624
833
  if (typeof entry === 'object' && entry !== null) {
625
834
  const hookEntry = entry;
626
- return hookEntry.hooks?.some(h => h.command?.includes('question-reflector-check'));
835
+ return hookEntry.hooks?.some(h => h.command?.includes('pf hooks reflector-check') || h.command?.includes('question-reflector-check'));
627
836
  }
628
837
  return false;
629
838
  });
@@ -631,7 +840,7 @@ function checkStopHook(projectRoot, installationType) {
631
840
  return {
632
841
  name: 'settings/stop-hook',
633
842
  status: 'warn',
634
- detail: 'question-reflector-check not configured',
843
+ detail: 'reflector-check hook not configured',
635
844
  fix: () => {
636
845
  addStopHook(projectRoot, installationType);
637
846
  }
@@ -670,11 +879,11 @@ function checkContextCircuitBreaker(projectRoot, installationType) {
670
879
  }
671
880
  };
672
881
  }
673
- // Check if context-circuit-breaker is configured
882
+ // Check if context-breaker hook is configured (pf hooks or legacy .sh)
674
883
  const hasCircuitBreaker = settings.hooks.PreToolUse.some((entry) => {
675
884
  if (typeof entry === 'object' && entry !== null) {
676
885
  const hookEntry = entry;
677
- return hookEntry.hooks?.some(h => h.command?.includes('context-circuit-breaker'));
886
+ return hookEntry.hooks?.some(h => h.command?.includes('pf hooks context-breaker') || h.command?.includes('context-circuit-breaker'));
678
887
  }
679
888
  return false;
680
889
  });
@@ -682,7 +891,7 @@ function checkContextCircuitBreaker(projectRoot, installationType) {
682
891
  return {
683
892
  name: 'settings/context-circuit-breaker',
684
893
  status: 'warn',
685
- detail: 'context-circuit-breaker not configured - context exhaustion protection disabled',
894
+ detail: 'context-breaker hook not configured - context exhaustion protection disabled',
686
895
  fix: () => {
687
896
  addContextCircuitBreaker(projectRoot, installationType);
688
897
  }
@@ -705,15 +914,14 @@ function checkContextCircuitBreaker(projectRoot, installationType) {
705
914
  /**
706
915
  * Fix function: Add context-circuit-breaker hook to PreToolUse in settings.local.json
707
916
  */
708
- function addContextCircuitBreaker(projectRoot, installationType) {
917
+ function addContextCircuitBreaker(projectRoot, _installationType) {
709
918
  const settingsPath = join(projectRoot, '.claude/settings.local.json');
710
- const scriptBase = getScriptBasePath(installationType);
711
919
  const requiredHook = {
712
920
  matcher: 'Edit|Write|Bash|Task',
713
921
  hooks: [
714
922
  {
715
923
  type: 'command',
716
- command: `"$CLAUDE_PROJECT_DIR"/${scriptBase}/hooks/context-circuit-breaker.sh`
924
+ command: 'pf hooks context-breaker'
717
925
  }
718
926
  ]
719
927
  };
@@ -794,15 +1002,14 @@ function checkSchemaValidationHook(projectRoot, installationType) {
794
1002
  /**
795
1003
  * Fix function: Add schema-validation hook to PreToolUse in settings.local.json
796
1004
  */
797
- function addSchemaValidationHook(projectRoot, installationType) {
1005
+ function addSchemaValidationHook(projectRoot, _installationType) {
798
1006
  const settingsPath = join(projectRoot, '.claude/settings.local.json');
799
- const scriptBase = getScriptBasePath(installationType);
800
1007
  const requiredHook = {
801
1008
  matcher: 'Write',
802
1009
  hooks: [
803
1010
  {
804
1011
  type: 'command',
805
- command: `"$CLAUDE_PROJECT_DIR"/${scriptBase}/hooks/schema-validation.sh`
1012
+ command: 'pf hooks schema-validation'
806
1013
  }
807
1014
  ]
808
1015
  };
@@ -848,11 +1055,11 @@ function checkPostToolUseHook(projectRoot, installationType) {
848
1055
  }
849
1056
  };
850
1057
  }
851
- // Check if bell-mode-hook is configured
1058
+ // Check if bell-mode hook is configured (pf hooks or legacy .sh)
852
1059
  const hasBellModeHook = settings.hooks.PostToolUse.some((entry) => {
853
1060
  if (typeof entry === 'object' && entry !== null) {
854
1061
  const hookEntry = entry;
855
- return hookEntry.hooks?.some(h => h.command?.includes('bell-mode-hook'));
1062
+ return hookEntry.hooks?.some(h => h.command?.includes('pf hooks bell-mode') || h.command?.includes('bell-mode-hook'));
856
1063
  }
857
1064
  return false;
858
1065
  });
@@ -860,7 +1067,7 @@ function checkPostToolUseHook(projectRoot, installationType) {
860
1067
  return {
861
1068
  name: 'settings/post-tool-use-hook',
862
1069
  status: 'warn',
863
- detail: 'bell-mode-hook not configured - bell mode will not work',
1070
+ detail: 'bell-mode hook not configured - bell mode will not work',
864
1071
  fix: () => {
865
1072
  addPostToolUseHook(projectRoot, installationType);
866
1073
  }
@@ -884,15 +1091,14 @@ function checkPostToolUseHook(projectRoot, installationType) {
884
1091
  * Fix function: Add PostToolUse hook to settings.local.json
885
1092
  * Required for bell mode to inject queued messages via additionalContext
886
1093
  */
887
- function addPostToolUseHook(projectRoot, installationType) {
1094
+ function addPostToolUseHook(projectRoot, _installationType) {
888
1095
  const settingsPath = join(projectRoot, '.claude/settings.local.json');
889
- const scriptBase = getScriptBasePath(installationType);
890
1096
  const requiredHook = {
891
1097
  matcher: '',
892
1098
  hooks: [
893
1099
  {
894
1100
  type: 'command',
895
- command: `"$CLAUDE_PROJECT_DIR"/${scriptBase}/hooks/bell-mode-hook.sh`
1101
+ command: 'pf hooks bell-mode'
896
1102
  }
897
1103
  ]
898
1104
  };
@@ -938,11 +1144,11 @@ function checkSprintYamlValidationHook(projectRoot, installationType) {
938
1144
  }
939
1145
  };
940
1146
  }
941
- // Check if sprint-yaml-validation is configured
1147
+ // Check if sprint-yaml hook is configured (pf hooks or legacy .sh)
942
1148
  const hasSprintYamlValidation = settings.hooks.PostToolUse.some((entry) => {
943
1149
  if (typeof entry === 'object' && entry !== null) {
944
1150
  const hookEntry = entry;
945
- return hookEntry.hooks?.some(h => h.command?.includes('sprint-yaml-validation'));
1151
+ return hookEntry.hooks?.some(h => h.command?.includes('pf hooks sprint-yaml') || h.command?.includes('sprint-yaml-validation'));
946
1152
  }
947
1153
  return false;
948
1154
  });
@@ -950,7 +1156,7 @@ function checkSprintYamlValidationHook(projectRoot, installationType) {
950
1156
  return {
951
1157
  name: 'settings/sprint-yaml-validation',
952
1158
  status: 'warn',
953
- detail: 'sprint-yaml-validation not configured - sprint YAML errors may break SprintPanel',
1159
+ detail: 'sprint-yaml hook not configured - sprint YAML errors may break SprintPanel',
954
1160
  fix: () => {
955
1161
  addSprintYamlValidationHook(projectRoot, installationType);
956
1162
  }
@@ -974,15 +1180,14 @@ function checkSprintYamlValidationHook(projectRoot, installationType) {
974
1180
  * Fix function: Add sprint-yaml-validation hook to PostToolUse in settings.local.json
975
1181
  * Validates sprint YAML files after Edit/Write for Cyclist SprintPanel compatibility
976
1182
  */
977
- function addSprintYamlValidationHook(projectRoot, installationType) {
1183
+ function addSprintYamlValidationHook(projectRoot, _installationType) {
978
1184
  const settingsPath = join(projectRoot, '.claude/settings.local.json');
979
- const scriptBase = getScriptBasePath(installationType);
980
1185
  const requiredHook = {
981
1186
  matcher: 'Edit|Write',
982
1187
  hooks: [
983
1188
  {
984
1189
  type: 'command',
985
- command: `"$CLAUDE_PROJECT_DIR"/${scriptBase}/hooks/sprint-yaml-validation.sh`
1190
+ command: 'pf hooks sprint-yaml'
986
1191
  }
987
1192
  ]
988
1193
  };
@@ -1012,18 +1217,28 @@ function addSprintYamlValidationHook(projectRoot, installationType) {
1012
1217
  /**
1013
1218
  * Fix function: Add Stop hook to settings.local.json
1014
1219
  */
1015
- function addStopHook(projectRoot, installationType) {
1220
+ function addStopHook(projectRoot, _installationType) {
1016
1221
  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
- };
1222
+ const requiredHooks = [
1223
+ {
1224
+ matcher: '',
1225
+ hooks: [
1226
+ {
1227
+ type: 'command',
1228
+ command: 'pf hooks reflector-check'
1229
+ }
1230
+ ]
1231
+ },
1232
+ {
1233
+ matcher: '',
1234
+ hooks: [
1235
+ {
1236
+ type: 'command',
1237
+ command: 'pf hooks session-stop'
1238
+ }
1239
+ ]
1240
+ }
1241
+ ];
1027
1242
  let settings = {};
1028
1243
  if (pathExists(settingsPath)) {
1029
1244
  try {
@@ -1039,11 +1254,11 @@ function addStopHook(projectRoot, installationType) {
1039
1254
  }
1040
1255
  const hooks = settings.hooks;
1041
1256
  if (!hooks.Stop) {
1042
- hooks.Stop = [requiredHook];
1257
+ hooks.Stop = requiredHooks;
1043
1258
  }
1044
1259
  else if (Array.isArray(hooks.Stop)) {
1045
- // Prepend the required hook
1046
- hooks.Stop = [requiredHook, ...hooks.Stop];
1260
+ // Prepend the required hooks
1261
+ hooks.Stop = [...requiredHooks, ...hooks.Stop];
1047
1262
  }
1048
1263
  writeFileSync(settingsPath, JSON.stringify(settings, null, 2), 'utf8');
1049
1264
  }
@@ -1060,15 +1275,14 @@ function getScriptBasePath(installationType) {
1060
1275
  /**
1061
1276
  * Fix function: Add SessionStart hooks to settings.local.json
1062
1277
  */
1063
- function addSessionStartHooks(projectRoot, installationType) {
1278
+ function addSessionStartHooks(projectRoot, _installationType) {
1064
1279
  const settingsPath = join(projectRoot, '.claude/settings.local.json');
1065
- const scriptBase = getScriptBasePath(installationType);
1066
1280
  const requiredHooks = [
1067
1281
  {
1068
1282
  hooks: [
1069
1283
  {
1070
1284
  type: 'command',
1071
- command: `"$CLAUDE_PROJECT_DIR"/${scriptBase}/hooks/session-start.sh`
1285
+ command: 'pf hooks session-start'
1072
1286
  }
1073
1287
  ]
1074
1288
  },
@@ -1117,10 +1331,9 @@ function addSessionStartHooks(projectRoot, installationType) {
1117
1331
  * Create settings.local.json from template
1118
1332
  * This is the critical fix for installations that are missing this file
1119
1333
  */
1120
- function createSettingsLocalJson(projectRoot, installationType) {
1334
+ function createSettingsLocalJson(projectRoot, _installationType) {
1121
1335
  const settingsPath = join(projectRoot, '.claude/settings.local.json');
1122
- const scriptBase = getScriptBasePath(installationType);
1123
- // Create full settings structure matching the template
1336
+ // Create full settings structure matching the template — uses pf hooks commands
1124
1337
  const settings = {
1125
1338
  permissions: {
1126
1339
  allow: [
@@ -1163,7 +1376,7 @@ function createSettingsLocalJson(projectRoot, installationType) {
1163
1376
  hooks: [
1164
1377
  {
1165
1378
  type: 'command',
1166
- command: `"$CLAUDE_PROJECT_DIR"/${scriptBase}/hooks/session-start.sh`
1379
+ command: 'pf hooks session-start'
1167
1380
  }
1168
1381
  ]
1169
1382
  },
@@ -1185,33 +1398,42 @@ function createSettingsLocalJson(projectRoot, installationType) {
1185
1398
  ]
1186
1399
  }
1187
1400
  ],
1188
- PostToolUse: [
1401
+ Stop: [
1189
1402
  {
1190
1403
  matcher: '',
1191
1404
  hooks: [
1192
1405
  {
1193
1406
  type: 'command',
1194
- command: `"$CLAUDE_PROJECT_DIR"/${scriptBase}/hooks/bell-mode-hook.sh`
1407
+ command: 'pf hooks reflector-check'
1195
1408
  }
1196
1409
  ]
1197
1410
  },
1198
1411
  {
1199
- matcher: 'Edit|Write',
1412
+ matcher: '',
1200
1413
  hooks: [
1201
1414
  {
1202
1415
  type: 'command',
1203
- command: `"$CLAUDE_PROJECT_DIR"/${scriptBase}/hooks/sprint-yaml-validation.sh`
1416
+ command: 'pf hooks session-stop'
1204
1417
  }
1205
1418
  ]
1206
1419
  }
1207
1420
  ],
1208
- Stop: [
1421
+ PostToolUse: [
1209
1422
  {
1210
1423
  matcher: '',
1211
1424
  hooks: [
1212
1425
  {
1213
1426
  type: 'command',
1214
- command: `"$CLAUDE_PROJECT_DIR"/${scriptBase}/hooks/question-reflector-check.sh`
1427
+ command: 'pf hooks bell-mode'
1428
+ }
1429
+ ]
1430
+ },
1431
+ {
1432
+ matcher: 'Edit|Write',
1433
+ hooks: [
1434
+ {
1435
+ type: 'command',
1436
+ command: 'pf hooks sprint-yaml'
1215
1437
  }
1216
1438
  ]
1217
1439
  }
@@ -1222,7 +1444,16 @@ function createSettingsLocalJson(projectRoot, installationType) {
1222
1444
  hooks: [
1223
1445
  {
1224
1446
  type: 'command',
1225
- command: `"$CLAUDE_PROJECT_DIR"/${scriptBase}/hooks/pre-edit-check.sh`
1447
+ command: 'pf hooks pre-edit-check'
1448
+ }
1449
+ ]
1450
+ },
1451
+ {
1452
+ matcher: 'Write',
1453
+ hooks: [
1454
+ {
1455
+ type: 'command',
1456
+ command: 'pf hooks schema-validation'
1226
1457
  }
1227
1458
  ]
1228
1459
  },
@@ -1231,7 +1462,7 @@ function createSettingsLocalJson(projectRoot, installationType) {
1231
1462
  hooks: [
1232
1463
  {
1233
1464
  type: 'command',
1234
- command: `"$CLAUDE_PROJECT_DIR"/${scriptBase}/hooks/context-warning.sh`
1465
+ command: 'pf hooks context-warning'
1235
1466
  }
1236
1467
  ]
1237
1468
  },
@@ -1240,7 +1471,15 @@ function createSettingsLocalJson(projectRoot, installationType) {
1240
1471
  hooks: [
1241
1472
  {
1242
1473
  type: 'command',
1243
- command: `"$CLAUDE_PROJECT_DIR"/${scriptBase}/hooks/context-circuit-breaker.sh`
1474
+ command: 'pf hooks context-breaker'
1475
+ }
1476
+ ]
1477
+ },
1478
+ {
1479
+ hooks: [
1480
+ {
1481
+ type: 'command',
1482
+ command: 'pf hooks cyclist-pretooluse'
1244
1483
  }
1245
1484
  ]
1246
1485
  }
@@ -1248,7 +1487,7 @@ function createSettingsLocalJson(projectRoot, installationType) {
1248
1487
  },
1249
1488
  statusLine: {
1250
1489
  type: 'command',
1251
- command: `"$CLAUDE_PROJECT_DIR"/${scriptBase}/misc/statusline.sh`
1490
+ command: 'pf hooks statusline'
1252
1491
  }
1253
1492
  };
1254
1493
  ensureDirSync(join(projectRoot, '.claude'));
@@ -1809,8 +2048,8 @@ export function checkLegacyStatuslinePath(projectRoot) {
1809
2048
  // or plain paths like .pennyfarthing/scripts/misc/statusline.sh
1810
2049
  const pathMatch = command.match(/(?:\"\$CLAUDE_PROJECT_DIR\"\/)?([^\s"]+)/);
1811
2050
  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')) {
2051
+ // Check if it's the canonical pf hooks command or the legacy .sh path
2052
+ if (command === 'pf hooks statusline' || currentPath.includes('misc/statusline.sh') || command.includes('misc/statusline.sh')) {
1814
2053
  return {
1815
2054
  name: 'settings/statusline-path',
1816
2055
  status: 'pass',
@@ -1831,7 +2070,7 @@ export function checkLegacyStatuslinePath(projectRoot) {
1831
2070
  const updatedSettings = { ...settings };
1832
2071
  updatedSettings.statusLine = {
1833
2072
  type: 'command',
1834
- command: `"$CLAUDE_PROJECT_DIR"/${CANONICAL_STATUSLINE_PATH}`
2073
+ command: 'pf hooks statusline'
1835
2074
  };
1836
2075
  writeFileSync(settingsPath, JSON.stringify(updatedSettings, null, 2));
1837
2076
  }
@@ -1845,6 +2084,82 @@ export function checkLegacyStatuslinePath(projectRoot) {
1845
2084
  detail: 'Configured'
1846
2085
  };
1847
2086
  }
2087
+ /**
2088
+ * Check if settings.local.json contains legacy .sh hook commands that should
2089
+ * be migrated to `pf hooks` commands. The .sh scripts still work (they're shims)
2090
+ * but `pf hooks` is the canonical path — faster, no shell indirection.
2091
+ */
2092
+ function checkLegacyHookCommands(projectRoot) {
2093
+ const settingsPath = join(projectRoot, '.claude/settings.local.json');
2094
+ if (!pathExists(settingsPath)) {
2095
+ return { name: 'legacy/hook-commands', status: 'pass', detail: 'No settings file' };
2096
+ }
2097
+ let settings;
2098
+ try {
2099
+ settings = JSON.parse(readFileSync(settingsPath, 'utf8'));
2100
+ }
2101
+ catch {
2102
+ return { name: 'legacy/hook-commands', status: 'warn', detail: 'Cannot parse settings.local.json' };
2103
+ }
2104
+ if (!settings.hooks) {
2105
+ return { name: 'legacy/hook-commands', status: 'pass' };
2106
+ }
2107
+ // Count how many hook commands still reference .sh scripts
2108
+ const hooks = settings.hooks;
2109
+ let legacyCount = 0;
2110
+ for (const hookType of ['SessionStart', 'SessionEnd', 'PreToolUse', 'PostToolUse', 'Stop']) {
2111
+ if (!Array.isArray(hooks[hookType]))
2112
+ continue;
2113
+ for (const entry of hooks[hookType]) {
2114
+ if (!entry.hooks)
2115
+ continue;
2116
+ for (const h of entry.hooks) {
2117
+ if (!h.command)
2118
+ continue;
2119
+ for (const shName of Object.keys(LEGACY_HOOK_MIGRATIONS)) {
2120
+ if (h.command.includes(shName)) {
2121
+ legacyCount++;
2122
+ }
2123
+ }
2124
+ }
2125
+ }
2126
+ }
2127
+ // Also check statusLine
2128
+ const statusLine = settings.statusLine;
2129
+ if (statusLine?.command && statusLine.command !== 'pf hooks statusline') {
2130
+ for (const shName of Object.keys(LEGACY_HOOK_MIGRATIONS)) {
2131
+ if (statusLine.command.includes(shName)) {
2132
+ legacyCount++;
2133
+ }
2134
+ }
2135
+ }
2136
+ if (legacyCount === 0) {
2137
+ return { name: 'legacy/hook-commands', status: 'pass' };
2138
+ }
2139
+ return {
2140
+ name: 'legacy/hook-commands',
2141
+ status: 'warn',
2142
+ detail: `${legacyCount} hook(s) still use .sh scripts — should use pf hooks commands`,
2143
+ fix: () => {
2144
+ // Migrate all hook arrays
2145
+ for (const hookType of ['SessionStart', 'SessionEnd', 'PreToolUse', 'PostToolUse', 'Stop']) {
2146
+ if (Array.isArray(hooks[hookType])) {
2147
+ migrateHookPaths(hooks[hookType]);
2148
+ }
2149
+ }
2150
+ // Migrate statusLine
2151
+ if (statusLine?.command && statusLine.command !== 'pf hooks statusline') {
2152
+ for (const [shName, pfCommand] of Object.entries(LEGACY_HOOK_MIGRATIONS)) {
2153
+ if (statusLine.command.includes(shName)) {
2154
+ statusLine.command = pfCommand;
2155
+ break;
2156
+ }
2157
+ }
2158
+ }
2159
+ writeFileSync(settingsPath, JSON.stringify(settings, null, 2), 'utf8');
2160
+ }
2161
+ };
2162
+ }
1848
2163
  /**
1849
2164
  * Check file layout — validate files are at correct .pennyfarthing/ locations.
1850
2165
  * Flags old .claude/ locations with migration instructions.