Skip to content

feat(remediation): wire change_impact output into the remediation loop - #445

Merged
cdeust merged 1 commit into
claude/change-remediationfrom
claude/change-remediation-wire
Aug 24, 2026
Merged

feat(remediation): wire change_impact output into the remediation loop#445
cdeust merged 1 commit into
claude/change-remediationfrom
claude/change-remediation-wire

Conversation

@cdeust

@cdeust cdeust commented Aug 24, 2026

Copy link
Copy Markdown
Owner

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 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 was pure glue — now added and unit-tested:

  • core/change_remediation.pybuild_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.
  • change_remediation_pass.pyremediate_from_impact(matches, memory_by_id, store, reingest_fn) = build_impactedremediate_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 incremental codebase_analyze over 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

  • New feature (non-breaking; pure glue + a convenience composition)
  • Audit-finding closure (partial — cdeust/fleet-watch#110, diff→remediate composition)

Test plan

All 10 change-remediation tests pass locally (ran directly); full suite in CI.

  • test_change_remediation.py: build_impacted attaches changed_refs and preserves memory fields; drops matches with no known memory.

  • test_change_remediation_pass.py: remediate_from_impact splits code-derived→reingest vs hand-authored→flag straight from raw ImpactMatch input.

  • 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 bypassing remediate_impacted's split are all caught.

Coding-standards compliance

  • §2.2 Layer direction: build_impacted is pure core/ (leaf; duck-typed, no matcher import); composition stays in the handler layer.
  • §4.1 / §4.2 sizes within limits · §4.4 ≤4 params · §8 no numeric constants.

Breaking changes

None. New pure function + one convenience wrapper; nothing invoked automatically.

Reviewer checklist

  • CHANGELOG.md — not touched; reviewer preference.
  • No secrets / PII in the diff.
  • CI passes on the latest commit — pending.

Generated by Claude Code

#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
@cdeust
cdeust merged commit 21837bf into claude/change-remediation Aug 24, 2026
1 check passed
@cdeust
cdeust deleted the claude/change-remediation-wire branch August 24, 2026 16:47
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.

2 participants