Reference

psy.config.ts

Every field, its type, and what it does.

import { defineConfig } from "@psy/config";

export default defineConfig({
    source: ["./src"],
});

psy.config.ts controls sources, outputs, frameworks, adapters, formatting, linting, and diagnostic policy.

It deliberately cannot inject symbols, activate declaration keywords, modify language semantics, or change the grammar. Those come from Psy source alone, which is what lets you read a .psy file without auditing a TypeScript file first.

Fields

FieldTypeMeaning
sourcestring[]files or directories of .psy sources, relative to the root
frameworksFramework[]domain validators, run after resolution
targetsTarget[]generators, run by psy build
formatFormatOptionsindent, printWidth, trailingCommas
lintLintOptionsper-rule settings
diagnosticsDiagnosticsOptionswarningsAsErrors, maxWarnings
artifactsArtifactOptionswhat a build stamps into its own output

format

format: {
    indent: 4,          // spaces per level
    printWidth: 100,    // width at which import lists break
    trailingCommas: true,
}

lint

lint: {
    noUnusedImports: true,
    noUnusedSpecs: "error",
    noUnusedConsts: true,
    noUnusedUse: true,
    specNaming: true,
    propertyNaming: "off",
    keywordNaming: false,
}

Accepted settings: true, false, "off", "info", "warning", "error". Rules and their codes are in Diagnostic Codes.

diagnostics

diagnostics: {
    warningsAsErrors: true,
    maxWarnings: 0,
}

artifacts

All three are off by default. Generated files belong to your project, not to Psy, so nothing announces itself unless you ask.

artifacts: {
    header: true,
    manifest: true,
    index: true,
}
FieldDefaultEffect
headerfalsea comment naming Psy and the source module, in formats that have comments
manifestfalse.psy-manifest, listing every path a target wrote
indexfalseaggregate index documents, such as the Claude adapter's psy-generated.md
<!-- Generated by Psy from src/skills/ReviewDiff.psy. Do not edit by hand; edit the .psy source and run `psy build`. -->

JSON never carries a header, because JSON has no comment syntax. A target may override index for itself — claude({ index: false }) opts out even when the project opts in.

Pruning depends on these

psy build removes artifacts it previously wrote but no longer generates, identifying them by a path in the .psy-manifest or a file still carrying the header.

With neither enabled it can prove nothing, so it does not prune, and says so:

note: stale artifacts were not removed; a build can only remove what it can
prove it wrote. Set `artifacts.manifest` or `artifacts.header` in psy.config.ts
to enable pruning.

Target options

Adapter targets take their own options. The shipped ones:

Discovery

Psy looks for, in order:

psy.config.ts
psy.config.mts
psy.config.mjs
psy.config.js

in the project root, or at the path given by --config. TypeScript config files are imported directly; modern Node strips the types.

With no config file, Psy compiles . with no frameworks and no targets — enough to check and format.

A config file that cannot be loaded is PSY9001.

Overriding sources for one command

psy check spec
psy format --check build

A positional path overrides source for that command only. A positional argument that does not name an existing file or directory is treated as a command target instead — which is how psy inspect BuildPsy works.

Psy's own config

For reference, the real thing:

import { defineConfig } from "@psy/config";
import { morphic } from "@psy/morphic";
import { claude } from "@psy/adapter-claude";
import { json, yaml } from "@psy/adapter-data";

export default defineConfig({
    source: ["./psy-spec", "./psy-build", "./examples", "./fixtures/valid"],

    frameworks: [morphic()],

    targets: [
        // The Morphic definitions become real Claude Code agents and skills.
        claude({
            output: ".claude",
        }),

        // The language specification, resolved, as one machine-readable document.
        json({
            output: "generated",
            mode: "single",
            file: "psy-spec.json",
            modules: ["spec/"],
            metadata: true,
        }),

        // The same definitions the Claude adapter consumes, as plain data.
        yaml({
            output: "generated/morphic",
            modules: ["build/"],
        }),
    ],

    format: {
        indent: 4,
        printWidth: 100,
        trailingCommas: true,
    },

    lint: {
        noUnusedImports: true,
        noUnusedSpecs: true,
        noUnusedConsts: true,
        noUnusedUse: true,
        specNaming: true,
        propertyNaming: true,
        keywordNaming: true,
    },

    diagnostics: {
        warningsAsErrors: true,
    },
});

On this page