# Configuration reference

> `.impeccable/config.json` and `config.local.json` keys for detector, hook, live, update, and staleness settings, plus live config.json fields and hard path excludes.

- Repository: pbakaus/impeccable
- GitHub: https://github.com/pbakaus/impeccable
- Human docs: https://grok-wiki.com/public/docs/pbakaus-impeccable-adadc04d8de4
- Complete Markdown: https://grok-wiki.com/public/docs/pbakaus-impeccable-adadc04d8de4/llms-full.txt

## Source Files

- `cli/lib/impeccable-config.mjs`
- `skill/scripts/lib/impeccable-paths.mjs`
- `skill/reference/live-setup.md`
- `skill/scripts/hook-lib.mjs`
- `skill/scripts/lib/staleness-notice.mjs`
- `tests/lib/impeccable-config.test.js`

---

---
title: "Configuration reference"
description: "`.impeccable/config.json` and `config.local.json` keys for detector, hook, live, update, and staleness settings, plus live config.json fields and hard path excludes."
---

Impeccable project settings live under `.impeccable/`. Shared team settings go in `config.json`; per-developer overrides go in `config.local.json`. Detector filters, design-hook runtime, update/staleness boot checks, and monorepo `projectRoots` all use that pair. Live mode uses a separate file at `.impeccable/live/config.json` for inject targets and CSP setup state.

## File layout

```text
.impeccable/
├── config.json          # shared (usually committed)
├── config.local.json    # per-developer (gitignored via .git/info/exclude)
├── design.json          # DESIGN.md sidecar (not this page)
├── hook.cache.json      # hook session cache (runtime)
├── hook.pending.json    # Cursor pending findings (runtime)
└── live/
    ├── config.json      # live inject / CSP setup
    ├── server.json      # running live server pid/port
    └── sessions/        # live session state
```

| Path | Role | Typical VCS |
|------|------|-------------|
| `.impeccable/config.json` | Shared project config | Commit |
| `.impeccable/config.local.json` | Local overrides (consent, private ignores, quiet, etc.) | Not committed; writers append `.impeccable/config.local.json` to `.git/info/exclude` |
| `.impeccable/live/config.json` | Live inject targets and CSP flag | Commit when the team shares inject paths |

Malformed JSON is ignored for that file; remaining valid layers and defaults still apply.

## Merge order

Both the CLI (`cli/lib/impeccable-config.mjs`) and the design hook (`skill/scripts/hook-lib.mjs`) read **shared then local**:

1. `.impeccable/config.json`
2. `.impeccable/config.local.json` (wins for scalar / last-write fields)

| Setting type | Merge behavior |
|--------------|----------------|
| Booleans / enums (`hook.enabled`, `hook.quiet`, `hook.consent`, `updateCheck`, `stalenessCheck`, `detector.designSystem.enabled`, `detector.advisoryRules`, `hook.perEditRules`, `hook.auditLog`, `hook.limits`) | Local overwrites shared when present |
| `detector.ignoreRules`, `detector.ignoreFiles` | Union (deduped strings); both files contribute |
| `detector.ignoreValues` | Map-merge by `(rule, value, files)`; local entry with same key replaces shared |
| `detector.extensions` | Merge by extension string; local entry wins per `ext` |
| Legacy detector keys under `hook.*` | Still read for back-compat; canonical `detector.*` wins when both set |

Writes that manage detector ignores (`npx impeccable ignores …`, `hooks ignore-*`) put filters under `detector` and strip legacy filter keys from `hook` while preserving `hook.consent`, `hook.quiet`, and other runtime fields.

## Unified config schema

Canonical shape (shared and/or local):

```json
{
  "updateCheck": true,
  "stalenessCheck": true,
  "projectRoots": ["apps/*", "packages/ui"],
  "detector": {
    "ignoreRules": [],
    "ignoreFiles": [],
    "ignoreValues": [],
    "designSystem": { "enabled": true },
    "advisoryRules": "exclude",
    "extensions": []
  },
  "hook": {
    "enabled": true,
    "quiet": false,
    "consent": "accepted",
    "auditLog": null,
    "perEditRules": "immediate",
    "limits": {
      "maxFindings": 5,
      "maxChars": 8000,
      "maxFileBytes": 131072
    }
  }
}
```

### Top-level keys

<ParamField body="updateCheck" type="boolean" default="true (when unset)">
When `false`, `context.mjs` skips the skill update directive at boot. Local config overrides shared. Also disabled by `IMPECCABLE_NO_UPDATE_CHECK`.
</ParamField>

<ParamField body="stalenessCheck" type="boolean" default="true (when unset)">
When `false`, boot skips `CONTEXT_STALE` collection for PRODUCT/DESIGN/config drift. Local overrides shared. Also disabled by `IMPECCABLE_NO_STALENESS_CHECK=1`.
</ParamField>

<ParamField body="projectRoots" type="string[]">
Optional monorepo project globs relative to the repo root. Takes precedence over package-manager workspace lists for paths they match (including negations). Used by context resolution and doctor diagnostics.
</ParamField>

### `detector` keys

Shared by `npx impeccable detect` (unless `--no-config`) and the design hook. `hook.enabled` does **not** turn off manual CLI scans.

<ParamField body="detector.ignoreRules" type="string[]" default="[]">
Rule ids suppressed project-wide (normalized lowercase). Example: `"side-tab"`. Prefer value-scoped ignores for fonts/motion when a single value is intentional.
</ParamField>

<ParamField body="detector.ignoreFiles" type="string[]" default="[]">
Globs that suppress **all** rules for matching paths. Matches absolute path, basename, and project-relative path. Supports `**`, `*`, `?`, and `{a,b}`.
</ParamField>

<ParamField body="detector.ignoreValues" type="object[]" default="[]">
Per-rule value suppressions. Each entry:

| Field | Type | Notes |
|-------|------|--------|
| `rule` | string | Required; normalized lowercase |
| `value` | string | Required; normalized (trim, collapse space, lowercase). Use `"*"` only with `files` for file-scoped full-rule silence |
| `files` | string[] | Optional globs; omit for project-wide value match |
| `file` | string | Accepted alias for a single path; normalized into `files` |
| `reason` | string | Optional human note |
| `createdAt` | string | Optional timestamp preserved on rewrite |

Value matching: string equality after normalize. For `design-system-color`, hex / `rgb()` / `hsl()` forms that parse to the same RGBA also match. Value-bearing rules include `overused-font`, `bounce-easing`, `design-system-font`, `design-system-color`, `design-system-radius`, `design-system-font-size`. File-scoped `value: "*"` also suppresses rules with no extractable value (for example `side-tab`).
</ParamField>

<ParamField body="detector.designSystem.enabled" type="boolean" default="true">
When `false`, design-system rules are not loaded for CLI or hook scans even if DESIGN.md / sidecar tokens exist.
</ParamField>

<ParamField body="detector.advisoryRules" type="\"include\" \| \"exclude\"" default="exclude">
Advisory rules (for example `em-dash-overuse`) never fail the CLI. The design hook **skips** them unless set to `"include"`.
</ParamField>

<ParamField body="detector.extensions" type="array" default="[]">
Extra markup/template suffixes beyond the built-in list. Config **adds** only; built-ins always apply.

Each entry is `{ "ext": ".blade.php", "engine": "html" }` or a bare string (engine defaults to `html`). `engine` is `html` or `text`. Matching uses **filename suffix** (longest wins), so `.blade.php` and `.html.erb` work. Live wrap/accept also merges these into its template search set.
</ParamField>

### `hook` keys

Runtime for the design edit hook only.

<ParamField body="hook.enabled" type="boolean" default="true">
When `false`, automatic hook scans do not run. Manual `npx impeccable detect` still runs and still honors `detector.*`. Env override: `IMPECCABLE_HOOK_DISABLED` (truthy).
</ParamField>

<ParamField body="hook.quiet" type="boolean" default="false">
When `true`, suppresses clean/pending acks; findings still surface. Env override: `IMPECCABLE_HOOK_QUIET`.
</ParamField>

<ParamField body="hook.consent" type="\"accepted\" \| \"declined\"">
Install / enable decision recorded by the CLI into **local** config. Local always wins over shared. Not a runtime scan filter.
</ParamField>

<ParamField body="hook.auditLog" type="string \| null" default="null">
Optional path for NDJSON audit entries. Env `IMPECCABLE_HOOK_LOG` overrides when set.
</ParamField>

<ParamField body="hook.perEditRules" type="\"immediate\" \| \"all\"" default="immediate">
`immediate` surfaces only the high-urgency rule tier on each edit; the Stop deep pass (where wired) runs the full set. `all` restores full rules on every edit.
</ParamField>

<ParamField body="hook.limits.maxFindings" type="number" default="5">
Cap on findings rendered per emission (must be finite and &gt; 0).
</ParamField>

<ParamField body="hook.limits.maxChars" type="number" default="8000">
Cap on rendered reminder size.
</ParamField>

<ParamField body="hook.limits.maxFileBytes" type="number" default="131072">
Skip scanning a single file larger than this (bundle / generated artifact guard).
</ParamField>

<Note>
Native platforms (`ios` / `android` / `adaptive` in PRODUCT.md) skip the design hook scan entirely. Detector rules are web-shaped; native projects still use native skill references.
</Note>

## Hard path excludes (hook)

These cannot be disabled by config. The hook never scans matching paths:

| Guard | What it skips |
|-------|----------------|
| Sensitive path regex | `.env*`, `.git/…`, key/PEM material, `*secret*` / `*credential*` config-like names |
| Generated path regex | `node_modules`, `dist` / `build` / `out` / `.next` / `.cache` / `coverage`, `generated/` segment, `*.min.*`, `*.d.ts`, lockfiles, `*.generated.*` |

`detector.ignoreFiles` is additive on top of these floors.

## Inline disable comments

Separate from JSON config. In source, comments like:

```text
impeccable-disable <rule>[, <rule>...]
impeccable-disable-line <rule>...
impeccable-disable-next-line <rule>...
```

are honored by default on detect. Bypass with CLI `--no-inline-ignores` or `--no-config`. Prefer config ignores for team-visible waivers; use inline only when the waiver must travel with an exported file.

## Live config (`.impeccable/live/config.json`)

Path resolution (`skill/scripts/lib/impeccable-paths.mjs`):

1. `IMPECCABLE_LIVE_CONFIG` if set (absolute or cwd-relative)
2. Else `.impeccable/live/config.json` if present
3. Else legacy `skill/scripts/config.json` if present when scripts dir is known
4. Else primary path (may not exist yet; setup creates it)

### Fields

```json
{
  "files": ["index.html"],
  "exclude": ["email/**/*.html"],
  "insertBefore": "</body>",
  "commentSyntax": "html",
  "cspChecked": true
}
```

<ParamField body="files" type="string[]" required>
Non-empty list of project-root-relative paths or globs for the HTML/shell files the browser loads. Not necessarily source-of-truth components; inject targets are what the browser actually fetches.
</ParamField>

<ParamField body="exclude" type="string[]">
Optional globs subtracted from expanded `files` (fixtures, email templates).
</ParamField>

<ParamField body="insertBefore" type="string">
Required unless `insertAfter` is set. Anchor string for script-tag insertion (usually `</body>`).
</ParamField>

<ParamField body="insertAfter" type="string">
Alternative anchor: insert after a matching line (for example TanStack Start `<Scripts`).
</ParamField>

<ParamField body="commentSyntax" type="\"html\" \| \"jsx\"" required>
Comment markers for inject bookkeeping (`html` vs JSX-style).
</ParamField>

<ParamField body="cspChecked" type="boolean">
Records that the first-time CSP consent step ran. Absent on first setup; set `true` after the user is asked (whether they accept or decline the patch). Delete the key to re-run CSP setup.
</ParamField>

Validation errors from `live-inject.mjs` include missing/empty `files`, missing insert anchor, invalid `commentSyntax`, or non-boolean `cspChecked`.

### Live hard excludes

Always applied; **not** overridable by `exclude` or any other flag:

| Pattern | Reason |
|---------|--------|
| `**/node_modules/**` | Would instrument third-party packages |
| `**/.git/**` | Not a page surface |

User `exclude` is appended after these.

### Framework-oriented examples

| Stack | Typical `files` | Anchor | `commentSyntax` |
|-------|-----------------|--------|-----------------|
| Vite / SPA shell | `["index.html"]` | `</body>` | `html` |
| Next App Router | `["app/layout.tsx"]` | `</body>` | `jsx` |
| Next Pages | `["pages/_document.tsx"]` | `</body>` | `jsx` |
| Nuxt | `["app.vue"]` | `</body>` | `html` |
| SvelteKit | `["src/app.html"]` | `</body>` | `html` |
| TanStack Start | `["src/routes/__root.tsx"]` | `<Scripts` | `jsx` |
| Multipage | `["public/**/*.html"]` | `</body>` | `html` |

Some frameworks route inject through adapters (SvelteKit root component, Nuxt client plugin, TanStack Start root component). `files` remains the detection/CSP hint even when the literal insertion site differs.

## Example configs

### Shared team detector filters

```json
{
  "detector": {
    "ignoreRules": [],
    "ignoreFiles": ["src/fixtures/**", "**/*.generated.tsx"],
    "ignoreValues": [
      {
        "rule": "overused-font",
        "value": "Avenir Next",
        "reason": "Brand primary typeface"
      },
      {
        "rule": "side-tab",
        "value": "*",
        "files": ["**/TopicCard.jsx"],
        "reason": "Intentional left accent"
      }
    ],
    "designSystem": { "enabled": true },
    "advisoryRules": "exclude"
  },
  "hook": {
    "enabled": true,
    "perEditRules": "immediate"
  }
}
```

### Local developer overrides

```json
{
  "updateCheck": false,
  "hook": {
    "consent": "accepted",
    "quiet": true
  },
  "detector": {
    "ignoreValues": [
      {
        "rule": "bounce-easing",
        "value": "bounce-ball",
        "reason": "Local playground only"
      }
    ]
  }
}
```

### Blade / server templates

```json
{
  "detector": {
    "extensions": [
      { "ext": ".blade.php", "engine": "html" },
      { "ext": ".html.twig", "engine": "html" }
    ]
  }
}
```

## Managing config without hand-editing

| Goal | Preferred tool |
|------|----------------|
| Detector ignores CRUD | `npx impeccable ignores …` or `/impeccable hooks ignore-*` |
| Hook on/off / status | `/impeccable hooks on\|off\|status` → `hook-admin.mjs` |
| Live inject setup | Live boot + `live-setup` flow writing `.impeccable/live/config.json` |
| Schema / drift repair | `/impeccable doctor` (and `doctor --fix` for auto severities) |

Hand-edit is fine for rare fields with no admin action (`detector.extensions`, top-level `projectRoots`, `updateCheck`, `stalenessCheck`). Keep JSON valid; prefer admin scripts for ignore lists so normalization and exclude markers stay consistent.

## Environment overrides (summary)

Full list lives on the environment variables page. Config-adjacent vars:

| Variable | Effect |
|----------|--------|
| `IMPECCABLE_NO_UPDATE_CHECK` | Disable update check regardless of config |
| `IMPECCABLE_NO_STALENESS_CHECK` | Disable staleness boot check |
| `IMPECCABLE_STALENESS_CACHE` | Override `~/.impeccable/staleness-check.json` path |
| `IMPECCABLE_UPDATE_HOST` / `IMPECCABLE_UPDATE_CACHE` | Update poll host and cache file |
| `IMPECCABLE_LIVE_CONFIG` | Override live config path |
| `IMPECCABLE_HOOK_DISABLED` / `IMPECCABLE_HOOK_QUIET` / `IMPECCABLE_HOOK_LOG` | Override hook enabled / quiet / audit log |
| `IMPECCABLE_CONTEXT_DIR` | Alternate context root for PRODUCT/DESIGN resolution |

## Troubleshooting

| Symptom | Check |
|---------|--------|
| Local ignores not applied | Confirm `.impeccable/config.local.json` is valid JSON; shared+local both merge for lists |
| Hook still quiet after config change | Env `IMPECCABLE_HOOK_QUIET` or `IMPECCABLE_HOOK_DISABLED` overrides config |
| CLI finds issues the hook does not | `hook.enabled: false` only affects the hook; compare `perEditRules` / advisory / designSystem; Stop deep pass may own deferred rules |
| Live inject misses pages | Expand `files` or globs; `configDrift.orphans` on boot lists uncovered HTML under common roots |
| Live blocked by CSP | Delete `cspChecked` and re-run live setup; ensure `localhost:8400` is allowed in dev CSP |
| Consent / local file committed | Writers use `.git/info/exclude`, not tracked `.gitignore`; ensure exclude marker block is present |

## Related pages

<CardGroup>
  <Card title="Manage detector ignores" href="/manage-detector-ignores">
    Add, list, and remove ignoreRules, ignoreFiles, and ignoreValues in shared or local config.
  </Card>
  <Card title="Design hook" href="/design-hook">
    Provider edit hooks, quiet/disabled controls, and native-platform skip behavior.
  </Card>
  <Card title="Live browser iteration" href="/live-mode">
    Live mode lifecycle, roots, and poll contract.
  </Card>
  <Card title="Live framework recipes" href="/live-framework-recipes">
    Copy-paste live config shapes for Vite, Next, Nuxt, SvelteKit, and more.
  </Card>
  <Card title="Environment variables" href="/environment-variables">
    IMPECCABLE_* overrides for context, update, live, hook, and telemetry.
  </Card>
  <Card title="Doctor" href="/doctor">
    Schema and config drift findings, auto fixes, and staleness opt-out.
  </Card>
  <Card title="CLI reference" href="/cli-reference">
    detect, ignores, and related flags including --no-config.
  </Card>
  <Card title="Project artifacts" href="/project-artifacts">
    PRODUCT.md, DESIGN.md, sidecar, and the rest of `.impeccable/`.
  </Card>
</CardGroup>
