Skip to content

v0.10.0

Latest

Choose a tag to compare

@jaysonmulwa jaysonmulwa released this 04 Sep 20:12
· 3 commits to main since this release

v0.10.0

Team sync worked in the tests and did not work on two machines. This release is mostly that gap, found by running the thing rather than reading it.

A minor bump rather than a patch, because behaviour changes. A peer's version no longer moves what you have open, flanner init refuses to overwrite a config file it cannot parse, and start and stop do something they previously only described.

The one that mattered

flanner peer pull reported accepted: 1 and wrote nothing. The artifact was fetched, its signature verified, and stored — and then no file appeared in .plans/, no version record existed, and flanner list, the web UI and every MCP tool showed nothing. The function that would have made it real, materialize_version, was called by one test file and by no production code at all.

Two green test suites, one broken product. Every sync test asserted on report.accepted, which was the number that lied.

There is now an end-to-end test that enrols two device identities, joins a workspace, pulls over http between them, and opens the file. With the materialise call removed it still exits 0 and still reports accepted, and fails on an empty listing — which is exactly the gap the old assertions could not see.

Your work is never displaced

An arriving version used to move the plan's current-version pointer on "higher number wins". Your file was never overwritten, but flanner show, the web UI and every agent read that pointer, so a teammate pushing changed what you had open.

It no longer moves. A plan this device has only ever received still tracks along, and accepting a baseline through flanner review moves it deliberately.

Because that leaves an arrival invisible, flanner list now sorts plans with something waiting to the top, names the version waiting, and shows who owns each plan — whoever wrote v1, rather than the hardcoded "user" it printed before.

Where a pulled plan goes

A workspace is a team, and a team has several repositories, so the workspace id alone cannot say which local project a plan belongs to. This used to be .first(): whichever project the query happened to return.

Now, in order: a plan you already hold goes where it lives; then --project, or the project you ran the command from; then a workspace's only project. Anything else is reported rather than guessed at, and the next pull that names one writes what the first could not.

Identity, and not losing it

Once the signing key moves to the system keychain, the file is deleted. From that moment a locked keychain looked exactly like a machine that had never run flanner, and the answer was to generate a new key — silently making it a different device, whose signatures peers reject and whose plans are stranded under an id nothing can produce again.

It now refuses, and names the device it should be. Installs that migrated under an earlier version are caught up on their first successful read.

Things that were destroying work

flanner init read .mcp.json and .claude/settings.json, merged into them, and wrote them back. A file that failed to parse was read as {}, so the write replaced it: every other MCP server, hook and permission, gone. A trailing comma was enough. It now refuses, says which file and why, and installs the rest of the integration anyway.

flanner join bound the project, committed, re-signed every plan into the workspace, and only then mentioned that this device holds no role there. A mistyped id cost a repository its plans' history in a workspace nobody can reach. The check comes first now, and a refusal changes nothing.

Creating a plan committed the row before writing the file, so a failed write left a plan with no versions holding the name — and every retry afterwards was refused as a duplicate, permanently, even once the cause was fixed.

Commands that now do what they say

flanner start and flanner stop ran no process. start printed a config snippet under a comment reading "For now, we'll just show instructions", and stop and status read a pid file nothing ever wrote.

start now runs the MCP server in the background over http on 127.0.0.1, for a client that cannot spawn its own copy over stdio. No option widens that bind: every tool acts with the full authority of whoever started it, and nothing authenticates a caller.

flanner status shows a row per agent — Claude Desktop, Claude Code, Codex — each checked where that agent actually looks. It used to read Claude Desktop's config and call the result "Claude Code", so a correct setup read as "not registered" on the one command a new user runs to find out whether it worked.

Security

The http peer transport listened on every interface by default, and parsed a request body of any size before checking a signature. Both existing limits run after the body is already a dict, so they bound what an authorised peer may store, not what an unauthenticated caller can make the process allocate. --host defaults to loopback now, and an oversized body is refused before parsing.

cryptography widens to <51, taking 50.x, which clears PYSEC-2026-3552.

Also

CI runs the suite on Windows and macOS as well as Linux, with a timeout per test so a hang fails instead of holding the runner.

Five tests assumed the system temp directory sits outside a git checkout, which is not true of every machine. Where it is not, every test covering the no-repo path asserted the opposite of what it claimed.

Full detail in CHANGELOG.md.