fplkit 2.4.0__tar.gz → 2.5.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.
- {fplkit-2.4.0 → fplkit-2.5.0}/.agents/TOOLS.md +27 -20
- fplkit-2.5.0/.agents/skills/build-test-plan/SKILL.md +360 -0
- fplkit-2.5.0/.agents/skills/build-test-plan/references/plan-template.md +224 -0
- fplkit-2.5.0/.agents/skills/build-test-plan/references/workspace-env.md +101 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/.agents/skills/gw-prep/SKILL.md +13 -9
- {fplkit-2.4.0 → fplkit-2.5.0}/.agents/skills/gw-prep/references/entity-normalisation.md +13 -3
- {fplkit-2.4.0 → fplkit-2.5.0}/.agents/skills/gw-prep/references/output-template.md +1 -1
- {fplkit-2.4.0 → fplkit-2.5.0}/.agents/skills/gw-prep/scripts/_bootstrap.py +33 -4
- {fplkit-2.4.0 → fplkit-2.5.0}/.agents/skills/gw-prep/scripts/bench_order.py +18 -17
- {fplkit-2.4.0 → fplkit-2.5.0}/.agents/skills/gw-prep/scripts/starting_xi.py +17 -16
- {fplkit-2.4.0 → fplkit-2.5.0}/.agents/skills/gw-prep/scripts/transfer_eval.py +19 -21
- {fplkit-2.4.0 → fplkit-2.5.0}/.agents/skills/squad-builder/SKILL.md +6 -3
- {fplkit-2.4.0 → fplkit-2.5.0}/.agents/skills/update-gw-prep/SKILL.md +4 -1
- {fplkit-2.4.0 → fplkit-2.5.0}/.claude/output-styles/fpl-mate.md +20 -10
- {fplkit-2.4.0 → fplkit-2.5.0}/AGENTS.md +15 -1
- {fplkit-2.4.0 → fplkit-2.5.0}/CHANGELOG.md +55 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/CLAUDE.md +15 -1
- {fplkit-2.4.0 → fplkit-2.5.0}/CONTRIBUTING.md +44 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/PKG-INFO +42 -7
- {fplkit-2.4.0 → fplkit-2.5.0}/README.md +37 -3
- {fplkit-2.4.0 → fplkit-2.5.0}/docs/architecture.md +39 -21
- {fplkit-2.4.0 → fplkit-2.5.0}/docs/command-reference.md +274 -42
- {fplkit-2.4.0 → fplkit-2.5.0}/docs/custom-analysis.md +24 -2
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/_version.py +2 -2
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/agents/action/waiver.py +1 -1
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/agents/analysis/bench_order.py +1 -1
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/agents/analysis/captain.py +98 -35
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/agents/analysis/squad_analyzer.py +1 -1
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/agents/analysis/starting_xi.py +1 -1
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/agents/analysis/stats.py +165 -17
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/agents/analysis/transfer_eval.py +1 -1
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/agents/base.py +15 -6
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/agents/common.py +89 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/agents/data/fixture.py +1 -1
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/agents/data/price.py +6 -3
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/agents/data/scout.py +1 -1
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/agents/orchestration/report.py +25 -9
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/api/core_insights.py +4 -4
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/api/football_data.py +18 -5
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/api/fpl.py +35 -4
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/api/fpl_draft.py +4 -4
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/api/historical.py +4 -1
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/api/providers/__init__.py +7 -5
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/api/providers/_http.py +1 -1
- fplkit-2.5.0/fpl_cli/api/providers/_models.py +99 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/api/providers/anthropic.py +11 -3
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/api/providers/openai_compat.py +19 -4
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/api/providers/perplexity.py +3 -3
- fplkit-2.5.0/fpl_cli/api/understat.py +937 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/api/vaastav.py +4 -4
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/cli/__init__.py +11 -1
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/cli/_context.py +155 -27
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/cli/_fines.py +47 -9
- fplkit-2.5.0/fpl_cli/cli/_fines_config.py +146 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/cli/_helpers.py +72 -14
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/cli/_json.py +55 -11
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/cli/_league_recap_data.py +539 -91
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/cli/_league_recap_history.py +699 -41
- fplkit-2.5.0/fpl_cli/cli/_league_recap_types.py +544 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/cli/_plan_grid.py +32 -7
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/cli/_review_analysis.py +197 -14
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/cli/_review_classic.py +52 -12
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/cli/_review_draft.py +20 -4
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/cli/_review_summarisation.py +478 -97
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/cli/allocate.py +11 -3
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/cli/captain.py +30 -15
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/cli/chips.py +36 -31
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/cli/credentials.py +3 -3
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/cli/differentials.py +28 -7
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/cli/doctor.py +108 -15
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/cli/doctor_providers.py +159 -11
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/cli/fdr.py +14 -12
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/cli/fixtures.py +15 -10
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/cli/history.py +14 -8
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/cli/init.py +26 -14
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/cli/intel.py +2 -2
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/cli/league.py +9 -8
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/cli/league_fines.py +31 -9
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/cli/league_recap.py +193 -29
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/cli/player.py +33 -13
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/cli/preview.py +27 -13
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/cli/price_changes.py +2 -2
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/cli/price_history.py +15 -10
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/cli/ratings.py +71 -17
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/cli/returnees.py +17 -9
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/cli/review.py +160 -45
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/cli/sell_prices.py +18 -17
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/cli/squad.py +101 -28
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/cli/stats.py +22 -8
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/cli/status.py +25 -10
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/cli/targets.py +9 -2
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/cli/transfer_eval.py +44 -27
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/cli/waivers.py +25 -14
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/cli/xg.py +42 -12
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/config/defaults.yaml +3 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/models/league_history.py +29 -1
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/models/types.py +1 -1
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/paths.py +43 -11
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/prompts/league_recap.py +357 -15
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/prompts/review.py +554 -31
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/prompts/scout.py +1 -1
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/scraper/fpl_prices.py +40 -11
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/season.py +114 -9
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/services/fixture_predictions.py +1 -1
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/services/league_history.py +85 -3
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/services/league_history_counters.py +20 -16
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/services/league_history_fines.py +42 -7
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/services/league_history_notes.py +5 -8
- fplkit-2.5.0/fpl_cli/services/player_clubs.py +366 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/services/player_prior.py +13 -13
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/services/returnee_radar.py +172 -45
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/services/scoring/__init__.py +4 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/services/scoring/constants.py +30 -4
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/services/scoring/data_prep.py +11 -3
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/services/season_previews.py +1 -1
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/services/team_ratings.py +46 -13
- fplkit-2.5.0/fpl_cli/services/team_ratings_prior.py +1353 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/templates/gw_league_recap.md.j2 +20 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/templates/gw_preview.md.j2 +5 -2
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/templates/gw_review.md.j2 +23 -1
- fplkit-2.5.0/fpl_cli/utils/text.py +63 -0
- fplkit-2.5.0/pyproject.toml +126 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/scripts/calibrate_quality_ceilings.py +15 -2
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/conftest.py +145 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/test_agents_analysis.py +153 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/test_agents_base.py +28 -2
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/test_api_client.py +29 -0
- fplkit-2.5.0/tests/test_api_understat.py +1620 -0
- fplkit-2.5.0/tests/test_calibrate_quality_ceilings.py +111 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/test_cli_allocate.py +56 -3
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/test_cli_captain.py +62 -2
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/test_cli_chips.py +108 -28
- fplkit-2.5.0/tests/test_cli_context_settings.py +63 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/test_cli_custom_analysis.py +91 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/test_cli_differentials.py +66 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/test_cli_doctor.py +134 -2
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/test_cli_doctor_providers.py +185 -9
- fplkit-2.5.0/tests/test_cli_failure_parity.py +113 -0
- fplkit-2.5.0/tests/test_cli_failure_streams.py +251 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/test_cli_fdr.py +51 -33
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/test_cli_fixtures.py +7 -7
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/test_cli_format_suppression.py +11 -9
- fplkit-2.5.0/tests/test_cli_fpl_block.py +211 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/test_cli_history.py +46 -13
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/test_cli_intel.py +7 -3
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/test_cli_json_contract.py +166 -32
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/test_cli_league.py +2 -2
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/test_cli_league_fines.py +106 -1
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/test_cli_league_recap.py +2021 -61
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/test_cli_plan_grid.py +4 -4
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/test_cli_player.py +64 -11
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/test_cli_preview.py +68 -9
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/test_cli_price_history.py +32 -21
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/test_cli_returnees.py +91 -13
- fplkit-2.5.0/tests/test_cli_review.py +2710 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/test_cli_sell_prices.py +28 -1
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/test_cli_squad.py +233 -2
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/test_cli_stats.py +65 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/test_cli_status.py +93 -3
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/test_cli_targets.py +30 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/test_cli_waivers.py +17 -1
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/test_cli_warnings_stderr.py +17 -9
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/test_cli_xg.py +119 -3
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/test_common.py +105 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/test_data_prep_harmonisation.py +30 -0
- fplkit-2.5.0/tests/test_dependencies.py +149 -0
- fplkit-2.5.0/tests/test_doc_consistency.py +162 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/test_fines.py +43 -0
- fplkit-2.5.0/tests/test_fines_config.py +224 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/test_gw_prep_script_bootstrap.py +34 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/test_init.py +11 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/test_league_history_fines.py +71 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/test_league_history_store.py +130 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/test_league_recap.py +1930 -33
- fplkit-2.5.0/tests/test_output_style_sync.py +108 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/test_paths.py +57 -0
- fplkit-2.5.0/tests/test_player_clubs.py +314 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/test_player_prior.py +49 -10
- fplkit-2.5.0/tests/test_prompt_tables.py +101 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/test_providers.py +154 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/test_report_agent.py +227 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/test_report_paths.py +37 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/test_returnee_radar.py +452 -58
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/test_review.py +807 -1
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/test_scoring.py +69 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/test_scraper.py +45 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/test_season.py +150 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/test_skill_fdr_quality_signals.py +2 -2
- fplkit-2.5.0/tests/test_skill_stats_thresholds.py +165 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/test_stats.py +321 -11
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/test_team_ratings.py +147 -0
- fplkit-2.5.0/tests/test_team_ratings_prior.py +2196 -0
- fplkit-2.5.0/tests/test_utils_text.py +54 -0
- fplkit-2.4.0/fpl_cli/api/providers/_models.py +0 -45
- fplkit-2.4.0/fpl_cli/api/understat.py +0 -418
- fplkit-2.4.0/fpl_cli/cli/_fines_config.py +0 -76
- fplkit-2.4.0/fpl_cli/cli/_league_recap_types.py +0 -235
- fplkit-2.4.0/fpl_cli/services/team_ratings_prior.py +0 -665
- fplkit-2.4.0/fpl_cli/utils/text.py +0 -26
- fplkit-2.4.0/pyproject.toml +0 -87
- fplkit-2.4.0/requirements.lock +0 -400
- fplkit-2.4.0/tests/test_api_understat.py +0 -824
- fplkit-2.4.0/tests/test_cli_review.py +0 -1352
- fplkit-2.4.0/tests/test_doc_consistency.py +0 -89
- fplkit-2.4.0/tests/test_fines_config.py +0 -99
- fplkit-2.4.0/tests/test_skill_stats_thresholds.py +0 -75
- fplkit-2.4.0/tests/test_team_ratings_prior.py +0 -1048
- fplkit-2.4.0/tests/test_utils_text.py +0 -31
- {fplkit-2.4.0 → fplkit-2.5.0}/.agents/README.md +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/.agents/skills/create-pr/SKILL.md +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/.agents/skills/gw-prep/references/rules.md +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/.agents/skills/gw-prep/scripts/extract_classic_squad.py +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/.agents/skills/gw-prep/scripts/normalise_entities.py +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/.agents/skills/gw-prep/scripts/validate_draft_waivers.py +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/.agents/skills/preview-ingest/SKILL.md +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/.agents/skills/release/SKILL.md +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/.agents/skills/release-notes/SKILL.md +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/.agents/skills/squad-builder/references/output-template.md +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/.agents/skills/squad-builder/references/rules.md +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/.claude/settings.json +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/.env.example +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/.github/workflows/ci.yml +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/.github/workflows/pr-title.yml +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/.github/workflows/release.yml +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/.gitignore +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/LICENSE +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/cliff.toml +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/config/team_ratings_overrides.yaml +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/docs/fpl-rules.md +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/docs/images/fpl-player-demo-v1-0.png +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/__init__.py +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/agents/__init__.py +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/agents/action/__init__.py +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/agents/analysis/__init__.py +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/agents/data/__init__.py +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/agents/orchestration/__init__.py +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/api/__init__.py +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/api/contract.py +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/api/dataset_fetcher.py +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/api/historical_types.py +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/cli/_banner.py +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/config/fixture_predictions.yaml +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/config/previews/EXAMPLE.yaml +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/config/team_managers.yaml +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/constants.py +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/models/__init__.py +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/models/chip_plan.py +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/models/fixture.py +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/models/player.py +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/models/team.py +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/parsers/__init__.py +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/parsers/recommendations.py +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/prompts/__init__.py +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/prompts/returnees.py +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/scraper/__init__.py +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/services/__init__.py +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/services/matchup.py +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/services/scoring/display.py +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/services/scoring/evaluation.py +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/services/scoring/ownership.py +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/services/scoring/shrinkage.py +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/services/scoring/signals.py +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/services/scoring/single_gw.py +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/services/scoring/value_quality.py +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/services/squad_allocator.py +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/services/team_form.py +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/utils/__init__.py +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/utils/files.py +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/utils/gameweek.py +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/utils/markdown.py +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/utils/teams.py +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/fpl_cli/utils/time.py +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/pyrightconfig.json +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/scripts/backtest_early_season_prior_blend.py +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/scripts/calibrate_promoted_prior.py +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/__init__.py +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/fixtures/classic_squad_fixture.md +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/fixtures/recommendations_fixture.md +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/fixtures/validate_draft_waivers/recs_clean.md +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/fixtures/validate_draft_waivers/squad_grid.json +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/fixtures/validate_draft_waivers/waivers.json +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/fixtures/waiver_ranked_golden.json +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/test_agents_action.py +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/test_agents_analysis_squad.py +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/test_agents_data.py +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/test_agents_data_price.py +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/test_agents_orchestration.py +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/test_allocate.py +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/test_api_core_insights.py +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/test_api_dataset_fetcher.py +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/test_api_draft.py +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/test_api_football_data.py +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/test_api_historical.py +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/test_api_vaastav.py +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/test_bench_order_script.py +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/test_classic_squad_extraction.py +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/test_cli_banner.py +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/test_cli_credentials.py +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/test_cli_fdr_blanks.py +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/test_cli_format.py +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/test_cli_init.py +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/test_cli_json.py +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/test_cli_price_changes.py +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/test_cli_settings_warning.py +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/test_cli_transfer_eval.py +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/test_common_scoring.py +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/test_fixture_predictions.py +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/test_fpl_prices.py +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/test_init_fines.py +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/test_league_history_counters.py +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/test_league_history_notes.py +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/test_matchup_service.py +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/test_models.py +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/test_normalise_entities.py +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/test_player_resolution.py +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/test_preseason.py +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/test_recommendations_parser.py +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/test_rolling_pts_per_m.py +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/test_season_awareness.py +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/test_season_previews.py +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/test_sell_prices.py +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/test_squad_allocator.py +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/test_starting_xi_agent.py +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/test_starting_xi_script.py +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/test_transfer_eval_agent.py +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/test_transfer_eval_script.py +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/test_utils_gameweek.py +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/test_utils_markdown.py +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/test_utils_time.py +0 -0
- {fplkit-2.4.0 → fplkit-2.5.0}/tests/test_validate_draft_waivers.py +0 -0
|
@@ -24,43 +24,47 @@ which envelope you got. Warnings and progress go to stderr; the failure message
|
|
|
24
24
|
in `error`, not on stderr. Full contract:
|
|
25
25
|
[Command Reference](../docs/command-reference.md#json-output).
|
|
26
26
|
|
|
27
|
+
Table mode is the mirror image: the table on stdout, and warnings, progress and the reason
|
|
28
|
+
for a nonzero exit on stderr — so a script reading table output can silence the commentary
|
|
29
|
+
with `2>/dev/null` without losing the table, whichever command it runs.
|
|
30
|
+
|
|
27
31
|
## CLI Commands
|
|
28
32
|
|
|
29
33
|
| Command | Description | JSON | Format | Experimental | Pattern |
|
|
30
34
|
|---------|-------------|------|--------|-------------|---------|
|
|
31
35
|
| `init` | Set up fpl-cli with your FPL IDs and optional features | No | General | No | direct-api |
|
|
32
36
|
| `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 |
|
|
33
|
-
| `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 and that the Core-Insights per-gameweek files parse to records through the parsers the scoring commands use (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 |
|
|
37
|
+
| `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, reports whether the calibrated quality ceilings were measured against the newest completed season (the one season-derived artefact frozen in code, so a rollover with no recalibration leaves the scoring scale anchored to a cohort that has turned over), 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 and that players actually join by name (both Understat checks call the enrichment's own gate and matcher on the payload the scoring commands scan, so probe and runtime cannot disagree; a club resolving does not mean its players' names line up) and that the Core-Insights per-gameweek files parse to records through the parsers the scoring commands use (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 |
|
|
34
38
|
| `fixtures` | Show fixtures for a gameweek. With custom analysis on, FDR is the venue-aware ATK/DEF mean `fpl fdr` and `fpl preview` show, so one match reads the same on all three; `-m/--mode` picks `difference` (default) or `opponent`, an unrated club scores a neutral 4.0 rather than the FPL API's 1-5 difficulty, and when the ratings cannot support difficulty at all, table mode warns on stderr and JSON adds a `team_ratings_unusable` entry to `metadata.warnings` (always present, empty or not). With it off, the raw FPL API 1-5 difficulty, and an explicitly passed `-m` reports on stderr that it does not apply. JSON `metadata` carries `custom_analysis`, `fdr_scale` (`team_ratings_1_7` / `fpl_api_1_5`) and `fdr_mode` (null when it does not apply) | Yes | General | No | direct-api |
|
|
35
|
-
| `player` | Look up a player's stats, xG, ownership and fixture run. Before GW10 `quality_score` blends last season's pedigree in and JSON `metadata.warnings` says so (`early_season_prior_informed`, or `early_season_small_sample` when history could not be loaded), same as `fpl stats --value
|
|
36
|
-
| `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`. Before GW10, `--value` quality scores blend last season's pts/90 pedigree with this season's observation and the command says so (stderr notice quoting the observation's weight band; JSON `metadata.warnings` code `early_season_prior_informed`); when last season's history cannot be loaded they are pure observation and the notice says that instead (`early_season_small_sample`, before GW6). GK ceilings scale with the calendar-possible sample, reaching full scale at GW6. 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 |
|
|
37
|
-
| `history` | Show historical player performance across seasons | Yes | General | No | direct-api |
|
|
39
|
+
| `player` | Look up a player's stats, xG, ownership and fixture run. Before GW10 `quality_score` blends last season's pedigree in and JSON `metadata.warnings` says so (`early_season_prior_informed`, or `early_season_small_sample` when history could not be loaded), same as `fpl stats --value`; `understat_team_unmatched` joins it for any club Understat carried no players for, whose whole squad loses xG enrichment. 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 |
|
|
40
|
+
| `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`. Before GW10, `--value` quality scores blend last season's pts/90 pedigree with this season's observation and the command says so (stderr notice quoting the observation's weight band; JSON `metadata.warnings` code `early_season_prior_informed`); when last season's history cannot be loaded they are pure observation and the notice says that instead (`early_season_small_sample`, before GW6). `understat_team_unmatched` is raised once per club Understat carried no players for, whose whole squad scores without xG. GK ceilings scale with the calendar-possible sample, reaching full scale at GW6. 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 |
|
|
41
|
+
| `history` | Show historical player performance across seasons. A provider that refuses exits 1 in both output formats, carrying the upstream's own reason: the error envelope used to say only `Failed to fetch historical data` while the cause went to stderr, and table mode printed the cause to stdout and exited 0 | Yes | General | No | direct-api |
|
|
38
42
|
| `league` | Show live league standings for Classic and Draft leagues | No | General | No | direct-api |
|
|
39
43
|
| `fdr` | Analyse fixture difficulty - easy runs, blanks, doubles. Three payloads, not one: the default call runs the agent and returns the full analysis plus `data.ratings_warning` (the team-ratings quality verdict, and with `--my-squad` also `data.predictions_stale` / `data.prediction_warnings` / `data.squad_exposure`); with custom analysis off it returns raw 1-5 FPL API difficulty (`data.easy_fixture_runs` only, `metadata.custom_analysis: false`, no ratings and so no warning); `--blanks` bypasses the agent for a schedule-only payload carrying neither the FDR analysis nor `ratings_warning`. `--blanks` mixes confirmed fixtures with predictions; when those predictions are stale, table mode warns on stderr and JSON adds a `fixture_predictions_stale` entry to `metadata.warnings` | Yes | General | No | via-agent |
|
|
40
|
-
| `xg` | Analyse underlying stats: xG, xA, overperformers | Yes | General | No | via-agent |
|
|
44
|
+
| `xg` | Analyse underlying stats: xG, xA, overperformers. The minutes floor and the window scale to the gameweeks played (`min(60 x window, 45 x gameweeks played)`, so 90 minutes over 2 GWs rather than an unreachable 360 over 6); the configured bar binds from GW9, or GW11 for `--all`. JSON `metadata` carries `window_label`, `gameweeks_played` and `min_minutes`, and `metadata.warnings` an `early_season_minutes_floor` notice while the bar is scaled, plus `understat_team_unmatched` for any club Understat carried no players for. When nothing qualifies, `data.empty_reason` says whether the floor (`below_minutes_floor`) or the data (`no_minutes_played`) caused it | Yes | General | No | via-agent |
|
|
41
45
|
| `price-changes` | Show price changes and transfer activity | No | General | No | via-agent |
|
|
42
|
-
| `price-history` | Show price trajectory and transfer momentum | Yes | General | No | direct-api |
|
|
43
|
-
| `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`/`_rate_limited` (true when the search provider rate-limited the run even after retrying — the one failure a re-run is likely to clear); `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 |
|
|
46
|
+
| `price-history` | Show price trajectory and transfer momentum. A trend provider that refuses exits 1 in both output formats with the upstream's own reason — the error envelope used to say only `Failed to fetch price history data` while the table path named the cause | Yes | General | No | direct-api |
|
|
47
|
+
| `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`, `transitions_baseline_gameweek` (the gameweek `transition` is measured against — every run in a gameweek diffs against the last state stored before it, so a repeat run reports the same changes), `quality_bar_available`, `quality_bar_applied` and `enrichment_requested`/`_available`/`_note`/`_count`/`_rate_limited` (true when the search provider rate-limited the run even after retrying — the one failure a re-run is likely to clear); `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 |
|
|
44
48
|
| `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 |
|
|
45
|
-
| `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 |
|
|
46
|
-
| `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). A rebuild keeps the name
|
|
49
|
+
| `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. Under `--summarise` the personal analysis is handed next gameweek's fixtures — every club's opponent and venue plus each squad player's positional FDR (1-7 team ratings with custom analysis on, the FPL API's 1-5 difficulty with it off, carrying the same scale footer and unrated-club caveat the FDR tables print, plus any warning that the ratings cannot support difficulty) — so its "Next Week" calls are grounded in fixtures rather than in one week of points; with no next gameweek to look at, that section is narrowed to fixture-independent observations and points at `fpl gw-prep`, and a gameweek already played is marked retrospective. The answer is then checked back against that block — a cited FDR the block never printed, or any fixture claim made without one — and against the sections its prompt asked for: an answer missing a `## ` heading, ending mid-sentence, or that the provider says it stopped early on is retried once, and a still-incomplete one is warned about on stderr (detail in `data/debug/synthesis_corrections.txt` under `--debug`) and carries the same list as a callout in the saved report. The summary is an add-on the review does not depend on: the two provider roles resolve independently, and a role with no usable key is skipped with the reason on stderr (`Community narrative skipped: ...` / `Personal analysis skipped: ...`) while the review prints, saves and exits 0 — so one key produces that half rather than nothing, and a saved report names each skipped half in a warning callout of its own. A gameweek it cannot resolve - one still being played, an id the season does not have, a season with nothing finished yet - is refused with the specific reason on stderr and exit 1; stdout carries the review or nothing | No | General | No | via-agent |
|
|
50
|
+
| `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). A rebuild keeps the name and position already recorded for each player instead of restamping them from today's bootstrap, and never drops a code the gameweek already resolved; it reads the earliest recorded row rather than the newest, so a gameweek an older version already restamped is repaired rather than re-copied. The club is not carried but *derived*, from the gameweek's own fixtures: FPL's live data records which fixtures each player's club played, so a double gameweek settles his club outright and a single narrows it to two, with one `element-summary` request spent only where today's club is absent from that pair (proof he has moved); today's club being one of the two is left unanswered rather than assumed, so a recorded club still wins there. That reaches the two cases a carry structurally cannot — a first capture and a coarse-to-detailed upgrade, neither of which has a recorded squad — and it supersedes a stale recorded club rather than preserving it, with `league_history_club_rederived` naming how many were replaced. A club that blanked has no fixture to intersect and falls back to the recorded club, then today's; name and position are not derivable from fixtures at all and stay carry-only. Everything the gameweek itself answers (points, cards, pick flags, blank status) is still re-derived, and a `league_history_identity_carried` warning names how many players were kept. A rebuild likewise never writes away a league position or cumulative total it cannot re-derive: draft has no per-manager history endpoint and its standings describe a later gameweek, so a replayed draft gameweek keeps the figures already recorded (`league_history_standings_carried`) instead of nulling them, which used to cost the ledger every position that gameweek held and collapse the streaks built on them. Where they were already lost, every run restores them from the ledger itself — the superseded line, or a cumulative total re-summed from earlier gameweeks — with no flag and no request (`league_history_standings_repaired`); a draft gameweek's table is derivable from `gross_points` alone at the league's start 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` (always present; the partition directory on a first capture, `null` after). Fines are ruled *and recorded* against every captured gameweek, backfilled ones included: the detailed replay rules every configured rule, the coarse tier rules only what cohort points support (`last-place`, `below-threshold`) and records that `red-card` went unruled. Season totals print under `Season Fines` on console and as a `# Season Fines` report section, but only at the two milestone gameweeks (GW19 and the finale) - every other week those surfaces stay a this-week view and `league-fines` answers the season question on demand. The `## Season Fines` prompt section and `metadata.season_fines` are ungated: the editorial gets the running totals every week as optional colour (never a required beat), and a scripted consumer sees no calendar-driven appearance and disappearance. Bottom-half gameweeks and green-arrow droughts no longer surface as streaks at all (their run only drives the season count). Streak conditions also keep reset-surviving season occurrence counts ("their sixth gameweek win of the season"), each surfaced on its own rule (see [Command Reference](../docs/command-reference.md#league-recap) for the per-count table): wins and last places on multiples of 3 plus any second-half first, captain blanks and waiver hauls/backfires on multiples of 5, hits on multiples of 3, weeks on top on multiples of 5, bottom-half gameweeks on multiples of 10 in the second half only, and green-arrow droughts on an unbroken run of 5 or 10 (gameweeks spent leading the league never count towards a drought — the leader could not have climbed). Some counts carry same-week peers alongside the milestone — past a fixed floor for counts that stay small, or within a relative window for counts that climb all season. A firing count reaches the report's `## Season Counts` subsection and the editorial's League History section; at the two milestone gameweeks both surfaces carry every nonzero count as the season set-piece. Every line states its span and any not-judged gameweeks beside the number, and every nonzero count is emitted in `metadata.notes_pack.season_count_entries` regardless. A recap of the most recently finished gameweek keeps working once the next gameweek has started: the league table describes that later gameweek from its deadline onward, so the run takes the replay path (figures from each manager's own gameweek history, positions and cumulative totals derived rather than read off the table) and raises a `league_standings_moved_on` warning, instead of reconciling two different gameweeks' numbers against each other and exiting 1 with the ledger row unwritten. `--summarise` with no usable synthesis key no longer aborts the run: the recap, report and ledger capture all proceed, and a `synthesis_provider_unavailable` warning says the editorial was skipped; an editorial the provider stopped early on is written out as it stands with the reason on stderr and a `synthesis_stopped_early` warning in JSON. A gameweek that cannot be resolved at all exits 1 in both output formats - it used to exit 1 under `--format json` with a generic `"Could not resolve a gameweek to recap."` and 0 in table mode - and the resolver's own reason (`Gameweek 7 is not yet finished`) now reaches the error envelope and the stderr prose alike. On the league's opening gameweek only (GW1, or its own `start_event`), each classic manager's FPL-wide prior-season record — seasons played, last season's points/rank/top-% finish, best season — is fetched from the manager-history endpoint (one request per manager, that gameweek only, and only with `--save`/`--output` or `--summarise`; a league created after GW1 shares the fetch with its league-start offset) and rendered as a `# Prior Seasons` report section and a locked `## Prior Seasons` prompt section with an explicit count; tenure is phrased FPL-wide because entry and league IDs are reissued each season, and a manager with no record or a failed fetch is named as such rather than omitted. Nothing of it reaches the ledger or the JSON rows; draft has no such endpoint and no such section | Yes | General | No | via-agent |
|
|
47
51
|
| `league-fines` | Show who has been fined this season and how much of it was actually ruled, folded out of the league history ledger. Reads local files only - no network - so it works for any season still on disk (`--season 2025-26`). Nothing is re-ruled: a fine is counted exactly as its gameweek recorded it. Counts, never money (`penalty` is free text; settlement and escalation are not modelled). Every gameweek that could not be ruled is named beneath the table - never captured, unreadable, captured before rulings were recorded, captured with no rules configured, or captured at a fidelity that could not rule a given rule. `--format json` emits one entry per recorded manager, fined or not, each with `counts` (zero included), `total`, `fined_gameweeks`, `ruled_gameweeks`, `unruled_gameweeks`, `first_recorded_gameweek`, `last_recorded_gameweek` and `is_fully_ruled`; `metadata` carries `season`, `fpl_format`, `league_id`, `gameweek`, `start_gameweek`, `rule_types`, `total_fines` and `qualifiers`. Exits 1 with the shared error envelope when no league id is configured for the format | Yes | General | No | direct-api |
|
|
48
|
-
| `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 |
|
|
49
|
-
| `differentials` | Find differential picks - high potential, low ownership. Inverted consistency bonus (volatile players score higher, phased in GW6-10). Before GW10 the score's quality baseline carries the prior blend (last season's pedigree weighted by confidence) rather than position-mean shrinkage, so a quiet-starting elite is not ranked below a one-game wonder; the ownership, matchup and consistency bonuses stay pure observation; JSON `metadata.warnings` carries the early-season notice while the blend is live (`early_season_prior_informed`, or `early_season_small_sample` when history could not be loaded), table mode prints
|
|
50
|
-
| `targets` | Find transfer targets - high performers across all ownership. Consistency bonus (CV-xGI percentile, phased in GW6-10). Before GW10 the score's quality baseline carries the prior blend (last season's pedigree weighted by confidence) rather than position-mean shrinkage, so a quiet-starting elite is not ranked below a one-game wonder; the matchup and consistency bonuses stay pure observation, and the score agrees with `fpl transfer-eval`'s Outlook column; JSON `metadata.warnings` carries the early-season notice while the blend is live (`early_season_prior_informed`, or `early_season_small_sample` when history could not be loaded), table mode prints
|
|
52
|
+
| `captain` | Analyse and rank captain options for next gameweek. Your squad is the default, so an absent `classic_entry_id` exits 1 with an `error` envelope naming `--global` rather than returning the global list as though it were your options; `metadata.my_squad_mode` says which list the payload holds, and `metadata.warnings` carries `captain_global_fallback` when there is no squad to rank yet (before the first deadline) or `captain_no_next_gameweek` when there is no next gameweek at all (finished season, or the next one unpublished). An entry ID that does not resolve is a failure, not a fallback, worded exactly as `squad` words it (`get_own_squad_picks`, `agents/common.py`). 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 |
|
|
53
|
+
| `differentials` | Find differential picks - high potential, low ownership. Inverted consistency bonus (volatile players score higher, phased in GW6-10). Before GW10 the score's quality baseline carries the prior blend (last season's pedigree weighted by confidence) rather than position-mean shrinkage, so a quiet-starting elite is not ranked below a one-game wonder; the ownership, matchup and consistency bonuses stay pure observation; JSON `metadata.warnings` carries the early-season notice while the blend is live (`early_season_prior_informed`, or `early_season_small_sample` when history could not be loaded) plus `understat_team_unmatched` for any club Understat carried no players for, table mode prints them to stderr. Players ruled out of the gameweek (0% chance, or no minutes from GW6) are held out of the blend rather than handed last season's standing. The gameweek stamped into JSON `metadata` is fetched after the agent succeeds, not before it runs, so an unreachable API is reported as the agent's failure in both output formats rather than as a client error under `--format json` and an agent error in table mode | Yes | Classic | Yes | via-agent |
|
|
54
|
+
| `targets` | Find transfer targets - high performers across all ownership. Consistency bonus (CV-xGI percentile, phased in GW6-10). Before GW10 the score's quality baseline carries the prior blend (last season's pedigree weighted by confidence) rather than position-mean shrinkage, so a quiet-starting elite is not ranked below a one-game wonder; the matchup and consistency bonuses stay pure observation, and the score agrees with `fpl transfer-eval`'s Outlook column; JSON `metadata.warnings` carries the early-season notice while the blend is live (`early_season_prior_informed`, or `early_season_small_sample` when history could not be loaded) plus `understat_team_unmatched` for any club Understat carried no players for, table mode prints them to stderr. Players ruled out of the gameweek (0% chance, or no minutes from GW6) are held out of the blend rather than handed last season's standing | Yes | Classic | Yes | via-agent |
|
|
51
55
|
| `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. Before GW10 `quality_score` carries the value family's prior blend (matching `fpl player` and `fpl stats --value`) and `target_score` the target family's (matching `fpl targets`); the single-GW `lineup_score` keeps position-mean shrinkage. JSON `metadata.warnings` carries the early-season notice naming both blended columns, or only `target_score` when no Understat match produced a quality score. Players ruled out of the gameweek (0% chance, or no minutes from GW6) are held out of whichever device applies, rather than pulled toward the position mean or handed last season's standing. A name two players answer to exactly (`Henderson`) exits 1 with an `error` envelope naming both, rather than resolving to whichever the API lists first; `Name (TEAM)` or a player ID picks one | Yes | General | Yes | via-agent |
|
|
52
|
-
| `allocate` | Select mathematically optimal 15-player squad within budget. At `--horizon >= 2` the solver ranks on the value family's GW1-9 prior blend (last season's pedigree weighted by confidence); players ruled out of the gameweek (0% chance, or no minutes from GW6) are held out of it rather than handed last season's standing, and stay in the solver pool; JSON `metadata.warnings` carries the early-season notice at `--horizon >= 2` (`early_season_prior_informed`, or `early_season_small_sample` when history could not be loaded) | Yes | Classic | Yes | direct-api |
|
|
53
|
-
| `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. Keeper scores carry the GK signal block (saves/90, defensive quality, clean-sheet rate), read from the main-game player each draft element is joined to on `code` because the draft bootstrap publishes none of it, so draft keepers rank on what separates them rather than on form and ppg alone. Before GW10 the score's quality baseline carries the prior blend (last season's pedigree weighted by confidence) rather than position-mean shrinkage; the matchup, position-need, team-stacking and consistency terms stay pure observation; JSON `metadata.warnings` carries the early-season notice while the blend is live, and reports `early_season_small_sample` both when history could not be loaded and when no draft element found a main-game counterpart to take a prior from. Players ruled out of the gameweek (0% chance, or no minutes from GW6) are held out of the blend rather than handed last season's standing | Yes | Draft | Yes | via-agent |
|
|
54
|
-
| `squad` | Analyse your FPL squad health and fixtures. Exits 1 with an `error` envelope when no entry ID is configured for the active format | Yes | General | No | via-agent |
|
|
55
|
-
| `squad grid` | Show squad fixture difficulty grid. Exits 1 with an `error` envelope when no entry ID is configured for the active format, or when the squad fetch fails. A `--watch` name that does not resolve is non-fatal either way: unmatched, or matching two players exactly, it warns on stderr and is skipped, and the grid still renders | Yes | General | No | via-agent |
|
|
56
|
+
| `allocate` | Select mathematically optimal 15-player squad within budget. At `--horizon >= 2` the solver ranks on the value family's GW1-9 prior blend (last season's pedigree weighted by confidence); players ruled out of the gameweek (0% chance, or no minutes from GW6) are held out of it rather than handed last season's standing, and stay in the solver pool; JSON `metadata.warnings` carries the early-season notice at `--horizon >= 2` (`early_season_prior_informed`, or `early_season_small_sample` when history could not be loaded), plus `understat_team_unmatched` for any club Understat carried no players for, whose squad therefore scored without xG | Yes | Classic | Yes | direct-api |
|
|
57
|
+
| `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. Keeper scores carry the GK signal block (saves/90, defensive quality, clean-sheet rate), read from the main-game player each draft element is joined to on `code` because the draft bootstrap publishes none of it, so draft keepers rank on what separates them rather than on form and ppg alone. Before GW10 the score's quality baseline carries the prior blend (last season's pedigree weighted by confidence) rather than position-mean shrinkage; the matchup, position-need, team-stacking and consistency terms stay pure observation; JSON `metadata.warnings` carries the early-season notice while the blend is live, and reports `early_season_small_sample` both when history could not be loaded and when no draft element found a main-game counterpart to take a prior from. Players ruled out of the gameweek (0% chance, or no minutes from GW6) are held out of the blend rather than handed last season's standing. No `draft_league_id` configured exits 1 in both output formats — table mode used to warn and exit 0 | Yes | Draft | Yes | via-agent |
|
|
58
|
+
| `squad` | Analyse your FPL squad health and fixtures. Exits 1 with an `error` envelope when no entry ID is configured for the active format. `--classic` / `--draft` (mutually exclusive) pin the format instead of inferring it from the configured IDs, so a caller that means classic gets the missing-ID error rather than the draft roster; with neither, single-format configs still auto-select. A picks 404 is checked against `entry/<id>/` before it is reported as "no squad submitted yet", so a reissued or wrong entry ID says so; `squad grid` and `captain` share that diagnosis through `agents/common.py` | Yes | General | No | via-agent |
|
|
59
|
+
| `squad grid` | Show squad fixture difficulty grid. Exits 1 with an `error` envelope when no entry ID is configured for the active format, or when the squad fetch fails. Takes the same `--classic` / `--draft` pinning as `squad`, before or after the subcommand, and the same stale-entry diagnosis. A `--watch` name that does not resolve is non-fatal either way: unmatched, or matching two players exactly, it warns on stderr and is skipped, and the grid still renders | Yes | General | No | via-agent |
|
|
56
60
|
| `squad sell-prices` | Show squad sell prices and financial breakdown. Nothing cached and no `--refresh`, a failed scrape, draft format, or a scrape the extraction heuristics distrust each exit 1 with an `error` envelope under `--format json`; table mode still renders a suspect scrape under a `Squad Budget (Suspect)` panel, since a reader can judge it and `fpl allocate --sell-prices` cannot | Yes | Classic | No | direct-api |
|
|
57
61
|
| `chips` | View and plan FPL chip usage | Yes | Classic | No | direct-api |
|
|
58
|
-
| `chips timing` | Recommend chip timing based on blank/double GW exposure | Yes | Classic | No | via-agent |
|
|
62
|
+
| `chips timing` | Recommend chip timing based on blank/double GW exposure. No `classic_entry_id` configured, and no completed gameweek to measure exposure against, each exit 1 in both output formats — table mode used to print the reason and exit 0. The missing-ID message is the shared one `fpl squad` and `fpl squad grid` give, and a failed fixture agent reports the agent's own reason in the same words either way (`Fixture agent failed: <reason>`), where the envelope and the terminal used to word it differently and neither passed the reason on | Yes | Classic | No | via-agent |
|
|
59
63
|
| `chips add` | Plan a chip for a gameweek | No | Classic | No | direct-api |
|
|
60
64
|
| `chips remove` | Remove a planned chip from a gameweek | No | Classic | No | direct-api |
|
|
61
65
|
| `chips sync` | Sync chip usage from FPL API | No | Classic | No | direct-api |
|
|
62
66
|
| `ratings` | Display team ratings | No | General | Yes | direct-api |
|
|
63
|
-
| `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 |
|
|
67
|
+
| `ratings update` | Recalculate ratings from fixture results (shrunk toward the previous-season prior before GW12; seeds from the prior when no results exist yet). Wherever it applies the prior it names the clubs rated from Championship results or the flat promoted estimate — with the remedy attached where no `FOOTBALL_DATA_API_KEY` is set — and points at `team_ratings_prior.yaml`, whose `inputs` block records each club's basis, matches and the rates it was ranked on. The prior rebuilds itself when an input it went without becomes available; `--refresh-prior` forces it | No | General | Yes | direct-api |
|
|
64
68
|
| `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 |
|
|
65
69
|
| `intel schema` | Print the preview file format with every field explained | No | General | No | direct-api |
|
|
66
70
|
| `intel init` | Scaffold an empty preview file per Premier League team (`--force` overwrites). Stubs never count toward coverage | No | General | No | direct-api |
|
|
@@ -92,6 +96,7 @@ Skills write their own outputs (`gw{N}-recommendations.md`, `gw{N}-squad-builder
|
|
|
92
96
|
| 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 |
|
|
93
97
|
| 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) |
|
|
94
98
|
| 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 |
|
|
99
|
+
| build-test-plan | `skills/build-test-plan/` | Build a runnable test plan for the user-facing changes shipped between two releases, written for a live-data session in the fpl-workspace vault (deep per-change research, carries forward the previous plan's BLOCKED/FAIL items) | Full: Claude Code (parallel research sub-agents). Partial: Codex, Cursor, Copilot |
|
|
95
100
|
|
|
96
101
|
## Analysis Agents
|
|
97
102
|
|
|
@@ -116,11 +121,13 @@ Python classes in `fpl_cli/agents/` that implement `async run(context) -> AgentR
|
|
|
116
121
|
- Every scoring family holds players known not to be playing out of its GW1-9 early-season device — the prior blend for the value family (`quality_score`, `fpl allocate`) and the ownership family (`targets`, `differentials`, `waivers`, transfer-eval's Outlook), position-mean shrinkage for the single-GW family (`captain`, `bench-order`, `starting-xi`, transfer-eval's This GW); bench and lineup order on raw scores, so for those two it changes the displayed score only
|
|
117
122
|
- TransferEvalAgent is used by both `transfer-eval` CLI command and gw-prep skill
|
|
118
123
|
- `bench_order.py`, `starting_xi.py` and `transfer_eval.py` (`.agents/skills/gw-prep/scripts/`) take player names, accept the `Name (TEAM)` disambiguator, and exit 1 naming both candidates when a name matches two players exactly (Dean and Jordan Henderson are both `Henderson`) rather than resolving to whichever the API lists first. Pass `Name (CLUB)` from the squad grid's `player`/`team` pair (issue #180). All three share that resolution with the `transfer-eval` CLI command through `resolve_player_or_report` / `resolve_players_or_report` (`fpl_cli/models/player.py`)
|
|
124
|
+
- Every gw-prep script that runs an agent (`bench_order.py`, `starting_xi.py`, `transfer_eval.py`) runs it inside `json_output_mode()`, so a phase can parse stdout from byte 0 on the success path as well as the failure one. Until #226 the agent's two progress lines landed ahead of the payload and only the error envelopes were clean — the reverse of what a lenient reader would guess. `Agent.log` now writes to stderr for every agent, so the same holds for `fpl transfer-eval --format json`; `tests/test_cli_json_contract.py` guards all four
|
|
119
125
|
- `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.
|
|
120
126
|
- 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`)
|
|
121
127
|
- gw-prep Phase B1 and squad-builder Phase B each run the `fpl fdr` analysis call alongside `fpl fdr --blanks --format json`, and read the quality signals both return -- `data.ratings_warning`, `metadata.custom_analysis`, the blanks payload's `metadata.warnings`, plus `data.predictions_stale` / `data.prediction_warnings` where `--my-squad` resolved a squad. `--blanks` bypasses the fixture agent for a schedule-only payload carrying none of the analysis and none of those fields, so the skills that issued only it were promising pFDR they never received (gw-prep's pFDR Overview, squad-builder's per-position `{ATK|DEF} column` blocks) and silently accepting whatever rating quality they got (issue #135). Whatever fires becomes `data_caveat`, inlined into every sub-agent prompt and written to the output's Data Quality section, messages quoted verbatim so the remedy they name (`fpl ratings update`, `fpl init`) survives. gw-prep passes `--my-squad` (it also wants squad blank/double exposure); squad-builder deliberately does not, since it is replacing the 15 and its season-start modes have no squad to resolve. Enforced by `tests/test_skill_fdr_quality_signals.py`
|
|
122
|
-
-
|
|
128
|
+
- StatsAgent (`fpl xg`, `fpl preview`) clamps its own window and minutes floor to the gameweeks played, scaling the floor by the same 45 minutes per gameweek the skills apply to `fpl stats --min-minutes` below -- off a different anchor, though: the skills scale off `(N - 1)` for the upcoming gameweek, this off the gameweeks the API reports finished, which diverges under a postponement and only downwards. The default 360-minute floor is arithmetically impossible until four gameweeks have finished and out of reach for all but ever-presents for several more, so it emptied every table through the opening weeks (issue #227); the configured bar binds again from 8 finished gameweeks, 10 for `--all`. `data.min_minutes` / `data.gameweeks_played` / `data.window_label` report what was applied, `data.empty_reason` says whether the floor or the data emptied the result, and `warnings` carries `early_season_minutes_floor` while the bar is scaled. A floor a caller passes explicitly (`fpl targets --min-minutes`, `fpl differentials --min-minutes`) is applied verbatim. Every surface renders the reason where its reader is: `fpl xg`, `fpl targets` and `fpl differentials` in place of their tables, `fpl preview` under a Performance Stats heading that now carries the analysed window rather than a hardcoded six gameweeks (console, `templates/gw_preview.md.j2`, and ReportAgent's inline fallback alike). Enforced by `tests/test_stats.py::TestStatsAgentMinutesFloor` and `::TestStatsAgentWindowPrefilter`
|
|
129
|
+
- gw-prep Phase B8 and squad-builder Phase B derive the `fpl stats --min-minutes` floor from the current gameweek (`min(cap, (N - 1) * 45)`, caps 315/450) rather than hardcoding it. A fixed floor is unreachable until GW8/GW11 and empties the result silently -- `fpl stats` returns a well-formed envelope with `"data": []`. Both also rank on `ep_next` before GW6 -- the positional sorts (xGI, form, total_points, ppg) order mostly on sample that early -- and label each block with the sort and floor it used, so a downstream agent knows what ordered it; an empty block is annotated rather than left bare. Both also say what that sort is *not*: before FPL's fixture factor moves off 1.0, `ep_next` tracks `form` almost exactly (at GW4, every row of all four positional shortlists), so the ordering is a form ordering with doubtful players scaled down by chance of playing, and a sub-agent must not be told it is a projection rather than observed output (issue #236). Enforced by `tests/test_skill_stats_thresholds.py`
|
|
123
130
|
- `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; exits 0 whatever the validation finds — the one non-zero exit is the startup guard below.
|
|
124
131
|
- `normalise_entities.py` (`.agents/skills/gw-prep/scripts/`) — decodes HTML-escaped text in a report assembled from sub-agent output and reports the entity references it deliberately leaves alone. JSON stdout; rewrites its `--file` in place; shared by gw-prep Phase D, squad-builder Phase E and update-gw-prep Phase D, all of which warn rather than block on its findings. Contract and warn posture live once, in `.agents/skills/gw-prep/references/entity-normalisation.md`.
|
|
125
|
-
- Every script under `.agents/skills/gw-prep/scripts/` imports the `fpl_cli` package, so it runs on the interpreter fpl-cli is installed on —
|
|
132
|
+
- Every script under `.agents/skills/gw-prep/scripts/` imports the `fpl_cli` package, so it runs on the interpreter fpl-cli is installed on — every SKILL.md that calls one (gw-prep, squad-builder, update-gw-prep) writes that interpreter as `[YOUR_PYTHON]`, and squad-builder/update-gw-prep reach gw-prep's `scripts/` cross-skill via `[YOUR_SKILLS_DIR]` since `${CLAUDE_SKILL_DIR}` resolves to the *calling* skill's own directory (issue #221). A standalone `fpl` on `PATH` (uv tool, pipx) provides the command but not the package; `_bootstrap.py` catches that case ahead of the fpl_cli imports and exits 1 with `{"error": true, "messages": [...]}` on stdout, which the phases parsing that stdout surface as a warning instead of a traceback
|
|
126
133
|
- The extractor and the waiver validator 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,360 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: build-test-plan
|
|
3
|
+
description: >
|
|
4
|
+
Build a runnable test plan covering every user-facing fpl-cli change
|
|
5
|
+
shipped between two releases, written for a Claude Code session in the
|
|
6
|
+
fpl-workspace vault (live FPL API, real leagues, LLM keys). Use whenever
|
|
7
|
+
the user says "build a test plan", "test plan for the release", "what
|
|
8
|
+
needs testing since v2.2", "verify the last release", or asks how to
|
|
9
|
+
check that shipped changes actually work against live data. Also the
|
|
10
|
+
natural follow-up after cutting a release — offer it once a release is
|
|
11
|
+
published.
|
|
12
|
+
model: opus
|
|
13
|
+
compatibility:
|
|
14
|
+
claude-code: full (parallel research sub-agents; GitHub MCP on the web, gh locally)
|
|
15
|
+
codex: partial (sequential research — no sub-agent spawning)
|
|
16
|
+
cursor: partial (sequential research)
|
|
17
|
+
copilot: partial (sequential research)
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
<!-- Runs in the fpl-cli repo; writes into the fpl-workspace vault. -->
|
|
21
|
+
|
|
22
|
+
# Build Test Plan
|
|
23
|
+
|
|
24
|
+
Turn a range of fpl-cli releases into a runbook another Claude Code
|
|
25
|
+
session can execute against live data in the fpl-workspace vault.
|
|
26
|
+
|
|
27
|
+
This skill runs **in the fpl-cli repo**, where the git history, PR bodies,
|
|
28
|
+
diffs and docs live. The plan it writes runs **in fpl-workspace**, which is
|
|
29
|
+
where the FPL credentials, LLM keys, real leagues and generated data are.
|
|
30
|
+
That split is the whole reason the plan has to be written down rather than
|
|
31
|
+
improvised: the session that knows what changed is not the session that can
|
|
32
|
+
exercise it.
|
|
33
|
+
|
|
34
|
+
Two things stay true throughout:
|
|
35
|
+
|
|
36
|
+
- **The plan targets released `fplkit`.** The vault installs the CLI from
|
|
37
|
+
PyPI, so a test written against an unreleased commit on `main` cannot be
|
|
38
|
+
run there. Unreleased work is noted, not tested.
|
|
39
|
+
- **fpl-cli is public; the vault is private.** The plan lives in the vault,
|
|
40
|
+
but write it so it *could* be public: no manager or league names, no
|
|
41
|
+
entry/league IDs. Refer to `classic-<league id>`, "your classic league".
|
|
42
|
+
Real names belong only in the Results log the runner fills in.
|
|
43
|
+
|
|
44
|
+
## Step 1 — Fix the range
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
git fetch origin main --tags
|
|
48
|
+
git tag --sort=-creatordate | head -10
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
The default range is **the end of the last test plan → the newest release
|
|
52
|
+
tag**. Find where the last one stopped:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
ls /home/user/fpl-workspace/docs/test-plans/
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
If the vault isn't cloned, attach `rossgroomio/fpl-workspace` and clone it
|
|
59
|
+
— the plan lands there and the environment facts come from there.
|
|
60
|
+
|
|
61
|
+
Then classify what sits beyond the newest tag:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
git log <newest tag>..origin/main --oneline --no-decorate
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
If those commits are `docs:`/`chore:`/`ci:` only, say so in the plan's
|
|
68
|
+
opening paragraph — it tells the runner that testing the released version
|
|
69
|
+
covers the whole range, which is worth stating because it is not obvious.
|
|
70
|
+
If unreleased *code* sits on main, the honest options are to cut a release
|
|
71
|
+
first or to scope the plan to the released range and list the unreleased
|
|
72
|
+
work as untested. Put that to the user rather than silently picking.
|
|
73
|
+
|
|
74
|
+
Confirm the range with the user before researching. Getting it wrong wastes
|
|
75
|
+
the most expensive step.
|
|
76
|
+
|
|
77
|
+
## Step 2 — Inventory the changes
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
git log <from>..<to> --oneline --no-decorate
|
|
81
|
+
sed -n '/## \[<to version>\]/,/## \[<from version>\]/p' CHANGELOG.md
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Squash-merged subjects carry their PR number as `(#N)` — that is the handle
|
|
85
|
+
for the research step.
|
|
86
|
+
|
|
87
|
+
Keep `feat:`, `fix:`, `refactor:`, `perf:`. Drop `chore:`/`docs:`/`ci:`/
|
|
88
|
+
`test:`/`style:` — but skim their subjects first. A user-visible change
|
|
89
|
+
mislabelled `chore:` is invisible to the changelog *and* to this plan, and
|
|
90
|
+
that is exactly the change nobody thinks to test.
|
|
91
|
+
|
|
92
|
+
## Step 3 — Carry forward the unfinished business
|
|
93
|
+
|
|
94
|
+
Two sources feed this, and a plan that reads only the first will miss work
|
|
95
|
+
the user has already specified.
|
|
96
|
+
|
|
97
|
+
### The previous plan's Results log
|
|
98
|
+
|
|
99
|
+
Three kinds of thing carry forward:
|
|
100
|
+
|
|
101
|
+
- **BLOCKED tests** — deferred because the season state didn't allow them
|
|
102
|
+
("only GW1 complete, defer the replay test"). Check whether the condition
|
|
103
|
+
has since cleared. If it has, the test is now runnable and belongs in the
|
|
104
|
+
new plan verbatim.
|
|
105
|
+
- **FAILs** — check whether the range under test fixes them. Match on PR
|
|
106
|
+
subject and number: a FAIL that a shipped `fix:` claims to address needs
|
|
107
|
+
a test that reproduces the *original* failure and confirms it is gone.
|
|
108
|
+
A FAIL nothing has addressed carries forward unchanged, so it doesn't
|
|
109
|
+
quietly become accepted behaviour.
|
|
110
|
+
- **Post-plan findings** — the observations recorded below the table. These
|
|
111
|
+
are usually the sharpest bugs in the whole document, because they came
|
|
112
|
+
from a human looking at real output. Same treatment as FAILs.
|
|
113
|
+
|
|
114
|
+
This loop is real: most of v2.3.0 exists because the v2.0→v2.2 run found
|
|
115
|
+
those bugs. A plan that doesn't close its predecessor's loop lets fixes ship
|
|
116
|
+
unverified.
|
|
117
|
+
|
|
118
|
+
Carry the *substance* across, not a pointer to it. A runner who has to open
|
|
119
|
+
the previous plan to understand a test has been handed two documents to
|
|
120
|
+
execute instead of one, and mid-run is the worst moment to go looking. So
|
|
121
|
+
state what the old test found and what changed, inline, in the new test.
|
|
122
|
+
|
|
123
|
+
Plans all live together in `docs/test-plans/`, so where provenance is
|
|
124
|
+
genuinely useful — tracing a claim, or flagging that this plan contradicts
|
|
125
|
+
its predecessor — name the file (`v2.0-to-v2.2.md`) and let co-location do
|
|
126
|
+
the rest. No paths, no links.
|
|
127
|
+
|
|
128
|
+
### The vault's open issues
|
|
129
|
+
|
|
130
|
+
The Results log records what a run found. The vault's issue tracker records
|
|
131
|
+
what the user decided to do about it, which is not the same set — and it is
|
|
132
|
+
where deferred testing work actually queues:
|
|
133
|
+
|
|
134
|
+
```
|
|
135
|
+
mcp__github__issue_read / list_issues on rossgroomio/fpl-workspace,
|
|
136
|
+
state OPEN — the `testing` label first, then the rest
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
**Read the comments, not just the body.** A deferred-items issue grows by
|
|
140
|
+
comment as later sessions hit the same wall, and the comments carry the
|
|
141
|
+
best material in the whole tracker: fully-specified tests with the exact
|
|
142
|
+
invocation and expected values, the data state at the time of testing, and
|
|
143
|
+
the condition that would unblock each one. Fold those in close to as
|
|
144
|
+
written — they are better specified than anything drafted fresh, and the
|
|
145
|
+
person who wrote them was looking at real output.
|
|
146
|
+
|
|
147
|
+
Two things to notice while you're there:
|
|
148
|
+
|
|
149
|
+
- **Tests with no home.** Work deferred from a version that never got its
|
|
150
|
+
own plan has nowhere to be logged, and the issue usually says so. The
|
|
151
|
+
plan you are writing is that home if its range covers the change.
|
|
152
|
+
- **Environment blockers.** An open issue about the vault's own setup — a
|
|
153
|
+
broken helper script, a wrong interpreter, a missing key — is not a
|
|
154
|
+
change under test, but it will make a test fail confusingly and get
|
|
155
|
+
misattributed to fpl-cli. Surface those in the preflight.
|
|
156
|
+
|
|
157
|
+
Cite the issue number against each carried test (`carried from
|
|
158
|
+
workspace#34`), and have the wrap-up tell the runner to close those items
|
|
159
|
+
as they are recorded. An issue that stays open after its tests pass is how
|
|
160
|
+
the same work gets queued twice.
|
|
161
|
+
|
|
162
|
+
## Step 4 — Research each change
|
|
163
|
+
|
|
164
|
+
This is where plan quality is won or lost. A test built from a commit
|
|
165
|
+
subject alone reads "verify `fpl doctor --providers` works" and cannot
|
|
166
|
+
fail. A test built from the diff reads "expect all six probes, each
|
|
167
|
+
asserting shape **and** volume: 20 teams, 38 gameweeks, every stat field
|
|
168
|
+
the tool reads present" — and that one catches a regression.
|
|
169
|
+
|
|
170
|
+
**Group before you fan out.** Cluster the commits by the surface they touch
|
|
171
|
+
(league-recap and its ledger; `doctor`; scoring and data quality; JSON
|
|
172
|
+
envelopes). Themes make better research units than individual PRs, because
|
|
173
|
+
several PRs usually move one surface together and the tests want to be
|
|
174
|
+
written against the surface's final state, not each intermediate step.
|
|
175
|
+
|
|
176
|
+
Then spawn one research sub-agent per theme, in a single batch so they run
|
|
177
|
+
concurrently. Brief each one:
|
|
178
|
+
|
|
179
|
+
> Research the fpl-cli changes in <theme> shipped between <from> and <to>,
|
|
180
|
+
> so a tester with a live FPL account can verify them. Commits: <list with
|
|
181
|
+
> PR numbers>.
|
|
182
|
+
>
|
|
183
|
+
> For each change, read: the PR body and any linked issue (via GitHub MCP
|
|
184
|
+
> `pull_request_read` / `issue_read`, or `gh pr view <N>`), the diff
|
|
185
|
+
> (`git show <sha>`), the relevant section of `docs/command-reference.md`,
|
|
186
|
+
> and the tests the PR added under `tests/`.
|
|
187
|
+
>
|
|
188
|
+
> Report back, per change:
|
|
189
|
+
> - The exact CLI invocation that exercises it, flags included.
|
|
190
|
+
> - Falsifiable expectations — specific counts, field names, value ranges,
|
|
191
|
+
> exit codes, which stream output lands on. Name the actual values
|
|
192
|
+
> (how many probes, which warning codes, which JSON keys), not "the
|
|
193
|
+
> documented ones".
|
|
194
|
+
> - The repro from the issue, for a `fix:` — the condition that used to
|
|
195
|
+
> break is what the test recreates.
|
|
196
|
+
> - Degradation and edge paths the change introduces or touches: missing
|
|
197
|
+
> data, corrupt file, unreachable provider, invalid input, exit codes.
|
|
198
|
+
> - Whether the current season state can exercise it at all, and if not,
|
|
199
|
+
> the condition that would unblock it.
|
|
200
|
+
>
|
|
201
|
+
> Ground every specific in a file you actually read. Do not describe
|
|
202
|
+
> behaviour you inferred from a commit subject.
|
|
203
|
+
|
|
204
|
+
Sub-agent findings are a draft, not the plan. Reconcile them yourself:
|
|
205
|
+
drop duplicates where two themes touch the same command, and check that
|
|
206
|
+
the specifics they quote really appear in the docs or diff before those
|
|
207
|
+
numbers become assertions the runner will trust.
|
|
208
|
+
|
|
209
|
+
## What makes a test worth writing
|
|
210
|
+
|
|
211
|
+
The reference plan (`docs/test-plans/v2.0-to-v2.2.md` in the vault) is the
|
|
212
|
+
quality bar. What separates its useful tests from filler:
|
|
213
|
+
|
|
214
|
+
**It can fail.** Every expectation is something that would read differently
|
|
215
|
+
if the fix regressed. "Renders without crashing" is worth one line for a
|
|
216
|
+
crash fix and nothing otherwise; "row count equals the real league size"
|
|
217
|
+
caught a real pagination bug.
|
|
218
|
+
|
|
219
|
+
**It tests the bug, not the fix.** For a `fix:`, recreate the condition
|
|
220
|
+
from the issue and assert the old symptom is absent. Testing that the new
|
|
221
|
+
code path runs tells you nothing about whether the bug is gone.
|
|
222
|
+
|
|
223
|
+
**It uses the observable surface.** The runner has a released binary, not
|
|
224
|
+
the source. Commands, files on disk, exit codes, stdout vs stderr — never
|
|
225
|
+
internal functions.
|
|
226
|
+
|
|
227
|
+
**It writes nothing into the vault the run wouldn't have written anyway.**
|
|
228
|
+
The vault is someone's working record, not a scratch pad. `01_Reports/`
|
|
229
|
+
holds point-in-time snapshots — the gameweek as it was analysed at the
|
|
230
|
+
time — so a test that regenerates one overwrites the record with a later
|
|
231
|
+
model's output, and a test that saves a preview for a gameweek whose
|
|
232
|
+
deadline has not passed creates a file that should not exist yet. Both
|
|
233
|
+
land in a PR as noise the author then has to spot and revert, which has
|
|
234
|
+
happened on more than one run.
|
|
235
|
+
|
|
236
|
+
So: every `--save` you write into a plan carries `-o <scratch>/reports`.
|
|
237
|
+
`preview`, `review` and `league-recap` all take `-o/--output`, and the
|
|
238
|
+
report is just as assertable from a scratch path — you are checking its
|
|
239
|
+
content, not its location. Where a test must assert the *default* path is
|
|
240
|
+
used, assert it on a scratch `HOME`/cwd rather than by writing into the
|
|
241
|
+
real tree.
|
|
242
|
+
|
|
243
|
+
The same discipline covers `data/`: the ledger under
|
|
244
|
+
`data/league_history/` is the one thing worth capturing for real, because
|
|
245
|
+
its rows are append-only and cannot be reconstructed once the season moves
|
|
246
|
+
on. Everything else there — `returnee_snapshot.json`, `team_ratings*.yaml`,
|
|
247
|
+
`team_finances.json` — is regenerable state the CLI rewrites, so a test
|
|
248
|
+
that migrates or rewrites one points `FPL_CLI_DATA_DIR` at a throwaway
|
|
249
|
+
copy. A migration performed as a side effect of a test is not a change
|
|
250
|
+
anyone decided to make, and it reads in review as if it were.
|
|
251
|
+
|
|
252
|
+
The question to ask of every artifact a plan produces: *would this have
|
|
253
|
+
been created anyway, by real use, today?* If no, it goes to scratch.
|
|
254
|
+
|
|
255
|
+
**It builds a fixture before it gives up.** When live data can't reach a
|
|
256
|
+
code path, a scratch fixture often can: point `FPL_CLI_DATA_DIR` at a
|
|
257
|
+
throwaway copy of the store and doctor a row to the shape the bug needed,
|
|
258
|
+
or feed the matching function inputs that disagree. This reaches repair
|
|
259
|
+
and migration paths that a healthy live store never exercises, and it
|
|
260
|
+
costs the runner a `cp -r` they throw away afterwards. Reach for it before
|
|
261
|
+
reaching for BLOCKED — and keep it off the real store, which for the
|
|
262
|
+
ledger is irreplaceable.
|
|
263
|
+
|
|
264
|
+
**It mines the environment for fixtures it already has.** The vault's own
|
|
265
|
+
data is full of them. Players in captured ledger rows who have since
|
|
266
|
+
changed club are a ready-made test that a re-run doesn't restamp recorded
|
|
267
|
+
history — verify what each row *should* say from that gameweek's live
|
|
268
|
+
data, tabulate it, and any drift is an unambiguous FAIL. Look for this
|
|
269
|
+
before inventing setup.
|
|
270
|
+
|
|
271
|
+
**It says when it genuinely can't run.** Some things no fixture reaches:
|
|
272
|
+
a blank gameweek that hasn't happened, a week-over-week comparison with no
|
|
273
|
+
prior week. Write the test anyway, mark it BLOCKED with the condition that
|
|
274
|
+
unblocks it, and the next plan picks it up via Step 3. A test that
|
|
275
|
+
silently can't run is worse than one marked BLOCKED, because it looks like
|
|
276
|
+
coverage.
|
|
277
|
+
|
|
278
|
+
**It covers degradation.** Missing config, corrupt store, provider down,
|
|
279
|
+
bad gameweek number. Make corruption tests reversible and say how to
|
|
280
|
+
restore (`git checkout -- <path>`) — the runner is working on real data
|
|
281
|
+
they want to keep.
|
|
282
|
+
|
|
283
|
+
**It names the channel.** This tool's JSON contract has moved between
|
|
284
|
+
stdout and stderr more than once. Where a test asserts on output, say which
|
|
285
|
+
stream, and have the runner capture stderr separately
|
|
286
|
+
(`2>/tmp/<id>-err.txt`).
|
|
287
|
+
|
|
288
|
+
**It earns its place.** Plan length tracks the range. Two releases and
|
|
289
|
+
twenty user-facing changes justify something like the reference plan's
|
|
290
|
+
~35 tests across four parts; a patch-only range with four fixes wants six
|
|
291
|
+
or seven. Padding a small range with smoke tests of untouched commands
|
|
292
|
+
buys no coverage and teaches the runner to skim.
|
|
293
|
+
|
|
294
|
+
## Step 5 — Order the parts
|
|
295
|
+
|
|
296
|
+
Group tests into parts by surface, then order the parts by **what the user
|
|
297
|
+
gets from running them**, not by release order:
|
|
298
|
+
|
|
299
|
+
1. Anything that produces artifacts worth having this week (a recap, a
|
|
300
|
+
ledger capture) goes first, because a plan often gets run across two
|
|
301
|
+
sessions and the first session should produce the thing with a deadline.
|
|
302
|
+
2. Health checks next (`fpl doctor`) — they surface environment problems
|
|
303
|
+
that would otherwise show up as confusing failures later.
|
|
304
|
+
3. Everything else, grouped by surface.
|
|
305
|
+
|
|
306
|
+
Say the ordering rationale in the preamble. A runner who understands why
|
|
307
|
+
Part 1 is first will keep it first when the session runs short.
|
|
308
|
+
|
|
309
|
+
Number tests with a per-part letter prefix (`P1`–`P4` preflight, `R1`–`R10`
|
|
310
|
+
recap, `D1`–`D4` doctor…) so the Results log stays readable and a follow-up
|
|
311
|
+
session can reference a single test unambiguously.
|
|
312
|
+
|
|
313
|
+
## Step 6 — Write the plan
|
|
314
|
+
|
|
315
|
+
Read `references/plan-template.md` for the exact document shape, and
|
|
316
|
+
`references/workspace-env.md` for the environment facts that go into the
|
|
317
|
+
preflight and the cost/safety notes. Write the plan to:
|
|
318
|
+
|
|
319
|
+
```
|
|
320
|
+
/home/user/fpl-workspace/docs/test-plans/v<from>-to-<to>.md
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
Name it for the range's ends. Trim a trailing `.0` only when the two ends
|
|
324
|
+
differ in major.minor — `v2.2.0`→`v2.4.0` becomes `v2.2-to-v2.4.md`,
|
|
325
|
+
matching `v2.0-to-v2.2.md`. When both ends sit in the same minor, keep
|
|
326
|
+
full patch versions on both, or the name reads as a range it isn't
|
|
327
|
+
(`v2.3.0`→`v2.3.2` is `v2.3.0-to-v2.3.2.md`, never `v2.3-to-v2.3.2.md`).
|
|
328
|
+
|
|
329
|
+
Pre-seed the Results log with one empty row per test ID. It is the runner's
|
|
330
|
+
worksheet and the next plan's Step 3 input, so every ID needs a row waiting
|
|
331
|
+
for it.
|
|
332
|
+
|
|
333
|
+
Then add a **coverage map**: every in-range PR number against the test IDs
|
|
334
|
+
that exercise it. Write it by walking the Step 2 inventory, not by
|
|
335
|
+
summarising what you already wrote — the point is to catch the change you
|
|
336
|
+
researched and then lost track of while grouping. A PR with no test ID
|
|
337
|
+
next to it is either a gap to fill or a deliberate omission to justify in
|
|
338
|
+
one line, and either way the runner can see it. This is cheap to produce
|
|
339
|
+
and the only mechanism that makes coverage auditable from the artifact
|
|
340
|
+
itself.
|
|
341
|
+
|
|
342
|
+
Vault convention: no blank lines before or after headings.
|
|
343
|
+
|
|
344
|
+
## Step 7 — Land it
|
|
345
|
+
|
|
346
|
+
Commit the plan in the vault on its own branch:
|
|
347
|
+
|
|
348
|
+
```bash
|
|
349
|
+
cd /home/user/fpl-workspace
|
|
350
|
+
git checkout -b claude/test-plan-v<to>
|
|
351
|
+
git add docs/test-plans/<file>
|
|
352
|
+
git commit -m "docs: add v<from> to v<to> test plan"
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
Ask before pushing — the vault is the user's own repo and they may want to
|
|
356
|
+
read the plan first.
|
|
357
|
+
|
|
358
|
+
Then summarise here: the range, how many tests in how many parts, which
|
|
359
|
+
carried forward from the last plan, and anything you had to mark BLOCKED
|
|
360
|
+
on season state so the user knows what the run won't cover.
|