# Extension settings contract Every repository extension that reads configuration declares it through its own committed `src/pi-ext-settings.ts` and remains usable when installed without the rest of `pi-ext`. `scripts/codegen/templates/pi-ext-settings.ts` generates these local copies through `mise run //:codegen` for the extensions listed in the `pi-ext-settings` target of `scripts/codegen/targets.json`. Root and extension `check` tasks compare generated files byte-for-byte, and the template's own tests live in `scripts/codegen/pi-ext-settings.test.ts`. Debug selection is separate and follows the [debug contract](DEBUG.md). ## Declarations A `SettingDeclaration` names a `key`, a `parse` function, a `default`, an optional `flag`, and an optional `env` variable. For example: ```ts import { parseBooleanSetting, registerSettingFlag, resolveSetting, type SettingDeclaration, } from "./src/pi-ext-settings.ts"; const ENABLE_SETTING: SettingDeclaration = { key: "nushell.enable", parse: parseBooleanSetting, default: false, flag: { name: "nushell", type: "boolean", description: "Enable Nushell tools at startup" }, env: "PI_NUSHELL_ENABLE", }; registerSettingFlag(pi, ENABLE_SETTING); // at extension load const setting = resolveSetting(pi, ctx, ENABLE_SETTING); // at session_start or first use ``` Keys start with the extension directory name and use camelCase leaves, such as `git-safe.cloneTimeoutMs`. An extension may declare its whole block, such as `ultra`, when one parser validates it as a unit. Renamed keys get no aliases; a stale key is invalid rather than silently ignored. ## Resolution Each declaration resolves independently in this order: flag, env, trusted project `.pi/settings.json`, user `~/.pi/agent/settings.json`, default. Pass the real `ctx.isProjectTrusted()`; untrusted project settings are never read. Settings needed before any session exists, such as keyboard shortcuts, resolve at load with `process.cwd()` and trust `false`, so they come from env and user settings only. When project and user values are both plain objects, they deep-merge with project winning per key; arrays and scalars replace. Resolve once per session or operation, not on hot paths. ## Sources Flags suit startup toggles only. Pi flags are boolean or string, and a boolean flag can only turn behavior on. `registerSettingFlag` registers a flag without a default so an omitted flag defers to lower sources; never call `pi.registerFlag` or `pi.getFlag` directly. Environment variables keep established names, including third-party ones such as `FIREFOX_BIN`. New variables are `PI__`, uppercasing the slug and replacing hyphens with underscores. An empty environment value counts as set. Negated variables such as `PI_MOCKUP_NO_OPEN` invert the boolean parser for the env source only. Credentials and tokens stay in environment variables or Pi's credential store and never become settings. ## Validation Settings files accept JSON types only; numeric and boolean strings are accepted only from flags and env. Use `parseBooleanSetting` and `parseNumberSetting`, then add range checks; timer and subprocess timeouts are capped at `MAX_TIMER_MS`. A parser returns `undefined` or throws an `Error` to reject a value; thrown messages appear in the reported error. An invalid value never falls through to a lower source. Report it once through `ctx.ui.notify` with warning severity and use the built-in default, or fail the operation when the setting is a safety limit. Never write diagnostics to stdout. ## Tests and documentation Each participating extension keeps `__tests__/settings.test.ts` covering the default, each declared source, trusted project over user, ignored untrusted project, and invalid values without fallthrough. Tests assert observable behavior where practical and use `test/harness` with its sandboxed home; emit `session_shutdown` before disposing a session. Each extension README documents its keys, env variables, flags, defaults, valid ranges, trust, and invalid-value behavior. ## Enforcement `scripts/extensions.mjs` rejects direct `getGlobalSettings`, `getProjectSettings`, `registerFlag`, and `getFlag` calls outside generated `src/pi-ext-*.ts` files. `SettingsManager.create` remains allowed for building child agent sessions.