diff --git a/docs/architecture/change-signature.md b/docs/architecture/change-signature.md new file mode 100644 index 0000000000..f709f68aec --- /dev/null +++ b/docs/architecture/change-signature.md @@ -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. + +## 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. diff --git a/docs/guide/mcp-server.md b/docs/guide/mcp-server.md index d2191d9ab4..b09fa48668 100644 --- a/docs/guide/mcp-server.md +++ b/docs/guide/mcp-server.md @@ -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. | diff --git a/mkdocs.yml b/mkdocs.yml index 3f2a7052cf..2c36b0f970 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -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