@pennyfarthing/core 10.1.0 → 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 (407) hide show
  1. package/README.md +13 -18
  2. package/package.json +3 -1
  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 +101 -15
  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/uninstall.d.ts.map +1 -1
  17. package/packages/core/dist/cli/commands/uninstall.js +24 -13
  18. package/packages/core/dist/cli/commands/uninstall.js.map +1 -1
  19. package/packages/core/dist/cli/commands/update-consolidation.test.js +0 -10
  20. package/packages/core/dist/cli/commands/update-consolidation.test.js.map +1 -1
  21. package/packages/core/dist/cli/commands/update.js.map +1 -1
  22. package/packages/core/dist/cli/ocean-profiles.test.js.map +1 -1
  23. package/packages/core/dist/cli/theme-maker.test.js +64 -115
  24. package/packages/core/dist/cli/theme-maker.test.js.map +1 -1
  25. package/packages/core/dist/index.d.ts +1 -1
  26. package/packages/core/dist/index.d.ts.map +1 -1
  27. package/packages/core/dist/index.js +2 -2
  28. package/packages/core/dist/index.js.map +1 -1
  29. package/packages/core/dist/plugins/plugin-discovery.d.ts +116 -0
  30. package/packages/core/dist/plugins/plugin-discovery.d.ts.map +1 -0
  31. package/packages/core/dist/plugins/plugin-discovery.js +165 -0
  32. package/packages/core/dist/plugins/plugin-discovery.js.map +1 -0
  33. package/packages/core/dist/plugins/plugin-discovery.test.d.ts +22 -0
  34. package/packages/core/dist/plugins/plugin-discovery.test.d.ts.map +1 -0
  35. package/packages/core/dist/plugins/plugin-discovery.test.js +498 -0
  36. package/packages/core/dist/plugins/plugin-discovery.test.js.map +1 -0
  37. package/packages/core/dist/scripts/generate-spider-report.js.map +1 -1
  38. package/packages/core/dist/workflow/context-watch.d.ts +80 -0
  39. package/packages/core/dist/workflow/context-watch.d.ts.map +1 -0
  40. package/packages/core/dist/workflow/context-watch.js +235 -0
  41. package/packages/core/dist/workflow/context-watch.js.map +1 -0
  42. package/packages/core/dist/workflow/context-watch.test.d.ts +1 -0
  43. package/packages/core/dist/workflow/context-watch.test.d.ts.map +1 -0
  44. package/packages/core/dist/workflow/context-watch.test.js +746 -0
  45. package/packages/core/dist/workflow/context-watch.test.js.map +1 -0
  46. package/packages/core/dist/workflow/file-watch.d.ts +82 -0
  47. package/packages/core/dist/workflow/file-watch.d.ts.map +1 -0
  48. package/packages/core/dist/workflow/file-watch.js +198 -0
  49. package/packages/core/dist/workflow/file-watch.js.map +1 -0
  50. package/packages/core/dist/workflow/file-watch.test.d.ts +21 -0
  51. package/packages/core/dist/workflow/file-watch.test.d.ts.map +1 -0
  52. package/packages/core/dist/workflow/file-watch.test.js +469 -0
  53. package/packages/core/dist/workflow/file-watch.test.js.map +1 -0
  54. package/packages/core/dist/workflow/observation-writer.d.ts +79 -0
  55. package/packages/core/dist/workflow/observation-writer.d.ts.map +1 -0
  56. package/packages/core/dist/workflow/observation-writer.js +97 -0
  57. package/packages/core/dist/workflow/observation-writer.js.map +1 -0
  58. package/packages/core/dist/workflow/observation-writer.test.d.ts +18 -0
  59. package/packages/core/dist/workflow/observation-writer.test.d.ts.map +1 -0
  60. package/packages/core/dist/workflow/observation-writer.test.js +424 -0
  61. package/packages/core/dist/workflow/observation-writer.test.js.map +1 -0
  62. package/packages/core/dist/workflow/story-workflow-routing.test.js +4 -2
  63. package/packages/core/dist/workflow/story-workflow-routing.test.js.map +1 -1
  64. package/packages/core/dist/workflow/tandem-lifecycle.d.ts +117 -0
  65. package/packages/core/dist/workflow/tandem-lifecycle.d.ts.map +1 -0
  66. package/packages/core/dist/workflow/tandem-lifecycle.js +186 -0
  67. package/packages/core/dist/workflow/tandem-lifecycle.js.map +1 -0
  68. package/packages/core/dist/workflow/tandem-lifecycle.test.d.ts +16 -0
  69. package/packages/core/dist/workflow/tandem-lifecycle.test.d.ts.map +1 -0
  70. package/packages/core/dist/workflow/tandem-lifecycle.test.js +531 -0
  71. package/packages/core/dist/workflow/tandem-lifecycle.test.js.map +1 -0
  72. package/packages/core/dist/workflow/tool-watch.d.ts +68 -0
  73. package/packages/core/dist/workflow/tool-watch.d.ts.map +1 -0
  74. package/packages/core/dist/workflow/tool-watch.js +166 -0
  75. package/packages/core/dist/workflow/tool-watch.js.map +1 -0
  76. package/packages/core/dist/workflow/tool-watch.test.d.ts +18 -0
  77. package/packages/core/dist/workflow/tool-watch.test.d.ts.map +1 -0
  78. package/packages/core/dist/workflow/tool-watch.test.js +718 -0
  79. package/packages/core/dist/workflow/tool-watch.test.js.map +1 -0
  80. package/packages/core/dist/workflow/workflow-migration.test.js +8 -4
  81. package/packages/core/dist/workflow/workflow-migration.test.js.map +1 -1
  82. package/packages/core/dist/workflow/workflow-schema.d.ts +7 -0
  83. package/packages/core/dist/workflow/workflow-schema.d.ts.map +1 -1
  84. package/packages/core/dist/workflow/workflow-schema.js +44 -0
  85. package/packages/core/dist/workflow/workflow-schema.js.map +1 -1
  86. package/packages/core/dist/workflow/workflow-schema.test.d.ts.map +1 -1
  87. package/packages/core/dist/workflow/workflow-schema.test.js +192 -0
  88. package/packages/core/dist/workflow/workflow-schema.test.js.map +1 -1
  89. package/pennyfarthing-dist/agents/handoff.md +18 -3
  90. package/pennyfarthing-dist/agents/sm-finish.md +1 -1
  91. package/pennyfarthing-dist/agents/sm-handoff.md +27 -4
  92. package/pennyfarthing-dist/agents/sm.md +11 -5
  93. package/pennyfarthing-dist/agents/tandem-backseat.md +119 -0
  94. package/pennyfarthing-dist/commands/setup.md +4 -0
  95. package/pennyfarthing-dist/guides/agent-behavior.md +62 -6
  96. package/pennyfarthing-dist/guides/bikelane.md +3 -2
  97. package/pennyfarthing-dist/guides/scale-levels.md +4 -6
  98. package/pennyfarthing-dist/guides/tandem-protocol.md +158 -0
  99. package/pennyfarthing-dist/personas/themes/discworld.yaml +1 -1
  100. package/pennyfarthing-dist/personas/themes/fifth-element.yaml +295 -0
  101. package/pennyfarthing-dist/scripts/README.md +1 -1
  102. package/pennyfarthing-dist/scripts/hooks/bell-mode-hook.sh +131 -54
  103. package/pennyfarthing-dist/scripts/hooks/post-merge.sh +20 -10
  104. package/pennyfarthing-dist/scripts/misc/statusline.sh +50 -8
  105. package/pennyfarthing-dist/scripts/workflow/README.md +2 -2
  106. package/pennyfarthing-dist/scripts/workflow/finish-story.sh +10 -189
  107. package/pennyfarthing-dist/skills/skill-registry.schema.json +8 -0
  108. package/pennyfarthing-dist/skills/skill-registry.yaml +1 -1
  109. package/pennyfarthing-dist/skills/sprint/skill.md +25 -2
  110. package/pennyfarthing-dist/skills/workflow/skill.md +24 -1
  111. package/pennyfarthing-dist/workflows/architecture/workflow.yaml +65 -0
  112. package/pennyfarthing-dist/workflows/bdd-tandem.yaml +70 -0
  113. package/pennyfarthing-dist/workflows/tdd-tandem.yaml +61 -0
  114. package/pennyfarthing_scripts/__pycache__/__init__.cpython-314.pyc +0 -0
  115. package/pennyfarthing_scripts/__pycache__/bellmode_hook.cpython-314.pyc +0 -0
  116. package/pennyfarthing_scripts/__pycache__/cli.cpython-314.pyc +0 -0
  117. package/pennyfarthing_scripts/__pycache__/config.cpython-314.pyc +0 -0
  118. package/pennyfarthing_scripts/__pycache__/hooks.cpython-314.pyc +0 -0
  119. package/pennyfarthing_scripts/__pycache__/jira_bidirectional_sync.cpython-314.pyc +0 -0
  120. package/pennyfarthing_scripts/__pycache__/jira_epic_creation.cpython-314.pyc +0 -0
  121. package/pennyfarthing_scripts/__pycache__/jira_sync.cpython-314.pyc +0 -0
  122. package/pennyfarthing_scripts/__pycache__/jira_sync_story.cpython-314.pyc +0 -0
  123. package/pennyfarthing_scripts/__pycache__/output.cpython-314.pyc +0 -0
  124. package/pennyfarthing_scripts/__pycache__/patch_mode.cpython-314.pyc +0 -0
  125. package/pennyfarthing_scripts/__pycache__/schema_validation_hook.cpython-314.pyc +0 -0
  126. package/pennyfarthing_scripts/__pycache__/workflow.cpython-314.pyc +0 -0
  127. package/pennyfarthing_scripts/bellmode_hook.py +202 -47
  128. package/pennyfarthing_scripts/brownfield/__init__.py +6 -6
  129. package/pennyfarthing_scripts/brownfield/__main__.py +1 -0
  130. package/pennyfarthing_scripts/brownfield/__pycache__/__init__.cpython-314.pyc +0 -0
  131. package/pennyfarthing_scripts/brownfield/__pycache__/__main__.cpython-314.pyc +0 -0
  132. package/pennyfarthing_scripts/brownfield/__pycache__/cli.cpython-314.pyc +0 -0
  133. package/pennyfarthing_scripts/brownfield/__pycache__/discover.cpython-314.pyc +0 -0
  134. package/pennyfarthing_scripts/brownfield/cli.py +0 -1
  135. package/pennyfarthing_scripts/brownfield/discover.py +1 -2
  136. package/pennyfarthing_scripts/cli.py +11 -6
  137. package/pennyfarthing_scripts/codemarkers/__init__.py +5 -1
  138. package/pennyfarthing_scripts/codemarkers/__pycache__/__init__.cpython-314.pyc +0 -0
  139. package/pennyfarthing_scripts/codemarkers/__pycache__/__main__.cpython-314.pyc +0 -0
  140. package/pennyfarthing_scripts/codemarkers/__pycache__/analyze.cpython-314.pyc +0 -0
  141. package/pennyfarthing_scripts/codemarkers/__pycache__/cli.cpython-314.pyc +0 -0
  142. package/pennyfarthing_scripts/codemarkers/__pycache__/formatters.cpython-314.pyc +0 -0
  143. package/pennyfarthing_scripts/codemarkers/__pycache__/models.cpython-314.pyc +0 -0
  144. package/pennyfarthing_scripts/codemarkers/analyze.py +177 -2
  145. package/pennyfarthing_scripts/codemarkers/cli.py +50 -0
  146. package/pennyfarthing_scripts/codemarkers/formatters.py +0 -1
  147. package/pennyfarthing_scripts/codemarkers/models.py +15 -0
  148. package/pennyfarthing_scripts/common/__init__.py +8 -9
  149. package/pennyfarthing_scripts/common/__pycache__/__init__.cpython-314.pyc +0 -0
  150. package/pennyfarthing_scripts/common/__pycache__/config.cpython-314.pyc +0 -0
  151. package/pennyfarthing_scripts/common/__pycache__/output.cpython-314.pyc +0 -0
  152. package/pennyfarthing_scripts/common/__pycache__/themes.cpython-314.pyc +0 -0
  153. package/pennyfarthing_scripts/common/config.py +1 -1
  154. package/pennyfarthing_scripts/complexity/__init__.py +1 -1
  155. package/pennyfarthing_scripts/complexity/__pycache__/__init__.cpython-314.pyc +0 -0
  156. package/pennyfarthing_scripts/complexity/__pycache__/__main__.cpython-314.pyc +0 -0
  157. package/pennyfarthing_scripts/complexity/__pycache__/analyze.cpython-314.pyc +0 -0
  158. package/pennyfarthing_scripts/complexity/__pycache__/cli.cpython-314.pyc +0 -0
  159. package/pennyfarthing_scripts/complexity/__pycache__/formatters.cpython-314.pyc +0 -0
  160. package/pennyfarthing_scripts/complexity/__pycache__/models.cpython-314.pyc +0 -0
  161. package/pennyfarthing_scripts/complexity/analyze.py +1 -1
  162. package/pennyfarthing_scripts/complexity/cli.py +5 -1
  163. package/pennyfarthing_scripts/complexity/formatters.py +1 -1
  164. package/pennyfarthing_scripts/context.py +14 -15
  165. package/pennyfarthing_scripts/deadcode/__pycache__/__init__.cpython-314.pyc +0 -0
  166. package/pennyfarthing_scripts/deadcode/__pycache__/__main__.cpython-314.pyc +0 -0
  167. package/pennyfarthing_scripts/deadcode/__pycache__/analyze.cpython-314.pyc +0 -0
  168. package/pennyfarthing_scripts/deadcode/__pycache__/cli.cpython-314.pyc +0 -0
  169. package/pennyfarthing_scripts/deadcode/__pycache__/formatters.cpython-314.pyc +0 -0
  170. package/pennyfarthing_scripts/deadcode/__pycache__/models.cpython-314.pyc +0 -0
  171. package/pennyfarthing_scripts/deadcode/analyze.py +3 -4
  172. package/pennyfarthing_scripts/deadcode/cli.py +2 -2
  173. package/pennyfarthing_scripts/dependencies/__init__.py +2 -2
  174. package/pennyfarthing_scripts/dependencies/__pycache__/__init__.cpython-314.pyc +0 -0
  175. package/pennyfarthing_scripts/dependencies/__pycache__/__main__.cpython-314.pyc +0 -0
  176. package/pennyfarthing_scripts/dependencies/__pycache__/analyze.cpython-314.pyc +0 -0
  177. package/pennyfarthing_scripts/dependencies/__pycache__/cli.cpython-314.pyc +0 -0
  178. package/pennyfarthing_scripts/dependencies/__pycache__/formatters.cpython-314.pyc +0 -0
  179. package/pennyfarthing_scripts/dependencies/__pycache__/models.cpython-314.pyc +0 -0
  180. package/pennyfarthing_scripts/dependencies/analyze.py +1 -1
  181. package/pennyfarthing_scripts/dependencies/cli.py +8 -4
  182. package/pennyfarthing_scripts/dependencies/formatters.py +1 -1
  183. package/pennyfarthing_scripts/git/__init__.py +5 -5
  184. package/pennyfarthing_scripts/git/__pycache__/__init__.cpython-314.pyc +0 -0
  185. package/pennyfarthing_scripts/git/__pycache__/create_branches.cpython-314.pyc +0 -0
  186. package/pennyfarthing_scripts/git/__pycache__/status_all.cpython-314.pyc +0 -0
  187. package/pennyfarthing_scripts/git/create_branches.py +3 -2
  188. package/pennyfarthing_scripts/git/status_all.py +1 -1
  189. package/pennyfarthing_scripts/healthscore/__init__.py +2 -2
  190. package/pennyfarthing_scripts/healthscore/__main__.py +8 -0
  191. package/pennyfarthing_scripts/healthscore/__pycache__/__init__.cpython-314.pyc +0 -0
  192. package/pennyfarthing_scripts/healthscore/__pycache__/__main__.cpython-314.pyc +0 -0
  193. package/pennyfarthing_scripts/healthscore/__pycache__/analyze.cpython-314.pyc +0 -0
  194. package/pennyfarthing_scripts/healthscore/__pycache__/cli.cpython-314.pyc +0 -0
  195. package/pennyfarthing_scripts/healthscore/__pycache__/formatters.cpython-314.pyc +0 -0
  196. package/pennyfarthing_scripts/healthscore/__pycache__/models.cpython-314.pyc +0 -0
  197. package/pennyfarthing_scripts/healthscore/analyze.py +451 -21
  198. package/pennyfarthing_scripts/healthscore/cli.py +5 -1
  199. package/pennyfarthing_scripts/healthscore/models.py +0 -1
  200. package/pennyfarthing_scripts/hooks.py +8 -11
  201. package/pennyfarthing_scripts/hotspots/__init__.py +6 -6
  202. package/pennyfarthing_scripts/hotspots/__pycache__/__init__.cpython-314.pyc +0 -0
  203. package/pennyfarthing_scripts/hotspots/__pycache__/__main__.cpython-314.pyc +0 -0
  204. package/pennyfarthing_scripts/hotspots/__pycache__/analyze.cpython-314.pyc +0 -0
  205. package/pennyfarthing_scripts/hotspots/__pycache__/cli.cpython-314.pyc +0 -0
  206. package/pennyfarthing_scripts/hotspots/__pycache__/formatters.cpython-314.pyc +0 -0
  207. package/pennyfarthing_scripts/hotspots/__pycache__/models.cpython-314.pyc +0 -0
  208. package/pennyfarthing_scripts/hotspots/analyze.py +128 -14
  209. package/pennyfarthing_scripts/hotspots/cli.py +2 -2
  210. package/pennyfarthing_scripts/hotspots/models.py +0 -1
  211. package/pennyfarthing_scripts/jira/__init__.py +15 -17
  212. package/pennyfarthing_scripts/jira/__pycache__/__init__.cpython-314.pyc +0 -0
  213. package/pennyfarthing_scripts/jira/__pycache__/__main__.cpython-314.pyc +0 -0
  214. package/pennyfarthing_scripts/jira/__pycache__/bidirectional.cpython-314.pyc +0 -0
  215. package/pennyfarthing_scripts/jira/__pycache__/claim.cpython-314.pyc +0 -0
  216. package/pennyfarthing_scripts/jira/__pycache__/cli.cpython-314.pyc +0 -0
  217. package/pennyfarthing_scripts/jira/__pycache__/client.cpython-314.pyc +0 -0
  218. package/pennyfarthing_scripts/jira/__pycache__/create.cpython-314.pyc +0 -0
  219. package/pennyfarthing_scripts/jira/__pycache__/epic.cpython-314.pyc +0 -0
  220. package/pennyfarthing_scripts/jira/__pycache__/operations.cpython-314.pyc +0 -0
  221. package/pennyfarthing_scripts/jira/__pycache__/reconcile.cpython-314.pyc +0 -0
  222. package/pennyfarthing_scripts/jira/__pycache__/story.cpython-314.pyc +0 -0
  223. package/pennyfarthing_scripts/jira/__pycache__/sync.cpython-314.pyc +0 -0
  224. package/pennyfarthing_scripts/jira/bidirectional.py +2 -3
  225. package/pennyfarthing_scripts/jira/claim.py +21 -0
  226. package/pennyfarthing_scripts/jira/cli.py +2 -2
  227. package/pennyfarthing_scripts/jira/client.py +4 -4
  228. package/pennyfarthing_scripts/jira/create.py +45 -1
  229. package/pennyfarthing_scripts/jira/epic.py +3 -2
  230. package/pennyfarthing_scripts/jira/reconcile.py +0 -1
  231. package/pennyfarthing_scripts/jira/story.py +2 -0
  232. package/pennyfarthing_scripts/jira/sync.py +1 -1
  233. package/pennyfarthing_scripts/migration/__pycache__/__init__.cpython-314.pyc +0 -0
  234. package/pennyfarthing_scripts/migration/__pycache__/session.cpython-314.pyc +0 -0
  235. package/pennyfarthing_scripts/migration/__pycache__/skill.cpython-314.pyc +0 -0
  236. package/pennyfarthing_scripts/migration/__pycache__/step.cpython-314.pyc +0 -0
  237. package/pennyfarthing_scripts/migration/__pycache__/validate.cpython-314.pyc +0 -0
  238. package/pennyfarthing_scripts/migration/skill.py +0 -1
  239. package/pennyfarthing_scripts/migration/step.py +0 -1
  240. package/pennyfarthing_scripts/migration/validate.py +8 -5
  241. package/pennyfarthing_scripts/patch_mode.py +2 -2
  242. package/pennyfarthing_scripts/preflight/__init__.py +1 -1
  243. package/pennyfarthing_scripts/preflight/__pycache__/__init__.cpython-314.pyc +0 -0
  244. package/pennyfarthing_scripts/preflight/__pycache__/__main__.cpython-314.pyc +0 -0
  245. package/pennyfarthing_scripts/preflight/__pycache__/cli.cpython-314.pyc +0 -0
  246. package/pennyfarthing_scripts/preflight/__pycache__/finish.cpython-314.pyc +0 -0
  247. package/pennyfarthing_scripts/preflight/finish.py +0 -1
  248. package/pennyfarthing_scripts/pretooluse_hook.py +6 -7
  249. package/pennyfarthing_scripts/prime/__pycache__/__init__.cpython-314.pyc +0 -0
  250. package/pennyfarthing_scripts/prime/__pycache__/cli.cpython-314.pyc +0 -0
  251. package/pennyfarthing_scripts/prime/__pycache__/loader.cpython-314.pyc +0 -0
  252. package/pennyfarthing_scripts/prime/__pycache__/models.cpython-314.pyc +0 -0
  253. package/pennyfarthing_scripts/prime/__pycache__/persona.cpython-314.pyc +0 -0
  254. package/pennyfarthing_scripts/prime/__pycache__/session.cpython-314.pyc +0 -0
  255. package/pennyfarthing_scripts/prime/__pycache__/tiers.cpython-314.pyc +0 -0
  256. package/pennyfarthing_scripts/prime/__pycache__/workflow.cpython-314.pyc +0 -0
  257. package/pennyfarthing_scripts/prime/cli.py +5 -1
  258. package/pennyfarthing_scripts/prime/loader.py +2 -3
  259. package/pennyfarthing_scripts/prime/persona.py +2 -1
  260. package/pennyfarthing_scripts/prime/tiers.py +4 -4
  261. package/pennyfarthing_scripts/schema_validation_hook.py +2 -3
  262. package/pennyfarthing_scripts/sprint/__init__.py +10 -12
  263. package/pennyfarthing_scripts/sprint/__main__.py +2 -2
  264. package/pennyfarthing_scripts/sprint/__pycache__/__init__.cpython-314.pyc +0 -0
  265. package/pennyfarthing_scripts/sprint/__pycache__/__main__.cpython-314.pyc +0 -0
  266. package/pennyfarthing_scripts/sprint/__pycache__/archive.cpython-314.pyc +0 -0
  267. package/pennyfarthing_scripts/sprint/__pycache__/archive_epic.cpython-314.pyc +0 -0
  268. package/pennyfarthing_scripts/sprint/__pycache__/cli.cpython-314.pyc +0 -0
  269. package/pennyfarthing_scripts/sprint/__pycache__/epic_add.cpython-314.pyc +0 -0
  270. package/pennyfarthing_scripts/sprint/__pycache__/import_epic.cpython-314.pyc +0 -0
  271. package/pennyfarthing_scripts/sprint/__pycache__/loader.cpython-314.pyc +0 -0
  272. package/pennyfarthing_scripts/sprint/__pycache__/status.cpython-314.pyc +0 -0
  273. package/pennyfarthing_scripts/sprint/__pycache__/story_add.cpython-314.pyc +0 -0
  274. package/pennyfarthing_scripts/sprint/__pycache__/story_finish.cpython-314.pyc +0 -0
  275. package/pennyfarthing_scripts/sprint/__pycache__/story_update.cpython-314.pyc +0 -0
  276. package/pennyfarthing_scripts/sprint/__pycache__/validate_cmd.cpython-314.pyc +0 -0
  277. package/pennyfarthing_scripts/sprint/__pycache__/validator.cpython-314.pyc +0 -0
  278. package/pennyfarthing_scripts/sprint/__pycache__/work.cpython-314.pyc +0 -0
  279. package/pennyfarthing_scripts/sprint/__pycache__/yaml_io.cpython-314.pyc +0 -0
  280. package/pennyfarthing_scripts/sprint/archive.py +0 -1
  281. package/pennyfarthing_scripts/sprint/archive_epic.py +1 -4
  282. package/pennyfarthing_scripts/sprint/cli.py +34 -28
  283. package/pennyfarthing_scripts/sprint/epic_add.py +8 -1
  284. package/pennyfarthing_scripts/sprint/import_epic.py +42 -18
  285. package/pennyfarthing_scripts/sprint/loader.py +6 -0
  286. package/pennyfarthing_scripts/sprint/status.py +1 -2
  287. package/pennyfarthing_scripts/sprint/story_add.py +2 -2
  288. package/pennyfarthing_scripts/sprint/story_finish.py +3 -5
  289. package/pennyfarthing_scripts/sprint/story_update.py +11 -3
  290. package/pennyfarthing_scripts/sprint/validate_cmd.py +0 -1
  291. package/pennyfarthing_scripts/sprint/validator.py +120 -6
  292. package/pennyfarthing_scripts/sprint/work.py +1 -4
  293. package/pennyfarthing_scripts/sprint/yaml_io.py +10 -2
  294. package/pennyfarthing_scripts/story/__init__.py +14 -16
  295. package/pennyfarthing_scripts/story/__pycache__/__init__.cpython-314.pyc +0 -0
  296. package/pennyfarthing_scripts/story/__pycache__/__main__.cpython-314.pyc +0 -0
  297. package/pennyfarthing_scripts/story/__pycache__/cli.cpython-314.pyc +0 -0
  298. package/pennyfarthing_scripts/story/__pycache__/create.cpython-314.pyc +0 -0
  299. package/pennyfarthing_scripts/story/__pycache__/size.cpython-314.pyc +0 -0
  300. package/pennyfarthing_scripts/story/__pycache__/template.cpython-314.pyc +0 -0
  301. package/pennyfarthing_scripts/story/size.py +0 -1
  302. package/pennyfarthing_scripts/story/template.py +0 -1
  303. package/pennyfarthing_scripts/swebench.py +1 -2
  304. package/pennyfarthing_scripts/tests/__pycache__/__init__.cpython-314.pyc +0 -0
  305. package/pennyfarthing_scripts/tests/__pycache__/conftest.cpython-314-pytest-9.0.2.pyc +0 -0
  306. package/pennyfarthing_scripts/tests/__pycache__/test_brownfield.cpython-314-pytest-9.0.2.pyc +0 -0
  307. package/pennyfarthing_scripts/tests/__pycache__/test_cli_modules.cpython-314-pytest-9.0.2.pyc +0 -0
  308. package/pennyfarthing_scripts/tests/__pycache__/test_codemarkers.cpython-314-pytest-9.0.2.pyc +0 -0
  309. package/pennyfarthing_scripts/tests/__pycache__/test_common.cpython-314-pytest-9.0.2.pyc +0 -0
  310. package/pennyfarthing_scripts/tests/__pycache__/test_git_utils.cpython-314-pytest-9.0.2.pyc +0 -0
  311. package/pennyfarthing_scripts/tests/__pycache__/test_healthscore.cpython-314-pytest-9.0.2.pyc +0 -0
  312. package/pennyfarthing_scripts/tests/__pycache__/test_jira_package.cpython-314-pytest-9.0.2.pyc +0 -0
  313. package/pennyfarthing_scripts/tests/__pycache__/test_package_structure.cpython-314-pytest-9.0.2.pyc +0 -0
  314. package/pennyfarthing_scripts/tests/__pycache__/test_patch_mode.cpython-314-pytest-9.0.2.pyc +0 -0
  315. package/pennyfarthing_scripts/tests/__pycache__/test_prime.cpython-314-pytest-9.0.2.pyc +0 -0
  316. package/pennyfarthing_scripts/tests/__pycache__/test_sprint_package.cpython-314-pytest-9.0.2.pyc +0 -0
  317. package/pennyfarthing_scripts/tests/__pycache__/test_sprint_validator.cpython-314-pytest-9.0.2.pyc +0 -0
  318. package/pennyfarthing_scripts/tests/__pycache__/test_story_add.cpython-314-pytest-9.0.2.pyc +0 -0
  319. package/pennyfarthing_scripts/tests/__pycache__/test_story_package.cpython-314-pytest-9.0.2.pyc +0 -0
  320. package/pennyfarthing_scripts/tests/__pycache__/test_story_update.cpython-314-pytest-9.0.2.pyc +0 -0
  321. package/pennyfarthing_scripts/tests/__pycache__/test_tiers.cpython-314-pytest-9.0.2.pyc +0 -0
  322. package/pennyfarthing_scripts/tests/__pycache__/test_token_counting.cpython-314-pytest-9.0.2.pyc +0 -0
  323. package/pennyfarthing_scripts/tests/__pycache__/test_validate_cmd.cpython-314-pytest-9.0.2.pyc +0 -0
  324. package/pennyfarthing_scripts/tests/__pycache__/test_workflow_check.cpython-314-pytest-9.0.2.pyc +0 -0
  325. package/pennyfarthing_scripts/tests/__pycache__/test_yaml_io.cpython-314-pytest-9.0.2.pyc +0 -0
  326. package/pennyfarthing_scripts/tests/conftest.py +1 -2
  327. package/pennyfarthing_scripts/tests/test_brownfield.py +10 -13
  328. package/pennyfarthing_scripts/tests/test_cli_modules.py +0 -4
  329. package/pennyfarthing_scripts/tests/test_codemarkers.py +13 -8
  330. package/pennyfarthing_scripts/tests/test_common.py +9 -4
  331. package/pennyfarthing_scripts/tests/test_epic_shard_validation.py +699 -0
  332. package/pennyfarthing_scripts/tests/test_git_utils.py +10 -13
  333. package/pennyfarthing_scripts/tests/test_healthscore.py +17 -25
  334. package/pennyfarthing_scripts/tests/test_jira_package.py +0 -3
  335. package/pennyfarthing_scripts/tests/test_package_structure.py +3 -16
  336. package/pennyfarthing_scripts/tests/test_patch_mode.py +7 -11
  337. package/pennyfarthing_scripts/tests/test_prime.py +39 -21
  338. package/pennyfarthing_scripts/tests/test_sprint_package.py +3 -8
  339. package/pennyfarthing_scripts/tests/test_sprint_validator.py +53 -5
  340. package/pennyfarthing_scripts/tests/test_story_add.py +3 -7
  341. package/pennyfarthing_scripts/tests/test_story_package.py +0 -3
  342. package/pennyfarthing_scripts/tests/test_story_update.py +5 -10
  343. package/pennyfarthing_scripts/tests/test_tiers.py +18 -17
  344. package/pennyfarthing_scripts/tests/test_token_counting.py +19 -13
  345. package/pennyfarthing_scripts/tests/test_validate_cmd.py +2 -7
  346. package/pennyfarthing_scripts/tests/test_workflow_check.py +0 -2
  347. package/pennyfarthing_scripts/tests/test_yaml_io.py +0 -3
  348. package/pennyfarthing_scripts/theme/__pycache__/__init__.cpython-314.pyc +0 -0
  349. package/pennyfarthing_scripts/theme/__pycache__/cli.cpython-314.pyc +0 -0
  350. package/pennyfarthing_scripts/theme/cli.py +3 -2
  351. package/pennyfarthing_scripts/validate/__init__.py +21 -0
  352. package/pennyfarthing_scripts/validate/__pycache__/__init__.cpython-314.pyc +0 -0
  353. package/pennyfarthing_scripts/validate/__pycache__/cli.cpython-314.pyc +0 -0
  354. package/pennyfarthing_scripts/validate/adapters/__init__.py +0 -0
  355. package/pennyfarthing_scripts/validate/adapters/__pycache__/__init__.cpython-314.pyc +0 -0
  356. package/pennyfarthing_scripts/validate/adapters/__pycache__/agent.cpython-314.pyc +0 -0
  357. package/pennyfarthing_scripts/validate/adapters/__pycache__/schema.cpython-314.pyc +0 -0
  358. package/pennyfarthing_scripts/validate/adapters/__pycache__/skill_command.cpython-314.pyc +0 -0
  359. package/pennyfarthing_scripts/validate/adapters/__pycache__/sprint.cpython-314.pyc +0 -0
  360. package/pennyfarthing_scripts/validate/adapters/__pycache__/workflow.cpython-314.pyc +0 -0
  361. package/pennyfarthing_scripts/validate/adapters/agent.py +239 -0
  362. package/pennyfarthing_scripts/validate/adapters/schema.py +30 -0
  363. package/pennyfarthing_scripts/validate/adapters/skill_command.py +292 -0
  364. package/pennyfarthing_scripts/validate/adapters/sprint.py +69 -0
  365. package/pennyfarthing_scripts/validate/adapters/workflow.py +320 -0
  366. package/pennyfarthing_scripts/validate/cli.py +141 -0
  367. package/pennyfarthing_scripts/welcome_hook.py +2 -3
  368. package/pennyfarthing_scripts/workflow.py +3 -3
  369. package/scripts/README.md +3 -15
  370. package/pennyfarthing-dist/commands/benchmark-control.md +0 -69
  371. package/pennyfarthing-dist/commands/benchmark.md +0 -485
  372. package/pennyfarthing-dist/commands/job-fair.md +0 -102
  373. package/pennyfarthing-dist/commands/solo.md +0 -447
  374. package/pennyfarthing-dist/guides/benchmarks.md +0 -62
  375. package/pennyfarthing-dist/scripts/hooks/__pycache__/question_reflector_check.cpython-314.pyc +0 -0
  376. package/pennyfarthing-dist/scripts/test/ensure-swebench-data.sh +0 -59
  377. package/pennyfarthing-dist/scripts/test/ground-truth-judge.py +0 -220
  378. package/pennyfarthing-dist/scripts/test/swebench-judge.py +0 -374
  379. package/pennyfarthing-dist/scripts/test/test-cache.sh +0 -165
  380. package/pennyfarthing-dist/scripts/test/test-setup.sh +0 -337
  381. package/pennyfarthing-dist/scripts/theme/compute-theme-tiers.sh +0 -13
  382. package/pennyfarthing-dist/scripts/theme/compute_theme_tiers.py +0 -402
  383. package/pennyfarthing-dist/scripts/theme/update-theme-tiers.sh +0 -97
  384. package/pennyfarthing-dist/skills/finalize-run/SKILL.md +0 -261
  385. package/pennyfarthing-dist/skills/judge/SKILL.md +0 -644
  386. package/pennyfarthing-dist/skills/persona-benchmark/SKILL.md +0 -187
  387. package/pennyfarthing-dist/workflows/dev-story/checklist.md +0 -80
  388. package/pennyfarthing-dist/workflows/dev-story/instructions.xml +0 -410
  389. package/pennyfarthing-dist/workflows/dev-story/workflow.yaml +0 -50
  390. package/pennyfarthing-dist/workflows/quick-spec/steps/step-01-understand.md +0 -201
  391. package/pennyfarthing-dist/workflows/quick-spec/steps/step-02-investigate.md +0 -156
  392. package/pennyfarthing-dist/workflows/quick-spec/steps/step-03-generate.md +0 -140
  393. package/pennyfarthing-dist/workflows/quick-spec/steps/step-04-review.md +0 -203
  394. package/pennyfarthing-dist/workflows/quick-spec/tech-spec-template.md +0 -74
  395. package/pennyfarthing-dist/workflows/quick-spec/workflow.yaml +0 -27
  396. package/pennyfarthing_scripts/__pycache__/__init__.cpython-311.pyc +0 -0
  397. package/pennyfarthing_scripts/__pycache__/jira.cpython-314.pyc +0 -0
  398. package/pennyfarthing_scripts/__pycache__/pretooluse_hook.cpython-314.pyc +0 -0
  399. package/pennyfarthing_scripts/__pycache__/sprint.cpython-314.pyc +0 -0
  400. package/pennyfarthing_scripts/__pycache__/workflow.cpython-311.pyc +0 -0
  401. package/pennyfarthing_scripts/jira/__pycache__/compat.cpython-314.pyc +0 -0
  402. package/pennyfarthing_scripts/jira/__pycache__/mappings.cpython-314.pyc +0 -0
  403. package/pennyfarthing_scripts/jira/__pycache__/models.cpython-314.pyc +0 -0
  404. package/pennyfarthing_scripts/migration/__pycache__/__main__.cpython-314.pyc +0 -0
  405. package/pennyfarthing_scripts/migration/__pycache__/cli.cpython-314.pyc +0 -0
  406. package/pennyfarthing_scripts/prime/__pycache__/__main__.cpython-314.pyc +0 -0
  407. package/pennyfarthing_scripts/tests/__pycache__/test_workflow_cli.cpython-314-pytest-9.0.2.pyc +0 -0
@@ -11,7 +11,6 @@ import re
11
11
  from dataclasses import dataclass, field
12
12
  from enum import Enum
13
13
  from pathlib import Path
14
- from typing import Literal
15
14
 
16
15
  # Try to import tomllib (Python 3.11+) or fall back to tomli
17
16
  try:
@@ -642,7 +641,7 @@ def generate_ai_guidance_doc(result: DiscoveryResult) -> str:
642
641
 
643
642
  if result.tech_stack:
644
643
  # Get unique tech names
645
- tech_names = sorted(set(item.name.lower() for item in result.tech_stack))[:10]
644
+ tech_names = sorted({item.name.lower() for item in result.tech_stack})[:10]
646
645
  lines.extend([
647
646
  "## Key Technologies",
648
647
  "",
@@ -31,35 +31,40 @@ def cli():
31
31
 
32
32
 
33
33
  # Import and register sprint group (lazy registration preserves startup time)
34
- from pennyfarthing_scripts.sprint.cli import sprint
34
+ from pennyfarthing_scripts.sprint.cli import sprint # noqa: E402
35
35
 
36
36
  cli.add_command(sprint)
37
37
 
38
38
  # Import and register hotspots group
39
- from pennyfarthing_scripts.hotspots.cli import hotspots
39
+ from pennyfarthing_scripts.hotspots.cli import hotspots # noqa: E402
40
40
 
41
41
  cli.add_command(hotspots)
42
42
 
43
43
  # Import and register jira group
44
- from pennyfarthing_scripts.jira.cli import jira
44
+ from pennyfarthing_scripts.jira.cli import jira # noqa: E402
45
45
 
46
46
  cli.add_command(jira)
47
47
 
48
48
  # Import and register deadcode group
49
- from pennyfarthing_scripts.deadcode.cli import deadcode
49
+ from pennyfarthing_scripts.deadcode.cli import deadcode # noqa: E402
50
50
 
51
51
  cli.add_command(deadcode)
52
52
 
53
53
  # Import and register theme group
54
- from pennyfarthing_scripts.theme.cli import theme
54
+ from pennyfarthing_scripts.theme.cli import theme # noqa: E402
55
55
 
56
56
  cli.add_command(theme)
57
57
 
58
58
  # Import and register healthscore group
59
- from pennyfarthing_scripts.healthscore.cli import healthscore
59
+ from pennyfarthing_scripts.healthscore.cli import healthscore # noqa: E402
60
60
 
61
61
  cli.add_command(healthscore)
62
62
 
63
+ # Import and register validate group
64
+ from pennyfarthing_scripts.validate.cli import validate # noqa: E402
65
+
66
+ cli.add_command(validate)
67
+
63
68
 
64
69
  @cli.group()
65
70
  def agent():
@@ -2,18 +2,22 @@
2
2
  Code marker analysis — TODO, FIXME, HACK, XXX detection with git blame.
3
3
 
4
4
  Story 80-1: Python codemarkers module.
5
+ Story 80-2: @deprecated detection and caller cross-reference.
5
6
  """
6
7
 
8
+ from pennyfarthing_scripts.codemarkers.analyze import analyze_deprecations, analyze_repo
7
9
  from pennyfarthing_scripts.codemarkers.models import (
8
10
  CodeMarker,
9
11
  CodeMarkersResult,
12
+ DeprecationMarker,
10
13
  MarkerSummary,
11
14
  )
12
- from pennyfarthing_scripts.codemarkers.analyze import analyze_repo
13
15
 
14
16
  __all__ = [
15
17
  "CodeMarker",
16
18
  "CodeMarkersResult",
19
+ "DeprecationMarker",
17
20
  "MarkerSummary",
21
+ "analyze_deprecations",
18
22
  "analyze_repo",
19
23
  ]
@@ -12,12 +12,13 @@ import fnmatch
12
12
  import re
13
13
  import time
14
14
  from collections import defaultdict
15
- from datetime import datetime, timezone
15
+ from datetime import UTC, datetime
16
16
  from pathlib import Path
17
17
 
18
18
  from pennyfarthing_scripts.codemarkers.models import (
19
19
  CodeMarker,
20
20
  CodeMarkersResult,
21
+ DeprecationMarker,
21
22
  MarkerSummary,
22
23
  )
23
24
 
@@ -286,7 +287,7 @@ async def analyze_repo(
286
287
  if author_time > 0:
287
288
  age_days = (now - author_time) / 86400
288
289
  date_str = datetime.fromtimestamp(
289
- author_time, tz=timezone.utc
290
+ author_time, tz=UTC
290
291
  ).isoformat()
291
292
  else:
292
293
  age_days = 0.0
@@ -324,3 +325,177 @@ async def analyze_repo(
324
325
  markers=markers,
325
326
  summary=summary,
326
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
+ }
@@ -127,3 +127,53 @@ def summary(repo, repo_path, days, top, fmt, output_file, exclude):
127
127
  else:
128
128
  from pennyfarthing_scripts.codemarkers.formatters import format_summary
129
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)
@@ -16,7 +16,6 @@ from typing import TextIO
16
16
  from pennyfarthing_scripts.codemarkers.models import (
17
17
  CodeMarker,
18
18
  CodeMarkersResult,
19
- MarkerSummary,
20
19
  )
21
20
 
22
21
 
@@ -43,3 +43,18 @@ class CodeMarkersResult:
43
43
  markers: list[CodeMarker] = field(default_factory=list)
44
44
  summary: MarkerSummary | None = None
45
45
  error: str | None = None
46
+
47
+
48
+ @dataclass
49
+ class DeprecationMarker:
50
+ """A deprecated symbol with caller cross-reference data (Story 80-2).
51
+
52
+ Stub: Dev will implement full functionality.
53
+ """
54
+
55
+ path: str
56
+ line: int
57
+ symbol: str
58
+ text: str
59
+ caller_count: int = 0
60
+ callers: list[str] = field(default_factory=list)
@@ -5,8 +5,16 @@ This package provides shared utilities used across all CLI tools:
5
5
  - config: Project configuration loading
6
6
  """
7
7
 
8
+ from pennyfarthing_scripts.common.config import (
9
+ find_project_root,
10
+ get_project_root,
11
+ load_pennyfarthing_config,
12
+ load_yaml_config,
13
+ )
8
14
  from pennyfarthing_scripts.common.output import (
9
15
  Colors,
16
+ _colorize,
17
+ _supports_color,
10
18
  bold,
11
19
  debug,
12
20
  dim,
@@ -16,15 +24,6 @@ from pennyfarthing_scripts.common.output import (
16
24
  info,
17
25
  success,
18
26
  warn,
19
- _colorize,
20
- _supports_color,
21
- )
22
-
23
- from pennyfarthing_scripts.common.config import (
24
- find_project_root,
25
- get_project_root,
26
- load_pennyfarthing_config,
27
- load_yaml_config,
28
27
  )
29
28
 
30
29
  __all__ = [
@@ -78,7 +78,7 @@ def load_yaml_config(path: Path) -> dict[str, Any] | None:
78
78
  if not path.exists():
79
79
  return None
80
80
 
81
- with open(path, "r") as f:
81
+ with open(path) as f:
82
82
  return yaml.safe_load(f)
83
83
 
84
84
 
@@ -5,8 +5,8 @@ Wraps eslint with complexity rules to extract per-file metrics:
5
5
  cyclomatic complexity, function length, nesting depth, and line counts.
6
6
  """
7
7
 
8
- from pennyfarthing_scripts.complexity.models import FileComplexity, ComplexityResult
9
8
  from pennyfarthing_scripts.complexity.analyze import analyze_complexity
9
+ from pennyfarthing_scripts.complexity.models import ComplexityResult, FileComplexity
10
10
 
11
11
  __all__ = [
12
12
  "FileComplexity",
@@ -13,7 +13,7 @@ import json
13
13
  import re
14
14
  from pathlib import Path
15
15
 
16
- from pennyfarthing_scripts.complexity.models import FileComplexity, ComplexityResult
16
+ from pennyfarthing_scripts.complexity.models import ComplexityResult, FileComplexity
17
17
 
18
18
  # Regex patterns for extracting numeric values from ESLint messages
19
19
  COMPLEXITY_RE = re.compile(r"complexity of (\d+)")
@@ -9,9 +9,13 @@ from __future__ import annotations
9
9
 
10
10
  import asyncio
11
11
  from pathlib import Path
12
+ from typing import TYPE_CHECKING
12
13
 
13
14
  import click
14
15
 
16
+ if TYPE_CHECKING:
17
+ from pennyfarthing_scripts.complexity.models import ComplexityResult
18
+
15
19
 
16
20
  @click.group()
17
21
  def complexity():
@@ -39,7 +43,7 @@ def _common_options(fn):
39
43
  return fn
40
44
 
41
45
 
42
- def _run_analysis(target_path: str | None, exclude: tuple) -> "ComplexityResult":
46
+ def _run_analysis(target_path: str | None, exclude: tuple) -> ComplexityResult:
43
47
  """Run analysis and return result."""
44
48
  from pennyfarthing_scripts.complexity.analyze import analyze_complexity
45
49
 
@@ -11,7 +11,7 @@ import io
11
11
  import json
12
12
  from dataclasses import asdict
13
13
 
14
- from pennyfarthing_scripts.complexity.models import FileComplexity, ComplexityResult
14
+ from pennyfarthing_scripts.complexity.models import ComplexityResult, FileComplexity
15
15
 
16
16
 
17
17
  def format_file_table(files: list[FileComplexity], top_n: int = 20) -> str:
@@ -9,7 +9,6 @@ import os
9
9
  import time
10
10
  from dataclasses import dataclass
11
11
  from pathlib import Path
12
- from typing import Optional
13
12
 
14
13
  try:
15
14
  import yaml
@@ -45,8 +44,8 @@ class ContextResult:
45
44
 
46
45
  # Status
47
46
  status: str = "OK" # OK, HIGH
48
- warning: Optional[str] = None # None, High, Critical
49
- recommendation: Optional[str] = None
47
+ warning: str | None = None # None, High, Critical
48
+ recommendation: str | None = None
50
49
 
51
50
  # Mode settings
52
51
  permission_mode: str = "manual"
@@ -56,7 +55,7 @@ class ContextResult:
56
55
  is_cyclist: bool = False
57
56
 
58
57
  # Error state
59
- error: Optional[str] = None
58
+ error: str | None = None
60
59
 
61
60
  def to_env_vars(self) -> str:
62
61
  """Output as shell environment variables."""
@@ -111,7 +110,7 @@ class ContextResult:
111
110
  return "\n".join(lines)
112
111
 
113
112
 
114
- def load_config(project_dir: Optional[str] = None) -> ContextConfig:
113
+ def load_config(project_dir: str | None = None) -> ContextConfig:
115
114
  """Load context configuration from config files.
116
115
 
117
116
  Checks .pennyfarthing/config.local.yaml first, falls back to
@@ -166,7 +165,7 @@ def _apply_config(config: ContextConfig, data: dict) -> None:
166
165
  config.relay_mode = wf.get("relay_mode", False) is True
167
166
 
168
167
 
169
- def get_claude_project_path(project_dir: Optional[str] = None) -> Path:
168
+ def get_claude_project_path(project_dir: str | None = None) -> Path:
170
169
  """Get the Claude Code project path for transcripts.
171
170
 
172
171
  Claude Code stores transcripts at ~/.claude/projects/<path-with-dashes>
@@ -184,10 +183,10 @@ def get_claude_project_path(project_dir: Optional[str] = None) -> Path:
184
183
 
185
184
  def find_transcript(
186
185
  project_path: Path,
187
- explicit_session: Optional[str] = None,
188
- session_id_env: Optional[str] = None,
186
+ explicit_session: str | None = None,
187
+ session_id_env: str | None = None,
189
188
  stale_threshold_seconds: int = 60,
190
- ) -> Optional[Path]:
189
+ ) -> Path | None:
191
190
  """Find the appropriate transcript file.
192
191
 
193
192
  Priority:
@@ -208,7 +207,7 @@ def find_transcript(
208
207
  return None
209
208
 
210
209
  # Helper to find most recent transcript
211
- def most_recent() -> Optional[Path]:
210
+ def most_recent() -> Path | None:
212
211
  transcripts = sorted(
213
212
  [f for f in project_path.glob("*.jsonl") if "agent-" not in f.name],
214
213
  key=lambda f: f.stat().st_mtime,
@@ -236,7 +235,7 @@ def find_transcript(
236
235
  return most_recent()
237
236
 
238
237
 
239
- def parse_transcript(transcript_path: Path) -> tuple[Optional[int], Optional[int]]:
238
+ def parse_transcript(transcript_path: Path) -> tuple[int | None, int | None]:
240
239
  """Parse transcript for first and last usage totals.
241
240
 
242
241
  Returns:
@@ -265,7 +264,7 @@ def parse_transcript(transcript_path: Path) -> tuple[Optional[int], Optional[int
265
264
  return first_total, last_total
266
265
 
267
266
 
268
- def detect_cyclist(project_dir: Optional[str] = None) -> bool:
267
+ def detect_cyclist(project_dir: str | None = None) -> bool:
269
268
  """Detect if running inside Cyclist.
270
269
 
271
270
  Checks:
@@ -300,7 +299,7 @@ def detect_cyclist(project_dir: Optional[str] = None) -> bool:
300
299
  result = s.connect_ex(("127.0.0.1", port))
301
300
  if result == 0:
302
301
  return True
303
- except (ValueError, OSError, socket.error):
302
+ except (ValueError, OSError):
304
303
  # Port file invalid or port not responding
305
304
  continue
306
305
 
@@ -308,8 +307,8 @@ def detect_cyclist(project_dir: Optional[str] = None) -> bool:
308
307
 
309
308
 
310
309
  def check_context(
311
- explicit_session: Optional[str] = None,
312
- project_dir: Optional[str] = None,
310
+ explicit_session: str | None = None,
311
+ project_dir: str | None = None,
313
312
  ) -> ContextResult:
314
313
  """Check current context usage.
315
314
 
@@ -9,10 +9,9 @@ from __future__ import annotations
9
9
 
10
10
  import asyncio
11
11
  import fnmatch
12
- from datetime import datetime, timezone
13
- from pathlib import Path
14
-
15
12
  import re
13
+ from datetime import UTC, datetime
14
+ from pathlib import Path
16
15
 
17
16
  from pennyfarthing_scripts.deadcode.models import (
18
17
  DeadCodeResult,
@@ -156,7 +155,7 @@ async def find_stale_files(
156
155
  filtered.append(fpath)
157
156
 
158
157
  # Enrich each stale file
159
- now = datetime.now(timezone.utc)
158
+ now = datetime.now(UTC)
160
159
  stale_files = []
161
160
  for fpath in sorted(filtered):
162
161
  # Get last commit date
@@ -41,8 +41,8 @@ def _common_options(fn):
41
41
 
42
42
  def _run_analysis(repo: str | None, repo_path: str | None, days: int, exclude: tuple, branch: str):
43
43
  """Run analysis and return result."""
44
- from pennyfarthing_scripts.deadcode.analyze import analyze_repo, find_stale_files
45
44
  from pennyfarthing_scripts.common.config import get_project_root
45
+ from pennyfarthing_scripts.deadcode.analyze import analyze_repo
46
46
 
47
47
  excludes = list(exclude) if exclude else None
48
48
 
@@ -109,8 +109,8 @@ def _exports_options(fn):
109
109
 
110
110
  def _run_exports_analysis(repo: str | None, repo_path: str | None):
111
111
  """Run unused export analysis and return result."""
112
- from pennyfarthing_scripts.deadcode.analyze import find_unused_exports
113
112
  from pennyfarthing_scripts.common.config import get_project_root
113
+ from pennyfarthing_scripts.deadcode.analyze import find_unused_exports
114
114
 
115
115
  if repo_path:
116
116
  p = Path(repo_path).resolve()