@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
@@ -7,6 +7,9 @@ Usage:
7
7
  Commands:
8
8
  status Show sprint status
9
9
  backlog Show available stories
10
+ use Switch active sprint (multi-sprint projects)
11
+ list Show registered sprints from sprint/sprints.yaml
12
+ active Show which sprint is currently active
10
13
  work Start work on a story
11
14
  archive Archive a completed story
12
15
  story Story subcommands (show, add, update, size, template, finish, claim)
@@ -25,6 +28,9 @@ def sprint():
25
28
  Commands:
26
29
  status - Show sprint status
27
30
  backlog - Show available stories
31
+ use - Switch active sprint (multi-sprint projects)
32
+ list - Show registered sprints
33
+ active - Show which sprint is active
28
34
  story - Story operations (show, add, update, size, template, finish, claim)
29
35
  epic - Epic operations (show, add, promote, archive, cancel, import, remove)
30
36
  initiative - Initiative operations (show, cancel)
@@ -118,6 +124,121 @@ def backlog():
118
124
  click.echo(f"**Total available:** {total_count} stories, {total_points} points")
119
125
 
120
126
 
127
+ @sprint.command("use")
128
+ @click.argument("name")
129
+ def sprint_use(name: str):
130
+ """Switch the active sprint (per-user preference).
131
+
132
+ \b
133
+ Validates the sprint name against sprint/sprints.yaml, then saves
134
+ the preference to .pennyfarthing/config.local.yaml. Does not modify
135
+ any shared repo files. Use "default" to clear the preference.
136
+
137
+ \b
138
+ Arguments:
139
+ NAME - Sprint name from the registry, or "default" to clear
140
+
141
+ \b
142
+ Examples:
143
+ pf sprint use main # Switch to main project sprint
144
+ pf sprint use ocsf-rs1 # Switch to OCSF research spike
145
+ pf sprint use default # Clear preference, use sprint/current-sprint.yaml
146
+ """
147
+ # Lazy import
148
+ from pennyfarthing_scripts.sprint.loader import switch_sprint
149
+
150
+ result = switch_sprint(name)
151
+ if result["success"]:
152
+ entry = result.get("sprint", {})
153
+ click.echo(result["message"])
154
+ if entry.get("description"):
155
+ click.echo(f" {entry['description']}")
156
+ if entry.get("type"):
157
+ click.echo(f" Type: {entry['type']}")
158
+ if entry.get("repos"):
159
+ click.echo(f" Repos: {', '.join(entry['repos'])}")
160
+ else:
161
+ raise click.ClickException(result["error"])
162
+
163
+
164
+ @sprint.command("list")
165
+ def sprint_list():
166
+ """Show all registered sprints from the sprint registry.
167
+
168
+ \b
169
+ Reads sprint/sprints.yaml and displays each registered sprint
170
+ with its type, description, and whether it is currently active
171
+ for this user (based on .pennyfarthing/config.local.yaml).
172
+ """
173
+ # Lazy import
174
+ from pennyfarthing_scripts.sprint.loader import get_active_sprint_name, load_sprint_registry
175
+
176
+ registry = load_sprint_registry()
177
+ if registry is None:
178
+ click.echo("No sprint registry found (sprint/sprints.yaml)")
179
+ click.echo("This project uses a single sprint.")
180
+ return
181
+
182
+ active = get_active_sprint_name()
183
+ sprints = registry.get("sprints", {})
184
+
185
+ if not sprints:
186
+ click.echo("Sprint registry is empty.")
187
+ return
188
+
189
+ click.echo("Registered Sprints:")
190
+ click.echo("")
191
+
192
+ for name, entry in sprints.items():
193
+ marker = "*" if name == active else " "
194
+ desc = entry.get("description", "")
195
+ sprint_type = entry.get("type", "project")
196
+ repos = ", ".join(entry.get("repos", []))
197
+ click.echo(f" {marker} {name:<16} [{sprint_type}] {desc}")
198
+ if repos:
199
+ click.echo(f" Repos: {repos}")
200
+
201
+ click.echo("")
202
+ if active:
203
+ click.echo(f"Active: {active} (per-user preference)")
204
+ else:
205
+ click.echo("Active: default (sprint/current-sprint.yaml)")
206
+ click.echo("Switch: pf sprint use <name>")
207
+
208
+
209
+ @sprint.command("active")
210
+ def sprint_active():
211
+ """Show which sprint is currently active for this user.
212
+
213
+ \b
214
+ Reads the per-user preference from .pennyfarthing/config.local.yaml
215
+ and displays the active sprint name and metadata. If no preference
216
+ is set, reports the default sprint/current-sprint.yaml.
217
+ """
218
+ # Lazy import
219
+ from pennyfarthing_scripts.sprint.loader import get_active_sprint_name, load_sprint_registry
220
+
221
+ active = get_active_sprint_name()
222
+ if not active:
223
+ click.echo("default (sprint/current-sprint.yaml)")
224
+ click.echo(" No per-user sprint preference set.")
225
+ click.echo(" Set one with: pf sprint use <name>")
226
+ return
227
+
228
+ registry = load_sprint_registry()
229
+ if registry:
230
+ sprints = registry.get("sprints", {})
231
+ entry = sprints.get(active, {})
232
+ sprint_type = entry.get("type", "project")
233
+ desc = entry.get("description", "")
234
+ click.echo(f"{active} ({sprint_type})")
235
+ if desc:
236
+ click.echo(f" {desc}")
237
+ else:
238
+ click.echo(f"{active}")
239
+ click.echo(" (no sprint registry found to look up metadata)")
240
+
241
+
121
242
  @sprint.command()
122
243
  @click.argument("story_id", required=False)
123
244
  @click.option("--dry-run", is_flag=True, help="Show what would be done without making changes")
@@ -1,15 +1,23 @@
1
1
  """
2
2
  Sprint YAML parsing utilities for Pennyfarthing scripts.
3
3
 
4
- Provides access to sprint/current-sprint.yaml data.
5
- Supports sharded per-epic format (epic-{ref}.yaml shard files).
4
+ Provides access to sprint data with support for:
5
+ - Default sprint: sprint/current-sprint.yaml
6
+ - Multi-sprint registry: sprint/sprints.yaml with per-user preference
7
+ stored in .pennyfarthing/config.local.yaml
8
+ - Sharded per-epic format: epic-{ref}.yaml shard files
6
9
  """
7
10
 
8
11
  import warnings
9
12
  from pathlib import Path
10
13
  from typing import Any
11
14
 
12
- from pennyfarthing_scripts.common.config import get_project_root, load_yaml_config
15
+ from pennyfarthing_scripts.common.config import (
16
+ get_project_root,
17
+ load_pennyfarthing_config,
18
+ load_yaml_config,
19
+ save_pennyfarthing_config_key,
20
+ )
13
21
 
14
22
 
15
23
  def _merge_epic_shards(data: dict[str, Any], sprint_dir: Path) -> dict[str, Any]:
@@ -101,6 +109,12 @@ def _merge_epic_shards(data: dict[str, Any], sprint_dir: Path) -> dict[str, Any]
101
109
  def load_sprint(project_root: Path | None = None) -> dict[str, Any] | None:
102
110
  """Load sprint data from project root.
103
111
 
112
+ Resolution order:
113
+ 1. Check .pennyfarthing/config.local.yaml for sprint.active preference
114
+ 2. If set, look up the sprint in sprint/sprints.yaml registry
115
+ 3. Load the referenced sprint file (resolved relative to sprint/)
116
+ 4. If no preference or no registry, fall back to sprint/current-sprint.yaml
117
+
104
118
  Supports both monolithic and sharded epic formats. When epics are
105
119
  string references, the corresponding epic-{ref}.yaml files are
106
120
  loaded and merged transparently.
@@ -113,6 +127,30 @@ def load_sprint(project_root: Path | None = None) -> dict[str, Any] | None:
113
127
  """
114
128
  root = project_root or get_project_root()
115
129
  sprint_dir = root / "sprint"
130
+
131
+ # Check for per-user sprint preference
132
+ active_name = get_active_sprint_name(root)
133
+ if active_name:
134
+ registry = load_sprint_registry(root)
135
+ if registry:
136
+ sprints = registry.get("sprints", {})
137
+ sprint_entry = sprints.get(active_name)
138
+ if sprint_entry and sprint_entry.get("file"):
139
+ sprint_path = (sprint_dir / sprint_entry["file"]).resolve()
140
+ if sprint_path.exists():
141
+ data = load_yaml_config(sprint_path)
142
+ if data is not None:
143
+ # Inject registry metadata for downstream consumers
144
+ data["_registry"] = {
145
+ "name": active_name,
146
+ "type": sprint_entry.get("type", "project"),
147
+ "context_root": sprint_entry.get("context_root"),
148
+ "session_root": sprint_entry.get("session_root"),
149
+ "docs": sprint_entry.get("docs", {}),
150
+ }
151
+ return _merge_epic_shards(data, sprint_path.parent)
152
+
153
+ # Default: load sprint/current-sprint.yaml
116
154
  sprint_path = sprint_dir / "current-sprint.yaml"
117
155
  data = load_yaml_config(sprint_path)
118
156
  if data is None:
@@ -121,6 +159,119 @@ def load_sprint(project_root: Path | None = None) -> dict[str, Any] | None:
121
159
  return _merge_epic_shards(data, sprint_dir)
122
160
 
123
161
 
162
+ def load_sprint_registry(project_root: Path | None = None) -> dict[str, Any] | None:
163
+ """Load sprint registry from sprint/sprints.yaml.
164
+
165
+ The sprint registry indexes multiple parallel sprints (e.g., a main
166
+ project sprint and research spike sprints). Each entry maps a sprint
167
+ name to its YAML file path, type, and metadata.
168
+
169
+ Args:
170
+ project_root: Project root path (defaults to auto-detect)
171
+
172
+ Returns:
173
+ Registry data as dict, or None if no registry exists
174
+ """
175
+ root = project_root or get_project_root()
176
+ registry_path = root / "sprint" / "sprints.yaml"
177
+ return load_yaml_config(registry_path)
178
+
179
+
180
+ def get_active_sprint_name(project_root: Path | None = None) -> str | None:
181
+ """Get the name of the currently active sprint.
182
+
183
+ Reads the per-user preference from .pennyfarthing/config.local.yaml
184
+ (sprint.active key). Returns None if no preference is set, meaning
185
+ the default sprint/current-sprint.yaml should be used.
186
+
187
+ Args:
188
+ project_root: Project root path (defaults to auto-detect)
189
+
190
+ Returns:
191
+ Active sprint name, or None if using the default sprint
192
+ """
193
+ root = project_root or get_project_root()
194
+ config = load_pennyfarthing_config(root)
195
+ sprint_config = config.get("sprint", {})
196
+ if isinstance(sprint_config, dict):
197
+ return sprint_config.get("active")
198
+ return None
199
+
200
+
201
+ def switch_sprint(name: str, project_root: Path | None = None) -> dict[str, Any]:
202
+ """Switch the active sprint by updating the per-user preference.
203
+
204
+ Validates the sprint name against sprint/sprints.yaml, then writes
205
+ the selection to .pennyfarthing/config.local.yaml (gitignored,
206
+ per-user). Does not modify any shared repo files.
207
+
208
+ Use name "default" to clear the preference and revert to the
209
+ project's sprint/current-sprint.yaml.
210
+
211
+ Args:
212
+ name: Sprint name from the registry (e.g., "main", "ocsf-rs1")
213
+ or "default" to clear the preference
214
+ project_root: Project root path (defaults to auto-detect)
215
+
216
+ Returns:
217
+ Dict with 'success', 'message', and optional 'error' or 'sprint'
218
+ """
219
+ root = project_root or get_project_root()
220
+
221
+ # "default" clears the preference
222
+ if name == "default":
223
+ config = load_pennyfarthing_config(root)
224
+ sprint_config = config.get("sprint", {})
225
+ if isinstance(sprint_config, dict) and "active" in sprint_config:
226
+ del sprint_config["active"]
227
+ if not sprint_config:
228
+ save_pennyfarthing_config_key("sprint", {}, root)
229
+ else:
230
+ save_pennyfarthing_config_key("sprint", sprint_config, root)
231
+ return {
232
+ "success": True,
233
+ "message": "Cleared sprint preference — using default sprint/current-sprint.yaml",
234
+ }
235
+
236
+ registry = load_sprint_registry(root)
237
+
238
+ if registry is None:
239
+ return {"success": False, "error": "No sprint registry found (sprint/sprints.yaml)"}
240
+
241
+ sprints = registry.get("sprints", {})
242
+ if name not in sprints:
243
+ available = ", ".join(sprints.keys())
244
+ return {"success": False, "error": f"Unknown sprint: {name}. Available: {available}"}
245
+
246
+ sprint_entry = sprints[name]
247
+ sprint_file = sprint_entry.get("file")
248
+ if not sprint_file:
249
+ return {"success": False, "error": f"Sprint '{name}' has no file configured"}
250
+
251
+ # Verify the target sprint file exists
252
+ sprint_dir = root / "sprint"
253
+ target_path = (sprint_dir / sprint_file).resolve()
254
+ if not target_path.exists():
255
+ return {
256
+ "success": False,
257
+ "error": f"Sprint file not found: {sprint_file} (resolved to {target_path})",
258
+ }
259
+
260
+ # Write preference to config.local.yaml (per-user, gitignored)
261
+ config = load_pennyfarthing_config(root)
262
+ sprint_config = config.get("sprint", {})
263
+ if not isinstance(sprint_config, dict):
264
+ sprint_config = {}
265
+ sprint_config["active"] = name
266
+ save_pennyfarthing_config_key("sprint", sprint_config, root)
267
+
268
+ return {
269
+ "success": True,
270
+ "message": f"Switched to sprint: {name}",
271
+ "sprint": sprint_entry,
272
+ }
273
+
274
+
124
275
  def find_epic(sprint_data: dict[str, Any], epic_num: str) -> dict[str, Any] | None:
125
276
  """Find epic in sprint data (handles various ID formats).
126
277
 
@@ -30,6 +30,9 @@ def update_story(
30
30
  completed_date: str | None = None,
31
31
  started_date: str | None = None,
32
32
  workflow: str | None = None,
33
+ description: str | None = None,
34
+ review_findings: str | None = None,
35
+ review_verdict: str | None = None,
33
36
  dry_run: bool = False,
34
37
  ) -> dict[str, Any]:
35
38
  """Update fields on a story in the sprint YAML.
@@ -44,6 +47,9 @@ def update_story(
44
47
  completed_date: Completed date (ISO format)
45
48
  started_date: Started date (ISO format)
46
49
  workflow: Workflow type (tdd, trivial, bdd, agent-docs)
50
+ description: Story description text
51
+ review_findings: Reviewer findings text
52
+ review_verdict: Review verdict (approved, rejected, pending)
47
53
  dry_run: If True, report changes without writing
48
54
 
49
55
  Returns:
@@ -97,6 +103,17 @@ def update_story(
97
103
  story["started"] = started_date
98
104
  if workflow is not None:
99
105
  story["workflow"] = workflow
106
+ if description is not None:
107
+ story["description"] = description
108
+ if review_findings is not None:
109
+ story["review_findings"] = review_findings
110
+ if review_verdict is not None:
111
+ if review_verdict not in ("approved", "rejected", "pending"):
112
+ return {
113
+ "success": False,
114
+ "error": f"Invalid review_verdict '{review_verdict}'. Must be one of: approved, rejected, pending",
115
+ }
116
+ story["review_verdict"] = review_verdict
100
117
 
101
118
  # Auto-cleanup rules
102
119
  if status == "done":
@@ -153,6 +170,9 @@ def update_story(
153
170
  @click.option("--priority", default=None)
154
171
  @click.option("--started", "started_date", default=None)
155
172
  @click.option("--workflow", default=None)
173
+ @click.option("--description", default=None, help="Story description text")
174
+ @click.option("--review-findings", default=None, help="Reviewer findings text")
175
+ @click.option("--review-verdict", type=click.Choice(["approved", "rejected", "pending"]), default=None)
156
176
  @click.option("--dry-run", is_flag=True)
157
177
  @click.option("--sprint-file", type=click.Path(), default=None, help="Path to sprint YAML file")
158
178
  def story_update_command(
@@ -164,6 +184,9 @@ def story_update_command(
164
184
  priority: str | None,
165
185
  started_date: str | None,
166
186
  workflow: str | None,
187
+ description: str | None,
188
+ review_findings: str | None,
189
+ review_verdict: str | None,
167
190
  dry_run: bool,
168
191
  sprint_file: str | None,
169
192
  ) -> None:
@@ -184,6 +207,9 @@ def story_update_command(
184
207
  completed_date=completed_date,
185
208
  started_date=started_date,
186
209
  workflow=workflow,
210
+ description=description,
211
+ review_findings=review_findings,
212
+ review_verdict=review_verdict,
187
213
  dry_run=dry_run,
188
214
  )
189
215
 
@@ -5,14 +5,14 @@ Epic: 101 — BikeRack Mode (ADR-0024)
5
5
 
6
6
  Acceptance Criteria:
7
7
  - [AC1] `pf bikerack start` starts WheelHub background with IS_BIKERACK=1
8
- - [AC2] Polls for .wheelhub-port file (100ms interval, 5s timeout)
8
+ - [AC2] Polls for .bikerack-port file (100ms interval, 5s timeout)
9
9
  - [AC3] Sets exactly 5 OTEL env vars from discovered port (Rule 5)
10
10
  - [AC4] Uses exec (not spawn) for Claude CLI (CE-4)
11
11
  - [AC5] trap EXIT registered before exec to kill WheelHub PID (Rule 8)
12
12
  - [AC6] Writes .wheelhub-pid after spawning WheelHub
13
13
  - [AC7] `pf bikerack stop` reads PID, sends SIGTERM, deletes files
14
14
  - [AC8] `pf bikerack status` shows running state (PID, port, uptime)
15
- - [AC9] Error if already running (.wheelhub-port exists with live PID)
15
+ - [AC9] Error if already running (.bikerack-port exists with live PID)
16
16
  - [AC10] Exit code 1 if WheelHub fails to start, 2 if already running
17
17
  - [AC11] Prints dashboard URL on startup
18
18
  - [AC12] `just bikerack` works as alias
@@ -101,7 +101,7 @@ class TestStartWheelHub:
101
101
 
102
102
 
103
103
  # ---------------------------------------------------------------------------
104
- # AC2: Polls for .wheelhub-port file (100ms interval, 5s timeout)
104
+ # AC2: Polls for .bikerack-port file (100ms interval, 5s timeout)
105
105
  # ---------------------------------------------------------------------------
106
106
 
107
107
 
@@ -110,7 +110,7 @@ class TestPortFilePolling:
110
110
 
111
111
  def test_returns_port_when_file_exists(self, tmp_path: Path) -> None:
112
112
  """poll_for_port_file should return port number from file."""
113
- port_file = tmp_path / ".wheelhub-port"
113
+ port_file = tmp_path / ".bikerack-port"
114
114
  port_file.write_text("2898")
115
115
 
116
116
  result = poll_for_port_file(tmp_path)
@@ -125,7 +125,7 @@ class TestPortFilePolling:
125
125
 
126
126
  def test_waits_for_file_to_appear(self, tmp_path: Path) -> None:
127
127
  """poll_for_port_file should poll until file appears."""
128
- port_file = tmp_path / ".wheelhub-port"
128
+ port_file = tmp_path / ".bikerack-port"
129
129
 
130
130
  # Simulate file appearing after short delay
131
131
  call_count = [0]
@@ -160,7 +160,7 @@ class TestPortFilePolling:
160
160
 
161
161
  def test_reads_integer_port(self, tmp_path: Path) -> None:
162
162
  """poll_for_port_file should parse port as integer."""
163
- port_file = tmp_path / ".wheelhub-port"
163
+ port_file = tmp_path / ".bikerack-port"
164
164
  port_file.write_text("3000\n") # Trailing newline should be handled
165
165
 
166
166
  result = poll_for_port_file(tmp_path)
@@ -340,8 +340,8 @@ class TestCleanupRegistration:
340
340
  assert kill_args[1] == signal.SIGTERM
341
341
 
342
342
  def test_cleanup_removes_port_file(self, tmp_path: Path) -> None:
343
- """Registered cleanup should delete .wheelhub-port file."""
344
- port_file = tmp_path / ".wheelhub-port"
343
+ """Registered cleanup should delete .bikerack-port file."""
344
+ port_file = tmp_path / ".bikerack-port"
345
345
  port_file.write_text("2898")
346
346
 
347
347
  cleanup_func = None
@@ -363,7 +363,7 @@ class TestCleanupRegistration:
363
363
  except (ProcessLookupError, OSError):
364
364
  pass
365
365
 
366
- assert not port_file.exists(), ".wheelhub-port should be deleted by cleanup"
366
+ assert not port_file.exists(), ".bikerack-port should be deleted by cleanup"
367
367
 
368
368
  def test_cleanup_removes_pid_file(self, tmp_path: Path) -> None:
369
369
  """Registered cleanup should delete .wheelhub-pid file."""
@@ -443,7 +443,7 @@ class TestStopBikeRack:
443
443
  def test_sends_sigterm_to_pid(self, tmp_path: Path) -> None:
444
444
  """stop_bikerack should send SIGTERM to the WheelHub PID."""
445
445
  # Setup: create port and pid files
446
- (tmp_path / ".wheelhub-port").write_text("2898")
446
+ (tmp_path / ".bikerack-port").write_text("2898")
447
447
  (tmp_path / ".wheelhub-pid").write_text("12345")
448
448
 
449
449
  with patch("pennyfarthing_scripts.bikerack.launcher.os.kill") as mock_kill:
@@ -453,8 +453,8 @@ class TestStopBikeRack:
453
453
  mock_kill.assert_called_with(12345, signal.SIGTERM)
454
454
 
455
455
  def test_deletes_port_file(self, tmp_path: Path) -> None:
456
- """stop_bikerack should delete .wheelhub-port."""
457
- port_file = tmp_path / ".wheelhub-port"
456
+ """stop_bikerack should delete .bikerack-port."""
457
+ port_file = tmp_path / ".bikerack-port"
458
458
  port_file.write_text("2898")
459
459
  (tmp_path / ".wheelhub-pid").write_text("12345")
460
460
 
@@ -466,7 +466,7 @@ class TestStopBikeRack:
466
466
 
467
467
  def test_deletes_pid_file(self, tmp_path: Path) -> None:
468
468
  """stop_bikerack should delete .wheelhub-pid."""
469
- (tmp_path / ".wheelhub-port").write_text("2898")
469
+ (tmp_path / ".bikerack-port").write_text("2898")
470
470
  pid_file = tmp_path / ".wheelhub-pid"
471
471
  pid_file.write_text("12345")
472
472
 
@@ -478,7 +478,7 @@ class TestStopBikeRack:
478
478
 
479
479
  def test_returns_success_dict(self, tmp_path: Path) -> None:
480
480
  """stop_bikerack should return {success: True, pid: N, message: str}."""
481
- (tmp_path / ".wheelhub-port").write_text("2898")
481
+ (tmp_path / ".bikerack-port").write_text("2898")
482
482
  (tmp_path / ".wheelhub-pid").write_text("12345")
483
483
 
484
484
  with patch("pennyfarthing_scripts.bikerack.launcher.os.kill"):
@@ -506,7 +506,7 @@ class TestStatus:
506
506
 
507
507
  def test_returns_running_state(self, tmp_path: Path) -> None:
508
508
  """get_status should detect running BikeRack."""
509
- (tmp_path / ".wheelhub-port").write_text("2898")
509
+ (tmp_path / ".bikerack-port").write_text("2898")
510
510
  (tmp_path / ".wheelhub-pid").write_text("12345")
511
511
 
512
512
  with patch("pennyfarthing_scripts.bikerack.launcher.is_process_alive", return_value=True):
@@ -524,7 +524,7 @@ class TestStatus:
524
524
 
525
525
  def test_includes_dashboard_url(self, tmp_path: Path) -> None:
526
526
  """get_status should include dashboard URL when running."""
527
- (tmp_path / ".wheelhub-port").write_text("2898")
527
+ (tmp_path / ".bikerack-port").write_text("2898")
528
528
  (tmp_path / ".wheelhub-pid").write_text("12345")
529
529
 
530
530
  with patch("pennyfarthing_scripts.bikerack.launcher.is_process_alive", return_value=True):
@@ -534,7 +534,7 @@ class TestStatus:
534
534
 
535
535
  def test_detects_stale_pid(self, tmp_path: Path) -> None:
536
536
  """get_status should detect stale PID (file exists, process dead)."""
537
- (tmp_path / ".wheelhub-port").write_text("2898")
537
+ (tmp_path / ".bikerack-port").write_text("2898")
538
538
  (tmp_path / ".wheelhub-pid").write_text("99999")
539
539
 
540
540
  with patch("pennyfarthing_scripts.bikerack.launcher.is_process_alive", return_value=False):
@@ -544,7 +544,7 @@ class TestStatus:
544
544
 
545
545
 
546
546
  # ---------------------------------------------------------------------------
547
- # AC9: Error if already running (.wheelhub-port exists with live PID)
547
+ # AC9: Error if already running (.bikerack-port exists with live PID)
548
548
  # ---------------------------------------------------------------------------
549
549
 
550
550
 
@@ -553,7 +553,7 @@ class TestAlreadyRunning:
553
553
 
554
554
  def test_detects_running_instance(self, tmp_path: Path) -> None:
555
555
  """is_already_running should return True when port file + live PID."""
556
- (tmp_path / ".wheelhub-port").write_text("2898")
556
+ (tmp_path / ".bikerack-port").write_text("2898")
557
557
  (tmp_path / ".wheelhub-pid").write_text("12345")
558
558
 
559
559
  with patch("pennyfarthing_scripts.bikerack.launcher.is_process_alive", return_value=True):
@@ -573,7 +573,7 @@ class TestAlreadyRunning:
573
573
 
574
574
  def test_not_running_when_stale_pid(self, tmp_path: Path) -> None:
575
575
  """is_already_running should return False when PID is dead (stale)."""
576
- (tmp_path / ".wheelhub-port").write_text("2898")
576
+ (tmp_path / ".bikerack-port").write_text("2898")
577
577
  (tmp_path / ".wheelhub-pid").write_text("99999")
578
578
 
579
579
  with patch("pennyfarthing_scripts.bikerack.launcher.is_process_alive", return_value=False):
@@ -583,7 +583,7 @@ class TestAlreadyRunning:
583
583
 
584
584
  def test_cleans_stale_files(self, tmp_path: Path) -> None:
585
585
  """is_already_running should clean up stale files when PID is dead."""
586
- port_file = tmp_path / ".wheelhub-port"
586
+ port_file = tmp_path / ".bikerack-port"
587
587
  pid_file = tmp_path / ".wheelhub-pid"
588
588
  port_file.write_text("2898")
589
589
  pid_file.write_text("99999")
@@ -605,7 +605,7 @@ class TestExitCodes:
605
605
 
606
606
  def test_exit_code_2_when_already_running(self, tmp_path: Path) -> None:
607
607
  """Start should raise SystemExit(2) when already running."""
608
- (tmp_path / ".wheelhub-port").write_text("2898")
608
+ (tmp_path / ".bikerack-port").write_text("2898")
609
609
  (tmp_path / ".wheelhub-pid").write_text("12345")
610
610
 
611
611
  with patch("pennyfarthing_scripts.bikerack.launcher.is_process_alive", return_value=True):
@@ -736,8 +736,8 @@ class TestCleanupFiles:
736
736
  """Utility: cleanup_files removes port and PID files."""
737
737
 
738
738
  def test_removes_port_file(self, tmp_path: Path) -> None:
739
- """cleanup_files should remove .wheelhub-port."""
740
- port_file = tmp_path / ".wheelhub-port"
739
+ """cleanup_files should remove .bikerack-port."""
740
+ port_file = tmp_path / ".bikerack-port"
741
741
  port_file.write_text("2898")
742
742
 
743
743
  cleanup_files(tmp_path)
@@ -764,7 +764,7 @@ class TestReadPortFile:
764
764
 
765
765
  def test_reads_port(self, tmp_path: Path) -> None:
766
766
  """read_port_file should return port as integer."""
767
- (tmp_path / ".wheelhub-port").write_text("2898")
767
+ (tmp_path / ".bikerack-port").write_text("2898")
768
768
 
769
769
  result = read_port_file(tmp_path)
770
770
 
@@ -778,7 +778,7 @@ class TestReadPortFile:
778
778
 
779
779
  def test_handles_trailing_whitespace(self, tmp_path: Path) -> None:
780
780
  """read_port_file should handle trailing newlines/spaces."""
781
- (tmp_path / ".wheelhub-port").write_text("2898\n")
781
+ (tmp_path / ".bikerack-port").write_text("2898\n")
782
782
 
783
783
  result = read_port_file(tmp_path)
784
784
 
@@ -19,7 +19,6 @@ from __future__ import annotations
19
19
 
20
20
  from pathlib import Path
21
21
 
22
- import pytest
23
22
  from click.testing import CliRunner
24
23
 
25
24
  from pennyfarthing_scripts.consultation.dialogue_manager import (