@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,339 @@
1
+ """Tests for removing handoff subagents and inline fallback — Story 108-2.
2
+
3
+ Epic: 108 (Full Migration & Cleanup)
4
+ Story: 108-2 — Remove handoff subagents and inline fallback
5
+
6
+ Tests the removal of deprecated handoff subagents and the gate.type
7
+ fallback code path, leaving only gate.file-based resolution.
8
+
9
+ Acceptance Criteria:
10
+ - [AC1] pennyfarthing-dist/agents/handoff.md deleted
11
+ - [AC2] pennyfarthing-dist/agents/sm-handoff.md deleted
12
+ - [AC3] gate.type fallback removed from resolve-gate — file-only gates resolved
13
+ - [AC4] checkGate() in Cyclist updated or removed (see TS test)
14
+ - [AC5] No remaining references to handoff/sm-handoff subagents in agent files
15
+ - [AC6] All existing tests pass
16
+ - [AC7] Single code path: all gated phases in phased workflows have gate.file
17
+ """
18
+
19
+ from __future__ import annotations
20
+
21
+ import re
22
+ import textwrap
23
+ from pathlib import Path
24
+
25
+ import pytest
26
+ import yaml
27
+
28
+ from pennyfarthing_scripts.handoff.resolve_gate import resolve_gate
29
+
30
+ # ---------------------------------------------------------------------------
31
+ # Paths
32
+ # ---------------------------------------------------------------------------
33
+
34
+ DIST_DIR = Path(__file__).resolve().parents[2] / "pennyfarthing-dist"
35
+ AGENTS_DIR = DIST_DIR / "agents"
36
+ WORKFLOWS_DIR = DIST_DIR / "workflows"
37
+ GATES_DIR = DIST_DIR / "gates"
38
+
39
+ # ---------------------------------------------------------------------------
40
+ # Fixtures
41
+ # ---------------------------------------------------------------------------
42
+
43
+ SESSION_WITH_ASSESSMENT = textwrap.dedent("""\
44
+ # Story 108-2: Remove handoff subagents and inline fallback
45
+
46
+ **Story ID:** 108-2
47
+ **Workflow:** tdd
48
+ **Phase:** green
49
+ **Phase Started:** 2026-02-15T10:00:00Z
50
+
51
+ ## TEA Assessment
52
+
53
+ **Tests Written:** 5 tests
54
+ **Status:** RED confirmed
55
+ """)
56
+
57
+ SESSION_WITHOUT_ASSESSMENT = textwrap.dedent("""\
58
+ # Story 108-2: Remove handoff subagents and inline fallback
59
+
60
+ **Story ID:** 108-2
61
+ **Workflow:** tdd
62
+ **Phase:** green
63
+ **Phase Started:** 2026-02-15T10:00:00Z
64
+ """)
65
+
66
+ WORKFLOW_FILE_ONLY_GATE = {
67
+ "workflow": {
68
+ "name": "file-only-test",
69
+ "phases": [
70
+ {"name": "setup", "agent": "sm"},
71
+ {
72
+ "name": "green",
73
+ "agent": "dev",
74
+ "gate": {
75
+ "file": "gates/tests-pass",
76
+ "condition": "All tests passing",
77
+ },
78
+ },
79
+ {"name": "finish", "agent": "sm"},
80
+ ],
81
+ }
82
+ }
83
+
84
+
85
+ @pytest.fixture
86
+ def project(tmp_path: Path) -> Path:
87
+ """Create a minimal project with workflow YAMLs and session."""
88
+ workflows_dir = tmp_path / ".pennyfarthing" / "workflows"
89
+ workflows_dir.mkdir(parents=True)
90
+ (tmp_path / ".session").mkdir()
91
+
92
+ # Write the file-only-gate workflow
93
+ (workflows_dir / "file-only-test.yaml").write_text(
94
+ yaml.dump(WORKFLOW_FILE_ONLY_GATE, default_flow_style=False)
95
+ )
96
+
97
+ # Copy real tdd.yaml for integration tests
98
+ tdd_src = WORKFLOWS_DIR / "tdd.yaml"
99
+ if tdd_src.exists():
100
+ (workflows_dir / "tdd.yaml").write_text(tdd_src.read_text())
101
+
102
+ return tmp_path
103
+
104
+
105
+ # ===========================================================================
106
+ # AC1: pennyfarthing-dist/agents/handoff.md deleted
107
+ # ===========================================================================
108
+
109
+
110
+ class TestHandoffMdDeleted:
111
+ """AC1: handoff.md must not exist in pennyfarthing-dist/agents/."""
112
+
113
+ def test_handoff_md_does_not_exist(self) -> None:
114
+ """AC1: handoff.md should be deleted from agents directory."""
115
+ assert not (AGENTS_DIR / "handoff.md").exists(), (
116
+ "handoff.md still exists — should be deleted in 108-2"
117
+ )
118
+
119
+
120
+ # ===========================================================================
121
+ # AC2: pennyfarthing-dist/agents/sm-handoff.md deleted
122
+ # ===========================================================================
123
+
124
+
125
+ class TestSmHandoffMdDeleted:
126
+ """AC2: sm-handoff.md must not exist in pennyfarthing-dist/agents/."""
127
+
128
+ def test_sm_handoff_md_does_not_exist(self) -> None:
129
+ """AC2: sm-handoff.md should be deleted from agents directory."""
130
+ assert not (AGENTS_DIR / "sm-handoff.md").exists(), (
131
+ "sm-handoff.md still exists — should be deleted in 108-2"
132
+ )
133
+
134
+
135
+ # ===========================================================================
136
+ # AC3: gate.type fallback removed — file-only gates resolve properly
137
+ # ===========================================================================
138
+
139
+
140
+ class TestGateTypeFallbackRemoved:
141
+ """AC3: gate with file but no type should resolve via assessment, not skip."""
142
+
143
+ def test_no_gate_at_all_still_returns_skip(
144
+ self, project: Path
145
+ ) -> None:
146
+ """AC3: Phase with no gate (setup/finish) should still return 'skip'."""
147
+ session = project / ".session" / "108-2-session.md"
148
+ session.write_text(SESSION_WITH_ASSESSMENT)
149
+
150
+ result = resolve_gate(
151
+ "108-2", "tdd", "setup", project_root=project
152
+ )
153
+ assert result["status"] == "skip", (
154
+ f"No-gate phase should skip, got: {result['status']}"
155
+ )
156
+
157
+ def test_gate_file_only_returns_ready_with_assessment(
158
+ self, project: Path
159
+ ) -> None:
160
+ """AC3: gate with file but no type + assessment → 'ready' (not 'skip').
161
+
162
+ Before 108-2: gate_type is None → skip (fallback).
163
+ After 108-2: gate.file present → check assessment → ready.
164
+ """
165
+ session = project / ".session" / "108-2-session.md"
166
+ session.write_text(SESSION_WITH_ASSESSMENT)
167
+
168
+ result = resolve_gate(
169
+ "108-2", "file-only-test", "green", project_root=project
170
+ )
171
+ assert result["status"] == "ready", (
172
+ f"File-only gate with assessment should be 'ready', got: "
173
+ f"'{result['status']}' — gate.type fallback still active?"
174
+ )
175
+
176
+ def test_gate_file_only_returns_blocked_without_assessment(
177
+ self, project: Path
178
+ ) -> None:
179
+ """AC3: gate with file but no type + NO assessment → 'blocked'.
180
+
181
+ Before 108-2: gate_type is None → skip (ignores missing assessment).
182
+ After 108-2: gate.file present → check assessment → blocked.
183
+ """
184
+ session = project / ".session" / "108-2-session.md"
185
+ session.write_text(SESSION_WITHOUT_ASSESSMENT)
186
+
187
+ result = resolve_gate(
188
+ "108-2", "file-only-test", "green", project_root=project
189
+ )
190
+ assert result["status"] == "blocked", (
191
+ f"File-only gate without assessment should be 'blocked', got: "
192
+ f"'{result['status']}' — gate.type fallback still active?"
193
+ )
194
+
195
+ def test_gate_file_only_populates_gate_file_field(
196
+ self, project: Path
197
+ ) -> None:
198
+ """AC3: gate_file field should be populated for file-only gates."""
199
+ session = project / ".session" / "108-2-session.md"
200
+ session.write_text(SESSION_WITH_ASSESSMENT)
201
+
202
+ result = resolve_gate(
203
+ "108-2", "file-only-test", "green", project_root=project
204
+ )
205
+ assert result["gate_file"] == "gates/tests-pass", (
206
+ f"Expected gate_file='gates/tests-pass', got: {result['gate_file']}"
207
+ )
208
+
209
+ def test_gate_with_both_file_and_type_still_ready(
210
+ self, project: Path
211
+ ) -> None:
212
+ """AC3: gate with both file and type should still return 'ready'."""
213
+ session = project / ".session" / "108-2-session.md"
214
+ session.write_text(SESSION_WITH_ASSESSMENT)
215
+
216
+ result = resolve_gate(
217
+ "108-2", "tdd", "green", project_root=project
218
+ )
219
+ assert result["status"] == "ready"
220
+ assert result["gate_file"] == "gates/tests-pass"
221
+ assert result["gate_type"] == "tests_pass"
222
+
223
+
224
+ # ===========================================================================
225
+ # AC5: No remaining references to handoff/sm-handoff subagents
226
+ # ===========================================================================
227
+
228
+
229
+ class TestNoSubagentReferences:
230
+ """AC5: No agent files should reference the deleted subagents."""
231
+
232
+ def _scan_agent_files(self, pattern: str) -> list[tuple[str, str]]:
233
+ """Scan all agent .md files for a regex pattern. Returns (file, line) pairs."""
234
+ hits = []
235
+ for md_file in AGENTS_DIR.glob("*.md"):
236
+ # Skip the files being deleted (they'll be gone)
237
+ if md_file.name in ("handoff.md", "sm-handoff.md"):
238
+ continue
239
+ content = md_file.read_text()
240
+ for line in content.splitlines():
241
+ if re.search(pattern, line, re.IGNORECASE):
242
+ hits.append((md_file.name, line.strip()))
243
+ return hits
244
+
245
+ def test_no_agent_references_handoff_subagent(self) -> None:
246
+ """AC5: No agent .md files should spawn or reference 'handoff' subagent.
247
+
248
+ Matches patterns like:
249
+ - subagent_type: handoff
250
+ - spawn handoff
251
+ - sm-handoff subagent
252
+ But NOT: 'pf handoff' (the CLI) or 'handoff.ts' (the module)
253
+ """
254
+ # Look for subagent spawn references to the deprecated handoff subagent
255
+ hits = self._scan_agent_files(
256
+ r'(?:subagent.*handoff\.md|sm-handoff\.md|"sm-handoff"|"handoff"'
257
+ r"|handoff subagent|sm-handoff subagent)"
258
+ )
259
+ assert len(hits) == 0, (
260
+ f"Found {len(hits)} references to deprecated handoff subagents:\n"
261
+ + "\n".join(f" {f}: {line}" for f, line in hits)
262
+ )
263
+
264
+ def test_agents_readme_no_deprecated_entries(self) -> None:
265
+ """AC5: README.md should not list handoff.md or sm-handoff.md."""
266
+ readme = AGENTS_DIR / "README.md"
267
+ if not readme.exists():
268
+ pytest.skip("No agents README.md")
269
+
270
+ content = readme.read_text()
271
+ # Should not have the deprecated entries
272
+ assert "handoff.md" not in content, (
273
+ "README.md still references handoff.md"
274
+ )
275
+ assert "sm-handoff.md" not in content, (
276
+ "README.md still references sm-handoff.md"
277
+ )
278
+
279
+
280
+ # ===========================================================================
281
+ # AC7: Single code path — all gated phases have gate.file
282
+ # ===========================================================================
283
+
284
+ # Phased workflows that should have gate.file on every gated phase
285
+ PHASED_WORKFLOWS = ["tdd", "trivial", "bdd", "bdd-tandem", "tdd-tandem",
286
+ "2party-tdd", "agent-docs", "patch"]
287
+
288
+
289
+ class TestAllGatedPhasesHaveGateFile:
290
+ """AC7: Every gated phase in phased workflows must have gate.file."""
291
+
292
+ def _load_workflow(self, name: str) -> dict | None:
293
+ """Load a workflow YAML, return None if not found."""
294
+ path = WORKFLOWS_DIR / f"{name}.yaml"
295
+ if not path.exists():
296
+ return None
297
+ return yaml.safe_load(path.read_text())
298
+
299
+ def _gated_phases_missing_file(
300
+ self, workflow_data: dict
301
+ ) -> list[tuple[str, str]]:
302
+ """Find phases that have a gate but no gate.file.
303
+
304
+ Returns list of (phase_name, gate_type) tuples.
305
+ Manual gates are excluded — they skip resolution entirely.
306
+ """
307
+ missing = []
308
+ for phase in workflow_data["workflow"]["phases"]:
309
+ gate = phase.get("gate")
310
+ if not gate:
311
+ continue # No gate at all — fine (setup, finish)
312
+ gate_type = gate.get("type")
313
+ if gate_type == "manual":
314
+ continue # Manual gates always skip
315
+ if not gate.get("file"):
316
+ missing.append((phase["name"], gate_type or "(no type)"))
317
+ return missing
318
+
319
+ @pytest.mark.parametrize("workflow_name", PHASED_WORKFLOWS)
320
+ def test_workflow_gated_phases_have_gate_file(
321
+ self, workflow_name: str
322
+ ) -> None:
323
+ """AC7: Every non-manual gated phase must have gate.file defined.
324
+
325
+ This ensures the single code path — resolve-gate only needs
326
+ gate.file, not gate.type, for resolution.
327
+ """
328
+ wf = self._load_workflow(workflow_name)
329
+ if wf is None:
330
+ pytest.skip(f"Workflow {workflow_name} not found")
331
+
332
+ missing = self._gated_phases_missing_file(wf)
333
+ assert len(missing) == 0, (
334
+ f"Workflow '{workflow_name}' has gated phases without gate.file:\n"
335
+ + "\n".join(
336
+ f" - {name} (type={gtype})" for name, gtype in missing
337
+ )
338
+ + "\nAll gated phases must have gate.file for single code path."
339
+ )
@@ -0,0 +1,253 @@
1
+ """Tests for SM confidence gate evaluation results — Story 90-3.
2
+
3
+ Epic: 90 (Confidence Circuit Breaker via Gate)
4
+ Story: 90-3 — Evaluate and document results
5
+
6
+ Tests the evaluation document that assesses the SM confidence gate's
7
+ effectiveness after deployment. The gate (confidence-sm.md) was shipped
8
+ in story 90-2. This story evaluates trigger frequency, wrong-approach
9
+ reduction, user experience, and makes a rollout recommendation.
10
+
11
+ Acceptance Criteria:
12
+ - [AC1] Evaluate trigger frequency after 1 sprint
13
+ - [AC2] Document whether it reduced wrong-approach incidents
14
+ - [AC3] Assess if the gate was annoying (user overrode or dismissed)
15
+ - [AC4] Document findings in a results file
16
+ - [AC5] Decide on rollout recommendation to other agents
17
+ """
18
+
19
+ from __future__ import annotations
20
+
21
+ import re
22
+ from pathlib import Path
23
+
24
+ import pytest
25
+
26
+ # ---------------------------------------------------------------------------
27
+ # Path resolution
28
+ # ---------------------------------------------------------------------------
29
+
30
+ _THIS_DIR = Path(__file__).resolve().parent
31
+ _SCRIPTS_DIR = _THIS_DIR.parent # pennyfarthing_scripts/
32
+ _FRAMEWORK_ROOT = _SCRIPTS_DIR.parent # pennyfarthing/
33
+ _EVAL_FILE = (
34
+ _FRAMEWORK_ROOT
35
+ / "pennyfarthing-dist"
36
+ / "gates"
37
+ / "evaluations"
38
+ / "confidence-sm.md"
39
+ )
40
+
41
+ # ---------------------------------------------------------------------------
42
+ # Fixtures
43
+ # ---------------------------------------------------------------------------
44
+
45
+
46
+ @pytest.fixture
47
+ def eval_path() -> Path:
48
+ """Return the expected path to the evaluation results file."""
49
+ return _EVAL_FILE
50
+
51
+
52
+ @pytest.fixture
53
+ def eval_content(eval_path: Path) -> str:
54
+ """Read and return the evaluation file content. Fails if file missing."""
55
+ assert eval_path.is_file(), f"Evaluation file not found: {eval_path}"
56
+ return eval_path.read_text()
57
+
58
+
59
+ # ===========================================================================
60
+ # AC1: Evaluate trigger frequency after 1 sprint
61
+ # ===========================================================================
62
+
63
+
64
+ class TestTriggerFrequency:
65
+ """AC1: Evaluation documents how often the gate triggered."""
66
+
67
+ def test_eval_has_trigger_section(self, eval_content: str) -> None:
68
+ """AC1: Evaluation has a section about trigger frequency."""
69
+ assert re.search(
70
+ r"(?i)trigger.*frequen|frequen.*trigger|how often|activation.*rate",
71
+ eval_content,
72
+ ), "Missing trigger frequency section"
73
+
74
+ def test_trigger_section_has_data(self, eval_content: str) -> None:
75
+ """AC1: Trigger section includes specific data or baseline note."""
76
+ content_lower = eval_content.lower()
77
+ assert any(
78
+ term in content_lower
79
+ for term in [
80
+ "triggered",
81
+ "fired",
82
+ "activated",
83
+ "invoked",
84
+ "n/a",
85
+ "baseline",
86
+ "times",
87
+ "occurrences",
88
+ ]
89
+ ), "Trigger section lacks frequency data or baseline acknowledgment"
90
+
91
+
92
+ # ===========================================================================
93
+ # AC2: Document whether it reduced wrong-approach incidents
94
+ # ===========================================================================
95
+
96
+
97
+ class TestWrongApproachReduction:
98
+ """AC2: Evaluation documents impact on wrong-approach incidents."""
99
+
100
+ def test_eval_has_wrong_approach_section(self, eval_content: str) -> None:
101
+ """AC2: Evaluation has a section about wrong-approach incidents."""
102
+ content_lower = eval_content.lower()
103
+ assert any(
104
+ term in content_lower
105
+ for term in [
106
+ "wrong-approach",
107
+ "wrong approach",
108
+ "incident",
109
+ "misroute",
110
+ "incorrect action",
111
+ "ambiguous",
112
+ ]
113
+ ), "Missing wrong-approach reduction section"
114
+
115
+ def test_wrong_approach_has_comparison(self, eval_content: str) -> None:
116
+ """AC2: Wrong-approach section includes comparison or baseline note."""
117
+ content_lower = eval_content.lower()
118
+ assert any(
119
+ term in content_lower
120
+ for term in [
121
+ "before",
122
+ "after",
123
+ "baseline",
124
+ "reduced",
125
+ "comparison",
126
+ "prior",
127
+ "improvement",
128
+ "no data yet",
129
+ ]
130
+ ), "Wrong-approach section lacks before/after comparison or baseline"
131
+
132
+
133
+ # ===========================================================================
134
+ # AC3: Assess if the gate was annoying
135
+ # ===========================================================================
136
+
137
+
138
+ class TestUserExperience:
139
+ """AC3: Evaluation assesses user experience with the gate."""
140
+
141
+ def test_eval_has_ux_section(self, eval_content: str) -> None:
142
+ """AC3: Evaluation has a section about user experience."""
143
+ content_lower = eval_content.lower()
144
+ assert any(
145
+ term in content_lower
146
+ for term in [
147
+ "user experience",
148
+ "annoying",
149
+ "friction",
150
+ "override",
151
+ "dismiss",
152
+ "usability",
153
+ ]
154
+ ), "Missing user experience assessment section"
155
+
156
+ def test_ux_mentions_override_behavior(self, eval_content: str) -> None:
157
+ """AC3: UX section addresses override/dismissal behavior."""
158
+ content_lower = eval_content.lower()
159
+ assert any(
160
+ term in content_lower
161
+ for term in ["override", "dismiss", "bypass", "skip", "ignored"]
162
+ ), "UX section doesn't address override/dismissal behavior"
163
+
164
+
165
+ # ===========================================================================
166
+ # AC4: Document findings in a results file
167
+ # ===========================================================================
168
+
169
+
170
+ class TestResultsFile:
171
+ """AC4: Evaluation results file exists with proper structure."""
172
+
173
+ def test_eval_file_exists(self, eval_path: Path) -> None:
174
+ """AC4: Evaluation results file exists at expected location."""
175
+ assert eval_path.is_file(), f"Evaluation file not found: {eval_path}"
176
+
177
+ def test_eval_file_not_empty(self, eval_path: Path) -> None:
178
+ """AC4: Evaluation file is not empty."""
179
+ assert eval_path.is_file(), f"File not found: {eval_path}"
180
+ content = eval_path.read_text()
181
+ assert len(content.strip()) > 0, "Evaluation file is empty"
182
+
183
+ def test_eval_has_title(self, eval_content: str) -> None:
184
+ """AC4: Evaluation has a title heading."""
185
+ assert re.search(r"^#\s+", eval_content, re.MULTILINE), (
186
+ "Evaluation file missing title heading"
187
+ )
188
+
189
+ def test_eval_has_structured_sections(self, eval_content: str) -> None:
190
+ """AC4: Evaluation has multiple sections (at least 3 headings)."""
191
+ headings = re.findall(r"^##\s+", eval_content, re.MULTILINE)
192
+ assert len(headings) >= 3, (
193
+ f"Expected at least 3 sections, found {len(headings)}"
194
+ )
195
+
196
+ def test_eval_references_gate(self, eval_content: str) -> None:
197
+ """AC4: Evaluation references the confidence-sm gate."""
198
+ assert "confidence-sm" in eval_content, (
199
+ "Evaluation doesn't reference the confidence-sm gate"
200
+ )
201
+
202
+
203
+ # ===========================================================================
204
+ # AC5: Decide on rollout recommendation to other agents
205
+ # ===========================================================================
206
+
207
+
208
+ class TestRolloutRecommendation:
209
+ """AC5: Evaluation includes a rollout recommendation."""
210
+
211
+ def test_eval_has_recommendation_section(self, eval_content: str) -> None:
212
+ """AC5: Evaluation has a rollout recommendation section."""
213
+ content_lower = eval_content.lower()
214
+ assert any(
215
+ term in content_lower
216
+ for term in ["recommendation", "rollout", "next steps", "decision"]
217
+ ), "Missing rollout recommendation section"
218
+
219
+ def test_recommendation_is_actionable(self, eval_content: str) -> None:
220
+ """AC5: Recommendation states a clear actionable decision."""
221
+ content_lower = eval_content.lower()
222
+ assert any(
223
+ term in content_lower
224
+ for term in [
225
+ "expand",
226
+ "hold",
227
+ "remove",
228
+ "proceed",
229
+ "defer",
230
+ "recommend",
231
+ "adopt",
232
+ "extend",
233
+ ]
234
+ ), "Recommendation lacks clear actionable decision"
235
+
236
+ def test_recommendation_mentions_other_agents(
237
+ self, eval_content: str
238
+ ) -> None:
239
+ """AC5: Recommendation addresses rollout to other agents."""
240
+ content_lower = eval_content.lower()
241
+ assert any(
242
+ term in content_lower
243
+ for term in [
244
+ "other agent",
245
+ "additional agent",
246
+ "tea",
247
+ "dev",
248
+ "reviewer",
249
+ "all agents",
250
+ "agent-specific",
251
+ "per-agent",
252
+ ]
253
+ ), "Recommendation doesn't address rollout to other agents"