fplkit 2.10.0__tar.gz → 2.11.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.10.0 → fplkit-2.11.0}/.agents/README.md +2 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/.agents/TOOLS.md +3 -2
- fplkit-2.11.0/.agents/skills/ab-brief/SKILL.md +401 -0
- fplkit-2.11.0/.agents/skills/ab-brief/references/brief-template.md +65 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/.agents/skills/create-pr/SKILL.md +16 -1
- {fplkit-2.10.0 → fplkit-2.11.0}/.agents/skills/release/SKILL.md +1 -1
- {fplkit-2.10.0 → fplkit-2.11.0}/.claude/output-styles/fpl-mate.md +2 -2
- {fplkit-2.10.0 → fplkit-2.11.0}/.github/workflows/ci.yml +1 -1
- {fplkit-2.10.0 → fplkit-2.11.0}/AGENTS.md +7 -6
- {fplkit-2.10.0 → fplkit-2.11.0}/CHANGELOG.md +24 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/CLAUDE.md +7 -6
- {fplkit-2.10.0 → fplkit-2.11.0}/CONTRIBUTING.md +7 -2
- {fplkit-2.10.0 → fplkit-2.11.0}/PKG-INFO +37 -4
- {fplkit-2.10.0 → fplkit-2.11.0}/README.md +35 -3
- {fplkit-2.10.0 → fplkit-2.11.0}/docs/architecture.md +23 -11
- {fplkit-2.10.0 → fplkit-2.11.0}/docs/command-reference.md +321 -25
- {fplkit-2.10.0 → fplkit-2.11.0}/docs/fpl-rules.md +1 -1
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/_version.py +2 -2
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/agents/orchestration/report.py +36 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/api/fpl.py +29 -1
- fplkit-2.11.0/fpl_cli/api/premier_league.py +231 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/cli/_fines.py +1 -1
- fplkit-2.11.0/fpl_cli/cli/_fines_config.py +391 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/cli/_league_recap_data.py +385 -140
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/cli/_league_recap_history.py +102 -1
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/cli/_league_recap_types.py +418 -24
- fplkit-2.11.0/fpl_cli/cli/_league_recap_vouchers.py +453 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/cli/doctor.py +1 -1
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/cli/doctor_providers.py +99 -6
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/cli/init.py +43 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/cli/league_recap.py +411 -14
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/config/defaults.yaml +15 -3
- fplkit-2.11.0/fpl_cli/constants.py +11 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/models/chip_plan.py +4 -4
- fplkit-2.11.0/fpl_cli/models/season_month.py +67 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/prompts/league_recap.py +323 -23
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/prompts/league_recap_checks.py +1682 -147
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/season.py +26 -1
- fplkit-2.11.0/fpl_cli/services/league_history_months.py +355 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/services/league_history_notes.py +436 -65
- fplkit-2.11.0/fpl_cli/services/league_history_vouchers.py +419 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/templates/gw_league_recap.md.j2 +49 -4
- fplkit-2.11.0/fpl_cli/utils/ranking.py +27 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/pyproject.toml +1 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/conftest.py +88 -1
- fplkit-2.11.0/tests/fixtures/premier_league_awards/season-2025.json +1406 -0
- fplkit-2.11.0/tests/fixtures/premier_league_awards/season-2026.json +260 -0
- fplkit-2.11.0/tests/test_ab_brief_skill.py +60 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_api_client.py +34 -0
- fplkit-2.11.0/tests/test_api_premier_league.py +176 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_cli_chips.py +4 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_cli_doctor_providers.py +164 -1
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_cli_league_recap.py +1145 -7
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_cli_review.py +5 -4
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_cli_status.py +1 -1
- fplkit-2.11.0/tests/test_fines_config.py +477 -0
- fplkit-2.11.0/tests/test_harness.py +25 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_init_fines.py +94 -0
- fplkit-2.11.0/tests/test_league_history_months.py +557 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_league_history_notes.py +424 -7
- fplkit-2.11.0/tests/test_league_history_vouchers.py +789 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_league_recap.py +3147 -65
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_season.py +50 -0
- fplkit-2.10.0/fpl_cli/cli/_fines_config.py +0 -146
- fplkit-2.10.0/fpl_cli/constants.py +0 -3
- fplkit-2.10.0/tests/test_fines_config.py +0 -224
- {fplkit-2.10.0 → fplkit-2.11.0}/.agents/skills/build-test-plan/SKILL.md +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/.agents/skills/build-test-plan/references/plan-template.md +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/.agents/skills/build-test-plan/references/workspace-env.md +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/.agents/skills/gw-prep/SKILL.md +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/.agents/skills/gw-prep/references/entity-normalisation.md +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/.agents/skills/gw-prep/references/output-template.md +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/.agents/skills/gw-prep/references/rules.md +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/.agents/skills/gw-prep/scripts/_bootstrap.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/.agents/skills/gw-prep/scripts/bench_order.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/.agents/skills/gw-prep/scripts/claimable_returnees.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/.agents/skills/gw-prep/scripts/extract_classic_squad.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/.agents/skills/gw-prep/scripts/normalise_entities.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/.agents/skills/gw-prep/scripts/starting_xi.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/.agents/skills/gw-prep/scripts/transfer_eval.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/.agents/skills/gw-prep/scripts/validate_draft_waivers.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/.agents/skills/preview-ingest/SKILL.md +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/.agents/skills/release-notes/SKILL.md +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/.agents/skills/squad-builder/SKILL.md +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/.agents/skills/squad-builder/references/output-template.md +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/.agents/skills/squad-builder/references/rules.md +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/.agents/skills/update-gw-prep/SKILL.md +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/.claude/settings.json +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/.env.example +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/.github/workflows/pr-title.yml +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/.github/workflows/release.yml +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/.gitignore +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/LICENSE +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/cliff.toml +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/config/team_ratings_overrides.yaml +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/docs/custom-analysis.md +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/docs/images/fpl-player-demo-v1-0.png +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/__init__.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/agents/__init__.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/agents/action/__init__.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/agents/action/waiver.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/agents/analysis/__init__.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/agents/analysis/bench_order.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/agents/analysis/captain.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/agents/analysis/squad_analyzer.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/agents/analysis/starting_xi.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/agents/analysis/stats.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/agents/analysis/transfer_eval.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/agents/base.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/agents/common.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/agents/data/__init__.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/agents/data/fixture.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/agents/data/price.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/agents/data/scout.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/agents/orchestration/__init__.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/api/__init__.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/api/contract.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/api/core_insights.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/api/dataset_fetcher.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/api/football_data.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/api/fpl_draft.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/api/historical.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/api/historical_types.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/api/providers/__init__.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/api/providers/_http.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/api/providers/_models.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/api/providers/anthropic.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/api/providers/openai_compat.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/api/providers/perplexity.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/api/understat.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/api/vaastav.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/cli/__init__.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/cli/_banner.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/cli/_context.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/cli/_draft_provenance.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/cli/_gameweek_status.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/cli/_helpers.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/cli/_json.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/cli/_plan_grid.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/cli/_review_analysis.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/cli/_review_classic.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/cli/_review_draft.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/cli/_review_summarisation.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/cli/_synthesis_retry.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/cli/allocate.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/cli/captain.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/cli/chips.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/cli/credentials.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/cli/differentials.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/cli/fdr.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/cli/fixtures.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/cli/history.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/cli/intel.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/cli/league.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/cli/league_fines.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/cli/player.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/cli/preview.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/cli/price_changes.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/cli/price_history.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/cli/ratings.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/cli/returnees.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/cli/review.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/cli/sell_prices.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/cli/squad.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/cli/stats.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/cli/status.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/cli/targets.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/cli/transfer_eval.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/cli/waivers.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/cli/xg.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/config/fixture_predictions.yaml +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/config/previews/EXAMPLE.yaml +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/config/team_managers.yaml +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/models/__init__.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/models/fixture.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/models/gameweek_status.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/models/league_history.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/models/player.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/models/team.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/models/types.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/parsers/__init__.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/parsers/recommendations.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/paths.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/prompts/__init__.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/prompts/returnees.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/prompts/review.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/prompts/scout.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/scraper/__init__.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/scraper/fpl_prices.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/services/__init__.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/services/draft_provenance.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/services/draft_rebuild_cache.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/services/fixture_predictions.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/services/league_history.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/services/league_history_counters.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/services/league_history_drafts.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/services/league_history_fines.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/services/matchup.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/services/player_clubs.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/services/player_prior.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/services/returnee_radar.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/services/scoring/__init__.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/services/scoring/constants.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/services/scoring/coverage.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/services/scoring/data_prep.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/services/scoring/display.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/services/scoring/evaluation.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/services/scoring/ownership.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/services/scoring/shrinkage.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/services/scoring/signals.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/services/scoring/single_gw.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/services/scoring/value_quality.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/services/season_previews.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/services/sell_prices.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/services/squad_allocator.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/services/team_form.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/services/team_ratings.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/services/team_ratings_prior.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/templates/gw_preview.md.j2 +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/templates/gw_review.md.j2 +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/utils/__init__.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/utils/files.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/utils/gameweek.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/utils/markdown.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/utils/teams.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/utils/text.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/fpl_cli/utils/time.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/pyrightconfig.json +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/scripts/backtest_early_season_prior_blend.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/scripts/calibrate_promoted_prior.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/scripts/calibrate_quality_ceilings.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/__init__.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/fixtures/classic_squad_fixture.md +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/fixtures/recommendations_fixture.md +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/fixtures/validate_draft_waivers/recs_clean.md +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/fixtures/validate_draft_waivers/squad_grid.json +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/fixtures/validate_draft_waivers/waivers.json +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/fixtures/waiver_ranked_golden.json +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_agents_action.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_agents_analysis.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_agents_analysis_squad.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_agents_base.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_agents_data.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_agents_data_price.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_agents_orchestration.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_allocate.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_api_core_insights.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_api_dataset_fetcher.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_api_draft.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_api_football_data.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_api_historical.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_api_understat.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_api_vaastav.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_bench_order_script.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_calibrate_quality_ceilings.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_claimable_returnees.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_classic_squad_extraction.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_cli_allocate.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_cli_banner.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_cli_captain.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_cli_context_settings.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_cli_credentials.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_cli_custom_analysis.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_cli_differentials.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_cli_doctor.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_cli_draft_provenance.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_cli_failure_parity.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_cli_failure_streams.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_cli_fdr.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_cli_fdr_blanks.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_cli_fixtures.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_cli_format.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_cli_format_suppression.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_cli_fpl_block.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_cli_history.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_cli_init.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_cli_intel.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_cli_json.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_cli_json_contract.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_cli_league.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_cli_league_fines.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_cli_notice_streams.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_cli_plan_grid.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_cli_player.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_cli_preview.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_cli_price_changes.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_cli_price_history.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_cli_returnees.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_cli_sell_prices.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_cli_settings_warning.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_cli_squad.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_cli_stats.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_cli_synthesis_retry.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_cli_targets.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_cli_transfer_eval.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_cli_waivers.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_cli_warnings_stderr.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_cli_xg.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_common.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_common_scoring.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_data_prep_harmonisation.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_dependencies.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_doc_consistency.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_draft_provenance.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_fines.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_fixture_predictions.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_formation_guidance_sync.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_fpl_prices.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_gameweek_status.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_gw_prep_script_bootstrap.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_init.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_league_history_counters.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_league_history_drafts.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_league_history_fines.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_league_history_store.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_matchup_service.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_models.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_normalise_entities.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_output_style_sync.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_paths.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_player_clubs.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_player_prior.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_player_resolution.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_preseason.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_prompt_tables.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_providers.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_recommendations_parser.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_report_agent.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_report_paths.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_returnee_radar.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_review.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_rolling_pts_per_m.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_scoring.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_scoring_coverage.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_scraper.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_season_awareness.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_season_previews.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_sell_prices.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_sell_prices_service.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_skill_effort_levels.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_skill_fdr_quality_signals.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_skill_stats_thresholds.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_squad_allocator.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_starting_xi_agent.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_starting_xi_script.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_stats.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_team_ratings.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_team_ratings_prior.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_transfer_eval_agent.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_transfer_eval_script.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_utils_gameweek.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_utils_markdown.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_utils_text.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_utils_time.py +0 -0
- {fplkit-2.10.0 → fplkit-2.11.0}/tests/test_validate_draft_waivers.py +0 -0
|
@@ -34,6 +34,8 @@ The four product skills (gw-prep, squad-builder, update-gw-prep, preview-ingest)
|
|
|
34
34
|
|
|
35
35
|
These levels are a judgement, not a measurement. `max` is untested here and probably not worth its time and cost; a gameweek of gw-prep run twice, once at these levels and once with classic at `max`, is the experiment that would settle it. Change a level in the block and in its row together: `tests/test_skill_effort_levels.py` reads each skill's frontmatter and every `Agent tool parameters` block, and holds each row of this table to the block its Where cell names (`orchestrator` is the frontmatter; the other labels are the sub-agent blocks in document order), so a swapped, missing or misspelt row fails there rather than being dropped.
|
|
36
36
|
|
|
37
|
+
The maintainer skills (release, release-notes, create-pr, build-test-plan, ab-brief) set no effort level and are not held to this table: they run once per release or PR, interactively, and follow the session's effort. ab-brief is the exception worth knowing, since the effort that matters is the vault session's, not its own, and it says in its brief which level that session should start at.
|
|
38
|
+
|
|
37
39
|
How far each key reaches, quoted from the Claude Code docs as read on 14 October 2026:
|
|
38
40
|
|
|
39
41
|
- `model` in the frontmatter ([skills, Frontmatter reference](https://code.claude.com/docs/en/skills#frontmatter-reference)): "The override applies for the rest of the current turn and isn't saved to settings. The session model resumes when you send your next prompt." So a follow-up question about the output ("why sell X?") is answered on the session model.
|
|
@@ -42,7 +42,7 @@ see [Feed coverage warnings](../docs/command-reference.md#json-output).
|
|
|
42
42
|
|---------|-------------|------|--------|-------------|---------|
|
|
43
43
|
| `init` | Set up fpl-cli with your FPL IDs and optional features | No | General | No | direct-api |
|
|
44
44
|
| `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. `data.gameweek_info` carries `finished` beside `bonus_confirmed` and `league_tables_updated`, read from FPL's processing feed (`event-status`) for each game shown (the classic one alone when no format is configured): `true` only when every game's feed says so, `false` as soon as one says otherwise, and `null` otherwise - always `null` while the gameweek is in progress (the feed can report the days played so far as settled), and also whenever some game's feed could not answer and none said no, so with both formats `null` can mean one confirmed and the other was unreadable. `finished` comes hours before the league-table refresh (issue #424), so a skill gating a league recap should wait on `league_tables_updated` being `true`, and table mode prints both beside "Finished" | Yes | General | No | direct-api |
|
|
45
|
-
| `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 (including that the draft game's club list agrees with the main game's by club code, since every draft path translates club ids through it), the vaastav and Core-Insights datasets, Understat,
|
|
45
|
+
| `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 (including that the draft game's club list agrees with the main game's by club code, since every draft path translates club ids through it), the vaastav and Core-Insights datasets, Understat, football-data.org, and the Premier League's awards feed the draft Player of the Month voucher reads (run through the recap's own parser, with its winners joined on FPL codes), 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, or waits on an upstream fix the tool works around). Exits non-zero when something is broken | Yes | General | No | direct-api |
|
|
46
46
|
| `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 |
|
|
47
47
|
| `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)`. Under a draft league (format not classic) the `Draft:` line says how the player came to stand where he does, from a replay of the league's draft, waivers and trades (issue #460): `Owned by Alice (round-3 pick)`, `Owned by Bob (GW4 waiver pickup; Cam's round-5 pick)`, `Available (released by Dan in GW5; Dan's round-9 pick)`, `Available (undrafted)`, round unknown where the draft's picks are lost, with a `Draft history:` line beneath when the chain has a step the line does not name; JSON keeps `info.draft_ownership` and adds `info.draft` (owner, acquisition, gameweek, drafter, round/pick, counterparty, full `history`), null with a `draft_provenance_unavailable` warning when the history cannot be shown complete | Yes | General | No | direct-api |
|
|
48
48
|
| `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. Under a draft league (format not classic) the table adds a Draft column: the owner, tagged with how they came by him (`R3` round-3 pick, `GW4 W` waiver, `GW9 FA` free agent, `GW7 T` trade; issue #460), or `Available`; table only, JSON carries no draft column | Yes | General | No | direct-api |
|
|
@@ -55,7 +55,7 @@ see [Feed coverage warnings](../docs/command-reference.md#json-output).
|
|
|
55
55
|
| `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 |
|
|
56
56
|
| `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 |
|
|
57
57
|
| `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 and grades the captain, each transfer and each waiver or free-agent move against it; transfer rows that sell the same player are read as ranked alternatives for one sale and graded once, as followed if any candidate was bought and as one miss otherwise; waiver rows that share a drop are read as one conditional chain, each claim keeping its own row: the move made is credited to the claim it filled wherever that claim sits in the chain, a claim below the one that landed is marked not needed rather than not executed, a claim skipped above one that was made is a miss, and a chain abandoned part-way is one miss where it was left, the claims below that marked as fallbacks not reached; the captain line sets the recommended captain against the armband in captain points (raw score times that week's multiplier, Triple Captain included), the armband being the vice's when the captain played no minutes, and adds the change to the team total when the recommended captain was in the side that scored; one who was not in the squad or played no minutes is labelled so rather than priced at 0; on a wildcard or free hit week, where the recommendation is a squad, each player brought in is graded against the recommended 15 instead, and a recommended name that fits more than one player is left ungraded with a warning on stderr rather than matched to the first; a part of the file it could not read (the captain, the transfers or the waivers) is left ungraded the same way, the warning naming the file, rather than grading every move unadvised, and a file nothing could be read from is not compared at all. 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 cut off at its token ceiling is retried once, told what the last draft lacked (a refused answer is flagged but never retried, and a retry that errors keeps the first attempt), and a still-incomplete one is warned about on stderr (detail in `data/debug/synthesis_corrections.txt` under `--debug`, where `synthesis_meta.json` also records how many answers were judged, which was kept, and the tokens they spent between them) 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 |
|
|
58
|
-
| `league-recap` | Recap a completed gameweek - awards, standings, and banter. Saves to `<output dir>/<season>/gw{N}-league-recap[-draft].md`, opening on a fixed `# Gameweek N Recap: <league name>` title the report writes itself, editorial or not; the editorial is asked for a `## ` headline beneath it and never a title, and whatever heading it opens with anyway is demoted to that level or, when it only restates the title, dropped, so the model's heading is never the document's (issue #349). Records every run into the league history ledger (`<data dir>/league_history/`) as a side effect, for draft alongside each held draft's picks (`drafts/draft-{id}.json` in the draft partition, saved once while the API still serves them, since a scheduled redraft drops them for good; `league_history_draft_picks_conflict`, `_lost` (once per draft), `_unsaved` and `_unchecked` name a disagreement with the API, a draft held before anything saved it, a run that could not save, and a `league.drafts` list it could not read), 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 last superseded line that recorded them (a gameweek captured repeatedly while it was still settling holds a stale position in its first capture and the settled one in its last), 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. A draft row also records the waiver claims its manager lost to a rival (`lost_claims`, kept apart from `transactions` because nothing moved — no points, no net, and never the denial that is the cascade of the manager's own accepted claim), so an outbid manager is not written into the permanent record as one who never tried; a gameweek captured before that field existed reads it back as unrecorded rather than as "lost none", is named by the coverage report, and is re-recorded by `--backfill-detail` while the league's transaction feed still serves it; a replay that finds fewer claims than the gameweek recorded keeps the recorded list (`league_history_claims_carried`). Those claims also make the waiver wire's races visible (issue #330): the editorial's waiver roster closes with every player more than one manager claimed, who won him and who was beaten to him at what priority, and a week where at least three managers claimed the same player (`MOST_CONTESTED_MIN_CLAIMANTS`) and one of them can be seen to have won him gets a Most Contested award beside Waiver Genius and Waiver Disaster — derived from the manager rows by `contested_draft_claims()`, never recorded, never a report section of its own, and absent from `--format json`, whose rows already carry the `lost_claims` and `transactions` they are regrouped from. The editorial is told to write each race as its own clause and never fold two into one ("respectively"), and is checked against the races afterwards (issue #357): a manager it names as beaten to, or winning, a contested player in a role that player's line does not give them triggers one regeneration told which claims were wrong (a retry that errors keeps the first attempt), and a kept editorial that still fails is saved as written with the wrong claims named on stderr, in a `synthesis_contested_misattribution` JSON warning, and in a callout above the editorial in the report. The same retry and reporting cover four checks on either format: a manager's net overall, pre-hit swing or another move's swing pinned on one named move (`synthesis_net_misattribution`, issue #359), a tie word ("joint-lowest", "tied for last", "joint-seventh") on a gameweek score or league position no two managers hold, or on the season's highest or lowest score where its season record was scored only once (`synthesis_unsupported_tie`, issues #359 and #494), a placing on the week the manager's gameweek score does not earn, said of the week ("third on the week") or set beside that score ("finished eighth on 31", a league position read as the week's finish) (`synthesis_wrong_gameweek_placing`, issue #486), and an escape from last place given to a manager who finished there ("He escaped the wooden spoon" of the week's lowest scorer, or "clear of the foot of the table" of the manager bottom of it) (`synthesis_escape_misattribution`, issue #488); and a fourth on draft (issue #390): the net overall, or the other kind's figure, said of one kind of a manager's moves ("his waiver moves netted +3 overall" of a -1 waiver and a +4 free-agent pickup, `synthesis_kind_net_misattribution`), for which a roster line that mixes kinds now gives each kind's net beside the net overall. A fifth, on draft (issue #459), holds the editorial to not reciting a race as a list: a paragraph naming the race's player and three or more of its beaten managers (`synthesis_contested_roll_call`) earns the same retry and reporting, but as a broken brief rather than a contradiction of the data, since the sentence is true, so the retry, stderr and the report callout say that instead. The prompt's GW Standings section names the week's highest and lowest scores, every shared score and position (Pos, and Prev where any is shared) and any season totals the league's tie-break placed apart, so "joint" has a line to be held to, and the roster lines call the manager's net "overall". Biggest Bench Haul (both formats) ranks on the best legal XI's gain over the XI that counted — one keeper, the eight legal formations, the doubled player held in place — never on the bench's raw sum, which counted a keeper who could not have played and outfielders no formation had room for (issue #350); its detail names the swap, a manager with bench points but no better XI is no candidate, and the raw sum stays on the row as `bench_points` (the API's `points_on_bench`) because the coarse tier can record nothing else. Coverage gaps and capture warnings go to stderr; the command exits 0 even when the store is unreadable. A `--save` whose report cannot be written exits 1 with `Failed to save report: <reason>` on stderr; under `--format json` that is the `{command, error}` envelope, with the codes of any capture warnings named in the message and any editorial printed on stderr first (issue #474). `--format json` emits one row per manager (the captured ledger row shape) plus `metadata.coverage`, `season_phase`, `notes_pack`, `synthesis_summary`, `synthesis_attempts` (answers judged: 1, or 2 after a retry), `synthesis_shipped_attempt` (which of them shipped) and `synthesis_usage` (input and output tokens summed over every answer judged; all three `null` when no editorial came back), `warnings` (always present, each entry a stable `code` plus rewritable `message` - codes listed in [Command Reference](../docs/command-reference.md#league-history)) `first_capture_store_path` (always present; the partition directory on a first capture, `null` after) and `draft_picks_saved` (always present; the draft-picks snapshots this run wrote, empty on classic). 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. `last-place` is ruled against every manager level on the gameweek's lowest score rather than whichever of them the cohort's arrival order reached first; because rulings are frozen at capture and the ledger is append-only, gameweeks recorded before that was true are swept on every run and the recorded ruling appended against the managers they left out, with no flag and no request (`league_history_tied_last_place_repaired`) — and where the row data cannot settle who was level, since the gameweek does not record whether it was ruled on gross or net points, the manager is named by `league_history_tied_last_place_ambiguous` rather than fined on a guess. 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. The editorial's League History section also states the league's highest and lowest gameweek score this season with who scored each and when, counted as the standings count the week (gross, or net under `use_net_points`) and qualified by any gameweek not every score of which was recorded, and the system prompt allows a season superlative ("the week of the season") only where that line names it (issue #494); prompt-only, absent on the league's first gameweek, and in `metadata.notes_pack.season_record_entries` with `points` and `holders`. 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 cut off at its token ceiling is regenerated once, told it was cut off (a refused editorial is never regenerated, even with a wrong claim in it), and one the provider stopped early on that is still the one kept is written out as it stands with the reason on stderr, a `synthesis_stopped_early` warning in JSON, and a line in the report's warning callout above the editorial (issue #372). 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. Every recap, classic and draft, also carries a Standout Players section (issue #455), read off the squads and live points the bench haul already uses: the week's top scorers in any manager's squad and its costliest blanks (2 points or fewer; in classic the one that counted for the most managers first, in draft the best by FPL's pre-season draft rank and, on a live recap from GW19, by season points per game, since the rank is never updated; a draft player neither can rank is left off, and no line states the rank or average, which only the heading and JSON carry), each with the managers he counted for, how long a blank played when it was under the hour, and any who left him on the bench, as one sentence per player that the console and, with `--summarise`, a `## Standout Players` prompt section print, never the saved report, where the editorial already tells the same week in prose (issue #482); `--format json` carries it as `metadata.standout_players`, every player row with the main game's `id` (under `--draft` too) and `code` beside it. A draft recap's lines also say how each player came to the manager they name (issue #458): the draft pick, the waiver or free-agent move, or the trade, the latest as of the recapped gameweek (a `-g` replay shows its own week), from a replay of the league's draft, accepted transactions and processed trades that must end where FPL's own ownership list does and match the week's squads, with the draft's picks read from the choices feed or, once it serves another draft, the saved snapshot, or, where neither has them, who drafted whom rebuilt from each manager's squad at the draft's gameweek with the earlier moves undone (the line then says "who drafted him" with no round, `round_unknown` in the JSON, and a `draft_rounds_unknown` notice replaces the warning). If the history cannot be shown complete (or a read fails, a failed trades read included once a trade has happened) the lines read without it and a `draft_provenance_unavailable` warning says why; draft rows carry `acquisition` in the JSON, classic rows do not. Before a live capture collects, it reads the recapped game's processing feed (`event-status/` on `FPLClient`, `pl/event-status` on `FPLDraftClient`, both `get_gameweek_status()`): when the feed's match days for the recapped gameweek show bonus unconfirmed or the league tables still "Updating" (`GameweekStatus.still_processing()`), it warns on stderr, in a `league_tables_updating` JSON warning and in a callout at the top of a saved report that positions and totals may be stale, and records the gameweek anyway, since a later run supersedes it and a missed draft gameweek cannot be recaptured; a reconciliation failure in that window keeps its own diagnosis and adds that it may be timing. Only a definite signal warns: an unreadable, slow (5s), reshaped or unrecognised feed is a debug log, and a replay never reads it (issue #424) | Yes | General | No | via-agent |
|
|
58
|
+
| `league-recap` | Recap a completed gameweek - awards, standings, and banter. Saves to `<output dir>/<season>/gw{N}-league-recap[-draft].md`, opening on a fixed `# Gameweek N Recap: <league name>` title the report writes itself, editorial or not; the editorial is asked for a `## ` headline beneath it and never a title, and whatever heading it opens with anyway is demoted to that level or, when it only restates the title, dropped, so the model's heading is never the document's (issue #349). Records every run into the league history ledger (`<data dir>/league_history/`) as a side effect, for draft alongside each held draft's picks (`drafts/draft-{id}.json` in the draft partition, saved once while the API still serves them, since a scheduled redraft drops them for good; `league_history_draft_picks_conflict`, `_lost` (once per draft), `_unsaved` and `_unchecked` name a disagreement with the API, a draft held before anything saved it, a run that could not save, and a `league.drafts` list it could not read), 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 last superseded line that recorded them (a gameweek captured repeatedly while it was still settling holds a stale position in its first capture and the settled one in its last), 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. A draft row also records the waiver claims its manager lost to a rival (`lost_claims`, kept apart from `transactions` because nothing moved — no points, no net, and never the denial that is the cascade of the manager's own accepted claim), so an outbid manager is not written into the permanent record as one who never tried; a gameweek captured before that field existed reads it back as unrecorded rather than as "lost none", is named by the coverage report, and is re-recorded by `--backfill-detail` while the league's transaction feed still serves it; a replay that finds fewer claims than the gameweek recorded keeps the recorded list (`league_history_claims_carried`). Those claims also make the waiver wire's races visible (issue #330): the editorial's waiver roster closes with every player more than one manager claimed, who won him and who was beaten to him at what priority, and a week where at least three managers claimed the same player (`MOST_CONTESTED_MIN_CLAIMANTS`) and one of them can be seen to have won him gets a Most Contested award beside Waiver Genius and Waiver Disaster — derived from the manager rows by `contested_draft_claims()`, never recorded, never a report section of its own, and absent from `--format json`, whose rows already carry the `lost_claims` and `transactions` they are regrouped from. The editorial is told to write each race as its own clause and never fold two into one ("respectively"), and is checked against the races afterwards (issue #357): a manager it names as beaten to, or winning, a contested player in a role that player's line does not give them triggers one regeneration told which claims were wrong (a retry that errors keeps the first attempt), and a kept editorial that still fails is saved as written with the wrong claims named on stderr, in a `synthesis_contested_misattribution` JSON warning, and in a callout above the editorial in the report. The same retry and reporting cover five checks on either format: a manager's net overall, pre-hit swing or another move's swing pinned on one named move (`synthesis_net_misattribution`, issue #359), a tie word ("joint-lowest", "tied for last", "joint-seventh") on a gameweek score or league position no two managers hold, or on the season's highest or lowest score where its season record was scored only once, a place in a milestone's highs or lows table nobody shares, or a manager's own season best or worst nobody equalled this gameweek (`synthesis_unsupported_tie`, issues #359, #494 and #496), one of this gameweek's scores given the league's best or worst of the season where the season-record line names another ("a new season low of 30", "the week of the season: 65 points"), unless a manager owns it ("his own season low") (`synthesis_unsupported_season_record`, issue #496), a placing on the week the manager's gameweek score does not earn, said of the week ("third on the week") or set beside that score ("finished eighth on 31", a league position read as the week's finish) (`synthesis_wrong_gameweek_placing`, issue #486), and an escape from last place given to a manager who finished there ("He escaped the wooden spoon" of the week's lowest scorer, or "clear of the foot of the table" of the manager bottom of it) (`synthesis_escape_misattribution`, issue #488); and a fourth on draft (issue #390): the net overall, or the other kind's figure, said of one kind of a manager's moves ("his waiver claims netted +3 overall" of a -1 waiver and a +4 free-agent pickup, `synthesis_kind_net_misattribution`; a generic waiver word such as "his waiver work" may mean all of a manager's moves, as the waiver awards are, and may carry the net overall, issue #539), for which a roster line that mixes kinds now gives each kind's net beside the net overall. A fifth, on draft (issue #459), holds the editorial to not reciting a race as a list: a paragraph naming the race's player and three or more of its beaten managers (`synthesis_contested_roll_call`) earns the same retry and reporting, but as a broken brief rather than a contradiction of the data, since the sentence is true, so the retry, stderr and the report callout say that instead; so does a fine's forfeit written out word for word more than once (`synthesis_penalty_repeated`, issue #503), which the prompt allows once, headline included, every other mention in other words or, where the `fines:` block or a rule sets `penalty_phrasings`, only those (a bare "the forfeit" counts, a short aside alluding to it may be in the model's own words, and only the headline may pun on it in its own words, never into more of it or a different act), while the Fines list and the ledger keep the configured `penalty`; and so does the brief's own wording read out to the league (`synthesis_brief_echoed`, issue #517): a prompt section named as a section, "exact phrase", "at most once", "official wording" or the "Naming the forfeit" label; and so does a false claim about who was fined this gameweek (`synthesis_fine_claim_contradicted`, issue #516): a manager called the only one without a fine where the Fines section's not-fined list holds more than them (or the only one on the week's highest or lowest score while another on it went unfined), or "a double fine", "fined twice" or "two fines" for a manager with other than two fine lines. The prompt's GW Standings section names the week's highest and lowest scores, every shared score and position (Pos, and Prev where any is shared) and any season totals the league's tie-break placed apart, so "joint" has a line to be held to, and the roster lines call the manager's net "overall". Biggest Bench Haul (both formats) ranks on the best legal XI's gain over the XI that counted — one keeper, the eight legal formations, the doubled player held in place — never on the bench's raw sum, which counted a keeper who could not have played and outfielders no formation had room for (issue #350); its detail names the swap, a manager with bench points but no better XI is no candidate, and the raw sum stays on the row as `bench_points` (the API's `points_on_bench`) because the coarse tier can record nothing else. Coverage gaps and capture warnings go to stderr; the command exits 0 even when the store is unreadable. A `--save` whose report cannot be written exits 1 with `Failed to save report: <reason>` on stderr; under `--format json` that is the `{command, error}` envelope, with the codes of any capture warnings named in the message and any editorial printed on stderr first (issue #474). `--format json` emits one row per manager (the captured ledger row shape) plus `metadata.coverage`, `season_phase`, `notes_pack`, `synthesis_summary`, `synthesis_attempts` (answers judged: 1, or 2 after a retry), `synthesis_shipped_attempt` (which of them shipped) and `synthesis_usage` (input and output tokens summed over every answer judged; all three `null` when no editorial came back), `warnings` (always present, each entry a stable `code` plus rewritable `message` - codes listed in [Command Reference](../docs/command-reference.md#league-history)) `first_capture_store_path` (always present; the partition directory on a first capture, `null` after) and `draft_picks_saved` (always present; the draft-picks snapshots this run wrote, empty on classic). 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. `last-place` is ruled against every manager level on the gameweek's lowest score rather than whichever of them the cohort's arrival order reached first; because rulings are frozen at capture and the ledger is append-only, gameweeks recorded before that was true are swept on every run and the recorded ruling appended against the managers they left out, with no flag and no request (`league_history_tied_last_place_repaired`) — and where the row data cannot settle who was level, since the gameweek does not record whether it was ruled on gross or net points, the manager is named by `league_history_tied_last_place_ambiguous` rather than fined on a guess. 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. The editorial's League History section also states the league's highest and lowest gameweek score this season with who scored each and when, counted as the standings count the week (gross, or net under `use_net_points`) and qualified by any gameweek not every score of which was recorded, and the system prompt allows a season superlative ("the week of the season") only where that line names it (issue #494); prompt-only, absent on the league's first gameweek, and in `metadata.notes_pack.season_record_entries` with `points` and `holders`. It also states a manager's own highest or lowest gameweek score this season when this gameweek set or equalled it, beside the record before it ("above their previous highest of 61 in GW9"), once the manager has ten recorded gameweeks of their own behind it; the system prompt allows "their best week of the season" only where such a line says so (issue #496). Prompt-only too, and in `metadata.notes_pack.personal_record_entries` for every manager every week, with `points`, `holders`, `previous_points` and `previous_holders`. At GW19 and the finale the report's League History section adds Season Highs and Lows, two tables ranking every manager's own highest gameweek score highest first and lowest worst first, places shared on a tie, which the editorial also gets, in `metadata.notes_pack.personal_record_tables`. 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 cut off at its token ceiling is regenerated once, told it was cut off (a refused editorial is never regenerated, even with a wrong claim in it), and one the provider stopped early on that is still the one kept is written out as it stands with the reason on stderr, a `synthesis_stopped_early` warning in JSON, and a line in the report's warning callout above the editorial (issue #372). 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. Every recap, classic and draft, also carries a Standout Players section (issue #455), read off the squads and live points the bench haul already uses: the week's top scorers in any manager's squad and its costliest blanks (2 points or fewer; in classic the one that counted for the most managers first, in draft the best by FPL's pre-season draft rank and, on a live recap from GW19, by season points per game, since the rank is never updated; a draft player neither can rank is left off, and no line states the rank or average, which only the heading and JSON carry), each with the managers he counted for, how long a blank played when it was under the hour, and any who left him on the bench, as one sentence per player that the console and, with `--summarise`, a `## Standout Players` prompt section print, never the saved report, where the editorial already tells the same week in prose (issue #482); `--format json` carries it as `metadata.standout_players`, every player row with the main game's `id` (under `--draft` too) and `code` beside it. A draft recap's lines also say how each player came to the manager they name (issue #458): the draft pick, the waiver or free-agent move, or the trade, the latest as of the recapped gameweek (a `-g` replay shows its own week), from a replay of the league's draft, accepted transactions and processed trades that must end where FPL's own ownership list does and match the week's squads, with the draft's picks read from the choices feed or, once it serves another draft, the saved snapshot, or, where neither has them, who drafted whom rebuilt from each manager's squad at the draft's gameweek with the earlier moves undone (the line then says "who drafted him" with no round, `round_unknown` in the JSON, and a `draft_rounds_unknown` notice replaces the warning). If the history cannot be shown complete (or a read fails, a failed trades read included once a trade has happened) the lines read without it and a `draft_provenance_unavailable` warning says why; draft rows carry `acquisition` in the JSON, classic rows do not. On the gameweek that closes one of FPL's months (bootstrap `phases`, read by `FPLClient.get_months()`; a draft league uses the main game's), the recap adds Manager of the Month (the month's top scorer, named with the rest of the top three) and Dud of the Month (its lowest) to the console, the report's Awards and, with `--summarise`, the editorial's, summed from the ledger the way FPL's own month table sums it (net of hits, except a hit for the month's first gameweek, which FPL never charges), ties shared, and both withheld with the reason when any manager in the month's table lacks a score (or, on classic, a hit) for one of its gameweeks, or when the recap reads only part of the league (classic standings past one page); derived every run, never stored (issue #425). The halfway (GW19) and final (GW38) recaps list each completed month's awards under `Monthly Honours` on console and as a `# Monthly Honours` report section, the printed season fines table's rhythm; the editorial gets the same list on those two recaps and on every month's last gameweek, and `metadata.monthly_honours` carries it every week. The month a classic recap closes is cross-checked against FPL's own month table (`?phase=N`) before either award is placed, a disagreement withholding both everywhere and raising `league_month_table_disagrees`; the editorial's tie check reads a month tie against that month's own table; months the bootstrap does not list readably skip both with `league_months_unavailable`. A draft league that sets a `player-of-the-month` entry under `fines.draft_vouchers` also gets the Premier League's Player of the Month (issue #492), read from the Premier League's own awards feed (`PremierLeagueClient`, whose player id is FPL's `code`) and announced once, by the first recap of the latest finished gameweek after the award is out (so a skipped recap still gets it, and a replay of an older gameweek never claims it or asks the feed; the announcing recap writes a record under the draft partition's `awards/`): an Awards line naming who held him for the month's last gameweek, bench included, read off the ledger's squads every run (with who held him when, where he changed hands), and a Fines section line saying that manager may hand out one fine, the voucher's `penalty`, to anyone they choose. A grant, never a ruling, so `league-fines` never counts it. Nobody holding him at the month's end means no voucher, said so; a month-end gameweek that cannot say (a missing or unknown row, an unidentified pick, a league read in part) withholds it with the reason, and the first latest-gameweek recap that can say settles it, once; an unreadable or reshaped feed, or a winner whose code FPL does not know, is "not checked" with `player_of_the_month_unchecked`, never "no award"; the voucher's `overrides` (month to code) win over the feed. `metadata.player_of_the_month` carries every completed month's winner (`player` with `code`, no `id`), owner, holders and announcement gameweek every week; classic never shows it. Before a live capture collects, it reads the recapped game's processing feed (`event-status/` on `FPLClient`, `pl/event-status` on `FPLDraftClient`, both `get_gameweek_status()`): when the feed's match days for the recapped gameweek show bonus unconfirmed or the league tables still "Updating" (`GameweekStatus.still_processing()`), it warns on stderr, in a `league_tables_updating` JSON warning and in a callout at the top of a saved report that positions and totals may be stale, and records the gameweek anyway, since a later run supersedes it and a missed draft gameweek cannot be recaptured; a reconciliation failure in that window keeps its own diagnosis and adds that it may be timing. Only a definite signal warns: an unreadable, slow (5s), reshaped or unrecognised feed is a debug log, and a replay never reads it (issue #424) | Yes | General | No | via-agent |
|
|
59
59
|
| `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 — including one `league-recap` appended against a manager who tied for last and was left out at capture. 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 |
|
|
60
60
|
| `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 |
|
|
61
61
|
| `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 |
|
|
@@ -105,6 +105,7 @@ Skills write their own outputs (`gw{N}-recommendations.md`, `gw{N}-squad-builder
|
|
|
105
105
|
| 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) |
|
|
106
106
|
| 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 |
|
|
107
107
|
| 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 |
|
|
108
|
+
| ab-brief | `skills/ab-brief/` | Write the brief a fresh fpl-workspace session runs to A/B a reviewed league-recap prompt or check change (gate on review, classify fresh runs vs corpus replay, pin both arms' SHAs, per-shape expectations), subscribe to the PR, and act on the `ab-handback` review the vault session posts back | Full: Claude Code (PR subscription wakes the session). Partial: Codex, Cursor, Copilot (no subscription) |
|
|
108
109
|
|
|
109
110
|
## Analysis Agents
|
|
110
111
|
|
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ab-brief
|
|
3
|
+
description: >
|
|
4
|
+
Write the brief a fresh fpl-workspace (vault) session runs to A/B a
|
|
5
|
+
league-recap prompt or editorial-check change, once its fpl-cli PR has
|
|
6
|
+
been through code review, and act on the handback review that session
|
|
7
|
+
posts on the PR. Use when the user says "A/B brief", "prompt for a vault
|
|
8
|
+
session", "give me a prompt for a fresh vault session to A/B this", or
|
|
9
|
+
"write the brief for the A/B"; as the step the create-pr skill names
|
|
10
|
+
after review for a PR touching fpl_cli/prompts/ or a data section the
|
|
11
|
+
model is shown; and when a review carrying the ab-handback marker lands
|
|
12
|
+
on the PR.
|
|
13
|
+
model: opus
|
|
14
|
+
compatibility:
|
|
15
|
+
claude-code: full (GitHub MCP on the web, gh locally; subscribe_pr_activity for the wake-up)
|
|
16
|
+
codex: partial (no PR subscription, so the user says when the handback is posted)
|
|
17
|
+
cursor: partial (no PR subscription)
|
|
18
|
+
copilot: partial (no PR subscription)
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
<!-- Runs in the fpl-cli repo; its brief runs in a fresh fpl-workspace session. -->
|
|
22
|
+
|
|
23
|
+
# A/B Brief
|
|
24
|
+
|
|
25
|
+
Hand a league-recap A/B to a dedicated vault session, and close the loop
|
|
26
|
+
when its results come back.
|
|
27
|
+
|
|
28
|
+
A change to a prompt, a data section the model is shown, or an editorial
|
|
29
|
+
check is verified by the vault's harness (`.claude/scripts/recap_ab/` in
|
|
30
|
+
fpl-workspace) before it merges: CLAUDE.md's prompt-change rule. The work
|
|
31
|
+
splits across two sessions, and this skill is the seam between them:
|
|
32
|
+
|
|
33
|
+
- **The fpl-cli session owns the code.** It writes the change, takes it
|
|
34
|
+
through code review, runs this skill, and makes every fix the round
|
|
35
|
+
turns up.
|
|
36
|
+
- **The vault session owns the test.** It has the FPL credentials, LLM
|
|
37
|
+
keys, real leagues and the drafts corpus, and it never changes fpl-cli.
|
|
38
|
+
|
|
39
|
+
The forward leg is copy and paste: this skill prints a brief, the user
|
|
40
|
+
pastes it into a fresh vault session. The return leg is a PR review: the
|
|
41
|
+
vault session posts one comment-only review on the fpl-cli PR, which wakes
|
|
42
|
+
this session because Step 6 subscribed it to the PR first.
|
|
43
|
+
|
|
44
|
+
**This session never runs the A/B or the replay, not even "just to
|
|
45
|
+
check".** Twice in October an fpl-cli session did the vault's work itself,
|
|
46
|
+
once three whole rounds and once a corpus replay the user had to stop.
|
|
47
|
+
That overlap is what the split exists to end: two sessions testing one PR
|
|
48
|
+
produce two sets of numbers and no owner for either.
|
|
49
|
+
|
|
50
|
+
Everything here is written for a public repo. The skill, the PR, its
|
|
51
|
+
commits and the handback carry no manager names, entry IDs, manager_keys
|
|
52
|
+
or league IDs; examples use Manager A, Manager B. The brief itself goes to
|
|
53
|
+
a private session, but write it to the same standard so a pasted fragment
|
|
54
|
+
can never leak.
|
|
55
|
+
|
|
56
|
+
## Step 1: The gate
|
|
57
|
+
|
|
58
|
+
Refuse to write a brief, and say exactly what is missing, unless all three
|
|
59
|
+
hold. A brief written early pins a SHA the review is about to move, and
|
|
60
|
+
the vault session then spends a round on code nobody will ship.
|
|
61
|
+
|
|
62
|
+
1. **The branch has an open PR.** `gh pr view --json number,state,headRefOid`
|
|
63
|
+
locally, or the GitHub MCP `list_pull_requests` with
|
|
64
|
+
`head: rossgroomio:<branch>` on the web.
|
|
65
|
+
2. **A code review has been posted, and every thread on it is resolved or
|
|
66
|
+
replied to.** Read the threads (`pull_request_read` with
|
|
67
|
+
`get_review_comments`, or `gh api graphql`). An unresolved thread whose
|
|
68
|
+
last comment is a reply counts as answered. A thread deliberately left
|
|
69
|
+
open as the A/B merge gate is fine: name it, because Step 5 resolves it
|
|
70
|
+
when the gate clears. A review carrying the ab-handback marker is an
|
|
71
|
+
earlier round's result, not a code review, and does not satisfy this.
|
|
72
|
+
3. **The head you will pin includes the review fixes.** Each fix reply
|
|
73
|
+
names a commit; check every one is in the head
|
|
74
|
+
(`git merge-base --is-ancestor <fix sha> <head sha>`). A reply saying
|
|
75
|
+
"pushing next" with no such commit on the branch means the fix is not
|
|
76
|
+
there yet.
|
|
77
|
+
|
|
78
|
+
Report the CI state on that head (`get_check_runs`, or `gh pr checks`) but
|
|
79
|
+
do not block on it. Briefs in October went out with CI still running and
|
|
80
|
+
it never mattered: the round takes longer than CI, and a red CI is a code
|
|
81
|
+
problem this session fixes on its own clock.
|
|
82
|
+
|
|
83
|
+
## Step 2: Classify the change
|
|
84
|
+
|
|
85
|
+
From the diff against the merge-base (Step 3), say which test the change
|
|
86
|
+
needs:
|
|
87
|
+
|
|
88
|
+
- **Fresh model runs** when what the model is shown, or the model itself,
|
|
89
|
+
changes: either prompt (`fpl_cli/prompts/league_recap.py`'s system or
|
|
90
|
+
user prompt), a data section the model is shown (what a
|
|
91
|
+
`format_recap_*` function emits, or the recap data keys it reads, often
|
|
92
|
+
set in `fpl_cli/cli/_league_recap_data.py` or `league_recap.py`), the
|
|
93
|
+
synthesis model or its settings (`llm.synthesis` in
|
|
94
|
+
`fpl_cli/config/defaults.yaml`, `fpl_cli/api/providers/`), or the retry
|
|
95
|
+
loop (`fpl_cli/cli/_synthesis_retry.py`).
|
|
96
|
+
- **A replay of the saved drafts corpus** when the editorial checks change
|
|
97
|
+
(`fpl_cli/prompts/league_recap_checks.py`). Never model runs for a check
|
|
98
|
+
change alone: the corpus already holds the drafts, and a replay judges
|
|
99
|
+
them in seconds where fresh runs cost an hour and add noise.
|
|
100
|
+
- **Both** when it does both.
|
|
101
|
+
|
|
102
|
+
Name every check whose `retry_instruction` changed or is new. A replay
|
|
103
|
+
shows which drafts would now retry, never how the model reads the new
|
|
104
|
+
wording; only fresh runs show that, so a new or reworded retry tips a
|
|
105
|
+
check-only change towards fresh runs too.
|
|
106
|
+
|
|
107
|
+
This is an expectation, not the verdict. The vault's `triage.sh` dry-runs
|
|
108
|
+
both builds and is authoritative; the brief states what you expect and
|
|
109
|
+
tells the vault session to stop if triage says otherwise. A disagreement
|
|
110
|
+
is itself a finding: either the diff does something its author did not
|
|
111
|
+
see, or the classification above has a gap.
|
|
112
|
+
|
|
113
|
+
## Step 3: Gather
|
|
114
|
+
|
|
115
|
+
Collect everything the vault session cannot cheaply rediscover. It starts
|
|
116
|
+
with no knowledge of the PR, and every fact it has to dig out of fpl-cli
|
|
117
|
+
is time spent and a chance to guess.
|
|
118
|
+
|
|
119
|
+
**Both arms as full 40-character SHAs.** Control is the PR's merge-base
|
|
120
|
+
with main, treatment is the PR head:
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
git fetch origin main <branch>
|
|
124
|
+
git merge-base origin/main origin/<branch> # control
|
|
125
|
+
git rev-parse origin/<branch> # treatment
|
|
126
|
+
git log --oneline <control>..origin/main # has main moved on?
|
|
127
|
+
git diff --stat <control> origin/main -- fpl_cli/
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Copy the SHAs out of command output, never type one. One October brief
|
|
131
|
+
went out with placeholder SHAs, and one session typed a SHA from memory
|
|
132
|
+
and broke a uvx run. If main has moved on, say whether those commits are
|
|
133
|
+
docs, tests or CI only (an empty `fpl_cli/` diffstat). If they touch
|
|
134
|
+
`fpl_cli/`, stop and put it to the user: the PR should probably merge main
|
|
135
|
+
first, so the round tests what will ship.
|
|
136
|
+
|
|
137
|
+
**Only what you have read in full.** Every expectation in the brief about
|
|
138
|
+
a file, probe, earlier change or count comes from reading the whole thing,
|
|
139
|
+
never a preview, a diffstat or an inference. The first real brief told the
|
|
140
|
+
vault session that a probe wrote the forfeit once, from the probe's first
|
|
141
|
+
600 characters (it wrote it twice), and that an earlier PR had changed the
|
|
142
|
+
prompt, from a diffstat that included console code (it had not). Each cost
|
|
143
|
+
the round a stop. Where you could not check, write it as a question the
|
|
144
|
+
dry run or the corpus answers, not as an expectation.
|
|
145
|
+
|
|
146
|
+
**What the change is meant to do, per sentence shape, and what counts as
|
|
147
|
+
a regression.** Quote real sentence shapes in full from the reports the
|
|
148
|
+
issue cites or the PR's tests, with every manager name replaced by
|
|
149
|
+
Manager A, Manager B. Never trim a sentence into a test-shaped fragment:
|
|
150
|
+
one October fix brief did, and the PR's test passed while the real
|
|
151
|
+
sentence was still missed. For each shape, say what treatment should write
|
|
152
|
+
instead (or that it should be unchanged) and what would be a regression,
|
|
153
|
+
and name the file each shape was quoted from so the vault session can find
|
|
154
|
+
it. If an expectation you want sits uneasily with the PR's own rule (puns
|
|
155
|
+
wanted in headlines, while the prompt's "no others" covers the headline
|
|
156
|
+
too), put that to the user before writing the brief: left in, it becomes a
|
|
157
|
+
definitional call the vault session makes mid-round, and the call moves
|
|
158
|
+
the count. The brief that listed expected changes per shape ran fastest and cleanest
|
|
159
|
+
of all the October rounds, because grading became a comparison against a
|
|
160
|
+
written expectation instead of a judgement made from scratch.
|
|
161
|
+
|
|
162
|
+
**The baseline.** The previous round's numbers for the same measure, from
|
|
163
|
+
the linked issue or earlier PR bodies, numbers only. If no round has
|
|
164
|
+
measured it, say so and say where the first measurement will come from
|
|
165
|
+
(often control's own runs, or a replay of older control drafts), and do
|
|
166
|
+
not predict its size: the first brief expected "a clear share" of repeats
|
|
167
|
+
in control and the round found 1 in 10, which left the vault session
|
|
168
|
+
deciding whether a wrong guess was a stop. "Better" is then stated against
|
|
169
|
+
whatever control measures, not against a guess.
|
|
170
|
+
|
|
171
|
+
**The gameweek.** The most recently finished one per
|
|
172
|
+
`fpl status --format json`, unless the PR's evidence came from a specific
|
|
173
|
+
gameweek (an issue quoting one week's recap), in which case name that
|
|
174
|
+
gameweek and say why. The evidence week is where the failure is known to
|
|
175
|
+
show, so it is the strongest test.
|
|
176
|
+
|
|
177
|
+
**Run counts.** For fresh runs where the gate is a frequency, 40 draft and
|
|
178
|
+
4 to 5 classic per arm. A round of 8 draft runs per arm in October was too
|
|
179
|
+
small to call first time, and the re-run cost more than doing 40 once.
|
|
180
|
+
Draft is the gate by default because it has the most checks and the most
|
|
181
|
+
variance; where the change's evidence lives in classic (a classic-only
|
|
182
|
+
section, a failure the issue saw only in classic), raise classic and say
|
|
183
|
+
why. Replay rounds need no run counts.
|
|
184
|
+
|
|
185
|
+
**The round name** for `RECAP_AB_ROUND`, as `pr<N>-gw<GW>` (`pr507-gw8`).
|
|
186
|
+
|
|
187
|
+
**For a replay: which corpus rounds, and what recheck data.** List the
|
|
188
|
+
rounds on the vault's main (`.claude/scripts/recap_ab/corpus/`, via the
|
|
189
|
+
GitHub MCP `get_file_contents` on `rossgroomio/fpl-workspace` or a clone).
|
|
190
|
+
Read the README's "Rechecking saved drafts": a check that reads a recap
|
|
191
|
+
data key older snapshots lack judges those drafts on less than its build
|
|
192
|
+
computes, so the replay needs `RECAP_AB_RECHECK_DATA` carrying that key for
|
|
193
|
+
each round's gameweek and format, merged with any round's own
|
|
194
|
+
`recheck-data.json`. Say what the key should hold and where its value
|
|
195
|
+
comes from. Name the rounds whose snapshots already carry the key (a round
|
|
196
|
+
recorded on this PR's own build does), since the override would replace
|
|
197
|
+
their values. Read each probe you expect a result from in full.
|
|
198
|
+
|
|
199
|
+
The replay's control will not reproduce a round recorded on an older build
|
|
200
|
+
wherever a check changed in between. For each round, run
|
|
201
|
+
`git log --oneline <round's recorded sha>..<control> --
|
|
202
|
+
fpl_cli/prompts/league_recap_checks.py` and name the expected
|
|
203
|
+
non-reproductions with their PR, giving every SHA involved in full: the
|
|
204
|
+
first round's vault session met one such miss, guessed the SHA of the
|
|
205
|
+
commit that explained it, and broke a replay before looking it up.
|
|
206
|
+
|
|
207
|
+
**Whether a second control exists:** an earlier corpus round, at the same
|
|
208
|
+
gameweek, holding control model runs (dry runs and probes alone cannot be
|
|
209
|
+
pooled), whose control prompts are byte-identical to this control's. One
|
|
210
|
+
October round pooled with its predecessor this way and halved the control
|
|
211
|
+
runs it needed. Read what changed between the two builds in the code that
|
|
212
|
+
writes the prompt (`fpl_cli/prompts/league_recap.py` and the recap data it
|
|
213
|
+
reads), not a diffstat: `fpl_cli/cli/` also holds the console and saved
|
|
214
|
+
report, which the model never sees. If you cannot tell, say the dry runs
|
|
215
|
+
decide and leave it there. If no such round exists, say so, so the vault
|
|
216
|
+
session does not go looking.
|
|
217
|
+
|
|
218
|
+
## Step 4: Write the brief
|
|
219
|
+
|
|
220
|
+
Fill in `references/brief-template.md` in its order; the template carries
|
|
221
|
+
the wording that must survive verbatim in spirit, and the reasons stay
|
|
222
|
+
here so the brief stays short enough to read before the round starts.
|
|
223
|
+
|
|
224
|
+
- **The opening line** points the vault session at the harness README
|
|
225
|
+
first. The harness changed after most October rounds; a session working
|
|
226
|
+
from memory of an earlier round runs an older procedure.
|
|
227
|
+
- **The role boundary** is near the top and absolute. In two October
|
|
228
|
+
rounds the vault session pushed fixes to the fpl-cli PR branch, and one
|
|
229
|
+
opened an fpl-cli PR. Both times the user asked it to, which is why the
|
|
230
|
+
boundary holds even against the user's own request mid-round: a fix made
|
|
231
|
+
in the vault session is invisible to the fpl-cli session that owns the
|
|
232
|
+
PR, which then pushes over it or reviews code it never saw.
|
|
233
|
+
- **A fresh `origin/main` checkout of the vault.** A leftover branch from
|
|
234
|
+
an earlier round once caused git and stop-hook noise that read as a
|
|
235
|
+
broken test.
|
|
236
|
+
- **Stop conditions at every step that has an expectation** (triage, dry
|
|
237
|
+
run, anything in the brief that does not match). "If anything in this
|
|
238
|
+
brief doesn't match what you find, stop and ask me" caught a missing
|
|
239
|
+
corpus in one round. A brief is written from one repo about another; a
|
|
240
|
+
stop costs a message, a wrong round costs an hour. Stops are for set-up
|
|
241
|
+
facts (triage, dry-run diffs, corpus contents, SHAs), never for results:
|
|
242
|
+
a number that differs from what the brief hoped for is the finding, and
|
|
243
|
+
the brief should not predict one in the first place.
|
|
244
|
+
- **Landing** is spelled out because every October session needed a
|
|
245
|
+
follow-up message to land its round. Never write a blanket "don't commit
|
|
246
|
+
anything": one October brief did, and that round's drafts were lost
|
|
247
|
+
before the next round needed them. The rule is that anything naming
|
|
248
|
+
managers stays under `data/debug/`, and the corpus is the one tracked
|
|
249
|
+
exception.
|
|
250
|
+
- **Out-of-scope findings and harness gaps are listed, not fixed.** One
|
|
251
|
+
round spent about an hour building unscoped harness and kept it only
|
|
252
|
+
because the user asked. A small harness fix the round cannot run
|
|
253
|
+
without may ride the round's vault PR; anything bigger is a finding.
|
|
254
|
+
- **Scratch paths outside the harness's own scripts** are written
|
|
255
|
+
`${TMPDIR:-/tmp}/...`, the fallback the harness's `common.sh` already
|
|
256
|
+
uses, never a bare `$TMPDIR/...`. A fresh vault session may not set
|
|
257
|
+
`TMPDIR`; the pr545 brief's `cp -R "$FPL_CLI_CONFIG_DIR"
|
|
258
|
+
"$TMPDIR/pr545-config"` expanded to a path at the filesystem root, so
|
|
259
|
+
the session improvised `/tmp` and logged a harness gap that was really
|
|
260
|
+
a brief gap.
|
|
261
|
+
- **The handback** (below) is required in full, because in October the
|
|
262
|
+
whole vault transcript, progress chatter included, was pasted back and
|
|
263
|
+
the fpl-cli session had to dig the results out.
|
|
264
|
+
|
|
265
|
+
Before printing, read the brief once as the vault session will: every SHA
|
|
266
|
+
is 40 characters and came from command output, no `<placeholder>` or
|
|
267
|
+
"TBD" is left, every number has a source, every expectation comes from
|
|
268
|
+
something you read in full, and no manager name or ID appears anywhere in
|
|
269
|
+
it.
|
|
270
|
+
|
|
271
|
+
## The handback
|
|
272
|
+
|
|
273
|
+
One comment-only pull request review on the fpl-cli PR (event `COMMENT`,
|
|
274
|
+
no inline comments, never `APPROVE` or `REQUEST_CHANGES`), posted once,
|
|
275
|
+
after the vault PR is open if the round has one. It is a review rather than
|
|
276
|
+
a plain comment because only the review reaches the fpl-cli session: in
|
|
277
|
+
#507's first round a plain handback comment landed and the subscribed
|
|
278
|
+
session never woke, while in its second the same text posted as a
|
|
279
|
+
comment-only review woke it. Its first line is the marker, an HTML comment
|
|
280
|
+
GitHub does not render:
|
|
281
|
+
|
|
282
|
+
```
|
|
283
|
+
<!-- ab-handback pr=<N> head=<40-char sha the round tested> round=<round> -->
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
Both sessions post as the same GitHub identity, and a session watching a
|
|
287
|
+
PR is told to skip echoes of what it posted itself. The marker is how this
|
|
288
|
+
session tells the handback apart from its own reviews and comments: it
|
|
289
|
+
never writes one, so a review carrying it is an A/B result to act on. It also carries
|
|
290
|
+
the head the round actually tested, which Step 5 checks.
|
|
291
|
+
|
|
292
|
+
Then a heading, `## A/B handback for <round>, from the fpl-workspace
|
|
293
|
+
session`, and two blocks:
|
|
294
|
+
|
|
295
|
+
- **Block A, "PR body".** First-person prose in the create-pr skill's
|
|
296
|
+
style and length budget (up to three short paragraphs, plus caveats in
|
|
297
|
+
italics), numbers only, ready to go into the PR body as written.
|
|
298
|
+
- **Block B, "Findings".** A verdict line (`ship`, `fix then re-run
|
|
299
|
+
fresh`, `fix then replay`, or `don't ship`), the vault PR link if one was
|
|
300
|
+
opened, then one entry per finding: kind (regression, check false
|
|
301
|
+
positive, missed catch, out of scope, harness gap); check code if any;
|
|
302
|
+
the sentence shape in full with managers as Manager A, Manager B; what
|
|
303
|
+
should have happened; and the test shape to add.
|
|
304
|
+
|
|
305
|
+
The review lands in a public repo, and GitHub keeps edit history and
|
|
306
|
+
sends notification emails, so a name in it cannot be taken back. The vault
|
|
307
|
+
session writes it to a file under `data/debug/`, runs the vault's
|
|
308
|
+
`.claude/scripts/recap_ab/handback_check.py` over it, and posts that
|
|
309
|
+
exact file as the review body only on exit 0 (the GitHub MCP
|
|
310
|
+
`pull_request_review_write`, or `gh pr review <N> --comment --body-file`).
|
|
311
|
+
The script reads every manager the vault's
|
|
312
|
+
ledger has recorded and fails (exit 1) on any spelling of a full name, any
|
|
313
|
+
part of one no other manager shares, an entry ID, manager_key or league
|
|
314
|
+
ID, and on a footballer who shares a manager's name, which has to be
|
|
315
|
+
reworded by club and position; there is no allow-list. Exit 2 means it
|
|
316
|
+
could not check, which is not a pass. If the script is missing or exits 2,
|
|
317
|
+
the vault session does not post; it prints the handback in chat and says
|
|
318
|
+
why, and the user carries it across. It never edits the review once
|
|
319
|
+
posted: a correction is a second short comment-only review, opening with
|
|
320
|
+
the same marker and a heading that says it corrects the first, through the
|
|
321
|
+
same check.
|
|
322
|
+
|
|
323
|
+
## Step 5: Closing the loop
|
|
324
|
+
|
|
325
|
+
When the handback lands (a review event wakes this session; otherwise
|
|
326
|
+
the check-in finds it or the user says it has been posted; read it from
|
|
327
|
+
the PR's reviews, and its comments too in case an older brief's session
|
|
328
|
+
posted a plain comment):
|
|
329
|
+
|
|
330
|
+
1. **Check the marker.** Its `pr` is this PR and its `head` is the head
|
|
331
|
+
the brief pinned, or a later head the brief allowed ("if the PR has
|
|
332
|
+
moved on, use its current head and say so"). For a later head, read
|
|
333
|
+
what changed between the two: if it changes what the model sees or the
|
|
334
|
+
checks, the round tested something other than what was briefed, so say
|
|
335
|
+
so to the user before acting on the numbers. A review or comment with
|
|
336
|
+
no marker is not a handback, whoever wrote it.
|
|
337
|
+
2. **Put Block A into the PR body**, replacing whatever caveat said the
|
|
338
|
+
A/B had not run, keeping the body inside the create-pr style.
|
|
339
|
+
3. **Treat each Block B finding like a review comment.** Fix and push
|
|
340
|
+
(`chore(review): ...` commits, per CLAUDE.md), or say on the PR why
|
|
341
|
+
not, in one comment covering every finding you are not fixing. Never
|
|
342
|
+
reply to the handback review itself: the vault session is not
|
|
343
|
+
listening, and a reply there reads as a thread for nobody.
|
|
344
|
+
4. **Re-run Step 2 on the new head.** If the fixes change what the model
|
|
345
|
+
sees, write a fresh-run brief for the new head; if they touch only the
|
|
346
|
+
checks, a replay brief; either way the next round's handback comes back
|
|
347
|
+
the same way, and this session stays subscribed. A verdict of
|
|
348
|
+
`fix then re-run fresh` or `fix then replay` is the vault session's
|
|
349
|
+
expectation, and Step 2 on the actual fixes decides. Scope the round by
|
|
350
|
+
the diff, not by the finding: a system-prompt line reaches every arm,
|
|
351
|
+
so a fix aimed at the phrasings arm alone still re-runs the plain
|
|
352
|
+
treatment arm when it touches the system prompt (#507's first fix did).
|
|
353
|
+
An earlier round's arm is reused only where its build is unchanged and
|
|
354
|
+
the new dry runs show the same data sections, and a follow-up round
|
|
355
|
+
whose predecessor's vault PR is still open continues on that PR's
|
|
356
|
+
branch under the same round name with new labels, so both rounds are
|
|
357
|
+
graded together and land together.
|
|
358
|
+
5. **If no fix is needed**, say the A/B gate is cleared, and resolve the
|
|
359
|
+
thread left open as the gate (reply first, saying which round cleared
|
|
360
|
+
it). Merging stays the user's call.
|
|
361
|
+
6. **Out-of-scope findings** become issues only once the user agrees: list
|
|
362
|
+
them for the user with a one-line proposal each.
|
|
363
|
+
|
|
364
|
+
A `don't ship` verdict goes to the user before anything else: it may mean
|
|
365
|
+
the approach is wrong, which is their decision, not a fix to push.
|
|
366
|
+
|
|
367
|
+
## Step 6: Deliver
|
|
368
|
+
|
|
369
|
+
1. **Subscribe this session to the PR's activity before printing**
|
|
370
|
+
(`subscribe_pr_activity`), so the handback review wakes it, and say so.
|
|
371
|
+
Subscribing after printing races a fast replay round. The wake has
|
|
372
|
+
worked once, so also arm one backstop `send_later` check-in about two
|
|
373
|
+
hours after delivery that reads the PR's reviews for the marker; re-arm
|
|
374
|
+
it twice at most, then tell the user once that the next message from
|
|
375
|
+
them will bring you back. Where either tool is missing (a local session,
|
|
376
|
+
another agent), tell the user to come back here and say when the
|
|
377
|
+
handback is posted.
|
|
378
|
+
2. **Print the brief in one fenced block** for copy and paste, with a
|
|
379
|
+
fence longer than any fence inside it (`~~~~` around a brief that holds
|
|
380
|
+
```` ``` ```` blocks), so the copy comes out whole.
|
|
381
|
+
3. **Above the block, not inside it, say how to start the vault session:**
|
|
382
|
+
a fresh session on `rossgroomio/fpl-workspace`, Opus, at xhigh effort
|
|
383
|
+
for a round with fresh runs and high for a replay alone. October's
|
|
384
|
+
fresh rounds ran at xhigh; a replay at high took under four minutes.
|
|
385
|
+
|
|
386
|
+
## Common mistakes
|
|
387
|
+
|
|
388
|
+
- Running triage, a dry run or a replay here "to check the brief": that
|
|
389
|
+
is the overlap this skill exists to end
|
|
390
|
+
- A placeholder or hand-typed SHA, or a short one
|
|
391
|
+
- A sentence shape trimmed to the fragment a test matches
|
|
392
|
+
- "Don't commit anything" in place of the landing rules
|
|
393
|
+
- A scratch path written `$TMPDIR/...` with no `:-/tmp` fallback
|
|
394
|
+
- Printing the brief before subscribing to the PR
|
|
395
|
+
- A brief that asks for the handback as a plain comment, which does not
|
|
396
|
+
wake the subscribed session
|
|
397
|
+
- An expectation built from a preview, a diffstat or a guess at a count
|
|
398
|
+
- Treating the handback as an echo of this session's own review, or
|
|
399
|
+
editing the PR body with the vault session's transcript instead of
|
|
400
|
+
Block A
|
|
401
|
+
- A manager name in the skill, the brief, the PR or a commit
|