Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 78 additions & 0 deletions docs/architecture/change-signature.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
---
description: "How cgr changes a function's parameter list and rewrites every call site the graph knows per an explicit mapping, listing what it could not map."
---

# Change Signature

`change_signature` is edit-algebra operation 2 (issue #1533). The
definition changes and its callers in other packages do not: that is the
most-cited cross-package breakage. The graph knows every call site with its
argument shape ([edge-site properties](graph-schema.md#edge-site-properties)),
how each edge was resolved (`resolution`), and the declared parameter
types (`param_types`), so every site is rewritten per an explicit mapping
or listed as unmapped.

```bash
cgr change-signature myproj.pkg.util.helper -p a@0 -p n:int=1 -p b@1 --dry-run
cgr change-signature myproj.pkg.util.helper -p a@0 -p n:int=1 -p b@1
```

The MCP tool `change_signature` takes `qualified_name`, `new_params` (a
list of the same specs), and the optional `allow_heuristic`, `dry_run` and
`project`, and returns the report as JSON.

## The mapping

`new_params` is the new parameter list in order. Each spec is
`name[:annotation][=default][@source]`:

| Spec | Meaning |
|-----------------|-----------------------------------------------------------------------------------------------|
| `a@0` | Keeps old positional parameter 0 (receiver excluded) as `a`; the old text (annotation, default) is carried over. |
| `key@old` | Keeps the old parameter named `old`, renamed to `key`. |
| `n:int=1` | A new parameter; every site that passes nothing for it gains the literal `1`, and the definition gets `n: int = 1`. |
| `b:str='x'@1` | Keeps old parameter 1 with a new annotation and default. |
| `extra` | A new parameter with no source: unmapped. Sites that pass no value for it are left untouched and listed. |

Old parameters that no spec names are dropped; the values sites passed for
them are dropped with them.
Comment on lines +37 to +38

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.

P2 Correct removed-value behavior

The guide says values supplied for omitted old parameters are dropped. The mapper instead treats a remaining supplied value as a surplus argument and leaves that call site unchanged. This is a non-blocking documentation concern, but users may otherwise expect incompatible calls to be rewritten automatically.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment on lines +37 to +38

## What is rewritten

- **Definitions**: the parameter list of the definition and of every method
in its override hierarchy (`OVERRIDES` edges, both directions). In Python
a required parameter placed after one with a default is refused.
- **Call sites**: each `CALLS` site of every hierarchy member. Positional
values follow the new order; a value the site passed by keyword keeps its
keyword; once a site relies on an old default that is no longer last,
every later value is spelled by name (Python only; a language without
keyword arguments leaves such a site unmapped). A keyword-only site that
keeps every name is left exactly as written.
- **Unmapped**: sites the mapping cannot complete (no value for a required
parameter, a spread or splat argument, an unknown keyword, a language
without keyword arguments where they would be needed), sites without a
recorded location, and sites resolved by guesswork (`heuristic`,
`overload`, `dynamic`) unless `--allow-heuristic`. Each carries the
reason.

## Type checks

A default literal is checked against the declared type of the parameter
it fills, from the new annotation or the graph's `param_types` of the old
parameter it maps from. For the builtin names (`int`, `float`, `str`,
`bool`, `list`, `dict`, `set`, `tuple`, unions of them, `None`) a literal
of the wrong kind is refused before anything is written; an annotation the
graph cannot interpret (a class, a generic) is not checked.

## Atomicity and contract

All edits are span-preserving [patcher](patchers.md) edits staged in one
[edit transaction](edit-transactions.md); a file that no longer parses rolls
the whole change back. Applied changes are recorded for `cgr edits undo`
and held to the [postcondition contract](postcondition-contract.md): every
call site of the changed signature must be mapped (a rewritten site is
mapped by construction) or listed as unmapped, no `too_many` finding may
remain unlisted, no site resolved by guesswork may have been rewritten
without leave, and no duplicate group or import cycle may appear. A failing
contract undoes the transaction and re-ingests the restored files; the
verdict, with the tests reaching the changed symbols, rides on the report.
Comment on lines +69 to +78

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.

P2 Qualify contract enforcement

The guide promises postcondition enforcement and a verdict containing affected tests for every applied change. Without a re-ingest callback, including for an unindexed MCP project, an edit can apply without contract enforcement and return no verdict. This is a non-blocking documentation concern, but users may rely on validation and rollback guarantees that are unavailable in that configuration.

T-Rex Ran code and verified through T-Rex

1 change: 1 addition & 0 deletions docs/guide/mcp-server.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ cgr daemon up
| `importers` | Modules that import a module, with each import statement's line, column, bound alias and imported symbol. Deterministic: fixed graph queries, no LLM, same graph gives the same JSON. Use this instead of query_code_graph whenever you know the exact name or location. |
| `tests_reaching` | Test functions and methods from which a qualified name is reachable through CALLS / REFERENCES / INSTANTIATES, with the distance and the symbol each test reaches it through: what to run after editing it. Deterministic: fixed graph queries, no LLM, same graph gives the same JSON. Use this instead of query_code_graph whenever you know the exact name or location. |
| `rename` | Rename a function, method, class or other definition everywhere the graph knows it is referenced: the definition, every call, reference and instantiation site, import statements (aliases kept), overrides in both directions, and Python `__all__` entries. Refuses when any site was resolved heuristically, by overload fan-out, or only by a trace, unless `allow_heuristic` is true. A class referenced by an inheritance or type annotation edge that carries no rewrite location refuses regardless of `allow_heuristic`. Every rewritten file must still parse; otherwise nothing is written. Set `dry_run` to see the plan and diff without touching the tree. Applied edits are recorded for `cgr edits undo`, and Markdown mentions of the old name are listed for a human to review. |
| `change_signature` | Change a function's or method's parameter list and rewrite every call site the graph knows per an explicit mapping. `new_params` lists the new parameters in order as `name[:annotation][=default][@source]`: `@2` takes the value from old positional index 2, `@old` from the old parameter `old`, `=literal` without a source inserts that literal where a site passes nothing (and becomes the definition's default), and a bare name is unmapped: sites passing no value for it are left untouched and listed. Overrides in the hierarchy are rewritten too; sites resolved by guesswork are listed as unmapped unless `allow_heuristic`. A default literal that does not fit the declared type is refused. Applied through one transaction and held to the postcondition contract; set `dry_run` to see the plan. |
| `query_code_graph` | Prefer the deterministic tools (resolve, definition, callers, callees, implementors, overrides, importers, tests_reaching) when you know the exact name or location: they run fixed queries with no LLM. Use this for open-ended questions. Query the codebase knowledge graph using natural language. Ask questions like 'What functions call UserService.create_user?' or 'Show me all classes that implement the Repository interface'. Pass `project` to restrict results to one indexed project; use list_projects for the available names. Omit it to search them all. The scope is enforced on the results before they are capped, so it holds regardless of the query generated and the result limit is spent on rows from the requested project. |
| `get_code_snippet` | Retrieve source code for a function, class, or method by its qualified name. Returns the source code, file path, line numbers, and docstring. |
| `surgical_replace_code` | Surgically replace an exact code block in a file using diff-match-patch. Only modifies the exact target block, leaving the rest unchanged. After a successful write on an indexed project, the touched files are re-ingested and a structural delta is appended to the result as JSON: symbols added, removed and renamed, callers left dangling, call sites passing too many arguments, signature changes with a verdict per call site, new duplicates of existing functions, new import cycles, and the tests reaching the edited symbols. A project that is not indexed appends nothing, and a failed analysis appends an error note instead of a delta. Read it before the next edit. |
Expand Down
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ nav:
- Edit Transactions: architecture/edit-transactions.md
- Rename Operation: architecture/rename.md
- Postcondition Contract: architecture/postcondition-contract.md
- Change Signature: architecture/change-signature.md
- Advanced:
- Adding Languages: advanced/adding-languages.md
- Ignore Patterns: advanced/ignore-patterns.md
Expand Down
Loading