@pennyfarthing/core 11.1.1 → 11.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (487) hide show
  1. package/README.md +8 -8
  2. package/package.json +2 -1
  3. package/packages/core/dist/cli/commands/doctor.d.ts.map +1 -1
  4. package/packages/core/dist/cli/commands/doctor.js +381 -66
  5. package/packages/core/dist/cli/commands/doctor.js.map +1 -1
  6. package/packages/core/dist/cli/commands/init.js +4 -4
  7. package/packages/core/dist/cli/commands/init.js.map +1 -1
  8. package/packages/core/dist/cli/commands/update.d.ts.map +1 -1
  9. package/packages/core/dist/cli/commands/update.js +4 -5
  10. package/packages/core/dist/cli/commands/update.js.map +1 -1
  11. package/packages/core/dist/cli/utils/constants.d.ts +3 -8
  12. package/packages/core/dist/cli/utils/constants.d.ts.map +1 -1
  13. package/packages/core/dist/cli/utils/constants.js +3 -4
  14. package/packages/core/dist/cli/utils/constants.js.map +1 -1
  15. package/packages/core/dist/cli/utils/settings.d.ts +11 -0
  16. package/packages/core/dist/cli/utils/settings.d.ts.map +1 -1
  17. package/packages/core/dist/cli/utils/settings.js +65 -29
  18. package/packages/core/dist/cli/utils/settings.js.map +1 -1
  19. package/packages/core/dist/cli/utils/symlinks.js +16 -16
  20. package/packages/core/dist/cli/utils/symlinks.js.map +1 -1
  21. package/packages/core/dist/consultation/tandem-metrics.d.ts +91 -0
  22. package/packages/core/dist/consultation/tandem-metrics.d.ts.map +1 -0
  23. package/packages/core/dist/consultation/tandem-metrics.js +131 -0
  24. package/packages/core/dist/consultation/tandem-metrics.js.map +1 -0
  25. package/packages/core/dist/consultation/tandem-metrics.test.d.ts +18 -0
  26. package/packages/core/dist/consultation/tandem-metrics.test.d.ts.map +1 -0
  27. package/packages/core/dist/consultation/tandem-metrics.test.js +457 -0
  28. package/packages/core/dist/consultation/tandem-metrics.test.js.map +1 -0
  29. package/packages/core/dist/public/js/react/react.js +14 -14
  30. package/packages/core/dist/scripts/benchmark-integration.d.ts +182 -0
  31. package/packages/core/dist/scripts/benchmark-integration.d.ts.map +1 -0
  32. package/packages/core/dist/scripts/benchmark-integration.js +691 -0
  33. package/packages/core/dist/scripts/benchmark-integration.js.map +1 -0
  34. package/packages/core/dist/scripts/job-fair-aggregator.d.ts +150 -0
  35. package/packages/core/dist/scripts/job-fair-aggregator.d.ts.map +1 -0
  36. package/packages/core/dist/scripts/job-fair-aggregator.js +547 -0
  37. package/packages/core/dist/scripts/job-fair-aggregator.js.map +1 -0
  38. package/packages/core/dist/server/api/agent-load.js +1 -1
  39. package/packages/core/dist/server/api/agent-load.js.map +1 -1
  40. package/packages/core/dist/server/otlp-receiver.d.ts +16 -11
  41. package/packages/core/dist/server/otlp-receiver.d.ts.map +1 -1
  42. package/packages/core/dist/server/otlp-receiver.js +185 -24
  43. package/packages/core/dist/server/otlp-receiver.js.map +1 -1
  44. package/packages/core/dist/server/otlp-receiver.test.d.ts +21 -0
  45. package/packages/core/dist/server/otlp-receiver.test.d.ts.map +1 -0
  46. package/packages/core/dist/server/otlp-receiver.test.js +446 -0
  47. package/packages/core/dist/server/otlp-receiver.test.js.map +1 -0
  48. package/packages/core/dist/server/server.d.ts +0 -3
  49. package/packages/core/dist/server/server.d.ts.map +1 -1
  50. package/packages/core/dist/server/server.js +3 -37
  51. package/packages/core/dist/server/server.js.map +1 -1
  52. package/packages/core/dist/server/server.test.d.ts +1 -1
  53. package/packages/core/dist/server/server.test.js +12 -23
  54. package/packages/core/dist/server/server.test.js.map +1 -1
  55. package/packages/core/dist/shared/capabilities.d.ts +88 -0
  56. package/packages/core/dist/shared/capabilities.d.ts.map +1 -0
  57. package/packages/core/dist/shared/capabilities.js +133 -0
  58. package/packages/core/dist/shared/capabilities.js.map +1 -0
  59. package/packages/core/dist/shared/capabilities.test.d.ts +2 -0
  60. package/packages/core/dist/shared/capabilities.test.d.ts.map +1 -0
  61. package/packages/core/dist/shared/capabilities.test.js +217 -0
  62. package/packages/core/dist/shared/capabilities.test.js.map +1 -0
  63. package/packages/core/dist/shared/portrait-resolver.d.ts +9 -0
  64. package/packages/core/dist/shared/portrait-resolver.d.ts.map +1 -1
  65. package/packages/core/dist/shared/portrait-resolver.js +27 -0
  66. package/packages/core/dist/shared/portrait-resolver.js.map +1 -1
  67. package/packages/core/dist/shared/portrait-resolver.test.js +47 -1
  68. package/packages/core/dist/shared/portrait-resolver.test.js.map +1 -1
  69. package/packages/core/dist/shared/spawn-prompt.d.ts +47 -0
  70. package/packages/core/dist/shared/spawn-prompt.d.ts.map +1 -0
  71. package/packages/core/dist/shared/spawn-prompt.js +82 -0
  72. package/packages/core/dist/shared/spawn-prompt.js.map +1 -0
  73. package/packages/core/dist/shared/spawn-prompt.test.d.ts +2 -0
  74. package/packages/core/dist/shared/spawn-prompt.test.d.ts.map +1 -0
  75. package/packages/core/dist/shared/spawn-prompt.test.js +251 -0
  76. package/packages/core/dist/shared/spawn-prompt.test.js.map +1 -0
  77. package/packages/core/dist/shared/tandem-portrait-inventory.test.d.ts +13 -0
  78. package/packages/core/dist/shared/tandem-portrait-inventory.test.d.ts.map +1 -0
  79. package/packages/core/dist/shared/tandem-portrait-inventory.test.js +126 -0
  80. package/packages/core/dist/shared/tandem-portrait-inventory.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/workflow-schema.d.ts +32 -0
  86. package/packages/core/dist/workflow/workflow-schema.d.ts.map +1 -1
  87. package/packages/core/dist/workflow/workflow-schema.js +120 -0
  88. package/packages/core/dist/workflow/workflow-schema.js.map +1 -1
  89. package/packages/core/dist/workflow/workflow-schema.test.d.ts.map +1 -1
  90. package/packages/core/dist/workflow/workflow-schema.test.js +570 -1
  91. package/packages/core/dist/workflow/workflow-schema.test.js.map +1 -1
  92. package/pennyfarthing-dist/agents/dev.md +7 -11
  93. package/pennyfarthing-dist/agents/reviewer.md +9 -3
  94. package/pennyfarthing-dist/agents/sm-finish.md +18 -1
  95. package/pennyfarthing-dist/agents/sm-setup.md +1 -1
  96. package/pennyfarthing-dist/agents/sm.md +2 -2
  97. package/pennyfarthing-dist/agents/tea.md +1 -1
  98. package/pennyfarthing-dist/agents/testing-runner.md +2 -1
  99. package/pennyfarthing-dist/commands/pf-chore.md +2 -2
  100. package/pennyfarthing-dist/commands/pf-git.md +4 -2
  101. package/pennyfarthing-dist/commands/pf-standalone.md +7 -2
  102. package/pennyfarthing-dist/gates/approval.md +63 -0
  103. package/pennyfarthing-dist/gates/confidence-sm.md +71 -0
  104. package/pennyfarthing-dist/gates/context-ok.md +56 -0
  105. package/pennyfarthing-dist/gates/evaluations/confidence-sm.md +54 -0
  106. package/pennyfarthing-dist/gates/quality-pass.md +67 -0
  107. package/pennyfarthing-dist/gates/tests-fail.md +84 -0
  108. package/pennyfarthing-dist/gates/tests-pass.md +79 -0
  109. package/pennyfarthing-dist/guides/agent-behavior.md +23 -19
  110. package/pennyfarthing-dist/guides/agent-tag-taxonomy.md +1 -1
  111. package/pennyfarthing-dist/guides/bell-mode.md +1 -1
  112. package/pennyfarthing-dist/guides/bikerack.md +3 -3
  113. package/pennyfarthing-dist/guides/hooks.md +29 -29
  114. package/pennyfarthing-dist/guides/reflector.md +1 -1
  115. package/pennyfarthing-dist/guides/tandem-protocol.md +3 -3
  116. package/pennyfarthing-dist/guides/worktree-mode.md +3 -3
  117. package/pennyfarthing-dist/guides/xml-tags.md +2 -2
  118. package/pennyfarthing-dist/scripts/README.md +1 -1
  119. package/pennyfarthing-dist/scripts/core/check-context.sh +3 -1
  120. package/pennyfarthing-dist/scripts/core/phase-check-start.sh +5 -87
  121. package/pennyfarthing-dist/scripts/git/README.md +24 -14
  122. package/pennyfarthing-dist/scripts/git/create-feature-branches.sh +5 -266
  123. package/pennyfarthing-dist/scripts/git/git-status-all.sh +5 -151
  124. package/pennyfarthing-dist/scripts/git/install-git-hooks.sh +6 -144
  125. package/pennyfarthing-dist/scripts/git/worktree-manager.sh +5 -496
  126. package/pennyfarthing-dist/scripts/hooks/README.md +6 -6
  127. package/pennyfarthing-dist/scripts/hooks/__pycache__/question_reflector_check.cpython-314.pyc +0 -0
  128. package/pennyfarthing-dist/scripts/hooks/bell-mode-hook.sh +4 -183
  129. package/pennyfarthing-dist/scripts/hooks/context-circuit-breaker.sh +4 -95
  130. package/pennyfarthing-dist/scripts/hooks/context-warning.sh +4 -65
  131. package/pennyfarthing-dist/scripts/hooks/cyclist-pretooluse-hook.sh +3 -31
  132. package/pennyfarthing-dist/scripts/hooks/otel-auto-config.sh +9 -11
  133. package/pennyfarthing-dist/scripts/hooks/pre-commit.sh +27 -33
  134. package/pennyfarthing-dist/scripts/hooks/pre-edit-check.sh +4 -71
  135. package/pennyfarthing-dist/scripts/hooks/question-reflector-check.sh +3 -19
  136. package/pennyfarthing-dist/scripts/hooks/schema-validation.sh +4 -30
  137. package/pennyfarthing-dist/scripts/hooks/session-start.sh +3 -32
  138. package/pennyfarthing-dist/scripts/hooks/session-stop.sh +4 -65
  139. package/pennyfarthing-dist/scripts/hooks/sprint-yaml-validation.sh +4 -78
  140. package/pennyfarthing-dist/scripts/hooks/welcome-hook.sh +4 -93
  141. package/pennyfarthing-dist/scripts/misc/README.md +1 -1
  142. package/pennyfarthing-dist/scripts/misc/statusline.sh +4 -301
  143. package/pennyfarthing-dist/scripts/portraits/generate-tandem-portraits.sh +76 -0
  144. package/pennyfarthing-dist/scripts/workflow/fix-session-phase.sh +4 -221
  145. package/pennyfarthing-dist/scripts/workflow/get-workflow-type.sh +5 -13
  146. package/pennyfarthing-dist/scripts/workflow/list-workflows.sh +4 -123
  147. package/pennyfarthing-dist/scripts/workflow/phase-owner.sh +4 -33
  148. package/pennyfarthing-dist/scripts/workflow/resume-workflow.sh +4 -156
  149. package/pennyfarthing-dist/scripts/workflow/show-workflow.sh +4 -131
  150. package/pennyfarthing-dist/scripts/workflow/start-workflow.sh +4 -249
  151. package/pennyfarthing-dist/scripts/workflow/workflow-status.sh +4 -160
  152. package/pennyfarthing-dist/skills/pf-bc/usage.md +1 -1
  153. package/pennyfarthing-dist/skills/pf-jira/examples.md +5 -2
  154. package/pennyfarthing-dist/skills/pf-workflow/examples.md +27 -16
  155. package/pennyfarthing-dist/skills/pf-workflow/skill.md +9 -12
  156. package/pennyfarthing-dist/skills/pf-workflow/usage.md +33 -8
  157. package/pennyfarthing-dist/templates/settings.local.json.template +19 -10
  158. package/pennyfarthing-dist/workflows/bdd-tandem.yaml +18 -6
  159. package/pennyfarthing-dist/workflows/git-cleanup/steps/step-01-analyze.md +1 -1
  160. package/pennyfarthing-dist/workflows/git-cleanup/steps/step-04-verify.md +1 -1
  161. package/pennyfarthing-dist/workflows/git-cleanup/steps/step-05-complete.md +1 -1
  162. package/pennyfarthing-dist/workflows/review-tandem.yaml +65 -0
  163. package/pennyfarthing-dist/workflows/tdd-tandem.yaml +16 -8
  164. package/pennyfarthing-dist/workflows/tdd.yaml +11 -2
  165. package/pennyfarthing_scripts/CLAUDE.md +45 -14
  166. package/pennyfarthing_scripts/__pycache__/__init__.cpython-311.pyc +0 -0
  167. package/pennyfarthing_scripts/__pycache__/__init__.cpython-314.pyc +0 -0
  168. package/pennyfarthing_scripts/__pycache__/bellmode_hook.cpython-314.pyc +0 -0
  169. package/pennyfarthing_scripts/__pycache__/cli.cpython-314.pyc +0 -0
  170. package/pennyfarthing_scripts/__pycache__/config.cpython-314.pyc +0 -0
  171. package/pennyfarthing_scripts/__pycache__/context.cpython-314.pyc +0 -0
  172. package/pennyfarthing_scripts/__pycache__/hooks.cpython-314.pyc +0 -0
  173. package/pennyfarthing_scripts/__pycache__/jira.cpython-314.pyc +0 -0
  174. package/pennyfarthing_scripts/__pycache__/jira_bidirectional_sync.cpython-314.pyc +0 -0
  175. package/pennyfarthing_scripts/__pycache__/jira_epic_creation.cpython-314.pyc +0 -0
  176. package/pennyfarthing_scripts/__pycache__/jira_sync.cpython-314.pyc +0 -0
  177. package/pennyfarthing_scripts/__pycache__/jira_sync_story.cpython-314.pyc +0 -0
  178. package/pennyfarthing_scripts/__pycache__/output.cpython-314.pyc +0 -0
  179. package/pennyfarthing_scripts/__pycache__/patch_mode.cpython-314.pyc +0 -0
  180. package/pennyfarthing_scripts/__pycache__/pretooluse_hook.cpython-314.pyc +0 -0
  181. package/pennyfarthing_scripts/__pycache__/schema_validation_hook.cpython-314.pyc +0 -0
  182. package/pennyfarthing_scripts/__pycache__/session_start_hook.cpython-314.pyc +0 -0
  183. package/pennyfarthing_scripts/__pycache__/sprint.cpython-314.pyc +0 -0
  184. package/pennyfarthing_scripts/__pycache__/workflow.cpython-311.pyc +0 -0
  185. package/pennyfarthing_scripts/__pycache__/workflow.cpython-314.pyc +0 -0
  186. package/pennyfarthing_scripts/bc/__pycache__/__init__.cpython-314.pyc +0 -0
  187. package/pennyfarthing_scripts/bc/__pycache__/cli.cpython-314.pyc +0 -0
  188. package/pennyfarthing_scripts/bc/__pycache__/focus.cpython-314.pyc +0 -0
  189. package/pennyfarthing_scripts/bc/cli.py +3 -5
  190. package/pennyfarthing_scripts/bellmode_hook.py +12 -296
  191. package/pennyfarthing_scripts/bikerack/__pycache__/__init__.cpython-314.pyc +0 -0
  192. package/pennyfarthing_scripts/bikerack/__pycache__/__main__.cpython-314.pyc +0 -0
  193. package/pennyfarthing_scripts/bikerack/__pycache__/audit_log_panel.cpython-314.pyc +0 -0
  194. package/pennyfarthing_scripts/bikerack/__pycache__/background_panel.cpython-314.pyc +0 -0
  195. package/pennyfarthing_scripts/bikerack/__pycache__/base_panel.cpython-314.pyc +0 -0
  196. package/pennyfarthing_scripts/bikerack/__pycache__/changed_panel.cpython-314.pyc +0 -0
  197. package/pennyfarthing_scripts/bikerack/__pycache__/cli.cpython-314.pyc +0 -0
  198. package/pennyfarthing_scripts/bikerack/__pycache__/context_meter_footer.cpython-314.pyc +0 -0
  199. package/pennyfarthing_scripts/bikerack/__pycache__/debug_panel.cpython-314.pyc +0 -0
  200. package/pennyfarthing_scripts/bikerack/__pycache__/diffs_panel.cpython-314.pyc +0 -0
  201. package/pennyfarthing_scripts/bikerack/__pycache__/events.cpython-314.pyc +0 -0
  202. package/pennyfarthing_scripts/bikerack/__pycache__/git_panel.cpython-314.pyc +0 -0
  203. package/pennyfarthing_scripts/bikerack/__pycache__/launcher.cpython-314.pyc +0 -0
  204. package/pennyfarthing_scripts/bikerack/__pycache__/portrait_resolver.cpython-314.pyc +0 -0
  205. package/pennyfarthing_scripts/bikerack/__pycache__/progress_panel.cpython-314.pyc +0 -0
  206. package/pennyfarthing_scripts/bikerack/__pycache__/sprint_panel.cpython-314.pyc +0 -0
  207. package/pennyfarthing_scripts/bikerack/__pycache__/story_detail_data.cpython-314.pyc +0 -0
  208. package/pennyfarthing_scripts/bikerack/__pycache__/story_detail_screen.cpython-314.pyc +0 -0
  209. package/pennyfarthing_scripts/bikerack/__pycache__/tui.cpython-314.pyc +0 -0
  210. package/pennyfarthing_scripts/bikerack/__pycache__/ws_client.cpython-314.pyc +0 -0
  211. package/pennyfarthing_scripts/bikerack/audit_log_panel.py +119 -0
  212. package/pennyfarthing_scripts/bikerack/background_panel.py +86 -5
  213. package/pennyfarthing_scripts/bikerack/base_panel.py +87 -2
  214. package/pennyfarthing_scripts/bikerack/changed_panel.py +125 -29
  215. package/pennyfarthing_scripts/bikerack/context_meter_footer.py +88 -0
  216. package/pennyfarthing_scripts/bikerack/debug_panel.py +32 -2
  217. package/pennyfarthing_scripts/bikerack/diffs_panel.py +104 -17
  218. package/pennyfarthing_scripts/bikerack/events.py +28 -0
  219. package/pennyfarthing_scripts/bikerack/git_panel.py +103 -33
  220. package/pennyfarthing_scripts/bikerack/launcher.py +15 -15
  221. package/pennyfarthing_scripts/bikerack/portrait_resolver.py +139 -0
  222. package/pennyfarthing_scripts/bikerack/progress_panel.py +315 -0
  223. package/pennyfarthing_scripts/bikerack/sprint_panel.py +395 -32
  224. package/pennyfarthing_scripts/bikerack/story_detail_data.py +244 -0
  225. package/pennyfarthing_scripts/bikerack/story_detail_screen.py +176 -0
  226. package/pennyfarthing_scripts/bikerack/tui.py +575 -37
  227. package/pennyfarthing_scripts/bikerack/ws_client.py +2 -2
  228. package/pennyfarthing_scripts/brownfield/__pycache__/__init__.cpython-314.pyc +0 -0
  229. package/pennyfarthing_scripts/brownfield/__pycache__/__main__.cpython-314.pyc +0 -0
  230. package/pennyfarthing_scripts/brownfield/__pycache__/cli.cpython-314.pyc +0 -0
  231. package/pennyfarthing_scripts/brownfield/__pycache__/discover.cpython-314.pyc +0 -0
  232. package/pennyfarthing_scripts/cli.py +42 -65
  233. package/pennyfarthing_scripts/codemarkers/__pycache__/__init__.cpython-314.pyc +0 -0
  234. package/pennyfarthing_scripts/codemarkers/__pycache__/__main__.cpython-314.pyc +0 -0
  235. package/pennyfarthing_scripts/codemarkers/__pycache__/analyze.cpython-314.pyc +0 -0
  236. package/pennyfarthing_scripts/codemarkers/__pycache__/cli.cpython-314.pyc +0 -0
  237. package/pennyfarthing_scripts/codemarkers/__pycache__/formatters.cpython-314.pyc +0 -0
  238. package/pennyfarthing_scripts/codemarkers/__pycache__/models.cpython-314.pyc +0 -0
  239. package/pennyfarthing_scripts/common/__pycache__/__init__.cpython-314.pyc +0 -0
  240. package/pennyfarthing_scripts/common/__pycache__/config.cpython-314.pyc +0 -0
  241. package/pennyfarthing_scripts/common/__pycache__/output.cpython-314.pyc +0 -0
  242. package/pennyfarthing_scripts/common/__pycache__/themes.cpython-314.pyc +0 -0
  243. package/pennyfarthing_scripts/common/pr_config.py +38 -0
  244. package/pennyfarthing_scripts/complexity/__pycache__/__init__.cpython-314.pyc +0 -0
  245. package/pennyfarthing_scripts/complexity/__pycache__/__main__.cpython-314.pyc +0 -0
  246. package/pennyfarthing_scripts/complexity/__pycache__/analyze.cpython-314.pyc +0 -0
  247. package/pennyfarthing_scripts/complexity/__pycache__/cli.cpython-314.pyc +0 -0
  248. package/pennyfarthing_scripts/complexity/__pycache__/formatters.cpython-314.pyc +0 -0
  249. package/pennyfarthing_scripts/complexity/__pycache__/models.cpython-314.pyc +0 -0
  250. package/pennyfarthing_scripts/consultation/__init__.py +1 -0
  251. package/pennyfarthing_scripts/consultation/__pycache__/__init__.cpython-314.pyc +0 -0
  252. package/pennyfarthing_scripts/consultation/__pycache__/cli.cpython-314.pyc +0 -0
  253. package/pennyfarthing_scripts/consultation/__pycache__/dialogue_manager.cpython-314.pyc +0 -0
  254. package/pennyfarthing_scripts/consultation/cli.py +149 -0
  255. package/pennyfarthing_scripts/consultation/dialogue_manager.py +417 -0
  256. package/pennyfarthing_scripts/context.py +3 -3
  257. package/pennyfarthing_scripts/deadcode/__pycache__/__init__.cpython-314.pyc +0 -0
  258. package/pennyfarthing_scripts/deadcode/__pycache__/__main__.cpython-314.pyc +0 -0
  259. package/pennyfarthing_scripts/deadcode/__pycache__/analyze.cpython-314.pyc +0 -0
  260. package/pennyfarthing_scripts/deadcode/__pycache__/cli.cpython-314.pyc +0 -0
  261. package/pennyfarthing_scripts/deadcode/__pycache__/formatters.cpython-314.pyc +0 -0
  262. package/pennyfarthing_scripts/deadcode/__pycache__/models.cpython-314.pyc +0 -0
  263. package/pennyfarthing_scripts/dependencies/__pycache__/__init__.cpython-314.pyc +0 -0
  264. package/pennyfarthing_scripts/dependencies/__pycache__/__main__.cpython-314.pyc +0 -0
  265. package/pennyfarthing_scripts/dependencies/__pycache__/analyze.cpython-314.pyc +0 -0
  266. package/pennyfarthing_scripts/dependencies/__pycache__/cli.cpython-314.pyc +0 -0
  267. package/pennyfarthing_scripts/dependencies/__pycache__/formatters.cpython-314.pyc +0 -0
  268. package/pennyfarthing_scripts/dependencies/__pycache__/models.cpython-314.pyc +0 -0
  269. package/pennyfarthing_scripts/epic/__pycache__/__init__.cpython-314.pyc +0 -0
  270. package/pennyfarthing_scripts/epic/__pycache__/cli.cpython-314.pyc +0 -0
  271. package/pennyfarthing_scripts/git/__init__.py +12 -1
  272. package/pennyfarthing_scripts/git/__pycache__/__init__.cpython-314.pyc +0 -0
  273. package/pennyfarthing_scripts/git/__pycache__/create_branches.cpython-314.pyc +0 -0
  274. package/pennyfarthing_scripts/git/__pycache__/status_all.cpython-314.pyc +0 -0
  275. package/pennyfarthing_scripts/git/create_branches.py +3 -4
  276. package/pennyfarthing_scripts/git/hooks_installer.py +152 -0
  277. package/pennyfarthing_scripts/git/repos.py +196 -0
  278. package/pennyfarthing_scripts/git/status_all.py +27 -11
  279. package/pennyfarthing_scripts/git/worktree.py +302 -0
  280. package/pennyfarthing_scripts/git_group/__pycache__/__init__.cpython-314.pyc +0 -0
  281. package/pennyfarthing_scripts/git_group/__pycache__/cli.cpython-314.pyc +0 -0
  282. package/pennyfarthing_scripts/git_group/cli.py +143 -40
  283. package/pennyfarthing_scripts/handoff/__pycache__/__init__.cpython-314.pyc +0 -0
  284. package/pennyfarthing_scripts/handoff/__pycache__/cli.cpython-314.pyc +0 -0
  285. package/pennyfarthing_scripts/handoff/__pycache__/complete_phase.cpython-314.pyc +0 -0
  286. package/pennyfarthing_scripts/handoff/__pycache__/gate_file.cpython-314.pyc +0 -0
  287. package/pennyfarthing_scripts/handoff/__pycache__/gate_runner.cpython-314.pyc +0 -0
  288. package/pennyfarthing_scripts/handoff/__pycache__/marker.cpython-314.pyc +0 -0
  289. package/pennyfarthing_scripts/handoff/__pycache__/resolve_gate.cpython-314.pyc +0 -0
  290. package/pennyfarthing_scripts/handoff/cli.py +33 -1
  291. package/pennyfarthing_scripts/handoff/complete_phase.py +40 -0
  292. package/pennyfarthing_scripts/handoff/marker.py +15 -15
  293. package/pennyfarthing_scripts/handoff/phase_check.py +96 -0
  294. package/pennyfarthing_scripts/handoff/resolve_gate.py +18 -15
  295. package/pennyfarthing_scripts/healthscore/__pycache__/__init__.cpython-314.pyc +0 -0
  296. package/pennyfarthing_scripts/healthscore/__pycache__/__main__.cpython-314.pyc +0 -0
  297. package/pennyfarthing_scripts/healthscore/__pycache__/analyze.cpython-314.pyc +0 -0
  298. package/pennyfarthing_scripts/healthscore/__pycache__/cli.cpython-314.pyc +0 -0
  299. package/pennyfarthing_scripts/healthscore/__pycache__/formatters.cpython-314.pyc +0 -0
  300. package/pennyfarthing_scripts/healthscore/__pycache__/models.cpython-314.pyc +0 -0
  301. package/pennyfarthing_scripts/hooks/__init__.py +437 -0
  302. package/pennyfarthing_scripts/hooks/__pycache__/__init__.cpython-314.pyc +0 -0
  303. package/pennyfarthing_scripts/hooks/__pycache__/bell_mode.cpython-314.pyc +0 -0
  304. package/pennyfarthing_scripts/hooks/__pycache__/cli.cpython-314.pyc +0 -0
  305. package/pennyfarthing_scripts/hooks/__pycache__/context_breaker.cpython-314.pyc +0 -0
  306. package/pennyfarthing_scripts/hooks/__pycache__/context_warning.cpython-314.pyc +0 -0
  307. package/pennyfarthing_scripts/hooks/__pycache__/cyclist_pretooluse.cpython-314.pyc +0 -0
  308. package/pennyfarthing_scripts/hooks/__pycache__/pre_edit_check.cpython-314.pyc +0 -0
  309. package/pennyfarthing_scripts/hooks/__pycache__/reflector_check.cpython-314.pyc +0 -0
  310. package/pennyfarthing_scripts/hooks/__pycache__/schema_validation.cpython-314.pyc +0 -0
  311. package/pennyfarthing_scripts/hooks/__pycache__/session_start.cpython-314.pyc +0 -0
  312. package/pennyfarthing_scripts/hooks/__pycache__/session_stop.cpython-314.pyc +0 -0
  313. package/pennyfarthing_scripts/hooks/__pycache__/sprint_yaml_validation.cpython-314.pyc +0 -0
  314. package/pennyfarthing_scripts/hooks/__pycache__/statusline.cpython-314.pyc +0 -0
  315. package/pennyfarthing_scripts/hooks/bell_mode.py +215 -0
  316. package/pennyfarthing_scripts/hooks/cli.py +96 -0
  317. package/pennyfarthing_scripts/hooks/context_breaker.py +104 -0
  318. package/pennyfarthing_scripts/hooks/context_warning.py +66 -0
  319. package/pennyfarthing_scripts/hooks/cyclist_pretooluse.py +129 -0
  320. package/pennyfarthing_scripts/hooks/pre_edit_check.py +78 -0
  321. package/pennyfarthing_scripts/hooks/reflector_check.py +271 -0
  322. package/pennyfarthing_scripts/hooks/schema_validation.py +203 -0
  323. package/pennyfarthing_scripts/hooks/session_start.py +296 -0
  324. package/pennyfarthing_scripts/hooks/session_stop.py +111 -0
  325. package/pennyfarthing_scripts/hooks/sprint_yaml_validation.py +97 -0
  326. package/pennyfarthing_scripts/hooks/statusline.py +420 -0
  327. package/pennyfarthing_scripts/hooks.py +27 -446
  328. package/pennyfarthing_scripts/hotspots/__pycache__/__init__.cpython-314.pyc +0 -0
  329. package/pennyfarthing_scripts/hotspots/__pycache__/__main__.cpython-314.pyc +0 -0
  330. package/pennyfarthing_scripts/hotspots/__pycache__/analyze.cpython-314.pyc +0 -0
  331. package/pennyfarthing_scripts/hotspots/__pycache__/cli.cpython-314.pyc +0 -0
  332. package/pennyfarthing_scripts/hotspots/__pycache__/formatters.cpython-314.pyc +0 -0
  333. package/pennyfarthing_scripts/hotspots/__pycache__/models.cpython-314.pyc +0 -0
  334. package/pennyfarthing_scripts/jira/__pycache__/__init__.cpython-314.pyc +0 -0
  335. package/pennyfarthing_scripts/jira/__pycache__/__main__.cpython-314.pyc +0 -0
  336. package/pennyfarthing_scripts/jira/__pycache__/bidirectional.cpython-314.pyc +0 -0
  337. package/pennyfarthing_scripts/jira/__pycache__/claim.cpython-314.pyc +0 -0
  338. package/pennyfarthing_scripts/jira/__pycache__/cli.cpython-314.pyc +0 -0
  339. package/pennyfarthing_scripts/jira/__pycache__/client.cpython-314.pyc +0 -0
  340. package/pennyfarthing_scripts/jira/__pycache__/compat.cpython-314.pyc +0 -0
  341. package/pennyfarthing_scripts/jira/__pycache__/create.cpython-314.pyc +0 -0
  342. package/pennyfarthing_scripts/jira/__pycache__/epic.cpython-314.pyc +0 -0
  343. package/pennyfarthing_scripts/jira/__pycache__/mappings.cpython-314.pyc +0 -0
  344. package/pennyfarthing_scripts/jira/__pycache__/models.cpython-314.pyc +0 -0
  345. package/pennyfarthing_scripts/jira/__pycache__/operations.cpython-314.pyc +0 -0
  346. package/pennyfarthing_scripts/jira/__pycache__/reconcile.cpython-314.pyc +0 -0
  347. package/pennyfarthing_scripts/jira/__pycache__/story.cpython-314.pyc +0 -0
  348. package/pennyfarthing_scripts/jira/__pycache__/sync.cpython-314.pyc +0 -0
  349. package/pennyfarthing_scripts/launch/__pycache__/__init__.cpython-314.pyc +0 -0
  350. package/pennyfarthing_scripts/launch/__pycache__/cli.cpython-314.pyc +0 -0
  351. package/pennyfarthing_scripts/migration/__pycache__/__init__.cpython-314.pyc +0 -0
  352. package/pennyfarthing_scripts/migration/__pycache__/__main__.cpython-314.pyc +0 -0
  353. package/pennyfarthing_scripts/migration/__pycache__/cli.cpython-314.pyc +0 -0
  354. package/pennyfarthing_scripts/migration/__pycache__/session.cpython-314.pyc +0 -0
  355. package/pennyfarthing_scripts/migration/__pycache__/skill.cpython-314.pyc +0 -0
  356. package/pennyfarthing_scripts/migration/__pycache__/step.cpython-314.pyc +0 -0
  357. package/pennyfarthing_scripts/migration/__pycache__/validate.cpython-314.pyc +0 -0
  358. package/pennyfarthing_scripts/preflight/__pycache__/__init__.cpython-314.pyc +0 -0
  359. package/pennyfarthing_scripts/preflight/__pycache__/__main__.cpython-314.pyc +0 -0
  360. package/pennyfarthing_scripts/preflight/__pycache__/cli.cpython-314.pyc +0 -0
  361. package/pennyfarthing_scripts/preflight/__pycache__/finish.cpython-314.pyc +0 -0
  362. package/pennyfarthing_scripts/pretooluse_hook.py +3 -185
  363. package/pennyfarthing_scripts/prime/__pycache__/__init__.cpython-314.pyc +0 -0
  364. package/pennyfarthing_scripts/prime/__pycache__/__main__.cpython-314.pyc +0 -0
  365. package/pennyfarthing_scripts/prime/__pycache__/cli.cpython-314.pyc +0 -0
  366. package/pennyfarthing_scripts/prime/__pycache__/loader.cpython-314.pyc +0 -0
  367. package/pennyfarthing_scripts/prime/__pycache__/models.cpython-314.pyc +0 -0
  368. package/pennyfarthing_scripts/prime/__pycache__/persona.cpython-314.pyc +0 -0
  369. package/pennyfarthing_scripts/prime/__pycache__/session.cpython-314.pyc +0 -0
  370. package/pennyfarthing_scripts/prime/__pycache__/tiers.cpython-314.pyc +0 -0
  371. package/pennyfarthing_scripts/prime/__pycache__/version_sentinel.cpython-314.pyc +0 -0
  372. package/pennyfarthing_scripts/prime/__pycache__/workflow.cpython-314.pyc +0 -0
  373. package/pennyfarthing_scripts/prime/heatmap.py +655 -0
  374. package/pennyfarthing_scripts/prime/workflow.py +2 -1
  375. package/pennyfarthing_scripts/schema_validation_hook.py +3 -298
  376. package/pennyfarthing_scripts/session/__pycache__/__init__.cpython-314.pyc +0 -0
  377. package/pennyfarthing_scripts/session/__pycache__/cli.cpython-314.pyc +0 -0
  378. package/pennyfarthing_scripts/session_start_hook.py +4 -186
  379. package/pennyfarthing_scripts/sprint/__pycache__/__init__.cpython-314.pyc +0 -0
  380. package/pennyfarthing_scripts/sprint/__pycache__/__main__.cpython-314.pyc +0 -0
  381. package/pennyfarthing_scripts/sprint/__pycache__/archive.cpython-314.pyc +0 -0
  382. package/pennyfarthing_scripts/sprint/__pycache__/archive_epic.cpython-314.pyc +0 -0
  383. package/pennyfarthing_scripts/sprint/__pycache__/cli.cpython-314.pyc +0 -0
  384. package/pennyfarthing_scripts/sprint/__pycache__/epic_add.cpython-314.pyc +0 -0
  385. package/pennyfarthing_scripts/sprint/__pycache__/epic_update.cpython-314.pyc +0 -0
  386. package/pennyfarthing_scripts/sprint/__pycache__/import_epic.cpython-314.pyc +0 -0
  387. package/pennyfarthing_scripts/sprint/__pycache__/loader.cpython-314.pyc +0 -0
  388. package/pennyfarthing_scripts/sprint/__pycache__/status.cpython-314.pyc +0 -0
  389. package/pennyfarthing_scripts/sprint/__pycache__/story_add.cpython-314.pyc +0 -0
  390. package/pennyfarthing_scripts/sprint/__pycache__/story_finish.cpython-314.pyc +0 -0
  391. package/pennyfarthing_scripts/sprint/__pycache__/story_update.cpython-314.pyc +0 -0
  392. package/pennyfarthing_scripts/sprint/__pycache__/validate_cmd.cpython-314.pyc +0 -0
  393. package/pennyfarthing_scripts/sprint/__pycache__/validator.cpython-314.pyc +0 -0
  394. package/pennyfarthing_scripts/sprint/__pycache__/work.cpython-314.pyc +0 -0
  395. package/pennyfarthing_scripts/sprint/__pycache__/yaml_io.cpython-314.pyc +0 -0
  396. package/pennyfarthing_scripts/sprint/loader.py +15 -1
  397. package/pennyfarthing_scripts/sprint/story_update.py +19 -0
  398. package/pennyfarthing_scripts/story/__pycache__/__init__.cpython-314.pyc +0 -0
  399. package/pennyfarthing_scripts/story/__pycache__/__main__.cpython-314.pyc +0 -0
  400. package/pennyfarthing_scripts/story/__pycache__/cli.cpython-314.pyc +0 -0
  401. package/pennyfarthing_scripts/story/__pycache__/create.cpython-314.pyc +0 -0
  402. package/pennyfarthing_scripts/story/__pycache__/size.cpython-314.pyc +0 -0
  403. package/pennyfarthing_scripts/story/__pycache__/template.cpython-314.pyc +0 -0
  404. package/pennyfarthing_scripts/tests/__pycache__/__init__.cpython-314.pyc +0 -0
  405. package/pennyfarthing_scripts/tests/__pycache__/conftest.cpython-314-pytest-9.0.2.pyc +0 -0
  406. package/pennyfarthing_scripts/tests/__pycache__/test_108_1_gate_migration.cpython-314-pytest-9.0.2.pyc +0 -0
  407. package/pennyfarthing_scripts/tests/__pycache__/test_archive_epic.cpython-314-pytest-9.0.2.pyc +0 -0
  408. package/pennyfarthing_scripts/tests/__pycache__/test_bc.cpython-314-pytest-9.0.2.pyc +0 -0
  409. package/pennyfarthing_scripts/tests/__pycache__/test_bikerack.cpython-314-pytest-9.0.2.pyc +0 -0
  410. package/pennyfarthing_scripts/tests/__pycache__/test_brownfield.cpython-314-pytest-9.0.2.pyc +0 -0
  411. package/pennyfarthing_scripts/tests/__pycache__/test_cli_modules.cpython-314-pytest-9.0.2.pyc +0 -0
  412. package/pennyfarthing_scripts/tests/__pycache__/test_cli_normalization.cpython-314-pytest-9.0.2.pyc +0 -0
  413. package/pennyfarthing_scripts/tests/__pycache__/test_codemarkers.cpython-314-pytest-9.0.2.pyc +0 -0
  414. package/pennyfarthing_scripts/tests/__pycache__/test_common.cpython-314-pytest-9.0.2.pyc +0 -0
  415. package/pennyfarthing_scripts/tests/__pycache__/test_confidence_sm_evaluation.cpython-314-pytest-9.0.2.pyc +0 -0
  416. package/pennyfarthing_scripts/tests/__pycache__/test_confidence_sm_gate.cpython-314-pytest-9.0.2.pyc +0 -0
  417. package/pennyfarthing_scripts/tests/__pycache__/test_dialogue_manager.cpython-314-pytest-9.0.2.pyc +0 -0
  418. package/pennyfarthing_scripts/tests/__pycache__/test_epic_shard_validation.cpython-314-pytest-9.0.2.pyc +0 -0
  419. package/pennyfarthing_scripts/tests/__pycache__/test_git_utils.cpython-314-pytest-9.0.2.pyc +0 -0
  420. package/pennyfarthing_scripts/tests/__pycache__/test_handoff_cli.cpython-314-pytest-9.0.2.pyc +0 -0
  421. package/pennyfarthing_scripts/tests/__pycache__/test_handoff_e2e.cpython-314-pytest-9.0.2.pyc +0 -0
  422. package/pennyfarthing_scripts/tests/__pycache__/test_healthscore.cpython-314-pytest-9.0.2.pyc +0 -0
  423. package/pennyfarthing_scripts/tests/__pycache__/test_jira_package.cpython-314-pytest-9.0.2.pyc +0 -0
  424. package/pennyfarthing_scripts/tests/__pycache__/test_package_structure.cpython-314-pytest-9.0.2.pyc +0 -0
  425. package/pennyfarthing_scripts/tests/__pycache__/test_patch_mode.cpython-314-pytest-9.0.2.pyc +0 -0
  426. package/pennyfarthing_scripts/tests/__pycache__/test_prime.cpython-314-pytest-9.0.2.pyc +0 -0
  427. package/pennyfarthing_scripts/tests/__pycache__/test_sprint_package.cpython-314-pytest-9.0.2.pyc +0 -0
  428. package/pennyfarthing_scripts/tests/__pycache__/test_sprint_panel.cpython-314-pytest-9.0.2.pyc +0 -0
  429. package/pennyfarthing_scripts/tests/__pycache__/test_sprint_validator.cpython-314-pytest-9.0.2.pyc +0 -0
  430. package/pennyfarthing_scripts/tests/__pycache__/test_story_add.cpython-314-pytest-9.0.2.pyc +0 -0
  431. package/pennyfarthing_scripts/tests/__pycache__/test_story_package.cpython-314-pytest-9.0.2.pyc +0 -0
  432. package/pennyfarthing_scripts/tests/__pycache__/test_story_update.cpython-314-pytest-9.0.2.pyc +0 -0
  433. package/pennyfarthing_scripts/tests/__pycache__/test_tiers.cpython-314-pytest-9.0.2.pyc +0 -0
  434. package/pennyfarthing_scripts/tests/__pycache__/test_token_counting.cpython-314-pytest-9.0.2.pyc +0 -0
  435. package/pennyfarthing_scripts/tests/__pycache__/test_topology_loader.cpython-314-pytest-9.0.2.pyc +0 -0
  436. package/pennyfarthing_scripts/tests/__pycache__/test_tui_focus.cpython-314-pytest-9.0.2.pyc +0 -0
  437. package/pennyfarthing_scripts/tests/__pycache__/test_tui_panel_persistence.cpython-314-pytest-9.0.2.pyc +0 -0
  438. package/pennyfarthing_scripts/tests/__pycache__/test_validate_cmd.cpython-314-pytest-9.0.2.pyc +0 -0
  439. package/pennyfarthing_scripts/tests/__pycache__/test_version_sentinel.cpython-314-pytest-9.0.2.pyc +0 -0
  440. package/pennyfarthing_scripts/tests/__pycache__/test_workflow_check.cpython-314-pytest-9.0.2.pyc +0 -0
  441. package/pennyfarthing_scripts/tests/__pycache__/test_workflow_cli.cpython-314-pytest-9.0.2.pyc +0 -0
  442. package/pennyfarthing_scripts/tests/__pycache__/test_yaml_io.cpython-314-pytest-9.0.2.pyc +0 -0
  443. package/pennyfarthing_scripts/tests/test_bikerack.py +51 -51
  444. package/pennyfarthing_scripts/tests/test_dialogue_manager.py +811 -0
  445. package/pennyfarthing_scripts/tests/test_handoff_cli.py +16 -11
  446. package/pennyfarthing_scripts/tests/test_sprint_panel.py +344 -265
  447. package/pennyfarthing_scripts/tests/test_workflow_check.py +2 -3
  448. package/pennyfarthing_scripts/theme/__pycache__/__init__.cpython-314.pyc +0 -0
  449. package/pennyfarthing_scripts/theme/__pycache__/cli.cpython-314.pyc +0 -0
  450. package/pennyfarthing_scripts/validate/__pycache__/__init__.cpython-314.pyc +0 -0
  451. package/pennyfarthing_scripts/validate/__pycache__/cli.cpython-314.pyc +0 -0
  452. package/pennyfarthing_scripts/validate/adapters/__pycache__/__init__.cpython-314.pyc +0 -0
  453. package/pennyfarthing_scripts/validate/adapters/__pycache__/agent.cpython-314.pyc +0 -0
  454. package/pennyfarthing_scripts/validate/adapters/__pycache__/schema.cpython-314.pyc +0 -0
  455. package/pennyfarthing_scripts/validate/adapters/__pycache__/skill_command.cpython-314.pyc +0 -0
  456. package/pennyfarthing_scripts/validate/adapters/__pycache__/sprint.cpython-314.pyc +0 -0
  457. package/pennyfarthing_scripts/validate/adapters/__pycache__/tandem_awareness.cpython-314.pyc +0 -0
  458. package/pennyfarthing_scripts/validate/adapters/__pycache__/workflow.cpython-314.pyc +0 -0
  459. package/pennyfarthing_scripts/validate/adapters/tandem_awareness.py +254 -0
  460. package/pennyfarthing_scripts/validate/adapters/workflow.py +19 -0
  461. package/pennyfarthing_scripts/validate/cli.py +17 -5
  462. package/pennyfarthing_scripts/welcome_hook.py +3 -149
  463. package/pennyfarthing_scripts/workflow/__init__.py +40 -0
  464. package/pennyfarthing_scripts/workflow/__pycache__/__init__.cpython-314.pyc +0 -0
  465. package/pennyfarthing_scripts/workflow/__pycache__/cli.cpython-314.pyc +0 -0
  466. package/pennyfarthing_scripts/workflow/__pycache__/helpers.cpython-314.pyc +0 -0
  467. package/pennyfarthing_scripts/workflow/__pycache__/scale.cpython-314.pyc +0 -0
  468. package/pennyfarthing_scripts/workflow/__pycache__/state.cpython-314.pyc +0 -0
  469. package/pennyfarthing_scripts/workflow/cli.py +1100 -0
  470. package/pennyfarthing_scripts/workflow/helpers.py +241 -0
  471. package/pennyfarthing_scripts/{workflow.py → workflow/scale.py} +0 -104
  472. package/pennyfarthing_scripts/workflow/state.py +112 -0
  473. package/pennyfarthing_scripts/workflow/team_lifecycle.py +257 -0
  474. package/packages/core/dist/scripts/theme-detail.test.d.ts +0 -10
  475. package/packages/core/dist/scripts/theme-detail.test.js +0 -199
  476. package/pennyfarthing-dist/skills/pf-workflow/scripts/list-workflows.sh +0 -91
  477. package/pennyfarthing-dist/skills/pf-workflow/scripts/resume-workflow.sh +0 -163
  478. package/pennyfarthing-dist/skills/pf-workflow/scripts/show-workflow.sh +0 -138
  479. package/pennyfarthing-dist/skills/pf-workflow/scripts/start-workflow.sh +0 -273
  480. package/pennyfarthing-dist/skills/pf-workflow/scripts/workflow-status.sh +0 -167
  481. package/pennyfarthing_scripts/gate/__pycache__/__init__.cpython-314.pyc +0 -0
  482. package/pennyfarthing_scripts/gate/__pycache__/cli.cpython-314.pyc +0 -0
  483. package/pennyfarthing_scripts/gate/__pycache__/validate.cpython-314.pyc +0 -0
  484. package/pennyfarthing_scripts/tests/__pycache__/test_108_2_remove_handoff_fallback.cpython-314-pytest-9.0.2.pyc +0 -0
  485. package/pennyfarthing_scripts/tests/__pycache__/test_gate_file_resolution.cpython-314-pytest-9.0.2.pyc +0 -0
  486. package/pennyfarthing_scripts/tests/__pycache__/test_gate_runner.cpython-314-pytest-9.0.2.pyc +0 -0
  487. package/pennyfarthing_scripts/tests/__pycache__/test_resolve_gate_file_field.cpython-314-pytest-9.0.2.pyc +0 -0
@@ -0,0 +1,1100 @@
1
+ """Workflow CLI — phase management, stepped workflow control, and state queries.
2
+
3
+ Usage:
4
+ pf workflow check [--json]
5
+ pf workflow phase-check WORKFLOW PHASE
6
+ pf workflow handoff NEXT_AGENT
7
+ pf workflow type WORKFLOW
8
+ pf workflow list
9
+ pf workflow show [NAME]
10
+ pf workflow start NAME [--mode MODE]
11
+ pf workflow resume [NAME]
12
+ pf workflow status [NAME]
13
+ pf workflow fix-phase STORY_ID PHASE [--dry-run]
14
+ pf workflow complete-step [NAME] [--step N]
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ import click
20
+
21
+
22
+ @click.group()
23
+ def workflow():
24
+ """Workflow state and phase management.
25
+
26
+ \b
27
+ Commands:
28
+ check - Check current workflow state
29
+ phase-check - Verify phase ownership
30
+ handoff - Emit handoff marker
31
+ type - Get workflow type (phased/stepped/procedural)
32
+ list - List all available workflows
33
+ show - Show workflow details
34
+ start - Start a stepped workflow
35
+ resume - Resume an interrupted workflow
36
+ status - Show stepped workflow progress
37
+ fix-phase - Repair session phase tracking
38
+ complete-step - Complete current step in stepped workflow
39
+ """
40
+ pass
41
+
42
+
43
+ # ---------------------------------------------------------------------------
44
+ # Existing commands (migrated from inline cli.py)
45
+ # ---------------------------------------------------------------------------
46
+
47
+
48
+ @workflow.command("check")
49
+ @click.option("--json", "output_json", is_flag=True, help="Output as JSON")
50
+ def workflow_check(output_json: bool):
51
+ """Check current workflow state.
52
+
53
+ Returns the current story ID, phase, and workflow state.
54
+ """
55
+ from pennyfarthing_scripts.workflow.state import get_workflow_state
56
+
57
+ state = get_workflow_state()
58
+
59
+ if output_json:
60
+ import json
61
+
62
+ click.echo(json.dumps(state, indent=2))
63
+ else:
64
+ click.echo(f"State: {state.get('state', 'unknown')}")
65
+ if state.get("story_id"):
66
+ click.echo(f"Story: {state['story_id']}")
67
+ if state.get("workflow"):
68
+ click.echo(f"Workflow: {state['workflow']}")
69
+ if state.get("phase"):
70
+ click.echo(f"Phase: {state['phase']}")
71
+
72
+
73
+ @workflow.command("phase-check")
74
+ @click.argument("workflow_name")
75
+ @click.argument("phase")
76
+ def workflow_phase_check(workflow_name: str, phase: str):
77
+ """Check which agent owns a workflow phase.
78
+
79
+ \b
80
+ Arguments:
81
+ WORKFLOW_NAME - The workflow type (tdd, trivial, etc.)
82
+ PHASE - The phase to check (red, implement, review, etc.)
83
+ """
84
+ from pennyfarthing_scripts.workflow.state import get_phase_owner
85
+
86
+ owner = get_phase_owner(workflow_name, phase)
87
+ click.echo(owner)
88
+
89
+
90
+ @workflow.command("handoff")
91
+ @click.argument("next_agent")
92
+ def workflow_handoff(next_agent: str):
93
+ """Emit an environment-aware handoff marker.
94
+
95
+ Delegates to generate_marker() which detects Cyclist, relay mode,
96
+ and context usage to choose the appropriate marker type.
97
+
98
+ \b
99
+ Arguments:
100
+ NEXT_AGENT - The agent to hand off to (tea, dev, reviewer, etc.)
101
+ """
102
+ from pennyfarthing_scripts.handoff.marker import generate_marker
103
+
104
+ click.echo(generate_marker(next_agent))
105
+
106
+
107
+ # ---------------------------------------------------------------------------
108
+ # New commands (migrated from bash scripts)
109
+ # ---------------------------------------------------------------------------
110
+
111
+
112
+ @workflow.command("type")
113
+ @click.argument("workflow_name")
114
+ def workflow_type_cmd(workflow_name: str):
115
+ """Get workflow type (phased, stepped, or procedural).
116
+
117
+ \b
118
+ Arguments:
119
+ WORKFLOW_NAME - Workflow name (e.g., tdd, architecture)
120
+ """
121
+ from pennyfarthing_scripts.workflow.helpers import (
122
+ find_workflow_file,
123
+ get_workflow_type,
124
+ get_workflows_dir,
125
+ load_workflow_data,
126
+ )
127
+
128
+ workflows_dir = get_workflows_dir()
129
+ wf_file = find_workflow_file(workflows_dir, workflow_name)
130
+ if not wf_file:
131
+ click.echo(f"Error: Workflow '{workflow_name}' not found", err=True)
132
+ raise SystemExit(1)
133
+
134
+ data = load_workflow_data(wf_file)
135
+ click.echo(get_workflow_type(data))
136
+
137
+
138
+ @workflow.command("list")
139
+ def workflow_list_cmd():
140
+ """List all available workflows.
141
+
142
+ Shows a markdown table with type, phases/steps, modes, and descriptions.
143
+ """
144
+ import yaml as yaml_mod
145
+
146
+ from pennyfarthing_scripts.workflow.helpers import (
147
+ count_steps,
148
+ get_workflows_dir,
149
+ load_workflow_data,
150
+ resolve_steps_path,
151
+ )
152
+
153
+ workflows_dir = get_workflows_dir()
154
+
155
+ if not workflows_dir.is_dir():
156
+ click.echo(f"Error: Workflows directory not found at {workflows_dir}", err=True)
157
+ raise SystemExit(1)
158
+
159
+ # Collect workflow files: top-level *.yaml and nested workflow.yaml
160
+ workflow_files = sorted(workflows_dir.glob("*.yaml"))
161
+ for subdir in sorted(workflows_dir.iterdir()):
162
+ if subdir.is_dir():
163
+ nested = subdir / "workflow.yaml"
164
+ if nested.exists():
165
+ workflow_files.append(nested)
166
+
167
+ if not workflow_files:
168
+ click.echo("No workflows found.")
169
+ return
170
+
171
+ click.echo("# Available Workflows")
172
+ click.echo("")
173
+ click.echo("| Workflow | Type | Default | Steps/Phases | Modes | Description |")
174
+ click.echo("|----------|------|---------|--------------|-------|-------------|")
175
+
176
+ from pennyfarthing_scripts.common.config import get_project_root
177
+
178
+ project_root = get_project_root()
179
+
180
+ for wf_file in workflow_files:
181
+ data = load_workflow_data(wf_file)
182
+ wf = data.get("workflow", {})
183
+
184
+ name = wf.get("name", wf_file.stem)
185
+ desc = (wf.get("description") or "-")
186
+ if isinstance(desc, str):
187
+ desc = desc.split("\n")[0][:80]
188
+ is_default = wf.get("triggers", {}).get("default", False)
189
+
190
+ # Detect type
191
+ wf_type_raw = wf.get("type", "phased")
192
+ has_steps = wf.get("steps") is not None
193
+
194
+ if has_steps or wf_type_raw == "stepped":
195
+ type_col = "stepped"
196
+ try:
197
+ steps_path = resolve_steps_path(data, wf_file.parent, None, project_root)
198
+ step_count = count_steps(steps_path)
199
+ steps_col = f"{step_count} steps" if step_count > 0 else "-"
200
+ except Exception:
201
+ steps_col = "-"
202
+ elif wf_type_raw == "procedural":
203
+ type_col = "procedural"
204
+ steps_col = "instructions"
205
+ else:
206
+ type_col = "phased"
207
+ phases = wf.get("phases", [])
208
+ steps_col = f"{len(phases)} phases"
209
+
210
+ default_col = "yes" if is_default else "no"
211
+
212
+ # Modes
213
+ modes_available = wf.get("modes", {}).get("available", [])
214
+ if modes_available:
215
+ modes_col = ",".join(modes_available)
216
+ else:
217
+ modes_col = "-"
218
+
219
+ click.echo(f"| {name} | {type_col} | {default_col} | {steps_col} | {modes_col} | {desc} |")
220
+
221
+ click.echo("")
222
+ click.echo("**Legend:**")
223
+ click.echo("- **phased**: Agent-driven workflow (SM > TEA > Dev > Reviewer)")
224
+ click.echo("- **stepped**: Step-by-step guided workflow with progressive disclosure")
225
+ click.echo("- **procedural**: BMAD reference workflow with instructions file")
226
+ click.echo("")
227
+ click.echo("Use `pf workflow show <name>` for workflow details.")
228
+
229
+
230
+ @workflow.command("show")
231
+ @click.argument("name", required=False, default=None)
232
+ def workflow_show_cmd(name: str | None):
233
+ """Show workflow details including phase flow, triggers, and gates.
234
+
235
+ \b
236
+ Arguments:
237
+ NAME - Workflow name (defaults to current session's workflow or tdd)
238
+ """
239
+ from pennyfarthing_scripts.common.config import get_project_root
240
+ from pennyfarthing_scripts.workflow.helpers import (
241
+ find_workflow_file,
242
+ get_session_dir,
243
+ get_workflows_dir,
244
+ load_workflow_data,
245
+ )
246
+
247
+ project_root = get_project_root()
248
+ workflows_dir = get_workflows_dir(project_root)
249
+ session_dir = get_session_dir(project_root)
250
+
251
+ workflow_name = name
252
+
253
+ if not workflow_name:
254
+ # Try to detect from current session
255
+ if session_dir.is_dir():
256
+ for sf in session_dir.glob("*-session.md"):
257
+ content = sf.read_text()
258
+ import re
259
+
260
+ match = re.search(r"\*\*Workflow:\*\*\s*(\S+)", content)
261
+ if match:
262
+ workflow_name = match.group(1)
263
+ break
264
+
265
+ if not workflow_name:
266
+ click.echo("# Current Workflow")
267
+ click.echo("")
268
+ click.echo("No active session found. Showing default workflow (tdd).")
269
+ click.echo("")
270
+ workflow_name = "tdd"
271
+ else:
272
+ click.echo(f"# Current Session Workflow: {workflow_name}")
273
+ click.echo("")
274
+ else:
275
+ click.echo(f"# Workflow: {workflow_name}")
276
+ click.echo("")
277
+
278
+ wf_file = find_workflow_file(workflows_dir, workflow_name)
279
+ if not wf_file:
280
+ click.echo(f"Error: Workflow '{workflow_name}' not found", err=True)
281
+ click.echo("", err=True)
282
+ click.echo("Available workflows:", err=True)
283
+ for f in sorted(workflows_dir.glob("*.yaml")):
284
+ click.echo(f" {f.stem}", err=True)
285
+ raise SystemExit(1)
286
+
287
+ data = load_workflow_data(wf_file)
288
+ wf = data.get("workflow", {})
289
+
290
+ desc = wf.get("description", "-")
291
+ version = wf.get("version", "-")
292
+
293
+ click.echo(f"**Description:** {desc}")
294
+ click.echo(f"**Version:** {version}")
295
+ click.echo("")
296
+
297
+ # Phase flow diagram
298
+ phases = wf.get("phases", [])
299
+ if phases:
300
+ click.echo("## Phase Flow")
301
+ click.echo("")
302
+ phase_names = [p.get("name", "?") for p in phases]
303
+ click.echo("```")
304
+ click.echo(" -> ".join(phase_names))
305
+ click.echo("```")
306
+ click.echo("")
307
+
308
+ # Phases table
309
+ click.echo("## Phases")
310
+ click.echo("")
311
+ click.echo("| Phase | Agent | Gate |")
312
+ click.echo("|-------|-------|------|")
313
+ for p in phases:
314
+ pname = p.get("name", "?")
315
+ pagent = p.get("agent", "?")
316
+ pgate = p.get("gate", {}).get("type", "none") if isinstance(p.get("gate"), dict) else "none"
317
+ click.echo(f"| {pname} | {pagent} | {pgate} |")
318
+ click.echo("")
319
+
320
+ # Triggers
321
+ triggers = wf.get("triggers", {})
322
+ if triggers:
323
+ click.echo("## Triggers")
324
+ click.echo("")
325
+
326
+ types = triggers.get("types", [])
327
+ if types:
328
+ click.echo(f"**Types:** {', '.join(types)}")
329
+
330
+ points = triggers.get("points", {})
331
+ if points.get("min") is not None:
332
+ click.echo(f"**Points Min:** {points['min']}")
333
+ if points.get("max") is not None:
334
+ click.echo(f"**Points Max:** {points['max']}")
335
+
336
+ if triggers.get("default"):
337
+ click.echo("**Default:** yes (used when no other workflow matches)")
338
+
339
+ tags = triggers.get("tags", [])
340
+ if tags:
341
+ click.echo(f"**Tags:** {', '.join(tags)}")
342
+
343
+
344
+ @workflow.command("start")
345
+ @click.argument("name")
346
+ @click.option("--mode", "-m", default=None, help="Mode: create, validate, or edit")
347
+ def workflow_start_cmd(name: str, mode: str | None):
348
+ """Start a stepped workflow from step 1.
349
+
350
+ Creates a new workflow session and loads the first step.
351
+
352
+ \b
353
+ Arguments:
354
+ NAME - Workflow name (e.g., architecture, release)
355
+ """
356
+ from datetime import datetime, timezone
357
+
358
+ from pennyfarthing_scripts.common.config import get_project_root
359
+ from pennyfarthing_scripts.workflow.helpers import (
360
+ count_steps,
361
+ find_step_file,
362
+ find_workflow_file,
363
+ get_session_dir,
364
+ get_workflows_dir,
365
+ load_workflow_data,
366
+ resolve_steps_path,
367
+ strip_frontmatter,
368
+ )
369
+
370
+ project_root = get_project_root()
371
+ workflows_dir = get_workflows_dir(project_root)
372
+ session_dir = get_session_dir(project_root)
373
+
374
+ # Find workflow file
375
+ wf_file = find_workflow_file(workflows_dir, name)
376
+ if not wf_file:
377
+ click.echo(f"Error: Workflow '{name}' not found", err=True)
378
+ raise SystemExit(1)
379
+
380
+ data = load_workflow_data(wf_file)
381
+ wf = data.get("workflow", {})
382
+
383
+ # Validate it's a stepped workflow
384
+ wf_type = wf.get("type", "phased")
385
+ has_steps = wf.get("steps") is not None
386
+ if wf_type != "stepped" and not has_steps:
387
+ click.echo(f"Error: '{name}' is a phased workflow, not stepped", err=True)
388
+ click.echo("Use TDD workflow commands (/sm, /tea, /dev, /reviewer) for phased workflows", err=True)
389
+ raise SystemExit(1)
390
+
391
+ # Validate mode
392
+ if mode:
393
+ valid_modes = {"create", "validate", "edit"}
394
+ if mode not in valid_modes:
395
+ click.echo(f"Error: Invalid mode '{mode}'. Must be one of: {', '.join(sorted(valid_modes))}", err=True)
396
+ raise SystemExit(1)
397
+
398
+ # Resolve mode
399
+ effective_mode = mode
400
+ if not effective_mode:
401
+ default_mode = wf.get("modes", {}).get("default")
402
+ if default_mode:
403
+ effective_mode = default_mode
404
+ else:
405
+ effective_mode = "create"
406
+
407
+ # If explicit mode, validate it exists for this workflow
408
+ if mode:
409
+ modes = wf.get("modes", {})
410
+ mode_path = modes.get(mode)
411
+ if not mode_path or mode_path == "null":
412
+ available = [k for k in modes if k not in ("default", "available")]
413
+ click.echo(f"Error: Mode '{mode}' not available for workflow '{name}'", err=True)
414
+ if available:
415
+ click.echo(f"Available modes: {', '.join(available)}", err=True)
416
+ raise SystemExit(1)
417
+
418
+ # Resolve steps path
419
+ steps_path = resolve_steps_path(data, wf_file.parent, effective_mode, project_root)
420
+ step_count = count_steps(steps_path)
421
+
422
+ if step_count == 0:
423
+ click.echo(f"Error: No step files found in {steps_path}", err=True)
424
+ raise SystemExit(1)
425
+
426
+ # Create session directory
427
+ session_dir.mkdir(parents=True, exist_ok=True)
428
+
429
+ # Check for existing session
430
+ session_file = session_dir / f"{name}-workflow-session.md"
431
+ if session_file.exists():
432
+ click.echo("**Warning:** Existing session found")
433
+ click.echo("")
434
+ click.echo(f"Session: {session_file}")
435
+ click.echo("")
436
+ click.echo("Options:")
437
+ click.echo(f"1. Use `pf workflow resume {name}` to continue")
438
+ click.echo("2. Delete the session file to start fresh")
439
+ click.echo("")
440
+ click.echo("To start fresh, run:")
441
+ click.echo("```bash")
442
+ click.echo(f'rm "{session_file}"')
443
+ click.echo("```")
444
+ return
445
+
446
+ # Create session file
447
+ now = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
448
+ wf_agent = wf.get("agent", "pm")
449
+ wf_desc = wf.get("description", "-")
450
+
451
+ session_content = f"""# Workflow Session: {name}
452
+
453
+ **Workflow:** {name}
454
+ **Type:** stepped
455
+ **Agent:** {wf_agent}
456
+ **Started:** {now}
457
+
458
+ ## Workflow State
459
+ - **Workflow Name:** {name}
460
+ - **Type:** stepped
461
+ - **Mode:** {effective_mode}
462
+ - **Started:** {now}
463
+ - **Last Updated:** {now}
464
+ - **Current Step:** 1
465
+ - **Steps Completed:** []
466
+ - **Status:** in_progress
467
+ - **Notes:** Session created via pf workflow start
468
+
469
+ ## Progress
470
+ - Total Steps: {step_count}
471
+ - Completion: 0%
472
+
473
+ ---
474
+
475
+ """
476
+ session_file.write_text(session_content)
477
+
478
+ # Find step 1
479
+ step_file = find_step_file(steps_path, 1)
480
+ if not step_file:
481
+ click.echo(f"Error: Could not find step 1 file in {steps_path}", err=True)
482
+ raise SystemExit(1)
483
+
484
+ # Output
485
+ click.echo(f"# Starting Workflow: {name}")
486
+ click.echo("")
487
+ click.echo(f"**Description:** {wf_desc}")
488
+ click.echo(f"**Mode:** {effective_mode}")
489
+ click.echo(f"**Steps:** {step_count}")
490
+ click.echo(f"**Agent:** {wf_agent}")
491
+ click.echo(f"**Session:** {session_file}")
492
+ click.echo("")
493
+ click.echo("---")
494
+ click.echo("")
495
+ click.echo(f"## Step 1 of {step_count}")
496
+ click.echo("")
497
+
498
+ step_content = step_file.read_text()
499
+ click.echo(strip_frontmatter(step_content))
500
+
501
+ click.echo("")
502
+ click.echo("---")
503
+ click.echo("")
504
+ click.echo("**Controls:**")
505
+ click.echo("- `C` - Continue to next step")
506
+ click.echo("- `pf workflow status` - Check progress")
507
+ click.echo("- `pf workflow resume` - Resume after break")
508
+
509
+
510
+ @workflow.command("resume")
511
+ @click.argument("name", required=False, default=None)
512
+ def workflow_resume_cmd(name: str | None):
513
+ """Resume a stepped workflow from the current step.
514
+
515
+ \b
516
+ Arguments:
517
+ NAME - Workflow name (auto-detects from active session if omitted)
518
+ """
519
+ from datetime import datetime, timezone
520
+
521
+ from pennyfarthing_scripts.common.config import get_project_root
522
+ from pennyfarthing_scripts.workflow.helpers import (
523
+ count_steps,
524
+ find_step_file,
525
+ find_workflow_file,
526
+ find_workflow_session,
527
+ get_session_dir,
528
+ get_workflows_dir,
529
+ load_workflow_data,
530
+ parse_session_field,
531
+ parse_steps_completed,
532
+ resolve_steps_path,
533
+ strip_frontmatter,
534
+ )
535
+
536
+ project_root = get_project_root()
537
+ workflows_dir = get_workflows_dir(project_root)
538
+ session_dir = get_session_dir(project_root)
539
+
540
+ if not session_dir.is_dir():
541
+ click.echo("# Resume Stepped Workflow")
542
+ click.echo("")
543
+ click.echo("No active workflow session found.")
544
+ click.echo("")
545
+ click.echo("Use `pf workflow start <name>` to begin a new workflow.")
546
+ raise SystemExit(1)
547
+
548
+ result = find_workflow_session(session_dir, name)
549
+ if not result:
550
+ if name:
551
+ click.echo(f"Error: No session found for workflow '{name}'", err=True)
552
+ click.echo(f"\nUse `pf workflow start {name}` to begin.", err=True)
553
+ else:
554
+ click.echo("# Resume Stepped Workflow")
555
+ click.echo("")
556
+ click.echo("No active workflow session found.")
557
+ click.echo("")
558
+ click.echo("Use `pf workflow start <name>` to begin a new workflow.")
559
+ raise SystemExit(1)
560
+
561
+ session_file, workflow_name = result
562
+ content = session_file.read_text()
563
+
564
+ # Parse session state
565
+ current_step_str = parse_session_field(content, "Current Step") or "1"
566
+ current_step = int(current_step_str)
567
+ mode_val = parse_session_field(content, "Mode") or "create"
568
+ status = parse_session_field(content, "Status") or "in_progress"
569
+ steps_completed_str = parse_session_field(content, "Steps Completed") or "[]"
570
+
571
+ # Check if complete
572
+ if status == "completed":
573
+ click.echo(f"# Workflow Complete: {workflow_name}")
574
+ click.echo("")
575
+ click.echo("This workflow has already been completed.")
576
+ click.echo("")
577
+ click.echo("To start a new session, delete the session file:")
578
+ click.echo("```bash")
579
+ click.echo(f'rm "{session_file}"')
580
+ click.echo("```")
581
+ click.echo("")
582
+ click.echo(f"Then run `pf workflow start {workflow_name}`")
583
+ return
584
+
585
+ # Find workflow definition
586
+ wf_file = find_workflow_file(workflows_dir, workflow_name)
587
+ if not wf_file:
588
+ click.echo(f"Error: Workflow definition '{workflow_name}' not found", err=True)
589
+ raise SystemExit(1)
590
+
591
+ data = load_workflow_data(wf_file)
592
+
593
+ # Resolve steps path
594
+ steps_path = resolve_steps_path(data, wf_file.parent, mode_val, project_root)
595
+ step_count = count_steps(steps_path)
596
+
597
+ # Find current step file
598
+ step_file = find_step_file(steps_path, current_step)
599
+ if not step_file:
600
+ click.echo(f"Error: Could not find step {current_step} file in {steps_path}", err=True)
601
+ raise SystemExit(1)
602
+
603
+ # Update last updated timestamp
604
+ now = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
605
+ import re
606
+
607
+ updated_content = re.sub(
608
+ r"^- \*\*Last Updated:\*\*.*$",
609
+ f"- **Last Updated:** {now}",
610
+ content, flags=re.MULTILINE
611
+ )
612
+ session_file.write_text(updated_content)
613
+
614
+ # Calculate completion
615
+ steps_completed = parse_steps_completed(steps_completed_str)
616
+ completed_count = len(steps_completed)
617
+ completion_pct = (completed_count * 100 // step_count) if step_count > 0 else 0
618
+
619
+ # Output
620
+ click.echo(f"# Resuming Workflow: {workflow_name}")
621
+ click.echo("")
622
+ click.echo(f"**Mode:** {mode_val}")
623
+ click.echo(f"**Progress:** Step {current_step} of {step_count} ({completion_pct}% complete)")
624
+ click.echo(f"**Steps Completed:** {steps_completed_str}")
625
+ click.echo(f"**Session:** {session_file}")
626
+ click.echo("")
627
+ click.echo("---")
628
+ click.echo("")
629
+ click.echo(f"## Step {current_step} of {step_count}")
630
+ click.echo("")
631
+
632
+ step_content = step_file.read_text()
633
+ click.echo(strip_frontmatter(step_content))
634
+
635
+ click.echo("")
636
+ click.echo("---")
637
+ click.echo("")
638
+ click.echo("**Controls:**")
639
+ click.echo("- `C` - Continue to next step")
640
+ click.echo("- `pf workflow status` - Check progress")
641
+
642
+
643
+ @workflow.command("status")
644
+ @click.argument("name", required=False, default=None)
645
+ def workflow_status_cmd(name: str | None):
646
+ """Show current stepped workflow progress.
647
+
648
+ \b
649
+ Arguments:
650
+ NAME - Workflow name (auto-detects from active session if omitted)
651
+ """
652
+ from pennyfarthing_scripts.common.config import get_project_root
653
+ from pennyfarthing_scripts.workflow.helpers import (
654
+ count_steps,
655
+ find_workflow_file,
656
+ find_workflow_session,
657
+ get_session_dir,
658
+ get_workflows_dir,
659
+ load_workflow_data,
660
+ parse_session_field,
661
+ parse_steps_completed,
662
+ resolve_steps_path,
663
+ )
664
+
665
+ project_root = get_project_root()
666
+ workflows_dir = get_workflows_dir(project_root)
667
+ session_dir = get_session_dir(project_root)
668
+
669
+ if not session_dir.is_dir():
670
+ click.echo("# Workflow Status")
671
+ click.echo("")
672
+ click.echo("No active workflow session found.")
673
+ click.echo("")
674
+ click.echo("Use `pf workflow start <name>` to begin a new workflow.")
675
+ return
676
+
677
+ result = find_workflow_session(session_dir, name)
678
+ if not result:
679
+ if name:
680
+ click.echo(f"# Workflow Status: {name}")
681
+ click.echo("")
682
+ click.echo(f"No session found for workflow '{name}'")
683
+ click.echo("")
684
+ click.echo(f"Use `pf workflow start {name}` to begin.")
685
+ else:
686
+ click.echo("# Workflow Status")
687
+ click.echo("")
688
+ click.echo("No active workflow session found.")
689
+ click.echo("")
690
+ click.echo("Use `pf workflow start <name>` to begin a new workflow.")
691
+ return
692
+
693
+ session_file, workflow_name = result
694
+ content = session_file.read_text()
695
+
696
+ # Parse session state
697
+ current_step_str = parse_session_field(content, "Current Step") or "1"
698
+ current_step = int(current_step_str)
699
+ mode_val = parse_session_field(content, "Mode") or "create"
700
+ status = parse_session_field(content, "Status") or "in_progress"
701
+ started = parse_session_field(content, "Started") or "-"
702
+ last_updated = parse_session_field(content, "Last Updated") or "-"
703
+ steps_completed_str = parse_session_field(content, "Steps Completed") or "[]"
704
+ notes = parse_session_field(content, "Notes") or "-"
705
+
706
+ # Get step count from workflow file
707
+ step_count_str = "?"
708
+ wf_desc = "-"
709
+ wf_file = find_workflow_file(workflows_dir, workflow_name)
710
+ if wf_file:
711
+ data = load_workflow_data(wf_file)
712
+ wf_desc = data.get("workflow", {}).get("description", "-")
713
+ if isinstance(wf_desc, str):
714
+ wf_desc = wf_desc.split("\n")[0]
715
+ try:
716
+ steps_path = resolve_steps_path(data, wf_file.parent, mode_val, project_root)
717
+ step_count = count_steps(steps_path)
718
+ step_count_str = str(step_count)
719
+ except Exception:
720
+ step_count = 0
721
+ else:
722
+ step_count = 0
723
+
724
+ # Calculate completion
725
+ steps_completed = parse_steps_completed(steps_completed_str)
726
+ completed_count = len(steps_completed)
727
+ if step_count > 0:
728
+ completion_pct = completed_count * 100 // step_count
729
+ else:
730
+ completion_pct = 0
731
+
732
+ # Progress bar
733
+ bar_width = 20
734
+ if step_count > 0:
735
+ filled = completion_pct * bar_width // 100
736
+ empty = bar_width - filled
737
+ progress_bar = "#" * filled + "-" * empty
738
+ else:
739
+ progress_bar = "?" * bar_width
740
+
741
+ # Status indicator
742
+ status_icons = {
743
+ "completed": "[COMPLETE]",
744
+ "paused": "[PAUSED]",
745
+ }
746
+ status_icon = status_icons.get(status, "[IN PROGRESS]")
747
+
748
+ # Output
749
+ click.echo(f"# Workflow Status: {workflow_name}")
750
+ click.echo("")
751
+ click.echo(f"**Description:** {wf_desc}")
752
+ click.echo("")
753
+ click.echo("## Progress")
754
+ click.echo("")
755
+ click.echo("```")
756
+ click.echo(f"[{progress_bar}] {completion_pct}%")
757
+ click.echo("```")
758
+ click.echo("")
759
+ click.echo("| Field | Value |")
760
+ click.echo("|-------|-------|")
761
+ click.echo(f"| Status | {status_icon} |")
762
+ click.echo(f"| Mode | {mode_val} |")
763
+ click.echo(f"| Current Step | {current_step} of {step_count_str} |")
764
+ click.echo(f"| Completed | {completed_count} steps |")
765
+ click.echo(f"| Steps Done | {steps_completed_str} |")
766
+ click.echo(f"| Started | {started} |")
767
+ click.echo(f"| Last Updated | {last_updated} |")
768
+ if notes != "-":
769
+ click.echo(f"| Notes | {notes} |")
770
+ click.echo("")
771
+ click.echo(f"**Session:** {session_file}")
772
+ click.echo("")
773
+
774
+ if status != "completed":
775
+ click.echo(f"**Next:** Use `pf workflow resume` to continue from step {current_step}")
776
+
777
+
778
+ @workflow.command("fix-phase")
779
+ @click.argument("story_id")
780
+ @click.argument("target_phase")
781
+ @click.option("--dry-run", is_flag=True, help="Preview without making changes")
782
+ def workflow_fix_phase_cmd(story_id: str, target_phase: str, dry_run: bool):
783
+ """Repair session phase tracking when handoffs didn't update properly.
784
+
785
+ \b
786
+ Arguments:
787
+ STORY_ID - Story ID (e.g., 56-1 or MSSCI-12190)
788
+ TARGET_PHASE - Target phase to set (e.g., review, approved, finish)
789
+ """
790
+ import re
791
+ from datetime import datetime, timezone
792
+
793
+ from pennyfarthing_scripts.common.config import get_project_root
794
+ from pennyfarthing_scripts.workflow.helpers import (
795
+ find_story_session,
796
+ get_session_dir,
797
+ )
798
+
799
+ project_root = get_project_root()
800
+ session_dir = get_session_dir(project_root)
801
+
802
+ if not session_dir.is_dir():
803
+ click.echo(f"Error: Session directory not found at {session_dir}", err=True)
804
+ raise SystemExit(1)
805
+
806
+ session_file = find_story_session(session_dir, story_id)
807
+ if not session_file:
808
+ click.echo(f"Error: Session file not found for story {story_id}", err=True)
809
+ click.echo(f"Searched in: {session_dir}", err=True)
810
+ raise SystemExit(1)
811
+
812
+ click.echo(f"Session file: {session_file}")
813
+
814
+ content = session_file.read_text()
815
+
816
+ # Extract current state
817
+ current_phase_match = re.search(r"\*\*Phase:\*\*\s*(\S+)", content)
818
+ current_phase = current_phase_match.group(1) if current_phase_match else "unknown"
819
+
820
+ workflow_match = re.search(r"\*\*Workflow:\*\*\s*(\S+)", content)
821
+ workflow_name = workflow_match.group(1) if workflow_match else "tdd"
822
+
823
+ click.echo(f"Current phase: {current_phase}")
824
+ click.echo(f"Target phase: {target_phase}")
825
+ click.echo(f"Workflow: {workflow_name}")
826
+
827
+ # Define valid phase sequences
828
+ phase_defs: dict[str, tuple[list[str], list[str], list[str]]] = {
829
+ "tdd": (
830
+ ["setup", "red", "green", "review", "approved", "finish"],
831
+ ["sm", "tea", "dev", "reviewer", "sm", "sm"],
832
+ ["manual", "tests_fail", "tests_pass", "approval", "complete", ""],
833
+ ),
834
+ "trivial": (
835
+ ["setup", "implement", "review", "approved", "finish"],
836
+ ["sm", "dev", "reviewer", "sm", "sm"],
837
+ ["manual", "tests_pass", "approval", "complete", ""],
838
+ ),
839
+ }
840
+
841
+ phases, agents, gates = phase_defs.get(
842
+ workflow_name,
843
+ phase_defs["tdd"], # Default to TDD
844
+ )
845
+
846
+ if workflow_name not in phase_defs:
847
+ click.echo(f"Warning: Unknown workflow '{workflow_name}', assuming TDD")
848
+
849
+ # Find indices
850
+ try:
851
+ current_idx = phases.index(current_phase)
852
+ except ValueError:
853
+ click.echo(f"Error: Current phase '{current_phase}' not found in {workflow_name} workflow", err=True)
854
+ click.echo(f"Valid phases: {', '.join(phases)}", err=True)
855
+ raise SystemExit(1)
856
+
857
+ try:
858
+ target_idx = phases.index(target_phase)
859
+ except ValueError:
860
+ click.echo(f"Error: Target phase '{target_phase}' not found in {workflow_name} workflow", err=True)
861
+ click.echo(f"Valid phases: {', '.join(phases)}", err=True)
862
+ raise SystemExit(1)
863
+
864
+ if target_idx <= current_idx:
865
+ click.echo(f"Error: Target phase '{target_phase}' is not ahead of current phase '{current_phase}'", err=True)
866
+ click.echo(f"Phase sequence: {', '.join(phases)}", err=True)
867
+ raise SystemExit(1)
868
+
869
+ # Calculate transitions
870
+ now = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
871
+ click.echo("")
872
+ click.echo("Transitions needed:")
873
+
874
+ transitions: list[tuple[str, str, str, str, str]] = []
875
+ for i in range(current_idx, target_idx):
876
+ from_phase = phases[i]
877
+ to_phase = phases[i + 1]
878
+ from_agent = agents[i]
879
+ to_agent = agents[i + 1]
880
+ gate = gates[i + 1]
881
+ click.echo(f" {from_phase} ({from_agent}) -> {to_phase} ({to_agent}) [gate: {gate}]")
882
+ transitions.append((from_phase, to_phase, from_agent, to_agent, gate))
883
+
884
+ if dry_run:
885
+ click.echo("")
886
+ click.echo("[DRY RUN] Would update session file with:")
887
+ click.echo(f" - **Phase:** {target_phase}")
888
+ click.echo(f" - **Phase Started:** {now}")
889
+ click.echo(f" - Phase History: close out {current_phase}, add intermediate phases")
890
+ click.echo(f" - Handoff History: add {len(transitions)} handoff(s)")
891
+ return
892
+
893
+ click.echo("")
894
+ click.echo("Updating session file...")
895
+
896
+ # Update Phase line
897
+ content = re.sub(
898
+ r"\*\*Phase:\*\*\s*\S+",
899
+ f"**Phase:** {target_phase}",
900
+ content,
901
+ )
902
+
903
+ # Update Phase Started line
904
+ content = re.sub(
905
+ r"\*\*Phase Started:\*\*\s*\S+",
906
+ f"**Phase Started:** {now}",
907
+ content,
908
+ )
909
+
910
+ # Build handoff history additions
911
+ handoff_lines = []
912
+ for from_phase, to_phase, from_agent, to_agent, gate in transitions:
913
+ handoff_lines.append(f"| {from_agent} | {to_agent} | {gate} | PASSED | {now} |")
914
+
915
+ # Insert handoff rows after the last PASSED/FAILED row
916
+ if handoff_lines:
917
+ lines = content.split("\n")
918
+ insert_idx = None
919
+ for i, line in enumerate(lines):
920
+ if ("PASSED" in line or "FAILED" in line) and line.strip().startswith("|"):
921
+ insert_idx = i
922
+
923
+ if insert_idx is not None:
924
+ for j, hl in enumerate(handoff_lines):
925
+ lines.insert(insert_idx + 1 + j, hl)
926
+ content = "\n".join(lines)
927
+
928
+ session_file.write_text(content)
929
+
930
+ click.echo("")
931
+ click.echo("Session file updated")
932
+ click.echo(f" Phase: {current_phase} -> {target_phase}")
933
+ click.echo(f" Handoffs added: {len(transitions)}")
934
+ click.echo("")
935
+ click.echo("Note: Phase History end times set to now. Review and adjust if needed.")
936
+
937
+
938
+ @workflow.command("complete-step")
939
+ @click.argument("name", required=False, default=None)
940
+ @click.option("--step", "step_override", type=int, default=None,
941
+ help="Complete a specific step number instead of current step")
942
+ def workflow_complete_step_cmd(name: str | None, step_override: int | None):
943
+ """Complete the current step of a stepped workflow.
944
+
945
+ Advances session state: increments current step, updates steps completed,
946
+ recalculates completion percentage. Marks workflow as completed when
947
+ all steps are done.
948
+
949
+ \b
950
+ Arguments:
951
+ NAME - Workflow name (auto-detects from session if omitted)
952
+ """
953
+ import re
954
+ from datetime import datetime, timezone
955
+
956
+ from pennyfarthing_scripts.common.config import get_project_root
957
+ from pennyfarthing_scripts.workflow.helpers import (
958
+ count_steps,
959
+ find_step_file,
960
+ find_workflow_file,
961
+ find_workflow_session,
962
+ format_steps_completed,
963
+ get_session_dir,
964
+ get_workflows_dir,
965
+ load_workflow_data,
966
+ parse_session_field,
967
+ parse_steps_completed,
968
+ resolve_steps_path,
969
+ strip_frontmatter,
970
+ )
971
+
972
+ project_root = get_project_root()
973
+ workflows_dir = get_workflows_dir(project_root)
974
+ session_dir = get_session_dir(project_root)
975
+
976
+ if not session_dir.is_dir():
977
+ click.echo("Error: No active workflow session found.", err=True)
978
+ raise SystemExit(1)
979
+
980
+ result = find_workflow_session(session_dir, name)
981
+ if not result:
982
+ if name:
983
+ click.echo(f"Error: No session found for workflow '{name}'", err=True)
984
+ click.echo(f"\nUse `pf workflow start {name}` to begin.", err=True)
985
+ else:
986
+ click.echo("Error: No active workflow session found.", err=True)
987
+ raise SystemExit(1)
988
+
989
+ session_file, workflow_name = result
990
+ content = session_file.read_text()
991
+
992
+ # Parse session state
993
+ current_step_str = parse_session_field(content, "Current Step") or "1"
994
+ current_step = int(current_step_str)
995
+ mode_val = parse_session_field(content, "Mode") or "create"
996
+ status = parse_session_field(content, "Status") or "in_progress"
997
+ steps_completed_str = parse_session_field(content, "Steps Completed") or "[]"
998
+
999
+ # Check if already completed
1000
+ if status == "completed":
1001
+ click.echo(f"# Workflow Already Completed: {workflow_name}")
1002
+ click.echo("")
1003
+ click.echo("This workflow has already been completed.")
1004
+ click.echo("")
1005
+ click.echo("To start a new session, delete the session file:")
1006
+ click.echo("```bash")
1007
+ click.echo(f'rm "{session_file}"')
1008
+ click.echo("```")
1009
+ click.echo("")
1010
+ click.echo(f"Then run `pf workflow start {workflow_name}`")
1011
+ return
1012
+
1013
+ # Determine which step to complete
1014
+ completing_step = step_override if step_override is not None else current_step
1015
+
1016
+ # Find workflow file and resolve steps path
1017
+ wf_file = find_workflow_file(workflows_dir, workflow_name)
1018
+ if not wf_file:
1019
+ click.echo(f"Error: Workflow definition '{workflow_name}' not found", err=True)
1020
+ raise SystemExit(1)
1021
+
1022
+ data = load_workflow_data(wf_file)
1023
+ steps_path = resolve_steps_path(data, wf_file.parent, mode_val, project_root)
1024
+ step_count = count_steps(steps_path)
1025
+
1026
+ # Update steps completed
1027
+ steps_completed = parse_steps_completed(steps_completed_str)
1028
+ if completing_step not in steps_completed:
1029
+ steps_completed.append(completing_step)
1030
+ new_steps_completed = format_steps_completed(steps_completed)
1031
+
1032
+ # Calculate
1033
+ next_step = completing_step + 1
1034
+ completed_count = len(steps_completed)
1035
+ completion_pct = (completed_count * 100 // step_count) if step_count > 0 else 0
1036
+ new_status = "completed" if completed_count >= step_count else "in_progress"
1037
+
1038
+ # Update session file
1039
+ now = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
1040
+
1041
+ content = re.sub(
1042
+ r"^- \*\*Current Step:\*\*.*$",
1043
+ f"- **Current Step:** {next_step}",
1044
+ content, flags=re.MULTILINE
1045
+ )
1046
+ content = re.sub(
1047
+ r"^- \*\*Steps Completed:\*\*.*$",
1048
+ f"- **Steps Completed:** {new_steps_completed}",
1049
+ content, flags=re.MULTILINE
1050
+ )
1051
+ content = re.sub(
1052
+ r"^- \*\*Last Updated:\*\*.*$",
1053
+ f"- **Last Updated:** {now}",
1054
+ content, flags=re.MULTILINE
1055
+ )
1056
+ content = re.sub(
1057
+ r"^- \*\*Status:\*\*.*$",
1058
+ f"- **Status:** {new_status}",
1059
+ content, flags=re.MULTILINE
1060
+ )
1061
+ content = re.sub(
1062
+ r"^- Completion:.*$",
1063
+ f"- Completion: {completion_pct}%",
1064
+ content, flags=re.MULTILINE
1065
+ )
1066
+
1067
+ session_file.write_text(content)
1068
+
1069
+ # Output
1070
+ if new_status == "completed":
1071
+ click.echo(f"# Workflow Complete: {workflow_name}")
1072
+ click.echo("")
1073
+ click.echo(f"All {step_count} steps completed!")
1074
+ click.echo("")
1075
+ click.echo(f"**Final Progress:** {completion_pct}%")
1076
+ click.echo(f"**Steps Completed:** {new_steps_completed}")
1077
+ click.echo("")
1078
+ click.echo(f"Session updated: {session_file}")
1079
+ else:
1080
+ click.echo(f"# Step {completing_step} Complete")
1081
+ click.echo("")
1082
+ click.echo(f"**Progress:** Step {next_step} of {step_count} ({completion_pct}% complete)")
1083
+ click.echo(f"**Steps Completed:** {new_steps_completed}")
1084
+ click.echo("")
1085
+ click.echo("---")
1086
+ click.echo("")
1087
+ click.echo(f"## Step {next_step} of {step_count}")
1088
+ click.echo("")
1089
+
1090
+ next_step_file = find_step_file(steps_path, next_step)
1091
+ if next_step_file:
1092
+ step_content = next_step_file.read_text()
1093
+ click.echo(strip_frontmatter(step_content))
1094
+
1095
+ click.echo("")
1096
+ click.echo("---")
1097
+ click.echo("")
1098
+ click.echo("**Controls:**")
1099
+ click.echo("- `C` - Continue to next step")
1100
+ click.echo("- `pf workflow status` - Check progress")