Skip to content

fix(flows): rematch incremental flows across node replacement - #673

Open
helasaoudi wants to merge 6 commits into
tirth8205:stagingfrom
helasaoudi:fix/569-incremental-flow-lifecycle
Open

helasaoudi wants to merge 6 commits into
tirth8205:stagingfrom
helasaoudi:fix/569-incremental-flow-lifecycle

Conversation

@helasaoudi

@helasaoudi helasaoudi commented Jul 19, 2026 •

Copy link
Copy Markdown

Summary

  • Capture and clear affected flows before file node IDs churn during incremental updates
  • Remap community assignments by qualified name and purge orphan flow memberships after replacement
  • Normalize relative git paths against absolute graph file_path values so new entry points are detected

Fixes #569

Test plan

  • tests/test_flows.py relative-vs-absolute new entry point + replacement lifecycle
  • tests/test_incremental.py end-to-end incremental flow lifecycle
  • tests/test_communities.py community capture/remap regression
  • Existing flow/community/incremental suites (143 related tests passed locally)

@tirth8205

Copy link
Copy Markdown
Owner

This is a useful lifecycle fix, but the four new focused tests are not enough for a 646-line stateful change. Path expansion uses suffix and SQL LIKE matching, yet there is no coverage for deleted or renamed entry points, different paths sharing a suffix, or literal % and _ in filenames. Please add those cases and prove incremental flow/community state matches a full rebuild before merge.

@tirth8205

Copy link
Copy Markdown
Owner

Revalidated head 98239a7 against current main at 9fed471; this remains blocked and still conflicts in code_review_graph/incremental.py. The four focused lifecycle tests pass on the PR head, but deletion parity and path matching still fail.

After a full build creates a two-node community for an isolated file, deleting that file and running the incremental full-postprocess path leaves the old community row at size=2 with zero assigned nodes; a fresh full rebuild correctly has no communities. Deleted nodes are removed before affected state is captured, and replacement paths include only files being parsed.

Path expansion also uses unescaped SQL LIKE: a_b.py matches acb.py, and a%z.py matches axz.py. Please rebase onto the current lifecycle code, capture deleted and renamed old paths before removal, replace suffix-LIKE matching with exact normalized matching or escape % and _, and add deletion/rename/suffix-collision/literal-character tests asserting incremental state equals a fresh full rebuild for flows, memberships, community rows, and assignments.

@tirth8205

Copy link
Copy Markdown
Owner

Revalidated head 98239a7 against exact current main 329dbb91. It still conflicts in code_review_graph/incremental.py, directly overlapping the newly merged last-synced-base work. The four submitted tests pass, but the missing lifecycle cases still fail.

Please rebase, capture deleted/renamed old paths before removal, use exact normalized or correctly escaped path matching, and add incremental-vs-clean-rebuild parity for flows, memberships, community rows, and node assignments. This head is not mergeable.

@helasaoudi
helasaoudi force-pushed the fix/569-incremental-flow-lifecycle branch from 98239a7 to 97caf68 Compare July 28, 2026 09:34
@helasaoudi

Copy link
Copy Markdown
Author

Thanks for the detailed revalidation addressed on the latest head 97caf68 (rebased onto current main).

Conflicts
Resolved the code_review_graph/incremental.py overlap with the last-synced-base work and force-pushed the rebased branch. The PR should now be mergeable from a conflict perspective.

Deletion / rename lifecycle
Affected flow and community state is now captured before permanent removal for deleted, renamed, and stale paths (not only to_parse). After replacement/removal we:

remap surviving community assignments by qualified name
purge orphan flow memberships / dead entry_point_ids
remove empty community rows so incremental deletion no longer leaves size=2 communities with zero assigned nodes
Path matching
Unescaped suffix-LIKE expansion is replaced with exact normalized path variants plus escaped LIKE (ESCAPE ''), so:

a_b.py no longer matches acb.py
a%z.py no longer matches axz.py
incremental_trace_flows(store, ["b.py"]) resolves absolute stored file_paths (including without an explicit repo_root)
Tests added

relative changed files without repo_root
literal _ / % filename matching
shared-basename suffix collision
deleted isolated community cleanup
rename vs fresh full-rebuild parity for flows, memberships, and community assignments
Locally, the focused lifecycle suites and related flow/community/incremental tests are green. CI checks are waiting on maintainer workflow approval — happy to follow up if anything still fails after that.

@tirth8205

Copy link
Copy Markdown
Owner

Verified head 97caf68 locally: the rebase resolved the incremental.py conflict, the full suite passes (2314 passed, 5 skipped, 2 xpassed), ruff is clean, and the #569 repro is fixed: incremental_trace_flows(store, ["b.py"]) now finds the flow when stored paths are absolute. Two items remain before merge.

  1. The Windows-separator LIKE pattern in expand_changed_file_paths is wrong. f"%\{escaped}" produces the SQL pattern %<first-char>..., and under ESCAPE '' that backslash escapes the first character of the filename instead of matching a literal backslash. The pattern degenerates to an unanchored suffix match. Verified: expand_changed_file_paths(store, ["b.py"]) also returns /repo/ab.py, and ["utils.py"] also returns /repo/data_utils.py, with or without repo_root. Unrelated files that merely share a basename suffix get their flows cleared and re-traced on every update, and with postprocess="none" their flows are deleted without being re-traced. Use an escaped backslash (%\ in SQL) for the separator anchor or drop that pattern and normalize stored backslash paths, and add a basename-suffix collision test (b.py vs ab.py).

  2. The community parity assertions are partly vacuous. test_incremental_delete_clears_orphan_community_rows computes clean_ids from the fresh rebuild and then discards it (_ = clean_ids), and test_incremental_rename_parity_with_full_rebuild ends with assert _assigned(store) >= 0, which is always true. Please assert real equality of community assignments between the incremental store and the clean rebuild for the deletion and rename cases.

Minor: incremental_update builds lifecycle_inputs variants that duplicate what expand_changed_file_paths already does, so paths are expanded multiple times per update, and clear_flows_for_files re-runs the node/flow ID queries that capture_affected_flow_entry_points just executed. Worth collapsing while you are in there.

@helasaoudi
helasaoudi force-pushed the fix/569-incremental-flow-lifecycle branch from 048061f to 6450e28 Compare July 31, 2026 12:06
@helasaoudi

Copy link
Copy Markdown
Author

Thanks for the revalidation on 97caf68 — the two remaining items (plus the minor cleanup) are addressed on head 6450e28
(rebased onto current main, conflicts clear).

  1. Windows-separator LIKE / basename-suffix collisions

Separator pattern is now a correctly escaped literal backslash: %\… under ESCAPE '' (f"%\\{escaped}" in Python).
Path expansion also goes through shared POSIX normalize_file_path (#774), so matching uses the same identity as stored graph paths.
Added coverage that b.py does not match ab.py, and utils.py does not match data_utils.py (with and without repo_root).
2. Community parity asserts

test_incremental_delete_clears_orphan_community_rows and test_incremental_rename_parity_with_full_rebuild now assert real equality of community assignments / signatures between the incremental store and a fresh full rebuild (no discarded clean_ids, no vacuous >= 0).
3. Minor: collapse duplicate work

incremental_update passes raw lifecycle inputs once through expand_changed_file_paths.
clear_flows_for_files returns entry-point QNs from a single _flows_touching_files query (no separate capture + clear round-trip).
Focused lifecycle / path-matching tests pass locally. Happy to follow up if anything still fails after CI runs on this head.

@tirth8205

Copy link
Copy Markdown
Owner

Verified head 6450e28 against current main: merge is clean, full suite passes (2408 passed, 5 skipped, 2 xpassed), ruff is clean, and all ten new tests fail without the fix. Two items block merge.

  1. mypy fails: flows.py:738 reuses batch across the expanded-paths loop (list[str]) and the affected-ids loop (list[int]). The type-check CI job rejects this. Rename one loop variable.

  2. expand_changed_file_paths still over-matches. The %/name LIKE fallback runs even when repo_root is provided, so a changed root-level util.py also matches an unchanged left/util.py. Repro: both files hold flows, then incremental_update(changed_files=["util.py"]) with postprocess none deletes the left/util.py flow and nothing re-traces it. Skip the LIKE fallback when repo_root is known; that also removes three unindexed scans per changed file, about 2s per 300 files on a 50k-node graph.

Minor: the new helpers commit an ambient transaction before BEGIN IMMEDIATE while store_flows rolls back; pick one convention.

@helasaoudi

Copy link
Copy Markdown
Author

Thanks for the revalidation on 6450e28 — the two blockers (plus the minor transaction note) are addressed on head bd17845.

1. mypy batch reuse

  • Renamed the loop variables in incremental_trace_flows so path expansion uses path_batch: list[str] and affected flow IDs use id_batch: list[int] (no shared batch across incompatible types).

2. expand_changed_file_paths over-match with repo_root

  • The %/name LIKE fallback now runs only when repo_root is unknown.
  • With repo_root set, matching uses exact absolute/relative variants only, so root-level util.py no longer matches left/util.py.
  • Added coverage:
    • test_expand_with_repo_root_skips_nested_same_basename
    • test_incremental_root_basename_does_not_clear_nested_flows (incremental update / postprocess-none path)

3. Minor: ambient transaction convention

  • New helpers now roll back ambient transactions before BEGIN IMMEDIATE, matching store_flows / store_communities (with the same warning).

Locally, the focused lifecycle/path-matching suites, mypy on the touched modules, and ruff on the changed files are green. Happy to follow up if anything still fails after CI on this head.

@tirth8205

Copy link
Copy Markdown
Owner

Changes required: a failed replacement parse removes a valid existing flow while retaining its old nodes. With CRG_SERIAL_PARSE=1, build a routes.py containing def handler(): return helper() and def helper(): return 1 using full_build, save trace_flows with store_flows, append another function, then call incremental_update(..., changed_files=['routes.py']) while CodeParser.parse_bytes raises RuntimeError; get_flows changes from one flow to an empty list on this PR. Preserve flow and community state until replacement parsing succeeds.

@tirth8205
tirth8205 changed the base branch from main to staging September 15, 2026 13:12
@tirth8205

tirth8205 commented Sep 15, 2026 •

Copy link
Copy Markdown
Owner

This no longer merges into staging. Conflicts in code_review_graph/incremental.py.

git fetch origin && git merge origin/staging
# resolve, then
uv run pytest tests/ -q
uv run ruff check code_review_graph/
uv run mypy code_review_graph/ --ignore-missing-imports --no-strict-optional
git push

When resolving:

  • Rebase onto current origin/staging and resolve code_review_graph/incremental.py: keep staging's known_text/stored_paths=True/content-mismatch flow and layer the PR's remove=False deferral, stale-file union, and remap/purge calls on top of it.
  • After rebase, re-run the full gate set (pytest 3.13, ruff, mypy) and the edge-case review; none of that was assessed here.

A large integration branch landed on staging today, which is why this drifted.

PRs now target staging, not main. Yours was retargeted already, so nothing to do there.

@tirth8205 tirth8205 added the needs-rebase Branch no longer merges into staging label Sep 15, 2026
@tirth8205

Copy link
Copy Markdown
Owner

This no longer merges into staging. 2 files conflict and the branch is 319 commits behind:

  • code_review_graph/incremental.py
  • code_review_graph/tools/build.py

Worth knowing before you resolve:

  • code_review_graph/incremental.py: staging added a build-state checkpoint at the tail of full_build and moved the Git timeout into constants.py.
git fetch origin
git merge origin/staging
# resolve, then
uv run pytest tests/ -q
uv run ruff check code_review_graph/
uv run mypy code_review_graph/ --ignore-missing-imports --no-strict-optional
git push

I have not reviewed the change itself yet. That comes once it merges and the checks run against the merged state, since staging has moved a long way and the result is what matters.

Capture and clear affected flows before file node IDs churn, remap
community assignments by qualified name, and normalize relative git
paths against absolute graph file_path values.

Fixes tirth8205#569
Add maintainer-requested lifecycle parity cases for tirth8205#569: relative
paths without repo_root, underscore/percent filename literals, suffix
collisions, deleted-community cleanup, and rename vs full-rebuild parity.
Escape the Windows backslash separator in expand_changed_file_paths so
relative matches stay boundary-anchored (b.py no longer hits ab.py).
Collapse duplicate lifecycle path expansion and flow ID queries, and
assert real community-assignment equality vs a clean rebuild.
Align expand_changed_file_paths with tirth8205#774 so incremental matching uses the
same file_path identity as the graph store after rebase onto main.
Avoid clearing nested same-basename flows during incremental updates,
fix the mypy batch type reuse, and align ambient-transaction handling
with store_flows rollback convention.
Cover the reviewer repro where a failed incremental parse must keep
existing flow and node state until a later successful replacement.
@helasaoudi
helasaoudi force-pushed the fix/569-incremental-flow-lifecycle branch from bd17845 to ab4d2d1 Compare October 9, 2026 14:55
@helasaoudi

Copy link
Copy Markdown
Author

Addressed the latest review items on head ab4d2d1 (rebased onto current origin/staging).

Rebase / conflicts

  • Rebased onto current staging.
  • Resolved code_review_graph/incremental.py and code_review_graph/tools/build.py.
  • Kept staging’s known_text / stored_paths=True / content-mismatch flow, and layered this PR’s remove=False stale deferral, stale∪missing removal, and remap/purge lifecycle on top.
  • Kept staging’s advance_to_postprocess_pending checkpoint wiring in build.py while preserving #569 entry-point / community capture handoff.

Failed replacement parse

  • Flows are no longer cleared before parse.
  • clear_flows_for_files now runs only after a successful replacement parse (or for permanent deletions).
  • A failed parse_bytes keeps existing flows and nodes intact.
  • Added test_failed_replacement_parse_preserves_existing_flows for the CRG_SERIAL_PARSE=1 repro.

Local checks on the touched suites: 223 passed / 4 skipped; mypy clean on touched modules; ruff clean. Happy to follow up after CI on this head.

@helasaoudi
helasaoudi force-pushed the fix/569-incremental-flow-lifecycle branch from ab4d2d1 to 7d825eb Compare October 9, 2026 14:58

This branch has not been deployed

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

Labels

needs-rebase Branch no longer merges into staging

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Incremental flow re-detection never finds new entry points (relative vs absolute path mismatch)

2 participants