Skip to content

Inheritance Guide

Learn how to build scalable, maintainable instruction hierarchies using PromptScript's inheritance system.

Overview

PromptScript uses single inheritance to build hierarchical instruction sets:

flowchart TD
    A["@org/base<br/>Organization defaults"] --> B["@org/frontend<br/>Frontend team"]
    A --> C["@org/backend<br/>Backend team"]
    B --> D["project-web<br/>Web application"]
    B --> E["project-mobile<br/>Mobile app"]
    C --> F["project-api<br/>API service"]

Basic Inheritance

Use @inherit to extend another PromptScript file:

@meta {
  id: "my-project"
  syntax: "1.0.0"
}

@inherit @company/frontend-team

Try in Playground

The child inherits all blocks from the parent, which can then be extended.

Registry Structure

Organize your registry with namespaces:

registry/
├── @company/
│   ├── base.prs           # Organization base
│   ├── frontend.prs       # Frontend team
│   ├── backend.prs        # Backend team
│   └── mobile.prs         # Mobile team
├── @core/
│   ├── security.prs       # Security standards
│   └── compliance.prs     # Compliance rules
└── @fragments/
    ├── testing.prs        # Testing patterns
    └── logging.prs        # Logging standards

Merge Behavior

Different blocks merge differently during inheritance:

Text Blocks (Concatenate)

@identity, @knowledge, and text content in other blocks concatenate:

# parent.prs
@meta {
  id: "parent"
  syntax: "1.0.0"
}

@identity {
  """
  You are a helpful assistant.
  """
}

Try in Playground

# child.prs
@meta {
  id: "child"
  syntax: "1.0.0"
}

@inherit ./parent

@identity {
  """
  You specialize in React development.
  """
}

Try in Playground

## Identity

You are a helpful assistant.

You specialize in React development.

Objects (Deep Merge)

@standards and object properties deep merge:

# parent.prs
@meta {
  id: "parent"
  syntax: "1.0.0"
}

@standards {
  code: ["Follow clean code principles", "Testing required"]
}

Try in Playground

# child.prs
@meta {
  id: "child"
  syntax: "1.0.0"
}

@inherit ./parent

@standards {
  code: ["Use React framework", "80% test coverage required"]
}

Try in Playground

code:
  # Arrays are concatenated (parent first, then child)
  - Follow clean code principles
  - Testing required
  - Use React framework
  - 80% test coverage required

Arrays (Concatenate)

@restrictions and array values concatenate:

# parent.prs
@meta {
  id: "parent"
  syntax: "1.0.0"
}

@restrictions {
  - "Never expose secrets"
}

Try in Playground

# child.prs
@meta {
  id: "child"
  syntax: "1.0.0"
}

@inherit ./parent

@restrictions {
  - "Always use TypeScript"
}

Try in Playground

## Restrictions

- Never expose secrets
- Always use TypeScript

Shortcuts (Override)

@shortcuts entries override by key:

# parent.prs
@meta {
  id: "parent"
  syntax: "1.0.0"
}

@shortcuts {
  "/test": "Write unit tests"
  "/docs": "Generate documentation"
}

Try in Playground

# child.prs
@meta {
  id: "child"
  syntax: "1.0.0"
}

@inherit ./parent

@shortcuts {
  "/test": "Write tests with Vitest"
  "/lint": "Run ESLint"
}

Try in Playground

## Shortcuts

| Command | Description |
|---------|-------------|
| /test | Write tests with Vitest |
| /docs | Generate documentation |
| /lint | Run ESLint |

Using @extend

The @extend block modifies specific paths:

Extending Top-Level Blocks

@inherit @company/base

# Add to identity
@extend identity {
  """
  Additional identity context.
  """
}

Try in Playground

Extending Nested Paths

@inherit @company/base

# Modify nested structure
@extend standards.code.testing {
  e2e: required
  coverage: 90
}

Try in Playground

Multiple Extensions

@inherit @company/base

@extend identity {
  """
  You are a frontend expert.
  """
}

@extend standards.code {
  framework: "react"
}

@extend restrictions {
  - "Use functional components only"
}

Try in Playground

Skill-Specific Extend Semantics

When @extend targets a skill definition, individual skill properties follow dedicated merge strategies rather than the generic block merge rules:

Strategy Properties
Replace content, description, trigger, userInvocable, allowedTools, disableModelInvocation, context, agent, license
Append references, examples, requires
Shallow merge params, inputs, outputs

Example — overlay content and add a reference file without replacing the base skill's references:

@use @company/skills as skills

@extend skills.code-review {
  content: """
  Enhanced review with stricter security checks.
  """
  references: [
    ./extra-context.md
  ]
}

Try in Playground

The overlay's references list is appended to the base skill's list. The content field replaces the base skill's content entirely.

Reference Negation

Use the ! prefix to remove entries added by a lower layer:

@use @company/skills as skills

@extend skills.code-review {
  references: [
    "!references/deprecated-patterns.md"
    "references/new-patterns.md"
  ]
  requires: [
    "!legacy-tool"
    "modern-tool"
  ]
}

Try in Playground

Negation uses normalized path matching — "!./references/foo.md" matches "references/foo.md". If a negation doesn't match any base entry, a warning is logged during compilation.

Negation applies to append-strategy properties only (references, requires). The ! prefix is only meaningful in @extend blocks — using it in a base skill definition triggers a validator warning (PS028).

Sealed Properties

The sealed property prevents higher layers from replacing specified skill properties:

@skills {
  code-review: {
    content: """
      Critical review workflow.
    """
    sealed: ["content"]
  }
}

Try in Playground

If an @extend block attempts to override a sealed property, compilation fails:

ResolveError: Cannot override sealed property 'content' on skill (sealed by base definition)

Use sealed: true to seal all replace-strategy properties at once. Only the base skill author can set sealed — overlays cannot add or remove it. Append-strategy properties (references, requires) remain extendable even when sealed: true is set.

Composition with @use

Use @use to import and merge fragments (like mixins):

@meta {
  id: "my-project"
  syntax: "1.0.0"
}

@inherit @company/frontend

# Import fragments - blocks are merged into your file
@use @core/security
@use @core/compliance
@use @fragments/testing

# With alias - also available for @extend
@use @fragments/api-standards as api

Try in Playground

How @use Differs from @inherit

Feature @inherit @use
Quantity Single parent only Multiple allowed
Semantics "IS-A" (this project IS a TypeScript library) "HAS-A" (this project HAS security standards)
Purpose Define fundamental project type Add optional capabilities
Merge precedence Child overrides parent Later @use overrides earlier
@extend support Always available Only with alias

When to Use Which

Use @inherit for:

  • Defining your project's fundamental type (library, backend, frontend)
  • Building organizational hierarchies (base → team → project)
  • When you want a single, clear inheritance chain
# This project IS a TypeScript library
@inherit @stacks/typescript-lib

Try in Playground

Use @use for:

  • Adding optional capabilities (security, testing, quality)
  • Mixing in reusable fragments
  • When you need multiple imports
# This project HAS these capabilities
@use @core/security
@use @core/quality
@use @fragments/testing

Try in Playground

Merge Precedence

PromptScript 1.6 applies declarations in source order. Import merge policy and later operations are separate decisions:

@meta { id: "service" syntax: "1.5.0" }

@inherit @stacks/typescript-lib
@use @core/security
@use @core/quality

@standards {
  deployment: ["Require approval"]
}

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

Try in Playground

Use Composition and Precedence as the normative source for conflict rules, declaration order, and resolved examples. This guide focuses on designing inheritance hierarchies rather than restating that matrix.

Fragment Files

Create reusable fragments:

# @fragments/testing.prs
@meta {
  id: "@fragments/testing"
  syntax: "1.5.0"
}

@standards {
  testing: ["Use vitest as test framework", "Maintain 80% code coverage", "Write unit and integration tests"]
}

@shortcuts {
  "/test": {
    description: "Write comprehensive tests"
    content: "Write unit and integration tests for the current change."
  }
  "/coverage": {
    description: "Check test coverage"
    content: "Run coverage and report untested behavior."
  }
}

Try in Playground

When imported with @use @fragments/testing, these blocks are merged directly into your file.

Best Practices

1. Keep Base Configurations Minimal

Organization base should include only universal standards:

# @company/base.prs
@meta {
  id: "@company/base"
  syntax: "1.0.0"
}

@identity {
  """
  You are an AI assistant at ACME Corp.
  Follow company guidelines and best practices.
  """
}

@restrictions {
  - "Never expose credentials"
  - "Follow data protection policies"
}

Try in Playground

2. Use Team Configurations for Specialization

# @company/frontend.prs
@meta {
  id: "@company/frontend"
  syntax: "1.0.0"
}

@inherit @company/base

@identity {
  """
  You specialize in frontend development.
  """
}

@context {
  """
  Tech stack: React, TypeScript, Vite
  """
}

Try in Playground

3. Project Configurations for Specifics

# project.prs
@meta {
  id: "checkout-app"
  syntax: "1.0.0"
}

@inherit @company/frontend

@context {
  project: "Checkout Application"

  """
  E-commerce checkout flow with Stripe integration.
  """
}

Try in Playground

4. Version Your Registry

Use semantic versioning for registry files:

@inherit @company/frontend@1.0.0

Try in Playground

5. Document Inheritance Chains

Include comments explaining the hierarchy:

# Inheritance chain:
# @company/base → @company/frontend → this file
@inherit @company/frontend

Try in Playground

Common Patterns

Platform-Specific Configurations

flowchart TD
    A["@company/base"] --> B["@company/web"]
    A --> C["@company/mobile"]
    A --> D["@company/backend"]

    B --> E["@company/web-react"]
    B --> F["@company/web-vue"]

    C --> G["@company/mobile-ios"]
    C --> H["@company/mobile-android"]

Shared Standards with Team Overrides

# Use shared security, override team-specific
@inherit @company/frontend
@use @core/security
@use @core/compliance

@extend standards.security {
  additionalRules: ["CSP headers"]
}

Try in Playground

Environment-Specific Extensions

@inherit @company/frontend

@context {
  environment: production
}

@extend restrictions {
  - "No console.log statements"
  - "No debug code"
}

Try in Playground

Parameterized Inheritance (Templates)

PromptScript supports parameterized inheritance, allowing you to create reusable templates with configurable values. This is similar to generics in programming languages or Handlebars-like templates.

Defining Parameters

Define parameters in the @meta block using the params field:

# @stacks/typescript-lib.prs
@meta {
  id: "@stacks/typescript-lib"
  syntax: "1.0.0"
  params: {
    projectName: string
    runtime: string = "node18"
    strict?: boolean
    testFramework: enum("vitest", "jest", "mocha") = "vitest"
  }
}

@project {
  name: {{projectName}}
  runtime: {{runtime}}
}

@standards {
  testing: ["Use {{testFramework}} for all tests"]
}

Try in Playground

Parameter Types

Type Syntax Description
string name: string Text value
number count: number Numeric value
boolean enabled: boolean True or false
enum mode: enum("a", "b") One of specified values

Parameter Modifiers

Modifier Syntax Description
Required name: string Must be provided
Optional name?: string Can be omitted (value is undefined)
Default name: string = "value" Uses default if not provided

Passing Parameters

Pass parameters when using @inherit or @use:

# project.prs
@meta {
  id: "my-app"
  syntax: "1.0.0"
}

@inherit @stacks/typescript-lib(projectName: "my-app", runtime: "node20")

Try in Playground

Or with @use:

@use ./fragments/testing(framework: "vitest", coverage: 90)

Try in Playground

Template Variables

Use {{variable}} syntax to reference parameters in content:

@meta {
  id: "template"
  syntax: "1.0.0"
  params: {
    projectName: string
    author: string = "Team"
  }
}

@identity {
  """
  You are working on {{projectName}}.
  This project is maintained by {{author}}.
  """
}

@project {
  name: {{projectName}}
  maintainer: {{author}}
}

Try in Playground

Complete Example

# @stacks/react-app.prs
@meta {
  id: "@stacks/react-app"
  syntax: "1.0.0"
  params: {
    projectName: string
    port: number = 3000
    strict: boolean = true
  }
}

@identity {
  """
  You are a React developer working on {{projectName}}.
  """
}

@context {
  project: {{projectName}}
  devServer: "http://localhost:{{port}}"
  strictMode: {{strict}}
}

@standards {
  code: ["TypeScript strict mode enabled"]
}

Try in Playground

# project.prs
@meta {
  id: "checkout-app"
  syntax: "1.0.0"
}

@inherit @stacks/react-app(projectName: "Checkout App", port: 8080)

@identity {
  """
  You specialize in e-commerce checkout flows.
  """
}

Try in Playground

## Identity

You are a React developer working on Checkout App.

You specialize in e-commerce checkout flows.

## Context

- project: Checkout App
- devServer: http://localhost:8080
- strictMode: true

## Standards

### Code
- TypeScript strict mode enabled

Template Variables vs Environment Variables

PromptScript has two interpolation mechanisms:

Feature Syntax Resolved At Purpose
Environment Variables ${VAR} Parse time System configuration, secrets
Template Variables {{var}} Resolve time Parameterized templates
@context {
  # Environment variable - resolved during parsing
  apiKey: ${API_KEY}

  # Template variable - resolved during inheritance
  project: {{projectName}}
}

Validation Errors

PromptScript validates parameters at compile time:

Missing required parameter:

Error: Missing required parameter 'projectName' for template '@stacks/react-app'

Unknown parameter:

Error: Unknown parameter 'unknownParam' for template '@stacks/react-app'.
Available parameters: projectName, port, strict

Type mismatch:

Error: Type mismatch for parameter 'port': expected number, got string

Invalid enum value:

Error: Type mismatch for parameter 'mode': expected enum("dev", "prod"), got "staging"

Best Practices

  1. Use descriptive parameter names - projectName instead of name
  2. Provide sensible defaults - Reduce required parameters
  3. Document parameters - Add comments explaining each parameter
  4. Validate with enums - Use enums for constrained choices
  5. Keep templates focused - One purpose per template
@meta {
  id: "@stacks/api-service"
  syntax: "1.0.0"
  params: {
    # Name of the API service
    serviceName: string

    # HTTP port for the service (default: 3000)
    port: number = 3000

    # Environment mode
    mode: enum("development", "staging", "production") = "development"
  }
}

Try in Playground

Debugging Inheritance

View Resolved Configuration

prs compile --dry-run --verbose

Validate Inheritance Chain

prs validate --verbose

Common Issues

Circular inheritance detected:

Error: Circular inheritance: a → b → a

Ensure no circular references in your inheritance chain.

Parent not found:

Error: Cannot resolve @company/unknown

Check registry configuration and file paths.

Version mismatch:

Warning: Requested @company/base@2.0.0, found 1.5.0

Update version constraints or registry.

Replacing Complete Values

Use @extend when inherited content should be merged. Use @override with syntax 1.5.0 when the complete existing value should be replaced:

@meta { id: "project" syntax: "1.5.0" }
@inherit @company/base

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

@extend standards {
  testing: ["Require coverage"]
}

The target must exist after preceding declarations are applied. Replacement is atomic, and later declarations operate on the replacement. field! remains supported for replacing one direct regular field inside @extend, but new code should use @override when complete replacement is the intended operation. Sealed skill properties cannot be replaced or removed.