@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
@@ -312,8 +312,8 @@ def story_add_command(
312
312
  raise click.ClickException("POINTS is required")
313
313
  try:
314
314
  init_points = int(title)
315
- except ValueError:
316
- raise click.ClickException(f"POINTS must be an integer, got '{title}'")
315
+ except ValueError as err:
316
+ raise click.ClickException(f"POINTS must be an integer, got '{title}'") from err
317
317
 
318
318
  result = add_initiative_story(
319
319
  initiative_slug=initiative,
@@ -7,7 +7,7 @@ Steps:
7
7
  1. Archive session file to sprint/archive/{jira-key}-session.md
8
8
  2. Squash merge PR via gh (handle already-merged)
9
9
  3. Transition Jira to Done
10
- 4. Update sprint YAML (status: done, completed date, remove assigned_to)
10
+ 4. Update sprint YAML (status: done, completed date)
11
11
  5. Archive completed epics
12
12
  6. Git cleanup (checkout develop, pull, delete local branch)
13
13
  7. Remove session file
@@ -16,6 +16,7 @@ Steps:
16
16
  import re
17
17
  import shutil
18
18
  import subprocess
19
+ import sys
19
20
  from datetime import date
20
21
  from pathlib import Path
21
22
  from typing import Any
@@ -23,7 +24,6 @@ from typing import Any
23
24
  from pennyfarthing_scripts.sprint.loader import find_epic, find_story
24
25
  from pennyfarthing_scripts.sprint.yaml_io import read_sprint, write_sprint
25
26
 
26
-
27
27
  SESSION_FIELD_RE = re.compile(r"\*\*(\w[\w\s]*):\*\*\s*(.*)")
28
28
 
29
29
 
@@ -175,8 +175,6 @@ def finish_story(
175
175
  if story:
176
176
  story["status"] = "done"
177
177
  story["completed"] = today
178
- if "assigned_to" in story:
179
- del story["assigned_to"]
180
178
  write_sprint(sprint_path, data)
181
179
  steps.append({"step": 4, "action": "yaml_update", "status": "done", "completed": today})
182
180
  else:
@@ -186,7 +184,7 @@ def finish_story(
186
184
 
187
185
  # --- Step 5: Archive completed epics ---
188
186
  result = _run(
189
- ["python", "-m", "pennyfarthing_scripts.cli", "sprint", "epic", "archive"],
187
+ [sys.executable, "-m", "pennyfarthing_scripts.cli", "sprint", "epic", "archive"],
190
188
  cwd=str(project_root),
191
189
  )
192
190
  steps.append({"step": 5, "action": "archive_epics", "ran": True})
@@ -7,6 +7,7 @@ This module provides:
7
7
  - story_update_command (Click command for CLI registration)
8
8
  """
9
9
 
10
+ import subprocess
10
11
  from datetime import date
11
12
  from pathlib import Path
12
13
  from typing import Any
@@ -95,9 +96,6 @@ def update_story(
95
96
 
96
97
  # Auto-cleanup rules
97
98
  if status == "done":
98
- # Remove assigned_to
99
- if "assigned_to" in story:
100
- del story["assigned_to"]
101
99
  # Auto-set completed if not explicitly provided
102
100
  if completed_date is None and "completed" not in story:
103
101
  story["completed"] = date.today().isoformat()
@@ -108,6 +106,16 @@ def update_story(
108
106
  # Auto-set started if not already present
109
107
  if "started" not in story:
110
108
  story["started"] = date.today().isoformat()
109
+ # Auto-set assignee from current Jira user if not already assigned
110
+ if "assigned_to" not in story and assigned_to is None:
111
+ try:
112
+ result = subprocess.run(
113
+ ["jira", "me"], capture_output=True, text=True
114
+ )
115
+ if result.returncode == 0 and result.stdout.strip():
116
+ story["assigned_to"] = result.stdout.strip()
117
+ except Exception:
118
+ pass
111
119
 
112
120
  # Validate after mutation
113
121
  result = validate_full_sprint(data)
@@ -28,7 +28,6 @@ from pennyfarthing_scripts.sprint.yaml_io import (
28
28
  SPRINT_KEY_ORDER,
29
29
  STORY_KEY_ORDER,
30
30
  TOP_KEY_ORDER,
31
- canonical_dump,
32
31
  read_sprint,
33
32
  write_sprint,
34
33
  )
@@ -78,6 +78,10 @@ REQUIRED_STORY_FIELDS = {"id", "title", "status", "points"}
78
78
  # Required fields for epic
79
79
  REQUIRED_EPIC_FIELDS = {"id", "title"}
80
80
 
81
+ # Required fields for epic shard files (write-time validation, ADR-0022)
82
+ REQUIRED_EPIC_SHARD_FIELDS = {"id", "title", "status", "stories"}
83
+ REQUIRED_SHARD_STORY_FIELDS = {"id", "title", "points", "status"}
84
+
81
85
  # Required fields for future.yaml initiative
82
86
  REQUIRED_INITIATIVE_FIELDS = {"name", "status"}
83
87
 
@@ -235,6 +239,15 @@ def validate_epic(epic: dict[str, Any], all_story_ids: set[str], epic_index: int
235
239
  f"{base_path}.{field_name}",
236
240
  )
237
241
 
242
+ # Reject non-string IDs (YAML parses bare integers like `id: 87` as int,
243
+ # which crashes Cyclist's sprint-data.ts — epicId.match() fails on non-strings)
244
+ if "id" in epic and not isinstance(epic["id"], str):
245
+ result.add_error(
246
+ f"Epic ID must be a string, got {type(epic['id']).__name__} ({epic['id']!r}). "
247
+ "Quote it in YAML (e.g., id: \"87\" not id: 87)",
248
+ f"{base_path}.id",
249
+ )
250
+
238
251
  # Validate stories if present
239
252
  if "stories" in epic:
240
253
  seen_in_epic: set[str] = set()
@@ -262,6 +275,95 @@ def validate_epic(epic: dict[str, Any], all_story_ids: set[str], epic_index: int
262
275
  return result
263
276
 
264
277
 
278
+ def validate_epic_shard(epic: dict[str, Any]) -> ValidationResult:
279
+ """Validate an epic shard dict before writing to disk.
280
+
281
+ Enforces stricter requirements than validate_epic() since shards
282
+ are standalone files that must be self-contained.
283
+
284
+ Validates:
285
+ - Required fields present (id, title, status, stories)
286
+ - stories is a list
287
+ - Each story has required fields (id, title, points, status)
288
+ - No duplicate story IDs within the epic
289
+ - jira key follows MSSCI-NNNNN pattern if present
290
+
291
+ Args:
292
+ epic: Epic shard dict to validate
293
+
294
+ Returns:
295
+ ValidationResult with any errors found
296
+ """
297
+ result = ValidationResult(valid=True)
298
+
299
+ # Check required shard fields
300
+ for field_name in REQUIRED_EPIC_SHARD_FIELDS:
301
+ if field_name not in epic:
302
+ result.add_error(
303
+ f"Missing required field: {field_name}",
304
+ f"epic.{field_name}",
305
+ )
306
+
307
+ # Reject non-string IDs (YAML parses bare integers like `id: 87` as int,
308
+ # which crashes Cyclist's sprint-data.ts checkEpicContext — epicId.match() fails)
309
+ if "id" in epic and not isinstance(epic["id"], str):
310
+ result.add_error(
311
+ f"Epic ID must be a string, got {type(epic['id']).__name__} ({epic['id']!r}). "
312
+ "Quote it in YAML (e.g., id: \"87\" not id: 87)",
313
+ "epic.id",
314
+ )
315
+
316
+ # Reject epic- prefix in ID (ADR-0022: reference prefix should not be baked into value)
317
+ if "id" in epic:
318
+ epic_id_val = str(epic["id"])
319
+ if epic_id_val.startswith("epic-"):
320
+ result.add_error(
321
+ f"Epic ID '{epic_id_val}' starts with 'epic-' prefix. "
322
+ "Use the numeric ID (e.g., '94' not 'epic-94')",
323
+ "epic.id",
324
+ )
325
+
326
+ # Validate jira key format if present
327
+ if "jira" in epic:
328
+ jira_key = str(epic["jira"])
329
+ if not JIRA_KEY_PATTERN.match(jira_key):
330
+ result.add_error(
331
+ f"Invalid Jira key format '{jira_key}'. Expected MSSCI-NNNNN",
332
+ "epic.jira",
333
+ )
334
+
335
+ # Validate stories field
336
+ if "stories" in epic:
337
+ stories = epic["stories"]
338
+ if not isinstance(stories, list):
339
+ result.add_error(
340
+ "'stories' must be a list",
341
+ "epic.stories",
342
+ )
343
+ else:
344
+ seen_ids: set[str] = set()
345
+ epic_id = epic.get("id", "epic")
346
+ for idx, story in enumerate(stories):
347
+ story_id = story.get("id")
348
+ if story_id:
349
+ if story_id in seen_ids:
350
+ result.add_error(
351
+ f"Duplicate story ID '{story_id}' within epic",
352
+ f"epic.stories[{idx}].id",
353
+ )
354
+ seen_ids.add(story_id)
355
+
356
+ # Check required story fields
357
+ for field_name in REQUIRED_SHARD_STORY_FIELDS:
358
+ if field_name not in story:
359
+ result.add_error(
360
+ f"Missing required field: {field_name}",
361
+ f"{epic_id}.stories[{idx}].{field_name}",
362
+ )
363
+
364
+ return result
365
+
366
+
265
367
  def validate_full_sprint(data: dict[str, Any]) -> ValidationResult:
266
368
  """Validate complete sprint YAML including all epics and stories.
267
369
 
@@ -356,7 +458,7 @@ def validate_future(data: dict[str, Any]) -> ValidationResult:
356
458
  if isinstance(initiative, str):
357
459
  continue
358
460
  if not isinstance(initiative, dict):
359
- result.add_error(f"Initiative must be a mapping", f"future.initiatives[{i}]")
461
+ result.add_error("Initiative must be a mapping", f"future.initiatives[{i}]")
360
462
  continue
361
463
 
362
464
  base_path = f"future.initiatives[{i}]"
@@ -433,13 +535,15 @@ def validate_future(data: dict[str, Any]) -> ValidationResult:
433
535
  return result
434
536
 
435
537
 
436
- def validate_sprint_file(file_path: Path) -> ValidationResult:
538
+ def validate_sprint_file(file_path: Path, *, strict: bool = False) -> ValidationResult:
437
539
  """Validate a sprint YAML file from disk.
438
540
 
439
- Loads the file and validates its contents.
541
+ Loads the file and validates its contents. In strict mode, loader
542
+ warnings (e.g., unresolvable shard refs) are promoted to errors.
440
543
 
441
544
  Args:
442
545
  file_path: Path to sprint YAML file
546
+ strict: If True, treat loader warnings as validation errors
443
547
 
444
548
  Returns:
445
549
  ValidationResult with any errors (including load errors)
@@ -509,12 +613,22 @@ def validate_sprint_file(file_path: Path) -> ValidationResult:
509
613
  )
510
614
  return result
511
615
 
512
- # Merge sharded epic files if present
616
+ # Merge sharded epic files if present, capturing warnings in strict mode
513
617
  from pennyfarthing_scripts.sprint.loader import _merge_epic_shards
514
- data = _merge_epic_shards(data, file_path.parent)
618
+ if strict:
619
+ import warnings as _warnings
620
+ with _warnings.catch_warnings(record=True) as caught:
621
+ _warnings.simplefilter("always")
622
+ data = _merge_epic_shards(data, file_path.parent)
623
+ for w in caught:
624
+ result.add_error(str(w.message), str(file_path))
625
+ else:
626
+ data = _merge_epic_shards(data, file_path.parent)
515
627
 
516
628
  # Validate loaded data
517
- return validate_full_sprint(data)
629
+ full_result = validate_full_sprint(data)
630
+ result.merge(full_result)
631
+ return result
518
632
 
519
633
 
520
634
  def format_validation_errors(result: ValidationResult) -> str:
@@ -7,11 +7,8 @@ Provides functions for starting and managing work on stories.
7
7
  from typing import Any
8
8
 
9
9
  from pennyfarthing_scripts.sprint.loader import (
10
- find_epic,
11
- find_story,
12
10
  get_stories_by_status,
13
11
  get_story_by_id,
14
- load_sprint,
15
12
  )
16
13
 
17
14
 
@@ -220,7 +217,7 @@ def main(args: list[str] | None = None) -> int:
220
217
  print(f"Story: {story.get('id')}")
221
218
  print(f"Title: {story.get('title')}")
222
219
  print(f"Points: {story.get('points')}")
223
- print(f"Status: Available")
220
+ print("Status: Available")
224
221
  return 0
225
222
  else:
226
223
  print(f"Not available: {result.get('error') or result.get('reason')}", file=sys.stderr)
@@ -272,7 +272,9 @@ def canonical_dump(data: Any) -> str:
272
272
  def _get_epic_ref(epic: Mapping) -> str:
273
273
  """Get the canonical reference ID for an epic shard file.
274
274
 
275
- Mirrors the logic in migrate-to-shards.py: prefer Jira key, fall back to ID.
275
+ Priority: Jira key > numeric ID extracted from epic-N > raw ID.
276
+ Strips 'epic-' prefix from IDs to prevent double-prefix filenames
277
+ (e.g., epic-epic-94.yaml). See ADR-0022.
276
278
  """
277
279
  jira = epic.get("jira")
278
280
  epic_id = str(epic.get("id", ""))
@@ -281,7 +283,13 @@ def _get_epic_ref(epic: Mapping) -> str:
281
283
  return str(jira)
282
284
  if JIRA_PATTERN.match(epic_id):
283
285
  return epic_id
284
- return epic_id
286
+
287
+ # Strip epic- prefix to prevent double-prefix filenames
288
+ # e.g., "epic-94" -> "94" so file becomes "epic-94.yaml" not "epic-epic-94.yaml"
289
+ stripped = epic_id
290
+ while stripped.startswith("epic-"):
291
+ stripped = stripped[5:]
292
+ return stripped or epic_id
285
293
 
286
294
 
287
295
  def _write_yaml_file(path: Path, data: Any) -> None:
@@ -15,6 +15,20 @@ Usage:
15
15
  """
16
16
 
17
17
  # Re-export common functions
18
+ # Import submodules
19
+ # CLI entry point - import module, not function, so "from story import cli" gets the module
20
+ from pennyfarthing_scripts.story import (
21
+ cli,
22
+ create,
23
+ size,
24
+ template,
25
+ )
26
+ from pennyfarthing_scripts.story.cli import main
27
+ from pennyfarthing_scripts.story.create import (
28
+ create_story,
29
+ generate_story_yaml,
30
+ validate_points,
31
+ )
18
32
  from pennyfarthing_scripts.story.size import (
19
33
  SIZING_GUIDELINES,
20
34
  format_size_info,
@@ -26,22 +40,6 @@ from pennyfarthing_scripts.story.template import (
26
40
  get_all_templates,
27
41
  get_template,
28
42
  )
29
- from pennyfarthing_scripts.story.create import (
30
- create_story,
31
- generate_story_yaml,
32
- validate_points,
33
- )
34
-
35
- # Import submodules
36
- from pennyfarthing_scripts.story import (
37
- create,
38
- size,
39
- template,
40
- )
41
-
42
- # CLI entry point - import module, not function, so "from story import cli" gets the module
43
- from pennyfarthing_scripts.story import cli
44
- from pennyfarthing_scripts.story.cli import main
45
43
 
46
44
  __all__ = [
47
45
  # Size
@@ -6,7 +6,6 @@ Provides guidelines and helpers for sizing stories.
6
6
 
7
7
  from typing import Any
8
8
 
9
-
10
9
  # Sizing guidelines
11
10
  SIZING_GUIDELINES = {
12
11
  1: {
@@ -6,7 +6,6 @@ Provides templates for different story types.
6
6
 
7
7
  from typing import Any
8
8
 
9
-
10
9
  # Story templates
11
10
  TEMPLATES = {
12
11
  "feature": {
@@ -19,7 +19,6 @@ from dataclasses import dataclass, field
19
19
  from pathlib import Path
20
20
  from typing import Any
21
21
 
22
-
23
22
  # Default cache location for SWE-bench data
24
23
  DEFAULT_CACHE_PATH = "/tmp/swebench_all.json"
25
24
 
@@ -108,7 +107,7 @@ def load_swebench_data(cache_path: str | Path = DEFAULT_CACHE_PATH) -> list[dict
108
107
  FileNotFoundError: If cache file doesn't exist
109
108
  json.JSONDecodeError: If cache file is invalid JSON
110
109
  """
111
- with open(cache_path, "r") as f:
110
+ with open(cache_path) as f:
112
111
  return json.load(f)
113
112
 
114
113
 
@@ -3,10 +3,9 @@
3
3
  Story 63-9: Reorganize pennyfarthing_scripts into fan-out CLI pattern.
4
4
  """
5
5
 
6
- import os
7
6
  import sys
7
+ from collections.abc import Generator
8
8
  from pathlib import Path
9
- from typing import Generator
10
9
 
11
10
  import pytest
12
11
 
@@ -12,37 +12,34 @@ Tests verify:
12
12
  7. CLI integration
13
13
  """
14
14
 
15
- import asyncio
16
15
  import subprocess
17
16
  import sys
18
17
  import tempfile
18
+ from collections.abc import Generator
19
19
  from pathlib import Path
20
- from typing import Generator
21
- from unittest.mock import MagicMock, patch
22
20
 
23
21
  import pytest
24
22
 
25
23
  from pennyfarthing_scripts.brownfield import (
26
24
  DepthLevel,
27
- ProjectType,
28
25
  DiscoveryResult,
26
+ ProjectType,
27
+ detect_architecture_patterns,
29
28
  detect_project_type,
30
29
  detect_tech_stack,
31
- scan_directory_structure,
32
- detect_architecture_patterns,
30
+ discover,
31
+ generate_ai_guidance_doc,
33
32
  generate_project_overview,
34
- generate_tech_stack_doc,
35
33
  generate_source_tree_doc,
36
- generate_ai_guidance_doc,
37
- discover,
34
+ generate_tech_stack_doc,
35
+ scan_directory_structure,
38
36
  )
39
37
  from pennyfarthing_scripts.brownfield.discover import (
40
- TechStackItem,
41
- DirectoryNode,
42
38
  ArchitecturePattern,
39
+ DirectoryNode,
40
+ TechStackItem,
43
41
  )
44
42
 
45
-
46
43
  # =============================================================================
47
44
  # FIXTURES
48
45
  # =============================================================================
@@ -623,7 +620,7 @@ class TestDiscover:
623
620
  output_dir = temp_project_dir / "output"
624
621
  output_dir.mkdir()
625
622
 
626
- result = await discover(node_project, output_dir=output_dir, depth=DepthLevel.DEEP)
623
+ await discover(node_project, output_dir=output_dir, depth=DepthLevel.DEEP)
627
624
 
628
625
  expected_files = [
629
626
  "project-overview.md",
@@ -8,10 +8,6 @@ properly delegate to library modules.
8
8
 
9
9
  import subprocess
10
10
  import sys
11
- from typing import Any
12
- from unittest.mock import MagicMock, patch
13
-
14
- import pytest
15
11
 
16
12
 
17
13
  class TestJiraCLIModule:
@@ -9,16 +9,13 @@ Tests cover: models, analyze engine, CLI, formatters.
9
9
 
10
10
  from __future__ import annotations
11
11
 
12
- import asyncio
13
12
  import json
14
13
  from dataclasses import asdict
15
14
  from pathlib import Path
16
- from typing import Any
17
- from unittest.mock import AsyncMock, MagicMock, patch
15
+ from unittest.mock import AsyncMock, patch
18
16
 
19
17
  import pytest
20
18
 
21
-
22
19
  # ---------------------------------------------------------------------------
23
20
  # Models
24
21
  # ---------------------------------------------------------------------------
@@ -123,7 +120,9 @@ class TestCodeMarkersResultModel:
123
120
  def test_json_serializable(self) -> None:
124
121
  """Full result serializes to JSON via asdict."""
125
122
  from pennyfarthing_scripts.codemarkers.models import (
126
- CodeMarker, CodeMarkersResult, MarkerSummary,
123
+ CodeMarker,
124
+ CodeMarkersResult,
125
+ MarkerSummary,
127
126
  )
128
127
 
129
128
  r = CodeMarkersResult(
@@ -534,7 +533,7 @@ class TestTableFormatter:
534
533
  ]
535
534
  out = format_marker_table(markers, top_n=5)
536
535
  # Header + separator + 5 data rows = 7 lines
537
- lines = [l for l in out.strip().split("\n") if l.strip()]
536
+ lines = [line for line in out.strip().split("\n") if line.strip()]
538
537
  assert len(lines) <= 7
539
538
 
540
539
 
@@ -544,7 +543,9 @@ class TestJsonExport:
544
543
  def test_valid_json(self) -> None:
545
544
  from pennyfarthing_scripts.codemarkers.formatters import export_json
546
545
  from pennyfarthing_scripts.codemarkers.models import (
547
- CodeMarker, CodeMarkersResult, MarkerSummary,
546
+ CodeMarker,
547
+ CodeMarkersResult,
548
+ MarkerSummary,
548
549
  )
549
550
 
550
551
  r = CodeMarkersResult(
@@ -587,6 +588,7 @@ class TestCLI:
587
588
  def test_cli_help(self) -> None:
588
589
  """codemarkers group shows help."""
589
590
  from click.testing import CliRunner
591
+
590
592
  from pennyfarthing_scripts.codemarkers.cli import codemarkers
591
593
 
592
594
  runner = CliRunner()
@@ -597,6 +599,7 @@ class TestCLI:
597
599
  def test_analyze_command_exists(self) -> None:
598
600
  """analyze subcommand is registered."""
599
601
  from click.testing import CliRunner
602
+
600
603
  from pennyfarthing_scripts.codemarkers.cli import codemarkers
601
604
 
602
605
  runner = CliRunner()
@@ -608,6 +611,7 @@ class TestCLI:
608
611
  def test_stale_command_exists(self) -> None:
609
612
  """stale subcommand is registered."""
610
613
  from click.testing import CliRunner
614
+
611
615
  from pennyfarthing_scripts.codemarkers.cli import codemarkers
612
616
 
613
617
  runner = CliRunner()
@@ -617,6 +621,7 @@ class TestCLI:
617
621
  def test_summary_command_exists(self) -> None:
618
622
  """summary subcommand is registered."""
619
623
  from click.testing import CliRunner
624
+
620
625
  from pennyfarthing_scripts.codemarkers.cli import codemarkers
621
626
 
622
627
  runner = CliRunner()
@@ -626,6 +631,7 @@ class TestCLI:
626
631
  def test_json_format_option(self) -> None:
627
632
  """--format json produces JSON output."""
628
633
  from click.testing import CliRunner
634
+
629
635
  from pennyfarthing_scripts.codemarkers.cli import codemarkers
630
636
 
631
637
  runner = CliRunner()
@@ -672,7 +678,6 @@ class TestModuleExports:
672
678
  from pennyfarthing_scripts.codemarkers import (
673
679
  CodeMarker,
674
680
  CodeMarkersResult,
675
- MarkerSummary,
676
681
  )
677
682
  assert CodeMarker is not None
678
683
  assert CodeMarkersResult is not None