Skip to content

Language Reference

Complete specification of the PromptScript language.

Choose a Topic

GoalReference
Understand a complete .prs fileFile Anatomy
Choose valid block body shapesValues and Block Bodies
Predict inheritance and import resultsComposition and Precedence
Understand declaration orderExecution Order
Choose @extend, field!, or @overrideMerge and Replacement
Customize generated headingsSection Headers
Upgrade syntax and resolve diagnosticsVersions and Diagnostics

Use this page as the complete block and grammar catalog. Task-oriented pages above explain decisions and show resolved results step by step.

File Structure

A PromptScript file (.prs) consists of:

# Comments start with #
@meta { ... } # Required: Metadata
@inherit @path # Optional: Inheritance
@use @path [as alias] # Optional: Imports
@identity { ... } # Content blocks
@context { ... }
@standards { ... }
@restrictions { ... }
@shortcuts { ... }
@params { ... }
@guards { ... }
@skills { ... }
@agents { ... }
@workflows { ... }
@hooks { ... }
@mcpServers { ... }
@plugins { ... }
@knowledge { ... }
@examples { ... }
@local { ... }
@extend path { ... } # Block modifications
@override path { ... } # Atomic replacement of an existing target

Aliased imports qualify imported agents so repeated local names remain distinct:

@use ./frontend-team as frontend
@use ./backend-team as backend
Try in Playground

An imported reviewer becomes frontend.reviewer or backend.reviewer. Unique unaliased names remain unchanged. Conflicting unaliased definitions produce a source-aware diagnostic instead of silently overwriting an agent. Native output maps dots to hyphens, for example frontend.reviewer becomes frontend-reviewer.

Namespaces can be nested through aliased imports:

team.prs
@use ./inner-team as inner
# project.prs
@use ./team as frontend
Try in Playground

The inner team’s reviewer resolves to frontend.inner.reviewer. Agent references are rewritten with the same qualified name, including agent fields and handoffs entries, so references do not retain the source-local reviewer name.

Syntax 1.5.0 resolves top-level declarations in source order. Put @meta first, then imports, local blocks, and modifications in the order they should apply. See Execution Order. Contextual @header entries live inside supported owner blocks, not at the top level.

@meta Block (Required)

Every PromptScript file must have a @meta block defining metadata:

@meta {
id: "project-id" # Required: Unique identifier
syntax: "1.0.0" # Required: PromptScript syntax version (semver)
# Optional fields
org: "Company Name"
team: "Frontend"
tags: [frontend, react, typescript]
}
FieldRequiredDescription
idYesUnique identifier (string)
syntaxYesPromptScript syntax version (semver)
orgNoOrganization name
teamNoTeam name
tagsNoArray of tags
paramsNoParameter definitions for templates

Parameter Definitions

The params field defines parameters for parameterized inheritance:

@meta {
id: "@stacks/typescript-lib"
syntax: "1.0.0"
params: {
# Required string parameter
projectName: string
# Optional with default value
runtime: string = "node18"
# Optional parameter (no default, can be undefined)
debug?: boolean
# Enum parameter with constrained values
testFramework: enum("vitest", "jest", "mocha") = "vitest"
# Number parameter
port: number = 3000
}
}

Parameter Types:

TypeSyntaxValues
stringname: stringAny text
numbercount: numberIntegers and floats
booleanenabled: booleantrue or false
enummode: enum("a", "b")One of listed options

Parameter Modifiers:

PatternMeaning
name: stringRequired, must be provided
name?: stringOptional, can be omitted
name: string = "default"Optional with default value

Syntax Versions

The syntax field in @meta declares which version of the PromptScript language the file uses. Versions follow semver.

Known Versions

VersionStatusBlocks and features
1.0.0Stable@identity, @context, @standards, @restrictions, @knowledge, @shortcuts, @commands, @guards, @params, @skills, @local
1.1.0StableAll 1.0.0 blocks + @agents, @workflows, and the @prompts block name
1.2.0StableAll 1.1.0 blocks + @examples
1.3.0StableAll 1.2.0 features + regular block field replacement in @extend
1.4.0StableAll 1.3.0 features + @hooks, @mcpServers, @plugins
1.5.0StableAll 1.4.0 features + @header section titles, @override replacement, declaration order, unquoted ${VAR} values
1.6.0CurrentAll 1.5.0 features + validated @prompts entries with typed inputs and runtime slots; coding agent targets skip them, bundle and prompt-text compile them

Block Availability

@workflows emits workflow files such as .claude/workflows/<name>.md. @prompts entries require syntax 1.6.0. @hooks, @mcpServers, and @plugins require syntax 1.4.0.

Block Version Requirements

BlockMinimum Syntax Version
@agents1.1.0
@workflows1.1.0
@prompts1.1.0 (entries: 1.6.0)
@examples1.2.0
@hooks1.4.0
@mcpServers1.4.0
@plugins1.4.0

All other built-in blocks are available from 1.0.0. Regular block field replacement with field!: value requires syntax 1.3.0. Generated section title overrides with @header, atomic target replacement with @override, and unquoted ${VAR} values require syntax 1.5.0.

Validation (PS018, PS019)

The validator enforces syntax version compatibility:

  • PS018 (syntax-version-compat): warns when the resolved program uses blocks or syntax features that require a higher version than declared in @meta. This includes requirements inherited, imported, or included through skill composition.
  • PS019 (unknown-block-name): warns when a block name is not a known PromptScript type, and suggests the closest match for typos.

Upgrading

To automatically update the syntax field to the required version for the resolved syntax you use:

Terminal window
prs validate --fix # Fix syntax versions in .prs files
prs upgrade # Upgrade all .prs files to the latest syntax version

prs validate --fix rewrites the syntax: "..." line in each @meta block to the minimum version required by resolved blocks and syntax features. It follows inheritance, imports, and skill composition.

prs upgrade upgrades all files to the latest known syntax version regardless of what blocks they use.

@inherit Declaration

Single inheritance from another PromptScript file:

# From registry namespace
@inherit @company/frontend-team
# Relative path
@inherit ./parent
# With version constraint
@inherit @company/[email protected]
# With parameters (see Parameterized Inheritance below)
@inherit @stacks/react-app(projectName: "my-app", port: 3000)

Single Inheritance

Each file can only have one @inherit declaration. Use @use for composition.

@use Declaration

Import and merge fragments for composition (like mixins):

# Import from registry - blocks are merged into current file
@use @core/guards/compliance
# Import with alias - blocks merged AND available for @extend
@use @core/guards/security as sec
# Import relative
@use ./fragments/logging
# Multiple imports - all merged in order
@use @core/standards/typescript
@use @core/restrictions/security
@use ./local-config
# With parameters (see Parameterized Inheritance below)
@use ./fragments/testing(framework: "vitest", coverage: 90) as testing
Try in Playground

Merge Behavior

When you use @use, all blocks from the imported file are merged into your file:

  • TextContent: Concatenated (source + target), with automatic deduplication of identical content
  • ObjectContent: Deep merged (the imported source wins same-shape key conflicts)
  • ArrayContent: Unique concatenation (preserves order, removes duplicates)

For incompatible block shapes, the existing target body wins. Under syntax 1.5.0, later declarations can modify the merged result, so a local block, @extend, or @override placed after @use can become the final value. See Composition and Precedence for the normative matrix.

# Source: @core/guards/security
@restrictions {
- "Never expose secrets"
}
# Target: ./project.prs
@use @core/guards/security
@restrictions {
- "Follow OWASP guidelines"
}
# Result after merge:
# @restrictions contains both items (source first, then target)
Try in Playground

Alias for @extend Access

When you provide an alias, imported blocks are also stored with a prefix for use with @extend:

@use @core/typescript as ts
# Now you can extend imported blocks
@extend ts.standards {
testing: { coverage: 90 }
}
Try in Playground

When to Use Alias

  • Without alias: Simple include/mixin behavior - blocks are merged directly
  • With alias: When you need to selectively extend specific imported blocks

Block Filtering

Control which blocks are imported using the reserved only and exclude parameters:

# Import only skills and context blocks
@use ./shared-config(only: ["skills", "context"])
# Import everything except knowledge
@use ./shared-config(exclude: ["knowledge"])
# Combine with template parameters
@use ./shared-config(exclude: ["knowledge"], mode: "strict")
# Combine with alias
@use ./shared-config(only: ["skills"]) as shared

Rules:

  • only and exclude are mutually exclusive — using both is a validation error (PS021)
  • Values are block type names: identity, context, standards, knowledge, skills, shortcuts, commands, guards, restrictions, agents, etc.
  • Unknown block names produce a warning (for forward compatibility)
  • Block filtering does not apply to @inherit directives

Skill Filtering

When importing from a repository or directory that contains multiple skills, control which individual skills are imported using the reserved includes and excludes parameters:

# Import only specific skills from a remote repo
@use github.com/owner/repo/skills(includes: ["code-review", "testing"])
# Import all skills except specific ones
@use github.com/owner/repo/skills(excludes: ["legacy-support"])
# Combine with version pinning (version comes before params)
@use github.com/owner/repo/[email protected](includes: ["code-review"])
# Combine with block-level filtering
@use github.com/owner/repo/skills(only: ["skills"], includes: ["code-review"])
Try in Playground

Rules:

  • includes and excludes are mutually exclusive — using both is a validation error (PS021)
  • Values are skill names (the keys inside the @skills block, matching the skill’s frontmatter name)
  • If neither is specified, all skills are imported (current behavior)
  • Skill filtering applies to imports that produce a @skills block (directory imports, remote repo imports)
  • Can be combined with only/exclude block filters — they operate at different levels (blocks vs skills within a block)
  • When combining with version pinning, the version must come before the params: @use github.com/owner/[email protected](includes: [...]), not after
  • If an excluded skill is a dependency of an included skill, resolution will fail — this is expected user error

Content Blocks

Every block and @extend body uses the same ordered content model. A body can interleave properties, free-form text, dash list items, and inline @use declarations:

@context {
project: "Checkout Service"
"""Shared context appears at this point in the body."""
- "Keep payment data out of logs"
@use ./team-context
environment: production
}
Try in Playground

The parser preserves this source order. Tools that consume the canonical AST can read the ordered body.entries sequence directly. Existing integrations can continue using the mutable Program, Block, and BlockContent interfaces, which remain available as a compatibility projection.

@identity

Core identity and persona definition:

@identity {
"""
You are an expert frontend developer specializing in React.
You write clean, maintainable, and well-tested code.
"""
}
Try in Playground

The identity block defines who the AI assistant should be.

Formatter Behavior:

FormatterHow @identity is used
GitHubIncluded in the output as introductory text
ClaudePlaced at the beginning of CLAUDE.md
CursorIf it starts with “You are…”, used as full intro; otherwise generates “You are working on {project}”
AntigravityIncluded in project description

Best Practice

Start your @identity with “You are…” for consistent output across all formatters. Multiline strings are automatically dedented to remove source indentation.

@context

Project context and environment:

@context {
project: "Checkout Service"
team: "Payments"
environment: production
"""
Additional context as text.
This service handles payment processing for the e-commerce platform.
"""
}
Try in Playground

Supports both key-value properties and text content. project, languages, runtime, monorepo, techStack and architecture get dedicated rendering; every other property renders as a Label: value list item under the context section.

@standards

Coding standards and conventions using category-based arrays:

@standards {
code: [
"Use clean code principles",
"Prefer hooks and composition patterns",
"Write tests for all code (80% coverage minimum)",
"Use vitest as the test framework"
]
naming: [
"Components: PascalCase",
"Functions: camelCase",
"Constants: UPPER_SNAKE_CASE"
]
documentation: [
"Document all public APIs",
"Use JSDoc format"
]
}
Try in Playground

Standards are organized by category with each category containing an array of human-readable rules. You can use any category name (e.g., code, naming, security, api, documentation) - all keys are supported and will generate corresponding subsections in the output.

Backwards Compatibility

The errors key is automatically mapped to error-handling in the output for backwards compatibility.

Structured Keys: git, config, documentation, diagrams

Four keys render as dedicated sections instead of code-standard subsections: git (commit conventions), config (tool configuration), documentation (doc standards), and diagrams (diagram preferences). Each known field gets specialized rendering (e.g. git.format, git.types, diagrams.format).

Custom fields are kept. Any extra key inside these objects renders as a generic Label: value list item - true renders as a bare label, false/null are dropped, nested objects render inline as key: value pairs. Known fields accept the same shapes: true emits the built-in wording, while a string replaces it with your own text:

@standards {
git: {
format: "Conventional Commits"
branch: "(feat|fix)/{project}/{issue-id}"
mergeRequest: {
title: "merge commit summary"
description: "purpose and issue link"
}
requireReview: true
}
}
Try in Playground

Renders on Markdown instruction targets (e.g. Claude Code, Factory AI) as:

## Git Commits
- Format: Conventional Commits
- Branch: (feat|fix)/{project}/{issue-id}
- Merge Request: title: merge commit summary, description: purpose and issue link
- Require Review

Cursor target

Cursor uses its compact key: value style instead of humanized labels (- branch: (feat|fix)/{project}/{issue-id}, - requireReview).

Free-form text is also supported via a triple-quoted string:

@standards {
"""
## Security
- Validate all inputs
- Never log secrets
"""
}
Try in Playground

Per-target support

Free-form text @standards currently renders only for the Factory target. Other targets render property-style @standards content only.

@restrictions

Things the AI should never do:

@restrictions {
- "Never expose API keys or secrets in code"
- "Never commit sensitive data to version control"
- "Always validate user input before processing"
- "Never use deprecated APIs"
}
Try in Playground

Restrictions are concatenated during inheritance.

@shortcuts

Custom commands for quick actions:

@shortcuts {
"/review": "Review code for quality and best practices"
"/test": """
Write unit tests using:
- Vitest as the test runner
- Testing Library for DOM testing
- MSW for API mocking
"""
"/refactor": "Suggest refactoring improvements for cleaner code"
}
Try in Playground

Shortcuts from child files override parent shortcuts with the same name.

Cursor Slash Commands (1.6+)

Multi-line shortcuts are automatically converted to executable slash commands:

Shortcut TypeOutput LocationBehavior
Single-line.cursor/rules/project.mdcListed as documentation in Commands section
Multi-line.cursor/commands/<name>.mdExecutable via /name in Cursor chat

Example:

@meta { id: "cursor-slash-commands" syntax: "1.0.0" }
@shortcuts {
# Single-line → documentation only
"/review": "Review code quality"
# Multi-line → .cursor/commands/test.md
"/test": """
Write unit tests using:
- Vitest as the test runner
- AAA pattern (Arrange, Act, Assert)
"""
}
Try in Playground

Generates .cursor/commands/test.md:

Write unit tests using:
- Vitest as the test runner
- AAA pattern (Arrange, Act, Assert)

Using Cursor Commands

Type / in Cursor chat to see available commands, then select to execute.

GitHub Copilot Output

Shortcuts are handled differently based on their type:

Shortcut TypeOutput LocationBehavior
Simple stringcopilot-instructions.mdListed in ## shortcuts section
Object without prompt: truecopilot-instructions.mdListed in ## shortcuts section (uses description)
Object with prompt: true.github/prompts/<name>.prompt.mdGenerates separate prompt file

GitHub Copilot Prompts

To generate .github/prompts/*.prompt.md files for GitHub Copilot, use the object syntax with prompt: true:

@meta { id: "github-prompts-example" syntax: "1.0.0" }
@shortcuts {
# Simple string → listed in ## shortcuts section
"/review": "Review code for quality"
# Object with prompt: true → generates .github/prompts/test.prompt.md
"/test": {
prompt: true
description: "Write unit tests"
content: """
Write unit tests using:
- Vitest as the test runner
- AAA pattern (Arrange, Act, Assert)
"""
}
# Agent mode prompt with tools
"/deploy": {
prompt: true
description: "Deploy to production"
mode: agent
tools: [run_terminal, read_file]
content: """
Deploy the application to production:
1. Run tests
2. Build the project
3. Deploy to staging
4. Run smoke tests
5. Deploy to production
"""
}
}
Try in Playground
PropertyTypeRequiredDescription
promptbooleanYesMust be true to generate a prompt file
descriptionstringYesShown in prompt picker UI
contentstringYesThe prompt instructions
modestringNoSet to "agent" for agentic prompts
toolsstring[]NoTools available in agent mode

Output Mode Required

Prompt files are only generated when using version: multifile or version: full in your target configuration:

```yaml
targets:
- github:
version: multifile # Enables .github/prompts/*.prompt.md
```

Generated file (.github/prompts/test.prompt.md):

---
description: 'Write unit tests'
---
Write unit tests using:
- Vitest as the test runner
- AAA pattern (Arrange, Act, Assert)

Antigravity Workflows

For Antigravity, shortcuts with steps property generate workflow files:

@shortcuts {
"/deploy": {
description: "Deploy the application"
steps: ["Build the project", "Run tests", "Deploy to staging"]
}
}
Try in Playground
PropertyTypeDescription
descriptionstringWorkflow description
stepsstring[]Ordered list of workflow steps

Generates .agent/workflows/deploy.md with numbered steps.

@params

Configurable parameters:

@params {
strictness: range(1..5) = 3
format?: enum("json", "text", "markdown") = "text"
verbose: boolean = false
}
Try in Playground
SyntaxDescription
name: typeRequired parameter
name?: typeOptional parameter
= valueDefault value, rejected when it does not match type

Available types:

TypeAccepts
stringAny string
numberAny number
booleantrue or false
range(min..max)A number within the range
enum("a", "b")One of the listed strings

@guards

Runtime validation rules and file targeting:

@guards {
maxFileSize: 1000
allowedLanguages: [typescript, javascript, css]
# Glob patterns for file-specific rules (used by multifile formatters)
globs: ["**/*.ts", "**/*.tsx"]
"""
Additional guard rules as text.
"""
}
Try in Playground

The globs property is used by multifile formatters (GitHub, Claude, Cursor) to generate path-specific instruction files.

GitHub Copilot applyTo Integration

When using version: multifile or version: full for GitHub Copilot, the globs patterns generate separate instruction files with applyTo frontmatter:

@meta { id: "guards-applyto-example" syntax: "1.0.0" }
@guards {
globs: ["**/*.ts", "**/*.tsx", "**/*.spec.ts", "**/*.test.ts"]
}
@standards {
typescript: [
"Use strict TypeScript with no any types",
"Prefer interfaces over type aliases"
]
testing: [
"Use Vitest for unit tests",
"Follow AAA pattern (Arrange, Act, Assert)"
]
}
Try in Playground

This generates:

.github/instructions/typescript.instructions.md:

---
applyTo:
- '**/*.ts'
- '**/*.tsx'
---
# TypeScript-specific rules
- Use strict TypeScript with no any types
- Prefer interfaces over type aliases

.github/instructions/testing.instructions.md:

---
applyTo:
- '**/*.spec.ts'
- '**/*.test.ts'
---
# Testing-specific rules
- Use Vitest for unit tests
- Follow AAA pattern (Arrange, Act, Assert)

Version Required

Path-specific instruction files are only generated with version: multifile or version: full:

```yaml
targets:
- github:
version: multifile
```

Named Instruction Entries

For projects with multiple path-specific instruction files, use named entries in @guards to generate individual instruction files with their own applyTo patterns:

@meta { id: "named-guards-example" syntax: "1.0.0" }
@guards {
angular-components: {
applyTo: ["apps/admin/**/*.ts", "apps/webview/**/*.ts"]
description: "Angular component coding standards"
content: """
Use OnPush change detection for all components.
Always implement OnDestroy for cleanup.
"""
}
inversify: {
applyTo: ["apps/api/**/*.ts"]
description: "InversifyJS DI coding standards"
content: """
Use constructor injection with inject decorator.
Register all bindings in the container module.
"""
}
}
Try in Playground

Each named entry generates a separate instruction file per target:

  • Multifile targets (version: multifile or full): .claude/rules/<name>.md, .cursor/rules/<name>.mdc, .github/instructions/<name>.instructions.md with applyTo frontmatter, and .factory/skills/<name>/SKILL.md with scope info in the description
  • Antigravity (any version): .agent/rules/<name>.md with glob activation

Named entries support three properties:

PropertyRequiredDescription
applyToYesGlob patterns for file targeting (alias: paths)
descriptionNoHuman-readable description (defaults to <name> rules)
contentNoFull instruction content (triple-quoted markdown)

Named entries and globs + @standards auto-split can coexist in the same @guards block.

Merge Behavior with @use

When multiple @use’d files contribute @guards blocks with named entries:

  • Named entries from different files are preserved as separate keys (ObjectContent deep merge)
  • If two files define the same entry name, the importing file’s entry takes precedence
  • globs arrays are concatenated with deduplication

@skills

Define reusable skills that AI assistants can invoke:

@meta { id: "skills-example" syntax: "1.0.0" }
@skills {
commit: {
description: "Create git commits"
disableModelInvocation: true
context: "fork"
agent: "general-purpose"
allowedTools: ["Bash", "Read", "Write"]
content: """
When creating commits:
1. Use conventional commit format
2. Include Co-Authored-By trailer
3. Never amend existing commits
"""
}
review: {
description: "Review code changes"
userInvocable: true
content: """
Perform thorough code review checking:
- Type safety
- Error handling
- Security vulnerabilities
"""
}
}
Try in Playground
PropertyTypeFormatterDescription
descriptionstringAllHuman-readable description
contentstringAllDetailed skill instructions
disableModelInvocationbooleanGitHub, FactoryPrevent model from auto-invoking skill
userInvocablebooleanClaude, FactoryAllow user to manually invoke skill
contextstringClaudeContext mode: "fork" or "inherit"
agentstringClaudeAgent type: "general-purpose", etc.
modelstringClaude, GrokModel to use while the skill is active
allowedToolsstring[]Claude, FactoryTools the skill can use

Skills are output differently based on the formatter:

GitHub Output (.github/skills/commit/SKILL.md, version: full):

---
name: commit
description: 'Create git commits'
disable-model-invocation: true
---
When creating commits:
1. Use conventional commit format
2. Include Co-Authored-By trailer
3. Never amend existing commits

Claude Output (.claude/skills/commit/SKILL.md, version: full):

---
name: 'commit'
description: 'Create git commits'
context: fork
agent: general-purpose
allowed-tools:
- Bash
- Read
- Write
disable-model-invocation: true
---
When creating commits:
1. Use conventional commit format
2. Include Co-Authored-By trailer
3. Never amend existing commits

@agents

Define specialized AI agents for target platforms with native agent support:

@meta { id: "agents-example" syntax: "1.1.0" }
@agents {
code-reviewer: {
description: "Reviews code for quality and best practices"
tools: ["Read", "Grep", "Glob", "Bash"]
model: "sonnet"
content: """
You are a senior code reviewer ensuring high standards.
When invoked:
1. Run git diff to see recent changes
2. Focus on modified files
3. Begin review immediately
Review checklist:
- Code is clear and readable
- Functions and variables are well-named
- No duplicated code
- Proper error handling
"""
}
debugger: {
description: "Debugging specialist for errors and test failures"
tools: ["Read", "Edit", "Bash", "Grep", "Glob"]
disallowedTools: ["Write"]
model: "inherit"
permissionMode: "acceptEdits"
skills: ["error-handling", "testing-patterns"]
content: """
You are an expert debugger specializing in root cause analysis.
"""
}
}
Try in Playground
PropertyTypeRequiredDescription
descriptionstringYesWhen the agent should be invoked
contentstringNoAdditional system prompt for the subagent
toolsstring[]NoAllowed tools (inherits all if omitted)
modelstringNoAI model to use, mapped to each target’s model names
reasoningEffortstringNoTarget-native reasoning level
specModelstringNoModel for Specification/planning mode (GitHub, Factory only)
specReasoningEffortstringNoReasoning effort for spec mode: low, medium, high (Factory only)
disallowedToolsstring[]NoTools to deny (Claude, grok only)
permissionModestringNodefault, acceptEdits, dontAsk, bypassPermissions, plan (Claude, grok only)
skillsstring[]NoNamed skills available to the agent
mcpServersstring[]NoNamed top-level MCP servers available to the agent
sandboxModestringNoTarget-native sandbox policy
nicknameCandidatesstring[]NoCandidate display names for spawned agents
handoffsarrayNoDelegation entries (label, agent, prompt, send; GitHub only)
maxTurnsnumberNoMaximum agentic turns before stopping (Claude, grok only)
memorystringNoMemory scope: user, project, local (Claude, grok only)
backgroundbooleanNoRun the agent as a background process (Claude, grok only)
isolationstringNoIsolation mode: worktree (Claude, grok only)

Every canonical field has an explicit per-target status in the Field Support Matrix. A field a target cannot represent is reported with a PS4003 compatibility warning (agent, field, target, and supporting targets) and omitted, never dropped silently.

Agents output by platform:

GitHub Output (.github/agents/code-reviewer.md, version: full)

Supports description, content, handoffs, and mcpServers (inlined from the referenced @mcpServers entries), plus tools, model, and specModel under mapping:

  • Tools: Read → read, Grep/Glob → search, Bash → execute
  • Models: Copilot names from the model catalog; floating aliases pick the newest release, listed in Floating Aliases, and inherit is omitted
---
name: code-reviewer
description: Reviews code for quality and best practices
tools: ['read', 'search', 'execute']
model: Claude Sonnet 5.5
---
You are a senior code reviewer ensuring high standards.
When invoked:
1. Run git diff to see recent changes
2. Focus on modified files
3. Begin review immediately
Review checklist:
- Code is clear and readable
- Functions and variables are well-named
- No duplicated code
- Proper error handling

Claude Output (.claude/agents/code-reviewer.md, version: full)

Supports description, content, tools, disallowedTools, model, permissionMode, skills, maxTurns, memory, mcpServers, background, and isolation (reasoningEffort, specModel, specReasoningEffort, sandboxMode, nicknameCandidates, and handoffs are reported with PS4003 and omitted):

---
name: code-reviewer
description: Reviews code for quality and best practices
tools: ['Read', 'Grep', 'Glob', 'Bash']
model: sonnet
---
You are a senior code reviewer ensuring high standards.
When invoked:
1. Run git diff to see recent changes
2. Focus on modified files
3. Begin review immediately
Review checklist:
- Code is clear and readable
- Functions and variables are well-named
- No duplicated code
- Proper error handling

Agent Platform Features

Agents can reference @skills and @mcpServers. Project lifecycle automation is defined separately through @hooks. Target-native support varies by formatter.

@local

Private instructions not committed to version control:

@local {
"""
Private development notes and local configuration.
This content is not committed to git.
Local environment setup:
- API keys are in .env.local
- Use staging backend at localhost:8080
"""
}
Try in Playground

Or with key-value properties:

@local {
apiEndpoint: "http://localhost:8080"
debugMode: true
customPaths: ["/tmp/dev" "/var/local"]
"""
Additional local notes...
"""
}

@local Output

The @local block generates CLAUDE.local.md when using the Claude formatter with version: full. This file should be added to .gitignore.

@commands

Alias for @shortcuts. The @commands block is functionally identical to @shortcuts — both define command aliases. Use @shortcuts in new files; @commands is supported for backward compatibility.

@commands {
"/review": "Review code for quality and best practices"
"/test": "Write unit tests with Vitest"
}
Try in Playground

@knowledge

Reference documentation and knowledge:

@knowledge {
"""
## API Reference
### Authentication
- POST /api/auth/login - User login
- POST /api/auth/logout - User logout
### Users
- GET /api/users - List users
- GET /api/users/:id - Get user by ID
## Architecture Notes
The service follows a clean architecture pattern with:
- Controllers for HTTP handling
- Services for business logic
- Repositories for data access
"""
}
Try in Playground

@examples

Structured few-shot examples for AI assistants (requires syntax 1.2.0):

@meta {
id: "commit-style"
syntax: "1.2.0"
}
@examples {
feat-commit: {
description: "Feature commit with scope"
input: "Added user authentication with JWT tokens"
output: "feat(auth): add JWT-based user authentication"
}
multiline-example: {
input: """
const x = users.filter(u => u.active).map(u => u.email);
"""
output: """
const activeEmails = users
.filter(u => u.active)
.map(u => u.email);
"""
}
}
Try in Playground
PropertyRequiredDescription
inputYesThe input the AI receives
outputYesThe expected output the AI should produce
descriptionNoHuman-readable label for the example

Examples can also be defined inline within a @skills entry via the examples property, scoping them to that specific skill.

See the Examples guide for a full walkthrough.

@workflows

Defines portable workflow definitions. Available since syntax 1.1.0. Targets that support workflows emit dedicated workflow files (e.g. Claude emits .claude/workflows/<name>.md).

@workflows {
release: {
description: "Prepare release"
content: """
Review changes, validate packages, and prepare release metadata.
"""
}
}
Try in Playground

Each workflow entry is an object with:

FieldRequiredDescription
descriptionNoShort description of the workflow
contentNoWorkflow instructions (multiline)

@hooks

Defines portable lifecycle hooks. Requires syntax 1.4.0. Formatters map portable events to each target’s native event system and configuration format.

@hooks {
protect-generated-files: {
event: "pre-tool-use"
matcher: "Edit|Write"
script: {
path: ".promptscript/scripts/protect.mjs"
interpreter: "node"
args: ["--strict"]
}
cwd: "project"
timeoutMs: 5000
statusMessage: "Checking generated files"
continueOnFailure: false
enabled: true
}
}

Portable events:

EventDescription
pre-terminal-commandFires before a terminal command
pre-tool-useFires before a tool invocation
post-tool-useFires after a tool invocation
session-startFires when a session begins
setupAlias for session-start
subagent-startFires when a subagent is launched
notificationFires on notification events
stopFires when the agent stops

Each hook entry is an object with:

FieldRequiredTypeDescription
eventYesstringPortable event name (see above)
commandOne ofstring[]Non-empty source argument array
scriptOne ofobjectRepository-local script descriptor
cwdNostring"project" or a forward-slash path relative to project root
matcherNostringTarget-native tool name matcher pattern
timeoutMsNonumberTimeout in ms (100-600000)
statusMessageNostringStatus message shown during execution
continueOnFailureNobooleanWhether to continue if hook fails
enabledNobooleanWhether the hook is enabled (default: true)
targetsNoobjectTarget-specific overrides, including an optional replacement command or script

Exactly one of command or script is required. A script object contains:

FieldRequiredTypeDescription
pathYesstringFile under .promptscript/scripts/ using forward slashes
interpreterYesstringWhitelisted executable name
argsNostring[]Additional arguments, preserved without shell re-splitting

Supported interpreters are python3, python, node, deno, bun, ruby, php, perl, bash, sh, zsh, pwsh, and powershell. The compiler requires the path to exist as a regular file and rejects traversal and symlink escapes from .promptscript/scripts/. Browser compilation applies the same location checks to the virtual filesystem.

Shell interpolation ($(), backticks, ${...}) is forbidden in command arguments, and command must contain at least one argument - PS034 rejects empty arrays and hooks without an executable command are omitted from target output. Target adapters preserve argument boundaries when they serialize an array or script descriptor as a native command string. Script paths and arguments are shell-quoted as data.

cwd: "project" requests execution from the resolved PromptScript project root. Other cwd values must be portable relative paths and resolve from that root. Absolute paths, backslashes, empty path segments, . segments, and .. traversal are rejected. Hook configuration location does not set the command working directory.

matcher uses each target’s tool vocabulary, such as Factory Execute or Read and Claude Edit|Write. GitHub emits it only for preToolUse, postToolUse, subagentStart, and notification. A matcher written for one target can match nothing on another, so review generated hook files per target.

Target overrides use the target name as the key:

targets: {
factory: { matcher: "Execute" command: ["node", "check-factory.mjs"] }
vscode: { script: { path: ".promptscript/scripts/check.py" interpreter: "python3" } }
github: { enabled: false }
}

VS Code Copilot Agent Hooks are emitted separately at .github/hooks/promptscript-vscode.json when a vscode override is present. They use PascalCase events, camelCase tool input fields, and currently ignore matcher values. GitHub Copilot CLI and cloud-agent hooks remain in .github/hooks/promptscript.json and use lower camelCase events.

multifile and full modes emit target-native hook files. simple mode preserves its single-file contract and reports a compatibility warning.

TargetOutputEvent namingTimeout field
Factory Droid.factory/hooks.jsonPascalCasetimeout
GitHub Copilot.github/hooks/promptscript.jsonlower camelCasetimeoutSec
Claude Code.claude/settings.jsonPascalCasetimeout
Cursor.cursor/hooks.jsonlower camelCasetimeout
Codex.codex/hooks.jsonPascalCasetimeout
Gemini CLI.gemini/settings.jsonPascalCasetimeout
Windsurf.windsurf/hooks.jsonsnake_case-
Grok Build.grok/hooks/promptscript.jsonPascalCasetimeout
VS Code Agent.github/hooks/promptscript-vscode.jsonPascalCasetimeout

GitHub output uses the version 1 repository-hook schema shared by Copilot CLI and cloud agent. Factory output uses the preferred dedicated project hook file. Factory still accepts hooks inside .factory/settings.json only as a fallback. prs compile reports PS4002 when that fallback file still carries a non-PromptScript-owned hooks key. prs hooks install factory migrates unambiguous legacy entries, preserves unrelated settings, and refuses partial migrations when event names or entries are ambiguous. Formatters report PS4002 when a target cannot represent an event or optional field instead of silently dropping it.

When @hooks is removed or stops emitting, the CLI deletes the obsolete hook file (.factory/hooks.json, .github/hooks/promptscript.json, or .github/hooks/promptscript-vscode.json) only when every command in it carries the PromptScript ownership marker, and prunes managed directories (such as .github/hooks/) that the removal leaves empty.

Every built-in target has an explicit hook capability classification. Targets without native project hooks and modes that cannot emit additional files report PS4002 with a fallback such as prs compile --watch. See Hooks and Workflows for the complete 50-target matrix and project-root behavior.

@mcpServers

Defines project-local MCP (Model Context Protocol) server configurations. Requires syntax 1.4.0. Servers are mapped to target-native MCP config files.

@mcpServers {
security-scanner: {
transport: "stdio"
command: ["node", "./tools/security-scanner.mjs"]
env: {
LOG_LEVEL: "info"
}
}
}
Try in Playground
FieldRequiredTypeDescription
transportYesstringstdio, http, or sse
commandYes*string[]Non-empty argument array (stdio only)
urlYes*stringHTTP(S) URL (http/sse only)
envNoobjectNon-secret environment values

Plaintext values are rejected for secret-bearing environment keys. Current target serializers emit string environment values only, so provide credentials through target-native runtime or secret management rather than .prs source.

command is a single array in source, and each target serializer splits it into that target’s native shape. JSON hosts receive the executable in command and the remaining entries in args, with args omitted when the command takes none:

{
"mcpServers": {
"security-scanner": {
"type": "stdio",
"command": "node",
"args": ["./tools/security-scanner.mjs"],
"env": { "LOG_LEVEL": "info" }
}
}
}

VS Code is the exception to the wrapper key: .vscode/mcp.json nests servers under servers instead of mcpServers. TOML hosts keep the array form under [mcp_servers.<name>].

Target Support: The @mcpServers block is emitted to target-native MCP config files. See Configuration Reference for the full list of supported targets and their output paths.

Agents can reference MCP servers by name via the mcpServers field in @agents:

@agents {
reviewer: {
description: "Code reviewer"
content: "Review code changes."
mcpServers: ["security-scanner", "linear"]
}
}
Try in Playground

@plugins

Defines portable plugin bundles that group skills, hooks, and MCP servers. Requires syntax 1.4.0.

@plugins {
security-suite: {
description: "Security review tooling"
version: "1.0.0"
skills: ["security-review"]
hooks: ["protect-generated-files"]
mcpServers: ["security-scanner"]
}
}
Try in Playground
FieldRequiredTypeDescription
descriptionNostringPlugin description
versionNostringSemantic version (e.g. 1.0.0)
skillsNostring[]Referenced skill names
hooksNostring[]Referenced hook IDs
mcpServersNostring[]Referenced MCP server names

Marketplace publishing and installation are outside the compiler scope.

Target Support: Plugins are emitted to .factory/plugins.json (Factory), .cursor/plugins.json (Cursor), .codex/plugins.json (Codex), and .grok/plugins.json (Grok).

@prompts

Defines named prompts with typed inputs. Requires syntax 1.6.0. The validator checks every entry. The coding agent targets skip the block, so it never changes their output. The opt-in bundle and prompt-text targets compile the entries, see Application Prompts.

@meta {
id: "acme-support"
syntax: "1.6.0"
owner: "team-support"
risk: "medium"
}
@identity {
"""
You work for the Acme support team.
"""
}
@restrictions {
- "Never promise a refund before it is approved."
}
@prompts {
refund-reply: {
description: "Answer a customer refund request"
risk: "high"
dataClasses: ["pii"]
compose: [identity, restrictions]
model: { ref: "claude-sonnet-5-5", temperature: 0.2, maxTokens: 800 }
inputs: { message: { type: "string", description: "Customer message" } }
output: {
decision: { type: "enum", options: ["approve", "escalate"] }
citations: { type: "list", items: { type: "string" } }
}
system: "You answer refund requests for Acme customers."
user: """
Customer wrote: {{input.message}}
"""
}
}
Try in Playground
FieldRequiredTypeDescription
entry keyYesnameLetters, digits, _ and -, up to 64 characters, unique when case is ignored
descriptionYesstringWhat the prompt does
system, userOnestringMessage text, a quoted string or a """ text block. At least one is required
composeNostring[]Blocks to compose into the system message: identity, context, knowledge, standards, restrictions, examples. Each must exist in the resolved program
modelNoobjectref (required) plus temperature, topP, topK, maxTokens, stop, seed
inputsNoobjectName to { type, description, options?, default? }. Types are string, number, boolean, enum
outputNoobjectField name to { type, description?, options?, items? }. Types add list, which needs items
risk, owner, domain, dataClasses, reviewersNomixedGovernance values. risk is low, medium, high or critical. @meta can hold defaults

The parser rejects a field named __proto__ in any block with a located parse error. PS042 rejects prototype, constructor and any other name that every JavaScript object has. A key also cannot be index, readme or a Windows device name such as con or com1, in any case. When the file has @prompts, @meta.id must not contain .., must not start or end with /, and each / segment uses letters, digits, ., _ and -.

Runtime slots. {{input.name}} inside a string or text block marks a value the caller supplies later. name must be a declared input. A slot has no spaces, filters or expressions. Write \{{ to keep two literal braces: the backslash and braces stand for {{, and no slot is read there. Slots are only live in the entry’s own system and user text. They are not allowed in the blocks named in compose. Slots must be written inside a string, because the unquoted form {{input.name}} does not parse.

Any other unescaped {{ in system or user is an error at every syntax version: {{ input.name }}, {{Input.name}}, {{input.a.b}} and a bare {{name}} all fail PS043. Slots in the system message are allowed but warned about by PS046, because caller input then reaches the most trusted message.

Composition. @inherit merges prompts by key: the child’s text and scalars win and arrays are joined. @extend prompts.<key> appends text and merges inputs and output. @override prompts.<key> replaces the entry, and @override prompts.<key>.user replaces one field. @use adds the imported prompts, and a same-key entry with different content is a resolve error. @use does not carry @meta, so set governance values on the entries themselves.

Diagnostics.

CodeSeverityChecks
PS042errorKeys, fields, parameters, contracts, compose, governance values, the @meta.id, floating model aliases at high risk
PS043errorUndeclared or malformed slots, and slots or dotted names in composed blocks
PS044warningDeclared inputs that no slot uses
PS046warningSlots in the system message

PS045 and PS018 also apply, see Versions and Diagnostics. PS045 skips fenced code blocks, inline code spans and escaped \{{.

@extend Block

Modify inherited or existing blocks:

# Extend a top-level block
@extend identity {
"""
Additional identity information.
"""
}
# Extend a nested path
@extend standards.code {
frameworks: [react vue]
}
# Extend multiple levels deep
@extend standards.code.testing {
e2e: true
coverage: 90
}

Replacing Regular Block Fields

Syntax 1.3.0 adds explicit replacement for regular block fields. Add ! after a field name inside @extend to replace its complete prior value:

@meta { id: "project" syntax: "1.4.0" }
@inherit ./company-base
@extend standards {
testing!: ["Use Vitest"]
linting: ["Use ESLint"]
}
Try in Playground

testing! replaces the inherited testing value. Unmarked linting keeps the normal merge behavior. Replacement also works after @use, with aliased imports, and at nested target paths:

@use ./shared as shared
@extend shared.standards.tooling {
frameworks!: ["Vue"]
}
Try in Playground

If the field does not exist, replacement sets it. Later overlays operate on the resulting value. The modifier applies only to direct fields in regular block extensions. Skill properties retain their dedicated merge and sealing semantics, so ! is rejected when @extend targets @skills.

The ! modifier cannot be combined with a default value: field!: value = default is rejected, because a replacement and a fallback default are mutually exclusive.

Skill-Specific Extend Semantics

When @extend targets a skill definition inside @skills, properties follow dedicated merge strategies:

StrategyProperties
Replacecontent, description, trigger, userInvocable, allowedTools, disableModelInvocation, context, agent, license
Appendreferences, requires
Shallow mergeparams, inputs, outputs

Reference Negation

Prefix an entry with ! in an @extend block to remove it from the base before appending:

@extend skills.code-review {
references: [
"!references/deprecated.md"
"references/replacement.md"
]
}

Path matching is normalized ("!./foo.md" matches "foo.md"). Only works in @extend blocks on append-strategy properties (references, requires).

Sealed Properties

Prevent @extend from overriding specific replace-strategy properties:

@skills {
expert: {
content: """..."""
sealed: ["content", "description"]
}
}
Try in Playground

sealed: true seals all replace-strategy properties. Attempting to override a sealed property is a compilation error. Append-strategy properties are not affected.

Overlay Consistency Warnings

The resolver emits warnings during compile when an @extend overlay drifts from its base. These warnings are always shown (not gated by --verbose or --strict):

WarningTrigger
Orphaned extend@extend targets a block that doesn’t exist (base removed/renamed)
Stale skill target@extend inside @skills would create a new skill not defined by the base
Negation orphan!entry in references/requires doesn’t match any base element

These come from the resolver, not the validator (PS0XX rules). They appear during prs compile, not prs validate. See the Skill Overlays Guide for examples and remediation.

Values

Primitive Types

TypeExamples
String"hello", 'world'
Number42, 3.14, -10
Booleantrue, false
Nullnull

Strings

PromptScript supports two string syntaxes:

Single-line Strings

Use double or single quotes for short, single-line values:

@shortcuts {
"/review": "Review code for quality and best practices"
"/help": 'Show available commands'
}
Try in Playground

Supported escapes are \n, \t, \r, \", \', and \\. Each escape is decoded once, from left to right. For example, "C:\\new" produces the literal path C:\new, not a newline. This applies to both quote styles.

Multi-line Strings

Use triple quotes (""") for content that spans multiple lines:

@shortcuts {
"/test": """
Write unit tests using:
- Vitest as the test runner
- AAA pattern (Arrange, Act, Assert)
- Target >90% coverage
"""
}
Try in Playground

Multi-line strings:

  • Preserve line breaks and formatting
  • Are ideal for lists, instructions, and documentation
  • Can be used anywhere a string is expected

When to Use Which

Content TypeRecommended Syntax
Short description (1 line)"..." or '...'
Multiple lines, lists, steps"""..."""
Code examples, documentation"""..."""

Both forms are semantically equivalent - choose based on readability.

Example: Mixed Usage

@shortcuts {
# Single-line - simple description
"/review": "Review code for quality and best practices"
# Multi-line - detailed instructions
"/deploy": """
Deploy to production:
1. Run tests: pnpm test
2. Build: pnpm build
3. Deploy: pnpm deploy:prod
"""
# Single-line - short command
"/format": "Run prettier on all files"
}
Try in Playground

Identifiers

Bare words are treated as strings:

@meta {
team: Frontend # Same as "Frontend"
}

Arrays

tags: [frontend react typescript]
patterns: ["hooks" "composition" "render props"]
numbers: [1 2 3]

Objects

code: {
style: "functional"
testing: {
required: true
coverage: 80
}
}

Template Expressions

Use {{variable}} syntax to reference template parameters:

@meta {
id: "template-example"
syntax: "1.0.0"
params: {
projectName: string
port: number = 3000
}
}
@identity {
"""
You are working on {{projectName}}.
"""
}
@context {
project: {{projectName}}
devServer: "http://localhost:{{port}}"
}
Try in Playground

Template expressions are resolved during inheritance resolution when parameters are bound.

A dotted name such as {{input.name}} is never a template variable. Template resolution leaves it untouched, which keeps runtime slots in @prompts text intact. See @prompts.

Valid Variable Names:

  • Must start with a letter or underscore
  • Can contain letters, numbers, and underscores
  • Examples: {{name}}, {{projectName}}, {{_internal}}

Template vs Environment Variables:

SyntaxResolvedPurpose
{{var}}At resolve timeTemplate parameters
${VAR}At parse timeEnvironment variables
# Environment variable - from system at parse time
apiUrl: "${API_URL:-https://api.example.com}"
# Template variable - from @inherit params at resolve time
project: {{projectName}}

Type Expressions

Range

Numeric range constraint:

strictness: range(1..10)
verbosity: range(0..5) = 2

Enum

String enumeration:

format: enum("json", "text", "markdown")
level: enum("debug", "info", "warn", "error") = "info"

Comments

Single-line comments with #:

# This is a comment
@meta {
id: "project" # Inline comment
syntax: "1.0.0"
}

Comments are not part of the compiled output. The parser keeps them next to the AST so its .prs printer can write them back; see the parser README.

URL Imports

PromptScript supports Go-module-style bare URL imports in @use and @inherit declarations. A URL import references a Git repository directly by its host path — no registry alias required.

Basic URL Import

@meta { id: "my-project" syntax: "1.0.0" }
# Import from a public GitHub repo
@use github.com/acme/shared-standards/@fragments/security
# Import from GitLab
@use gitlab.com/myorg/prompts/@stacks/python
Try in Playground

Extended Version Syntax

Append a version specifier after the import path with @:

# Exact tag
@use github.com/acme/shared-standards/@org/[email protected]
# Semver range (latest compatible patch)
@use github.com/acme/shared-standards/@org/base@^1.0.0
# Branch
@use github.com/acme/shared-standards/@org/base@main
Try in Playground
SpecifierMeaning
@1.2.0Exact tag v1.2.0 or 1.2.0
@^1.0.0Latest tag matching ^1.0.0 (semver)
@mainTip of branch main
(none)Default branch as configured

Auto-Discovery

When the imported path does not contain .prs files, PromptScript automatically discovers and converts native AI plugin files:

Source File PatternImported As
SKILL.md in root or skills/ directory@skills block
.claude/agents/*.md@agents block
.claude/commands/*.md@shortcuts block
.github/skills/*/SKILL.md@skills block

This means you can import skills from any repository — including projects that were not authored with PromptScript:

@meta { id: "my-project" syntax: "1.0.0" }
# Repo has SKILL.md but no .prs files — auto-discovered
@use github.com/some-org/claude-skills/skills/tdd-workflow
Try in Playground

Alias vs URL Import

Registry aliases (configured via registries in promptscript.yaml) are a shorthand for URL imports. Both resolve to the same Git fetch:

# With alias (configured as @company -> github.com/acme/base)
@inherit @company/@org/base
# Equivalent full URL import
@inherit github.com/acme/base/@org/base

See Registry Aliases for alias configuration.

Path References

Path syntax for imports and inheritance:

FormatExampleDescription
Namespaced@company/teamRegistry namespace
Versioned@company/[email protected]With version
Relative./parentRelative path
Nested@company/guards/securityNested path
URLgithub.com/org/repo/@pathGo-style URL import
URL+vergithub.com/org/repo/@path@^1.0.0URL with version
SSH[email protected]:org/repo/@pathSCP-style Git import

Reserved Words

The following are reserved and cannot be used as identifiers:

Literals:

  • true, false, null

Type expressions (for @params):

  • range, enum

Directives:

  • meta, inherit, use, extend, as

Block names:

  • identity, context, standards, restrictions
  • knowledge, shortcuts, commands, guards, params
  • skills, agents, local
  • workflows, hooks, mcpServers, plugins, examples
  • prompts

Prompt Files

@prompts holds the prompts of an application. GitHub Copilot prompt files are generated from @shortcuts with prompt: true. Use the @workflows block for reusable procedures.

File Extensions

ExtensionDescription
.prsPromptScript source file
.promptscriptAlternative extension (supported)

Known Issues & Gotchas

Multiline Strings in Objects

Multiline strings ("""...""") cannot be used as “loose” content inside an object with curly braces. They must always be assigned to a key.

❌ Invalid:

@standards {
diagrams: {
format: "Mermaid"
types: [flowchart sequence]
"""
Example:
```mermaid
flowchart LR
A[Input] --> B[Process] --> C[Output]
```
"""
}
}

This will cause a parse error:

Expecting token of type --> RBrace <-- but found --> '"""...

✅ Valid - assign to a key:

@standards {
diagrams: {
format: "Mermaid"
types: [flowchart sequence]
example: """
```mermaid
flowchart LR
A[Input] --> B[Process] --> C[Output]
```
"""
}
}

✅ Valid - use at block level:

@knowledge {
"""
Multiline content works directly in blocks
without needing a key assignment.
"""
}
Try in Playground

Rule of Thumb

Inside { } braces, everything needs a key. Multiline strings without keys only work directly inside blocks like @identity { ... } or @knowledge { ... }.

Environment Variable Interpolation

String values can reference environment variables for dynamic configuration:

@context {
api-endpoint: "${API_ENDPOINT}"
environment: "${NODE_ENV:-development}"
}
Try in Playground

Syntax

PatternDescription
${VAR}Substitute with variable value
${VAR:-default}Substitute with variable or default

Syntax 1.5.0 accepts a reference without quotes wherever a value is expected, which keeps single-variable fields readable:

@meta {
id: "unquoted-env-vars"
syntax: "1.5.0"
}
@context {
environment: ${NODE_ENV:-development}
regions: [${PRIMARY_REGION:-us-east-1}, "eu-west-1"]
}
Try in Playground

The value is always a string. Quote the reference when the field mixes it with other text.

Examples

@meta {
id: "env-vars-example"
syntax: "1.0.0"
}
@context {
project: "My App - ${PROJECT_NAME:-default}"
"""
Running in ${NODE_ENV:-development} mode.
API Key: ${API_KEY}
"""
}
Try in Playground

Missing Variables

If a variable is not set and no default is provided:

- An empty string is substituted
- A warning is logged to the console
This follows Linux shell behavior for unset variables.

Best Practices

  1. Always provide defaults for non-sensitive values
  2. Never commit secrets - use environment variables for API keys
  3. Document required variables in your project README

Generated Section Headers

Syntax 1.5.0 lets source files override human-readable section titles without forking a formatter. Place contextual @header directives directly inside a registered owner block:

@meta {
id: "localized-project"
syntax: "1.5.0"
}
@standards {
@header "Coding Rules"
@header git-commits "Commit Rules"
@header documentation "Dokumentacja zespołu"
code: ["Use strict TypeScript"]
git: { format: "conventional" }
documentation: { verifyAfter: true }
}
Try in Playground
  • @header "Title" names the block’s primary generated section.
  • @header <section-key> "Title" names a derived section.
  • Titles must be non-empty, single-line strings.
  • Canonical section keys use kebab-case.
  • Source overrides take precedence over formatter configuration and target defaults.
  • Overrides change only human-readable titles. Filenames, frontmatter properties, XML tags, and structured JSON, TOML, or YAML keys stay unchanged.
  • Ordinary header and headers fields remain domain data, including nested HTTP headers in @mcpServers.
Section keyPrimary ownerFallback ownersKeyless @header owner
project@identity@context@identity
tech-stack@context@standards-
architecture@context--
context@context-@context
code-standards@standards-@standards
git-commits@standards--
configuration-files@standards--
commands@shortcuts@commands, @knowledge@shortcuts, @commands
post-work@knowledge--
documentation@standards--
diagrams@standards--
knowledge@knowledge-@knowledge
restrictions@restrictions-@restrictions
examples@examples-@examples

For registered text-only primary owners, syntax 1.5.0 also recognizes an initial ## Heading as a compatibility fallback:

@identity {
"""
## Project Instructions
Follow repository conventions.
"""
}
Try in Playground

The formatter emits that heading once and preserves the remaining body. Explicit @header metadata always wins over this fallback. Syntax 1.4.x and earlier keep the heading as ordinary body text, so existing output remains unchanged.

Hook Target Executable Overrides

An @hooks target override supports event, command, script, matcher, timeoutMs, statusMessage, continueOnFailure, enabled, and cwd. A target may define either command or script to replace the base executable for that target. It inherits the base executable when neither field is present, and PS034 rejects overrides that define both.

Replacement executables use the same command interpolation, script path, interpreter, and argument validation as base executables. Enabled target scripts are included in Node and browser compiler resource validation. Disabled target overrides emit no hook and do not require their script resource.

Terminal Command Hook Portability

pre-terminal-command supplies deterministic native defaults for terminal policy: Factory Execute, Claude and Codex Bash, Windsurf pre_run_command, Cursor run_terminal_cmd, Gemini run_shell_command, and VS Code run_in_terminal. Set targets.<name>.matcher to replace a native tool name. Cursor, Gemini, and VS Code emit PS4002 because their coverage is best effort. GitHub Copilot CLI/cloud and Grok omit the event with PS4002 because their repository hook contracts do not guarantee terminal interception.

Hook Project Root Failure

Environment-root and Git-root wrappers exit non-zero before an interpreter or command runs when they cannot resolve a non-empty project root. Native-cwd and workspace-cwd targets retain host-provided cwd fields and report PS4002 because PromptScript cannot independently verify the host cwd. No target wrapper falls back to the process working directory.

Canonical Block Shape Reference

See Block Shapes for the canonical shape, compatibility forms, merge rules, diagnostics, and formatter behavior of every built-in block.

Atomic Replacement with @override

Syntax 1.5.0 adds @override for replacing a complete existing target:

@standards {
testing: ["Use Jest", "Use Mocha"]
tooling: { runner: "jest" coverage: 80 }
}
@override standards.testing {
["Use Vitest"]
}
@override standards.tooling.runner {
"vitest"
}
@extend standards {
testing: ["Require coverage"]
}

@override standards.testing replaces the complete array. The later @extend then adds to that replacement. A root override replaces the complete block body:

@override standards {
testing: ["Use Vitest"]
}

Root replacements accept regular text, object, array, or mixed block bodies. Nested replacements also accept standalone object, string, number, boolean, and null values. The complete target path must already exist when the operation runs. Missing targets and traversal through scalar values are errors.

Operations use declaration order in syntax 1.5.0. This includes @inherit, top-level @use, local blocks, @extend, and @override. An @override used with an older declared syntax also uses declaration order so replacement remains deterministic, while PS018 requests a syntax upgrade.

Resolver derives operation mode from the complete reachable composition graph. Inherited files, top-level imports, and inline composed skills can enable declaration order for a lower-version entry file. Such mixed-version graphs remain valid, while PS018 reports the ordered-operation requirement. Graphs without ordered sources retain legacy phase ordering.

Use the forms according to intent:

FormBehavior
@extendAdd or merge content using the target shape’s merge policy.
field!Compatibility replacement for one direct regular extend field.
@overrideReplace one complete existing block or nested target value.

@override cannot change or remove sealed skill properties. @override { ... } without a target remains a legal custom block named override.

This complete example exercises root and nested replacement shapes:

@meta { id: "override-shapes" syntax: "1.5.0" }
@identity { """Old identity""" }
@restrictions { - "Old restriction" }
@standards {
testing: ["Use Jest"]
config: { enabled: true retries: 1 }
}
@override identity { "New identity" }
@override restrictions { ["No unsafe casts"] }
@override standards {
"""Required engineering rules."""
testing: ["Use Vitest"]
config: { enabled: true retries: 1 }
- "Document failures"
}
@override standards.config.enabled { false }
@override standards.config.retries { 3 }
Try in Playground

AST compatibility timeline

The compiler and validator use CanonicalProgram as their internal contract. Resolver results expose canonicalAst as the primary representation and retain ast for legacy integrations. Formatter implementations can opt into formatCanonical(); legacy formatters receive one detached projection through toLegacyProgram().

ResolvedAST now always includes canonicalAst: successful resolutions carry a CanonicalProgram, while failed resolutions carry null. Integrations that construct ResolvedAST values must provide this field.

During the remaining 1.x compatibility window, integrations should migrate from Program to CanonicalProgram. A future major release may remove legacy-only entry points after the formatter and integration ecosystem has migrated.