Skip to content
Working with AI assistants

Working with AI assistants

Build Neatship by describing features to Claude Code, Cursor or Codex: what the assistant checks and fixes on its own, the one command that says a change is done, example requests, and what to do when something fails.

Neatship is made to be built with an AI coding assistant: Claude Code, Cursor, Codex or another one. You describe what you want in plain words. The assistant writes the code, checks it against Neatship's rules, and fixes what fails before it tells you it's done. You don't need to know the rules.

This guide explains what the assistant does on its own, what is left to you, which requests work well, and what to do when something fails.

The short version

  1. The first time, run yarn setup. Then start the app with yarn start.
  2. Open your assistant in the project folder and describe one feature.
  3. The assistant writes the code, then runs yarn verify and fixes what it reports until it passes.
  4. You try the result in your browser, at http://localhost:5180.
  5. Not sure the assistant checked its work? Run yarn verify yourself.

The one command: yarn verify

yarn verify is the definition of done. A change is finished when it ends with All checks passed.

It checks only what changed compared to main: what you committed on your branch, what isn't committed yet, and new files. The quickest checks run first:

CheckWhat it catchesWhen it runs
Forbidden wordsA word that must never appear in the projectAlways
Lint and formattingBreaks of the code rules, and code that isn't formattedA file in a package changed
GraphQL schema and web app typesRegenerates both files; fails when the web app asks the API for a field it doesn't haveThe server, the shared code or a web app request changed
TranslationsUpdates the translation files; fails while a French translation is missing, and lists each oneText changed in the web app, the server or the website
Design system generated filesTheme and icon files that are out of dateThe design system changed
TypecheckType errors, in every package the change touchesAlways, for the affected packages
Unit testsTests that fail, in every package the change touchesAlways, for the affected packages
Database migrationsA table changed in the code without its migrationA table definition (entity) or a migration changed; needs the local database running

It takes from a few seconds (a small change) to a few minutes (a change to the shared code, which every package uses).

At the end you see one line per check: PASS, FAIL, SKIP (it couldn't run, with the reason) or - (not needed for this change). Each FAIL comes with the errors and a To fix line. When yarn verify regenerated files (the GraphQL schema, the translation files), it lists them: keep them with your change, because CI checks them.

yarn verify:full checks everything instead, like CI: every package, plus the builds, the API integration tests, and the end-to-end tests and the design system's story tests in a real browser. Run it before a release. It needs the local database and Redis (yarn setup), and once, the test browser: npx playwright install chromium.

What the assistant does on its own

It learns the rules

  • CLAUDE.md at the root holds the product decisions, the house rules, the definition of done, the commands and the known pitfalls. AGENTS.md is the same file, for assistants that look for that name.
  • docs/conventions/ holds the full rules per area (stack, code style, front end, back end, design system, testing, CI, security, sign-in and billing). CLAUDE.md tells the assistant to read the right one before working in an area.
  • The Projects feature is a complete example to copy: database table, API, page, permissions, translations and tests. Assistants follow an example more reliably than a written rule.

Claude Code

Everything is set up in .claude/settings.json, committed with the project:

  • After every file it edits, a hook checks that one file with the lint rules and the formatter, usually in less than a second. If something is wrong, Claude Code gets the errors straight away and fixes them before it moves on.
  • Before it ends its turn, a second hook looks at what changed. If files changed since yarn verify last passed, Claude Code must run it first. It asks once per turn: if a check can't pass (the database is stopped, for example), the assistant tells you which check and why. A turn where you only asked a question is never affected.
  • Recipes in .claude/skills/ walk it through common tasks, step by step. It picks the right one by itself; you can also name it ("use the add-feature skill").
RecipeFor
start-hereYour first session: the whole project explained, the first run on your computer, then the path from the starter to your own app in production. Type /start-here
add-featureA new kind of record, from the database to the page, copied from Projects
change-databaseA new field or table, with its generated migration
add-page-or-screenA page, its address, its menu entry, its shortcut and its permissions
add-translation-or-languageFrench translations, or a new language
add-background-job-or-cronSlow work in the background, or work on a schedule
connect-clerk-and-stripeTurning on real sign-in and test payments, with your keys
deploy-to-productionThe deploy guide and its checklist, in order

Each recipe follows a guide you can read too: Add a feature, Translations, Background jobs and webhooks, Authentication, Billing and Deploy.

The settings also let Claude Code run yarn verify and the formatter without asking you each time. The first time you open Claude Code in the folder, it asks whether you trust it: answer yes. Until you do, Claude Code ignores these permissions and asks before each check. If you only run it without a window (claude -p), open it once normally first to answer that question.

Cursor

Cursor reads the rules in .cursor/rules/: the house rules and the definition of done always, plus the server rules when it edits server files, the web rules for the web app and the website, and the design system rules for packages/app-ui. Cursor has no automatic checks here: the rules tell it to lint each file it edits and to run yarn verify before it says it's done. Ask it to show you the end of the output.

Codex and other assistants

They read AGENTS.md, which tells them to run yarn verify before they say they're done. To use a recipe, point them to it: "follow .claude/skills/add-feature/SKILL.md".

What you do

  1. Describe the feature. Say who uses it, what they see, what they can do, and who may not (the roles are admin and member). One feature per request.
  2. Try it in the browser, the way your customers will: the normal case, then mistakes (an empty name, a name already used), then in French (?locale=fr at the end of the address).
  3. Check that it was verified. The assistant's last message should say that yarn verify passed. If it doesn't, ask, or run it yourself.
  4. Review the few things machines can't judge (the list is below).
  5. Commit and open a pull request. CI checks everything again before you merge.

Requests that work well

Say what you want and who may do it. Name the example to copy when there is one.

text
Add a Clients feature, like Projects: a name, an email and a phone number.
Members can create and edit clients; only admins can delete them.
text
Add an optional due date to projects, shown in the list and editable in the
side panel.
text
Every night, archive the projects nobody has changed for 90 days.
text
Add a Reports page to the sidebar, with the shortcut g r, that shows how many
projects each status has.
text
Connect Clerk and Stripe: I have my test keys ready.
text
Explain, with file paths, what happens between a click on "Delete" in the
project side panel and the row being deleted in the database.

The last one is a good way to learn the codebase: the assistant reads the code and explains it, and changes nothing.

When something fails

  • yarn verify fails. Read the To fix line under each FAIL. Usually you just tell the assistant: "yarn verify fails, fix it." The most common cases:

    You seeIt means
    Lint errorsA code rule is broken. Each message says why the rule exists and what to write instead.
    Not formattedRun the npx oxfmt ... command it prints.
    Translations missingNew text has no French yet. It lists each message: ask the assistant to translate them.
    An entity changed without a matching migrationA table changed. Ask the assistant to generate the migration (never to write it by hand).
    Unit tests failedThe change broke something a test protects. If the behaviour changed on purpose, the test is updated; a test is never deleted to make it pass.
  • SKIP on database migrations. The local database isn't running. Run yarn setup, then yarn verify again.

  • The assistant goes round in circles on the same error. Stop it and ask it to explain the error in plain words. Then start a new session with a smaller request.

  • It says it's done, but the page doesn't work. Describe exactly what you see (the error message, a screenshot), and ask it to fix it and to add a test that reproduces the problem.

  • Strange type errors after switching branches. Run npx nx build app-shared --skip-nx-cache, then yarn verify again.

  • "Could not build the lint rules" after an edit in Claude Code. Run npx nx build app-lint-rules and give the error to the assistant.

What to review yourself

The checks catch broken rules. They can't judge intent. Before you merge, look at:

  • Permissions. Does each new action require the right permission? NoPermissionGuard means "any member may do this": is that true? Is the role mapping in WORKSPACE_PERMISSIONS_BY_ROLE what you want?
  • Lint exceptions. Any new entry under exemptions in packages/app-server/.oxlintrc.json, and any comment that switches a lint rule off. Each needs a real reason.
  • Migrations. Read the generated file in packages/app-server/src/database/typeorm/core/migrations/. It must match what you asked for, and must not delete a column or table you still need.
  • Sign-in, billing and webhooks. The server trusts only Clerk's verified token and Stripe's signed webhooks. Access is never granted from the checkout success page.
  • Secrets. No key in the code, in tests or in a committed file. .env files stay out of git.
  • New dependencies. A new line in a package.json is new code you now depend on.
  • Words on screen. English in sentence case, buttons that start with a verb, French that reads naturally.

Warning: Never let an assistant change production by hand: no manual SQL, no one-off scripts. A data fix is a command or a migration in the code, reviewed and deployed like any other change. Give assistants read-only database access only.

The rules the machines enforce

Every rule a machine can check is checked: by lint, by the type checker, or by a test. Neatship adds 17 rules of its own to the linter (oxlint), in packages/app-lint-rules, under the app/ prefix:

RuleWhat it enforcesWhere
graphql-resolvers-should-be-guardedEvery GraphQL query and mutation declares an authentication guard and a permission guard, so no endpoint ships open by accidentserver
rest-api-methods-should-be-guardedThe same for REST routes (@Get, @Post and the others)server
prefer-workspace-scoped-repositoryWorkspace data is read and written only through the workspace-scoped repository, so one customer can't see another's dataserver
enforce-module-boundariesPackages import only what their tag allows (the web app never imports server code)all packages
component-props-namingA component's props type is named <Component>Props (fixes itself)web app, website
styled-components-prefixed-with-styledStyled components are named Styled<Something>web app, website
sort-css-properties-alphabeticallyCSS properties are in alphabetical order (fixes itself)web app, website
no-hardcoded-colorsNo hex, rgb() or hsl() color in code: colors come from the theme, so dark mode worksweb app, website
effect-componentsA component that renders nothing ends in Effect, and only those do (fixes itself)web app, website
matching-state-variableState from an atom is named after it: [foo, setFoo] for fooState (fixes itself)web app, website
no-state-userefuseRef holds DOM elements only, never state, which would go staleweb app, website
no-navigate-prefer-linkA click that only navigates uses a <Link>, which supports new tabs and keyboardsweb app, website
max-consts-per-fileOne exported constant per file, so each one is found by its file nameweb app, website, shared
types-folder-filenameFiles in a types/ folder are named after the type they exportweb app, website, shared, server
no-miscased-acronym-in-identifierAcronyms are written as words: userId, not userIDall packages
no-storybook-a11y-disableStories never switch off the accessibility checksdesign system
folder-structureFeature folders are kebab-case, at most 5 levels deep, with hooks in hooks/ and utils in utils/web app, website

Built-in oxlint rules add more: no any, no console.log, no ../ imports, icons only from app-ui/icon, and the React hooks rules. Warnings count as errors: a rule is either enforced or deleted.

Each message says what to do and why, so the assistant can fix it without help. For example, when a new mutation has no permission guard:

text
GraphQL resolver `archiveClient` has no permission guard. Add a *PermissionGuard (for example SettingsPermissionGuard(...)) with @UseGuards on the method or its class: every endpoint must state which permission it requires. Use NoPermissionGuard to declare explicitly that none is needed.

The types help too: every switch over a list of values ends with assertUnreachable(value), so when you add a value (a plan, a language, an error code), the compiler lists every place that must handle it.