mirror of
https://github.com/NeuralNomadsAI/CodeNomad.git
synced 2026-11-02 20:10:31 +00:00
## Summary
Slices **F0** (integration contract) and **T0** (CodeNomad test
fixtures) of the approved integrations SCR (§4.2 basis; AC-F1, AC-F2,
AC-T1, AC-T2, AC-T3, AC-T5, AC-T6). No user-visible change: this is the
base the recognition, attachment and API 4 slices build on.
### F0: contract
- `packages/server/src/opencode/integrations/contract.ts`: zod schemas
of the marker, the reviewed catalog and every RPC definition of contract
1 (`integrationRpcSchemas`, keyed like `rpc.schema.json`:
`<method>.input|output`, `<event>.event`), plus the draft constants.
- Vendored JSON Schemas of
`NeuralNomadsAI/opencode-integrations@68eb8aa` (`marker`, `catalog`,
`rpc`, `host`; byte-identical to spec 0.1 at `dac13a74`) with
`VENDORED.json` (revision + Git blob IDs, checked by the test).
- **`readIntegrationRpcOutput(name, value)`**: the one way to read an
RPC output. Integrations register their definitions without `pattern`
(OpenCode 2.2.5 refuses it; spec/rpc-contract.md §4), so OpenCode passes
outputs the spec forbids; the full zod schema (patterns and bounds)
decides, and a non-conforming output is an integration error (`{ ok:
false, error: "non-conforming" }`) that never echoes the value. Tested
against every sample the pattern-free definitions accept but the spec
refuses.
- **Parity test** (`contract.test.ts`): a corpus of valid and invalid
samples (including the conformance package's cases) through zod **and**
Ajv 2020-12; fails on any disagreement and on any definition missing on
either side. Lengths count code points like JSON Schema (astral
characters agree). Four seeded mutations of `contract.ts` are each
caught.
- Outputs are reader schemas (unknown fields dropped); inputs strict;
`integrationCatalogSchema` adds the uniqueness JSON Schema cannot
express (`integrationCatalogDocumentSchema` stays exact).
- Types in the new `packages/server/src/integration-types.ts` (not
`api-types.ts`).
- `ajv2020` = `npm:ajv@^8.20.0` dev alias, so the hoisted Ajv 6 used by
existing mission tests keeps resolving (a plain `ajv@8` dev dependency
shadowed it and broke their types).
### T0: fixtures
- `scripts/fixtures/fake-npm-registry.mjs`: the loopback fake registry,
now shared by `test-plugin-sources-native.mjs` (scoped names, 404 for
unknown names, integrity/shasum).
- `scripts/fixtures/fake-integration/`: installable fake integration
package (`@neuralnomads/opencode-<id>`): marker,
`status`/`installHost`/`hosts`/`attachHost`/`detachHost`/`advertise`/`withdraw`
and payload-free events, offer registry on an **injectable clock** (15 s
lease, renewal without hello, replacement only after a new hello, 16
offers, `busy`/`gone`/`refused`), **marker-mismatch** mode,
`hostPart`/`installOutcome` options, a loopback **fake host with
sentinel secrets** (and a `leaky` mode),
`findSentinels`/`assertNoSentinels`.
- **Python discovery isolation**: `python-host.py` runs the vendored
reference client (`Service.discover`, `advertise_all`/`withdraw_all`)
under `python -I -B` and an audit hook; the native test asserts every
file and socket access stays inside the fixture and reaches only the
fixture daemon. `writeFixtureServiceRegistration` registers the fixture
daemon under the fixture's `XDG_STATE_HOME`; `withProductRuntime` now
passes its `env` to `run`.
- Docs: new `dev-docs/OPENCODE_INTEGRATIONS.md` (indexed in `INDEX.md`
and the `AGENTS.md` area list), native fixtures section in
`BROWSER_TESTING.md`, `PLUGIN_CONTROLS.md` reference.
## Finding to note
OpenCode 2.2.5 refuses `pattern` in RPC definitions: Effect's JSON
Schema import throws "Patterns may block validation and are disabled",
so a call whose declared output has a pattern fails with
`rpc.invalid_output`. The fake package registers its definitions without
`pattern` and validates its own input; CodeNomad revalidates outputs
with `contract.ts`. The Blender pilot hit the same refusal;
opencode-integrations `68eb8aa` now registers without patterns and says
readers validate outputs, which `readIntegrationRpcOutput` does.
## Out of scope
- AC-T4 (Blender 5.2 in the integrations CI, manual protocol) belongs to
`opencode-integrations`.
- AC-T3's §4.4 breaker clock: the breaker arrives with the API 4 slice;
the convention (every lease/expiry/rate window takes `now`) is
documented.
## Tests
- `node --import tsx --test
packages/server/src/opencode/integrations/contract.test.ts`: 24 pass
- `node --import tsx --test
scripts/fixtures/fake-integration/fake-integration.test.mjs`: 15 pass
- `node --import tsx scripts/test-integration-fixtures-native.mjs
<opencode 2.2.5>`: PASS (fake registry install, contract outputs, marker
mismatch, Python isolation + advertise/attach/detach/withdraw, no
sentinel in outputs or daemon log)
- `node --import tsx scripts/test-plugin-sources-native.mjs <opencode
2.2.5>`: PASS with the shared registry
- `npm run typecheck --workspace @neuralnomads/codenomad`: clean
- Full server suite: 2966 tests, 2953 pass, 2 fail under load
(`plugin-sources.test.ts`, `media-blob.test.ts`; both pass alone, as
does the `index.test.ts` load timeout)
- `node --test scripts/native-fixture-guards.test.mjs
"scripts/fixtures/wsl-*.test.mjs"`: 11 pass
- CI: unit fixtures in the server test job; the native test in the
runtime-contract job (minimum and latest).
14 KiB
14 KiB
AGENT NOTES
Rules here apply everywhere. Before changing code in an area listed under Area conventions, read that area's document; its rules bind like this file. dev-docs/INDEX.md lists every document.
Styling Guidelines
- Reuse the existing token & utility layers before introducing new CSS variables or custom properties. Extend
src/styles/tokens.css/src/styles/utilities.cssif a shared pattern is needed. - Place new styles beside their peers in focused files (≈150 lines or less) under
src/styles/{components|messaging|panels}/and@importthem from the matching aggregate entry file (src/styles/controls.css,messaging.css,panels.css), which holds only imports. Reusable patterns (buttons, selectors, dropdowns) live insrc/styles/components/; never redefine the same utility classes elsewhere. - Dialog, popover and floating-window headers, toolbars, bodies, footers, titles and actions use the shared
.window-*primitives (src/styles/components/window.css). Persistent utility windows and transcript-covering previews putcomponents/window-close-button.tsxat the upper right of their existing top toolbar, never in a dedicated row; transient popups such as transcript filters dismiss on outside interaction and have no close button. - Corners are square unless the user explicitly requests otherwise for a specific change. Round exceptions: Yolo, MCP and plugin activation switches and the lone-setting Settings switch (
settings/settings-switch.tsx), all sharingstyles/components/switches.cssgeometry; floating message scroll buttons; tags and numeric/context/token labels via--chip-radius(alias--pill-radius; register badge variants instyles/components/badges.css, use.badge-shapefor utility-styled labels, never a local radius). Other chrome, including the fixed panel toggles, stays square. - Pick backgrounds by surface role (
styles/tokens.css), never by a colour that merely looks close:--surface-field(the base colour) is reserved for what users type into (composer, inputs, textareas, search rows);--surface-canvasfor reading areas (transcript, editors, diffs, main panel bodies);--surface-overlayfor floating windows, dialogs, popovers, tooltips and every menu or list picker;--surface-inset(also--surface-code) for code, shell, log and diagnostics output and status cards;--surface-controlfor buttons, segmented controls and select triggers; icon controls that rest flush on a canvas (Files panel toolbars, session header buttons) take the canvas they sit on. The three non-field roles follow the title-bar tint (--surface-shade-strong). Utilities.bg-surface-{field,canvas,overlay,inset}mirror them; consumers that need concrete colours (Monaco, MUI, extension frames) resolve roles throughlib/css-color.ts. - Menus share one item treatment (
--menu-item-hover-bg/--menu-item-selected-bg) across Kobalte menus, selectors and native<select>.styles/components/native-select.cssstyles every native select globally: the closed control matches selector triggers (control surface, chevron) and, whereappearance: base-selectand content-box clipping are supported (Electron, WebView2), the open list matches the shared menus and long values stay on one line, clipped before a background-drawn chevron; elsewhere the native select remains. Customizable selects size to the shown value, so auto-width selects need a stable feature width (container units, not percentages of shrink-to-fit parents). Do not style individual selects as fields. - The
.icon-toggleactive state (aria-pressed/aria-expanded/data-active) is one shared rule instyles/utilities.cssfor every palette; never add per-button or per-palette active overrides (exact tint, exception and contrast test indev-docs/UI_CONVENTIONS.md). - SUID buttons never animate on press (
MuiButtonBasedisableTouchRippletheme default inlib/theme.tsx), like the Message content trigger; the keyboard focus ripple remains their focus-visible cue, squared instyles/components/buttons.css. Do not re-enable press ripples per component, nor disable the focus ripple without another focus cue. - Document new conventions where agents will find them: rules that apply everywhere belong in this file; an area's contract belongs in that area's document, under Agent conventions when it fits nowhere else in the body (create the document and add it to the index below when none fits).
- Documentation (this file,
dev-docs/, skills) describes the current state only. Keep no history: removed features and their names, former behavior ("formerly", "no longer", "replaced by"), PR/commit/date provenance, dated validation logs or abandoned designs; Git holds them. Code that reads older data is described as current behavior. Do not add sentences denying something ("X is not Y", "this does not prove…"); when one would counter a statement elsewhere, correct or delete that statement instead. Prohibitions that tell an agent what never to do remain instructions.
Coding Principles
- Keep modules narrowly scoped with only the public API callers need (KISS); share helpers through dedicated modules instead of copy/paste (DRY); split files when concerns diverge (state, actions, API, events); prefer composable primitives (signals, hooks, utilities) over deep inheritance or implicit global state; isolate platform integrations (SSE, IPC, SDK) in thin adapters surfacing typed events/actions.
- Memoize action-menu item arrays (
createMemo, or stable per-row arrays) instead of rebuilding them with fresh JSX icons on each evaluation behind<For>or intoActionOverflowMenu. An item whose icon renders in two places at once (inline buttons and the overflow menu, as inFileRowActionsor tool-call headers) creates the icon in a getter or comes from an unmemoized builder such as message-block'sheaderMenuItems: one DOM node cannot be inserted twice. - Follow
dev-docs/CACHE_REFRESH_CONVENTIONS.mdfor display snapshots, coalesced trailing refreshes, stale-response fencing and authoritative mutation reads. Check existing feature semantics before adding another cache or refresh policy. - CodeNomad integrates with an independently installed and updated OpenCode service: compatibility depends only on the API/capability contracts actually consumed; the versions pinned to build or test CodeNomad only make its own build reproducible. Never turn tested versions into a daemon allowlist or block a runtime/feature only because its version is untested; a version-specific restriction needs a demonstrated failing test or an identified breaking change in a consumed contract, cited with why the restriction is necessary. Review upstream changes and affected callers rigorously (private API use needs contract scrutiny) instead of exhaustively retesting unchanged contracts. Missing capabilities must fail safely without destructive or mass-loading fallbacks.
- Never modify OpenCode for Missions autonomy: use and qualify existing native service/plugin mechanisms, and never infer an upstream-extension requirement from the absence of a named scheduling API.
Area conventions
Paths are under packages/ui/src/ or packages/server/src/ unless stated.
- Missions (
missions/,opencode/missions-plugin.ts,server/routes/mission-*,host-lifetime/; UIcomponents/mission-*,components/settings/missions-settings-section.tsx,stores/mission-*,styles/panels/mission-*):dev-docs/MISSIONS.md; recurring schedules and Wayfinder human gates (its "Wayfinder human decision mark" section)dev-docs/MISSIONS_RECURRING.md. - Questions, permissions and pending-request recovery (interruption dock, permission receipts,
question-data.ts,server/routes/pending-requests.ts,workspaces/pending-discovery.ts,stores/pending-recovery-*):dev-docs/NATIVE_INTERRUPTION_UX.md. - Composer (
components/prompt-input*,prompt-input/,PromptContextControls,/btw):dev-docs/COMPOSER.md. - Transcript (message rendering, Message content and presets, token lines, tool renderers,
stores/message-v2/,stores/opencode-data.ts,lib/native-execution-events.ts):dev-docs/TRANSCRIPT.md; long-text boundsdev-docs/TEXT_DISPLAY_BOUNDS.md. - Timeline, transcript scrolling and history search (
virtual-follow-list.tsx,timeline-*,stores/timeline-previews.ts, history search):dev-docs/SESSION_HISTORY_NAVIGATION.md,dev-docs/SESSION_HISTORY_QUERIES.md. - Shell, panels, tabs, session sidebar and native menus (
instance/shell/,tab-scroll.tsx, session list,lib/native/menu-labels*):dev-docs/UI_SHELL.md. - Session state and recovery (
stores/session-state.ts,stores/session-generation-recovery.ts):dev-docs/SESSION_RECOVERY.md. - Dialogs, utility windows, icon toggles, surfaces and scrollbars (
alert-dialog.tsx,dismissible-window.tsx, auth recovery,styles/utilities.css,index.css):dev-docs/UI_CONVENTIONS.md. - Settings and Preferences (settings rows/actions, Preferences window and project context, Providers/Web search, Stats, notifications, bug report):
dev-docs/SETTINGS.md; provider accounts and usagedev-docs/PROVIDER_ACCOUNTS.md; hotkeysdev-docs/HOTKEYS.md; palettesdev-docs/PALETTE_SOURCES.md; plugin controlsdev-docs/PLUGIN_CONTROLS.md; right-panel customization and panel extensionsdev-docs/PANEL_EXTENSIONS.md. - Projects and tabs (closing tabs, temporary workspaces, new-instance launcher, Git-unavailable mode):
dev-docs/PROJECTS.md. - Files panel and Git (Workspace/Changes/Commits, file watches, Git init, whitespace filter):
dev-docs/FILES_PANEL.md. - Worktrees and event ownership (
workspaces/native-worktrees.ts,workspaces/worktree-inventory.ts):dev-docs/WORKTREE_SESSION_PLACEMENT.md. - OpenCode runtime, setup, upgrades and OpenCode settings:
dev-docs/OPENCODE_RUNTIME_SETUP.md; bundled plugin builds, provisioning, presence readiness, the event relay and the Design agent and skills (opencode/design/):dev-docs/OPENCODE_COMPATIBILITY.md; connection budget:dev-docs/CACHE_REFRESH_CONVENTIONS.md; session environment:dev-docs/SESSION_ENVIRONMENT.md; pruning:dev-docs/SESSION_PRUNING_RPC.md. - OpenCode integrations (
opencode/integrations/,integration-types.ts; fixturesscripts/fixtures/fake-integration/,scripts/fixtures/fake-npm-registry.mjs):dev-docs/OPENCODE_INTEGRATIONS.md. - Remote Control and Remote Access settings:
dev-docs/REMOTE_CONTROL.md; SideCars:dev-docs/architecture.md. - Browser previews and automation:
dev-docs/BROWSER_AUTOMATION.md,dev-docs/BROWSER_DEVICE_EMULATION.md. - Desktop hosts: data profiles
dev-docs/DESKTOP_DATA_PROFILES.md; updates and update feeddev-docs/TAURI_UPDATER.md; Tao/Tauri backportspackages/tauri-app/vendor/README.md; bundled Node/npmdev-docs/RUNTIME_MAINTENANCE.md. - Public help (
docs/help/, help entry points):dev-docs/USER_HELP.md.
Multi-Language Support (i18n)
Never hardcode user-visible strings. The UI uses a small custom i18n layer (packages/ui/src/lib/i18n/index.tsx).
- Use
useI18n()(const { t } = useI18n();) in components andtGlobal(...)in stores/non-component code; never importenMessagesin feature code. - Messages are flat dot-key TypeScript objects (
"flat.dot.keys": "string") inpackages/ui/src/lib/i18n/messages/<locale>/, merged by each locale'sindex.tsviamessages/merge.ts(duplicate keys throw at build time). Add a string to the righten/*.tspart file and the same key to every other locale's matching file: missing keys silently fall back to English, then the key. - Placeholders are simple
{name}replacements (word characters only; never{file-name}); plurals use separate keys such assomething.one/something.other, chosen in code. - A new language needs a
messages/<locale>/folder +index.ts, an entry inSUPPORTED_LOCALES(lib/i18n/index.tsx) and an option incomponents/locale-selector.tsx. - The selected locale is the
localeapp preference, persisted via the server config (default~/.config/codenomad/config.yaml;config.jsonis migration input only).
File Length Guidelines (Highlight Only)
File size is a refactoring signal, never by itself a reason to refactor. When a change touches a source file over ~500 lines (target limit ~800) or a test file over ~1000, mention its path and approximate line count in your final response; keep new files under these thresholds unless there is a clear reason.
Tooling Preferences
- Modify existing files with the
edittool; usewriteonly to create new files. - Browser rendering regressions live in
packages/ui/tests/browser/(deterministic HTTP fixtures infixtures/) and exercise the real Solid components and native event dispatcher, never reimplemented rendering logic. Runnpm run test:browser --workspace @codenomad/uiafternpx playwright install chromium;CODENOMAD_BROWSER_PATHmay select an existing Chromium executable. - Concurrent Vite browser fixtures use
tests/browser/fixture-cache.tsfor per-server optimized-dependency caches; close the server before disposing its owned temporary cache. Never clear the checkout's shared cache to repair a fixture or make parallel tests share mutable optimizer output. The one sanctioned exception is the runner-owned fixture daemon (dev-docs/BROWSER_TESTING.md): each run spawns one daemon child that owns a single cache dir and serves read-only borrowers over loopback, stopping with an authenticated request when the run ends.
Desktop Runtime Launch
- Native automation instrumentation starts automatically. Do not configure a fixed CDP port or a manual WebView2 profile.
- Rebuild Electron before calling
codenomad.act({ action: "restart" }). For Windows Tauri, stop and relaunch the release executable only when the linker cannot replace it; never stop the shared OpenCode daemon.
Pull Requests
- A PR that changes user-visible behavior (labels, controls, steps, defaults, limitations) updates the affected help pages in the same PR: English first, then the nine translations using each locale's UI labels (
dev-docs/USER_HELP.md). Help publishes fromdev, and CI checks only its structure and links. Answer the non-blockinghelp-update-reminder.ymlcomment (English UI messages changed withoutdocs/help) before merging: update the help, or confirm nothing user-facing is documented there.
Commit Message Guidelines
- Commit messages: a concise conventional-style subject, then body paragraphs explaining why the change exists, the user-visible behavior change, the implementation approach, important edge cases or platform considerations, and the validation or tests that prevent regressions.