@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
@@ -16,6 +16,8 @@ Usage:
16
16
 
17
17
  from __future__ import annotations
18
18
 
19
+ from datetime import UTC
20
+
19
21
  import click
20
22
 
21
23
 
@@ -90,17 +92,18 @@ def workflow_phase_check(workflow_name: str, phase: str):
90
92
  @workflow.command("handoff")
91
93
  @click.argument("next_agent")
92
94
  def workflow_handoff(next_agent: str):
93
- """Emit a handoff marker for Cyclist.
95
+ """Emit an environment-aware handoff marker.
96
+
97
+ Delegates to generate_marker() which detects Cyclist, relay mode,
98
+ and context usage to choose the appropriate marker type.
94
99
 
95
100
  \b
96
101
  Arguments:
97
102
  NEXT_AGENT - The agent to hand off to (tea, dev, reviewer, etc.)
98
103
  """
99
- click.echo("---")
100
- click.echo("AGENT_COMMAND:")
101
- click.echo(f' marker: "<!-- CYCLIST:HANDOFF:/{next_agent} -->"')
102
- click.echo(f' fallback: "Run `/{next_agent}` to continue"')
103
- click.echo("---")
104
+ from pennyfarthing_scripts.handoff.marker import generate_marker
105
+
106
+ click.echo(generate_marker(next_agent))
104
107
 
105
108
 
106
109
  # ---------------------------------------------------------------------------
@@ -140,7 +143,6 @@ def workflow_list_cmd():
140
143
 
141
144
  Shows a markdown table with type, phases/steps, modes, and descriptions.
142
145
  """
143
- import yaml as yaml_mod
144
146
 
145
147
  from pennyfarthing_scripts.workflow.helpers import (
146
148
  count_steps,
@@ -352,7 +354,7 @@ def workflow_start_cmd(name: str, mode: str | None):
352
354
  Arguments:
353
355
  NAME - Workflow name (e.g., architecture, release)
354
356
  """
355
- from datetime import datetime, timezone
357
+ from datetime import datetime
356
358
 
357
359
  from pennyfarthing_scripts.common.config import get_project_root
358
360
  from pennyfarthing_scripts.workflow.helpers import (
@@ -443,7 +445,7 @@ def workflow_start_cmd(name: str, mode: str | None):
443
445
  return
444
446
 
445
447
  # Create session file
446
- now = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
448
+ now = datetime.now(UTC).strftime("%Y-%m-%dT%H:%M:%SZ")
447
449
  wf_agent = wf.get("agent", "pm")
448
450
  wf_desc = wf.get("description", "-")
449
451
 
@@ -515,7 +517,7 @@ def workflow_resume_cmd(name: str | None):
515
517
  Arguments:
516
518
  NAME - Workflow name (auto-detects from active session if omitted)
517
519
  """
518
- from datetime import datetime, timezone
520
+ from datetime import datetime
519
521
 
520
522
  from pennyfarthing_scripts.common.config import get_project_root
521
523
  from pennyfarthing_scripts.workflow.helpers import (
@@ -600,7 +602,7 @@ def workflow_resume_cmd(name: str | None):
600
602
  raise SystemExit(1)
601
603
 
602
604
  # Update last updated timestamp
603
- now = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
605
+ now = datetime.now(UTC).strftime("%Y-%m-%dT%H:%M:%SZ")
604
606
  import re
605
607
 
606
608
  updated_content = re.sub(
@@ -787,7 +789,7 @@ def workflow_fix_phase_cmd(story_id: str, target_phase: str, dry_run: bool):
787
789
  TARGET_PHASE - Target phase to set (e.g., review, approved, finish)
788
790
  """
789
791
  import re
790
- from datetime import datetime, timezone
792
+ from datetime import datetime
791
793
 
792
794
  from pennyfarthing_scripts.common.config import get_project_root
793
795
  from pennyfarthing_scripts.workflow.helpers import (
@@ -848,17 +850,17 @@ def workflow_fix_phase_cmd(story_id: str, target_phase: str, dry_run: bool):
848
850
  # Find indices
849
851
  try:
850
852
  current_idx = phases.index(current_phase)
851
- except ValueError:
853
+ except ValueError as err:
852
854
  click.echo(f"Error: Current phase '{current_phase}' not found in {workflow_name} workflow", err=True)
853
855
  click.echo(f"Valid phases: {', '.join(phases)}", err=True)
854
- raise SystemExit(1)
856
+ raise SystemExit(1) from err
855
857
 
856
858
  try:
857
859
  target_idx = phases.index(target_phase)
858
- except ValueError:
860
+ except ValueError as err:
859
861
  click.echo(f"Error: Target phase '{target_phase}' not found in {workflow_name} workflow", err=True)
860
862
  click.echo(f"Valid phases: {', '.join(phases)}", err=True)
861
- raise SystemExit(1)
863
+ raise SystemExit(1) from err
862
864
 
863
865
  if target_idx <= current_idx:
864
866
  click.echo(f"Error: Target phase '{target_phase}' is not ahead of current phase '{current_phase}'", err=True)
@@ -866,7 +868,7 @@ def workflow_fix_phase_cmd(story_id: str, target_phase: str, dry_run: bool):
866
868
  raise SystemExit(1)
867
869
 
868
870
  # Calculate transitions
869
- now = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
871
+ now = datetime.now(UTC).strftime("%Y-%m-%dT%H:%M:%SZ")
870
872
  click.echo("")
871
873
  click.echo("Transitions needed:")
872
874
 
@@ -908,7 +910,7 @@ def workflow_fix_phase_cmd(story_id: str, target_phase: str, dry_run: bool):
908
910
 
909
911
  # Build handoff history additions
910
912
  handoff_lines = []
911
- for from_phase, to_phase, from_agent, to_agent, gate in transitions:
913
+ for _from_phase, _to_phase, from_agent, to_agent, gate in transitions:
912
914
  handoff_lines.append(f"| {from_agent} | {to_agent} | {gate} | PASSED | {now} |")
913
915
 
914
916
  # Insert handoff rows after the last PASSED/FAILED row
@@ -950,7 +952,7 @@ def workflow_complete_step_cmd(name: str | None, step_override: int | None):
950
952
  NAME - Workflow name (auto-detects from session if omitted)
951
953
  """
952
954
  import re
953
- from datetime import datetime, timezone
955
+ from datetime import datetime
954
956
 
955
957
  from pennyfarthing_scripts.common.config import get_project_root
956
958
  from pennyfarthing_scripts.workflow.helpers import (
@@ -1035,7 +1037,7 @@ def workflow_complete_step_cmd(name: str | None, step_override: int | None):
1035
1037
  new_status = "completed" if completed_count >= step_count else "in_progress"
1036
1038
 
1037
1039
  # Update session file
1038
- now = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
1040
+ now = datetime.now(UTC).strftime("%Y-%m-%dT%H:%M:%SZ")
1039
1041
 
1040
1042
  content = re.sub(
1041
1043
  r"^- \*\*Current Step:\*\*.*$",
@@ -7,7 +7,6 @@ state detection from .session/ files.
7
7
 
8
8
  from typing import Any
9
9
 
10
-
11
10
  # Phase ownership mapping for TDD workflow
12
11
  # Canonical YAML names: setup, red, green, review, finish
13
12
  TDD_PHASE_OWNERS: dict[str, str] = {
@@ -0,0 +1,256 @@
1
+ """
2
+ Phase-scoped Team Lifecycle for Story 86-10.
3
+
4
+ Manages the lifecycle of native Agent Teams within workflow phases.
5
+ When a workflow phase has a `team:` config block, the lead agent creates
6
+ a team at phase start, spawns teammates, and cleans up before handoff.
7
+
8
+ Python port of packages/core/dist/workflow/team-lifecycle.js.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from datetime import UTC, datetime
14
+ from pathlib import Path
15
+ from typing import Any
16
+
17
+ # =============================================================================
18
+ # In-memory registries
19
+ # =============================================================================
20
+
21
+ _active_teams: dict[str, dict[str, Any]] = {}
22
+ """Active teams keyed by story_id."""
23
+
24
+ _sidecar_locks: dict[str, dict[str, Any]] = {}
25
+ """Sidecar locks keyed by file path."""
26
+
27
+
28
+ def _reset_for_testing() -> None:
29
+ """Reset all in-memory state. For testing only."""
30
+ _active_teams.clear()
31
+ _sidecar_locks.clear()
32
+
33
+
34
+ async def create_team(
35
+ phase: dict[str, Any],
36
+ story_id: str,
37
+ adapter: Any | None = None,
38
+ ) -> dict[str, Any]:
39
+ """Create a phase-scoped team.
40
+
41
+ If phase has no team config, returns success with no handle (no-op).
42
+ If phase has team config, creates team and returns handle.
43
+ """
44
+ if not phase.get("team"):
45
+ return {"success": True}
46
+
47
+ team_name = f"{story_id}-{phase['name']}"
48
+
49
+ # Clean up existing team for same story
50
+ existing = _active_teams.get(story_id)
51
+ if existing and adapter:
52
+ try:
53
+ await adapter.delete_team(existing["teamName"])
54
+ except Exception:
55
+ pass
56
+ del _active_teams[story_id]
57
+
58
+ handle: dict[str, Any] = {
59
+ "teamName": team_name,
60
+ "storyId": story_id,
61
+ "phase": phase["name"],
62
+ "teammates": [],
63
+ "createdAt": datetime.now(UTC).isoformat(),
64
+ }
65
+
66
+ if adapter:
67
+ try:
68
+ await adapter.create_team({"teamName": team_name})
69
+ except Exception as exc:
70
+ return {"success": False, "error": str(exc)}
71
+
72
+ _active_teams[story_id] = handle
73
+ return {"success": True, "data": handle}
74
+
75
+
76
+ async def spawn_teammates(
77
+ handle: dict[str, Any],
78
+ config: dict[str, Any],
79
+ story_id: str,
80
+ phase: dict[str, Any],
81
+ adapter: Any | None = None,
82
+ ) -> dict[str, Any]:
83
+ """Spawn all teammates for a team based on workflow YAML config."""
84
+ teammates: list[dict[str, Any]] = []
85
+
86
+ for member in config["teammates"]:
87
+ teammate: dict[str, Any] = {
88
+ "agent": member["agent"],
89
+ "task": member["task"],
90
+ "status": "spawned",
91
+ }
92
+ if adapter:
93
+ try:
94
+ await adapter.spawn_teammate({
95
+ "teamName": handle["teamName"],
96
+ "agent": member["agent"],
97
+ "prompt": f'pf agent start "{member["agent"]}"',
98
+ "model": config.get("model"),
99
+ })
100
+ except Exception:
101
+ teammate["status"] = "crashed"
102
+ teammates.append(teammate)
103
+
104
+ handle["teammates"] = teammates
105
+
106
+ # Update registry
107
+ if handle.get("storyId") in _active_teams:
108
+ _active_teams[handle["storyId"]] = handle
109
+
110
+ return {"success": True, "data": teammates}
111
+
112
+
113
+ async def shutdown_all_teammates(
114
+ handle: dict[str, Any],
115
+ adapter: Any | None = None,
116
+ ) -> dict[str, Any]:
117
+ """Shut down all active teammates in a team."""
118
+ shutdown_count = 0
119
+
120
+ for teammate in handle["teammates"]:
121
+ if teammate["status"] in ("shutdown", "crashed"):
122
+ continue
123
+ if adapter:
124
+ try:
125
+ await adapter.shutdown_teammate({
126
+ "teamName": handle["teamName"],
127
+ "agent": teammate["agent"],
128
+ })
129
+ except Exception:
130
+ pass # Graceful degradation — swallow adapter errors
131
+ teammate["status"] = "shutdown"
132
+ shutdown_count += 1
133
+
134
+ return {"success": True, "data": {"shutdownCount": shutdown_count}}
135
+
136
+
137
+ async def cleanup_team(
138
+ handle: dict[str, Any],
139
+ adapter: Any | None = None,
140
+ ) -> dict[str, Any]:
141
+ """Clean up a team entirely (TeamDelete). Must run before pf handoff."""
142
+ if adapter:
143
+ try:
144
+ await adapter.delete_team(handle["teamName"])
145
+ except Exception:
146
+ pass
147
+
148
+ _active_teams.pop(handle.get("storyId", ""), None)
149
+ return {"success": True, "data": {"cleaned": True}}
150
+
151
+
152
+ def check_gate_on_task_completed(
153
+ handle: dict[str, Any],
154
+ phase: dict[str, Any],
155
+ ) -> dict[str, Any]:
156
+ """Check gate condition when a TaskCompleted event fires."""
157
+ if not phase.get("gate"):
158
+ return {"passed": True, "gate": "none"}
159
+
160
+ gate_type = phase["gate"].get("type", "unknown")
161
+ has_active = any(t["status"] == "active" for t in handle["teammates"])
162
+
163
+ if has_active:
164
+ return {"passed": False, "gate": gate_type, "reason": "Teammates still active"}
165
+
166
+ return {"passed": True, "gate": gate_type}
167
+
168
+
169
+ def check_gate_on_teammate_idle(
170
+ handle: dict[str, Any],
171
+ teammate: dict[str, Any],
172
+ phase: dict[str, Any],
173
+ ) -> dict[str, Any]:
174
+ """Check gate condition when a TeammateIdle event fires."""
175
+ if not phase.get("gate"):
176
+ return {"passed": True, "gate": "none"}
177
+
178
+ gate_type = phase["gate"].get("type", "unknown")
179
+
180
+ if teammate["status"] == "crashed":
181
+ return {"passed": False, "gate": gate_type, "reason": "Teammate crashed"}
182
+
183
+ return {"passed": True, "gate": gate_type}
184
+
185
+
186
+ def generate_team_summary(handle: dict[str, Any]) -> dict[str, Any]:
187
+ """Generate a team activity summary for the session file audit trail."""
188
+ return {
189
+ "teamName": handle["teamName"],
190
+ "storyId": handle["storyId"],
191
+ "phase": handle["phase"],
192
+ "members": [
193
+ {"agent": t["agent"], "status": t["status"], "task": t["task"]}
194
+ for t in handle["teammates"]
195
+ ],
196
+ "cleanShutdown": all(t["status"] == "shutdown" for t in handle["teammates"]),
197
+ }
198
+
199
+
200
+ def acquire_sidecar_lock(
201
+ file_path: str,
202
+ story_id: str,
203
+ timeout: int = 5000,
204
+ ) -> dict[str, Any]:
205
+ """Acquire an exclusive lock for sidecar file writing."""
206
+ existing = _sidecar_locks.get(file_path)
207
+ if existing:
208
+ if existing["storyId"] == story_id:
209
+ return {"success": True, "data": existing}
210
+ return {"success": False, "error": f"Lock held by story {existing['storyId']}"}
211
+
212
+ lock: dict[str, Any] = {
213
+ "lockPath": f"{file_path}.lock",
214
+ "storyId": story_id,
215
+ "acquiredAt": datetime.now(UTC).isoformat(),
216
+ }
217
+ _sidecar_locks[file_path] = lock
218
+ return {"success": True, "data": lock}
219
+
220
+
221
+ def release_sidecar_lock(lock: dict[str, Any]) -> dict[str, Any]:
222
+ """Release a sidecar file lock."""
223
+ for path, held in list(_sidecar_locks.items()):
224
+ if held["lockPath"] == lock["lockPath"]:
225
+ del _sidecar_locks[path]
226
+ break
227
+ return {"success": True}
228
+
229
+
230
+ def get_active_team(story_id: str) -> dict[str, Any] | None:
231
+ """Get the active team for a story, if any."""
232
+ return _active_teams.get(story_id)
233
+
234
+
235
+ def update_session_with_summary(
236
+ session_path: str | Path,
237
+ summary: dict[str, Any],
238
+ ) -> dict[str, Any]:
239
+ """Write team activity summary to session file for audit trail."""
240
+ path = Path(session_path)
241
+ content = path.read_text() if path.exists() else ""
242
+
243
+ lines = [
244
+ "\n## Team Activity Summary\n",
245
+ f"**Team:** {summary['teamName']}",
246
+ f"**Phase:** {summary['phase']}",
247
+ f"**Clean Shutdown:** {'Yes' if summary.get('cleanShutdown') else 'No'}\n",
248
+ "| Agent | Status | Task |",
249
+ "|-------|--------|------|",
250
+ ]
251
+ for member in summary.get("members", []):
252
+ lines.append(f"| {member['agent']} | {member['status']} | {member['task']} |")
253
+
254
+ content += "\n".join(lines) + "\n"
255
+ path.write_text(content)
256
+ return {"success": True}
@@ -1,16 +0,0 @@
1
- /**
2
- * Story 11-3: Cyclist Migration Tests
3
- *
4
- * These tests verify the Cyclist Electron GUI is correctly migrated
5
- * into the pnpm workspace as @pennyfarthing/cyclist.
6
- * Tests are designed to FAIL until the migration is complete.
7
- *
8
- * Acceptance Criteria:
9
- * 1. packages/cyclist/ contains Cyclist source
10
- * 2. Dependencies use workspace:* for internal packages
11
- * 3. Portrait resolution uses @pennyfarthing/shared
12
- * 4. Electron app starts and shows portraits
13
- * 5. Dogfooding scenario works (portraits load from pennyfarthing-dist/)
14
- */
15
- export {};
16
- //# sourceMappingURL=cyclist-migration.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"cyclist-migration.test.d.ts","sourceRoot":"","sources":["../../src/cli/cyclist-migration.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG"}
@@ -1,229 +0,0 @@
1
- /**
2
- * Story 11-3: Cyclist Migration Tests
3
- *
4
- * These tests verify the Cyclist Electron GUI is correctly migrated
5
- * into the pnpm workspace as @pennyfarthing/cyclist.
6
- * Tests are designed to FAIL until the migration is complete.
7
- *
8
- * Acceptance Criteria:
9
- * 1. packages/cyclist/ contains Cyclist source
10
- * 2. Dependencies use workspace:* for internal packages
11
- * 3. Portrait resolution uses @pennyfarthing/shared
12
- * 4. Electron app starts and shows portraits
13
- * 5. Dogfooding scenario works (portraits load from pennyfarthing-dist/)
14
- */
15
- import { describe, it } from 'node:test';
16
- import assert from 'node:assert';
17
- import { existsSync, readFileSync, readdirSync } from 'node:fs';
18
- import { join } from 'node:path';
19
- import { findMonorepoRoot } from './utils/files.js';
20
- const __dirname = import.meta.dirname;
21
- const PROJECT_ROOT = findMonorepoRoot(__dirname);
22
- describe('Story 11-3: Cyclist Migration into Monorepo', () => {
23
- describe('AC1: packages/cyclist/ contains Cyclist source', () => {
24
- it('should have packages/cyclist directory', () => {
25
- const cyclistDir = join(PROJECT_ROOT, 'packages', 'cyclist');
26
- assert.ok(existsSync(cyclistDir), 'packages/cyclist/ directory must exist');
27
- });
28
- it('should have packages/cyclist/src directory', () => {
29
- const srcDir = join(PROJECT_ROOT, 'packages', 'cyclist', 'src');
30
- assert.ok(existsSync(srcDir), 'packages/cyclist/src/ directory must exist');
31
- });
32
- it('should have packages/cyclist/src/main.ts (Electron main process)', () => {
33
- const mainTs = join(PROJECT_ROOT, 'packages', 'cyclist', 'src', 'main.ts');
34
- assert.ok(existsSync(mainTs), 'packages/cyclist/src/main.ts must exist');
35
- });
36
- it('should have packages/cyclist/src/paths.ts (path resolution)', () => {
37
- const pathsTs = join(PROJECT_ROOT, 'packages', 'cyclist', 'src', 'paths.ts');
38
- assert.ok(existsSync(pathsTs), 'packages/cyclist/src/paths.ts must exist');
39
- });
40
- it('should have packages/cyclist/src/public directory', () => {
41
- const publicDir = join(PROJECT_ROOT, 'packages', 'cyclist', 'src', 'public');
42
- assert.ok(existsSync(publicDir), 'packages/cyclist/src/public/ directory must exist');
43
- });
44
- it('should have packages/cyclist/package.json', () => {
45
- const packageJson = join(PROJECT_ROOT, 'packages', 'cyclist', 'package.json');
46
- assert.ok(existsSync(packageJson), 'packages/cyclist/package.json must exist');
47
- });
48
- it('should have @pennyfarthing/cyclist as package name', () => {
49
- const packageJsonPath = join(PROJECT_ROOT, 'packages', 'cyclist', 'package.json');
50
- if (!existsSync(packageJsonPath)) {
51
- assert.fail('packages/cyclist/package.json must exist first');
52
- }
53
- const pkg = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
54
- assert.strictEqual(pkg.name, '@pennyfarthing/cyclist', 'Package name must be @pennyfarthing/cyclist');
55
- });
56
- it('should have packages/cyclist/tsconfig.json', () => {
57
- const tsconfig = join(PROJECT_ROOT, 'packages', 'cyclist', 'tsconfig.json');
58
- assert.ok(existsSync(tsconfig), 'packages/cyclist/tsconfig.json must exist');
59
- });
60
- });
61
- describe('AC2: Dependencies use workspace:* for internal packages', () => {
62
- it('should have @pennyfarthing/shared as workspace dependency', () => {
63
- const packageJsonPath = join(PROJECT_ROOT, 'packages', 'cyclist', 'package.json');
64
- if (!existsSync(packageJsonPath)) {
65
- assert.fail('packages/cyclist/package.json must exist first');
66
- }
67
- const pkg = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
68
- const deps = pkg.dependencies || {};
69
- assert.ok(deps['@pennyfarthing/shared'], 'packages/cyclist should depend on @pennyfarthing/shared');
70
- // Dependency can use workspace:* or a version range
71
- assert.ok(deps['@pennyfarthing/shared'].includes('workspace') || /^\^?\d/.test(deps['@pennyfarthing/shared']), '@pennyfarthing/shared dependency should use workspace protocol or version range');
72
- });
73
- it('should NOT have pennyfarthing GitHub dependency', () => {
74
- const packageJsonPath = join(PROJECT_ROOT, 'packages', 'cyclist', 'package.json');
75
- if (!existsSync(packageJsonPath)) {
76
- assert.fail('packages/cyclist/package.json must exist first');
77
- }
78
- const pkg = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
79
- const deps = pkg.dependencies || {};
80
- assert.ok(!deps['pennyfarthing'] || !deps['pennyfarthing'].includes('github'), 'packages/cyclist should NOT have GitHub pennyfarthing dependency (use workspace instead)');
81
- });
82
- it('should be listed in pnpm-workspace.yaml packages', () => {
83
- const workspaceYamlPath = join(PROJECT_ROOT, 'pnpm-workspace.yaml');
84
- const content = readFileSync(workspaceYamlPath, 'utf-8');
85
- // packages/* pattern should include packages/cyclist
86
- assert.ok(content.includes('packages/*') || content.includes('packages/cyclist'), 'pnpm-workspace.yaml should include packages/cyclist (via packages/* pattern)');
87
- });
88
- });
89
- describe('AC3: Portrait resolution uses shared resolver pattern', () => {
90
- // Note: paths.ts inlines the shared resolver functions for standalone npm distribution
91
- // This is valid - we test for the functions existing, not the import pattern
92
- it('should have resolvePennyfarthingDist function', () => {
93
- const pathsPath = join(PROJECT_ROOT, 'packages', 'cyclist', 'src', 'paths.ts');
94
- if (!existsSync(pathsPath)) {
95
- assert.fail('packages/cyclist/src/paths.ts must exist first');
96
- }
97
- const content = readFileSync(pathsPath, 'utf-8');
98
- assert.ok(content.includes('resolvePennyfarthingDist'), 'paths.ts should have resolvePennyfarthingDist function (inlined or imported)');
99
- });
100
- it('should have getPortraitPaths function', () => {
101
- const pathsPath = join(PROJECT_ROOT, 'packages', 'cyclist', 'src', 'paths.ts');
102
- if (!existsSync(pathsPath)) {
103
- assert.fail('packages/cyclist/src/paths.ts must exist first');
104
- }
105
- const content = readFileSync(pathsPath, 'utf-8');
106
- assert.ok(content.includes('getPortraitPaths'), 'paths.ts should have getPortraitPaths function (inlined or imported)');
107
- });
108
- it('should NOT have hardcoded pennyfarthing path in getPortraitsDir', () => {
109
- const pathsPath = join(PROJECT_ROOT, 'packages', 'cyclist', 'src', 'paths.ts');
110
- if (!existsSync(pathsPath)) {
111
- assert.fail('packages/cyclist/src/paths.ts must exist first');
112
- }
113
- const content = readFileSync(pathsPath, 'utf-8');
114
- // Should not contain the old hardcoded path logic (join calls, not comments)
115
- // Old pattern was: join(..., 'pennyfarthing', 'pennyfarthing-dist')
116
- const hasOldPattern = content.includes("join(__dirname, 'pennyfarthing', 'pennyfarthing-dist')") ||
117
- content.includes('join(__dirname, "pennyfarthing", "pennyfarthing-dist")');
118
- assert.ok(!hasOldPattern, 'paths.ts should NOT have hardcoded pennyfarthing path - use shared resolver instead');
119
- });
120
- it('should depend on @pennyfarthing/shared in package.json', () => {
121
- const packageJsonPath = join(PROJECT_ROOT, 'packages', 'cyclist', 'package.json');
122
- if (!existsSync(packageJsonPath)) {
123
- assert.fail('packages/cyclist/package.json must exist first');
124
- }
125
- const pkg = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
126
- const deps = pkg.dependencies || {};
127
- assert.ok(deps['@pennyfarthing/shared'], 'packages/cyclist should depend on @pennyfarthing/shared');
128
- });
129
- });
130
- describe('AC4: Electron app starts and shows portraits', () => {
131
- // Note: Full build verification requires pnpm install which needs workspace resolution
132
- // These tests verify the package configuration is correct for building
133
- it('should have Electron dependency', () => {
134
- const packageJsonPath = join(PROJECT_ROOT, 'packages', 'cyclist', 'package.json');
135
- if (!existsSync(packageJsonPath)) {
136
- assert.fail('packages/cyclist/package.json must exist first');
137
- }
138
- const pkg = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
139
- const devDeps = pkg.devDependencies || {};
140
- assert.ok(devDeps['electron'], 'packages/cyclist should have electron as devDependency');
141
- });
142
- it('should have build script in package.json', () => {
143
- const packageJsonPath = join(PROJECT_ROOT, 'packages', 'cyclist', 'package.json');
144
- if (!existsSync(packageJsonPath)) {
145
- assert.fail('packages/cyclist/package.json must exist first');
146
- }
147
- const pkg = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
148
- assert.ok(pkg.scripts?.build, 'packages/cyclist should have a build script');
149
- });
150
- it('should have main entry point configured', () => {
151
- const packageJsonPath = join(PROJECT_ROOT, 'packages', 'cyclist', 'package.json');
152
- if (!existsSync(packageJsonPath)) {
153
- assert.fail('packages/cyclist/package.json must exist first');
154
- }
155
- const pkg = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
156
- assert.ok(pkg.main, 'packages/cyclist should have a main entry point');
157
- });
158
- });
159
- describe('AC5: Dogfooding scenario works', () => {
160
- it('should resolve portraits from pennyfarthing-dist in monorepo', () => {
161
- // This test validates that portrait resolution works in the monorepo context
162
- const pennyfarthingDist = join(PROJECT_ROOT, 'pennyfarthing-dist');
163
- assert.ok(existsSync(pennyfarthingDist), 'pennyfarthing-dist/ should exist at monorepo root for dogfooding');
164
- });
165
- it('should have portraits directory in pennyfarthing-dist', () => {
166
- const portraitsDir = join(PROJECT_ROOT, 'pennyfarthing-dist', 'personas', 'portraits');
167
- assert.ok(existsSync(portraitsDir), 'pennyfarthing-dist/personas/portraits/ should exist');
168
- });
169
- it('should have at least one theme with portraits', () => {
170
- const portraitsDir = join(PROJECT_ROOT, 'pennyfarthing-dist', 'personas', 'portraits');
171
- if (!existsSync(portraitsDir)) {
172
- assert.fail('portraits directory must exist first');
173
- }
174
- const themes = readdirSync(portraitsDir);
175
- assert.ok(themes.length > 0, 'Should have at least one theme directory in portraits');
176
- });
177
- it('should have paths.ts configured to use shared resolver', () => {
178
- // Verify paths.ts source code uses the shared resolver
179
- // Runtime verification requires pnpm install + build
180
- const pathsPath = join(PROJECT_ROOT, 'packages', 'cyclist', 'src', 'paths.ts');
181
- if (!existsSync(pathsPath)) {
182
- assert.fail('packages/cyclist/src/paths.ts must exist');
183
- }
184
- const content = readFileSync(pathsPath, 'utf-8');
185
- assert.ok(content.includes('resolvePennyfarthingDist') && content.includes('getPortraitPaths'), 'paths.ts should use resolvePennyfarthingDist and getPortraitPaths from shared');
186
- });
187
- });
188
- describe('Build Integration', () => {
189
- // Note: Full TypeScript compilation requires pnpm install to resolve workspace dependencies
190
- // These tests verify the build configuration is correct
191
- it('should have valid tsconfig.json extending base', () => {
192
- const tsconfigPath = join(PROJECT_ROOT, 'packages', 'cyclist', 'tsconfig.json');
193
- if (!existsSync(tsconfigPath)) {
194
- assert.fail('packages/cyclist/tsconfig.json must exist');
195
- }
196
- const content = readFileSync(tsconfigPath, 'utf-8');
197
- const tsconfig = JSON.parse(content);
198
- assert.ok(tsconfig.extends?.includes('tsconfig.base.json'), 'tsconfig.json should extend ../../tsconfig.base.json');
199
- });
200
- it('should have outDir configured for dist', () => {
201
- const tsconfigPath = join(PROJECT_ROOT, 'packages', 'cyclist', 'tsconfig.json');
202
- if (!existsSync(tsconfigPath)) {
203
- assert.fail('packages/cyclist/tsconfig.json must exist');
204
- }
205
- const content = readFileSync(tsconfigPath, 'utf-8');
206
- const tsconfig = JSON.parse(content);
207
- assert.strictEqual(tsconfig.compilerOptions?.outDir, 'dist', 'tsconfig.json should output to dist directory');
208
- });
209
- });
210
- describe('Source File Completeness', () => {
211
- it('should have pennyfarthing.ts (persona/theme loading)', () => {
212
- const pennyfarthingTs = join(PROJECT_ROOT, 'packages', 'cyclist', 'src', 'pennyfarthing.ts');
213
- assert.ok(existsSync(pennyfarthingTs), 'packages/cyclist/src/pennyfarthing.ts must exist');
214
- });
215
- it('should have server.ts (Express server)', () => {
216
- const serverTs = join(PROJECT_ROOT, 'packages', 'cyclist', 'src', 'server.ts');
217
- assert.ok(existsSync(serverTs), 'packages/cyclist/src/server.ts must exist');
218
- });
219
- it('should have api/ directory', () => {
220
- const apiDir = join(PROJECT_ROOT, 'packages', 'cyclist', 'src', 'api');
221
- assert.ok(existsSync(apiDir), 'packages/cyclist/src/api/ directory must exist');
222
- });
223
- it('should have tests/ directory', () => {
224
- const testsDir = join(PROJECT_ROOT, 'packages', 'cyclist', 'tests');
225
- assert.ok(existsSync(testsDir), 'packages/cyclist/tests/ directory must exist');
226
- });
227
- });
228
- });
229
- //# sourceMappingURL=cyclist-migration.test.js.map