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.
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 | --ai | Files |
|---|---|---|
| Claude Code | claude |
CLAUDE.md.claude/skills/parse-model/SKILL.md.claude/skills/parse-endpoint/SKILL.md.claude/agents/parse-reviewer.md |
| Cursor | cursor | .cursor/rules/parse-server-kit.mdc |
| GitHub Copilot | copilot | .github/copilot-instructions.md |
| Windsurf | windsurf | .windsurf/rules/parse-server-kit.md |
| Gemini CLI | gemini | GEMINI.md |
| Anything else | agents | AGENTS.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:
| Rule | What happens when it is broken |
|---|---|
declare, never !: | Field reads as undefined, writes are discarded |
@CloudFunction above @Transactional() | The transaction never opens |
implementACL returns an ACL | Will not compile — the one loud failure in the list |
Triggers need @ParseClass | The trigger never registers |
Build requests with fromParams | Manual set() calls drift from the model |
| Never trust the body for identity or money | The client picks its own price, or its own owner |
| GET parameters are strings | limit of "20" compares wrong |
| Cast query results | Typed properties unavailable |
targetClass on Pointer, Relation and Array | An array of ids stays an array of strings |
@ParseVersionField declares its own field | Duplicate 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.
| File | Loads when | The trap it covers |
|---|---|---|
parse-model | Adding or changing a model | Field shadowing; targetClass on arrays |
parse-endpoint | Adding an endpoint | GET params as strings; client-supplied identity |
parse-permissions | Deciding who may read or write | The three implementACL reversals |
parse-triggers | Any of the 21 trigger types | A trigger without @ParseClass never registers |
parse-transactions | Atomic writes or lost updates | Decorator order; directAccess; unread objects are unprotected |
parse-jobs | Scheduled work | No node-cron means no job ever runs |
parse-boot | Touching app.ts | Boot order; importFiles extensions; blocked auth routes |
parse-reviewer (agent) | You ask for a review | All 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:
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:
- Run the build. The decorators are compile-time. A type error is a real error, and it is free.
-
Read the boot log.
Registered Parse class: XandRoute: /x/yconfirm a model and an endpoint actually registered — which is the one thing a passing build cannot tell you.
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.