/** * `.vigilesrc.json`, AS A SCHEMA — the one place the config's shape, its * defaults and its error messages live. * * 🔴 WHY A SCHEMA AND NOT THE HAND-WRITTEN CHECKS IT REPLACED. `loadConfig` used * to coerce the keys it happened to remember (`asStringArray` on three of them), * spread everything else through untouched, and say nothing at all about a key * it had never heard of. MEASURED on this repo's own CLI before the change: * * ``` * $ echo '{"surfaceRootz":[".ai"]}' > .vigilesrc.json && vigiles audit . * (no complaint about the unknown key — exit 0) * ``` * * A key the tool does not read is a line the user believes is working. That is * the product's own subject — a passing signal standing in for work nobody did — * happening inside the tool, so the shape is now DECLARED and anything outside * it is named out loud. * * THE TYPE IS DERIVED FROM THIS, not written beside it: `VigilesConfigOf` is * the static keys' `z.output` plus the rule set's own entries (see `./types.ts`), * so a field cannot exist in the type and not in the validator, which is how * `surfaceRoots` ended up documented in `docs/cli.md` for a week after it * stopped being read. * * THE RULES ARE A PARAMETER. The `rules` object, its defaults and the presets * `extends` accepts are BUILT from the rule set a composition root hands to * {@link configFor} — each rule declares its id, default and options schema in * its own module (`src/rules/`), and this file names none of them. Every key * the loaded config is guaranteed to carry (`rules`, `files`, `ruleMarkers`) * carries a Zod `.default(...)`, so parsing `{}` yields every rule at its * shipped default (`src/rules/index.test.ts` pins that snapshot). * * ⚠️ ZOD COSTS ~45 ms TO IMPORT (measured, Zod 4.6.5), AND IT IS IMPORTED * NORMALLY — `src/core/validate.ts` has a top-level `import`, not a deferred * `require`. The deferral was tried and is recorded there rather than here, * because the reason it was dropped is a property of the two worlds this code * runs in, not of this file. What matters here: the genuinely hot rail — a * compiled hook's decision, `vigiles hook-runtime run-program`, one fresh * process per matching tool call — never loads the verb barrel and therefore * never loads this module (`src/cli.ts` branches first; * `src/hook-runtime-graph.test.ts` fails the day that stops being true). The * rails that DO load the barrel already pay ~316 ms of Node startup and ~85 * requires, against which 45 ms is ~13%. */ import { z } from "zod"; import { type RuleGroups, type RuleSpec } from "./rule.js"; import type { RuleSeverity, RuleWithOptions } from "./types.js"; /** The config file; the directory holding it is a project root. */ export declare const PROJECT_CONFIG_FILE = ".vigilesrc.json"; /** * A string, or a string ARRAY — coerced to an array. * * The bare-string case is the natural first-value mistake and it used to spread * a string's CHARACTERS as globs: `"exclude": "bench"` became * `["b","e","n","c","h"]`, which is a no-op at best and garbage `orphan` matches * (`.`, `/`, `README.md`) at worst. Accepting it as a one-element array is what * `asStringArray` did; the difference is that anything that is neither is now a * schema error instead of a `console.warn` nobody reads. */ export declare const stringList: z.ZodPipe]>, z.ZodTransform>; /** * ONE harness's entry in the config's `harnesses`. * * `.strict()` is load-bearing here specifically: the whole reason this key * exists is that a declaration which reaches nothing used to be silent, and * `{"claude-code": {"root": ".ai"}}` (singular, no `s`) reaches nothing. */ declare const harnessDeclarationSchema: z.ZodObject<{ roots: z.ZodOptional]>, z.ZodTransform>>; }, z.core.$strict>; /** The harness keys' value shape, exported so `types.ts` can derive the type. */ export type HarnessDeclarationShape = z.infer; /** What counts as a rule in markdown, by default: both constructs. */ export declare const DEFAULT_RULE_MARKERS: readonly ["headings", "checkboxes"]; /** * The keys of `.vigilesrc.json` that do not depend on the rule set, in the * order the "Known: …" list prints them (it is capped, so the keys people * misspell most come first). `rules`, `overrides` and `extends` are built from * the rule set by {@link configFor} and slotted in around these. */ declare const configKeys: { /** Which markdown constructs count as a rule — headings, checkboxes, or both. */ ruleMarkers: z.ZodDefault>>; /** The instruction files to validate. */ files: z.ZodDefault>; maxRules: z.ZodOptional; maxTokens: z.ZodOptional; maxSectionLines: z.ZodOptional; catalogOnly: z.ZodOptional; linters: z.ZodOptional]>>; }, z.core.$strict>>>; bundles: z.ZodOptional>; orphans: z.ZodOptional]>, z.ZodTransform>>; }, z.core.$strict>>; exclude: z.ZodOptional]>, z.ZodTransform>>; }; /** The keys after `overrides`; `extends` and `$schema` close the shape. */ declare const configTailKeys: { sharedDirs: z.ZodOptional]>, z.ZodTransform>>; harnesses: z.ZodOptional]>, z.ZodTransform>>; }, z.core.$strict>>>; audit: z.ZodOptional; }, z.core.$strict>>; eval: z.ZodOptional; }, z.core.$strict>>; nudge: z.ZodOptional>; }; /** * The editor's pointer at the published JSON Schema. Accepted, never read. * * 🔴 IT IS DECLARED HERE RATHER THAN EXCUSED IN THE UNKNOWN-KEY WALKER, and * the reason is that the walker is only half the surface. `dist/vigilesrc. * schema.json` is generated FROM this schema by * `scripts/build-config-schema.mjs`, with `additionalProperties: false`, so * a key missing here is refused TWICE: once by the CLI, and once by the * editor being pointed at the schema. Measured before this key existed, on * `{"$schema": , "harnesses": {"claude-code": {}}}`: * * ``` * $ vigiles audit . --no-interactive * ✗ .vigilesrc.json: unknown key "$schema" in (top level). Known: … * (exit 2) * $ # and the same document against dist/vigilesrc.schema.json: * additionalProperties: should NOT have additional properties ($schema) * ``` * * It is LAST in the shape on purpose: `knownKeysAt` reads the schema to build * the "Known: …" candidate list, which is capped, so a key nobody misspells * belongs past the cap rather than at the head of the suggestion. */ declare const schemaPointer: z.ZodOptional; /** The keys that do not depend on the rule set, as one object type. */ type StaticConfig = z.output>; /** One rule's entry in `rules`: a severity, or `[severity, options]`. */ export type RuleEntry = RuleWithOptions; /** The `rules` of a config, keyed by rule id. */ export type RulesConfig = { readonly [K in Id]: RuleEntry; }; /** The config entry a rule takes: with its options' type when it has options. */ type EntryOf = S extends { readonly options: infer O extends z.ZodType; } ? RuleWithOptions> : RuleSeverity; /** * The PRECISE `rules` of a rule set — each id with its own options type. The * composition root instantiates it over `RULES`; generic code takes * {@link RulesConfig} and never names an id (the `local/no-rule-ids` fence). */ export type RulesConfigOf = { readonly [S in R[number] as S["id"]]: EntryOf; }; /** * The whole parsed config: the static keys as the schema defines them, plus * the rule set's `rules` and `overrides`. Exported from here and re-exported * (type-only) by `./types.ts`; a key cannot be in the type and absent from the * validator. */ export type VigilesConfigOf = StaticConfig & { rules: Rules; overrides?: readonly { readonly files: readonly string[]; readonly rules: { readonly [K in keyof Rules]?: RuleSeverity; }; }[]; extends?: string | readonly string[]; }; /** A parse of a raw config: the config, or the issues that refused it. */ export type ConfigParse = { readonly success: true; readonly data: C; } | { readonly success: false; readonly error: { readonly issues: readonly ConfigIssue[]; }; }; /** * `.vigilesrc.json` for one rule set — the schema, its presets and its * defaults, BUILT from the rules a composition root hands in. `C` is the parsed * config's type: precise (`VigilesConfigOf>`) at * the root, string-keyed in generic code. */ export interface ConfigSet { /** The whole schema — for the JSON Schema build and the error messages. */ readonly schema: z.ZodType; /** Every rule id the schema accepts, in the set's order. */ readonly ruleNames: readonly string[]; /** The preset names `extends` and `--preset=` accept, from the groups. */ readonly presetNames: readonly string[]; /** * Parse a raw `.vigilesrc.json` value — THE one entry point every reader uses. * * 🔴 THE PRESET IS MERGED INTO THE RAW INPUT, BEFORE THE SCHEMA: after * parsing, every rule carries a value (the schema fills each default), so * "the user wrote `hook-events: warn`" and "nobody wrote it" are the same * output, and an explicit downgrade could not win over the preset. Before * parsing, the user's own `rules` keys are exactly what they wrote, so the * preset's severities go UNDER them. Only a recognized preset with an object * (or absent) `rules` is expanded; anything else is passed through for the * schema to refuse with its own message. * * `extraPresets` — `audit`/`lint --preset=` for one run — go after the * file's own presets (so they win over them) and never over its explicit * `rules`. They are not written into the parsed `extends`: that key stays * what the FILE says. */ parse(raw: unknown, extraPresets?: readonly string[]): ConfigParse; } /** * Build `.vigilesrc.json` for a rule set: each rule's key with its default and, * for a rule with `options`, the `[severity, options]` form; `overrides` over * the same ids; `extends` over the groups' preset names. * * The one place Zod cannot carry the type: an object built from * `Object.fromEntries` has string keys, so the parse result is narrowed to the * set's precise type by `complete`, which checks at run time that every id is * present — never by an assertion. */ export declare function configFor(rules: R, groups: RuleGroups): ConfigSet>>; /** * Parse the values of `--preset=` (each may be a comma list) into preset * names, through the SAME enum `extends` validates with, so the config file * and the command line refuse an unknown name with the same message. The * `vigiles:` prefix is optional on the command line: `--preset=experimental` * and `--preset=vigiles:experimental` are the same preset. */ export declare function parsePresetFlags(known: readonly string[], values: readonly string[]): { readonly ok: true; readonly presets: readonly string[]; } | { readonly ok: false; readonly message: string; }; /** * A `.vigilesrc.json` that cannot be honoured as written. * * 🔴 IT HAS ITS OWN CLASS BECAUSE THE READER CATCHES EVERYTHING ELSE. A missing * file, an unreadable one and malformed JSON all mean "use the defaults", which * is right — and a file that IS readable and says something we refuse must not * join them, or the user's declaration vanishes into the defaults and the run * looks clean. A distinct class is what lets the CLI print it as a config error * and a hook rail downgrade it to a warning, from one throw site. */ export declare class VigilesConfigError extends Error { readonly name = "VigilesConfigError"; } /** * Turn a Zod failure into the lines a human acts on — ONE per real problem. * * 🔴 THE FORMATTER IS THE POINT, because the library's own message is worse than * what it replaced on the two things that matter. Measured on Zod 4.6.5 against * this schema: * * ``` * {"rules":{"spec-refs":"errr"}} * -> invalid_union, EIGHT branch errors: expected "warn" / "error" / false / * "off" / 0 / 1 / 2 / true — one line per union member, none of them the * sentence "these are the values this key takes" * {"harnessez":{}} * -> Unrecognized key: "harnessez" (names the culprit, suggests nothing) * ``` * * The first is CASCADE NOISE: a union failure is one problem, not eight, and * printing the branches makes the schema's internals the user's problem. The * second is the regression we refuse to ship — the line it would replace is * `✗ Unknown harness "claud-code". Known: claude-code, codex.`, which names the * candidates AND the near-miss. So a union collapses to one line listing what * the key accepts, and an unknown key carries the candidate list plus a * distance-bounded "did you mean". */ export declare function formatConfigIssues(schema: Readonly, issues: readonly ConfigIssue[]): string[]; /** * The shape of a Zod issue this formatter reads — structural, not imported. * * Zod's own `$ZodIssue` union is exhaustive over every code the library can * emit, and switching on it would make adding a schema construct a compile * error in a message formatter that has a perfectly good default branch. The * fields below are the ones read; everything else falls through to `message`. */ export interface ConfigIssue { readonly code?: string; readonly path?: readonly PropertyKey[]; readonly message?: string; readonly keys?: readonly string[]; readonly expected?: string; readonly values?: readonly unknown[]; readonly errors?: ReadonlyArray; } export {}; //# sourceMappingURL=config-schema.d.ts.map