@pennyfarthing/core 10.2.0 → 10.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (372) hide show
  1. package/README.md +11 -8
  2. package/package.json +14 -16
  3. package/packages/core/dist/cli/commands/e2e-fresh-install.test.js +1 -1
  4. package/packages/core/dist/cli/commands/e2e-fresh-install.test.js.map +1 -1
  5. package/packages/core/dist/cli/commands/e2e-upgrade.test.js +1 -1
  6. package/packages/core/dist/cli/commands/e2e-upgrade.test.js.map +1 -1
  7. package/packages/core/dist/cli/commands/theme.js +1 -1
  8. package/packages/core/dist/cli/commands/theme.js.map +1 -1
  9. package/packages/core/dist/cli/utils/themes.d.ts.map +1 -1
  10. package/packages/core/dist/cli/utils/themes.js +3 -2
  11. package/packages/core/dist/cli/utils/themes.js.map +1 -1
  12. package/packages/core/dist/scripts/add-ocean-profiles.js +1 -1
  13. package/packages/core/dist/scripts/add-ocean-profiles.js.map +1 -1
  14. package/packages/core/dist/scripts/generate-all-spiders.js +2 -0
  15. package/packages/core/dist/scripts/generate-all-spiders.js.map +1 -1
  16. package/packages/core/dist/scripts/generate-report.d.ts.map +1 -1
  17. package/packages/core/dist/scripts/generate-report.js +2 -0
  18. package/packages/core/dist/scripts/generate-report.js.map +1 -1
  19. package/packages/core/dist/scripts/generate-spider-report.d.ts.map +1 -1
  20. package/packages/core/dist/scripts/generate-spider-report.js +2 -0
  21. package/packages/core/dist/scripts/generate-spider-report.js.map +1 -1
  22. package/packages/core/dist/scripts/generate-spider.d.ts.map +1 -1
  23. package/packages/core/dist/scripts/generate-spider.js +3 -0
  24. package/packages/core/dist/scripts/generate-spider.js.map +1 -1
  25. package/packages/core/dist/scripts/validate-ocean-profiles.js +1 -1
  26. package/packages/core/dist/scripts/validate-ocean-profiles.js.map +1 -1
  27. package/packages/core/dist/workflow/file-watch.test.js.map +1 -1
  28. package/packages/core/dist/workflow/output-path-normalizer.d.ts +47 -0
  29. package/packages/core/dist/workflow/output-path-normalizer.d.ts.map +1 -0
  30. package/packages/core/dist/workflow/output-path-normalizer.js +79 -0
  31. package/packages/core/dist/workflow/output-path-normalizer.js.map +1 -0
  32. package/packages/core/dist/workflow/output-path-normalizer.test.d.ts +16 -0
  33. package/packages/core/dist/workflow/output-path-normalizer.test.d.ts.map +1 -0
  34. package/packages/core/dist/workflow/output-path-normalizer.test.js +157 -0
  35. package/packages/core/dist/workflow/output-path-normalizer.test.js.map +1 -0
  36. package/packages/core/dist/workflow/tool-watch.test.js +1 -2
  37. package/packages/core/dist/workflow/tool-watch.test.js.map +1 -1
  38. package/packages/core/dist/workflow/variable-resolver.js +1 -1
  39. package/packages/core/dist/workflow/variable-resolver.js.map +1 -1
  40. package/pennyfarthing-dist/agents/README.md +3 -1
  41. package/pennyfarthing-dist/agents/ba.md +165 -0
  42. package/pennyfarthing-dist/commands/ba.md +17 -0
  43. package/pennyfarthing-dist/guides/workflow-schema.md +1 -1
  44. package/pennyfarthing-dist/personas/themes/a-team.yaml +30 -0
  45. package/pennyfarthing-dist/personas/themes/alice-in-wonderland.yaml +30 -0
  46. package/pennyfarthing-dist/personas/themes/battlestar-galactica.yaml +30 -0
  47. package/pennyfarthing-dist/personas/themes/blade-runner.yaml +30 -0
  48. package/pennyfarthing-dist/personas/themes/catch-22.yaml +30 -0
  49. package/pennyfarthing-dist/personas/themes/control.yaml +30 -0
  50. package/pennyfarthing-dist/personas/themes/cowboy-bebop.yaml +31 -0
  51. package/pennyfarthing-dist/personas/themes/discworld.yaml +31 -0
  52. package/pennyfarthing-dist/personas/themes/doctor-who.yaml +31 -0
  53. package/pennyfarthing-dist/personas/themes/dune.yaml +32 -0
  54. package/pennyfarthing-dist/personas/themes/fifth-element.yaml +31 -0
  55. package/pennyfarthing-dist/personas/themes/firefly.yaml +31 -0
  56. package/pennyfarthing-dist/personas/themes/game-of-thrones.yaml +30 -0
  57. package/pennyfarthing-dist/personas/themes/harry-potter.yaml +30 -0
  58. package/pennyfarthing-dist/personas/themes/hitchhikers-guide.yaml +30 -0
  59. package/pennyfarthing-dist/personas/themes/lord-of-the-rings.yaml +30 -0
  60. package/pennyfarthing-dist/personas/themes/mad-max.yaml +30 -0
  61. package/pennyfarthing-dist/personas/themes/mash.yaml +33 -0
  62. package/pennyfarthing-dist/personas/themes/princess-bride.yaml +34 -0
  63. package/pennyfarthing-dist/personas/themes/sandman.yaml +33 -0
  64. package/pennyfarthing-dist/personas/themes/star-trek-tng.yaml +34 -0
  65. package/pennyfarthing-dist/personas/themes/star-wars.yaml +33 -0
  66. package/pennyfarthing-dist/personas/themes/the-expanse.yaml +30 -0
  67. package/pennyfarthing-dist/personas/themes/the-matrix.yaml +30 -0
  68. package/pennyfarthing-dist/personas/themes/watchmen.yaml +30 -0
  69. package/pennyfarthing-dist/personas/themes/west-wing.yaml +30 -0
  70. package/pennyfarthing-dist/personas/themes/x-files.yaml +30 -0
  71. package/pennyfarthing-dist/scripts/core/agent-session.sh +1 -1
  72. package/pennyfarthing-dist/scripts/core/check-context.sh +0 -0
  73. package/pennyfarthing-dist/scripts/core/handoff-marker.sh +0 -0
  74. package/pennyfarthing-dist/scripts/core/phase-check-start.sh +0 -0
  75. package/pennyfarthing-dist/scripts/core/prime.sh +0 -0
  76. package/pennyfarthing-dist/scripts/cyclist/is-cyclist.sh +0 -0
  77. package/pennyfarthing-dist/scripts/git/create-feature-branches.sh +0 -0
  78. package/pennyfarthing-dist/scripts/git/git-status-all.sh +0 -0
  79. package/pennyfarthing-dist/scripts/git/install-git-hooks.sh +0 -0
  80. package/pennyfarthing-dist/scripts/git/release.sh +0 -0
  81. package/pennyfarthing-dist/scripts/git/worktree-manager.sh +0 -0
  82. package/pennyfarthing-dist/scripts/health/drift-detection.sh +0 -0
  83. package/pennyfarthing-dist/scripts/hooks/__pycache__/question_reflector_check.cpython-314.pyc +0 -0
  84. package/pennyfarthing-dist/scripts/hooks/bell-mode-hook.sh +0 -0
  85. package/pennyfarthing-dist/scripts/hooks/context-circuit-breaker.sh +0 -0
  86. package/pennyfarthing-dist/scripts/hooks/context-warning.sh +0 -0
  87. package/pennyfarthing-dist/scripts/hooks/cyclist-pretooluse-hook.sh +0 -0
  88. package/pennyfarthing-dist/scripts/hooks/otel-auto-config.sh +0 -0
  89. package/pennyfarthing-dist/scripts/hooks/post-merge.sh +0 -0
  90. package/pennyfarthing-dist/scripts/hooks/pre-commit.sh +0 -0
  91. package/pennyfarthing-dist/scripts/hooks/pre-edit-check.sh +0 -0
  92. package/pennyfarthing-dist/scripts/hooks/pre-push.sh +0 -0
  93. package/pennyfarthing-dist/scripts/hooks/question-reflector-check.sh +0 -0
  94. package/pennyfarthing-dist/scripts/hooks/question_reflector_check.py +0 -0
  95. package/pennyfarthing-dist/scripts/hooks/schema-validation.sh +0 -0
  96. package/pennyfarthing-dist/scripts/hooks/session-start.sh +0 -0
  97. package/pennyfarthing-dist/scripts/hooks/session-stop.sh +0 -0
  98. package/pennyfarthing-dist/scripts/hooks/sprint-yaml-validation.sh +0 -0
  99. package/pennyfarthing-dist/scripts/hooks/welcome-hook.sh +0 -0
  100. package/pennyfarthing-dist/scripts/jira/create-jira-epic.sh +0 -0
  101. package/pennyfarthing-dist/scripts/jira/create-jira-story.sh +0 -0
  102. package/pennyfarthing-dist/scripts/jira/jira-claim-story.sh +0 -0
  103. package/pennyfarthing-dist/scripts/jira/jira-reconcile.sh +0 -0
  104. package/pennyfarthing-dist/scripts/jira/jira-sync-story.sh +0 -0
  105. package/pennyfarthing-dist/scripts/jira/sync-epic-jira.sh +0 -0
  106. package/pennyfarthing-dist/scripts/lib/background-tasks.sh +0 -0
  107. package/pennyfarthing-dist/scripts/lib/checkpoint.sh +0 -0
  108. package/pennyfarthing-dist/scripts/lib/common.sh +0 -0
  109. package/pennyfarthing-dist/scripts/lib/file-lock.sh +0 -0
  110. package/pennyfarthing-dist/scripts/lib/logging.sh +0 -0
  111. package/pennyfarthing-dist/scripts/lib/retry.sh +0 -0
  112. package/pennyfarthing-dist/scripts/maintenance/migrate-theme-schema.mjs +0 -0
  113. package/pennyfarthing-dist/scripts/maintenance/sidecar-health.sh +0 -0
  114. package/pennyfarthing-dist/scripts/misc/add-short-names.sh +0 -0
  115. package/pennyfarthing-dist/scripts/misc/add_short_names.py +0 -0
  116. package/pennyfarthing-dist/scripts/misc/backlog.sh +0 -0
  117. package/pennyfarthing-dist/scripts/misc/check-status.sh +0 -0
  118. package/pennyfarthing-dist/scripts/misc/find-related-work.sh +0 -0
  119. package/pennyfarthing-dist/scripts/misc/generate-skill-docs.sh +0 -0
  120. package/pennyfarthing-dist/scripts/misc/log-skill-usage.sh +0 -0
  121. package/pennyfarthing-dist/scripts/misc/migrate-bmad-workflow.sh +0 -0
  122. package/pennyfarthing-dist/scripts/misc/migrate_bmad_workflow.py +0 -0
  123. package/pennyfarthing-dist/scripts/misc/repo-scan.sh +0 -0
  124. package/pennyfarthing-dist/scripts/misc/repo-utils.sh +0 -0
  125. package/pennyfarthing-dist/scripts/misc/run-ci.sh +0 -0
  126. package/pennyfarthing-dist/scripts/misc/run-timestamp.sh +0 -0
  127. package/pennyfarthing-dist/scripts/misc/session-cleanup.sh +0 -0
  128. package/pennyfarthing-dist/scripts/misc/skill-usage-report.sh +0 -0
  129. package/pennyfarthing-dist/scripts/misc/statusline.sh +2 -0
  130. package/pennyfarthing-dist/scripts/misc/uninstall.sh +0 -0
  131. package/pennyfarthing-dist/scripts/misc/validate-subagent-frontmatter.sh +0 -0
  132. package/pennyfarthing-dist/scripts/portraits/generate-portraits.py +2 -2
  133. package/pennyfarthing-dist/scripts/portraits/generate-portraits.sh +0 -0
  134. package/pennyfarthing-dist/scripts/story/create-story.sh +0 -0
  135. package/pennyfarthing-dist/scripts/story/size-story.sh +0 -0
  136. package/pennyfarthing-dist/scripts/story/story-template.sh +0 -0
  137. package/pennyfarthing-dist/scripts/tests/check.test.sh +0 -0
  138. package/pennyfarthing-dist/scripts/tests/dev-story-workflow-import.test.sh +0 -0
  139. package/pennyfarthing-dist/scripts/tests/epics-and-stories-workflow-import.test.sh +0 -0
  140. package/pennyfarthing-dist/scripts/tests/handoff-phase-update.test.sh +0 -0
  141. package/pennyfarthing-dist/scripts/tests/implementation-readiness-workflow-import.test.sh +0 -0
  142. package/pennyfarthing-dist/scripts/tests/migrate-bmad-workflow.test.sh +0 -0
  143. package/pennyfarthing-dist/scripts/tests/prd-workflow-import.test.sh +0 -0
  144. package/pennyfarthing-dist/scripts/tests/project-context-workflow-import.test.sh +0 -0
  145. package/pennyfarthing-dist/scripts/tests/test-character-voice.sh +0 -0
  146. package/pennyfarthing-dist/scripts/tests/test-drift-detection.sh +0 -0
  147. package/pennyfarthing-dist/scripts/tests/test-post-merge-hook.sh +0 -0
  148. package/pennyfarthing-dist/scripts/tests/test-session-checkpoint.sh +0 -0
  149. package/pennyfarthing-dist/scripts/tests/test-solo-command.sh +0 -0
  150. package/pennyfarthing-dist/scripts/tests/ux-design-workflow-import.test.sh +0 -0
  151. package/pennyfarthing-dist/scripts/theme/list-themes.sh +0 -0
  152. package/pennyfarthing-dist/scripts/validation/validate-agent-schema.sh +1 -0
  153. package/pennyfarthing-dist/scripts/workflow/check.py +0 -0
  154. package/pennyfarthing-dist/scripts/workflow/check.sh +0 -0
  155. package/pennyfarthing-dist/scripts/workflow/complete-step.py +0 -0
  156. package/pennyfarthing-dist/scripts/workflow/finish-story.sh +0 -0
  157. package/pennyfarthing-dist/scripts/workflow/fix-session-phase.sh +0 -0
  158. package/pennyfarthing-dist/scripts/workflow/get-workflow-type.py +0 -0
  159. package/pennyfarthing-dist/scripts/workflow/get-workflow-type.sh +0 -0
  160. package/pennyfarthing-dist/scripts/workflow/list-workflows.sh +0 -0
  161. package/pennyfarthing-dist/scripts/workflow/phase-owner.sh +0 -0
  162. package/pennyfarthing-dist/scripts/workflow/resume-workflow.sh +0 -0
  163. package/pennyfarthing-dist/scripts/workflow/show-workflow.sh +0 -0
  164. package/pennyfarthing-dist/scripts/workflow/start-workflow.sh +0 -0
  165. package/pennyfarthing-dist/scripts/workflow/workflow-status.sh +0 -0
  166. package/pennyfarthing-dist/skills/story/scripts/create-story.sh +0 -0
  167. package/pennyfarthing-dist/skills/story/scripts/size-story.sh +0 -0
  168. package/pennyfarthing-dist/skills/story/scripts/story-template.sh +0 -0
  169. package/pennyfarthing-dist/skills/theme/skill.md +1 -1
  170. package/pennyfarthing-dist/skills/workflow/scripts/list-workflows.sh +0 -0
  171. package/pennyfarthing-dist/skills/workflow/scripts/resume-workflow.sh +0 -0
  172. package/pennyfarthing-dist/skills/workflow/scripts/show-workflow.sh +0 -0
  173. package/pennyfarthing-dist/skills/workflow/scripts/start-workflow.sh +0 -0
  174. package/pennyfarthing-dist/skills/workflow/scripts/workflow-status.sh +0 -0
  175. package/pennyfarthing-dist/workflows/architecture/workflow.yaml +2 -2
  176. package/pennyfarthing-dist/workflows/architecture.yaml +2 -2
  177. package/pennyfarthing-dist/workflows/epics-and-stories/workflow.yaml +2 -2
  178. package/pennyfarthing-dist/workflows/implementation-readiness/workflow.yaml +2 -2
  179. package/pennyfarthing-dist/workflows/prd/workflow.yaml +2 -2
  180. package/pennyfarthing-dist/workflows/product-brief/workflow.yaml +2 -2
  181. package/pennyfarthing-dist/workflows/project-context/workflow.yaml +2 -2
  182. package/pennyfarthing-dist/workflows/quick-dev/workflow.yaml +2 -2
  183. package/pennyfarthing-dist/workflows/research/workflow.yaml +2 -2
  184. package/pennyfarthing-dist/workflows/retrospective/workflow.yaml +1 -1
  185. package/pennyfarthing-dist/workflows/sprint-planning/workflow.yaml +3 -3
  186. package/pennyfarthing-dist/workflows/ux-design/workflow.yaml +2 -2
  187. package/pennyfarthing_scripts/__pycache__/__init__.cpython-311.pyc +0 -0
  188. package/pennyfarthing_scripts/__pycache__/__init__.cpython-314.pyc +0 -0
  189. package/pennyfarthing_scripts/__pycache__/cli.cpython-314.pyc +0 -0
  190. package/pennyfarthing_scripts/__pycache__/config.cpython-314.pyc +0 -0
  191. package/pennyfarthing_scripts/__pycache__/hooks.cpython-314.pyc +0 -0
  192. package/pennyfarthing_scripts/__pycache__/jira.cpython-314.pyc +0 -0
  193. package/pennyfarthing_scripts/__pycache__/jira_bidirectional_sync.cpython-314.pyc +0 -0
  194. package/pennyfarthing_scripts/__pycache__/jira_epic_creation.cpython-314.pyc +0 -0
  195. package/pennyfarthing_scripts/__pycache__/jira_sync.cpython-314.pyc +0 -0
  196. package/pennyfarthing_scripts/__pycache__/jira_sync_story.cpython-314.pyc +0 -0
  197. package/pennyfarthing_scripts/__pycache__/output.cpython-314.pyc +0 -0
  198. package/pennyfarthing_scripts/__pycache__/patch_mode.cpython-314.pyc +0 -0
  199. package/pennyfarthing_scripts/__pycache__/pretooluse_hook.cpython-314.pyc +0 -0
  200. package/pennyfarthing_scripts/__pycache__/schema_validation_hook.cpython-314.pyc +0 -0
  201. package/pennyfarthing_scripts/__pycache__/sprint.cpython-314.pyc +0 -0
  202. package/pennyfarthing_scripts/__pycache__/workflow.cpython-311.pyc +0 -0
  203. package/pennyfarthing_scripts/__pycache__/workflow.cpython-314.pyc +0 -0
  204. package/pennyfarthing_scripts/bellmode_hook.py +1 -1
  205. package/pennyfarthing_scripts/bikerack/__init__.py +36 -0
  206. package/pennyfarthing_scripts/bikerack/__main__.py +5 -0
  207. package/pennyfarthing_scripts/bikerack/__pycache__/__init__.cpython-314.pyc +0 -0
  208. package/pennyfarthing_scripts/bikerack/__pycache__/__main__.cpython-314.pyc +0 -0
  209. package/pennyfarthing_scripts/bikerack/__pycache__/cli.cpython-314.pyc +0 -0
  210. package/pennyfarthing_scripts/bikerack/__pycache__/launcher.cpython-314.pyc +0 -0
  211. package/pennyfarthing_scripts/bikerack/cli.py +148 -0
  212. package/pennyfarthing_scripts/bikerack/launcher.py +181 -0
  213. package/pennyfarthing_scripts/brownfield/__pycache__/__init__.cpython-314.pyc +0 -0
  214. package/pennyfarthing_scripts/brownfield/__pycache__/__main__.cpython-314.pyc +0 -0
  215. package/pennyfarthing_scripts/brownfield/__pycache__/cli.cpython-314.pyc +0 -0
  216. package/pennyfarthing_scripts/brownfield/__pycache__/discover.cpython-314.pyc +0 -0
  217. package/pennyfarthing_scripts/cli.py +5 -0
  218. package/pennyfarthing_scripts/codemarkers/__pycache__/__init__.cpython-314.pyc +0 -0
  219. package/pennyfarthing_scripts/codemarkers/__pycache__/__main__.cpython-314.pyc +0 -0
  220. package/pennyfarthing_scripts/codemarkers/__pycache__/analyze.cpython-314.pyc +0 -0
  221. package/pennyfarthing_scripts/codemarkers/__pycache__/cli.cpython-314.pyc +0 -0
  222. package/pennyfarthing_scripts/codemarkers/__pycache__/formatters.cpython-314.pyc +0 -0
  223. package/pennyfarthing_scripts/codemarkers/__pycache__/models.cpython-314.pyc +0 -0
  224. package/pennyfarthing_scripts/common/__pycache__/__init__.cpython-314.pyc +0 -0
  225. package/pennyfarthing_scripts/common/__pycache__/config.cpython-314.pyc +0 -0
  226. package/pennyfarthing_scripts/common/__pycache__/output.cpython-314.pyc +0 -0
  227. package/pennyfarthing_scripts/common/__pycache__/themes.cpython-314.pyc +0 -0
  228. package/pennyfarthing_scripts/complexity/__pycache__/__init__.cpython-314.pyc +0 -0
  229. package/pennyfarthing_scripts/complexity/__pycache__/__main__.cpython-314.pyc +0 -0
  230. package/pennyfarthing_scripts/complexity/__pycache__/analyze.cpython-314.pyc +0 -0
  231. package/pennyfarthing_scripts/complexity/__pycache__/cli.cpython-314.pyc +0 -0
  232. package/pennyfarthing_scripts/complexity/__pycache__/formatters.cpython-314.pyc +0 -0
  233. package/pennyfarthing_scripts/complexity/__pycache__/models.cpython-314.pyc +0 -0
  234. package/pennyfarthing_scripts/deadcode/__pycache__/__init__.cpython-314.pyc +0 -0
  235. package/pennyfarthing_scripts/deadcode/__pycache__/__main__.cpython-314.pyc +0 -0
  236. package/pennyfarthing_scripts/deadcode/__pycache__/analyze.cpython-314.pyc +0 -0
  237. package/pennyfarthing_scripts/deadcode/__pycache__/cli.cpython-314.pyc +0 -0
  238. package/pennyfarthing_scripts/deadcode/__pycache__/formatters.cpython-314.pyc +0 -0
  239. package/pennyfarthing_scripts/deadcode/__pycache__/models.cpython-314.pyc +0 -0
  240. package/pennyfarthing_scripts/dependencies/__pycache__/__init__.cpython-314.pyc +0 -0
  241. package/pennyfarthing_scripts/dependencies/__pycache__/__main__.cpython-314.pyc +0 -0
  242. package/pennyfarthing_scripts/dependencies/__pycache__/analyze.cpython-314.pyc +0 -0
  243. package/pennyfarthing_scripts/dependencies/__pycache__/cli.cpython-314.pyc +0 -0
  244. package/pennyfarthing_scripts/dependencies/__pycache__/formatters.cpython-314.pyc +0 -0
  245. package/pennyfarthing_scripts/dependencies/__pycache__/models.cpython-314.pyc +0 -0
  246. package/pennyfarthing_scripts/git/__pycache__/__init__.cpython-314.pyc +0 -0
  247. package/pennyfarthing_scripts/git/__pycache__/create_branches.cpython-314.pyc +0 -0
  248. package/pennyfarthing_scripts/git/__pycache__/status_all.cpython-314.pyc +0 -0
  249. package/pennyfarthing_scripts/healthscore/__pycache__/__init__.cpython-314.pyc +0 -0
  250. package/pennyfarthing_scripts/healthscore/__pycache__/__main__.cpython-314.pyc +0 -0
  251. package/pennyfarthing_scripts/healthscore/__pycache__/analyze.cpython-314.pyc +0 -0
  252. package/pennyfarthing_scripts/healthscore/__pycache__/cli.cpython-314.pyc +0 -0
  253. package/pennyfarthing_scripts/healthscore/__pycache__/formatters.cpython-314.pyc +0 -0
  254. package/pennyfarthing_scripts/healthscore/__pycache__/models.cpython-314.pyc +0 -0
  255. package/pennyfarthing_scripts/healthscore/analyze.py +2 -1
  256. package/pennyfarthing_scripts/hooks/cyclist-pretooluse-hook.sh +0 -0
  257. package/pennyfarthing_scripts/hotspots/__pycache__/__init__.cpython-314.pyc +0 -0
  258. package/pennyfarthing_scripts/hotspots/__pycache__/__main__.cpython-314.pyc +0 -0
  259. package/pennyfarthing_scripts/hotspots/__pycache__/analyze.cpython-314.pyc +0 -0
  260. package/pennyfarthing_scripts/hotspots/__pycache__/cli.cpython-314.pyc +0 -0
  261. package/pennyfarthing_scripts/hotspots/__pycache__/formatters.cpython-314.pyc +0 -0
  262. package/pennyfarthing_scripts/hotspots/__pycache__/models.cpython-314.pyc +0 -0
  263. package/pennyfarthing_scripts/jira/__pycache__/__init__.cpython-314.pyc +0 -0
  264. package/pennyfarthing_scripts/jira/__pycache__/__main__.cpython-314.pyc +0 -0
  265. package/pennyfarthing_scripts/jira/__pycache__/bidirectional.cpython-314.pyc +0 -0
  266. package/pennyfarthing_scripts/jira/__pycache__/claim.cpython-314.pyc +0 -0
  267. package/pennyfarthing_scripts/jira/__pycache__/cli.cpython-314.pyc +0 -0
  268. package/pennyfarthing_scripts/jira/__pycache__/client.cpython-314.pyc +0 -0
  269. package/pennyfarthing_scripts/jira/__pycache__/compat.cpython-314.pyc +0 -0
  270. package/pennyfarthing_scripts/jira/__pycache__/create.cpython-314.pyc +0 -0
  271. package/pennyfarthing_scripts/jira/__pycache__/epic.cpython-314.pyc +0 -0
  272. package/pennyfarthing_scripts/jira/__pycache__/mappings.cpython-314.pyc +0 -0
  273. package/pennyfarthing_scripts/jira/__pycache__/models.cpython-314.pyc +0 -0
  274. package/pennyfarthing_scripts/jira/__pycache__/operations.cpython-314.pyc +0 -0
  275. package/pennyfarthing_scripts/jira/__pycache__/reconcile.cpython-314.pyc +0 -0
  276. package/pennyfarthing_scripts/jira/__pycache__/story.cpython-314.pyc +0 -0
  277. package/pennyfarthing_scripts/jira/__pycache__/sync.cpython-314.pyc +0 -0
  278. package/pennyfarthing_scripts/migration/__pycache__/__init__.cpython-314.pyc +0 -0
  279. package/pennyfarthing_scripts/migration/__pycache__/__main__.cpython-314.pyc +0 -0
  280. package/pennyfarthing_scripts/migration/__pycache__/cli.cpython-314.pyc +0 -0
  281. package/pennyfarthing_scripts/migration/__pycache__/session.cpython-314.pyc +0 -0
  282. package/pennyfarthing_scripts/migration/__pycache__/skill.cpython-314.pyc +0 -0
  283. package/pennyfarthing_scripts/migration/__pycache__/step.cpython-314.pyc +0 -0
  284. package/pennyfarthing_scripts/migration/__pycache__/validate.cpython-314.pyc +0 -0
  285. package/pennyfarthing_scripts/preflight/__pycache__/__init__.cpython-314.pyc +0 -0
  286. package/pennyfarthing_scripts/preflight/__pycache__/__main__.cpython-314.pyc +0 -0
  287. package/pennyfarthing_scripts/preflight/__pycache__/cli.cpython-314.pyc +0 -0
  288. package/pennyfarthing_scripts/preflight/__pycache__/finish.cpython-314.pyc +0 -0
  289. package/pennyfarthing_scripts/prime/__init__.py +2 -0
  290. package/pennyfarthing_scripts/prime/__pycache__/__init__.cpython-314.pyc +0 -0
  291. package/pennyfarthing_scripts/prime/__pycache__/__main__.cpython-314.pyc +0 -0
  292. package/pennyfarthing_scripts/prime/__pycache__/cli.cpython-314.pyc +0 -0
  293. package/pennyfarthing_scripts/prime/__pycache__/loader.cpython-314.pyc +0 -0
  294. package/pennyfarthing_scripts/prime/__pycache__/models.cpython-314.pyc +0 -0
  295. package/pennyfarthing_scripts/prime/__pycache__/persona.cpython-314.pyc +0 -0
  296. package/pennyfarthing_scripts/prime/__pycache__/session.cpython-314.pyc +0 -0
  297. package/pennyfarthing_scripts/prime/__pycache__/tiers.cpython-314.pyc +0 -0
  298. package/pennyfarthing_scripts/prime/__pycache__/workflow.cpython-314.pyc +0 -0
  299. package/pennyfarthing_scripts/prime/cli.py +13 -0
  300. package/pennyfarthing_scripts/prime/loader.py +70 -0
  301. package/pennyfarthing_scripts/prime/persona.py +2 -1
  302. package/pennyfarthing_scripts/prime/tiers.py +13 -0
  303. package/pennyfarthing_scripts/sprint/__pycache__/__init__.cpython-314.pyc +0 -0
  304. package/pennyfarthing_scripts/sprint/__pycache__/__main__.cpython-314.pyc +0 -0
  305. package/pennyfarthing_scripts/sprint/__pycache__/archive.cpython-314.pyc +0 -0
  306. package/pennyfarthing_scripts/sprint/__pycache__/archive_epic.cpython-314.pyc +0 -0
  307. package/pennyfarthing_scripts/sprint/__pycache__/cli.cpython-314.pyc +0 -0
  308. package/pennyfarthing_scripts/sprint/__pycache__/epic_add.cpython-314.pyc +0 -0
  309. package/pennyfarthing_scripts/sprint/__pycache__/import_epic.cpython-314.pyc +0 -0
  310. package/pennyfarthing_scripts/sprint/__pycache__/loader.cpython-314.pyc +0 -0
  311. package/pennyfarthing_scripts/sprint/__pycache__/status.cpython-314.pyc +0 -0
  312. package/pennyfarthing_scripts/sprint/__pycache__/story_add.cpython-314.pyc +0 -0
  313. package/pennyfarthing_scripts/sprint/__pycache__/story_finish.cpython-314.pyc +0 -0
  314. package/pennyfarthing_scripts/sprint/__pycache__/story_update.cpython-314.pyc +0 -0
  315. package/pennyfarthing_scripts/sprint/__pycache__/validate_cmd.cpython-314.pyc +0 -0
  316. package/pennyfarthing_scripts/sprint/__pycache__/validator.cpython-314.pyc +0 -0
  317. package/pennyfarthing_scripts/sprint/__pycache__/work.cpython-314.pyc +0 -0
  318. package/pennyfarthing_scripts/sprint/__pycache__/yaml_io.cpython-314.pyc +0 -0
  319. package/pennyfarthing_scripts/story/__pycache__/__init__.cpython-314.pyc +0 -0
  320. package/pennyfarthing_scripts/story/__pycache__/__main__.cpython-314.pyc +0 -0
  321. package/pennyfarthing_scripts/story/__pycache__/cli.cpython-314.pyc +0 -0
  322. package/pennyfarthing_scripts/story/__pycache__/create.cpython-314.pyc +0 -0
  323. package/pennyfarthing_scripts/story/__pycache__/size.cpython-314.pyc +0 -0
  324. package/pennyfarthing_scripts/story/__pycache__/template.cpython-314.pyc +0 -0
  325. package/pennyfarthing_scripts/tests/__pycache__/__init__.cpython-314.pyc +0 -0
  326. package/pennyfarthing_scripts/tests/__pycache__/conftest.cpython-314-pytest-9.0.2.pyc +0 -0
  327. package/pennyfarthing_scripts/tests/__pycache__/test_bikerack.cpython-314-pytest-9.0.2.pyc +0 -0
  328. package/pennyfarthing_scripts/tests/__pycache__/test_brownfield.cpython-314-pytest-9.0.2.pyc +0 -0
  329. package/pennyfarthing_scripts/tests/__pycache__/test_cli_modules.cpython-314-pytest-9.0.2.pyc +0 -0
  330. package/pennyfarthing_scripts/tests/__pycache__/test_codemarkers.cpython-314-pytest-9.0.2.pyc +0 -0
  331. package/pennyfarthing_scripts/tests/__pycache__/test_common.cpython-314-pytest-9.0.2.pyc +0 -0
  332. package/pennyfarthing_scripts/tests/__pycache__/test_epic_shard_validation.cpython-314-pytest-9.0.2.pyc +0 -0
  333. package/pennyfarthing_scripts/tests/__pycache__/test_git_utils.cpython-314-pytest-9.0.2.pyc +0 -0
  334. package/pennyfarthing_scripts/tests/__pycache__/test_healthscore.cpython-314-pytest-9.0.2.pyc +0 -0
  335. package/pennyfarthing_scripts/tests/__pycache__/test_jira_package.cpython-314-pytest-9.0.2.pyc +0 -0
  336. package/pennyfarthing_scripts/tests/__pycache__/test_package_structure.cpython-314-pytest-9.0.2.pyc +0 -0
  337. package/pennyfarthing_scripts/tests/__pycache__/test_patch_mode.cpython-314-pytest-9.0.2.pyc +0 -0
  338. package/pennyfarthing_scripts/tests/__pycache__/test_prime.cpython-314-pytest-9.0.2.pyc +0 -0
  339. package/pennyfarthing_scripts/tests/__pycache__/test_sprint_package.cpython-314-pytest-9.0.2.pyc +0 -0
  340. package/pennyfarthing_scripts/tests/__pycache__/test_sprint_validator.cpython-314-pytest-9.0.2.pyc +0 -0
  341. package/pennyfarthing_scripts/tests/__pycache__/test_story_add.cpython-314-pytest-9.0.2.pyc +0 -0
  342. package/pennyfarthing_scripts/tests/__pycache__/test_story_package.cpython-314-pytest-9.0.2.pyc +0 -0
  343. package/pennyfarthing_scripts/tests/__pycache__/test_story_update.cpython-314-pytest-9.0.2.pyc +0 -0
  344. package/pennyfarthing_scripts/tests/__pycache__/test_tiers.cpython-314-pytest-9.0.2.pyc +0 -0
  345. package/pennyfarthing_scripts/tests/__pycache__/test_token_counting.cpython-314-pytest-9.0.2.pyc +0 -0
  346. package/pennyfarthing_scripts/tests/__pycache__/test_validate_cmd.cpython-314-pytest-9.0.2.pyc +0 -0
  347. package/pennyfarthing_scripts/tests/__pycache__/test_workflow_check.cpython-314-pytest-9.0.2.pyc +0 -0
  348. package/pennyfarthing_scripts/tests/__pycache__/test_workflow_cli.cpython-314-pytest-9.0.2.pyc +0 -0
  349. package/pennyfarthing_scripts/tests/__pycache__/test_yaml_io.cpython-314-pytest-9.0.2.pyc +0 -0
  350. package/pennyfarthing_scripts/tests/test_bikerack.py +785 -0
  351. package/pennyfarthing_scripts/tests/test_topology_loader.py +620 -0
  352. package/pennyfarthing_scripts/theme/__pycache__/__init__.cpython-314.pyc +0 -0
  353. package/pennyfarthing_scripts/theme/__pycache__/cli.cpython-314.pyc +0 -0
  354. package/pennyfarthing_scripts/validate/__pycache__/__init__.cpython-314.pyc +0 -0
  355. package/pennyfarthing_scripts/validate/__pycache__/cli.cpython-314.pyc +0 -0
  356. package/pennyfarthing_scripts/validate/adapters/__pycache__/__init__.cpython-314.pyc +0 -0
  357. package/pennyfarthing_scripts/validate/adapters/__pycache__/agent.cpython-314.pyc +0 -0
  358. package/pennyfarthing_scripts/validate/adapters/__pycache__/schema.cpython-314.pyc +0 -0
  359. package/pennyfarthing_scripts/validate/adapters/__pycache__/skill_command.cpython-314.pyc +0 -0
  360. package/pennyfarthing_scripts/validate/adapters/__pycache__/sprint.cpython-314.pyc +0 -0
  361. package/pennyfarthing_scripts/validate/adapters/__pycache__/workflow.cpython-314.pyc +0 -0
  362. package/pennyfarthing_scripts/validate/adapters/skill_command.py +0 -1
  363. package/packages/core/dist/workflow/context-watch.d.ts +0 -80
  364. package/packages/core/dist/workflow/context-watch.d.ts.map +0 -1
  365. package/packages/core/dist/workflow/context-watch.js +0 -235
  366. package/packages/core/dist/workflow/context-watch.js.map +0 -1
  367. package/packages/core/dist/workflow/context-watch.test.d.ts +0 -1
  368. package/packages/core/dist/workflow/context-watch.test.d.ts.map +0 -1
  369. package/packages/core/dist/workflow/context-watch.test.js +0 -746
  370. package/packages/core/dist/workflow/context-watch.test.js.map +0 -1
  371. package/pennyfarthing_scripts/__pycache__/bellmode_hook.cpython-314.pyc +0 -0
  372. package/scripts/README.md +0 -41
@@ -26,8 +26,8 @@ workflow:
26
26
 
27
27
  # Variables available in step files
28
28
  variables:
29
- output_file: planning-artifacts/architecture.md
30
- planning_artifacts: planning-artifacts
29
+ output_file: sprint/planning/architecture.md
30
+ planning_artifacts: sprint/planning/
31
31
  input_required:
32
32
  - prd
33
33
 
@@ -16,8 +16,8 @@ workflow:
16
16
  # Variables available in step files
17
17
  variables:
18
18
  project_root: .
19
- planning_artifacts: ./artifacts
20
- output_file: artifacts/create-epics-and-stories.md
19
+ planning_artifacts: sprint/planning/
20
+ output_file: sprint/planning/create-epics-and-stories.md
21
21
 
22
22
  # Agent assignment (customize as needed)
23
23
  agent: architect
@@ -28,8 +28,8 @@ workflow:
28
28
  # Variables available in step files
29
29
  variables:
30
30
  project_root: .
31
- planning_artifacts: ./artifacts
32
- output_file: artifacts/implementation-readiness-report.md
31
+ planning_artifacts: sprint/planning/
32
+ output_file: sprint/planning/implementation-readiness-report.md
33
33
 
34
34
  # Agent assignment - PM/SM role for adversarial review
35
35
  agent: sm
@@ -22,8 +22,8 @@ workflow:
22
22
  # Variables available in step files
23
23
  variables:
24
24
  project_root: .
25
- planning_artifacts: ./artifacts
26
- output_file: artifacts/prd.md
25
+ planning_artifacts: sprint/planning/
26
+ output_file: sprint/planning/prd.md
27
27
 
28
28
  # Gates - pause for user approval at key decision points
29
29
  gates:
@@ -16,8 +16,8 @@ workflow:
16
16
  # Variables available in step files
17
17
  variables:
18
18
  project_root: .
19
- planning_artifacts: ./artifacts
20
- output_file: artifacts/product-brief.md
19
+ planning_artifacts: sprint/planning/
20
+ output_file: sprint/planning/product-brief.md
21
21
 
22
22
  # Template for product brief output
23
23
  template: ./templates/product-brief.template.md
@@ -15,8 +15,8 @@ workflow:
15
15
  # Variables available in step files
16
16
  variables:
17
17
  project_root: .
18
- planning_artifacts: ./artifacts
19
- output_file: artifacts/generate-project-context.md
18
+ planning_artifacts: sprint/planning/
19
+ output_file: sprint/planning/generate-project-context.md
20
20
 
21
21
  # Agent assignment (customize as needed)
22
22
  agent: architect
@@ -15,8 +15,8 @@ workflow:
15
15
  # Variables available in step files
16
16
  variables:
17
17
  project_root: .
18
- planning_artifacts: ./artifacts
19
- output_file: artifacts/quick-dev.md
18
+ planning_artifacts: sprint/planning/
19
+ output_file: sprint/planning/quick-dev.md
20
20
 
21
21
  # Agent assignment (customize as needed)
22
22
  agent: architect
@@ -23,8 +23,8 @@ workflow:
23
23
  # Variables available in step files
24
24
  variables:
25
25
  project_root: .
26
- planning_artifacts: ./artifacts
27
- output_file: artifacts/research.md
26
+ planning_artifacts: sprint/planning/
27
+ output_file: sprint/planning/research.md
28
28
  research_type: ""
29
29
  research_topic: ""
30
30
  research_goals: ""
@@ -38,7 +38,7 @@ workflow:
38
38
  variables:
39
39
  project_root: .
40
40
  config_source: "./_bmad/bmm/config.yaml"
41
- planning_artifacts: ./artifacts/planning
41
+ planning_artifacts: sprint/planning/
42
42
  implementation_artifacts: ./artifacts
43
43
  sprint_status_file: ./artifacts/sprint-status.yaml
44
44
  story_directory: ./artifacts/stories
@@ -15,12 +15,12 @@ workflow:
15
15
  # Variables available in step files
16
16
  variables:
17
17
  project_root: .
18
- planning_artifacts: ./artifacts
18
+ planning_artifacts: sprint/planning/
19
19
  implementation_artifacts: ./artifacts
20
20
  epics_location: ./artifacts
21
21
  epics_pattern: "epic*.md"
22
- status_file: artifacts/sprint-status.yaml
23
- output_file: artifacts/sprint-status.yaml
22
+ status_file: sprint/planning/sprint-status.yaml
23
+ output_file: sprint/planning/sprint-status.yaml
24
24
 
25
25
  # Agent assignment - SM handles sprint planning
26
26
  agent: sm
@@ -29,8 +29,8 @@ workflow:
29
29
  # Variables available in step files
30
30
  variables:
31
31
  project_root: .
32
- planning_artifacts: ./artifacts
33
- output_file: artifacts/ux-design-specification.md
32
+ planning_artifacts: sprint/planning/
33
+ output_file: sprint/planning/ux-design-specification.md
34
34
 
35
35
  # Agent assignment - UX Designer for visual design workflow
36
36
  agent: ux-designer
@@ -150,7 +150,7 @@ def get_latest_observation(file_content: str) -> dict | None:
150
150
  last_entry = re.sub(r"\n---\s*$", "", last_entry).strip()
151
151
  # Remove the **Trigger:** line
152
152
  lines = last_entry.split("\n")
153
- text_lines = [l for l in lines if not l.startswith("**Trigger:**")]
153
+ text_lines = [line for line in lines if not line.startswith("**Trigger:**")]
154
154
  text = "\n".join(text_lines).strip()
155
155
 
156
156
  if not text:
@@ -0,0 +1,36 @@
1
+ """BikeRack Mode — Decoupled WheelHub launcher for CLI-first developers.
2
+
3
+ Story 101-5: BikeRack launcher CLI (pf bikerack start/stop/status)
4
+ """
5
+
6
+ from pennyfarthing_scripts.bikerack.launcher import (
7
+ build_otel_env,
8
+ cleanup_files,
9
+ exec_claude,
10
+ get_status,
11
+ is_already_running,
12
+ is_process_alive,
13
+ poll_for_port_file,
14
+ read_pid_file,
15
+ read_port_file,
16
+ register_cleanup,
17
+ start_wheelhub,
18
+ stop_bikerack,
19
+ write_pid_file,
20
+ )
21
+
22
+ __all__ = [
23
+ "build_otel_env",
24
+ "cleanup_files",
25
+ "exec_claude",
26
+ "get_status",
27
+ "is_already_running",
28
+ "is_process_alive",
29
+ "poll_for_port_file",
30
+ "read_pid_file",
31
+ "read_port_file",
32
+ "register_cleanup",
33
+ "start_wheelhub",
34
+ "stop_bikerack",
35
+ "write_pid_file",
36
+ ]
@@ -0,0 +1,5 @@
1
+ """Entry point for python -m pennyfarthing_scripts.bikerack."""
2
+
3
+ from pennyfarthing_scripts.bikerack.cli import bikerack
4
+
5
+ bikerack()
@@ -0,0 +1,148 @@
1
+ """BikeRack CLI — Click-based CLI for bikerack operations.
2
+
3
+ Usage:
4
+ pf bikerack [COMMAND]
5
+
6
+ Commands:
7
+ start Start BikeRack mode (default)
8
+ stop Stop running BikeRack instance
9
+ status Show running state
10
+ """
11
+
12
+ import os
13
+ import sys
14
+ from pathlib import Path
15
+
16
+ import click
17
+
18
+
19
+ @click.group(invoke_without_command=True)
20
+ @click.pass_context
21
+ def bikerack(ctx):
22
+ """BikeRack Mode — Decoupled WheelHub dashboard launcher.
23
+
24
+ \b
25
+ Commands:
26
+ start - Start WheelHub + Claude CLI (default)
27
+ stop - Stop running BikeRack instance
28
+ status - Show running state (PID, port, uptime)
29
+ """
30
+ if ctx.invoked_subcommand is None:
31
+ ctx.invoke(start)
32
+
33
+
34
+ @bikerack.command()
35
+ @click.option(
36
+ "--project-dir",
37
+ type=click.Path(exists=True, file_okay=False, resolve_path=True),
38
+ default=None,
39
+ help="Project directory (where .pennyfarthing/ lives). Falls back to CYCLIST_PROJECT_DIR env var, then cwd.",
40
+ )
41
+ def start(project_dir):
42
+ """Start BikeRack mode.
43
+
44
+ Starts WheelHub in background, waits for readiness,
45
+ sets OTEL env vars, and execs Claude CLI.
46
+ """
47
+ from pennyfarthing_scripts.bikerack.launcher import (
48
+ build_otel_env,
49
+ exec_claude,
50
+ is_already_running,
51
+ poll_for_port_file,
52
+ register_cleanup,
53
+ start_wheelhub,
54
+ write_pid_file,
55
+ )
56
+
57
+ if project_dir:
58
+ project_dir = Path(project_dir)
59
+ elif os.environ.get("CYCLIST_PROJECT_DIR"):
60
+ project_dir = Path(os.environ["CYCLIST_PROJECT_DIR"])
61
+ else:
62
+ project_dir = Path.cwd()
63
+
64
+ running, pid, port = is_already_running(project_dir)
65
+ if running:
66
+ click.echo(
67
+ f"Error: BikeRack is already running (PID {pid}, port {port})",
68
+ err=True,
69
+ )
70
+ click.echo("Use 'pf bikerack stop' to stop it.", err=True)
71
+ sys.exit(2)
72
+
73
+ click.echo("Starting BikeRack mode...")
74
+ try:
75
+ proc = start_wheelhub(project_dir)
76
+ write_pid_file(project_dir, proc.pid)
77
+
78
+ port = poll_for_port_file(project_dir)
79
+ click.echo(f"WheelHub listening on http://localhost:{port}")
80
+
81
+ otel_env = build_otel_env(port)
82
+ click.echo("Setting OTEL environment variables...")
83
+
84
+ register_cleanup(project_dir, proc.pid)
85
+
86
+ click.echo(f"Dashboard: http://localhost:{port}/bikerack")
87
+ click.echo("Starting Claude CLI...")
88
+ exec_claude(otel_env, project_dir)
89
+ except TimeoutError as e:
90
+ click.echo(f"Error: {e}", err=True)
91
+ sys.exit(1)
92
+ except Exception as e:
93
+ click.echo(f"Error: {e}", err=True)
94
+ sys.exit(1)
95
+
96
+
97
+ @bikerack.command()
98
+ @click.option(
99
+ "--project-dir",
100
+ type=click.Path(exists=True, file_okay=False, resolve_path=True),
101
+ default=None,
102
+ help="Project directory. Falls back to CYCLIST_PROJECT_DIR env var, then cwd.",
103
+ )
104
+ def stop(project_dir):
105
+ """Stop running BikeRack instance."""
106
+ from pennyfarthing_scripts.bikerack.launcher import stop_bikerack
107
+
108
+ if project_dir:
109
+ project_dir = Path(project_dir)
110
+ elif os.environ.get("CYCLIST_PROJECT_DIR"):
111
+ project_dir = Path(os.environ["CYCLIST_PROJECT_DIR"])
112
+ else:
113
+ project_dir = Path.cwd()
114
+ result = stop_bikerack(project_dir)
115
+
116
+ if result["success"]:
117
+ click.echo(result["message"])
118
+ else:
119
+ click.echo(result["message"], err=True)
120
+ sys.exit(1)
121
+
122
+
123
+ @bikerack.command()
124
+ @click.option(
125
+ "--project-dir",
126
+ type=click.Path(exists=True, file_okay=False, resolve_path=True),
127
+ default=None,
128
+ help="Project directory. Falls back to CYCLIST_PROJECT_DIR env var, then cwd.",
129
+ )
130
+ def status(project_dir):
131
+ """Show BikeRack running state."""
132
+ from pennyfarthing_scripts.bikerack.launcher import get_status
133
+
134
+ if project_dir:
135
+ project_dir = Path(project_dir)
136
+ elif os.environ.get("CYCLIST_PROJECT_DIR"):
137
+ project_dir = Path(os.environ["CYCLIST_PROJECT_DIR"])
138
+ else:
139
+ project_dir = Path.cwd()
140
+ result = get_status(project_dir)
141
+
142
+ if result["running"]:
143
+ click.echo("BikeRack is running")
144
+ click.echo(f" PID: {result['pid']}")
145
+ click.echo(f" Port: {result['port']}")
146
+ click.echo(f" Dashboard: {result['dashboard']}")
147
+ else:
148
+ click.echo("BikeRack is not running")
@@ -0,0 +1,181 @@
1
+ """BikeRack launcher — core lifecycle functions.
2
+
3
+ Story 101-5: BikeRack launcher CLI (pf bikerack start/stop/status)
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ import atexit
9
+ import os
10
+ import signal
11
+ import subprocess
12
+ import time
13
+ from pathlib import Path
14
+ from typing import NoReturn
15
+
16
+
17
+ def is_process_alive(pid: int) -> bool:
18
+ """Check if a process with given PID is alive."""
19
+ try:
20
+ os.kill(pid, 0)
21
+ return True
22
+ except (ProcessLookupError, PermissionError, OSError):
23
+ return False
24
+
25
+
26
+ def cleanup_files(project_dir: Path) -> None:
27
+ """Clean up .bikerack-port and .bikerack-pid files."""
28
+ for name in (".bikerack-port", ".bikerack-pid"):
29
+ try:
30
+ (project_dir / name).unlink()
31
+ except FileNotFoundError:
32
+ pass
33
+
34
+
35
+ def read_port_file(project_dir: Path) -> int | None:
36
+ """Read port from .bikerack-port file. Returns None if not found."""
37
+ try:
38
+ return int((project_dir / ".bikerack-port").read_text().strip())
39
+ except (FileNotFoundError, ValueError):
40
+ return None
41
+
42
+
43
+ def read_pid_file(project_dir: Path) -> int | None:
44
+ """Read PID from .bikerack-pid file. Returns None if not found."""
45
+ try:
46
+ return int((project_dir / ".bikerack-pid").read_text().strip())
47
+ except (FileNotFoundError, ValueError):
48
+ return None
49
+
50
+
51
+ def write_pid_file(project_dir: Path, pid: int) -> None:
52
+ """Write .bikerack-pid file."""
53
+ (project_dir / ".bikerack-pid").write_text(str(pid))
54
+
55
+
56
+ def build_otel_env(port: int) -> dict[str, str]:
57
+ """Build the 5 OTEL environment variables from discovered port (Rule 5)."""
58
+ return {
59
+ "CLAUDE_CODE_ENABLE_TELEMETRY": "1",
60
+ "OTEL_LOGS_EXPORTER": "otlp",
61
+ "OTEL_METRICS_EXPORTER": "otlp",
62
+ "OTEL_EXPORTER_OTLP_PROTOCOL": "http/json",
63
+ "OTEL_EXPORTER_OTLP_ENDPOINT": f"http://localhost:{port}",
64
+ }
65
+
66
+
67
+ def _find_framework_dir() -> Path:
68
+ """Locate the pennyfarthing framework root from this package's location."""
69
+ # pennyfarthing_scripts/bikerack/launcher.py -> pennyfarthing/
70
+ return Path(__file__).resolve().parent.parent.parent
71
+
72
+
73
+ def start_wheelhub(project_dir: Path) -> subprocess.Popen:
74
+ """Start WheelHub server in background with IS_BIKERACK=1."""
75
+ framework_dir = _find_framework_dir()
76
+ bikerack_entry = framework_dir / "packages" / "cyclist" / "dist" / "bikerack.js"
77
+
78
+ env = os.environ.copy()
79
+ env["IS_BIKERACK"] = "1"
80
+ env["CYCLIST_PROJECT_DIR"] = str(project_dir)
81
+
82
+ return subprocess.Popen(
83
+ ["node", str(bikerack_entry)],
84
+ env=env,
85
+ cwd=str(project_dir),
86
+ stdout=subprocess.DEVNULL,
87
+ stderr=subprocess.DEVNULL,
88
+ )
89
+
90
+
91
+ def poll_for_port_file(
92
+ project_dir: Path, timeout: float = 5.0, interval: float = 0.1
93
+ ) -> int:
94
+ """Poll for .bikerack-port file, return port number."""
95
+ port_file = project_dir / ".bikerack-port"
96
+ deadline = time.monotonic() + timeout
97
+
98
+ while True:
99
+ if port_file.exists():
100
+ return int(port_file.read_text().strip())
101
+ if time.monotonic() >= deadline:
102
+ raise TimeoutError(
103
+ f"Timed out waiting for {port_file} after {timeout}s"
104
+ )
105
+ time.sleep(interval)
106
+
107
+
108
+ def register_cleanup(project_dir: Path, pid: int) -> None:
109
+ """Register atexit handler to kill WheelHub and clean up files (Rule 8)."""
110
+
111
+ def _cleanup(project_dir: Path, pid: int) -> None:
112
+ try:
113
+ os.kill(pid, signal.SIGTERM)
114
+ except (ProcessLookupError, OSError):
115
+ pass
116
+ cleanup_files(project_dir)
117
+
118
+ atexit.register(_cleanup, project_dir, pid)
119
+
120
+
121
+ def exec_claude(otel_env: dict[str, str], project_dir: Path | None = None) -> NoReturn:
122
+ """Replace current process with Claude CLI using os.execvpe (CE-4)."""
123
+ env = os.environ.copy()
124
+ env.update(otel_env)
125
+ if project_dir:
126
+ os.chdir(project_dir)
127
+ os.execvpe("claude", ["claude"], env)
128
+
129
+
130
+ def is_already_running(project_dir: Path) -> tuple[bool, int | None, int | None]:
131
+ """Check if BikeRack is already running.
132
+
133
+ Returns (is_running, pid_or_none, port_or_none).
134
+ Cleans up stale files if PID is dead.
135
+ """
136
+ pid = read_pid_file(project_dir)
137
+ port = read_port_file(project_dir)
138
+
139
+ if pid is None or port is None:
140
+ return (False, None, None)
141
+
142
+ if is_process_alive(pid):
143
+ return (True, pid, port)
144
+
145
+ cleanup_files(project_dir)
146
+ return (False, None, None)
147
+
148
+
149
+ def stop_bikerack(project_dir: Path) -> dict:
150
+ """Stop running BikeRack instance. Returns {success, pid, message}."""
151
+ pid = read_pid_file(project_dir)
152
+
153
+ if pid is None:
154
+ return {"success": False, "message": "BikeRack is not running"}
155
+
156
+ if not is_process_alive(pid):
157
+ cleanup_files(project_dir)
158
+ return {"success": False, "message": "BikeRack is not running (stale PID)"}
159
+
160
+ os.kill(pid, signal.SIGTERM)
161
+ cleanup_files(project_dir)
162
+ return {"success": True, "pid": pid, "message": f"Stopped BikeRack (PID {pid})"}
163
+
164
+
165
+ def get_status(project_dir: Path) -> dict:
166
+ """Get BikeRack running status. Returns {running, pid, port, dashboard}."""
167
+ pid = read_pid_file(project_dir)
168
+ port = read_port_file(project_dir)
169
+
170
+ if pid is None or port is None:
171
+ return {"running": False}
172
+
173
+ if not is_process_alive(pid):
174
+ return {"running": False}
175
+
176
+ return {
177
+ "running": True,
178
+ "pid": pid,
179
+ "port": port,
180
+ "dashboard": f"http://localhost:{port}/bikerack",
181
+ }
@@ -65,6 +65,11 @@ from pennyfarthing_scripts.validate.cli import validate # noqa: E402
65
65
 
66
66
  cli.add_command(validate)
67
67
 
68
+ # Import and register bikerack group
69
+ from pennyfarthing_scripts.bikerack.cli import bikerack # noqa: E402
70
+
71
+ cli.add_command(bikerack)
72
+
68
73
 
69
74
  @cli.group()
70
75
  def agent():