A starting point for an agent-first analog design repo. The parts every design shares — run
ledger, lints, context pack, spec checks — come from the SpiceXplorer platform package
spicexplorer-harness. This repo holds only what is specific to one design: harness.yaml,
the docs under doc/, the design/ code that builds decks and measures them, and the agent
definitions and method notes under .claude/.
export SX_ROOT=<your spicexplorer-workspace checkout>(the lab's~/.sx_envdoes this), then get the tree. Prefermake newinmacanalog-design-directory: it names the repo by the lab convention, creates it from the template on GitHub, clones it and runs themake initof step 2 for you. Otherwise copy the tree by hand. A tree copied from a commit that is not a release tag also records that commit —git -C <template checkout> rev-parse HEAD > .sx/template-commit— somake template-updatemerges from it and not from the tag.sx/template-versionnames.make init: it links.sx/platform -> $SX_ROOT/spicexplorer-platform, initialises the.sx/skillslibrary (analog-skill-directory) with its agent/skill links, and runsuv sync. Repeat in every new checkout or worktree.- Fill
harness.yaml,pyproject.toml(name, description) and every<…>inCLAUDE.mdanddoc/. Declare the process inharness.yaml(pdk: ihp-sg13g2, as the kit's own preflight reports it) and runmake initagain: when the library ships apdk-<id>link set for that process, it links those skills too.make lintwarns whilepdk:is empty. - Rename the design package:
git mv design <name>, setpackage:inharness.yaml, and update the imports plusMakefileandexperiments/_template/. The package is named for the DESIGN — the instances areldo/andmzm_tx/.make lintchecks the package is importable;layout/signoff.pyandscripts/lint.pyresolve it frompackage:and need no edit. - The harness, core and waveview are editable path dependencies through
.sx/platform(make initmade the link; a read-onlySX_ROOTis fine). A design that pulls in a furtherspicexplorer-*member names it in bothdependenciesand[tool.uv.sources], then runsuv synconce. make doctormust report the lane alive: it runs a one-resistor deck with a per-run.spiceinitthrough ngspice, driven bydesign/sim_ngspice.py— this repo's policy layer over the platform'srun_deck, and whatdesign/sim.pyre-exports whilelane:is absent. A commercial-PDK design instead setslane: bridgeinharness.yaml, fills indesign/pdk.py(REVISION,SECTIONS, the two variable names) and adds the bridge-lane package topyproject.toml; itsmake doctorruns the same probe on the EDA server. Then implementdesign/dut.py(benches()+deck(bench)) anddesign/metrics.py'sKEYMAP.make testcovers the generic modules and the scorecard lifecycle.make certify(addARGS="--author X --verified-by Y"once a second actor has re-measured it), add the dir tofrozen:and the scorecard toreference_scorecard:, thenmake freeze.make lintmust pass before the first experiment.make guardis the one command that runs it withmake testand a clean-tree check before a push;make hook-installis the recommended per-clone step that attaches it togit push(see below).- For the layout lane, uncomment the
spicexplorer-gmid/-layout/-signoffsources inpyproject.tomlanduv sync.
The workflow is feat/<name> branch → PR → squash (CLAUDE.md, "Parallel sessions, blast radius,
git"). make guard is what makes it safe to automate: it refuses unless the tree is clean — both
git diff and git diff --cached, because staged-but-uncommitted is exactly the window a
&& chain slips through — and make lint and make test are green, printing one REFUSING: line
naming the clause that failed. GUARD_SKIP_TEST=1 make guard drops only the test clause and says
so, for a design whose suite is too slow to sit in front of every push.
make hook-install installs a pre-push hook that runs it (make hook-remove uninstalls). It is
opt-in — make init does not install it — because a hook that blocks ordinary work gets
removed, and the only hook this repo ships by default is the NDA kit-tree ask-hook, which asks
rather than blocks. The hook honours core.hooksPath, never overwrites a pre-push you wrote,
and its refusal names both deliberate bypasses (git push --no-verify, GUARD_SKIP_TEST=1 git push). Linked worktrees share the hooks directory, so one install covers every worktree of the
clone. make lint reports whether the hook is there as an INFO line; it is never a failure.
Three kinds of directory, and the difference between them is the whole organizing idea:
experiments/ is where work happens, signoff/ is what survived it, and doc/ is what was
learned. Everything else is plumbing.
| path | what |
|---|---|
harness.yaml |
the design described to the harness: spec rows, frozen dirs, denylist, ledger columns |
CLAUDE.md |
the entry map agents read first — including "Where things go" |
doc/ |
target spec, design reference (constraints), benches, environment, experiment log, journal + index, reviews/ (verifier reports), the memory model |
design/ |
this design's own code. Yours to write: dut (sizing point → deck), metrics (measure, check, log), bench (the reductions). Generic, imported as-is: sim (the lane dispatcher — lane: in harness.yaml selects sim_ngspice, the default policy layer over spicexplorer_core.spice_engine.run_deck, or sim_bridge + pdk for a commercial kit simulated on the EDA server), exp (labelled batches, markdown, CSV), plot (spec boxes). Data stimulus and eye metrics are the platform's — spicexplorer_waveview.stimulus / .eye — imported directly by the designs that send symbols |
experiments/NNN-*/ |
one directory per hypothesis, tagged with its phase; _template/ is the shape — README.md, run.py (simulates into git-ignored out/, committed figs/ and tables/) and mk_readme.py (regenerates the README from out/*.json) |
signoff/ |
the design of record, one directory per simulation fidelity (prelayout, postlayout-pex, postlayout-em, and whatever physics a design adds) plus schematic/ and layout/. signoff/README.md is the index: what is signed off, at which fidelity, by whom, when |
layout/ |
the layout as code: PLAN.template.md (the plan a designer copies to layout/<cell>/PLAN.md before any geometry), gen_cell.py (open-lane generator contract, LayoutParams, the per-net obstacle map), gen_cell_bridge.py (the lane: bridge generator skeleton: plan(params, kit) -> LayoutPlan, every kit fact read from $SX_KIT_FILE by name) and signoff.py (build → render → DRC → current density → LVS → PEX → the cell's own benches on the open lane; build → one batch DRC/LVS/PEX on the EDA server → the cell's own benches on the extracted DSPF on lane: bridge). Its output lands in signoff/layout/ |
references/ |
papers, datasheets and standards + INDEX.md (cite by handle, never by filename) |
tests/ |
make test: the generic design/ modules (the live-lane test skips without ngspice) |
scripts/lint.py |
repo-specific checks on top of the harness — including artifact-home, which keeps the map above true |
scripts/clean_runs.py |
make clean-runs (sweep the run dirs whose reduction is recorded and whose log has gone cold) and the scratch report make doctor prints |
scripts/githook.py |
make hook-install / make hook-remove: the opt-in pre-push hook that runs make guard |
scripts/session_pack.py |
the SessionStart hook: the bare make pack output, cut to memory.pack_budget; silent and exit 0 on any error or without a venv |
scripts/pdk_links.py |
the step of make init and make skills-update that links the library's pdk-<id> skills for the pdk: declared in harness.yaml, and the private per-kit skill library $SX_KIT_SKILLS names (each link written to that clone's info/exclude, never committed) |
scripts/layout_lane.py |
the arguments make layout-flow hands workflows.layout for the lane: in harness.yaml: the open lane's generator, or on lane: bridge the bridge generator, the literal $SX_KIT_FILE, OA_LIB and WORKAREA (else $<PREFIX>_OA_LIB and $<PREFIX>_WORKAREA, the variables layout/signoff.py reads), and --load for LOAD=1 or else --skip drc,lvs,pex,benches (doc/environment.md, row OA library); it exits 2 before the workflow starts when one is missing |
notebooks/ |
executed in place by make notebooks, outputs committed |
.sx/ |
the per-checkout plumbing make init sets up: platform (git-ignored link to $SX_ROOT/spicexplorer-platform) and skills (the analog-skill-directory submodule: shared agents, skills, guard hooks, bin/sx-link) |
.claude/agents/ |
links into .sx/skills/agents/ (the library's design link set, 15 agents): variant-runner, signoff-verifier, schematic-builder, schematic-reviewer, paper-analyst, gardener, the layout chain (brief-author, designer, reviewer, schematic-codesign), the four use-case agents (design-tutor, troubleshooter, design-customizer, results-analyst) and pr-reviewer; design-specific agents are plain files beside them |
.claude/skills/ |
links into .sx/skills/skills/ (the design link set, 27 skills): the visual-evidence methods (schematic of record, testbench schematics, findings as plots, layout evidence), measurement-setup-of-record (the measurement traps of the lane, the harness and the experiment process), design-writing (every document this repo produces), analog-knowledge-authoring (the standard a knowledge skill is written to), gm/ID sizing + LUTs, current mirrors, layout annotation, optimizer convergence plots, neutral naming, spicexplorer-tools, sx-contributing, sx-coding-style, sx-use-cases, hand-analysis, postlayout-analysis (a post-layout metric shift attributed to nets), the remote-simulator learning journal, the bridge-lane skill and the bridge's two simulator skills, and the bridge layout lane's two skills (the commercial-kit layout lane, and the review setup of record in the kit's editor) and the bridge lane's checkpoint skill (the incremental uploads to the design's OA library), and agent-run-hygiene (how an agent runs long jobs: no kit text on a command line, waits on a pid, a stall check); plus the library's pdk-<id> set when pdk: in harness.yaml names a process it has one for |
.claude/settings.json |
a copy of the library's settings file (.sx/skills/settings/project-settings.json): the one PreToolUse hook (.sx/skills/hooks/cmc_ask_hook.py), under which any tool call that names a path in the NDA kit tree, an MCP tool call included, asks for permission and nothing else is blocked (owner ruling 2026-09-07), and the permissions a design session needs. The template adds one SessionStart hook: scripts/session_pack.py prints the bare context pack, cut to memory.pack_budget, into each new session (doc/memory/README.md §1) |
runs/ |
ledger.ndjson, git-ignored; the numbers worth keeping move into experiment READMEs and signoff/ |
| (the scratch root) | rawfiles, run dirs and simulator logs — $SX_SCRATCH, never the repo. A raw record is scratch, not evidence: reduce it, commit the reduction, make clean-runs |