Start here

AI assistants

This library's failure modes are, almost exactly, the mistakes a language model makes by default. That is not a coincidence — and it is fixable by handing your assistant the rules up front, in the file it already reads.

Why this library specifically

A model writing TypeScript reaches for the form that is correct nearly everywhere:

@ParseField({ type: 'String', required: true })
title!: string;                    // correct in every other library

Here it is wrong, and wrong in the worst way. @ParseField installs an accessor on the prototype; title!: string emits a real class field that shadows it. Reads return undefined, writes never reach Parse's attribute store, and save() sends nothing. The build passes. The request returns 200. The row is empty.

The same shape recurs across the library — a reversed decorator pair that silently disables a transaction, an implementACL call with the arguments a reasonable person would guess, an Array field missing its targetClass. An assistant cannot infer any of it, because in each case the wrong answer is the one the rest of the ecosystem taught it.

Which is why the instructions are worth shipping These are not style preferences that a linter would catch anyway. They are the handful of rules where being wrong produces no error at all — so a written rule is the only thing standing between the model and a silent bug.

Getting them

psk new asks, before it writes anything:

  Which AI coding assistants do you use?

   • 1  Claude Code       CLAUDE.md, plus two skills and a review agent
     2  Cursor            .cursor/rules/parse-server-kit.mdc
     3  GitHub Copilot    .github/copilot-instructions.md
     4  Windsurf          .windsurf/rules/parse-server-kit.md
     5  Gemini CLI        GEMINI.md
     6  AGENTS.md         the cross-tool convention
     0  none

  Numbers, comma separated (6)

Pick several — most people use two. The default is AGENTS.md alone, because it is the convention several tools already read and it costs one file. Anything tool-specific should be a directory you asked for, not one that appeared in your repository unannounced.

A project that already exists

Skipped the question, or changed tools since? psk ai writes into the project you are standing in, taking its name from package.json:

psk ai                      # asks, same list
psk ai claude cursor        # or name them
psk ai claude --force       # replace what is there

Existing files are never replaced without --force. A project's CLAUDE.md is usually hand-edited by the time you run this, and quietly overwriting it would be the rudest thing a generator could do.

Unattended

psk new my-api --ai=claude,agents -y
psk new my-api --ai=none -y            # nothing written

--ai= wins over the prompt, and a non-interactive shell takes the default rather than hanging — so this works in CI without threading --yes through everything.

What each tool gets

Assistant--aiFiles
Claude Codeclaude CLAUDE.md
.claude/skills/parse-model/SKILL.md
.claude/skills/parse-endpoint/SKILL.md
.claude/agents/parse-reviewer.md
Cursorcursor.cursor/rules/parse-server-kit.mdc
GitHub Copilotcopilot.github/copilot-instructions.md
Windsurfwindsurf.windsurf/rules/parse-server-kit.md
Gemini CLIgeminiGEMINI.md
Anything elseagentsAGENTS.md

One body of rules, written once, placed wherever your tool looks. The tools disagree about the filename and almost nothing else — Cursor wants frontmatter deciding when the rule attaches (set to always, since these are not situational), and Claude Code takes skills and agents as well as project instructions.

What is in them

Ten rules, each one a silent failure, each shown with the wrong form next to the right one — a rule stated in the abstract is easy to misapply:

RuleWhat happens when it is broken
declare, never !:Field reads as undefined, writes are discarded
@CloudFunction above @Transactional()The transaction never opens
implementACL returns an ACLWill not compile — the one loud failure in the list
Triggers need @ParseClassThe trigger never registers
Build requests with fromParamsManual set() calls drift from the model
Never trust the body for identity or moneyThe client picks its own price, or its own owner
GET parameters are stringslimit of "20" compares wrong
Cast query resultsTyped properties unavailable
targetClass on Pointer, Relation and ArrayAn array of ids stays an array of strings
@ParseVersionField declares its own fieldDuplicate declaration

Plus this project's conventions — catchError over try/catch, roleKey() over literal 'role:Admin', the method name being the route, and the fact that nothing needs registering.

Knowing what exists

The rules file also carries a one-line index of every export — models, endpoints, all 21 trigger types, cron, permissions, validation, transactions, locking, indexes, schema, middleware, config, the role cache, Swagger and the utilities. Not the signatures; just the names, so an assistant reaches for the built-in instead of reinventing it. It cannot use syncImageAcl if it does not know the function is there.

A test in this repository fails if an export is added without appearing in that index, so it cannot quietly fall behind.

Skills and the review agent

Claude Code gets eight more files, because it can load instructions on demand rather than carrying everything in context. That split is deliberate: the rules file is read on every request, so breadth lives in the skills — otherwise the ten rules that always matter get buried under material that applies once a month.

FileLoads whenThe trap it covers
parse-modelAdding or changing a modelField shadowing; targetClass on arrays
parse-endpointAdding an endpointGET params as strings; client-supplied identity
parse-permissionsDeciding who may read or writeThe three implementACL reversals
parse-triggersAny of the 21 trigger typesA trigger without @ParseClass never registers
parse-transactionsAtomic writes or lost updatesDecorator order; directAccess; unread objects are unprotected
parse-jobsScheduled workNo node-cron means no job ever runs
parse-bootTouching app.tsBoot order; importFiles extensions; blocked auth routes
parse-reviewer (agent)You ask for a reviewAll of the above, after the fact

The reviewer is deliberately narrow. It looks for the mistakes that produce no error and nothing else, ranked by consequence — silent data loss first, silent permission holes next:

# 1. Field shadowing - silent, total data loss
grep -n "@ParseField" -A2 src/models/*.ts

# 2. Decorator order on transactions - silently no transaction
grep -n -B3 "@Transactional" src/functions/*.ts

# 4. Client-supplied identity and money
grep -n -A6 "fromParams" src/functions/*.ts

A reviewer that reports style opinions is noise. One that catches a shadowed field has earned its run.

Pointing an assistant at the full API

The generated files are the rules, not the reference. The complete API — every signature, taken from the source rather than paraphrased — ships inside the package:

node_modules/parse-server-kit/CLAUDE.md

It is there deliberately, so an assistant working in your project can read the contract without a network request and without you pasting anything. Tell it where to look:

A prompt that works "Read node_modules/parse-server-kit/CLAUDE.md before writing against this library, and follow AGENTS.md in the repository root."

What this does not do

Instructions raise the floor; they do not remove the need to read the diff. Two habits catch nearly everything that gets through:

And when something behaves oddly with nothing logged, that is the signature of this library's whole class of bug: start at Troubleshooting, which is organised symptom-first for exactly that reason.

Next: the first tutorial, which is the fastest way to see the whole shape — then Troubleshooting for this same list read from the symptom end, and The psk CLI for everything else the generator does.