@pennyfarthing/core 10.0.3 → 10.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (282) hide show
  1. package/README.md +9 -7
  2. package/package.json +7 -1
  3. package/packages/core/dist/cli/commands/cyclist.d.ts +5 -1
  4. package/packages/core/dist/cli/commands/cyclist.d.ts.map +1 -1
  5. package/packages/core/dist/cli/commands/cyclist.js +4 -4
  6. package/packages/core/dist/cli/commands/cyclist.js.map +1 -1
  7. package/packages/core/dist/cli/commands/cyclist.test.js +2 -2
  8. package/packages/core/dist/cli/commands/cyclist.test.js.map +1 -1
  9. package/packages/core/dist/cli/commands/doctor-legacy.test.js +17 -16
  10. package/packages/core/dist/cli/commands/doctor-legacy.test.js.map +1 -1
  11. package/packages/core/dist/cli/commands/doctor.d.ts.map +1 -1
  12. package/packages/core/dist/cli/commands/doctor.js +251 -4
  13. package/packages/core/dist/cli/commands/doctor.js.map +1 -1
  14. package/packages/core/dist/cli/commands/init.d.ts +7 -0
  15. package/packages/core/dist/cli/commands/init.d.ts.map +1 -1
  16. package/packages/core/dist/cli/commands/init.js +43 -7
  17. package/packages/core/dist/cli/commands/init.js.map +1 -1
  18. package/packages/core/dist/cli/commands/update.d.ts.map +1 -1
  19. package/packages/core/dist/cli/commands/update.js +26 -0
  20. package/packages/core/dist/cli/commands/update.js.map +1 -1
  21. package/packages/core/dist/cli/index.js +1 -1
  22. package/packages/core/dist/cli/index.js.map +1 -1
  23. package/packages/core/dist/cli/ocean-profiles.test.js +1 -1
  24. package/packages/core/dist/cli/ocean-profiles.test.js.map +1 -1
  25. package/packages/core/dist/cli/utils/files.d.ts +10 -0
  26. package/packages/core/dist/cli/utils/files.d.ts.map +1 -1
  27. package/packages/core/dist/cli/utils/files.js +35 -0
  28. package/packages/core/dist/cli/utils/files.js.map +1 -1
  29. package/packages/core/dist/cli/utils/python.d.ts +22 -0
  30. package/packages/core/dist/cli/utils/python.d.ts.map +1 -0
  31. package/packages/core/dist/cli/utils/python.js +102 -0
  32. package/packages/core/dist/cli/utils/python.js.map +1 -0
  33. package/packages/core/dist/cli/utils/settings.d.ts.map +1 -1
  34. package/packages/core/dist/cli/utils/settings.js +10 -0
  35. package/packages/core/dist/cli/utils/settings.js.map +1 -1
  36. package/packages/core/dist/scripts/generate-report.d.ts.map +1 -1
  37. package/packages/core/dist/scripts/generate-report.js +11 -7
  38. package/packages/core/dist/scripts/generate-report.js.map +1 -1
  39. package/packages/core/dist/scripts/generate-spider-report.d.ts.map +1 -1
  40. package/packages/core/dist/scripts/generate-spider-report.js +12 -8
  41. package/packages/core/dist/scripts/generate-spider-report.js.map +1 -1
  42. package/packages/core/dist/scripts/generate-spider.d.ts.map +1 -1
  43. package/packages/core/dist/scripts/generate-spider.js +6 -4
  44. package/packages/core/dist/scripts/generate-spider.js.map +1 -1
  45. package/packages/core/dist/scripts/generate-spider.test.js +2 -2
  46. package/packages/core/dist/scripts/generate-spider.test.js.map +1 -1
  47. package/pennyfarthing-dist/agents/README.md +1 -3
  48. package/pennyfarthing-dist/agents/architect.md +0 -6
  49. package/pennyfarthing-dist/agents/devops.md +0 -6
  50. package/pennyfarthing-dist/agents/orchestrator.md +0 -6
  51. package/pennyfarthing-dist/agents/pm.md +1 -7
  52. package/pennyfarthing-dist/agents/sm-finish.md +1 -1
  53. package/pennyfarthing-dist/agents/sm-setup.md +2 -2
  54. package/pennyfarthing-dist/agents/sm.md +4 -11
  55. package/pennyfarthing-dist/commands/architect.md +11 -3
  56. package/pennyfarthing-dist/commands/close-epic.md +24 -131
  57. package/pennyfarthing-dist/commands/create-theme.md +14 -24
  58. package/pennyfarthing-dist/commands/dev.md +11 -3
  59. package/pennyfarthing-dist/commands/devops.md +11 -3
  60. package/pennyfarthing-dist/commands/health-check.md +1 -3
  61. package/pennyfarthing-dist/commands/help.md +8 -12
  62. package/pennyfarthing-dist/commands/list-themes.md +14 -16
  63. package/pennyfarthing-dist/commands/orchestrator.md +11 -3
  64. package/pennyfarthing-dist/commands/parallel-work.md +1 -3
  65. package/pennyfarthing-dist/commands/pm.md +11 -3
  66. package/pennyfarthing-dist/commands/prime.md +6 -6
  67. package/pennyfarthing-dist/commands/repo-status.md +2 -2
  68. package/pennyfarthing-dist/commands/reviewer.md +11 -3
  69. package/pennyfarthing-dist/commands/run-ci.md +1 -1
  70. package/pennyfarthing-dist/commands/set-theme.md +14 -51
  71. package/pennyfarthing-dist/commands/setup.md +1 -1
  72. package/pennyfarthing-dist/commands/show-theme.md +14 -16
  73. package/pennyfarthing-dist/commands/sm.md +11 -3
  74. package/pennyfarthing-dist/commands/sprint.md +8 -8
  75. package/pennyfarthing-dist/commands/tea.md +11 -3
  76. package/pennyfarthing-dist/commands/tech-writer.md +11 -3
  77. package/pennyfarthing-dist/commands/theme-maker.md +14 -671
  78. package/pennyfarthing-dist/commands/theme.md +95 -0
  79. package/pennyfarthing-dist/commands/ux-designer.md +11 -3
  80. package/pennyfarthing-dist/commands/work.md +3 -5
  81. package/pennyfarthing-dist/guides/agent-coordination.md +11 -13
  82. package/pennyfarthing-dist/guides/agent-template-tactical.md +2 -3
  83. package/pennyfarthing-dist/guides/command-tag-taxonomy.md +212 -0
  84. package/pennyfarthing-dist/guides/hooks.md +5 -5
  85. package/pennyfarthing-dist/guides/patterns/fan-out-fan-in-pattern.md +3 -3
  86. package/pennyfarthing-dist/guides/patterns/helper-delegation-pattern.md +9 -59
  87. package/pennyfarthing-dist/guides/patterns/tdd-flow-pattern.md +4 -5
  88. package/pennyfarthing-dist/guides/prime.md +2 -2
  89. package/pennyfarthing-dist/guides/skill-schema.md +25 -26
  90. package/pennyfarthing-dist/guides/xml-tags.md +2 -2
  91. package/pennyfarthing-dist/scripts/README.md +2 -2
  92. package/pennyfarthing-dist/scripts/core/agent-session.sh +6 -2
  93. package/pennyfarthing-dist/scripts/core/prime.sh +8 -10
  94. package/pennyfarthing-dist/scripts/git/git-status-all.sh +1 -1
  95. package/pennyfarthing-dist/scripts/git/install-git-hooks.sh +8 -6
  96. package/pennyfarthing-dist/scripts/git/worktree-manager.sh +3 -3
  97. package/pennyfarthing-dist/scripts/hooks/post-merge.sh +14 -12
  98. package/pennyfarthing-dist/scripts/hooks/pre-commit.sh +4 -3
  99. package/pennyfarthing-dist/scripts/hooks/pre-push.sh +11 -5
  100. package/pennyfarthing-dist/scripts/hooks/sprint-yaml-validation.sh +1 -1
  101. package/pennyfarthing-dist/scripts/misc/README.md +1 -1
  102. package/pennyfarthing-dist/scripts/misc/repo-utils.sh +3 -3
  103. package/pennyfarthing-dist/scripts/misc/validate-subagent-frontmatter.sh +1 -2
  104. package/pennyfarthing-dist/scripts/sprint/README.md +32 -17
  105. package/pennyfarthing-dist/scripts/story/README.md +1 -1
  106. package/pennyfarthing-dist/scripts/test/test-setup.sh +1 -1
  107. package/pennyfarthing-dist/scripts/tests/handoff-phase-update.test.sh +5 -5
  108. package/pennyfarthing-dist/scripts/tests/test-drift-detection.sh +3 -79
  109. package/pennyfarthing-dist/scripts/theme/README.md +1 -1
  110. package/pennyfarthing-dist/scripts/validation/validate-agent-schema.sh +0 -1
  111. package/pennyfarthing-dist/scripts/workflow/finish-story.sh +62 -17
  112. package/pennyfarthing-dist/skills/dev-patterns/SKILL.md +2 -2
  113. package/pennyfarthing-dist/skills/skill-registry.yaml +41 -28
  114. package/pennyfarthing-dist/skills/sprint/skill.md +386 -68
  115. package/pennyfarthing-dist/skills/story/skill.md +14 -206
  116. package/pennyfarthing-dist/skills/theme/skill.md +290 -75
  117. package/pennyfarthing-dist/skills/theme-creation/SKILL.md +23 -166
  118. package/pennyfarthing-dist/skills/workflow/skill.md +4 -4
  119. package/pennyfarthing-dist/templates/agent-scopes.yaml.template +0 -11
  120. package/pennyfarthing-dist/templates/auto-load-sm.sh.template +14 -0
  121. package/pennyfarthing-dist/templates/settings.local.json.template +9 -0
  122. package/pennyfarthing-dist/workflows/2party-tdd.yaml +399 -0
  123. package/pennyfarthing-dist/workflows/epics-and-stories/steps/step-05-import-to-future.md +42 -25
  124. package/pennyfarthing-dist/workflows/git-cleanup.yaml +1 -1
  125. package/pennyfarthing-dist/workflows/project-setup/steps/step-10-complete.md +1 -1
  126. package/pennyfarthing_scripts/__pycache__/cli.cpython-314.pyc +0 -0
  127. package/pennyfarthing_scripts/__pycache__/hooks.cpython-314.pyc +0 -0
  128. package/pennyfarthing_scripts/__pycache__/schema_validation_hook.cpython-314.pyc +0 -0
  129. package/pennyfarthing_scripts/__pycache__/workflow.cpython-314.pyc +0 -0
  130. package/pennyfarthing_scripts/cli.py +15 -0
  131. package/pennyfarthing_scripts/codemarkers/__init__.py +19 -0
  132. package/pennyfarthing_scripts/codemarkers/__main__.py +6 -0
  133. package/pennyfarthing_scripts/codemarkers/__pycache__/__init__.cpython-314.pyc +0 -0
  134. package/pennyfarthing_scripts/codemarkers/__pycache__/__main__.cpython-314.pyc +0 -0
  135. package/pennyfarthing_scripts/codemarkers/__pycache__/analyze.cpython-314.pyc +0 -0
  136. package/pennyfarthing_scripts/codemarkers/__pycache__/cli.cpython-314.pyc +0 -0
  137. package/pennyfarthing_scripts/codemarkers/__pycache__/formatters.cpython-314.pyc +0 -0
  138. package/pennyfarthing_scripts/codemarkers/__pycache__/models.cpython-314.pyc +0 -0
  139. package/pennyfarthing_scripts/codemarkers/analyze.py +326 -0
  140. package/pennyfarthing_scripts/codemarkers/cli.py +129 -0
  141. package/pennyfarthing_scripts/codemarkers/formatters.py +89 -0
  142. package/pennyfarthing_scripts/codemarkers/models.py +45 -0
  143. package/pennyfarthing_scripts/common/__pycache__/config.cpython-314.pyc +0 -0
  144. package/pennyfarthing_scripts/common/__pycache__/themes.cpython-314.pyc +0 -0
  145. package/pennyfarthing_scripts/common/config.py +2 -1
  146. package/pennyfarthing_scripts/complexity/__init__.py +15 -0
  147. package/pennyfarthing_scripts/complexity/__main__.py +6 -0
  148. package/pennyfarthing_scripts/complexity/__pycache__/__init__.cpython-314.pyc +0 -0
  149. package/pennyfarthing_scripts/complexity/__pycache__/__main__.cpython-314.pyc +0 -0
  150. package/pennyfarthing_scripts/complexity/__pycache__/analyze.cpython-314.pyc +0 -0
  151. package/pennyfarthing_scripts/complexity/__pycache__/cli.cpython-314.pyc +0 -0
  152. package/pennyfarthing_scripts/complexity/__pycache__/formatters.cpython-314.pyc +0 -0
  153. package/pennyfarthing_scripts/complexity/__pycache__/models.cpython-314.pyc +0 -0
  154. package/pennyfarthing_scripts/complexity/analyze.py +207 -0
  155. package/pennyfarthing_scripts/complexity/cli.py +78 -0
  156. package/pennyfarthing_scripts/complexity/formatters.py +64 -0
  157. package/pennyfarthing_scripts/complexity/models.py +32 -0
  158. package/pennyfarthing_scripts/deadcode/__init__.py +6 -0
  159. package/pennyfarthing_scripts/deadcode/__main__.py +6 -0
  160. package/pennyfarthing_scripts/deadcode/__pycache__/__init__.cpython-314.pyc +0 -0
  161. package/pennyfarthing_scripts/deadcode/__pycache__/__main__.cpython-314.pyc +0 -0
  162. package/pennyfarthing_scripts/deadcode/__pycache__/analyze.cpython-314.pyc +0 -0
  163. package/pennyfarthing_scripts/deadcode/__pycache__/cli.cpython-314.pyc +0 -0
  164. package/pennyfarthing_scripts/deadcode/__pycache__/formatters.cpython-314.pyc +0 -0
  165. package/pennyfarthing_scripts/deadcode/__pycache__/models.cpython-314.pyc +0 -0
  166. package/pennyfarthing_scripts/deadcode/analyze.py +323 -0
  167. package/pennyfarthing_scripts/deadcode/cli.py +163 -0
  168. package/pennyfarthing_scripts/deadcode/formatters.py +106 -0
  169. package/pennyfarthing_scripts/deadcode/models.py +54 -0
  170. package/pennyfarthing_scripts/dependencies/__init__.py +20 -0
  171. package/pennyfarthing_scripts/dependencies/__main__.py +5 -0
  172. package/pennyfarthing_scripts/dependencies/__pycache__/__init__.cpython-314.pyc +0 -0
  173. package/pennyfarthing_scripts/dependencies/__pycache__/__main__.cpython-314.pyc +0 -0
  174. package/pennyfarthing_scripts/dependencies/__pycache__/analyze.cpython-314.pyc +0 -0
  175. package/pennyfarthing_scripts/dependencies/__pycache__/cli.cpython-314.pyc +0 -0
  176. package/pennyfarthing_scripts/dependencies/__pycache__/formatters.cpython-314.pyc +0 -0
  177. package/pennyfarthing_scripts/dependencies/__pycache__/models.cpython-314.pyc +0 -0
  178. package/pennyfarthing_scripts/dependencies/analyze.py +155 -0
  179. package/pennyfarthing_scripts/dependencies/cli.py +72 -0
  180. package/pennyfarthing_scripts/dependencies/formatters.py +63 -0
  181. package/pennyfarthing_scripts/dependencies/models.py +39 -0
  182. package/pennyfarthing_scripts/healthscore/__init__.py +21 -0
  183. package/pennyfarthing_scripts/healthscore/__main__.py +6 -0
  184. package/pennyfarthing_scripts/healthscore/__pycache__/__init__.cpython-314.pyc +0 -0
  185. package/pennyfarthing_scripts/healthscore/__pycache__/__main__.cpython-314.pyc +0 -0
  186. package/pennyfarthing_scripts/healthscore/__pycache__/analyze.cpython-314.pyc +0 -0
  187. package/pennyfarthing_scripts/healthscore/__pycache__/cli.cpython-314.pyc +0 -0
  188. package/pennyfarthing_scripts/healthscore/__pycache__/formatters.cpython-314.pyc +0 -0
  189. package/pennyfarthing_scripts/healthscore/__pycache__/models.cpython-314.pyc +0 -0
  190. package/pennyfarthing_scripts/healthscore/analyze.py +161 -0
  191. package/pennyfarthing_scripts/healthscore/cli.py +76 -0
  192. package/pennyfarthing_scripts/healthscore/formatters.py +46 -0
  193. package/pennyfarthing_scripts/healthscore/models.py +44 -0
  194. package/pennyfarthing_scripts/hotspots/__pycache__/__init__.cpython-314.pyc +0 -0
  195. package/pennyfarthing_scripts/hotspots/__pycache__/__main__.cpython-314.pyc +0 -0
  196. package/pennyfarthing_scripts/hotspots/__pycache__/analyze.cpython-314.pyc +0 -0
  197. package/pennyfarthing_scripts/hotspots/__pycache__/cli.cpython-314.pyc +0 -0
  198. package/pennyfarthing_scripts/hotspots/__pycache__/formatters.cpython-314.pyc +0 -0
  199. package/pennyfarthing_scripts/hotspots/__pycache__/models.cpython-314.pyc +0 -0
  200. package/pennyfarthing_scripts/hotspots/analyze.py +28 -1
  201. package/pennyfarthing_scripts/hotspots/cli.py +11 -9
  202. package/pennyfarthing_scripts/jira/__pycache__/__init__.cpython-314.pyc +0 -0
  203. package/pennyfarthing_scripts/jira/__pycache__/bidirectional.cpython-314.pyc +0 -0
  204. package/pennyfarthing_scripts/jira/__pycache__/cli.cpython-314.pyc +0 -0
  205. package/pennyfarthing_scripts/jira/__pycache__/client.cpython-314.pyc +0 -0
  206. package/pennyfarthing_scripts/jira/__pycache__/create.cpython-314.pyc +0 -0
  207. package/pennyfarthing_scripts/jira/__pycache__/operations.cpython-314.pyc +0 -0
  208. package/pennyfarthing_scripts/jira/__pycache__/reconcile.cpython-314.pyc +0 -0
  209. package/pennyfarthing_scripts/jira/bidirectional.py +42 -15
  210. package/pennyfarthing_scripts/jira/cli.py +78 -1
  211. package/pennyfarthing_scripts/jira/client.py +28 -0
  212. package/pennyfarthing_scripts/prime/__pycache__/cli.cpython-314.pyc +0 -0
  213. package/pennyfarthing_scripts/prime/__pycache__/models.cpython-314.pyc +0 -0
  214. package/pennyfarthing_scripts/prime/__pycache__/persona.cpython-314.pyc +0 -0
  215. package/pennyfarthing_scripts/prime/__pycache__/tiers.cpython-314.pyc +0 -0
  216. package/pennyfarthing_scripts/prime/__pycache__/workflow.cpython-314.pyc +0 -0
  217. package/pennyfarthing_scripts/prime/workflow.py +5 -3
  218. package/pennyfarthing_scripts/sprint/__pycache__/archive.cpython-314.pyc +0 -0
  219. package/pennyfarthing_scripts/sprint/__pycache__/archive_epic.cpython-314.pyc +0 -0
  220. package/pennyfarthing_scripts/sprint/__pycache__/cli.cpython-314.pyc +0 -0
  221. package/pennyfarthing_scripts/sprint/__pycache__/epic_add.cpython-314.pyc +0 -0
  222. package/pennyfarthing_scripts/sprint/__pycache__/loader.cpython-314.pyc +0 -0
  223. package/pennyfarthing_scripts/sprint/__pycache__/story_add.cpython-314.pyc +0 -0
  224. package/pennyfarthing_scripts/sprint/__pycache__/story_finish.cpython-314.pyc +0 -0
  225. package/pennyfarthing_scripts/sprint/__pycache__/story_update.cpython-314.pyc +0 -0
  226. package/pennyfarthing_scripts/sprint/__pycache__/validate_cmd.cpython-314.pyc +0 -0
  227. package/pennyfarthing_scripts/sprint/__pycache__/validator.cpython-314.pyc +0 -0
  228. package/pennyfarthing_scripts/sprint/__pycache__/work.cpython-314.pyc +0 -0
  229. package/pennyfarthing_scripts/sprint/__pycache__/yaml_io.cpython-314.pyc +0 -0
  230. package/pennyfarthing_scripts/sprint/archive.py +63 -6
  231. package/pennyfarthing_scripts/sprint/archive_epic.py +198 -85
  232. package/pennyfarthing_scripts/sprint/cli.py +1565 -65
  233. package/pennyfarthing_scripts/sprint/epic_add.py +173 -0
  234. package/pennyfarthing_scripts/sprint/loader.py +46 -2
  235. package/pennyfarthing_scripts/sprint/story_add.py +202 -27
  236. package/pennyfarthing_scripts/sprint/story_finish.py +211 -0
  237. package/pennyfarthing_scripts/sprint/validate_cmd.py +44 -5
  238. package/pennyfarthing_scripts/sprint/validator.py +13 -3
  239. package/pennyfarthing_scripts/sprint/work.py +43 -3
  240. package/pennyfarthing_scripts/sprint/yaml_io.py +124 -15
  241. package/pennyfarthing_scripts/tests/__pycache__/test_codemarkers.cpython-314-pytest-9.0.2.pyc +0 -0
  242. package/pennyfarthing_scripts/tests/__pycache__/test_healthscore.cpython-314-pytest-9.0.2.pyc +0 -0
  243. package/pennyfarthing_scripts/tests/__pycache__/test_sprint_package.cpython-314-pytest-9.0.2.pyc +0 -0
  244. package/pennyfarthing_scripts/tests/__pycache__/test_sprint_validator.cpython-314-pytest-9.0.2.pyc +0 -0
  245. package/pennyfarthing_scripts/tests/__pycache__/test_story_add.cpython-314-pytest-9.0.2.pyc +0 -0
  246. package/pennyfarthing_scripts/tests/__pycache__/test_story_update.cpython-314-pytest-9.0.2.pyc +0 -0
  247. package/pennyfarthing_scripts/tests/__pycache__/test_validate_cmd.cpython-314-pytest-9.0.2.pyc +0 -0
  248. package/pennyfarthing_scripts/tests/__pycache__/test_yaml_io.cpython-314-pytest-9.0.2.pyc +0 -0
  249. package/pennyfarthing_scripts/tests/test_codemarkers.py +682 -0
  250. package/pennyfarthing_scripts/tests/test_healthscore.py +524 -0
  251. package/pennyfarthing_scripts/tests/test_sprint_package.py +166 -0
  252. package/pennyfarthing_scripts/tests/test_yaml_io.py +117 -0
  253. package/pennyfarthing_scripts/theme/__init__.py +5 -0
  254. package/pennyfarthing_scripts/theme/__main__.py +6 -0
  255. package/pennyfarthing_scripts/theme/__pycache__/__init__.cpython-314.pyc +0 -0
  256. package/pennyfarthing_scripts/theme/__pycache__/cli.cpython-314.pyc +0 -0
  257. package/pennyfarthing_scripts/theme/cli.py +286 -0
  258. package/scripts/README.md +53 -0
  259. package/scripts/postinstall.cjs +34 -0
  260. package/pennyfarthing-dist/agents/workflow-status-check.md +0 -96
  261. package/pennyfarthing-dist/scripts/sprint/archive-story.sh +0 -133
  262. package/pennyfarthing-dist/scripts/sprint/available-stories.sh +0 -91
  263. package/pennyfarthing-dist/scripts/sprint/check-story.sh +0 -158
  264. package/pennyfarthing-dist/scripts/sprint/get-epic-field.sh +0 -52
  265. package/pennyfarthing-dist/scripts/sprint/get-story-field.sh +0 -63
  266. package/pennyfarthing-dist/scripts/sprint/list-future.sh +0 -145
  267. package/pennyfarthing-dist/scripts/sprint/new-sprint.sh +0 -110
  268. package/pennyfarthing-dist/scripts/sprint/promote-epic.sh +0 -148
  269. package/pennyfarthing-dist/scripts/sprint/sprint-common.sh +0 -415
  270. package/pennyfarthing-dist/scripts/sprint/sprint-info.sh +0 -33
  271. package/pennyfarthing-dist/scripts/sprint/sprint-metrics.sh +0 -230
  272. package/pennyfarthing-dist/scripts/sprint/sprint-status.sh +0 -134
  273. package/pennyfarthing-dist/scripts/sprint/validate-sprint-yaml.sh +0 -139
  274. package/pennyfarthing-dist/skills/sprint/scripts/archive-story.sh +0 -101
  275. package/pennyfarthing-dist/skills/sprint/scripts/available-stories.sh +0 -97
  276. package/pennyfarthing-dist/skills/sprint/scripts/check-story.sh +0 -164
  277. package/pennyfarthing-dist/skills/sprint/scripts/create-jira-epic.sh +0 -23
  278. package/pennyfarthing-dist/skills/sprint/scripts/new-sprint.sh +0 -116
  279. package/pennyfarthing-dist/skills/sprint/scripts/promote-epic.sh +0 -164
  280. package/pennyfarthing-dist/skills/sprint/scripts/sprint-info.sh +0 -39
  281. package/pennyfarthing-dist/skills/sprint/scripts/sprint-status.sh +0 -147
  282. package/pennyfarthing-dist/skills/sprint/scripts/sync-epic-jira.sh +0 -23
@@ -0,0 +1,323 @@
1
+ """
2
+ Core dead code detection engine.
3
+
4
+ Layer 1: Compares git ls-files against git log --since to find files with no recent commits.
5
+ Layer 2: Runs ts-prune to find unused TypeScript exports.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import asyncio
11
+ import fnmatch
12
+ from datetime import datetime, timezone
13
+ from pathlib import Path
14
+
15
+ import re
16
+
17
+ from pennyfarthing_scripts.deadcode.models import (
18
+ DeadCodeResult,
19
+ StaleFile,
20
+ UnusedExport,
21
+ UnusedExportResult,
22
+ )
23
+
24
+ # Default file patterns to exclude from analysis
25
+ DEFAULT_EXCLUDES = [
26
+ "node_modules/*",
27
+ "dist/*",
28
+ "build/*",
29
+ "*.lock",
30
+ "*.min.js",
31
+ "*.min.css",
32
+ "package-lock.json",
33
+ "pnpm-lock.yaml",
34
+ ]
35
+
36
+ # Source file extensions to include
37
+ SOURCE_EXTENSIONS = {
38
+ ".py", ".ts", ".tsx", ".js", ".jsx", ".go", ".rs",
39
+ ".java", ".kt", ".swift", ".rb", ".sh", ".bash",
40
+ ".css", ".scss", ".less", ".html", ".md", ".yaml", ".yml",
41
+ ".json", ".toml",
42
+ }
43
+
44
+
45
+ async def _run_git_command(args: list[str], cwd: Path) -> tuple[str, str, int]:
46
+ """Run a git command asynchronously.
47
+
48
+ Args:
49
+ args: Git command arguments (without 'git')
50
+ cwd: Working directory
51
+
52
+ Returns:
53
+ (stdout, stderr, return_code)
54
+ """
55
+ proc = await asyncio.create_subprocess_exec(
56
+ "git",
57
+ *args,
58
+ cwd=cwd,
59
+ stdout=asyncio.subprocess.PIPE,
60
+ stderr=asyncio.subprocess.PIPE,
61
+ )
62
+ stdout, stderr = await proc.communicate()
63
+ return (
64
+ stdout.decode("utf-8", errors="replace").strip(),
65
+ stderr.decode("utf-8", errors="replace").strip(),
66
+ proc.returncode or 0,
67
+ )
68
+
69
+
70
+ def _should_exclude(path: str, patterns: list[str]) -> bool:
71
+ """Check if a file path matches any exclusion pattern."""
72
+ for pattern in patterns:
73
+ if fnmatch.fnmatch(path, pattern):
74
+ return True
75
+ if fnmatch.fnmatch(path.split("/")[-1], pattern):
76
+ return True
77
+ return False
78
+
79
+
80
+ def _is_source_file(path: str) -> bool:
81
+ """Check if a file has a recognized source extension."""
82
+ suffix = Path(path).suffix.lower()
83
+ return suffix in SOURCE_EXTENSIONS
84
+
85
+
86
+ async def find_stale_files(
87
+ repo_path: Path,
88
+ days: int = 180,
89
+ excludes: list[str] | None = None,
90
+ branch: str = "--all",
91
+ ) -> DeadCodeResult:
92
+ """Find files with no commits in the given time window.
93
+
94
+ Args:
95
+ repo_path: Path to the git repository
96
+ days: Time window in days
97
+ excludes: Additional file patterns to exclude
98
+ branch: Branch spec (default --all)
99
+
100
+ Returns:
101
+ DeadCodeResult with stale files
102
+ """
103
+ all_excludes = DEFAULT_EXCLUDES + (excludes or [])
104
+ resolved = Path(repo_path).resolve()
105
+
106
+ # Get all tracked files
107
+ ls_stdout, ls_stderr, ls_rc = await _run_git_command(["ls-files"], resolved)
108
+
109
+ if ls_rc != 0:
110
+ return DeadCodeResult(
111
+ success=False,
112
+ repo_name=resolved.name,
113
+ repo_path=str(resolved),
114
+ time_window_days=days,
115
+ error=f"git ls-files failed: {ls_stderr}",
116
+ )
117
+
118
+ all_files = set()
119
+ for line in ls_stdout.split("\n"):
120
+ line = line.strip()
121
+ if line:
122
+ all_files.add(line)
123
+
124
+ if not all_files:
125
+ return DeadCodeResult(
126
+ success=True,
127
+ repo_name=resolved.name,
128
+ repo_path=str(resolved),
129
+ time_window_days=days,
130
+ total_files=0,
131
+ )
132
+
133
+ # Get recently touched files
134
+ log_stdout, log_stderr, log_rc = await _run_git_command(
135
+ ["log", f"--since={days} days ago", branch, "--name-only", "--pretty=format:"],
136
+ resolved,
137
+ )
138
+
139
+ recent_files = set()
140
+ if log_rc == 0 and log_stdout:
141
+ for line in log_stdout.split("\n"):
142
+ line = line.strip()
143
+ if line:
144
+ recent_files.add(line)
145
+
146
+ # Set difference: stale = all - recent
147
+ candidate_stale = all_files - recent_files
148
+
149
+ # Filter: exclude patterns and non-source files
150
+ filtered = []
151
+ for fpath in candidate_stale:
152
+ if _should_exclude(fpath, all_excludes):
153
+ continue
154
+ if not _is_source_file(fpath):
155
+ continue
156
+ filtered.append(fpath)
157
+
158
+ # Enrich each stale file
159
+ now = datetime.now(timezone.utc)
160
+ stale_files = []
161
+ for fpath in sorted(filtered):
162
+ # Get last commit date
163
+ date_stdout, _, _ = await _run_git_command(
164
+ ["log", "-1", "--format=%aI", "--", fpath],
165
+ resolved,
166
+ )
167
+ last_commit_date = date_stdout.strip()
168
+
169
+ # Calculate days since last commit
170
+ days_since = 0
171
+ if last_commit_date:
172
+ try:
173
+ last_dt = datetime.fromisoformat(last_commit_date)
174
+ days_since = int((now - last_dt).total_seconds() / 86400)
175
+ except (ValueError, TypeError):
176
+ pass
177
+
178
+ # Get file size
179
+ size_bytes = 0
180
+ try:
181
+ full_path = resolved / fpath
182
+ size_bytes = full_path.stat().st_size
183
+ except (OSError, FileNotFoundError):
184
+ pass
185
+
186
+ stale_files.append(
187
+ StaleFile(
188
+ path=fpath,
189
+ last_commit_date=last_commit_date,
190
+ days_since_last_commit=days_since,
191
+ size_bytes=size_bytes,
192
+ )
193
+ )
194
+
195
+ return DeadCodeResult(
196
+ success=True,
197
+ repo_name=resolved.name,
198
+ repo_path=str(resolved),
199
+ time_window_days=days,
200
+ stale_files=stale_files,
201
+ total_files=len(all_files),
202
+ )
203
+
204
+
205
+ async def analyze_repo(
206
+ name: str,
207
+ path: Path,
208
+ days: int = 180,
209
+ excludes: list[str] | None = None,
210
+ branch: str = "--all",
211
+ ) -> DeadCodeResult:
212
+ """Analyze a single repository for stale files.
213
+
214
+ Args:
215
+ name: Display name for the repository
216
+ path: Path to the git repository
217
+ days: Time window in days
218
+ excludes: Additional file patterns to exclude
219
+ branch: Branch spec (default --all)
220
+
221
+ Returns:
222
+ DeadCodeResult with stale files
223
+ """
224
+ resolved = Path(path).resolve()
225
+
226
+ if not resolved.exists():
227
+ return DeadCodeResult(
228
+ success=False,
229
+ repo_name=name,
230
+ repo_path=str(resolved),
231
+ time_window_days=days,
232
+ error=f"Path not found: {resolved}",
233
+ )
234
+
235
+ result = await find_stale_files(resolved, days, excludes, branch)
236
+ # Override repo_name with the provided name
237
+ result.repo_name = name
238
+ return result
239
+
240
+
241
+ # ts-prune output line pattern: "path/to/file.ts:line - symbolName"
242
+ _TS_PRUNE_LINE_RE = re.compile(r"^(.+):(\d+) - (.+)$")
243
+
244
+
245
+ def _parse_ts_prune_output(output: str) -> list[UnusedExport]:
246
+ """Parse ts-prune stdout into UnusedExport instances.
247
+
248
+ ts-prune format: ``path/to/file.ts:10 - symbolName``
249
+ Lines ending with ``(used in module)`` are skipped (not truly unused).
250
+ """
251
+ exports: list[UnusedExport] = []
252
+ for line in output.strip().split("\n"):
253
+ line = line.strip()
254
+ if not line:
255
+ continue
256
+ # Skip "used in module" markers — these are re-exported and consumed
257
+ if line.endswith("(used in module)"):
258
+ continue
259
+
260
+ m = _TS_PRUNE_LINE_RE.match(line)
261
+ if not m:
262
+ continue
263
+
264
+ file_path, line_no, symbol = m.group(1), int(m.group(2)), m.group(3).strip()
265
+ export_type = "default" if symbol == "default" else "named"
266
+ exports.append(
267
+ UnusedExport(
268
+ symbol=symbol,
269
+ file=file_path,
270
+ line=line_no,
271
+ export_type=export_type,
272
+ )
273
+ )
274
+ return exports
275
+
276
+
277
+ async def find_unused_exports(repo_path: Path) -> UnusedExportResult:
278
+ """Find unused TypeScript exports via ts-prune.
279
+
280
+ Runs ``npx ts-prune`` in the repo and parses the output.
281
+
282
+ Args:
283
+ repo_path: Path to a TypeScript project with tsconfig.json
284
+
285
+ Returns:
286
+ UnusedExportResult with unused exports
287
+ """
288
+ resolved = Path(repo_path).resolve()
289
+
290
+ if not resolved.exists():
291
+ return UnusedExportResult(
292
+ success=False,
293
+ repo_name=resolved.name,
294
+ repo_path=str(resolved),
295
+ error=f"Path not found: {resolved}",
296
+ )
297
+
298
+ proc = await asyncio.create_subprocess_exec(
299
+ "npx", "ts-prune",
300
+ cwd=resolved,
301
+ stdout=asyncio.subprocess.PIPE,
302
+ stderr=asyncio.subprocess.PIPE,
303
+ )
304
+ stdout_bytes, stderr_bytes = await proc.communicate()
305
+ stdout = stdout_bytes.decode("utf-8", errors="replace").strip()
306
+ stderr = stderr_bytes.decode("utf-8", errors="replace").strip()
307
+
308
+ if proc.returncode and proc.returncode != 0:
309
+ return UnusedExportResult(
310
+ success=False,
311
+ repo_name=resolved.name,
312
+ repo_path=str(resolved),
313
+ error=f"ts-prune failed (exit {proc.returncode}): {stderr}",
314
+ )
315
+
316
+ exports = _parse_ts_prune_output(stdout)
317
+ return UnusedExportResult(
318
+ success=True,
319
+ repo_name=resolved.name,
320
+ repo_path=str(resolved),
321
+ unused_exports=exports,
322
+ total_exports_scanned=0, # ts-prune doesn't report total count
323
+ )
@@ -0,0 +1,163 @@
1
+ """
2
+ CLI commands for dead code detection.
3
+
4
+ Usage:
5
+ pf deadcode stale [OPTIONS]
6
+ pf deadcode exports [OPTIONS]
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import asyncio
12
+ from pathlib import Path
13
+
14
+ import click
15
+
16
+
17
+ @click.group()
18
+ def deadcode():
19
+ """Dead code detection tools.
20
+
21
+ \b
22
+ Commands:
23
+ stale - Find files with no recent commits
24
+ exports - Find unused TypeScript exports via ts-prune
25
+ """
26
+ pass
27
+
28
+
29
+ def _common_options(fn):
30
+ """Shared options for deadcode commands."""
31
+ fn = click.option("--repo", help="Analyze a single named repo from repos.yaml")(fn)
32
+ fn = click.option("--path", "repo_path", type=click.Path(exists=True), help="Analyze a standalone repo path")(fn)
33
+ fn = click.option("--days", default=180, show_default=True, help="Time window in days")(fn)
34
+ fn = click.option("--top", default=20, show_default=True, help="Number of top results to show")(fn)
35
+ fn = click.option("--format", "fmt", type=click.Choice(["table", "json", "csv"]), default="table", show_default=True)(fn)
36
+ fn = click.option("--output", "output_file", type=click.Path(), help="Write output to file")(fn)
37
+ fn = click.option("--exclude", multiple=True, help="Additional exclude patterns (repeatable)")(fn)
38
+ fn = click.option("--branch", default="--all", show_default=True, help="Branch spec for git log")(fn)
39
+ return fn
40
+
41
+
42
+ def _run_analysis(repo: str | None, repo_path: str | None, days: int, exclude: tuple, branch: str):
43
+ """Run analysis and return result."""
44
+ from pennyfarthing_scripts.deadcode.analyze import analyze_repo, find_stale_files
45
+ from pennyfarthing_scripts.common.config import get_project_root
46
+
47
+ excludes = list(exclude) if exclude else None
48
+
49
+ if repo_path:
50
+ p = Path(repo_path).resolve()
51
+ return asyncio.run(analyze_repo(p.name, p, days, excludes, branch))
52
+ elif repo:
53
+ project_root = get_project_root()
54
+ from pennyfarthing_scripts.common.config import load_yaml_config
55
+ repos_yaml = load_yaml_config(project_root / ".pennyfarthing" / "repos.yaml")
56
+ if repos_yaml and repo in repos_yaml:
57
+ cfg = repos_yaml[repo]
58
+ rpath = cfg.get("path", repo) if isinstance(cfg, dict) else str(cfg)
59
+ return asyncio.run(analyze_repo(repo, project_root / rpath, days, excludes, branch))
60
+ else:
61
+ candidate = project_root / repo
62
+ if candidate.exists():
63
+ return asyncio.run(analyze_repo(repo, candidate, days, excludes, branch))
64
+ raise click.ClickException(f"Repo not found: {repo}")
65
+ else:
66
+ project_root = get_project_root()
67
+ return asyncio.run(analyze_repo(project_root.name, project_root, days, excludes, branch))
68
+
69
+
70
+ def _output_result(result, fmt: str, output_file: str | None, top: int):
71
+ """Format and output the analysis result."""
72
+ from pennyfarthing_scripts.deadcode.formatters import (
73
+ export_csv,
74
+ export_json,
75
+ format_table,
76
+ )
77
+
78
+ if fmt == "json":
79
+ text = export_json(result)
80
+ elif fmt == "csv":
81
+ text = export_csv(result.stale_files[:top])
82
+ else:
83
+ text = format_table(result.stale_files, top)
84
+
85
+ if output_file:
86
+ Path(output_file).write_text(text)
87
+ click.echo(f"Output written to {output_file}", err=True)
88
+ else:
89
+ click.echo(text)
90
+
91
+
92
+ @deadcode.command()
93
+ @_common_options
94
+ def stale(repo, repo_path, days, top, fmt, output_file, exclude, branch):
95
+ """Find files with no recent git commits."""
96
+ result = _run_analysis(repo, repo_path, days, exclude, branch)
97
+ _output_result(result, fmt, output_file, top)
98
+
99
+
100
+ def _exports_options(fn):
101
+ """Shared options for exports subcommand."""
102
+ fn = click.option("--repo", help="Analyze a single named repo from repos.yaml")(fn)
103
+ fn = click.option("--path", "repo_path", type=click.Path(exists=True), help="Analyze a standalone repo path")(fn)
104
+ fn = click.option("--top", default=20, show_default=True, help="Number of top results to show")(fn)
105
+ fn = click.option("--format", "fmt", type=click.Choice(["table", "json", "csv"]), default="table", show_default=True)(fn)
106
+ fn = click.option("--output", "output_file", type=click.Path(), help="Write output to file")(fn)
107
+ return fn
108
+
109
+
110
+ def _run_exports_analysis(repo: str | None, repo_path: str | None):
111
+ """Run unused export analysis and return result."""
112
+ from pennyfarthing_scripts.deadcode.analyze import find_unused_exports
113
+ from pennyfarthing_scripts.common.config import get_project_root
114
+
115
+ if repo_path:
116
+ p = Path(repo_path).resolve()
117
+ return asyncio.run(find_unused_exports(p))
118
+ elif repo:
119
+ project_root = get_project_root()
120
+ from pennyfarthing_scripts.common.config import load_yaml_config
121
+ repos_yaml = load_yaml_config(project_root / ".pennyfarthing" / "repos.yaml")
122
+ if repos_yaml and repo in repos_yaml:
123
+ cfg = repos_yaml[repo]
124
+ rpath = cfg.get("path", repo) if isinstance(cfg, dict) else str(cfg)
125
+ return asyncio.run(find_unused_exports(project_root / rpath))
126
+ else:
127
+ candidate = project_root / repo
128
+ if candidate.exists():
129
+ return asyncio.run(find_unused_exports(candidate))
130
+ raise click.ClickException(f"Repo not found: {repo}")
131
+ else:
132
+ project_root = get_project_root()
133
+ return asyncio.run(find_unused_exports(project_root))
134
+
135
+
136
+ def _output_exports_result(result, fmt: str, output_file: str | None, top: int):
137
+ """Format and output the exports analysis result."""
138
+ from pennyfarthing_scripts.deadcode.formatters import (
139
+ export_exports_csv,
140
+ export_exports_json,
141
+ format_exports_table,
142
+ )
143
+
144
+ if fmt == "json":
145
+ text = export_exports_json(result)
146
+ elif fmt == "csv":
147
+ text = export_exports_csv(result.unused_exports[:top])
148
+ else:
149
+ text = format_exports_table(result.unused_exports, top)
150
+
151
+ if output_file:
152
+ Path(output_file).write_text(text)
153
+ click.echo(f"Output written to {output_file}", err=True)
154
+ else:
155
+ click.echo(text)
156
+
157
+
158
+ @deadcode.command()
159
+ @_exports_options
160
+ def exports(repo, repo_path, top, fmt, output_file):
161
+ """Find unused TypeScript exports via ts-prune."""
162
+ result = _run_exports_analysis(repo, repo_path)
163
+ _output_exports_result(result, fmt, output_file, top)
@@ -0,0 +1,106 @@
1
+ """
2
+ Output formatters for dead code analysis results.
3
+
4
+ Supports table, JSON, and CSV output formats.
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.deadcode.models import (
15
+ DeadCodeResult,
16
+ StaleFile,
17
+ UnusedExport,
18
+ UnusedExportResult,
19
+ )
20
+
21
+
22
+ def format_table(stale_files: list[StaleFile], top_n: int = 20) -> str:
23
+ """Format stale files as a human-readable table."""
24
+ if not stale_files:
25
+ return "No stale files found."
26
+
27
+ files = stale_files[:top_n]
28
+
29
+ # Column widths
30
+ path_width = max(len("Path"), max(len(f.path) for f in files))
31
+ header = f"{'Path':<{path_width}} {'Days':>6} {'Size':>10} {'Last Commit'}"
32
+ separator = "-" * len(header)
33
+
34
+ lines = [header, separator]
35
+ for f in files:
36
+ size_str = _format_size(f.size_bytes)
37
+ date_str = f.last_commit_date[:10] if f.last_commit_date else "unknown"
38
+ lines.append(
39
+ f"{f.path:<{path_width}} {f.days_since_last_commit:>6} {size_str:>10} {date_str}"
40
+ )
41
+
42
+ return "\n".join(lines)
43
+
44
+
45
+ def _format_size(size_bytes: int) -> str:
46
+ """Format bytes as human-readable size."""
47
+ if size_bytes < 1024:
48
+ return f"{size_bytes} B"
49
+ elif size_bytes < 1024 * 1024:
50
+ return f"{size_bytes / 1024:.1f} KB"
51
+ else:
52
+ return f"{size_bytes / (1024 * 1024):.1f} MB"
53
+
54
+
55
+ def export_json(result: DeadCodeResult) -> str:
56
+ """Export result as JSON."""
57
+ return json.dumps(asdict(result), indent=2)
58
+
59
+
60
+ def export_csv(stale_files: list[StaleFile]) -> str:
61
+ """Export stale files as CSV."""
62
+ output = io.StringIO()
63
+ writer = csv.writer(output)
64
+ writer.writerow(["path", "last_commit_date", "days_since_last_commit", "size_bytes"])
65
+ for f in stale_files:
66
+ writer.writerow([f.path, f.last_commit_date, f.days_since_last_commit, f.size_bytes])
67
+ return output.getvalue()
68
+
69
+
70
+ # ---- Unused export formatters ----
71
+
72
+
73
+ def format_exports_table(unused_exports: list[UnusedExport], top_n: int = 20) -> str:
74
+ """Format unused exports as a human-readable table."""
75
+ if not unused_exports:
76
+ return "No unused exports found."
77
+
78
+ exports = unused_exports[:top_n]
79
+
80
+ file_width = max(len("File"), max(len(ue.file) for ue in exports))
81
+ symbol_width = max(len("Symbol"), max(len(ue.symbol) for ue in exports))
82
+ header = f"{'File':<{file_width}} {'Line':>5} {'Symbol':<{symbol_width}} {'Type'}"
83
+ separator = "-" * len(header)
84
+
85
+ lines = [header, separator]
86
+ for ue in exports:
87
+ lines.append(
88
+ f"{ue.file:<{file_width}} {ue.line:>5} {ue.symbol:<{symbol_width}} {ue.export_type}"
89
+ )
90
+
91
+ return "\n".join(lines)
92
+
93
+
94
+ def export_exports_json(result: UnusedExportResult) -> str:
95
+ """Export unused export result as JSON."""
96
+ return json.dumps(asdict(result), indent=2)
97
+
98
+
99
+ def export_exports_csv(unused_exports: list[UnusedExport]) -> str:
100
+ """Export unused exports as CSV."""
101
+ output = io.StringIO()
102
+ writer = csv.writer(output)
103
+ writer.writerow(["symbol", "file", "line", "export_type"])
104
+ for ue in unused_exports:
105
+ writer.writerow([ue.symbol, ue.file, ue.line, ue.export_type])
106
+ return output.getvalue()
@@ -0,0 +1,54 @@
1
+ """
2
+ Data models for dead code 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 StaleFile:
14
+ """A file detected as stale (no commits within time window)."""
15
+
16
+ path: str
17
+ last_commit_date: str = "" # ISO 8601
18
+ days_since_last_commit: int = 0
19
+ size_bytes: int = 0
20
+
21
+
22
+ @dataclass
23
+ class DeadCodeResult:
24
+ """Analysis result for stale file detection in a single repository."""
25
+
26
+ success: bool
27
+ repo_name: str
28
+ repo_path: str
29
+ time_window_days: int
30
+ stale_files: list[StaleFile] = field(default_factory=list)
31
+ total_files: int = 0
32
+ error: str | None = None
33
+
34
+
35
+ @dataclass
36
+ class UnusedExport:
37
+ """An exported symbol with no importers, detected by ts-prune."""
38
+
39
+ symbol: str
40
+ file: str
41
+ line: int
42
+ export_type: str = "named" # named, default, re-export
43
+
44
+
45
+ @dataclass
46
+ class UnusedExportResult:
47
+ """Analysis result for unused export detection in a single repository."""
48
+
49
+ success: bool
50
+ repo_name: str
51
+ repo_path: str
52
+ unused_exports: list[UnusedExport] = field(default_factory=list)
53
+ total_exports_scanned: int = 0
54
+ error: str | None = None
@@ -0,0 +1,20 @@
1
+ """
2
+ Dependency staleness and security analysis for Node.js projects.
3
+
4
+ Wraps npm outdated and npm audit to detect outdated packages
5
+ and security vulnerabilities.
6
+ """
7
+
8
+ from pennyfarthing_scripts.dependencies.models import (
9
+ OutdatedPackage,
10
+ SecurityAdvisory,
11
+ DependenciesResult,
12
+ )
13
+ from pennyfarthing_scripts.dependencies.analyze import analyze_dependencies
14
+
15
+ __all__ = [
16
+ "OutdatedPackage",
17
+ "SecurityAdvisory",
18
+ "DependenciesResult",
19
+ "analyze_dependencies",
20
+ ]
@@ -0,0 +1,5 @@
1
+ """Allow running as: python -m pennyfarthing_scripts.dependencies"""
2
+
3
+ from pennyfarthing_scripts.dependencies.cli import dependencies
4
+
5
+ dependencies()