@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
@@ -0,0 +1,294 @@
1
+ """
2
+ SessionStart hook — initialize environment for Claude Code session.
3
+
4
+ Consolidates session_start_hook.py and welcome_hook.py into a single module.
5
+
6
+ Handles:
7
+ 1. Session directory setup and logging
8
+ 2. Checkpoint validation (cross-session drift detection)
9
+ 3. WheelHub auto-start (ensure BikeRack server is running)
10
+ 4. OTEL auto-configuration via CLAUDE_ENV_FILE
11
+ 5. Welcome message display (CLI ASCII art or Cyclist API)
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import json
17
+ import os
18
+ import subprocess
19
+ import sys
20
+ from datetime import UTC, datetime
21
+ from pathlib import Path
22
+
23
+ from pennyfarthing_scripts.hooks import (
24
+ is_cyclist_running,
25
+ load_settings,
26
+ send_to_cyclist,
27
+ )
28
+
29
+ # =============================================================================
30
+ # Session Setup
31
+ # =============================================================================
32
+
33
+
34
+ def _read_input() -> dict:
35
+ """Read JSON from stdin (hook protocol)."""
36
+ raw = sys.stdin.read()
37
+ try:
38
+ return json.loads(raw)
39
+ except (json.JSONDecodeError, ValueError):
40
+ return {}
41
+
42
+
43
+ def _setup_session_dir(project_dir: Path, session_id: str, source_type: str) -> None:
44
+ """Ensure .session directory exists and log session start."""
45
+ session_dir = project_dir / ".session"
46
+ session_dir.mkdir(parents=True, exist_ok=True)
47
+ (session_dir / "agents").mkdir(exist_ok=True)
48
+
49
+ timestamp = datetime.now(UTC).isoformat()
50
+ log_file = session_dir / "session-log.txt"
51
+ with open(log_file, "a") as f:
52
+ f.write(f"{timestamp} | Session {source_type}: {session_id}\n")
53
+
54
+
55
+ # =============================================================================
56
+ # Checkpoint Validation
57
+ # =============================================================================
58
+
59
+
60
+ def _validate_checkpoint(project_dir: Path) -> None:
61
+ """Validate previous session checkpoint for cross-session drift detection."""
62
+ try:
63
+ checkpoint_file = project_dir / ".session" / "checkpoints.log"
64
+ if not checkpoint_file.exists():
65
+ return
66
+
67
+ # Read the last session_state checkpoint
68
+ prev_state = ""
69
+ with open(checkpoint_file) as f:
70
+ for line in f:
71
+ if "|session_state|" in line:
72
+ prev_state = line.strip().split("|", 2)[-1] if "|" in line else ""
73
+
74
+ if not prev_state:
75
+ return
76
+
77
+ # Parse checkpoint data
78
+ fields = {}
79
+ for part in prev_state.split(";"):
80
+ if "=" in part:
81
+ k, v = part.split("=", 1)
82
+ fields[k] = v
83
+
84
+ prev_sha = fields.get("sha", "")
85
+ if not prev_sha:
86
+ return
87
+
88
+ # Get current git SHA
89
+ result = subprocess.run(
90
+ ["git", "rev-parse", "--short", "HEAD"],
91
+ capture_output=True,
92
+ text=True,
93
+ cwd=str(project_dir),
94
+ timeout=5,
95
+ )
96
+ current_sha = result.stdout.strip()
97
+ if not current_sha or current_sha == prev_sha:
98
+ return
99
+
100
+ # Log drift
101
+ timestamp = datetime.now(UTC).isoformat()
102
+ session_dir = project_dir / ".session"
103
+
104
+ warning = f"CROSS_SESSION_DRIFT: Git changed (was: {prev_sha}, now: {current_sha})"
105
+ if fields.get("story"):
106
+ warning += f" | Story: {fields['story']}"
107
+ if fields.get("agent"):
108
+ warning += f" | Agent: {fields['agent']}"
109
+
110
+ with open(session_dir / "session-log.txt", "a") as f:
111
+ f.write(f"{timestamp} | {warning}\n")
112
+
113
+ drift_entry = f"{timestamp} | prev_sha={prev_sha} | current_sha={current_sha}"
114
+ for key in ("story", "agent", "phase"):
115
+ if fields.get(key):
116
+ drift_entry += f" | {key}={fields[key]}"
117
+ with open(session_dir / "drift-log.txt", "a") as f:
118
+ f.write(drift_entry + "\n")
119
+
120
+ except Exception:
121
+ pass
122
+
123
+
124
+ # =============================================================================
125
+ # WheelHub Auto-Start
126
+ # =============================================================================
127
+
128
+
129
+ def _ensure_wheelhub(project_dir: Path) -> int | None:
130
+ """Auto-start WheelHub if not already running. Returns port or None."""
131
+ from pennyfarthing_scripts.bikerack.launcher import (
132
+ is_already_running,
133
+ poll_for_port_file,
134
+ start_wheelhub,
135
+ write_pid_file,
136
+ )
137
+
138
+ # Skip if full Cyclist is running
139
+ cyclist_port_file = project_dir / ".bikerack-port"
140
+ if cyclist_port_file.exists():
141
+ try:
142
+ return int(cyclist_port_file.read_text().strip())
143
+ except (ValueError, OSError):
144
+ return None
145
+
146
+ # Check if BikeRack WheelHub is already running
147
+ running, _pid, port = is_already_running(project_dir)
148
+ if running:
149
+ return port
150
+
151
+ # Start WheelHub
152
+ try:
153
+ proc = start_wheelhub(project_dir)
154
+ write_pid_file(project_dir, proc.pid)
155
+ return poll_for_port_file(project_dir)
156
+ except Exception:
157
+ return None
158
+
159
+
160
+ # =============================================================================
161
+ # Environment File
162
+ # =============================================================================
163
+
164
+
165
+ def _write_env_file(project_dir: Path, session_id: str, otel_port: int | None) -> None:
166
+ """Write environment variables to CLAUDE_ENV_FILE."""
167
+ env_file = os.environ.get("CLAUDE_ENV_FILE")
168
+ if not env_file:
169
+ return
170
+
171
+ lines = [
172
+ "# Pennyfarthing core environment",
173
+ f'export PROJECT_ROOT="{project_dir}"',
174
+ f'export SESSION_ID="{session_id}"',
175
+ ]
176
+
177
+ if otel_port is not None:
178
+ from pennyfarthing_scripts.bikerack.launcher import build_otel_env
179
+
180
+ lines.append("# OTEL auto-configuration for Cyclist/WheelHub")
181
+ for key, value in build_otel_env(otel_port).items():
182
+ lines.append(f'export {key}="{value}"')
183
+
184
+ with open(env_file, "a") as f:
185
+ f.write("\n".join(lines) + "\n")
186
+
187
+
188
+ # =============================================================================
189
+ # Welcome Message
190
+ # =============================================================================
191
+
192
+
193
+ def _get_welcome_lock_path(project_root: Path) -> Path:
194
+ """Get path to welcome shown lock file."""
195
+ session_id = os.environ.get("CLAUDE_SESSION_ID", str(os.getpid()))
196
+ session_dir = project_root / ".session"
197
+ session_dir.mkdir(parents=True, exist_ok=True)
198
+ return session_dir / f".welcome-shown-{session_id}"
199
+
200
+
201
+ def _get_project_name(project_root: Path) -> str:
202
+ """Get project name from package.json or directory name."""
203
+ package_json = project_root / "package.json"
204
+ if package_json.exists():
205
+ try:
206
+ with open(package_json) as f:
207
+ data = json.load(f)
208
+ name = data.get("name")
209
+ if name:
210
+ return name
211
+ except (json.JSONDecodeError, OSError):
212
+ pass
213
+ return project_root.name
214
+
215
+
216
+ def _display_cli_welcome(project_name: str, theme: str | None) -> None:
217
+ """Display ASCII art welcome for CLI mode."""
218
+ print("""
219
+ ___
220
+ / \\
221
+ | | Welcome to
222
+ | | \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557
223
+ \\___/ \u2551 \u2554\u2550\u2557\u2554\u2550\u2557\u2554\u2557\u2554\u2554\u2557\u2554\u2566\u2550\u2557\u2554\u2550\u2557\u2554\u2550\u2557\u2554\u2550\u2557\u2566\u2554\u2550\u2557 \u2551
224
+ \u2551 \u2551 \u2560\u2550\u2569\u2551\u2563 \u2551\u2551\u2551\u2551\u2551\u2551 \u2560\u2563 \u2560\u2550\u2563\u2560\u2566\u2569 \u2551 \u2560\u2550\u2563 \u2551
225
+ \u2551 \u2551 \u2569 \u255a\u2550\u255d\u255d\u255a\u255d\u255d\u255a\u255d\u2569 \u2569 \u2569\u2569\u255a\u2550 \u2569 \u2569 \u2569 \u2551
226
+ \u2554\u2550\u2569\u2550\u2557 \u255a\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255d
227
+ / \\
228
+ \u2502 O \u2502 Agent-powered development with style
229
+ \\ /
230
+ \u255a\u2550\u2550\u2550\u255d
231
+ """)
232
+
233
+ if project_name:
234
+ print(f" Project: {project_name}")
235
+ if theme:
236
+ print(f" Theme: {theme}")
237
+ print()
238
+
239
+
240
+ def _show_welcome(project_dir: Path) -> None:
241
+ """Show welcome message (once per session)."""
242
+ lock_path = _get_welcome_lock_path(project_dir)
243
+ if lock_path.exists():
244
+ return
245
+
246
+ lock_path.touch()
247
+
248
+ project_name = _get_project_name(project_dir)
249
+ settings = load_settings(project_dir)
250
+ theme = settings.theme
251
+
252
+ if is_cyclist_running(project_dir):
253
+ try:
254
+ send_to_cyclist(
255
+ endpoint="/api/welcome",
256
+ data={"project": project_name or "", "theme": theme or ""},
257
+ project_root=project_dir,
258
+ timeout=5,
259
+ )
260
+ except Exception:
261
+ pass
262
+ else:
263
+ _display_cli_welcome(project_name, theme)
264
+
265
+
266
+ # =============================================================================
267
+ # Entry Point
268
+ # =============================================================================
269
+
270
+
271
+ def main() -> None:
272
+ """Entry point for SessionStart hook."""
273
+ try:
274
+ input_data = _read_input()
275
+ session_id = input_data.get("session_id", "unknown")
276
+ source_type = input_data.get("source", "unknown")
277
+
278
+ project_dir = Path(os.environ.get("CLAUDE_PROJECT_DIR", os.getcwd()))
279
+
280
+ _setup_session_dir(project_dir, session_id, source_type)
281
+ _validate_checkpoint(project_dir)
282
+
283
+ otel_port = _ensure_wheelhub(project_dir)
284
+ _write_env_file(project_dir, session_id, otel_port)
285
+ _show_welcome(project_dir)
286
+
287
+ except Exception:
288
+ pass
289
+
290
+ sys.exit(0)
291
+
292
+
293
+ if __name__ == "__main__":
294
+ main()
@@ -0,0 +1,111 @@
1
+ """
2
+ Session stop hook — save checkpoint for cross-session continuity.
3
+
4
+ Writes session state (agent, story, phase, git SHA, session ID) to
5
+ checkpoints.log so the next session can detect drift.
6
+
7
+ Replaces session-stop.sh + checkpoint.sh logic.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import json
13
+ import os
14
+ import re
15
+ import subprocess
16
+ import sys
17
+ from datetime import UTC, datetime
18
+ from pathlib import Path
19
+
20
+
21
+ def _checkpoint_save(project_dir: Path, label: str, data: str) -> None:
22
+ """Save a checkpoint entry to checkpoints.log."""
23
+ try:
24
+ checkpoint_file = project_dir / ".session" / "checkpoints.log"
25
+ checkpoint_file.parent.mkdir(parents=True, exist_ok=True)
26
+ timestamp = datetime.now(UTC).strftime("%Y-%m-%dT%H:%M:%SZ")
27
+ with open(checkpoint_file, "a") as f:
28
+ f.write(f"{timestamp}|{label}|{data}\n")
29
+ except OSError:
30
+ pass
31
+
32
+
33
+ def main() -> None:
34
+ """Main entry point for session stop hook."""
35
+ try:
36
+ raw = sys.stdin.read()
37
+ try:
38
+ input_data = json.loads(raw)
39
+ except (json.JSONDecodeError, ValueError):
40
+ input_data = {}
41
+
42
+ session_id = input_data.get("session_id", "unknown")
43
+ project_dir = Path(os.environ.get("CLAUDE_PROJECT_DIR", os.getcwd()))
44
+
45
+ # Get current agent
46
+ agent = ""
47
+ agent_file = project_dir / ".session" / "agents" / session_id
48
+ if agent_file.is_file():
49
+ try:
50
+ agent = agent_file.read_text().strip()
51
+ except OSError:
52
+ pass
53
+
54
+ # Find active story from session files
55
+ story = ""
56
+ phase = ""
57
+ session_dir = project_dir / ".session"
58
+ if session_dir.is_dir():
59
+ session_files = sorted(
60
+ session_dir.glob("*-session.md"),
61
+ key=lambda f: f.stat().st_mtime,
62
+ reverse=True,
63
+ )
64
+ if session_files:
65
+ session_file = session_files[0]
66
+ # Extract story ID from filename (e.g., 8-3-session.md -> 8-3)
67
+ story = re.sub(r'-session\.md$', '', session_file.name)
68
+ # Extract phase from session file
69
+ try:
70
+ content = session_file.read_text()
71
+ phase_match = re.search(r'^- Phase:\s*(\S+)', content, re.MULTILINE | re.IGNORECASE)
72
+ if phase_match:
73
+ phase = phase_match.group(1)
74
+ except OSError:
75
+ pass
76
+
77
+ # Get current git SHA
78
+ git_sha = ""
79
+ try:
80
+ result = subprocess.run(
81
+ ["git", "rev-parse", "--short", "HEAD"],
82
+ capture_output=True,
83
+ text=True,
84
+ cwd=str(project_dir),
85
+ timeout=5,
86
+ )
87
+ git_sha = result.stdout.strip()
88
+ except (subprocess.TimeoutExpired, OSError):
89
+ pass
90
+
91
+ # Build and save checkpoint
92
+ checkpoint_data = f"agent={agent};story={story};phase={phase};sha={git_sha};session={session_id}"
93
+ _checkpoint_save(project_dir, "session_state", checkpoint_data)
94
+
95
+ # Log session end
96
+ try:
97
+ timestamp = datetime.now(UTC).isoformat()
98
+ log_file = session_dir / "session-log.txt"
99
+ with open(log_file, "a") as f:
100
+ f.write(f"{timestamp} | Session end: {session_id} (agent={agent}, story={story})\n")
101
+ except OSError:
102
+ pass
103
+
104
+ except Exception:
105
+ pass
106
+
107
+ sys.exit(0)
108
+
109
+
110
+ if __name__ == "__main__":
111
+ main()
@@ -0,0 +1,97 @@
1
+ """
2
+ Sprint YAML validation hook (PostToolUse) — validate sprint YAML after edits.
3
+
4
+ Validates that sprint YAML files are compatible with the yaml npm package
5
+ used by Cyclist's SprintPanel (strict YAML 1.2). When validation fails,
6
+ returns additionalContext prompting the agent to fix the format.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import json
12
+ import re
13
+ import subprocess
14
+ import sys
15
+
16
+ from pennyfarthing_scripts.hooks import (
17
+ HookResponse,
18
+ output_hook_response,
19
+ )
20
+
21
+
22
+ def main() -> None:
23
+ """Main entry point for sprint YAML validation hook."""
24
+ try:
25
+ raw = sys.stdin.read()
26
+ try:
27
+ input_data = json.loads(raw)
28
+ except (json.JSONDecodeError, ValueError):
29
+ sys.exit(0)
30
+
31
+ tool_name = input_data.get("tool_name", "")
32
+ tool_input = input_data.get("tool_input", {})
33
+ file_path = tool_input.get("file_path", "")
34
+
35
+ # Only process Edit/Write on sprint YAML files
36
+ if tool_name not in ("Edit", "Write"):
37
+ sys.exit(0)
38
+
39
+ if not re.search(r'sprint/.*\.(yaml|yml)$', file_path):
40
+ sys.exit(0)
41
+
42
+ from pathlib import Path
43
+ if not Path(file_path).is_file():
44
+ sys.exit(0)
45
+
46
+ # Validate using Node.js yaml package (same parser Cyclist uses)
47
+ validation_script = """
48
+ import { parse } from 'yaml';
49
+ import { readFileSync } from 'fs';
50
+ try {
51
+ const content = readFileSync(process.argv[1], 'utf-8');
52
+ parse(content);
53
+ process.exit(0);
54
+ } catch (e) {
55
+ console.error(e.message);
56
+ process.exit(1);
57
+ }
58
+ """
59
+ try:
60
+ result = subprocess.run(
61
+ ["node", "--input-type=module", "-e", validation_script, file_path],
62
+ capture_output=True,
63
+ text=True,
64
+ timeout=10,
65
+ )
66
+ except (subprocess.TimeoutExpired, FileNotFoundError):
67
+ sys.exit(0)
68
+
69
+ if result.returncode == 0:
70
+ sys.exit(0)
71
+
72
+ # Validation failed
73
+ error_text = result.stderr.strip().replace('"', '\\"').replace('\n', ' ')
74
+ escaped_path = file_path.replace('"', '\\"')
75
+
76
+ output_hook_response(HookResponse(
77
+ event_name="PostToolUse",
78
+ additional_context=(
79
+ f"SPRINT YAML VALIDATION FAILED\n\n"
80
+ f"File: {escaped_path}\n"
81
+ f"Error: {error_text}\n\n"
82
+ f"The sprint YAML file has invalid syntax that will break the Cyclist SprintPanel.\n\n"
83
+ f"Common fix: Single-quoted strings cannot contain blank lines in YAML 1.2.\n"
84
+ f"Use literal block scalars (|) for multiline strings instead."
85
+ ),
86
+ ))
87
+
88
+ except SystemExit:
89
+ raise
90
+ except Exception:
91
+ pass
92
+
93
+ sys.exit(0)
94
+
95
+
96
+ if __name__ == "__main__":
97
+ main()