A pure bun workspaces monorepo containing the Noesis apps, their shared contract packages, and AI-harness plugins (Claude Code today; Codex, OpenCode, pi planned).
┌─────────────────┐ REST ┌────────────────┐ REST ┌─────────────────────┐
│ server/frontend │ ────────► │ server/backend │ ◄──────── │ plugins/mcp-bridge │
│ React/Vite │ │ Hono (bun) │ │ MCP bridge (stdio) │
│ :5173 │ │ :3000 │ │ npm: @noesis-vision│
└─────────────────┘ └────────────────┘ │ /mcp-bridge │
└─────────────────────┘
▲ launched via bunx by
│
┌───────────┴───────────┐
│ plugins/claude-code │
│ (OpenCode, Codex, ... │
│ planned) │
└───────────────────────┘
| App | Stack | Purpose |
|---|---|---|
server/frontend |
React 19 + Vite | Web frontend; typed RPC (hc<AppType>) to backend |
server/backend |
Hono on Bun.serve |
Backend API (port 3000) |
A stdio MCP server (plain TS on bun) bridging coding agents to backend
over REST. Published to npm as @noesis-vision/mcp-bridge — a
self-contained dist/main.js bin built at publish time — so every agent
plugin launches the same bridge via bunx @noesis-vision/mcp-bridge@<version>
instead of shipping its own copy (decision 33). Versioned in lockstep with the
Claude Code plugin.
All contracts are zod schemas with inferred TS types, consumed directly as TypeScript source (no build step):
@repo/shared-contracts DTOs common to all of the below
▲ ▲
@repo/local-contracts plugins/mcp-bridge/src/contracts
(backend↔bridge) (MCP tool payloads + registry, owned by the
bridge; feeds skill schemas & bridge validation)
The backend↔frontend boundary needs no contracts package: the frontend infers
request and response types from the backend's route tree via Hono's
hc<AppType> client.
One folder per AI harness. plugins/claude-code is a Claude Code plugin and a workspace member:
skills/prepare-mcp-data/— teaches the model how to build MCP payloads;references/*.schema.json+*.example.jsonare generated by the bridge from its contracts (decision 38)tools/— dev/build tooling (generate, bump, release); not shipped.mcp.json— launches the bridge viabunx @noesis-vision/mcp-bridge@<version>(pin stamped bybun run generate); target server is configurable viaNOESIS_SERVER_URL(defaulthttp://localhost:3000)
The plugin is distributed as the npm package @noesis-vision/claude-code-plugin (only .claude-plugin/plugin.json, .mcp.json, and skills ship — see the files field). The marketplace catalog lives at plugins/claude-code/.claude-plugin/marketplace.json and is added by direct URL, so users never clone this monorepo.
@repo/typescript-config— shared tsconfig presets:base.json,vite.json
Linting and formatting need no config package: a single root biome.json covers the whole workspace (per-area rule tweaks live in its overrides).
Shared dependency versions (typescript, @biomejs/biome, zod, hono, …) are pinned once in the root package.json catalog — workspaces reference them as "catalog:". Internal packages depend on each other via the workspace:* protocol.
Planned language scanners (java/, dotnet/) — not yet implemented and not part of the bun workspace.
| Tool | Role |
|---|---|
| bun | Package manager, TS runtime (apps run TS directly), server bundler, test runner, task orchestration |
| TypeScript | Everything is TS; internal packages export src/*.ts directly |
| zod (v4) | Contract schemas, env validation, JSON Schema generation |
| Hono 4 | backend app (routing on Bun.serve) + typed RPC client (hc) in the frontend app |
| React 19 + Vite | frontend app (Vite dev server proxies /ui to the backend; vite build emits the SPA it ships) |
| @modelcontextprotocol/sdk | MCP server in plugins/mcp-bridge |
| Biome 2 | Linting and formatting (TS/TSX/JS/JSON); Prettier formats Markdown only |
| GitHub Actions | CI (verify + generated-artifact drift check) and tag-driven npm releases via trusted publishing |
- bun ≥ 1.3 (pinned via
packageManagerinpackage.json)
bun install # install all workspaces
bun run dev # run all apps in watch mode
bun run dev:server # just backend + frontend
bun run build # build everything
bun run lint # biome check (lint + format check; `lint:fix` to autofix)
bun run check-types # tsc --noEmit across packages
bun run test # unit tests
bun run test:e2e # e2e tests
bun run format # biome + prettier(md) --write (`format:check` to verify)Filter to one package: bun run --filter=backend build.
- Add/edit a zod schema in the right place (
@repo/shared-contracts,@repo/local-contracts, orplugins/mcp-bridge/src/contracts). - For MCP payloads, register it in
plugins/mcp-bridge/src/contracts/registry.ts. - Regenerate plugin artifacts:
bun run generate # refreshes skill schemas/examples, plugin.json version, .mcp.json pin- Commit the generated output — CI (
.github/workflows/ci.yml) regenerates and fails on any diff.
The plugin installs from npm — no monorepo clone needed. Add the marketplace by direct URL:
# in Claude Code:
/plugin marketplace add https://raw.githubusercontent.com/<owner>/noesis/main/plugins/claude-code/.claude-plugin/marketplace.json
/plugin install noesis@noesis # stable channel
/plugin install noesis-beta@noesis # beta channel (prerelease builds)Note: the catalog references the published npm package (
@noesis-vision/claude-code-plugin), so installs track releases, notmain. Thenoesis-betaentry is pinned to the latest published prerelease. When developing the plugin itself, point a local marketplace entry at the folder instead ("source": "./").
Releasing a new version (from plugins/claude-code; the plugin and @noesis-vision/mcp-bridge release in lockstep — one version train, decision 33):
# Beta: one command — bump, generate, smoke-test, commit, tag, push
bun run release:beta # or: bun run release:beta 0.2.0-beta.1
# Stable: the same steps by hand
bun run bump 0.2.0 # plugin + bridge package.json + matching marketplace channel pin
bun run generate # stamps .claude-plugin/plugin.json + the .mcp.json bridge pin
git commit -am "Release 0.2.0"
git tag -a v0.2.0 -m "Release 0.2.0" && git push origin main v0.2.0The Release workflow (.github/workflows/release.yml) verifies the tag against both package versions, packs with bun pm pack (rewrites workspace:*/catalog:), and publishes both packages via npm trusted publishing (bridge first, so the plugin's pin always resolves) — prereleases land on the beta dist-tag, stable versions on latest. Testers install with /plugin install noesis-beta@noesis (or npm i @noesis-vision/claude-code-plugin@beta).
Local fallback:
bun publish/bun run publish:beta(never rawnpm publishfrom the workspace — only the bun pack pipeline rewritesworkspace:*/catalog:versions in the manifest).
Point the MCP server at a different backend per project via .claude/settings.local.json (e.g. the deployed Railway domain):
{ "env": { "NOESIS_SERVER_URL": "https://<service>.up.railway.app" } }Payload validation happens in the MCP bridge itself (decision 34): every tools/call is checked against its contract's zod schema, and mismatches come back as descriptive in-band tool errors (failing fields + a valid example) so the calling agent can correct itself.
backend + frontend deploy as one Railway service: the Hono backend
serves the built SPA (decisions 17/18/28). Routes are segregated by consumer — /ui/* for
the SPA, /api/* for the MCP bridge, /internal/* for health and other
technical endpoints — so each surface can carry its own auth later.
- How it ships: every green push to
maintriggers thedeployjob inci.yml, which runsrailway up --ci. Railway buildsserver/backend/Dockerfile(multi-stageoven/bun, pinned topackageManager, repo-root build context) and health-checks/internal/health(railway.json). - Configuration:
RAILWAY_TOKEN(GitHub Actions secret, a Railway project token) andRAILWAY_SERVICE(GitHub Actions repository variable, the Railway service name). The container needs no service variables — Railway injectsPORT;UI_DIST_PATHis baked into the image. - Run the production image locally:
docker build -f server/backend/Dockerfile -t noesis-backend .
docker run --rm -p 3000:3000 noesis-backend- Plugin users point
NOESIS_SERVER_URLat the service's generated Railway domain (see above).