@pennyfarthing/core 11.0.0 → 11.1.1

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 (401) hide show
  1. package/README.md +81 -23
  2. package/package.json +1 -1
  3. package/packages/core/dist/cli/utils/010-detect-remove-old-packages.test.d.ts +20 -0
  4. package/packages/core/dist/cli/utils/010-detect-remove-old-packages.test.d.ts.map +1 -0
  5. package/packages/core/dist/cli/utils/010-detect-remove-old-packages.test.js +278 -0
  6. package/packages/core/dist/cli/utils/010-detect-remove-old-packages.test.js.map +1 -0
  7. package/packages/core/dist/cli/utils/constants.d.ts +8 -2
  8. package/packages/core/dist/cli/utils/constants.d.ts.map +1 -1
  9. package/packages/core/dist/cli/utils/constants.js +4 -1
  10. package/packages/core/dist/cli/utils/constants.js.map +1 -1
  11. package/packages/core/dist/cli/utils/constants.test.d.ts +10 -0
  12. package/packages/core/dist/cli/utils/constants.test.d.ts.map +1 -0
  13. package/packages/core/dist/cli/utils/constants.test.js +38 -0
  14. package/packages/core/dist/cli/utils/constants.test.js.map +1 -0
  15. package/packages/core/dist/consultation/consultation-protocol.d.ts +139 -0
  16. package/packages/core/dist/consultation/consultation-protocol.d.ts.map +1 -0
  17. package/packages/core/dist/consultation/consultation-protocol.js +178 -0
  18. package/packages/core/dist/consultation/consultation-protocol.js.map +1 -0
  19. package/packages/core/dist/consultation/consultation-protocol.test.d.ts +20 -0
  20. package/packages/core/dist/consultation/consultation-protocol.test.d.ts.map +1 -0
  21. package/packages/core/dist/consultation/consultation-protocol.test.js +474 -0
  22. package/packages/core/dist/consultation/consultation-protocol.test.js.map +1 -0
  23. package/packages/core/dist/consultation/dialogue-manager.d.ts +75 -0
  24. package/packages/core/dist/consultation/dialogue-manager.d.ts.map +1 -0
  25. package/packages/core/dist/consultation/dialogue-manager.js +334 -0
  26. package/packages/core/dist/consultation/dialogue-manager.js.map +1 -0
  27. package/packages/core/dist/consultation/dialogue-manager.test.d.ts +19 -0
  28. package/packages/core/dist/consultation/dialogue-manager.test.d.ts.map +1 -0
  29. package/packages/core/dist/consultation/dialogue-manager.test.js +444 -0
  30. package/packages/core/dist/consultation/dialogue-manager.test.js.map +1 -0
  31. package/packages/core/dist/public/js/react/react.js +3 -3
  32. package/packages/core/dist/scripts/theme-detail.test.d.ts +10 -0
  33. package/packages/core/dist/scripts/theme-detail.test.js +199 -0
  34. package/packages/core/dist/server/api/git.d.ts +13 -1
  35. package/packages/core/dist/server/api/git.d.ts.map +1 -1
  36. package/packages/core/dist/server/api/git.js +53 -34
  37. package/packages/core/dist/server/api/git.js.map +1 -1
  38. package/packages/core/dist/server/api/health-score.d.ts.map +1 -1
  39. package/packages/core/dist/server/api/health-score.js +25 -1
  40. package/packages/core/dist/server/api/health-score.js.map +1 -1
  41. package/packages/core/dist/server/api/settings.d.ts.map +1 -1
  42. package/packages/core/dist/server/api/settings.js +63 -1
  43. package/packages/core/dist/server/api/settings.js.map +1 -1
  44. package/packages/core/dist/server/api/theme-agents.d.ts.map +1 -1
  45. package/packages/core/dist/server/api/theme-agents.js +61 -0
  46. package/packages/core/dist/server/api/theme-agents.js.map +1 -1
  47. package/packages/core/dist/server/server.d.ts.map +1 -1
  48. package/packages/core/dist/server/server.js +17 -12
  49. package/packages/core/dist/server/server.js.map +1 -1
  50. package/packages/core/dist/shared/skill-search.test.js +2 -2
  51. package/packages/core/dist/workflow/gate-file-validation.d.ts +49 -0
  52. package/packages/core/dist/workflow/gate-file-validation.d.ts.map +1 -0
  53. package/packages/core/dist/workflow/gate-file-validation.js +157 -0
  54. package/packages/core/dist/workflow/gate-file-validation.js.map +1 -0
  55. package/packages/core/dist/workflow/gate-file-validation.test.d.ts +19 -0
  56. package/packages/core/dist/workflow/gate-file-validation.test.d.ts.map +1 -0
  57. package/packages/core/dist/workflow/gate-file-validation.test.js +536 -0
  58. package/packages/core/dist/workflow/gate-file-validation.test.js.map +1 -0
  59. package/packages/core/dist/workflow/gate-schema-validation.test.d.ts +14 -0
  60. package/packages/core/dist/workflow/gate-schema-validation.test.d.ts.map +1 -0
  61. package/packages/core/dist/workflow/gate-schema-validation.test.js +339 -0
  62. package/packages/core/dist/workflow/gate-schema-validation.test.js.map +1 -0
  63. package/packages/core/dist/workflow/handoff.js +2 -2
  64. package/packages/core/dist/workflow/handoff.js.map +1 -1
  65. package/packages/core/dist/workflow/handoff.test.js +16 -0
  66. package/packages/core/dist/workflow/handoff.test.js.map +1 -1
  67. package/packages/core/dist/workflow/workflow-schema.d.ts +4 -2
  68. package/packages/core/dist/workflow/workflow-schema.d.ts.map +1 -1
  69. package/packages/core/dist/workflow/workflow-schema.js +43 -8
  70. package/packages/core/dist/workflow/workflow-schema.js.map +1 -1
  71. package/pennyfarthing-dist/agents/README.md +6 -14
  72. package/pennyfarthing-dist/agents/architect.md +43 -29
  73. package/pennyfarthing-dist/agents/ba.md +30 -29
  74. package/pennyfarthing-dist/agents/dev.md +32 -43
  75. package/pennyfarthing-dist/agents/devops.md +57 -21
  76. package/pennyfarthing-dist/agents/orchestrator.md +3 -10
  77. package/pennyfarthing-dist/agents/pm.md +45 -31
  78. package/pennyfarthing-dist/agents/reviewer.md +20 -66
  79. package/pennyfarthing-dist/agents/sm-setup.md +2 -2
  80. package/pennyfarthing-dist/agents/sm.md +8 -30
  81. package/pennyfarthing-dist/agents/tea.md +25 -41
  82. package/pennyfarthing-dist/agents/tech-writer.md +33 -90
  83. package/pennyfarthing-dist/agents/ux-designer.md +39 -39
  84. package/pennyfarthing-dist/commands/benchmark-control.md +8 -64
  85. package/pennyfarthing-dist/commands/benchmark.md +8 -480
  86. package/pennyfarthing-dist/commands/job-fair.md +8 -97
  87. package/pennyfarthing-dist/commands/pf-benchmark-control.md +70 -0
  88. package/pennyfarthing-dist/commands/pf-benchmark.md +486 -0
  89. package/pennyfarthing-dist/commands/pf-chore.md +4 -4
  90. package/pennyfarthing-dist/commands/pf-ci.md +40 -0
  91. package/pennyfarthing-dist/commands/pf-close-epic.md +9 -27
  92. package/pennyfarthing-dist/commands/pf-continue-session.md +9 -213
  93. package/pennyfarthing-dist/commands/pf-create-branches-from-story.md +11 -353
  94. package/pennyfarthing-dist/commands/pf-docs.md +28 -0
  95. package/pennyfarthing-dist/commands/pf-epic.md +67 -0
  96. package/pennyfarthing-dist/commands/pf-git-cleanup.md +11 -52
  97. package/pennyfarthing-dist/commands/pf-git.md +75 -0
  98. package/pennyfarthing-dist/commands/pf-help.md +110 -128
  99. package/pennyfarthing-dist/commands/pf-job-fair.md +102 -0
  100. package/pennyfarthing-dist/commands/pf-new-work.md +9 -18
  101. package/pennyfarthing-dist/commands/pf-parallel-work.md +6 -66
  102. package/pennyfarthing-dist/commands/pf-release.md +11 -76
  103. package/pennyfarthing-dist/commands/pf-repo-status.md +11 -44
  104. package/pennyfarthing-dist/commands/pf-run-ci.md +8 -111
  105. package/pennyfarthing-dist/commands/pf-session.md +51 -0
  106. package/pennyfarthing-dist/commands/pf-solo.md +447 -0
  107. package/pennyfarthing-dist/commands/pf-sprint-planning.md +8 -104
  108. package/pennyfarthing-dist/commands/pf-standalone.md +1 -1
  109. package/pennyfarthing-dist/commands/pf-start-epic.md +9 -163
  110. package/pennyfarthing-dist/commands/pf-sync-epic-to-jira.md +8 -179
  111. package/pennyfarthing-dist/commands/pf-sync-work-with-sprint.md +8 -368
  112. package/pennyfarthing-dist/commands/pf-update-domain-docs.md +8 -78
  113. package/pennyfarthing-dist/commands/solo.md +8 -442
  114. package/pennyfarthing-dist/guides/agent-behavior.md +13 -13
  115. package/pennyfarthing-dist/guides/agent-coordination.md +7 -7
  116. package/pennyfarthing-dist/guides/agent-tag-taxonomy.md +6 -5
  117. package/pennyfarthing-dist/guides/bikerack.md +128 -0
  118. package/pennyfarthing-dist/guides/brownfield-tools.md +133 -0
  119. package/pennyfarthing-dist/guides/command-tag-taxonomy.md +2 -2
  120. package/pennyfarthing-dist/guides/gate-schema.md +227 -0
  121. package/pennyfarthing-dist/guides/gates.md +120 -0
  122. package/pennyfarthing-dist/guides/handoff-cli.md +116 -0
  123. package/pennyfarthing-dist/guides/hooks.md +86 -4
  124. package/pennyfarthing-dist/guides/output-styles.md +65 -0
  125. package/pennyfarthing-dist/guides/patterns/approval-gates-pattern.md +5 -5
  126. package/pennyfarthing-dist/guides/patterns/tdd-flow-pattern.md +4 -4
  127. package/pennyfarthing-dist/guides/prompt-patterns.md +5 -5
  128. package/pennyfarthing-dist/guides/reflector.md +4 -4
  129. package/pennyfarthing-dist/guides/session-artifacts.md +1 -1
  130. package/pennyfarthing-dist/guides/skill-schema.md +1 -1
  131. package/pennyfarthing-dist/guides/tandem-protocol.md +13 -1
  132. package/pennyfarthing-dist/guides/worktree-mode.md +3 -3
  133. package/pennyfarthing-dist/guides/xml-tags.md +5 -4
  134. package/pennyfarthing-dist/personas/themes/hogans-heroes.yaml +11 -22
  135. package/pennyfarthing-dist/personas/themes/stephen-king.yaml +13 -24
  136. package/pennyfarthing-dist/scripts/core/dialogue-manager.sh +322 -0
  137. package/pennyfarthing-dist/scripts/core/phase-check-start.sh +1 -1
  138. package/pennyfarthing-dist/scripts/hooks/otel-auto-config.sh +19 -14
  139. package/pennyfarthing-dist/scripts/portraits/generate-portraits.py +191 -57
  140. package/pennyfarthing-dist/scripts/portraits/generate-portraits.sh +26 -10
  141. package/pennyfarthing-dist/skills/pf-changelog/SKILL.md +4 -4
  142. package/pennyfarthing-dist/skills/pf-sprint/skill.md +1 -1
  143. package/pennyfarthing-dist/skills/skill-registry.schema.json +4 -0
  144. package/pennyfarthing-dist/skills/skill-registry.yaml +5 -0
  145. package/pennyfarthing-dist/workflows/2party-tdd.yaml +11 -0
  146. package/pennyfarthing-dist/workflows/agent-docs.yaml +2 -0
  147. package/pennyfarthing-dist/workflows/bdd-tandem.yaml +4 -0
  148. package/pennyfarthing-dist/workflows/bdd.yaml +4 -0
  149. package/pennyfarthing-dist/workflows/git-cleanup/steps/step-05-complete.md +1 -1
  150. package/pennyfarthing-dist/workflows/tdd-tandem.yaml +3 -0
  151. package/pennyfarthing-dist/workflows/tdd.yaml +3 -0
  152. package/pennyfarthing-dist/workflows/trivial.yaml +2 -0
  153. package/pennyfarthing_scripts/__pycache__/__init__.cpython-314.pyc +0 -0
  154. package/pennyfarthing_scripts/__pycache__/bellmode_hook.cpython-314.pyc +0 -0
  155. package/pennyfarthing_scripts/__pycache__/cli.cpython-314.pyc +0 -0
  156. package/pennyfarthing_scripts/__pycache__/config.cpython-314.pyc +0 -0
  157. package/pennyfarthing_scripts/__pycache__/context.cpython-314.pyc +0 -0
  158. package/pennyfarthing_scripts/__pycache__/hooks.cpython-314.pyc +0 -0
  159. package/pennyfarthing_scripts/__pycache__/jira_bidirectional_sync.cpython-314.pyc +0 -0
  160. package/pennyfarthing_scripts/__pycache__/jira_epic_creation.cpython-314.pyc +0 -0
  161. package/pennyfarthing_scripts/__pycache__/jira_sync.cpython-314.pyc +0 -0
  162. package/pennyfarthing_scripts/__pycache__/jira_sync_story.cpython-314.pyc +0 -0
  163. package/pennyfarthing_scripts/__pycache__/output.cpython-314.pyc +0 -0
  164. package/pennyfarthing_scripts/__pycache__/patch_mode.cpython-314.pyc +0 -0
  165. package/pennyfarthing_scripts/__pycache__/pretooluse_hook.cpython-314.pyc +0 -0
  166. package/pennyfarthing_scripts/__pycache__/schema_validation_hook.cpython-314.pyc +0 -0
  167. package/pennyfarthing_scripts/__pycache__/session_start_hook.cpython-314.pyc +0 -0
  168. package/pennyfarthing_scripts/__pycache__/workflow.cpython-314.pyc +0 -0
  169. package/pennyfarthing_scripts/bc/__pycache__/__init__.cpython-314.pyc +0 -0
  170. package/pennyfarthing_scripts/bc/__pycache__/cli.cpython-314.pyc +0 -0
  171. package/pennyfarthing_scripts/bc/__pycache__/focus.cpython-314.pyc +0 -0
  172. package/pennyfarthing_scripts/bikerack/__pycache__/__init__.cpython-314.pyc +0 -0
  173. package/pennyfarthing_scripts/bikerack/__pycache__/__main__.cpython-314.pyc +0 -0
  174. package/pennyfarthing_scripts/bikerack/__pycache__/background_panel.cpython-314.pyc +0 -0
  175. package/pennyfarthing_scripts/bikerack/__pycache__/base_panel.cpython-314.pyc +0 -0
  176. package/pennyfarthing_scripts/bikerack/__pycache__/changed_panel.cpython-314.pyc +0 -0
  177. package/pennyfarthing_scripts/bikerack/__pycache__/cli.cpython-314.pyc +0 -0
  178. package/pennyfarthing_scripts/bikerack/__pycache__/debug_panel.cpython-314.pyc +0 -0
  179. package/pennyfarthing_scripts/bikerack/__pycache__/diffs_panel.cpython-314.pyc +0 -0
  180. package/pennyfarthing_scripts/bikerack/__pycache__/git_panel.cpython-314.pyc +0 -0
  181. package/pennyfarthing_scripts/bikerack/__pycache__/launcher.cpython-314.pyc +0 -0
  182. package/pennyfarthing_scripts/bikerack/__pycache__/sprint_panel.cpython-314.pyc +0 -0
  183. package/pennyfarthing_scripts/bikerack/__pycache__/tui.cpython-314.pyc +0 -0
  184. package/pennyfarthing_scripts/bikerack/__pycache__/ws_client.cpython-314.pyc +0 -0
  185. package/pennyfarthing_scripts/bikerack/cli.py +10 -11
  186. package/pennyfarthing_scripts/bikerack/debug_panel.py +218 -0
  187. package/pennyfarthing_scripts/bikerack/diffs_panel.py +203 -27
  188. package/pennyfarthing_scripts/brownfield/__pycache__/__init__.cpython-314.pyc +0 -0
  189. package/pennyfarthing_scripts/brownfield/__pycache__/__main__.cpython-314.pyc +0 -0
  190. package/pennyfarthing_scripts/brownfield/__pycache__/cli.cpython-314.pyc +0 -0
  191. package/pennyfarthing_scripts/brownfield/__pycache__/discover.cpython-314.pyc +0 -0
  192. package/pennyfarthing_scripts/cli.py +114 -0
  193. package/pennyfarthing_scripts/codemarkers/__pycache__/__init__.cpython-314.pyc +0 -0
  194. package/pennyfarthing_scripts/codemarkers/__pycache__/__main__.cpython-314.pyc +0 -0
  195. package/pennyfarthing_scripts/codemarkers/__pycache__/analyze.cpython-314.pyc +0 -0
  196. package/pennyfarthing_scripts/codemarkers/__pycache__/cli.cpython-314.pyc +0 -0
  197. package/pennyfarthing_scripts/codemarkers/__pycache__/formatters.cpython-314.pyc +0 -0
  198. package/pennyfarthing_scripts/codemarkers/__pycache__/models.cpython-314.pyc +0 -0
  199. package/pennyfarthing_scripts/common/__pycache__/__init__.cpython-314.pyc +0 -0
  200. package/pennyfarthing_scripts/common/__pycache__/config.cpython-314.pyc +0 -0
  201. package/pennyfarthing_scripts/common/__pycache__/output.cpython-314.pyc +0 -0
  202. package/pennyfarthing_scripts/common/__pycache__/themes.cpython-314.pyc +0 -0
  203. package/pennyfarthing_scripts/complexity/__pycache__/__init__.cpython-314.pyc +0 -0
  204. package/pennyfarthing_scripts/complexity/__pycache__/__main__.cpython-314.pyc +0 -0
  205. package/pennyfarthing_scripts/complexity/__pycache__/analyze.cpython-314.pyc +0 -0
  206. package/pennyfarthing_scripts/complexity/__pycache__/cli.cpython-314.pyc +0 -0
  207. package/pennyfarthing_scripts/complexity/__pycache__/formatters.cpython-314.pyc +0 -0
  208. package/pennyfarthing_scripts/complexity/__pycache__/models.cpython-314.pyc +0 -0
  209. package/pennyfarthing_scripts/deadcode/__pycache__/__init__.cpython-314.pyc +0 -0
  210. package/pennyfarthing_scripts/deadcode/__pycache__/__main__.cpython-314.pyc +0 -0
  211. package/pennyfarthing_scripts/deadcode/__pycache__/analyze.cpython-314.pyc +0 -0
  212. package/pennyfarthing_scripts/deadcode/__pycache__/cli.cpython-314.pyc +0 -0
  213. package/pennyfarthing_scripts/deadcode/__pycache__/formatters.cpython-314.pyc +0 -0
  214. package/pennyfarthing_scripts/deadcode/__pycache__/models.cpython-314.pyc +0 -0
  215. package/pennyfarthing_scripts/dependencies/__pycache__/__init__.cpython-314.pyc +0 -0
  216. package/pennyfarthing_scripts/dependencies/__pycache__/__main__.cpython-314.pyc +0 -0
  217. package/pennyfarthing_scripts/dependencies/__pycache__/analyze.cpython-314.pyc +0 -0
  218. package/pennyfarthing_scripts/dependencies/__pycache__/cli.cpython-314.pyc +0 -0
  219. package/pennyfarthing_scripts/dependencies/__pycache__/formatters.cpython-314.pyc +0 -0
  220. package/pennyfarthing_scripts/dependencies/__pycache__/models.cpython-314.pyc +0 -0
  221. package/pennyfarthing_scripts/epic/__init__.py +0 -0
  222. package/pennyfarthing_scripts/epic/cli.py +64 -0
  223. package/pennyfarthing_scripts/gate/__init__.py +1 -0
  224. package/pennyfarthing_scripts/gate/__pycache__/__init__.cpython-314.pyc +0 -0
  225. package/pennyfarthing_scripts/gate/__pycache__/cli.cpython-314.pyc +0 -0
  226. package/pennyfarthing_scripts/gate/__pycache__/validate.cpython-314.pyc +0 -0
  227. package/pennyfarthing_scripts/gate/cli.py +56 -0
  228. package/pennyfarthing_scripts/gate/validate.py +266 -0
  229. package/pennyfarthing_scripts/git/__pycache__/__init__.cpython-314.pyc +0 -0
  230. package/pennyfarthing_scripts/git/__pycache__/create_branches.cpython-314.pyc +0 -0
  231. package/pennyfarthing_scripts/git/__pycache__/status_all.cpython-314.pyc +0 -0
  232. package/pennyfarthing_scripts/git_group/__init__.py +0 -0
  233. package/pennyfarthing_scripts/git_group/cli.py +100 -0
  234. package/pennyfarthing_scripts/handoff/__init__.py +1 -0
  235. package/pennyfarthing_scripts/handoff/__pycache__/__init__.cpython-314.pyc +0 -0
  236. package/pennyfarthing_scripts/handoff/__pycache__/cli.cpython-314.pyc +0 -0
  237. package/pennyfarthing_scripts/handoff/__pycache__/complete_phase.cpython-314.pyc +0 -0
  238. package/pennyfarthing_scripts/handoff/__pycache__/gate_file.cpython-314.pyc +0 -0
  239. package/pennyfarthing_scripts/handoff/__pycache__/gate_runner.cpython-314.pyc +0 -0
  240. package/pennyfarthing_scripts/handoff/__pycache__/marker.cpython-314.pyc +0 -0
  241. package/pennyfarthing_scripts/handoff/__pycache__/resolve_gate.cpython-314.pyc +0 -0
  242. package/pennyfarthing_scripts/handoff/cli.py +120 -0
  243. package/pennyfarthing_scripts/handoff/complete_phase.py +155 -0
  244. package/pennyfarthing_scripts/handoff/gate_file.py +105 -0
  245. package/pennyfarthing_scripts/handoff/gate_runner.py +152 -0
  246. package/pennyfarthing_scripts/handoff/marker.py +109 -0
  247. package/pennyfarthing_scripts/handoff/resolve_gate.py +152 -0
  248. package/pennyfarthing_scripts/healthscore/__pycache__/__init__.cpython-314.pyc +0 -0
  249. package/pennyfarthing_scripts/healthscore/__pycache__/__main__.cpython-314.pyc +0 -0
  250. package/pennyfarthing_scripts/healthscore/__pycache__/analyze.cpython-314.pyc +0 -0
  251. package/pennyfarthing_scripts/healthscore/__pycache__/cli.cpython-314.pyc +0 -0
  252. package/pennyfarthing_scripts/healthscore/__pycache__/formatters.cpython-314.pyc +0 -0
  253. package/pennyfarthing_scripts/healthscore/__pycache__/models.cpython-314.pyc +0 -0
  254. package/pennyfarthing_scripts/hotspots/__pycache__/__init__.cpython-314.pyc +0 -0
  255. package/pennyfarthing_scripts/hotspots/__pycache__/__main__.cpython-314.pyc +0 -0
  256. package/pennyfarthing_scripts/hotspots/__pycache__/analyze.cpython-314.pyc +0 -0
  257. package/pennyfarthing_scripts/hotspots/__pycache__/cli.cpython-314.pyc +0 -0
  258. package/pennyfarthing_scripts/hotspots/__pycache__/formatters.cpython-314.pyc +0 -0
  259. package/pennyfarthing_scripts/hotspots/__pycache__/models.cpython-314.pyc +0 -0
  260. package/pennyfarthing_scripts/jira/__pycache__/__init__.cpython-314.pyc +0 -0
  261. package/pennyfarthing_scripts/jira/__pycache__/__main__.cpython-314.pyc +0 -0
  262. package/pennyfarthing_scripts/jira/__pycache__/bidirectional.cpython-314.pyc +0 -0
  263. package/pennyfarthing_scripts/jira/__pycache__/claim.cpython-314.pyc +0 -0
  264. package/pennyfarthing_scripts/jira/__pycache__/cli.cpython-314.pyc +0 -0
  265. package/pennyfarthing_scripts/jira/__pycache__/client.cpython-314.pyc +0 -0
  266. package/pennyfarthing_scripts/jira/__pycache__/create.cpython-314.pyc +0 -0
  267. package/pennyfarthing_scripts/jira/__pycache__/epic.cpython-314.pyc +0 -0
  268. package/pennyfarthing_scripts/jira/__pycache__/operations.cpython-314.pyc +0 -0
  269. package/pennyfarthing_scripts/jira/__pycache__/reconcile.cpython-314.pyc +0 -0
  270. package/pennyfarthing_scripts/jira/__pycache__/story.cpython-314.pyc +0 -0
  271. package/pennyfarthing_scripts/jira/__pycache__/sync.cpython-314.pyc +0 -0
  272. package/pennyfarthing_scripts/launch/__pycache__/__init__.cpython-314.pyc +0 -0
  273. package/pennyfarthing_scripts/launch/__pycache__/cli.cpython-314.pyc +0 -0
  274. package/pennyfarthing_scripts/migration/__pycache__/__init__.cpython-314.pyc +0 -0
  275. package/pennyfarthing_scripts/migration/__pycache__/session.cpython-314.pyc +0 -0
  276. package/pennyfarthing_scripts/migration/__pycache__/skill.cpython-314.pyc +0 -0
  277. package/pennyfarthing_scripts/migration/__pycache__/step.cpython-314.pyc +0 -0
  278. package/pennyfarthing_scripts/migration/__pycache__/validate.cpython-314.pyc +0 -0
  279. package/pennyfarthing_scripts/preflight/__pycache__/__init__.cpython-314.pyc +0 -0
  280. package/pennyfarthing_scripts/preflight/__pycache__/__main__.cpython-314.pyc +0 -0
  281. package/pennyfarthing_scripts/preflight/__pycache__/cli.cpython-314.pyc +0 -0
  282. package/pennyfarthing_scripts/preflight/__pycache__/finish.cpython-314.pyc +0 -0
  283. package/pennyfarthing_scripts/prime/__pycache__/__init__.cpython-314.pyc +0 -0
  284. package/pennyfarthing_scripts/prime/__pycache__/cli.cpython-314.pyc +0 -0
  285. package/pennyfarthing_scripts/prime/__pycache__/loader.cpython-314.pyc +0 -0
  286. package/pennyfarthing_scripts/prime/__pycache__/models.cpython-314.pyc +0 -0
  287. package/pennyfarthing_scripts/prime/__pycache__/persona.cpython-314.pyc +0 -0
  288. package/pennyfarthing_scripts/prime/__pycache__/session.cpython-314.pyc +0 -0
  289. package/pennyfarthing_scripts/prime/__pycache__/tiers.cpython-314.pyc +0 -0
  290. package/pennyfarthing_scripts/prime/__pycache__/version_sentinel.cpython-314.pyc +0 -0
  291. package/pennyfarthing_scripts/prime/__pycache__/workflow.cpython-314.pyc +0 -0
  292. package/pennyfarthing_scripts/prime/workflow.py +39 -0
  293. package/pennyfarthing_scripts/session/__init__.py +0 -0
  294. package/pennyfarthing_scripts/session/cli.py +87 -0
  295. package/pennyfarthing_scripts/sprint/__pycache__/__init__.cpython-314.pyc +0 -0
  296. package/pennyfarthing_scripts/sprint/__pycache__/__main__.cpython-314.pyc +0 -0
  297. package/pennyfarthing_scripts/sprint/__pycache__/archive.cpython-314.pyc +0 -0
  298. package/pennyfarthing_scripts/sprint/__pycache__/archive_epic.cpython-314.pyc +0 -0
  299. package/pennyfarthing_scripts/sprint/__pycache__/cli.cpython-314.pyc +0 -0
  300. package/pennyfarthing_scripts/sprint/__pycache__/epic_add.cpython-314.pyc +0 -0
  301. package/pennyfarthing_scripts/sprint/__pycache__/epic_update.cpython-314.pyc +0 -0
  302. package/pennyfarthing_scripts/sprint/__pycache__/import_epic.cpython-314.pyc +0 -0
  303. package/pennyfarthing_scripts/sprint/__pycache__/loader.cpython-314.pyc +0 -0
  304. package/pennyfarthing_scripts/sprint/__pycache__/status.cpython-314.pyc +0 -0
  305. package/pennyfarthing_scripts/sprint/__pycache__/story_add.cpython-314.pyc +0 -0
  306. package/pennyfarthing_scripts/sprint/__pycache__/story_finish.cpython-314.pyc +0 -0
  307. package/pennyfarthing_scripts/sprint/__pycache__/story_update.cpython-314.pyc +0 -0
  308. package/pennyfarthing_scripts/sprint/__pycache__/validate_cmd.cpython-314.pyc +0 -0
  309. package/pennyfarthing_scripts/sprint/__pycache__/validator.cpython-314.pyc +0 -0
  310. package/pennyfarthing_scripts/sprint/__pycache__/work.cpython-314.pyc +0 -0
  311. package/pennyfarthing_scripts/sprint/__pycache__/yaml_io.cpython-314.pyc +0 -0
  312. package/pennyfarthing_scripts/sprint/story_finish.py +14 -0
  313. package/pennyfarthing_scripts/story/__pycache__/__init__.cpython-314.pyc +0 -0
  314. package/pennyfarthing_scripts/story/__pycache__/__main__.cpython-314.pyc +0 -0
  315. package/pennyfarthing_scripts/story/__pycache__/cli.cpython-314.pyc +0 -0
  316. package/pennyfarthing_scripts/story/__pycache__/create.cpython-314.pyc +0 -0
  317. package/pennyfarthing_scripts/story/__pycache__/size.cpython-314.pyc +0 -0
  318. package/pennyfarthing_scripts/story/__pycache__/template.cpython-314.pyc +0 -0
  319. package/pennyfarthing_scripts/tests/__pycache__/__init__.cpython-314.pyc +0 -0
  320. package/pennyfarthing_scripts/tests/__pycache__/conftest.cpython-314-pytest-9.0.2.pyc +0 -0
  321. package/pennyfarthing_scripts/tests/__pycache__/test_108_2_remove_handoff_fallback.cpython-314-pytest-9.0.2.pyc +0 -0
  322. package/pennyfarthing_scripts/tests/__pycache__/test_archive_epic.cpython-314-pytest-9.0.2.pyc +0 -0
  323. package/pennyfarthing_scripts/tests/__pycache__/test_bc.cpython-314-pytest-9.0.2.pyc +0 -0
  324. package/pennyfarthing_scripts/tests/__pycache__/test_bikerack.cpython-314-pytest-9.0.2.pyc +0 -0
  325. package/pennyfarthing_scripts/tests/__pycache__/test_brownfield.cpython-314-pytest-9.0.2.pyc +0 -0
  326. package/pennyfarthing_scripts/tests/__pycache__/test_cli_modules.cpython-314-pytest-9.0.2.pyc +0 -0
  327. package/pennyfarthing_scripts/tests/__pycache__/test_cli_normalization.cpython-314-pytest-9.0.2.pyc +0 -0
  328. package/pennyfarthing_scripts/tests/__pycache__/test_codemarkers.cpython-314-pytest-9.0.2.pyc +0 -0
  329. package/pennyfarthing_scripts/tests/__pycache__/test_common.cpython-314-pytest-9.0.2.pyc +0 -0
  330. package/pennyfarthing_scripts/tests/__pycache__/test_epic_shard_validation.cpython-314-pytest-9.0.2.pyc +0 -0
  331. package/pennyfarthing_scripts/tests/__pycache__/test_gate_file_resolution.cpython-314-pytest-9.0.2.pyc +0 -0
  332. package/pennyfarthing_scripts/tests/__pycache__/test_gate_runner.cpython-314-pytest-9.0.2.pyc +0 -0
  333. package/pennyfarthing_scripts/tests/__pycache__/test_git_utils.cpython-314-pytest-9.0.2.pyc +0 -0
  334. package/pennyfarthing_scripts/tests/__pycache__/test_handoff_cli.cpython-314-pytest-9.0.2.pyc +0 -0
  335. package/pennyfarthing_scripts/tests/__pycache__/test_handoff_e2e.cpython-314-pytest-9.0.2.pyc +0 -0
  336. package/pennyfarthing_scripts/tests/__pycache__/test_healthscore.cpython-314-pytest-9.0.2.pyc +0 -0
  337. package/pennyfarthing_scripts/tests/__pycache__/test_jira_package.cpython-314-pytest-9.0.2.pyc +0 -0
  338. package/pennyfarthing_scripts/tests/__pycache__/test_package_structure.cpython-314-pytest-9.0.2.pyc +0 -0
  339. package/pennyfarthing_scripts/tests/__pycache__/test_patch_mode.cpython-314-pytest-9.0.2.pyc +0 -0
  340. package/pennyfarthing_scripts/tests/__pycache__/test_prime.cpython-314-pytest-9.0.2.pyc +0 -0
  341. package/pennyfarthing_scripts/tests/__pycache__/test_resolve_gate_file_field.cpython-314-pytest-9.0.2.pyc +0 -0
  342. package/pennyfarthing_scripts/tests/__pycache__/test_sprint_package.cpython-314-pytest-9.0.2.pyc +0 -0
  343. package/pennyfarthing_scripts/tests/__pycache__/test_sprint_panel.cpython-314-pytest-9.0.2.pyc +0 -0
  344. package/pennyfarthing_scripts/tests/__pycache__/test_sprint_validator.cpython-314-pytest-9.0.2.pyc +0 -0
  345. package/pennyfarthing_scripts/tests/__pycache__/test_story_add.cpython-314-pytest-9.0.2.pyc +0 -0
  346. package/pennyfarthing_scripts/tests/__pycache__/test_story_package.cpython-314-pytest-9.0.2.pyc +0 -0
  347. package/pennyfarthing_scripts/tests/__pycache__/test_story_update.cpython-314-pytest-9.0.2.pyc +0 -0
  348. package/pennyfarthing_scripts/tests/__pycache__/test_tiers.cpython-314-pytest-9.0.2.pyc +0 -0
  349. package/pennyfarthing_scripts/tests/__pycache__/test_token_counting.cpython-314-pytest-9.0.2.pyc +0 -0
  350. package/pennyfarthing_scripts/tests/__pycache__/test_topology_loader.cpython-314-pytest-9.0.2.pyc +0 -0
  351. package/pennyfarthing_scripts/tests/__pycache__/test_tui_focus.cpython-314-pytest-9.0.2.pyc +0 -0
  352. package/pennyfarthing_scripts/tests/__pycache__/test_tui_panel_persistence.cpython-314-pytest-9.0.2.pyc +0 -0
  353. package/pennyfarthing_scripts/tests/__pycache__/test_validate_cmd.cpython-314-pytest-9.0.2.pyc +0 -0
  354. package/pennyfarthing_scripts/tests/__pycache__/test_version_sentinel.cpython-314-pytest-9.0.2.pyc +0 -0
  355. package/pennyfarthing_scripts/tests/__pycache__/test_workflow_check.cpython-314-pytest-9.0.2.pyc +0 -0
  356. package/pennyfarthing_scripts/tests/__pycache__/test_yaml_io.cpython-314-pytest-9.0.2.pyc +0 -0
  357. package/pennyfarthing_scripts/tests/test_108_1_gate_migration.py +540 -0
  358. package/pennyfarthing_scripts/tests/test_108_2_remove_handoff_fallback.py +339 -0
  359. package/pennyfarthing_scripts/tests/test_confidence_sm_evaluation.py +253 -0
  360. package/pennyfarthing_scripts/tests/test_confidence_sm_gate.py +315 -0
  361. package/pennyfarthing_scripts/tests/test_gate_file_resolution.py +341 -0
  362. package/pennyfarthing_scripts/tests/test_gate_runner.py +620 -0
  363. package/pennyfarthing_scripts/tests/test_handoff_cli.py +929 -0
  364. package/pennyfarthing_scripts/tests/test_handoff_e2e.py +454 -0
  365. package/pennyfarthing_scripts/tests/test_resolve_gate_file_field.py +464 -0
  366. package/pennyfarthing_scripts/theme/__pycache__/__init__.cpython-314.pyc +0 -0
  367. package/pennyfarthing_scripts/theme/__pycache__/cli.cpython-314.pyc +0 -0
  368. package/pennyfarthing_scripts/validate/__pycache__/__init__.cpython-314.pyc +0 -0
  369. package/pennyfarthing_scripts/validate/__pycache__/cli.cpython-314.pyc +0 -0
  370. package/pennyfarthing_scripts/validate/adapters/__pycache__/__init__.cpython-314.pyc +0 -0
  371. package/pennyfarthing_scripts/validate/adapters/__pycache__/agent.cpython-314.pyc +0 -0
  372. package/pennyfarthing_scripts/validate/adapters/__pycache__/schema.cpython-314.pyc +0 -0
  373. package/pennyfarthing_scripts/validate/adapters/__pycache__/skill_command.cpython-314.pyc +0 -0
  374. package/pennyfarthing_scripts/validate/adapters/__pycache__/sprint.cpython-314.pyc +0 -0
  375. package/pennyfarthing_scripts/validate/adapters/__pycache__/workflow.cpython-314.pyc +0 -0
  376. package/pennyfarthing_scripts/validate/adapters/skill_command.py +200 -0
  377. package/pennyfarthing_scripts/validate/adapters/workflow.py +64 -0
  378. package/pennyfarthing_scripts/validate/cli.py +15 -4
  379. package/packages/core/dist/scripts/benchmark-integration.d.ts +0 -182
  380. package/packages/core/dist/scripts/benchmark-integration.d.ts.map +0 -1
  381. package/packages/core/dist/scripts/benchmark-integration.js +0 -691
  382. package/packages/core/dist/scripts/benchmark-integration.js.map +0 -1
  383. package/packages/core/dist/scripts/job-fair-aggregator.d.ts +0 -150
  384. package/packages/core/dist/scripts/job-fair-aggregator.d.ts.map +0 -1
  385. package/packages/core/dist/scripts/job-fair-aggregator.js +0 -547
  386. package/packages/core/dist/scripts/job-fair-aggregator.js.map +0 -1
  387. package/pennyfarthing-dist/agents/handoff.md +0 -250
  388. package/pennyfarthing-dist/agents/sm-handoff.md +0 -152
  389. package/pennyfarthing-dist/scripts/core/handoff-marker.sh +0 -112
  390. package/pennyfarthing-dist/scripts/hooks/__pycache__/question_reflector_check.cpython-314.pyc +0 -0
  391. package/pennyfarthing_scripts/__pycache__/__init__.cpython-311.pyc +0 -0
  392. package/pennyfarthing_scripts/__pycache__/jira.cpython-314.pyc +0 -0
  393. package/pennyfarthing_scripts/__pycache__/sprint.cpython-314.pyc +0 -0
  394. package/pennyfarthing_scripts/__pycache__/workflow.cpython-311.pyc +0 -0
  395. package/pennyfarthing_scripts/jira/__pycache__/compat.cpython-314.pyc +0 -0
  396. package/pennyfarthing_scripts/jira/__pycache__/mappings.cpython-314.pyc +0 -0
  397. package/pennyfarthing_scripts/jira/__pycache__/models.cpython-314.pyc +0 -0
  398. package/pennyfarthing_scripts/migration/__pycache__/__main__.cpython-314.pyc +0 -0
  399. package/pennyfarthing_scripts/migration/__pycache__/cli.cpython-314.pyc +0 -0
  400. package/pennyfarthing_scripts/prime/__pycache__/__main__.cpython-314.pyc +0 -0
  401. package/pennyfarthing_scripts/tests/__pycache__/test_workflow_cli.cpython-314-pytest-9.0.2.pyc +0 -0
@@ -2,12 +2,16 @@
2
2
 
3
3
  Story 103-18: Subscribes to /ws/diffs, renders file diffs with syntax
4
4
  highlighting using Rich. File headers, added/removed line coloring, line numbers.
5
+
6
+ Story 103-19: Large diff handling — truncation at 1000 lines, pagination,
7
+ temp file storage for very large diffs (>5000 lines), non-blocking rendering.
5
8
  """
6
9
 
7
10
  from __future__ import annotations
8
11
 
9
12
  import os
10
13
  import re
14
+ import tempfile
11
15
  from typing import Any
12
16
 
13
17
  from rich.console import Group
@@ -16,6 +20,16 @@ from rich.text import Text
16
20
 
17
21
  from pennyfarthing_scripts.bikerack.base_panel import PANEL_ICONS, BasePanel
18
22
 
23
+ #: Default max content lines rendered per page before truncation kicks in.
24
+ DEFAULT_LINE_LIMIT = 1000
25
+
26
+ #: Diffs with more raw lines than this threshold are written to temp files.
27
+ TEMP_FILE_THRESHOLD = 5000
28
+
29
+ #: Skip syntax highlighting for diffs larger than this to maintain rendering performance.
30
+ #: At 5000+ lines, syntax highlighting cost becomes prohibitive for <100ms frame time.
31
+ HIGHLIGHT_THRESHOLD = 5000
32
+
19
33
 
20
34
  class DiffsPanel(BasePanel):
21
35
  """Diff rendering panel with syntax highlighting.
@@ -23,25 +37,99 @@ class DiffsPanel(BasePanel):
23
37
  Subscribes to the ``diffs`` WebSocket channel and renders
24
38
  file diffs with syntax highlighting, colored added/removed lines,
25
39
  file headers, and line numbers.
40
+
41
+ Large diffs (>1000 content lines) are truncated with a text indicator
42
+ and support pagination via ``next_page()`` / ``prev_page()``.
43
+ Very large diffs (>5000 lines) use temp file storage for memory efficiency.
26
44
  """
27
45
 
28
46
  channel: str = "diffs"
29
47
  panel_name: str = "Diffs"
30
48
  icon: str = PANEL_ICONS["diffs"][0]
31
49
 
50
+ def __init__(self, client=None, **kwargs):
51
+ super().__init__(client=client, **kwargs)
52
+ self._current_page: int = 0
53
+ self._max_page: int = 0
54
+ self._temp_files: list[str] = []
55
+
56
+ def next_page(self) -> None:
57
+ """Advance to the next page of truncated diff content."""
58
+ if self._current_page < self._max_page:
59
+ self._current_page += 1
60
+
61
+ def prev_page(self) -> None:
62
+ """Go back to the previous page of truncated diff content."""
63
+ if self._current_page > 0:
64
+ self._current_page -= 1
65
+
66
+ def handle_message(self, message: dict[str, Any] | None) -> None:
67
+ """Handle incoming WebSocket message with pagination reset and temp management."""
68
+ if not self._mounted or message is None:
69
+ return
70
+ self._current_page = 0
71
+ self._cleanup_temp_files()
72
+ self._store_large_diffs(message)
73
+ super().handle_message(message)
74
+
75
+ def on_unmount(self) -> None:
76
+ """Clean up temp files and mark panel as unmounted."""
77
+ self._cleanup_temp_files()
78
+ super().on_unmount()
79
+
32
80
  def render_panel(self, payload: dict[str, Any]) -> Any:
33
- """Render diff data from WebSocket payload."""
81
+ """Render diff data from WebSocket payload with truncation/pagination."""
34
82
  diffs = payload.get("diffs", [])
35
83
  if not diffs:
36
84
  return Text("No diffs yet", style="dim italic")
37
85
 
38
86
  parts: list[Any] = []
87
+ max_total = 0
39
88
  for diff_entry in diffs:
40
- parts.extend(_render_file_diff(diff_entry))
89
+ # Skip syntax highlighting for very large diffs (>2000 lines) for performance
90
+ raw_diff = diff_entry.get("diff", "")
91
+ skip_highlight = raw_diff.count("\n") > HIGHLIGHT_THRESHOLD
92
+
93
+ file_parts, total_lines = _render_file_diff(
94
+ diff_entry,
95
+ page=self._current_page,
96
+ page_size=DEFAULT_LINE_LIMIT,
97
+ skip_highlight=skip_highlight,
98
+ )
99
+ parts.extend(file_parts)
41
100
  parts.append(Text("")) # separator between files
101
+ max_total = max(max_total, total_lines)
102
+
103
+ # Track max page for pagination bounds
104
+ if max_total > DEFAULT_LINE_LIMIT:
105
+ self._max_page = -(-max_total // DEFAULT_LINE_LIMIT) - 1
106
+ else:
107
+ self._max_page = 0
42
108
 
43
109
  return Group(*parts)
44
110
 
111
+ def _store_large_diffs(self, message: dict[str, Any]) -> None:
112
+ """Write very large diffs to temp files for memory-efficient access."""
113
+ diffs = message.get("diffs", [])
114
+ for diff_entry in diffs:
115
+ raw_diff = diff_entry.get("diff", "")
116
+ if raw_diff.count("\n") > TEMP_FILE_THRESHOLD:
117
+ fd, path = tempfile.mkstemp(
118
+ prefix="bikerack_diff_", suffix=".diff"
119
+ )
120
+ with os.fdopen(fd, "w") as f:
121
+ f.write(raw_diff)
122
+ self._temp_files.append(path)
123
+
124
+ def _cleanup_temp_files(self) -> None:
125
+ """Remove any temp files created for large diff storage."""
126
+ for path in self._temp_files:
127
+ try:
128
+ os.unlink(path)
129
+ except OSError:
130
+ pass
131
+ self._temp_files.clear()
132
+
45
133
 
46
134
  _LANG_MAP: dict[str, str] = {
47
135
  ".py": "python", ".ts": "typescript", ".tsx": "tsx",
@@ -74,8 +162,23 @@ def _highlight_code(code: str, language: str) -> Text:
74
162
  return Text(code)
75
163
 
76
164
 
77
- def _render_file_diff(diff_entry: dict[str, Any]) -> list[Any]:
78
- """Render a single file's diff as a list of Rich renderables."""
165
+ def _render_file_diff(
166
+ diff_entry: dict[str, Any],
167
+ page: int = 0,
168
+ page_size: int = DEFAULT_LINE_LIMIT,
169
+ skip_highlight: bool = False,
170
+ ) -> tuple[list[Any], int]:
171
+ """Render a single file's diff as a list of Rich renderables.
172
+
173
+ Args:
174
+ diff_entry: Diff data from WebSocket payload
175
+ page: Current page number for pagination
176
+ page_size: Lines per page for truncation
177
+ skip_highlight: Skip syntax highlighting for performance on large diffs
178
+
179
+ Returns:
180
+ Tuple of (renderable_parts, total_content_line_count).
181
+ """
79
182
  path = diff_entry.get("path", "unknown")
80
183
  status = diff_entry.get("status", "")
81
184
  additions = diff_entry.get("additions")
@@ -92,19 +195,70 @@ def _render_file_diff(diff_entry: dict[str, Any]) -> list[Any]:
92
195
 
93
196
  language = _detect_language(path)
94
197
  parts: list[Any] = [header]
95
- parts.extend(_parse_diff_lines(raw_diff, language))
96
- return parts
198
+ rendered_lines, total_lines = _parse_diff_lines(
199
+ raw_diff, language, page=page, page_size=page_size, skip_highlight=skip_highlight,
200
+ )
201
+ parts.extend(rendered_lines)
202
+
203
+ # Add truncation/pagination indicator if content was truncated
204
+ if total_lines > page_size:
205
+ total_pages = -(-total_lines // page_size) # ceil division
206
+ current_page_display = page + 1
207
+ visible_end = min((page + 1) * page_size, total_lines)
208
+
209
+ if page == 0:
210
+ indicator = (
211
+ f"Showing first {visible_end} of {total_lines} lines"
212
+ f" — Page {current_page_display} / {total_pages}"
213
+ )
214
+ else:
215
+ visible_start = page * page_size + 1
216
+ indicator = (
217
+ f"Showing lines {visible_start}-{visible_end}"
218
+ f" of {total_lines} lines"
219
+ f" — Page {current_page_display} / {total_pages}"
220
+ )
221
+ parts.append(Text(indicator, style="dim yellow"))
97
222
 
223
+ return parts, total_lines
98
224
 
99
- def _parse_diff_lines(raw_diff: str, language: str = "text") -> list[Any]:
100
- """Parse unified diff output into styled Rich Text lines with syntax highlighting."""
225
+
226
+ def _parse_diff_lines(
227
+ raw_diff: str,
228
+ language: str = "text",
229
+ page: int = 0,
230
+ page_size: int = DEFAULT_LINE_LIMIT,
231
+ skip_highlight: bool = False,
232
+ ) -> tuple[list[Any], int]:
233
+ """Parse unified diff output into styled Rich Text lines with syntax highlighting.
234
+
235
+ Optimized single-pass streaming:
236
+ - Only highlight lines within current page range (unless skip_highlight=True)
237
+ - Count total lines without building objects for them
238
+ - Scan entire diff to get accurate total for pagination
239
+
240
+ Args:
241
+ raw_diff: Raw unified diff string
242
+ language: Programming language for syntax highlighting
243
+ page: Current page number for pagination
244
+ page_size: Lines per page
245
+ skip_highlight: Skip syntax highlighting for performance on large diffs
246
+
247
+ Returns:
248
+ Tuple of (rendered_lines, total_content_line_count).
249
+ """
101
250
  if not raw_diff or not raw_diff.strip():
102
- return []
251
+ return [], 0
103
252
 
104
253
  result: list[Any] = []
105
254
  line_num = 0
255
+ content_idx = 0
256
+ start = page * page_size
257
+ end = start + page_size
258
+ current_hunk_header = None
106
259
 
107
260
  for line in raw_diff.split("\n"):
261
+ # Skip diff metadata lines (not counted as content)
108
262
  if line.startswith("diff --git") or line.startswith("index "):
109
263
  continue
110
264
  if line.startswith("---") or line.startswith("+++"):
@@ -113,34 +267,56 @@ def _parse_diff_lines(raw_diff: str, language: str = "text") -> list[Any]:
113
267
  continue
114
268
  if line.startswith("rename "):
115
269
  continue
270
+ if line == "":
271
+ continue
116
272
 
117
273
  if line.startswith("Binary files"):
118
- result.append(Text(line, style="dim italic"))
274
+ if start <= content_idx < end:
275
+ result.append(Text(line, style="dim italic"))
276
+ content_idx += 1
119
277
  elif line.startswith("@@"):
278
+ # @@ line is metadata, extract line number but don't count as content
279
+ current_hunk_header = line
120
280
  match = re.match(r"@@ -\d+(?:,\d+)? \+(\d+)", line)
121
281
  if match:
122
282
  line_num = int(match.group(1)) - 1
123
- result.append(Text(line, style="cyan"))
283
+ if start <= content_idx < end:
284
+ result.append(Text(current_hunk_header, style="cyan"))
124
285
  elif line.startswith("+"):
125
286
  line_num += 1
126
- t = Text()
127
- t.append(f"{line_num:4d} ", style="dim green")
128
- t.append_text(_highlight_code(line[1:], language))
129
- result.append(t)
287
+ if start <= content_idx < end:
288
+ t = Text()
289
+ t.append(f"{line_num:4d} ", style="dim green")
290
+ if not skip_highlight:
291
+ t.append_text(_highlight_code(line[1:], language))
292
+ else:
293
+ t.append(line[1:])
294
+ result.append(t)
295
+ content_idx += 1
130
296
  elif line.startswith("-"):
131
- t = Text()
132
- t.append(" - ", style="red")
133
- t.append_text(_highlight_code(line[1:], language))
134
- result.append(t)
297
+ if start <= content_idx < end:
298
+ t = Text()
299
+ t.append(" - ", style="red")
300
+ if not skip_highlight:
301
+ t.append_text(_highlight_code(line[1:], language))
302
+ else:
303
+ t.append(line[1:])
304
+ result.append(t)
305
+ content_idx += 1
135
306
  elif line.startswith(" "):
136
307
  line_num += 1
137
- t = Text()
138
- t.append(f"{line_num:4d} ", style="dim")
139
- t.append_text(_highlight_code(line[1:], language))
140
- result.append(t)
141
- elif line == "":
142
- continue
308
+ if start <= content_idx < end:
309
+ t = Text()
310
+ t.append(f"{line_num:4d} ", style="dim")
311
+ if not skip_highlight:
312
+ t.append_text(_highlight_code(line[1:], language))
313
+ else:
314
+ t.append(line[1:])
315
+ result.append(t)
316
+ content_idx += 1
143
317
  else:
144
- result.append(Text(line, style="dim"))
318
+ if start <= content_idx < end:
319
+ result.append(Text(line, style="dim"))
320
+ content_idx += 1
145
321
 
146
- return result
322
+ return result, content_idx
@@ -26,6 +26,9 @@ def cli():
26
26
  workflow - Workflow state and phase management
27
27
  agent - Agent session management
28
28
  sprint - Sprint status and story operations
29
+ git - Repository operations (status, cleanup, branches, release)
30
+ session - Session lifecycle (new, continue, parallel)
31
+ epic - Epic lifecycle (start, close)
29
32
  debug - Analysis tools (hotspots, deadcode, healthscore)
30
33
 
31
34
  \b
@@ -114,6 +117,26 @@ from pennyfarthing_scripts.bc.cli import bc # noqa: E402
114
117
 
115
118
  cli.add_command(bc)
116
119
 
120
+ # Import and register handoff group
121
+ from pennyfarthing_scripts.handoff.cli import handoff # noqa: E402
122
+
123
+ cli.add_command(handoff)
124
+
125
+ # Import and register git group
126
+ from pennyfarthing_scripts.git_group.cli import git # noqa: E402
127
+
128
+ cli.add_command(git)
129
+
130
+ # Import and register session group
131
+ from pennyfarthing_scripts.session.cli import session # noqa: E402
132
+
133
+ cli.add_command(session)
134
+
135
+ # Import and register epic group
136
+ from pennyfarthing_scripts.epic.cli import epic # noqa: E402
137
+
138
+ cli.add_command(epic)
139
+
117
140
 
118
141
  @cli.group()
119
142
  def agent():
@@ -244,6 +267,97 @@ def workflow_handoff(next_agent: str):
244
267
  click.echo("---")
245
268
 
246
269
 
270
+ @cli.command("help")
271
+ @click.argument("group", required=False)
272
+ def help_cmd(group: str | None):
273
+ """Context-aware help for Pennyfarthing commands.
274
+
275
+ \b
276
+ Arguments:
277
+ GROUP - Optional command group name (sprint, git, session, epic, jira, theme, workflow, etc.)
278
+ """
279
+
280
+ import yaml
281
+
282
+ from pennyfarthing_scripts.common.config import get_project_root
283
+
284
+ root = get_project_root()
285
+ registry_path = root / "pennyfarthing-dist" / "command-registry.yaml"
286
+
287
+ if not registry_path.is_file():
288
+ click.echo("Command registry not found. Run /pf-health-check.", err=True)
289
+ raise SystemExit(1)
290
+
291
+ registry = yaml.safe_load(registry_path.read_text())
292
+
293
+ if group is None:
294
+ # Show overview
295
+ click.echo("Pennyfarthing CLI — Command Reference")
296
+ click.echo("=" * 45)
297
+ click.echo("")
298
+ click.echo("Resource Groups:")
299
+ for name, grp in registry.get("groups", {}).items():
300
+ cli_cmd = grp.get("cli", "")
301
+ slash = grp.get("slash", "")
302
+ desc = grp.get("description", "")
303
+ parts = []
304
+ if slash:
305
+ parts.append(slash)
306
+ if cli_cmd:
307
+ parts.append(cli_cmd)
308
+ ref = ", ".join(parts)
309
+ click.echo(f" {name:<12} {desc:<45} ({ref})")
310
+ click.echo("")
311
+ click.echo("Standalone:")
312
+ for name, cmd in registry.get("standalone", {}).items():
313
+ slash = cmd.get("slash", "")
314
+ desc = cmd.get("description", "")
315
+ click.echo(f" {name:<12} {desc:<45} ({slash})")
316
+ click.echo("")
317
+ click.echo("Use 'pf help <group>' for detailed commands.")
318
+ return
319
+
320
+ # Show specific group
321
+ groups = registry.get("groups", {})
322
+ if group not in groups:
323
+ click.echo(f"Unknown group: {group}", err=True)
324
+ click.echo(f"Available groups: {', '.join(groups.keys())}", err=True)
325
+ raise SystemExit(1)
326
+
327
+ grp = groups[group]
328
+ click.echo(f"{group} — {grp.get('description', '')}")
329
+ click.echo("-" * 45)
330
+ if grp.get("cli"):
331
+ click.echo(f"CLI: {grp['cli']}")
332
+ if grp.get("slash"):
333
+ click.echo(f"Slash: {grp['slash']}")
334
+ if grp.get("skill"):
335
+ click.echo(f"Skill: {grp['skill']}")
336
+ click.echo("")
337
+
338
+ commands = grp.get("commands", {})
339
+ if commands:
340
+ click.echo("Commands:")
341
+ for cmd_name, cmd in commands.items():
342
+ args = cmd.get("args", "")
343
+ desc = cmd.get("description", "")
344
+ if args:
345
+ click.echo(f" {cmd_name} {args:<20} {desc}")
346
+ else:
347
+ click.echo(f" {cmd_name:<25} {desc}")
348
+
349
+ subgroups = grp.get("subgroups", {})
350
+ for sg_name, sg in subgroups.items():
351
+ click.echo(f"\n {sg_name} — {sg.get('description', '')}")
352
+ for cmd_name, cmd in sg.get("commands", {}).items():
353
+ args = cmd.get("args", "")
354
+ desc = cmd.get("description", "")
355
+ if args:
356
+ click.echo(f" {cmd_name} {args:<18} {desc}")
357
+ else:
358
+ click.echo(f" {cmd_name:<23} {desc}")
359
+
360
+
247
361
  def main():
248
362
  """Entry point for the CLI."""
249
363
  cli()
File without changes
@@ -0,0 +1,64 @@
1
+ """
2
+ Epic CLI - Click-based CLI for epic lifecycle management.
3
+
4
+ Usage:
5
+ pf epic [COMMAND] [ARGS]...
6
+
7
+ Commands:
8
+ start Start an epic - move to current sprint and generate context
9
+ close Close an epic - verify completion and archive
10
+ """
11
+
12
+ import click
13
+
14
+
15
+ @click.group()
16
+ def epic():
17
+ """Epic lifecycle management.
18
+
19
+ \b
20
+ Commands:
21
+ start - Start an epic (move to sprint + generate tech context)
22
+ close - Close an epic (verify completion + archive)
23
+ """
24
+ pass
25
+
26
+
27
+ @epic.command()
28
+ @click.argument("epic_id")
29
+ def start(epic_id: str):
30
+ """Start an epic - move to current sprint and generate tech context.
31
+
32
+ \b
33
+ Arguments:
34
+ EPIC_ID - Epic identifier (e.g., 79, epic-79)
35
+ """
36
+ # Normalize epic ID
37
+ if not epic_id.startswith("epic-"):
38
+ epic_id = f"epic-{epic_id}"
39
+
40
+ click.echo(f"Starting epic {epic_id}...")
41
+ click.echo("1. Checking epic location in sprint files...")
42
+ click.echo("2. Moving to current sprint if needed...")
43
+ click.echo("3. Generating tech context via SM agent...")
44
+ click.echo(f"\nTo complete setup, run: /sm with task epic-tech-context for {epic_id}")
45
+
46
+
47
+ @epic.command()
48
+ @click.argument("epic_id")
49
+ def close(epic_id: str):
50
+ """Close an epic - verify completion, update status, and archive.
51
+
52
+ \b
53
+ Arguments:
54
+ EPIC_ID - Epic identifier (e.g., 79, epic-79)
55
+ """
56
+ # Normalize epic ID
57
+ if not epic_id.startswith("epic-"):
58
+ epic_id = f"epic-{epic_id}"
59
+
60
+ click.echo(f"Closing epic {epic_id}...")
61
+ click.echo("1. Verifying all stories are done...")
62
+ click.echo("2. Updating epic status to done...")
63
+ click.echo("3. Archiving epic context...")
64
+ click.echo(f"\nUse pf sprint epic archive {epic_id} to complete the archival.")
@@ -0,0 +1 @@
1
+ """Gate file operations — validation, inspection, and authoring tools."""
@@ -0,0 +1,56 @@
1
+ """Gate CLI — gate file operations.
2
+
3
+ Usage:
4
+ pf gate validate <file> # Validate a gate file
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import click
10
+
11
+ from pennyfarthing_scripts.common.output import error, info, success
12
+
13
+
14
+ @click.group()
15
+ def gate():
16
+ """Gate file operations.
17
+
18
+ \b
19
+ Commands:
20
+ validate - Validate a gate file for schema, depth, and cycles
21
+ """
22
+ pass
23
+
24
+
25
+ @gate.command("validate")
26
+ @click.argument("file", type=click.Path(exists=True))
27
+ def gate_validate(file: str):
28
+ """Validate a gate file for schema, cycles, and depth.
29
+
30
+ Checks:
31
+ - Schema: <gate name="...">, <purpose>, <pass>, <fail>
32
+ - Depth: nesting does not exceed 3 levels
33
+ - Cycles: no duplicate gate names
34
+ - Completeness: all required elements non-empty
35
+
36
+ Reports ALL errors at once. On success, prints a structure summary.
37
+ """
38
+ from pennyfarthing_scripts.gate.validate import validate_gate_file
39
+
40
+ result = validate_gate_file(file)
41
+
42
+ if result.valid:
43
+ name = result.gate_name or "(unnamed)"
44
+ success(f"Gate '{name}' is valid")
45
+ info(f" Model: {result.model or 'haiku'}")
46
+ if result.child_count > 0:
47
+ info(f" Depth: {result.depth} ({result.child_count} nested gate(s))")
48
+ else:
49
+ info(f" Depth: {result.depth} (no nesting)")
50
+ info(f" Children: {result.child_count}")
51
+ else:
52
+ name = result.gate_name or file
53
+ error(f"Gate '{name}' has {len(result.errors)} error(s):")
54
+ for err in result.errors:
55
+ error(f" - {err}")
56
+ raise SystemExit(1)