feat(remediation): wire change_impact output into the remediation loop - #445
Merged
Merged
Conversation
#444 shipped the remediation *policy* + orchestrator but left the composition (diff -> impacted memories -> remediate) to the caller, noting it needed AP. The AP is ai-architect-mcp-codebase, which Cortex already reaches through ap_bridge (detect_changes / get_impact / ingest_codebase), and change_impact.py already turns a commit diff into ImpactMatch rows whose matched_files ARE the subset of each memory's refs that changed. So the only missing piece is pure glue — now added and unit-tested: - core/change_remediation.py: build_impacted(matches, memory_by_id) attaches each ImpactMatch.matched_files to its memory dict as `changed_refs` — the exact shape remediate_impacted consumes. Duck-typed on the match (memory_id + matched_files) so the policy module stays a leaf (no import of the matcher). - change_remediation_pass.py: remediate_from_impact(matches, memory_by_id, store, reingest_fn) = build_impacted -> remediate_impacted. The one call a caller needs; it supplies the real reingest_fn (incremental codebase_analyze over the changed paths), still validated e2e against AP + a real codebase. Tests (all pass locally; full suite in CI): build_impacted attaches refs and drops unknown-memory matches; remediate_from_impact end-to-end splits code-derived -> reingest vs hand-authored -> flag from raw ImpactMatch input. ruff + craftsmanship clean; no numeric constants introduced. Stacked on #444 (claude/change-remediation); merge after it. Addresses cdeust/fleet-watch#110 (closes the diff->remediate composition gap). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017fnomaXS71hgxMw2zr7HGR
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Stacked on #444. That PR shipped the remediation policy + orchestrator but deliberately left the composition (diff → impacted memories → remediate) to the caller, noting it "needs AP." It turns out the AP is
ai-architect-mcp-codebase, which Cortex already reaches throughap_bridge(detect_changes/get_impact/ingest_codebase), andchange_impact.pyalready turns a commit diff intoImpactMatchrows whosematched_filesare the subset of each memory's refs that changed. So the only missing piece was pure glue — now added and unit-tested:core/change_remediation.py—build_impacted(matches, memory_by_id)attaches eachImpactMatch.matched_filesto its memory dict aschanged_refs, the exact shaperemediate_impactedconsumes. Duck-typed on the match (memory_id+matched_files) so the policy module stays a leaf.change_remediation_pass.py—remediate_from_impact(matches, memory_by_id, store, reingest_fn)=build_impacted→remediate_impacted. The one call a caller needs.So the full loop is now expressible end to end:
change_impact(diff → matches) →remediate_from_impact→ code-derived memories re-ingested (supersede), hand-authored ones flagged.Honest boundary
This closes the logic gap and it's fully unit-tested. What it does not do is run itself: the caller still supplies the real
reingest_fn(an incrementalcodebase_analyzeover the changed paths) and invokes it from the commit path, and that end-to-end behavior is only truly validated against a live AP + a real codebase — same boundary #444 drew. I'm not claiming CI proves the live re-ingest; CI proves the composition logic.Type of change
Test plan
All 10 change-remediation tests pass locally (ran directly); full suite in CI.
test_change_remediation.py:build_impactedattacheschanged_refsand preserves memory fields; drops matches with no known memory.test_change_remediation_pass.py:remediate_from_impactsplits code-derived→reingest vs hand-authored→flag straight from rawImpactMatchinput.ruff+ craftsmanship gate clean; no numeric constants introduced (the# source:lesson from feat(staleness): auto-fire is_stale via a bounded file-existence revalidation pass #443 stays applied).All existing tests pass — deferred to CI.
New tests for new behavior.
Mutation check: not attaching
matched_files, not dropping unknown-id matches, or bypassingremediate_impacted's split are all caught.Coding-standards compliance
build_impactedis purecore/(leaf; duck-typed, no matcher import); composition stays in the handler layer.Breaking changes
None. New pure function + one convenience wrapper; nothing invoked automatically.
Reviewer checklist
Generated by Claude Code