Ctrl K
ComunidadGuías…

AGENTS.md

Rama master · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# AGENTS.md

Read this top to bottom before writing any code.

## What is this project?

Read `README.md` (one page). It explains the product, where the idea comes
from, and the MCP-first loop. The deployment model is in the install guide on
nexul.io, and the standing principles are at the end of `ROADMAP.md`.

The product is implemented. The work now is improving it domain by domain.

## How to navigate the docs

| You are doing | Read first | Then read |
|---|---|---|
| Anything | `CONTEXT.md` (the vocabulary) | `docs/adr/` for the decisions in your area |
| Picking up work | `.scratch/`, the issue tracker | `docs/agents/issue-tracker.md` for its conventions |
| Go backend | `practices/go.md` | `practices/architecture.md` |
| Frontend (React) | `practices/react-guide.md`, the F1 to F7 commandments are enforced | `practices/design-language.md`, the shared core and the web app section |
| SDK, automations host, desktop | `practices/typescript.md` | `practices/react-guide.md` for the desktop launcher |
| Phone app (React Native) | `practices/native.md` | `practices/react-guide.md`, the rules it inherits; `practices/design-language.md`, the shared core and the phone app section |
| Code both the web and phone apps run (`client-core/`) | `practices/typescript.md`, section 11 | ADR 0139 |
| Design or visual work in `web/`, `website/` or `native/` | `practices/design-language.md`, one language for all three: the shared core, then your surface's section | `practices/react-guide.md`, or `practices/native.md` on the phone |
| Testing, or making something faster | `practices/testing.md` | The language file above |
| Any code | `practices/architecture.md`, Principles | `practices/README.md` for the index |
| Install, CI, release | [CI and releases](https://nexul.io/docs/contributing/ci-and-releases/) | `internal/install/`, `.goreleaser.yaml`, `docker-compose.debug.yml` for development |
| MCP server or a domain's `mcp.go` | `practices/mcp.md` | `practices/architecture.md`, section 8 |
| A list call: an MCP list tool or a paged endpoint | `practices/go.md`, section 17 | `practices/mcp.md`, section 7; ADR 0140 |
| Event bus or resilience | `practices/architecture.md`, sections 2 to 6 | `docs/adr/` |
| Why is it built this way? | `docs/adr/` | The effort's spec in `.scratch/` |
| Unfamiliar with a term | `CONTEXT.md` | (the ubiquitous language) |

The practices files are the standard. The contributing pages on nexul.io are a
digest derived from them, never the other way round.

## Agent skills

This repo is configured for the mattpocock/skills engineering set.

Issues and specs live as markdown under `.scratch/<effort-slug>/`, committed
to the repo. See `docs/agents/issue-tracker.md`.

The five triage labels are `needs-triage`, `needs-info`, `ready-for-agent`,
`ready-for-human`, `wontfix`. See `docs/agents/triage-labels.md`.

`CONTEXT.md` is the glossary, `docs/adr/` is the decision log, and the tracker
holds the specs. See `docs/agents/domain.md`.

`.scratch/pre-release/` holds deferred concerns, each with a "Surface when"
list. If a trigger matches the current task, raise it with the owner before
implementing. Never act on one silently.

## The hard rules

These override everything else. If a practices file says something different,
these win.

1. Read the practices before writing code, every session. The navigation
   table above is a gate, not a suggestion. Before the first edit, read the
   `practices/` files matching the work. Delegated work inherits this: any
   brief that involves writing code names the files to read before editing,
   and they get read there. A summary in the brief does not count.
2. MCP-first, not MCP-only. The browser uses the HTTP/JSON gateway. The MCP
   server is for LLMs. Both are adapters over the same use-case layer, which
   is the single source of behavior.
3. SQLite is the spine. One file, one owner, WAL mode, FTS5. No Postgres. No
   dual-backend abstraction. Semantic search deferred.
4. The EventBus interface is the microservice seam. Domains communicate only
   through it. Today it is in-process channels; a broker adapter can replace
   it without touching a domain.
5. Coverage is a floor (80%), not a goal. Test error paths first. Exempt:
   `cmd/*`, generated code, wire types, `testutil/`, `t3rpctest/`, `components/ui/`.
6. No barrels. No `index.ts` re-exports. Import by full path.
7. Early return, no `else`, in Go and TypeScript alike.
8. Structured logging: `slog` in Go, console with a propagated `trace_id` in
   TypeScript. No `fmt.Println`, no `console.log` in production code.
9. Comments are sparse. The default is no comment; the code and its names
   carry the meaning. A comment exists only to say a why, a warning about a
   non-local consequence, or a pointer to context, and it fits on one line.
   Knowledge that needs more than one line is an ADR if it is a decision,
   otherwise it belongs in the tracker spec for the work. No comment restates
   the code. No change history ("previously", "per PR"). No commented-out
   code. No TODO without a tracking issue in `.scratch/`.
10. Web work obeys the Frontend Commandments. F1 to F7 in
    `practices/react-guide.md` are hard rules for anything in `web/`: Page,
    Feed, Section, Card; defensive ordering; shared display components; `&&`
    over ternary; no gratuitous `useState` or `useEffect`; no prop drilling;
    thin files. Implement against the rule, not against nearby code that
    predates it. Run the self-review checklist before calling web work done.
11. One design language across `web/`, `website/` and `native/`; extend it,
    never re-theme. `practices/design-language.md` is the spec for all three,
    and a screen that disagrees with it is the defect. Tokens only, no
    one-off colours: the one accent is `brand`, held to the roles the spec
    names, and status keeps its own hues. Fraunces is the one serif, for
    display headlines only; no script fonts. The three token files
    (`web/src/index.css`, `website/src/styles/tokens.css`,
    `native/src/global.css`) carry the same names and values; a design that
    needs a missing token adds it to each surface that uses it and to the
    token table in `practices/design-language.md`, never as a one-off class.
12. Tablet and desktop. Build `web/` at 768px first and verify at 768, 1024,
    and 1440px before calling web work done; phones are served by the
    Android app in `native/` (ADR 0080). `website/` stays mobile first: build
    at 320, 375, and 414px and verify at 320, 375, 414, and 768px.
13. Fast by default, and kept fast by a test. Lists page and filter by access
    in SQL, a client refreshes only what a frame names, and a background loop
    waits on the commit broadcast instead of polling. A performance fix ships
    with a before and after number from a production build and a guard test
    that counts something deterministic (`practices/testing.md`, section 10).

## What enforces the rules

A rule a linter can check is checked by a linter, because prose rules get
skipped and lint errors do not.

| Rule | Gate |
|---|---|
| Early return after a return, no `else if` chains, no `fmt.Print` outside `cmd/` (Go) | `revive` (`indent-error-flow`, `superfluous-else`, `early-return`), `gocritic` (`ifElseChain`, `elseif`) and `forbidigo` in `.golangci.yml`; an `else` after a branch that does not return is not caught, so review it |
| No `else` and no `console.log` in TypeScript | `no-restricted-syntax` (`IfStatement[alternate]`) and `no-console` (`warn` and `error` allowed; tests exempt) in `web/eslint.config.js` (covers `client-core/`) and `native/eslint.config.js` |
| Go static analysis, formatting, imports, complexity | `golangci-lint` with `.golangci.yml`, in CI and `make lint` |
| Known-vulnerable Go dependencies | `govulncheck`, in CI and `make vuln` |
| Go coverage floor | `make coverage`, in CI and locally |
| Generated SQL code matches the queries | `sqlc vet` and `sqlc diff`, in CI and `make sqlc-check` |
| Web lint, types, tests, coverage | `bun run lint`, `typecheck`, `test` in `web/`, in CI |
| Native lint, types, tests | `bun run lint`, `typecheck`, `test` in `native/`, in CI |
| SDK and automations host types and tests | `bun run typecheck` and `bun run test` in each package, in CI |
| Desktop types, tests, build | the desktop CI job |
| Every pushed topic has an audience rule, and the topics the server pushes match the ones the web followers follow | `make live-topics`, `TestLiveRules_MatchWhatIsPushed`, `TestLiveTopicsFile_MatchesTheRules` and `web/src/hooks/liveTopics.test.tsx` |
| The clients' typed permission list matches the server's catalog | `make permission-catalog` and `TestPermissionActionsFile_MatchesTheCatalog` in `internal/platform/permissions` |
| Every writing route names the permission it checks, and every web and phone write declares one the route accepts | `make permission-catalog`, `TestRoutePermissions_NameEveryWritingRouteAndOnlyCatalogActions` and `TestRoutePermissionsFile_MatchesTheRoutes` in `server/cmd`, and `client-core/routePermissions.test.ts` |
| Event schemas match the payload types and the published contract only grows | `make event-schemas`, `TestSchemas_MatchThePublishedContract` in `internal/eventcatalog/contract_test.go` |
| HTTP bodies through `jsonx` match the encoder it replaced | `server/cmd/json_parity_test.go` |
| An event payload's empty list is `[]`, never `null` | the contract test above, and `TestInsertOutboxRow_WritesAnEmptyListAsAnArray` |
| A memoised access check answers what an unmemoised one does, and a commit clears it | `TestIntegration_PermissionSuitesAnswerTheSameWithAMemo` and `TestIntegration_LiveAudienceMemo_FollowsEveryChange` in `server/cmd/access_memo_test.go` |
| A list path's access reads stay the same at any length | the `TestStatements_*` guards in `server/cmd/access_memo_test.go` |
| A background loop wakes on a write to every table its pass reads, and on no other | `TestLoopWrites_CoverEveryTableTheLoopsPassReads` and `TestStore_CommitsTo_AMachineTouchWakesNoLoopButTheMemo` in `internal/platform/storage` |
| A paged list tool shows each viewer what the row-by-row check showed, and reads about one page of rows | `TestListTools_ShowWhatTheRowByRowCheckShowed` and the `TestRows_*` guards in `server/cmd/list_paging_test.go` |
| Every non-GET route writes an audit row unless it is listed as read-only | `TestAudited_EveryRouteOfTheRouter_IsClassifiedByMethod` in `server/cmd/audit_integration_test.go` |
| No goroutine outlives a package's tests | `goleak.VerifyTestMain` in each goroutine-owning package's `main_test.go` |
| The web shell loads no page, editor, canvas, voice or shader | `web/src/pageChunks.test.tsx` |
| A gated page's chunk loads only for a viewer who may open it | the gated page chunks cases in `web/src/RouterPermissions.test.tsx` |
| Requests one live frame or one phone session sends | `web/src/hooks/liveRequests.test.tsx`, `native/src/hooks/requestCounts.test.tsx` |
| `client-core/` lint, tests and coverage | the web job: `bun run lint` covers the folder, and vitest runs its tests and counts its code toward the 80% gate; each app's typecheck compiles what it imports |
| A phone query cached until pushed names a topic, and its topics and payload fields exist in the catalog | `bun run typecheck` in `native/`, through the `@ts-expect-error` cases in `native/src/lib/liveQuery.test.ts` |
| A catalog topic reaches exactly the phone queries that declare it | the per-topic table test in `native/src/hooks/useLiveEvents.test.tsx` |
| Phone icons import by path; the SDK's event types import as types only | `no-restricted-imports` in `native/eslint.config.js` |
| Web text sizes come from the type scale, and no component writes its own focus ring | `no-restricted-syntax` in `web/eslint.config.js` |
| Dependency freshness | Dependabot, weekly, grouped per directory |

A rule that could be a lint rule and is not yet is a candidate for one; add
it to the tracker rather than repeating it in review.

## Hit every surface

The most common defect in this repo is a change that works on the path you
tested and is missing everywhere else. Before calling a feature done, walk
this list and say which entries applied:

- UI page or component. The browser flow works at 768, 1024, and 1440px.
- HTTP gateway route. The browser and integrations reach it (ADR 0019).
- MCP tool. Agents are peers of the browser; a capability without a tool is
  half shipped. A list tool pages in SQL through its use-case, filters by
  access in the query, and joins the parity and row guards in
  `server/cmd/list_paging_test.go` (`practices/go.md`, section 17).
- Events. A `Topics()` entry naming the payload type, `make event-schemas`,
  and an outbox write, designed for publication (ADR 0044, ADR 0137). A new
  field goes beside the existing ones, never in place of one, and is
  followed by `make event-schemas`. A client that refreshes too much because
  a frame lacks an id is fixed by adding that id this way.
- Live WebSocket push, if the UI should update without a refresh. A new
  topic gets an audience rule in `liveRules` (that bridges it),
  `make live-topics`, a follower in the web
  domain's hooks file (`practices/react-guide.md`, The live topic contract),
  and a line in the `refreshes` of each phone query that shows the entity
  (`practices/native.md`, section 4).
- Both apps. Code the web and the phone app both need lives once in
  `client-core/`, with its tests beside it (`practices/typescript.md`,
  section 11); each app keeps only its adapter.
- Search, if the entity is indexed.
- Permissions. Enforced through the permission table, not assumed.
- Reverse states. If you added a way in, add the way out and the way to see
  it. Archive needs restore, close needs reopen. A one-way door is a bug.
- Docs. Check whether the change makes an ADR, `CONTEXT.md`, or a practices
  file inaccurate, and fix it in the same change.
- User guide. A feature added or changed updates its page under
  `website/src/content/docs/docs/guide/` in the same change, or adds one.

## Plans and work artifacts

- Do not commit implementation plans, research notes, audit reports, or agent
  scratch files. Temporary working material lives outside the tree or in a
  path listed in `.git/info/exclude`.
- `.scratch/` holds specs and tickets for open efforts, nothing else. When an
  effort ships, its directory is deleted.
- A merged PR is the implementation record. Close its ticket when the work
  lands; do not keep a second checklist anywhere in the repository.
- Nothing shipped names the tool that wrote it. Commit messages, PR bodies,
  comments, and docs describe the change from the code's point of view.

## Branches and pull requests

Parallel work happens in worktrees (`git worktree add ../nexul-<slug> -b
<slug>`). Things the commands do not tell you:

- Remove the worktree when the branch is done; each one is about 65MB.
- Parallel runs share this host. Give each its own dev-server port and say so
  up front, or they collide on the default port and the screenshots come back
  belonging to somebody else's container.
- Kill only a PID you captured at spawn, never by pattern (`pkill -f`,
  `pgrep | kill`): your own session matches the pattern.

A pull request:

- Carries one topic. A description that says "also" is two pull requests.
- Is rebased onto the latest `master` before it opens, so the diff is only
  the change.
- Is squash-merged. Its title is one sentence describing the change from the
  code's point of view, because that sentence becomes the commit on `master`
  and the line in the release notes.
- States the problem in a sentence or two, then how the change fixes it. A UI
  change carries before and after screenshots at 768, 1024, and 1440px (320,
  375, 414, and 768px for `website/`); a change that depends on motion
  carries a short video.
- Never commits a secret. A dev-only shared value is tracked as an open item
  in `.scratch/pre-release/` until it is rotated.

CI runs per service through `dorny/paths-filter`, so only the affected
service's jobs run.

## Code discovery

`rg` is the default and is never stale. Reach for the `codebase-memory-mcp`
server only for structural questions it answers better: `trace_path` for who
calls what, `search_graph` to disambiguate an overloaded name. Reindex
(`index_repository`) before tracing if files moved, not after every new file.

## Quick reference: adding a new domain

1. Create `internal/<domain>/` with `model.go`, `repo.go`, `usecase.go`,
   `handler.go`, `events.go`, and `mcp.go` if it exposes tools.
2. Add the repo implementation in `internal/platform/storage/`, with the
   queries in `internal/platform/storage/queries/<table>.sql` and `make sqlc`.
3. Register the domain's topics with their payload types in its `Topics()`
   function and run `make event-schemas`; the catalog aggregates them
   (`practices/architecture.md`, section 2).
4. Expose the use-cases to agents per `practices/mcp.md`: extend an existing
   tool first, add one only inside the tool budget, named `<object>_<verb>`.
   A list use-case takes its filters and a `paging.Window` (ADR 0140).
5. Add the HTTP routes in `server/cmd/` or a router package.
6. Add the frontend: model, hooks with the domain's live follower,
   components, page, route; on the phone, its `defineQuery` declarations.
7. Add tests: unit (table-driven) and integration (real SQLite).
8. Add the domain's terms to `CONTEXT.md`, and record any decision that was
   hard to reverse, surprising, or a real trade-off as an ADR in `docs/adr/`.

## Quick reference: adding a new service binary

Releases ship native binaries only, never images (ADR 0073).

1. Create `<service>/cmd/main.go` (composition root).
2. Ship it as `nexul-<service>-<os>-<arch>[.exe]` for every release target: a
   Go binary is a build in `.goreleaser.yaml`; anything else is built in
   `.github/workflows/release.yml` and attached through the GoReleaser
   `extra_files` and checksum globs.
3. Teach `internal/install/` to run it: a unit kind, its environment, and
   what `nexul install`, `upgrade`, `status` and `uninstall` do with it on
   systemd, launchd and the Windows service host.
4. For development, add a `<service>/Dockerfile.debug` and a service in
   `docker-compose.debug.yml`.
5. Add a CI job in `.github/workflows/ci.yml` with a `dorny/paths-filter`
   entry.

## When in doubt

- Read `CONTEXT.md`. It defines the project's vocabulary. Use its terms and
  respect its `_Avoid_` lists.
- Read `docs/adr/`. It has the decisions and the trade-offs behind them. If
  your work contradicts one, say so explicitly rather than quietly overriding
  it.
- Read the effort's spec in `.scratch/`. It has the requirements for the work
  in front of you.
- If something is genuinely ambiguous, raise it with the owner and record the
  answer as an ADR if it is a real decision, otherwise in the tracker spec.
- Push back if a rule seems wrong. The docs evolve, but only with a written
  rationale.