# pi-config-valid Pi package and project resource declarations must have valid types | | | |---|---| | **Severity** | warning (auto) | | **Autofix** | - | | **Since** | v0.21.0 | | **Repo Types** | pi, pi-package | | **Category** | [Pi](pi.md) | ## Why Pi reads resource declarations from `package.json#pi` and project `.pi/settings.json`, and handles a malformed field differently in each file. - **`.pi/settings.json`** is not sanitized. A number or object where an array belongs, or a non-string entry, stops Pi at startup with a `TypeError`. A string is iterated character by character, so `"skills": "./x"` makes Pi scan the filesystem root. - **`package.json#pi`** fields that are not arrays of strings are ignored, so that resource type silently loads nothing. This rule names the field and the expected type before Pi runs. ## Checks - JSON must parse to an object. Duplicate keys use JavaScript's last-value behavior. - The package's `pi` field must be an object when present. - `extensions`, `skills`, `prompts`, and `themes` must be arrays of strings. - Project `packages` must contain source strings or objects with a nonempty `source`, optional resource filter arrays, and an optional boolean `autoload`. - `null` is accepted where Pi reads it as absent: top-level settings resource fields and `packages`, a package entry's `autoload`, and `pi` resource fields. A `null` package filter still warns, because Pi drops that package. - Pi only tests `autoload === false`, so a non-boolean `autoload` warns: Pi loads that package as if autoload were on. Unknown npm fields, Pi gallery metadata, and unrelated settings are accepted. Empty resource lists are valid. Findings are consolidated by file and default to warning, because the same checks cover `package.json#pi` fields that Pi ignores. Legacy project settings such as `"skills": {"customDirectories": ["../skills"]}` are normalized before validation and discovery, as in Pi's settings loader. A nonempty `customDirectories` array supplies the skill paths; an empty, missing, or non-array value removes the legacy override and leaves autoload active. Malformed array members still warn. This migration applies only to project settings, not package manifests or package selectors. ## How to fix Put paths in arrays, such as `"skills": ["./skills"]`. In project settings, use `"packages": [{"source": "npm:my-package", "skills": []}]` to configure a package and disable its skills. Keep settings paths relative to the directory containing `settings.json`, and package paths relative to `package.json`. ## Examples Use arrays for resource paths: ```json {"pi": {"skills": ["./skills"], "prompts": ["./prompts"]}} ``` A string such as `"skills": "./skills"` is invalid. In a package manifest it drops that resource type; in `.pi/settings.json` Pi reads each character as a path and scans from the filesystem root. ## Discovery and boundaries The `pi-package` repository type comes from a `pi` key, the `pi-package` npm keyword, or a repository-local directory listed in project `packages`. Ordinary npm packages and unmarked conventional directories do not establish Pi ownership. A conventional-only package can be selected through project settings. The `pi` type comes from `.pi` project resources, including those in monorepo subprojects. Portable `SKILL.md` files not selected by Pi keep their Agent Skills checks. A `pi` object replaces conventional Pi package discovery; omitted resource types and empty arrays contribute no resources. Without that object, packages use `extensions/`, `skills/`, `prompts/`, and `themes/`. Manifest globs discover visible paths; `!` exclusions, `+` exact re-inclusions and `-` exact exclusions filter those resources. Project resource lists resolve literal paths and use wildcards as filters, matching the native loader. Project prompt and theme autoload is shallow; explicit prompt directories and package prompts are recursive. Locally referenced packages are linted as authored packages, including resources that a particular consumer's package filters disable. Package filters are validated, but do not suppress diagnostics on the package's authored content. Remote sources and paths outside the checkout are not loaded. Glob traversal does not descend through symlinked directories; exact contained roots may use symlinks. Resource walks read `.gitignore`, `.ignore`, and `.fdignore` at or below each resource root, using Pi's directory-relative prefixing. Pi-only packages receive shared prose checks for README, commands, agents and rules. Claude configuration at `hooks/hooks.json` or `settings.json` is checked only when the directory also declares Claude ownership. Extensions are represented as entrypoint nodes. Themes are configuration nodes, not prose; theme colors and runtime extension behavior are outside this rule. First-party `.pi/mcp.json` servers receive shared MCP security checks and the opt-in [pi-mcp-valid](pi-mcp-valid.md) rule. ## Upstream reference Pinned to Pi commit [`36b60d2e`](https://github.com/earendil-works/pi/tree/36b60d2e8985899743c4cf5bd5f8929832a3f05d), particularly `packages/coding-agent/src/core/pi-manifest.ts`, `package-manager.ts`, and `skills.ts`. See the [Pi package documentation](https://pi.dev/docs/latest/packages). ## Configuration ```yaml rules: pi-config-valid: enabled: auto # true | false | auto severity: warning ``` *Run `skillsaw explain pi-config-valid` to see this documentation and the rule's effective configuration in your terminal.*