@pennyfarthing/core 11.1.0 → 11.2.0

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 (263) hide show
  1. package/README.md +8 -8
  2. package/package.json +16 -14
  3. package/packages/core/dist/cli/utils/constants.d.ts +1 -1
  4. package/packages/core/dist/cli/utils/constants.d.ts.map +1 -1
  5. package/packages/core/dist/cli/utils/constants.js +2 -1
  6. package/packages/core/dist/cli/utils/constants.js.map +1 -1
  7. package/packages/core/dist/consultation/dialogue-manager.d.ts +75 -0
  8. package/packages/core/dist/consultation/dialogue-manager.d.ts.map +1 -0
  9. package/packages/core/dist/consultation/dialogue-manager.js +334 -0
  10. package/packages/core/dist/consultation/dialogue-manager.js.map +1 -0
  11. package/packages/core/dist/consultation/dialogue-manager.test.d.ts +19 -0
  12. package/packages/core/dist/consultation/dialogue-manager.test.d.ts.map +1 -0
  13. package/packages/core/dist/consultation/dialogue-manager.test.js +444 -0
  14. package/packages/core/dist/consultation/dialogue-manager.test.js.map +1 -0
  15. package/packages/core/dist/server/api/git.d.ts +13 -1
  16. package/packages/core/dist/server/api/git.d.ts.map +1 -1
  17. package/packages/core/dist/server/api/git.js +53 -34
  18. package/packages/core/dist/server/api/git.js.map +1 -1
  19. package/packages/core/dist/server/otlp-receiver.d.ts +16 -11
  20. package/packages/core/dist/server/otlp-receiver.d.ts.map +1 -1
  21. package/packages/core/dist/server/otlp-receiver.js +185 -24
  22. package/packages/core/dist/server/otlp-receiver.js.map +1 -1
  23. package/packages/core/dist/server/otlp-receiver.test.d.ts +21 -0
  24. package/packages/core/dist/server/otlp-receiver.test.d.ts.map +1 -0
  25. package/packages/core/dist/server/otlp-receiver.test.js +446 -0
  26. package/packages/core/dist/server/otlp-receiver.test.js.map +1 -0
  27. package/packages/core/dist/shared/portrait-resolver.d.ts +9 -0
  28. package/packages/core/dist/shared/portrait-resolver.d.ts.map +1 -1
  29. package/packages/core/dist/shared/portrait-resolver.js +27 -0
  30. package/packages/core/dist/shared/portrait-resolver.js.map +1 -1
  31. package/packages/core/dist/shared/portrait-resolver.test.js +47 -1
  32. package/packages/core/dist/shared/portrait-resolver.test.js.map +1 -1
  33. package/packages/core/dist/shared/skill-search.test.js +2 -2
  34. package/packages/core/dist/shared/tandem-portrait-inventory.test.d.ts +13 -0
  35. package/packages/core/dist/shared/tandem-portrait-inventory.test.d.ts.map +1 -0
  36. package/packages/core/dist/shared/tandem-portrait-inventory.test.js +126 -0
  37. package/packages/core/dist/shared/tandem-portrait-inventory.test.js.map +1 -0
  38. package/pennyfarthing-dist/agents/dev.md +1 -1
  39. package/pennyfarthing-dist/agents/reviewer.md +1 -1
  40. package/pennyfarthing-dist/agents/sm-setup.md +1 -1
  41. package/pennyfarthing-dist/agents/sm.md +2 -2
  42. package/pennyfarthing-dist/agents/tea.md +1 -1
  43. package/pennyfarthing-dist/agents/testing-runner.md +2 -1
  44. package/pennyfarthing-dist/commands/pf-chore.md +2 -2
  45. package/pennyfarthing-dist/commands/pf-standalone.md +7 -2
  46. package/pennyfarthing-dist/guides/agent-behavior.md +1 -1
  47. package/pennyfarthing-dist/guides/agent-tag-taxonomy.md +1 -1
  48. package/pennyfarthing-dist/guides/bikerack.md +3 -3
  49. package/pennyfarthing-dist/guides/hooks.md +1 -1
  50. package/pennyfarthing-dist/guides/worktree-mode.md +3 -3
  51. package/pennyfarthing-dist/guides/xml-tags.md +2 -2
  52. package/pennyfarthing-dist/scripts/README.md +1 -1
  53. package/pennyfarthing-dist/scripts/core/agent-session.sh +0 -0
  54. package/pennyfarthing-dist/scripts/core/check-context.sh +1 -1
  55. package/pennyfarthing-dist/scripts/core/dialogue-manager.sh +322 -0
  56. package/pennyfarthing-dist/scripts/core/phase-check-start.sh +0 -0
  57. package/pennyfarthing-dist/scripts/core/prime.sh +0 -0
  58. package/pennyfarthing-dist/scripts/cyclist/is-cyclist.sh +0 -0
  59. package/pennyfarthing-dist/scripts/git/README.md +24 -14
  60. package/pennyfarthing-dist/scripts/git/create-feature-branches.sh +5 -266
  61. package/pennyfarthing-dist/scripts/git/git-status-all.sh +5 -151
  62. package/pennyfarthing-dist/scripts/git/install-git-hooks.sh +6 -144
  63. package/pennyfarthing-dist/scripts/git/release.sh +0 -0
  64. package/pennyfarthing-dist/scripts/git/worktree-manager.sh +5 -496
  65. package/pennyfarthing-dist/scripts/health/drift-detection.sh +0 -0
  66. package/pennyfarthing-dist/scripts/hooks/README.md +1 -1
  67. package/pennyfarthing-dist/scripts/hooks/bell-mode-hook.sh +1 -1
  68. package/pennyfarthing-dist/scripts/hooks/context-circuit-breaker.sh +0 -0
  69. package/pennyfarthing-dist/scripts/hooks/context-warning.sh +0 -0
  70. package/pennyfarthing-dist/scripts/hooks/cyclist-pretooluse-hook.sh +0 -0
  71. package/pennyfarthing-dist/scripts/hooks/dispatcher-template.sh +0 -0
  72. package/pennyfarthing-dist/scripts/hooks/otel-auto-config.sh +9 -11
  73. package/pennyfarthing-dist/scripts/hooks/post-merge.sh +0 -0
  74. package/pennyfarthing-dist/scripts/hooks/pre-commit.sh +0 -0
  75. package/pennyfarthing-dist/scripts/hooks/pre-edit-check.sh +0 -0
  76. package/pennyfarthing-dist/scripts/hooks/pre-push.sh +0 -0
  77. package/pennyfarthing-dist/scripts/hooks/question-reflector-check.sh +0 -0
  78. package/pennyfarthing-dist/scripts/hooks/question_reflector_check.py +0 -0
  79. package/pennyfarthing-dist/scripts/hooks/schema-validation.sh +0 -0
  80. package/pennyfarthing-dist/scripts/hooks/session-start.sh +0 -0
  81. package/pennyfarthing-dist/scripts/hooks/session-stop.sh +0 -0
  82. package/pennyfarthing-dist/scripts/hooks/sprint-yaml-validation.sh +0 -0
  83. package/pennyfarthing-dist/scripts/hooks/welcome-hook.sh +1 -1
  84. package/pennyfarthing-dist/scripts/jira/create-jira-epic.sh +0 -0
  85. package/pennyfarthing-dist/scripts/jira/create-jira-story.sh +0 -0
  86. package/pennyfarthing-dist/scripts/jira/jira-claim-story.sh +0 -0
  87. package/pennyfarthing-dist/scripts/jira/jira-reconcile.sh +0 -0
  88. package/pennyfarthing-dist/scripts/jira/jira-sync-story.sh +0 -0
  89. package/pennyfarthing-dist/scripts/jira/sync-epic-jira.sh +0 -0
  90. package/pennyfarthing-dist/scripts/lib/background-tasks.sh +0 -0
  91. package/pennyfarthing-dist/scripts/lib/checkpoint.sh +0 -0
  92. package/pennyfarthing-dist/scripts/lib/common.sh +0 -0
  93. package/pennyfarthing-dist/scripts/lib/file-lock.sh +0 -0
  94. package/pennyfarthing-dist/scripts/lib/logging.sh +0 -0
  95. package/pennyfarthing-dist/scripts/lib/retry.sh +0 -0
  96. package/pennyfarthing-dist/scripts/maintenance/migrate-theme-schema.mjs +0 -0
  97. package/pennyfarthing-dist/scripts/maintenance/sidecar-health.sh +0 -0
  98. package/pennyfarthing-dist/scripts/misc/add-short-names.sh +0 -0
  99. package/pennyfarthing-dist/scripts/misc/add_short_names.py +0 -0
  100. package/pennyfarthing-dist/scripts/misc/backlog.sh +0 -0
  101. package/pennyfarthing-dist/scripts/misc/check-status.sh +0 -0
  102. package/pennyfarthing-dist/scripts/misc/find-related-work.sh +0 -0
  103. package/pennyfarthing-dist/scripts/misc/generate-skill-docs.sh +0 -0
  104. package/pennyfarthing-dist/scripts/misc/log-skill-usage.sh +0 -0
  105. package/pennyfarthing-dist/scripts/misc/migrate-bmad-workflow.sh +0 -0
  106. package/pennyfarthing-dist/scripts/misc/migrate_bmad_workflow.py +0 -0
  107. package/pennyfarthing-dist/scripts/misc/repo-scan.sh +0 -0
  108. package/pennyfarthing-dist/scripts/misc/repo-utils.sh +0 -0
  109. package/pennyfarthing-dist/scripts/misc/run-ci.sh +0 -0
  110. package/pennyfarthing-dist/scripts/misc/run-timestamp.sh +0 -0
  111. package/pennyfarthing-dist/scripts/misc/session-cleanup.sh +0 -0
  112. package/pennyfarthing-dist/scripts/misc/skill-usage-report.sh +0 -0
  113. package/pennyfarthing-dist/scripts/misc/statusline.sh +0 -0
  114. package/pennyfarthing-dist/scripts/misc/uninstall.sh +0 -0
  115. package/pennyfarthing-dist/scripts/misc/validate-subagent-frontmatter.sh +0 -0
  116. package/pennyfarthing-dist/scripts/portraits/generate-portraits.sh +0 -0
  117. package/pennyfarthing-dist/scripts/portraits/generate-tandem-portraits.sh +76 -0
  118. package/pennyfarthing-dist/scripts/story/create-story.sh +0 -0
  119. package/pennyfarthing-dist/scripts/story/size-story.sh +0 -0
  120. package/pennyfarthing-dist/scripts/story/story-template.sh +0 -0
  121. package/pennyfarthing-dist/scripts/tests/check.test.sh +0 -0
  122. package/pennyfarthing-dist/scripts/tests/dev-story-workflow-import.test.sh +0 -0
  123. package/pennyfarthing-dist/scripts/tests/epics-and-stories-workflow-import.test.sh +0 -0
  124. package/pennyfarthing-dist/scripts/tests/handoff-phase-update.test.sh +0 -0
  125. package/pennyfarthing-dist/scripts/tests/implementation-readiness-workflow-import.test.sh +0 -0
  126. package/pennyfarthing-dist/scripts/tests/migrate-bmad-workflow.test.sh +0 -0
  127. package/pennyfarthing-dist/scripts/tests/prd-workflow-import.test.sh +0 -0
  128. package/pennyfarthing-dist/scripts/tests/project-context-workflow-import.test.sh +0 -0
  129. package/pennyfarthing-dist/scripts/tests/test-character-voice.sh +0 -0
  130. package/pennyfarthing-dist/scripts/tests/test-drift-detection.sh +0 -0
  131. package/pennyfarthing-dist/scripts/tests/test-post-merge-hook.sh +0 -0
  132. package/pennyfarthing-dist/scripts/tests/test-session-checkpoint.sh +0 -0
  133. package/pennyfarthing-dist/scripts/tests/test-solo-command.sh +0 -0
  134. package/pennyfarthing-dist/scripts/tests/ux-design-workflow-import.test.sh +0 -0
  135. package/pennyfarthing-dist/scripts/theme/list-themes.sh +0 -0
  136. package/pennyfarthing-dist/scripts/validation/validate-agent-schema.sh +0 -0
  137. package/pennyfarthing-dist/scripts/workflow/check.py +0 -0
  138. package/pennyfarthing-dist/scripts/workflow/check.sh +0 -0
  139. package/pennyfarthing-dist/scripts/workflow/complete-step.py +0 -0
  140. package/pennyfarthing-dist/scripts/workflow/finish-story.sh +0 -0
  141. package/pennyfarthing-dist/scripts/workflow/fix-session-phase.sh +4 -221
  142. package/pennyfarthing-dist/scripts/workflow/get-workflow-type.py +0 -0
  143. package/pennyfarthing-dist/scripts/workflow/get-workflow-type.sh +5 -13
  144. package/pennyfarthing-dist/scripts/workflow/list-workflows.sh +4 -123
  145. package/pennyfarthing-dist/scripts/workflow/phase-owner.sh +4 -33
  146. package/pennyfarthing-dist/scripts/workflow/resume-workflow.sh +4 -156
  147. package/pennyfarthing-dist/scripts/workflow/show-workflow.sh +4 -131
  148. package/pennyfarthing-dist/scripts/workflow/start-workflow.sh +4 -249
  149. package/pennyfarthing-dist/scripts/workflow/workflow-status.sh +4 -160
  150. package/pennyfarthing-dist/skills/pf-bc/usage.md +1 -1
  151. package/pennyfarthing-dist/skills/pf-jira/examples.md +5 -2
  152. package/pennyfarthing-dist/skills/pf-story/scripts/create-story.sh +0 -0
  153. package/pennyfarthing-dist/skills/pf-story/scripts/size-story.sh +0 -0
  154. package/pennyfarthing-dist/skills/pf-story/scripts/story-template.sh +0 -0
  155. package/pennyfarthing-dist/skills/pf-workflow/examples.md +27 -16
  156. package/pennyfarthing-dist/skills/pf-workflow/skill.md +9 -12
  157. package/pennyfarthing-dist/skills/pf-workflow/usage.md +33 -8
  158. package/pennyfarthing-dist/workflows/bdd-tandem.yaml +18 -6
  159. package/pennyfarthing-dist/workflows/git-cleanup/steps/step-01-analyze.md +1 -1
  160. package/pennyfarthing-dist/workflows/git-cleanup/steps/step-04-verify.md +1 -1
  161. package/pennyfarthing-dist/workflows/git-cleanup/steps/step-05-complete.md +1 -1
  162. package/pennyfarthing-dist/workflows/review-tandem.yaml +65 -0
  163. package/pennyfarthing-dist/workflows/tdd-tandem.yaml +16 -8
  164. package/pennyfarthing_scripts/CLAUDE.md +26 -4
  165. package/pennyfarthing_scripts/__pycache__/cli.cpython-314.pyc +0 -0
  166. package/pennyfarthing_scripts/__pycache__/hooks.cpython-314.pyc +0 -0
  167. package/pennyfarthing_scripts/__pycache__/pretooluse_hook.cpython-314.pyc +0 -0
  168. package/pennyfarthing_scripts/__pycache__/session_start_hook.cpython-314.pyc +0 -0
  169. package/pennyfarthing_scripts/bc/__pycache__/cli.cpython-314.pyc +0 -0
  170. package/pennyfarthing_scripts/bc/cli.py +3 -5
  171. package/pennyfarthing_scripts/bikerack/__pycache__/background_panel.cpython-314.pyc +0 -0
  172. package/pennyfarthing_scripts/bikerack/__pycache__/base_panel.cpython-314.pyc +0 -0
  173. package/pennyfarthing_scripts/bikerack/__pycache__/changed_panel.cpython-314.pyc +0 -0
  174. package/pennyfarthing_scripts/bikerack/__pycache__/cli.cpython-314.pyc +0 -0
  175. package/pennyfarthing_scripts/bikerack/__pycache__/debug_panel.cpython-314.pyc +0 -0
  176. package/pennyfarthing_scripts/bikerack/__pycache__/diffs_panel.cpython-314.pyc +0 -0
  177. package/pennyfarthing_scripts/bikerack/__pycache__/git_panel.cpython-314.pyc +0 -0
  178. package/pennyfarthing_scripts/bikerack/__pycache__/launcher.cpython-314.pyc +0 -0
  179. package/pennyfarthing_scripts/bikerack/__pycache__/portrait.cpython-314.pyc +0 -0
  180. package/pennyfarthing_scripts/bikerack/__pycache__/progress_panel.cpython-314.pyc +0 -0
  181. package/pennyfarthing_scripts/bikerack/__pycache__/sprint_panel.cpython-314.pyc +0 -0
  182. package/pennyfarthing_scripts/bikerack/__pycache__/tui.cpython-314.pyc +0 -0
  183. package/pennyfarthing_scripts/bikerack/__pycache__/ws_client.cpython-314.pyc +0 -0
  184. package/pennyfarthing_scripts/bikerack/background_panel.py +86 -5
  185. package/pennyfarthing_scripts/bikerack/base_panel.py +62 -0
  186. package/pennyfarthing_scripts/bikerack/changed_panel.py +32 -28
  187. package/pennyfarthing_scripts/bikerack/cli.py +10 -11
  188. package/pennyfarthing_scripts/bikerack/debug_panel.py +31 -1
  189. package/pennyfarthing_scripts/bikerack/diffs_panel.py +74 -17
  190. package/pennyfarthing_scripts/bikerack/git_panel.py +103 -33
  191. package/pennyfarthing_scripts/bikerack/launcher.py +15 -15
  192. package/pennyfarthing_scripts/bikerack/progress_panel.py +315 -0
  193. package/pennyfarthing_scripts/bikerack/sprint_panel.py +158 -26
  194. package/pennyfarthing_scripts/bikerack/tui.py +336 -30
  195. package/pennyfarthing_scripts/bikerack/ws_client.py +2 -2
  196. package/pennyfarthing_scripts/cli.py +37 -65
  197. package/pennyfarthing_scripts/consultation/__init__.py +1 -0
  198. package/pennyfarthing_scripts/consultation/__pycache__/__init__.cpython-314.pyc +0 -0
  199. package/pennyfarthing_scripts/consultation/__pycache__/cli.cpython-314.pyc +0 -0
  200. package/pennyfarthing_scripts/consultation/cli.py +149 -0
  201. package/pennyfarthing_scripts/consultation/dialogue_manager.py +417 -0
  202. package/pennyfarthing_scripts/context.py +3 -3
  203. package/pennyfarthing_scripts/epic/__pycache__/__init__.cpython-314.pyc +0 -0
  204. package/pennyfarthing_scripts/epic/__pycache__/cli.cpython-314.pyc +0 -0
  205. package/pennyfarthing_scripts/git/__init__.py +12 -1
  206. package/pennyfarthing_scripts/git/__pycache__/__init__.cpython-314.pyc +0 -0
  207. package/pennyfarthing_scripts/git/__pycache__/create_branches.cpython-314.pyc +0 -0
  208. package/pennyfarthing_scripts/git/__pycache__/hooks_installer.cpython-314.pyc +0 -0
  209. package/pennyfarthing_scripts/git/__pycache__/repos.cpython-314.pyc +0 -0
  210. package/pennyfarthing_scripts/git/__pycache__/status_all.cpython-314.pyc +0 -0
  211. package/pennyfarthing_scripts/git/__pycache__/worktree.cpython-314.pyc +0 -0
  212. package/pennyfarthing_scripts/git/create_branches.py +3 -4
  213. package/pennyfarthing_scripts/git/hooks_installer.py +152 -0
  214. package/pennyfarthing_scripts/git/repos.py +196 -0
  215. package/pennyfarthing_scripts/git/status_all.py +27 -11
  216. package/pennyfarthing_scripts/git/worktree.py +302 -0
  217. package/pennyfarthing_scripts/git_group/__pycache__/__init__.cpython-314.pyc +0 -0
  218. package/pennyfarthing_scripts/git_group/__pycache__/cli.cpython-314.pyc +0 -0
  219. package/pennyfarthing_scripts/git_group/cli.py +143 -40
  220. package/pennyfarthing_scripts/handoff/__pycache__/__init__.cpython-314.pyc +0 -0
  221. package/pennyfarthing_scripts/handoff/__pycache__/cli.cpython-314.pyc +0 -0
  222. package/pennyfarthing_scripts/handoff/__pycache__/complete_phase.cpython-314.pyc +0 -0
  223. package/pennyfarthing_scripts/handoff/__pycache__/resolve_gate.cpython-314.pyc +0 -0
  224. package/pennyfarthing_scripts/handoff/complete_phase.py +12 -0
  225. package/pennyfarthing_scripts/handoff/resolve_gate.py +5 -14
  226. package/pennyfarthing_scripts/hooks/cyclist-pretooluse-hook.sh +0 -0
  227. package/pennyfarthing_scripts/hooks.py +3 -17
  228. package/pennyfarthing_scripts/pretooluse_hook.py +1 -1
  229. package/pennyfarthing_scripts/prime/__pycache__/heatmap.cpython-314.pyc +0 -0
  230. package/pennyfarthing_scripts/prime/__pycache__/workflow.cpython-314.pyc +0 -0
  231. package/pennyfarthing_scripts/prime/heatmap.py +655 -0
  232. package/pennyfarthing_scripts/session/__pycache__/__init__.cpython-314.pyc +0 -0
  233. package/pennyfarthing_scripts/session/__pycache__/cli.cpython-314.pyc +0 -0
  234. package/pennyfarthing_scripts/session_start_hook.py +1 -1
  235. package/pennyfarthing_scripts/sprint/__pycache__/loader.cpython-314.pyc +0 -0
  236. package/pennyfarthing_scripts/sprint/loader.py +15 -1
  237. package/pennyfarthing_scripts/sprint/story_finish.py +14 -0
  238. package/pennyfarthing_scripts/tests/__pycache__/test_handoff_cli.cpython-314-pytest-9.0.2.pyc +0 -0
  239. package/pennyfarthing_scripts/tests/__pycache__/test_handoff_e2e.cpython-314-pytest-9.0.2.pyc +0 -0
  240. package/pennyfarthing_scripts/tests/__pycache__/test_workflow_check.cpython-314-pytest-9.0.2.pyc +0 -0
  241. package/pennyfarthing_scripts/tests/test_bikerack.py +51 -51
  242. package/pennyfarthing_scripts/tests/test_dialogue_manager.py +811 -0
  243. package/pennyfarthing_scripts/tests/test_handoff_cli.py +16 -11
  244. package/pennyfarthing_scripts/tests/test_workflow_check.py +2 -3
  245. package/pennyfarthing_scripts/validate/__pycache__/cli.cpython-314.pyc +0 -0
  246. package/pennyfarthing_scripts/validate/adapters/tandem_awareness.py +254 -0
  247. package/pennyfarthing_scripts/validate/cli.py +17 -5
  248. package/pennyfarthing_scripts/workflow/__init__.py +40 -0
  249. package/pennyfarthing_scripts/workflow/__pycache__/__init__.cpython-314.pyc +0 -0
  250. package/pennyfarthing_scripts/workflow/__pycache__/cli.cpython-314.pyc +0 -0
  251. package/pennyfarthing_scripts/workflow/__pycache__/helpers.cpython-314.pyc +0 -0
  252. package/pennyfarthing_scripts/workflow/__pycache__/scale.cpython-314.pyc +0 -0
  253. package/pennyfarthing_scripts/workflow/__pycache__/state.cpython-314.pyc +0 -0
  254. package/pennyfarthing_scripts/workflow/cli.py +1099 -0
  255. package/pennyfarthing_scripts/workflow/helpers.py +241 -0
  256. package/pennyfarthing_scripts/{workflow.py → workflow/scale.py} +0 -104
  257. package/pennyfarthing_scripts/workflow/state.py +112 -0
  258. package/scripts/README.md +41 -0
  259. package/pennyfarthing-dist/skills/pf-workflow/scripts/list-workflows.sh +0 -91
  260. package/pennyfarthing-dist/skills/pf-workflow/scripts/resume-workflow.sh +0 -163
  261. package/pennyfarthing-dist/skills/pf-workflow/scripts/show-workflow.sh +0 -138
  262. package/pennyfarthing-dist/skills/pf-workflow/scripts/start-workflow.sh +0 -273
  263. package/pennyfarthing-dist/skills/pf-workflow/scripts/workflow-status.sh +0 -167
@@ -295,21 +295,26 @@ class TestResolveGateReady:
295
295
 
296
296
 
297
297
  class TestResolveGateBlocked:
298
- """AC1 + AC5: resolve-gate returns 'blocked' when no assessment."""
298
+ """AC1 + AC5: resolve-gate no longer blocks on missing assessment.
299
299
 
300
- def test_missing_assessment_returns_blocked(
300
+ Assessment guard moved to complete_phase to prevent race conditions
301
+ where agents call resolve-gate before writing their assessment.
302
+ """
303
+
304
+ def test_missing_assessment_resolve_gate_still_ready(
301
305
  self, project: Path, session_without_assessment: Path
302
306
  ) -> None:
303
- """AC1: Missing assessment section status: blocked."""
307
+ """resolve-gate returns ready regardless of assessment (guard moved to complete-phase)."""
304
308
  result = resolve_gate("105-1", "tdd", "green", project_root=project)
305
- assert result["status"] == "blocked"
309
+ assert result["status"] == "ready"
306
310
 
307
- def test_blocked_has_assessment_found_false(
311
+ def test_missing_assessment_complete_phase_blocks(
308
312
  self, project: Path, session_without_assessment: Path
309
313
  ) -> None:
310
- """AC1: Blocked result should have assessment_found: False."""
311
- result = resolve_gate("105-1", "tdd", "green", project_root=project)
312
- assert result["assessment_found"] is False
314
+ """complete-phase blocks when no assessment found in session file."""
315
+ result = complete_phase("105-1", "tdd", "green", "review", "tests_pass", project)
316
+ assert result["status"] == "error"
317
+ assert "assessment" in result["error"].lower()
313
318
 
314
319
  def test_blocked_exit_code_one(self, runner: CliRunner) -> None:
315
320
  """AC5: Exit code 1 when gate resolves to blocked."""
@@ -383,10 +388,10 @@ class TestResolveGateErrors:
383
388
  result = resolve_gate("105-1", "tdd", "nonexistent", project_root=project)
384
389
  assert result["status"] == "error" or result.get("error") is not None
385
390
 
386
- def test_missing_session_file_returns_blocked(self, project: Path) -> None:
387
- """AC1: No session file blocked (can't check assessment)."""
391
+ def test_missing_session_file_resolve_gate_ready(self, project: Path) -> None:
392
+ """resolve-gate returns ready even without session file (guard moved to complete-phase)."""
388
393
  result = resolve_gate("105-1", "tdd", "green", project_root=project)
389
- assert result["status"] in ("blocked", "error")
394
+ assert result["status"] == "ready"
390
395
 
391
396
 
392
397
  class TestResolveGateOutputContract:
@@ -24,9 +24,8 @@ from click.testing import CliRunner
24
24
  from pennyfarthing_scripts.cli import cli
25
25
  from pennyfarthing_scripts.workflow import get_workflow_state
26
26
 
27
- # Mock path: since cli.py does `from pennyfarthing_scripts.workflow import get_workflow_state`
28
- # inside the command function, we mock at the source module
29
- MOCK_PATH = "pennyfarthing_scripts.workflow.get_workflow_state"
27
+ # Mock path: workflow.cli imports from workflow.state, so mock at the source module
28
+ MOCK_PATH = "pennyfarthing_scripts.workflow.state.get_workflow_state"
30
29
 
31
30
 
32
31
  class TestWorkflowCheckCLI:
@@ -0,0 +1,254 @@
1
+ """Tandem awareness validator adapter.
2
+
3
+ Validates that agent definitions include proper tandem consultation sections
4
+ per ADR-0012 and the tandem-consultation protocol.
5
+
6
+ Story: MSSCI-14499 (86-4)
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 <tandem-consultation> section content
17
+ _TANDEM_RE = re.compile(
18
+ r"<tandem-consultation>(.*?)</tandem-consultation>", re.DOTALL
19
+ )
20
+
21
+ # Regex to extract role from heading: ## Tandem Consultation (Leader + Partner)
22
+ _HEADING_RE = re.compile(r"##\s+Tandem Consultation\s*\(([^)]+)\)")
23
+
24
+ # Required response format fields for partner agents
25
+ _PARTNER_FIELDS = {
26
+ "Recommendation": "**Recommendation:**",
27
+ "Rationale": "**Rationale:**",
28
+ "Watch-Out-For": "**Watch-Out-For:**",
29
+ "Confidence": "**Confidence:**",
30
+ }
31
+
32
+ # ADR-0012 high-value pairings (leader, partner) — directional
33
+ ADR_0012_PAIRINGS: list[tuple[str, str]] = [
34
+ ("dev", "architect"),
35
+ ("dev", "tea"),
36
+ ("reviewer", "architect"),
37
+ ("dev", "devops"),
38
+ ]
39
+
40
+
41
+ def _extract_tandem_section(content: str) -> str | None:
42
+ """Extract content between <tandem-consultation> tags."""
43
+ m = _TANDEM_RE.search(content)
44
+ return m.group(1) if m else None
45
+
46
+
47
+ def classify_tandem_roles(
48
+ agents_dir: Path,
49
+ ) -> tuple[list[Path], list[Path]]:
50
+ """Classify agent files into leaders and partners based on tandem sections.
51
+
52
+ Classification is based on the heading within the tandem-consultation section:
53
+ - "(Leader)" → leader only
54
+ - "(Partner)" → partner only
55
+ - "(Leader + Partner)" → both
56
+
57
+ Returns:
58
+ (leaders, partners) — two lists of Path objects. An agent may appear in both.
59
+ """
60
+ leaders: list[Path] = []
61
+ partners: list[Path] = []
62
+
63
+ for f in sorted(agents_dir.glob("*.md")):
64
+ if f.name == "README.md":
65
+ continue
66
+
67
+ content = f.read_text()
68
+ section = _extract_tandem_section(content)
69
+ if section is None:
70
+ continue
71
+
72
+ # Match heading like "## Tandem Consultation (Leader + Partner)"
73
+ heading = _HEADING_RE.search(section)
74
+ if heading:
75
+ role = heading.group(1)
76
+ if "Leader" in role:
77
+ leaders.append(f)
78
+ if "Partner" in role:
79
+ partners.append(f)
80
+
81
+ return leaders, partners
82
+
83
+
84
+ def validate_leader_tandem(path: Path) -> tuple[list[str], list[str]]:
85
+ """Validate leader tandem consultation section content.
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_tandem_section(content)
94
+
95
+ if section is None:
96
+ errors.append("Missing <tandem-consultation> section")
97
+ return errors, warnings
98
+
99
+ stripped = section.strip()
100
+ if not stripped:
101
+ errors.append("Empty <tandem-consultation> section — no content")
102
+ return errors, warnings
103
+
104
+ # Workflow phase check — must reference tandem.mode
105
+ if "tandem.mode" not in section:
106
+ errors.append(
107
+ "Leader section must reference workflow phase availability (tandem.mode)"
108
+ )
109
+
110
+ # Request format template (recommended)
111
+ if "request format" not in section.lower():
112
+ warnings.append("Missing request format template in leader section")
113
+
114
+ # Graceful degradation guidance (recommended)
115
+ section_lower = section.lower()
116
+ if not any(term in section_lower for term in ("fail", "degrad", "solo")):
117
+ warnings.append(
118
+ "Missing graceful degradation guidance (what to do if consultation fails)"
119
+ )
120
+
121
+ return errors, warnings
122
+
123
+
124
+ def validate_partner_tandem(path: Path) -> tuple[list[str], list[str]]:
125
+ """Validate partner tandem consultation response guidance.
126
+
127
+ Returns:
128
+ (errors, warnings) — two lists of message strings.
129
+ """
130
+ errors: list[str] = []
131
+ warnings: list[str] = []
132
+ content = path.read_text()
133
+ section = _extract_tandem_section(content)
134
+
135
+ if section is None:
136
+ errors.append("Missing <tandem-consultation> section")
137
+ return errors, warnings
138
+
139
+ # Check for required response format fields
140
+ missing = [
141
+ name for name, marker in _PARTNER_FIELDS.items() if marker not in section
142
+ ]
143
+ if missing:
144
+ errors.append(
145
+ f"Partner response format missing required fields: {', '.join(missing)}"
146
+ )
147
+
148
+ return errors, warnings
149
+
150
+
151
+ def validate_pairings_documented(
152
+ leader_names: set[str],
153
+ partner_names: set[str],
154
+ pairings: list[tuple[str, str]],
155
+ ) -> tuple[list[tuple[str, str]], list[tuple[str, str]]]:
156
+ """Check which ADR-0012 pairings are covered by agent tandem sections.
157
+
158
+ Directional check: verifies the leader agent is classified as a leader
159
+ and the partner agent is classified as a partner.
160
+
161
+ Returns:
162
+ (covered, missing) — two lists of (leader, partner) tuples.
163
+ """
164
+ covered: list[tuple[str, str]] = []
165
+ missing: list[tuple[str, str]] = []
166
+
167
+ for leader, partner in pairings:
168
+ if leader in leader_names and partner in partner_names:
169
+ covered.append((leader, partner))
170
+ else:
171
+ missing.append((leader, partner))
172
+
173
+ return covered, missing
174
+
175
+
176
+ def run(
177
+ root: Path, *, fix: bool = False, strict: bool = False
178
+ ) -> ValidateReport:
179
+ """Validate agent tandem awareness sections."""
180
+ report = ValidateReport(validator="tandem-awareness")
181
+ agents_dir = root / "pennyfarthing-dist" / "agents"
182
+
183
+ if not agents_dir.is_dir():
184
+ report.details.append("[ERROR] agents directory not found")
185
+ report.errors += 1
186
+ return report
187
+
188
+ leaders, partners = classify_tandem_roles(agents_dir)
189
+
190
+ for path in leaders:
191
+ file_errors, file_warnings = validate_leader_tandem(path)
192
+
193
+ for e in file_errors:
194
+ report.errors += 1
195
+ report.details.append(f"[ERROR] {path.name}: {e}")
196
+
197
+ for w in file_warnings:
198
+ if strict:
199
+ report.errors += 1
200
+ report.details.append(f"[ERROR] {path.name}: {w}")
201
+ else:
202
+ report.warnings += 1
203
+ report.details.append(f"[WARN] {path.name}: {w}")
204
+
205
+ if not file_errors:
206
+ report.passed += 1
207
+
208
+ for path in partners:
209
+ file_errors, file_warnings = validate_partner_tandem(path)
210
+
211
+ for e in file_errors:
212
+ report.errors += 1
213
+ report.details.append(f"[ERROR] {path.name}: {e}")
214
+
215
+ for w in file_warnings:
216
+ if strict:
217
+ report.errors += 1
218
+ report.details.append(f"[ERROR] {path.name}: {w}")
219
+ else:
220
+ report.warnings += 1
221
+ report.details.append(f"[WARN] {path.name}: {w}")
222
+
223
+ if not file_errors:
224
+ report.passed += 1
225
+
226
+ # Validate ADR-0012 pairings (directional)
227
+ leader_names = {f.stem for f in leaders}
228
+ partner_names = {f.stem for f in partners}
229
+ covered, missing_pairings = validate_pairings_documented(
230
+ leader_names, partner_names, ADR_0012_PAIRINGS
231
+ )
232
+ for leader_name, partner_name in missing_pairings:
233
+ report.errors += 1
234
+ report.details.append(
235
+ f"[ERROR] ADR-0012 pairing {leader_name}\u2192{partner_name} not covered "
236
+ f"(leader or partner missing tandem section with correct role)"
237
+ )
238
+ if covered:
239
+ report.passed += 1
240
+
241
+ # Detect agents with tandem section but no matching heading
242
+ classified_stems = leader_names | partner_names
243
+ for f in sorted(agents_dir.glob("*.md")):
244
+ if f.name == "README.md":
245
+ continue
246
+ content = f.read_text()
247
+ if _extract_tandem_section(content) is not None and f.stem not in classified_stems:
248
+ report.warnings += 1
249
+ report.details.append(
250
+ f"[WARN] {f.name}: has <tandem-consultation> section but no "
251
+ f"matching '## Tandem Consultation (Role)' heading"
252
+ )
253
+
254
+ return report
@@ -22,6 +22,7 @@ VALIDATORS = {
22
22
  "agent": "pennyfarthing_scripts.validate.adapters.agent",
23
23
  "workflow": "pennyfarthing_scripts.validate.adapters.workflow",
24
24
  "skill-command": "pennyfarthing_scripts.validate.adapters.skill_command",
25
+ "tandem-awareness": "pennyfarthing_scripts.validate.adapters.tandem_awareness",
25
26
  }
26
27
 
27
28
 
@@ -83,11 +84,12 @@ def validate(ctx, fix: bool, strict: bool):
83
84
 
84
85
  \b
85
86
  Validators:
86
- sprint - Sprint YAML (epics, initiatives, future, current-sprint)
87
- schema - XML schema (sessions, skills, workflow steps)
88
- agent - Agent definitions (required sections, model values, subagent refs)
89
- workflow - Workflow definitions (phased/stepped/procedural structure)
90
- skill-command - Skill registry and command files (prefix, deprecated, cross-ref)
87
+ sprint - Sprint YAML (epics, initiatives, future, current-sprint)
88
+ schema - XML schema (sessions, skills, workflow steps)
89
+ agent - Agent definitions (required sections, model values, subagent refs)
90
+ workflow - Workflow definitions (phased/stepped/procedural structure)
91
+ skill-command - Skill registry and command files (prefix, deprecated, cross-ref)
92
+ tandem-awareness - Agent tandem consultation sections (ADR-0012 pairings)
91
93
  """
92
94
  ctx.ensure_object(dict)
93
95
  ctx.obj["fix"] = fix
@@ -150,3 +152,13 @@ def validate_skill_command(ctx):
150
152
  _print_reports([report])
151
153
  if not report.success:
152
154
  raise SystemExit(1)
155
+
156
+
157
+ @validate.command("tandem-awareness")
158
+ @click.pass_context
159
+ def validate_tandem_awareness(ctx):
160
+ """Validate agent tandem consultation sections (ADR-0012 pairings, roles)."""
161
+ report = _run_validator("tandem-awareness", fix=ctx.obj["fix"], strict=ctx.obj["strict"])
162
+ _print_reports([report])
163
+ if not report.success:
164
+ raise SystemExit(1)
@@ -0,0 +1,40 @@
1
+ """
2
+ Workflow package — scale levels, phase ownership, and workflow CLI commands.
3
+
4
+ Re-exports all public symbols for backward compatibility with
5
+ `from pennyfarthing_scripts.workflow import ...`.
6
+ """
7
+
8
+ from pennyfarthing_scripts.workflow.scale import (
9
+ SCALE_LEVELS,
10
+ detect_scale_level,
11
+ determine_scale_level,
12
+ get_required_artifacts,
13
+ get_scale_level_info,
14
+ get_workflow_for_scale_level,
15
+ scale_level_from_story_count,
16
+ )
17
+ from pennyfarthing_scripts.workflow.state import (
18
+ TDD_PHASE_OWNERS,
19
+ TRIVIAL_PHASE_OWNERS,
20
+ WORKFLOW_PHASES,
21
+ get_phase_owner,
22
+ get_workflow_state,
23
+ )
24
+
25
+ __all__ = [
26
+ # Scale
27
+ "SCALE_LEVELS",
28
+ "detect_scale_level",
29
+ "determine_scale_level",
30
+ "get_required_artifacts",
31
+ "get_scale_level_info",
32
+ "get_workflow_for_scale_level",
33
+ "scale_level_from_story_count",
34
+ # State
35
+ "TDD_PHASE_OWNERS",
36
+ "TRIVIAL_PHASE_OWNERS",
37
+ "WORKFLOW_PHASES",
38
+ "get_phase_owner",
39
+ "get_workflow_state",
40
+ ]