@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
@@ -0,0 +1,501 @@
1
+ """
2
+ Core code marker analysis engine.
3
+
4
+ Greps source files for TODO/FIXME/HACK/XXX markers, runs git blame for
5
+ author and age data, and computes staleness.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import asyncio
11
+ import fnmatch
12
+ import re
13
+ import time
14
+ from collections import defaultdict
15
+ from datetime import UTC, datetime
16
+ from pathlib import Path
17
+
18
+ from pennyfarthing_scripts.codemarkers.models import (
19
+ CodeMarker,
20
+ CodeMarkersResult,
21
+ DeprecationMarker,
22
+ MarkerSummary,
23
+ )
24
+
25
+ # Marker types to detect (case-sensitive, uppercase only)
26
+ MARKER_PATTERN = re.compile(r"\b(TODO|FIXME|HACK|XXX)\b")
27
+
28
+ # Default file patterns to exclude from analysis
29
+ DEFAULT_EXCLUDES = [
30
+ "node_modules/*",
31
+ "dist/*",
32
+ "build/*",
33
+ "*.lock",
34
+ "*.min.js",
35
+ "*.min.css",
36
+ "*.map",
37
+ "package-lock.json",
38
+ "pnpm-lock.yaml",
39
+ ]
40
+
41
+ # Binary file extensions to skip
42
+ _BINARY_EXTENSIONS = frozenset({
43
+ ".png", ".jpg", ".jpeg", ".gif", ".bmp", ".ico", ".svg",
44
+ ".woff", ".woff2", ".ttf", ".eot", ".otf",
45
+ ".zip", ".gz", ".tar", ".bz2", ".7z", ".rar",
46
+ ".pdf", ".doc", ".docx", ".xls", ".xlsx",
47
+ ".exe", ".dll", ".so", ".dylib", ".o", ".a",
48
+ ".pyc", ".pyo", ".class", ".jar",
49
+ ".mp3", ".mp4", ".wav", ".avi", ".mov",
50
+ ".sqlite", ".db",
51
+ })
52
+
53
+
54
+ def _should_exclude(path: str, patterns: list[str]) -> bool:
55
+ """Check if a file path matches any exclusion pattern."""
56
+ for pattern in patterns:
57
+ if fnmatch.fnmatch(path, pattern):
58
+ return True
59
+ # Also check the basename for patterns like "*.lock"
60
+ if fnmatch.fnmatch(path.split("/")[-1], pattern):
61
+ return True
62
+ return False
63
+
64
+
65
+ def _grep_markers(root: Path, excludes: list[str]) -> list[dict]:
66
+ """Scan files under root for TODO/FIXME/HACK/XXX markers.
67
+
68
+ Args:
69
+ root: Directory to scan recursively
70
+ excludes: Glob patterns for files/dirs to skip
71
+
72
+ Returns:
73
+ List of dicts: {path, line, marker_type, text}
74
+ """
75
+ results: list[dict] = []
76
+
77
+ for file_path in sorted(root.rglob("*")):
78
+ if not file_path.is_file():
79
+ continue
80
+
81
+ # Skip binary files by extension
82
+ if file_path.suffix.lower() in _BINARY_EXTENSIONS:
83
+ continue
84
+
85
+ # Get relative path for exclusion matching
86
+ rel_path = str(file_path.relative_to(root))
87
+
88
+ if _should_exclude(rel_path, excludes):
89
+ continue
90
+
91
+ try:
92
+ content = file_path.read_text(encoding="utf-8", errors="strict")
93
+ except (UnicodeDecodeError, OSError):
94
+ continue
95
+
96
+ for line_num, line_text in enumerate(content.split("\n"), start=1):
97
+ match = MARKER_PATTERN.search(line_text)
98
+ if match:
99
+ results.append({
100
+ "path": rel_path,
101
+ "line": line_num,
102
+ "marker_type": match.group(1),
103
+ "text": line_text.strip(),
104
+ })
105
+
106
+ return results
107
+
108
+
109
+ def _parse_blame_porcelain(output: str, line: int) -> dict:
110
+ """Parse git blame --porcelain output for a specific line.
111
+
112
+ Extracts author name and author-time (Unix timestamp).
113
+
114
+ Args:
115
+ output: Raw porcelain output from git blame
116
+ line: Line number to extract (used for context)
117
+
118
+ Returns:
119
+ Dict with 'author' and 'author_time' keys, or empty dict
120
+ """
121
+ if not output.strip():
122
+ return {}
123
+
124
+ author = ""
125
+ author_time = 0
126
+
127
+ for raw_line in output.split("\n"):
128
+ if raw_line.startswith("author "):
129
+ author = raw_line[7:]
130
+ elif raw_line.startswith("author-time "):
131
+ try:
132
+ author_time = int(raw_line[12:])
133
+ except ValueError:
134
+ pass
135
+
136
+ if not author:
137
+ return {}
138
+
139
+ return {"author": author, "author_time": author_time}
140
+
141
+
142
+ async def _run_git_command(args: list[str], cwd: Path) -> tuple[str, str, int]:
143
+ """Run a git command asynchronously.
144
+
145
+ Args:
146
+ args: Git command arguments (without 'git')
147
+ cwd: Working directory
148
+
149
+ Returns:
150
+ (stdout, stderr, return_code)
151
+ """
152
+ proc = await asyncio.create_subprocess_exec(
153
+ "git",
154
+ *args,
155
+ cwd=cwd,
156
+ stdout=asyncio.subprocess.PIPE,
157
+ stderr=asyncio.subprocess.PIPE,
158
+ )
159
+ stdout, stderr = await proc.communicate()
160
+ return (
161
+ stdout.decode("utf-8", errors="replace").strip(),
162
+ stderr.decode("utf-8", errors="replace").strip(),
163
+ proc.returncode or 0,
164
+ )
165
+
166
+
167
+ async def _batch_blame_file(
168
+ repo_path: Path, file_path: str, lines: list[int]
169
+ ) -> dict[int, dict]:
170
+ """Blame an entire file once and extract data for requested lines.
171
+
172
+ Args:
173
+ repo_path: Root of the git repository
174
+ file_path: Relative path to file within repo
175
+ lines: Line numbers to extract blame data for
176
+
177
+ Returns:
178
+ Dict mapping line number -> {author, author_time}
179
+ """
180
+ stdout, stderr, rc = await _run_git_command(
181
+ ["blame", "--porcelain", file_path], repo_path
182
+ )
183
+
184
+ if rc != 0:
185
+ return {}
186
+
187
+ # Parse porcelain: each block starts with "<hash> <orig_line> <final_line> <count>"
188
+ results: dict[int, dict] = {}
189
+ current_line = 0
190
+ current_author = ""
191
+ current_time = 0
192
+ lines_set = set(lines)
193
+
194
+ for raw_line in stdout.split("\n"):
195
+ # Header line: hash orig_line final_line [count]
196
+ # Detect by checking that parts[1] is a digit (orig_line number)
197
+ parts = raw_line.split(" ")
198
+ if len(parts) >= 3 and len(parts[0]) >= 6 and parts[1].isdigit():
199
+ try:
200
+ current_line = int(parts[2])
201
+ except (ValueError, IndexError):
202
+ pass
203
+ current_author = ""
204
+ current_time = 0
205
+ elif raw_line.startswith("author "):
206
+ current_author = raw_line[7:]
207
+ elif raw_line.startswith("author-time "):
208
+ try:
209
+ current_time = int(raw_line[12:])
210
+ except ValueError:
211
+ pass
212
+ elif raw_line.startswith("\t"):
213
+ # Content line — end of this block
214
+ if current_line in lines_set:
215
+ results[current_line] = {
216
+ "author": current_author,
217
+ "author_time": current_time,
218
+ }
219
+
220
+ return results
221
+
222
+
223
+ async def analyze_repo(
224
+ name: str,
225
+ path: Path,
226
+ days: int = 90,
227
+ excludes: list[str] | None = None,
228
+ ) -> CodeMarkersResult:
229
+ """Analyze a repository for code markers.
230
+
231
+ Args:
232
+ name: Display name for the repository
233
+ path: Path to the git repository
234
+ days: Stale threshold in days
235
+ excludes: Additional file patterns to exclude
236
+
237
+ Returns:
238
+ CodeMarkersResult with markers and summary
239
+ """
240
+ resolved = Path(path).resolve()
241
+
242
+ if not resolved.exists():
243
+ return CodeMarkersResult(
244
+ success=False,
245
+ repo_name=name,
246
+ repo_path=str(resolved),
247
+ stale_threshold_days=days,
248
+ error=f"Path not found: {resolved}",
249
+ )
250
+
251
+ all_excludes = DEFAULT_EXCLUDES + (excludes or [])
252
+
253
+ # Grep for markers
254
+ raw_markers = _grep_markers(resolved, all_excludes)
255
+
256
+ if not raw_markers:
257
+ return CodeMarkersResult(
258
+ success=True,
259
+ repo_name=name,
260
+ repo_path=str(resolved),
261
+ stale_threshold_days=days,
262
+ markers=[],
263
+ summary=MarkerSummary(),
264
+ )
265
+
266
+ # Group markers by file for batch blame
267
+ by_file: dict[str, list[dict]] = defaultdict(list)
268
+ for m in raw_markers:
269
+ by_file[m["path"]].append(m)
270
+
271
+ # Blame each file once
272
+ now = time.time()
273
+ markers: list[CodeMarker] = []
274
+ stale_count = 0
275
+ type_counts: dict[str, int] = defaultdict(int)
276
+
277
+ for file_path, file_markers in by_file.items():
278
+ line_numbers = [m["line"] for m in file_markers]
279
+ blame_data = await _batch_blame_file(resolved, file_path, line_numbers)
280
+
281
+ for m in file_markers:
282
+ blame = blame_data.get(m["line"], {})
283
+ author = blame.get("author", "")
284
+ author_time = blame.get("author_time", 0)
285
+
286
+ # Compute age
287
+ if author_time > 0:
288
+ age_days = (now - author_time) / 86400
289
+ date_str = datetime.fromtimestamp(
290
+ author_time, tz=UTC
291
+ ).isoformat()
292
+ else:
293
+ age_days = 0.0
294
+ date_str = ""
295
+
296
+ is_stale = age_days > days
297
+
298
+ marker = CodeMarker(
299
+ path=m["path"],
300
+ line=m["line"],
301
+ marker_type=m["marker_type"],
302
+ text=m["text"],
303
+ author=author,
304
+ date=date_str,
305
+ age_days=round(age_days, 1),
306
+ is_stale=is_stale,
307
+ )
308
+ markers.append(marker)
309
+
310
+ type_counts[m["marker_type"]] += 1
311
+ if is_stale:
312
+ stale_count += 1
313
+
314
+ summary = MarkerSummary(
315
+ total_markers=len(markers),
316
+ stale_markers=stale_count,
317
+ by_type=dict(type_counts),
318
+ )
319
+
320
+ return CodeMarkersResult(
321
+ success=True,
322
+ repo_name=name,
323
+ repo_path=str(resolved),
324
+ stale_threshold_days=days,
325
+ markers=markers,
326
+ summary=summary,
327
+ )
328
+
329
+
330
+ # =============================================================================
331
+ # @deprecated detection (Story 80-2)
332
+ # =============================================================================
333
+
334
+ # File extensions to scan for @deprecated JSDoc tags
335
+ _DEPRECATION_EXTENSIONS = frozenset({".ts", ".tsx", ".js"})
336
+
337
+ # Pattern to find @deprecated in JSDoc comments
338
+ _DEPRECATED_PATTERN = re.compile(r"@deprecated\b(.*)")
339
+
340
+ # Pattern to extract symbol name from export declarations
341
+ _SYMBOL_PATTERN = re.compile(
342
+ r"export\s+(?:default\s+)?(?:function|class|const|let|var|interface|type|enum)\s+(\w+)"
343
+ )
344
+
345
+
346
+ def _grep_deprecations(root: Path, excludes: list[str]) -> list[dict]:
347
+ """Scan TypeScript/JS files for @deprecated JSDoc tags.
348
+
349
+ For each @deprecated tag found, extracts the symbol name from the
350
+ next export declaration line following the JSDoc block.
351
+
352
+ Args:
353
+ root: Directory to scan recursively
354
+ excludes: Glob patterns for files/dirs to skip
355
+
356
+ Returns:
357
+ List of dicts: {path, line, symbol, text}
358
+ """
359
+ results: list[dict] = []
360
+
361
+ for file_path in sorted(root.rglob("*")):
362
+ if not file_path.is_file():
363
+ continue
364
+
365
+ if file_path.suffix.lower() not in _DEPRECATION_EXTENSIONS:
366
+ continue
367
+
368
+ rel_path = str(file_path.relative_to(root))
369
+
370
+ if _should_exclude(rel_path, excludes):
371
+ continue
372
+
373
+ try:
374
+ content = file_path.read_text(encoding="utf-8", errors="strict")
375
+ except (UnicodeDecodeError, OSError):
376
+ continue
377
+
378
+ lines = content.split("\n")
379
+ for line_num, line_text in enumerate(lines, start=1):
380
+ match = _DEPRECATED_PATTERN.search(line_text)
381
+ if not match:
382
+ continue
383
+
384
+ # Extract the @deprecated text
385
+ deprecated_text = line_text.strip()
386
+
387
+ # Look ahead for the symbol declaration
388
+ symbol = ""
389
+ for ahead in lines[line_num:]: # line_num is already 1-indexed, so lines[line_num:] starts after current
390
+ sym_match = _SYMBOL_PATTERN.search(ahead)
391
+ if sym_match:
392
+ symbol = sym_match.group(1)
393
+ break
394
+ # Stop looking if we hit another JSDoc or a blank line after the block closes
395
+ stripped = ahead.strip()
396
+ if stripped and not stripped.startswith("*") and not stripped.startswith("/") and not stripped == "":
397
+ break
398
+
399
+ if symbol:
400
+ results.append({
401
+ "path": rel_path,
402
+ "line": line_num,
403
+ "symbol": symbol,
404
+ "text": deprecated_text,
405
+ })
406
+
407
+ return results
408
+
409
+
410
+ def _count_callers(
411
+ symbol: str, root: Path, defining_file: str
412
+ ) -> tuple[int, list[str]]:
413
+ """Count files that import/reference a deprecated symbol.
414
+
415
+ Greps all TypeScript/JS files for the symbol name, excluding
416
+ the file that defines it.
417
+
418
+ Args:
419
+ symbol: The deprecated symbol name to search for
420
+ root: Directory to scan
421
+ defining_file: Relative path of the file defining the symbol (excluded)
422
+
423
+ Returns:
424
+ (caller_count, list_of_caller_paths)
425
+ """
426
+ callers: list[str] = []
427
+
428
+ for file_path in sorted(root.rglob("*")):
429
+ if not file_path.is_file():
430
+ continue
431
+
432
+ if file_path.suffix.lower() not in _DEPRECATION_EXTENSIONS:
433
+ continue
434
+
435
+ rel_path = str(file_path.relative_to(root))
436
+
437
+ if rel_path == defining_file:
438
+ continue
439
+
440
+ try:
441
+ content = file_path.read_text(encoding="utf-8", errors="strict")
442
+ except (UnicodeDecodeError, OSError):
443
+ continue
444
+
445
+ if symbol in content:
446
+ callers.append(rel_path)
447
+
448
+ return len(callers), callers
449
+
450
+
451
+ async def analyze_deprecations(
452
+ path: Path,
453
+ excludes: list[str] | None = None,
454
+ ) -> dict:
455
+ """Analyze a directory for @deprecated symbols and their callers.
456
+
457
+ Args:
458
+ path: Directory to scan
459
+ excludes: Additional file patterns to exclude
460
+
461
+ Returns:
462
+ Dict with success, deprecations (list of DeprecationMarker),
463
+ summary (total_deprecations, deprecations_with_callers), and
464
+ optionally error.
465
+ """
466
+ resolved = Path(path).resolve()
467
+
468
+ if not resolved.exists():
469
+ return {
470
+ "success": False,
471
+ "error": f"Path not found: {resolved}",
472
+ }
473
+
474
+ all_excludes = DEFAULT_EXCLUDES + (excludes or [])
475
+
476
+ raw = _grep_deprecations(resolved, all_excludes)
477
+
478
+ markers: list[DeprecationMarker] = []
479
+ for item in raw:
480
+ count, caller_list = _count_callers(
481
+ item["symbol"], resolved, item["path"]
482
+ )
483
+ markers.append(DeprecationMarker(
484
+ path=item["path"],
485
+ line=item["line"],
486
+ symbol=item["symbol"],
487
+ text=item["text"],
488
+ caller_count=count,
489
+ callers=caller_list,
490
+ ))
491
+
492
+ with_callers = sum(1 for m in markers if m.caller_count > 0)
493
+
494
+ return {
495
+ "success": True,
496
+ "deprecations": markers,
497
+ "summary": {
498
+ "total_deprecations": len(markers),
499
+ "deprecations_with_callers": with_callers,
500
+ },
501
+ }
@@ -0,0 +1,179 @@
1
+ """
2
+ CLI commands for code marker analysis.
3
+
4
+ Usage:
5
+ pf codemarkers analyze [OPTIONS]
6
+ pf codemarkers stale [OPTIONS]
7
+ pf codemarkers summary [OPTIONS]
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import asyncio
13
+ from pathlib import Path
14
+
15
+ import click
16
+
17
+
18
+ @click.group()
19
+ def codemarkers():
20
+ """Code marker detection (TODO, FIXME, HACK, XXX).
21
+
22
+ \b
23
+ Commands:
24
+ analyze - Full marker analysis with blame data
25
+ stale - Show only stale markers (older than threshold)
26
+ summary - Summary counts by marker type
27
+ """
28
+ pass
29
+
30
+
31
+ def _common_options(fn):
32
+ """Shared options for all codemarkers commands."""
33
+ fn = click.option("--repo", help="Analyze a single named repo from repos.yaml")(fn)
34
+ fn = click.option("--path", "repo_path", type=click.Path(), help="Analyze a standalone repo path")(fn)
35
+ fn = click.option("--days", default=90, show_default=True, help="Stale threshold in days")(fn)
36
+ fn = click.option("--top", default=20, show_default=True, help="Number of top results to show")(fn)
37
+ fn = click.option("--format", "fmt", type=click.Choice(["table", "json", "csv"]), default="table", show_default=True)(fn)
38
+ fn = click.option("--output", "output_file", type=click.Path(), help="Write output to file")(fn)
39
+ fn = click.option("--exclude", multiple=True, help="Additional exclude patterns (repeatable)")(fn)
40
+ return fn
41
+
42
+
43
+ def _run_analysis(repo: str | None, repo_path: str | None, days: int, exclude: tuple):
44
+ """Run analysis and return result."""
45
+ from pennyfarthing_scripts.codemarkers.analyze import analyze_repo
46
+ from pennyfarthing_scripts.common.config import get_project_root
47
+
48
+ excludes = list(exclude) if exclude else None
49
+
50
+ if repo_path:
51
+ p = Path(repo_path).resolve()
52
+ return asyncio.run(analyze_repo(p.name, p, days, excludes))
53
+ elif repo:
54
+ project_root = get_project_root()
55
+ from pennyfarthing_scripts.common.config import load_yaml_config
56
+ repos_yaml = load_yaml_config(project_root / ".pennyfarthing" / "repos.yaml")
57
+ if repos_yaml and repo in repos_yaml:
58
+ cfg = repos_yaml[repo]
59
+ rpath = cfg.get("path", repo) if isinstance(cfg, dict) else str(cfg)
60
+ return asyncio.run(
61
+ analyze_repo(repo, project_root / rpath, days, excludes)
62
+ )
63
+ else:
64
+ candidate = project_root / repo
65
+ if candidate.exists():
66
+ return asyncio.run(analyze_repo(repo, candidate, days, excludes))
67
+ raise click.ClickException(f"Repo not found: {repo}")
68
+ else:
69
+ project_root = get_project_root()
70
+ return asyncio.run(
71
+ analyze_repo(project_root.name, project_root, days, excludes)
72
+ )
73
+
74
+
75
+ def _output_result(result, fmt: str, output_file: str | None, top: int, mode: str):
76
+ """Format and output the analysis result."""
77
+ from pennyfarthing_scripts.codemarkers.formatters import (
78
+ export_csv,
79
+ export_json,
80
+ format_marker_table,
81
+ format_summary,
82
+ )
83
+
84
+ if fmt == "json":
85
+ text = export_json(result)
86
+ elif fmt == "csv":
87
+ text = export_csv(result.markers[:top])
88
+ else:
89
+ format_summary(result)
90
+ if mode == "stale":
91
+ stale = [m for m in result.markers if m.is_stale]
92
+ text = format_marker_table(stale, top)
93
+ else:
94
+ text = format_marker_table(result.markers, top)
95
+
96
+ if output_file:
97
+ Path(output_file).write_text(text)
98
+ click.echo(f"Output written to {output_file}", err=True)
99
+ else:
100
+ click.echo(text)
101
+
102
+
103
+ @codemarkers.command()
104
+ @_common_options
105
+ def analyze(repo, repo_path, days, top, fmt, output_file, exclude):
106
+ """Full marker analysis with blame data."""
107
+ result = _run_analysis(repo, repo_path, days, exclude)
108
+ _output_result(result, fmt, output_file, top, "analyze")
109
+
110
+
111
+ @codemarkers.command()
112
+ @_common_options
113
+ def stale(repo, repo_path, days, top, fmt, output_file, exclude):
114
+ """Show only stale markers (older than threshold)."""
115
+ result = _run_analysis(repo, repo_path, days, exclude)
116
+ _output_result(result, fmt, output_file, top, "stale")
117
+
118
+
119
+ @codemarkers.command()
120
+ @_common_options
121
+ def summary(repo, repo_path, days, top, fmt, output_file, exclude):
122
+ """Summary counts by marker type."""
123
+ result = _run_analysis(repo, repo_path, days, exclude)
124
+ if fmt == "json":
125
+ from pennyfarthing_scripts.codemarkers.formatters import export_json
126
+ click.echo(export_json(result))
127
+ else:
128
+ from pennyfarthing_scripts.codemarkers.formatters import format_summary
129
+ format_summary(result, file=click.get_text_stream("stdout"))
130
+
131
+
132
+ def _run_deprecation_analysis(repo_path, exclude):
133
+ """Run deprecation analysis and return result dict."""
134
+ from pennyfarthing_scripts.codemarkers.analyze import analyze_deprecations
135
+
136
+ excludes = list(exclude) if exclude else None
137
+ if repo_path:
138
+ p = Path(repo_path).resolve()
139
+ else:
140
+ from pennyfarthing_scripts.common.config import get_project_root
141
+ p = get_project_root()
142
+
143
+ return asyncio.run(analyze_deprecations(p, excludes))
144
+
145
+
146
+ @codemarkers.command()
147
+ @_common_options
148
+ def deprecations(repo, repo_path, days, top, fmt, output_file, exclude):
149
+ """Scan for @deprecated symbols and cross-reference callers."""
150
+ result = _run_deprecation_analysis(repo_path, exclude)
151
+
152
+ if fmt == "json":
153
+ import json
154
+ from dataclasses import asdict
155
+
156
+ output = {
157
+ "success": result["success"],
158
+ "deprecations": [asdict(m) for m in result.get("deprecations", [])],
159
+ "summary": result.get("summary", {}),
160
+ }
161
+ text = json.dumps(output, indent=2)
162
+ else:
163
+ lines = []
164
+ deps = result.get("deprecations", [])
165
+ summary = result.get("summary", {})
166
+ lines.append(f"Total deprecations: {summary.get('total_deprecations', 0)}")
167
+ lines.append(f"With active callers: {summary.get('deprecations_with_callers', 0)}")
168
+ lines.append("")
169
+ for m in deps[:top]:
170
+ callers_str = f" ({m.caller_count} callers)" if m.caller_count else ""
171
+ lines.append(f" {m.symbol} @ {m.path}:{m.line}{callers_str}")
172
+ lines.append(f" {m.text}")
173
+ text = "\n".join(lines)
174
+
175
+ if output_file:
176
+ Path(output_file).write_text(text)
177
+ click.echo(f"Output written to {output_file}", err=True)
178
+ else:
179
+ click.echo(text)