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```suggestionblocks.- 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
PostImageindex mapspath → new-line → textfrom 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.
AnchorVerifiedmeans 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. --bugbotis a real flag now.bugbot.rshad advertisedvibecli --bugbot --diff/--pr 123in its module docs since it was written; no such flag existed and the only caller was the GitHub webhook.--bugbotreviews uncommitted changes,--stagedthe index,--pr Na pull request, and it exits 1 on any error-severity finding so it drops into a pre-push hook or CI step.--propose-fixesadds suggestions;--apply-fixeswrites them, skipping any file that moved since the diff and printing both counts.--prrefuses a non-GitHub remote instead of guessing a slug that would review an unrelated repository.
- 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
- 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 acoverageobject 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 distinguishesSome(vec![])(the model looked and found nothing) fromNone(it never answered); files whose every pass errored are reported as unreviewed,llm_calls_failedis counted separately, and the caveat points atvibecli --doctor. Found by running the command against a scratch repo, not by the build. --passes Ntrades 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.
- 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
- 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.mdper directory,name+descriptionfrontmatter) 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/*.mdcompanion 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, theexamples/*.pyhelpers) were already dangling at the source and are reworded rather than shipped as dead ends.
- Every relative cross-reference was rewritten, because a flat catalogue has no
Fixed
list_skillsandget_skillre-read the entire catalogue on every MCP call. Both calledSkillCatalog::load_from_with_cwd_pluginsper invocation — 1,143 file reads, ~990 YAML parses, and aWorkspaceStore::open(which creates the encrypted database, in whatever directory the MCP host happened to launch in) to answer one question. An agent callinglist_skillsthree times in a turn paid it three times.skill_catalog::load_with_cwd_plugins_cachednow shares oneArc<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
WorkspaceStoreopen is now gated on<cwd>/.vibecli/workspace.dbalready existing, so the skills path stops creating stray workspace databases in scratch directories.
- 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
-
Adding or removing a skill did not rebuild the binary.
include_dir!bakesskills/**in at compile time; rustc’s dep-info tracks the contents of the files the macro expanded to, but not the directory listing.build.rsalready emittedrerun-if-env-changeddirectives, 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=skillsnow 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 ~710skills/*.mdfiles were never packaged. The resolver’s primary path was${CARGO_MANIFEST_DIR}/skills, baked in at compile time, so an installedvibeclilooked 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.ymltars the bare executable, so no siblingshare/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, soskill.pathstill 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_serverandskillforge_indexeach carried a copy of the fallback chain; both now callskills_embedded::resolve_skills_dir().VIBECLI_SKILLS_DIRstill wins and is used verbatim — an override that silently fell through to the embedded copy would hide an operator’s typo. vibecli doctorreports 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.
- The catalogue is now compiled into the binary (
-
POST /webhook/githubfailed open when no webhook secret was set. Signature verification ran onlyif 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 theset-keycommand that fixes it. -
The GitHub App webhook secret could not be stored encrypted.
GithubAppConfig::resolve_webhook_secretreads the ProfileStore keygithub_app_webhook_secretfirst, per Zero-Config First — butvibecli set-keyvalidates the name against a fixed list that omitted it, so the command answered unknown provider. The only reachable paths were a plaintextconfig.tomlfield and an environment variable, both of which the same rule forbids for a secret.vibecli set-key github_app_webhook_secret <secret>now works, andlist-keysshows it. [github_app] auto_fixwas 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 honestfixes_proposedcount, 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-variantEmbeddingProviderenum (Ollama with a hard-coded127.0.0.1:11434, OpenAI locked totext-embedding-3-small), whilevibe-infercarried a separateEmbeddertrait 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-3is the strongest code-retrieval option), Cohere, Gemini, and an in-process candle backend registered at runtime sovibe-embednever 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-embedand 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.
EmbedKindis a required argument, expressed natively where the provider supports it (Voyage/Cohereinput_type, GeminitaskType) 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>.jsonplus a small.meta.jsonsidecar 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-statusin the REPL, Agent SDK + VS Code client methods, anembeddingblock in/healthand the startup banner, anddocs/embeddings.md. [index]config is finally wired.embedding_provider/embedding_modelexisted but were referenced only by tests;/indexused its own hard-coded literal. An unrecognised provider is now an error rather than a silent fallback to Ollama.
- Six providers — Ollama, OpenAI (base-URL overridable, so Azure / LiteLLM / vLLM / text-embeddings-inference need no new variant), Voyage (
- Voice input on every client — one daemon route, one shared hook. Before this, the whole voice stack (
VoiceDispatcherinvoice.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).- Daemon —
POST /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 audioContent-Type+X-Voice-Language/X-Voice-Prefer-Localheaders. Seven audio types are recognised; anything else is a415rather than a guessed extension.GET /voice/statusreports what the machine can actually do, including acan_transcribethat 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_enginereports which engine ran, so a response can saylocal_whispervscloud_whisperinstead of the caller guessing. 7 new serve tests, including one asserting a 2 MB upload gets past the body layer. - Shared React hook —
packages/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_statusTauri 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 deadsrc/hooks/useVoiceInput.tsare deleted in favour of the shared one;@vibe/sharedis now aliased in its vite/vitest/tsconfig.transcribe_audio_bytes(Groq-only) is removed and the file-basedtranscribe_audiorenamed totranscribe_audio_fileto free the shared command name. macOS mic entitlement (com.apple.security.device.audio-input) added to all three, plusInfo.plistusage strings for VibeDesk and VibeAIChat — both keys are load-bearing and fail differently. - VibeMobile —
VoiceService(speech_to_texton-device recogniser →record+ upload fallback) with aVoiceStatussum type mirroring the web hook,VoiceMicButton/VoicePartialStripwidgets in the chat and watch-chat composers,ApiClient.transcribeAudio/voiceStatus, iOSNSMicrophoneUsageDescription+NSSpeechRecognitionUsageDescription, AndroidRECORD_AUDIOand the Android 11+RecognitionService<queries>entry (without which the recogniser is invisible on every device). - Editor plugins — SoX
reccapture, matching whatVoiceDispatcher::listenhas 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 — webviewgetUserMediais unreliable) plus aVibeCLI: Dictatecommand. JetBrains: mic button in the Chat tool window. Neovim::VibeCLIVoicetoggle (!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 SDK —
agent.transcribe(audio, {mimeType, language, preferLocal})andagent.voiceStatus(). 9 new vitest cases; 60 total green.
- Daemon —
Fixed
-
The code index persisted API keys in plaintext.
EmbeddingProvider::OpenAI { api_key }wasSerialize, and the index was written as plain JSON — so every.vibecli/index.jsonbuilt 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 nosk-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.0against 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; andvibe-memory’s SQLite had no model or dimension column at all, so aVIBE_MEMORY_DIMchange 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 thevibeclidependency. Since nearly every daemon route is behindrequire_authand 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/healthreuse, binary resolution beyond barePATH, poll-to-deadline for the ~16 s cold start, and four distinct failure states. -
Stale bearer tokens produced a permanent 401 loop.
vibecli servemints 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, andcrates/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 asend_authedhelper that retries once with a token read straight from~/.vibecli/daemon.token, deliberately bypassing the explicit-token /VIBECLI_TOKENprecedence — 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’srenderContentextracted 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 sharedMarkdowncomponent 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) andvar(--border)(74 uses, plus--border-default/--border-secondary/--border-primary) were referenced but defined nowhere; CSS drops an undefinedvar()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-btndeclared nobackgroundand nocolor, so 43 buttons carrying no modifier class fell through to the browser’s nativebuttonface/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,/jobsand 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_TOKEN→VIBECLI_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/goalsupplies the durable intent +success_criteria,/loopsupplies the run-until-done engine. Parsing, prompt rendering, and hydration are pure and live inloop_engine.rs(GoalBrief,hydrate_goal_loop,goal_loop_prompt,goal_validator_prompt,LoopSpec.goal_id—#[serde(default)], so pre-existingloops.jsonjobs still load); the store-touching edge (resolve_goal_brief/goal_brief_by_id/link_goal_loop_start/record_goal_loop_outcome) lives inexec_goal_repl.rsover a newSessionStore::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
DONEonly 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 back —
Doneflips the goal todone; 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 showcarries the history. Already-donegoals are refused at start. - Bounds + secrets —
--max-iter Nand--max-duration 45moverride the 20-iteration / 30-minute defaults (a mistyped bound errors rather than silently falling back);--secret NAMEworks as on any other loop. - Machine-off parity —
POST /v1/loops {"args":"goal <id>"}hydrates server-side, and the daemon’s hosted scheduler now runs a real done-validator:LoopExecutor::run_iterationtakes avalidator: Option<&str>,scheduler_ticktakes avalidator_lookupclosure (criteria re-read each tick, so mid-run edits are honoured), andProviderLoopExecutorasks 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_engineunit tests, 2 newhosted_loopscheduler tests, afind_goal_by_prefixstore test (incl.LIKE-wildcard rejection), and 4 newloop_engine_bddscenarios (10 total). REPL completions,/loophelp, anddocs/vibecli.md(§ Goal-Driven Loops) updated.
- 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
- 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
Trajectoryschema, 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 abest_skill.mddeployed with zero inference-time overhead.- Crates —
skilllensai-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-rsfacade. Provider-agnostic via a crate-localSkillLlmtrait; neither crate depends onvibecli/vibe-ai.cargo clippy --features cli -- -D warnings+cargo fmt --checkclean. - Daemon bridge —
vibecli/vibecli-cli/src/skillforge_index.rsadapts both crates ontovibe_ai::AIProvider(AiProviderLlm), reuses the existingskill_catalog::SkillCatalog(no re-parse ofskills/*.md), and exposes ten routes:/v1/skilllens/{skills,skills/:name,refresh,convert,extract,score}+/v1/skillopt/{train,status/:job,cancel/:job,promote}.trainis async-job (spawn + pollstatus);promotewrites*.opt.mdto the per-workspace override dir (<ws>/.vibecli/skills/, fallback~/.vibecli/skills/) and never overwrites a shipped skill./health.skillforge+ the startup banner reportloading → ready (N skills). 10 bridge tests green;cargo checkclean. - Watch mirror — two curated
/watch/skilllens/*routes (skillscompact{count,top5},skills/:nameone-line) for the wrist form factor, registered inwatch_bridge.rs. - VibeCoder panel —
SkillForgePanel.tsx(Catalog / Lens / Optimize) mounted as a “SkillForge” tab inAiMlComposite. 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 asmobile_get_active_session) — the daemon stays the single source of truth.tsc --noEmitclean. - Client fan-out — full surface (
skilllens.{list,get,refresh,convert,extract,score}+skillopt.{train,status,cancel,promote}) in VS Codeapi-client.ts+ the Agent SDK (agent.skilllens.*/agent.skillopt.*); read-only catalogue + train-status on Flutterapi_client.dart(skilllensSkills/skilllensSkill/skilloptStatus), Apple WatchWatchNetworkManager.swift(loadSkilllensSkills/loadSkilllensSkill), and Wear OSWearNetworkManager.kt(skilllensSkills/skilllensSkill). Every LLM-calling client method takesprovider+model(STRICT — no hard-coded Anthropic). - Docs —
docs/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 +TrainConfigreference),docs/demos/index.md(Agentic Systems row) + a “What’s Next” cross-link fromdocs/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 innotes/skillforge/. - Follow-ups deferred — efficacy-metric substrate (LLM-judge vs embedding-overlap); nightly “sleep” job (offline self-evolution with experience replay); external benchmark
Envimpls (SWE-bench / BFCL) behind abenchmarksfeature. - Per-epoch SSE streaming + true cancellation —
skilloptai::trainer::train_with_signalsthreads a dependency-freeCancelToken(Arc<AtomicBool>, checked at the top of each epoch) and an optional per-epochEpochEventmpsc channel;train()is now a thin wrapper with empty signals. NewPOST /v1/skillopt/train/streamSSE route (job→epoch* →done/error, 15 s keep-alive) shares the same job map as poll-based/train, so/status+/cancelwork on both.cancel/:jobnow flips the live token so the run stops at the next epoch boundary instead of running to completion;TrainingReport.cancelleddistinguishes 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 --checkclean. Client SSE consumption — the VS Code extension (VibeCLIClient.skilloptStreamTrain) and Agent SDK (agent.skillopt.streamTrain) consume the stream asAsyncGenerator<SkilloptTrainEvent>({type:'job'|'epoch'|'done'|'error', …}) via a newreadSseTypedEventshelper (typedevent:+data:parser layered over the existingdata:-only SSE helpers; the Agent SDKstreamTrainasync 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 --noEmitclean for both clients; 42 Agent SDK tests green. - Promoted-skill override dir —
promotenow writes<skill>.opt.mdto 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 shippedskills/*.mdstay pristine — no in-repo overwrite. The catalogue list surfaceshas_promoted_overrideper skill and the detail view surfacespromoted_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 —SkillCatalogstill wins over same-named plugins). The write is factored into a purewrite_promoted_override_inhelper for unit testability. 4 new bridge tests (dir resolver, write helper, stem-keyed scan, missing-dir empty); 17 bridge tests +cargo check+tsc --noEmitclean. Client-facing text in the VibeCoder panel, REPL/skillforge promotehelp,commands.rsdoc, and the VS Code / Agent SDKpromotedoc comments updated to point at the override dir. - Real agent-job history env — a third train env kind,
history, derivesEvalTasks from actual agent runs instead of the catalog. The CLI now writes a lightweight per-sessionSkillEvalRecord(<session_id>-eval.json) at the end of every agent run viavibe_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>.jsonltrace (secrets scrubbed, same redaction assave_messages/save_context).env.kind=historyscans~/.vibecli/traces/(or anenv.tasksoverride dir) via the newvibe_ai::load_eval_recordsand builds one task per run: the session’s prompt becomes the task prompt; the grader isLlmJudge(default — rubric cites the reference final answer + tool-success rate + completion; one extra LLM call per task per epoch) orContains(free, weak — a phrase from the reference answer), selected viaenv.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_recordsinvibe-ai::trace(re-exported fromvibe_ai);EnvKind::History+EnvSpec.grader+HistoryGrader+RepoAgentEnv::from_history+history_trace_dir+parse_history_grader+rubric_for+phrase_frominskillforge_index.rs. The eval record is written at bothAgentEvent::Complete(completed=true) andAgentEvent::Partial(completed=false) in the interactive agent path (skipped onError— no reference answer); a smallhistory_tool_success_ratehelper computes the rate (1.0when 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 optionalenvGrader:'llm_judge'|'contains'; the catalog-derivedrepoenv is unchanged. - Gap-closure pass — closed the seven post-Phase-5 visibility gaps tracked in
notes/skillforge/07: (G1) VibeCLI REPL/skillforgecommand (list/show/refresh/score/train/status/cancel/promote/health, in-process, STRICT viaactive_provider/active_model); (G2) VibeCLI TUISkillforgeComponent+SkillForgescreen (/skillforgefrom chat, catalogue + train-jobs pane +/healthfooter); (G3)AgentContext.skill_healthrendered as a## Skill Healthsection inbuild_system_prompt, auto-gated oncached_reports > 0(no prompt bloat when nothing is scored) — populated byskillforge_index::render_health_line()at the daemon’sAgentContextconstruction sites; (G4) Apple WatchSkillforgeView(6th “Skills” tab,top5→ detail); (G5) Wear OSSkillforgeScreen+SkillforgeDetailScreen+SkillforgeTileService(routes + deeplink + manifest); (G6) FlutterSkillforgeScreen(8th “Skills” bottom-nav tab, cross-machine catalogue + detail + train-status lookup); (G7) VibeAIChat — 10 SkillForge Tauri proxy commands registered invibeaichat/src-tauri(no panel; the bespoke UI has no AiMlComposite/toolbar).cargo checkclean (vibecli, vibe-ai, vibecoder/src-tauri, vibeaichat/src-tauri); 199 TUI + 44 repl tests + 2 new context-assembly tests green;dart analyzeclean on new/touched Flutter files;swiftc -parseclean on Watch files. Full ledger innotes/skillforge/07 — Client Visibility & UX Gaps.md.
- Crates —
- Code Graph integration (kodegraph) — token-reduction substrate. Wired the standalone
kodegraphcrate (tree-sitter → SQLite code-knowledge-graph) into the daemon as a single bridge modulevibecli/vibecli-cli/src/graph_index.rs(kodegraph is a dep ofvibecli-clionly;vibe-ai/vibe-corestay kodegraph-free and receive pre-rendered strings /SymbolInfovecs):- Background build on daemon startup — non-blocking
std::thread::spawnparse; the startup banner,/health, and/graph/statussurfaceindexing → 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 prompt —
AgentContext.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 Structurewhen the graph is ready (falls back to the dir-tree when disabled). - TUI context —
ContextBuilder::with_relevant_symbolsseeds## Relevant Symbolsfrom 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 returningserde_json::Value(no kodegraph-type coupling); two curated/watch/graph/*routes (statuscompact{status,n,m},querycapped to ≤5 nodes) for the watch form factor. - 7-client fan-out — Tauri commands (
graph_*incommands.rs+lib.rs), Agent SDK (agent.graph.*), VS Code (apiClient.graph*), FlutterApiClient, Watch SwiftWatchNetworkManager, Wear KotlinWearNetworkManager. /semindexrewired ontograph_index(build/query/node/callers/callees/hierarchy/stats); the supersededsemantic_index.rs(regex/manual parser) anddep_visualizer.rswere deleted (their module decls removed fromlib.rs/main.rs).semantic_mcp.rs’s ownSemanticIndexServeris unrelated and untouched.- PreToolUse hook — a non-blocking
Glob|Grepnudge in.claude/settings.jsonthat 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,lsptier throughvibe-lsp,ast_edit.rsparser delegation, andsemantic_mcp.rsreuse ofkodegraph::mcp.
- Background build on daemon startup — non-blocking
- 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, defaulthigh) invibe-ai/provider.rs, mapped per provider: Claude/Gemini extended-thinking budget, OpenAIreasoning_effort(clamped to “high”). Wired throughProviderConfig.effort,cost_router::route_task_with_effort, the daemon/agentpath (serve.rs, via the VX-111reasoninglabel), theai_chat_with_effortTauri command,vibeaichatstart_agent_session, and a VibeCoder toolbar selector (utils/effort.ts). Fix: Gemini Pro models can’t disable thinking —Effort::Lownow clamps Pro to the 128-token minimum instead of emitting an API-rejectedthinkingBudget: 0. BDD:effort_bdd(5 scenarios). - C1 ·
/loop— recurring (/loop 5m <prompt>) and self-paced (/loop auto <prompt>, loop-until-done) REPL command with aMAX_ITERguard (20), wall-clock auto-expiry, job IDs, Ctrl-C stop, an LLM done-validator, andlist/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|cancelregistry with aWorking→…→Completed/Failed/Cancelledstate machine, plus stateless_meta(RequestMeta) and astatelessflag onStreamableHttpConfig(mcp_tasks.rs). BDD:mcp_tasks_bdd(5 scenarios). - C6 · ACP + MCP Registry self-listing —
vibecli --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. Tauridesign_emit_diff(design_diff.rs). - Opt-in security review — default-OFF watcher that emits standard
self_review::Findingrecords (no auto-apply). Taurisecurity_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.tswith updated defaults; Fable 5 / Mythos 5 omitted (export-suspended 2026-06-12). - Follow-ups still open: UI panels (
/loopAutomations, Design-Mode wiring, SecurityPanel), daemon/live-transport loops (B3 always-on watcher, C3 live MCP dispatch, C4browser_agentCDP), C5 streaming-path propagation, and C2 (dynamic large-scale workflow primitive).
- C5 · Per-request effort knob — provider-agnostic
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, reportedPartial, and left the user to press Resume to finish work that was going fine.AgentLoopnow grants up tomax_step_extensions(default 3) further budgets ofmax_stepseach — a ceiling ofmax_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 readProgress(notStalled/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
stepat the top of its body rather than the bottom. The body has manycontinues, and a bottom increment would skip every one of them and spin forever. The post-loopPartialreporting now quotes the effective budget rather thanmax_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 run —
POST /agenttakes an optionalmax_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 == 0is 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.
- An extension is earned, not automatic. The decision is a pure
Fixed
-
A chat history containing one huge message wedged the run permanently.
prune_messagesonly ever dropped the middle of the conversation. When the oversize lived in a message it must preserve — the system prompt, or aread_file/bashresult 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_tokenscharges 8 tokens of framing per message, so a budget under8 × lencannot be met by clipping, and the helper degrades rather than spinning (pinned by a test). - Subprocess dispatch had the original
/agentbug, untouched. The child loop inmain.rsmatched fourAgentEventvariants and swallowed the rest with_ => continue— so a dispatched run that stopped mid-plan drained the channel, leftcompletedfalse, and the fallback sentDispatchFrame::Complete { "Agent finished." }, which the parent records asJobStatus::Complete. Byte-for-byte the defect fixed inserve.rson the first pass, in a code path never opened.ToolCallPendingwas dropped there too, silently abandoning the run. Found by verifying a claim (“the remaining catch-alls are already audited”) instead of restating it — twoAgentEventmatches inmain.rshad never been read.DispatchFramegains aPartialvariant, sinceCompleteis defined by this protocol as success andErroroverstates a resumable run. The parent publishes the richpartialevent to SSE and marksJobStatus::Partial. Safe to add a variant: the child is this same binary, self-spawned viacurrent_exe(), so parent and child can never be different versions.- A second
main.rsloop (console output) also droppedPartial, 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).HashMapiteration order is randomly seeded per process andmax_by_keyreturns the last maximum, so which label won a tie varied run to run and the same graph settled differently —detects_a_community_from_a_clusterfailed roughly one run in ten. Two further orderings had the same flaw: communities were grouped out of aHashMapand 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 runningcargo test --workspacerather than--lib; unrelated to the rest of this changelog entry, and pre-existing. -
The CLI’s
spawn_agenthad the same unfinished-subtask bug as VibeCoder’s, verbatim.tool_executor.rsandvibecoder/src-tauri/agent_executor.rscarry 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
partialorcancelledsession as finished.watch_sync_service.darttestedstatus == 'complete' || status == 'failed', an allow-list of two — socancelled(pre-existing) andpartial(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.onFailurewith a null throwable when the failure is an unsuccessful HTTP response, and bothopenStreamandopenTaintedPendingStreamdidif (t != null) onError(t) else onComplete(). So a 401 or 500 ran the completion path:ConversationScreencleared 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
stepwith nosuccessfield rendered as “ok”. Introduced by this changelog’s own parser rewrite, and caught by the follow-up sweep rather than by review.AgentEvent.Step.successis nowBoolean?and rendersoutcome not reportedwhen the daemon said nothing. -
An unverified observe-act step erased the failure streak, so a failing loop never bailed out.
ObserveActSession::record_stepcomputed success asverification_result.map(|v| v.success).unwrap_or(true)— the comment said// No verification = assume success— and a “success” resetsconsecutive_failuresto 0. With unverified steps interleaved (the normal case whenverify_after_actionis off, or verification itself fails), the streak could never reachmax_consecutive_failures, so a loop that was failing every step ran the entiremax_stepsbudget 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_endwith nosuccessfield showed a green tick on the wrist.to_watch_event_jsondefaulted the missing field totrueand reportedstatus: "ok".statusisOption<String>precisely so absence can stay absent; it is nowNonewhen the producer said nothing. -
A failed watch/phone reply was recorded in the database as a completed one. The
/watch/dispatchstreaming task matched chunks withif 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 emitserror, storesfailedwith 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 intopersist_watch_turnand pinned by three tests (failed-with-partial, clean, failed-before-any-output). -
/chatand/chat/streamignored 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, andPOST /agentalready did. The chat routes did not:ChatRequest.modelrode the wire but was marked#[allow(dead_code)]and never read, and there was noproviderfield 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 sharedchat_provider_for, which honoursprovider+modeltogether (matching/agent), wraps the result inresilient()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/streamreported a failed reply as a finished one. A mid-stream provider error emitted anerrorevent and then carried on, so the trailingdonestill fired: clients sawerrorfollowed bydoneand rendered a truncated reply as complete.erroris now terminal. - A dropped connection reported a still-running task as finished. VibeDesk settled a stream that closed while
runningstraight todone, 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 toreviewing.- The daemon is authoritative and already had the pieces:
GET /jobs/:idfor 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
cancelledbefore any of this.
- The daemon is authoritative and already had the pieces:
- Choosing a model in the toolbar silently disabled retry. The daemon’s default provider is wrapped in
ResilientProviderbymain.rs::create_provider, butbuild_provider_override_with_effort— used whenever a request carriesprovider+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 newresilient()helper.- The builder deliberately still returns raw:
AgentLoopretriesstream_chatitself, 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 thatresilient()is identity-preserving (name()delegates), so wrapping never changes which model a caller believes it is talking to.
- The builder deliberately still returns raw:
- Unfinished agent runs were reported to every remote client as successes.
AgentEventhas 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 publishedcomplete("Agent finished.")and marked the jobComplete. 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.ToolCallPendingwas swallowed the same way, which killed the run outright. Droppingresult_txwithout answering madeAgentLoop::runhitErr(_) => 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 passingapproval: "suggest"/"autoedit", which the route accepts.- Harness — that silent
return Ok(())now emitsAgentEvent::Errornaming the tool and the dropped channel. A newassert_terminal_eventtest pins the contract across five paths: every run ends with exactly one ofComplete/Partial/Error. Callers that infer success from “noErrorseen” are now safe by construction. - Wire protocol — two new SSE kinds.
partialis terminal and carriessteps_completed/steps_planned/remaining_plan;retryis non-terminal and carriesattempt/max_attempts/backoff_ms(previously a retrying agent was indistinguishable from a hung one for up to 60 s per attempt).systemnow also carries circuit-breaker and verifier notices. NewAgentEventPayloadfields areOption+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 neitherCompletenorFailed: the work done is real and the run is resumable. Folding it intoCompleteis what let unfinished tasks read as successes.- Client fan-out — Agent SDK + VS Code (
isTerminalAgentEvent, replacing four hand-rolledcomplete || errorchecks), VibeDesk (stream_agentforwardspartial/retry;RunStategainspartial, 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), VibeCoderBackgroundJobsPanel(closes theEventSourceonpartialinstead 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
parseEventlooked forthinking/text/tool_call/tool_resultand readsummary/message/text: kinds and fields the daemon has never emitted. Every streamed token fell through tonullandcompleterendered an empty summary. Rebuilt against the realAgentEventPayloadshape, withStep/System/Retry/Partialadded. - Sub-agents —
spawn_agent(vibecoder/src-tauri/agent_executor.rs) droppedPartialinto_ => {}, leavingsummaryempty, which its tail rendered as “Sub-agent completed.” to the parent agent. A sub-agent that ran out of steps mid-plan now returnsToolResult::errwith what it finished and what it did not, so the parent re-dispatches instead of building on unfinished work. - Docs:
docs/api-reference.mdgains a terminal/non-terminal event table, the partial-run example, and thepartialjob status.
- VibeDesk · Sandbox mode with per-axis permissions. The agent is normally jailed to
workspace_root—ToolExecutor::resolve_safecanonicalizes 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. Newsandbox_policy.rs(read_outside·write_outside·exec_outside·network, plusallow_roots/deny_roots), asandboxfield onPOST /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 anallow_rootpointing straight at~/.ssh— reaches a key. Pinned bycredentials_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_rootsis checked beforeallow_roots.effective_sandbox_policy()is extracted and tested precisely so asandboxfield 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_FILENAMESincludesconfig.json, an ordinary project file. - Sub-agents inherit the policy.
spawn_agentbuilds a child executor field by field; omittingsandbox_policythere 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_outsideenforces something real.bashis 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 drivesToolExecutor’s existing OS-level confinement (bwrap+Landlock / sandbox-exec / AppContainer): off confines commands, on runs them as they do today. An earlier draft shipped aallows_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.
- Credentials are unreachable by construction.
- VibeDesk · Chat mode, thinking off by default, and a resizable Environment rail.
- Run mode (Agent · Chat). New
modefield onPOST /agentwith aRunModeparser (unknown values fall back toAgent, so older clients are untouched), and a chat path inserve.rsthat 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 composerModePill, persisted with the other run controls. - Live-verified. “In one sentence, what is the Mandelbrot set?” in Chat mode against
minimax-m3:cloudreturns 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), sostrip_thinkingcorrectly 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 usesstrip_thinking, because reasoning quotes calls the model then rejected.- Thinking is off by default.
ReasoningEffortgains anofftier, now the composer default. Off is sent as the absence of thereasoningfield, not a value: the daemon maps an unknown effort to no budget but still publishes aReasoning effort: …system line for whatever it receives, which would have announced a tier the user had turned off. One sharedeffortParam()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
useLayoutPrefsfollowinguseTheme’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. - CI —
vibedesk-checks(added with the release job) covers all of this;npm run test:thinkingis a new script with 11 cases.
- Run mode (Agent · Chat). New
</mm:think>rendered verbatim in chat, mid-sentence. minimax-m3 emits a namespaced reasoning tag, and all three strippers matched onlythink|thinking— so<mm:think>sailed through every one. Fixed invibe_ai::tools(strip_thinking/unwrap_thinking),vibedesk/src/lib/thinking.tsandvibecoder/src/components/AIChat.tsxwith 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
thinkingfield, 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 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
- The tool parser now accepts three dialects, not one.
parse_tool_callsunderstood 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 inAVAILABLE_TOOL_NAMESare considered, so<div>,<p>and prose mentioning a tool are never mistaken for calls. Common aliases (file_path/file→path,cmd→command) are normalised, and element bodies map to each tool’s natural parameter (write_file→content,bash→command, …). - Element matching uses one regex per tool name rather than an alternation with a backreference — Rust’s
regexcrate 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).
- Element style —
- Native tool-calling models produced an empty turn and a stuck run.
OllamaChatRequestcarriedmodel,messages,streamandoptions— but nevertools. Tools existed only as prose inTOOL_SYSTEM_PROMPT, which works for models that follow prompt instructions and not at all for one trained to call a tool API:minimax-m3:cloudreasoned “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 inreviewing. The response side had always transcribed nativetool_callsback 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/chatcall sites now send them./api/generate(prompt-completion, not the agent path) is untouched. - Gated, not unconditional —
tools::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_callturns each JSON key into an XML tag, so a renamed parameter would silently turn every native call into an unparsed block.tool_definitions_match_parserbuilds a call from each schema’s required params and asserts it round-trips throughparse_tool_calls. 5 new tests; 1181vibe-aitests 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 againstminimax-m3:cloudand 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 open —
openai_compat.rshas the same gap, so every OpenAI-compatible endpoint is prompt-only today.tool_definitions()is provider-neutral and ready for it.
- Fix — new
- 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 withstrip_thinkingbefore treating the text as an answer — butSessionStream.tsxpiped 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.tssplits reasoning from the answer, andReasoningBlock.tsxrenders 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 vianpm run test:thinking. - VibeCoder was not affected —
AIChat.tsx:extractThinkingalready handled this; VibeDesk simply never got the equivalent.
- Fix — new
ProfileStoresplit intocrates/vibe-profile-store. Reading one API key used to mean linking the whole CLI: the store lived insidevibecli, so anything touching settings pulledmistralrsandcandlewith it.vibeaichat/src-tauri/src/commands.rsdocuments avoidingvibeclifor 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 (zerocrate::references), so the lift was clean.vibeclire-exports it asvibecli_cli::profile_store, leaving all ~30 call sites unchanged —serve.rs,config.rs,watch_auth.rs, VibeCoder’scommands.rsand the rest.vibe-desktop-settingsnow depends on the store alone, so VibeAIChat’s tree carries novibecli,mistralrsorcandle(396 crates). VibeDesk still linksvibecli— 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
vibecliwent 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.
- Rename —
vibeapp/→vibeaichat/(git mv, history preserved), cratevibeapp/vibeapp_lib→vibeaichat/vibeaichat_lib, npm package,productName: VibeAIChat,identifier: com.vibecody.vibeaichat, window title, workspace member, Makefile targets (app→aichat,build-app→build-aichat, …), thebuild-vibeaichatrelease job,vibeaichat-checksin 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~/.vibecliand are unaffected. packages/vibe-ui-shared— the settings screens (Providers · Appearance · Account), theme definitions,useTheme,useProviderSettingsand the reasoning parser now exist once, consumed as source via a@vibe/sharedalias. No build step, nodist, 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.)SettingsViewgainedextraTabsso 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 bothvite.config.tsandtsconfig.json— which also keeps one React instance in the bundle. The Vite aliases use regexfind: a plain string alias is a prefix match, so"react"would rewrite"react-dom"into"<path-to-react>-dom". Andresolve()strips a trailing slash, so sub-path replacements re-append it orreact/jsx-runtimeconcatenates intoreactjsx-runtime. Intsconfig,@typesmust precede the JS package inpathsor every import lands asany. - Verified —
tscandvite buildclean in all three shells,cargo check/fmtclean, the 4 settings-migration tests moved with the crate and pass, bothtest:thinkingsuites 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).
- Rename —
- VibeAIChat answered “hi” with pages of the model arguing with itself. Two stacked defects, both visible in one screenshot.
start_agent_sessionhad nomodeparameter 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 againstminimax-m3:cloud: the samehinow returns 37 characters (“Hi there! 👋 How can I help you today?”) with no thinking tags and no tool-rule argument. vibeaichat/src/lib/thinking.tsstrips 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 vianpm 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.
- VibeAIChat is an assistant, not a task runner, so its UI now sends
- 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:
/healthshared the public routes’ 10-req/min per-IP bucket, and three desktop apps plusdaemon_bootstrap::probe’s 250ms startup poll all arrive from127.0.0.1— so the bucket was exhausted in seconds and never recovered. A throttled/healthreturns{"error":"Rate limit exceeded"}, which carries noservicefield, soprobe()read a healthy daemon as a foreign process; clients then spawned replacement daemons, and each replacement wrote~/.vibecli/daemon.tokenbefore 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./healthis 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
bindonly opens the socket and nothing is answered untilaxum::serveaccepts, 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/healthprobes 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,/healthpublic and self-identifying). Verified to fail without the fix: reverting the/healthlimiter made it fail atwait_ready—probe()could not identify the daemon at all within 90s, the production symptom exactly. HOMEis isolated in both daemon integration suites. The token path is shared across daemons regardless of port, so a test inheriting the realHOMEoverwrites the developer’s own daemon token and breaks every client on the machine — the very failure under test.daemon_bootstrap_integration.rshad 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.applaunched 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 “runvibecli --serve --port 7878in a terminal to see why” — where it does not reproduce. - Fix —
daemon_bootstrap.rsgainsspawn_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) andspawn_output()(capture stdout/stderr to~/.vibecli/daemon-spawn.log, truncated per spawn).DaemonState::TimedOutnow 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.rshad its owntokio::process::Commandspawn — precisely the second copydaemon_bootstrap.rsexists to prevent, and the one that carried this bug. It now callsboot::spawn_working_dir()/boot::spawn_output(), as does the sharedspawn_detached. - Tests — 3 new
daemon_bootstrapunit tests (unwritable candidate skipped, caller’s cwd preferred, candidate list ordered), following the existingfind_binary_inpattern of taking candidates as an argument instead of mutating process-global cwd. Verified end-to-end with the same binary under a Finder-likeenv -i: cwd/exits 1, cwd$HOMEcomes up listening.
- The failure was undiagnosable by construction. Both spawn sites set
- 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-analyzernow returns 165 completions fortext.on text that exists only in the editor, andclangdreturns exactly the struct’s fields.- The message pump deadlocked the connection. The client forwarded every inbound message into a 32-slot
mpscthat only an in-flight request drained.rust-analyzeremits thousands of$/progressnotifications 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.rsis 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), storespublishDiagnostics, 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 sendContent-Typeused to desynchronise the stream permanently). - Every document URI was one no server had heard of.
<Editor>had nopathprop, so all files shared a single model atinmemory://model/1, whiledidOpenannouncedfile://…. Each file now gets its own model at a real file URI, produced by one encoder on each side (fileUri/path_to_uri) — the oldformat!("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_changehad 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 adidOpen. - Completion kinds and snippets were mistranslated. LSP and Monaco both call the enum
CompletionItemKindand agree on no value (LSPTextis 1; Monaco’s is 18,Methodis 0), so every suggestion carried a wrong icon; mapping is now by name, since Monaco renumbers (Snippetis 28 in 0.55, was 27).insertTextFormat: 2now setsInsertAsSnippetinstead of typing${1:arg}literally. Auto-import edits, commit characters,sortText/filterText/preselect, deprecation tags,labelDetailsandcompletionItem/resolvedocumentation are all carried through. - No trigger characters were registered, so
foo.andVec::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 withdidSave,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 — sorust-analyzerwasNo such file or directoryin the bundled.appeven when it worked incargo run. Newdiscovery.rsresolves servers across the standard install dirs and hands the spawned server the same augmentedPATH(servers shell out: rust-analyzer → cargo, tsserver → node). Availability is a directory scan, not ~60whichsubprocesses. - A missing server no longer costs a spawn per keystroke. The manager remembers a failed start and fails fast until
lsp_restart_languageclears it (the action to take after installing a server — no app restart). Clients are handed out asArc, so the manager lock is released before awaiting: one coldrust-analyzerno 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, becauseonMountcapturedactiveFilePathonce; 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.tsranks,manager.rsserver configs,lib/lsp.tsextension 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 formatlab-language-server,asm-lsp,ada_language_server,superbol-free(COBOL),sas-lsp,abaplsp,powershell-editor-services,bash-language-server, the Nomic Solidity server, andclangdfor Objective-C (a first-class clangd language, not an approximation); PL/SQL and T-SQL ride on genericsqlsunder their own language ids, with install hints that say so rather than implying dialect awareness. Scratch (a block language whose.sb3is 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 usedcobolas 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, Gleamgleam lsp, CUDA viaclangd; Zig/Nim/Crystal/V/D/Vala were already wired), the component frameworks (Sveltesvelteserver, Vuevue-language-server, Astroastro-ls), functional newcomers (Elm, PureScript, ReScript), infrastructure (Terraform/HCLterraform-ls, Nixnil, CMake, Protobufprotols), shaders and hardware description (GLSLglsl_analyzer, WGSLwgsl-analyzer, SystemVerilogsvls, VHDLvhdl_ls), plus LaTeXtexlaband 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 caughtcfml(configured, no.cfm/.cfcroute, so unreachable) anddlang(an unreachable duplicate ofd, now removed).- The built-in-service check had to move from the Monaco language to the LSP language.
.vue,.svelteand.astroall highlight ashtml, andhtmlis 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.LspStatuslost its now-redundantmonacoLanguageprop as a result. - Highlighting followed routing. Files whose Monaco language id was never registered (
matlab,asm,cobol,sas, and nowodin,gleam,nix,elm,purescript,rescript,astro,cmake,vhdl,latex) got neither highlighting nor providers, because Monaco rejects an unknown language id.detectLanguagenow returns registered ids — includingcmakeandlatex, which previously resolved toplaintextand 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:
.vstays V (not Verilog — SystemVerilog uses.sv/.svh), alongside the existing.pl→ Perl and.m→ MATLAB decisions.
- The built-in-service check had to move from the Monaco language to the LSP language.
- 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 declarativeLanguageSpec(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
detectLanguagecan return must be registered (Monaco rejects an unknown id, somatlab/cobol/sas/cmakefiles 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’sA'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 theObj'Lengthattribute 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-1C, Prolog’s variable-versus-atom distinction, and case-insensitive keywords in COBOL/Ada/Fortran/VHDL/SAS/CMake.
- 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
- A missing language server is now two clicks, not a copy-paste hunt. The status bar offers a Copy install button beside the warning;
parseInstallHintsplits 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 inmanager.rsand rejects any command that could not be pasted as-is — that is howcargo install --git, truncated at a URL that was actually an argument, was caught.lsp_language_supportnow returns the rawinstallHintalongside the human-readabledetail, 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-lspunit 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 realrust-analyzer/clangd; 102 frontend tests for the bridge and status indicator, with enums imported from Monaco’s ownstandaloneEnums.jsso a renumbering can’t slip through.cargo check --workspace --exclude vibe-collab,tsc --noEmit, 362vibe-coderand 1353 frontend tests clean.
- The message pump deadlocked the connection. The client forwarded every inbound message into a 32-slot
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 trackvibecoder/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-hdpionly, with nomipmap-anydpi-v26, so modern Wear OS could not mask it to the device icon shape; and VibeAIChat/VibeDesk’sindex.htmlpointed at/vite.svg, a file that existed in neither app (a 404 favicon). All three now carry the real mark, plusandroid:roundIconin both Android manifests. - Pipeline —
make iconsregenerates all 143 artefacts fromscripts/brand/brandkit.py;make icons-checkfails 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.pyis a dependency-free PNG/ICO codec (no Pillow, no ImageMagick) covering the two jobsrsvg-convertcannot 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 inassets/brand/README.md.
- 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
- VibeDesk is now a released artifact. It had shipped in no release:
release.ymlbuilt VibeCoder and VibeAIChat only,ci.ymldid not check it at all, andvibedesk/src-tauri/tauri.conf.jsondeclared neithersigningIdentitynorentitlements— so the third desktop shell was buildable locally and invisible everywhere else.- Release — new
build-vibedeskjob mirroringbuild-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::whenAPPLE_CERT_P12_BASE64is absent, and the same artifact collection (.dmg/.deb/.AppImage/.msi/ NSIS.exe). Added torelease.needs[]and given its own downloads table in the release body. Deliberately not added to the release job’s criticalif:gate — the job has never run on Linux or Windows, and a first-run failure there must not block an entire release; promote it withneeds.build-vibedesk.result == 'success'once it ships green. - CI — new
vibedesk-checksjob (typecheck+ theno-inline-editVX-013 guard, the latter having existed as an npm script that nothing ran), wired intoci-gate’sneedsand its aggregate result. A release job for code CI never checks is a trap; both checks verified green locally before wiring. - Signing —
vibedesk/src-tauri/macos/entitlements.plistadded (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 fortauri-plugin-dialog), withsigningIdentity: "-"and theentitlementspath intauri.conf.jsonso VibeDesk matches the other two shells. - Cost note — VibeDesk’s
src-tauriembeds the wholevibeclicrate 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 thevibe-mistralrsfeature flags.
- Release — new
- 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 (theAPPLE_CERT_P12_BASE64secret is a CI-only import mechanism), exportAPPLE_SIGNING_IDENTITY/APPLE_TEAM_ID/APPLE_ID/APPLE_PASSWORD, and build. Documents thatAPPLE_SIGNING_IDENTITYoverrides the committed"signingIdentity": "-", so an ordinary dev build still needs no certificate; thatAPPLE_PASSWORDmust be an app-specific password; that notarization costs 2–15 min per build; and the threecodesign/stapler/spctlcommands that verify the result. Also records that VibeDesk is absent fromrelease.ymland declares nosigningIdentity— it is not currently released at all.error running bundle_dmg.sh— new troubleshooting entry, written against a reproduction.bundle_dmg.shdrives Finder over AppleScript and exits64when thatosascriptcall fails, but Tauri captures the script’s output and surfaces onlyfailed to run …/bundle_dmg.sh, so the reason is unrecoverable from the build log. Documents the fix that was actually verified —CI=true(notCI=1: Tauri bindsCIto its own--ciflag, which accepts onlytrue/falseand otherwise dies witherror: invalid value '1' for '--ci'), which passes--skip-jenkinsand 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.3to3.44.2(Dart 3.7 → ≥3.10) and thevibemobileDart SDK floor to^3.8.0, to supportflutter_lints6.0.0 (requires Dart^3.8.0) and the regenerated lockfile (resolved deps require Flutter ≥3.38.4 / Dart ≥3.10.3).FLUTTER_VERSIONupdated inci.ymlandrelease.yml; platform-requirements table indocs/vibemobile.mdupdated 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) asTuringWorks/mistral.rs@vibe/kv-cache-codec-kernels-v0.9;vibe-infer/Cargo.tomlnow pins5860f815. The previous branchvibe/kv-cache-codec-kernelsis untouched atf8f3a105, so the bump reverts with one line. Zero VibeCody source changes — every API we consume survived (with_isq(IsqType)now routes through the newIsqSettingviabuilder_macros.rsbut keeps its signature; sampler setters,From<TextMessages> for RequestBuilder, andmistralrs::coreunchanged).- 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-disconnectSendError#2170, engine-creation error propagation #2226, errored-sequence cleanup #2243);logprobscorrected 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-inferimplementsmistralrs::core::KvCacheCodec, whose methods takecandle_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-inferis the workspace’s only candle consumer;candle-transformershas notokenizersdep, sominilm.rsis unaffected. - Divergence shrank — the fork patch is 16 files instead of 17.
mistralrs-quant/build.rsdropped out entirely: #2288 replaced the hand-maintainedMETAL_SOURCESarray with ametal_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 existingkernels/*/*.cuglob. The#[allow(dead_code)]onMoEExperts::num_expertsalso dropped — it is a usedpubfield upstream now. RotatingCachehooks re-derived, not merged — upstream rewrote the cache into a relocating ring buffer (write_pos/window_start/retained_len). Invariant is now explicit:all_dataalways 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.9snapshot()/restore_from_snapshot()machinery, which our patch predated, needed the codec carried onRotatingCacheSnapshotand 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_encodebound its output buffer via a bare&Buffer→set_buffer(the input path) instead ofOutput::new→set_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 usedOutput::new. (2)turboquant.metaldeclared a file-scopeconstant uint SIMD_SIZE, private under per-file AOT compilation but colliding with MLX’sMLX_MTL_CONST int SIMD_SIZEonce #2288’s runtime path concatenates every source into one translation unit; sinceKernels::LIBRARYis a globalOnceLock, 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 toTQ_SIMD_SIZE. - Verification — 31/31 fork
kv_cachetests (including all of upstream’s own ring-buffer tests) and 25/25vibe-infertests withmistralrs,mistralrs-metal, on both the AOT and runtime-compile Metal paths;turboquant_encode_{float,half}symbols confirmed present in all three builtmistralrs_quantmetallibs;cargo check --workspace --exclude vibe-collabclean. 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-sandboxunconditionally (withlandlock/seccompileron Linux only).cargo tree -e featuresconfirms onlydefault,metal,mistralrs-paged-attnare enabled onmistralrs-core, so thecode-executionfeature is off and the Python code-execution tool is never exposed.cargo deny check(andcargo deny --all-features check, assecurity.ymlruns it) passes clean — advisories, bans, licenses, sources — withhttps://github.com/huggingface/candle.gitadded to theallow-gitlist, which the new[patch.crates-io]requires underunknown-git = "deny".deny.toml’sRUSTSEC-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 explicit —
turboquant/ops.rsdestructuredstorage_and_layout()as(storage, _)for the rotation/projection matrices and passed the kernel a base pointer, so a non-zerostart_offsetor non-contiguous view would have been read from element 0: wrong numbers, no error. Callers useTensor::from_vecso 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 sharedensure_matrix_layout, matching the checkinputalready had.
- What we gain — the scheduler / concurrent-serving overhaul (#2354) against
Fixed
- CI · the Metal GPU path was never executed anywhere. Every
ci.ymljob runs onubuntu-latest, wheremistralrs-metalcannot build, and the note claiming “the release workflow uses--all-featureson macOS runners” was false —--all-featuresappears only in that comment and insecurity.yml’s cargo-deny step, which compiles nothing.mistralrs-metalis compiled in macOS release builds, but via thecfg(target_os = "macos")dep invibecli/Cargo.toml, and no workflow ever ran its tests. Net effect: the TurboQuant GPU codec was shipped on the strength ofcargo checkalone, and both Metal bugs found during the v0.9 sync would have reached a release. Added ametal-gpu-testsjob (macos-latest) that runs thevibe-infersuite on both Metal paths — precompiled metallib andMISTRALRS_METAL_PRECOMPILE=0runtime compilation — because those exercise different code and each caught a different one of the two bugs. Wired into the requiredci-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 requirementsaccepts the requirements path positionally, not as-i FILE; the bad flag caused the tool to fall back to looking for./requirements.txtand emitCRITICAL | CDX > Could not open requirements file. Drop the-isovibe-rl-py.cdx.jsonis produced. Closes #28. - Mobile · iOS build (
b8d95e0f) —vibemobile/ios/Runner/AppDelegate.swiftreferencedFlutterImplicitEngineDelegateandFlutterImplicitEngineBridge, 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 reportedCannot find type 'FlutterImplicitEngineBridge' in scope. Rewrite to the Flutter 3.29-compatibleGeneratedPluginRegistrant.register(with: self)pattern and register the relay-credentials method channel synchronously indidFinishLaunchingWithOptions. Closes #29. - Watch · watchOS build (
014f5cce) —GoalsView.swift,JobPickerView.swift,RecapView.swift, andTaintedConfirmationView.swiftwere on disk and referenced byContentView.swift/SessionPickerView.swiftbut never added toVibeCodyWatch.xcodeproj’sPBXSourcesBuildPhase. The Swift compiler reported fourcannot find … in scopeerrors and the watchOS simulator app build exited 65. Register each as aPBXFileReference+PBXBuildFile, add to the group and sources phase (plutil -lintpasses). Closes #30. - Watch · Wear OS build (
6193920a) —JobRecapTileService.ktandGoalsTileService.ktimportandroidx.concurrent.futures.CallbackToFutureAdapterandcom.google.common.util.concurrent.{Futures, ListenableFuture}, andRecapScreen.ktusesandroidx.compose.ui.tooling.preview.Preview; none were declared as dependencies, so:app:compileReleaseKotlinfailed. Addguava(33.4.0-android),androidx-concurrent-futures(1.2.0), andandroidx-compose-ui-tooling-preview(1.7.6) tolibs.versions.tomlandimplementationthem inapp/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 becausevibe-memory/src/was never copied over the empty stub, leavingvibecliunable to findMemoryContextHub,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_*(notVibeCLI_*),VibeCody-Mobile-vX.Y.Z-{ios,android}.*,VibeCody-WatchOS-vX.Y.Z.app.zip,VibeCody-Wear-vX.Y.Z.*. Surface the newaarch64.AppImageandarm64.debartifacts 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: everyAgentLoop::newinserve.rs(/v1/agentstart, ACP submit-task, timed-task path) now callsplugin_runtime::merge_with_plugin_hooks(workspace, vec![])and attaches the resulting HookRunner beforeagent.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 eachHookComponentinto avibe_ai::HookConfigwith aCommandhandler 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 CLIHookRunner::newsites (run_parallel_agentsorchestrator +run_agent_repl_with_contextREPL). Best-effort withwarn!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 newplugin_rulesContextSection 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_NAMESgrows the new entry so/v1/capabilitiesadvertises the shape correctly. Daemon-sideAgentLoopsites inserve.rsintentionally 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 + JetBrains —
vscode-extension/src/hook-executor.tsimplements the same seven-event hook contract asvibecli-cli/src/hook_abort.rsand the JetBrainsHookExecutor:sh -c <command>(orcmd /con 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.UserPromptSubmitis 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.hooksjoins the configuration schema as anarray<{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 (
7709bc0b→080bf920) brings the JetBrains plugin to hook-protocol parity with CLI/Tauri.HookExecutorservice mirrorsvibecli-cli/src/hook_abort.rs: subprocess invocation viash -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 existingPersistentStateComponentinfra. Settings UI under IDE Settings → Tools → VibeCLI grows a hooks table with Add / Remove viaToolbarDecorator— Event column constrained to the seven allow-listed kinds matchingplugin_manifest::ALLOWED_HOOK_EVENTS. Both user-driven prompt-submission paths now run through the chain:AgentPanel.startAgentandInlineEditAction.actionPerformedfireUserPromptSubmitbefore 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 viacat), and the event-kind allow-list as a drift guard. Advisory firings for SSE-arrivingPreToolUse/PostToolUse/Stopdeferred 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.tsxrenders fencedmcp.appblocks 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 dispatchvibecoder:mcp-app-actionwindow events for the chat layer to consume. Newmcp_apps_parseTauri command bridges the daemon-side parser (mcp_apps_payload.rs, shipped earlier as647b58de) to the webview as defence in depth. Fence regex inAIChat.tsxrelaxed from\w*to[\w.+/@-]*so the full MIME-like tagapplication/vnd.mcp.app+jsonmatches without truncation. Malformed payloads fall back to a plainCodeBlockso 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-ups —
ConfigPortability::register_plugin_serversregisters MCP-server components from policy-active plugins under namespaced idsplugin:<plugin>:<component>, disjoint from the flat user-configured id space (16da6354).plugin_install::install_from_urladds HTTPS-only URL fetch (60 s timeout, 50 MB cap, scheme guard) sovibecli plugin install <https://…>works alongside the local-file path; newplugin_install_from_urlTauri command and aLocal file | HTTPS URLtoggle 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.tomlmanifest schema + validator (B2.1,cea41606); detached per-publisher P-256 ECDSA signing via siblingvibecli-plugin.sig(B2.2,6275cf06);WorkspaceStoreplugin_policiestable 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 viewplugin_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 — MCPlist_skills/get_skillnow 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 (reusesmcpb_bundle.rs), and per-publisher P-256 trust roots (reusessigned_agent_card.rsJWK). 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 anExecutionPlanon 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/goalsCRUD + 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/goalsfor mobile + Apple Watch + Wear OS (G1.6), and VS Code + Agent SDK + design docs (G1.7). Seedocs/design/goal/README.md. Tauri commands use theexec_goal_*prefix to avoid conflict with the existingCompanyGoalsPanelsurface./goal— hardening round (G4) —GET /v1/goals/:id/tree?depth=Nrecursive subtree walk (depth clamped 1..10, default 3, cycle-safe, truncation flag) andGET/PUT/DELETE /v1/goals/currentfor per-workspace “current pin” withpinned_goalscascading on goal delete. CLI gains/goal pin|unpin|currentsubcommands; aggregate/v1/goals/:id/recaphonors{ provider, model }for LLM synthesis with heuristic fallback (response carriesrecap_synthesizer). Apple Watch routes its “Start session” through the new curated/watch/goals/:id/startwrapper, and the VS Code extension grows avibecli.goalsViewtree-view in the sidebar with refresh + per-row context-menu actions./goal— fan-out round (G5) — Wear OSGoalDetailScreengains a “Start session” chip backed byWearNetworkManager.startGoal()(curated/watch/goals/:id/start)./agentauto-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 SDKgoals.*namespace addstree(id, depth?),pin(id, ws?),unpin(ws?),current(ws?), andrecap(id, { provider, model }); the FlutterApiClientgains parallelgetGoalTree,getCurrentGoal,pinGoal,unpinGoal. The VibeCoderGoalPaneladds a tree-view toggle (indents children under parents) and an “Aggregate recap” section that routes throughselectedProvider+selectedModelfrom the toolbar (heuristic fallback when either is empty)./goal— pin UX round (G6) — Pin/unpin chips in VibeCoderGoalPaneland the mobile detail sheet, with a ★ marker on whichever goal is pinned in the list. The agent stream emits asystemevent (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_goalnow returns the linked(goal_id, title)so callers can wire attribution UI. End-to-end tests confirm the helper inserts agoal_linksrow through the realSessionStore::open_default()path (HOME-redirect pattern from G4.1)./goal— goal-aware agent context (G7) — When/agentauto-links a session to a pinned goal, the daemon now synthesizes a model-readable preamble from the goal’s title, statement, success criteria, andcurrent_planand injects it intoAgentContext.approved_plan(only when that field is empty — the Phase 7 S3context_requestpath is untouched).auto_link_to_pinned_goalreturns the fullGoalstruct 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’ssystemauto-link event with a distinct[goal]prefix in the agent output channel (0b64f6b4); the SDKAgentEventTypeis 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. Pollsexec_goal_currentevery 15 s for external pin changes (CLI / mobile) and also listens forvibecoder:pin-changedwindow 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/goalsaccepts?q=<text>for case-insensitive substring search across title + statement, AND-ed with the existing status / workspace / tag / limit filters. VibeCoderGoalPanelgains 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 throughexec_goal_update. 2 new daemon tests cover the q-filter; frontendnpx tsc --noEmitclean (b755926b)./goal— TUI tree + Watch ★ marker (G11) — TUI Goals screen gains atkey that toggles between flat list and tree layout: children indent under parents via a client-side BFS overparent_goal_id, mirroring VibeCoder’s tree mode. Title-bar advertises the new key and current view mode. The curated/watch/goalspayload gains apinned: boolfield (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/:idenvelope gains an envelope-levelpinned: 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/currentround-trip — the watch never hits/v1/*directly. Apple WatchGoalDetailViewand Wear OSGoalDetailScreenshow the ★ on the title; Wear OSGoalsTileServicenow 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:GoalTreeItemprefixes 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. NewVibeCLIClient.getPinnedGoalIds(workspace?)unions the daemon’s global pin with the workspace pin (union of two/v1/goals/currentcalls) 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 inpinned_goals(any workspace, including the global slot) renders with a yellow bold ★ in front of the title — same glyph the other surfaces use. Newpkey toggles the pin on the selected row, advertised in the screen’s title bar alongsidef,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.SessionStoregains two helpers:list_all_pinned_goal_ids()(oneSELECT DISTINCTper refresh) andlist_pin_workspaces_for_goal(id)(used by the unpin-everywhere walk).- TurboQuant-compressed OpenMemory index —
CompressedMemoryIndexreplaces 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/statsexposes index telemetry — response now includesembedding_dim,embedding_compression_ratio, andembedding_backend(always"turboquant"today; treat as opaque). Surfaced in the VibeCoder OpenMemory panel, theopenmemory_index_statsTauri command, and the MCPmemory_statstool.vibe-infercrate — pure-Rust local inference traits (Embedder,TextGenerator) with a stub backend by default and an opt-incandlefeature that loadssentence-transformers/all-MiniLM-L6-v2(384-dim, mean-pooled + L2-normalized) via candle 0.10 + hf-hub.candle-metaladds Apple GPU acceleration. Default workspace builds pull no ML deps.- Linux arm64 Tauri builds — VibeCoder and VibeCLI App now ship
.deb/.AppImageforaarch64Linux via the GitHub-hostedubuntu-22.04-armrunner (free for public repos). Matrix coverage now matches VibeCLI (which already had Linux arm64 viacross). - Ubuntu 24.04 forward-compat smoke job — new
smoke-linux-nextCI job runscargo check --releaseonvibecoder/src-tauriandvibeaichat/src-tauriagainst webkit2gtk-4.1 on Ubuntu 24.04.continue-on-error: trueand excluded fromrelease.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.jsonandvibeaichat/src-tauri/tauri.conf.jsonnow setbundle.macOS.minimumSystemVersion = "12.0"(was the Tauri 2 default of 10.13). Matches Apple’s current supported-OS cutoff.
Security
- Bump
rand0.8 → 0.9 acrossvibecli/vibecli-cli,vibecoder/src-tauri,vibecoder/crates/vibe-core, andvibecoder/crates/vibe-collabto pick up GHSA-cq8v-f236-94qc (low severity; unsound interaction betweenrand::rng()and customlogimplementations invoking RNG during reseed). Call sites updated to the 0.9 API (thread_rng→rng,.gen::<T>()→.random::<T>(),.gen_range(…)→.random_range(…)).p256 0.13SigningKey::random call-sites now usep256::elliptic_curve::rand_core::OsRngto 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). Newvibe-infer::mistral::MistralGeneratorimplementsTextGeneratoron top of themistralrs 0.8.1crate (PagedAttention, ISQ, LoRA, OpenAI-compat types, candle 0.10.x transitively). Feature-gated behindmistralrs(CPU) /mistralrs-cuda/mistralrs-metal/mistralrs-flash-attn; defaults unchanged so baseline builds stay fast. Smoke example atexamples/generate.rsexercises Qwen/Qwen2.5-0.5B-Instruct end-to-end. The dep is pinned to the TuringWorks fork (TuringWorks/mistral.rs@3d422fde, branchvibe/kv-cache-codec, upstream basev0.8.1) via git + rev so we can iterate on theKvCacheCodechook without waiting on upstream review. Explicit SHA pin keeps local / CI / release builds reproducible — bump therevinvibe-infer/Cargo.tomlas the fork advances. KvCacheCodectrait landed on the fork (Phase 3 follow-up).TuringWorks/mistral.rs@vibe/kv-cache-codecadds apub trait KvCacheCodec { fn encode/decode/name }inmistralrs-core::kv_cachewith aPassthroughCodecdefault, threaded throughSingleCache/RotatingCacheasOption<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 underlyingslice_set/narrowbuffers 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 atmistralrs-corecrate root so downstream crates can install codecs without plumbing through private modules.CandleTurboQuantCodecbridge invibe-infer::kv_cache_codec— candle-backed implementation of the fork’sKvCacheCodectrait that reuses the pure-RustKvCacheTurboQuantfrom the Phase 3 spike. Encode shuttles the tensor to host-f32, quantizes + reconstructs eachhead_dimvector via PolarQuant + QJL, and returns a same-shape same-dtype tensor; decode is identity (the reconstruction already happened in encode). Shape contract:head_dimis 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 intoSingleCacheproving the trait dispatch works. Gated behind themistralrsfeature so default builds stay thin.- Install-all codec fan-out + discovery helpers on the fork (Phase 3 wiring).
TuringWorks/mistral.rs@3d422fdeaddsKvCache::set_codec(fans out to both K and V sub-caches; no-op onShared),EitherCache::set_kv_cache_codec(codec) -> usize(walks every attention layer of aNormalorHybridcache, skipping recurrent layers, and returns the install count for logging), plus two async accessors onMistralRs/Model:set_kv_cache_codec(codec, model_id)— locks the pipeline behind the engine’sRebootStateand installs in one shot — andkv_head_dims(model_id)— returns(k_head_dim, v_head_dim)fromGeneralMetadata::model_metadataso 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::loadinstalls KV codec on demand (Phase 3 completion). NewKvCacheMode { Fp16, TurboQuant { seed, qjl_proj_dim } }+KvCacheMode::from_env()resolver readsVIBE_INFER_KV_CACHE/VIBE_INFER_KV_CACHE_SEED/VIBE_INFER_KV_CACHE_QJL_DIM.MistralGenerator::load/load_isqcallinstall_codec_after_load, which queries head-dim from the fork, builds aCandleTurboQuantCodec, and fans it out viaModel::set_kv_cache_codec. Layer count is logged atinfofor observability. MLA-style models (k_head_dim ≠ v_head_dim) warn and pick the larger; pipelines withoutModelConfigLikemetadata (speech / diffusion) error out loudly instead of silently booting with a broken codec.- InferencePanel KV-cache dropdown (Phase 3 UX). When
Backend = Mistral.rsis selected, a new “KV Cache” dropdown appears with optionsFP16 (default)andTurboQuant (experimental). PickingturboquantprependsVIBE_INFER_KV_CACHE=turboquantto the generatedcargo runinvocation 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::MistralRsis now a first-class variant alongside vLLM / TGI / Triton / llama.cpp / Ollama, markedis_in_process()so callers can skip sidecar provisioning.build_mistralrs_command()emits theVIBE_INFER_MODEL=… cargo run -p vibe-infer --features {mistralrs | -cuda | -metal | -flash-attn} --example generate …invocation;generate_k8s_inference_deploymentuses thevibecody/vibecli-daemon:latestimage 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. KvCacheBackendtrait +KvCacheMethodenum invibe-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.KvCacheReportis 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 amistralrs-quantcontribution.- 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 withcargo 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_agreedrops sharply, but that is a data-shape artifact (flat softmax has arbitrary argmax) rather than a codec fault; judge viability byattn_maewhich stays ~0.0002 even there. TheKvCacheMethod::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 amistralrs-quantkernel 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). NewSemanticScorerowns aTurboQuantIndexfromvibe-coreand a caller-suppliedEmbedFn; eachadd_segmentembeds + compresses the content, andContextStreamingEngine::queryprefers approximate cosine similarity over the old keyword-overlap scorer when a scorer is attached.refocus(query)rewritesrelevance_scoreacross every segment so theRelevanceScore/Hybrideviction strategies drop segments least relevant to the current task — a prerequisite for 10M–100M-token workflows that outgrow working memory. Optional construction viaContextStreamingEngine::with_semantic(config, scorer); engines built withnew(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/VibeCodyWearapps 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 artifacts —
VibeCodyWatch-watchOS.app.zip+VibeCodyWear-wearos.apk/.aabalongside existing binaries. - Makefile targets —
build-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 attachfailures 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
\nafter<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 sendsev["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/Token —
useCallbackdependencies 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;/aireviewREPL. - Architecture Spec Engine (
architecture_spec.rs, 108 tests) — TOGAF ADM, Zachman, C4 Model, ADRs, governance engine;/archspecREPL. - Policy Engine (
policy_engine.rs, 91 tests) — Cerbos-style RBAC/ABAC, 14 condition operators, derived roles, policy testing, YAML, audit trail;/policyREPL. - 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).
- Phase 23:
- 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 Detection —
ThemeTogglenow respectsprefers-color-schemeon 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
descriptionfield 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
TabbedPanelcomponent 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:
ResilientProviderwrapper — 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),
RetryableErrorevent, 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
indexfield instead of array position (circuits remained accessible after deletions). - Missing
gatesarray in circuit detail for pre-existing circuits (defaults to empty). - Quantum simulator returns tuple arrays matching frontend TypeScript types.
TabbedPaneldisplay:contents breaking child panel height.LazyPanelsprops aligned with refactoredcreateCompositepattern.
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 languagecommand — type# find large filesto generate shell commands via AI with explanation and confirmation.- Command corrections (thefuck-style) — 13 built-in rules for typos (
gtitogit), 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.txtandllms-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/vibecodytoTuringWorks/vibecodyacross 18 files (docs, GitHub Actions, package.json, config). - DeepSeek default model:
deepseek-codertodeepseek-chat(V3 current). - Gemini default model:
gemini-2.0-flashtogemini-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.txtfor AI-friendly project context.- Tutorial guides for getting started, provider configuration, and skill development.
Fixed
- DeepSeek default model updated from deprecated
deepseek-codertodeepseek-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 withexpect()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,/idpwith 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 withexpect()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.