Change-contract pipeline: one verdict envelope for every change source - #93
Merged
Conversation
edit_file and write_file now parse the candidate content with tree-sitter before the atomic swap and refuse a write that would leave the file more syntactically broken than it already was. Only a regression blocks — editing an already-broken file, or one whose language the gate cannot parse, is never refused. Pass allow_parse_errors=true to override; set GORTEX_EDIT_PARSE_GATE=0 to disable globally.
Lowers a set of (file, line-range) pairs to the graph symbols those ranges touch — the input adapter for any range-shaped change source (editor selection, LSP code-action range, diff hunk). Adds fileSymbolIndex.enclosingForRange, which returns the smallest enclosing symbol at each covered line so a range inside one function yields that function and a range spanning two yields both.
One envelope every change source lowers into: a WorkspaceEdit (speculative
simulation with broken-caller detection), a git diff range, an explicit symbol
set, or file line-ranges. Runs LOWER -> PREDICT -> EVALUATE (guards +
architecture) -> SCORE (blast x centrality) -> CLASSIFY (behavioral /
structural / runtime_drift / metadata_only) -> EMIT, returning one verdict
{allow|warn|refuse} with reasons, risk, a verification_command, and a checkable
stop_condition. The analysis advises; a pretooluse hook decides.
When a change touches a heavy symbol, the envelope's edit_strategy now names a refactoring technique (Extract Method / Introduce Parameter Object), ranks its complexity impact, and lists graph-derived safety signals (covering tests, tracked callers, broken-caller status) — so the gate offers the safe path, not just a refusal.
Co-change omissions surface files the changed set historically moves with but left out (mined file-level co-change edges, thresholded). The lens=api mode focuses the verdict on the public surface: each changed exported symbol reports its cross-file consumers and participating API contracts, so a diff between two refs (source=diff base=…) flags breaking-change risk and contract drift on the API it touches.
…t globs Generalises the change_contract evaluator to run every registered RuleFamily over the changed set (guards + architecture today; events / taint plug in the same way). Guard and architecture rules gain a severity tier — error maps to a change_contract refuse, warn to warn, info to annotate (default warn, so a rule advises until it opts into blocking) — and an except-glob list that exempts matching paths.
A declarative events: block in .gortex.yaml constrains the pub/sub graph — which paths may produce or consume a topic, whether a produced topic must have a consumer, and which paths are forbidden from it. EventBoundaryFamily walks the emit / produces_topic / listens_on / consumes_topic edges of each changed symbol and plugs into change_contract as a RuleFamily like guards and architecture.
analyze kind=suggest_boundaries turns the Leiden community partition into a starter architecture: block — each community becomes a candidate layer (named from its common path prefix) and the observed cross-community call edges become its allow list. Outputs ready-to-paste .gortex.yaml that the change_contract architecture family enforces, so the graph bootstraps its own layer rules.
…ories Load-bearing symbols (high fan-in / centrality) can require a prior, TTL'd impact-review ack: change_contract risk_gate:true refuses a changed set whose gated symbols lack a fresh ack. The ack is stored as a development memory keyed on the symbol (change_contract ack:true), so it is graph state that survives restarts and surfaces for the next agent. TTL and thresholds are env-tunable; the gate is opt-in.
explain_change_impact and detect_changes now always return by_depth_counts (the '47 affected, 3 at depth-1' headline) and page the heavy by_depth rows: offset / limit serve a window in depth order, and summary_only drops the rows entirely. Default limit keeps existing callers' shapes intact while giving large blast radii a cheap summary by default.
safe_delete_symbol gains a propagate mode: instead of refusing when a symbol still has callers, it builds a per-caller plan — standalone statement calls are removed outright (each removal parse-gate validated so it can't corrupt a file), embedded references are flagged for manual patching. dry_run returns the plan; applying it patches the removable sites and then deletes the symbol, so the delete leaves no dangling reference behind.
edit_file gains expected_occurrences: when set, the edit refuses unless old_string matches exactly that many locations, so a wrong-cardinality sweep (replace_all hitting more or fewer sites than intended) is caught before it writes rather than after.
- config round-trip: mirror the new GuardRule severity/except fields. - syntax-health test: pass allow_parse_errors so the post-write health warning (not the new pre-write parse gate) stays the assertion under test. - suggest_boundaries: satisfy staticcheck (Fprintf) and ineffassign, drop a dead map.
Adds the change-contract capability bullet (change_contract / symbols_for_ranges / parse gate / propagate-delete) and lists the new suggest_boundaries analyze kind (now 61).
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.
What
Adds one
change_contractprimitive that any change source lowers into, runs through a single pipeline, and emits one verdict envelope — plus the surrounding edit-safety surface. Built feature-by-feature (one complete feature per commit).The analysis emits a verdict (data the agent reads); a thin enforcement layer (a pretooluse hook) is the only thing that turns a
refuseinto a block. The graph advises; it doesn't wall.New surface
change_contract— lower a WorkspaceEdit (speculative simulation with broken-caller detection), a git diff range (source:diff base:…), an explicit symbol set, or file line-ranges; returns{verdict: allow|warn|refuse}with reasons + confidence, a risk score, a change classification, averification_command, a checkablestop_condition, and anedit_strategy(named refactor technique + CC-impact + safety signals).lens:apifocuses the verdict on the public surface (cross-file consumers + API contracts).risk_gate:truerequires a TTL'd impact-review ack (ack:true, stored as a development memory) for load-bearing symbols.symbols_for_ranges— the standalone lowering primitive: (file, [ranges]) → enclosing symbols.analyze kind=suggest_boundaries— seeds anarchitecture:block from the detected Leiden communities (the graph teaches its own layer rules).Edit-safety changes
edit_file/write_file: refuses an edit that would introduce new tree-sitter parse errors (overrideallow_parse_errors; disableGORTEX_EDIT_PARSE_GATE=0).explain_change_impact/detect_changesreturnby_depth_countsby default withsummary_only/offset/limit.safe_delete_symbol propagate:truepatches surviving call sites (standalone calls removed and parse-gate validated; embedded references flagged manual).edit_file expected_occurrences:Nrefuses unless the match count is exactly N.Verification
go build ./...and the CGOcmd/gortexbinary build green.go testandgo test -race(internal/mcp,internal/analysis,internal/config).golangci-lintclean on the touched packages.cmd/gortexwire-contract golden is unchanged (no graph node/edge schema change).