Skip to content

Latest commit

 

History

History
executable file
·
506 lines (392 loc) · 26.2 KB

File metadata and controls

executable file
·
506 lines (392 loc) · 26.2 KB

AGENT RULES

Guidelines for working with code in this repository. These are linter-like rules and build/test instructions — not a code summary.

Last updated: 2026-06-19. Keep this in sync with the codebase; update it in the same PR when rules or commands change.

Canonical standards live in docs/DEVELOPMENT_STANDARDS.md. That document is the single source of truth for enforceable rules and aspirational targets across TypeScript, React, Zustand, MUI, TanStack Query, ReactFlow, Fastify, Drizzle, Zod, Electron security, accessibility, performance, security, observability, error handling, git/PR hygiene, and dependency management. The rules in this file are the area-specific overlay — read both.

Quick Navigation


Agent Harnesses & Tooling

The repo ships harnesses built for the agent edit→verify loop: check a workflow before running it, run it and read everything it emitted, run a single node in isolation, drive the real browser, deploy, and trace token/cost. Reach for these before hand-rolling a script. Every CLI command runs from source with npm run dev:nodetool -- <cmd> (no build) or from dist with npm run nodetool -- <cmd> after npm run build:packages. The full flag reference lives in CLAUDE.md and docs/cli.md.

Need CLI harness Agent/MCP tool Speed
Static pre-flight (unknown nodes, missing props, bad edges) — run this first nodetool validate <id|file.json|file.ts> validate_workflow (inline graph or workflow_id) < 1 s, no DB for file targets
Run a workflow end-to-end and read every message/log/output/error nodetool debug <id|file> (server surface, default) debug_workflow (status + outputs + errors + job logs + graph in one call) seconds
Build a mini app from a prompt and verify it end to end nodetool app build "<prompt>" -p <provider> -m <model> build_app (POST /api/applications/build; poll/cancel via the debug-session endpoints) minutes
Real-browser surface (Playwright + Chromium canvas), trace, per-stage shots nodetool debug <id> --browser --trace --stages tens of seconds (opt-in)
Tight edit→verify loop on a file target nodetool debug file.ts --watch (prints a verdict diff per save) per-save
Run one node in isolation with a prop bag nodetool node run <type> --props '{…}' [--no-secrets] sub-second hermetic
Run a workflow (id, JSON, or DSL .ts) nodetool run <file> / nodetool workflows run <id> [--params …] run_workflow, start_background_job varies
Map changed files → minimal workspaces to rebuild/test nodetool affected [--base main] instant
Author/inspect a graph against the live registry create_workflow, search_nodes, list_nodes, get_node_info, get_example_workflow, export_workflow_digraph
Jobs & assets nodetool jobs … / nodetool assets … list_jobs, get_job, get_job_logs, list_assets, get_asset
Agent/chat REPL (loop | plan | graph | multi-agent) nodetool-chat --agent (npm run dev:chat)
Deploy + remote ops (Docker/SSH/RunPod/GCP/Supabase) nodetool deploy <init|plan|apply|status|logs|destroy>; deploy workflows <sync|run>, deploy database, deploy collections
Trace tokens/cost/timing (OTel span tree) --trace-file <f.jsonl> / --trace-stdout pretty|json on any CLI run

The agent/MCP tools above are the @nodetool-ai/agents MCP tools (packages/agents/src/tools/mcp-tools.ts), exposed to in-product agents and over the websocket MCP server — use them instead of shelling out when you are already inside an agent context.

Browser workflow harness. The in-browser graph harness runs whole workflows against the real backend and renders the actual ReactFlow canvas, recording IO, traces, and screenshots — see In-Browser Workflow Harness below and web/src/e2e_runner/README.md. The same surface backs nodetool debug --browser and web's npm run test:debug-harness.

Suggested loop: validate (cheap, catches structural bugs) → node run to isolate a suspect node → debug to run the whole graph and read messages → debug --browser only when a bug is browser-specific → --trace when chasing token/cost/latency.


Prerequisites

  • Node.js 22.22.1 (required — see .nvmrc). Matches Electron 39's embedded Node (22.22.1). The one source-built native module (better-sqlite3) is rebuilt against the Node ABI by the root postinstall hook (electron/scripts/rebuild-native.mjs), which runs after npm install/npm ci finishes reifying the tree.
  • Use nvm use to activate the correct version.
  • If you see NODE_MODULE_VERSION errors, run npm run rebuild:native.
  • Fresh checkout or missing node_modules: if npm run typecheck/lint/test fail with module-resolution errors (Cannot find module, Cannot find type definition file) on files you didn't touch, run npm install first — don't spend a cycle proving the failure predates your change. Re-run the checks after install before investigating further.

Install in sandboxed / proxied environments

Three postinstall steps break npm install in locked-down containers (CI sandboxes, Claude Code on the web, proxied networks). A failed postinstall makes npm roll back the entire node_modules tree, so one bad package means no dependencies at all — including ESLint and the design-lint gate.

  1. keytar needs libsecret headers on Linux. Without them node-gyp fails with Package libsecret-1 was not found. Fix first: apt-get install -y libsecret-1-dev.
  2. electron downloads its binary in postinstall. Proxies that block the download (HTTP 403) fail the install. Skip it when you don't need to launch Electron: ELECTRON_SKIP_BINARY_DOWNLOAD=1 npm install.
  3. onnxruntime-node downloads CUDA binaries from GitHub releases in postinstall (it assumes CUDA when nvcc is absent). Same proxy failure mode, and there is no skip env var in our pinned version.

When only the JS toolchain matters (typecheck, lint, unit tests that don't hit native modules), bypass all of the above in one step:

npm install --ignore-scripts --no-audit --no-fund

This skips every postinstall — including the root better-sqlite3 rebuild — so anything touching the database needs npm run rebuild:native afterwards (which will still require the downloads above to have succeeded).

WebGPU on a headless machine

The image nodes are shader-backed: every lib.image.* generator and every nodetool.image transform reaches WebGPU through Dawn. On a machine with no Vulkan driver they fail with:

No WebGPU adapter available (Node/Dawn). On headless Linux this usually means
no Vulkan driver (ICD) is installed — Dawn has no software fallback of its own.

This is an environment gap, not a broken test and not an unsupported platform. CI already solves it: the test-packages leg of .github/workflows/quality-checks.yml and the browser job in test.yml both install mesa-vulkan-drivers, which ships lavapipe — a CPU Vulkan ICD. Do not conclude from this error that shader-backed nodes cannot be tested, and do not skip a test because your box hits it; the same test passes in CI.

With root:

apt-get install -y mesa-vulkan-drivers

Without root (sandboxes, dev containers), extract the driver and point the Vulkan loader at it. libvulkan1 — the loader — is usually already present; only the ICD is missing:

apt-get download mesa-vulkan-drivers
dpkg-deb -x mesa-vulkan-drivers_*.deb /tmp/vk
# The shipped manifest names the library relatively, so rewrite it absolutely:
python3 - <<'PY'
import json
p = "/tmp/vk/usr/share/vulkan/icd.d/lvp_icd.json"
d = json.load(open(p))
d["ICD"]["library_path"] = "/tmp/vk/usr/lib/x86_64-linux-gnu/libvulkan_lvp.so"
json.dump(d, open("/tmp/vk/lvp_icd.json", "w"))
PY
export VK_DRIVER_FILES=/tmp/vk/lvp_icd.json

Then run the tests as usual. Lavapipe is a software rasterizer, so it is slow but exact — pixel comparisons (nodetool.compare.CompareImages) are reproducible under it, which is what packages/base-nodes/tests/image-examples-run.test.ts relies on.

Build, Lint & Test Commands

Make Targets (Recommended)

npm install          # Install all dependencies (web, electron, mobile)
npm run build            # Build all packages
npm run typecheck        # Type check all packages
npm run lint             # Lint all packages
npm run lint:fix         # Auto-fix linting issues
npm run test             # Run all tests
npm run check            # Run all checks (typecheck, lint, test)

Backend Packages

npm run build:packages                          # Build all in dependency order
npm run test:packages                           # Test all packages
npm run test --workspace=packages/<name>        # Test single package
npm run test:watch --workspace=packages/<name>  # Watch mode for single package

Web Package

cd web
npm install              # Install dependencies
npm start                # Start dev server
npm run build            # Production build
npm run typecheck        # TypeScript check
npm run lint             # ESLint
npm run lint:fix         # Auto-fix lint issues
npm test                 # Run tests
npm run test:watch       # Watch mode
npm run test:coverage    # With coverage
npm run test:e2e         # Run e2e tests (requires backend)

Electron Package

cd electron
npm install              # Install dependencies
npm start                # Start electron
npm run build            # Production build (tsc + vite + electron-builder)
npm run typecheck        # TypeScript check
npm run lint             # ESLint
npm run lint:fix         # Auto-fix lint issues
npm test                 # Run tests

Development Servers

npm run dev                 # Backend (tsx --watch) + web Vite server
npm run dev:server          # Backend dev server only (tsx --watch, port 7777)
npm run electron            # Build web and start Electron app
npm run electron:dev        # Electron against Vite server (requires conda env)

Mandatory Post-Change Verification

After any code change, run:

npm run typecheck  # Type check all packages
npm run lint       # Lint all packages
npm run test       # Run all tests

All three must pass before the task is complete.

Running suites per package instead is fine when the full run is too slow — but run every package nodetool affected names, not the ones you remember touching. lint passing is not test passing: a change to packages/websocket that was linted and never tested broke two route suites, and CI found it rather than the author.

Claims, Checks, and Measurements

Verification failures in this repo are rarely "forgot to run the tests". They are green signals that were never earned. Four rules, each paid for:

Prove a new check can fail. Invert the condition once and watch it go red, then restore. nodetool validate returned ✅ on a workflow whose model id was totally-not-a-real-model-xyz — it had never checked ids at all, and only a deliberately-bogus input revealed it. A check that has only ever been green is indistinguishable from one that examines nothing. For an audit that scans files, also assert it found something, so it cannot pass by matching nothing. This is rule 7's sibling in docs/HARNESS_FIRST.md.

Reproduce before you enforce. Rule 5 requires a bug fix to ship a reproduction; the same applies to a new rule. A validator check was written from a log warning plus code reading, shipped as an error, and would have failed the examples gate on six shipped workflows — until three reproductions of its own criterion passed cleanly and the feature was reverted. Until you have watched the failure, report it; do not enforce it.

"I checked" means you enumerated. Not one plausible file, and never a comment — a comment is a hypothesis about code, not the code. Claims that validateGraph was ungated in CI, and that six call sites were safe, were both made from a sample and both wrong in method. To assert "all X do Y", produce the list; if that is too expensive, scope the claim to what you actually read.

Distrust the measurement before the conclusion. pgrep -f matches the shell command that contains the pattern, so a probe can report a dead process as running. cmd | head && echo ok prints ok on failure, because head exits 0 — capture the exit code of the command you care about. When a result is surprising, re-measure with a different tool before believing it.

Two mechanical traps worth naming: after a programmatic edit, byte-count the file for stray control characters (a \u0000 written as the byte it denotes got a .ts file staged as binary), and if the code walks a graph or list, run it once on a large input — an edges.some() inside a node loop is O(n·m) and passed every hand-written fixture before timing out on the 20 000-node chain in CI.

Code Review for Regressions

Before submitting a PR, review for:

  1. Existing tests still pass and cover the changes
  2. No new TypeScript errors or lint warnings
  3. No unintended side effects in related code
  4. Edge cases and error handling are covered
  5. Performance implications considered

Common Pitfalls

  • Decorator packages load from dist/: base-nodes, node-sdk, fal-nodes, replicate-nodes, elevenlabs-nodes, minimax-nodes use decorators. After changing these, run npm run build:packages before running npm run dev.
  • Package build order matters: Always use npm run build:packages (builds in dependency order). Don't build individual packages with unbuilt dependencies.
  • Mobile typecheck needs protocol: Run cd packages/protocol && npm run build before npm run typecheck:mobile.
  • mobile/ is not a root workspace: it keeps its own Expo/React Native deps, so its scripts use npm --prefix mobile … (not npm --workspace=mobile …, which would fail).
  • WebSocket uses MsgPack, not JSON: Use existing serialization helpers. Don't serialize WebSocket messages as JSON.
  • Don't create WebSocket instances: Use GlobalWebSocketManager singleton in the frontend.
  • ES Modules everywhere: All packages use "type": "module". Compiled imports need .js extensions.
  • Never import from dist/: Use @nodetool-ai/<package> workspace references in source code.

TypeScript Rules

Full standards: DEVELOPMENT_STANDARDS §1 TypeScript.

  • Use TypeScript for all new code. Never use any — prefer unknown + narrowing or proper generics.
  • Use const by default, let when reassignment is needed. Never use var.
  • Use strict equality (=== / !==). Exception: == null for null/undefined checks.
  • Always use curly braces for control statements.
  • Use Array.isArray() to check for arrays, not typeof.
  • Throw Error objects, not strings.
  • Always add comments for intentionally empty catch blocks.
  • No // @ts-ignore — use // @ts-expect-error <reason>.
  • No enum in new code — use as const objects + keyof typeof unions.
  • Prefer discriminated unions over optional fields with implicit invariants.
  • Validate untrusted input with Zod at the boundary — see DEVELOPMENT_STANDARDS §11.

React Rules

  • Use functional components only. No class components.
  • Always define a TypeScript interface for component props.
  • Never mutate state directly. Use immutable patterns.
  • Don't use inline functions in JSX when passed to memoized child components.
  • Test behavior, not implementation details.

Hooks

Hook Use When Do Not Use When
useEffect Side effects (network, subscriptions, timers, DOM) Deriving data from props/state
useMemo Expensive computation, referential stability Cheap computation
useCallback Passing to memoized children, dependency of effect/memo Function used only locally
React.memo Pure component, stable props, renders often, expensive Props change every render

Never add these "just in case." If performance is fine, do nothing.

Custom Hooks

  • Always prefix with use.
  • Use descriptive names: useWorkflowActions not useActions.
  • Include all dependencies in useEffect, useCallback, useMemo arrays.
  • Provide TypeScript types for all return values.

Zustand Rules

  • Keep stores focused on a single domain.
  • Use selectors to prevent unnecessary re-renders: useStore(state => state.value).
  • Use shallow equality for object selections.
  • Define actions within the store alongside state.
  • Use persist middleware for settings stored in localStorage.

MUI / Styling Rules

  • MANDATORY: Use UI primitives from web/src/components/ui_primitives/ for all frontend UI. Never import raw MUI components (Typography, Button, IconButton, Tooltip, CircularProgress, Chip, Dialog, Alert, Divider, Paper, Skeleton, Tabs, Drawer, Breadcrumbs, Select, Switch, TextField) directly in component files. These are only allowed inside ui_primitives/ and editor_ui/ where the primitives are defined.
  • See the Primitives Strategy for the full decision tree, migration rules, and 90+ available primitives.
  • When touching any component file, opportunistically migrate raw MUI usage to primitives.
  • Replace display: "flex" / flexDirection patterns with FlexRow / FlexColumn layout primitives.
  • Replace <Typography> with Text, Label, or Caption primitives.
  • Replace <CircularProgress> with LoadingSpinner. Replace <Tooltip> with Tooltip primitive.
  • Use sx prop for one-off styles on primitives. Use styled() only inside ui_primitives/ for defining new primitives.
  • Use theme values for spacing, colors, and typography — never hardcode hex colors or pixel values.
  • Prefer composition over deep prop drilling.
  • If no primitive exists for your use case, create a new primitive in ui_primitives/ rather than using raw MUI.

Design Token Rules (see docs/DESIGN.md for full reference)

Every style value that falls into one of the categories below must use the corresponding token — never hardcode.

Category Forbidden Use instead
Spacing / gap / padding 5px, 10px, 13px, 0.25 theme units SPACING.* / GAP.* / PADDING.*
Font size "14px", "0.85rem", any raw px/rem var(--fontSize*) or <Text>/<Label>/<Caption>
Font weight 700, "bold", 300 400, 500, or 600 only
Border radius 4, 10, 18, "var(--rounded-*)" BORDER_RADIUS.xs/sm/md/lg/xl/xxl/pill/circle
Transitions "all 200ms ease", raw timing strings MOTION.all/border/background/…
Z-index 9999, 1000, arbitrary integers Z_INDEX.dropdown/modal/tooltip/…

TanStack Query Rules

  • Use hierarchical query keys: ['workflows', workflowId].
  • Set appropriate staleTime based on data volatility.
  • Use enabled option for conditional queries.
  • Use optimistic updates for mutations where appropriate.
  • Always invalidate related queries after successful mutations.

File & Naming Conventions

  • Components: PascalCase (MyComponent.tsx)
  • Hooks: camelCase with use prefix (useMyHook.ts)
  • Stores: PascalCase file, camelCase use prefix for hook (useMyStore)
  • Utilities: camelCase (formatDate.ts)
  • Constants: UPPER_SNAKE_CASE (MAX_NODES)
  • Types/Interfaces: PascalCase (NodeData)
  • Tests: Same as source + .test.ts(x), placed in __tests__/ directories

Import Order

  1. React and core libraries
  2. Third-party libraries (MUI, TanStack Query, etc.)
  3. Internal stores and contexts
  4. Internal components
  5. Internal utilities and types
  6. Styles

Testing Rules

  • Use React Testing Library queries (getByRole, getByLabelText).
  • Use userEvent for interactions, not fireEvent.
  • Use waitFor for async assertions.
  • Mock external dependencies and API calls.
  • Test user-facing behavior, not implementation details.
  • Keep tests independent and isolated.

E2E Testing Setup

E2E tests require the TypeScript backend and Node.js frontend. For comprehensive E2E testing documentation, see web/TESTING.md.

# Build the backend packages first (one time)
npm run build:packages

# Install and run
cd web
npm install
npx playwright install chromium
npm run test:e2e           # Automatically starts servers

# Manual setup for debugging
# Terminal 1: PORT=7777 HOST=127.0.0.1 node packages/websocket/dist/server.js
# Terminal 2: cd web && npm start
# Terminal 3: cd web && npx playwright test

In-Browser Workflow Harness

A browser-based graph harness that runs whole workflows against the real backend and renders the actual ReactFlow canvas per workflow, recording IO, traces, and screenshots into a self-contained HTML report. Frontend lives in web/src/e2e_runner/, backend in packages/websocket/src/e2e-server.ts. See web/src/e2e_runner/README.md.

cd web
npm run test:e2e-runner          # headless: boots backend + Vite, runs the suite
npm run test:e2e-runner:headed   # watch it run in a browser

Electron Tests

The Electron workspace has no Playwright suite — the main process is covered by Jest tests in electron/src/__tests__/.

cd electron
npm test

See electron/src/AGENTS.md for Electron-specific testing.

Security

Full standards: DEVELOPMENT_STANDARDS §16 Security and §12 Electron Security.

  • Use DOMPurify.sanitize() for user input rendered as HTML.
  • Never use dangerouslySetInnerHTML with unsanitized input.
  • Use contextBridge for Electron IPC — never expose nodeIntegration.
  • Validate all IPC inputs with Zod before acting on them.
  • No eval, new Function, or setTimeout with string arguments.
  • Secrets never appear in code, logs, or error messages.
  • npm audit must pass — high/critical advisories block merge unless waived with rationale.

Accessibility, Performance, Observability

These three areas have full sections in the central standards doc:

Git, Commits, Pull Requests

Full standards: DEVELOPMENT_STANDARDS §20.

  • Conventional commits: feat(scope):, fix(scope):, etc. Subject ≤ 72 chars, imperative mood.
  • One concept per commit. Body explains WHY, not WHAT.
  • Never --no-verify. Never rewrite published history.
  • PRs are small (target <400 LOC), self-reviewed, and CI-green before review.

Writing & Docs

Full guide: docs/WRITING_STYLE.md. Comment/README rules: DEVELOPMENT_STANDARDS §19.

  • Write prose — docs, READMEs, this file, PR descriptions, comments — concise and concrete. Cut any sentence that survives deletion without losing meaning.
  • No AI slop. Forbidden: leverage, utilize, seamless, robust, powerful, comprehensive, cutting-edge, unlock, empower, streamline, it's worth noting, dive into, rule-of-three padding, "it's not just X, it's Y", emoji decoration, and the rest of the forbidden list.
  • Bold-label bullets must add information beyond the label. Claims are concrete: numbers, names, paths — not adjectives.
  • When you edit a Markdown file, fix slop you pass in the same change. For code, use the unslop skill.

Technologies

TypeScript Backend (packages/)

  • Node.js LTS, TypeScript 5.4+, ES Modules
  • Vitest for testing
  • Key packages: @nodetool-ai/websocket (server), @nodetool-ai/kernel (runtime), @nodetool-ai/cli (CLI)
  • See packages/AGENTS.md for full package list

Web

  • React 18.2.0, TypeScript 5.7.2, Vite 6.4.1
  • MUI v7.2.0 + Emotion, Zustand 4.5.7, ReactFlow 12.10.0
  • TanStack Query v5.62.3, React Router v7.12.0
  • Jest 29.7.0 + React Testing Library 16.1.0, Playwright for E2E

Electron

  • Electron 39.8.8, React 19.1.0, TypeScript 5.3.3
  • Zustand 5.0.3, Vite 6.4.1

Mobile