@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
@@ -1,110 +0,0 @@
1
- #!/bin/bash
2
- # Initialize a new sprint from template
3
- # Usage: .pennyfarthing/scripts/sprint/new-sprint.sh <sprint-yyww> <jira-id> <start-date> <end-date> "<goal>"
4
- #
5
- # Example: .pennyfarthing/scripts/sprint/new-sprint.sh 2605 277 2026-02-03 2026-02-16 "Polish and stabilization"
6
-
7
- set -euo pipefail
8
-
9
- SPRINT_YYWW="${1:-}"
10
- JIRA_ID="${2:-}"
11
- START_DATE="${3:-}"
12
- END_DATE="${4:-}"
13
- GOAL="${5:-}"
14
-
15
- if [[ -z "$SPRINT_YYWW" || -z "$JIRA_ID" || -z "$START_DATE" || -z "$END_DATE" || -z "$GOAL" ]]; then
16
- echo "Usage: new-sprint.sh <sprint-yyww> <jira-id> <start-date> <end-date> \"<goal>\""
17
- echo ""
18
- echo "Arguments:"
19
- echo " sprint-yyww Sprint identifier in YYWW format (e.g., 2605 for 2026 week 5)"
20
- echo " jira-id Jira sprint ID number (e.g., 277)"
21
- echo " start-date Sprint start date YYYY-MM-DD"
22
- echo " end-date Sprint end date YYYY-MM-DD"
23
- echo " goal Sprint goal (quoted string)"
24
- echo ""
25
- echo "Example:"
26
- echo " new-sprint.sh 2605 277 2026-02-03 2026-02-16 \"Polish and stabilization\""
27
- exit 1
28
- fi
29
-
30
- # Find project root
31
- source "$(dirname "${BASH_SOURCE[0]}")/../lib/find-root.sh"
32
- TEMPLATE_FILE="$PROJECT_ROOT/sprint/sprint-template.yaml"
33
- SPRINT_FILE="$PROJECT_ROOT/sprint/current-sprint.yaml"
34
- ARCHIVE_FILE="$PROJECT_ROOT/sprint/archive/sprint-${SPRINT_YYWW}-completed.yaml"
35
-
36
- # Check template exists
37
- if [[ ! -f "$TEMPLATE_FILE" ]]; then
38
- echo "Error: Template file not found at $TEMPLATE_FILE"
39
- exit 1
40
- fi
41
-
42
- # Warn if current sprint file exists and is active
43
- if [[ -f "$SPRINT_FILE" ]]; then
44
- if ! command -v yq &> /dev/null; then
45
- echo "Warning: yq not installed, cannot check current sprint status"
46
- else
47
- CURRENT_STATUS=$(yq eval '.sprint.status' "$SPRINT_FILE")
48
- if [[ "$CURRENT_STATUS" == "active" ]]; then
49
- echo "Warning: Current sprint is still active!"
50
- echo "Current sprint file will be overwritten."
51
- echo ""
52
- read -p "Continue? [y/N] " -n 1 -r
53
- echo
54
- if [[ ! $REPLY =~ ^[Yy]$ ]]; then
55
- echo "Aborted."
56
- exit 1
57
- fi
58
- fi
59
- fi
60
- fi
61
-
62
- # Create new sprint file
63
- cat > "$SPRINT_FILE" << EOF
64
- sprint:
65
- name: "TO Sprint $SPRINT_YYWW"
66
- jira_sprint_id: $JIRA_ID
67
- jira_sprint_name: "TO Sprint $SPRINT_YYWW"
68
- goal: $GOAL
69
- start_date: $START_DATE
70
- end_date: $END_DATE
71
- status: active
72
-
73
- # Completed stories archived to: sprint/archive/sprint-${SPRINT_YYWW}-completed.yaml
74
-
75
- epics:
76
- # Add epics and stories here
77
- # See sprint/sprint-template.yaml for format reference
78
- EOF
79
-
80
- echo "Created $SPRINT_FILE"
81
-
82
- # Create archive file
83
- cat > "$ARCHIVE_FILE" << EOF
84
- # Sprint TO Sprint $SPRINT_YYWW - Completed Stories
85
- # Jira Sprint ID: $JIRA_ID
86
- # Archived: $(date +%Y-%m-%d)
87
-
88
- sprint:
89
- name: "TO Sprint $SPRINT_YYWW"
90
- jira_sprint_id: $JIRA_ID
91
- jira_sprint_name: "TO Sprint $SPRINT_YYWW"
92
- goal: $GOAL
93
-
94
- completed:
95
- # Completed stories will be appended here by archive-story.sh
96
- EOF
97
-
98
- echo "Created $ARCHIVE_FILE"
99
-
100
- echo ""
101
- echo "New sprint initialized:"
102
- echo " Name: TO Sprint $SPRINT_YYWW"
103
- echo " Jira ID: $JIRA_ID"
104
- echo " Dates: $START_DATE to $END_DATE"
105
- echo " Goal: $GOAL"
106
- echo ""
107
- echo "Next steps:"
108
- echo " 1. Add epics and stories to $SPRINT_FILE"
109
- echo " 2. Use sprint-status.sh to check progress"
110
- echo " 3. Use archive-story.sh to move completed stories"
@@ -1,148 +0,0 @@
1
- #!/bin/bash
2
- # Promote an epic from future.yaml to current-sprint.yaml
3
- # Usage: .pennyfarthing/scripts/sprint/promote-epic.sh <epic-id>
4
- #
5
- # Example: .pennyfarthing/scripts/sprint/promote-epic.sh epic-41
6
- #
7
- # Features:
8
- # - Detects ID collisions and assigns new ID if needed
9
- # - Uses yq for proper YAML array insertion
10
- # - Automatically removes from future.yaml after successful promotion
11
-
12
- set -euo pipefail
13
-
14
- EPIC_ID="${1:-}"
15
-
16
- if [[ -z "$EPIC_ID" ]]; then
17
- echo "Usage: promote-epic.sh <epic-id>"
18
- echo "Example: promote-epic.sh epic-41"
19
- echo ""
20
- echo "Moves an epic and its stories from future.yaml to current-sprint.yaml"
21
- exit 1
22
- fi
23
-
24
- # Find project root
25
- source "$(dirname "${BASH_SOURCE[0]}")/../lib/find-root.sh"
26
-
27
- FUTURE_FILE="$PROJECT_ROOT/sprint/future.yaml"
28
- SPRINT_FILE="$PROJECT_ROOT/sprint/current-sprint.yaml"
29
-
30
- if [[ ! -f "$FUTURE_FILE" ]]; then
31
- echo "Error: Future file not found at $FUTURE_FILE"
32
- exit 1
33
- fi
34
-
35
- if [[ ! -f "$SPRINT_FILE" ]]; then
36
- echo "Error: Sprint file not found at $SPRINT_FILE"
37
- exit 1
38
- fi
39
-
40
- if ! command -v yq &> /dev/null; then
41
- echo "Error: yq is required but not installed"
42
- echo "Install with: brew install yq"
43
- exit 1
44
- fi
45
-
46
- # Find the epic in future.yaml
47
- # Epics are nested under future.initiatives[].epics[]
48
- EPIC_DATA=$(yq -o json ".future.initiatives[].epics[] | select(.id == \"$EPIC_ID\")" "$FUTURE_FILE" 2>/dev/null || echo "")
49
-
50
- if [[ -z "$EPIC_DATA" || "$EPIC_DATA" == "null" ]]; then
51
- echo "Error: Epic $EPIC_ID not found in $FUTURE_FILE"
52
- echo ""
53
- echo "Available epics:"
54
- yq '.future.initiatives[].epics[].id' "$FUTURE_FILE" 2>/dev/null || echo " None found"
55
- exit 1
56
- fi
57
-
58
- # Check for ID collision in current-sprint.yaml
59
- EXISTING_ID=$(yq ".epics[] | select(.id == \"$EPIC_ID\") | .id" "$SPRINT_FILE" 2>/dev/null || echo "")
60
-
61
- NEW_EPIC_ID="$EPIC_ID"
62
- if [[ -n "$EXISTING_ID" && "$EXISTING_ID" != "null" ]]; then
63
- echo "Warning: Epic ID $EPIC_ID already exists in current sprint."
64
-
65
- # Find the highest epic-N ID and increment
66
- MAX_EPIC_NUM=$(yq '.epics[].id' "$SPRINT_FILE" 2>/dev/null | grep -oE 'epic-[0-9]+' | sed 's/epic-//' | sort -n | tail -1 || echo "0")
67
- if [[ -z "$MAX_EPIC_NUM" ]]; then
68
- MAX_EPIC_NUM=0
69
- fi
70
- NEW_EPIC_NUM=$((MAX_EPIC_NUM + 1))
71
- NEW_EPIC_ID="epic-$NEW_EPIC_NUM"
72
-
73
- echo "Assigning new ID: $NEW_EPIC_ID"
74
- echo ""
75
- fi
76
-
77
- # Extract epic fields
78
- EPIC_TITLE=$(echo "$EPIC_DATA" | yq -r '.title // "Unknown"')
79
- EPIC_DESCRIPTION=$(echo "$EPIC_DATA" | yq -r '.description // ""')
80
- EPIC_POINTS=$(echo "$EPIC_DATA" | yq -r '.points // 0')
81
- EPIC_PRIORITY=$(echo "$EPIC_DATA" | yq -r '.priority // "P2"')
82
- EPIC_REPOS=$(echo "$EPIC_DATA" | yq -r '.repos // "pennyfarthing"')
83
- STORY_COUNT=$(echo "$EPIC_DATA" | yq '[.stories[]] | length')
84
-
85
- echo ""
86
- echo "Promoting epic to current sprint:"
87
- echo " Original ID: $EPIC_ID"
88
- if [[ "$NEW_EPIC_ID" != "$EPIC_ID" ]]; then
89
- echo " New ID: $NEW_EPIC_ID"
90
- fi
91
- echo " Title: $EPIC_TITLE"
92
- echo " Points: $EPIC_POINTS"
93
- echo " Priority: $EPIC_PRIORITY"
94
- echo " Stories: $STORY_COUNT"
95
- echo ""
96
-
97
- # Build the new epic object as JSON, then use yq to append it properly
98
- # This ensures valid YAML structure
99
-
100
- # Extract old epic ID prefix for updating story IDs (e.g., "64" from "epic-64")
101
- OLD_ID_NUM=$(echo "$EPIC_ID" | sed 's/epic-//')
102
- NEW_ID_NUM=$(echo "$NEW_EPIC_ID" | sed 's/epic-//')
103
-
104
- # Create a temp file for the new epic
105
- TEMP_EPIC=$(mktemp)
106
- trap "rm -f $TEMP_EPIC" EXIT
107
-
108
- # Transform the epic data: update IDs, add required fields, format for current-sprint.yaml
109
- # Note: Using -o yaml for Go yq (not -y which is Python yq)
110
- echo "$EPIC_DATA" | yq -o yaml "
111
- .id = \"$NEW_EPIC_ID\" |
112
- .type = \"epic\" |
113
- .title = \"Epic: \" + .title |
114
- .status = \"backlog\" |
115
- .stories = [.stories[] |
116
- .id = ((.id | tostring) | sub(\"^${OLD_ID_NUM}-\"; \"${NEW_ID_NUM}-\")) |
117
- .status = \"backlog\" |
118
- .repos = (.repos // \"pennyfarthing\") |
119
- .workflow = (.workflow // \"tdd\") |
120
- .priority = (.priority // \"P2\") |
121
- .acceptance_criteria = (.acceptance_criteria // [])
122
- ]
123
- " > "$TEMP_EPIC"
124
-
125
- echo "Epic to add:"
126
- echo "---"
127
- cat "$TEMP_EPIC"
128
- echo "---"
129
- echo ""
130
-
131
- # Use yq to properly append the epic to the epics array
132
- yq eval -i ".epics += [$(cat "$TEMP_EPIC" | yq -o json)]" "$SPRINT_FILE"
133
-
134
- echo "Successfully added epic to $SPRINT_FILE"
135
-
136
- # Remove from future.yaml
137
- echo ""
138
- echo "Removing from future.yaml..."
139
- yq eval -i "del(.future.initiatives[].epics[] | select(.id == \"$EPIC_ID\"))" "$FUTURE_FILE"
140
- echo "Removed $EPIC_ID from future.yaml"
141
-
142
- echo ""
143
- echo "Promotion complete!"
144
- echo ""
145
- echo "Next steps:"
146
- echo " 1. Review the epic in $SPRINT_FILE"
147
- echo " 2. Create Jira epic: .pennyfarthing/scripts/jira/create-jira-epic.sh $NEW_EPIC_ID"
148
- echo " 3. Start work: /sprint work ${NEW_ID_NUM}-1"
@@ -1,415 +0,0 @@
1
- #!/usr/bin/env zsh
2
- # Sprint Common Functions
3
- # Shared functions for sprint management
4
- #
5
- # Usage: Scripts must set up PROJECT_ROOT before sourcing this file:
6
- # SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd -P)"
7
- # source "$SCRIPT_DIR/../lib/find-root.sh"
8
- # source "$SCRIPT_DIR/sprint-common.sh"
9
-
10
- # Jira project identifier
11
- export JIRA_PROJECT="MSSCI"
12
-
13
- # Require PROJECT_ROOT to be set
14
- if [[ -z "${PROJECT_ROOT:-}" ]]; then
15
- echo "Error: PROJECT_ROOT must be set before sourcing sprint-common.sh" >&2
16
- echo "Source lib/find-root.sh first" >&2
17
- exit 1
18
- fi
19
-
20
- # find_story_file STORY_KEY
21
- # Search for a story in sprint YAML files
22
- # Returns: path to the YAML file containing the story
23
- find_story_file() {
24
- local story_key="$1"
25
-
26
- # Extract epic number from story key (e.g., "11-9" from "11-9-theme-maker")
27
- local epic_num="${story_key%%-*}"
28
-
29
- # Check current sprint first
30
- if [[ -f "$PROJECT_ROOT/sprint/current-sprint.yaml" ]]; then
31
- # Simple check: grep for the story key in the file
32
- if grep -q "id: \"$epic_num-" "$PROJECT_ROOT/sprint/current-sprint.yaml" 2>/dev/null; then
33
- echo "$PROJECT_ROOT/sprint/current-sprint.yaml"
34
- return 0
35
- fi
36
- fi
37
-
38
- # Check archived sprints
39
- local archive_dir="$PROJECT_ROOT/sprint/archive"
40
- if [[ -d "$archive_dir" ]]; then
41
- local found=$(grep -l "id: \"$epic_num-" "$archive_dir"/*.yaml 2>/dev/null | head -1)
42
- if [[ -n "$found" ]]; then
43
- echo "$found"
44
- return 0
45
- fi
46
- fi
47
-
48
- return 1
49
- }
50
-
51
- # get_story_field STORY_KEY FIELD_NAME
52
- # Extract a field value from a story in sprint YAML
53
- # Returns: the field value or "null"
54
- get_story_field() {
55
- local story_key="$1"
56
- local field_name="$2"
57
-
58
- # Extract epic and story numbers from story key
59
- local epic_num="${story_key%%-*}"
60
- local story_rest="${story_key#*-}"
61
- local story_num="${story_rest%%-*}"
62
-
63
- local story_file=$(find_story_file "$story_key")
64
- if [[ -z "$story_file" ]]; then
65
- echo "null"
66
- return 1
67
- fi
68
-
69
- # Use yq to extract the field if available, otherwise fallback to grep
70
- if command -v yq &> /dev/null; then
71
- yq eval ".epics[] | select(.id == \"$epic_num\") | .stories[] | select(.id == \"$story_num\") | .$field_name" "$story_file" 2>/dev/null || echo "null"
72
- else
73
- # Fallback: grep for the pattern and extract value
74
- # This is a simplified approach - full YAML parsing is better
75
- grep -A 50 "id: \"$story_key" "$story_file" 2>/dev/null | grep "$field_name:" | head -1 | sed "s/.*$field_name:[[:space:]]*\(.*\)/\1/" || echo "null"
76
- fi
77
- }
78
-
79
- # extract_jira_key JIRA_URL_OR_KEY
80
- # Extract Jira issue key from URL or return as-is
81
- # Returns: MSSCI-12345 format
82
- extract_jira_key() {
83
- local input="$1"
84
-
85
- # If already in key format, return as-is
86
- if [[ "$input" =~ ^${JIRA_PROJECT}-[0-9]+$ ]]; then
87
- echo "$input"
88
- return 0
89
- fi
90
-
91
- # If it's a URL, extract the key
92
- if [[ "$input" =~ ${JIRA_PROJECT}-[0-9]+ ]]; then
93
- grep -o "${JIRA_PROJECT}-[0-9]\+" <<< "$input"
94
- return 0
95
- fi
96
-
97
- # Return input as-is if no extraction possible
98
- echo "$input"
99
- return 1
100
- }
101
-
102
- # extract_story_id BRANCH_NAME
103
- # Extract story ID from branch name pattern feat/X-Y-*
104
- # Returns: X-Y (e.g., "8-1" from "feat/8-1-merge-detection")
105
- # Returns empty string for non-matching branches
106
- extract_story_id() {
107
- local branch="$1"
108
-
109
- # Match pattern: feat/EPIC-STORY-description or feat/EPIC-STORY
110
- # Uses basic regex compatible with both bash and zsh
111
- if [[ "$branch" =~ ^feat/([0-9]+-[0-9]+) ]]; then
112
- echo "${match[1]:-${BASH_REMATCH[1]}}"
113
- fi
114
- # Returns empty string for non-matching patterns
115
- }
116
-
117
- # update_story_status STORY_ID [NEW_STATUS]
118
- # Update story status in sprint YAML and add completed date
119
- # Uses yq for YAML manipulation
120
- # Arguments:
121
- # STORY_ID - Story identifier (e.g., "8-1")
122
- # NEW_STATUS - Optional status, defaults to "done"
123
- update_story_status() {
124
- local story_id="$1"
125
- local new_status="${2:-done}"
126
- local completed_date
127
- completed_date=$(date +%Y-%m-%d)
128
-
129
- local sprint_file="$PROJECT_ROOT/sprint/current-sprint.yaml"
130
-
131
- if [[ ! -f "$sprint_file" ]]; then
132
- return 1
133
- fi
134
-
135
- # Check if yq is available
136
- if ! command -v yq &>/dev/null; then
137
- echo "Warning: yq not found, cannot update sprint YAML" >&2
138
- return 1
139
- fi
140
-
141
- # Extract epic and story numbers
142
- local epic_num="${story_id%%-*}"
143
- local story_num="${story_id#*-}"
144
-
145
- # Update status and add completed date using yq
146
- yq eval -i "
147
- (.epics[] | select(.id == \"$epic_num\") | .stories[] | select(.id == \"$story_num\")).status = \"$new_status\" |
148
- (.epics[] | select(.id == \"$epic_num\") | .stories[] | select(.id == \"$story_num\")).completed = \"$completed_date\"
149
- " "$sprint_file"
150
-
151
- return $?
152
- }
153
-
154
- # log_reconciliation STORY_ID MESSAGE
155
- # Log reconciliation event to .session/ directory
156
- # Arguments:
157
- # STORY_ID - Story identifier (e.g., "8-1")
158
- # MESSAGE - Optional message, defaults to "Merge detected"
159
- log_reconciliation() {
160
- local story_id="$1"
161
- local message="${2:-Merge detected}"
162
- local timestamp
163
- timestamp=$(date +"%Y-%m-%d %H:%M:%S")
164
-
165
- local session_dir="$PROJECT_ROOT/.session"
166
-
167
- # Ensure session directory exists
168
- mkdir -p "$session_dir"
169
-
170
- local log_file="$session_dir/reconciliation.log"
171
-
172
- echo "[$timestamp] Story $story_id: $message" >> "$log_file"
173
- }
174
-
175
- # detect_drift
176
- # Detect stories that have been merged but not marked as done in YAML or Jira
177
- # Scans git log for recent merges (past 7 days) and compares against YAML/Jira status
178
- # Returns: list of drifted stories in "story_id:yaml_status:jira_status" format
179
- # A story is "drifted" when its branch was merged but YAML or Jira still shows in_progress
180
- detect_drift() {
181
- local drifted=()
182
-
183
- # Get recently merged branches (past 7 days)
184
- # Look for feat/X-Y-* pattern in merge commit messages
185
- local merged_branches
186
- merged_branches=$(git log --merges --oneline --since="7 days ago" develop 2>/dev/null | \
187
- grep -oE 'feat/[0-9]+-[0-9]+[^[:space:]]*' | sort -u)
188
-
189
- for branch in $merged_branches; do
190
- # Use extract_story_id to parse branch name
191
- local story_id
192
- story_id=$(extract_story_id "$branch")
193
-
194
- if [[ -n "$story_id" ]]; then
195
- # Check current status in YAML via get_story_field
196
- local yaml_status
197
- yaml_status=$(get_story_field "$story_id" "status")
198
-
199
- # Get Jira key and check Jira status
200
- local jira_key jira_status
201
- jira_key=$(get_story_field "$story_id" "jira")
202
- jira_status="unknown"
203
-
204
- if [[ -n "$jira_key" && "$jira_key" != "null" ]]; then
205
- # Query Jira for current status
206
- jira_status=$(jira issue view "$jira_key" --raw 2>/dev/null | \
207
- jq -r '.fields.status.name // "unknown"' 2>/dev/null || echo "unknown")
208
- fi
209
-
210
- # Story is drifted if merged but YAML status is not "done" and not "backlog"
211
- # OR if Jira status is not "Done" (case-insensitive check)
212
- local yaml_drifted=false
213
- local jira_drifted=false
214
-
215
- if [[ "$yaml_status" != "done" && "$yaml_status" != "backlog" && "$yaml_status" != "null" ]]; then
216
- yaml_drifted=true
217
- fi
218
-
219
- # Check Jira drift - status should be "Done" for merged stories
220
- if [[ "$jira_status" != "unknown" && "$jira_status" != "Done" && "$jira_status" != "Closed" ]]; then
221
- jira_drifted=true
222
- fi
223
-
224
- # Report if either YAML or Jira is drifted
225
- if [[ "$yaml_drifted" == "true" || "$jira_drifted" == "true" ]]; then
226
- drifted+=("$story_id:$yaml_status:$jira_status")
227
- fi
228
- fi
229
- done
230
-
231
- # Output drifted stories (one per line)
232
- printf '%s\n' "${drifted[@]}" 2>/dev/null || true
233
- }
234
-
235
- # =============================================================================
236
- # Sprint Summary Functions
237
- # =============================================================================
238
-
239
- # get_sprint_file
240
- # Returns path to current sprint YAML file
241
- get_sprint_file() {
242
- echo "$PROJECT_ROOT/sprint/current-sprint.yaml"
243
- }
244
-
245
- # check_yq
246
- # Verify yq is available, return error message if not
247
- # Returns: 0 if yq available, 1 if not
248
- check_yq() {
249
- if ! command -v yq &>/dev/null; then
250
- echo "Error: yq is required but not installed. Install with: brew install yq" >&2
251
- return 1
252
- fi
253
- return 0
254
- }
255
-
256
- # get_sprint_metadata FIELD
257
- # Extract a field from sprint metadata
258
- # Arguments:
259
- # FIELD - Field name (number, name, goal, start_date, end_date, status)
260
- # Returns: field value or empty string
261
- get_sprint_metadata() {
262
- local field="$1"
263
- local sprint_file
264
- sprint_file=$(get_sprint_file)
265
-
266
- if [[ ! -f "$sprint_file" ]]; then
267
- return 1
268
- fi
269
-
270
- check_yq || return 1
271
- yq eval ".sprint.$field // \"\"" "$sprint_file" 2>/dev/null
272
- }
273
-
274
- # get_sprint_summary
275
- # Get one-line sprint summary: "Sprint N: Goal"
276
- # Returns: formatted summary string
277
- get_sprint_summary() {
278
- local sprint_num sprint_goal
279
- sprint_num=$(get_sprint_metadata "number")
280
- sprint_goal=$(get_sprint_metadata "goal")
281
-
282
- if [[ -n "$sprint_num" && "$sprint_num" != "null" ]]; then
283
- echo "Sprint ${sprint_num}: ${sprint_goal}"
284
- fi
285
- }
286
-
287
- # sum_points VALUES
288
- # Sum a list of point values (one per line)
289
- # Arguments:
290
- # VALUES - newline-separated point values from yq
291
- # Returns: integer sum
292
- sum_points() {
293
- local result
294
- result=$(echo "$1" | paste -sd+ - | bc 2>/dev/null)
295
- echo "${result:-0}"
296
- }
297
-
298
- # get_sprint_progress
299
- # Get sprint progress as "completed/total points"
300
- # Returns: formatted progress string
301
- get_sprint_progress() {
302
- local sprint_file
303
- sprint_file=$(get_sprint_file)
304
-
305
- if [[ ! -f "$sprint_file" ]]; then
306
- return 1
307
- fi
308
-
309
- check_yq || return 1
310
-
311
- # Get summary fields if available
312
- local completed total
313
- completed=$(yq '.summary.completed_points // 0' "$sprint_file" 2>/dev/null)
314
- total=$(yq '.summary.total_points // 0' "$sprint_file" 2>/dev/null)
315
-
316
- # If summary not available, calculate from stories
317
- if [[ "$total" == "0" || "$total" == "null" ]]; then
318
- total=$(sum_points "$(yq '.epics[].stories[].points' "$sprint_file" 2>/dev/null)")
319
- fi
320
-
321
- echo "Progress: ${completed:-0}/${total:-0} points"
322
- }
323
-
324
- # get_story_counts
325
- # Get story counts by status
326
- # Returns: "backlog:N in_progress:N done:N" format
327
- get_story_counts() {
328
- local sprint_file
329
- sprint_file=$(get_sprint_file)
330
-
331
- if [[ ! -f "$sprint_file" ]]; then
332
- return 1
333
- fi
334
-
335
- check_yq || return 1
336
-
337
- local backlog in_progress done
338
- backlog=$(yq eval '[.epics[].stories[] | select(.status == "backlog")] | length' "$sprint_file" 2>/dev/null)
339
- in_progress=$(yq eval '[.epics[].stories[] | select(.status == "in_progress")] | length' "$sprint_file" 2>/dev/null)
340
- done=$(yq eval '[.epics[].stories[] | select(.status == "done")] | length' "$sprint_file" 2>/dev/null)
341
-
342
- echo "backlog:${backlog:-0} in_progress:${in_progress:-0} done:${done:-0}"
343
- }
344
-
345
- # get_point_counts
346
- # Get point totals by status
347
- # Returns: "backlog:N in_progress:N done:N total:N" format
348
- get_point_counts() {
349
- local sprint_file
350
- sprint_file=$(get_sprint_file)
351
-
352
- if [[ ! -f "$sprint_file" ]]; then
353
- return 1
354
- fi
355
-
356
- check_yq || return 1
357
-
358
- local backlog in_progress total
359
- total=$(sum_points "$(yq '.epics[].stories[].points' "$sprint_file" 2>/dev/null)")
360
- backlog=$(sum_points "$(yq '.epics[].stories[] | select(.status == "backlog") | .points' "$sprint_file" 2>/dev/null)")
361
- in_progress=$(sum_points "$(yq '.epics[].stories[] | select(.status == "in_progress") | .points' "$sprint_file" 2>/dev/null)")
362
-
363
- echo "backlog:${backlog:-0} in_progress:${in_progress:-0} total:${total:-0}"
364
- }
365
-
366
- # =============================================================================
367
- # Drift Detection Functions
368
- # =============================================================================
369
-
370
- # reconcile_drift STORY_ID
371
- # Auto-reconcile a drifted story by updating YAML status to done and Jira to Done
372
- # Logs the reconciliation event
373
- # Arguments:
374
- # STORY_ID - Story identifier (e.g., "8-1")
375
- reconcile_drift() {
376
- local story_id="$1"
377
-
378
- if [[ -z "$story_id" ]]; then
379
- echo "Error: story_id required" >&2
380
- return 1
381
- fi
382
-
383
- local yaml_updated=false
384
- local jira_updated=false
385
- local messages=()
386
-
387
- # Update YAML status to done
388
- update_story_status "$story_id" "done"
389
- if [[ $? -eq 0 ]]; then
390
- yaml_updated=true
391
- messages+=("YAML status updated to done")
392
- fi
393
-
394
- # Get Jira key and transition to Done
395
- local jira_key
396
- jira_key=$(get_story_field "$story_id" "jira")
397
-
398
- if [[ -n "$jira_key" && "$jira_key" != "null" ]]; then
399
- # Try to transition Jira to Done
400
- if jira issue move "$jira_key" "Done" 2>/dev/null; then
401
- jira_updated=true
402
- messages+=("Jira $jira_key transitioned to Done")
403
- else
404
- messages+=("Jira $jira_key transition failed (may need manual update)")
405
- fi
406
- fi
407
-
408
- # Log the reconciliation event
409
- if [[ "$yaml_updated" == "true" || "$jira_updated" == "true" ]]; then
410
- log_reconciliation "$story_id" "Auto-reconciled: ${messages[*]}"
411
- return 0
412
- else
413
- return 1
414
- fi
415
- }