Changelog
0.18.1 — 2026-08-02 — Adopt hub standards (first fairyfox check) + compliance pass (PATCH)
First-ever fairyfox check-for-updates run on this node (seeded 2026-07-22 from the
despawned-items sibling, not the hub). Hub is at 1.6.2 (b87b4d4). Full account:
notes/fairyfox-reports/2026-08-02-adopting-updates.md.
- Adopted 8 missing standards into
notes/reference/(pre-authorized by the hub's standingadopt-standards-by-defaultledger grant; pure additions, no clobber):checklists-are-contracts,mandate-ledger,ci-secrets,legal-docs,docker,farm-operating-model,new-project-setup,onboarding-existing-project. - Refreshed 7 behind-hub standards to hub-verbatim (zero node-unique lines lost):
agent-tooling,coins,dependencies,engineering-quality,git-workflow,notes-system,testing. - Reconciled 10 divergent standards to hub-verbatim (
notes/reference/is verbatim-hub by design; only-node lines were path-noise + 2 sibling-ahead snippets kept in the report as upstream candidates):adopting-updates,badges,compliance,cross-project-sync,deployment,planning,process-reports,repo-hygiene,self-hosted-assets,supply-chain-hardening. - Adopted the README standard as
readme-standard.md(renamed to avoid the Windowsreadme.md/README.mdcollision). Not adopted (N/A):VERIFY-INDEX+build-verify-index.mjs(hub tooling). Every applicable hub 1.6.2 standard is now carried. - Compliance pass green: link gate
31 files OK;npm test193/193 with c8 at 99.02% lines / 91.56% branch (all four metrics above the 90% gate); VERSION == latest tag;devcontainsmain; no parked TODO/FIXME insrc/. Scorecard remains 6.9 (time-gated) per status.md. - Docs/standards-only change — no
src/or agent code touched.
0.18.0 — 2026-07-25 — Craft + place + build goals (MINOR)
The last goal-level intents in plans/goal-level-autonomy-2026-07.md (owner Esc. 11: "give it a
recipe to do and have it make the recipe … ask it to build/make and/or place something"). Core
Mineflayer — no new dependencies — so the goal-autonomy plan is now complete end to end.
- Bot registry (
src/bot.mjs):craft(corerecipesFor/craft, table-aware — a crafting table in reach unlocks 3x3 recipes, else the 2x2 grid),place(equip the block, place against the block below the target via coreplaceBlock),build(place a list of{block,x,y,z}— composesplace). All by name, resolved throughbot.registry.itemsByName/blocksByName. - Capabilities + goals: the bot executor advertises
craft/place/build;src/goals.mjsgainscraft/place/build(through the sharednativeGoal— delegate to the native capability, else refuse with a clear error; no composition path for the client agent yet, so a missing capability is named, not glued). Reachable in scenarios via the existinginvokeaction. - MCP tools:
goal_craft(item, count?),goal_place(block, x, y, z),goal_build(blocks[]), all via the sharedgoalToolhelper. - Real-machine proof
scripts/craft-smoke.mjs+ CI jobcraft-smoke(on the sharedwithBootedBotharness): give the bot planks, craft a crafting_table (inventory 16→12 planks +1 table), place a block (12→11), build a 2-block stack (11→9) — the inventory deltas prove real crafting + placing through the goal engine. Verified locally end to end. - Tests: 193 driver tests — bot craft (table/no-table/unknown-item/no-recipe/no-name), place
(equip+placeBlock, missing-item/non-numeric/nothing-below/no-name), build (loop + empty + unknown
bot); the executor's new capability set; goals delegate + gate; protocol dispatch + a client-agent
refusal. c8 99.0% lines / 91.6% branches — gate (≥90 all four) held.
check-links,npm audit(0),build-docsgreen.
0.17.0 — 2026-07-25 — Gather + hunt goals (chop / attack) (MINOR)
The next goal-level intents on the backend-agnostic engine (plans/goal-level-autonomy-2026-07.md,
owner Esc. 11): "chop down a tree", "hunt down a mob", using tools/weapons. Delegated to the mature
Mineflayer plugin ecosystem (the library-first standing directive), exposed as capabilities the goal
engine plans over — never hard-wired to a backend.
- Two new runtime deps, pinned + audit-clean:
mineflayer-collectblock@1.6.0(path → auto-equip best tool → mine → collect the drop) andmineflayer-pvp@1.3.2(approach → attack on cooldown). Compatible with ourmineflayer@4.37.1/mineflayer-pathfinder@2.4.5;npm auditstays at 0. Per the fresh inbound-content threat model, each added dep is treated as attack surface and gated. - Bot registry (
src/bot.mjs) gainsgather(nearest matching block by name,counttimes, reporting what it got),hunt(optional weapon equip → nearest matching mob in range → attack until the entity is gone/killed or a timeout, always stopping pvp), andequip(item by inventory name → destination). Real plugins loaded in the bot factory (src/index.mjs). - Capabilities + goals: the bot executor advertises
gather/hunt/equip;src/goals.mjsgainsgather/huntgoals that delegate to the native capability — and, absent it, refuse with a CLEAR error (no primitive-composition path exists yet for the Fabric client, so a missing capability is named, not glued — the anti-hack contract). Reachable in scenarios via the existinginvokeaction. - MCP tools:
goal_gather(targetId, block, count?, maxDistance?),goal_hunt(targetId, mob, count?, weapon?, maxDistance?),bot_equip(botId, item, destination?). - Real-machine proof
scripts/combat-smoke.mjs+ CI jobcombat-smoke: boot a real Paper server, join a real Mineflayer bot,setblockan oak log and gather it (assertingoak_loglands in the bot's inventory), then give a sword,summona chicken and hunt it (asserting a kill) — all through the goal engine, no mocks. Verified locally end to end. - Tests: 186 driver tests — bot gather/hunt/equip (success, timeouts, no-plugin / unknown-block /
no-mob / attack-reject / missing-name errors), the executor's new capability set, goals delegate +
capability-gate, and protocol dispatch + a client-agent refusal. c8 99.0% lines / 91.6% branches —
gate (≥90 all four) held.
check-links,npm audit(0),build-docsgreen.
0.16.0 — 2026-07-25 — Security hardening: inbound / untrusted content (MINOR)
Owner flagged (2026-07-25) a second trust boundary the posture hadn't addressed: not "can something reach in?" (covered — stdio-only, loopback+token, agents off by default) but "what does the tool process from a server/client/download it connects to?" One hostile server, malicious plugin/mod, or tampered artifact can feed crafted input to code we run. Named every surface and hardened the two the tool could silently expose.
- Threat model
notes/security/threat-model.md— surface-by-surface (untrusted server → the in-process Mineflayer packet parser; hostile server → agent-enabled client + control-plane amplification; co-resident malicious plugin/mod reading the loopback token; supply chain of downloaded Paper / deployed plugin jars;build_gradle= arbitrary exec by design; console authority; DoS buffering), each with blast radius, current mitigation, residual risk, and operator guidance + a short "brace list".SECURITY.mdgains an "Inbound / untrusted content" section;CLAUDE.mdsecurity model +decisions/architecture.mdrecord it as a standing boundary. - Bot loopback-by-default (
src/bot.mjs): a Mineflayer bot parses the server's packets IN the driver process, sobot_join(and the scenariobotactor) now refuse a non-loopback host unlessallowRemote: true— the in-process parser can't be aimed at an untrusted remote by accident.isLoopbackHostexported + unit-tested;allowRemotewired through the tool + scenario provisioner. - Paper jar SHA-256 verification (
src/paper.mjs):resolvePaperDownloadcarries the fill API's publishedchecksums.sha256;provisionServerhashes the bytes as they stream to disk (single pass,createHashtee) and fails closed on a mismatch — deletes the jar and throws, so a tampered/corrupt server jar is never booted. Skips gracefully (logged) if the API ever omits a hash. - Tests: 177 driver tests — bot loopback guard (refuse remote,
allowRemoteopt-in,isLoopbackHostforms), Paper integrity (match keeps, mismatch rejects + deletes, checksum carried through). c8 99.10% lines / 91.95% branches — gate (≥90 all four) held.check-links,npm audit(0),build-docsgreen. No behaviour change for the default loopback/local workflow (bots default to 127.0.0.1).
0.15.0 — 2026-07-25 — Multi-target scenarios: scenario_run (MINOR)
The third pillar of goal-level autonomy (plans/goal-level-autonomy-2026-07.md §3): provision and
coordinate several targets AT ONCE — a Paper server, spawned Fabric client(s), and/or Mineflayer
bot(s) — as ONE lifecycle object with guaranteed teardown, instead of hand-wiring server_start +
client_spawn + bot_join per call. Owner directive (Esc. 12): server-and-client(-and-bot) testing
"at the same time" as a first-class object; MCP-side and backend-agnostic; "no fragile glue".
src/scenario.mjs— a PURE orchestration engine.runScenario(spec, {provisioners, actions}): provisions actors in declared order, runs coordinated steps, and GUARANTEES teardown in reverse order — even when a provision or step throws, and even when a teardown itself throws (errors are collected, cleanup continues). Provisioners (by actor kind) and actions (by step verb) are INJECTED, so the engine never hard-codes a backend and is fully unit-testable against fakes — the same capability-contract, anti-hack discipline asgoals.mjs/flows.mjs.validateScenariofails a structural mistake (empty/duplicate actors, unknown kind/action, a step referencing a missing actor) up front, before anything is provisioned.- Real wiring in
src/index.mjs(thin closures over the live managers): provisioners forserver(provision → deploy plugins → start → wait ready),bot(join + register executor),client(spawn + optionally connect its agent), andagent(attach to an already-running agent); actionswait,invoke(any advertised capability on a bot/agent target — the backend-agnostic cross-target verb),navigate(the goal engine), andserver_exec(server console). A provisioner returns{info, teardown};info.targetIdmarks a controllable executor so a step can drive it. scenario_runMCP tool runs the scenario as a job (heavy — boots servers/clients): the job RESULT is the full report{ok, provisioned, steps, teardown};waitMsoptionally blocks for the finished report inline. A bad spec errors at the tool boundary (not as a job to poll). Backend managers stay injectable, so the protocol suite drives it with fakes.- Real-machine proof
scripts/scenario-smoke.mjs+ CI jobscenario-smoke: through the ACTUAL MCP tool (createServer()with all backends real), onescenario_runboots a local offline Paper server AND a real Mineflayer bot together, coordinates them (server console + bot chat/messages/ status), and the engine tears both down — no mocks. Verified locally (bot spawned in-world; both targets torn down green). - Tests: 174 driver tests — a dedicated
scenario.test.mjsunit-proves the engine (declared-order provisioning, short-circuit on provision/step failure, LIFO teardown, collected teardown errors, validation, the connect-false path) + protocol tests over the in-memory client (server+bot together, spawned-client + attached-agent executors, step-argument guards, boundary validation). c8 99.09% lines / 91.80% branches — gate (≥90 all four metrics) held.check-links29 OK,npm audit0.
0.14.0 — 2026-07-24 — Sliders + typed option values (mixin accessors) (MINOR)
Close the last 0.13.0 not-done item (owner override 2026-07-24: an incomplete/limited API isn't acceptable — sliders need real support, "at the very least a more raw way to access the slider and its values"). Delivered both the raw fraction surface AND typed per-option values.
- Fabric mixin infrastructure (first mixins in the agent):
minecraft-automated-driver-agent.mixins.jsonfabric.mod.jsonwiring, package…fabric.mixin. Loom's remapper handles it (no refmap needed; verified the config + accessor classes land in the remapped jar). Accessors:SliderWidgetAccessor(@Invokerfor the protectedSliderWidget.setValue(double)+@Accessorforvalue) andOptionSliderWidgetImplAccessor(@Accessorfor theSimpleOptionbehind an option slider). This is the remap-safe way to reach protected members — plain reflection by yarn name would break against a production client's intermediary names.
slideragent op (ClientOps.slider, capabilityslider): resolve a slider by label (exact-or-unique-substring) or the sole slider when unnamed, then —fraction(0..1) sets the raw normalised progress on ANY slider (and applies it to the backing option);valuesets an option-backed slider's TYPED value directly (e.g. FOV = 90), coerced to the option's own int/long/float/double via the publicSimpleOption.setValue; neither → read. Always returns{handled,label,fraction,value}. All reachable API verified against the yarn jar withjavap.- MCP:
ui_settings_setnow routes a numeric value to the typed slider set (so "set FOV to 70" works — cyclers/toggles unchanged); newui_slidertool exposes the raw 0..1 fraction get/set on the current screen.src/flows.mjsgainssliderRaw(capability-gated passthrough). - Real-client proof: the client gametest drives the OptionsScreen's FOV slider — sets the raw fraction, then the typed value 90, asserting it applied — on a genuine rendering client (XVFB, CI).
- Tests: 159 driver tests (slider dispatch shape,
sliderRawforward/guard,ui_sliderprotocol success + capability-gated refusal), c8 98.98% lines / 92.55% branches — gate held. JVM: main + test + gametest compile green, unit tests + JaCoCo verification pass.check-linksOK. - Mandate ledger clause 2 now fully done — every option kind is covered: toggles/cyclers by click, sliders + typed option values by the new op. No residual API gap.
0.13.0 — 2026-07-24 — Enter saved worlds by name: world_list/world_select agent ops (MINOR)
Close two of the 0.12.0 not-done items (owner mandate 2026-07-24 repeated → escalate the
executable deferrals). ui_world_enter now works end to end, and numeric ui_settings_set is
wired for a slider capability.
- Fabric agent — two new ops (
ClientOps+ClientControlServer, capabilitiesworld_list/world_select):world_listreturns the saved worlds on the SelectWorldScreen by display name (world-list entries aren't clickable widgets, soscreen/clickcan't see them — this is the clean capability that can);world_selectresolves a world by name (exact-or-unique-substring via the already-testedWidgetIntrospector.resolve, never guessed) and enters it via the entry's publicplay(). Both use only public 1.21.11 API — verified against the yarn-mapped client jar withjavap, compiled locally (compileJava/compileTestJava/ compileGametestJava all green), so no guessed mappings reach CI. JaCoCo gate unaffected (the ops are client-coupled, gametest-covered; the name resolution they reuse is unit-tested). - Real-client proof: the client gametest gains a third phase — back at the title screen it
opens Singleplayer by name, polls
world_listuntil the created world appears, and confirmsworld_selectrefuses an unknown name. (It deliberately does not re-play()a world, which would fight the gametest's own world lifecycle.) Runs under XVFB in CI. - MCP side:
ui_world_enternow retriesworld_selectwhile the async world list loads, then waits for the world —worldEnterno longer needs a separate "Play Selected World" click (the op enters directly).ui_settings_setaccepts a numeric value and dispatches it to aslidercapability when the target advertises one (else a clear error) — the same forward-dispatch pattern, so it lights up the moment the agent grows the op. - Tests: 157 driver tests (flows: slider dispatch + terse-echo fallback, world-enter by name),
c8 98.97% lines / 92.47% branches — gate held (
flows.mjs100% lines / 90.8% branches). - NOT done (S9), with evidence: the slider agent op stays deferred —
SliderWidget.setValueisprotected(needs a Mixin@Invokerto reach it in a remap-safe way; plain reflection by yarn name breaks against a production client's intermediary names), AND option sliders carry per-option value ranges with no generic display-value setter, so a clean "set FOV to 70" op isn't generically expressible. Recorded indocs/control-protocol.md+ the mandate ledger; the MCP dispatch is already in place for when the mixin lands. git-flow releasedev → mainpending the owner's cut.
0.12.0 — 2026-07-24 — Semantic convenience verbs (menus, worlds, connect, settings) (MINOR)
Raise the client-control ceiling from primitives to few-word intents: one call performs the
whole multi-step UI/menu/world/settings sequence (owner mandate 2026-07-24, clauses 2–4 —
plans/mandate-2026-07-24-semantic-verbs.md).
src/flows.mjs— an MCP-side flow engine, sibling togoals.mjs: pure orchestration that composes the client agent's advertised primitives (screen/click/type/key/close/position) into semantic verbs. Backend-agnostic (plans over advertised capabilities, never a hard-coded target), every seam injectable → fully unit-testable with no live client. Verbs:menuNavigate(open/close/traverse menus by name),serverConnect(Multiplayer → Direct Connection → type address → Join → wait for the world),worldCreate(Singleplayer → Create → name → optional game-mode cycle → wait for the world),worldEnter(existing world; needs the agentworld_selectop, a clean forward boundary — 0.13.0),worldLeave(Escape → Save and Quit → wait for the menu),settingsSet(Options → traverse → toggle/cycle a control by label; sliders flagged for the agent slider op). SharedreadScreen/findWidget/waitFor/cycleControlTohelpers. UI labels are overridable inputs with English defaults — i18n/version drift is a call-site override, never a code change.- Six new MCP tools (
src/index.mjs):ui_menu_navigate,ui_server_connect,ui_world_create,ui_world_enter,ui_world_leave,ui_settings_set— each resolves an executor bytargetIdfrom the target registry (thegoal_navigatepattern) and dispatches to the flow engine, so a verb runs against ANY target advertising the needed capabilities. - Library-preference directive (clause 1) reconciled + recorded (
decisions/architecture.md):navigatealready delegates to themineflayer-pathfinderlibrary where a target has one; the custom A* only runs as the composition fallback for a backend with no pathfinding library (the Fabric rendering client). Kept — that's the recorded good reason. New work reaches for a library first. The new UI verbs have no off-the-shelf library (they drive this client's own menus), so composing the agent's real UI is the correct clean approach, not a from-scratch build. - Tests: 26 new unit tests in
test/flows.test.mjs(each verb, ambiguity/failure paths, label overrides, capability-missing rejections,waitFor/findWidget/cycleControlToedge cases) + a protocol test driving theui_*tools over the in-memory MCP client (success + capability- gated refusals). 155 tests, c8 98.96% lines / 92.41% branches — gate held (flows.mjsitself 100% lines / 90.5% branches).check-links27 files OK. - NOT done (S9), disclosed:
worldEnterof a saved world + slider settings need two new Fabric agent ops (world_select, slider set) — 0.13.0. A real-clientflows-smoke(XVFB in CI) to confirm the English label assumptions on a production client is the validation step, queued with 0.13.0. Scenario orchestration (0.12.0 on the founding roadmap) stays queued behind this owner-reprioritised slice.
0.11.0 — 2026-07-24 — The universal navigation goal (A* + goal_navigate) (MINOR)
First real goal on the backend-agnostic engine — "get this target to (x,y,z)", one call, many steps (owner Esc. 11/12).
src/pathfind.mjs— a pure, dependency-free A* planner over a world snapshot (isSolid(x,y,z)): flat steps, one-block step-ups (jumps) and step-downs, goal ranges, an expansion bound so it never spins. This is the universal navigation intelligence — it works for any executor that can describe its surroundings, not just one with a built-in pathfinder. 8 unit tests over synthetic worlds (route, wall-detour, jump, drop, no-path, range, bound).src/goals.mjs— the goal engine.navigate(executor, target)picks a strategy from the target's advertised capabilities: native (navigate— e.g. a Mineflayer bot's pathfinder) or planned (world_snapshot+position+look+move→ A* the path, walk the waypoints). PureyawTo+walkWaypointshelpers. Fully unit-tested against fake executors (both strategies, unreachable, not-in-world, missing-capability).- Native bot navigation:
bot.mjsgainsnavigateviamineflayer-pathfinder@2.4.5(real walk/jump/parkour); the bot executor advertises thenavigatecapability; the real bot factory loads the plugin + sets Movements on spawn. Newgoal_navigateMCP tool. - Real proof:
scripts/nav-smoke.mjs+ anav-smokeCI job boot an offline Paper server, join a real Mineflayer bot, register it as an executor, and drive it with the goal engine (→ native A* pathfinder) to a target ~6 blocks away, asserting it actually moved. The whole stack, no mocks: goal engine → executor → bot registry → pathfinder → live server. - Security (no-parked-findings): the newly-disclosed brace-expansion DoS advisory
GHSA-mh99-v99m-4gvg (affects
<=5.0.7, reaches us only through the c8 dev-tooling chain) is cleared with anoverrides: {"brace-expansion":"5.0.8"}— 5.0.8 is dual-package so minimatch's CJSrequirestill resolves.npm auditclean; c8 coverage unaffected. - 129 driver tests, c8 98.96% lines / 93.75% branches — gate held. Ledger row 30.
0.10.0 — 2026-07-24 — The backend-agnostic capability/executor seam (MINOR)
First phase of goal-level autonomy, built the way the owner directed (ledger Esc. 12): the intelligence goes MCP-side and universal, not bolted onto one backend. This release lays the seam everything else hangs off, so nothing later is a hack.
src/executors.mjs— every controllable target (Mineflayer bot, Fabric client agent, Paper server agent) is wrapped as an executor:{ id, kind, capabilities, invoke(cap, args), has(cap), describe() }. Agent executors advertise the connection's ownwelcome.capabilitiesand dispatchinvoke→request; bot executors map a canonical capability set onto the bot registry. AcreateTargetRegistry()holds them and can find targets by capability (withCapability) — the mechanism the goal engine will use to pick an executor without knowing its type. The capability contract is the anti-hack device: an executor refuses any capability it didn't advertise.- Wired into the driver: connecting an agent or joining a bot registers an executor;
disconnecting/quitting removes it. New
targets_listtool returns every target as{id, kind, capabilities}— one uniform view over bots and agents. - Tests:
test/executors.test.mjs(advertisement, invoke dispatch, unknown-capability rejection, registry find-by-capability, input validation) + a protocol test proving a bot and a client agent both appear as executors, that both exposemove(the universal seam) while only the client exposesslots, and that they drop off on quit/disconnect. 114 driver tests, c8 99.04% lines / 94.3% branches — gate held. No new external behaviour, so no new smoke; the first real orchestration smoke lands with navigation (0.11.0). Ledger row 33.
0.9.0 — 2026-07-24 — Phase 4 complete: in-world driving (walk / aim / containers / text) (MINOR)
The rest of the Phase-4 exit, so the client agent now meets it in full — "drive a client from title screen into a world, walk somewhere, open a container, screenshot it — all by name / by intent, zero pixel coordinates."
- New Fabric client agent ops (all render-thread marshalled, driven by name/intent):
position(player pose +inWorld),look(set yaw/pitch by value),move(hold a movement direction — forward/back/left/right/jump/sneak — across real ticks, capped 5s, reporting the distance walked and always releasing the key),type(set a text field: focused, else sole, else by label),slots(read a container's slots by index: item + count), andclose(dismiss a screen — the Escape equivalent). Thekeyop now also fires an edge (onKeyPressed) so edge-triggered opens (inventory, chat) work by intent, not just held keys. - Driver tools:
agent_position,agent_move,agent_look,agent_type,agent_slots,agent_close_screen.docs/control-protocol.md's Fabric table documents every op. - Proven end-to-end by the real headless client gametest, now two-phase: (1) title
screen — introspect, click by name, screenshot, wrong-token refused; (2) create/join a
singleplayer world via the gametest API, then over the loopback plane read the position
(
inWorld:true), walk forward and assert the position actually changed, aim the camera, open chat by intent and type into it, close it, open the inventory by intent and read its container slots, and screenshot again. Protocol tests plumb every new MCP tool; c8 + JaCoCo gates held (driver 99.19% lines / 94.7% branches). Ledger row 29. - Validated every yarn-API change with a local warm-Loom
compileJava/compileGametestJavabefore CI (the 1.21 async screenshot lesson): caught the gametest source set's intermediary-mapping constraint (no direct Minecraft-client type refs — drive screens via agent ops instead) locally, not on a 15-minute CI cycle. - Recorded new owner directives (ledger Escalation 11, rows 30–32): goal-level autonomy
— A* navigation, "chop a tree" / "hunt zombies" + weapon/tool use, recipe crafting, and
build/place — to land on the L2 Mineflayer lane in the next phases. Plan:
notes/plans/goal-level-autonomy-2026-07.md.
0.8.0 — 2026-07-24 — Close Phase 4 gaps: in-process screenshot + honest coverage (MINOR)
Directive: "dont leave gaps everywhere as you pave new road … keeps things fixed and
developed." Went back and finished Phase 4's road before paving new. Plan:
notes/plans/phase4-gap-closure-2026-07.md; ledger Escalation 10 (rows 27–28).
- In-process framebuffer screenshot (the "screenshot it" half of the Phase-4 exit).
New Fabric agent op
screenshot: grabs the client's rendered frame off the GPU framebuffer (the 1.21 render system reads back asynchronously, so it's a callback kicked off on the render thread and awaited on the control thread;NativeImagehas no in-memory encoder, so PNG bytes come via a short-lived temp file), returns base64 PNG + width/height. Driver toolagent_screenshot. Advertised capabilityscreenshot. Documented indocs/control-protocol.md(which also gained the previously-missing Fabric capabilities table). Proven three ways: the client gametest asserts a real PNG (magic bytes) off a live client; the client-spawn-smoke does the same driver→real-client; a protocol test plumbs it through the MCP tool. c8 + JaCoCo gates held. - CodeQL now actually analyzes the Fabric Java agent. The
analyze-kotlinjob (build-mode manual) only traced-compiledagents/paper, so the Fabric agent's token/handshake/loopback code was unscanned behind a green check — a hollow pass. It now traced-compilesagents/fabrictoo, in the same trace. - Two latent gaps the Gradle build cache was hiding — surfaced by forcing honest,
uncached local builds and fixed: (1) the client gametest referenced the package-private
FabricClientAgent.AGENT_DIRfrom another package, which only compiled from cache — made itpublic(the gametest is a legitimate consumer); (2)Handshake's JaCoCo coverage was actually 86% (below the 90 gate) because the exception-driven POSIX-permission branch was uncovered on every OS — replaced with a deterministic capability check (supports posix) and added a permission assertion, so the gate passes on real measurement (100% line on Linux CI).
0.7.0 — 2026-07-24 — Phase 5 completed: instanced client spawn (spawn→connect→drive→kill) (MINOR)
- The instance mode is real, not deferred (owner call: no shortcut). The DRIVER now
boots a real Fabric client with the agent enabled — no launcher, no Microsoft account —
and owns its whole lifecycle:
client_spawn(runs the agent's LoomrunProductionClientas a job, waits for the agent's loopback handshake to appear in the run dir, returns aconnectDirforagent_connect kind:"fabric"),clients_list,client_kill(aborts the job → kills the client process tree). - New Loom
runProductionClienttask (agents/fabric): a real production client with our remapped mod + fabric-api + the enable flag, fixedrunDirso the handshake lands at a known path, XVFB for headless. Unlike the gametest it stays up until killed. - Tests:
src/client.mjsunit-tested (spawn runs the gradle task + polls the handshake; fails fast if the client dies first; times out cleanly; kill aborts the job) with the run- filesystem faked; protocol tests for
client_spawn/clients_list/client_kill; c8 gate held. Real CI smokeclient-spawn-smoke(XVFB, PRs into main): the driver spawns a real client → reads its handshake → connects over loopback → introspects the title screen → clicks "Options" by name → refuses a wrong token → kills the client. Timeout-capped so a crash fails fast. Ledger row 26. This closes the Phase-5 "instance" mode the 0.6.0 note had deferred.
- filesystem faked; protocol tests for
0.6.0 — 2026-07-24 — Phase 5: the Mineflayer protocol-bot lane (L2) (MINOR)
- L2 lands. New Mineflayer bot lane (
src/bot.mjs) — headless protocol-level players for cheap behaviour/load checks against localonline-mode=falsetest servers. Tools:bot_join,bot_status,bot_chat,bot_messages,bot_move(walk to a block by facing + forward),bot_inventory,bots_list,bot_quit.driver_status→ phase 5, L2 available. - Tests: the registry is unit-tested against a fake bot (join/kick/error/timeout,
status, chat, message ring buffer, move, inventory, quit, graceful degradation) —
the mineflayer factory is injected so no server is needed; c8 gate held. Protocol
tests drive every
bot_*tool over the in-memory client. Real smoke (local + new CIbot-smokejob): provision + boot offline Paper, join with a REAL Mineflayer bot, read state, chat a command, receive the reply, get an item + read inventory, walk, quit, stop.mineflayer4.37.1 +vec3pinned; uuid override clears the transitive advisory (no parked findings). Ledger row 25 (Phase 5). - Deferred honestly (S9): driver-orchestrated instanced client spawn + the
attach-handshake flow reuse the same heavy Loom production-client infra as the Phase-4
gametest and are a focused follow-up;
agent_connect kind=fabricalready attaches to a known client dir.
0.5.0 — 2026-07-24 — Phase 4: the Fabric client agent (the crown jewel) (MINOR)
- Semantic client control lands. New Fabric client agent (
agents/fabric/, Java + Loom) — the second L3 agent, same control-protocol as Paper. - Disabled by default, two enable paths (owner spec, verbatim): the launch flag
-Dfairyfox.driver.enable=true(instanced), or the in-game title-screen "Automated Testing…" button + a confirmation that enables for the game's lifetime only (attach mode; never persisted). Loopback-only, ephemeral port, 256-bit per-session token, handshake file deleted on shutdown. - Drive by name, never pixels:
screenintrospects the current screen into a named widget tree;clickpresses a widget by exact/unique-substring name (ambiguous names refused, never guessed);keyinvokes a keybinding by id. Driver tools:agent_screen/agent_click/agent_key;agent_connectnow handles both the Paper (plugins/…) and Fabric (config/…) handshake layouts via akindparam. - Tests: pure logic (widget resolution, the enable gate, the handshake) JUnit + JaCoCo-gated ≥90; the client-coupled surface proven by a real headless client gametest (Fabric client gametest API + Loom production run + XVFB) that boots a rendering client with the agent enabled and drives it over loopback — introspect the title screen, click a button by name, wrong-token refused. Runs on PRs into main + manual dispatch, timeout-capped. Driver suite extended for the new tools + the Fabric handshake layout.
- CI iteration (all real bugs, none parked): Loom 1.11→1.13 (MC 1.21.11 needs it);
project.propertyin processResources; yarn-1.21.11 API names (client.event.lifecycle,PressableWidget.onPress(Click)built fromMouseInput,KeyBinding.byId); Handshake IOException-catch coverage; and — caught from the gametest crash log — Fabric Loader 0.17.2→0.17.3 (fabric-api 0.141.4 hard-refuses older loaders; the client crashed atKnotClient.mainand xvfb-run left the task idle, which looked like a hang until the log named the real cause). Added a job timeout so any future client crash fails fast instead of idling. - Language: Java (owner call — matches the sibling's Fabric mod, avoids fabric-language-kotlin). Ledger row 24 (Phase 4).
0.4.0 — 2026-07-23 — Phase 3: the control plane is real (MINOR)
- The security model becomes running code. New:
docs/control-protocol.md(one spec for every agent) and the Paper agent plugin (agents/paper/, Kotlin) — the first L3 agent. - Disabled by default, loopback + token. The agent self-disables (Paper's natural
disablePlugin) unless launched with-Dfairyfox.driver.enable=trueor opted in viaconfig.yml. When enabled it binds127.0.0.1ONLY on an ephemeral port with a fresh 256-bit session token, published through a handshake file that's deleted on shutdown. Game-state work is marshalled to the main thread so a request can't wedge the loop. - Driver side:
src/agent.mjs(handshake read, NDJSON client, req/res correlation, event buffer) + toolsagent_connect/agent_state/agent_exec/agent_events/agent_disconnect. - Transport amendment (recorded): NDJSON over loopback TCP, not WebSocket — zero dependencies on both sides, identical security posture.
- Tests: agent — 17 MockBukkit tests, Kover ≥90 gate green (gating self-disable,
handshake, token auth incl. wrong-token refusal, state/exec/events over a real
loopback socket); driver — the full
agent.mjsclient against a real loopback stub- the
agent_*protocol tools. Node suite now 30+ tests, gate held. Real end-to-end smoke passed (local + new CIagent-smokejob): driver builds the agent → boots Paper with it enabled → connects → live state (3 worlds) → console command through the agent → wrong token refused → stop. New CI:agent-build(Kover-gated),agent-smoke, CodeQLjava-kotlin, dependabot gradle entry.
- the
0.3.0 — 2026-07-23 — Phase 2: build/test orchestration is real (MINOR)
- L1 lands. New MCP tools:
build_gradle(wrapper tasks in any checkout, as a job — outcome + built jars in the result),server_provision(auto-download Paper by version from fill.papermc.io, eula + loopback offline flat-world config, plugin deploy — as a job),server_start/server_exec/server_stop/servers_list(boot with "Done"-line readiness, console over stdin, graceful-then-forced stop), and the job model (jobs_list,job_status,job_logring-buffered,job_killvia abort) — landed with its first consumers per the Phase-1 amendment. - Auto-provision by default (owner directive 2026-07-23):
server_startresolves Java itself — a suitable host runtime if present, otherwise a Temurin 21 JRE is downloaded from Adoptium into the managed runtimes dir. CI proves the download path with--force-java-download(deliberately no setup-java on that job). - Tests: 80 across all layers, gate held (99.6% lines, 95%+ branches): unit for
jobs/proc (real child processes)/java/build/paper (scriptable fake server), protocol
for every new tool incl. error surfacing, e2e stdio. Real smokes: locally the
driver gradle-built the sibling plugin, provisioned Paper 1.21.11, booted it, saw
the plugin enable cleanly, drove the console, stopped it; CI adds a
Server smoke (Paper 1.21.11)ubuntu job (auto-provisioned JRE, real boot). - Test-harness lore captured: job fns start on a microtask (fixture ordering), and
never import a test file for its constants (re-registers the suite) —
test/helpers/expected-tools.mjs.
0.2.0 — 2026-07-23 — Phase 1: the OS layer is real (MINOR)
- First working capability layer (L0). New MCP tools:
os_windows_list(every top-level window on the host — vanilla Minecraft clients included, nothing installed),os_screenshot(per-window PNG via PrintWindow/PW_RENDERFULLCONTENT with a screen-copy fallback for stubborn GL surfaces; returns real image content),instance_open/instance_close/instances_list(spawn detached processes, graceful-then-forced close, registry of driver-owned instances beside live windows).driver_statusnow reports phase 1 / L0 available. - Under the hood:
src/config.mjs(optionaldriver.config.json, loud on breakage),src/instances.mjs(registry),src/os/windows.mjs(PowerShell/Win32 backend — runner, spawner, and platform all injectable). Non-Windows hosts get a clear "Windows OS layer" error; other host OSes stay a recorded future item. - Tests: 54 across all layers, coverage gate held (100% stmts/lines/funcs,
~99% branches): unit (config/registry/backend script-building + parsing +
validation + platform guard + the real subprocess runner cross-platform),
protocol (every tool incl. error paths over the in-memory client), e2e stdio, and
a no-mocks OS smoke (spawn notepad → enumerate → screenshot → close) run
locally AND as a new
windows-latestCI job. - Roadmap amendment: the job model moved to Phase 2 (its first real consumers live
there; no-dead-code principle).
zodpinned for tool schemas.
0.1.4 — 2026-07-23 — Project icon live; Codecov + Sonar integrations wired (token-gated)
- The owner's project icon (
assets/icon.png) is now the docs-site favicon (build fails if it's ever missing; regression test asserts it isn't the placeholder) and heads the README. - CI mirrors the sibling's key-backed integrations: Codecov upload (c8 lcov,
non-blocking) and a SonarQube Cloud scan that skips itself when
SONAR_TOKENis absent;codecov.yml+sonar-project.propertiesadded. Owner adds the three secrets (CODECOV_TOKEN / SONAR_TOKEN / SCORECARD_TOKEN) and imports the repo on the two platforms — no further code changes needed. Ledger rows 15–18.
0.1.3 — 2026-07-22 — Themed docs site: onboarding's last project-side gap closed
- Docs site on the fairyfox docs-site standard (Case A, hand-authored): vendored theme
chrome from the sibling node, canonical three-zone subnav, two-way fairyfox.io links,
pages for Overview / Roadmap / Security / Download / Legal (privacy, terms, cookies
matched to actual data practices), and a changelog page generated from
notes/version/at build time — never stale. - Build:
scripts/build-docs.mjs(Node + marked, placeholder-checked, fails loudly);docs.ymlpublishes to GitHub Pages on push tomain→ fairyfox.io/minecraft-automated-driver/. Docs build added to the CI gate. - Tests: docs-build layer added (9 tests total) — placeholder resolution, single-insert regression (shell-comment substitution bug caught and fixed), theme header/footer and back-link presence, changelog completeness. Coverage gate still 100%.
- No generated-API zone yet: no generator exists for the JS server; the boundaried
/api/zone gets added when there is real generated reference to wrap.
0.1.2 — 2026-07-22 — Quality gates live: coverage ≥90%, CI, CodeQL, Scorecard, branch protection
- Owner mandate (same day): thorough testing at every layer, coverage ≥ 90% gated,
Scorecard toward the 7.x range. Delivered:
- Test suite across three layers — unit (version handling), protocol (real MCP
session over the SDK's in-memory transport: identity, tools/list, driver_status
contract), e2e (spawned
src/index.mjs, full stdio JSON-RPC session). 7 tests. - c8 gate wired into
npm test: fails below 90% on lines/statements/functions/ branches (scoped tosrc/). At gate-arming time: 100% on all four. - CI (
ci.yml): npm ci → gated tests →npm audit --audit-level=low(no parked findings) → link check. CodeQL (javascript, security-and-quality). Scorecard workflow publishing to the OpenSSF API. All actions SHA-pinned, top-levelpermissions: contents: read. - Repo hardening: private vulnerability reporting enabled;
mainbranch-protected with required status checks; this release lands via PR with all checks green.
- Test suite across three layers — unit (version handling), protocol (real MCP
session over the SDK's in-memory transport: identity, tools/list, driver_status
contract), e2e (spawned
- Mandate ledger updated (escalation rows 7–12):
notes/plans/mandate-2026-07-22-rename.md.
0.1.1 — 2026-07-22 — Rename: PaperMC Automated Driver → Minecraft Automated Driver
- Owner call: the project isn't Paper-specific (it drives clients, servers, and mods
across platforms), so the papermc- prefix was wrong. New identity everywhere:
human name Minecraft Automated Driver, slug/npm
minecraft-automated-driver, JVM packagesio.fairyfox.minecraft.automateddriver, docs URL and GitHub repo follow the slug. Platform references to Paper/PaperMC-the-server are unchanged. - Mandate ledger:
notes/plans/mandate-2026-07-22-rename.md. - GitHub repo
1fairyfox/minecraft-automated-drivercreated and pushed (dev,main, tags v0.1.0 + v0.1.1) — unblocks the owner's hub registration.
0.1.0 — 2026-07-22 — Project founding: scaffold, standards, roadmap
- Repo created and scaffolded on the fairyfox mesh standards, seeded from the sibling despawned-items node (its locally-modified standards, not the hub originals — divergence to be reconciled upstream on the first fairyfox check-for-updates run).
- Founding architecture + phase plan written:
notes/plans/roadmap-2026-07.md(layer model L0–L3, instance/attach dual mode, gating design, security model, Phases 0–8). - Minimal-but-real MCP server (Node/JS, stdio-only,
@modelcontextprotocol/sdk1.29.0) withdriver_statustool + first test. - Decisions recorded (JS-not-TS host; MC 1.21.11/Java 21 target; loopback+token control plane; agents disabled-by-default; GitHub-only distribution) with rejections (TypeScript; automating the official launcher; marketplace publishing; pixel-first driving).