@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,464 @@
1
+ """Tests for gate.file field in resolve_gate — Story 106-3.
2
+
3
+ Epic: 106 (Gate Files & First Migration)
4
+ Story: 106-3 — Workflow YAML gate.file integration
5
+
6
+ Tests the gate.file field support in resolve_gate():
7
+ - Schema extension: gate.file extracted from workflow YAML
8
+ - Backward compatibility: gate.type-only workflows unchanged
9
+ - TDD workflow migration: green phase has file: gates/tests-pass
10
+
11
+ Acceptance Criteria:
12
+ - [AC1] resolve-gate.py reads gate.file field from workflow YAML phases
13
+ - [AC1] gate.file takes precedence over gate.type when both present
14
+ - [AC1] Returns gate_file: null when only gate.type exists
15
+ - [AC2] Workflows with only gate.type continue to work unchanged
16
+ - [AC2] Existing gate type logic remains functional
17
+ - [AC2] No breaking changes to resolve-gate API
18
+ - [AC3] Green phase in tdd.yaml has file: gates/tests-pass and type: tests_pass
19
+ - [AC3] Other phases remain unchanged (backward compat period)
20
+ - [AC3] File path is relative: gates/tests-pass (not absolute)
21
+ """
22
+
23
+ from __future__ import annotations
24
+
25
+ import textwrap
26
+ from pathlib import Path
27
+
28
+ import pytest
29
+ import yaml
30
+
31
+ from pennyfarthing_scripts.handoff.resolve_gate import resolve_gate
32
+
33
+ # ---------------------------------------------------------------------------
34
+ # Fixtures: Workflow YAML data with gate.file support
35
+ # ---------------------------------------------------------------------------
36
+
37
+ WORKFLOW_WITH_FILE_AND_TYPE = {
38
+ "workflow": {
39
+ "name": "tdd",
40
+ "phases": [
41
+ {"name": "setup", "agent": "sm"},
42
+ {"name": "red", "agent": "tea", "gate": {"type": "tests_fail"}},
43
+ {
44
+ "name": "green",
45
+ "agent": "dev",
46
+ "gate": {
47
+ "file": "gates/tests-pass",
48
+ "type": "tests_pass",
49
+ "condition": "All tests passing, no skipped tests",
50
+ },
51
+ },
52
+ {"name": "review", "agent": "reviewer", "gate": {"type": "approval"}},
53
+ {"name": "finish", "agent": "sm"},
54
+ ],
55
+ }
56
+ }
57
+
58
+ WORKFLOW_WITH_FILE_ONLY = {
59
+ "workflow": {
60
+ "name": "file-only",
61
+ "phases": [
62
+ {"name": "setup", "agent": "sm"},
63
+ {
64
+ "name": "green",
65
+ "agent": "dev",
66
+ "gate": {
67
+ "file": "gates/tests-pass",
68
+ "condition": "All tests passing",
69
+ },
70
+ },
71
+ {"name": "finish", "agent": "sm"},
72
+ ],
73
+ }
74
+ }
75
+
76
+ WORKFLOW_WITH_TYPE_ONLY = {
77
+ "workflow": {
78
+ "name": "legacy",
79
+ "phases": [
80
+ {"name": "setup", "agent": "sm"},
81
+ {
82
+ "name": "green",
83
+ "agent": "dev",
84
+ "gate": {"type": "tests_pass"},
85
+ },
86
+ {"name": "finish", "agent": "sm"},
87
+ ],
88
+ }
89
+ }
90
+
91
+ SESSION_WITH_ASSESSMENT = textwrap.dedent("""\
92
+ # Story 106-3: Workflow YAML gate.file integration
93
+
94
+ **Story ID:** 106-3
95
+ **Workflow:** tdd
96
+ **Phase:** green
97
+ **Phase Started:** 2026-02-15T10:00:00Z
98
+
99
+ ## TEA Assessment
100
+
101
+ **Tests Written:** 5 tests
102
+ **Status:** RED confirmed
103
+
104
+ ## Workflow Tracking
105
+
106
+ **Phase:** green
107
+ **Phase Started:** 2026-02-15T10:00:00Z
108
+
109
+ ### Phase History
110
+ | Phase | Started | Ended | Duration |
111
+ |-------|---------|-------|----------|
112
+ | green | 2026-02-15T10:00:00Z | - | - |
113
+
114
+ ### Handoff History
115
+ | From | To | Gate | Status | Timestamp |
116
+ |------|-----|------|--------|-----------|
117
+ """)
118
+
119
+
120
+ # ---------------------------------------------------------------------------
121
+ # Fixtures: Project structure
122
+ # ---------------------------------------------------------------------------
123
+
124
+
125
+ @pytest.fixture
126
+ def project(tmp_path: Path) -> Path:
127
+ """Create a minimal project structure with workflow YAMLs."""
128
+ workflows_dir = tmp_path / ".pennyfarthing" / "workflows"
129
+ workflows_dir.mkdir(parents=True)
130
+
131
+ for name, data in [
132
+ ("tdd", WORKFLOW_WITH_FILE_AND_TYPE),
133
+ ("file-only", WORKFLOW_WITH_FILE_ONLY),
134
+ ("legacy", WORKFLOW_WITH_TYPE_ONLY),
135
+ ]:
136
+ (workflows_dir / f"{name}.yaml").write_text(
137
+ yaml.dump(data, default_flow_style=False)
138
+ )
139
+
140
+ (tmp_path / ".session").mkdir()
141
+ session_file = tmp_path / ".session" / "106-3-session.md"
142
+ session_file.write_text(SESSION_WITH_ASSESSMENT)
143
+ return tmp_path
144
+
145
+
146
+ # ===========================================================================
147
+ # AC1: Schema Extension — gate.file extracted from workflow YAML
148
+ # ===========================================================================
149
+
150
+
151
+ class TestResolveGateFilePresent:
152
+ """AC1: resolve_gate returns gate_file when gate.file is in workflow YAML."""
153
+
154
+ def test_gate_file_populated_when_file_in_yaml(
155
+ self, project: Path
156
+ ) -> None:
157
+ """AC1: gate_file should be 'gates/tests-pass' when file field exists."""
158
+ result = resolve_gate("106-3", "tdd", "green", project_root=project)
159
+ assert result["gate_file"] == "gates/tests-pass"
160
+
161
+ def test_gate_type_still_populated_with_file(
162
+ self, project: Path
163
+ ) -> None:
164
+ """AC1: gate_type should still be 'tests_pass' when both file and type exist."""
165
+ result = resolve_gate("106-3", "tdd", "green", project_root=project)
166
+ assert result["gate_type"] == "tests_pass"
167
+
168
+ def test_both_fields_present_in_result(
169
+ self, project: Path
170
+ ) -> None:
171
+ """AC1: Both gate_file and gate_type should be non-None when both are in YAML."""
172
+ result = resolve_gate("106-3", "tdd", "green", project_root=project)
173
+ assert result["gate_file"] is not None
174
+ assert result["gate_type"] is not None
175
+
176
+ def test_status_still_ready_with_file(
177
+ self, project: Path
178
+ ) -> None:
179
+ """AC1: Status should still be 'ready' when assessment exists and gate.file is present."""
180
+ result = resolve_gate("106-3", "tdd", "green", project_root=project)
181
+ assert result["status"] == "ready"
182
+
183
+
184
+ class TestResolveGateFileOnly:
185
+ """AC1: resolve_gate handles gate with file but no type."""
186
+
187
+ def test_gate_file_populated_without_type(
188
+ self, project: Path
189
+ ) -> None:
190
+ """AC1: gate_file should be populated when only file exists (no type)."""
191
+ result = resolve_gate(
192
+ "106-3", "file-only", "green", project_root=project
193
+ )
194
+ assert result["gate_file"] == "gates/tests-pass"
195
+
196
+ def test_gate_type_null_when_only_file(
197
+ self, project: Path
198
+ ) -> None:
199
+ """AC1: gate_type should be None when only file is in gate."""
200
+ result = resolve_gate(
201
+ "106-3", "file-only", "green", project_root=project
202
+ )
203
+ assert result["gate_type"] is None
204
+
205
+ def test_status_skip_when_no_type_and_file_only(
206
+ self, project: Path
207
+ ) -> None:
208
+ """AC1: gate with file-only and no type → status depends on gate_type logic.
209
+
210
+ Current resolve_gate checks gate_type for skip logic:
211
+ - gate_type == 'manual' → skip
212
+ - gate_type is None → skip
213
+ So file-only gates (no type) currently get 'skip' status.
214
+ This is expected during migration — consumers check gate_file separately.
215
+ """
216
+ result = resolve_gate(
217
+ "106-3", "file-only", "green", project_root=project
218
+ )
219
+ assert result["status"] == "skip"
220
+
221
+
222
+ # ===========================================================================
223
+ # AC1: gate_file null when only gate.type exists
224
+ # ===========================================================================
225
+
226
+
227
+ class TestResolveGateTypeOnlyReturnsNullFile:
228
+ """AC1: gate_file is None when workflow only has gate.type."""
229
+
230
+ def test_gate_file_null_for_type_only(
231
+ self, project: Path
232
+ ) -> None:
233
+ """AC1: gate_file should be None when only gate.type exists."""
234
+ result = resolve_gate(
235
+ "106-3", "legacy", "green", project_root=project
236
+ )
237
+ assert result["gate_file"] is None
238
+
239
+ def test_gate_type_populated_for_type_only(
240
+ self, project: Path
241
+ ) -> None:
242
+ """AC1: gate_type should be 'tests_pass' for type-only gate."""
243
+ result = resolve_gate(
244
+ "106-3", "legacy", "green", project_root=project
245
+ )
246
+ assert result["gate_type"] == "tests_pass"
247
+
248
+
249
+ # ===========================================================================
250
+ # AC2: Backward Compatibility
251
+ # ===========================================================================
252
+
253
+
254
+ class TestResolveGateBackwardCompat:
255
+ """AC2: Workflows with only gate.type continue to work unchanged."""
256
+
257
+ def test_legacy_workflow_status_ready(
258
+ self, project: Path
259
+ ) -> None:
260
+ """AC2: Legacy type-only workflow should return 'ready' with assessment."""
261
+ result = resolve_gate(
262
+ "106-3", "legacy", "green", project_root=project
263
+ )
264
+ assert result["status"] == "ready"
265
+
266
+ def test_legacy_workflow_next_agent(
267
+ self, project: Path
268
+ ) -> None:
269
+ """AC2: Legacy workflow should still resolve next_agent correctly."""
270
+ result = resolve_gate(
271
+ "106-3", "legacy", "green", project_root=project
272
+ )
273
+ assert result["next_agent"] == "sm"
274
+
275
+ def test_legacy_workflow_next_phase(
276
+ self, project: Path
277
+ ) -> None:
278
+ """AC2: Legacy workflow should still resolve next_phase correctly."""
279
+ result = resolve_gate(
280
+ "106-3", "legacy", "green", project_root=project
281
+ )
282
+ assert result["next_phase"] == "finish"
283
+
284
+ def test_resolve_result_contract_unchanged(
285
+ self, project: Path
286
+ ) -> None:
287
+ """AC2: RESOLVE_RESULT still has all 7 required fields."""
288
+ result = resolve_gate(
289
+ "106-3", "legacy", "green", project_root=project
290
+ )
291
+ required_fields = [
292
+ "status",
293
+ "gate_type",
294
+ "gate_file",
295
+ "next_agent",
296
+ "next_phase",
297
+ "assessment_found",
298
+ "error",
299
+ ]
300
+ for field in required_fields:
301
+ assert field in result, f"Missing field: {field}"
302
+
303
+ def test_red_phase_type_only_unaffected(
304
+ self, project: Path
305
+ ) -> None:
306
+ """AC2: TDD red phase (type-only, no file) still works correctly."""
307
+ result = resolve_gate("106-3", "tdd", "red", project_root=project)
308
+ assert result["gate_type"] == "tests_fail"
309
+ assert result["gate_file"] is None
310
+
311
+
312
+ # ===========================================================================
313
+ # AC3: TDD Workflow Migration — verify actual tdd.yaml
314
+ # ===========================================================================
315
+
316
+
317
+ class TestTddWorkflowMigration:
318
+ """AC3: TDD workflow green phase has file: gates/tests-pass.
319
+
320
+ These tests read the ACTUAL tdd.yaml from the project to verify
321
+ the migration has been applied. They will FAIL until the Dev
322
+ updates tdd.yaml.
323
+ """
324
+
325
+ @pytest.fixture
326
+ def tdd_yaml(self) -> dict:
327
+ """Load the actual tdd.yaml from pennyfarthing-dist."""
328
+ tdd_path = (
329
+ Path(__file__).resolve().parents[2]
330
+ / "pennyfarthing-dist"
331
+ / "workflows"
332
+ / "tdd.yaml"
333
+ )
334
+ assert tdd_path.exists(), f"tdd.yaml not found at {tdd_path}"
335
+ return yaml.safe_load(tdd_path.read_text())
336
+
337
+ def _get_phase(self, tdd_yaml: dict, name: str) -> dict:
338
+ """Get a phase by name from the workflow."""
339
+ for phase in tdd_yaml["workflow"]["phases"]:
340
+ if phase["name"] == name:
341
+ return phase
342
+ raise ValueError(f"Phase '{name}' not found")
343
+
344
+ def test_green_phase_has_gate_file(self, tdd_yaml: dict) -> None:
345
+ """AC3: Green phase should have gate.file = 'gates/tests-pass'."""
346
+ green = self._get_phase(tdd_yaml, "green")
347
+ gate = green.get("gate", {})
348
+ assert gate.get("file") == "gates/tests-pass", (
349
+ f"Expected gate.file='gates/tests-pass', got gate={gate}"
350
+ )
351
+
352
+ def test_green_phase_keeps_legacy_type(self, tdd_yaml: dict) -> None:
353
+ """AC3: Green phase should keep gate.type = 'tests_pass' for backward compat."""
354
+ green = self._get_phase(tdd_yaml, "green")
355
+ gate = green.get("gate", {})
356
+ assert gate.get("type") == "tests_pass", (
357
+ f"Expected gate.type='tests_pass', got gate={gate}"
358
+ )
359
+
360
+ def test_green_phase_file_is_relative(self, tdd_yaml: dict) -> None:
361
+ """AC3: File path should be relative (gates/tests-pass), not absolute."""
362
+ green = self._get_phase(tdd_yaml, "green")
363
+ gate = green.get("gate", {})
364
+ file_path = gate.get("file", "")
365
+ assert not file_path.startswith("/"), (
366
+ f"gate.file should be relative, got: {file_path}"
367
+ )
368
+ assert file_path == "gates/tests-pass", (
369
+ f"Expected 'gates/tests-pass', got: {file_path}"
370
+ )
371
+
372
+ def test_red_phase_unchanged(self, tdd_yaml: dict) -> None:
373
+ """AC3: Red phase should NOT have gate.file (backward compat period)."""
374
+ red = self._get_phase(tdd_yaml, "red")
375
+ gate = red.get("gate", {})
376
+ assert "file" not in gate, (
377
+ f"Red phase should not have gate.file yet, got gate={gate}"
378
+ )
379
+
380
+ def test_review_phase_unchanged(self, tdd_yaml: dict) -> None:
381
+ """AC3: Review phase should NOT have gate.file (backward compat period)."""
382
+ review = self._get_phase(tdd_yaml, "review")
383
+ gate = review.get("gate", {})
384
+ assert "file" not in gate, (
385
+ f"Review phase should not have gate.file yet, got gate={gate}"
386
+ )
387
+
388
+ def test_setup_phase_unchanged(self, tdd_yaml: dict) -> None:
389
+ """AC3: Setup phase should remain gateless."""
390
+ setup = self._get_phase(tdd_yaml, "setup")
391
+ assert "gate" not in setup, (
392
+ f"Setup phase should not have a gate, got: {setup}"
393
+ )
394
+
395
+ def test_finish_phase_unchanged(self, tdd_yaml: dict) -> None:
396
+ """AC3: Finish phase should remain gateless."""
397
+ finish = self._get_phase(tdd_yaml, "finish")
398
+ assert "gate" not in finish, (
399
+ f"Finish phase should not have a gate, got: {finish}"
400
+ )
401
+
402
+
403
+ # ===========================================================================
404
+ # AC3: Integration — resolve_gate against actual tdd.yaml
405
+ # ===========================================================================
406
+
407
+
408
+ class TestResolveGateWithRealTddYaml:
409
+ """AC3: resolve_gate returns gate_file when run against actual tdd.yaml.
410
+
411
+ These integration tests use the real workflow file (via the project root)
412
+ to verify the end-to-end gate.file field extraction.
413
+ """
414
+
415
+ @pytest.fixture
416
+ def real_project(self, tmp_path: Path) -> Path:
417
+ """Create a project that uses the actual tdd.yaml from pennyfarthing-dist."""
418
+ tdd_source = (
419
+ Path(__file__).resolve().parents[2]
420
+ / "pennyfarthing-dist"
421
+ / "workflows"
422
+ / "tdd.yaml"
423
+ )
424
+ assert tdd_source.exists(), f"tdd.yaml not found at {tdd_source}"
425
+
426
+ workflows_dir = tmp_path / ".pennyfarthing" / "workflows"
427
+ workflows_dir.mkdir(parents=True)
428
+ (workflows_dir / "tdd.yaml").write_text(tdd_source.read_text())
429
+
430
+ (tmp_path / ".session").mkdir()
431
+ session_file = tmp_path / ".session" / "106-3-session.md"
432
+ session_file.write_text(SESSION_WITH_ASSESSMENT)
433
+ return tmp_path
434
+
435
+ def test_green_phase_returns_gate_file(
436
+ self, real_project: Path
437
+ ) -> None:
438
+ """AC3: resolve_gate for tdd/green should return gate_file='gates/tests-pass'."""
439
+ result = resolve_gate(
440
+ "106-3", "tdd", "green", project_root=real_project
441
+ )
442
+ assert result["gate_file"] == "gates/tests-pass", (
443
+ f"Expected gate_file='gates/tests-pass', got: {result}"
444
+ )
445
+
446
+ def test_green_phase_still_returns_gate_type(
447
+ self, real_project: Path
448
+ ) -> None:
449
+ """AC3: resolve_gate for tdd/green should still return gate_type='tests_pass'."""
450
+ result = resolve_gate(
451
+ "106-3", "tdd", "green", project_root=real_project
452
+ )
453
+ assert result["gate_type"] == "tests_pass", (
454
+ f"Expected gate_type='tests_pass', got: {result}"
455
+ )
456
+
457
+ def test_red_phase_gate_file_is_none(
458
+ self, real_project: Path
459
+ ) -> None:
460
+ """AC3: resolve_gate for tdd/red should have gate_file=None (not migrated yet)."""
461
+ result = resolve_gate(
462
+ "106-3", "tdd", "red", project_root=real_project
463
+ )
464
+ assert result["gate_file"] is None