Skip to content

v2.3.0

Choose a tag to compare

@github-actions github-actions released this 17 Aug 02:24
· 54 commits to main since this release

Added

  • Fettler ΓÇö file,
    search and edit tools for a model and a script, with no shell in between.

    The project is Fettler; the command is fettle. It performs the operations
    an assistant actually carries out on a source tree ΓÇö find, search, read,
    write, edit, replace, move, copy, delete, extract, and run a declared task ΓÇö
    as one program with a command line and an MCP front end over one dispatcher.
    It ships as a self-contained single-file download per OS, attached to this
    release beside the .vsix and sparky. It needs no .NET on the target
    machine and none of Shoddy to run.
  • A boundary you declare, and nothing outside it exists.
    A path outside every declared tree is refused with exit 4 ΓÇö identically
    whether or not it is there
    , so a refusal cannot be used to ask what is on a
    disk. There is no working directory, no path that climbs out, and no shell
    to reach past it with. So an assistant cannot drift into a sibling checkout,
    a vendored copy or an archived branch and answer from it fluently. Damage is
    the obvious half; grounding is the half that bites quietly.
  • Seven permissions, per tree and per folder. list read create update rename delete execute, stated in a .fettler.json a person writes. A scope
    replaces what its tree grants rather than adding to it, so it can take a
    permission away ΓÇö which is what makes "may reorganise, may never destroy"
    sayable, and what makes a folder inside a readable tree genuinely invisible.
    execute is never a default anywhere, including the tree the configuration
    sits in. See the boundary.
  • The tool cannot write the files that say what it may do. .fettler.json
    and .fettler.local.json are refused by every write path at every
    permission level, with their own outcome governed and exit code 11.
    Without it the permission model is decoration: the escape was two commands
    long ΓÇö write a task declaration, then run what you just wrote.
  • Nor the files that say what the assistant may do. .mcp.json,
    .claude/settings.json, .claude/settings.local.json and
    .vscode/mcp.json refuse on the same terms. No boundary has to be breached
    to reach one: a project's assistant configuration lives in the project, so
    the file governing the assistant and a file the assistant may write were the
    same file. A model that can edit its own deny list has no deny list. Matched
    on the last two segments, so a bare settings.json and
    .vscode/settings.json stay ordinary; CLAUDE.md is left out because it
    persuades rather than permits. setup still writes all of them.
  • A write that would introduce a credential is refused ΓÇö outcome
    credential, exit 12. It judges the diff, not the file: only a secret
    absent from the previous content stops a write, so a config that already
    holds a key stays editable, which is the difference between a rule people
    keep and a rule people switch off. Two tiers ΓÇö issued credentials whose
    shape their issuer fixed, and a secret-shaped name assigned a long,
    high-entropy, whitespace-free value that is not a reference, so an empty
    password and a value naming an environment variable both pass. The
    refusal never repeats what it found
    : a line number and a detector name
    only, because a message naming the secret writes it into the log and the
    transcript. --allow-credential is offered by the command line and by
    nothing else. It is a safety net, not a boundary ΓÇö base64 the value or
    split the string and every rule is defeated. It stops the ordinary
    accident, not an adversary.
  • fettle doctor answers, in one screen, whether Fettler is registered
    with each client at each level, whether the binary it names can be
    launched, and what on this machine still lets an assistant go round the
    boundary ΓÇö a pre-approved sed -i, an allow on a built-in editor, an
    instructions file that has never heard of Fettler. It writes nothing.
    fettle setup writes the wiring; it is the one verb a model is never
    offered, because it names the command that launches the server.
    Setting it up.
  • It reads more than source. Notebooks as cells, PDFs as text per page,
    images as facts plus a native image part, and archives as a manifest ΓÇö
    --member NAME reads one entry out without unpacking anything. --tail N
    is the end of a log without needing its length. extract unpacks into a
    declared tree, refused whole or not at all, with zip slip refused by
    construction rather than by a check bolted on.
  • One version, stamped on every binary the release builds. The mill's
    banner, sparky's serverInfo and fettle --version now answer the same
    number, from one place. Before this, nothing stamped a version at all.

Changed

  • The gate covers all four lanes, not one of them. scripts/shoddy.ps1 gate now proves core, fettler, the MCP host and the MAUI host, plus the
    harness's own self-test, both shipped archives driven from outside the
    repository, and the headless floor. Three of those four suites used to run
    nowhere but a workflow
    , and every one of those workflows is path-filtered
    ΓÇö so a change outside their paths triggered none of them.
    Directory.Build.props, which sets the version every one of those binaries
    reports, matched no filter at all. The workflows remain as the backstop; the
    gate is no longer a claim about one quarter of the repo.
  • A receipt names the tree it was earned against, not the commit.
    gate --resume used to key on the SHA alone, and on a dirty tree it skipped
    all 72 steps and printed a pass in a tenth of a second, describing a tree
    from before the morning's work. publish was never at risk ΓÇö it demands a
    clean tree and a receipt for the exact commit it will tag ΓÇö but a maintainer
    reading that pass was being told something false. Receipts now key on the
    commit plus a digest of everything uncommitted.

Fixed

  • Debug and Release stopped sharing one mill's bin/. Every
    configuration wove to the same mills/<name>/bin DLL, so a Release build
    starting while the previous step's test host was still exiting failed its
    final move with "Access to the path is denied". Neither existing guard
    reached it: MillGate serializes writers across processes, and
    weave-beside-then-rename protects a compiler reading the previous weave ΓÇö
    but the holder here had the assembly loaded, and Windows holds a loaded
    assembly against rename until its process exits. No lock can fix that, so
    the configurations stop sharing the path instead. Set ShoddyBinScope on a
    lane that builds one mill in two configurations at once; it is empty by
    default, so nothing else changes. See
    hosting.
  • The Fettler page claimed an atomicity the code does not promise. A batch
    resolves all-or-nothing ΓÇö every file read, hash-checked and every
    substitution located before the first byte moves ΓÇö but the writing itself
    can still fail halfway on a full disk or a file another process holds open.
    The page now says so, and says what you get instead: the file it stopped at
    and how many had already been written, which identifies the survivors
    exactly rather than leaving them to be guessed.
  • mills/pac-vt100/mill.manifest had lost julian. A generated file
    describing the mill as it used to be, and the only thing in the repository
    that noticed was the MAUI host lane's build ΓÇö which nothing local ran and
    which CI only runs when hosts/maui/** changes. A file in mills/ was
    breaking a lane in hosts/, connected by nothing that would ever fire.
    verify-manifests.js now checks every manifest that exists on every gate
    run.