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
- The first time, run
yarn setup. Then start the app withyarn start. - Open your assistant in the project folder and describe one feature.
- The assistant writes the code, then runs
yarn verifyand fixes what it reports until it passes. - You try the result in your browser, at
http://localhost:5180. - Not sure the assistant checked its work? Run
yarn verifyyourself.
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:
| Check | What it catches | When it runs |
|---|---|---|
| Forbidden words | A word that must never appear in the project | Always |
| Lint and formatting | Breaks of the code rules, and code that isn't formatted | A file in a package changed |
| GraphQL schema and web app types | Regenerates both files; fails when the web app asks the API for a field it doesn't have | The server, the shared code or a web app request changed |
| Translations | Updates the translation files; fails while a French translation is missing, and lists each one | Text changed in the web app, the server or the website |
| Design system generated files | Theme and icon files that are out of date | The design system changed |
| Typecheck | Type errors, in every package the change touches | Always, for the affected packages |
| Unit tests | Tests that fail, in every package the change touches | Always, for the affected packages |
| Database migrations | A table changed in the code without its migration | A 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.mdat the root holds the product decisions, the house rules, the definition of done, the commands and the known pitfalls.AGENTS.mdis 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.mdtells 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 verifylast 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").
| Recipe | For |
|---|---|
start-here | Your 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-feature | A new kind of record, from the database to the page, copied from Projects |
change-database | A new field or table, with its generated migration |
add-page-or-screen | A page, its address, its menu entry, its shortcut and its permissions |
add-translation-or-language | French translations, or a new language |
add-background-job-or-cron | Slow work in the background, or work on a schedule |
connect-clerk-and-stripe | Turning on real sign-in and test payments, with your keys |
deploy-to-production | The 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
- 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.
- 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=frat the end of the address). - Check that it was verified. The assistant's last message should say that
yarn verifypassed. If it doesn't, ask, or run it yourself. - Review the few things machines can't judge (the list is below).
- 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.
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.Add an optional due date to projects, shown in the list and editable in the
side panel.Every night, archive the projects nobody has changed for 90 days.Add a Reports page to the sidebar, with the shortcut g r, that shows how many
projects each status has.Connect Clerk and Stripe: I have my test keys ready.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 verifyfails. Read the To fix line under eachFAIL. Usually you just tell the assistant: "yarn verify fails, fix it." The most common cases:You see It means Lint errors A code rule is broken. Each message says why the rule exists and what to write instead. Not formatted Run the npx oxfmt ...command it prints.Translations missing New text has no French yet. It lists each message: ask the assistant to translate them. An entity changed without a matching migration A table changed. Ask the assistant to generate the migration (never to write it by hand). Unit tests failed The 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. SKIPon database migrations. The local database isn't running. Runyarn setup, thenyarn verifyagain.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, thenyarn verifyagain."Could not build the lint rules" after an edit in Claude Code. Run
npx nx build app-lint-rulesand 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?
NoPermissionGuardmeans "any member may do this": is that true? Is the role mapping inWORKSPACE_PERMISSIONS_BY_ROLEwhat you want? - Lint exceptions. Any new entry under
exemptionsinpackages/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.
.envfiles stay out of git. - New dependencies. A new line in a
package.jsonis 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:
| Rule | What it enforces | Where |
|---|---|---|
graphql-resolvers-should-be-guarded | Every GraphQL query and mutation declares an authentication guard and a permission guard, so no endpoint ships open by accident | server |
rest-api-methods-should-be-guarded | The same for REST routes (@Get, @Post and the others) | server |
prefer-workspace-scoped-repository | Workspace data is read and written only through the workspace-scoped repository, so one customer can't see another's data | server |
enforce-module-boundaries | Packages import only what their tag allows (the web app never imports server code) | all packages |
component-props-naming | A component's props type is named <Component>Props (fixes itself) | web app, website |
styled-components-prefixed-with-styled | Styled components are named Styled<Something> | web app, website |
sort-css-properties-alphabetically | CSS properties are in alphabetical order (fixes itself) | web app, website |
no-hardcoded-colors | No hex, rgb() or hsl() color in code: colors come from the theme, so dark mode works | web app, website |
effect-components | A component that renders nothing ends in Effect, and only those do (fixes itself) | web app, website |
matching-state-variable | State from an atom is named after it: [foo, setFoo] for fooState (fixes itself) | web app, website |
no-state-useref | useRef holds DOM elements only, never state, which would go stale | web app, website |
no-navigate-prefer-link | A click that only navigates uses a <Link>, which supports new tabs and keyboards | web app, website |
max-consts-per-file | One exported constant per file, so each one is found by its file name | web app, website, shared |
types-folder-filename | Files in a types/ folder are named after the type they export | web app, website, shared, server |
no-miscased-acronym-in-identifier | Acronyms are written as words: userId, not userID | all packages |
no-storybook-a11y-disable | Stories never switch off the accessibility checks | design system |
folder-structure | Feature 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:
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.