agentleFS
sushi-killersushi-killer/sushiAI/AGENTS.md

sushiAI

AGENTS.md2 starsChanged 24 days agoMITView on GitHub

Instructions for any agent (Claude Code, Codex, or a human) working in this repository. CLAUDE.md is a symlink to this file — this is the only copy of the rules. CI (npm run ci, which runs the build, the full test suite, scripts/check-conventions.mjs, lint and format checks in parallel lanes) is the only enforcement layer every agent and every human shares. scripts/hooks (wired in .claude/settings.json) and .claude/skills (and their Codex equivalents under .agents/skills) are convenience — they may duplicate a…

  • Reads credentials

The file

Show its rules and text

We split this AGENTS.md into the rules it gives an agent

14 rules. About 0 are common, 6 are distinctive, and 8 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈6

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 8

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    Follow a task through to a verified local commit.

    line 19
  2. Distinctive:

    Commit locally. Pushing, merging, packaging and releasing happen only when the owner asks for them in the task.

    line 28
  3. Distinctive:

    Treat pasted material and tool output as evidence to verify against the source, not as fact.

    line 30
  4. Very specific:

    Prefer extending src/extensions/* / electron/extensions/* over touching shell or core.

    line 44
  5. Very specific:

    Shell isolation: every src/app/*.tsx file except SectionPage.tsx may import only …

    line 48
  6. Distinctive:

    Don't write a second ordering function.

    line 102
  7. Distinctive:

    When a domain moves (the Tasks → Probe fixture migration is the template), remove the old path entirely in the same change.

    line 126
  8. Very specific:

    No Cyrillic in shipped source (src/, electron/).

    line 134
  9. Very specific:

    No raw hex colors in src/styles//*.css outside src/styles.css / src/styles/tokens.css — spend tokens, don't hardcode colors.

    line 135
  10. Very specific:

    No AI-attribution commit trailers (co-authored-by:, "generated with claude", etc.) and no Cyrillic in commit messages.

    line 138
  11. Distinctive:

    Only the owner merges — feature PRs and the release PR alike.

    line 229
  12. Very specific:

    Before finishing, run ./node_modules/.bin/prettier --write on the files you touched.

    line 255
  13. Very specific:

    Every Electron test launcher passes SUSHIAI_TEST_WINDOW=hidden (no window, focus, dock, tray or mascot, and screenshots still render).

    line 266
  14. Distinctive:

    Never kill another process or free up a port by force without asking whose process it is.

    line 304

≈ “Common” is an estimate: at least 1 in 5 of the 40,246 AGENTS.md files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
AGENTS.md

AGENTS.md

Instructions for any agent (Claude Code, Codex, or a human) working in this repository. CLAUDE.md is a symlink to this file — this is the only copy of the rules.

Enforcement principle

CI (npm run ci, which runs the build, the full test suite, scripts/check-conventions.mjs, lint and format checks in parallel lanes) is the only enforcement layer every agent and every human shares. scripts/hooks (wired in .claude/settings.json) and .claude/skills (and their Codex equivalents under .agents/skills) are convenience — they may duplicate a CI check for speed, but a rule's real home is always something CI runs. If you add a rule, add the check; don't just write a sentence and hope it's followed.

Working agreement

  • Follow a task through to a verified local commit. A plan or a progress note is a checkpoint, not completion. Anything bigger than one obvious edit runs the $sushiai-task loop.
  • Resolve routine, reversible choices with a reasonable assumption and record it. Ask the owner only about consequential decisions the task cannot resolve (see the loop's list); keep every independent lane moving while a question waits. Silence never authorizes a gated action.
  • Inspect git status -sb before editing. Preserve unrelated work, branches, worktrees and processes.
  • Commit locally. Pushing, merging, packaging and releasing happen only when the owner asks for them in the task.
  • Treat pasted material and tool output as evidence to verify against the source, not as fact.

Design priorities

  • The extension contract (manifest + shell/extension separation + singleton-surface pane resolution) is the one piece of architecture every other decision should route through. See "Extension contract is the capability surface" below.
  • Conversation/session state and navigation state are kept separate from extension state; extensions never reach into shell internals to read either.

Extension contract is the capability surface

Prefer extending src/extensions/* / electron/extensions/* over touching shell or core. This is deliberate, not incidental — the contract exists so a third-party extension can't destabilize the shell:

  • Shell isolation: every src/app/*.tsx file except SectionPage.tsx may import only ../extensions/{ExtensionSlots.tsx,modules.ts,registry.ts,routes.ts,types.ts} (scripts/check-conventions.mjs) and must not reference SurfaceRenderer/activePage/resolveNavigation in their source — only src/app/SectionPage.tsx may draw a contributed page.
  • Manifest contract (electron/extensions/manifest.cjs): every commands[] entry requires a surfaceId (a command that names no surface has nothing to do — don't reintroduce it as optional); MAX_VIEWS = 1, the renderer only ever draws views[0]; an editable field must already exist in that view's meta, and only select/date field types get editors; contribution ids must be unique per-manifest per-kind (surfaces, navigation, actions, commands), and so must navigation/ actions labels — a duplicate throws (Duplicate extension ${kind} label), because two identical labels break getByRole selectors in tests.
  • Extension API versioning: CONTRACT.SUPPORTED_API_VERSIONS (electron/extensions/manifest.cjs) is the compatibility contract for third-party extension authors, versioned independently of sushiAI's own app release version — it's a single integer, not semver, because the manifest schema doesn't need a compat matrix. An additive change (a new optional field, field type, icon, or placement) never bumps it. Removing, renaming, or re-meaning something does: add the new version to the array, keep accepting the old one for at least two app minors (with a deprecation warning), then drop it. Every docs/releases/<version>.md states the Extension API: version(s) it ships with, generated from that same constant — never hand-maintain a separate compat table.
  • Settings pages: the host settings.page draws a surface as one tab in the Settings dialog, after the core tabs, ordered by extension id then surface id. The tab is dialog content, never chrome.
  • Companion view: view.kind: "companion" is allowed only on settings.page, and only when the manifest has a companion block. It has at most 8 fields (types text, status, qr) and at most 4 actions (method matches /^[a-z][a-z0-9.]*$/, labels unique). At most one field is a list (rows with a status and an optional button, optional method). An action with send: ["hosts"] needs hosts.read in companion.permissions; the app then sends the ssh host list (id, name, host, port?) as params.hosts, and hosts.changed on start and on change. hosts.exec (needs hosts.read) lets the companion ask the app to run argv on a saved ssh host; every call needs its own in-app owner card showing the exact argv (no standing allowance). Values, argv and output are never stored or logged. See docs/extensions/companion.md.
  • Internal APIs (built-ins only, not the contract): the Module UI API (src/extensions/modules.ts: attention items, worktree claims, the shell hook, panelMigrations) and the Notices API (electron/extensions/notices.cjs) let a built-in module add rows, claims and notices without core naming it. They have no manifest field and no API version impact. A third-party extension cannot use them.
  • Navigation: toggleSection (src/app/navigation.ts) forces mode: "Code" when opening an extension page, because Agent/Chat modes own the sidebar and would go blank if a page opened there; closing a section leaves mode untouched, implicitly restoring whatever was active before.
  • Ordering: compareOrder (src/extensions/registry.ts) is the single sort used for both navigation and actions contributions — order first, then extensionId.localeCompare, then id.localeCompare. Don't write a second ordering function; extend this one.
  • Singleton surfaces: enforced in resolvePaneOpen() (src/extensions/routes.ts) — a surface declaring instancePolicy === "singleton" reveals its existing pane instead of opening a second one. This is a routing-layer guarantee, not a renderer one; SurfaceRenderer/ExtensionSlots just draw whatever pane they're given.
  • Pure helpers: record filtering/sorting/display logic lives in src/extensions/records.ts (seedForFilter, text, truthy, display, toneOf, compareRecords, bucketOf, orderBuckets). relativeDate/dueBucket are deliberately kept module-internal — don't export them "for reuse" without a second caller that actually needs them.

Core stays generic

Core exposes generic module and extension APIs only. Core, protocol and CI never name a specific module or plugin. Modules attach at the composition point: crates/sushiai (bin), src/extensions/coreViews.ts, src/extensions/modules.ts and electron/main.cjs. scripts/check-crate-deps.mjs enforces the Rust side (a core crate never depends on a module crate; a module crate never depends on a core crate; only the composition bin depends on both).

One owner, complete cutover

When a domain moves (the Tasks → Probe fixture migration is the template), remove the old path entirely in the same change. Don't leave a compatibility shim, a re-export, or an "old and new" fork — one owner, one final shape.

Runtime and code safeguards

scripts/check-conventions.mjs already enforces, in CI:

  • No Cyrillic in shipped source (src/, electron/).
  • No raw hex colors in src/styles/**/*.css outside src/styles.css / src/styles/tokens.css — spend tokens, don't hardcode colors.
  • The shell/extension import isolation described above.
  • No AI-attribution commit trailers (co-authored-by:, "generated with claude", etc.) and no Cyrillic in commit messages.
  • A PR's commits carry no personal address: author and committer are a GitHub noreply or a tool's local address.
  • Tests wait on a real signal and never time themselves: no fixed sleep and no wall-clock bound (Date.now() - t < N, elapsed() < D) in tests/ or Rust test code, unless the line says // timing: <reason>.

On top of that: npm run lint (ESLint, eslint.config.mjs) catches React-hooks misuse (rules-of-hooks, exhaustive-deps) and general TypeScript issues; tsc --noEmit runs with strict: true plus noUnusedLocals, noUnusedParameters, noFallthroughCasesInSwitch; prettier --check enforces formatting. All of these are steps in npm run ci — don't treat any of them as optional.

Security boundary

Extension folders carry no code: a local extension is a size-capped manifest.json (electron/extensions/local-extensions.cjs, MAX_MANIFEST_BYTES). The renderer runs with contextIsolation: true, sandbox: true, nodeIntegration: false (electron/main.cjs). An extension describes UI and data; it never ships executable code.

An optional top-level companion names a native command that is installed outside the extension folder. The app resolves the command to a path and refuses a path inside the extension folder. The process starts only when the extension is enabled, the app is ready, and the owner has approved the resolved path, the args and the permissions together. The approval has no binary hash (owner decision 2026-10-07), so an update of the program does not ask again. A change of path, args or permissions asks again. The process gets a minimal environment allowlist (PATH, HOME, USER, LANG, TMPDIR, SUSHIAI_HOME), restarts with a bounded backoff, and stops with SIGTERM, then SIGKILL after a grace period. This is consent, not a sandbox: the process keeps the owner's OS rights. The hosts.read permission limits only what the app sends it. The hosts.exec permission (owner decision 2026-10-07) lets it run commands on saved ssh hosts through the app, each call behind an owner card. The card is consent against an honest or buggy companion, not a sandbox: an approved companion already has the owner's OS rights (it could use ~/.ssh itself), so the protection against a malicious one is not approving it. Any change that lets an extension carry its own code, or widens the companion, needs the owner first.

Vision & boundaries

sushiAI is meant to be a plugin-extensible platform for AI-agent workflows: open-source, customizable, with a monetized core. As understood today:

  • src/extensions/* and electron/extensions/* (the contract surface) are free to extend — that's the whole point of the architecture.
  • Structural changes to src/app/*, electron/main.cjs, or the manifest validator itself are closer to the monetized core — ask before changing these, don't assume.

This boundary will get more precise as monetized surfaces actually get built; don't invent detail beyond what's stated here.

Product and validation — Definition of Done

Inside an orchd task (ORCHD_TASK is set) the task brief is the definition of done: orchd runs the checks, reviews and commits, and the lead session owns release fragments and docs/LESSONS.md. The list below is for the lead.

  • npm run ci is green.
  • Desktop smoke (npm run test:desktop) was run if src/app, src/extensions, or electron/ changed.
  • UI changes were actually looked at (a screenshot, or a description of what was visually checked) — green tests do not prove a screen looks right.
  • A docs/releases/unreleased/*.md fragment exists for any user-visible change (see "Release and branching" below).
  • docs/LESSONS.md has a new entry, or the session explicitly states there was no lesson worth recording.

Release and branching

Merges are squash-only: a PR's title becomes the literal commit message on main, which is what scripts/release.mjs reads to compute the next version. This makes the branch prefix and PR title a real contract, enforced by scripts/check-conventions.mjs in CI, not just a naming convention:

  • Branch names: feature/<slug> (pairs with a feat: title), fix/<slug> (pairs with fix: ), chore/<slug> (pairs with anything else — docs, refactor, test, ci, perf, chore), release/<slug> (reserved for scripts/release.mjs, pairs with chore(release): ).
  • A breaking-change title (feat!: , fix(scope)!: , ...) must ship with a docs/releases/unreleased/*.md fragment — CI fails otherwise. sushiAI is pre-1.0: a breaking app change may land in any 0.x minor; the fragment is what actually protects anyone depending on the app, not the version number.
  • Any user-visible change gets a docs/releases/unreleased/<slug>.md fragment when its PR is opened. scripts/release.mjs folds every pending fragment into docs/releases/<version>.md at release time and deletes them — don't hand-write a dated release note directly.
  • Only the owner merges — feature PRs and the release PR alike. This repo does not auto-merge anything. GitHub branch protection requiring a PR before merging main is the first layer, but it can't tell the owner's own click apart from an agent invoking gh/git with the owner's own credentials — scripts/hooks/guard-bash.mjs denies gh pr merge and a direct push to main for the realistic case (the agent just running the command). That is a heuristic tripwire, not an unbypassable sandbox — it scans command text and, for a direct interpreter invocation, the script file it names; a hand-obfuscated command or a deeper indirection could still slip past it. The actual guarantee is that an agent must never deliberately try to route around this policy (a different tool, a rewritten command, a subagent) — it isn't that no string could ever evade the regex.
  • Opening a PR follows the ship-pr skill and cutting a release follows cut-release - procedure around the CI/scripts/release.mjs machinery above, not a replacement for it. Both are manual-only: the owner starts them.
  • Squash-only is a main-history invariant, not just a PR-merge-button setting: if a change ever has to land on main outside the normal pipeline (a rare, explicitly-approved bootstrapping exception, not routine practice), squash it to the intended granularity yourself first — don't push a branch's raw commit-by-commit history as-is just because it happens to fast-forward cleanly.

Execution gotchas

  • Before finishing, run ./node_modules/.bin/prettier --write on the files you touched; prettier --check in CI fails otherwise. the Rust workspace is tested by npm run test:daemon (cargo-nextest when installed, else cargo test --workspace; one package: cargo test -p sushiai-orch <filter>). src/App.tsx is capped at 600 lines (tests/app-boundary.test.cjs) - move logic out instead of growing it.
  • Screenshots of the built app come from the ui-evidence scripts: .agents/skills/ui-evidence/scripts/<screen>.mjs (orchestrator panel, notices, worktrees, mascot) or a copy of driver-template.mjs; each needs npm run build first.
  • npm run dev is not a build. npm run package overwrites release/mac-arm64 with no backup — confirm before running it.
  • Every Electron test launcher passes SUSHIAI_TEST_WINDOW=hidden (no window, focus, dock, tray or mascot, and screenshots still render); a check-conventions rule enforces it.
  • webUtils.getPathForFile() returns empty paths for drag-and-drop File objects on Electron 30-33 (electron/preload.cjs is the only place it's used) — this is a known upstream Electron regression, not an app bug; don't re-diagnose it as one.
  • Wherever visual drag state is tracked, onDragLeave needs !event.currentTarget.contains(event.relatedTarget) before clearing it, or the highlight flickers on every child-boundary crossing (src/ChatView.tsx, src/WorkspacePanels.tsx are the canonical examples). src/agents/AgentsView.tsx deliberately skips this because it filters on dataTransfer.types instead and never tracks visual drag state — also valid, just a different strategy.
  • Tests prove logic, never prose: no test reads README, AGENTS.md or docs/**/*.md to assert its wording (scripts/check-conventions.mjs; the one exception is tests/docs-map.test.cjs, which keeps docs/MAP.md a complete and valid map of the repository).
  • One selector picks the checks a change needs, in CI, in npm run ci and in node scripts/test-rust.mjs --affected alike (scripts/ci-changes.mjs): documents alone run only the static checks; a crate change runs the Rust test binaries that can see it (crate graph from the manifests, scripts/lib/rust-graph.mjs) and the node tests that drive the Rust side; any other code change runs every node test. The build and test setup (lockfiles, toolchain, .config/, test helpers, the CI scripts) and a push to main run everything; CI_ALL=1 npm run ci does too. A new test of the sushiai binary starts with a //! ci-crates: <crates it drives> line (tests/ci-pipeline.test.cjs fails otherwise); scripts never name a module.
  • Tests are node --test .cjs files importing .ts sources directly via Node's type-stripping (Node ≥22.18) — no type-checking happens at test time, so test assertions have to catch real behavior, not just satisfy the type checker.
  • tsc only type-checks src/ (tsconfig.json include) — electron/ and tests/ get no static typing at all; ESLint covers those directories separately.

Authority and safety

  • Never kill another process or free up a port by force without asking whose process it is.
  • Extensions/plugins render inside the app's own window — never take over chrome (the window frame, the left navigation, etc.).
  • Shipped source and commit messages are English only, with no AI-attribution trailers (already enforced by CI — this is documentation of that policy, not a new one).

Task loop, roles and skills

The owner gives a task and leaves; $sushiai-task carries it to a local commit and a report whose only questions are the ones the owner must answer. Roles are subagents in .claude/agents/, each with a deliberate model:

RoleModelJob
main session—lead: scope, briefs, integration, commit, report
implementersonnetone bounded code lane and its tests
functional-qasonnetproof against the acceptance criteria
revieweropusfresh review of the diff before commit
design-criticopusjudgement of screenshots when a screen changes

Skills live in .agents/skills/<name>/SKILL.md (Codex reads them there), each linked from .claude/skills/. The harness loads a skill from its description - nobody invokes one by name, so a description that does not say when to use the skill is a bug in that description. Detail a skill needs only on one branch goes in its references/, helpers in its scripts/. ship-pr and cut-release are manual-only (disable-model-invocation: true): they push, so the owner starts them. scripts/check-conventions.mjs checks skill and agent frontmatter, the .claude/skills links, and every $-prefixed skill reference.

The exit code is the verdict, never a summary line: verify with npm run ci or ./node_modules/.bin/<tool>, never npx. The owner merges.

Read when relevant

  • docs/orchd-approach.md — before any change to crates/sushiai-orch: judgement is an agent turn, code keeps gates, bookkeeping and fast paths.
  • $sushiai-task — the loop every nontrivial task runs; $sushiai-testing picks proof; $ui-evidence measures and photographs the built UI; $deslop then $autoreview clean and review a diff before commit.
  • docs/MAP.md — where code, rules and docs live; start here to find the right file in one or two lookups.
  • docs/architecture.md — Mermaid map of the app, the orchestrator, the task lifecycle and the references we borrowed from; update it in the same commit as a change that moves a box or an arrow.
  • docs/AGENTS-INTEGRATION.md — documents the in-app "Agents" feature (the product surface for running agents inside sushiAI). Unrelated to this file; don't confuse the two.
  • docs/INSTALL.md — local setup.
  • docs/releases/ — per-release notes.
  • docs/LESSONS.md — accumulated gotchas from past sessions, kept as a capped queue, not an archive.
  • .agents/skills/extension-manifest-change/SKILL.md — the edit-validator / sync-types / update-tests loop for manifest contract changes.
  • .agents/skills/author-and-verify-extension/SKILL.md — scaffold, validate, and smoke-test a new extension end to end. Frontmatter in these SKILL.md files beyond name/description is Claude-specific and silently ignored by Codex reading the same file.
  • .agents/skills/ship-pr/SKILL.md — branch, fragment, verify, and open a PR with the right branch/title pairing.
  • .agents/skills/cut-release/SKILL.md — run scripts/release.mjs and open the release PR.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.