@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,44 @@
1
+ """
2
+ Data models for health score analysis results.
3
+
4
+ Follows ADR-0008 result pattern — structured dataclasses with success/error fields.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from dataclasses import dataclass, field
10
+
11
+
12
+ # Default weights for each dimension (must sum to 1.0)
13
+ DEFAULT_WEIGHTS: dict[str, float] = {
14
+ "churn": 0.15,
15
+ "todo_density": 0.15,
16
+ "complexity": 0.15,
17
+ "test_gaps": 0.15,
18
+ "dead_code": 0.10,
19
+ "deprecation_debt": 0.10,
20
+ "dependency_freshness": 0.10,
21
+ "agent_context_efficiency": 0.10,
22
+ }
23
+
24
+
25
+ @dataclass
26
+ class DimensionScore:
27
+ """Score for a single health dimension."""
28
+
29
+ name: str
30
+ score: float | None = None # 0-100, None if unavailable
31
+ weight: float = 0.0
32
+ error: str | None = None
33
+
34
+
35
+ @dataclass
36
+ class HealthscoreResult:
37
+ """Composite health score following ADR-0008 pattern."""
38
+
39
+ success: bool
40
+ composite_score: float = 0.0 # 0-100 weighted average
41
+ target_path: str = ""
42
+ dimensions: list[DimensionScore] = field(default_factory=list)
43
+ cached: bool = False
44
+ error: str | None = None
@@ -39,6 +39,26 @@ DEFAULT_EXCLUDES = [
39
39
  "*.map",
40
40
  "package-lock.json",
41
41
  "pnpm-lock.yaml",
42
+ # Dotfiles
43
+ ".*",
44
+ # Images
45
+ "*.png",
46
+ "*.jpg",
47
+ "*.jpeg",
48
+ "*.gif",
49
+ "*.svg",
50
+ "*.ico",
51
+ # Fonts
52
+ "*.woff",
53
+ "*.woff2",
54
+ "*.ttf",
55
+ "*.eot",
56
+ # Generated files
57
+ "*.d.ts",
58
+ "*.snap",
59
+ "*.d.ts.map",
60
+ # CI config
61
+ ".github/*",
42
62
  ]
43
63
 
44
64
  # Regex for identifying bug-fix commits
@@ -422,6 +442,7 @@ async def analyze_all_repos(
422
442
  days: int = 90,
423
443
  excludes: list[str] | None = None,
424
444
  branch: str = "--all",
445
+ skip_types: list[str] | None = None,
425
446
  ) -> MultiRepoHotspotResult:
426
447
  """Analyze all repos found under project root in parallel.
427
448
 
@@ -432,13 +453,14 @@ async def analyze_all_repos(
432
453
  days: Time window in days
433
454
  excludes: Additional file patterns to exclude
434
455
  branch: Branch spec
456
+ skip_types: Repo types to exclude (e.g. ["orchestrator"])
435
457
 
436
458
  Returns:
437
459
  MultiRepoHotspotResult with per-repo results
438
460
  """
439
461
  from pennyfarthing_scripts.common.config import load_yaml_config
440
462
 
441
- repos_yaml = load_yaml_config(project_root / "repos.yaml")
463
+ repos_yaml = load_yaml_config(project_root / ".pennyfarthing" / "repos.yaml")
442
464
 
443
465
  repos: list[tuple[str, Path]] = []
444
466
 
@@ -446,6 +468,11 @@ async def analyze_all_repos(
446
468
  # Extract repos from repos.yaml
447
469
  for repo_name, repo_config in repos_yaml.items():
448
470
  if isinstance(repo_config, dict):
471
+ # Filter by type if skip_types is provided
472
+ if skip_types:
473
+ repo_type = repo_config.get("type")
474
+ if repo_type and repo_type in skip_types:
475
+ continue
449
476
  repo_path = repo_config.get("path", repo_name)
450
477
  else:
451
478
  repo_path = str(repo_config)
@@ -38,15 +38,17 @@ def _common_options(fn):
38
38
  fn = click.option("--output", "output_file", type=click.Path(), help="Write output to file")(fn)
39
39
  fn = click.option("--exclude", multiple=True, help="Additional exclude patterns (repeatable)")(fn)
40
40
  fn = click.option("--branch", default="--all", show_default=True, help="Branch spec for git log")(fn)
41
+ fn = click.option("--skip-type", "skip_type", multiple=True, help="Skip repos by type (repeatable, e.g. --skip-type orchestrator)")(fn)
41
42
  return fn
42
43
 
43
44
 
44
- def _run_analysis(repo: str | None, repo_path: str | None, days: int, exclude: tuple, branch: str):
45
+ def _run_analysis(repo: str | None, repo_path: str | None, days: int, exclude: tuple, branch: str, skip_type: tuple = ()):
45
46
  """Run analysis and return result."""
46
47
  from pennyfarthing_scripts.hotspots.analyze import analyze_all_repos, analyze_repo
47
48
  from pennyfarthing_scripts.common.config import get_project_root
48
49
 
49
50
  excludes = list(exclude) if exclude else None
51
+ skip_types = list(skip_type) if skip_type else None
50
52
 
51
53
  if repo_path:
52
54
  # Standalone analysis of a specific path
@@ -56,7 +58,7 @@ def _run_analysis(repo: str | None, repo_path: str | None, days: int, exclude: t
56
58
  # Single named repo from project
57
59
  project_root = get_project_root()
58
60
  from pennyfarthing_scripts.common.config import load_yaml_config
59
- repos_yaml = load_yaml_config(project_root / "repos.yaml")
61
+ repos_yaml = load_yaml_config(project_root / ".pennyfarthing" / "repos.yaml")
60
62
  if repos_yaml and repo in repos_yaml:
61
63
  cfg = repos_yaml[repo]
62
64
  rpath = cfg.get("path", repo) if isinstance(cfg, dict) else str(cfg)
@@ -75,7 +77,7 @@ def _run_analysis(repo: str | None, repo_path: str | None, days: int, exclude: t
75
77
  # All repos
76
78
  project_root = get_project_root()
77
79
  return asyncio.run(
78
- analyze_all_repos(project_root, days, excludes, branch)
80
+ analyze_all_repos(project_root, days, excludes, branch, skip_types)
79
81
  )
80
82
 
81
83
 
@@ -130,23 +132,23 @@ def _output_result(result, fmt: str, output_file: str | None, top: int, mode: st
130
132
 
131
133
  @hotspots.command()
132
134
  @_common_options
133
- def analyze(repo, repo_path, days, top, fmt, output_file, exclude, branch):
135
+ def analyze(repo, repo_path, days, top, fmt, output_file, exclude, branch, skip_type):
134
136
  """Full hotspot analysis — files and directories."""
135
- result = _run_analysis(repo, repo_path, days, exclude, branch)
137
+ result = _run_analysis(repo, repo_path, days, exclude, branch, skip_type)
136
138
  _output_result(result, fmt, output_file, top, "analyze")
137
139
 
138
140
 
139
141
  @hotspots.command()
140
142
  @_common_options
141
- def files(repo, repo_path, days, top, fmt, output_file, exclude, branch):
143
+ def files(repo, repo_path, days, top, fmt, output_file, exclude, branch, skip_type):
142
144
  """File-level hotspot report."""
143
- result = _run_analysis(repo, repo_path, days, exclude, branch)
145
+ result = _run_analysis(repo, repo_path, days, exclude, branch, skip_type)
144
146
  _output_result(result, fmt, output_file, top, "files")
145
147
 
146
148
 
147
149
  @hotspots.command()
148
150
  @_common_options
149
- def dirs(repo, repo_path, days, top, fmt, output_file, exclude, branch):
151
+ def dirs(repo, repo_path, days, top, fmt, output_file, exclude, branch, skip_type):
150
152
  """Directory-level hotspot report."""
151
- result = _run_analysis(repo, repo_path, days, exclude, branch)
153
+ result = _run_analysis(repo, repo_path, days, exclude, branch, skip_type)
152
154
  _output_result(result, fmt, output_file, top, "dirs")
@@ -43,7 +43,7 @@ class SyncChange:
43
43
  """Represents a single sync change to apply."""
44
44
 
45
45
  key: str
46
- field: Literal["status", "points"]
46
+ field: Literal["status", "points", "assigned_to"]
47
47
  action: Literal["update-yaml", "update-jira"]
48
48
  yaml_value: Any
49
49
  jira_value: Any
@@ -123,10 +123,15 @@ Examples:
123
123
  action="store_true",
124
124
  help="Sync story points",
125
125
  )
126
+ parser.add_argument(
127
+ "--assignee",
128
+ action="store_true",
129
+ help="Sync assignee field (Jira -> YAML only)",
130
+ )
126
131
  parser.add_argument(
127
132
  "--all",
128
133
  action="store_true",
129
- help="Sync all fields (status + points)",
134
+ help="Sync all fields (status + points + assignee)",
130
135
  )
131
136
  parser.add_argument(
132
137
  "--sprint",
@@ -137,10 +142,11 @@ Examples:
137
142
 
138
143
  args = parser.parse_args(argv)
139
144
 
140
- # --all implies both --status and --points
145
+ # --all implies --status, --points, and --assignee
141
146
  if args.all:
142
147
  args.status = True
143
148
  args.points = True
149
+ args.assignee = True
144
150
 
145
151
  return args
146
152
 
@@ -156,6 +162,7 @@ def generate_sync_plan(
156
162
  *,
157
163
  sync_status: bool = False,
158
164
  sync_points: bool = False,
165
+ sync_assignee: bool = False,
159
166
  yaml_wins: bool = False,
160
167
  ) -> SyncPlan:
161
168
  """Generate a sync plan comparing YAML and Jira stories.
@@ -165,6 +172,7 @@ def generate_sync_plan(
165
172
  jira_stories: Stories from Jira [{key, fields: {status: {name}, customfield_10031, ...}}]
166
173
  sync_status: Whether to sync status field
167
174
  sync_points: Whether to sync points field
175
+ sync_assignee: Whether to sync assignee field (always Jira -> YAML)
168
176
  yaml_wins: If True, YAML wins conflicts (default: Jira wins)
169
177
 
170
178
  Returns:
@@ -248,6 +256,26 @@ def generate_sync_plan(
248
256
  )
249
257
  )
250
258
 
259
+ # Check assignee differences (always Jira -> YAML, ignores yaml_wins)
260
+ if sync_assignee:
261
+ yaml_assignee = yaml_story.get("assigned_to")
262
+ jira_assignee_obj = jira_story.get("fields", {}).get("assignee")
263
+ jira_assignee_email = (
264
+ jira_assignee_obj.get("emailAddress") if jira_assignee_obj else None
265
+ )
266
+
267
+ if yaml_assignee != jira_assignee_email:
268
+ plan.changes.append(
269
+ SyncChange(
270
+ key=key,
271
+ field="assigned_to",
272
+ action="update-yaml",
273
+ yaml_value=yaml_assignee,
274
+ jira_value=jira_assignee_email,
275
+ target_value=jira_assignee_email,
276
+ )
277
+ )
278
+
251
279
  return plan
252
280
 
253
281
 
@@ -326,21 +354,17 @@ async def execute_sync_plan(
326
354
 
327
355
  # Execute YAML updates (sequential, file-based)
328
356
  if yaml_updates:
329
- # Load current sprint data
330
357
  from pennyfarthing_scripts.common.config import get_project_root
358
+ from pennyfarthing_scripts.sprint.yaml_io import read_sprint, write_sprint
331
359
 
332
360
  root = get_project_root()
333
361
  sprint_file = sprint_path or (root / "sprint" / "current-sprint.yaml")
334
362
 
335
363
  if sprint_file.exists():
336
- import yaml
337
-
338
- with open(sprint_file) as f:
339
- sprint_data = yaml.safe_load(f)
364
+ sprint_data = read_sprint(sprint_file)
340
365
 
341
366
  # Apply YAML updates
342
367
  for change in yaml_updates:
343
- # Find and update the story in sprint data
344
368
  updated = _update_story_in_sprint(
345
369
  sprint_data, change.key, change.field, change.target_value
346
370
  )
@@ -348,10 +372,9 @@ async def execute_sync_plan(
348
372
  result.changes_applied += 1
349
373
  result.yaml_modified = True
350
374
 
351
- # Write back if modified
375
+ # Write back if modified (handles shards automatically)
352
376
  if result.yaml_modified:
353
- with open(sprint_file, "w") as f:
354
- yaml.dump(sprint_data, f, default_flow_style=False, sort_keys=False)
377
+ write_sprint(sprint_file, sprint_data)
355
378
 
356
379
  return result
357
380
 
@@ -379,7 +402,10 @@ def _update_story_in_sprint(
379
402
  for epic in sprint_data.get("epics", []):
380
403
  for story in epic.get("stories", []):
381
404
  if story.get("jira") == jira_key:
382
- story[field] = value
405
+ if value is None and field in story:
406
+ del story[field]
407
+ elif value is not None:
408
+ story[field] = value
383
409
  return True
384
410
 
385
411
  return False
@@ -465,8 +491,8 @@ async def async_main(args: argparse.Namespace) -> int:
465
491
  Exit code (0 for success, 1 for error)
466
492
  """
467
493
  # Validate at least one field is selected
468
- if not args.status and not args.points:
469
- error("Must specify at least one field to sync: --status, --points, or --all")
494
+ if not args.status and not args.points and not args.assignee:
495
+ error("Must specify at least one field to sync: --status, --points, --assignee, or --all")
470
496
  return 1
471
497
 
472
498
  # Load sprint data
@@ -510,6 +536,7 @@ async def async_main(args: argparse.Namespace) -> int:
510
536
  jira_stories,
511
537
  sync_status=args.status,
512
538
  sync_points=args.points,
539
+ sync_assignee=args.assignee,
513
540
  yaml_wins=args.yaml_wins,
514
541
  )
515
542
 
@@ -175,6 +175,80 @@ def create_story(epic_jira_key, story_id, dry_run):
175
175
  raise SystemExit(1)
176
176
 
177
177
 
178
+ @create.command("standalone")
179
+ @click.argument("title")
180
+ @click.option("--points", default=2, type=int, help="Story points (default: 2)")
181
+ @click.option("--description", "-d", default="", help="Story description")
182
+ @click.option("--dry-run", is_flag=True, help="Preview without creating")
183
+ def create_standalone(title, points, description, dry_run):
184
+ """Create a standalone Jira story, add to sprint, mark Done.
185
+
186
+ Uses REST API directly — no interactive prompts, no stdin issues.
187
+
188
+ \b
189
+ Arguments:
190
+ TITLE - Story summary
191
+
192
+ \b
193
+ Examples:
194
+ pf jira create standalone "Fix sprint script shard support" --points 3
195
+ pf jira create standalone "Add drift detection" -d "Detects YAML drift"
196
+ pf jira create standalone "Quick fix" --dry-run
197
+ """
198
+ from pennyfarthing_scripts.jira.client import JIRA_PROJECT, get_client
199
+ from pennyfarthing_scripts.jira.create import _build_adf_description
200
+ from pennyfarthing_scripts.sprint.loader import get_sprint_info
201
+
202
+ sprint_info = get_sprint_info()
203
+ sprint_id = sprint_info.get("jira_sprint_id")
204
+
205
+ if dry_run:
206
+ click.echo(f"[DRY-RUN] Would create: {title}")
207
+ click.echo(f" Points: {points}")
208
+ click.echo(f" Sprint: {sprint_id}")
209
+ click.echo(f" Actions: create -> add to sprint -> transition to Done")
210
+ return
211
+
212
+ client = get_client()
213
+
214
+ # 1. Create the story
215
+ payload = {
216
+ "fields": {
217
+ "project": {"key": JIRA_PROJECT},
218
+ "summary": title,
219
+ "description": _build_adf_description(description),
220
+ "issuetype": {"name": "Story"},
221
+ "labels": ["pennyfarthing"],
222
+ }
223
+ }
224
+
225
+ response = client.create_issue_sync(payload)
226
+ if not response or "key" not in response:
227
+ raise click.ClickException(f"Failed to create story: {response}")
228
+
229
+ jira_key = response["key"]
230
+ click.echo(f"Created: {jira_key}")
231
+
232
+ # 2. Set story points
233
+ if points > 0:
234
+ client.update_issue_sync(jira_key, {"customfield_10031": points})
235
+
236
+ # 3. Add to sprint
237
+ if sprint_id:
238
+ client.add_to_sprint_sync(sprint_id, jira_key)
239
+ click.echo(f"Added to sprint {sprint_id}")
240
+
241
+ # 4. Transition to Done
242
+ result = client.transition_sync(jira_key, "Done")
243
+ if result.get("success"):
244
+ click.echo(f"Transitioned to Done")
245
+ else:
246
+ click.echo(f"Warning: could not transition to Done: {result.get('reason')}")
247
+
248
+ click.echo(f"\n{jira_key}: {title}")
249
+ click.echo(f"https://1898andco.atlassian.net/browse/{jira_key}")
250
+
251
+
178
252
  @jira.command()
179
253
  @click.argument("epic")
180
254
  @click.option("--dry-run", is_flag=True, help="Preview without applying")
@@ -200,9 +274,10 @@ def sync(epic, dry_run, transition, points, sync_all):
200
274
  @click.option("--yaml-wins", is_flag=True, help="Prefer YAML values on conflict")
201
275
  @click.option("--status", is_flag=True, help="Sync status field")
202
276
  @click.option("--points", is_flag=True, help="Sync story points")
277
+ @click.option("--assignee", is_flag=True, help="Sync assignee field (Jira -> YAML only)")
203
278
  @click.option("--all", "sync_all", is_flag=True, help="Sync all fields")
204
279
  @click.option("--sprint", "sprint_id", help="Target specific sprint")
205
- def bidirectional(dry_run, yaml_wins, status, points, sync_all, sprint_id):
280
+ def bidirectional(dry_run, yaml_wins, status, points, assignee, sync_all, sprint_id):
206
281
  """Bidirectional sync between YAML and Jira."""
207
282
  from pennyfarthing_scripts.jira.bidirectional import main as bidirectional_main
208
283
 
@@ -215,6 +290,8 @@ def bidirectional(dry_run, yaml_wins, status, points, sync_all, sprint_id):
215
290
  args.append("--status")
216
291
  if points or sync_all:
217
292
  args.append("--points")
293
+ if assignee or sync_all:
294
+ args.append("--assignee")
218
295
  if sync_all:
219
296
  args.append("--all")
220
297
  if sprint_id:
@@ -301,6 +301,34 @@ def map_github_to_jira(github_user: str | None) -> str | None:
301
301
  return GITHUB_TO_JIRA_MAP.get(github_user, f"{github_user}@1898andco.io")
302
302
 
303
303
 
304
+ def get_current_user_email() -> str:
305
+ """Get the current user's Jira email address.
306
+
307
+ Resolution order:
308
+ 1. JIRA_USER environment variable
309
+ 2. git config user.email
310
+ 3. Default fallback
311
+
312
+ Returns:
313
+ Email address string
314
+ """
315
+ jira_user = os.environ.get("JIRA_USER")
316
+ if jira_user:
317
+ return jira_user
318
+
319
+ try:
320
+ result = subprocess.run(
321
+ ["git", "config", "user.email"],
322
+ capture_output=True, text=True, timeout=5,
323
+ )
324
+ if result.returncode == 0 and result.stdout.strip():
325
+ return result.stdout.strip()
326
+ except Exception:
327
+ pass
328
+
329
+ return "keith.avery@1898andco.io"
330
+
331
+
304
332
  # =============================================================================
305
333
  # JiraClient - Unified REST API client
306
334
  # =============================================================================
@@ -154,13 +154,13 @@ def get_phase_owner(workflow: str, phase: str, project_root: Path) -> str | None
154
154
 
155
155
 
156
156
  def get_backlog_count(project_root: Path) -> int:
157
- """Count stories in backlog or ready status.
157
+ """Count stories in backlog, ready, or planning status.
158
158
 
159
159
  Args:
160
160
  project_root: Project root path
161
161
 
162
162
  Returns:
163
- Number of stories with status backlog or ready
163
+ Number of stories available for work
164
164
  """
165
165
  # Import here to avoid circular imports
166
166
  from pennyfarthing_scripts.sprint.loader import load_sprint
@@ -171,9 +171,11 @@ def get_backlog_count(project_root: Path) -> int:
171
171
 
172
172
  count = 0
173
173
  for epic in sprint["epics"]:
174
+ if not isinstance(epic, dict):
175
+ continue # Skip string refs (defensive)
174
176
  for story in epic.get("stories", []):
175
177
  status = story.get("status", "").lower()
176
- if status in ("backlog", "ready"):
178
+ if status in ("backlog", "ready", "planning"):
177
179
  count += 1
178
180
 
179
181
  return count
@@ -18,7 +18,7 @@ def archive_story(
18
18
  dry_run: bool = False,
19
19
  apply: bool = False,
20
20
  ) -> dict[str, Any]:
21
- """Archive a completed story.
21
+ """Archive a completed story to the sprint archive file.
22
22
 
23
23
  Args:
24
24
  story_id: Story ID to archive
@@ -29,6 +29,11 @@ def archive_story(
29
29
  Returns:
30
30
  Dict with success status and details
31
31
  """
32
+ import re
33
+ from datetime import date
34
+
35
+ import yaml
36
+
32
37
  # Find the story
33
38
  story = get_story_by_id(story_id)
34
39
  if not story:
@@ -45,23 +50,75 @@ def archive_story(
45
50
  "error": f"Story status is '{status}', expected 'done' or 'completed'",
46
51
  }
47
52
 
53
+ root = get_project_root()
54
+ sprint_file = root / "sprint" / "current-sprint.yaml"
55
+
56
+ if not sprint_file.exists():
57
+ return {"success": False, "error": f"Sprint file not found: {sprint_file}"}
58
+
59
+ # Get sprint name for archive file
60
+ with open(sprint_file) as f:
61
+ sprint_data = yaml.safe_load(f.read())
62
+
63
+ sprint_name = sprint_data.get("sprint", {}).get("jira_sprint_name", "")
64
+ match = re.search(r"(\d{4})", sprint_name)
65
+ sprint_num = match.group(1) if match else "unknown"
66
+ archive_file = root / "sprint" / "archive" / f"sprint-{sprint_num}-completed.yaml"
67
+
68
+ # Find parent epic
69
+ epic_id = ""
70
+ for epic in sprint_data.get("epics", []):
71
+ if isinstance(epic, dict):
72
+ for s in epic.get("stories", []):
73
+ if s.get("id") == story_id:
74
+ epic_id = str(epic.get("id", ""))
75
+ break
76
+
77
+ completed_date = str(date.today())
78
+
48
79
  if dry_run:
49
80
  return {
50
81
  "success": True,
51
82
  "dry_run": True,
52
83
  "story": story,
53
84
  "pr_number": pr_number,
54
- "message": f"Would archive {story_id}",
85
+ "message": f"Would archive {story_id} to {archive_file}",
55
86
  }
56
87
 
57
- # In a real implementation, this would:
58
- # 1. Write to archive file
59
- # 2. Optionally remove from current-sprint.yaml
88
+ # Append to archive file
89
+ if not archive_file.exists():
90
+ return {"success": False, "error": f"Archive file not found: {archive_file}"}
91
+
92
+ entry_lines = [
93
+ f" - id: {story_id}",
94
+ f" epic: {epic_id}",
95
+ f' title: "{story.get("title", "Unknown")}"',
96
+ f" points: {story.get('points', 0)}",
97
+ f" completed: {completed_date}",
98
+ ]
99
+ if pr_number:
100
+ entry_lines.append(f" pr: {pr_number}")
101
+
102
+ with open(archive_file, "a") as f:
103
+ f.write("\n".join(entry_lines) + "\n")
104
+
105
+ msg = f"Archived {story_id} to {archive_file.name}"
106
+
107
+ # Remove from current sprint if --apply
108
+ if apply:
109
+ for epic in sprint_data.get("epics", []):
110
+ if isinstance(epic, dict):
111
+ epic["stories"] = [s for s in epic.get("stories", []) if s.get("id") != story_id]
112
+
113
+ from pennyfarthing_scripts.sprint.yaml_io import write_sprint
114
+ write_sprint(sprint_file, sprint_data)
115
+ msg += f" and removed from {sprint_file.name}"
116
+
60
117
  return {
61
118
  "success": True,
62
119
  "story": story,
63
120
  "pr_number": pr_number,
64
- "message": f"Archived {story_id}",
121
+ "message": msg,
65
122
  }
66
123
 
67
124