zudo-circuit-doc

Type to search...

to open search from anywhere

circuit.config.ts

Every field of the CircuitConfig contract, its default, and the validation errors the loader raises.

circuit.config.ts sits at the project root and is loaded by dynamic import, relying on Node's native TypeScript type stripping — no bundler, no build step. Its canonical form:

import type { CircuitConfig } from "@takazudo/zudo-circuit-doc/config";

export default {
  configVersion: 1,
  // ...
} satisfies CircuitConfig;

The type-only import is erased by type stripping, so the file loads in a fresh scaffold before pnpm install. defineCircuitConfig() also exists as an optional identity helper for the same shape.

Every path field is relative to this file's own directory, and every path is rejected if it's absolute, uses backslashes, or escapes the directory with ... The loader resolves all of them to absolute paths before anything else runs, so "absent file" and "empty file" stay distinguishable in one place (the CLI/adapter), never confused inside the schema check itself.

Top-level fields

FieldRequiredDefaultNotes
configVersionYes—Must be exactly 1
rootNo"."The project data root; used as the validator's projectRoot/cwd, the default cad.footprintPathBase, and the base for paths stored inside evidence data (for example, inventoryProvider generator spec paths)
projectYes—{ name, title }, both non-empty strings
docsYes—See below
evidenceYes—See below
inventoryProviderYes—{ kind: "manual" } or { kind: "led-generator-v1", specs: [...] } — see Concepts → Inventory profiles
publicationYes—See below
cadYes—{ enabled: false, libraryName? } or the full enabled shape — see below
validationNo{}See below
scanNoundefined (no overrides)ScanPolicyConfig overrides for scan thresholds and expected counts
browserSmokeNoundefined{ representatives: [...] }

docs

FieldRequiredDefaultNotes
rootYes—The zudo-doc site directory, e.g. "doc"
generatedContentYes—Where generated component MDX is written. The route /docs/components/ itself is fixed in v1
preflightYes—Where generate writes the preflight report JSON
publicRootYes—zudo-doc's public/ directory; previews go under <publicRoot>/assets/component-previews/{footprints,models}
distYes—The built-site directory check-built/scan/check-browser read by default
agentResourcesNotrueGenerated pages link /docs/claude/ and /docs/claude-skills/<owner>/ (needs zudo-doc's claudeResources)
generatedMarkerNothe neutral markerA custom generated-page marker string (single line)
generatedNoticeNotrue, unless generatedMarker is a legacy marker and this is unset (then false)Whether generated pages carry the generated-page notice paragraph. Set it explicitly to decouple the notice from the marker, e.g. a custom marker with no notice, or a legacy marker with a notice
integrationGlossNonullAn optional JSON file { domain: text }

evidence

FieldRequiredDefaultNotes
contractVersionYes—Must be exactly 1
bundlesRootYes—e.g. .claude/skills
ownerPrefixYes—e.g. component-; must be a single path segment
auditSkillYes—Directory name under bundlesRoot, e.g. component-spec-audit
integrationSkillYes—Directory name under bundlesRoot, e.g. circuit-spec-integration
inventoryYes—Path to inventory.json
integrationRulesYes—Path to rules.json
directRoutingYes—Path to direct-routing.json
vendorQualifiersYes—Path to external-vendor-qualifiers.json
forwardTestsNonullPath to forward-tests.json, or null/omitted
sourceCacheYes—e.g. .circuit-cache/sources (git-ignored working cache)

inventoryProvider

{ kind: "manual" }
// or
{ kind: "led-generator-v1", specs: [{ path: "scripts/schgen/board_p_spec.py" }, ...] }

kind must be one of the two registered values; led-generator-v1's specs array must be non-empty. See Concepts → Inventory profiles.

publication

FieldRequiredDefaultNotes
selectionYes—Path to selection.json — see Publication schemas
assetsYes—Path to assets.json — see Publication schemas
matrixNopackage preset component-evidence-v1Path to a custom FieldKey → PUBLISH | DENY matrix JSON

cad

Discriminated on enabled:

Disabled (the default for a new project):

{ enabled: false, libraryName?: string }

Enabled:

FieldRequiredDefaultNotes
enabledYes—true
libraryNameYes—The KiCad library name
symbolLibrariesYes—Non-empty array of .kicad_sym paths
footprintMasterRootYes—Where footprints are authored/merged
footprintLibraryRootYes—The .pretty library directory footprints must also match byte-for-byte
modelRootYes—The .3dshapes directory
modelLocatorPrefixYes—A KiCad model locator prefix such as ${KIPRJMOD}/../../footprints/kicad/<lib>.3dshapes/ — not a filesystem path
footprintPathBaseNorootBase for footprint paths recorded in preview manifests
previewRendererYes—{ image, version, platform, layers[], theme, options[] } — the pinned KiCad container and render settings
limitsNosee belowPartial override of the byte-size limits

A project doesn't need to invent its own previewRenderer: the package exports DEFAULT_PREVIEW_RENDERER, which most projects can reuse rather than re-typing the pinned image digest:

import type { CircuitConfig } from "@takazudo/zudo-circuit-doc/config";
import { DEFAULT_PREVIEW_RENDERER } from "@takazudo/zudo-circuit-doc/config";

export default {
  // ...
  cad: {
    enabled: true,
    libraryName: "my-project",
    symbolLibraries: ["symbols/my-project.kicad_sym"],
    footprintMasterRoot: "footprints/kicad",
    footprintLibraryRoot: "footprints/kicad/my-project.pretty",
    modelRoot: "footprints/kicad/my-project.3dshapes",
    modelLocatorPrefix: "${KIPRJMOD}/../../footprints/kicad/my-project.3dshapes/",
    previewRenderer: DEFAULT_PREVIEW_RENDERER,
  },
} satisfies CircuitConfig;

DEFAULT_PREVIEW_RENDERER (type PreviewRendererConfig) currently pins:

{
  image: "kicad/kicad@sha256:e638b79b0321f29395a5b783e94bb9f3c73303e8da15da27b8f5cb4b67a37729",
  version: "9.0.9",
  platform: "linux/amd64",
  layers: ["F.Cu", "F.Silkscreen", "F.Fabrication", "F.Courtyard"],
  theme: "KiCad Default",
  options: ["--black-and-white"],
}

pnpm previews:generate (footprints generate) needs Docker to pull and run that image; without it you'll see footprint preview root is missing; run \pnpm previews:generate`orfootprint preview manifest is missing; run `pnpm previews:generate``. STEP files are optional cosmetic input to CAD authoring — see Concepts → CAD fidelity for what's actually required (WRL) versus optional (STEP).

These reference limits can be overridden per key under cad.limits:

LimitDefault
footprintBytes512 KiB
modelBytes2 MiB
aggregateModelBytes8 MiB

validation

FieldRequiredDefaultNotes
pythonMinVersionNo"3.10""MAJOR.MINOR"; must be ≥ 3.10
policyNonullPath to a data-driven project policy JSON — see Schemas → validation.policy for the shape and every check type
userAgentNonullSent as the User-Agent header for online source fetches

scan (overrides only)

minimumOwnedCanaries, minimumOwnedFiles, minimumSiteCanaries, minimumSiteFiles, expectedWithheld (all non-negative integers), and positiveControlRecord (a slug, or null to pick one deterministically). Omitting scan entirely means "derive a proportional policy from the resolved config" — a project supplies nothing and gets a proportional policy based on its own configuration.

browserSmoke

{ representatives: [{ kind, path, slug, identity, availability? }, ...] }. path must be a site route starting with /. For example:

{
  "representatives": [
    { "kind": "passive", "path": "/docs/components/records/resistor-10k/", "slug": "resistor-10k", "identity": "RES-10K-0603" },
    { "kind": "IC", "path": "/docs/components/records/example-regulator/", "slug": "example-regulator", "identity": "EXAMPLE-REGULATOR" },
    { "kind": "connector", "path": "/docs/components/records/example-header/", "slug": "example-header", "identity": "EXAMPLE-HEADER-2X3" }
  ]
}

representatives is what check-browser exercises: the first entry whose built page publishes a component-references section is the deep-interaction page (dialogs, SPA navigation, forced failures); every entry with references is checked at every viewport/theme.

Leaving browserSmoke unset is a supported default, not an oversight. With no config value and no --representatives flag, check-browser derives up to 3 representatives itself, in this order:

  1. Read the preflight report's published record slugs, sorted.

  2. For each slug (site not yet built), a record qualifies when its generated page (<generatedContent>/records/<slug>/index.mdx) has a ComponentReferences descriptor that decodes to a reviewed PDF label, an http(s) URL, a non-empty authority, a footprint and a WRL model, followed by at least one <EvidenceFact> and a ## Sources heading.

  3. If the site is already built, a qualifying record must also have its built page carry the zcd-component-references marker, with the footprint SVG and model WRL present in docs.dist.

  4. The first 3 qualifying slugs (in sorted order) become derived representatives: { kind: "derived <slug>", identity: <page title, i.e. the MPN>, path: "/docs/components/records/<slug>/" }.

Zero published records is a legitimate "declared-zero project" and still exits 0 (nothing to smoke). Published records that exist but none of which qualifies exits 4 — see CLI → check-browser for the exact messages.

Validation errors

Every loader failure is a ConfigError, mapped by the CLI to exit 2:

CodeMeaning
CONFIG_NOT_FOUNDNo circuit.config.ts at the resolved path
CONFIG_LOAD_FAILEDThe file exists but throws or fails to import
CONFIG_INVALIDThe file loaded, but its shape fails validation
NODE_VERSION_UNSUPPORTEDThe running Node version can't type-strip the config

A CONFIG_INVALID error lists every issue at once (never just the first one), each with its dotted field path and message. For example, a config missing docs.dist and cad.enabled, with a stray top-level extraTypo key, reports all three together:

CONFIG_INVALID: docs.dist is required; cad.enabled is required; extraTypo unknown key
(allowed: configVersion, root, project, docs, evidence, inventoryProvider, publication,
cad, validation, scan, browserSmoke)

Unknown keys anywhere in the shape are reported by name, listing every allowed key at that level — a typo in a field name fails loudly instead of being silently ignored.

Revision History

CreatedUpdated