@pennyfarthing/core 11.0.0 → 11.1.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 (401) hide show
  1. package/README.md +81 -23
  2. package/package.json +1 -1
  3. package/packages/core/dist/cli/utils/010-detect-remove-old-packages.test.d.ts +20 -0
  4. package/packages/core/dist/cli/utils/010-detect-remove-old-packages.test.d.ts.map +1 -0
  5. package/packages/core/dist/cli/utils/010-detect-remove-old-packages.test.js +278 -0
  6. package/packages/core/dist/cli/utils/010-detect-remove-old-packages.test.js.map +1 -0
  7. package/packages/core/dist/cli/utils/constants.d.ts +8 -2
  8. package/packages/core/dist/cli/utils/constants.d.ts.map +1 -1
  9. package/packages/core/dist/cli/utils/constants.js +4 -1
  10. package/packages/core/dist/cli/utils/constants.js.map +1 -1
  11. package/packages/core/dist/cli/utils/constants.test.d.ts +10 -0
  12. package/packages/core/dist/cli/utils/constants.test.d.ts.map +1 -0
  13. package/packages/core/dist/cli/utils/constants.test.js +38 -0
  14. package/packages/core/dist/cli/utils/constants.test.js.map +1 -0
  15. package/packages/core/dist/consultation/consultation-protocol.d.ts +139 -0
  16. package/packages/core/dist/consultation/consultation-protocol.d.ts.map +1 -0
  17. package/packages/core/dist/consultation/consultation-protocol.js +178 -0
  18. package/packages/core/dist/consultation/consultation-protocol.js.map +1 -0
  19. package/packages/core/dist/consultation/consultation-protocol.test.d.ts +20 -0
  20. package/packages/core/dist/consultation/consultation-protocol.test.d.ts.map +1 -0
  21. package/packages/core/dist/consultation/consultation-protocol.test.js +474 -0
  22. package/packages/core/dist/consultation/consultation-protocol.test.js.map +1 -0
  23. package/packages/core/dist/consultation/dialogue-manager.d.ts +75 -0
  24. package/packages/core/dist/consultation/dialogue-manager.d.ts.map +1 -0
  25. package/packages/core/dist/consultation/dialogue-manager.js +334 -0
  26. package/packages/core/dist/consultation/dialogue-manager.js.map +1 -0
  27. package/packages/core/dist/consultation/dialogue-manager.test.d.ts +19 -0
  28. package/packages/core/dist/consultation/dialogue-manager.test.d.ts.map +1 -0
  29. package/packages/core/dist/consultation/dialogue-manager.test.js +444 -0
  30. package/packages/core/dist/consultation/dialogue-manager.test.js.map +1 -0
  31. package/packages/core/dist/public/js/react/react.js +3 -3
  32. package/packages/core/dist/scripts/theme-detail.test.d.ts +10 -0
  33. package/packages/core/dist/scripts/theme-detail.test.js +199 -0
  34. package/packages/core/dist/server/api/git.d.ts +13 -1
  35. package/packages/core/dist/server/api/git.d.ts.map +1 -1
  36. package/packages/core/dist/server/api/git.js +53 -34
  37. package/packages/core/dist/server/api/git.js.map +1 -1
  38. package/packages/core/dist/server/api/health-score.d.ts.map +1 -1
  39. package/packages/core/dist/server/api/health-score.js +25 -1
  40. package/packages/core/dist/server/api/health-score.js.map +1 -1
  41. package/packages/core/dist/server/api/settings.d.ts.map +1 -1
  42. package/packages/core/dist/server/api/settings.js +63 -1
  43. package/packages/core/dist/server/api/settings.js.map +1 -1
  44. package/packages/core/dist/server/api/theme-agents.d.ts.map +1 -1
  45. package/packages/core/dist/server/api/theme-agents.js +61 -0
  46. package/packages/core/dist/server/api/theme-agents.js.map +1 -1
  47. package/packages/core/dist/server/server.d.ts.map +1 -1
  48. package/packages/core/dist/server/server.js +17 -12
  49. package/packages/core/dist/server/server.js.map +1 -1
  50. package/packages/core/dist/shared/skill-search.test.js +2 -2
  51. package/packages/core/dist/workflow/gate-file-validation.d.ts +49 -0
  52. package/packages/core/dist/workflow/gate-file-validation.d.ts.map +1 -0
  53. package/packages/core/dist/workflow/gate-file-validation.js +157 -0
  54. package/packages/core/dist/workflow/gate-file-validation.js.map +1 -0
  55. package/packages/core/dist/workflow/gate-file-validation.test.d.ts +19 -0
  56. package/packages/core/dist/workflow/gate-file-validation.test.d.ts.map +1 -0
  57. package/packages/core/dist/workflow/gate-file-validation.test.js +536 -0
  58. package/packages/core/dist/workflow/gate-file-validation.test.js.map +1 -0
  59. package/packages/core/dist/workflow/gate-schema-validation.test.d.ts +14 -0
  60. package/packages/core/dist/workflow/gate-schema-validation.test.d.ts.map +1 -0
  61. package/packages/core/dist/workflow/gate-schema-validation.test.js +339 -0
  62. package/packages/core/dist/workflow/gate-schema-validation.test.js.map +1 -0
  63. package/packages/core/dist/workflow/handoff.js +2 -2
  64. package/packages/core/dist/workflow/handoff.js.map +1 -1
  65. package/packages/core/dist/workflow/handoff.test.js +16 -0
  66. package/packages/core/dist/workflow/handoff.test.js.map +1 -1
  67. package/packages/core/dist/workflow/workflow-schema.d.ts +4 -2
  68. package/packages/core/dist/workflow/workflow-schema.d.ts.map +1 -1
  69. package/packages/core/dist/workflow/workflow-schema.js +43 -8
  70. package/packages/core/dist/workflow/workflow-schema.js.map +1 -1
  71. package/pennyfarthing-dist/agents/README.md +6 -14
  72. package/pennyfarthing-dist/agents/architect.md +43 -29
  73. package/pennyfarthing-dist/agents/ba.md +30 -29
  74. package/pennyfarthing-dist/agents/dev.md +32 -43
  75. package/pennyfarthing-dist/agents/devops.md +57 -21
  76. package/pennyfarthing-dist/agents/orchestrator.md +3 -10
  77. package/pennyfarthing-dist/agents/pm.md +45 -31
  78. package/pennyfarthing-dist/agents/reviewer.md +20 -66
  79. package/pennyfarthing-dist/agents/sm-setup.md +2 -2
  80. package/pennyfarthing-dist/agents/sm.md +8 -30
  81. package/pennyfarthing-dist/agents/tea.md +25 -41
  82. package/pennyfarthing-dist/agents/tech-writer.md +33 -90
  83. package/pennyfarthing-dist/agents/ux-designer.md +39 -39
  84. package/pennyfarthing-dist/commands/benchmark-control.md +8 -64
  85. package/pennyfarthing-dist/commands/benchmark.md +8 -480
  86. package/pennyfarthing-dist/commands/job-fair.md +8 -97
  87. package/pennyfarthing-dist/commands/pf-benchmark-control.md +70 -0
  88. package/pennyfarthing-dist/commands/pf-benchmark.md +486 -0
  89. package/pennyfarthing-dist/commands/pf-chore.md +4 -4
  90. package/pennyfarthing-dist/commands/pf-ci.md +40 -0
  91. package/pennyfarthing-dist/commands/pf-close-epic.md +9 -27
  92. package/pennyfarthing-dist/commands/pf-continue-session.md +9 -213
  93. package/pennyfarthing-dist/commands/pf-create-branches-from-story.md +11 -353
  94. package/pennyfarthing-dist/commands/pf-docs.md +28 -0
  95. package/pennyfarthing-dist/commands/pf-epic.md +67 -0
  96. package/pennyfarthing-dist/commands/pf-git-cleanup.md +11 -52
  97. package/pennyfarthing-dist/commands/pf-git.md +75 -0
  98. package/pennyfarthing-dist/commands/pf-help.md +110 -128
  99. package/pennyfarthing-dist/commands/pf-job-fair.md +102 -0
  100. package/pennyfarthing-dist/commands/pf-new-work.md +9 -18
  101. package/pennyfarthing-dist/commands/pf-parallel-work.md +6 -66
  102. package/pennyfarthing-dist/commands/pf-release.md +11 -76
  103. package/pennyfarthing-dist/commands/pf-repo-status.md +11 -44
  104. package/pennyfarthing-dist/commands/pf-run-ci.md +8 -111
  105. package/pennyfarthing-dist/commands/pf-session.md +51 -0
  106. package/pennyfarthing-dist/commands/pf-solo.md +447 -0
  107. package/pennyfarthing-dist/commands/pf-sprint-planning.md +8 -104
  108. package/pennyfarthing-dist/commands/pf-standalone.md +1 -1
  109. package/pennyfarthing-dist/commands/pf-start-epic.md +9 -163
  110. package/pennyfarthing-dist/commands/pf-sync-epic-to-jira.md +8 -179
  111. package/pennyfarthing-dist/commands/pf-sync-work-with-sprint.md +8 -368
  112. package/pennyfarthing-dist/commands/pf-update-domain-docs.md +8 -78
  113. package/pennyfarthing-dist/commands/solo.md +8 -442
  114. package/pennyfarthing-dist/guides/agent-behavior.md +13 -13
  115. package/pennyfarthing-dist/guides/agent-coordination.md +7 -7
  116. package/pennyfarthing-dist/guides/agent-tag-taxonomy.md +6 -5
  117. package/pennyfarthing-dist/guides/bikerack.md +128 -0
  118. package/pennyfarthing-dist/guides/brownfield-tools.md +133 -0
  119. package/pennyfarthing-dist/guides/command-tag-taxonomy.md +2 -2
  120. package/pennyfarthing-dist/guides/gate-schema.md +227 -0
  121. package/pennyfarthing-dist/guides/gates.md +120 -0
  122. package/pennyfarthing-dist/guides/handoff-cli.md +116 -0
  123. package/pennyfarthing-dist/guides/hooks.md +86 -4
  124. package/pennyfarthing-dist/guides/output-styles.md +65 -0
  125. package/pennyfarthing-dist/guides/patterns/approval-gates-pattern.md +5 -5
  126. package/pennyfarthing-dist/guides/patterns/tdd-flow-pattern.md +4 -4
  127. package/pennyfarthing-dist/guides/prompt-patterns.md +5 -5
  128. package/pennyfarthing-dist/guides/reflector.md +4 -4
  129. package/pennyfarthing-dist/guides/session-artifacts.md +1 -1
  130. package/pennyfarthing-dist/guides/skill-schema.md +1 -1
  131. package/pennyfarthing-dist/guides/tandem-protocol.md +13 -1
  132. package/pennyfarthing-dist/guides/worktree-mode.md +3 -3
  133. package/pennyfarthing-dist/guides/xml-tags.md +5 -4
  134. package/pennyfarthing-dist/personas/themes/hogans-heroes.yaml +11 -22
  135. package/pennyfarthing-dist/personas/themes/stephen-king.yaml +13 -24
  136. package/pennyfarthing-dist/scripts/core/dialogue-manager.sh +322 -0
  137. package/pennyfarthing-dist/scripts/core/phase-check-start.sh +1 -1
  138. package/pennyfarthing-dist/scripts/hooks/otel-auto-config.sh +19 -14
  139. package/pennyfarthing-dist/scripts/portraits/generate-portraits.py +191 -57
  140. package/pennyfarthing-dist/scripts/portraits/generate-portraits.sh +26 -10
  141. package/pennyfarthing-dist/skills/pf-changelog/SKILL.md +4 -4
  142. package/pennyfarthing-dist/skills/pf-sprint/skill.md +1 -1
  143. package/pennyfarthing-dist/skills/skill-registry.schema.json +4 -0
  144. package/pennyfarthing-dist/skills/skill-registry.yaml +5 -0
  145. package/pennyfarthing-dist/workflows/2party-tdd.yaml +11 -0
  146. package/pennyfarthing-dist/workflows/agent-docs.yaml +2 -0
  147. package/pennyfarthing-dist/workflows/bdd-tandem.yaml +4 -0
  148. package/pennyfarthing-dist/workflows/bdd.yaml +4 -0
  149. package/pennyfarthing-dist/workflows/git-cleanup/steps/step-05-complete.md +1 -1
  150. package/pennyfarthing-dist/workflows/tdd-tandem.yaml +3 -0
  151. package/pennyfarthing-dist/workflows/tdd.yaml +3 -0
  152. package/pennyfarthing-dist/workflows/trivial.yaml +2 -0
  153. package/pennyfarthing_scripts/__pycache__/__init__.cpython-314.pyc +0 -0
  154. package/pennyfarthing_scripts/__pycache__/bellmode_hook.cpython-314.pyc +0 -0
  155. package/pennyfarthing_scripts/__pycache__/cli.cpython-314.pyc +0 -0
  156. package/pennyfarthing_scripts/__pycache__/config.cpython-314.pyc +0 -0
  157. package/pennyfarthing_scripts/__pycache__/context.cpython-314.pyc +0 -0
  158. package/pennyfarthing_scripts/__pycache__/hooks.cpython-314.pyc +0 -0
  159. package/pennyfarthing_scripts/__pycache__/jira_bidirectional_sync.cpython-314.pyc +0 -0
  160. package/pennyfarthing_scripts/__pycache__/jira_epic_creation.cpython-314.pyc +0 -0
  161. package/pennyfarthing_scripts/__pycache__/jira_sync.cpython-314.pyc +0 -0
  162. package/pennyfarthing_scripts/__pycache__/jira_sync_story.cpython-314.pyc +0 -0
  163. package/pennyfarthing_scripts/__pycache__/output.cpython-314.pyc +0 -0
  164. package/pennyfarthing_scripts/__pycache__/patch_mode.cpython-314.pyc +0 -0
  165. package/pennyfarthing_scripts/__pycache__/pretooluse_hook.cpython-314.pyc +0 -0
  166. package/pennyfarthing_scripts/__pycache__/schema_validation_hook.cpython-314.pyc +0 -0
  167. package/pennyfarthing_scripts/__pycache__/session_start_hook.cpython-314.pyc +0 -0
  168. package/pennyfarthing_scripts/__pycache__/workflow.cpython-314.pyc +0 -0
  169. package/pennyfarthing_scripts/bc/__pycache__/__init__.cpython-314.pyc +0 -0
  170. package/pennyfarthing_scripts/bc/__pycache__/cli.cpython-314.pyc +0 -0
  171. package/pennyfarthing_scripts/bc/__pycache__/focus.cpython-314.pyc +0 -0
  172. package/pennyfarthing_scripts/bikerack/__pycache__/__init__.cpython-314.pyc +0 -0
  173. package/pennyfarthing_scripts/bikerack/__pycache__/__main__.cpython-314.pyc +0 -0
  174. package/pennyfarthing_scripts/bikerack/__pycache__/background_panel.cpython-314.pyc +0 -0
  175. package/pennyfarthing_scripts/bikerack/__pycache__/base_panel.cpython-314.pyc +0 -0
  176. package/pennyfarthing_scripts/bikerack/__pycache__/changed_panel.cpython-314.pyc +0 -0
  177. package/pennyfarthing_scripts/bikerack/__pycache__/cli.cpython-314.pyc +0 -0
  178. package/pennyfarthing_scripts/bikerack/__pycache__/debug_panel.cpython-314.pyc +0 -0
  179. package/pennyfarthing_scripts/bikerack/__pycache__/diffs_panel.cpython-314.pyc +0 -0
  180. package/pennyfarthing_scripts/bikerack/__pycache__/git_panel.cpython-314.pyc +0 -0
  181. package/pennyfarthing_scripts/bikerack/__pycache__/launcher.cpython-314.pyc +0 -0
  182. package/pennyfarthing_scripts/bikerack/__pycache__/sprint_panel.cpython-314.pyc +0 -0
  183. package/pennyfarthing_scripts/bikerack/__pycache__/tui.cpython-314.pyc +0 -0
  184. package/pennyfarthing_scripts/bikerack/__pycache__/ws_client.cpython-314.pyc +0 -0
  185. package/pennyfarthing_scripts/bikerack/cli.py +10 -11
  186. package/pennyfarthing_scripts/bikerack/debug_panel.py +218 -0
  187. package/pennyfarthing_scripts/bikerack/diffs_panel.py +203 -27
  188. package/pennyfarthing_scripts/brownfield/__pycache__/__init__.cpython-314.pyc +0 -0
  189. package/pennyfarthing_scripts/brownfield/__pycache__/__main__.cpython-314.pyc +0 -0
  190. package/pennyfarthing_scripts/brownfield/__pycache__/cli.cpython-314.pyc +0 -0
  191. package/pennyfarthing_scripts/brownfield/__pycache__/discover.cpython-314.pyc +0 -0
  192. package/pennyfarthing_scripts/cli.py +114 -0
  193. package/pennyfarthing_scripts/codemarkers/__pycache__/__init__.cpython-314.pyc +0 -0
  194. package/pennyfarthing_scripts/codemarkers/__pycache__/__main__.cpython-314.pyc +0 -0
  195. package/pennyfarthing_scripts/codemarkers/__pycache__/analyze.cpython-314.pyc +0 -0
  196. package/pennyfarthing_scripts/codemarkers/__pycache__/cli.cpython-314.pyc +0 -0
  197. package/pennyfarthing_scripts/codemarkers/__pycache__/formatters.cpython-314.pyc +0 -0
  198. package/pennyfarthing_scripts/codemarkers/__pycache__/models.cpython-314.pyc +0 -0
  199. package/pennyfarthing_scripts/common/__pycache__/__init__.cpython-314.pyc +0 -0
  200. package/pennyfarthing_scripts/common/__pycache__/config.cpython-314.pyc +0 -0
  201. package/pennyfarthing_scripts/common/__pycache__/output.cpython-314.pyc +0 -0
  202. package/pennyfarthing_scripts/common/__pycache__/themes.cpython-314.pyc +0 -0
  203. package/pennyfarthing_scripts/complexity/__pycache__/__init__.cpython-314.pyc +0 -0
  204. package/pennyfarthing_scripts/complexity/__pycache__/__main__.cpython-314.pyc +0 -0
  205. package/pennyfarthing_scripts/complexity/__pycache__/analyze.cpython-314.pyc +0 -0
  206. package/pennyfarthing_scripts/complexity/__pycache__/cli.cpython-314.pyc +0 -0
  207. package/pennyfarthing_scripts/complexity/__pycache__/formatters.cpython-314.pyc +0 -0
  208. package/pennyfarthing_scripts/complexity/__pycache__/models.cpython-314.pyc +0 -0
  209. package/pennyfarthing_scripts/deadcode/__pycache__/__init__.cpython-314.pyc +0 -0
  210. package/pennyfarthing_scripts/deadcode/__pycache__/__main__.cpython-314.pyc +0 -0
  211. package/pennyfarthing_scripts/deadcode/__pycache__/analyze.cpython-314.pyc +0 -0
  212. package/pennyfarthing_scripts/deadcode/__pycache__/cli.cpython-314.pyc +0 -0
  213. package/pennyfarthing_scripts/deadcode/__pycache__/formatters.cpython-314.pyc +0 -0
  214. package/pennyfarthing_scripts/deadcode/__pycache__/models.cpython-314.pyc +0 -0
  215. package/pennyfarthing_scripts/dependencies/__pycache__/__init__.cpython-314.pyc +0 -0
  216. package/pennyfarthing_scripts/dependencies/__pycache__/__main__.cpython-314.pyc +0 -0
  217. package/pennyfarthing_scripts/dependencies/__pycache__/analyze.cpython-314.pyc +0 -0
  218. package/pennyfarthing_scripts/dependencies/__pycache__/cli.cpython-314.pyc +0 -0
  219. package/pennyfarthing_scripts/dependencies/__pycache__/formatters.cpython-314.pyc +0 -0
  220. package/pennyfarthing_scripts/dependencies/__pycache__/models.cpython-314.pyc +0 -0
  221. package/pennyfarthing_scripts/epic/__init__.py +0 -0
  222. package/pennyfarthing_scripts/epic/cli.py +64 -0
  223. package/pennyfarthing_scripts/gate/__init__.py +1 -0
  224. package/pennyfarthing_scripts/gate/__pycache__/__init__.cpython-314.pyc +0 -0
  225. package/pennyfarthing_scripts/gate/__pycache__/cli.cpython-314.pyc +0 -0
  226. package/pennyfarthing_scripts/gate/__pycache__/validate.cpython-314.pyc +0 -0
  227. package/pennyfarthing_scripts/gate/cli.py +56 -0
  228. package/pennyfarthing_scripts/gate/validate.py +266 -0
  229. package/pennyfarthing_scripts/git/__pycache__/__init__.cpython-314.pyc +0 -0
  230. package/pennyfarthing_scripts/git/__pycache__/create_branches.cpython-314.pyc +0 -0
  231. package/pennyfarthing_scripts/git/__pycache__/status_all.cpython-314.pyc +0 -0
  232. package/pennyfarthing_scripts/git_group/__init__.py +0 -0
  233. package/pennyfarthing_scripts/git_group/cli.py +100 -0
  234. package/pennyfarthing_scripts/handoff/__init__.py +1 -0
  235. package/pennyfarthing_scripts/handoff/__pycache__/__init__.cpython-314.pyc +0 -0
  236. package/pennyfarthing_scripts/handoff/__pycache__/cli.cpython-314.pyc +0 -0
  237. package/pennyfarthing_scripts/handoff/__pycache__/complete_phase.cpython-314.pyc +0 -0
  238. package/pennyfarthing_scripts/handoff/__pycache__/gate_file.cpython-314.pyc +0 -0
  239. package/pennyfarthing_scripts/handoff/__pycache__/gate_runner.cpython-314.pyc +0 -0
  240. package/pennyfarthing_scripts/handoff/__pycache__/marker.cpython-314.pyc +0 -0
  241. package/pennyfarthing_scripts/handoff/__pycache__/resolve_gate.cpython-314.pyc +0 -0
  242. package/pennyfarthing_scripts/handoff/cli.py +120 -0
  243. package/pennyfarthing_scripts/handoff/complete_phase.py +155 -0
  244. package/pennyfarthing_scripts/handoff/gate_file.py +105 -0
  245. package/pennyfarthing_scripts/handoff/gate_runner.py +152 -0
  246. package/pennyfarthing_scripts/handoff/marker.py +109 -0
  247. package/pennyfarthing_scripts/handoff/resolve_gate.py +152 -0
  248. package/pennyfarthing_scripts/healthscore/__pycache__/__init__.cpython-314.pyc +0 -0
  249. package/pennyfarthing_scripts/healthscore/__pycache__/__main__.cpython-314.pyc +0 -0
  250. package/pennyfarthing_scripts/healthscore/__pycache__/analyze.cpython-314.pyc +0 -0
  251. package/pennyfarthing_scripts/healthscore/__pycache__/cli.cpython-314.pyc +0 -0
  252. package/pennyfarthing_scripts/healthscore/__pycache__/formatters.cpython-314.pyc +0 -0
  253. package/pennyfarthing_scripts/healthscore/__pycache__/models.cpython-314.pyc +0 -0
  254. package/pennyfarthing_scripts/hotspots/__pycache__/__init__.cpython-314.pyc +0 -0
  255. package/pennyfarthing_scripts/hotspots/__pycache__/__main__.cpython-314.pyc +0 -0
  256. package/pennyfarthing_scripts/hotspots/__pycache__/analyze.cpython-314.pyc +0 -0
  257. package/pennyfarthing_scripts/hotspots/__pycache__/cli.cpython-314.pyc +0 -0
  258. package/pennyfarthing_scripts/hotspots/__pycache__/formatters.cpython-314.pyc +0 -0
  259. package/pennyfarthing_scripts/hotspots/__pycache__/models.cpython-314.pyc +0 -0
  260. package/pennyfarthing_scripts/jira/__pycache__/__init__.cpython-314.pyc +0 -0
  261. package/pennyfarthing_scripts/jira/__pycache__/__main__.cpython-314.pyc +0 -0
  262. package/pennyfarthing_scripts/jira/__pycache__/bidirectional.cpython-314.pyc +0 -0
  263. package/pennyfarthing_scripts/jira/__pycache__/claim.cpython-314.pyc +0 -0
  264. package/pennyfarthing_scripts/jira/__pycache__/cli.cpython-314.pyc +0 -0
  265. package/pennyfarthing_scripts/jira/__pycache__/client.cpython-314.pyc +0 -0
  266. package/pennyfarthing_scripts/jira/__pycache__/create.cpython-314.pyc +0 -0
  267. package/pennyfarthing_scripts/jira/__pycache__/epic.cpython-314.pyc +0 -0
  268. package/pennyfarthing_scripts/jira/__pycache__/operations.cpython-314.pyc +0 -0
  269. package/pennyfarthing_scripts/jira/__pycache__/reconcile.cpython-314.pyc +0 -0
  270. package/pennyfarthing_scripts/jira/__pycache__/story.cpython-314.pyc +0 -0
  271. package/pennyfarthing_scripts/jira/__pycache__/sync.cpython-314.pyc +0 -0
  272. package/pennyfarthing_scripts/launch/__pycache__/__init__.cpython-314.pyc +0 -0
  273. package/pennyfarthing_scripts/launch/__pycache__/cli.cpython-314.pyc +0 -0
  274. package/pennyfarthing_scripts/migration/__pycache__/__init__.cpython-314.pyc +0 -0
  275. package/pennyfarthing_scripts/migration/__pycache__/session.cpython-314.pyc +0 -0
  276. package/pennyfarthing_scripts/migration/__pycache__/skill.cpython-314.pyc +0 -0
  277. package/pennyfarthing_scripts/migration/__pycache__/step.cpython-314.pyc +0 -0
  278. package/pennyfarthing_scripts/migration/__pycache__/validate.cpython-314.pyc +0 -0
  279. package/pennyfarthing_scripts/preflight/__pycache__/__init__.cpython-314.pyc +0 -0
  280. package/pennyfarthing_scripts/preflight/__pycache__/__main__.cpython-314.pyc +0 -0
  281. package/pennyfarthing_scripts/preflight/__pycache__/cli.cpython-314.pyc +0 -0
  282. package/pennyfarthing_scripts/preflight/__pycache__/finish.cpython-314.pyc +0 -0
  283. package/pennyfarthing_scripts/prime/__pycache__/__init__.cpython-314.pyc +0 -0
  284. package/pennyfarthing_scripts/prime/__pycache__/cli.cpython-314.pyc +0 -0
  285. package/pennyfarthing_scripts/prime/__pycache__/loader.cpython-314.pyc +0 -0
  286. package/pennyfarthing_scripts/prime/__pycache__/models.cpython-314.pyc +0 -0
  287. package/pennyfarthing_scripts/prime/__pycache__/persona.cpython-314.pyc +0 -0
  288. package/pennyfarthing_scripts/prime/__pycache__/session.cpython-314.pyc +0 -0
  289. package/pennyfarthing_scripts/prime/__pycache__/tiers.cpython-314.pyc +0 -0
  290. package/pennyfarthing_scripts/prime/__pycache__/version_sentinel.cpython-314.pyc +0 -0
  291. package/pennyfarthing_scripts/prime/__pycache__/workflow.cpython-314.pyc +0 -0
  292. package/pennyfarthing_scripts/prime/workflow.py +39 -0
  293. package/pennyfarthing_scripts/session/__init__.py +0 -0
  294. package/pennyfarthing_scripts/session/cli.py +87 -0
  295. package/pennyfarthing_scripts/sprint/__pycache__/__init__.cpython-314.pyc +0 -0
  296. package/pennyfarthing_scripts/sprint/__pycache__/__main__.cpython-314.pyc +0 -0
  297. package/pennyfarthing_scripts/sprint/__pycache__/archive.cpython-314.pyc +0 -0
  298. package/pennyfarthing_scripts/sprint/__pycache__/archive_epic.cpython-314.pyc +0 -0
  299. package/pennyfarthing_scripts/sprint/__pycache__/cli.cpython-314.pyc +0 -0
  300. package/pennyfarthing_scripts/sprint/__pycache__/epic_add.cpython-314.pyc +0 -0
  301. package/pennyfarthing_scripts/sprint/__pycache__/epic_update.cpython-314.pyc +0 -0
  302. package/pennyfarthing_scripts/sprint/__pycache__/import_epic.cpython-314.pyc +0 -0
  303. package/pennyfarthing_scripts/sprint/__pycache__/loader.cpython-314.pyc +0 -0
  304. package/pennyfarthing_scripts/sprint/__pycache__/status.cpython-314.pyc +0 -0
  305. package/pennyfarthing_scripts/sprint/__pycache__/story_add.cpython-314.pyc +0 -0
  306. package/pennyfarthing_scripts/sprint/__pycache__/story_finish.cpython-314.pyc +0 -0
  307. package/pennyfarthing_scripts/sprint/__pycache__/story_update.cpython-314.pyc +0 -0
  308. package/pennyfarthing_scripts/sprint/__pycache__/validate_cmd.cpython-314.pyc +0 -0
  309. package/pennyfarthing_scripts/sprint/__pycache__/validator.cpython-314.pyc +0 -0
  310. package/pennyfarthing_scripts/sprint/__pycache__/work.cpython-314.pyc +0 -0
  311. package/pennyfarthing_scripts/sprint/__pycache__/yaml_io.cpython-314.pyc +0 -0
  312. package/pennyfarthing_scripts/sprint/story_finish.py +14 -0
  313. package/pennyfarthing_scripts/story/__pycache__/__init__.cpython-314.pyc +0 -0
  314. package/pennyfarthing_scripts/story/__pycache__/__main__.cpython-314.pyc +0 -0
  315. package/pennyfarthing_scripts/story/__pycache__/cli.cpython-314.pyc +0 -0
  316. package/pennyfarthing_scripts/story/__pycache__/create.cpython-314.pyc +0 -0
  317. package/pennyfarthing_scripts/story/__pycache__/size.cpython-314.pyc +0 -0
  318. package/pennyfarthing_scripts/story/__pycache__/template.cpython-314.pyc +0 -0
  319. package/pennyfarthing_scripts/tests/__pycache__/__init__.cpython-314.pyc +0 -0
  320. package/pennyfarthing_scripts/tests/__pycache__/conftest.cpython-314-pytest-9.0.2.pyc +0 -0
  321. package/pennyfarthing_scripts/tests/__pycache__/test_108_2_remove_handoff_fallback.cpython-314-pytest-9.0.2.pyc +0 -0
  322. package/pennyfarthing_scripts/tests/__pycache__/test_archive_epic.cpython-314-pytest-9.0.2.pyc +0 -0
  323. package/pennyfarthing_scripts/tests/__pycache__/test_bc.cpython-314-pytest-9.0.2.pyc +0 -0
  324. package/pennyfarthing_scripts/tests/__pycache__/test_bikerack.cpython-314-pytest-9.0.2.pyc +0 -0
  325. package/pennyfarthing_scripts/tests/__pycache__/test_brownfield.cpython-314-pytest-9.0.2.pyc +0 -0
  326. package/pennyfarthing_scripts/tests/__pycache__/test_cli_modules.cpython-314-pytest-9.0.2.pyc +0 -0
  327. package/pennyfarthing_scripts/tests/__pycache__/test_cli_normalization.cpython-314-pytest-9.0.2.pyc +0 -0
  328. package/pennyfarthing_scripts/tests/__pycache__/test_codemarkers.cpython-314-pytest-9.0.2.pyc +0 -0
  329. package/pennyfarthing_scripts/tests/__pycache__/test_common.cpython-314-pytest-9.0.2.pyc +0 -0
  330. package/pennyfarthing_scripts/tests/__pycache__/test_epic_shard_validation.cpython-314-pytest-9.0.2.pyc +0 -0
  331. package/pennyfarthing_scripts/tests/__pycache__/test_gate_file_resolution.cpython-314-pytest-9.0.2.pyc +0 -0
  332. package/pennyfarthing_scripts/tests/__pycache__/test_gate_runner.cpython-314-pytest-9.0.2.pyc +0 -0
  333. package/pennyfarthing_scripts/tests/__pycache__/test_git_utils.cpython-314-pytest-9.0.2.pyc +0 -0
  334. package/pennyfarthing_scripts/tests/__pycache__/test_handoff_cli.cpython-314-pytest-9.0.2.pyc +0 -0
  335. package/pennyfarthing_scripts/tests/__pycache__/test_handoff_e2e.cpython-314-pytest-9.0.2.pyc +0 -0
  336. package/pennyfarthing_scripts/tests/__pycache__/test_healthscore.cpython-314-pytest-9.0.2.pyc +0 -0
  337. package/pennyfarthing_scripts/tests/__pycache__/test_jira_package.cpython-314-pytest-9.0.2.pyc +0 -0
  338. package/pennyfarthing_scripts/tests/__pycache__/test_package_structure.cpython-314-pytest-9.0.2.pyc +0 -0
  339. package/pennyfarthing_scripts/tests/__pycache__/test_patch_mode.cpython-314-pytest-9.0.2.pyc +0 -0
  340. package/pennyfarthing_scripts/tests/__pycache__/test_prime.cpython-314-pytest-9.0.2.pyc +0 -0
  341. package/pennyfarthing_scripts/tests/__pycache__/test_resolve_gate_file_field.cpython-314-pytest-9.0.2.pyc +0 -0
  342. package/pennyfarthing_scripts/tests/__pycache__/test_sprint_package.cpython-314-pytest-9.0.2.pyc +0 -0
  343. package/pennyfarthing_scripts/tests/__pycache__/test_sprint_panel.cpython-314-pytest-9.0.2.pyc +0 -0
  344. package/pennyfarthing_scripts/tests/__pycache__/test_sprint_validator.cpython-314-pytest-9.0.2.pyc +0 -0
  345. package/pennyfarthing_scripts/tests/__pycache__/test_story_add.cpython-314-pytest-9.0.2.pyc +0 -0
  346. package/pennyfarthing_scripts/tests/__pycache__/test_story_package.cpython-314-pytest-9.0.2.pyc +0 -0
  347. package/pennyfarthing_scripts/tests/__pycache__/test_story_update.cpython-314-pytest-9.0.2.pyc +0 -0
  348. package/pennyfarthing_scripts/tests/__pycache__/test_tiers.cpython-314-pytest-9.0.2.pyc +0 -0
  349. package/pennyfarthing_scripts/tests/__pycache__/test_token_counting.cpython-314-pytest-9.0.2.pyc +0 -0
  350. package/pennyfarthing_scripts/tests/__pycache__/test_topology_loader.cpython-314-pytest-9.0.2.pyc +0 -0
  351. package/pennyfarthing_scripts/tests/__pycache__/test_tui_focus.cpython-314-pytest-9.0.2.pyc +0 -0
  352. package/pennyfarthing_scripts/tests/__pycache__/test_tui_panel_persistence.cpython-314-pytest-9.0.2.pyc +0 -0
  353. package/pennyfarthing_scripts/tests/__pycache__/test_validate_cmd.cpython-314-pytest-9.0.2.pyc +0 -0
  354. package/pennyfarthing_scripts/tests/__pycache__/test_version_sentinel.cpython-314-pytest-9.0.2.pyc +0 -0
  355. package/pennyfarthing_scripts/tests/__pycache__/test_workflow_check.cpython-314-pytest-9.0.2.pyc +0 -0
  356. package/pennyfarthing_scripts/tests/__pycache__/test_yaml_io.cpython-314-pytest-9.0.2.pyc +0 -0
  357. package/pennyfarthing_scripts/tests/test_108_1_gate_migration.py +540 -0
  358. package/pennyfarthing_scripts/tests/test_108_2_remove_handoff_fallback.py +339 -0
  359. package/pennyfarthing_scripts/tests/test_confidence_sm_evaluation.py +253 -0
  360. package/pennyfarthing_scripts/tests/test_confidence_sm_gate.py +315 -0
  361. package/pennyfarthing_scripts/tests/test_gate_file_resolution.py +341 -0
  362. package/pennyfarthing_scripts/tests/test_gate_runner.py +620 -0
  363. package/pennyfarthing_scripts/tests/test_handoff_cli.py +929 -0
  364. package/pennyfarthing_scripts/tests/test_handoff_e2e.py +454 -0
  365. package/pennyfarthing_scripts/tests/test_resolve_gate_file_field.py +464 -0
  366. package/pennyfarthing_scripts/theme/__pycache__/__init__.cpython-314.pyc +0 -0
  367. package/pennyfarthing_scripts/theme/__pycache__/cli.cpython-314.pyc +0 -0
  368. package/pennyfarthing_scripts/validate/__pycache__/__init__.cpython-314.pyc +0 -0
  369. package/pennyfarthing_scripts/validate/__pycache__/cli.cpython-314.pyc +0 -0
  370. package/pennyfarthing_scripts/validate/adapters/__pycache__/__init__.cpython-314.pyc +0 -0
  371. package/pennyfarthing_scripts/validate/adapters/__pycache__/agent.cpython-314.pyc +0 -0
  372. package/pennyfarthing_scripts/validate/adapters/__pycache__/schema.cpython-314.pyc +0 -0
  373. package/pennyfarthing_scripts/validate/adapters/__pycache__/skill_command.cpython-314.pyc +0 -0
  374. package/pennyfarthing_scripts/validate/adapters/__pycache__/sprint.cpython-314.pyc +0 -0
  375. package/pennyfarthing_scripts/validate/adapters/__pycache__/workflow.cpython-314.pyc +0 -0
  376. package/pennyfarthing_scripts/validate/adapters/skill_command.py +200 -0
  377. package/pennyfarthing_scripts/validate/adapters/workflow.py +64 -0
  378. package/pennyfarthing_scripts/validate/cli.py +15 -4
  379. package/packages/core/dist/scripts/benchmark-integration.d.ts +0 -182
  380. package/packages/core/dist/scripts/benchmark-integration.d.ts.map +0 -1
  381. package/packages/core/dist/scripts/benchmark-integration.js +0 -691
  382. package/packages/core/dist/scripts/benchmark-integration.js.map +0 -1
  383. package/packages/core/dist/scripts/job-fair-aggregator.d.ts +0 -150
  384. package/packages/core/dist/scripts/job-fair-aggregator.d.ts.map +0 -1
  385. package/packages/core/dist/scripts/job-fair-aggregator.js +0 -547
  386. package/packages/core/dist/scripts/job-fair-aggregator.js.map +0 -1
  387. package/pennyfarthing-dist/agents/handoff.md +0 -250
  388. package/pennyfarthing-dist/agents/sm-handoff.md +0 -152
  389. package/pennyfarthing-dist/scripts/core/handoff-marker.sh +0 -112
  390. package/pennyfarthing-dist/scripts/hooks/__pycache__/question_reflector_check.cpython-314.pyc +0 -0
  391. package/pennyfarthing_scripts/__pycache__/__init__.cpython-311.pyc +0 -0
  392. package/pennyfarthing_scripts/__pycache__/jira.cpython-314.pyc +0 -0
  393. package/pennyfarthing_scripts/__pycache__/sprint.cpython-314.pyc +0 -0
  394. package/pennyfarthing_scripts/__pycache__/workflow.cpython-311.pyc +0 -0
  395. package/pennyfarthing_scripts/jira/__pycache__/compat.cpython-314.pyc +0 -0
  396. package/pennyfarthing_scripts/jira/__pycache__/mappings.cpython-314.pyc +0 -0
  397. package/pennyfarthing_scripts/jira/__pycache__/models.cpython-314.pyc +0 -0
  398. package/pennyfarthing_scripts/migration/__pycache__/__main__.cpython-314.pyc +0 -0
  399. package/pennyfarthing_scripts/migration/__pycache__/cli.cpython-314.pyc +0 -0
  400. package/pennyfarthing_scripts/prime/__pycache__/__main__.cpython-314.pyc +0 -0
  401. package/pennyfarthing_scripts/tests/__pycache__/test_workflow_cli.cpython-314-pytest-9.0.2.pyc +0 -0
@@ -0,0 +1,540 @@
1
+ """Tests for gate file migration — Story 108-1.
2
+
3
+ Epic: 108 (Full Migration & Cleanup)
4
+ Story: 108-1 — Migrate tests-fail and approval gates to files
5
+
6
+ Tests the creation of tests-fail.md and approval.md gate files,
7
+ and the addition of gate.file entries to workflow YAMLs.
8
+
9
+ Acceptance Criteria:
10
+ - [AC1] Gate files tests-fail.md and approval.md exist with correct structure
11
+ - [AC2] Workflow YAMLs have gate.file entries for tests_fail and approval phases
12
+ - [AC3] Gate files resolve via resolve_gate_file() and validate correctly
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import re
18
+ from pathlib import Path
19
+
20
+ import pytest
21
+ import yaml
22
+
23
+ from pennyfarthing_scripts.handoff.gate_file import resolve_gate_file
24
+ from pennyfarthing_scripts.handoff.resolve_gate import resolve_gate
25
+
26
+ # ---------------------------------------------------------------------------
27
+ # Paths
28
+ # ---------------------------------------------------------------------------
29
+
30
+ DIST_DIR = Path(__file__).resolve().parents[2] / "pennyfarthing-dist"
31
+ GATES_DIR = DIST_DIR / "gates"
32
+ WORKFLOWS_DIR = DIST_DIR / "workflows"
33
+
34
+
35
+ # ---------------------------------------------------------------------------
36
+ # Helpers
37
+ # ---------------------------------------------------------------------------
38
+
39
+
40
+ def _read_gate(name: str) -> str:
41
+ """Read a gate file by name from pennyfarthing-dist/gates/."""
42
+ path = GATES_DIR / f"{name}.md"
43
+ assert path.exists(), f"Gate file not found: {path}"
44
+ return path.read_text()
45
+
46
+
47
+ def _load_workflow(name: str) -> dict:
48
+ """Load a workflow YAML from pennyfarthing-dist/workflows/."""
49
+ path = WORKFLOWS_DIR / f"{name}.yaml"
50
+ assert path.exists(), f"Workflow not found: {path}"
51
+ return yaml.safe_load(path.read_text())
52
+
53
+
54
+ def _get_phase(workflow_data: dict, phase_name: str) -> dict:
55
+ """Extract a phase dict from workflow data by name."""
56
+ for phase in workflow_data["workflow"]["phases"]:
57
+ if phase["name"] == phase_name:
58
+ return phase
59
+ raise ValueError(
60
+ f"Phase '{phase_name}' not found in workflow "
61
+ f"'{workflow_data['workflow']['name']}'"
62
+ )
63
+
64
+
65
+ def _get_gate_file(workflow_data: dict, phase_name: str) -> str | None:
66
+ """Get the gate.file value for a phase, or None."""
67
+ phase = _get_phase(workflow_data, phase_name)
68
+ gate = phase.get("gate") or {}
69
+ return gate.get("file")
70
+
71
+
72
+ def _get_gate_type(workflow_data: dict, phase_name: str) -> str | None:
73
+ """Get the gate.type value for a phase, or None."""
74
+ phase = _get_phase(workflow_data, phase_name)
75
+ gate = phase.get("gate") or {}
76
+ return gate.get("type")
77
+
78
+
79
+ # ===========================================================================
80
+ # AC1: Gate files exist with correct structure
81
+ # ===========================================================================
82
+
83
+
84
+ class TestTestsFailGateFile:
85
+ """AC1: tests-fail.md gate file exists and has correct structure."""
86
+
87
+ def test_file_exists(self) -> None:
88
+ """AC1: tests-fail.md must exist in pennyfarthing-dist/gates/."""
89
+ assert (GATES_DIR / "tests-fail.md").exists()
90
+
91
+ def test_has_gate_root_element(self) -> None:
92
+ """AC1: File must have <gate> root element."""
93
+ content = _read_gate("tests-fail")
94
+ assert "<gate " in content, "Missing <gate> root element"
95
+ assert "</gate>" in content, "Missing closing </gate> tag"
96
+
97
+ def test_gate_name_attribute(self) -> None:
98
+ """AC1: Gate name must be 'tests-fail'."""
99
+ content = _read_gate("tests-fail")
100
+ assert 'name="tests-fail"' in content
101
+
102
+ def test_gate_model_attribute(self) -> None:
103
+ """AC1: Gate model must be 'haiku'."""
104
+ content = _read_gate("tests-fail")
105
+ assert 'model="haiku"' in content
106
+
107
+ def test_has_purpose_section(self) -> None:
108
+ """AC1: Must have <purpose> section."""
109
+ content = _read_gate("tests-fail")
110
+ assert "<purpose>" in content
111
+ assert "</purpose>" in content
112
+
113
+ def test_has_pass_section(self) -> None:
114
+ """AC1: Must have <pass> section."""
115
+ content = _read_gate("tests-fail")
116
+ assert "<pass>" in content
117
+ assert "</pass>" in content
118
+
119
+ def test_has_fail_section(self) -> None:
120
+ """AC1: Must have <fail> section."""
121
+ content = _read_gate("tests-fail")
122
+ assert "<fail>" in content
123
+ assert "</fail>" in content
124
+
125
+ def test_purpose_mentions_failing_tests(self) -> None:
126
+ """AC1: Purpose should describe checking for failing/RED tests."""
127
+ content = _read_gate("tests-fail")
128
+ purpose = re.search(
129
+ r"<purpose>(.*?)</purpose>", content, re.DOTALL
130
+ )
131
+ assert purpose is not None
132
+ purpose_text = purpose.group(1).lower()
133
+ assert "fail" in purpose_text or "red" in purpose_text
134
+
135
+ def test_pass_section_mentions_test_coverage(self) -> None:
136
+ """AC1: Pass section should reference test coverage of acceptance criteria."""
137
+ content = _read_gate("tests-fail")
138
+ pass_section = re.search(r"<pass>(.*?)</pass>", content, re.DOTALL)
139
+ assert pass_section is not None
140
+ pass_text = pass_section.group(1).lower()
141
+ assert "test" in pass_text
142
+ assert "acceptance" in pass_text or "criteria" in pass_text or "committed" in pass_text
143
+
144
+ def test_pass_section_mentions_gate_result(self) -> None:
145
+ """AC1: Pass section should return GATE_RESULT."""
146
+ content = _read_gate("tests-fail")
147
+ pass_section = re.search(r"<pass>(.*?)</pass>", content, re.DOTALL)
148
+ assert pass_section is not None
149
+ assert "GATE_RESULT" in pass_section.group(1)
150
+
151
+ def test_fail_section_mentions_gate_result(self) -> None:
152
+ """AC1: Fail section should return GATE_RESULT."""
153
+ content = _read_gate("tests-fail")
154
+ fail_section = re.search(r"<fail>(.*?)</fail>", content, re.DOTALL)
155
+ assert fail_section is not None
156
+ assert "GATE_RESULT" in fail_section.group(1)
157
+
158
+
159
+ class TestApprovalGateFile:
160
+ """AC1: approval.md gate file exists and has correct structure."""
161
+
162
+ def test_file_exists(self) -> None:
163
+ """AC1: approval.md must exist in pennyfarthing-dist/gates/."""
164
+ assert (GATES_DIR / "approval.md").exists()
165
+
166
+ def test_has_gate_root_element(self) -> None:
167
+ """AC1: File must have <gate> root element."""
168
+ content = _read_gate("approval")
169
+ assert "<gate " in content, "Missing <gate> root element"
170
+ assert "</gate>" in content, "Missing closing </gate> tag"
171
+
172
+ def test_gate_name_attribute(self) -> None:
173
+ """AC1: Gate name must be 'approval'."""
174
+ content = _read_gate("approval")
175
+ assert 'name="approval"' in content
176
+
177
+ def test_gate_model_attribute(self) -> None:
178
+ """AC1: Gate model must be 'haiku'."""
179
+ content = _read_gate("approval")
180
+ assert 'model="haiku"' in content
181
+
182
+ def test_has_purpose_section(self) -> None:
183
+ """AC1: Must have <purpose> section."""
184
+ content = _read_gate("approval")
185
+ assert "<purpose>" in content
186
+ assert "</purpose>" in content
187
+
188
+ def test_has_pass_section(self) -> None:
189
+ """AC1: Must have <pass> section."""
190
+ content = _read_gate("approval")
191
+ assert "<pass>" in content
192
+ assert "</pass>" in content
193
+
194
+ def test_has_fail_section(self) -> None:
195
+ """AC1: Must have <fail> section."""
196
+ content = _read_gate("approval")
197
+ assert "<fail>" in content
198
+ assert "</fail>" in content
199
+
200
+ def test_purpose_mentions_reviewer(self) -> None:
201
+ """AC1: Purpose should describe checking reviewer verdict."""
202
+ content = _read_gate("approval")
203
+ purpose = re.search(
204
+ r"<purpose>(.*?)</purpose>", content, re.DOTALL
205
+ )
206
+ assert purpose is not None
207
+ purpose_text = purpose.group(1).lower()
208
+ assert "review" in purpose_text or "verdict" in purpose_text
209
+
210
+ def test_pass_section_mentions_approved(self) -> None:
211
+ """AC1: Pass section should reference APPROVED verdict."""
212
+ content = _read_gate("approval")
213
+ pass_section = re.search(r"<pass>(.*?)</pass>", content, re.DOTALL)
214
+ assert pass_section is not None
215
+ assert "APPROVED" in pass_section.group(1) or "approved" in pass_section.group(1).lower()
216
+
217
+ def test_fail_section_handles_rejected(self) -> None:
218
+ """AC1: Fail section should handle REJECTED verdict."""
219
+ content = _read_gate("approval")
220
+ fail_section = re.search(r"<fail>(.*?)</fail>", content, re.DOTALL)
221
+ assert fail_section is not None
222
+ fail_text = fail_section.group(1)
223
+ assert "REJECTED" in fail_text or "rejected" in fail_text.lower()
224
+
225
+ def test_pass_section_mentions_gate_result(self) -> None:
226
+ """AC1: Pass section should return GATE_RESULT."""
227
+ content = _read_gate("approval")
228
+ pass_section = re.search(r"<pass>(.*?)</pass>", content, re.DOTALL)
229
+ assert pass_section is not None
230
+ assert "GATE_RESULT" in pass_section.group(1)
231
+
232
+ def test_fail_section_mentions_gate_result(self) -> None:
233
+ """AC1: Fail section should return GATE_RESULT."""
234
+ content = _read_gate("approval")
235
+ fail_section = re.search(r"<fail>(.*?)</fail>", content, re.DOTALL)
236
+ assert fail_section is not None
237
+ assert "GATE_RESULT" in fail_section.group(1)
238
+
239
+
240
+ # ===========================================================================
241
+ # AC2: Workflow YAMLs have gate.file entries
242
+ # ===========================================================================
243
+
244
+
245
+ class TestTddWorkflowGateFiles:
246
+ """AC2: tdd.yaml has gate.file for red (tests_fail) and review (approval)."""
247
+
248
+ @pytest.fixture
249
+ def tdd(self) -> dict:
250
+ return _load_workflow("tdd")
251
+
252
+ def test_red_phase_has_gate_file(self, tdd: dict) -> None:
253
+ """AC2: tdd red phase must have gate.file = 'gates/tests-fail'."""
254
+ assert _get_gate_file(tdd, "red") == "gates/tests-fail"
255
+
256
+ def test_red_phase_keeps_gate_type(self, tdd: dict) -> None:
257
+ """AC2: tdd red phase must retain gate.type = 'tests_fail' during transition."""
258
+ assert _get_gate_type(tdd, "red") == "tests_fail"
259
+
260
+ def test_review_phase_has_gate_file(self, tdd: dict) -> None:
261
+ """AC2: tdd review phase must have gate.file = 'gates/approval'."""
262
+ assert _get_gate_file(tdd, "review") == "gates/approval"
263
+
264
+ def test_review_phase_keeps_gate_type(self, tdd: dict) -> None:
265
+ """AC2: tdd review phase must retain gate.type = 'approval' during transition."""
266
+ assert _get_gate_type(tdd, "review") == "approval"
267
+
268
+ def test_green_phase_unchanged(self, tdd: dict) -> None:
269
+ """AC2: tdd green phase gate.file should still be gates/tests-pass (from 106)."""
270
+ assert _get_gate_file(tdd, "green") == "gates/tests-pass"
271
+
272
+
273
+ class TestTrivialWorkflowGateFiles:
274
+ """AC2: trivial.yaml has gate.file for review (approval)."""
275
+
276
+ @pytest.fixture
277
+ def trivial(self) -> dict:
278
+ return _load_workflow("trivial")
279
+
280
+ def test_review_phase_has_gate_file(self, trivial: dict) -> None:
281
+ """AC2: trivial review phase must have gate.file = 'gates/approval'."""
282
+ assert _get_gate_file(trivial, "review") == "gates/approval"
283
+
284
+ def test_review_phase_keeps_gate_type(self, trivial: dict) -> None:
285
+ """AC2: trivial review phase must retain gate.type = 'approval'."""
286
+ assert _get_gate_type(trivial, "review") == "approval"
287
+
288
+
289
+ class TestBddWorkflowGateFiles:
290
+ """AC2: bdd.yaml has gate.file for red (tests_fail) and review (approval)."""
291
+
292
+ @pytest.fixture
293
+ def bdd(self) -> dict:
294
+ return _load_workflow("bdd")
295
+
296
+ def test_red_phase_has_gate_file(self, bdd: dict) -> None:
297
+ """AC2: bdd red phase must have gate.file = 'gates/tests-fail'."""
298
+ assert _get_gate_file(bdd, "red") == "gates/tests-fail"
299
+
300
+ def test_red_phase_keeps_gate_type(self, bdd: dict) -> None:
301
+ """AC2: bdd red phase must retain gate.type = 'tests_fail'."""
302
+ assert _get_gate_type(bdd, "red") == "tests_fail"
303
+
304
+ def test_review_phase_has_gate_file(self, bdd: dict) -> None:
305
+ """AC2: bdd review phase must have gate.file = 'gates/approval'."""
306
+ assert _get_gate_file(bdd, "review") == "gates/approval"
307
+
308
+ def test_review_phase_keeps_gate_type(self, bdd: dict) -> None:
309
+ """AC2: bdd review phase must retain gate.type = 'approval'."""
310
+ assert _get_gate_type(bdd, "review") == "approval"
311
+
312
+
313
+ class TestTddTandemWorkflowGateFiles:
314
+ """AC2: tdd-tandem.yaml has gate.file for red and review."""
315
+
316
+ @pytest.fixture
317
+ def tdd_tandem(self) -> dict:
318
+ return _load_workflow("tdd-tandem")
319
+
320
+ def test_red_phase_has_gate_file(self, tdd_tandem: dict) -> None:
321
+ """AC2: tdd-tandem red phase must have gate.file = 'gates/tests-fail'."""
322
+ assert _get_gate_file(tdd_tandem, "red") == "gates/tests-fail"
323
+
324
+ def test_review_phase_has_gate_file(self, tdd_tandem: dict) -> None:
325
+ """AC2: tdd-tandem review phase must have gate.file = 'gates/approval'."""
326
+ assert _get_gate_file(tdd_tandem, "review") == "gates/approval"
327
+
328
+
329
+ class TestBddTandemWorkflowGateFiles:
330
+ """AC2: bdd-tandem.yaml has gate.file for red and review."""
331
+
332
+ @pytest.fixture
333
+ def bdd_tandem(self) -> dict:
334
+ return _load_workflow("bdd-tandem")
335
+
336
+ def test_red_phase_has_gate_file(self, bdd_tandem: dict) -> None:
337
+ """AC2: bdd-tandem red phase must have gate.file = 'gates/tests-fail'."""
338
+ assert _get_gate_file(bdd_tandem, "red") == "gates/tests-fail"
339
+
340
+ def test_review_phase_has_gate_file(self, bdd_tandem: dict) -> None:
341
+ """AC2: bdd-tandem review phase must have gate.file = 'gates/approval'."""
342
+ assert _get_gate_file(bdd_tandem, "review") == "gates/approval"
343
+
344
+
345
+ class TestTwoPartyTddWorkflowGateFiles:
346
+ """AC2: 2party-tdd.yaml has gate.file for all tests_fail and approval phases."""
347
+
348
+ @pytest.fixture
349
+ def two_party(self) -> dict:
350
+ return _load_workflow("2party-tdd")
351
+
352
+ # tests_fail phases
353
+ def test_red_phase_has_gate_file(self, two_party: dict) -> None:
354
+ """AC2: 2party-tdd red phase must have gate.file = 'gates/tests-fail'."""
355
+ assert _get_gate_file(two_party, "red") == "gates/tests-fail"
356
+
357
+ def test_review_fix_tea_has_gate_file(self, two_party: dict) -> None:
358
+ """AC2: 2party-tdd review-fix-tea phase must have gate.file = 'gates/tests-fail'."""
359
+ assert _get_gate_file(two_party, "review-fix-tea") == "gates/tests-fail"
360
+
361
+ # approval phases
362
+ def test_refine_dev_has_gate_file(self, two_party: dict) -> None:
363
+ """AC2: 2party-tdd refine-dev phase must have gate.file = 'gates/approval'."""
364
+ assert _get_gate_file(two_party, "refine-dev") == "gates/approval"
365
+
366
+ def test_refine_tea_has_gate_file(self, two_party: dict) -> None:
367
+ """AC2: 2party-tdd refine-tea phase must have gate.file = 'gates/approval'."""
368
+ assert _get_gate_file(two_party, "refine-tea") == "gates/approval"
369
+
370
+ def test_quality_pass_has_gate_file(self, two_party: dict) -> None:
371
+ """AC2: 2party-tdd quality-pass phase must have gate.file = 'gates/approval'."""
372
+ assert _get_gate_file(two_party, "quality-pass") == "gates/approval"
373
+
374
+ def test_review_has_gate_file(self, two_party: dict) -> None:
375
+ """AC2: 2party-tdd review phase must have gate.file = 'gates/approval'."""
376
+ assert _get_gate_file(two_party, "review") == "gates/approval"
377
+
378
+ def test_pr_prepare_has_gate_file(self, two_party: dict) -> None:
379
+ """AC2: 2party-tdd pr-prepare phase must have gate.file = 'gates/approval'."""
380
+ assert _get_gate_file(two_party, "pr-prepare") == "gates/approval"
381
+
382
+ def test_pr_review_party_has_gate_file(self, two_party: dict) -> None:
383
+ """AC2: 2party-tdd pr-review-party phase must have gate.file = 'gates/approval'."""
384
+ assert _get_gate_file(two_party, "pr-review-party") == "gates/approval"
385
+
386
+ def test_pr_review_respond_has_gate_file(self, two_party: dict) -> None:
387
+ """AC2: 2party-tdd pr-review-respond phase must have gate.file = 'gates/approval'."""
388
+ assert _get_gate_file(two_party, "pr-review-respond") == "gates/approval"
389
+
390
+
391
+ class TestAgentDocsWorkflowGateFiles:
392
+ """AC2: agent-docs.yaml has gate.file for review (approval)."""
393
+
394
+ @pytest.fixture
395
+ def agent_docs(self) -> dict:
396
+ return _load_workflow("agent-docs")
397
+
398
+ def test_review_phase_has_gate_file(self, agent_docs: dict) -> None:
399
+ """AC2: agent-docs review phase must have gate.file = 'gates/approval'."""
400
+ assert _get_gate_file(agent_docs, "review") == "gates/approval"
401
+
402
+ def test_review_phase_keeps_gate_type(self, agent_docs: dict) -> None:
403
+ """AC2: agent-docs review phase must retain gate.type = 'approval'."""
404
+ assert _get_gate_type(agent_docs, "review") == "approval"
405
+
406
+
407
+ # ===========================================================================
408
+ # AC2: gate.type retained during transition
409
+ # ===========================================================================
410
+
411
+
412
+ class TestGateTypeRetainedDuringTransition:
413
+ """AC2: All phases that get gate.file must also keep gate.type."""
414
+
415
+ @pytest.mark.parametrize(
416
+ "workflow_name,phase_name,expected_type",
417
+ [
418
+ ("tdd", "red", "tests_fail"),
419
+ ("tdd", "review", "approval"),
420
+ ("trivial", "review", "approval"),
421
+ ("bdd", "red", "tests_fail"),
422
+ ("bdd", "review", "approval"),
423
+ ("tdd-tandem", "red", "tests_fail"),
424
+ ("tdd-tandem", "review", "approval"),
425
+ ("bdd-tandem", "red", "tests_fail"),
426
+ ("bdd-tandem", "review", "approval"),
427
+ ("2party-tdd", "red", "tests_fail"),
428
+ ("2party-tdd", "review", "approval"),
429
+ ("2party-tdd", "review-fix-tea", "tests_fail"),
430
+ ("agent-docs", "review", "approval"),
431
+ ],
432
+ )
433
+ def test_gate_type_retained(
434
+ self, workflow_name: str, phase_name: str, expected_type: str
435
+ ) -> None:
436
+ """AC2: gate.type must be retained alongside gate.file during transition."""
437
+ wf = _load_workflow(workflow_name)
438
+ assert _get_gate_type(wf, phase_name) == expected_type
439
+
440
+
441
+ # ===========================================================================
442
+ # AC3: Gate file resolution
443
+ # ===========================================================================
444
+
445
+
446
+ class TestGateFileResolution:
447
+ """AC3: New gate files resolve via resolve_gate_file()."""
448
+
449
+ @pytest.fixture
450
+ def project(self, tmp_path: Path) -> Path:
451
+ """Create project structure with symlink to real gates."""
452
+ (tmp_path / ".pennyfarthing" / "gates").mkdir(parents=True)
453
+ (tmp_path / "pennyfarthing-dist" / "gates").mkdir(parents=True)
454
+
455
+ # Copy the actual gate files (they should exist after implementation)
456
+ for name in ("tests-fail", "approval"):
457
+ src = GATES_DIR / f"{name}.md"
458
+ if src.exists():
459
+ dst = tmp_path / "pennyfarthing-dist" / "gates" / f"{name}.md"
460
+ dst.write_text(src.read_text())
461
+ return tmp_path
462
+
463
+ def test_tests_fail_resolves(self, project: Path) -> None:
464
+ """AC3: resolve_gate_file('tests-fail') should return 'found'."""
465
+ result = resolve_gate_file("tests-fail", project_root=project)
466
+ assert result["status"] == "found", f"Expected 'found', got: {result}"
467
+
468
+ def test_approval_resolves(self, project: Path) -> None:
469
+ """AC3: resolve_gate_file('approval') should return 'found'."""
470
+ result = resolve_gate_file("approval", project_root=project)
471
+ assert result["status"] == "found", f"Expected 'found', got: {result}"
472
+
473
+ def test_tests_fail_with_prefix_resolves(self, project: Path) -> None:
474
+ """AC3: resolve_gate_file('gates/tests-fail') should resolve same path."""
475
+ result = resolve_gate_file("gates/tests-fail", project_root=project)
476
+ assert result["status"] == "found"
477
+
478
+ def test_approval_with_prefix_resolves(self, project: Path) -> None:
479
+ """AC3: resolve_gate_file('gates/approval') should resolve same path."""
480
+ result = resolve_gate_file("gates/approval", project_root=project)
481
+ assert result["status"] == "found"
482
+
483
+
484
+ class TestResolveGateIntegration:
485
+ """AC3: resolve_gate returns gate_file for migrated phases."""
486
+
487
+ @pytest.fixture
488
+ def project(self, tmp_path: Path) -> Path:
489
+ """Create project with real workflow YAMLs and a session file."""
490
+ workflows_dir = tmp_path / ".pennyfarthing" / "workflows"
491
+ workflows_dir.mkdir(parents=True)
492
+
493
+ # Copy real workflow YAMLs
494
+ for name in ("tdd", "trivial", "bdd"):
495
+ src = WORKFLOWS_DIR / f"{name}.yaml"
496
+ if src.exists():
497
+ (workflows_dir / f"{name}.yaml").write_text(src.read_text())
498
+
499
+ # Create session with assessment
500
+ (tmp_path / ".session").mkdir()
501
+ session = tmp_path / ".session" / "108-1-session.md"
502
+ session.write_text(
503
+ "# Story 108-1\n\n"
504
+ "**Workflow:** tdd\n"
505
+ "**Phase:** red\n\n"
506
+ "## TEA Assessment\n\n"
507
+ "**Tests Written:** 5\n"
508
+ "**Status:** RED confirmed\n"
509
+ )
510
+ return tmp_path
511
+
512
+ def test_tdd_red_returns_gate_file(self, project: Path) -> None:
513
+ """AC3: resolve_gate for tdd/red should return gate_file='gates/tests-fail'."""
514
+ result = resolve_gate("108-1", "tdd", "red", project_root=project)
515
+ assert result["gate_file"] == "gates/tests-fail", (
516
+ f"Expected gate_file='gates/tests-fail', got: {result}"
517
+ )
518
+
519
+ def test_tdd_review_returns_gate_file(self, project: Path) -> None:
520
+ """AC3: resolve_gate for tdd/review should return gate_file='gates/approval'."""
521
+ result = resolve_gate("108-1", "tdd", "review", project_root=project)
522
+ assert result["gate_file"] == "gates/approval", (
523
+ f"Expected gate_file='gates/approval', got: {result}"
524
+ )
525
+
526
+ def test_trivial_review_returns_gate_file(self, project: Path) -> None:
527
+ """AC3: resolve_gate for trivial/review should return gate_file='gates/approval'."""
528
+ result = resolve_gate(
529
+ "108-1", "trivial", "review", project_root=project
530
+ )
531
+ assert result["gate_file"] == "gates/approval", (
532
+ f"Expected gate_file='gates/approval', got: {result}"
533
+ )
534
+
535
+ def test_bdd_red_returns_gate_file(self, project: Path) -> None:
536
+ """AC3: resolve_gate for bdd/red should return gate_file='gates/tests-fail'."""
537
+ result = resolve_gate("108-1", "bdd", "red", project_root=project)
538
+ assert result["gate_file"] == "gates/tests-fail", (
539
+ f"Expected gate_file='gates/tests-fail', got: {result}"
540
+ )