@pennyfarthing/core 11.2.1 → 11.3.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 (405) hide show
  1. package/README.md +102 -42
  2. package/package.json +1 -1
  3. package/packages/core/dist/cli/commands/doctor-legacy.test.js +2 -2
  4. package/packages/core/dist/cli/commands/doctor-legacy.test.js.map +1 -1
  5. package/packages/core/dist/cli/commands/doctor.d.ts +55 -0
  6. package/packages/core/dist/cli/commands/doctor.d.ts.map +1 -1
  7. package/packages/core/dist/cli/commands/doctor.js +324 -50
  8. package/packages/core/dist/cli/commands/doctor.js.map +1 -1
  9. package/packages/core/dist/cli/commands/init.d.ts +12 -0
  10. package/packages/core/dist/cli/commands/init.d.ts.map +1 -1
  11. package/packages/core/dist/cli/commands/init.js +45 -0
  12. package/packages/core/dist/cli/commands/init.js.map +1 -1
  13. package/packages/core/dist/cli/commands/pyproject-install.test.d.ts +19 -0
  14. package/packages/core/dist/cli/commands/pyproject-install.test.d.ts.map +1 -0
  15. package/packages/core/dist/cli/commands/pyproject-install.test.js +261 -0
  16. package/packages/core/dist/cli/commands/pyproject-install.test.js.map +1 -0
  17. package/packages/core/dist/cli/commands/stale-artifacts-cleanup.test.d.ts +17 -0
  18. package/packages/core/dist/cli/commands/stale-artifacts-cleanup.test.d.ts.map +1 -0
  19. package/packages/core/dist/cli/commands/stale-artifacts-cleanup.test.js +470 -0
  20. package/packages/core/dist/cli/commands/stale-artifacts-cleanup.test.js.map +1 -0
  21. package/packages/core/dist/cli/commands/update-consolidation.test.js +14 -6
  22. package/packages/core/dist/cli/commands/update-consolidation.test.js.map +1 -1
  23. package/packages/core/dist/cli/commands/update.d.ts.map +1 -1
  24. package/packages/core/dist/cli/commands/update.js +31 -2
  25. package/packages/core/dist/cli/commands/update.js.map +1 -1
  26. package/packages/core/dist/cli/index.js +2 -0
  27. package/packages/core/dist/cli/index.js.map +1 -1
  28. package/packages/core/dist/cli/utils/python.d.ts.map +1 -1
  29. package/packages/core/dist/cli/utils/python.js +11 -0
  30. package/packages/core/dist/cli/utils/python.js.map +1 -1
  31. package/packages/core/dist/cli/utils/settings-hook-migration.test.d.ts +17 -0
  32. package/packages/core/dist/cli/utils/settings-hook-migration.test.d.ts.map +1 -0
  33. package/packages/core/dist/cli/utils/settings-hook-migration.test.js +382 -0
  34. package/packages/core/dist/cli/utils/settings-hook-migration.test.js.map +1 -0
  35. package/packages/core/dist/cli/utils/settings.d.ts +0 -4
  36. package/packages/core/dist/cli/utils/settings.d.ts.map +1 -1
  37. package/packages/core/dist/cli/utils/settings.js +45 -27
  38. package/packages/core/dist/cli/utils/settings.js.map +1 -1
  39. package/packages/core/dist/cli/utils/stale-artifacts.d.ts +59 -0
  40. package/packages/core/dist/cli/utils/stale-artifacts.d.ts.map +1 -0
  41. package/packages/core/dist/cli/utils/stale-artifacts.js +163 -0
  42. package/packages/core/dist/cli/utils/stale-artifacts.js.map +1 -0
  43. package/packages/core/dist/consultation/dialogue-manager.d.ts +1 -1
  44. package/packages/core/dist/consultation/dialogue-manager.d.ts.map +1 -1
  45. package/packages/core/dist/consultation/dialogue-manager.js +1 -1
  46. package/packages/core/dist/consultation/dialogue-manager.js.map +1 -1
  47. package/packages/core/dist/consultation/dialogue-manager.test.js.map +1 -1
  48. package/packages/core/dist/consultation/tandem-metrics.test.js.map +1 -1
  49. package/packages/core/dist/public/css/react.css +1 -1
  50. package/packages/core/dist/public/js/react/react.js +9 -9
  51. package/packages/core/dist/server/api/git.d.ts.map +1 -1
  52. package/packages/core/dist/server/api/git.js +0 -1
  53. package/packages/core/dist/server/api/git.js.map +1 -1
  54. package/packages/core/dist/server/api/index.d.ts +2 -0
  55. package/packages/core/dist/server/api/index.d.ts.map +1 -1
  56. package/packages/core/dist/server/api/index.js +2 -0
  57. package/packages/core/dist/server/api/index.js.map +1 -1
  58. package/packages/core/dist/server/api/project-info.d.ts +11 -0
  59. package/packages/core/dist/server/api/project-info.d.ts.map +1 -0
  60. package/packages/core/dist/server/api/project-info.js +18 -0
  61. package/packages/core/dist/server/api/project-info.js.map +1 -0
  62. package/packages/core/dist/server/otlp-receiver.d.ts.map +1 -1
  63. package/packages/core/dist/server/otlp-receiver.js +18 -1
  64. package/packages/core/dist/server/otlp-receiver.js.map +1 -1
  65. package/packages/core/dist/server/otlp-receiver.test.js +1 -1
  66. package/packages/core/dist/server/otlp-receiver.test.js.map +1 -1
  67. package/packages/core/dist/server/server.d.ts.map +1 -1
  68. package/packages/core/dist/server/server.js +3 -2
  69. package/packages/core/dist/server/server.js.map +1 -1
  70. package/packages/core/dist/server/server.test.d.ts +1 -1
  71. package/packages/core/dist/server/server.test.js +8 -8
  72. package/packages/core/dist/server/settings.d.ts +1 -0
  73. package/packages/core/dist/server/settings.d.ts.map +1 -1
  74. package/packages/core/dist/server/settings.js +18 -0
  75. package/packages/core/dist/server/settings.js.map +1 -1
  76. package/packages/core/dist/workflow/tandem-workflow-templates.test.js +7 -5
  77. package/packages/core/dist/workflow/tandem-workflow-templates.test.js.map +1 -1
  78. package/packages/core/dist/workflow/workflow-migration.test.js +6 -5
  79. package/packages/core/dist/workflow/workflow-migration.test.js.map +1 -1
  80. package/packages/core/dist/workflow/workflow-team-templates.test.d.ts +17 -0
  81. package/packages/core/dist/workflow/workflow-team-templates.test.d.ts.map +1 -0
  82. package/packages/core/dist/workflow/workflow-team-templates.test.js +275 -0
  83. package/packages/core/dist/workflow/workflow-team-templates.test.js.map +1 -0
  84. package/pennyfarthing-dist/agents/dev.md +19 -4
  85. package/pennyfarthing-dist/agents/devops.md +2 -10
  86. package/pennyfarthing-dist/agents/reviewer-preflight.md +4 -5
  87. package/pennyfarthing-dist/agents/reviewer.md +17 -4
  88. package/pennyfarthing-dist/agents/sm-finish.md +1 -1
  89. package/pennyfarthing-dist/agents/sm-setup.md +7 -7
  90. package/pennyfarthing-dist/agents/sm.md +16 -29
  91. package/pennyfarthing-dist/agents/tea.md +2 -2
  92. package/pennyfarthing-dist/agents/testing-runner.md +1 -1
  93. package/pennyfarthing-dist/commands/pf-architect.md +1 -1
  94. package/pennyfarthing-dist/commands/pf-ba.md +1 -1
  95. package/pennyfarthing-dist/commands/pf-chore.md +2 -2
  96. package/pennyfarthing-dist/commands/pf-dev.md +1 -1
  97. package/pennyfarthing-dist/commands/pf-devops.md +1 -1
  98. package/pennyfarthing-dist/commands/pf-epic.md +6 -6
  99. package/pennyfarthing-dist/commands/pf-git.md +10 -10
  100. package/pennyfarthing-dist/commands/pf-health-check.md +31 -12
  101. package/pennyfarthing-dist/commands/pf-help.md +12 -12
  102. package/pennyfarthing-dist/commands/pf-orchestrator.md +1 -1
  103. package/pennyfarthing-dist/commands/pf-pm.md +1 -1
  104. package/pennyfarthing-dist/commands/pf-prime.md +8 -8
  105. package/pennyfarthing-dist/commands/pf-reviewer.md +1 -1
  106. package/pennyfarthing-dist/commands/pf-session.md +7 -7
  107. package/pennyfarthing-dist/commands/pf-sm.md +1 -1
  108. package/pennyfarthing-dist/commands/pf-sprint.md +7 -7
  109. package/pennyfarthing-dist/commands/pf-tea.md +1 -1
  110. package/pennyfarthing-dist/commands/pf-tech-writer.md +1 -1
  111. package/pennyfarthing-dist/commands/pf-theme.md +9 -9
  112. package/pennyfarthing-dist/commands/pf-ux-designer.md +1 -1
  113. package/pennyfarthing-dist/commands/pf-work.md +1 -1
  114. package/pennyfarthing-dist/gates/{confidence-sm.md → confidence.md} +16 -17
  115. package/pennyfarthing-dist/gates/dev-exit.md +75 -0
  116. package/pennyfarthing-dist/gates/merge-ready.md +49 -0
  117. package/pennyfarthing-dist/gates/release-ready.md +95 -0
  118. package/pennyfarthing-dist/gates/reviewer-preflight-check.md +90 -0
  119. package/pennyfarthing-dist/gates/sm-setup-exit.md +82 -0
  120. package/pennyfarthing-dist/guides/agent-behavior.md +129 -20
  121. package/pennyfarthing-dist/guides/agent-coordination.md +10 -10
  122. package/pennyfarthing-dist/guides/agent-tag-taxonomy.md +6 -6
  123. package/pennyfarthing-dist/guides/agent-template-tactical.md +1 -1
  124. package/pennyfarthing-dist/guides/bell-mode.md +1 -1
  125. package/pennyfarthing-dist/guides/bikerack.md +10 -10
  126. package/pennyfarthing-dist/guides/brownfield-tools.md +24 -24
  127. package/pennyfarthing-dist/guides/command-tag-taxonomy.md +1 -1
  128. package/pennyfarthing-dist/guides/gate-schema.md +2 -2
  129. package/pennyfarthing-dist/guides/gates.md +10 -5
  130. package/pennyfarthing-dist/guides/handoff-cli.md +8 -8
  131. package/pennyfarthing-dist/guides/hooks.md +27 -27
  132. package/pennyfarthing-dist/guides/prime.md +2 -2
  133. package/pennyfarthing-dist/guides/reflector.md +1 -1
  134. package/pennyfarthing-dist/guides/skill-schema.md +6 -6
  135. package/pennyfarthing-dist/guides/tandem-protocol.md +3 -3
  136. package/pennyfarthing-dist/guides/workflow-schema.md +1 -1
  137. package/pennyfarthing-dist/guides/worktree-mode.md +3 -3
  138. package/pennyfarthing-dist/guides/xml-tags.md +8 -8
  139. package/pennyfarthing-dist/scripts/README.md +4 -4
  140. package/pennyfarthing-dist/scripts/core/agent-session.sh +2 -5
  141. package/pennyfarthing-dist/scripts/core/check-context.sh +1 -1
  142. package/pennyfarthing-dist/scripts/core/pf.sh +5 -0
  143. package/pennyfarthing-dist/scripts/core/phase-check-start.sh +2 -5
  144. package/pennyfarthing-dist/scripts/core/prime.sh +2 -25
  145. package/pennyfarthing-dist/scripts/git/README.md +14 -14
  146. package/pennyfarthing-dist/scripts/git/create-feature-branches.sh +2 -3
  147. package/pennyfarthing-dist/scripts/git/git-status-all.sh +2 -3
  148. package/pennyfarthing-dist/scripts/git/install-git-hooks.sh +2 -3
  149. package/pennyfarthing-dist/scripts/git/worktree-manager.sh +2 -4
  150. package/pennyfarthing-dist/scripts/hooks/README.md +6 -6
  151. package/pennyfarthing-dist/scripts/hooks/bell-mode-hook.sh +3 -3
  152. package/pennyfarthing-dist/scripts/hooks/context-circuit-breaker.sh +3 -3
  153. package/pennyfarthing-dist/scripts/hooks/context-warning.sh +3 -3
  154. package/pennyfarthing-dist/scripts/hooks/cyclist-pretooluse-hook.sh +3 -3
  155. package/pennyfarthing-dist/scripts/hooks/otel-auto-config.sh +5 -4
  156. package/pennyfarthing-dist/scripts/hooks/pre-commit.sh +2 -1
  157. package/pennyfarthing-dist/scripts/hooks/pre-edit-check.sh +3 -3
  158. package/pennyfarthing-dist/scripts/hooks/question-reflector-check.sh +3 -3
  159. package/pennyfarthing-dist/scripts/hooks/schema-validation.sh +3 -3
  160. package/pennyfarthing-dist/scripts/hooks/session-start.sh +3 -3
  161. package/pennyfarthing-dist/scripts/hooks/session-stop.sh +3 -3
  162. package/pennyfarthing-dist/scripts/hooks/sprint-yaml-validation.sh +3 -3
  163. package/pennyfarthing-dist/scripts/hooks/welcome-hook.sh +3 -4
  164. package/pennyfarthing-dist/scripts/lib/env.sh +34 -0
  165. package/pennyfarthing-dist/scripts/lib/find-root.sh +5 -0
  166. package/pennyfarthing-dist/scripts/lib/run-pf.sh +43 -0
  167. package/pennyfarthing-dist/scripts/misc/README.md +1 -1
  168. package/pennyfarthing-dist/scripts/misc/statusline.sh +3 -3
  169. package/pennyfarthing-dist/scripts/sprint/README.md +21 -21
  170. package/pennyfarthing-dist/scripts/workflow/README.md +2 -2
  171. package/pennyfarthing-dist/scripts/workflow/finish-story.sh +2 -16
  172. package/pennyfarthing-dist/scripts/workflow/fix-session-phase.sh +3 -3
  173. package/pennyfarthing-dist/scripts/workflow/get-workflow-type.sh +3 -3
  174. package/pennyfarthing-dist/scripts/workflow/list-workflows.sh +3 -3
  175. package/pennyfarthing-dist/scripts/workflow/phase-owner.sh +3 -3
  176. package/pennyfarthing-dist/scripts/workflow/resume-workflow.sh +3 -3
  177. package/pennyfarthing-dist/scripts/workflow/show-workflow.sh +3 -3
  178. package/pennyfarthing-dist/scripts/workflow/start-workflow.sh +3 -3
  179. package/pennyfarthing-dist/scripts/workflow/workflow-status.sh +3 -3
  180. package/pennyfarthing-dist/skills/pf-bc/examples.md +23 -23
  181. package/pennyfarthing-dist/skills/pf-bc/skill.md +17 -17
  182. package/pennyfarthing-dist/skills/pf-bc/usage.md +8 -8
  183. package/pennyfarthing-dist/skills/pf-jira/SKILL.md +15 -15
  184. package/pennyfarthing-dist/skills/pf-jira/examples.md +48 -48
  185. package/pennyfarthing-dist/skills/pf-jira/usage.md +15 -15
  186. package/pennyfarthing-dist/skills/pf-settings/skill.md +42 -0
  187. package/pennyfarthing-dist/skills/pf-sprint/examples.md +80 -80
  188. package/pennyfarthing-dist/skills/pf-sprint/skill.md +35 -35
  189. package/pennyfarthing-dist/skills/pf-sprint/usage.md +30 -30
  190. package/pennyfarthing-dist/skills/pf-theme/examples.md +15 -15
  191. package/pennyfarthing-dist/skills/pf-theme/skill.md +6 -6
  192. package/pennyfarthing-dist/skills/pf-theme/usage.md +5 -5
  193. package/pennyfarthing-dist/skills/pf-workflow/examples.md +27 -27
  194. package/pennyfarthing-dist/skills/pf-workflow/skill.md +11 -11
  195. package/pennyfarthing-dist/skills/pf-workflow/usage.md +11 -11
  196. package/pennyfarthing-dist/skills/skill-registry.yaml +34 -19
  197. package/pennyfarthing-dist/templates/pyproject.toml +27 -0
  198. package/pennyfarthing-dist/templates/settings.local.json.template +11 -11
  199. package/pennyfarthing-dist/workflows/bdd-tandem.yaml +7 -3
  200. package/pennyfarthing-dist/workflows/bdd-team.yaml +89 -0
  201. package/pennyfarthing-dist/workflows/bdd.yaml +7 -3
  202. package/pennyfarthing-dist/workflows/epics-and-stories/steps/step-05-import-to-future.md +1 -1
  203. package/pennyfarthing-dist/workflows/git-cleanup/steps/step-01-analyze.md +1 -1
  204. package/pennyfarthing-dist/workflows/git-cleanup/steps/step-04-verify.md +1 -1
  205. package/pennyfarthing-dist/workflows/git-cleanup/steps/step-05-complete.md +1 -1
  206. package/pennyfarthing-dist/workflows/installation-check/steps/step-01-foundation.md +77 -0
  207. package/pennyfarthing-dist/workflows/installation-check/steps/step-02-commands.md +82 -0
  208. package/pennyfarthing-dist/workflows/installation-check/steps/step-03-hooks.md +121 -0
  209. package/pennyfarthing-dist/workflows/installation-check/steps/step-04-scripts.md +83 -0
  210. package/pennyfarthing-dist/workflows/installation-check/steps/step-05-layout.md +81 -0
  211. package/pennyfarthing-dist/workflows/installation-check/steps/step-06-legacy.md +94 -0
  212. package/pennyfarthing-dist/workflows/installation-check/steps/step-07-tools.md +80 -0
  213. package/pennyfarthing-dist/workflows/installation-check/steps/step-08-summary.md +99 -0
  214. package/pennyfarthing-dist/workflows/installation-check/workflow.yaml +47 -0
  215. package/pennyfarthing-dist/workflows/project-setup/steps/step-01-discover.md +47 -0
  216. package/pennyfarthing-dist/workflows/tdd-tandem.yaml +7 -3
  217. package/pennyfarthing-dist/workflows/tdd-team.yaml +80 -0
  218. package/pennyfarthing-dist/workflows/tdd.yaml +7 -3
  219. package/pennyfarthing-dist/workflows/trivial.yaml +7 -3
  220. package/pennyfarthing_scripts/__init__.py +1 -1
  221. package/pennyfarthing_scripts/__pycache__/__init__.cpython-311.pyc +0 -0
  222. package/pennyfarthing_scripts/__pycache__/__init__.cpython-314.pyc +0 -0
  223. package/pennyfarthing_scripts/__pycache__/cli.cpython-311.pyc +0 -0
  224. package/pennyfarthing_scripts/__pycache__/cli.cpython-314.pyc +0 -0
  225. package/pennyfarthing_scripts/__pycache__/context.cpython-311.pyc +0 -0
  226. package/pennyfarthing_scripts/__pycache__/context.cpython-314.pyc +0 -0
  227. package/pennyfarthing_scripts/bc/__pycache__/__init__.cpython-311.pyc +0 -0
  228. package/pennyfarthing_scripts/bc/__pycache__/cli.cpython-311.pyc +0 -0
  229. package/pennyfarthing_scripts/bc/__pycache__/cli.cpython-314.pyc +0 -0
  230. package/pennyfarthing_scripts/bc/__pycache__/focus.cpython-311.pyc +0 -0
  231. package/pennyfarthing_scripts/bc/__pycache__/focus.cpython-314.pyc +0 -0
  232. package/pennyfarthing_scripts/bc/__pycache__/split.cpython-314.pyc +0 -0
  233. package/pennyfarthing_scripts/bc/cli.py +23 -2
  234. package/pennyfarthing_scripts/bc/focus.py +1 -0
  235. package/pennyfarthing_scripts/bc/split.py +52 -0
  236. package/pennyfarthing_scripts/bellmode_hook.py +2 -5
  237. package/pennyfarthing_scripts/bikerack/__pycache__/__init__.cpython-311.pyc +0 -0
  238. package/pennyfarthing_scripts/bikerack/__pycache__/audit_log_panel.cpython-314.pyc +0 -0
  239. package/pennyfarthing_scripts/bikerack/__pycache__/background_panel.cpython-314.pyc +0 -0
  240. package/pennyfarthing_scripts/bikerack/__pycache__/base_panel.cpython-314.pyc +0 -0
  241. package/pennyfarthing_scripts/bikerack/__pycache__/changed_panel.cpython-314.pyc +0 -0
  242. package/pennyfarthing_scripts/bikerack/__pycache__/cli.cpython-311.pyc +0 -0
  243. package/pennyfarthing_scripts/bikerack/__pycache__/cli.cpython-314.pyc +0 -0
  244. package/pennyfarthing_scripts/bikerack/__pycache__/context_meter_footer.cpython-314.pyc +0 -0
  245. package/pennyfarthing_scripts/bikerack/__pycache__/debug_panel.cpython-314.pyc +0 -0
  246. package/pennyfarthing_scripts/bikerack/__pycache__/diffs_panel.cpython-314.pyc +0 -0
  247. package/pennyfarthing_scripts/bikerack/__pycache__/events.cpython-314.pyc +0 -0
  248. package/pennyfarthing_scripts/bikerack/__pycache__/git_panel.cpython-314.pyc +0 -0
  249. package/pennyfarthing_scripts/bikerack/__pycache__/launcher.cpython-311.pyc +0 -0
  250. package/pennyfarthing_scripts/bikerack/__pycache__/launcher.cpython-314.pyc +0 -0
  251. package/pennyfarthing_scripts/bikerack/__pycache__/portrait_resolver.cpython-314.pyc +0 -0
  252. package/pennyfarthing_scripts/bikerack/__pycache__/progress_panel.cpython-314.pyc +0 -0
  253. package/pennyfarthing_scripts/bikerack/__pycache__/sprint_panel.cpython-314.pyc +0 -0
  254. package/pennyfarthing_scripts/bikerack/__pycache__/story_detail_data.cpython-314.pyc +0 -0
  255. package/pennyfarthing_scripts/bikerack/__pycache__/story_detail_screen.cpython-314.pyc +0 -0
  256. package/pennyfarthing_scripts/bikerack/__pycache__/tui.cpython-314.pyc +0 -0
  257. package/pennyfarthing_scripts/bikerack/__pycache__/ws_client.cpython-314.pyc +0 -0
  258. package/pennyfarthing_scripts/bikerack/audit_log_panel.py +48 -6
  259. package/pennyfarthing_scripts/bikerack/context_meter_footer.py +53 -3
  260. package/pennyfarthing_scripts/bikerack/launcher.py +6 -6
  261. package/pennyfarthing_scripts/bikerack/progress_panel.py +0 -1
  262. package/pennyfarthing_scripts/bikerack/sprint_panel.py +1 -1
  263. package/pennyfarthing_scripts/bikerack/story_detail_data.py +4 -1
  264. package/pennyfarthing_scripts/bikerack/story_detail_screen.py +2 -1
  265. package/pennyfarthing_scripts/bikerack/tui.py +214 -10
  266. package/pennyfarthing_scripts/bikerack/ws_client.py +2 -2
  267. package/pennyfarthing_scripts/cli.py +5 -0
  268. package/pennyfarthing_scripts/common/__pycache__/__init__.cpython-311.pyc +0 -0
  269. package/pennyfarthing_scripts/common/__pycache__/config.cpython-311.pyc +0 -0
  270. package/pennyfarthing_scripts/common/__pycache__/config.cpython-314.pyc +0 -0
  271. package/pennyfarthing_scripts/common/__pycache__/output.cpython-311.pyc +0 -0
  272. package/pennyfarthing_scripts/common/__pycache__/pr_config.cpython-314.pyc +0 -0
  273. package/pennyfarthing_scripts/common/config.py +29 -2
  274. package/pennyfarthing_scripts/consultation/__pycache__/__init__.cpython-311.pyc +0 -0
  275. package/pennyfarthing_scripts/consultation/__pycache__/__init__.cpython-314.pyc +0 -0
  276. package/pennyfarthing_scripts/consultation/__pycache__/cli.cpython-311.pyc +0 -0
  277. package/pennyfarthing_scripts/consultation/__pycache__/cli.cpython-314.pyc +0 -0
  278. package/pennyfarthing_scripts/consultation/cli.py +3 -3
  279. package/pennyfarthing_scripts/context.py +3 -3
  280. package/pennyfarthing_scripts/deadcode/__pycache__/__init__.cpython-311.pyc +0 -0
  281. package/pennyfarthing_scripts/deadcode/__pycache__/cli.cpython-311.pyc +0 -0
  282. package/pennyfarthing_scripts/epic/__pycache__/__init__.cpython-311.pyc +0 -0
  283. package/pennyfarthing_scripts/epic/__pycache__/cli.cpython-311.pyc +0 -0
  284. package/pennyfarthing_scripts/git/hooks_installer.py +2 -3
  285. package/pennyfarthing_scripts/git/status_all.py +1 -1
  286. package/pennyfarthing_scripts/git/worktree.py +2 -2
  287. package/pennyfarthing_scripts/git_group/__pycache__/__init__.cpython-311.pyc +0 -0
  288. package/pennyfarthing_scripts/git_group/__pycache__/cli.cpython-311.pyc +0 -0
  289. package/pennyfarthing_scripts/git_group/__pycache__/cli.cpython-314.pyc +0 -0
  290. package/pennyfarthing_scripts/handoff/__pycache__/__init__.cpython-311.pyc +0 -0
  291. package/pennyfarthing_scripts/handoff/__pycache__/cli.cpython-311.pyc +0 -0
  292. package/pennyfarthing_scripts/handoff/__pycache__/cli.cpython-314.pyc +0 -0
  293. package/pennyfarthing_scripts/handoff/__pycache__/complete_phase.cpython-314.pyc +0 -0
  294. package/pennyfarthing_scripts/handoff/__pycache__/marker.cpython-314.pyc +0 -0
  295. package/pennyfarthing_scripts/handoff/__pycache__/phase_check.cpython-314.pyc +0 -0
  296. package/pennyfarthing_scripts/handoff/__pycache__/resolve_gate.cpython-314.pyc +0 -0
  297. package/pennyfarthing_scripts/healthscore/__pycache__/__init__.cpython-311.pyc +0 -0
  298. package/pennyfarthing_scripts/healthscore/__pycache__/analyze.cpython-311.pyc +0 -0
  299. package/pennyfarthing_scripts/healthscore/__pycache__/cli.cpython-311.pyc +0 -0
  300. package/pennyfarthing_scripts/healthscore/__pycache__/models.cpython-311.pyc +0 -0
  301. package/pennyfarthing_scripts/hooks/__init__.py +8 -3
  302. package/pennyfarthing_scripts/hooks/__pycache__/__init__.cpython-311.pyc +0 -0
  303. package/pennyfarthing_scripts/hooks/__pycache__/__init__.cpython-314.pyc +0 -0
  304. package/pennyfarthing_scripts/hooks/__pycache__/bell_mode.cpython-311.pyc +0 -0
  305. package/pennyfarthing_scripts/hooks/__pycache__/bell_mode.cpython-314.pyc +0 -0
  306. package/pennyfarthing_scripts/hooks/__pycache__/cli.cpython-311.pyc +0 -0
  307. package/pennyfarthing_scripts/hooks/__pycache__/cli.cpython-314.pyc +0 -0
  308. package/pennyfarthing_scripts/hooks/__pycache__/context_breaker.cpython-311.pyc +0 -0
  309. package/pennyfarthing_scripts/hooks/__pycache__/context_breaker.cpython-314.pyc +0 -0
  310. package/pennyfarthing_scripts/hooks/__pycache__/context_warning.cpython-311.pyc +0 -0
  311. package/pennyfarthing_scripts/hooks/__pycache__/context_warning.cpython-314.pyc +0 -0
  312. package/pennyfarthing_scripts/hooks/__pycache__/cyclist_pretooluse.cpython-311.pyc +0 -0
  313. package/pennyfarthing_scripts/hooks/__pycache__/cyclist_pretooluse.cpython-314.pyc +0 -0
  314. package/pennyfarthing_scripts/hooks/__pycache__/pre_edit_check.cpython-311.pyc +0 -0
  315. package/pennyfarthing_scripts/hooks/__pycache__/pre_edit_check.cpython-314.pyc +0 -0
  316. package/pennyfarthing_scripts/hooks/__pycache__/reflector_check.cpython-311.pyc +0 -0
  317. package/pennyfarthing_scripts/hooks/__pycache__/reflector_check.cpython-314.pyc +0 -0
  318. package/pennyfarthing_scripts/hooks/__pycache__/schema_validation.cpython-311.pyc +0 -0
  319. package/pennyfarthing_scripts/hooks/__pycache__/schema_validation.cpython-314.pyc +0 -0
  320. package/pennyfarthing_scripts/hooks/__pycache__/session_start.cpython-311.pyc +0 -0
  321. package/pennyfarthing_scripts/hooks/__pycache__/session_start.cpython-314.pyc +0 -0
  322. package/pennyfarthing_scripts/hooks/__pycache__/session_stop.cpython-314.pyc +0 -0
  323. package/pennyfarthing_scripts/hooks/__pycache__/sprint_yaml_validation.cpython-311.pyc +0 -0
  324. package/pennyfarthing_scripts/hooks/__pycache__/sprint_yaml_validation.cpython-314.pyc +0 -0
  325. package/pennyfarthing_scripts/hooks/__pycache__/statusline.cpython-311.pyc +0 -0
  326. package/pennyfarthing_scripts/hooks/__pycache__/statusline.cpython-314.pyc +0 -0
  327. package/pennyfarthing_scripts/hooks/bell_mode.py +0 -1
  328. package/pennyfarthing_scripts/hooks/pre_edit_check.py +0 -1
  329. package/pennyfarthing_scripts/hooks/reflector_check.py +1 -2
  330. package/pennyfarthing_scripts/hooks/schema_validation.py +0 -1
  331. package/pennyfarthing_scripts/hooks/session_start.py +6 -8
  332. package/pennyfarthing_scripts/hooks/statusline.py +10 -1
  333. package/pennyfarthing_scripts/hotspots/__pycache__/__init__.cpython-311.pyc +0 -0
  334. package/pennyfarthing_scripts/hotspots/__pycache__/analyze.cpython-311.pyc +0 -0
  335. package/pennyfarthing_scripts/hotspots/__pycache__/cli.cpython-311.pyc +0 -0
  336. package/pennyfarthing_scripts/hotspots/__pycache__/models.cpython-311.pyc +0 -0
  337. package/pennyfarthing_scripts/jira/__pycache__/__init__.cpython-311.pyc +0 -0
  338. package/pennyfarthing_scripts/jira/__pycache__/bidirectional.cpython-311.pyc +0 -0
  339. package/pennyfarthing_scripts/jira/__pycache__/claim.cpython-311.pyc +0 -0
  340. package/pennyfarthing_scripts/jira/__pycache__/cli.cpython-311.pyc +0 -0
  341. package/pennyfarthing_scripts/jira/__pycache__/client.cpython-311.pyc +0 -0
  342. package/pennyfarthing_scripts/jira/__pycache__/create.cpython-311.pyc +0 -0
  343. package/pennyfarthing_scripts/jira/__pycache__/epic.cpython-311.pyc +0 -0
  344. package/pennyfarthing_scripts/jira/__pycache__/operations.cpython-311.pyc +0 -0
  345. package/pennyfarthing_scripts/jira/__pycache__/reconcile.cpython-311.pyc +0 -0
  346. package/pennyfarthing_scripts/jira/__pycache__/story.cpython-311.pyc +0 -0
  347. package/pennyfarthing_scripts/jira/__pycache__/sync.cpython-311.pyc +0 -0
  348. package/pennyfarthing_scripts/launch/__pycache__/__init__.cpython-311.pyc +0 -0
  349. package/pennyfarthing_scripts/launch/__pycache__/cli.cpython-311.pyc +0 -0
  350. package/pennyfarthing_scripts/prime/__pycache__/workflow.cpython-314.pyc +0 -0
  351. package/pennyfarthing_scripts/prime/heatmap.py +3 -15
  352. package/pennyfarthing_scripts/session/__pycache__/__init__.cpython-311.pyc +0 -0
  353. package/pennyfarthing_scripts/session/__pycache__/cli.cpython-311.pyc +0 -0
  354. package/pennyfarthing_scripts/settings/__init__.py +0 -0
  355. package/pennyfarthing_scripts/settings/__pycache__/__init__.cpython-314.pyc +0 -0
  356. package/pennyfarthing_scripts/settings/__pycache__/cli.cpython-314.pyc +0 -0
  357. package/pennyfarthing_scripts/settings/__pycache__/settings.cpython-314.pyc +0 -0
  358. package/pennyfarthing_scripts/settings/cli.py +55 -0
  359. package/pennyfarthing_scripts/settings/settings.py +98 -0
  360. package/pennyfarthing_scripts/sprint/__pycache__/__init__.cpython-311.pyc +0 -0
  361. package/pennyfarthing_scripts/sprint/__pycache__/archive.cpython-311.pyc +0 -0
  362. package/pennyfarthing_scripts/sprint/__pycache__/cli.cpython-311.pyc +0 -0
  363. package/pennyfarthing_scripts/sprint/__pycache__/cli.cpython-314.pyc +0 -0
  364. package/pennyfarthing_scripts/sprint/__pycache__/epic_add.cpython-311.pyc +0 -0
  365. package/pennyfarthing_scripts/sprint/__pycache__/epic_update.cpython-311.pyc +0 -0
  366. package/pennyfarthing_scripts/sprint/__pycache__/loader.cpython-311.pyc +0 -0
  367. package/pennyfarthing_scripts/sprint/__pycache__/loader.cpython-314.pyc +0 -0
  368. package/pennyfarthing_scripts/sprint/__pycache__/status.cpython-311.pyc +0 -0
  369. package/pennyfarthing_scripts/sprint/__pycache__/story_add.cpython-311.pyc +0 -0
  370. package/pennyfarthing_scripts/sprint/__pycache__/story_finish.cpython-314.pyc +0 -0
  371. package/pennyfarthing_scripts/sprint/__pycache__/story_update.cpython-311.pyc +0 -0
  372. package/pennyfarthing_scripts/sprint/__pycache__/story_update.cpython-314.pyc +0 -0
  373. package/pennyfarthing_scripts/sprint/__pycache__/validate_cmd.cpython-311.pyc +0 -0
  374. package/pennyfarthing_scripts/sprint/__pycache__/validator.cpython-311.pyc +0 -0
  375. package/pennyfarthing_scripts/sprint/__pycache__/work.cpython-311.pyc +0 -0
  376. package/pennyfarthing_scripts/sprint/__pycache__/yaml_io.cpython-311.pyc +0 -0
  377. package/pennyfarthing_scripts/sprint/cli.py +121 -0
  378. package/pennyfarthing_scripts/sprint/loader.py +154 -3
  379. package/pennyfarthing_scripts/sprint/story_update.py +7 -0
  380. package/pennyfarthing_scripts/tests/__pycache__/test_bikerack.cpython-314-pytest-9.0.2.pyc +0 -0
  381. package/pennyfarthing_scripts/tests/__pycache__/test_tui_focus.cpython-314-pytest-9.0.2.pyc +0 -0
  382. package/pennyfarthing_scripts/tests/__pycache__/test_tui_panel_persistence.cpython-314-pytest-9.0.2.pyc +0 -0
  383. package/pennyfarthing_scripts/tests/test_bikerack.py +26 -26
  384. package/pennyfarthing_scripts/tests/test_confidence_sm_gate.py +17 -16
  385. package/pennyfarthing_scripts/tests/test_dialogue_manager.py +0 -1
  386. package/pennyfarthing_scripts/tests/test_resolve_gate_file_field.py +45 -47
  387. package/pennyfarthing_scripts/tests/test_workflow_list_team.py +143 -0
  388. package/pennyfarthing_scripts/theme/__pycache__/__init__.cpython-311.pyc +0 -0
  389. package/pennyfarthing_scripts/theme/__pycache__/cli.cpython-311.pyc +0 -0
  390. package/pennyfarthing_scripts/validate/__pycache__/__init__.cpython-311.pyc +0 -0
  391. package/pennyfarthing_scripts/validate/__pycache__/cli.cpython-311.pyc +0 -0
  392. package/pennyfarthing_scripts/validate/__pycache__/cli.cpython-314.pyc +0 -0
  393. package/pennyfarthing_scripts/validate/adapters/team_mode.py +323 -0
  394. package/pennyfarthing_scripts/workflow/__pycache__/__init__.cpython-311.pyc +0 -0
  395. package/pennyfarthing_scripts/workflow/__pycache__/__init__.cpython-314.pyc +0 -0
  396. package/pennyfarthing_scripts/workflow/__pycache__/cli.cpython-311.pyc +0 -0
  397. package/pennyfarthing_scripts/workflow/__pycache__/cli.cpython-314.pyc +0 -0
  398. package/pennyfarthing_scripts/workflow/__pycache__/helpers.cpython-314.pyc +0 -0
  399. package/pennyfarthing_scripts/workflow/__pycache__/scale.cpython-311.pyc +0 -0
  400. package/pennyfarthing_scripts/workflow/__pycache__/scale.cpython-314.pyc +0 -0
  401. package/pennyfarthing_scripts/workflow/__pycache__/state.cpython-311.pyc +0 -0
  402. package/pennyfarthing_scripts/workflow/__pycache__/state.cpython-314.pyc +0 -0
  403. package/pennyfarthing_scripts/workflow/cli.py +15 -14
  404. package/pennyfarthing_scripts/workflow/state.py +0 -1
  405. package/pennyfarthing_scripts/workflow/team_lifecycle.py +3 -4
@@ -6,7 +6,7 @@ Story: 106-3 — Workflow YAML gate.file integration
6
6
  Tests the gate.file field support in resolve_gate():
7
7
  - Schema extension: gate.file extracted from workflow YAML
8
8
  - Backward compatibility: gate.type-only workflows unchanged
9
- - TDD workflow migration: green phase has file: gates/tests-pass
9
+ - TDD workflow migration: all phases now have gate.file fields
10
10
 
11
11
  Acceptance Criteria:
12
12
  - [AC1] resolve-gate.py reads gate.file field from workflow YAML phases
@@ -15,9 +15,9 @@ Acceptance Criteria:
15
15
  - [AC2] Workflows with only gate.type continue to work unchanged
16
16
  - [AC2] Existing gate type logic remains functional
17
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)
18
+ - [AC3] Green phase in tdd.yaml has file: gates/dev-exit and type: dev_exit
19
+ - [AC3] All phases have gate.file fields (full migration complete)
20
+ - [AC3] File paths are relative (not absolute)
21
21
  """
22
22
 
23
23
  from __future__ import annotations
@@ -202,21 +202,19 @@ class TestResolveGateFileOnly:
202
202
  )
203
203
  assert result["gate_type"] is None
204
204
 
205
- def test_status_skip_when_no_type_and_file_only(
205
+ def test_status_ready_when_no_type_and_file_only(
206
206
  self, project: Path
207
207
  ) -> None:
208
- """AC1: gate with file-only and no type → status depends on gate_type logic.
208
+ """AC1: gate with file-only and no type → status is 'ready'.
209
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.
210
+ File-only gates (no type) are resolvable — the gate runner uses
211
+ gate_file to locate and execute the gate. Status is 'ready' when
212
+ an assessment exists.
215
213
  """
216
214
  result = resolve_gate(
217
215
  "106-3", "file-only", "green", project_root=project
218
216
  )
219
- assert result["status"] == "skip"
217
+ assert result["status"] == "ready"
220
218
 
221
219
 
222
220
  # ===========================================================================
@@ -315,11 +313,10 @@ class TestResolveGateBackwardCompat:
315
313
 
316
314
 
317
315
  class TestTddWorkflowMigration:
318
- """AC3: TDD workflow green phase has file: gates/tests-pass.
316
+ """AC3: TDD workflow phases have gate.file fields after full migration.
319
317
 
320
318
  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.
319
+ the migration has been applied.
323
320
  """
324
321
 
325
322
  @pytest.fixture
@@ -342,54 +339,55 @@ class TestTddWorkflowMigration:
342
339
  raise ValueError(f"Phase '{name}' not found")
343
340
 
344
341
  def test_green_phase_has_gate_file(self, tdd_yaml: dict) -> None:
345
- """AC3: Green phase should have gate.file = 'gates/tests-pass'."""
342
+ """AC3: Green phase should have gate.file = 'gates/dev-exit'."""
346
343
  green = self._get_phase(tdd_yaml, "green")
347
344
  gate = green.get("gate", {})
348
- assert gate.get("file") == "gates/tests-pass", (
349
- f"Expected gate.file='gates/tests-pass', got gate={gate}"
345
+ assert gate.get("file") == "gates/dev-exit", (
346
+ f"Expected gate.file='gates/dev-exit', got gate={gate}"
350
347
  )
351
348
 
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."""
349
+ def test_green_phase_has_dev_exit_type(self, tdd_yaml: dict) -> None:
350
+ """AC3: Green phase should have gate.type = 'dev_exit'."""
354
351
  green = self._get_phase(tdd_yaml, "green")
355
352
  gate = green.get("gate", {})
356
- assert gate.get("type") == "tests_pass", (
357
- f"Expected gate.type='tests_pass', got gate={gate}"
353
+ assert gate.get("type") == "dev_exit", (
354
+ f"Expected gate.type='dev_exit', got gate={gate}"
358
355
  )
359
356
 
360
357
  def test_green_phase_file_is_relative(self, tdd_yaml: dict) -> None:
361
- """AC3: File path should be relative (gates/tests-pass), not absolute."""
358
+ """AC3: File path should be relative (gates/dev-exit), not absolute."""
362
359
  green = self._get_phase(tdd_yaml, "green")
363
360
  gate = green.get("gate", {})
364
361
  file_path = gate.get("file", "")
365
362
  assert not file_path.startswith("/"), (
366
363
  f"gate.file should be relative, got: {file_path}"
367
364
  )
368
- assert file_path == "gates/tests-pass", (
369
- f"Expected 'gates/tests-pass', got: {file_path}"
365
+ assert file_path == "gates/dev-exit", (
366
+ f"Expected 'gates/dev-exit', got: {file_path}"
370
367
  )
371
368
 
372
- def test_red_phase_unchanged(self, tdd_yaml: dict) -> None:
373
- """AC3: Red phase should NOT have gate.file (backward compat period)."""
369
+ def test_red_phase_has_gate_file(self, tdd_yaml: dict) -> None:
370
+ """AC3: Red phase should have gate.file = 'gates/tests-fail'."""
374
371
  red = self._get_phase(tdd_yaml, "red")
375
372
  gate = red.get("gate", {})
376
- assert "file" not in gate, (
377
- f"Red phase should not have gate.file yet, got gate={gate}"
373
+ assert gate.get("file") == "gates/tests-fail", (
374
+ f"Expected gate.file='gates/tests-fail', got gate={gate}"
378
375
  )
379
376
 
380
- def test_review_phase_unchanged(self, tdd_yaml: dict) -> None:
381
- """AC3: Review phase should NOT have gate.file (backward compat period)."""
377
+ def test_review_phase_has_gate_file(self, tdd_yaml: dict) -> None:
378
+ """AC3: Review phase should have gate.file = 'gates/approval'."""
382
379
  review = self._get_phase(tdd_yaml, "review")
383
380
  gate = review.get("gate", {})
384
- assert "file" not in gate, (
385
- f"Review phase should not have gate.file yet, got gate={gate}"
381
+ assert gate.get("file") == "gates/approval", (
382
+ f"Expected gate.file='gates/approval', got gate={gate}"
386
383
  )
387
384
 
388
- def test_setup_phase_unchanged(self, tdd_yaml: dict) -> None:
389
- """AC3: Setup phase should remain gateless."""
385
+ def test_setup_phase_has_gate(self, tdd_yaml: dict) -> None:
386
+ """AC3: Setup phase should have gate.file = 'gates/sm-setup-exit'."""
390
387
  setup = self._get_phase(tdd_yaml, "setup")
391
- assert "gate" not in setup, (
392
- f"Setup phase should not have a gate, got: {setup}"
388
+ gate = setup.get("gate", {})
389
+ assert gate.get("file") == "gates/sm-setup-exit", (
390
+ f"Expected gate.file='gates/sm-setup-exit', got gate={gate}"
393
391
  )
394
392
 
395
393
  def test_finish_phase_unchanged(self, tdd_yaml: dict) -> None:
@@ -435,30 +433,30 @@ class TestResolveGateWithRealTddYaml:
435
433
  def test_green_phase_returns_gate_file(
436
434
  self, real_project: Path
437
435
  ) -> None:
438
- """AC3: resolve_gate for tdd/green should return gate_file='gates/tests-pass'."""
436
+ """AC3: resolve_gate for tdd/green should return gate_file='gates/dev-exit'."""
439
437
  result = resolve_gate(
440
438
  "106-3", "tdd", "green", project_root=real_project
441
439
  )
442
- assert result["gate_file"] == "gates/tests-pass", (
443
- f"Expected gate_file='gates/tests-pass', got: {result}"
440
+ assert result["gate_file"] == "gates/dev-exit", (
441
+ f"Expected gate_file='gates/dev-exit', got: {result}"
444
442
  )
445
443
 
446
- def test_green_phase_still_returns_gate_type(
444
+ def test_green_phase_returns_dev_exit_type(
447
445
  self, real_project: Path
448
446
  ) -> None:
449
- """AC3: resolve_gate for tdd/green should still return gate_type='tests_pass'."""
447
+ """AC3: resolve_gate for tdd/green should return gate_type='dev_exit'."""
450
448
  result = resolve_gate(
451
449
  "106-3", "tdd", "green", project_root=real_project
452
450
  )
453
- assert result["gate_type"] == "tests_pass", (
454
- f"Expected gate_type='tests_pass', got: {result}"
451
+ assert result["gate_type"] == "dev_exit", (
452
+ f"Expected gate_type='dev_exit', got: {result}"
455
453
  )
456
454
 
457
- def test_red_phase_gate_file_is_none(
455
+ def test_red_phase_gate_file_is_tests_fail(
458
456
  self, real_project: Path
459
457
  ) -> None:
460
- """AC3: resolve_gate for tdd/red should have gate_file=None (not migrated yet)."""
458
+ """AC3: resolve_gate for tdd/red should have gate_file='gates/tests-fail'."""
461
459
  result = resolve_gate(
462
460
  "106-3", "tdd", "red", project_root=real_project
463
461
  )
464
- assert result["gate_file"] is None
462
+ assert result["gate_file"] == "gates/tests-fail"
@@ -0,0 +1,143 @@
1
+ """Tests for Story 86-15 AC4: workflow list shows team-enabled workflows with indicator.
2
+
3
+ Story: 86-15 — Team-enabled workflow templates
4
+ Epic: 86 — Agent Collaboration: Tandem to Teams
5
+
6
+ Acceptance Criteria:
7
+ - [AC4] /workflow list shows team-enabled workflows with indicator
8
+
9
+ The `pf workflow list` command should visually distinguish team-enabled
10
+ workflows from regular and tandem workflows. When a workflow has `team:`
11
+ blocks on any phase, the output must include an indicator.
12
+
13
+ These tests verify the CLI output format. Tests should fail until
14
+ both the workflow templates (tdd-team.yaml, bdd-team.yaml) and the
15
+ workflow list team indicator are implemented.
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ import pytest
21
+ from click.testing import CliRunner
22
+
23
+ from pennyfarthing_scripts.cli import cli
24
+
25
+
26
+ class TestWorkflowListTeamIndicator:
27
+ """Tests for team indicator in workflow list output (AC4)."""
28
+
29
+ @pytest.fixture
30
+ def runner(self) -> CliRunner:
31
+ return CliRunner()
32
+
33
+ def test_workflow_list_shows_team_workflows(self, runner: CliRunner) -> None:
34
+ """AC4: workflow list should include tdd-team and bdd-team."""
35
+ result = runner.invoke(cli, ["workflow", "list"])
36
+ assert result.exit_code == 0
37
+ assert "tdd-team" in result.output, "tdd-team workflow should appear in list"
38
+ assert "bdd-team" in result.output, "bdd-team workflow should appear in list"
39
+
40
+ def test_workflow_list_has_team_indicator_for_tdd_team(
41
+ self, runner: CliRunner
42
+ ) -> None:
43
+ """AC4: tdd-team should have a team indicator in workflow list."""
44
+ result = runner.invoke(cli, ["workflow", "list"])
45
+ assert result.exit_code == 0
46
+
47
+ # Find the tdd-team row in the markdown table
48
+ tdd_team_line = None
49
+ for line in result.output.splitlines():
50
+ if "tdd-team" in line and "|" in line:
51
+ tdd_team_line = line
52
+ break
53
+
54
+ assert tdd_team_line is not None, "tdd-team should appear as a table row"
55
+
56
+ # The row should have a team indicator — could be a column value,
57
+ # emoji, or tag that distinguishes it from non-team workflows
58
+ line_lower = tdd_team_line.lower()
59
+ assert (
60
+ "team" in line_lower
61
+ ), "tdd-team row should contain a 'team' indicator"
62
+
63
+ def test_workflow_list_has_team_indicator_for_bdd_team(
64
+ self, runner: CliRunner
65
+ ) -> None:
66
+ """AC4: bdd-team should have a team indicator in workflow list."""
67
+ result = runner.invoke(cli, ["workflow", "list"])
68
+ assert result.exit_code == 0
69
+
70
+ bdd_team_line = None
71
+ for line in result.output.splitlines():
72
+ if "bdd-team" in line and "|" in line:
73
+ bdd_team_line = line
74
+ break
75
+
76
+ assert bdd_team_line is not None, "bdd-team should appear as a table row"
77
+
78
+ line_lower = bdd_team_line.lower()
79
+ assert (
80
+ "team" in line_lower
81
+ ), "bdd-team row should contain a 'team' indicator"
82
+
83
+ def test_non_team_workflows_lack_team_indicator(
84
+ self, runner: CliRunner
85
+ ) -> None:
86
+ """AC4: Regular workflows should NOT have team indicator."""
87
+ result = runner.invoke(cli, ["workflow", "list"])
88
+ assert result.exit_code == 0
89
+
90
+ for line in result.output.splitlines():
91
+ if "|" not in line:
92
+ continue
93
+ # Skip header, separator, and team workflows
94
+ if "tdd-team" in line or "bdd-team" in line:
95
+ continue
96
+ if "Workflow" in line or "---" in line:
97
+ continue
98
+
99
+ # Check columns (split by |)
100
+ cols = [c.strip() for c in line.split("|")]
101
+ name_col = cols[1] if len(cols) > 1 else ""
102
+
103
+ # tdd-tandem and bdd-tandem are tandem, not team
104
+ if "tandem" in name_col:
105
+ continue
106
+
107
+ # Plain workflows like tdd, trivial, bdd should not show team
108
+ # in their mode/indicator columns (but "team" might appear
109
+ # in description text, which is fine — we check indicator columns)
110
+ # The name itself (e.g., "tdd") doesn't contain "team", confirming
111
+ # no false positives in the name column
112
+
113
+
114
+ class TestWorkflowListTeamColumnOrTag:
115
+ """Verify the team indicator mechanism in workflow list."""
116
+
117
+ @pytest.fixture
118
+ def runner(self) -> CliRunner:
119
+ return CliRunner()
120
+
121
+ def test_workflow_list_distinguishes_team_from_tandem(
122
+ self, runner: CliRunner
123
+ ) -> None:
124
+ """Team workflows should be distinguishable from tandem workflows."""
125
+ result = runner.invoke(cli, ["workflow", "list"])
126
+ assert result.exit_code == 0
127
+
128
+ tdd_team_line = None
129
+ tdd_tandem_line = None
130
+ for line in result.output.splitlines():
131
+ if "tdd-team" in line and "|" in line:
132
+ tdd_team_line = line
133
+ elif "tdd-tandem" in line and "|" in line:
134
+ tdd_tandem_line = line
135
+
136
+ assert tdd_team_line is not None, "tdd-team should be in the list"
137
+ assert tdd_tandem_line is not None, "tdd-tandem should be in the list"
138
+
139
+ # They should have different indicators — team vs tandem
140
+ # At minimum, the descriptions should be distinct
141
+ assert tdd_team_line != tdd_tandem_line, (
142
+ "tdd-team and tdd-tandem should have different rows"
143
+ )
@@ -0,0 +1,323 @@
1
+ """Team-mode protocol validator adapter.
2
+
3
+ Validates that agent definitions include proper team-mode sections
4
+ for Claude Code native Agent Teams integration.
5
+
6
+ Story: MSSCI-15109 (86-14)
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import re
12
+ from pathlib import Path
13
+
14
+ from pennyfarthing_scripts.validate import ValidateReport
15
+
16
+ # Regex to extract <team-mode> section content
17
+ _TEAM_MODE_RE = re.compile(r"<team-mode>(.*?)</team-mode>", re.DOTALL)
18
+
19
+ # Required topics in the behavior guide's <team-mode> section
20
+ _BEHAVIOR_GUIDE_TOPICS = {
21
+ "team_creation": ["TeamCreate", "team creation", "create team"],
22
+ "spawning": ["spawn", "Task tool", "teammate"],
23
+ "sendmessage": ["SendMessage"],
24
+ "cleanup": ["cleanup", "TeamDelete", "shut down", "shutdown"],
25
+ }
26
+
27
+ # Required lead agent topics
28
+ _LEAD_TOPICS = {
29
+ "phase_entry": ["phase entry", "phase start", "on phase"],
30
+ "spawn_per_yaml": ["spawn", "teammates", "workflow YAML", "YAML"],
31
+ "shutdown_before_exit": ["shut down", "shutdown", "before exit", "before handoff"],
32
+ }
33
+
34
+ # Required teammate awareness topics
35
+ _TEAMMATE_TOPICS = {
36
+ "identity": ["teammate", "not lead", "not the lead"],
37
+ "sendmessage": ["SendMessage"],
38
+ "idle": ["idle", "go idle"],
39
+ "shutdown_response": ["shutdown", "shutdown_response", "shutdown request"],
40
+ }
41
+
42
+
43
+ def extract_team_mode_section(content: str) -> str | None:
44
+ """Extract content between <team-mode> tags."""
45
+ m = _TEAM_MODE_RE.search(content)
46
+ return m.group(1) if m else None
47
+
48
+
49
+ def validate_behavior_guide_team_mode(
50
+ path: Path,
51
+ ) -> tuple[list[str], list[str]]:
52
+ """Validate the behavior guide has a <team-mode> section with required topics.
53
+
54
+ Returns:
55
+ (errors, warnings) — two lists of message strings.
56
+ """
57
+ errors: list[str] = []
58
+ warnings: list[str] = []
59
+ content = path.read_text()
60
+ section = extract_team_mode_section(content)
61
+
62
+ if section is None:
63
+ errors.append("Missing <team-mode> section in behavior guide")
64
+ return errors, warnings
65
+
66
+ stripped = section.strip()
67
+ if not stripped:
68
+ errors.append("Empty <team-mode> section — no content")
69
+ return errors, warnings
70
+
71
+ # Check required topics
72
+ for topic_name, keywords in _BEHAVIOR_GUIDE_TOPICS.items():
73
+ if not any(kw.lower() in section.lower() for kw in keywords):
74
+ errors.append(
75
+ f"<team-mode> section missing required topic: {topic_name} "
76
+ f"(expected one of: {', '.join(keywords)})"
77
+ )
78
+
79
+ return errors, warnings
80
+
81
+
82
+ def validate_lead_agent_team_mode(
83
+ path: Path,
84
+ ) -> tuple[list[str], list[str]]:
85
+ """Validate lead agent has team-mode behavior section.
86
+
87
+ Returns:
88
+ (errors, warnings) — two lists of message strings.
89
+ """
90
+ errors: list[str] = []
91
+ warnings: list[str] = []
92
+ content = path.read_text()
93
+ section = extract_team_mode_section(content)
94
+
95
+ if section is None:
96
+ errors.append(f"Missing <team-mode> section in lead agent {path.name}")
97
+ return errors, warnings
98
+
99
+ stripped = section.strip()
100
+ if not stripped:
101
+ errors.append(f"Empty <team-mode> section in lead agent {path.name}")
102
+ return errors, warnings
103
+
104
+ section_lower = section.lower()
105
+
106
+ # Must reference lead role
107
+ if "lead" not in section_lower:
108
+ errors.append("Lead agent <team-mode> section must reference 'lead' role")
109
+
110
+ # Check lead-specific topics
111
+ for topic_name, keywords in _LEAD_TOPICS.items():
112
+ if not any(kw.lower() in section_lower for kw in keywords):
113
+ warnings.append(
114
+ f"Lead agent <team-mode> missing topic: {topic_name} "
115
+ f"(expected one of: {', '.join(keywords)})"
116
+ )
117
+
118
+ return errors, warnings
119
+
120
+
121
+ def validate_teammate_awareness(
122
+ content: str,
123
+ ) -> tuple[list[str], list[str]]:
124
+ """Validate teammate behavior content in team-mode section.
125
+
126
+ Returns:
127
+ (errors, warnings) — two lists of message strings.
128
+ """
129
+ errors: list[str] = []
130
+ warnings: list[str] = []
131
+ section = extract_team_mode_section(content)
132
+
133
+ if section is None:
134
+ errors.append("Missing <team-mode> section for teammate awareness")
135
+ return errors, warnings
136
+
137
+ section_lower = section.lower()
138
+
139
+ # Check teammate-specific topics
140
+ for topic_name, keywords in _TEAMMATE_TOPICS.items():
141
+ if not any(kw.lower() in section_lower for kw in keywords):
142
+ errors.append(
143
+ f"Teammate awareness missing topic: {topic_name} "
144
+ f"(expected one of: {', '.join(keywords)})"
145
+ )
146
+
147
+ return errors, warnings
148
+
149
+
150
+ def validate_exit_protocol_team_branch(
151
+ content: str,
152
+ ) -> tuple[list[str], list[str]]:
153
+ """Validate exit protocol has team-mode cleanup branch.
154
+
155
+ Returns:
156
+ (errors, warnings) — two lists of message strings.
157
+ """
158
+ errors: list[str] = []
159
+ warnings: list[str] = []
160
+
161
+ # Look for team cleanup in exit protocol section
162
+ # The exit protocol is in <agent-exit-protocol> tags
163
+ exit_re = re.compile(
164
+ r"<agent-exit-protocol>(.*?)</agent-exit-protocol>", re.DOTALL
165
+ )
166
+ exit_match = exit_re.search(content)
167
+
168
+ if exit_match is None:
169
+ errors.append("Missing <agent-exit-protocol> section")
170
+ return errors, warnings
171
+
172
+ exit_content = exit_match.group(1).lower()
173
+
174
+ # Must reference team cleanup
175
+ team_cleanup_terms = ["team", "teamdelete", "cleanup team", "shut down teammate"]
176
+ if not any(term in exit_content for term in team_cleanup_terms):
177
+ errors.append(
178
+ "Exit protocol missing team-mode branch "
179
+ "(must reference team cleanup before handoff)"
180
+ )
181
+
182
+ return errors, warnings
183
+
184
+
185
+ def validate_communication_protocols(
186
+ content: str,
187
+ ) -> tuple[list[str], list[str]]:
188
+ """Validate that reflector markers and SendMessage are properly documented.
189
+
190
+ Reflector markers for inter-phase handoff (unchanged).
191
+ SendMessage for intra-phase teammate communication (new).
192
+
193
+ Returns:
194
+ (errors, warnings) — two lists of message strings.
195
+ """
196
+ errors: list[str] = []
197
+ warnings: list[str] = []
198
+
199
+ # Reflector section must still exist
200
+ if "<critical>" not in content or "CYCLIST" not in content:
201
+ errors.append("Reflector/CYCLIST marker section missing — must remain for inter-phase handoff")
202
+
203
+ # Team-mode section must reference SendMessage for intra-phase
204
+ section = extract_team_mode_section(content)
205
+ if section is not None:
206
+ if "SendMessage" not in section:
207
+ errors.append(
208
+ "<team-mode> must reference SendMessage for intra-phase communication"
209
+ )
210
+ # Should distinguish inter-phase (markers) from intra-phase (SendMessage)
211
+ if "inter-phase" not in section.lower() and "intra-phase" not in section.lower():
212
+ warnings.append(
213
+ "<team-mode> should distinguish inter-phase (markers) "
214
+ "from intra-phase (SendMessage) communication"
215
+ )
216
+
217
+ return errors, warnings
218
+
219
+
220
+ def classify_team_mode_agents(
221
+ agents_dir: Path,
222
+ ) -> tuple[list[Path], list[Path]]:
223
+ """Classify agent files into lead agents and all agents with team-mode.
224
+
225
+ Lead agents are those whose <team-mode> section mentions 'lead'.
226
+
227
+ Returns:
228
+ (lead_agents, all_team_mode_agents)
229
+ """
230
+ leads: list[Path] = []
231
+ all_tm: list[Path] = []
232
+
233
+ for f in sorted(agents_dir.glob("*.md")):
234
+ if f.name == "README.md":
235
+ continue
236
+
237
+ content = f.read_text()
238
+ section = extract_team_mode_section(content)
239
+ if section is None:
240
+ continue
241
+
242
+ all_tm.append(f)
243
+ if "lead" in section.lower():
244
+ leads.append(f)
245
+
246
+ return leads, all_tm
247
+
248
+
249
+ def run(
250
+ root: Path, *, fix: bool = False, strict: bool = False
251
+ ) -> ValidateReport:
252
+ """Validate agent team-mode protocol sections."""
253
+ report = ValidateReport(validator="team-mode")
254
+
255
+ # Validate behavior guide
256
+ guides_dir = root / "pennyfarthing-dist" / "guides"
257
+ behavior_guide = guides_dir / "agent-behavior.md"
258
+ if behavior_guide.is_file():
259
+ file_errors, file_warnings = validate_behavior_guide_team_mode(behavior_guide)
260
+ for e in file_errors:
261
+ report.errors += 1
262
+ report.details.append(f"[ERROR] agent-behavior.md: {e}")
263
+ for w in file_warnings:
264
+ if strict:
265
+ report.errors += 1
266
+ report.details.append(f"[ERROR] agent-behavior.md: {w}")
267
+ else:
268
+ report.warnings += 1
269
+ report.details.append(f"[WARN] agent-behavior.md: {w}")
270
+ if not file_errors:
271
+ report.passed += 1
272
+
273
+ # Validate exit protocol team branch
274
+ guide_content = behavior_guide.read_text()
275
+ exit_errors, exit_warnings = validate_exit_protocol_team_branch(guide_content)
276
+ for e in exit_errors:
277
+ report.errors += 1
278
+ report.details.append(f"[ERROR] agent-behavior.md exit: {e}")
279
+ for w in exit_warnings:
280
+ if strict:
281
+ report.errors += 1
282
+ report.details.append(f"[ERROR] agent-behavior.md exit: {w}")
283
+ else:
284
+ report.warnings += 1
285
+ report.details.append(f"[WARN] agent-behavior.md exit: {w}")
286
+
287
+ # Validate communication protocol distinction
288
+ comm_errors, comm_warnings = validate_communication_protocols(guide_content)
289
+ for e in comm_errors:
290
+ report.errors += 1
291
+ report.details.append(f"[ERROR] agent-behavior.md comm: {e}")
292
+ for w in comm_warnings:
293
+ if strict:
294
+ report.errors += 1
295
+ report.details.append(f"[ERROR] agent-behavior.md comm: {w}")
296
+ else:
297
+ report.warnings += 1
298
+ report.details.append(f"[WARN] agent-behavior.md comm: {w}")
299
+ else:
300
+ report.errors += 1
301
+ report.details.append("[ERROR] agent-behavior.md guide not found")
302
+
303
+ # Validate lead agents
304
+ agents_dir = root / "pennyfarthing-dist" / "agents"
305
+ if agents_dir.is_dir():
306
+ for agent_name in ("dev", "reviewer"):
307
+ agent_path = agents_dir / f"{agent_name}.md"
308
+ if agent_path.is_file():
309
+ lead_errors, lead_warnings = validate_lead_agent_team_mode(agent_path)
310
+ for e in lead_errors:
311
+ report.errors += 1
312
+ report.details.append(f"[ERROR] {agent_name}.md: {e}")
313
+ for w in lead_warnings:
314
+ if strict:
315
+ report.errors += 1
316
+ report.details.append(f"[ERROR] {agent_name}.md: {w}")
317
+ else:
318
+ report.warnings += 1
319
+ report.details.append(f"[WARN] {agent_name}.md: {w}")
320
+ if not lead_errors:
321
+ report.passed += 1
322
+
323
+ return report