VibeX

Contribution model

integrations maps package resources onto Host extension points. Each row has a stable id, a Host-known kind, and an in-package resource. Runtime may bind only declared rows.

Agent-side contributions (Skill, managed MCP, Hook) inject into conversations created or rebound after the plugin is enabled. UI and Provider contributions appear on enable and vanish atomically on disable.

Handler ids

Worker handler ids must match:

text
^[a-z][A-Za-z0-9]*(?:[.-][A-Za-z0-9]*)*$

The id starts with a lowercase letter. Every segment after . or - also starts with a lowercase letter. Valid: hello, surface.createSession, office-preview. Invalid: Hello, save.XML, task.1. JavaScript, Python, and Rust SDKs share this regex. CLI validation requires surface.createSession for an editable file-tab App surface and for structure surfaces.

Kinds

kind Role Author notes
content.skill Project contents/skills/... to compatible agents as a read-only native Skill entry resource must exist in-package
content.mcp Host-managed MCP started per Agent session See managedRuntime below
content.hook Hook resource Manifest validates; the Host has no Hook runtime yet and inspect warns
workflow.binding Expose contents/workflows/ to Composer and Automation resource is workflow JSON
file.opener Open by extension, fileNameSuffixes, or mediaTypes Exactly one previewProvider or exactly one editorSurface
artifact.preview Broker-managed preview process handler, optional runtime and process.argv
app.surface Full Trust App appEntrypoint is app; editable text uses slot: artifact.editor; detail panel uses plugin.detail.panel
app.command Command palette title, handler; icon from the controlled set
app.toolbar Toolbar Omit slot for toolbar.main; add-only
app.status Status bar text or handler; poll only with refreshSeconds; extras go to an overflow menu
app.composer.slash Composer slash command command defaults to id; prompt is inserted
app.timeline.card Timeline card handler is surface.createSession; the App fetches via bridge.invoke
app.settings.section Inline block on General settings handler is surface.createSession; config.json remains the config fact
app.settings.page Full settings sidebar page title, handler, optional remote
app.tab Top-level central tab Optional Module Federation remote
app.panel Workspace Dockview panel defaultPosition is left or center
app.kanban.view One view inside the Kanban tab The Kanban tab leaves the bar when every view is disabled
app.composer.action Pre-submit Composer action Requires handler or prompt
host.service Periodic Worker handler intervalSeconds minimum 5, default 30; at most eight per package
provider.model.importSource Model-provider import source Handler returns { providers } or an array; import writes presets only
provider.remote.provisioner Remote Host provisioner provisionKind is a Host-owned source tag such as ssh

Validation rejects missing, ambiguous, and wrong-slot references. Stable app.surface.slot values are plugin.detail.panel and artifact.editor. Timeline cards and settings sections are synthesized from app.timeline.card / app.settings.section. icon names come from @vibex/plugin-contract/catalog/icons.

Stable surface (init templates and the author Skill describe these): every kind in the table; content.hook validates on the manifest only. Templates: skill, mcp, hooks, file-tab, editor-tab, full, ts-worker, node-worker, python-worker, rust-worker, host-service, host-chrome, provider-import, panel, kanban-view. Official samples: vibex.host-chrome (six chrome slots), vibex.host-surface (tab / panel / kanban view / Composer action / settings page), vibex.provider-import (provider import), vibex.remote-ssh (remote provisioner).

Six chrome slots

vibex-plugin init x --template host-chrome writes all six; delete the ones you do not need.

kind Required What the Host reads from the return value
app.command title, handler Invoke
app.toolbar title, handler Invoke
app.status text or handler text and tooltip; refreshSeconds starts polling
app.composer.slash command (optional), prompt prompt is inserted into the draft
app.timeline.card label, handler, allowedMethods The App fetches via bridge.invoke
app.settings.section title, handler, allowedMethods Same

Toolbar and status bar are add-only: a plugin may add items and leaves built-in indicators in place. Extra status items beyond three go to an overflow menu. Chrome slots refresh through the activation generation.

Structure surfaces and Federation

app.tab, app.panel, app.kanban.view, and app.settings.page default to Module Federation remotes. remote declares name, entry (usually dist/remoteEntry.js), and module. init --template panel and kanban-view write a Vite Federation project. During vibex plugin run dev, Host loadRemote points at Vite; leaving dev falls back to dist/remoteEntry.js. High-frequency panels use the Federation track.

Structure mount(root, environment) receives workspaceId and invoke(handler, input). invoke reaches this plugin's Worker, which then calls host.call.

app.panel defaultPosition is left (left sidebar) or center (center-group tab). Panel IDs are namespaced as plugin:<pluginId>/<panelId>. After disable, the layout slot remains as a placeholder and restores on enable.

The Kanban tab aggregates every enabled app.kanban.view. Arrow keys switch views. When every view is disabled, the Kanban tab leaves the central bar.

Skill

resource points at an existing path under contents/. Official packages use a skill directory such as contents/skills/office-docx with SKILL.md inside. The CLI accepts that directory and also accepts a path to SKILL.md. The content index items[].path names a file:

json
{
  "path": "contents/skills/office-docx/SKILL.md",
  "kind": "skill",
  "title": "Word documents"
}

A user Skill of the same name stays. targets may restrict agents; the default is the all-compatible-agents binding intent. SKILL.md is written for the Agent: when to use it, steps, inputs and outputs, hard limits. An external CLI that is not declared as a Runtime requirement still imports; readiness marks the dependency unknown.

MCP

Managed MCP uses managedRuntime:

json
{
  "managedRuntime": {
    "source": "runtime/mcp-server.mjs",
    "entrypoint": "dist/mcp/server.mjs",
    "protocolRevision": "2026-07-28",
    "defaultBinding": "all-compatible-agents"
  }
}

protocolRevision must be 2026-07-28. entrypoint must exist in-package. source is optional; when present, build compiles it to entrypoint. You may also declare kind: "hostFamilyBinary" with binaryId so a Host-family binary supplies the process.

The Host injects connection context bound to the Workspace and parent Conversation. The plugin omits server addresses and credentials from storage. New MCP prefers protocol revision 2026-07-28 and negotiates a compatible version. contents/mcps/*.json may also describe MCP projected into agent-native config; native config remains the Agent Runtime authority.

Official product faces: session and delegation use binaryId: "vibex-mcp"; Workflow Creator uses binaryId: "vibex-workflow-mcp".

Provider seams

provider.model.importSource appears in Settings → Agent → Model Provider under Import. The handler returns { providers: [...] } or an array. Each item needs name and apiUrl; a missing apiKey is listed and cannot be checked. Import writes presets only. Binding uses provider.presets.bind and always shows a Host confirmation. See vibex.provider-import.

provider.remote.provisioner declares provisionKind (for example ssh), label, and handler. Connecting from the saved-Host list with only a profileId calls the matching provisioner. provisionKind is a Host-owned source tag. Uninstalling the plugin keeps saved Hosts by default. See vibex.remote-ssh.

Workflow and file surfaces

A Workflow reference carries identity and dependency evidence. Publish, debug, and run use Workflow Core on the Host. Those entries close when the Host is offline.

artifact.preview declares handler, optional runtime, process.argv, and a ready timeout. After idle timeout the preview process exits and the next open starts it again. The Worker opens a preview with environment.host.call("artifact.preview", "open", { artifactHandle, providerId }). artifactHandle is issued by the Host, lasts about 30 seconds, and is single-use. The current Host routes every artifact.preview operation to open; close is reclaimed when the lease expires or the generation is withdrawn.

Editable file-tab steps:

  1. file.opener fills one of extensions / fileNameSuffixes / mediaTypes and sets editorSurface to an App surface id.
  2. That app.surface uses slot: artifact.editor, appEntrypoint: app, handler: surface.createSession.
  3. The Worker registers surface.createSession.
  4. The App calls bridge.artifact.readText() and saves with writeText(content, expectedRevision).

The Host owns the canonical path. The App receives the file name, revision, and bridge.artifact. Writes use the expected revision and atomic replacement. An external edit yields a recoverable conflict, code artifact_revision_conflict.

init --template file-tab writes a .txt artifact.preview plus a plugin.detail.panel. init --template editor-tab writes the four steps above for an editable UTF-8 file tab.

Commands and background services

app.command and app.composer.slash keep Plugin/Command identity and coexist with agent-native commands by source. host.service fits periodic probes or background work. The handler must be registered on the Worker. Interval reads intervalSeconds, minimum 5 seconds, default 30; at most eight services tick per package. A tick is skipped while the previous call is still running.

Development workflow

Author path: init a template, declare integrations, implement Worker or App, wire the stdio entry, build / validate / test, bind a running Host, run dev, pack, submit to the marketplace.

Development, validation, linking, and diagnostics stay on the command line. Bind the Host with vibex plugin run server, then run vibex plugin run … from the plugin directory. Linking uses the local Host token from Desktop or npx vibex serve. Do not ask the operator for a token.

text
vibex plugin run server --http://127.0.0.1:17891 --token <token>
vibex-plugin init my-notes --publisher you --template full
vibex plugin run build
vibex-plugin validate
vibex plugin run test
vibex plugin add --dev .
vibex plugin run dev
vibex plugin run test --host
vibex plugin pack
npx vibex plugin publish

vibex-plugin toolchain prints Host version, CLI, contract, JS / Python / Rust SDK paths, and template names.

Templates

bash
vibex-plugin init my-notes --publisher you --template full

init writes the manifest, README, config.json, content index, tests, and matching source, then builds immediately. Default template: full. For the chosen template, build / validate / test must pass, and the declared integrations must activate on a real Host.

Template Output
skill Skill projection
mcp Managed MCP descriptor and placeholder process
hooks Hook resource
file-tab Node Worker, read-only .txt preview (artifact.preview), and a detail panel (slot: plugin.detail.panel)
editor-tab Editable UTF-8 file tab (file.opener.editorSurface + slot: artifact.editor)
full Node Worker, App detail panel, Workflow
ts-worker TypeScript Worker definition (runtime/main.ts)
node-worker JavaScript Worker definition (runtime/main.mjs)
python-worker CPython Worker (runtime/worker.py includes the stdio entry)
rust-worker native Worker source (runtime/src/main.rs includes the stdio entry)
host-service Periodic handler, default intervalSeconds 30
host-chrome One contribution per chrome slot
provider-import One provider.model.importSource
panel One app.panel with a Vite Module Federation remote
kanban-view One app.kanban.view with a Vite Module Federation remote

engines.vibex is >=0.1.3 <1.0.0. engines.pluginSdk is ^1.0.0. python-worker and rust-worker source already call run_stdio_plugin_worker / run_stdio_plugin_worker_blocking. vibex-plugin build does not compile Rust; the Rust template path points at the compiled binary, and the README states to run cargo build.

host-chrome writes app.command / app.toolbar / app.status / app.composer.slash / app.timeline.card / app.settings.section. panel and kanban-view write a Federation project; during run dev Host loadRemote points at Vite. Editable file-tab steps: Contribution model.

Node Worker entry

The Host starts a Node Worker with:

text
node --max-old-space-size=128 <entrypoints.worker.path>

That process must run protocol 1.1 on stdin/stdout. Recommended split:

text
runtime/worker.mjs   # definePluginWorker(...)
runtime/main.mjs     # runStdioPluginWorker(definition)

runtime/main.mjs:

js
import { runStdioPluginWorker } from '@vibex/plugin-sdk/stdio';
import definition from './worker.mjs';

await runStdioPluginWorker(definition);

vibex-plugin build bundles runtime/main.mjs into dist/worker.mjs. Manifest:

json
"entrypoints": {
  "worker": {
    "path": "dist/worker.mjs",
    "runtime": "node",
    "protocol": "1.1"
  }
}

Official Office uses this split. init --template node-worker writes the handler definition in runtime/main.mjs; call runStdioPluginWorker at the top level, or split like Office. Tests import the definition module so they skip the stdio loop; comparison data is exported from a module.

Author CLI and product CLI

vibex-plugin writes packages: init, build, validate, test, pack, toolchain, doctor. Run it from the plugin root.

Command Role
validate [--json] Validate manifest, index, references
build Validate; compile runtime/main.mjs to dist/worker.mjs; compile App, Federation remote, and managed MCP source
test build first, then run test/*.{test,spec}.{mjs,js,mts,ts} in a temp directory
pack [--output file.vxp] Write package.lock.json, emit a deterministic .vxp, print sha256:
doctor Install, activation, Runtime, surfaces, bindings, recent crashes
toolchain Print local SDK and template paths
run server Write Host URL and token to ~/.vibex/pluginrc
run dev Build, link, watch; start Vite HMR when a Federation remote exists
run test --host Against a running Host: install → enable → contributions appear → disable withdraws them → enable again → uninstall

npx vibex plugin operates the same Host catalog and the official marketplace. When desktop or npx vibex serve is running, the command finds the local Host token. Default URL is http://127.0.0.1:17891; override with VIBEX_URL / VIBEX_TOKEN. If no Host is up, snapshots go to ~/.vibex/imports/ and linked directories to ~/.vibex/imports/links.jsonl; the next launch imports them.

bash
npx vibex plugin add --dev .
npx vibex plugin add --dev . --detach
npx vibex plugin add --profile dist/notes.vxp
npx vibex plugin add --web https://github.com/<owner>/<repo>#v1.0.0
npx vibex plugin add --web https://vibex.xforever.xin/marketplace/<owner>/<name>
npx vibex plugin list
npx vibex plugin test --host
npx vibex plugin pack
npx vibex plugin publish
npx vibex plugin remove <plugin-id>

add --dev only links the directory and returns. HMR is started by vibex plugin run dev. vibex-plugin dev is an alias of run dev. vibex-plugin install --link, when still present, is the same Host import.

run dev builds, links, then watches. A digest change reloads the Worker (activation generation). With a Federation remote, Host loadRemote points at Vite so panel code hot-reloads without reloading the Host. On failure the previous complete generation stays visible. Neither path deletes the development directory.

Snapshot install, origin lock, update, and Update available: Install a Plugin from the marketplace. Marketplace submit: below and plugin.

Accept against a Host

  1. Edit the README summary and body.
  2. Declare only stable-surface integrations you actually implement.
  3. Handler ids match the declarations and the handler regex.
  4. For a Node package, confirm dist/worker.mjs contains the stdio loop.
  5. build, validate, test. Run npx vibex plugin test --host when you need the real install path.
  6. Open VibeX or npx vibex serve, run vibex plugin run server, then npx vibex plugin add --dev . and vibex plugin run dev.
  7. Enable the package in Settings → Plugins. Exercise the declared integrations: Skill projection, MCP injection, read-only preview, editable-file revision conflict, detail panel, chrome slots, structure surfaces, provider import. UI contributions appear on enable; the Agent tool list follows a new session.
  8. Inspect the crash ring with vibex-plugin doctor.
  9. Distribute with npx vibex plugin pack. Install locally with add --profile or by dropping a .vxp. Pin Git with #tag on --web. Publish with npx vibex plugin publish.

test --host against a chrome or structure package: install → enable → declared kinds appear in plugin_contribution_catalog → they vanish on disable → enable again → uninstall, source directory kept. A Skill-bearing package also reloads SKILL.md.

The harness covers in-process contracts. File tabs, preview processes, Runtimes, structure surfaces, and remote-workstation observation finish on a running Host. A passing validate proves the manifest is legal; whether App, Runtime, or MCP is available on the Host is decided by Host acceptance.

Submit to the marketplace

Register a marketplace account on the site. The CLI sends a packed result or a GitHub repository into the review queue:

bash
npx vibex plugin publish . --owner <marketplace-user> --password <password> [--show-tree]
npx vibex plugin publish --web <github-owner/repo[#tag]> --owner <marketplace-user> --password <password>

Credentials also come from VIBEX_MARKET_OWNER / VIBEX_MARKET_PASSWORD. Default submit URL is https://vibex.xforever.xin; override with VIBEX_MARKETPLACE_URL. The GitHub owner must match the marketplace username. Uploaded packages hide the tree by default; --show-tree shows it. GitHub listings show the tree by default.

After review, the listing appears in the public catalog and the Host Marketplace tab. One owner/plugin-name card keeps history and defaults to latest. A published GitHub repository appends a version when a new tag is pushed. Pending packages stay out of the Catalog.

Developing inside the VibeX repository

From the VibeX repository root, list contract files for the current checkout:

bash
python3 .agents/skills/vibex-plugin-development/scripts/locate_toolchain.py
node packages/plugin-cli/dist/cli.js toolchain

required includes the SDK, CLI validator, docs/plugins/package-v4.md, and docs/plugins/sdk-and-cli.md. Fill any missing path first. Python SDK lives in sdk/python. Rust SDK lives in crates/plugin-sdk. The author Skill follows that checkout.

Testing, security, and publishing

Test coverage

In-package tests cover the paths that apply:

  • Declarative package: summary, schema, index, integration references.
  • Worker: handler registration, invoke, typed error, dispose.
  • App: mount, ready, revoke, abort.
  • Editable file tab: readText, writeText with revision, external-edit conflict (artifact_revision_conflict).
  • Chrome / structure surfaces: declared kinds enter the contribution catalog on enable and leave on disable.
  • Generation: a candidate missing a required handler rolls back; after a successful switch, old handles are stale.
  • Negative fixtures: illegal summary, path escape, unknown integration, oversized document.
bash
vibex-plugin build
vibex-plugin validate
vibex-plugin test
npx vibex plugin add --dev .
vibex plugin run dev
npx vibex plugin test --host
npx vibex plugin pack
npx vibex plugin publish
vibex-plugin doctor .

npx vibex plugin test --host against a running Host walks link, enable, contribution appear/withdraw, Skill hot reload, and uninstall. User data is kept by default; the development directory is not deleted. add --dev only links. HMR is started by vibex plugin run dev.

test runs build first. Node tests are packed into a temp directory and handed to node --test; a test file that reads plugin-root files via import.meta.url fails. Export fixtures from the test module or test/*.mjs.

Python packages use the sdk/python harness and await create_worker_harness(...). Rust packages use cargo test -p vibex-plugin-sdk plus in-package tests. A template that ships test/plugin.test.mjs asserts the handler list with createWorkerHarness.

After a linked install, finish enable, Content, Config save, session injection, chrome slots appearing immediately, structure-surface mount, candidate reload after source edits, and uninstall (data kept by default, development directory left) on a running Host. A remote workstation window shows Host-side results. After a failed update, the UI and doctor still point at the previous complete generation.

Security

  • Deterministic pack: the same source yields the same digest. Activation binds that digest.
  • Candidate bytes are content-addressed. A failed validate, migration, or dependency readiness keeps the previous generation.
  • App and Runtime sessions bind a generation and are revoked on disable, replacement, expiry, or uninstall.
  • Release listeners, timers, child processes, MessagePorts, and temp files. onDispose has a deadline; the Host kills the process after timeout. Persist state during normal requests.
  • Editable files use bridge.artifact. The Host owns the canonical path. Saves carry the expected revision. Conflicts show both versions.
  • Pin or document every external editor, Runtime, executable, and network endpoint. State offline behavior and data flow in the README.
  • Validate messages from third-party frames before writing user data. Licenses and NOTICE live in the package.
  • Logs, traces, error details, events, and URLs omit secrets, pairing codes, and device tokens. Worker stderr stays in scoped diagnostics.
  • The permissions array is compatibility metadata. New packages omit per-capability grant simulation.
  • Plugin data is isolated by Publisher + Plugin ID. A derived package uses a new identity and does not read the original KV, settings, or secrets.

Full Trust network access uses the language runtime (Node fetch, Python fetch_url, and equivalents). The network.fetch Host RPC returns network_denied. Until a workspace root is bound to the Worker, files.read / files.write / files.stat / files.list return files_root_denied. Until a conversation is bound, conversation.read.get and conversation.append.enqueueInput return conversation_scope_denied.

Publish checklist

  • Import only the public SDK modules for the language: @vibex/plugin-sdk, /worker, /app, /testing, /protocol, /stdio; Python vibex-plugin; Rust vibex-plugin-sdk.
  • README: requirements, operation, offline and network, troubleshooting, licenses, config retention after uninstall.
  • A Node package's dist/worker.mjs contains the stdio loop; a native worker path points at a compiled binary.
  • The pack sha256 is reproducible.
  • Release notes state the activation boundary: UI and Provider contributions appear on enable and vanish on disable; Agent-side Skill / MCP arrive in later new or rebound conversations.

Publish with npx vibex plugin publish: upload a .vxp or list a GitHub repository into the review queue. The GitHub owner must match the marketplace username. Use --show-tree to show the package tree for an uploaded archive. After review, one card keeps history. Pin Git with #tag on the install command. A GitHub Release ships both .vxp and .sha256. Product identity is Publisher + ID; marketplace install identity is owner/plugin-name/tag/version.

SDK overview

The Plugin SDK is versioned with the Host. Worker protocol is 1.1. App protocol is 1.0. API version 1.0. Current matrix: Host 0.1.3, SDK 1.0.0.

Pick an implementation from the Worker runtime:

Runtime Language Package Source Template Host launch
node TypeScript @vibex/plugin-sdk packages/plugin-sdk ts-worker node --max-old-space-size=128 dist/worker.mjs
node JavaScript @vibex/plugin-sdk packages/plugin-sdk node-worker same
python Python vibex-plugin sdk/python python-worker Host-locked CPython 3.12.11 plus runtime/worker.py
native Rust vibex-plugin-sdk crates/plugin-sdk rust-worker spawn the binary at entrypoints.worker.path

All four share the stdio JSON-line protocol, handler rules, generation semantics, and Host capability names. App surfaces ship definePluginApp in the TypeScript / JavaScript SDK. Python and Rust SDKs cover Worker.

Language chapters:

bash
vibex-plugin toolchain

The command prints local cli, contract, js, python, rust paths and templates. In the VibeX checkout, use node packages/plugin-cli/dist/cli.js toolchain. See Development workflow.

Transport

Each Worker has one stdio connection, one JSON object per line. Frame limit 1 MiB. Default request timeout 30 seconds. Cancellation is an explicit notification. stderr is scoped diagnostics.

Handshake:

  1. Host sends initialize with protocolRange: ["1.1"], hostVersion, pluginIdentity, packageDigest, generationId, declaredContributions, packageClass, and limits (maxFrameBytes, requestTimeoutMs).
  2. Worker replies protocolVersion: "1.1", sdkVersion, registrations: [], requestedFeatures.
  3. Host sends activate with pluginId, pluginVersion, generation, packageClass, grantedCapabilities.
  4. Worker runs setup, freezes the handler table, and replies with the registered handler list.
  5. Later messages are invoke / ping / dispose. Worker-to-Host uses host.call with capability, operation, input.

protocolVersion must be 1.1; otherwise the Host returns worker_protocol_unsupported.

Registration

Setup only registers handlers, then freezes. Duplicate ids, undeclared ids, or missing required handlers fail candidate activation. Handles from an old generation become stale after a new generation publishes. Further calls return a stable error. The Host starts a Runtime when a contribution needs it.

Handler regex: Contribution model.

Host capabilities

environment.host.call(capability, operation, input?) is the only Host RPC. Current Host behavior (crates/plugins/src/host_capability_broker.rs):

capability.operation Result
runtime.execute plus any operation Spawn the locked Runtime, JSON on stdin, 120s timeout, 1 MiB input cap
artifact.preview plus any operation Open preview. input needs a Host-issued artifactHandle (~30s, single use) and providerId
artifact.readText / artifact.writeText artifact_not_found. Text I/O is on the artifact.editor App bridge
storage.kv.get / put / delete / list In-process map keyed by plugin ID; cleared when the process exits
storage.settings.get / put Read and write this plugin’s root config.json
log.debug / info / warn / error Empty object; scoped diagnostics
plugin.self.doctor { pluginId, generation, diagnostics, recentCrashes }
conversation.create Create and bind a Conversation. Omit workspaceId to use the plugin scratch workspace
conversation.list / conversation.get / conversation.read.get Only conversations this plugin owns or was granted
conversation.append.enqueueInput The same submit control plane
conversation.steer / conversation.cancel In-flight steer and cancel
conversation.cancelInput / conversation.listInputs Queued inputs
conversation.respondPermission / conversation.respondQuestion Permission and question replies
conversation.setMode / conversation.setConfigOption Session config
conversation.catalog / conversation.archive Available agents and archive
conversation.events.since Changed timeline rows
remote.profile.list / upsert / forget Client-saved Hosts. provisionKind is a source tag
remote.connect / disconnect Attach the app shell to a saved Host; call the provisioner by provisionKind
provider.presets.list / save / bind Provider presets; bind uses Host confirmation UI
secrets.get / put / delete { "present": false }
network.fetch network_denied. Full Trust uses the language runtime
files.read / write / stat / list files_root_denied
agent.invoke handler_not_visible
events.subscribe / ack Placeholder success
app.notify.toast Empty object
other capability_unimplemented

Structure surfaces (app.tab / app.panel / app.kanban.view / app.settings.page) receive Federation mount(root, environment) with workspaceId and invoke(handler, input). invoke reaches this plugin’s Worker.

Unbound conversation.* calls return conversation_scope_denied. Uninstall keeps saved Hosts; omit remote.profile.forget.

Execution class

v4 packages spawn Workers under Full Trust. packageClass=isolated does not change Host spawn. The Python managed interpreter is locked to CPython 3.12.11 (python-build-standalone). The Rust crate isolated feature omits filesystem and network helpers; the author API remains define_plugin_worker.

TypeScript SDK

Package @vibex/plugin-sdk, Node >=20. Template ts-worker writes the Worker definition to runtime/main.ts and re-exports it from runtime/main.mjs. Engine field pluginSdk is ^1.0.0. Templates full, file-tab, editor-tab, and host.service use the same npm package with .mjs sources.

Editable file tabs, detail panels, and host.service consume this package's Worker, App, and testing modules.

Modules

Export Role
@vibex/plugin-sdk / /protocol VIBEX_PLUGIN_API_VERSION ("1.0"), VIBEX_PLUGIN_PROTOCOL_VERSION ("1.1"), JSON and context types
/worker definePluginWorker, activatePluginWorker, PluginSdkError
/app definePluginApp, VibeXAppBridge
/stdio runStdioPluginWorker
/testing createWorkerHarness, createGenerationHarness, createAppHarness

The public SDK exports the modules above. Tauri commands, Axum routes, SQLite schema, and absolute Host paths stay in the Host. Full Trust Workers may use the Node standard library. Structured Runtime / Artifact lifecycle uses environment.host.call.

Worker

ts
import { definePluginWorker } from '@vibex/plugin-sdk/worker';

export default definePluginWorker((registrar, environment) => {
  registrar.handle('hello', async (input, env) => {
    env.log.info('hello', { input });
    return { ok: true };
  });
  registrar.onDispose({
    dispose() {
      environment.log.info('disposed');
    },
  });
});

registrar.handle(id, handler): id must match the handler regex and appear in the manifest. handler(input, environment) takes JSON and returns JSON or a Promise. Duplicate registration throws handler_duplicate.

registrar.onDispose(disposable) accepts { dispose() } or a function. Unload runs them in reverse.

environment:

Field Meaning
context.pluginId Plugin ID
context.pluginVersion Version
context.generation Current activation generation
context.packageClass Execution class; the current Host always spawns under Full Trust
context.grantedCapabilities Usually ["*"] under Full Trust
host.call(capability, operation, input?) Host RPC
signal AbortSignal; aborted on dispose
log.debug/info/warn/error(message, fields?) Structured log

activatePluginWorker(definition, environment) is for tests or self-hosting. A apiVersion other than "1.0" throws sdk_incompatible. Invoke after dispose throws worker_disposed. A missing handler throws handler_not_found.

stdio entry

The Host runs node --max-old-space-size=128 dist/worker.mjs. Split definition and entry:

ts
// runtime/worker.ts
export default definePluginWorker((registrar) => {
  registrar.handle('hello', async () => ({ ok: true }));
});
js
// runtime/main.mjs
import { runStdioPluginWorker } from '@vibex/plugin-sdk/stdio';
import definition from './worker.ts';

await runStdioPluginWorker(definition);

init --template ts-worker writes the definition in runtime/main.ts and export { default } from "./main.ts" in runtime/main.mjs. Add runStdioPluginWorker in main.mjs, or split as above. build emits runtime/main.mjs to dist/worker.mjs.

Tests import the definition module:

ts
import definition from '../runtime/worker.ts';

App

ts
import { definePluginApp } from '@vibex/plugin-sdk/app';

export default definePluginApp(({ bridge, root, signal }) => {
  const button = document.createElement('button');
  button.textContent = 'Refresh';
  button.addEventListener('click', () => {
    void bridge.invoke('dashboard.refresh', {});
  });
  root.replaceChildren(button);
  bridge.ready();
  const dispose = () => root.replaceChildren();
  signal.addEventListener('abort', dispose, { once: true });
  return dispose;
});

bridge: pluginId, generation, invoke(handler, input?), subscribe(channel, listener) (returns unsubscribe), ready(). An artifact.editor mount also has artifact.

bridge.artifact: name is the file name; readText() returns { name, content, revision }; writeText(content, expectedRevision) writes by revision. An external edit yields a recoverable conflict with code artifact_revision_conflict. The Host gives the App the file name, revision, and this bridge. Theme and locale arrive in Host bootstrap.

Editable file tab:

  1. file.opener declares extensions and editorSurface.
  2. **app.surface uses slot**: artifact.editor, appEntrypoint: "app", handler: "surface.createSession".
  3. The Worker registers that handler.
  4. The App calls readText(), keeps the revision, and passes it on save.

host-chrome mounts the same App on the timeline card and settings section. panel / kanban-view use Federation mount(root, environment); environment.invoke reaches this plugin’s Worker.

Testing

ts
import { createWorkerHarness, createGenerationHarness } from '@vibex/plugin-sdk/testing';

const worker = await createWorkerHarness(definition, {
  context: { pluginId: 'you.notes', pluginVersion: '0.1.0', generation: 1 },
});
await worker.invoke('hello', {});
// worker.hostCalls records host.call
await worker.dispose();

const gen = await createGenerationHarness(definition, {
  requiredHandlers: ['hello'],
});
await gen.activateCandidate(definition);
await gen.dispose();

createAppHarness(definition, { root, artifact }) simulates the bridge, subscriptions, revoke, and artifact revision conflicts. A missing required handler makes activateCandidate dispose the candidate and throw required_handler_missing.

Record "@vibex/plugin-sdk": "^1.0.0" in package.json. Until the SDK is on npm, use a file: path to the Host checkout or the Host-family sdk/. When developing VibeX itself, run pnpm --filter @vibex/plugin-sdk build first.

JavaScript SDK

JavaScript Workers share npm package @vibex/plugin-sdk, protocol 1.1, and the same handler rules as TypeScript. Template node-worker writes runtime/main.mjs and "type": "module" in package.json. Engine fields, digest, and generation match the TypeScript package.

Type imports may be omitted. Runtime is ESM. Node >=20.

Worker

js
import { definePluginWorker } from '@vibex/plugin-sdk/worker';

export default definePluginWorker((registrar, environment) => {
  registrar.handle('hello', async (input, env) => {
    env.log.info('hello', { input });
    return { message: 'Hello from VibeX', input };
  });
});

definePluginWorker, registrar.handle, onDispose, environment.host.call, log, and signal follow TypeScript SDK. Error codes match: handler_duplicate, handler_not_found, worker_disposed, sdk_incompatible.

stdio entry

The Host runs node --max-old-space-size=128 dist/worker.mjs. Recommended split:

js
// runtime/worker.mjs — handler definition
export default definePluginWorker((registrar) => {
  registrar.handle('hello', async (input) => ({ message: 'Hello from VibeX', input }));
});
js
// runtime/main.mjs — Host entry
import { runStdioPluginWorker } from '@vibex/plugin-sdk/stdio';
import definition from './worker.mjs';

await runStdioPluginWorker(definition);

vibex-plugin build emits runtime/main.mjs to dist/worker.mjs. Manifest:

json
"entrypoints": {
  "worker": {
    "path": "dist/worker.mjs",
    "runtime": "node",
    "protocol": "1.1"
  }
}

init --template node-worker writes the definition in runtime/main.mjs. Add runStdioPluginWorker there, or split into worker.mjs and main.mjs. Official Office uses the split.

Tests import runtime/worker.mjs. Importing dist/worker.mjs starts the stdio loop.

App

JavaScript Apps use the same definePluginApp. full, file-tab, and editor-tab templates emit runtime/app.mjs, app.html, and app.css. bridge.invoke reaches only Worker handlers registered in this generation. Call bridge.ready() after the first paint is mounted.

Without a TypeScript compiler, keep .mjs sources. For types, use ts-worker or add .d.ts in the same package; runtime remains Node ESM.

The file-tab template App mounts on plugin.detail.panel. The editor-tab template App mounts on artifact.editor. host-chrome mounts the same App on the timeline card and settings section. panel / kanban-view use Federation mount. Declaration steps: Contribution model.

Testing

js
import test from 'node:test';
import assert from 'node:assert/strict';
import definition from '../runtime/worker.mjs';
import { createWorkerHarness } from '@vibex/plugin-sdk/testing';

test('registers hello', async () => {
  const worker = await createWorkerHarness(definition);
  assert.deepEqual(worker.handlers, ['hello']);
  await assert.rejects(() => worker.invoke('undeclared', null), /not registered/);
  await worker.dispose();
});

init --template node-worker generates a test that imports ../dist/worker.mjs. After splitting sources, change the import to ../runtime/worker.mjs. vibex-plugin test runs build first. The host.service template also uses a JavaScript Worker plus intervalSeconds.

Using TypeScript in the same package

JavaScript templates fit a pure Worker with ESM sources. ts-worker writes the definition in runtime/main.ts. One product package may contain a .mjs Worker and a TypeScript App when build output paths match the manifest.

Python SDK

Package name vibex-plugin, source sdk/python. Author environments and the Host managed interpreter require CPython 3.12 or newer. The Host locks CPython 3.12.11 (python-build-standalone install_only). Template python-worker writes runtime/worker.py and pyproject.toml. entrypoints.worker.runtime is python, protocol 1.1.

The Host launches the locked CPython executable plus entrypoints.worker.path (default runtime/worker.py). That file must call run_stdio_plugin_worker under __main__.

toml
[project]
name = "my-plugin"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = ["vibex-plugin>=1.0.0"]

[tool.vibex.plugin]
worker = "runtime/worker.py"

Constants: PLUGIN_API_VERSION = "1.0", PLUGIN_PROTOCOL_VERSION = "1.1", PLUGIN_SDK_VERSION = "1.0.0".

Worker

python
from vibex_plugin import define_plugin_worker, run_stdio_plugin_worker


def setup(registrar, environment):
    def hello(value, env):
        env.log.info("hello", {"input": value})
        return {"ok": True, "input": value}

    registrar.handle("hello", hello)


if __name__ == "__main__":
    run_stdio_plugin_worker(define_plugin_worker(setup))

The async entry is run_stdio_plugin_worker_async. define_plugin_worker(setup) accepts a synchronous setup(registrar, environment). A handler may be a plain function or a coroutine; the SDK uses inspect to decide whether to await.

Handler id regex matches TypeScript. Duplicate registrar.handle(id, fn) throws PluginSdkError("handler_duplicate"). registrar.on_dispose(fn) runs in reverse.

environment.context is an attribute dict: plugin_id, plugin_version, generation, package_class, granted_capabilities. Underlying keys are camelCase (pluginId and the rest), aligned with the protocol.

environment.host.call(capability, operation, input=None) is async. Use an async handler, or put I/O in Full Trust helpers.

environment.log provides debug / info / warn / error. environment carries a cancellation flag and aborts on dispose.

activate_plugin_worker is for tests. Error type: PluginSdkError(code, message, details=None).

Full Trust local I/O

HostClient.call is the only Host RPC. files.* returns files_root_denied until a workspace root is bound. Full Trust also ships local helpers:

python
from vibex_plugin import fetch_url, read_local_file, write_local_file

raw = read_local_file("/path/on/host")
write_local_file("/path/on/host", "text")
result = fetch_url("https://example.com", method="GET", timeout=30.0)

vibex_plugin.isolated exports the same define_plugin_worker and omits local file and network helpers. The Host still spawns the Worker under Full Trust. See Plugin architecture.

Testing

python
from vibex_plugin import (
    create_worker_harness,
    create_generation_harness,
    MemoryHostClient,
    define_plugin_worker,
)


async def test_hello():
    worker = await create_worker_harness(define_plugin_worker(setup))
    result = await worker.invoke("hello", {"n": 1})
    await worker.dispose()

create_worker_harness, invoke, and dispose are coroutines and must be awaited. MemoryHostClient records call. create_generation_harness checks candidate switches. In-package tests live under sdk/python/tests/: stdio, protocol fixtures, worker, testing.

init --template python-worker default tests check manifestVersion on plugin.json. Authors add business handler tests.

Rust SDK

Crate name vibex-plugin-sdk, path crates/plugin-sdk. MSRV 1.85. stdio uses a tokio current-thread runtime. publish = false in Cargo.toml; plugins depend via path or the crate bundled in the Host family. Template rust-worker writes runtime/Cargo.toml and runtime/src/main.rs. entrypoints.worker.runtime is native, protocol 1.1.

toml
[package]
name = "my-plugin-worker"
version = "0.1.0"
edition = "2021"
rust-version = "1.85"

[dependencies]
serde_json = "1"
vibex-plugin-sdk = { path = "../../../crates/plugin-sdk" }

Constants PLUGIN_API_VERSION, PLUGIN_PROTOCOL_VERSION, and PLUGIN_SDK_VERSION match JS and Python.

Worker

rust
use serde_json::json;
use vibex_plugin_sdk::{define_plugin_worker, run_stdio_plugin_worker_blocking};

fn main() {
    let definition = define_plugin_worker(|registrar, _env| {
        registrar.handle_sync("hello", |input, _env| {
            Ok(json!({
                "message": "Hello from VibeX",
                "input": input,
            }))
        });
        registrar.on_dispose(|| async { Ok(()) });
    });
    if let Err(error) = run_stdio_plugin_worker_blocking(definition) {
        eprintln!("{error}");
        std::process::exit(1);
    }
}

PluginRegistrar:

  • handle(id, async_fn): async handler, Result<Value, PluginSdkError>
  • handle_sync(id, fn): sync wrapper
  • on_dispose(async_fn): cleanup, reverse order

WorkerEnv: context() / replace_context(), host (HostClient), log.debug/info/warn/error, is_cancelled() / cancel(), async call(capability, operation, input).

run_stdio_plugin_worker is async. run_stdio_plugin_worker_blocking is for main. The panic hook writes protocol error worker_panic then dispose.

PluginSdkError is the error type. Handler regex matches the other languages. hello_plugin_worker is an in-crate sample used by protocol fixtures.

Compile and manifest path

The Host spawns entrypoints.worker.path as an executable: Command::new(path), working directory the package root. That path must be a compiled binary.

Sequence:

  1. Run cargo build --release inside runtime/ on the same OS / CPU triple as the Host.
  2. Copy the artifact to dist/, for example dist/my-plugin-worker.
  3. Set the manifest to "path": "dist/my-plugin-worker", "runtime": "native", "protocol": "1.1".
  4. vibex-plugin validate / pack.

vibex-plugin build compiles JavaScript Workers (runtime/main.mjs → dist/worker.mjs) and managed MCP sources. Authors supply the native binary. init --template rust-worker writes path runtime/src/main.rs; change it to the compiled binary before release.

isolated feature

bash
cargo build --release --no-default-features --features isolated

Default feature std includes filesystem and network helpers. The isolated feature omits those helpers. define_plugin_worker stays the same. The Host still spawns the native Worker under Full Trust. See Plugin architecture.

Testing

The crate exports create_worker_harness, create_generation_harness, and MemoryHost. WorkerHarness::invoke is async. dispose runs on_dispose in reverse.

bash
cargo test -p vibex-plugin-sdk

Protocol fixtures read packages/plugin-contract/fixtures/protocol/*.jsonl, shared across the three language SDKs. Plugin-package tests are author-maintained. init --template rust-worker ships a Node test that checks the manifest; authors add Rust-side tests.

Platform architecture

This track modifies VibeX source: new Host capabilities, Application Core, the desktop shell, or the remote protocol. Plugins consume those capabilities as described in Plugin architecture.

Before work, read root CONTEXT.md, the ADRs in docs/adr/ that apply, and apply maiden-skill in full. Then load the smallest set of SKILL.md files matched by the Agent Skill rules in CONTEXT.md. Cross-layer changes load every matching skill together. A new Tauri command also covers IPC, frontend integration, and tests.

Layout

The repo is a pnpm workspace plus a Cargo workspace. The Rust toolchain is pinned in rust-toolchain.toml (currently nightly-2025-12-04).

Path Role
frontend/src/ React + TypeScript UI. @ → frontend/src, shared → shared/
src-tauri/ Desktop shell, invoke_handler, windows, AppState
crates/ Tauri-agnostic domain logic
shared/types.ts TS types from generate_types.rs. Generated; refresh with pnpm run generate-types
packages/plugin-sdk, packages/plugin-cli Public plugin contract
docs/adr/ Architecture decisions
assets/plugins/ Official bundled plugin sources

Three process layers

  1. The frontend talks to the backend only through invoke and event subscriptions.
  2. The Tauri shell registers commands and holds AppState. Commands live under src-tauri/src/commands/ by domain.
  3. crates/* implement the business. Services are injected through the Deployment trait. The desktop concrete type is LocalDeployment.

Agent subsystem

New agent, conversation, and turn work goes through crates/agents (AgentRuntime, AgentConnectionManager) and the event-sourced conversation core. The CLI-executor path has been removed. ExecutorActionType keeps only ScriptRequest. crates/executors still owns script execution, the executor config schema, and log normalization. Agent execution stays in crates/agents.

Conversation events append to conversation_event and project into the timeline. The frontend renders AgentTimelineConversation only. A conversation has at most one in-flight turn. See Conversation and Turn state machine.

Application Core and remote

Desktop commands, Web routes, and the remote-desktop adapter authenticate and map DTOs/errors, then call the same Application Core. The frontend uses BackendTransport: TauriTransport locally, WebTransport in the browser and on a workstation. One window binds one Host. One data directory has one Host occupant at a time. See Application Core.

Maiden principles

User-visible completeness outranks keeping a wrong abstraction. Defects are fixed at the origin. Names state behavior. Comments record why a decision exists. Backup files, commented experiments, and incorrect intermediate states are removed. Local source and deployed source are the same artifact. UI copy helps act, decide, understand state, recover, or judge a consequence.

Build environment

Environment

  • Node 22, pnpm 10.x (CI uses pnpm 10.13.1).
  • Rust nightly, see rust-toolchain.toml. The first cargo install follows that file.
  • SQLx CLI: cargo install sqlx-cli --no-default-features --features sqlite. After query changes run pnpm run prepare-db.
  • Optional cargo install cargo-watch.
  • Secrets stay in a local .env. .dev-ports.json and generated Tauri dev config are local runtime artifacts.

Startup installs a single rustls crypto provider (install_rustls_crypto_provider). reqwest is built in no-provider mode. Construct TLS clients after that function runs.

Dev ports are allocated dynamically into .dev-ports.json. scripts/run-tauri-dev-desktop.js writes src-tauri/tauri.dev.generated.conf.json per run.

Commands

From the repository root:

bash
pnpm install                 # JS deps; required before any pnpm script
pnpm run dev                 # Tauri desktop + Vite HMR
pnpm run check               # frontend tsc --noEmit + cargo check
pnpm run lint                # eslint max-warnings 0; clippy -D warnings --features qa-mode
pnpm run format              # cargo fmt --all + prettier

Frontend (frontend/ or pnpm --filter ./frontend):

bash
pnpm test
pnpm exec vitest run src/path/file.test.ts
pnpm exec vitest run -t "renders tool card"
pnpm run check
pnpm run lint

Backend:

bash
cargo test --workspace
cargo test -p agents
cargo test -p agents acp_session_resume
cargo clippy --workspace --all-targets --features qa-mode -- -D warnings

Codegen. Re-run when inputs change. CI fails :check on stale artifacts:

bash
pnpm run generate-types
pnpm run generate-types:check
pnpm run prepare-db
pnpm run prepare-db:check

generate-types runs with SQLX_OFFLINE=true. The generator merges: it keeps declarations outside its replacement list, replaces replacement_declarations(), and drops removed_declarations(). To export a new #[derive(TS)] type, add insert_declaration::<T>() in src-tauri/src/bin/generate_types.rs, then generate.

  1. Read CONTEXT.md, relevant ADRs, maiden-skill, and directly matching SKILL.md files.
  2. Isolate the branch with a git worktree (using-git-worktrees skill in-repo).
  3. For a behavior change, write a failing test first (tdd skill), then the smallest implementation.
  4. Run targeted tests, then the matching check / lint.
  5. After type, SQL, or agent-schema changes, run the matching generate/prepare.
  6. pnpm run format.
  7. Open a PR per PR and security.

Engineering conventions

Frontend

Prettier: 2 spaces, semicolons, single quotes, ES5 trailing commas, 80 columns. ESLint forbids unused imports and requires exhaustive switches. React component files are PascalCase .tsx. Hooks start with use. Utilities and config are camelCase.

Visual design follows root DESIGN.md: macOS Tahoe target. Liquid Glass is reserved for navigation and control chrome. Content surfaces stay opaque. Every route is wrapped in LegacyDesignScope (historical name; treat it as the active design scope). Tokens live in frontend/src/styles/legacy/index.css. Tailwind config is tailwind.legacy.config.js. Use role classes such as --surface-*, --text-*, .settings-surface. Product color uses tokens and role classes. Radii go through --radius (14px).

Visible copy helps act, decide, understand state, recover from an error, or judge a consequence. UI copy omits implementation notes such as “settings live in settings.json”.

Rust

Edition 2024, rustfmt.toml. Grouped imports. CI clippy runs --features qa-mode -- -D warnings. Local pnpm run lint enables qa-mode (including the QaMock executor) the same way.

Domain logic belongs in a crate and is reached through Deployment. Command handlers stay thin. New conversation capability goes in crates/agents. Scripts, config schema, and normalized logs stay in crates/executors.

Generated artifacts

These files are generated; refresh them with the matching command before commit:

  • shared/types.ts
  • crates/db/.sqlx offline query cache
  • src-tauri/tauri.dev.generated.conf.json
  • built packages/*/dist

After a SQL query! / query_as! or a migration, run pnpm run prepare-db. After exporting a #[derive(TS)] type, run pnpm run generate-types. Name those generated files in the PR.

Module boundaries

shared/types.ts is the frontend/backend contract. The frontend talks through invoke and shared/types.ts. Plugins import the public SDK only. Official bundled plugins are reference packages on that same contract. A Host special case keyed by plugin ID is debt to delete.

Test strategy

A behavior change or regression fix starts with a failing test, then the smallest implementation. Tests ship with the change.

Frontend

Unit tests sit beside sources: *.test.ts, *.test.tsx, *.spec.ts, *.spec.tsx. Vitest + jsdom. IPC is mocked in unit tests. Broader regressions live in frontend/tests/. E2E lives in frontend/tests-e2e/, chosen by target-platform feasibility.

bash
cd frontend
pnpm exec vitest run src/pages/settings/AgentSettings.test.tsx

UI changes (layout, style, routing, client state) are verified with real interaction before merge: click, type, submit, navigate; visit every route that shares the state; cover empty and error states; check desktop and narrow viewports for layout work. Without browser tools, use unit tests, the dev server, or a render script, and state in the PR what was left unverified.

Rust

In-crate src unit tests and tests/ integration tests. Prefer:

bash
cargo test -p agents acp_session_resume
cargo test -p plugins bundled_office

Then cargo test --workspace when the blast radius warrants it. SQLx tests follow offline cache or the test-database convention. Keep SQLX_OFFLINE aligned with CI.

Plugin contract

Changes to packages/plugin-sdk or plugin-cli run that package’s pnpm test and build. Changes to Host parsing or the contribution registry add crate tests and at least one real linked-install path (official Office or the workflow-creator fixture). Reference packages must keep using only the public SDK.

CI

.github/workflows/test.yml runs on pull_request and push to master. It includes dependency licenses and advisories, frontend checks, Rust clippy (qa-mode), and tests. generate-types:check and prepare-db:check fail on stale artifacts. Run the checks you touched before push.

Review and security

Commits

History uses Conventional Commits: feat:, fix(scope):, chore(scope):, docs(scope):, plus explicit merge commits. One commit, one change. Titles are imperative.

Pull request

The description includes:

  • A short summary of the user-visible result.
  • Linked issue, PRD, or ADR.
  • Test commands and results.
  • Screenshots or recordings for visible UI.
  • Generated files: shared/types.ts, .sqlx, plugin locks.
  • UI paths left unverified in a browser, if any.

Keep the diff small. Split refactors from features. Agent refactors finish on the ACP path.

Security

  • Secrets, tokens, and pairing codes live in a local .env, the OS keychain, or the Host token store. They live only in those stores.
  • Error envelopes on the remote protocol and plugin Workers strip secrets. Main token and device token travel in protected headers or the keychain.
  • Plugin packages are Full Trust. Official plugins merged into the Host, and APIs merged into the SDK, run at local rights. A new Host capability needs a schema, tests, and docs before plugins may call it.
  • Public Host exposure terminates TLS on a reverse proxy. Cross-origin allow lists use exact Origins.
  • CI runs pnpm audit --prod --audit-level high and rustsec/audit-check. Licenses: pnpm run dependency:licenses.
  • Crash reports stay in the local data directory by default. Content leaves the machine when the user chooses Submit on GitHub.
  • Docs and UI use placeholders. Samples use placeholders.

Review axes

Code review checks standards and spec together. Over-engineering review deletes reinvented stdlib, speculative abstraction, and flexibility with no caller. Correctness review covers failure paths, occupancy, event sequence, exclusive permission resolution, and freshness of generated artifacts.