Official agent skill

PSL Syntax Tree Layers

by prisma in prisma/orm

Explains which of the three PSL syntax tree layers to use with Prisma's parser output, and how to move between them without hand-written walks.

OfficialApache-2.0Auto-check passedDevelopment

Install PSL Syntax Tree Layers

skills CLI
$ npx skills add prisma/orm --skill psl-ast-layers -a claude-code

Project install by default; add -g for ~/.claude/skills/.

GitHub CLI
$ gh skill install prisma/orm psl-ast-layers --agent claude-code

Project scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).

Manual copy
$ git clone --depth 1 https://github.com/prisma/orm.git skills-src && mkdir -p .claude/skills && cp -r skills-src/skills-contrib/psl-ast-layers .claude/skills/psl-ast-layers && rm -rf skills-src

Use ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.

Claude Code skills documentation · loads skills from .claude/skills/

Facts

Skill name
psl-ast-layers
GitHub stars
48k
Token cost
~2.3k tokens
SKILL.md length
783 words
Files
1
Skills in repo
20
Repo updated
First seen
Licence
Apache-2.0

At a glance

Explains which of the three PSL syntax tree layers to use with Prisma's parser output, and how to move between them without hand-written walks.

  • Works in 4 steps: Re-stringifying the AST to extract… → Reading through the green tree → Collecting child iterators into arrays → …
  • Writing a PSL interpreter or formatter on top of the parser output
  • SKILL.md covers Choosing a layer, Adding missing getters instead…, Anti-patterns and Quick reference
  • Instructions only: no scripts, shell commands, URLs or credentials in SKILL.md

What it does

The PSL parser produces a three-layer syntax tree, and each layer has one job. The green tree, `GreenNode` and `GreenToken`, is immutable, position-independent storage and is never used in consumer code. The red tree, `SyntaxNode` and `SyntaxToken`, adds offsets and parents for navigation outside the current node. The typed AST, with classes such as `ModelDeclarationAst` and `FieldDeclarationAst`, exposes getters like `name()` and `fields()` and is the default choice.

Everything is exported from `@internal/psl-parser/syntax`, and `parse(source)` returns the document, diagnostics and source file, so work starts in the typed layer. When you know the node, call its getters rather than digging through children. To move outward or sideways, use the red tree helpers such as `findAncestor`, `tokenAtOffset`, `nextSiblingOrToken` and the trivia-aware `nonTriviaSibling`, then re-enter the typed layer with a static `cast`. Do not write your own whitespace or comment-skipping loops. The guidance applies to PSL interpreters, helpers in the parser package, the language server and formatters.

When your agent uses it

  • Writing a PSL interpreter or formatter on top of the parser output
  • Resolving a cursor position to a node in the language server
  • Finding the enclosing model or the neighboring declaration of a node

Example prompts

  • “Find the model that contains this field node, using the red tree helpers and a cast back to the typed AST.”
  • “Write a helper that lists the fields of every model declaration in a parsed PSL document.”
  • “Skip the comments and whitespace between two tokens without writing a custom loop.”

Requirements

  • The Prisma ORM repository with the psl-parser package

Workflow steps

4 steps, taken from the step headings in SKILL.md.

  1. Re-stringifying the AST to extract information
  2. Reading through the green tree
  3. Collecting child iterators into arrays
  4. Red-tree spelunking on a node of known type

What it can do on your machine

Read from SKILL.md and the folder at commit 09aaa4f. It shows what the files ask for, not the result of running them.

  • Tool permissions

    Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.

    From allowed-tools in the SKILL.md frontmatter.

  • Runs code

    No scripts in the folder and no shell commands in SKILL.md (its code samples are typescript).

    From the folder's file list and the shell code blocks in SKILL.md.

  • Network

    No URLs in SKILL.md.

    From URLs in SKILL.md, links to its own repository left out.

  • Credentials

    Names no API keys, tokens, secrets or passwords.

    From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.

Context cost

PSL Syntax Tree Layers loads about 2.3k tokens when it runs. Until then it costs about 81 tokens; SKILL.md has 783 words of instructions outside code blocks.

Always · name and description, kept in context so the agent knows when to use it
~81
When it runs · the whole SKILL.md, loaded when a task matches
~2.3k

Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.

Safety

Auto-check passed

The automated check found no risky patterns in SKILL.md.

Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.

SKILL.md

The full file from prisma/orm at commit 09aaa4f, republished under its Apache-2.0 licence (© prisma). 783 words, ~2,255 tokens.

Download SKILL.mdSave it as .claude/skills/psl-ast-layers/SKILL.md (or your agent's skills folder).
name
psl-ast-layers
description
How to use the PSL syntax tree layers (green tree, red tree, strongly-typed AST classes) correctly. Use for any PSL-related work: PSL interpreters (contract-psl), helpers inside the psl-parser package, the language server, formatters, or anything else that consumes `parse()` output from @internal/psl-parser.

PSL AST Layers

The PSL parser (packages/1-framework/2-authoring/psl-parser) produces a three-layer syntax tree. Each layer has exactly one job — pick the right one and the code stays lossless, typed, and cheap.

LayerTypesJobUse in consumer code?
Green treeGreenNode, GreenToken (syntax/green.ts)Immutable, position-independent storage. Foundation only.Never
Red treeSyntaxNode, SyntaxToken (syntax/red.ts, syntax/navigation.ts)Navigation with offsets and parents: findAncestor(), tokenAtOffset(), nextToken/prevToken, nonTriviaSibling()Navigation outside the current node
Typed ASTModelDeclarationAst, FieldDeclarationAst, … (syntax/ast/)Structural information about a known node via getters (name(), fields(), lbrace(), value())Default choice

Everything is exported from @internal/psl-parser/syntax. parse(source) returns { document: DocumentAst, diagnostics, sourceFile } — you start in the typed layer.

Choosing a layer

  1. You know what the node is (you hold a ModelDeclarationAst, a FieldAttributeAst, …) and want its parts → call the typed getters. Never dig through children yourself.

  2. You need to move outward or sideways (find the enclosing model, the previous declaration, the token after the cursor) → use the red tree's navigation helpers, then immediately re-enter the typed layer with a static cast:

    ts
    // enclosing model (tests the node itself first, then walks ancestors)
    const model = node.syntax.findAncestor(ModelDeclarationAst.cast);
    
    // enclosing model OR composite type — combine casts with any(…)
    const owner = node.syntax.findAncestor(
      any(ModelDeclarationAst.cast, CompositeTypeDeclarationAst.cast),
    );

    Sideways and token-level movement all have dedicated helpers — do not hand-roll the walks:

    • nextSiblingOrToken / prevSiblingOrToken — adjacent element within the same parent (works from both nodes and tokens)
    • token.nextToken / token.prevToken — document order, crossing node boundaries
    • nonTriviaSibling(element, 'next' | 'prev'), skipTriviaToken(token, direction), isTrivia(token) (from syntax/navigation.ts) — trivia-aware movement; never write your own whitespace/comment-skipping loop
  3. You genuinely don't know the node's type yet (e.g. resolving a cursor position in the language server) → anchor on the red tree, then cast back into the typed layer immediately:

    ts
    // cursor → token: seam-aware, no descendant scanning
    const token = document.syntax.tokenAtOffset(offset).leftBiased();
    const attr = token?.parent.findAncestor(FieldAttributeAst.cast);
    
    // selection range → smallest enclosing element
    const covering = document.syntax.coveringElement(start, end);

    tokenAtOffset returns a TokenAtOffset that models the offset-on-a-seam case explicitly — pick leftBiased() or rightBiased() deliberately (completions usually want left, hover often wants right). Reach for a manual descendants() walk only when no offset anchors the search, and even then the loop body's first move is a cast (castExpression(child), ModelDeclarationAst.cast(child), …).

  4. Green tree → only inside psl-parser itself (parser, GreenNodeBuilder, red-tree internals). If consumer code touches node.green, that's a bug.

Every typed AST class exposes readonly syntax: SyntaxNode (the AstNode interface), so switching layers is always one property access away — there is no excuse to stay in the wrong layer.

Adding missing getters instead of working around them

If a typed AST class lacks a getter for the structure you need, add the getter to the class in syntax/ast/ (test-first, exported via exports/syntax.ts) rather than hand-rolling child iteration at the call site. The helpers findChildToken, findFirstChild, and filterChildren from ast-helpers.ts are the building blocks for those getters — they belong inside AST classes, not scattered through consumer code.

Anti-patterns

Show full SKILL.md (367 more words)Show less
1. Re-stringifying the AST to extract information

Never round-trip through text: neither printSyntax(node) nor slicing the SourceFile by offsets, followed by string matching / regex / re-parsing. The tree already holds the structure; text extraction throws away parsing work and breaks on comments, whitespace, and escapes.

ts
// BAD: stringify then string-hack
const text = printSyntax(attr.syntax);
const isUnique = text.includes('@unique');

// BAD: slicing the source file by offsets
const raw = source.slice(node.syntax.offset, node.syntax.offset + node.syntax.textLength);
const name = raw.split(' ')[1];

// GOOD: ask the tree
const isUnique = attr.name()?.identifier()?.token()?.text === 'unique';
const name = model.name()?.token()?.text;

Same rule for values: StringLiteralExprAst.value() returns the decoded string (escapes resolved, quotes stripped); slicing quotes off raw text yields wrong results for \n, \u…., etc.

printSyntax and SourceFile offsets have legitimate uses — producing output for humans: error-message snippets, formatter output, positionAt for LSP ranges. Extracting structural facts from that text is the anti-pattern.

2. Reading through the green tree

node.green exists so the red tree can do its job. Consumer code must not inspect green children, kinds, or text — green elements have no offsets and no parents, so any information you pull from them is positionally blind and will not survive refactors of the storage layer.

ts
// BAD: peeking into green storage
const first = model.syntax.green.children[0];
if (first?.type === 'token' && first.text === 'model') { … }

// GOOD: red/typed access
const keyword = model.keyword(); // SyntaxToken with a real offset
3. Collecting child iterators into arrays

children(), childNodes(), descendants(), fields(), attributes(), declarations() are lazy generators on purpose. Materializing them just to index or filter allocates for nothing and hides intent.

ts
// BAD: collect then poke
const fields = Array.from(model.fields());
const idField = fields.filter((f) => f.name()?.token()?.text === 'id')[0];

// GOOD: iterate lazily, stop early
let idField: FieldDeclarationAst | undefined;
for (const field of model.fields()) {
  if (field.name()?.token()?.text === 'id') {
    idField = field;
    break;
  }
}
4. Red-tree spelunking on a node of known type

If you already know the node is a ModelDeclarationAst, iterating its red children to find tokens or sub-nodes manually re-implements the typed getters — badly.

ts
// BAD: manual token hunt on a known node
let lbrace: SyntaxToken | undefined;
for (const child of model.syntax.children()) {
  if (child instanceof SyntaxToken && child.kind === 'LBrace') {
    lbrace = child;
    break;
  }
}

// GOOD: the getter already exists
const lbrace = model.lbrace();

Likewise use field.typeAnnotation(), attr.argList()?.args(), kv.value() — and if the getter you want is missing, add it to the AST class (see above) instead of spelunking.

The same rule applies to navigation: a hand-written ancestor loop, whitespace-skipping loop, or offset-scanning descendants() walk re-implements findAncestor, skipTriviaToken / nonTriviaSibling, or tokenAtOffset / coveringElement. Use the helper.

Quick reference

  • Parse: parse(source) → ParseResult { document, diagnostics, sourceFile }
  • Enter typed layer from red: SomeAst.cast(syntaxNode) (returns undefined on kind mismatch), castExpression(node) for expression unions, any(CastA, CastB, …) to combine casts into one predicate
  • Drop to red from typed: astNode.syntax
  • Upward: findAncestor(cast) (checks self first), ancestors(), parent
  • Sideways: nextSiblingOrToken / prevSiblingOrToken; trivia-aware: nonTriviaSibling, skipTriviaToken, isTrivia
  • Token order: token.nextToken / token.prevToken (crosses node boundaries); subtree edges: node.firstToken / node.lastToken
  • Offsets: tokenAtOffset(offset) (seam-aware TokenAtOffset), coveringElement(start, end), endOffset, isInside(offset) / isOutside(offset)
  • Positions for humans/LSP: sourceFile.positionAt(token.offset) / sourceFile.offsetAt(position) — offsets live only on red SyntaxToken / SyntaxNode, never green
  • Getter helpers for building AST classes: findChildToken, findFirstChild, filterChildren, any, and the BracedBlock interface (for lbrace()/rbrace() blocks) in syntax/ast-helpers.ts

© prisma, Apache-2.0. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file

Files

Just SKILL.md in skills-contrib/psl-ast-layers of prisma/orm.

Open the folder on GitHubat commit 09aaa4f

Compare with similar skills

PSL Syntax Tree Layers next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.

PSL Syntax Tree Layers compared with similar skills
SkillStarsUsed inTokensAuto-checkLicenceRepo updated
PSL Syntax Tree Layers this skillprisma/orm48k—~2.3kAutomated safety check: PassApache-2.0
Drizzle Orm Expertdavila7/claude-code-templates32k3 repos~2.6kAutomated safety check: PassMIT
Drizzle Ormericrisco/rsc-harness167—~2.6kAutomated safety check: PassMIT
Prisma Ormericrisco/rsc-harness167—~3kAutomated safety check: PassMIT
Twenty Syncable Entity Typestwentyhq/twenty58k—~2.8kAutomated safety check: PassCustom licence
Typescriptericrisco/rsc-harness167—~2.8kAutomated safety check: PassMIT

Similar skills

  • Drizzle Orm Expert

    davila7/claude-code-templates

    Expert in Drizzle ORM for TypeScript — schema design, relational queries, migrations, and serverless database integration.

    32k GitHub starsUsed in 3 repos~2.6k tokens
    DatabasesAuto-check passed
  • Drizzle Orm

    ericrisco/rsc-harness

    A skill your agent uses when modeling data or querying with Drizzle ORM in TypeScript — pgTable schema in .ts, type-safe select/insert/relational queries, drizzle-kit migrations.

    167 GitHub stars~2.6k tokensUpdated today
    DatabasesAuto-check passed
  • Prisma Orm

    ericrisco/rsc-harness

    A skill your agent uses when modeling data or writing type-safe queries with Prisma ORM in TypeScript — schema.prisma, prisma.config.ts, the generated Prisma Client, and Prisma Migrate, including…

    167 GitHub stars~3k tokensUpdated today
    DatabasesAuto-check passed
  • Walks through the first of six steps for adding a syncable entity to Twenty's server: metadata name, TypeORM entity, flat types and central constants.

    58k GitHub stars~2.8k tokensUpdated today
    DevelopmentAuto-check passed
  • Typescript

    ericrisco/rsc-harness

    A skill your agent uses when writing or fixing TypeScript type code (.ts/.tsx/.d.ts), modeling data with generics/unions/branded types, diagnosing narrowing failures, or configuring tsconfig and the…

    167 GitHub stars~2.8k tokensUpdated today
    DatabasesAuto-check passed
  • Better Auth Best Practices

    latitude-dev/latitude-llm

    Configure Better Auth server and client, set up database adapters, manage sessions, add plugins, and handle environment variables.

    4.7k GitHub starsUsed in 7 repos~1.6k tokens
    Backend & APIsAuto-check passed

More from prisma/orm

All 20 skills in this repo
  • Official

    Runs a loop on a GitHub pull request: fetch review state, triage comments into actions, implement them and resolve threads, repeating until nothing actionable is left.

    48k GitHub stars~2.2k tokensUpdated today
    Auto-check passed
  • Official

    Fetches a pull request's canonical review state as JSON, validates it, and renders markdown, a text summary and triage target files from it using bundled scripts.

    48k GitHub stars~767 tokensUpdated today
    Auto-check passed
  • Official

    Implements triaged pull request review actions, commits focused fixes, posts status replies on GitHub and resolves the threads.

    48k GitHub stars~1.7k tokensUpdated today
    Auto-check passed
  • Official

    Runs the triage step of the review-framework loop: reads fetched PR review state, builds `review-actions.json`, validates it and renders `review-actions.md`.

    48k GitHub stars~995 tokensUpdated today
    Auto-check passed
  • Official

    Replaces a plain TypeScript union plus switch statements with frozen subclasses and a visitor interface when several places dispatch on the same variants.

    48k GitHub stars~830 tokensUpdated today
    Auto-check passed
  • Official

    Guides an outside contributor through opening a prisma/orm pull request from a fork that follows CONTRIBUTING.md and passes review on the first round.

    48k GitHub stars~2.3k tokensUpdated today
    Auto-check passed

Questions about PSL Syntax Tree Layers

What does PSL Syntax Tree Layers do?

Explains which of the three PSL syntax tree layers to use with Prisma's parser output, and how to move between them without hand-written walks. The PSL parser produces a three-layer syntax tree, and each layer has one job. The green tree, `GreenNode` and `GreenToken`, is immutable, position-independent storage and is never used in consumer code.

When should I use PSL Syntax Tree Layers?

PSL Syntax Tree Layers fits situations like: writing a PSL interpreter or formatter on top of the parser output; resolving a cursor position to a node in the language server; finding the enclosing model or the neighboring declaration of a node.

How do I install PSL Syntax Tree Layers in Claude Code?

Run `npx skills add prisma/orm --skill psl-ast-layers -a claude-code`. Or copy the skill folder (skills-contrib/psl-ast-layers in prisma/orm) into .claude/skills/psl-ast-layers in your project. Claude Code loads it when a task matches its description.

How do I install PSL Syntax Tree Layers in Codex?

Run `npx skills add prisma/orm --skill psl-ast-layers -a codex`. Or copy the skill folder (skills-contrib/psl-ast-layers in prisma/orm) into .agents/skills/psl-ast-layers in your project. Codex loads it when a task matches its description.

Can I use PSL Syntax Tree Layers in Cursor, Gemini CLI or GitHub Copilot?

Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add prisma/orm --skill psl-ast-layers -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/psl-ast-layers, .gemini/skills/psl-ast-layers, .github/skills/psl-ast-layers and .opencode/skills/psl-ast-layers in your project.

What does PSL Syntax Tree Layers need to run?

SKILL.md names no scripts, command-line tools or credentials: PSL Syntax Tree Layers is instructions for the agent only. Our summary lists: The Prisma ORM repository with the psl-parser package.

Does PSL Syntax Tree Layers access the network?

SKILL.md contains no URLs. Any network use would come from the scripts or tools the agent runs. This is read from the text; nothing was executed.

Is PSL Syntax Tree Layers safe to install?

Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.

What licence does PSL Syntax Tree Layers use?

PSL Syntax Tree Layers is published under the Apache-2.0 licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.

How many tokens does PSL Syntax Tree Layers use?

About 2.3k tokens (SKILL.md is roughly 9k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.

What are the alternatives to PSL Syntax Tree Layers?

Skills that share tags, products or a category with PSL Syntax Tree Layers: Drizzle Orm Expert (davila7/claude-code-templates, 32k stars), Drizzle Orm (ericrisco/rsc-harness, 167 stars), Prisma Orm (ericrisco/rsc-harness, 167 stars) and Twenty Syncable Entity Types (twentyhq/twenty, 58k stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.

Who maintains PSL Syntax Tree Layers?

prisma (a GitHub organization, an official publisher) maintains it in prisma/orm, which has 47,696 GitHub stars. The repository holds 20 skills in this directory. The repository was last updated on October 8, 2026.

Source: prisma/orm on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.