Repository navigation
v0.7.0
obelize can now move a dependency that no one has written a pack for. obelize upgrade finds what is
behind, makes a pack from two releases of the library, tries the move in a copy of your repository
with your own tests and writes it only if no test that passed now fails. With your own coding agent
it also moves the places a pack can only flag, and it can commit the result to a branch. Around it
are the pieces that let a pipeline and a coding agent use obelize: SARIF, --fail-on, an MCP
server, a GitHub Action and a pre-commit hook. Everything new is reached by a command or a flag,
apart from the few changes listed under Changed, and what the three bundled packs write is what it
was.
Benchmark
I did not run round 1 again. Its numbers are still 0.1.0's and are for the Gemini pack alone: 25 of
277 usages (9.0%) migrated in 20 repositories with no wrong edit. I did take Gate 1's scan and the
three dry runs again with this release, over the same 80 repositories each at the same commits.
All 320 records are the ones 0.6.0 gave, finding for finding and withheld row for withheld row, so
they now read obelize 0.7.0 and nothing else in them moved.
The commands added in this release have no benchmark of their own. Four runs of upgrade on three
public repositories (docs/UPGRADING.md) passed their
own tests before and after the move. They show that the steps join up, not how often a move
passes.
Unsupported patterns
docs/KNOWN_ISSUES.md has a new section on upgrading a dependency. What it
names:
- A trial installs packages and runs their build scripts, and your tests, as you on your machine. The
question it asks first is the only gate: nothing sandboxes them. - A library that keeps its import name across the move is one distribution for both APIs, so
upgradewrites to the whole repository or to none of it. A trial of a real repository ends in
exit6until the places a pack only flags have been moved, by you or by--agent. - A name is found through its module (
pydantic.BaseModel.parse_obj), not through an object whose
class obelize cannot tell, such as a subclass you wrote.User.parse_obj(...)and.dict()are
not seen, and a method that is deprecated but still works shows only if a test breaks. - A parameter that was renamed, removed or made required is flagged and never rewritten.
- A requirement with extras (
mcp[cli]>=1.28.1) is left as it was, and the summary and the commit
message say so. - The environment for the release in use holds today's resolution of the dependencies you did not
pin, so a test that fails there for that reason is not blamed on the move. - A module that makes its names when it is imported (pydantic 2's top level, for one) is counted as
names obelize could not judge, not as removed. - A failing test whose error names no line of your code cannot be put to the agent.
- The agent is asked about the places a pack flags only when the first run of the rules wrote
nothing, as it does for a library that keeps its import name. Where the rules wrote some files, it
sees only the places where tests then broke. - A flagged place in a test file is put to the agent like any other. Only the repair rounds leave
test files alone, and the commit message names every file the agent edited. - A library that changed more names than a pack file holds (pandas) gets a pack for the names your
code reaches through its imports, and a name reached any other way is not in it. - It reads Python only, the trial needs uv, and
claudeis the one agent program it can use. The
agent's tests run on Linux and macOS, not on Windows.
The formats that changed are pack.yaml (source.type: api_diff, and rename_symbol's renamed and
calls, with symbols no longer required), the model proposal (imports, and the reason
no_pack_for_usage), findings.json, plan.json and run.json (the reason no_pack_for_usage,
packs[].source: inventory and verify.edited), the user config (network and agent) and the
JSON Schemas, which gain outdated.schema.json. pack synth writes a new file, hints.json,
beside the pack it makes, and a trial writes a new folder, .obelize/trials/<id>/, with a
trial.json that no schema describes yet.
Added
obelize scan --sarifprints the findings as SARIF 2.1.0, which GitHub code scanning reads.obelize scan --fail-on <status>exits4when a finding has the status you name (eligible,
needs_revieworunsupported), and the run'sexit_coderecords it.obelize verify --jsonandobelize undo --jsonprint therun.jsonandundo.jsonthey wrote.obelize scan --library <distribution>lists every place a repository uses a library that has
no pack, with--moduleto name the modules when the library is imported under another name.
Each place isneeds_reviewwith the new reasonno_pack_for_usageand nothing is rewritten. The
run recordssource: inventoryinpacks[]and holds no copy of a pack.obelize pack new <id> --from "<requirement>" --to "<requirement>" --guide <url>makes a pack
that loads, with fixtures and sources to edit.obelize pack test <id|path>runs a pack over its own fixtures and sources: the checks the three
bundled packs pass in the test suite, now available for a pack of your own.obelize verify --currentruns a run's verification on the tree as it stands instead of
answeringtree_changed, and records the files that differ from what the run wrote in
verify.edited(a new field ofrun.json, empty for any other verification). An--applyrun
that wrote no file can be checked this way, so a migration that an agent made by hand can be
verified.obelize mcpserves obelize to a coding agent over the Model Context Protocol, on standard input
and output and with no new dependency. Seven tools:obelize_scan,obelize_plan,
obelize_apply,obelize_verifyandobelize_undorun the commands and return what they print,
obelize_reportreturns a run'sREPORT.md, andobelize_residuelists the places obelize could
not migrate with the code around each. No tool takes a command, a path, a model or a consent, and
--read-onlyleaves out the two that write files.action.ymlis a GitHub Action that runs a scan, annotates each place on its file and line,
uploads the findings to code scanning if asked and fails the job on the statuses you name.
.pre-commit-hooks.yamloffers the same scan as the hookobelize-scan.obelize pack synth <distribution> --from <version>makes a pack for a library that has none. It
asks the package index for two releases and, for the names that left, the newest release of each
minor line between them (at most six, newest first, until each name is found), opens each for its
Python source and the metadata that names its modules, reads what each exports without importing
or building anything, and writes a pack: a name the library's deprecation message says to replace
is renamed, a name moved up out of a submodule is moved, everything else that changed is flagged
with the library's own message where it gave one, and the dependency line moves. The pack must
passpack testor it is not kept. Every host is printed before it is contacted, answers are
cached for you alone, and--offlineuses only the cache.- A function is not flagged only because one of its parameters is deprecated or because a
parameter's default became a sentinel such as_Unset: the library decides the value
elsewhere, which a name cannot show. Two old names that become one new name are flagged and not
renamed. - A wheel's compiled library does not stop the archive from being read (numpy), and a fixture
names at most 60 of a large library's names. --used-by <path>makes the pack for the names the code in a folder reaches through its
imports, for a library that changed more names than a pack file holds (pandas, for one).
upgradedoes so by itself and keeps the pack for that repository. A star import of the library
is refused, since it leaves no telling which names are used.
- A function is not flagged only because one of its parameters is deprecated or because a
obelize outdatedlists the dependencies a repository declares that the package index says have
moved on: the release in use, the newest, the size of the gap, whether the library marks itself
inactive, the release was withdrawn or has an advisory, and what can migrate it (a bundled pack,
or two releases to compare). It lists a dependency only if it has a flag or a major gap, unless
you pass--all.--jsonprints the rows, described by the newoutdated.schema.json.obelize upgrade <distribution>shows what moving one dependency to a newer release would change:
it uses the pack that migrates it, or makes one from the two releases and keeps it, then plans the
migration asfixdoes without--apply. It writes no file of your code, only its run folder
under.obelize/. With no dependency it lists those thatoutdatedshows and obelize can
migrate, and asks which to move; where no one can be asked it prints the list and exits2.obelize upgrade <distribution> --apply --yestries the move before it writes it: in a copy of the
repository and two environments made with uv (the release in use, and the release moved to), it
runs the repository's own tests before and after the migration, compares them test by test and
runs the ones that broke once more. The move is written only if no test that passed now fails; the
evidence is.obelize/trials/<id>/. It asks first, refuses a tree with uncommitted changes to
tracked files, and exits3for tests that broke and6for a trial that could not judge.
--timeout <seconds>sets how long the tests may run, and tests that did not finish are reported
as that.--offlinekeeps uv to its cache.--branchcommits a passed move to a new branch
instead of the working tree, with git's plumbing: no checkout, no hook, no signature, no push.obelize upgrade <distribution> --apply --branch --agent claude_codeputs the places a pack only
flags to your ownclaudeprogram as a text-only model (every tool, session, MCP server, skill and
setting of yours off, and an environment cut down to the variables a login and a network need,
listed in the privacy notes), checks each answer with
the guard, writes it to the trial's copy, lets the rules write the rest and your tests judge. It
needs--branch, so nothing a model helped write reaches your working tree; the commit message
names the files it edited.agent.backendandagent.modelin your own configuration do the
same where--apply --branchis given, and a repository's.obelize.ymlmay not setagent.- A place the pack flags is sent with the new names closest to the removed one, which
pack synth
keeps inhints.jsonbeside the pack, and the dependency line follows the places the agent
moved. A dependency line the rules cannot write is named in the summary and the commit message. - An answer may list
imports, up to four plainimportorfrom ... importstatements of the
pack's target modules, which the guard adds at module level, to the file's ownfrom m import ...line when it has one. A file's places are asked about from the bottom, each where the
earlier answers left it, and the import of a name is covered once every place that uses it moved. - An answer may be that a place needs no edit (
"unchanged", with a sentence). The place is
settled and nothing is written for it, and the evidence and the commit message name such places:
only the tests check them. - In a library that has one release for both APIs, a place left in a file the agent moved does not
hold the dependency line. - Tests that passed before the move and fail after it are put to the agent too, with the error and
the line of non-test code the traceback points at, for up to--max-roundsrounds (3). A test
that fails because its module would not import is put with the line pytest points at. The rounds
never edit a test file.
- A place the pack flags is sent with the new names closest to the removed one, which
~/.config/obelize/config.ymltakesnetwork.indexandnetwork.offline, and the environment
OBELIZE_INDEX_URLandOBELIZE_OFFLINEdo the same for one run.OBELIZE_INDEX_AUTHORIZATION
holds the value of anAuthorizationheader, sent to the index host and no other. A repository's
.obelize.ymlmay not setnetwork: it is refused, asmodelis.source.typeof a pack may beapi_diff, the type of a pack made bypack synth. A bundled pack
never is.- griffelib is the seventh runtime dependency. It reads a release's API in a child process and is
loaded by no other command.
Changed
- A
rename_symbolchange may carryrenamed, a name that stays in its module and takes
another spelling, andcalls: allowed, which lets a call of a renamed name be rewritten. The
defaults leave every pack as it was, and the pack schema,docs/PACK_SPEC.mdand the negative
corpus describe the new fields. obelize undoskips a recorded path in.git/or.obelize/asoutside_root: a run never
writes either, so a run folder that names one was forged.- A model's proposal may carry
imports, as an agent's may, and the guard lets a replacement name
classmethod,staticmethod,property,superandobject. - A scan that finds no pack to apply names the libraries the repository imports most and the
command that lists one. - The package description, keywords and README say what
upgradedoes, and the development status
is Beta.