fplkit 2.0.0__tar.gz → 2.2.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 (308) hide show
  1. fplkit-2.2.0/.agents/TOOLS.md +116 -0
  2. fplkit-2.2.0/.agents/skills/create-pr/SKILL.md +189 -0
  3. {fplkit-2.0.0 → fplkit-2.2.0}/.agents/skills/gw-prep/SKILL.md +157 -18
  4. {fplkit-2.0.0 → fplkit-2.2.0}/.agents/skills/gw-prep/references/output-template.md +32 -4
  5. {fplkit-2.0.0 → fplkit-2.2.0}/.agents/skills/gw-prep/references/rules.md +5 -2
  6. fplkit-2.2.0/.agents/skills/gw-prep/scripts/_bootstrap.py +25 -0
  7. {fplkit-2.0.0 → fplkit-2.2.0}/.agents/skills/gw-prep/scripts/bench_order.py +3 -0
  8. {fplkit-2.0.0 → fplkit-2.2.0}/.agents/skills/gw-prep/scripts/extract_classic_squad.py +126 -118
  9. {fplkit-2.0.0 → fplkit-2.2.0}/.agents/skills/gw-prep/scripts/starting_xi.py +3 -0
  10. {fplkit-2.0.0 → fplkit-2.2.0}/.agents/skills/gw-prep/scripts/transfer_eval.py +3 -0
  11. {fplkit-2.0.0 → fplkit-2.2.0}/.agents/skills/gw-prep/scripts/validate_draft_waivers.py +71 -38
  12. fplkit-2.2.0/.agents/skills/preview-ingest/SKILL.md +162 -0
  13. fplkit-2.2.0/.agents/skills/release/SKILL.md +172 -0
  14. fplkit-2.2.0/.agents/skills/release-notes/SKILL.md +98 -0
  15. {fplkit-2.0.0 → fplkit-2.2.0}/.agents/skills/squad-builder/SKILL.md +96 -14
  16. {fplkit-2.0.0 → fplkit-2.2.0}/.agents/skills/squad-builder/references/output-template.md +11 -0
  17. {fplkit-2.0.0 → fplkit-2.2.0}/.agents/skills/squad-builder/references/rules.md +24 -0
  18. {fplkit-2.0.0 → fplkit-2.2.0}/.agents/skills/update-gw-prep/SKILL.md +25 -8
  19. fplkit-2.2.0/.github/workflows/ci.yml +64 -0
  20. fplkit-2.2.0/.github/workflows/pr-title.yml +30 -0
  21. {fplkit-2.0.0 → fplkit-2.2.0}/.github/workflows/release.yml +8 -3
  22. {fplkit-2.0.0 → fplkit-2.2.0}/.gitignore +0 -3
  23. fplkit-2.2.0/AGENTS.md +94 -0
  24. {fplkit-2.0.0 → fplkit-2.2.0}/CHANGELOG.md +55 -1
  25. fplkit-2.2.0/CLAUDE.md +94 -0
  26. fplkit-2.2.0/CONTRIBUTING.md +109 -0
  27. {fplkit-2.0.0 → fplkit-2.2.0}/PKG-INFO +44 -7
  28. {fplkit-2.0.0 → fplkit-2.2.0}/README.md +40 -4
  29. {fplkit-2.0.0 → fplkit-2.2.0}/cliff.toml +14 -1
  30. {fplkit-2.0.0 → fplkit-2.2.0}/docs/architecture.md +103 -38
  31. fplkit-2.2.0/docs/command-reference.md +952 -0
  32. {fplkit-2.0.0 → fplkit-2.2.0}/docs/custom-analysis.md +60 -15
  33. {fplkit-2.0.0 → fplkit-2.2.0}/docs/fpl-rules.md +5 -5
  34. {fplkit-2.0.0 → fplkit-2.2.0}/fpl_cli/_version.py +2 -2
  35. {fplkit-2.0.0 → fplkit-2.2.0}/fpl_cli/agents/action/waiver.py +30 -4
  36. {fplkit-2.0.0 → fplkit-2.2.0}/fpl_cli/agents/analysis/bench_order.py +8 -3
  37. {fplkit-2.0.0 → fplkit-2.2.0}/fpl_cli/agents/analysis/captain.py +8 -3
  38. {fplkit-2.0.0 → fplkit-2.2.0}/fpl_cli/agents/analysis/squad_analyzer.py +1 -1
  39. {fplkit-2.0.0 → fplkit-2.2.0}/fpl_cli/agents/analysis/starting_xi.py +26 -3
  40. {fplkit-2.0.0 → fplkit-2.2.0}/fpl_cli/agents/analysis/stats.py +10 -3
  41. {fplkit-2.0.0 → fplkit-2.2.0}/fpl_cli/agents/analysis/transfer_eval.py +15 -4
  42. {fplkit-2.0.0 → fplkit-2.2.0}/fpl_cli/agents/common.py +6 -3
  43. {fplkit-2.0.0 → fplkit-2.2.0}/fpl_cli/agents/data/fixture.py +9 -0
  44. {fplkit-2.0.0 → fplkit-2.2.0}/fpl_cli/agents/orchestration/report.py +62 -12
  45. fplkit-2.2.0/fpl_cli/api/contract.py +77 -0
  46. {fplkit-2.0.0 → fplkit-2.2.0}/fpl_cli/api/core_insights.py +83 -23
  47. {fplkit-2.0.0 → fplkit-2.2.0}/fpl_cli/api/dataset_fetcher.py +3 -21
  48. {fplkit-2.0.0 → fplkit-2.2.0}/fpl_cli/api/football_data.py +18 -2
  49. {fplkit-2.0.0 → fplkit-2.2.0}/fpl_cli/api/fpl_draft.py +7 -1
  50. {fplkit-2.0.0 → fplkit-2.2.0}/fpl_cli/api/providers/anthropic.py +1 -1
  51. {fplkit-2.0.0 → fplkit-2.2.0}/fpl_cli/api/understat.py +35 -4
  52. {fplkit-2.0.0 → fplkit-2.2.0}/fpl_cli/api/vaastav.py +45 -5
  53. {fplkit-2.0.0 → fplkit-2.2.0}/fpl_cli/cli/__init__.py +17 -4
  54. fplkit-2.2.0/fpl_cli/cli/_context.py +321 -0
  55. {fplkit-2.0.0 → fplkit-2.2.0}/fpl_cli/cli/_fines.py +15 -7
  56. {fplkit-2.0.0 → fplkit-2.2.0}/fpl_cli/cli/_helpers.py +21 -0
  57. {fplkit-2.0.0 → fplkit-2.2.0}/fpl_cli/cli/_league_recap_data.py +674 -67
  58. fplkit-2.2.0/fpl_cli/cli/_league_recap_history.py +1076 -0
  59. fplkit-2.2.0/fpl_cli/cli/_league_recap_types.py +198 -0
  60. {fplkit-2.0.0 → fplkit-2.2.0}/fpl_cli/cli/_review_analysis.py +2 -2
  61. {fplkit-2.0.0 → fplkit-2.2.0}/fpl_cli/cli/_review_classic.py +23 -0
  62. {fplkit-2.0.0 → fplkit-2.2.0}/fpl_cli/cli/_review_summarisation.py +163 -20
  63. {fplkit-2.0.0 → fplkit-2.2.0}/fpl_cli/cli/allocate.py +45 -25
  64. {fplkit-2.0.0 → fplkit-2.2.0}/fpl_cli/cli/captain.py +2 -5
  65. {fplkit-2.0.0 → fplkit-2.2.0}/fpl_cli/cli/chips.py +1 -1
  66. {fplkit-2.0.0 → fplkit-2.2.0}/fpl_cli/cli/differentials.py +2 -5
  67. fplkit-2.2.0/fpl_cli/cli/doctor.py +694 -0
  68. fplkit-2.2.0/fpl_cli/cli/doctor_providers.py +710 -0
  69. {fplkit-2.0.0 → fplkit-2.2.0}/fpl_cli/cli/fdr.py +7 -7
  70. {fplkit-2.0.0 → fplkit-2.2.0}/fpl_cli/cli/init.py +18 -21
  71. fplkit-2.2.0/fpl_cli/cli/intel.py +534 -0
  72. {fplkit-2.0.0 → fplkit-2.2.0}/fpl_cli/cli/league.py +9 -2
  73. fplkit-2.2.0/fpl_cli/cli/league_recap.py +547 -0
  74. {fplkit-2.0.0 → fplkit-2.2.0}/fpl_cli/cli/player.py +32 -23
  75. {fplkit-2.0.0 → fplkit-2.2.0}/fpl_cli/cli/preview.py +6 -3
  76. {fplkit-2.0.0 → fplkit-2.2.0}/fpl_cli/cli/price_changes.py +2 -5
  77. {fplkit-2.0.0 → fplkit-2.2.0}/fpl_cli/cli/ratings.py +120 -10
  78. fplkit-2.2.0/fpl_cli/cli/returnees.py +798 -0
  79. {fplkit-2.0.0 → fplkit-2.2.0}/fpl_cli/cli/review.py +36 -9
  80. {fplkit-2.0.0 → fplkit-2.2.0}/fpl_cli/cli/sell_prices.py +22 -3
  81. {fplkit-2.0.0 → fplkit-2.2.0}/fpl_cli/cli/squad.py +39 -10
  82. {fplkit-2.0.0 → fplkit-2.2.0}/fpl_cli/cli/stats.py +11 -7
  83. {fplkit-2.0.0 → fplkit-2.2.0}/fpl_cli/cli/status.py +117 -32
  84. {fplkit-2.0.0 → fplkit-2.2.0}/fpl_cli/cli/targets.py +2 -5
  85. {fplkit-2.0.0 → fplkit-2.2.0}/fpl_cli/cli/waivers.py +2 -5
  86. {fplkit-2.0.0 → fplkit-2.2.0}/fpl_cli/cli/xg.py +2 -5
  87. {fplkit-2.0.0 → fplkit-2.2.0}/fpl_cli/config/defaults.yaml +19 -1
  88. fplkit-2.2.0/fpl_cli/config/fixture_predictions.yaml +5 -0
  89. fplkit-2.2.0/fpl_cli/config/previews/EXAMPLE.yaml +97 -0
  90. {fplkit-2.0.0 → fplkit-2.2.0/fpl_cli}/config/team_managers.yaml +10 -10
  91. {fplkit-2.0.0 → fplkit-2.2.0}/fpl_cli/models/chip_plan.py +7 -4
  92. fplkit-2.2.0/fpl_cli/models/league_history.py +415 -0
  93. {fplkit-2.0.0 → fplkit-2.2.0}/fpl_cli/models/player.py +8 -0
  94. {fplkit-2.0.0 → fplkit-2.2.0}/fpl_cli/models/team.py +18 -8
  95. {fplkit-2.0.0 → fplkit-2.2.0}/fpl_cli/models/types.py +16 -0
  96. fplkit-2.2.0/fpl_cli/paths.py +244 -0
  97. {fplkit-2.0.0 → fplkit-2.2.0}/fpl_cli/prompts/league_recap.py +68 -4
  98. fplkit-2.2.0/fpl_cli/prompts/returnees.py +104 -0
  99. {fplkit-2.0.0 → fplkit-2.2.0}/fpl_cli/prompts/review.py +59 -16
  100. {fplkit-2.0.0 → fplkit-2.2.0}/fpl_cli/scraper/fpl_prices.py +63 -17
  101. fplkit-2.2.0/fpl_cli/season.py +162 -0
  102. {fplkit-2.0.0 → fplkit-2.2.0}/fpl_cli/services/fixture_predictions.py +124 -20
  103. fplkit-2.2.0/fpl_cli/services/league_history.py +343 -0
  104. fplkit-2.2.0/fpl_cli/services/league_history_counters.py +679 -0
  105. fplkit-2.2.0/fpl_cli/services/league_history_notes.py +648 -0
  106. {fplkit-2.0.0 → fplkit-2.2.0}/fpl_cli/services/player_prior.py +20 -21
  107. fplkit-2.2.0/fpl_cli/services/returnee_radar.py +1655 -0
  108. fplkit-2.2.0/fpl_cli/services/scoring/__init__.py +252 -0
  109. fplkit-2.2.0/fpl_cli/services/scoring/constants.py +493 -0
  110. fplkit-2.2.0/fpl_cli/services/scoring/data_prep.py +498 -0
  111. fplkit-2.2.0/fpl_cli/services/scoring/display.py +57 -0
  112. fplkit-2.2.0/fpl_cli/services/scoring/evaluation.py +333 -0
  113. fplkit-2.2.0/fpl_cli/services/scoring/ownership.py +241 -0
  114. fplkit-2.2.0/fpl_cli/services/scoring/shrinkage.py +213 -0
  115. fplkit-2.2.0/fpl_cli/services/scoring/signals.py +708 -0
  116. fplkit-2.2.0/fpl_cli/services/scoring/single_gw.py +550 -0
  117. fplkit-2.2.0/fpl_cli/services/scoring/value_quality.py +217 -0
  118. fplkit-2.2.0/fpl_cli/services/season_previews.py +953 -0
  119. {fplkit-2.0.0 → fplkit-2.2.0}/fpl_cli/services/squad_allocator.py +12 -4
  120. {fplkit-2.0.0 → fplkit-2.2.0}/fpl_cli/services/team_ratings.py +323 -68
  121. fplkit-2.2.0/fpl_cli/services/team_ratings_prior.py +651 -0
  122. {fplkit-2.0.0 → fplkit-2.2.0}/fpl_cli/templates/gw_league_recap.md.j2 +23 -0
  123. {fplkit-2.0.0 → fplkit-2.2.0}/fpl_cli/templates/gw_review.md.j2 +9 -1
  124. fplkit-2.2.0/fpl_cli/utils/files.py +37 -0
  125. fplkit-2.2.0/fpl_cli/utils/gameweek.py +10 -0
  126. fplkit-2.2.0/fpl_cli/utils/markdown.py +292 -0
  127. fplkit-2.2.0/fpl_cli/utils/teams.py +51 -0
  128. {fplkit-2.0.0 → fplkit-2.2.0}/pyproject.toml +14 -1
  129. fplkit-2.2.0/scripts/calibrate_promoted_prior.py +283 -0
  130. fplkit-2.2.0/scripts/calibrate_quality_ceilings.py +669 -0
  131. {fplkit-2.0.0 → fplkit-2.2.0}/tests/conftest.py +116 -4
  132. fplkit-2.2.0/tests/fixtures/validate_draft_waivers/recs_clean.md +28 -0
  133. fplkit-2.2.0/tests/fixtures/validate_draft_waivers/waivers.json +19 -0
  134. fplkit-2.2.0/tests/fixtures/waiver_ranked_golden.json +1053 -0
  135. {fplkit-2.0.0 → fplkit-2.2.0}/tests/test_agents_action.py +344 -5
  136. {fplkit-2.0.0 → fplkit-2.2.0}/tests/test_agents_analysis.py +21 -3
  137. {fplkit-2.0.0 → fplkit-2.2.0}/tests/test_agents_analysis_squad.py +9 -1
  138. {fplkit-2.0.0 → fplkit-2.2.0}/tests/test_agents_data.py +21 -4
  139. {fplkit-2.0.0 → fplkit-2.2.0}/tests/test_allocate.py +73 -27
  140. {fplkit-2.0.0 → fplkit-2.2.0}/tests/test_api_core_insights.py +74 -1
  141. {fplkit-2.0.0 → fplkit-2.2.0}/tests/test_api_draft.py +33 -0
  142. {fplkit-2.0.0 → fplkit-2.2.0}/tests/test_api_football_data.py +14 -0
  143. {fplkit-2.0.0 → fplkit-2.2.0}/tests/test_api_understat.py +73 -3
  144. {fplkit-2.0.0 → fplkit-2.2.0}/tests/test_api_vaastav.py +84 -0
  145. {fplkit-2.0.0 → fplkit-2.2.0}/tests/test_bench_order_script.py +17 -6
  146. {fplkit-2.0.0 → fplkit-2.2.0}/tests/test_classic_squad_extraction.py +633 -1
  147. {fplkit-2.0.0 → fplkit-2.2.0}/tests/test_cli_allocate.py +4 -4
  148. {fplkit-2.0.0 → fplkit-2.2.0}/tests/test_cli_captain.py +7 -0
  149. {fplkit-2.0.0 → fplkit-2.2.0}/tests/test_cli_chips.py +17 -0
  150. {fplkit-2.0.0 → fplkit-2.2.0}/tests/test_cli_custom_analysis.py +73 -0
  151. {fplkit-2.0.0 → fplkit-2.2.0}/tests/test_cli_differentials.py +7 -0
  152. fplkit-2.2.0/tests/test_cli_doctor.py +485 -0
  153. fplkit-2.2.0/tests/test_cli_doctor_providers.py +359 -0
  154. {fplkit-2.0.0 → fplkit-2.2.0}/tests/test_cli_fdr.py +54 -0
  155. {fplkit-2.0.0 → fplkit-2.2.0}/tests/test_cli_fdr_blanks.py +43 -7
  156. {fplkit-2.0.0 → fplkit-2.2.0}/tests/test_cli_init.py +5 -1
  157. fplkit-2.2.0/tests/test_cli_intel.py +344 -0
  158. fplkit-2.2.0/tests/test_cli_league.py +67 -0
  159. fplkit-2.2.0/tests/test_cli_league_recap.py +1941 -0
  160. {fplkit-2.0.0 → fplkit-2.2.0}/tests/test_cli_player.py +140 -2
  161. {fplkit-2.0.0 → fplkit-2.2.0}/tests/test_cli_preview.py +67 -0
  162. fplkit-2.2.0/tests/test_cli_price_changes.py +57 -0
  163. fplkit-2.2.0/tests/test_cli_returnees.py +868 -0
  164. {fplkit-2.0.0 → fplkit-2.2.0}/tests/test_cli_review.py +208 -2
  165. {fplkit-2.0.0 → fplkit-2.2.0}/tests/test_cli_sell_prices.py +91 -1
  166. fplkit-2.2.0/tests/test_cli_settings_warning.py +99 -0
  167. {fplkit-2.0.0 → fplkit-2.2.0}/tests/test_cli_squad.py +109 -0
  168. {fplkit-2.0.0 → fplkit-2.2.0}/tests/test_cli_stats.py +8 -1
  169. {fplkit-2.0.0 → fplkit-2.2.0}/tests/test_cli_status.py +332 -0
  170. {fplkit-2.0.0 → fplkit-2.2.0}/tests/test_cli_targets.py +7 -0
  171. {fplkit-2.0.0 → fplkit-2.2.0}/tests/test_cli_waivers.py +7 -0
  172. {fplkit-2.0.0 → fplkit-2.2.0}/tests/test_cli_warnings_stderr.py +2 -1
  173. {fplkit-2.0.0 → fplkit-2.2.0}/tests/test_cli_xg.py +7 -0
  174. {fplkit-2.0.0 → fplkit-2.2.0}/tests/test_common.py +37 -0
  175. {fplkit-2.0.0 → fplkit-2.2.0}/tests/test_common_scoring.py +4 -4
  176. {fplkit-2.0.0 → fplkit-2.2.0}/tests/test_data_prep_harmonisation.py +17 -9
  177. fplkit-2.2.0/tests/test_doc_consistency.py +89 -0
  178. {fplkit-2.0.0 → fplkit-2.2.0}/tests/test_fines.py +16 -1
  179. {fplkit-2.0.0 → fplkit-2.2.0}/tests/test_fixture_predictions.py +183 -7
  180. {fplkit-2.0.0 → fplkit-2.2.0}/tests/test_init.py +51 -0
  181. fplkit-2.2.0/tests/test_league_history_counters.py +935 -0
  182. fplkit-2.2.0/tests/test_league_history_notes.py +701 -0
  183. fplkit-2.2.0/tests/test_league_history_store.py +588 -0
  184. fplkit-2.2.0/tests/test_league_recap.py +2876 -0
  185. fplkit-2.2.0/tests/test_paths.py +454 -0
  186. {fplkit-2.0.0 → fplkit-2.2.0}/tests/test_player_prior.py +14 -14
  187. fplkit-2.2.0/tests/test_preseason.py +542 -0
  188. {fplkit-2.0.0 → fplkit-2.2.0}/tests/test_providers.py +5 -5
  189. {fplkit-2.0.0 → fplkit-2.2.0}/tests/test_report_agent.py +50 -0
  190. fplkit-2.2.0/tests/test_report_paths.py +212 -0
  191. fplkit-2.2.0/tests/test_returnee_radar.py +1467 -0
  192. {fplkit-2.0.0 → fplkit-2.2.0}/tests/test_review.py +257 -0
  193. {fplkit-2.0.0 → fplkit-2.2.0}/tests/test_rolling_pts_per_m.py +1 -1
  194. fplkit-2.0.0/tests/test_player_scoring.py → fplkit-2.2.0/tests/test_scoring.py +655 -93
  195. {fplkit-2.0.0 → fplkit-2.2.0}/tests/test_scraper.py +187 -25
  196. fplkit-2.2.0/tests/test_season.py +219 -0
  197. fplkit-2.2.0/tests/test_season_awareness.py +382 -0
  198. fplkit-2.2.0/tests/test_season_previews.py +763 -0
  199. {fplkit-2.0.0 → fplkit-2.2.0}/tests/test_squad_allocator.py +63 -2
  200. {fplkit-2.0.0 → fplkit-2.2.0}/tests/test_starting_xi_agent.py +53 -1
  201. {fplkit-2.0.0 → fplkit-2.2.0}/tests/test_stats.py +60 -0
  202. {fplkit-2.0.0 → fplkit-2.2.0}/tests/test_team_ratings.py +564 -17
  203. fplkit-2.2.0/tests/test_team_ratings_prior.py +1048 -0
  204. {fplkit-2.0.0 → fplkit-2.2.0}/tests/test_transfer_eval_agent.py +1 -1
  205. fplkit-2.2.0/tests/test_utils_gameweek.py +18 -0
  206. fplkit-2.2.0/tests/test_utils_markdown.py +238 -0
  207. {fplkit-2.0.0 → fplkit-2.2.0}/tests/test_validate_draft_waivers.py +363 -0
  208. fplkit-2.0.0/.agents/TOOLS.md +0 -97
  209. fplkit-2.0.0/.github/workflows/ci.yml +0 -35
  210. fplkit-2.0.0/AGENTS.md +0 -70
  211. fplkit-2.0.0/CLAUDE.md +0 -70
  212. fplkit-2.0.0/config/team_ratings.yaml +0 -119
  213. fplkit-2.0.0/docs/command-reference.md +0 -588
  214. fplkit-2.0.0/fpl_cli/cli/_context.py +0 -171
  215. fplkit-2.0.0/fpl_cli/cli/_league_recap_types.py +0 -120
  216. fplkit-2.0.0/fpl_cli/cli/league_recap.py +0 -297
  217. fplkit-2.0.0/fpl_cli/config/fixture_predictions.yaml +0 -10
  218. fplkit-2.0.0/fpl_cli/paths.py +0 -130
  219. fplkit-2.0.0/fpl_cli/season.py +0 -88
  220. fplkit-2.0.0/fpl_cli/services/player_scoring.py +0 -2784
  221. fplkit-2.0.0/fpl_cli/services/team_ratings_prior.py +0 -291
  222. fplkit-2.0.0/tests/fixtures/validate_draft_waivers/recs_clean.md +0 -20
  223. fplkit-2.0.0/tests/fixtures/validate_draft_waivers/waivers.json +0 -12
  224. fplkit-2.0.0/tests/test_league_recap.py +0 -1425
  225. fplkit-2.0.0/tests/test_paths.py +0 -182
  226. fplkit-2.0.0/tests/test_season.py +0 -95
  227. fplkit-2.0.0/tests/test_team_ratings_prior.py +0 -194
  228. {fplkit-2.0.0 → fplkit-2.2.0}/.agents/README.md +0 -0
  229. {fplkit-2.0.0 → fplkit-2.2.0}/.claude/output-styles/fpl-mate.md +0 -0
  230. {fplkit-2.0.0 → fplkit-2.2.0}/.claude/settings.json +0 -0
  231. {fplkit-2.0.0 → fplkit-2.2.0}/.env.example +0 -0
  232. {fplkit-2.0.0 → fplkit-2.2.0}/LICENSE +0 -0
  233. {fplkit-2.0.0 → fplkit-2.2.0}/config/team_ratings_overrides.yaml +0 -0
  234. {fplkit-2.0.0 → fplkit-2.2.0}/docs/images/fpl-player-demo-v1-0.png +0 -0
  235. {fplkit-2.0.0 → fplkit-2.2.0}/fpl_cli/__init__.py +0 -0
  236. {fplkit-2.0.0 → fplkit-2.2.0}/fpl_cli/agents/__init__.py +0 -0
  237. {fplkit-2.0.0 → fplkit-2.2.0}/fpl_cli/agents/action/__init__.py +0 -0
  238. {fplkit-2.0.0 → fplkit-2.2.0}/fpl_cli/agents/analysis/__init__.py +0 -0
  239. {fplkit-2.0.0 → fplkit-2.2.0}/fpl_cli/agents/base.py +0 -0
  240. {fplkit-2.0.0 → fplkit-2.2.0}/fpl_cli/agents/data/__init__.py +0 -0
  241. {fplkit-2.0.0 → fplkit-2.2.0}/fpl_cli/agents/data/price.py +0 -0
  242. {fplkit-2.0.0 → fplkit-2.2.0}/fpl_cli/agents/data/scout.py +0 -0
  243. {fplkit-2.0.0 → fplkit-2.2.0}/fpl_cli/agents/orchestration/__init__.py +0 -0
  244. {fplkit-2.0.0 → fplkit-2.2.0}/fpl_cli/api/__init__.py +0 -0
  245. {fplkit-2.0.0 → fplkit-2.2.0}/fpl_cli/api/fpl.py +0 -0
  246. {fplkit-2.0.0 → fplkit-2.2.0}/fpl_cli/api/historical.py +0 -0
  247. {fplkit-2.0.0 → fplkit-2.2.0}/fpl_cli/api/historical_types.py +0 -0
  248. {fplkit-2.0.0 → fplkit-2.2.0}/fpl_cli/api/providers/__init__.py +0 -0
  249. {fplkit-2.0.0 → fplkit-2.2.0}/fpl_cli/api/providers/_models.py +0 -0
  250. {fplkit-2.0.0 → fplkit-2.2.0}/fpl_cli/api/providers/openai_compat.py +0 -0
  251. {fplkit-2.0.0 → fplkit-2.2.0}/fpl_cli/api/providers/perplexity.py +0 -0
  252. {fplkit-2.0.0 → fplkit-2.2.0}/fpl_cli/cli/_banner.py +0 -0
  253. {fplkit-2.0.0 → fplkit-2.2.0}/fpl_cli/cli/_fines_config.py +0 -0
  254. {fplkit-2.0.0 → fplkit-2.2.0}/fpl_cli/cli/_json.py +0 -0
  255. {fplkit-2.0.0 → fplkit-2.2.0}/fpl_cli/cli/_plan_grid.py +0 -0
  256. {fplkit-2.0.0 → fplkit-2.2.0}/fpl_cli/cli/_review_draft.py +0 -0
  257. {fplkit-2.0.0 → fplkit-2.2.0}/fpl_cli/cli/credentials.py +0 -0
  258. {fplkit-2.0.0 → fplkit-2.2.0}/fpl_cli/cli/fixtures.py +0 -0
  259. {fplkit-2.0.0 → fplkit-2.2.0}/fpl_cli/cli/history.py +0 -0
  260. {fplkit-2.0.0 → fplkit-2.2.0}/fpl_cli/cli/price_history.py +0 -0
  261. {fplkit-2.0.0 → fplkit-2.2.0}/fpl_cli/cli/transfer_eval.py +0 -0
  262. {fplkit-2.0.0 → fplkit-2.2.0}/fpl_cli/constants.py +0 -0
  263. {fplkit-2.0.0 → fplkit-2.2.0}/fpl_cli/models/__init__.py +0 -0
  264. {fplkit-2.0.0 → fplkit-2.2.0}/fpl_cli/models/fixture.py +0 -0
  265. {fplkit-2.0.0 → fplkit-2.2.0}/fpl_cli/parsers/__init__.py +0 -0
  266. {fplkit-2.0.0 → fplkit-2.2.0}/fpl_cli/parsers/recommendations.py +0 -0
  267. {fplkit-2.0.0 → fplkit-2.2.0}/fpl_cli/prompts/__init__.py +0 -0
  268. {fplkit-2.0.0 → fplkit-2.2.0}/fpl_cli/prompts/scout.py +0 -0
  269. {fplkit-2.0.0 → fplkit-2.2.0}/fpl_cli/scraper/__init__.py +0 -0
  270. {fplkit-2.0.0 → fplkit-2.2.0}/fpl_cli/services/__init__.py +0 -0
  271. {fplkit-2.0.0 → fplkit-2.2.0}/fpl_cli/services/matchup.py +0 -0
  272. {fplkit-2.0.0 → fplkit-2.2.0}/fpl_cli/services/team_form.py +0 -0
  273. {fplkit-2.0.0 → fplkit-2.2.0}/fpl_cli/templates/gw_preview.md.j2 +0 -0
  274. {fplkit-2.0.0 → fplkit-2.2.0}/fpl_cli/utils/__init__.py +0 -0
  275. {fplkit-2.0.0 → fplkit-2.2.0}/fpl_cli/utils/text.py +0 -0
  276. {fplkit-2.0.0 → fplkit-2.2.0}/fpl_cli/utils/time.py +0 -0
  277. {fplkit-2.0.0 → fplkit-2.2.0}/pyrightconfig.json +0 -0
  278. {fplkit-2.0.0 → fplkit-2.2.0}/requirements.lock +0 -0
  279. {fplkit-2.0.0 → fplkit-2.2.0}/tests/__init__.py +0 -0
  280. {fplkit-2.0.0 → fplkit-2.2.0}/tests/fixtures/classic_squad_fixture.md +0 -0
  281. {fplkit-2.0.0 → fplkit-2.2.0}/tests/fixtures/recommendations_fixture.md +0 -0
  282. {fplkit-2.0.0 → fplkit-2.2.0}/tests/fixtures/validate_draft_waivers/squad_grid.json +0 -0
  283. {fplkit-2.0.0 → fplkit-2.2.0}/tests/test_agents_base.py +0 -0
  284. {fplkit-2.0.0 → fplkit-2.2.0}/tests/test_agents_data_price.py +0 -0
  285. {fplkit-2.0.0 → fplkit-2.2.0}/tests/test_agents_orchestration.py +0 -0
  286. {fplkit-2.0.0 → fplkit-2.2.0}/tests/test_api_client.py +0 -0
  287. {fplkit-2.0.0 → fplkit-2.2.0}/tests/test_api_dataset_fetcher.py +0 -0
  288. {fplkit-2.0.0 → fplkit-2.2.0}/tests/test_api_historical.py +0 -0
  289. {fplkit-2.0.0 → fplkit-2.2.0}/tests/test_cli_banner.py +0 -0
  290. {fplkit-2.0.0 → fplkit-2.2.0}/tests/test_cli_credentials.py +0 -0
  291. {fplkit-2.0.0 → fplkit-2.2.0}/tests/test_cli_fixtures.py +0 -0
  292. {fplkit-2.0.0 → fplkit-2.2.0}/tests/test_cli_format.py +0 -0
  293. {fplkit-2.0.0 → fplkit-2.2.0}/tests/test_cli_format_suppression.py +0 -0
  294. {fplkit-2.0.0 → fplkit-2.2.0}/tests/test_cli_history.py +0 -0
  295. {fplkit-2.0.0 → fplkit-2.2.0}/tests/test_cli_json.py +0 -0
  296. {fplkit-2.0.0 → fplkit-2.2.0}/tests/test_cli_plan_grid.py +0 -0
  297. {fplkit-2.0.0 → fplkit-2.2.0}/tests/test_cli_price_history.py +0 -0
  298. {fplkit-2.0.0 → fplkit-2.2.0}/tests/test_cli_transfer_eval.py +0 -0
  299. {fplkit-2.0.0 → fplkit-2.2.0}/tests/test_fines_config.py +0 -0
  300. {fplkit-2.0.0 → fplkit-2.2.0}/tests/test_fpl_prices.py +0 -0
  301. {fplkit-2.0.0 → fplkit-2.2.0}/tests/test_init_fines.py +0 -0
  302. {fplkit-2.0.0 → fplkit-2.2.0}/tests/test_matchup_service.py +0 -0
  303. {fplkit-2.0.0 → fplkit-2.2.0}/tests/test_models.py +0 -0
  304. {fplkit-2.0.0 → fplkit-2.2.0}/tests/test_player_resolution.py +0 -0
  305. {fplkit-2.0.0 → fplkit-2.2.0}/tests/test_recommendations_parser.py +0 -0
  306. {fplkit-2.0.0 → fplkit-2.2.0}/tests/test_sell_prices.py +0 -0
  307. {fplkit-2.0.0 → fplkit-2.2.0}/tests/test_utils_text.py +0 -0
  308. {fplkit-2.0.0 → fplkit-2.2.0}/tests/test_utils_time.py +0 -0
@@ -0,0 +1,116 @@
1
+ # Tools & Capabilities
2
+
3
+ Complete inventory of fpl-cli's composable surface. For architectural diagrams and data flow, see [docs/architecture.md](../docs/architecture.md). For FPL-context usage guidance, see [.claude/output-styles/fpl-mate.md](../.claude/output-styles/fpl-mate.md).
4
+
5
+ ## JSON Envelope
6
+
7
+ All `--format json` commands emit a standard envelope via `emit_json()` in `fpl_cli/cli/_json.py`:
8
+
9
+ ```json
10
+ {
11
+ "command": "<command-name>",
12
+ "metadata": { ... },
13
+ "data": [ ... ]
14
+ }
15
+ ```
16
+
17
+ - `command` - the CLI command name (e.g. `"captain"`, `"chips-timing"`)
18
+ - `metadata` - command-specific context (typically includes `gameweek`)
19
+ - `data` - the payload (list or dict, varies per command)
20
+
21
+ Errors use `emit_json_error()` with `{"command", "error"}` shape.
22
+
23
+ ## CLI Commands
24
+
25
+ | Command | Description | JSON | Format | Experimental | Pattern |
26
+ |---------|-------------|------|--------|-------------|---------|
27
+ | `init` | Set up fpl-cli with your FPL IDs and optional features | No | General | No | direct-api |
28
+ | `status` | Show FPL gameweek status and upcoming deadlines. JSON `metadata` carries `gameweek`, `format` and `season` (hyphenated label, e.g. `2026-27`) - `season` is the directory segment skills must use when writing alongside fpl-cli's own reports | Yes | General | No | direct-api |
29
+ | `doctor` | Check the setup for dead IDs, stale data, and config problems: resolves each configured ID against the live API (reporting the team/league name back so a wrong-but-valid ID is visible), verifies per-team data files (team ratings, managers, season preview intel, finances, player prior) describe the current season's clubs, and reports which config/data/cache directories are in use. With `--providers`, probes the external data sources instead: live shape-and-volume checks against the FPL and Draft APIs, the vaastav and Core-Insights datasets, Understat, and football-data.org, including that every club resolves across sources (the drift that otherwise surfaces as plausible but wrong output). Distinguishes broken (needs a fix today) from stale (self-corrects). Exits non-zero when something is broken | Yes | General | No | direct-api |
30
+ | `fixtures` | Show fixtures for a gameweek | Yes | General | No | direct-api |
31
+ | `player` | Look up a player's stats, xG, ownership and fixture run. JSON includes `ep_next` (predicted pts next GW) and `ep_this` (current GW), emitting `null` when FPL has no projection; panel shows `xPts` (ep_next) when available, omits the segment when FPL has no projection. `--detail` (`-d`) shows GW-by-GW performance and, for FWD/MID with sufficient history, xGI sustainability (per-match GI-xGI divergence -> form modifier). With `custom_analysis`: JSON adds `info.adjusted_npxg_per_90` (fixture-adjusted) and `info.raw_npxg_per_90` (Understat season avg); panel shows `adj. npxG/90: X.XXX (raw: Y.YYY)` | Yes | General | No | direct-api |
32
+ | `stats` | List players with filtering and sorting. `--value` adds quality/value per £m columns; `--window N` sets rolling lookback (3-10) for `rolling_pts_per_m`. Sortable by `ep_next`/`ep_this` (FPL predicted points; `ep_this` is only meaningful before the gameweek's first kickoff — FPL rolls it forward to `ep_next` as each player's match finishes); both in JSON output (emit `null` when FPL has no projection; table renders `—`). Players with no projection sort to the bottom in either direction | Yes | General | No | direct-api |
33
+ | `history` | Show historical player performance across seasons | Yes | General | No | direct-api |
34
+ | `league` | Show live league standings for Classic and Draft leagues | No | General | No | direct-api |
35
+ | `fdr` | Analyse fixture difficulty - easy runs, blanks, doubles | Yes | General | No | via-agent |
36
+ | `xg` | Analyse underlying stats: xG, xA, overperformers | Yes | General | No | via-agent |
37
+ | `price-changes` | Show price changes and transfer activity | No | General | No | via-agent |
38
+ | `price-history` | Show price trajectory and transfer momentum | Yes | General | No | direct-api |
39
+ | `returnees` | Track injured and suspended players due back soon, so a returnee can be claimed in draft before they are fit or planned around in classic. Reads the FPL availability news, filters to returns inside a window (`--window N`, 1-38) and through a source-aware quality bar, and diffs against the stored watchlist for week-over-week changes. Most flagged players carry no parseable return date, so date-unknown entries are the norm rather than the exception; `--enrich` searches the web for fresher timing on those and on stale-news entries (needs a Perplexity key; skips with a note without one) and shows it beside the FPL news, never over it. `--all` bypasses the quality bar and deliberately does not persist the snapshot. JSON `metadata` carries `window`, `escalation_window`, `stash_upgrade_margin`, `transitions_available`, `quality_bar_available`, `quality_bar_applied` and `enrichment_requested`/`_available`/`_note`/`_count`; `data` is `{entries, departures}`, each entry carrying `quality.basis`/`quality.meets_stash`, `expected_return`/`return_gameweek`/`return_source`, `escalation_eligible`/`escalation_basis`, `transition`, and `enrichment` (null unless enriched) | Yes | General | No | direct-api |
40
+ | `preview` | Run full pre-gameweek analysis and generate report. `--save` writes to `<output dir>/<season>/gw{N}-preview.md`; `--scout` writes to `<research dir>/ai-scout-reports/<season>/gw{N}-scout-preview[-referenced].md` | No | General | No | via-agent |
41
+ | `review` | Review a completed gameweek - squad performance and standings. `--save` writes to `<output dir>/<season>/gw{N}-review.md`; `--compare-recs` reads `gw{N}-recommendations.md` from the same season directory | No | General | No | via-agent |
42
+ | `league-recap` | Recap a completed gameweek - awards, standings, and banter. Saves to `<output dir>/<season>/gw{N}-league-recap[-draft].md`. Records every run into the league history ledger (`<data dir>/league_history/`) as a side effect, backfilling classic gaps at a coarse tier automatically; `--backfill-detail` rebuilds earlier gameweeks in full (one request per manager per gameweek). Coverage gaps and capture warnings go to stderr; the command exits 0 even when the store is unreadable. `--format json` emits one row per manager (the captured ledger row shape) plus `metadata.coverage`, `season_phase`, `notes_pack`, `synthesis_summary`, `warnings` (always present, each entry a stable `code` plus rewritable `message` - codes listed in [Command Reference](../docs/command-reference.md#league-history)) and `first_capture_store_path` | Yes | General | No | via-agent |
43
+ | `captain` | Analyse and rank captain options for next gameweek. JSON candidates include `adjusted_npxg_per_90` and `raw_npxg_per_90` when fixture adjustment is active. Consistency tiebreaker (CV-xGI percentile) phased in GW6-10. Players ruled out of the gameweek (0% chance, or no minutes from GW6) are held out of the GW1-9 shrinkage rather than pulled toward the position mean | Yes | Classic | Yes | via-agent |
44
+ | `differentials` | Find differential picks - high potential, low ownership. Inverted consistency bonus (volatile players score higher, phased in GW6-10). Players ruled out of the gameweek (0% chance, or no minutes from GW6) are held out of the GW1-9 shrinkage rather than pulled toward the position mean | Yes | Classic | Yes | via-agent |
45
+ | `targets` | Find transfer targets - high performers across all ownership. Consistency bonus (CV-xGI percentile, phased in GW6-10). Players ruled out of the gameweek (0% chance, or no minutes from GW6) are held out of the GW1-9 shrinkage rather than pulled toward the position mean | Yes | Classic | Yes | via-agent |
46
+ | `transfer-eval` | Compare transfer OUT player against IN candidates. JSON includes `adjusted_npxg_per_90` and `raw_npxg_per_90` per player when fixture adjustment is active. Players ruled out of the gameweek (0% chance, or no minutes from GW6) are held out of the GW1-9 shrinkage rather than pulled toward the position mean | Yes | General | Yes | via-agent |
47
+ | `allocate` | Select mathematically optimal 15-player squad within budget. Players ruled out of the gameweek (0% chance, or no minutes from GW6) are held out of the GW1-9 shrinkage rather than pulled toward the position mean; they stay in the solver pool | Yes | Classic | Yes | direct-api |
48
+ | `waivers` | Show waiver recommendations for your draft league. JSON `data` carries `top_targets` and `targets_by_position` (ranked claims) plus `pool` — the full unowned roster, one `{id, player_name, position, team_short}` row each, unranked and untruncated, so a consumer can answer "is this player claimable at all" without inferring it from the ranked lists. Players ruled out of the gameweek (0% chance, or no minutes from GW6) are held out of the GW1-9 shrinkage rather than pulled toward the position mean | Yes | Draft | Yes | via-agent |
49
+ | `squad` | Analyse your FPL squad health and fixtures | Yes | General | No | via-agent |
50
+ | `squad grid` | Show squad fixture difficulty grid | Yes | General | No | via-agent |
51
+ | `squad sell-prices` | Show squad sell prices and financial breakdown | Yes | Classic | No | direct-api |
52
+ | `chips` | View and plan FPL chip usage | Yes | Classic | No | direct-api |
53
+ | `chips timing` | Recommend chip timing based on blank/double GW exposure | Yes | Classic | No | via-agent |
54
+ | `chips add` | Plan a chip for a gameweek | No | Classic | No | direct-api |
55
+ | `chips remove` | Remove a planned chip from a gameweek | No | Classic | No | direct-api |
56
+ | `chips sync` | Sync chip usage from FPL API | No | Classic | No | direct-api |
57
+ | `ratings` | Display team ratings | No | General | Yes | direct-api |
58
+ | `ratings update` | Recalculate ratings from fixture results (shrunk toward the previous-season prior before GW12; seeds from the prior when no results exist yet) | No | General | Yes | direct-api |
59
+ | `intel` | Show season preview intel collected per team, with coverage and per-gameweek decay. JSON `metadata` carries `coverage.usable_as` (`full` / `negative_filter_only` / `none`), `section_confidence`, `sections_live`/`sections_expired`, `decay_schedule`, `team_set_warning` and `warnings`. `-g/--gameweek` ages the payload to any gameweek; `--show-decay` prints the expiry schedule | Yes | General | No | direct-api |
60
+ | `intel schema` | Print the preview file format with every field explained | No | General | No | direct-api |
61
+ | `intel init` | Scaffold an empty preview file per Premier League team (`--force` overwrites). Stubs never count toward coverage | No | General | No | direct-api |
62
+ | `intel show` | Show one team's preview, aged to the current gameweek | Yes | General | No | direct-api |
63
+ | `intel resolve` | Match preview player names to FPL `element_code`s; `--write` saves them back with comments preserved (never touching an existing code), `--all` re-resolves coded players and with `--write` saves corrections over them. Ambiguity is reported, never guessed | Yes | General | No | direct-api |
64
+ | `credentials set` | Store FPL email and password in system keyring | No | Classic | No | direct-api |
65
+ | `credentials clear` | Remove FPL credentials from system keyring | No | Classic | No | direct-api |
66
+
67
+ **Column key:**
68
+ - **JSON** - supports `--format json` output
69
+ - **Format** - Classic (classic league only), Draft (draft league only), General (both)
70
+ - **Experimental** - requires `custom_analysis: true` in settings; hidden from `--help` by default, and invoking one while it is off reports the toggle and the settings.yaml being read
71
+ - **Pattern** - `direct-api` (API client only), `via-agent` (uses analysis agent), `mixed` (both patterns in subcommands)
72
+
73
+ **Report paths are season-partitioned.** Every command that saves a report writes to `<dir>/<season>/gw{N}-*.md`, where `<season>` is the hyphenated label (`2026-27`). Report filenames carry a gameweek but no season, so a flat directory lets one season's GW21 file overwrite the previous season's. This applies to an explicit `--output` too. Any skill or script writing alongside these reports must use the same season directory, and must take the label from `fpl status --format json` (`metadata.season`) rather than hardcoding it — a hardcoded label silently rots at the July rollover.
74
+
75
+ ## Skills
76
+
77
+ Agent playbooks in `.agents/skills/`. Each has a `SKILL.md` entry point. Claude Code discovers them via the `.claude/skills/` symlink.
78
+
79
+ Skills write their own outputs (`gw{N}-recommendations.md`, `gw{N}-squad-builder.md`, `season-start-squad.md`) into the same `<output dir>/<season>/` directory, for the same reason.
80
+
81
+ | Skill | Path | Purpose | Compatibility |
82
+ |-------|------|---------|--------------|
83
+ | gw-prep | `skills/gw-prep/` | Gameweek preparation recommendations for classic and draft (embed / rederive / transfer branches; Phase B9 preview intel; Phase B10 injury returnee radar; Phase E post-write validation) | Full: Claude Code. Partial: Codex, Cursor, Copilot |
84
+ | update-gw-prep | `skills/update-gw-prep/` | Append GW update to existing recommendations | Full: Claude Code, Codex, Cursor, Copilot |
85
+ | squad-builder | `skills/squad-builder/` | Build optimal 15-player squad (wildcard, free hit, season start; Phase B3 preview intel gate) | Full: Claude Code, Codex, Cursor, Copilot |
86
+ | preview-ingest | `skills/preview-ingest/` | Convert season preview prose into structured per-team intel files, resolve player codes, verify coverage | Full: Claude Code (parallel per team), Codex, Cursor, Copilot |
87
+ | release-notes | `skills/release-notes/` | Draft release notes and suggest the next semver (read-only preview; never tags or publishes) | Full: Claude Code, Codex, Cursor, Copilot |
88
+ | release | `skills/release/` | Cut a release end-to-end: preflight, notes via release-notes, approval gate, publish GitHub release (tag drives PyPI publish + changelog automation) | Full: Claude Code (local), Codex, Cursor, Copilot. Partial: Claude Code web (publish step handed to user) |
89
+ | create-pr | `skills/create-pr/` | Open a PR for the current branch: conventional-commit title (the changelog line) + why-first prose body in the fpl-cli PR description style | Full: Claude Code, Codex, Cursor, Copilot |
90
+
91
+ ## Analysis Agents
92
+
93
+ Python classes in `fpl_cli/agents/` that implement `async run(context) -> AgentResult`.
94
+
95
+ | Agent | Module | Category | CLI Commands | External Consumers |
96
+ |-------|--------|----------|-------------|-------------------|
97
+ | FixtureAgent | `agents/data/fixture.py` | Data | `fdr`, `player -f`, `chips timing`, `preview` | - |
98
+ | PriceAgent | `agents/data/price.py` | Data | `price-changes`, `preview` | - |
99
+ | ScoutAgent | `agents/data/scout.py` | Data | `preview` | - |
100
+ | StatsAgent | `agents/analysis/stats.py` | Analysis | `xg`, `targets`, `differentials`, `preview` | - |
101
+ | CaptainAgent | `agents/analysis/captain.py` | Analysis | `captain`, `differentials` | - |
102
+ | SquadAnalyzerAgent | `agents/analysis/squad_analyzer.py` | Analysis | `squad` | - |
103
+ | BenchOrderAgent | `agents/analysis/bench_order.py` | Analysis | - | gw-prep skill |
104
+ | StartingXIAgent | `agents/analysis/starting_xi.py` | Analysis | - | gw-prep skill |
105
+ | TransferEvalAgent | `agents/analysis/transfer_eval.py` | Analysis | `transfer-eval` | gw-prep skill |
106
+ | WaiverAgent | `agents/action/waiver.py` | Action | `waivers` | - |
107
+ | ReportAgent | `agents/orchestration/report.py` | Orchestration | `preview`, `review`, `league-recap` | - |
108
+
109
+ **Notes:**
110
+ - BenchOrderAgent and StartingXIAgent have no CLI command - they are invoked by gw-prep skill wrapper scripts in `.agents/skills/gw-prep/scripts/`
111
+ - Every scoring family holds players known not to be playing out of the GW1-9 shrinkage; bench and lineup order on raw scores, so for those two it changes the displayed score only
112
+ - TransferEvalAgent is used by both `transfer-eval` CLI command and gw-prep skill
113
+ - `extract_classic_squad.py` (`.agents/skills/gw-prep/scripts/`) — deterministic Classic Squad block extractor used by gw-prep Phase A3 + Phase E. JSON stdout; read-only; emits TypedDict-annotated payloads.
114
+ - gw-prep Phase B10 runs `fpl returnees --enrich --format json` and inlines the payload into both Phase C prompts, rendering a Returning Soon section in the classic and draft output templates. In draft a row can escalate into a stash claim; in classic the watchlist is informational and transfer recommendations may not name a tracked returnee (`references/rules.md`)
115
+ - `validate_draft_waivers.py` (`.agents/skills/gw-prep/scripts/`) — cross-checks the Draft waiver table against the live waiver pool and squad grid (gw-prep Phase D1). JSON stdout; read-only; always exits 0.
116
+ - Both scripts locate markdown sections via `fpl_cli.utils.markdown` (`HeadingMatcher`, `find_section`, `section_body`, `leaf_body`, `fence_flags`), tolerating LLM heading drift (qualifiers, case, leading annotations, opt-in aliases) without matching a different heading that shares a prefix. Also used by `fpl_cli/prompts/review.py` for the GW Narrative section boundary.
@@ -0,0 +1,189 @@
1
+ ---
2
+ name: create-pr
3
+ description: >
4
+ Use when the user asks to create a pull request or PR for the current
5
+ branch — "create a PR", "open a PR", "raise a PR", "push and create a
6
+ PR", "ship this branch". Writes the title as the changelog line
7
+ (conventional commits) and the body in the fpl-cli PR description style:
8
+ first-person prose, why-first, no boilerplate sections. Also the style
9
+ reference whenever a session writes a PR title or body outside the skill.
10
+ model: sonnet
11
+ compatibility:
12
+ claude-code: full (gh locally, GitHub MCP tools on the web)
13
+ codex: full (gh)
14
+ cursor: full (gh)
15
+ copilot: full (gh)
16
+ ---
17
+
18
+ # Create Pull Request
19
+
20
+ Opens a PR for the current branch against main: pushes the branch, writes
21
+ a conventional-commit title, and a prose description in the fpl-cli PR
22
+ description style. Solo repo — there are no reviewers, squads, or labels
23
+ to assign, and the PR is created ready (not draft) unless asked otherwise.
24
+
25
+ ## Title: the changelog line
26
+
27
+ PRs squash-merge with the title as the commit subject on main, and
28
+ git-cliff publishes `feat:`/`fix:`/`refactor:`/`perf:` subjects verbatim in
29
+ CHANGELOG.md and the GitHub release notes. The `PR Title` CI check rejects
30
+ anything non-conventional. So:
31
+
32
+ - Format `type(scope)?!?: subject` — types as in `cliff.toml` (`feat`,
33
+ `fix`, `refactor`, `perf` are changelog-visible; `chore`, `docs`, `ci`,
34
+ `test`, `style` are skipped). `!` marks a breaking CLI change and drives
35
+ a major bump.
36
+ - A changelog-visible title must read as the standalone user-facing change
37
+ ("fix: pin PuLP below 4.0 so squad allocation keeps working"), never the
38
+ implementation ("fix: address review feedback").
39
+ - Keep it ≤ 72 characters — GitHub appends ` (#N)` on squash.
40
+ - No issue references in the title; `Closes #N` lives in the body, so the
41
+ changelog line stays clean.
42
+ - One purpose per PR, so one title can describe it. If no single
43
+ conventional subject covers the diff, the branch wants splitting, not a
44
+ vaguer title.
45
+
46
+ ## Description: the fpl-cli PR description style
47
+
48
+ The body is **flowing first-person prose**, as if explaining the change to
49
+ a colleague. No `## Summary`/`## Changes`/`## Testing` headings, no
50
+ file-by-file changelog, no checklists.
51
+
52
+ ### Tone
53
+
54
+ First person, conversational, proper sentence casing. Reads like an
55
+ explanation, not a filled-in template.
56
+
57
+ Good: "The changelog job pushes straight to main, which the new ruleset
58
+ would block — so it now authenticates as the repo admin, who is a bypass
59
+ actor."
60
+ Bad: "## Summary\nThis PR updates the changelog job's authentication."
61
+
62
+ ### Content
63
+
64
+ - **Open with why, not what.** Start with the problem or context that
65
+ motivated the change. The diff already shows what changed.
66
+ - **Explain the approach when it is non-obvious.** Natural phrasing ("the
67
+ general strategy is"), not a formal section.
68
+ - **`Closes #N`** inline when the PR resolves an issue — GitHub closes it
69
+ on merge. The branch name often carries the number (`claude/issue-46-…`).
70
+ - **Use italics for caveats, scope notes, and verification gaps.**
71
+ Deliberate omissions, known limitations, and anything you could not
72
+ verify — and why — go in `_italics_`. In this repo that is usually
73
+ behaviour needing live FPL API data, LLM provider keys, or the scraper's
74
+ FPL credentials, none of which a web session has.
75
+ - **Include stats when relevant.** Test counts, performance numbers,
76
+ migration sizes — plainly stated, at the end.
77
+
78
+ ### Structure and length
79
+
80
+ No fixed sections. Bullets only where you are genuinely listing items
81
+ (rules, scenarios, per-command effects) — never as the default shape.
82
+
83
+ Pick the budget before writing, and stay inside it:
84
+
85
+ | Change | Body |
86
+ |---|---|
87
+ | Dependency bump, config/data refresh, docs, cleanup, test-only | 1–2 sentences |
88
+ | Bug fix, small feature, new flag, refactor | 2–3 sentences |
89
+ | New agent, new data source, scoring change, or a strategy the diff cannot show | up to 3 short paragraphs |
90
+
91
+ Break the body up at every length: at 2–3 sentences, one sentence per
92
+ line; beyond that, paragraphs of two-three sentences with blank lines
93
+ between. Then one editing pass — delete every sentence that only tells the
94
+ reviewer something the diff or the title already tells them.
95
+
96
+ ### What to leave out
97
+
98
+ - No "Test Plan"/"How to verify"/"Acceptance Criteria" sections
99
+ - No "Changes" lists that duplicate the diff
100
+ - No empty validation ("great improvement to the codebase")
101
+ - No restating the title or narrating commits
102
+ - No extra AI-attribution lines — remote Claude Code sessions append a
103
+ "Generated with Claude Code" footer automatically; that platform footer
104
+ stays, and nothing more gets added
105
+
106
+ ## Workflow
107
+
108
+ ### Step 1: Branch and push
109
+
110
+ ```bash
111
+ BRANCH=$(git branch --show-current) # never main
112
+ git push -u origin "$BRANCH"
113
+ ```
114
+
115
+ Target is always `main`. If the branch's previous PR was already merged,
116
+ restart the branch from `origin/main` first and carry only unmerged
117
+ commits.
118
+
119
+ ### Step 2: Gather the why
120
+
121
+ If the branch or session references an issue, read it (`gh issue view N`,
122
+ or the GitHub MCP `issue_read` tool) for motivation. `git log
123
+ origin/main..HEAD --oneline` shows scope — use it to understand intent,
124
+ never to write a commit list into the body.
125
+
126
+ ### Step 3: Write title and body
127
+
128
+ Title per the rules above; body inside the length budget. Sanity-check the
129
+ title against the lint regex:
130
+ `^(feat|fix|refactor|perf|chore|docs|ci|test|style)(\([a-zA-Z0-9._/ -]+\))?!?: .+`
131
+
132
+ ### Step 4: Create the PR
133
+
134
+ Local session:
135
+
136
+ ```bash
137
+ gh pr create --title "<title>" --base main --body "$(cat <<'EOF'
138
+ <body prose>
139
+ EOF
140
+ )"
141
+ ```
142
+
143
+ Web/remote session: the GitHub MCP `create_pull_request` tool (load via
144
+ ToolSearch) with the same title/body, `base: main`.
145
+
146
+ Pass `--draft` (or `draft: true`) only if the user asked to open it as a
147
+ draft to keep working on the branch.
148
+
149
+ ### Step 5: Report
150
+
151
+ Print the PR URL. If the user asked for auto-merge, arm it (`gh pr merge
152
+ --auto --squash`, or the MCP `enable_pr_auto_merge` tool) — it merges once
153
+ the required `check` and `lint` checks pass.
154
+
155
+ ## Stacked PRs
156
+
157
+ When this branch builds on another open PR, target that PR's branch
158
+ instead of main and end the body with `Stacked on #N.` Sanity-check with
159
+ `git log origin/<target>..HEAD --oneline` — only this PR's commits should
160
+ appear. When the base PR squash-merges and its branch auto-deletes, GitHub
161
+ retargets this PR to main and the diff inherits the base PR's commits:
162
+ rebase onto main before review continues.
163
+
164
+ ## Common Mistakes
165
+
166
+ - Issue number in the title (belongs in the body as `Closes #N`)
167
+ - A changelog-visible title describing the implementation or the review
168
+ process instead of the user-facing change
169
+ - Narrating the diff or padding the body to look thorough
170
+ - Describing only what changed and never why — even a two-sentence body
171
+ carries the motivation
172
+ - Bundling unrelated changes so no single title can describe the PR
173
+ - Forgetting to push before creating the PR
174
+ - Adding attribution lines beyond the automatic platform footer
175
+
176
+ ## Validation
177
+
178
+ - The branch is pushed and the PR targets `main` (or a stacked base per
179
+ the rules above), created ready unless the user asked for a draft.
180
+ - The title matches the lint regex, is ≤ 72 characters, carries no issue
181
+ ref, and — if changelog-visible — reads as the standalone user-facing
182
+ change.
183
+ - The body is why-first first-person prose within its length budget: no
184
+ boilerplate headings, no diff narration, no empty validation, and no
185
+ sentence restating the title or the diff.
186
+ - `Closes #N` appears when the PR resolves an issue; caveats and
187
+ verification gaps are in italics; any stats are plainly stated at the
188
+ end.
189
+ - The PR URL was reported to the user.
@@ -13,7 +13,7 @@ compatibility:
13
13
  copilot: fallback (sequential execution)
14
14
  ---
15
15
 
16
- <!-- CLI commands composed: status, chips, chips sync, chips timing, fdr, captain, waivers, squad grid, squad sell-prices, price-history, player, stats -->
16
+ <!-- CLI commands composed: status, chips, chips sync, chips timing, fdr, captain, waivers, squad grid, squad sell-prices, price-history, player, stats, intel, returnees -->
17
17
 
18
18
  # Gameweek Preparation
19
19
 
@@ -48,6 +48,9 @@ Extract:
48
48
  - `deadline` -- the transfer deadline timestamp
49
49
  - `phase` -- current status (e.g. "Fixture day 1 of 2", "Between gameweeks")
50
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
+ - `metadata.season` -- the hyphenated season label (e.g. `"2026-27"`). Referred to below as `{season}`.
52
+
53
+ **Every file this skill reads or writes lives under `[YOUR_OUTPUT_DIR]/{season}/`.** Report filenames carry the gameweek but no season, so a flat output directory lets 2026-27's GW21 file overwrite 2025-26's. Take `{season}` from this command rather than hardcoding it -- a hardcoded label silently rots at the July rollover, which is the failure this partition exists to prevent. `fpl` writes its own reports to the same season directory.
51
54
 
52
55
  ### A1.5 -- Chip Status
53
56
 
@@ -82,7 +85,7 @@ _Skip unless `mode == "squad-builder"` AND `active_chip ∈ {wildcard, freehit}`
82
85
 
83
86
  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
87
 
85
- 1. Look for `[YOUR_OUTPUT_DIR]/gw{N}-squad-builder.md`.
88
+ 1. Look for `[YOUR_OUTPUT_DIR]/{season}/gw{N}-squad-builder.md`.
86
89
  - Not found → `squad_builder_result = "rederive"`, `squad_builder_reason = "file-missing"`. Done.
87
90
  2. Parse the file's YAML frontmatter. Required fields: `mode`, `gameweek`.
88
91
  - Missing or malformed → `squad_builder_result = "rederive"`, `squad_builder_reason = "frontmatter-malformed"`. Done.
@@ -103,7 +106,7 @@ Locate a matching squad-builder output file and extract the Classic Squad block
103
106
  5. Call the extraction helper:
104
107
 
105
108
  ```bash
106
- python3 "${CLAUDE_SKILL_DIR}/scripts/extract_classic_squad.py" --file "[YOUR_OUTPUT_DIR]/gw{N}-squad-builder.md"
109
+ python3 "${CLAUDE_SKILL_DIR}/scripts/extract_classic_squad.py" --file "[YOUR_OUTPUT_DIR]/{season}/gw{N}-squad-builder.md"
107
110
  ```
108
111
 
109
112
  Parse stdout as JSON regardless of exit code. If JSON parse fails, treat as extraction-failed with a generic error message.
@@ -200,7 +203,60 @@ fpl stats -p DEF -s clean_sheets --min-minutes 450 -n 10 --available-only --form
200
203
 
201
204
  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
205
 
203
- <!-- ADAPT: Add your own supplementary data sources here. Examples:
206
+ ### B9 -- Season Preview Intel
207
+
208
+ ```bash
209
+ fpl intel --format json
210
+ ```
211
+
212
+ Hand-curated pre-season notes on minutes, injuries, role and set-piece duty. Optional: most setups
213
+ have none, and every downstream step is unchanged when there is nothing to read.
214
+
215
+ **This is an early-season source by design.** Each section expires at the point real data
216
+ supersedes it -- projected XIs once actual minutes exist, everything eventually. Do not assume the
217
+ schedule: the response's `metadata.decay_schedule` carries the live expiry table and
218
+ `metadata.sections_live` what still counts at this gameweek. Once `sections_live` is empty, expect
219
+ an empty payload -- that is the decay working, not a failure. Its value is concentrated in the
220
+ opening gameweeks, when `starting_xi.py` and `bench_order.py` are ranking players who have not
221
+ played yet.
222
+
223
+ Read `metadata.coverage.usable_as` and store it as `intel_gate`:
224
+
225
+ | `intel_gate` | What sub-agents may do with intel |
226
+ |---|---|
227
+ | `full` | Support **or** oppose a pick |
228
+ | `negative_filter_only` | Only downgrade: injuries, rotation risk, "not nailed on". Never promote. |
229
+ | `none` | Ignore entirely; omit the intel block from every Phase C prompt |
230
+
231
+ With partial coverage the written-up teams carry annotations and the rest carry nothing, so absence
232
+ of a flag would read as absence of merit. Store the payload as `intel` for Phase C.
233
+
234
+ ### B10 -- Injury Returnee Radar
235
+
236
+ ```bash
237
+ fpl returnees --enrich --format json
238
+ ```
239
+
240
+ Flagged players -- injured, suspended, unavailable, doubtful -- whose expected return lands inside
241
+ `metadata.window` gameweeks and who clear the radar's quality bar. Each entry in `data.entries`
242
+ carries the return estimate and where it came from (`expected_return`, `return_gameweek`,
243
+ `return_source`, `escalation_basis`), a quality verdict (`quality.passed`, `quality.meets_stash`)
244
+ and what moved since the previous run (`transition`). `data.departures` says who left the
245
+ watchlist and why.
246
+
247
+ `--enrich` searches the web for fresher return timing on the players FPL's own news field is silent
248
+ or stale about, which is what supplies a date for the injured majority. It needs a Perplexity API
249
+ key; without one the command skips enrichment, records why in `metadata.enrichment_note` and still
250
+ returns the FPL-sourced watchlist -- so this step is never a reason to abort the run.
251
+
252
+ Store the payload as `returnee_radar` and inline it into both Phase C prompts as a labelled
253
+ section. C1 renders it as an informational watchlist and is barred from naming its players in
254
+ transfer recommendations; C2 renders the subset still in the waiver pool and may escalate an entry
255
+ to a stash claim. The Returning Soon section quotes this payload, which is inlined into the prompt
256
+ like every other cited source -- so its numbers are grounded the same way every other cited number
257
+ is.
258
+
259
+ <!-- ADAPT: Add your own further supplementary data sources here. Examples:
204
260
  - `fpl preview --save --scout` generates a GW preview with fixture analysis and scout insights.
205
261
  Read the saved file and inject its content into Phase C sub-agents as additional context.
206
262
  - Newsletter extracts (e.g. community tips, model projections) saved as markdown files
@@ -252,7 +308,19 @@ Branch on `squad_builder_result` (set in Phase A3; unset on transfer weeks):
252
308
  > - Chip timing: {B6 output}
253
309
  > - Stats: {stats_form}, {stats_transfer_momentum}, {stats_mid_xgi}, {stats_fwd_xgi}, {stats_def_clean_sheets} (from B8)
254
310
  >
255
- > <!-- ADAPT: Add your own supplementary data sources here (newsletters, external reports) -->
311
+ > - Preview intel: {intel} (from B9 - omit this line entirely when `intel_gate` is `none`)
312
+ > Gate: {intel_gate}. Governs **minutes and role only**, never how good a player is. Attribute it
313
+ > ("Transfer Flow projects him starting"), never assert it. Under `negative_filter_only` use it
314
+ > only to downgrade. A player with no intel is not a worse player.
315
+ >
316
+ > - Returning soon: {returnee_radar} (from B10)
317
+ > **Informational only.** Render it as the Returning Soon section of the Classic output template,
318
+ > one row per entry in `data.entries`, quoting only fields present in that payload. The
319
+ > injury/suspension rule in the analysis rules above governs what may be recommended while a
320
+ > player is flagged, and its classic branch is what bars transfer recommendations -- inline
321
+ > late-change swaps included -- from naming a tracked returnee. Do not restate or relax it here.
322
+ >
323
+ > <!-- ADAPT: Add your own further supplementary data sources here (newsletters, external reports) -->
256
324
  >
257
325
  > **Embedded Classic Squad (from squad-builder — insert this as the `### Classic Squad` section):**
258
326
  >
@@ -262,11 +330,11 @@ Branch on `squad_builder_result` (set in Phase A3; unset on transfer weeks):
262
330
  >
263
331
  > 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
332
  >
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.**
333
+ > 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, and never fold an annotation (formation, notes) into a heading line — if squad-builder wrote `#### Starting XI`, keep the heading exactly as `#### Starting XI`; the formation belongs only on the `**Formation:**` line beneath it.**
266
334
  >
267
335
  > 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
336
  >
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).
337
+ > **Sections to produce:** Chip Timing (from B6), Momentum Alerts (from B5/B8), Returning Soon (from B10), 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
338
  >
271
339
  > **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
340
  >
@@ -284,7 +352,7 @@ Branch on `squad_builder_result` (set in Phase A3; unset on transfer weeks):
284
352
  Before dispatching, print the in-chat warning (variant by `squad_builder_reason`):
285
353
 
286
354
  **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`.
355
+ > ⚠️ **Wildcard/Free Hit detected for GW{N}, but no squad-builder file was found.** Expected `gw{N}-squad-builder.md` in `[YOUR_OUTPUT_DIR]/{season}`. Re-derivation will run (weaker squad selection). To use squad-builder output, run `/squad-builder --{wildcard|freehit}` first, then re-run `/gw-prep`.
288
356
 
289
357
  **Variant B** (`squad_builder_reason ∈ {"gameweek-mismatch", "mode-mismatch"}`):
290
358
  > ⚠️ **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`.
@@ -318,7 +386,19 @@ Proceed immediately (non-interactive).
318
386
  > - Chip timing: {B6 output}
319
387
  > - Stats: {stats_form}, {stats_transfer_momentum}, {stats_mid_xgi}, {stats_fwd_xgi}, {stats_def_clean_sheets} (from B8)
320
388
  >
321
- > <!-- ADAPT: Add your own supplementary data sources here (newsletters, external reports) -->
389
+ > - Preview intel: {intel} (from B9 - omit this line entirely when `intel_gate` is `none`)
390
+ > Gate: {intel_gate}. Governs **minutes and role only**, never how good a player is. Attribute it
391
+ > ("Transfer Flow projects him starting"), never assert it. Under `negative_filter_only` use it
392
+ > only to downgrade. A player with no intel is not a worse player.
393
+ >
394
+ > - Returning soon: {returnee_radar} (from B10)
395
+ > **Informational only.** Render it as the Returning Soon section of the Classic output template,
396
+ > one row per entry in `data.entries`, quoting only fields present in that payload. The
397
+ > injury/suspension rule in the analysis rules above governs what may be recommended while a
398
+ > player is flagged, and its classic branch is what bars transfer recommendations -- inline
399
+ > late-change swaps included -- from naming a tracked returnee. Do not restate or relax it here.
400
+ >
401
+ > <!-- ADAPT: Add your own further supplementary data sources here (newsletters, external reports) -->
322
402
  >
323
403
  > 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
404
  >
@@ -350,7 +430,13 @@ Proceed immediately (non-interactive).
350
430
  > - Squad: {B4 output}
351
431
  > - Price movements: {B5 output}
352
432
  > - Chip timing: {B6 output}
353
- > - Stats: {stats_form}, {stats_transfer_momentum}, {stats_mid_xgi}, {stats_fwd_xgi}, {stats_def_clean_sheets} (from B8)
433
+ > - Stats: {stats_form}, {stats_transfer_momentum}, {stats_mid_xgi}, {stats_fwd_xgi}, {stats_def_clean_sheets} (from B8)>
434
+ > - Returning soon: {returnee_radar} (from B10)
435
+ > **Informational only.** Render it as the Returning Soon section of the Classic output template,
436
+ > one row per entry in `data.entries`, quoting only fields present in that payload. The
437
+ > injury/suspension rule in the analysis rules above governs what may be recommended while a
438
+ > player is flagged, and its classic branch is what bars transfer recommendations -- inline
439
+ > late-change swaps included -- from naming a tracked returnee. Do not restate or relax it here.
354
440
  >
355
441
  > **Bench Boost instructions:**
356
442
  >
@@ -390,7 +476,19 @@ Proceed immediately (non-interactive).
390
476
  > - Chip timing: {B6 output}
391
477
  > - Stats: {stats_form}, {stats_transfer_momentum}, {stats_mid_xgi}, {stats_fwd_xgi}, {stats_def_clean_sheets} (from B8)
392
478
  >
393
- > <!-- ADAPT: Add your own supplementary data sources here (newsletters, external reports) -->
479
+ > - Preview intel: {intel} (from B9 - omit this line entirely when `intel_gate` is `none`)
480
+ > Gate: {intel_gate}. Governs **minutes and role only**, never how good a player is. Attribute it
481
+ > ("Transfer Flow projects him starting"), never assert it. Under `negative_filter_only` use it
482
+ > only to downgrade. A player with no intel is not a worse player.
483
+ >
484
+ > - Returning soon: {returnee_radar} (from B10)
485
+ > **Informational only.** Render it as the Returning Soon section of the Classic output template,
486
+ > one row per entry in `data.entries`, quoting only fields present in that payload. The
487
+ > injury/suspension rule in the analysis rules above governs what may be recommended while a
488
+ > player is flagged, and its classic branch is what bars transfer recommendations -- inline
489
+ > late-change swaps included -- from naming a tracked returnee. Do not restate or relax it here.
490
+ >
491
+ > <!-- ADAPT: Add your own further supplementary data sources here (newsletters, external reports) -->
394
492
  >
395
493
  > Produce the **Classic** section of the output template.
396
494
 
@@ -417,11 +515,50 @@ Proceed immediately (non-interactive).
417
515
  >
418
516
  > **WAIVER POOL IS AUTHORITATIVE:** `fpl waivers` output is the only source for available players. All other data (stats, form tables, squad context) is for analysis only. Never recommend a claim not present in the waivers output — Phase D1 will flag pool misses as a warning. Cross-position recommendations (e.g. dropping a MID to claim a DEF) are structurally illegal and will be blocked by Phase D1.
419
517
  >
518
+ > **`data.pool` is an availability roster, not a shortlist.** It lists every unowned player in the league — one `{id, player_name, position, team_short}` row each, unranked and untruncated — so membership answers only "may this player be claimed at all". Ranked claims come from `data.top_targets` and `data.targets_by_position`; presence in `data.pool` is never on its own a reason to recommend someone. The single claim allowed to rest on pool membership alone is a stash claim for a tracked returnee, which the waiver scoring suppresses by design and which therefore cannot reach the ranked targets.
519
+ >
420
520
  > - Waivers: {B3 output}
421
521
  > - Squad: {B4 output}
422
522
  > - Stats: {stats_form}, {stats_transfer_momentum}, {stats_mid_xgi}, {stats_fwd_xgi}, {stats_def_clean_sheets} (from B8)
423
523
  >
424
- > <!-- ADAPT: Add your own supplementary data sources here (newsletters, external reports) -->
524
+ > - Preview intel: {intel} (from B9 - omit this line entirely when `intel_gate` is `none`)
525
+ > Gate: {intel_gate}. Governs **minutes and role only**, never how good a player is. Attribute it
526
+ > ("Transfer Flow projects him starting"), never assert it. Under `negative_filter_only` use it
527
+ > only to downgrade. A player with no intel is not a worse player.
528
+ >
529
+ > - Returning soon: {returnee_radar} (from B10)
530
+ > Render the Returning Soon section of the Draft output template from this payload, restricted to
531
+ > tracked returnees whose `id` appears in the waivers `data.pool` list. A returnee missing from
532
+ > the pool is already owned by a rival and is not actionable — leave it out rather than listing it
533
+ > as unavailable. Quote only fields present in this payload.
534
+ >
535
+ > **STASH CLAIMS:** a Returning Soon row escalates into the Waiver Recommendations table only when
536
+ > **all four** gates below hold. Any one failing means watchlist only — no claim.
537
+ >
538
+ > 1. **Elite by prior:** `quality.meets_stash` is `true`.
539
+ > 2. **Returning soon enough:** `escalation_eligible` is `true`. The command has already applied
540
+ > `metadata.escalation_window` and the citation gate, so do not re-derive the timing yourself:
541
+ > an enrichment date counts here only when it arrived cited, and `escalation_basis` names which
542
+ > source the verdict rests on (`fpl-news` or `ai-search`).
543
+ > 3. **Claimable and position-for-position:** the returnee's `id` is in the waivers `data.pool`, and
544
+ > the player dropped for them plays the returnee's own position.
545
+ > 4. **Beats the incumbent by the configured margin:** take the drop candidate your squad analysis
546
+ > already ranks lowest at that position, run C2.5's `transfer_eval.py --out "{that player}"
547
+ > --in "{returnee}"`, and require `outlook_delta` to exceed the radar's
548
+ > `metadata.stash_upgrade_margin`, which already carries the effective configured value; both
549
+ > are quality points on the same 0-100 scale. If the script errors, or the margin is missing
550
+ > from the metadata, or the delta does not clear it, do not escalate. A drop-priority ordering
551
+ > always yields a lowest-ranked player and so can never answer "no" — the margin is what makes
552
+ > this gate refusable.
553
+ >
554
+ > Write every stash claim as a stash, never as a straight upgrade: it spends a roster slot until the
555
+ > player is fit again, and it is bought to lock the asset before a rival can claim them. Each stash
556
+ > row's Rationale must carry the expected return (`expected_return`, or `return_gameweek` where the
557
+ > date is unknown) and name the provenance of that date from `escalation_basis`. The
558
+ > injury/suspension rule in the analysis rules above is the single authority on what may be
559
+ > recommended while a player is flagged.
560
+ >
561
+ > <!-- ADAPT: Add your own further supplementary data sources here (newsletters, external reports) -->
425
562
  >
426
563
  > Additionally enforce: every waiver swap must be position-for-position (MID out → MID in, DEF out → DEF in, etc.). Cross-position swaps are illegal under FPL Draft rules.
427
564
  >
@@ -435,7 +572,7 @@ After each sub-agent identifies OUT candidates and an IN shortlist (from squad a
435
572
  python3 "${CLAUDE_SKILL_DIR}/scripts/transfer_eval.py" --out "{out_player_name}" --in "{comma-separated IN candidate names}"
436
573
  ```
437
574
 
438
- 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.
575
+ 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, season preview intel from B9) using the same `⚡ Override: {reason}` pattern as starting XI overrides. Name the source in the reason.
439
576
 
440
577
  If the script fails (exit 1), fall back to LLM-driven transfer reasoning and note the failure.
441
578
 
@@ -449,7 +586,9 @@ python3 "${CLAUDE_SKILL_DIR}/scripts/starting_xi.py" --squad "{comma-separated 1
449
586
 
450
587
  <!-- ADAPT: Replace with your squad player names from the squad grid output -->
451
588
 
452
- 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.
589
+ 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, season preview intel from B9). Mark any overrides with `⚡ Override: {reason}` in the output, naming the source. If the script fails (exit 1), fall back to manual selection and note the failure.
590
+
591
+ **Preview intel is at its most useful here in GW1-3**, when the lineup engine is ranking players with no minutes on the board and a projected XI is the only nailed-on signal available. It feeds this override channel deliberately rather than the scoring inside `starting_xi.py`: an override is visible and attributed in the output, whereas a scoring input would move picks invisibly. Respect `intel_gate` -- under `negative_filter_only`, intel may bench a player but never promote one into the XI.
453
592
 
454
593
  ### C4 -- Bench Ordering (skip if `squad_builder_result == "embed"` OR `mode == "benchboost"` — embed has bench order inside block, bench boost has no bench ordering)
455
594
 
@@ -467,8 +606,8 @@ Incorporate the bench ordering output into the relevant sections of each sub-age
467
606
 
468
607
  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.
469
608
 
470
- <!-- ADAPT: Set your output directory -->
471
- **Output path:** `[YOUR_OUTPUT_DIR]/gw{N}-recommendations.md`
609
+ <!-- ADAPT: Set your output directory. Keep the `/{season}/` segment -- it is what stops a new season overwriting the last one's reports. -->
610
+ **Output path:** `[YOUR_OUTPUT_DIR]/{season}/gw{N}-recommendations.md`
472
611
 
473
612
  The file should follow the structure defined in `references/output-template.md`, with both Classic and Draft sections populated.
474
613
 
@@ -492,7 +631,7 @@ _Runs after Phase D file write, before Phase E. Not embed-gated._
492
631
 
493
632
  ```bash
494
633
  cd "$FPL_CLI_DIR" && source .venv/bin/activate && python "$FPL_CLI_DIR/.agents/skills/gw-prep/scripts/validate_draft_waivers.py" \
495
- --recommendations-file "[YOUR_OUTPUT_DIR]/gw{N}-recommendations.md" \
634
+ --recommendations-file "[YOUR_OUTPUT_DIR]/{season}/gw{N}-recommendations.md" \
496
635
  --waivers-json /tmp/gw-prep-waivers-{N}.json \
497
636
  --squad-grid-json /tmp/gw-prep-squad-grid-{N}.json
498
637
  ```
@@ -537,7 +676,7 @@ _Skip unless `squad_builder_result == "embed"`. Transfer and rederive runs do no
537
676
  1. Run:
538
677
 
539
678
  ```bash
540
- python3 "${CLAUDE_SKILL_DIR}/scripts/extract_classic_squad.py" --from-recommendations --file "[YOUR_OUTPUT_DIR]/gw{N}-recommendations.md"
679
+ python3 "${CLAUDE_SKILL_DIR}/scripts/extract_classic_squad.py" --from-recommendations --file "[YOUR_OUTPUT_DIR]/{season}/gw{N}-recommendations.md"
541
680
  ```
542
681
 
543
682
  Parse stdout as JSON regardless of exit code. If exit is non-zero → emit warning and proceed: