@pennyfarthing/core 10.0.5 → 10.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (580) hide show
  1. package/README.md +19 -22
  2. package/package.json +17 -11
  3. package/packages/core/dist/cli/commands/doctor-file-layout.test.js.map +1 -1
  4. package/packages/core/dist/cli/commands/doctor-legacy.test.js +24 -0
  5. package/packages/core/dist/cli/commands/doctor-legacy.test.js.map +1 -1
  6. package/packages/core/dist/cli/commands/doctor.d.ts.map +1 -1
  7. package/packages/core/dist/cli/commands/doctor.js +346 -13
  8. package/packages/core/dist/cli/commands/doctor.js.map +1 -1
  9. package/packages/core/dist/cli/commands/e2e-fresh-install.test.js +1 -1
  10. package/packages/core/dist/cli/commands/e2e-fresh-install.test.js.map +1 -1
  11. package/packages/core/dist/cli/commands/e2e-upgrade.test.js +1 -1
  12. package/packages/core/dist/cli/commands/e2e-upgrade.test.js.map +1 -1
  13. package/packages/core/dist/cli/commands/hooks-consolidation.test.js +2 -2
  14. package/packages/core/dist/cli/commands/hooks-consolidation.test.js.map +1 -1
  15. package/packages/core/dist/cli/commands/init-consolidation.test.js.map +1 -1
  16. package/packages/core/dist/cli/commands/init.d.ts +7 -0
  17. package/packages/core/dist/cli/commands/init.d.ts.map +1 -1
  18. package/packages/core/dist/cli/commands/init.js +41 -8
  19. package/packages/core/dist/cli/commands/init.js.map +1 -1
  20. package/packages/core/dist/cli/commands/uninstall.d.ts.map +1 -1
  21. package/packages/core/dist/cli/commands/uninstall.js +24 -13
  22. package/packages/core/dist/cli/commands/uninstall.js.map +1 -1
  23. package/packages/core/dist/cli/commands/update-consolidation.test.js +0 -10
  24. package/packages/core/dist/cli/commands/update-consolidation.test.js.map +1 -1
  25. package/packages/core/dist/cli/commands/update.d.ts.map +1 -1
  26. package/packages/core/dist/cli/commands/update.js +26 -0
  27. package/packages/core/dist/cli/commands/update.js.map +1 -1
  28. package/packages/core/dist/cli/index.js +1 -1
  29. package/packages/core/dist/cli/index.js.map +1 -1
  30. package/packages/core/dist/cli/ocean-profiles.test.js.map +1 -1
  31. package/packages/core/dist/cli/theme-maker.test.js +64 -115
  32. package/packages/core/dist/cli/theme-maker.test.js.map +1 -1
  33. package/packages/core/dist/cli/utils/python.d.ts +22 -0
  34. package/packages/core/dist/cli/utils/python.d.ts.map +1 -0
  35. package/packages/core/dist/cli/utils/python.js +102 -0
  36. package/packages/core/dist/cli/utils/python.js.map +1 -0
  37. package/packages/core/dist/cli/utils/settings.d.ts.map +1 -1
  38. package/packages/core/dist/cli/utils/settings.js +10 -0
  39. package/packages/core/dist/cli/utils/settings.js.map +1 -1
  40. package/packages/core/dist/index.d.ts +1 -1
  41. package/packages/core/dist/index.d.ts.map +1 -1
  42. package/packages/core/dist/index.js +2 -2
  43. package/packages/core/dist/index.js.map +1 -1
  44. package/packages/core/dist/plugins/plugin-discovery.d.ts +116 -0
  45. package/packages/core/dist/plugins/plugin-discovery.d.ts.map +1 -0
  46. package/packages/core/dist/plugins/plugin-discovery.js +165 -0
  47. package/packages/core/dist/plugins/plugin-discovery.js.map +1 -0
  48. package/packages/core/dist/plugins/plugin-discovery.test.d.ts +22 -0
  49. package/packages/core/dist/plugins/plugin-discovery.test.d.ts.map +1 -0
  50. package/packages/core/dist/plugins/plugin-discovery.test.js +498 -0
  51. package/packages/core/dist/plugins/plugin-discovery.test.js.map +1 -0
  52. package/packages/core/dist/scripts/generate-spider-report.js.map +1 -1
  53. package/packages/core/dist/workflow/context-watch.d.ts +80 -0
  54. package/packages/core/dist/workflow/context-watch.d.ts.map +1 -0
  55. package/packages/core/dist/workflow/context-watch.js +235 -0
  56. package/packages/core/dist/workflow/context-watch.js.map +1 -0
  57. package/packages/core/dist/workflow/context-watch.test.d.ts +1 -0
  58. package/packages/core/dist/workflow/context-watch.test.d.ts.map +1 -0
  59. package/packages/core/dist/workflow/context-watch.test.js +746 -0
  60. package/packages/core/dist/workflow/context-watch.test.js.map +1 -0
  61. package/packages/core/dist/workflow/file-watch.d.ts +82 -0
  62. package/packages/core/dist/workflow/file-watch.d.ts.map +1 -0
  63. package/packages/core/dist/workflow/file-watch.js +198 -0
  64. package/packages/core/dist/workflow/file-watch.js.map +1 -0
  65. package/packages/core/dist/workflow/file-watch.test.d.ts +21 -0
  66. package/packages/core/dist/workflow/file-watch.test.d.ts.map +1 -0
  67. package/packages/core/dist/workflow/file-watch.test.js +469 -0
  68. package/packages/core/dist/workflow/file-watch.test.js.map +1 -0
  69. package/packages/core/dist/workflow/observation-writer.d.ts +79 -0
  70. package/packages/core/dist/workflow/observation-writer.d.ts.map +1 -0
  71. package/packages/core/dist/workflow/observation-writer.js +97 -0
  72. package/packages/core/dist/workflow/observation-writer.js.map +1 -0
  73. package/packages/core/dist/workflow/observation-writer.test.d.ts +18 -0
  74. package/packages/core/dist/workflow/observation-writer.test.d.ts.map +1 -0
  75. package/packages/core/dist/workflow/observation-writer.test.js +424 -0
  76. package/packages/core/dist/workflow/observation-writer.test.js.map +1 -0
  77. package/packages/core/dist/workflow/story-workflow-routing.test.js +4 -2
  78. package/packages/core/dist/workflow/story-workflow-routing.test.js.map +1 -1
  79. package/packages/core/dist/workflow/tandem-lifecycle.d.ts +117 -0
  80. package/packages/core/dist/workflow/tandem-lifecycle.d.ts.map +1 -0
  81. package/packages/core/dist/workflow/tandem-lifecycle.js +186 -0
  82. package/packages/core/dist/workflow/tandem-lifecycle.js.map +1 -0
  83. package/packages/core/dist/workflow/tandem-lifecycle.test.d.ts +16 -0
  84. package/packages/core/dist/workflow/tandem-lifecycle.test.d.ts.map +1 -0
  85. package/packages/core/dist/workflow/tandem-lifecycle.test.js +531 -0
  86. package/packages/core/dist/workflow/tandem-lifecycle.test.js.map +1 -0
  87. package/packages/core/dist/workflow/tool-watch.d.ts +68 -0
  88. package/packages/core/dist/workflow/tool-watch.d.ts.map +1 -0
  89. package/packages/core/dist/workflow/tool-watch.js +166 -0
  90. package/packages/core/dist/workflow/tool-watch.js.map +1 -0
  91. package/packages/core/dist/workflow/tool-watch.test.d.ts +18 -0
  92. package/packages/core/dist/workflow/tool-watch.test.d.ts.map +1 -0
  93. package/packages/core/dist/workflow/tool-watch.test.js +718 -0
  94. package/packages/core/dist/workflow/tool-watch.test.js.map +1 -0
  95. package/packages/core/dist/workflow/workflow-migration.test.js +8 -4
  96. package/packages/core/dist/workflow/workflow-migration.test.js.map +1 -1
  97. package/packages/core/dist/workflow/workflow-schema.d.ts +7 -0
  98. package/packages/core/dist/workflow/workflow-schema.d.ts.map +1 -1
  99. package/packages/core/dist/workflow/workflow-schema.js +44 -0
  100. package/packages/core/dist/workflow/workflow-schema.js.map +1 -1
  101. package/packages/core/dist/workflow/workflow-schema.test.d.ts.map +1 -1
  102. package/packages/core/dist/workflow/workflow-schema.test.js +192 -0
  103. package/packages/core/dist/workflow/workflow-schema.test.js.map +1 -1
  104. package/pennyfarthing-dist/agents/README.md +1 -3
  105. package/pennyfarthing-dist/agents/architect.md +0 -6
  106. package/pennyfarthing-dist/agents/devops.md +0 -6
  107. package/pennyfarthing-dist/agents/handoff.md +18 -3
  108. package/pennyfarthing-dist/agents/orchestrator.md +0 -6
  109. package/pennyfarthing-dist/agents/pm.md +0 -6
  110. package/pennyfarthing-dist/agents/sm-finish.md +1 -1
  111. package/pennyfarthing-dist/agents/sm-handoff.md +27 -4
  112. package/pennyfarthing-dist/agents/sm.md +11 -11
  113. package/pennyfarthing-dist/agents/tandem-backseat.md +119 -0
  114. package/pennyfarthing-dist/commands/architect.md +11 -3
  115. package/pennyfarthing-dist/commands/close-epic.md +24 -131
  116. package/pennyfarthing-dist/commands/create-theme.md +14 -24
  117. package/pennyfarthing-dist/commands/dev.md +11 -3
  118. package/pennyfarthing-dist/commands/devops.md +11 -3
  119. package/pennyfarthing-dist/commands/health-check.md +1 -3
  120. package/pennyfarthing-dist/commands/help.md +8 -12
  121. package/pennyfarthing-dist/commands/list-themes.md +14 -16
  122. package/pennyfarthing-dist/commands/orchestrator.md +11 -3
  123. package/pennyfarthing-dist/commands/parallel-work.md +1 -3
  124. package/pennyfarthing-dist/commands/pm.md +11 -3
  125. package/pennyfarthing-dist/commands/prime.md +6 -6
  126. package/pennyfarthing-dist/commands/reviewer.md +11 -3
  127. package/pennyfarthing-dist/commands/run-ci.md +1 -1
  128. package/pennyfarthing-dist/commands/set-theme.md +14 -51
  129. package/pennyfarthing-dist/commands/setup.md +5 -1
  130. package/pennyfarthing-dist/commands/show-theme.md +14 -16
  131. package/pennyfarthing-dist/commands/sm.md +11 -3
  132. package/pennyfarthing-dist/commands/tea.md +11 -3
  133. package/pennyfarthing-dist/commands/tech-writer.md +11 -3
  134. package/pennyfarthing-dist/commands/theme-maker.md +14 -671
  135. package/pennyfarthing-dist/commands/theme.md +95 -0
  136. package/pennyfarthing-dist/commands/ux-designer.md +11 -3
  137. package/pennyfarthing-dist/commands/work.md +3 -5
  138. package/pennyfarthing-dist/guides/agent-behavior.md +62 -6
  139. package/pennyfarthing-dist/guides/agent-coordination.md +11 -13
  140. package/pennyfarthing-dist/guides/agent-template-tactical.md +2 -3
  141. package/pennyfarthing-dist/guides/bikelane.md +3 -2
  142. package/pennyfarthing-dist/guides/command-tag-taxonomy.md +212 -0
  143. package/pennyfarthing-dist/guides/hooks.md +5 -5
  144. package/pennyfarthing-dist/guides/patterns/fan-out-fan-in-pattern.md +3 -3
  145. package/pennyfarthing-dist/guides/patterns/helper-delegation-pattern.md +9 -59
  146. package/pennyfarthing-dist/guides/patterns/tdd-flow-pattern.md +4 -5
  147. package/pennyfarthing-dist/guides/prime.md +2 -2
  148. package/pennyfarthing-dist/guides/scale-levels.md +4 -6
  149. package/pennyfarthing-dist/guides/skill-schema.md +4 -4
  150. package/pennyfarthing-dist/guides/tandem-protocol.md +158 -0
  151. package/pennyfarthing-dist/personas/themes/discworld.yaml +1 -1
  152. package/pennyfarthing-dist/personas/themes/fifth-element.yaml +295 -0
  153. package/pennyfarthing-dist/scripts/README.md +1 -1
  154. package/pennyfarthing-dist/scripts/core/agent-session.sh +6 -2
  155. package/pennyfarthing-dist/scripts/core/check-context.sh +0 -0
  156. package/pennyfarthing-dist/scripts/core/handoff-marker.sh +0 -0
  157. package/pennyfarthing-dist/scripts/core/phase-check-start.sh +0 -0
  158. package/pennyfarthing-dist/scripts/core/prime.sh +8 -10
  159. package/pennyfarthing-dist/scripts/cyclist/is-cyclist.sh +0 -0
  160. package/pennyfarthing-dist/scripts/git/create-feature-branches.sh +0 -0
  161. package/pennyfarthing-dist/scripts/git/git-status-all.sh +0 -0
  162. package/pennyfarthing-dist/scripts/git/install-git-hooks.sh +8 -6
  163. package/pennyfarthing-dist/scripts/git/release.sh +0 -0
  164. package/pennyfarthing-dist/scripts/git/worktree-manager.sh +0 -0
  165. package/pennyfarthing-dist/scripts/health/drift-detection.sh +0 -0
  166. package/pennyfarthing-dist/scripts/hooks/bell-mode-hook.sh +131 -54
  167. package/pennyfarthing-dist/scripts/hooks/context-circuit-breaker.sh +0 -0
  168. package/pennyfarthing-dist/scripts/hooks/context-warning.sh +0 -0
  169. package/pennyfarthing-dist/scripts/hooks/cyclist-pretooluse-hook.sh +0 -0
  170. package/pennyfarthing-dist/scripts/hooks/otel-auto-config.sh +0 -0
  171. package/pennyfarthing-dist/scripts/hooks/post-merge.sh +32 -15
  172. package/pennyfarthing-dist/scripts/hooks/pre-commit.sh +4 -3
  173. package/pennyfarthing-dist/scripts/hooks/pre-edit-check.sh +0 -0
  174. package/pennyfarthing-dist/scripts/hooks/pre-push.sh +11 -5
  175. package/pennyfarthing-dist/scripts/hooks/question-reflector-check.sh +0 -0
  176. package/pennyfarthing-dist/scripts/hooks/question_reflector_check.py +0 -0
  177. package/pennyfarthing-dist/scripts/hooks/schema-validation.sh +0 -0
  178. package/pennyfarthing-dist/scripts/hooks/session-start.sh +0 -0
  179. package/pennyfarthing-dist/scripts/hooks/session-stop.sh +0 -0
  180. package/pennyfarthing-dist/scripts/hooks/sprint-yaml-validation.sh +0 -0
  181. package/pennyfarthing-dist/scripts/hooks/welcome-hook.sh +0 -0
  182. package/pennyfarthing-dist/scripts/jira/create-jira-epic.sh +0 -0
  183. package/pennyfarthing-dist/scripts/jira/create-jira-story.sh +0 -0
  184. package/pennyfarthing-dist/scripts/jira/jira-claim-story.sh +0 -0
  185. package/pennyfarthing-dist/scripts/jira/jira-reconcile.sh +0 -0
  186. package/pennyfarthing-dist/scripts/jira/jira-sync-story.sh +0 -0
  187. package/pennyfarthing-dist/scripts/jira/sync-epic-jira.sh +0 -0
  188. package/pennyfarthing-dist/scripts/lib/background-tasks.sh +0 -0
  189. package/pennyfarthing-dist/scripts/lib/checkpoint.sh +0 -0
  190. package/pennyfarthing-dist/scripts/lib/common.sh +0 -0
  191. package/pennyfarthing-dist/scripts/lib/file-lock.sh +0 -0
  192. package/pennyfarthing-dist/scripts/lib/logging.sh +0 -0
  193. package/pennyfarthing-dist/scripts/lib/retry.sh +0 -0
  194. package/pennyfarthing-dist/scripts/maintenance/migrate-theme-schema.mjs +0 -0
  195. package/pennyfarthing-dist/scripts/maintenance/sidecar-health.sh +0 -0
  196. package/pennyfarthing-dist/scripts/misc/README.md +1 -1
  197. package/pennyfarthing-dist/scripts/misc/add-short-names.sh +0 -0
  198. package/pennyfarthing-dist/scripts/misc/add_short_names.py +0 -0
  199. package/pennyfarthing-dist/scripts/misc/backlog.sh +0 -0
  200. package/pennyfarthing-dist/scripts/misc/check-status.sh +0 -0
  201. package/pennyfarthing-dist/scripts/misc/find-related-work.sh +0 -0
  202. package/pennyfarthing-dist/scripts/misc/generate-skill-docs.sh +0 -0
  203. package/pennyfarthing-dist/scripts/misc/log-skill-usage.sh +0 -0
  204. package/pennyfarthing-dist/scripts/misc/migrate-bmad-workflow.sh +0 -0
  205. package/pennyfarthing-dist/scripts/misc/migrate_bmad_workflow.py +0 -0
  206. package/pennyfarthing-dist/scripts/misc/repo-scan.sh +0 -0
  207. package/pennyfarthing-dist/scripts/misc/repo-utils.sh +0 -0
  208. package/pennyfarthing-dist/scripts/misc/run-ci.sh +0 -0
  209. package/pennyfarthing-dist/scripts/misc/run-timestamp.sh +0 -0
  210. package/pennyfarthing-dist/scripts/misc/session-cleanup.sh +0 -0
  211. package/pennyfarthing-dist/scripts/misc/skill-usage-report.sh +0 -0
  212. package/pennyfarthing-dist/scripts/misc/statusline.sh +50 -8
  213. package/pennyfarthing-dist/scripts/misc/uninstall.sh +0 -0
  214. package/pennyfarthing-dist/scripts/misc/validate-subagent-frontmatter.sh +1 -2
  215. package/pennyfarthing-dist/scripts/portraits/generate-portraits.sh +0 -0
  216. package/pennyfarthing-dist/scripts/story/create-story.sh +0 -0
  217. package/pennyfarthing-dist/scripts/story/size-story.sh +0 -0
  218. package/pennyfarthing-dist/scripts/story/story-template.sh +0 -0
  219. package/pennyfarthing-dist/scripts/tests/check.test.sh +0 -0
  220. package/pennyfarthing-dist/scripts/tests/dev-story-workflow-import.test.sh +0 -0
  221. package/pennyfarthing-dist/scripts/tests/epics-and-stories-workflow-import.test.sh +0 -0
  222. package/pennyfarthing-dist/scripts/tests/handoff-phase-update.test.sh +5 -5
  223. package/pennyfarthing-dist/scripts/tests/implementation-readiness-workflow-import.test.sh +0 -0
  224. package/pennyfarthing-dist/scripts/tests/migrate-bmad-workflow.test.sh +0 -0
  225. package/pennyfarthing-dist/scripts/tests/prd-workflow-import.test.sh +0 -0
  226. package/pennyfarthing-dist/scripts/tests/project-context-workflow-import.test.sh +0 -0
  227. package/pennyfarthing-dist/scripts/tests/test-character-voice.sh +0 -0
  228. package/pennyfarthing-dist/scripts/tests/test-drift-detection.sh +3 -79
  229. package/pennyfarthing-dist/scripts/tests/test-post-merge-hook.sh +0 -0
  230. package/pennyfarthing-dist/scripts/tests/test-session-checkpoint.sh +0 -0
  231. package/pennyfarthing-dist/scripts/tests/test-solo-command.sh +0 -0
  232. package/pennyfarthing-dist/scripts/tests/ux-design-workflow-import.test.sh +0 -0
  233. package/pennyfarthing-dist/scripts/theme/README.md +1 -1
  234. package/pennyfarthing-dist/scripts/theme/list-themes.sh +0 -0
  235. package/pennyfarthing-dist/scripts/validation/validate-agent-schema.sh +0 -1
  236. package/pennyfarthing-dist/scripts/workflow/README.md +2 -2
  237. package/pennyfarthing-dist/scripts/workflow/check.py +0 -0
  238. package/pennyfarthing-dist/scripts/workflow/check.sh +0 -0
  239. package/pennyfarthing-dist/scripts/workflow/complete-step.py +0 -0
  240. package/pennyfarthing-dist/scripts/workflow/finish-story.sh +10 -144
  241. package/pennyfarthing-dist/scripts/workflow/fix-session-phase.sh +0 -0
  242. package/pennyfarthing-dist/scripts/workflow/get-workflow-type.py +0 -0
  243. package/pennyfarthing-dist/scripts/workflow/get-workflow-type.sh +0 -0
  244. package/pennyfarthing-dist/scripts/workflow/list-workflows.sh +0 -0
  245. package/pennyfarthing-dist/scripts/workflow/phase-owner.sh +0 -0
  246. package/pennyfarthing-dist/scripts/workflow/resume-workflow.sh +0 -0
  247. package/pennyfarthing-dist/scripts/workflow/show-workflow.sh +0 -0
  248. package/pennyfarthing-dist/scripts/workflow/start-workflow.sh +0 -0
  249. package/pennyfarthing-dist/scripts/workflow/workflow-status.sh +0 -0
  250. package/pennyfarthing-dist/skills/dev-patterns/SKILL.md +2 -2
  251. package/pennyfarthing-dist/skills/skill-registry.schema.json +8 -0
  252. package/pennyfarthing-dist/skills/skill-registry.yaml +21 -17
  253. package/pennyfarthing-dist/skills/sprint/skill.md +25 -2
  254. package/pennyfarthing-dist/skills/story/scripts/create-story.sh +0 -0
  255. package/pennyfarthing-dist/skills/story/scripts/size-story.sh +0 -0
  256. package/pennyfarthing-dist/skills/story/scripts/story-template.sh +0 -0
  257. package/pennyfarthing-dist/skills/theme/skill.md +290 -75
  258. package/pennyfarthing-dist/skills/theme-creation/SKILL.md +23 -166
  259. package/pennyfarthing-dist/skills/workflow/scripts/list-workflows.sh +0 -0
  260. package/pennyfarthing-dist/skills/workflow/scripts/resume-workflow.sh +0 -0
  261. package/pennyfarthing-dist/skills/workflow/scripts/show-workflow.sh +0 -0
  262. package/pennyfarthing-dist/skills/workflow/scripts/start-workflow.sh +0 -0
  263. package/pennyfarthing-dist/skills/workflow/scripts/workflow-status.sh +0 -0
  264. package/pennyfarthing-dist/skills/workflow/skill.md +27 -4
  265. package/pennyfarthing-dist/templates/agent-scopes.yaml.template +0 -11
  266. package/pennyfarthing-dist/templates/auto-load-sm.sh.template +14 -0
  267. package/pennyfarthing-dist/templates/settings.local.json.template +9 -0
  268. package/pennyfarthing-dist/workflows/2party-tdd.yaml +399 -0
  269. package/pennyfarthing-dist/workflows/architecture/workflow.yaml +65 -0
  270. package/pennyfarthing-dist/workflows/bdd-tandem.yaml +70 -0
  271. package/pennyfarthing-dist/workflows/epics-and-stories/steps/step-05-import-to-future.md +41 -24
  272. package/pennyfarthing-dist/workflows/tdd-tandem.yaml +61 -0
  273. package/pennyfarthing_scripts/__pycache__/__init__.cpython-314.pyc +0 -0
  274. package/pennyfarthing_scripts/__pycache__/bellmode_hook.cpython-314.pyc +0 -0
  275. package/pennyfarthing_scripts/__pycache__/cli.cpython-314.pyc +0 -0
  276. package/pennyfarthing_scripts/__pycache__/config.cpython-314.pyc +0 -0
  277. package/pennyfarthing_scripts/__pycache__/hooks.cpython-314.pyc +0 -0
  278. package/pennyfarthing_scripts/__pycache__/jira_bidirectional_sync.cpython-314.pyc +0 -0
  279. package/pennyfarthing_scripts/__pycache__/jira_epic_creation.cpython-314.pyc +0 -0
  280. package/pennyfarthing_scripts/__pycache__/jira_sync.cpython-314.pyc +0 -0
  281. package/pennyfarthing_scripts/__pycache__/jira_sync_story.cpython-314.pyc +0 -0
  282. package/pennyfarthing_scripts/__pycache__/output.cpython-314.pyc +0 -0
  283. package/pennyfarthing_scripts/__pycache__/patch_mode.cpython-314.pyc +0 -0
  284. package/pennyfarthing_scripts/__pycache__/schema_validation_hook.cpython-314.pyc +0 -0
  285. package/pennyfarthing_scripts/__pycache__/workflow.cpython-314.pyc +0 -0
  286. package/pennyfarthing_scripts/bellmode_hook.py +202 -47
  287. package/pennyfarthing_scripts/brownfield/__init__.py +6 -6
  288. package/pennyfarthing_scripts/brownfield/__main__.py +1 -0
  289. package/pennyfarthing_scripts/brownfield/__pycache__/__init__.cpython-314.pyc +0 -0
  290. package/pennyfarthing_scripts/brownfield/__pycache__/__main__.cpython-314.pyc +0 -0
  291. package/pennyfarthing_scripts/brownfield/__pycache__/cli.cpython-314.pyc +0 -0
  292. package/pennyfarthing_scripts/brownfield/__pycache__/discover.cpython-314.pyc +0 -0
  293. package/pennyfarthing_scripts/brownfield/cli.py +0 -1
  294. package/pennyfarthing_scripts/brownfield/discover.py +1 -2
  295. package/pennyfarthing_scripts/cli.py +23 -3
  296. package/pennyfarthing_scripts/codemarkers/__init__.py +23 -0
  297. package/pennyfarthing_scripts/codemarkers/__main__.py +6 -0
  298. package/pennyfarthing_scripts/codemarkers/__pycache__/__init__.cpython-314.pyc +0 -0
  299. package/pennyfarthing_scripts/codemarkers/__pycache__/__main__.cpython-314.pyc +0 -0
  300. package/pennyfarthing_scripts/codemarkers/__pycache__/analyze.cpython-314.pyc +0 -0
  301. package/pennyfarthing_scripts/codemarkers/__pycache__/cli.cpython-314.pyc +0 -0
  302. package/pennyfarthing_scripts/codemarkers/__pycache__/formatters.cpython-314.pyc +0 -0
  303. package/pennyfarthing_scripts/codemarkers/__pycache__/models.cpython-314.pyc +0 -0
  304. package/pennyfarthing_scripts/codemarkers/analyze.py +501 -0
  305. package/pennyfarthing_scripts/codemarkers/cli.py +179 -0
  306. package/pennyfarthing_scripts/codemarkers/formatters.py +88 -0
  307. package/pennyfarthing_scripts/codemarkers/models.py +60 -0
  308. package/pennyfarthing_scripts/common/__init__.py +8 -9
  309. package/pennyfarthing_scripts/common/__pycache__/__init__.cpython-314.pyc +0 -0
  310. package/pennyfarthing_scripts/common/__pycache__/config.cpython-314.pyc +0 -0
  311. package/pennyfarthing_scripts/common/__pycache__/output.cpython-314.pyc +0 -0
  312. package/pennyfarthing_scripts/common/__pycache__/themes.cpython-314.pyc +0 -0
  313. package/pennyfarthing_scripts/common/config.py +1 -1
  314. package/pennyfarthing_scripts/complexity/__init__.py +15 -0
  315. package/pennyfarthing_scripts/complexity/__main__.py +6 -0
  316. package/pennyfarthing_scripts/complexity/__pycache__/__init__.cpython-314.pyc +0 -0
  317. package/pennyfarthing_scripts/complexity/__pycache__/__main__.cpython-314.pyc +0 -0
  318. package/pennyfarthing_scripts/complexity/__pycache__/analyze.cpython-314.pyc +0 -0
  319. package/pennyfarthing_scripts/complexity/__pycache__/cli.cpython-314.pyc +0 -0
  320. package/pennyfarthing_scripts/complexity/__pycache__/formatters.cpython-314.pyc +0 -0
  321. package/pennyfarthing_scripts/complexity/__pycache__/models.cpython-314.pyc +0 -0
  322. package/pennyfarthing_scripts/complexity/analyze.py +207 -0
  323. package/pennyfarthing_scripts/complexity/cli.py +82 -0
  324. package/pennyfarthing_scripts/complexity/formatters.py +64 -0
  325. package/pennyfarthing_scripts/complexity/models.py +32 -0
  326. package/pennyfarthing_scripts/context.py +14 -15
  327. package/pennyfarthing_scripts/deadcode/__init__.py +6 -0
  328. package/pennyfarthing_scripts/deadcode/__main__.py +6 -0
  329. package/pennyfarthing_scripts/deadcode/__pycache__/__init__.cpython-314.pyc +0 -0
  330. package/pennyfarthing_scripts/deadcode/__pycache__/__main__.cpython-314.pyc +0 -0
  331. package/pennyfarthing_scripts/deadcode/__pycache__/analyze.cpython-314.pyc +0 -0
  332. package/pennyfarthing_scripts/deadcode/__pycache__/cli.cpython-314.pyc +0 -0
  333. package/pennyfarthing_scripts/deadcode/__pycache__/formatters.cpython-314.pyc +0 -0
  334. package/pennyfarthing_scripts/deadcode/__pycache__/models.cpython-314.pyc +0 -0
  335. package/pennyfarthing_scripts/deadcode/analyze.py +322 -0
  336. package/pennyfarthing_scripts/deadcode/cli.py +163 -0
  337. package/pennyfarthing_scripts/deadcode/formatters.py +106 -0
  338. package/pennyfarthing_scripts/deadcode/models.py +54 -0
  339. package/pennyfarthing_scripts/dependencies/__init__.py +20 -0
  340. package/pennyfarthing_scripts/dependencies/__main__.py +5 -0
  341. package/pennyfarthing_scripts/dependencies/__pycache__/__init__.cpython-314.pyc +0 -0
  342. package/pennyfarthing_scripts/dependencies/__pycache__/__main__.cpython-314.pyc +0 -0
  343. package/pennyfarthing_scripts/dependencies/__pycache__/analyze.cpython-314.pyc +0 -0
  344. package/pennyfarthing_scripts/dependencies/__pycache__/cli.cpython-314.pyc +0 -0
  345. package/pennyfarthing_scripts/dependencies/__pycache__/formatters.cpython-314.pyc +0 -0
  346. package/pennyfarthing_scripts/dependencies/__pycache__/models.cpython-314.pyc +0 -0
  347. package/pennyfarthing_scripts/dependencies/analyze.py +155 -0
  348. package/pennyfarthing_scripts/dependencies/cli.py +76 -0
  349. package/pennyfarthing_scripts/dependencies/formatters.py +63 -0
  350. package/pennyfarthing_scripts/dependencies/models.py +39 -0
  351. package/pennyfarthing_scripts/git/__init__.py +5 -5
  352. package/pennyfarthing_scripts/git/__pycache__/__init__.cpython-314.pyc +0 -0
  353. package/pennyfarthing_scripts/git/__pycache__/create_branches.cpython-314.pyc +0 -0
  354. package/pennyfarthing_scripts/git/__pycache__/status_all.cpython-314.pyc +0 -0
  355. package/pennyfarthing_scripts/git/create_branches.py +3 -2
  356. package/pennyfarthing_scripts/git/status_all.py +1 -1
  357. package/pennyfarthing_scripts/healthscore/__init__.py +21 -0
  358. package/pennyfarthing_scripts/healthscore/__main__.py +14 -0
  359. package/pennyfarthing_scripts/healthscore/__pycache__/__init__.cpython-314.pyc +0 -0
  360. package/pennyfarthing_scripts/healthscore/__pycache__/__main__.cpython-314.pyc +0 -0
  361. package/pennyfarthing_scripts/healthscore/__pycache__/analyze.cpython-314.pyc +0 -0
  362. package/pennyfarthing_scripts/healthscore/__pycache__/cli.cpython-314.pyc +0 -0
  363. package/pennyfarthing_scripts/healthscore/__pycache__/formatters.cpython-314.pyc +0 -0
  364. package/pennyfarthing_scripts/healthscore/__pycache__/models.cpython-314.pyc +0 -0
  365. package/pennyfarthing_scripts/healthscore/analyze.py +591 -0
  366. package/pennyfarthing_scripts/healthscore/cli.py +80 -0
  367. package/pennyfarthing_scripts/healthscore/formatters.py +46 -0
  368. package/pennyfarthing_scripts/healthscore/models.py +43 -0
  369. package/pennyfarthing_scripts/hooks/cyclist-pretooluse-hook.sh +0 -0
  370. package/pennyfarthing_scripts/hooks.py +8 -11
  371. package/pennyfarthing_scripts/hotspots/__init__.py +6 -6
  372. package/pennyfarthing_scripts/hotspots/__pycache__/__init__.cpython-314.pyc +0 -0
  373. package/pennyfarthing_scripts/hotspots/__pycache__/__main__.cpython-314.pyc +0 -0
  374. package/pennyfarthing_scripts/hotspots/__pycache__/analyze.cpython-314.pyc +0 -0
  375. package/pennyfarthing_scripts/hotspots/__pycache__/cli.cpython-314.pyc +0 -0
  376. package/pennyfarthing_scripts/hotspots/__pycache__/formatters.cpython-314.pyc +0 -0
  377. package/pennyfarthing_scripts/hotspots/__pycache__/models.cpython-314.pyc +0 -0
  378. package/pennyfarthing_scripts/hotspots/analyze.py +155 -14
  379. package/pennyfarthing_scripts/hotspots/cli.py +12 -10
  380. package/pennyfarthing_scripts/hotspots/models.py +0 -1
  381. package/pennyfarthing_scripts/jira/__init__.py +15 -17
  382. package/pennyfarthing_scripts/jira/__pycache__/__init__.cpython-314.pyc +0 -0
  383. package/pennyfarthing_scripts/jira/__pycache__/__main__.cpython-314.pyc +0 -0
  384. package/pennyfarthing_scripts/jira/__pycache__/bidirectional.cpython-314.pyc +0 -0
  385. package/pennyfarthing_scripts/jira/__pycache__/claim.cpython-314.pyc +0 -0
  386. package/pennyfarthing_scripts/jira/__pycache__/cli.cpython-314.pyc +0 -0
  387. package/pennyfarthing_scripts/jira/__pycache__/client.cpython-314.pyc +0 -0
  388. package/pennyfarthing_scripts/jira/__pycache__/create.cpython-314.pyc +0 -0
  389. package/pennyfarthing_scripts/jira/__pycache__/epic.cpython-314.pyc +0 -0
  390. package/pennyfarthing_scripts/jira/__pycache__/operations.cpython-314.pyc +0 -0
  391. package/pennyfarthing_scripts/jira/__pycache__/reconcile.cpython-314.pyc +0 -0
  392. package/pennyfarthing_scripts/jira/__pycache__/story.cpython-314.pyc +0 -0
  393. package/pennyfarthing_scripts/jira/__pycache__/sync.cpython-314.pyc +0 -0
  394. package/pennyfarthing_scripts/jira/bidirectional.py +44 -18
  395. package/pennyfarthing_scripts/jira/claim.py +21 -0
  396. package/pennyfarthing_scripts/jira/cli.py +6 -3
  397. package/pennyfarthing_scripts/jira/client.py +32 -4
  398. package/pennyfarthing_scripts/jira/create.py +45 -1
  399. package/pennyfarthing_scripts/jira/epic.py +3 -2
  400. package/pennyfarthing_scripts/jira/reconcile.py +0 -1
  401. package/pennyfarthing_scripts/jira/story.py +2 -0
  402. package/pennyfarthing_scripts/jira/sync.py +1 -1
  403. package/pennyfarthing_scripts/migration/__pycache__/__init__.cpython-314.pyc +0 -0
  404. package/pennyfarthing_scripts/migration/__pycache__/session.cpython-314.pyc +0 -0
  405. package/pennyfarthing_scripts/migration/__pycache__/skill.cpython-314.pyc +0 -0
  406. package/pennyfarthing_scripts/migration/__pycache__/step.cpython-314.pyc +0 -0
  407. package/pennyfarthing_scripts/migration/__pycache__/validate.cpython-314.pyc +0 -0
  408. package/pennyfarthing_scripts/migration/skill.py +0 -1
  409. package/pennyfarthing_scripts/migration/step.py +0 -1
  410. package/pennyfarthing_scripts/migration/validate.py +8 -5
  411. package/pennyfarthing_scripts/patch_mode.py +2 -2
  412. package/pennyfarthing_scripts/preflight/__init__.py +1 -1
  413. package/pennyfarthing_scripts/preflight/__pycache__/__init__.cpython-314.pyc +0 -0
  414. package/pennyfarthing_scripts/preflight/__pycache__/__main__.cpython-314.pyc +0 -0
  415. package/pennyfarthing_scripts/preflight/__pycache__/cli.cpython-314.pyc +0 -0
  416. package/pennyfarthing_scripts/preflight/__pycache__/finish.cpython-314.pyc +0 -0
  417. package/pennyfarthing_scripts/preflight/finish.py +0 -1
  418. package/pennyfarthing_scripts/pretooluse_hook.py +6 -7
  419. package/pennyfarthing_scripts/prime/__pycache__/__init__.cpython-314.pyc +0 -0
  420. package/pennyfarthing_scripts/prime/__pycache__/cli.cpython-314.pyc +0 -0
  421. package/pennyfarthing_scripts/prime/__pycache__/loader.cpython-314.pyc +0 -0
  422. package/pennyfarthing_scripts/prime/__pycache__/models.cpython-314.pyc +0 -0
  423. package/pennyfarthing_scripts/prime/__pycache__/persona.cpython-314.pyc +0 -0
  424. package/pennyfarthing_scripts/prime/__pycache__/session.cpython-314.pyc +0 -0
  425. package/pennyfarthing_scripts/prime/__pycache__/tiers.cpython-314.pyc +0 -0
  426. package/pennyfarthing_scripts/prime/__pycache__/workflow.cpython-314.pyc +0 -0
  427. package/pennyfarthing_scripts/prime/cli.py +5 -1
  428. package/pennyfarthing_scripts/prime/loader.py +2 -3
  429. package/pennyfarthing_scripts/prime/persona.py +2 -1
  430. package/pennyfarthing_scripts/prime/tiers.py +4 -4
  431. package/pennyfarthing_scripts/schema_validation_hook.py +2 -3
  432. package/pennyfarthing_scripts/sprint/__init__.py +10 -12
  433. package/pennyfarthing_scripts/sprint/__main__.py +2 -2
  434. package/pennyfarthing_scripts/sprint/__pycache__/__init__.cpython-314.pyc +0 -0
  435. package/pennyfarthing_scripts/sprint/__pycache__/__main__.cpython-314.pyc +0 -0
  436. package/pennyfarthing_scripts/sprint/__pycache__/archive.cpython-314.pyc +0 -0
  437. package/pennyfarthing_scripts/sprint/__pycache__/archive_epic.cpython-314.pyc +0 -0
  438. package/pennyfarthing_scripts/sprint/__pycache__/cli.cpython-314.pyc +0 -0
  439. package/pennyfarthing_scripts/sprint/__pycache__/epic_add.cpython-314.pyc +0 -0
  440. package/pennyfarthing_scripts/sprint/__pycache__/import_epic.cpython-314.pyc +0 -0
  441. package/pennyfarthing_scripts/sprint/__pycache__/loader.cpython-314.pyc +0 -0
  442. package/pennyfarthing_scripts/sprint/__pycache__/status.cpython-314.pyc +0 -0
  443. package/pennyfarthing_scripts/sprint/__pycache__/story_add.cpython-314.pyc +0 -0
  444. package/pennyfarthing_scripts/sprint/__pycache__/story_finish.cpython-314.pyc +0 -0
  445. package/pennyfarthing_scripts/sprint/__pycache__/story_update.cpython-314.pyc +0 -0
  446. package/pennyfarthing_scripts/sprint/__pycache__/validate_cmd.cpython-314.pyc +0 -0
  447. package/pennyfarthing_scripts/sprint/__pycache__/validator.cpython-314.pyc +0 -0
  448. package/pennyfarthing_scripts/sprint/__pycache__/work.cpython-314.pyc +0 -0
  449. package/pennyfarthing_scripts/sprint/__pycache__/yaml_io.cpython-314.pyc +0 -0
  450. package/pennyfarthing_scripts/sprint/archive.py +0 -1
  451. package/pennyfarthing_scripts/sprint/archive_epic.py +198 -97
  452. package/pennyfarthing_scripts/sprint/cli.py +62 -46
  453. package/pennyfarthing_scripts/sprint/epic_add.py +8 -1
  454. package/pennyfarthing_scripts/sprint/import_epic.py +42 -18
  455. package/pennyfarthing_scripts/sprint/loader.py +6 -0
  456. package/pennyfarthing_scripts/sprint/status.py +1 -2
  457. package/pennyfarthing_scripts/sprint/story_add.py +202 -27
  458. package/pennyfarthing_scripts/sprint/story_finish.py +209 -0
  459. package/pennyfarthing_scripts/sprint/story_update.py +11 -3
  460. package/pennyfarthing_scripts/sprint/validate_cmd.py +0 -1
  461. package/pennyfarthing_scripts/sprint/validator.py +120 -6
  462. package/pennyfarthing_scripts/sprint/work.py +28 -7
  463. package/pennyfarthing_scripts/sprint/yaml_io.py +10 -2
  464. package/pennyfarthing_scripts/story/__init__.py +14 -16
  465. package/pennyfarthing_scripts/story/__pycache__/__init__.cpython-314.pyc +0 -0
  466. package/pennyfarthing_scripts/story/__pycache__/__main__.cpython-314.pyc +0 -0
  467. package/pennyfarthing_scripts/story/__pycache__/cli.cpython-314.pyc +0 -0
  468. package/pennyfarthing_scripts/story/__pycache__/create.cpython-314.pyc +0 -0
  469. package/pennyfarthing_scripts/story/__pycache__/size.cpython-314.pyc +0 -0
  470. package/pennyfarthing_scripts/story/__pycache__/template.cpython-314.pyc +0 -0
  471. package/pennyfarthing_scripts/story/size.py +0 -1
  472. package/pennyfarthing_scripts/story/template.py +0 -1
  473. package/pennyfarthing_scripts/swebench.py +1 -2
  474. package/pennyfarthing_scripts/tests/__pycache__/__init__.cpython-314.pyc +0 -0
  475. package/pennyfarthing_scripts/tests/__pycache__/conftest.cpython-314-pytest-9.0.2.pyc +0 -0
  476. package/pennyfarthing_scripts/tests/__pycache__/test_brownfield.cpython-314-pytest-9.0.2.pyc +0 -0
  477. package/pennyfarthing_scripts/tests/__pycache__/test_cli_modules.cpython-314-pytest-9.0.2.pyc +0 -0
  478. package/pennyfarthing_scripts/tests/__pycache__/test_codemarkers.cpython-314-pytest-9.0.2.pyc +0 -0
  479. package/pennyfarthing_scripts/tests/__pycache__/test_common.cpython-314-pytest-9.0.2.pyc +0 -0
  480. package/pennyfarthing_scripts/tests/__pycache__/test_git_utils.cpython-314-pytest-9.0.2.pyc +0 -0
  481. package/pennyfarthing_scripts/tests/__pycache__/test_healthscore.cpython-314-pytest-9.0.2.pyc +0 -0
  482. package/pennyfarthing_scripts/tests/__pycache__/test_jira_package.cpython-314-pytest-9.0.2.pyc +0 -0
  483. package/pennyfarthing_scripts/tests/__pycache__/test_package_structure.cpython-314-pytest-9.0.2.pyc +0 -0
  484. package/pennyfarthing_scripts/tests/__pycache__/test_patch_mode.cpython-314-pytest-9.0.2.pyc +0 -0
  485. package/pennyfarthing_scripts/tests/__pycache__/test_prime.cpython-314-pytest-9.0.2.pyc +0 -0
  486. package/pennyfarthing_scripts/tests/__pycache__/test_sprint_package.cpython-314-pytest-9.0.2.pyc +0 -0
  487. package/pennyfarthing_scripts/tests/__pycache__/test_sprint_validator.cpython-314-pytest-9.0.2.pyc +0 -0
  488. package/pennyfarthing_scripts/tests/__pycache__/test_story_add.cpython-314-pytest-9.0.2.pyc +0 -0
  489. package/pennyfarthing_scripts/tests/__pycache__/test_story_package.cpython-314-pytest-9.0.2.pyc +0 -0
  490. package/pennyfarthing_scripts/tests/__pycache__/test_story_update.cpython-314-pytest-9.0.2.pyc +0 -0
  491. package/pennyfarthing_scripts/tests/__pycache__/test_tiers.cpython-314-pytest-9.0.2.pyc +0 -0
  492. package/pennyfarthing_scripts/tests/__pycache__/test_token_counting.cpython-314-pytest-9.0.2.pyc +0 -0
  493. package/pennyfarthing_scripts/tests/__pycache__/test_validate_cmd.cpython-314-pytest-9.0.2.pyc +0 -0
  494. package/pennyfarthing_scripts/tests/__pycache__/test_workflow_check.cpython-314-pytest-9.0.2.pyc +0 -0
  495. package/pennyfarthing_scripts/tests/__pycache__/test_yaml_io.cpython-314-pytest-9.0.2.pyc +0 -0
  496. package/pennyfarthing_scripts/tests/conftest.py +1 -2
  497. package/pennyfarthing_scripts/tests/test_brownfield.py +10 -13
  498. package/pennyfarthing_scripts/tests/test_cli_modules.py +0 -4
  499. package/pennyfarthing_scripts/tests/test_codemarkers.py +687 -0
  500. package/pennyfarthing_scripts/tests/test_common.py +9 -4
  501. package/pennyfarthing_scripts/tests/test_epic_shard_validation.py +699 -0
  502. package/pennyfarthing_scripts/tests/test_git_utils.py +10 -13
  503. package/pennyfarthing_scripts/tests/test_healthscore.py +516 -0
  504. package/pennyfarthing_scripts/tests/test_jira_package.py +0 -3
  505. package/pennyfarthing_scripts/tests/test_package_structure.py +3 -16
  506. package/pennyfarthing_scripts/tests/test_patch_mode.py +7 -11
  507. package/pennyfarthing_scripts/tests/test_prime.py +39 -21
  508. package/pennyfarthing_scripts/tests/test_sprint_package.py +3 -8
  509. package/pennyfarthing_scripts/tests/test_sprint_validator.py +53 -5
  510. package/pennyfarthing_scripts/tests/test_story_add.py +3 -7
  511. package/pennyfarthing_scripts/tests/test_story_package.py +0 -3
  512. package/pennyfarthing_scripts/tests/test_story_update.py +5 -10
  513. package/pennyfarthing_scripts/tests/test_tiers.py +18 -17
  514. package/pennyfarthing_scripts/tests/test_token_counting.py +19 -13
  515. package/pennyfarthing_scripts/tests/test_validate_cmd.py +2 -7
  516. package/pennyfarthing_scripts/tests/test_workflow_check.py +0 -2
  517. package/pennyfarthing_scripts/tests/test_yaml_io.py +0 -3
  518. package/pennyfarthing_scripts/theme/__init__.py +5 -0
  519. package/pennyfarthing_scripts/theme/__main__.py +6 -0
  520. package/pennyfarthing_scripts/theme/__pycache__/__init__.cpython-314.pyc +0 -0
  521. package/pennyfarthing_scripts/theme/__pycache__/cli.cpython-314.pyc +0 -0
  522. package/pennyfarthing_scripts/theme/cli.py +287 -0
  523. package/pennyfarthing_scripts/validate/__init__.py +21 -0
  524. package/pennyfarthing_scripts/validate/__pycache__/__init__.cpython-314.pyc +0 -0
  525. package/pennyfarthing_scripts/validate/__pycache__/cli.cpython-314.pyc +0 -0
  526. package/pennyfarthing_scripts/validate/adapters/__init__.py +0 -0
  527. package/pennyfarthing_scripts/validate/adapters/__pycache__/__init__.cpython-314.pyc +0 -0
  528. package/pennyfarthing_scripts/validate/adapters/__pycache__/agent.cpython-314.pyc +0 -0
  529. package/pennyfarthing_scripts/validate/adapters/__pycache__/schema.cpython-314.pyc +0 -0
  530. package/pennyfarthing_scripts/validate/adapters/__pycache__/skill_command.cpython-314.pyc +0 -0
  531. package/pennyfarthing_scripts/validate/adapters/__pycache__/sprint.cpython-314.pyc +0 -0
  532. package/pennyfarthing_scripts/validate/adapters/__pycache__/workflow.cpython-314.pyc +0 -0
  533. package/pennyfarthing_scripts/validate/adapters/agent.py +239 -0
  534. package/pennyfarthing_scripts/validate/adapters/schema.py +30 -0
  535. package/pennyfarthing_scripts/validate/adapters/skill_command.py +292 -0
  536. package/pennyfarthing_scripts/validate/adapters/sprint.py +69 -0
  537. package/pennyfarthing_scripts/validate/adapters/workflow.py +320 -0
  538. package/pennyfarthing_scripts/validate/cli.py +141 -0
  539. package/pennyfarthing_scripts/welcome_hook.py +2 -3
  540. package/pennyfarthing_scripts/workflow.py +3 -3
  541. package/scripts/README.md +41 -0
  542. package/pennyfarthing-dist/agents/workflow-status-check.md +0 -96
  543. package/pennyfarthing-dist/commands/benchmark-control.md +0 -69
  544. package/pennyfarthing-dist/commands/benchmark.md +0 -485
  545. package/pennyfarthing-dist/commands/job-fair.md +0 -102
  546. package/pennyfarthing-dist/commands/solo.md +0 -447
  547. package/pennyfarthing-dist/guides/benchmarks.md +0 -62
  548. package/pennyfarthing-dist/scripts/hooks/__pycache__/question_reflector_check.cpython-314.pyc +0 -0
  549. package/pennyfarthing-dist/scripts/test/ensure-swebench-data.sh +0 -59
  550. package/pennyfarthing-dist/scripts/test/ground-truth-judge.py +0 -220
  551. package/pennyfarthing-dist/scripts/test/swebench-judge.py +0 -374
  552. package/pennyfarthing-dist/scripts/test/test-cache.sh +0 -165
  553. package/pennyfarthing-dist/scripts/test/test-setup.sh +0 -337
  554. package/pennyfarthing-dist/scripts/theme/compute-theme-tiers.sh +0 -13
  555. package/pennyfarthing-dist/scripts/theme/compute_theme_tiers.py +0 -402
  556. package/pennyfarthing-dist/scripts/theme/update-theme-tiers.sh +0 -97
  557. package/pennyfarthing-dist/skills/finalize-run/SKILL.md +0 -261
  558. package/pennyfarthing-dist/skills/judge/SKILL.md +0 -644
  559. package/pennyfarthing-dist/skills/persona-benchmark/SKILL.md +0 -187
  560. package/pennyfarthing-dist/workflows/dev-story/checklist.md +0 -80
  561. package/pennyfarthing-dist/workflows/dev-story/instructions.xml +0 -410
  562. package/pennyfarthing-dist/workflows/dev-story/workflow.yaml +0 -50
  563. package/pennyfarthing-dist/workflows/quick-spec/steps/step-01-understand.md +0 -201
  564. package/pennyfarthing-dist/workflows/quick-spec/steps/step-02-investigate.md +0 -156
  565. package/pennyfarthing-dist/workflows/quick-spec/steps/step-03-generate.md +0 -140
  566. package/pennyfarthing-dist/workflows/quick-spec/steps/step-04-review.md +0 -203
  567. package/pennyfarthing-dist/workflows/quick-spec/tech-spec-template.md +0 -74
  568. package/pennyfarthing-dist/workflows/quick-spec/workflow.yaml +0 -27
  569. package/pennyfarthing_scripts/__pycache__/__init__.cpython-311.pyc +0 -0
  570. package/pennyfarthing_scripts/__pycache__/jira.cpython-314.pyc +0 -0
  571. package/pennyfarthing_scripts/__pycache__/pretooluse_hook.cpython-314.pyc +0 -0
  572. package/pennyfarthing_scripts/__pycache__/sprint.cpython-314.pyc +0 -0
  573. package/pennyfarthing_scripts/__pycache__/workflow.cpython-311.pyc +0 -0
  574. package/pennyfarthing_scripts/jira/__pycache__/compat.cpython-314.pyc +0 -0
  575. package/pennyfarthing_scripts/jira/__pycache__/mappings.cpython-314.pyc +0 -0
  576. package/pennyfarthing_scripts/jira/__pycache__/models.cpython-314.pyc +0 -0
  577. package/pennyfarthing_scripts/migration/__pycache__/__main__.cpython-314.pyc +0 -0
  578. package/pennyfarthing_scripts/migration/__pycache__/cli.cpython-314.pyc +0 -0
  579. package/pennyfarthing_scripts/prime/__pycache__/__main__.cpython-314.pyc +0 -0
  580. package/pennyfarthing_scripts/tests/__pycache__/test_workflow_cli.cpython-314-pytest-9.0.2.pyc +0 -0
@@ -16,21 +16,11 @@ Acceptance Criteria:
16
16
  """
17
17
 
18
18
  import asyncio
19
- import tempfile
20
19
  from pathlib import Path
21
- from typing import Any
22
- from unittest.mock import AsyncMock, MagicMock, patch
20
+ from unittest.mock import AsyncMock, patch
23
21
 
24
22
  import pytest
25
23
 
26
- from pennyfarthing_scripts.git.status_all import (
27
- RepoStatus,
28
- format_status_brief,
29
- format_status_full,
30
- format_summary,
31
- get_all_repo_status,
32
- get_repo_status,
33
- )
34
24
  from pennyfarthing_scripts.git.create_branches import (
35
25
  BranchAction,
36
26
  BranchResult,
@@ -40,7 +30,14 @@ from pennyfarthing_scripts.git.create_branches import (
40
30
  filter_repos,
41
31
  format_results,
42
32
  )
43
-
33
+ from pennyfarthing_scripts.git.status_all import (
34
+ RepoStatus,
35
+ format_status_brief,
36
+ format_status_full,
37
+ format_summary,
38
+ get_all_repo_status,
39
+ get_repo_status,
40
+ )
44
41
 
45
42
  # =============================================================================
46
43
  # Test Fixtures
@@ -793,8 +790,8 @@ class TestCrossPlatformCompatibility:
793
790
  def test_no_shell_specific_commands(self) -> None:
794
791
  """Implementation should not use shell-specific commands."""
795
792
  # This is more of a code review check, but we can verify the modules exist
796
- import pennyfarthing_scripts.git.status_all as status_mod
797
793
  import pennyfarthing_scripts.git.create_branches as branch_mod
794
+ import pennyfarthing_scripts.git.status_all as status_mod
798
795
 
799
796
  # Modules should exist and be importable
800
797
  assert status_mod is not None
@@ -0,0 +1,516 @@
1
+ """
2
+ Tests for the healthscore module.
3
+
4
+ Covers all acceptance criteria for MSSCI-14470:
5
+ AC1: Module structure (cli.py, models.py, analyze.py, formatters.py)
6
+ AC2: Weighted scoring algorithm with 8 configurable dimensions
7
+ AC3: Each dimension 0-100, composite is weighted average 0-100
8
+ AC4: CLI command: pf healthscore analyze [--format table|json|csv] [--path DIR]
9
+ AC5: Result caching within 5-minute window
10
+ AC6: Cache stored in .pennyfarthing/.cache/healthscore/
11
+ AC7: ADR-0008 result pattern
12
+ AC8: Registered in main CLI
13
+ AC9: Tests cover scoring algorithm, caching, and CLI output
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import asyncio
19
+ import json
20
+ from dataclasses import asdict
21
+ from pathlib import Path
22
+ from unittest.mock import patch
23
+
24
+ from click.testing import CliRunner
25
+
26
+ from pennyfarthing_scripts.healthscore.analyze import (
27
+ analyze_healthscore,
28
+ compute_composite_score,
29
+ get_cache_path,
30
+ read_cached_score,
31
+ write_cached_score,
32
+ )
33
+ from pennyfarthing_scripts.healthscore.cli import healthscore
34
+ from pennyfarthing_scripts.healthscore.models import (
35
+ DEFAULT_WEIGHTS,
36
+ DimensionScore,
37
+ HealthscoreResult,
38
+ )
39
+
40
+ # ---------------------------------------------------------------------------
41
+ # AC1: Module structure
42
+ # ---------------------------------------------------------------------------
43
+
44
+ class TestModuleStructure:
45
+ """AC1: New module at pennyfarthing_scripts/healthscore/ with standard files."""
46
+
47
+ def test_package_has_init(self):
48
+ """Module must be importable as a package."""
49
+ import pennyfarthing_scripts.healthscore
50
+ assert hasattr(pennyfarthing_scripts.healthscore, "HealthscoreResult")
51
+ assert hasattr(pennyfarthing_scripts.healthscore, "DimensionScore")
52
+ assert hasattr(pennyfarthing_scripts.healthscore, "DEFAULT_WEIGHTS")
53
+ assert hasattr(pennyfarthing_scripts.healthscore, "analyze_healthscore")
54
+
55
+ def test_models_module_exists(self):
56
+ """models.py must define DimensionScore and HealthscoreResult."""
57
+ from pennyfarthing_scripts.healthscore import models
58
+ assert hasattr(models, "DimensionScore")
59
+ assert hasattr(models, "HealthscoreResult")
60
+
61
+ def test_analyze_module_exists(self):
62
+ """analyze.py must define analyze_healthscore."""
63
+ from pennyfarthing_scripts.healthscore import analyze
64
+ assert hasattr(analyze, "analyze_healthscore")
65
+
66
+ def test_cli_module_exists(self):
67
+ """cli.py must define the click group."""
68
+ from pennyfarthing_scripts.healthscore import cli
69
+ assert hasattr(cli, "healthscore")
70
+
71
+ def test_formatters_module_exists(self):
72
+ """formatters.py must define table/json/csv formatters."""
73
+ from pennyfarthing_scripts.healthscore import formatters
74
+ assert hasattr(formatters, "format_table")
75
+ assert hasattr(formatters, "export_json")
76
+ assert hasattr(formatters, "export_csv")
77
+
78
+
79
+ # ---------------------------------------------------------------------------
80
+ # AC2: Weighted scoring algorithm with 8 configurable dimensions
81
+ # ---------------------------------------------------------------------------
82
+
83
+ class TestWeightedScoring:
84
+ """AC2: 8 dimensions with configurable weights."""
85
+
86
+ def test_default_weights_has_8_dimensions(self):
87
+ """Must define exactly 8 dimensions."""
88
+ assert len(DEFAULT_WEIGHTS) == 8
89
+
90
+ def test_default_weights_sum_to_one(self):
91
+ """Default weights must sum to 1.0."""
92
+ total = sum(DEFAULT_WEIGHTS.values())
93
+ assert abs(total - 1.0) < 1e-9, f"Weights sum to {total}, expected 1.0"
94
+
95
+ def test_default_weight_keys(self):
96
+ """Must include all 8 named dimensions."""
97
+ expected = {
98
+ "churn", "todo_density", "complexity", "test_gaps",
99
+ "dead_code", "deprecation_debt", "dependency_freshness",
100
+ "agent_context_efficiency",
101
+ }
102
+ assert set(DEFAULT_WEIGHTS.keys()) == expected
103
+
104
+ def test_default_weight_values(self):
105
+ """Default weights must match spec."""
106
+ assert DEFAULT_WEIGHTS["churn"] == 0.15
107
+ assert DEFAULT_WEIGHTS["todo_density"] == 0.15
108
+ assert DEFAULT_WEIGHTS["complexity"] == 0.15
109
+ assert DEFAULT_WEIGHTS["test_gaps"] == 0.15
110
+ assert DEFAULT_WEIGHTS["dead_code"] == 0.10
111
+ assert DEFAULT_WEIGHTS["deprecation_debt"] == 0.10
112
+ assert DEFAULT_WEIGHTS["dependency_freshness"] == 0.10
113
+ assert DEFAULT_WEIGHTS["agent_context_efficiency"] == 0.10
114
+
115
+ def test_custom_weights_override_defaults(self):
116
+ """compute_composite_score must accept custom weights."""
117
+ custom = dict.fromkeys(DEFAULT_WEIGHTS, 1.0 / 8)
118
+ scores = dict.fromkeys(DEFAULT_WEIGHTS, 80.0)
119
+ result = compute_composite_score(scores, custom)
120
+ assert abs(result - 80.0) < 1e-9
121
+
122
+ def test_unequal_custom_weights(self):
123
+ """Asymmetric weights should shift composite score."""
124
+ weights = dict.fromkeys(DEFAULT_WEIGHTS, 0.0)
125
+ weights["churn"] = 1.0 # All weight on churn
126
+
127
+ scores = dict.fromkeys(DEFAULT_WEIGHTS, 50.0)
128
+ scores["churn"] = 100.0
129
+
130
+ result = compute_composite_score(scores, weights)
131
+ assert abs(result - 100.0) < 1e-9
132
+
133
+
134
+ # ---------------------------------------------------------------------------
135
+ # AC3: Each dimension 0-100, composite is weighted average 0-100
136
+ # ---------------------------------------------------------------------------
137
+
138
+ class TestScoreRanges:
139
+ """AC3: Dimension scores 0-100, composite 0-100."""
140
+
141
+ def test_all_zeros_yields_zero(self):
142
+ """All dimensions at 0 → composite 0."""
143
+ scores = dict.fromkeys(DEFAULT_WEIGHTS, 0.0)
144
+ result = compute_composite_score(scores, DEFAULT_WEIGHTS)
145
+ assert result == 0.0
146
+
147
+ def test_all_hundreds_yields_hundred(self):
148
+ """All dimensions at 100 → composite 100."""
149
+ scores = dict.fromkeys(DEFAULT_WEIGHTS, 100.0)
150
+ result = compute_composite_score(scores, DEFAULT_WEIGHTS)
151
+ assert abs(result - 100.0) < 1e-9
152
+
153
+ def test_mixed_scores_weighted_average(self):
154
+ """Mixed scores should produce correct weighted average."""
155
+ scores = {
156
+ "churn": 80.0,
157
+ "todo_density": 60.0,
158
+ "complexity": 70.0,
159
+ "test_gaps": 50.0,
160
+ "dead_code": 90.0,
161
+ "deprecation_debt": 40.0,
162
+ "dependency_freshness": 85.0,
163
+ "agent_context_efficiency": 75.0,
164
+ }
165
+ expected = sum(scores[k] * DEFAULT_WEIGHTS[k] for k in DEFAULT_WEIGHTS)
166
+ result = compute_composite_score(scores, DEFAULT_WEIGHTS)
167
+ assert abs(result - expected) < 1e-9
168
+
169
+ def test_none_dimensions_excluded_and_renormalized(self):
170
+ """Unavailable dimensions (None) are excluded; remaining weights renormalize."""
171
+ scores = dict.fromkeys(DEFAULT_WEIGHTS)
172
+ scores["churn"] = 80.0
173
+ scores["complexity"] = 60.0
174
+ # Only churn (0.15) and complexity (0.15) available → renorm to 0.5 each
175
+ expected = (80.0 * 0.5) + (60.0 * 0.5)
176
+ result = compute_composite_score(scores, DEFAULT_WEIGHTS)
177
+ assert abs(result - expected) < 1e-9
178
+
179
+ def test_all_none_dimensions_returns_zero(self):
180
+ """All dimensions unavailable → composite 0."""
181
+ scores = dict.fromkeys(DEFAULT_WEIGHTS)
182
+ result = compute_composite_score(scores, DEFAULT_WEIGHTS)
183
+ assert result == 0.0
184
+
185
+ def test_composite_score_in_result_object(self):
186
+ """HealthscoreResult.composite_score should reflect computed value."""
187
+ result = HealthscoreResult(
188
+ success=True,
189
+ composite_score=73.5,
190
+ target_path="/tmp/project",
191
+ dimensions=[
192
+ DimensionScore(name="churn", score=80.0, weight=0.15),
193
+ DimensionScore(name="complexity", score=67.0, weight=0.15),
194
+ ],
195
+ )
196
+ assert 0.0 <= result.composite_score <= 100.0
197
+
198
+
199
+ # ---------------------------------------------------------------------------
200
+ # AC4: CLI command
201
+ # ---------------------------------------------------------------------------
202
+
203
+ class TestCLI:
204
+ """AC4: pf healthscore analyze [--format table|json|csv] [--path DIR]."""
205
+
206
+ def test_help_shows_analyze_command(self):
207
+ """CLI group must expose the analyze subcommand."""
208
+ runner = CliRunner()
209
+ result = runner.invoke(healthscore, ["--help"])
210
+ assert result.exit_code == 0
211
+ assert "analyze" in result.output
212
+
213
+ def test_analyze_help_shows_options(self):
214
+ """analyze command must show --format, --path, --output, --no-cache."""
215
+ runner = CliRunner()
216
+ result = runner.invoke(healthscore, ["analyze", "--help"])
217
+ assert result.exit_code == 0
218
+ assert "--format" in result.output
219
+ assert "--path" in result.output
220
+ assert "--output" in result.output
221
+ assert "--no-cache" in result.output
222
+
223
+ def test_analyze_format_choices(self):
224
+ """--format must accept table, json, csv."""
225
+ runner = CliRunner()
226
+ result = runner.invoke(healthscore, ["analyze", "--help"])
227
+ assert "table" in result.output
228
+ assert "json" in result.output
229
+ assert "csv" in result.output
230
+
231
+ def test_analyze_json_output_is_valid_json(self):
232
+ """--format json must produce parseable JSON."""
233
+ mock_result = HealthscoreResult(
234
+ success=True,
235
+ composite_score=72.5,
236
+ target_path="/tmp/project",
237
+ dimensions=[
238
+ DimensionScore(name="churn", score=80.0, weight=0.15),
239
+ ],
240
+ )
241
+ with patch(
242
+ "pennyfarthing_scripts.healthscore.cli._run_analysis",
243
+ return_value=mock_result,
244
+ ):
245
+ runner = CliRunner()
246
+ result = runner.invoke(healthscore, ["analyze", "--format", "json"])
247
+ assert result.exit_code == 0
248
+ data = json.loads(result.output)
249
+ assert data["success"] is True
250
+ assert data["composite_score"] == 72.5
251
+
252
+ def test_analyze_table_output_has_score_header(self):
253
+ """Table output must include score and dimension labels."""
254
+ mock_result = HealthscoreResult(
255
+ success=True,
256
+ composite_score=72.5,
257
+ target_path="/tmp/project",
258
+ dimensions=[
259
+ DimensionScore(name="churn", score=80.0, weight=0.15),
260
+ DimensionScore(name="complexity", score=65.0, weight=0.15),
261
+ ],
262
+ )
263
+ with patch(
264
+ "pennyfarthing_scripts.healthscore.cli._run_analysis",
265
+ return_value=mock_result,
266
+ ):
267
+ runner = CliRunner()
268
+ result = runner.invoke(healthscore, ["analyze", "--format", "table"])
269
+ assert result.exit_code == 0
270
+ assert "churn" in result.output.lower() or "Churn" in result.output
271
+
272
+ def test_analyze_csv_output_has_header_row(self):
273
+ """CSV output must include header row with dimension names."""
274
+ mock_result = HealthscoreResult(
275
+ success=True,
276
+ composite_score=72.5,
277
+ target_path="/tmp/project",
278
+ dimensions=[
279
+ DimensionScore(name="churn", score=80.0, weight=0.15),
280
+ ],
281
+ )
282
+ with patch(
283
+ "pennyfarthing_scripts.healthscore.cli._run_analysis",
284
+ return_value=mock_result,
285
+ ):
286
+ runner = CliRunner()
287
+ result = runner.invoke(healthscore, ["analyze", "--format", "csv"])
288
+ assert result.exit_code == 0
289
+ lines = result.output.strip().split("\n")
290
+ assert len(lines) >= 2 # header + at least one data row
291
+
292
+ def test_analyze_output_to_file(self, tmp_path):
293
+ """--output must write result to file."""
294
+ mock_result = HealthscoreResult(
295
+ success=True,
296
+ composite_score=72.5,
297
+ target_path="/tmp/project",
298
+ dimensions=[],
299
+ )
300
+ output_file = tmp_path / "result.json"
301
+ with patch(
302
+ "pennyfarthing_scripts.healthscore.cli._run_analysis",
303
+ return_value=mock_result,
304
+ ):
305
+ runner = CliRunner()
306
+ result = runner.invoke(healthscore, [
307
+ "analyze", "--format", "json", "--output", str(output_file)
308
+ ])
309
+ assert result.exit_code == 0
310
+ assert output_file.exists()
311
+ data = json.loads(output_file.read_text())
312
+ assert data["success"] is True
313
+
314
+
315
+ # ---------------------------------------------------------------------------
316
+ # AC5: Result caching within 5-minute window
317
+ # ---------------------------------------------------------------------------
318
+
319
+ class TestCaching:
320
+ """AC5: Component scores cached, reused within 5-minute window."""
321
+
322
+ def test_write_then_read_cached_score(self, tmp_path):
323
+ """Written score must be readable back."""
324
+ write_cached_score(tmp_path, "churn", 85.0)
325
+ result = read_cached_score(tmp_path, "churn", ttl=300)
326
+ assert result == 85.0
327
+
328
+ def test_cache_miss_returns_none(self, tmp_path):
329
+ """Reading a non-existent cache entry returns None."""
330
+ result = read_cached_score(tmp_path, "nonexistent", ttl=300)
331
+ assert result is None
332
+
333
+ def test_expired_cache_returns_none(self, tmp_path):
334
+ """Cache entry older than TTL returns None."""
335
+ write_cached_score(tmp_path, "churn", 85.0)
336
+ # Read with 0 TTL → always expired
337
+ result = read_cached_score(tmp_path, "churn", ttl=0)
338
+ assert result is None
339
+
340
+ def test_cache_ttl_boundary(self, tmp_path):
341
+ """Cache at exactly TTL boundary should still be valid."""
342
+ write_cached_score(tmp_path, "churn", 85.0)
343
+ # Read within generous TTL
344
+ result = read_cached_score(tmp_path, "churn", ttl=300)
345
+ assert result == 85.0
346
+
347
+ def test_multiple_dimensions_cached_independently(self, tmp_path):
348
+ """Each dimension has its own cache entry."""
349
+ write_cached_score(tmp_path, "churn", 85.0)
350
+ write_cached_score(tmp_path, "complexity", 60.0)
351
+ assert read_cached_score(tmp_path, "churn", ttl=300) == 85.0
352
+ assert read_cached_score(tmp_path, "complexity", ttl=300) == 60.0
353
+
354
+ def test_overwrite_cached_score(self, tmp_path):
355
+ """Writing a new score overwrites the previous value."""
356
+ write_cached_score(tmp_path, "churn", 85.0)
357
+ write_cached_score(tmp_path, "churn", 42.0)
358
+ result = read_cached_score(tmp_path, "churn", ttl=300)
359
+ assert result == 42.0
360
+
361
+ def test_no_cache_flag_bypasses_cache(self):
362
+ """analyze_healthscore with cache_ttl=0 must not use cached results."""
363
+ # This tests that the analyze function respects cache_ttl=0
364
+ # (will fail until implementation — that's the point)
365
+ with patch(
366
+ "pennyfarthing_scripts.healthscore.analyze.read_cached_score",
367
+ return_value=99.0,
368
+ ) as mock_read:
369
+ asyncio.run(
370
+ analyze_healthscore(Path("/tmp/project"), cache_ttl=0)
371
+ )
372
+ # With ttl=0, cached values should not be used
373
+ mock_read.assert_not_called()
374
+
375
+
376
+ # ---------------------------------------------------------------------------
377
+ # AC6: Cache stored in .pennyfarthing/.cache/healthscore/
378
+ # ---------------------------------------------------------------------------
379
+
380
+ class TestCacheLocation:
381
+ """AC6: Cache files stored in .pennyfarthing/.cache/healthscore/."""
382
+
383
+ def test_cache_path_under_pennyfarthing(self, tmp_path):
384
+ """get_cache_path must return a path under .pennyfarthing/.cache/healthscore/."""
385
+ cache_dir = get_cache_path(tmp_path)
386
+ path_str = str(cache_dir)
387
+ assert ".pennyfarthing" in path_str
388
+ assert ".cache" in path_str
389
+ assert "healthscore" in path_str
390
+
391
+ def test_cache_path_includes_target_directory(self, tmp_path):
392
+ """Cache path should be specific to the target directory."""
393
+ path_a = get_cache_path(tmp_path / "project-a")
394
+ path_b = get_cache_path(tmp_path / "project-b")
395
+ # Different projects should get different cache dirs (or at least different keys)
396
+ assert path_a != path_b
397
+
398
+
399
+ # ---------------------------------------------------------------------------
400
+ # AC7: ADR-0008 result pattern
401
+ # ---------------------------------------------------------------------------
402
+
403
+ class TestADR0008Pattern:
404
+ """AC7: HealthscoreResult follows ADR-0008 pattern."""
405
+
406
+ def test_result_has_success_field(self):
407
+ """Result must have a success boolean."""
408
+ result = HealthscoreResult(success=True)
409
+ assert result.success is True
410
+
411
+ def test_result_has_error_field(self):
412
+ """Result must have an optional error field."""
413
+ result = HealthscoreResult(success=False, error="something broke")
414
+ assert result.error == "something broke"
415
+
416
+ def test_result_serializable_with_asdict(self):
417
+ """Result must be serializable via dataclasses.asdict."""
418
+ result = HealthscoreResult(
419
+ success=True,
420
+ composite_score=72.5,
421
+ target_path="/tmp/project",
422
+ dimensions=[
423
+ DimensionScore(name="churn", score=80.0, weight=0.15),
424
+ ],
425
+ )
426
+ d = asdict(result)
427
+ assert d["success"] is True
428
+ assert d["composite_score"] == 72.5
429
+ assert len(d["dimensions"]) == 1
430
+
431
+ def test_result_json_roundtrip(self):
432
+ """Result must survive JSON serialization."""
433
+ result = HealthscoreResult(
434
+ success=True,
435
+ composite_score=72.5,
436
+ target_path="/tmp/project",
437
+ dimensions=[
438
+ DimensionScore(name="churn", score=80.0, weight=0.15),
439
+ ],
440
+ )
441
+ d = asdict(result)
442
+ text = json.dumps(d, default=str)
443
+ loaded = json.loads(text)
444
+ assert loaded["success"] is True
445
+ assert loaded["composite_score"] == 72.5
446
+
447
+ def test_dimension_score_has_name_score_weight(self):
448
+ """DimensionScore must have name, score, weight, error fields."""
449
+ ds = DimensionScore(name="churn", score=80.0, weight=0.15)
450
+ assert ds.name == "churn"
451
+ assert ds.score == 80.0
452
+ assert ds.weight == 0.15
453
+ assert ds.error is None
454
+
455
+ def test_dimension_score_none_means_unavailable(self):
456
+ """DimensionScore with score=None means dimension unavailable."""
457
+ ds = DimensionScore(name="churn", score=None, weight=0.15, error="no data")
458
+ assert ds.score is None
459
+ assert ds.error == "no data"
460
+
461
+ def test_error_result(self):
462
+ """Failed result has success=False and error message."""
463
+ result = HealthscoreResult(success=False, error="target not found")
464
+ assert result.success is False
465
+ assert result.error == "target not found"
466
+ assert result.composite_score == 0.0
467
+
468
+
469
+ # ---------------------------------------------------------------------------
470
+ # AC8: Registered in main CLI
471
+ # ---------------------------------------------------------------------------
472
+
473
+ class TestMainCLIRegistration:
474
+ """AC8: healthscore command registered in pennyfarthing_scripts/cli.py."""
475
+
476
+ def test_healthscore_registered_in_main_cli(self):
477
+ """Main CLI must have a 'healthscore' command group."""
478
+ from pennyfarthing_scripts.cli import cli
479
+ command_names = list(cli.commands)
480
+ assert "healthscore" in command_names
481
+
482
+
483
+ # ---------------------------------------------------------------------------
484
+ # AC9: Full integration — analyze_healthscore returns real result
485
+ # ---------------------------------------------------------------------------
486
+
487
+ class TestAnalyzeIntegration:
488
+ """AC9: End-to-end analysis returns HealthscoreResult."""
489
+
490
+ def test_analyze_returns_healthscore_result(self):
491
+ """analyze_healthscore must return a HealthscoreResult."""
492
+ result = asyncio.run(analyze_healthscore(Path("/tmp/nonexistent")))
493
+ assert isinstance(result, HealthscoreResult)
494
+
495
+ def test_analyze_result_has_dimensions(self):
496
+ """Result must include dimension scores list."""
497
+ result = asyncio.run(analyze_healthscore(Path("/tmp/nonexistent")))
498
+ assert isinstance(result.dimensions, list)
499
+
500
+ def test_analyze_with_custom_weights(self):
501
+ """Custom weights must be accepted and applied."""
502
+ custom = dict.fromkeys(DEFAULT_WEIGHTS, 1.0 / 8)
503
+ result = asyncio.run(analyze_healthscore(Path("/tmp/nonexistent"), weights=custom))
504
+ assert isinstance(result, HealthscoreResult)
505
+
506
+ def test_analyze_result_cached_flag(self):
507
+ """Result must indicate whether values came from cache."""
508
+ result = asyncio.run(analyze_healthscore(Path("/tmp/nonexistent")))
509
+ assert isinstance(result.cached, bool)
510
+
511
+ def test_analyze_graceful_on_missing_path(self):
512
+ """Missing target path should return error result, not raise."""
513
+ result = asyncio.run(analyze_healthscore(Path("/tmp/definitely-not-a-real-path-xyz123")))
514
+ assert isinstance(result, HealthscoreResult)
515
+ # Should either succeed with degraded scores or fail gracefully
516
+ assert isinstance(result.success, bool)
@@ -7,9 +7,6 @@ after reorganization from flat modules.
7
7
  """
8
8
 
9
9
  from typing import Any
10
- from unittest.mock import MagicMock, patch
11
-
12
- import pytest
13
10
 
14
11
 
15
12
  class TestJiraClient:
@@ -10,12 +10,7 @@ These tests verify:
10
10
  5. Backwards compatibility for existing imports
11
11
  """
12
12
 
13
- import importlib
14
13
  import sys
15
- from pathlib import Path
16
- from typing import Any
17
-
18
- import pytest
19
14
 
20
15
 
21
16
  class TestCommonPackage:
@@ -126,8 +121,6 @@ class TestJiraPackage:
126
121
  from pennyfarthing_scripts.jira import (
127
122
  JiraClient,
128
123
  extract_jira_key,
129
- map_status_to_jira,
130
- map_jira_to_status,
131
124
  )
132
125
 
133
126
  assert JiraClient is not None
@@ -136,9 +129,7 @@ class TestJiraPackage:
136
129
  def test_jira_backwards_compatibility(self) -> None:
137
130
  """Old-style imports should still work."""
138
131
  # These imports should work for backwards compatibility
139
- from pennyfarthing_scripts.jira import JiraClient
140
- from pennyfarthing_scripts.jira import get_issue
141
- from pennyfarthing_scripts.jira import STATUS_TO_JIRA
132
+ from pennyfarthing_scripts.jira import STATUS_TO_JIRA, JiraClient, get_issue
142
133
 
143
134
  assert JiraClient is not None
144
135
  assert callable(get_issue)
@@ -188,10 +179,8 @@ class TestSprintPackage:
188
179
  def test_sprint_package_reexports(self) -> None:
189
180
  """sprint/__init__.py should re-export commonly used items."""
190
181
  from pennyfarthing_scripts.sprint import (
191
- load_sprint,
192
182
  find_epic,
193
- find_story,
194
- get_all_stories,
183
+ load_sprint,
195
184
  )
196
185
 
197
186
  assert callable(load_sprint)
@@ -199,9 +188,7 @@ class TestSprintPackage:
199
188
 
200
189
  def test_sprint_backwards_compatibility(self) -> None:
201
190
  """Old-style imports should still work."""
202
- from pennyfarthing_scripts.sprint import load_sprint
203
- from pennyfarthing_scripts.sprint import find_epic
204
- from pennyfarthing_scripts.sprint import load_current_sprint
191
+ from pennyfarthing_scripts.sprint import find_epic, load_current_sprint, load_sprint
205
192
 
206
193
  assert callable(load_sprint)
207
194
  assert callable(find_epic)
@@ -18,13 +18,9 @@ Acceptance Criteria:
18
18
  These tests should FAIL until patch_mode.py is implemented.
19
19
  """
20
20
 
21
- import json
22
- import os
23
- import subprocess
24
- import sys
25
21
  from pathlib import Path
26
- from typing import Any, Generator
27
- from unittest.mock import MagicMock, patch, call
22
+ from typing import Any
23
+ from unittest.mock import MagicMock, patch
28
24
 
29
25
  import pytest
30
26
  import yaml
@@ -32,17 +28,17 @@ import yaml
32
28
  # Import will fail until module exists - this is intentional for RED state
33
29
  try:
34
30
  from pennyfarthing_scripts.patch_mode import (
35
- PatchState,
36
31
  PatchStack,
32
+ PatchState,
33
+ create_patch_branch,
37
34
  enter_patch_mode,
38
35
  exit_patch_mode,
36
+ generate_patch_commit_message,
39
37
  get_patch_stack,
40
- create_patch_branch,
38
+ is_in_patch_mode,
39
+ log_patch_to_session,
41
40
  merge_patch_branch,
42
41
  restore_workflow_state,
43
- log_patch_to_session,
44
- generate_patch_commit_message,
45
- is_in_patch_mode,
46
42
  )
47
43
  IMPORT_SUCCESS = True
48
44
  except ImportError: