Skip to content

fix(generation): prioritize oldest stale pages when the budget is constrained - #1845

Open
sloemo01 wants to merge 1 commit into
repowise-dev:mainfrom
sloemo01:fix/staleness-sort
Open

fix(generation): prioritize oldest stale pages when the budget is constrained#1845
sloemo01 wants to merge 1 commit into
repowise-dev:mainfrom
sloemo01:fix/staleness-sort

Conversation

@sloemo01

Copy link
Copy Markdown
Contributor

Summary

Fixes #851 (spun out of #847).

When the cascade budget is constrained, the ordering that decides which pages to regenerate was driven purely by pagerank (importance). That meant a docs run could spend its LLM calls regenerating high-importance pages that were already current while leaving genuinely stale prose untouched — the exact staleness-vs-importance mis-prioritization reported.

This change makes staleness the primary sort axis for the budgeted slice:

  • ChangeDetector.get_affected_pages now accepts a stale_pages map ({file_path: staleness_age_seconds}). When provided, an already-stale page outranks a fresh page no matter how central the fresh one is; among stale pages, the oldest stale page regenerates first. Importance (pagerank) breaks ties within each staleness class.
  • Absent/empty stale_pages keeps the historical pure-importance ordering, so no call site changes behaviour unless it opts in.

Plumbing

  • New get_stale_file_page_ages(session, repo_id) persistence CRUD helper: returns {file_path: age_seconds} for stale/expired file_page rows, age measured from updated_at (the last regeneration) so the longest-lagging page carries the largest value.
  • CLI repowise update --docs loads stale ages and passes them into the cascade.
  • Server incremental page regen (_incremental_page_regen) loads stale ages through its session/repo and passes them in (best-effort; missing session/repo falls back to the old ordering).
  • Exported via repowise.core.persistence.

Tests

  • tests/unit/ingestion/test_change_detector.py: a stale page regenerates ahead of a more important fresh page; oldest-stale-first within a budget; absent stale_pages preserves importance ordering.
  • tests/unit/persistence/test_stale_file_page_ages.py: which rows count, age semantics, and empty-return contract.

Verification

  • uv run ruff check — clean on all changed files.
  • uv run pytest over the changed modules — all pass (the one test_repo_totals_churn failure is a pre-existing git-environment issue, reproduced with the changes stashed).

…strained

The cascade budget in ChangeDetector.get_affected_pages sliced its
regenerate set by pagerank alone, so a constrained docs run could
regenerate pages that were already current while leaving genuinely stale
prose untouched (issues repowise-dev#847 / repowise-dev#851). Thread existing stale-page ages
into the sort so an already-stale page outranks a fresh page no matter
how central the fresh one is; among stale pages the oldest decays first.

Adds get_stale_file_page_ages to the persistence CRUD, wires it into the
CLI update path and the server's incremental page regen, and pins the
behaviour with regression tests.
@repowise-bot

repowise-bot Bot commented Aug 22, 2026

Copy link
Copy Markdown

✅ Health of changed files: 2.6 → 2.9 (+0.3)
⚠️ Change risk: moderate, riskier than 58% of this repo's commits.

📋 At a glance
1 file changed health · 5 hotspots touched · 4 new findings introduced · 3 co-change pairs left out · 5 files with recent fix history.

Files & modules (2)
  • packages (5 files)
    • .../update_cmd/command.py
    • .../server/job_executor.py
    • .../persistence/__init__.py
    • .../update_cmd/deterministic.py
    • .../crud/pages.py
  • tests (1 file)
    • .../ingestion/test_change_detector.py

✅ Health gate: passed

📌 Before you merge

  • Run tests/integration/test_cli.py, .../cli/test_docs_pointer.py, .../cli/test_editor_setup.py, .../cli/test_deterministic_update.py (+9 more): they import the changed files
  • .../update_cmd/persistence.py changed together with .../update_cmd/command.py in 14 past commits and isn't in this PR
  • .../workspace/update.py changed together with .../update_cmd/command.py in 13 past commits and isn't in this PR
  • .../init_cmd/command.py changed together with .../update_cmd/command.py in 10 past commits and isn't in this PR

🎯 Blast radius (symbols whose signature this PR changed, and who calls them)

  • _incremental_page_regen in .../server/job_executor.py signature changed. Called by 2 symbols outside this PR: .../server/test_job_executor.py::test_incremental_page_regen_passes_repo_path, .../server/test_job_executor.py::test_incremental_page_regen_sets_file_pages_only
🔎 More signals (3)

🗺️ Change map

flowchart LR
  subgraph PR ["Changed in this PR (6 with dependents)"]
    f_packages_cli_src_repowise_cli_commands_update_cmd_command_py[".../update_cmd/command.py 🔥"]:::changed
    f_packages_cli_src_repowise_cli_commands_update_cmd_deterministic_py[".../update_cmd/deterministic.py 🔥"]:::changed
    f_packages_core_src_repowise_core_persistence___init___py[".../persistence/__init__.py 🔥"]:::changed
    f_packages_server_src_repowise_server_job_executor_py[".../server/job_executor.py 🔥"]:::changed
    f_packages_core_src_repowise_core_ingestion_change_detector_py[".../ingestion/change_detector.py"]:::changed
    f_packages_core_src_repowise_core_persistence_crud_pages_py[".../crud/pages.py"]:::changed
  end
  f_packages_cli_src_repowise_cli_commands_generate_cmd_command_py[".../generate_cmd/command.py"]
  f_packages_cli_src_repowise_cli_commands_update_cmd_command_py --> f_packages_cli_src_repowise_cli_commands_generate_cmd_command_py
  f_packages_cli_src_repowise_cli_commands_init_cmd_command_py[".../init_cmd/command.py"]
  f_packages_cli_src_repowise_cli_commands_update_cmd_command_py --> f_packages_cli_src_repowise_cli_commands_init_cmd_command_py
  f_packages_cli_src_repowise_cli_commands_init_cmd_generation_py[".../init_cmd/generation.py"]
  f_packages_cli_src_repowise_cli_commands_update_cmd_command_py --> f_packages_cli_src_repowise_cli_commands_init_cmd_generation_py
  f_packages_cli_src_repowise_cli_commands_init_cmd_persistence_py[".../init_cmd/persistence.py"]
  f_packages_cli_src_repowise_cli_commands_update_cmd_command_py --> f_packages_cli_src_repowise_cli_commands_init_cmd_persistence_py
  f_packages_cli_src_repowise_cli_commands_update_cmd_deterministic_py --> f_packages_cli_src_repowise_cli_commands_init_cmd_command_py
  f_packages_cli_src_repowise_cli_commands_restyle_cmd_py[".../commands/restyle_cmd.py"]
  f_packages_cli_src_repowise_cli_commands_update_cmd_deterministic_py --> f_packages_cli_src_repowise_cli_commands_restyle_cmd_py
  f_packages_cli_src_repowise_cli__repo_session_py[".../cli/_repo_session.py"]
  f_packages_core_src_repowise_core_persistence___init___py --> f_packages_cli_src_repowise_cli__repo_session_py
  f_packages_cli_src_repowise_cli_commands_augment_cmd_search_py[".../augment_cmd/search.py"]
  f_packages_core_src_repowise_core_persistence___init___py --> f_packages_cli_src_repowise_cli_commands_augment_cmd_search_py
  f_packages_cli_src_repowise_cli_commands_claude_md_cmd_py[".../commands/claude_md_cmd.py"]
  f_packages_core_src_repowise_core_persistence___init___py --> f_packages_cli_src_repowise_cli_commands_claude_md_cmd_py
  f_packages_cli_src_repowise_cli_commands_costs_cmd_py[".../commands/costs_cmd.py"]
  f_packages_core_src_repowise_core_persistence___init___py --> f_packages_cli_src_repowise_cli_commands_costs_cmd_py
  f_packages_server_src_repowise_server_job_executor_py --> f_packages_cli_src_repowise_cli_commands_init_cmd_command_py
  f_packages_server_src_repowise_server_job_executor_py --> f_packages_cli_src_repowise_cli_commands_init_cmd_persistence_py
  f_packages_cli_src_repowise_cli_commands_init_cmd_workspace_py[".../init_cmd/workspace.py"]
  f_packages_server_src_repowise_server_job_executor_py --> f_packages_cli_src_repowise_cli_commands_init_cmd_workspace_py
  f_packages_server_src_repowise_server_job_executor_py --> f_packages_cli_src_repowise_cli_commands_restyle_cmd_py
  more(["+89 more dependents"])
  PR --> more
  w_packages_cli_src_repowise_cli_commands_update_cmd_persistence_py(["⚠️ .../update_cmd/persistence.py changed together 14×, not in PR"]):::warn
  f_packages_cli_src_repowise_cli_commands_update_cmd_command_py -.- w_packages_cli_src_repowise_cli_commands_update_cmd_persistence_py
  w_packages_core_src_repowise_core_workspace_update_py(["⚠️ .../workspace/update.py changed together 13×, not in PR"]):::warn
  f_packages_cli_src_repowise_cli_commands_update_cmd_command_py -.- w_packages_core_src_repowise_core_workspace_update_py
  t_tests_integration_test_cli_py(["✅ tests/integration/test_cli.py"]):::guard
  t_tests_integration_test_cli_py -.-> f_packages_cli_src_repowise_cli_commands_update_cmd_command_py
  t_tests_unit_cli_test_deterministic_update_py(["✅ .../cli/test_deterministic_update.py"]):::guard
  t_tests_unit_cli_test_deterministic_update_py -.-> f_packages_cli_src_repowise_cli_commands_update_cmd_deterministic_py
  t_tests_integration_persistence_test_graph_metrics_pg_py(["✅ .../persistence/test_graph_metrics_pg.py"]):::guard
  t_tests_integration_persistence_test_graph_metrics_pg_py -.-> f_packages_core_src_repowise_core_persistence___init___py
  classDef changed fill:#dbeafe,stroke:#1d4ed8,color:#1e3a5f
  classDef warn fill:#fef3c7,stroke:#b45309,color:#78350f
  classDef guard fill:#dcfce7,stroke:#15803d,color:#14532d
Loading

Solid arrows: code that imports the changed files (99 direct dependents, from the last indexed snapshot). Dashed: history/tests.

🔥 Hotspots touched (5)

  • .../update_cmd/command.py: 54 commits/90d, 19 dependents · primary owner: Raghav Chamadiya (87%)
  • .../ingestion/test_change_detector.py: 1 commits/90d, 0 dependents · primary owner: Raghav Chamadiya (100%)
  • .../server/job_executor.py: 20 commits/90d, 20 dependents · primary owner: Raghav Chamadiya (74%)
2 more
  • .../persistence/__init__.py: 7 commits/90d, 161 dependents · primary owner: Raghav Chamadiya (90%)
  • .../update_cmd/deterministic.py: 10 commits/90d, 5 dependents · primary owner: Raghav Chamadiya (84%)

🔗 Hidden coupling (1 file)

  • .../update_cmd/command.py co-changes with these files (not in this PR):
    • .../update_cmd/persistence.py (14×, 🟡 notable)
    • .../workspace/update.py (13×, 🟡 notable)
    • .../init_cmd/command.py (10×, 🟡 notable)

👀 Suggested reviewers @RaghavChamadiya


📊 See the full report for this PR
Your repo map with this PR's blast radius lit up, every caller of the contracts it changes, and health before and after. No sign-in. · ⭐ Star Repowise · 📥 Install bot · Silence on a single PR with [skip repowise] in the title · Per-repo toggle on repowise.dev/settings?tab=bot · Updated 2026-08-22 14:15 UTC

@Ayush7614 Ayush7614 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verified: packages/core/src/repowise/core/ingestion/change_detector.py:1 now accepts stale_pages map and sorts staleness-first (oldest stale outranks fresh regardless of pagerank) for budget-constrained regen (#851). Plumbing via get_stale_file_page_ages and CLI/server call sites is best-effort with fallback to pure-importance. Tests in test_change_detector.py + test_stale_file_page_ages.py cover ordering. No fake code — LGTM.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] Staleness vs importance sorting is not correctly prioritizing stale pages

2 participants