fplkit 1.2.0__tar.gz → 1.3.0__tar.gz

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 (228) hide show
  1. {fplkit-1.2.0 → fplkit-1.3.0}/.agents/TOOLS.md +2 -1
  2. fplkit-1.3.0/.agents/skills/gw-prep/SKILL.md +553 -0
  3. fplkit-1.3.0/.agents/skills/gw-prep/references/output-template.md +177 -0
  4. {fplkit-1.2.0 → fplkit-1.3.0}/.agents/skills/gw-prep/references/rules.md +11 -0
  5. fplkit-1.3.0/.agents/skills/gw-prep/scripts/extract_classic_squad.py +445 -0
  6. {fplkit-1.2.0 → fplkit-1.3.0}/.agents/skills/squad-builder/SKILL.md +3 -3
  7. {fplkit-1.2.0 → fplkit-1.3.0}/.agents/skills/squad-builder/references/rules.md +1 -1
  8. {fplkit-1.2.0 → fplkit-1.3.0}/.gitignore +5 -2
  9. {fplkit-1.2.0 → fplkit-1.3.0}/AGENTS.md +2 -2
  10. {fplkit-1.2.0 → fplkit-1.3.0}/CHANGELOG.md +23 -0
  11. {fplkit-1.2.0 → fplkit-1.3.0}/CLAUDE.md +4 -4
  12. {fplkit-1.2.0 → fplkit-1.3.0}/PKG-INFO +2 -2
  13. {fplkit-1.2.0 → fplkit-1.3.0}/README.md +1 -1
  14. {fplkit-1.2.0 → fplkit-1.3.0}/docs/architecture.md +4 -3
  15. {fplkit-1.2.0 → fplkit-1.3.0}/docs/command-reference.md +10 -6
  16. {fplkit-1.2.0 → fplkit-1.3.0}/docs/custom-analysis.md +13 -7
  17. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/_version.py +2 -2
  18. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/agents/analysis/transfer_eval.py +5 -2
  19. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/agents/orchestration/report.py +55 -5
  20. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/cli/_helpers.py +27 -0
  21. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/cli/_review_analysis.py +4 -0
  22. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/cli/_review_classic.py +36 -10
  23. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/cli/_review_draft.py +60 -66
  24. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/cli/_review_summarisation.py +17 -3
  25. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/cli/allocate.py +11 -9
  26. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/cli/league_recap.py +3 -0
  27. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/cli/preview.py +37 -14
  28. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/cli/review.py +1 -1
  29. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/cli/stats.py +39 -1
  30. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/config/defaults.yaml +1 -1
  31. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/prompts/league_recap.py +48 -1
  32. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/prompts/review.py +20 -6
  33. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/services/player_prior.py +4 -4
  34. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/services/player_scoring.py +205 -30
  35. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/services/squad_allocator.py +10 -8
  36. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/templates/gw_league_recap.md.j2 +3 -5
  37. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/templates/gw_preview.md.j2 +4 -3
  38. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/templates/gw_review.md.j2 +20 -16
  39. {fplkit-1.2.0 → fplkit-1.3.0}/pyproject.toml +2 -0
  40. {fplkit-1.2.0 → fplkit-1.3.0}/pyrightconfig.json +1 -0
  41. {fplkit-1.2.0 → fplkit-1.3.0}/tests/conftest.py +1 -1
  42. fplkit-1.3.0/tests/fixtures/classic_squad_fixture.md +90 -0
  43. fplkit-1.3.0/tests/fixtures/recommendations_fixture.md +83 -0
  44. {fplkit-1.2.0 → fplkit-1.3.0}/tests/test_agents_action.py +4 -5
  45. {fplkit-1.2.0 → fplkit-1.3.0}/tests/test_agents_analysis.py +2 -3
  46. {fplkit-1.2.0 → fplkit-1.3.0}/tests/test_agents_data.py +20 -4
  47. {fplkit-1.2.0 → fplkit-1.3.0}/tests/test_agents_data_price.py +3 -4
  48. {fplkit-1.2.0 → fplkit-1.3.0}/tests/test_allocate.py +2 -2
  49. {fplkit-1.2.0 → fplkit-1.3.0}/tests/test_api_core_insights.py +1 -1
  50. {fplkit-1.2.0 → fplkit-1.3.0}/tests/test_api_draft.py +4 -5
  51. {fplkit-1.2.0 → fplkit-1.3.0}/tests/test_api_understat.py +5 -5
  52. fplkit-1.3.0/tests/test_classic_squad_extraction.py +772 -0
  53. {fplkit-1.2.0 → fplkit-1.3.0}/tests/test_cli_allocate.py +62 -9
  54. {fplkit-1.2.0 → fplkit-1.3.0}/tests/test_cli_player.py +0 -1
  55. {fplkit-1.2.0 → fplkit-1.3.0}/tests/test_cli_preview.py +58 -0
  56. {fplkit-1.2.0 → fplkit-1.3.0}/tests/test_cli_stats.py +72 -0
  57. {fplkit-1.2.0 → fplkit-1.3.0}/tests/test_cli_transfer_eval.py +0 -1
  58. {fplkit-1.2.0 → fplkit-1.3.0}/tests/test_cli_warnings_stderr.py +1 -1
  59. {fplkit-1.2.0 → fplkit-1.3.0}/tests/test_common_scoring.py +5 -4
  60. {fplkit-1.2.0 → fplkit-1.3.0}/tests/test_data_prep_harmonisation.py +5 -5
  61. {fplkit-1.2.0 → fplkit-1.3.0}/tests/test_league_recap.py +190 -0
  62. {fplkit-1.2.0 → fplkit-1.3.0}/tests/test_matchup_service.py +1 -3
  63. {fplkit-1.2.0 → fplkit-1.3.0}/tests/test_player_scoring.py +579 -43
  64. {fplkit-1.2.0 → fplkit-1.3.0}/tests/test_providers.py +2 -2
  65. {fplkit-1.2.0 → fplkit-1.3.0}/tests/test_report_agent.py +0 -3
  66. {fplkit-1.2.0 → fplkit-1.3.0}/tests/test_review.py +231 -11
  67. {fplkit-1.2.0 → fplkit-1.3.0}/tests/test_scraper.py +0 -2
  68. {fplkit-1.2.0 → fplkit-1.3.0}/tests/test_season.py +0 -1
  69. {fplkit-1.2.0 → fplkit-1.3.0}/tests/test_squad_allocator.py +76 -2
  70. {fplkit-1.2.0 → fplkit-1.3.0}/tests/test_starting_xi_agent.py +1 -3
  71. {fplkit-1.2.0 → fplkit-1.3.0}/tests/test_stats.py +2 -1
  72. {fplkit-1.2.0 → fplkit-1.3.0}/tests/test_team_ratings.py +7 -7
  73. {fplkit-1.2.0 → fplkit-1.3.0}/tests/test_transfer_eval_agent.py +0 -1
  74. fplkit-1.2.0/.agents/skills/gw-prep/SKILL.md +0 -258
  75. fplkit-1.2.0/.agents/skills/gw-prep/references/output-template.md +0 -161
  76. {fplkit-1.2.0 → fplkit-1.3.0}/.agents/README.md +0 -0
  77. {fplkit-1.2.0 → fplkit-1.3.0}/.agents/skills/gw-prep/scripts/bench_order.py +0 -0
  78. {fplkit-1.2.0 → fplkit-1.3.0}/.agents/skills/gw-prep/scripts/starting_xi.py +0 -0
  79. {fplkit-1.2.0 → fplkit-1.3.0}/.agents/skills/gw-prep/scripts/transfer_eval.py +0 -0
  80. {fplkit-1.2.0 → fplkit-1.3.0}/.agents/skills/squad-builder/references/output-template.md +0 -0
  81. {fplkit-1.2.0 → fplkit-1.3.0}/.agents/skills/update-gw-prep/SKILL.md +0 -0
  82. {fplkit-1.2.0 → fplkit-1.3.0}/.claude/output-styles/fpl-mate.md +0 -0
  83. {fplkit-1.2.0 → fplkit-1.3.0}/.claude/settings.json +0 -0
  84. {fplkit-1.2.0 → fplkit-1.3.0}/.env.example +0 -0
  85. {fplkit-1.2.0 → fplkit-1.3.0}/.github/workflows/ci.yml +0 -0
  86. {fplkit-1.2.0 → fplkit-1.3.0}/.github/workflows/release.yml +0 -0
  87. {fplkit-1.2.0 → fplkit-1.3.0}/LICENSE +0 -0
  88. {fplkit-1.2.0 → fplkit-1.3.0}/cliff.toml +0 -0
  89. {fplkit-1.2.0 → fplkit-1.3.0}/config/fixture_predictions.yaml +0 -0
  90. {fplkit-1.2.0 → fplkit-1.3.0}/config/team_managers.yaml +0 -0
  91. {fplkit-1.2.0 → fplkit-1.3.0}/config/team_ratings.yaml +0 -0
  92. {fplkit-1.2.0 → fplkit-1.3.0}/config/team_ratings_overrides.yaml +0 -0
  93. {fplkit-1.2.0 → fplkit-1.3.0}/docs/fpl-rules.md +0 -0
  94. {fplkit-1.2.0 → fplkit-1.3.0}/docs/images/fpl-player-demo-v1-0.png +0 -0
  95. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/__init__.py +0 -0
  96. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/agents/__init__.py +0 -0
  97. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/agents/action/__init__.py +0 -0
  98. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/agents/action/waiver.py +0 -0
  99. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/agents/analysis/__init__.py +0 -0
  100. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/agents/analysis/bench_order.py +0 -0
  101. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/agents/analysis/captain.py +0 -0
  102. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/agents/analysis/squad_analyzer.py +0 -0
  103. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/agents/analysis/starting_xi.py +0 -0
  104. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/agents/analysis/stats.py +0 -0
  105. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/agents/base.py +0 -0
  106. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/agents/common.py +0 -0
  107. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/agents/data/__init__.py +0 -0
  108. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/agents/data/fixture.py +0 -0
  109. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/agents/data/price.py +0 -0
  110. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/agents/data/scout.py +0 -0
  111. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/agents/orchestration/__init__.py +0 -0
  112. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/api/__init__.py +0 -0
  113. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/api/core_insights.py +0 -0
  114. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/api/dataset_fetcher.py +0 -0
  115. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/api/football_data.py +0 -0
  116. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/api/fpl.py +0 -0
  117. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/api/fpl_draft.py +0 -0
  118. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/api/historical.py +0 -0
  119. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/api/historical_types.py +0 -0
  120. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/api/providers/__init__.py +0 -0
  121. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/api/providers/_models.py +0 -0
  122. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/api/providers/anthropic.py +0 -0
  123. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/api/providers/openai_compat.py +0 -0
  124. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/api/providers/perplexity.py +0 -0
  125. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/api/understat.py +0 -0
  126. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/api/vaastav.py +0 -0
  127. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/cli/__init__.py +0 -0
  128. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/cli/_banner.py +0 -0
  129. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/cli/_context.py +0 -0
  130. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/cli/_fines.py +0 -0
  131. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/cli/_fines_config.py +0 -0
  132. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/cli/_json.py +0 -0
  133. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/cli/_league_recap_data.py +0 -0
  134. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/cli/_league_recap_types.py +0 -0
  135. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/cli/_plan_grid.py +0 -0
  136. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/cli/captain.py +0 -0
  137. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/cli/chips.py +0 -0
  138. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/cli/credentials.py +0 -0
  139. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/cli/differentials.py +0 -0
  140. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/cli/fdr.py +0 -0
  141. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/cli/fixtures.py +0 -0
  142. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/cli/history.py +0 -0
  143. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/cli/init.py +0 -0
  144. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/cli/league.py +0 -0
  145. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/cli/player.py +0 -0
  146. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/cli/price_changes.py +0 -0
  147. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/cli/price_history.py +0 -0
  148. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/cli/ratings.py +0 -0
  149. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/cli/sell_prices.py +0 -0
  150. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/cli/squad.py +0 -0
  151. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/cli/status.py +0 -0
  152. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/cli/targets.py +0 -0
  153. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/cli/transfer_eval.py +0 -0
  154. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/cli/waivers.py +0 -0
  155. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/cli/xg.py +0 -0
  156. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/constants.py +0 -0
  157. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/models/__init__.py +0 -0
  158. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/models/chip_plan.py +0 -0
  159. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/models/fixture.py +0 -0
  160. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/models/player.py +0 -0
  161. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/models/team.py +0 -0
  162. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/models/types.py +0 -0
  163. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/parsers/__init__.py +0 -0
  164. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/parsers/recommendations.py +0 -0
  165. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/paths.py +0 -0
  166. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/prompts/__init__.py +0 -0
  167. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/prompts/scout.py +0 -0
  168. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/scraper/__init__.py +0 -0
  169. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/scraper/fpl_prices.py +0 -0
  170. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/season.py +0 -0
  171. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/services/__init__.py +0 -0
  172. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/services/fixture_predictions.py +0 -0
  173. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/services/matchup.py +0 -0
  174. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/services/team_form.py +0 -0
  175. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/services/team_ratings.py +0 -0
  176. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/services/team_ratings_prior.py +0 -0
  177. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/utils/__init__.py +0 -0
  178. {fplkit-1.2.0 → fplkit-1.3.0}/fpl_cli/utils/text.py +0 -0
  179. {fplkit-1.2.0 → fplkit-1.3.0}/requirements.lock +0 -0
  180. {fplkit-1.2.0 → fplkit-1.3.0}/tests/__init__.py +0 -0
  181. {fplkit-1.2.0 → fplkit-1.3.0}/tests/test_agents_analysis_squad.py +0 -0
  182. {fplkit-1.2.0 → fplkit-1.3.0}/tests/test_agents_base.py +0 -0
  183. {fplkit-1.2.0 → fplkit-1.3.0}/tests/test_agents_orchestration.py +0 -0
  184. {fplkit-1.2.0 → fplkit-1.3.0}/tests/test_api_client.py +0 -0
  185. {fplkit-1.2.0 → fplkit-1.3.0}/tests/test_api_dataset_fetcher.py +0 -0
  186. {fplkit-1.2.0 → fplkit-1.3.0}/tests/test_api_football_data.py +0 -0
  187. {fplkit-1.2.0 → fplkit-1.3.0}/tests/test_api_historical.py +0 -0
  188. {fplkit-1.2.0 → fplkit-1.3.0}/tests/test_api_vaastav.py +0 -0
  189. {fplkit-1.2.0 → fplkit-1.3.0}/tests/test_bench_order_script.py +0 -0
  190. {fplkit-1.2.0 → fplkit-1.3.0}/tests/test_cli_banner.py +0 -0
  191. {fplkit-1.2.0 → fplkit-1.3.0}/tests/test_cli_captain.py +0 -0
  192. {fplkit-1.2.0 → fplkit-1.3.0}/tests/test_cli_chips.py +0 -0
  193. {fplkit-1.2.0 → fplkit-1.3.0}/tests/test_cli_credentials.py +0 -0
  194. {fplkit-1.2.0 → fplkit-1.3.0}/tests/test_cli_custom_analysis.py +0 -0
  195. {fplkit-1.2.0 → fplkit-1.3.0}/tests/test_cli_differentials.py +0 -0
  196. {fplkit-1.2.0 → fplkit-1.3.0}/tests/test_cli_fdr.py +0 -0
  197. {fplkit-1.2.0 → fplkit-1.3.0}/tests/test_cli_fdr_blanks.py +0 -0
  198. {fplkit-1.2.0 → fplkit-1.3.0}/tests/test_cli_fixtures.py +0 -0
  199. {fplkit-1.2.0 → fplkit-1.3.0}/tests/test_cli_format.py +0 -0
  200. {fplkit-1.2.0 → fplkit-1.3.0}/tests/test_cli_format_suppression.py +0 -0
  201. {fplkit-1.2.0 → fplkit-1.3.0}/tests/test_cli_history.py +0 -0
  202. {fplkit-1.2.0 → fplkit-1.3.0}/tests/test_cli_init.py +0 -0
  203. {fplkit-1.2.0 → fplkit-1.3.0}/tests/test_cli_json.py +0 -0
  204. {fplkit-1.2.0 → fplkit-1.3.0}/tests/test_cli_plan_grid.py +0 -0
  205. {fplkit-1.2.0 → fplkit-1.3.0}/tests/test_cli_price_history.py +0 -0
  206. {fplkit-1.2.0 → fplkit-1.3.0}/tests/test_cli_review.py +0 -0
  207. {fplkit-1.2.0 → fplkit-1.3.0}/tests/test_cli_sell_prices.py +0 -0
  208. {fplkit-1.2.0 → fplkit-1.3.0}/tests/test_cli_squad.py +0 -0
  209. {fplkit-1.2.0 → fplkit-1.3.0}/tests/test_cli_status.py +0 -0
  210. {fplkit-1.2.0 → fplkit-1.3.0}/tests/test_cli_targets.py +0 -0
  211. {fplkit-1.2.0 → fplkit-1.3.0}/tests/test_cli_waivers.py +0 -0
  212. {fplkit-1.2.0 → fplkit-1.3.0}/tests/test_cli_xg.py +0 -0
  213. {fplkit-1.2.0 → fplkit-1.3.0}/tests/test_common.py +0 -0
  214. {fplkit-1.2.0 → fplkit-1.3.0}/tests/test_fines.py +0 -0
  215. {fplkit-1.2.0 → fplkit-1.3.0}/tests/test_fines_config.py +0 -0
  216. {fplkit-1.2.0 → fplkit-1.3.0}/tests/test_fixture_predictions.py +0 -0
  217. {fplkit-1.2.0 → fplkit-1.3.0}/tests/test_fpl_prices.py +0 -0
  218. {fplkit-1.2.0 → fplkit-1.3.0}/tests/test_init.py +0 -0
  219. {fplkit-1.2.0 → fplkit-1.3.0}/tests/test_init_fines.py +0 -0
  220. {fplkit-1.2.0 → fplkit-1.3.0}/tests/test_models.py +0 -0
  221. {fplkit-1.2.0 → fplkit-1.3.0}/tests/test_paths.py +0 -0
  222. {fplkit-1.2.0 → fplkit-1.3.0}/tests/test_player_prior.py +0 -0
  223. {fplkit-1.2.0 → fplkit-1.3.0}/tests/test_player_resolution.py +0 -0
  224. {fplkit-1.2.0 → fplkit-1.3.0}/tests/test_recommendations_parser.py +0 -0
  225. {fplkit-1.2.0 → fplkit-1.3.0}/tests/test_rolling_pts_per_m.py +0 -0
  226. {fplkit-1.2.0 → fplkit-1.3.0}/tests/test_sell_prices.py +0 -0
  227. {fplkit-1.2.0 → fplkit-1.3.0}/tests/test_team_ratings_prior.py +0 -0
  228. {fplkit-1.2.0 → fplkit-1.3.0}/tests/test_utils_text.py +0 -0
@@ -69,7 +69,7 @@ Agent playbooks in `.agents/skills/`. Each has a `SKILL.md` entry point. Claude
69
69
 
70
70
  | Skill | Path | Purpose | Compatibility |
71
71
  |-------|------|---------|--------------|
72
- | gw-prep | `skills/gw-prep/` | Gameweek preparation recommendations for classic and draft | Full: Claude Code. Partial: Codex, Cursor, Copilot |
72
+ | gw-prep | `skills/gw-prep/` | Gameweek preparation recommendations for classic and draft (embed / rederive / transfer branches; Phase E post-write validation) | Full: Claude Code. Partial: Codex, Cursor, Copilot |
73
73
  | update-gw-prep | `skills/update-gw-prep/` | Append GW update to existing recommendations | Full: Claude Code, Codex, Cursor, Copilot |
74
74
  | squad-builder | `skills/squad-builder/` | Build optimal 15-player squad (wildcard, free hit, season start) | Full: Claude Code, Codex, Cursor, Copilot |
75
75
 
@@ -94,3 +94,4 @@ Python classes in `fpl_cli/agents/` that implement `async run(context) -> AgentR
94
94
  **Notes:**
95
95
  - BenchOrderAgent and StartingXIAgent have no CLI command - they are invoked by gw-prep skill wrapper scripts in `.agents/skills/gw-prep/scripts/`
96
96
  - TransferEvalAgent is used by both `transfer-eval` CLI command and gw-prep skill
97
+ - `extract_classic_squad.py` (`.agents/skills/gw-prep/scripts/`) — deterministic Classic Squad block extractor used by gw-prep Phase B9 + Phase E. JSON stdout; read-only; emits TypedDict-annotated payloads.
@@ -0,0 +1,553 @@
1
+ ---
2
+ name: gw-prep
3
+ description: >
4
+ Generate gameweek preparation recommendations for FPL classic and draft leagues.
5
+ Runs data gathering via fpl-cli, then dispatches parallel sub-agent analysis for
6
+ each league format. Use when the user asks to prepare for the next gameweek,
7
+ get transfer/waiver recommendations, or review their squad.
8
+ model: opus
9
+ compatibility:
10
+ claude-code: full (parallel Classic + Draft sub-agents via Agent tool)
11
+ codex: partial (sequential execution - no sub-agent spawning)
12
+ cursor: partial (sequential execution)
13
+ copilot: fallback (sequential execution)
14
+ ---
15
+
16
+ <!-- CLI commands composed: status, chips, chips sync, chips timing, fdr, captain, waivers, squad grid, squad sell-prices, price-history, player, stats -->
17
+
18
+ # Gameweek Preparation
19
+
20
+ Generate transfer/waiver recommendations and squad analysis for the upcoming FPL gameweek across classic and draft formats.
21
+
22
+ ## Environment
23
+
24
+ ```bash
25
+ cd "$FPL_CLI_DIR" && source .venv/bin/activate
26
+ ```
27
+
28
+ ## Execution Strategy
29
+
30
+ **Claude Code:** Launch Phase C sub-agents in parallel (classic + draft simultaneously) using the `Agent tool parameters:` blocks shown in Phase C.
31
+
32
+ **Codex / Cursor / Copilot / other agents:** Do not attempt to spawn sub-agents. Run Phase C-classic then Phase C-draft sequentially in the same context. Use the same prompts and output templates - just one after the other.
33
+
34
+ **In all cases** the final output format is identical: Classic section followed by Draft section (or whichever formats are active).
35
+
36
+ ---
37
+
38
+ ## Phase A: Context Detection
39
+
40
+ ### A1 -- Gameweek and Deadline
41
+
42
+ ```bash
43
+ fpl status --format json
44
+ ```
45
+
46
+ Extract:
47
+ - `gameweek` -- the upcoming GW number (N)
48
+ - `deadline` -- the transfer deadline timestamp
49
+ - `phase` -- current status (e.g. "Fixture day 1 of 2", "Between gameweeks")
50
+ - `metadata.format` -- `"classic"`, `"draft"`, or `"both"`. This determines which sub-agents to dispatch and which Phase B commands to run. If format is not present (no entry IDs configured), ask the user.
51
+
52
+ ### A1.5 -- Chip Status
53
+
54
+ ```bash
55
+ fpl chips sync
56
+ fpl chips --format json
57
+ ```
58
+
59
+ `chips sync` ensures the local chip plan reflects any changes made via the FPL website. Run it silently before reading chip status.
60
+
61
+ Extract active chip status for GW N:
62
+ - **Wildcard** active → `mode = "squad-builder"`, `active_chip = "wildcard"`
63
+ - **Free Hit** active → `mode = "squad-builder"`, `active_chip = "freehit"`
64
+ - **Bench Boost** active → `mode = "benchboost"`, `active_chip = "benchboost"`
65
+ - Otherwise → `mode = "transfer"`, `active_chip` is unset
66
+
67
+ This mode switch affects which rules apply in Phase C sub-agents. `active_chip` is used in Phase A3 to locate a matching squad-builder output file (squad-builder mode only).
68
+
69
+ ### A2 -- Budget Data (classic only - skip if format is "draft")
70
+
71
+ ```bash
72
+ fpl squad sell-prices --refresh
73
+ ```
74
+
75
+ Scrapes current sell prices from the FPL website. Requires `FPL_EMAIL` and `FPL_PASSWORD` in `.env`. If credentials are not configured, skip this step - affordability analysis in Phase C will be limited to the data available from other commands.
76
+
77
+ <!-- ADAPT: If you don't use FPL website credentials, remove this step -->
78
+
79
+ ### A3 -- Squad-builder File Discovery (classic/both + squad-builder mode only - skip otherwise)
80
+
81
+ _Skip unless `mode == "squad-builder"` AND `active_chip ∈ {wildcard, freehit}` AND `metadata.format ∈ {"classic", "both"}`._
82
+
83
+ Locate a matching squad-builder output file and extract the Classic Squad block for embedding. Sets `squad_builder_result = "embed"` on success, or `"rederive"` with a `squad_builder_reason` code on failure. Resolving mode in Phase A (before Phase B) lets sub-agents know up-front which Phase B outputs will actually feed them, and prints the rederive warning banner before any data-gather commands run. All steps are synchronous and must complete before Phase B begins.
84
+
85
+ 1. Look for `[YOUR_OUTPUT_DIR]/gw{N}-squad-builder.md`.
86
+ - Not found → `squad_builder_result = "rederive"`, `squad_builder_reason = "file-missing"`. Done.
87
+ 2. Parse the file's YAML frontmatter. Required fields: `mode`, `gameweek`.
88
+ - Missing or malformed → `squad_builder_result = "rederive"`, `squad_builder_reason = "frontmatter-malformed"`. Done.
89
+ 3. Gameweek check: `file.gameweek == N`?
90
+ - Mismatch → `squad_builder_result = "rederive"`, `squad_builder_reason = "gameweek-mismatch"`. Done.
91
+ 4. Mode check: use the following explicit mode map to normalise `file.mode` and compare to `active_chip`.
92
+
93
+ | `file.mode` value | Normalised | Match against `active_chip`? |
94
+ |---|---|---|
95
+ | `Wildcard` | `wildcard` | ✓ matches `wildcard` |
96
+ | `Free Hit` | `freehit` | ✓ matches `freehit` |
97
+ | `Season Start Classic` | `seasonstartclassic` | ✗ mode-mismatch by design |
98
+ | `Season Start Draft` | `seasonstartdraft` | ✗ mode-mismatch by design |
99
+ | `Re-draft` | `redraft` | ✗ mode-mismatch by design |
100
+
101
+ Any `file.mode` value not in this table is treated as mode-mismatch.
102
+ - Mismatch → `squad_builder_result = "rederive"`, `squad_builder_reason = "mode-mismatch"`. Done.
103
+ 5. Call the extraction helper:
104
+
105
+ ```bash
106
+ python3 "${CLAUDE_SKILL_DIR}/scripts/extract_classic_squad.py" --file "[YOUR_OUTPUT_DIR]/gw{N}-squad-builder.md"
107
+ ```
108
+
109
+ Parse stdout as JSON regardless of exit code. If JSON parse fails, treat as extraction-failed with a generic error message.
110
+ - Non-zero exit → `squad_builder_result = "rederive"`, `squad_builder_reason = "extraction-failed"`. If stdout parsed with `error: true`, include `messages[0]` verbatim in the in-chat warning banner (see C1 Rederive Variant C). Done.
111
+ - Zero exit → store `data.block` in phase context as `embedded_classic_squad_block`. Set `squad_builder_result = "embed"`.
112
+
113
+ 6. Freshness check (embed path only): if `file.mtime` is older than 72 hours, emit an in-chat info note:
114
+ > ℹ️ `gw{N}-squad-builder.md` was last modified more than 72h ago. The embedded squad may not reflect late-breaking team news. Proceeding — review Phase B data and apply inline swaps if warranted.
115
+
116
+ Non-gating. mtime is a weak proxy (confounded by editor saves, iCloud sync, git checkouts).
117
+
118
+ 7. If `squad_builder_result == "rederive"`, print the appropriate in-chat warning banner (variants A/B/C — see C1 Rederive section below for exact wording) before proceeding to Phase B. Proceed immediately; non-interactive.
119
+
120
+ ---
121
+
122
+ ## Phase B: Data Gathering
123
+
124
+ Run all applicable commands below. Every command uses `--format json`. Skip commands marked with a format condition that doesn't match `metadata.format` from A1.
125
+
126
+ **Reference preload (every run):** before issuing the CLI commands, read the full contents of `${CLAUDE_SKILL_DIR}/references/rules.md` and `${CLAUDE_SKILL_DIR}/references/output-template.md` into memory. These are inlined verbatim into each Phase C prompt as `{rules_content}` and `{output_template_content}` — sub-agents cannot reach the skill directory on their own, so the orchestrator must pass the text through. If either file is missing, abort with an error naming the expected path.
127
+
128
+ ### B1 -- Fixture Difficulty
129
+
130
+ ```bash
131
+ fpl fdr --blanks --format json
132
+ ```
133
+
134
+ Returns fixture difficulty runs (pFDR, positional ATK/DEF ratings, upcoming fixtures) plus BGW/DGW predictions with confidence levels. `--blanks` surfaces the confirmed + predicted blank/double-GW schedule inline.
135
+
136
+ ### B2 -- Captain Candidates (classic only - skip if format is "draft")
137
+
138
+ ```bash
139
+ fpl captain --format json
140
+ ```
141
+
142
+ ### B3 -- Waiver Targets (draft only - skip if format is "classic")
143
+
144
+ ```bash
145
+ fpl waivers --format json
146
+ ```
147
+
148
+ ### B4 -- Current Squad
149
+
150
+ ```bash
151
+ fpl squad grid --format json
152
+ ```
153
+
154
+ If format is `"both"`, also run:
155
+
156
+ ```bash
157
+ fpl squad grid --draft --format json
158
+ ```
159
+
160
+ ### B5 -- Price Movements
161
+
162
+ ```bash
163
+ fpl price-history --format json
164
+ ```
165
+
166
+ ### B6 -- Chip Timing Analysis (classic only - skip if format is "draft")
167
+
168
+ ```bash
169
+ fpl chips timing --format json
170
+ ```
171
+
172
+ ### B7 -- Player Detail (on-demand)
173
+
174
+ For deeper analysis of specific players flagged by other commands:
175
+
176
+ ```bash
177
+ fpl player "{player_name}" --format json
178
+ ```
179
+
180
+ ### B8 -- Statistical Leaders
181
+
182
+ Run the five differentiated queries below in parallel. Each targets a distinct transfer-signal surface that downstream Phase C analysis depends on; a single generic `fpl stats` call sorts by total points and misses most of them.
183
+
184
+ ```bash
185
+ # In-form players across positions (trending, minutes-filtered, available)
186
+ fpl stats -s form --min-minutes 315 -n 15 --available-only --format json
187
+
188
+ # Transfer momentum (who the market is chasing this week)
189
+ fpl stats -s transfers_in_event -n 15 --format json
190
+
191
+ # MID xGI leaders (attacking mids, hauls signal)
192
+ fpl stats -p MID -s expected_goal_involvements --min-minutes 450 -n 15 --available-only --format json
193
+
194
+ # FWD xGI leaders (strikers, hauls signal)
195
+ fpl stats -p FWD -s expected_goal_involvements --min-minutes 450 -n 10 --available-only --format json
196
+
197
+ # DEF clean-sheet leaders (defensive picks)
198
+ fpl stats -p DEF -s clean_sheets --min-minutes 450 -n 10 --available-only --format json
199
+ ```
200
+
201
+ Store each result under a distinct key (e.g. `stats_form`, `stats_transfer_momentum`, `stats_mid_xgi`, `stats_fwd_xgi`, `stats_def_clean_sheets`) and inline them into Phase C prompts as labelled sections.
202
+
203
+ <!-- ADAPT: Add your own supplementary data sources here. Examples:
204
+ - `fpl preview --save --scout` generates a GW preview with fixture analysis and scout insights.
205
+ Read the saved file and inject its content into Phase C sub-agents as additional context.
206
+ - Newsletter extracts (e.g. community tips, model projections) saved as markdown files
207
+ -->
208
+
209
+ ---
210
+
211
+ ## Phase C: Analysis Sub-agents
212
+
213
+ Dispatch sub-agents based on `metadata.format` from A1:
214
+ - `"classic"` -- dispatch C1 only
215
+ - `"draft"` -- dispatch C2 only
216
+ - `"both"` -- dispatch C1 and C2 in parallel (or sequentially if parallel is unsupported)
217
+
218
+ Each sub-agent receives the JSON output from Phase B commands as context.
219
+
220
+ ### C1 -- Classic League Analysis (skip if format is "draft")
221
+
222
+ - **model**: opus
223
+ - **subagent_type**: general-purpose
224
+
225
+ Branch on `squad_builder_result` (set in Phase A3; unset on transfer weeks):
226
+
227
+ ---
228
+
229
+ **[Embed] `squad_builder_result == "embed"`** — wildcard/freehit with matched squad-builder file
230
+
231
+ **Prompt structure:**
232
+
233
+ > You are an FPL analyst preparing gameweek {N} recommendations for a classic league.
234
+ >
235
+ > **Mode: squad-builder — EMBED** (wildcard/free hit — Classic Squad block embedded below)
236
+ >
237
+ > **Analysis rules (apply in full):**
238
+ >
239
+ > {rules_content}
240
+ >
241
+ > **Output format (follow exactly):**
242
+ >
243
+ > {output_template_content}
244
+ >
245
+ > **Data (JSON):**
246
+ > - Status: {A1 output}
247
+ > - Chips: {A1.5 output}
248
+ > - pFDR: {B1 output}
249
+ > - Captain candidates: {B2 output}
250
+ > - Squad: {B4 output}
251
+ > - Price movements: {B5 output}
252
+ > - Chip timing: {B6 output}
253
+ > - Stats: {stats_form}, {stats_transfer_momentum}, {stats_mid_xgi}, {stats_fwd_xgi}, {stats_def_clean_sheets} (from B8)
254
+ >
255
+ > <!-- ADAPT: Add your own supplementary data sources here (newsletters, external reports) -->
256
+ >
257
+ > **Embedded Classic Squad (from squad-builder — insert this as the `### Classic Squad` section):**
258
+ >
259
+ > {embedded_classic_squad_block}
260
+ >
261
+ > **Instructions:**
262
+ >
263
+ > Default: insert the embedded `### Classic Squad` block above **verbatim** as the Classic Squad section of the recommendations file. This is the expected path for nearly every embed-mode run.
264
+ >
265
+ > Late-changes path (use only when clearly warranted): if Phase B data reveals a material change squad-builder could not have seen (a starter ruled out after the file was written, an injury confirmed, a price move materially shifting affordability), you may apply a swap inline. To apply a swap: replace the relevant Starting XI or Bench row, update Captain/Vice if affected, adjust the Budget table (subtract OUT price, add IN price, update Total and Remaining) and Team Exposure table accordingly. Immediately after the block's `#### Alternatives` section, append a blockquote note: `> Late change: {OUT name} → {IN name} — {one-line reason, named source e.g. "ruled out per Thursday presser"}`. One note per swap. **Default is verbatim pass-through. Only deviate for a specific named reason. Never rewrite tables to "improve" formatting or fix perceived typos in squad-builder's output.**
266
+ >
267
+ > For Phase C2.5 (`transfer_eval.py`): on embed mode, the OUT-candidate pool is the 15 players in the Player column of the embedded Starting XI and Bench tables (not the current `fpl squad grid`). Use this to evaluate whether any late-breaking swaps are justified.
268
+ >
269
+ > **Sections to produce:** Chip Timing (from B6), Momentum Alerts (from B5/B8), pFDR Overview (from B1), and the `### Classic Squad` block (embedded verbatim or swap-edited with trailing note). **Suppress:** Captain Pick top-3 table (captain is inside the embedded block), standalone Bench Order section (bench order is inside the embedded block), and Transfer Recommendations section (any swaps are applied inline in the block).
270
+ >
271
+ > **User workflow note:** The `### Classic Squad` block is the final 15-player squad the user should enter into FPL. If a trailing `> Late change:` note is present, it explains the swap — the user can revert it in the FPL site before saving if they disagree.
272
+ >
273
+ > Write the recommendations file with frontmatter:
274
+ > ```yaml
275
+ > squad_builder_mode: true
276
+ > mode: {wildcard|freehit}
277
+ > # phase_e_ok is written by Phase E after post-write validation — do not include it here
278
+ > ```
279
+
280
+ ---
281
+
282
+ **[Rederive] `squad_builder_result == "rederive"` AND `mode == "squad-builder"`** — wildcard/freehit but file not usable
283
+
284
+ Before dispatching, print the in-chat warning (variant by `squad_builder_reason`):
285
+
286
+ **Variant A** (`squad_builder_reason == "file-missing"`):
287
+ > ⚠️ **Wildcard/Free Hit detected for GW{N}, but no squad-builder file was found.** Expected `gw{N}-squad-builder.md` in `[YOUR_OUTPUT_DIR]`. Re-derivation will run (weaker squad selection). To use squad-builder output, run `/squad-builder --{wildcard|freehit}` first, then re-run `/gw-prep`.
288
+
289
+ **Variant B** (`squad_builder_reason ∈ {"gameweek-mismatch", "mode-mismatch"}`):
290
+ > ⚠️ **Squad-builder file found but does not match this run.** Found `gw{N}-squad-builder.md` with `mode: {file.mode}` / `gameweek: {file.gameweek}`. Expected mode `{active_chip}` / gameweek `{N}`. Re-derivation will run. To use squad-builder output, run `/squad-builder --{wildcard|freehit}` for GW{N}, then re-run `/gw-prep`.
291
+
292
+ **Variant C** (`squad_builder_reason ∈ {"frontmatter-malformed", "extraction-failed"}`):
293
+ > ⚠️ **{Wildcard|Free Hit} detected for GW{N}. Found `gw{N}-squad-builder.md` but its frontmatter is malformed or its Classic Squad block could not be extracted.** Typical cause: file was produced with `--draft` only. Run `/squad-builder --{wildcard|freehit}` to regenerate, then re-run `/gw-prep`.
294
+
295
+ Proceed immediately (non-interactive).
296
+
297
+ **Prompt structure:**
298
+
299
+ > You are an FPL analyst preparing gameweek {N} recommendations for a classic league.
300
+ >
301
+ > **Mode: squad-builder — REDERIVE** (wildcard/free hit, squad-builder output unavailable)
302
+ >
303
+ > **Analysis rules (apply in full):**
304
+ >
305
+ > {rules_content}
306
+ >
307
+ > **Output format (follow exactly):**
308
+ >
309
+ > {output_template_content}
310
+ >
311
+ > **Data (JSON):**
312
+ > - Status: {A1 output}
313
+ > - Chips: {A1.5 output}
314
+ > - pFDR: {B1 output}
315
+ > - Captain candidates: {B2 output}
316
+ > - Squad: {B4 output}
317
+ > - Price movements: {B5 output}
318
+ > - Chip timing: {B6 output}
319
+ > - Stats: {stats_form}, {stats_transfer_momentum}, {stats_mid_xgi}, {stats_fwd_xgi}, {stats_def_clean_sheets} (from B8)
320
+ >
321
+ > <!-- ADAPT: Add your own supplementary data sources here (newsletters, external reports) -->
322
+ >
323
+ > Apply squad-builder rules from `references/rules.md` for full squad selection. Prepend the fallback banner (Variant A/B/C — see `references/output-template.md` for exact wording) on the line immediately before the Classic section heading in the output file. Do not include `squad_builder_mode` in the frontmatter — the squad was re-derived, not embedded from squad-builder.
324
+ >
325
+ > Produce the **Classic** section of the output template.
326
+
327
+ ---
328
+
329
+ **[Bench Boost] `mode == "benchboost"`** — bench boost active, all 15 players score
330
+
331
+ **Prompt structure:**
332
+
333
+ > You are an FPL analyst preparing gameweek {N} recommendations for a classic league.
334
+ >
335
+ > **Mode: benchboost** (Bench Boost active — all 15 squad players score this GW)
336
+ >
337
+ > **Analysis rules (apply in full):**
338
+ >
339
+ > {rules_content}
340
+ >
341
+ > **Output format (follow exactly):**
342
+ >
343
+ > {output_template_content}
344
+ >
345
+ > **Data (JSON):**
346
+ > - Status: {A1 output}
347
+ > - Chips: {A1.5 output}
348
+ > - pFDR: {B1 output}
349
+ > - Captain candidates: {B2 output}
350
+ > - Squad: {B4 output}
351
+ > - Price movements: {B5 output}
352
+ > - Chip timing: {B6 output}
353
+ > - Stats: {stats_form}, {stats_transfer_momentum}, {stats_mid_xgi}, {stats_fwd_xgi}, {stats_def_clean_sheets} (from B8)
354
+ >
355
+ > **Bench Boost instructions:**
356
+ >
357
+ > All 15 players score this GW. The starting XI/bench boundary does not exist. Present all 15 as the scoring squad in a single table (no separate Starting XI and Bench tables). Suppress the Bench Order section entirely.
358
+ >
359
+ > When evaluating transfers, assess all 15 slots equally. A bench player who won't play (injured, £4.0m non-player) is a wasted scoring slot - flag them as transfer-out candidates even if they wouldn't normally be considered. The hit threshold still applies (expected gain > 8pts over horizon), but the gain calculation should include the bench player's expected return for this GW.
360
+ >
361
+ > Captain pick proceeds as normal. Chip Timing section should note that Bench Boost is being used this GW.
362
+ >
363
+ > Produce the **Classic** section of the output template.
364
+
365
+ ---
366
+
367
+ **[Transfer] `mode == "transfer"`** — normal incremental week (no wildcard/freehit/benchboost active)
368
+
369
+ **Prompt structure:**
370
+
371
+ > You are an FPL analyst preparing gameweek {N} recommendations for a classic league.
372
+ >
373
+ > **Mode: transfer**
374
+ >
375
+ > **Analysis rules (apply in full):**
376
+ >
377
+ > {rules_content}
378
+ >
379
+ > **Output format (follow exactly):**
380
+ >
381
+ > {output_template_content}
382
+ >
383
+ > **Data (JSON):**
384
+ > - Status: {A1 output}
385
+ > - Chips: {A1.5 output}
386
+ > - pFDR: {B1 output}
387
+ > - Captain candidates: {B2 output}
388
+ > - Squad: {B4 output}
389
+ > - Price movements: {B5 output}
390
+ > - Chip timing: {B6 output}
391
+ > - Stats: {stats_form}, {stats_transfer_momentum}, {stats_mid_xgi}, {stats_fwd_xgi}, {stats_def_clean_sheets} (from B8)
392
+ >
393
+ > <!-- ADAPT: Add your own supplementary data sources here (newsletters, external reports) -->
394
+ >
395
+ > Produce the **Classic** section of the output template.
396
+
397
+ ### C2 -- Draft League Analysis (skip if format is "classic")
398
+
399
+ - **model**: sonnet
400
+ - **subagent_type**: general-purpose
401
+
402
+ **Prompt structure:**
403
+
404
+ > You are an FPL analyst preparing gameweek {N} recommendations for a draft league.
405
+ >
406
+ > **Analysis rules (apply in full):**
407
+ >
408
+ > {rules_content}
409
+ >
410
+ > **Output format (follow exactly):**
411
+ >
412
+ > {output_template_content}
413
+ >
414
+ > **Data (JSON):**
415
+ > - Status: {A1 output}
416
+ > - pFDR: {B1 output}
417
+ > - Waivers: {B3 output}
418
+ > - Squad: {B4 output}
419
+ > - Stats: {stats_form}, {stats_transfer_momentum}, {stats_mid_xgi}, {stats_fwd_xgi}, {stats_def_clean_sheets} (from B8)
420
+ >
421
+ > <!-- ADAPT: Add your own supplementary data sources here (newsletters, external reports) -->
422
+ >
423
+ > Produce the **Draft** section of the output template.
424
+
425
+ ### C2.5 -- Transfer/Waiver Evaluation
426
+
427
+ After each sub-agent identifies OUT candidates and an IN shortlist (from squad analysis, `fpl targets`, `fpl waivers`), run the transfer evaluation script for each OUT/shortlist pair:
428
+
429
+ ```bash
430
+ python3 "${CLAUDE_SKILL_DIR}/scripts/transfer_eval.py" --out "{out_player_name}" --in "{comma-separated IN candidate names}"
431
+ ```
432
+
433
+ The script outputs JSON with Outlook (multi-GW quality) and This GW (lineup impact) deltas for each IN candidate vs the OUT player. Use these scores as the quantitative baseline for transfer/waiver recommendations. Sub-agents may override with qualitative reasons (press conference intel, newsletter signals) using the same `⚡ Override: {reason}` pattern as starting XI overrides.
434
+
435
+ If the script fails (exit 1), fall back to LLM-driven transfer reasoning and note the failure.
436
+
437
+ ### C3 -- Starting XI Selection (skip if `squad_builder_result == "embed"` OR `mode == "benchboost"` — embed has XI chosen, bench boost has no XI/bench split)
438
+
439
+ Run the lineup engine for each active format's squad **before** bench ordering:
440
+
441
+ ```bash
442
+ python3 "${CLAUDE_SKILL_DIR}/scripts/starting_xi.py" --squad "{comma-separated 15 squad player names from squad grid}"
443
+ ```
444
+
445
+ <!-- ADAPT: Replace with your squad player names from the squad grid output -->
446
+
447
+ Use the script's recommended XI as the default lineup. Sub-agents may override specific picks with stated qualitative reasons (press conference intel, newsletter signals, rotation predictions). Mark any overrides with `⚡ Override: {reason}` in the output. If the script fails (exit 1), fall back to manual selection and note the failure.
448
+
449
+ ### C4 -- Bench Ordering (skip if `squad_builder_result == "embed"` OR `mode == "benchboost"` — embed has bench order inside block, bench boost has no bench ordering)
450
+
451
+ Using the starting XI from C3 (or the sub-agent's overridden version), run the bench order script:
452
+
453
+ ```bash
454
+ python3 "${CLAUDE_SKILL_DIR}/scripts/bench_order.py" --starting "{comma-separated starter names}" --bench "{comma-separated bench names}"
455
+ ```
456
+
457
+ Incorporate the bench ordering output into the relevant sections of each sub-agent's recommendations.
458
+
459
+ ---
460
+
461
+ ## Phase D: Output
462
+
463
+ Combine the outputs from whichever sub-agents were dispatched into a single recommendations file. If only one format is active, the file contains only that format's section.
464
+
465
+ <!-- ADAPT: Set your output directory -->
466
+ **Output path:** `[YOUR_OUTPUT_DIR]/gw{N}-recommendations.md`
467
+
468
+ The file should follow the structure defined in `references/output-template.md`, with both Classic and Draft sections populated.
469
+
470
+ Present a brief summary to the user:
471
+ - GW number and deadline
472
+ - Mode (transfer or squad-builder)
473
+ - Key highlights (top captain pick, priority transfer/waiver, chip timing note)
474
+ - Output file path
475
+
476
+ ---
477
+
478
+ ## Phase E: Post-write Validation (embed-mode only)
479
+
480
+ _Skip unless `squad_builder_result == "embed"`. Transfer and rederive runs do not produce a `### Classic Squad` block._
481
+
482
+ 1. Run:
483
+
484
+ ```bash
485
+ python3 "${CLAUDE_SKILL_DIR}/scripts/extract_classic_squad.py" --from-recommendations --file "[YOUR_OUTPUT_DIR]/gw{N}-recommendations.md"
486
+ ```
487
+
488
+ Parse stdout as JSON regardless of exit code. If exit is non-zero → emit warning and proceed:
489
+
490
+ - If stdout parses as JSON with `error: true` → include `messages[0]` verbatim in the warning:
491
+ > ⚠️ Phase E: could not recover `### Classic Squad` block from `gw{N}-recommendations.md` — {messages[0]}. Review manually before entering into FPL.
492
+ - Otherwise (JSON parse failed or no `error` key) → emit the generic warning:
493
+ > ⚠️ Phase E: could not recover `### Classic Squad` block from `gw{N}-recommendations.md` — the sub-agent's output may be malformed. Review manually before entering into FPL.
494
+
495
+ **Do not mutate the file.** Proceed.
496
+
497
+ 2. Parse the JSON output. Read `validation.structural` and `validation.arithmetic`.
498
+
499
+ 3. **Structural checks:** collect an issue string for each failure:
500
+ - `sub_headings_present[name] == false` → `"missing sub-heading: #### {name}"`
501
+ - `starting_xi_rows != 11` → `"Starting XI has {N} rows, expected 11"`
502
+ - `bench_rows != 4` → `"Bench has {N} rows, expected 4"`
503
+ - `captain_named == false` → `"Captain not named"`
504
+ - `vice_named == false` → `"Vice not named"`
505
+
506
+ 4. **Arithmetic checks:** collect an issue string for each failure:
507
+ - `budget_within_cap == false` OR `budget_total_gbp_m == null` → `"Budget parse failed or over 100.0m cap (parsed: {value})"`
508
+ - `max_per_team_ok == false` → `"Team exposure violation: {list of teams with count > 3}"`. Build this list from `arithmetic.team_exposure` — iterate its entries and include each team whose integer value is greater than 3.
509
+ - `player_count != 15` → `"Squad size is {N}, expected 15"`
510
+
511
+ 5. **Report:** if the issues list is empty, silent continue (no in-chat output). If non-empty, emit:
512
+ > ⚠️ Phase E validation: the Classic Squad in `gw{N}-recommendations.md` has {N} issue(s):
513
+ > - {issue 1}
514
+ > - {issue 2}
515
+ > ...
516
+ >
517
+ > Phase E did not modify the file — the issues above were detected in the file the sub-agent just wrote. Review manually and either re-run `/gw-prep` or edit the file by hand before entering your squad into FPL.
518
+
519
+ 6. **Frontmatter update (narrow exception):** after validation completes, write the result to the recommendations file's YAML frontmatter. Read the frontmatter block only (lines between the opening `---` and closing `---`), update or append the relevant fields, then write the full file as: new frontmatter block (from opening `---` to closing `---` inclusive) followed immediately by the original file content starting from the character after the closing `---`. **Reconstruction contract:** never write a file containing only the frontmatter — the body must be preserved verbatim.
520
+
521
+ On successful validation (empty issues list):
522
+ ```yaml
523
+ phase_e_ok: true
524
+ ```
525
+
526
+ On failed validation (non-empty issues list):
527
+ ```yaml
528
+ phase_e_ok: false
529
+ phase_e_issues:
530
+ - missing-subheading
531
+ - xi-row-count-wrong
532
+ # ... one short-code per issue, from the vocabulary in references/output-template.md
533
+ ```
534
+
535
+ **Short-code vocabulary** (use the code that matches each issue string from steps 3-4):
536
+
537
+ | Issue | Short code |
538
+ |---|---|
539
+ | any `#### {name}` sub-heading absent | `missing-subheading` |
540
+ | `starting_xi_rows != 11` | `xi-row-count-wrong` |
541
+ | `bench_rows != 4` | `bench-row-count-wrong` |
542
+ | `captain_named == false` | `captain-unnamed` |
543
+ | `vice_named == false` | `vice-unnamed` |
544
+ | `budget_total_gbp_m is None` (parse failure) | `budget-parse-failed` |
545
+ | `budget_total_gbp_m > 100.0` | `budget-over-cap` |
546
+ | `max_per_team_ok == false` | `team-cap-violation` |
547
+ | `player_count != 15` | `squad-size-wrong` |
548
+
549
+ This frontmatter write is the single narrow exception to "Phase E never mutates the file" — it is idempotent (re-running Phase E produces the same frontmatter) and applies to frontmatter only. Flag in the commit message.
550
+
551
+ **Embed-mode runs only.** On transfer and rederive runs, Phase E does not execute, so these fields are omitted.
552
+
553
+ 7. Proceed to end of pipeline regardless of validation outcome. **The file body is never mutated by Phase E.**