@pennyfarthing/core 10.0.3 → 10.1.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 (282) hide show
  1. package/README.md +9 -7
  2. package/package.json +7 -1
  3. package/packages/core/dist/cli/commands/cyclist.d.ts +5 -1
  4. package/packages/core/dist/cli/commands/cyclist.d.ts.map +1 -1
  5. package/packages/core/dist/cli/commands/cyclist.js +4 -4
  6. package/packages/core/dist/cli/commands/cyclist.js.map +1 -1
  7. package/packages/core/dist/cli/commands/cyclist.test.js +2 -2
  8. package/packages/core/dist/cli/commands/cyclist.test.js.map +1 -1
  9. package/packages/core/dist/cli/commands/doctor-legacy.test.js +17 -16
  10. package/packages/core/dist/cli/commands/doctor-legacy.test.js.map +1 -1
  11. package/packages/core/dist/cli/commands/doctor.d.ts.map +1 -1
  12. package/packages/core/dist/cli/commands/doctor.js +251 -4
  13. package/packages/core/dist/cli/commands/doctor.js.map +1 -1
  14. package/packages/core/dist/cli/commands/init.d.ts +7 -0
  15. package/packages/core/dist/cli/commands/init.d.ts.map +1 -1
  16. package/packages/core/dist/cli/commands/init.js +43 -7
  17. package/packages/core/dist/cli/commands/init.js.map +1 -1
  18. package/packages/core/dist/cli/commands/update.d.ts.map +1 -1
  19. package/packages/core/dist/cli/commands/update.js +26 -0
  20. package/packages/core/dist/cli/commands/update.js.map +1 -1
  21. package/packages/core/dist/cli/index.js +1 -1
  22. package/packages/core/dist/cli/index.js.map +1 -1
  23. package/packages/core/dist/cli/ocean-profiles.test.js +1 -1
  24. package/packages/core/dist/cli/ocean-profiles.test.js.map +1 -1
  25. package/packages/core/dist/cli/utils/files.d.ts +10 -0
  26. package/packages/core/dist/cli/utils/files.d.ts.map +1 -1
  27. package/packages/core/dist/cli/utils/files.js +35 -0
  28. package/packages/core/dist/cli/utils/files.js.map +1 -1
  29. package/packages/core/dist/cli/utils/python.d.ts +22 -0
  30. package/packages/core/dist/cli/utils/python.d.ts.map +1 -0
  31. package/packages/core/dist/cli/utils/python.js +102 -0
  32. package/packages/core/dist/cli/utils/python.js.map +1 -0
  33. package/packages/core/dist/cli/utils/settings.d.ts.map +1 -1
  34. package/packages/core/dist/cli/utils/settings.js +10 -0
  35. package/packages/core/dist/cli/utils/settings.js.map +1 -1
  36. package/packages/core/dist/scripts/generate-report.d.ts.map +1 -1
  37. package/packages/core/dist/scripts/generate-report.js +11 -7
  38. package/packages/core/dist/scripts/generate-report.js.map +1 -1
  39. package/packages/core/dist/scripts/generate-spider-report.d.ts.map +1 -1
  40. package/packages/core/dist/scripts/generate-spider-report.js +12 -8
  41. package/packages/core/dist/scripts/generate-spider-report.js.map +1 -1
  42. package/packages/core/dist/scripts/generate-spider.d.ts.map +1 -1
  43. package/packages/core/dist/scripts/generate-spider.js +6 -4
  44. package/packages/core/dist/scripts/generate-spider.js.map +1 -1
  45. package/packages/core/dist/scripts/generate-spider.test.js +2 -2
  46. package/packages/core/dist/scripts/generate-spider.test.js.map +1 -1
  47. package/pennyfarthing-dist/agents/README.md +1 -3
  48. package/pennyfarthing-dist/agents/architect.md +0 -6
  49. package/pennyfarthing-dist/agents/devops.md +0 -6
  50. package/pennyfarthing-dist/agents/orchestrator.md +0 -6
  51. package/pennyfarthing-dist/agents/pm.md +1 -7
  52. package/pennyfarthing-dist/agents/sm-finish.md +1 -1
  53. package/pennyfarthing-dist/agents/sm-setup.md +2 -2
  54. package/pennyfarthing-dist/agents/sm.md +4 -11
  55. package/pennyfarthing-dist/commands/architect.md +11 -3
  56. package/pennyfarthing-dist/commands/close-epic.md +24 -131
  57. package/pennyfarthing-dist/commands/create-theme.md +14 -24
  58. package/pennyfarthing-dist/commands/dev.md +11 -3
  59. package/pennyfarthing-dist/commands/devops.md +11 -3
  60. package/pennyfarthing-dist/commands/health-check.md +1 -3
  61. package/pennyfarthing-dist/commands/help.md +8 -12
  62. package/pennyfarthing-dist/commands/list-themes.md +14 -16
  63. package/pennyfarthing-dist/commands/orchestrator.md +11 -3
  64. package/pennyfarthing-dist/commands/parallel-work.md +1 -3
  65. package/pennyfarthing-dist/commands/pm.md +11 -3
  66. package/pennyfarthing-dist/commands/prime.md +6 -6
  67. package/pennyfarthing-dist/commands/repo-status.md +2 -2
  68. package/pennyfarthing-dist/commands/reviewer.md +11 -3
  69. package/pennyfarthing-dist/commands/run-ci.md +1 -1
  70. package/pennyfarthing-dist/commands/set-theme.md +14 -51
  71. package/pennyfarthing-dist/commands/setup.md +1 -1
  72. package/pennyfarthing-dist/commands/show-theme.md +14 -16
  73. package/pennyfarthing-dist/commands/sm.md +11 -3
  74. package/pennyfarthing-dist/commands/sprint.md +8 -8
  75. package/pennyfarthing-dist/commands/tea.md +11 -3
  76. package/pennyfarthing-dist/commands/tech-writer.md +11 -3
  77. package/pennyfarthing-dist/commands/theme-maker.md +14 -671
  78. package/pennyfarthing-dist/commands/theme.md +95 -0
  79. package/pennyfarthing-dist/commands/ux-designer.md +11 -3
  80. package/pennyfarthing-dist/commands/work.md +3 -5
  81. package/pennyfarthing-dist/guides/agent-coordination.md +11 -13
  82. package/pennyfarthing-dist/guides/agent-template-tactical.md +2 -3
  83. package/pennyfarthing-dist/guides/command-tag-taxonomy.md +212 -0
  84. package/pennyfarthing-dist/guides/hooks.md +5 -5
  85. package/pennyfarthing-dist/guides/patterns/fan-out-fan-in-pattern.md +3 -3
  86. package/pennyfarthing-dist/guides/patterns/helper-delegation-pattern.md +9 -59
  87. package/pennyfarthing-dist/guides/patterns/tdd-flow-pattern.md +4 -5
  88. package/pennyfarthing-dist/guides/prime.md +2 -2
  89. package/pennyfarthing-dist/guides/skill-schema.md +25 -26
  90. package/pennyfarthing-dist/guides/xml-tags.md +2 -2
  91. package/pennyfarthing-dist/scripts/README.md +2 -2
  92. package/pennyfarthing-dist/scripts/core/agent-session.sh +6 -2
  93. package/pennyfarthing-dist/scripts/core/prime.sh +8 -10
  94. package/pennyfarthing-dist/scripts/git/git-status-all.sh +1 -1
  95. package/pennyfarthing-dist/scripts/git/install-git-hooks.sh +8 -6
  96. package/pennyfarthing-dist/scripts/git/worktree-manager.sh +3 -3
  97. package/pennyfarthing-dist/scripts/hooks/post-merge.sh +14 -12
  98. package/pennyfarthing-dist/scripts/hooks/pre-commit.sh +4 -3
  99. package/pennyfarthing-dist/scripts/hooks/pre-push.sh +11 -5
  100. package/pennyfarthing-dist/scripts/hooks/sprint-yaml-validation.sh +1 -1
  101. package/pennyfarthing-dist/scripts/misc/README.md +1 -1
  102. package/pennyfarthing-dist/scripts/misc/repo-utils.sh +3 -3
  103. package/pennyfarthing-dist/scripts/misc/validate-subagent-frontmatter.sh +1 -2
  104. package/pennyfarthing-dist/scripts/sprint/README.md +32 -17
  105. package/pennyfarthing-dist/scripts/story/README.md +1 -1
  106. package/pennyfarthing-dist/scripts/test/test-setup.sh +1 -1
  107. package/pennyfarthing-dist/scripts/tests/handoff-phase-update.test.sh +5 -5
  108. package/pennyfarthing-dist/scripts/tests/test-drift-detection.sh +3 -79
  109. package/pennyfarthing-dist/scripts/theme/README.md +1 -1
  110. package/pennyfarthing-dist/scripts/validation/validate-agent-schema.sh +0 -1
  111. package/pennyfarthing-dist/scripts/workflow/finish-story.sh +62 -17
  112. package/pennyfarthing-dist/skills/dev-patterns/SKILL.md +2 -2
  113. package/pennyfarthing-dist/skills/skill-registry.yaml +41 -28
  114. package/pennyfarthing-dist/skills/sprint/skill.md +386 -68
  115. package/pennyfarthing-dist/skills/story/skill.md +14 -206
  116. package/pennyfarthing-dist/skills/theme/skill.md +290 -75
  117. package/pennyfarthing-dist/skills/theme-creation/SKILL.md +23 -166
  118. package/pennyfarthing-dist/skills/workflow/skill.md +4 -4
  119. package/pennyfarthing-dist/templates/agent-scopes.yaml.template +0 -11
  120. package/pennyfarthing-dist/templates/auto-load-sm.sh.template +14 -0
  121. package/pennyfarthing-dist/templates/settings.local.json.template +9 -0
  122. package/pennyfarthing-dist/workflows/2party-tdd.yaml +399 -0
  123. package/pennyfarthing-dist/workflows/epics-and-stories/steps/step-05-import-to-future.md +42 -25
  124. package/pennyfarthing-dist/workflows/git-cleanup.yaml +1 -1
  125. package/pennyfarthing-dist/workflows/project-setup/steps/step-10-complete.md +1 -1
  126. package/pennyfarthing_scripts/__pycache__/cli.cpython-314.pyc +0 -0
  127. package/pennyfarthing_scripts/__pycache__/hooks.cpython-314.pyc +0 -0
  128. package/pennyfarthing_scripts/__pycache__/schema_validation_hook.cpython-314.pyc +0 -0
  129. package/pennyfarthing_scripts/__pycache__/workflow.cpython-314.pyc +0 -0
  130. package/pennyfarthing_scripts/cli.py +15 -0
  131. package/pennyfarthing_scripts/codemarkers/__init__.py +19 -0
  132. package/pennyfarthing_scripts/codemarkers/__main__.py +6 -0
  133. package/pennyfarthing_scripts/codemarkers/__pycache__/__init__.cpython-314.pyc +0 -0
  134. package/pennyfarthing_scripts/codemarkers/__pycache__/__main__.cpython-314.pyc +0 -0
  135. package/pennyfarthing_scripts/codemarkers/__pycache__/analyze.cpython-314.pyc +0 -0
  136. package/pennyfarthing_scripts/codemarkers/__pycache__/cli.cpython-314.pyc +0 -0
  137. package/pennyfarthing_scripts/codemarkers/__pycache__/formatters.cpython-314.pyc +0 -0
  138. package/pennyfarthing_scripts/codemarkers/__pycache__/models.cpython-314.pyc +0 -0
  139. package/pennyfarthing_scripts/codemarkers/analyze.py +326 -0
  140. package/pennyfarthing_scripts/codemarkers/cli.py +129 -0
  141. package/pennyfarthing_scripts/codemarkers/formatters.py +89 -0
  142. package/pennyfarthing_scripts/codemarkers/models.py +45 -0
  143. package/pennyfarthing_scripts/common/__pycache__/config.cpython-314.pyc +0 -0
  144. package/pennyfarthing_scripts/common/__pycache__/themes.cpython-314.pyc +0 -0
  145. package/pennyfarthing_scripts/common/config.py +2 -1
  146. package/pennyfarthing_scripts/complexity/__init__.py +15 -0
  147. package/pennyfarthing_scripts/complexity/__main__.py +6 -0
  148. package/pennyfarthing_scripts/complexity/__pycache__/__init__.cpython-314.pyc +0 -0
  149. package/pennyfarthing_scripts/complexity/__pycache__/__main__.cpython-314.pyc +0 -0
  150. package/pennyfarthing_scripts/complexity/__pycache__/analyze.cpython-314.pyc +0 -0
  151. package/pennyfarthing_scripts/complexity/__pycache__/cli.cpython-314.pyc +0 -0
  152. package/pennyfarthing_scripts/complexity/__pycache__/formatters.cpython-314.pyc +0 -0
  153. package/pennyfarthing_scripts/complexity/__pycache__/models.cpython-314.pyc +0 -0
  154. package/pennyfarthing_scripts/complexity/analyze.py +207 -0
  155. package/pennyfarthing_scripts/complexity/cli.py +78 -0
  156. package/pennyfarthing_scripts/complexity/formatters.py +64 -0
  157. package/pennyfarthing_scripts/complexity/models.py +32 -0
  158. package/pennyfarthing_scripts/deadcode/__init__.py +6 -0
  159. package/pennyfarthing_scripts/deadcode/__main__.py +6 -0
  160. package/pennyfarthing_scripts/deadcode/__pycache__/__init__.cpython-314.pyc +0 -0
  161. package/pennyfarthing_scripts/deadcode/__pycache__/__main__.cpython-314.pyc +0 -0
  162. package/pennyfarthing_scripts/deadcode/__pycache__/analyze.cpython-314.pyc +0 -0
  163. package/pennyfarthing_scripts/deadcode/__pycache__/cli.cpython-314.pyc +0 -0
  164. package/pennyfarthing_scripts/deadcode/__pycache__/formatters.cpython-314.pyc +0 -0
  165. package/pennyfarthing_scripts/deadcode/__pycache__/models.cpython-314.pyc +0 -0
  166. package/pennyfarthing_scripts/deadcode/analyze.py +323 -0
  167. package/pennyfarthing_scripts/deadcode/cli.py +163 -0
  168. package/pennyfarthing_scripts/deadcode/formatters.py +106 -0
  169. package/pennyfarthing_scripts/deadcode/models.py +54 -0
  170. package/pennyfarthing_scripts/dependencies/__init__.py +20 -0
  171. package/pennyfarthing_scripts/dependencies/__main__.py +5 -0
  172. package/pennyfarthing_scripts/dependencies/__pycache__/__init__.cpython-314.pyc +0 -0
  173. package/pennyfarthing_scripts/dependencies/__pycache__/__main__.cpython-314.pyc +0 -0
  174. package/pennyfarthing_scripts/dependencies/__pycache__/analyze.cpython-314.pyc +0 -0
  175. package/pennyfarthing_scripts/dependencies/__pycache__/cli.cpython-314.pyc +0 -0
  176. package/pennyfarthing_scripts/dependencies/__pycache__/formatters.cpython-314.pyc +0 -0
  177. package/pennyfarthing_scripts/dependencies/__pycache__/models.cpython-314.pyc +0 -0
  178. package/pennyfarthing_scripts/dependencies/analyze.py +155 -0
  179. package/pennyfarthing_scripts/dependencies/cli.py +72 -0
  180. package/pennyfarthing_scripts/dependencies/formatters.py +63 -0
  181. package/pennyfarthing_scripts/dependencies/models.py +39 -0
  182. package/pennyfarthing_scripts/healthscore/__init__.py +21 -0
  183. package/pennyfarthing_scripts/healthscore/__main__.py +6 -0
  184. package/pennyfarthing_scripts/healthscore/__pycache__/__init__.cpython-314.pyc +0 -0
  185. package/pennyfarthing_scripts/healthscore/__pycache__/__main__.cpython-314.pyc +0 -0
  186. package/pennyfarthing_scripts/healthscore/__pycache__/analyze.cpython-314.pyc +0 -0
  187. package/pennyfarthing_scripts/healthscore/__pycache__/cli.cpython-314.pyc +0 -0
  188. package/pennyfarthing_scripts/healthscore/__pycache__/formatters.cpython-314.pyc +0 -0
  189. package/pennyfarthing_scripts/healthscore/__pycache__/models.cpython-314.pyc +0 -0
  190. package/pennyfarthing_scripts/healthscore/analyze.py +161 -0
  191. package/pennyfarthing_scripts/healthscore/cli.py +76 -0
  192. package/pennyfarthing_scripts/healthscore/formatters.py +46 -0
  193. package/pennyfarthing_scripts/healthscore/models.py +44 -0
  194. package/pennyfarthing_scripts/hotspots/__pycache__/__init__.cpython-314.pyc +0 -0
  195. package/pennyfarthing_scripts/hotspots/__pycache__/__main__.cpython-314.pyc +0 -0
  196. package/pennyfarthing_scripts/hotspots/__pycache__/analyze.cpython-314.pyc +0 -0
  197. package/pennyfarthing_scripts/hotspots/__pycache__/cli.cpython-314.pyc +0 -0
  198. package/pennyfarthing_scripts/hotspots/__pycache__/formatters.cpython-314.pyc +0 -0
  199. package/pennyfarthing_scripts/hotspots/__pycache__/models.cpython-314.pyc +0 -0
  200. package/pennyfarthing_scripts/hotspots/analyze.py +28 -1
  201. package/pennyfarthing_scripts/hotspots/cli.py +11 -9
  202. package/pennyfarthing_scripts/jira/__pycache__/__init__.cpython-314.pyc +0 -0
  203. package/pennyfarthing_scripts/jira/__pycache__/bidirectional.cpython-314.pyc +0 -0
  204. package/pennyfarthing_scripts/jira/__pycache__/cli.cpython-314.pyc +0 -0
  205. package/pennyfarthing_scripts/jira/__pycache__/client.cpython-314.pyc +0 -0
  206. package/pennyfarthing_scripts/jira/__pycache__/create.cpython-314.pyc +0 -0
  207. package/pennyfarthing_scripts/jira/__pycache__/operations.cpython-314.pyc +0 -0
  208. package/pennyfarthing_scripts/jira/__pycache__/reconcile.cpython-314.pyc +0 -0
  209. package/pennyfarthing_scripts/jira/bidirectional.py +42 -15
  210. package/pennyfarthing_scripts/jira/cli.py +78 -1
  211. package/pennyfarthing_scripts/jira/client.py +28 -0
  212. package/pennyfarthing_scripts/prime/__pycache__/cli.cpython-314.pyc +0 -0
  213. package/pennyfarthing_scripts/prime/__pycache__/models.cpython-314.pyc +0 -0
  214. package/pennyfarthing_scripts/prime/__pycache__/persona.cpython-314.pyc +0 -0
  215. package/pennyfarthing_scripts/prime/__pycache__/tiers.cpython-314.pyc +0 -0
  216. package/pennyfarthing_scripts/prime/__pycache__/workflow.cpython-314.pyc +0 -0
  217. package/pennyfarthing_scripts/prime/workflow.py +5 -3
  218. package/pennyfarthing_scripts/sprint/__pycache__/archive.cpython-314.pyc +0 -0
  219. package/pennyfarthing_scripts/sprint/__pycache__/archive_epic.cpython-314.pyc +0 -0
  220. package/pennyfarthing_scripts/sprint/__pycache__/cli.cpython-314.pyc +0 -0
  221. package/pennyfarthing_scripts/sprint/__pycache__/epic_add.cpython-314.pyc +0 -0
  222. package/pennyfarthing_scripts/sprint/__pycache__/loader.cpython-314.pyc +0 -0
  223. package/pennyfarthing_scripts/sprint/__pycache__/story_add.cpython-314.pyc +0 -0
  224. package/pennyfarthing_scripts/sprint/__pycache__/story_finish.cpython-314.pyc +0 -0
  225. package/pennyfarthing_scripts/sprint/__pycache__/story_update.cpython-314.pyc +0 -0
  226. package/pennyfarthing_scripts/sprint/__pycache__/validate_cmd.cpython-314.pyc +0 -0
  227. package/pennyfarthing_scripts/sprint/__pycache__/validator.cpython-314.pyc +0 -0
  228. package/pennyfarthing_scripts/sprint/__pycache__/work.cpython-314.pyc +0 -0
  229. package/pennyfarthing_scripts/sprint/__pycache__/yaml_io.cpython-314.pyc +0 -0
  230. package/pennyfarthing_scripts/sprint/archive.py +63 -6
  231. package/pennyfarthing_scripts/sprint/archive_epic.py +198 -85
  232. package/pennyfarthing_scripts/sprint/cli.py +1565 -65
  233. package/pennyfarthing_scripts/sprint/epic_add.py +173 -0
  234. package/pennyfarthing_scripts/sprint/loader.py +46 -2
  235. package/pennyfarthing_scripts/sprint/story_add.py +202 -27
  236. package/pennyfarthing_scripts/sprint/story_finish.py +211 -0
  237. package/pennyfarthing_scripts/sprint/validate_cmd.py +44 -5
  238. package/pennyfarthing_scripts/sprint/validator.py +13 -3
  239. package/pennyfarthing_scripts/sprint/work.py +43 -3
  240. package/pennyfarthing_scripts/sprint/yaml_io.py +124 -15
  241. package/pennyfarthing_scripts/tests/__pycache__/test_codemarkers.cpython-314-pytest-9.0.2.pyc +0 -0
  242. package/pennyfarthing_scripts/tests/__pycache__/test_healthscore.cpython-314-pytest-9.0.2.pyc +0 -0
  243. package/pennyfarthing_scripts/tests/__pycache__/test_sprint_package.cpython-314-pytest-9.0.2.pyc +0 -0
  244. package/pennyfarthing_scripts/tests/__pycache__/test_sprint_validator.cpython-314-pytest-9.0.2.pyc +0 -0
  245. package/pennyfarthing_scripts/tests/__pycache__/test_story_add.cpython-314-pytest-9.0.2.pyc +0 -0
  246. package/pennyfarthing_scripts/tests/__pycache__/test_story_update.cpython-314-pytest-9.0.2.pyc +0 -0
  247. package/pennyfarthing_scripts/tests/__pycache__/test_validate_cmd.cpython-314-pytest-9.0.2.pyc +0 -0
  248. package/pennyfarthing_scripts/tests/__pycache__/test_yaml_io.cpython-314-pytest-9.0.2.pyc +0 -0
  249. package/pennyfarthing_scripts/tests/test_codemarkers.py +682 -0
  250. package/pennyfarthing_scripts/tests/test_healthscore.py +524 -0
  251. package/pennyfarthing_scripts/tests/test_sprint_package.py +166 -0
  252. package/pennyfarthing_scripts/tests/test_yaml_io.py +117 -0
  253. package/pennyfarthing_scripts/theme/__init__.py +5 -0
  254. package/pennyfarthing_scripts/theme/__main__.py +6 -0
  255. package/pennyfarthing_scripts/theme/__pycache__/__init__.cpython-314.pyc +0 -0
  256. package/pennyfarthing_scripts/theme/__pycache__/cli.cpython-314.pyc +0 -0
  257. package/pennyfarthing_scripts/theme/cli.py +286 -0
  258. package/scripts/README.md +53 -0
  259. package/scripts/postinstall.cjs +34 -0
  260. package/pennyfarthing-dist/agents/workflow-status-check.md +0 -96
  261. package/pennyfarthing-dist/scripts/sprint/archive-story.sh +0 -133
  262. package/pennyfarthing-dist/scripts/sprint/available-stories.sh +0 -91
  263. package/pennyfarthing-dist/scripts/sprint/check-story.sh +0 -158
  264. package/pennyfarthing-dist/scripts/sprint/get-epic-field.sh +0 -52
  265. package/pennyfarthing-dist/scripts/sprint/get-story-field.sh +0 -63
  266. package/pennyfarthing-dist/scripts/sprint/list-future.sh +0 -145
  267. package/pennyfarthing-dist/scripts/sprint/new-sprint.sh +0 -110
  268. package/pennyfarthing-dist/scripts/sprint/promote-epic.sh +0 -148
  269. package/pennyfarthing-dist/scripts/sprint/sprint-common.sh +0 -415
  270. package/pennyfarthing-dist/scripts/sprint/sprint-info.sh +0 -33
  271. package/pennyfarthing-dist/scripts/sprint/sprint-metrics.sh +0 -230
  272. package/pennyfarthing-dist/scripts/sprint/sprint-status.sh +0 -134
  273. package/pennyfarthing-dist/scripts/sprint/validate-sprint-yaml.sh +0 -139
  274. package/pennyfarthing-dist/skills/sprint/scripts/archive-story.sh +0 -101
  275. package/pennyfarthing-dist/skills/sprint/scripts/available-stories.sh +0 -97
  276. package/pennyfarthing-dist/skills/sprint/scripts/check-story.sh +0 -164
  277. package/pennyfarthing-dist/skills/sprint/scripts/create-jira-epic.sh +0 -23
  278. package/pennyfarthing-dist/skills/sprint/scripts/new-sprint.sh +0 -116
  279. package/pennyfarthing-dist/skills/sprint/scripts/promote-epic.sh +0 -164
  280. package/pennyfarthing-dist/skills/sprint/scripts/sprint-info.sh +0 -39
  281. package/pennyfarthing-dist/skills/sprint/scripts/sprint-status.sh +0 -147
  282. package/pennyfarthing-dist/skills/sprint/scripts/sync-epic-jira.sh +0 -23
@@ -0,0 +1,173 @@
1
+ """Sprint epic add command.
2
+
3
+ Adds a new epic to the current sprint, creating a shard file
4
+ and updating the index.
5
+
6
+ Provides:
7
+ - add_epic(sprint_path, epic_id, title, ...) -> dict
8
+ - epic_add_command (Click command for CLI registration)
9
+ """
10
+
11
+ from pathlib import Path
12
+ from typing import Any
13
+
14
+ import click
15
+ from ruamel.yaml.comments import CommentedMap, CommentedSeq
16
+
17
+ from pennyfarthing_scripts.sprint.yaml_io import (
18
+ EPIC_KEY_ORDER,
19
+ _read_yaml_file,
20
+ _write_yaml_file,
21
+ _get_epic_ref,
22
+ )
23
+
24
+
25
+ def add_epic(
26
+ sprint_path: Path,
27
+ epic_id: str,
28
+ title: str,
29
+ *,
30
+ priority: str = "P1",
31
+ status: str = "backlog",
32
+ repos: str = "pennyfarthing",
33
+ jira: str | None = None,
34
+ description: str | None = None,
35
+ ) -> dict[str, Any]:
36
+ """Add a new epic to the current sprint.
37
+
38
+ Creates a shard file for the epic and adds its reference to the index.
39
+
40
+ Args:
41
+ sprint_path: Path to sprint index YAML file
42
+ epic_id: Epic ID (e.g., "epic-85" or "MSSCI-14400")
43
+ title: Epic title
44
+ priority: Priority (default: P1)
45
+ status: Initial status (default: backlog)
46
+ repos: Repository scope (default: pennyfarthing)
47
+ jira: Optional Jira epic key
48
+ description: Optional epic description
49
+
50
+ Returns:
51
+ Dict with success status and epic_id or error
52
+ """
53
+ sprint_dir = sprint_path.parent
54
+
55
+ # Build epic as CommentedMap with canonical key ordering
56
+ epic = CommentedMap()
57
+ fields: dict[str, Any] = {
58
+ "id": epic_id,
59
+ "type": "epic",
60
+ "title": title,
61
+ "priority": priority,
62
+ "status": status,
63
+ "repos": repos,
64
+ "stories": CommentedSeq(),
65
+ }
66
+ if description is not None:
67
+ fields["description"] = description
68
+ if jira is not None:
69
+ fields["jira"] = jira
70
+
71
+ # Insert keys in EPIC_KEY_ORDER, then any extras
72
+ for key in EPIC_KEY_ORDER:
73
+ if key in fields:
74
+ epic[key] = fields[key]
75
+ for key in fields:
76
+ if key not in EPIC_KEY_ORDER:
77
+ epic[key] = fields[key]
78
+
79
+ # Determine the shard reference
80
+ ref = _get_epic_ref(epic)
81
+ shard_file = sprint_dir / f"epic-{ref}.yaml"
82
+
83
+ if shard_file.exists():
84
+ return {
85
+ "success": False,
86
+ "error": f"Epic shard file already exists: {shard_file.name}",
87
+ }
88
+
89
+ # Read current index
90
+ data = _read_yaml_file(sprint_path)
91
+ epics = data.get("epics", [])
92
+
93
+ # Check for duplicate refs
94
+ for existing in epics:
95
+ existing_str = str(existing) if isinstance(existing, str) else str(existing.get("id", ""))
96
+ if existing_str == ref or existing_str == epic_id:
97
+ return {
98
+ "success": False,
99
+ "error": f"Epic '{epic_id}' already exists in the sprint",
100
+ }
101
+
102
+ # Detect format: sharded (string refs) or monolithic (full dicts)
103
+ is_sharded = bool(epics) and isinstance(epics[0], str)
104
+
105
+ if is_sharded or not epics:
106
+ # Sharded format: write shard file and add string ref to index
107
+ _write_yaml_file(shard_file, epic)
108
+
109
+ if not isinstance(epics, CommentedSeq):
110
+ epics = CommentedSeq(epics)
111
+ epics.append(ref)
112
+ data["epics"] = epics
113
+ _write_yaml_file(sprint_path, data)
114
+ else:
115
+ # Monolithic format: add full epic dict to index
116
+ if not isinstance(epics, CommentedSeq):
117
+ epics = CommentedSeq(epics)
118
+ epics.append(epic)
119
+ data["epics"] = epics
120
+ _write_yaml_file(sprint_path, data)
121
+
122
+ return {
123
+ "success": True,
124
+ "epic_id": epic_id,
125
+ "ref": ref,
126
+ "shard_file": shard_file.name if is_sharded or not epics else None,
127
+ }
128
+
129
+
130
+ @click.command("epic-add")
131
+ @click.argument("epic_id", type=str)
132
+ @click.argument("title", type=str)
133
+ @click.option("--priority", type=click.Choice(["P0", "P1", "P2", "P3"]), default="P1")
134
+ @click.option("--status", type=click.Choice(["backlog", "ready", "in_progress"]), default="backlog")
135
+ @click.option("--repos", default="pennyfarthing")
136
+ @click.option("--jira", "jira_id", type=str, default=None, help="Jira epic key (MSSCI-NNNNN)")
137
+ @click.option("--description", "-d", type=str, default=None, help="Epic description")
138
+ @click.option("--sprint-file", type=click.Path(), default=None, help="Path to sprint YAML file")
139
+ def epic_add_command(
140
+ epic_id: str,
141
+ title: str,
142
+ priority: str,
143
+ status: str,
144
+ repos: str,
145
+ jira_id: str | None,
146
+ description: str | None,
147
+ sprint_file: str | None,
148
+ ) -> None:
149
+ """Add a new epic to the current sprint."""
150
+ if sprint_file is None:
151
+ from pennyfarthing_scripts.common.config import get_project_root
152
+ path = get_project_root() / "sprint" / "current-sprint.yaml"
153
+ else:
154
+ path = Path(sprint_file)
155
+
156
+ result = add_epic(
157
+ sprint_path=path,
158
+ epic_id=epic_id,
159
+ title=title,
160
+ priority=priority,
161
+ status=status,
162
+ repos=repos,
163
+ jira=jira_id,
164
+ description=description,
165
+ )
166
+
167
+ if result["success"]:
168
+ msg = f"Added epic {result['epic_id']}: {title}"
169
+ if result.get("shard_file"):
170
+ msg += f" ({result['shard_file']})"
171
+ click.echo(msg)
172
+ else:
173
+ raise click.ClickException(result["error"])
@@ -2,6 +2,7 @@
2
2
  Sprint YAML parsing utilities for Pennyfarthing scripts.
3
3
 
4
4
  Provides access to sprint/current-sprint.yaml data.
5
+ Supports sharded per-epic format (epic-{ref}.yaml shard files).
5
6
  """
6
7
 
7
8
  from pathlib import Path
@@ -10,9 +11,47 @@ from typing import Any
10
11
  from pennyfarthing_scripts.common.config import get_project_root, load_yaml_config
11
12
 
12
13
 
14
+ def _merge_epic_shards(data: dict[str, Any], sprint_dir: Path) -> dict[str, Any]:
15
+ """Merge sharded epic files into the sprint data structure.
16
+
17
+ When the epics list contains strings (shard references like "MSSCI-14298"
18
+ or "epic-40"), load each epic-{ref}.yaml and replace the string with
19
+ the full epic dict.
20
+
21
+ Args:
22
+ data: Sprint data with possible string refs in epics
23
+ sprint_dir: Directory containing the shard files
24
+
25
+ Returns:
26
+ Sprint data with full epic dicts
27
+ """
28
+ epics = data.get("epics", [])
29
+ if not epics or not isinstance(epics[0], str):
30
+ return data
31
+
32
+ merged_epics = []
33
+ for ref in epics:
34
+ if not isinstance(ref, str):
35
+ merged_epics.append(ref)
36
+ continue
37
+
38
+ epic_file = sprint_dir / f"epic-{ref}.yaml"
39
+ if epic_file.exists():
40
+ epic_data = load_yaml_config(epic_file)
41
+ if epic_data is not None:
42
+ merged_epics.append(epic_data)
43
+
44
+ data["epics"] = merged_epics
45
+ return data
46
+
47
+
13
48
  def load_sprint(project_root: Path | None = None) -> dict[str, Any] | None:
14
49
  """Load sprint data from project root.
15
50
 
51
+ Supports both monolithic and sharded epic formats. When epics are
52
+ string references, the corresponding epic-{ref}.yaml files are
53
+ loaded and merged transparently.
54
+
16
55
  Args:
17
56
  project_root: Project root path (defaults to auto-detect)
18
57
 
@@ -20,8 +59,13 @@ def load_sprint(project_root: Path | None = None) -> dict[str, Any] | None:
20
59
  Sprint data as dict, or None if not found
21
60
  """
22
61
  root = project_root or get_project_root()
23
- sprint_path = root / "sprint" / "current-sprint.yaml"
24
- return load_yaml_config(sprint_path)
62
+ sprint_dir = root / "sprint"
63
+ sprint_path = sprint_dir / "current-sprint.yaml"
64
+ data = load_yaml_config(sprint_path)
65
+ if data is None:
66
+ return None
67
+
68
+ return _merge_epic_shards(data, sprint_dir)
25
69
 
26
70
 
27
71
  def find_epic(sprint_data: dict[str, Any], epic_num: str) -> dict[str, Any] | None:
@@ -5,6 +5,7 @@ Story: MSSCI-14256 - Sprint story add command
5
5
  This module provides:
6
6
  - generate_story_id(sprint_data, epic) -> str
7
7
  - add_story(sprint_path, epic_id, title, points, ...) -> dict
8
+ - add_initiative_story(initiative_slug, title, points, ...) -> dict
8
9
  - story_add_command (Click command for CLI registration)
9
10
  """
10
11
 
@@ -144,44 +145,218 @@ def add_story(
144
145
  }
145
146
 
146
147
 
148
+ def _generate_initiative_story_id(init_data: dict[str, Any], slug: str) -> str:
149
+ """Generate the next standalone story ID for an initiative.
150
+
151
+ Uses the pattern {slug-prefix}-{N} where slug-prefix is derived from
152
+ the initiative slug (e.g., "technical-debt" -> "td", "quality-scale" -> "qs").
153
+ Only counts existing stories that share the same prefix.
154
+
155
+ Args:
156
+ init_data: Initiative YAML data
157
+ slug: Initiative slug (e.g., "technical-debt")
158
+
159
+ Returns:
160
+ Next story ID string (e.g., "td-2")
161
+ """
162
+ # Build prefix from initiative slug initials
163
+ parts = slug.split("-")
164
+ prefix = "".join(p[0] for p in parts if p)
165
+
166
+ stories = init_data.get("standalone_stories", [])
167
+ max_seq = 0
168
+ for story in stories:
169
+ story_id = str(story.get("id", ""))
170
+ # Only count stories with matching prefix
171
+ if not story_id.startswith(f"{prefix}-"):
172
+ continue
173
+ suffix = story_id[len(prefix) + 1:]
174
+ try:
175
+ seq = int(suffix)
176
+ if seq > max_seq:
177
+ max_seq = seq
178
+ except ValueError:
179
+ pass
180
+
181
+ return f"{prefix}-{max_seq + 1}"
182
+
183
+
184
+ def add_initiative_story(
185
+ initiative_slug: str,
186
+ title: str,
187
+ points: int,
188
+ *,
189
+ story_type: str | None = None,
190
+ priority: str = "P1",
191
+ workflow: str = "tdd",
192
+ jira: str | None = None,
193
+ repos: str = "pennyfarthing",
194
+ ) -> dict[str, Any]:
195
+ """Add a standalone story to an initiative YAML file.
196
+
197
+ Args:
198
+ initiative_slug: Initiative slug (e.g., "technical-debt")
199
+ title: Story title
200
+ points: Story points
201
+ story_type: Optional story type (feature, bug, chore, refactor)
202
+ priority: Priority (default: P1)
203
+ workflow: Workflow (default: tdd)
204
+ jira: Optional Jira key
205
+ repos: Repos (default: pennyfarthing)
206
+
207
+ Returns:
208
+ Dict with success status and story_id or error
209
+ """
210
+ from pennyfarthing_scripts.common.config import get_project_root
211
+
212
+ root = get_project_root()
213
+ init_path = root / "sprint" / f"initiative-{initiative_slug}.yaml"
214
+
215
+ if not init_path.exists():
216
+ available = [
217
+ f.stem.replace("initiative-", "")
218
+ for f in (root / "sprint").glob("initiative-*.yaml")
219
+ ]
220
+ return {
221
+ "success": False,
222
+ "error": f"Initiative '{initiative_slug}' not found. Available: {', '.join(sorted(available))}",
223
+ }
224
+
225
+ # Use ruamel.yaml to preserve block scalars and formatting
226
+ from ruamel.yaml import YAML as RuamelYAML
227
+
228
+ ryml = RuamelYAML()
229
+ ryml.preserve_quotes = True
230
+ ryml.default_flow_style = False
231
+ ryml.indent(mapping=2, sequence=4, offset=2)
232
+ ryml.width = 4096
233
+
234
+ with open(init_path) as f:
235
+ init_data = ryml.load(f)
236
+
237
+ if not init_data:
238
+ return {"success": False, "error": f"Empty initiative file: {init_path}"}
239
+
240
+ story_id = _generate_initiative_story_id(init_data, initiative_slug)
241
+
242
+ story: dict[str, Any] = {
243
+ "id": story_id,
244
+ "title": title,
245
+ "points": points,
246
+ "priority": priority,
247
+ "status": "backlog",
248
+ "repos": repos,
249
+ "workflow": workflow,
250
+ }
251
+ if jira is not None:
252
+ story["jira"] = jira
253
+ if story_type is not None:
254
+ story["type"] = story_type
255
+
256
+ if "standalone_stories" not in init_data:
257
+ init_data["standalone_stories"] = []
258
+ init_data["standalone_stories"].append(story)
259
+
260
+ # Update total_points
261
+ current_total = init_data.get("total_points", 0) or 0
262
+ init_data["total_points"] = current_total + points
263
+
264
+ with open(init_path, "w") as f:
265
+ ryml.dump(init_data, f)
266
+
267
+ return {
268
+ "success": True,
269
+ "story_id": story_id,
270
+ }
271
+
272
+
147
273
  @click.command("add")
148
- @click.argument("epic_id", type=str)
149
- @click.argument("title", type=str)
150
- @click.argument("points", type=int)
274
+ @click.argument("epic_id", type=str, required=False)
275
+ @click.argument("title", type=str, required=False)
276
+ @click.argument("points", type=int, required=False)
151
277
  @click.option("--type", "story_type", type=click.Choice(["feature", "bug", "chore", "refactor"]), default="feature")
152
278
  @click.option("--priority", type=click.Choice(["P0", "P1", "P2", "P3"]), default="P1")
153
279
  @click.option("--workflow", type=click.Choice(["tdd", "trivial", "bdd"]), default="tdd")
154
280
  @click.option("--jira", "jira_id", type=str, default=None)
155
281
  @click.option("--sprint-file", type=click.Path(), default=None, help="Path to sprint YAML file")
282
+ @click.option("--initiative", type=str, default=None, help="Add as standalone story to initiative (e.g., technical-debt)")
283
+ @click.option("--repos", type=str, default="pennyfarthing", help="Repos (default: pennyfarthing)")
156
284
  def story_add_command(
157
- epic_id: str,
158
- title: str,
159
- points: int,
285
+ epic_id: str | None,
286
+ title: str | None,
287
+ points: int | None,
160
288
  story_type: str,
161
289
  priority: str,
162
290
  workflow: str,
163
291
  jira_id: str | None,
164
292
  sprint_file: str | None,
293
+ initiative: str | None,
294
+ repos: str,
165
295
  ) -> None:
166
- """Add a new story to an epic."""
167
- if sprint_file is None:
168
- from pennyfarthing_scripts.common.config import get_project_root
169
- path = get_project_root() / "sprint" / "current-sprint.yaml"
170
- else:
171
- path = Path(sprint_file)
172
-
173
- result = add_story(
174
- sprint_path=path,
175
- epic_id=epic_id,
176
- title=title,
177
- points=points,
178
- story_type=story_type if story_type != "feature" else None,
179
- priority=priority,
180
- workflow=workflow,
181
- jira=jira_id,
182
- )
183
-
184
- if result["success"]:
185
- click.echo(f"Added story {result['story_id']}: {title} [{points}pts]")
296
+ """Add a new story to an epic or initiative.
297
+
298
+ \b
299
+ Epic mode (default):
300
+ pf sprint story add <EPIC_ID> <TITLE> <POINTS>
301
+
302
+ Initiative mode (--initiative):
303
+ pf sprint story add --initiative <SLUG> <TITLE> <POINTS>
304
+ """
305
+ if initiative:
306
+ # Initiative mode: first positional arg is title, second is points
307
+ # epic_id absorbs the title, title absorbs points (as str)
308
+ if epic_id is None:
309
+ raise click.ClickException("TITLE is required")
310
+ init_title = epic_id
311
+ if title is None:
312
+ raise click.ClickException("POINTS is required")
313
+ try:
314
+ init_points = int(title)
315
+ except ValueError:
316
+ raise click.ClickException(f"POINTS must be an integer, got '{title}'")
317
+
318
+ result = add_initiative_story(
319
+ initiative_slug=initiative,
320
+ title=init_title,
321
+ points=init_points,
322
+ story_type=story_type if story_type != "feature" else None,
323
+ priority=priority,
324
+ workflow=workflow,
325
+ jira=jira_id,
326
+ repos=repos,
327
+ )
328
+
329
+ if result["success"]:
330
+ click.echo(f"Added story {result['story_id']}: {init_title} [{init_points}pts] to initiative {initiative}")
331
+ else:
332
+ raise click.ClickException(result["error"])
186
333
  else:
187
- raise click.ClickException(result["error"])
334
+ # Epic mode: all three positional args required
335
+ if epic_id is None:
336
+ raise click.ClickException("EPIC_ID is required")
337
+ if title is None:
338
+ raise click.ClickException("TITLE is required")
339
+ if points is None:
340
+ raise click.ClickException("POINTS is required")
341
+
342
+ if sprint_file is None:
343
+ from pennyfarthing_scripts.common.config import get_project_root
344
+ path = get_project_root() / "sprint" / "current-sprint.yaml"
345
+ else:
346
+ path = Path(sprint_file)
347
+
348
+ result = add_story(
349
+ sprint_path=path,
350
+ epic_id=epic_id,
351
+ title=title,
352
+ points=points,
353
+ story_type=story_type if story_type != "feature" else None,
354
+ priority=priority,
355
+ workflow=workflow,
356
+ jira=jira_id,
357
+ )
358
+
359
+ if result["success"]:
360
+ click.echo(f"Added story {result['story_id']}: {title} [{points}pts]")
361
+ else:
362
+ raise click.ClickException(result["error"])