@pennyfarthing/core 10.0.5 → 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 (303) hide show
  1. package/README.md +9 -7
  2. package/package.json +15 -11
  3. package/packages/core/dist/cli/commands/doctor.d.ts.map +1 -1
  4. package/packages/core/dist/cli/commands/doctor.js +251 -4
  5. package/packages/core/dist/cli/commands/doctor.js.map +1 -1
  6. package/packages/core/dist/cli/commands/init.d.ts +7 -0
  7. package/packages/core/dist/cli/commands/init.d.ts.map +1 -1
  8. package/packages/core/dist/cli/commands/init.js +41 -8
  9. package/packages/core/dist/cli/commands/init.js.map +1 -1
  10. package/packages/core/dist/cli/commands/update.d.ts.map +1 -1
  11. package/packages/core/dist/cli/commands/update.js +26 -0
  12. package/packages/core/dist/cli/commands/update.js.map +1 -1
  13. package/packages/core/dist/cli/index.js +1 -1
  14. package/packages/core/dist/cli/index.js.map +1 -1
  15. package/packages/core/dist/cli/utils/python.d.ts +22 -0
  16. package/packages/core/dist/cli/utils/python.d.ts.map +1 -0
  17. package/packages/core/dist/cli/utils/python.js +102 -0
  18. package/packages/core/dist/cli/utils/python.js.map +1 -0
  19. package/packages/core/dist/cli/utils/settings.d.ts.map +1 -1
  20. package/packages/core/dist/cli/utils/settings.js +10 -0
  21. package/packages/core/dist/cli/utils/settings.js.map +1 -1
  22. package/pennyfarthing-dist/agents/README.md +1 -3
  23. package/pennyfarthing-dist/agents/architect.md +0 -6
  24. package/pennyfarthing-dist/agents/devops.md +0 -6
  25. package/pennyfarthing-dist/agents/orchestrator.md +0 -6
  26. package/pennyfarthing-dist/agents/pm.md +0 -6
  27. package/pennyfarthing-dist/agents/sm.md +0 -6
  28. package/pennyfarthing-dist/commands/architect.md +11 -3
  29. package/pennyfarthing-dist/commands/close-epic.md +24 -131
  30. package/pennyfarthing-dist/commands/create-theme.md +14 -24
  31. package/pennyfarthing-dist/commands/dev.md +11 -3
  32. package/pennyfarthing-dist/commands/devops.md +11 -3
  33. package/pennyfarthing-dist/commands/health-check.md +1 -3
  34. package/pennyfarthing-dist/commands/help.md +8 -12
  35. package/pennyfarthing-dist/commands/list-themes.md +14 -16
  36. package/pennyfarthing-dist/commands/orchestrator.md +11 -3
  37. package/pennyfarthing-dist/commands/parallel-work.md +1 -3
  38. package/pennyfarthing-dist/commands/pm.md +11 -3
  39. package/pennyfarthing-dist/commands/prime.md +6 -6
  40. package/pennyfarthing-dist/commands/reviewer.md +11 -3
  41. package/pennyfarthing-dist/commands/run-ci.md +1 -1
  42. package/pennyfarthing-dist/commands/set-theme.md +14 -51
  43. package/pennyfarthing-dist/commands/setup.md +1 -1
  44. package/pennyfarthing-dist/commands/show-theme.md +14 -16
  45. package/pennyfarthing-dist/commands/sm.md +11 -3
  46. package/pennyfarthing-dist/commands/tea.md +11 -3
  47. package/pennyfarthing-dist/commands/tech-writer.md +11 -3
  48. package/pennyfarthing-dist/commands/theme-maker.md +14 -671
  49. package/pennyfarthing-dist/commands/theme.md +95 -0
  50. package/pennyfarthing-dist/commands/ux-designer.md +11 -3
  51. package/pennyfarthing-dist/commands/work.md +3 -5
  52. package/pennyfarthing-dist/guides/agent-coordination.md +11 -13
  53. package/pennyfarthing-dist/guides/agent-template-tactical.md +2 -3
  54. package/pennyfarthing-dist/guides/command-tag-taxonomy.md +212 -0
  55. package/pennyfarthing-dist/guides/hooks.md +5 -5
  56. package/pennyfarthing-dist/guides/patterns/fan-out-fan-in-pattern.md +3 -3
  57. package/pennyfarthing-dist/guides/patterns/helper-delegation-pattern.md +9 -59
  58. package/pennyfarthing-dist/guides/patterns/tdd-flow-pattern.md +4 -5
  59. package/pennyfarthing-dist/guides/prime.md +2 -2
  60. package/pennyfarthing-dist/guides/skill-schema.md +4 -4
  61. package/pennyfarthing-dist/scripts/core/agent-session.sh +6 -2
  62. package/pennyfarthing-dist/scripts/core/check-context.sh +0 -0
  63. package/pennyfarthing-dist/scripts/core/handoff-marker.sh +0 -0
  64. package/pennyfarthing-dist/scripts/core/phase-check-start.sh +0 -0
  65. package/pennyfarthing-dist/scripts/core/prime.sh +8 -10
  66. package/pennyfarthing-dist/scripts/cyclist/is-cyclist.sh +0 -0
  67. package/pennyfarthing-dist/scripts/git/create-feature-branches.sh +0 -0
  68. package/pennyfarthing-dist/scripts/git/git-status-all.sh +0 -0
  69. package/pennyfarthing-dist/scripts/git/install-git-hooks.sh +8 -6
  70. package/pennyfarthing-dist/scripts/git/release.sh +0 -0
  71. package/pennyfarthing-dist/scripts/git/worktree-manager.sh +0 -0
  72. package/pennyfarthing-dist/scripts/health/drift-detection.sh +0 -0
  73. package/pennyfarthing-dist/scripts/hooks/bell-mode-hook.sh +0 -0
  74. package/pennyfarthing-dist/scripts/hooks/context-circuit-breaker.sh +0 -0
  75. package/pennyfarthing-dist/scripts/hooks/context-warning.sh +0 -0
  76. package/pennyfarthing-dist/scripts/hooks/cyclist-pretooluse-hook.sh +0 -0
  77. package/pennyfarthing-dist/scripts/hooks/otel-auto-config.sh +0 -0
  78. package/pennyfarthing-dist/scripts/hooks/post-merge.sh +12 -5
  79. package/pennyfarthing-dist/scripts/hooks/pre-commit.sh +4 -3
  80. package/pennyfarthing-dist/scripts/hooks/pre-edit-check.sh +0 -0
  81. package/pennyfarthing-dist/scripts/hooks/pre-push.sh +11 -5
  82. package/pennyfarthing-dist/scripts/hooks/question-reflector-check.sh +0 -0
  83. package/pennyfarthing-dist/scripts/hooks/question_reflector_check.py +0 -0
  84. package/pennyfarthing-dist/scripts/hooks/schema-validation.sh +0 -0
  85. package/pennyfarthing-dist/scripts/hooks/session-start.sh +0 -0
  86. package/pennyfarthing-dist/scripts/hooks/session-stop.sh +0 -0
  87. package/pennyfarthing-dist/scripts/hooks/sprint-yaml-validation.sh +0 -0
  88. package/pennyfarthing-dist/scripts/hooks/welcome-hook.sh +0 -0
  89. package/pennyfarthing-dist/scripts/jira/create-jira-epic.sh +0 -0
  90. package/pennyfarthing-dist/scripts/jira/create-jira-story.sh +0 -0
  91. package/pennyfarthing-dist/scripts/jira/jira-claim-story.sh +0 -0
  92. package/pennyfarthing-dist/scripts/jira/jira-reconcile.sh +0 -0
  93. package/pennyfarthing-dist/scripts/jira/jira-sync-story.sh +0 -0
  94. package/pennyfarthing-dist/scripts/jira/sync-epic-jira.sh +0 -0
  95. package/pennyfarthing-dist/scripts/lib/background-tasks.sh +0 -0
  96. package/pennyfarthing-dist/scripts/lib/checkpoint.sh +0 -0
  97. package/pennyfarthing-dist/scripts/lib/common.sh +0 -0
  98. package/pennyfarthing-dist/scripts/lib/file-lock.sh +0 -0
  99. package/pennyfarthing-dist/scripts/lib/logging.sh +0 -0
  100. package/pennyfarthing-dist/scripts/lib/retry.sh +0 -0
  101. package/pennyfarthing-dist/scripts/maintenance/migrate-theme-schema.mjs +0 -0
  102. package/pennyfarthing-dist/scripts/maintenance/sidecar-health.sh +0 -0
  103. package/pennyfarthing-dist/scripts/misc/README.md +1 -1
  104. package/pennyfarthing-dist/scripts/misc/add-short-names.sh +0 -0
  105. package/pennyfarthing-dist/scripts/misc/add_short_names.py +0 -0
  106. package/pennyfarthing-dist/scripts/misc/backlog.sh +0 -0
  107. package/pennyfarthing-dist/scripts/misc/check-status.sh +0 -0
  108. package/pennyfarthing-dist/scripts/misc/find-related-work.sh +0 -0
  109. package/pennyfarthing-dist/scripts/misc/generate-skill-docs.sh +0 -0
  110. package/pennyfarthing-dist/scripts/misc/log-skill-usage.sh +0 -0
  111. package/pennyfarthing-dist/scripts/misc/migrate-bmad-workflow.sh +0 -0
  112. package/pennyfarthing-dist/scripts/misc/migrate_bmad_workflow.py +0 -0
  113. package/pennyfarthing-dist/scripts/misc/repo-scan.sh +0 -0
  114. package/pennyfarthing-dist/scripts/misc/repo-utils.sh +0 -0
  115. package/pennyfarthing-dist/scripts/misc/run-ci.sh +0 -0
  116. package/pennyfarthing-dist/scripts/misc/run-timestamp.sh +0 -0
  117. package/pennyfarthing-dist/scripts/misc/session-cleanup.sh +0 -0
  118. package/pennyfarthing-dist/scripts/misc/skill-usage-report.sh +0 -0
  119. package/pennyfarthing-dist/scripts/misc/statusline.sh +0 -0
  120. package/pennyfarthing-dist/scripts/misc/uninstall.sh +0 -0
  121. package/pennyfarthing-dist/scripts/misc/validate-subagent-frontmatter.sh +1 -2
  122. package/pennyfarthing-dist/scripts/portraits/generate-portraits.sh +0 -0
  123. package/pennyfarthing-dist/scripts/story/create-story.sh +0 -0
  124. package/pennyfarthing-dist/scripts/story/size-story.sh +0 -0
  125. package/pennyfarthing-dist/scripts/story/story-template.sh +0 -0
  126. package/pennyfarthing-dist/scripts/test/ensure-swebench-data.sh +0 -0
  127. package/pennyfarthing-dist/scripts/test/swebench-judge.py +0 -0
  128. package/pennyfarthing-dist/scripts/test/test-cache.sh +0 -0
  129. package/pennyfarthing-dist/scripts/test/test-setup.sh +0 -0
  130. package/pennyfarthing-dist/scripts/tests/check.test.sh +0 -0
  131. package/pennyfarthing-dist/scripts/tests/dev-story-workflow-import.test.sh +0 -0
  132. package/pennyfarthing-dist/scripts/tests/epics-and-stories-workflow-import.test.sh +0 -0
  133. package/pennyfarthing-dist/scripts/tests/handoff-phase-update.test.sh +5 -5
  134. package/pennyfarthing-dist/scripts/tests/implementation-readiness-workflow-import.test.sh +0 -0
  135. package/pennyfarthing-dist/scripts/tests/migrate-bmad-workflow.test.sh +0 -0
  136. package/pennyfarthing-dist/scripts/tests/prd-workflow-import.test.sh +0 -0
  137. package/pennyfarthing-dist/scripts/tests/project-context-workflow-import.test.sh +0 -0
  138. package/pennyfarthing-dist/scripts/tests/test-character-voice.sh +0 -0
  139. package/pennyfarthing-dist/scripts/tests/test-drift-detection.sh +3 -79
  140. package/pennyfarthing-dist/scripts/tests/test-post-merge-hook.sh +0 -0
  141. package/pennyfarthing-dist/scripts/tests/test-session-checkpoint.sh +0 -0
  142. package/pennyfarthing-dist/scripts/tests/test-solo-command.sh +0 -0
  143. package/pennyfarthing-dist/scripts/tests/ux-design-workflow-import.test.sh +0 -0
  144. package/pennyfarthing-dist/scripts/theme/README.md +1 -1
  145. package/pennyfarthing-dist/scripts/theme/compute-theme-tiers.sh +0 -0
  146. package/pennyfarthing-dist/scripts/theme/compute_theme_tiers.py +0 -0
  147. package/pennyfarthing-dist/scripts/theme/list-themes.sh +0 -0
  148. package/pennyfarthing-dist/scripts/theme/update-theme-tiers.sh +0 -0
  149. package/pennyfarthing-dist/scripts/validation/validate-agent-schema.sh +0 -1
  150. package/pennyfarthing-dist/scripts/workflow/check.py +0 -0
  151. package/pennyfarthing-dist/scripts/workflow/check.sh +0 -0
  152. package/pennyfarthing-dist/scripts/workflow/complete-step.py +0 -0
  153. package/pennyfarthing-dist/scripts/workflow/finish-story.sh +62 -17
  154. package/pennyfarthing-dist/scripts/workflow/fix-session-phase.sh +0 -0
  155. package/pennyfarthing-dist/scripts/workflow/get-workflow-type.py +0 -0
  156. package/pennyfarthing-dist/scripts/workflow/get-workflow-type.sh +0 -0
  157. package/pennyfarthing-dist/scripts/workflow/list-workflows.sh +0 -0
  158. package/pennyfarthing-dist/scripts/workflow/phase-owner.sh +0 -0
  159. package/pennyfarthing-dist/scripts/workflow/resume-workflow.sh +0 -0
  160. package/pennyfarthing-dist/scripts/workflow/show-workflow.sh +0 -0
  161. package/pennyfarthing-dist/scripts/workflow/start-workflow.sh +0 -0
  162. package/pennyfarthing-dist/scripts/workflow/workflow-status.sh +0 -0
  163. package/pennyfarthing-dist/skills/dev-patterns/SKILL.md +2 -2
  164. package/pennyfarthing-dist/skills/skill-registry.yaml +20 -16
  165. package/pennyfarthing-dist/skills/story/scripts/create-story.sh +0 -0
  166. package/pennyfarthing-dist/skills/story/scripts/size-story.sh +0 -0
  167. package/pennyfarthing-dist/skills/story/scripts/story-template.sh +0 -0
  168. package/pennyfarthing-dist/skills/theme/skill.md +290 -75
  169. package/pennyfarthing-dist/skills/theme-creation/SKILL.md +23 -166
  170. package/pennyfarthing-dist/skills/workflow/scripts/list-workflows.sh +0 -0
  171. package/pennyfarthing-dist/skills/workflow/scripts/resume-workflow.sh +0 -0
  172. package/pennyfarthing-dist/skills/workflow/scripts/show-workflow.sh +0 -0
  173. package/pennyfarthing-dist/skills/workflow/scripts/start-workflow.sh +0 -0
  174. package/pennyfarthing-dist/skills/workflow/scripts/workflow-status.sh +0 -0
  175. package/pennyfarthing-dist/skills/workflow/skill.md +4 -4
  176. package/pennyfarthing-dist/templates/agent-scopes.yaml.template +0 -11
  177. package/pennyfarthing-dist/templates/auto-load-sm.sh.template +14 -0
  178. package/pennyfarthing-dist/templates/settings.local.json.template +9 -0
  179. package/pennyfarthing-dist/workflows/2party-tdd.yaml +399 -0
  180. package/pennyfarthing-dist/workflows/epics-and-stories/steps/step-05-import-to-future.md +41 -24
  181. package/pennyfarthing_scripts/__pycache__/cli.cpython-314.pyc +0 -0
  182. package/pennyfarthing_scripts/__pycache__/hooks.cpython-314.pyc +0 -0
  183. package/pennyfarthing_scripts/__pycache__/schema_validation_hook.cpython-314.pyc +0 -0
  184. package/pennyfarthing_scripts/__pycache__/workflow.cpython-314.pyc +0 -0
  185. package/pennyfarthing_scripts/cli.py +15 -0
  186. package/pennyfarthing_scripts/codemarkers/__init__.py +19 -0
  187. package/pennyfarthing_scripts/codemarkers/__main__.py +6 -0
  188. package/pennyfarthing_scripts/codemarkers/__pycache__/__init__.cpython-314.pyc +0 -0
  189. package/pennyfarthing_scripts/codemarkers/__pycache__/__main__.cpython-314.pyc +0 -0
  190. package/pennyfarthing_scripts/codemarkers/__pycache__/analyze.cpython-314.pyc +0 -0
  191. package/pennyfarthing_scripts/codemarkers/__pycache__/cli.cpython-314.pyc +0 -0
  192. package/pennyfarthing_scripts/codemarkers/__pycache__/formatters.cpython-314.pyc +0 -0
  193. package/pennyfarthing_scripts/codemarkers/__pycache__/models.cpython-314.pyc +0 -0
  194. package/pennyfarthing_scripts/codemarkers/analyze.py +326 -0
  195. package/pennyfarthing_scripts/codemarkers/cli.py +129 -0
  196. package/pennyfarthing_scripts/codemarkers/formatters.py +89 -0
  197. package/pennyfarthing_scripts/codemarkers/models.py +45 -0
  198. package/pennyfarthing_scripts/common/__pycache__/config.cpython-314.pyc +0 -0
  199. package/pennyfarthing_scripts/common/__pycache__/themes.cpython-314.pyc +0 -0
  200. package/pennyfarthing_scripts/complexity/__init__.py +15 -0
  201. package/pennyfarthing_scripts/complexity/__main__.py +6 -0
  202. package/pennyfarthing_scripts/complexity/__pycache__/__init__.cpython-314.pyc +0 -0
  203. package/pennyfarthing_scripts/complexity/__pycache__/__main__.cpython-314.pyc +0 -0
  204. package/pennyfarthing_scripts/complexity/__pycache__/analyze.cpython-314.pyc +0 -0
  205. package/pennyfarthing_scripts/complexity/__pycache__/cli.cpython-314.pyc +0 -0
  206. package/pennyfarthing_scripts/complexity/__pycache__/formatters.cpython-314.pyc +0 -0
  207. package/pennyfarthing_scripts/complexity/__pycache__/models.cpython-314.pyc +0 -0
  208. package/pennyfarthing_scripts/complexity/analyze.py +207 -0
  209. package/pennyfarthing_scripts/complexity/cli.py +78 -0
  210. package/pennyfarthing_scripts/complexity/formatters.py +64 -0
  211. package/pennyfarthing_scripts/complexity/models.py +32 -0
  212. package/pennyfarthing_scripts/deadcode/__init__.py +6 -0
  213. package/pennyfarthing_scripts/deadcode/__main__.py +6 -0
  214. package/pennyfarthing_scripts/deadcode/__pycache__/__init__.cpython-314.pyc +0 -0
  215. package/pennyfarthing_scripts/deadcode/__pycache__/__main__.cpython-314.pyc +0 -0
  216. package/pennyfarthing_scripts/deadcode/__pycache__/analyze.cpython-314.pyc +0 -0
  217. package/pennyfarthing_scripts/deadcode/__pycache__/cli.cpython-314.pyc +0 -0
  218. package/pennyfarthing_scripts/deadcode/__pycache__/formatters.cpython-314.pyc +0 -0
  219. package/pennyfarthing_scripts/deadcode/__pycache__/models.cpython-314.pyc +0 -0
  220. package/pennyfarthing_scripts/deadcode/analyze.py +323 -0
  221. package/pennyfarthing_scripts/deadcode/cli.py +163 -0
  222. package/pennyfarthing_scripts/deadcode/formatters.py +106 -0
  223. package/pennyfarthing_scripts/deadcode/models.py +54 -0
  224. package/pennyfarthing_scripts/dependencies/__init__.py +20 -0
  225. package/pennyfarthing_scripts/dependencies/__main__.py +5 -0
  226. package/pennyfarthing_scripts/dependencies/__pycache__/__init__.cpython-314.pyc +0 -0
  227. package/pennyfarthing_scripts/dependencies/__pycache__/__main__.cpython-314.pyc +0 -0
  228. package/pennyfarthing_scripts/dependencies/__pycache__/analyze.cpython-314.pyc +0 -0
  229. package/pennyfarthing_scripts/dependencies/__pycache__/cli.cpython-314.pyc +0 -0
  230. package/pennyfarthing_scripts/dependencies/__pycache__/formatters.cpython-314.pyc +0 -0
  231. package/pennyfarthing_scripts/dependencies/__pycache__/models.cpython-314.pyc +0 -0
  232. package/pennyfarthing_scripts/dependencies/analyze.py +155 -0
  233. package/pennyfarthing_scripts/dependencies/cli.py +72 -0
  234. package/pennyfarthing_scripts/dependencies/formatters.py +63 -0
  235. package/pennyfarthing_scripts/dependencies/models.py +39 -0
  236. package/pennyfarthing_scripts/healthscore/__init__.py +21 -0
  237. package/pennyfarthing_scripts/healthscore/__main__.py +6 -0
  238. package/pennyfarthing_scripts/healthscore/__pycache__/__init__.cpython-314.pyc +0 -0
  239. package/pennyfarthing_scripts/healthscore/__pycache__/__main__.cpython-314.pyc +0 -0
  240. package/pennyfarthing_scripts/healthscore/__pycache__/analyze.cpython-314.pyc +0 -0
  241. package/pennyfarthing_scripts/healthscore/__pycache__/cli.cpython-314.pyc +0 -0
  242. package/pennyfarthing_scripts/healthscore/__pycache__/formatters.cpython-314.pyc +0 -0
  243. package/pennyfarthing_scripts/healthscore/__pycache__/models.cpython-314.pyc +0 -0
  244. package/pennyfarthing_scripts/healthscore/analyze.py +161 -0
  245. package/pennyfarthing_scripts/healthscore/cli.py +76 -0
  246. package/pennyfarthing_scripts/healthscore/formatters.py +46 -0
  247. package/pennyfarthing_scripts/healthscore/models.py +44 -0
  248. package/pennyfarthing_scripts/hooks/cyclist-pretooluse-hook.sh +0 -0
  249. package/pennyfarthing_scripts/hotspots/__pycache__/__init__.cpython-314.pyc +0 -0
  250. package/pennyfarthing_scripts/hotspots/__pycache__/__main__.cpython-314.pyc +0 -0
  251. package/pennyfarthing_scripts/hotspots/__pycache__/analyze.cpython-314.pyc +0 -0
  252. package/pennyfarthing_scripts/hotspots/__pycache__/cli.cpython-314.pyc +0 -0
  253. package/pennyfarthing_scripts/hotspots/__pycache__/formatters.cpython-314.pyc +0 -0
  254. package/pennyfarthing_scripts/hotspots/__pycache__/models.cpython-314.pyc +0 -0
  255. package/pennyfarthing_scripts/hotspots/analyze.py +27 -0
  256. package/pennyfarthing_scripts/hotspots/cli.py +10 -8
  257. package/pennyfarthing_scripts/jira/__pycache__/__init__.cpython-314.pyc +0 -0
  258. package/pennyfarthing_scripts/jira/__pycache__/bidirectional.cpython-314.pyc +0 -0
  259. package/pennyfarthing_scripts/jira/__pycache__/cli.cpython-314.pyc +0 -0
  260. package/pennyfarthing_scripts/jira/__pycache__/client.cpython-314.pyc +0 -0
  261. package/pennyfarthing_scripts/jira/__pycache__/create.cpython-314.pyc +0 -0
  262. package/pennyfarthing_scripts/jira/__pycache__/operations.cpython-314.pyc +0 -0
  263. package/pennyfarthing_scripts/jira/__pycache__/reconcile.cpython-314.pyc +0 -0
  264. package/pennyfarthing_scripts/jira/bidirectional.py +42 -15
  265. package/pennyfarthing_scripts/jira/cli.py +4 -1
  266. package/pennyfarthing_scripts/jira/client.py +28 -0
  267. package/pennyfarthing_scripts/prime/__pycache__/cli.cpython-314.pyc +0 -0
  268. package/pennyfarthing_scripts/prime/__pycache__/models.cpython-314.pyc +0 -0
  269. package/pennyfarthing_scripts/prime/__pycache__/persona.cpython-314.pyc +0 -0
  270. package/pennyfarthing_scripts/prime/__pycache__/tiers.cpython-314.pyc +0 -0
  271. package/pennyfarthing_scripts/prime/__pycache__/workflow.cpython-314.pyc +0 -0
  272. package/pennyfarthing_scripts/sprint/__pycache__/archive.cpython-314.pyc +0 -0
  273. package/pennyfarthing_scripts/sprint/__pycache__/archive_epic.cpython-314.pyc +0 -0
  274. package/pennyfarthing_scripts/sprint/__pycache__/cli.cpython-314.pyc +0 -0
  275. package/pennyfarthing_scripts/sprint/__pycache__/epic_add.cpython-314.pyc +0 -0
  276. package/pennyfarthing_scripts/sprint/__pycache__/loader.cpython-314.pyc +0 -0
  277. package/pennyfarthing_scripts/sprint/__pycache__/story_add.cpython-314.pyc +0 -0
  278. package/pennyfarthing_scripts/sprint/__pycache__/story_finish.cpython-314.pyc +0 -0
  279. package/pennyfarthing_scripts/sprint/__pycache__/story_update.cpython-314.pyc +0 -0
  280. package/pennyfarthing_scripts/sprint/__pycache__/validate_cmd.cpython-314.pyc +0 -0
  281. package/pennyfarthing_scripts/sprint/__pycache__/validator.cpython-314.pyc +0 -0
  282. package/pennyfarthing_scripts/sprint/__pycache__/work.cpython-314.pyc +0 -0
  283. package/pennyfarthing_scripts/sprint/__pycache__/yaml_io.cpython-314.pyc +0 -0
  284. package/pennyfarthing_scripts/sprint/archive_epic.py +198 -94
  285. package/pennyfarthing_scripts/sprint/cli.py +29 -19
  286. package/pennyfarthing_scripts/sprint/story_add.py +202 -27
  287. package/pennyfarthing_scripts/sprint/story_finish.py +211 -0
  288. package/pennyfarthing_scripts/sprint/work.py +27 -3
  289. package/pennyfarthing_scripts/tests/__pycache__/test_codemarkers.cpython-314-pytest-9.0.2.pyc +0 -0
  290. package/pennyfarthing_scripts/tests/__pycache__/test_healthscore.cpython-314-pytest-9.0.2.pyc +0 -0
  291. package/pennyfarthing_scripts/tests/__pycache__/test_sprint_package.cpython-314-pytest-9.0.2.pyc +0 -0
  292. package/pennyfarthing_scripts/tests/__pycache__/test_sprint_validator.cpython-314-pytest-9.0.2.pyc +0 -0
  293. package/pennyfarthing_scripts/tests/__pycache__/test_validate_cmd.cpython-314-pytest-9.0.2.pyc +0 -0
  294. package/pennyfarthing_scripts/tests/__pycache__/test_yaml_io.cpython-314-pytest-9.0.2.pyc +0 -0
  295. package/pennyfarthing_scripts/tests/test_codemarkers.py +682 -0
  296. package/pennyfarthing_scripts/tests/test_healthscore.py +524 -0
  297. package/pennyfarthing_scripts/theme/__init__.py +5 -0
  298. package/pennyfarthing_scripts/theme/__main__.py +6 -0
  299. package/pennyfarthing_scripts/theme/__pycache__/__init__.cpython-314.pyc +0 -0
  300. package/pennyfarthing_scripts/theme/__pycache__/cli.cpython-314.pyc +0 -0
  301. package/pennyfarthing_scripts/theme/cli.py +286 -0
  302. package/scripts/README.md +53 -0
  303. package/pennyfarthing-dist/agents/workflow-status-check.md +0 -96
@@ -0,0 +1,207 @@
1
+ """
2
+ Core complexity analysis engine.
3
+
4
+ Wraps eslint --format json with complexity/max-depth/max-lines-per-function rules.
5
+ Parses output into per-file metrics following ADR-0008 result pattern.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import asyncio
11
+ import fnmatch
12
+ import json
13
+ import re
14
+ from pathlib import Path
15
+
16
+ from pennyfarthing_scripts.complexity.models import FileComplexity, ComplexityResult
17
+
18
+ # Regex patterns for extracting numeric values from ESLint messages
19
+ COMPLEXITY_RE = re.compile(r"complexity of (\d+)")
20
+ MAX_DEPTH_RE = re.compile(r"too deeply \((\d+)\)")
21
+ MAX_LINES_RE = re.compile(r"too many lines \((\d+)\)")
22
+
23
+
24
+ def _find_eslint(target_path: Path) -> Path | None:
25
+ """Find eslint binary in node_modules/.bin/.
26
+
27
+ Walks up from target_path looking for node_modules/.bin/eslint.
28
+ """
29
+ candidate = target_path / "node_modules" / ".bin" / "eslint"
30
+ if candidate.exists():
31
+ return candidate
32
+
33
+ for parent in target_path.parents:
34
+ candidate = parent / "node_modules" / ".bin" / "eslint"
35
+ if candidate.exists():
36
+ return candidate
37
+
38
+ return None
39
+
40
+
41
+ def _parse_eslint_output(output: str, target_path: Path) -> list[FileComplexity]:
42
+ """Parse eslint JSON output into FileComplexity models.
43
+
44
+ Extracts per-file metrics from ESLint rule violations:
45
+ - complexity rule → cyclomatic complexity per function
46
+ - max-depth rule → nesting depth
47
+ - max-lines-per-function rule → function line count
48
+ """
49
+ try:
50
+ data = json.loads(output)
51
+ except (json.JSONDecodeError, TypeError):
52
+ return []
53
+
54
+ if not data:
55
+ return []
56
+
57
+ target_str = str(target_path)
58
+ if not target_str.endswith("/"):
59
+ target_str += "/"
60
+
61
+ files = []
62
+ for file_entry in data:
63
+ file_path = file_entry.get("filePath", "")
64
+ messages = file_entry.get("messages", [])
65
+
66
+ # Make path relative to target directory
67
+ if file_path.startswith(target_str):
68
+ rel_path = file_path[len(target_str):]
69
+ else:
70
+ rel_path = file_path
71
+
72
+ complexities = []
73
+ depths = []
74
+ line_counts = []
75
+
76
+ for msg in messages:
77
+ rule_id = msg.get("ruleId", "")
78
+ message = msg.get("message", "")
79
+
80
+ if rule_id == "complexity":
81
+ m = COMPLEXITY_RE.search(message)
82
+ if m:
83
+ complexities.append(int(m.group(1)))
84
+
85
+ elif rule_id == "max-depth":
86
+ m = MAX_DEPTH_RE.search(message)
87
+ if m:
88
+ depths.append(int(m.group(1)))
89
+
90
+ elif rule_id == "max-lines-per-function":
91
+ m = MAX_LINES_RE.search(message)
92
+ if m:
93
+ line_counts.append(int(m.group(1)))
94
+
95
+ function_count = len(complexities)
96
+ avg_complexity = (
97
+ sum(complexities) / len(complexities) if complexities else 0.0
98
+ )
99
+ max_nesting = max(depths) if depths else 0
100
+ longest_fn = max(line_counts) if line_counts else 0
101
+
102
+ files.append(FileComplexity(
103
+ path=rel_path,
104
+ total_lines=0, # populated by _count_file_lines
105
+ longest_function=longest_fn,
106
+ avg_cyclomatic_complexity=round(avg_complexity, 1),
107
+ max_nesting_depth=max_nesting,
108
+ function_count=function_count,
109
+ ))
110
+
111
+ return files
112
+
113
+
114
+ async def _run_eslint(eslint_bin: Path, target_path: Path) -> tuple[str, str, int]:
115
+ """Run eslint subprocess with complexity rules enabled."""
116
+ proc = await asyncio.create_subprocess_exec(
117
+ str(eslint_bin),
118
+ str(target_path),
119
+ "--format", "json",
120
+ "--no-config-lookup",
121
+ "--rule", 'complexity: ["warn", 1]',
122
+ "--rule", 'max-depth: ["warn", 1]',
123
+ "--rule", 'max-lines-per-function: ["warn", 1]',
124
+ stdout=asyncio.subprocess.PIPE,
125
+ stderr=asyncio.subprocess.PIPE,
126
+ )
127
+ stdout, stderr = await proc.communicate()
128
+ return (
129
+ stdout.decode("utf-8", errors="replace"),
130
+ stderr.decode("utf-8", errors="replace"),
131
+ proc.returncode or 0,
132
+ )
133
+
134
+
135
+ async def _count_file_lines(file_path: Path) -> int:
136
+ """Count lines in a file."""
137
+ try:
138
+ content = file_path.read_bytes()
139
+ return content.count(b"\n") + (1 if content and not content.endswith(b"\n") else 0)
140
+ except OSError:
141
+ return 0
142
+
143
+
144
+ def _should_exclude(path: str, patterns: list[str]) -> bool:
145
+ """Check if a file path matches any exclusion pattern."""
146
+ for pattern in patterns:
147
+ if fnmatch.fnmatch(path, pattern):
148
+ return True
149
+ # Check basename
150
+ if fnmatch.fnmatch(path.split("/")[-1], pattern):
151
+ return True
152
+ # Check if any parent directory matches (e.g. node_modules/* matches node_modules/lib/foo.ts)
153
+ parts = path.split("/")
154
+ for i in range(len(parts)):
155
+ partial = "/".join(parts[: i + 1])
156
+ if fnmatch.fnmatch(partial, pattern):
157
+ return True
158
+ return False
159
+
160
+
161
+ async def analyze_complexity(
162
+ target_path: Path,
163
+ excludes: list[str] | None = None,
164
+ ) -> ComplexityResult:
165
+ """Analyze complexity of files in the target directory."""
166
+ resolved = target_path.resolve()
167
+
168
+ eslint_bin = _find_eslint(resolved)
169
+ if eslint_bin is None:
170
+ return ComplexityResult(
171
+ success=False,
172
+ target_path=str(resolved),
173
+ error="eslint not found. Install with: npm install -D eslint",
174
+ )
175
+
176
+ stdout, stderr, rc = await _run_eslint(eslint_bin, resolved)
177
+
178
+ # Try resolved path first, fall back to original (handles /tmp → /private/tmp on macOS)
179
+ files = _parse_eslint_output(stdout, resolved)
180
+ if not files or (files and files[0].path.startswith("/")):
181
+ alt_files = _parse_eslint_output(stdout, target_path)
182
+ if alt_files and (not files or not alt_files[0].path.startswith("/")):
183
+ files = alt_files
184
+
185
+ if not files:
186
+ return ComplexityResult(
187
+ success=True,
188
+ target_path=str(resolved),
189
+ file_count=0,
190
+ files=[],
191
+ )
192
+
193
+ # Apply exclude filters
194
+ if excludes:
195
+ files = [f for f in files if not _should_exclude(f.path, excludes)]
196
+
197
+ # Count lines for each file
198
+ for fc in files:
199
+ full_path = resolved / fc.path
200
+ fc.total_lines = await _count_file_lines(full_path)
201
+
202
+ return ComplexityResult(
203
+ success=True,
204
+ target_path=str(resolved),
205
+ file_count=len(files),
206
+ files=files,
207
+ )
@@ -0,0 +1,78 @@
1
+ """
2
+ CLI commands for complexity analysis.
3
+
4
+ Usage:
5
+ python -m pennyfarthing_scripts.complexity analyze [OPTIONS]
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import asyncio
11
+ from pathlib import Path
12
+
13
+ import click
14
+
15
+
16
+ @click.group()
17
+ def complexity():
18
+ """Code complexity analysis.
19
+
20
+ \b
21
+ Commands:
22
+ analyze - Analyze code complexity metrics
23
+ """
24
+ pass
25
+
26
+
27
+ def _common_options(fn):
28
+ """Shared options for complexity commands."""
29
+ fn = click.option("--path", "target_path", type=click.Path(exists=True),
30
+ help="Directory to analyze")(fn)
31
+ fn = click.option("--format", "fmt", type=click.Choice(["table", "json", "csv"]),
32
+ default="table", show_default=True)(fn)
33
+ fn = click.option("--top", default=20, show_default=True,
34
+ help="Number of top results")(fn)
35
+ fn = click.option("--output", "output_file", type=click.Path(),
36
+ help="Write output to file")(fn)
37
+ fn = click.option("--exclude", multiple=True,
38
+ help="Exclude patterns (repeatable)")(fn)
39
+ return fn
40
+
41
+
42
+ def _run_analysis(target_path: str | None, exclude: tuple) -> "ComplexityResult":
43
+ """Run analysis and return result."""
44
+ from pennyfarthing_scripts.complexity.analyze import analyze_complexity
45
+
46
+ excludes = list(exclude) if exclude else None
47
+ p = Path(target_path).resolve() if target_path else Path(".").resolve()
48
+ return asyncio.run(analyze_complexity(p, excludes))
49
+
50
+
51
+ def _output_result(result, fmt: str, output_file: str | None, top: int):
52
+ """Format and output the analysis result."""
53
+ from pennyfarthing_scripts.complexity.formatters import (
54
+ export_csv,
55
+ export_json,
56
+ format_file_table,
57
+ )
58
+
59
+ if fmt == "json":
60
+ text = export_json(result)
61
+ elif fmt == "csv":
62
+ text = export_csv(result.files[:top])
63
+ else:
64
+ text = format_file_table(result.files, top)
65
+
66
+ if output_file:
67
+ Path(output_file).write_text(text)
68
+ click.echo(f"Output written to {output_file}", err=True)
69
+ else:
70
+ click.echo(text)
71
+
72
+
73
+ @complexity.command()
74
+ @_common_options
75
+ def analyze(target_path, fmt, top, output_file, exclude):
76
+ """Analyze code complexity."""
77
+ result = _run_analysis(target_path, exclude)
78
+ _output_result(result, fmt, output_file, top)
@@ -0,0 +1,64 @@
1
+ """
2
+ Output formatters for complexity analysis results.
3
+
4
+ Supports table, JSON, and CSV output — no external dependencies.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import csv
10
+ import io
11
+ import json
12
+ from dataclasses import asdict
13
+
14
+ from pennyfarthing_scripts.complexity.models import FileComplexity, ComplexityResult
15
+
16
+
17
+ def format_file_table(files: list[FileComplexity], top_n: int = 20) -> str:
18
+ """Format complexity results as column-aligned table."""
19
+ if not files:
20
+ return " No complexity results found."
21
+
22
+ items = files[:top_n]
23
+
24
+ hdr = f"{'Complexity':>11} {'Longest Fn':>11} {'Nesting':>8} {'Functions':>10} {'Lines':>6} File"
25
+ sep = f"{'-----------':>11} {'-----------':>11} {'--------':>8} {'----------':>10} {'------':>6} ----"
26
+
27
+ lines = [hdr, sep]
28
+ for f in items:
29
+ lines.append(
30
+ f"{f.avg_cyclomatic_complexity:>11.1f} {f.longest_function:>11} "
31
+ f"{f.max_nesting_depth:>8} {f.function_count:>10} "
32
+ f"{f.total_lines:>6} {f.path}"
33
+ )
34
+
35
+ return "\n".join(lines)
36
+
37
+
38
+ def export_json(result: ComplexityResult) -> str:
39
+ """Serialize result to JSON string."""
40
+ return json.dumps(asdict(result), indent=2, default=str)
41
+
42
+
43
+ def export_csv(files: list[FileComplexity]) -> str:
44
+ """Export file complexity data as CSV."""
45
+ buf = io.StringIO()
46
+ writer = csv.writer(buf)
47
+ writer.writerow([
48
+ "path",
49
+ "total_lines",
50
+ "longest_function",
51
+ "avg_cyclomatic_complexity",
52
+ "max_nesting_depth",
53
+ "function_count",
54
+ ])
55
+ for f in files:
56
+ writer.writerow([
57
+ f.path,
58
+ f.total_lines,
59
+ f.longest_function,
60
+ f.avg_cyclomatic_complexity,
61
+ f.max_nesting_depth,
62
+ f.function_count,
63
+ ])
64
+ return buf.getvalue()
@@ -0,0 +1,32 @@
1
+ """
2
+ Data models for complexity analysis results.
3
+
4
+ Follows ADR-0008 result pattern — structured dataclasses with success/error fields.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from dataclasses import dataclass, field
10
+
11
+
12
+ @dataclass
13
+ class FileComplexity:
14
+ """Complexity metrics for a single file."""
15
+
16
+ path: str
17
+ total_lines: int = 0
18
+ longest_function: int = 0
19
+ avg_cyclomatic_complexity: float = 0.0
20
+ max_nesting_depth: int = 0
21
+ function_count: int = 0
22
+
23
+
24
+ @dataclass
25
+ class ComplexityResult:
26
+ """Analysis result following ADR-0008 pattern."""
27
+
28
+ success: bool
29
+ target_path: str = ""
30
+ file_count: int = 0
31
+ files: list[FileComplexity] = field(default_factory=list)
32
+ error: str | None = None
@@ -0,0 +1,6 @@
1
+ """
2
+ Dead code detection module.
3
+
4
+ Layer 1: Git-based stale file detection.
5
+ Layer 2: Unused TypeScript export detection via ts-prune.
6
+ """
@@ -0,0 +1,6 @@
1
+ """Allow running as python -m pennyfarthing_scripts.deadcode."""
2
+
3
+ from pennyfarthing_scripts.deadcode.cli import deadcode
4
+
5
+ if __name__ == "__main__":
6
+ deadcode()