Op catalog
Generated from ago help (catalog version v9) at build time. 33 patch ops.
Tools
status
Load or refresh the workspace snapshot. Returns package and file counts and any type errors. No arguments.
help
Return this versioned op catalog: every patch op’s argument schema, one worked example, and its v1 ceilings, plus short descriptions of the six tools. No arguments.
query
Semantic questions against the typechecked snapshot, dispatched by kind: search (case-insensitive name fragment -> exact addresses), inspect (kind, signature, decl position, doc), refs (every reference, tests included, defs marked), callers/callees (static call-graph edges; a call through an interface reports the interface method), implementations (interface -> implementing types, or type -> satisfied interfaces), doc (doc comment text). Args: kind (required), pkg, sym, q (name fragment, for kind=search, falls back to sym), offset (page offset for list results). Lists are position-sorted and paged 50 at a time: count is the total found; a truncated response carries truncated=true and next_offset to pass back as offset.
view
Render a declaration as annotated text. Functions and methods get a per-statement nK: handle prefix plus a generation counter for staleness checks; other declarations (const, var, type) render as plain source. Handles are meaningful only against the generation the same response reports. Args: pkg, sym.
patch
Apply an ordered list of ops as one atomic, generation-checked transaction: every op applies to an in-memory copy, the dirty set re-typechecks once, then everything writes and splices together — or nothing does. Ops compose: an op later in the list can address a handle an earlier op returned, referenced as $1, $2, … by 1-based op index. dry_run runs the identical pipeline and reports accept/reject without writing. Op families (full schemas and examples via help): decl ops (rename, set_body, add_param, upsert_decl, delete_decl, set_doc, add_field, remove_field), statement ops (add_assign, add_call, add_return, add_if, add_for, add_switch, add_case, add_defer, add_go, set_cond, replace_expr, delete_node, wrap_stmts, wrap_error), test ops (add_test, add_test_case, set_test_case, remove_test_case), project ops (delete_file, move_file, add_dependency, remove_dependency, mod_tidy). A decl, test, or project op (rename, set_body, add_param, upsert_decl, delete_decl, set_doc, add_field, remove_field, add_test, add_test_case, set_test_case, remove_test_case) and a statement op cannot edit the same file in one patch; run them as separate patches. An accepted patch that touched exactly one declaration embeds that declaration’s fresh view (same {text, nodes, generation} payload the view tool returns) under “view”, so back-to-back edits need no view call in between; when several declarations were touched the response carries views_omitted instead. Args: pkg/sym (defaults for ops that omit them), generation, dry_run, ops (required, the array of op objects).
test
Run go test, scoped to a package (default the whole workspace) and optionally filtered by name, and return structured per-test results: pass/fail, elapsed time, and captured output for failures. Validation of mutations stays compiler-only; this is how you close the behavior loop after a set of changes. Args: pkg, run (a -run filter).
Patch ops
rename
proves post-splice resolution: every rewritten reference must resolve to the renamed object; reference capture rejects even when the compiler is satisfied
| arg | type | required | description |
|---|---|---|---|
pkg | string | package import path; defaults to the envelope’s pkg | |
sym | string | symbol: Name or Type.Member; defaults to the envelope’s sym | |
to | string | yes | new name |
Example:
[
{
"op": "rename",
"sym": "Double",
"to": "Twice"
}
]
set_body
the coarse escape hatch: replaces the whole block between braces, validated by typecheck like every other op
| arg | type | required | description |
|---|---|---|---|
pkg | string | package import path; defaults to the envelope’s pkg | |
sym | string | symbol: Name or Type.Member; defaults to the envelope’s sym | |
body | string | yes | new body as statements, no surrounding braces |
Example:
[
{
"op": "set_body",
"sym": "Double",
"body": "return v + v"
}
]
add_param
callers updated with default; a top-level local name := <default> in the body is superseded and deleted (parameters share the body scope), any other same-named body declaration is rejected with its position; references to the function as a value (assigned, passed, satisfying an interface) cannot be repaired and are rejected with their positions
| arg | type | required | description |
|---|---|---|---|
pkg | string | package import path; defaults to the envelope’s pkg | |
sym | string | symbol: Name or Type.Member; defaults to the envelope’s sym | |
name | string | yes | new parameter name |
type | string | yes | new parameter type, e.g. context.Context |
default | string | argument expression for existing call sites; required whenever the function already has callers |
Example:
[
{
"op": "add_param",
"pkg": "demo/sig",
"sym": "Scale",
"name": "offset",
"type": "int",
"default": "0"
}
]
upsert_decl
add or replace a whole top-level declaration; goimports runs in the loop. Replacement finds declarations in _test.go files too, and replaces single members inside grouped const/var/type blocks in place (send the standalone form; iota groups and members a following bare spec inherits from reject with the blocker named). New declarations append to an existing package file (test funcs to a _test.go file, so they actually run) and may reference symbols other ops in the same patch introduce; a package with no such file gets agent.go / agent_test.go created on demand — including a brand-new package mid-patch, so one atomic patch can create a package and move declarations into it
| arg | type | required | description |
|---|---|---|---|
pkg | string | package import path; defaults to the envelope’s pkg | |
text | string | yes | complete declaration source, including doc comment if any; the symbol name is parsed from it |
imports | []{path,name} | imports the declaration needs that goimports cannot infer: an aliased import or an ambiguous package name; name is the alias, empty for the default |
Example:
[
{
"op": "upsert_decl",
"pkg": "demo/lib",
"text": "// Double doubles v.\nfunc Double(v int) int {\n\treturn v + v\n}"
}
]
delete_decl
rejected while any non-declaring reference remains outside the declaration itself (a recursive self-call does not count), outside the batch, and outside spans earlier ops in the same patch rewrote; the diagnostics list where. A member of a grouped const/var/type block excises in place (iota and inherited-value members reject, position defines them); a method whose receiver type deletes in the same batch skips the reference guard, the end-of-list typecheck arbitrates
| arg | type | required | description |
|---|---|---|---|
pkg | string | package import path; defaults to the envelope’s pkg | |
sym | string | symbol: Name or Type.Member; defaults to the envelope’s sym | |
syms | []string | batch form: several symbols delete together, so intra-set references (a helper and the test that used it) do not block |
Example:
[
{
"op": "delete_decl",
"sym": "Unused"
}
]
set_doc
doc comment only; replaces an existing one rather than appending, and does not affect the typecheck surface
| arg | type | required | description |
|---|---|---|---|
pkg | string | package import path; defaults to the envelope’s pkg | |
sym | string | symbol: Name or Type.Member; defaults to the envelope’s sym | |
text | string | yes | doc comment body; each line is rendered with a “// “ prefix |
Example:
[
{
"op": "set_doc",
"sym": "Double",
"text": "Double doubles v."
}
]
add_field
appended to the struct’s field list; rejected if the name already exists
| arg | type | required | description |
|---|---|---|---|
pkg | string | package import path; defaults to the envelope’s pkg | |
sym | string | the struct type’s name; defaults to the envelope’s sym | |
name | string | yes | new field name |
type | string | yes | new field type |
tag | string | struct tag, without backticks |
Example:
[
{
"op": "add_field",
"sym": "Store",
"name": "Tag",
"type": "string"
}
]
remove_field
rejected while referenced. v1 ceiling: a field sharing a multi-name declaration (“a, b int”) or an embedded field is not supported
| arg | type | required | description |
|---|---|---|---|
pkg | string | package import path; defaults to the envelope’s pkg | |
sym | string | field: Type.Field; defaults to the envelope’s sym |
Example:
[
{
"op": "remove_field",
"sym": "Config.Legacy"
}
]
move_decl
relocates the whole declaration (doc comment included) and requalifies every reference, adding imports where needed; a type moves together with its whole method set, and one spec of a grouped const/var/type block extracts standalone. v1 ceilings: the declaration must be self-contained (no uses of its old package’s other top-level symbols) and grouped specs may not lean on iota or an inherited value; each rejects with the blocking names
| arg | type | required | description |
|---|---|---|---|
pkg | string | source package import path; defaults to the envelope’s pkg | |
sym | string | top-level declaration name; defaults to the envelope’s sym | |
syms | array | several declarations moving as one set (type + constructor + tests); intra-set references are legal; mutually exclusive with sym | |
to_pkg | string | yes | target package import path (must exist by the time this op runs; an earlier upsert_decl in the same patch can create it) |
create_pkg | boolean | create a missing module-local target package as part of this patch; without it a missing target rejects (typo safety) and offers this flag as a repair |
Example:
[
{
"op": "move_decl",
"pkg": "demo/sig",
"sym": "Fetch",
"to_pkg": "demo/lib"
}
]
set_signature
full parameter/result rewrite: parameters are matched to the old signature by name — carried ones keep each call site’s argument (reordering reorders them), dropped ones drop it, new ones take their default; underscore params pair positionally when their type matches, so widening func(ctx context.Context, _ DecryptFn) keeps the _ argument; a spread call site f(args…) survives insertions before the variadic. Value uses of the function and the body itself are not rewritten: repair them with sibling ops in the same patch or the end-of-list typecheck rejects with the site positions
| arg | type | required | description |
|---|---|---|---|
pkg | string | package import path; defaults to the envelope’s pkg | |
sym | string | symbol: Name or Type.Member; defaults to the envelope’s sym | |
signature | string | yes | the complete new signature as Go text: “(params) results” |
defaults | object | argument expression per NEW parameter name, spliced into every existing call site; required for any new parameter when call sites exist |
Example:
[
{
"op": "set_signature",
"pkg": "demo/sig",
"sym": "Fetch",
"signature": "(ctx context.Context, a int, b string, rest ...int) int",
"defaults": {
"ctx": "context.Background()"
}
}
]
delete_file
removes one file; rejected while any package-level symbol it declares is referenced from outside it (the rejection lists the reference positions). Deleting a package’s last file removes the package
| arg | type | required | description |
|---|---|---|---|
path | string | yes | file path relative to the module root (or absolute) |
Example:
[
{
"op": "upsert_decl",
"pkg": "demo/scratch",
"text": "func Tmp() int {\n\treturn 0\n}"
},
{
"op": "delete_file",
"path": "scratch/agent.go"
}
]
move_file
same-directory moves are pure renames; a cross-package move rewrites the package clause to the target package and drops a now-self import, and is rejected while the file declares symbols referenced from outside it (their qualifiers would all be wrong — use move_decl per declaration instead)
| arg | type | required | description |
|---|---|---|---|
from | string | yes | current file path relative to the module root (or absolute) |
to | string | yes | new file path; a different directory must hold an already-loaded package |
Example:
[
{
"op": "move_file",
"from": "lib/lib.go",
"to": "lib/core.go"
}
]
add_dependency
runs go get module@version against the workspace module; go.mod and go.sum restore byte-for-byte on any later rejection in the same patch. Needs the module in the local cache or network access
| arg | type | required | description |
|---|---|---|---|
module | string | yes | module path, e.g. golang.org/x/sync |
version | string | module version; defaults to latest |
Example:
[
{
"op": "add_dependency",
"module": "golang.org/x/sync",
"version": "v0.10.0"
}
]
remove_dependency
runs go get module@none; rejected while any workspace file still imports the module (the rejection lists the import positions)
| arg | type | required | description |
|---|---|---|---|
module | string | yes | module path to drop |
Example:
[
{
"op": "remove_dependency",
"module": "golang.org/x/sync"
}
]
mod_tidy
runs go mod tidy with the same go.mod/go.sum restore-and-validate wrapper as the other module ops
| arg | type | required | description |
|---|
Example:
[
{
"op": "mod_tidy"
}
]
add_assign
| arg | type | required | description |
|---|---|---|---|
at | string | yes | handle (or $N) to place the statement relative to |
where | string | yes | before | after | first | last (first/last need a block-owning handle) |
lhs | string | yes | assignment target identifier |
rhs | string | yes | right-hand-side expression, parsed and typechecked in scope |
define | bool | use := instead of = |
Example:
[
{
"op": "add_assign",
"at": "n2",
"where": "after",
"lhs": "_",
"rhs": "h(3)",
"define": false
}
]
add_call
| arg | type | required | description |
|---|---|---|---|
at | string | yes | handle (or $N) to place the statement relative to |
where | string | yes | before | after | first | last |
expr | string | yes | a call expression or channel receive (<-ch); assignments belong to add_assign |
Example:
[
{
"op": "add_call",
"at": "n2",
"where": "after",
"expr": "fmt.Println(h(1))"
}
]
add_return
arity and result types are checked against the enclosing signature at end-of-list typecheck, not here
| arg | type | required | description |
|---|---|---|---|
at | string | yes | handle (or $N) to place the statement relative to |
where | string | yes | before | after | first | last |
exprs | []string | result expressions, in order; omit for a bare “return” |
Example:
[
{
"op": "add_return",
"at": "n2",
"where": "after",
"exprs": [
"h(2)"
]
}
]
add_defer
| arg | type | required | description |
|---|---|---|---|
at | string | yes | handle (or $N) to place the statement relative to |
where | string | yes | before | after | first | last |
expr | string | yes | a call expression |
Example:
[
{
"op": "add_defer",
"at": "n1",
"where": "after",
"expr": "fmt.Println(\"done\")"
}
]
add_go
| arg | type | required | description |
|---|---|---|---|
at | string | yes | handle (or $N) to place the statement relative to |
where | string | yes | before | after | first | last |
expr | string | yes | a call expression |
Example:
[
{
"op": "add_go",
"at": "n2",
"where": "after",
"expr": "h(1)"
}
]
delete_node
a block-owning statement with children, or an if with an else, is rejected rather than silently discarding content — delete children first
| arg | type | required | description |
|---|---|---|---|
at | string | yes | handle of the statement or case clause to remove |
Example:
[
{
"op": "add_return",
"at": "n3",
"where": "after",
"exprs": [
"h(9)"
]
},
{
"op": "delete_node",
"at": "n3"
}
]
add_if
returns the new then-block’s own handle via $N; there is no v1 handle for a requested else block (view again to reach it)
| arg | type | required | description |
|---|---|---|---|
at | string | yes | handle (or $N) to place the statement relative to |
where | string | yes | before | after | first | last |
cond | string | yes | condition expression |
else | bool | also create an empty else block |
Example:
[
{
"op": "add_if",
"at": "n2",
"where": "after",
"cond": "h != nil",
"else": false
}
]
add_for
empty body, returns its handle via $N. v1 ceiling: no init/post clauses — use upsert_decl/set_body for a classic three-clause loop
| arg | type | required | description |
|---|---|---|---|
at | string | yes | handle (or $N) to place the statement relative to |
where | string | yes | before | after | first | last |
cond | string | condition expression; mutually exclusive with range | |
range | string | a full range clause, e.g. “k, v := range coll”; mutually exclusive with cond |
Example:
[
{
"op": "add_for",
"at": "n2",
"where": "after",
"cond": "h(0) \u003e 0"
}
]
add_switch
empty body; extend with add_case
| arg | type | required | description |
|---|---|---|---|
at | string | yes | handle (or $N) to place the statement relative to |
where | string | yes | before | after | first | last |
tag | string | switch tag expression; omit for a tagless switch |
Example:
[
{
"op": "add_switch",
"at": "n2",
"where": "after",
"tag": "h(1)"
}
]
add_case
always appends as the last clause (v1 has no argument for placing a case among existing ones); returns the new case’s body handle via $N
| arg | type | required | description |
|---|---|---|---|
at | string | yes | handle (or $N) of the switch statement to extend |
exprs | []string | case expressions; mutually exclusive with default | |
default | bool | make this the default clause; mutually exclusive with exprs |
Example:
[
{
"op": "add_switch",
"at": "n2",
"where": "after",
"tag": "h(1)"
},
{
"op": "add_case",
"at": "$1",
"exprs": [
"1",
"2"
]
}
]
set_cond
a case clause’s whole expression list is replaced as one; v1 has no per-element case-expr addressing
| arg | type | required | description |
|---|---|---|---|
at | string | yes | handle (or $N) of the if/for/case to retarget |
expr | string | yes | replacement condition |
Example:
[
{
"op": "add_if",
"at": "n2",
"where": "after",
"cond": "h == nil"
},
{
"op": "set_cond",
"at": "$1",
"expr": "h != nil"
}
]
replace_expr
v1 ceiling: an if/for/case condition or a whole expression statement only; per-argument sub-expression handles are future work
| arg | type | required | description |
|---|---|---|---|
at | string | yes | handle (or $N) of the target node |
expr | string | yes | replacement expression |
Example:
[
{
"op": "add_call",
"at": "n2",
"where": "after",
"expr": "h(1)"
},
{
"op": "replace_expr",
"at": "$1",
"expr": "h(2)"
}
]
wrap_stmts
from/to must be direct siblings, in order, of the same statement list; returns the new node’s handle via $N
| arg | type | required | description |
|---|---|---|---|
from | string | yes | handle (or $N) of the first statement to enclose |
to | string | yes | handle (or $N) of the last statement to enclose |
with | string | yes | if | for | block |
cond | string | condition; required for with=if/for, forbidden for with=block |
Example:
[
{
"op": "wrap_stmts",
"from": "n1",
"to": "n2",
"with": "if",
"cond": "helper(1) \u003e 0"
}
]
wrap_error
the Go idiom automated end to end: binds err, inserts “if err != nil { return …, fmt.Errorf(…) }”. v1 ceiling: a bare expression-statement call resolves its return arity only for a same-package function identifier
| arg | type | required | description |
|---|---|---|---|
at | string | yes | handle (or $N) of the assignment or expression-statement call to wrap |
message | string | yes | context prefix for fmt.Errorf(“…: %w”, err) |
Example:
[
{
"op": "wrap_error",
"at": "n1",
"message": "fetch"
}
]
add_test
scaffolds a table-driven test: case struct derived from the target’s signature, rows slice, range+t.Run loop. v1 targets a plain function, not a method; address the generated test by name in follow-up ops
| arg | type | required | description |
|---|---|---|---|
pkg | string | package import path; defaults to the envelope’s pkg | |
target | string | yes | the function under test; defaults to the envelope’s sym when omitted |
name | string | test function name; defaults to Test<Target> |
Example:
[
{
"op": "add_test",
"target": "Double"
}
]
add_test_case
values are expression atoms, typechecked against the case struct at end-of-list typecheck
| arg | type | required | description |
|---|---|---|---|
pkg | string | package import path; defaults to the envelope’s pkg | |
test | string | test function name (from add_test); defaults to the envelope’s sym | |
name | string | yes | case row name |
args | []string | argument expressions, in target parameter order | |
want | []string | expected-result expressions, in result order (wantErr last, if the target returns error) |
Example:
[
{
"op": "add_test",
"target": "Double"
},
{
"op": "add_test_case",
"test": "TestDouble",
"name": "positive",
"args": [
"2"
],
"want": [
"4"
]
}
]
set_test_case
| arg | type | required | description |
|---|---|---|---|
pkg | string | package import path; defaults to the envelope’s pkg | |
test | string | test function name; defaults to the envelope’s sym | |
case | string | yes | existing row’s current name |
name | string | new row name; defaults to case (no rename) | |
args | []string | replacement argument expressions | |
want | []string | replacement expected-result expressions |
Example:
[
{
"op": "set_test_case",
"test": "TestScale",
"case": "one",
"args": [
"3",
"3"
],
"want": [
"9"
]
}
]
remove_test_case
| arg | type | required | description |
|---|---|---|---|
pkg | string | package import path; defaults to the envelope’s pkg | |
test | string | test function name; defaults to the envelope’s sym | |
case | string | yes | row name to remove |
Example:
[
{
"op": "remove_test_case",
"test": "TestScale",
"case": "one"
}
]