GitHub Docs

All notable changes to VibeCody are documented here. This project follows Semantic Versioning.

[Unreleased]

Added

  • BugBot now proposes committable fixes, not just comments — vibecli/vibecli-cli/src/bugbot_autofix.rs, docs/bugbot.md. Every competing PR-review bot ends its review with a fix a reviewer applies in one click; BugBot ended its review with prose. It now emits GitHub ```suggestion blocks.
    • Anchors come from the diff, never from the model. GitHub applies a suggestion by replacing the exact lines the comment is anchored to, so an off-by-one anchor silently destroys code. A new PostImage index maps path → new-line → text from the diff’s own context and added lines; a proposal that cannot be located there is refused, not guessed. Seven typed refusals (AnchorMissing, SpanTooLarge, EmptyReplacement, Unchanged, FenceInReplacement, ModelDeclined, Unparseable) are printed with the finding rather than swallowed.
    • The verification level is in the type. AnchorVerified means the target lines were found and the replacement is non-empty and different — nothing more. Every posted comment says the fix has not been compiled or tested, because it hasn’t.
    • --bugbot is a real flag now. bugbot.rs had advertised vibecli --bugbot --diff / --pr 123 in its module docs since it was written; no such flag existed and the only caller was the GitHub webhook. --bugbot reviews uncommitted changes, --staged the index, --pr N a pull request, and it exits 1 on any error-severity finding so it drops into a pre-push hook or CI step. --propose-fixes adds suggestions; --apply-fixes writes them, skipping any file that moved since the diff and printing both counts.
    • --pr refuses a non-GitHub remote instead of guessing a slug that would review an unrelated repository.
  • Full-diff coverage and multi-pass review — ReviewPlan / ReviewCoverage. BugBot sent the first 8 000 characters of the diff and nothing else, so on any PR past a few files everything after the cutoff went unreviewed with nothing said about it. The diff is now split per file, packed into batches that each fit the request budget, and every batch is reviewed — a small diff still costs exactly one call, a 64 KB one costs up to eight. What was read is reported (Reviewed 12/12 file(s) in 3 model call(s)), and when it wasn’t everything, the skipped and truncated files are named on stderr, appended to the commit-status description, and returned as a coverage object on the webhook response.
    • A failed model call is not coverage. A provider error used to be swallowed into an empty finding list, so with the provider down the review printed Reviewed 1/1 file(s) and no findings — an outage rendered as a clean bill of health. The review pass now distinguishes Some(vec![]) (the model looked and found nothing) from None (it never answered); files whose every pass errored are reported as unreviewed, llm_calls_failed is counted separately, and the caveat points at vibecli --doctor. Found by running the command against a scratch repo, not by the build.
    • --passes N trades cost for recall. Each batch is reviewed N times with the file order rotated, because a defect in the last file of a prompt is likelier to be missed than one in the first. Rotation is deterministic — two runs over the same diff issue the same requests, which a randomised ordering could not promise in CI. Findings are deduplicated by location plus a normalised message (so “off-by-one” and “off by one” collapse), keeping the highest severity seen. The call ceiling scales with --passes, so extra passes never cost coverage.
  • 433 Jobs-To-Be-Done skills — the catalogue goes from 710 to 1,143. A national-operating-system skill library imported from Anthropic Agent-Skills format (SKILL.md per directory, name + description frontmatter) into VibeCody’s flat catalogue: 24 sector operating systems (energy-sector-operations, healthcare-sector-operations, …) with their 204 AI-personnel role skills and 27 autonomous-machine skills, 26 industry overlays, 15 cross-cutting role archetypes, 12 strategic missions, 8 subsector packs, 108 catalogue skills (humanoid robots, embodied-AI stack, capability optimisation, simulation training), and 2 routers. Categories map onto the existing vocabulary where one fits (energy, healthcare, defense, finance, logistics, robotics) and add a domain slug where none did.
    • Every relative cross-reference was rewritten, because a flat catalogue has no ../... The source tree navigates by path — 302 references to ../../../00-framework/SKILL.md, 266 to a role’s own ../../SKILL.md, plus directory pointers like _catalogs/humanoid-robots/. Left alone these would send an agent to files that do not exist. Each now names the flat skill (jobs-to-be-done-framework, energy-sector-operations) or the name glob the directory became (humanoid-*). The import refuses to write while any path-shaped reference remains unresolved; it currently reports zero.
    • The 52 references/*.md companion files are inlined, not dropped. They carry the controls, exception tables, and subsector maps that the industry overlays and subsector packs point at. A flat catalogue can hold neither the directory nor the pointer, so each is appended as a ## Reference — <title> section with its headings demoted, and every pointer to it repointed at that section.
    • Triggers are derived, never invented — from the skill’s title, the bold spans its own description uses to name its subject, its domain, and the explicit “trigger this skill when…” sentence the source carries. Triggers are the main signal in skill_matches_query, so no skill ships with none.
    • Pointers to files outside the skill tree (the source-map vault note, docs/capability-routing-matrix.md, tools/capability-router.html, the examples/*.py helpers) were already dangling at the source and are reworded rather than shipped as dead ends.

Fixed

  • list_skills and get_skill re-read the entire catalogue on every MCP call. Both called SkillCatalog::load_from_with_cwd_plugins per invocation — 1,143 file reads, ~990 YAML parses, and a WorkspaceStore::open (which creates the encrypted database, in whatever directory the MCP host happened to launch in) to answer one question. An agent calling list_skills three times in a turn paid it three times. skill_catalog::load_with_cwd_plugins_cached now shares one Arc<SkillCatalog> per directory.
    • Revalidated by fingerprint, not by a timer, so authoring a skill in-tree still takes effect without a restart: file count, combined size, and newest mtime across the directory — measured ~7 ms over 1,143 files against ~60 ms+ to re-read and re-parse them. The directory’s own mtime would have been a single stat, but it does not move when a file’s contents change, which is exactly how skills are edited.
    • The plugin overlay is deliberately not cached — a handful of files whose enablement can change at any time, recomposed per call while the expensive built-in load is shared. When no plugin contributes a skill the shared catalogue is returned as-is, with no copy of its ~5.8 MB of bodies.
    • The WorkspaceStore open is now gated on <cwd>/.vibecli/workspace.db already existing, so the skills path stops creating stray workspace databases in scratch directories.
  • Adding or removing a skill did not rebuild the binary. include_dir! bakes skills/** in at compile time; rustc’s dep-info tracks the contents of the files the macro expanded to, but not the directory listing. build.rs already emitted rerun-if-env-changed directives, which turns off cargo’s default “rescan the whole package” behaviour — so a new skill file changed nothing until something else forced a rebuild. cargo:rerun-if-changed=skills now names the directory explicitly.

  • Every installed build shipped an empty skill catalogue — vibecli/vibecli-cli/src/skills_embedded.rs. list_skills / get_skill, GET /v1/skilllens/skills, and the SkillForge panel returned zero skills on every release binary while working fine in-tree, which read as a regression rather than what it was: the ~710 skills/*.md files were never packaged. The resolver’s primary path was ${CARGO_MANIFEST_DIR}/skills, baked in at compile time, so an installed vibecli looked for /Users/runner/work/vibecody/vibecody/vibecli/vibecli-cli/skills — a GitHub Actions path. The documented next fallback, <exe>/../share/vibecli/skills, was a convention nothing implemented: release.yml tars the bare executable, so no sibling share/ tree ever travels with it.
    • The catalogue is now compiled into the binary (include_dir!) — the only fallback that survives how the binary is actually distributed, a single file copied anywhere. It is extracted once to ~/.vibecli/bundled-skills/<version>/ and loaded from there, so skill.path still names a file that exists and the scoring / body-render paths that re-read it are unchanged. Extraction is version-scoped, guarded by a completeness marker so an interrupted run re-extracts instead of caching a partial tree, and prunes older versions. It is deliberately not ~/.vibecli/skills, which is the promoted-override dir.
    • One resolver, not two. mcp_server and skillforge_index each carried a copy of the fallback chain; both now call skills_embedded::resolve_skills_dir(). VIBECLI_SKILLS_DIR still wins and is used verbatim — an override that silently fell through to the embedded copy would hide an operator’s typo.
    • vibecli doctor reports the catalogue it actually loads. The old check looked at ~/.vibecli/skills — the override dir, not the catalogue — so it printed a benign “no directory” line throughout. It now prints the resolved path, the skill count, which rule chose it, and fails the line when the count is zero.
  • POST /webhook/github failed open when no webhook secret was set. Signature verification ran only if let Some(secret) = …; with none configured, every unsigned POST was acted on. The route is public by design, and a review is not a read — it spends model budget and calls the GitHub API with the operator’s token against whatever repository the payload names, so an unsigned request from anywhere drove both. Unsigned webhooks are now rejected, and the error names the set-key command that fixes it.

  • The GitHub App webhook secret could not be stored encrypted. GithubAppConfig::resolve_webhook_secret reads the ProfileStore key github_app_webhook_secret first, per Zero-Config First — but vibecli set-key validates the name against a fixed list that omitted it, so the command answered unknown provider. The only reachable paths were a plaintext config.toml field and an environment variable, both of which the same rule forbids for a secret. vibecli set-key github_app_webhook_secret <secret> now works, and list-keys shows it.

  • [github_app] auto_fix was a dead flag. It was documented (“push auto-fixes to PR branch”), serialised, defaulted, and covered by three tests — and read by no production code path. It now drives the suggestion pass, and its documentation says what it actually does: it attaches committable suggestions and never pushes a commit. The webhook response gained an honest fixes_proposed count, which excludes findings the fixer declined.

[0.5.8] — 2026-08-10

The largest release so far — 410 commits since v0.5.7. Voice input on every client, SkillForge, the kodegraph code-graph substrate, goal-driven loops, a provider-agnostic embedding layer, and the VibeApp → VibeAIChat rename that brought VibeDesk in as a third desktop shell.

This release adds end-to-end Developer ID code signing for macOS — the CLI binaries were previously shipped with no signature at all, and the three app bundles ad-hoc. Whether a given download is signed depends on whether the build that produced it had the certificate; docs/release.md § Code signing has the one-line check. Signing is not notarization: a signed-but-un-notarized app still prompts on first launch.

Added

  • Embedding models are now provider-agnostic and selectable — crates/vibe-embed. Semantic search, @codebase:, and memory recall previously ran through a two-variant EmbeddingProvider enum (Ollama with a hard-coded 127.0.0.1:11434, OpenAI locked to text-embedding-3-small), while vibe-infer carried a separate Embedder trait that only the candle backend implemented and nothing bridged the two. A new low-level crate collapses both into one trait with one model catalog, shared by the code index, the memory stores, the remote indexer and the daemon.
    • Six providers — Ollama, OpenAI (base-URL overridable, so Azure / LiteLLM / vLLM / text-embeddings-inference need no new variant), Voyage (voyage-code-3 is the strongest code-retrieval option), Cohere, Gemini, and an in-process candle backend registered at runtime so vibe-embed never links an ML toolchain.
    • Models the catalog doesn’t know still work. The catalog is a hint list, not an allow-list — ollama pull some-custom-embed and select it. What it buys is metadata no API reports: the task prefix a model expects, its Matryoshka dimensions, its input limit.
    • Dimension is observed, never assumed. What gets persisted is the length of a vector the model actually returned. A guessed dimension in an index header is worse than an absent one: absent triggers a measurement, wrong triggers silence.
    • Documents and queries embed differently. EmbedKind is a required argument, expressed natively where the provider supports it (Voyage/Cohere input_type, Gemini taskType) and by prefix where it doesn’t (nomic-embed-text, mxbai, bge, e5). Getting it wrong doesn’t error — it silently costs recall, which is why it isn’t defaulted.
    • Indexes are per-model and coexist<workspace>/.vibecli/index/index__<provider>__<model>.json plus a small .meta.json sidecar so listing doesn’t parse gigabytes of vectors. Switching models is instant when the target index exists and switching back never re-embeds.
    • Batched embedding. The previous implementation issued one HTTP request per chunk; a 5 000-chunk repository meant 5 000 sequential round-trips.
    • Surface — four daemon routes (/embeddings/models, /embeddings/embed, /index/status, /index/build), six Tauri commands, a Settings → Embeddings picker that labels each provider local or cloud before you choose, /index-status in the REPL, Agent SDK + VS Code client methods, an embedding block in /health and the startup banner, and docs/embeddings.md.
    • [index] config is finally wired. embedding_provider / embedding_model existed but were referenced only by tests; /index used its own hard-coded literal. An unrecognised provider is now an error rather than a silent fallback to Ollama.
  • Voice input on every client — one daemon route, one shared hook. Before this, the whole voice stack (VoiceDispatcher in voice.rs: Groq Whisper + local whisper.cpp fallback + model download) was reachable only from the REPL, and the daemon had no voice route at all — so VibeDesk, VibeAIChat, VibeMobile, VS Code, JetBrains, Neovim and the Agent SDK had nothing to call, while VibeCoder had quietly reimplemented Groq in its own Tauri command (cloud-only, so it could never run offline).
    • DaemonPOST /voice/transcribe (bearer auth, 16 MB per-route body limit overriding the 1 MB daemon default) accepts either {audio_base64, mime_type, language, prefer_local} JSON or raw bytes with an audio Content-Type + X-Voice-Language / X-Voice-Prefer-Local headers. Seven audio types are recognised; anything else is a 415 rather than a guessed extension. GET /voice/status reports what the machine can actually do, including a can_transcribe that accounts for a downloaded model with no runtime to execute it; the probe (three subprocess spawns) is cached for 60 s. VoiceDispatcher::transcribe_file_with_engine reports which engine ran, so a response can say local_whisper vs cloud_whisper instead of the caller guessing. 7 new serve tests, including one asserting a 2 MB upload gets past the body layer.
    • Shared React hookpackages/vibe-ui-shared/src/voice/ (useVoiceInput, VoiceButton, tauriTranscriber / daemonTranscriber, voice.css). Web Speech API first, MediaRecorder + a pluggable transcriber as fallback. State is a discriminated union (idle | listening | transcribing | error) rather than three booleans, and every failure path now produces a message — the previous VibeCoder-only copy swallowed all of them, so a denied mic, a missing API key, and an unsupported webview were indistinguishable from a dead button. 18 BDD tests.
    • Desktop shells — new crates/vibe-desktop-voice (transcribe_audio / voice_status Tauri commands) registered in VibeDesk, VibeAIChat and VibeCoder, so all three share one daemon bridge. Mic buttons in the VibeDesk composer + side chat and the VibeAIChat chat box. VibeCoder’s inline duplicate hook and its dead src/hooks/useVoiceInput.ts are deleted in favour of the shared one; @vibe/shared is now aliased in its vite/vitest/tsconfig. transcribe_audio_bytes (Groq-only) is removed and the file-based transcribe_audio renamed to transcribe_audio_file to free the shared command name. macOS mic entitlement (com.apple.security.device.audio-input) added to all three, plus Info.plist usage strings for VibeDesk and VibeAIChat — both keys are load-bearing and fail differently.
    • VibeMobileVoiceService (speech_to_text on-device recogniser → record + upload fallback) with a VoiceStatus sum type mirroring the web hook, VoiceMicButton / VoicePartialStrip widgets in the chat and watch-chat composers, ApiClient.transcribeAudio / voiceStatus, iOS NSMicrophoneUsageDescription + NSSpeechRecognitionUsageDescription, Android RECORD_AUDIO and the Android 11+ RecognitionService <queries> entry (without which the recogniser is invisible on every device).
    • Editor plugins — SoX rec capture, matching what VoiceDispatcher::listen has always done for the REPL, so there is one capture path for every non-browser client. VS Code: mic in the chat webview (recording runs in the extension host — webview getUserMedia is unreliable) plus a VibeCLI: Dictate command. JetBrains: mic button in the Chat tool window. Neovim: :VibeCLIVoice toggle (! submits the transcript as a task). All three report a missing SoX with install instructions rather than a generic failure, and stop recording with SIGINT/SIGTERM so SoX finalises the WAV header.
    • Agent SDKagent.transcribe(audio, {mimeType, language, preferLocal}) and agent.voiceStatus(). 9 new vitest cases; 60 total green.

Fixed

  • The code index persisted API keys in plaintext. EmbeddingProvider::OpenAI { api_key } was Serialize, and the index was written as plain JSON — so every .vibecli/index.json built against a cloud provider contained that provider’s key on disk. The index header now stores a model reference only; the embedder (and therefore the key) is supplied at runtime from the encrypted ProfileStore and is #[serde(skip)]. Pre-existing v1 indexes migrate automatically and the credential is dropped in the process, with a test asserting no sk- survives.

  • Changing embedding model silently returned nonsense instead of failing. Three separate holes: the code index recorded no dimension or format version, so a mismatched model scored 0.0 against every chunk and looked empty rather than broken; TurboQuantIndex::insert’s dimension error was discarded at both call sites (compressed_hnsw.rs, embeddings.rs), so wrong-sized vectors were dropped without a word; and vibe-memory’s SQLite had no model or dimension column at all, so a VIBE_MEMORY_DIM change made every existing memory unreachable while the rows sat in the database. Indexes now carry a validated header, TurboQuant’s error propagates, and each memory row records the model that embedded it — search compares only what it can compare and logs what it skipped.

  • VibeAIChat never started the VibeCLI daemon. VibeCoder and VibeDesk both autostart it via the shared daemon_bootstrap; VibeAIChat had no spawn path whatsoever, and its manifest deliberately avoided the vibecli dependency. Since nearly every daemon route is behind require_auth and the bearer token only exists once a daemon has written it, launching VibeAIChat on its own produced a blanket 401 with nothing on screen explaining why. It now uses the same shared module — identity-checked /health reuse, binary resolution beyond bare PATH, poll-to-deadline for the ~16 s cold start, and four distinct failure states.

  • Stale bearer tokens produced a permanent 401 loop. vibecli serve mints a fresh token on every start and all three shells autostart the daemon, so any token held across a restart is dead — and nothing invalidated it. VibeDesk retried on none of its 20 daemon calls, and crates/vibe-desktop-voice (linked into all three shells, so voice input broke everywhere at once) retried on neither of its two. Both now go through a send_authed helper that retries once with a token read straight from ~/.vibecli/daemon.token, deliberately bypassing the explicit-token / VIBECLI_TOKEN precedence — that order is right for a first attempt and exactly wrong for a retry, since the stale value is usually why the request 401’d.

  • VibeCoder chat rendered markdown as raw text. Assistant replies containing tables arrived as walls of | --- | rows and every emphasis as literal asterisks, while VibeDesk and VibeAIChat rendered the same reply correctly. Three causes stacked: AIChat’s renderContent extracted fenced code blocks and dumped all remaining prose into a <pre>; remark-gfm — which is what renders tables — was missing from VibeCoder’s dependencies entirely; and neither the Vite alias table nor the tsconfig let the shared Markdown component resolve its imports. Chat now renders through the same component the other two shells use. Code blocks keep their Apply/copy affordances, and user messages stay literal.

  • Two phantom design tokens meant borders and buttons never rendered. var(--accent) (22 uses across 11 panels) and var(--border) (74 uses, plus --border-default / --border-secondary / --border-primary) were referenced but defined nowhere; CSS drops an undefined var() declaration silently, so those backgrounds were transparent and those borders simply absent. Both now use the real --accent-color / --border-color, which are themed per-theme. Separately, .panel-btn declared no background and no color, so 43 buttons carrying no modifier class fell through to the browser’s native buttonface/buttontext — light grey with black text on a dark panel, which is what made Configuration → Keys look unthemed. The base class is now themed, and all variants share one border box so buttons in a row line up. Three source-scan regression tests guard the lot, with the remaining undefined tokens recorded as an explicit, self-cleaning inventory.

  • JetBrains plugin and Neovim plugin sent no bearer token. Nearly every daemon route is behind require_auth, so /chat, /agent, /jobs and the new voice routes were a silent 100% 401 from both — the JetBrains tool window’s “Error” line and the Neovim “Daemon error” notice were that 401 every time. Both now resolve a token the same way every other client does (explicit config → VIBECLI_TOKENVIBECLI_DAEMON_TOKEN~/.vibecli/daemon.token), and Neovim’s SSE stream appends ?token= since curl-as-argv can’t carry a header there.

  • Goal-driven loops — /loop goal <id-prefix>. Joined the two halves of autonomous execution: a /goal supplies the durable intent + success_criteria, /loop supplies the run-until-done engine. Parsing, prompt rendering, and hydration are pure and live in loop_engine.rs (GoalBrief, hydrate_goal_loop, goal_loop_prompt, goal_validator_prompt, LoopSpec.goal_id#[serde(default)], so pre-existing loops.json jobs still load); the store-touching edge (resolve_goal_brief / goal_brief_by_id / link_goal_loop_start / record_goal_loop_outcome) lives in exec_goal_repl.rs over a new SessionStore::find_goal_by_prefix.
    • Stop condition is the criteria, not the model’s opinion — the worker prompt is explicitly told not to declare completion; a separate validator turn is shown the numbered criteria and answers DONE only if every one verifiably holds. A goal with no criteria still runs, with a warning that judgement falls back to the statement.
    • Only confirmed success writes backDone flips the goal to done; max-iter, wall-clock expiry, Ctrl-C, and iteration errors leave the status untouched (an exhausted budget is not evidence of completion). Every outcome is attached to the goal as a link, so /goal show carries the history. Already-done goals are refused at start.
    • Bounds + secrets--max-iter N and --max-duration 45m override the 20-iteration / 30-minute defaults (a mistyped bound errors rather than silently falling back); --secret NAME works as on any other loop.
    • Machine-off parityPOST /v1/loops {"args":"goal <id>"} hydrates server-side, and the daemon’s hosted scheduler now runs a real done-validator: LoopExecutor::run_iteration takes a validator: Option<&str>, scheduler_tick takes a validator_lookup closure (criteria re-read each tick, so mid-run edits are honoured), and ProviderLoopExecutor asks the completion question in a separate turn. Recurring jobs skip the extra turn. Previously no hosted self-paced loop could ever signal done — it ran to its caps.
    • Tests — 8 new loop_engine unit tests, 2 new hosted_loop scheduler tests, a find_goal_by_prefix store test (incl. LIKE-wildcard rejection), and 4 new loop_engine_bdd scenarios (10 total). REPL completions, /loop help, and docs/vibecli.md (§ Goal-Driven Loops) updated.
  • SkillForge — analyse + train agent-skill docs (skilllensai-rs + skilloptai-rs). Ported TuringWorks/SkillLens + TuringWorks/SkillOpt into two standalone MIT Rust crates and wired them into the daemon kodegraph-style. SkillLens normalises agent runs into a Trajectory schema, extracts candidate skills (sequential / parallel mode-merge), and scores utility via trigger-coverage (deterministic) + target-evolvability (LLM-held-out); SkillOpt treats a skill markdown doc as the trainable state of a frozen agent — scored rollouts drive bounded add/delete/replace edits, accepted only when a held-out val score strictly improves, epoch after epoch, with a rejected-edit buffer + textual learning rate. Output is a best_skill.md deployed with zero inference-time overhead.
    • Cratesskilllensai-rs (23 tests + golden parse over ~710 shipped skills) + skilloptai-rs (30 tests, incl. a deterministic val-curve test + a strict-gate rejection test) + skillforgeai-rs facade. Provider-agnostic via a crate-local SkillLlm trait; neither crate depends on vibecli/vibe-ai. cargo clippy --features cli -- -D warnings + cargo fmt --check clean.
    • Daemon bridgevibecli/vibecli-cli/src/skillforge_index.rs adapts both crates onto vibe_ai::AIProvider (AiProviderLlm), reuses the existing skill_catalog::SkillCatalog (no re-parse of skills/*.md), and exposes ten routes: /v1/skilllens/{skills,skills/:name,refresh,convert,extract,score} + /v1/skillopt/{train,status/:job,cancel/:job,promote}. train is async-job (spawn + poll status); promote writes *.opt.md to the per-workspace override dir (<ws>/.vibecli/skills/, fallback ~/.vibecli/skills/) and never overwrites a shipped skill. /health.skillforge + the startup banner report loading → ready (N skills). 10 bridge tests green; cargo check clean.
    • Watch mirror — two curated /watch/skilllens/* routes (skills compact {count,top5}, skills/:name one-line) for the wrist form factor, registered in watch_bridge.rs.
    • VibeCoder panelSkillForgePanel.tsx (Catalog / Lens / Optimize) mounted as a “SkillForge” tab in AiMlComposite. Catalog lists the ~710 skills (no LLM); Lens scores a skill against the toolbar-selected model; Optimize launches a train job, polls status every 1.5 s, renders the validation curve as an inline SVG sparkline, shows accepted/rejected + spent-tokens, and offers a guarded Promote (confirm banner; writes *.opt.md). 10 Tauri commands proxy the daemon routes (bearer ~/.vibecli/daemon.token, same pattern as mobile_get_active_session) — the daemon stays the single source of truth. tsc --noEmit clean.
    • Client fan-out — full surface (skilllens.{list,get,refresh,convert,extract,score} + skillopt.{train,status,cancel,promote}) in VS Code api-client.ts + the Agent SDK (agent.skilllens.* / agent.skillopt.*); read-only catalogue + train-status on Flutter api_client.dart (skilllensSkills / skilllensSkill / skilloptStatus), Apple Watch WatchNetworkManager.swift (loadSkilllensSkills / loadSkilllensSkill), and Wear OS WearNetworkManager.kt (skilllensSkills / skilllensSkill). Every LLM-calling client method takes provider + model (STRICT — no hard-coded Anthropic).
    • Docsdocs/skillforge.md (dedicated Jekyll page, permalink /skillforge/: overview, the VibeCoder panel, the full HTTP surface, per-client fan-out table, standalone crates), docs/demos/66-skillforge.md (runnable walkthrough — panel flow + curl equivalents + TrainConfig reference), docs/demos/index.md (Agentic Systems row) + a “What’s Next” cross-link from docs/demos/41-semantic-index.md, docs/index.md (nav row + VibeCLI/VibeCoder highlights bullets), docs/FEATURE-REFERENCE.md (SkillForge route table + watch surface), docs/FEATURE-MATRIX.md (SkillForge row). Design + roadmap in notes/skillforge/.
    • Follow-ups deferred — efficacy-metric substrate (LLM-judge vs embedding-overlap); nightly “sleep” job (offline self-evolution with experience replay); external benchmark Env impls (SWE-bench / BFCL) behind a benchmarks feature.
    • Per-epoch SSE streaming + true cancellationskilloptai::trainer::train_with_signals threads a dependency-free CancelToken (Arc<AtomicBool>, checked at the top of each epoch) and an optional per-epoch EpochEvent mpsc channel; train() is now a thin wrapper with empty signals. New POST /v1/skillopt/train/stream SSE route (jobepoch* → done/error, 15 s keep-alive) shares the same job map as poll-based /train, so /status + /cancel work on both. cancel/:job now flips the live token so the run stops at the next epoch boundary instead of running to completion; TrainingReport.cancelled distinguishes cancellation from early-stop. 3 new skilloptai tests (cancel-before-first-epoch, cancel-observed-between-epochs, one-progress-event-per-epoch) + 2 new bridge cancel-token tests; 33 skilloptai + 13 bridge tests green; cargo clippy --all-features -- -D warnings + cargo fmt --check clean. Client SSE consumption — the VS Code extension (VibeCLIClient.skilloptStreamTrain) and Agent SDK (agent.skillopt.streamTrain) consume the stream as AsyncGenerator<SkilloptTrainEvent> ({type:'job'|'epoch'|'done'|'error', …}) via a new readSseTypedEvents helper (typed event:+data: parser layered over the existing data:-only SSE helpers; the Agent SDK streamTrain async generator is .bind(this)‘d because arrow generators aren’t valid syntax). 4 new Agent SDK vitest cases (job→epoch*→done ordering, error-stop, non-2xx throw, request-body shape); tsc --noEmit clean for both clients; 42 Agent SDK tests green.
    • Promoted-skill override dirpromote now writes <skill>.opt.md to the per-workspace override dir (promote_dir_for(ws) = <ws>/.vibecli/skills/, or ~/.vibecli/skills/ when no workspace resolves and cwd has no .vibecli/workspace.db), so the 710 shipped skills/*.md stay pristine — no in-repo overwrite. The catalogue list surfaces has_promoted_override per skill and the detail view surfaces promoted_override (path or null); the bridge scans the override dir at init + refresh so overrides are picked up without a restart (the shipped-skill path resolution is unchanged — SkillCatalog still wins over same-named plugins). The write is factored into a pure write_promoted_override_in helper for unit testability. 4 new bridge tests (dir resolver, write helper, stem-keyed scan, missing-dir empty); 17 bridge tests + cargo check + tsc --noEmit clean. Client-facing text in the VibeCoder panel, REPL /skillforge promote help, commands.rs doc, and the VS Code / Agent SDK promote doc comments updated to point at the override dir.
    • Real agent-job history env — a third train env kind, history, derives EvalTasks from actual agent runs instead of the catalog. The CLI now writes a lightweight per-session SkillEvalRecord (<session_id>-eval.json) at the end of every agent run via vibe_ai::trace::TraceWriter::save_eval_record{session_id, timestamp, prompt (first user message), final_answer (last assistant prose), tool_success_rate, steps, completed} — alongside the existing <session_id>.jsonl trace (secrets scrubbed, same redaction as save_messages/save_context). env.kind=history scans ~/.vibecli/traces/ (or an env.tasks override dir) via the new vibe_ai::load_eval_records and builds one task per run: the session’s prompt becomes the task prompt; the grader is LlmJudge (default — rubric cites the reference final answer + tool-success rate + completion; one extra LLM call per task per epoch) or Contains (free, weak — a phrase from the reference answer), selected via env.grader="llm_judge"|"contains". Records with an empty prompt or final answer (errored/truncated runs) are skipped; an empty trace dir returns a user-facing “no agent-job history found” error. New types: SkillEvalRecord + load_eval_records in vibe-ai::trace (re-exported from vibe_ai); EnvKind::History + EnvSpec.grader + HistoryGrader + RepoAgentEnv::from_history + history_trace_dir + parse_history_grader + rubric_for + phrase_from in skillforge_index.rs. The eval record is written at both AgentEvent::Complete (completed=true) and AgentEvent::Partial (completed=false) in the interactive agent path (skipped on Error — no reference answer); a small history_tool_success_rate helper computes the rate (1.0 when no tools ran). 9 new bridge tests (llm_judge rubric, contains phrase, skip-empty, all-unusable=err, grader parse + case-insensitive, trace-dir override + home fallback, env_for reads records, env_for no-records=err) + 4 new trace tests (redaction, read dir sorted, missing-dir empty, ignores non-eval); 26 bridge + 20 trace tests green; cargo check (vibe-ai + vibecli) + tsc --noEmit (vscode + agent-sdk) + 42 Agent SDK vitest clean. The VS Code extension (skilloptTrain/skilloptStreamTrain) + Agent SDK (skillopt.train/skillopt.streamTrain) accept 'history' + an optional envGrader:'llm_judge'|'contains'; the catalog-derived repo env is unchanged.
    • Gap-closure pass — closed the seven post-Phase-5 visibility gaps tracked in notes/skillforge/07: (G1) VibeCLI REPL /skillforge command (list/show/refresh/score/train/status/cancel/promote/health, in-process, STRICT via active_provider/active_model); (G2) VibeCLI TUI SkillforgeComponent + SkillForge screen (/skillforge from chat, catalogue + train-jobs pane + /health footer); (G3) AgentContext.skill_health rendered as a ## Skill Health section in build_system_prompt, auto-gated on cached_reports > 0 (no prompt bloat when nothing is scored) — populated by skillforge_index::render_health_line() at the daemon’s AgentContext construction sites; (G4) Apple Watch SkillforgeView (6th “Skills” tab, top5 → detail); (G5) Wear OS SkillforgeScreen + SkillforgeDetailScreen + SkillforgeTileService (routes + deeplink + manifest); (G6) Flutter SkillforgeScreen (8th “Skills” bottom-nav tab, cross-machine catalogue + detail + train-status lookup); (G7) VibeAIChat — 10 SkillForge Tauri proxy commands registered in vibeaichat/src-tauri (no panel; the bespoke UI has no AiMlComposite/toolbar). cargo check clean (vibecli, vibe-ai, vibecoder/src-tauri, vibeaichat/src-tauri); 199 TUI + 44 repl tests + 2 new context-assembly tests green; dart analyze clean on new/touched Flutter files; swiftc -parse clean on Watch files. Full ledger in notes/skillforge/07 — Client Visibility & UX Gaps.md.
  • Code Graph integration (kodegraph) — token-reduction substrate. Wired the standalone kodegraph crate (tree-sitter → SQLite code-knowledge-graph) into the daemon as a single bridge module vibecli/vibecli-cli/src/graph_index.rs (kodegraph is a dep of vibecli-cli only; vibe-ai / vibe-core stay kodegraph-free and receive pre-rendered strings / SymbolInfo vecs):
    • Background build on daemon startup — non-blocking std::thread::spawn parse; the startup banner, /health, and /graph/status surface indexing → ready (N nodes, M edges); the graph persists at <workspace>/.vibecli/codegraph.db (the workspace-DB convention) and is reloaded on next boot; cheap mtime+size incremental refresh runs on agent spawn + explicit /graph/build.
    • Agent system promptAgentContext.graph_summary (new #[serde(default)] field) carries a ~few-hundred-token god-node / community / surprising-edge summary that replaces the flat directory-tree repo map in ## Workspace Structure when the graph is ready (falls back to the dir-tree when disabled).
    • TUI contextContextBuilder::with_relevant_symbols seeds ## Relevant Symbols from a blast-radius around the task terms (graph_aware_symbols); empty when no graph, so behavior is preserved.
    • HTTP surface — eight /v1/graph/* routes (build/status/query/node/:name/neighbors/:name/path/:from/:to/blast/report) carrying no LLM call (provider-agnostic-panel rule moot) and returning serde_json::Value (no kodegraph-type coupling); two curated /watch/graph/* routes (status compact {status,n,m}, query capped to ≤5 nodes) for the watch form factor.
    • 7-client fan-out — Tauri commands (graph_* in commands.rs + lib.rs), Agent SDK (agent.graph.*), VS Code (apiClient.graph*), Flutter ApiClient, Watch Swift WatchNetworkManager, Wear Kotlin WearNetworkManager.
    • /semindex rewired onto graph_index (build/query/node/callers/callees/hierarchy/stats); the superseded semantic_index.rs (regex/manual parser) and dep_visualizer.rs were deleted (their module decls removed from lib.rs/main.rs). semantic_mcp.rs’s own SemanticIndexServer is unrelated and untouched.
    • PreToolUse hook — a non-blocking Glob|Grep nudge in .claude/settings.json that surfaces the graph’s presence so broad file sweeps can consult /graph/* first.
    • Unit tests for graph_index (build/query, symbol mapping, graph-aware seeding); follow-ups deferred: background debounce poll for incremental refresh, lsp tier through vibe-lsp, ast_edit.rs parser delegation, and semantic_mcp.rs reuse of kodegraph::mcp.
  • Phase 55 — feat/bridge-cgap-fitgap-gaps — eight capability gaps shipped as compiling, unit + BDD-tested code:
    • C5 · Per-request effort knob — provider-agnostic Effort (low|medium|high|xhigh, default high) in vibe-ai/provider.rs, mapped per provider: Claude/Gemini extended-thinking budget, OpenAI reasoning_effort (clamped to “high”). Wired through ProviderConfig.effort, cost_router::route_task_with_effort, the daemon /agent path (serve.rs, via the VX-111 reasoning label), the ai_chat_with_effort Tauri command, vibeaichat start_agent_session, and a VibeCoder toolbar selector (utils/effort.ts). Fix: Gemini Pro models can’t disable thinking — Effort::Low now clamps Pro to the 128-token minimum instead of emitting an API-rejected thinkingBudget: 0. BDD: effort_bdd (5 scenarios).
    • C1 · /loop — recurring (/loop 5m <prompt>) and self-paced (/loop auto <prompt>, loop-until-done) REPL command with a MAX_ITER guard (20), wall-clock auto-expiry, job IDs, Ctrl-C stop, an LLM done-validator, and list/stop/status + JSON persistence (loop_engine.rs). BDD: loop_engine_bdd (6 scenarios).
    • C3 · MCP Tasks extension + stateless transport (2026-07-28 RC) — tasks/get|update|cancel registry with a Working→…→Completed/Failed/Cancelled state machine, plus stateless _meta (RequestMeta) and a stateless flag on StreamableHttpConfig (mcp_tasks.rs). BDD: mcp_tasks_bdd (5 scenarios).
    • C6 · ACP + MCP Registry self-listingvibecli --registry <acp|mcp|all> emits the ACP agent-card / MCP Registry v0.1 server entry from one versioned identity (registry_listing.rs).
    • Design-Mode diffcomplete — clicked element + instruction → a CSS/HTML unified diff for DiffReviewPanel; rejects live-DOM-mutation payloads. Tauri design_emit_diff (design_diff.rs).
    • Opt-in security review — default-OFF watcher that emits standard self_review::Finding records (no auto-apply). Tauri security_review_file (security_review_watch.rs).
    • WebMCP (origin-trial gated) — consumer (parse/validate site tools) + producer (panel→tool, read-only). Tauri webmcp_parse_tools / webmcp_publish_panels (webmcp.rs).
    • c-series model registry — added Claude Opus 4.8, Gemini 3.5 Pro/Flash, DeepSeek V4, Qwen 3.6, Kimi K2.7 Code (via OpenRouter), GLM-5.2, MiniMax M3 to useModelRegistry.ts with updated defaults; Fable 5 / Mythos 5 omitted (export-suspended 2026-06-12).
    • Follow-ups still open: UI panels (/loop Automations, Design-Mode wiring, SecurityPanel), daemon/live-transport loops (B3 always-on watcher, C3 live MCP dispatch, C4 browser_agent CDP), C5 streaming-path propagation, and C2 (dynamic large-scale workflow primitive).

Changed

  • The step budget now extends for a run that is still working, instead of cutting it off. max_steps (default 50) is a runaway guard, but as a hard wall it also stopped healthy runs: an agent executing its plan tool-call by tool-call hit the limit mid-plan, reported Partial, and left the user to press Resume to finish work that was going fine. AgentLoop now grants up to max_step_extensions (default 3) further budgets of max_steps each — a ceiling of max_steps * (1 + N), 200 steps by default.
    • An extension is earned, not automatic. The decision is a pure should_extend_budget(extensions_used, max_extensions, health, steps_since_progress), and every guard vetoes independently: extensions must remain, the circuit breaker must read Progress (not Stalled / Spinning / Degraded / Blocked), and a successful tool call must have landed within the last 10 steps. Failed tool calls deliberately do not count as progress — an agent retrying the same broken command is precisely what the budget exists to stop.
    • AgentLoop::with_step_extensions(0) restores the old hard wall; a test pins that.
    • Mechanically, the loop advances step at the top of its body rather than the bottom. The body has many continues, and a bottom increment would skip every one of them and spin forever. The post-loop Partial reporting now quotes the effective budget rather than max_steps, so it no longer understates how far the run got.
    • 5 new tests: the policy’s veto cases, the bound, a productive run that overruns a 2-step budget and now completes where it previously reported Partial, with_step_extensions(0) restoring the wall, and an agent whose every tool call fails not being extended.
    • Tunable per runPOST /agent takes an optional max_step_extensions, so an operator can trade completion against cost without a rebuild (0 = hard wall). Absent means the harness default, so no existing client changes behaviour. max_steps == 0 is honoured rather than extended.
    • Sub-agents get 1 extension, not 3. The multiplier compounds down the tree: at the root default a depth-2 nest would let every sub-agent run 4× its budget, and a parent spawning many of them multiplies that again. 2× absorbs an overrun without the fan-out.

Fixed

  • A chat history containing one huge message wedged the run permanently. prune_messages only ever dropped the middle of the conversation. When the oversize lived in a message it must preserve — the system prompt, or a read_file / bash result in the six-message tail — pruning was a no-op, so every following step re-sent the identical too-large payload and the provider rejected it the same way each time. A test reproduced it at 100,120 tokens against a 10,000 budget. Pruning now falls through to clipping individual messages (largest first, both ends kept around a …[truncated to fit the context window]… marker, always on UTF-8 char boundaries) so the next request actually fits. Message count and order are preserved — dropping the system prompt or the newest tool result breaks the loop worse than eliding the middle of one payload. Best-effort by construction: estimate_tokens charges 8 tokens of framing per message, so a budget under 8 × len cannot be met by clipping, and the helper degrades rather than spinning (pinned by a test).

  • Subprocess dispatch had the original /agent bug, untouched. The child loop in main.rs matched four AgentEvent variants and swallowed the rest with _ => continue — so a dispatched run that stopped mid-plan drained the channel, left completed false, and the fallback sent DispatchFrame::Complete { "Agent finished." }, which the parent records as JobStatus::Complete. Byte-for-byte the defect fixed in serve.rs on the first pass, in a code path never opened. ToolCallPending was dropped there too, silently abandoning the run. Found by verifying a claim (“the remaining catch-alls are already audited”) instead of restating it — two AgentEvent matches in main.rs had never been read.
    • DispatchFrame gains a Partial variant, since Complete is defined by this protocol as success and Error overstates a resumable run. The parent publishes the rich partial event to SSE and marks JobStatus::Partial. Safe to add a variant: the child is this same binary, self-spawned via current_exe(), so parent and child can never be different versions.
    • A second main.rs loop (console output) also dropped Partial, printing nothing at all when a run ended — the tool steps then silence.
  • kodegraph community detection was seeded by hash order, making the workspace suite intermittently red. Label propagation resolved ties with tally.into_iter().max_by_key(|(_, c)| *c). HashMap iteration order is randomly seeded per process and max_by_key returns the last maximum, so which label won a tie varied run to run and the same graph settled differently — detects_a_community_from_a_cluster failed roughly one run in ten. Two further orderings had the same flaw: communities were grouped out of a HashMap and sorted with a stable sort on size alone, so equal-sized communities kept their hash order, and member lists were unsorted. Ties now break on the label id, the sort is total (size, then id), and members are sorted — the result is a function of the graph alone. A new test asserts the a/b/c triangle groups together and that 25 rebuilds produce byte-identical ids, members and ordering. Found by finally running cargo test --workspace rather than --lib; unrelated to the rest of this changelog entry, and pre-existing.

  • The CLI’s spawn_agent had the same unfinished-subtask bug as VibeCoder’s, verbatim. tool_executor.rs and vibecoder/src-tauri/agent_executor.rs carry duplicate copies of the sub-agent event loop; fixing one left the other reporting “Sub-agent completed.” for a sub-agent that stopped mid-plan, and silently abandoning the run when a policy gated a tool. Both copies now match. Found by auditing catch-all match arms rather than by review.

  • Flutter’s watch poller never saw a partial or cancelled session as finished. watch_sync_service.dart tested status == 'complete' || status == 'failed', an allow-list of two — so cancelled (pre-existing) and partial (once the daemon gained it) spun the full 60-second timeout and then returned [], discarding assistant messages already fetched and leaving the phone blank. Terminal is now “not running/queued”, and a timeout hands back whatever did arrive.

  • Wear OS read an HTTP failure on either SSE stream as a clean end. OkHttp calls EventSourceListener.onFailure with a null throwable when the failure is an unsuccessful HTTP response, and both openStream and openTaintedPendingStream did if (t != null) onError(t) else onComplete(). So a 401 or 500 ran the completion path: ConversationScreen cleared the partial reply and dropped the spinner with no error shown. On the tainted-prompt stream it is worse — the wrist shows no pending approvals while a run sits blocked waiting for one. Both now distinguish an unsuccessful response from a genuine close.

  • A JetBrains step with no success field rendered as “ok”. Introduced by this changelog’s own parser rewrite, and caught by the follow-up sweep rather than by review. AgentEvent.Step.success is now Boolean? and renders outcome not reported when the daemon said nothing.

  • An unverified observe-act step erased the failure streak, so a failing loop never bailed out. ObserveActSession::record_step computed success as verification_result.map(|v| v.success).unwrap_or(true) — the comment said // No verification = assume success — and a “success” resets consecutive_failures to 0. With unverified steps interleaved (the normal case when verify_after_action is off, or verification itself fails), the streak could never reach max_consecutive_failures, so a loop that was failing every step ran the entire max_steps budget instead of aborting after three. Absent verification is now neither success nor failure: the streak is left unchanged. Three tests, including one that drives failure/unverified pairs and asserts the loop stops rather than running to the cap.

  • A tool_end with no success field showed a green tick on the wrist. to_watch_event_json defaulted the missing field to true and reported status: "ok". status is Option<String> precisely so absence can stay absent; it is now None when the producer said nothing.

  • A failed watch/phone reply was recorded in the database as a completed one. The /watch/dispatch streaming task matched chunks with if let Ok(text) = chunk, so a mid-stream provider error was silently discarded and the loop carried on. The trailing {"type":"done","status":"complete"} still fired, and — uniquely on this path — finish_session(…, "complete") plus the truncated assistant message were persisted. The wrist showed a half-answer as finished and the transcript agreed with it from then on. A failed stream is now terminal: it emits error, stores failed with the provider’s message, and keeps the tokens that did arrive (the user watched them stream in) without ever recording them as a completion. The persistence decision is extracted into persist_watch_turn and pinned by three tests (failed-with-partial, clean, failed-before-any-output).

  • /chat and /chat/stream ignored the model you picked. AGENTS.md’s provider-agnostic rule says daemon routes read provider/model from the request body, not the daemon’s startup config, and POST /agent already did. The chat routes did not: ChatRequest.model rode the wire but was marked #[allow(dead_code)] and never read, and there was no provider field at all. A user who chose a model in the toolbar was answered by whatever the daemon happened to boot with — silently, and differently from the agent route on the same screen. Both routes now resolve through a shared chat_provider_for, which honours provider + model together (matching /agent), wraps the result in resilient() since a chat turn has no retry loop of its own, and falls back to the daemon provider when the override is absent or unbuildable — so existing callers are unaffected. Pinned by tests covering both-present, either-alone, empty-string, unknown-provider, and the identity that surfaces through the wrapper.

  • /chat/stream reported a failed reply as a finished one. A mid-stream provider error emitted an error event and then carried on, so the trailing done still fired: clients saw error followed by done and rendered a truncated reply as complete. error is now terminal.

  • A dropped connection reported a still-running task as finished. VibeDesk settled a stream that closed while running straight to done, on the assumption that it meant “the run ended without a terminal event”. That assumption stopped holding once the daemon began always emitting a terminal event: reaching that path now means the transport dropped — a network blip, a daemon restart, the laptop sleeping — while the agent carried on working. The chat said the task was done, the transcript stopped mid-stream, and the task card was PATCHed to reviewing.
    • The daemon is authoritative and already had the pieces: GET /jobs/:id for real status and a durable event log replayable from seq 0. A closed stream now reconciles — if the job is genuinely terminal it settles to that outcome (failed → error, cancelled, partial, else done); if it is still running it reattaches and replays, so nothing streamed before the drop is lost. Bounded at 5 consecutive attempts, after which the run is reported as lost rather than reconnecting forever, and the allowance resets on every real terminal event.
    • A user-requested cancel still short-circuits to cancelled before any of this.
  • Choosing a model in the toolbar silently disabled retry. The daemon’s default provider is wrapped in ResilientProvider by main.rs::create_provider, but build_provider_override_with_effort — used whenever a request carries provider + model, which VibeDesk’s model picker, mobile, watch and the IDE clients all send — returned a raw provider. Every one-shot LLM call with no retry loop of its own (/chat, chat-mode agent turns, the planner, recap synthesis) therefore died on a single transient 503, and only for users who had picked a model. Those call sites now wrap explicitly via a new resilient() helper.
    • The builder deliberately still returns raw: AgentLoop retries stream_chat itself, so wrapping there would nest two backoff loops (4 × 5 = 20 attempts) and stall a run for minutes on a hard outage. A test pins that the builder is unwrapped and that resilient() is identity-preserving (name() delegates), so wrapping never changes which model a caller believes it is talking to.
  • Unfinished agent runs were reported to every remote client as successes. AgentEvent has nine variants; the daemon’s /agent/stream/{id} route matched four and swallowed the rest with _ => continue. AgentEvent::Partial — the event the harness emits when the agent exhausts its step budget or stops mid-plan — was one of the swallowed ones, so the loop drained with no terminal event and the “clients must not hang” fallback published complete("Agent finished.") and marked the job Complete. The real partial summary and the list of plan items that were never executed were discarded. Every client behind the daemon (mobile, watch, VS Code, JetBrains, Neovim, Agent SDK, VibeDesk) was told the task had finished.
    • ToolCallPending was swallowed the same way, which killed the run outright. Dropping result_tx without answering made AgentLoop::run hit Err(_) => return Ok(()) — the one path in the harness that ended a run with no terminal event at all — and the same fallback then reported that abandoned run as a success. Reachable from any caller passing approval: "suggest"/"autoedit", which the route accepts.
    • Harness — that silent return Ok(()) now emits AgentEvent::Error naming the tool and the dropped channel. A new assert_terminal_event test pins the contract across five paths: every run ends with exactly one of Complete / Partial / Error. Callers that infer success from “no Error seen” are now safe by construction.
    • Wire protocol — two new SSE kinds. partial is terminal and carries steps_completed / steps_planned / remaining_plan; retry is non-terminal and carries attempt / max_attempts / backoff_ms (previously a retrying agent was indistinguishable from a hung one for up to 60 s per attempt). system now also carries circuit-breaker and verifier notices. New AgentEventPayload fields are Option + skip_serializing_if, so the existing kinds’ wire shape is byte-identical and old rows in the durable event log still deserialize — both pinned by tests.
    • JobStatus::Partial — terminal, but neither Complete nor Failed: the work done is real and the run is resumable. Folding it into Complete is what let unfinished tasks read as successes.
    • Client fan-out — Agent SDK + VS Code (isTerminalAgentEvent, replacing four hand-rolled complete || error checks), VibeDesk (stream_agent forwards partial/retry; RunState gains partial, rendered as “incomplete” and now offering Retry, which it previously withheld on exactly the runs that needed it; the replay path no longer drops the partial notice, so a reopened chat still shows the work was unfinished), VibeCoder BackgroundJobsPanel (closes the EventSource on partial instead of leaking it until the browser errors out), and Neovim.
    • JetBrains agent window rendered nothing at all — pre-existing and unrelated to the above. Its parseEvent looked for thinking / text / tool_call / tool_result and read summary / message / text: kinds and fields the daemon has never emitted. Every streamed token fell through to null and complete rendered an empty summary. Rebuilt against the real AgentEventPayload shape, with Step / System / Retry / Partial added.
    • Sub-agentsspawn_agent (vibecoder/src-tauri/agent_executor.rs) dropped Partial into _ => {}, leaving summary empty, which its tail rendered as “Sub-agent completed.” to the parent agent. A sub-agent that ran out of steps mid-plan now returns ToolResult::err with what it finished and what it did not, so the parent re-dispatches instead of building on unfinished work.
    • Docs: docs/api-reference.md gains a terminal/non-terminal event table, the partial-run example, and the partial job status.
  • VibeDesk · Sandbox mode with per-axis permissions. The agent is normally jailed to workspace_rootToolExecutor::resolve_safe canonicalizes every path and rejects escapes. Sandbox mode relaxes that jail, which is the only place in the product where a model gets reach beyond the project it was pointed at, so the relaxation is explicit, per-axis, and denies by default. New sandbox_policy.rs (read_outside · write_outside · exec_outside · network, plus allow_roots / deny_roots), a sandbox field on POST /agent, and a composer settings sheet in VibeDesk.
    • Credentials are unreachable by construction. vibe_core::path_guard (~/.ssh, ~/.aws, ~/.vibecli, id_rsa, daemon.token, …) is applied to every outside path before any grant is consulted, so no combination of toggles — including an allow_root pointing straight at ~/.ssh — reaches a key. Pinned by credentials_are_never_reachable_however_configured, which asserts it under a maximally permissive policy.
    • Deny beats allow; grants only apply in the mode that asked for them. deny_roots is checked before allow_roots. effective_sandbox_policy() is extracted and tested precisely so a sandbox field left on an Agent request cannot silently widen an ordinary run — the obvious way this feature could have leaked.
    • Inside the workspace is untouched. The policy is consulted only for paths that escape the jail, so enabling sandbox mode can only add reach, never remove it. Deliberately, the credential deny-list is not applied inward: DENIED_FILENAMES includes config.json, an ordinary project file.
    • Sub-agents inherit the policy. spawn_agent builds a child executor field by field; omitting sandbox_policy there would have handed the child a locked default, so a sandbox run’s sub-agent would fail on the very paths its parent was granted.
    • exec_outside enforces something real. bash is not path-jailed — the shell reaches whatever the OS allows, in Agent mode too — so a “commands may not leave the workspace” path check would have been theatre. The axis instead drives ToolExecutor’s existing OS-level confinement (bwrap+Landlock / sandbox-exec / AppContainer): off confines commands, on runs them as they do today. An earlier draft shipped a allows_exec_in() guard that could never fire; it was removed rather than left as a setting that enforces nothing.
    • Verified — 9 policy tests, 3 executor tests proving the jail widens for the granted axis only, 4 request-plumbing tests, and a live run: sandbox mode with no grants refused to read a canary file outside the workspace and reported the jail message. 12,926 vibecli tests green.
  • VibeDesk · Chat mode, thinking off by default, and a resizable Environment rail.
    • Run mode (Agent · Chat). New mode field on POST /agent with a RunMode parser (unknown values fall back to Agent, so older clients are untouched), and a chat path in serve.rs that answers in one turn with no tool loop, no executor, no approvals and no workspace access. It reuses the session + event plumbing, so a chat turn replays, resumes and appears in history like any other. The agent’s system prompt orders it to reply only with a tool call, so asking a plain question in Agent mode got an answer shaped like work — Chat mode is how you ask without starting a task. Surfaced as a composer ModePill, persisted with the other run controls.
    • Live-verified. “In one sentence, what is the Mandelbrot set?” in Chat mode against minimax-m3:cloud returns a one-sentence answer with no <tool_call> and no reasoning tags.
    • unwrap_thinking() — found only by running it: minimax-m3 put a 54,000-character reply entirely inside one <thinking> block (</thinking> at char 54,210 of 54,222), so strip_thinking correctly returned nothing and the turn would have rendered as an empty message with the answer hidden in a collapsed aside. When stripping leaves nothing, the chat path now unwraps the tags and keeps the text. Display paths only — tool parsing still uses strip_thinking, because reasoning quotes calls the model then rejected.
    • Thinking is off by default. ReasoningEffort gains an off tier, now the composer default. Off is sent as the absence of the reasoning field, not a value: the daemon maps an unknown effort to no budget but still publishes a Reasoning effort: … system line for whatever it receives, which would have announced a tier the user had turned off. One shared effortParam() covers the main composer and the side chat.
    • Environment rail — drag-to-resize (200–520px, double-click to reset) plus collapsible sections with counts, both persisted via useLayoutPrefs following useTheme’s localStorage-mirror-then-settings-store pattern so the first paint is already correct. The handle is absolutely positioned on the column boundary rather than occupying a grid track, so the three-column template is unchanged whether or not it renders.
    • CIvibedesk-checks (added with the release job) covers all of this; npm run test:thinking is a new script with 11 cases.
  • </mm:think> rendered verbatim in chat, mid-sentence. minimax-m3 emits a namespaced reasoning tag, and all three strippers matched only think|thinking — so <mm:think> sailed through every one. Fixed in vibe_ai::tools (strip_thinking / unwrap_thinking), vibedesk/src/lib/thinking.ts and vibecoder/src/components/AIChat.tsx with an optional namespace prefix (<mm:think>, <ns:thinking>, …).
    • The Rust stripper was also missing the orphan-close rule the TypeScript side already had. That is the other half of the leak: Ollama consumes the opening tag into its own thinking field, so only the close reaches us, and everything before it is still reasoning. Verified on the exact failing turn — <thinking>…</thinking>\nLet me write a single Python file.</mm:think><write_file …> now yields just the <write_file> element, with neither the tag nor the trailing reasoning.
    • 4 new Rust tests and 4 new VibeDesk tests, including one asserting that ordinary prose containing </div> is untouched — the orphan rule is greedy by design and must not eat a sentence.
  • The tool parser now accepts three dialects, not one. parse_tool_calls understood only <tool_call name="…">, so a model that reached for any other shape produced a turn that looked like prose and did nothing.
    • Element style<write_file path="a.py">body</write_file>, <read_file path="x"/>. This is the format VibeCoder’s own chat prompt teaches (vibecoder/src-tauri/src/commands.rs), so models carry it into agent runs where nothing parsed it.
    • JSON style{"name": "read_file", "arguments": {…}}, with or without a code fence. Captured live from minimax-m3 given no native tool definitions: it falls back to the OpenAI function-call shape it was trained on.
    • Canonical wins and is never re-scanned, so a documented example quoted inside a real <tool_call> body cannot double-fire. Only names in AVAILABLE_TOOL_NAMES are considered, so <div>, <p> and prose mentioning a tool are never mistaken for calls. Common aliases (file_path/filepath, cmdcommand) are normalised, and element bodies map to each tool’s natural parameter (write_filecontent, bashcommand, …).
    • Element matching uses one regex per tool name rather than an alternation with a backreference — Rust’s regex crate has none, so </\1> cannot express “the same tag we opened”. Matches are position-sorted so a write-then-run turn executes in that order.
    • 6 new tests covering both dialects, precedence, false positives, and the rule that every dialect is read from the visible turn only (reasoning quotes calls the model then rejects).
  • Native tool-calling models produced an empty turn and a stuck run. OllamaChatRequest carried model, messages, stream and options — but never tools. Tools existed only as prose in TOOL_SYSTEM_PROMPT, which works for models that follow prompt instructions and not at all for one trained to call a tool API: minimax-m3:cloud reasoned “Let me first check the workspace…”, reached for a tool interface that was never advertised, and returned nothing. The agent’s reasoning-only guard (agent.rs:1077) then re-prompted twice and gave up, leaving the task in reviewing. The response side had always transcribed native tool_calls back to <tool_call> markup — only the outbound half was missing.
    • Fix — new tools::tool_definitions() publishes all 14 tools as OpenAI-shaped function schemas, and the three /api/chat call sites now send them. /api/generate (prompt-completion, not the agent path) is untouched.
    • Gated, not unconditionaltools::expects_tools() sends schemas only for conversations carrying the agent’s system prompt. Advertising tools to a plain chat panel would produce <tool_call> markup that the panel renders as literal text — the same class of leak as the raw <thinking> tags below.
    • Schemas are pinned to the parser. render_tool_call turns each JSON key into an XML tag, so a renamed parameter would silently turn every native call into an unparsed block. tool_definitions_match_parser builds a call from each schema’s required params and asserts it round-trips through parse_tool_calls. 5 new tests; 1181 vibe-ai tests green.
    • Verified live, since no unit test can catch this — every schema assertion passed while the bug was present. tests/ollama_native_tools_live.rs (#[ignore]d; needs a signed-in Ollama) replays the original prompt against minimax-m3:cloud and asserts a parsable tool call comes back. It now answers <thinking>…Let me check the workspace first.</thinking><tool_call name="bash"><command>pwd && ls -la</command></tool_call> — the same sentence that used to end the turn, now followed by an action. A second case pins the gating: the same task with no agent prompt returns prose and zero tool calls.
    • Still openopenai_compat.rs has the same gap, so every OpenAI-compatible endpoint is prompt-only today. tool_definitions() is provider-neutral and ready for it.
  • VibeDesk rendered <thinking> tags as literal text. Providers wrap reasoning into the content string so it travels as one value (ollama.rs, openai_compat.rs), and Rust strips it with strip_thinking before treating the text as an answer — but SessionStream.tsx piped the raw turn into <Markdown>, so the transport markup appeared on screen. Worst on a reasoning-only turn, where the tags were the entire message.
    • Fix — new lib/thinking.ts splits reasoning from the answer, and ReasoningBlock.tsx renders it collapsed (dashed, italic, --accent-purple) rather than discarding it: when a turn is only reasoning, that block is the sole explanation of why nothing happened. Copy-to-clipboard now yields the answer, not the internal markup.
    • Matches the Rust semantics plus the unbalanced shapes seen in practice: both <think>/<thinking> spellings, an unclosed block swallowing its tail (stream cut mid-thought), and an orphan closing tag where the provider already consumed the opening one. 11 new tests via npm run test:thinking.
    • VibeCoder was not affected — AIChat.tsx:extractThinking already handled this; VibeDesk simply never got the equivalent.
  • ProfileStore split into crates/vibe-profile-store. Reading one API key used to mean linking the whole CLI: the store lived inside vibecli, so anything touching settings pulled mistralrs and candle with it. vibeaichat/src-tauri/src/commands.rs documents avoiding vibecli for exactly that reason — and the shared settings crate had quietly reintroduced the dependency behind that comment’s back. The module turned out to be fully self-contained (zero crate:: references), so the lift was clean.
    • vibecli re-exports it as vibecli_cli::profile_store, leaving all ~30 call sites unchanged — serve.rs, config.rs, watch_auth.rs, VibeCoder’s commands.rs and the rest.
    • vibe-desktop-settings now depends on the store alone, so VibeAIChat’s tree carries no vibecli, mistralrs or candle (396 crates). VibeDesk still links vibecli — by its own design, not by accident.
    • Verified by test accounting, not just a green build: the 30 profile-store tests moved with the crate and pass there, and vibecli went from 12,937 to 12,907 — exactly the 30 that left, nothing silently dropped.
  • Vibe App is now VibeAIChat, and shares its settings with VibeDesk.
    • Renamevibeapp/vibeaichat/ (git mv, history preserved), crate vibeapp/vibeapp_libvibeaichat/vibeaichat_lib, npm package, productName: VibeAIChat, identifier: com.vibecody.vibeaichat, window title, workspace member, Makefile targets (appaichat, build-appbuild-aichat, …), the build-vibeaichat release job, vibeaichat-checks in CI, .semgrep, and every doc. macOS treats the new bundle id as a new app, so it re-asks for permissions once; settings live in ~/.vibecli and are unaffected.
    • packages/vibe-ui-shared — the settings screens (Providers · Appearance · Account), theme definitions, useTheme, useProviderSettings and the reasoning parser now exist once, consumed as source via a @vibe/shared alias. No build step, no dist, no version to bump. This ends a drift that had already caused a bug: the reasoning rules existed in three copies, and a fix to one left the others rendering raw <thinking> markup.
    • crates/vibe-desktop-settings — the same for the Rust half. #[tauri::command] functions do not have to live in the app crate, so both shells now drive one copy of the ProfileStore code rather than two. (They must live in a module, not the crate root: at the root each command collides with the macro #[tauri::command] generates for it.)
    • SettingsView gained extraTabs so a shell can contribute settings that are its own without pushing them into the shared screens. VibeAIChat uses it for a Connection tab (daemon URL, optional token override, provider, model); VibeDesk passes none and is unchanged.
    • Wiring notes, because both are silent when wrong. The shared sources live outside each app’s project root and there is no hoisted node_modules, so their bare imports are mapped to the host app’s copies in both vite.config.ts and tsconfig.json — which also keeps one React instance in the bundle. The Vite aliases use regex find: a plain string alias is a prefix match, so "react" would rewrite "react-dom" into "<path-to-react>-dom". And resolve() strips a trailing slash, so sub-path replacements re-append it or react/jsx-runtime concatenates into reactjsx-runtime. In tsconfig, @types must precede the JS package in paths or every import lands as any.
    • Verifiedtsc and vite build clean in all three shells, cargo check/fmt clean, the 4 settings-migration tests moved with the crate and pass, both test:thinking suites pass against the single shared parser, and the settings CSS was confirmed present in each bundle (VibeAIChat’s was missing at first — the screens render unstyled when the stylesheet is not imported, exactly the trap the package README documents).
  • VibeAIChat answered “hi” with pages of the model arguing with itself. Two stacked defects, both visible in one screenshot. start_agent_session had no mode parameter at all, so every turn ran the agent loop — whose system prompt orders the model to reply only with a tool call. The model’s own output diagnosed it: “the system prompt says every response MUST be a tool call”, followed by five paragraphs deciding whether a greeting counted. And the render site was {msg.content} straight to the DOM, so the <thinking> block wrapping all of it appeared verbatim.
    • VibeAIChat is an assistant, not a task runner, so its UI now sends mode: "chat" — a single reply, no tools, no approvals. Verified live against minimax-m3:cloud: the same hi now returns 37 characters (“Hi there! 👋 How can I help you today?”) with no thinking tags and no tool-rule argument.
    • vibeaichat/src/lib/thinking.ts strips reasoning at the render site as a guard. Chat mode already strips it server-side, so this covers the rest — agent-mode turns, an older daemon, or a spelling the server missed — because the failure mode is raw markup in the user’s face. Falls back to unwrapping when the reasoning is the whole reply, rather than rendering an empty bubble. 8 tests via npm run test:thinking.
    • Note the duplication. These rules now exist in three places (Rust strip_thinking, VibeDesk, VibeAIChat) because the two Tauri apps share no frontend package. Each copy carries its own tests and a pointer to the others; consolidating them behind a shared package would be the real fix.
  • Every client 401’d against a healthy daemon — one cascade, three causes. Reported as three unrelated symptoms (VibeAIChat 401, VibeDesk “Couldn’t load chats”, VibeCoder “Port 7878 is in use by another program”). All one chain: /health shared the public routes’ 10-req/min per-IP bucket, and three desktop apps plus daemon_bootstrap::probe’s 250ms startup poll all arrive from 127.0.0.1 — so the bucket was exhausted in seconds and never recovered. A throttled /health returns {"error":"Rate limit exceeded"}, which carries no service field, so probe() read a healthy daemon as a foreign process; clients then spawned replacement daemons, and each replacement wrote ~/.vibecli/daemon.token before binding, so the loser clobbered the live daemon’s token on its way out. Confirmed on the machine: daemon started 19:45:32, token file rewritten 20:00:40 by a process that never held the port.
    • /health is off the shared bucket (serve.rs) with its own 600/min floodgate — bounded against a runaway client, unreachable by honest polling. Throttling the one route every client polls for liveness and identity is self-defeating: the failure is not a slow health check, it is the cascade above.
    • Bind precedes the token write. Binding reserves the port, so a daemon that cannot have it now fails before touching the shared token file. The comment defending the old order worried about a client reading a stale token the instant the port opens; that cannot happen, because bind only opens the socket and nothing is answered until axum::serve accepts, well after the write.
    • probe() treats 429 as a throttle, not a stranger — retries once rather than declaring a foreign service. Defence in depth now that 429 is unlikely.
    • New tests/client_contract.rs — three tests against a real spawned daemon, one per link: 40 concurrent /health probes must all identify and none be throttled; a daemon that loses the port must exit without rewriting the token, which must still authenticate; and the bearer contract (protected 401s bare, succeeds with the file’s token, /health public and self-identifying). Verified to fail without the fix: reverting the /health limiter made it fail at wait_readyprobe() could not identify the daemon at all within 90s, the production symptom exactly.
    • HOME is isolated in both daemon integration suites. The token path is shared across daemons regardless of port, so a test inheriting the real HOME overwrites the developer’s own daemon token and breaks every client on the machine — the very failure under test. daemon_bootstrap_integration.rs had been doing this since it was written; verified fixed by hashing the live token across a full run (unchanged, same mtime).
  • Daemon autostart failed in every Finder-launched app — “exited immediately (exit status: 1)”. The daemon derives its workspace root, and therefore <workspace>/.vibecli/, from its working directory. Neither spawn site set one, so the child inherited the parent’s cwd — which for a .app launched from Finder rather than a terminal is /. The daemon then tried to create /.vibecli/ on the read-only system volume and died before binding: Error: rl run store: storage: Read-only file system (os error 30). Launching from a terminal always worked, because a shell’s cwd is writable — which is why this survived so long.
    • The failure was undiagnosable by construction. Both spawn sites set stderr(Stdio::null()), discarding the one line that explained it, then told the user to “run vibecli --serve --port 7878 in a terminal to see why” — where it does not reproduce.
    • Fixdaemon_bootstrap.rs gains spawn_working_dir() (keep the caller’s cwd when the daemon could actually create its .vibecli/ there, else $HOME, else the temp dir, so a client started from a repo still gets that repo as its workspace) and spawn_output() (capture stdout/stderr to ~/.vibecli/daemon-spawn.log, truncated per spawn). DaemonState::TimedOut now names that log rather than suggesting a command that will not reproduce the failure, and VibeCoder’s “exited immediately” error appends the daemon’s own last log line.
    • Both spawn sites now share it. vibecoder/src-tauri/src/commands.rs had its own tokio::process::Command spawn — precisely the second copy daemon_bootstrap.rs exists to prevent, and the one that carried this bug. It now calls boot::spawn_working_dir() / boot::spawn_output(), as does the shared spawn_detached.
    • Tests — 3 new daemon_bootstrap unit tests (unwritable candidate skipped, caller’s cwd preferred, candidate list ordered), following the existing find_binary_in pattern of taking candidates as an argument instead of mutating process-global cwd. Verified end-to-end with the same binary under a Finder-like env -i: cwd / exits 1, cwd $HOME comes up listening.
  • Editor IntelliSense — completion, hover, go-to-definition, signature help and diagnostics work again. Five independent defects, each sufficient on its own to make every LSP request return nothing. Verified against real servers, not just fixtures: rust-analyzer now returns 165 completions for text. on text that exists only in the editor, and clangd returns exactly the struct’s fields.
    • The message pump deadlocked the connection. The client forwarded every inbound message into a 32-slot mpsc that only an in-flight request drained. rust-analyzer emits thousands of $/progress notifications while indexing, so the reader wedged on the full channel, stopped draining the server’s stdout, the pipe filled, and the server blocked on write — IntelliSense died seconds after opening a file and never recovered. client.rs is now a dispatcher: a reader task routes responses through a pending-request map (so concurrent requests can’t consume each other’s replies — hover could previously return a completion list), stores publishDiagnostics, and answers server→client requests (workspace/configuration, client/registerCapability, window/workDoneProgress/create, …) instead of dropping them, which is what a server waits on forever. Every request has a timeout, and a server that exits fails its in-flight requests immediately rather than making each wait out the timeout. Framing now scans headers to the blank line (servers that send Content-Type used to desynchronise the stream permanently).
    • Every document URI was one no server had heard of. <Editor> had no path prop, so all files shared a single model at inmemory://model/1, while didOpen announced file://…. Each file now gets its own model at a real file URI, produced by one encoder on each side (fileUri / path_to_uri) — the old format!("file://{path}") also produced an unparseable URI for any path with a space or #. As a side effect Monaco’s own TypeScript service can finally resolve imports across open files.
    • Edits were never sent. lsp_did_change had no caller, so servers answered against the text as it was when the file opened — nothing you typed could be completed. Edits are now pushed (debounced 250 ms, and always flushed before a completion/hover/definition request so a request can’t race its own edit), on AI-applied diffs and undo too. Versions are owned by the client and increase monotonically; a change to a document the server never saw self-heals into a didOpen.
    • Completion kinds and snippets were mistranslated. LSP and Monaco both call the enum CompletionItemKind and agree on no value (LSP Text is 1; Monaco’s is 18, Method is 0), so every suggestion carried a wrong icon; mapping is now by name, since Monaco renumbers (Snippet is 28 in 0.55, was 27). insertTextFormat: 2 now sets InsertAsSnippet instead of typing ${1:arg} literally. Auto-import edits, commit characters, sortText/filterText/preselect, deprecation tags, labelDetails and completionItem/resolve documentation are all carried through.
    • No trigger characters were registered, so foo. and Vec:: showed nothing while mid-identifier completion appeared to work. They now come from the server’s own capabilities (multi-character triggers split into the single characters Monaco accepts).
    • ClientCapabilities::default() declared nothing — an empty object is how you get a server that answers with plain labels, no snippets and no docs, or refuses outright. We now declare completion (snippets, resolve, all 25 kinds), hover/signature-help markdown, synchronization with didSave, publishDiagnostics, and workspace folders.
    • Servers are found where they are installed. A Finder-launched app inherits launchd’s bare PATH, which contains no ~/.cargo/bin, Homebrew prefix or npm global — so rust-analyzer was No such file or directory in the bundled .app even when it worked in cargo run. New discovery.rs resolves servers across the standard install dirs and hands the spawned server the same augmented PATH (servers shell out: rust-analyzer → cargo, tsserver → node). Availability is a directory scan, not ~60 which subprocesses.
    • A missing server no longer costs a spawn per keystroke. The manager remembers a failed start and fails fast until lsp_restart_language clears it (the action to take after installing a server — no app restart). Clients are handed out as Arc, so the manager lock is released before awaiting: one cold rust-analyzer no longer stalls hover in every other language. Dead clients are replaced, not reused.
    • Diagnostics are surfaced as Monaco markers under their own owner, polled in a short bounded burst after each sync rather than a standing interval (null = “nothing published yet” is kept distinct from [] = “clean”, so an early poll can’t erase real errors). New status-bar indicator names the active server, or the missing one plus its install command, with click-to-retry. Languages Monaco services in-browser (TypeScript, JavaScript, JSON, CSS, HTML) deliberately get no LSP providers — Monaco’s cannot be unregistered, so ours would duplicate every suggestion.
    • Also fixed in passing⌘. (DiffComplete) and cursor sync acted on whichever tab was open first, because onMount captured activeFilePath once; and Monaco models are now reaped when tabs close, since per-file models would otherwise accumulate a copy of every file ever opened.
    • Language coverage raised to the TIOBE top 30 — 28 of 30, and the 2 remaining cannot be done. An audit cross-referencing the three tables that must agree (useLanguageRegistry.ts ranks, manager.rs server configs, lib/lsp.ts extension routes) found 8 of the top 30 unreachable: MATLAB, Assembly, Ada, PL/SQL, COBOL, SAS, Objective-C and Classic VB had no route from a file extension to a server, so those files got neither IntelliSense nor — because their Monaco language ids were never registered — syntax highlighting. Added servers for matlab-language-server, asm-lsp, ada_language_server, superbol-free (COBOL), sas-lsp, abaplsp, powershell-editor-services, bash-language-server, the Nomic Solidity server, and clangd for Objective-C (a first-class clangd language, not an approximation); PL/SQL and T-SQL ride on generic sqls under their own language ids, with install hints that say so rather than implying dialect awareness. Scratch (a block language whose .sb3 is a zip) and VB6 (no LSP exists anywhere) are asserted as deliberate exemptions — a test pins the exemption list at exactly those two, so a third needs a stated reason. Coverage is now enforced from both ends: every_tiobe_top_30_language_has_a_configured_server (id → binary) and a frontend “TIOBE top-30 coverage” suite (extension → id), because either half alone leaves a dead file type. Also found and fixed: 4 pre-existing servers (vb, dlang, lisp, cfml) reported “not installed” with no install hint, and a test used cobol as its stand-in for an unconfigured language — a false negative the moment COBOL gained a server. .pl (Perl vs Prolog) and .m (MATLAB vs Objective-C) resolve the same way IntelliSense and highlighting both do, so a file can never highlight as one language and complete as another; both are pinned by tests and documented with the unambiguous alternative.
    • Language coverage extended past TIOBE — 84 languages get IntelliSense (79 via a language server, plus the 10 Monaco services in-browser). Added servers for the modern systems languages (Odin ols, Gleam gleam lsp, CUDA via clangd; Zig/Nim/Crystal/V/D/Vala were already wired), the component frameworks (Svelte svelteserver, Vue vue-language-server, Astro astro-ls), functional newcomers (Elm, PureScript, ReScript), infrastructure (Terraform/HCL terraform-ls, Nix nil, CMake, Protobuf protols), shaders and hardware description (GLSL glsl_analyzer, WGSL wgsl-analyzer, SystemVerilog svls, VHDL vhdl_ls), plus LaTeX texlab and Nushell. Two invariants are now test-enforced in both directions: every configured server is reachable from at least one file extension, and every routed extension has a server — which caught cfml (configured, no .cfm/.cfc route, so unreachable) and dlang (an unreachable duplicate of d, now removed).
      • The built-in-service check had to move from the Monaco language to the LSP language. .vue, .svelte and .astro all highlight as html, and html is Monaco-serviced — so the original keying would have silently skipped Volar and svelteserver, the only servers that understand script blocks, props and typed templates. Keyed on the LSP language they run correctly; those files get some HTML suggestions alongside, which is the right trade. LspStatus lost its now-redundant monacoLanguage prop as a result.
      • Highlighting followed routing. Files whose Monaco language id was never registered (matlab, asm, cobol, sas, and now odin, gleam, nix, elm, purescript, rescript, astro, cmake, vhdl, latex) got neither highlighting nor providers, because Monaco rejects an unknown language id. detectLanguage now returns registered ids — including cmake and latex, which previously resolved to plaintext and so could never attach a provider — and the ids are registered at editor mount. Registration makes providers work; it does not add a grammar, so those files still render unhighlighted.
      • Ambiguity kept deliberate and pinned by tests: .v stays V (not Verilog — SystemVerilog uses .sv/.svh), alongside the existing .pl → Perl and .m → MATLAB decisions.
    • Syntax highlighting for the 27 languages Monaco ships no grammar for — Zig, Nim, Crystal, V, D, Vala, Odin, Gleam, Haskell, Elm, PureScript, ReScript, Erlang, Nix, CMake, Protobuf, LaTeX, PostScript, MATLAB, Assembly, COBOL, SAS, Ada, Fortran, Prolog, VHDL, FoxPro and Astro, in a new vibecoder/src/lib/monarch. Each language is a declarative LanguageSpec (keywords, comment markers, string forms) that one tested factory turns into a Monarch tokenizer and a language configuration, so ⌘/ comments correctly and brackets match rather than only colours appearing. Registration is lazy (onLanguage) and never overrides a Monaco built-in — if Monaco gains one of these, theirs wins and ours is skipped, enforced by a test.
      • Zig, Nim, Crystal, D and V stopped borrowing C++/Python/Ruby highlighting. The approximation mis-coloured the keywords each language actually has and left their LSP providers registered against cpp; each now uses its own id and grammar.
      • Two invariants are now test-enforced, both silently broken before: every id detectLanguage can return must be registered (Monaco rejects an unknown id, so matlab/cobol/sas/cmake files got neither highlighting nor IntelliSense, since the LSP providers key on the same id), and every server-backed extension must have a highlighting entry — which caught nine (.mts, .cts, .hh, .hxx, .sc, .psd1, .dpr, .prolog, .cbo) that would have rendered as plain text.
      • The grammar tests run Monaco’s own Monarch engine and assert on real tokens, not on the shape of the definition object — which is the only way to catch what actually breaks. Six genuine bugs surfaced that way and were fixed: in a Monarch regex @@ is an escaped @, so @@? collapsed to @? (an optional at-sign) and coloured every bare identifier in Crystal and ReScript as a variable/annotation; Monarch matches at the cursor with no lookbehind, so MATLAB’s A' transpose needed complete-string-then-operator ordering instead (as written, the rest of every line with a transpose became a string); Ada’s 'x' was consumed by the Obj'Length attribute rule; and CMake’s "${CMAKE_SOURCE_DIR}/src" needed variables inside strings. Also covered: nested comments that would otherwise un-comment the rest of the file ({- -}, /+ +/, #[ ]#), PostScript’s nested (a (b) c) strings, COBOL’s column-7 indicator comments and picture clauses, Fortran’s fixed-form column-1 C, Prolog’s variable-versus-atom distinction, and case-insensitive keywords in COBOL/Ada/Fortran/VHDL/SAS/CMake.
    • A missing language server is now two clicks, not a copy-paste hunt. The status bar offers a Copy install button beside the warning; parseInstallHint splits the backend hint into a runnable command and a documentation link (51 of 79 servers yield a copyable one-liner, 23 a project URL, 5 are genuinely prose like “Included with Xcode”). A test runs the parser across the real hint table in manager.rs and rejects any command that could not be pasted as-is — that is how cargo install --git, truncated at a URL that was actually an argument, was caught. lsp_language_support now returns the raw installHint alongside the human-readable detail, so the editor parses the hint rather than scraping a sentence the backend composed. The command is copied, never executed: installing software is the user’s decision, and several hints need a platform choice first.
    • Tests — 139 vibe-lsp unit tests; 15 end-to-end tests against a real stdio server (including a 1.6 MB notification flood that reproduces the original deadlock, out-of-order replies, timeout recovery, and URI encoding); 3 #[ignore]d smoke tests against real rust-analyzer/clangd; 102 frontend tests for the bridge and status indicator, with enums imported from Monaco’s own standaloneEnums.js so a renumbering can’t slip through. cargo check --workspace --exclude vibe-collab, tsc --noEmit, 362 vibe-coder and 1353 frontend tests clean.

Changed

  • Brand · new app icons across every client, generated from one mark. The icon is now a bold tapered V with a gold cursor block on its baseline, replacing the old V-plus-thin-brackets artwork whose < > dissolved below ~32px and which carried a stray white hairline around the tile. One silhouette family-wide, tinted per client (VibeCoder blue→violet, VibeCLI App purple→indigo, VibeDesk cyan→blue, VibeMobile green→cyan, Watch/Wear gold→orange with a blue cursor) so the apps stay apart in a dock or app switcher. Hues track vibecoder/design-system/tokens.css.
    • Three real bugs fixed, not just a redraw — VibeMobile was still shipping the stock Flutter logo on iOS, Android, macOS and the PWA; VibeCodyWear had a placeholder chevron in mipmap-hdpi only, with no mipmap-anydpi-v26, so modern Wear OS could not mask it to the device icon shape; and VibeAIChat/VibeDesk’s index.html pointed at /vite.svg, a file that existed in neither app (a 404 favicon). All three now carry the real mark, plus android:roundIcon in both Android manifests.
    • Pipelinemake icons regenerates all 143 artefacts from scripts/brand/brandkit.py; make icons-check fails when a committed icon drifts from the mark. Generated icons stay committed, so an ordinary build needs neither the pipeline nor librsvg. scripts/brand/pngkit.py is a dependency-free PNG/ICO codec (no Pillow, no ImageMagick) covering the two jobs rsvg-convert cannot do: stripping the alpha channel the App Store rejects on iOS icons, and writing Windows .ico (DIB entries below 256px, PNG at 256).
    • Per-platform variants, because one PNG cannot serve them all — full-bleed no-alpha squares for iOS/watchOS, Apple’s 824-in-1024 grid plus drop shadow for .icns, Android’s three adaptive layers (foreground scaled to the 66-of-108dp safe zone, background, monochrome for themed icons), circular artwork for Wear OS, and PWA maskable icons. Below 40px the artwork automatically drops the cursor block and grows the V, since at 16px the block is an indistinct smudge. Rationale and the variant table live in assets/brand/README.md.
  • VibeDesk is now a released artifact. It had shipped in no release: release.yml built VibeCoder and VibeAIChat only, ci.yml did not check it at all, and vibedesk/src-tauri/tauri.conf.json declared neither signingIdentity nor entitlements — so the third desktop shell was buildable locally and invisible everywhere else.
    • Release — new build-vibedesk job mirroring build-vibeaichat: the same five-platform matrix (macOS arm64/x64, Linux x64/arm64, Windows x64), the same dual-mode Apple codesigning that falls back to ad-hoc with a ::notice:: when APPLE_CERT_P12_BASE64 is absent, and the same artifact collection (.dmg / .deb / .AppImage / .msi / NSIS .exe). Added to release.needs[] and given its own downloads table in the release body. Deliberately not added to the release job’s critical if: gate — the job has never run on Linux or Windows, and a first-run failure there must not block an entire release; promote it with needs.build-vibedesk.result == 'success' once it ships green.
    • CI — new vibedesk-checks job (typecheck + the no-inline-edit VX-013 guard, the latter having existed as an npm script that nothing ran), wired into ci-gate’s needs and its aggregate result. A release job for code CI never checks is a trap; both checks verified green locally before wiring.
    • Signingvibedesk/src-tauri/macos/entitlements.plist added (JIT + unsigned-executable-memory for the webview, disabled library validation for bundled dylibs, network client/server since every desktop shell autostarts the daemon, and user-selected file read-write for tauri-plugin-dialog), with signingIdentity: "-" and the entitlements path in tauri.conf.json so VibeDesk matches the other two shells.
    • Cost note — VibeDesk’s src-tauri embeds the whole vibecli crate for ProfileStore access exactly as VibeCoder’s does, so the new job’s cost tracks VibeCoder’s rather than VibeAIChat’s; on macOS that includes the Metal mistral.rs backend, pulled unconditionally by a [target.'cfg(target_os = "macos")'] block irrespective of the vibe-mistralrs feature flags.
  • Docs · macOS signing, notarization and DMG troubleshooting (docs/release.md). The existing section covered only the seven CI repository secrets; added the local path — install the Developer ID cert in your login keychain (the APPLE_CERT_P12_BASE64 secret is a CI-only import mechanism), export APPLE_SIGNING_IDENTITY / APPLE_TEAM_ID / APPLE_ID / APPLE_PASSWORD, and build. Documents that APPLE_SIGNING_IDENTITY overrides the committed "signingIdentity": "-", so an ordinary dev build still needs no certificate; that APPLE_PASSWORD must be an app-specific password; that notarization costs 2–15 min per build; and the three codesign / stapler / spctl commands that verify the result. Also records that VibeDesk is absent from release.yml and declares no signingIdentity — it is not currently released at all.
    • error running bundle_dmg.sh — new troubleshooting entry, written against a reproduction. bundle_dmg.sh drives Finder over AppleScript and exits 64 when that osascript call fails, but Tauri captures the script’s output and surfaces only failed to run …/bundle_dmg.sh, so the reason is unrecoverable from the build log. Documents the fix that was actually verified — CI=true (not CI=1: Tauri binds CI to its own --ci flag, which accepts only true/false and otherwise dies with error: invalid value '1' for '--ci'), which passes --skip-jenkins and skips the AppleScript, costing only the DMG’s cosmetic layout. Also covers the leaked-scratch-volume trap where a failed run leaves /Volumes/dmg.* mounted and breaks every retry with a different error, the per-app nature of the Automation permission, and the -1728 “Can’t get disk” race that the script guards with only a fixed 2 s sleep — which is why the same script can succeed standalone on an idle machine and fail right after an LTO link.
  • Mobile · Flutter toolchain floor — raised the CI/release Flutter pin from 3.29.3 to 3.44.2 (Dart 3.7 → ≥3.10) and the vibemobile Dart SDK floor to ^3.8.0, to support flutter_lints 6.0.0 (requires Dart ^3.8.0) and the regenerated lockfile (resolved deps require Flutter ≥3.38.4 / Dart ≥3.10.3). FLUTTER_VERSION updated in ci.yml and release.yml; platform-requirements table in docs/vibemobile.md updated to match.

  • mistral.rs fork synced to upstream v0.9.0 — the pin was 9 commits ahead / 189 behind upstream (fork base 2d4ba4f1 ≈ v0.8.1). Our TurboQuant KV-cache patch was replayed onto upstream master (8010b6a0, post-v0.9.0) as TuringWorks/mistral.rs@vibe/kv-cache-codec-kernels-v0.9; vibe-infer/Cargo.toml now pins 5860f815. The previous branch vibe/kv-cache-codec-kernels is untouched at f8f3a105, so the bump reverts with one line. Zero VibeCody source changes — every API we consume survived (with_isq(IsqType) now routes through the new IsqSetting via builder_macros.rs but keeps its signature; sampler setters, From<TextMessages> for RequestBuilder, and mistralrs::core unchanged).
    • What we gain — the scheduler / concurrent-serving overhaul (#2354) against inference_server.rs; the Metal runtime+AOT compilation rework (#2288) plus hardened kernel builds (#2176, #2139); aarch64 + x86 CPU decode/prefill kernels (#2304, #2311); roughly six removed panic paths that sit in daemon code (chat-template panics #2286, GGUF special-token OOB #2282, engine shutdown #2266, client-disconnect SendError #2170, engine-creation error propagation #2226, errored-sequence cleanup #2243); logprobs corrected to natural log (#2327); reversed FCFS priority in paged preemption (#2250). New models: Gemma 4 12B (+MTP spec decoding), LFM 2.5, Hunyuan v1, DiffusionGemma.
    • candle 0.10.2 → 0.11.0, via a workspace [patch.crates-io] — upstream pins candle to a git rev (27f20fea), not the crates.io release. vibe-infer implements mistralrs::core::KvCacheCodec, whose methods take candle_core::Tensor; a registry candle and a git candle are different crates to cargo even at the same version, so the impl would not typecheck. The patch forces all six candle crates onto one source. vibe-infer is the workspace’s only candle consumer; candle-transformers has no tokenizers dep, so minilm.rs is unaffected.
    • Divergence shrank — the fork patch is 16 files instead of 17. mistralrs-quant/build.rs dropped out entirely: #2288 replaced the hand-maintained METAL_SOURCES array with a metal_source_set! macro that feeds both the AOT and runtime-compile paths from one list (so the split-registration bug class of #2169 is gone by construction), and CUDA was always covered by the existing kernels/*/*.cu glob. The #[allow(dead_code)] on MoEExperts::num_experts also dropped — it is a used pub field upstream now.
    • RotatingCache hooks re-derived, not merged — upstream rewrote the cache into a relocating ring buffer (write_pos / window_start / retained_len). Invariant is now explicit: all_data always holds encoded values, so plain tensors are encoded on the way in, buffer slices decoded on the way out, and buffer-internal window relocation deliberately skips the codec (running it would double-quantize the retained window). The v0.9 snapshot() / restore_from_snapshot() machinery, which our patch predated, needed the codec carried on RotatingCacheSnapshot and re-encoded on restore — without it, restore writes plain values into a buffer whose readers assume encoded, corrupting speculative-decode rollback with no compile error.
    • Two Metal bugs the sync exposed, both silent under the old pin. (1) call_turboquant_encode bound its output buffer via a bare &Bufferset_buffer (the input path) instead of Output::newset_output_buffer, so the kernel’s write was invisible to candle 0.11’s input/output encoder tracking; the Metal-vs-CPU parity test went from passing to 1.48 max abs diff against a 5e-3 tolerance — wrong numbers, not an error. All 48 other call sites in that file already used Output::new. (2) turboquant.metal declared a file-scope constant uint SIMD_SIZE, private under per-file AOT compilation but colliding with MLX’s MLX_MTL_CONST int SIMD_SIZE once #2288’s runtime path concatenates every source into one translation unit; since Kernels::LIBRARY is a global OnceLock, that single collision failed the whole library and took out every quant kernel whenever the precompiled metallib was absent (MISTRALRS_METAL_PRECOMPILE=0 — the standard fallback when the Apple Metal Toolchain is missing). Renamed to TQ_SIMD_SIZE.
    • Verification — 31/31 fork kv_cache tests (including all of upstream’s own ring-buffer tests) and 25/25 vibe-infer tests with mistralrs,mistralrs-metal, on both the AOT and runtime-compile Metal paths; turboquant_encode_{float,half} symbols confirmed present in all three built mistralrs_quant metallibs; cargo check --workspace --exclude vibe-collab clean. Note that the GPU parity test (native_codec_metal_matches_cpu_within_fp16_tolerance) is the only thing that catches this class of bug — a green build and a successful kernel dispatch both looked like success.
    • Dependency-surface note — upstream 0.9 pulls mistralrs-code-exec + mistralrs-sandbox unconditionally (with landlock / seccompiler on Linux only). cargo tree -e features confirms only default, metal, mistralrs-paged-attn are enabled on mistralrs-core, so the code-execution feature is off and the Python code-execution tool is never exposed. cargo deny check (and cargo deny --all-features check, as security.yml runs it) passes clean — advisories, bans, licenses, sources — with https://github.com/huggingface/candle.git added to the allow-git list, which the new [patch.crates-io] requires under unknown-git = "deny". deny.toml’s RUSTSEC-2025-0057 (fxhash) note was corrected: it claimed the advisory would clear on the next fork sync, but bm25 is still 2.3.2 and still depends on fxhash, so it was never ours to fix.
    • Layout assumptions made explicitturboquant/ops.rs destructured storage_and_layout() as (storage, _) for the rotation/projection matrices and passed the kernel a base pointer, so a non-zero start_offset or non-contiguous view would have been read from element 0: wrong numbers, no error. Callers use Tensor::from_vec so it held, but that is the caller’s invariant, not one the module can enforce — the same shape of unchecked assumption behind the output-buffer bug. Both backends now go through a shared ensure_matrix_layout, matching the check input already had.

Fixed

  • CI · the Metal GPU path was never executed anywhere. Every ci.yml job runs on ubuntu-latest, where mistralrs-metal cannot build, and the note claiming “the release workflow uses --all-features on macOS runners” was false — --all-features appears only in that comment and in security.yml’s cargo-deny step, which compiles nothing. mistralrs-metal is compiled in macOS release builds, but via the cfg(target_os = "macos") dep in vibecli/Cargo.toml, and no workflow ever ran its tests. Net effect: the TurboQuant GPU codec was shipped on the strength of cargo check alone, and both Metal bugs found during the v0.9 sync would have reached a release. Added a metal-gpu-tests job (macos-latest) that runs the vibe-infer suite on both Metal paths — precompiled metallib and MISTRALRS_METAL_PRECOMPILE=0 runtime compilation — because those exercise different code and each caught a different one of the two bugs. Wired into the required ci-gate, and the false comment corrected.

[0.5.7]

Release-engineering patch — restores the artifacts that didn’t build for v0.5.6.

Fixed

  • CycloneDX SBOM job (a6d670bf) — cyclonedx-py requirements accepts the requirements path positionally, not as -i FILE; the bad flag caused the tool to fall back to looking for ./requirements.txt and emit CRITICAL | CDX > Could not open requirements file. Drop the -i so vibe-rl-py.cdx.json is produced. Closes #28.
  • Mobile · iOS build (b8d95e0f) — vibemobile/ios/Runner/AppDelegate.swift referenced FlutterImplicitEngineDelegate and FlutterImplicitEngineBridge, both of which were introduced in Flutter 3.38 for the UIScene rework; the CI Flutter SDK is pinned to 3.29.3, so the swift-frontend reported Cannot find type 'FlutterImplicitEngineBridge' in scope. Rewrite to the Flutter 3.29-compatible GeneratedPluginRegistrant.register(with: self) pattern and register the relay-credentials method channel synchronously in didFinishLaunchingWithOptions. Closes #29.
  • Watch · watchOS build (014f5cce) — GoalsView.swift, JobPickerView.swift, RecapView.swift, and TaintedConfirmationView.swift were on disk and referenced by ContentView.swift / SessionPickerView.swift but never added to VibeCodyWatch.xcodeproj’s PBXSourcesBuildPhase. The Swift compiler reported four cannot find … in scope errors and the watchOS simulator app build exited 65. Register each as a PBXFileReference + PBXBuildFile, add to the group and sources phase (plutil -lint passes). Closes #30.
  • Watch · Wear OS build (6193920a) — JobRecapTileService.kt and GoalsTileService.kt import androidx.concurrent.futures.CallbackToFutureAdapter and com.google.common.util.concurrent.{Futures, ListenableFuture}, and RecapScreen.kt uses androidx.compose.ui.tooling.preview.Preview; none were declared as dependencies, so :app:compileReleaseKotlin failed. Add guava (33.4.0-android), androidx-concurrent-futures (1.2.0), and androidx-compose-ui-tooling-preview (1.7.6) to libs.versions.toml and implementation them in app/build.gradle.kts. Closes #31.
  • Docker image build (99d8adfe + f922536b) — the Dockerfile’s two-phase cargo cache (copy manifests → stub sources → prebuild deps → copy real sources) had drifted from [workspace] members. Seven members added since March (vibecli/crates/vibe-sandbox{,-native,-firecracker,-hyperlight}, vibecli/crates/vibe-broker, vibecoder/crates/vibe-infer, vibe-memory) had no manifest COPY, so cargo refused to resolve the workspace. After the manifest sync, the real cargo build then failed because vibe-memory/src/ was never copied over the empty stub, leaving vibecli unable to find MemoryContextHub, ProjectMemStore, GlobalMemStore, MemoryMeta. Add the missing manifests + stubs + real-source COPY. Closes #32.

Docs

  • docs/release.md, docs/vibemobile.md, docs/watchos.md, docs/wearos.md (41f189eb) — fix the download links to match the actual Tauri/Flutter output filenames: VibeAIChat_* (not VibeCLI_*), VibeCody-Mobile-vX.Y.Z-{ios,android}.*, VibeCody-WatchOS-vX.Y.Z.app.zip, VibeCody-Wear-vX.Y.Z.*. Surface the new aarch64.AppImage and arm64.deb artifacts that landed in v0.5.6.

[0.5.6]

Added

  • B2.9.daemon — Plugin hooks fire on the daemon agent path too (41c6382e) — Closes the parallel gap to B2.9 on the daemon side: every AgentLoop::new in serve.rs (/v1/agent start, ACP submit-task, timed-task path) now calls plugin_runtime::merge_with_plugin_hooks(workspace, vec![]) and attaches the resulting HookRunner before agent.run. Plugin hooks were previously silently bypassed on the most-used path (mobile, watch, VibeCoder, VS Code, JetBrains all go through /v1/agent); admin policy now reaches them. User hooks remain CLI-only by design — turning on user-hook dispatch from remote clients is a separate decision.
  • B2.9 + B2.10 — Plugin hooks and rules now actually run — Two follow-up slices that close the original B2 deferrals. plugin_runtime::merge_with_plugin_hooks(workspace, user_hooks) (543255f4) opens the WorkspaceStore, fetches policy-active plugin hooks, converts each HookComponent into a vibe_ai::HookConfig with a Command handler that runs the absolute path under the plugin install dir, and appends them after the user’s hooks — first BLOCK still wins so user veto outranks plugin policy on the same event. Wired at both CLI HookRunner::new sites (run_parallel_agents orchestrator + run_agent_repl_with_context REPL). Best-effort with warn! on any store/loader failure so admin policy can extend the CLI agent path but never break it. context_assembler::collect_plugin_rules (403a53cd) does the parallel job for Markdown rules: every On / Required plugin’s rule files render under “### {plugin}/{rule}” inside a new plugin_rules ContextSection at priority 1, joining the same system-prompt lane the CLAUDE.md / VIBECLI.md / AGENTS.md project memory feeds. Both chat and agent collectors consult it, so plugin rules influence REPL chat and every agent-task path. KNOWN_SECTION_NAMES grows the new entry so /v1/capabilities advertises the shape correctly. Daemon-side AgentLoop sites in serve.rs intentionally not wired in this slice — those have never run user hooks either, and turning on hook dispatch there is a separate decision that affects user-configured hooks too.
  • VS Code Agent Hooks — protocol parity with CLI + JetBrainsvscode-extension/src/hook-executor.ts implements the same seven-event hook contract as vibecli-cli/src/hook_abort.rs and the JetBrains HookExecutor: sh -c <command> (or cmd /c on Windows), exit-code semantics (0 = allow, 2 = block, other = generic-error / non-blocking), structured-JSON-decision stdout override, 30 s per-hook timeout (timeout → BLOCK), spawn-failure ALLOW + warning, and ordered-chain short-circuit on first BLOCK. UserPromptSubmit is gated at every prompt entry point — vibecli.startAgent, vibecli.chat, vibecli.inlineEdit, vibecli.sendSelection, and the sidebar chat webview — each carrying an event-source discriminator ("source": "agent" | "chat" | "inline-edit" | "send-selection" | "chat-webview") so policies can vary by entry point. vibecli.hooks joins the configuration schema as an array<{name, event, command, enabled}> with the seven-kind event enum, surfaced inline by the VS Code Settings UI (aeae6c83).
  • JetBrains Agent Hooks — meaningful gate coverage (P3.10) — Four-commit slice (7709bc0b080bf920) brings the JetBrains plugin to hook-protocol parity with CLI/Tauri. HookExecutor service mirrors vibecli-cli/src/hook_abort.rs: subprocess invocation via sh -c <command>, exit-code semantics (0 = allow, 2 = block, other = generic error / non-blocking), structured-JSON-decision stdout override ({action, reason?, message?}), 30 s per-hook timeout (timeout → BLOCK), spawn-failure ALLOW + warning (matches CLI), and ordered-chain short-circuit on first BLOCK. HookConfig { name, event, command, enabled } persists via the existing PersistentStateComponent infra. Settings UI under IDE Settings → Tools → VibeCLI grows a hooks table with Add / Remove via ToolbarDecorator — Event column constrained to the seven allow-listed kinds matching plugin_manifest::ALLOWED_HOOK_EVENTS. Both user-driven prompt-submission paths now run through the chain: AgentPanel.startAgent and InlineEditAction.actionPerformed fire UserPromptSubmit before any prompt reaches the daemon. Payload carries an event-source discriminator ("source": "inline-edit") so policies can vary by entry point. 14 JUnit cases cover decision semantics, structured-JSON override, chain short-circuit (sentinel-file proof), payload-on-stdin (capture via cat), and the event-kind allow-list as a drift guard. Advisory firings for SSE-arriving PreToolUse / PostToolUse / Stop deferred until a concrete audit-trail use case emerges (the daemon has already run its own pre/post chain by the time those events surface on the plugin side, so firing again would double-fire).
  • A1 — MCP Apps generic React embedding host (SEP-1865)McpAppEmbed.tsx renders fenced mcp.app blocks inline in chat as a typed React card: title + component+version chip + collapsible Props (JSON) + CSP declarations (informational) + action buttons. Components are an allow-list (react@18, react@19, json-view, list, card); unknown component refs render a clear “unsupported” warning with props still inspectable — the host never executes arbitrary JSX. Action clicks dispatch vibecoder:mcp-app-action window events for the chat layer to consume. New mcp_apps_parse Tauri command bridges the daemon-side parser (mcp_apps_payload.rs, shipped earlier as 647b58de) to the webview as defence in depth. Fence regex in AIChat.tsx relaxed from \w* to [\w.+/@-]* so the full MIME-like tag application/vnd.mcp.app+json matches without truncation. Malformed payloads fall back to a plain CodeBlock so the raw bytes stay visible (39e95b17). Closes the last open Phase 53 P0 item — all of A1–A11 now shipped.
  • B2.8 + B2.12 — Plugin governance follow-upsConfigPortability::register_plugin_servers registers MCP-server components from policy-active plugins under namespaced ids plugin:<plugin>:<component>, disjoint from the flat user-configured id space (16da6354). plugin_install::install_from_url adds HTTPS-only URL fetch (60 s timeout, 50 MB cap, scheme guard) so vibecli plugin install <https://…> works alongside the local-file path; new plugin_install_from_url Tauri command and a Local file | HTTPS URL toggle in the governance panel (b7e7f988).
  • B2 — Plugin bundle format with admin install policies — Phase 54 P0 shipped end-to-end across 7 slices. Inner vibecli-plugin.toml manifest schema + validator (B2.1, cea41606); detached per-publisher P-256 ECDSA signing via sibling vibecli-plugin.sig (B2.2, 6275cf06); WorkspaceStore plugin_policies table with Off / On / Required tiers and an admin-only Required-pin guard (B2.3, eb7dcbfe); atomic install (stage → swap with RAII cleanup) preserving Required pins across force re-install (B2.4, 2d52bb4e); runtime view plugin_runtime::enabled_* returning only components from plugins with policy ≠ Off (B2.5, 82d4a00b); PluginGovernancePanel.tsx + 5 Tauri commands (plugin_install_from_file, plugin_list_installed, plugin_uninstall, plugin_get_policy, plugin_set_policy), all sensitive-path-gated (B2.6, fb9b80b6); first per-loader activation — MCP list_skills / get_skill now serve built-in + enabled-plugin skills tagged with {"kind": "builtin" \| "plugin", "plugin": "<name>"} provenance (B2.7, 9c0ac982 + 32793d4d). All four design anchors held: no telemetry-driven personalization, client-side admin-authored policy, open MCPB bundle format (reuses mcpb_bundle.rs), and per-publisher P-256 trust roots (reuses signed_agent_card.rs JWK). See §23 in FEATURE-REFERENCE.
  • /goal — durable execution intent — A new cross-session primitive: each goal is a persistent record of intent (title + statement + success criteria + status: Active / Paused / Done / Abandoned) that decomposes into an ExecutionPlan on demand and gathers a link graph of contributing sessions / jobs / recaps. Shipped end-to-end across 7 slices: types + schema in ~/.vibecli/sessions.db (G1.1), daemon /v1/goals CRUD + plan/link/start routes (G1.2 / G1.3), REPL /goal new|list|show|status|link|start|delete (G1.4), VibeCoder Goals panel with slash-palette opener (G1.5), curated /watch/goals for mobile + Apple Watch + Wear OS (G1.6), and VS Code + Agent SDK + design docs (G1.7). See docs/design/goal/README.md. Tauri commands use the exec_goal_* prefix to avoid conflict with the existing CompanyGoalsPanel surface.
  • /goal — hardening round (G4)GET /v1/goals/:id/tree?depth=N recursive subtree walk (depth clamped 1..10, default 3, cycle-safe, truncation flag) and GET/PUT/DELETE /v1/goals/current for per-workspace “current pin” with pinned_goals cascading on goal delete. CLI gains /goal pin|unpin|current subcommands; aggregate /v1/goals/:id/recap honors { provider, model } for LLM synthesis with heuristic fallback (response carries recap_synthesizer). Apple Watch routes its “Start session” through the new curated /watch/goals/:id/start wrapper, and the VS Code extension grows a vibecli.goalsView tree-view in the sidebar with refresh + per-row context-menu actions.
  • /goal — fan-out round (G5) — Wear OS GoalDetailScreen gains a “Start session” chip backed by WearNetworkManager.startGoal() (curated /watch/goals/:id/start). /agent auto-links new sessions to the pinned goal for the daemon’s workspace (or the global slot) — silent best-effort, never blocks session creation. The TypeScript Agent SDK goals.* namespace adds tree(id, depth?), pin(id, ws?), unpin(ws?), current(ws?), and recap(id, { provider, model }); the Flutter ApiClient gains parallel getGoalTree, getCurrentGoal, pinGoal, unpinGoal. The VibeCoder GoalPanel adds a tree-view toggle (indents children under parents) and an “Aggregate recap” section that routes through selectedProvider + selectedModel from the toolbar (heuristic fallback when either is empty).
  • /goal — pin UX round (G6) — Pin/unpin chips in VibeCoder GoalPanel and the mobile detail sheet, with a ★ marker on whichever goal is pinned in the list. The agent stream emits a system event (AgentEventPayload::system) on every auto-link so SDK / VibeCoder / CLI consumers see “Auto-linked to pinned goal {id_prefix}: {title}” before the model’s first token. auto_link_to_pinned_goal now returns the linked (goal_id, title) so callers can wire attribution UI. End-to-end tests confirm the helper inserts a goal_links row through the real SessionStore::open_default() path (HOME-redirect pattern from G4.1).
  • /goal — goal-aware agent context (G7) — When /agent auto-links a session to a pinned goal, the daemon now synthesizes a model-readable preamble from the goal’s title, statement, success criteria, and current_plan and injects it into AgentContext.approved_plan (only when that field is empty — the Phase 7 S3 context_request path is untouched). auto_link_to_pinned_goal returns the full Goal struct rather than just (id, title) so callers don’t re-fetch. Result: agents now run goal-aware, not just goal-attributed. 5 unit tests cover preamble shape (bare title, statement, criteria, plan steps, empty-plan edge).
  • /goal — client surfacing round (G8) — VS Code extension renders the daemon’s system auto-link event with a distinct [goal] prefix in the agent output channel (0b64f6b4); the SDK AgentEventType is extended so programmatic consumers also see the new kind. Flutter mobile gains a “+ New Goal” flow — AppBar + action + empty-state button → modal with machine picker (only shown when ≥2 paired), 120-char title field, optional statement, and a 409-aware “already exists” snackbar (d2e1b236). VibeCoder main chat intentionally not touched — it runs the agent in-process via Tauri commands instead of consuming the daemon’s SSE stream, so the system event has no rendering surface there without a larger refactor.
  • /goal — “Working toward” banner (G9) — Pinned goal now surfaces above the VibeCoder chat tabs as a compact banner (PinnedGoalBanner.tsx), making the auto-link target visible from the surface users spend the most time on. Polls exec_goal_current every 15 s for external pin changes (CLI / mobile) and also listens for vibecoder:pin-changed window events so a pin/unpin from the Goals panel updates the banner instantly. Click the ✕ to unpin. Zero chrome when nothing is pinned (43e7f697).
  • /goal — search + tag chips (G10)GET /v1/goals accepts ?q=<text> for case-insensitive substring search across title + statement, AND-ed with the existing status / workspace / tag / limit filters. VibeCoder GoalPanel gains a 200 ms-debounced search input in the left header. Tags (schema since G1.1, previously read-only) are now inline-editable in the detail-header chip list — × per chip to remove, in-line input to add (Enter or blur to submit), both routing through exec_goal_update. 2 new daemon tests cover the q-filter; frontend npx tsc --noEmit clean (b755926b).
  • /goal — TUI tree + Watch ★ marker (G11) — TUI Goals screen gains a t key that toggles between flat list and tree layout: children indent under parents via a client-side BFS over parent_goal_id, mirroring VibeCoder’s tree mode. Title-bar advertises the new key and current view mode. The curated /watch/goals payload gains a pinned: bool field (one global-pin lookup plus one workspace-pin lookup per distinct workspace in the list — bounded at the 25-row Watch cap); Apple Watch + Wear OS goal rows render a ★ on whichever goal matches. Older daemons that lack the field decode cleanly (pinned: Bool? on Swift, optBoolean(..., false) on Kotlin). 3 new TUI tests cover tree ordering, orphan-as-root, and view-mode toggle.
  • /goal — Watch ★ everywhere (G12) — Curated /watch/goals/:id envelope gains an envelope-level pinned: bool (workspace-specific OR global slot, computed by the same logic the list uses) so the watch detail screen can render the ★ without a second /v1/goals/current round-trip — the watch never hits /v1/* directly. Apple Watch GoalDetailView and Wear OS GoalDetailScreen show the ★ on the title; Wear OS GoalsTileService now prefers the pinned goal over the freshest-updated row (“what am I working on” beats “what did I touch last”) and prefixes the tile body with ★ when pinned.
  • /goal — VS Code ★ pin parity (G13) — VS Code goals tree closes the cross-surface ★ loop on the editor side: GoalTreeItem prefixes the title with ★ for pinned rows and pinned roots sort above the rest so “what am I working on now” lands at the top of the sidebar. New VibeCLIClient.getPinnedGoalIds(workspace?) unions the daemon’s global pin with the workspace pin (union of two /v1/goals/current calls) so a goal pinned from any surface — mobile, watch, VibeCoder, CLI — surfaces in the editor.
  • /goal — TUI ★ pin parity (G13 cont.) — TUI Goals screen catches up to the rest: every row whose id appears in pinned_goals (any workspace, including the global slot) renders with a yellow bold ★ in front of the title — same glyph the other surfaces use. New p key toggles the pin on the selected row, advertised in the screen’s title bar alongside f, t, r. Unpin walks every workspace slot pointing at the goal (covers the rare both-workspace-and-global case); pin writes to the goal’s own workspace (or globally if the goal is workspace-less), mirroring how the watch / mobile pin flows scope their writes. SessionStore gains two helpers: list_all_pinned_goal_ids() (one SELECT DISTINCT per refresh) and list_pin_workspaces_for_goal(id) (used by the unpin-everywhere walk).
  • TurboQuant-compressed OpenMemory indexCompressedMemoryIndex replaces the legacy f32 HNSW with a ~3 bits/dim PolarQuant + QJL backing store (≥ 8× smaller on disk, same insert/query API). Ships behind no feature flag — every memory write benefits.
  • /memory/stats exposes index telemetry — response now includes embedding_dim, embedding_compression_ratio, and embedding_backend (always "turboquant" today; treat as opaque). Surfaced in the VibeCoder OpenMemory panel, the openmemory_index_stats Tauri command, and the MCP memory_stats tool.
  • vibe-infer crate — pure-Rust local inference traits (Embedder, TextGenerator) with a stub backend by default and an opt-in candle feature that loads sentence-transformers/all-MiniLM-L6-v2 (384-dim, mean-pooled + L2-normalized) via candle 0.10 + hf-hub. candle-metal adds Apple GPU acceleration. Default workspace builds pull no ML deps.
  • Linux arm64 Tauri builds — VibeCoder and VibeCLI App now ship .deb / .AppImage for aarch64 Linux via the GitHub-hosted ubuntu-22.04-arm runner (free for public repos). Matrix coverage now matches VibeCLI (which already had Linux arm64 via cross).
  • Ubuntu 24.04 forward-compat smoke job — new smoke-linux-next CI job runs cargo check --release on vibecoder/src-tauri and vibeaichat/src-tauri against webkit2gtk-4.1 on Ubuntu 24.04. continue-on-error: true and excluded from release.needs[], so distro-drift regressions surface early but never block a tag. Foundation for the Ubuntu 26.04 LTS roll-forward (2026-04-23).

Changed

  • Explicit macOS 12.0 floor — both vibecoder/src-tauri/tauri.conf.json and vibeaichat/src-tauri/tauri.conf.json now set bundle.macOS.minimumSystemVersion = "12.0" (was the Tauri 2 default of 10.13). Matches Apple’s current supported-OS cutoff.

Security

  • Bump rand 0.8 → 0.9 across vibecli/vibecli-cli, vibecoder/src-tauri, vibecoder/crates/vibe-core, and vibecoder/crates/vibe-collab to pick up GHSA-cq8v-f236-94qc (low severity; unsound interaction between rand::rng() and custom log implementations invoking RNG during reseed). Call sites updated to the 0.9 API (thread_rngrng, .gen::<T>().random::<T>(), .gen_range(…).random_range(…)). p256 0.13 SigningKey::random call-sites now use p256::elliptic_curve::rand_core::OsRng to pin the rand_core 0.6 RNG the crate’s signature bound requires.

Added (inference)

  • Mistral.rs backend in vibe-infer (Phase 1 of the Rust-inference runtime plan). New vibe-infer::mistral::MistralGenerator implements TextGenerator on top of the mistralrs 0.8.1 crate (PagedAttention, ISQ, LoRA, OpenAI-compat types, candle 0.10.x transitively). Feature-gated behind mistralrs (CPU) / mistralrs-cuda / mistralrs-metal / mistralrs-flash-attn; defaults unchanged so baseline builds stay fast. Smoke example at examples/generate.rs exercises Qwen/Qwen2.5-0.5B-Instruct end-to-end. The dep is pinned to the TuringWorks fork (TuringWorks/mistral.rs@3d422fde, branch vibe/kv-cache-codec, upstream base v0.8.1) via git + rev so we can iterate on the KvCacheCodec hook without waiting on upstream review. Explicit SHA pin keeps local / CI / release builds reproducible — bump the rev in vibe-infer/Cargo.toml as the fork advances.
  • KvCacheCodec trait landed on the fork (Phase 3 follow-up). TuringWorks/mistral.rs@vibe/kv-cache-codec adds a pub trait KvCacheCodec { fn encode/decode/name } in mistralrs-core::kv_cache with a PassthroughCodec default, threaded through SingleCache / RotatingCache as Option<Arc<dyn KvCacheCodec>>. None (the default) short-circuits to the existing bit-exact append / current_data paths; installing a codec runs encode on every write and decode on every read. Shape + dtype must be preserved by the codec, so the underlying slice_set / narrow buffers stay uniform — true packed-storage codecs need a richer interface and are out of scope for this landing. Round-trip tests on both cache types prove the dispatch path. The cache types (KvCache, SingleCache, RotatingCache) are now re-exported at mistralrs-core crate root so downstream crates can install codecs without plumbing through private modules.
  • CandleTurboQuantCodec bridge in vibe-infer::kv_cache_codec — candle-backed implementation of the fork’s KvCacheCodec trait that reuses the pure-Rust KvCacheTurboQuant from the Phase 3 spike. Encode shuttles the tensor to host-f32, quantizes + reconstructs each head_dim vector via PolarQuant + QJL, and returns a same-shape same-dtype tensor; decode is identity (the reconstruction already happened in encode). Shape contract: head_dim is the last axis. Six unit tests cover shape/dtype preservation, determinism across codec instances, wrong-axis rejection, uniform-random cosine floor matching the spike, and an end-to-end install into SingleCache proving the trait dispatch works. Gated behind the mistralrs feature so default builds stay thin.
  • Install-all codec fan-out + discovery helpers on the fork (Phase 3 wiring). TuringWorks/mistral.rs@3d422fde adds KvCache::set_codec (fans out to both K and V sub-caches; no-op on Shared), EitherCache::set_kv_cache_codec(codec) -> usize (walks every attention layer of a Normal or Hybrid cache, skipping recurrent layers, and returns the install count for logging), plus two async accessors on MistralRs / Model: set_kv_cache_codec(codec, model_id) — locks the pipeline behind the engine’s RebootState and installs in one shot — and kv_head_dims(model_id) — returns (k_head_dim, v_head_dim) from GeneralMetadata::model_metadata so callers can size a codec from the loaded model instead of hard-coding. 8 fork-side tests; lockout is held only for the duration of a pointer-clone per layer, so it’s safe to await mid-runtime even with other engines busy.
  • MistralGenerator::load installs KV codec on demand (Phase 3 completion). New KvCacheMode { Fp16, TurboQuant { seed, qjl_proj_dim } } + KvCacheMode::from_env() resolver reads VIBE_INFER_KV_CACHE / VIBE_INFER_KV_CACHE_SEED / VIBE_INFER_KV_CACHE_QJL_DIM. MistralGenerator::load / load_isq call install_codec_after_load, which queries head-dim from the fork, builds a CandleTurboQuantCodec, and fans it out via Model::set_kv_cache_codec. Layer count is logged at info for observability. MLA-style models (k_head_dim ≠ v_head_dim) warn and pick the larger; pipelines without ModelConfigLike metadata (speech / diffusion) error out loudly instead of silently booting with a broken codec.
  • InferencePanel KV-cache dropdown (Phase 3 UX). When Backend = Mistral.rs is selected, a new “KV Cache” dropdown appears with options FP16 (default) and TurboQuant (experimental). Picking turboquant prepends VIBE_INFER_KV_CACHE=turboquant to the generated cargo run invocation so the daemon picks it up at load time — no recompile required. Hidden for sidecar backends (where the codec has nowhere to install).
  • Mistral.rs wired into InferencePanel + inference_server (Phase 1.4). InferenceBackend::MistralRs is now a first-class variant alongside vLLM / TGI / Triton / llama.cpp / Ollama, marked is_in_process() so callers can skip sidecar provisioning. build_mistralrs_command() emits the VIBE_INFER_MODEL=… cargo run -p vibe-infer --features {mistralrs | -cuda | -metal | -flash-attn} --example generate … invocation; generate_k8s_inference_deployment uses the vibecody/vibecli-daemon:latest image for the in-process path. The VibeCoder Deploy tab adds an “Accelerator” dropdown (CPU / Metal / CUDA / FlashAttn-2) that replaces Port when Mistral.rs is selected, and hides the GPU-count / tensor-parallel / quantization / batch / VRAM-slider / Docker-Compose controls — they don’t apply to an in-process backend that picks its device at compile time.
  • KvCacheBackend trait + KvCacheMethod enum in vibe-infer::kv_cache (Phase 2 — experimentation harness). Declarative surface for swapping attention KV-storage strategies: Fp16 | Fp8 | Int8 | TurboQuant | Custom(name). Each variant carries a CLI-flag name and a bytes-per-element estimate so pod-sizing heuristics agree across backends. KvCacheReport is the harness record — tokens/sec prefill + decode, resident bytes, optional perplexity / recall@k. No runtime effect yet; the actual KV-cache kernel work lands in Phase 3 as a mistralrs-quant contribution.
  • Phase 3 KV-cache TurboQuant spike (vibe-infer::kv_cache_tq + examples/kv_cache_bench) — pure-Rust PolarQuant+QJL prototype over [num_heads, seq_len, head_dim] tensors, with a bench that compares against simulated Fp8 (E4M3) and symmetric Int8. Run with cargo run -p vibe-infer --release --example kv_cache_bench [num_heads seq_len head_dim]. Findings at head_dim=128 against realistic spiked-attention data: 4.57× memory savings vs fp16 (measured 0.4375 B/el — the theoretical ≥5× asymptotic is not reached at typical head_dim because the per-vector 8 B scalar overhead (radius + residual_norm) amortises to +0.0625 B/el), mean reconstructed-K cosine 0.92, softmax attention MAE 0.0000 (below printable precision), and 100% top-1 attention-argmax agreement with fp16 — zero misrouted tokens. On uniform-random stress data, top1_agree drops sharply, but that is a data-shape artifact (flat softmax has arbitrary argmax) rather than a codec fault; judge viability by attn_mae which stays ~0.0002 even there. The KvCacheMethod::TurboQuant.bytes_per_element() estimate is updated 0.375 → 0.4375 to reflect the real number; downstream pod-sizing numbers follow. Spike conclusion: viable for a mistralrs-quant kernel PR — TurboQuant delivers 2.3× more compression than Fp8 without costing attention quality on realistic distributions.
  • TurboQuant-backed semantic scoring in context_streaming (Phase 4 — runtime-agnostic). New SemanticScorer owns a TurboQuantIndex from vibe-core and a caller-supplied EmbedFn; each add_segment embeds + compresses the content, and ContextStreamingEngine::query prefers approximate cosine similarity over the old keyword-overlap scorer when a scorer is attached. refocus(query) rewrites relevance_score across every segment so the RelevanceScore / Hybrid eviction strategies drop segments least relevant to the current task — a prerequisite for 10M–100M-token workflows that outgrow working memory. Optional construction via ContextStreamingEngine::with_semantic(config, scorer); engines built with new(config) behave identically to before. Runtime-agnostic — works with Mistral.rs, vLLM sidecar, or the Claude API; the scorer doesn’t care where embeddings come from.

[0.5.5] — 2026-04-17

Added

  • Apple Watch client (SwiftUI, watchOS 10+) and Wear OS client (Kotlin / Compose, Wear OS 3+) — native VibeCodyWatch / VibeCodyWear apps with pairing, session list, live transcript, and dictated reply; share a single /watch/* backend.
  • /watch/* Axum routes/watch/pair/challenge, /watch/pair/confirm, /watch/sessions, /watch/sessions/{id}/stream, /watch/sessions/{id}/reply. New modules: watch_auth, watch_bridge, watch_session_relay.
  • P-256 ECDSA (secp256r1) pairing — replaces Ed25519 for Apple Secure Enclave compatibility; 64-byte raw public key, signature over SHA-256(nonce ‖ device_id ‖ issued_at_be).
  • URL-only / Bearer pairing everywhere — no QR code or JSON clipboard required; emulator-friendly.
  • Zero-config connectivity — mDNS advertising (_vibecli._tcp.local.), Tailscale Funnel auto-detection, ngrok auto-detect + opt-in auto-start. Clients race all reachable paths.
  • Apple-Handoff-style session continuity — paired devices see live sessions in real time; VibeCoder auto-switches to the Sandbox tab when a watch joins.
  • Google-Docs-style real-time sync — ID-based message reconciliation with content-window dedup; no more 80/512-char truncation.
  • Watch Devices panel in VibeCoder (Governance → Watch Devices) to approve / rename / revoke devices.
  • CI release artifactsVibeCodyWatch-watchOS.app.zip + VibeCodyWear-wearos.apk / .aab alongside existing binaries.
  • Makefile targetsbuild-watch, watch-ios, watch-wear, watch-wear-bundle, build-all.

Fixed

  • 80 / 512-char message truncation — the legacy ring-buffer fallback was replaced with full-content sync.
  • DMG bundling race on macOS 15 — hardened fallback against transient hdiutil attach failures under concurrent DiskImages2 load.
  • Emulator pairing — pairing now works with a pasted URL + Bearer token on Android emulators and watchOS simulators.

Changed

  • Pairing algorithm: Ed25519 → P-256 ECDSA. Previously-paired devices must re-pair once on v0.5.5.
  • Watch / phone auth — JWT (HS256), 32-byte secret in ProfileStore, 30-day default TTL.
  • Version bumped to 0.5.5 across all manifests.

[0.5.4] — 2026-04-03

Added

  • Claude Code System Prompts — integrated 254 prompts from TuringWorks/claude-code-system-prompts: core behavioral guidelines baked into TOOL_SYSTEM_PROMPT; all prompts stored as reference skills in skills/claude-code-prompts/.
  • Auto-mode guidance — when FullAuto approval policy is active, agent receives autonomous execution rules.
  • Error Boundary — React ErrorBoundary catches render crashes with error + stack trace display.
  • 5 dynamic skill files — git-commit, pr-creation, security-review, debugging, simplify.
  • WebView DevTools — auto-open in debug builds for crash diagnosis.

Fixed

  • GLM/Qwen tool call parsing — normalize <|tag|> delimiters so XML tool calls are correctly executed.
  • Incremental file saves during streaming<write_file> blocks flushed to disk as closing tag streams in.
  • Leading newline in generated files — strip \n after <write_file path="...">.
  • <build> and <run> tag variants — recognize block form in addition to self-closing.
  • Apply crash — DiffReviewPanel overlays editor with deferred unmount; removed React.StrictMode.
  • Terminal buffer cleared on tab switch — Terminal stays mounted with display toggle.
  • Duplicate provider keys — 14 providers now return unique "Provider (model)" names.
  • LSP invoke params — fixed snake_case to camelCase field names for hover, completion, goto-definition.
  • Diff review toolbar — thinner, outlined ghost buttons, visible text with ellipsis.
  • Tool call card icons — replaced emoji with thin-line SVG icons using CSS variables.

Changed

  • Agent context window: 80K → 200K tokens; max_steps: 30 → 50.
  • Claude max_tokens: 4,096 → 16,384; Ollama num_predict: 2,048 → 16,384.
  • Retry attempts: 4 → 2 (500ms initial, 5s max backoff).
  • Ollama HTTP timeout: 90s → 300s.

[0.5.3] — 2026-04-02

Added

  • Document & Media Viewers — DocumentViewer, ImageViewer, HtmlPreview, DrawioPreview for VibeCoder.
  • Per-Provider Model Lists — provider-appropriate models with auto-selection; Ollama uses live-discovered models.
  • RL-OS Core Modules — 8 modules (EnvOS, TrainOS, EvalOS, OptiOS, ModelHub, ServeOS, RLHF, MultiAgent) with 660 tests, 10 panels, 20 Tauri commands.
  • Sketch Canvas — drawing with Move tool, inline text, SVG/PNG export, shape recognition, code generation.
  • Training Run Wizard — step-by-step RL training setup wizard.

Fixed

  • VibeAIChat: Empty AI Responses — SSE parser read ev["text"] but daemon sends ev["content"].
  • VibeAIChat: Duplicate Streaming Text — guarded against React StrictMode double-mount race.
  • VibeAIChat: Response Never Completing — fallback completion event on agent exit.
  • VibeAIChat: Stale Model/TokenuseCallback dependencies updated.
  • VibeAIChat: Window Icon — replaced default Tauri icon with VibeCoder icon.
  • Ollama Model List Slow/Missing — removed per-model chat probe; instant name-based filter.
  • Monaco Crash on Apply All — editor kept always mounted.
  • VibeCoder Panel Bugs — TLS Inspector, Design Mode, Screenshot to App, file explorer, Fast Context, SemanticIndexPanel.

Changed

  • Agent Identity — renamed from “VibeCLI” to “Vibe Agent” across all system prompts.
  • RL-OS composite panels registered in panel host, tab groups, and search.

[0.5.2] — 2026-03-30

Added

  • RL-OS: Unified Reinforcement Learning Lifecycle Platform — exhaustive fit-gap analysis against 40+ RL competitors (Ray RLlib, Stable Baselines3, Isaac Lab, TRL, d3rlpy, PettingZoo, SageMaker RL, etc.) identifying 52 gaps across 8 categories and 12 unique capabilities no existing tool provides.
  • RL-OS Architecture Specification — production-grade architecture for 7 core modules (EnvOS, TrainOS, EvalOS, OptiOS, ModelHub, ServeOS, RLHF) with Rust crate structure (vibe-rl/), declarative YAML DSL, RL-aware quantization, and 8-phase roadmap.
  • 12-Stage RL Lifecycle Scorecard — comprehensive lifecycle coverage model; closest competitor scores 5/12 vs. RL-OS target of 12/12.

[0.5.1] — 2026-03-29

Added

  • AI Code Review (ai_code_review.rs, 97 tests) — Qodo/CodeRabbit/Bito parity: 7 detectors, 8-linter aggregation, quality gates, learning loop, PR summary + Mermaid diagrams; /aireview REPL.
  • Architecture Spec Engine (architecture_spec.rs, 108 tests) — TOGAF ADM, Zachman, C4 Model, ADRs, governance engine; /archspec REPL.
  • Policy Engine (policy_engine.rs, 91 tests) — Cerbos-style RBAC/ABAC, 14 condition operators, derived roles, policy testing, YAML, audit trail; /policy REPL.
  • Health Score (health_score.rs, 92 tests) — multi-dimensional codebase health scoring.
  • Intent Refactor (intent_refactor.rs, 89 tests) — natural-language-driven refactoring.
  • Review Protocol (review_protocol.rs, 50 tests) — structured code review workflow.
  • Skill Distillation (skill_distillation.rs, 82 tests) — extract reusable skills from agent traces.
  • Phase 32 P0 — context_protocol, code_review_agent, diff_review, code_replay, speculative_exec, explainable_agent.
  • TurboQuant KV-Cache — PolarQuant + QJL (~3 bits/dim) for vector DB integration.
  • Phase 32 — Advanced Agent Intelligence (6 new modules):
    • context_protocol.rs — Streaming context protocol for long-running agent sessions.
    • code_review_agent.rs — Automated code review with configurable rulesets.
    • diff_review.rs — Change-aware review focused on diff hunks.
    • code_replay.rs — Reproduce past interactions for debugging and auditing.
    • speculative_exec.rs — Predictive code path execution.
    • explainable_agent.rs — Interpretable reasoning chain for agent decisions.
  • FIT-GAP v7 — All 22 Gaps Closed (Phases 23-31):
    • Phase 23: a2a_protocol.rs (A2A protocol), agent_skills_compat.rs (cross-tool skills standard).
    • Phase 24: worktree_pool.rs (parallel worktree agents), agent_host.rs (multi-agent terminal host).
    • Phase 25: proactive_agent.rs (background intelligence scanner), issue_triage.rs (autonomous issue classification).
    • Phase 26: web_grounding.rs (5-provider web search grounding), semantic_index.rs (AST-level codebase understanding).
    • Phase 27: mcp_streamable.rs (Streamable HTTP + OAuth 2.1).
    • Phase 28: mcts_repair.rs (MCTS code repair), cost_router.rs (cost-optimized agent routing).
    • Phase 29: visual_verify.rs (UI screenshot verification), next_task.rs (workflow-level prediction), voice_local.rs (offline whisper.cpp voice), doc_sync.rs (bidirectional spec-code sync).
    • Phase 30: native_connectors.rs (20 service connectors), agent_analytics.rs (enterprise metrics), agent_trust.rs (trust scoring), smart_deps.rs (agentic package manager).
    • Phase 31: rlcef_loop.rs (execution-based learning), langgraph_bridge.rs (LangGraph compatibility), sketch_canvas.rs (sketch-to-code).
  • File Attachments[file.ext] bracket syntax in VibeCLI REPL and VibeCoder chat for attaching documents, code, and images.
  • Image Lightbox — Click image attachments in chat to view full size with download button.
  • System Theme DetectionThemeToggle now respects prefers-color-scheme on first visit.
  • Data Analysis Panel Backend — 9 new da_* Tauri commands.
  • Counsel — Multi-LLM Deliberation (counsel.rs, 534 lines, 20+ tests).
  • SuperBrain — Multi-Provider Query Routing (superbrain.rs, 424 lines, 14+ tests).
  • Web Client (web_client.rs, 1,048 lines) — zero CDN dependencies (air-gap safe).
  • FIT-GAP Code Review Architecture comparison across 12+ competitors.
  • VibeCody vs OpenClaw whitepaper.
  • Demo guides 36-60.
  • 3 VibeCoder composite panels, 7 skill files, 10 new Tauri commands.

Changed

  • Zero Demo Panels — All 23 previously demo-only panels wired to real Tauri backends (34 new commands, 17 new AppState fields). Panel status: 196+ total.
  • Theme Variable Migration — Converted 85+ hardcoded colors to CSS variables.
  • Tests: ~10,535 (0 failures). REPL commands: 106+. Rust modules: 196+. Skill files: ~550. Tauri commands: 360+.
  • Documentation: FIT-GAP through v7, ROADMAP through v5.
  • Provider count: 23 direct + OpenRouter (300+).

Fixed

  • Production Hardening — Zero compiler warnings. Safe unwraps, flush-on-exit, configurable A2A server, poison recovery for Mutex locks.
  • Clippy Clean — All lints resolved across workspace.
  • Tokio Mutex Fix — 45 instances corrected.
  • Crate Metadata — Added description field to 6 Cargo.toml files.
  • Ollama Streaming — Status check fix + streaming hot path optimization.
  • Suppressed warnings in ai_code_review, architecture_spec, diff_review modules.
  • Duplicate REPL handlers removed; missing module stubs created.

[0.5.0] - 2026-03-24

Added

  • 9 Quantum Computing Tools:
    • Statevector Simulator — pure Rust simulator supporting up to 16 qubits with all 14 quantum gates (H, X, Y, Z, S, T, Rx, Ry, Rz, CNOT, CZ, SWAP, Toffoli, Measure). Complex number arithmetic, probability extraction, amplitude readout, and shot-based sampling.
    • Visual Circuit Builder — SVG-based editor with categorized gate palette, click-to-place on qubit wires, multi-qubit gate workflow (control then target), click-to-delete, and live metrics bar (gate count, depth, 2Q gates, circuit volume).
    • Circuit Optimizer — multi-pass optimization: identity cancellation (HH, XX, YY, ZZ, CNOT pairs), gate merging (SS to Z, TT to S), rotation merging (adjacent Rx/Ry/Rz on same qubit), with savings percentage reporting.
    • Bloch Sphere Visualizer — SVG rendering of single-qubit states with oblique projection, axis labels, state arrow, and theta/phi readout.
    • Cost Estimator — pricing comparison for IBM Quantum ($1.60/sec), Amazon Braket ($0.30/task + per-shot), and IonQ (per-gate) with itemized breakdowns.
    • Project Scaffolding — complete project generation for Qiskit, Cirq, PennyLane, and Q# with source, tests, requirements, CI config, and README.
    • Algorithm Templates — 8 pre-built circuits: Bell State, GHZ(n), QFT(n), Grover 2-qubit, Deutsch-Jozsa, Bernstein-Vazirani, VQE ansatz, QAOA.
    • Hardware Topology Viewer — SVG connectivity maps for IBM Eagle (127q), Google Sycamore (53q), IonQ Aria (25q), Rigetti Ankaa-2 (84q), Quantinuum H2 (32q).
    • Multi-language Code Examples — 11 algorithms with implementations in Qiskit, Cirq, and PennyLane (Grover, Shor, VQE, QAOA, QPE, Deutsch-Jozsa, BV, HHL, Quantum Walk, QSVM, QNN).
  • Panel Consolidation (137 tabs to 36):
    • 33 composite panels replacing 137 individual tabs, organized into 9 renamed groups (AI, Project, Code Quality, Source Control, Infrastructure, Data & APIs, Developer Tools, Toolkit, Settings).
    • Reusable TabbedPanel component with keep-alive behavior for sub-tabs.
    • createComposite() factory for one-liner composite panel definitions.
    • Alias-based search — typing old panel names (e.g., “docker”) still finds the consolidated tab (“Containers”).
  • Full-stack Resilience:
    • ResilientProvider wrapper — automatic retry with exponential backoff and jitter on all 21 AI providers.
    • retry_async() generic utility for any async operation with configurable max attempts, backoff, and error classification.
    • is_retryable() classifier covering 20+ transient error patterns (429, 503, timeouts, connection resets, decode errors).
    • Agent loop: stream-level retry (5 attempts, 1-60s backoff), RetryableError event, frontend Retry button preserving completed work.
    • Streaming chat: full retry loop with mid-stream error recovery.
    • 30+ HTTP API calls wrapped with retry: JIRA, GitHub, Linear, Groq Whisper, ElevenLabs, Telegram, Discord, Slack, Signal, Matrix, Twilio, WhatsApp, Teams, OpenSandbox (all operations), BugBot.
  • 11 new Tauri commands for quantum operations (add/remove gate, simulate, optimize, cost estimate, templates, scaffold, circuit detail/delete/clear).
  • 105 quantum computing tests (32 new for simulator, optimizer, templates, cost, scaffold).

Fixed

  • Quantum circuit lookup uses index field instead of array position (circuits remained accessible after deletions).
  • Missing gates array in circuit detail for pre-existing circuits (defaults to empty).
  • Quantum simulator returns tuple arrays matching frontend TypeScript types.
  • TabbedPanel display:contents breaking child panel height.
  • LazyPanels props aligned with refactored createComposite pattern.

Changed

  • Quantum panel expanded from 6 tabs to 11 (Circuit Builder, Simulator, Optimizer, Cost, Templates, Scaffold, Topology, Languages, Quantum OS, Projects, Algorithms).
  • Version bumped to 0.5.0 across all manifests (Cargo.toml, package.json, tauri.conf.json).
  • VibeCLI crate now also builds as a library (vibecli_cli) for Tauri backend integration.
  • Canvas workflow panel properties sidebar added.
  • Tab labels corrected for Red Team, Blue Team, Purple Team.
  • AI/ML Workflow and Model Wizard added to tab groups.

[0.4.0] - 2026-03-21

Added

  • Warp terminal-style features (warp_features.rs, 55 tests):
    • # natural language command — type # find large files to generate shell commands via AI with explanation and confirmation.
    • Command corrections (thefuck-style) — 13 built-in rules for typos (gti to git), missing sudo, git push upstream, permission denied, wrong Python version, etc.
    • Secret redaction — auto-detects and masks API keys (sk-****), AWS keys (AKIA****), GitHub tokens (ghp_****), Bearer tokens, passwords, and private keys in command output.
    • Next command suggestions — proactive hints after successful commands (git add to git commit, cargo build to cargo test, etc.).
    • Block-style output — shell command output formatted with colored left border (green=success, red=failure), command header, and duration display.
    • Desktop notifications — macOS/Linux notifications for commands taking longer than 30 seconds.
    • Output filtering and AI error explanation prompts.
  • Auth scaffolding expanded to 85+ frameworks across 17 languages — Go (Gin, Fiber, Echo, Chi, Hertz), Java (Spring Boot, Quarkus, Micronaut, Vert.x, Helidon, Javalin), Kotlin (Ktor, http4k), C# (ASP.NET Core, FastEndpoints), TypeScript (Next.js, Fastify, NestJS, Hono, Elysia), Python (FastAPI, Django, Flask, Starlette, Litestar), Rust (Axum, Actix, Rocket), Ruby (Rails, Sinatra), PHP (Laravel, Symfony), Elixir (Phoenix), Scala, Swift, Dart, Clojure, Haskell, Crystal, Nim, Zig. Auth providers expanded to 40+ including SAML, LDAP, OIDC, Passkey, TOTP, and 10 BaaS platforms. UI: searchable grid with language filter.
  • Best-in-class documentation (20 new files, ~5,500 lines):
    • llms.txt and llms-full.txt — AI-agent-optimized project docs following the llms.txt standard. First AI coding tool to support this.
    • quickstart.md — zero-to-productive in 5 minutes.
    • 3 tutorials: first-provider setup, agent workflow, AI code review.
    • api-reference.md — complete HTTP daemon API reference with curl examples for all endpoints.
    • Per-provider setup guides: Ollama, Claude, OpenAI, DeepSeek, Gemini.
    • troubleshooting.md (24 issues), faq.md (22 questions), glossary.md (50+ terms), security.md (13 sections), CHANGELOG.md.
    • Jekyll nav reorganized: quickstart-first user journey ordering.
  • Full ANSI markdown rendering in VibeCLI REPL:
    • Headers (H1-H4) in bold green/cyan/magenta/blue.
    • Bold, italic, bold+italic text styling.
    • Inline code with gray background and cyan text.
    • Unordered and ordered lists with styled bullets/numbers.
    • Blockquotes with green pipe and italic text.
    • Task lists with checkbox symbols.
    • Horizontal rules, links with underlined text and dim URLs.
    • Code blocks with line numbers, language labels, dark background, and syntect syntax highlighting.
  • Claude Code-style tool call rendering — dark background boxes with terminal-width padding, green checkmark or red cross, tool output displayed below (capped at 30 lines).
  • MCP panels consolidated — merged MCP, MCP Lazy, and MCP Directory into a single unified panel with 4 tabs (Servers, Tools, Directory, Metrics).
  • Model name in REPL prompt[vibecli ollama (deepseek-chat)] > shows both provider and model.

Fixed

  • All GitHub URLs corrected from vibecody/vibecody, AceCana662/vibecody, AiChefDev/vibecody to TuringWorks/vibecody across 18 files (docs, GitHub Actions, package.json, config).
  • DeepSeek default model: deepseek-coder to deepseek-chat (V3 current).
  • Gemini default model: gemini-2.0-flash to gemini-2.5-flash (latest).
  • Streaming chat response rendering — replaced flawed stream-then-clear-then-rerender with direct rendering, eliminating blank line artifacts.
  • Rustyline prompt double-bracket and cursor offset — switched to plain text prompt for reliable cursor positioning.
  • Tool output now displayed in agent REPL (was captured but not shown).
  • REPL args trimming — extra spaces after commands removed.
  • All decorative emojis removed from REPL output (70+) and documentation (1,138 replacements across 14 files).
  • Build warnings resolved (zero warnings, zero errors).
  • .vibecli/ added to .gitignore (auto-generated local data).

Changed

  • 23 direct AI providers (was 17): added MiniMax, Perplexity, Together AI, Fireworks AI, SambaNova, plus Gemini provider upgrade.
  • Cost estimation expanded to cover all 23 providers with per-model pricing (was only Claude + OpenAI).
  • Doctor command checks all 14 cloud provider API keys (was 4).
  • Help text reorganized by popularity with all 23 providers listed.
  • 55 new unit tests (warp_features), 130 new provider tests, 812 gap-closure tests.
  • Documentation icons replaced with plain text (Yes/No/Warning instead of emoji checkmarks).

[0.3.3] - 2026-03-20

Added

  • 5 new AI providers: MiniMax, Perplexity, Together AI, Fireworks AI, SambaNova — bringing the total to 23 supported providers.
  • FIT-GAP v6: 19 new competitive gaps identified and closed across agent capabilities, context management, and cloud integrations.
  • 17 new Rust modules:
    • channel_daemon.rs — Always-on background listener for multi-platform integration.
    • vm_orchestrator.rs — Virtual machine lifecycle management for cloud sandboxes.
    • spec_pipeline.rs — Spec-driven development pipeline with EARS syntax support.
    • branch_agent.rs — Autonomous branch management with PR creation workflows.
    • design_import.rs — Figma/Sketch design-to-code import pipeline.
    • audio_output.rs — Text-to-speech for agent responses and accessibility.
    • org_context.rs — Organization-wide context sharing across teams.
    • session_sharing.rs — Share agent sessions with teammates via link or export.
    • ci_gates.rs — Quality gates for CI pipelines with configurable thresholds.
    • data_analysis.rs — Tabular data analysis with chart generation.
    • managed_deploy.rs — One-click deploy to Vercel, Netlify, Railway, Fly.io.
    • context_streaming.rs — Streaming context injection for long-running sessions.
    • extension_compat.rs — Extension compatibility verification and migration.
    • model_marketplace.rs — Browse and install models from community marketplace.
    • agentic_cicd.rs — AI-driven CI/CD pipeline generation and optimization.
    • cross_surface_routing.rs — Route agent actions across CLI, UI, and API surfaces.
    • soul.rs — Project philosophy document management with agent integration.
  • 10 new VibeCoder panels: Soul, McpLazy, ContextBundle, CloudProvider, ACP, McpDirectory, UsageMetering, SweBench, SessionMemory, IDP.
  • Gemini provider upgraded to native implementation (previously OpenRouter-only).
  • Best-in-class support documentation: troubleshooting guide, FAQ, glossary, security practices, and this changelog.
  • llms.txt for AI-friendly project context.
  • Tutorial guides for getting started, provider configuration, and skill development.

Fixed

  • DeepSeek default model updated from deprecated deepseek-coder to deepseek-chat (V3).
  • Gemini default model updated to gemini-2.5-flash.
  • Cost estimation now covers all 23 providers (previously only Claude and OpenAI).
  • Doctor command checks all 14 cloud provider API keys (previously only 4).
  • Session resume stability improved for cross-version session files.
  • Monaco editor performance with files over 1 MB (disabled minimap by default for large files).

Changed

  • Provider help text reorganized by popularity tier (Local, Major Cloud, Specialized, Meta).
  • 812 new unit tests across 17 modules, bringing the workspace total to approximately 6,050.
  • All production unwrap() calls replaced with expect() with descriptive messages.
  • Release profile optimized: LTO enabled, symbols stripped, panic set to abort, opt-level=s for workspace with opt-level=2 for vibecli.

[0.3.2] - 2026-03-14

Added

  • Blue Team module (blue_team.rs, 49 tests) — Defensive security operations: incident management with P1-P4 severity, IOC tracking across 9 indicator types, SIEM integration for 8 platforms (Splunk, Sentinel, Elastic, QRadar, CrowdStrike, Wazuh, Datadog, SumoLogic), forensic case management, detection rules with platform-specific query generation, playbooks with 8 action types, and threat hunting workflows.
  • Purple Team module (purple_team.rs, 38 tests) — ATT&CK-aligned security exercises: 14 tactics, 20 pre-loaded techniques, attack simulation with outcome tracking, detection validation, coverage gap analysis, heatmap generation, and cross-exercise comparison.
  • IDP module (idp.rs, 80 tests) — Internal Developer Platform support for 12 platforms: Backstage, Cycloid, Humanitec, Port, Qovery, Mia Platform, OpsLevel, Roadie, Cortex, Morpheus Data, CloudBolt, Harness. Includes service catalogs, golden paths, DORA-metric scorecards, self-service infrastructure provisioning, and team onboarding.
  • 3 new VibeCoder panels: BlueTeamPanel (7 tabs), PurpleTeamPanel (5 tabs), IdpPanel (7 tabs).
  • 3 new REPL commands: /blueteam, /purpleteam, /idp with full subcommand sets.
  • Workspace total: approximately 5,912 tests with 0 failures.

[0.3.1] - 2026-03-13

Added

  • Futureproofing Phases 10-14: 10 new Rust modules implementing 12 FIT-GAP v5 gaps (419 tests total):
    • MCP lazy loading with tool search and LRU eviction.
    • Context bundles (Spaces) with priority ordering and TOML serialization.
    • AWS/GCP/Azure deep integration: service detection, IAM policy generation, Terraform/CloudFormation/Pulumi templates, cost estimation.
    • ACP (Agent Client Protocol) server/client modes with capability negotiation.
    • MCP verified plugin directory with search, install, and review pipeline.
    • Usage metering credit system with per-user/project/team budgets and alerts.
    • SWE-bench benchmarking harness for run/compare/export.
    • Session memory profiling with leak detection and auto-compact.
    • SOC 2 compliance controls with audit trail, PII redaction, and retention policies.
    • Unified voice+vision+code multimodal agent.
  • 8 new VibeCoder panels and 4 new REPL commands.
  • 12 v5 capability gaps catalogued and Phases 10–14 planned.
  • Workspace total: approximately 5,745 tests with 0 failures and 136+ panels.

[0.3.0] - 2026-03-09

Added

  • FIT-GAP v4: All 23 identified gaps closed, including automations, self-review, MCP apps, agent teams v2, semantic MCP, docgen, remote control, AST editing, CI status checks, VS Code sessions, cloud sandbox, plan documents, security scanning, sub-agent roles, and edit prediction (RL Q-learning).
  • Competitor Parity: 13 new modules closing all code-addressable “Partial” entries from competitive analysis — debug mode, three agent modes (Smart/Rush/Deep), conversational search, clarifying questions, fast context (SWE-grep), image generation agent, discussion mode, full-stack generation, enhanced agent teams, team governance, cloud autofix, GitHub Actions agent, and render optimization.
  • App Builder (app_builder.rs, 70 tests) — Template-based application scaffolding with AI enhancement.
  • Infinite Context (infinite_context.rs, 79 tests) — 5-level context hierarchy with token budget, eviction, compression, and LRU caching.
  • Blitzy Parity: Batch builder (109 tests), QA validation pipeline (99 tests), legacy migration engine supporting 18 source languages including COBOL and Fortran (101 tests), and unified git platform manager for 5 hosting services (111 tests).
  • 13 new skill files and 4 new VibeCoder panels.
  • Workspace total: approximately 5,236 tests with 0 failures.

Changed

  • Security audit completed: 20 findings (P0-P3) all resolved, including path traversal prevention, cryptographic IDs, CORS hardening, and command blocklist.
  • All production unwrap() calls replaced with expect() with descriptive messages.
  • Release profile added to workspace: LTO, symbol stripping, panic=abort.

[0.2.x] - 2026-02 through 2026-03

Earlier releases established the foundation:

  • Core agent loop with tool calling, streaming, and multi-provider support.
  • VibeCLI with TUI (Ratatui) and REPL (Rustyline).
  • VibeCoder with Tauri 2, React, Monaco Editor.
  • 17 AI providers including Ollama, Claude, OpenAI, Gemini, and FailoverProvider.
  • MCP client and server support.
  • Container sandbox with Docker/Podman/OpenSandbox.
  • 500+ built-in skills across 25+ categories.
  • RAG pipeline with document ingestion, web crawling, and vector database support.
  • Gateway system supporting 18 messaging platforms.
  • Voice input (Groq Whisper), pairing (QR code), and Tailscale integration.

See the Changelog for the complete feature history.