ccppccpp home
Browse the documentation

Plugins · CCPP PQL

Functions

A PQL function is a reusable block defined in a .pql file. #id(...) expands it at compile time; FOR repeats statements.

A function is PQL you reuse: a .pql file with a YAML frontmatter (its signature) and a PQL body. A call #id(...) produces statements at compile time. It never runs a tool or a Claude action.

.claude/pql/add-features.pql
---
format: 1
description: Adds numbered features.
kind: fragment
parameters:
  - name: name
    type: NonEmptyString
  - name: n
    type: integer
    default: 1
    minimum: 1
    maximum: 20
let:
  end: "params.n + 1"
---
FOR i IN RANGE(1, {{ vars.end }}) {
  ADD "{{ params.name }} {{ vars.i }}";
}
The call
$ FROM @packages/pql {
  #add-features("Diagnostic", 3);
}

It compiles to three objectives: ADD "Diagnostic 1", "Diagnostic 2", "Diagnostic 3".

Where definitions live

LayerDirectory
project.claude/pql/*.pql
user~/.ccpp/pql/*.pql ($CCPP_HOME/pql/)

The id is the file name without .pql, in kebab-case. A project definition replaces the user one entirely, even when it is disabled or invalid. An invalid definition blocks only the programs that call it. ccpp pql functions lists them with their status (active, disabled, overridden, invalid).

.claude/pql/ is protected by CPO like .claude/policies/: with claudeCanEdit: false, Claude cannot change a definition.

Frontmatter

FieldMeaning
format1
descriptionrequired, at most 200 characters
kindfragment (default), how or program
parametersordered list: name, type, optional default and constraints; required ones first
letvariables computed with CEL from params and other vars
id, category, enabled, claudeCanEdit, x-*optional

Parameter types: string, NonEmptyString, integer, number, boolean, array (with items), the references PathRef, ExistingPathRef, ExistingDirectoryRef, NewDirectoryRef, and any identifier domain (principle, naming-convention…). Constraints: minimum, maximum, minLength, maxLength, enum, items.

Kinds

KindCalled inProduces
fragmenta scope or branch bodystatements of the active scope, without SESSION SUCCESS
howa body, a HOW block or another how functionHOW directives only
programthe root of the promptroot scopes

Rules declared inside a fragment (stop rules, directives, policy references) apply inside it only.

Calls

#id(a, b); by position or #id(name: a, n: b); by name, never mixed. Nothing is converted: "3" is not an integer, null is not an omitted argument, a string is not a reference. Lists are written ["a", "b"].

let, interpolation and FOR

  • let maps names to CEL expressions, evaluated once per call. They read params.* and vars.*.
  • {{ params.x }} and {{ vars.y }} insert a value. Alone in a value position, the type is kept (LIMIT FILE TO {{ params.n }} LINES;); inside a string, a scalar is rendered as text.
  • FOR v IN RANGE(a, b) { … } runs from a included to b excluded; FOR v IN {{ params.list }} { … } runs over a list. The variable is vars.v, read-only. FOR also works directly in a prompt.

Limits

Recursion, direct or indirect, is refused (PQL-F050). Call depth, call count, iterations, statements, output size and CEL size are bounded for the whole compilation; going over one aborts the expansion with an error (PQL-F060 to PQL-F065), never a truncated program. Every expanded statement remembers its file and call chain, so a diagnostic points into the definition with via #a, #b.