@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
@@ -8,7 +8,6 @@ concurrent execution regardless of model behavior.
8
8
  import asyncio
9
9
  import json
10
10
  import re
11
- import sys
12
11
  from dataclasses import dataclass, field
13
12
  from pathlib import Path
14
13
  from typing import Any
@@ -40,15 +40,14 @@ from pathlib import Path
40
40
  sys.path.insert(0, str(Path(__file__).parent))
41
41
 
42
42
  from hooks import (
43
- find_project_root,
44
- get_cyclist_port,
45
- send_to_cyclist,
46
- read_stdin_json,
47
- output_hook_response,
48
43
  HookResponse,
49
- load_settings,
50
- is_cyclist_running,
44
+ find_project_root,
51
45
  get_context_state,
46
+ is_cyclist_running,
47
+ load_settings,
48
+ output_hook_response,
49
+ read_stdin_json,
50
+ send_to_cyclist,
52
51
  )
53
52
 
54
53
 
@@ -22,6 +22,10 @@ import argparse
22
22
  import json
23
23
  import sys
24
24
  from pathlib import Path
25
+ from typing import TYPE_CHECKING
26
+
27
+ if TYPE_CHECKING:
28
+ from typing import Any
25
29
 
26
30
  from pennyfarthing_scripts.common.config import get_project_root
27
31
  from pennyfarthing_scripts.prime.loader import (
@@ -42,7 +46,7 @@ from pennyfarthing_scripts.prime.persona import (
42
46
  load_persona,
43
47
  )
44
48
  from pennyfarthing_scripts.prime.session import cleanup_old_sessions, register_session
45
- from pennyfarthing_scripts.prime.tiers import ContextTier, tier_from_string, load_tier_components
49
+ from pennyfarthing_scripts.prime.tiers import ContextTier, load_tier_components, tier_from_string
46
50
  from pennyfarthing_scripts.prime.workflow import check_redirect, detect_workflow_state
47
51
 
48
52
 
@@ -14,7 +14,6 @@ from __future__ import annotations
14
14
 
15
15
  import re
16
16
  from pathlib import Path
17
- from typing import Any
18
17
 
19
18
  from pennyfarthing_scripts.common.config import get_project_root
20
19
 
@@ -68,7 +67,7 @@ def load_sprint_context(project_root: Path | None = None) -> str | None:
68
67
  Formatted sprint summary, or None if no sprint data
69
68
  """
70
69
  # Import here to avoid circular imports
71
- from pennyfarthing_scripts.sprint.loader import load_sprint, get_sprint_info
70
+ from pennyfarthing_scripts.sprint.loader import load_sprint
72
71
  from pennyfarthing_scripts.sprint.status import get_sprint_status
73
72
 
74
73
  root = project_root or get_project_root()
@@ -136,7 +135,7 @@ def _extract_session_parts(content: str) -> tuple[str, str]:
136
135
 
137
136
  # Header: everything before first ## heading
138
137
  header_lines = []
139
- for i, line in enumerate(lines):
138
+ for line in lines:
140
139
  if line.startswith("## "):
141
140
  break
142
141
  header_lines.append(line)
@@ -14,11 +14,12 @@ import yaml
14
14
  from pennyfarthing_scripts.common.config import get_project_root, load_yaml_config
15
15
  from pennyfarthing_scripts.common.themes import (
16
16
  get_current_theme as _get_current_theme,
17
+ )
18
+ from pennyfarthing_scripts.common.themes import (
17
19
  resolve_theme_path,
18
20
  )
19
21
  from pennyfarthing_scripts.prime.models import CrewMember, Persona
20
22
 
21
-
22
23
  # Standard agent roles for crew manifest
23
24
  AGENT_ROLES = [
24
25
  "sm", "tea", "dev", "reviewer", "architect",
@@ -35,21 +35,21 @@ def estimate_tokens(text: str) -> int:
35
35
  return max(1, len(text) // 4)
36
36
 
37
37
 
38
- from pennyfarthing_scripts.prime.loader import (
38
+ from pennyfarthing_scripts.prime.loader import ( # noqa: E402
39
39
  load_agent_definition,
40
40
  load_behavior_guide,
41
41
  load_session_context,
42
42
  load_sidecars,
43
43
  load_sprint_context,
44
44
  )
45
- from pennyfarthing_scripts.prime.persona import (
45
+ from pennyfarthing_scripts.prime.persona import ( # noqa: E402
46
46
  format_persona_compressed,
47
47
  get_crew_manifest,
48
48
  get_user_title,
49
49
  is_character_voice_enabled,
50
50
  load_persona,
51
51
  )
52
- from pennyfarthing_scripts.prime.workflow import detect_workflow_state
52
+ from pennyfarthing_scripts.prime.workflow import detect_workflow_state # noqa: E402
53
53
 
54
54
 
55
55
  class ContextTier(Enum):
@@ -78,7 +78,7 @@ def tier_from_string(value: str) -> ContextTier:
78
78
  return ContextTier(normalized)
79
79
  except ValueError:
80
80
  valid = ", ".join(t.value for t in ContextTier)
81
- raise ValueError(f"Invalid tier '{value}'. Must be one of: {valid}")
81
+ raise ValueError(f"Invalid tier '{value}'. Must be one of: {valid}") from None
82
82
 
83
83
 
84
84
  def load_tier_components(
@@ -29,12 +29,11 @@ from pathlib import Path
29
29
  sys.path.insert(0, str(Path(__file__).parent))
30
30
 
31
31
  from hooks import (
32
- read_stdin_json,
33
- output_hook_response,
34
32
  HookResponse,
33
+ output_hook_response,
34
+ read_stdin_json,
35
35
  )
36
36
 
37
-
38
37
  # =============================================================================
39
38
  # File Type Detection
40
39
  # =============================================================================
@@ -18,6 +18,16 @@ Usage:
18
18
  """
19
19
 
20
20
  # Re-export from loader for backwards compatibility
21
+ # Import submodules to make them accessible
22
+ # CLI entry point - import module, not function, so "from sprint import cli" gets the module
23
+ from pennyfarthing_scripts.sprint import (
24
+ archive,
25
+ cli,
26
+ loader,
27
+ status,
28
+ work,
29
+ )
30
+ from pennyfarthing_scripts.sprint.cli import main
21
31
  from pennyfarthing_scripts.sprint.loader import (
22
32
  find_epic,
23
33
  find_story,
@@ -31,18 +41,6 @@ from pennyfarthing_scripts.sprint.loader import (
31
41
  load_sprint,
32
42
  )
33
43
 
34
- # Import submodules to make them accessible
35
- from pennyfarthing_scripts.sprint import (
36
- archive,
37
- loader,
38
- status,
39
- work,
40
- )
41
-
42
- # CLI entry point - import module, not function, so "from sprint import cli" gets the module
43
- from pennyfarthing_scripts.sprint import cli
44
- from pennyfarthing_scripts.sprint.cli import main
45
-
46
44
  __all__ = [
47
45
  # Loader functions
48
46
  "find_epic",
@@ -4,7 +4,7 @@ Entry point for python -m pennyfarthing_scripts.sprint
4
4
 
5
5
  import sys
6
6
 
7
- from pennyfarthing_scripts.sprint.cli import cli
7
+ from pennyfarthing_scripts.sprint.cli import sprint
8
8
 
9
9
  if __name__ == "__main__":
10
- sys.exit(cli())
10
+ sys.exit(sprint())
@@ -4,7 +4,6 @@ Sprint story archiving.
4
4
  Provides functions for archiving completed stories.
5
5
  """
6
6
 
7
- from pathlib import Path
8
7
  from typing import Any
9
8
 
10
9
  from pennyfarthing_scripts.common.config import get_project_root
@@ -18,7 +18,6 @@ from pennyfarthing_scripts.sprint.yaml_io import (
18
18
  _read_yaml_file,
19
19
  _write_yaml_file,
20
20
  read_sprint,
21
- write_sprint,
22
21
  )
23
22
 
24
23
 
@@ -252,13 +251,11 @@ def archive_epic(
252
251
 
253
252
  # Find the epic in merged data
254
253
  epic = None
255
- epic_index = None
256
- for i, e in enumerate(sprint_data["epics"]):
254
+ for e in sprint_data["epics"]:
257
255
  eid = str(e.get("id", ""))
258
256
  ejira = str(e.get("jira", ""))
259
257
  if eid == epic_id or ejira == epic_id:
260
258
  epic = e
261
- epic_index = i
262
259
  break
263
260
 
264
261
  if not epic:
@@ -150,7 +150,7 @@ def work(story_id: str | None, dry_run: bool):
150
150
  click.echo(f"Story: {story.get('id')}")
151
151
  click.echo(f"Title: {story.get('title')}")
152
152
  click.echo(f"Points: {story.get('points')}")
153
- click.echo(f"Status: Available")
153
+ click.echo("Status: Available")
154
154
  else:
155
155
  error_msg = result.get("error") or result.get("reason")
156
156
  raise click.ClickException(f"Not available: {error_msg}")
@@ -334,12 +334,12 @@ def story_claim(story_id: str, claim: bool):
334
334
 
335
335
 
336
336
  # Register story-add as story.add
337
- from pennyfarthing_scripts.sprint.story_add import story_add_command
337
+ from pennyfarthing_scripts.sprint.story_add import story_add_command # noqa: E402
338
338
 
339
339
  story.add_command(story_add_command, "add")
340
340
 
341
341
  # Register story-update as story.update
342
- from pennyfarthing_scripts.sprint.story_update import story_update_command
342
+ from pennyfarthing_scripts.sprint.story_update import story_update_command # noqa: E402
343
343
 
344
344
  story.add_command(story_update_command, "update")
345
345
 
@@ -501,7 +501,6 @@ def epic_cancel(epic_id: str, jira: bool, dry_run: bool):
501
501
  pf sprint epic cancel epic-42 --jira
502
502
  """
503
503
  from pennyfarthing_scripts.common.config import get_project_root
504
- from pennyfarthing_scripts.sprint.loader import load_sprint
505
504
  from pennyfarthing_scripts.sprint.yaml_io import read_sprint, write_sprint
506
505
 
507
506
  root = get_project_root()
@@ -583,7 +582,7 @@ def _cancel_epic_in_initiatives(epic_id: str, root, *, jira: bool, dry_run: bool
583
582
  init_name = init_data.get("name", init_file.stem)
584
583
  epics = init_data.get("epics", [])
585
584
 
586
- for i, e in enumerate(epics):
585
+ for _i, e in enumerate(epics):
587
586
  matched = False
588
587
  epic_dict = None
589
588
 
@@ -663,6 +662,8 @@ def epic_archive(epic_id: str | None, dry_run: bool, jira: bool):
663
662
  # Lazy import
664
663
  from pennyfarthing_scripts.sprint.archive_epic import (
665
664
  archive_all_completed,
665
+ )
666
+ from pennyfarthing_scripts.sprint.archive_epic import (
666
667
  archive_epic as do_archive_epic,
667
668
  )
668
669
 
@@ -757,7 +758,6 @@ def epic_remove(epic_id: str, dry_run: bool):
757
758
  pf sprint epic remove epic-41
758
759
  pf sprint epic remove epic-41 --dry-run
759
760
  """
760
- from pathlib import Path
761
761
 
762
762
  import yaml
763
763
 
@@ -886,20 +886,23 @@ def epic_promote(epic_id: str):
886
886
  new_epic_id = original_id
887
887
  existing_ids = {str(e.get("id", "")) for e in sprint_data["epics"] if isinstance(e, dict)}
888
888
 
889
+ # Normalize to numeric ID (ADR-0022: strip epic- prefix from values)
890
+ new_epic_id = new_epic_id.replace("epic-", "") if new_epic_id.startswith("epic-") else new_epic_id
891
+
889
892
  if new_epic_id in existing_ids:
890
893
  max_num = 0
891
894
  for eid in existing_ids:
892
- if eid.startswith("epic-"):
893
- try:
894
- max_num = max(max_num, int(eid.replace("epic-", "")))
895
- except ValueError:
896
- pass
897
- new_epic_id = f"epic-{max_num + 1}"
895
+ clean_eid = eid.replace("epic-", "") if eid.startswith("epic-") else eid
896
+ try:
897
+ max_num = max(max_num, int(clean_eid))
898
+ except ValueError:
899
+ pass
900
+ new_epic_id = str(max_num + 1)
898
901
  click.echo(f"Warning: Epic ID {original_id} already exists. Assigning new ID: {new_epic_id}")
899
902
 
900
903
  # Transform epic for current sprint
901
904
  old_id_num = original_id.replace("epic-", "")
902
- new_id_num = new_epic_id.replace("epic-", "")
905
+ new_id_num = new_epic_id.replace("epic-", "") if new_epic_id.startswith("epic-") else new_epic_id
903
906
 
904
907
  epic_data["id"] = new_epic_id
905
908
  epic_data["status"] = "backlog"
@@ -928,6 +931,13 @@ def epic_promote(epic_id: str):
928
931
  click.echo(f" Stories: {story_count}")
929
932
  click.echo("")
930
933
 
934
+ # Validate epic shard before writing (ADR-0022)
935
+ from pennyfarthing_scripts.sprint.validator import validate_epic_shard
936
+ validation = validate_epic_shard(dict(epic_data))
937
+ if not validation.valid:
938
+ error_msgs = "; ".join(e.message for e in validation.errors)
939
+ raise click.ClickException(f"Epic validation failed: {error_msgs}")
940
+
931
941
  # Append to sprint
932
942
  sprint_data["epics"].append(epic_data)
933
943
 
@@ -960,7 +970,6 @@ def epic_promote(epic_id: str):
960
970
  click.echo(f"Removed {original_id} from {source_init_file.name}")
961
971
  else:
962
972
  # Initiative is empty — remove shard and future.yaml reference
963
- init_name = init_data.get("name", "")
964
973
  init_slug = source_init_file.stem.replace("initiative-", "")
965
974
  source_init_file.unlink()
966
975
  click.echo(f"Removed empty initiative shard: {source_init_file.name}")
@@ -987,7 +996,7 @@ def epic_promote(epic_id: str):
987
996
 
988
997
 
989
998
  # Register epic-add as epic.add
990
- from pennyfarthing_scripts.sprint.epic_add import epic_add_command
999
+ from pennyfarthing_scripts.sprint.epic_add import epic_add_command # noqa: E402
991
1000
 
992
1001
  epic.add_command(epic_add_command, "add")
993
1002
 
@@ -1161,7 +1170,7 @@ def initiative_cancel(name: str, jira: bool, dry_run: bool):
1161
1170
  return
1162
1171
 
1163
1172
  # Cancel all epics
1164
- for i, e in enumerate(epics):
1173
+ for _i, e in enumerate(epics):
1165
1174
  if isinstance(e, str):
1166
1175
  shard = _epic_shard_path(sprint_dir, e)
1167
1176
  if shard.exists():
@@ -1216,7 +1225,6 @@ def check(id: str):
1216
1225
 
1217
1226
  from pennyfarthing_scripts.sprint.loader import (
1218
1227
  find_epic,
1219
- get_all_stories,
1220
1228
  load_sprint,
1221
1229
  )
1222
1230
  from pennyfarthing_scripts.sprint.work import check_story, get_next_story
@@ -1328,10 +1336,10 @@ def _find_epic_for_story(data: dict | None, story_id: str) -> str:
1328
1336
 
1329
1337
  @sprint.command()
1330
1338
  def info():
1331
- """Output sprint info as JSON for Cyclist sidebar.
1339
+ """Output sprint info as JSON.
1332
1340
 
1333
1341
  \b
1334
- Returns: {"remaining": N, "inProgress": N, "endDate": "YYYY-MM-DD"}
1342
+ Returns sprint header fields plus computed story point totals.
1335
1343
  """
1336
1344
  import json
1337
1345
 
@@ -1340,8 +1348,6 @@ def info():
1340
1348
  sprint_data = get_sprint_info()
1341
1349
  stories = get_all_stories()
1342
1350
 
1343
- end_date = sprint_data.get("end_date")
1344
-
1345
1351
  remaining = sum(
1346
1352
  s.get("points", 0) or 0
1347
1353
  for s in stories
@@ -1353,11 +1359,11 @@ def info():
1353
1359
  if s.get("status") == "in_progress"
1354
1360
  )
1355
1361
 
1356
- click.echo(json.dumps({
1357
- "remaining": remaining,
1358
- "inProgress": in_progress,
1359
- "endDate": str(end_date) if end_date else None,
1360
- }))
1362
+ result = {str(k): str(v) if hasattr(v, 'isoformat') else v for k, v in sprint_data.items()}
1363
+ result["remaining"] = remaining
1364
+ result["inProgress"] = in_progress
1365
+
1366
+ click.echo(json.dumps(result))
1361
1367
 
1362
1368
 
1363
1369
  # --- Metrics command (replaces sprint-metrics.sh) ---
@@ -1783,7 +1789,7 @@ completed:
1783
1789
  click.echo(f"Created {archive_file}")
1784
1790
 
1785
1791
  click.echo("")
1786
- click.echo(f"New sprint initialized:")
1792
+ click.echo("New sprint initialized:")
1787
1793
  click.echo(f" Name: TO Sprint {sprint_yyww}")
1788
1794
  click.echo(f" Jira ID: {jira_id}")
1789
1795
  click.echo(f" Dates: {start_date} to {end_date}")
@@ -1854,7 +1860,7 @@ sprint.commands["epic-add"].hidden = True
1854
1860
 
1855
1861
 
1856
1862
  # Register validate command from validate_cmd module
1857
- from pennyfarthing_scripts.sprint.validate_cmd import validate_command
1863
+ from pennyfarthing_scripts.sprint.validate_cmd import validate_command # noqa: E402
1858
1864
 
1859
1865
  sprint.add_command(validate_command)
1860
1866
 
@@ -14,11 +14,12 @@ from typing import Any
14
14
  import click
15
15
  from ruamel.yaml.comments import CommentedMap, CommentedSeq
16
16
 
17
+ from pennyfarthing_scripts.sprint import validator as _shard_validator
17
18
  from pennyfarthing_scripts.sprint.yaml_io import (
18
19
  EPIC_KEY_ORDER,
20
+ _get_epic_ref,
19
21
  _read_yaml_file,
20
22
  _write_yaml_file,
21
- _get_epic_ref,
22
23
  )
23
24
 
24
25
 
@@ -76,6 +77,12 @@ def add_epic(
76
77
  if key not in EPIC_KEY_ORDER:
77
78
  epic[key] = fields[key]
78
79
 
80
+ # Validate epic shard before writing
81
+ validation = _shard_validator.validate_epic_shard(dict(epic))
82
+ if not validation.valid:
83
+ error_msgs = "; ".join(e.message for e in validation.errors)
84
+ return {"success": False, "error": f"Epic validation failed: {error_msgs}"}
85
+
79
86
  # Determine the shard reference
80
87
  ref = _get_epic_ref(epic)
81
88
  shard_file = sprint_dir / f"epic-{ref}.yaml"
@@ -10,9 +10,7 @@ from datetime import date
10
10
  from pathlib import Path
11
11
  from typing import Any
12
12
 
13
- import yaml
14
-
15
- from pennyfarthing_scripts.common.config import get_project_root, load_yaml_config
13
+ from pennyfarthing_scripts.common.config import get_project_root
16
14
 
17
15
 
18
16
  def parse_epics_markdown(content: str) -> dict[str, Any]:
@@ -195,13 +193,13 @@ def generate_initiative_yaml(
195
193
  today = date.today().isoformat()
196
194
 
197
195
  lines = [
198
- f" # ==========================================================================",
196
+ " # ==========================================================================",
199
197
  f" # {initiative_name.upper()}",
200
198
  f" # Imported from: {source_file}",
201
199
  f" # Date: {today}",
202
- f" # ==========================================================================",
200
+ " # ==========================================================================",
203
201
  f' - name: "{initiative_name}"',
204
- f" description: |",
202
+ " description: |",
205
203
  ]
206
204
 
207
205
  # Add description lines with proper indentation
@@ -210,10 +208,10 @@ def generate_initiative_yaml(
210
208
 
211
209
  lines.extend(
212
210
  [
213
- f" status: ready",
214
- f" blocked_by: null",
211
+ " status: ready",
212
+ " blocked_by: null",
215
213
  f" total_points: {parsed['total_points']}",
216
- f" epics:",
214
+ " epics:",
217
215
  ]
218
216
  )
219
217
 
@@ -228,14 +226,14 @@ def generate_initiative_yaml(
228
226
  [
229
227
  f" - id: epic-{current_epic_num}",
230
228
  f' title: "{epic["title"]}"',
231
- f" description: |",
229
+ " description: |",
232
230
  f" {epic.get('description', epic['title'])}",
233
231
  f" points: {epic_points}",
234
- f" priority: P1",
232
+ " priority: P1",
235
233
  f' marker: "{marker}"',
236
- f" repos: pennyfarthing",
237
- f" status: planning",
238
- f" stories:",
234
+ " repos: pennyfarthing",
235
+ " status: planning",
236
+ " stories:",
239
237
  ]
240
238
  )
241
239
 
@@ -247,12 +245,12 @@ def generate_initiative_yaml(
247
245
  [
248
246
  f' - id: "{story_id}"',
249
247
  f' title: "{title}"',
250
- f" description: |",
248
+ " description: |",
251
249
  f" {story.get('description', title)}",
252
250
  f" points: {story.get('points', 1)}",
253
- f" priority: P0",
254
- f" status: planning",
255
- f" repos: pennyfarthing",
251
+ " priority: P0",
252
+ " status: planning",
253
+ " repos: pennyfarthing",
256
254
  ]
257
255
  )
258
256
 
@@ -304,6 +302,32 @@ def import_epic(
304
302
  # Get next epic number
305
303
  start_epic_num = get_next_epic_number(root)
306
304
 
305
+ # Validate each parsed epic before writing (ADR-0022)
306
+ from pennyfarthing_scripts.sprint.validator import validate_epic_shard
307
+
308
+ epic_num = start_epic_num
309
+ for epic in parsed["epics"]:
310
+ # Build a shard-like dict for validation
311
+ shard_dict = {
312
+ "id": str(epic_num),
313
+ "title": epic.get("title", ""),
314
+ "status": "planning",
315
+ "stories": [
316
+ {
317
+ "id": f"{epic_num}-{s['num']}",
318
+ "title": s.get("title", ""),
319
+ "points": s.get("points", 1),
320
+ "status": "planning",
321
+ }
322
+ for s in epic.get("stories", [])
323
+ ],
324
+ }
325
+ validation = validate_epic_shard(shard_dict)
326
+ if not validation.valid:
327
+ error_msgs = "; ".join(e.message for e in validation.errors)
328
+ return {"success": False, "error": f"Epic {epic_num} validation failed: {error_msgs}"}
329
+ epic_num += 1
330
+
307
331
  # Generate YAML
308
332
  relative_path = str(epics_path.relative_to(root)) if epics_path.is_relative_to(root) else str(epics_path)
309
333
  new_yaml, next_epic_num = generate_initiative_yaml(
@@ -5,6 +5,7 @@ Provides access to sprint/current-sprint.yaml data.
5
5
  Supports sharded per-epic format (epic-{ref}.yaml shard files).
6
6
  """
7
7
 
8
+ import warnings
8
9
  from pathlib import Path
9
10
  from typing import Any
10
11
 
@@ -40,6 +41,11 @@ def _merge_epic_shards(data: dict[str, Any], sprint_dir: Path) -> dict[str, Any]
40
41
  epic_data = load_yaml_config(epic_file)
41
42
  if epic_data is not None:
42
43
  merged_epics.append(epic_data)
44
+ else:
45
+ warnings.warn(
46
+ f"Sprint epic ref '{ref}' not found: {epic_file}",
47
+ stacklevel=2,
48
+ )
43
49
 
44
50
  data["epics"] = merged_epics
45
51
  return data
@@ -6,7 +6,7 @@ Provides functions for getting and displaying sprint status.
6
6
 
7
7
  from typing import Any
8
8
 
9
- from pennyfarthing_scripts.sprint.loader import get_all_stories, get_sprint_info, load_sprint
9
+ from pennyfarthing_scripts.sprint.loader import get_all_stories, get_sprint_info
10
10
 
11
11
 
12
12
  def get_sprint_status(filter_status: str | None = None) -> dict[str, Any]:
@@ -99,7 +99,6 @@ def main(args: list[str] | None = None) -> int:
99
99
  Exit code
100
100
  """
101
101
  import argparse
102
- import sys
103
102
 
104
103
  parser = argparse.ArgumentParser(description="Show sprint status")
105
104
  parser.add_argument(