@pennyfarthing/core 10.1.0 → 10.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 (407) hide show
  1. package/README.md +13 -18
  2. package/package.json +3 -1
  3. package/packages/core/dist/cli/commands/doctor-file-layout.test.js.map +1 -1
  4. package/packages/core/dist/cli/commands/doctor-legacy.test.js +24 -0
  5. package/packages/core/dist/cli/commands/doctor-legacy.test.js.map +1 -1
  6. package/packages/core/dist/cli/commands/doctor.d.ts.map +1 -1
  7. package/packages/core/dist/cli/commands/doctor.js +101 -15
  8. package/packages/core/dist/cli/commands/doctor.js.map +1 -1
  9. package/packages/core/dist/cli/commands/e2e-fresh-install.test.js +1 -1
  10. package/packages/core/dist/cli/commands/e2e-fresh-install.test.js.map +1 -1
  11. package/packages/core/dist/cli/commands/e2e-upgrade.test.js +1 -1
  12. package/packages/core/dist/cli/commands/e2e-upgrade.test.js.map +1 -1
  13. package/packages/core/dist/cli/commands/hooks-consolidation.test.js +2 -2
  14. package/packages/core/dist/cli/commands/hooks-consolidation.test.js.map +1 -1
  15. package/packages/core/dist/cli/commands/init-consolidation.test.js.map +1 -1
  16. package/packages/core/dist/cli/commands/uninstall.d.ts.map +1 -1
  17. package/packages/core/dist/cli/commands/uninstall.js +24 -13
  18. package/packages/core/dist/cli/commands/uninstall.js.map +1 -1
  19. package/packages/core/dist/cli/commands/update-consolidation.test.js +0 -10
  20. package/packages/core/dist/cli/commands/update-consolidation.test.js.map +1 -1
  21. package/packages/core/dist/cli/commands/update.js.map +1 -1
  22. package/packages/core/dist/cli/ocean-profiles.test.js.map +1 -1
  23. package/packages/core/dist/cli/theme-maker.test.js +64 -115
  24. package/packages/core/dist/cli/theme-maker.test.js.map +1 -1
  25. package/packages/core/dist/index.d.ts +1 -1
  26. package/packages/core/dist/index.d.ts.map +1 -1
  27. package/packages/core/dist/index.js +2 -2
  28. package/packages/core/dist/index.js.map +1 -1
  29. package/packages/core/dist/plugins/plugin-discovery.d.ts +116 -0
  30. package/packages/core/dist/plugins/plugin-discovery.d.ts.map +1 -0
  31. package/packages/core/dist/plugins/plugin-discovery.js +165 -0
  32. package/packages/core/dist/plugins/plugin-discovery.js.map +1 -0
  33. package/packages/core/dist/plugins/plugin-discovery.test.d.ts +22 -0
  34. package/packages/core/dist/plugins/plugin-discovery.test.d.ts.map +1 -0
  35. package/packages/core/dist/plugins/plugin-discovery.test.js +498 -0
  36. package/packages/core/dist/plugins/plugin-discovery.test.js.map +1 -0
  37. package/packages/core/dist/scripts/generate-spider-report.js.map +1 -1
  38. package/packages/core/dist/workflow/context-watch.d.ts +80 -0
  39. package/packages/core/dist/workflow/context-watch.d.ts.map +1 -0
  40. package/packages/core/dist/workflow/context-watch.js +235 -0
  41. package/packages/core/dist/workflow/context-watch.js.map +1 -0
  42. package/packages/core/dist/workflow/context-watch.test.d.ts +1 -0
  43. package/packages/core/dist/workflow/context-watch.test.d.ts.map +1 -0
  44. package/packages/core/dist/workflow/context-watch.test.js +746 -0
  45. package/packages/core/dist/workflow/context-watch.test.js.map +1 -0
  46. package/packages/core/dist/workflow/file-watch.d.ts +82 -0
  47. package/packages/core/dist/workflow/file-watch.d.ts.map +1 -0
  48. package/packages/core/dist/workflow/file-watch.js +198 -0
  49. package/packages/core/dist/workflow/file-watch.js.map +1 -0
  50. package/packages/core/dist/workflow/file-watch.test.d.ts +21 -0
  51. package/packages/core/dist/workflow/file-watch.test.d.ts.map +1 -0
  52. package/packages/core/dist/workflow/file-watch.test.js +469 -0
  53. package/packages/core/dist/workflow/file-watch.test.js.map +1 -0
  54. package/packages/core/dist/workflow/observation-writer.d.ts +79 -0
  55. package/packages/core/dist/workflow/observation-writer.d.ts.map +1 -0
  56. package/packages/core/dist/workflow/observation-writer.js +97 -0
  57. package/packages/core/dist/workflow/observation-writer.js.map +1 -0
  58. package/packages/core/dist/workflow/observation-writer.test.d.ts +18 -0
  59. package/packages/core/dist/workflow/observation-writer.test.d.ts.map +1 -0
  60. package/packages/core/dist/workflow/observation-writer.test.js +424 -0
  61. package/packages/core/dist/workflow/observation-writer.test.js.map +1 -0
  62. package/packages/core/dist/workflow/story-workflow-routing.test.js +4 -2
  63. package/packages/core/dist/workflow/story-workflow-routing.test.js.map +1 -1
  64. package/packages/core/dist/workflow/tandem-lifecycle.d.ts +117 -0
  65. package/packages/core/dist/workflow/tandem-lifecycle.d.ts.map +1 -0
  66. package/packages/core/dist/workflow/tandem-lifecycle.js +186 -0
  67. package/packages/core/dist/workflow/tandem-lifecycle.js.map +1 -0
  68. package/packages/core/dist/workflow/tandem-lifecycle.test.d.ts +16 -0
  69. package/packages/core/dist/workflow/tandem-lifecycle.test.d.ts.map +1 -0
  70. package/packages/core/dist/workflow/tandem-lifecycle.test.js +531 -0
  71. package/packages/core/dist/workflow/tandem-lifecycle.test.js.map +1 -0
  72. package/packages/core/dist/workflow/tool-watch.d.ts +68 -0
  73. package/packages/core/dist/workflow/tool-watch.d.ts.map +1 -0
  74. package/packages/core/dist/workflow/tool-watch.js +166 -0
  75. package/packages/core/dist/workflow/tool-watch.js.map +1 -0
  76. package/packages/core/dist/workflow/tool-watch.test.d.ts +18 -0
  77. package/packages/core/dist/workflow/tool-watch.test.d.ts.map +1 -0
  78. package/packages/core/dist/workflow/tool-watch.test.js +718 -0
  79. package/packages/core/dist/workflow/tool-watch.test.js.map +1 -0
  80. package/packages/core/dist/workflow/workflow-migration.test.js +8 -4
  81. package/packages/core/dist/workflow/workflow-migration.test.js.map +1 -1
  82. package/packages/core/dist/workflow/workflow-schema.d.ts +7 -0
  83. package/packages/core/dist/workflow/workflow-schema.d.ts.map +1 -1
  84. package/packages/core/dist/workflow/workflow-schema.js +44 -0
  85. package/packages/core/dist/workflow/workflow-schema.js.map +1 -1
  86. package/packages/core/dist/workflow/workflow-schema.test.d.ts.map +1 -1
  87. package/packages/core/dist/workflow/workflow-schema.test.js +192 -0
  88. package/packages/core/dist/workflow/workflow-schema.test.js.map +1 -1
  89. package/pennyfarthing-dist/agents/handoff.md +18 -3
  90. package/pennyfarthing-dist/agents/sm-finish.md +1 -1
  91. package/pennyfarthing-dist/agents/sm-handoff.md +27 -4
  92. package/pennyfarthing-dist/agents/sm.md +11 -5
  93. package/pennyfarthing-dist/agents/tandem-backseat.md +119 -0
  94. package/pennyfarthing-dist/commands/setup.md +4 -0
  95. package/pennyfarthing-dist/guides/agent-behavior.md +62 -6
  96. package/pennyfarthing-dist/guides/bikelane.md +3 -2
  97. package/pennyfarthing-dist/guides/scale-levels.md +4 -6
  98. package/pennyfarthing-dist/guides/tandem-protocol.md +158 -0
  99. package/pennyfarthing-dist/personas/themes/discworld.yaml +1 -1
  100. package/pennyfarthing-dist/personas/themes/fifth-element.yaml +295 -0
  101. package/pennyfarthing-dist/scripts/README.md +1 -1
  102. package/pennyfarthing-dist/scripts/hooks/bell-mode-hook.sh +131 -54
  103. package/pennyfarthing-dist/scripts/hooks/post-merge.sh +20 -10
  104. package/pennyfarthing-dist/scripts/misc/statusline.sh +50 -8
  105. package/pennyfarthing-dist/scripts/workflow/README.md +2 -2
  106. package/pennyfarthing-dist/scripts/workflow/finish-story.sh +10 -189
  107. package/pennyfarthing-dist/skills/skill-registry.schema.json +8 -0
  108. package/pennyfarthing-dist/skills/skill-registry.yaml +1 -1
  109. package/pennyfarthing-dist/skills/sprint/skill.md +25 -2
  110. package/pennyfarthing-dist/skills/workflow/skill.md +24 -1
  111. package/pennyfarthing-dist/workflows/architecture/workflow.yaml +65 -0
  112. package/pennyfarthing-dist/workflows/bdd-tandem.yaml +70 -0
  113. package/pennyfarthing-dist/workflows/tdd-tandem.yaml +61 -0
  114. package/pennyfarthing_scripts/__pycache__/__init__.cpython-314.pyc +0 -0
  115. package/pennyfarthing_scripts/__pycache__/bellmode_hook.cpython-314.pyc +0 -0
  116. package/pennyfarthing_scripts/__pycache__/cli.cpython-314.pyc +0 -0
  117. package/pennyfarthing_scripts/__pycache__/config.cpython-314.pyc +0 -0
  118. package/pennyfarthing_scripts/__pycache__/hooks.cpython-314.pyc +0 -0
  119. package/pennyfarthing_scripts/__pycache__/jira_bidirectional_sync.cpython-314.pyc +0 -0
  120. package/pennyfarthing_scripts/__pycache__/jira_epic_creation.cpython-314.pyc +0 -0
  121. package/pennyfarthing_scripts/__pycache__/jira_sync.cpython-314.pyc +0 -0
  122. package/pennyfarthing_scripts/__pycache__/jira_sync_story.cpython-314.pyc +0 -0
  123. package/pennyfarthing_scripts/__pycache__/output.cpython-314.pyc +0 -0
  124. package/pennyfarthing_scripts/__pycache__/patch_mode.cpython-314.pyc +0 -0
  125. package/pennyfarthing_scripts/__pycache__/schema_validation_hook.cpython-314.pyc +0 -0
  126. package/pennyfarthing_scripts/__pycache__/workflow.cpython-314.pyc +0 -0
  127. package/pennyfarthing_scripts/bellmode_hook.py +202 -47
  128. package/pennyfarthing_scripts/brownfield/__init__.py +6 -6
  129. package/pennyfarthing_scripts/brownfield/__main__.py +1 -0
  130. package/pennyfarthing_scripts/brownfield/__pycache__/__init__.cpython-314.pyc +0 -0
  131. package/pennyfarthing_scripts/brownfield/__pycache__/__main__.cpython-314.pyc +0 -0
  132. package/pennyfarthing_scripts/brownfield/__pycache__/cli.cpython-314.pyc +0 -0
  133. package/pennyfarthing_scripts/brownfield/__pycache__/discover.cpython-314.pyc +0 -0
  134. package/pennyfarthing_scripts/brownfield/cli.py +0 -1
  135. package/pennyfarthing_scripts/brownfield/discover.py +1 -2
  136. package/pennyfarthing_scripts/cli.py +11 -6
  137. package/pennyfarthing_scripts/codemarkers/__init__.py +5 -1
  138. package/pennyfarthing_scripts/codemarkers/__pycache__/__init__.cpython-314.pyc +0 -0
  139. package/pennyfarthing_scripts/codemarkers/__pycache__/__main__.cpython-314.pyc +0 -0
  140. package/pennyfarthing_scripts/codemarkers/__pycache__/analyze.cpython-314.pyc +0 -0
  141. package/pennyfarthing_scripts/codemarkers/__pycache__/cli.cpython-314.pyc +0 -0
  142. package/pennyfarthing_scripts/codemarkers/__pycache__/formatters.cpython-314.pyc +0 -0
  143. package/pennyfarthing_scripts/codemarkers/__pycache__/models.cpython-314.pyc +0 -0
  144. package/pennyfarthing_scripts/codemarkers/analyze.py +177 -2
  145. package/pennyfarthing_scripts/codemarkers/cli.py +50 -0
  146. package/pennyfarthing_scripts/codemarkers/formatters.py +0 -1
  147. package/pennyfarthing_scripts/codemarkers/models.py +15 -0
  148. package/pennyfarthing_scripts/common/__init__.py +8 -9
  149. package/pennyfarthing_scripts/common/__pycache__/__init__.cpython-314.pyc +0 -0
  150. package/pennyfarthing_scripts/common/__pycache__/config.cpython-314.pyc +0 -0
  151. package/pennyfarthing_scripts/common/__pycache__/output.cpython-314.pyc +0 -0
  152. package/pennyfarthing_scripts/common/__pycache__/themes.cpython-314.pyc +0 -0
  153. package/pennyfarthing_scripts/common/config.py +1 -1
  154. package/pennyfarthing_scripts/complexity/__init__.py +1 -1
  155. package/pennyfarthing_scripts/complexity/__pycache__/__init__.cpython-314.pyc +0 -0
  156. package/pennyfarthing_scripts/complexity/__pycache__/__main__.cpython-314.pyc +0 -0
  157. package/pennyfarthing_scripts/complexity/__pycache__/analyze.cpython-314.pyc +0 -0
  158. package/pennyfarthing_scripts/complexity/__pycache__/cli.cpython-314.pyc +0 -0
  159. package/pennyfarthing_scripts/complexity/__pycache__/formatters.cpython-314.pyc +0 -0
  160. package/pennyfarthing_scripts/complexity/__pycache__/models.cpython-314.pyc +0 -0
  161. package/pennyfarthing_scripts/complexity/analyze.py +1 -1
  162. package/pennyfarthing_scripts/complexity/cli.py +5 -1
  163. package/pennyfarthing_scripts/complexity/formatters.py +1 -1
  164. package/pennyfarthing_scripts/context.py +14 -15
  165. package/pennyfarthing_scripts/deadcode/__pycache__/__init__.cpython-314.pyc +0 -0
  166. package/pennyfarthing_scripts/deadcode/__pycache__/__main__.cpython-314.pyc +0 -0
  167. package/pennyfarthing_scripts/deadcode/__pycache__/analyze.cpython-314.pyc +0 -0
  168. package/pennyfarthing_scripts/deadcode/__pycache__/cli.cpython-314.pyc +0 -0
  169. package/pennyfarthing_scripts/deadcode/__pycache__/formatters.cpython-314.pyc +0 -0
  170. package/pennyfarthing_scripts/deadcode/__pycache__/models.cpython-314.pyc +0 -0
  171. package/pennyfarthing_scripts/deadcode/analyze.py +3 -4
  172. package/pennyfarthing_scripts/deadcode/cli.py +2 -2
  173. package/pennyfarthing_scripts/dependencies/__init__.py +2 -2
  174. package/pennyfarthing_scripts/dependencies/__pycache__/__init__.cpython-314.pyc +0 -0
  175. package/pennyfarthing_scripts/dependencies/__pycache__/__main__.cpython-314.pyc +0 -0
  176. package/pennyfarthing_scripts/dependencies/__pycache__/analyze.cpython-314.pyc +0 -0
  177. package/pennyfarthing_scripts/dependencies/__pycache__/cli.cpython-314.pyc +0 -0
  178. package/pennyfarthing_scripts/dependencies/__pycache__/formatters.cpython-314.pyc +0 -0
  179. package/pennyfarthing_scripts/dependencies/__pycache__/models.cpython-314.pyc +0 -0
  180. package/pennyfarthing_scripts/dependencies/analyze.py +1 -1
  181. package/pennyfarthing_scripts/dependencies/cli.py +8 -4
  182. package/pennyfarthing_scripts/dependencies/formatters.py +1 -1
  183. package/pennyfarthing_scripts/git/__init__.py +5 -5
  184. package/pennyfarthing_scripts/git/__pycache__/__init__.cpython-314.pyc +0 -0
  185. package/pennyfarthing_scripts/git/__pycache__/create_branches.cpython-314.pyc +0 -0
  186. package/pennyfarthing_scripts/git/__pycache__/status_all.cpython-314.pyc +0 -0
  187. package/pennyfarthing_scripts/git/create_branches.py +3 -2
  188. package/pennyfarthing_scripts/git/status_all.py +1 -1
  189. package/pennyfarthing_scripts/healthscore/__init__.py +2 -2
  190. package/pennyfarthing_scripts/healthscore/__main__.py +8 -0
  191. package/pennyfarthing_scripts/healthscore/__pycache__/__init__.cpython-314.pyc +0 -0
  192. package/pennyfarthing_scripts/healthscore/__pycache__/__main__.cpython-314.pyc +0 -0
  193. package/pennyfarthing_scripts/healthscore/__pycache__/analyze.cpython-314.pyc +0 -0
  194. package/pennyfarthing_scripts/healthscore/__pycache__/cli.cpython-314.pyc +0 -0
  195. package/pennyfarthing_scripts/healthscore/__pycache__/formatters.cpython-314.pyc +0 -0
  196. package/pennyfarthing_scripts/healthscore/__pycache__/models.cpython-314.pyc +0 -0
  197. package/pennyfarthing_scripts/healthscore/analyze.py +451 -21
  198. package/pennyfarthing_scripts/healthscore/cli.py +5 -1
  199. package/pennyfarthing_scripts/healthscore/models.py +0 -1
  200. package/pennyfarthing_scripts/hooks.py +8 -11
  201. package/pennyfarthing_scripts/hotspots/__init__.py +6 -6
  202. package/pennyfarthing_scripts/hotspots/__pycache__/__init__.cpython-314.pyc +0 -0
  203. package/pennyfarthing_scripts/hotspots/__pycache__/__main__.cpython-314.pyc +0 -0
  204. package/pennyfarthing_scripts/hotspots/__pycache__/analyze.cpython-314.pyc +0 -0
  205. package/pennyfarthing_scripts/hotspots/__pycache__/cli.cpython-314.pyc +0 -0
  206. package/pennyfarthing_scripts/hotspots/__pycache__/formatters.cpython-314.pyc +0 -0
  207. package/pennyfarthing_scripts/hotspots/__pycache__/models.cpython-314.pyc +0 -0
  208. package/pennyfarthing_scripts/hotspots/analyze.py +128 -14
  209. package/pennyfarthing_scripts/hotspots/cli.py +2 -2
  210. package/pennyfarthing_scripts/hotspots/models.py +0 -1
  211. package/pennyfarthing_scripts/jira/__init__.py +15 -17
  212. package/pennyfarthing_scripts/jira/__pycache__/__init__.cpython-314.pyc +0 -0
  213. package/pennyfarthing_scripts/jira/__pycache__/__main__.cpython-314.pyc +0 -0
  214. package/pennyfarthing_scripts/jira/__pycache__/bidirectional.cpython-314.pyc +0 -0
  215. package/pennyfarthing_scripts/jira/__pycache__/claim.cpython-314.pyc +0 -0
  216. package/pennyfarthing_scripts/jira/__pycache__/cli.cpython-314.pyc +0 -0
  217. package/pennyfarthing_scripts/jira/__pycache__/client.cpython-314.pyc +0 -0
  218. package/pennyfarthing_scripts/jira/__pycache__/create.cpython-314.pyc +0 -0
  219. package/pennyfarthing_scripts/jira/__pycache__/epic.cpython-314.pyc +0 -0
  220. package/pennyfarthing_scripts/jira/__pycache__/operations.cpython-314.pyc +0 -0
  221. package/pennyfarthing_scripts/jira/__pycache__/reconcile.cpython-314.pyc +0 -0
  222. package/pennyfarthing_scripts/jira/__pycache__/story.cpython-314.pyc +0 -0
  223. package/pennyfarthing_scripts/jira/__pycache__/sync.cpython-314.pyc +0 -0
  224. package/pennyfarthing_scripts/jira/bidirectional.py +2 -3
  225. package/pennyfarthing_scripts/jira/claim.py +21 -0
  226. package/pennyfarthing_scripts/jira/cli.py +2 -2
  227. package/pennyfarthing_scripts/jira/client.py +4 -4
  228. package/pennyfarthing_scripts/jira/create.py +45 -1
  229. package/pennyfarthing_scripts/jira/epic.py +3 -2
  230. package/pennyfarthing_scripts/jira/reconcile.py +0 -1
  231. package/pennyfarthing_scripts/jira/story.py +2 -0
  232. package/pennyfarthing_scripts/jira/sync.py +1 -1
  233. package/pennyfarthing_scripts/migration/__pycache__/__init__.cpython-314.pyc +0 -0
  234. package/pennyfarthing_scripts/migration/__pycache__/session.cpython-314.pyc +0 -0
  235. package/pennyfarthing_scripts/migration/__pycache__/skill.cpython-314.pyc +0 -0
  236. package/pennyfarthing_scripts/migration/__pycache__/step.cpython-314.pyc +0 -0
  237. package/pennyfarthing_scripts/migration/__pycache__/validate.cpython-314.pyc +0 -0
  238. package/pennyfarthing_scripts/migration/skill.py +0 -1
  239. package/pennyfarthing_scripts/migration/step.py +0 -1
  240. package/pennyfarthing_scripts/migration/validate.py +8 -5
  241. package/pennyfarthing_scripts/patch_mode.py +2 -2
  242. package/pennyfarthing_scripts/preflight/__init__.py +1 -1
  243. package/pennyfarthing_scripts/preflight/__pycache__/__init__.cpython-314.pyc +0 -0
  244. package/pennyfarthing_scripts/preflight/__pycache__/__main__.cpython-314.pyc +0 -0
  245. package/pennyfarthing_scripts/preflight/__pycache__/cli.cpython-314.pyc +0 -0
  246. package/pennyfarthing_scripts/preflight/__pycache__/finish.cpython-314.pyc +0 -0
  247. package/pennyfarthing_scripts/preflight/finish.py +0 -1
  248. package/pennyfarthing_scripts/pretooluse_hook.py +6 -7
  249. package/pennyfarthing_scripts/prime/__pycache__/__init__.cpython-314.pyc +0 -0
  250. package/pennyfarthing_scripts/prime/__pycache__/cli.cpython-314.pyc +0 -0
  251. package/pennyfarthing_scripts/prime/__pycache__/loader.cpython-314.pyc +0 -0
  252. package/pennyfarthing_scripts/prime/__pycache__/models.cpython-314.pyc +0 -0
  253. package/pennyfarthing_scripts/prime/__pycache__/persona.cpython-314.pyc +0 -0
  254. package/pennyfarthing_scripts/prime/__pycache__/session.cpython-314.pyc +0 -0
  255. package/pennyfarthing_scripts/prime/__pycache__/tiers.cpython-314.pyc +0 -0
  256. package/pennyfarthing_scripts/prime/__pycache__/workflow.cpython-314.pyc +0 -0
  257. package/pennyfarthing_scripts/prime/cli.py +5 -1
  258. package/pennyfarthing_scripts/prime/loader.py +2 -3
  259. package/pennyfarthing_scripts/prime/persona.py +2 -1
  260. package/pennyfarthing_scripts/prime/tiers.py +4 -4
  261. package/pennyfarthing_scripts/schema_validation_hook.py +2 -3
  262. package/pennyfarthing_scripts/sprint/__init__.py +10 -12
  263. package/pennyfarthing_scripts/sprint/__main__.py +2 -2
  264. package/pennyfarthing_scripts/sprint/__pycache__/__init__.cpython-314.pyc +0 -0
  265. package/pennyfarthing_scripts/sprint/__pycache__/__main__.cpython-314.pyc +0 -0
  266. package/pennyfarthing_scripts/sprint/__pycache__/archive.cpython-314.pyc +0 -0
  267. package/pennyfarthing_scripts/sprint/__pycache__/archive_epic.cpython-314.pyc +0 -0
  268. package/pennyfarthing_scripts/sprint/__pycache__/cli.cpython-314.pyc +0 -0
  269. package/pennyfarthing_scripts/sprint/__pycache__/epic_add.cpython-314.pyc +0 -0
  270. package/pennyfarthing_scripts/sprint/__pycache__/import_epic.cpython-314.pyc +0 -0
  271. package/pennyfarthing_scripts/sprint/__pycache__/loader.cpython-314.pyc +0 -0
  272. package/pennyfarthing_scripts/sprint/__pycache__/status.cpython-314.pyc +0 -0
  273. package/pennyfarthing_scripts/sprint/__pycache__/story_add.cpython-314.pyc +0 -0
  274. package/pennyfarthing_scripts/sprint/__pycache__/story_finish.cpython-314.pyc +0 -0
  275. package/pennyfarthing_scripts/sprint/__pycache__/story_update.cpython-314.pyc +0 -0
  276. package/pennyfarthing_scripts/sprint/__pycache__/validate_cmd.cpython-314.pyc +0 -0
  277. package/pennyfarthing_scripts/sprint/__pycache__/validator.cpython-314.pyc +0 -0
  278. package/pennyfarthing_scripts/sprint/__pycache__/work.cpython-314.pyc +0 -0
  279. package/pennyfarthing_scripts/sprint/__pycache__/yaml_io.cpython-314.pyc +0 -0
  280. package/pennyfarthing_scripts/sprint/archive.py +0 -1
  281. package/pennyfarthing_scripts/sprint/archive_epic.py +1 -4
  282. package/pennyfarthing_scripts/sprint/cli.py +34 -28
  283. package/pennyfarthing_scripts/sprint/epic_add.py +8 -1
  284. package/pennyfarthing_scripts/sprint/import_epic.py +42 -18
  285. package/pennyfarthing_scripts/sprint/loader.py +6 -0
  286. package/pennyfarthing_scripts/sprint/status.py +1 -2
  287. package/pennyfarthing_scripts/sprint/story_add.py +2 -2
  288. package/pennyfarthing_scripts/sprint/story_finish.py +3 -5
  289. package/pennyfarthing_scripts/sprint/story_update.py +11 -3
  290. package/pennyfarthing_scripts/sprint/validate_cmd.py +0 -1
  291. package/pennyfarthing_scripts/sprint/validator.py +120 -6
  292. package/pennyfarthing_scripts/sprint/work.py +1 -4
  293. package/pennyfarthing_scripts/sprint/yaml_io.py +10 -2
  294. package/pennyfarthing_scripts/story/__init__.py +14 -16
  295. package/pennyfarthing_scripts/story/__pycache__/__init__.cpython-314.pyc +0 -0
  296. package/pennyfarthing_scripts/story/__pycache__/__main__.cpython-314.pyc +0 -0
  297. package/pennyfarthing_scripts/story/__pycache__/cli.cpython-314.pyc +0 -0
  298. package/pennyfarthing_scripts/story/__pycache__/create.cpython-314.pyc +0 -0
  299. package/pennyfarthing_scripts/story/__pycache__/size.cpython-314.pyc +0 -0
  300. package/pennyfarthing_scripts/story/__pycache__/template.cpython-314.pyc +0 -0
  301. package/pennyfarthing_scripts/story/size.py +0 -1
  302. package/pennyfarthing_scripts/story/template.py +0 -1
  303. package/pennyfarthing_scripts/swebench.py +1 -2
  304. package/pennyfarthing_scripts/tests/__pycache__/__init__.cpython-314.pyc +0 -0
  305. package/pennyfarthing_scripts/tests/__pycache__/conftest.cpython-314-pytest-9.0.2.pyc +0 -0
  306. package/pennyfarthing_scripts/tests/__pycache__/test_brownfield.cpython-314-pytest-9.0.2.pyc +0 -0
  307. package/pennyfarthing_scripts/tests/__pycache__/test_cli_modules.cpython-314-pytest-9.0.2.pyc +0 -0
  308. package/pennyfarthing_scripts/tests/__pycache__/test_codemarkers.cpython-314-pytest-9.0.2.pyc +0 -0
  309. package/pennyfarthing_scripts/tests/__pycache__/test_common.cpython-314-pytest-9.0.2.pyc +0 -0
  310. package/pennyfarthing_scripts/tests/__pycache__/test_git_utils.cpython-314-pytest-9.0.2.pyc +0 -0
  311. package/pennyfarthing_scripts/tests/__pycache__/test_healthscore.cpython-314-pytest-9.0.2.pyc +0 -0
  312. package/pennyfarthing_scripts/tests/__pycache__/test_jira_package.cpython-314-pytest-9.0.2.pyc +0 -0
  313. package/pennyfarthing_scripts/tests/__pycache__/test_package_structure.cpython-314-pytest-9.0.2.pyc +0 -0
  314. package/pennyfarthing_scripts/tests/__pycache__/test_patch_mode.cpython-314-pytest-9.0.2.pyc +0 -0
  315. package/pennyfarthing_scripts/tests/__pycache__/test_prime.cpython-314-pytest-9.0.2.pyc +0 -0
  316. package/pennyfarthing_scripts/tests/__pycache__/test_sprint_package.cpython-314-pytest-9.0.2.pyc +0 -0
  317. package/pennyfarthing_scripts/tests/__pycache__/test_sprint_validator.cpython-314-pytest-9.0.2.pyc +0 -0
  318. package/pennyfarthing_scripts/tests/__pycache__/test_story_add.cpython-314-pytest-9.0.2.pyc +0 -0
  319. package/pennyfarthing_scripts/tests/__pycache__/test_story_package.cpython-314-pytest-9.0.2.pyc +0 -0
  320. package/pennyfarthing_scripts/tests/__pycache__/test_story_update.cpython-314-pytest-9.0.2.pyc +0 -0
  321. package/pennyfarthing_scripts/tests/__pycache__/test_tiers.cpython-314-pytest-9.0.2.pyc +0 -0
  322. package/pennyfarthing_scripts/tests/__pycache__/test_token_counting.cpython-314-pytest-9.0.2.pyc +0 -0
  323. package/pennyfarthing_scripts/tests/__pycache__/test_validate_cmd.cpython-314-pytest-9.0.2.pyc +0 -0
  324. package/pennyfarthing_scripts/tests/__pycache__/test_workflow_check.cpython-314-pytest-9.0.2.pyc +0 -0
  325. package/pennyfarthing_scripts/tests/__pycache__/test_yaml_io.cpython-314-pytest-9.0.2.pyc +0 -0
  326. package/pennyfarthing_scripts/tests/conftest.py +1 -2
  327. package/pennyfarthing_scripts/tests/test_brownfield.py +10 -13
  328. package/pennyfarthing_scripts/tests/test_cli_modules.py +0 -4
  329. package/pennyfarthing_scripts/tests/test_codemarkers.py +13 -8
  330. package/pennyfarthing_scripts/tests/test_common.py +9 -4
  331. package/pennyfarthing_scripts/tests/test_epic_shard_validation.py +699 -0
  332. package/pennyfarthing_scripts/tests/test_git_utils.py +10 -13
  333. package/pennyfarthing_scripts/tests/test_healthscore.py +17 -25
  334. package/pennyfarthing_scripts/tests/test_jira_package.py +0 -3
  335. package/pennyfarthing_scripts/tests/test_package_structure.py +3 -16
  336. package/pennyfarthing_scripts/tests/test_patch_mode.py +7 -11
  337. package/pennyfarthing_scripts/tests/test_prime.py +39 -21
  338. package/pennyfarthing_scripts/tests/test_sprint_package.py +3 -8
  339. package/pennyfarthing_scripts/tests/test_sprint_validator.py +53 -5
  340. package/pennyfarthing_scripts/tests/test_story_add.py +3 -7
  341. package/pennyfarthing_scripts/tests/test_story_package.py +0 -3
  342. package/pennyfarthing_scripts/tests/test_story_update.py +5 -10
  343. package/pennyfarthing_scripts/tests/test_tiers.py +18 -17
  344. package/pennyfarthing_scripts/tests/test_token_counting.py +19 -13
  345. package/pennyfarthing_scripts/tests/test_validate_cmd.py +2 -7
  346. package/pennyfarthing_scripts/tests/test_workflow_check.py +0 -2
  347. package/pennyfarthing_scripts/tests/test_yaml_io.py +0 -3
  348. package/pennyfarthing_scripts/theme/__pycache__/__init__.cpython-314.pyc +0 -0
  349. package/pennyfarthing_scripts/theme/__pycache__/cli.cpython-314.pyc +0 -0
  350. package/pennyfarthing_scripts/theme/cli.py +3 -2
  351. package/pennyfarthing_scripts/validate/__init__.py +21 -0
  352. package/pennyfarthing_scripts/validate/__pycache__/__init__.cpython-314.pyc +0 -0
  353. package/pennyfarthing_scripts/validate/__pycache__/cli.cpython-314.pyc +0 -0
  354. package/pennyfarthing_scripts/validate/adapters/__init__.py +0 -0
  355. package/pennyfarthing_scripts/validate/adapters/__pycache__/__init__.cpython-314.pyc +0 -0
  356. package/pennyfarthing_scripts/validate/adapters/__pycache__/agent.cpython-314.pyc +0 -0
  357. package/pennyfarthing_scripts/validate/adapters/__pycache__/schema.cpython-314.pyc +0 -0
  358. package/pennyfarthing_scripts/validate/adapters/__pycache__/skill_command.cpython-314.pyc +0 -0
  359. package/pennyfarthing_scripts/validate/adapters/__pycache__/sprint.cpython-314.pyc +0 -0
  360. package/pennyfarthing_scripts/validate/adapters/__pycache__/workflow.cpython-314.pyc +0 -0
  361. package/pennyfarthing_scripts/validate/adapters/agent.py +239 -0
  362. package/pennyfarthing_scripts/validate/adapters/schema.py +30 -0
  363. package/pennyfarthing_scripts/validate/adapters/skill_command.py +292 -0
  364. package/pennyfarthing_scripts/validate/adapters/sprint.py +69 -0
  365. package/pennyfarthing_scripts/validate/adapters/workflow.py +320 -0
  366. package/pennyfarthing_scripts/validate/cli.py +141 -0
  367. package/pennyfarthing_scripts/welcome_hook.py +2 -3
  368. package/pennyfarthing_scripts/workflow.py +3 -3
  369. package/scripts/README.md +3 -15
  370. package/pennyfarthing-dist/commands/benchmark-control.md +0 -69
  371. package/pennyfarthing-dist/commands/benchmark.md +0 -485
  372. package/pennyfarthing-dist/commands/job-fair.md +0 -102
  373. package/pennyfarthing-dist/commands/solo.md +0 -447
  374. package/pennyfarthing-dist/guides/benchmarks.md +0 -62
  375. package/pennyfarthing-dist/scripts/hooks/__pycache__/question_reflector_check.cpython-314.pyc +0 -0
  376. package/pennyfarthing-dist/scripts/test/ensure-swebench-data.sh +0 -59
  377. package/pennyfarthing-dist/scripts/test/ground-truth-judge.py +0 -220
  378. package/pennyfarthing-dist/scripts/test/swebench-judge.py +0 -374
  379. package/pennyfarthing-dist/scripts/test/test-cache.sh +0 -165
  380. package/pennyfarthing-dist/scripts/test/test-setup.sh +0 -337
  381. package/pennyfarthing-dist/scripts/theme/compute-theme-tiers.sh +0 -13
  382. package/pennyfarthing-dist/scripts/theme/compute_theme_tiers.py +0 -402
  383. package/pennyfarthing-dist/scripts/theme/update-theme-tiers.sh +0 -97
  384. package/pennyfarthing-dist/skills/finalize-run/SKILL.md +0 -261
  385. package/pennyfarthing-dist/skills/judge/SKILL.md +0 -644
  386. package/pennyfarthing-dist/skills/persona-benchmark/SKILL.md +0 -187
  387. package/pennyfarthing-dist/workflows/dev-story/checklist.md +0 -80
  388. package/pennyfarthing-dist/workflows/dev-story/instructions.xml +0 -410
  389. package/pennyfarthing-dist/workflows/dev-story/workflow.yaml +0 -50
  390. package/pennyfarthing-dist/workflows/quick-spec/steps/step-01-understand.md +0 -201
  391. package/pennyfarthing-dist/workflows/quick-spec/steps/step-02-investigate.md +0 -156
  392. package/pennyfarthing-dist/workflows/quick-spec/steps/step-03-generate.md +0 -140
  393. package/pennyfarthing-dist/workflows/quick-spec/steps/step-04-review.md +0 -203
  394. package/pennyfarthing-dist/workflows/quick-spec/tech-spec-template.md +0 -74
  395. package/pennyfarthing-dist/workflows/quick-spec/workflow.yaml +0 -27
  396. package/pennyfarthing_scripts/__pycache__/__init__.cpython-311.pyc +0 -0
  397. package/pennyfarthing_scripts/__pycache__/jira.cpython-314.pyc +0 -0
  398. package/pennyfarthing_scripts/__pycache__/pretooluse_hook.cpython-314.pyc +0 -0
  399. package/pennyfarthing_scripts/__pycache__/sprint.cpython-314.pyc +0 -0
  400. package/pennyfarthing_scripts/__pycache__/workflow.cpython-311.pyc +0 -0
  401. package/pennyfarthing_scripts/jira/__pycache__/compat.cpython-314.pyc +0 -0
  402. package/pennyfarthing_scripts/jira/__pycache__/mappings.cpython-314.pyc +0 -0
  403. package/pennyfarthing_scripts/jira/__pycache__/models.cpython-314.pyc +0 -0
  404. package/pennyfarthing_scripts/migration/__pycache__/__main__.cpython-314.pyc +0 -0
  405. package/pennyfarthing_scripts/migration/__pycache__/cli.cpython-314.pyc +0 -0
  406. package/pennyfarthing_scripts/prime/__pycache__/__main__.cpython-314.pyc +0 -0
  407. package/pennyfarthing_scripts/tests/__pycache__/test_workflow_cli.cpython-314-pytest-9.0.2.pyc +0 -0
@@ -1,25 +1,27 @@
1
1
  #!/bin/bash
2
2
  #
3
- # Bell Mode PostToolUse Hook (Story MSSCI-12275)
3
+ # PostToolUse Hook — Bell Mode + Tandem Injection
4
4
  #
5
- # This hook is called by Claude Code after each tool execution.
6
- # When bell mode is enabled and there are queued messages, it returns
7
- # the first queued message as additionalContext to be injected into
8
- # Claude's next API call.
5
+ # Called by Claude Code after each tool execution. Handles two independent
6
+ # injection systems:
9
7
  #
10
- # Configuration files:
11
- # .pennyfarthing/config.local.yaml - workflow.bell_mode: true/false
12
- # .pennyfarthing/bell-queue.json - [{ "text": "...", "images": [...] }, ...]
8
+ # 1. Bell queue (Cyclist only) — injects queued user messages when Cyclist
9
+ # is running and bell_mode is enabled. In CLI sessions this is a no-op.
10
+ # 2. Tandem observations (always active) injects backseat agent observations
11
+ # when tandem observation files exist. No configuration required.
12
+ #
13
+ # Bell queue takes precedence: if a queued message exists, tandem is
14
+ # deferred to the next hook invocation.
13
15
  #
14
16
  # Output format (when injecting):
15
17
  # {
16
18
  # "hookSpecificOutput": {
17
19
  # "hookEventName": "PostToolUse",
18
- # "additionalContext": "User feedback: <message>"
20
+ # "additionalContext": "<message>"
19
21
  # }
20
22
  # }
21
23
  #
22
- # Output when disabled or queue empty: (nothing - exit 0)
24
+ # Output when nothing to inject: (nothing - exit 0)
23
25
 
24
26
  # Find project root (walk up to find .pennyfarthing)
25
27
  PROJECT_ROOT="$PWD"
@@ -31,76 +33,151 @@ while [[ "$PROJECT_ROOT" != "/" ]]; do
31
33
  done
32
34
 
33
35
  if [[ ! -d "$PROJECT_ROOT/.pennyfarthing" ]]; then
34
- # No .pennyfarthing directory found - exit silently
35
36
  exit 0
36
37
  fi
37
38
 
38
39
  CONFIG_LOCAL_YAML="$PROJECT_ROOT/.pennyfarthing/config.local.yaml"
39
40
  BELL_QUEUE_FILE="$PROJECT_ROOT/.pennyfarthing/bell-queue.json"
40
41
 
41
- # Check if bell mode is enabled in config.local.yaml
42
- if [[ ! -f "$CONFIG_LOCAL_YAML" ]]; then
43
- exit 0
42
+ # Detect Cyclist bell queue is a Cyclist-only feature
43
+ IS_CYCLIST=false
44
+ CYCLIST_PORT=""
45
+ PORT_FILE="$PROJECT_ROOT/.cyclist-port"
46
+ if [[ -f "$PORT_FILE" ]]; then
47
+ CYCLIST_PORT=$(cat "$PORT_FILE" 2>/dev/null)
48
+ if [[ -n "$CYCLIST_PORT" ]] && [[ "$CYCLIST_PORT" =~ ^[0-9]+$ ]]; then
49
+ IS_CYCLIST=true
50
+ fi
44
51
  fi
45
52
 
46
- # Parse YAML to check workflow.bell_mode - look for "bell_mode: true"
47
- # This handles both "bell_mode: true" and " bell_mode: true" (indented under workflow)
48
- ENABLED=$(grep -E '^\s*bell_mode:\s*true' "$CONFIG_LOCAL_YAML" 2>/dev/null || true)
49
- if [[ -z "$ENABLED" ]]; then
50
- exit 0
53
+ # --- Bell queue check (Cyclist only, requires bell_mode: true) ---
54
+ # Takes precedence over tandem injection when active.
55
+
56
+ if [[ "$IS_CYCLIST" == "true" ]] && [[ -f "$CONFIG_LOCAL_YAML" ]]; then
57
+ BELL_ENABLED=$(grep -E '^\s*bell_mode:\s*true' "$CONFIG_LOCAL_YAML" 2>/dev/null || true)
58
+
59
+ if [[ -n "$BELL_ENABLED" ]] && [[ -f "$BELL_QUEUE_FILE" ]]; then
60
+ QUEUE_CONTENT=$(cat "$BELL_QUEUE_FILE" 2>/dev/null)
61
+ if [[ -n "$QUEUE_CONTENT" ]] && [[ "$QUEUE_CONTENT" != "[]" ]]; then
62
+ FIRST_MESSAGE_TEXT=$(echo "$QUEUE_CONTENT" | sed -n 's/.*"text"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' | head -1)
63
+
64
+ if [[ -n "$FIRST_MESSAGE_TEXT" ]]; then
65
+ cat << EOF
66
+ {
67
+ "hookSpecificOutput": {
68
+ "hookEventName": "PostToolUse",
69
+ "additionalContext": "User feedback: $FIRST_MESSAGE_TEXT"
70
+ }
71
+ }
72
+ EOF
73
+
74
+ # Dequeue and notify Cyclist in background
75
+ (
76
+ if command -v jq &> /dev/null; then
77
+ jq 'if length > 0 then .[1:] else [] end' "$BELL_QUEUE_FILE" > "$BELL_QUEUE_FILE.tmp" 2>/dev/null && mv "$BELL_QUEUE_FILE.tmp" "$BELL_QUEUE_FILE"
78
+ fi
79
+
80
+ curl -s -X POST "http://localhost:$CYCLIST_PORT/api/bell-consumed" \
81
+ -H "Content-Type: application/json" \
82
+ -d "{\"text\": \"$FIRST_MESSAGE_TEXT\"}" \
83
+ >/dev/null 2>&1 || true
84
+ ) &
85
+
86
+ exit 0
87
+ fi
88
+ fi
89
+ fi
51
90
  fi
52
91
 
53
- # Check if queue file exists and has messages
54
- if [[ ! -f "$BELL_QUEUE_FILE" ]]; then
92
+ # --- Tandem observation injection ---
93
+ # Always active. No configuration required.
94
+ # Checks .session/*-tandem-*.md files for new observations via mtime comparison.
95
+
96
+ SESSION_DIR="$PROJECT_ROOT/.session"
97
+ if [[ ! -d "$SESSION_DIR" ]]; then
55
98
  exit 0
56
99
  fi
57
100
 
58
- # Read queue and check if non-empty
59
- QUEUE_CONTENT=$(cat "$BELL_QUEUE_FILE" 2>/dev/null)
60
- if [[ -z "$QUEUE_CONTENT" ]] || [[ "$QUEUE_CONTENT" == "[]" ]]; then
101
+ # Find tandem observation files
102
+ TANDEM_FILES=$(find "$SESSION_DIR" -maxdepth 1 -name "*-tandem-*.md" 2>/dev/null)
103
+ if [[ -z "$TANDEM_FILES" ]]; then
61
104
  exit 0
62
105
  fi
63
106
 
64
- # Extract first message text using simple parsing
65
- # The queue format is: [{"text":"...","images":[...]}, ...]
66
- FIRST_MESSAGE_TEXT=$(echo "$QUEUE_CONTENT" | sed -n 's/.*"text"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' | head -1)
107
+ # Check each tandem file for new content
108
+ for TANDEM_FILE in $TANDEM_FILES; do
109
+ # Extract agent name from filename: *-tandem-{agent}.md
110
+ AGENT=$(echo "$TANDEM_FILE" | sed -n 's/.*-tandem-\([a-zA-Z_]*\)\.md$/\1/p')
111
+ if [[ -z "$AGENT" ]]; then
112
+ continue
113
+ fi
67
114
 
68
- if [[ -z "$FIRST_MESSAGE_TEXT" ]]; then
69
- exit 0
70
- fi
115
+ # Get file mtime (portable: stat -f %m on macOS, stat -c %Y on Linux)
116
+ if [[ "$(uname)" == "Darwin" ]]; then
117
+ FILE_MTIME=$(stat -f %m "$TANDEM_FILE" 2>/dev/null)
118
+ else
119
+ FILE_MTIME=$(stat -c %Y "$TANDEM_FILE" 2>/dev/null)
120
+ fi
121
+ if [[ -z "$FILE_MTIME" ]]; then
122
+ continue
123
+ fi
71
124
 
72
- # Get Cyclist port (if running)
73
- CYCLIST_PORT=""
74
- PORT_FILE="$PROJECT_ROOT/.cyclist-port"
75
- if [[ -f "$PORT_FILE" ]]; then
76
- CYCLIST_PORT=$(cat "$PORT_FILE" 2>/dev/null)
77
- fi
125
+ # Compare with saved mtime sidecar
126
+ MTIME_SIDECAR="$SESSION_DIR/.tandem-mtime-$AGENT"
127
+ SAVED_MTIME="0"
128
+ if [[ -f "$MTIME_SIDECAR" ]]; then
129
+ SAVED_MTIME=$(cat "$MTIME_SIDECAR" 2>/dev/null)
130
+ fi
131
+
132
+ if [[ "$FILE_MTIME" == "$SAVED_MTIME" ]]; then
133
+ continue
134
+ fi
135
+
136
+ # Read tandem file and extract latest observation
137
+ CONTENT=$(cat "$TANDEM_FILE" 2>/dev/null)
138
+ if [[ -z "$CONTENT" ]]; then
139
+ continue
140
+ fi
78
141
 
79
- # Output the hook response JSON
80
- cat << EOF
142
+ # Extract persona from header: **Observer:** agent (Persona Name)
143
+ PERSONA=$(echo "$CONTENT" | sed -n 's/.*\*\*Observer:\*\*[[:space:]]*[a-zA-Z_]*[[:space:]]*(\([^)]*\)).*/\1/p' | head -1)
144
+ if [[ -z "$PERSONA" ]]; then
145
+ PERSONA="Unknown"
146
+ fi
147
+
148
+ # Extract latest observation text (last ## [HH:MM] Observation block)
149
+ # Get text between last observation header and trailing ---
150
+ OBS_TEXT=$(echo "$CONTENT" | awk '
151
+ /^## \[[0-9]+:[0-9]+\] Observation/ { found=1; text=""; next }
152
+ found && /^---/ { next }
153
+ found && /^\*\*Trigger:\*\*/ { next }
154
+ found { text = text (text ? "\n" : "") $0 }
155
+ END { if (text) print text }
156
+ ' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
157
+
158
+ if [[ -z "$OBS_TEXT" ]]; then
159
+ # Update mtime even if no parseable observation
160
+ echo "$FILE_MTIME" > "$MTIME_SIDECAR"
161
+ continue
162
+ fi
163
+
164
+ # Format as [Tandem] prefix and output
165
+ TANDEM_MSG="[Tandem] $PERSONA: $OBS_TEXT"
166
+
167
+ cat << EOF
81
168
  {
82
169
  "hookSpecificOutput": {
83
170
  "hookEventName": "PostToolUse",
84
- "additionalContext": "User feedback: $FIRST_MESSAGE_TEXT"
171
+ "additionalContext": "$TANDEM_MSG"
85
172
  }
86
173
  }
87
174
  EOF
88
175
 
89
- # Remove the first message from the queue and notify Cyclist
90
- # Run in background to avoid blocking hook response
91
- (
92
- # Dequeue using jq if available
93
- if command -v jq &> /dev/null; then
94
- jq 'if length > 0 then .[1:] else [] end' "$BELL_QUEUE_FILE" > "$BELL_QUEUE_FILE.tmp" 2>/dev/null && mv "$BELL_QUEUE_FILE.tmp" "$BELL_QUEUE_FILE"
95
- fi
176
+ # Save mtime sidecar
177
+ echo "$FILE_MTIME" > "$MTIME_SIDECAR"
96
178
 
97
- # Notify Cyclist browser to dequeue and display the message
98
- if [[ -n "$CYCLIST_PORT" ]] && [[ "$CYCLIST_PORT" =~ ^[0-9]+$ ]]; then
99
- curl -s -X POST "http://localhost:$CYCLIST_PORT/api/bell-consumed" \
100
- -H "Content-Type: application/json" \
101
- -d "{\"text\": \"$FIRST_MESSAGE_TEXT\"}" \
102
- >/dev/null 2>&1 || true
103
- fi
104
- ) &
179
+ # Only inject one tandem observation per hook invocation
180
+ exit 0
181
+ done
105
182
 
106
183
  exit 0
@@ -47,7 +47,7 @@ extract_story_id() {
47
47
 
48
48
  # update_story_status STORY_ID NEW_STATUS
49
49
  # Update story status in sprint YAML and add completed date
50
- # Uses yq for YAML manipulation
50
+ # Handles both inline stories in current-sprint.yaml and sharded epic files
51
51
  update_story_status() {
52
52
  local story_id="$1"
53
53
  local new_status="${2:-done}"
@@ -58,23 +58,33 @@ update_story_status() {
58
58
  return 1
59
59
  fi
60
60
 
61
- # Check if yq is available
62
61
  if ! command -v yq &>/dev/null; then
63
62
  echo "Warning: yq not found, cannot update sprint YAML" >&2
64
63
  return 1
65
64
  fi
66
65
 
67
- # Extract epic and story numbers
68
- local epic_num="${story_id%%-*}"
69
- local story_num="${story_id#*-}"
66
+ local sprint_dir
67
+ sprint_dir="$(dirname "$SPRINT_FILE")"
70
68
 
71
- # Update status and add completed date using yq
69
+ # Try inline stories in current-sprint.yaml first
72
70
  yq eval -i "
73
- (.epics[] | select(.id == \"$epic_num\") | .stories[] | select(.id == \"$story_num\")).status = \"$new_status\" |
74
- (.epics[] | select(.id == \"$epic_num\") | .stories[] | select(.id == \"$story_num\")).completed = \"$completed_date\"
75
- " "$SPRINT_FILE"
71
+ (.stories[] | select(.id == \"$story_id\")).status = \"$new_status\" |
72
+ (.stories[] | select(.id == \"$story_id\")).completed = \"$completed_date\"
73
+ " "$SPRINT_FILE" 2>/dev/null
74
+
75
+ # Also try each epic shard file
76
+ for shard in "$sprint_dir"/epic-*.yaml; do
77
+ [[ -f "$shard" ]] || continue
78
+ if yq eval ".stories[] | select(.id == \"$story_id\")" "$shard" 2>/dev/null | grep -q "$story_id"; then
79
+ yq eval -i "
80
+ (.stories[] | select(.id == \"$story_id\")).status = \"$new_status\" |
81
+ (.stories[] | select(.id == \"$story_id\")).completed = \"$completed_date\"
82
+ " "$shard"
83
+ return $?
84
+ fi
85
+ done
76
86
 
77
- return $?
87
+ return 0
78
88
  }
79
89
 
80
90
  # log_reconciliation STORY_ID MESSAGE
@@ -134,6 +134,39 @@ if [ -n "$config_file" ] && [ -n "$agent_name" ]; then
134
134
  fi
135
135
  theme_display="$character_display"
136
136
 
137
+ # Tandem indicator: detect active backseat agent from observation files
138
+ # Files: .session/{storyId}-tandem-{partner}.md (created by backseat spawn, cleaned on finish)
139
+ tandem_partner=""
140
+ tandem_partner_display=""
141
+ if [ -d "$PROJECT_ROOT/.session" ]; then
142
+ tandem_file=$(find "$PROJECT_ROOT/.session" -maxdepth 1 -name "*-tandem-*.md" -print 2>/dev/null | head -1)
143
+ if [ -n "$tandem_file" ]; then
144
+ # Extract partner name from filename: {storyId}-tandem-{partner}.md
145
+ tandem_partner=$(basename "$tandem_file" | sed -n 's/.*-tandem-\([a-zA-Z_-]*\)\.md$/\1/p')
146
+ if [ -n "$tandem_partner" ] && [ -n "$theme_file" ] && [ -f "$theme_file" ]; then
147
+ # Look up partner's character name from theme
148
+ partner_full=$(yq ".agents.${tandem_partner}.character" "$theme_file" 2>/dev/null)
149
+ if [ -n "$partner_full" ] && [ "$partner_full" != "null" ]; then
150
+ partner_clean=$(echo "$partner_full" | sed 's/ *([^)]*)//g' | xargs)
151
+ partner_clean=$(echo "$partner_clean" | sed -E 's/^(Captain|Lieutenant|Dr\.|Doc|Mr\.|Mrs\.|Ms\.|Admiral|Commander|Chief|Ensign|Translator|Agent|Colonel|Major|Sergeant|Professor|Lord|Lady|Sir|The) +//i')
152
+ partner_wc=$(echo "$partner_clean" | wc -w | tr -d ' ')
153
+ if [ "$partner_wc" -eq 1 ]; then
154
+ tandem_partner_display="$partner_clean"
155
+ else
156
+ tandem_partner_display=$(echo "$partner_clean" | awk '{print $NF}')
157
+ fi
158
+ fi
159
+ # Fallback to agent abbreviation if no theme character
160
+ if [ -z "$tandem_partner_display" ]; then
161
+ tandem_partner_display=$(get_agent_abbrev "$tandem_partner")
162
+ fi
163
+ elif [ -n "$tandem_partner" ]; then
164
+ # No theme file — use abbreviation
165
+ tandem_partner_display=$(get_agent_abbrev "$tandem_partner")
166
+ fi
167
+ fi
168
+ fi
169
+
137
170
  # ANSI colors
138
171
  RESET=$'\033[0m'
139
172
  DIM=$'\033[2m'
@@ -214,20 +247,29 @@ repo_fmt=$(printf "%-14s" "$dir_name")
214
247
  branch_fmt=$(printf "%-12s" "${branch}${branch_dirty}")
215
248
  model_fmt=$(printf "%-10s" "$model")
216
249
 
217
- # Build agent display: [ROLE] Theme (role in reverse text with color)
250
+ # Build tandem suffix: "+ Partner" when backseat agent is active
251
+ tandem_suffix=""
252
+ tandem_suffix_len=0
253
+ if [ -n "$tandem_partner_display" ]; then
254
+ partner_color=$(get_agent_color "$tandem_partner")
255
+ tandem_suffix=" ${DIM}+${RESET} ${partner_color}${tandem_partner_display}${RESET}"
256
+ tandem_suffix_len=$((3 + ${#tandem_partner_display})) # " + " + name
257
+ fi
258
+
259
+ # Build agent display: [ROLE] Theme (+ Partner) (role in reverse text with color)
218
260
  if [ -n "$agent_abbrev" ]; then
219
261
  agent_color=$(get_agent_color "$agent_name")
220
262
  # Role in reverse text with color, then theme
221
263
  if [ -n "$theme_display" ]; then
222
- agent_section="${agent_color}${REVERSE} ${agent_abbrev} ${RESET} ${DIM}${theme_display}${RESET}"
223
- # Pad to ~20 chars visual width (abbrev ~3 + spaces ~2 + theme ~10 = ~15, pad to 20)
224
- pad_len=$((18 - ${#agent_abbrev} - ${#theme_display}))
264
+ agent_section="${agent_color}${REVERSE} ${agent_abbrev} ${RESET} ${DIM}${theme_display}${RESET}${tandem_suffix}"
265
+ # Pad to ~20 chars visual width (abbrev ~3 + spaces ~2 + theme ~10 + tandem = ~15+, pad to 20)
266
+ pad_len=$((18 - ${#agent_abbrev} - ${#theme_display} - tandem_suffix_len))
225
267
  [ "$pad_len" -lt 0 ] && pad_len=0
226
268
  padding=$(printf "%${pad_len}s" "")
227
269
  agent_section="${agent_section}${padding}"
228
270
  else
229
- agent_section="${agent_color}${REVERSE} ${agent_abbrev} ${RESET}"
230
- pad_len=$((17 - ${#agent_abbrev}))
271
+ agent_section="${agent_color}${REVERSE} ${agent_abbrev} ${RESET}${tandem_suffix}"
272
+ pad_len=$((17 - ${#agent_abbrev} - tandem_suffix_len))
231
273
  [ "$pad_len" -lt 0 ] && pad_len=0
232
274
  padding=$(printf "%${pad_len}s" "")
233
275
  agent_section="${agent_section}${padding}"
@@ -235,8 +277,8 @@ if [ -n "$agent_abbrev" ]; then
235
277
  else
236
278
  # No agent - just show theme if available
237
279
  if [ -n "$theme_display" ]; then
238
- agent_section="${DIM}${theme_display}${RESET}"
239
- pad_len=$((20 - ${#theme_display}))
280
+ agent_section="${DIM}${theme_display}${RESET}${tandem_suffix}"
281
+ pad_len=$((20 - ${#theme_display} - tandem_suffix_len))
240
282
  [ "$pad_len" -lt 0 ] && pad_len=0
241
283
  padding=$(printf "%${pad_len}s" "")
242
284
  agent_section="${agent_section}${padding}"
@@ -6,7 +6,7 @@ Scripts for workflow mechanics, phase transitions, and quality gates.
6
6
 
7
7
  | Script | Purpose |
8
8
  |--------|---------|
9
- | `finish-story.sh` | Story completion workflow (archive, PR merge, Jira transition) |
9
+ | `finish-story.sh` | **DEPRECATED** forwards to `pf sprint story finish` |
10
10
  | `fix-session-phase.sh` | Repair session file phase state |
11
11
  | `check.sh` | Quality gates runner (lint, type check, tests) |
12
12
  | `list-workflows.sh` | List available workflows |
@@ -18,7 +18,7 @@ Scripts for workflow mechanics, phase transitions, and quality gates.
18
18
  ## Usage
19
19
 
20
20
  ```bash
21
- .pennyfarthing/scripts/workflow/finish-story.sh MSSCI-12345
21
+ pf sprint story finish MSSCI-12345
22
22
  .pennyfarthing/scripts/workflow/start-workflow.sh prd --mode create
23
23
  ```
24
24
 
@@ -1,199 +1,20 @@
1
1
  #!/bin/bash
2
- # Finish a story: archive, merge PR, transition Jira, update sprint YAML
3
- # Usage: finish-story.sh <story-id> [--dry-run]
4
- #
5
- # Example: finish-story.sh MSSCI-12052
6
- # finish-story.sh MSSCI-12052 --dry-run
2
+ # DEPRECATED: Use `pf sprint story finish` instead.
7
3
  #
8
- # Prerequisites:
9
- # - Session file exists at .session/{story-id}-session.md
10
- # - PR is approved and mergeable
11
- # - Reviewer has approved (phase: finish in session)
4
+ # This shell script had a bug where yq paths targeted .epics[].stories[]
5
+ # but the sprint YAML uses sharded epic files with .stories[] at root.
6
+ # The Python implementation (pennyfarthing_scripts.sprint.story_finish)
7
+ # correctly handles sharded YAML via read_sprint/write_sprint.
12
8
  #
13
- # This script performs:
14
- # 1. Archive session file to sprint/archive/{jira-key}-session.md
15
- # 2. Squash merge PR and delete remote branch
16
- # 3. Transition Jira to Done
17
- # 4. Update sprint YAML (status: done, completed date)
18
- # 5. Archive completed epics (if last story in epic was just finished)
19
- # 6. Clean up local branch and session file
9
+ # Usage: finish-story.sh <story-id> [--dry-run]
20
10
 
21
11
  set -euo pipefail
22
12
 
23
- STORY_ID="${1:-}"
24
- DRY_RUN=false
13
+ echo "DEPRECATED: finish-story.sh is deprecated. Forwarding to 'pf sprint story finish'." >&2
25
14
 
26
- # Parse options
15
+ ARGS=()
27
16
  for arg in "$@"; do
28
- case $arg in
29
- --dry-run)
30
- DRY_RUN=true
31
- shift
32
- ;;
33
- esac
17
+ ARGS+=("$arg")
34
18
  done
35
19
 
36
- if [[ -z "$STORY_ID" ]]; then
37
- echo "Usage: finish-story.sh <story-id> [--dry-run]"
38
- echo ""
39
- echo "Options:"
40
- echo " --dry-run Show what would be done without executing"
41
- exit 1
42
- fi
43
-
44
- # Find project root
45
- source "$(dirname "${BASH_SOURCE[0]}")/../lib/find-root.sh"
46
-
47
- SPRINT_FILE="$PROJECT_ROOT/sprint/current-sprint.yaml"
48
- ARCHIVE_DIR="$PROJECT_ROOT/sprint/archive"
49
-
50
- # Ensure archive directory exists
51
- mkdir -p "$ARCHIVE_DIR"
52
-
53
- # Resolve session file: prefer Jira key, fall back to numeric story ID
54
- SESSION_FILE="$PROJECT_ROOT/.session/${STORY_ID}-session.md"
55
- if [[ ! -f "$SESSION_FILE" ]]; then
56
- # Look up Jira key from sprint YAML and try that
57
- _JIRA_LOOKUP=$(pf sprint story field "$STORY_ID" jira 2>/dev/null || echo "")
58
- if [[ -n "$_JIRA_LOOKUP" && "$_JIRA_LOOKUP" != "null" ]]; then
59
- ALT_SESSION="$PROJECT_ROOT/.session/${_JIRA_LOOKUP}-session.md"
60
- if [[ -f "$ALT_SESSION" ]]; then
61
- SESSION_FILE="$ALT_SESSION"
62
- fi
63
- fi
64
- fi
65
-
66
- # Also try the reverse: if called with Jira key, try numeric ID
67
- if [[ ! -f "$SESSION_FILE" ]]; then
68
- # List session files and look for one containing the story ID
69
- for f in "$PROJECT_ROOT/.session/"*-session.md; do
70
- [[ -f "$f" ]] || continue
71
- if grep -q "$STORY_ID" "$f" 2>/dev/null; then
72
- SESSION_FILE="$f"
73
- break
74
- fi
75
- done
76
- fi
77
-
78
- # Validate session file exists
79
- if [[ ! -f "$SESSION_FILE" ]]; then
80
- echo "Error: Session file not found for story $STORY_ID"
81
- echo "Looked for: $PROJECT_ROOT/.session/${STORY_ID}-session.md"
82
- [[ -n "${_JIRA_LOOKUP:-}" ]] && echo "Also tried: $PROJECT_ROOT/.session/${_JIRA_LOOKUP}-session.md"
83
- exit 1
84
- fi
85
-
86
- # Extract metadata from session file
87
- # Try **Jira:** field first (handle both "**Jira:**" and "- **Jira:**", markdown link format)
88
- JIRA_KEY=$(grep -E '\*\*Jira:\*\*' "$SESSION_FILE" | sed 's/.*\*\*Jira:\*\* //' | sed 's/\[//' | sed 's/\].*//' | tr -d ' ' || echo "")
89
-
90
- # Fallback: extract MSSCI key from **ID:** field (format: "MSSCI-14459 (81-2)" or just "MSSCI-14459")
91
- if [[ -z "$JIRA_KEY" || "$JIRA_KEY" == "null" ]]; then
92
- JIRA_KEY=$(grep -E '\*\*ID:\*\*' "$SESSION_FILE" | grep -oE 'MSSCI-[0-9]+' | head -1 || echo "")
93
- fi
94
-
95
- # Fallback: if STORY_ID itself is a Jira key, use it directly
96
- if [[ -z "$JIRA_KEY" || "$JIRA_KEY" == "null" ]]; then
97
- if [[ "$STORY_ID" =~ ^MSSCI-[0-9]+$ ]]; then
98
- JIRA_KEY="$STORY_ID"
99
- fi
100
- fi
101
-
102
- # Fallback: look up Jira key from sprint YAML via pf CLI
103
- if [[ -z "$JIRA_KEY" || "$JIRA_KEY" == "null" ]]; then
104
- JIRA_KEY=$(pf sprint story field "$STORY_ID" jira 2>/dev/null || echo "")
105
- fi
106
-
107
- if [[ -z "$JIRA_KEY" || "$JIRA_KEY" == "null" ]]; then
108
- echo "Error: Could not determine Jira key for story $STORY_ID"
109
- echo "Check session file or sprint YAML for jira: field"
110
- exit 1
111
- fi
112
-
113
- # Extract branch - strip any trailing annotations like "(pushed)"
114
- BRANCH=$(grep -E '\*\*Branch:\*\*' "$SESSION_FILE" | sed 's/.*\*\*Branch:\*\* //' | sed 's/ *(.*//' | tr -d ' ' || echo "")
115
-
116
- # Try to get PR number from session file first (format: **PR:** #422 - title)
117
- PR_NUMBER=$(grep -E '\*\*PR:\*\*' "$SESSION_FILE" | sed 's/.*#\([0-9]*\).*/\1/' || echo "")
118
-
119
- # Fallback: get PR number from GitHub if not in session file
120
- if [[ -z "$PR_NUMBER" ]] && [[ -n "$BRANCH" ]]; then
121
- PR_NUMBER=$(gh pr list --head "$BRANCH" --json number --jq '.[0].number' 2>/dev/null || echo "")
122
- fi
123
-
124
- TODAY=$(date +%Y-%m-%d)
125
-
126
- echo "=== Finish Story: $STORY_ID ==="
127
- echo "Jira Key: $JIRA_KEY"
128
- echo "Branch: ${BRANCH:-none}"
129
- echo "PR: ${PR_NUMBER:-none}"
130
- echo ""
131
-
132
- if $DRY_RUN; then
133
- echo "[DRY RUN] Would perform:"
134
- echo " 1. Archive session → $ARCHIVE_DIR/${JIRA_KEY}-session.md"
135
- if [[ -n "$PR_NUMBER" ]]; then
136
- echo " 2. Merge PR #$PR_NUMBER (squash, delete branch)"
137
- else
138
- echo " 2. No PR to merge"
139
- fi
140
- echo " 3. Transition $JIRA_KEY to Done"
141
- echo " 4. Update sprint YAML (status: done, completed: $TODAY)"
142
- echo " 5. Archive any completed epics"
143
- echo " 6. Delete local branch: $BRANCH"
144
- echo " 7. Remove session file"
145
- exit 0
146
- fi
147
-
148
- # Step 1: Archive session file
149
- echo "1. Archiving session file..."
150
- cp "$SESSION_FILE" "$ARCHIVE_DIR/${JIRA_KEY}-session.md"
151
- echo " → $ARCHIVE_DIR/${JIRA_KEY}-session.md"
152
-
153
- # Step 2: Merge PR (if exists)
154
- if [[ -n "$PR_NUMBER" ]]; then
155
- echo "2. Merging PR #$PR_NUMBER..."
156
- gh pr merge "$PR_NUMBER" --squash --delete-branch || {
157
- echo " Warning: PR merge failed (may already be merged)"
158
- }
159
- else
160
- echo "2. No PR found for branch $BRANCH (skipping merge)"
161
- fi
162
-
163
- # Step 3: Transition Jira to Done
164
- echo "3. Transitioning Jira to Done..."
165
- jira issue move "$JIRA_KEY" "Done" 2>/dev/null || {
166
- echo " Warning: Jira transition failed (may already be Done)"
167
- }
168
-
169
- # Step 4: Update sprint YAML
170
- echo "4. Updating sprint YAML..."
171
- # Update status to done
172
- yq eval -i "(.epics[].stories[] | select(.id == \"$STORY_ID\")).status = \"done\"" "$SPRINT_FILE"
173
- # Add completed date
174
- yq eval -i "(.epics[].stories[] | select(.id == \"$STORY_ID\")).completed = \"$TODAY\"" "$SPRINT_FILE"
175
- # Remove assigned_to (no longer in progress)
176
- yq eval -i "del((.epics[].stories[] | select(.id == \"$STORY_ID\")).assigned_to)" "$SPRINT_FILE"
177
- echo " → status: done, completed: $TODAY"
178
-
179
- # Step 5: Archive completed epics
180
- echo "5. Archiving completed epics..."
181
- pf sprint epic archive 2>/dev/null && echo " → Checked for completed epics" || echo " → No epics to archive"
182
-
183
- # Step 6: Clean up git
184
- echo "6. Cleaning up git..."
185
- git checkout develop 2>/dev/null || git checkout main 2>/dev/null || true
186
- git pull origin "$(git branch --show-current)" 2>/dev/null || true
187
-
188
- if [[ -n "$BRANCH" ]]; then
189
- git branch -d "$BRANCH" 2>/dev/null || echo " Local branch already deleted"
190
- fi
191
-
192
- # Step 7: Remove session file
193
- echo "7. Removing session file..."
194
- rm "$SESSION_FILE"
195
-
196
- echo ""
197
- echo "=== Story $STORY_ID Complete ==="
198
- echo "Archived: sprint/archive/${JIRA_KEY}-session.md"
199
- echo "Jira: https://1898andco.atlassian.net/browse/$JIRA_KEY"
20
+ exec pf sprint story finish "${ARGS[@]}"
@@ -98,6 +98,14 @@
98
98
  "type": "array",
99
99
  "items": { "type": "string" },
100
100
  "description": "Tools this skill is allowed to use. If omitted, all tools are allowed. Use to restrict read-only skills from writing."
101
+ },
102
+ "deprecated": {
103
+ "type": "boolean",
104
+ "description": "Whether this skill is deprecated"
105
+ },
106
+ "redirect": {
107
+ "type": "string",
108
+ "description": "Skill name to redirect to when deprecated"
101
109
  }
102
110
  },
103
111
  "required": ["name", "description", "category", "tags"],
@@ -386,7 +386,7 @@ skills:
386
386
  anti_patterns:
387
387
  - Don't switch workflows mid-story unless requirements fundamentally changed
388
388
  related_skills: [sprint]
389
- keywords: [tdd, trivial, agent-docs, bdd, architecture, bikelane, stepped, phased]
389
+ keywords: [tdd, tdd-tandem, trivial, agent-docs, bdd, bdd-tandem, architecture, bikelane, stepped, phased]
390
390
 
391
391
  yq:
392
392
  name: yq