@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
@@ -696,3 +696,120 @@ class TestReadSprint:
696
696
  assert isinstance(data["epics"], list)
697
697
  assert isinstance(data["epics"][0]["stories"], list)
698
698
  assert isinstance(data["epics"][0]["stories"][0]["points"], int)
699
+
700
+
701
+ # =============================================================================
702
+ # Sharded format support
703
+ # =============================================================================
704
+
705
+
706
+ SHARDED_INDEX_YAML = """\
707
+ sprint:
708
+ name: "TO Sprint 2606"
709
+ jira_sprint_id: 309
710
+ jira_sprint_name: "TO Sprint 2606"
711
+ goal: Test sharding
712
+ start_date: 2026-02-02
713
+ end_date: 2026-02-15
714
+ status: active
715
+ epics:
716
+ - MSSCI-14298
717
+ - epic-40
718
+ stories: []
719
+ """
720
+
721
+ SHARD_JIRA_YAML = """\
722
+ id: MSSCI-14298
723
+ type: epic
724
+ title: "Epic: Stepped Workflow"
725
+ priority: P1
726
+ status: in_progress
727
+ jira: MSSCI-14298
728
+ stories:
729
+ - id: MSSCI-14299
730
+ title: Wire up stepped workflow
731
+ points: 5
732
+ priority: P0
733
+ status: done
734
+ """
735
+
736
+ SHARD_INTERNAL_YAML = """\
737
+ id: epic-40
738
+ type: epic
739
+ title: "Epic: Scale Adaptation"
740
+ priority: P2
741
+ status: backlog
742
+ stories:
743
+ - id: 40-1
744
+ title: First story
745
+ points: 3
746
+ priority: P1
747
+ status: backlog
748
+ """
749
+
750
+
751
+ @pytest.fixture
752
+ def sharded_sprint_dir(tmp_path: Path) -> Path:
753
+ """Create a sharded sprint directory structure."""
754
+ (tmp_path / "current-sprint.yaml").write_text(SHARDED_INDEX_YAML)
755
+ (tmp_path / "epic-MSSCI-14298.yaml").write_text(SHARD_JIRA_YAML)
756
+ (tmp_path / "epic-epic-40.yaml").write_text(SHARD_INTERNAL_YAML)
757
+ return tmp_path
758
+
759
+
760
+ class TestShardedReadWrite:
761
+ """Tests for sharded epic format in yaml_io."""
762
+
763
+ def test_read_merges_shards(self, sharded_sprint_dir: Path) -> None:
764
+ """read_sprint should merge shard files into full epics."""
765
+ data = read_sprint(sharded_sprint_dir / "current-sprint.yaml")
766
+
767
+ assert len(data["epics"]) == 2
768
+ assert data["epics"][0]["id"] == "MSSCI-14298"
769
+ assert data["epics"][1]["id"] == "epic-40"
770
+ assert len(data["epics"][0]["stories"]) == 1
771
+ assert len(data["epics"][1]["stories"]) == 1
772
+
773
+ def test_write_preserves_sharded_format(self, sharded_sprint_dir: Path) -> None:
774
+ """write_sprint should write back to shard files when format is sharded."""
775
+ index_path = sharded_sprint_dir / "current-sprint.yaml"
776
+ data = read_sprint(index_path)
777
+
778
+ # Mutate a story
779
+ data["epics"][1]["stories"][0]["status"] = "in_progress"
780
+
781
+ write_sprint(index_path, data)
782
+
783
+ # Index should still have string refs
784
+ import yaml
785
+ with open(index_path) as f:
786
+ raw_index = yaml.safe_load(f)
787
+ assert isinstance(raw_index["epics"][0], str)
788
+ assert raw_index["epics"][0] == "MSSCI-14298"
789
+
790
+ # Shard file should have the updated story
791
+ shard = read_sprint(sharded_sprint_dir / "epic-epic-40.yaml")
792
+ assert shard["stories"][0]["status"] == "in_progress"
793
+
794
+ def test_sharded_round_trip(self, sharded_sprint_dir: Path) -> None:
795
+ """Read-write-read on sharded format should be stable."""
796
+ index_path = sharded_sprint_dir / "current-sprint.yaml"
797
+
798
+ data1 = read_sprint(index_path)
799
+ write_sprint(index_path, data1)
800
+ data2 = read_sprint(index_path)
801
+
802
+ assert canonical_dump(data1) == canonical_dump(data2)
803
+
804
+ def test_non_sharded_write_unchanged(self, tmp_path: Path, full_sprint_file: Path) -> None:
805
+ """write_sprint on non-sharded data should write a single file."""
806
+ data = read_sprint(full_sprint_file)
807
+ out_path = tmp_path / "output.yaml"
808
+
809
+ write_sprint(out_path, data)
810
+
811
+ # Should be a single file, no shard files created
812
+ import yaml
813
+ with open(out_path) as f:
814
+ raw = yaml.safe_load(f)
815
+ assert isinstance(raw["epics"][0], dict) # Full dicts, not refs
@@ -0,0 +1,5 @@
1
+ """
2
+ Theme management for Pennyfarthing persona themes.
3
+
4
+ Provides CLI commands for listing, showing, setting, and creating themes.
5
+ """
@@ -0,0 +1,6 @@
1
+ """Allow running as: python -m pennyfarthing_scripts.theme"""
2
+
3
+ from pennyfarthing_scripts.theme.cli import theme
4
+
5
+ if __name__ == "__main__":
6
+ theme()
@@ -0,0 +1,286 @@
1
+ """
2
+ CLI commands for theme management.
3
+
4
+ Usage:
5
+ pf theme list
6
+ pf theme show [NAME] [--full]
7
+ pf theme set <NAME>
8
+ pf theme create <NAME> [--base <THEME>] [--user]
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import re
14
+
15
+ import click
16
+
17
+
18
+ @click.group()
19
+ def theme():
20
+ """Persona theme management.
21
+
22
+ \b
23
+ Commands:
24
+ list - Show all available themes
25
+ show - Show theme details
26
+ set - Set the active theme
27
+ create - Create a new custom theme
28
+ """
29
+ pass
30
+
31
+
32
+ @theme.command("list")
33
+ def list_cmd():
34
+ """Show all available themes with current theme highlighted."""
35
+ from pennyfarthing_scripts.common.themes import format_theme_list
36
+
37
+ click.echo(format_theme_list())
38
+
39
+
40
+ @theme.command("show")
41
+ @click.argument("name", required=False)
42
+ @click.option("--full", is_flag=True, help="Show full agent details (OCEAN, quirks, catchphrases, etc.)")
43
+ def show(name: str | None, full: bool):
44
+ """Show theme details including agent character mappings.
45
+
46
+ \b
47
+ Arguments:
48
+ NAME - Theme to show (defaults to current theme)
49
+ """
50
+ import yaml
51
+
52
+ from pennyfarthing_scripts.common.themes import (
53
+ get_current_theme,
54
+ list_themes,
55
+ resolve_theme_path,
56
+ )
57
+
58
+ theme_name = name
59
+ if not theme_name:
60
+ theme_name = get_current_theme()
61
+ if not theme_name:
62
+ click.echo("No theme currently set.")
63
+ click.echo("Use 'pf theme set <name>' to select a theme.")
64
+ return
65
+
66
+ theme_path = resolve_theme_path(theme_name)
67
+ if not theme_path:
68
+ available = ", ".join(list_themes()[:10])
69
+ raise click.ClickException(f"Theme '{theme_name}' not found.\nAvailable: {available}...")
70
+
71
+ data = yaml.safe_load(theme_path.read_text())
72
+ if not data:
73
+ raise click.ClickException(f"Invalid theme file: {theme_path}")
74
+
75
+ theme_meta = data.get("theme", {})
76
+ agents = data.get("agents", {})
77
+
78
+ # Header
79
+ click.echo(f"Theme: {theme_name}")
80
+ if theme_meta.get("description"):
81
+ click.echo(f"Description: {theme_meta['description']}")
82
+ if theme_meta.get("tier"):
83
+ click.echo(f"Tier: {theme_meta['tier']}")
84
+ click.echo()
85
+
86
+ # Agents
87
+ click.echo("Agents:")
88
+ agent_order = [
89
+ "sm", "tea", "dev", "reviewer", "orchestrator",
90
+ "pm", "architect", "devops", "tech-writer", "ux-designer",
91
+ ]
92
+
93
+ displayed: set[str] = set()
94
+ for agent_name in agent_order:
95
+ agent = agents.get(agent_name)
96
+ if agent and agent.get("character"):
97
+ _display_agent(agent_name, agent, full)
98
+ displayed.add(agent_name)
99
+
100
+ for agent_name, agent in agents.items():
101
+ if agent_name not in displayed and isinstance(agent, dict) and agent.get("character"):
102
+ _display_agent(agent_name, agent, full)
103
+
104
+
105
+ def _display_agent(name: str, agent: dict, full: bool) -> None:
106
+ """Display a single agent's info."""
107
+ click.echo(f" {name}:")
108
+ click.echo(f" Character: {agent['character']}")
109
+ if agent.get("style"):
110
+ click.echo(f" Style: {agent['style']}")
111
+
112
+ if not full:
113
+ return
114
+
115
+ if agent.get("ocean"):
116
+ ocean = agent["ocean"]
117
+ click.echo(
118
+ f" OCEAN: O={ocean.get('O', '?')} C={ocean.get('C', '?')} "
119
+ f"E={ocean.get('E', '?')} A={ocean.get('A', '?')} N={ocean.get('N', '?')}"
120
+ )
121
+ if agent.get("trait"):
122
+ click.echo(f" Trait: {agent['trait']}")
123
+ if agent.get("expertise"):
124
+ click.echo(f" Expertise: {agent['expertise']}")
125
+ if agent.get("role"):
126
+ click.echo(f" Role: {agent['role']}")
127
+ if agent.get("quirks"):
128
+ click.echo(" Quirks:")
129
+ for q in agent["quirks"]:
130
+ click.echo(f" - {q}")
131
+ if agent.get("catchphrases"):
132
+ click.echo(" Catchphrases:")
133
+ for c in agent["catchphrases"]:
134
+ click.echo(f" - {c}")
135
+ if agent.get("emoji"):
136
+ click.echo(f" Emoji: {agent['emoji']}")
137
+ if agent.get("helper"):
138
+ helper = agent["helper"]
139
+ hname = helper.get("name", "?")
140
+ hstyle = helper.get("style", "?")
141
+ click.echo(f" Helper: {hname} ({hstyle})")
142
+
143
+
144
+ @theme.command("set")
145
+ @click.argument("name")
146
+ def set_theme(name: str):
147
+ """Set the active persona theme.
148
+
149
+ \b
150
+ Arguments:
151
+ NAME - Theme name to activate
152
+ """
153
+ import yaml
154
+
155
+ from pennyfarthing_scripts.common.config import get_project_root
156
+ from pennyfarthing_scripts.common.themes import (
157
+ list_themes,
158
+ resolve_theme_path,
159
+ )
160
+
161
+ theme_path = resolve_theme_path(name)
162
+ if not theme_path:
163
+ available = ", ".join(list_themes()[:10])
164
+ raise click.ClickException(f"Theme '{name}' not found.\nAvailable: {available}...")
165
+
166
+ theme_data = yaml.safe_load(theme_path.read_text())
167
+ theme_meta = theme_data.get("theme", {})
168
+ agents = theme_data.get("agents", {})
169
+
170
+ # Build theme_characters map (matches Node.js setTheme behavior)
171
+ theme_characters: dict[str, str] = {}
172
+ for agent_id, agent_info in agents.items():
173
+ if isinstance(agent_info, dict) and agent_info.get("character"):
174
+ theme_characters[agent_id] = agent_info["character"]
175
+
176
+ # Read existing config, preserve other fields
177
+ root = get_project_root()
178
+ config_path = root / ".pennyfarthing" / "config.local.yaml"
179
+
180
+ config: dict = {}
181
+ if config_path.exists():
182
+ try:
183
+ existing = yaml.safe_load(config_path.read_text())
184
+ if existing and isinstance(existing, dict):
185
+ config = existing
186
+ except Exception:
187
+ pass
188
+
189
+ config["theme"] = name
190
+ config["theme_characters"] = theme_characters
191
+
192
+ config_path.parent.mkdir(parents=True, exist_ok=True)
193
+ header = (
194
+ "# Pennyfarthing Local Configuration\n"
195
+ "# This file is gitignored - your personal preferences\n\n"
196
+ )
197
+ config_path.write_text(
198
+ header + yaml.dump(config, default_flow_style=False, sort_keys=False)
199
+ )
200
+
201
+ click.echo(f"Theme changed to '{name}'.")
202
+ click.echo()
203
+ click.echo(f" {theme_meta.get('name', name)}")
204
+
205
+ samples = []
206
+ for key in ["sm", "tea", "dev"]:
207
+ agent = agents.get(key)
208
+ if agent and agent.get("character"):
209
+ samples.append(f"{key.upper()}: {agent['character']}")
210
+ if samples:
211
+ click.echo(f" {' | '.join(samples)}")
212
+
213
+ click.echo()
214
+ click.echo("Start a new agent session to use the new theme.")
215
+
216
+
217
+ @theme.command("create")
218
+ @click.argument("name")
219
+ @click.option("--base", default=None, help="Base theme to copy from (defaults to current theme)")
220
+ @click.option("--user", is_flag=True, help="Create as user-level theme (~/.claude/pennyfarthing/themes/)")
221
+ def create(name: str, base: str | None, user: bool):
222
+ """Create a new custom theme from a base theme.
223
+
224
+ \b
225
+ Arguments:
226
+ NAME - Name for the new theme (lowercase, hyphens allowed)
227
+ """
228
+ import yaml
229
+ from pathlib import Path
230
+
231
+ from pennyfarthing_scripts.common.config import get_project_root
232
+ from pennyfarthing_scripts.common.themes import (
233
+ get_current_theme,
234
+ list_themes,
235
+ resolve_theme_path,
236
+ )
237
+
238
+ if not base:
239
+ base = get_current_theme() or "blade-runner"
240
+
241
+ if name != name.lower():
242
+ raise click.ClickException("Theme name must be lowercase")
243
+ if " " in name:
244
+ raise click.ClickException("Theme name cannot contain spaces (use hyphens)")
245
+ if not re.match(r"^[a-z][a-z0-9-]*$", name):
246
+ raise click.ClickException(
247
+ "Theme name must start with a letter and contain only lowercase letters, numbers, and hyphens"
248
+ )
249
+
250
+ if name in list_themes():
251
+ raise click.ClickException(f"Theme '{name}' already exists")
252
+
253
+ base_path = resolve_theme_path(base)
254
+ if not base_path:
255
+ available = ", ".join(list_themes()[:10])
256
+ raise click.ClickException(f"Base theme '{base}' not found.\nAvailable: {available}...")
257
+
258
+ if user:
259
+ target_dir = Path.home() / ".claude" / "pennyfarthing" / "themes"
260
+ else:
261
+ root = get_project_root()
262
+ target_dir = root / ".claude" / "pennyfarthing" / "themes"
263
+
264
+ target_dir.mkdir(parents=True, exist_ok=True)
265
+ target_path = target_dir / f"{name}.yaml"
266
+
267
+ base_data = yaml.safe_load(base_path.read_text())
268
+ base_data["theme"]["name"] = name.replace("-", " ").title()
269
+ base_data["theme"]["description"] = f"Custom theme based on {base}"
270
+
271
+ file_header = (
272
+ f"# Custom Theme: {name}\n"
273
+ f"# Based on: {base}\n"
274
+ "# Edit this file to customize your agent personas\n\n"
275
+ )
276
+ target_path.write_text(
277
+ file_header + yaml.dump(base_data, default_flow_style=False, sort_keys=False)
278
+ )
279
+
280
+ click.echo(f"Created theme '{name}'.")
281
+ click.echo()
282
+ click.echo(f" File: {target_path}")
283
+ click.echo()
284
+ click.echo("Next steps:")
285
+ click.echo(f" 1. Edit the theme file to customize your agents")
286
+ click.echo(f" 2. Run 'pf theme set {name}' to activate")
@@ -0,0 +1,53 @@
1
+ # Meta Scripts
2
+
3
+ **These scripts are NOT distributed to users.** They are for Pennyfarthing framework development only.
4
+
5
+ ## Contents
6
+
7
+ | Script | Purpose |
8
+ |--------|---------|
9
+ | `deploy.sh` | Release Pennyfarthing (version bump, tag, push, GitHub release) |
10
+ | `benchmark-runner.{sh,js}` | Run persona benchmarks |
11
+ | `job-fair-*.sh` | Job Fair character evaluations |
12
+ | `aggregate-benchmark-stats.{sh,js}` | Aggregate benchmark results |
13
+ | `solo-runner.sh` | Run single agent on a scenario |
14
+ | `parallel-benchmark.sh` | Parallel benchmark execution |
15
+ | `consolidate-job-fair.sh` | Consolidate Job Fair results |
16
+ | `convert-jobfair-to-benchmarks.sh` | Format conversion |
17
+ | `generate-leaderboard.sh` | Build leaderboard from benchmarks |
18
+ | `regenerate-summaries.sh` | Regenerate benchmark summaries |
19
+ | `cyclist-debug.mjs` | Debug Cyclist connection |
20
+ | `handoff-cli.{sh,js}` | Test handoff flow |
21
+ | `verify-visual-mapping.js` | Verify theme visual mappings |
22
+ | `migrate-assets-to-slug.sh` | One-time migration script |
23
+ | `resize-portraits.sh` | Resize portrait images |
24
+ | `resolve-portrait.mjs` | Portrait resolution logic |
25
+
26
+ ## Usage
27
+
28
+ Run from pennyfarthing repo root:
29
+
30
+ ```bash
31
+ # Release a new version
32
+ ./scripts/deploy.sh --dry-run patch
33
+ ./scripts/deploy.sh patch
34
+
35
+ # Run benchmarks
36
+ ./scripts/benchmark-runner.sh --theme mash --agent sm
37
+
38
+ # Job Fair
39
+ ./scripts/job-fair-runner.sh mash
40
+ ```
41
+
42
+ ## Where Should My Script Go?
43
+
44
+ **Put it here if:**
45
+ - It's for framework development/CI only
46
+ - Users should NOT have access to it
47
+ - It uses GPU/heavy dependencies (keep in meta, not distributed)
48
+
49
+ **Put it in `pennyfarthing-dist/scripts/` if:**
50
+ - Users need it for their workflows
51
+ - It's part of the sprint/story/jira tooling
52
+
53
+ See `CLAUDE.md` for the full decision tree.
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/env node
2
+ // Fix execute permissions on shell scripts after npm install.
3
+ // npm may strip +x bits depending on platform/version.
4
+
5
+ const { readdirSync, chmodSync, statSync } = require('fs');
6
+ const { join } = require('path');
7
+
8
+ const scriptsDir = join(__dirname, '..', 'pennyfarthing-dist', 'scripts');
9
+
10
+ function fixPermissions(dir) {
11
+ let entries;
12
+ try {
13
+ entries = readdirSync(dir, { withFileTypes: true });
14
+ } catch {
15
+ return;
16
+ }
17
+ for (const entry of entries) {
18
+ const full = join(dir, entry.name);
19
+ if (entry.isDirectory()) {
20
+ fixPermissions(full);
21
+ } else if (entry.name.endsWith('.sh') && entry.name !== 'find-root.sh') {
22
+ try {
23
+ const stat = statSync(full);
24
+ if ((stat.mode & 0o111) === 0) {
25
+ chmodSync(full, 0o755);
26
+ }
27
+ } catch {
28
+ // Skip files we can't chmod
29
+ }
30
+ }
31
+ }
32
+ }
33
+
34
+ fixPermissions(scriptsDir);
@@ -1,96 +0,0 @@
1
- ---
2
- name: workflow-status-check
3
- description: Determine workflow state using sprint scripts
4
- tools: Bash, Read
5
- model: haiku
6
- ---
7
-
8
- <info>
9
- Universal entry point telling agents: what work exists, what phase, and whether to activate.
10
- Uses `/sprint` skill scripts for deterministic output.
11
- </info>
12
-
13
- <arguments>
14
- | Argument | Required | Description |
15
- |----------|----------|-------------|
16
- | `CALLING_AGENT` | Yes | Agent requesting status check (e.g., "SM", "Architect", "PM") |
17
- </arguments>
18
-
19
- ---
20
-
21
- ## Execution
22
-
23
- Run the sprint status script and parse output:
24
-
25
- ```bash
26
- .pennyfarthing/scripts/sprint/sprint-status.sh
27
- ```
28
-
29
- Then check for active sessions:
30
-
31
- ```bash
32
- if ls .session/*-session.md 1>/dev/null 2>&1; then
33
- for f in .session/*-session.md; do head -30 "$f"; done
34
- else
35
- echo "No active sessions"
36
- fi
37
- ```
38
-
39
- ---
40
-
41
- ## State Determination
42
-
43
- | State | Condition |
44
- |-------|-----------|
45
- | `FINISH_STATE` | Session exists with Phase=approved OR Status=approved |
46
- | `IN_PROGRESS_STATE` | Phased workflow session with active phase (setup/red/green/implement/review) |
47
- | `STEPPED_WORKFLOW_STATE` | Stepped workflow session (workflow type = stepped) |
48
- | `NEW_WORK_STATE` | No sessions AND sprint has backlog/ready stories |
49
- | `EMPTY_BACKLOG_STATE` | No sessions AND sprint has NO backlog/ready stories |
50
-
51
- **Detecting workflow type from session:**
52
- ```bash
53
- # Read workflow name from session
54
- WORKFLOW=$(grep '^\*\*Workflow:\*\*' .session/*-session.md | head -1 | sed 's/.*: //')
55
- # Check if stepped
56
- .pennyfarthing/scripts/workflow/get-workflow-type.sh "$WORKFLOW"
57
- ```
58
-
59
- **Important:** Sprints are fixed two-week periods (kanban-style). Never suggest closing a sprint early or starting sprint planning when backlog is empty. The correct response to `EMPTY_BACKLOG_STATE` is to suggest promoting stories from `future.yaml`.
60
-
61
- ---
62
-
63
- <output>
64
- ## Output Format
65
-
66
- Return a `STATUS_CHECK_RESULT` block:
67
-
68
- ### Success
69
- ```
70
- STATUS_CHECK_RESULT:
71
- status: success
72
- state: {FINISH_STATE|IN_PROGRESS_STATE|NEW_WORK_STATE|EMPTY_BACKLOG_STATE}
73
- story_id: {ID or null}
74
- phase: {current phase or null}
75
- phase_owner: {agent name or null}
76
- sprint_number: {N}
77
- backlog_count: {N}
78
-
79
- next_steps:
80
- - FINISH_STATE: "Proceed to Finish Flow - spawn sm-finish with PHASE=preflight"
81
- - IN_PROGRESS_STATE: "Report phase owner '{phase_owner}' should continue. Run handoff-marker.sh {phase_owner}"
82
- - STEPPED_WORKFLOW_STATE: "Stepped workflow in progress. Tell user to run /workflow resume or /workflow status"
83
- - NEW_WORK_STATE: "Present available stories to user. Await selection, then spawn sm-setup MODE=setup"
84
- - EMPTY_BACKLOG_STATE: "Report backlog empty. Suggest promoting from future.yaml"
85
- ```
86
-
87
- ### Active Session Details (if IN_PROGRESS_STATE)
88
- ```
89
- session:
90
- story_id: {ID}
91
- title: "{title}"
92
- workflow: {workflow}
93
- phase: {phase}
94
- branch: {branch}
95
- ```
96
- </output>